summaryrefslogtreecommitdiff
path: root/includes
diff options
context:
space:
mode:
Diffstat (limited to 'includes')
-rw-r--r--includes/Action.php57
-rw-r--r--includes/AjaxDispatcher.php10
-rw-r--r--includes/AjaxResponse.php2
-rw-r--r--includes/ArrayUtils.php2
-rw-r--r--includes/Article.php147
-rw-r--r--includes/AuthPlugin.php13
-rw-r--r--includes/AutoLoader.php147
-rw-r--r--includes/Autopromote.php5
-rw-r--r--includes/Block.php308
-rw-r--r--includes/CallableUpdate.php30
-rw-r--r--includes/Category.php3
-rw-r--r--includes/CategoryPage.php8
-rw-r--r--includes/CategoryViewer.php60
-rw-r--r--includes/Cdb.php4
-rw-r--r--includes/Cdb_PHP.php16
-rw-r--r--includes/ChangeTags.php75
-rw-r--r--includes/ChangesFeed.php37
-rw-r--r--includes/ChangesList.php1304
-rw-r--r--includes/Collation.php22
-rw-r--r--includes/ConfEditor.php55
-rw-r--r--includes/Cookie.php7
-rw-r--r--includes/DataUpdate.php4
-rw-r--r--includes/DefaultSettings.php1309
-rw-r--r--includes/DeferredUpdates.php12
-rw-r--r--includes/Defines.php11
-rw-r--r--includes/EditPage.php290
-rw-r--r--includes/Exception.php275
-rw-r--r--includes/Export.php33
-rw-r--r--includes/ExternalEdit.php132
-rw-r--r--includes/ExternalUser.php309
-rw-r--r--includes/Fallback.php28
-rw-r--r--includes/Feed.php26
-rw-r--r--includes/FeedUtils.php27
-rw-r--r--includes/FileDeleteForm.php36
-rw-r--r--includes/ForkController.php4
-rw-r--r--includes/FormOptions.php163
-rw-r--r--includes/GitInfo.php39
-rw-r--r--includes/GlobalFunctions.php675
-rw-r--r--includes/HTMLForm.php541
-rw-r--r--includes/HashRing.php142
-rw-r--r--includes/HistoryBlob.php35
-rw-r--r--includes/Hooks.php282
-rw-r--r--includes/Html.php179
-rw-r--r--includes/HtmlFormatter.php356
-rw-r--r--includes/HttpFunctions.php118
-rw-r--r--includes/IP.php32
-rw-r--r--includes/ImagePage.php49
-rw-r--r--includes/ImageQueryPage.php25
-rw-r--r--includes/Import.php135
-rw-r--r--includes/Init.php150
-rw-r--r--includes/Licenses.php2
-rw-r--r--includes/LinkFilter.php4
-rw-r--r--includes/Linker.php103
-rw-r--r--includes/LinksUpdate.php154
-rw-r--r--includes/MWCryptRand.php (renamed from includes/CryptRand.php)2
-rw-r--r--includes/MWFunction.php34
-rw-r--r--includes/MagicWord.php23
-rw-r--r--includes/MappedIterator.php88
-rw-r--r--includes/Message.php212
-rw-r--r--includes/Metadata.php28
-rw-r--r--includes/MimeMagic.php88
-rw-r--r--includes/Namespace.php6
-rw-r--r--includes/OutputHandler.php80
-rw-r--r--includes/OutputPage.php476
-rw-r--r--includes/PHPVersionError.php12
-rw-r--r--includes/PageQueryPage.php5
-rw-r--r--includes/Pager.php140
-rw-r--r--includes/PathRouter.php10
-rw-r--r--includes/PoolCounter.php221
-rw-r--r--includes/Preferences.php541
-rw-r--r--includes/PrefixSearch.php40
-rw-r--r--includes/ProtectionForm.php110
-rw-r--r--includes/ProxyTools.php40
-rw-r--r--includes/QueryPage.php92
-rw-r--r--includes/Revision.php265
-rw-r--r--includes/RevisionList.php5
-rw-r--r--includes/Sanitizer.php361
-rw-r--r--includes/ScopedCallback.php41
-rw-r--r--includes/SeleniumWebSettings.php221
-rw-r--r--includes/Setup.php153
-rw-r--r--includes/SiteConfiguration.php58
-rw-r--r--includes/SiteStats.php126
-rw-r--r--includes/Skin.php202
-rw-r--r--includes/SkinLegacy.php882
-rw-r--r--includes/SkinTemplate.php346
-rw-r--r--includes/SpecialPage.php160
-rw-r--r--includes/SpecialPageFactory.php15
-rw-r--r--includes/SqlDataUpdate.php2
-rw-r--r--includes/StatCounter.php150
-rw-r--r--includes/Status.php91
-rw-r--r--includes/StreamFile.php1
-rw-r--r--includes/StringUtils.php135
-rw-r--r--includes/StubObject.php25
-rw-r--r--includes/Timestamp.php267
-rw-r--r--includes/Title.php528
-rw-r--r--includes/UIDGenerator.php35
-rw-r--r--includes/User.php1151
-rw-r--r--includes/UserArray.php2
-rw-r--r--includes/UserMailer.php53
-rw-r--r--includes/UserRightsProxy.php10
-rw-r--r--includes/WatchedItem.php65
-rw-r--r--includes/WebRequest.php187
-rw-r--r--includes/WebResponse.php121
-rw-r--r--includes/WebStart.php52
-rw-r--r--includes/Wiki.php145
-rw-r--r--includes/WikiError.php2
-rw-r--r--includes/WikiFilePage.php4
-rw-r--r--includes/WikiMap.php20
-rw-r--r--includes/WikiPage.php632
-rw-r--r--includes/Xml.php221
-rw-r--r--includes/XmlTypeCheck.php86
-rw-r--r--includes/ZhClient.php6
-rw-r--r--includes/ZipDirectoryReader.php2
-rw-r--r--includes/actions/CreditsAction.php15
-rw-r--r--includes/actions/EditAction.php14
-rw-r--r--includes/actions/HistoryAction.php51
-rw-r--r--includes/actions/InfoAction.php69
-rw-r--r--includes/actions/PurgeAction.php4
-rw-r--r--includes/actions/RawAction.php29
-rw-r--r--includes/actions/WatchAction.php62
-rw-r--r--includes/api/ApiBase.php119
-rw-r--r--includes/api/ApiBlock.php2
-rw-r--r--includes/api/ApiComparePages.php8
-rw-r--r--includes/api/ApiCreateAccount.php52
-rw-r--r--includes/api/ApiDelete.php3
-rw-r--r--includes/api/ApiEditPage.php59
-rw-r--r--includes/api/ApiExpandTemplates.php4
-rw-r--r--includes/api/ApiFeedContributions.php15
-rw-r--r--includes/api/ApiFeedWatchlist.php90
-rw-r--r--includes/api/ApiFormatBase.php8
-rw-r--r--includes/api/ApiFormatJson.php24
-rw-r--r--includes/api/ApiFormatWddx.php75
-rw-r--r--includes/api/ApiFormatXml.php137
-rw-r--r--includes/api/ApiImageRotate.php32
-rw-r--r--includes/api/ApiImport.php6
-rw-r--r--includes/api/ApiMain.php36
-rw-r--r--includes/api/ApiMove.php4
-rw-r--r--includes/api/ApiOpenSearch.php21
-rw-r--r--includes/api/ApiOptions.php8
-rw-r--r--includes/api/ApiPageSet.php18
-rw-r--r--includes/api/ApiParamInfo.php6
-rw-r--r--includes/api/ApiParse.php157
-rw-r--r--includes/api/ApiPatrol.php46
-rw-r--r--includes/api/ApiProtect.php3
-rw-r--r--includes/api/ApiPurge.php29
-rw-r--r--includes/api/ApiQuery.php10
-rw-r--r--includes/api/ApiQueryAllCategories.php4
-rw-r--r--includes/api/ApiQueryAllImages.php4
-rw-r--r--includes/api/ApiQueryAllLinks.php116
-rw-r--r--includes/api/ApiQueryAllMessages.php2
-rw-r--r--includes/api/ApiQueryAllPages.php7
-rw-r--r--includes/api/ApiQueryAllUsers.php4
-rw-r--r--includes/api/ApiQueryBacklinks.php15
-rw-r--r--includes/api/ApiQueryBase.php4
-rw-r--r--includes/api/ApiQueryBlocks.php61
-rw-r--r--includes/api/ApiQueryCategories.php5
-rw-r--r--includes/api/ApiQueryCategoryMembers.php6
-rw-r--r--includes/api/ApiQueryDeletedrevs.php6
-rw-r--r--includes/api/ApiQueryDuplicateFiles.php23
-rw-r--r--includes/api/ApiQueryExtLinksUsage.php6
-rw-r--r--includes/api/ApiQueryExternalLinks.php8
-rw-r--r--includes/api/ApiQueryFileRepoInfo.php115
-rw-r--r--includes/api/ApiQueryFilearchive.php8
-rw-r--r--includes/api/ApiQueryIWBacklinks.php4
-rw-r--r--includes/api/ApiQueryIWLinks.php13
-rw-r--r--includes/api/ApiQueryImageInfo.php80
-rw-r--r--includes/api/ApiQueryInfo.php20
-rw-r--r--includes/api/ApiQueryLangBacklinks.php9
-rw-r--r--includes/api/ApiQueryLangLinks.php17
-rw-r--r--includes/api/ApiQueryLogEvents.php24
-rw-r--r--includes/api/ApiQueryORM.php4
-rw-r--r--includes/api/ApiQueryPagesWithProp.php2
-rw-r--r--includes/api/ApiQueryProtectedTitles.php2
-rw-r--r--includes/api/ApiQueryQueryPage.php19
-rw-r--r--includes/api/ApiQueryRandom.php4
-rw-r--r--includes/api/ApiQueryRecentChanges.php43
-rw-r--r--includes/api/ApiQueryRevisions.php29
-rw-r--r--includes/api/ApiQuerySearch.php39
-rw-r--r--includes/api/ApiQuerySiteinfo.php62
-rw-r--r--includes/api/ApiQueryTags.php7
-rw-r--r--includes/api/ApiQueryUserContributions.php21
-rw-r--r--includes/api/ApiQueryUserInfo.php20
-rw-r--r--includes/api/ApiQueryUsers.php6
-rw-r--r--includes/api/ApiQueryWatchlist.php95
-rw-r--r--includes/api/ApiQueryWatchlistRaw.php4
-rw-r--r--includes/api/ApiRsd.php2
-rw-r--r--includes/api/ApiSetNotificationTimestamp.php7
-rw-r--r--includes/api/ApiUpload.php84
-rw-r--r--includes/api/ApiUserrights.php7
-rw-r--r--includes/api/ApiWatch.php11
-rw-r--r--includes/cache/BacklinkCache.php134
-rw-r--r--includes/cache/CacheDependency.php2
-rw-r--r--includes/cache/FileCacheBase.php6
-rw-r--r--includes/cache/GenderCache.php14
-rw-r--r--includes/cache/HTMLCacheUpdate.php11
-rw-r--r--includes/cache/HTMLFileCache.php4
-rw-r--r--includes/cache/LinkBatch.php4
-rw-r--r--includes/cache/LinkCache.php68
-rw-r--r--includes/cache/LocalisationCache.php96
-rw-r--r--includes/cache/MessageCache.php729
-rw-r--r--includes/cache/ResourceFileCache.php2
-rw-r--r--includes/cache/SquidUpdate.php197
-rw-r--r--includes/cache/UserCache.php13
-rw-r--r--includes/changes/ChangesList.php552
-rw-r--r--includes/changes/EnhancedChangesList.php662
-rw-r--r--includes/changes/OldChangesList.php130
-rw-r--r--includes/changes/RCCacheEntry.php35
-rw-r--r--includes/changes/RecentChange.php (renamed from includes/RecentChange.php)232
-rw-r--r--includes/clientpool/RedisConnectionPool.php90
-rw-r--r--includes/content/Content.php14
-rw-r--r--includes/content/ContentHandler.php19
-rw-r--r--includes/content/CssContent.php2
-rw-r--r--includes/content/JavaScriptContent.php2
-rw-r--r--includes/content/TextContent.php2
-rw-r--r--includes/content/WikitextContent.php3
-rw-r--r--includes/content/WikitextContentHandler.php19
-rw-r--r--includes/context/ContextSource.php2
-rw-r--r--includes/context/DerivativeContext.php25
-rw-r--r--includes/context/IContextSource.php2
-rw-r--r--includes/context/RequestContext.php19
-rw-r--r--includes/db/ChronologyProtector.php106
-rw-r--r--includes/db/CloneDatabase.php18
-rw-r--r--includes/db/Database.php517
-rw-r--r--includes/db/DatabaseError.php202
-rw-r--r--includes/db/DatabaseMssql.php190
-rw-r--r--includes/db/DatabaseMysql.php933
-rw-r--r--includes/db/DatabaseMysqlBase.php1154
-rw-r--r--includes/db/DatabaseMysqli.php194
-rw-r--r--includes/db/DatabaseOracle.php92
-rw-r--r--includes/db/DatabasePostgres.php98
-rw-r--r--includes/db/DatabaseSqlite.php49
-rw-r--r--includes/db/DatabaseUtility.php3
-rw-r--r--includes/db/IORMRow.php24
-rw-r--r--includes/db/LBFactory.php94
-rw-r--r--includes/db/LBFactory_Multi.php6
-rw-r--r--includes/db/LoadBalancer.php104
-rw-r--r--includes/db/LoadMonitor.php27
-rw-r--r--includes/db/ORMRow.php182
-rw-r--r--includes/db/ORMTable.php211
-rw-r--r--includes/debug/Debug.php74
-rw-r--r--includes/diff/DairikiDiff.php40
-rw-r--r--includes/diff/DifferenceEngine.php205
-rw-r--r--includes/extauth/Hardcoded.php84
-rw-r--r--includes/extauth/MediaWiki.php168
-rw-r--r--includes/extauth/vB.php146
-rw-r--r--includes/externalstore/ExternalStore.php57
-rw-r--r--includes/externalstore/ExternalStoreDB.php142
-rw-r--r--includes/externalstore/ExternalStoreMedium.php19
-rw-r--r--includes/externalstore/ExternalStoreMwstore.php23
-rw-r--r--includes/filebackend/FSFile.php29
-rw-r--r--includes/filebackend/FSFileBackend.php117
-rw-r--r--includes/filebackend/FileBackend.php143
-rw-r--r--includes/filebackend/FileBackendGroup.php28
-rw-r--r--includes/filebackend/FileBackendMultiWrite.php179
-rw-r--r--includes/filebackend/FileBackendStore.php576
-rw-r--r--includes/filebackend/FileOp.php193
-rw-r--r--includes/filebackend/FileOpBatch.php6
-rw-r--r--includes/filebackend/README2
-rw-r--r--includes/filebackend/SwiftFileBackend.php262
-rw-r--r--includes/filebackend/TempFSFile.php6
-rw-r--r--includes/filebackend/filejournal/DBFileJournal.php10
-rw-r--r--includes/filebackend/lockmanager/DBLockManager.php37
-rw-r--r--includes/filebackend/lockmanager/LockManager.php125
-rw-r--r--includes/filebackend/lockmanager/LockManagerGroup.php4
-rw-r--r--includes/filebackend/lockmanager/MemcLockManager.php41
-rw-r--r--includes/filebackend/lockmanager/QuorumLockManager.php140
-rw-r--r--includes/filebackend/lockmanager/RedisLockManager.php288
-rw-r--r--includes/filebackend/lockmanager/ScopedLock.php44
-rw-r--r--includes/filerepo/FSRepo.php14
-rw-r--r--includes/filerepo/FileRepo.php108
-rw-r--r--includes/filerepo/ForeignAPIRepo.php226
-rw-r--r--includes/filerepo/ForeignDBRepo.php5
-rw-r--r--includes/filerepo/LocalRepo.php42
-rw-r--r--includes/filerepo/RepoGroup.php12
-rw-r--r--includes/filerepo/file/ArchivedFile.php23
-rw-r--r--includes/filerepo/file/File.php37
-rw-r--r--includes/filerepo/file/ForeignAPIFile.php49
-rw-r--r--includes/filerepo/file/ForeignDBFile.php5
-rw-r--r--includes/filerepo/file/LocalFile.php234
-rw-r--r--includes/filerepo/file/OldLocalFile.php3
-rw-r--r--includes/gallery/ImageGalleryBase.php (renamed from includes/ImageGallery.php)239
-rw-r--r--includes/gallery/NolinesImageGallery.php38
-rw-r--r--includes/gallery/PackedImageGallery.php105
-rw-r--r--includes/gallery/PackedOverlayImageGallery.php60
-rw-r--r--includes/gallery/TraditionalImageGallery.php328
-rw-r--r--includes/installer/CliInstaller.php9
-rw-r--r--includes/installer/DatabaseInstaller.php64
-rw-r--r--includes/installer/DatabaseUpdater.php156
-rw-r--r--includes/installer/InstallDocFormatter.php12
-rw-r--r--includes/installer/Installer.i18n.php1959
-rw-r--r--includes/installer/Installer.php298
-rw-r--r--includes/installer/LocalSettingsGenerator.php59
-rw-r--r--includes/installer/MysqlInstaller.php135
-rw-r--r--includes/installer/MysqlUpdater.php516
-rw-r--r--includes/installer/OracleInstaller.php61
-rw-r--r--includes/installer/OracleUpdater.php72
-rw-r--r--includes/installer/PhpBugTests.php2
-rw-r--r--includes/installer/PostgresInstaller.php62
-rw-r--r--includes/installer/PostgresUpdater.php575
-rw-r--r--includes/installer/SqliteInstaller.php38
-rw-r--r--includes/installer/SqliteUpdater.php121
-rw-r--r--includes/installer/WebInstaller.php208
-rw-r--r--includes/installer/WebInstallerOutput.php112
-rw-r--r--includes/installer/WebInstallerPage.php259
-rw-r--r--includes/job/Job.php56
-rw-r--r--includes/job/JobQueue.php355
-rw-r--r--includes/job/JobQueueDB.php688
-rw-r--r--includes/job/JobQueueFederated.php473
-rw-r--r--includes/job/JobQueueGroup.php90
-rw-r--r--includes/job/JobQueueRedis.php856
-rw-r--r--includes/job/aggregator/JobQueueAggregator.php (renamed from includes/job/JobQueueAggregator.php)17
-rw-r--r--includes/job/aggregator/JobQueueAggregatorMemc.php (renamed from includes/job/JobQueueAggregatorMemc.php)11
-rw-r--r--includes/job/aggregator/JobQueueAggregatorRedis.php (renamed from includes/job/JobQueueAggregatorRedis.php)28
-rw-r--r--includes/job/jobs/AssembleUploadChunksJob.php19
-rw-r--r--includes/job/jobs/DoubleRedirectJob.php29
-rw-r--r--includes/job/jobs/DuplicateJob.php2
-rw-r--r--includes/job/jobs/EnotifNotifyJob.php2
-rw-r--r--includes/job/jobs/HTMLCacheUpdateJob.php25
-rw-r--r--includes/job/jobs/NullJob.php16
-rw-r--r--includes/job/jobs/PublishStashedFileJob.php22
-rw-r--r--includes/job/jobs/RefreshLinksJob.php28
-rw-r--r--includes/job/jobs/UploadFromUrlJob.php7
-rw-r--r--includes/json/FormatJson.php217
-rw-r--r--includes/json/Services_JSON.php882
-rw-r--r--includes/libs/CSSJanus.php72
-rw-r--r--includes/libs/CSSMin.php47
-rw-r--r--includes/libs/HttpStatus.php2
-rw-r--r--includes/libs/lessc.inc.php3742
-rw-r--r--includes/limit.sh9
-rw-r--r--includes/logging/DeleteLogFormatter.php196
-rw-r--r--includes/logging/LogEntry.php55
-rw-r--r--includes/logging/LogEventsList.php82
-rw-r--r--includes/logging/LogFormatter.php412
-rw-r--r--includes/logging/LogPage.php62
-rw-r--r--includes/logging/LogPager.php77
-rw-r--r--includes/logging/MoveLogFormatter.php82
-rw-r--r--includes/logging/NewUsersLogFormatter.php65
-rw-r--r--includes/logging/PatrolLog.php7
-rw-r--r--includes/logging/PatrolLogFormatter.php63
-rw-r--r--includes/logging/RightsLogFormatter.php112
-rw-r--r--includes/media/BMP.php6
-rw-r--r--includes/media/Bitmap.php47
-rw-r--r--includes/media/BitmapMetadataHandler.php24
-rw-r--r--includes/media/DjVu.php21
-rw-r--r--includes/media/DjVuImage.php44
-rw-r--r--includes/media/Exif.php76
-rw-r--r--includes/media/ExifBitmap.php8
-rw-r--r--includes/media/FormatMetadata.php101
-rw-r--r--includes/media/GIF.php8
-rw-r--r--includes/media/GIFMetadataExtractor.php46
-rw-r--r--includes/media/IPTC.php8
-rw-r--r--includes/media/ImageHandler.php23
-rw-r--r--includes/media/Jpeg.php20
-rw-r--r--includes/media/JpegMetadataExtractor.php22
-rw-r--r--includes/media/MediaHandler.php149
-rw-r--r--includes/media/MediaTransformOutput.php53
-rw-r--r--includes/media/PNG.php13
-rw-r--r--includes/media/PNGMetadataExtractor.php30
-rw-r--r--includes/media/SVG.php92
-rw-r--r--includes/media/SVGMetadataExtractor.php64
-rw-r--r--includes/media/Tiff.php2
-rw-r--r--includes/media/XCF.php6
-rw-r--r--includes/media/XMP.php26
-rw-r--r--includes/media/XMPInfo.php6
-rw-r--r--includes/media/XMPValidate.php12
-rw-r--r--includes/mime.info4
-rw-r--r--includes/mime.types5
-rw-r--r--includes/normal/README10
-rw-r--r--includes/normal/RandomTest.php6
-rw-r--r--includes/normal/UtfNormal.php4
-rw-r--r--includes/normal/UtfNormalTest.php8
-rw-r--r--includes/normal/UtfNormalTest2.php2
-rw-r--r--includes/normal/UtfNormalUtil.php8
-rw-r--r--includes/objectcache/BagOStuff.php8
-rw-r--r--includes/objectcache/MemcachedBagOStuff.php2
-rw-r--r--includes/objectcache/MemcachedClient.php24
-rw-r--r--includes/objectcache/MemcachedPeclBagOStuff.php14
-rw-r--r--includes/objectcache/MultiWriteBagOStuff.php2
-rw-r--r--includes/objectcache/ObjectCache.php14
-rw-r--r--includes/objectcache/ObjectCacheSessionHandler.php2
-rw-r--r--includes/objectcache/RedisBagOStuff.php40
-rw-r--r--includes/objectcache/SqlBagOStuff.php22
-rw-r--r--includes/parser/CacheTime.php2
-rw-r--r--includes/parser/CoreLinkFunctions.php92
-rw-r--r--includes/parser/CoreParserFunctions.php197
-rw-r--r--includes/parser/CoreTagHooks.php2
-rw-r--r--includes/parser/DateFormatter.php24
-rw-r--r--includes/parser/LinkHolderArray.php216
-rw-r--r--includes/parser/Parser.php848
-rw-r--r--includes/parser/ParserCache.php20
-rw-r--r--includes/parser/ParserOptions.php21
-rw-r--r--includes/parser/ParserOutput.php111
-rw-r--r--includes/parser/Parser_DiffTest.php2
-rw-r--r--includes/parser/Parser_LinkHooks.php326
-rw-r--r--includes/parser/Preprocessor_DOM.php99
-rw-r--r--includes/parser/Preprocessor_Hash.php61
-rw-r--r--includes/parser/Tidy.php11
-rw-r--r--includes/profiler/Profiler.php264
-rw-r--r--includes/profiler/ProfilerSimple.php5
-rw-r--r--includes/profiler/ProfilerSimpleText.php12
-rw-r--r--includes/profiler/ProfilerSimpleTrace.php2
-rw-r--r--includes/profiler/ProfilerSimpleUDP.php12
-rw-r--r--includes/profiler/ProfilerStub.php2
-rw-r--r--includes/rcfeed/IRCColourfulRCFeedFormatter.php99
-rw-r--r--includes/rcfeed/JSONRCFeedFormatter.php90
-rw-r--r--includes/rcfeed/RCFeedEngine.php12
-rw-r--r--includes/rcfeed/RCFeedFormatter.php13
-rw-r--r--includes/rcfeed/RedisPubSubFeedEngine.php41
-rw-r--r--includes/rcfeed/UDPRCFeedEngine.php10
-rw-r--r--includes/resourceloader/ResourceLoader.php181
-rw-r--r--includes/resourceloader/ResourceLoaderContext.php2
-rw-r--r--includes/resourceloader/ResourceLoaderFileModule.php176
-rw-r--r--includes/resourceloader/ResourceLoaderLESSFunctions.php67
-rw-r--r--includes/resourceloader/ResourceLoaderLanguageDataModule.php31
-rw-r--r--includes/resourceloader/ResourceLoaderModule.php102
-rw-r--r--includes/resourceloader/ResourceLoaderSiteModule.php23
-rw-r--r--includes/resourceloader/ResourceLoaderStartUpModule.php10
-rw-r--r--includes/resourceloader/ResourceLoaderUserCSSPrefsModule.php67
-rw-r--r--includes/resourceloader/ResourceLoaderUserGroupsModule.php15
-rw-r--r--includes/resourceloader/ResourceLoaderUserModule.php21
-rw-r--r--includes/resourceloader/ResourceLoaderUserOptionsModule.php4
-rw-r--r--includes/resourceloader/ResourceLoaderUserTokensModule.php9
-rw-r--r--includes/resourceloader/ResourceLoaderWikiModule.php4
-rw-r--r--includes/revisiondelete/RevisionDelete.php86
-rw-r--r--includes/revisiondelete/RevisionDeleteAbstracts.php50
-rw-r--r--includes/revisiondelete/RevisionDeleteUser.php2
-rw-r--r--includes/revisiondelete/RevisionDeleter.php149
-rw-r--r--includes/search/SearchEngine.php169
-rw-r--r--includes/search/SearchMssql.php6
-rw-r--r--includes/search/SearchMySQL.php66
-rw-r--r--includes/search/SearchOracle.php86
-rw-r--r--includes/search/SearchPostgres.php43
-rw-r--r--includes/search/SearchSqlite.php39
-rw-r--r--includes/search/SearchUpdate.php123
-rw-r--r--includes/site/MediaWikiSite.php8
-rw-r--r--includes/site/SiteSQLStore.php52
-rw-r--r--includes/specials/SpecialActiveusers.php74
-rw-r--r--includes/specials/SpecialAllmessages.php143
-rw-r--r--includes/specials/SpecialAllpages.php174
-rw-r--r--includes/specials/SpecialAncientpages.php22
-rw-r--r--includes/specials/SpecialBlankpage.php1
-rw-r--r--includes/specials/SpecialBlock.php49
-rw-r--r--includes/specials/SpecialBlockList.php52
-rw-r--r--includes/specials/SpecialBlockme.php66
-rw-r--r--includes/specials/SpecialBooksources.php49
-rw-r--r--includes/specials/SpecialBrokenRedirects.php12
-rw-r--r--includes/specials/SpecialCachedPage.php1
-rw-r--r--includes/specials/SpecialCategories.php38
-rw-r--r--includes/specials/SpecialChangeEmail.php67
-rw-r--r--includes/specials/SpecialChangePassword.php134
-rw-r--r--includes/specials/SpecialComparePages.php11
-rw-r--r--includes/specials/SpecialConfirmemail.php101
-rw-r--r--includes/specials/SpecialContributions.php380
-rw-r--r--includes/specials/SpecialDeadendpages.php25
-rw-r--r--includes/specials/SpecialDeletedContributions.php179
-rw-r--r--includes/specials/SpecialDisambiguations.php165
-rw-r--r--includes/specials/SpecialDoubleRedirects.php31
-rw-r--r--includes/specials/SpecialEditWatchlist.php174
-rw-r--r--includes/specials/SpecialEmailuser.php82
-rw-r--r--includes/specials/SpecialExport.php143
-rw-r--r--includes/specials/SpecialFewestrevisions.php53
-rw-r--r--includes/specials/SpecialFileDuplicateSearch.php51
-rw-r--r--includes/specials/SpecialFilepath.php62
-rw-r--r--includes/specials/SpecialImport.php245
-rw-r--r--includes/specials/SpecialJavaScriptTest.php56
-rw-r--r--includes/specials/SpecialLinkSearch.php71
-rw-r--r--includes/specials/SpecialListfiles.php301
-rw-r--r--includes/specials/SpecialListgrouprights.php51
-rw-r--r--includes/specials/SpecialListredirects.php34
-rw-r--r--includes/specials/SpecialListusers.php113
-rw-r--r--includes/specials/SpecialLog.php79
-rw-r--r--includes/specials/SpecialLonelypages.php48
-rw-r--r--includes/specials/SpecialLongpages.php1
-rw-r--r--includes/specials/SpecialMIMEsearch.php97
-rw-r--r--includes/specials/SpecialMergeHistory.php118
-rw-r--r--includes/specials/SpecialMostcategories.php48
-rw-r--r--includes/specials/SpecialMostimages.php19
-rw-r--r--includes/specials/SpecialMostinterwikis.php32
-rw-r--r--includes/specials/SpecialMostlinked.php69
-rw-r--r--includes/specials/SpecialMostlinkedcategories.php32
-rw-r--r--includes/specials/SpecialMostlinkedtemplates.php39
-rw-r--r--includes/specials/SpecialMovepage.php263
-rw-r--r--includes/specials/SpecialNewimages.php40
-rw-r--r--includes/specials/SpecialNewpages.php156
-rw-r--r--includes/specials/SpecialPagesWithProp.php32
-rw-r--r--includes/specials/SpecialPasswordReset.php94
-rw-r--r--includes/specials/SpecialPopularpages.php34
-rw-r--r--includes/specials/SpecialPreferences.php26
-rw-r--r--includes/specials/SpecialPrefixindex.php147
-rw-r--r--includes/specials/SpecialProtectedpages.php169
-rw-r--r--includes/specials/SpecialProtectedtitles.php80
-rw-r--r--includes/specials/SpecialRandomInCategory.php291
-rw-r--r--includes/specials/SpecialRandompage.php31
-rw-r--r--includes/specials/SpecialRandomredirect.php1
-rw-r--r--includes/specials/SpecialRecentchanges.php410
-rw-r--r--includes/specials/SpecialRecentchangeslinked.php63
-rw-r--r--includes/specials/SpecialRedirect.php235
-rw-r--r--includes/specials/SpecialResetTokens.php145
-rw-r--r--includes/specials/SpecialRevisiondelete.php170
-rw-r--r--includes/specials/SpecialSearch.php198
-rw-r--r--includes/specials/SpecialShortpages.php18
-rw-r--r--includes/specials/SpecialSpecialpages.php17
-rw-r--r--includes/specials/SpecialStatistics.php37
-rw-r--r--includes/specials/SpecialTags.php69
-rw-r--r--includes/specials/SpecialUnblock.php22
-rw-r--r--includes/specials/SpecialUncategorizedcategories.php6
-rw-r--r--includes/specials/SpecialUncategorizedimages.php2
-rw-r--r--includes/specials/SpecialUncategorizedpages.php13
-rw-r--r--includes/specials/SpecialUndelete.php680
-rw-r--r--includes/specials/SpecialUnusedcategories.php15
-rw-r--r--includes/specials/SpecialUnusedimages.php16
-rw-r--r--includes/specials/SpecialUnusedtemplates.php16
-rw-r--r--includes/specials/SpecialUnwatchedpages.php16
-rw-r--r--includes/specials/SpecialUpload.php126
-rw-r--r--includes/specials/SpecialUploadStash.php29
-rw-r--r--includes/specials/SpecialUserlogin.php369
-rw-r--r--includes/specials/SpecialUserrights.php162
-rw-r--r--includes/specials/SpecialVersion.php203
-rw-r--r--includes/specials/SpecialWantedcategories.php18
-rw-r--r--includes/specials/SpecialWantedfiles.php16
-rw-r--r--includes/specials/SpecialWantedtemplates.php14
-rw-r--r--includes/specials/SpecialWatchlist.php192
-rw-r--r--includes/specials/SpecialWhatlinkshere.php69
-rw-r--r--includes/specials/SpecialWithoutinterwiki.php28
-rw-r--r--includes/templates/NoLocalSettings.php14
-rw-r--r--includes/templates/Usercreate.php445
-rw-r--r--includes/templates/Userlogin.php278
-rw-r--r--includes/tidy.conf2
-rw-r--r--includes/upload/UploadBase.php202
-rw-r--r--includes/upload/UploadFromChunks.php26
-rw-r--r--includes/upload/UploadFromFile.php2
-rw-r--r--includes/upload/UploadFromStash.php6
-rw-r--r--includes/upload/UploadFromUrl.php49
-rw-r--r--includes/upload/UploadStash.php29
534 files changed, 37258 insertions, 22541 deletions
diff --git a/includes/Action.php b/includes/Action.php
index 2e0c88ba..4b6e4468 100644
--- a/includes/Action.php
+++ b/includes/Action.php
@@ -59,7 +59,7 @@ abstract class Action {
* the action is disabled, or null if it's not recognised
* @param $action String
* @param $overrides Array
- * @return bool|null|string
+ * @return bool|null|string|callable
*/
final private static function getClass( $action, array $overrides ) {
global $wgActions;
@@ -89,12 +89,18 @@ abstract class Action {
* if it is not recognised
*/
final public static function factory( $action, Page $page, IContextSource $context = null ) {
- $class = self::getClass( $action, $page->getActionOverrides() );
- if ( $class ) {
- $obj = new $class( $page, $context );
+ $classOrCallable = self::getClass( $action, $page->getActionOverrides() );
+
+ if ( is_string( $classOrCallable ) ) {
+ $obj = new $classOrCallable( $page, $context );
return $obj;
}
- return $class;
+
+ if ( is_callable( $classOrCallable ) ) {
+ return call_user_func_array( $classOrCallable, array( $page, $context ) );
+ }
+
+ return $classOrCallable;
}
/**
@@ -136,7 +142,7 @@ abstract class Action {
return 'view';
}
- $action = Action::factory( $actionName, $context->getWikiPage() );
+ $action = Action::factory( $actionName, $context->getWikiPage(), $context );
if ( $action instanceof Action ) {
return $action->getName();
}
@@ -161,8 +167,14 @@ abstract class Action {
final public function getContext() {
if ( $this->context instanceof IContextSource ) {
return $this->context;
+ } else if ( $this->page instanceof Article ) {
+ // NOTE: $this->page can be a WikiPage, which does not have a context.
+ wfDebug( __METHOD__ . ': no context known, falling back to Article\'s context.' );
+ return $this->page->getContext();
}
- return $this->page->getContext();
+
+ wfWarn( __METHOD__ . ': no context known, falling back to RequestContext::getMain().' );
+ return RequestContext::getMain();
}
/**
@@ -213,7 +225,7 @@ abstract class Action {
/**
* Shortcut to get the user Language being used for this instance
*
- * @deprecated 1.19 Use getLanguage instead
+ * @deprecated since 1.19 Use getLanguage instead
* @return Language
*/
final public function getLang() {
@@ -241,12 +253,20 @@ abstract class Action {
}
/**
- * Protected constructor: use Action::factory( $action, $page ) to actually build
- * these things in the real world
+ * Constructor.
+ *
+ * Only public since 1.21
+ *
* @param $page Page
* @param $context IContextSource
*/
- protected function __construct( Page $page, IContextSource $context = null ) {
+ public function __construct( Page $page, IContextSource $context = null ) {
+ if ( $context === null ) {
+ wfWarn( __METHOD__ . ' called without providing a Context object.' );
+ // NOTE: We could try to initialize $context using $page->getContext(),
+ // if $page is an Article. That however seems to not work seamlessly.
+ }
+
$this->page = $page;
$this->context = $context;
}
@@ -374,18 +394,23 @@ abstract class FormAction extends Action {
* Add pre- or post-text to the form
* @return String HTML which will be sent to $form->addPreText()
*/
- protected function preText() { return ''; }
+ protected function preText() {
+ return '';
+ }
/**
* @return string
*/
- protected function postText() { return ''; }
+ protected function postText() {
+ return '';
+ }
/**
* Play with the HTMLForm if you need to more substantially
* @param $form HTMLForm
*/
- protected function alterForm( HTMLForm $form ) {}
+ protected function alterForm( HTMLForm $form ) {
+ }
/**
* Get the HTMLForm to control behavior
@@ -464,7 +489,7 @@ abstract class FormAction extends Action {
public function execute( array $data = null, $captureErrors = true ) {
try {
// Set a new context so output doesn't leak.
- $this->context = clone $this->page->getContext();
+ $this->context = clone $this->getContext();
// This will throw exceptions if there's a problem
$this->checkCanExecute( $this->getUser() );
@@ -553,7 +578,7 @@ abstract class FormlessAction extends Action {
public function execute( array $data = null, $captureErrors = true ) {
try {
// Set a new context so output doesn't leak.
- $this->context = clone $this->page->getContext();
+ $this->context = clone $this->getContext();
if ( is_array( $data ) ) {
$this->context->setRequest( new FauxRequest( $data, false ) );
}
diff --git a/includes/AjaxDispatcher.php b/includes/AjaxDispatcher.php
index b00cf309..c9ca1283 100644
--- a/includes/AjaxDispatcher.php
+++ b/includes/AjaxDispatcher.php
@@ -63,7 +63,7 @@ class AjaxDispatcher {
$this->mode = "post";
}
- switch( $this->mode ) {
+ switch ( $this->mode ) {
case 'get':
$this->func_name = isset( $_GET["rs"] ) ? $_GET["rs"] : '';
if ( ! empty( $_GET["rsargs"] ) ) {
@@ -111,15 +111,13 @@ class AjaxDispatcher {
wfHttpError(
400,
'Bad Request',
- "unknown function " . (string) $this->func_name
+ "unknown function " . $this->func_name
);
- } elseif ( !in_array( 'read', User::getGroupPermissions( array( '*' ) ), true )
- && !$wgUser->isAllowed( 'read' ) )
- {
+ } elseif ( !User::isEveryoneAllowed( 'read' ) && !$wgUser->isAllowed( 'read' ) ) {
wfHttpError(
403,
'Forbidden',
- 'You must log in to view pages.' );
+ 'You are not allowed to view pages.' );
} else {
wfDebug( __METHOD__ . ' dispatching ' . $this->func_name . "\n" );
diff --git a/includes/AjaxResponse.php b/includes/AjaxResponse.php
index 138f808a..d5536529 100644
--- a/includes/AjaxResponse.php
+++ b/includes/AjaxResponse.php
@@ -210,7 +210,7 @@ class AjaxResponse {
* @param $timestamp string
* @return bool Returns true if the response code was set to 304 Not Modified.
*/
- function checkLastModified ( $timestamp ) {
+ function checkLastModified( $timestamp ) {
global $wgCachePages, $wgCacheEpoch, $wgUser;
$fname = 'AjaxResponse::checkLastModified';
diff --git a/includes/ArrayUtils.php b/includes/ArrayUtils.php
index 0b74f06a..985271f7 100644
--- a/includes/ArrayUtils.php
+++ b/includes/ArrayUtils.php
@@ -39,7 +39,7 @@ class ArrayUtils {
*
* @return bool|int|string
*/
- public static function pickRandom( $weights ){
+ public static function pickRandom( $weights ) {
if ( !is_array( $weights ) || count( $weights ) == 0 ) {
return false;
}
diff --git a/includes/Article.php b/includes/Article.php
index 9b4afe44..0b18221a 100644
--- a/includes/Article.php
+++ b/includes/Article.php
@@ -25,7 +25,7 @@
*
* This maintains WikiPage functions for backwards compatibility.
*
- * @todo move and rewrite code to an Action class
+ * @todo Move and rewrite code to an Action class
*
* See design.txt for an overview.
* Note: edit user interface and cache support functions have been
@@ -160,7 +160,7 @@ class Article implements Page {
$page = null;
wfRunHooks( 'ArticleFromTitle', array( &$title, &$page ) );
if ( !$page ) {
- switch( $title->getNamespace() ) {
+ switch ( $title->getNamespace() ) {
case NS_FILE:
$page = new ImagePage( $title );
break;
@@ -385,7 +385,8 @@ class Article implements Page {
$content = $this->fetchContentObject();
- $this->mContent = ContentHandler::getContentText( $content ); #@todo: get rid of mContent everywhere!
+ // @todo Get rid of mContent everywhere!
+ $this->mContent = ContentHandler::getContentText( $content );
ContentHandler::runLegacyHooks( 'ArticleAfterFetchContent', array( &$this, &$this->mContent ) );
wfProfileOut( __METHOD__ );
@@ -609,7 +610,7 @@ class Article implements Page {
$this->mParserOutput = false;
while ( !$outputDone && ++$pass ) {
- switch( $pass ) {
+ switch ( $pass ) {
case 1:
wfRunHooks( 'ArticleViewHeader', array( &$this, &$outputDone, &$useParserCache ) );
break;
@@ -673,12 +674,12 @@ class Article implements Page {
wfDebug( __METHOD__ . ": showing CSS/JS source\n" );
$this->showCssOrJsPage();
$outputDone = true;
- } elseif( !wfRunHooks( 'ArticleContentViewCustom',
+ } 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',
+ } elseif ( !ContentHandler::runLegacyHooks( 'ArticleViewCustom',
array( $this->fetchContentObject(), $this->getTitle(), $outputPage ) ) ) {
# Allow extensions do their own custom view for certain pages
@@ -787,7 +788,7 @@ class Article implements 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
+ * @todo Make protected
*/
public function showDiffPage() {
$request = $this->getContext()->getRequest();
@@ -854,11 +855,11 @@ class Article implements Page {
/**
* Get the robot policy to be used for the current view
* @param string $action the action= GET parameter
- * @param $pOutput ParserOutput
+ * @param $pOutput ParserOutput|null
* @return Array the policy that should be set
* TODO: actions other than 'view'
*/
- public function getRobotPolicy( $action, $pOutput ) {
+ public function getRobotPolicy( $action, $pOutput = null ) {
global $wgArticleRobotPolicies, $wgNamespaceRobotPolicies, $wgDefaultRobotPolicy;
$ns = $this->getTitle()->getNamespace();
@@ -875,7 +876,7 @@ class Article implements Page {
}
if ( Block::newFromTarget( $specificTarget, $vagueTarget ) instanceof Block ) {
return array(
- 'index' => 'noindex',
+ 'index' => 'noindex',
'follow' => 'nofollow'
);
}
@@ -884,19 +885,19 @@ class Article implements Page {
if ( $this->mPage->getID() === 0 || $this->getOldID() ) {
# Non-articles (special pages etc), and old revisions
return array(
- 'index' => 'noindex',
+ 'index' => 'noindex',
'follow' => 'nofollow'
);
} elseif ( $this->getContext()->getOutput()->isPrintable() ) {
# Discourage indexing of printable versions, but encourage following
return array(
- 'index' => 'noindex',
+ 'index' => 'noindex',
'follow' => 'follow'
);
} elseif ( $this->getContext()->getRequest()->getInt( 'curid' ) ) {
# For ?curid=x urls, disallow indexing
return array(
- 'index' => 'noindex',
+ 'index' => 'noindex',
'follow' => 'follow'
);
}
@@ -988,8 +989,9 @@ class Article implements Page {
// Set the fragment if one was specified in the redirect
if ( strval( $this->getTitle()->getFragment() ) != '' ) {
- $fragment = Xml::escapeJsString( $this->getTitle()->getFragmentForURL() );
- $outputPage->addInlineScript( "redirectToFragment(\"$fragment\");" );
+ $outputPage->addInlineScript( Xml::encodeJsCall(
+ 'redirectToFragment', array( $this->getTitle()->getFragmentForURL() )
+ ) );
}
// Add a <link rel="canonical"> tag
@@ -1035,11 +1037,10 @@ class Article implements Page {
$this->getContext()->getOutput()->addWikiMsg( 'anontalkpagetext' );
}
- # If we have been passed an &rcid= parameter, we want to give the user a
- # chance to mark this new article as patrolled.
- $this->showPatrolFooter();
+ // Show a footer allowing the user to patrol the shown revision or page if possible
+ $patrolFooterShown = $this->showPatrolFooter();
- wfRunHooks( 'ArticleViewFooter', array( $this ) );
+ wfRunHooks( 'ArticleViewFooter', array( $this, $patrolFooterShown ) );
}
@@ -1049,21 +1050,92 @@ class Article implements Page {
* desired, does nothing.
* Side effect: When the patrol link is build, this method will call
* OutputPage::preventClickjacking() and load mediawiki.page.patrol.ajax.
+ *
+ * @return bool
*/
public function showPatrolFooter() {
- $request = $this->getContext()->getRequest();
+ global $wgUseNPPatrol, $wgUseRCPatrol, $wgEnableAPI, $wgEnableWriteAPI;
+
$outputPage = $this->getContext()->getOutput();
$user = $this->getContext()->getUser();
- $rcid = $request->getVal( 'rcid' );
+ $cache = wfGetMainCache();
+ $rc = false;
- if ( !$rcid || !$this->getTitle()->quickUserCan( 'patrol', $user ) ) {
- return;
+ if ( !$this->getTitle()->quickUserCan( 'patrol', $user ) || !( $wgUseRCPatrol || $wgUseNPPatrol ) ) {
+ // Patrolling is disabled or the user isn't allowed to
+ return false;
+ }
+
+ wfProfileIn( __METHOD__ );
+
+ // New page patrol: Get the timestamp of the oldest revison which
+ // the revision table holds for the given page. Then we look
+ // whether it's within the RC lifespan and if it is, we try
+ // to get the recentchanges row belonging to that entry
+ // (with rc_new = 1).
+
+ // Check for cached results
+ if ( $cache->get( wfMemcKey( 'NotPatrollablePage', $this->getTitle()->getArticleID() ) ) ) {
+ wfProfileOut( __METHOD__ );
+ return false;
}
+ if ( $this->mRevision && !RecentChange::isInRCLifespan( $this->mRevision->getTimestamp(), 21600 ) ) {
+ // The current revision is already older than what could be in the RC table
+ // 6h tolerance because the RC might not be cleaned out regularly
+ wfProfileOut( __METHOD__ );
+ return false;
+ }
+
+ $dbr = wfGetDB( DB_SLAVE );
+ $oldestRevisionTimestamp = $dbr->selectField(
+ 'revision',
+ 'MIN( rev_timestamp )',
+ array( 'rev_page' => $this->getTitle()->getArticleID() ),
+ __METHOD__
+ );
+
+ if ( $oldestRevisionTimestamp && RecentChange::isInRCLifespan( $oldestRevisionTimestamp, 21600 ) ) {
+ // 6h tolerance because the RC might not be cleaned out regularly
+ $rc = RecentChange::newFromConds(
+ array(
+ 'rc_new' => 1,
+ 'rc_timestamp' => $oldestRevisionTimestamp,
+ 'rc_namespace' => $this->getTitle()->getNamespace(),
+ 'rc_cur_id' => $this->getTitle()->getArticleID(),
+ 'rc_patrolled' => 0
+ ),
+ __METHOD__,
+ array( 'USE INDEX' => 'new_name_timestamp' )
+ );
+ }
+
+ if ( !$rc ) {
+ // No RC entry around
+
+ // Cache the information we gathered above in case we can't patrol
+ // Don't cache in case we can patrol as this could change
+ $cache->set( wfMemcKey( 'NotPatrollablePage', $this->getTitle()->getArticleID() ), '1' );
+
+ wfProfileOut( __METHOD__ );
+ return false;
+ }
+
+ if ( $rc->getPerformer()->getName() == $user->getName() ) {
+ // Don't show a patrol link for own creations. If the user could
+ // patrol them, they already would be patrolled
+ wfProfileOut( __METHOD__ );
+ return false;
+ }
+
+ $rcid = $rc->getAttribute( 'rc_id' );
+
$token = $user->getEditToken( $rcid );
$outputPage->preventClickjacking();
- $outputPage->addModules( 'mediawiki.page.patrol.ajax' );
+ if ( $wgEnableAPI && $wgEnableWriteAPI && $user->isAllowed( 'writeapi' ) ) {
+ $outputPage->addModules( 'mediawiki.page.patrol.ajax' );
+ }
$link = Linker::linkKnown(
$this->getTitle(),
@@ -1081,6 +1153,9 @@ class Article implements Page {
wfMessage( 'markaspatrolledlink' )->rawParams( $link )->escaped() .
'</div>'
);
+
+ wfProfileOut( __METHOD__ );
+ return true;
}
/**
@@ -1090,6 +1165,7 @@ class Article implements Page {
public function showMissingArticle() {
global $wgSend404Code;
$outputPage = $this->getContext()->getOutput();
+ // Whether the page is a root user page of an existing user (but not a subpage)
$validUserPage = false;
# Show info in user (talk) namespace. Does the user exist? Is he blocked?
@@ -1099,7 +1175,7 @@ class Article implements Page {
$user = User::newFromName( $rootPart, false /* allow IP users*/ );
$ip = User::isIP( $rootPart );
- if ( !($user && $user->isLoggedIn()) && !$ip ) { # User does not exist
+ if ( !( $user && $user->isLoggedIn() ) && !$ip ) { # User does not exist
$outputPage->wrapWikiMsg( "<div class=\"mw-userpage-userdoesnotexist error\">\n\$1\n</div>",
array( 'userpage-userdoesnotexist-view', wfEscapeWikiText( $rootPart ) ) );
} elseif ( $user->isBlocked() ) { # Show log extract if the user is currently blocked
@@ -1117,9 +1193,9 @@ class Article implements Page {
)
)
);
- $validUserPage = true;
+ $validUserPage = !$this->getTitle()->isSubpage();
} else {
- $validUserPage = true;
+ $validUserPage = !$this->getTitle()->isSubpage();
}
}
@@ -1139,6 +1215,13 @@ class Article implements Page {
$this->getContext()->getRequest()->response()->header( "HTTP/1.1 404 Not Found" );
}
+ if ( $validUserPage ) {
+ // Also apply the robot policy for nonexisting user pages (as those aren't served as 404)
+ $policy = $this->getRobotPolicy( 'view' );
+ $outputPage->setIndexPolicy( $policy['index'] );
+ $outputPage->setFollowPolicy( $policy['follow'] );
+ }
+
$hookResult = wfRunHooks( 'BeforeDisplayNoArticleText', array( $this ) );
if ( ! $hookResult ) {
@@ -1188,7 +1271,7 @@ class Article implements Page {
} elseif ( $this->getContext()->getRequest()->getInt( 'unhide' ) != 1 ) {
# Give explanation and add a link to view the revision...
$oldid = intval( $this->getOldID() );
- $link = $this->getTitle()->getFullUrl( "oldid={$oldid}&unhide=1" );
+ $link = $this->getTitle()->getFullURL( "oldid={$oldid}&unhide=1" );
$msg = $this->mRevision->isDeleted( Revision::DELETED_RESTRICTED ) ?
'rev-suppressed-text-unhide' : 'rev-deleted-text-unhide';
$outputPage->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
@@ -1470,13 +1553,7 @@ class Article implements Page {
$this->doDelete( $reason, $suppress );
- if ( $user->isLoggedIn() && $request->getCheck( 'wpWatch' ) != $user->isWatched( $title ) ) {
- if ( $request->getCheck( 'wpWatch' ) ) {
- WatchAction::doWatch( $title, $user );
- } else {
- WatchAction::doUnwatch( $title, $user );
- }
- }
+ WatchAction::doWatchOrUnwatch( $request->getCheck( 'wpWatch' ), $title, $user );
return;
}
diff --git a/includes/AuthPlugin.php b/includes/AuthPlugin.php
index a4658176..84cf3d5e 100644
--- a/includes/AuthPlugin.php
+++ b/includes/AuthPlugin.php
@@ -213,6 +213,19 @@ class AuthPlugin {
}
/**
+ * Update user groups in the external authentication database.
+ * Return true if successful.
+ *
+ * @param $user User object.
+ * @param $addgroups Groups to add.
+ * @param $delgroups Groups to remove.
+ * @return Boolean
+ */
+ public function updateExternalDBGroups( $user, $addgroups, $delgroups = array() ) {
+ return true;
+ }
+
+ /**
* Check to see if external accounts can be created.
* Return true if external accounts can be created.
* @return Boolean
diff --git a/includes/AutoLoader.php b/includes/AutoLoader.php
index 3555e2c3..0706fe3f 100644
--- a/includes/AutoLoader.php
+++ b/includes/AutoLoader.php
@@ -55,7 +55,6 @@ $wgAutoloadLocalClasses = array(
'CdbWriter_DBA' => 'includes/Cdb.php',
'CdbWriter_PHP' => 'includes/Cdb_PHP.php',
'ChangesFeed' => 'includes/ChangesFeed.php',
- 'ChangesList' => 'includes/ChangesList.php',
'ChangeTags' => 'includes/ChangeTags.php',
'ChannelFeed' => 'includes/Feed.php',
'Collation' => 'includes/Collation.php',
@@ -65,10 +64,10 @@ $wgAutoloadLocalClasses = array(
'ConfEditorToken' => 'includes/ConfEditor.php',
'Cookie' => 'includes/Cookie.php',
'CookieJar' => 'includes/Cookie.php',
- 'MWCryptRand' => 'includes/CryptRand.php',
'CurlHttpRequest' => 'includes/HttpFunctions.php',
'DeferrableUpdate' => 'includes/DeferredUpdates.php',
'DeferredUpdates' => 'includes/DeferredUpdates.php',
+ 'MWCallableUpdate' => 'includes/CallableUpdate.php',
'DeprecatedGlobal' => 'includes/DeprecatedGlobal.php',
'DerivativeRequest' => 'includes/WebRequest.php',
'DiffHistoryBlob' => 'includes/HistoryBlob.php',
@@ -87,16 +86,8 @@ $wgAutoloadLocalClasses = array(
'DumpPipeOutput' => 'includes/Export.php',
'EditPage' => 'includes/EditPage.php',
'EmailNotification' => 'includes/UserMailer.php',
- 'EnhancedChangesList' => 'includes/ChangesList.php',
'ErrorPageError' => 'includes/Exception.php',
'ExplodeIterator' => 'includes/StringUtils.php',
- 'ExternalEdit' => 'includes/ExternalEdit.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',
'FatalError' => 'includes/Exception.php',
@@ -111,22 +102,27 @@ $wgAutoloadLocalClasses = array(
'FormOptions' => 'includes/FormOptions.php',
'FormSpecialPage' => 'includes/SpecialPage.php',
'GitInfo' => 'includes/GitInfo.php',
+ 'HashRing' => 'includes/HashRing.php',
'HashtableReplacer' => 'includes/StringUtils.php',
'HistoryBlob' => 'includes/HistoryBlob.php',
'HistoryBlobCurStub' => 'includes/HistoryBlob.php',
'HistoryBlobStub' => 'includes/HistoryBlob.php',
'Hooks' => 'includes/Hooks.php',
'Html' => 'includes/Html.php',
+ 'HtmlFormatter' => 'includes/HtmlFormatter.php',
'HTMLApiField' => 'includes/HTMLForm.php',
+ 'HTMLButtonField' => 'includes/HTMLForm.php',
'HTMLCheckField' => 'includes/HTMLForm.php',
'HTMLCheckMatrix' => 'includes/HTMLForm.php',
'HTMLEditTools' => 'includes/HTMLForm.php',
'HTMLFloatField' => 'includes/HTMLForm.php',
'HTMLForm' => 'includes/HTMLForm.php',
'HTMLFormField' => 'includes/HTMLForm.php',
+ 'HTMLFormFieldRequiredOptionsException' => 'includes/HTMLForm.php',
'HTMLHiddenField' => 'includes/HTMLForm.php',
'HTMLInfoField' => 'includes/HTMLForm.php',
'HTMLIntField' => 'includes/HTMLForm.php',
+ 'HTMLNestedFilterable' => 'includes/HTMLForm.php',
'HTMLMultiSelectField' => 'includes/HTMLForm.php',
'HTMLRadioField' => 'includes/HTMLForm.php',
'HTMLSelectAndOtherField' => 'includes/HTMLForm.php',
@@ -140,7 +136,6 @@ $wgAutoloadLocalClasses = array(
'ICacheHelper' => 'includes/CacheHelper.php',
'IcuCollation' => 'includes/Collation.php',
'IdentityCollation' => 'includes/Collation.php',
- 'ImageGallery' => 'includes/ImageGallery.php',
'ImageHistoryList' => 'includes/ImagePage.php',
'ImageHistoryPseudoPager' => 'includes/ImagePage.php',
'ImagePage' => 'includes/ImagePage.php',
@@ -156,7 +151,6 @@ $wgAutoloadLocalClasses = array(
'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',
'Linker' => 'includes/Linker.php',
@@ -174,6 +168,7 @@ $wgAutoloadLocalClasses = array(
'Message' => 'includes/Message.php',
'MessageBlobStore' => 'includes/MessageBlobStore.php',
'MimeMagic' => 'includes/MimeMagic.php',
+ 'MWCryptRand' => 'includes/MWCryptRand.php',
'MWException' => 'includes/Exception.php',
'MWExceptionHandler' => 'includes/Exception.php',
'MWFunction' => 'includes/MWFunction.php',
@@ -181,7 +176,6 @@ $wgAutoloadLocalClasses = array(
'MWHttpRequest' => 'includes/HttpFunctions.php',
'MWInit' => 'includes/Init.php',
'MWNamespace' => 'includes/Namespace.php',
- 'OldChangesList' => 'includes/ChangesList.php',
'OutputPage' => 'includes/OutputPage.php',
'Page' => 'includes/WikiPage.php',
'PageQueryPage' => 'includes/PageQueryPage.php',
@@ -194,6 +188,7 @@ $wgAutoloadLocalClasses = array(
'PoolCounter' => 'includes/PoolCounter.php',
'PoolCounter_Stub' => 'includes/PoolCounter.php',
'PoolCounterWork' => 'includes/PoolCounter.php',
+ 'PoolCounterWorkViaCallback' => 'includes/PoolCounter.php',
'PoolWorkArticleView' => 'includes/WikiPage.php',
'Preferences' => 'includes/Preferences.php',
'PreferencesForm' => 'includes/Preferences.php',
@@ -202,10 +197,8 @@ $wgAutoloadLocalClasses = array(
'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',
@@ -228,7 +221,6 @@ $wgAutoloadLocalClasses = array(
'SiteStatsInit' => 'includes/SiteStats.php',
'SiteStatsUpdate' => 'includes/SiteStats.php',
'Skin' => 'includes/Skin.php',
- 'SkinLegacy' => 'includes/SkinLegacy.php',
'SkinTemplate' => 'includes/SkinTemplate.php',
'SpecialCreateAccount' => 'includes/SpecialPage.php',
'SpecialListAdmins' => 'includes/SpecialPage.php',
@@ -237,11 +229,13 @@ $wgAutoloadLocalClasses = array(
'SpecialMypage' => 'includes/SpecialPage.php',
'SpecialMytalk' => 'includes/SpecialPage.php',
'SpecialMyuploads' => 'includes/SpecialPage.php',
+ 'SpecialAllMyUploads' => 'includes/SpecialPage.php',
'SpecialPage' => 'includes/SpecialPage.php',
'SpecialPageFactory' => 'includes/SpecialPageFactory.php',
'SpecialRedirectToSpecial' => 'includes/SpecialPage.php',
'SquidPurgeClient' => 'includes/SquidPurgeClient.php',
'SquidPurgeClientPool' => 'includes/SquidPurgeClient.php',
+ 'StatCounter' => 'includes/StatCounter.php',
'Status' => 'includes/Status.php',
'StreamFile' => 'includes/StreamFile.php',
'StringUtils' => 'includes/StringUtils.php',
@@ -415,6 +409,7 @@ $wgAutoloadLocalClasses = array(
'ApiQueryQueryPage' => 'includes/api/ApiQueryQueryPage.php',
'ApiQueryRandom' => 'includes/api/ApiQueryRandom.php',
'ApiQueryRecentChanges' => 'includes/api/ApiQueryRecentChanges.php',
+ 'ApiQueryFileRepoInfo' => 'includes/api/ApiQueryFileRepoInfo.php',
'ApiQueryRevisions' => 'includes/api/ApiQueryRevisions.php',
'ApiQuerySearch' => 'includes/api/ApiQuerySearch.php',
'ApiQuerySiteinfo' => 'includes/api/ApiQuerySiteinfo.php',
@@ -457,6 +452,13 @@ $wgAutoloadLocalClasses = array(
'TitleDependency' => 'includes/cache/CacheDependency.php',
'TitleListDependency' => 'includes/cache/CacheDependency.php',
+ # includes/changes
+ 'ChangesList' => 'includes/changes/ChangesList.php',
+ 'EnhancedChangesList' => 'includes/changes/EnhancedChangesList.php',
+ 'OldChangesList' => 'includes/changes/OldChangesList.php',
+ 'RCCacheEntry' => 'includes/changes/RCCacheEntry.php',
+ 'RecentChange' => 'includes/changes/RecentChange.php',
+
# includes/clientpool
'RedisConnectionPool' => 'includes/clientpool/RedisConnectionPool.php',
'RedisConnRef' => 'includes/clientpool/RedisConnectionPool.php',
@@ -473,11 +475,13 @@ $wgAutoloadLocalClasses = array(
# includes/db
'Blob' => 'includes/db/DatabaseUtility.php',
- 'ChronologyProtector' => 'includes/db/LBFactory.php',
+ 'ChronologyProtector' => 'includes/db/ChronologyProtector.php',
'CloneDatabase' => 'includes/db/CloneDatabase.php',
'DatabaseBase' => 'includes/db/Database.php',
'DatabaseMssql' => 'includes/db/DatabaseMssql.php',
'DatabaseMysql' => 'includes/db/DatabaseMysql.php',
+ 'DatabaseMysqlBase' => 'includes/db/DatabaseMysqlBase.php',
+ 'DatabaseMysqli' => 'includes/db/DatabaseMysqli.php',
'DatabaseOracle' => 'includes/db/DatabaseOracle.php',
'DatabasePostgres' => 'includes/db/DatabasePostgres.php',
'DatabaseSqlite' => 'includes/db/DatabaseSqlite.php',
@@ -485,8 +489,10 @@ $wgAutoloadLocalClasses = array(
'DatabaseType' => 'includes/db/Database.php',
'DBAccessError' => 'includes/db/LBFactory.php',
'DBConnectionError' => 'includes/db/DatabaseError.php',
+ 'DBConnRef' => 'includes/db/LoadBalancer.php',
'DBError' => 'includes/db/DatabaseError.php',
'DBObject' => 'includes/db/DatabaseUtility.php',
+ 'IDatabase' => 'includes/db/Database.php',
'IORMRow' => 'includes/db/IORMRow.php',
'IORMTable' => 'includes/db/IORMTable.php',
'DBMasterPos' => 'includes/db/DatabaseUtility.php',
@@ -507,8 +513,8 @@ $wgAutoloadLocalClasses = array(
'LoadMonitor_Null' => 'includes/db/LoadMonitor.php',
'MssqlField' => 'includes/db/DatabaseMssql.php',
'MssqlResult' => 'includes/db/DatabaseMssql.php',
- 'MySQLField' => 'includes/db/DatabaseMysql.php',
- 'MySQLMasterPos' => 'includes/db/DatabaseMysql.php',
+ 'MySQLField' => 'includes/db/DatabaseMysqlBase.php',
+ 'MySQLMasterPos' => 'includes/db/DatabaseMysqlBase.php',
'ORAField' => 'includes/db/DatabaseOracle.php',
'ORAResult' => 'includes/db/DatabaseOracle.php',
'ORMIterator' => 'includes/db/ORMIterator.php',
@@ -543,14 +549,17 @@ $wgAutoloadLocalClasses = array(
'WikiDiff3' => 'includes/diff/WikiDiff3.php',
'WordLevelDiff' => 'includes/diff/DairikiDiff.php',
- # includes/extauth
- 'ExternalUser_Hardcoded' => 'includes/extauth/Hardcoded.php',
- 'ExternalUser_MediaWiki' => 'includes/extauth/MediaWiki.php',
- 'ExternalUser_vB' => 'includes/extauth/vB.php',
+ # includes/externalstore
+ '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',
# includes/filebackend
'FileBackendGroup' => 'includes/filebackend/FileBackendGroup.php',
'FileBackend' => 'includes/filebackend/FileBackend.php',
+ 'FileBackendError' => 'includes/filebackend/FileBackend.php',
'FileBackendStore' => 'includes/filebackend/FileBackendStore.php',
'FileBackendStoreShardListIterator' => 'includes/filebackend/FileBackendStore.php',
'FileBackendStoreShardDirIterator' => 'includes/filebackend/FileBackendStore.php',
@@ -582,6 +591,7 @@ $wgAutoloadLocalClasses = array(
'QuorumLockManager' => 'includes/filebackend/lockmanager/QuorumLockManager.php',
'MySqlLockManager' => 'includes/filebackend/lockmanager/DBLockManager.php',
'PostgreSqlLockManager' => 'includes/filebackend/lockmanager/DBLockManager.php',
+ 'RedisLockManager' => 'includes/filebackend/lockmanager/RedisLockManager.php',
'NullLockManager' => 'includes/filebackend/lockmanager/LockManager.php',
'FileOp' => 'includes/filebackend/FileOp.php',
'FileOpBatch' => 'includes/filebackend/FileOpBatch.php',
@@ -657,11 +667,15 @@ $wgAutoloadLocalClasses = array(
# includes/job
'Job' => 'includes/job/Job.php',
'JobQueue' => 'includes/job/JobQueue.php',
- 'JobQueueAggregator' => 'includes/job/JobQueueAggregator.php',
- 'JobQueueAggregatorMemc' => 'includes/job/JobQueueAggregatorMemc.php',
- 'JobQueueAggregatorRedis' => 'includes/job/JobQueueAggregatorRedis.php',
+ 'JobQueueAggregator' => 'includes/job/aggregator/JobQueueAggregator.php',
+ 'JobQueueAggregatorMemc' => 'includes/job/aggregator/JobQueueAggregatorMemc.php',
+ 'JobQueueAggregatorRedis' => 'includes/job/aggregator/JobQueueAggregatorRedis.php',
'JobQueueDB' => 'includes/job/JobQueueDB.php',
+ 'JobQueueConnectionError' => 'includes/job/JobQueue.php',
+ 'JobQueueError' => 'includes/job/JobQueue.php',
'JobQueueGroup' => 'includes/job/JobQueueGroup.php',
+ 'JobQueueFederated' => 'includes/job/JobQueueFederated.php',
+ 'JobQueueRedis' => 'includes/job/JobQueueRedis.php',
# includes/job/jobs
'DoubleRedirectJob' => 'includes/job/jobs/DoubleRedirectJob.php',
@@ -678,8 +692,6 @@ $wgAutoloadLocalClasses = array(
# includes/json
'FormatJson' => 'includes/json/FormatJson.php',
- 'Services_JSON' => 'includes/json/Services_JSON.php',
- 'Services_JSON_Error' => 'includes/json/Services_JSON.php',
# includes/libs
'CSSJanus' => 'includes/libs/CSSJanus.php',
@@ -697,9 +709,16 @@ $wgAutoloadLocalClasses = array(
'JSToken' => 'includes/libs/jsminplus.php',
'JSTokenizer' => 'includes/libs/jsminplus.php',
+ # includes/libs/lessphp
+ 'lessc' => 'includes/libs/lessc.inc.php',
+ 'lessc_parser' => 'includes/libs/lessc.inc.php',
+ 'lessc_formatter_classic' => 'includes/libs/lessc.inc.php',
+ 'lessc_formatter_compressed' => 'includes/libs/lessc.inc.php',
+ 'lessc_formatter_lessjs' => 'includes/libs/lessc.inc.php',
+
# includes/logging
'DatabaseLogEntry' => 'includes/logging/LogEntry.php',
- 'DeleteLogFormatter' => 'includes/logging/LogFormatter.php',
+ 'DeleteLogFormatter' => 'includes/logging/DeleteLogFormatter.php',
'LegacyLogFormatter' => 'includes/logging/LogFormatter.php',
'LogEntry' => 'includes/logging/LogEntry.php',
'LogEventsList' => 'includes/logging/LogEventsList.php',
@@ -708,12 +727,22 @@ $wgAutoloadLocalClasses = array(
'LogPage' => 'includes/logging/LogPage.php',
'LogPager' => 'includes/logging/LogPager.php',
'ManualLogEntry' => 'includes/logging/LogEntry.php',
- 'MoveLogFormatter' => 'includes/logging/LogFormatter.php',
- 'NewUsersLogFormatter' => 'includes/logging/LogFormatter.php',
+ 'MoveLogFormatter' => 'includes/logging/MoveLogFormatter.php',
+ 'NewUsersLogFormatter' => 'includes/logging/NewUsersLogFormatter.php',
'PatrolLog' => 'includes/logging/PatrolLog.php',
- 'PatrolLogFormatter' => 'includes/logging/LogFormatter.php',
+ 'PatrolLogFormatter' => 'includes/logging/PatrolLogFormatter.php',
'RCDatabaseLogEntry' => 'includes/logging/LogEntry.php',
- 'RightsLogFormatter' => 'includes/logging/LogFormatter.php',
+ 'RightsLogFormatter' => 'includes/logging/RightsLogFormatter.php',
+
+ # Image gallery
+
+ 'ImageGallery' => 'includes/gallery/TraditionalImageGallery.php',
+ 'ImageGalleryBase' => 'includes/gallery/ImageGalleryBase.php',
+ 'NolinesImageGallery' => 'includes/gallery/NolinesImageGallery.php',
+ 'TraditionalImageGallery' => 'includes/gallery/TraditionalImageGallery.php',
+ 'PackedImageGallery' => 'includes/gallery/PackedImageGallery.php',
+ 'PackedHoverImageGallery' => 'includes/gallery/PackedOverlayImageGallery.php',
+ 'PackedOverlayImageGallery' => 'includes/gallery/PackedOverlayImageGallery.php',
# includes/media
'BitmapHandler' => 'includes/media/Bitmap.php',
@@ -775,12 +804,10 @@ $wgAutoloadLocalClasses = array(
# includes/parser
'CacheTime' => 'includes/parser/CacheTime.php',
- 'CoreLinkFunctions' => 'includes/parser/CoreLinkFunctions.php',
'CoreParserFunctions' => 'includes/parser/CoreParserFunctions.php',
'CoreTagHooks' => 'includes/parser/CoreTagHooks.php',
'DateFormatter' => 'includes/parser/DateFormatter.php',
'LinkHolderArray' => 'includes/parser/LinkHolderArray.php',
- 'LinkMarkerReplacer' => 'includes/parser/Parser_LinkHooks.php',
'MWTidy' => 'includes/parser/Tidy.php',
'MWTidyWrapper' => 'includes/parser/Tidy.php',
'PPCustomFrame_DOM' => 'includes/parser/Preprocessor_DOM.php',
@@ -808,7 +835,6 @@ $wgAutoloadLocalClasses = array(
'ParserOptions' => 'includes/parser/ParserOptions.php',
'ParserOutput' => 'includes/parser/ParserOutput.php',
'Parser_DiffTest' => 'includes/parser/Parser_DiffTest.php',
- 'Parser_LinkHooks' => 'includes/parser/Parser_LinkHooks.php',
'Preprocessor' => 'includes/parser/Preprocessor.php',
'Preprocessor_DOM' => 'includes/parser/Preprocessor_DOM.php',
'Preprocessor_Hash' => 'includes/parser/Preprocessor_Hash.php',
@@ -821,12 +847,22 @@ $wgAutoloadLocalClasses = array(
'ProfilerSimpleTrace' => 'includes/profiler/ProfilerSimpleTrace.php',
'ProfilerSimpleUDP' => 'includes/profiler/ProfilerSimpleUDP.php',
'ProfilerStub' => 'includes/profiler/ProfilerStub.php',
+ 'ProfileSection' => 'includes/profiler/Profiler.php',
+
+ # includes/rcfeed
+ 'RCFeedEngine' => 'includes/rcfeed/RCFeedEngine.php',
+ 'RedisPubSubFeedEngine' => 'includes/rcfeed/RedisPubSubFeedEngine.php',
+ 'UDPRCFeedEngine' => 'includes/rcfeed/UDPRCFeedEngine.php',
+ 'RCFeedFormatter' => 'includes/rcfeed/RCFeedFormatter.php',
+ 'IRCColourfulRCFeedFormatter' => 'includes/rcfeed/IRCColourfulRCFeedFormatter.php',
+ 'JSONRCFeedFormatter' => 'includes/rcfeed/JSONRCFeedFormatter.php',
# includes/resourceloader
'ResourceLoader' => 'includes/resourceloader/ResourceLoader.php',
'ResourceLoaderContext' => 'includes/resourceloader/ResourceLoaderContext.php',
'ResourceLoaderFileModule' => 'includes/resourceloader/ResourceLoaderFileModule.php',
'ResourceLoaderFilePageModule' => 'includes/resourceloader/ResourceLoaderFilePageModule.php',
+ 'ResourceLoaderLESSFunctions' => 'includes/resourceloader/ResourceLoaderLESSFunctions.php',
'ResourceLoaderModule' => 'includes/resourceloader/ResourceLoaderModule.php',
'ResourceLoaderNoscriptModule' => 'includes/resourceloader/ResourceLoaderNoscriptModule.php',
'ResourceLoaderSiteModule' => 'includes/resourceloader/ResourceLoaderSiteModule.php',
@@ -874,7 +910,6 @@ $wgAutoloadLocalClasses = array(
'SearchResultTooMany' => 'includes/search/SearchEngine.php',
'SearchSqlite' => 'includes/search/SearchSqlite.php',
'SearchUpdate' => 'includes/search/SearchUpdate.php',
- 'SearchUpdateMyISAM' => 'includes/search/SearchUpdate.php',
'SqliteSearchResultSet' => 'includes/search/SearchSqlite.php',
'SqlSearchResultSet' => 'includes/search/SearchEngine.php',
@@ -899,7 +934,6 @@ $wgAutoloadLocalClasses = array(
'DeadendPagesPage' => 'includes/specials/SpecialDeadendpages.php',
'DeletedContribsPager' => 'includes/specials/SpecialDeletedContributions.php',
'DeletedContributionsPage' => 'includes/specials/SpecialDeletedContributions.php',
- 'DisambiguationsPage' => 'includes/specials/SpecialDisambiguations.php',
'DoubleRedirectsPage' => 'includes/specials/SpecialDoubleRedirects.php',
'EditWatchlistCheckboxSeriesField' => 'includes/specials/SpecialEditWatchlist.php',
'EditWatchlistNormalHTMLForm' => 'includes/specials/SpecialEditWatchlist.php',
@@ -940,7 +974,6 @@ $wgAutoloadLocalClasses = array(
'SpecialBlankpage' => 'includes/specials/SpecialBlankpage.php',
'SpecialBlock' => 'includes/specials/SpecialBlock.php',
'SpecialBlockList' => 'includes/specials/SpecialBlockList.php',
- 'SpecialBlockme' => 'includes/specials/SpecialBlockme.php',
'SpecialBookSources' => 'includes/specials/SpecialBooksources.php',
'SpecialCachedPage' => 'includes/specials/SpecialCachedPage.php',
'SpecialCategories' => 'includes/specials/SpecialCategories.php',
@@ -969,9 +1002,12 @@ $wgAutoloadLocalClasses = array(
'SpecialPrefixindex' => 'includes/specials/SpecialPrefixindex.php',
'SpecialProtectedpages' => 'includes/specials/SpecialProtectedpages.php',
'SpecialProtectedtitles' => 'includes/specials/SpecialProtectedtitles.php',
+ 'SpecialRandomInCategory' => 'includes/specials/SpecialRandomInCategory.php',
'SpecialRandomredirect' => 'includes/specials/SpecialRandomredirect.php',
'SpecialRecentChanges' => 'includes/specials/SpecialRecentchanges.php',
'SpecialRecentchangeslinked' => 'includes/specials/SpecialRecentchangeslinked.php',
+ 'SpecialRedirect' => 'includes/specials/SpecialRedirect.php',
+ 'SpecialResetTokens' => 'includes/specials/SpecialResetTokens.php',
'SpecialRevisionDelete' => 'includes/specials/SpecialRevisiondelete.php',
'SpecialSearch' => 'includes/specials/SpecialSearch.php',
'SpecialSpecialpages' => 'includes/specials/SpecialSpecialpages.php',
@@ -1021,6 +1057,7 @@ $wgAutoloadLocalClasses = array(
'UploadFromUrl' => 'includes/upload/UploadFromUrl.php',
'UploadStash' => 'includes/upload/UploadStash.php',
'UploadStashBadPathException' => 'includes/upload/UploadStash.php',
+ 'UploadStashException' => 'includes/upload/UploadStash.php',
'UploadStashFile' => 'includes/upload/UploadStash.php',
'UploadStashFileException' => 'includes/upload/UploadStash.php',
'UploadStashFileNotFoundException' => 'includes/upload/UploadStash.php',
@@ -1091,17 +1128,10 @@ $wgAutoloadLocalClasses = array(
'CologneBlueTemplate' => 'skins/CologneBlue.php',
'ModernTemplate' => 'skins/Modern.php',
'MonoBookTemplate' => 'skins/MonoBook.php',
- 'NostalgiaTemplate' => 'skins/Nostalgia.php',
- 'SkinChick' => 'skins/Chick.php',
'SkinCologneBlue' => 'skins/CologneBlue.php',
'SkinModern' => 'skins/Modern.php',
'SkinMonoBook' => 'skins/MonoBook.php',
- 'SkinMySkin' => 'skins/MySkin.php',
- 'SkinNostalgia' => 'skins/Nostalgia.php',
- 'SkinSimple' => 'skins/Simple.php',
- 'SkinStandard' => 'skins/Standard.php',
'SkinVector' => 'skins/Vector.php',
- 'StandardTemplate' => 'skins/Standard.php',
'VectorTemplate' => 'skins/Vector.php',
);
@@ -1117,12 +1147,13 @@ class AutoLoader {
static function autoload( $className ) {
global $wgAutoloadClasses, $wgAutoloadLocalClasses;
- // Workaround for PHP bug <https://bugs.php.net/bug.php?id=49143> (5.3.2. is broken, it's fixed in 5.3.6).
- // Strip leading backslashes from class names. When namespaces are used, leading backslashes are used to indicate
- // the top-level namespace, e.g. \foo\Bar. When used like this in the code, the leading backslash isn't passed to
- // the auto-loader ($className would be 'foo\Bar'). However, if a class is accessed using a string instead of a
- // class literal (e.g. $class = '\foo\Bar'; new $class()), then some versions of PHP do not strip the leading
- // backlash in this case, causing autoloading to fail.
+ // Workaround for PHP bug <https://bugs.php.net/bug.php?id=49143> (5.3.2. is broken, it's
+ // fixed in 5.3.6). Strip leading backslashes from class names. When namespaces are used,
+ // leading backslashes are used to indicate the top-level namespace, e.g. \foo\Bar. When
+ // used like this in the code, the leading backslash isn't passed to the auto-loader
+ // ($className would be 'foo\Bar'). However, if a class is accessed using a string instead
+ // of a class literal (e.g. $class = '\foo\Bar'; new $class()), then some versions of PHP
+ // do not strip the leading backlash in this case, causing autoloading to fail.
$className = ltrim( $className, '\\' );
if ( isset( $wgAutoloadLocalClasses[$className] ) ) {
@@ -1157,7 +1188,7 @@ class AutoLoader {
$filename = "$IP/$filename";
}
- require( $filename );
+ require $filename;
return true;
}
@@ -1175,12 +1206,4 @@ class AutoLoader {
}
}
-if ( function_exists( 'spl_autoload_register' ) ) {
- spl_autoload_register( array( 'AutoLoader', 'autoload' ) );
-} else {
- function __autoload( $class ) {
- AutoLoader::autoload( $class );
- }
-
- ini_set( 'unserialize_callback_func', '__autoload' );
-}
+spl_autoload_register( array( 'AutoLoader', 'autoload' ) );
diff --git a/includes/Autopromote.php b/includes/Autopromote.php
index 604b9248..170d7abf 100644
--- a/includes/Autopromote.php
+++ b/includes/Autopromote.php
@@ -126,7 +126,8 @@ class Autopromote {
return false;
} elseif ( $cond[0] == '^' ) { // XOR (exactly one cond passes)
if ( count( $cond ) > 3 ) {
- wfWarn( 'recCheckCondition() given XOR ("^") condition on three or more conditions. Check your $wgAutopromote and $wgAutopromoteOnce settings.' );
+ wfWarn( 'recCheckCondition() given XOR ("^") condition on three or more conditions.' .
+ ' Check your $wgAutopromote and $wgAutopromoteOnce settings.' );
}
return self::recCheckCondition( $cond[1], $user )
xor self::recCheckCondition( $cond[2], $user );
@@ -165,7 +166,7 @@ class Autopromote {
return false;
}
- switch( $cond[0] ) {
+ switch ( $cond[0] ) {
case APCOND_EMAILCONFIRMED:
if ( Sanitizer::validateEmail( $user->getEmail() ) ) {
if ( $wgEmailAuthentication ) {
diff --git a/includes/Block.php b/includes/Block.php
index 7ee36ce9..34b89e73 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" );
}
@@ -206,16 +206,16 @@ class Block {
*/
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;
@@ -237,7 +237,7 @@ class Block {
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 ),
);
@@ -247,9 +247,9 @@ 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 ) {
+ switch ( $type ) {
case self::TYPE_USER:
# Slightly weird, but who are we to argue?
$conds['ipb_address'][] = (string)$target;
@@ -285,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 );
@@ -313,14 +313,14 @@ class Block {
$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;
@@ -511,7 +511,7 @@ class Block {
* @return Array
*/
protected function getDatabaseArray( $db = null ) {
- if( !$db ) {
+ if ( !$db ) {
$db = wfGetDB( DB_SLAVE );
}
$expiry = $db->encodeExpiry( $this->mExpiry );
@@ -579,7 +579,7 @@ class Block {
global $wgPutIPinRC;
// No IPs are in recentchanges table, so nothing to select
- if( !$wgPutIPinRC ) {
+ if ( !$wgPutIPinRC ) {
return;
}
@@ -601,7 +601,9 @@ class Block {
foreach ( $res as $row ) {
if ( $row->rc_ip ) {
$id = $block->doAutoblock( $row->rc_ip );
- if ( $id ) $blockIds[] = $id;
+ if ( $id ) {
+ $blockIds[] = $id;
+ }
}
}
}
@@ -681,7 +683,7 @@ class Block {
if ( $ipblock ) {
# Check if the block is an autoblock and would exceed the user block
# if renewed. If so, do nothing, otherwise prolong the block time...
- if ( $ipblock->mAuto && // @TODO: why not compare $ipblock->mExpiry?
+ if ( $ipblock->mAuto && // @todo Why not compare $ipblock->mExpiry?
$this->mExpiry > Block::getAutoblockExpiry( $ipblock->mTimestamp )
) {
# Reset block timestamp to now and its expiry to
@@ -793,7 +795,7 @@ class Block {
* @return String IP in Hex form
*/
public function getRangeStart() {
- switch( $this->type ) {
+ switch ( $this->type ) {
case self::TYPE_USER:
return '';
case self::TYPE_IP:
@@ -801,17 +803,18 @@ class Block {
case self::TYPE_RANGE:
list( $start, /*...*/ ) = IP::parseRange( $this->target );
return $start;
- default: throw new MWException( "Block with invalid type" );
+ default:
+ throw new MWException( "Block with invalid type" );
}
}
/**
- * Get the IP address at the start of the range in Hex form
+ * Get the IP address at the end of the range in Hex form
* @throws MWException
* @return String IP in Hex form
*/
public function getRangeEnd() {
- switch( $this->type ) {
+ switch ( $this->type ) {
case self::TYPE_USER:
return '';
case self::TYPE_IP:
@@ -819,7 +822,8 @@ class Block {
case self::TYPE_RANGE:
list( /*...*/, $end ) = IP::parseRange( $this->target );
return $end;
- default: throw new MWException( "Block with invalid type" );
+ default:
+ throw new MWException( "Block with invalid type" );
}
}
@@ -907,7 +911,7 @@ class Block {
* @return Bool
*/
public function prevents( $action, $x = null ) {
- switch( $action ) {
+ switch ( $action ) {
case 'edit':
# For now... <evil laugh>
return true;
@@ -997,11 +1001,16 @@ class Block {
* Purge expired blocks from the ipblocks table
*/
public static function purgeExpired() {
- if ( !wfReadOnly() ) {
- $dbw = wfGetDB( DB_MASTER );
- $dbw->delete( 'ipblocks',
- array( 'ipb_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ), __METHOD__ );
+ if ( wfReadOnly() ) {
+ return;
}
+
+ $method = __METHOD__;
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->onTransactionIdle( function() use ( $dbw, $method ) {
+ $dbw->delete( 'ipblocks',
+ array( 'ipb_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ), $method );
+ } );
}
/**
@@ -1050,30 +1059,216 @@ 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, null ) ) ) {
+ } 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;
}
}
return null;
}
+
+ /**
+ * Get all blocks that match any IP from an array of IP addresses
+ *
+ * @param Array $ipChain list of IPs (strings), usually retrieved from the
+ * X-Forwarded-For header of the request
+ * @param Bool $isAnon Exclude anonymous-only blocks if false
+ * @param Bool $fromMaster Whether to query the master or slave database
+ * @return Array of Blocks
+ * @since 1.22
+ */
+ public static function getBlocksForIPList( array $ipChain, $isAnon, $fromMaster = false ) {
+ if ( !count( $ipChain ) ) {
+ return array();
+ }
+
+ wfProfileIn( __METHOD__ );
+ $conds = array();
+ foreach ( array_unique( $ipChain ) as $ipaddr ) {
+ # Discard invalid IP addresses. Since XFF can be spoofed and we do not
+ # necessarily trust the header given to us, make sure that we are only
+ # checking for blocks on well-formatted IP addresses (IPv4 and IPv6).
+ # Do not treat private IP spaces as special as it may be desirable for wikis
+ # to block those IP ranges in order to stop misbehaving proxies that spoof XFF.
+ if ( !IP::isValid( $ipaddr ) ) {
+ continue;
+ }
+ # Don't check trusted IPs (includes local squids which will be in every request)
+ if ( wfIsTrustedProxy( $ipaddr ) ) {
+ continue;
+ }
+ # Check both the original IP (to check against single blocks), as well as build
+ # the clause to check for rangeblocks for the given IP.
+ $conds['ipb_address'][] = $ipaddr;
+ $conds[] = self::getRangeCond( IP::toHex( $ipaddr ) );
+ }
+
+ if ( !count( $conds ) ) {
+ wfProfileOut( __METHOD__ );
+ return array();
+ }
+
+ if ( $fromMaster ) {
+ $db = wfGetDB( DB_MASTER );
+ } else {
+ $db = wfGetDB( DB_SLAVE );
+ }
+ $conds = $db->makeList( $conds, LIST_OR );
+ if ( !$isAnon ) {
+ $conds = array( $conds, 'ipb_anon_only' => 0 );
+ }
+ $selectFields = array_merge(
+ array( 'ipb_range_start', 'ipb_range_end' ),
+ Block::selectFields()
+ );
+ $rows = $db->select( 'ipblocks',
+ $selectFields,
+ $conds,
+ __METHOD__
+ );
+
+ $blocks = array();
+ foreach ( $rows as $row ) {
+ $block = self::newFromRow( $row );
+ if ( !$block->deleteIfExpired() ) {
+ $blocks[] = $block;
+ }
+ }
+
+ wfProfileOut( __METHOD__ );
+ return $blocks;
+ }
+
+ /**
+ * From a list of multiple blocks, find the most exact and strongest Block.
+ * The logic for finding the "best" block is:
+ * - Blocks that match the block's target IP are preferred over ones in a range
+ * - Hardblocks are chosen over softblocks that prevent account creation
+ * - Softblocks that prevent account creation are chosen over other softblocks
+ * - Other softblocks are chosen over autoblocks
+ * - If there are multiple exact or range blocks at the same level, the one chosen
+ * is random
+
+ * @param Array $ipChain list of IPs (strings). This is used to determine how "close"
+ * a block is to the server, and if a block matches exactly, or is in a range.
+ * The order is furthest from the server to nearest e.g., (Browser, proxy1, proxy2,
+ * local-squid, ...)
+ * @param Array $block Array of blocks
+ * @return Block|null the "best" block from the list
+ */
+ public static function chooseBlock( array $blocks, array $ipChain ) {
+ if ( !count( $blocks ) ) {
+ return null;
+ } elseif ( count( $blocks ) == 1 ) {
+ return $blocks[0];
+ }
+
+ wfProfileIn( __METHOD__ );
+
+ // Sort hard blocks before soft ones and secondarily sort blocks
+ // that disable account creation before those that don't.
+ usort( $blocks, function( Block $a, Block $b ) {
+ $aWeight = (int)$a->isHardblock() . (int)$a->prevents( 'createaccount' );
+ $bWeight = (int)$b->isHardblock() . (int)$b->prevents( 'createaccount' );
+ return strcmp( $bWeight, $aWeight ); // highest weight first
+ } );
+
+ $blocksListExact = array(
+ 'hard' => false,
+ 'disable_create' => false,
+ 'other' => false,
+ 'auto' => false
+ );
+ $blocksListRange = array(
+ 'hard' => false,
+ 'disable_create' => false,
+ 'other' => false,
+ 'auto' => false
+ );
+ $ipChain = array_reverse( $ipChain );
+
+ foreach ( $blocks as $block ) {
+ // Stop searching if we have already have a "better" block. This
+ // is why the order of the blocks matters
+ if ( !$block->isHardblock() && $blocksListExact['hard'] ) {
+ break;
+ } elseif ( !$block->prevents( 'createaccount' ) && $blocksListExact['disable_create'] ) {
+ break;
+ }
+
+ foreach ( $ipChain as $checkip ) {
+ $checkipHex = IP::toHex( $checkip );
+ if ( (string)$block->getTarget() === $checkip ) {
+ if ( $block->isHardblock() ) {
+ $blocksListExact['hard'] = $blocksListExact['hard'] ?: $block;
+ } elseif ( $block->prevents( 'createaccount' ) ) {
+ $blocksListExact['disable_create'] = $blocksListExact['disable_create'] ?: $block;
+ } elseif ( $block->mAuto ) {
+ $blocksListExact['auto'] = $blocksListExact['auto'] ?: $block;
+ } else {
+ $blocksListExact['other'] = $blocksListExact['other'] ?: $block;
+ }
+ // We found closest exact match in the ip list, so go to the next Block
+ break;
+ } elseif ( array_filter( $blocksListExact ) == array()
+ && $block->getRangeStart() <= $checkipHex
+ && $block->getRangeEnd() >= $checkipHex
+ ) {
+ if ( $block->isHardblock() ) {
+ $blocksListRange['hard'] = $blocksListRange['hard'] ?: $block;
+ } elseif ( $block->prevents( 'createaccount' ) ) {
+ $blocksListRange['disable_create'] = $blocksListRange['disable_create'] ?: $block;
+ } elseif ( $block->mAuto ) {
+ $blocksListRange['auto'] = $blocksListRange['auto'] ?: $block;
+ } else {
+ $blocksListRange['other'] = $blocksListRange['other'] ?: $block;
+ }
+ break;
+ }
+ }
+ }
+
+ if ( array_filter( $blocksListExact ) == array() ) {
+ $blocksList = &$blocksListRange;
+ } else {
+ $blocksList = &$blocksListExact;
+ }
+
+ $chosenBlock = null;
+ if ( $blocksList['hard'] ) {
+ $chosenBlock = $blocksList['hard'];
+ } elseif ( $blocksList['disable_create'] ) {
+ $chosenBlock = $blocksList['disable_create'];
+ } elseif ( $blocksList['other'] ) {
+ $chosenBlock = $blocksList['other'];
+ } elseif ( $blocksList['auto'] ) {
+ $chosenBlock = $blocksList['auto'];
+ } else {
+ wfProfileOut( __METHOD__ );
+ throw new MWException( "Proxy block found, but couldn't be classified." );
+ }
+
+ wfProfileOut( __METHOD__ );
+ return $chosenBlock;
+ }
+
/**
* 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
@@ -1085,13 +1280,13 @@ class Block {
*/
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 );
}
@@ -1112,7 +1307,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];
@@ -1198,4 +1393,43 @@ class Block {
public function setBlocker( $user ) {
$this->blocker = $user;
}
+
+ /**
+ * Get the key and parameters for the corresponding error message.
+ *
+ * @since 1.22
+ * @param IContextSource $context
+ * @return array
+ */
+ public function getPermissionsError( IContextSource $context ) {
+ $blocker = $this->getBlocker();
+ if ( $blocker instanceof User ) { // local user
+ $blockerUserpage = $blocker->getUserPage();
+ $link = "[[{$blockerUserpage->getPrefixedText()}|{$blockerUserpage->getText()}]]";
+ } else { // foreign user
+ $link = $blocker;
+ }
+
+ $reason = $this->mReason;
+ if ( $reason == '' ) {
+ $reason = $context->msg( 'blockednoreason' )->text();
+ }
+
+ /* $ip returns who *is* being blocked, $intended contains who was meant to be blocked.
+ * This could be a username, an IP range, or a single IP. */
+ $intended = $this->getTarget();
+
+ $lang = $context->getLanguage();
+ return array(
+ $this->mAuto ? 'autoblockedtext' : 'blockedtext',
+ $link,
+ $reason,
+ $context->getRequest()->getIP(),
+ $this->getByName(),
+ $this->getId(),
+ $lang->formatExpiry( $this->mExpiry ),
+ (string)$intended,
+ $lang->timeanddate( wfTimestamp( TS_MW, $this->mTimestamp ), true ),
+ );
+ }
}
diff --git a/includes/CallableUpdate.php b/includes/CallableUpdate.php
new file mode 100644
index 00000000..6eb55413
--- /dev/null
+++ b/includes/CallableUpdate.php
@@ -0,0 +1,30 @@
+<?php
+
+/**
+ * Deferrable Update for closure/callback
+ */
+class MWCallableUpdate implements DeferrableUpdate {
+
+ /**
+ * @var closure/callabck
+ */
+ private $callback;
+
+ /**
+ * @param callable $callback
+ */
+ public function __construct( $callback ) {
+ if ( !is_callable( $callback ) ) {
+ throw new MWException( 'Not a valid callback/closure!' );
+ }
+ $this->callback = $callback;
+ }
+
+ /**
+ * Run the update
+ */
+ public function doUpdate() {
+ call_user_func( $this->callback );
+ }
+
+}
diff --git a/includes/Category.php b/includes/Category.php
index 868d6c46..126b8fee 100644
--- a/includes/Category.php
+++ b/includes/Category.php
@@ -40,7 +40,8 @@ class Category {
/** Counts of membership (cat_pages, cat_subcats, cat_files) */
private $mPages = null, $mSubcats = null, $mFiles = null;
- private function __construct() { }
+ private function __construct() {
+ }
/**
* Set up all member variables using a database query.
diff --git a/includes/CategoryPage.php b/includes/CategoryPage.php
index 43ab4dbd..ba71aa01 100644
--- a/includes/CategoryPage.php
+++ b/includes/CategoryPage.php
@@ -106,7 +106,13 @@ class CategoryPage extends Article {
unset( $reqArray["from"] );
unset( $reqArray["to"] );
- $viewer = new $this->mCategoryViewerClass( $this->getContext()->getTitle(), $this->getContext(), $from, $until, $reqArray );
+ $viewer = new $this->mCategoryViewerClass(
+ $this->getContext()->getTitle(),
+ $this->getContext(),
+ $from,
+ $until,
+ $reqArray
+ );
$this->getContext()->getOutput()->addHTML( $viewer->getHTML() );
}
}
diff --git a/includes/CategoryViewer.php b/includes/CategoryViewer.php
index 970adb53..55d9c1e5 100644
--- a/includes/CategoryViewer.php
+++ b/includes/CategoryViewer.php
@@ -141,8 +141,17 @@ class CategoryViewer extends ContextSource {
$this->children = array();
$this->children_start_char = array();
if ( $this->showGallery ) {
- $this->gallery = new ImageGallery();
+ // Note that null for mode is taken to mean use default.
+ $mode = $this->getRequest()->getVal( 'gallerymode', null );
+ try {
+ $this->gallery = ImageGalleryBase::factory( $mode );
+ } catch ( MWException $e ) {
+ // User specified something invalid, fallback to default.
+ $this->gallery = ImageGalleryBase::factory();
+ }
+
$this->gallery->setHideBadImages();
+ $this->gallery->setContext( $this->getContext() );
} else {
$this->imgsNoGallery = array();
$this->imgsNoGallery_start_char = array();
@@ -526,7 +535,10 @@ class CategoryViewer extends ContextSource {
$first = true;
foreach ( $colContents as $char => $articles ) {
- $ret .= '<h3>' . htmlspecialchars( $char );
+ # Change space to non-breaking space to keep headers aligned
+ $h3char = $char === ' ' ? '&#160;' : htmlspecialchars( $char );
+
+ $ret .= '<h3>' . $h3char;
if ( $first && $char === $prevchar ) {
# We're continuing a previous chunk at the top of a new
# column, so add " cont." after the letter.
@@ -645,28 +657,23 @@ class CategoryViewer extends ContextSource {
* returned? This function says what. Each type is considered independently
* of the other types.
*
- * Note for grepping: uses the messages category-article-count,
- * category-article-count-limited, category-subcat-count,
- * category-subcat-count-limited, category-file-count,
- * category-file-count-limited.
- *
* @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.
+ * @return string: A message giving the number of items, to output to HTML.
*/
private function getCountMessage( $rescnt, $dbcnt, $type ) {
- # There are three cases:
- # 1) The category table figure seems sane. It might be wrong, but
- # we can't do anything about it if we don't recalculate it on ev-
- # ery category view.
- # 2) The category table figure isn't sane, like it's smaller than the
- # number of actual results, *but* the number of results is less
- # than $this->limit and there's no offset. In this case we still
- # know the right figure.
- # 3) We have no idea.
-
- # Check if there's a "from" or "until" for anything
+ // There are three cases:
+ // 1) The category table figure seems sane. It might be wrong, but
+ // we can't do anything about it if we don't recalculate it on ev-
+ // ery category view.
+ // 2) The category table figure isn't sane, like it's smaller than the
+ // number of actual results, *but* the number of results is less
+ // than $this->limit and there's no offset. In this case we still
+ // know the right figure.
+ // 3) We have no idea.
+
+ // Check if there's a "from" or "until" for anything
// This is a little ugly, but we seem to use different names
// for the paging types then for the messages.
@@ -686,19 +693,22 @@ class CategoryViewer extends ContextSource {
if ( $dbcnt == $rescnt ||
( ( $rescnt == $this->limit || $fromOrUntil ) && $dbcnt > $rescnt )
) {
- # Case 1: seems sane.
+ // Case 1: seems sane.
$totalcnt = $dbcnt;
} elseif ( $rescnt < $this->limit && !$fromOrUntil ) {
- # Case 2: not sane, but salvageable. Use the number of results.
- # Since there are fewer than 200, we can also take this opportunity
- # to refresh the incorrect category table entry -- which should be
- # quick due to the small number of entries.
+ // Case 2: not sane, but salvageable. Use the number of results.
+ // Since there are fewer than 200, we can also take this opportunity
+ // to refresh the incorrect category table entry -- which should be
+ // quick due to the small number of entries.
$totalcnt = $rescnt;
$this->cat->refreshCounts();
} else {
- # Case 3: hopeless. Don't give a total count at all.
+ // Case 3: hopeless. Don't give a total count at all.
+ // Messages: category-subcat-count-limited, category-article-count-limited,
+ // category-file-count-limited
return $this->msg( "category-$type-count-limited" )->numParams( $rescnt )->parseAsBlock();
}
+ // Messages: category-subcat-count, category-article-count, category-file-count
return $this->msg( "category-$type-count" )->numParams( $rescnt, $totalcnt )->parseAsBlock();
}
}
diff --git a/includes/Cdb.php b/includes/Cdb.php
index a142c7cb..81c0afe1 100644
--- a/includes/Cdb.php
+++ b/includes/Cdb.php
@@ -133,7 +133,7 @@ class CdbReader_DBA {
}
function close() {
- if( isset( $this->handle ) ) {
+ if ( isset( $this->handle ) ) {
dba_close( $this->handle );
}
unset( $this->handle );
@@ -164,7 +164,7 @@ class CdbWriter_DBA {
}
function close() {
- if( isset( $this->handle ) ) {
+ if ( isset( $this->handle ) ) {
dba_close( $this->handle );
}
if ( wfIsWindows() ) {
diff --git a/includes/Cdb_PHP.php b/includes/Cdb_PHP.php
index 71b55f87..a38b9a86 100644
--- a/includes/Cdb_PHP.php
+++ b/includes/Cdb_PHP.php
@@ -73,10 +73,10 @@ class CdbFunctions {
public static function hash( $s ) {
$h = 5381;
for ( $i = 0; $i < strlen( $s ); $i++ ) {
- $h5 = ($h << 5) & 0xffffffff;
+ $h5 = ( $h << 5 ) & 0xffffffff;
// Do a 32-bit sum
// Inlined here for speed
- $sum = ($h & 0x3fffffff) + ($h5 & 0x3fffffff);
+ $sum = ( $h & 0x3fffffff ) + ( $h5 & 0x3fffffff );
$h =
(
( $sum & 0x40000000 ? 1 : 0 )
@@ -138,7 +138,7 @@ class CdbReader_PHP extends CdbReader {
}
function close() {
- if( isset( $this->handle ) ) {
+ if ( isset( $this->handle ) ) {
fclose( $this->handle );
}
unset( $this->handle );
@@ -332,7 +332,7 @@ 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 ) ) {
@@ -411,7 +411,7 @@ class CdbWriter_PHP extends CdbWriter {
// Calculate the number of items that will be in each hashtable
$counts = array_fill( 0, 256, 0 );
foreach ( $this->hplist as $item ) {
- ++ $counts[ 255 & $item['h'] ];
+ ++ $counts[255 & $item['h']];
}
// Fill in $starts with the *end* indexes
@@ -450,9 +450,11 @@ class CdbWriter_PHP extends CdbWriter {
$hp = $packedTables[$starts[$i] + $u];
$where = CdbFunctions::unsignedMod(
CdbFunctions::unsignedShiftRight( $hp['h'], 8 ), $len );
- while ( $hashtable[$where]['p'] )
- if ( ++$where == $len )
+ while ( $hashtable[$where]['p'] ) {
+ if ( ++$where == $len ) {
$where = 0;
+ }
+ }
$hashtable[$where] = $hp;
}
diff --git a/includes/ChangeTags.php b/includes/ChangeTags.php
index 3adf58f8..3fc27f9a 100644
--- a/includes/ChangeTags.php
+++ b/includes/ChangeTags.php
@@ -34,17 +34,18 @@ class ChangeTags {
* - classes: Array of strings: CSS classes used in the generated html, one class for each tag
*
*/
- static function formatSummaryRow( $tags, $page ) {
+ public static function formatSummaryRow( $tags, $page ) {
global $wgLang;
- if( !$tags )
+ if ( !$tags ) {
return array( '', array() );
+ }
$classes = array();
$tags = explode( ',', $tags );
$displayTags = array();
- foreach( $tags as $tag ) {
+ foreach ( $tags as $tag ) {
$displayTags[] = Xml::tags(
'span',
array( 'class' => 'mw-tag-marker ' .
@@ -53,7 +54,10 @@ class ChangeTags {
);
$classes[] = Sanitizer::escapeClass( "mw-tag-$tag" );
}
- $markers = wfMessage( 'parentheses' )->rawParams( $wgLang->commaList( $displayTags ) )->text();
+ $markers = wfMessage( 'tag-list-wrapper' )
+ ->numParams( count( $displayTags ) )
+ ->rawParams( $wgLang->commaList( $displayTags ) )
+ ->parse();
$markers = Xml::tags( 'span', array( 'class' => 'mw-tag-markers' ), $markers );
return array( $markers, $classes );
@@ -67,7 +71,7 @@ class ChangeTags {
* @return String: Short description of the tag from "mediawiki:tag-$tag" if this message exists,
* html-escaped version of $tag otherwise
*/
- static function tagDescription( $tag ) {
+ public static function tagDescription( $tag ) {
$msg = wfMessage( "tag-$tag" );
return $msg->exists() ? $msg->parse() : htmlspecialchars( $tag );
}
@@ -86,28 +90,29 @@ class ChangeTags {
*
* @exception MWException when $rc_id, $rev_id and $log_id are all null
*/
- static function addTags( $tags, $rc_id = null, $rev_id = null, $log_id = null, $params = null ) {
+ public static function addTags( $tags, $rc_id = null, $rev_id = null, $log_id = null, $params = null ) {
if ( !is_array( $tags ) ) {
$tags = array( $tags );
}
$tags = array_filter( $tags ); // Make sure we're submitting all tags...
- if( !$rc_id && !$rev_id && !$log_id ) {
- throw new MWException( "At least one of: RCID, revision ID, and log ID MUST be specified when adding a tag to a change!" );
+ if ( !$rc_id && !$rev_id && !$log_id ) {
+ throw new MWException( 'At least one of: RCID, revision ID, and log ID MUST be ' .
+ 'specified when adding a tag to a change!' );
}
$dbr = wfGetDB( DB_SLAVE );
// Might as well look for rcids and so on.
- if( !$rc_id ) {
+ if ( !$rc_id ) {
$dbr = wfGetDB( DB_MASTER ); // Info might be out of date, somewhat fractionally, on slave.
- if( $log_id ) {
+ if ( $log_id ) {
$rc_id = $dbr->selectField( 'recentchanges', 'rc_id', array( 'rc_logid' => $log_id ), __METHOD__ );
- } elseif( $rev_id ) {
+ } elseif ( $rev_id ) {
$rc_id = $dbr->selectField( 'recentchanges', 'rc_id', array( 'rc_this_oldid' => $rev_id ), __METHOD__ );
}
- } elseif( !$log_id && !$rev_id ) {
+ } elseif ( !$log_id && !$rev_id ) {
$dbr = wfGetDB( DB_MASTER ); // Info might be out of date, somewhat fractionally, on slave.
$log_id = $dbr->selectField( 'recentchanges', 'rc_logid', array( 'rc_id' => $rc_id ), __METHOD__ );
$rev_id = $dbr->selectField( 'recentchanges', 'rc_this_oldid', array( 'rc_id' => $rc_id ), __METHOD__ );
@@ -138,7 +143,7 @@ class ChangeTags {
// Insert the tags rows.
$tagsRows = array();
- foreach( $tags as $tag ) { // Filter so we don't insert NULLs as zero accidentally.
+ foreach ( $tags as $tag ) { // Filter so we don't insert NULLs as zero accidentally.
$tagsRows[] = array_filter(
array(
'ct_tag' => $tag,
@@ -169,18 +174,18 @@ class ChangeTags {
*
* @throws MWException When unable to determine appropriate JOIN condition for tagging
*/
- static function modifyDisplayQuery( &$tables, &$fields, &$conds,
+ public static function modifyDisplayQuery( &$tables, &$fields, &$conds,
&$join_conds, &$options, $filter_tag = false ) {
global $wgRequest, $wgUseTagFilter;
- if( $filter_tag === false ) {
+ if ( $filter_tag === false ) {
$filter_tag = $wgRequest->getVal( 'tagfilter' );
}
// Figure out which conditions can be done.
if ( in_array( 'recentchanges', $tables ) ) {
$join_cond = 'rc_id';
- } elseif( in_array( 'logging', $tables ) ) {
+ } elseif ( in_array( 'logging', $tables ) ) {
$join_cond = 'log_id';
} elseif ( in_array( 'revision', $tables ) ) {
$join_cond = 'rev_id';
@@ -193,14 +198,12 @@ class ChangeTags {
$join_conds['tag_summary'] = array( 'LEFT JOIN', "ts_$join_cond=$join_cond" );
$fields[] = 'ts_tags';
- if( $wgUseTagFilter && $filter_tag ) {
+ if ( $wgUseTagFilter && $filter_tag ) {
// Somebody wants to filter on a tag.
// Add an INNER JOIN on change_tag
// FORCE INDEX -- change_tags will almost ALWAYS be the correct query plan.
- global $wgOldChangeTagsIndex;
- $index = $wgOldChangeTagsIndex ? 'ct_tag' : 'change_tag_tag_id';
- $options['USE INDEX'] = array( 'change_tag' => $index );
+ $options['USE INDEX'] = array( 'change_tag' => 'change_tag_tag_id' );
unset( $options['FORCE INDEX'] );
$tables[] = 'change_tag';
$join_conds['change_tag'] = array( 'INNER JOIN', "ct_$join_cond=$join_cond" );
@@ -252,7 +255,7 @@ class ChangeTags {
*
* @return Array of strings: tags
*/
- static function listDefinedTags() {
+ public static function listDefinedTags() {
// Caching...
global $wgMemc;
$key = wfMemcKey( 'valid-tags' );
@@ -278,4 +281,34 @@ class ChangeTags {
$wgMemc->set( $key, $emptyTags, 300 );
return $emptyTags;
}
+
+ /**
+ * Returns a map of any tags used on the wiki to number of edits
+ * tagged with them, ordered descending by the hitcount.
+ *
+ * @return array Array of string => int
+ */
+ public static function tagUsageStatistics() {
+ $out = array();
+
+ $dbr = wfGetDB( DB_SLAVE );
+ $res = $dbr->select(
+ 'change_tag',
+ array( 'ct_tag', 'hitcount' => 'count(*)' ),
+ array(),
+ __METHOD__,
+ array( 'GROUP BY' => 'ct_tag', 'ORDER BY' => 'hitcount DESC' )
+ );
+
+ foreach ( $res as $row ) {
+ $out[$row->ct_tag] = $row->hitcount;
+ }
+ foreach ( self::listDefinedTags() as $tag ) {
+ if ( !isset( $out[$tag] ) ) {
+ $out[$tag] = 0;
+ }
+ }
+
+ return $out;
+ }
}
diff --git a/includes/ChangesFeed.php b/includes/ChangesFeed.php
index 476de5bd..0736c507 100644
--- a/includes/ChangesFeed.php
+++ b/includes/ChangesFeed.php
@@ -54,7 +54,7 @@ class ChangesFeed {
return false;
}
- if( !array_key_exists( $this->format, $wgFeedClasses ) ) {
+ if ( !array_key_exists( $this->format, $wgFeedClasses ) ) {
// falling back to atom
$this->format = 'atom';
}
@@ -92,7 +92,7 @@ class ChangesFeed {
* gets it quick too.
*/
$cachedFeed = $this->loadFromCache( $lastmod, $timekey, $key );
- if( is_string( $cachedFeed ) ) {
+ if ( is_string( $cachedFeed ) ) {
wfDebug( "RC: Outputting cached feed\n" );
$feed->httpHeaders();
echo $cachedFeed;
@@ -134,7 +134,7 @@ class ChangesFeed {
$feedLastmod = $messageMemc->get( $timekey );
- if( ( $wgFeedCacheTimeout > 0 ) && $feedLastmod ) {
+ 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
@@ -146,7 +146,7 @@ class ChangesFeed {
$feedLastmodUnix = wfTimestamp( TS_UNIX, $feedLastmod );
$lastmodUnix = wfTimestamp( TS_UNIX, $lastmod );
- if( $feedAge < $wgFeedCacheTimeout || $feedLastmodUnix > $lastmodUnix) {
+ if ( $feedAge < $wgFeedCacheTimeout || $feedLastmodUnix > $lastmodUnix ) {
wfDebug( "RC: loading feed from cache ($key; $feedLastmod; $lastmod)...\n" );
if ( $feedLastmodUnix < $lastmodUnix ) {
$wgOut->setLastModified( $feedLastmod ); // bug 21916
@@ -172,30 +172,32 @@ class ChangesFeed {
# Merge adjacent edits by one user
$sorted = array();
$n = 0;
- foreach( $rows as $obj ) {
- if( $n > 0 &&
+ foreach ( $rows as $obj ) {
+ if ( $n > 0 &&
$obj->rc_type == RC_EDIT &&
$obj->rc_namespace >= 0 &&
- $obj->rc_cur_id == $sorted[$n-1]->rc_cur_id &&
- $obj->rc_user_text == $sorted[$n-1]->rc_user_text ) {
- $sorted[$n-1]->rc_last_oldid = $obj->rc_last_oldid;
+ $obj->rc_cur_id == $sorted[$n - 1]->rc_cur_id &&
+ $obj->rc_user_text == $sorted[$n - 1]->rc_user_text ) {
+ $sorted[$n - 1]->rc_last_oldid = $obj->rc_last_oldid;
} else {
$sorted[$n] = $obj;
$n++;
}
}
- foreach( $sorted as $obj ) {
+ foreach ( $sorted as $obj ) {
$title = Title::makeTitle( $obj->rc_namespace, $obj->rc_title );
- $talkpage = MWNamespace::canTalk( $obj->rc_namespace ) ? $title->getTalkPage()->getFullUrl() : '';
+ $talkpage = MWNamespace::canTalk( $obj->rc_namespace ) ? $title->getTalkPage()->getFullURL() : '';
// Skip items with deleted content (avoids partially complete/inconsistent output)
- if( $obj->rc_deleted ) continue;
+ if ( $obj->rc_deleted ) {
+ continue;
+ }
if ( $obj->rc_this_oldid ) {
- $url = $title->getFullURL(
- 'diff=' . $obj->rc_this_oldid .
- '&oldid=' . $obj->rc_last_oldid
- );
+ $url = $title->getFullURL( array(
+ 'diff' => $obj->rc_this_oldid,
+ 'oldid' => $obj->rc_last_oldid,
+ ) );
} else {
// log entry or something like that.
$url = $title->getFullURL();
@@ -206,7 +208,8 @@ class ChangesFeed {
FeedUtils::formatDiff( $obj ),
$url,
$obj->rc_timestamp,
- ( $obj->rc_deleted & Revision::DELETED_USER ) ? wfMessage( 'rev-deleted-user' )->escaped() : $obj->rc_user_text,
+ ( $obj->rc_deleted & Revision::DELETED_USER )
+ ? wfMessage( 'rev-deleted-user' )->escaped() : $obj->rc_user_text,
$talkpage
);
$feed->outItem( $item );
diff --git a/includes/ChangesList.php b/includes/ChangesList.php
deleted file mode 100644
index 8461001e..00000000
--- a/includes/ChangesList.php
+++ /dev/null
@@ -1,1304 +0,0 @@
-<?php
-/**
- * Classes to show lists of changes.
- *
- * These can be:
- * - watchlist
- * - related changes
- * - recent changes
- *
- * 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
- */
-
-/**
- * @todo document
- */
-class RCCacheEntry extends RecentChange {
- var $secureName, $link;
- var $curlink, $difflink, $lastlink, $usertalklink, $versionlink;
- var $userlink, $timestamp, $watched;
-
- /**
- * @param $rc RecentChange
- * @return RCCacheEntry
- */
- static function newFromParent( $rc ) {
- $rc2 = new RCCacheEntry;
- $rc2->mAttribs = $rc->mAttribs;
- $rc2->mExtra = $rc->mExtra;
- return $rc2;
- }
-}
-
-/**
- * Base class for all changes lists
- */
-class ChangesList extends ContextSource {
-
- /**
- * @var Skin
- */
- public $skin;
-
- protected $watchlist = false;
-
- protected $message;
-
- /**
- * Changeslist constructor
- *
- * @param $obj Skin or IContextSource
- */
- public function __construct( $obj ) {
- if ( $obj instanceof IContextSource ) {
- $this->setContext( $obj );
- $this->skin = $obj->getSkin();
- } else {
- $this->setContext( $obj->getContext() );
- $this->skin = $obj;
- }
- $this->preCacheMessages();
- }
-
- /**
- * Fetch an appropriate changes list class for the main context
- * This first argument used to be an User object.
- *
- * @deprecated in 1.18; use newFromContext() instead
- * @param string|User $unused Unused
- * @return ChangesList|EnhancedChangesList|OldChangesList derivative
- */
- public static function newFromUser( $unused ) {
- wfDeprecated( __METHOD__, '1.18' );
- return self::newFromContext( RequestContext::getMain() );
- }
-
- /**
- * Fetch an appropriate changes list class for the specified context
- * Some users might want to use an enhanced list format, for instance
- *
- * @param $context IContextSource to use
- * @return ChangesList|EnhancedChangesList|OldChangesList derivative
- */
- public static function newFromContext( IContextSource $context ) {
- $user = $context->getUser();
- $sk = $context->getSkin();
- $list = null;
- if( wfRunHooks( 'FetchChangesList', array( $user, &$sk, &$list ) ) ) {
- $new = $context->getRequest()->getBool( 'enhanced', $user->getOption( 'usenewrc' ) );
- return $new ? new EnhancedChangesList( $context ) : new OldChangesList( $context );
- } else {
- return $list;
- }
- }
-
- /**
- * Sets the list to use a "<li class='watchlist-(namespace)-(page)'>" tag
- * @param $value Boolean
- */
- public function setWatchlistDivs( $value = true ) {
- $this->watchlist = $value;
- }
-
- /**
- * As we use the same small set of messages in various methods and that
- * they are called often, we call them once and save them in $this->message
- */
- private function preCacheMessages() {
- if( !isset( $this->message ) ) {
- foreach ( explode( ' ', 'cur diff hist last blocklink history ' .
- 'semicolon-separator pipe-separator' ) as $msg ) {
- $this->message[$msg] = $this->msg( $msg )->escaped();
- }
- }
- }
-
- /**
- * Returns the appropriate flags for new page, minor change and patrolling
- * @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 ) {
- $f .= isset( $flags[$flag] ) && $flags[$flag]
- ? self::flag( $flag )
- : $nothing;
- }
- return $f;
- }
-
- /**
- * Provide the "<abbr>" element appropriate to a given abbreviated flag,
- * namely the flag indicating a new page, a minor edit, a bot edit, or an
- * unpatrolled edit. By default in English it will contain "N", "m", "b",
- * "!" respectively, plus it will have an appropriate title and class.
- *
- * @param string $flag 'newpage', 'unpatrolled', 'minor', or 'bot'
- * @return String: Raw HTML
- */
- public static function flag( $flag ) {
- static $messages = null;
- if ( is_null( $messages ) ) {
- $messages = array(
- 'newpage' => array( 'newpageletter', 'recentchanges-label-newpage' ),
- 'minoredit' => array( 'minoreditletter', 'recentchanges-label-minor' ),
- 'botedit' => array( 'boteditletter', 'recentchanges-label-bot' ),
- 'unpatrolled' => array( 'unpatrolledletter', 'recentchanges-label-unpatrolled' ),
- );
- foreach( $messages as &$value ) {
- $value[0] = wfMessage( $value[0] )->escaped();
- $value[1] = wfMessage( $value[1] )->escaped();
- }
- }
-
- # Inconsistent naming, bleh
- $map = array(
- 'newpage' => 'newpage',
- 'minor' => 'minoredit',
- 'bot' => 'botedit',
- 'unpatrolled' => 'unpatrolled',
- 'minoredit' => 'minoredit',
- 'botedit' => 'botedit',
- );
- $flag = $map[$flag];
-
- return "<abbr class='$flag' title='" . $messages[$flag][1] . "'>" . $messages[$flag][0] . '</abbr>';
- }
-
- /**
- * Returns text for the start of the tabular part of RC
- * @return String
- */
- public function beginRecentChangesList() {
- $this->rc_cache = array();
- $this->rcMoveIndex = 0;
- $this->rcCacheIndex = 0;
- $this->lastdate = '';
- $this->rclistOpen = false;
- $this->getOutput()->addModuleStyles( 'mediawiki.special.changeslist' );
- return '';
- }
-
- /**
- * Show formatted char difference
- * @param $old Integer: bytes
- * @param $new Integer: bytes
- * @param $context IContextSource context to use
- * @return String
- */
- public static function showCharacterDifference( $old, $new, IContextSource $context = null ) {
- global $wgRCChangedSizeThreshold, $wgMiserMode;
-
- if ( !$context ) {
- $context = RequestContext::getMain();
- }
-
- $new = (int)$new;
- $old = (int)$old;
- $szdiff = $new - $old;
-
- $lang = $context->getLanguage();
- $code = $lang->getCode();
- static $fastCharDiff = array();
- if ( !isset( $fastCharDiff[$code] ) ) {
- $fastCharDiff[$code] = $wgMiserMode || $context->msg( 'rc-change-size' )->plain() === '$1';
- }
-
- $formattedSize = $lang->formatNum( $szdiff );
-
- if ( !$fastCharDiff[$code] ) {
- $formattedSize = $context->msg( 'rc-change-size', $formattedSize )->text();
- }
-
- if( abs( $szdiff ) > abs( $wgRCChangedSizeThreshold ) ) {
- $tag = 'strong';
- } else {
- $tag = 'span';
- }
-
- if ( $szdiff === 0 ) {
- $formattedSizeClass = 'mw-plusminus-null';
- }
- if ( $szdiff > 0 ) {
- $formattedSize = '+' . $formattedSize;
- $formattedSizeClass = 'mw-plusminus-pos';
- }
- if ( $szdiff < 0 ) {
- $formattedSizeClass = 'mw-plusminus-neg';
- }
-
- $formattedTotalSize = $context->msg( 'rc-change-size-new' )->numParams( $new )->text();
-
- return Html::element( $tag,
- array( 'dir' => 'ltr', 'class' => $formattedSizeClass, 'title' => $formattedTotalSize ),
- $context->msg( 'parentheses', $formattedSize )->plain() ) . $lang->getDirMark();
- }
-
- /**
- * Format the character difference of one or several changes.
- *
- * @param $old RecentChange
- * @param $new RecentChange last change to use, if not provided, $old will be used
- * @return string HTML fragment
- */
- public function formatCharacterDifference( RecentChange $old, RecentChange $new = null ) {
- $oldlen = $old->mAttribs['rc_old_len'];
-
- if ( $new ) {
- $newlen = $new->mAttribs['rc_new_len'];
- } else {
- $newlen = $old->mAttribs['rc_new_len'];
- }
-
- if( $oldlen === null || $newlen === null ) {
- return '';
- }
-
- return self::showCharacterDifference( $oldlen, $newlen, $this->getContext() );
- }
-
- /**
- * Returns text for the end of RC
- * @return String
- */
- public function endRecentChangesList() {
- if( $this->rclistOpen ) {
- return "</ul>\n";
- } else {
- return '';
- }
- }
-
- /**
- * @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() );
- if( $date != $this->lastdate ) {
- if( $this->lastdate != '' ) {
- $s .= "</ul>\n";
- }
- $s .= Xml::element( 'h4', null, $date ) . "\n<ul class=\"special\">";
- $this->lastdate = $date;
- $this->rclistOpen = true;
- }
- }
-
- /**
- * @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();
- $s .= $this->msg( 'parentheses' )->rawParams( Linker::linkKnown( $title, $logname ) )->escaped();
- }
-
- /**
- * @param string $s HTML to update
- * @param $rc RecentChange
- * @param $unpatrolled
- */
- public function insertDiffHist( &$s, &$rc, $unpatrolled ) {
- # Diff link
- if( $rc->mAttribs['rc_type'] == RC_NEW || $rc->mAttribs['rc_type'] == RC_LOG ) {
- $diffLink = $this->message['diff'];
- } elseif ( !self::userCan( $rc, Revision::DELETED_TEXT, $this->getUser() ) ) {
- $diffLink = $this->message['diff'];
- } else {
- $query = array(
- 'curid' => $rc->mAttribs['rc_cur_id'],
- 'diff' => $rc->mAttribs['rc_this_oldid'],
- 'oldid' => $rc->mAttribs['rc_last_oldid']
- );
-
- if( $unpatrolled ) {
- $query['rcid'] = $rc->mAttribs['rc_id'];
- };
-
- $diffLink = Linker::linkKnown(
- $rc->getTitle(),
- $this->message['diff'],
- array( 'tabindex' => $rc->counter ),
- $query
- );
- }
- $diffhist = $diffLink . $this->message['pipe-separator'];
- # History link
- $diffhist .= Linker::linkKnown(
- $rc->getTitle(),
- $this->message['hist'],
- array(),
- array(
- 'curid' => $rc->mAttribs['rc_cur_id'],
- 'action' => 'history'
- )
- );
- $s .= $this->msg( 'parentheses' )->rawParams( $diffhist )->escaped() . ' <span class="mw-changeslist-separator">. .</span> ';
- }
-
- /**
- * @param string $s HTML to update
- * @param $rc RecentChange
- * @param $unpatrolled
- * @param $watched
- */
- public function insertArticleLink( &$s, &$rc, $unpatrolled, $watched ) {
- # If it's a new article, there is no diff link, but if it hasn't been
- # patrolled yet, we need to give users a way to do so
- $params = array();
-
- if ( $unpatrolled && $rc->mAttribs['rc_type'] == RC_NEW ) {
- $params['rcid'] = $rc->mAttribs['rc_id'];
- }
-
- $articlelink = Linker::linkKnown(
- $rc->getTitle(),
- null,
- array( 'class' => 'mw-changeslist-title' ),
- $params
- );
- if( $this->isDeleted( $rc, Revision::DELETED_TEXT ) ) {
- $articlelink = '<span class="history-deleted">' . $articlelink . '</span>';
- }
- # To allow for boldening pages watched by this user
- $articlelink = "<span class=\"mw-title\">{$articlelink}</span>";
- # RTL/LTR marker
- $articlelink .= $this->getLanguage()->getDirMark();
-
- wfRunHooks( 'ChangesListInsertArticleLink',
- array( &$this, &$articlelink, &$s, &$rc, $unpatrolled, $watched ) );
-
- $s .= " $articlelink";
- }
-
- /**
- * Get the timestamp from $rc formatted with current user's settings
- * and a separator
- *
- * @param $rc RecentChange
- * @return string HTML fragment
- */
- 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
- * @param &$rc RecentChange
- */
- public function insertUserRelatedLinks( &$s, &$rc ) {
- if( $this->isDeleted( $rc, Revision::DELETED_USER ) ) {
- $s .= ' <span class="history-deleted">' . $this->msg( 'rev-deleted-user' )->escaped() . '</span>';
- } else {
- $s .= $this->getLanguage()->getDirMark() . Linker::userLink( $rc->mAttribs['rc_user'],
- $rc->mAttribs['rc_user_text'] );
- $s .= Linker::userToolLinks( $rc->mAttribs['rc_user'], $rc->mAttribs['rc_user_text'] );
- }
- }
-
- /**
- * Insert a formatted action
- *
- * @param $rc RecentChange
- * @return string
- */
- public function insertLogEntry( $rc ) {
- $formatter = LogFormatter::newFromRow( $rc->mAttribs );
- $formatter->setContext( $this->getContext() );
- $formatter->setShowUserToolLinks( true );
- $mark = $this->getLanguage()->getDirMark();
- return $formatter->getActionText() . " $mark" . $formatter->getComment();
- }
-
- /**
- * Insert a formatted comment
- * @param $rc RecentChange
- * @return string
- */
- public function insertComment( $rc ) {
- if( $rc->mAttribs['rc_type'] != RC_MOVE && $rc->mAttribs['rc_type'] != RC_MOVE_OVER_REDIRECT ) {
- if( $this->isDeleted( $rc, Revision::DELETED_COMMENT ) ) {
- return ' <span class="history-deleted">' . $this->msg( 'rev-deleted-comment' )->escaped() . '</span>';
- } else {
- return Linker::commentBlock( $rc->mAttribs['rc_comment'], $rc->getTitle() );
- }
- }
- return '';
- }
-
- /**
- * Check whether to enable recent changes patrol features
- * @return Boolean
- */
- public static function usePatrol() {
- global $wgUser;
- return $wgUser->useRCPatrol();
- }
-
- /**
- * Returns the string which indicates the number of watching users
- * @return string
- */
- protected function numberofWatchingusers( $count ) {
- static $cache = array();
- if( $count > 0 ) {
- if( !isset( $cache[$count] ) ) {
- $cache[$count] = $this->msg( 'number_of_watching_users_RCview' )->numParams( $count )->escaped();
- }
- return $cache[$count];
- } else {
- return '';
- }
- }
-
- /**
- * Determine if said field of a revision is hidden
- * @param $rc RCCacheEntry
- * @param $field Integer: one of DELETED_* bitfield constants
- * @return Boolean
- */
- public static function isDeleted( $rc, $field ) {
- return ( $rc->mAttribs['rc_deleted'] & $field ) == $field;
- }
-
- /**
- * Determine if the current user is allowed to view a particular
- * field of this revision, if it's marked as deleted.
- * @param $rc RCCacheEntry
- * @param $field Integer
- * @param $user User object to check, or null to use $wgUser
- * @return Boolean
- */
- public static function userCan( $rc, $field, User $user = null ) {
- if( $rc->mAttribs['rc_type'] == RC_LOG ) {
- return LogEventsList::userCanBitfield( $rc->mAttribs['rc_deleted'], $field, $user );
- } else {
- return Revision::userCanBitfield( $rc->mAttribs['rc_deleted'], $field, $user );
- }
- }
-
- /**
- * @param $link string
- * @param $watched bool
- * @return string
- */
- protected function maybeWatchedLink( $link, $watched = false ) {
- if( $watched ) {
- return '<strong class="mw-watched">' . $link . '</strong>';
- } else {
- return '<span class="mw-rc-unwatched">' . $link . '</span>';
- }
- }
-
- /** Inserts a rollback link
- *
- * @param $s string
- * @param $rc RecentChange
- */
- public function insertRollback( &$s, &$rc ) {
- if( $rc->mAttribs['rc_type'] != RC_NEW && $rc->mAttribs['rc_this_oldid'] && $rc->mAttribs['rc_cur_id'] ) {
- $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'] )
- {
- $rev = new Revision( array(
- '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']
- ) );
- $s .= ' '.Linker::generateRollback( $rev, $this->getContext() );
- }
- }
- }
-
- /**
- * @param $s string
- * @param $rc RecentChange
- * @param $classes
- */
- public function insertTags( &$s, &$rc, &$classes ) {
- if ( empty( $rc->mAttribs['ts_tags'] ) )
- return;
-
- 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 subclasses to add anything special.
- }
-
- protected function showAsUnpatrolled( RecentChange $rc ) {
- $unpatrolled = false;
- if ( !$rc->mAttribs['rc_patrolled'] ) {
- if ( $this->getUser()->useRCPatrol() ) {
- $unpatrolled = true;
- } elseif ( $this->getUser()->useNPPatrol() && $rc->mAttribs['rc_type'] == RC_NEW ) {
- $unpatrolled = true;
- }
- }
- return $unpatrolled;
- }
-}
-
-/**
- * Generate a list of changes using the good old system (no javascript)
- */
-class OldChangesList extends ChangesList {
- /**
- * Format a line using the old system (aka without any javascript).
- *
- * @param $rc RecentChange, passed by reference
- * @param bool $watched (default false)
- * @param int $linenumber (default null)
- *
- * @return string|bool
- */
- public function recentChangesLine( &$rc, $watched = false, $linenumber = null ) {
- global $wgRCShowChangedSize;
- wfProfileIn( __METHOD__ );
-
- # Should patrol-related stuff be shown?
- $unpatrolled = $this->showAsUnpatrolled( $rc );
-
- $dateheader = ''; // $s now contains only <li>...</li>, for hooks' convenience.
- $this->insertDateHeader( $dateheader, $rc->mAttribs['rc_timestamp'] );
-
- $s = '';
- $classes = array();
- // use mw-line-even/mw-line-odd class only if linenumber is given (feature from bug 14468)
- if( $linenumber ) {
- if( $linenumber & 1 ) {
- $classes[] = 'mw-line-odd';
- }
- else {
- $classes[] = 'mw-line-even';
- }
- }
-
- // Indicate watched status on the line to allow for more
- // comprehensive styling.
- $classes[] = $watched ? 'mw-changeslist-line-watched' : 'mw-changeslist-line-not-watched';
-
- // Moved pages (very very old, not supported anymore)
- if( $rc->mAttribs['rc_type'] == RC_MOVE || $rc->mAttribs['rc_type'] == RC_MOVE_OVER_REDIRECT ) {
- // Log entries
- } elseif( $rc->mAttribs['rc_log_type'] ) {
- $logtitle = SpecialPage::getTitleFor( 'Log', $rc->mAttribs['rc_log_type'] );
- $this->insertLog( $s, $logtitle, $rc->mAttribs['rc_log_type'] );
- // Log entries (old format) or log targets, and special pages
- } elseif( $rc->mAttribs['rc_namespace'] == NS_SPECIAL ) {
- list( $name, $subpage ) = SpecialPageFactory::resolveAlias( $rc->mAttribs['rc_title'] );
- if( $name == 'Log' ) {
- $this->insertLog( $s, $rc->getTitle(), $subpage );
- }
- // Regular entries
- } else {
- $this->insertDiffHist( $s, $rc, $unpatrolled );
- # M, N, b and ! (minor, new, bot and unpatrolled)
- $s .= $this->recentChangesFlags(
- array(
- 'newpage' => $rc->mAttribs['rc_type'] == RC_NEW,
- 'minor' => $rc->mAttribs['rc_minor'],
- 'unpatrolled' => $unpatrolled,
- 'bot' => $rc->mAttribs['rc_bot']
- ),
- ''
- );
- $this->insertArticleLink( $s, $rc, $unpatrolled, $watched );
- }
- # Edit/log timestamp
- $this->insertTimestamp( $s, $rc );
- # Bytes added or removed
- if ( $wgRCShowChangedSize ) {
- $cd = $this->formatCharacterDifference( $rc );
- if ( $cd !== '' ) {
- $s .= $cd . ' <span class="mw-changeslist-separator">. .</span> ';
- }
- }
-
- if ( $rc->mAttribs['rc_type'] == RC_LOG ) {
- $s .= $this->insertLogEntry( $rc );
- } else {
- # User tool links
- $this->insertUserRelatedLinks( $s, $rc );
- # LTR/RTL direction mark
- $s .= $this->getLanguage()->getDirMark();
- $s .= $this->insertComment( $rc );
- }
-
- # Tags
- $this->insertTags( $s, $rc, $classes );
- # Rollback
- $this->insertRollback( $s, $rc );
- # For subclasses
- $this->insertExtra( $s, $rc, $classes );
-
- # How many users watch this page
- if( $rc->numberofWatchingusers > 0 ) {
- $s .= ' ' . $this->numberofWatchingusers( $rc->numberofWatchingusers );
- }
-
- if( $this->watchlist ) {
- $classes[] = Sanitizer::escapeClass( 'watchlist-' . $rc->mAttribs['rc_namespace'] . '-' . $rc->mAttribs['rc_title'] );
- }
-
- if ( !wfRunHooks( 'OldChangesListRecentChangesLine', array( &$this, &$s, $rc, &$classes ) ) ) {
- wfProfileOut( __METHOD__ );
- return false;
- }
-
- wfProfileOut( __METHOD__ );
- return "$dateheader<li class=\"" . implode( ' ', $classes ) . "\">" . $s . "</li>\n";
- }
-}
-
-/**
- * Generate a list of changes using an Enhanced system (uses javascript).
- */
-class EnhancedChangesList extends ChangesList {
-
- protected $rc_cache;
-
- /**
- * Add the JavaScript file for enhanced changeslist
- * @return String
- */
- public function beginRecentChangesList() {
- $this->rc_cache = array();
- $this->rcMoveIndex = 0;
- $this->rcCacheIndex = 0;
- $this->lastdate = '';
- $this->rclistOpen = false;
- $this->getOutput()->addModuleStyles( 'mediawiki.special.changeslist' );
- return '';
- }
- /**
- * Format a line for enhanced recentchange (aka with javascript and block of lines).
- *
- * @param $baseRC RecentChange
- * @param $watched bool
- *
- * @return string
- */
- public function recentChangesLine( &$baseRC, $watched = false ) {
- wfProfileIn( __METHOD__ );
-
- # Create a specialised object
- $rc = RCCacheEntry::newFromParent( $baseRC );
-
- $curIdEq = array( 'curid' => $rc->mAttribs['rc_cur_id'] );
-
- # If it's a new day, add the headline and flush the cache
- $date = $this->getLanguage()->userDate( $rc->mAttribs['rc_timestamp'], $this->getUser() );
- $ret = '';
- if( $date != $this->lastdate ) {
- # Process current cache
- $ret = $this->recentChangesBlock();
- $this->rc_cache = array();
- $ret .= Xml::element( 'h4', null, $date ) . "\n";
- $this->lastdate = $date;
- }
-
- # Should patrol-related stuff be shown?
- $rc->unpatrolled = $this->showAsUnpatrolled( $rc );
-
- $showdifflinks = true;
- # Make article link
- $type = $rc->mAttribs['rc_type'];
- $logType = $rc->mAttribs['rc_log_type'];
- // Page moves, very old style, not supported anymore
- if( $type == RC_MOVE || $type == RC_MOVE_OVER_REDIRECT ) {
- // New unpatrolled pages
- } elseif( $rc->unpatrolled && $type == RC_NEW ) {
- $clink = Linker::linkKnown( $rc->getTitle(), null, array(),
- array( 'rcid' => $rc->mAttribs['rc_id'] ) );
- // Log entries
- } elseif( $type == RC_LOG ) {
- if( $logType ) {
- $logtitle = SpecialPage::getTitleFor( 'Log', $logType );
- $logpage = new LogPage( $logType );
- $logname = $logpage->getName()->escaped();
- $clink = $this->msg( 'parentheses' )->rawParams( Linker::linkKnown( $logtitle, $logname ) )->escaped();
- } else {
- $clink = Linker::link( $rc->getTitle() );
- }
- $watched = false;
- // Log entries (old format) and special pages
- } elseif( $rc->mAttribs['rc_namespace'] == NS_SPECIAL ) {
- wfDebug( "Unexpected special page in recentchanges\n" );
- $clink = '';
- // Edits
- } else {
- $clink = Linker::linkKnown( $rc->getTitle() );
- }
-
- # Don't show unusable diff links
- if ( !ChangesList::userCan( $rc, Revision::DELETED_TEXT, $this->getUser() ) ) {
- $showdifflinks = false;
- }
-
- $time = $this->getLanguage()->userTime( $rc->mAttribs['rc_timestamp'], $this->getUser() );
- $rc->watched = $watched;
- $rc->link = $clink;
- $rc->timestamp = $time;
- $rc->numberofWatchingusers = $baseRC->numberofWatchingusers;
-
- # Make "cur" and "diff" links. Do not use link(), it is too slow if
- # called too many times (50% of CPU time on RecentChanges!).
- $thisOldid = $rc->mAttribs['rc_this_oldid'];
- $lastOldid = $rc->mAttribs['rc_last_oldid'];
- if( $rc->unpatrolled ) {
- $rcIdQuery = array( 'rcid' => $rc->mAttribs['rc_id'] );
- } else {
- $rcIdQuery = array();
- }
- $querycur = $curIdEq + array( 'diff' => '0', 'oldid' => $thisOldid );
- $querydiff = $curIdEq + array( 'diff' => $thisOldid, 'oldid' =>
- $lastOldid ) + $rcIdQuery;
-
- if( !$showdifflinks ) {
- $curLink = $this->message['cur'];
- $diffLink = $this->message['diff'];
- } elseif( in_array( $type, array( RC_NEW, RC_LOG, RC_MOVE, RC_MOVE_OVER_REDIRECT ) ) ) {
- if ( $type != RC_NEW ) {
- $curLink = $this->message['cur'];
- } else {
- $curUrl = htmlspecialchars( $rc->getTitle()->getLinkURL( $querycur ) );
- $curLink = "<a href=\"$curUrl\" tabindex=\"{$baseRC->counter}\">{$this->message['cur']}</a>";
- }
- $diffLink = $this->message['diff'];
- } else {
- $diffUrl = htmlspecialchars( $rc->getTitle()->getLinkURL( $querydiff ) );
- $curUrl = htmlspecialchars( $rc->getTitle()->getLinkURL( $querycur ) );
- $diffLink = "<a href=\"$diffUrl\" tabindex=\"{$baseRC->counter}\">{$this->message['diff']}</a>";
- $curLink = "<a href=\"$curUrl\" tabindex=\"{$baseRC->counter}\">{$this->message['cur']}</a>";
- }
-
- # Make "last" link
- if( !$showdifflinks || !$lastOldid ) {
- $lastLink = $this->message['last'];
- } elseif( in_array( $type, array( RC_LOG, RC_MOVE, RC_MOVE_OVER_REDIRECT ) ) ) {
- $lastLink = $this->message['last'];
- } else {
- $lastLink = Linker::linkKnown( $rc->getTitle(), $this->message['last'],
- array(), $curIdEq + array( 'diff' => $thisOldid, 'oldid' => $lastOldid ) + $rcIdQuery );
- }
-
- # Make user links
- if( $this->isDeleted( $rc, Revision::DELETED_USER ) ) {
- $rc->userlink = ' <span class="history-deleted">' . $this->msg( 'rev-deleted-user' )->escaped() . '</span>';
- } else {
- $rc->userlink = Linker::userLink( $rc->mAttribs['rc_user'], $rc->mAttribs['rc_user_text'] );
- $rc->usertalklink = Linker::userToolLinks( $rc->mAttribs['rc_user'], $rc->mAttribs['rc_user_text'] );
- }
-
- $rc->lastlink = $lastLink;
- $rc->curlink = $curLink;
- $rc->difflink = $diffLink;
-
- # Put accumulated information into the cache, for later display
- # Page moves go on their own line
- $title = $rc->getTitle();
- $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 );
- } else {
- # Logs are grouped by type
- if( $type == RC_LOG ) {
- $secureName = SpecialPage::getTitleFor( 'Log', $logType )->getPrefixedDBkey();
- }
- if( !isset( $this->rc_cache[$secureName] ) ) {
- $this->rc_cache[$secureName] = array();
- }
-
- array_push( $this->rc_cache[$secureName], $rc );
- }
-
- wfProfileOut( __METHOD__ );
-
- return $ret;
- }
-
- /**
- * Enhanced RC group
- * @return string
- */
- protected function recentChangesBlockGroup( $block ) {
- global $wgRCShowChangedSize;
-
- wfProfileIn( __METHOD__ );
-
- # Add the namespace and title of the block as part of the class
- $classes = array( 'mw-collapsible', 'mw-collapsed', 'mw-enhanced-rc' );
- if ( $block[0]->mAttribs['rc_log_type'] ) {
- # Log entry
- $classes[] = Sanitizer::escapeClass( 'mw-changeslist-log-'
- . $block[0]->mAttribs['rc_log_type'] . '-' . $block[0]->mAttribs['rc_title'] );
- } else {
- $classes[] = Sanitizer::escapeClass( 'mw-changeslist-ns'
- . $block[0]->mAttribs['rc_namespace'] . '-' . $block[0]->mAttribs['rc_title'] );
- }
- $classes[] = $block[0]->watched ? 'mw-changeslist-line-watched' : 'mw-changeslist-line-not-watched';
- $r = Html::openElement( 'table', array( 'class' => $classes ) ) .
- Html::openElement( 'tr' );
-
- # Collate list of users
- $userlinks = array();
- # Other properties
- $unpatrolled = false;
- $isnew = false;
- $allBots = true;
- $allMinors = true;
- $curId = $currentRevision = 0;
- # Some catalyst variables...
- $namehidden = true;
- $allLogs = true;
- foreach( $block as $rcObj ) {
- $oldid = $rcObj->mAttribs['rc_last_oldid'];
- if( $rcObj->mAttribs['rc_type'] == RC_NEW ) {
- $isnew = true;
- }
- // If all log actions to this page were hidden, then don't
- // give the name of the affected page for this block!
- if( !$this->isDeleted( $rcObj, LogPage::DELETED_ACTION ) ) {
- $namehidden = false;
- }
- $u = $rcObj->userlink;
- if( !isset( $userlinks[$u] ) ) {
- $userlinks[$u] = 0;
- }
- if( $rcObj->unpatrolled ) {
- $unpatrolled = true;
- }
- if( $rcObj->mAttribs['rc_type'] != RC_LOG ) {
- $allLogs = false;
- }
- # Get the latest entry with a page_id and oldid
- # since logs may not have these.
- if( !$curId && $rcObj->mAttribs['rc_cur_id'] ) {
- $curId = $rcObj->mAttribs['rc_cur_id'];
- }
- if( !$currentRevision && $rcObj->mAttribs['rc_this_oldid'] ) {
- $currentRevision = $rcObj->mAttribs['rc_this_oldid'];
- }
-
- if( !$rcObj->mAttribs['rc_bot'] ) {
- $allBots = false;
- }
- if( !$rcObj->mAttribs['rc_minor'] ) {
- $allMinors = false;
- }
-
- $userlinks[$u]++;
- }
-
- # Sort the list and convert to text
- krsort( $userlinks );
- asort( $userlinks );
- $users = array();
- foreach( $userlinks as $userlink => $count) {
- $text = $userlink;
- $text .= $this->getLanguage()->getDirMark();
- if( $count > 1 ) {
- $text .= ' ' . $this->msg( 'parentheses' )->rawParams( $this->getLanguage()->formatNum( $count ) . '×' )->escaped();
- }
- array_push( $users, $text );
- }
-
- $users = ' <span class="changedby">'
- . $this->msg( 'brackets' )->rawParams(
- implode( $this->message['semicolon-separator'], $users )
- )->escaped() . '</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, # 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>';
-
- # Article link
- if( $namehidden ) {
- $r .= ' <span class="history-deleted">' . $this->msg( 'rev-deleted-event' )->escaped() . '</span>';
- } elseif( $allLogs ) {
- $r .= $this->maybeWatchedLink( $block[0]->link, $block[0]->watched );
- } else {
- $this->insertArticleLink( $r, $block[0], $block[0]->unpatrolled, $block[0]->watched );
- }
-
- $r .= $this->getLanguage()->getDirMark();
-
- $queryParams['curid'] = $curId;
- # Changes message
- $n = count( $block );
- static $nchanges = array();
- if ( !isset( $nchanges[$n] ) ) {
- $nchanges[$n] = $this->msg( 'nchanges' )->numParams( $n )->escaped();
- }
- # Total change link
- $r .= ' ';
- $logtext = '';
- if( !$allLogs ) {
- if( !ChangesList::userCan( $rcObj, Revision::DELETED_TEXT, $this->getUser() ) ) {
- $logtext .= $nchanges[$n];
- } elseif( $isnew ) {
- $logtext .= $nchanges[$n];
- } else {
- $params = $queryParams;
- $params['diff'] = $currentRevision;
- $params['oldid'] = $oldid;
-
- $logtext .= Linker::link(
- $block[0]->getTitle(),
- $nchanges[$n],
- array(),
- $params,
- array( 'known', 'noclasses' )
- );
- }
- }
-
- # History
- if( $allLogs ) {
- // don't show history link for logs
- } elseif( $namehidden || !$block[0]->getTitle()->exists() ) {
- $logtext .= $this->message['pipe-separator'] . $this->message['hist'];
- } else {
- $params = $queryParams;
- $params['action'] = 'history';
-
- $logtext .= $this->message['pipe-separator'] .
- Linker::linkKnown(
- $block[0]->getTitle(),
- $this->message['hist'],
- array(),
- $params
- );
- }
-
- if( $logtext !== '' ) {
- $r .= $this->msg( 'parentheses' )->rawParams( $logtext )->escaped();
- }
-
- $r .= ' <span class="mw-changeslist-separator">. .</span> ';
-
- # Character difference (does not apply if only log items)
- if( $wgRCShowChangedSize && !$allLogs ) {
- $last = 0;
- $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++;
- }
- while( $first > $last && $block[$first]->mAttribs['rc_old_len'] === null ) {
- $first--;
- }
- # Get net change
- $chardiff = $this->formatCharacterDifference( $block[$first], $block[$last] );
-
- if( $chardiff == '' ) {
- $r .= ' ';
- } else {
- $r .= ' ' . $chardiff. ' <span class="mw-changeslist-separator">. .</span> ';
- }
- }
-
- $r .= $users;
- $r .= $this->numberofWatchingusers( $block[0]->numberofWatchingusers );
-
- # Sub-entries
- foreach( $block as $rcObj ) {
- # Classes to apply -- TODO implement
- $classes = array();
- $type = $rcObj->mAttribs['rc_type'];
-
- $r .= '<tr><td></td><td class="mw-enhanced-rc">';
- $r .= $this->recentChangesFlags( array(
- 'newpage' => $type == RC_NEW,
- 'minor' => $rcObj->mAttribs['rc_minor'],
- 'unpatrolled' => $rcObj->unpatrolled,
- 'bot' => $rcObj->mAttribs['rc_bot'],
- ) );
- $r .= '&#160;</td><td class="mw-enhanced-rc-nested"><span class="mw-enhanced-rc-time">';
-
- $params = $queryParams;
-
- if( $rcObj->mAttribs['rc_this_oldid'] != 0 ) {
- $params['oldid'] = $rcObj->mAttribs['rc_this_oldid'];
- }
-
- # Log timestamp
- if( $type == RC_LOG ) {
- $link = $rcObj->timestamp;
- # Revision link
- } elseif( !ChangesList::userCan( $rcObj, Revision::DELETED_TEXT, $this->getUser() ) ) {
- $link = '<span class="history-deleted">' . $rcObj->timestamp . '</span> ';
- } else {
- if ( $rcObj->unpatrolled && $type == RC_NEW) {
- $params['rcid'] = $rcObj->mAttribs['rc_id'];
- }
-
- $link = Linker::linkKnown(
- $rcObj->getTitle(),
- $rcObj->timestamp,
- array(),
- $params
- );
- if( $this->isDeleted( $rcObj, Revision::DELETED_TEXT ) ) {
- $link = '<span class="history-deleted">' . $link . '</span> ';
- }
- }
- $r .= $link . '</span>';
-
- if ( !$type == RC_LOG || $type == RC_NEW ) {
- $r .= ' ' . $this->msg( 'parentheses' )->rawParams( $rcObj->curlink . $this->message['pipe-separator'] . $rcObj->lastlink )->escaped();
- }
- $r .= ' <span class="mw-changeslist-separator">. .</span> ';
-
- # Character diff
- if ( $wgRCShowChangedSize ) {
- $cd = $this->formatCharacterDifference( $rcObj );
- if ( $cd !== '' ) {
- $r .= $cd . ' <span class="mw-changeslist-separator">. .</span> ';
- }
- }
-
- if ( $rcObj->mAttribs['rc_type'] == RC_LOG ) {
- $r .= $this->insertLogEntry( $rcObj );
- } else {
- # User links
- $r .= $rcObj->userlink;
- $r .= $rcObj->usertalklink;
- $r .= $this->insertComment( $rcObj );
- }
-
- # Rollback
- $this->insertRollback( $r, $rcObj );
- # Tags
- $this->insertTags( $r, $rcObj, $classes );
-
- $r .= "</td></tr>\n";
- }
- $r .= "</table>\n";
-
- $this->rcCacheIndex++;
-
- wfProfileOut( __METHOD__ );
-
- return $r;
- }
-
- /**
- * Generate HTML for an arrow or placeholder graphic
- * @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 = '' ) {
- global $wgStylePath;
- $encUrl = htmlspecialchars( $wgStylePath . '/common/images/Arr_' . $dir . '.png' );
- $encAlt = htmlspecialchars( $alt );
- $encTitle = htmlspecialchars( $title );
- return "<img src=\"$encUrl\" width=\"12\" height=\"12\" alt=\"$encAlt\" title=\"$encTitle\" />";
- }
-
- /**
- * Generate HTML for a right- or left-facing arrow,
- * depending on language direction.
- * @return String: HTML "<img>" tag
- */
- protected function sideArrow() {
- $dir = $this->getLanguage()->isRTL() ? 'l' : 'r';
- return $this->arrow( $dir, '+', $this->msg( 'rc-enhanced-expand' )->text() );
- }
-
- /**
- * Generate HTML for a down-facing arrow
- * depending on language direction.
- * @return String: HTML "<img>" tag
- */
- protected function downArrow() {
- return $this->arrow( 'd', '-', $this->msg( 'rc-enhanced-hide' )->text() );
- }
-
- /**
- * Generate HTML for a spacer image
- * @return String: HTML "<img>" tag
- */
- protected function spacerArrow() {
- return $this->arrow( '', codepointToUtf8( 0xa0 ) ); // non-breaking space
- }
-
- /**
- * Enhanced RC ungrouped line.
- *
- * @param $rcObj RecentChange
- * @return String: a HTML formatted line (generated using $r)
- */
- protected function recentChangesBlockLine( $rcObj ) {
- global $wgRCShowChangedSize;
-
- wfProfileIn( __METHOD__ );
- $query['curid'] = $rcObj->mAttribs['rc_cur_id'];
-
- $type = $rcObj->mAttribs['rc_type'];
- $logType = $rcObj->mAttribs['rc_log_type'];
- $classes = array( 'mw-enhanced-rc' );
- if( $logType ) {
- # Log entry
- $classes[] = Sanitizer::escapeClass( 'mw-changeslist-log-'
- . $logType . '-' . $rcObj->mAttribs['rc_title'] );
- } else {
- $classes[] = Sanitizer::escapeClass( 'mw-changeslist-ns' .
- $rcObj->mAttribs['rc_namespace'] . '-' . $rcObj->mAttribs['rc_title'] );
- }
- $classes[] = $rcObj->watched ? 'mw-changeslist-line-watched' : 'mw-changeslist-line-not-watched';
- $r = Html::openElement( 'table', array( 'class' => $classes ) ) .
- Html::openElement( 'tr' );
-
- $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
- } else {
- $r .= $this->recentChangesFlags( array(
- 'newpage' => $type == RC_NEW,
- 'minor' => $rcObj->mAttribs['rc_minor'],
- 'unpatrolled' => $rcObj->unpatrolled,
- 'bot' => $rcObj->mAttribs['rc_bot'],
- ) );
- }
- $r .= '&#160;' . $rcObj->timestamp . '&#160;</td><td>';
- # Article or log link
- if( $logType ) {
- $logPage = new LogPage( $logType );
- $logTitle = SpecialPage::getTitleFor( 'Log', $logType );
- $logName = $logPage->getName()->escaped();
- $r .= $this->msg( 'parentheses' )->rawParams( Linker::linkKnown( $logTitle, $logName ) )->escaped();
- } else {
- $this->insertArticleLink( $r, $rcObj, $rcObj->unpatrolled, $rcObj->watched );
- }
- # Diff and hist links
- if ( $type != RC_LOG ) {
- $query['action'] = 'history';
- $r .= ' ' . $this->msg( 'parentheses' )->rawParams( $rcObj->difflink . $this->message['pipe-separator'] . Linker::linkKnown(
- $rcObj->getTitle(),
- $this->message['hist'],
- array(),
- $query
- ) )->escaped();
- }
- $r .= ' <span class="mw-changeslist-separator">. .</span> ';
- # Character diff
- if ( $wgRCShowChangedSize ) {
- $cd = $this->formatCharacterDifference( $rcObj );
- if ( $cd !== '' ) {
- $r .= $cd . ' <span class="mw-changeslist-separator">. .</span> ';
- }
- }
-
- if ( $type == RC_LOG ) {
- $r .= $this->insertLogEntry( $rcObj );
- } else {
- $r .= ' ' . $rcObj->userlink . $rcObj->usertalklink;
- $r .= $this->insertComment( $rcObj );
- $this->insertRollback( $r, $rcObj );
- }
-
- # Tags
- $this->insertTags( $r, $rcObj, $classes );
- # Show how many people are watching this if enabled
- $r .= $this->numberofWatchingusers( $rcObj->numberofWatchingusers );
-
- $r .= "</td></tr></table>\n";
-
- wfProfileOut( __METHOD__ );
-
- return $r;
- }
-
- /**
- * If enhanced RC is in use, this function takes the previously cached
- * RC lines, arranges them, and outputs the HTML
- *
- * @return string
- */
- protected function recentChangesBlock() {
- if( count ( $this->rc_cache ) == 0 ) {
- return '';
- }
-
- wfProfileIn( __METHOD__ );
-
- $blockOut = '';
- foreach( $this->rc_cache as $block ) {
- if( count( $block ) < 2 ) {
- $blockOut .= $this->recentChangesBlockLine( array_shift( $block ) );
- } else {
- $blockOut .= $this->recentChangesBlockGroup( $block );
- }
- }
-
- wfProfileOut( __METHOD__ );
-
- return '<div>' . $blockOut . '</div>';
- }
-
- /**
- * Returns text for the end of RC
- * If enhanced RC is in use, returns pretty much all the text
- * @return string
- */
- public function endRecentChangesList() {
- return $this->recentChangesBlock() . parent::endRecentChangesList();
- }
-
-}
diff --git a/includes/Collation.php b/includes/Collation.php
index 6bba019b..b0252c70 100644
--- a/includes/Collation.php
+++ b/includes/Collation.php
@@ -40,7 +40,7 @@ abstract class Collation {
* @return Collation
*/
static function factory( $collationName ) {
- switch( $collationName ) {
+ switch ( $collationName ) {
case 'uppercase':
return new UppercaseCollation;
case 'identity':
@@ -49,7 +49,7 @@ abstract class Collation {
return new IcuCollation( 'root' );
default:
$match = array();
- if ( preg_match( '/^uca-([a-z-]+)$/', $collationName, $match ) ) {
+ if ( preg_match( '/^uca-([a-z@=-]+)$/', $collationName, $match ) ) {
return new IcuCollation( $match[1] );
}
@@ -62,7 +62,7 @@ abstract class Collation {
}
// If all else fails...
- throw new MWException( __METHOD__.": unknown collation type \"$collationName\"" );
+ throw new MWException( __METHOD__ . ": unknown collation type \"$collationName\"" );
}
}
@@ -214,6 +214,7 @@ class IcuCollation extends Collation {
'pt' => array(),
'ru' => array(),
'sv' => array( "Å", "Ä", "Ö" ),
+ 'sv@collation=standard' => array( "Å", "Ä", "Ö" ),
'uk' => array( "Ò", "Ь" ),
'vi' => array( "Ä‚", "Â", "Ä", "Ê", "Ô", "Æ ", "Ư" ),
// Not verified, but likely correct
@@ -235,6 +236,7 @@ class IcuCollation extends Collation {
'es' => array( "Ñ" ),
'et' => array( "Š", "Ž", "Õ", "Ä", "Ö", "Ü" ),
'eu' => array( "Ñ" ),
+ 'fa' => array( "Ø¢", "Ø¡", "Ù‡" ),
'fo' => array( "Ã", "Ã", "Ã", "Ó", "Ú", "Ã", "Æ", "Ø", "Ã…" ),
'fr' => array(),
'fur' => array( "À", "Ã", "Â", "È", "ÃŒ", "Ã’", "Ù" ),
@@ -349,21 +351,21 @@ class IcuCollation extends Collation {
$cacheEntry = $cache->get( $cacheKey );
if ( $cacheEntry && isset( $cacheEntry['version'] )
- && $cacheEntry['version'] == self::FIRST_LETTER_VERSION )
- {
+ && $cacheEntry['version'] == self::FIRST_LETTER_VERSION
+ ) {
$this->firstLetterData = $cacheEntry;
return $this->firstLetterData;
}
// Generate data from serialized data file
- if ( isset ( self::$tailoringFirstLetters[$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 ] );
+ if ( isset( self::$tailoringFirstLetters['-' . $this->locale] ) ) {
+ $letters = array_diff( $letters, self::$tailoringFirstLetters['-' . $this->locale] );
}
} else {
$letters = wfGetPrecompiledData( "first-letters-{$this->locale}.ser" );
@@ -430,7 +432,7 @@ class IcuCollation extends Collation {
$prev = false;
$duplicatePrefixes = array();
- foreach( $letterMap as $key => $value ) {
+ foreach ( $letterMap as $key => $value ) {
// Remove terminator byte. Otherwise the prefix
// comparison will get hung up on that.
$trimmedKey = rtrim( $key, "\0" );
@@ -456,7 +458,7 @@ class IcuCollation extends Collation {
}
$prev = $trimmedKey;
}
- foreach( $duplicatePrefixes as $badKey ) {
+ foreach ( $duplicatePrefixes as $badKey ) {
wfDebug( "Removing '{$letterMap[$badKey]}' from first letters." );
unset( $letterMap[$badKey] );
// This code assumes that unsetting does not change sort order.
diff --git a/includes/ConfEditor.php b/includes/ConfEditor.php
index 1d9ca921..67cb87db 100644
--- a/includes/ConfEditor.php
+++ b/includes/ConfEditor.php
@@ -278,19 +278,23 @@ class ConfEditor {
function getVars() {
$vars = array();
$this->parse();
- foreach( $this->pathInfo as $path => $data ) {
- if ( $path[0] != '$' )
+ foreach ( $this->pathInfo as $path => $data ) {
+ if ( $path[0] != '$' ) {
continue;
+ }
$trimmedPath = substr( $path, 1 );
$name = $data['name'];
- if ( $name[0] == '@' )
+ if ( $name[0] == '@' ) {
continue;
- if ( $name[0] == '$' )
+ }
+ if ( $name[0] == '$' ) {
$name = substr( $name, 1 );
+ }
$parentPath = substr( $trimmedPath, 0,
strlen( $trimmedPath ) - strlen( $name ) );
- if( substr( $parentPath, -1 ) == '/' )
+ if ( substr( $parentPath, -1 ) == '/' ) {
$parentPath = substr( $parentPath, 0, -1 );
+ }
$value = substr( $this->text, $data['valueStartByte'],
$data['valueEndByte'] - $data['valueStartByte']
@@ -315,13 +319,15 @@ class ConfEditor {
$target =& $array;
if ( $path !== '' ) {
foreach ( $pathArr as $p ) {
- if( !isset( $target[$p] ) )
+ if ( !isset( $target[$p] ) ) {
$target[$p] = array();
+ }
$target =& $target[$p];
}
}
- if ( !isset( $target[$key] ) )
+ if ( !isset( $target[$key] ) ) {
$target[$key] = $value;
+ }
}
/**
@@ -329,25 +335,30 @@ class ConfEditor {
* @return mixed Parsed value
*/
function parseScalar( $str ) {
- if ( $str !== '' && $str[0] == '\'' )
+ if ( $str !== '' && $str[0] == '\'' ) {
// Single-quoted string
// @todo FIXME: trim() call is due to mystery bug where whitespace gets
// appended to the token; without it we ended up reading in the
// extra quote on the end!
return strtr( substr( trim( $str ), 1, -1 ),
array( '\\\'' => '\'', '\\\\' => '\\' ) );
- if ( $str !== '' && $str[0] == '"' )
+ }
+ if ( $str !== '' && $str[0] == '"' ) {
// Double-quoted string
// @todo FIXME: trim() call is due to mystery bug where whitespace gets
// appended to the token; without it we ended up reading in the
// extra quote on the end!
return stripcslashes( substr( trim( $str ), 1, -1 ) );
- if ( substr( $str, 0, 4 ) == 'true' )
+ }
+ if ( substr( $str, 0, 4 ) == 'true' ) {
return true;
- if ( substr( $str, 0, 5 ) == 'false' )
+ }
+ if ( substr( $str, 0, 5 ) == 'false' ) {
return false;
- if ( substr( $str, 0, 4 ) == 'null' )
+ }
+ if ( substr( $str, 0, 4 ) == 'null' ) {
return null;
+ }
// Must be some kind of numeric value, so let PHP's weak typing
// be useful for a change
return $str;
@@ -537,7 +548,7 @@ class ConfEditor {
*/
function getIndent( $pos, $key = false, $arrowPos = false ) {
$arrowIndent = ' ';
- if ( $pos == 0 || $this->text[$pos-1] == "\n" ) {
+ if ( $pos == 0 || $this->text[$pos - 1] == "\n" ) {
$indentLength = strspn( $this->text, " \t", $pos );
$indent = substr( $this->text, $pos, $indentLength );
} else {
@@ -559,7 +570,7 @@ class ConfEditor {
public function parse() {
$this->initParse();
$this->pushState( 'file' );
- $this->pushPath( '@extra-' . ($this->serial++) );
+ $this->pushPath( '@extra-' . ( $this->serial++ ) );
$token = $this->firstToken();
while ( !$token->isEnd() ) {
@@ -617,19 +628,21 @@ class ConfEditor {
$this->expect( '=' );
$this->skipSpace();
$this->startPathValue();
- if ( $arrayAssign )
+ if ( $arrayAssign ) {
$this->pushState( 'expression', 'array assign end' );
- else
+ } else {
$this->pushState( 'expression', 'statement end' );
+ }
break;
case 'array assign end':
case 'statement end':
$this->endPathValue();
- if ( $state == 'array assign end' )
+ if ( $state == 'array assign end' ) {
$this->popPath();
+ }
$this->skipSpace();
$this->expect( ';' );
- $this->nextPath( '@extra-' . ($this->serial++) );
+ $this->nextPath( '@extra-' . ( $this->serial++ ) );
break;
case 'expression':
$token = $this->skipSpace();
@@ -649,7 +662,7 @@ class ConfEditor {
$this->skipSpace();
$this->expect( '(' );
$this->skipSpace();
- $this->pushPath( '@extra-' . ($this->serial++) );
+ $this->pushPath( '@extra-' . ( $this->serial++ ) );
if ( $this->isAhead( ')' ) ) {
// Empty array
$this->pushState( 'array end' );
@@ -681,7 +694,7 @@ class ConfEditor {
$this->endPathValue();
$this->markComma();
$this->nextToken();
- $this->nextPath( '@extra-' . ($this->serial++) );
+ $this->nextPath( '@extra-' . ( $this->serial++ ) );
// Look ahead to find ending bracket
if ( $this->isAhead( ")" ) ) {
// Found ending bracket, no continuation
@@ -1056,7 +1069,7 @@ class ConfEditorParseError extends MWException {
$lines = StringUtils::explode( "\n", $text );
foreach ( $lines as $lineNum => $line ) {
if ( $lineNum == $this->lineNum - 1 ) {
- return "$line\n" .str_repeat( ' ', $this->colNum - 1 ) . "^\n";
+ return "$line\n" . str_repeat( ' ', $this->colNum - 1 ) . "^\n";
}
}
return '';
diff --git a/includes/Cookie.php b/includes/Cookie.php
index 27a85072..ecf4667d 100644
--- a/includes/Cookie.php
+++ b/includes/Cookie.php
@@ -82,7 +82,8 @@ class Cookie {
* http://publicsuffix.org/
*
* @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)
+ * @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 string $domain the domain to validate
* @param string $originDomain (optional) the domain the cookie originates from
@@ -197,7 +198,7 @@ class CookieJar {
* Set a cookie in the cookie jar. Make sure only one cookie per-name exists.
* @see Cookie::set()
*/
- public function setCookie ( $name, $value, $attr ) {
+ public function setCookie( $name, $value, $attr ) {
/* cookies: case insensitive, so this should work.
* We'll still send the cookies back in the same case we got them, though.
*/
@@ -235,7 +236,7 @@ class CookieJar {
* @param string $domain cookie's domain
* @return null
*/
- public function parseCookieResponseHeader ( $cookie, $domain ) {
+ public function parseCookieResponseHeader( $cookie, $domain ) {
$len = strlen( 'Set-Cookie:' );
if ( substr_compare( 'Set-Cookie:', $cookie, 0, $len, true ) === 0 ) {
diff --git a/includes/DataUpdate.php b/includes/DataUpdate.php
index c1076b23..7b9ac281 100644
--- a/includes/DataUpdate.php
+++ b/includes/DataUpdate.php
@@ -78,7 +78,9 @@ abstract class DataUpdate implements DeferrableUpdate {
* @throws Exception|null
*/
public static function runUpdates( $updates ) {
- if ( empty( $updates ) ) return; # nothing to do
+ if ( empty( $updates ) ) {
+ return; # nothing to do
+ }
$open_transactions = array();
$exception = null;
diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php
index a56ef849..0fc59fb2 100644
--- a/includes/DefaultSettings.php
+++ b/includes/DefaultSettings.php
@@ -63,7 +63,7 @@ $wgConf = new SiteConfiguration;
* MediaWiki version number
* @since 1.2
*/
-$wgVersion = '1.21.3';
+$wgVersion = '1.22.0';
/**
* Name of the site. It must be changed in LocalSettings.php
@@ -172,14 +172,6 @@ $wgScriptExtension = '.php';
$wgScript = false;
/**
- * The URL path to redirect.php. This is a script that is used by the Nostalgia
- * skin.
- *
- * Defaults to "{$wgScriptPath}/redirect{$wgScriptExtension}".
- */
-$wgRedirectScript = false;
-
-/**
* The URL path to load.php.
*
* Defaults to "{$wgScriptPath}/load{$wgScriptExtension}".
@@ -274,7 +266,6 @@ $wgAppleTouchIcon = false;
*
* @see wfTempDir()
* @note Default changed to false in MediaWiki 1.20.
- *
*/
$wgTmpDirectory = false;
@@ -317,7 +308,9 @@ $wgActionPaths = array();
* @{
*/
-/** Uploads have to be specially set up to be secure */
+/**
+ * Uploads have to be specially set up to be secure
+ */
$wgEnableUploads = false;
/**
@@ -325,24 +318,22 @@ $wgEnableUploads = false;
*/
$wgUploadStashMaxAge = 6 * 3600; // 6 hours
-/** Allows to move images and other media files */
+/**
+ * Allows to move images and other media files
+ */
$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.
+ *
+ * @note If you use suhosin, this setting is incompatible with
+ * suhosin.session.encrypt.
*/
$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 = ":";
@@ -456,7 +447,9 @@ $wgImgAuthPublicTest = true;
*/
$wgLocalFileRepo = false;
-/** @see $wgLocalFileRepo */
+/**
+ * @see $wgLocalFileRepo
+ */
$wgForeignFileRepos = array();
/**
@@ -512,11 +505,11 @@ $wgFileBackends = array();
$wgLockManagers = array();
/**
- * Show EXIF data, on by default if available.
- * Requires PHP's EXIF extension: http://www.php.net/manual/en/ref.exif.php
+ * Show Exif data, on by default if available.
+ * Requires PHP's Exif extension: http://www.php.net/manual/en/ref.exif.php
*
* @note FOR WINDOWS USERS:
- * To enable EXIF functions, add the following lines to the "Windows
+ * To enable Exif functions, add the following lines to the "Windows
* extensions" section of php.ini:
* @code{.ini}
* extension=extensions/php_mbstring.dll
@@ -547,22 +540,36 @@ $wgUpdateCompatibleMetadata = false;
*/
$wgUseSharedUploads = false;
-/** Full path on the web server where shared uploads can be found */
+/**
+ * Full path on the web server where shared uploads can be found
+ */
$wgSharedUploadPath = "http://commons.wikimedia.org/shared/images";
-/** Fetch commons image description pages and display them on the local wiki? */
+/**
+ * Fetch commons image description pages and display them on the local wiki?
+ */
$wgFetchCommonsDescriptions = false;
-/** Path on the file system where shared uploads can be found. */
+/**
+ * Path on the file system where shared uploads can be found.
+ */
$wgSharedUploadDirectory = "/var/www/wiki3/images";
-/** DB name with metadata about shared directory. Set this to false if the uploads do not come from a wiki. */
+/**
+ * DB name with metadata about shared directory.
+ * Set this to false if the uploads do not come from a wiki.
+ */
$wgSharedUploadDBname = false;
-/** Optional table prefix used in database. */
+/**
+ * Optional table prefix used in database.
+ */
$wgSharedUploadDBprefix = '';
-/** Cache shared metadata in memcached. Don't do this if the commons wiki is in a different memcached domain */
+/**
+ * Cache shared metadata in memcached.
+ * Don't do this if the commons wiki is in a different memcached domain
+ */
$wgCacheSharedUploads = true;
/**
@@ -599,6 +606,27 @@ $wgCopyUploadsFromSpecialUpload = false;
$wgCopyUploadProxy = false;
/**
+ * Different timeout for upload by url
+ * This could be useful since when fetching large files, you may want a
+ * timeout longer than the default $wgHTTPTimeout. False means fallback
+ * to default.
+ *
+ * @since 1.22
+ */
+$wgCopyUploadTimeout = false;
+
+/**
+ * Different timeout for upload by url when run as a background job
+ * This could be useful since when fetching large files via job queue,
+ * you may want a different timeout, especially because there is no
+ * http request being kept alive.
+ *
+ * false means fallback to $wgCopyUploadTimeout.
+ * @since 1.22
+ */
+$wgCopyUploadAsyncTimeout = false;
+
+/**
* Max size for uploads, in bytes. If not set to an array, applies to all
* uploads. If set to an array, per upload type maximums can be set, using the
* file and url keys. If the * key is set this value will be used as maximum
@@ -613,7 +641,6 @@ $wgCopyUploadProxy = false;
* @endcode
* Sets the maximum for all uploads to 250 kB except for upload-by-url, which
* will have a maximum of 500 kB.
- *
*/
$wgMaxUploadSize = 1024 * 1024 * 100; # 100MB
@@ -648,6 +675,7 @@ $wgUploadMissingFileUrl = false;
* @endcode
*/
$wgThumbnailScriptPath = false;
+
/**
* @see $wgThumbnailScriptPath
*/
@@ -699,7 +727,7 @@ $wgFileExtensions = array( 'png', 'gif', 'jpg', 'jpeg' );
* Files with these extensions will never be allowed as uploads.
* An array of file extensions to blacklist. You should append to this array
* if you want to blacklist additional files.
- * */
+ */
$wgFileBlacklist = array(
# HTML may contain cookie-stealing JavaScript and web bugs
'html', 'htm', 'js', 'jsb', 'mhtml', 'mht', 'xhtml', 'xht',
@@ -826,15 +854,25 @@ $wgContentHandlers = array(
* Use Image Magick instead of PHP builtin functions.
*/
$wgUseImageMagick = false;
-/** The convert command shipped with ImageMagick */
+
+/**
+ * The convert command shipped with ImageMagick
+ */
$wgImageMagickConvertCommand = '/usr/bin/convert';
-/** The identify command shipped with ImageMagick */
+
+/**
+ * The identify command shipped with ImageMagick
+ */
$wgImageMagickIdentifyCommand = '/usr/bin/identify';
-/** Sharpening parameter to ImageMagick */
+/**
+ * Sharpening parameter to ImageMagick
+ */
$wgSharpenParameter = '0x0.4';
-/** Reduction in linear dimensions below which sharpening will be enabled */
+/**
+ * Reduction in linear dimensions below which sharpening will be enabled
+ */
$wgSharpenReductionThreshold = 0.85;
/**
@@ -857,15 +895,15 @@ $wgImageMagickTempDir = false;
*/
$wgCustomConvertCommand = false;
-/** used for lossless jpeg rotation
+/**
+ * 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
+ * Some tests and extensions use exiv2 to manipulate the Exif metadata in some
* image formats.
*/
$wgExiv2Command = '/usr/bin/exiv2';
@@ -889,16 +927,23 @@ $wgSVGConverters = array(
'ImagickExt' => array( 'SvgHandler::rasterizeImagickExt' ),
);
-/** Pick a converter defined in $wgSVGConverters */
+/**
+ * Pick a converter defined in $wgSVGConverters
+ */
$wgSVGConverter = 'ImageMagick';
-/** If not in the executable PATH, specify the SVG converter path. */
+/**
+ * If not in the executable PATH, specify the SVG converter path.
+ */
$wgSVGConverterPath = '';
-/** Don't scale a SVG larger than this */
+/**
+ * Don't scale a SVG larger than this
+ */
$wgSVGMaxSize = 2048;
-/** Don't read SVG metadata beyond this point.
+/**
+ * Don't read SVG metadata beyond this point.
* Default is 1024*256 bytes
*/
$wgSVGMetadataCutoff = 262144;
@@ -930,6 +975,7 @@ $wgAllowTitlesInSVG = false;
* 12.5 million pixels or 3500x3500.
*/
$wgMaxImageArea = 1.25e7;
+
/**
* Force thumbnailing of animated GIFs above this size to a single
* frame instead of an animated thumbnail. As of MW 1.17 this limit
@@ -937,6 +983,7 @@ $wgMaxImageArea = 1.25e7;
* It probably makes sense to keep this equal to $wgMaxImageArea.
*/
$wgMaxAnimatedGifArea = 1.25e7;
+
/**
* Browsers don't support TIFF inline generally...
* For inline display, we need to convert to PNG or JPEG.
@@ -987,7 +1034,9 @@ $wgGenerateThumbnailOnParse = true;
*/
$wgShowArchiveThumbnails = true;
-/** Obsolete, always true, kept for compatibility with extensions */
+/**
+ * Obsolete, always true, kept for compatibility with extensions
+ */
$wgUseImageResize = true;
/**
@@ -1053,29 +1102,31 @@ $wgAntivirusSetup = array(
),
);
-/** Determines if a failed virus scan (AV_SCAN_FAILED) will cause the file to be rejected. */
+/**
+ * Determines if a failed virus scan (AV_SCAN_FAILED) will cause the file to be rejected.
+ */
$wgAntivirusRequired = true;
-/** Determines if the mime type of uploaded files should be checked */
+/**
+ * Determines if the mime type of uploaded files should be checked
+ */
$wgVerifyMimeType = true;
-/** Sets the mime type definition file to use by MimeMagic.php. */
+/**
+ * Sets the mime type definition file to use by MimeMagic.php.
+ * Set to null, to use built-in defaults only.
+ * example: $wgMimeTypeFile = '/etc/mime.types';
+ */
$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.
/**
- * Switch for loading the FileInfo extension by PECL at runtime.
- * This should be used only if fileinfo is installed as a shared object
- * or a dynamic library.
+ * Sets the mime type info file to use by MimeMagic.php.
+ * Set to null, to use built-in defaults only.
*/
-$wgLoadFileinfoExtension = false;
+$wgMimeInfoFile = 'includes/mime.info';
-/** Sets an external mime detector program. The command must print only
+/**
+ * Sets an external mime detector program. The command must print only
* the mime type to standard output.
* The name of the file to process will be appended to the command given here.
* If not set or NULL, mime_content_type will be used if available.
@@ -1145,6 +1196,7 @@ $wgGalleryOptions = array(
'imageHeight' => 120, // Height of the cells containing images in galleries (in "px")
'captionLength' => 25, // Length of caption to truncate (in characters)
'showBytes' => true, // Show the filesize in bytes in categories
+ 'mode' => 'traditional',
);
/**
@@ -1173,25 +1225,26 @@ $wgResponsiveImages = true;
* @name DJVU settings
* @{
*/
+
/**
* Path of the djvudump executable
* Enable this and $wgDjvuRenderer to enable djvu rendering
+ * example: $wgDjvuDump = 'djvudump';
*/
-# $wgDjvuDump = 'djvudump';
$wgDjvuDump = null;
/**
* Path of the ddjvu DJVU renderer
* Enable this and $wgDjvuDump to enable djvu rendering
+ * example: $wgDjvuRenderer = 'ddjvu';
*/
-# $wgDjvuRenderer = 'ddjvu';
$wgDjvuRenderer = null;
/**
* Path of the djvutxt DJVU text extraction utility
* Enable this and $wgDjvuDump to enable text layer extraction from djvu files
+ * example: $wgDjvuTxt = 'djvutxt';
*/
-# $wgDjvuTxt = 'djvutxt';
$wgDjvuTxt = null;
/**
@@ -1216,10 +1269,12 @@ $wgDjvuToXML = null;
* Set this to false to output the ppm file directly.
*/
$wgDjvuPostProcessor = 'pnmtojpeg';
+
/**
* File extension for the DJVU post processor output
*/
$wgDjvuOutputExtension = 'jpg';
+
/** @} */ # end of DJvu }
/** @} */ # end of file uploads }
@@ -1334,7 +1389,8 @@ $wgAllowHTMLEmail = false;
$wgEnotifFromEditor = false;
// TODO move UPO to preferences probably ?
-# If set to true, users get a corresponding option in their preferences and can choose to enable or disable at their discretion
+# If set to true, users get a corresponding option in their preferences and can choose to
+# enable or disable at their discretion
# If set to false, the corresponding input form on the user preference page is suppressed
# It call this to be a "user-preferences-option (UPO)"
@@ -1400,33 +1456,61 @@ $wgEnotifUseRealName = false;
*/
$wgUsersNotifiedOnAllChanges = array();
-
/** @} */ # end of email settings
/************************************************************************//**
* @name Database settings
* @{
*/
-/** Database host name or IP address */
+
+/**
+ * Database host name or IP address
+ */
$wgDBserver = 'localhost';
-/** Database port number (for PostgreSQL) */
+
+/**
+ * Database port number (for PostgreSQL)
+ */
$wgDBport = 5432;
-/** Name of the database */
+
+/**
+ * Name of the database
+ */
$wgDBname = 'my_wiki';
-/** Database username */
+
+/**
+ * Database username
+ */
$wgDBuser = 'wikiuser';
-/** Database user's password */
+
+/**
+ * Database user's password
+ */
$wgDBpassword = '';
-/** Database type */
+
+/**
+ * Database type
+ */
$wgDBtype = 'mysql';
-/** Whether to use SSL in DB connection. */
+
+/**
+ * Whether to use SSL in DB connection.
+ */
$wgDBssl = false;
-/** Whether to use compression in DB connection. */
+
+/**
+ * Whether to use compression in DB connection.
+ */
$wgDBcompress = false;
-/** Separate username for maintenance tasks. Leave as null to use the default. */
+/**
+ * Separate username for maintenance tasks. Leave as null to use the default.
+ */
$wgDBadminuser = null;
-/** Separate password for maintenance tasks. Leave as null to use the default. */
+
+/**
+ * Separate password for maintenance tasks. Leave as null to use the default.
+ */
$wgDBadminpassword = null;
/**
@@ -1437,9 +1521,23 @@ $wgDBadminpassword = null;
*/
$wgSearchType = null;
-/** Table name prefix */
+/**
+ * Alternative search types
+ * Sometimes you want to support multiple search engines for testing. This
+ * allows users to select their search engine of choice via url parameters
+ * to Special:Search and the action=search API. If using this, there's no
+ * need to add $wgSearchType to it, that is handled automatically.
+ */
+$wgSearchTypeAlternatives = null;
+
+/**
+ * Table name prefix
+ */
$wgDBprefix = '';
-/** MySQL table options to use during installation or update */
+
+/**
+ * MySQL table options to use during installation or update
+ */
$wgDBTableOptions = 'ENGINE=InnoDB';
/**
@@ -1450,10 +1548,14 @@ $wgDBTableOptions = 'ENGINE=InnoDB';
*/
$wgSQLMode = '';
-/** Mediawiki schema */
+/**
+ * Mediawiki schema
+ */
$wgDBmwschema = 'mediawiki';
-/** To override default SQLite data directory ($docroot/../data) */
+/**
+ * To override default SQLite data directory ($docroot/../data)
+ */
$wgSQLiteDataDir = '';
/**
@@ -1487,9 +1589,14 @@ $wgAllDBsAreLocalhost = false;
*/
$wgSharedDB = null;
-/** @see $wgSharedDB */
+/**
+ * @see $wgSharedDB
+ */
$wgSharedPrefix = false;
-/** @see $wgSharedDB */
+
+/**
+ * @see $wgSharedDB
+ */
$wgSharedTables = array( 'user', 'user_properties' );
/**
@@ -1509,11 +1616,11 @@ $wgSharedTables = array( 'user', 'user_properties' );
* - DBO_DEFAULT -- turns on DBO_TRX only if !$wgCommandLineMode (recommended)
* - DBO_DEBUG -- equivalent of $wgDebugDumpSql
* - DBO_TRX -- wrap entire request in a transaction
- * - DBO_IGNORE -- ignore errors (not useful in LocalSettings.php)
* - DBO_NOBUFFER -- turn off buffering (not useful in LocalSettings.php)
* - DBO_PERSISTENT -- enables persistent database connections
* - DBO_SSL -- uses SSL/TLS encryption in database connections, if available
- * - DBO_COMPRESS -- uses internal compression in database connections, if available
+ * - DBO_COMPRESS -- uses internal compression in database connections,
+ * if available
*
* - max lag: (optional) Maximum replication lag before a slave will taken out of rotation
* - max threads: (optional) Maximum number of running threads
@@ -1552,10 +1659,14 @@ $wgDBservers = false;
*/
$wgLBFactoryConf = array( 'class' => 'LBFactory_Simple' );
-/** How long to wait for a slave to catch up to the master */
+/**
+ * How long to wait for a slave to catch up to the master
+ */
$wgMasterWaitTimeout = 10;
-/** File to log database errors to */
+/**
+ * File to log database errors to
+ */
$wgDBerrorLog = false;
/**
@@ -1578,7 +1689,9 @@ $wgDBerrorLog = false;
*/
$wgDBerrorLogTZ = false;
-/** When to give an error message */
+/**
+ * When to give an error message
+ */
$wgDBClusterTimeout = 10;
/**
@@ -1607,6 +1720,35 @@ $wgDBAvgStatusPoll = 2000;
$wgDBmysql5 = false;
/**
+ * Set true to enable Oracle DCRP (supported from 11gR1 onward)
+ *
+ * To use this feature set to true and use a datasource defined as
+ * POOLED (i.e. in tnsnames definition set server=pooled in connect_data
+ * block).
+ *
+ * Starting from 11gR1 you can use DCRP (Database Resident Connection
+ * Pool) that maintains established sessions and reuses them on new
+ * connections.
+ *
+ * Not completely tested, but it should fall back on normal connection
+ * in case the pool is full or the datasource is not configured as
+ * pooled.
+ * And the other way around; using oci_pconnect on a non pooled
+ * datasource should produce a normal connection.
+ *
+ * When it comes to frequent shortlived DB connections like with MW
+ * Oracle tends to s***. The problem is the driver connects to the
+ * database reasonably fast, but establishing a session takes time and
+ * resources. MW does not rely on session state (as it does not use
+ * features such as package variables) so establishing a valid session
+ * is in this case an unwanted overhead that just slows things down.
+ *
+ * @warning EXPERIMENTAL!
+ *
+ */
+$wgDBOracleDRCP = false;
+
+/**
* Other wikis on this site, can be administered from a single developer
* account.
* Array numeric key => database name
@@ -1619,13 +1761,11 @@ $wgLocalDatabases = array();
* show a more obvious warning.
*/
$wgSlaveLagWarning = 10;
-/** @see $wgSlaveLagWarning */
-$wgSlaveLagCritical = 30;
/**
- * Use old names for change_tags indices.
+ * @see $wgSlaveLagWarning
*/
-$wgOldChangeTagsIndex = false;
+$wgSlaveLagCritical = 30;
/**@}*/ # End of DB settings }
@@ -1637,8 +1777,8 @@ $wgOldChangeTagsIndex = false;
/**
* We can also compress text stored in the 'text' table. If this is set on, new
* revisions will be compressed on page save if zlib support is available. Any
- * compressed revisions will be decompressed on load regardless of this setting
- * *but will not be readable at all* if zlib support is not available.
+ * compressed revisions will be decompressed on load regardless of this setting,
+ * but will not be readable at all* if zlib support is not available.
*/
$wgCompressRevisions = false;
@@ -1701,23 +1841,36 @@ $wgRevisionCacheExpiry = 0;
* @name Performance hacks and limits
* @{
*/
-/** Disable database-intensive features */
+
+/**
+ * Disable database-intensive features
+ */
$wgMiserMode = false;
-/** Disable all query pages if miser mode is on, not just some */
+
+/**
+ * Disable all query pages if miser mode is on, not just some
+ */
$wgDisableQueryPages = false;
-/** Number of rows to cache in 'querycache' table when miser mode is on */
+
+/**
+ * Number of rows to cache in 'querycache' table when miser mode is on
+ */
$wgQueryCacheLimit = 1000;
-/** Number of links to a page required before it is deemed "wanted" */
+
+/**
+ * Number of links to a page required before it is deemed "wanted"
+ */
$wgWantedPagesThreshold = 1;
-/** Enable slow parser functions */
+
+/**
+ * Enable slow parser functions
+ */
$wgAllowSlowParserFunctions = false;
-/** Allow schema updates */
-$wgAllowSchemaUpdates = true;
/**
- * Do DELETE/INSERT for link updates instead of incremental
+ * Allow schema updates
*/
-$wgUseDumbLinkUpdate = false;
+$wgAllowSchemaUpdates = true;
/**
* Anti-lock flags - bitfield
@@ -1886,11 +2039,15 @@ $wgObjectCacheSessionExpiry = 3600;
*/
$wgSessionHandler = null;
-/** If enabled, will send MemCached debugging information to $wgDebugLogFile */
+/**
+ * If enabled, will send MemCached debugging information to $wgDebugLogFile
+ */
$wgMemCachedDebug = false;
-/** The list of MemCached servers and port numbers */
-$wgMemCachedServers = array( '127.0.0.1:11000' );
+/**
+ * The list of MemCached servers and port numbers
+ */
+$wgMemCachedServers = array( '127.0.0.1:11211' );
/**
* Use persistent connections to MemCached, which are shared across multiple
@@ -1910,13 +2067,6 @@ $wgMemCachedTimeout = 500000;
$wgUseLocalMessageCache = false;
/**
- * Defines format of local cache.
- * - true: Serialized object
- * - false: PHP source file (Warning - security risk)
- */
-$wgLocalMessageCacheSerialized = true;
-
-/**
* 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.
@@ -1952,7 +2102,9 @@ $wgLocalisationCacheConf = array(
'manualRecache' => false,
);
-/** Allow client-side caching of pages */
+/**
+ * Allow client-side caching of pages
+ */
$wgCachePages = true;
/**
@@ -2038,7 +2190,8 @@ $wgUseGzip = false;
*/
$wgUseETag = false;
-/** Clock skew or the one-second resolution of time() can occasionally cause cache
+/**
+ * Clock skew or the one-second resolution of time() can occasionally cause cache
* problems when the user requests two pages within a short period of time. This
* variable adds a given number of seconds to vulnerable timestamps, thereby giving
* a grace period.
@@ -2077,13 +2230,18 @@ $wgInvalidateCacheOnLocalSettingsChange = true;
*/
$wgUseSquid = false;
-/** If you run Squid3 with ESI support, enable this (default:false): */
+/**
+ * If you run Squid3 with ESI support, enable this (default:false):
+ */
$wgUseESI = false;
-/** Send X-Vary-Options header for better caching (requires patched Squid) */
+/**
+ * Send X-Vary-Options header for better caching (requires patched Squid)
+ */
$wgUseXVO = false;
-/** Add X-Forwarded-Proto to the Vary and X-Vary-Options headers for API
+/**
+ * Add X-Forwarded-Proto to the Vary and X-Vary-Options headers for API
* requests and RSS/Atom feeds. Use this if you have an SSL termination setup
* and need to split the cache between HTTP and HTTPS for API requests,
* feed requests and HTTP redirect responses in order to prevent cache
@@ -2131,7 +2289,9 @@ $wgSquidServers = array();
*/
$wgSquidServersNoPurge = array();
-/** Maximum number of titles to purge in any one client operation */
+/**
+ * Maximum number of titles to purge in any one client operation
+ */
$wgMaxSquidPurgeTitles = 400;
/**
@@ -2163,12 +2323,12 @@ $wgSquidPurgeUseHostHeader = true;
* Each key in this array is a regular expression to match against the purged
* URL, or an empty string to match all URLs. The purged URL is matched against
* the regexes in the order specified, and the first rule whose regex matches
- * is used.
+ * is used, all remaining rules will thus be ignored.
*
- * Example configuration to send purges for upload.wikimedia.org to one
+ * @par Example configuration to send purges for upload.wikimedia.org to one
* multicast group and all other purges to another:
* @code
- * $wgHTCPMulticastRouting = array(
+ * $wgHTCPRouting = array(
* '|^https?://upload\.wikimedia\.org|' => array(
* 'host' => '239.128.0.113',
* 'port' => 4827,
@@ -2180,11 +2340,44 @@ $wgSquidPurgeUseHostHeader = true;
* );
* @endcode
*
- * @since 1.20
+ * You can also pass an array of hosts to send purges too. This is useful when
+ * you have several multicast groups or unicast address that should receive a
+ * given purge. Multiple hosts support was introduced in MediaWiki 1.22.
+ *
+ * @par Example of sending purges to multiple hosts:
+ * @code
+ * $wgHTCPRouting = array(
+ * '' => array(
+ * // Purges to text caches using multicast
+ * array( 'host' => '239.128.0.114', 'port' => '4827' ),
+ * // Purges to a hardcoded list of caches
+ * array( 'host' => '10.88.66.1', 'port' => '4827' ),
+ * array( 'host' => '10.88.66.2', 'port' => '4827' ),
+ * array( 'host' => '10.88.66.3', 'port' => '4827' ),
+ * ),
+ * );
+ * @endcode
+ *
+ * @since 1.22
+ *
+ * $wgHTCPRouting replaces $wgHTCPMulticastRouting that was introduced in 1.20.
+ * For back compatibility purposes, whenever its array is empty
+ * $wgHTCPMutlicastRouting will be used as a fallback if it not null.
*
* @see $wgHTCPMulticastTTL
*/
-$wgHTCPMulticastRouting = array();
+$wgHTCPRouting = array();
+
+/**
+ * @deprecated since 1.22, please use $wgHTCPRouting instead.
+ *
+ * Whenever this is set and $wgHTCPRouting evaluates to false, $wgHTCPRouting
+ * will be set to this value.
+ * This is merely for back compatibility.
+ *
+ * @since 1.20
+ */
+$wgHTCPMulticastRouting = null;
/**
* HTCP multicast address. Set this to a multicast IP address to enable HTCP.
@@ -2192,29 +2385,34 @@ $wgHTCPMulticastRouting = array();
* Note that MediaWiki uses the old non-RFC compliant HTCP format, which was
* present in the earliest Squid implementations of the protocol.
*
- * This setting is DEPRECATED in favor of $wgHTCPMulticastRouting , and kept
- * for backwards compatibility only. If $wgHTCPMulticastRouting is set, this
- * setting is ignored. If $wgHTCPMulticastRouting is not set and this setting
- * is, it is used to populate $wgHTCPMulticastRouting.
+ * This setting is DEPRECATED in favor of $wgHTCPRouting , and kept for
+ * backwards compatibility only. If $wgHTCPRouting is set, this setting is
+ * ignored. If $wgHTCPRouting is not set and this setting is, it is used to
+ * populate $wgHTCPRouting.
*
- * @deprecated since 1.20 in favor of $wgHTCPMulticastRouting
+ * @deprecated since 1.20 in favor of $wgHTCPMulticastRouting and since 1.22 in
+ * favor of $wgHTCPRouting.
*/
$wgHTCPMulticastAddress = false;
/**
* HTCP multicast port.
- * @deprecated since 1.20 in favor of $wgHTCPMulticastRouting
+ * @deprecated since 1.20 in favor of $wgHTCPMulticastRouting and since 1.22 in
+ * favor of $wgHTCPRouting.
+ *
* @see $wgHTCPMulticastAddress
*/
$wgHTCPPort = 4827;
/**
* HTCP multicast TTL.
- * @see $wgHTCPMulticastRouting
+ * @see $wgHTCPRouting
*/
$wgHTCPMulticastTTL = 1;
-/** Should forwarded Private IPs be accepted? */
+/**
+ * Should forwarded Private IPs be accepted?
+ */
$wgUsePrivateIPs = false;
/** @} */ # end of HTTP proxy settings
@@ -2258,13 +2456,19 @@ $wgLangObjCacheSize = 10;
*/
$wgGrammarForms = array();
-/** Treat language links as magic connectors, not inline links */
+/**
+ * Treat language links as magic connectors, not inline links
+ */
$wgInterwikiMagic = true;
-/** Hide interlanguage links from the sidebar */
+/**
+ * Hide interlanguage links from the sidebar
+ */
$wgHideInterlanguageLinks = false;
-/** List of language names or overrides for default names in Names.php */
+/**
+ * List of language names or overrides for default names in Names.php
+ */
$wgExtraLanguageNames = array();
/**
@@ -2364,7 +2568,8 @@ $wgBrowserBlackList = array(
'/^Mozilla\/4\.[^ ]+ [^(]*?\((?!compatible).*; [UIN]/',
/**
- * MSIE on Mac OS 9 is teh sux0r, converts þ to <thorn>, ð to <eth>, Þ to <THORN> and à to <ETH>
+ * MSIE on Mac OS 9 is teh sux0r, converts þ to <thorn>, ð to <eth>,
+ * Þ to <THORN> and à to <ETH>
*
* Known useragents:
* - Mozilla/4.0 (compatible; MSIE 5.0; Mac_PowerPC)
@@ -2372,7 +2577,7 @@ $wgBrowserBlackList = array(
* - Mozilla/4.0 (compatible; MSIE 5.23; Mac_PowerPC)
* - [...]
*
- * @link http://en.wikipedia.org/w/index.php?title=User%3A%C6var_Arnfj%F6r%F0_Bjarmason%2Ftestme&diff=12356041&oldid=12355864
+ * @link http://en.wikipedia.org/w/index.php?diff=12356041&oldid=12355864
* @link http://en.wikipedia.org/wiki/Template%3AOS9
*/
'/^Mozilla\/4\.0 \(compatible; MSIE \d+\.\d+; Mac_PowerPC\)/',
@@ -2403,6 +2608,7 @@ $wgLegacySchemaConversion = false;
* the interface is set to English.
*/
$wgAmericanDates = false;
+
/**
* For Hindi and Arabic use local numerals instead of Western style (0-9)
* numerals in interface.
@@ -2425,16 +2631,24 @@ $wgMsgCacheExpiry = 86400;
*/
$wgMaxMsgCacheEntrySize = 10000;
-/** Whether to enable language variant conversion. */
+/**
+ * Whether to enable language variant conversion.
+ */
$wgDisableLangConversion = false;
-/** Whether to enable language variant conversion for links. */
+/**
+ * Whether to enable language variant conversion for links.
+ */
$wgDisableTitleConversion = false;
-/** Whether to enable canonical 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 */
+/**
+ * Default variant code, if false, the default will be the language code
+ */
$wgDefaultLanguageVariant = false;
/**
@@ -2538,52 +2752,44 @@ $wgLocalTZoffset = null;
* @{
*/
-/** The default Content-Type header. */
-$wgMimeType = 'text/html';
-
-/**
- * The content type used in script tags. This is mostly going to be ignored if
- * $wgHtml5 is true, at least for actual HTML output, since HTML5 doesn't
- * require a MIME type for JavaScript or CSS (those are the default script and
- * style languages).
- */
-$wgJsMimeType = 'text/javascript';
-
/**
- * The HTML document type. Ignored if $wgHtml5 is true, since <!DOCTYPE html>
- * doesn't actually have a doctype part to put this variable's contents in.
+ * The default Content-Type header.
*/
-$wgDocType = '-//W3C//DTD XHTML 1.0 Transitional//EN';
+$wgMimeType = 'text/html';
/**
- * The URL of the document type declaration. Ignored if $wgHtml5 is true,
- * since HTML5 has no DTD, and <!DOCTYPE html> doesn't actually have a DTD part
- * to put this variable's contents in.
+ * Previously used as content type in HTML script tags. This is now ignored since
+ * HTML5 doesn't require a MIME type for script tags (javascript is the default).
+ * It was also previously used by RawAction to determine the ctype query parameter
+ * value that will result in a javascript response.
+ * @deprecated since 1.22
*/
-$wgDTD = 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd';
+$wgJsMimeType = null;
/**
- * The default xmlns attribute. Ignored if $wgHtml5 is true (or it's supposed
- * to be), since we don't currently support XHTML5, and in HTML5 (i.e., served
- * as text/html) the attribute has no effect, so why bother?
+ * The default xmlns attribute. The option to define this has been removed.
+ * The value of this variable is no longer used by core and is set to a fixed
+ * value in Setup.php for compatibility with extensions that depend on the value
+ * of this variable being set. Such a dependency however is deprecated.
+ * @deprecated since 1.22
*/
-$wgXhtmlDefaultNamespace = 'http://www.w3.org/1999/xhtml';
+$wgXhtmlDefaultNamespace = null;
/**
- * Should we output an HTML5 doctype? If false, use XHTML 1.0 Transitional
- * instead, and disable HTML5 features. This may eventually be removed and set
- * to always true. If it's true, a number of other settings will be irrelevant
- * and have no effect.
+ * Previously used to determine if we should output an HTML5 doctype.
+ * This is no longer used as we always output HTML5 now. For compatibility with
+ * extensions that still check the value of this config it's value is now forced
+ * to true by Setup.php.
+ * @deprecated since 1.22
*/
$wgHtml5 = true;
/**
* Defines the value of the version attribute in the &lt;html&gt; tag, if any.
- * This is ignored if $wgHtml5 is false. If $wgAllowRdfaAttributes and
- * $wgHtml5 are both true, and this evaluates to boolean false (like if it's
- * left at the default null value), it will be auto-initialized to the correct
- * value for RDFa+HTML5. As such, you should have no reason to ever actually
- * set this to anything.
+ * If $wgAllowRdfaAttributes is true, and this evaluates to boolean false
+ * (like if it's left at the default null value), it will be auto-initialized
+ * to the correct value for RDFa+HTML5. As such, you should have no reason to
+ * ever actually set this to anything.
*/
$wgHtml5Version = null;
@@ -2594,7 +2800,7 @@ $wgHtml5Version = null;
$wgAllowRdfaAttributes = false;
/**
- * Enabled HTML5 microdata attributes for use in wikitext, if $wgHtml5 is also true.
+ * Enabled HTML5 microdata attributes for use in wikitext.
*/
$wgAllowMicrodataAttributes = false;
@@ -2628,8 +2834,7 @@ $wgWellFormedXml = true;
* Normally we wouldn't have to define this in the root "<html>"
* element, but IE needs it there in some circumstances.
*
- * This is ignored if $wgHtml5 is true, for the same reason as
- * $wgXhtmlDefaultNamespace.
+ * This is ignored if $wgMimeType is set to a non-XML mimetype.
*/
$wgXhtmlNamespaces = array();
@@ -2679,34 +2884,11 @@ $wgDefaultSkin = 'vector';
* remove from the .../skins/ directory
*/
$wgSkipSkin = '';
-/** Array for more like $wgSkipSkin. */
-$wgSkipSkins = array();
/**
- * Optionally, we can specify a stylesheet to use for media="handheld".
- * This is recognized by some, but not all, handheld/mobile/PDA browsers.
- * If left empty, compliant handheld browsers won't pick up the skin
- * stylesheet, which is specified for 'screen' media.
- *
- * Can be a complete URL, base-relative path, or $wgStylePath-relative path.
- * Try 'chick/main.css' to apply the Chick styles to the MonoBook HTML.
- *
- * Will also be switched in when 'handheld=yes' is added to the URL, like
- * the 'printable=yes' mode for print media.
+ * Array for more like $wgSkipSkin.
*/
-$wgHandheldStyle = false;
-
-/**
- * If set, 'screen' and 'handheld' media specifiers for stylesheets are
- * transformed such that they apply to the iPhone/iPod Touch Mobile Safari,
- * which doesn't recognize 'handheld' but does support media queries on its
- * screen size.
- *
- * Consider only using this if you have a *really good* handheld stylesheet,
- * as iPhone users won't have any way to disable it and use the "grown-up"
- * styles instead.
- */
-$wgHandheldForIPhone = false;
+$wgSkipSkins = array();
/**
* Allow user Javascript page?
@@ -2729,10 +2911,14 @@ $wgAllowUserCss = false;
*/
$wgAllowUserCssPrefs = true;
-/** Use the site's Javascript page? */
+/**
+ * Use the site's Javascript page?
+ */
$wgUseSiteJs = true;
-/** Use the site's Cascading Style Sheets (CSS)? */
+/**
+ * Use the site's Cascading Style Sheets (CSS)?
+ */
$wgUseSiteCss = true;
/**
@@ -2771,7 +2957,6 @@ $wgEditPageFrameOptions = 'DENY';
* - 'SAMEORIGIN': Allow framing by pages on the same domain.
* - false: Allow all framing.
*/
-
$wgApiFrameOptions = 'DENY';
/**
@@ -2807,9 +2992,10 @@ $wgExperimentalHtmlIds = false;
* The value should be either a string or an array. If it is a string it will be output
* directly as html, however some skins may choose to ignore it. An array is the preferred format
* for the icon, the following keys are used:
- * - src: An absolute url to the image to use for the icon, this is recommended
+ * - 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 around 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.
@@ -2851,7 +3037,7 @@ $wgVectorUseSimpleSearch = true;
* - true = use an icon watch/unwatch button
* - false = use watch/unwatch text link
*/
-$wgVectorUseIconWatch = false;
+$wgVectorUseIconWatch = true;
/**
* Display user edit counts in various prominent places.
@@ -3052,8 +3238,10 @@ $wgLegacyJavaScriptGlobals = true;
*
* If set to a negative number, ResourceLoader will assume there is no query
* string length limit.
+ *
+ * Defaults to a value based on php configuration.
*/
-$wgResourceLoaderMaxQueryLength = -1;
+$wgResourceLoaderMaxQueryLength = false;
/**
* If set to true, JavaScript modules loaded from wiki pages will be parsed
@@ -3081,6 +3269,59 @@ $wgResourceLoaderValidateStaticJS = false;
*/
$wgResourceLoaderExperimentalAsyncLoading = false;
+/**
+ * Global LESS variables. An associative array binding variable names to CSS
+ * string values.
+ *
+ * Because the hashed contents of this array are used to construct the cache key
+ * that ResourceLoader uses to look up LESS compilation results, updating this
+ * array can be used to deliberately invalidate the set of cached results.
+ *
+ * @par Example:
+ * @code
+ * $wgResourceLoaderLESSVars = array(
+ * 'baseFontSize' => '1em',
+ * 'smallFontSize' => '0.75em',
+ * 'WikimediaBlue' => '#006699',
+ * );
+ * @endcode
+ * @since 1.22
+ */
+$wgResourceLoaderLESSVars = array();
+
+/**
+ * Custom LESS functions. An associative array mapping function name to PHP
+ * callable.
+ *
+ * Changes to LESS functions do not trigger cache invalidation. If you update
+ * the behavior of a LESS function and need to invalidate stale compilation
+ * results, you can touch one of values in $wgResourceLoaderLESSVars, as
+ * documented above.
+ *
+ * @since 1.22
+ */
+$wgResourceLoaderLESSFunctions = array(
+ 'embeddable' => 'ResourceLoaderLESSFunctions::embeddable',
+ 'embed' => 'ResourceLoaderLESSFunctions::embed',
+);
+
+/**
+ * Default import paths for LESS modules. LESS files referenced in @import
+ * statements will be looked up here first, and relative to the importing file
+ * second. To avoid collisions, it's important for the LESS files in these
+ * directories to have a common, predictable file name prefix.
+ *
+ * Extensions need not (and should not) register paths in
+ * $wgResourceLoaderLESSImportPaths. The import path includes the path of the
+ * currently compiling LESS file, which allows each extension to freely import
+ * files from its own tree.
+ *
+ * @since 1.22
+ */
+$wgResourceLoaderLESSImportPaths = array(
+ "$IP/resources/mediawiki.less/",
+);
+
/** @} */ # End of resource loader settings }
/*************************************************************************//**
@@ -3167,7 +3408,8 @@ $wgNamespaceAliases = array();
* - []{}|# Are needed for link syntax, never enable these
* - <> Causes problems with HTML escaping, don't use
* - % Enabled by default, minor problems with path to query rewrite rules, see below
- * - + Enabled by default, but doesn't work with path to query rewrite rules, corrupted by apache
+ * - + Enabled by default, but doesn't work with path to query rewrite rules,
+ * corrupted by apache
* - ? Enabled by default, but doesn't work with path to PATH_INFO rewrites
*
* All three of these punctuation problems can be avoided by using an alias,
@@ -3201,6 +3443,7 @@ $wgInterwikiExpiry = 10800;
* @name Interwiki caching settings.
* @{
*/
+
/**
*$wgInterwikiCache specifies path to constant database file.
*
@@ -3215,6 +3458,7 @@ $wgInterwikiExpiry = 10800;
* data layout.
*/
$wgInterwikiCache = false;
+
/**
* Specify number of domains to check for messages.
* - 1: Just wiki(db)-level
@@ -3222,10 +3466,12 @@ $wgInterwikiCache = false;
* - 3: site levels
*/
$wgInterwikiScopes = 3;
+
/**
- * $wgInterwikiFallbackSite - if unable to resolve from cache
+ * Fallback site, if unable to resolve from cache
*/
$wgInterwikiFallbackSite = 'wiki';
+
/** @} */ # end of Interwiki caching settings.
/**
@@ -3270,7 +3516,8 @@ $wgCapitalLinks = true;
*/
$wgCapitalLinkOverrides = array();
-/** Which namespaces should support subpages?
+/**
+ * Which namespaces should support subpages?
* See Language.php for a list of namespaces.
*/
$wgNamespacesWithSubpages = array(
@@ -3313,7 +3560,7 @@ $wgMaxRedirects = 1;
* As of now, this only checks special pages. Redirects to pages in
* other namespaces cannot be invalidated by this variable.
*/
-$wgInvalidRedirectTargets = array( 'Filepath', 'Mypage', 'Mytalk' );
+$wgInvalidRedirectTargets = array( 'Filepath', 'Mypage', 'Mytalk', 'Redirect' );
/** @} */ # End of title and interwiki settings }
@@ -3349,7 +3596,9 @@ $wgParserConf = array(
#'preprocessorClass' => 'Preprocessor_Hash',
);
-/** Maximum indent level of toc. */
+/**
+ * Maximum indent level of toc.
+ */
$wgMaxTocLevel = 999;
/**
@@ -3377,25 +3626,41 @@ $wgMaxGeneratedPPNodeCount = 1000000;
*/
$wgMaxTemplateDepth = 40;
-/** @see $wgMaxTemplateDepth */
+/**
+ * @see $wgMaxTemplateDepth
+ */
$wgMaxPPExpandDepth = 40;
-/** The external URL protocols */
+/**
+ * The external URL protocols
+ */
$wgUrlProtocols = array(
'http://',
'https://',
'ftp://',
+ 'ftps://', // If we allow ftp:// we should allow the secure version.
+ 'ssh://',
+ 'sftp://', // SFTP > FTP
'irc://',
'ircs://', // @bug 28503
+ 'xmpp:', // Another open communication protocol
+ 'sip:',
+ 'sips:',
'gopher://',
'telnet://', // Well if we're going to support the above.. -ævar
'nntp://', // @bug 3808 RFC 1738
'worldwind://',
'mailto:',
+ 'tel:', // If we can make emails linkable, why not phone numbers?
+ 'sms:', // Likewise this is standardized too
'news:',
'svn://',
'git://',
'mms://',
+ 'bitcoin:', // Even registerProtocolHandler whitelists this along with mailto:
+ 'magnet:', // No reason to reject torrents over magnet: when they're allowed over http://
+ 'urn:', // Allow URNs to be used in Microdata/RDFa <link ... href="urn:...">s
+ 'geo:', // urls define geo locations, they're useful in Microdata/RDFa and for coordinates
'//', // for protocol-relative URLs
);
@@ -3404,7 +3669,9 @@ $wgUrlProtocols = array(
*/
$wgCleanSignatures = true;
-/** Whether to allow inline image pointing to other websites */
+/**
+ * Whether to allow inline image pointing to other websites
+ */
$wgAllowExternalImages = false;
/**
@@ -3421,7 +3688,8 @@ $wgAllowExternalImages = false;
*/
$wgAllowExternalImagesFrom = '';
-/** If $wgAllowExternalImages is false, you can allow an on-wiki
+/**
+ * If $wgAllowExternalImages is false, you can allow an on-wiki
* whitelist of regular expression fragments to match the image URL
* against. If the image matches one of the regular expression fragments,
* The image will be displayed.
@@ -3457,15 +3725,30 @@ $wgAllowImageTag = false;
* 'extension=tidy.so' to php.ini.
*/
$wgUseTidy = false;
-/** @see $wgUseTidy */
+
+/**
+ * @see $wgUseTidy
+ */
$wgAlwaysUseTidy = false;
-/** @see $wgUseTidy */
+
+/**
+ * @see $wgUseTidy
+ */
$wgTidyBin = 'tidy';
-/** @see $wgUseTidy */
+
+/**
+ * @see $wgUseTidy
+ */
$wgTidyConf = $IP . '/includes/tidy.conf';
-/** @see $wgUseTidy */
+
+/**
+ * @see $wgUseTidy
+ */
$wgTidyOpts = '';
-/** @see $wgUseTidy */
+
+/**
+ * @see $wgUseTidy
+ */
$wgTidyInternal = extension_loaded( 'tidy' );
/**
@@ -3474,7 +3757,8 @@ $wgTidyInternal = extension_loaded( 'tidy' );
*/
$wgDebugTidy = false;
-/** Allow raw, unchecked HTML in "<html>...</html>" sections.
+/**
+ * Allow raw, unchecked HTML in "<html>...</html>" sections.
* THIS IS VERY DANGEROUS on a publicly editable site, so USE wgGroupPermissions
* TO RESTRICT EDITING to only those that you trust
*/
@@ -3518,8 +3802,9 @@ $wgNoFollowDomainExceptions = array();
$wgAllowDisplayTitle = true;
/**
- * For consistency, restrict DISPLAYTITLE to titles that normalize to the same
- * canonical DB key.
+ * For consistency, restrict DISPLAYTITLE to text that normalizes to the same
+ * canonical DB key. Also disallow some inline CSS rules like display: none;
+ * which can cause the text to be hidden or unselectable.
*/
$wgRestrictDisplayTitle = true;
@@ -3536,12 +3821,13 @@ $wgExpensiveParserFunctionLimit = 100;
$wgPreprocessorCacheThreshold = 1000;
/**
- * Enable interwiki transcluding. Only when iw_trans=1.
+ * Enable interwiki transcluding. Only when iw_trans=1 in the interwiki table.
*/
$wgEnableScaryTranscluding = false;
/**
- * (see next option $wgGlobalDatabase).
+ * Expiry time for transcluded templates cached in transcache database table.
+ * Only used $wgEnableInterwikiTranscluding is set to true.
*/
$wgTranscludeCacheExpiry = 3600;
@@ -3591,7 +3877,8 @@ $wgHitcounterUpdateFreq = 1;
/**
* How many days user must be idle before he is considered inactive. Will affect
- * the number shown on Special:Statistics and Special:ActiveUsers special page.
+ * the number shown on Special:Statistics, Special:ActiveUsers, and the
+ * {{NUMBEROFACTIVEUSERS}} magic word in wikitext.
* You might want to leave this as the default value, to provide comparable
* numbers between different wikis.
*/
@@ -3604,7 +3891,9 @@ $wgActiveUserDays = 30;
* @{
*/
-/** For compatibility with old installations set to false */
+/**
+ * For compatibility with old installations set to false
+ */
$wgPasswordSalt = true;
/**
@@ -3647,7 +3936,7 @@ $wgReservedUsernames = array(
'ScriptImporter', // Default user name used by maintenance/importSiteScripts.php
'msg:double-redirect-fixer', // Automatic double redirect fix
'msg:usermessage-editor', // Default user for leaving user messages
- 'msg:proxyblocker', // For Special:Blockme
+ 'msg:proxyblocker', // For $wgProxyList and Special:Blockme (removed in 1.22)
);
/**
@@ -3655,7 +3944,6 @@ $wgReservedUsernames = array(
* preferences used by anonymous visitors and newly created accounts.
* For instance, to disable section editing links:
* $wgDefaultUserOptions ['editsection'] = 0;
- *
*/
$wgDefaultUserOptions = array(
'ccmeonemails' => 0,
@@ -3673,8 +3961,6 @@ $wgDefaultUserOptions = array(
'enotifusertalkpages' => 1,
'enotifwatchlistpages' => 0,
'extendwatchlist' => 0,
- 'externaldiff' => 0,
- 'externaleditor' => 0,
'fancysig' => 0,
'forceeditsummary' => 0,
'gender' => 'unknown',
@@ -3691,14 +3977,12 @@ $wgDefaultUserOptions = array(
'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,
@@ -3708,6 +3992,7 @@ $wgDefaultUserOptions = array(
'underline' => 2,
'uselivepreview' => 0,
'usenewrc' => 0,
+ 'vector-simplesearch' => 1,
'watchcreations' => 0,
'watchdefault' => 0,
'watchdeletion' => 0,
@@ -3720,9 +4005,13 @@ $wgDefaultUserOptions = array(
'watchlisthidepatrolled' => 0,
'watchmoves' => 0,
'wllimit' => 250,
+ 'useeditwarning' => 1,
+ 'prefershttps' => 1,
);
-/** An array of preferences to not show for the user */
+/**
+ * An array of preferences to not show for the user
+ */
$wgHiddenPrefs = array();
/**
@@ -3744,63 +4033,6 @@ $wgInvalidUsernameCharacters = '@';
$wgUserrightsInterwikiDelimiter = '@';
/**
- * Use some particular type of external authentication. The specific
- * authentication module you use will normally require some extra settings to
- * be specified.
- *
- * null indicates no external authentication is to be used. Otherwise,
- * $wgExternalAuthType must be the name of a non-abstract class that extends
- * ExternalUser.
- *
- * Core authentication modules can be found in includes/extauth/.
- */
-$wgExternalAuthType = null;
-
-/**
- * Configuration for the external authentication. This may include arbitrary
- * keys that depend on the authentication mechanism. For instance,
- * authentication against another web app might require that the database login
- * info be provided. Check the file where your auth mechanism is defined for
- * info on what to put here.
- */
-$wgExternalAuthConf = array();
-
-/**
- * When should we automatically create local accounts when external accounts
- * already exist, if using ExternalAuth? Can have three values: 'never',
- * 'login', 'view'. 'view' requires the external database to support cookies,
- * and implies 'login'.
- *
- * TODO: Implement 'view' (currently behaves like 'login').
- */
-$wgAutocreatePolicy = 'login';
-
-/**
- * Policies for how each preference is allowed to be changed, in the presence
- * of external authentication. The keys are preference keys, e.g., 'password'
- * or 'emailaddress' (see Preferences.php et al.). The value can be one of the
- * following:
- *
- * - local: Allow changes to this pref through the wiki interface but only
- * apply them locally (default).
- * - semiglobal: Allow changes through the wiki interface and try to apply them
- * to the foreign database, but continue on anyway if that fails.
- * - global: Allow changes through the wiki interface, but only let them go
- * through if they successfully update the foreign database.
- * - message: Allow no local changes for linked accounts; replace the change
- * form with a message provided by the auth plugin, telling the user how to
- * change the setting externally (maybe providing a link, etc.). If the auth
- * plugin provides no message for this preference, hide it entirely.
- *
- * Accounts that are not linked to an external account are never affected by
- * this setting. You may want to look at $wgHiddenPrefs instead.
- * $wgHiddenPrefs supersedes this option.
- *
- * TODO: Implement message, global.
- */
-$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/050039.html
@@ -3808,13 +4040,6 @@ $wgAllowPrefChange = array();
*/
$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 }
/************************************************************************//**
@@ -3832,7 +4057,9 @@ $wgAutoblockExpiry = 86400;
*/
$wgBlockAllowsUTEdit = false;
-/** Allow sysops to ban users from accessing Emailuser */
+/**
+ * Allow sysops to ban users from accessing Emailuser
+ */
$wgSysopEmailBans = true;
/**
@@ -3950,6 +4177,13 @@ $wgGroupPermissions['*']['edit'] = true;
$wgGroupPermissions['*']['createpage'] = true;
$wgGroupPermissions['*']['createtalk'] = true;
$wgGroupPermissions['*']['writeapi'] = true;
+$wgGroupPermissions['*']['editmyusercss'] = true;
+$wgGroupPermissions['*']['editmyuserjs'] = true;
+$wgGroupPermissions['*']['viewmywatchlist'] = true;
+$wgGroupPermissions['*']['editmywatchlist'] = true;
+$wgGroupPermissions['*']['viewmyprivateinfo'] = true;
+$wgGroupPermissions['*']['editmyprivateinfo'] = true;
+$wgGroupPermissions['*']['editmyoptions'] = true;
#$wgGroupPermissions['*']['patrolmarks'] = false; // let anons see what was patrolled
// Implicit group for all logged-in accounts
@@ -3971,25 +4205,29 @@ $wgGroupPermissions['user']['sendemail'] = true;
// Implicit group for accounts that pass $wgAutoConfirmAge
$wgGroupPermissions['autoconfirmed']['autoconfirmed'] = true;
+$wgGroupPermissions['autoconfirmed']['editsemiprotected'] = 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']['editsemiprotected'] = 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
// 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
+// can be separately configured for pages with > $wgDeleteRevisionsLimit revs
+$wgGroupPermissions['sysop']['bigdelete'] = true;
+// can view deleted history entries, but not see or restore the text
+$wgGroupPermissions['sysop']['deletedhistory'] = true;
+// can view deleted revision text
+$wgGroupPermissions['sysop']['deletedtext'] = true;
$wgGroupPermissions['sysop']['undelete'] = true;
$wgGroupPermissions['sysop']['editinterface'] = true;
$wgGroupPermissions['sysop']['editusercss'] = true;
@@ -4002,6 +4240,7 @@ $wgGroupPermissions['sysop']['move-rootuserpages'] = true;
$wgGroupPermissions['sysop']['patrol'] = true;
$wgGroupPermissions['sysop']['autopatrol'] = true;
$wgGroupPermissions['sysop']['protect'] = true;
+$wgGroupPermissions['sysop']['editprotected'] = true;
$wgGroupPermissions['sysop']['proxyunbannable'] = true;
$wgGroupPermissions['sysop']['rollback'] = true;
$wgGroupPermissions['sysop']['upload'] = true;
@@ -4009,6 +4248,7 @@ $wgGroupPermissions['sysop']['reupload'] = true;
$wgGroupPermissions['sysop']['reupload-shared'] = true;
$wgGroupPermissions['sysop']['unwatchedpages'] = true;
$wgGroupPermissions['sysop']['autoconfirmed'] = true;
+$wgGroupPermissions['sysop']['editsemiprotected'] = true;
$wgGroupPermissions['sysop']['ipblock-exempt'] = true;
$wgGroupPermissions['sysop']['blockemail'] = true;
$wgGroupPermissions['sysop']['markbotedits'] = true;
@@ -4084,11 +4324,12 @@ $wgImplicitGroups = array( '*', 'user', 'autoconfirmed' );
* @endcode
* This allows users in the '*' group (i.e. any user) to remove themselves from
* any group that they happen to be in.
- *
*/
$wgGroupsAddToSelf = array();
-/** @see $wgGroupsAddToSelf */
+/**
+ * @see $wgGroupsAddToSelf
+ */
$wgGroupsRemoveFromSelf = array();
/**
@@ -4108,11 +4349,37 @@ $wgRestrictionTypes = array( 'create', 'edit', 'move', 'upload' );
* dictates the order on the protection form's lists.
*
* - '' will be ignored (i.e. unprotected)
- * - 'sysop' is quietly rewritten to 'protect' for backwards compatibility
+ * - 'autoconfirmed' is quietly rewritten to 'editsemiprotected' for backwards compatibility
+ * - 'sysop' is quietly rewritten to 'editprotected' for backwards compatibility
*/
$wgRestrictionLevels = array( '', 'autoconfirmed', 'sysop' );
/**
+ * Restriction levels that can be used with cascading protection
+ *
+ * A page can only be protected with cascading protection if the
+ * requested restriction level is included in this array.
+ *
+ * 'autoconfirmed' is quietly rewritten to 'editsemiprotected' for backwards compatibility.
+ * 'sysop' is quietly rewritten to 'editprotected' for backwards compatibility.
+ */
+$wgCascadingRestrictionLevels = array( 'sysop' );
+
+/**
+ * Restriction levels that should be considered "semiprotected"
+ *
+ * Certain places in the interface recognize a dichotomy between "protected"
+ * and "semiprotected", without further distinguishing the specific levels. In
+ * general, if anyone can be eligible to edit a protection level merely by
+ * reaching some condition in $wgAutopromote, it should probably be considered
+ * "semiprotected".
+ *
+ * 'autoconfirmed' is quietly rewritten to 'editsemiprotected' for backwards compatibility.
+ * 'sysop' is not changed, since it really shouldn't be here.
+ */
+$wgSemiprotectedRestrictionLevels = array( 'autoconfirmed' );
+
+/**
* Set the minimum permissions required to edit pages in each
* namespace. If you list more than one permission, a user must
* have all of them to edit pages in that namespace.
@@ -4248,7 +4515,10 @@ $wgAutopromoteOnceLogInRC = true;
* @endcode
*/
$wgAddGroups = array();
-/** @see $wgAddGroups */
+
+/**
+ * @see $wgAddGroups
+ */
$wgRemoveGroups = array();
/**
@@ -4283,7 +4553,9 @@ $wgAccountCreationThrottle = 0;
*/
$wgSpamRegex = array();
-/** Same as the above except for edit summaries */
+/**
+ * Same as the above except for edit summaries
+ */
$wgSummarySpamRegex = array();
/**
@@ -4337,6 +4609,13 @@ $wgSorbsUrl = array();
$wgProxyWhitelist = array();
/**
+ * Whether to look at the X-Forwarded-For header's list of (potentially spoofed)
+ * IPs and apply IP blocks to them. This allows for IP blocks to work with correctly-configured
+ * (transparent) proxies without needing to block the proxies themselves.
+ */
+$wgApplyIpBlocksToXff = false;
+
+/**
* Simple rate limiter options to brake edit floods.
*
* Maximum number actions allowed in the given number of seconds; after that
@@ -4362,7 +4641,7 @@ $wgRateLimits = array(
'user' => null, // for each logged-in user
'newbie' => null, // for each recent (autoconfirmed) account; overrides 'user'
'ip' => null, // for each anon and recent account
- 'subnet' => null, // ... with final octet removed
+ 'subnet' => null, // ... within a /24 subnet in IPv4 or /64 in IPv6
),
'move' => array(
'user' => null,
@@ -4370,11 +4649,25 @@ $wgRateLimits = array(
'ip' => null,
'subnet' => null,
),
- 'mailpassword' => array(
+ 'mailpassword' => array( // triggering password resets emails
'anon' => null,
),
- 'emailuser' => array(
+ 'emailuser' => array( // emailing other users using MediaWiki
+ 'user' => null,
+ ),
+ 'linkpurge' => array( // purges of link tables
+ 'anon' => null,
'user' => null,
+ 'newbie' => null,
+ 'ip' => null,
+ 'subnet' => null,
+ ),
+ 'renderfile' => array( // files rendered via thumb.php or thumb_handler.php
+ 'anon' => null,
+ 'user' => null,
+ 'newbie' => null,
+ 'ip' => null,
+ 'subnet' => null,
),
);
@@ -4417,22 +4710,8 @@ $wgPasswordAttemptThrottle = array( 'count' => 5, 'seconds' => 300 );
*/
/**
- * If you enable this, every editor's IP address will be scanned for open HTTP
- * proxies.
- *
- * @warning Don't enable this. Many sysops will report "hostile TCP port scans"
- * to your ISP and ask for your server to be shut down.
- * You have been warned.
- *
+ * This should always be customised in LocalSettings.php
*/
-$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/proxyCheck.php";
-/** */
-$wgProxyMemcExpiry = 86400;
-/** This should always be customised in LocalSettings.php */
$wgSecretKey = false;
/**
@@ -4516,10 +4795,14 @@ $wgHttpOnlyBlacklist = array(
'/^Mozilla\/4\.0 \(compatible; MSIE \d+\.\d+; Mac_PowerPC\)/',
);
-/** A list of cookies that vary the cache (for use by extensions) */
+/**
+ * A list of cookies that vary the cache (for use by extensions)
+ */
$wgCacheVaryCookies = array();
-/** Override to customise the session name */
+/**
+ * Override to customise the session name
+ */
$wgSessionName = false;
/** @} */ # end of cookie settings }
@@ -4537,7 +4820,7 @@ $wgSessionName = false;
*/
$wgUseTeX = false;
-/* @} */ # end LaTeX }
+/** @} */ # end LaTeX }
/************************************************************************//**
* @name Profiling, testing and debugging
@@ -4676,10 +4959,14 @@ $wgDevelopmentWarnings = false;
*/
$wgDeprecationReleaseLimit = false;
-/** Only record profiling info for pages that took longer than this */
+/**
+ * Only record profiling info for pages that took longer than this
+ */
$wgProfileLimit = 0.0;
-/** Don't put non-profiling info into log file */
+/**
+ * Don't put non-profiling info into log file
+ */
$wgProfileOnly = false;
/**
@@ -4694,10 +4981,14 @@ $wgProfileOnly = false;
*/
$wgProfileToDatabase = false;
-/** If true, print a raw call tree instead of per-function report */
+/**
+ * If true, print a raw call tree instead of per-function report
+ */
$wgProfileCallTree = false;
-/** Should application server host be put into profiling table */
+/**
+ * Should application server host be put into profiling table
+ */
$wgProfilePerHost = false;
/**
@@ -4714,10 +5005,25 @@ $wgUDPProfilerHost = '127.0.0.1';
*/
$wgUDPProfilerPort = '3811';
-/** Detects non-matching wfProfileIn/wfProfileOut calls */
+/**
+ * Format string for the UDP profiler. The UDP profiler invokes sprintf() with
+ * (profile id, count, cpu, cpu_sq, real, real_sq, entry name) as arguments.
+ * You can use sprintf's argument numbering/swapping capability to repeat,
+ * re-order or omit fields.
+ *
+ * @see $wgStatsFormatString
+ * @since 1.22
+ */
+$wgUDPProfilerFormatString = "%s - %d %f %f %f %f %s\n";
+
+/**
+ * Detects non-matching wfProfileIn/wfProfileOut calls
+ */
$wgDebugProfiling = false;
-/** Output debug message on every wfProfileIn/wfProfileOut */
+/**
+ * Output debug message on every wfProfileIn/wfProfileOut
+ */
$wgDebugFunctionEntry = false;
/**
@@ -4736,7 +5042,21 @@ $wgStatsMethod = 'cache';
*/
$wgAggregateStatsID = false;
-/** Whereas to count the number of time an article is viewed.
+/**
+ * When $wgStatsMethod is 'udp', this variable specifies how stats should be
+ * formatted. Its value should be a format string suitable for a sprintf()
+ * invocation with (id, count, key) arguments, where 'id' is either
+ * $wgAggregateStatsID or the DB name, 'count' is the value by which the metric
+ * is being incremented, and 'key' is the metric name.
+ *
+ * @see $wgUDPProfilerFormatString
+ * @see $wgAggregateStatsID
+ * @since 1.22
+ */
+$wgStatsFormatString = "stats/%s - %s 1 1 1 1 %s\n";
+
+/**
+ * Whereas to count the number of time an article is viewed.
* Does not work if pages are cached (for example with squid).
*/
$wgDisableCounters = false;
@@ -4867,7 +5187,6 @@ $wgCountTotalSearchHits = false;
* PHP wrapper to avoid firing up mediawiki for every keystroke
*
* Placeholders: {searchTerms}
- *
*/
$wgOpenSearchTemplate = false;
@@ -4919,7 +5238,6 @@ $wgNamespacesToBeSearchedHelp = array(
* logged-in users.
* Useful for big wikis to maintain different search profiles for anonymous and
* logged-in users.
- *
*/
$wgSearchEverythingOnlyLoggedIn = false;
@@ -5011,12 +5329,8 @@ $wgPreviewOnOpenNamespaces = array(
);
/**
- * Activate external editor interface for files and pages
- * See http://www.mediawiki.org/wiki/Manual:External_editors
+ * Go button goes straight to the edit screen if the article doesn't exist.
*/
-$wgUseExternalEditor = true;
-
-/** Go button goes straight to the edit screen if the article doesn't exist. */
$wgGoToEdit = false;
/**
@@ -5050,7 +5364,9 @@ if ( !isset( $wgCommandLineMode ) ) {
}
/** @endcond */
-/** For colorized maintenance script output, is your terminal background dark ? */
+/**
+ * For colorized maintenance script output, is your terminal background dark ?
+ */
$wgCommandLineDarkBg = false;
/**
@@ -5091,6 +5407,11 @@ $wgReadOnlyFile = false;
$wgUpgradeKey = false;
/**
+ * Fully specified path to git binary
+ */
+$wgGitBin = '/usr/bin/git';
+
+/**
* Map GIT repository URLs to viewer URLs to provide links in Special:Version
*
* Key is a pattern passed to preg_match() and preg_replace(),
@@ -5098,12 +5419,14 @@ $wgUpgradeKey = false;
* The value is the replacement for the key (it can contain $1, etc.)
* %h will be replaced by the short SHA-1 (7 first chars) and %H by the
* full SHA-1 of the HEAD revision.
+ * %r will be replaced with a URL-encoded version of $1.
*
* @since 1.20
*/
$wgGitRepositoryViewers = array(
- 'https://gerrit.wikimedia.org/r/p/(.*)' => 'https://gerrit.wikimedia.org/r/gitweb?p=$1;h=%H',
- 'ssh://(?:[a-z0-9_]+@)?gerrit.wikimedia.org:29418/(.*)' => 'https://gerrit.wikimedia.org/r/gitweb?p=$1;h=%H',
+ 'https://gerrit.wikimedia.org/r/p/(.*)' => 'https://git.wikimedia.org/commit/%r/%H',
+ 'ssh://(?:[a-z0-9_]+@)?gerrit.wikimedia.org:29418/(.*)'
+ => 'https://git.wikimedia.org/commit/%r/%H',
);
/** @} */ # End of maintenance }
@@ -5139,11 +5462,15 @@ $wgRCLinkDays = array( 1, 3, 7, 14, 30 );
/**
* Send recent changes updates via UDP. The updates will be formatted for IRC.
* Set this to the IP address of the receiver.
+ *
+ * @deprecated since 1.22, use $wgRCFeeds
*/
$wgRC2UDPAddress = false;
/**
* Port number for RC updates
+ *
+ * @deprecated since 1.22, use $wgRCFeeds
*/
$wgRC2UDPPort = false;
@@ -5152,42 +5479,108 @@ $wgRC2UDPPort = false;
* This can be used to identify the wiki. A script is available called
* mxircecho.py which listens on a UDP port, and uses a prefix ending in a
* tab to identify the IRC channel to send the log line to.
+ *
+ * @deprecated since 1.22, use $wgRCFeeds
*/
$wgRC2UDPPrefix = '';
/**
* If this is set to true, $wgLocalInterwiki will be prepended to links in the
* IRC feed. If this is set to a string, that string will be used as the prefix.
+ *
+ * @deprecated since 1.22, use $wgRCFeeds
*/
$wgRC2UDPInterwikiPrefix = false;
/**
* Set to true to omit "bot" edits (by users with the bot permission) from the
* UDP feed.
+ *
+ * @deprecated since 1.22, use $wgRCFeeds
*/
$wgRC2UDPOmitBots = false;
/**
+ * Destinations to which notifications about recent changes
+ * should be sent.
+ *
+ * As of MediaWiki 1.22, the only supported 'engine' parameter option in core
+ * is 'UDPRCFeedEngine', which is used to send recent changes over UDP to the
+ * specified server.
+ * The common options are:
+ * * 'uri' -- the address to which the notices are to be sent.
+ * * 'formatter' -- the class name (implementing RCFeedFormatter) which will
+ * produce the text to send.
+ * * 'omit_bots' -- whether the bot edits should be in the feed
+ * The IRC-specific options are:
+ * * 'add_interwiki_prefix' -- whether the titles should be prefixed with
+ * $wgLocalInterwiki.
+ * The JSON-specific options are:
+ * * 'channel' -- if set, the 'channel' parameter is also set in JSON values.
+ *
+ * To ensure backwards-compatability, whenever $wgRC2UDPAddress is set, a
+ * 'default' feed will be created reusing the deprecated $wgRC2UDP* variables.
+ *
+ * @example $wgRCFeeds['example'] = array(
+ * 'formatter' => 'JSONRCFeedFormatter',
+ * 'uri' => "udp://localhost:1336",
+ * 'add_interwiki_prefix' => false,
+ * 'omit_bots' => true,
+ * );
+ * @example $wgRCFeeds['exampleirc'] = array(
+ * 'formatter' => 'IRCColourfulRCFeedFormatter',
+ * 'uri' => "udp://localhost:1338",
+ * 'add_interwiki_prefix' => false,
+ * 'omit_bots' => true,
+ * );
+ * @since 1.22
+ */
+$wgRCFeeds = array();
+
+/**
+ * Used by RecentChange::getEngine to find the correct engine to use for a given URI scheme.
+ * Keys are scheme names, values are names of engine classes.
+ */
+$wgRCEngines = array(
+ 'redis' => 'RedisPubSubFeedEngine',
+ 'udp' => 'UDPRCFeedEngine',
+);
+
+/**
* Enable user search in Special:Newpages
* This is really a temporary hack around an index install bug on some Wikipedias.
* Kill it once fixed.
*/
$wgEnableNewpagesUserFilter = true;
-/** Use RC Patrolling to check for vandalism */
+/**
+ * Use RC Patrolling to check for vandalism
+ */
$wgUseRCPatrol = true;
-/** Use new page patrolling to check new pages on Special:Newpages */
+/**
+ * Use new page patrolling to check new pages on Special:Newpages
+ */
$wgUseNPPatrol = true;
-/** Provide syndication feeds (RSS, Atom) for, e.g., Recentchanges, Newpages */
+/**
+ * Log autopatrol actions to the log table
+ */
+$wgLogAutopatrol = true;
+
+/**
+ * Provide syndication feeds (RSS, Atom) for, e.g., Recentchanges, Newpages
+ */
$wgFeed = true;
-/** Set maximum number of results to return in syndication feeds (RSS, Atom) for
- * eg Recentchanges, Newpages. */
+/**
+ * Set maximum number of results to return in syndication feeds (RSS, Atom) for
+ * eg Recentchanges, Newpages.
+ */
$wgFeedLimit = 50;
-/** _Minimum_ timeout for cached Recentchanges feed, in seconds.
+/**
+ * _Minimum_ timeout for cached Recentchanges feed, in seconds.
* A cached version will continue to be served out even if changes
* are made, until this many seconds runs out since the last render.
*
@@ -5196,11 +5589,14 @@ $wgFeedLimit = 50;
*/
$wgFeedCacheTimeout = 60;
-/** When generating Recentchanges RSS/Atom feed, diffs will not be generated for
- * pages larger than this size. */
+/**
+ * When generating Recentchanges RSS/Atom feed, diffs will not be generated for
+ * pages larger than this size.
+ */
$wgFeedDiffCutoff = 32768;
-/** Override the site's default RSS/ATOM feed for recentchanges that appears on
+/**
+ * Override the site's default RSS/ATOM feed for recentchanges that appears on
* every page. Some sites might have a different feed they'd like to promote
* instead of the RC feed (maybe like a "Recent New Articles" or "Breaking news" one).
* Should be a format as key (either 'rss' or 'atom') and an URL to the feed
@@ -5229,11 +5625,19 @@ $wgFeedClasses = array(
*/
$wgAdvertisedFeedTypes = array( 'atom' );
-/** Show watching users in recent changes, watchlist and page history views */
+/**
+ * Show watching users in recent changes, watchlist and page history views
+ */
$wgRCShowWatchingUsers = false; # UPO
-/** Show watching users in Page views */
+
+/**
+ * Show watching users in Page views
+ */
$wgPageShowWatchingUsers = false;
-/** Show the amount of changed characters in recent changes */
+
+/**
+ * Show the amount of changed characters in recent changes
+ */
$wgRCShowChangedSize = true;
/**
@@ -5245,7 +5649,8 @@ $wgRCChangedSizeThreshold = 500;
/**
* Show "Updated (since my last visit)" marker in RC view, watchlist and history
- * view for watched pages with new changes */
+ * view for watched pages with new changes
+ */
$wgShowUpdatedMarker = true;
/**
@@ -5274,6 +5679,33 @@ $wgUseTagFilter = true;
*/
$wgUnwatchedPageThreshold = false;
+/**
+ * Flags (letter symbols) shown in recent changes and watchlist to indicate
+ * certain types of edits.
+ *
+ * To register a new one:
+ * @code
+ * $wgRecentChangesFlags['flag'] => array(
+ * 'letter' => 'letter-msg',
+ * 'title' => 'tooltip-msg'
+ * );
+ * @endcode
+ *
+ * Optional 'class' allows to set a css class different than the flag name.
+ *
+ * @since 1.22
+ */
+$wgRecentChangesFlags = array(
+ 'newpage' => array( 'letter' => 'newpageletter',
+ 'title' => 'recentchanges-label-newpage' ),
+ 'minor' => array( 'letter' => 'minoreditletter',
+ 'title' => 'recentchanges-label-minor', 'class' => 'minoredit' ),
+ 'bot' => array( 'letter' => 'boteditletter',
+ 'title' => 'recentchanges-label-bot', 'class' => 'botedit' ),
+ 'unpatrolled' => array( 'letter' => 'unpatrolledletter',
+ 'title' => 'recentchanges-label-unpatrolled' ),
+);
+
/** @} */ # end RC/watchlist }
/************************************************************************//**
@@ -5291,15 +5723,17 @@ $wgUnwatchedPageThreshold = false;
$wgRightsPage = null;
/**
- * Set this to specify an external URL containing details about the content license used on your wiki.
+ * Set this to specify an external URL containing details about the content license used on your
+ * wiki.
* If $wgRightsPage is set then this setting is ignored.
*/
$wgRightsUrl = null;
/**
- * If either $wgRightsUrl or $wgRightsPage is specified then this variable gives the text for the link.
- * If using $wgRightsUrl then this value must be specified. If using $wgRightsPage then the name of the
- * page will also be used as the link if this variable is not set.
+ * If either $wgRightsUrl or $wgRightsPage is specified then this variable gives the text for the
+ * link.
+ * If using $wgRightsUrl then this value must be specified. If using $wgRightsPage then the name
+ * of the page will also be used as the link if this variable is not set.
*/
$wgRightsText = null;
@@ -5319,7 +5753,9 @@ $wgLicenseTerms = false;
*/
$wgCopyrightIcon = null;
-/** Set this to true if you want detailed copyright information forms on Upload. */
+/**
+ * Set this to true if you want detailed copyright information forms on Upload.
+ */
$wgUseCopyrightUpload = false;
/**
@@ -5331,8 +5767,10 @@ $wgUseCopyrightUpload = false;
*/
$wgMaxCredits = 0;
-/** If there are more than $wgMaxCredits authors, show $wgMaxCredits of them.
- * Otherwise, link to a separate credits page. */
+/**
+ * If there are more than $wgMaxCredits authors, show $wgMaxCredits of them.
+ * Otherwise, link to a separate credits page.
+ */
$wgShowCreditsIfMax = true;
/** @} */ # end of copyright and credits settings }
@@ -5435,6 +5873,13 @@ $wgExtensionFunctions = array();
$wgExtensionMessagesFiles = array();
/**
+ * Array of files with list(s) of extension entry points to be used in
+ * maintenance/mergeMessageFileList.php
+ * @since 1.22
+ */
+$wgExtensionEntryPointListFiles = array();
+
+/**
* Parser output hooks.
* This is an associative array where the key is an extension-defined tag
* (typically the extension name), and the value is a PHP callback.
@@ -5451,6 +5896,11 @@ $wgExtensionMessagesFiles = array();
$wgParserOutputHooks = array();
/**
+ * Whether to include the NewPP limit report as a HTML comment
+ */
+$wgEnableParserLimitReporting = true;
+
+/**
* List of valid skin names.
* The key should be the name in all lower case, the value should be a properly
* cased name for the skin. This value will be prefixed with "Skin" to create the
@@ -5521,17 +5971,24 @@ $wgAuth = null;
* @endcode
* - A function with some data:
* @code
- * $wgHooks['event_name'][] = array($function, $data);
+ * $wgHooks['event_name'][] = array( $function, $data );
* @endcode
* - A an object method:
* @code
- * $wgHooks['event_name'][] = array($object, 'method');
+ * $wgHooks['event_name'][] = array( $object, 'method' );
+ * @endcode
+ * - A closure:
+ * @code
+ * $wgHooks['event_name'][] = function ( $hookParam ) {
+ * // Handler code goes here.
+ * };
* @endcode
*
* @warning You should always append to an event array or you will end up
* deleting a previous registered hook.
*
- * @todo Does it support PHP closures?
+ * @warning Hook handlers should be registered at file scope. Registering
+ * handlers after file scope can lead to unexpected results due to caching.
*/
$wgHooks = array();
@@ -5543,7 +6000,6 @@ $wgJobClasses = array(
'refreshLinks' => 'RefreshLinksJob',
'refreshLinks2' => 'RefreshLinksJob2',
'htmlCacheUpdate' => 'HTMLCacheUpdateJob',
- 'html_cache_update' => 'HTMLCacheUpdateJob', // backwards-compatible
'sendMail' => 'EmaillingJob',
'enotifNotify' => 'EnotifNotifyJob',
'fixDoubleRedirect' => 'DoubleRedirectJob',
@@ -5843,12 +6299,13 @@ $wgDisableQueryPageUpdate = false;
* List of special pages, followed by what subtitle they should go under
* at Special:SpecialPages
*
- * @deprecated 1.21 Override SpecialPage::getGroupName instead
+ * @deprecated since 1.21 Override SpecialPage::getGroupName instead
*/
$wgSpecialPageGroups = array();
-/** Whether or not to sort special pages in Special:Specialpages */
-
+/**
+ * Whether or not to sort special pages in Special:Specialpages
+ */
$wgSortSpecialPages = true;
/**
@@ -6066,6 +6523,16 @@ $wgAPIRequestLog = false;
$wgAPICacheHelpTimeout = 60 * 60;
/**
+ * The ApiQueryQueryPages module should skip pages that are redundant to true
+ * API queries.
+ */
+$wgAPIUselessQueryPages = array(
+ 'MIMEsearch', // aiprop=mime
+ 'LinkSearch', // list=exturlusage
+ 'FileDuplicateSearch', // prop=duplicatefiles
+);
+
+/**
* Enable AJAX framework
*/
$wgUseAjax = true;
@@ -6119,7 +6586,6 @@ $wgCrossSiteAJAXdomains = array();
* even if they match one of the domains allowed by $wgCrossSiteAJAXdomains
* Uses the same syntax as $wgCrossSiteAJAXdomains
*/
-
$wgCrossSiteAJAXdomainExceptions = array();
/** @} */ # End AJAX and API }
@@ -6132,7 +6598,7 @@ $wgCrossSiteAJAXdomainExceptions = array();
/**
* Maximum amount of virtual memory available to shell processes under linux, in KB.
*/
-$wgMaxShellMemory = 102400;
+$wgMaxShellMemory = 307200;
/**
* Maximum file size created by shell processes under linux, in KB
@@ -6210,6 +6676,12 @@ $wgAsyncHTTPTimeout = 25;
*/
$wgHTTPProxy = false;
+/**
+ * Timeout for connections done internally (in seconds)
+ * Only works for curl
+ */
+$wgHTTPConnectTimeout = 5e0;
+
/** @} */ # End HTTP client }
/************************************************************************//**
@@ -6248,61 +6720,13 @@ $wgMaxBacklinksInvalidate = false;
/** @} */ # End job queue }
/************************************************************************//**
- * @name HipHop compilation
+ * @name Miscellaneous
* @{
*/
/**
- * The build directory for HipHop compilation.
- * Defaults to '$IP/maintenance/hiphop/build'.
- */
-$wgHipHopBuildDirectory = false;
-
-/**
- * The HipHop build type. Can be either "Debug" or "Release".
+ * Name of the external diff engine to use
*/
-$wgHipHopBuildType = 'Debug';
-
-/**
- * Number of parallel processes to use during HipHop compilation, or "detect"
- * to guess from system properties.
- */
-$wgHipHopCompilerProcs = 'detect';
-
-/**
- * Filesystem extensions directory. Defaults to $IP/../extensions.
- *
- * To compile extensions with HipHop, set $wgExtensionsDirectory correctly,
- * and use code like:
- * @code
- * require( MWInit::extensionSetupPath( 'Extension/Extension.php' ) );
- * @endcode
- *
- * to include the extension setup file from LocalSettings.php. It is not
- * necessary to set this variable unless you use MWInit::extensionSetupPath().
- */
-$wgExtensionsDirectory = false;
-
-/**
- * A list of files that should be compiled into a HipHop build, in addition to
- * those listed in $wgAutoloadClasses. Add to this array in an extension setup
- * file in order to add files to the build.
- *
- * The files listed here must either be either absolute paths under $IP or
- * under $wgExtensionsDirectory, or paths relative to the virtual source root
- * "$IP/..", i.e. starting with "phase3" for core files, and "extensions" for
- * extension files.
- */
-$wgCompiledFiles = array();
-
-/** @} */ # End of HipHop compilation }
-
-/************************************************************************//**
- * @name Miscellaneous
- * @{
- */
-
-/** Name of the external diff engine to use */
$wgExternalDiffEngine = false;
/**
@@ -6372,25 +6796,18 @@ $wgPoolCounterConf = null;
$wgUploadMaintenance = false;
/**
- * Allows running of selenium tests via maintenance/tests/RunSeleniumTests.php
- */
-$wgEnableSelenium = false;
-$wgSeleniumTestConfigs = array();
-$wgSeleniumConfigFile = null;
-$wgDBtestuser = ''; //db user that has permission to create and drop the test databases only
-$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).
+ * 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():
+ * 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
@@ -6402,14 +6819,14 @@ $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.
+ * 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.
+ * Overall, with $wgContentHandlerUseDB = false, no database updates are needed, but content
+ * handling is less robust and less flexible.
*
* @since 1.21
*/
@@ -6420,7 +6837,7 @@ $wgContentHandlerUseDB = true;
* 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,
+ * @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
@@ -6448,6 +6865,12 @@ $wgSiteTypes = array(
);
/**
+ * Formerly a list of files for HipHop compilation
+ * @deprecated since 1.22
+ */
+$wgCompiledFiles = array();
+
+/**
* 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 89c4df68..c385f138 100644
--- a/includes/DeferredUpdates.php
+++ b/includes/DeferredUpdates.php
@@ -64,6 +64,16 @@ class DeferredUpdates {
}
/**
+ * Add a callable update. In a lot of cases, we just need a callback/closure,
+ * defining a new DeferrableUpdate object is not necessary
+ * @see MWCallableUpdate::__construct()
+ * @param callable $callable
+ */
+ public static function addCallableUpdate( $callable ) {
+ self::addUpdate( new MWCallableUpdate( $callable ) );
+ }
+
+ /**
* Do any deferred updates and clear the list
*
* @param string $commit set to 'commit' to commit after every update to
@@ -99,7 +109,7 @@ class DeferredUpdates {
// be reported to the user since the output is already sent.
// Instead we just log them.
if ( !$e instanceof ErrorPageError ) {
- wfDebugLog( 'exception', $e->getLogMessage() );
+ MWExceptionHandler::logException( $e );
}
}
}
diff --git a/includes/Defines.php b/includes/Defines.php
index c4a86335..86c5520b 100644
--- a/includes/Defines.php
+++ b/includes/Defines.php
@@ -197,14 +197,14 @@ define( 'EDIT_AUTOSUMMARY', 64 );
define( 'LIST_COMMA', 0 );
define( 'LIST_AND', 1 );
define( 'LIST_SET', 2 );
-define( 'LIST_NAMES', 3);
-define( 'LIST_OR', 4);
+define( 'LIST_NAMES', 3 );
+define( 'LIST_OR', 4 );
/**@}*/
/**
* Unicode and normalisation related
*/
-require_once __DIR__.'/normal/UtfNormalDefines.php';
+require_once __DIR__ . '/normal/UtfNormalDefines.php';
/**@{
* Hook support constants
@@ -237,11 +237,6 @@ define( 'SFH_OBJECT_ARGS', 2 );
/**@}*/
/**
- * Flags for Parser::setLinkHook
- */
-define( 'SLH_PATTERN', 1 );
-
-/**
* Flags for Parser::replaceLinkHolders
*/
define( 'RLH_FOR_UPDATE', 1 );
diff --git a/includes/EditPage.php b/includes/EditPage.php
index 8b2dbb5f..530e2674 100644
--- a/includes/EditPage.php
+++ b/includes/EditPage.php
@@ -100,7 +100,7 @@ class EditPage {
/**
* Status: user tried to create this page, but is not allowed to do that
- * ( Title->usercan('create') == false )
+ * ( Title->userCan('create') == false )
*/
const AS_NO_CREATE_PERMISSION = 223;
@@ -487,7 +487,7 @@ class EditPage {
// The edit page was reached via a red link.
// Redirect to the article page and let them click the edit tab if
// they really want a permission error.
- $wgOut->redirect( $this->mTitle->getFullUrl() );
+ $wgOut->redirect( $this->mTitle->getFullURL() );
return;
}
@@ -521,6 +521,8 @@ class EditPage {
$wgOut->addHTML( Html::rawElement( 'div', array( 'class' => 'templatesUsed' ),
Linker::formatTemplates( $this->getTemplates() ) ) );
+ $wgOut->addModules( 'mediawiki.action.edit.collapsibleFooter' );
+
if ( $this->mTitle->exists() ) {
$wgOut->returnToMain( null, $this->mTitle );
}
@@ -540,7 +542,7 @@ class EditPage {
// The edit page was reached via a red link.
// Redirect to the article page and let them click the edit tab if
// they really want a permission error.
- $wgOut->redirect( $this->mTitle->getFullUrl() );
+ $wgOut->redirect( $this->mTitle->getFullURL() );
} else {
$wgOut->readOnlyPage( $source, $protected, $reasons, $action );
}
@@ -621,6 +623,7 @@ class EditPage {
$this->section = $request->getVal( 'wpSection', $request->getVal( 'section' ) );
if ( $this->section !== null && $this->section !== '' && !$this->isSectionEditSupported() ) {
+ wfProfileOut( __METHOD__ );
throw new ErrorPageError( 'sectioneditnotsupported-title', 'sectioneditnotsupported-text' );
}
@@ -663,6 +666,11 @@ class EditPage {
$this->edittime = $request->getVal( 'wpEdittime' );
$this->starttime = $request->getVal( 'wpStarttime' );
+ $undidRev = $request->getInt( 'wpUndidRevision' );
+ if ( $undidRev ) {
+ $this->undidRev = $undidRev;
+ }
+
$this->scrolltop = $request->getIntOrNull( 'wpScrolltop' );
if ( $this->textbox1 === '' && $request->getVal( 'wpTextbox1' ) === null ) {
@@ -832,7 +840,6 @@ class EditPage {
if ( $this->textbox1 === false ) {
return false;
}
- wfProxyCheck();
return true;
}
@@ -897,7 +904,9 @@ class EditPage {
$orig = $this->getOriginalContent();
$content = $orig ? $orig->getSection( $this->section ) : null;
- if ( !$content ) $content = $def_content;
+ if ( !$content ) {
+ $content = $def_content;
+ }
} else {
$undoafter = $wgRequest->getInt( 'undoafter' );
$undo = $wgRequest->getInt( 'undo' );
@@ -932,7 +941,19 @@ class EditPage {
# If we just undid one rev, use an autosummary
$firstrev = $oldrev->getNext();
if ( $firstrev && $firstrev->getId() == $undo ) {
- $undoSummary = wfMessage( 'undo-summary', $undo, $undorev->getUserText() )->inContentLanguage()->text();
+ $userText = $undorev->getUserText();
+ if ( $userText === '' ) {
+ $undoSummary = wfMessage(
+ 'undo-summary-username-hidden',
+ $undo
+ )->inContentLanguage()->text();
+ } else {
+ $undoSummary = wfMessage(
+ 'undo-summary',
+ $undo,
+ $userText
+ )->inContentLanguage()->text();
+ }
if ( $this->summary === '' ) {
$this->summary = $undoSummary;
} else {
@@ -950,6 +971,7 @@ class EditPage {
$undoMsg = 'norev';
}
+ // Messages: undo-success, undo-failure, undo-norev
$class = ( $undoMsg == 'success' ? '' : 'error ' ) . "mw-undo-{$undoMsg}";
$this->editFormPageTop .= $wgOut->parse( "<div class=\"{$class}\">" .
wfMessage( 'undo-' . $undoMsg )->plain() . '</div>', true, /* interface */true );
@@ -985,7 +1007,9 @@ class EditPage {
}
$revision = $this->mArticle->getRevisionFetched();
if ( $revision === null ) {
- if ( !$this->contentModel ) $this->contentModel = $this->getTitle()->getContentModel();
+ if ( !$this->contentModel ) {
+ $this->contentModel = $this->getTitle()->getContentModel();
+ }
$handler = ContentHandler::getForModelID( $this->contentModel );
return $handler->makeEmptyContent();
@@ -1007,7 +1031,9 @@ class EditPage {
$content = $rev ? $rev->getContent( Revision::RAW ) : null;
if ( $content === false || $content === null ) {
- if ( !$this->contentModel ) $this->contentModel = $this->getTitle()->getContentModel();
+ if ( !$this->contentModel ) {
+ $this->contentModel = $this->getTitle()->getContentModel();
+ }
$handler = ContentHandler::getForModelID( $this->contentModel );
return $handler->makeEmptyContent();
@@ -1157,20 +1183,20 @@ class EditPage {
* 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 );
+ $response = RequestContext::getMain()->getRequest()->response();
+ $response->setcookie( $postEditKey, '1', time() + self::POST_EDIT_COOKIE_DURATION, array(
+ 'path' => '/',
+ 'httpOnly' => false,
+ ) );
}
/**
@@ -1213,7 +1239,7 @@ class EditPage {
case self::AS_SUCCESS_NEW_ARTICLE:
$query = $resultDetails['redirect'] ? 'redirect=no' : '';
- $anchor = isset ( $resultDetails['sectionanchor'] ) ? $resultDetails['sectionanchor'] : '';
+ $anchor = isset( $resultDetails['sectionanchor'] ) ? $resultDetails['sectionanchor'] : '';
$wgOut->redirect( $this->mTitle->getFullURL( $query ) . $anchor );
return false;
@@ -1355,6 +1381,24 @@ class EditPage {
return $status;
}
+ $spam = $wgRequest->getText( 'wpAntispam' );
+ if ( $spam !== '' ) {
+ wfDebugLog(
+ 'SimpleAntiSpam',
+ $wgUser->getName() .
+ ' editing "' .
+ $this->mTitle->getPrefixedText() .
+ '" submitted bogus field "' .
+ $spam .
+ '"'
+ );
+ $status->fatal( 'spamprotectionmatch', false );
+ $status->value = self::AS_SPAM_ERROR;
+ wfProfileOut( __METHOD__ . '-checks' );
+ wfProfileOut( __METHOD__ );
+ return $status;
+ }
+
try {
# Construct Content object
$textbox_content = $this->toEditContent( $this->textbox1 );
@@ -1381,6 +1425,18 @@ class EditPage {
# Check for spam
$match = self::matchSummarySpamRegex( $this->summary );
+ if ( $match === false && $this->section == 'new' ) {
+ # $wgSpamRegex is enforced on this new heading/summary because, unlike
+ # regular summaries, it is added to the actual wikitext.
+ if ( $this->sectiontitle !== '' ) {
+ # This branch is taken when the API is used with the 'sectiontitle' parameter.
+ $match = self::matchSpamRegex( $this->sectiontitle );
+ } else {
+ # This branch is taken when the "Add Topic" user interface is used, or the API
+ # is used with the 'summary' parameter.
+ $match = self::matchSpamRegex( $this->summary );
+ }
+ }
if ( $match === false ) {
$match = self::matchSpamRegex( $this->textbox1 );
}
@@ -1454,7 +1510,7 @@ class EditPage {
wfProfileOut( __METHOD__ );
return $status;
}
- if ( $wgUser->pingLimiter() ) {
+ if ( $wgUser->pingLimiter() || $wgUser->pingLimiter( 'linkpurge', 0 ) ) {
$status->fatal( 'actionthrottledtext' );
$status->value = self::AS_RATE_LIMITED;
wfProfileOut( __METHOD__ . '-checks' );
@@ -1488,8 +1544,17 @@ class EditPage {
return $status;
}
- # Don't save a new article if it's blank.
- if ( $this->textbox1 == '' ) {
+ // Don't save a new page if it's blank or if it's a MediaWiki:
+ // message with content equivalent to default (allow empty pages
+ // in this case to disable messages, see bug 50124)
+ $defaultMessageText = $this->mTitle->getDefaultMessageText();
+ if ( $this->mTitle->getNamespace() === NS_MEDIAWIKI && $defaultMessageText !== false ) {
+ $defaultText = $defaultMessageText;
+ } else {
+ $defaultText = '';
+ }
+
+ if ( $this->textbox1 === $defaultText ) {
$status->setResult( false, self::AS_BLANK_ARTICLE );
wfProfileOut( __METHOD__ );
return $status;
@@ -1566,8 +1631,7 @@ class EditPage {
}
}
- // If sectiontitle is set, use it, otherwise use the summary as the section title (for
- // backwards compatibility with old forms/bots).
+ // If sectiontitle is set, use it, otherwise use the summary as the section title.
if ( $this->sectiontitle !== '' ) {
$sectionTitle = $this->sectiontitle;
} else {
@@ -1724,6 +1788,10 @@ class EditPage {
}
$result['nullEdit'] = $doEditStatus->hasMessage( 'edit-no-change' );
+ if ( $result['nullEdit'] ) {
+ // We don't know if it was a null edit until now, so increment here
+ $wgUser->pingLimiter( 'linkpurge' );
+ }
$result['redirect'] = $content->isRedirect();
$this->updateWatchlist();
wfProfileOut( __METHOD__ );
@@ -1736,7 +1804,9 @@ class EditPage {
protected function updateWatchlist() {
global $wgUser;
- if ( $wgUser->isLoggedIn() && $this->watchthis != $wgUser->isWatched( $this->mTitle ) ) {
+ if ( $wgUser->isLoggedIn()
+ && $this->watchthis != $wgUser->isWatched( $this->mTitle, WatchedItem::IGNORE_USER_RIGHTS )
+ ) {
$fname = __METHOD__;
$title = $this->mTitle;
$watch = $this->watchthis;
@@ -1745,11 +1815,7 @@ class EditPage {
$dbw = wfGetDB( DB_MASTER );
$dbw->onTransactionIdle( function() use ( $dbw, $title, $watch, $wgUser, $fname ) {
$dbw->begin( $fname );
- if ( $watch ) {
- WatchAction::doWatch( $title, $wgUser );
- } else {
- WatchAction::doUnwatch( $title, $wgUser );
- }
+ WatchAction::doWatchOrUnwatch( $watch, $title, $wgUser );
$dbw->commit( $fname );
} );
}
@@ -1854,11 +1920,11 @@ class EditPage {
}
/**
- * Check given input text against $wgSpamRegex, and return the text of the first match.
+ * Check given input text against $wgSummarySpamRegex, and return the text of the first match.
*
* @param $text string
*
- * @return string|bool matching string or false
+ * @return string|bool matching string or false
*/
public static function matchSummarySpamRegex( $text ) {
global $wgSummarySpamRegex;
@@ -1885,10 +1951,16 @@ class EditPage {
global $wgOut, $wgUser;
$wgOut->addModules( 'mediawiki.action.edit' );
+ $wgOut->addModuleStyles( 'mediawiki.action.edit.styles' );
if ( $wgUser->getOption( 'uselivepreview', false ) ) {
$wgOut->addModules( 'mediawiki.action.edit.preview' );
}
+
+ if ( $wgUser->getOption( 'useeditwarning', false ) ) {
+ $wgOut->addModules( 'mediawiki.action.edit.editWarning' );
+ }
+
// Bug #19334: textarea jumps when editing articles in IE8
$wgOut->addStyle( 'common/IE80Fixes.css', 'screen', 'IE 8' );
@@ -1928,15 +2000,15 @@ class EditPage {
if ( $namespace == NS_MEDIAWIKI ) {
# Show a warning if editing an interface message
$wgOut->wrapWikiMsg( "<div class='mw-editinginterface'>\n$1\n</div>", 'editinginterface' );
- } else if( $namespace == NS_FILE ) {
+ } elseif ( $namespace == NS_FILE ) {
# Show a hint to shared repo
$file = wfFindFile( $this->mTitle );
- if( $file && !$file->isLocal() ) {
+ if ( $file && !$file->isLocal() ) {
$descUrl = $file->getDescriptionUrl();
# there must be a description url to show a hint to shared repo
- if( $descUrl ) {
- if( !$this->mTitle->exists() ) {
- $wgOut->wrapWikiMsg( "<div class=\"mw-sharedupload-desc-create\">\n$1\n</div>", array (
+ if ( $descUrl ) {
+ if ( !$this->mTitle->exists() ) {
+ $wgOut->wrapWikiMsg( "<div class=\"mw-sharedupload-desc-create\">\n$1\n</div>", array(
'sharedupload-desc-create', $file->getRepo()->getDisplayName(), $descUrl
) );
} else {
@@ -2126,7 +2198,7 @@ class EditPage {
}
}
- //@todo: add EditForm plugin interface and use it here!
+ // @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() ),
@@ -2136,6 +2208,14 @@ class EditPage {
call_user_func_array( $formCallback, array( &$wgOut ) );
}
+ // Add an empty field to trip up spambots
+ $wgOut->addHTML(
+ Xml::openElement( 'div', array( 'id' => 'antispam-container', 'style' => 'display: none;' ) )
+ . Html::rawElement( 'label', array( 'for' => 'wpAntiSpam' ), wfMessage( 'simpleantispam-label' )->parse() )
+ . Xml::element( 'input', array( 'type' => 'text', 'name' => 'wpAntispam', 'id' => 'wpAntispam', 'value' => '' ) )
+ . Xml::closeElement( 'div' )
+ );
+
wfRunHooks( 'EditPage::showEditForm:fields', array( &$this, &$wgOut ) );
// Put these up at the top to ensure they aren't lost on early form submission
@@ -2161,7 +2241,7 @@ class EditPage {
}
# When the summary is hidden, also hide them on preview/show changes
- if( $this->nosummary ) {
+ if ( $this->nosummary ) {
$wgOut->addHTML( Html::hidden( 'nosummary', true ) );
}
@@ -2239,6 +2319,11 @@ class EditPage {
$wgOut->addHTML( Html::rawElement( 'div', array( 'class' => 'hiddencats' ),
Linker::formatHiddenCategories( $this->mArticle->getHiddenCategories() ) ) );
+ $wgOut->addHTML( Html::rawElement( 'div', array( 'class' => 'limitreport' ),
+ self::getPreviewLimitReport( $this->mParserOutput ) ) );
+
+ $wgOut->addModules( 'mediawiki.action.edit.collapsibleFooter' );
+
if ( $this->isConflict ) {
try {
$this->showConflict();
@@ -2282,7 +2367,7 @@ class EditPage {
}
// Add edit notices
- $wgOut->addHTML( implode( "\n", $this->mTitle->getEditNotices() ) );
+ $wgOut->addHTML( implode( "\n", $this->mTitle->getEditNotices( $this->oldid ) ) );
if ( $this->isConflict ) {
$wgOut->wrapWikiMsg( "<div class='mw-explainconflict'>\n$1\n</div>", 'explainconflict' );
@@ -2509,7 +2594,7 @@ class EditPage {
global $wgParser;
if ( $isSubjectPreview ) {
- $summary = wfMessage( 'newsectionsummary', $wgParser->stripSectionName( $summary ) )
+ $summary = wfMessage( 'newsectionsummary' )->rawParams( $wgParser->stripSectionName( $summary ) )
->inContentLanguage()->text();
}
@@ -2626,7 +2711,7 @@ HTML
$attribs = $customAttribs + array(
'accesskey' => ',',
- 'id' => $name,
+ 'id' => $name,
'cols' => $wgUser->getIntOption( 'cols' ),
'rows' => $wgUser->getIntOption( 'rows' ),
'style' => '' // avoid php notices when appending preferences (appending allows customAttribs['style'] to still work
@@ -2702,9 +2787,9 @@ HTML
$oldtitlemsg = 'currentrev';
# if message does not exist, show diff against the preloaded default
- if( $this->mTitle->getNamespace() == NS_MEDIAWIKI && !$this->mTitle->exists() ) {
+ if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI && !$this->mTitle->exists() ) {
$oldtext = $this->mTitle->getDefaultMessageText();
- if( $oldtext !== false ) {
+ if ( $oldtext !== false ) {
$oldtitlemsg = 'defaultmessagetext';
$oldContent = $this->toEditContent( $oldtext );
} else {
@@ -2799,7 +2884,15 @@ HTML
return self::getCopyrightWarning( $this->mTitle );
}
- public static function getCopyrightWarning( $title ) {
+ /**
+ * Get the copyright warning, by default returns wikitext
+ *
+ * @param Title $title
+ * @param string $format output format, valid values are any function of
+ * a Message object
+ * @return string
+ */
+ public static function getCopyrightWarning( $title, $format = 'plain' ) {
global $wgRightsText;
if ( $wgRightsText ) {
$copywarnMsg = array( 'copyrightwarning',
@@ -2813,7 +2906,60 @@ HTML
wfRunHooks( 'EditPageCopyrightWarning', array( $title, &$copywarnMsg ) );
return "<div id=\"editpage-copywarn\">\n" .
- call_user_func_array( 'wfMessage', $copywarnMsg )->plain() . "\n</div>";
+ call_user_func_array( 'wfMessage', $copywarnMsg )->$format() . "\n</div>";
+ }
+
+ /**
+ * Get the Limit report for page previews
+ *
+ * @since 1.22
+ * @param ParserOutput $output ParserOutput object from the parse
+ * @return string HTML
+ */
+ public static function getPreviewLimitReport( $output ) {
+ if ( !$output || !$output->getLimitReportData() ) {
+ return '';
+ }
+
+ wfProfileIn( __METHOD__ );
+
+ $limitReport = Html::rawElement( 'div', array( 'class' => 'mw-limitReportExplanation' ),
+ wfMessage( 'limitreport-title' )->parseAsBlock()
+ );
+
+ // Show/hide animation doesn't work correctly on a table, so wrap it in a div.
+ $limitReport .= Html::openElement( 'div', array( 'class' => 'preview-limit-report-wrapper' ) );
+
+ $limitReport .= Html::openElement( 'table', array(
+ 'class' => 'preview-limit-report wikitable'
+ ) ) .
+ Html::openElement( 'tbody' );
+
+ foreach ( $output->getLimitReportData() as $key => $value ) {
+ if ( wfRunHooks( 'ParserLimitReportFormat',
+ array( $key, $value, &$limitReport, true, true )
+ ) ) {
+ $keyMsg = wfMessage( $key );
+ $valueMsg = wfMessage( array( "$key-value-html", "$key-value" ) );
+ if ( !$valueMsg->exists() ) {
+ $valueMsg = new RawMessage( '$1' );
+ }
+ if ( !$keyMsg->isDisabled() && !$valueMsg->isDisabled() ) {
+ $limitReport .= Html::openElement( 'tr' ) .
+ Html::rawElement( 'th', null, $keyMsg->parse() ) .
+ Html::rawElement( 'td', null, $valueMsg->params( $value )->parse() ) .
+ Html::closeElement( 'tr' );
+ }
+ }
+ }
+
+ $limitReport .= Html::closeElement( 'tbody' ) .
+ Html::closeElement( 'table' ) .
+ Html::closeElement( 'div' );
+
+ wfProfileOut( __METHOD__ );
+
+ return $limitReport;
}
protected function showStandardInputs( &$tabindex = 2 ) {
@@ -2838,7 +2984,9 @@ HTML
$cancel = $this->getCancelLink();
if ( $cancel !== '' ) {
- $cancel .= wfMessage( 'pipe-separator' )->text();
+ $cancel .= Html::element( 'span',
+ array( 'class' => 'mw-editButtons-pipe-separator' ),
+ wfMessage( 'pipe-separator' )->text() );
}
$edithelpurl = Skin::makeInternalOrExternalUrl( wfMessage( 'edithelppage' )->inContentLanguage()->text() );
$edithelp = '<a target="helpwindow" href="' . $edithelpurl . '">' .
@@ -3028,9 +3176,9 @@ HTML
# don't parse non-wikitext pages, show message about preview
if ( $this->mTitle->isCssJsSubpage() || $this->mTitle->isCssOrJsPage() ) {
- if( $this->mTitle->isCssJsSubpage() ) {
+ if ( $this->mTitle->isCssJsSubpage() ) {
$level = 'user';
- } elseif( $this->mTitle->isCssOrJsPage() ) {
+ } elseif ( $this->mTitle->isCssOrJsPage() ) {
$level = 'site';
} else {
$level = false;
@@ -3046,7 +3194,7 @@ HTML
# Used messages to make sure grep find them:
# Messages: usercsspreview, userjspreview, sitecsspreview, sitejspreview
- if( $level && $format ) {
+ if ( $level && $format ) {
$note = "<div id='mw-{$level}{$format}preview'>" . wfMessage( "{$level}{$format}preview" )->text() . "</div>";
}
}
@@ -3098,9 +3246,9 @@ HTML
'<h2 id="mw-previewheader">' . wfMessage( 'preview' )->escaped() . "</h2>" .
$wgOut->parse( $note, true, /* interface */true ) . $conflict . "</div>\n";
- $pageLang = $this->mTitle->getPageLanguage();
- $attribs = array( 'lang' => $pageLang->getCode(), 'dir' => $pageLang->getDir(),
- 'class' => 'mw-content-' . $pageLang->getDir() );
+ $pageViewLang = $this->mTitle->getPageViewLanguage();
+ $attribs = array( 'lang' => $pageViewLang->getHtmlCode(), 'dir' => $pageViewLang->getDir(),
+ 'class' => 'mw-content-' . $pageViewLang->getDir() );
$previewHTML = Html::rawElement( 'div', $attribs, $previewHTML );
wfProfileOut( __METHOD__ );
@@ -3314,9 +3462,9 @@ HTML
$minorLabel = wfMessage( 'minoredit' )->parse();
if ( $wgUser->isAllowed( 'minoredit' ) ) {
$attribs = array(
- 'tabindex' => ++$tabindex,
+ 'tabindex' => ++$tabindex,
'accesskey' => wfMessage( 'accesskey-minoredit' )->text(),
- 'id' => 'wpMinoredit',
+ 'id' => 'wpMinoredit',
);
$checkboxes['minor'] =
Xml::check( 'wpMinoredit', $checked['minor'], $attribs ) .
@@ -3330,9 +3478,9 @@ HTML
$checkboxes['watch'] = '';
if ( $wgUser->isLoggedIn() ) {
$attribs = array(
- 'tabindex' => ++$tabindex,
+ 'tabindex' => ++$tabindex,
'accesskey' => wfMessage( 'accesskey-watch' )->text(),
- 'id' => 'wpWatchthis',
+ 'id' => 'wpWatchthis',
);
$checkboxes['watch'] =
Xml::check( 'wpWatchthis', $checked['watch'], $attribs ) .
@@ -3356,37 +3504,37 @@ HTML
$buttons = array();
$temp = array(
- 'id' => 'wpSave',
- 'name' => 'wpSave',
- 'type' => 'submit',
- 'tabindex' => ++$tabindex,
- 'value' => wfMessage( 'savearticle' )->text(),
+ 'id' => 'wpSave',
+ 'name' => 'wpSave',
+ 'type' => 'submit',
+ 'tabindex' => ++$tabindex,
+ 'value' => wfMessage( 'savearticle' )->text(),
'accesskey' => wfMessage( 'accesskey-save' )->text(),
- 'title' => wfMessage( 'tooltip-save' )->text() . ' [' . wfMessage( 'accesskey-save' )->text() . ']',
+ 'title' => wfMessage( 'tooltip-save' )->text() . ' [' . wfMessage( 'accesskey-save' )->text() . ']',
);
$buttons['save'] = Xml::element( 'input', $temp, '' );
++$tabindex; // use the same for preview and live preview
$temp = array(
- 'id' => 'wpPreview',
- 'name' => 'wpPreview',
- 'type' => 'submit',
- 'tabindex' => $tabindex,
- 'value' => wfMessage( 'showpreview' )->text(),
+ 'id' => 'wpPreview',
+ 'name' => 'wpPreview',
+ 'type' => 'submit',
+ 'tabindex' => $tabindex,
+ 'value' => wfMessage( 'showpreview' )->text(),
'accesskey' => wfMessage( 'accesskey-preview' )->text(),
- 'title' => wfMessage( 'tooltip-preview' )->text() . ' [' . wfMessage( 'accesskey-preview' )->text() . ']',
+ 'title' => wfMessage( 'tooltip-preview' )->text() . ' [' . wfMessage( 'accesskey-preview' )->text() . ']',
);
$buttons['preview'] = Xml::element( 'input', $temp, '' );
$buttons['live'] = '';
$temp = array(
- 'id' => 'wpDiff',
- 'name' => 'wpDiff',
- 'type' => 'submit',
- 'tabindex' => ++$tabindex,
- 'value' => wfMessage( 'showdiff' )->text(),
+ 'id' => 'wpDiff',
+ 'name' => 'wpDiff',
+ 'type' => 'submit',
+ 'tabindex' => ++$tabindex,
+ 'value' => wfMessage( 'showdiff' )->text(),
'accesskey' => wfMessage( 'accesskey-diff' )->text(),
- 'title' => wfMessage( 'tooltip-diff' )->text() . ' [' . wfMessage( 'accesskey-diff' )->text() . ']',
+ 'title' => wfMessage( 'tooltip-diff' )->text() . ' [' . wfMessage( 'accesskey-diff' )->text() . ']',
);
$buttons['diff'] = Xml::element( 'input', $temp, '' );
@@ -3506,7 +3654,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' ) );
diff --git a/includes/Exception.php b/includes/Exception.php
index 0bd7a2a7..5bad88c2 100644
--- a/includes/Exception.php
+++ b/includes/Exception.php
@@ -30,7 +30,6 @@
* @ingroup Exception
*/
class MWException extends Exception {
- var $logId;
/**
* Should the exception use $wgOut to output the error?
@@ -45,6 +44,16 @@ class MWException extends Exception {
}
/**
+ * Whether to log this exception in the exception debug log.
+ *
+ * @since 1.23
+ * @return boolean
+ */
+ function isLoggable() {
+ return true;
+ }
+
+ /**
* Can the extension use the Message class/wfMessage to get i18n-ed messages?
*
* @return bool
@@ -75,11 +84,11 @@ class MWException extends Exception {
return null; // Just silently ignore
}
- if ( !array_key_exists( $name, $wgExceptionHooks ) || !is_array( $wgExceptionHooks[ $name ] ) ) {
+ if ( !array_key_exists( $name, $wgExceptionHooks ) || !is_array( $wgExceptionHooks[$name] ) ) {
return null;
}
- $hooks = $wgExceptionHooks[ $name ];
+ $hooks = $wgExceptionHooks[$name];
$callargs = array_merge( array( $this ), $args );
foreach ( $hooks as $hook ) {
@@ -126,13 +135,12 @@ class MWException extends Exception {
global $wgShowExceptionDetails;
if ( $wgShowExceptionDetails ) {
- return '<p>' . nl2br( htmlspecialchars( $this->getMessage() ) ) .
- '</p><p>Backtrace:</p><p>' . nl2br( htmlspecialchars( $this->getTraceAsString() ) ) .
+ return '<p>' . nl2br( htmlspecialchars( MWExceptionHandler::getLogMessage( $this ) ) ) .
+ '</p><p>Backtrace:</p><p>' . nl2br( htmlspecialchars( MWExceptionHandler::getRedactedTraceAsString( $this ) ) ) .
"</p>\n";
} else {
- return
- "<div class=\"errorbox\">" .
- '[' . $this->getLogId() . '] ' .
+ return "<div class=\"errorbox\">" .
+ '[' . MWExceptionHandler::getLogId( $this ) . '] ' .
gmdate( 'Y-m-d H:i:s' ) .
": Fatal exception of type " . get_class( $this ) . "</div>\n" .
"<!-- Set \$wgShowExceptionDetails = true; " .
@@ -152,8 +160,8 @@ class MWException extends Exception {
global $wgShowExceptionDetails;
if ( $wgShowExceptionDetails ) {
- return $this->getMessage() .
- "\nBacktrace:\n" . $this->getTraceAsString() . "\n";
+ return MWExceptionHandler::getLogMessage( $this ) .
+ "\nBacktrace:\n" . MWExceptionHandler::getRedactedTraceAsString( $this ) . "\n";
} else {
return "Set \$wgShowExceptionDetails = true; " .
"in LocalSettings.php to show detailed debugging information.\n";
@@ -170,43 +178,28 @@ class MWException extends Exception {
}
/**
- * Get a random ID for this error.
- * This allows to link the exception to its corresponding log entry when
- * $wgShowExceptionDetails is set to false.
+ * Get a the ID for this error.
*
+ * @since 1.20
+ * @deprecated since 1.22 Use MWExceptionHandler::getLogId instead.
* @return string
*/
function getLogId() {
- if ( $this->logId === null ) {
- $this->logId = wfRandomString( 8 );
- }
- return $this->logId;
+ wfDeprecated( __METHOD__, '1.22' );
+ return MWExceptionHandler::getLogId( $this );
}
/**
* Return the requested URL and point to file and line number from which the
* exception occurred
*
+ * @since 1.8
+ * @deprecated since 1.22 Use MWExceptionHandler::getLogMessage instead.
* @return string
*/
function getLogMessage() {
- global $wgRequest;
-
- $id = $this->getLogId();
- $file = $this->getFile();
- $line = $this->getLine();
- $message = $this->getMessage();
-
- if ( isset( $wgRequest ) && !$wgRequest instanceof FauxRequest ) {
- $url = $wgRequest->getRequestURL();
- if ( !$url ) {
- $url = '[no URL]';
- }
- } else {
- $url = '[no req]';
- }
-
- return "[$id] $url Exception from line $line of $file: $message";
+ wfDeprecated( __METHOD__, '1.22' );
+ return MWExceptionHandler::getLogMessage( $this );
}
/**
@@ -248,16 +241,9 @@ class MWException extends Exception {
* It will be either HTML or plain text based on isCommandLine().
*/
function report() {
- global $wgLogExceptionBacktrace;
- $log = $this->getLogMessage();
+ global $wgMimeType;
- if ( $log ) {
- if ( $wgLogExceptionBacktrace ) {
- wfDebugLog( 'exception', $log . "\n" . $this->getTraceAsString() . "\n" );
- } else {
- wfDebugLog( 'exception', $log );
- }
- }
+ MWExceptionHandler::logException( $this );
if ( defined( 'MW_API' ) ) {
// Unhandled API exception, we can't be sure that format printer is alive
@@ -268,6 +254,7 @@ class MWException extends Exception {
} else {
header( "HTTP/1.1 500 MediaWiki exception" );
header( "Status: 500 MediaWiki exception", true );
+ header( "Content-Type: $wgMimeType; charset=utf-8", true );
$this->reportHTML();
}
@@ -329,11 +316,17 @@ class ErrorPageError extends MWException {
$this->msg = $msg;
$this->params = $params;
- if( $msg instanceof Message ) {
- parent::__construct( $msg );
+ // Bug 44111: Messages in the log files should be in English and not
+ // customized by the local wiki. So get the default English version for
+ // passing to the parent constructor. Our overridden report() below
+ // makes sure that the page shown to the user is not forced to English.
+ if ( $msg instanceof Message ) {
+ $enMsg = clone( $msg );
} else {
- parent::__construct( wfMessage( $msg )->text() );
+ $enMsg = wfMessage( $msg, $params );
}
+ $enMsg->inLanguage( 'en' )->useDatabase( false );
+ parent::__construct( $enMsg->text() );
}
function report() {
@@ -461,39 +454,9 @@ class ThrottledError extends ErrorPageError {
*/
class UserBlockedError extends ErrorPageError {
public function __construct( Block $block ) {
- global $wgLang, $wgRequest;
-
- $blocker = $block->getBlocker();
- if ( $blocker instanceof User ) { // local user
- $blockerUserpage = $block->getBlocker()->getUserPage();
- $link = "[[{$blockerUserpage->getPrefixedText()}|{$blockerUserpage->getText()}]]";
- } else { // foreign user
- $link = $blocker;
- }
-
- $reason = $block->mReason;
- if( $reason == '' ) {
- $reason = wfMessage( 'blockednoreason' )->text();
- }
-
- /* $ip returns who *is* being blocked, $intended contains who was meant to be blocked.
- * This could be a username, an IP range, or a single IP. */
- $intended = $block->getTarget();
-
- parent::__construct(
- 'blockedtitle',
- $block->mAuto ? 'autoblockedtext' : 'blockedtext',
- array(
- $link,
- $reason,
- $wgRequest->getIP(),
- $block->getByName(),
- $block->getId(),
- $wgLang->formatExpiry( $block->mExpiry ),
- $intended,
- $wgLang->timeanddate( wfTimestamp( TS_MW, $block->mTimestamp ), true )
- )
- );
+ // @todo FIXME: Implement a more proper way to get context here.
+ $params = $block->getPermissionsError( RequestContext::getMain() );
+ parent::__construct( 'blockedtitle', array_shift( $params ), $params );
}
}
@@ -611,7 +574,7 @@ class HttpError extends MWException {
$content = htmlspecialchars( $this->content );
}
- return "<!DOCTYPE html>\n".
+ return "<!DOCTYPE html>\n" .
"<html><head><title>$header</title></head>\n" .
"<body><h1>$header</h1><p>$content</p></body></html>\n";
}
@@ -648,8 +611,10 @@ class MWExceptionHandler {
$message = "MediaWiki internal error.\n\n";
if ( $wgShowExceptionDetails ) {
- $message .= 'Original exception: ' . $e->__toString() . "\n\n" .
- 'Exception caught inside exception handler: ' . $e2->__toString();
+ $message .= 'Original exception: ' . self::getLogMessage( $e ) .
+ "\nBacktrace:\n" . self::getRedactedTraceAsString( $e ) .
+ "\n\nException caught inside exception handler: " . self::getLogMessage( $e2 ) .
+ "\nBacktrace:\n" . self::getRedactedTraceAsString( $e2 );
} else {
$message .= "Exception caught inside exception handler.\n\n" .
"Set \$wgShowExceptionDetails = true; at the bottom of LocalSettings.php " .
@@ -665,11 +630,11 @@ class MWExceptionHandler {
}
}
} else {
- $message = "Unexpected non-MediaWiki exception encountered, of type \"" . get_class( $e ) . "\"\n" .
- $e->__toString() . "\n";
+ $message = "Unexpected non-MediaWiki exception encountered, of type \"" . get_class( $e ) . "\"";
if ( $wgShowExceptionDetails ) {
- $message .= "\n" . $e->getTraceAsString() . "\n";
+ $message .= "\n" . MWExceptionHandler::getLogMessage( $e ) . "\nBacktrace:\n" .
+ self::getRedactedTraceAsString( $e ) . "\n";
}
if ( $cmdLine ) {
@@ -692,7 +657,7 @@ class MWExceptionHandler {
if ( defined( 'STDERR' ) ) {
fwrite( STDERR, $message );
} else {
- echo( $message );
+ echo $message;
}
}
@@ -715,11 +680,145 @@ class MWExceptionHandler {
// Final cleanup
if ( $wgFullyInitialised ) {
try {
- wfLogProfilingData(); // uses $wgRequest, hence the $wgFullyInitialised condition
- } catch ( Exception $e ) {}
+ // uses $wgRequest, hence the $wgFullyInitialised condition
+ wfLogProfilingData();
+ } catch ( Exception $e ) {
+ }
}
// Exit value should be nonzero for the benefit of shell jobs
exit( 1 );
}
+
+ /**
+ * Generate a string representation of an exception's stack trace
+ *
+ * Like Exception::getTraceAsString, but replaces argument values with
+ * argument type or class name.
+ *
+ * @param Exception $e
+ * @return string
+ */
+ public static function getRedactedTraceAsString( Exception $e ) {
+ $text = '';
+
+ foreach ( self::getRedactedTrace( $e ) as $level => $frame ) {
+ if ( isset( $frame['file'] ) && isset( $frame['line'] ) ) {
+ $text .= "#{$level} {$frame['file']}({$frame['line']}): ";
+ } else {
+ // 'file' and 'line' are unset for calls via call_user_func (bug 55634)
+ // This matches behaviour of Exception::getTraceAsString to instead
+ // display "[internal function]".
+ $text .= "#{$level} [internal function]: ";
+ }
+
+ if ( isset( $frame['class'] ) ) {
+ $text .= $frame['class'] . $frame['type'] . $frame['function'];
+ } else {
+ $text .= $frame['function'];
+ }
+
+ if ( isset( $frame['args'] ) ) {
+ $text .= '(' . implode( ', ', $frame['args'] ) . ")\n";
+ } else {
+ $text .= "()\n";
+ }
+ }
+
+ $level = $level + 1;
+ $text .= "#{$level} {main}";
+
+ return $text;
+ }
+
+ /**
+ * Return a copy of an exception's backtrace as an array.
+ *
+ * Like Exception::getTrace, but replaces each element in each frame's
+ * argument array with the name of its class (if the element is an object)
+ * or its type (if the element is a PHP primitive).
+ *
+ * @since 1.22
+ * @param Exception $e
+ * @return array
+ */
+ public static function getRedactedTrace( Exception $e ) {
+ return array_map( function ( $frame ) {
+ if ( isset( $frame['args'] ) ) {
+ $frame['args'] = array_map( function ( $arg ) {
+ return is_object( $arg ) ? get_class( $arg ) : gettype( $arg );
+ }, $frame['args'] );
+ }
+ return $frame;
+ }, $e->getTrace() );
+ }
+
+
+ /**
+ * Get the ID for this error.
+ *
+ * The ID is saved so that one can match the one output to the user (when
+ * $wgShowExceptionDetails is set to false), to the entry in the debug log.
+ *
+ * @since 1.22
+ * @param Exception $e
+ * @return string
+ */
+ public static function getLogId( Exception $e ) {
+ if ( !isset( $e->_mwLogId ) ) {
+ $e->_mwLogId = wfRandomString( 8 );
+ }
+ return $e->_mwLogId;
+ }
+
+ /**
+ * Return the requested URL and point to file and line number from which the
+ * exception occurred.
+ *
+ * @since 1.22
+ * @param Exception $e
+ * @return string
+ */
+ public static function getLogMessage( Exception $e ) {
+ global $wgRequest;
+
+ $id = self::getLogId( $e );
+ $file = $e->getFile();
+ $line = $e->getLine();
+ $message = $e->getMessage();
+
+ if ( isset( $wgRequest ) && !$wgRequest instanceof FauxRequest ) {
+ $url = $wgRequest->getRequestURL();
+ if ( !$url ) {
+ $url = '[no URL]';
+ }
+ } else {
+ $url = '[no req]';
+ }
+
+ return "[$id] $url Exception from line $line of $file: $message";
+ }
+
+ /**
+ * Log an exception to the exception log (if enabled).
+ *
+ * This method must not assume the exception is an MWException,
+ * it is also used to handle PHP errors or errors from other libraries.
+ *
+ * @since 1.22
+ * @param Exception $e
+ */
+ public static function logException( Exception $e ) {
+ global $wgLogExceptionBacktrace;
+
+ if ( !( $e instanceof MWException ) || $e->isLoggable() ) {
+ $log = self::getLogMessage( $e );
+ if ( $wgLogExceptionBacktrace ) {
+ wfDebugLog( 'exception', $log . "\n" . $e->getTraceAsString() . "\n" );
+ } else {
+ wfDebugLog( 'exception', $log );
+ }
+ }
+ }
+
}
diff --git a/includes/Export.php b/includes/Export.php
index d8cc0242..98de4c00 100644
--- a/includes/Export.php
+++ b/includes/Export.php
@@ -249,9 +249,13 @@ class WikiExporter {
$where = array( 'user_id = log_user' );
# Hide private logs
$hideLogs = LogEventsList::getExcludeClause( $this->db );
- if ( $hideLogs ) $where[] = $hideLogs;
+ if ( $hideLogs ) {
+ $where[] = $hideLogs;
+ }
# Add on any caller specified conditions
- if ( $cond ) $where[] = $cond;
+ if ( $cond ) {
+ $where[] = $cond;
+ }
# Get logging table name for logging.* clause
$logging = $this->db->tableName( 'logging' );
@@ -296,6 +300,7 @@ class WikiExporter {
}
// Inform caller about problem
+ wfProfileOut( __METHOD__ );
throw $e;
}
# For page dumps...
@@ -541,7 +546,7 @@ class XmlDumpWriter {
* @return string
*/
function homelink() {
- return Xml::element( 'base', array(), Title::newMainPage()->getCanonicalUrl() );
+ return Xml::element( 'base', array(), Title::newMainPage()->getCanonicalURL() );
}
/**
@@ -563,8 +568,9 @@ class XmlDumpWriter {
foreach ( $wgContLang->getFormattedNamespaces() as $ns => $title ) {
$spaces .= ' ' .
Xml::element( 'namespace',
- array( 'key' => $ns,
- 'case' => MWNamespace::isCapitalized( $ns ) ? 'first-letter' : 'case-sensitive',
+ array(
+ 'key' => $ns,
+ 'case' => MWNamespace::isCapitalized( $ns ) ? 'first-letter' : 'case-sensitive',
), $title ) . "\n";
}
$spaces .= " </namespaces>";
@@ -593,7 +599,7 @@ class XmlDumpWriter {
$out = " <page>\n";
$title = Title::makeTitle( $row->page_namespace, $row->page_title );
$out .= ' ' . Xml::elementClean( 'title', array(), self::canonicalTitle( $title ) ) . "\n";
- $out .= ' ' . Xml::element( 'ns', array(), strval( $row->page_namespace) ) . "\n";
+ $out .= ' ' . Xml::element( 'ns', array(), strval( $row->page_namespace ) ) . "\n";
$out .= ' ' . Xml::element( 'id', array(), strval( $row->page_id ) ) . "\n";
if ( $row->page_is_redirect ) {
$page = WikiPage::factory( $title );
@@ -636,7 +642,7 @@ class XmlDumpWriter {
$out = " <revision>\n";
$out .= " " . Xml::element( 'id', null, strval( $row->rev_id ) ) . "\n";
- if( isset( $row->rev_parent_id ) && $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";
}
@@ -683,7 +689,7 @@ class XmlDumpWriter {
$content_model = strval( $row->rev_content_model );
} else {
// probably using $wgContentHandlerUseDB = false;
- // @todo: test!
+ // @todo test!
$title = Title::makeTitle( $row->page_namespace, $row->page_title );
$content_model = ContentHandler::getDefaultModelFor( $title );
}
@@ -694,7 +700,7 @@ class XmlDumpWriter {
$content_format = strval( $row->rev_content_format );
} else {
// probably using $wgContentHandlerUseDB = false;
- // @todo: test!
+ // @todo test!
$content_handler = ContentHandler::getForModelID( $content_model );
$content_format = $content_handler->getDefaultFormat();
}
@@ -818,10 +824,13 @@ class XmlDumpWriter {
$archiveName = '';
}
if ( $dumpContents ) {
+ $be = $file->getRepo()->getBackend();
# Dump file as base64
# Uses only XML-safe characters, so does not need escaping
+ # @TODO: too bad this loads the contents into memory (script might swap)
$contents = ' <contents encoding="base64">' .
- chunk_split( base64_encode( file_get_contents( $file->getPath() ) ) ) .
+ chunk_split( base64_encode(
+ $be->getFileContents( array( 'src' => $file->getPath() ) ) ) ) .
" </contents>\n";
} else {
$contents = '';
@@ -837,7 +846,7 @@ class XmlDumpWriter {
" " . $comment . "\n" .
" " . Xml::element( 'filename', null, $file->getName() ) . "\n" .
$archiveName .
- " " . Xml::element( 'src', null, $file->getCanonicalUrl() ) . "\n" .
+ " " . Xml::element( 'src', null, $file->getCanonicalURL() ) . "\n" .
" " . Xml::element( 'size', null, $file->getSize() ) . "\n" .
" " . Xml::element( 'sha1base36', null, $file->getSha1() ) . "\n" .
" " . Xml::element( 'rel', null, $file->getRel() ) . "\n" .
@@ -1185,7 +1194,7 @@ class Dump7ZipOutput extends DumpPipeOutput {
// Suppress annoying useless crap from p7zip
// Unfortunately this could suppress real error messages too
$command .= ' >' . wfGetNull() . ' 2>&1';
- return( $command );
+ return $command;
}
/**
diff --git a/includes/ExternalEdit.php b/includes/ExternalEdit.php
deleted file mode 100644
index 11e94230..00000000
--- a/includes/ExternalEdit.php
+++ /dev/null
@@ -1,132 +0,0 @@
-<?php
-/**
- * External editors support
- *
- * License: Public domain
- *
- * @file
- * @author Erik Moeller <moeller@scireview.de>
- */
-
-/**
- * Support for external editors to modify both text and files
- * in external applications. It works as follows: MediaWiki
- * sends a meta-file with the MIME type 'application/x-external-editor'
- * to the client. The user has to associate that MIME type with
- * a helper application (a reference implementation in Perl
- * can be found in extensions/ee), which will launch the editor,
- * and save the modified data back to the server.
- *
- */
-class ExternalEdit extends ContextSource {
-
- /**
- * Array of URLs to link to
- * @var Array
- */
- private $urls;
-
- /**
- * Constructor
- * @param $context IContextSource context to use
- * @param $urls array
- */
- public function __construct( IContextSource $context, array $urls = array() ) {
- $this->setContext( $context );
- $this->urls = $urls;
- }
-
- /**
- * Check whether external edit or diff should be used.
- *
- * @param $context IContextSource context to use
- * @param string $type can be either 'edit' or 'diff'
- * @return Bool
- */
- public static function useExternalEngine( IContextSource $context, $type ) {
- global $wgUseExternalEditor;
-
- if ( !$wgUseExternalEditor ) {
- return false;
- }
-
- $pref = $type == 'diff' ? 'externaldiff' : 'externaleditor';
- $request = $context->getRequest();
-
- return !$request->getVal( 'internaledit' ) &&
- ( $context->getUser()->getOption( $pref ) || $request->getVal( 'externaledit' ) );
- }
-
- /**
- * Output the information for the external editor
- */
- public function execute() {
- global $wgContLang, $wgScript, $wgScriptPath, $wgCanonicalServer;
-
- $this->getOutput()->disable();
-
- $response = $this->getRequest()->response();
- $response->header( 'Content-type: application/x-external-editor; charset=utf-8' );
- $response->header( 'Cache-control: no-cache' );
-
- $special = $wgContLang->getNsText( NS_SPECIAL );
-
- # $type can be "Edit text", "Edit file" or "Diff text" at the moment
- # See the protocol specifications at [[m:Help:External editors/Tech]] for
- # details.
- if ( count( $this->urls ) ) {
- $urls = $this->urls;
- $type = "Diff text";
- } elseif ( $this->getRequest()->getVal( 'mode' ) == 'file' ) {
- $type = "Edit file";
- $image = wfLocalFile( $this->getTitle() );
- if ( $image ) {
- $urls = array(
- 'File' => array(
- 'Extension' => $image->getExtension(),
- 'URL' => $image->getCanonicalURL()
- )
- );
- } else {
- $urls = array();
- }
- } else {
- $type = "Edit text";
- # *.wiki file extension is used by some editors for syntax
- # highlighting, so we follow that convention
- $urls = array( 'File' => array(
- 'Extension' => 'wiki',
- 'URL' => $this->getTitle()->getCanonicalURL(
- array( 'action' => 'edit', 'internaledit' => 'true' ) )
- ) );
- }
-
- $files = '';
- foreach( $urls as $key => $vars ) {
- $files .= "\n[$key]\n";
- foreach( $vars as $varname => $varval ) {
- $files .= "$varname=$varval\n";
- }
- }
-
- $url = $this->getTitle()->getFullURL(
- $this->getRequest()->appendQueryValue( 'internaledit', 1, true ) );
-
- $control = <<<CONTROL
-; You're seeing this file because you're using Mediawiki's External Editor feature.
-; This is probably because you selected use external editor in your preferences.
-; To edit normally, either disable that preference or go to the URL:
-; $url
-; See http://www.mediawiki.org/wiki/Manual:External_editors for details.
-[Process]
-Type=$type
-Engine=MediaWiki
-Script={$wgCanonicalServer}{$wgScript}
-Server={$wgCanonicalServer}
-Path={$wgScriptPath}
-Special namespace=$special
-$files
-CONTROL;
- echo $control;
- }
-}
diff --git a/includes/ExternalUser.php b/includes/ExternalUser.php
deleted file mode 100644
index 580b9896..00000000
--- a/includes/ExternalUser.php
+++ /dev/null
@@ -1,309 +0,0 @@
-<?php
-/**
- * Authentication with a foreign database
- *
- * Copyright © 2009 Aryeh Gregor
- *
- * 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 ExternalUser ExternalUser
- */
-
-/**
- * A class intended to supplement, and perhaps eventually replace, AuthPlugin.
- * See: http://www.mediawiki.org/wiki/ExternalAuth
- *
- * The class represents a user whose data is in a foreign database. The
- * database may have entirely different conventions from MediaWiki, but it's
- * assumed to at least support the concept of a user id (possibly not an
- * integer), a user name (possibly not meeting MediaWiki's username
- * requirements), and a password.
- *
- * @ingroup ExternalUser
- */
-abstract class ExternalUser {
- protected function __construct() {}
-
- /**
- * Wrappers around initFrom*().
- */
-
- /**
- * @param $name string
- * @return mixed ExternalUser, or false on failure
- */
- public static function newFromName( $name ) {
- global $wgExternalAuthType;
- if ( is_null( $wgExternalAuthType ) ) {
- return false;
- }
- $obj = new $wgExternalAuthType;
- if ( !$obj->initFromName( $name ) ) {
- return false;
- }
- return $obj;
- }
-
- /**
- * @param $id string
- * @return mixed ExternalUser, or false on failure
- */
- public static function newFromId( $id ) {
- global $wgExternalAuthType;
- if ( is_null( $wgExternalAuthType ) ) {
- return false;
- }
- $obj = new $wgExternalAuthType;
- if ( !$obj->initFromId( $id ) ) {
- return false;
- }
- return $obj;
- }
-
- /**
- * @return mixed ExternalUser, or false on failure
- */
- public static function newFromCookie() {
- global $wgExternalAuthType;
- if ( is_null( $wgExternalAuthType ) ) {
- return false;
- }
- $obj = new $wgExternalAuthType;
- if ( !$obj->initFromCookie() ) {
- return false;
- }
- return $obj;
- }
-
- /**
- * Creates the object corresponding to the given User object, assuming the
- * user exists on the wiki and is linked to an external account. If either
- * of these is false, this will return false.
- *
- * This is a wrapper around newFromId().
- *
- * @param $user User
- * @return ExternalUser|bool False on failure
- */
- public static function newFromUser( $user ) {
- global $wgExternalAuthType;
- if ( is_null( $wgExternalAuthType ) ) {
- # Short-circuit to avoid database query in common case so no one
- # kills me
- return false;
- }
-
- $dbr = wfGetDB( DB_SLAVE );
- $id = $dbr->selectField( 'external_user', 'eu_external_id',
- array( 'eu_local_id' => $user->getId() ), __METHOD__ );
- if ( $id === false ) {
- return false;
- }
- return self::newFromId( $id );
- }
-
- /**
- * Given a name, which is a string exactly as input by the user in the
- * login form but with whitespace stripped, initialize this object to be
- * the corresponding ExternalUser. Return true if successful, otherwise
- * false.
- *
- * @param $name string
- * @return bool Success?
- */
- abstract protected function initFromName( $name );
-
- /**
- * Given an id, which was at some previous point in history returned by
- * getId(), initialize this object to be the corresponding ExternalUser.
- * Return true if successful, false otherwise.
- *
- * @param $id string
- * @return bool Success?
- */
- abstract protected function initFromId( $id );
-
- /**
- * Try to magically initialize the user from cookies or similar information
- * so he or she can be logged in on just viewing the wiki. If this is
- * impossible to do, just return false.
- *
- * TODO: Actually use this.
- *
- * @return bool Success?
- */
- protected function initFromCookie() {
- return false;
- }
-
- /**
- * This must return some identifier that stably, uniquely identifies the
- * user. In a typical web application, this could be an integer
- * representing the "user id". In other cases, it might be a string. In
- * any event, the return value should be a string between 1 and 255
- * characters in length; must uniquely identify the user in the foreign
- * database; and, if at all possible, should be permanent.
- *
- * This will only ever be used to reconstruct this ExternalUser object via
- * newFromId(). The resulting object in that case should correspond to the
- * same user, even if details have changed in the interim (e.g., renames or
- * preference changes).
- *
- * @return string
- */
- abstract public function getId();
-
- /**
- * This must return the name that the user would normally use for login to
- * the external database. It is subject to no particular restrictions
- * beyond rudimentary sanity, and in particular may be invalid as a
- * MediaWiki username. It's used to auto-generate an account name that
- * *is* valid for MediaWiki, either with or without user input, but
- * basically is only a hint.
- *
- * @return string
- */
- abstract public function getName();
-
- /**
- * Is the given password valid for the external user? The password is
- * provided in plaintext.
- *
- * @param $password string
- * @return bool
- */
- abstract public function authenticate( $password );
-
- /**
- * Retrieve the value corresponding to the given preference key. The most
- * important values are:
- *
- * - emailaddress
- * - language
- *
- * The value must meet MediaWiki's requirements for values of this type,
- * and will be checked for validity before use. If the preference makes no
- * sense for the backend, or it makes sense but is unset for this user, or
- * is unrecognized, return null.
- *
- * $pref will never equal 'password', since passwords are usually hashed
- * and cannot be directly retrieved. authenticate() is used for this
- * instead.
- *
- * TODO: Currently this is only called for 'emailaddress'; generalize! Add
- * some config option to decide which values are grabbed on user
- * initialization.
- *
- * @param $pref string
- * @return mixed
- */
- public function getPref( $pref ) {
- return null;
- }
-
- /**
- * Return an array of identifiers for all the foreign groups that this user
- * has. The identifiers are opaque objects that only need to be
- * specifiable by the administrator in LocalSettings.php when configuring
- * $wgAutopromote. They may be, for instance, strings or integers.
- *
- * TODO: Support this in $wgAutopromote.
- *
- * @return array
- */
- public function getGroups() {
- return array();
- }
-
- /**
- * Given a preference key (e.g., 'emailaddress'), provide an HTML message
- * telling the user how to change it in the external database. The
- * administrator has specified that this preference cannot be changed on
- * the wiki, and may only be changed in the foreign database. If no
- * message is available, such as for an unrecognized preference, return
- * false.
- *
- * TODO: Use this somewhere.
- *
- * @param $pref string
- * @return mixed String or false
- */
- public static function getPrefMessage( $pref ) {
- return false;
- }
-
- /**
- * Set the given preference key to the given value. Two important
- * preference keys that you might want to implement are 'password' and
- * 'emailaddress'. If the set fails, such as because the preference is
- * unrecognized or because the external database can't be changed right
- * now, return false. If it succeeds, return true.
- *
- * If applicable, you should make sure to validate the new value against
- * any constraints the external database may have, since MediaWiki may have
- * more limited constraints (e.g., on password strength).
- *
- * TODO: Untested.
- *
- * @param $key string
- * @param $value string
- * @return bool Success?
- */
- public static function setPref( $key, $value ) {
- return false;
- }
-
- /**
- * Create a link for future reference between this object and the provided
- * user_id. If the user was already linked, the old link will be
- * overwritten.
- *
- * This is part of the core code and is not overridable by specific
- * plugins. It's in this class only for convenience.
- *
- * @param int $id user_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() ),
- __METHOD__ );
- }
-
- /**
- * Check whether this external user id is already linked with
- * a local user.
- * @return Mixed User if the account is linked, Null otherwise.
- */
- final public function getLocalUser() {
- $dbr = wfGetDB( DB_SLAVE );
- $row = $dbr->selectRow(
- 'external_user',
- '*',
- array( 'eu_external_id' => $this->getId() )
- );
- return $row
- ? User::newFromId( $row->eu_local_id )
- : null;
- }
-
-}
diff --git a/includes/Fallback.php b/includes/Fallback.php
index 2e19a095..cdf6c88e 100644
--- a/includes/Fallback.php
+++ b/includes/Fallback.php
@@ -35,13 +35,13 @@ class Fallback {
if ( substr( $to, -8 ) == '//IGNORE' ) {
$to = substr( $to, 0, strlen( $to ) - 8 );
}
- if( strcasecmp( $from, $to ) == 0 ) {
+ if ( strcasecmp( $from, $to ) == 0 ) {
return $string;
}
- if( strcasecmp( $from, 'utf-8' ) == 0 ) {
+ if ( strcasecmp( $from, 'utf-8' ) == 0 ) {
return utf8_decode( $string );
}
- if( strcasecmp( $to, 'utf-8' ) == 0 ) {
+ if ( strcasecmp( $to, 'utf-8' ) == 0 ) {
return utf8_encode( $string );
}
return $string;
@@ -64,12 +64,12 @@ class Fallback {
* @return string
*/
public static function mb_substr( $str, $start, $count = 'end' ) {
- if( $start != 0 ) {
+ if ( $start != 0 ) {
$split = self::mb_substr_split_unicode( $str, intval( $start ) );
$str = substr( $str, $split );
}
- if( $count !== 'end' ) {
+ if ( $count !== 'end' ) {
$split = self::mb_substr_split_unicode( $str, intval( $count ) );
$str = substr( $str, 0, $split );
}
@@ -83,14 +83,14 @@ class Fallback {
* @return int
*/
public static function mb_substr_split_unicode( $str, $splitPos ) {
- if( $splitPos == 0 ) {
+ if ( $splitPos == 0 ) {
return 0;
}
$byteLen = strlen( $str );
- if( $splitPos > 0 ) {
- if( $splitPos > 256 ) {
+ if ( $splitPos > 0 ) {
+ if ( $splitPos > 256 ) {
// Optimize large string offsets by skipping ahead N bytes.
// This will cut out most of our slow time on Latin-based text,
// and 1/2 to 1/3 on East European and Asian scripts.
@@ -104,7 +104,7 @@ class Fallback {
$bytePos = 0;
}
- while( $charPos++ < $splitPos ) {
+ while ( $charPos++ < $splitPos ) {
++$bytePos;
// Move past any tail bytes
while ( $bytePos < $byteLen && $str[$bytePos] >= "\x80" && $str[$bytePos] < "\xc0" ) {
@@ -115,7 +115,7 @@ class Fallback {
$splitPosX = $splitPos + 1;
$charPos = 0; // relative to end of string; we don't care about the actual char position here
$bytePos = $byteLen;
- while( $bytePos > 0 && $charPos-- >= $splitPosX ) {
+ while ( $bytePos > 0 && $charPos-- >= $splitPosX ) {
--$bytePos;
// Move past any tail bytes
while ( $bytePos > 0 && $str[$bytePos] >= "\x80" && $str[$bytePos] < "\xc0" ) {
@@ -138,12 +138,12 @@ class Fallback {
$total = 0;
// Count ASCII bytes
- for( $i = 0; $i < 0x80; $i++ ) {
+ for ( $i = 0; $i < 0x80; $i++ ) {
$total += $counts[$i];
}
// Count multibyte sequence heads
- for( $i = 0xc0; $i < 0xff; $i++ ) {
+ for ( $i = 0xc0; $i < 0xff; $i++ ) {
$total += $counts[$i];
}
return $total;
@@ -163,7 +163,7 @@ class Fallback {
$ar = array();
preg_match( '/' . $needle . '/u', $haystack, $ar, PREG_OFFSET_CAPTURE, $offset );
- if( isset( $ar[0][1] ) ) {
+ if ( isset( $ar[0][1] ) ) {
return $ar[0][1];
} else {
return false;
@@ -184,7 +184,7 @@ class Fallback {
$ar = array();
preg_match_all( '/' . $needle . '/u', $haystack, $ar, PREG_OFFSET_CAPTURE, $offset );
- if( isset( $ar[0] ) && count( $ar[0] ) > 0 &&
+ if ( isset( $ar[0] ) && count( $ar[0] ) > 0 &&
isset( $ar[0][count( $ar[0] ) - 1][1] ) ) {
return $ar[0][count( $ar[0] ) - 1][1];
} else {
diff --git a/includes/Feed.php b/includes/Feed.php
index caf2e571..635b04e4 100644
--- a/includes/Feed.php
+++ b/includes/Feed.php
@@ -245,7 +245,7 @@ abstract class ChannelFeed extends FeedItem {
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');
+ return ( in_array( $ctype, $allowedctypes ) ? $ctype : 'application/xml' );
}
/**
@@ -306,13 +306,13 @@ class RSSFeed extends ChannelFeed {
function outItem( $item ) {
?>
<item>
- <title><?php print $item->getTitle() ?></title>
- <link><?php print wfExpandUrl( $item->getUrl(), PROTO_CURRENT ) ?></link>
- <guid<?php if( !$item->rssIsPermalink ) print ' isPermaLink="false"' ?>><?php print $item->getUniqueId() ?></guid>
+ <title><?php print $item->getTitle(); ?></title>
+ <link><?php print wfExpandUrl( $item->getUrl(), PROTO_CURRENT ); ?></link>
+ <guid<?php if ( !$item->rssIsPermalink ) { print ' isPermaLink="false"'; } ?>><?php print $item->getUniqueId(); ?></guid>
<description><?php print $item->getDescription() ?></description>
- <?php if( $item->getDate() ) { ?><pubDate><?php print $this->formatTime( $item->getDate() ) ?></pubDate><?php } ?>
- <?php if( $item->getAuthor() ) { ?><dc:creator><?php print $item->getAuthor() ?></dc:creator><?php }?>
- <?php if( $item->getComments() ) { ?><comments><?php print wfExpandUrl( $item->getComments(), PROTO_CURRENT ) ?></comments><?php }?>
+ <?php if ( $item->getDate() ) { ?><pubDate><?php print $this->formatTime( $item->getDate() ); ?></pubDate><?php } ?>
+ <?php if ( $item->getAuthor() ) { ?><dc:creator><?php print $item->getAuthor(); ?></dc:creator><?php }?>
+ <?php if ( $item->getComments() ) { ?><comments><?php print wfExpandUrl( $item->getComments(), PROTO_CURRENT ); ?></comments><?php }?>
</item>
<?php
}
@@ -392,15 +392,15 @@ class AtomFeed extends ChannelFeed {
global $wgMimeType;
?>
<entry>
- <id><?php print $item->getUniqueId() ?></id>
- <title><?php print $item->getTitle() ?></title>
- <link rel="alternate" type="<?php print $wgMimeType ?>" href="<?php print wfExpandUrl( $item->getUrl(), PROTO_CURRENT ) ?>"/>
- <?php if( $item->getDate() ) { ?>
- <updated><?php print $this->formatTime( $item->getDate() ) ?>Z</updated>
+ <id><?php print $item->getUniqueId(); ?></id>
+ <title><?php print $item->getTitle(); ?></title>
+ <link rel="alternate" type="<?php print $wgMimeType ?>" href="<?php print wfExpandUrl( $item->getUrl(), PROTO_CURRENT ); ?>"/>
+ <?php if ( $item->getDate() ) { ?>
+ <updated><?php print $this->formatTime( $item->getDate() ); ?>Z</updated>
<?php } ?>
<summary type="html"><?php print $item->getDescription() ?></summary>
- <?php if( $item->getAuthor() ) { ?><author><name><?php print $item->getAuthor() ?></name></author><?php }?>
+ <?php if ( $item->getAuthor() ) { ?><author><name><?php print $item->getAuthor(); ?></name></author><?php }?>
</entry>
<?php /* @todo FIXME: Need to add comments
diff --git a/includes/FeedUtils.php b/includes/FeedUtils.php
index adc1f781..22cb52be 100644
--- a/includes/FeedUtils.php
+++ b/includes/FeedUtils.php
@@ -59,7 +59,7 @@ class FeedUtils {
return false;
}
- if( !isset( $wgFeedClasses[$type] ) ) {
+ if ( !isset( $wgFeedClasses[$type] ) ) {
$wgOut->addWikiMsg( 'feed-invalid' );
return false;
}
@@ -77,14 +77,14 @@ class FeedUtils {
$titleObj = Title::makeTitle( $row->rc_namespace, $row->rc_title );
$timestamp = wfTimestamp( TS_MW, $row->rc_timestamp );
$actiontext = '';
- if( $row->rc_type == RC_LOG ) {
+ if ( $row->rc_type == RC_LOG ) {
$rcRow = (array)$row; // newFromRow() only accepts arrays for RC rows
$actiontext = LogFormatter::newFromRow( $rcRow )->getActionText();
}
return self::formatDiffRow( $titleObj,
$row->rc_last_oldid, $row->rc_this_oldid,
$timestamp,
- ($row->rc_deleted & Revision::DELETED_COMMENT)
+ $row->rc_deleted & Revision::DELETED_COMMENT
? wfMessage( 'rev-deleted-comment' )->escaped()
: $row->rc_comment,
$actiontext
@@ -121,13 +121,13 @@ class FeedUtils {
// Can't diff special pages, unreadable pages or pages with no new revision
// to compare against: just return the text.
- if( $title->getNamespace() < 0 || $accErrors || !$newid ) {
+ if ( $title->getNamespace() < 0 || $accErrors || !$newid ) {
wfProfileOut( __METHOD__ );
return $completeText;
}
- if( $oldid ) {
- wfProfileIn( __METHOD__."-dodiff" );
+ if ( $oldid ) {
+ wfProfileIn( __METHOD__ . "-dodiff" );
#$diffText = $de->getDiff( wfMessage( 'revisionasof',
# $wgLang->timeanddate( $timestamp ),
@@ -168,10 +168,10 @@ class FeedUtils {
$diffText = UtfNormal::cleanUp( $diffText );
$diffText = self::applyDiffStyle( $diffText );
}
- wfProfileOut( __METHOD__."-dodiff" );
+ wfProfileOut( __METHOD__ . "-dodiff" );
} else {
$rev = Revision::newFromId( $newid );
- if( $wgFeedDiffCutoff <= 0 || is_null( $rev ) ) {
+ if ( $wgFeedDiffCutoff <= 0 || is_null( $rev ) ) {
$newContent = ContentHandler::getForTitle( $title )->makeEmptyContent();
} else {
$newContent = $rev->getContent();
@@ -220,10 +220,11 @@ class FeedUtils {
* @return string
*/
protected static function getDiffLink( Title $title, $newid, $oldid = null ) {
- $queryParameters = ($oldid == null)
- ? "diff={$newid}"
- : "diff={$newid}&oldid={$oldid}";
- $diffUrl = $title->getFullUrl( $queryParameters );
+ $queryParameters = array( 'diff' => $newid );
+ if ( $oldid != null ) {
+ $queryParameters['oldid'] = $oldid;
+ }
+ $diffUrl = $title->getFullURL( $queryParameters );
$diffLink = Html::element( 'a', array( 'href' => $diffUrl ),
wfMessage( 'showdiff' )->inContentLanguage()->text() );
@@ -250,7 +251,7 @@ class FeedUtils {
'diffchange' => 'font-weight: bold; text-decoration: none;',
);
- foreach( $styles as $class => $style ) {
+ foreach ( $styles as $class => $style ) {
$text = preg_replace( "/(<[^>]+)class=(['\"])$class\\2([^>]*>)/",
"\\1style=\"$style\"\\3", $text );
}
diff --git a/includes/FileDeleteForm.php b/includes/FileDeleteForm.php
index 28403cca..65d82b87 100644
--- a/includes/FileDeleteForm.php
+++ b/includes/FileDeleteForm.php
@@ -82,18 +82,18 @@ class FileDeleteForm {
# Flag to hide all contents of the archived revisions
$suppress = $wgRequest->getVal( 'wpSuppress' ) && $wgUser->isAllowed( 'suppressrevision' );
- if( $this->oldimage ) {
+ 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;
}
// Perform the deletion if appropriate
- if( $wgRequest->wasPosted() && $wgUser->matchEditToken( $token, $this->oldimage ) ) {
+ if ( $wgRequest->wasPosted() && $wgUser->matchEditToken( $token, $this->oldimage ) ) {
$deleteReasonList = $wgRequest->getText( 'wpDeleteReasonList' );
$deleteReason = $wgRequest->getText( 'wpReason' );
@@ -109,24 +109,18 @@ class FileDeleteForm {
$status = self::doDelete( $this->title, $this->file, $this->oldimage, $reason, $suppress, $wgUser );
- if( !$status->isGood() ) {
+ if ( !$status->isGood() ) {
$wgOut->addHTML( '<h2>' . $this->prepareMessage( 'filedeleteerror-short' ) . "</h2>\n" );
$wgOut->addWikiText( '<div class="error">' . $status->getWikiText( 'filedeleteerror-short', 'filedeleteerror-long' ) . '</div>' );
}
- if( $status->ok ) {
+ if ( $status->ok ) {
$wgOut->setPageTitle( wfMessage( 'actioncomplete' ) );
$wgOut->addHTML( $this->prepareMessage( 'filedelete-success' ) );
// Return to the main page if we just deleted all versions of the
// file, otherwise go back to the description page
$wgOut->addReturnTo( $this->oldimage ? $this->title : Title::newMainPage() );
- if ( $wgUser->isLoggedIn() && $wgRequest->getCheck( 'wpWatch' ) != $wgUser->isWatched( $this->title ) ) {
- if ( $wgRequest->getCheck( 'wpWatch' ) ) {
- WatchAction::doWatch( $this->title, $wgUser );
- } else {
- WatchAction::doUnwatch( $this->title, $wgUser );
- }
- }
+ WatchAction::doWatchOrUnwatch( $wgRequest->getCheck( 'wpWatch' ), $this->title, $wgUser );
}
return;
}
@@ -153,13 +147,13 @@ class FileDeleteForm {
$user = $wgUser;
}
- if( $oldimage ) {
+ if ( $oldimage ) {
$page = null;
$status = $file->deleteOld( $oldimage, $reason, $suppress );
- if( $status->ok ) {
+ if ( $status->ok ) {
// Need to do a log item
$logComment = wfMessage( 'deletedrevision', $oldimage )->inContentLanguage()->text();
- if( trim( $reason ) != '' ) {
+ if ( trim( $reason ) != '' ) {
$logComment .= wfMessage( 'colon-separator' )
->inContentLanguage()->text() . $reason;
}
@@ -187,7 +181,7 @@ class FileDeleteForm {
// or revision is missing, so check for isOK() rather than isGood()
if ( $deleteStatus->isOK() ) {
$status = $file->delete( $reason, $suppress );
- if( $status->isOK() ) {
+ if ( $status->isOK() ) {
$dbw->commit( __METHOD__ );
} else {
$dbw->rollback( __METHOD__ );
@@ -213,7 +207,7 @@ class FileDeleteForm {
private function showForm() {
global $wgOut, $wgUser, $wgRequest;
- if( $wgUser->isAllowed( 'suppressrevision' ) ) {
+ if ( $wgUser->isAllowed( 'suppressrevision' ) ) {
$suppress = "<tr id=\"wpDeleteSuppressRow\">
<td></td>
<td class='mw-input'><strong>" .
@@ -258,7 +252,7 @@ class FileDeleteForm {
"</td>
</tr>
{$suppress}";
- if( $wgUser->isLoggedIn() ) {
+ if ( $wgUser->isLoggedIn() ) {
$form .= "
<tr>
<td></td>
@@ -314,7 +308,7 @@ class FileDeleteForm {
*/
private function prepareMessage( $message ) {
global $wgLang;
- if( $this->oldimage ) {
+ if ( $this->oldimage ) {
return wfMessage(
"{$message}-old", # To ensure grep will find them: 'filedelete-intro-old', 'filedelete-nofile-old', 'filedelete-success-old'
wfEscapeWikiText( $this->title->getText() ),
@@ -375,11 +369,11 @@ class FileDeleteForm {
$q = array();
$q['action'] = 'delete';
- if( $this->oldimage ) {
+ if ( $this->oldimage ) {
$q['oldimage'] = $this->oldimage;
}
- return $this->title->getLocalUrl( $q );
+ return $this->title->getLocalURL( $q );
}
/**
diff --git a/includes/ForkController.php b/includes/ForkController.php
index 89ad9553..ced45af6 100644
--- a/includes/ForkController.php
+++ b/includes/ForkController.php
@@ -121,7 +121,9 @@ class ForkController {
if ( function_exists( 'pcntl_signal_dispatch' ) ) {
pcntl_signal_dispatch();
} else {
- declare (ticks=1) { $status = $status; }
+ declare( ticks = 1 ) {
+ $status = $status;
+ }
}
// Respond to TERM signal
if ( $this->termReceived ) {
diff --git a/includes/FormOptions.php b/includes/FormOptions.php
index 8477ed98..54822e32 100644
--- a/includes/FormOptions.php
+++ b/includes/FormOptions.php
@@ -28,7 +28,8 @@
/**
* Helper class to keep track of options when mixing links and form elements.
*
- * @todo This badly need some examples and tests :-)
+ * @todo This badly needs some examples and tests :) The usage in SpecialRecentchanges class is a
+ * good ersatz in the meantime.
*/
class FormOptions implements ArrayAccess {
/** @name Type constants
@@ -50,12 +51,26 @@ class FormOptions implements ArrayAccess {
/* @} */
/**
- * @todo Document!
+ * Map of known option names to information about them.
+ *
+ * Each value is an array with the following keys:
+ * - 'default' - the default value as passed to add()
+ * - 'value' - current value, start with null, can be set by various functions
+ * - 'consumed' - true/false, whether the option was consumed using
+ * consumeValue() or consumeValues()
+ * - 'type' - one of the type constants (but never AUTO)
*/
protected $options = array();
# Setting up
+ /**
+ * Add an option to be handled by this FormOptions instance.
+ *
+ * @param string $name Request parameter name
+ * @param mixed $default Default value when the request parameter is not present
+ * @param int $type One of the type constants (optional, defaults to AUTO)
+ */
public function add( $name, $default, $type = self::AUTO ) {
$option = array();
$option['default'] = $default;
@@ -71,20 +86,25 @@ class FormOptions implements ArrayAccess {
$this->options[$name] = $option;
}
+ /**
+ * Remove an option being handled by this FormOptions instance. This is the inverse of add().
+ *
+ * @param string $name Request parameter name
+ */
public function delete( $name ) {
$this->validateName( $name, true );
unset( $this->options[$name] );
}
/**
- * Used to find out which type the data is.
- * All types are defined in the 'Type constants' section of this class
- * Please note we do not support detection of INTNULL MediaWiki type
- * which will be assumed as INT if the data is an integer.
+ * Used to find out which type the data is. All types are defined in the 'Type constants' section
+ * of this class.
*
- * @param $data Mixed: value to guess type for
- * @throws MWException
- * @exception MWException Unsupported datatype
+ * Detection of the INTNULL type is not supported; INT will be assumed if the data is an integer,
+ * MWException will be thrown if it's null.
+ *
+ * @param mixed $data Value to guess the type for
+ * @throws MWException If unable to guess the type
* @return int Type constant
*/
public static function guessType( $data ) {
@@ -102,12 +122,12 @@ class FormOptions implements ArrayAccess {
# Handling values
/**
- * Verify the given option name exist.
+ * Verify that the given option name exists.
*
- * @param string $name option name
- * @param $strict Boolean: throw an exception when the option does not exist (default false)
+ * @param string $name Option name
+ * @param bool $strict Throw an exception when the option doesn't exist instead of returning false
* @throws MWException
- * @return Boolean: true if option exist, false otherwise
+ * @return bool True if the option exists, false otherwise
*/
public function validateName( $name, $strict = false ) {
if ( !isset( $this->options[$name] ) ) {
@@ -117,16 +137,17 @@ class FormOptions implements ArrayAccess {
return false;
}
}
+
return true;
}
/**
* Use to set the value of an option.
*
- * @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
+ * @param string $name Option name
+ * @param mixed $value Value for the option
+ * @param bool $force Whether to set the value when it is equivalent to the default value for this
+ * option (default false).
*/
public function setValue( $name, $value, $force = false ) {
$this->validateName( $name, true );
@@ -140,11 +161,10 @@ class FormOptions implements ArrayAccess {
}
/**
- * Get the value for the given option name.
- * Internally use getValueReal()
+ * Get the value for the given option name. Uses getValueReal() internally.
*
- * @param string $name option name
- * @return Mixed
+ * @param string $name Option name
+ * @return mixed
*/
public function getValue( $name ) {
$this->validateName( $name, true );
@@ -153,9 +173,10 @@ class FormOptions implements ArrayAccess {
}
/**
- * @todo Document
- * @param array $option array structure describing the option
- * @return Mixed. Value or the default value if it is null
+ * Return current option value, based on a structure taken from $options.
+ *
+ * @param array $option Array structure describing the option
+ * @return mixed Value, or the default value if it is null
*/
protected function getValueReal( $option ) {
if ( $option['value'] !== null ) {
@@ -167,9 +188,8 @@ class FormOptions implements ArrayAccess {
/**
* Delete the option value.
- * This will make future calls to getValue() return the default value.
- * @param string $name option name
- * @return null
+ * This will make future calls to getValue() return the default value.
+ * @param string $name Option name
*/
public function reset( $name ) {
$this->validateName( $name, true );
@@ -177,10 +197,13 @@ class FormOptions implements ArrayAccess {
}
/**
- * @todo Document
+ * Get the value of given option and mark it as 'consumed'. Consumed options are not returned
+ * by getUnconsumedValues().
+ *
+ * @see consumeValues()
+ * @throws MWException If the option does not exist
* @param string $name Option name
- * @throws MWException If option does not exist.
- * @return mixed Value or the default value if it is null.
+ * @return mixed Value, or the default value if it is null
*/
public function consumeValue( $name ) {
$this->validateName( $name, true );
@@ -190,11 +213,15 @@ class FormOptions implements ArrayAccess {
}
/**
- * @todo Document
- * @param array $names array of option names
- * @return null
+ * Get the values of given options and mark them as 'consumed'. Consumed options are not returned
+ * by getUnconsumedValues().
+ *
+ * @see consumeValue()
+ * @throws MWException If any option does not exist
+ * @param array $names Array of option names as strings
+ * @return array Array of option values, or the default values if they are null
*/
- public function consumeValues( /*Array*/ $names ) {
+ public function consumeValues( $names ) {
$out = array();
foreach ( $names as $name ) {
@@ -213,9 +240,7 @@ class FormOptions implements ArrayAccess {
* @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
+ * @throws MWException If option is not of type INT
*/
public function validateIntBounds( $name, $min, $max ) {
$this->validateName( $name, true );
@@ -231,9 +256,10 @@ class FormOptions implements ArrayAccess {
}
/**
- * Getting the data out for use
- * @param $all Boolean: whether to include unchanged options (default: false)
- * @return Array
+ * Get all remaining values which have not been consumed by consumeValue() or consumeValues().
+ *
+ * @param bool $all Whether to include unchanged options (default: false)
+ * @return array
*/
public function getUnconsumedValues( $all = false ) {
$values = array();
@@ -251,7 +277,7 @@ class FormOptions implements ArrayAccess {
/**
* Return options modified as an array ( name => value )
- * @return Array
+ * @return array
*/
public function getChangedValues() {
$values = array();
@@ -266,8 +292,8 @@ class FormOptions implements ArrayAccess {
}
/**
- * Format options to an array ( name => value)
- * @return Array
+ * Format options to an array ( name => value )
+ * @return array
*/
public function getAllValues() {
$values = array();
@@ -281,24 +307,38 @@ class FormOptions implements ArrayAccess {
# Reading values
- public function fetchValuesFromRequest( WebRequest $r, $values = false ) {
- if ( !$values ) {
- $values = array_keys( $this->options );
+ /**
+ * Fetch values for all options (or selected options) from the given WebRequest, making them
+ * available for accessing with getValue() or consumeValue() etc.
+ *
+ * @param WebRequest $r The request to fetch values from
+ * @param array $optionKeys Which options to fetch the values for (default:
+ * all of them). Note that passing an empty array will also result in
+ * values for all keys being fetched.
+ * @throws MWException If the type of any option is invalid
+ */
+ public function fetchValuesFromRequest( WebRequest $r, $optionKeys = null ) {
+ if ( !$optionKeys ) {
+ $optionKeys = array_keys( $this->options );
}
- foreach ( $values as $name ) {
+ foreach ( $optionKeys as $name ) {
$default = $this->options[$name]['default'];
$type = $this->options[$name]['type'];
- switch( $type ) {
+ switch ( $type ) {
case self::BOOL:
- $value = $r->getBool( $name, $default ); break;
+ $value = $r->getBool( $name, $default );
+ break;
case self::INT:
- $value = $r->getInt( $name, $default ); break;
+ $value = $r->getInt( $name, $default );
+ break;
case self::STRING:
- $value = $r->getText( $name, $default ); break;
+ $value = $r->getText( $name, $default );
+ break;
case self::INTNULL:
- $value = $r->getIntOrNull( $name ); break;
+ $value = $r->getIntOrNull( $name );
+ break;
default:
throw new MWException( 'Unsupported datatype' );
}
@@ -310,29 +350,26 @@ class FormOptions implements ArrayAccess {
}
/** @name ArrayAccess functions
- * Those function implements PHP ArrayAccess interface
+ * These functions implement the ArrayAccess PHP interface.
* @see http://php.net/manual/en/class.arrayaccess.php
*/
/* @{ */
- /**
- * Whether option exist
- * @return bool
- */
+ /** Whether the option exists. */
public function offsetExists( $name ) {
return isset( $this->options[$name] );
}
- /**
- * Retrieve an option value
- * @return Mixed
- */
+
+ /** Retrieve an option value. */
public function offsetGet( $name ) {
return $this->getValue( $name );
}
- /** Set an option to given value */
+
+ /** Set an option to given value. */
public function offsetSet( $name, $value ) {
$this->setValue( $name, $value );
}
- /** Delete the option */
+
+ /** Delete the option. */
public function offsetUnset( $name ) {
$this->delete( $name );
}
diff --git a/includes/GitInfo.php b/includes/GitInfo.php
index 6f7f8020..f49f9be1 100644
--- a/includes/GitInfo.php
+++ b/includes/GitInfo.php
@@ -121,6 +121,32 @@ class GitInfo {
}
/**
+ * Return the commit date of HEAD entry of the git code repository
+ *
+ * @since 1.22
+ * @return int|bool Commit date (UNIX timestamp) or false
+ */
+ public function getHeadCommitDate() {
+ global $wgGitBin;
+
+ if ( !is_file( $wgGitBin ) || !is_executable( $wgGitBin ) ) {
+ return false;
+ }
+
+ $environment = array( "GIT_DIR" => $this->basedir );
+ $cmd = wfEscapeShellArg( $wgGitBin ) . " show -s --format=format:%ct HEAD";
+ $retc = false;
+ $commitDate = wfShellExec( $cmd, $retc, $environment );
+
+ if ( $retc !== 0 ) {
+ return false;
+ } else {
+ return (int)$commitDate;
+ }
+
+ }
+
+ /**
* Return the name of the current branch, or HEAD if not found
* @return string The branch name, HEAD, or false
*/
@@ -136,7 +162,7 @@ class GitInfo {
/**
* Get an URL to a web viewer link to the HEAD revision.
*
- * @return string|bool string if an URL is available or false otherwise.
+ * @return string|bool string if a URL is available or false otherwise.
*/
public function getHeadViewUrl() {
$config = "{$this->basedir}/config";
@@ -151,7 +177,7 @@ class GitInfo {
if ( isset( $configArray['remote origin'] ) ) {
$remote = $configArray['remote origin'];
} else {
- foreach( $configArray as $sectionName => $sectionConf ) {
+ foreach ( $configArray as $sectionName => $sectionConf ) {
if ( substr( $sectionName, 0, 6 ) == 'remote' ) {
$remote = $sectionConf;
}
@@ -166,14 +192,15 @@ class GitInfo {
if ( substr( $url, -4 ) !== '.git' ) {
$url .= '.git';
}
- foreach( self::getViewers() as $repo => $viewer ) {
+ foreach ( self::getViewers() as $repo => $viewer ) {
$pattern = '#^' . $repo . '$#';
- if ( preg_match( $pattern, $url ) ) {
+ if ( preg_match( $pattern, $url, $matches ) ) {
$viewerUrl = preg_replace( $pattern, $viewer, $url );
$headSHA1 = $this->getHeadSHA1();
$replacements = array(
'%h' => substr( $headSHA1, 0, 7 ),
- '%H' => $headSHA1
+ '%H' => $headSHA1,
+ '%r' => urlencode( $matches[1] ),
);
return strtr( $viewerUrl, $replacements );
}
@@ -212,7 +239,7 @@ class GitInfo {
protected static function getViewers() {
global $wgGitRepositoryViewers;
- if( self::$viewers === false ) {
+ if ( self::$viewers === false ) {
self::$viewers = $wgGitRepositoryViewers;
wfRunHooks( 'GitViewers', array( &self::$viewers ) );
}
diff --git a/includes/GlobalFunctions.php b/includes/GlobalFunctions.php
index 016736f4..77c09e53 100644
--- a/includes/GlobalFunctions.php
+++ b/includes/GlobalFunctions.php
@@ -35,7 +35,7 @@ if ( !defined( 'MEDIAWIKI' ) ) {
* PHP extensions may be included here.
*/
-if( !function_exists( 'iconv' ) ) {
+if ( !function_exists( 'iconv' ) ) {
/**
* @codeCoverageIgnore
* @return string
@@ -73,7 +73,7 @@ if ( !function_exists( 'mb_strlen' ) ) {
}
}
-if( !function_exists( 'mb_strpos' ) ) {
+if ( !function_exists( 'mb_strpos' ) ) {
/**
* @codeCoverageIgnore
* @return int
@@ -84,7 +84,7 @@ if( !function_exists( 'mb_strpos' ) ) {
}
-if( !function_exists( 'mb_strrpos' ) ) {
+if ( !function_exists( 'mb_strrpos' ) ) {
/**
* @codeCoverageIgnore
* @return int
@@ -94,24 +94,16 @@ if( !function_exists( 'mb_strrpos' ) ) {
}
}
-// Support for Wietse Venema's taint feature
-if ( !function_exists( 'istainted' ) ) {
+// gzdecode function only exists in PHP >= 5.4.0
+// http://php.net/gzdecode
+if ( !function_exists( 'gzdecode' ) ) {
/**
* @codeCoverageIgnore
- * @return int
+ * @return string
*/
- function istainted( $var ) {
- return 0;
+ function gzdecode( $data ) {
+ return gzinflate( substr( $data, 10, -8 ) );
}
- /** @codeCoverageIgnore */
- function taint( $var, $level = 0 ) {}
- /** @codeCoverageIgnore */
- function untaint( $var, $level = 0 ) {}
- define( 'TC_HTML', 1 );
- define( 'TC_SHELL', 1 );
- define( 'TC_MYSQL', 1 );
- define( 'TC_PCRE', 1 );
- define( 'TC_SELF', 1 );
}
/// @endcond
@@ -126,19 +118,19 @@ function wfArrayDiff2( $a, $b ) {
}
/**
- * @param $a
- * @param $b
+ * @param $a array|string
+ * @param $b array|string
* @return int
*/
function wfArrayDiff2_cmp( $a, $b ) {
- if ( !is_array( $a ) ) {
+ if ( is_string( $a ) && is_string( $b ) ) {
return strcmp( $a, $b );
} elseif ( count( $a ) !== count( $b ) ) {
return count( $a ) < count( $b ) ? -1 : 1;
} else {
reset( $a );
reset( $b );
- while( ( list( , $valueA ) = each( $a ) ) && ( list( , $valueB ) = each( $b ) ) ) {
+ while ( ( list( , $valueA ) = each( $a ) ) && ( list( , $valueB ) = each( $b ) ) ) {
$cmp = strcmp( $valueA, $valueB );
if ( $cmp !== 0 ) {
return $cmp;
@@ -150,14 +142,16 @@ function wfArrayDiff2_cmp( $a, $b ) {
/**
* Array lookup
- * Returns an array where the values in the first array are replaced by the
- * values in the second array with the corresponding keys
+ * Returns an array where the values in array $b are replaced by the
+ * values in array $a with the corresponding keys
*
+ * @deprecated since 1.22; use array_intersect_key()
* @param $a Array
* @param $b Array
* @return array
*/
function wfArrayLookup( $a, $b ) {
+ wfDeprecated( __FUNCTION__, '1.22' );
return array_flip( array_intersect( array_flip( $a ), array_keys( $b ) ) );
}
@@ -183,11 +177,13 @@ function wfAppendToArrayIfNotDefault( $key, $value, $default, &$changed ) {
* Backwards array plus for people who haven't bothered to read the PHP manual
* XXX: will not darn your socks for you.
*
+ * @deprecated since 1.22; use array_replace()
* @param $array1 Array
* @param [$array2, [...]] Arrays
* @return Array
*/
function wfArrayMerge( $array1/* ... */ ) {
+ wfDeprecated( __FUNCTION__, '1.22' );
$args = func_get_args();
$args = array_reverse( $args, true );
$out = array();
@@ -262,7 +258,7 @@ function wfArrayInsertAfter( array $array, array $insert, $after ) {
*/
function wfObjectToArray( $objOrArray, $recursive = true ) {
$array = array();
- if( is_object( $objOrArray ) ) {
+ if ( is_object( $objOrArray ) ) {
$objOrArray = get_object_vars( $objOrArray );
}
foreach ( $objOrArray as $key => $value ) {
@@ -277,24 +273,6 @@ function wfObjectToArray( $objOrArray, $recursive = true ) {
}
/**
- * Wrapper around array_map() which also taints variables
- *
- * @param $function Callback
- * @param $input Array
- * @return Array
- */
-function wfArrayMap( $function, $input ) {
- $ret = array_map( $function, $input );
- foreach ( $ret as $key => $value ) {
- $taint = istainted( $input[$key] );
- if ( $taint ) {
- taint( $ret[$key], $taint );
- }
- }
- return $ret;
-}
-
-/**
* Get a random decimal value between 0 and 1, in a way
* not likely to give duplicate values for any realistic
* number of articles.
@@ -322,8 +300,8 @@ function wfRandom() {
*/
function wfRandomString( $length = 32 ) {
$str = '';
- while ( strlen( $str ) < $length ) {
- $str .= dechex( mt_rand() );
+ for ( $n = 0; $n < $length; $n += 7 ) {
+ $str .= sprintf( '%07x', mt_rand() & 0xfffffff );
}
return substr( $str, 0, $length );
}
@@ -480,8 +458,8 @@ function wfAppendQuery( $url, $query ) {
if ( is_array( $query ) ) {
$query = wfArrayToCgi( $query );
}
- if( $query != '' ) {
- if( false === strpos( $url, '?' ) ) {
+ if ( $query != '' ) {
+ if ( false === strpos( $url, '?' ) ) {
$url .= '?';
} else {
$url .= '&';
@@ -913,10 +891,10 @@ function wfMakeUrlIndexes( $url ) {
function wfMatchesDomainList( $url, $domains ) {
$bits = wfParseUrl( $url );
if ( is_array( $bits ) && isset( $bits['host'] ) ) {
+ $host = '.' . $bits['host'];
foreach ( (array)$domains as $domain ) {
- // FIXME: This gives false positives. http://nds-nl.wikipedia.org will match nl.wikipedia.org
- // We should use something that interprets dots instead
- if ( substr( $bits['host'], -strlen( $domain ) ) === $domain ) {
+ $domain = '.' . $domain;
+ if ( substr( $host, -strlen( $domain ) ) === $domain ) {
return true;
}
}
@@ -953,14 +931,12 @@ function wfDebug( $text, $logonly = false ) {
MWDebug::debugMsg( $text );
}
- if ( wfRunHooks( 'Debug', array( $text, null /* no log group */ ) ) ) {
- if ( $wgDebugLogFile != '' && !$wgProfileOnly ) {
- # Strip unprintables; they can switch terminal modes when binary data
- # gets dumped, which is pretty annoying.
- $text = preg_replace( '![\x00-\x08\x0b\x0c\x0e-\x1f]!', ' ', $text );
- $text = $wgDebugLogPrefix . $text;
- wfErrorLog( $text, $wgDebugLogFile );
- }
+ if ( $wgDebugLogFile != '' && !$wgProfileOnly ) {
+ # Strip unprintables; they can switch terminal modes when binary data
+ # gets dumped, which is pretty annoying.
+ $text = preg_replace( '![\x00-\x08\x0b\x0c\x0e-\x1f]!', ' ', $text );
+ $text = $wgDebugLogPrefix . $text;
+ wfErrorLog( $text, $wgDebugLogFile );
}
}
@@ -1011,7 +987,7 @@ function wfDebugTimer() {
*/
function wfDebugMem( $exact = false ) {
$mem = memory_get_usage();
- if( !$exact ) {
+ if ( !$exact ) {
$mem = floor( $mem / 1024 ) . ' kilobytes';
} else {
$mem .= ' bytes';
@@ -1031,15 +1007,13 @@ function wfDebugMem( $exact = false ) {
function wfDebugLog( $logGroup, $text, $public = true ) {
global $wgDebugLogGroups;
$text = trim( $text ) . "\n";
- if( isset( $wgDebugLogGroups[$logGroup] ) ) {
+ if ( isset( $wgDebugLogGroups[$logGroup] ) ) {
$time = wfTimestamp( TS_DB );
$wiki = wfWikiID();
$host = wfHostname();
- if ( wfRunHooks( 'Debug', array( $text, $logGroup ) ) ) {
- wfErrorLog( "$time $host $wiki: $text", $wgDebugLogGroups[$logGroup] );
- }
+ wfErrorLog( "$time $host $wiki: $text", $wgDebugLogGroups[$logGroup] );
} elseif ( $public === true ) {
- wfDebug( "[$logGroup] $text", true );
+ wfDebug( "[$logGroup] $text", false );
}
}
@@ -1093,16 +1067,29 @@ function wfDeprecated( $function, $version = false, $component = false, $callerO
/**
* Send a warning either to the debug log or in a PHP error depending on
- * $wgDevelopmentWarnings
+ * $wgDevelopmentWarnings. To log warnings in production, use wfLogWarning() instead.
*
* @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
- * is true
+ * @param $level Integer: PHP error level; defaults to E_USER_NOTICE;
+ * only used when $wgDevelopmentWarnings is true
*/
function wfWarn( $msg, $callerOffset = 1, $level = E_USER_NOTICE ) {
- MWDebug::warning( $msg, $callerOffset + 1, $level );
+ MWDebug::warning( $msg, $callerOffset + 1, $level, 'auto' );
+}
+
+/**
+ * Send a warning as a PHP error and the debug log. This is intended for logging
+ * warnings in production. For logging development warnings, use WfWarn instead.
+ *
+ * @param $msg String: message to send
+ * @param $callerOffset Integer: number of items to go back in the backtrace to
+ * find the correct caller (1 = function calling wfLogWarning, ...)
+ * @param $level Integer: PHP error level; defaults to E_USER_WARNING
+ */
+function wfLogWarning( $msg, $callerOffset = 1, $level = E_USER_WARNING ) {
+ MWDebug::warning( $msg, $callerOffset + 1, $level, 'production' );
}
/**
@@ -1177,6 +1164,8 @@ function wfLogProfilingData() {
global $wgRequestTime, $wgDebugLogFile, $wgDebugRawPage, $wgRequest;
global $wgProfileLimit, $wgUser;
+ StatCounter::singleton()->flush();
+
$profiler = Profiler::instance();
# Profiling must actually be enabled...
@@ -1240,84 +1229,35 @@ function wfLogProfilingData() {
* @return void
*/
function wfIncrStats( $key, $count = 1 ) {
- global $wgStatsMethod;
-
- $count = intval( $count );
- if ( $count == 0 ) {
- return;
- }
-
- if( $wgStatsMethod == 'udp' ) {
- global $wgUDPProfilerHost, $wgUDPProfilerPort, $wgAggregateStatsID;
- static $socket;
-
- $id = $wgAggregateStatsID !== false ? $wgAggregateStatsID : wfWikiID();
-
- if ( !$socket ) {
- $socket = socket_create( AF_INET, SOCK_DGRAM, SOL_UDP );
- $statline = "stats/{$id} - 1 1 1 1 1 -total\n";
- socket_sendto(
- $socket,
- $statline,
- strlen( $statline ),
- 0,
- $wgUDPProfilerHost,
- $wgUDPProfilerPort
- );
- }
- $statline = "stats/{$id} - {$count} 1 1 1 1 {$key}\n";
- wfSuppressWarnings();
- socket_sendto(
- $socket,
- $statline,
- strlen( $statline ),
- 0,
- $wgUDPProfilerHost,
- $wgUDPProfilerPort
- );
- wfRestoreWarnings();
- } elseif( $wgStatsMethod == 'cache' ) {
- global $wgMemc;
- $key = wfMemcKey( 'stats', $key );
- if ( is_null( $wgMemc->incr( $key, $count ) ) ) {
- $wgMemc->add( $key, $count );
- }
- } else {
- // Disabled
- }
+ StatCounter::singleton()->incr( $key, $count );
}
/**
- * Check if the wiki read-only lock file is present. This can be used to lock
- * off editing functions, but doesn't guarantee that the database will not be
- * modified.
+ * Check whether the wiki is in read-only mode.
*
* @return bool
*/
function wfReadOnly() {
- global $wgReadOnlyFile, $wgReadOnly;
-
- if ( !is_null( $wgReadOnly ) ) {
- return (bool)$wgReadOnly;
- }
- if ( $wgReadOnlyFile == '' ) {
- return false;
- }
- // Set $wgReadOnly for faster access next time
- if ( is_file( $wgReadOnlyFile ) ) {
- $wgReadOnly = file_get_contents( $wgReadOnlyFile );
- } else {
- $wgReadOnly = false;
- }
- return (bool)$wgReadOnly;
+ return wfReadOnlyReason() !== false;
}
/**
- * @return bool
+ * Get the value of $wgReadOnly or the contents of $wgReadOnlyFile.
+ *
+ * @return string|bool: String when in read-only mode; false otherwise
*/
function wfReadOnlyReason() {
- global $wgReadOnly;
- wfReadOnly();
+ global $wgReadOnly, $wgReadOnlyFile;
+
+ if ( $wgReadOnly === null ) {
+ // Set $wgReadOnly for faster access next time
+ if ( is_file( $wgReadOnlyFile ) && filesize( $wgReadOnlyFile ) > 0 ) {
+ $wgReadOnly = file_get_contents( $wgReadOnlyFile );
+ } else {
+ $wgReadOnly = false;
+ }
+ }
+
return $wgReadOnly;
}
@@ -1339,27 +1279,27 @@ function wfReadOnlyReason() {
function wfGetLangObj( $langcode = false ) {
# Identify which language to get or create a language object for.
# Using is_object here due to Stub objects.
- if( is_object( $langcode ) ) {
+ if ( is_object( $langcode ) ) {
# Great, we already have the object (hopefully)!
return $langcode;
}
global $wgContLang, $wgLanguageCode;
- if( $langcode === true || $langcode === $wgLanguageCode ) {
+ if ( $langcode === true || $langcode === $wgLanguageCode ) {
# $langcode is the language code of the wikis content language object.
# or it is a boolean and value is true
return $wgContLang;
}
global $wgLang;
- if( $langcode === false || $langcode === $wgLang->getCode() ) {
+ if ( $langcode === false || $langcode === $wgLang->getCode() ) {
# $langcode is the language code of user language object.
# or it was a boolean and value is false
return $wgLang;
}
$validCodes = array_keys( Language::fetchLanguageNames() );
- if( in_array( $langcode, $validCodes ) ) {
+ if ( in_array( $langcode, $validCodes ) ) {
# $langcode corresponds to a valid language.
return Language::factory( $langcode );
}
@@ -1414,7 +1354,7 @@ function wfMessage( $key /*...*/) {
*/
function wfMessageFallback( /*...*/ ) {
$args = func_get_args();
- return MWFunction::callArray( 'Message::newFallbackSequence', $args );
+ return call_user_func_array( 'Message::newFallbackSequence', $args );
}
/**
@@ -1492,7 +1432,7 @@ function wfMsgForContent( $key ) {
$args = func_get_args();
array_shift( $args );
$forcontent = true;
- if( is_array( $wgForceUIMsgAsContentMsg ) &&
+ if ( is_array( $wgForceUIMsgAsContentMsg ) &&
in_array( $key, $wgForceUIMsgAsContentMsg ) )
{
$forcontent = false;
@@ -1515,7 +1455,7 @@ function wfMsgForContentNoTrans( $key ) {
$args = func_get_args();
array_shift( $args );
$forcontent = true;
- if( is_array( $wgForceUIMsgAsContentMsg ) &&
+ if ( is_array( $wgForceUIMsgAsContentMsg ) &&
in_array( $key, $wgForceUIMsgAsContentMsg ) )
{
$forcontent = false;
@@ -1564,7 +1504,7 @@ function wfMsgGetKey( $key, $useDB = true, $langCode = false, $transform = true
$cache = MessageCache::singleton();
$message = $cache->get( $key, $useDB, $langCode );
- if( $message === false ) {
+ if ( $message === false ) {
$message = '&lt;' . htmlspecialchars( $key ) . '&gt;';
} elseif ( $transform ) {
$message = $cache->transform( $message );
@@ -1591,7 +1531,7 @@ function wfMsgReplaceArgs( $message, $args ) {
$args = array_values( $args[0] );
}
$replacementKeys = array();
- foreach( $args as $n => $param ) {
+ foreach ( $args as $n => $param ) {
$replacementKeys['$' . ( $n + 1 )] = $param;
}
$message = strtr( $message, $replacementKeys );
@@ -1675,11 +1615,11 @@ function wfMsgExt( $key, $options ) {
array_shift( $args );
$options = (array)$options;
- foreach( $options as $arrayKey => $option ) {
- if( !preg_match( '/^[0-9]+|language$/', $arrayKey ) ) {
+ foreach ( $options as $arrayKey => $option ) {
+ if ( !preg_match( '/^[0-9]+|language$/', $arrayKey ) ) {
# An unknown index, neither numeric nor "language"
wfWarn( "wfMsgExt called with incorrect parameter key $arrayKey", 1, E_USER_WARNING );
- } elseif( preg_match( '/^[0-9]+$/', $arrayKey ) && !in_array( $option,
+ } elseif ( preg_match( '/^[0-9]+$/', $arrayKey ) && !in_array( $option,
array( 'parse', 'parseinline', 'escape', 'escapenoentities',
'replaceafter', 'parsemag', 'content' ) ) ) {
# A numeric index with unknown value
@@ -1687,11 +1627,11 @@ function wfMsgExt( $key, $options ) {
}
}
- if( in_array( 'content', $options, true ) ) {
+ if ( in_array( 'content', $options, true ) ) {
$forContent = true;
$langCode = true;
$langCodeObj = null;
- } elseif( array_key_exists( 'language', $options ) ) {
+ } elseif ( array_key_exists( 'language', $options ) ) {
$forContent = false;
$langCode = wfGetLangObj( $options['language'] );
$langCodeObj = $langCode;
@@ -1703,13 +1643,13 @@ function wfMsgExt( $key, $options ) {
$string = wfMsgGetKey( $key, /*DB*/true, $langCode, /*Transform*/false );
- if( !in_array( 'replaceafter', $options, true ) ) {
+ if ( !in_array( 'replaceafter', $options, true ) ) {
$string = wfMsgReplaceArgs( $string, $args );
}
$messageCache = MessageCache::singleton();
$parseInline = in_array( 'parseinline', $options, true );
- if( in_array( 'parse', $options, true ) || $parseInline ) {
+ if ( in_array( 'parse', $options, true ) || $parseInline ) {
$string = $messageCache->parse( $string, null, true, !$forContent, $langCodeObj );
if ( $string instanceof ParserOutput ) {
$string = $string->getText();
@@ -1717,7 +1657,7 @@ function wfMsgExt( $key, $options ) {
if ( $parseInline ) {
$m = array();
- if( preg_match( '/^<p>(.*)\n?<\/p>\n?$/sU', $string, $m ) ) {
+ if ( preg_match( '/^<p>(.*)\n?<\/p>\n?$/sU', $string, $m ) ) {
$string = $m[1];
}
}
@@ -1732,7 +1672,7 @@ function wfMsgExt( $key, $options ) {
$string = Sanitizer::escapeHtmlAllowEntities( $string );
}
- if( in_array( 'replaceafter', $options, true ) ) {
+ if ( in_array( 'replaceafter', $options, true ) ) {
$string = wfMsgReplaceArgs( $string, $args );
}
@@ -1741,7 +1681,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
+ * looked up didn't exist but instead the key wrapped in <>'s, this function checks for the
* nonexistence of messages by checking the MessageCache::get() result directly.
*
* @deprecated since 1.18. Use Message::isDisabled().
@@ -1759,10 +1699,12 @@ function wfEmptyMsg( $key ) {
* Throw a debugging exception. This function previously once exited the process,
* but now throws an exception instead, with similar results.
*
+ * @deprecated since 1.22; just throw an MWException yourself
* @param string $msg message shown when dying.
* @throws MWException
*/
function wfDebugDieBacktrace( $msg = '' ) {
+ wfDeprecated( __FUNCTION__, '1.22' );
throw new MWException( $msg );
}
@@ -1779,7 +1721,7 @@ function wfHostname() {
# Hostname overriding
global $wgOverrideHostname;
- if( $wgOverrideHostname !== false ) {
+ if ( $wgOverrideHostname !== false ) {
# Set static and skip any detection
$host = $wgOverrideHostname;
return $host;
@@ -1791,7 +1733,7 @@ function wfHostname() {
} else {
$uname = false;
}
- if( is_array( $uname ) && isset( $uname['nodename'] ) ) {
+ if ( is_array( $uname ) && isset( $uname['nodename'] ) ) {
$host = $uname['nodename'];
} elseif ( getenv( 'COMPUTERNAME' ) ) {
# Windows computer name
@@ -1838,7 +1780,7 @@ function wfReportTime() {
function wfDebugBacktrace( $limit = 0 ) {
static $disabled = null;
- if( extension_loaded( 'Zend Optimizer' ) ) {
+ if ( extension_loaded( 'Zend Optimizer' ) ) {
wfDebug( "Zend Optimizer detected; skipping debug_backtrace for safety.\n" );
return array();
}
@@ -1878,14 +1820,14 @@ function wfBacktrace() {
$msg = "<ul>\n";
}
$backtrace = wfDebugBacktrace();
- foreach( $backtrace as $call ) {
- if( isset( $call['file'] ) ) {
+ foreach ( $backtrace as $call ) {
+ if ( isset( $call['file'] ) ) {
$f = explode( DIRECTORY_SEPARATOR, $call['file'] );
$file = $f[count( $f ) - 1];
} else {
$file = '-';
}
- if( isset( $call['line'] ) ) {
+ if ( isset( $call['line'] ) ) {
$line = $call['line'];
} else {
$line = '-';
@@ -1895,7 +1837,7 @@ function wfBacktrace() {
} else {
$msg .= '<li>' . $file . ' line ' . $line . ' calls ';
}
- if( !empty( $call['class'] ) ) {
+ if ( !empty( $call['class'] ) ) {
$msg .= $call['class'] . $call['type'];
}
$msg .= $call['function'] . '()';
@@ -1993,11 +1935,11 @@ function wfViewPrevNext( $offset, $limit, $link, $query = '', $atend = false ) {
$query = wfCgiToArray( $query );
- if( is_object( $link ) ) {
+ if ( is_object( $link ) ) {
$title = $link;
} else {
$title = Title::newFromText( $link );
- if( is_null( $title ) ) {
+ if ( is_null( $title ) ) {
return false;
}
}
@@ -2006,23 +1948,6 @@ function wfViewPrevNext( $offset, $limit, $link, $query = '', $atend = false ) {
}
/**
- * Make a list item, used by various special pages
- *
- * @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
- * @deprecated since 1.19; use Language::specialList() instead
- */
-function wfSpecialList( $page, $details, $oppositedm = true ) {
- wfDeprecated( __METHOD__, '1.19' );
-
- global $wgLang;
- return $wgLang->specialList( $page, $details, $oppositedm );
-}
-
-/**
* @todo document
* @todo FIXME: We may want to blacklist some broken browsers
*
@@ -2033,16 +1958,16 @@ function wfClientAcceptsGzip( $force = false ) {
static $result = null;
if ( $result === null || $force ) {
$result = false;
- if( isset( $_SERVER['HTTP_ACCEPT_ENCODING'] ) ) {
+ if ( isset( $_SERVER['HTTP_ACCEPT_ENCODING'] ) ) {
# @todo FIXME: We may want to blacklist some broken browsers
$m = array();
- if( preg_match(
+ if ( preg_match(
'/\bgzip(?:;(q)=([0-9]+(?:\.[0-9]+)))?\b/',
$_SERVER['HTTP_ACCEPT_ENCODING'],
$m )
)
{
- if( isset( $m[2] ) && ( $m[1] == 'q' ) && ( $m[2] == 0 ) ) {
+ if ( isset( $m[2] ) && ( $m[1] == 'q' ) && ( $m[2] == 0 ) ) {
$result = false;
return $result;
}
@@ -2078,22 +2003,54 @@ function wfCheckLimits( $deflimit = 50, $optionname = 'rclimit' ) {
* @return String
*/
function wfEscapeWikiText( $text ) {
- $text = strtr( "\n$text", array(
- '"' => '&#34;', '&' => '&#38;', "'" => '&#39;', '<' => '&#60;',
- '=' => '&#61;', '>' => '&#62;', '[' => '&#91;', ']' => '&#93;',
- '{' => '&#123;', '|' => '&#124;', '}' => '&#125;',
- "\n#" => "\n&#35;", "\n*" => "\n&#42;",
- "\n:" => "\n&#58;", "\n;" => "\n&#59;",
- '://' => '&#58;//', 'ISBN ' => 'ISBN&#32;', 'RFC ' => 'RFC&#32;',
- ) );
- return substr( $text, 1 );
+ static $repl = null, $repl2 = null;
+ if ( $repl === null ) {
+ $repl = array(
+ '"' => '&#34;', '&' => '&#38;', "'" => '&#39;', '<' => '&#60;',
+ '=' => '&#61;', '>' => '&#62;', '[' => '&#91;', ']' => '&#93;',
+ '{' => '&#123;', '|' => '&#124;', '}' => '&#125;', ';' => '&#59;',
+ "\n#" => "\n&#35;", "\r#" => "\r&#35;",
+ "\n*" => "\n&#42;", "\r*" => "\r&#42;",
+ "\n:" => "\n&#58;", "\r:" => "\r&#58;",
+ "\n " => "\n&#32;", "\r " => "\r&#32;",
+ "\n\n" => "\n&#10;", "\r\n" => "&#13;\n",
+ "\n\r" => "\n&#13;", "\r\r" => "\r&#13;",
+ "\n\t" => "\n&#9;", "\r\t" => "\r&#9;", // "\n\t\n" is treated like "\n\n"
+ "\n----" => "\n&#45;---", "\r----" => "\r&#45;---",
+ '__' => '_&#95;', '://' => '&#58;//',
+ );
+
+ // We have to catch everything "\s" matches in PCRE
+ foreach ( array( 'ISBN', 'RFC', 'PMID' ) as $magic ) {
+ $repl["$magic "] = "$magic&#32;";
+ $repl["$magic\t"] = "$magic&#9;";
+ $repl["$magic\r"] = "$magic&#13;";
+ $repl["$magic\n"] = "$magic&#10;";
+ $repl["$magic\f"] = "$magic&#12;";
+ }
+
+ // And handle protocols that don't use "://"
+ global $wgUrlProtocols;
+ $repl2 = array();
+ foreach ( $wgUrlProtocols as $prot ) {
+ if ( substr( $prot, -1 ) === ':' ) {
+ $repl2[] = preg_quote( substr( $prot, 0, -1 ), '/' );
+ }
+ }
+ $repl2 = $repl2 ? '/\b(' . join( '|', $repl2 ) . '):/i' : '/^(?!)/';
+ }
+ $text = substr( strtr( "\n$text", $repl ), 1 );
+ $text = preg_replace( $repl2, '$1&#58;', $text );
+ return $text;
}
/**
* Get the current unix timestamp with microseconds. Useful for profiling
+ * @deprecated since 1.22; call microtime() directly
* @return Float
*/
function wfTime() {
+ wfDeprecated( __FUNCTION__, '1.22' );
return microtime( true );
}
@@ -2195,14 +2152,14 @@ function wfHttpError( $code, $label, $desc ) {
* @param $resetGzipEncoding Bool
*/
function wfResetOutputBuffers( $resetGzipEncoding = true ) {
- if( $resetGzipEncoding ) {
+ if ( $resetGzipEncoding ) {
// Suppress Content-Encoding and Content-Length
// headers from 1.10+s wfOutputHandler
global $wgDisableOutputCompression;
$wgDisableOutputCompression = true;
}
- while( $status = ob_get_status() ) {
- if( $status['type'] == 0 /* PHP_OUTPUT_HANDLER_INTERNAL */ ) {
+ while ( $status = ob_get_status() ) {
+ if ( $status['type'] == 0 /* PHP_OUTPUT_HANDLER_INTERNAL */ ) {
// Probably from zlib.output_compression or other
// PHP-internal setting which can't be removed.
//
@@ -2210,13 +2167,13 @@ function wfResetOutputBuffers( $resetGzipEncoding = true ) {
// output behavior.
break;
}
- if( !ob_end_clean() ) {
+ if ( !ob_end_clean() ) {
// Could not remove output buffer handler; abort now
// to avoid getting in some kind of infinite loop.
break;
}
- if( $resetGzipEncoding ) {
- if( $status['name'] == 'ob_gzhandler' ) {
+ if ( $resetGzipEncoding ) {
+ if ( $status['name'] == 'ob_gzhandler' ) {
// Reset the 'Content-Encoding' field set by this handler
// so we can start fresh.
header_remove( 'Content-Encoding' );
@@ -2252,7 +2209,7 @@ function wfClearOutputBuffers() {
*/
function wfAcceptToPrefs( $accept, $def = '*/*' ) {
# No arg means accept anything (per HTTP spec)
- if( !$accept ) {
+ if ( !$accept ) {
return array( $def => 1.0 );
}
@@ -2260,7 +2217,7 @@ function wfAcceptToPrefs( $accept, $def = '*/*' ) {
$parts = explode( ',', $accept );
- foreach( $parts as $part ) {
+ foreach ( $parts as $part ) {
# @todo FIXME: Doesn't deal with params like 'text/html; level=1'
$values = explode( ';', trim( $part ) );
$match = array();
@@ -2287,13 +2244,13 @@ function wfAcceptToPrefs( $accept, $def = '*/*' ) {
* @private
*/
function mimeTypeMatch( $type, $avail ) {
- if( array_key_exists( $type, $avail ) ) {
+ if ( array_key_exists( $type, $avail ) ) {
return $type;
} else {
$parts = explode( '/', $type );
- if( array_key_exists( $parts[0] . '/*', $avail ) ) {
+ if ( array_key_exists( $parts[0] . '/*', $avail ) ) {
return $parts[0] . '/*';
- } elseif( array_key_exists( '*/*', $avail ) ) {
+ } elseif ( array_key_exists( '*/*', $avail ) ) {
return '*/*';
} else {
return null;
@@ -2317,21 +2274,21 @@ function mimeTypeMatch( $type, $avail ) {
function wfNegotiateType( $cprefs, $sprefs ) {
$combine = array();
- foreach( array_keys( $sprefs ) as $type ) {
+ foreach ( array_keys( $sprefs ) as $type ) {
$parts = explode( '/', $type );
- if( $parts[1] != '*' ) {
+ if ( $parts[1] != '*' ) {
$ckey = mimeTypeMatch( $type, $cprefs );
- if( $ckey ) {
+ if ( $ckey ) {
$combine[$type] = $sprefs[$type] * $cprefs[$ckey];
}
}
}
- foreach( array_keys( $cprefs ) as $type ) {
+ foreach ( array_keys( $cprefs ) as $type ) {
$parts = explode( '/', $type );
- if( $parts[1] != '*' && !array_key_exists( $type, $sprefs ) ) {
+ if ( $parts[1] != '*' && !array_key_exists( $type, $sprefs ) ) {
$skey = mimeTypeMatch( $type, $sprefs );
- if( $skey ) {
+ if ( $skey ) {
$combine[$type] = $sprefs[$skey] * $cprefs[$type];
}
}
@@ -2340,8 +2297,8 @@ function wfNegotiateType( $cprefs, $sprefs ) {
$bestq = 0;
$besttype = null;
- foreach( array_keys( $combine ) as $type ) {
- if( $combine[$type] > $bestq ) {
+ foreach ( array_keys( $combine ) as $type ) {
+ if ( $combine[$type] > $bestq ) {
$besttype = $type;
$bestq = $combine[$type];
}
@@ -2447,7 +2404,7 @@ function wfTimestamp( $outputtype = TS_UNIX, $ts = 0 ) {
try {
$timestamp = new MWTimestamp( $ts );
return $timestamp->getTimestamp( $outputtype );
- } catch( TimestampException $e ) {
+ } catch ( TimestampException $e ) {
wfDebug( "wfTimestamp() fed bogus time value: TYPE=$outputtype; VALUE=$ts\n" );
return false;
}
@@ -2462,7 +2419,7 @@ function wfTimestamp( $outputtype = TS_UNIX, $ts = 0 ) {
* @return String
*/
function wfTimestampOrNull( $outputtype = TS_UNIX, $ts = null ) {
- if( is_null( $ts ) ) {
+ if ( is_null( $ts ) ) {
return null;
} else {
return wfTimestamp( $outputtype, $ts );
@@ -2498,7 +2455,7 @@ function wfIsWindows() {
* @return Bool
*/
function wfIsHipHop() {
- return function_exists( 'hphp_thread_set_warmup_enabled' );
+ return defined( 'HPHP_VERSION' );
}
/**
@@ -2533,8 +2490,8 @@ function wfTempDir() {
$tmpDir = array_map( "getenv", array( 'TMPDIR', 'TMP', 'TEMP' ) );
- foreach( $tmpDir as $tmp ) {
- if( $tmp && file_exists( $tmp ) && is_dir( $tmp ) && is_writable( $tmp ) ) {
+ foreach ( $tmpDir as $tmp ) {
+ if ( $tmp && file_exists( $tmp ) && is_dir( $tmp ) && is_writable( $tmp ) ) {
return $tmp;
}
}
@@ -2561,7 +2518,7 @@ function wfMkdirParents( $dir, $mode = null, $caller = null ) {
wfDebug( "$caller: called wfMkdirParents($dir)\n" );
}
- if( strval( $dir ) === '' || file_exists( $dir ) ) {
+ if ( strval( $dir ) === '' || ( file_exists( $dir ) && is_dir( $dir ) ) ) {
return true;
}
@@ -2576,10 +2533,14 @@ function wfMkdirParents( $dir, $mode = null, $caller = null ) {
$ok = mkdir( $dir, $mode, true ); // PHP5 <3
wfRestoreWarnings();
- if( !$ok ) {
+ if ( !$ok ) {
+ //directory may have been created on another request since we last checked
+ if ( is_dir( $dir ) ) {
+ return true;
+ }
+
// PHP doesn't report the path in its warning message, so add our own to aid in diagnosis.
- trigger_error( sprintf( "%s: failed to mkdir \"%s\" mode 0%o", __FUNCTION__, $dir, $mode ),
- E_USER_WARNING );
+ wfLogWarning( sprintf( "failed to mkdir \"%s\" mode 0%o", $dir, $mode ) );
}
return $ok;
}
@@ -2630,7 +2591,9 @@ function wfPercent( $nr, $acc = 2, $round = true ) {
function in_string( $needle, $str, $insensitive = false ) {
wfDeprecated( __METHOD__, '1.21' );
$func = 'strpos';
- if( $insensitive ) $func = 'stripos';
+ if ( $insensitive ) {
+ $func = 'stripos';
+ }
return $func( $str, $needle ) !== false;
}
@@ -2659,47 +2622,15 @@ function in_string( $needle, $str, $insensitive = false ) {
* @return Bool
*/
function wfIniGetBool( $setting ) {
- $val = ini_get( $setting );
+ $val = strtolower( ini_get( $setting ) );
// 'on' and 'true' can't have whitespace around them, but '1' can.
- return strtolower( $val ) == 'on'
- || strtolower( $val ) == 'true'
- || strtolower( $val ) == 'yes'
+ return $val == 'on'
+ || $val == 'true'
+ || $val == 'yes'
|| preg_match( "/^\s*[+-]?0*[1-9]/", $val ); // approx C atoi() function
}
/**
- * 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 string $extension A PHP extension. The file suffix (.so or .dll)
- * should be omitted
- * @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 ) {
- if( extension_loaded( $extension ) ) {
- return true;
- }
-
- $canDl = false;
- if( PHP_SAPI == 'cli' || PHP_SAPI == 'cgi' || PHP_SAPI == 'embed' ) {
- $canDl = ( function_exists( 'dl' ) && is_callable( 'dl' )
- && wfIniGetBool( 'enable_dl' ) && !wfIniGetBool( 'safe_mode' ) );
- }
-
- if( $canDl ) {
- $fileName = $fileName ? $fileName : $extension;
- if( wfIsWindows() ) {
- $fileName = 'php_' . $fileName;
- }
- wfSuppressWarnings();
- dl( $fileName . '.' . PHP_SHLIB_SUFFIX );
- wfRestoreWarnings();
- }
- return extension_loaded( $extension );
-}
-
-/**
* Windows-compatible version of escapeshellarg()
* Windows doesn't recognise single-quotes in the shell, but the escapeshellarg()
* function puts single quotes in regardless of OS.
@@ -2764,25 +2695,15 @@ function wfEscapeShellArg() {
}
/**
- * Execute a shell command, with time and memory limits mirrored from the PHP
- * configuration if supported.
- * @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 array $environ optional environment variables which should be
- * added to the executed command environment.
- * @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)
+ * Check if wfShellExec() is effectively disabled via php.ini config
+ * @return bool|string False or one of (safemode,disabled)
+ * @since 1.22
*/
-function wfShellExec( $cmd, &$retval = null, $environ = array(), $limits = array() ) {
- global $IP, $wgMaxShellMemory, $wgMaxShellFileSize, $wgMaxShellTime,
- $wgMaxShellWallClockTime, $wgShellCgroup;
-
- static $disabled;
+function wfShellExecDisabled() {
+ static $disabled = null;
if ( is_null( $disabled ) ) {
$disabled = false;
- if( wfIniGetBool( 'safe_mode' ) ) {
+ if ( wfIniGetBool( 'safe_mode' ) ) {
wfDebug( "wfShellExec can't run in safe_mode, PHP's exec functions are too broken.\n" );
$disabled = 'safemode';
} else {
@@ -2795,6 +2716,28 @@ function wfShellExec( $cmd, &$retval = null, $environ = array(), $limits = array
}
}
}
+ return $disabled;
+}
+
+/**
+ * Execute a shell command, with time and memory limits mirrored from the PHP
+ * configuration if supported.
+ * @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 array $environ optional environment variables which should be
+ * added to the executed command environment.
+ * @param array $limits optional array with limits(filesize, memory, time, walltime)
+ * this overwrites the global wgShellMax* limits.
+ * @param array $options Array of options. Only one is "duplicateStderr" => true, which
+ * Which duplicates stderr to stdout, including errors from limit.sh
+ * @return string collected stdout as a string
+ */
+function wfShellExec( $cmd, &$retval = null, $environ = array(), $limits = array(), $options = array() ) {
+ global $IP, $wgMaxShellMemory, $wgMaxShellFileSize, $wgMaxShellTime,
+ $wgMaxShellWallClockTime, $wgShellCgroup;
+
+ $disabled = wfShellExecDisabled();
if ( $disabled ) {
$retval = 1;
return $disabled == 'safemode' ?
@@ -2802,10 +2745,12 @@ function wfShellExec( $cmd, &$retval = null, $environ = array(), $limits = array
'Unable to run external programs, passthru() is disabled.';
}
+ $includeStderr = isset( $options['duplicateStderr'] ) && $options['duplicateStderr'];
+
wfInitShellLocale();
$envcmd = '';
- foreach( $environ as $k => $v ) {
+ foreach ( $environ as $k => $v ) {
if ( wfIsWindows() ) {
/* Surrounding a set in quotes (method used by wfEscapeShellArg) makes the quotes themselves
* appear in the environment variable, so we must use carat escaping as documented in
@@ -2839,17 +2784,25 @@ function wfShellExec( $cmd, &$retval = null, $environ = array(), $limits = array
$cmd = '/bin/bash ' . escapeshellarg( "$IP/includes/limit.sh" ) . ' ' .
escapeshellarg( $cmd ) . ' ' .
escapeshellarg(
+ "MW_INCLUDE_STDERR=" . ( $includeStderr ? '1' : '' ) . ';' .
"MW_CPU_LIMIT=$time; " .
'MW_CGROUP=' . escapeshellarg( $wgShellCgroup ) . '; ' .
"MW_MEM_LIMIT=$mem; " .
"MW_FILE_SIZE_LIMIT=$filesize; " .
"MW_WALL_CLOCK_LIMIT=$wallTime"
);
+ } elseif ( $includeStderr ) {
+ $cmd .= ' 2>&1';
}
+ } elseif ( $includeStderr ) {
+ $cmd .= ' 2>&1';
}
wfDebug( "wfShellExec: $cmd\n" );
- $retval = 1; // error by default?
+ // Default to an unusual value that shouldn't happen naturally,
+ // so in the unlikely event of a weird php bug, it would be
+ // more obvious what happened.
+ $retval = 200;
ob_start();
passthru( $cmd, $retval );
$output = ob_get_contents();
@@ -2862,6 +2815,24 @@ function wfShellExec( $cmd, &$retval = null, $environ = array(), $limits = array
}
/**
+ * Execute a shell command, returning both stdout and stderr. Convenience
+ * function, as all the arguments to wfShellExec can become unwieldy.
+ *
+ * @note This also includes errors from limit.sh, e.g. if $wgMaxShellFileSize is exceeded.
+ * @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 array $environ optional environment variables which should be
+ * added to the executed command environment.
+ * @param array $limits optional array with limits(filesize, memory, time, walltime)
+ * this overwrites the global wgShellMax* limits.
+ * @return string collected stdout and stderr as a string
+ */
+function wfShellExecWithStderr( $cmd, &$retval = null, $environ = array(), $limits = array() ) {
+ return wfShellExec( $cmd, $retval, $environ, $limits, array( 'duplicateStderr' => true ) );
+}
+
+/**
* Workaround for http://bugs.php.net/bug.php?id=45132
* escapeshellarg() destroys non-ASCII characters if LANG is not a UTF-8 locale
*/
@@ -2930,7 +2901,7 @@ function wfMerge( $old, $mine, $yours, &$result ) {
$haveDiff3 = $wgDiff3 && file_exists( $wgDiff3 );
wfRestoreWarnings();
- if( !$haveDiff3 ) {
+ if ( !$haveDiff3 ) {
wfDebug( "diff3 not found\n" );
return false;
}
@@ -2959,7 +2930,7 @@ function wfMerge( $old, $mine, $yours, &$result ) {
wfEscapeShellArg( $yourtextName );
$handle = popen( $cmd, 'r' );
- if( fgets( $handle, 1024 ) ) {
+ if ( fgets( $handle, 1024 ) ) {
$conflict = true;
} else {
$conflict = false;
@@ -3011,7 +2982,7 @@ function wfDiff( $before, $after, $params = '-u' ) {
# This check may also protect against code injection in
# case of broken installations.
- if( !$haveDiff ) {
+ if ( !$haveDiff ) {
wfDebug( "diff executable not found\n" );
$diffs = new Diff( explode( "\n", $before ), explode( "\n", $after ) );
$format = new UnifiedDiffFormatter();
@@ -3125,7 +3096,7 @@ function wfBaseName( $path, $suffix = '' ) {
? ''
: ( '(?:' . preg_quote( $suffix, '#' ) . ')?' );
$matches = array();
- if( preg_match( "#([^/\\\\]*?){$encSuffix}[/\\\\]*$#", $path, $matches ) ) {
+ if ( preg_match( "#([^/\\\\]*?){$encSuffix}[/\\\\]*$#", $path, $matches ) ) {
return $matches[1];
} else {
return '';
@@ -3153,21 +3124,21 @@ function wfRelativePath( $path, $from ) {
$pieces = explode( DIRECTORY_SEPARATOR, dirname( $path ) );
$against = explode( DIRECTORY_SEPARATOR, $from );
- if( $pieces[0] !== $against[0] ) {
+ if ( $pieces[0] !== $against[0] ) {
// Non-matching Windows drive letters?
// Return a full path.
return $path;
}
// Trim off common prefix
- while( count( $pieces ) && count( $against )
+ while ( count( $pieces ) && count( $against )
&& $pieces[0] == $against[0] ) {
array_shift( $pieces );
array_shift( $against );
}
// relative dots to bump us to the parent
- while( count( $against ) ) {
+ while ( count( $against ) ) {
array_unshift( $pieces, '..' );
array_shift( $against );
}
@@ -3206,20 +3177,20 @@ function wfDoUpdates( $commit = '' ) {
*/
function wfBaseConvert( $input, $sourceBase, $destBase, $pad = 1, $lowercase = true, $engine = 'auto' ) {
$input = (string)$input;
- if(
+ if (
$sourceBase < 2 ||
$sourceBase > 36 ||
$destBase < 2 ||
$destBase > 36 ||
- $sourceBase != (int) $sourceBase ||
- $destBase != (int) $destBase ||
- $pad != (int) $pad ||
+ $sourceBase != (int)$sourceBase ||
+ $destBase != (int)$destBase ||
+ $pad != (int)$pad ||
!preg_match( "/^[" . substr( '0123456789abcdefghijklmnopqrstuvwxyz', 0, $sourceBase ) . "]+$/i", $input )
) {
return false;
}
- static $baseChars = array (
+ 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',
@@ -3234,40 +3205,40 @@ function wfBaseConvert( $input, $sourceBase, $destBase, $pad = 1, $lowercase = t
'u' => 30, 'v' => 31, 'w' => 32, 'x' => 33, 'y' => 34, 'z' => 35
);
- if( extension_loaded( 'gmp' ) && ( $engine == 'auto' || $engine == 'gmp' ) ) {
+ if ( extension_loaded( 'gmp' ) && ( $engine == 'auto' || $engine == 'gmp' ) ) {
$result = gmp_strval( gmp_init( $input, $sourceBase ), $destBase );
- } elseif( extension_loaded( 'bcmath' ) && ( $engine == 'auto' || $engine == 'bcmath' ) ) {
+ } elseif ( extension_loaded( 'bcmath' ) && ( $engine == 'auto' || $engine == 'bcmath' ) ) {
$decimal = '0';
- foreach( str_split( strtolower( $input ) ) as $char ) {
+ foreach ( str_split( strtolower( $input ) ) as $char ) {
$decimal = bcmul( $decimal, $sourceBase );
$decimal = bcadd( $decimal, $baseChars[$char] );
}
- for( $result = ''; bccomp( $decimal, 0 ); $decimal = bcdiv( $decimal, $destBase, 0 ) ) {
+ for ( $result = ''; bccomp( $decimal, 0 ); $decimal = bcdiv( $decimal, $destBase, 0 ) ) {
$result .= $baseChars[bcmod( $decimal, $destBase )];
}
$result = strrev( $result );
} else {
$inDigits = array();
- foreach( str_split( strtolower( $input ) ) as $char ) {
+ foreach ( str_split( strtolower( $input ) ) as $char ) {
$inDigits[] = $baseChars[$char];
}
// Iterate over the input, modulo-ing out an output digit
// at a time until input is gone.
$result = '';
- while( $inDigits ) {
+ while ( $inDigits ) {
$work = 0;
$workDigits = array();
// Long division...
- foreach( $inDigits as $digit ) {
+ foreach ( $inDigits as $digit ) {
$work *= $sourceBase;
$work += $digit;
- if( $workDigits || $work >= $destBase ) {
- $workDigits[] = (int) ( $work / $destBase );
+ if ( $workDigits || $work >= $destBase ) {
+ $workDigits[] = (int)( $work / $destBase );
}
$work %= $destBase;
}
@@ -3283,7 +3254,7 @@ function wfBaseConvert( $input, $sourceBase, $destBase, $pad = 1, $lowercase = t
$result = strrev( $result );
}
- if( !$lowercase ) {
+ if ( !$lowercase ) {
$result = strtoupper( $result );
}
@@ -3309,9 +3280,9 @@ function wfCreateObject( $name, $p ) {
function wfHttpOnlySafe() {
global $wgHttpOnlyBlacklist;
- if( isset( $_SERVER['HTTP_USER_AGENT'] ) ) {
- foreach( $wgHttpOnlyBlacklist as $regex ) {
- if( preg_match( $regex, $_SERVER['HTTP_USER_AGENT'] ) ) {
+ if ( isset( $_SERVER['HTTP_USER_AGENT'] ) ) {
+ foreach ( $wgHttpOnlyBlacklist as $regex ) {
+ if ( preg_match( $regex, $_SERVER['HTTP_USER_AGENT'] ) ) {
return false;
}
}
@@ -3338,7 +3309,7 @@ function wfCheckEntropy() {
*/
function wfFixSessionID() {
// If the cookie or session id is already set we already have a session and should abort
- if ( isset( $_COOKIE[ session_name() ] ) || session_id() ) {
+ if ( isset( $_COOKIE[session_name()] ) || session_id() ) {
return;
}
@@ -3356,6 +3327,27 @@ function wfFixSessionID() {
}
/**
+ * Reset the session_id
+ * @since 1.22
+ */
+function wfResetSessionID() {
+ global $wgCookieSecure;
+ $oldSessionId = session_id();
+ $cookieParams = session_get_cookie_params();
+ if ( wfCheckEntropy() && $wgCookieSecure == $cookieParams['secure'] ) {
+ session_regenerate_id( false );
+ } else {
+ $tmp = $_SESSION;
+ session_destroy();
+ wfSetupSession( MWCryptRand::generateHex( 32 ) );
+ $_SESSION = $tmp;
+ }
+ $newSessionId = session_id();
+ wfRunHooks( 'ResetSessionID', array( $oldSessionId, $newSessionId ) );
+}
+
+
+/**
* Initialise php session
*
* @param $sessionId Bool
@@ -3363,9 +3355,9 @@ function wfFixSessionID() {
function wfSetupSession( $sessionId = false ) {
global $wgSessionsInMemcached, $wgSessionsInObjectCache, $wgCookiePath, $wgCookieDomain,
$wgCookieSecure, $wgCookieHttpOnly, $wgSessionHandler;
- if( $wgSessionsInObjectCache || $wgSessionsInMemcached ) {
+ if ( $wgSessionsInObjectCache || $wgSessionsInMemcached ) {
ObjectCacheSessionHandler::install();
- } elseif( $wgSessionHandler && $wgSessionHandler != ini_get( 'session.save_handler' ) ) {
+ } elseif ( $wgSessionHandler && $wgSessionHandler != ini_get( 'session.save_handler' ) ) {
# Only set this if $wgSessionHandler isn't null and session.save_handler
# hasn't already been set to the desired value (that causes errors)
ini_set( 'session.save_handler', $wgSessionHandler );
@@ -3440,7 +3432,7 @@ function wfForeignMemcKey( $db, $prefix /*, ... */ ) {
} else {
$key = $db . ':' . implode( ':', $args );
}
- return $key;
+ return str_replace( ' ', '_', $key );
}
/**
@@ -3588,7 +3580,7 @@ function wfScript( $script = 'index' ) {
global $wgScriptPath, $wgScriptExtension, $wgScript, $wgLoadScript;
if ( $script === 'index' ) {
return $wgScript;
- } else if ( $script === 'load' ) {
+ } elseif ( $script === 'load' ) {
return $wgLoadScript;
} else {
return "{$wgScriptPath}/{$script}{$wgScriptExtension}";
@@ -3601,7 +3593,7 @@ function wfScript( $script = 'index' ) {
* @return string script URL
*/
function wfGetScriptUrl() {
- if( isset( $_SERVER['SCRIPT_NAME'] ) ) {
+ if ( isset( $_SERVER['SCRIPT_NAME'] ) ) {
#
# as it was called, minus the query string.
#
@@ -3649,15 +3641,22 @@ function wfGetNull() {
*
* @param $maxLag Integer (deprecated)
* @param $wiki mixed Wiki identifier accepted by wfGetLB
+ * @param $cluster string cluster name accepted by LBFactory
*/
-function wfWaitForSlaves( $maxLag = false, $wiki = false ) {
- $lb = wfGetLB( $wiki );
+function wfWaitForSlaves( $maxLag = false, $wiki = false, $cluster = false ) {
+ $lb = ( $cluster !== false )
+ ? wfGetLBFactory()->getExternalLB( $cluster )
+ : wfGetLB( $wiki );
// bug 27975 - Don't try to wait for slaves if there are none
// Prevents permission error when getting master position
if ( $lb->getServerCount() > 1 ) {
- $dbw = $lb->getConnection( DB_MASTER );
+ $dbw = $lb->getConnection( DB_MASTER, array(), $wiki );
$pos = $dbw->getMasterPos();
- $lb->waitForAll( $pos );
+ // The DBMS may not support getMasterPos() or the whole
+ // load balancer might be fake (e.g. $wgAllDBsAreLocalhost).
+ if ( $pos !== false ) {
+ $lb->waitForAll( $pos );
+ }
}
}
@@ -3739,9 +3738,9 @@ function wfStripIllegalFilenameChars( $name ) {
function wfMemoryLimit() {
global $wgMemoryLimit;
$memlimit = wfShorthandToInteger( ini_get( 'memory_limit' ) );
- if( $memlimit != -1 ) {
+ if ( $memlimit != -1 ) {
$conflimit = wfShorthandToInteger( $wgMemoryLimit );
- if( $conflimit == -1 ) {
+ if ( $conflimit == -1 ) {
wfDebug( "Removing PHP's memory limit\n" );
wfSuppressWarnings();
ini_set( 'memory_limit', $conflimit );
@@ -3766,12 +3765,12 @@ function wfMemoryLimit() {
*/
function wfShorthandToInteger( $string = '' ) {
$string = trim( $string );
- if( $string === '' ) {
+ if ( $string === '' ) {
return -1;
}
$last = $string[strlen( $string ) - 1];
$val = intval( $string );
- switch( $last ) {
+ switch ( $last ) {
case 'g':
case 'G':
$val *= 1024;
@@ -3799,22 +3798,17 @@ function wfBCP47( $code ) {
$codeSegment = explode( '-', $code );
$codeBCP = array();
foreach ( $codeSegment as $segNo => $seg ) {
- if ( count( $codeSegment ) > 0 ) {
- // when previous segment is x, it is a private segment and should be lc
- if( $segNo > 0 && strtolower( $codeSegment[( $segNo - 1 )] ) == 'x' ) {
- $codeBCP[$segNo] = strtolower( $seg );
- // ISO 3166 country code
- } elseif ( ( strlen( $seg ) == 2 ) && ( $segNo > 0 ) ) {
- $codeBCP[$segNo] = strtoupper( $seg );
- // ISO 15924 script code
- } elseif ( ( strlen( $seg ) == 4 ) && ( $segNo > 0 ) ) {
- $codeBCP[$segNo] = ucfirst( strtolower( $seg ) );
- // Use lowercase for other cases
- } else {
- $codeBCP[$segNo] = strtolower( $seg );
- }
+ // when previous segment is x, it is a private segment and should be lc
+ if ( $segNo > 0 && strtolower( $codeSegment[( $segNo - 1 )] ) == 'x' ) {
+ $codeBCP[$segNo] = strtolower( $seg );
+ // ISO 3166 country code
+ } elseif ( ( strlen( $seg ) == 2 ) && ( $segNo > 0 ) ) {
+ $codeBCP[$segNo] = strtoupper( $seg );
+ // ISO 15924 script code
+ } elseif ( ( strlen( $seg ) == 4 ) && ( $segNo > 0 ) ) {
+ $codeBCP[$segNo] = ucfirst( strtolower( $seg ) );
+ // Use lowercase for other cases
} else {
- // Use lowercase for single segment
$codeBCP[$segNo] = strtolower( $seg );
}
}
@@ -3879,7 +3873,7 @@ function wfGetLangConverterCacheStorage() {
* @param array $args parameters passed to hook functions
* @return Boolean True if no handler aborted the hook
*/
-function wfRunHooks( $event, $args = array() ) {
+function wfRunHooks( $event, array $args = array() ) {
return Hooks::run( $event, $args );
}
@@ -3897,7 +3891,7 @@ function wfRunHooks( $event, $args = array() ) {
* @throws MWException if $data not long enough, or if unpack fails
* @return array Associative array of the extracted data
*/
-function wfUnpack( $format, $data, $length=false ) {
+function wfUnpack( $format, $data, $length = false ) {
if ( $length !== false ) {
$realLen = strlen( $data );
if ( $realLen < $length ) {
@@ -3939,19 +3933,19 @@ function wfIsBadImage( $name, $contextTitle = false, $blacklist = null ) {
# Handle redirects
$redirectTitle = RepoGroup::singleton()->checkRedirect( Title::makeTitle( NS_FILE, $name ) );
- if( $redirectTitle ) {
- $name = $redirectTitle->getDbKey();
+ if ( $redirectTitle ) {
+ $name = $redirectTitle->getDBkey();
}
# Run the extension hook
$bad = false;
- if( !wfRunHooks( 'BadImage', array( $name, &$bad ) ) ) {
+ if ( !wfRunHooks( 'BadImage', array( $name, &$bad ) ) ) {
wfProfileOut( __METHOD__ );
return $bad;
}
$cacheable = ( $blacklist === null );
- if( $cacheable && $badImageCache !== null ) {
+ if ( $cacheable && $badImageCache !== null ) {
$badImages = $badImageCache;
} else { // cache miss
if ( $blacklist === null ) {
@@ -3960,7 +3954,7 @@ function wfIsBadImage( $name, $contextTitle = false, $blacklist = null ) {
# Build the list now
$badImages = array();
$lines = explode( "\n", $blacklist );
- foreach( $lines as $line ) {
+ foreach ( $lines as $line ) {
# List items only
if ( substr( $line, 0, 1 ) !== '*' ) {
continue;
@@ -3999,3 +3993,16 @@ function wfIsBadImage( $name, $contextTitle = false, $blacklist = null ) {
wfProfileOut( __METHOD__ );
return $bad;
}
+
+/**
+ * Determine whether the client at a given source IP is likely to be able to
+ * access the wiki via HTTPS.
+ *
+ * @param string $ip The IPv4/6 address in the normal human-readable form
+ * @return boolean
+ */
+function wfCanIPUseHTTPS( $ip ) {
+ $canDo = true;
+ wfRunHooks( 'CanIPUseHTTPS', array( $ip, &$canDo ) );
+ return !!$canDo;
+}
diff --git a/includes/HTMLForm.php b/includes/HTMLForm.php
index 68639739..d260862c 100644
--- a/includes/HTMLForm.php
+++ b/includes/HTMLForm.php
@@ -83,9 +83,8 @@
* $form = new HTMLForm( $someFields );
* $form->setMethod( 'get' )
* ->setWrapperLegendMsg( 'message-key' )
- * ->suppressReset()
* ->prepareForm()
- * ->displayForm();
+ * ->displayForm( '' );
* @endcode
* Note that you will have prepareForm and displayForm at the end. Other
* methods call done after that would simply not be part of the form :(
@@ -95,7 +94,7 @@
class HTMLForm extends ContextSource {
// A mapping of 'type' inputs onto standard HTMLFormField subclasses
- static $typeMappings = array(
+ public static $typeMappings = array(
'api' => 'HTMLApiField',
'text' => 'HTMLTextField',
'textarea' => 'HTMLTextAreaField',
@@ -128,6 +127,7 @@ class HTMLForm extends ContextSource {
protected $mFieldTree;
protected $mShowReset = false;
+ protected $mShowSubmit = true;
public $mFieldData;
protected $mSubmitCallback;
@@ -140,6 +140,7 @@ class HTMLForm extends ContextSource {
protected $mSectionFooters = array();
protected $mPost = '';
protected $mId;
+ protected $mTableId = '';
protected $mSubmitID;
protected $mSubmitName;
@@ -186,6 +187,7 @@ class HTMLForm extends ContextSource {
'table',
'div',
'raw',
+ 'vform',
);
/**
@@ -200,12 +202,12 @@ class HTMLForm extends ContextSource {
$this->setContext( $context );
$this->mTitle = false; // We don't need them to set a title
$this->mMessagePrefix = $messagePrefix;
- } else {
+ } elseif ( is_null( $context ) && $messagePrefix !== '' ) {
+ $this->mMessagePrefix = $messagePrefix;
+ } elseif ( is_string( $context ) && $messagePrefix === '' ) {
// B/C since 1.18
- if ( is_string( $context ) && $messagePrefix === '' ) {
- // it's actually $messagePrefix
- $this->mMessagePrefix = $context;
- }
+ // it's actually $messagePrefix
+ $this->mMessagePrefix = $context;
}
// Expand out into a tree.
@@ -222,8 +224,15 @@ class HTMLForm extends ContextSource {
}
$field = self::loadInputFromParameters( $fieldname, $info );
+ // FIXME During field's construct, the parent form isn't available!
+ // could add a 'parent' name-value to $info, could add a third parameter.
$field->mParent = $this;
+ // vform gets too much space if empty labels generate HTML.
+ if ( $this->isVForm() ) {
+ $field->setShowEmptyLabel( false );
+ }
+
$setSection =& $loadedDescriptor;
if ( $section ) {
$sectionParts = explode( '/', $section );
@@ -256,7 +265,7 @@ class HTMLForm extends ContextSource {
*/
public function setDisplayFormat( $format ) {
if ( !in_array( $format, $this->availableDisplayFormats ) ) {
- throw new MWException ( 'Display format must be one of ' . print_r( $this->availableDisplayFormats, true ) );
+ throw new MWException( 'Display format must be one of ' . print_r( $this->availableDisplayFormats, true ) );
}
$this->displayFormat = $format;
return $this;
@@ -272,11 +281,22 @@ class HTMLForm extends ContextSource {
}
/**
+ * Test if displayFormat is 'vform'
+ * @since 1.22
+ * @return Bool
+ */
+ public function isVForm() {
+ return $this->displayFormat === 'vform';
+ }
+
+ /**
* Add the HTMLForm-specific JavaScript, if it hasn't been
* done already.
* @deprecated since 1.18 load modules with ResourceLoader instead
*/
- static function addJS() { wfDeprecated( __METHOD__, '1.18' ); }
+ static function addJS() {
+ wfDeprecated( __METHOD__, '1.18' );
+ }
/**
* Initialise a new Object for the field
@@ -572,6 +592,21 @@ class HTMLForm extends ContextSource {
}
/**
+ * Add an array of hidden fields to the output
+ *
+ * @since 1.22
+ * @param array $fields Associative array of fields to add;
+ * mapping names to their values
+ * @return HTMLForm $this for chaining calls
+ */
+ public function addHiddenFields( array $fields ) {
+ foreach ( $fields as $name => $value ) {
+ $this->mHiddenFields[] = array( $value, array( 'name' => $name ) );
+ }
+ return $this;
+ }
+
+ /**
* Add a button to the form
* @param string $name field name.
* @param string $value field value
@@ -585,8 +620,8 @@ class HTMLForm extends ContextSource {
}
/**
- * Display the form (sending to $wgOut), with an appropriate error
- * message or stack of messages, and any validation errors, etc.
+ * Display the form (sending to the context's OutputPage object), with an
+ * appropriate error message or stack of messages, and any validation errors, etc.
*
* @attention You should call prepareForm() before calling this function.
* Moreover, when doing method chaining this should be the very last method
@@ -608,6 +643,11 @@ class HTMLForm extends ContextSource {
# For good measure (it is the default)
$this->getOutput()->preventClickjacking();
$this->getOutput()->addModules( 'mediawiki.htmlform' );
+ if ( $this->isVForm() ) {
+ $this->getOutput()->addModuleStyles( 'mediawiki.ui' );
+ // TODO should vertical form set setWrapperLegend( false )
+ // to hide ugly fieldsets?
+ }
$html = ''
. $this->getErrors( $submitResult )
@@ -640,15 +680,18 @@ 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->getAction(),
+ 'method' => $this->getMethod(),
+ 'class' => array( 'visualClear' ),
'enctype' => $encType,
);
if ( !empty( $this->mId ) ) {
$attribs['id'] = $this->mId;
}
+ if ( $this->isVForm() ) {
+ array_push( $attribs['class'], 'mw-ui-vform', 'mw-ui-container' );
+ }
return Html::rawElement( 'form', $attribs, $html );
}
@@ -682,24 +725,40 @@ class HTMLForm extends ContextSource {
* @return String HTML.
*/
function getButtons() {
- $html = '';
- $attribs = array();
+ $html = '<span class="mw-htmlform-submit-buttons">';
- if ( isset( $this->mSubmitID ) ) {
- $attribs['id'] = $this->mSubmitID;
- }
+ if ( $this->mShowSubmit ) {
+ $attribs = array();
- if ( isset( $this->mSubmitName ) ) {
- $attribs['name'] = $this->mSubmitName;
- }
+ if ( isset( $this->mSubmitID ) ) {
+ $attribs['id'] = $this->mSubmitID;
+ }
- if ( isset( $this->mSubmitTooltip ) ) {
- $attribs += Linker::tooltipAndAccesskeyAttribs( $this->mSubmitTooltip );
- }
+ if ( isset( $this->mSubmitName ) ) {
+ $attribs['name'] = $this->mSubmitName;
+ }
- $attribs['class'] = 'mw-htmlform-submit';
+ if ( isset( $this->mSubmitTooltip ) ) {
+ $attribs += Linker::tooltipAndAccesskeyAttribs( $this->mSubmitTooltip );
+ }
- $html .= Xml::submitButton( $this->getSubmitText(), $attribs ) . "\n";
+ $attribs['class'] = array( 'mw-htmlform-submit' );
+
+ if ( $this->isVForm() ) {
+ // mw-ui-block is necessary because the buttons aren't necessarily in an
+ // immediate child div of the vform.
+ array_push( $attribs['class'], 'mw-ui-button', 'mw-ui-big', 'mw-ui-primary', 'mw-ui-block' );
+ }
+
+ $html .= Xml::submitButton( $this->getSubmitText(), $attribs ) . "\n";
+
+ // Buttons are top-level form elements in table and div layouts,
+ // but vform wants all elements inside divs to get spaced-out block
+ // styling.
+ if ( $this->isVForm() ) {
+ $html = Html::rawElement( 'div', null, "\n$html\n" );
+ }
+ }
if ( $this->mShowReset ) {
$html .= Html::element(
@@ -729,6 +788,8 @@ class HTMLForm extends ContextSource {
$html .= Html::element( 'input', $attrs );
}
+ $html .= '</span>';
+
return $html;
}
@@ -737,7 +798,7 @@ class HTMLForm extends ContextSource {
* @return String
*/
function getBody() {
- return $this->displaySection( $this->mFieldTree );
+ return $this->displaySection( $this->mFieldTree, $this->mTableId );
}
/**
@@ -852,6 +913,33 @@ class HTMLForm extends ContextSource {
}
/**
+ * Stop a default submit button being shown for this form. This implies that an
+ * alternate submit method must be provided manually.
+ *
+ * @since 1.22
+ *
+ * @param bool $suppressSubmit Set to false to re-enable the button again
+ *
+ * @return HTMLForm $this for chaining calls
+ */
+ function suppressDefaultSubmit( $suppressSubmit = true ) {
+ $this->mShowSubmit = !$suppressSubmit;
+ return $this;
+ }
+
+ /**
+ * Set the id of the \<table\> or outermost \<div\> element.
+ *
+ * @since 1.22
+ * @param string $id new value of the id attribute, or "" to remove
+ * @return HTMLForm $this for chaining calls
+ */
+ public function setTableId( $id ) {
+ $this->mTableId = $id;
+ return $this;
+ }
+
+ /**
* @param string $id DOM id for the form
* @return HTMLForm $this for chaining calls (since 1.20)
*/
@@ -859,10 +947,12 @@ class HTMLForm extends ContextSource {
$this->mId = $id;
return $this;
}
+
/**
* Prompt the whole form to be wrapped in a "<fieldset>", with
* this text as its "<legend>" element.
- * @param string $legend HTML to go inside the "<legend>" element.
+ * @param string|false $legend HTML to go inside the "<legend>" element, or
+ * false for no <legend>
* Will be escaped
* @return HTMLForm $this for chaining calls (since 1.20)
*/
@@ -931,19 +1021,30 @@ class HTMLForm extends ContextSource {
/**
* @todo Document
- * @param $fields array[]|HTMLFormField[] array of fields (either arrays or objects)
+ * @param array[]|HTMLFormField[] $fields array of fields (either arrays or objects)
* @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
+ * @param boolean &$hasUserVisibleFields Whether the section had user-visible fields
* @return String
*/
- public function displaySection( $fields, $sectionName = '', $fieldsetIDPrefix = '' ) {
+ public function displaySection( $fields, $sectionName = '', $fieldsetIDPrefix = '', &$hasUserVisibleFields = false ) {
$displayFormat = $this->getDisplayFormat();
$html = '';
$subsectionHtml = '';
$hasLabel = false;
- $getFieldHtmlMethod = ( $displayFormat == 'table' ) ? 'getTableRow' : 'get' . ucfirst( $displayFormat );
+ switch( $displayFormat ) {
+ case 'table':
+ $getFieldHtmlMethod = 'getTableRow';
+ break;
+ case 'vform':
+ // Close enough to a div.
+ $getFieldHtmlMethod = 'getDiv';
+ break;
+ default:
+ $getFieldHtmlMethod = 'get' . ucfirst( $displayFormat );
+ }
foreach ( $fields as $key => $value ) {
if ( $value instanceof HTMLFormField ) {
@@ -956,20 +1057,38 @@ class HTMLForm extends ContextSource {
if ( $labelValue != '&#160;' && $labelValue !== '' ) {
$hasLabel = true;
}
- } elseif ( is_array( $value ) ) {
- $section = $this->displaySection( $value, $key, "$fieldsetIDPrefix$key-" );
- $legend = $this->getLegend( $key );
- if ( isset( $this->mSectionHeaders[$key] ) ) {
- $section = $this->mSectionHeaders[$key] . $section;
- }
- if ( isset( $this->mSectionFooters[$key] ) ) {
- $section .= $this->mSectionFooters[$key];
+
+ if ( get_class( $value ) !== 'HTMLHiddenField' &&
+ get_class( $value ) !== 'HTMLApiField' ) {
+ $hasUserVisibleFields = true;
}
- $attributes = array();
- if ( $fieldsetIDPrefix ) {
- $attributes['id'] = Sanitizer::escapeId( "$fieldsetIDPrefix$key" );
+ } elseif ( is_array( $value ) ) {
+ $subsectionHasVisibleFields = false;
+ $section = $this->displaySection( $value, "mw-htmlform-$key", "$fieldsetIDPrefix$key-", $subsectionHasVisibleFields );
+ $legend = null;
+
+ if ( $subsectionHasVisibleFields === true ) {
+ // Display the section with various niceties.
+ $hasUserVisibleFields = true;
+
+ $legend = $this->getLegend( $key );
+
+ if ( isset( $this->mSectionHeaders[$key] ) ) {
+ $section = $this->mSectionHeaders[$key] . $section;
+ }
+ if ( isset( $this->mSectionFooters[$key] ) ) {
+ $section .= $this->mSectionFooters[$key];
+ }
+
+ $attributes = array();
+ if ( $fieldsetIDPrefix ) {
+ $attributes['id'] = Sanitizer::escapeId( "$fieldsetIDPrefix$key" );
+ }
+ $subsectionHtml .= Xml::fieldset( $legend, $section, $attributes ) . "\n";
+ } else {
+ // Just return the inputs, nothing fancy.
+ $subsectionHtml .= $section;
}
- $subsectionHtml .= Xml::fieldset( $legend, $section, $attributes ) . "\n";
}
}
@@ -985,13 +1104,13 @@ class HTMLForm extends ContextSource {
);
if ( $sectionName ) {
- $attribs['id'] = Sanitizer::escapeId( "mw-htmlform-$sectionName" );
+ $attribs['id'] = Sanitizer::escapeId( $sectionName );
}
if ( $displayFormat === 'table' ) {
$html = Html::rawElement( 'table', $attribs,
Html::rawElement( 'tbody', array(), "\n$html\n" ) ) . "\n";
- } elseif ( $displayFormat === 'div' ) {
+ } elseif ( $displayFormat === 'div' || $displayFormat === 'vform' ) {
$html = Html::rawElement( 'div', $attribs, "\n$html\n" );
}
}
@@ -1074,6 +1193,32 @@ class HTMLForm extends ContextSource {
return $this;
}
+ /**
+ * Get the value for the action attribute of the form.
+ *
+ * @since 1.22
+ *
+ * @return string
+ */
+ public function getAction() {
+ global $wgScript, $wgArticlePath;
+
+ // If an action is alredy provided, return it
+ if ( $this->mAction !== false ) {
+ return $this->mAction;
+ }
+
+ // Check whether we are in GET mode and $wgArticlePath contains a "?"
+ // meaning that getLocalURL() would return something like "index.php?title=...".
+ // As browser remove the query string before submitting GET forms,
+ // it means that the title would be lost. In such case use $wgScript instead
+ // and put title in an hidden field (see getHiddenFields()).
+ if ( strpos( $wgArticlePath, '?' ) !== false && $this->getMethod() === 'get' ) {
+ return $wgScript;
+ }
+
+ return $this->getTitle()->getLocalURL();
+ }
}
/**
@@ -1092,6 +1237,12 @@ abstract class HTMLFormField {
protected $mDefault;
/**
+ * @var bool If true will generate an empty div element with no label
+ * @since 1.22
+ */
+ protected $mShowEmptyLabels = true;
+
+ /**
* @var HTMLForm
*/
public $mParent;
@@ -1167,6 +1318,18 @@ abstract class HTMLFormField {
}
/**
+ * Tell the field whether to generate a separate label element if its label
+ * is blank.
+ *
+ * @since 1.22
+ * @param bool $show Set to false to not generate a label.
+ * @return void
+ */
+ public function setShowEmptyLabel( $show ) {
+ $this->mShowEmptyLabels = $show;
+ }
+
+ /**
* Get the value that this input has been set to from a posted form,
* or the input's default value if it has not been set.
* @param $request WebRequest
@@ -1183,6 +1346,8 @@ abstract class HTMLFormField {
/**
* Initialise the object
* @param array $params Associative Array. See HTMLForm doc for syntax.
+ *
+ * @since 1.22 The 'label' attribute no longer accepts raw HTML, use 'label-raw' instead
* @throws MWException
*/
function __construct( $params ) {
@@ -1201,7 +1366,14 @@ abstract class HTMLFormField {
$this->mLabel = wfMessage( $msg, $msgInfo )->parse();
} elseif ( isset( $params['label'] ) ) {
- $this->mLabel = $params['label'];
+ if ( $params['label'] === '&#160;' ) {
+ // Apparently some things set &nbsp directly and in an odd format
+ $this->mLabel = '&#160;';
+ } else {
+ $this->mLabel = htmlspecialchars( $params['label'] );
+ }
+ } elseif ( isset( $params['label-raw'] ) ) {
+ $this->mLabel = $params['label-raw'];
}
$this->mName = "wp{$params['fieldname']}";
@@ -1246,6 +1418,10 @@ abstract class HTMLFormField {
if ( isset( $params['flatlist'] ) ) {
$this->mClass .= ' mw-htmlform-flatlist';
}
+
+ if ( isset( $params['hidelabel'] ) ) {
+ $this->mShowEmptyLabels = false;
+ }
}
/**
@@ -1306,13 +1482,22 @@ abstract class HTMLFormField {
$cellAttributes = array();
$label = $this->getLabelHtml( $cellAttributes );
+ $outerDivClass = array(
+ 'mw-input',
+ 'mw-htmlform-nolabel' => ( $label === '' )
+ );
+
$field = Html::rawElement(
'div',
- array( 'class' => 'mw-input' ) + $cellAttributes,
+ array( 'class' => $outerDivClass ) + $cellAttributes,
$inputHtml . "\n$errors"
);
+ $divCssClasses = array( "mw-htmlform-field-$fieldType", $this->mClass, $errorClass );
+ if ( $this->mParent->isVForm() ) {
+ $divCssClasses[] = 'mw-ui-vform-div';
+ }
$html = Html::rawElement( 'div',
- array( 'class' => "mw-htmlform-field-$fieldType {$this->mClass} $errorClass" ),
+ array( 'class' => $divCssClasses ),
$label . $field );
$html .= $helptext;
return $html;
@@ -1437,7 +1622,7 @@ abstract class HTMLFormField {
}
function getLabel() {
- return $this->mLabel;
+ return is_null( $this->mLabel ) ? '' : $this->mLabel;
}
function getLabelHtml( $cellAttributes = array() ) {
@@ -1449,20 +1634,32 @@ abstract class HTMLFormField {
$for['for'] = $this->mID;
}
+ $labelValue = trim( $this->getLabel() );
+ $hasLabel = false;
+ if ( $labelValue !== '&#160;' && $labelValue !== '' ) {
+ $hasLabel = true;
+ }
+
$displayFormat = $this->mParent->getDisplayFormat();
- $labelElement = Html::rawElement( 'label', $for, $this->getLabel() );
+ $html = '';
- if ( $displayFormat == 'table' ) {
- return Html::rawElement( 'td', array( 'class' => 'mw-label' ) + $cellAttributes,
- Html::rawElement( 'label', $for, $this->getLabel() )
+ if ( $displayFormat === 'table' ) {
+ $html = Html::rawElement( 'td', array( 'class' => 'mw-label' ) + $cellAttributes,
+ Html::rawElement( 'label', $for, $labelValue )
);
- } elseif ( $displayFormat == 'div' ) {
- return Html::rawElement( 'div', array( 'class' => 'mw-label' ) + $cellAttributes,
- Html::rawElement( 'label', $for, $this->getLabel() )
- );
- } else {
- return $labelElement;
+ } elseif ( $hasLabel || $this->mShowEmptyLabels ) {
+ if ( $displayFormat === 'div' ) {
+ $html = Html::rawElement(
+ 'div',
+ array( 'class' => 'mw-label' ) + $cellAttributes,
+ Html::rawElement( 'label', $for, $labelValue )
+ );
+ } else {
+ $html = Html::rawElement( 'label', $for, $labelValue );
+ }
}
+
+ return $html;
}
function getDefault() {
@@ -1602,16 +1799,19 @@ class HTMLTextField extends HTMLFormField {
}
}
class HTMLTextAreaField extends HTMLFormField {
+ const DEFAULT_COLS = 80;
+ const DEFAULT_ROWS = 25;
+
function getCols() {
return isset( $this->mParams['cols'] )
? $this->mParams['cols']
- : 80;
+ : static::DEFAULT_COLS;
}
function getRows() {
return isset( $this->mParams['rows'] )
? $this->mParams['rows']
- : 25;
+ : static::DEFAULT_ROWS;
}
function getInputHTML( $value ) {
@@ -1741,8 +1941,25 @@ class HTMLCheckField extends HTMLFormField {
$attr['class'] = $this->mClass;
}
- return Xml::check( $this->mName, $value, $attr ) . '&#160;' .
- Html::rawElement( 'label', array( 'for' => $this->mID ), $this->mLabel );
+ if ( $this->mParent->isVForm() ) {
+ // Nest checkbox inside label.
+ return Html::rawElement(
+ 'label',
+ array(
+ 'class' => 'mw-ui-checkbox-label'
+ ),
+ Xml::check(
+ $this->mName,
+ $value,
+ $attr
+ ) .
+ // Html:rawElement doesn't escape contents.
+ htmlspecialchars( $this->mLabel )
+ );
+ } else {
+ return Xml::check( $this->mName, $value, $attr ) . '&#160;' .
+ Html::rawElement( 'label', array( 'for' => $this->mID ), $this->mLabel );
+ }
}
/**
@@ -1755,6 +1972,13 @@ class HTMLCheckField extends HTMLFormField {
}
/**
+ * checkboxes don't need a label.
+ */
+ protected function needsLabel() {
+ return false;
+ }
+
+ /**
* @param $request WebRequest
* @return String
*/
@@ -1786,9 +2010,39 @@ 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.
+ * construct a matrix of options. The tags used to identify a particular cell
+ * are of the form "columnName-rowName"
+ *
+ * Options:
+ * - columns
+ * - Required list of columns in the matrix.
+ * - rows
+ * - Required list of rows in the matrix.
+ * - force-options-on
+ * - Accepts array of column-row tags to be displayed as enabled but unavailable to change
+ * - force-options-off
+ * - Accepts array of column-row tags to be displayed as disabled but unavailable to change.
+ * - tooltips
+ * - Optional array mapping row label to tooltip content
+ * - tooltip-class
+ * - Optional CSS class used on tooltip container span. Defaults to mw-icon-question.
*/
-class HTMLCheckMatrix extends HTMLFormField {
+class HTMLCheckMatrix extends HTMLFormField implements HTMLNestedFilterable {
+
+ static private $requiredParams = array(
+ // Required by underlying HTMLFormField
+ 'fieldname',
+ // Required by HTMLCheckMatrix
+ 'rows', 'columns'
+ );
+
+ public function __construct( $params ) {
+ $missing = array_diff( self::$requiredParams, array_keys( $params ) );
+ if ( $missing ) {
+ throw new HTMLFormFieldRequiredOptionsException( $this, $missing );
+ }
+ parent::__construct( $params );
+ }
function validate( $value, $alldata ) {
$rows = $this->mParams['rows'];
@@ -1848,27 +2102,42 @@ class HTMLCheckMatrix extends HTMLFormField {
}
$tableContents .= Html::rawElement( 'tr', array(), "\n$headerContents\n" );
+ $tooltipClass = 'mw-icon-question';
+ if ( isset( $this->mParams['tooltip-class'] ) ) {
+ $tooltipClass = $this->mParams['tooltip-class'];
+ }
+
// Build the options matrix
foreach ( $rows as $rowLabel => $rowTag ) {
+ // Append tooltip if configured
+ if ( isset( $this->mParams['tooltips'][$rowLabel] ) ) {
+ $tooltipAttribs = array(
+ 'class' => "mw-htmlform-tooltip $tooltipClass",
+ 'title' => $this->mParams['tooltips'][$rowLabel],
+ );
+ $rowLabel .= ' ' . Html::element( 'span', $tooltipAttribs, '' );
+ }
$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 );
+ $thisTag = "$columnTag-$rowTag";
+ // Construct the checkbox
+ $thisAttribs = array(
+ 'id' => "{$this->mID}-$thisTag",
+ 'value' => $thisTag,
+ );
+ $checked = in_array( $thisTag, (array)$value, true );
+ if ( $this->isTagForcedOff( $thisTag ) ) {
+ $checked = false;
+ $thisAttribs['disabled'] = 1;
+ } elseif ( $this->isTagForcedOn( $thisTag ) ) {
+ $checked = true;
+ $thisAttribs['disabled'] = 1;
}
+ $rowContents .= Html::rawElement(
+ 'td',
+ array(),
+ Xml::check( "{$this->mName}[]", $checked, $attribs + $thisAttribs )
+ );
}
$tableContents .= Html::rawElement( 'tr', array(), "\n$rowContents\n" );
}
@@ -1880,6 +2149,16 @@ class HTMLCheckMatrix extends HTMLFormField {
return $html;
}
+ protected function isTagForcedOff( $tag ) {
+ return isset( $this->mParams['force-options-off'] )
+ && in_array( $tag, $this->mParams['force-options-off'] );
+ }
+
+ protected function isTagForcedOn( $tag ) {
+ return isset( $this->mParams['force-options-on'] )
+ && in_array( $tag, $this->mParams['force-options-on'] );
+ }
+
/**
* Get the complete table row for the input, including help text,
* labels, and whatever.
@@ -1944,6 +2223,27 @@ class HTMLCheckMatrix extends HTMLFormField {
return array();
}
}
+
+ function filterDataForSubmit( $data ) {
+ $columns = HTMLFormField::flattenOptions( $this->mParams['columns'] );
+ $rows = HTMLFormField::flattenOptions( $this->mParams['rows'] );
+ $res = array();
+ foreach ( $columns as $column ) {
+ foreach ( $rows as $row ) {
+ // Make sure option hasn't been forced
+ $thisTag = "$column-$row";
+ if ( $this->isTagForcedOff( $thisTag ) ) {
+ $res[$thisTag] = false;
+ } elseif ( $this->isTagForcedOn( $thisTag ) ) {
+ $res[$thisTag] = true;
+ } else {
+ $res[$thisTag] = in_array( $thisTag, $data );
+ }
+ }
+ }
+
+ return $res;
+ }
}
/**
@@ -1959,10 +2259,11 @@ class HTMLSelectField extends HTMLFormField {
$validOptions = HTMLFormField::flattenOptions( $this->mParams['options'] );
- if ( in_array( $value, $validOptions ) )
+ if ( in_array( $value, $validOptions ) ) {
return true;
- else
+ } else {
return $this->msg( 'htmlform-select-badoption' )->parse();
+ }
}
function getInputHTML( $value ) {
@@ -1996,7 +2297,6 @@ class HTMLSelectField extends HTMLFormField {
* Select dropdown field, with an additional "other" textbox.
*/
class HTMLSelectOrOtherField extends HTMLTextField {
- static $jsAdded = false;
function __construct( $params ) {
if ( !in_array( 'other', $params['options'], true ) ) {
@@ -2085,7 +2385,7 @@ class HTMLSelectOrOtherField extends HTMLTextField {
/**
* Multi-select field
*/
-class HTMLMultiSelectField extends HTMLFormField {
+class HTMLMultiSelectField extends HTMLFormField implements HTMLNestedFilterable {
function validate( $value, $alldata ) {
$p = parent::validate( $value, $alldata );
@@ -2178,6 +2478,17 @@ class HTMLMultiSelectField extends HTMLFormField {
}
}
+ function filterDataForSubmit( $data ) {
+ $options = HTMLFormField::flattenOptions( $this->mParams['options'] );
+
+ $res = array();
+ foreach ( $options as $opt ) {
+ $res["$opt"] = in_array( $opt, $data );
+ }
+
+ return $res;
+ }
+
protected function needsLabel() {
return false;
}
@@ -2512,14 +2823,28 @@ class HTMLHiddenField extends HTMLFormField {
return $this->getTableRow( $value );
}
- public function getInputHTML( $value ) { return ''; }
+ public function getInputHTML( $value ) {
+ return '';
+ }
}
/**
* Add a submit button inline in the form (as opposed to
* HTMLForm::addButton(), which will add it at the end).
*/
-class HTMLSubmitField extends HTMLFormField {
+class HTMLSubmitField extends HTMLButtonField {
+ protected $buttonType = 'submit';
+}
+
+/**
+ * Adds a generic button inline to the form. Does not do anything, you must add
+ * click handling code in JavaScript. Use a HTMLSubmitField if you merely
+ * wish to add a submit button to a form.
+ *
+ * @since 1.22
+ */
+class HTMLButtonField extends HTMLFormField {
+ protected $buttonType = 'button';
public function __construct( $info ) {
$info['nodata'] = true;
@@ -2527,13 +2852,20 @@ class HTMLSubmitField extends HTMLFormField {
}
public function getInputHTML( $value ) {
- return Xml::submitButton(
+ $attr = array(
+ 'class' => 'mw-htmlform-submit ' . $this->mClass,
+ 'id' => $this->mID,
+ );
+
+ if ( !empty( $this->mParams['disabled'] ) ) {
+ $attr['disabled'] = 'disabled';
+ }
+
+ return Html::input(
+ $this->mName,
$value,
- array(
- 'class' => 'mw-htmlform-submit ' . $this->mClass,
- 'name' => $this->mName,
- 'id' => $this->mID,
- )
+ $this->buttonType,
+ $attr
);
}
@@ -2612,3 +2944,22 @@ class HTMLApiField extends HTMLFormField {
return '';
}
}
+
+interface HTMLNestedFilterable {
+ /**
+ * Support for seperating multi-option preferences into multiple preferences
+ * Due to lack of array support.
+ * @param $data array
+ */
+ function filterDataForSubmit( $data );
+}
+
+class HTMLFormFieldRequiredOptionsException extends MWException {
+ public function __construct( HTMLFormField $field, array $missing ) {
+ parent::__construct( sprintf(
+ "Form type `%s` expected the following parameters to be set: %s",
+ get_class( $field ),
+ implode( ', ', $missing )
+ ) );
+ }
+}
diff --git a/includes/HashRing.php b/includes/HashRing.php
new file mode 100644
index 00000000..930f8c0a
--- /dev/null
+++ b/includes/HashRing.php
@@ -0,0 +1,142 @@
+<?php
+/**
+ * Convenience class for weighted consistent hash rings.
+ *
+ * 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 weighted consistent hash rings
+ *
+ * @since 1.22
+ */
+class HashRing {
+ /** @var Array (location => weight) */
+ protected $sourceMap = array();
+ /** @var Array (location => (start, end)) */
+ protected $ring = array();
+
+ const RING_SIZE = 268435456; // 2^28
+
+ /**
+ * @param array $map (location => weight)
+ */
+ public function __construct( array $map ) {
+ $map = array_filter( $map, function( $w ) { return $w > 0; } );
+ if ( !count( $map ) ) {
+ throw new MWException( "Ring is empty or all weights are zero." );
+ }
+ $this->sourceMap = $map;
+ // Sort the locations based on the hash of their names
+ $hashes = array();
+ foreach ( $map as $location => $weight ) {
+ $hashes[$location] = sha1( $location );
+ }
+ uksort( $map, function ( $a, $b ) use ( $hashes ) {
+ return strcmp( $hashes[$a], $hashes[$b] );
+ } );
+ // Fit the map to weight-proportionate one with a space of size RING_SIZE
+ $sum = array_sum( $map );
+ $standardMap = array();
+ foreach ( $map as $location => $weight ) {
+ $standardMap[$location] = (int)floor( $weight / $sum * self::RING_SIZE );
+ }
+ // Build a ring of RING_SIZE spots, with each location at a spot in location hash order
+ $index = 0;
+ foreach ( $standardMap as $location => $weight ) {
+ // Location covers half-closed interval [$index,$index + $weight)
+ $this->ring[$location] = array( $index, $index + $weight );
+ $index += $weight;
+ }
+ // Make sure the last location covers what is left
+ end( $this->ring );
+ $this->ring[key( $this->ring )][1] = self::RING_SIZE;
+ }
+
+ /**
+ * Get the location of an item on the ring
+ *
+ * @param string $item
+ * @return string Location
+ */
+ public function getLocation( $item ) {
+ $locations = $this->getLocations( $item, 1 );
+ return $locations[0];
+ }
+
+ /**
+ * Get the location of an item on the ring, as well as the next clockwise locations
+ *
+ * @param string $item
+ * @param integer $limit Maximum number of locations to return
+ * @return array List of locations
+ */
+ public function getLocations( $item, $limit ) {
+ $locations = array();
+ $primaryLocation = null;
+ $spot = hexdec( substr( sha1( $item ), 0, 7 ) ); // first 28 bits
+ foreach ( $this->ring as $location => $range ) {
+ if ( count( $locations ) >= $limit ) {
+ break;
+ }
+ // The $primaryLocation is the location the item spot is in.
+ // After that is reached, keep appending the next locations.
+ if ( ( $range[0] <= $spot && $spot < $range[1] ) || $primaryLocation !== null ) {
+ if ( $primaryLocation === null ) {
+ $primaryLocation = $location;
+ }
+ $locations[] = $location;
+ }
+ }
+ // If more locations are requested, wrap-around and keep adding them
+ reset( $this->ring );
+ while ( count( $locations ) < $limit ) {
+ list( $location, ) = each( $this->ring );
+ if ( $location === $primaryLocation ) {
+ break; // don't go in circles
+ }
+ $locations[] = $location;
+ }
+ return $locations;
+ }
+
+ /**
+ * Get the map of locations to weight (ignores 0-weight items)
+ *
+ * @return array
+ */
+ public function getLocationWeights() {
+ return $this->sourceMap;
+ }
+
+ /**
+ * Get a new hash ring with a location removed from the ring
+ *
+ * @param string $location
+ * @return HashRing|bool Returns false if no non-zero weighted spots are left
+ */
+ public function newWithoutLocation( $location ) {
+ $map = $this->sourceMap;
+ unset( $map[$location] );
+ if ( count( $map ) ) {
+ return new self( $map );
+ }
+ return false;
+ }
+}
diff --git a/includes/HistoryBlob.php b/includes/HistoryBlob.php
index 1af733a2..31aa0f87 100644
--- a/includes/HistoryBlob.php
+++ b/includes/HistoryBlob.php
@@ -143,7 +143,7 @@ class ConcatenatedGzipHistoryBlob implements HistoryBlob
* Compress the bulk data in the object
*/
public function compress() {
- if ( !$this->mCompressed ) {
+ if ( !$this->mCompressed ) {
$this->mItems = gzdeflate( serialize( $this->mItems ) );
$this->mCompressed = true;
}
@@ -231,16 +231,16 @@ class HistoryBlobStub {
* @return string
*/
function getText() {
- if( isset( self::$blobCache[$this->mOldId] ) ) {
+ if ( isset( self::$blobCache[$this->mOldId] ) ) {
$obj = self::$blobCache[$this->mOldId];
} else {
$dbr = wfGetDB( DB_SLAVE );
$row = $dbr->selectRow( 'text', array( 'old_flags', 'old_text' ), array( 'old_id' => $this->mOldId ) );
- if( !$row ) {
+ if ( !$row ) {
return false;
}
$flags = explode( ',', $row->old_flags );
- if( in_array( 'external', $flags ) ) {
+ if ( in_array( 'external', $flags ) ) {
$url = $row->old_text;
$parts = explode( '://', $url, 2 );
if ( !isset( $parts[1] ) || $parts[1] == '' ) {
@@ -249,11 +249,11 @@ class HistoryBlobStub {
$row->old_text = ExternalStore::fetchFromUrl( $url );
}
- if( !in_array( 'object', $flags ) ) {
+ if ( !in_array( 'object', $flags ) ) {
return false;
}
- if( in_array( 'gzip', $flags ) ) {
+ if ( in_array( 'gzip', $flags ) ) {
// This shouldn't happen, but a bug in the compress script
// may at times gzip-compress a HistoryBlob object row.
$obj = unserialize( gzinflate( $row->old_text ) );
@@ -261,7 +261,7 @@ class HistoryBlobStub {
$obj = unserialize( $row->old_text );
}
- if( !is_object( $obj ) ) {
+ if ( !is_object( $obj ) ) {
// Correct for old double-serialization bug.
$obj = unserialize( $obj );
}
@@ -318,7 +318,7 @@ class HistoryBlobCurStub {
function getText() {
$dbr = wfGetDB( DB_SLAVE );
$row = $dbr->selectRow( 'cur', array( 'cur_text' ), array( 'cur_id' => $this->mCurId ) );
- if( !$row ) {
+ if ( !$row ) {
return false;
}
return $row->cur_text;
@@ -530,7 +530,7 @@ class DiffHistoryBlob implements HistoryBlob {
$header = unpack( 'Vofp/Vcsize', substr( $diff, 0, 8 ) );
- # Check the checksum if hash/mhash is available
+ # Check the checksum if hash extension is available
$ofp = $this->xdiffAdler32( $base );
if ( $ofp !== false && $ofp !== substr( $diff, 0, 4 ) ) {
wfDebug( __METHOD__ . ": incorrect base checksum\n" );
@@ -577,24 +577,23 @@ class DiffHistoryBlob implements HistoryBlob {
* 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
+ * @param string $s
+ * @return string|bool: false if the hash extension is not available
*/
function xdiffAdler32( $s ) {
+ if ( !function_exists( 'hash' ) ) {
+ return false;
+ }
+
static $init;
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
// algorithm. Appending the subject string then simulates LibXDiff.
- if ( function_exists( 'hash' ) ) {
- $hash = hash( 'adler32', $init . $s, true );
- } elseif ( function_exists( 'mhash' ) ) {
- $hash = mhash( MHASH_ADLER32, $init . $s );
- } else {
- return false;
- }
- return strrev( $hash );
+ return strrev( hash( 'adler32', $init . $s, true ) );
}
function uncompress() {
diff --git a/includes/Hooks.php b/includes/Hooks.php
index 8cc7acea..396e360d 100644
--- a/includes/Hooks.php
+++ b/includes/Hooks.php
@@ -1,4 +1,5 @@
<?php
+
/**
* A tool for running hook functions.
*
@@ -37,40 +38,43 @@ class MWHookException extends MWException {}
*/
class Hooks {
+ /**
+ * Array of events mapped to an array of callbacks to be run
+ * when that event is triggered.
+ */
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.
+ * Attach an event handler to a given hook.
*
- * @since 1.21
- *
- * @param string $name the name of the hook to clear.
+ * @param string $name Name of hook
+ * @param mixed $callback Callback function to attach
*
- * @throws MWException if not in testing mode.
+ * @since 1.18
*/
- public static function clear( $name ) {
- if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
- throw new MWException( 'can not reset hooks in operation.' );
+ public static function register( $name, $callback ) {
+ if ( !isset( self::$handlers[$name] ) ) {
+ self::$handlers[$name] = array();
}
- unset( self::$handlers[$name] );
+ self::$handlers[$name][] = $callback;
}
/**
- * Attach an event handler to a given hook
+ * 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.18
+ * @param string $name the name of the hook to clear.
*
- * @param string $name name of hook
- * @param $callback Mixed: callback function to attach
+ * @since 1.21
+ * @throws MWException if not in testing mode.
*/
- public static function register( $name, $callback ) {
- if( !isset( self::$handlers[$name] ) ) {
- self::$handlers[$name] = array();
+ public static function clear( $name ) {
+ if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
+ throw new MWException( 'Cannot reset hooks in operation.' );
}
- self::$handlers[$name][] = $callback;
+ unset( self::$handlers[$name] );
}
/**
@@ -79,216 +83,139 @@ class Hooks {
*
* @since 1.18
*
- * @param string $name name of hook
- * @return Boolean: true if the hook has a function registered to it
+ * @param string $name Name of hook
+ * @return bool True if the hook has a function registered to it
*/
public static function isRegistered( $name ) {
global $wgHooks;
-
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
+ * @since 1.18
*
+ * @param string $name Name of the hook
* @return array
*/
public static function getHandlers( $name ) {
global $wgHooks;
- // Return quickly in the most common case
- if ( empty( self::$handlers[$name] ) && empty( $wgHooks[$name] ) ) {
+ if ( !self::isRegistered( $name ) ) {
return array();
- }
-
- 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];
+ } elseif ( !isset( self::$handlers[$name] ) ) {
+ return $wgHooks[$name];
+ } elseif ( !isset( $wgHooks[$name] ) ) {
+ return self::$handlers[$name];
} else {
- // so they are both not empty...
- $hooks = array_merge( Hooks::$handlers[$name], $wgHooks[$name] );
+ return array_merge( self::$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
+ * Call hook functions defined in Hooks::register and $wgHooks.
*
- * @param string $event event name
- * @param $args Array: parameters passed to hook functions
+ * For a certain hook event, fetch the array of hook events and
+ * process them. Determine the proper callback for each hook and
+ * then call the actual hook using the appropriate arguments.
+ * Finally, process the return value and return/throw accordingly.
*
+ * @param string $event Event name
+ * @param array $args Array of parameters passed to hook functions
+ * @return bool True if no handler aborted the hook
+ *
+ * @since 1.22 A hook function is not required to return a value for
+ * processing to continue. Not returning a value (or explicitly
+ * returning null) is equivalent to returning true.
* @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 ( empty( self::$handlers[$event] ) && empty( $wgHooks[$event] ) ) {
- return true;
- }
-
+ public static function run( $event, array $args = array() ) {
wfProfileIn( 'hook: ' . $event );
- $hooks = self::getHandlers( $event );
+ foreach ( self::getHandlers( $event ) as $hook ) {
+ // Turn non-array values into an array. (Can't use casting because of objects.)
+ if ( !is_array( $hook ) ) {
+ $hook = array( $hook );
+ }
- foreach ( $hooks as $hook ) {
- $object = null;
- $method = null;
- $func = null;
- $data = null;
- $have_data = false;
- $closure = false;
- $badhookmsg = false;
+ if ( !array_filter( $hook ) ) {
+ // Either array is empty or it's an array filled with null/false/empty.
+ continue;
+ } elseif ( is_array( $hook[0] ) ) {
+ // First element is an array, meaning the developer intended
+ // the first element to be a callback. Merge it in so that
+ // processing can be uniform.
+ $hook = array_merge( $hook[0], array_slice( $hook, 1 ) );
+ }
/**
* $hook can be: a function, an object, an array of $function and
* $data, an array of just a function, an array of object and
* method, or an array of object, method, and data.
*/
- if ( is_array( $hook ) ) {
- if ( count( $hook ) < 1 ) {
- throw new MWException( 'Empty array in hooks for ' . $event . "\n" );
- } elseif ( is_object( $hook[0] ) ) {
- $object = $hook[0];
- if ( $object instanceof Closure ) {
- $closure = true;
- if ( count( $hook ) > 1 ) {
- $data = $hook[1];
- $have_data = true;
- }
- } else {
- if ( count( $hook ) < 2 ) {
- $method = 'on' . $event;
- } else {
- $method = $hook[1];
- if ( count( $hook ) > 2 ) {
- $data = $hook[2];
- $have_data = true;
- }
- }
- }
- } elseif ( is_string( $hook[0] ) ) {
- $func = $hook[0];
- if ( count( $hook ) > 1) {
- $data = $hook[1];
- $have_data = true;
- }
- } else {
- throw new MWException( 'Unknown datatype in hooks for ' . $event . "\n" );
- }
- } elseif ( is_string( $hook ) ) { # functions look like strings, too
- $func = $hook;
- } elseif ( is_object( $hook ) ) {
- $object = $hook;
- if ( $object instanceof Closure ) {
- $closure = true;
- } else {
- $method = "on" . $event;
+ if ( $hook[0] instanceof Closure ) {
+ $func = "hook-$event-closure";
+ $callback = array_shift( $hook );
+ } elseif ( is_object( $hook[0] ) ) {
+ $object = array_shift( $hook );
+ $method = array_shift( $hook );
+
+ // If no method was specified, default to on$event.
+ if ( $method === null ) {
+ $method = "on$event";
}
- } else {
- throw new MWException( 'Unknown datatype in hooks for ' . $event . "\n" );
- }
-
- /* We put the first data element on, if needed. */
- if ( $have_data ) {
- $hook_args = array_merge( array( $data ), $args );
- } else {
- $hook_args = $args;
- }
- if ( $closure ) {
- $callback = $object;
- $func = "hook-$event-closure";
- } elseif ( isset( $object ) ) {
$func = get_class( $object ) . '::' . $method;
$callback = array( $object, $method );
+ } elseif ( is_string( $hook[0] ) ) {
+ $func = $callback = array_shift( $hook );
} else {
- $callback = $func;
+ throw new MWException( 'Unknown datatype in hooks for ' . $event . "\n" );
}
// Run autoloader (workaround for call_user_func_array bug)
- is_callable( $callback );
+ // and throw error if not callable.
+ if ( !is_callable( $callback ) ) {
+ throw new MWException( 'Invalid callback in hooks for ' . $event . "\n" );
+ }
- /**
- * Call the hook. The documentation of call_user_func_array clearly
- * states that FALSE is returned on failure. However this is not
- * case always. In some version of PHP if the function signature
- * does not match the call signature, PHP will issue an warning:
- * Param y in x expected to be a reference, value given.
- *
- * In that case the call will also return null. The following code
- * catches that warning and provides better error message. The
- * function documentation also says that:
- * In other words, it does not depend on the function signature
- * whether the parameter is passed by a value or by a reference.
- * There is also PHP bug http://bugs.php.net/bug.php?id=47554 which
- * is unsurprisingly marked as bogus. In short handling of failures
- * with call_user_func_array is a failure, the documentation for that
- * function is wrong and misleading and PHP developers don't see any
- * problem here.
+ /*
+ * Call the hook. The documentation of call_user_func_array says
+ * false is returned on failure. However, if the function signature
+ * does not match the call signature, PHP will issue an warning and
+ * return null instead. The following code catches that warning and
+ * provides better error message.
*/
$retval = null;
- set_error_handler( 'Hooks::hookErrorHandler' );
+ $badhookmsg = null;
+ $hook_args = array_merge( $hook, $args );
+
+ // Profile first in case the Profiler causes errors.
wfProfileIn( $func );
+ set_error_handler( 'Hooks::hookErrorHandler' );
try {
$retval = call_user_func_array( $callback, $hook_args );
} catch ( MWHookException $e ) {
$badhookmsg = $e->getMessage();
}
- wfProfileOut( $func );
restore_error_handler();
+ wfProfileOut( $func );
- /* String return is an error; false return means stop processing. */
+ // Process the return value.
if ( is_string( $retval ) ) {
+ // String returned means error.
throw new FatalError( $retval );
- } elseif( $retval === null ) {
- if ( $closure ) {
- $prettyFunc = "$event closure";
- } elseif( is_array( $callback ) ) {
- if( is_object( $callback[0] ) ) {
- $prettyClass = get_class( $callback[0] );
- } else {
- $prettyClass = strval( $callback[0] );
- }
- $prettyFunc = $prettyClass . '::' . strval( $callback[1] );
- } else {
- $prettyFunc = strval( $callback );
- }
- if ( $badhookmsg ) {
- throw new MWException(
- 'Detected bug in an extension! ' .
- "Hook $prettyFunc has invalid call signature; " . $badhookmsg
- );
- } else {
- throw new MWException(
- 'Detected bug in an extension! ' .
- "Hook $prettyFunc failed to return a value; " .
- 'should return true to continue hook processing or false to abort.'
- );
- }
- } elseif ( !$retval ) {
+ } elseif ( $badhookmsg !== null ) {
+ // Exception was thrown from Hooks::hookErrorHandler.
+ throw new MWException(
+ 'Detected bug in an extension! ' .
+ "Hook $func has invalid call signature; " . $badhookmsg
+ );
+ } elseif ( $retval === false ) {
wfProfileOut( 'hook: ' . $event );
+ // False was returned. Stop processing, but no error.
return false;
}
}
@@ -298,18 +225,21 @@ class Hooks {
}
/**
+ * Handle PHP errors issued inside a hook. Catch errors that have to do with
+ * a function expecting a reference, and let all others pass through.
+ *
* This REALLY should be protected... but it's public for compatibility
*
* @since 1.18
*
- * @param int $errno Unused
- * @param string $errstr error message
- * @throws MWHookException
- * @return Boolean: false
+ * @param int $errno Error number (unused)
+ * @param string $errstr Error message
+ * @throws MWHookException If the error has to do with the function signature
+ * @return bool Always returns false
*/
public static function hookErrorHandler( $errno, $errstr ) {
if ( strpos( $errstr, 'expected to be a reference, value given' ) !== false ) {
- throw new MWHookException( $errstr );
+ throw new MWHookException( $errstr, $errno );
}
return false;
}
diff --git a/includes/Html.php b/includes/Html.php
index af4b4bbf..932f753e 100644
--- a/includes/Html.php
+++ b/includes/Html.php
@@ -36,8 +36,8 @@
*
* There are two important configuration options this class uses:
*
- * $wgHtml5: If this is set to false, then all output should be valid XHTML 1.0
- * Transitional.
+ * $wgMimeType: If this is set to an xml mimetype then output should be
+ * valid XHTML5.
* $wgWellFormedXml: If this is set to true, then all output should be
* well-formed XML (quotes on attributes, self-closing tags, etc.).
*
@@ -101,19 +101,6 @@ class Html {
'itemscope',
);
- private static $HTMLFiveOnlyAttribs = array(
- 'autocomplete',
- 'autofocus',
- 'max',
- 'min',
- 'multiple',
- 'pattern',
- 'placeholder',
- 'required',
- 'step',
- 'spellcheck',
- );
-
/**
* Returns an HTML element in a string. The major advantage here over
* manually typing out the HTML is that it will escape all attribute
@@ -177,7 +164,7 @@ class Html {
* @return string
*/
public static function openElement( $element, $attribs = array() ) {
- global $wgHtml5, $wgWellFormedXml;
+ global $wgWellFormedXml;
$attribs = (array)$attribs;
// This is not required in HTML5, but let's do it anyway, for
// consistency and better compression.
@@ -185,7 +172,7 @@ class Html {
// 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>
+ // <http://www.whatwg.org/html/syntax.html#optional-tags>
if ( !$wgWellFormedXml && !$attribs
&& in_array( $element, array( 'html', 'head' ) ) ) {
return '';
@@ -204,36 +191,28 @@ class Html {
'image',
'reset',
'button',
- );
- // Allow more input types in HTML5 mode
- if( $wgHtml5 ) {
- $validTypes = array_merge( $validTypes, array(
- 'datetime',
- 'datetime-local',
- 'date',
- 'month',
- 'time',
- 'week',
- 'number',
- 'range',
- 'email',
- 'url',
- 'search',
- 'tel',
- 'color',
- ) );
- }
+ // HTML input types
+ 'datetime',
+ 'datetime-local',
+ 'date',
+ 'month',
+ 'time',
+ 'week',
+ 'number',
+ 'range',
+ 'email',
+ 'url',
+ 'search',
+ 'tel',
+ 'color',
+ );
if ( isset( $attribs['type'] )
&& !in_array( $attribs['type'], $validTypes ) ) {
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".
@@ -259,7 +238,7 @@ class Html {
$element = strtolower( $element );
// Reference:
- // http://www.whatwg.org/specs/web-apps/current-work/multipage/syntax.html#optional-tags
+ // http://www.whatwg.org/html/syntax.html#optional-tags
if ( !$wgWellFormedXml && in_array( $element, array(
'html',
'head',
@@ -294,12 +273,6 @@ class Html {
* @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.
- global $wgHtml5;
- if ( !$wgHtml5 ) {
- return $attribs;
- }
// Whenever altering this array, please provide a covering test case
// in HtmlTest::provideElementsWithAttributesHavingDefaultValues
@@ -340,7 +313,7 @@ class Html {
foreach ( $attribs as $attrib => $value ) {
$lcattrib = strtolower( $attrib );
- if( is_array( $value ) ) {
+ if ( is_array( $value ) ) {
$value = implode( ' ', $value );
} else {
$value = strval( $value );
@@ -444,11 +417,12 @@ class Html {
* (starting with a space if at least one attribute is output)
*/
public static function expandAttributes( $attribs ) {
- global $wgHtml5, $wgWellFormedXml;
+ global $wgWellFormedXml;
$ret = '';
$attribs = (array)$attribs;
foreach ( $attribs as $key => $value ) {
+ // Support intuitive array( 'checked' => true/false ) form
if ( $value === false || is_null( $value ) ) {
continue;
}
@@ -460,15 +434,10 @@ class Html {
$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 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 ) ) {
- 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
@@ -552,15 +521,12 @@ 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 HTML5, we can leave the value empty. If we don't need
+ // well-formed XML, we can omit the = entirely.
if ( !$wgWellFormedXml ) {
$ret .= " $key";
- } elseif ( $wgHtml5 ) {
- $ret .= " $key=\"\"";
} else {
- $ret .= " $key=\"$key\"";
+ $ret .= " $key=\"\"";
}
} else {
// Apparently we need to entity-encode \n, \r, \t, although the
@@ -602,14 +568,10 @@ class Html {
* @return string Raw HTML
*/
public static function inlineScript( $contents ) {
- global $wgHtml5, $wgJsMimeType, $wgWellFormedXml;
+ global $wgWellFormedXml;
$attrs = array();
- if ( !$wgHtml5 ) {
- $attrs['type'] = $wgJsMimeType;
- }
-
if ( $wgWellFormedXml && preg_match( '/[<&]/', $contents ) ) {
$contents = "/*<![CDATA[*/$contents/*]]>*/";
}
@@ -625,14 +587,8 @@ class Html {
* @return string Raw HTML
*/
public static function linkedScript( $url ) {
- global $wgHtml5, $wgJsMimeType;
-
$attrs = array( 'src' => $url );
- if ( !$wgHtml5 ) {
- $attrs['type'] = $wgJsMimeType;
- }
-
return self::element( 'script', $attrs );
}
@@ -677,8 +633,7 @@ class Html {
/**
* Convenience function to produce an "<input>" element. This supports the
- * new HTML5 input types and attributes, and will silently strip them if
- * $wgHtml5 is false.
+ * new HTML5 input types and attributes.
*
* @param $name string name attribute
* @param $value mixed value attribute
@@ -712,9 +667,7 @@ class Html {
* Convenience function to produce an "<input>" element.
*
* This supports leaving out the cols= and rows= which Xml requires and are
- * required by HTML4/XHTML but not required by HTML5 and will silently set
- * cols="" and rows="" if $wgHtml5 is false and cols and rows are omitted
- * (HTML4 validates present but empty cols="" and rows="" as valid).
+ * required by HTML4/XHTML but not required by HTML5.
*
* @param $name string name attribute
* @param $value string value attribute
@@ -723,20 +676,8 @@ class Html {
* @return string Raw HTML
*/
public static function textarea( $name, $value = '', $attribs = array() ) {
- global $wgHtml5;
-
$attribs['name'] = $name;
- if ( !$wgHtml5 ) {
- if ( !isset( $attribs['cols'] ) ) {
- $attribs['cols'] = "";
- }
-
- if ( !isset( $attribs['rows'] ) ) {
- $attribs['rows'] = "";
- }
- }
-
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
@@ -859,28 +800,29 @@ class Html {
public static function htmlHeader( $attribs = array() ) {
$ret = '';
- global $wgMimeType;
-
- if ( self::isXmlMimeType( $wgMimeType ) ) {
- $ret .= "<?xml version=\"1.0\" encoding=\"UTF-8\" ?" . ">\n";
- }
+ global $wgHtml5Version, $wgMimeType, $wgXhtmlNamespaces;
- global $wgHtml5, $wgHtml5Version, $wgDocType, $wgDTD;
- global $wgXhtmlNamespaces, $wgXhtmlDefaultNamespace;
+ $isXHTML = self::isXmlMimeType( $wgMimeType );
- if ( $wgHtml5 ) {
- $ret .= "<!DOCTYPE html>\n";
+ if ( $isXHTML ) { // XHTML5
+ // XML mimetyped markup should have an xml header.
+ // However a DOCTYPE is not needed.
+ $ret .= "<?xml version=\"1.0\" encoding=\"UTF-8\" ?" . ">\n";
- if ( $wgHtml5Version ) {
- $attribs['version'] = $wgHtml5Version;
- }
- } else {
- $ret .= "<!DOCTYPE html PUBLIC \"$wgDocType\" \"$wgDTD\">\n";
- $attribs['xmlns'] = $wgXhtmlDefaultNamespace;
+ // Add the standard xmlns
+ $attribs['xmlns'] = 'http://www.w3.org/1999/xhtml';
+ // And support custom namespaces
foreach ( $wgXhtmlNamespaces as $tag => $ns ) {
$attribs["xmlns:$tag"] = $ns;
}
+ } else { // HTML5
+ // DOCTYPE
+ $ret .= "<!DOCTYPE html>\n";
+ }
+
+ if ( $wgHtml5Version ) {
+ $attribs['version'] = $wgHtml5Version;
}
$html = Html::openElement( 'html', $attribs );
@@ -901,14 +843,11 @@ class Html {
* @return Boolean
*/
public static function isXmlMimeType( $mimetype ) {
- switch ( $mimetype ) {
- case 'text/xml':
- case 'application/xhtml+xml':
- case 'application/xml':
- return true;
- default:
- return false;
- }
+ # http://www.whatwg.org/html/infrastructure.html#xml-mime-type
+ # * text/xml
+ # * application/xml
+ # * Any mimetype with a subtype ending in +xml (this implicitly includes application/xhtml+xml)
+ return (bool)preg_match( '!^(text|application)/xml$|^.+/.+\+xml$!', $mimetype );
}
/**
@@ -926,22 +865,22 @@ class Html {
global $wgStylePath;
if ( $useStylePath ) {
- $icon = $wgStylePath.'/common/images/'.$icon;
+ $icon = $wgStylePath . '/common/images/' . $icon;
}
$s = Html::openElement( 'div', array( 'class' => "mw-infobox $class" ) );
- $s .= Html::openElement( 'div', array( 'class' => 'mw-infobox-left' ) ).
+ $s .= Html::openElement( 'div', array( 'class' => 'mw-infobox-left' ) ) .
Html::element( 'img',
array(
'src' => $icon,
'alt' => $alt,
)
- ).
+ ) .
Html::closeElement( 'div' );
- $s .= Html::openElement( 'div', array( 'class' => 'mw-infobox-right' ) ).
- $text.
+ $s .= Html::openElement( 'div', array( 'class' => 'mw-infobox-right' ) ) .
+ $text .
Html::closeElement( 'div' );
$s .= Html::element( 'div', array( 'style' => 'clear: left;' ), ' ' );
@@ -962,9 +901,9 @@ class Html {
*/
static function srcSet( $urls ) {
$candidates = array();
- foreach( $urls as $density => $url ) {
+ 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
+ // http://www.whatwg.org/html/embedded-content-1.html#attr-img-srcset
$candidates[] = "{$url} {$density}x";
}
return implode( ", ", $candidates );
diff --git a/includes/HtmlFormatter.php b/includes/HtmlFormatter.php
new file mode 100644
index 00000000..248a76fe
--- /dev/null
+++ b/includes/HtmlFormatter.php
@@ -0,0 +1,356 @@
+<?php
+/**
+ * Performs transformations of HTML by wrapping around libxml2 and working
+ * around its countless bugs.
+ *
+ * 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 HtmlFormatter {
+ /**
+ * @var DOMDocument
+ */
+ private $doc;
+
+ private $html;
+ private $itemsToRemove = array();
+ private $elementsToFlatten = array();
+ protected $removeMedia = false;
+
+ /**
+ * Constructor
+ *
+ * @param string $html: Text to process
+ */
+ public function __construct( $html ) {
+ $this->html = $html;
+ }
+
+ /**
+ * Turns a chunk of HTML into a proper document
+ * @param string $html
+ * @return string
+ */
+ public static function wrapHTML( $html ) {
+ return '<!doctype html><html><head></head><body>' . $html . '</body></html>';
+ }
+
+ /**
+ * Override this in descendant class to modify HTML after it has been converted from DOM tree
+ * @param string $html: HTML to process
+ * @return string: Processed HTML
+ */
+ protected function onHtmlReady( $html ) {
+ return $html;
+ }
+
+ /**
+ * @return DOMDocument: DOM to manipulate
+ */
+ public function getDoc() {
+ if ( !$this->doc ) {
+ $html = mb_convert_encoding( $this->html, 'HTML-ENTITIES', 'UTF-8' );
+
+ // Workaround for bug that caused spaces before references
+ // to disappear during processing:
+ // https://bugzilla.wikimedia.org/show_bug.cgi?id=53086
+ //
+ // Please replace with a better fix if one can be found.
+ $html = str_replace( ' <', '&#32;<', $html );
+
+ libxml_use_internal_errors( true );
+ $loader = libxml_disable_entity_loader();
+ $this->doc = new DOMDocument();
+ $this->doc->strictErrorChecking = false;
+ $this->doc->loadHTML( $html );
+ libxml_disable_entity_loader( $loader );
+ libxml_use_internal_errors( false );
+ $this->doc->encoding = 'UTF-8';
+ }
+ return $this->doc;
+ }
+
+ /**
+ * Sets whether images/videos/sounds should be removed from output
+ * @param bool $flag
+ */
+ public function setRemoveMedia( $flag = true ) {
+ $this->removeMedia = $flag;
+ }
+
+ /**
+ * Adds one or more selector of content to remove. A subset of CSS selector
+ * syntax is supported:
+ *
+ * <tag>
+ * <tag>.class
+ * .<class>
+ * #<id>
+ *
+ * @param Array|string $selectors: Selector(s) of stuff to remove
+ */
+ public function remove( $selectors ) {
+ $this->itemsToRemove = array_merge( $this->itemsToRemove, (array)$selectors );
+ }
+
+ /**
+ * Adds one or more element name to the list to flatten (remove tag, but not its content)
+ * Can accept undelimited regexes
+ *
+ * Note this interface may fail in surprising unexpected ways due to usage of regexes,
+ * so should not be relied on for HTML markup security measures.
+ *
+ * @param Array|string $elements: Name(s) of tag(s) to flatten
+ */
+ public function flatten( $elements ) {
+ $this->elementsToFlatten = array_merge( $this->elementsToFlatten, (array)$elements );
+ }
+
+ /**
+ * Instructs the formatter to flatten all tags
+ */
+ public function flattenAllTags() {
+ $this->flatten( '[?!]?[a-z0-9]+' );
+ }
+
+ /**
+ * Removes content we've chosen to remove
+ */
+ public function filterContent() {
+ wfProfileIn( __METHOD__ );
+ $removals = $this->parseItemsToRemove();
+
+ if ( !$removals ) {
+ wfProfileOut( __METHOD__ );
+ return;
+ }
+
+ $doc = $this->getDoc();
+
+ // Remove tags
+
+ // You can't remove DOMNodes from a DOMNodeList as you're iterating
+ // over them in a foreach loop. It will seemingly leave the internal
+ // iterator on the foreach out of wack and results will be quite
+ // strange. Though, making a queue of items to remove seems to work.
+ $domElemsToRemove = array();
+ foreach ( $removals['TAG'] as $tagToRemove ) {
+ $tagToRemoveNodes = $doc->getElementsByTagName( $tagToRemove );
+ foreach ( $tagToRemoveNodes as $tagToRemoveNode ) {
+ if ( $tagToRemoveNode ) {
+ $domElemsToRemove[] = $tagToRemoveNode;
+ }
+ }
+ }
+
+ $this->removeElements( $domElemsToRemove );
+
+ // Elements with named IDs
+ $domElemsToRemove = array();
+ foreach ( $removals['ID'] as $itemToRemove ) {
+ $itemToRemoveNode = $doc->getElementById( $itemToRemove );
+ if ( $itemToRemoveNode ) {
+ $domElemsToRemove[] = $itemToRemoveNode;
+ }
+ }
+ $this->removeElements( $domElemsToRemove );
+
+ // CSS Classes
+ $domElemsToRemove = array();
+ $xpath = new DOMXpath( $doc );
+ foreach ( $removals['CLASS'] as $classToRemove ) {
+ $elements = $xpath->query( '//*[contains(@class, "' . $classToRemove . '")]' );
+
+ /** @var $element DOMElement */
+ foreach ( $elements as $element ) {
+ $classes = $element->getAttribute( 'class' );
+ if ( preg_match( "/\b$classToRemove\b/", $classes ) && $element->parentNode ) {
+ $domElemsToRemove[] = $element;
+ }
+ }
+ }
+ $this->removeElements( $domElemsToRemove );
+
+ // Tags with CSS Classes
+ foreach ( $removals['TAG_CLASS'] as $classToRemove ) {
+ $parts = explode( '.', $classToRemove );
+
+ $elements = $xpath->query(
+ '//' . $parts[0] . '[@class="' . $parts[1] . '"]'
+ );
+
+ $this->removeElements( $elements );
+ }
+
+ wfProfileOut( __METHOD__ );
+ }
+
+ /**
+ * Removes a list of elelments from DOMDocument
+ * @param array|DOMNodeList $elements
+ */
+ private function removeElements( $elements ) {
+ $list = $elements;
+ if ( $elements instanceof DOMNodeList ) {
+ $list = array();
+ foreach ( $elements as $element ) {
+ $list[] = $element;
+ }
+ }
+ /** @var $element DOMElement */
+ foreach ( $list as $element ) {
+ if ( $element->parentNode ) {
+ $element->parentNode->removeChild( $element );
+ }
+ }
+ }
+
+ /**
+ * libxml in its usual pointlessness converts many chars to entities - this function
+ * perfoms a reverse conversion
+ * @param string $html
+ * @return string
+ */
+ private function fixLibXML( $html ) {
+ wfProfileIn( __METHOD__ );
+ static $replacements;
+ if ( ! $replacements ) {
+ // We don't include rules like '&#34;' => '&amp;quot;' because entities had already been
+ // normalized by libxml. Using this function with input not sanitized by libxml is UNSAFE!
+ $replacements = new ReplacementArray( array(
+ '&quot;' => '&amp;quot;',
+ '&amp;' => '&amp;amp;',
+ '&lt;' => '&amp;lt;',
+ '&gt;' => '&amp;gt;',
+ ) );
+ }
+ $html = $replacements->replace( $html );
+ $html = mb_convert_encoding( $html, 'UTF-8', 'HTML-ENTITIES' );
+ wfProfileOut( __METHOD__ );
+ return $html;
+ }
+
+ /**
+ * Performs final transformations and returns resulting HTML
+ *
+ * @param DOMElement|string|null $element: ID of element to get HTML from or false to get it from the whole tree
+ * @return string: Processed HTML
+ */
+ public function getText( $element = null ) {
+ wfProfileIn( __METHOD__ );
+
+ if ( $this->doc ) {
+ if ( $element !== null && !( $element instanceof DOMElement ) ) {
+ $element = $this->doc->getElementById( $element );
+ }
+ if ( $element ) {
+ $body = $this->doc->getElementsByTagName( 'body' )->item( 0 );
+ $nodesArray = array();
+ foreach ( $body->childNodes as $node ) {
+ $nodesArray[] = $node;
+ }
+ foreach ( $nodesArray as $nodeArray ) {
+ $body->removeChild( $nodeArray );
+ }
+ $body->appendChild( $element );
+ }
+ $html = $this->doc->saveHTML();
+ $html = $this->fixLibXml( $html );
+ } else {
+ $html = $this->html;
+ }
+ if ( wfIsWindows() ) {
+ // Appears to be cleanup for CRLF misprocessing of unknown origin
+ // when running server on Windows platform.
+ //
+ // If this error continues in the future, please track it down in the
+ // XML code paths if possible and fix there.
+ $html = str_replace( '&#13;', '', $html );
+ }
+ $html = preg_replace( '/<!--.*?-->|^.*?<body>|<\/body>.*$/s', '', $html );
+ $html = $this->onHtmlReady( $html );
+
+ if ( $this->elementsToFlatten ) {
+ $elements = implode( '|', $this->elementsToFlatten );
+ $html = preg_replace( "#</?($elements)\\b[^>]*>#is", '', $html );
+ }
+
+ wfProfileOut( __METHOD__ );
+ return $html;
+ }
+
+ /**
+ * @param $selector: CSS selector to parse
+ * @param $type
+ * @param $rawName
+ * @return bool: Whether the selector was successfully recognised
+ */
+ protected function parseSelector( $selector, &$type, &$rawName ) {
+ if ( strpos( $selector, '.' ) === 0 ) {
+ $type = 'CLASS';
+ $rawName = substr( $selector, 1 );
+ } elseif ( strpos( $selector, '#' ) === 0 ) {
+ $type = 'ID';
+ $rawName = substr( $selector, 1 );
+ } elseif ( strpos( $selector, '.' ) !== 0 &&
+ strpos( $selector, '.' ) !== false )
+ {
+ $type = 'TAG_CLASS';
+ $rawName = $selector;
+ } elseif ( strpos( $selector, '[' ) === false
+ && strpos( $selector, ']' ) === false )
+ {
+ $type = 'TAG';
+ $rawName = $selector;
+ } else {
+ throw new MWException( __METHOD__ . "(): unrecognized selector '$selector'" );
+ }
+
+ return true;
+ }
+
+ /**
+ * Transforms CSS selectors into an internal representation suitable for processing
+ * @return array
+ */
+ protected function parseItemsToRemove() {
+ wfProfileIn( __METHOD__ );
+ $removals = array(
+ 'ID' => array(),
+ 'TAG' => array(),
+ 'CLASS' => array(),
+ 'TAG_CLASS' => array(),
+ );
+
+ foreach ( $this->itemsToRemove as $itemToRemove ) {
+ $type = '';
+ $rawName = '';
+ if ( $this->parseSelector( $itemToRemove, $type, $rawName ) ) {
+ $removals[$type][] = $rawName;
+ }
+ }
+
+ if ( $this->removeMedia ) {
+ $removals['TAG'][] = 'img';
+ $removals['TAG'][] = 'audio';
+ $removals['TAG'][] = 'video';
+ }
+
+ wfProfileOut( __METHOD__ );
+ return $removals;
+ }
+}
diff --git a/includes/HttpFunctions.php b/includes/HttpFunctions.php
index dc65c67e..78c2ac7a 100644
--- a/includes/HttpFunctions.php
+++ b/includes/HttpFunctions.php
@@ -40,14 +40,15 @@ class Http {
* @param array $options options to pass to MWHttpRequest object.
* Possible keys for the array:
* - timeout Timeout length in seconds
+ * - connectTimeout Timeout for connection, in seconds (curl only)
* - postData An array of key-value pairs or a url-encoded form data
* - proxy The proxy to use.
* Otherwise it will use $wgHTTPProxy (if set)
* Otherwise it will use the environment variable "http_proxy" (if set)
* - noProxy Don't use any proxy at all. Takes precedence over proxy value(s).
- * - sslVerifyHost (curl only) Verify hostname against certificate
- * - sslVerifyCert (curl only) Verify SSL certificate
- * - caInfo (curl only) Provide CA information
+ * - sslVerifyHost Verify hostname against certificate
+ * - sslVerifyCert Verify SSL certificate
+ * - caInfo Provide CA information
* - maxRedirects Maximum number of redirects to follow (defaults to 5)
* - followRedirects Whether to follow redirects (defaults to false).
* Note: this should only be used when the target URL is trusted,
@@ -58,20 +59,26 @@ class Http {
*/
public static function request( $method, $url, $options = array() ) {
wfDebug( "HTTP: $method: $url\n" );
+ wfProfileIn( __METHOD__ . "-$method" );
+
$options['method'] = strtoupper( $method );
if ( !isset( $options['timeout'] ) ) {
$options['timeout'] = 'default';
}
+ if ( !isset( $options['connectTimeout'] ) ) {
+ $options['connectTimeout'] = 'default';
+ }
$req = MWHttpRequest::factory( $url, $options );
$status = $req->execute();
+ $content = false;
if ( $status->isOK() ) {
- return $req->getContent();
- } else {
- return false;
+ $content = $req->getContent();
}
+ wfProfileOut( __METHOD__ . "-$method" );
+ return $content;
}
/**
@@ -213,7 +220,7 @@ class MWHttpRequest {
* @param array $options (optional) extra params to pass (see Http::request())
*/
protected function __construct( $url, $options = array() ) {
- global $wgHTTPTimeout;
+ global $wgHTTPTimeout, $wgHTTPConnectTimeout;
$this->url = wfExpandUrl( $url, PROTO_HTTP );
$this->parsedUrl = wfParseUrl( $this->url );
@@ -229,7 +236,12 @@ class MWHttpRequest {
} else {
$this->timeout = $wgHTTPTimeout;
}
- if( isset( $options['userAgent'] ) ) {
+ if ( isset( $options['connectTimeout'] ) && $options['connectTimeout'] != 'default' ) {
+ $this->connectTimeout = $options['connectTimeout'];
+ } else {
+ $this->connectTimeout = $wgHTTPConnectTimeout;
+ }
+ if ( isset( $options['userAgent'] ) ) {
$this->setUserAgent( $options['userAgent'] );
}
@@ -277,7 +289,7 @@ class MWHttpRequest {
' Http::$httpEngine is set to "curl"' );
}
- switch( Http::$httpEngine ) {
+ switch ( Http::$httpEngine ) {
case 'curl':
return new CurlHttpRequest( $url, $options );
case 'php':
@@ -302,7 +314,7 @@ class MWHttpRequest {
/**
* Set the parameters of the request
-
+ *
* @param $args Array
* @todo overload the args param
*/
@@ -427,6 +439,8 @@ class MWHttpRequest {
public function execute() {
global $wgTitle;
+ wfProfileIn( __METHOD__ );
+
$this->content = "";
if ( strtoupper( $this->method ) == "HEAD" ) {
@@ -446,6 +460,8 @@ class MWHttpRequest {
if ( !isset( $this->reqHeaders['User-Agent'] ) ) {
$this->setUserAgent( Http::userAgent() );
}
+
+ wfProfileOut( __METHOD__ );
}
/**
@@ -454,6 +470,8 @@ class MWHttpRequest {
* found in an array in the member variable headerList.
*/
protected function parseHeader() {
+ wfProfileIn( __METHOD__ );
+
$lastname = "";
foreach ( $this->headerList as $header ) {
@@ -470,6 +488,8 @@ class MWHttpRequest {
}
$this->parseCookies();
+
+ wfProfileOut( __METHOD__ );
}
/**
@@ -552,8 +572,8 @@ class MWHttpRequest {
$this->parseHeader();
}
- if ( isset( $this->respHeaders[strtolower ( $header ) ] ) ) {
- $v = $this->respHeaders[strtolower ( $header ) ];
+ if ( isset( $this->respHeaders[strtolower( $header )] ) ) {
+ $v = $this->respHeaders[strtolower( $header )];
return $v[count( $v ) - 1];
}
@@ -603,6 +623,8 @@ class MWHttpRequest {
* Parse the cookies in the response headers and store them in the cookie jar.
*/
protected function parseCookies() {
+ wfProfileIn( __METHOD__ );
+
if ( !$this->cookieJar ) {
$this->cookieJar = new CookieJar;
}
@@ -613,6 +635,8 @@ class MWHttpRequest {
$this->cookieJar->parseCookieResponseHeader( $cookie, $url['host'] );
}
}
+
+ wfProfileOut( __METHOD__ );
}
/**
@@ -631,17 +655,17 @@ class MWHttpRequest {
$headers = $this->getResponseHeaders();
//return full url (fix for incorrect but handled relative location)
- if ( isset( $headers[ 'location' ] ) ) {
- $locations = $headers[ 'location' ];
+ if ( isset( $headers['location'] ) ) {
+ $locations = $headers['location'];
$domain = '';
$foundRelativeURI = false;
$countLocations = count( $locations );
for ( $i = $countLocations - 1; $i >= 0; $i-- ) {
- $url = parse_url( $locations[ $i ] );
+ $url = parse_url( $locations[$i] );
if ( isset( $url['host'] ) ) {
- $domain = $url[ 'scheme' ] . '://' . $url[ 'host' ];
+ $domain = $url['scheme'] . '://' . $url['host'];
break; //found correct URI (with host)
} else {
$foundRelativeURI = true;
@@ -650,15 +674,15 @@ class MWHttpRequest {
if ( $foundRelativeURI ) {
if ( $domain ) {
- return $domain . $locations[ $countLocations - 1 ];
+ return $domain . $locations[$countLocations - 1];
} else {
$url = parse_url( $this->url );
- if ( isset($url[ 'host' ]) ) {
- return $url[ 'scheme' ] . '://' . $url[ 'host' ] . $locations[ $countLocations - 1 ];
+ if ( isset( $url['host'] ) ) {
+ return $url['scheme'] . '://' . $url['host'] . $locations[$countLocations - 1];
}
}
} else {
- return $locations[ $countLocations - 1 ];
+ return $locations[$countLocations - 1];
}
}
@@ -681,11 +705,6 @@ class MWHttpRequest {
class CurlHttpRequest extends MWHttpRequest {
const SUPPORTS_FILE_POSTS = true;
- static $curlMessageMap = array(
- 6 => 'http-host-unreachable',
- 28 => 'http-timed-out'
- );
-
protected $curlOptions = array();
protected $headerText = "";
@@ -700,14 +719,18 @@ class CurlHttpRequest extends MWHttpRequest {
}
public function execute() {
+ wfProfileIn( __METHOD__ );
+
parent::execute();
if ( !$this->status->isOK() ) {
+ wfProfileOut( __METHOD__ );
return $this->status;
}
$this->curlOptions[CURLOPT_PROXY] = $this->proxy;
$this->curlOptions[CURLOPT_TIMEOUT] = $this->timeout;
+ $this->curlOptions[CURLOPT_CONNECTTIMEOUT_MS] = $this->connectTimeout * 1000;
$this->curlOptions[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_0;
$this->curlOptions[CURLOPT_WRITEFUNCTION] = $this->callback;
$this->curlOptions[CURLOPT_HEADERFUNCTION] = array( $this, "readHeader" );
@@ -746,6 +769,7 @@ class CurlHttpRequest extends MWHttpRequest {
$curlHandle = curl_init( $this->url );
if ( !curl_setopt_array( $curlHandle, $this->curlOptions ) ) {
+ wfProfileOut( __METHOD__ );
throw new MWException( "Error setting curl options." );
}
@@ -760,14 +784,11 @@ class CurlHttpRequest extends MWHttpRequest {
wfRestoreWarnings();
}
- if ( false === curl_exec( $curlHandle ) ) {
- $code = curl_error( $curlHandle );
-
- if ( isset( self::$curlMessageMap[$code] ) ) {
- $this->status->fatal( self::$curlMessageMap[$code] );
- } else {
- $this->status->fatal( 'http-curl-error', curl_error( $curlHandle ) );
- }
+ $curlRes = curl_exec( $curlHandle );
+ if ( curl_errno( $curlHandle ) == CURLE_OPERATION_TIMEOUTED ) {
+ $this->status->fatal( 'http-timed-out', $this->url );
+ } elseif ( $curlRes === false ) {
+ $this->status->fatal( 'http-curl-error', curl_error( $curlHandle ) );
} else {
$this->headerList = explode( "\r\n", $this->headerText );
}
@@ -777,6 +798,8 @@ class CurlHttpRequest extends MWHttpRequest {
$this->parseHeader();
$this->setStatus();
+ wfProfileOut( __METHOD__ );
+
return $this->status;
}
@@ -811,6 +834,8 @@ class PhpHttpRequest extends MWHttpRequest {
}
public function execute() {
+ wfProfileIn( __METHOD__ );
+
parent::execute();
if ( is_array( $this->postData ) ) {
@@ -826,7 +851,7 @@ class PhpHttpRequest extends MWHttpRequest {
if ( $this->method == 'POST' ) {
// Required for HTTP 1.0 POSTs
$this->reqHeaders['Content-Length'] = strlen( $this->postData );
- if( !isset( $this->reqHeaders['Content-Type'] ) ) {
+ if ( !isset( $this->reqHeaders['Content-Type'] ) ) {
$this->reqHeaders['Content-Type'] = "application/x-www-form-urlencoded";
}
}
@@ -860,7 +885,23 @@ class PhpHttpRequest extends MWHttpRequest {
$options['timeout'] = $this->timeout;
- $context = stream_context_create( array( 'http' => $options ) );
+ if ( $this->sslVerifyHost ) {
+ $options['CN_match'] = $this->parsedUrl['host'];
+ }
+ if ( $this->sslVerifyCert ) {
+ $options['verify_peer'] = true;
+ }
+
+ if ( is_dir( $this->caInfo ) ) {
+ $options['capath'] = $this->caInfo;
+ } elseif ( is_file( $this->caInfo ) ) {
+ $options['cafile'] = $this->caInfo;
+ } elseif ( $this->caInfo ) {
+ throw new MWException( "Invalid CA info passed: {$this->caInfo}" );
+ }
+
+ $scheme = $this->parsedUrl['scheme'];
+ $context = stream_context_create( array( "$scheme" => $options ) );
$this->headerList = array();
$reqCount = 0;
@@ -903,18 +944,19 @@ class PhpHttpRequest extends MWHttpRequest {
if ( $fh === false ) {
$this->status->fatal( 'http-request-error' );
+ wfProfileOut( __METHOD__ );
return $this->status;
}
if ( $result['timed_out'] ) {
$this->status->fatal( 'http-timed-out', $this->url );
+ wfProfileOut( __METHOD__ );
return $this->status;
}
// If everything went OK, or we received some error code
// get the response body content.
- if ( $this->status->isOK()
- || (int)$this->respStatus >= 300) {
+ if ( $this->status->isOK() || (int)$this->respStatus >= 300 ) {
while ( !feof( $fh ) ) {
$buf = fread( $fh, 8192 );
@@ -930,6 +972,8 @@ class PhpHttpRequest extends MWHttpRequest {
}
fclose( $fh );
+ wfProfileOut( __METHOD__ );
+
return $this->status;
}
}
diff --git a/includes/IP.php b/includes/IP.php
index 72b9a52c..73834a59 100644
--- a/includes/IP.php
+++ b/includes/IP.php
@@ -33,24 +33,17 @@ define( 'RE_IP_BLOCK', RE_IP_ADD . '\/' . RE_IP_PREFIX );
// An IPv6 address is made up of 8 words (each x0000 to xFFFF).
// However, the "::" abbreviation can be used on consecutive x0000 words.
define( 'RE_IPV6_WORD', '([0-9A-Fa-f]{1,4})' );
-define( 'RE_IPV6_PREFIX', '(12[0-8]|1[01][0-9]|[1-9]?\d)');
+define( 'RE_IPV6_PREFIX', '(12[0-8]|1[01][0-9]|[1-9]?\d)' );
define( 'RE_IPV6_ADD',
'(?:' . // starts with "::" (including "::")
':(?::|(?::' . RE_IPV6_WORD . '){1,7})' .
'|' . // ends with "::" (except "::")
RE_IPV6_WORD . '(?::' . RE_IPV6_WORD . '){0,6}::' .
- '|' . // contains one "::" in the middle, ending in "::WORD"
- RE_IPV6_WORD . '(?::' . RE_IPV6_WORD . '){0,5}' . '::' . RE_IPV6_WORD .
- '|' . // contains one "::" in the middle, not ending in "::WORD" (regex for PCRE 4.0+)
- RE_IPV6_WORD . '(?::(?P<abn>:(?P<iabn>))?' . RE_IPV6_WORD . '(?!:(?P=abn))){1,5}' .
- ':' . RE_IPV6_WORD . '(?P=iabn)' .
- // NOTE: (?!(?P=abn)) fails iff "::" used twice; (?P=iabn) passes iff a "::" was found.
+ '|' . // contains one "::" in the middle (the ^ makes the test fail if none found)
+ RE_IPV6_WORD . '(?::((?(-1)|:))?' . RE_IPV6_WORD . '){1,6}(?(-2)|^)' .
'|' . // contains no "::"
RE_IPV6_WORD . '(?::' . RE_IPV6_WORD . '){7}' .
')'
- // NOTE: With PCRE 7.2+, we can combine the two '"::" in the middle' cases into:
- // RE_IPV6_WORD . '(?::((?(-1)|:))?' . RE_IPV6_WORD . '){1,6}(?(-2)|^)'
- // This also improves regex concatenation by using relative references.
);
// An IPv6 block is an IP address and a prefix (d1 to d128)
define( 'RE_IPV6_BLOCK', RE_IPV6_ADD . '\/' . RE_IPV6_PREFIX );
@@ -212,7 +205,7 @@ class IP {
$longest = $match;
$longestPos = $pos;
}
- $offset += ( $pos + strlen( $match ) ); // advance
+ $offset = ( $pos + strlen( $match ) ); // advance
}
if ( $longest !== false ) {
// Replace this portion of the string with the '::' abbreviation
@@ -392,11 +385,11 @@ class IP {
static $privateRanges = false;
if ( !$privateRanges ) {
$privateRanges = array(
- array( '10.0.0.0', '10.255.255.255' ), # RFC 1918 (private)
- array( '172.16.0.0', '172.31.255.255' ), # "
- array( '192.168.0.0', '192.168.255.255' ), # "
- array( '0.0.0.0', '0.255.255.255' ), # this network
- array( '127.0.0.0', '127.255.255.255' ), # loopback
+ array( '10.0.0.0', '10.255.255.255' ), # RFC 1918 (private)
+ array( '172.16.0.0', '172.31.255.255' ), # RFC 1918 (private)
+ array( '192.168.0.0', '192.168.255.255' ), # RFC 1918 (private)
+ array( '0.0.0.0', '0.255.255.255' ), # this network
+ array( '127.0.0.0', '127.255.255.255' ), # loopback
);
}
@@ -492,6 +485,11 @@ class IP {
$n = ip2long( $ip );
if ( $n < 0 ) {
$n += pow( 2, 32 );
+ # On 32-bit platforms (and on Windows), 2^32 does not fit into an int,
+ # so $n becomes a float. We convert it to string instead.
+ if ( is_float( $n ) ) {
+ $n = (string)$n;
+ }
}
}
return $n;
@@ -526,7 +524,7 @@ class IP {
if ( $bits == 0 ) {
$network = 0;
} else {
- $network &= ~( ( 1 << ( 32 - $bits ) ) - 1);
+ $network &= ~( ( 1 << ( 32 - $bits ) ) - 1 );
}
# Convert to unsigned
if ( $network < 0 ) {
diff --git a/includes/ImagePage.php b/includes/ImagePage.php
index b3a485aa..7ea06b0e 100644
--- a/includes/ImagePage.php
+++ b/includes/ImagePage.php
@@ -206,7 +206,7 @@ class ImagePage extends Article {
}
// Add remote Filepage.css
- if( !$this->repo->isLocal() ) {
+ if ( !$this->repo->isLocal() ) {
$css = $this->repo->getDescriptionStylesheetUrl();
if ( $css ) {
$out->addStyle( $css );
@@ -250,7 +250,7 @@ class ImagePage extends Article {
*
* @todo FIXME: Bad interface, see note on MediaHandler::formatMetadata().
*
- * @param array $metadata 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 ) {
@@ -311,6 +311,12 @@ class ImagePage extends Article {
} else {
$params = array( 'page' => $page );
}
+
+ $renderLang = $request->getVal( 'lang' );
+ if ( !is_null( $renderLang ) ) {
+ $params['lang'] = $renderLang;
+ }
+
$width_orig = $this->displayImg->getWidth( $page );
$width = $width_orig;
$height_orig = $this->displayImg->getHeight( $page );
@@ -395,6 +401,7 @@ class ImagePage extends Article {
$isMulti = $this->displayImg->isMultipage() && $this->displayImg->pageCount() > 1;
if ( $isMulti ) {
+ $out->addModules( 'mediawiki.page.image.pagination' );
$out->addHTML( '<table class="multipageimage"><tr><td>' );
}
@@ -444,7 +451,6 @@ class ImagePage extends Article {
$formParams = array(
'name' => 'pageselector',
'action' => $wgScript,
- 'onchange' => 'document.pageselector.submit();',
);
$options = array();
for ( $i = 1; $i <= $count; $i++ ) {
@@ -597,7 +603,7 @@ EOT
$descText = $this->mPage->getFile()->getDescriptionText();
/* Add canonical to head if there is no local page for this shared file */
- if( $descUrl && $this->mPage->getID() == 0 ) {
+ if ( $descUrl && $this->mPage->getID() == 0 ) {
$out->setCanonicalUrl( $descUrl );
}
@@ -631,7 +637,7 @@ EOT
* external editing (and instructions link) etc.
*/
protected function uploadLinksBox() {
- global $wgEnableUploads, $wgUseExternalEditor;
+ global $wgEnableUploads;
if ( !$wgEnableUploads ) {
return;
@@ -654,25 +660,6 @@ EOT
$out->addHTML( "<li id=\"mw-imagepage-upload-disallowed\">" . $this->getContext()->msg( 'upload-disallowed-here' )->escaped() . "</li>\n" );
}
- # External editing link
- if ( $wgUseExternalEditor ) {
- $elink = Linker::linkKnown(
- $this->getTitle(),
- wfMessage( 'edit-externally' )->escaped(),
- array(),
- array(
- 'action' => 'edit',
- 'externaledit' => 'true',
- 'mode' => 'file'
- )
- );
- $out->addHTML(
- '<li id="mw-imagepage-edit-external">' . $elink . ' <small>' .
- wfMessage( 'edit-externally-help' )->parse() .
- "</small></li>\n"
- );
- }
-
$out->addHTML( "</ul>\n" );
}
@@ -719,7 +706,7 @@ EOT
$limit = 100;
$out = $this->getContext()->getOutput();
- $res = $this->queryImageLinks( $this->getTitle()->getDbKey(), $limit + 1);
+ $res = $this->queryImageLinks( $this->getTitle()->getDBkey(), $limit + 1 );
$rows = array();
$redirects = array();
foreach ( $res as $row ) {
@@ -772,13 +759,21 @@ EOT
// Create links for every element
$currentCount = 0;
- foreach( $rows as $element ) {
+ foreach ( $rows as $element ) {
$currentCount++;
if ( $currentCount > $limit ) {
break;
}
- $link = Linker::linkKnown( Title::makeTitle( $element->page_namespace, $element->page_title ) );
+ $query = array();
+ # Add a redirect=no to make redirect pages reachable
+ if ( isset( $redirects[$element->page_title] ) ) {
+ $query['redirect'] = 'no';
+ }
+ $link = Linker::linkKnown(
+ Title::makeTitle( $element->page_namespace, $element->page_title ),
+ null, array(), $query
+ );
if ( !isset( $redirects[$element->page_title] ) ) {
# No redirects
$liContents = $link;
diff --git a/includes/ImageQueryPage.php b/includes/ImageQueryPage.php
index f9f6ceed..75f7ba64 100644
--- a/includes/ImageQueryPage.php
+++ b/includes/ImageQueryPage.php
@@ -29,25 +29,25 @@
* @author Rob Church <robchur@gmail.com>
*/
abstract class ImageQueryPage extends QueryPage {
-
/**
* Format and output report results using the given information plus
* OutputPage
*
- * @param $out OutputPage to print to
- * @param $skin Skin: user skin to use [unused]
- * @param $dbr DatabaseBase (read) connection to use
- * @param $res Integer: result pointer
- * @param $num Integer: number of available result rows
- * @param $offset Integer: paging offset
+ * @param OutputPage $out OutputPage to print to
+ * @param Skin $skin User skin to use [unused]
+ * @param DatabaseBase $dbr (read) connection to use
+ * @param int $res Result pointer
+ * @param int $num Number of available result rows
+ * @param int $offset Paging offset
*/
protected function outputResults( $out, $skin, $dbr, $res, $num, $offset ) {
- if( $num > 0 ) {
- $gallery = new ImageGallery();
+ if ( $num > 0 ) {
+ $gallery = ImageGalleryBase::factory();
+ $gallery->setContext( $this->getContext() );
# $res might contain the whole 1,000 rows, so we read up to
# $num [should update this to use a Pager]
- for( $i = 0; $i < $num && $row = $dbr->fetchObject( $res ); $i++ ) {
+ for ( $i = 0; $i < $num && $row = $dbr->fetchObject( $res ); $i++ ) {
$namespace = isset( $row->namespace ) ? $row->namespace : NS_FILE;
$title = Title::makeTitleSafe( $namespace, $row->title );
if ( $title instanceof Title && $title->getNamespace() == NS_FILE ) {
@@ -65,11 +65,10 @@ abstract class ImageQueryPage extends QueryPage {
/**
* Get additional HTML to be shown in a results' cell
*
- * @param $row Object: result row
- * @return String
+ * @param object $row Result row
+ * @return string
*/
protected function getCellHtml( $row ) {
return '';
}
-
}
diff --git a/includes/Import.php b/includes/Import.php
index bb5d6349..8b7af02a 100644
--- a/includes/Import.php
+++ b/includes/Import.php
@@ -47,7 +47,7 @@ class WikiImporter {
stream_wrapper_register( 'uploadsource', 'UploadSourceAdapter' );
$id = UploadSourceAdapter::registerSource( $source );
- if (defined( 'LIBXML_PARSEHUGE' ) ) {
+ if ( defined( 'LIBXML_PARSEHUGE' ) ) {
$this->reader->open( "uploadsource://$id", null, LIBXML_PARSEHUGE );
} else {
$this->reader->open( "uploadsource://$id" );
@@ -66,7 +66,7 @@ class WikiImporter {
}
private function debug( $data ) {
- if( $this->mDebug ) {
+ if ( $this->mDebug ) {
wfDebug( "IMPORT: $data\n" );
}
}
@@ -188,10 +188,10 @@ class WikiImporter {
* @return bool
*/
public function setTargetNamespace( $namespace ) {
- if( is_null( $namespace ) ) {
+ if ( is_null( $namespace ) ) {
// Don't override namespaces
$this->mTargetNamespace = null;
- } elseif( $namespace >= 0 ) {
+ } elseif ( $namespace >= 0 ) {
// @todo FIXME: Check for validity
$this->mTargetNamespace = intval( $namespace );
} else {
@@ -206,16 +206,16 @@ class WikiImporter {
*/
public function setTargetRootPage( $rootpage ) {
$status = Status::newGood();
- if( is_null( $rootpage ) ) {
+ if ( is_null( $rootpage ) ) {
// No rootpage
$this->mTargetRootPage = null;
- } elseif( $rootpage !== '' ) {
+ } elseif ( $rootpage !== '' ) {
$rootpage = rtrim( $rootpage, '/' ); //avoid double slashes
$title = Title::newFromText( $rootpage, !is_null( $this->mTargetNamespace ) ? $this->mTargetNamespace : NS_MAIN );
- if( !$title || $title->isExternal() ) {
+ if ( !$title || $title->isExternal() ) {
$status->fatal( 'import-rootpage-invalid' );
} else {
- if( !MWNamespace::hasSubpages( $title->getNamespace() ) ) {
+ if ( !MWNamespace::hasSubpages( $title->getNamespace() ) ) {
global $wgContLang;
$displayNSText = $title->getNamespace() == NS_MAIN
@@ -304,7 +304,7 @@ class WikiImporter {
*/
public function debugRevisionHandler( &$revision ) {
$this->debug( "Got revision:" );
- if( is_object( $revision->title ) ) {
+ if ( is_object( $revision->title ) ) {
$this->debug( "-- Title: " . $revision->title->getPrefixedText() );
} else {
$this->debug( "-- Title: <invalid>" );
@@ -320,7 +320,7 @@ class WikiImporter {
* @param $title Title
*/
function pageCallback( $title ) {
- if( isset( $this->mPageCallback ) ) {
+ if ( isset( $this->mPageCallback ) ) {
call_user_func( $this->mPageCallback, $title );
}
}
@@ -334,7 +334,7 @@ class WikiImporter {
* @param array $pageInfo associative array of page information
*/
private function pageOutCallback( $title, $origTitle, $revCount, $sucCount, $pageInfo ) {
- if( isset( $this->mPageOutCallback ) ) {
+ if ( isset( $this->mPageOutCallback ) ) {
$args = func_get_args();
call_user_func_array( $this->mPageOutCallback, $args );
}
@@ -376,12 +376,12 @@ class WikiImporter {
* @access private
*/
private function nodeContents() {
- if( $this->reader->isEmptyElement ) {
+ if ( $this->reader->isEmptyElement ) {
return "";
}
$buffer = "";
- while( $this->reader->read() ) {
- switch( $this->reader->nodeType ) {
+ while ( $this->reader->read() ) {
+ switch ( $this->reader->nodeType ) {
case XmlReader::TEXT:
case XmlReader::SIGNIFICANT_WHITESPACE:
$buffer .= $this->reader->value;
@@ -420,19 +420,19 @@ class WikiImporter {
"END_ELEMENT",
"END_ENTITY",
"XML_DECLARATION",
- );
+ );
$lookup = array();
- foreach( $xmlReaderConstants as $name ) {
- $lookup[constant("XmlReader::$name")] = $name;
+ foreach ( $xmlReaderConstants as $name ) {
+ $lookup[constant( "XmlReader::$name" )] = $name;
}
}
- print( var_dump(
+ print var_dump(
$lookup[$this->reader->nodeType],
$this->reader->name,
$this->reader->value
- )."\n\n" );
+ ) . "\n\n";
}
/**
@@ -450,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." );
@@ -463,7 +463,7 @@ class WikiImporter {
$tag = $this->reader->name;
$type = $this->reader->nodeType;
- if ( !wfRunHooks( 'ImportHandleToplevelXMLTag', $this ) ) {
+ if ( !wfRunHooks( 'ImportHandleToplevelXMLTag', array( $this ) ) ) {
// Do nothing
} elseif ( $tag == 'mediawiki' && $type == XmlReader::END_ELEMENT ) {
break;
@@ -522,8 +522,9 @@ class WikiImporter {
$tag = $this->reader->name;
- if ( !wfRunHooks( 'ImportHandleLogItemXMLTag',
- $this, $logInfo ) ) {
+ if ( !wfRunHooks( 'ImportHandleLogItemXMLTag', array(
+ $this, $logInfo
+ ) ) ) {
// Do nothing
} elseif ( in_array( $tag, $normalFields ) ) {
$logInfo[$tag] = $this->nodeContents();
@@ -639,8 +640,9 @@ class WikiImporter {
$tag = $this->reader->name;
- if ( !wfRunHooks( 'ImportHandleRevisionXMLTag', $this,
- $pageInfo, $revisionInfo ) ) {
+ if ( !wfRunHooks( 'ImportHandleRevisionXMLTag', array(
+ $this, $pageInfo, $revisionInfo
+ ) ) ) {
// Do nothing
} elseif ( in_array( $tag, $normalFields ) ) {
$revisionInfo[$tag] = $this->nodeContents();
@@ -666,7 +668,7 @@ class WikiImporter {
private function processRevision( $pageInfo, $revisionInfo ) {
$revision = new WikiRevision;
- if( isset( $revisionInfo['id'] ) ) {
+ if ( isset( $revisionInfo['id'] ) ) {
$revision->setID( $revisionInfo['id'] );
}
if ( isset( $revisionInfo['text'] ) ) {
@@ -725,8 +727,9 @@ class WikiImporter {
$tag = $this->reader->name;
- if ( !wfRunHooks( 'ImportHandleUploadXMLTag', $this,
- $pageInfo ) ) {
+ if ( !wfRunHooks( 'ImportHandleUploadXMLTag', array(
+ $this, $pageInfo
+ ) ) ) {
// Do nothing
} elseif ( in_array( $tag, $normalFields ) ) {
$uploadInfo[$tag] = $this->nodeContents();
@@ -840,33 +843,33 @@ class WikiImporter {
$workTitle = $text;
$origTitle = Title::newFromText( $workTitle );
- if( !is_null( $this->mTargetNamespace ) && !is_null( $origTitle ) ) {
+ if ( !is_null( $this->mTargetNamespace ) && !is_null( $origTitle ) ) {
# makeTitleSafe, because $origTitle can have a interwiki (different setting of interwiki map)
# and than dbKey can begin with a lowercase char
$title = Title::makeTitleSafe( $this->mTargetNamespace,
$origTitle->getDBkey() );
} else {
- if( !is_null( $this->mTargetRootPage ) ) {
+ if ( !is_null( $this->mTargetRootPage ) ) {
$workTitle = $this->mTargetRootPage . '/' . $workTitle;
}
$title = Title::newFromText( $workTitle );
}
- if( is_null( $title ) ) {
+ if ( is_null( $title ) ) {
# Invalid page title? Ignore the page
$this->notice( 'import-error-invalid', $workTitle );
return false;
- } elseif( $title->isExternal() ) {
+ } elseif ( $title->isExternal() ) {
$this->notice( 'import-error-interwiki', $title->getPrefixedText() );
return false;
- } elseif( !$title->canExist() ) {
+ } elseif ( !$title->canExist() ) {
$this->notice( 'import-error-special', $title->getPrefixedText() );
return false;
- } elseif( !$title->userCan( 'edit' ) && !$wgCommandLineMode ) {
+ } elseif ( !$title->userCan( 'edit' ) && !$wgCommandLineMode ) {
# Do not import if the importing wiki user cannot edit this page
$this->notice( 'import-error-edit', $title->getPrefixedText() );
return false;
- } elseif( !$title->exists() && !$title->userCan( 'create' ) && !$wgCommandLineMode ) {
+ } elseif ( !$title->exists() && !$title->userCan( 'create' ) && !$wgCommandLineMode ) {
# Do not import if the importing wiki user cannot create this page
$this->notice( 'import-error-create', $title->getPrefixedText() );
return false;
@@ -997,12 +1000,12 @@ class XMLReader2 extends XMLReader {
* @return bool|string
*/
function nodeContents() {
- if( $this->isEmptyElement ) {
+ if ( $this->isEmptyElement ) {
return "";
}
$buffer = "";
- while( $this->read() ) {
- switch( $this->nodeType ) {
+ while ( $this->read() ) {
+ switch ( $this->nodeType ) {
case XmlReader::TEXT:
case XmlReader::SIGNIFICANT_WHITESPACE:
$buffer .= $this->value;
@@ -1051,9 +1054,9 @@ class WikiRevision {
* @throws MWException
*/
function setTitle( $title ) {
- if( is_object( $title ) ) {
+ if ( is_object( $title ) ) {
$this->title = $title;
- } elseif( is_null( $title ) ) {
+ } elseif ( is_null( $title ) ) {
throw new MWException( "WikiRevision given a null title in import. You may need to adjust \$wgLegalTitleChars." );
} else {
throw new MWException( "WikiRevision given non-object title in import." );
@@ -1369,7 +1372,7 @@ class WikiRevision {
# Sneak a single revision into place
$user = User::newFromName( $this->getUser() );
- if( $user ) {
+ if ( $user ) {
$userId = intval( $user->getId() );
$userText = $user->getName();
$userObj = $user;
@@ -1384,7 +1387,7 @@ class WikiRevision {
$linkCache->clear();
$page = WikiPage::factory( $this->title );
- if( !$page->exists() ) {
+ if ( !$page->exists() ) {
# must create the page...
$pageId = $page->insertOn( $dbw );
$created = true;
@@ -1400,7 +1403,7 @@ class WikiRevision {
'rev_comment' => $this->getComment() ),
__METHOD__
);
- if( $prior ) {
+ if ( $prior ) {
// @todo FIXME: This could fail slightly for multiple matches :P
wfDebug( __METHOD__ . ": skipping existing revision for [[" .
$this->title->getPrefixedText() . "]], timestamp " . $this->timestamp . "\n" );
@@ -1440,7 +1443,7 @@ class WikiRevision {
function importLogItem() {
$dbw = wfGetDB( DB_MASTER );
# @todo FIXME: This will not record autoblocks
- if( !$this->getTitle() ) {
+ if ( !$this->getTitle() ) {
wfDebug( __METHOD__ . ": skipping invalid {$this->type}/{$this->action} log time, timestamp " .
$this->timestamp . "\n" );
return;
@@ -1459,7 +1462,7 @@ class WikiRevision {
__METHOD__
);
// @todo FIXME: This could fail slightly for multiple matches :P
- if( $prior ) {
+ if ( $prior ) {
wfDebug( __METHOD__ . ": skipping existing item for Log:{$this->type}/{$this->action}, timestamp " .
$this->timestamp . "\n" );
return;
@@ -1500,7 +1503,7 @@ class WikiRevision {
wfDebug( __METHOD__ . "File already exists; importing as $archiveName\n" );
}
}
- if( !$file ) {
+ if ( !$file ) {
wfDebug( __METHOD__ . ': Bad file for ' . $this->getTitle() . "\n" );
return false;
}
@@ -1512,7 +1515,7 @@ class WikiRevision {
$source = $this->downloadSource();
$flags |= File::DELETE_SOURCE;
}
- if( !$source ) {
+ if ( !$source ) {
wfDebug( __METHOD__ . ": Could not fetch remote file.\n" );
return false;
}
@@ -1551,13 +1554,13 @@ class WikiRevision {
*/
function downloadSource() {
global $wgEnableUploads;
- if( !$wgEnableUploads ) {
+ if ( !$wgEnableUploads ) {
return false;
}
$tempo = tempnam( wfTempDir(), 'download' );
$f = fopen( $tempo, 'wb' );
- if( !$f ) {
+ if ( !$f ) {
wfDebug( "IMPORT: couldn't write to temp file $tempo\n" );
return false;
}
@@ -1565,7 +1568,7 @@ class WikiRevision {
// @todo FIXME!
$src = $this->getSrc();
$data = Http::get( $src );
- if( !$data ) {
+ if ( !$data ) {
wfDebug( "IMPORT: couldn't fetch source $src\n" );
fclose( $f );
unlink( $tempo );
@@ -1601,7 +1604,7 @@ class ImportStringSource {
* @return bool|string
*/
function readChunk() {
- if( $this->atEnd() ) {
+ if ( $this->atEnd() ) {
return false;
}
$this->mRead = true;
@@ -1640,7 +1643,7 @@ class ImportStreamSource {
wfSuppressWarnings();
$file = fopen( $filename, 'rt' );
wfRestoreWarnings();
- if( !$file ) {
+ if ( !$file ) {
return Status::newFatal( "importcantopen" );
}
return Status::newGood( new ImportStreamSource( $file ) );
@@ -1653,11 +1656,11 @@ class ImportStreamSource {
static function newFromUpload( $fieldname = "xmlimport" ) {
$upload =& $_FILES[$fieldname];
- if( $upload === null || !$upload['name'] ) {
+ if ( $upload === null || !$upload['name'] ) {
return Status::newFatal( 'importnofile' );
}
- if( !empty( $upload['error'] ) ) {
- switch( $upload['error'] ) {
+ if ( !empty( $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.
@@ -1671,7 +1674,7 @@ class ImportStreamSource {
}
$fname = $upload['tmp_name'];
- if( is_uploaded_file( $fname ) ) {
+ if ( is_uploaded_file( $fname ) ) {
return ImportStreamSource::newFromFile( $fname );
} else {
return Status::newFatal( 'importnofile' );
@@ -1690,7 +1693,7 @@ class ImportStreamSource {
# otherwise prevent importing from large sites, such
# as the Wikimedia cluster, etc.
$data = Http::request( $method, $url, array( 'followRedirects' => true ) );
- if( $data !== false ) {
+ if ( $data !== false ) {
$file = tmpfile();
fwrite( $file, $data );
fflush( $file );
@@ -1710,18 +1713,24 @@ class ImportStreamSource {
* @return Status
*/
public static function newFromInterwiki( $interwiki, $page, $history = false, $templates = false, $pageLinkDepth = 0 ) {
- if( $page == '' ) {
+ if ( $page == '' ) {
return Status::newFatal( 'import-noarticle' );
}
$link = Title::newFromText( "$interwiki:Special:Export/$page" );
- if( is_null( $link ) || $link->getInterwiki() == '' ) {
+ if ( is_null( $link ) || $link->getInterwiki() == '' ) {
return Status::newFatal( 'importbadinterwiki' );
} else {
$params = array();
- if ( $history ) $params['history'] = 1;
- if ( $templates ) $params['templates'] = 1;
- if ( $pageLinkDepth ) $params['pagelink-depth'] = $pageLinkDepth;
- $url = $link->getFullUrl( $params );
+ if ( $history ) {
+ $params['history'] = 1;
+ }
+ if ( $templates ) {
+ $params['templates'] = 1;
+ }
+ if ( $pageLinkDepth ) {
+ $params['pagelink-depth'] = $pageLinkDepth;
+ }
+ $url = $link->getFullURL( $params );
# For interwikis, use POST to avoid redirects.
return ImportStreamSource::newFromURL( $url, "POST" );
}
diff --git a/includes/Init.php b/includes/Init.php
index 66f9544d..64431f09 100644
--- a/includes/Init.php
+++ b/includes/Init.php
@@ -2,6 +2,9 @@
/**
* Some functions that are useful during startup.
*
+ * This class previously contained some functionality related to a PHP compiler
+ * called hphpc. That compiler has now been discontinued.
+ *
* 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
@@ -22,40 +25,35 @@
/**
* Some functions that are useful during startup.
+ *
+ * This class previously contained some functionality related to a PHP compiler
+ * called hphpc. That compiler has now been discontinued. All methods are now
+ * deprecated.
*/
class MWInit {
static $compilerVersion;
/**
- * Get the version of HipHop used to compile, or false if MediaWiki was not
- * compiled. This works by having our build script insert a special function
- * into the compiled code.
+ * @deprecated since 1.22
*/
static function getCompilerVersion() {
- if ( self::$compilerVersion === null ) {
- if ( self::functionExists( 'wfHipHopCompilerVersion' ) ) {
- self::$compilerVersion = wfHipHopCompilerVersion();
- } else {
- self::$compilerVersion = false;
- }
- }
- return self::$compilerVersion;
+ return false;
}
/**
* Returns true if we are running under HipHop, whether in compiled or
* interpreted mode.
*
+ * @deprecated since 1.22
* @return bool
*/
static function isHipHop() {
- return function_exists( 'hphp_thread_set_warmup_enabled' );
+ return defined( 'HPHP_VERSION' );
}
/**
- * Get a fully-qualified path for a source file relative to $IP. Including
- * such a path under HipHop will force the file to be interpreted. This is
- * useful for configuration files.
+ * Get a fully-qualified path for a source file relative to $IP.
+ * @deprecated since 1.22
*
* @param $file string
*
@@ -67,117 +65,39 @@ class MWInit {
}
/**
- * If we are running code compiled by HipHop, this will pass through the
- * input path, assumed to be relative to $IP. If the code is interpreted,
- * it will converted to a fully qualified path. It is necessary to use a
- * path which is relative to $IP in order to make HipHop use its compiled
- * code.
- *
+ * @deprecated since 1.22
* @param $file string
- *
* @return string
*/
static function compiledPath( $file ) {
global $IP;
-
- if ( defined( 'MW_COMPILED' ) ) {
- return "phase3/$file";
- } else {
- return "$IP/$file";
- }
- }
-
- /**
- * The equivalent of MWInit::interpretedPath() but for files relative to the
- * extensions directory.
- *
- * @param $file string
- * @return string
- */
- static function extInterpretedPath( $file ) {
- return self::getExtensionsDirectory() . '/' . $file;
+ return "$IP/$file";
}
/**
- * The equivalent of MWInit::compiledPath() but for files relative to the
- * extensions directory. Any files referenced in this way must be registered
- * for compilation by including them in $wgCompiledFiles.
+ * @deprecated since 1.22
* @param $file string
* @return string
*/
static function extCompiledPath( $file ) {
- if ( defined( 'MW_COMPILED' ) ) {
- return "extensions/$file";
- } else {
- return self::getExtensionsDirectory() . '/' . $file;
- }
+ return false;
}
/**
- * Register an extension setup file and return its path for compiled
- * inclusion. Use this function in LocalSettings.php to add extensions
- * to the build. For example:
- *
- * require( MWInit::extSetupPath( 'ParserFunctions/ParserFunctions.php' ) );
- *
- * @param string $extRel The path relative to the extensions directory, as defined by
- * $wgExtensionsDirectory.
- *
- * @return string
- */
- static function extSetupPath( $extRel ) {
- $baseRel = "extensions/$extRel";
- if ( defined( 'MW_COMPILED' ) ) {
- return $baseRel;
- } else {
- global $wgCompiledFiles;
- $wgCompiledFiles[] = $baseRel;
- return self::getExtensionsDirectory() . '/' . $extRel;
- }
- }
-
- /**
- * @return bool|string
- */
- static function getExtensionsDirectory() {
- global $wgExtensionsDirectory, $IP;
- if ( $wgExtensionsDirectory === false ) {
- $wgExtensionsDirectory = "$IP/../extensions";
- }
- return $wgExtensionsDirectory;
- }
-
- /**
- * Determine whether a class exists, using a method which works under HipHop.
- *
- * Note that it's not possible to implement this with any variant of
- * class_exists(), because class_exists() returns false for classes which
- * are compiled in.
- *
- * Calling class_exists() on a literal string causes the class to be made
- * "volatile", which means (as of March 2011) that the class is broken and
- * can't be used at all. So don't do that. See
- * https://github.com/facebook/hiphop-php/issues/314
+ * Deprecated wrapper for class_exists()
+ * @deprecated since 1.22
*
* @param $class string
*
* @return bool
*/
static function classExists( $class ) {
- try {
- $r = new ReflectionClass( $class );
- } catch( ReflectionException $r ) {
- $r = false;
- }
- return $r !== false;
+ return class_exists( $class );
}
/**
- * 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
- * such as to test for a static method has the same issues as class_exists does.
+ * Deprecated wrapper for method_exists()
+ * @deprecated since 1.22
*
* @param $class string
* @param $method string
@@ -185,34 +105,25 @@ class MWInit {
* @return bool
*/
static function methodExists( $class, $method ) {
- try {
- $r = new ReflectionMethod( $class, $method );
- } catch( ReflectionException $r ) {
- $r = false;
- }
- return $r !== false;
+ return method_exists( $class, $method );
}
/**
- * Determine whether a function exists, using a method which works under
- * HipHop.
+ * Deprecated wrapper for function_exists()
+ * @deprecated since 1.22
*
* @param $function string
*
* @return bool
*/
static function functionExists( $function ) {
- try {
- $r = new ReflectionFunction( $function );
- } catch( ReflectionException $r ) {
- $r = false;
- }
- return $r !== false;
+ return function_exists( $function );
}
/**
- * Call a static method of a class with variable arguments without causing
- * it to become volatile.
+ * Deprecated wrapper for call_user_func_array()
+ * @deprecated since 1.22
+ *
* @param $className string
* @param $methodName string
* @param $args array
@@ -220,7 +131,6 @@ class MWInit {
* @return mixed
*/
static function callStaticMethod( $className, $methodName, $args ) {
- $r = new ReflectionMethod( $className, $methodName );
- return $r->invokeArgs( null, $args );
+ return call_user_func_array( array( $className, $methodName ), $args );
}
}
diff --git a/includes/Licenses.php b/includes/Licenses.php
index e8aee0b5..6c582aaf 100644
--- a/includes/Licenses.php
+++ b/includes/Licenses.php
@@ -105,7 +105,7 @@ class Licenses extends HTMLFormField {
protected function stackItem( &$list, $path, $item ) {
$position =& $list;
if ( $path ) {
- foreach( $path as $key ) {
+ foreach ( $path as $key ) {
$position =& $position[$key];
}
}
diff --git a/includes/LinkFilter.php b/includes/LinkFilter.php
index 11b65595..d552c696 100644
--- a/includes/LinkFilter.php
+++ b/includes/LinkFilter.php
@@ -152,11 +152,11 @@ class LinkFilter {
* @return array filtered array
*/
public static function keepOneWildcard( $arr ) {
- if( !is_array( $arr ) ) {
+ if ( !is_array( $arr ) ) {
return $arr;
}
- foreach( $arr as $key => $value ) {
+ foreach ( $arr as $key => $value ) {
if ( $value instanceof LikeMatch ) {
return array_slice( $arr, 0, $key + 1 );
}
diff --git a/includes/Linker.php b/includes/Linker.php
index 972adfce..23ece751 100644
--- a/includes/Linker.php
+++ b/includes/Linker.php
@@ -199,7 +199,7 @@ class Linker {
return "<!-- ERROR -->$html";
}
- if( is_string( $query ) ) {
+ if ( is_string( $query ) ) {
// some functions withing core using this still hand over query strings
wfDeprecated( __METHOD__ . ' with parameter $query as string (should be array)', '1.20' );
$query = wfCgiToArray( $query );
@@ -238,7 +238,7 @@ class Linker {
# Note: we want the href attribute first, for prettiness.
$attribs = array( 'href' => self::linkUrl( $target, $query, $options ) );
if ( in_array( 'forcearticlepath', $options ) && $oldquery ) {
- $attribs['href'] = wfAppendQuery( $attribs['href'], wfArrayToCgi( $oldquery ) );
+ $attribs['href'] = wfAppendQuery( $attribs['href'], $oldquery );
}
$attribs = array_merge(
@@ -612,7 +612,7 @@ class Linker {
$hp['width'] = $file->getWidth( $page );
}
- if ( isset( $fp['thumbnail'] ) || isset( $fp['framed'] ) || isset( $fp['frameless'] ) || !$hp['width'] ) {
+ if ( isset( $fp['thumbnail'] ) || isset( $fp['manualthumb'] ) || isset( $fp['framed'] ) || isset( $fp['frameless'] ) || !$hp['width'] ) {
global $wgThumbLimits, $wgThumbUpright;
if ( $widthOption === null || !isset( $wgThumbLimits[$widthOption] ) ) {
$widthOption = User::getDefaultOption( 'thumbsize' );
@@ -644,7 +644,7 @@ class Linker {
# If a thumbnail width has not been provided, it is set
# to the default user option as specified in Language*.php
if ( $fp['align'] == '' ) {
- if( $parser instanceof Parser ) {
+ if ( $parser instanceof Parser ) {
$fp['align'] = $parser->getTargetLanguage()->alignEnd();
} else {
# backwards compatibility, remove with makeImageLink2()
@@ -786,10 +786,18 @@ class Linker {
$hp =& $handlerParams;
$page = isset( $hp['page'] ) ? $hp['page'] : false;
- if ( !isset( $fp['align'] ) ) $fp['align'] = 'right';
- if ( !isset( $fp['alt'] ) ) $fp['alt'] = '';
- if ( !isset( $fp['title'] ) ) $fp['title'] = '';
- if ( !isset( $fp['caption'] ) ) $fp['caption'] = '';
+ if ( !isset( $fp['align'] ) ) {
+ $fp['align'] = 'right';
+ }
+ if ( !isset( $fp['alt'] ) ) {
+ $fp['alt'] = '';
+ }
+ if ( !isset( $fp['title'] ) ) {
+ $fp['title'] = '';
+ }
+ if ( !isset( $fp['caption'] ) ) {
+ $fp['caption'] = '';
+ }
if ( empty( $hp['width'] ) ) {
// Reduce width for upright images when parameter 'upright' is used
@@ -797,6 +805,7 @@ class Linker {
}
$thumb = false;
$noscale = false;
+ $manualthumb = false;
if ( !$exists ) {
$outerWidth = $hp['width'] + 2;
@@ -808,6 +817,7 @@ class Linker {
$manual_img = wfFindFile( $manual_title );
if ( $manual_img ) {
$thumb = $manual_img->getUnscaledThumb( $hp );
+ $manualthumb = true;
} else {
$exists = false;
}
@@ -838,7 +848,13 @@ class Linker {
# zoom icon still needs it, so we make a unique query for it. See bug 14771
$url = $title->getLocalURL( $query );
if ( $page ) {
- $url = wfAppendQuery( $url, 'page=' . urlencode( $page ) );
+ $url = wfAppendQuery( $url, array( 'page' => $page ) );
+ }
+ if ( $manualthumb &&
+ !isset( $fp['link-title'] ) &&
+ !isset( $fp['link-url'] ) &&
+ !isset( $fp['no-link'] ) ) {
+ $fp['link-url'] = $url;
}
$s = "<div class=\"thumb t{$fp['align']}\"><div class=\"thumbinner\" style=\"width:{$outerWidth}px;\">";
@@ -849,7 +865,7 @@ class Linker {
$s .= wfMessage( 'thumbnail_error', '' )->escaped();
$zoomIcon = '';
} else {
- if ( !$noscale ) {
+ if ( !$noscale && !$manualthumb ) {
self::processResponsiveImages( $file, $thumb, $hp );
}
$params = array(
@@ -961,17 +977,18 @@ class Linker {
*/
protected static function getUploadUrl( $destFile, $query = '' ) {
global $wgUploadMissingFileUrl, $wgUploadNavigationUrl;
- $q = 'wpDestFile=' . $destFile->getPartialUrl();
- if ( $query != '' )
+ $q = 'wpDestFile=' . $destFile->getPartialURL();
+ if ( $query != '' ) {
$q .= '&' . $query;
+ }
if ( $wgUploadMissingFileUrl ) {
return wfAppendQuery( $wgUploadMissingFileUrl, $q );
- } elseif( $wgUploadNavigationUrl ) {
+ } elseif ( $wgUploadNavigationUrl ) {
return wfAppendQuery( $wgUploadNavigationUrl, $q );
} else {
$upload = SpecialPage::getTitleFor( 'Upload' );
- return $upload->getLocalUrl( $q );
+ return $upload->getLocalURL( $q );
}
}
@@ -1442,8 +1459,9 @@ class Linker {
$trail = "";
}
$linkRegexp = '/\[\[(.*?)\]\]' . preg_quote( $trail, '/' ) . '/';
- if ( isset( $match[1][0] ) && $match[1][0] == ':' )
+ if ( isset( $match[1][0] ) && $match[1][0] == ':' ) {
$match[1] = substr( $match[1], 1 );
+ }
list( $inside, $trail ) = self::splitTrail( $trail );
$linkText = $text;
@@ -1527,7 +1545,7 @@ class Linker {
$nodotdot = substr( $nodotdot, 3 );
}
if ( $dotdotcount > 0 ) {
- $exploded = explode( '/', $contextTitle->GetPrefixedText() );
+ $exploded = explode( '/', $contextTitle->getPrefixedText() );
if ( count( $exploded ) > $dotdotcount ) { # not allowed to go below top level page
$ret = implode( '/', array_slice( $exploded, 0, -$dotdotcount ) );
# / at the end means don't show full path
@@ -1670,10 +1688,10 @@ class Linker {
$lang = wfGetLangObj( $lang );
$title = wfMessage( 'toc' )->inLanguage( $lang )->escaped();
- return '<table id="toc" class="toc"><tr><td>'
+ return '<div id="toc" class="toc">'
. '<div id="toctitle"><h2>' . $title . "</h2></div>\n"
. $toc
- . "</ul>\n</td></tr></table>\n";
+ . "</ul>\n</div>\n";
}
/**
@@ -1687,13 +1705,14 @@ class Linker {
$toc = '';
$lastLevel = 0;
foreach ( $tree as $section ) {
- if ( $section['toclevel'] > $lastLevel )
+ if ( $section['toclevel'] > $lastLevel ) {
$toc .= self::tocIndent();
- elseif ( $section['toclevel'] < $lastLevel )
+ } elseif ( $section['toclevel'] < $lastLevel ) {
$toc .= self::tocUnindent(
$lastLevel - $section['toclevel'] );
- else
+ } else {
$toc .= self::tocLineEnd();
+ }
$toc .= self::tocLine( $section['anchor'],
$section['line'], $section['number'],
@@ -1721,8 +1740,8 @@ class Linker {
*/
public static function makeHeadline( $level, $attribs, $anchor, $html, $link, $legacyAnchor = false ) {
$ret = "<h$level$attribs"
+ . "<span class=\"mw-headline\" id=\"$anchor\">$html</span>"
. $link
- . " <span class=\"mw-headline\" id=\"$anchor\">$html</span>"
. "</h$level>";
if ( $legacyAnchor !== false ) {
$ret = "<div id=\"$legacyAnchor\"></div>$ret";
@@ -1887,21 +1906,21 @@ class Linker {
}
$disableRollbackEditCount = false;
- if( $wgMiserMode ) {
- foreach( $disableRollbackEditCountSpecialPage as $specialPage ) {
- if( $context->getTitle()->isSpecial( $specialPage ) ) {
+ if ( $wgMiserMode ) {
+ foreach ( $disableRollbackEditCountSpecialPage as $specialPage ) {
+ if ( $context->getTitle()->isSpecial( $specialPage ) ) {
$disableRollbackEditCount = true;
break;
}
}
}
- if( !$disableRollbackEditCount && is_int( $wgShowRollbackEditCount ) && $wgShowRollbackEditCount > 0 ) {
+ if ( !$disableRollbackEditCount && is_int( $wgShowRollbackEditCount ) && $wgShowRollbackEditCount > 0 ) {
if ( !is_numeric( $editCount ) ) {
$editCount = self::getRollbackEditCount( $rev, false );
}
- if( $editCount > $wgShowRollbackEditCount ) {
+ if ( $editCount > $wgShowRollbackEditCount ) {
$editCount_output = $context->msg( 'rollbacklinkcount-morethan' )->numParams( $wgShowRollbackEditCount )->parse();
} else {
$editCount_output = $context->msg( 'rollbacklinkcount' )->numParams( $editCount )->parse();
@@ -1941,6 +1960,7 @@ class Linker {
* @return String: HTML output
*/
public static function formatTemplates( $templates, $preview = false, $section = false, $more = null ) {
+ global $wgLang;
wfProfileIn( __METHOD__ );
$outText = '';
@@ -1968,13 +1988,28 @@ class Linker {
usort( $templates, 'Title::compare' );
foreach ( $templates as $titleObj ) {
- $r = $titleObj->getRestrictions( 'edit' );
- if ( in_array( 'sysop', $r ) ) {
- $protected = wfMessage( 'template-protected' )->parse();
- } elseif ( in_array( 'autoconfirmed', $r ) ) {
- $protected = wfMessage( 'template-semiprotected' )->parse();
- } else {
- $protected = '';
+ $protected = '';
+ $restrictions = $titleObj->getRestrictions( 'edit' );
+ if ( $restrictions ) {
+ // Check backwards-compatible messages
+ $msg = null;
+ if ( $restrictions === array( 'sysop' ) ) {
+ $msg = wfMessage( 'template-protected' );
+ } elseif ( $restrictions === array( 'autoconfirmed' ) ) {
+ $msg = wfMessage( 'template-semiprotected' );
+ }
+ if ( $msg && !$msg->isDisabled() ) {
+ $protected = $msg->parse();
+ } else {
+ // Construct the message from restriction-level-*
+ // e.g. restriction-level-sysop, restriction-level-autoconfirmed
+ $msgs = array();
+ foreach ( $restrictions as $r ) {
+ $msgs[] = wfMessage( "restriction-level-$r" )->parse();
+ }
+ $protected = wfMessage( 'parentheses' )
+ ->rawParams( $wgLang->commaList( $msgs ) )->escaped();
+ }
}
if ( $titleObj->quickUserCan( 'edit' ) ) {
$editLink = self::link(
diff --git a/includes/LinksUpdate.php b/includes/LinksUpdate.php
index d99ae22d..fdd0e3c1 100644
--- a/includes/LinksUpdate.php
+++ b/includes/LinksUpdate.php
@@ -27,7 +27,7 @@
*/
class LinksUpdate extends SqlDataUpdate {
- // @todo: make members protected, but make sure extensions don't break
+ // @todo make members protected, but make sure extensions don't break
public $mId, //!< Page ID of the article linked from
$mTitle, //!< Title object of the article linked from
@@ -44,6 +44,16 @@ class LinksUpdate extends SqlDataUpdate {
$mRecursive; //!< Whether to queue jobs for recursive updates
/**
+ * @var null|array Added links if calculated.
+ */
+ private $linkInsertions = null;
+
+ /**
+ * @var null|array Deleted links if calculated.
+ */
+ private $linkDeletions = null;
+
+ /**
* Constructor
*
* @param $title Title of the page we're updating
@@ -112,14 +122,8 @@ class LinksUpdate extends SqlDataUpdate {
* Update link tables with outgoing links from an updated article
*/
public function doUpdate() {
- global $wgUseDumbLinkUpdate;
-
wfRunHooks( 'LinksUpdate', array( &$this ) );
- if ( $wgUseDumbLinkUpdate ) {
- $this->doDumbUpdate();
- } else {
- $this->doIncrementalUpdate();
- }
+ $this->doIncrementalUpdate();
wfRunHooks( 'LinksUpdateComplete', array( &$this ) );
}
@@ -128,8 +132,9 @@ class LinksUpdate extends SqlDataUpdate {
# Page links
$existing = $this->getExistingLinks();
- $this->incrTableUpdate( 'pagelinks', 'pl', $this->getLinkDeletions( $existing ),
- $this->getLinkInsertions( $existing ) );
+ $this->linkDeletions = $this->getLinkDeletions( $existing );
+ $this->linkInsertions = $this->getLinkInsertions( $existing );
+ $this->incrTableUpdate( 'pagelinks', 'pl', $this->linkDeletions, $this->linkInsertions );
# Image links
$existing = $this->getExistingImages();
@@ -198,61 +203,35 @@ class LinksUpdate extends SqlDataUpdate {
}
/**
- * Link update which clears the previous entries and inserts new ones
- * May be slower or faster depending on level of lock contention and write speed of DB
- * Also useful where link table corruption needs to be repaired, e.g. in refreshLinks.php
+ * Queue recursive jobs for this page
+ *
+ * Which means do LinksUpdate on all templates
+ * that include the current page, using the job queue.
*/
- protected function doDumbUpdate() {
- wfProfileIn( __METHOD__ );
-
- # Refresh category pages and image description pages
- $existing = $this->getExistingCategories();
- $categoryInserts = array_diff_assoc( $this->mCategories, $existing );
- $categoryDeletes = array_diff_assoc( $existing, $this->mCategories );
- $categoryUpdates = $categoryInserts + $categoryDeletes;
- $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( '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' );
-
- # Update the cache of all the category pages and image description
- # pages which were changed, and fix the category table count
- $this->invalidateCategories( $categoryUpdates );
- $this->updateCategoryCounts( $categoryInserts, $categoryDeletes );
- $this->invalidateImageDescriptions( $imageUpdates );
-
- # Refresh links of all pages including this page
- # This will be in a separate transaction
- if ( $this->mRecursive ) {
- $this->queueRecursiveJobs();
- }
-
- wfProfileOut( __METHOD__ );
+ function queueRecursiveJobs() {
+ self::queueRecursiveJobsForTable( $this->mTitle, 'templatelinks' );
}
- function queueRecursiveJobs() {
+ /**
+ * Queue a RefreshLinks job for any table.
+ *
+ * @param Title $title Title to do job for
+ * @param String $table Table to use (e.g. 'templatelinks')
+ */
+ public static function queueRecursiveJobsForTable( Title $title, $table ) {
wfProfileIn( __METHOD__ );
-
- if ( $this->mTitle->getBacklinkCache()->hasLinks( 'templatelinks' ) ) {
+ if ( $title->getBacklinkCache()->hasLinks( $table ) ) {
$job = new RefreshLinksJob2(
- $this->mTitle,
+ $title,
array(
- 'table' => 'templatelinks',
+ 'table' => $table,
) + Job::newRootJobParams( // "overall" refresh links job info
- "refreshlinks:templatelinks:{$this->mTitle->getPrefixedText()}"
+ "refreshlinks:{$table}:{$title->getPrefixedText()}"
)
);
JobQueueGroup::singleton()->push( $job );
JobQueueGroup::singleton()->deduplicateRootJob( $job );
}
-
wfProfileOut( __METHOD__ );
}
@@ -283,21 +262,6 @@ class LinksUpdate extends SqlDataUpdate {
}
/**
- * @param $table
- * @param $insertions
- * @param $fromField
- */
- private function dumbTableUpdate( $table, $insertions, $fromField ) {
- $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,
- # which is better than crippling the site with lock contention.
- $this->mDb->insert( $table, $insertions, __METHOD__, array( 'IGNORE' ) );
- }
- }
-
- /**
* Update a table by doing a delete query then an insert query
* @param $table
* @param $prefix
@@ -354,7 +318,7 @@ class LinksUpdate extends SqlDataUpdate {
*/
private function getLinkInsertions( $existing = array() ) {
$arr = array();
- foreach( $this->mLinks as $ns => $dbkeys ) {
+ foreach ( $this->mLinks as $ns => $dbkeys ) {
$diffs = isset( $existing[$ns] )
? array_diff_key( $dbkeys, $existing[$ns] )
: $dbkeys;
@@ -376,7 +340,7 @@ class LinksUpdate extends SqlDataUpdate {
*/
private function getTemplateInsertions( $existing = array() ) {
$arr = array();
- foreach( $this->mTemplates as $ns => $dbkeys ) {
+ foreach ( $this->mTemplates as $ns => $dbkeys ) {
$diffs = isset( $existing[$ns] ) ? array_diff_key( $dbkeys, $existing[$ns] ) : $dbkeys;
foreach ( $diffs as $dbk => $id ) {
$arr[] = array(
@@ -398,7 +362,7 @@ class LinksUpdate extends SqlDataUpdate {
private function getImageInsertions( $existing = array() ) {
$arr = array();
$diffs = array_diff_key( $this->mImages, $existing );
- foreach( $diffs as $iname => $dummy ) {
+ foreach ( $diffs as $iname => $dummy ) {
$arr[] = array(
'il_from' => $this->mId,
'il_to' => $iname
@@ -415,8 +379,8 @@ class LinksUpdate extends SqlDataUpdate {
private function getExternalInsertions( $existing = array() ) {
$arr = array();
$diffs = array_diff_key( $this->mExternals, $existing );
- foreach( $diffs as $url => $dummy ) {
- foreach( wfMakeUrlIndexes( $url ) as $index ) {
+ foreach ( $diffs as $url => $dummy ) {
+ foreach ( wfMakeUrlIndexes( $url ) as $index ) {
$arr[] = array(
'el_from' => $this->mId,
'el_to' => $url,
@@ -481,7 +445,7 @@ class LinksUpdate extends SqlDataUpdate {
private function getInterlangInsertions( $existing = array() ) {
$diffs = array_diff_assoc( $this->mInterlangs, $existing );
$arr = array();
- foreach( $diffs as $lang => $title ) {
+ foreach ( $diffs as $lang => $title ) {
$arr[] = array(
'll_from' => $this->mId,
'll_lang' => $lang,
@@ -517,7 +481,7 @@ class LinksUpdate extends SqlDataUpdate {
*/
private function getInterwikiInsertions( $existing = array() ) {
$arr = array();
- foreach( $this->mInterwikis as $prefix => $dbkeys ) {
+ foreach ( $this->mInterwikis as $prefix => $dbkeys ) {
$diffs = isset( $existing[$prefix] ) ? array_diff_key( $dbkeys, $existing[$prefix] ) : $dbkeys;
foreach ( $diffs as $dbk => $id ) {
$arr[] = array(
@@ -807,6 +771,40 @@ class LinksUpdate extends SqlDataUpdate {
}
}
}
+
+ /**
+ * Fetch page links added by this LinksUpdate. Only available after the update is complete.
+ * @since 1.22
+ * @return null|array of Titles
+ */
+ public function getAddedLinks() {
+ if ( $this->linkInsertions === null ) {
+ return null;
+ }
+ $result = array();
+ foreach ( $this->linkInsertions as $insertion ) {
+ $result[] = Title::makeTitle( $insertion[ 'pl_namespace' ], $insertion[ 'pl_title' ] );
+ }
+ return $result;
+ }
+
+ /**
+ * Fetch page links removed by this LinksUpdate. Only available after the update is complete.
+ * @since 1.22
+ * @return null|array of Titles
+ */
+ public function getRemovedLinks() {
+ if ( $this->linkDeletions === null ) {
+ return null;
+ }
+ $result = array();
+ foreach ( $this->linkDeletions as $ns => $titles ) {
+ foreach ( $titles as $title => $unused ) {
+ $result[] = Title::makeTitle( $ns, $title );
+ }
+ }
+ return $result;
+ }
}
/**
@@ -840,22 +838,20 @@ class LinksDeletionUpdate extends SqlDataUpdate {
$id = $this->mPage->getId();
# Delete restrictions for it
- $this->mDb->delete( 'page_restrictions', array ( 'pr_page' => $id ), __METHOD__ );
+ $this->mDb->delete( 'page_restrictions', array( 'pr_page' => $id ), __METHOD__ );
# Fix category table counts
$cats = array();
$res = $this->mDb->select( 'categorylinks', 'cl_to', array( 'cl_from' => $id ), __METHOD__ );
foreach ( $res as $row ) {
- $cats [] = $row->cl_to;
+ $cats[] = $row->cl_to;
}
$this->mPage->updateCategoryCounts( array(), $cats );
# If using cascading deletes, we can skip some explicit deletes
if ( !$this->mDb->cascadingDeletes() ) {
- $this->mDb->delete( 'revision', array( 'rev_page' => $id ), __METHOD__ );
-
# Delete outgoing links
$this->mDb->delete( 'pagelinks', array( 'pl_from' => $id ), __METHOD__ );
$this->mDb->delete( 'imagelinks', array( 'il_from' => $id ), __METHOD__ );
diff --git a/includes/CryptRand.php b/includes/MWCryptRand.php
index d0305d8b..bac018e8 100644
--- a/includes/CryptRand.php
+++ b/includes/MWCryptRand.php
@@ -106,7 +106,7 @@ class MWCryptRand {
}
}
// The absolute filename itself will differ from install to install so don't leave it out
- if( ( $path = realpath( $file ) ) !== false ) {
+ if ( ( $path = realpath( $file ) ) !== false ) {
$state .= $path;
} else {
$state .= $file;
diff --git a/includes/MWFunction.php b/includes/MWFunction.php
index 36fcc30b..6d11d178 100644
--- a/includes/MWFunction.php
+++ b/includes/MWFunction.php
@@ -23,48 +23,24 @@
class MWFunction {
/**
- * @param $callback
- * @return array
- * @throws MWException
- */
- protected static function cleanCallback( $callback ) {
- if( is_string( $callback ) ) {
- if ( strpos( $callback, '::' ) !== false ) {
- // PHP 5.1 cannot use call_user_func( 'Class::Method' )
- // It can only handle only call_user_func( array( 'Class', 'Method' ) )
- $callback = explode( '::', $callback, 2);
- }
- }
-
- if( count( $callback ) == 2 && $callback[0] == 'self' || $callback[0] == 'parent' ) {
- throw new MWException( 'MWFunction cannot call self::method() or parent::method()' );
- }
-
- // Run autoloader (workaround for call_user_func_array bug: http://bugs.php.net/bug.php?id=51329)
- is_callable( $callback );
-
- return $callback;
- }
-
- /**
+ * @deprecated since 1.22; use call_user_func()
* @param $callback
* @return mixed
*/
public static function call( $callback ) {
- $callback = self::cleanCallback( $callback );
-
+ wfDeprecated( __METHOD__, '1.22' );
$args = func_get_args();
-
return call_user_func_array( 'call_user_func', $args );
}
/**
+ * @deprecated since 1.22; use call_user_func_array()
* @param $callback
* @param $argsarams
* @return mixed
*/
public static function callArray( $callback, $argsarams ) {
- $callback = self::cleanCallback( $callback );
+ wfDeprecated( __METHOD__, '1.22' );
return call_user_func_array( $callback, $argsarams );
}
@@ -74,7 +50,7 @@ class MWFunction {
* @return object
*/
public static function newObj( $class, $args = array() ) {
- if( !count( $args ) ) {
+ if ( !count( $args ) ) {
return new $class;
}
diff --git a/includes/MagicWord.php b/includes/MagicWord.php
index 7b669249..427a1adc 100644
--- a/includes/MagicWord.php
+++ b/includes/MagicWord.php
@@ -124,6 +124,7 @@ class MagicWord {
'revisionyear',
'revisiontimestamp',
'revisionuser',
+ 'revisionsize',
'subpagename',
'subpagenamee',
'talkspace',
@@ -138,6 +139,8 @@ class MagicWord {
'numberofactiveusers',
'numberofpages',
'currentversion',
+ 'rootpagename',
+ 'rootpagenamee',
'basepagename',
'basepagenamee',
'currenttimestamp',
@@ -149,7 +152,7 @@ class MagicWord {
);
/* Array of caching hints for ParserCache */
- static public $mCacheTTLs = array (
+ static public $mCacheTTLs = array(
'currentmonth' => 86400,
'currentmonth1' => 86400,
'currentmonthname' => 86400,
@@ -308,9 +311,9 @@ class MagicWord {
$this->mId = $id;
$wgContLang->getMagic( $this );
if ( !$this->mSynonyms ) {
- $this->mSynonyms = array( 'dkjsagfjsgashfajsh' );
+ $this->mSynonyms = array( 'brionmademeputthishere' );
+ wfProfileOut( __METHOD__ );
throw new MWException( "Error: invalid magic word '$id'" );
- #wfDebugLog( 'exception', "Error: invalid magic word '$id'\n" );
}
wfProfileOut( __METHOD__ );
}
@@ -326,9 +329,10 @@ class MagicWord {
usort( $synonyms, array( $this, 'compareStringLength' ) );
$escSyn = array();
- foreach ( $synonyms as $synonym )
+ foreach ( $synonyms as $synonym ) {
// In case a magic word contains /, like that's going to happen;)
$escSyn[] = preg_quote( $synonym, '/' );
+ }
$this->mBaseRegex = implode( '|', $escSyn );
$case = $this->mCaseSensitive ? '' : 'iu';
@@ -381,8 +385,9 @@ class MagicWord {
* @return string
*/
function getRegexCase() {
- if ( $this->mRegex === '' )
+ if ( $this->mRegex === '' ) {
$this->initRegex();
+ }
return $this->mCaseSensitive ? '' : 'iu';
}
@@ -509,7 +514,7 @@ class MagicWord {
*/
function replace( $replacement, $subject, $limit = -1 ) {
$res = preg_replace( $this->getRegex(), StringUtils::escapeRegexReplacement( $replacement ), $subject, $limit );
- $this->mModified = !($res === $subject);
+ $this->mModified = $res !== $subject;
return $res;
}
@@ -525,7 +530,7 @@ class MagicWord {
*/
function substituteCallback( $text, $callback ) {
$res = preg_replace_callback( $this->getVariableRegex(), $callback, $text );
- $this->mModified = !($res === $text);
+ $this->mModified = $res !== $text;
return $res;
}
@@ -597,14 +602,14 @@ class MagicWord {
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;
}
$result = preg_replace( $search, $replace, $subject );
- return !($result === $subject);
+ return $result !== $subject;
}
/**
diff --git a/includes/MappedIterator.php b/includes/MappedIterator.php
index b4376f44..70d20327 100644
--- a/includes/MappedIterator.php
+++ b/includes/MappedIterator.php
@@ -26,11 +26,15 @@
*
* @since 1.21
*/
-class MappedIterator implements Iterator {
- /** @var Iterator */
- protected $baseIterator;
- /** @var Closure */
+class MappedIterator extends FilterIterator {
+ /** @var callable */
protected $vCallback;
+ /** @var callable */
+ protected $aCallback;
+ /** @var array */
+ protected $cache = array();
+
+ protected $rewound = false; // boolean; whether rewind() has been called
/**
* Build an new iterator from a base iterator by having the former wrap the
@@ -38,59 +42,73 @@ class MappedIterator implements Iterator {
* The callback takes the result of current() on the base iterator as an argument.
* The keys of the base iterator are reused verbatim.
*
+ * An "accept" callback can also be provided which will be called for each value in
+ * the base iterator (post-callback) and will return true if that value should be
+ * included in iteration of the MappedIterator (otherwise it will be filtered out).
+ *
* @param Iterator|Array $iter
- * @param Closure $vCallback
+ * @param callable $vCallback Value transformation callback
+ * @param array $options Options map (includes "accept") (since 1.22)
* @throws MWException
*/
- public function __construct( $iter, Closure $vCallback ) {
+ public function __construct( $iter, $vCallback, array $options = array() ) {
if ( is_array( $iter ) ) {
- $this->baseIterator = new ArrayIterator( $iter );
+ $baseIterator = new ArrayIterator( $iter );
} elseif ( $iter instanceof Iterator ) {
- $this->baseIterator = $iter;
+ $baseIterator = $iter;
} else {
throw new MWException( "Invalid base iterator provided." );
}
+ parent::__construct( $baseIterator );
$this->vCallback = $vCallback;
- }
+ $this->aCallback = isset( $options['accept'] ) ? $options['accept'] : null;
+ }
+
+ public function next() {
+ $this->cache = array();
+ parent::next();
+ }
- /**
- * @return void
- */
public function rewind() {
- $this->baseIterator->rewind();
+ $this->rewound = true;
+ $this->cache = array();
+ parent::rewind();
}
- /**
- * @return Mixed|null Returns null if out of range
- */
- public function current() {
- if ( !$this->baseIterator->valid() ) {
- return null; // out of range
+ public function accept() {
+ $value = call_user_func( $this->vCallback, $this->getInnerIterator()->current() );
+ $ok = ( $this->aCallback ) ? call_user_func( $this->aCallback, $value ) : true;
+ if ( $ok ) {
+ $this->cache['current'] = $value;
}
- return call_user_func_array( $this->vCallback, array( $this->baseIterator->current() ) );
+ return $ok;
}
- /**
- * @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();
+ $this->init();
+ return parent::key();
}
- /**
- * @return void
- */
- public function next() {
- $this->baseIterator->next();
+ public function valid() {
+ $this->init();
+ return parent::valid();
+ }
+
+ public function current() {
+ $this->init();
+ if ( parent::valid() ) {
+ return $this->cache['current'];
+ } else {
+ return null; // out of range
+ }
}
/**
- * @return bool
+ * Obviate the usual need for rewind() before using a FilterIterator in a manual loop
*/
- public function valid() {
- return $this->baseIterator->valid();
+ protected function init() {
+ if ( !$this->rewound ) {
+ $this->rewind();
+ }
}
}
diff --git a/includes/Message.php b/includes/Message.php
index 5719f830..57c6264d 100644
--- a/includes/Message.php
+++ b/includes/Message.php
@@ -234,7 +234,13 @@ class Message {
* @return string
*/
public function getKey() {
- return $this->key;
+ if ( is_array( $this->key ) ) {
+ // May happen if some kind of fallback is applied.
+ // For now, just use the first key. We really need a better solution.
+ return $this->key[0];
+ } else {
+ return $this->key;
+ }
}
/**
@@ -326,7 +332,7 @@ class Message {
if ( isset( $params[0] ) && is_array( $params[0] ) ) {
$params = $params[0];
}
- foreach( $params as $param ) {
+ foreach ( $params as $param ) {
$this->parameters[] = self::rawParam( $param );
}
return $this;
@@ -344,13 +350,103 @@ class Message {
if ( isset( $params[0] ) && is_array( $params[0] ) ) {
$params = $params[0];
}
- foreach( $params as $param ) {
+ foreach ( $params as $param ) {
$this->parameters[] = self::numParam( $param );
}
return $this;
}
/**
+ * Add parameters that are durations of time and will be passed through
+ * Language::formatDuration before substitution
+ * @since 1.22
+ * @param Varargs: numeric parameters (or single argument that is array of numeric parameters)
+ * @return Message: $this
+ */
+ public function durationParams( /*...*/ ) {
+ $params = func_get_args();
+ if ( isset( $params[0] ) && is_array( $params[0] ) ) {
+ $params = $params[0];
+ }
+ foreach ( $params as $param ) {
+ $this->parameters[] = self::durationParam( $param );
+ }
+ return $this;
+ }
+
+ /**
+ * Add parameters that are expiration times and will be passed through
+ * Language::formatExpiry before substitution
+ * @since 1.22
+ * @param Varargs: numeric parameters (or single argument that is array of numeric parameters)
+ * @return Message: $this
+ */
+ public function expiryParams( /*...*/ ) {
+ $params = func_get_args();
+ if ( isset( $params[0] ) && is_array( $params[0] ) ) {
+ $params = $params[0];
+ }
+ foreach ( $params as $param ) {
+ $this->parameters[] = self::expiryParam( $param );
+ }
+ return $this;
+ }
+
+ /**
+ * Add parameters that are time periods and will be passed through
+ * Language::formatTimePeriod before substitution
+ * @since 1.22
+ * @param Varargs: numeric parameters (or single argument that is array of numeric parameters)
+ * @return Message: $this
+ */
+ public function timeperiodParams( /*...*/ ) {
+ $params = func_get_args();
+ if ( isset( $params[0] ) && is_array( $params[0] ) ) {
+ $params = $params[0];
+ }
+ foreach ( $params as $param ) {
+ $this->parameters[] = self::timeperiodParam( $param );
+ }
+ return $this;
+ }
+
+ /**
+ * Add parameters that are file sizes and will be passed through
+ * Language::formatSize before substitution
+ * @since 1.22
+ * @param Varargs: numeric parameters (or single argument that is array of numeric parameters)
+ * @return Message: $this
+ */
+ public function sizeParams( /*...*/ ) {
+ $params = func_get_args();
+ if ( isset( $params[0] ) && is_array( $params[0] ) ) {
+ $params = $params[0];
+ }
+ foreach ( $params as $param ) {
+ $this->parameters[] = self::sizeParam( $param );
+ }
+ return $this;
+ }
+
+ /**
+ * Add parameters that are bitrates and will be passed through
+ * Language::formatBitrate before substitution
+ * @since 1.22
+ * @param Varargs: numeric parameters (or single argument that is array of numeric parameters)
+ * @return Message: $this
+ */
+ public function bitrateParams( /*...*/ ) {
+ $params = func_get_args();
+ if ( isset( $params[0] ) && is_array( $params[0] ) ) {
+ $params = $params[0];
+ }
+ foreach ( $params as $param ) {
+ $this->parameters[] = self::bitrateParam( $param );
+ }
+ return $this;
+ }
+
+ /**
* Set the language and the title from a context object
* @since 1.19
* @param $context IContextSource
@@ -377,7 +473,7 @@ class Message {
if ( $lang instanceof Language || $lang instanceof StubUserLang ) {
$this->language = $lang;
} elseif ( is_string( $lang ) ) {
- if( $this->language->getCode() != $lang ) {
+ if ( $this->language->getCode() != $lang ) {
$this->language = Language::factory( $lang );
}
} else {
@@ -417,7 +513,7 @@ class Message {
* @since 1.20
*/
public function setInterfaceMessageFlag( $value ) {
- $this->interface = (bool) $value;
+ $this->interface = (bool)$value;
return $this;
}
@@ -428,7 +524,7 @@ class Message {
* @return Message: $this
*/
public function useDatabase( $value ) {
- $this->useDatabase = (bool) $value;
+ $this->useDatabase = (bool)$value;
return $this;
}
@@ -484,17 +580,17 @@ class Message {
$string = $this->replaceParameters( $string, 'before' );
# Maybe transform using the full parser
- if( $this->format === 'parse' ) {
+ if ( $this->format === 'parse' ) {
$string = $this->parseText( $string );
$m = array();
- if( preg_match( '/^<p>(.*)\n?<\/p>\n?$/sU', $string, $m ) ) {
+ 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 );
}
@@ -633,6 +729,51 @@ class Message {
}
/**
+ * @since 1.22
+ * @param $value
+ * @return array
+ */
+ public static function durationParam( $value ) {
+ return array( 'duration' => $value );
+ }
+
+ /**
+ * @since 1.22
+ * @param $value
+ * @return array
+ */
+ public static function expiryParam( $value ) {
+ return array( 'expiry' => $value );
+ }
+
+ /**
+ * @since 1.22
+ * @param $value
+ * @return array
+ */
+ public static function timeperiodParam( $value ) {
+ return array( 'period' => $value );
+ }
+
+ /**
+ * @since 1.22
+ * @param $value
+ * @return array
+ */
+ public static function sizeParam( $value ) {
+ return array( 'size' => $value );
+ }
+
+ /**
+ * @since 1.22
+ * @param $value
+ * @return array
+ */
+ public static function bitrateParam( $value ) {
+ return array( 'bitrate' => $value );
+ }
+
+ /**
* Substitutes any parameters into the message text.
* @since 1.17
* @param string $message the message text
@@ -641,10 +782,10 @@ class Message {
*/
protected function replaceParameters( $message, $type = 'before' ) {
$replacementKeys = array();
- foreach( $this->parameters as $n => $param ) {
+ foreach ( $this->parameters as $n => $param ) {
list( $paramType, $value ) = $this->extractParam( $param );
if ( $type === $paramType ) {
- $replacementKeys['$' . ($n + 1)] = $value;
+ $replacementKeys['$' . ( $n + 1 )] = $value;
}
}
$message = strtr( $message, $replacementKeys );
@@ -658,20 +799,37 @@ class Message {
* @return Tuple(type, value)
*/
protected function extractParam( $param ) {
- if ( is_array( $param ) && isset( $param['raw'] ) ) {
- return array( 'after', $param['raw'] );
- } elseif ( is_array( $param ) && isset( $param['num'] ) ) {
- // Replace number params always in before step for now.
- // No support for combined raw and num params
- return array( 'before', $this->language->formatNum( $param['num'] ) );
- } elseif ( !is_array( $param ) ) {
- return array( 'before', $param );
+ if ( is_array( $param ) ) {
+ if ( isset( $param['raw'] ) ) {
+ return array( 'after', $param['raw'] );
+ } elseif ( isset( $param['num'] ) ) {
+ // Replace number params always in before step for now.
+ // No support for combined raw and num params
+ return array( 'before', $this->language->formatNum( $param['num'] ) );
+ } elseif ( isset( $param['duration'] ) ) {
+ return array( 'before', $this->language->formatDuration( $param['duration'] ) );
+ } elseif ( isset( $param['expiry'] ) ) {
+ return array( 'before', $this->language->formatExpiry( $param['expiry'] ) );
+ } elseif ( isset( $param['period'] ) ) {
+ return array( 'before', $this->language->formatTimePeriod( $param['period'] ) );
+ } elseif ( isset( $param['size'] ) ) {
+ return array( 'before', $this->language->formatSize( $param['size'] ) );
+ } elseif ( isset( $param['bitrate'] ) ) {
+ return array( 'before', $this->language->formatBitrate( $param['bitrate'] ) );
+ } else {
+ trigger_error(
+ "Invalid message parameter: " . htmlspecialchars( serialize( $param ) ),
+ E_USER_WARNING
+ );
+ return array( 'before', '[INVALID]' );
+ }
+ } elseif ( $param instanceof Message ) {
+ // Message objects should not be before parameters because
+ // then they'll get double escaped. If the message needs to be
+ // escaped, it'll happen right here when we call toString().
+ return array( 'after', $param->toString() );
} else {
- trigger_error(
- "Invalid message parameter: " . htmlspecialchars( serialize( $param ) ),
- E_USER_WARNING
- );
- return array( 'before', '[INVALID]' );
+ return array( 'before', $param );
}
}
@@ -760,7 +918,7 @@ class RawMessage extends Message {
*/
public function fetchMessage() {
// Just in case the message is unset somewhere.
- if( !isset( $this->message ) ) {
+ if ( !isset( $this->message ) ) {
$this->message = $this->key;
}
return $this->message;
diff --git a/includes/Metadata.php b/includes/Metadata.php
index 0b8014a7..37df489c 100644
--- a/includes/Metadata.php
+++ b/includes/Metadata.php
@@ -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 );
}
@@ -89,13 +89,13 @@ abstract class RdfMetaData {
}
protected function pageOrString( $name, $page, $str ) {
- if( $page instanceof Title ) {
+ if ( $page instanceof Title ) {
$nt = $page;
} else {
$nt = Title::newFromText( $page );
}
- if( !$nt || $nt->getArticleID() == 0 ) {
+ if ( !$nt || $nt->getArticleID() == 0 ) {
$this->element( $name, $str );
} else {
$this->page( $name, $nt );
@@ -107,7 +107,7 @@ abstract class RdfMetaData {
* @param $title Title
*/
protected function page( $name, $title ) {
- $this->url( $name, $title->getFullUrl() );
+ $this->url( $name, $title->getFullURL() );
}
protected function url( $name, $url ) {
@@ -116,11 +116,13 @@ abstract class RdfMetaData {
}
protected function person( $name, User $user ) {
- if( $user->isAnon() ) {
+ global $wgHiddenPrefs;
+
+ if ( $user->isAnon() ) {
$this->element( $name, wfMessage( 'anonymous' )->numParams( 1 )->text() );
} else {
$real = $user->getRealName();
- if( $real ) {
+ if ( $real && !in_array( 'realname', $wgHiddenPrefs ) ) {
$this->element( $name, $real );
} else {
$userName = $user->getName();
@@ -140,12 +142,12 @@ abstract class RdfMetaData {
protected function rights() {
global $wgRightsPage, $wgRightsUrl, $wgRightsText;
- if( $wgRightsPage && ( $nt = Title::newFromText( $wgRightsPage ) )
+ if ( $wgRightsPage && ( $nt = Title::newFromText( $wgRightsPage ) )
&& ( $nt->getArticleID() != 0 ) ) {
$this->page( 'rights', $nt );
- } elseif( $wgRightsUrl ) {
+ } elseif ( $wgRightsUrl ) {
$this->url( 'rights', $wgRightsUrl );
- } elseif( $wgRightsText ) {
+ } elseif ( $wgRightsText ) {
$this->element( 'rights', $wgRightsText );
}
}
@@ -153,11 +155,11 @@ abstract class RdfMetaData {
protected function getTerms( $url ) {
global $wgLicenseTerms;
- if( $wgLicenseTerms ) {
+ if ( $wgLicenseTerms ) {
return $wgLicenseTerms;
} else {
$known = $this->getKnownLicenses();
- if( isset( $known[$url] ) ) {
+ if ( isset( $known[$url] ) ) {
return $known[$url];
} else {
return array();
@@ -173,7 +175,7 @@ abstract class RdfMetaData {
foreach ( $ccVersions as $version ) {
foreach ( $ccLicenses as $license ) {
- if( $version == '2.0' && substr( $license, 0, 2 ) != 'by' ) {
+ if ( $version == '2.0' && substr( $license, 0, 2 ) != 'by' ) {
# 2.0 dropped the non-attribs licenses
continue;
}
diff --git a/includes/MimeMagic.php b/includes/MimeMagic.php
index edabd54c..8220e92f 100644
--- a/includes/MimeMagic.php
+++ b/includes/MimeMagic.php
@@ -39,7 +39,7 @@
* 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
@@ -91,7 +91,7 @@ END_STRING
* An extensive list of well known mime types is provided by
* the file mime.info in the includes directory.
*/
-define('MM_WELL_KNOWN_MIME_INFO', <<<END_STRING
+define( 'MM_WELL_KNOWN_MIME_INFO', <<<END_STRING
application/pdf [OFFICE]
application/vnd.oasis.opendocument.chart [OFFICE]
application/vnd.oasis.opendocument.chart-template [OFFICE]
@@ -169,10 +169,6 @@ class MimeMagic {
*/
private static $instance;
- /** True if the fileinfo extension has been loaded
- */
- private static $extensionLoaded = false;
-
/** Initializes the MimeMagic object. This is called by MimeMagic::singleton().
*
* This constructor parses the mime.types and mime.info files and build internal mappings.
@@ -182,7 +178,7 @@ class MimeMagic {
* --- load mime.types ---
*/
- global $wgMimeTypeFile, $IP, $wgLoadFileinfoExtension;
+ global $wgMimeTypeFile, $IP;
$types = MM_WELL_KNOWN_MIME_TYPES;
@@ -190,11 +186,6 @@ class MimeMagic {
$wgMimeTypeFile = "$IP/$wgMimeTypeFile";
}
- if ( $wgLoadFileinfoExtension && !self::$extensionLoaded ) {
- self::$extensionLoaded = true;
- wfDl( 'fileinfo' );
- }
-
if ( $wgMimeTypeFile ) {
if ( is_file( $wgMimeTypeFile ) and is_readable( $wgMimeTypeFile ) ) {
wfDebug( __METHOD__ . ": loading mime types from $wgMimeTypeFile\n" );
@@ -231,7 +222,7 @@ class MimeMagic {
}
$mime = substr( $s, 0, $i );
- $ext = trim( substr( $s, $i+1 ) );
+ $ext = trim( substr( $s, $i + 1 ) );
if ( empty( $ext ) ) {
continue;
@@ -432,7 +423,7 @@ class MimeMagic {
$ext = explode( ' ', $ext );
$extension = strtolower( $extension );
- return in_array( $extension, $ext );
+ return in_array( $extension, $ext );
}
/**
@@ -560,7 +551,7 @@ class MimeMagic {
$mime = $this->doGuessMimeType( $file, $ext );
- if( !$mime ) {
+ if ( !$mime ) {
wfDebug( __METHOD__ . ": internal type detection failed for $file (.$ext)...\n" );
$mime = $this->detectMimeType( $file, $ext );
}
@@ -587,7 +578,7 @@ class MimeMagic {
$f = fopen( $file, 'rt' );
wfRestoreWarnings();
- if( !$f ) {
+ if ( !$f ) {
return 'unknown/unknown';
}
$head = fread( $f, 1024 );
@@ -628,7 +619,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";
@@ -642,7 +633,7 @@ class MimeMagic {
}
/* Look for WebP */
- if ( strncmp( $head, "RIFF", 4 ) == 0 && strncmp( substr( $head, 8, 8), "WEBPVP8 ", 8 ) == 0 ) {
+ if ( strncmp( $head, "RIFF", 4 ) == 0 && strncmp( substr( $head, 8, 8 ), "WEBPVP8 ", 8 ) == 0 ) {
wfDebug( __METHOD__ . ": recognized file as image/webp\n" );
return "image/webp";
}
@@ -689,11 +680,11 @@ class MimeMagic {
$script_type = null;
# detect by shebang
- if ( substr( $head, 0, 2) == "#!" ) {
+ if ( substr( $head, 0, 2 ) == "#!" ) {
$script_type = "ASCII";
- } elseif ( substr( $head, 0, 5) == "\xef\xbb\xbf#!" ) {
+ } elseif ( substr( $head, 0, 5 ) == "\xef\xbb\xbf#!" ) {
$script_type = "UTF-8";
- } elseif ( substr( $head, 0, 7) == "\xfe\xff\x00#\x00!" ) {
+ } 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";
@@ -705,8 +696,8 @@ class MimeMagic {
$pack = array( 'UTF-16BE' => 'n*', 'UTF-16LE' => 'v*' );
$chars = unpack( $pack[$script_type], substr( $head, 2 ) );
$head = '';
- foreach( $chars as $codepoint ) {
- if( $codepoint < 128 ) {
+ foreach ( $chars as $codepoint ) {
+ if ( $codepoint < 128 ) {
$head .= chr( $codepoint );
} else {
$head .= '?';
@@ -733,7 +724,7 @@ class MimeMagic {
$gis = getimagesize( $file );
wfRestoreWarnings();
- if( $gis && isset( $gis['mime'] ) ) {
+ if ( $gis && isset( $gis['mime'] ) ) {
$mime = $gis['mime'];
wfDebug( __METHOD__ . ": getimagesize detected $file as $mime\n" );
return $mime;
@@ -741,7 +732,7 @@ class MimeMagic {
// Also test DjVu
$deja = new DjVuImage( $file );
- if( $deja->isValid() ) {
+ if ( $deja->isValid() ) {
wfDebug( __METHOD__ . ": detected $file as image/vnd.djvu\n" );
return 'image/vnd.djvu';
}
@@ -763,7 +754,7 @@ class MimeMagic {
* @return string
*/
function detectZipType( $header, $tail = null, $ext = false ) {
- if( $ext ) { # TODO: remove $ext param
+ if ( $ext ) { # TODO: remove $ext param
wfDebug( __METHOD__ . ": WARNING: use of the \$ext parameter is deprecated. " .
"Use improveTypeFromExtension(\$mime, \$ext) instead.\n" );
}
@@ -804,7 +795,7 @@ class MimeMagic {
* 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) ) {
+ if ( $this->isMatchingExtension( $ext, $mime ) ) {
/* A known file extension for an OPC file,
* find the proper mime type for that file extension
*/
@@ -815,12 +806,12 @@ class MimeMagic {
}
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 &&
+ ( $headerpos = strpos( $tail, "PK\x03\x04" ) ) !== false &&
preg_match( $openxmlRegex, substr( $tail, $headerpos + 30 ) ) ) {
- if ( substr( $header, 512, 4) == "\xEC\xA5\xC1\x00" ) {
+ if ( substr( $header, 512, 4 ) == "\xEC\xA5\xC1\x00" ) {
$mime = "application/msword";
}
- switch( substr( $header, 512, 6) ) {
+ switch ( substr( $header, 512, 6 ) ) {
case "\xEC\xA5\xC1\x00\x0E\x00":
case "\xEC\xA5\xC1\x00\x1C\x00":
case "\xEC\xA5\xC1\x00\x43\x00":
@@ -876,9 +867,8 @@ class MimeMagic {
$m = null;
if ( $wgMimeDetectorCommand ) {
- // @todo FIXME: Use wfShellExec
- $fn = wfEscapeShellArg( $file );
- $m = `$wgMimeDetectorCommand $fn`;
+ $args = wfEscapeShellArg( $file );
+ $m = wfShellExec( "$wgMimeDetectorCommand $args" );
} elseif ( function_exists( "finfo_open" ) && function_exists( "finfo_file" ) ) {
# This required the fileinfo extension by PECL,
@@ -897,7 +887,7 @@ 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" ) ) {
@@ -935,7 +925,7 @@ class MimeMagic {
$ext = strtolower( $i ? substr( $file, $i + 1 ) : '' );
}
if ( $ext ) {
- if( $this->isRecognizableExtension( $ext ) ) {
+ if ( $this->isRecognizableExtension( $ext ) ) {
wfDebug( __METHOD__ . ": refusing to guess mime type for .$ext file, we should have recognized it\n" );
} else {
$m = $this->guessTypesForExtension( $ext );
@@ -968,12 +958,12 @@ class MimeMagic {
* @return (int?string?) a value to be used with the MEDIATYPE_xxx constants.
*/
function getMediaType( $path = null, $mime = null ) {
- if( !$mime && !$path ) {
+ if ( !$mime && !$path ) {
return MEDIATYPE_UNKNOWN;
}
// If mime type is unknown, guess it
- if( !$mime ) {
+ if ( !$mime ) {
$mime = $this->guessMimeType( $path, false );
}
@@ -983,22 +973,30 @@ class MimeMagic {
// Read a chunk of the file
$f = fopen( $path, "rt" );
- if ( !$f ) return MEDIATYPE_UNKNOWN;
+ if ( !$f ) {
+ return MEDIATYPE_UNKNOWN;
+ }
$head = fread( $f, 256 );
fclose( $f );
$head = strtolower( $head );
// This is an UGLY HACK, file should be parsed correctly
- if ( strpos( $head, 'theora' ) !== false ) return MEDIATYPE_VIDEO;
- elseif ( strpos( $head, 'vorbis' ) !== false ) return MEDIATYPE_AUDIO;
- elseif ( strpos( $head, 'flac' ) !== false ) return MEDIATYPE_AUDIO;
- elseif ( strpos( $head, 'speex' ) !== false ) return MEDIATYPE_AUDIO;
- else return MEDIATYPE_MULTIMEDIA;
+ if ( strpos( $head, 'theora' ) !== false ) {
+ return MEDIATYPE_VIDEO;
+ } elseif ( strpos( $head, 'vorbis' ) !== false ) {
+ return MEDIATYPE_AUDIO;
+ } elseif ( strpos( $head, 'flac' ) !== false ) {
+ return MEDIATYPE_AUDIO;
+ } elseif ( strpos( $head, 'speex' ) !== false ) {
+ return MEDIATYPE_AUDIO;
+ } else {
+ return MEDIATYPE_MULTIMEDIA;
+ }
}
// Check for entry for full mime type
- if( $mime ) {
+ if ( $mime ) {
$type = $this->findMediaType( $mime );
if ( $type !== MEDIATYPE_UNKNOWN ) {
return $type;
@@ -1029,7 +1027,7 @@ class MimeMagic {
}
}
- if( !$type ) {
+ if ( !$type ) {
$type = MEDIATYPE_UNKNOWN;
}
diff --git a/includes/Namespace.php b/includes/Namespace.php
index fccfbedb..5c8e63b7 100644
--- a/includes/Namespace.php
+++ b/includes/Namespace.php
@@ -334,7 +334,7 @@ class MWNamespace {
public static function getContentNamespaces() {
global $wgContentNamespaces;
if ( !is_array( $wgContentNamespaces ) || $wgContentNamespaces === array() ) {
- return NS_MAIN;
+ return array( NS_MAIN );
} elseif ( !in_array( NS_MAIN, $wgContentNamespaces ) ) {
// always force NS_MAIN to be part of array (to match the algorithm used by isContent)
return array_merge( array( NS_MAIN ), $wgContentNamespaces );
@@ -387,9 +387,9 @@ class MWNamespace {
if ( in_array( $index, self::$alwaysCapitalizedNamespaces ) ) {
return true;
}
- if ( isset( $wgCapitalLinkOverrides[ $index ] ) ) {
+ if ( isset( $wgCapitalLinkOverrides[$index] ) ) {
// $wgCapitalLinkOverrides is explicitly set
- return $wgCapitalLinkOverrides[ $index ];
+ return $wgCapitalLinkOverrides[$index];
}
// Default to the global setting
return $wgCapitalLinks;
diff --git a/includes/OutputHandler.php b/includes/OutputHandler.php
index 6b40c307..3860b8e2 100644
--- a/includes/OutputHandler.php
+++ b/includes/OutputHandler.php
@@ -31,11 +31,19 @@ function wfOutputHandler( $s ) {
global $wgDisableOutputCompression, $wgValidateAllHtml;
$s = wfMangleFlashPolicy( $s );
if ( $wgValidateAllHtml ) {
- $headers = apache_response_headers();
- $isHTML = true;
- foreach ( $headers as $name => $value ) {
- if ( strtolower( $name ) == 'content-type' && strpos( $value, 'text/html' ) === false && strpos( $value, 'application/xhtml+xml' ) === false ) {
- $isHTML = false;
+ $headers = headers_list();
+ $isHTML = false;
+ foreach ( $headers as $header ) {
+ $parts = explode( ':', $header, 2 );
+ if ( count( $parts ) !== 2 ) {
+ continue;
+ }
+ $name = strtolower( trim( $parts[0] ) );
+ $value = trim( $parts[1] );
+ if ( $name == 'content-type' && ( strpos( $value, 'text/html' ) === 0
+ || strpos( $value, 'application/xhtml+xml' ) === 0 )
+ ) {
+ $isHTML = true;
break;
}
}
@@ -64,10 +72,10 @@ function wfOutputHandler( $s ) {
*/
function wfRequestExtension() {
/// @todo FIXME: this sort of dupes some code in WebRequest::getRequestUrl()
- if( isset( $_SERVER['REQUEST_URI'] ) ) {
+ if ( isset( $_SERVER['REQUEST_URI'] ) ) {
// Strip the query string...
list( $path ) = explode( '?', $_SERVER['REQUEST_URI'], 2 );
- } elseif( isset( $_SERVER['SCRIPT_NAME'] ) ) {
+ } elseif ( isset( $_SERVER['SCRIPT_NAME'] ) ) {
// Probably IIS. QUERY_STRING appears separately.
$path = $_SERVER['SCRIPT_NAME'];
} else {
@@ -76,7 +84,7 @@ function wfRequestExtension() {
}
$period = strrpos( $path, '.' );
- if( $period !== false ) {
+ if ( $period !== false ) {
return strtolower( substr( $path, $period ) );
}
return '';
@@ -91,17 +99,17 @@ function wfRequestExtension() {
* @return string
*/
function wfGzipHandler( $s ) {
- if( !function_exists( 'gzencode' ) ) {
+ if ( !function_exists( 'gzencode' ) ) {
wfDebug( __FUNCTION__ . "() skipping compression (gzencode unavailable)\n" );
return $s;
}
- if( headers_sent() ) {
+ if ( headers_sent() ) {
wfDebug( __FUNCTION__ . "() skipping compression (headers already sent)\n" );
return $s;
}
$ext = wfRequestExtension();
- if( $ext == '.gz' || $ext == '.tgz' ) {
+ if ( $ext == '.gz' || $ext == '.tgz' ) {
// Don't do gzip compression if the URL path ends in .gz or .tgz
// This confuses Safari and triggers a download of the page,
// even though it's pretty clearly labeled as viewable HTML.
@@ -109,7 +117,7 @@ function wfGzipHandler( $s ) {
return $s;
}
- if( wfClientAcceptsGzip() ) {
+ if ( wfClientAcceptsGzip() ) {
wfDebug( __FUNCTION__ . "() is compressing output\n" );
header( 'Content-Encoding: gzip' );
$s = gzencode( $s, 6 );
@@ -177,20 +185,8 @@ function wfHtmlValidationHandler( $s ) {
header( 'Cache-Control: no-cache' );
- $out = <<<EOT
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" lang="en" dir="ltr">
-<head>
-<title>HTML validation error</title>
-<style>
-.highlight { background-color: #ffc }
-li { white-space: pre }
-</style>
-</head>
-<body>
-<h1>HTML validation error</h1>
-<ul>
-EOT;
+ $out = Html::element( 'h1', null, 'HTML validation error' );
+ $out .= Html::openElement( 'ul' );
$error = strtok( $errors, "\n" );
$badLines = array();
@@ -198,26 +194,40 @@ EOT;
if ( preg_match( '/^line (\d+)/', $error, $m ) ) {
$lineNum = intval( $m[1] );
$badLines[$lineNum] = true;
- $out .= "<li><a href=\"#line-{$lineNum}\">" . htmlspecialchars( $error ) . "</a></li>\n";
+ $out .= Html::rawElement( 'li', null,
+ Html::element( 'a', array( 'href' => "#line-{$lineNum}" ), $error ) ) . "\n";
}
$error = strtok( "\n" );
}
- $out .= '</ul>';
- $out .= '<pre>' . htmlspecialchars( $errors ) . '</pre>';
- $out .= "<ol>\n";
+ $out .= Html::closeElement( 'ul' );
+ $out .= Html::element( 'pre', null, $errors );
+ $out .= Html::openElement( 'ol' ) . "\n";
$line = strtok( $s, "\n" );
$i = 1;
while ( $line !== false ) {
+ $attrs = array();
if ( isset( $badLines[$i] ) ) {
- $out .= "<li class=\"highlight\" id=\"line-$i\">";
- } else {
- $out .= '<li>';
+ $attrs['class'] = 'highlight';
+ $attrs['id'] = "line-$i";
}
- $out .= htmlspecialchars( $line ) . "</li>\n";
+ $out .= Html::element( 'li', $attrs, $line ) . "\n";
$line = strtok( "\n" );
$i++;
}
- $out .= '</ol></body></html>';
+ $out .= Html::closeElement( 'ol' );
+
+ $style = <<<CSS
+.highlight { background-color: #ffc }
+li { white-space: pre }
+CSS;
+
+ $out = Html::htmlHeader( array( 'lang' => 'en', 'dir' => 'ltr' ) ) .
+ Html::rawElement( 'head', null,
+ Html::element( 'title', null, 'HTML validation error' ) .
+ Html::inlineStyle( $style ) ) .
+ Html::rawElement( 'body', null, $out ) .
+ Html::closeElement( 'html' );
+
return $out;
}
diff --git a/includes/OutputPage.php b/includes/OutputPage.php
index 1e0c396a..7f0454f6 100644
--- a/includes/OutputPage.php
+++ b/includes/OutputPage.php
@@ -39,9 +39,6 @@ class OutputPage extends ContextSource {
/// Should be private. Used with addMeta() which adds "<meta>"
var $mMetatags = array();
- /// "<meta keywords='stuff'>" most of the time the first 10 links to an article
- var $mKeywords = array();
-
var $mLinktags = array();
var $mCanonicalUrl = false;
@@ -254,6 +251,16 @@ class OutputPage extends ContextSource {
private $mProperties = array();
/**
+ * @var string|null: ResourceLoader target for load.php links. If null, will be omitted
+ */
+ private $mTarget = null;
+
+ /**
+ * @var bool: Whether output should contain table of contents
+ */
+ private $mEnableTOC = true;
+
+ /**
* 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.
@@ -309,19 +316,6 @@ class OutputPage extends ContextSource {
}
/**
- * Add a keyword or a list of keywords in the page header
- *
- * @param string $text or array of strings
- */
- function addKeyword( $text ) {
- if( is_array( $text ) ) {
- $this->mKeywords = array_merge( $this->mKeywords, $text );
- } else {
- array_push( $this->mKeywords, $text );
- }
- }
-
- /**
* Add a new \<link\> tag to the page header.
*
* Note: use setCanonicalUrl() for rel=canonical.
@@ -408,13 +402,14 @@ class OutputPage extends ContextSource {
public function addScriptFile( $file, $version = null ) {
global $wgStylePath, $wgStyleVersion;
// See if $file parameter is an absolute URL or begins with a slash
- if( substr( $file, 0, 1 ) == '/' || preg_match( '#^[a-z]*://#i', $file ) ) {
+ if ( substr( $file, 0, 1 ) == '/' || preg_match( '#^[a-z]*://#i', $file ) ) {
$path = $file;
} else {
$path = "{$wgStylePath}/common/{$file}";
}
- if ( is_null( $version ) )
+ if ( is_null( $version ) ) {
$version = $wgStyleVersion;
+ }
$this->addScript( Html::linkedScript( wfAppendQuery( $path, $version ) ) );
}
@@ -447,11 +442,12 @@ class OutputPage extends ContextSource {
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
+ if ( $module instanceof ResourceLoaderModule
&& $module->getOrigin() <= $this->getAllowedModules( $type )
- && ( is_null( $position ) || $module->getPosition() == $position ) )
+ && ( is_null( $position ) || $module->getPosition() == $position )
+ && ( !$this->mTarget || in_array( $this->mTarget, $module->getTargets() ) ) )
{
$filteredModules[] = $val;
}
@@ -557,6 +553,22 @@ class OutputPage extends ContextSource {
}
/**
+ * @return null|string: ResourceLoader target
+ */
+ public function getTarget() {
+ return $this->mTarget;
+ }
+
+ /**
+ * Sets ResourceLoader target for load.php links. If null, will be omitted
+ *
+ * @param $target string|null
+ */
+ public function setTarget( $target ) {
+ $this->mTarget = $target;
+ }
+
+ /**
* Get an array of head items
*
* @return Array
@@ -662,20 +674,20 @@ class OutputPage extends ContextSource {
*
* @param $timestamp string
*
- * @return Boolean: true iff cache-ok headers was sent.
+ * @return Boolean: true if cache-ok headers was sent.
*/
public function checkLastModified( $timestamp ) {
- global $wgCachePages, $wgCacheEpoch;
+ global $wgCachePages, $wgCacheEpoch, $wgUseSquid, $wgSquidMaxage;
if ( !$timestamp || $timestamp == '19700101000000' ) {
wfDebug( __METHOD__ . ": CACHE DISABLED, NO TIMESTAMP\n" );
return false;
}
- if( !$wgCachePages ) {
+ if ( !$wgCachePages ) {
wfDebug( __METHOD__ . ": CACHE DISABLED\n", false );
return false;
}
- if( $this->getUser()->getOption( 'nocache' ) ) {
+ if ( $this->getUser()->getOption( 'nocache' ) ) {
wfDebug( __METHOD__ . ": USER DISABLED CACHE\n", false );
return false;
}
@@ -686,6 +698,10 @@ class OutputPage extends ContextSource {
'user' => $this->getUser()->getTouched(),
'epoch' => $wgCacheEpoch
);
+ if ( $wgUseSquid ) {
+ // bug 44570: the core page itself may not change, but resources might
+ $modifiedTimes['sepoch'] = wfTimestamp( TS_MW, time() - $wgSquidMaxage );
+ }
wfRunHooks( 'OutputPageCheckLastModified', array( &$modifiedTimes ) );
$maxModified = max( $modifiedTimes );
@@ -724,7 +740,7 @@ class OutputPage extends ContextSource {
wfTimestamp( TS_ISO_8601, $clientHeaderTime ) . "\n", false );
wfDebug( __METHOD__ . ": effective Last-Modified: " .
wfTimestamp( TS_ISO_8601, $maxModified ) . "\n", false );
- if( $clientHeaderTime < $maxModified ) {
+ if ( $clientHeaderTime < $maxModified ) {
wfDebug( __METHOD__ . ": STALE, $info\n", false );
return false;
}
@@ -766,10 +782,10 @@ class OutputPage extends ContextSource {
public function setRobotPolicy( $policy ) {
$policy = Article::formatRobotPolicy( $policy );
- if( isset( $policy['index'] ) ) {
+ if ( isset( $policy['index'] ) ) {
$this->setIndexPolicy( $policy['index'] );
}
- if( isset( $policy['follow'] ) ) {
+ if ( isset( $policy['follow'] ) ) {
$this->setFollowPolicy( $policy['follow'] );
}
}
@@ -783,7 +799,7 @@ class OutputPage extends ContextSource {
*/
public function setIndexPolicy( $policy ) {
$policy = trim( $policy );
- if( in_array( $policy, array( 'index', 'noindex' ) ) ) {
+ if ( in_array( $policy, array( 'index', 'noindex' ) ) ) {
$this->mIndexPolicy = $policy;
}
}
@@ -797,7 +813,7 @@ class OutputPage extends ContextSource {
*/
public function setFollowPolicy( $policy ) {
$policy = trim( $policy );
- if( in_array( $policy, array( 'follow', 'nofollow' ) ) ) {
+ if ( in_array( $policy, array( 'follow', 'nofollow' ) ) ) {
$this->mFollowPolicy = $policy;
}
}
@@ -1269,7 +1285,6 @@ class OutputPage extends ContextSource {
* Return whether user JavaScript is allowed for this page
* @deprecated since 1.18 Load modules with ResourceLoader, and origin and
* trustworthiness is identified and enforced automagically.
- * Will be removed in 1.20.
* @return Boolean
*/
public function isUserJsAllowed() {
@@ -1284,7 +1299,7 @@ class OutputPage extends ContextSource {
* @return Int ResourceLoaderModule ORIGIN_ class constant
*/
public function getAllowedModules( $type ) {
- if( $type == ResourceLoaderModule::TYPE_COMBINED ) {
+ if ( $type == ResourceLoaderModule::TYPE_COMBINED ) {
return min( array_values( $this->mAllowedModules ) );
} else {
return isset( $this->mAllowedModules[$type] )
@@ -1401,7 +1416,7 @@ class OutputPage extends ContextSource {
* @param $timestamp Mixed: string, or null
* @return Mixed: previous value
*/
- public function setRevisionTimestamp( $timestamp) {
+ public function setRevisionTimestamp( $timestamp ) {
return wfSetVar( $this->mRevisionTimestamp, $timestamp );
}
@@ -1524,7 +1539,7 @@ class OutputPage extends ContextSource {
$popts = $this->parserOptions();
$oldTidy = $popts->setTidy( $tidy );
- $popts->setInterfaceMessage( (bool) $interface );
+ $popts->setInterfaceMessage( (bool)$interface );
$parserOutput = $wgParser->parse(
$text, $title, $popts,
@@ -1582,6 +1597,10 @@ class OutputPage extends ContextSource {
}
}
+ // Link flags are ignored for now, but may in the future be
+ // used to mark individual language links.
+ $linkFlags = array();
+ wfRunHooks( 'LanguageLinks', array( $this->getTitle(), &$this->mLanguageLinks, &$linkFlags ) );
wfRunHooks( 'OutputPageParserOutput', array( &$this, $parserOutput ) );
}
@@ -1592,6 +1611,7 @@ class OutputPage extends ContextSource {
*/
function addParserOutput( &$parserOutput ) {
$this->addParserOutputNoText( $parserOutput );
+ $parserOutput->setTOCEnabled( $this->mEnableTOC );
$text = $parserOutput->getText();
wfRunHooks( 'OutputPageBeforeHTML', array( &$this, &$text ) );
$this->addHTML( $text );
@@ -1626,7 +1646,7 @@ class OutputPage extends ContextSource {
public function parse( $text, $linestart = true, $interface = false, $language = null ) {
global $wgParser;
- if( is_null( $this->getTitle() ) ) {
+ if ( is_null( $this->getTitle() ) ) {
throw new MWException( 'Empty $mTitle in ' . __METHOD__ );
}
@@ -1707,6 +1727,7 @@ class OutputPage extends ContextSource {
array(
"{$wgCookiePrefix}Token",
"{$wgCookiePrefix}LoggedOut",
+ "forceHTTPS",
session_name()
),
$wgCacheVaryCookies
@@ -1750,8 +1771,8 @@ class OutputPage extends ContextSource {
public function addVaryHeader( $header, $option = null ) {
if ( !array_key_exists( $header, $this->mVaryHeader ) ) {
$this->mVaryHeader[$header] = (array)$option;
- } elseif( is_array( $option ) ) {
- if( is_array( $this->mVaryHeader[$header] ) ) {
+ } elseif ( is_array( $option ) ) {
+ if ( is_array( $this->mVaryHeader[$header] ) ) {
$this->mVaryHeader[$header] = array_merge( $this->mVaryHeader[$header], $option );
} else {
$this->mVaryHeader[$header] = $option;
@@ -1785,7 +1806,7 @@ class OutputPage extends ContextSource {
$this->addVaryHeader( 'Cookie', $cookiesOption );
$headers = array();
- foreach( $this->mVaryHeader as $header => $option ) {
+ foreach ( $this->mVaryHeader as $header => $option ) {
$newheader = $header;
if ( is_array( $option ) && count( $option ) > 0 ) {
$newheader .= ';' . implode( ';', $option );
@@ -1807,11 +1828,11 @@ class OutputPage extends ContextSource {
*/
function addAcceptLanguage() {
$lang = $this->getTitle()->getPageLanguage();
- if( !$this->getRequest()->getCheck( 'variant' ) && $lang->hasVariants() ) {
+ if ( !$this->getRequest()->getCheck( 'variant' ) && $lang->hasVariants() ) {
$variants = $lang->getVariants();
$aloption = array();
foreach ( $variants as $variant ) {
- if( $variant === $lang->getCode() ) {
+ if ( $variant === $lang->getCode() ) {
continue;
} else {
$aloption[] = 'string-contains=' . $variant;
@@ -1892,12 +1913,11 @@ class OutputPage extends ContextSource {
$response->header( $this->getXVO() );
}
- if( $this->mEnableClientCache ) {
- if(
+ if ( $this->mEnableClientCache ) {
+ if (
$wgUseSquid && session_id() == '' && !$this->isPrintable() &&
$this->mSquidMaxage != 0 && !$this->haveCacheVaryCookies()
- )
- {
+ ) {
if ( $wgUseESI ) {
# We'll purge the proxy cache explicitly, but require end user agents
# to revalidate against the proxy on each visit.
@@ -1924,7 +1944,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 {
@@ -1957,9 +1977,10 @@ class OutputPage extends ContextSource {
* the object, let's actually output it:
*/
public function output() {
- global $wgLanguageCode, $wgDebugRedirects, $wgMimeType, $wgVaryOnXFP;
+ global $wgLanguageCode, $wgDebugRedirects, $wgMimeType, $wgVaryOnXFP,
+ $wgUseAjax, $wgResponsiveImages;
- if( $this->mDoNothing ) {
+ if ( $this->mDoNothing ) {
return;
}
@@ -1974,9 +1995,9 @@ class OutputPage extends ContextSource {
$redirect = $this->mRedirect;
$code = $this->mRedirectCode;
- if( wfRunHooks( "BeforePageRedirect", array( $this, &$redirect, &$code ) ) ) {
- if( $code == '301' || $code == '303' ) {
- if( !$wgDebugRedirects ) {
+ if ( wfRunHooks( "BeforePageRedirect", array( $this, &$redirect, &$code ) ) ) {
+ if ( $code == '301' || $code == '303' ) {
+ if ( !$wgDebugRedirects ) {
$message = HttpStatus::getMessage( $code );
$response->header( "HTTP/1.1 $code $message" );
}
@@ -1988,7 +2009,7 @@ class OutputPage extends ContextSource {
$this->sendCacheControl();
$response->header( "Content-Type: text/html; charset=utf-8" );
- if( $wgDebugRedirects ) {
+ if ( $wgDebugRedirects ) {
$url = htmlspecialchars( $redirect );
print "<html>\n<head>\n<title>Redirect</title>\n</head>\n<body>\n";
print "<p>Location: <a href=\"$url\">$url</a></p>\n";
@@ -2020,11 +2041,34 @@ class OutputPage extends ContextSource {
}
if ( $this->mArticleBodyOnly ) {
- $this->out( $this->mBodytext );
+ echo $this->mBodytext;
} else {
- $this->addDefaultModules();
$sk = $this->getSkin();
+ // add skin specific modules
+ $modules = $sk->getDefaultModules();
+
+ // enforce various default modules for all skins
+ $coreModules = array(
+ // keep this list as small as possible
+ 'mediawiki.page.startup',
+ 'mediawiki.user',
+ );
+
+ // Support for high-density display images if enabled
+ if ( $wgResponsiveImages ) {
+ $coreModules[] = 'mediawiki.hidpi';
+ }
+
+ $this->addModules( $coreModules );
+ foreach ( $modules as $group ) {
+ $this->addModules( $group );
+ }
+ MWDebug::addModules( $this );
+ if ( $wgUseAjax ) {
+ // FIXME: deprecate? - not clear why this is useful
+ wfRunHooks( 'AjaxAddScript', array( &$this ) );
+ }
// Hook that allows last minute changes to the output page, e.g.
// adding of CSS or Javascript by extensions.
@@ -2046,11 +2090,13 @@ class OutputPage extends ContextSource {
}
/**
- * Actually output something with print().
+ * Actually output something with print.
*
* @param string $ins the string to output
+ * @deprecated since 1.22 Use echo yourself.
*/
public function out( $ins ) {
+ wfDeprecated( __METHOD__, '1.22' );
print $ins;
}
@@ -2097,7 +2143,7 @@ class OutputPage extends ContextSource {
* @param array $params message parameters; ignored if $msg is a Message object
*/
public function showErrorPage( $title, $msg, $params = array() ) {
- if( !$title instanceof Message ) {
+ if ( !$title instanceof Message ) {
$title = $this->msg( $title );
}
@@ -2238,7 +2284,7 @@ class OutputPage extends ContextSource {
if ( count( $errors ) > 1 ) {
$text .= '<ul class="permissions-errors">' . "\n";
- foreach( $errors as $error ) {
+ foreach ( $errors as $error ) {
$text .= '<li>';
$text .= call_user_func_array( array( $this, 'msg' ), $error )->plain();
$text .= "</li>\n";
@@ -2286,7 +2332,7 @@ class OutputPage extends ContextSource {
if ( !empty( $reasons ) ) {
// Permissions error
- if( $source ) {
+ if ( $source ) {
$this->setPageTitle( $this->msg( 'viewsource-title', $this->getTitle()->getPrefixedText() ) );
$this->addBacklinkSubtitle( $this->getTitle() );
} else {
@@ -2299,7 +2345,7 @@ class OutputPage extends ContextSource {
}
// Show source, if supplied
- if( is_string( $source ) ) {
+ if ( is_string( $source ) ) {
$this->addWikiMsg( 'viewsourcetext' );
$pageLang = $this->getTitle()->getPageLanguage();
@@ -2325,7 +2371,7 @@ $templates
# If the title doesn't exist, it's fairly pointless to print a return
# link to it. After all, you just tried editing it and couldn't, so
# what's there to do there?
- if( $this->getTitle()->exists() ) {
+ if ( $this->getTitle()->exists() ) {
$this->returnToMain( null, $this->getTitle() );
}
}
@@ -2349,7 +2395,7 @@ $templates
*/
public function showLagWarning( $lag ) {
global $wgSlaveLagWarning, $wgSlaveLagCritical;
- if( $lag >= $wgSlaveLagWarning ) {
+ if ( $lag >= $wgSlaveLagWarning ) {
$message = $lag < $wgSlaveLagCritical
? 'lag-warn-normal'
: 'lag-warn-high';
@@ -2393,15 +2439,6 @@ $templates
* @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, $options ) )->escaped();
$this->addHTML( "<p id=\"mw-returnto\">{$link}</p>\n" );
@@ -2446,15 +2483,11 @@ $templates
* @return String: The doctype, opening "<html>", and head element.
*/
public function headElement( Skin $sk, $includeStyle = true ) {
- global $wgContLang;
+ global $wgContLang, $wgMimeType;
$userdir = $this->getLanguage()->getDir();
$sitedir = $wgContLang->getDir();
- if ( $sk->commonPrintStylesheet() ) {
- $this->addModuleStyles( 'mediawiki.legacy.wikiprintable' );
- }
-
$ret = Html::htmlHeader( array( 'lang' => $this->getLanguage()->getHtmlCode(), 'dir' => $userdir, 'class' => 'client-nojs' ) );
if ( $this->getHTMLTitle() == '' ) {
@@ -2467,10 +2500,22 @@ $templates
$ret .= "$openHead\n";
}
+ if ( !Html::isXmlMimeType( $wgMimeType ) ) {
+ // Add <meta charset="UTF-8">
+ // This should be before <title> since it defines the charset used by
+ // text including the text inside <title>.
+ // The spec recommends defining XHTML5's charset using the XML declaration
+ // instead of meta.
+ // Our XML declaration is output by Html::htmlHeader.
+ // http://www.whatwg.org/html/semantics.html#attr-meta-http-equiv-content-type
+ // http://www.whatwg.org/html/semantics.html#charset
+ $ret .= Html::element( 'meta', array( 'charset' => 'UTF-8' ) );
+ }
+
$ret .= Html::element( 'title', null, $this->getHTMLTitle() ) . "\n";
$ret .= implode( "\n", array(
- $this->getHeadLinks( null, true ),
+ $this->getHeadLinks(),
$this->buildCssLinks(),
$this->getHeadScripts(),
$this->getHeadItems()
@@ -2481,20 +2526,29 @@ $templates
$ret .= "$closeHead\n";
}
- $bodyAttrs = array();
+ $bodyClasses = array();
+ $bodyClasses[] = 'mediawiki';
# Classes for LTR/RTL directionality support
- $bodyAttrs['class'] = "mediawiki $userdir sitedir-$sitedir";
+ $bodyClasses[] = $userdir;
+ $bodyClasses[] = "sitedir-$sitedir";
if ( $this->getLanguage()->capitalizeAllNouns() ) {
# A <body> class is probably not the best way to do this . . .
- $bodyAttrs['class'] .= ' capitalize-all-nouns';
+ $bodyClasses[] = 'capitalize-all-nouns';
}
- $bodyAttrs['class'] .= ' ' . $sk->getPageClasses( $this->getTitle() );
- $bodyAttrs['class'] .= ' skin-' . Sanitizer::escapeClass( $sk->getSkinName() );
- $bodyAttrs['class'] .= ' action-' . Sanitizer::escapeClass( Action::getActionName( $this->getContext() ) );
- $sk->addToBodyAttributes( $this, $bodyAttrs ); // Allow skins to add body attributes they need
+ $bodyClasses[] = $sk->getPageClasses( $this->getTitle() );
+ $bodyClasses[] = 'skin-' . Sanitizer::escapeClass( $sk->getSkinName() );
+ $bodyClasses[] = 'action-' . Sanitizer::escapeClass( Action::getActionName( $this->getContext() ) );
+
+ $bodyAttrs = array();
+ // While the implode() is not strictly needed, it's used for backwards compatibility
+ // (this used to be built as a string and hooks likely still expect that).
+ $bodyAttrs['class'] = implode( ' ', $bodyClasses );
+
+ // Allow skins and extensions to add body attributes they need
+ $sk->addToBodyAttributes( $this, $bodyAttrs );
wfRunHooks( 'OutputPageBodyAttributes', array( $this, $sk, &$bodyAttrs ) );
$ret .= Html::openElement( 'body', $bodyAttrs ) . "\n";
@@ -2503,59 +2557,6 @@ $templates
}
/**
- * Add the default ResourceLoader modules to this object
- */
- private function addDefaultModules() {
- global $wgIncludeLegacyJavaScript, $wgPreloadJavaScriptMwUtil, $wgUseAjax,
- $wgAjaxWatch, $wgResponsiveImages;
-
- // Add base resources
- $this->addModules( array(
- 'mediawiki.user',
- 'mediawiki.page.startup',
- 'mediawiki.page.ready',
- ) );
- if ( $wgIncludeLegacyJavaScript ) {
- $this->addModules( 'mediawiki.legacy.wikibits' );
- }
-
- if ( $wgPreloadJavaScriptMwUtil ) {
- $this->addModules( 'mediawiki.util' );
- }
-
- MWDebug::addModules( $this );
-
- // Add various resources if required
- if ( $wgUseAjax ) {
- $this->addModules( 'mediawiki.legacy.ajax' );
-
- wfRunHooks( 'AjaxAddScript', array( &$this ) );
-
- if( $wgAjaxWatch && $this->getUser()->isLoggedIn() ) {
- $this->addModules( 'mediawiki.page.watch.ajax' );
- }
-
- if ( !$this->getUser()->getOption( 'disablesuggest', false ) ) {
- $this->addModules( 'mediawiki.searchSuggest' );
- }
- }
-
- if ( $this->getUser()->getBoolOption( 'editsectiononrightclick' ) ) {
- $this->addModules( 'mediawiki.action.view.rightClickEdit' );
- }
-
- # Crazy edit-on-double-click stuff
- 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' );
- }
- }
-
- /**
* Get a ResourceLoader object associated with this OutputPage
*
* @return ResourceLoader
@@ -2579,7 +2580,7 @@ $templates
protected function makeResourceLoaderLink( $modules, $only, $useESI = false, array $extraQuery = array(), $loadCall = false ) {
global $wgResourceLoaderUseESI;
- $modules = (array) $modules;
+ $modules = (array)$modules;
if ( !count( $modules ) ) {
return '';
@@ -2600,6 +2601,9 @@ $templates
return $links;
}
}
+ if ( !is_null( $this->mTarget ) ) {
+ $extraQuery['target'] = $this->mTarget;
+ }
// Create keyed-by-group list of module objects from modules list
$groups = array();
@@ -2612,8 +2616,8 @@ $templates
&& $only == ResourceLoaderModule::TYPE_SCRIPTS )
|| ( $module->getOrigin() > $this->getAllowedModules( ResourceLoaderModule::TYPE_STYLES )
&& $only == ResourceLoaderModule::TYPE_STYLES )
- )
- {
+ || ( $this->mTarget && !in_array( $this->mTarget, $module->getTargets() ) )
+ ) {
continue;
}
@@ -2648,7 +2652,7 @@ $templates
);
$context = new ResourceLoaderContext( $resourceLoader, new FauxRequest( $query ) );
// Extract modules that know they're empty
- $emptyModules = array ();
+ $emptyModules = array();
foreach ( $grpModules as $key => $module ) {
if ( $module->isKnownEmpty( $context ) ) {
$emptyModules[$key] = 'ready';
@@ -2659,13 +2663,9 @@ $templates
if ( count( $emptyModules ) > 0 && $only !== ResourceLoaderModule::TYPE_STYLES ) {
// If we're only getting the styles, we don't need to do anything for empty modules.
$links .= Html::inlineScript(
-
ResourceLoader::makeLoaderConditionalScript(
-
ResourceLoader::makeLoaderStateScript( $emptyModules )
-
)
-
) . "\n";
}
@@ -2732,7 +2732,7 @@ $templates
// Automatically select style/script elements
if ( $only === ResourceLoaderModule::TYPE_STYLES ) {
$link = Html::linkedStyle( $url );
- } else if ( $loadCall ) {
+ } elseif ( $loadCall ) {
$link = Html::inlineScript(
ResourceLoader::makeLoaderConditionalScript(
Xml::encodeJsCall( 'mw.loader.load', array( $url, 'text/javascript', true ) )
@@ -2743,7 +2743,7 @@ $templates
}
}
- if( $group == 'noscript' ) {
+ if ( $group == 'noscript' ) {
$links .= Html::rawElement( 'noscript', array(), $link ) . "\n";
} else {
$links .= $link . "\n";
@@ -2847,14 +2847,14 @@ $templates
);
$defaultModules['site'] = 'loading';
} else {
- // The wiki is configured to not allow a site module.
- $defaultModules['site'] = 'missing';
+ // Site module is empty, save request by marking ready in advance (bug 46857)
+ $defaultModules['site'] = 'ready';
}
// Add user JS if enabled
if ( $wgAllowUserJs ) {
if ( $this->getUser()->isLoggedIn() ) {
- if( $this->getTitle() && $this->getTitle()->isJsSubpage() && $this->userCanPreview() ) {
+ if ( $this->getTitle() && $this->getTitle()->isJsSubpage() && $this->userCanPreview() ) {
# XXX: additional security check/prompt?
// We're on a preview of a JS subpage
// Exclude this page from the user module in case it's in there (bug 26283)
@@ -2874,15 +2874,14 @@ $templates
}
$defaultModules['user'] = 'loading';
} else {
- // Non-logged-in users have no user module. Treat it as empty and 'ready' to avoid
- // blocking default gadgets that might depend on it. Although arguably default-enabled
- // gadgets should not depend on the user module, it's harmless and less error-prone to
- // handle this case.
+ // Non-logged-in users have an empty user module.
+ // Save request by marking ready in advance (bug 46857)
$defaultModules['user'] = 'ready';
}
} else {
- // User JS disabled
- $defaultModules['user'] = 'missing';
+ // User modules are disabled on this wiki.
+ // Save request by marking ready in advance (bug 46857)
+ $defaultModules['user'] = 'ready';
}
// Group JS is only enabled if site JS is enabled.
@@ -2893,13 +2892,13 @@ $templates
);
$defaultModules['user.groups'] = 'loading';
} else {
- // Non-logged-in users have no user.groups module. Treat it as empty and 'ready' to
- // avoid blocking gadgets that might depend upon the module.
+ // Non-logged-in users have no user.groups module.
+ // Save request by marking ready in advance (bug 46857)
$defaultModules['user.groups'] = 'ready';
}
} else {
// Site (and group JS) disabled
- $defaultModules['user.groups'] = 'missing';
+ $defaultModules['user.groups'] = 'ready';
}
$loaderInit = '';
@@ -2927,11 +2926,18 @@ $templates
*/
function getBottomScripts() {
global $wgResourceLoaderExperimentalAsyncLoading;
+
+ // Optimise jQuery ready event cross-browser.
+ // This also enforces $.isReady to be true at </body> which fixes the
+ // mw.loader bug in Firefox with using document.write between </body>
+ // and the DOMContentReady event (bug 47457).
+ $html = Html::inlineScript( 'window.jQuery && jQuery.ready();' );
+
if ( !$wgResourceLoaderExperimentalAsyncLoading ) {
- return $this->getScriptsForBottomQueue( false );
- } else {
- return '';
+ $html .= $this->getScriptsForBottomQueue( false );
}
+
+ return $html;
}
/**
@@ -2966,24 +2972,24 @@ $templates
public function getJSVars() {
global $wgContLang;
- $latestRevID = 0;
- $pageID = 0;
- $canonicalName = false; # bug 21115
+ $curRevisionId = 0;
+ $articleId = 0;
+ $canonicalSpecialPageName = false; # bug 21115
$title = $this->getTitle();
$ns = $title->getNamespace();
- $nsname = MWNamespace::exists( $ns ) ? MWNamespace::getCanonicalName( $ns ) : $title->getNsText();
+ $canonicalNamespace = MWNamespace::exists( $ns ) ? MWNamespace::getCanonicalName( $ns ) : $title->getNsText();
// Get the relevant title so that AJAX features can use the correct page name
// when making API requests from certain special pages (bug 34972).
$relevantTitle = $this->getSkin()->getRelevantTitle();
if ( $ns == NS_SPECIAL ) {
- list( $canonicalName, /*...*/ ) = SpecialPageFactory::resolveAlias( $title->getDBkey() );
+ list( $canonicalSpecialPageName, /*...*/ ) = SpecialPageFactory::resolveAlias( $title->getDBkey() );
} elseif ( $this->canUseWikiPage() ) {
$wikiPage = $this->getWikiPage();
- $latestRevID = $wikiPage->getLatest();
- $pageID = $wikiPage->getId();
+ $curRevisionId = $wikiPage->getLatest();
+ $articleId = $wikiPage->getId();
}
$lang = $title->getPageLanguage();
@@ -3005,20 +3011,23 @@ $templates
$user = $this->getUser();
$vars = array(
- 'wgCanonicalNamespace' => $nsname,
- 'wgCanonicalSpecialPageName' => $canonicalName,
+ 'wgCanonicalNamespace' => $canonicalNamespace,
+ 'wgCanonicalSpecialPageName' => $canonicalSpecialPageName,
'wgNamespaceNumber' => $title->getNamespace(),
'wgPageName' => $title->getPrefixedDBkey(),
'wgTitle' => $title->getText(),
- 'wgCurRevisionId' => $latestRevID,
- 'wgArticleId' => $pageID,
+ 'wgCurRevisionId' => $curRevisionId,
+ 'wgRevisionId' => (int)$this->getRevisionId(),
+ 'wgArticleId' => $articleId,
'wgIsArticle' => $this->isArticle(),
+ 'wgIsRedirect' => $title->isRedirect(),
'wgAction' => Action::getActionName( $this->getContext() ),
'wgUserName' => $user->isAnon() ? null : $user->getName(),
'wgUserGroups' => $user->getEffectiveGroups(),
'wgCategories' => $this->getCategories(),
'wgBreakFrames' => $this->getFrameOptions() == 'DENY',
'wgPageContentLanguage' => $lang->getCode(),
+ 'wgPageContentModel' => $title->getContentModel(),
'wgSeparatorTransformTable' => $compactSeparatorTransTable,
'wgDigitTransformTable' => $compactDigitTransTable,
'wgDefaultDateFormat' => $lang->getDefaultDateFormat(),
@@ -3031,10 +3040,16 @@ $templates
$vars['wgUserEditCount'] = $user->getEditCount();
$userReg = wfTimestampOrNull( TS_UNIX, $user->getRegistration() );
$vars['wgUserRegistration'] = $userReg !== null ? ( $userReg * 1000 ) : null;
+ // Get the revision ID of the oldest new message on the user's talk
+ // page. This can be used for constructing new message alerts on
+ // the client side.
+ $vars['wgUserNewMsgRevisionId'] = $user->getNewMessageRevisionId();
}
if ( $wgContLang->hasVariants() ) {
$vars['wgUserVariant'] = $wgContLang->getPreferredVariant();
}
+ // Same test as SkinTemplate
+ $vars['wgIsProbablyEditable'] = $title->quickUserCan( 'edit', $user ) && ( $title->exists() || $title->quickUserCan( 'create', $user ) );
foreach ( $title->getRestrictionTypes() as $type ) {
$vars['wgRestriction' . ucfirst( $type )] = $title->getRestrictions( $type );
}
@@ -3080,13 +3095,11 @@ $templates
}
/**
- * @param bool $addContentType Whether "<meta>" specifying content type should be returned
- *
* @return array in format "link name or number => 'link html'".
*/
- public function getHeadLinksArray( $addContentType = false ) {
+ public function getHeadLinksArray() {
global $wgUniversalEditButton, $wgFavicon, $wgAppleTouchIcon, $wgEnableAPI,
- $wgSitename, $wgVersion, $wgHtml5, $wgMimeType,
+ $wgSitename, $wgVersion,
$wgFeed, $wgOverrideSiteFeed, $wgAdvertisedFeedTypes,
$wgDisableLangConversion, $wgCanonicalLanguageLinks,
$wgRightsPage, $wgRightsUrl;
@@ -3095,30 +3108,13 @@ $templates
$canonicalUrl = $this->mCanonicalUrl;
- if ( $addContentType ) {
- if ( $wgHtml5 ) {
- # More succinct than <meta http-equiv=Content-Type>, has the
- # same effect
- $tags['meta-charset'] = Html::element( 'meta', array( 'charset' => 'UTF-8' ) );
- } else {
- $tags['meta-content-type'] = Html::element( 'meta', array(
- 'http-equiv' => 'Content-Type',
- 'content' => "$wgMimeType; charset=UTF-8"
- ) );
- $tags['meta-content-style-type'] = Html::element( 'meta', array( // bug 15835
- 'http-equiv' => 'Content-Style-Type',
- 'content' => 'text/css'
- ) );
- }
- }
-
$tags['meta-generator'] = Html::element( 'meta', array(
'name' => 'generator',
'content' => "MediaWiki $wgVersion",
) );
$p = "{$this->mIndexPolicy},{$this->mFollowPolicy}";
- if( $p !== 'index,follow' ) {
+ if ( $p !== 'index,follow' ) {
// http://www.robotstxt.org/wc/meta-user.html
// Only show if it's different from the default robots policy
$tags['meta-robots'] = Html::element( 'meta', array(
@@ -3127,21 +3123,6 @@ $templates
) );
}
- if ( count( $this->mKeywords ) > 0 ) {
- $strip = array(
- "/<.*?" . ">/" => '',
- "/_/" => ' '
- );
- $tags['meta-keywords'] = Html::element( 'meta', array(
- 'name' => 'keywords',
- 'content' => preg_replace(
- array_keys( $strip ),
- array_values( $strip ),
- implode( ',', $this->mKeywords )
- )
- ) );
- }
-
foreach ( $this->mMetatags as $tag ) {
if ( 0 == strcasecmp( 'http:', substr( $tag[0], 0, 5 ) ) ) {
$a = 'http-equiv';
@@ -3233,7 +3214,7 @@ $templates
foreach ( $variants as $_v ) {
$tags["variant-$_v"] = Html::element( 'link', array(
'rel' => 'alternate',
- 'hreflang' => $_v,
+ 'hreflang' => wfBCP47( $_v ),
'href' => $this->getTitle()->getLocalURL( array( 'variant' => $_v ) ) )
);
}
@@ -3266,7 +3247,7 @@ $templates
# Feeds
if ( $wgFeed ) {
- foreach( $this->getSyndicationLinks() as $format => $link ) {
+ foreach ( $this->getSyndicationLinks() as $format => $link ) {
# Use the page name for the title. In principle, this could
# lead to issues with having the same name for different feeds
# corresponding to the same page, but we can't avoid that at
@@ -3301,7 +3282,7 @@ $templates
foreach ( $wgAdvertisedFeedTypes as $format ) {
$tags[] = $this->feedLink(
$format,
- $rctitle->getLocalURL( "feed={$format}" ),
+ $rctitle->getLocalURL( array( 'feed' => $format ) ),
$this->msg( "site-{$format}-feed", $wgSitename )->text() # For grep: 'site-rss-feed', 'site-atom-feed'.
);
}
@@ -3329,13 +3310,10 @@ $templates
}
/**
- * @param $unused
- * @param bool $addContentType Whether "<meta>" specifying content type should be returned
- *
* @return string HTML tag links to be put in the header.
*/
- public function getHeadLinks( $unused = null, $addContentType = false ) {
- return implode( "\n", $this->getHeadLinksArray( $addContentType ) );
+ public function getHeadLinks() {
+ return implode( "\n", $this->getHeadLinksArray() );
}
/**
@@ -3368,13 +3346,13 @@ $templates
$options = array();
// Even though we expect the media type to be lowercase, but here we
// force it to lowercase to be safe.
- if( $media ) {
+ if ( $media ) {
$options['media'] = $media;
}
- if( $condition ) {
+ if ( $condition ) {
$options['condition'] = $condition;
}
- if( $dir ) {
+ if ( $dir ) {
$options['dir'] = $dir;
}
$this->styles[$style] = $options;
@@ -3386,7 +3364,7 @@ $templates
* @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() ) {
+ if ( $flip === 'flip' && $this->getLanguage()->isRTL() ) {
# If wanted, and the interface is right-to-left, flip the CSS
$style_css = CSSJanus::transform( $style_css, true, false );
}
@@ -3416,7 +3394,7 @@ $templates
if ( $wgUseSiteCss ) {
$moduleStyles[] = 'site';
$moduleStyles[] = 'noscript';
- if( $this->getUser()->isLoggedIn() ) {
+ if ( $this->getUser()->isLoggedIn() ) {
$moduleStyles[] = 'user.groups';
}
}
@@ -3497,9 +3475,9 @@ $templates
}
$this->mExtStyles = array();
- foreach( $this->styles as $file => $options ) {
+ foreach ( $this->styles as $file => $options ) {
$link = $this->styleLink( $file, $options );
- if( $link ) {
+ if ( $link ) {
$links[$file] = $link;
}
}
@@ -3515,22 +3493,22 @@ $templates
* @return String: HTML fragment
*/
protected function styleLink( $style, $options ) {
- if( isset( $options['dir'] ) ) {
- if( $this->getLanguage()->getDir() != $options['dir'] ) {
+ if ( isset( $options['dir'] ) ) {
+ if ( $this->getLanguage()->getDir() != $options['dir'] ) {
return '';
}
}
- if( isset( $options['media'] ) ) {
+ if ( isset( $options['media'] ) ) {
$media = self::transformCssMedia( $options['media'] );
- if( is_null( $media ) ) {
+ if ( is_null( $media ) ) {
return '';
}
} else {
$media = 'all';
}
- if( substr( $style, 0, 1 ) == '/' ||
+ if ( substr( $style, 0, 1 ) == '/' ||
substr( $style, 0, 5 ) == 'http:' ||
substr( $style, 0, 6 ) == 'https:' ) {
$url = $style;
@@ -3541,7 +3519,7 @@ $templates
$link = Html::linkedStyle( $url, $media );
- if( isset( $options['condition'] ) ) {
+ if ( isset( $options['condition'] ) ) {
$condition = htmlspecialchars( $options['condition'] );
$link = "<!--[if $condition]>$link<![endif]-->";
}
@@ -3556,7 +3534,7 @@ $templates
* this stylesheet
*/
public static function transformCssMedia( $media ) {
- global $wgRequest, $wgHandheldForIPhone;
+ global $wgRequest;
// http://www.w3.org/TR/css3-mediaqueries/#syntax
$screenMediaQueryRegex = '/^(?:only\s+)?screen\b/i';
@@ -3566,11 +3544,11 @@ $templates
'printable' => 'print',
'handheld' => 'handheld',
);
- foreach( $switches as $switch => $targetMedia ) {
- if( $wgRequest->getBool( $switch ) ) {
- if( $media == $targetMedia ) {
+ foreach ( $switches as $switch => $targetMedia ) {
+ if ( $wgRequest->getBool( $switch ) ) {
+ if ( $media == $targetMedia ) {
$media = '';
- } elseif( preg_match( $screenMediaQueryRegex, $media ) === 1 ) {
+ } 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)' ),
@@ -3581,25 +3559,13 @@ $templates
// 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' ) {
+ if ( $targetMedia == 'print' || $media == 'screen' ) {
return null;
}
}
}
}
- // Expand longer media queries as iPhone doesn't grok 'handheld'
- if( $wgHandheldForIPhone ) {
- $mediaAliases = array(
- 'screen' => 'screen and (min-device-width: 481px)',
- 'handheld' => 'handheld, only screen and (max-device-width: 480px)',
- );
-
- if( isset( $mediaAliases[$media] ) ) {
- $media = $mediaAliases[$media];
- }
- }
-
return $media;
}
@@ -3688,4 +3654,20 @@ $templates
return array();
}
+ /**
+ * Enables/disables TOC, doesn't override __NOTOC__
+ * @param bool $flag
+ * @since 1.22
+ */
+ public function enableTOC( $flag = true ) {
+ $this->mEnableTOC = $flag;
+ }
+
+ /**
+ * @return bool
+ * @since 1.22
+ */
+ public function isTOCEnabled() {
+ return $this->mEnableTOC;
+ }
}
diff --git a/includes/PHPVersionError.php b/includes/PHPVersionError.php
index 7749bf1f..02d3546f 100644
--- a/includes/PHPVersionError.php
+++ b/includes/PHPVersionError.php
@@ -38,7 +38,7 @@
* version are hardcoded here
*/
function wfPHPVersionError( $type ) {
- $mwVersion = '1.21';
+ $mwVersion = '1.22';
$minimumVersionPHP = '5.3.2';
$phpVersion = phpversion();
@@ -61,12 +61,12 @@ function wfPHPVersionError( $type ) {
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">
-<html xmlns='http://www.w3.org/1999/xhtml' lang='en'>
+<!DOCTYPE html>
+<html lang="en" dir="ltr">
<head>
+ <meta charset="UTF-8" />
<title>MediaWiki {$mwVersion}</title>
- <meta http-equiv='Content-Type' content='text/html; charset=utf-8' />
- <style type='text/css' media='screen'>
+ <style media='screen'>
body {
color: #000;
background-color: #fff;
@@ -112,6 +112,6 @@ HTML;
$finalOutput = ( $type == 'load.php' ) ? "/* $message */" : $message;
header( "$protocol 500 MediaWiki configuration Error" );
}
- echo( "$finalOutput\n" );
+ echo "$finalOutput\n";
die( 1 );
}
diff --git a/includes/PageQueryPage.php b/includes/PageQueryPage.php
index 01a2439f..61a535d6 100644
--- a/includes/PageQueryPage.php
+++ b/includes/PageQueryPage.php
@@ -27,12 +27,11 @@
* @ingroup SpecialPage
*/
abstract class PageQueryPage extends QueryPage {
-
/**
* Format the result as a simple link to the page
*
- * @param $skin Skin
- * @param $row Object: result row
+ * @param Skin $skin
+ * @param object $row Result row
* @return string
*/
public function formatResult( $skin, $row ) {
diff --git a/includes/Pager.php b/includes/Pager.php
index 9c514927..4a14c7e0 100644
--- a/includes/Pager.php
+++ b/includes/Pager.php
@@ -150,18 +150,19 @@ abstract class IndexPager extends ContextSource implements Pager {
}
$this->mIsBackwards = ( $this->mRequest->getVal( 'dir' ) == 'prev' );
- $this->mDb = wfGetDB( DB_SLAVE );
+ # Let the subclass set the DB here; otherwise use a slave DB for the current wiki
+ $this->mDb = $this->mDb ?: wfGetDB( DB_SLAVE );
$index = $this->getIndexField(); // column to sort on
$extraSort = $this->getExtraSortFields(); // extra columns to sort on for query planning
$order = $this->mRequest->getVal( 'order' );
- if( is_array( $index ) && isset( $index[$order] ) ) {
+ if ( is_array( $index ) && isset( $index[$order] ) ) {
$this->mOrderType = $order;
$this->mIndexField = $index[$order];
$this->mExtraSortFields = isset( $extraSort[$order] )
? (array)$extraSort[$order]
: array();
- } elseif( is_array( $index ) ) {
+ } elseif ( is_array( $index ) ) {
# First element is the default
reset( $index );
list( $this->mOrderType, $this->mIndexField ) = each( $index );
@@ -175,7 +176,7 @@ abstract class IndexPager extends ContextSource implements Pager {
$this->mExtraSortFields = (array)$extraSort;
}
- if( !isset( $this->mDefaultDirection ) ) {
+ if ( !isset( $this->mDefaultDirection ) ) {
$dir = $this->getDefaultDirections();
$this->mDefaultDirection = is_array( $dir )
? $dir[$this->mOrderType]
@@ -256,7 +257,7 @@ abstract class IndexPager extends ContextSource implements Pager {
* @param $limit Int|String
*/
function setLimit( $limit ) {
- $limit = (int) $limit;
+ $limit = (int)$limit;
// WebRequest::getLimitOffset() puts a cap of 5000, so do same here.
if ( $limit > 5000 ) {
$limit = 5000;
@@ -456,12 +457,12 @@ abstract class IndexPager extends ContextSource implements Pager {
}
$attrs = array();
- if( in_array( $type, array( 'first', 'prev', 'next', 'last' ) ) ) {
+ if ( in_array( $type, array( 'first', 'prev', 'next', 'last' ) ) ) {
# HTML5 rel attributes
$attrs['rel'] = $type;
}
- if( $type ) {
+ if ( $type ) {
$attrs['class'] = "mw-{$type}link";
}
@@ -593,7 +594,7 @@ abstract class IndexPager extends ContextSource implements Pager {
$this->doQuery();
}
// Hide navigation by default if there is nothing to page
- return !($this->mIsFirst && $this->mIsLast);
+ return !( $this->mIsFirst && $this->mIsLast );
}
/**
@@ -698,7 +699,9 @@ abstract class IndexPager extends ContextSource implements Pager {
*
* @return Array
*/
- protected function getExtraSortFields() { return array(); }
+ protected function getExtraSortFields() {
+ return array();
+ }
/**
* Return the default sorting direction: false for ascending, true for
@@ -719,7 +722,9 @@ abstract class IndexPager extends ContextSource implements Pager {
*
* @return Boolean
*/
- protected function getDefaultDirections() { return false; }
+ protected function getDefaultDirections() {
+ return false;
+ }
}
/**
@@ -739,7 +744,7 @@ abstract class AlphabeticPager extends IndexPager {
return '';
}
- if( isset( $this->mNavigationBar ) ) {
+ if ( isset( $this->mNavigationBar ) ) {
return $this->mNavigationBar;
}
@@ -762,7 +767,7 @@ abstract class AlphabeticPager extends IndexPager {
$this->msg( 'viewprevnext' )->rawParams( $pagingLinks['prev'],
$pagingLinks['next'], $limits )->escaped();
- if( !is_array( $this->getIndexField() ) ) {
+ if ( !is_array( $this->getIndexField() ) ) {
# Early return to avoid undue nesting
return $this->mNavigationBar;
}
@@ -770,14 +775,14 @@ abstract class AlphabeticPager extends IndexPager {
$extra = '';
$first = true;
$msgs = $this->getOrderTypeMessages();
- foreach( array_keys( $msgs ) as $order ) {
- if( $first ) {
+ foreach ( array_keys( $msgs ) as $order ) {
+ if ( $first ) {
$first = false;
} else {
$extra .= $this->msg( 'pipe-separator' )->escaped();
}
- if( $order == $this->mOrderType ) {
+ if ( $order == $this->mOrderType ) {
$extra .= $this->msg( $msgs[$order] )->escaped();
} else {
$extra .= $this->makeLink(
@@ -787,7 +792,7 @@ abstract class AlphabeticPager extends IndexPager {
}
}
- if( $extra !== '' ) {
+ if ( $extra !== '' ) {
$extra = ' ' . $this->msg( 'parentheses' )->rawParams( $extra )->escaped();
$this->mNavigationBar .= $extra;
}
@@ -868,9 +873,10 @@ abstract class ReverseChronologicalPager extends IndexPager {
$year = $this->mYear;
} else {
// If no year given, assume the current one
- $year = gmdate( 'Y' );
+ $timestamp = MWTimestamp::getInstance();
+ $year = $timestamp->format( 'Y' );
// If this month hasn't happened yet this year, go back to last year's month
- if( $this->mMonth > gmdate( 'n' ) ) {
+ if ( $this->mMonth > $timestamp->format( 'n' ) ) {
$year--;
}
}
@@ -917,7 +923,9 @@ abstract class TablePager extends IndexPager {
}
$this->mSort = $this->getRequest()->getText( 'sort' );
- if ( !array_key_exists( $this->mSort, $this->getFieldNames() ) ) {
+ if ( !array_key_exists( $this->mSort, $this->getFieldNames() )
+ || !$this->isFieldSortable( $this->mSort )
+ ) {
$this->mSort = $this->getDefaultSort();
}
if ( $this->getRequest()->getBool( 'asc' ) ) {
@@ -935,16 +943,15 @@ abstract class TablePager extends IndexPager {
*/
function getStartBody() {
global $wgStylePath;
- $tableClass = htmlspecialchars( $this->getTableClass() );
- $sortClass = htmlspecialchars( $this->getSortHeaderClass() );
+ $sortClass = $this->getSortHeaderClass();
- $s = "<table style='border:1px;' class=\"mw-datatable $tableClass\"><thead><tr>\n";
+ $s = '';
$fields = $this->getFieldNames();
# Make table header
foreach ( $fields as $field => $name ) {
if ( strval( $name ) == '' ) {
- $s .= "<th>&#160;</th>\n";
+ $s .= Html::rawElement( 'th', array(), '&#160;' ) . "\n";
} elseif ( $this->isFieldSortable( $field ) ) {
$query = array( 'sort' => $field, 'limit' => $this->mLimit );
if ( $field == $this->mSort ) {
@@ -963,20 +970,26 @@ abstract class TablePager extends IndexPager {
$query['desc'] = '1';
$alt = $this->msg( 'ascending_abbrev' )->escaped();
}
- $image = htmlspecialchars( "$wgStylePath/common/images/$image" );
+ $image = "$wgStylePath/common/images/$image";
$link = $this->makeLink(
- "<img width=\"12\" height=\"12\" alt=\"$alt\" src=\"$image\" />" .
- htmlspecialchars( $name ), $query );
- $s .= "<th class=\"$sortClass\">$link</th>\n";
+ Html::element( 'img', array( 'width' => 12, 'height' => 12,
+ 'alt' => $alt, 'src' => $image ) ) . htmlspecialchars( $name ), $query );
+ $s .= Html::rawElement( 'th', array( 'class' => $sortClass ), $link ) . "\n";
} else {
- $s .= '<th>' . $this->makeLink( htmlspecialchars( $name ), $query ) . "</th>\n";
+ $s .= Html::rawElement( 'th', array(),
+ $this->makeLink( htmlspecialchars( $name ), $query ) ) . "\n";
}
} else {
- $s .= '<th>' . htmlspecialchars( $name ) . "</th>\n";
+ $s .= Html::element( 'th', array(), $name ) . "\n";
}
}
- $s .= "</tr></thead><tbody>\n";
- return $s;
+
+ $tableClass = $this->getTableClass();
+ $ret = Html::openElement( 'table', array( 'style' => 'border:1px;', 'class' => "mw-datatable $tableClass" ) );
+ $ret .= Html::rawElement( 'thead', array(), Html::rawElement( 'tr', array(), "\n" . $s . "\n" ) );
+ $ret .= Html::openElement( 'tbody' ) . "\n";
+
+ return $ret;
}
/**
@@ -993,8 +1006,9 @@ abstract class TablePager extends IndexPager {
*/
function getEmptyBody() {
$colspan = count( $this->getFieldNames() );
- $msgEmpty = $this->msg( 'table_pager_empty' )->escaped();
- return "<tr><td colspan=\"$colspan\">$msgEmpty</td></tr>\n";
+ $msgEmpty = $this->msg( 'table_pager_empty' )->text();
+ return Html::rawElement( 'tr', array(),
+ Html::element( 'td', array( 'colspan' => $colspan ), $msgEmpty ) );
}
/**
@@ -1004,7 +1018,7 @@ abstract class TablePager extends IndexPager {
*/
function formatRow( $row ) {
$this->mCurrentRow = $row; // In case formatValue etc need to know
- $s = Xml::openElement( 'tr', $this->getRowAttrs( $row ) );
+ $s = Html::openElement( 'tr', $this->getRowAttrs( $row ) ) . "\n";
$fieldNames = $this->getFieldNames();
foreach ( $fieldNames as $field => $name ) {
@@ -1015,10 +1029,10 @@ abstract class TablePager extends IndexPager {
$formatted = '&#160;';
}
- $s .= Xml::tags( 'td', $this->getCellAttrs( $field, $value ), $formatted );
+ $s .= Html::rawElement( 'td', $this->getCellAttrs( $field, $value ), $formatted ) . "\n";
}
- $s .= "</tr>\n";
+ $s .= Html::closeElement( 'tr' ) . "\n";
return $s;
}
@@ -1130,7 +1144,7 @@ abstract class TablePager extends IndexPager {
'next' => 'arrow_disabled_right_25.png',
'last' => 'arrow_disabled_last_25.png',
);
- if( $this->getLanguage()->isRTL() ) {
+ if ( $this->getLanguage()->isRTL() ) {
$keys = array_keys( $labels );
$images = array_combine( $keys, array_reverse( $images ) );
$disabledImages = array_combine( $keys, array_reverse( $disabledImages ) );
@@ -1140,49 +1154,67 @@ abstract class TablePager extends IndexPager {
$disabledTexts = array();
foreach ( $labels as $type => $label ) {
$msgLabel = $this->msg( $label )->escaped();
- $linkTexts[$type] = "<img src=\"$path/{$images[$type]}\" alt=\"$msgLabel\"/><br />$msgLabel";
- $disabledTexts[$type] = "<img src=\"$path/{$disabledImages[$type]}\" alt=\"$msgLabel\"/><br />$msgLabel";
+ $linkTexts[$type] = Html::element( 'img', array( 'src' => "$path/{$images[$type]}",
+ 'alt' => $msgLabel ) ) . "<br />$msgLabel";
+ $disabledTexts[$type] = Html::element( 'img', array( 'src' => "$path/{$disabledImages[$type]}",
+ 'alt' => $msgLabel ) ) . "<br />$msgLabel";
}
$links = $this->getPagingLinks( $linkTexts, $disabledTexts );
- $navClass = htmlspecialchars( $this->getNavClass() );
- $s = "<table class=\"$navClass\"><tr>\n";
+ $s = Html::openElement( 'table', array( 'class' => $this->getNavClass() ) );
+ $s .= Html::openElement( 'tr' ) . "\n";
$width = 100 / count( $links ) . '%';
foreach ( $labels as $type => $label ) {
- $s .= "<td style='width:$width;'>{$links[$type]}</td>\n";
+ $s .= Html::rawElement( 'td', array( 'style' => "width:$width;" ), $links[$type] ) . "\n";
}
- $s .= "</tr></table>\n";
+ $s .= Html::closeElement( 'tr' ) . Html::closeElement( 'table' ) . "\n";
return $s;
}
/**
* Get a "<select>" element which has options for each of the allowed limits
*
+ * @param $attribs String: Extra attributes to set
* @return String: HTML fragment
*/
- public function getLimitSelect() {
+ public function getLimitSelect( $attribs = array() ) {
+ $select = new XmlSelect( 'limit', false, $this->mLimit );
+ $select->addOptions( $this->getLimitSelectList() );
+ foreach ( $attribs as $name => $value ) {
+ $select->setAttribute( $name, $value );
+ }
+ return $select->getHTML();
+ }
+
+ /**
+ * Get a list of items to show in a "<select>" element of limits.
+ * This can be passed directly to XmlSelect::addOptions().
+ *
+ * @since 1.22
+ * @return array
+ */
+ public function getLimitSelectList() {
# Add the current limit from the query string
# to avoid that the limit is lost after clicking Go next time
if ( !in_array( $this->mLimit, $this->mLimitsShown ) ) {
$this->mLimitsShown[] = $this->mLimit;
sort( $this->mLimitsShown );
}
- $s = Html::openElement( 'select', array( 'name' => 'limit' ) ) . "\n";
+ $ret = array();
foreach ( $this->mLimitsShown as $key => $value ) {
# 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 {
$limit = $key;
$text = $value;
}
- $s .= Xml::option( $text, $limit, $limit == $this->mLimit ) . "\n";
+ $ret[$text] = $limit;
}
- $s .= Html::closeElement( 'select' );
- return $s;
+ return $ret;
}
/**
@@ -1201,9 +1233,7 @@ abstract class TablePager extends IndexPager {
}
$s = '';
foreach ( $query as $name => $value ) {
- $encName = htmlspecialchars( $name );
- $encValue = htmlspecialchars( $value );
- $s .= "<input type=\"hidden\" name=\"$encName\" value=\"$encValue\"/>\n";
+ $s .= Html::hidden( $name, $value ) . "\n";
}
return $s;
}
@@ -1216,12 +1246,14 @@ abstract class TablePager extends IndexPager {
function getLimitForm() {
global $wgScript;
- return Xml::openElement(
+ return Html::rawElement(
'form',
array(
'method' => 'get',
'action' => $wgScript
- ) ) . "\n" . $this->getLimitDropdown() . "</form>\n";
+ ),
+ "\n" . $this->getLimitDropdown()
+ ) . "\n";
}
/**
diff --git a/includes/PathRouter.php b/includes/PathRouter.php
index fc891bb3..435e09ef 100644
--- a/includes/PathRouter.php
+++ b/includes/PathRouter.php
@@ -141,10 +141,10 @@ class PathRouter {
}
$pattern = (object)array(
- 'path' => $path,
- 'params' => $params,
+ 'path' => $path,
+ 'params' => $params,
'options' => $options,
- 'key' => $key,
+ 'key' => $key,
);
$pattern->weight = self::makeWeight( $pattern );
$this->patterns[] = $pattern;
@@ -185,7 +185,7 @@ class PathRouter {
*/
protected function sortByWeight() {
$weights = array();
- foreach( $this->patterns as $key => $pattern ) {
+ foreach ( $this->patterns as $key => $pattern ) {
$weights[$key] = $pattern->weight;
}
array_multisort( $weights, SORT_DESC, SORT_NUMERIC, $this->patterns );
@@ -203,7 +203,7 @@ class PathRouter {
$path = explode( '/', $pattern->path );
# For each level of the path
- foreach( $path as $piece ) {
+ foreach ( $path as $piece ) {
if ( preg_match( '/^\$(\d+|key)$/u', $piece ) ) {
# For a piece that is only a $1 variable add 1 points of weight
$weight += 1;
diff --git a/includes/PoolCounter.php b/includes/PoolCounter.php
index 2ebef04e..2dac9388 100644
--- a/includes/PoolCounter.php
+++ b/includes/PoolCounter.php
@@ -22,25 +22,24 @@
*/
/**
- * When you have many workers (threads/servers) giving service, and a
+ * When you have many workers (threads/servers) giving service, and a
* cached item expensive to produce expires, you may get several workers
* doing the job at the same time.
*
- * Given enough requests and the item expiring fast (non-cacheable,
+ * Given enough requests and the item expiring fast (non-cacheable,
* lots of edits...) that single work can end up unfairly using most (all)
* of the cpu of the pool. This is also known as 'Michael Jackson effect'
* since this effect triggered on the english wikipedia on the day Michael
* Jackson died, the biographical article got hit with several edits per
* minutes and hundreds of read hits.
*
- * The PoolCounter provides semaphore semantics for restricting the number
+ * The PoolCounter provides semaphore semantics for restricting the number
* of workers that may be concurrently performing such single task.
*
- * By default PoolCounter_Stub is used, which provides no locking. You
+ * By default PoolCounter_Stub is used, which provides no locking. You
* can get a useful one in the PoolCounter extension.
*/
abstract class PoolCounter {
-
/* Return codes */
const LOCKED = 1; /* Lock acquired */
const RELEASED = 2; /* Lock released */
@@ -52,39 +51,26 @@ abstract class PoolCounter {
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.
- *
- * @return Locked/Error
- */
- abstract function acquireForMe();
-
- /**
- * I want to do this task, but if anyone else does it
- * instead, it's also fine for me. I will read its cached data.
- *
- * @return Locked/Done/Error
- */
- abstract function acquireForAnyone();
-
- /**
- * I have successfully finished my task.
- * Lets another one grab the lock, and returns the workers
- * waiting on acquireForAnyone()
- *
- * @return Released/NotLocked/Error
- */
- abstract function release();
+ /** @var string All workers with the same key share the lock */
+ protected $key;
+ /** @var integer Maximum number of workers doing the task simultaneously */
+ protected $workers;
+ /** @var integer If this number of workers are already working/waiting, fail instead of wait */
+ protected $maxqueue;
+ /** @var float Maximum time in seconds to wait for the lock */
+ protected $timeout;
/**
- * $key: All workers with the same key share the lock.
- * $workers: It wouldn't be a good idea to have more than this number of
- * workers doing the task simultaneously.
- * $maxqueue: If this number of workers are already working/waiting,
- * fail instead of wait.
- * $timeout: Maximum time in seconds to wait for the lock.
+ * @param array $conf
+ * @param string $type
+ * @param string $key
*/
- protected $key, $workers, $maxqueue, $timeout;
+ protected function __construct( $conf, $type, $key ) {
+ $this->key = $key;
+ $this->workers = $conf['workers'];
+ $this->maxqueue = $conf['maxqueue'];
+ $this->timeout = $conf['timeout'];
+ }
/**
* Create a Pool counter. This should only be called from the PoolWorks.
@@ -105,58 +91,74 @@ abstract class PoolCounter {
return new $class( $conf, $type, $key );
}
- protected function __construct( $conf, $type, $key ) {
- $this->key = $key;
- $this->workers = $conf['workers'];
- $this->maxqueue = $conf['maxqueue'];
- $this->timeout = $conf['timeout'];
- }
-}
-
-class PoolCounter_Stub extends PoolCounter {
-
/**
- * @return Status
+ * I want to do this task and I need to do it myself.
+ *
+ * @return Status Value is one of Locked/Error
*/
- function acquireForMe() {
- return Status::newGood( PoolCounter::LOCKED );
- }
+ abstract public function acquireForMe();
/**
- * @return Status
+ * I want to do this task, but if anyone else does it
+ * instead, it's also fine for me. I will read its cached data.
+ *
+ * @return Status Value is one of Locked/Done/Error
*/
- function acquireForAnyone() {
- return Status::newGood( PoolCounter::LOCKED );
- }
+ abstract public function acquireForAnyone();
/**
- * @return Status
+ * I have successfully finished my task.
+ * Lets another one grab the lock, and returns the workers
+ * waiting on acquireForAnyone()
+ *
+ * @return Status value is one of Released/NotLocked/Error
*/
- function release() {
- return Status::newGood( PoolCounter::RELEASED );
- }
+ abstract public function release();
+}
+class PoolCounter_Stub extends PoolCounter {
public function __construct() {
/* No parameters needed */
}
+
+ public function acquireForMe() {
+ return Status::newGood( PoolCounter::LOCKED );
+ }
+
+ public function acquireForAnyone() {
+ return Status::newGood( PoolCounter::LOCKED );
+ }
+
+ public function release() {
+ return Status::newGood( PoolCounter::RELEASED );
+ }
}
/**
- * Handy class for dealing with PoolCounters using class members instead of callbacks.
+ * Class for dealing with PoolCounters using class members
*/
abstract class PoolCounterWork {
protected $cacheable = false; //Does this override getCachedWork() ?
/**
- * Actually perform the work, caching it if needed.
+ * @param string $type The type of PoolCounter to use
+ * @param string $key Key that identifies the queue this work is placed on
*/
- abstract function doWork();
+ public function __construct( $type, $key ) {
+ $this->poolCounter = PoolCounter::factory( $type, $key );
+ }
+
+ /**
+ * Actually perform the work, caching it if needed
+ * @return mixed work result or false
+ */
+ abstract public function doWork();
/**
* Retrieve the work from cache
* @return mixed work result or false
*/
- function getCachedWork() {
+ public function getCachedWork() {
return false;
}
@@ -165,7 +167,7 @@ abstract class PoolCounterWork {
* message.
* @return mixed work result or false
*/
- function fallback() {
+ public function fallback() {
return false;
}
@@ -181,17 +183,28 @@ abstract class PoolCounterWork {
* Log an error
*
* @param $status Status
+ * @return void
*/
function logError( $status ) {
wfDebugLog( 'poolcounter', $status->getWikiText() );
}
/**
- * Get the result of the work (whatever it is), or false.
+ * Get the result of the work (whatever it is), or the result of the error() function.
+ * This returns the result of the first applicable method that returns a non-false value,
+ * where the methods are checked in the following order:
+ * - a) doWork() : Applies if the work is exclusive or no another process
+ * is doing it, and on the condition that either this process
+ * successfully entered the pool or the pool counter is down.
+ * - b) doCachedWork() : Applies if the work is cacheable and this blocked on another
+ * process which finished the work.
+ * - c) fallback() : Applies for all remaining cases.
+ * If these all fall through (by returning false), then the result of error() is returned.
+ *
* @param $skipcache bool
- * @return bool|mixed
+ * @return mixed
*/
- function execute( $skipcache = false ) {
+ public function execute( $skipcache = false ) {
if ( $this->cacheable && !$skipcache ) {
$status = $this->poolCounter->acquireForAnyone();
} else {
@@ -232,15 +245,85 @@ abstract class PoolCounterWork {
/* These two cases should never be hit... */
case PoolCounter::ERROR:
default:
- $errors = array( PoolCounter::QUEUE_FULL => 'pool-queuefull', PoolCounter::TIMEOUT => 'pool-timeout' );
+ $errors = array(
+ PoolCounter::QUEUE_FULL => 'pool-queuefull',
+ PoolCounter::TIMEOUT => 'pool-timeout' );
- $status = Status::newFatal( isset( $errors[$status->value] ) ? $errors[$status->value] : 'pool-errorunknown' );
+ $status = Status::newFatal( isset( $errors[$status->value] )
+ ? $errors[$status->value]
+ : 'pool-errorunknown' );
$this->logError( $status );
return $this->error( $status );
}
}
+}
- function __construct( $type, $key ) {
- $this->poolCounter = PoolCounter::factory( $type, $key );
+/**
+ * Convenience class for dealing with PoolCounters using callbacks
+ * @since 1.22
+ */
+class PoolCounterWorkViaCallback extends PoolCounterWork {
+ /** @var callable */
+ protected $doWork;
+ /** @var callable|null */
+ protected $doCachedWork;
+ /** @var callable|null */
+ protected $fallback;
+ /** @var callable|null */
+ protected $error;
+
+ /**
+ * Build a PoolCounterWork class from a type, key, and callback map.
+ *
+ * The callback map must at least have a callback for the 'doWork' method.
+ * Additionally, callbacks can be provided for the 'doCachedWork', 'fallback',
+ * and 'error' methods. Methods without callbacks will be no-ops that return false.
+ * If a 'doCachedWork' callback is provided, then execute() may wait for any prior
+ * process in the pool to finish and reuse its cached result.
+ *
+ * @param string $type
+ * @param string $key
+ * @param array $callbacks Map of callbacks
+ * @throws MWException
+ */
+ public function __construct( $type, $key, array $callbacks ) {
+ parent::__construct( $type, $key );
+ foreach ( array( 'doWork', 'doCachedWork', 'fallback', 'error' ) as $name ) {
+ if ( isset( $callbacks[$name] ) ) {
+ if ( !is_callable( $callbacks[$name] ) ) {
+ throw new MWException( "Invalid callback provided for '$name' function." );
+ }
+ $this->$name = $callbacks[$name];
+ }
+ }
+ if ( !isset( $this->doWork ) ) {
+ throw new MWException( "No callback provided for 'doWork' function." );
+ }
+ $this->cacheable = isset( $this->doCachedWork );
+ }
+
+ public function doWork() {
+ return call_user_func_array( $this->doWork, array() );
+ }
+
+ public function getCachedWork() {
+ if ( $this->doCachedWork ) {
+ return call_user_func_array( $this->doCachedWork, array() );
+ }
+ return false;
+ }
+
+ function fallback() {
+ if ( $this->fallback ) {
+ return call_user_func_array( $this->fallback, array() );
+ }
+ return false;
+ }
+
+ function error( $status ) {
+ if ( $this->error ) {
+ return call_user_func_array( $this->error, array( $status ) );
+ }
+ return false;
}
}
diff --git a/includes/Preferences.php b/includes/Preferences.php
index 56dba05e..c9caf4f7 100644
--- a/includes/Preferences.php
+++ b/includes/Preferences.php
@@ -56,6 +56,12 @@ class Preferences {
'searchlimit' => array( 'Preferences', 'filterIntval' ),
);
+ // Stuff that shouldn't be saved as a preference.
+ private static $saveBlacklist = array(
+ 'realname',
+ 'emailaddress',
+ );
+
/**
* @throws MWException
* @param $user User
@@ -90,10 +96,19 @@ class Preferences {
}
}
+ ## Make sure that form fields have their parent set. See bug 41337.
+ $dummyForm = new HTMLForm( array(), $context );
+
+ $disable = !$user->isAllowed( 'editmyoptions' );
+
## Prod in defaults from the user
foreach ( $defaultPreferences as $name => &$info ) {
$prefFromUser = self::getOptionFromUser( $name, $info, $user );
+ if ( $disable && !in_array( $name, self::$saveBlacklist ) ) {
+ $info['disabled'] = 'disabled';
+ }
$field = HTMLForm::loadInputFromParameters( $name, $info ); // For validation
+ $field->mParent = $dummyForm;
$defaultOptions = User::getDefaultOptions();
$globalDefault = isset( $defaultOptions[$name] )
? $defaultOptions[$name]
@@ -153,7 +168,7 @@ class Preferences {
foreach ( $columns as $column ) {
foreach ( $rows as $row ) {
- if ( $user->getOption( "$prefix-$column-$row" ) ) {
+ if ( $user->getOption( "$prefix$column-$row" ) ) {
$val[] = "$column-$row";
}
}
@@ -173,7 +188,8 @@ class Preferences {
global $wgAuth, $wgContLang, $wgParser, $wgCookieExpiration, $wgLanguageCode,
$wgDisableTitleConversion, $wgDisableLangConversion, $wgMaxSigChars,
$wgEnableEmail, $wgEmailConfirmToEdit, $wgEnableUserEmail, $wgEmailAuthentication,
- $wgEnotifWatchlist, $wgEnotifUserTalk, $wgEnotifRevealEditorAddress;
+ $wgEnotifWatchlist, $wgEnotifUserTalk, $wgEnotifRevealEditorAddress,
+ $wgSecureLogin;
// retrieving user name for GENDER and misc.
$userName = $user->getName();
@@ -225,10 +241,14 @@ class Preferences {
'section' => 'personal/info',
);
+ $editCount = Linker::link( SpecialPage::getTitleFor( "Contributions", $userName ),
+ $lang->formatNum( $user->getEditCount() ) );
+
$defaultPreferences['editcount'] = array(
'type' => 'info',
+ 'raw' => true,
'label-message' => 'prefs-edits',
- 'default' => $lang->formatNum( $user->getEditCount() ),
+ 'default' => $editCount,
'section' => 'personal/info',
);
@@ -248,28 +268,20 @@ class Preferences {
);
}
+ $canViewPrivateInfo = $user->isAllowed( 'viewmyprivateinfo' );
+ $canEditPrivateInfo = $user->isAllowed( 'editmyprivateinfo' );
+
// Actually changeable stuff
$defaultPreferences['realname'] = array(
- 'type' => $wgAuth->allowPropChange( 'realname' ) ? 'text' : 'info',
+ // (not really "private", but still shouldn't be edited without permission)
+ 'type' => $canEditPrivateInfo && $wgAuth->allowPropChange( 'realname' ) ? 'text' : 'info',
'default' => $user->getRealName(),
'section' => 'personal/info',
'label-message' => 'yourrealname',
'help-message' => 'prefs-help-realname',
);
- $defaultPreferences['gender'] = array(
- 'type' => 'select',
- 'section' => 'personal/info',
- 'options' => array(
- $context->msg( 'gender-male' )->text() => 'male',
- $context->msg( 'gender-female' )->text() => 'female',
- $context->msg( 'gender-unknown' )->text() => 'unknown',
- ),
- 'label-message' => 'yourgender',
- 'help-message' => 'prefs-help-gender',
- );
-
- if ( $wgAuth->allowPasswordChange() ) {
+ if ( $canEditPrivateInfo && $wgAuth->allowPasswordChange() ) {
$link = Linker::link( SpecialPage::getTitleFor( 'ChangePassword' ),
$context->msg( 'prefs-resetpass' )->escaped(), array(),
array( 'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText() ) );
@@ -290,6 +302,15 @@ class Preferences {
'section' => 'personal/info',
);
}
+ // Only show preferhttps if secure login is turned on
+ if ( $wgSecureLogin && wfCanIPUseHTTPS( $context->getRequest()->getIP() ) ) {
+ $defaultPreferences['prefershttps'] = array(
+ 'type' => 'toggle',
+ 'label-message' => 'tog-prefershttps',
+ 'help-message' => 'prefs-help-prefershttps',
+ 'section' => 'personal/info'
+ );
+ }
// Language
$languages = Language::fetchLanguageNames( null, 'mw' );
@@ -310,39 +331,74 @@ class Preferences {
'label-message' => 'yourlanguage',
);
- /* see if there are multiple language variants to choose from*/
- $variantArray = array();
+ $defaultPreferences['gender'] = array(
+ 'type' => 'radio',
+ 'section' => 'personal/i18n',
+ 'options' => array(
+ $context->msg( 'parentheses',
+ $context->msg( 'gender-unknown' )->text()
+ )->text() => 'unknown',
+ $context->msg( 'gender-female' )->text() => 'female',
+ $context->msg( 'gender-male' )->text() => 'male',
+ ),
+ 'label-message' => 'yourgender',
+ 'help-message' => 'prefs-help-gender',
+ );
+
+ // see if there are multiple language variants to choose from
if ( !$wgDisableLangConversion ) {
- $variants = $wgContLang->getVariants();
+ foreach ( LanguageConverter::$languagesWithVariants as $langCode ) {
+ if ( $langCode == $wgContLang->getCode() ) {
+ $variants = $wgContLang->getVariants();
- foreach ( $variants as $v ) {
- $v = str_replace( '_', '-', strtolower( $v ) );
- $variantArray[$v] = $wgContLang->getVariantname( $v, false );
- }
+ if ( count( $variants ) <= 1 ) {
+ continue;
+ }
- $options = array();
- foreach ( $variantArray as $code => $name ) {
- $display = wfBCP47( $code ) . ' - ' . $name;
- $options[$display] = $code;
- }
+ $variantArray = array();
+ foreach ( $variants as $v ) {
+ $v = str_replace( '_', '-', strtolower( $v ) );
+ $variantArray[$v] = $lang->getVariantname( $v, false );
+ }
- if ( count( $variantArray ) > 1 ) {
- $defaultPreferences['variant'] = array(
- 'label-message' => 'yourvariant',
- 'type' => 'select',
- 'options' => $options,
- 'section' => 'personal/i18n',
- 'help-message' => 'prefs-help-variant',
- );
+ $options = array();
+ foreach ( $variantArray as $code => $name ) {
+ $display = wfBCP47( $code ) . ' - ' . $name;
+ $options[$display] = $code;
+ }
+
+ $defaultPreferences['variant'] = array(
+ 'label-message' => 'yourvariant',
+ 'type' => 'select',
+ 'options' => $options,
+ 'section' => 'personal/i18n',
+ 'help-message' => 'prefs-help-variant',
+ );
+
+ if ( !$wgDisableTitleConversion ) {
+ $defaultPreferences['noconvertlink'] = array(
+ 'type' => 'toggle',
+ 'section' => 'personal/i18n',
+ 'label-message' => 'tog-noconvertlink',
+ );
+ }
+ } else {
+ $defaultPreferences["variant-$langCode"] = array(
+ 'type' => 'api',
+ );
+ }
}
}
- if ( count( $variantArray ) > 1 && !$wgDisableLangConversion && !$wgDisableTitleConversion ) {
- $defaultPreferences['noconvertlink'] =
- array(
+ // Stuff from Language::getExtraUserToggles()
+ // FIXME is this dead code? $extraUserToggles doesn't seem to be defined for any language
+ $toggles = $wgContLang->getExtraUserToggles();
+
+ foreach ( $toggles as $toggle ) {
+ $defaultPreferences[$toggle] = array(
'type' => 'toggle',
'section' => 'personal/i18n',
- 'label-message' => 'tog-noconvertlink',
+ 'label-message' => "tog-$toggle",
);
}
@@ -374,43 +430,45 @@ class Preferences {
## Email stuff
if ( $wgEnableEmail ) {
- $helpMessages[] = $wgEmailConfirmToEdit
- ? 'prefs-help-email-required'
- : 'prefs-help-email';
-
- if( $wgEnableUserEmail ) {
- // additional messages when users can send email to each other
- $helpMessages[] = 'prefs-help-email-others';
- }
+ if ( $canViewPrivateInfo ) {
+ $helpMessages[] = $wgEmailConfirmToEdit
+ ? 'prefs-help-email-required'
+ : 'prefs-help-email';
+
+ if ( $wgEnableUserEmail ) {
+ // additional messages when users can send email to each other
+ $helpMessages[] = 'prefs-help-email-others';
+ }
- $link = Linker::link(
- SpecialPage::getTitleFor( 'ChangeEmail' ),
- $context->msg( $user->getEmail() ? 'prefs-changeemail' : 'prefs-setemail' )->escaped(),
- array(),
- array( 'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText() ) );
+ $emailAddress = $user->getEmail() ? htmlspecialchars( $user->getEmail() ) : '';
+ if ( $canEditPrivateInfo && $wgAuth->allowPropChange( 'emailaddress' ) ) {
+ $link = Linker::link(
+ SpecialPage::getTitleFor( 'ChangeEmail' ),
+ $context->msg( $user->getEmail() ? 'prefs-changeemail' : 'prefs-setemail' )->escaped(),
+ array(),
+ array( 'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText() ) );
+
+ $emailAddress .= $emailAddress == '' ? $link : (
+ $context->msg( 'word-separator' )->plain()
+ . $context->msg( 'parentheses' )->rawParams( $link )->plain()
+ );
+ }
- $emailAddress = $user->getEmail() ? htmlspecialchars( $user->getEmail() ) : '';
- if ( $wgAuth->allowPropChange( 'emailaddress' ) ) {
- $emailAddress .= $emailAddress == '' ? $link : (
- $context->msg( 'word-separator' )->plain()
- . $context->msg( 'parentheses' )->rawParams( $link )->plain()
+ $defaultPreferences['emailaddress'] = array(
+ 'type' => 'info',
+ 'raw' => true,
+ 'default' => $emailAddress,
+ 'label-message' => 'youremail',
+ 'section' => 'personal/email',
+ 'help-messages' => $helpMessages,
+ # 'cssclass' chosen below
);
}
- $defaultPreferences['emailaddress'] = array(
- 'type' => 'info',
- 'raw' => true,
- 'default' => $emailAddress,
- 'label-message' => 'youremail',
- 'section' => 'personal/email',
- 'help-messages' => $helpMessages,
- # 'cssclass' chosen below
- );
-
$disableEmailPrefs = false;
- $emailauthenticationclass = 'mw-email-not-authenticated';
if ( $wgEmailAuthentication ) {
+ $emailauthenticationclass = 'mw-email-not-authenticated';
if ( $user->getEmail() ) {
if ( $user->getEmailAuthenticationTimestamp() ) {
// date and time are separate parameters to facilitate localisation.
@@ -432,7 +490,7 @@ class Preferences {
SpecialPage::getTitleFor( 'Confirmemail' ),
$context->msg( 'emailconfirmlink' )->escaped()
) . '<br />';
- $emailauthenticationclass="mw-email-not-authenticated";
+ $emailauthenticationclass = "mw-email-not-authenticated";
}
} else {
$disableEmailPrefs = true;
@@ -440,17 +498,19 @@ class Preferences {
$emailauthenticationclass = 'mw-email-none';
}
- $defaultPreferences['emailauthentication'] = array(
- 'type' => 'info',
- 'raw' => true,
- 'section' => 'personal/email',
- 'label-message' => 'prefs-emailconfirm-label',
- 'default' => $emailauthenticated,
- # Apply the same CSS class used on the input to the message:
- 'cssclass' => $emailauthenticationclass,
- );
+ if ( $canViewPrivateInfo ) {
+ $defaultPreferences['emailauthentication'] = array(
+ 'type' => 'info',
+ 'raw' => true,
+ 'section' => 'personal/email',
+ 'label-message' => 'prefs-emailconfirm-label',
+ 'default' => $emailauthenticated,
+ # Apply the same CSS class used on the input to the message:
+ 'cssclass' => $emailauthenticationclass,
+ );
+ $defaultPreferences['emailaddress']['cssclass'] = $emailauthenticationclass;
+ }
}
- $defaultPreferences['emailaddress']['cssclass'] = $emailauthenticationclass;
if ( $wgEnableUserEmail && $user->isAllowed( 'sendemail' ) ) {
$defaultPreferences['disablemail'] = array(
@@ -546,18 +606,6 @@ class Preferences {
'section' => 'rendering/skin',
);
}
-
- $selectedSkin = $user->getOption( 'skin' );
- if ( in_array( $selectedSkin, array( 'cologneblue', 'standard' ) ) ) {
- $settings = array_flip( $context->getLanguage()->getQuickbarSettings() );
-
- $defaultPreferences['quickbar'] = array(
- 'type' => 'radio',
- 'options' => $settings,
- 'section' => 'rendering/skin',
- 'label-message' => 'qbsettings',
- );
- }
}
/**
@@ -660,6 +708,18 @@ class Preferences {
* @param $defaultPreferences Array
*/
static function renderingPreferences( $user, IContextSource $context, &$defaultPreferences ) {
+ ## Diffs ####################################
+ $defaultPreferences['diffonly'] = array(
+ 'type' => 'toggle',
+ 'section' => 'rendering/diffs',
+ 'label-message' => 'tog-diffonly',
+ );
+ $defaultPreferences['norollbackdiff'] = array(
+ 'type' => 'toggle',
+ 'section' => 'rendering/diffs',
+ 'label-message' => 'tog-norollbackdiff',
+ );
+
## Page Rendering ##############################
global $wgAllowUserCssPrefs;
if ( $wgAllowUserCssPrefs ) {
@@ -686,7 +746,7 @@ class Preferences {
'section' => 'rendering/advancedrendering',
'options' => $stubThresholdOptions,
'size' => 20,
- 'label' => $context->msg( 'stub-threshold' )->text(), // Raw HTML message. Yay?
+ 'label-raw' => $context->msg( 'stub-threshold' )->text(), // Raw HTML message. Yay?
);
if ( $wgAllowUserCssPrefs ) {
@@ -706,11 +766,6 @@ class Preferences {
'section' => 'rendering/advancedrendering',
'label-message' => 'tog-showhiddencats'
);
- $defaultPreferences['showjumplinks'] = array(
- 'type' => 'toggle',
- 'section' => 'rendering/advancedrendering',
- 'label-message' => 'tog-showjumplinks',
- );
if ( $wgAllowUserCssPrefs ) {
$defaultPreferences['justify'] = array(
@@ -733,28 +788,31 @@ class Preferences {
* @param $defaultPreferences Array
*/
static function editingPreferences( $user, IContextSource $context, &$defaultPreferences ) {
- global $wgUseExternalEditor, $wgAllowUserCssPrefs;
+ global $wgAllowUserCssPrefs;
## Editing #####################################
- $defaultPreferences['cols'] = array(
- 'type' => 'int',
- 'label-message' => 'columns',
- 'section' => 'editing/textboxsize',
- 'min' => 4,
- 'max' => 1000,
+ if ( $wgAllowUserCssPrefs ) {
+ $defaultPreferences['editsection'] = array(
+ 'type' => 'toggle',
+ 'section' => 'editing/advancedediting',
+ 'label-message' => 'tog-editsection',
+ );
+ }
+ $defaultPreferences['editsectiononrightclick'] = array(
+ 'type' => 'toggle',
+ 'section' => 'editing/advancedediting',
+ 'label-message' => 'tog-editsectiononrightclick',
);
- $defaultPreferences['rows'] = array(
- 'type' => 'int',
- 'label-message' => 'rows',
- 'section' => 'editing/textboxsize',
- 'min' => 4,
- 'max' => 1000,
+ $defaultPreferences['editondblclick'] = array(
+ 'type' => 'toggle',
+ 'section' => 'editing/advancedediting',
+ 'label-message' => 'tog-editondblclick',
);
if ( $wgAllowUserCssPrefs ) {
$defaultPreferences['editfont'] = array(
'type' => 'select',
- 'section' => 'editing/advancedediting',
+ 'section' => 'editing/editor',
'label-message' => 'editfont-style',
'options' => array(
$context->msg( 'editfont-default' )->text() => 'default',
@@ -764,72 +822,59 @@ class Preferences {
)
);
}
- $defaultPreferences['previewontop'] = array(
- 'type' => 'toggle',
- 'section' => 'editing/advancedediting',
- 'label-message' => 'tog-previewontop',
+ $defaultPreferences['cols'] = array(
+ 'type' => 'int',
+ 'label-message' => 'columns',
+ 'section' => 'editing/editor',
+ 'min' => 4,
+ 'max' => 1000,
);
- $defaultPreferences['previewonfirst'] = array(
- 'type' => 'toggle',
- 'section' => 'editing/advancedediting',
- 'label-message' => 'tog-previewonfirst',
+ $defaultPreferences['rows'] = array(
+ 'type' => 'int',
+ 'label-message' => 'rows',
+ 'section' => 'editing/editor',
+ 'min' => 4,
+ 'max' => 1000,
);
-
- if ( $wgAllowUserCssPrefs ) {
- $defaultPreferences['editsection'] = array(
+ if ( $user->isAllowed( 'minoredit' ) ) {
+ $defaultPreferences['minordefault'] = array(
'type' => 'toggle',
- 'section' => 'editing/advancedediting',
- 'label-message' => 'tog-editsection',
+ 'section' => 'editing/editor',
+ 'label-message' => 'tog-minordefault',
);
}
- $defaultPreferences['editsectiononrightclick'] = array(
+ $defaultPreferences['forceeditsummary'] = array(
'type' => 'toggle',
- 'section' => 'editing/advancedediting',
- 'label-message' => 'tog-editsectiononrightclick',
+ 'section' => 'editing/editor',
+ 'label-message' => 'tog-forceeditsummary',
);
- $defaultPreferences['editondblclick'] = array(
+ $defaultPreferences['useeditwarning'] = array(
'type' => 'toggle',
- 'section' => 'editing/advancedediting',
- 'label-message' => 'tog-editondblclick',
+ 'section' => 'editing/editor',
+ 'label-message' => 'tog-useeditwarning',
);
$defaultPreferences['showtoolbar'] = array(
'type' => 'toggle',
- 'section' => 'editing/advancedediting',
+ 'section' => 'editing/editor',
'label-message' => 'tog-showtoolbar',
);
- if ( $user->isAllowed( 'minoredit' ) ) {
- $defaultPreferences['minordefault'] = array(
- 'type' => 'toggle',
- 'section' => 'editing/advancedediting',
- 'label-message' => 'tog-minordefault',
- );
- }
-
- if ( $wgUseExternalEditor ) {
- $defaultPreferences['externaleditor'] = array(
- 'type' => 'toggle',
- 'section' => 'editing/advancedediting',
- 'label-message' => 'tog-externaleditor',
- );
- $defaultPreferences['externaldiff'] = array(
- 'type' => 'toggle',
- 'section' => 'editing/advancedediting',
- 'label-message' => 'tog-externaldiff',
- );
- }
-
- $defaultPreferences['forceeditsummary'] = array(
+ $defaultPreferences['previewonfirst'] = array(
'type' => 'toggle',
- 'section' => 'editing/advancedediting',
- 'label-message' => 'tog-forceeditsummary',
+ 'section' => 'editing/preview',
+ 'label-message' => 'tog-previewonfirst',
+ );
+ $defaultPreferences['previewontop'] = array(
+ 'type' => 'toggle',
+ 'section' => 'editing/preview',
+ 'label-message' => 'tog-previewontop',
);
-
$defaultPreferences['uselivepreview'] = array(
'type' => 'toggle',
- 'section' => 'editing/advancedediting',
+ 'section' => 'editing/preview',
'label-message' => 'tog-uselivepreview',
);
+
}
/**
@@ -956,19 +1001,6 @@ class Preferences {
);
}
- if ( $wgEnableAPI ) {
- # Some random gibberish as a proposed default
- // @todo Fixme: this should use CryptRand but we may not want to read urandom on every view
- $hash = sha1( mt_rand() . microtime( true ) );
-
- $defaultPreferences['watchlisttoken'] = array(
- 'type' => 'text',
- 'section' => 'watchlist/advancedwatchlist',
- 'label-message' => 'prefs-watchlist-token',
- 'help' => $context->msg( 'prefs-help-watchlist-token', $hash )->escaped()
- );
- }
-
$watchTypes = array(
'edit' => 'watchdefault',
'move' => 'watchmoves',
@@ -982,6 +1014,8 @@ class Preferences {
foreach ( $watchTypes as $action => $pref ) {
if ( $user->isAllowed( $action ) ) {
+ // Messages:
+ // tog-watchdefault, tog-watchmoves, tog-watchdeletion, tog-watchcreations
$defaultPreferences[$pref] = array(
'type' => 'toggle',
'section' => 'watchlist/advancedwatchlist',
@@ -989,6 +1023,19 @@ class Preferences {
);
}
}
+
+ if ( $wgEnableAPI ) {
+ $defaultPreferences['watchlisttoken'] = array(
+ 'type' => 'api',
+ );
+ $defaultPreferences['watchlisttoken-info'] = array(
+ 'type' => 'info',
+ 'section' => 'watchlist/tokenwatchlist',
+ 'label-message' => 'prefs-watchlist-token',
+ 'default' => $user->getTokenFromOption( 'watchlisttoken' ),
+ 'help-message' => 'prefs-help-watchlist-token2',
+ );
+ }
}
/**
@@ -1030,8 +1077,9 @@ class Preferences {
$nsOptions = $wgContLang->getFormattedNamespaces();
$nsOptions[0] = $context->msg( 'blanknamespace' )->text();
foreach ( $nsOptions as $ns => $name ) {
- if ( $ns < 0 )
+ if ( $ns < 0 ) {
unset( $nsOptions[$ns] );
+ }
}
$defaultPreferences['searchnamespaces'] = array(
@@ -1044,35 +1092,9 @@ class Preferences {
}
/**
- * @param $user User
- * @param $context IContextSource
- * @param $defaultPreferences Array
+ * Dummy, kept for backwards-compatibility.
*/
static function miscPreferences( $user, IContextSource $context, &$defaultPreferences ) {
- global $wgContLang;
-
- ## Misc #####################################
- $defaultPreferences['diffonly'] = array(
- 'type' => 'toggle',
- 'section' => 'misc/diffs',
- 'label-message' => 'tog-diffonly',
- );
- $defaultPreferences['norollbackdiff'] = array(
- 'type' => 'toggle',
- 'section' => 'misc/diffs',
- 'label-message' => 'tog-norollbackdiff',
- );
-
- // Stuff from Language::getExtraUserToggles()
- $toggles = $wgContLang->getExtraUserToggles();
-
- foreach ( $toggles as $toggle ) {
- $defaultPreferences[$toggle] = array(
- 'type' => 'toggle',
- 'section' => 'personal/i18n',
- 'label-message' => "tog-$toggle",
- );
- }
}
/**
@@ -1110,7 +1132,7 @@ class Preferences {
}
# Create preview link
- $mplink = htmlspecialchars( $mptitle->getLocalURL( "useskin=$skinkey" ) );
+ $mplink = htmlspecialchars( $mptitle->getLocalURL( array( 'useskin' => $skinkey ) ) );
$linkTools[] = "<a target='_blank' href=\"$mplink\">$previewtext</a>";
# Create links to user CSS/JS pages
@@ -1278,15 +1300,17 @@ class Preferences {
}
/**
+ * @param $context IContextSource
* @return array
*/
static function getTimezoneOptions( IContextSource $context ) {
$opt = array();
- global $wgLocalTZoffset, $wgLocaltimezone;
- // Check that $wgLocalTZoffset is the same as $wgLocaltimezone
- if ( $wgLocalTZoffset == date( 'Z' ) / 60 ) {
- $server_tz_msg = $context->msg( 'timezoneuseserverdefault', $wgLocaltimezone )->text();
+ global $wgLocalTZoffset;
+ $timestamp = MWTimestamp::getLocalInstance();
+ // Check that $wgLocalTZoffset is the same as the local time zone offset
+ if ( $wgLocalTZoffset == $timestamp->format( 'Z' ) / 60 ) {
+ $server_tz_msg = $context->msg( 'timezoneuseserverdefault', $timestamp->getTimezone()->getName() )->text();
} else {
$tzstring = sprintf( '%+03d:%02d', floor( $wgLocalTZoffset / 60 ), abs( $wgLocalTZoffset ) % 60 );
$server_tz_msg = $context->msg( 'timezoneuseserverdefault', $tzstring )->text();
@@ -1368,7 +1392,9 @@ class Preferences {
$data[0] = intval( $data[0] );
$data[1] = intval( $data[1] );
$minDiff = abs( $data[0] ) * 60 + $data[1];
- if ( $data[0] < 0 ) $minDiff = - $minDiff;
+ if ( $data[0] < 0 ) {
+ $minDiff = - $minDiff;
+ }
} else {
$minDiff = intval( $data[0] ) * 60;
}
@@ -1382,6 +1408,8 @@ class Preferences {
}
/**
+ * Handle the form submission if everything validated properly
+ *
* @param $formData
* @param $form PreferencesForm
* @param $entryPoint string
@@ -1393,6 +1421,10 @@ class Preferences {
$user = $form->getModifiedUser();
$result = true;
+ if ( !$user->isAllowedAny( 'editmyprivateinfo', 'editmyoptions' ) ) {
+ return Status::newFatal( 'mypreferencesprotected' );
+ }
+
// Filter input
foreach ( array_keys( $formData ) as $name ) {
if ( isset( self::$saveFilters[$name] ) ) {
@@ -1401,40 +1433,37 @@ class Preferences {
}
}
- // Stuff that shouldn't be saved as a preference.
- $saveBlacklist = array(
- 'realname',
- 'emailaddress',
- );
-
// Fortunately, the realname field is MUCH simpler
- if ( !in_array( 'realname', $wgHiddenPrefs ) ) {
+ // (not really "private", but still shouldn't be edited without permission)
+ if ( !in_array( 'realname', $wgHiddenPrefs ) && $user->isAllowed( 'editmyprivateinfo' ) ) {
$realName = $formData['realname'];
$user->setRealName( $realName );
}
- foreach ( $saveBlacklist as $b ) {
- unset( $formData[$b] );
- }
+ if ( $user->isAllowed( 'editmyoptions' ) ) {
+ foreach ( self::$saveBlacklist as $b ) {
+ unset( $formData[$b] );
+ }
- # If users have saved a value for a preference which has subsequently been disabled
- # 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 ) {
- # 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 );
- }
+ # If users have saved a value for a preference which has subsequently been disabled
+ # 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 ) {
+ # 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 );
+ }
- // Keep old preferences from interfering due to back-compat code, etc.
- $user->resetOptions( 'unused', $form->getContext() );
+ // 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 );
- }
+ foreach ( $formData as $key => $value ) {
+ $user->setOption( $key, $value );
+ }
- $user->saveSettings();
+ $user->saveSettings();
+ }
$wgAuth->updateExternalDB( $user );
@@ -1469,7 +1498,8 @@ class Preferences {
/**
* Try to set a user's email address.
* This does *not* try to validate the address.
- * Caller is responsible for checking $wgAuth.
+ * Caller is responsible for checking $wgAuth and 'editmyprivateinfo'
+ * right.
*
* @deprecated in 1.20; use User::setEmailWithConfirmation() instead.
* @param $user User
@@ -1488,7 +1518,7 @@ class Preferences {
}
/**
- * @deprecated in 1.19; will be removed in 1.20.
+ * @deprecated in 1.19
* @param $user User
* @return array
*/
@@ -1558,52 +1588,37 @@ class PreferencesForm extends HTMLForm {
* @return String
*/
function getButtons() {
+ if ( !$this->getModifiedUser()->isAllowedAny( 'editmyprivateinfo', 'editmyoptions' ) ) {
+ return '';
+ }
+
$html = parent::getButtons();
- $t = SpecialPage::getTitleFor( 'Preferences', 'reset' );
+ if ( $this->getModifiedUser()->isAllowed( 'editmyoptions' ) ) {
+ $t = SpecialPage::getTitleFor( 'Preferences', 'reset' );
- $html .= "\n" . Linker::link( $t, $this->msg( 'restoreprefs' )->escaped() );
+ $html .= "\n" . Linker::link( $t, $this->msg( 'restoreprefs' )->escaped() );
- $html = Xml::tags( 'div', array( 'class' => 'mw-prefs-buttons' ), $html );
+ $html = Xml::tags( 'div', array( 'class' => 'mw-prefs-buttons' ), $html );
+ }
return $html;
}
/**
+ * Separate multi-option preferences into multiple preferences, since we
+ * have to store them separately
* @param $data array
* @return array
*/
function filterDataForSubmit( $data ) {
- // 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'] );
+ if ( $field instanceof HTMLNestedFilterable ) {
+ $info = $field->mParams;
$prefix = isset( $info['prefix'] ) ? $info['prefix'] : $fieldname;
-
- foreach ( $options as $opt ) {
- $data["$prefix$opt"] = in_array( $opt, $data[$fieldname] );
+ foreach ( $field->filterDataForSubmit( $data[$fieldname] ) as $key => $value ) {
+ $data["$prefix$key"] = $value;
}
-
- 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 d37d9e80..3c464c58 100644
--- a/includes/PrefixSearch.php
+++ b/includes/PrefixSearch.php
@@ -37,16 +37,16 @@ class PrefixSearch {
*/
public static function titleSearch( $search, $limit, $namespaces = array() ) {
$search = trim( $search );
- if( $search == '' ) {
+ if ( $search == '' ) {
return array(); // Return empty result
}
$namespaces = self::validateNamespaces( $namespaces );
// Find a Title which is not an interwiki and is in NS_MAIN
$title = Title::newFromText( $search );
- if( $title && $title->getInterwiki() == '' ) {
+ if ( $title && $title->getInterwiki() == '' ) {
$ns = array( $title->getNamespace() );
- if( $ns[0] == NS_MAIN ) {
+ if ( $ns[0] == NS_MAIN ) {
$ns = $namespaces; // no explicit prefix, use default namespaces
}
return self::searchBackend(
@@ -55,7 +55,7 @@ class PrefixSearch {
// Is this a namespace prefix?
$title = Title::newFromText( $search . 'Dummy' );
- if( $title && $title->getText() == 'Dummy'
+ if ( $title && $title->getText() == 'Dummy'
&& $title->getNamespace() != NS_MAIN
&& $title->getInterwiki() == '' ) {
return self::searchBackend(
@@ -73,16 +73,16 @@ class PrefixSearch {
* @return Array of strings
*/
protected static function searchBackend( $namespaces, $search, $limit ) {
- if( count( $namespaces ) == 1 ) {
+ if ( count( $namespaces ) == 1 ) {
$ns = $namespaces[0];
- if( $ns == NS_MEDIA ) {
+ if ( $ns == NS_MEDIA ) {
$namespaces = array( NS_FILE );
- } elseif( $ns == NS_SPECIAL ) {
+ } elseif ( $ns == NS_SPECIAL ) {
return self::specialSearch( $search, $limit );
}
}
$srchres = array();
- if( wfRunHooks( 'PrefixSearchBackend', array( $namespaces, $search, $limit, &$srchres ) ) ) {
+ if ( wfRunHooks( 'PrefixSearchBackend', array( $namespaces, $search, $limit, &$srchres ) ) ) {
return self::defaultSearchBackend( $namespaces, $search, $limit );
}
return $srchres;
@@ -106,24 +106,24 @@ class PrefixSearch {
// Unlike SpecialPage itself, we want the canonical forms of both
// canonical and alias title forms...
$keys = array();
- foreach( SpecialPageFactory::getList() as $page => $class ) {
+ foreach ( SpecialPageFactory::getList() as $page => $class ) {
$keys[$wgContLang->caseFold( $page )] = $page;
}
- foreach( $wgContLang->getSpecialPageAliases() as $page => $aliases ) {
- if( !array_key_exists( $page, SpecialPageFactory::getList() ) ) {# bug 20885
+ foreach ( $wgContLang->getSpecialPageAliases() as $page => $aliases ) {
+ if ( !array_key_exists( $page, SpecialPageFactory::getList() ) ) {# bug 20885
continue;
}
- foreach( $aliases as $alias ) {
+ foreach ( $aliases as $alias ) {
$keys[$wgContLang->caseFold( $alias )] = $alias;
}
}
ksort( $keys );
$srchres = array();
- foreach( $keys as $pageKey => $page ) {
- if( $searchKey === '' || strpos( $pageKey, $searchKey ) === 0 ) {
+ foreach ( $keys as $pageKey => $page ) {
+ if ( $searchKey === '' || strpos( $pageKey, $searchKey ) === 0 ) {
wfSuppressWarnings();
// bug 27671: Don't use SpecialPage::getTitleFor() here because it
// localizes its input leading to searches for e.g. Special:All
@@ -133,7 +133,7 @@ class PrefixSearch {
wfRestoreWarnings();
}
- if( count( $srchres ) >= $limit ) {
+ if ( count( $srchres ) >= $limit ) {
break;
}
}
@@ -154,7 +154,7 @@ class PrefixSearch {
*/
protected static function defaultSearchBackend( $namespaces, $search, $limit ) {
$ns = array_shift( $namespaces ); // support only one namespace
- if( in_array( NS_MAIN, $namespaces ) ) {
+ if ( in_array( NS_MAIN, $namespaces ) ) {
$ns = NS_MAIN; // if searching on many always default to main
}
@@ -175,7 +175,7 @@ class PrefixSearch {
$data = $module->getResultData();
// Reformat useful data for future printing by JSON engine
- $srchres = array ();
+ $srchres = array();
foreach ( (array)$data['query']['allpages'] as $pageinfo ) {
// Note: this data will no be printable by the xml engine
// because it does not support lists of unnamed items
@@ -196,14 +196,14 @@ class PrefixSearch {
// We will look at each given namespace against wgContLang namespaces
$validNamespaces = $wgContLang->getNamespaces();
- if( is_array( $namespaces ) && count( $namespaces ) > 0 ) {
+ if ( is_array( $namespaces ) && count( $namespaces ) > 0 ) {
$valid = array();
foreach ( $namespaces as $ns ) {
- if( is_numeric( $ns ) && array_key_exists( $ns, $validNamespaces ) ) {
+ if ( is_numeric( $ns ) && array_key_exists( $ns, $validNamespaces ) ) {
$valid[] = $ns;
}
}
- if( count( $valid ) > 0 ) {
+ if ( count( $valid ) > 0 ) {
return $valid;
}
}
diff --git a/includes/ProtectionForm.php b/includes/ProtectionForm.php
index d7b88400..f10317a9 100644
--- a/includes/ProtectionForm.php
+++ b/includes/ProtectionForm.php
@@ -91,7 +91,7 @@ class ProtectionForm {
$this->mReasonSelection = $wgRequest->getText( 'wpProtectReasonSelection' );
$this->mCascade = $wgRequest->getBool( 'mwProtect-cascade', $this->mCascade );
- foreach( $this->mApplicableTypes as $action ) {
+ foreach ( $this->mApplicableTypes as $action ) {
// @todo FIXME: This form currently requires individual selections,
// but the db allows multiples separated by commas.
@@ -132,15 +132,20 @@ class ProtectionForm {
}
$val = $wgRequest->getVal( "mwProtect-level-$action" );
- if( isset( $val ) && in_array( $val, $wgRestrictionLevels ) ) {
+ if ( isset( $val ) && in_array( $val, $wgRestrictionLevels ) ) {
// Prevent users from setting levels that they cannot later unset
- if( $val == 'sysop' ) {
- // Special case, rewrite sysop to either protect and editprotected
- if( !$wgUser->isAllowedAny( 'protect', 'editprotected' ) )
+ if ( $val == 'sysop' ) {
+ // Special case, rewrite sysop to editprotected
+ if ( !$wgUser->isAllowed( 'editprotected' ) ) {
continue;
- } else {
- if( !$wgUser->isAllowed( $val ) )
+ }
+ } elseif ( $val == 'autoconfirmed' ) {
+ // Special case, rewrite autoconfirmed to editsemiprotected
+ if ( !$wgUser->isAllowed( 'editsemiprotected' ) ) {
continue;
+ }
+ } elseif ( !$wgUser->isAllowed( $val ) ) {
+ continue;
}
$this->mRestrictions[$action] = $val;
}
@@ -188,10 +193,10 @@ class ProtectionForm {
throw new ErrorPageError( 'protect-badnamespace-title', 'protect-badnamespace-text' );
}
- if( $wgRequest->wasPosted() ) {
- if( $this->save() ) {
+ if ( $wgRequest->wasPosted() ) {
+ if ( $this->save() ) {
$q = $this->mArticle->isRedirect() ? 'redirect=no' : '';
- $wgOut->redirect( $this->mTitle->getFullUrl( $q ) );
+ $wgOut->redirect( $this->mTitle->getFullURL( $q ) );
}
} else {
$this->show();
@@ -282,10 +287,11 @@ class ProtectionForm {
$reasonstr = $this->mReason;
}
$expiry = array();
- foreach( $this->mApplicableTypes as $action ) {
+ 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' ) );
return false;
@@ -296,13 +302,7 @@ 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.
- $edit_restriction = isset( $this->mRestrictions['edit'] ) ? $this->mRestrictions['edit'] : '';
$this->mCascade = $wgRequest->getBool( 'mwProtect-cascade' );
- 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 );
@@ -318,23 +318,18 @@ class ProtectionForm {
* you can also return an array of message name and its parameters
*/
$errorMsg = '';
- if( !wfRunHooks( 'ProtectionForm::save', array( $this->mArticle, &$errorMsg ) ) ) {
+ if ( !wfRunHooks( 'ProtectionForm::save', array( $this->mArticle, &$errorMsg, $reasonstr ) ) ) {
if ( $errorMsg == '' ) {
$errorMsg = array( 'hookaborted' );
}
}
- if( $errorMsg != '' ) {
+ if ( $errorMsg != '' ) {
$this->show( $errorMsg );
return false;
}
- if ( $wgUser->isLoggedIn() && $wgRequest->getCheck( 'mwProtectWatch' ) != $wgUser->isWatched( $this->mTitle ) ) {
- if ( $wgRequest->getCheck( 'mwProtectWatch' ) ) {
- WatchAction::doWatch( $this->mTitle, $wgUser );
- } else {
- WatchAction::doUnwatch( $this->mTitle, $wgUser );
- }
- }
+ WatchAction::doWatchOrUnwatch( $wgRequest->getCheck( 'mwProtectWatch' ), $this->mTitle, $wgUser );
+
return true;
}
@@ -356,10 +351,10 @@ class ProtectionForm {
);
$out = '';
- if( !$this->disabled ) {
+ if ( !$this->disabled ) {
$wgOut->addModules( 'mediawiki.legacy.protect' );
$out .= Xml::openElement( 'form', array( 'method' => 'post',
- 'action' => $this->mTitle->getLocalUrl( 'action=protect' ),
+ 'action' => $this->mTitle->getLocalURL( 'action=protect' ),
'id' => 'mw-Protect-Form', 'onsubmit' => 'ProtectionForm.enableUnchainedInputs(true)' ) );
}
@@ -368,10 +363,12 @@ class ProtectionForm {
Xml::openElement( 'table', array( 'id' => 'mwProtectSet' ) ) .
Xml::openElement( 'tbody' );
- foreach( $this->mRestrictions as $action => $selected ) {
- /* Not all languages have V_x <-> N_x relation */
+ // Not all languages have V_x <-> N_x relation
+ foreach ( $this->mRestrictions as $action => $selected ) {
+ // Messages:
+ // restriction-edit, restriction-move, restriction-create, restriction-upload
$msg = wfMessage( 'restriction-' . $action );
- $out .= "<tr><td>".
+ $out .= "<tr><td>" .
Xml::openElement( 'fieldset' ) .
Xml::element( 'legend', null, $msg->exists() ? $msg->text() : $action ) .
Xml::openElement( 'table', array( 'id' => "mw-protect-table-$action" ) ) .
@@ -384,7 +381,7 @@ class ProtectionForm {
'mwProtect-reason', 4 );
$scExpiryOptions = wfMessage( 'protect-expiry-options' )->inContentLanguage()->text();
- $showProtectOptions = ($scExpiryOptions !== '-' && !$this->disabled);
+ $showProtectOptions = $scExpiryOptions !== '-' && !$this->disabled;
$mProtectexpiry = Xml::label(
wfMessage( 'protectexpiry' )->text(),
@@ -412,7 +409,7 @@ class ProtectionForm {
wfMessage( 'protect-othertime-op' )->text(),
"othertime"
) . "\n";
- foreach( explode( ',', $scExpiryOptions ) as $option ) {
+ foreach ( explode( ',', $scExpiryOptions ) as $option ) {
if ( strpos( $option, ":" ) === false ) {
$show = $value = $option;
} else {
@@ -423,7 +420,7 @@ class ProtectionForm {
$expiryFormOptions .= Xml::option( $show, $value, $this->mExpirySelection[$action] === $value ) . "\n";
}
# Add expiry dropdown
- if( $showProtectOptions && !$this->disabled ) {
+ if ( $showProtectOptions && !$this->disabled ) {
$out .= "
<table><tr>
<td class='mw-label'>
@@ -463,7 +460,7 @@ class ProtectionForm {
$out .= Xml::closeElement( 'tbody' ) . Xml::closeElement( 'table' );
// JavaScript will add another row with a value-chaining checkbox
- if( $this->mTitle->exists() ) {
+ if ( $this->mTitle->exists() ) {
$out .= Xml::openElement( 'table', array( 'id' => 'mw-protect-table2' ) ) .
Xml::openElement( 'tbody' );
$out .= '<tr>
@@ -481,7 +478,7 @@ class ProtectionForm {
}
# Add manual and custom reason field/selects as well as submit
- if( !$this->disabled ) {
+ if ( !$this->disabled ) {
$out .= Xml::openElement( 'table', array( 'id' => 'mw-protect-table3' ) ) .
Xml::openElement( 'tbody' );
$out .= "
@@ -506,7 +503,7 @@ class ProtectionForm {
"</td>
</tr>";
# Disallow watching is user is not logged in
- if( $wgUser->isLoggedIn() ) {
+ if ( $wgUser->isLoggedIn() ) {
$out .= "
<tr>
<td></td>
@@ -562,15 +559,22 @@ class ProtectionForm {
global $wgRestrictionLevels, $wgUser;
$levels = array();
- foreach( $wgRestrictionLevels as $key ) {
+ foreach ( $wgRestrictionLevels as $key ) {
//don't let them choose levels above their own (aka so they can still unprotect and edit the page). but only when the form isn't disabled
- if( $key == 'sysop' ) {
- //special case, rewrite sysop to protect and editprotected
- if( !$wgUser->isAllowedAny( 'protect', 'editprotected' ) && !$this->disabled )
+ if ( $key == 'sysop' ) {
+ //special case, rewrite sysop to editprotected
+ if ( !$wgUser->isAllowed( 'editprotected' ) && !$this->disabled ) {
+ continue;
+ }
+ } elseif ( $key == 'autoconfirmed' ) {
+ //special case, rewrite autoconfirmed to editsemiprotected
+ if ( !$wgUser->isAllowed( 'editsemiprotected' ) && !$this->disabled ) {
continue;
+ }
} else {
- if( !$wgUser->isAllowed( $key ) && !$this->disabled )
+ if ( !$wgUser->isAllowed( $key ) && !$this->disabled ) {
continue;
+ }
}
$levels[] = $key;
}
@@ -584,7 +588,7 @@ class ProtectionForm {
) + $this->disabledAttrib;
$out = Xml::openElement( 'select', $attribs );
- foreach( $levels as $key ) {
+ foreach ( $levels as $key ) {
$out .= Xml::option( $this->getOptionLabel( $key ), $key, $key == $selected );
}
$out .= Xml::closeElement( 'select' );
@@ -598,11 +602,12 @@ class ProtectionForm {
* @return String
*/
private function getOptionLabel( $permission ) {
- if( $permission == '' ) {
+ if ( $permission == '' ) {
return wfMessage( 'protect-default' )->text();
} else {
+ // Messages: protect-level-autoconfirmed, protect-level-sysop
$msg = wfMessage( "protect-level-{$permission}" );
- if( $msg->exists() ) {
+ if ( $msg->exists() ) {
return $msg->text();
}
return wfMessage( 'protect-fallback', $permission )->text();
@@ -610,16 +615,9 @@ class ProtectionForm {
}
function buildCleanupScript() {
- global $wgRestrictionLevels, $wgOut;
-
- $cascadeableLevels = array();
- foreach( $wgRestrictionLevels as $key ) {
- if ( User::groupHasPermission( $key, 'protect' )
- || $key == 'protect'
- ) {
- $cascadeableLevels[] = $key;
- }
- }
+ global $wgCascadingRestrictionLevels, $wgOut;
+
+ $cascadeableLevels = $wgCascadingRestrictionLevels;
$options = array(
'tableId' => 'mwProtectSet',
'labelText' => wfMessage( 'protect-unchain-permissions' )->plain(),
diff --git a/includes/ProxyTools.php b/includes/ProxyTools.php
index b54a9a35..bf1c4059 100644
--- a/includes/ProxyTools.php
+++ b/includes/ProxyTools.php
@@ -84,43 +84,3 @@ function wfIsConfiguredProxy( $ip ) {
in_array( $ip, $wgSquidServersNoPurge );
return $trusted;
}
-
-/**
- * Forks processes to scan the originating IP for an open proxy server
- * MemCached can be used to skip IPs that have already been scanned
- */
-function wfProxyCheck() {
- global $wgBlockOpenProxies, $wgProxyPorts, $wgProxyScriptPath;
- global $wgMemc, $wgProxyMemcExpiry, $wgRequest;
- global $wgProxyKey;
-
- if ( !$wgBlockOpenProxies ) {
- return;
- }
-
- $ip = $wgRequest->getIP();
-
- # Get MemCached key
- $mcKey = wfMemcKey( 'proxy', 'ip', $ip );
- $mcValue = $wgMemc->get( $mcKey );
- $skip = (bool)$mcValue;
-
- # Fork the processes
- if ( !$skip ) {
- $title = SpecialPage::getTitleFor( 'Blockme' );
- $iphash = md5( $ip . $wgProxyKey );
- $url = wfExpandUrl( $title->getFullURL( 'ip=' . $iphash ), PROTO_HTTP );
-
- foreach ( $wgProxyPorts as $port ) {
- $params = implode( ' ', array(
- escapeshellarg( $wgProxyScriptPath ),
- escapeshellarg( $ip ),
- escapeshellarg( $port ),
- escapeshellarg( $url )
- ));
- exec( "php $params >" . wfGetNull() . " 2>&1 &" );
- }
- # Set MemCached key
- $wgMemc->set( $mcKey, 1, $wgProxyMemcExpiry );
- }
-}
diff --git a/includes/QueryPage.php b/includes/QueryPage.php
index e1f24fa9..51b5706f 100644
--- a/includes/QueryPage.php
+++ b/includes/QueryPage.php
@@ -35,7 +35,6 @@ $wgQueryPages = array(
array( 'AncientPagesPage', 'Ancientpages' ),
array( 'BrokenRedirectsPage', 'BrokenRedirects' ),
array( 'DeadendPagesPage', 'Deadendpages' ),
- array( 'DisambiguationsPage', 'Disambiguations' ),
array( 'DoubleRedirectsPage', 'DoubleRedirects' ),
array( 'FileDuplicateSearchPage', 'FileDuplicateSearch' ),
array( 'LinkSearchPage', 'LinkSearch' ),
@@ -69,8 +68,9 @@ $wgQueryPages = array(
wfRunHooks( 'wgQueryPages', array( &$wgQueryPages ) );
global $wgDisableCounters;
-if ( !$wgDisableCounters )
+if ( !$wgDisableCounters ) {
$wgQueryPages[] = array( 'PopularPagesPage', 'Popularpages' );
+}
/**
* This is a class for doing query pages; since they're almost all the same,
@@ -89,7 +89,7 @@ abstract class QueryPage extends SpecialPage {
/**
* The offset and limit in use, as passed to the query() function
*
- * @var integer
+ * @var int
*/
var $offset = 0;
var $limit = 0;
@@ -111,7 +111,7 @@ abstract class QueryPage extends SpecialPage {
/**
* A mutator for $this->listoutput;
*
- * @param $bool Boolean
+ * @param bool $bool
*/
function setListoutput( $bool ) {
$this->listoutput = $bool;
@@ -186,7 +186,7 @@ abstract class QueryPage extends SpecialPage {
/**
* Override to sort by increasing values
*
- * @return Boolean
+ * @return bool
*/
function sortDescending() {
return true;
@@ -197,7 +197,7 @@ abstract class QueryPage extends SpecialPage {
* don't let it run in miser mode. $wgDisableQueryPages causes all query
* pages to be declared expensive. Some query pages are always expensive.
*
- * @return Boolean
+ * @return bool
*/
function isExpensive() {
global $wgDisableQueryPages;
@@ -208,7 +208,7 @@ abstract class QueryPage extends SpecialPage {
* Is the output of this query cacheable? Non-cacheable expensive pages
* will be disabled in miser mode and will not have their results written
* to the querycache table.
- * @return Boolean
+ * @return bool
* @since 1.18
*/
public function isCacheable() {
@@ -219,7 +219,7 @@ abstract class QueryPage extends SpecialPage {
* Whether or not the output of the page in question is retrieved from
* the database cache.
*
- * @return Boolean
+ * @return bool
*/
function isCached() {
global $wgMiserMode;
@@ -230,7 +230,7 @@ abstract class QueryPage extends SpecialPage {
/**
* Sometime we don't want to build rss / atom feeds.
*
- * @return Boolean
+ * @return bool
*/
function isSyndicated() {
return true;
@@ -241,19 +241,16 @@ abstract class QueryPage extends SpecialPage {
* skin; you can use it for making links. The result is a single row of
* result data. You should be able to grab SQL results off of it.
* If the function returns false, the line output will be skipped.
- * @param $skin Skin
- * @param $result object Result row
- * @return mixed String or false to skip
- *
- * @param $skin Skin object
- * @param $result Object: database row
+ * @param Skin $skin
+ * @param object $result Result row
+ * @return string|bool String or false to skip
*/
abstract function formatResult( $skin, $result );
/**
* The content returned by this function will be output before any result
*
- * @return String
+ * @return string
*/
function getPageHeader() {
return '';
@@ -264,7 +261,7 @@ abstract class QueryPage extends SpecialPage {
* as an associative array. They will be encoded and added to the paging
* links (prev/next/lengths).
*
- * @return Array
+ * @return array
*/
function linkParameters() {
return array();
@@ -285,8 +282,9 @@ abstract class QueryPage extends SpecialPage {
/**
* Clear the cache and save new results
*
- * @param $limit Integer: limit for SQL statement
- * @param $ignoreErrors Boolean: whether to ignore database errors
+ * @param int|bool $limit Limit for SQL statement
+ * @param bool $ignoreErrors Whether to ignore database errors
+ * @throws DBError|Exception
* @return bool|int
*/
function recache( $limit, $ignoreErrors = true ) {
@@ -351,8 +349,8 @@ abstract class QueryPage extends SpecialPage {
/**
* Run the query and return the result
- * @param $limit mixed Numerical limit or false for no limit
- * @param $offset mixed Numerical offset or false for no offset
+ * @param int|bool $limit Numerical limit or false for no limit
+ * @param int|bool $offset Numerical offset or false for no offset
* @return ResultWrapper
* @since 1.18
*/
@@ -361,23 +359,28 @@ abstract class QueryPage extends SpecialPage {
$dbr = wfGetDB( DB_SLAVE );
$query = $this->getQueryInfo();
$order = $this->getOrderFields();
+
if ( $this->sortDescending() ) {
foreach ( $order as &$field ) {
$field .= ' DESC';
}
}
+
if ( is_array( $query ) ) {
$tables = isset( $query['tables'] ) ? (array)$query['tables'] : array();
$fields = isset( $query['fields'] ) ? (array)$query['fields'] : array();
$conds = isset( $query['conds'] ) ? (array)$query['conds'] : array();
$options = isset( $query['options'] ) ? (array)$query['options'] : array();
$join_conds = isset( $query['join_conds'] ) ? (array)$query['join_conds'] : array();
+
if ( count( $order ) ) {
$options['ORDER BY'] = $order;
}
+
if ( $limit !== false ) {
$options['LIMIT'] = intval( $limit );
}
+
if ( $offset !== false ) {
$options['OFFSET'] = intval( $offset );
}
@@ -392,11 +395,14 @@ abstract class QueryPage extends SpecialPage {
$sql = $dbr->limitResult( $sql, $limit, $offset );
$res = $dbr->query( $sql, $fname );
}
+
return $dbr->resultObject( $res );
}
/**
* Somewhat deprecated, you probably want to be using execute()
+ * @param int|bool $offset
+ * @oaram int|bool $limit
* @return ResultWrapper
*/
function doQuery( $offset = false, $limit = false ) {
@@ -409,14 +415,14 @@ abstract class QueryPage extends SpecialPage {
/**
* Fetch the query results from the query cache
- * @param $limit mixed Numerical limit or false for no limit
- * @param $offset mixed Numerical offset or false for no offset
+ * @param int|bool $limit Numerical limit or false for no limit
+ * @param int|bool $offset Numerical offset or false for no offset
* @return ResultWrapper
* @since 1.18
*/
function fetchFromCache( $limit, $offset = false ) {
$dbr = wfGetDB( DB_SLAVE );
- $options = array ();
+ $options = array();
if ( $limit !== false ) {
$options['LIMIT'] = intval( $limit );
}
@@ -451,6 +457,7 @@ abstract class QueryPage extends SpecialPage {
/**
* This is the actual workhorse. It does everything needed to make a
* real, honest-to-gosh query page.
+ * @para $par
* @return int
*/
function execute( $par ) {
@@ -562,12 +569,12 @@ abstract class QueryPage extends SpecialPage {
* Format and output report results using the given information plus
* OutputPage
*
- * @param $out OutputPage to print to
- * @param $skin Skin: user skin to use
- * @param $dbr Database (read) connection to use
- * @param $res Integer: result pointer
- * @param $num Integer: number of available result rows
- * @param $offset Integer: paging offset
+ * @param OutputPage $out OutputPage to print to
+ * @param Skin $skin User skin to use
+ * @param DatabaseBase $dbr Database (read) connection to use
+ * @param int $res Result pointer
+ * @param int $num Number of available result rows
+ * @param int $offset Paging offset
*/
protected function outputResults( $out, $skin, $dbr, $res, $num, $offset ) {
global $wgContLang;
@@ -580,7 +587,7 @@ abstract class QueryPage extends SpecialPage {
# $res might contain the whole 1,000 rows, so we read up to
# $num [should update this to use a Pager]
- for ( $i = 0; $i < $num && $row = $dbr->fetchObject( $res ); $i++ ) {
+ for ( $i = 0; $i < $num && $row = $res->fetchObject(); $i++ ) {
$line = $this->formatResult( $skin, $row );
if ( $line ) {
$attr = ( isset( $row->usepatrol ) && $row->usepatrol && $row->patrolled == 0 )
@@ -635,25 +642,26 @@ abstract class QueryPage extends SpecialPage {
/**
* Do any necessary preprocessing of the result object.
+ * @param DatabaseBase $db
+ * @param ResultWrapper $res
*/
function preprocessResults( $db, $res ) {}
/**
* Similar to above, but packaging in a syndicated feed instead of a web page
+ * @param string $class
+ * @param int $limit
* @return bool
*/
function doFeed( $class = '', $limit = 50 ) {
- global $wgFeed, $wgFeedClasses;
+ global $wgFeed, $wgFeedClasses, $wgFeedLimit;
if ( !$wgFeed ) {
$this->getOutput()->addWikiMsg( 'feed-unavailable' );
return false;
}
- global $wgFeedLimit;
- if ( $limit > $wgFeedLimit ) {
- $limit = $wgFeedLimit;
- }
+ $limit = min( $limit, $wgFeedLimit );
if ( isset( $wgFeedClasses[$class] ) ) {
$feed = new $wgFeedClasses[$class](
@@ -680,6 +688,7 @@ abstract class QueryPage extends SpecialPage {
/**
* Override for custom handling. If the titles/links are ok, just do
* feedItemDesc()
+ * @param object $row
* @return FeedItem|null
*/
function feedResult( $row ) {
@@ -735,7 +744,6 @@ abstract class QueryPage extends SpecialPage {
* WantedPages, WantedTemplates, etc
*/
abstract class WantedQueryPage extends QueryPage {
-
function isExpensive() {
return true;
}
@@ -746,6 +754,8 @@ abstract class WantedQueryPage extends QueryPage {
/**
* Cache page existence for performance
+ * @param DatabaseBase $db
+ * @param ResultWrapper $res
*/
function preprocessResults( $db, $res ) {
if ( !$res->numRows() ) {
@@ -777,8 +787,8 @@ abstract class WantedQueryPage extends QueryPage {
/**
* Format an individual result
*
- * @param $skin Skin to use for UI elements
- * @param $result Result row
+ * @param Skin $skin Skin to use for UI elements
+ * @param object $result Result row
* @return string
*/
public function formatResult( $skin, $result ) {
@@ -812,8 +822,8 @@ abstract class WantedQueryPage extends QueryPage {
/**
* Make a "what links here" link for a given title
*
- * @param $title Title to make the link for
- * @param $result Object: result row
+ * @param Title $title Title to make the link for
+ * @param object $result Result row
* @return string
*/
private function makeWlhLink( $title, $result ) {
diff --git a/includes/Revision.php b/includes/Revision.php
index 2b34984b..233eac01 100644
--- a/includes/Revision.php
+++ b/includes/Revision.php
@@ -61,6 +61,11 @@ class Revision implements IDBAccessObject {
*/
protected $mContentHandler;
+ /**
+ * @var int
+ */
+ protected $mQueryFlags = 0;
+
// Revision deletion constants
const DELETED_TEXT = 1;
const DELETED_COMMENT = 2;
@@ -106,16 +111,18 @@ class Revision implements IDBAccessObject {
public static function newFromTitle( $title, $id = 0, $flags = 0 ) {
$conds = array(
'page_namespace' => $title->getNamespace(),
- 'page_title' => $title->getDBkey()
+ 'page_title' => $title->getDBkey()
);
if ( $id ) {
// Use the specified ID
$conds['rev_id'] = $id;
+ return self::newFromConds( $conds, (int)$flags );
} else {
// Use a join to get the latest revision
$conds[] = 'rev_id=page_latest';
+ $db = wfGetDB( ( $flags & self::READ_LATEST ) ? DB_MASTER : DB_SLAVE );
+ return self::loadFromConds( $db, $conds, $flags );
}
- return self::newFromConds( $conds, (int)$flags );
}
/**
@@ -228,8 +235,8 @@ class Revision implements IDBAccessObject {
* @return Revision or null
*/
public static function loadFromPageId( $db, $pageid, $id = 0 ) {
- $conds = array( 'rev_page' => intval( $pageid ), 'page_id' => intval( $pageid ) );
- if( $id ) {
+ $conds = array( 'rev_page' => intval( $pageid ), 'page_id' => intval( $pageid ) );
+ if ( $id ) {
$conds['rev_id'] = intval( $id );
} else {
$conds[] = 'rev_id=page_latest';
@@ -248,7 +255,7 @@ class Revision implements IDBAccessObject {
* @return Revision or null
*/
public static function loadFromTitle( $db, $title, $id = 0 ) {
- if( $id ) {
+ if ( $id ) {
$matchId = intval( $id );
} else {
$matchId = 'page_latest';
@@ -257,7 +264,7 @@ class Revision implements IDBAccessObject {
array(
"rev_id=$matchId",
'page_namespace' => $title->getNamespace(),
- 'page_title' => $title->getDBkey()
+ 'page_title' => $title->getDBkey()
)
);
}
@@ -275,9 +282,9 @@ class Revision implements IDBAccessObject {
public static function loadFromTimestamp( $db, $title, $timestamp ) {
return self::loadFromConds( $db,
array(
- 'rev_timestamp' => $db->timestamp( $timestamp ),
+ 'rev_timestamp' => $db->timestamp( $timestamp ),
'page_namespace' => $title->getNamespace(),
- 'page_title' => $title->getDBkey()
+ 'page_title' => $title->getDBkey()
)
);
}
@@ -298,6 +305,9 @@ class Revision implements IDBAccessObject {
$rev = self::loadFromConds( $dbw, $conditions, $flags );
}
}
+ if ( $rev ) {
+ $rev->mQueryFlags = $flags;
+ }
return $rev;
}
@@ -312,9 +322,9 @@ class Revision implements IDBAccessObject {
*/
private static function loadFromConds( $db, $conditions, $flags = 0 ) {
$res = self::fetchFromConds( $db, $conditions, $flags );
- if( $res ) {
+ if ( $res ) {
$row = $res->fetchObject();
- if( $row ) {
+ if ( $row ) {
$ret = new Revision( $row );
return $ret;
}
@@ -337,7 +347,7 @@ class Revision implements IDBAccessObject {
array(
'rev_id=page_latest',
'page_namespace' => $title->getNamespace(),
- 'page_title' => $title->getDBkey()
+ 'page_title' => $title->getDBkey()
)
);
}
@@ -489,7 +499,7 @@ class Revision implements IDBAccessObject {
* @access private
*/
function __construct( $row ) {
- if( is_object( $row ) ) {
+ if ( is_object( $row ) ) {
$this->mId = intval( $row->rev_id );
$this->mPage = intval( $row->rev_page );
$this->mTextId = intval( $row->rev_text_id );
@@ -517,7 +527,7 @@ class Revision implements IDBAccessObject {
$this->mSha1 = $row->rev_sha1;
}
- if( isset( $row->page_latest ) ) {
+ if ( isset( $row->page_latest ) ) {
$this->mCurrent = ( $row->rev_id == $row->page_latest );
$this->mTitle = Title::newFromRow( $row );
} else {
@@ -525,13 +535,13 @@ class Revision implements IDBAccessObject {
$this->mTitle = null;
}
- if( !isset( $row->rev_content_model ) || is_null( $row->rev_content_model ) ) {
+ 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 ) ) {
+ 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 );
@@ -539,7 +549,7 @@ class Revision implements IDBAccessObject {
// Lazy extraction...
$this->mText = null;
- if( isset( $row->old_text ) ) {
+ if ( isset( $row->old_text ) ) {
$this->mTextRow = $row;
} else {
// 'text' table row entry will be lazy-loaded
@@ -554,13 +564,13 @@ class Revision implements IDBAccessObject {
$this->mUserText = $row->user_name; // logged-in user
}
$this->mOrigUserText = $row->rev_user_text;
- } elseif( is_array( $row ) ) {
+ } elseif ( is_array( $row ) ) {
// 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]
+ // @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" );
@@ -714,10 +724,10 @@ class Revision implements IDBAccessObject {
* @return Title|null
*/
public function getTitle() {
- if( isset( $this->mTitle ) ) {
+ if ( isset( $this->mTitle ) ) {
return $this->mTitle;
}
- if( !is_null( $this->mId ) ) { //rev_id is defined as NOT NULL, but this revision may not yet have been inserted.
+ 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' ),
@@ -769,9 +779,9 @@ class Revision implements IDBAccessObject {
* @return Integer
*/
public function getUser( $audience = self::FOR_PUBLIC, User $user = null ) {
- if( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_USER ) ) {
+ if ( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_USER ) ) {
return 0;
- } elseif( $audience == self::FOR_THIS_USER && !$this->userCan( self::DELETED_USER, $user ) ) {
+ } elseif ( $audience == self::FOR_THIS_USER && !$this->userCan( self::DELETED_USER, $user ) ) {
return 0;
} else {
return $this->mUser;
@@ -801,9 +811,9 @@ class Revision implements IDBAccessObject {
* @return string
*/
public function getUserText( $audience = self::FOR_PUBLIC, User $user = null ) {
- if( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_USER ) ) {
+ if ( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_USER ) ) {
return '';
- } elseif( $audience == self::FOR_THIS_USER && !$this->userCan( self::DELETED_USER, $user ) ) {
+ } elseif ( $audience == self::FOR_THIS_USER && !$this->userCan( self::DELETED_USER, $user ) ) {
return '';
} else {
return $this->getRawUserText();
@@ -841,9 +851,9 @@ class Revision implements IDBAccessObject {
* @return String
*/
function getComment( $audience = self::FOR_PUBLIC, User $user = null ) {
- if( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_COMMENT ) ) {
+ if ( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_COMMENT ) ) {
return '';
- } elseif( $audience == self::FOR_THIS_USER && !$this->userCan( self::DELETED_COMMENT, $user ) ) {
+ } elseif ( $audience == self::FOR_THIS_USER && !$this->userCan( self::DELETED_COMMENT, $user ) ) {
return '';
} else {
return $this->mComment;
@@ -867,24 +877,37 @@ class Revision implements IDBAccessObject {
}
/**
- * @return Integer rcid of the unpatrolled row, zero if there isn't one
+ * @return integer rcid of the unpatrolled row, zero if there isn't one
*/
public function isUnpatrolled() {
- if( $this->mUnpatrolled !== null ) {
+ if ( $this->mUnpatrolled !== null ) {
return $this->mUnpatrolled;
}
+ $rc = $this->getRecentChange();
+ if ( $rc && $rc->getAttribute( 'rc_patrolled' ) == 0 ) {
+ $this->mUnpatrolled = $rc->getAttribute( 'rc_id' );
+ } else {
+ $this->mUnpatrolled = 0;
+ }
+ return $this->mUnpatrolled;
+ }
+
+ /**
+ * Get the RC object belonging to the current revision, if there's one
+ *
+ * @since 1.22
+ * @return RecentChange|null
+ */
+ public function getRecentChange() {
$dbr = wfGetDB( DB_SLAVE );
- $this->mUnpatrolled = $dbr->selectField( 'recentchanges',
- 'rc_id',
- array( // Add redundant user,timestamp condition so we can use the existing index
- 'rc_user_text' => $this->getRawUserText(),
- 'rc_timestamp' => $dbr->timestamp( $this->getTimestamp() ),
- 'rc_this_oldid' => $this->getId(),
- 'rc_patrolled' => 0
+ return RecentChange::newFromConds(
+ array(
+ 'rc_user_text' => $this->getRawUserText(),
+ 'rc_timestamp' => $dbr->timestamp( $this->getTimestamp() ),
+ 'rc_this_oldid' => $this->getId()
),
__METHOD__
);
- return (int)$this->mUnpatrolled;
}
/**
@@ -918,7 +941,7 @@ class Revision implements IDBAccessObject {
* to the $audience parameter
*
* @deprecated in 1.21, use getContent() instead
- * @todo: replace usage in core
+ * @todo Replace usage in core
* @return String
*/
public function getText( $audience = self::FOR_PUBLIC, User $user = null ) {
@@ -943,9 +966,9 @@ class Revision implements IDBAccessObject {
* @return Content|null
*/
public function getContent( $audience = self::FOR_PUBLIC, User $user = null ) {
- if( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_TEXT ) ) {
+ if ( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_TEXT ) ) {
return null;
- } elseif( $audience == self::FOR_THIS_USER && !$this->userCan( self::DELETED_TEXT, $user ) ) {
+ } elseif ( $audience == self::FOR_THIS_USER && !$this->userCan( self::DELETED_TEXT, $user ) ) {
return null;
} else {
return $this->getContentInternal();
@@ -983,6 +1006,10 @@ class Revision implements IDBAccessObject {
* @return String
*/
public function getSerializedData() {
+ if ( is_null( $this->mText ) ) {
+ $this->mText = $this->loadText();
+ }
+
return $this->mText;
}
@@ -996,9 +1023,9 @@ class Revision implements IDBAccessObject {
* @return Content|null the Revision's content, or null on failure.
*/
protected function getContentInternal() {
- if( is_null( $this->mContent ) ) {
+ if ( is_null( $this->mContent ) ) {
// Revision is immutable. Load on demand:
- if( is_null( $this->mText ) ) {
+ if ( is_null( $this->mText ) ) {
$this->mText = $this->loadText();
}
@@ -1097,9 +1124,9 @@ class Revision implements IDBAccessObject {
* @return Revision|null
*/
public function getPrevious() {
- if( $this->getTitle() ) {
+ if ( $this->getTitle() ) {
$prev = $this->getTitle()->getPreviousRevisionID( $this->getId() );
- if( $prev ) {
+ if ( $prev ) {
return self::newFromTitle( $this->getTitle(), $prev );
}
}
@@ -1112,7 +1139,7 @@ class Revision implements IDBAccessObject {
* @return Revision or null
*/
public function getNext() {
- if( $this->getTitle() ) {
+ if ( $this->getTitle() ) {
$next = $this->getTitle()->getNextRevisionID( $this->getId() );
if ( $next ) {
return self::newFromTitle( $this->getTitle(), $next );
@@ -1129,11 +1156,11 @@ class Revision implements IDBAccessObject {
* @return Integer
*/
private function getPreviousRevisionId( $db ) {
- if( is_null( $this->mPage ) ) {
+ if ( is_null( $this->mPage ) ) {
return 0;
}
# Use page_latest if ID is not given
- if( !$this->mId ) {
+ if ( !$this->mId ) {
$prevId = $db->selectField( 'page', 'page_latest',
array( 'page_id' => $this->mPage ),
__METHOD__ );
@@ -1166,13 +1193,13 @@ class Revision implements IDBAccessObject {
$textField = $prefix . 'text';
$flagsField = $prefix . 'flags';
- if( isset( $row->$flagsField ) ) {
+ if ( isset( $row->$flagsField ) ) {
$flags = explode( ',', $row->$flagsField );
} else {
$flags = array();
}
- if( isset( $row->$textField ) ) {
+ if ( isset( $row->$textField ) ) {
$text = $row->$textField;
} else {
wfProfileOut( __METHOD__ );
@@ -1183,7 +1210,7 @@ class Revision implements IDBAccessObject {
if ( in_array( 'external', $flags ) ) {
$url = $text;
$parts = explode( '://', $url, 2 );
- if( count( $parts ) == 1 || $parts[1] == '' ) {
+ if ( count( $parts ) == 1 || $parts[1] == '' ) {
wfProfileOut( __METHOD__ );
return false;
}
@@ -1192,35 +1219,7 @@ class Revision implements IDBAccessObject {
// If the text was fetched without an error, convert it
if ( $text !== false ) {
- if( in_array( 'gzip', $flags ) ) {
- # Deal with optional compression of archived pages.
- # This can be done periodically via maintenance/compressOld.php, and
- # as pages are saved if $wgCompressRevisions is set.
- $text = gzinflate( $text );
- }
-
- if( in_array( 'object', $flags ) ) {
- # Generic compressed storage
- $obj = unserialize( $text );
- if ( !is_object( $obj ) ) {
- // Invalid object
- wfProfileOut( __METHOD__ );
- return false;
- }
- $text = $obj->getText();
- }
-
- global $wgLegacyEncoding;
- if( $text !== false && $wgLegacyEncoding
- && !in_array( 'utf-8', $flags ) && !in_array( 'utf8', $flags ) )
- {
- # Old revisions kept around in a legacy encoding?
- # Upconvert on demand.
- # ("utf8" checked for compatibility with some broken
- # conversion scripts 2008-12-30)
- global $wgContLang;
- $text = $wgContLang->iconv( $wgLegacyEncoding, 'UTF-8', $text );
- }
+ $text = self::decompressRevisionText( $text, $flags );
}
wfProfileOut( __METHOD__ );
return $text;
@@ -1244,8 +1243,8 @@ class Revision implements IDBAccessObject {
# on load if $wgLegacyCharset is set in the future.
$flags[] = 'utf-8';
- if( $wgCompressRevisions ) {
- if( function_exists( 'gzdeflate' ) ) {
+ if ( $wgCompressRevisions ) {
+ if ( function_exists( 'gzdeflate' ) ) {
$text = gzdeflate( $text );
$flags[] = 'gzip';
} else {
@@ -1256,6 +1255,46 @@ class Revision implements IDBAccessObject {
}
/**
+ * Re-converts revision text according to it's flags.
+ *
+ * @param $text Mixed: reference to a text
+ * @param $flags array: compression flags
+ * @return String|bool decompressed text, or false on failure
+ */
+ public static function decompressRevisionText( $text, $flags ) {
+ if ( in_array( 'gzip', $flags ) ) {
+ # Deal with optional compression of archived pages.
+ # This can be done periodically via maintenance/compressOld.php, and
+ # as pages are saved if $wgCompressRevisions is set.
+ $text = gzinflate( $text );
+ }
+
+ if ( in_array( 'object', $flags ) ) {
+ # Generic compressed storage
+ $obj = unserialize( $text );
+ if ( !is_object( $obj ) ) {
+ // Invalid object
+ return false;
+ }
+ $text = $obj->getText();
+ }
+
+ global $wgLegacyEncoding;
+ if ( $text !== false && $wgLegacyEncoding
+ && !in_array( 'utf-8', $flags ) && !in_array( 'utf8', $flags ) )
+ {
+ # Old revisions kept around in a legacy encoding?
+ # Upconvert on demand.
+ # ("utf8" checked for compatibility with some broken
+ # conversion scripts 2008-12-30)
+ global $wgContLang;
+ $text = $wgContLang->iconv( $wgLegacyEncoding, 'UTF-8', $text );
+ }
+
+ return $text;
+ }
+
+ /**
* Insert a new revision into the database, returning the new revision ID
* number on success and dies horribly on failure.
*
@@ -1274,32 +1313,35 @@ class Revision implements IDBAccessObject {
$flags = self::compressRevisionText( $data );
# Write to external storage if required
- if( $wgDefaultExternalStore ) {
+ if ( $wgDefaultExternalStore ) {
// Store and get the URL
$data = ExternalStore::insertToDefault( $data );
- if( !$data ) {
+ if ( !$data ) {
+ wfProfileOut( __METHOD__ );
throw new MWException( "Unable to store text to external storage" );
}
- if( $flags ) {
+ if ( $flags ) {
$flags .= ',';
}
$flags .= 'external';
}
# Record the text (or external storage URL) to the text table
- if( !isset( $this->mTextId ) ) {
+ if ( !isset( $this->mTextId ) ) {
$old_id = $dbw->nextSequenceValue( 'text_old_id_seq' );
$dbw->insert( 'text',
array(
- 'old_id' => $old_id,
- 'old_text' => $data,
+ 'old_id' => $old_id,
+ 'old_text' => $data,
'old_flags' => $flags,
), __METHOD__
);
$this->mTextId = $dbw->insertId();
}
- if ( $this->mComment === null ) $this->mComment = "";
+ if ( $this->mComment === null ) {
+ $this->mComment = "";
+ }
# Record the edit in revisions
$rev_id = isset( $this->mId )
@@ -1334,14 +1376,15 @@ class Revision implements IDBAccessObject {
$title = $this->getTitle();
if ( $title === null ) {
+ wfProfileOut( __METHOD__ );
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;
+ $row['rev_content_model'] = ( $model === $defaultModel ) ? null : $model;
+ $row['rev_content_format'] = ( $format === $defaultFormat ) ? null : $format;
}
$dbw->insert( 'revision', $row, __METHOD__ );
@@ -1422,9 +1465,9 @@ class Revision implements IDBAccessObject {
global $wgRevisionCacheExpiry, $wgMemc;
$textId = $this->getTextId();
$key = wfMemcKey( 'revisiontext', 'textid', $textId );
- if( $wgRevisionCacheExpiry ) {
+ if ( $wgRevisionCacheExpiry ) {
$text = $wgMemc->get( $key );
- if( is_string( $text ) ) {
+ if ( is_string( $text ) ) {
wfDebug( __METHOD__ . ": got id $textId from cache\n" );
wfProfileOut( __METHOD__ );
return $text;
@@ -1439,28 +1482,38 @@ class Revision implements IDBAccessObject {
$row = null;
}
- if( !$row ) {
+ if ( !$row ) {
// Text data is immutable; check slaves first.
$dbr = wfGetDB( DB_SLAVE );
$row = $dbr->selectRow( 'text',
array( 'old_text', 'old_flags' ),
- array( 'old_id' => $this->getTextId() ),
+ array( 'old_id' => $textId ),
__METHOD__ );
}
- if( !$row && wfGetLB()->getServerCount() > 1 ) {
- // Possible slave lag!
+ // Fallback to the master in case of slave lag. Also use FOR UPDATE if it was
+ // used to fetch this revision to avoid missing the row due to REPEATABLE-READ.
+ $forUpdate = ( $this->mQueryFlags & self::READ_LOCKING == self::READ_LOCKING );
+ if ( !$row && ( $forUpdate || wfGetLB()->getServerCount() > 1 ) ) {
$dbw = wfGetDB( DB_MASTER );
$row = $dbw->selectRow( 'text',
array( 'old_text', 'old_flags' ),
- array( 'old_id' => $this->getTextId() ),
- __METHOD__ );
+ array( 'old_id' => $textId ),
+ __METHOD__,
+ $forUpdate ? array( 'FOR UPDATE' ) : array() );
+ }
+
+ if ( !$row ) {
+ wfDebugLog( 'Revision', "No text row with ID '$textId' (revision {$this->getId()})." );
}
$text = self::getRevisionText( $row );
+ if ( $row && $text === false ) {
+ wfDebugLog( 'Revision', "No blob for text row '$textId' (revision {$this->getId()})." );
+ }
# No negative caching -- negative hits on text rows may be due to corrupted slave servers
- if( $wgRevisionCacheExpiry && $text !== false ) {
+ if ( $wgRevisionCacheExpiry && $text !== false ) {
$wgMemc->set( $key, $text, $wgRevisionCacheExpiry );
}
@@ -1505,7 +1558,7 @@ class Revision implements IDBAccessObject {
),
__METHOD__ );
- if( $current ) {
+ if ( $current ) {
$row = array(
'page' => $pageId,
'comment' => $summary,
@@ -1517,8 +1570,8 @@ class Revision implements IDBAccessObject {
);
if ( $wgContentHandlerUseDB ) {
- $row[ 'content_model' ] = $current->rev_content_model;
- $row[ 'content_format' ] = $current->rev_content_format;
+ $row['content_model'] = $current->rev_content_model;
+ $row['content_format'] = $current->rev_content_format;
}
$revision = new Revision( $row );
@@ -1558,7 +1611,7 @@ class Revision implements IDBAccessObject {
* @return Boolean
*/
public static function userCanBitfield( $bitfield, $field, User $user = null ) {
- if( $bitfield & $field ) { // aspect is deleted
+ if ( $bitfield & $field ) { // aspect is deleted
if ( $bitfield & self::DELETED_RESTRICTED ) {
$permission = 'suppressrevision';
} elseif ( $field & self::DELETED_TEXT ) {
@@ -1611,7 +1664,7 @@ class Revision implements IDBAccessObject {
static function countByPageId( $db, $id ) {
$row = $db->selectRow( 'revision', array( 'revCount' => 'COUNT(*)' ),
array( 'rev_page' => $id ), __METHOD__ );
- if( $row ) {
+ if ( $row ) {
return $row->revCount;
}
return 0;
@@ -1626,7 +1679,7 @@ class Revision implements IDBAccessObject {
*/
static function countByTitle( $db, $title ) {
$id = $title->getArticleID();
- if( $id ) {
+ if ( $id ) {
return self::countByPageId( $db, $id );
}
return 0;
@@ -1648,7 +1701,9 @@ class Revision implements IDBAccessObject {
* @return bool True if the given user was the only one to edit since the given timestamp
*/
public static function userWasLastToEdit( $db, $pageId, $userId, $since ) {
- if ( !$userId ) return false;
+ if ( !$userId ) {
+ return false;
+ }
if ( is_int( $db ) ) {
$db = wfGetDB( $db );
diff --git a/includes/RevisionList.php b/includes/RevisionList.php
index d87c540f..1b865bb0 100644
--- a/includes/RevisionList.php
+++ b/includes/RevisionList.php
@@ -105,7 +105,7 @@ abstract class RevisionListBase extends ContextSource {
* @return int
*/
public function length() {
- if( !$this->res ) {
+ if ( !$this->res ) {
return 0;
} else {
return $this->res->numRows();
@@ -352,8 +352,7 @@ class RevisionItem extends RevisionItemBase {
if ( $this->isDeleted() && !$this->canViewContent() ) {
return $this->context->msg( 'diff' )->escaped();
} else {
- return
- Linker::link(
+ return Linker::link(
$this->list->title,
$this->context->msg( 'diff' )->escaped(),
array(),
diff --git a/includes/Sanitizer.php b/includes/Sanitizer.php
index 849e4d66..499d8218 100644
--- a/includes/Sanitizer.php
+++ b/includes/Sanitizer.php
@@ -1,6 +1,6 @@
<?php
/**
- * XHTML sanitizer for %MediaWiki.
+ * HTML sanitizer for %MediaWiki.
*
* Copyright © 2002-2005 Brion Vibber <brion@pobox.com> et al
* http://www.mediawiki.org/
@@ -25,7 +25,7 @@
*/
/**
- * XHTML sanitizer for MediaWiki
+ * HTML sanitizer for MediaWiki
* @ingroup Parser
*/
class Sanitizer {
@@ -54,9 +54,8 @@ class Sanitizer {
* List of all named character entities defined in HTML 4.01
* http://www.w3.org/TR/html4/sgml/entities.html
* As well as &apos; which is only defined starting in XHTML1.
- * @private
*/
- static $htmlEntities = array(
+ private static $htmlEntities = array(
'Aacute' => 193,
'aacute' => 225,
'Acirc' => 194,
@@ -315,7 +314,7 @@ class Sanitizer {
/**
* Character entity aliases accepted by MediaWiki
*/
- static $htmlEntityAliases = array(
+ private static $htmlEntityAliases = array(
'רלמ' => 'rlm',
'رلم' => 'rlm',
);
@@ -323,7 +322,7 @@ class Sanitizer {
/**
* Lazy-initialised attributes regex, see getAttribsRegex()
*/
- static $attribsRegex;
+ private static $attribsRegex;
/**
* Regular expression to match HTML/XML attribute pairs within a tag.
@@ -357,14 +356,17 @@ class Sanitizer {
* removes HTML comments
* @private
* @param $text String
- * @param $processCallback Callback to do any variable or parameter replacements in HTML attribute values
+ * @param $processCallback Callback to do any variable or parameter
+ * replacements in HTML attribute values
* @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, $wgHtml5, $wgAllowMicrodataAttributes, $wgAllowImageTag;
+ static function removeHTMLtags( $text, $processCallback = null,
+ $args = array(), $extratags = array(), $removetags = array()
+ ) {
+ global $wgUseTidy, $wgAllowMicrodataAttributes, $wgAllowImageTag;
static $htmlpairsStatic, $htmlsingle, $htmlsingleonly, $htmlnest, $tabletags,
$htmllist, $listtags, $htmlsingleallowed, $htmlelementsStatic, $staticInitialised;
@@ -373,7 +375,7 @@ class Sanitizer {
// 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' ) );
+ $globalContext = implode( '-', compact( 'wgAllowMicrodataAttributes', 'wgAllowImageTag' ) );
if ( !$staticInitialised || $staticInitialised != $globalContext ) {
$htmlpairsStatic = array( # Tags that must be closed
@@ -382,30 +384,28 @@ class Sanitizer {
'strike', 'strong', 'tt', 'var', 'div', 'center',
'blockquote', 'ol', 'ul', 'dl', 'table', 'caption', 'pre',
'ruby', 'rt', 'rb', 'rp', 'p', 'span', 'abbr', 'dfn',
- 'kbd', 'samp'
+ 'kbd', 'samp', 'data', 'time', 'mark'
);
- if ( $wgHtml5 ) {
- $htmlpairsStatic = array_merge( $htmlpairsStatic, array( 'data', 'time', 'mark' ) );
- }
$htmlsingle = array(
- 'br', 'hr', 'li', 'dt', 'dd'
+ 'br', 'wbr', 'hr', 'li', 'dt', 'dd'
);
$htmlsingleonly = array( # Elements that cannot have close tags
- 'br', 'hr'
+ 'br', 'wbr', 'hr'
);
- if ( $wgHtml5 && $wgAllowMicrodataAttributes ) {
+ if ( $wgAllowMicrodataAttributes ) {
$htmlsingle[] = $htmlsingleonly[] = 'meta';
$htmlsingle[] = $htmlsingleonly[] = 'link';
}
$htmlnest = array( # Tags that can be nested--??
'table', 'tr', 'td', 'th', 'div', 'blockquote', 'ol', 'ul',
- 'li', 'dl', 'dt', 'dd', 'font', 'big', 'small', 'sub', 'sup', 'span'
+ 'li', 'dl', 'dt', 'dd', 'font', 'big', 'small', 'sub', 'sup', 'span',
+ 'var', 'kbd', 'samp', 'em', 'strong', 'q', 'ruby', 'bdo'
);
$tabletags = array( # Can only appear inside table, we will close them
'td', 'th', 'tr',
);
$htmllist = array( # Tags used by list
- 'ul','ol',
+ 'ul', 'ol',
);
$listtags = array( # Tags that can appear in a list
'li',
@@ -446,7 +446,7 @@ class Sanitizer {
# $params: String between element name and >
# $brace: Ending '>' or '/>'
# $rest: Everything until the next element of $bits
- if( preg_match( '!^(/?)(\\w+)([^>]*?)(/{0,1}>)([^<]*)$!', $x, $regs ) ) {
+ if ( preg_match( '!^(/?)([^\\s/>]+)([^>]*?)(/{0,1}>)([^<]*)$!', $x, $regs ) ) {
list( /* $qbar */, $slash, $t, $params, $brace, $rest ) = $regs;
} else {
$slash = $t = $params = $brace = $rest = null;
@@ -507,7 +507,7 @@ class Sanitizer {
!in_array( 'table', $tagstack ) ) {
$badtag = true;
} elseif ( in_array( $t, $tagstack ) &&
- !isset( $htmlnest [$t ] ) ) {
+ !isset( $htmlnest[$t] ) ) {
$badtag = true;
# Is it a self closed htmlpair ? (bug 5487)
} elseif ( $brace == '/>' &&
@@ -537,7 +537,7 @@ class Sanitizer {
# Replace any variables or template parameters with
# plaintext results.
- if( is_callable( $processCallback ) ) {
+ if ( is_callable( $processCallback ) ) {
call_user_func_array( $processCallback, array( &$params, $args ) );
}
@@ -555,12 +555,14 @@ class Sanitizer {
continue;
}
}
- $text .= '&lt;' . str_replace( '>', '&gt;', $x);
+ $text .= '&lt;' . str_replace( '>', '&gt;', $x );
}
# Close off any remaining tags
- while ( is_array( $tagstack ) && ($t = array_pop( $tagstack )) ) {
+ while ( is_array( $tagstack ) && ( $t = array_pop( $tagstack ) ) ) {
$text .= "</$t>\n";
- if ( $t == 'table' ) { $tagstack = array_pop( $tablestack ); }
+ if ( $t == 'table' ) {
+ $tagstack = array_pop( $tablestack );
+ }
}
} else {
# this might be possible using tidy itself
@@ -570,7 +572,7 @@ class Sanitizer {
@list( /* $qbar */, $slash, $t, $params, $brace, $rest ) = $regs;
$badtag = false;
if ( isset( $htmlelements[$t = strtolower( $t )] ) ) {
- if( is_callable( $processCallback ) ) {
+ if ( is_callable( $processCallback ) ) {
call_user_func_array( $processCallback, array( &$params, $args ) );
}
@@ -585,7 +587,7 @@ class Sanitizer {
continue;
}
}
- $text .= '&lt;' . str_replace( '>', '&gt;', $x);
+ $text .= '&lt;' . str_replace( '>', '&gt;', $x );
}
}
wfProfileOut( __METHOD__ );
@@ -604,7 +606,7 @@ class Sanitizer {
*/
static function removeHTMLcomments( $text ) {
wfProfileIn( __METHOD__ );
- while ( ($start = strpos( $text, '<!--' ) ) !== false ) {
+ while ( ( $start = strpos( $text, '<!--' ) ) !== false ) {
$end = strpos( $text, '-->', $start + 4 );
if ( $end === false ) {
# Unterminated comment; bail out
@@ -621,9 +623,11 @@ class Sanitizer {
$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"
+ && 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 );
@@ -706,13 +710,13 @@ class Sanitizer {
* @todo Check for unique id attribute :P
*/
static function validateAttributes( $attribs, $whitelist ) {
- global $wgAllowRdfaAttributes, $wgAllowMicrodataAttributes, $wgHtml5;
+ global $wgAllowRdfaAttributes, $wgAllowMicrodataAttributes;
$whitelist = array_flip( $whitelist );
$hrefExp = '/^(' . wfUrlProtocols() . ')[^\s]+$/';
$out = array();
- foreach( $attribs as $attribute => $value ) {
+ foreach ( $attribs as $attribute => $value ) {
#allow XML namespace declaration if RDFa is enabled
if ( $wgAllowRdfaAttributes && preg_match( self::XMLNS_ATTRIBUTE_PATTERN, $attribute ) ) {
if ( !preg_match( self::EVIL_URI_PATTERN, $value ) ) {
@@ -722,14 +726,14 @@ class Sanitizer {
continue;
}
- # Allow any attribute beginning with "data-", if in HTML5 mode
- if ( !($wgHtml5 && preg_match( '/^data-/i', $attribute )) && !isset( $whitelist[$attribute] ) ) {
+ # Allow any attribute beginning with "data-"
+ if ( !preg_match( '/^data-/i', $attribute ) && !isset( $whitelist[$attribute] ) ) {
continue;
}
# Strip javascript "expression" from stylesheets.
# http://msdn.microsoft.com/workshop/author/dhtml/overview/recalc.asp
- if( $attribute == 'style' ) {
+ if ( $attribute == 'style' ) {
$value = Sanitizer::checkCss( $value );
}
@@ -739,7 +743,7 @@ class Sanitizer {
# WAI-ARIA
# http://www.w3.org/TR/wai-aria/
- # http://www.whatwg.org/specs/web-apps/current-work/multipage/elements.html#wai-aria
+ # http://www.whatwg.org/html/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.
@@ -747,13 +751,18 @@ class Sanitizer {
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
- $attribute === 'datatype' || $attribute === 'typeof' || #RDFa
- $attribute === 'itemid' || $attribute === 'itemprop' || $attribute === 'itemref' || #HTML5 microdata
- $attribute === 'itemscope' || $attribute === 'itemtype' ) { #HTML5 microdata
-
+ // RDFa and microdata properties allow URLs, URIs and/or CURIs.
+ // Check them for sanity.
+ if ( $attribute === 'rel' || $attribute === 'rev'
+ # RDFa
+ || $attribute === 'about' || $attribute === 'property'
+ || $attribute === 'resource' || $attribute === 'datatype'
+ || $attribute === 'typeof'
+ # HTML5 microdata
+ || $attribute === 'itemid' || $attribute === 'itemprop'
+ || $attribute === 'itemref' || $attribute === 'itemscope'
+ || $attribute === 'itemtype'
+ ) {
//Paranoia. Allow "simple" values but suppress javascript
if ( preg_match( self::EVIL_URI_PATTERN, $value ) ) {
continue;
@@ -765,7 +774,7 @@ class Sanitizer {
if ( $attribute === 'href' || $attribute === 'src' ) {
if ( !preg_match( $hrefExp, $value ) ) {
continue; //drop any href or src attributes not using an allowed protocol.
- //NOTE: this also drops all relative URLs
+ // NOTE: this also drops all relative URLs
}
}
@@ -798,9 +807,10 @@ class Sanitizer {
*/
static function mergeAttributes( $a, $b ) {
$out = array_merge( $a, $b );
- if( isset( $a['class'] ) && isset( $b['class'] )
- && is_string( $a['class'] ) && is_string( $b['class'] )
- && $a['class'] !== $b['class'] ) {
+ if ( isset( $a['class'] ) && isset( $b['class'] )
+ && is_string( $a['class'] ) && is_string( $b['class'] )
+ && $a['class'] !== $b['class']
+ ) {
$classes = preg_split( '/\s+/', "{$a['class']} {$b['class']}",
-1, PREG_SPLIT_NO_EMPTY );
$out['class'] = implode( ' ', array_unique( $classes ) );
@@ -811,9 +821,10 @@ class Sanitizer {
/**
* Pick apart some CSS and check it for forbidden or unsafe structures.
* Returns a sanitized string. This sanitized string will have
- * character references and escape sequences decoded, and comments
- * stripped. If the input is just too evil, only a comment complaining
- * about evilness will be returned.
+ * character references and escape sequences decoded and comments
+ * stripped (unless it is itself one valid comment, in which case the value
+ * will be passed through). If the input is just too evil, only a comment
+ * complaining about evilness will be returned.
*
* Currently URL references, 'expression', 'tps' are forbidden.
*
@@ -854,60 +865,28 @@ class Sanitizer {
$value = preg_replace_callback( $decodeRegex,
array( __CLASS__, 'cssDecodeCallback' ), $value );
- // Normalize Halfwidth and Fullwidth Unicode block that IE6 might treat as ascii
- $value = preg_replace_callback(
- '/[ï¼-z]/u', // U+FF01 to U+FF5A
- function ( $matches ) {
- $cp = utf8ToCodepoint( $matches[0] );
- if ( $cp === false ) {
- return '';
- }
- return chr( $cp - 65248 ); // ASCII range \x21-\x7A
- },
- $value
- );
-
- // Convert more characters IE6 might treat as ascii
- // U+0280, U+0274, U+207F, U+029F, U+026A, U+207D, U+208D
- $value = str_replace(
- array( 'Ê€', 'É´', 'â¿', 'ÊŸ', 'ɪ', 'â½', 'â‚' ),
- array( 'r', 'n', 'n', 'l', 'i', '(', '(' ),
- $value
- );
-
- // Remove any comments; IE gets token splitting wrong
- // This must be done AFTER decoding character references and
- // escape sequences, because those steps can introduce comments
- // This step cannot introduce character references or escape
- // sequences, because it replaces comments with spaces rather
- // than removing them completely.
- $value = StringUtils::delimiterReplace( '/*', '*/', ' ', $value );
-
- // Remove anything after a comment-start token, to guard against
- // incorrect client implementations.
- $commentPos = strpos( $value, '/*' );
- if ( $commentPos !== false ) {
- $value = substr( $value, 0, $commentPos );
+ // Let the value through if it's nothing but a single comment, to
+ // allow other functions which may reject it to pass some error
+ // message through.
+ if ( !preg_match( '! ^ \s* /\* [^*\\/]* \*/ \s* $ !x', $value ) ) {
+ // Remove any comments; IE gets token splitting wrong
+ // This must be done AFTER decoding character references and
+ // escape sequences, because those steps can introduce comments
+ // This step cannot introduce character references or escape
+ // sequences, because it replaces comments with spaces rather
+ // than removing them completely.
+ $value = StringUtils::delimiterReplace( '/*', '*/', ' ', $value );
+
+ // Remove anything after a comment-start token, to guard against
+ // incorrect client implementations.
+ $commentPos = strpos( $value, '/*' );
+ if ( $commentPos !== false ) {
+ $value = substr( $value, 0, $commentPos );
+ }
}
- // S followed by repeat, iteration, or prolonged sound marks,
- // which IE will treat as "ss"
- $value = preg_replace(
- '/s(?:
- \xE3\x80\xB1 | # U+3031
- \xE3\x82\x9D | # U+309D
- \xE3\x83\xBC | # U+30FC
- \xE3\x83\xBD | # U+30FD
- \xEF\xB9\xBC | # U+FE7C
- \xEF\xB9\xBD | # U+FE7D
- \xEF\xBD\xB0 # U+FF70
- )/ix',
- 'ss',
- $value
- );
-
// Reject problematic keywords and control characters
- if ( preg_match( '/[\000-\010\013\016-\037\177]/', $value ) ) {
+ if ( preg_match( '/[\000-\010\016-\037\177]/', $value ) ) {
return '/* invalid control char */';
} elseif ( preg_match( '! expression | filter\s*: | accelerator\s*: | url\s*\( | image\s*\( | image-set\s*\( !ix', $value ) ) {
return '/* insecure input */';
@@ -960,21 +939,14 @@ class Sanitizer {
* @return String
*/
static function fixTagAttributes( $text, $element ) {
- if( trim( $text ) == '' ) {
+ if ( trim( $text ) == '' ) {
return '';
}
$decoded = Sanitizer::decodeTagAttributes( $text );
$stripped = Sanitizer::validateTagAttributes( $decoded, $element );
- $attribs = array();
- foreach( $stripped as $attribute => $value ) {
- $encAttribute = htmlspecialchars( $attribute );
- $encValue = Sanitizer::safeEncodeAttribute( $value );
-
- $attribs[] = "$encAttribute=\"$encValue\"";
- }
- return count( $attribs ) ? ' ' . implode( ' ', $attribs ) : '';
+ return Sanitizer::safeEncodeTagAttributes( $stripped );
}
/**
@@ -1047,7 +1019,7 @@ class Sanitizer {
* in the id and
* name attributes
* @see http://www.w3.org/TR/html401/struct/links.html#h-12.2.3 Anchors with the id attribute
- * @see http://www.whatwg.org/specs/web-apps/current-work/multipage/elements.html#the-id-attribute
+ * @see http://www.whatwg.org/html/elements.html#the-id-attribute
* HTML5 definition of id attribute
*
* @param string $id id to escape
@@ -1062,10 +1034,10 @@ class Sanitizer {
* @return String
*/
static function escapeId( $id, $options = array() ) {
- global $wgHtml5, $wgExperimentalHtmlIds;
+ global $wgExperimentalHtmlIds;
$options = (array)$options;
- if ( $wgHtml5 && $wgExperimentalHtmlIds && !in_array( 'legacy', $options ) ) {
+ if ( $wgExperimentalHtmlIds && !in_array( 'legacy', $options ) ) {
$id = Sanitizer::decodeCharReferences( $id );
$id = preg_replace( '/[ \t\n\r\f_\'"&#%]+/', '_', $id );
$id = trim( $id, '_' );
@@ -1146,13 +1118,13 @@ class Sanitizer {
* @return Array
*/
public static function decodeTagAttributes( $text ) {
- if( trim( $text ) == '' ) {
+ if ( trim( $text ) == '' ) {
return array();
}
$attribs = array();
$pairs = array();
- if( !preg_match_all(
+ if ( !preg_match_all(
self::getAttribsRegex(),
$text,
$pairs,
@@ -1160,7 +1132,7 @@ class Sanitizer {
return $attribs;
}
- foreach( $pairs as $set ) {
+ foreach ( $pairs as $set ) {
$attribute = strtolower( $set[1] );
$value = Sanitizer::getTagAttributeCallback( $set );
@@ -1175,6 +1147,24 @@ class Sanitizer {
}
/**
+ * Build a partial tag string from an associative array of attribute
+ * names and values as returned by decodeTagAttributes.
+ *
+ * @param $assoc_array Array
+ * @return String
+ */
+ public static function safeEncodeTagAttributes( $assoc_array ) {
+ $attribs = array();
+ foreach ( $assoc_array as $attribute => $value ) {
+ $encAttribute = htmlspecialchars( $attribute );
+ $encValue = Sanitizer::safeEncodeAttribute( $value );
+
+ $attribs[] = "$encAttribute=\"$encValue\"";
+ }
+ return count( $attribs ) ? ' ' . implode( ' ', $attribs ) : '';
+ }
+
+ /**
* Pick the appropriate attribute value from a match set from the
* attribs regex matches.
*
@@ -1183,19 +1173,19 @@ class Sanitizer {
* @return String
*/
private static function getTagAttributeCallback( $set ) {
- if( isset( $set[6] ) ) {
+ if ( isset( $set[6] ) ) {
# Illegal #XXXXXX color with no quotes.
return $set[6];
- } elseif( isset( $set[5] ) ) {
+ } elseif ( isset( $set[5] ) ) {
# No quotes.
return $set[5];
- } elseif( isset( $set[4] ) ) {
+ } elseif ( isset( $set[4] ) ) {
# Single-quoted
return $set[4];
- } elseif( isset( $set[3] ) ) {
+ } elseif ( isset( $set[3] ) ) {
# Double-quoted
return $set[3];
- } elseif( !isset( $set[2] ) ) {
+ } elseif ( !isset( $set[2] ) ) {
# In XHTML, attributes must have a value.
# For 'reduced' form, return explicitly the attribute name here.
return $set[1];
@@ -1271,14 +1261,14 @@ class Sanitizer {
*/
static function normalizeCharReferencesCallback( $matches ) {
$ret = null;
- if( $matches[1] != '' ) {
+ if ( $matches[1] != '' ) {
$ret = Sanitizer::normalizeEntity( $matches[1] );
- } elseif( $matches[2] != '' ) {
+ } elseif ( $matches[2] != '' ) {
$ret = Sanitizer::decCharReference( $matches[2] );
- } elseif( $matches[3] != '' ) {
+ } elseif ( $matches[3] != '' ) {
$ret = Sanitizer::hexCharReference( $matches[3] );
}
- if( is_null( $ret ) ) {
+ if ( is_null( $ret ) ) {
return htmlspecialchars( $matches[0] );
} else {
return $ret;
@@ -1314,7 +1304,7 @@ class Sanitizer {
*/
static function decCharReference( $codepoint ) {
$point = intval( $codepoint );
- if( Sanitizer::validateCodepoint( $point ) ) {
+ if ( Sanitizer::validateCodepoint( $point ) ) {
return sprintf( '&#%d;', $point );
} else {
return null;
@@ -1327,7 +1317,7 @@ class Sanitizer {
*/
static function hexCharReference( $codepoint ) {
$point = hexdec( $codepoint );
- if( Sanitizer::validateCodepoint( $point ) ) {
+ if ( Sanitizer::validateCodepoint( $point ) ) {
return sprintf( '&#x%x;', $point );
} else {
return null;
@@ -1340,12 +1330,12 @@ class Sanitizer {
* @return Boolean
*/
private static function validateCodepoint( $codepoint ) {
- return ($codepoint == 0x09)
- || ($codepoint == 0x0a)
- || ($codepoint == 0x0d)
- || ($codepoint >= 0x20 && $codepoint <= 0xd7ff)
- || ($codepoint >= 0xe000 && $codepoint <= 0xfffd)
- || ($codepoint >= 0x10000 && $codepoint <= 0x10ffff);
+ return $codepoint == 0x09
+ || $codepoint == 0x0a
+ || $codepoint == 0x0d
+ || ( $codepoint >= 0x20 && $codepoint <= 0xd7ff )
+ || ( $codepoint >= 0xe000 && $codepoint <= 0xfffd )
+ || ( $codepoint >= 0x10000 && $codepoint <= 0x10ffff );
}
/**
@@ -1391,12 +1381,12 @@ class Sanitizer {
* @return String
*/
static function decodeCharReferencesCallback( $matches ) {
- if( $matches[1] != '' ) {
+ if ( $matches[1] != '' ) {
return Sanitizer::decodeEntity( $matches[1] );
- } elseif( $matches[2] != '' ) {
- return Sanitizer::decodeChar( intval( $matches[2] ) );
- } elseif( $matches[3] != '' ) {
- return Sanitizer::decodeChar( hexdec( $matches[3] ) );
+ } elseif ( $matches[2] != '' ) {
+ return Sanitizer::decodeChar( intval( $matches[2] ) );
+ } elseif ( $matches[3] != '' ) {
+ return Sanitizer::decodeChar( hexdec( $matches[3] ) );
}
# Last case should be an ampersand by itself
return $matches[0];
@@ -1410,7 +1400,7 @@ class Sanitizer {
* @private
*/
static function decodeChar( $codepoint ) {
- if( Sanitizer::validateCodepoint( $codepoint ) ) {
+ if ( Sanitizer::validateCodepoint( $codepoint ) ) {
return codepointToUtf8( $codepoint );
} else {
return UTF8_REPLACEMENT;
@@ -1429,7 +1419,7 @@ class Sanitizer {
if ( isset( self::$htmlEntityAliases[$name] ) ) {
$name = self::$htmlEntityAliases[$name];
}
- if( isset( self::$htmlEntities[$name] ) ) {
+ if ( isset( self::$htmlEntities[$name] ) ) {
return codepointToUtf8( self::$htmlEntities[$name] );
} else {
return "&$name;";
@@ -1455,10 +1445,10 @@ class Sanitizer {
* @return Array
*/
static function setupAttributeWhitelist() {
- global $wgAllowRdfaAttributes, $wgHtml5, $wgAllowMicrodataAttributes;
+ global $wgAllowRdfaAttributes, $wgAllowMicrodataAttributes;
static $whitelist, $staticInitialised;
- $globalContext = implode( '-', compact( 'wgAllowRdfaAttributes', 'wgHtml5', 'wgAllowMicrodataAttributes' ) );
+ $globalContext = implode( '-', compact( 'wgAllowRdfaAttributes', 'wgAllowMicrodataAttributes' ) );
if ( isset( $whitelist ) && $staticInitialised == $globalContext ) {
return $whitelist;
@@ -1478,32 +1468,35 @@ class Sanitizer {
);
if ( $wgAllowRdfaAttributes ) {
- #RDFa attributes as specified in section 9 of http://www.w3.org/TR/2008/REC-rdfa-syntax-20081014
+ # RDFa attributes as specified in section 9 of
+ # http://www.w3.org/TR/2008/REC-rdfa-syntax-20081014
$common = array_merge( $common, array(
- 'about', 'property', 'resource', 'datatype', 'typeof',
+ 'about', 'property', 'resource', 'datatype', 'typeof',
) );
}
- if ( $wgHtml5 && $wgAllowMicrodataAttributes ) {
- # add HTML5 microdata tags as specified by http://www.whatwg.org/specs/web-apps/current-work/multipage/microdata.html#the-microdata-model
+ if ( $wgAllowMicrodataAttributes ) {
+ # add HTML5 microdata tags as specified by
+ # http://www.whatwg.org/html/microdata.html#the-microdata-model
$common = array_merge( $common, array(
- 'itemid', 'itemprop', 'itemref', 'itemscope', 'itemtype'
+ 'itemid', 'itemprop', 'itemref', 'itemscope', 'itemtype'
) );
}
$block = array_merge( $common, array( 'align' ) );
$tablealign = array( 'align', 'char', 'charoff', 'valign' );
- $tablecell = array( 'abbr',
- 'axis',
- 'headers',
- 'scope',
- 'rowspan',
- 'colspan',
- 'nowrap', # deprecated
- 'width', # deprecated
- 'height', # deprecated
- 'bgcolor' # deprecated
- );
+ $tablecell = array(
+ 'abbr',
+ 'axis',
+ 'headers',
+ 'scope',
+ 'rowspan',
+ 'colspan',
+ 'nowrap', # deprecated
+ 'width', # deprecated
+ 'height', # deprecated
+ 'bgcolor', # deprecated
+ );
# Numbers refer to sections in HTML 4.01 standard describing the element.
# See: http://www.w3.org/TR/html4/
@@ -1553,6 +1546,9 @@ class Sanitizer {
# 9.3.2
'br' => array( 'id', 'class', 'title', 'style', 'clear' ),
+ # http://www.whatwg.org/html/text-level-semantics.html#the-wbr-element
+ 'wbr' => array( 'id', 'class', 'title', 'style' ),
+
# 9.3.4
'pre' => array_merge( $common, array( 'width' ) ),
@@ -1596,7 +1592,9 @@ class Sanitizer {
'td' => array_merge( $common, $tablecell, $tablealign ),
'th' => array_merge( $common, $tablecell, $tablealign ),
- # 12.2 # NOTE: <a> is not allowed directly, but the attrib whitelist is used from the Parser object
+ # 12.2
+ # NOTE: <a> is not allowed directly, but the attrib
+ # whitelist is used from the Parser object
'a' => array_merge( $common, array( 'href', 'rel', 'rev' ) ), # rel/rev esp. for RDFa
# 13.2
@@ -1622,8 +1620,8 @@ class Sanitizer {
# 15.3
'hr' => array_merge( $common, array( 'noshade', 'size', 'width' ) ),
- # XHTML Ruby annotation text module, simple ruby only.
- # http://www.w3c.org/TR/ruby/
+ # HTML Ruby annotation text module, simple ruby only.
+ # http://www.whatwg.org/html/text-level-semantics.html#the-ruby-element
'ruby' => $common,
# rbc
# rtc
@@ -1639,25 +1637,20 @@ 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' ),
- );
- }
+ # http://www.whatwg.org/html/
+ '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;
@@ -1696,7 +1689,7 @@ class Sanitizer {
*/
static function hackDocType() {
$out = "<!DOCTYPE html [\n";
- foreach( self::$htmlEntities as $entity => $codepoint ) {
+ foreach ( self::$htmlEntities as $entity => $codepoint ) {
$out .= "<!ENTITY $entity \"&#$codepoint;\">";
}
$out .= "]>\n";
@@ -1718,7 +1711,7 @@ class Sanitizer {
# Validate hostname portion
$matches = array();
- if( preg_match( '!^([^:]+:)(//[^/]+)?(.*)$!iD', $url, $matches ) ) {
+ if ( preg_match( '!^([^:]+:)(//[^/]+)?(.*)$!iD', $url, $matches ) ) {
list( /* $whole */, $protocol, $host, $rest ) = $matches;
// Characters that will be ignored in IDNs.
@@ -1762,7 +1755,7 @@ class Sanitizer {
* Does a string look like an e-mail address?
*
* This validates an email address using an HTML5 specification found at:
- * http://www.whatwg.org/specs/web-apps/current-work/multipage/states-of-the-type-attribute.html#valid-e-mail-address
+ * http://www.whatwg.org/html/states-of-the-type-attribute.html#valid-e-mail-address
* Which as of 2011-01-24 says:
*
* A valid e-mail address is a string that matches the ABNF production
@@ -1788,7 +1781,7 @@ class Sanitizer {
*/
public static function validateEmail( $addr ) {
$result = null;
- if( !wfRunHooks( 'isValidEmailAddr', array( $addr, &$result ) ) ) {
+ if ( !wfRunHooks( 'isValidEmailAddr', array( $addr, &$result ) ) ) {
return $result;
}
@@ -1807,6 +1800,6 @@ class Sanitizer {
$ # End of string
/ix"; // case Insensitive, eXtended
- return (bool) preg_match( $HTML5_email_regexp, $addr );
+ return (bool)preg_match( $HTML5_email_regexp, $addr );
}
}
diff --git a/includes/ScopedCallback.php b/includes/ScopedCallback.php
index 1d5b26bf..ef22e0a3 100644
--- a/includes/ScopedCallback.php
+++ b/includes/ScopedCallback.php
@@ -22,19 +22,52 @@
/**
* Class for asserting that a callback happens when an dummy object leaves scope
+ *
+ * @since 1.21
*/
class ScopedCallback {
- /** @var Closure */
+ /** @var callable */
protected $callback;
/**
- * @param $callback Closure
+ * @param callable $callback
+ * @throws MWException
*/
- public function __construct( Closure $callback ) {
+ public function __construct( $callback ) {
+ if ( !is_callable( $callback ) ) {
+ throw new MWException( "Provided callback is not valid." );
+ }
$this->callback = $callback;
}
+ /**
+ * Trigger a scoped callback and destroy it.
+ * This is the same is just setting it to null.
+ *
+ * @param ScopedCallback $sc
+ */
+ public static function consume( ScopedCallback &$sc = null ) {
+ $sc = null;
+ }
+
+ /**
+ * Destroy a scoped callback without triggering it
+ *
+ * @param ScopedCallback $sc
+ */
+ public static function cancel( ScopedCallback &$sc = null ) {
+ if ( $sc ) {
+ $sc->callback = null;
+ }
+ $sc = null;
+ }
+
+ /**
+ * Trigger the callback when this leaves scope
+ */
function __destruct() {
- call_user_func( $this->callback );
+ if ( $this->callback !== null ) {
+ call_user_func( $this->callback );
+ }
}
}
diff --git a/includes/SeleniumWebSettings.php b/includes/SeleniumWebSettings.php
deleted file mode 100644
index 7715484e..00000000
--- a/includes/SeleniumWebSettings.php
+++ /dev/null
@@ -1,221 +0,0 @@
-<?php
-/**
- * Dynamically change configuration variables based on the test suite name and a cookie value.
- *
- * For details on how to configure a wiki for a Selenium test, see:
- * http://www.mediawiki.org/wiki/SeleniumFramework#Test_Wiki_configuration
- *
- * 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
- */
-
-if ( !defined( 'MEDIAWIKI' ) ) {
- die( 1 );
-}
-
-require_once( "$IP/includes/GlobalFunctions.php" );
-
-$fname = 'SeleniumWebSettings.php';
-wfProfileIn( $fname );
-
-$cookiePrefix = $wgSitename . '-';
-$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'];
-
- if (
- preg_match( '/[^a-zA-Z0-9_-]/', $setupTestSuiteName ) ||
- !isset( $wgSeleniumTestConfigs[$setupTestSuiteName] )
- )
- {
- return;
- }
- if ( strlen( $setupTestSuiteName ) > 0 ) {
- $expire = time() + 600;
- setcookie(
- $cookieName,
- $setupTestSuiteName,
- $expire,
- $wgCookiePath,
- $wgCookieDomain,
- $wgCookieSecure,
- true
- );
- }
-
- $testIncludes = array(); // array containing all the includes 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));
-
- 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 );
- }
-}
-
-// clear the cookie based on a request param
-if ( isset( $_GET['clearTestSuite'] ) ) {
- $testSuiteName = getTestSuiteNameFromCookie( $cookieName );
-
- $expire = time() - 600;
- setcookie(
- $cookieName,
- '',
- $expire,
- $wgCookiePath,
- $wgCookieDomain,
- $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] ) ) {
- $testSuiteName = getTestSuiteNameFromCookie( $cookieName );
- if ( !isset( $wgSeleniumTestConfigs[$testSuiteName] ) ) {
- return;
- }
-
- $testIncludes = array(); // array containing all the includes 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];
- call_user_func_array( $callback, array( &$testIncludes, &$testGlobalConfigs, &$testResourceFiles));
-
- if ( isset( $testResourceFiles['db'] ) ) {
- $testResourceName = getTestResourceNameFromTestSuiteName( $testSuiteName );
- switchToTestResources( $testResourceName );
- }
- foreach ( $testIncludes as $includeFile ) {
- $file = $IP . '/' . $includeFile;
- require_once( $file );
- }
- foreach ( $testGlobalConfigs as $key => $value ) {
- if ( is_array( $value ) ) {
- $GLOBALS[$key] = array_merge( $GLOBALS[$key], $value );
- } else {
- $GLOBALS[$key] = $value;
- }
- }
-}
-
-wfProfileOut( $fname );
-
-function getTestSuiteNameFromCookie( $cookieName ) {
- $testSuiteName = null;
- if ( isset( $_COOKIE[$cookieName] ) ) {
- $testSuiteName = $_COOKIE[$cookieName];
- }
- return $testSuiteName;
-}
-
-function getTestResourceNameFromTestSuiteName( $testSuiteName ) {
- $testResourceName = null;
- if ( isset( $testSuiteName ) ) {
- $testResourceName = $testSuiteName;
- }
- return $testResourceName;
-}
-
-function getTestUploadPathFromResourceName( $testResourceName ) {
- global $IP;
- $testUploadPath = "$IP/images/$testResourceName";
- return $testUploadPath;
-}
-
-function setupTestResources( $testResourceName, $testSqlFile, $testImageZip ) {
- global $wgDBname;
-
- // Basic security. Do not allow to drop productive database.
- if ( $testResourceName == $wgDBname ) {
- die( 'Cannot override productive database.' );
- }
- 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 );
- $dbw->query( 'CREATE DATABASE ' . $testResourceName );
-
- // do not set the new DB name before database is setup
- $wgDBname = $testResourceName;
- $dbw->selectDB( $testResourceName );
- // populate from SQL file
- if ( $testSqlFile ) {
- $dbw->sourceFile( $testSqlFile );
- }
-
- // create test image dir
- $testUploadPath = getTestUploadPathFromResourceName( $testResourceName );
- if ( !file_exists( $testUploadPath ) ) {
- mkdir( $testUploadPath );
- }
-
- if ( $testImageZip ) {
- $zip = new ZipArchive();
- $zip->open( $testImageZip );
- $zip->extractTo( $testUploadPath );
- $zip->close();
- }
-}
-
-function teardownTestResources( $testResourceName ) {
- // remove test database
- $dbw = wfGetDB( DB_MASTER );
- $dbw->query( 'DROP DATABASE IF EXISTS ' . $testResourceName );
-
- $testUploadPath = getTestUploadPathFromResourceName( $testResourceName );
- // remove test image dir
- if ( file_exists( $testUploadPath ) ) {
- wfRecursiveRemoveDir( $testUploadPath );
- }
-}
-
-function switchToTestResources( $testResourceName, $switchDB = true ) {
- global $wgDBuser, $wgDBpassword, $wgDBname;
- global $wgDBtestuser, $wgDBtestpassword;
- global $wgUploadPath;
-
- if ( $switchDB ) {
- $wgDBname = $testResourceName;
- }
- $wgDBuser = $wgDBtestuser;
- $wgDBpassword = $wgDBtestpassword;
-
- $testUploadPath = getTestUploadPathFromResourceName( $testResourceName );
- $wgUploadPath = $testUploadPath;
-}
diff --git a/includes/Setup.php b/includes/Setup.php
index e87b200f..2e083d83 100644
--- a/includes/Setup.php
+++ b/includes/Setup.php
@@ -44,9 +44,12 @@ if ( !isset( $wgVersion ) ) {
}
// Set various default paths sensibly...
-if ( $wgScript === false ) $wgScript = "$wgScriptPath/index$wgScriptExtension";
-if ( $wgRedirectScript === false ) $wgRedirectScript = "$wgScriptPath/redirect$wgScriptExtension";
-if ( $wgLoadScript === false ) $wgLoadScript = "$wgScriptPath/load$wgScriptExtension";
+if ( $wgScript === false ) {
+ $wgScript = "$wgScriptPath/index$wgScriptExtension";
+}
+if ( $wgLoadScript === false ) {
+ $wgLoadScript = "$wgScriptPath/load$wgScriptExtension";
+}
if ( $wgArticlePath === false ) {
if ( $wgUsePathInfo ) {
@@ -62,24 +65,38 @@ if ( !empty( $wgActionPaths ) && !isset( $wgActionPaths['view'] ) ) {
$wgActionPaths['view'] = $wgArticlePath;
}
-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 ( $wgStylePath === false ) {
+ $wgStylePath = "$wgScriptPath/skins";
+}
+if ( $wgLocalStylePath === false ) {
+ $wgLocalStylePath = "$wgScriptPath/skins";
+}
+if ( $wgStyleDirectory === false ) {
+ $wgStyleDirectory = "$IP/skins";
+}
+if ( $wgExtensionAssetsPath === false ) {
+ $wgExtensionAssetsPath = "$wgScriptPath/extensions";
}
-if ( $wgStylePath === false ) $wgStylePath = "$wgScriptPath/skins";
-if ( $wgLocalStylePath === false ) $wgLocalStylePath = "$wgScriptPath/skins";
-if ( $wgStyleDirectory === false ) $wgStyleDirectory = "$IP/skins";
-if ( $wgExtensionAssetsPath === false ) $wgExtensionAssetsPath = "$wgScriptPath/extensions";
-
-if ( $wgLogo === false ) $wgLogo = "$wgStylePath/common/images/wiki.png";
+if ( $wgLogo === false ) {
+ $wgLogo = "$wgStylePath/common/images/wiki.png";
+}
-if ( $wgUploadPath === false ) $wgUploadPath = "$wgScriptPath/images";
-if ( $wgUploadDirectory === false ) $wgUploadDirectory = "$IP/images";
-if ( $wgReadOnlyFile === false ) $wgReadOnlyFile = "{$wgUploadDirectory}/lock_yBgMBwiR";
-if ( $wgFileCacheDirectory === false ) $wgFileCacheDirectory = "{$wgUploadDirectory}/cache";
-if ( $wgDeletedDirectory === false ) $wgDeletedDirectory = "{$wgUploadDirectory}/deleted";
+if ( $wgUploadPath === false ) {
+ $wgUploadPath = "$wgScriptPath/images";
+}
+if ( $wgUploadDirectory === false ) {
+ $wgUploadDirectory = "$IP/images";
+}
+if ( $wgReadOnlyFile === false ) {
+ $wgReadOnlyFile = "{$wgUploadDirectory}/lock_yBgMBwiR";
+}
+if ( $wgFileCacheDirectory === false ) {
+ $wgFileCacheDirectory = "{$wgUploadDirectory}/cache";
+}
+if ( $wgDeletedDirectory === false ) {
+ $wgDeletedDirectory = "{$wgUploadDirectory}/deleted";
+}
if ( isset( $wgFileStore['deleted']['directory'] ) ) {
$wgDeletedDirectory = $wgFileStore['deleted']['directory'];
@@ -130,13 +147,13 @@ $wgNamespaceAliases['Image_talk'] = NS_FILE_TALK;
* Initialise $wgLockManagers to include basic FS version
*/
$wgLockManagers[] = array(
- 'name' => 'fsLockManager',
- 'class' => 'FSLockManager',
+ 'name' => 'fsLockManager',
+ 'class' => 'FSLockManager',
'lockDirectory' => "{$wgUploadDirectory}/lockdir",
);
$wgLockManagers[] = array(
- 'name' => 'nullLockManager',
- 'class' => 'NullLockManager',
+ 'name' => 'nullLockManager',
+ 'class' => 'NullLockManager',
);
/**
@@ -202,15 +219,15 @@ if ( $wgUseSharedUploads ) {
}
if ( $wgUseInstantCommons ) {
$wgForeignFileRepos[] = array(
- 'class' => 'ForeignAPIRepo',
- 'name' => 'wikimediacommons',
- 'apibase' => WebRequest::detectProtocol() === 'https' ?
+ 'class' => 'ForeignAPIRepo',
+ 'name' => 'wikimediacommons',
+ 'apibase' => WebRequest::detectProtocol() === 'https' ?
'https://commons.wikimedia.org/w/api.php' :
'http://commons.wikimedia.org/w/api.php',
- 'hashLevels' => 2,
- 'fetchDescription' => true,
+ 'hashLevels' => 2,
+ 'fetchDescription' => true,
'descriptionCacheExpiry' => 43200,
- 'apiThumbCacheExpiry' => 86400,
+ 'apiThumbCacheExpiry' => 86400,
);
}
/*
@@ -276,6 +293,13 @@ if ( $wgMetaNamespace === false ) {
$wgMetaNamespace = str_replace( ' ', '_', $wgSitename );
}
+
+// Default value is either the suhosin limit or -1 for unlimited
+if ( $wgResourceLoaderMaxQueryLength === false ) {
+ $maxValueLength = ini_get( 'suhosin.get.max_value_length' );
+ $wgResourceLoaderMaxQueryLength = $maxValueLength > 0 ? $maxValueLength : -1;
+}
+
/**
* Definitions of the NS_ constants are in Defines.php
* @private
@@ -301,7 +325,7 @@ $wgCanonicalNamespaceNames = array(
);
/// @todo UGLY UGLY
-if( is_array( $wgExtraNamespaces ) ) {
+if ( is_array( $wgExtraNamespaces ) ) {
$wgCanonicalNamespaceNames = $wgCanonicalNamespaceNames + $wgExtraNamespaces;
}
@@ -323,11 +347,17 @@ if ( !$wgEnotifMinorEdits ) {
}
# $wgDisabledActions is deprecated as of 1.18
-foreach( $wgDisabledActions as $action ) {
+foreach ( $wgDisabledActions as $action ) {
$wgActions[$action] = false;
}
-if ( !$wgHtml5Version && $wgHtml5 && $wgAllowRdfaAttributes ) {
+# We always output HTML5 since 1.22, overriding these is no longer supported
+# we set them here for extensions that depend on its value.
+$wgHtml5 = true;
+$wgXhtmlDefaultNamespace = 'http://www.w3.org/1999/xhtml';
+$wgJsMimeType = 'text/javascript';
+
+if ( !$wgHtml5Version && $wgAllowRdfaAttributes ) {
# see http://www.w3.org/TR/rdfa-in-html/#document-conformance
if ( $wgMimeType == 'application/xhtml+xml' ) {
$wgHtml5Version = 'XHTML+RDFa 1.0';
@@ -347,10 +377,6 @@ if ( $wgInvalidateCacheOnLocalSettingsChange ) {
$wgCacheEpoch = max( $wgCacheEpoch, gmdate( 'YmdHis', @filemtime( "$IP/LocalSettings.php" ) ) );
}
-if ( $wgAjaxUploadDestCheck ) {
- $wgAjaxExportList[] = 'SpecialUpload::ajaxGetExistsWarning';
-}
-
if ( $wgNewUserLog ) {
# Add a new log type
$wgLogTypes[] = 'newusers';
@@ -367,6 +393,15 @@ if ( $wgCookieSecure === 'detect' ) {
$wgCookieSecure = ( WebRequest::detectProtocol() === 'https' );
}
+if ( $wgRC2UDPAddress ) {
+ $wgRCFeeds['default'] = array(
+ 'formatter' => 'IRCColourfulRCFeedFormatter',
+ 'uri' => "udp://$wgRC2UDPAddress:$wgRC2UDPPort/$wgRC2UDPPrefix",
+ 'add_interwiki_prefix' => &$wgRC2UDPInterwikiPrefix,
+ 'omit_bots' => &$wgRC2UDPOmitBots,
+ );
+}
+
// Disable MWDebug for command line mode, this prevents MWDebug from eating up
// all the memory from logging SQL queries on maintenance scripts
global $wgCommandLineMode;
@@ -374,22 +409,20 @@ if ( $wgDebugToolbar && !$wgCommandLineMode ) {
MWDebug::init();
}
-if ( !defined( 'MW_COMPILED' ) ) {
- if ( !MWInit::classExists( 'AutoLoader' ) ) {
- require_once( "$IP/includes/AutoLoader.php" );
- }
+if ( !class_exists( 'AutoLoader' ) ) {
+ require_once "$IP/includes/AutoLoader.php";
+}
- wfProfileIn( $fname . '-exception' );
- MWExceptionHandler::installHandler();
- wfProfileOut( $fname . '-exception' );
+wfProfileIn( $fname . '-exception' );
+MWExceptionHandler::installHandler();
+wfProfileOut( $fname . '-exception' );
- wfProfileIn( $fname . '-includes' );
- require_once( "$IP/includes/normal/UtfNormalUtil.php" );
- require_once( "$IP/includes/GlobalFunctions.php" );
- require_once( "$IP/includes/ProxyTools.php" );
- require_once( "$IP/includes/normal/UtfNormalDefines.php" );
- wfProfileOut( $fname . '-includes' );
-}
+wfProfileIn( $fname . '-includes' );
+require_once "$IP/includes/normal/UtfNormalUtil.php";
+require_once "$IP/includes/GlobalFunctions.php";
+require_once "$IP/includes/ProxyTools.php";
+require_once "$IP/includes/normal/UtfNormalDefines.php";
+wfProfileOut( $fname . '-includes' );
if ( $wgSecureLogin && substr( $wgServer, 0, 2 ) !== '//' ) {
$wgSecureLogin = false;
@@ -406,9 +439,16 @@ if ( $wgCanonicalServer === false ) {
$wgCanonicalServer = wfExpandUrl( $wgServer, PROTO_HTTP );
}
-// Initialize $wgHTCPMulticastRouting from backwards-compatible settings
-if ( !$wgHTCPMulticastRouting && $wgHTCPMulticastAddress ) {
- $wgHTCPMulticastRouting = array(
+// $wgHTCPMulticastRouting got renamed to $wgHTCPRouting in MediaWiki 1.22
+// ensure back compatibility.
+if ( !$wgHTCPRouting && $wgHTCPMulticastRouting ) {
+ $wgHTCPRouting = $wgHTCPMulticastRouting;
+}
+
+// Initialize $wgHTCPRouting from backwards-compatible settings that
+// comes from pre 1.20 version.
+if ( !$wgHTCPRouting && $wgHTCPMulticastAddress ) {
+ $wgHTCPRouting = array(
'' => array(
'host' => $wgHTCPMulticastAddress,
'port' => $wgHTCPPort,
@@ -426,14 +466,14 @@ wfMemoryLimit();
* that happens whenever you use a date function without the timezone being
* explicitly set. Inspired by phpMyAdmin's treatment of the problem.
*/
-if ( is_null( $wgLocaltimezone) ) {
+if ( is_null( $wgLocaltimezone ) ) {
wfSuppressWarnings();
$wgLocaltimezone = date_default_timezone_get();
wfRestoreWarnings();
}
date_default_timezone_set( $wgLocaltimezone );
-if( is_null( $wgLocalTZoffset ) ) {
+if ( is_null( $wgLocalTZoffset ) ) {
$wgLocalTZoffset = date( 'Z' ) / 60;
}
@@ -539,10 +579,11 @@ foreach ( $wgExtensionFunctions as $func ) {
if ( is_object( $func ) && $func instanceof Closure ) {
$profName = $fname . '-extensions-closure';
} elseif ( is_array( $func ) ) {
- if ( is_object( $func[0] ) )
+ if ( is_object( $func[0] ) ) {
$profName = $fname . '-extensions-' . get_class( $func[0] ) . '::' . $func[1];
- else
+ } else {
$profName = $fname . '-extensions-' . implode( '::', $func );
+ }
} else {
$profName = $fname . '-extensions-' . strval( $func );
}
diff --git a/includes/SiteConfiguration.php b/includes/SiteConfiguration.php
index bbc14a12..fa871fe0 100644
--- a/includes/SiteConfiguration.php
+++ b/includes/SiteConfiguration.php
@@ -191,11 +191,11 @@ class SiteConfiguration {
*/
protected function getSetting( $settingName, $wiki, /*array*/ $params ) {
$retval = null;
- if( array_key_exists( $settingName, $this->settings ) ) {
+ if ( array_key_exists( $settingName, $this->settings ) ) {
$thisSetting =& $this->settings[$settingName];
do {
// Do individual wiki settings
- if( array_key_exists( $wiki, $thisSetting ) ) {
+ if ( array_key_exists( $wiki, $thisSetting ) ) {
$retval = $thisSetting[$wiki];
break;
} elseif ( array_key_exists( "+$wiki", $thisSetting ) && is_array( $thisSetting["+$wiki"] ) ) {
@@ -203,16 +203,16 @@ class SiteConfiguration {
}
// Do tag settings
- foreach( $params['tags'] as $tag ) {
- if( array_key_exists( $tag, $thisSetting ) ) {
+ foreach ( $params['tags'] as $tag ) {
+ if ( array_key_exists( $tag, $thisSetting ) ) {
if ( isset( $retval ) && is_array( $retval ) && is_array( $thisSetting[$tag] ) ) {
$retval = self::arrayMerge( $retval, $thisSetting[$tag] );
} else {
$retval = $thisSetting[$tag];
}
break 2;
- } elseif( array_key_exists( "+$tag", $thisSetting ) && is_array( $thisSetting["+$tag"] ) ) {
- if( !isset( $retval ) ) {
+ } elseif ( array_key_exists( "+$tag", $thisSetting ) && is_array( $thisSetting["+$tag"] ) ) {
+ if ( !isset( $retval ) ) {
$retval = array();
}
$retval = self::arrayMerge( $retval, $thisSetting["+$tag"] );
@@ -220,15 +220,15 @@ class SiteConfiguration {
}
// Do suffix settings
$suffix = $params['suffix'];
- if( !is_null( $suffix ) ) {
- if( array_key_exists( $suffix, $thisSetting ) ) {
+ if ( !is_null( $suffix ) ) {
+ if ( array_key_exists( $suffix, $thisSetting ) ) {
if ( isset( $retval ) && is_array( $retval ) && is_array( $thisSetting[$suffix] ) ) {
$retval = self::arrayMerge( $retval, $thisSetting[$suffix] );
} else {
$retval = $thisSetting[$suffix];
}
break;
- } elseif ( array_key_exists( "+$suffix", $thisSetting ) && is_array($thisSetting["+$suffix"]) ) {
+ } elseif ( array_key_exists( "+$suffix", $thisSetting ) && is_array( $thisSetting["+$suffix"] ) ) {
if ( !isset( $retval ) ) {
$retval = array();
}
@@ -237,8 +237,8 @@ class SiteConfiguration {
}
// Fall back to default.
- if( array_key_exists( 'default', $thisSetting ) ) {
- if( is_array( $retval ) && is_array( $thisSetting['default'] ) ) {
+ if ( array_key_exists( 'default', $thisSetting ) ) {
+ if ( is_array( $retval ) && is_array( $thisSetting['default'] ) ) {
$retval = self::arrayMerge( $retval, $thisSetting['default'] );
} else {
$retval = $thisSetting['default'];
@@ -248,7 +248,7 @@ class SiteConfiguration {
} while ( false );
}
- if( !is_null( $retval ) && count( $params['params'] ) ) {
+ if ( !is_null( $retval ) && count( $params['params'] ) ) {
foreach ( $params['params'] as $key => $value ) {
$retval = $this->doReplace( '$' . $key, $value, $retval );
}
@@ -266,10 +266,10 @@ class SiteConfiguration {
* @return string
*/
function doReplace( $from, $to, $in ) {
- if( is_string( $in ) ) {
+ if ( is_string( $in ) ) {
return str_replace( $from, $to, $in );
- } elseif( is_array( $in ) ) {
- foreach( $in as $key => $val ) {
+ } elseif ( is_array( $in ) ) {
+ foreach ( $in as $key => $val ) {
$in[$key] = $this->doReplace( $from, $to, $val );
}
return $in;
@@ -289,7 +289,7 @@ class SiteConfiguration {
public function getAll( $wiki, $suffix = null, $params = array(), $wikiTags = array() ) {
$params = $this->mergeParams( $wiki, $suffix, $params, $wikiTags );
$localSettings = array();
- foreach( $this->settings as $varname => $stuff ) {
+ foreach ( $this->settings as $varname => $stuff ) {
$append = false;
$var = $varname;
if ( substr( $varname, 0, 1 ) == '+' ) {
@@ -317,7 +317,7 @@ class SiteConfiguration {
* @return bool The value of the setting requested.
*/
public function getBool( $setting, $wiki, $suffix = null, $wikiTags = array() ) {
- return (bool)($this->get( $setting, $wiki, $suffix, array(), $wikiTags ) );
+ return (bool)$this->get( $setting, $wiki, $suffix, array(), $wikiTags );
}
/**
@@ -409,18 +409,18 @@ class SiteConfiguration {
'params' => array(),
);
- if( !is_callable( $this->siteParamsCallback ) ) {
+ if ( !is_callable( $this->siteParamsCallback ) ) {
return $default;
}
$ret = call_user_func_array( $this->siteParamsCallback, array( $this, $wiki ) );
# Validate the returned value
- if( !is_array( $ret ) ) {
+ if ( !is_array( $ret ) ) {
return $default;
}
- foreach( $default as $name => $def ) {
- if( !isset( $ret[$name] ) || ( is_array( $default[$name] ) && !is_array( $ret[$name] ) ) ) {
+ foreach ( $default as $name => $def ) {
+ if ( !isset( $ret[$name] ) || ( is_array( $default[$name] ) && !is_array( $ret[$name] ) ) ) {
$ret[$name] = $default[$name];
}
}
@@ -443,7 +443,7 @@ class SiteConfiguration {
protected function mergeParams( $wiki, $suffix, /*array*/ $params, /*array*/ $wikiTags ) {
$ret = $this->getWikiParams( $wiki );
- if( is_null( $ret['suffix'] ) ) {
+ if ( is_null( $ret['suffix'] ) ) {
$ret['suffix'] = $suffix;
}
@@ -452,10 +452,10 @@ 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'] ) ) {
+ if ( !isset( $ret['params']['site'] ) && !is_null( $ret['suffix'] ) ) {
$ret['params']['site'] = $ret['suffix'];
}
@@ -471,19 +471,19 @@ class SiteConfiguration {
public function siteFromDB( $db ) {
// Allow override
$def = $this->getWikiParams( $db );
- if( !is_null( $def['suffix'] ) && !is_null( $def['lang'] ) ) {
+ if ( !is_null( $def['suffix'] ) && !is_null( $def['lang'] ) ) {
return array( $def['suffix'], $def['lang'] );
}
$site = null;
$lang = null;
- foreach ( $this->suffixes as $suffix ) {
+ foreach ( $this->suffixes as $altSite => $suffix ) {
if ( $suffix === '' ) {
$site = '';
$lang = $db;
break;
} elseif ( substr( $db, -strlen( $suffix ) ) == $suffix ) {
- $site = $suffix == 'wiki' ? 'wikipedia' : $suffix;
+ $site = is_numeric( $altSite ) ? $suffix : $altSite;
$lang = substr( $db, 0, strlen( $db ) - strlen( $suffix ) );
break;
}
@@ -574,8 +574,8 @@ class SiteConfiguration {
*/
static function arrayMerge( $array1/* ... */ ) {
$out = $array1;
- for( $i = 1; $i < func_num_args(); $i++ ) {
- foreach( func_get_arg( $i ) as $key => $value ) {
+ 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 ) ) {
$out[$key] = self::arrayMerge( $out[$key], $value );
} elseif ( !isset( $out[$key] ) || !$out[$key] && !is_numeric( $key ) ) {
diff --git a/includes/SiteStats.php b/includes/SiteStats.php
index b7be29d6..355993c6 100644
--- a/includes/SiteStats.php
+++ b/includes/SiteStats.php
@@ -61,13 +61,13 @@ class SiteStats {
wfDebug( __METHOD__ . ": reading site_stats from slave\n" );
$row = self::doLoad( wfGetDB( DB_SLAVE ) );
- if( !self::isSane( $row ) ) {
+ if ( !self::isSane( $row ) ) {
// Might have just been initialized during this request? Underflow?
wfDebug( __METHOD__ . ": site_stats damaged or missing on slave\n" );
$row = self::doLoad( wfGetDB( DB_MASTER ) );
}
- if( !self::isSane( $row ) ) {
+ if ( !self::isSane( $row ) ) {
// Normally the site_stats table is initialized at install time.
// Some manual construction scenarios may leave the table empty or
// broken, however, for instance when importing from a dump into a
@@ -79,7 +79,7 @@ class SiteStats {
$row = self::doLoad( wfGetDB( DB_MASTER ) );
}
- if( !self::isSane( $row ) ) {
+ if ( !self::isSane( $row ) ) {
wfDebug( __METHOD__ . ": site_stats persistently nonsensical o_O\n" );
}
return $row;
@@ -189,7 +189,7 @@ class SiteStats {
static function jobs() {
if ( !isset( self::$jobs ) ) {
$dbr = wfGetDB( DB_SLAVE );
- self::$jobs = $dbr->estimateRowCount( 'job' );
+ self::$jobs = array_sum( JobQueueGroup::singleton()->getQueueSizes() );
/* Zero rows still do single row read for row that doesn't exist, but people are annoyed by that */
if ( self::$jobs == 1 ) {
self::$jobs = 0;
@@ -205,7 +205,7 @@ class SiteStats {
*/
static function pagesInNs( $ns ) {
wfProfileIn( __METHOD__ );
- if( !isset( self::$pageCount[$ns] ) ) {
+ if ( !isset( self::$pageCount[$ns] ) ) {
$dbr = wfGetDB( DB_SLAVE );
self::$pageCount[$ns] = (int)$dbr->selectField(
'page',
@@ -226,20 +226,24 @@ class SiteStats {
* @return bool
*/
private static function isSane( $row ) {
- if(
- $row === false
+ if ( $row === false
|| $row->ss_total_pages < $row->ss_good_articles
|| $row->ss_total_edits < $row->ss_total_pages
+ || $row->ss_users < $row->ss_active_users
) {
return false;
}
// Now check for underflow/overflow
- foreach( array( 'total_views', 'total_edits', 'good_articles',
- 'total_pages', 'users', 'images' ) as $member ) {
- if(
- $row->{"ss_$member"} > 2000000000
- || $row->{"ss_$member"} < 0
- ) {
+ foreach ( array(
+ 'ss_total_views',
+ 'ss_total_edits',
+ 'ss_good_articles',
+ 'ss_total_pages',
+ 'ss_users',
+ 'ss_active_users',
+ 'ss_images',
+ ) as $member ) {
+ if ( $row->$member > 2000000000 || $row->$member < 0 ) {
return false;
}
}
@@ -258,7 +262,7 @@ class SiteStatsUpdate implements DeferrableUpdate {
protected $users = 0;
protected $images = 0;
- // @TODO: deprecate this constructor
+ // @todo deprecate this constructor
function __construct( $views, $edits, $good, $pages = 0, $users = 0 ) {
$this->views = $views;
$this->edits = $edits;
@@ -293,50 +297,56 @@ class SiteStatsUpdate implements DeferrableUpdate {
if ( $rate && ( $rate < 0 || mt_rand( 0, $rate - 1 ) != 0 ) ) {
$this->doUpdatePendingDeltas();
} else {
- $dbw = wfGetDB( DB_MASTER );
-
- $lockKey = wfMemcKey( 'site_stats' ); // prepend wiki ID
- if ( $rate ) {
- // Lock the table so we don't have double DB/memcached updates
- if ( !$dbw->lockIsFree( $lockKey, __METHOD__ )
- || !$dbw->lock( $lockKey, __METHOD__, 1 ) // 1 sec timeout
- ) {
- $this->doUpdatePendingDeltas();
- return;
- }
- $pd = $this->getPendingDeltas();
- // Piggy-back the async deltas onto those of this stats update....
- $this->views += ( $pd['ss_total_views']['+'] - $pd['ss_total_views']['-'] );
- $this->edits += ( $pd['ss_total_edits']['+'] - $pd['ss_total_edits']['-'] );
- $this->articles += ( $pd['ss_good_articles']['+'] - $pd['ss_good_articles']['-'] );
- $this->pages += ( $pd['ss_total_pages']['+'] - $pd['ss_total_pages']['-'] );
- $this->users += ( $pd['ss_users']['+'] - $pd['ss_users']['-'] );
- $this->images += ( $pd['ss_images']['+'] - $pd['ss_images']['-'] );
- }
-
// Need a separate transaction because this a global lock
- $dbw->begin( __METHOD__ );
-
- // Build up an SQL query of deltas and apply them...
- $updates = '';
- $this->appendUpdate( $updates, 'ss_total_views', $this->views );
- $this->appendUpdate( $updates, 'ss_total_edits', $this->edits );
- $this->appendUpdate( $updates, 'ss_good_articles', $this->articles );
- $this->appendUpdate( $updates, 'ss_total_pages', $this->pages );
- $this->appendUpdate( $updates, 'ss_users', $this->users );
- $this->appendUpdate( $updates, 'ss_images', $this->images );
- if ( $updates != '' ) {
- $dbw->update( 'site_stats', array( $updates ), array(), __METHOD__ );
- }
+ wfGetDB( DB_MASTER )->onTransactionIdle( array( $this, 'tryDBUpdateInternal' ) );
+ }
+ }
- if ( $rate ) {
- // Decrement the async deltas now that we applied them
- $this->removePendingDeltas( $pd );
- // Commit the updates and unlock the table
- $dbw->unlock( $lockKey, __METHOD__ );
+ /**
+ * Do not call this outside of SiteStatsUpdate
+ *
+ * @return void
+ */
+ public function tryDBUpdateInternal() {
+ global $wgSiteStatsAsyncFactor;
+
+ $dbw = wfGetDB( DB_MASTER );
+ $lockKey = wfMemcKey( 'site_stats' ); // prepend wiki ID
+ if ( $wgSiteStatsAsyncFactor ) {
+ // Lock the table so we don't have double DB/memcached updates
+ if ( !$dbw->lockIsFree( $lockKey, __METHOD__ )
+ || !$dbw->lock( $lockKey, __METHOD__, 1 ) // 1 sec timeout
+ ) {
+ $this->doUpdatePendingDeltas();
+ return;
}
+ $pd = $this->getPendingDeltas();
+ // Piggy-back the async deltas onto those of this stats update....
+ $this->views += ( $pd['ss_total_views']['+'] - $pd['ss_total_views']['-'] );
+ $this->edits += ( $pd['ss_total_edits']['+'] - $pd['ss_total_edits']['-'] );
+ $this->articles += ( $pd['ss_good_articles']['+'] - $pd['ss_good_articles']['-'] );
+ $this->pages += ( $pd['ss_total_pages']['+'] - $pd['ss_total_pages']['-'] );
+ $this->users += ( $pd['ss_users']['+'] - $pd['ss_users']['-'] );
+ $this->images += ( $pd['ss_images']['+'] - $pd['ss_images']['-'] );
+ }
+
+ // Build up an SQL query of deltas and apply them...
+ $updates = '';
+ $this->appendUpdate( $updates, 'ss_total_views', $this->views );
+ $this->appendUpdate( $updates, 'ss_total_edits', $this->edits );
+ $this->appendUpdate( $updates, 'ss_good_articles', $this->articles );
+ $this->appendUpdate( $updates, 'ss_total_pages', $this->pages );
+ $this->appendUpdate( $updates, 'ss_users', $this->users );
+ $this->appendUpdate( $updates, 'ss_images', $this->images );
+ if ( $updates != '' ) {
+ $dbw->update( 'site_stats', array( $updates ), array(), __METHOD__ );
+ }
- $dbw->commit( __METHOD__ );
+ if ( $wgSiteStatsAsyncFactor ) {
+ // Decrement the async deltas now that we applied them
+ $this->removePendingDeltas( $pd );
+ // Commit the updates and unlock the table
+ $dbw->unlock( $lockKey, __METHOD__ );
}
}
@@ -356,7 +366,7 @@ class SiteStatsUpdate implements DeferrableUpdate {
'rc_user != 0',
'rc_bot' => 0,
'rc_log_type != ' . $dbr->addQuotes( 'newusers' ) . ' OR rc_log_type IS NULL',
- 'rc_timestamp >= ' . $dbr->addQuotes( $dbr->timestamp( wfTimestamp( TS_UNIX ) - $wgActiveUserDays*24*3600 ) ),
+ 'rc_timestamp >= ' . $dbr->addQuotes( $dbr->timestamp( wfTimestamp( TS_UNIX ) - $wgActiveUserDays * 24 * 3600 ) ),
),
__METHOD__
);
@@ -592,19 +602,19 @@ class SiteStatsInit {
$counter->files();
// Only do views if we don't want to not count them
- if( $options['views'] ) {
+ if ( $options['views'] ) {
$counter->views();
}
// Update/refresh
- if( $options['update'] ) {
+ if ( $options['update'] ) {
$counter->update();
} else {
$counter->refresh();
}
// Count active users if need be
- if( $options['activeUsers'] ) {
+ if ( $options['activeUsers'] ) {
SiteStatsUpdate::cacheUpdate( wfGetDB( DB_MASTER ) );
}
}
diff --git a/includes/Skin.php b/includes/Skin.php
index 0cc1086c..5801806c 100644
--- a/includes/Skin.php
+++ b/includes/Skin.php
@@ -25,7 +25,7 @@
*/
/**
- * The main skin class that provide methods and properties for all other skins.
+ * The main skin class which provides methods and properties for all other skins.
* This base class is also the "Standard" skin.
*
* See docs/skin.txt for more information.
@@ -56,17 +56,19 @@ abstract class Skin extends ContextSource {
$skinDir = dir( $wgStyleDirectory );
- # while code from www.php.net
- while ( false !== ( $file = $skinDir->read() ) ) {
- // Skip non-PHP files, hidden files, and '.dep' includes
- $matches = array();
+ if ( $skinDir !== false && $skinDir !== null ) {
+ # while code from www.php.net
+ while ( false !== ( $file = $skinDir->read() ) ) {
+ // Skip non-PHP files, hidden files, and '.dep' includes
+ $matches = array();
- if ( preg_match( '/^([^.]*)\.php$/', $file, $matches ) ) {
- $aSkin = $matches[1];
- $wgValidSkinNames[strtolower( $aSkin )] = $aSkin;
+ if ( preg_match( '/^([^.]*)\.php$/', $file, $matches ) ) {
+ $aSkin = $matches[1];
+ $wgValidSkinNames[strtolower( $aSkin )] = $aSkin;
+ }
}
+ $skinDir->close();
}
- $skinDir->close();
$skinsInitialised = true;
wfProfileOut( __METHOD__ . '-init' );
}
@@ -79,14 +81,15 @@ abstract class Skin extends ContextSource {
*/
static function getSkinNameMessages() {
$messages = array();
- foreach( self::getSkinNames() as $skinKey => $skinName ) {
+ foreach ( self::getSkinNames() as $skinKey => $skinName ) {
+ // Messages: skinname-cologneblue, skinname-monobook, skinname-modern, skinname-vector
$messages[] = "skinname-$skinKey";
}
return $messages;
}
/**
- * Fetch the list of usable skins in regards to $wgSkipSkins.
+ * Fetch the list of user-selectable skins in regards to $wgSkipSkins.
* Useful for Special:Preferences and other places where you
* only want to show skins users _can_ use.
* @return array of strings
@@ -94,13 +97,13 @@ abstract class Skin extends ContextSource {
public static function getUsableSkins() {
global $wgSkipSkins;
- $usableSkins = self::getSkinNames();
+ $allowedSkins = self::getSkinNames();
foreach ( $wgSkipSkins as $skip ) {
- unset( $usableSkins[$skip] );
+ unset( $allowedSkins[$skip] );
}
- return $usableSkins;
+ return $allowedSkins;
}
/**
@@ -129,7 +132,6 @@ abstract class Skin extends ContextSource {
// in the user preferences.
$fallback = array(
0 => $wgDefaultSkin,
- 1 => 'nostalgia',
2 => 'cologneblue'
);
@@ -161,23 +163,19 @@ abstract class Skin extends ContextSource {
$className = "Skin{$skinName}";
# Grab the skin class and initialise it.
- if ( !MWInit::classExists( $className ) ) {
+ if ( !class_exists( $className ) ) {
- if ( !defined( 'MW_COMPILED' ) ) {
- require_once( "{$wgStyleDirectory}/{$skinName}.php" );
- }
+ require_once "{$wgStyleDirectory}/{$skinName}.php";
# Check if we got if not fallback to default skin
- if ( !MWInit::classExists( $className ) ) {
+ if ( !class_exists( $className ) ) {
# DO NOT die if the class isn't found. This breaks maintenance
# scripts and can cause a user account to be unrecoverable
# except by SQL manipulation if a previously valid skin name
# is no longer valid.
wfDebug( "Skin class does not exist: $className\n" );
$className = 'SkinVector';
- if ( !defined( 'MW_COMPILED' ) ) {
- require_once( "{$wgStyleDirectory}/Vector.php" );
- }
+ require_once "{$wgStyleDirectory}/Vector.php";
}
}
$skin = new $className( $key );
@@ -201,6 +199,68 @@ abstract class Skin extends ContextSource {
}
/**
+ * Defines the ResourceLoader modules that should be added to the skin
+ * It is recommended that skins wishing to override call parent::getDefaultModules()
+ * and substitute out any modules they wish to change by using a key to look them up
+ * @return Array of modules with helper keys for easy overriding
+ */
+ public function getDefaultModules() {
+ global $wgIncludeLegacyJavaScript, $wgPreloadJavaScriptMwUtil, $wgUseAjax,
+ $wgAjaxWatch, $wgEnableAPI, $wgEnableWriteAPI;
+
+ $out = $this->getOutput();
+ $user = $out->getUser();
+ $modules = array(
+ // modules that enhance the page content in some way
+ 'content' => array(
+ 'mediawiki.page.ready',
+ ),
+ // modules that exist for legacy reasons
+ 'legacy' => array(),
+ // modules relating to search functionality
+ 'search' => array(),
+ // modules relating to functionality relating to watching an article
+ 'watch' => array(),
+ // modules which relate to the current users preferences
+ 'user' => array(),
+ );
+ if ( $wgIncludeLegacyJavaScript ) {
+ $modules['legacy'][] = 'mediawiki.legacy.wikibits';
+ }
+
+ if ( $wgPreloadJavaScriptMwUtil ) {
+ $modules['legacy'][] = 'mediawiki.util';
+ }
+
+ // Add various resources if required
+ if ( $wgUseAjax ) {
+ $modules['legacy'][] = 'mediawiki.legacy.ajax';
+
+ if ( $wgEnableAPI ) {
+ if ( $wgEnableWriteAPI && $wgAjaxWatch && $user->isLoggedIn()
+ && $user->isAllowed( 'writeapi' )
+ ) {
+ $modules['watch'][] = 'mediawiki.page.watch.ajax';
+ }
+
+ if ( !$user->getOption( 'disablesuggest', false ) ) {
+ $modules['search'][] = 'mediawiki.searchSuggest';
+ }
+ }
+ }
+
+ if ( $user->getBoolOption( 'editsectiononrightclick' ) ) {
+ $modules['user'][] = 'mediawiki.action.view.rightClickEdit';
+ }
+
+ // Crazy edit-on-double-click stuff
+ if ( $out->isArticle() && $user->getOption( 'editondblclick' ) ) {
+ $modules['user'][] = 'mediawiki.action.view.dblClickEdit';
+ }
+ return $modules;
+ }
+
+ /**
* Preload the existence of three commonly-requested pages in a single query
*/
function preloadExistence() {
@@ -290,7 +350,7 @@ abstract class Skin extends ContextSource {
return $this->mRelevantUser;
}
$title = $this->getRelevantTitle();
- if( $title->hasSubjectNamespace( NS_USER ) ) {
+ if ( $title->hasSubjectNamespace( NS_USER ) ) {
$rootUser = $title->getRootText();
if ( User::isIP( $rootUser ) ) {
$this->mRelevantUser = User::newFromName( $rootUser, false );
@@ -667,7 +727,7 @@ abstract class Skin extends ContextSource {
if ( $c > 1 ) {
$subpages .= $wgLang->getDirMarkEntity() . $this->msg( 'pipe-separator' )->escaped();
- } else {
+ } else {
$subpages .= '&lt; ';
}
@@ -1014,7 +1074,7 @@ abstract class Skin extends ContextSource {
*/
static function makeSpecialUrl( $name, $urlaction = '', $proto = null ) {
$title = SpecialPage::getSafeTitleFor( $name );
- if( is_null( $proto ) ) {
+ if ( is_null( $proto ) ) {
return $title->getLocalURL( $urlaction );
} else {
return $title->getFullURL( $urlaction, false, $proto );
@@ -1252,7 +1312,7 @@ abstract class Skin extends ContextSource {
}
global $wgExternalLinkTarget;
- if ( $wgExternalLinkTarget) {
+ if ( $wgExternalLinkTarget ) {
$extraAttribs['target'] = $wgExternalLinkTarget;
}
} else {
@@ -1282,28 +1342,43 @@ abstract class Skin extends ContextSource {
}
/**
- * Should we load mediawiki.legacy.wikiprintable? Skins that have their own
- * print stylesheet should override this and return false. (This is an
- * ugly hack to get Monobook to play nicely with OutputPage::headElement().)
+ * This function previously controlled whether the 'mediawiki.legacy.wikiprintable' module
+ * should be loaded by OutputPage. That module no longer exists and the return value of this
+ * method is ignored.
*
+ * If your skin doesn't provide its own print styles, the 'mediawiki.legacy.commonPrint' module
+ * can be used instead (SkinTemplate-based skins do it automatically).
+ *
+ * @deprecated since 1.22
* @return bool
*/
public function commonPrintStylesheet() {
- return true;
+ wfDeprecated( __METHOD__, '1.22' );
+ return false;
}
/**
- * Gets new talk page messages for the current user.
- * @return MediaWiki message or if no new talk page messages, nothing
+ * Gets new talk page messages for the current user and returns an
+ * appropriate alert message (or an empty string if there are no messages)
+ * @return String
*/
function getNewtalks() {
+
+ $newMessagesAlert = '';
+ $user = $this->getUser();
+ $newtalks = $user->getNewMessageLinks();
$out = $this->getOutput();
- $newtalks = $this->getUser()->getNewMessageLinks();
- $ntl = '';
+ // Allow extensions to disable or modify the new messages alert
+ if ( !wfRunHooks( 'GetNewMessagesAlert', array( &$newMessagesAlert, $newtalks, $user, $out ) ) ) {
+ return '';
+ }
+ if ( $newMessagesAlert ) {
+ return $newMessagesAlert;
+ }
if ( count( $newtalks ) == 1 && $newtalks[0]['wiki'] === wfWikiID() ) {
- $uTalkTitle = $this->getUser()->getTalkPage();
+ $uTalkTitle = $user->getTalkPage();
if ( !$uTalkTitle->equals( $out->getTitle() ) ) {
$lastSeenRev = isset( $newtalks[0]['rev'] ) ? $newtalks[0]['rev'] : null;
@@ -1342,26 +1417,25 @@ abstract class Skin extends ContextSource {
);
if ( $nofAuthors >= 1 && $nofAuthors <= 10 ) {
- $ntl = $this->msg(
+ $newMessagesAlert = $this->msg(
'youhavenewmessagesfromusers',
$newMessagesLink,
$newMessagesDiffLink
)->numParams( $nofAuthors );
} else {
// $nofAuthors === 11 signifies "11 or more" ("more than 10")
- $ntl = $this->msg(
+ $newMessagesAlert = $this->msg(
$nofAuthors > 10 ? 'youhavenewmessagesmanyusers' : 'youhavenewmessages',
$newMessagesLink,
$newMessagesDiffLink
);
}
- $ntl = $ntl->text();
+ $newMessagesAlert = $newMessagesAlert->text();
# Disable Squid cache
$out->setSquidMaxage( 0 );
}
} elseif ( count( $newtalks ) ) {
- // _>" " for BC <= 1.16
- $sep = str_replace( '_', ' ', $this->msg( 'newtalkseparator' )->escaped() );
+ $sep = $this->msg( 'newtalkseparator' )->escaped();
$msgs = array();
foreach ( $newtalks as $newtalk ) {
@@ -1371,11 +1445,11 @@ abstract class Skin extends ContextSource {
);
}
$parts = implode( $sep, $msgs );
- $ntl = $this->msg( 'youhavenewmessagesmulti' )->rawParams( $parts )->escaped();
+ $newMessagesAlert = $this->msg( 'youhavenewmessagesmulti' )->rawParams( $parts )->escaped();
$out->setSquidMaxage( 0 );
}
- return $ntl;
+ return $newMessagesAlert;
}
/**
@@ -1391,17 +1465,17 @@ abstract class Skin extends ContextSource {
$needParse = false;
- if( $name === 'default' ) {
+ if ( $name === 'default' ) {
// special case
global $wgSiteNotice;
$notice = $wgSiteNotice;
- if( empty( $notice ) ) {
+ if ( empty( $notice ) ) {
wfProfileOut( __METHOD__ );
return false;
}
} else {
$msg = $this->msg( $name )->inContentLanguage();
- if( $msg->isDisabled() ) {
+ if ( $msg->isDisabled() ) {
wfProfileOut( __METHOD__ );
return false;
}
@@ -1411,8 +1485,8 @@ abstract class Skin extends ContextSource {
// Use the extra hash appender to let eg SSL variants separately cache.
$key = wfMemcKey( $name . $wgRenderHashAppend );
$cachedNotice = $parserMemc->get( $key );
- if( is_array( $cachedNotice ) ) {
- if( md5( $notice ) == $cachedNotice['hash'] ) {
+ if ( is_array( $cachedNotice ) ) {
+ if ( md5( $notice ) == $cachedNotice['hash'] ) {
$notice = $cachedNotice['html'];
} else {
$needParse = true;
@@ -1515,31 +1589,12 @@ abstract class Skin extends ContextSource {
array( 'noclasses', 'known' )
);
- # Run the old hook. This takes up half of the function . . . hopefully
- # we can rid of it someday.
- $attribs = '';
- if ( $tooltip ) {
- $attribs = wfMessage( 'editsectionhint' )->rawParams( $tooltip )
- ->inLanguage( $lang )->escaped();
- $attribs = " title=\"$attribs\"";
- }
- $result = null;
- wfRunHooks( 'EditSectionLink', array( &$this, $nt, $section, $attribs, $link, &$result, $lang ) );
- if ( !is_null( $result ) ) {
- # For reverse compatibility, add the brackets *after* the hook is
- # run, and even add them to hook-provided text. (This is the main
- # reason that the EditSectionLink hook is deprecated in favor of
- # DoEditSectionLink: it can't change the brackets or the span.)
- $result = wfMessage( 'editsection-brackets' )->rawParams( $result )
- ->inLanguage( $lang )->escaped();
- return "<span class=\"editsection\">$result</span>";
- }
-
- # Add the brackets and the span, and *then* run the nice new hook, with
- # clean and non-redundant arguments.
- $result = wfMessage( 'editsection-brackets' )->rawParams( $link )
- ->inLanguage( $lang )->escaped();
- $result = "<span class=\"editsection\">$result</span>";
+ # Add the brackets and the span and run the hook.
+ $result = '<span class="mw-editsection">'
+ . '<span class="mw-editsection-bracket">[</span>'
+ . $link
+ . '<span class="mw-editsection-bracket">]</span>'
+ . '</span>';
wfRunHooks( 'DoEditSectionLink', array( $this, $nt, $section, $tooltip, &$result, $lang ) );
return $result;
@@ -1557,6 +1612,7 @@ abstract class Skin extends ContextSource {
function __call( $fname, $args ) {
$realFunction = array( 'Linker', $fname );
if ( is_callable( $realFunction ) ) {
+ wfDeprecated( get_class( $this ) . '::' . $fname, '1.21' );
return call_user_func_array( $realFunction, $args );
} else {
$className = get_class( $this );
diff --git a/includes/SkinLegacy.php b/includes/SkinLegacy.php
deleted file mode 100644
index b9766a9f..00000000
--- a/includes/SkinLegacy.php
+++ /dev/null
@@ -1,882 +0,0 @@
-<?php
-/**
- * Base class for legacy skins.
- *
- * 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 SkinLegacy extends SkinTemplate {
- var $useHeadElement = true;
- protected $mWatchLinkNum = 0; // Appended to end of watch link id's
-
- /**
- * Add skin specific stylesheets
- * @param $out OutputPage
- */
- function setupSkinUserCss( OutputPage $out ) {
- $out->addModuleStyles( 'mediawiki.legacy.shared' );
- $out->addModuleStyles( 'mediawiki.legacy.oldshared' );
- }
-
- public function commonPrintStylesheet() {
- return true;
- }
-
- /**
- * This was for the old skins and for users with 640x480 screen.
- * Please note old skins are still used and might prove useful for
- * users having old computers or visually impaired.
- */
- var $mSuppressQuickbar = false;
-
- /**
- * Suppress the quickbar from the output, only for skin supporting
- * the quickbar
- */
- public function suppressQuickbar() {
- $this->mSuppressQuickbar = true;
- }
-
- /**
- * Return whether the quickbar should be suppressed from the output
- *
- * @return Boolean
- */
- public function isQuickbarSuppressed() {
- return $this->mSuppressQuickbar;
- }
-
- function qbSetting() {
- global $wgUser;
- if ( $this->isQuickbarSuppressed() ) {
- return 0;
- }
- $q = $wgUser->getOption( 'quickbar', 0 );
- if( $q == 5 ) {
- # 5 is the default, which chooses the setting
- # depending on the directionality of your interface language
- global $wgLang;
- return $wgLang->isRTL() ? 2 : 1;
- }
- return $q;
- }
-
-}
-
-class LegacyTemplate extends BaseTemplate {
-
- // How many search boxes have we made? Avoid duplicate id's.
- protected $searchboxes = '';
-
- function execute() {
- $this->html( 'headelement' );
- echo $this->beforeContent();
- $this->html( 'bodytext' );
- echo "\n";
- echo $this->afterContent();
- $this->html( 'dataAfterContent' );
- $this->printTrail();
- echo "\n</body></html>";
- }
-
- /**
- * This will be called immediately after the "<body>" tag. Split into
- * two functions to make it easier to subclass.
- * @return string
- */
- function beforeContent() {
- return $this->doBeforeContent();
- }
-
- function doBeforeContent() {
- global $wgLang;
- wfProfileIn( __METHOD__ );
-
- $s = '';
-
- $langlinks = $this->otherLanguages();
- if ( $langlinks ) {
- $rows = 2;
- $borderhack = '';
- } else {
- $rows = 1;
- $langlinks = false;
- $borderhack = 'class="top"';
- }
-
- $s .= "\n<div id='content'>\n<div id='topbar'>\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" .
- $this->getSkin()->logoText( $wgLang->alignStart() ) . '</td>';
- }
-
- $l = $wgLang->alignStart();
- $s .= "<td {$borderhack} style='text-align: $l; vertical-align: top;'>\n";
-
- $s .= $this->topLinks();
- $s .= '<p class="subtitle">' . $this->pageTitleLinks() . "</p>\n";
-
- $r = $wgLang->alignEnd();
- $s .= "</td>\n<td {$borderhack} style='text-align: $r; vertical-align: top;' nowrap='nowrap'>";
- $s .= $this->nameAndLogin();
- $s .= "\n<br />" . $this->searchForm() . '</td>';
-
- if ( $langlinks ) {
- $s .= "</tr>\n<tr>\n<td class='top' colspan=\"2\">$langlinks</td>\n";
- }
-
- $s .= "</tr>\n</table>\n</div>\n";
- $s .= "\n<div id='article'>\n";
-
- $notice = $this->getSkin()->getSiteNotice();
-
- if ( $notice ) {
- $s .= "\n<div id='siteNotice'>$notice</div>\n";
- }
- $s .= $this->pageTitle();
- $s .= $this->pageSubtitle();
- $s .= $this->getSkin()->getCategories();
-
- wfProfileOut( __METHOD__ );
- return $s;
- }
-
- /**
- * This gets called shortly before the "</body>" tag.
- * @return String HTML to be put before "</body>"
- */
- function afterContent() {
- return $this->doAfterContent();
- }
-
- /** overloaded by derived classes
- * @return string
- */
- function doAfterContent() {
- return '</div></div>';
- }
-
- function searchForm() {
- global $wgRequest, $wgUseTwoButtonsSearchForm;
-
- $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() . '" />';
-
- if ( $wgUseTwoButtonsSearchForm ) {
- $s .= '&#160;<input type="submit" name="fulltext" value="' . wfMessage( 'searchbutton' )->text() . "\" />\n";
- } else {
- $s .= ' <a href="' . $this->getSkin()->escapeSearchLink() . '" rel="search">' . wfMessage( 'powersearch-legend' )->text() . "</a>\n";
- }
-
- $s .= '</form>';
-
- // Ensure unique id's for search boxes made after the first
- $this->searchboxes = $this->searchboxes == '' ? 2 : $this->searchboxes + 1;
-
- return $s;
- }
-
- function pageStats() {
- $ret = array();
- $items = array( 'viewcount', 'credits', 'lastmod', 'numberofwatchingusers', 'copyright' );
-
- foreach( $items as $item ) {
- if ( $this->data[$item] !== false ) {
- $ret[] = $this->data[$item];
- }
- }
-
- return implode( ' ', $ret );
- }
-
- function topLinks() {
- global $wgOut;
-
- $s = array(
- $this->getSkin()->mainPageLink(),
- Linker::specialLink( 'Recentchanges' )
- );
-
- if ( $wgOut->isArticleRelated() ) {
- $s[] = $this->editThisPage();
- $s[] = $this->historyLink();
- }
-
- # Many people don't like this dropdown box
- # $s[] = $this->specialPagesList();
-
- if ( $this->variantLinks() ) {
- $s[] = $this->variantLinks();
- }
-
- if ( $this->extensionTabLinks() ) {
- $s[] = $this->extensionTabLinks();
- }
-
- // @todo FIXME: Is using Language::pipeList impossible here? Do not quite understand the use of the newline
- return implode( $s, wfMessage( 'pipe-separator' )->escaped() . "\n" );
- }
-
- /**
- * Language/charset variant links for classic-style skins
- * @return string
- */
- function variantLinks() {
- $s = '';
-
- /* show links to different language variants */
- global $wgDisableLangConversion, $wgLang;
-
- $title = $this->getSkin()->getTitle();
- $lang = $title->getPageLanguage();
- $variants = $lang->getVariants();
-
- if ( !$wgDisableLangConversion && count( $variants ) > 1
- && !$title->isSpecialPage() ) {
- foreach ( $variants as $code ) {
- $varname = $lang->getVariantname( $code );
-
- if ( $varname == 'disable' ) {
- continue;
- }
- $s = $wgLang->pipeList( array(
- $s,
- '<a href="' . htmlspecialchars( $title->getLocalURL( 'variant=' . $code ) ) . '" lang="' . $code . '" hreflang="' . $code . '">' . htmlspecialchars( $varname ) . '</a>'
- ) );
- }
- }
-
- return $s;
- }
-
- /**
- * Compatibility for extensions adding functionality through tabs.
- * Eventually these old skins should be replaced with SkinTemplate-based
- * versions, sigh...
- * @return string
- * @todo Exterminate! ...that, and replace it with normal SkinTemplate stuff
- */
- function extensionTabLinks() {
- $tabs = array();
- $out = '';
- $s = array();
- wfRunHooks( 'SkinTemplateTabs', array( $this->getSkin(), &$tabs ) );
- foreach ( $tabs as $tab ) {
- $s[] = Xml::element( 'a',
- array( 'href' => $tab['href'] ),
- $tab['text'] );
- }
-
- if ( count( $s ) ) {
- global $wgLang;
-
- $out = wfMessage( 'pipe-separator' )->escaped();
- $out .= $wgLang->pipeList( $s );
- }
-
- return $out;
- }
-
- function bottomLinks() {
- global $wgOut, $wgUser;
- $sep = wfMessage( 'pipe-separator' )->escaped() . "\n";
-
- $s = '';
- if ( $wgOut->isArticleRelated() ) {
- $element[] = '<strong>' . $this->editThisPage() . '</strong>';
-
- if ( $wgUser->isLoggedIn() ) {
- $element[] = $this->watchThisPage();
- }
-
- $element[] = $this->talkLink();
- $element[] = $this->historyLink();
- $element[] = $this->whatLinksHere();
- $element[] = $this->watchPageLinksLink();
-
- $title = $this->getSkin()->getTitle();
-
- if (
- $title->getNamespace() == NS_USER ||
- $title->getNamespace() == NS_USER_TALK
- ) {
- $id = User::idFromName( $title->getText() );
- $ip = User::isIP( $title->getText() );
-
- # Both anons and non-anons have contributions list
- if ( $id || $ip ) {
- $element[] = $this->userContribsLink();
- }
-
- if ( $this->getSkin()->showEmailUser( $id ) ) {
- $element[] = $this->emailUserLink();
- }
- }
-
- $s = implode( $element, $sep );
-
- if ( $title->getArticleID() ) {
- $s .= "\n<br />";
-
- // Delete/protect/move links for privileged users
- if ( $wgUser->isAllowed( 'delete' ) ) {
- $s .= $this->deleteThisPage();
- }
-
- if ( $wgUser->isAllowed( 'protect' ) && $title->getRestrictionTypes() ) {
- $s .= $sep . $this->protectThisPage();
- }
-
- if ( $wgUser->isAllowed( 'move' ) ) {
- $s .= $sep . $this->moveThisPage();
- }
- }
-
- $s .= "<br />\n" . $this->otherLanguages();
- }
-
- return $s;
- }
-
- function otherLanguages() {
- global $wgOut, $wgLang, $wgHideInterlanguageLinks;
-
- if ( $wgHideInterlanguageLinks ) {
- return '';
- }
-
- $a = $wgOut->getLanguageLinks();
-
- if ( 0 == count( $a ) ) {
- return '';
- }
-
- $s = wfMessage( 'otherlanguages' )->text() . wfMessage( 'colon-separator' )->text();
- $first = true;
-
- if ( $wgLang->isRTL() ) {
- $s .= '<span dir="ltr">';
- }
-
- foreach ( $a as $l ) {
- if ( !$first ) {
- $s .= wfMessage( 'pipe-separator' )->escaped();
- }
-
- $first = false;
-
- $nt = Title::newFromText( $l );
- $text = Language::fetchLanguageName( $nt->getInterwiki() );
-
- $s .= Html::element( 'a',
- array( 'href' => $nt->getFullURL(), 'title' => $nt->getText(), 'class' => "external" ),
- $text == '' ? $l : $text );
- }
-
- if ( $wgLang->isRTL() ) {
- $s .= '</span>';
- }
-
- return $s;
- }
-
- /**
- * Show a drop-down box of special pages
- * @return string
- */
- function specialPagesList() {
- global $wgScript;
-
- $select = new XmlSelect( 'title' );
- $pages = SpecialPageFactory::getUsablePages();
- array_unshift( $pages, SpecialPageFactory::getPage( 'SpecialPages' ) );
- foreach ( $pages as $obj ) {
- $select->addOption( $obj->getDescription(),
- $obj->getTitle()->getPrefixedDBkey() );
- }
-
- return Html::rawElement( 'form',
- array( 'id' => 'specialpages', 'method' => 'get', 'action' => $wgScript ),
- $select->getHTML() . Xml::submitButton( wfMessage( 'go' )->text() ) );
- }
-
- function pageTitleLinks() {
- global $wgOut, $wgUser, $wgRequest, $wgLang;
-
- $oldid = $wgRequest->getVal( 'oldid' );
- $diff = $wgRequest->getVal( 'diff' );
- $action = $wgRequest->getText( 'action' );
-
- $skin = $this->getSkin();
- $title = $skin->getTitle();
-
- $s[] = $this->printableLink();
- $disclaimer = $skin->disclaimerLink(); # may be empty
-
- if ( $disclaimer ) {
- $s[] = $disclaimer;
- }
-
- $privacy = $skin->privacyLink(); # may be empty too
-
- if ( $privacy ) {
- $s[] = $privacy;
- }
-
- if ( $wgOut->isArticleRelated() ) {
- if ( $title->getNamespace() == NS_FILE ) {
- $image = wfFindFile( $title );
-
- if ( $image ) {
- $href = $image->getURL();
- $s[] = Html::element( 'a', array( 'href' => $href,
- 'title' => $href ), $title->getText() );
-
- }
- }
- }
-
- if ( 'history' == $action || isset( $diff ) || isset( $oldid ) ) {
- $s[] .= Linker::linkKnown(
- $title,
- wfMessage( 'currentrev' )->text()
- );
- }
-
- if ( $wgUser->getNewtalk() ) {
- # do not show "You have new messages" text when we are viewing our
- # own talk page
- if ( !$title->equals( $wgUser->getTalkPage() ) ) {
- $tl = Linker::linkKnown(
- $wgUser->getTalkPage(),
- wfMessage( 'newmessageslink' )->escaped(),
- array(),
- array( 'redirect' => 'no' )
- );
-
- $dl = Linker::linkKnown(
- $wgUser->getTalkPage(),
- wfMessage( 'newmessagesdifflink' )->escaped(),
- array(),
- array( 'diff' => 'cur' )
- );
- $s[] = '<strong>' . wfMessage( 'youhavenewmessages', $tl, $dl )->text() . '</strong>';
- # disable caching
- $wgOut->setSquidMaxage( 0 );
- $wgOut->enableClientCache( false );
- }
- }
-
- $undelete = $skin->getUndeleteLink();
-
- if ( !empty( $undelete ) ) {
- $s[] = $undelete;
- }
-
- return $wgLang->pipeList( $s );
- }
-
- /**
- * Gets the h1 element with the page title.
- * @return string
- */
- function pageTitle() {
- global $wgOut;
- $s = '<h1 class="pagetitle"><span dir="auto">' . $wgOut->getPageTitle() . '</span></h1>';
- return $s;
- }
-
- function pageSubtitle() {
- global $wgOut;
-
- $sub = $wgOut->getSubtitle();
-
- if ( $sub == '' ) {
- global $wgExtraSubtitle;
- $sub = wfMessage( 'tagline' )->parse() . $wgExtraSubtitle;
- }
-
- $subpages = $this->getSkin()->subPageSubtitle();
- $sub .= !empty( $subpages ) ? "</p><p class='subpages'>$subpages" : '';
- $s = "<p class='subtitle'>{$sub}</p>\n";
-
- return $s;
- }
-
- function printableLink() {
- global $wgOut, $wgRequest, $wgLang;
-
- $s = array();
-
- if ( !$wgOut->isPrintable() ) {
- $printurl = htmlspecialchars( $this->getSkin()->getTitle()->getLocalUrl(
- $wgRequest->appendQueryValue( 'printable', 'yes', true ) ) );
- $s[] = "<a href=\"$printurl\" rel=\"alternate\">"
- . wfMessage( 'printableversion' )->text() . '</a>';
- }
-
- if ( $wgOut->isSyndicated() ) {
- foreach ( $wgOut->getSyndicationLinks() as $format => $link ) {
- $feedurl = htmlspecialchars( $link );
- $s[] = "<a href=\"$feedurl\" rel=\"alternate\" type=\"application/{$format}+xml\""
- . " class=\"feedlink\">" . wfMessage( "feed-$format" )->escaped() . "</a>";
- }
- }
- return $wgLang->pipeList( $s );
- }
-
- /**
- * @deprecated in 1.19
- * @return string
- */
- function getQuickbarCompensator( $rows = 1 ) {
- wfDeprecated( __METHOD__, '1.19' );
- return "<td style='width: 152px;' rowspan='{$rows}'>&#160;</td>";
- }
-
- function editThisPage() {
- global $wgOut;
-
- if ( !$wgOut->isArticleRelated() ) {
- $s = wfMessage( 'protectedpage' )->text();
- } else {
- $title = $this->getSkin()->getTitle();
- if ( $title->quickUserCan( 'edit' ) && $title->exists() ) {
- $t = wfMessage( 'editthispage' )->text();
- } elseif ( $title->quickUserCan( 'create' ) && !$title->exists() ) {
- $t = wfMessage( 'create-this-page' )->text();
- } else {
- $t = wfMessage( 'viewsource' )->text();
- }
-
- $s = Linker::linkKnown(
- $title,
- $t,
- array(),
- $this->getSkin()->editUrlOptions()
- );
- }
-
- return $s;
- }
-
- function deleteThisPage() {
- global $wgUser, $wgRequest;
-
- $diff = $wgRequest->getVal( 'diff' );
- $title = $this->getSkin()->getTitle();
-
- if ( $title->getArticleID() && ( !$diff ) && $wgUser->isAllowed( 'delete' ) ) {
- $t = wfMessage( 'deletethispage' )->text();
-
- $s = Linker::linkKnown(
- $title,
- $t,
- array(),
- array( 'action' => 'delete' )
- );
- } else {
- $s = '';
- }
-
- return $s;
- }
-
- function protectThisPage() {
- global $wgUser, $wgRequest;
-
- $diff = $wgRequest->getVal( 'diff' );
- $title = $this->getSkin()->getTitle();
-
- if ( $title->getArticleID() && ( ! $diff ) && $wgUser->isAllowed( 'protect' ) && $title->getRestrictionTypes() ) {
- if ( $title->isProtected() ) {
- $text = wfMessage( 'unprotectthispage' )->text();
- $query = array( 'action' => 'unprotect' );
- } else {
- $text = wfMessage( 'protectthispage' )->text();
- $query = array( 'action' => 'protect' );
- }
-
- $s = Linker::linkKnown(
- $title,
- $text,
- array(),
- $query
- );
- } else {
- $s = '';
- }
-
- return $s;
- }
-
- function watchThisPage() {
- global $wgOut, $wgUser;
- ++$this->mWatchLinkNum;
-
- // Cache
- $title = $this->getSkin()->getTitle();
-
- if ( $wgOut->isArticleRelated() ) {
- if ( $wgUser->isWatched( $title ) ) {
- $text = wfMessage( 'unwatchthispage' )->text();
- $query = array(
- 'action' => 'unwatch',
- 'token' => UnwatchAction::getUnwatchToken( $title, $wgUser ),
- );
- $id = 'mw-unwatch-link' . $this->mWatchLinkNum;
- } else {
- $text = wfMessage( 'watchthispage' )->text();
- $query = array(
- 'action' => 'watch',
- 'token' => WatchAction::getWatchToken( $title, $wgUser ),
- );
- $id = 'mw-watch-link' . $this->mWatchLinkNum;
- }
-
- $s = Linker::linkKnown(
- $title,
- $text,
- array( 'id' => $id ),
- $query
- );
- } else {
- $s = wfMessage( 'notanarticle' )->text();
- }
-
- return $s;
- }
-
- function moveThisPage() {
- if ( $this->getSkin()->getTitle()->quickUserCan( 'move' ) ) {
- return Linker::linkKnown(
- SpecialPage::getTitleFor( 'Movepage' ),
- wfMessage( 'movethispage' )->text(),
- array(),
- array( 'target' => $this->getSkin()->getTitle()->getPrefixedDBkey() )
- );
- } else {
- // no message if page is protected - would be redundant
- return '';
- }
- }
-
- function historyLink() {
- return Linker::link(
- $this->getSkin()->getTitle(),
- wfMessage( 'history' )->escaped(),
- array( 'rel' => 'archives' ),
- array( 'action' => 'history' )
- );
- }
-
- function whatLinksHere() {
- return Linker::linkKnown(
- SpecialPage::getTitleFor( 'Whatlinkshere', $this->getSkin()->getTitle()->getPrefixedDBkey() ),
- wfMessage( 'whatlinkshere' )->escaped()
- );
- }
-
- function userContribsLink() {
- return Linker::linkKnown(
- SpecialPage::getTitleFor( 'Contributions', $this->getSkin()->getTitle()->getDBkey() ),
- wfMessage( 'contributions' )->escaped()
- );
- }
-
- function emailUserLink() {
- return Linker::linkKnown(
- SpecialPage::getTitleFor( 'Emailuser', $this->getSkin()->getTitle()->getDBkey() ),
- wfMessage( 'emailuser' )->escaped()
- );
- }
-
- function watchPageLinksLink() {
- global $wgOut;
-
- if ( !$wgOut->isArticleRelated() ) {
- return wfMessage( 'parentheses', wfMessage( 'notanarticle' )->text() )->escaped();
- } else {
- return Linker::linkKnown(
- SpecialPage::getTitleFor( 'Recentchangeslinked', $this->getSkin()->getTitle()->getPrefixedDBkey() ),
- wfMessage( 'recentchangeslinked-toolbox' )->escaped()
- );
- }
- }
-
- function talkLink() {
- $title = $this->getSkin()->getTitle();
- if ( NS_SPECIAL == $title->getNamespace() ) {
- # No discussion links for special pages
- return '';
- }
-
- $linkOptions = array();
-
- if ( $title->isTalkPage() ) {
- $link = $title->getSubjectPage();
- switch( $link->getNamespace() ) {
- case NS_MAIN:
- $text = wfMessage( 'articlepage' );
- break;
- case NS_USER:
- $text = wfMessage( 'userpage' );
- break;
- case NS_PROJECT:
- $text = wfMessage( 'projectpage' );
- break;
- case NS_FILE:
- $text = wfMessage( 'imagepage' );
- # Make link known if image exists, even if the desc. page doesn't.
- if ( wfFindFile( $link ) )
- $linkOptions[] = 'known';
- break;
- case NS_MEDIAWIKI:
- $text = wfMessage( 'mediawikipage' );
- break;
- case NS_TEMPLATE:
- $text = wfMessage( 'templatepage' );
- break;
- case NS_HELP:
- $text = wfMessage( 'viewhelppage' );
- break;
- case NS_CATEGORY:
- $text = wfMessage( 'categorypage' );
- break;
- default:
- $text = wfMessage( 'articlepage' );
- }
- } else {
- $link = $title->getTalkPage();
- $text = wfMessage( 'talkpage' );
- }
-
- $s = Linker::link( $link, $text->text(), array(), array(), $linkOptions );
-
- return $s;
- }
-
- function commentLink() {
- global $wgOut;
-
- $title = $this->getSkin()->getTitle();
- if ( $title->isSpecialPage() ) {
- return '';
- }
-
- # __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() ) {
- $title = $title->getTalkPage();
- }
-
- return Linker::linkKnown(
- $title,
- wfMessage( 'postcomment' )->text(),
- array(),
- array(
- 'action' => 'edit',
- 'section' => 'new'
- )
- );
- }
-
- function getUploadLink() {
- global $wgUploadNavigationUrl;
-
- if ( $wgUploadNavigationUrl ) {
- # Using an empty class attribute to avoid automatic setting of "external" class
- return Linker::makeExternalLink( $wgUploadNavigationUrl,
- wfMessage( 'upload' )->escaped(),
- false, null, array( 'class' => '' ) );
- } else {
- return Linker::linkKnown(
- SpecialPage::getTitleFor( 'Upload' ),
- wfMessage( 'upload' )->escaped()
- );
- }
- }
-
- function nameAndLogin() {
- global $wgUser, $wgLang, $wgRequest;
-
- $returnTo = $this->getSkin()->getTitle();
- $ret = '';
-
- if ( $wgUser->isAnon() ) {
- if ( $this->getSkin()->showIPinHeader() ) {
- $name = $wgRequest->getIP();
-
- $talkLink = Linker::link( $wgUser->getTalkPage(),
- $wgLang->getNsText( NS_TALK ) );
- $talkLink = wfMessage( 'parentheses' )->rawParams( $talkLink )->escaped();
-
- $ret .= "$name $talkLink";
- } else {
- $ret .= wfMessage( 'notloggedin' )->text();
- }
-
- $query = array();
-
- if ( !$returnTo->isSpecial( 'Userlogout' ) ) {
- $query['returnto'] = $returnTo->getPrefixedDBkey();
- }
-
- $loginlink = $wgUser->isAllowed( 'createaccount' )
- ? 'nav-login-createaccount'
- : 'login';
- $ret .= "\n<br />" . Linker::link(
- SpecialPage::getTitleFor( 'Userlogin' ),
- wfMessage( $loginlink )->text(), array(), $query
- );
- } else {
- $talkLink = Linker::link( $wgUser->getTalkPage(),
- $wgLang->getNsText( NS_TALK ) );
- $talkLink = wfMessage( 'parentheses' )->rawParams( $talkLink )->escaped();
-
- $ret .= Linker::link( $wgUser->getUserPage(),
- htmlspecialchars( $wgUser->getName() ) );
- $ret .= " $talkLink<br />";
- $ret .= $wgLang->pipeList( array(
- Linker::link(
- SpecialPage::getTitleFor( 'Userlogout' ), wfMessage( 'logout' )->text(),
- array(), array( 'returnto' => $returnTo->getPrefixedDBkey() )
- ),
- Linker::specialLink( 'Preferences' ),
- ) );
- }
-
- $ret = $wgLang->pipeList( array(
- $ret,
- Linker::link(
- Title::newFromText( wfMessage( 'helppage' )->inContentLanguage()->text() ),
- wfMessage( 'help' )->text()
- ),
- ) );
-
- return $ret;
- }
-}
diff --git a/includes/SkinTemplate.php b/includes/SkinTemplate.php
index 59e1ccff..53f11998 100644
--- a/includes/SkinTemplate.php
+++ b/includes/SkinTemplate.php
@@ -43,7 +43,7 @@ class MediaWiki_I18N {
$value = wfMessage( $value )->text();
// interpolate variables
$m = array();
- while( preg_match( '/\$([0-9]*?)/sm', $value, $m ) ) {
+ while ( preg_match( '/\$([0-9]*?)/sm', $value, $m ) ) {
list( $src, $var ) = $m;
wfSuppressWarnings();
$varValue = $this->_context[$var];
@@ -123,38 +123,61 @@ class SkinTemplate extends Skin {
}
/**
- * initialize various variables and generate the template
+ * Generates array of language links for the current page
*
- * @param $out OutputPage
+ * @return array
+ * @public
*/
- function outputPage( OutputPage $out=null ) {
- global $wgContLang;
- global $wgScript, $wgStylePath;
- global $wgMimeType, $wgJsMimeType;
- global $wgXhtmlDefaultNamespace, $wgXhtmlNamespaces, $wgHtml5Version;
- global $wgDisableCounters, $wgSitename, $wgLogo, $wgHideInterlanguageLinks;
- global $wgMaxCredits, $wgShowCreditsIfMax;
- global $wgPageShowWatchingUsers;
- global $wgArticlePath, $wgScriptPath, $wgServer;
+ public function getLanguages() {
+ global $wgHideInterlanguageLinks;
+ $out = $this->getOutput();
+ $userLang = $this->getLanguage();
- wfProfileIn( __METHOD__ );
- Profiler::instance()->setTemplated( true );
+ # Language links
+ $language_urls = array();
- $oldContext = null;
- if ( $out !== null ) {
- // @todo Add wfDeprecated in 1.20
- $oldContext = $this->getContext();
- $this->setContext( $out->getContext() );
+ if ( !$wgHideInterlanguageLinks ) {
+ 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 = $languageLinkText;
+ } else {
+ $ilLangName = $this->formatLanguageName( $ilLangName );
+ }
+
+ // CLDR extension or similar is required to localize the language name;
+ // otherwise we'll end up with the autonym again.
+ $ilLangLocalName = Language::fetchLanguageName( $ilInterwikiCode, $userLang->getCode() );
+
+ $language_urls[] = array(
+ 'href' => $languageLinkTitle->getFullURL(),
+ 'text' => $ilLangName,
+ 'title' => wfMessage( 'interlanguage-link-title', $languageLinkTitle->getText(), $ilLangLocalName )->text(),
+ 'class' => $class,
+ 'lang' => wfBCP47( $ilInterwikiCode ),
+ 'hreflang' => wfBCP47( $ilInterwikiCode ),
+ );
+ }
+ }
}
+ return $language_urls;
+ }
+
+ protected function setupTemplateForOutput() {
+ wfProfileIn( __METHOD__ );
- $out = $this->getOutput();
$request = $this->getRequest();
$user = $this->getUser();
$title = $this->getTitle();
wfProfileIn( __METHOD__ . '-init' );
- $this->initPage( $out );
-
$tpl = $this->setupTemplate( $this->template, 'skins' );
wfProfileOut( __METHOD__ . '-init' );
@@ -183,6 +206,47 @@ class SkinTemplate extends Skin {
wfProfileOut( __METHOD__ . '-stuff' );
+ wfProfileOut( __METHOD__ );
+
+ return $tpl;
+ }
+
+ /**
+ * initialize various variables and generate the template
+ *
+ * @param $out OutputPage
+ */
+ function outputPage( OutputPage $out = null ) {
+ global $wgContLang;
+ global $wgScript, $wgStylePath;
+ global $wgMimeType, $wgJsMimeType;
+ global $wgXhtmlNamespaces, $wgHtml5Version;
+ global $wgDisableCounters, $wgSitename, $wgLogo;
+ global $wgMaxCredits, $wgShowCreditsIfMax;
+ global $wgPageShowWatchingUsers;
+ global $wgArticlePath, $wgScriptPath, $wgServer;
+
+ wfProfileIn( __METHOD__ );
+ Profiler::instance()->setTemplated( true );
+
+ $oldContext = null;
+ if ( $out !== null ) {
+ // @todo Add wfDeprecated in 1.20
+ $oldContext = $this->getContext();
+ $this->setContext( $out->getContext() );
+ }
+
+ $out = $this->getOutput();
+ $request = $this->getRequest();
+ $user = $this->getUser();
+ $title = $this->getTitle();
+
+ wfProfileIn( __METHOD__ . '-init' );
+ $this->initPage( $out );
+ wfProfileOut( __METHOD__ . '-init' );
+
+ $tpl = $this->setupTemplateForOutput();
+
wfProfileIn( __METHOD__ . '-stuff-head' );
if ( !$this->useHeadElement ) {
$tpl->set( 'pagecss', false );
@@ -193,7 +257,7 @@ class SkinTemplate extends Skin {
$tpl->set( 'jsvarurl', false );
- $tpl->setRef( 'xhtmldefaultnamespace', $wgXhtmlDefaultNamespace );
+ $tpl->set( 'xhtmldefaultnamespace', 'http://www.w3.org/1999/xhtml' );
$tpl->set( 'xhtmlnamespaces', $wgXhtmlNamespaces );
$tpl->set( 'html5version', $wgHtml5Version );
$tpl->set( 'headlinks', $out->getHeadLinks() );
@@ -229,10 +293,11 @@ class SkinTemplate extends Skin {
}
$tpl->set( 'catlinks', $this->getCategories() );
- if( $out->isSyndicated() ) {
+ if ( $out->isSyndicated() ) {
$feeds = array();
- foreach( $out->getSyndicationLinks() as $format => $link ) {
+ foreach ( $out->getSyndicationLinks() as $format => $link ) {
$feeds[$format] = array(
+ // Messages: feed-atom, feed-rss
'text' => $this->msg( "feed-$format" )->text(),
'href' => $link
);
@@ -280,7 +345,7 @@ class SkinTemplate extends Skin {
$tpl->set( 'rtl', $userLang->isRTL() );
$tpl->set( 'capitalizeallnouns', $userLang->capitalizeAllNouns() ? ' capitalize-all-nouns' : '' );
- $tpl->set( 'showjumplinks', $user->getOption( 'showjumplinks' ) );
+ $tpl->set( 'showjumplinks', true ); // showjumplinks preference has been removed
$tpl->set( 'username', $this->loggedin ? $this->username : null );
$tpl->setRef( 'userpage', $this->userpage );
$tpl->setRef( 'userpageurl', $this->userpageUrlDetails['href'] );
@@ -409,36 +474,7 @@ class SkinTemplate extends Skin {
$out->mBodytext = Html::rawElement( 'div', $realBodyAttribs, $out->mBodytext );
$tpl->setRef( 'bodytext', $out->mBodytext );
- # Language links
- $language_urls = array();
-
- if ( !$wgHideInterlanguageLinks ) {
- 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 = $languageLinkText;
- } else {
- $ilLangName = $this->formatLanguageName( $ilLangName );
- }
-
- $language_urls[] = array(
- 'href' => $languageLinkTitle->getFullURL(),
- 'text' => $ilLangName,
- 'title' => $languageLinkTitle->getText(),
- 'class' => $class,
- 'lang' => $ilInterwikiCode,
- 'hreflang' => $ilInterwikiCode
- );
- }
- }
- }
+ $language_urls = $this->getLanguages();
if ( count( $language_urls ) ) {
$tpl->setRef( 'language_urls', $language_urls );
} else {
@@ -469,7 +505,7 @@ class SkinTemplate extends Skin {
$tpl->set( 'reporttime', wfReportTime() );
// original version by hansm
- if( !wfRunHooks( 'SkinTemplateOutputPageBeforeExec', array( &$this, &$tpl ) ) ) {
+ if ( !wfRunHooks( 'SkinTemplateOutputPageBeforeExec', array( &$this, &$tpl ) ) ) {
wfDebug( __METHOD__ . ": Hook SkinTemplateOutputPageBeforeExec broke outputPage execution!\n" );
}
@@ -502,6 +538,20 @@ class SkinTemplate extends Skin {
}
/**
+ * Get the HTML for the p-personal list
+ * @return string
+ */
+ public function getPersonalToolsList() {
+ $tpl = $this->setupTemplateForOutput();
+ $tpl->set( 'personal_urls', $this->buildPersonalUrls() );
+ $html = '';
+ foreach ( $tpl->getPersonalTools() as $key => $item ) {
+ $html .= $tpl->makeListItem( $key, $item );
+ }
+ return $html;
+ }
+
+ /**
* Format language name for use in sidebar interlanguage links list.
* By default it is capitalized.
*
@@ -544,8 +594,6 @@ class SkinTemplate extends Skin {
* @return array
*/
protected function buildPersonalUrls() {
- global $wgSecureLogin;
-
$title = $this->getTitle();
$request = $this->getRequest();
$pageurl = $title->getLocalURL();
@@ -568,17 +616,13 @@ class SkinTemplate extends Skin {
if ( strval( $page ) !== '' ) {
$a['returnto'] = $page;
$query = $request->getVal( 'returntoquery', $this->thisquery );
- if( $query != '' ) {
+ if ( $query != '' ) {
$a['returntoquery'] = $query;
}
}
- if ( $wgSecureLogin && $request->detectProtocol() === 'https' ) {
- $a['wpStickHTTPS'] = true;
- }
-
$returnto = wfArrayToCgi( $a );
- if( $this->loggedin ) {
+ if ( $this->loggedin ) {
$personal_urls['userpage'] = array(
'text' => $this->username,
'href' => &$this->userpageUrlDetails['href'],
@@ -599,12 +643,15 @@ class SkinTemplate extends Skin {
'href' => $href,
'active' => ( $href == $pageurl )
);
- $href = self::makeSpecialUrl( 'Watchlist' );
- $personal_urls['watchlist'] = array(
- 'text' => $this->msg( 'mywatchlist' )->text(),
- 'href' => $href,
- 'active' => ( $href == $pageurl )
- );
+
+ if ( $this->getUser()->isAllowed( 'viewmywatchlist' ) ) {
+ $href = self::makeSpecialUrl( 'Watchlist' );
+ $personal_urls['watchlist'] = array(
+ 'text' => $this->msg( 'mywatchlist' )->text(),
+ 'href' => $href,
+ 'active' => ( $href == $pageurl )
+ );
+ }
# We need to do an explicit check for Special:Contributions, as we
# have to match both the title, and the target, which could come
@@ -614,7 +661,7 @@ class SkinTemplate extends Skin {
# thickens, because the Title object is altered for special pages,
# so it doesn't contain the original alias-with-subpage.
$origTitle = Title::newFromText( $request->getText( 'title' ) );
- if( $origTitle instanceof Title && $origTitle->isSpecialPage() ) {
+ if ( $origTitle instanceof Title && $origTitle->isSpecialPage() ) {
list( $spName, $spPar ) = SpecialPageFactory::resolveAlias( $origTitle->getText() );
$active = $spName == 'Contributions'
&& ( ( $spPar && $spPar == $this->username )
@@ -645,24 +692,19 @@ class SkinTemplate extends Skin {
: 'login';
$is_signup = $request->getText( 'type' ) == 'signup';
- # anonlogin & login are the same
- $proto = $wgSecureLogin ? PROTO_HTTPS : null;
-
$login_id = $this->showIPinHeader() ? 'anonlogin' : 'login';
$login_url = array(
'text' => $this->msg( $loginlink )->text(),
- 'href' => self::makeSpecialUrl( 'Userlogin', $returnto, $proto ),
+ 'href' => self::makeSpecialUrl( 'Userlogin', $returnto ),
'active' => $title->isSpecial( 'Userlogin' ) && ( $loginlink == 'nav-login-createaccount' || !$is_signup ),
- 'class' => $wgSecureLogin ? 'link-https' : ''
);
$createaccount_url = array(
'text' => $this->msg( 'createaccount' )->text(),
- 'href' => self::makeSpecialUrl( 'Userlogin', "$returnto&type=signup", $proto ),
+ 'href' => self::makeSpecialUrl( 'Userlogin', "$returnto&type=signup" ),
'active' => $title->isSpecial( 'Userlogin' ) && $is_signup,
- 'class' => $wgSecureLogin ? 'link-https' : ''
);
- if( $this->showIPinHeader() ) {
+ if ( $this->showIPinHeader() ) {
$href = &$this->userpageUrlDetails['href'];
$personal_urls['anonuserpage'] = array(
'text' => $this->username,
@@ -705,10 +747,10 @@ class SkinTemplate extends Skin {
*/
function tabAction( $title, $message, $selected, $query = '', $checkEdit = false ) {
$classes = array();
- if( $selected ) {
+ if ( $selected ) {
$classes[] = 'selected';
}
- if( $checkEdit && !$title->isKnown() ) {
+ if ( $checkEdit && !$title->isKnown() ) {
$classes[] = 'new';
if ( $query !== '' ) {
$query = 'action=edit&redlink=1&' . $query;
@@ -733,7 +775,7 @@ class SkinTemplate extends Skin {
}
$result = array();
- if( !wfRunHooks( 'SkinTemplateTabAction', array( &$this,
+ if ( !wfRunHooks( 'SkinTemplateTabAction', array( &$this,
$title, $message, $selected, $checkEdit,
&$classes, &$query, &$text, &$result ) ) ) {
return $result;
@@ -742,13 +784,13 @@ class SkinTemplate extends Skin {
return array(
'class' => implode( ' ', $classes ),
'text' => $text,
- 'href' => $title->getLocalUrl( $query ),
+ 'href' => $title->getLocalURL( $query ),
'primary' => true );
}
function makeTalkUrlDetails( $name, $urlaction = '' ) {
$title = Title::newFromText( $name );
- if( !is_object( $title ) ) {
+ if ( !is_object( $title ) ) {
throw new MWException( __METHOD__ . " given invalid pagename $name" );
}
$title = $title->getTalkPage();
@@ -761,7 +803,7 @@ class SkinTemplate extends Skin {
function makeArticleUrlDetails( $name, $urlaction = '' ) {
$title = Title::newFromText( $name );
- $title= $title->getSubjectPage();
+ $title = $title->getSubjectPage();
self::checkTitle( $title, $name );
return array(
'href' => $title->getLocalURL( $urlaction ),
@@ -831,7 +873,7 @@ class SkinTemplate extends Skin {
wfRunHooks( 'SkinTemplatePreventOtherActiveTabs', array( &$this, &$preventActiveTabs ) );
// Checks if page is some kind of content
- if( $title->canExist() ) {
+ if ( $title->canExist() ) {
// Gets page objects for the related namespaces
$subjectPage = $title->getSubjectPage();
$talkPage = $title->getTalkPage();
@@ -978,7 +1020,7 @@ class SkinTemplate extends Skin {
wfProfileOut( __METHOD__ . '-live' );
// Checks if the user is logged in
- if ( $this->loggedin ) {
+ if ( $this->loggedin && $user->isAllowedAll( 'viewmywatchlist', 'editmywatchlist' ) ) {
/**
* The following actions use messages which, if made particular to
* the any specific skins, would break the Ajax code which makes this
@@ -1018,21 +1060,16 @@ class SkinTemplate extends Skin {
$params = array();
}
// Loops over each variant
- foreach( $variants as $code ) {
+ foreach ( $variants as $code ) {
// Gets variant name from language code
$varname = $pageLang->getVariantname( $code );
- // Checks if the variant is marked as disabled
- if( $varname == 'disable' ) {
- // Skips this variant
- continue;
- }
// Appends variant link
$content_navigation['variants'][] = array(
'class' => ( $code == $preferred ) ? 'selected' : false,
'text' => $varname,
'href' => $title->getLocalURL( array( 'variant' => $code ) + $params ),
- 'lang' => $code,
- 'hreflang' => $code
+ 'lang' => wfBCP47( $code ),
+ 'hreflang' => wfBCP47( $code ),
);
}
}
@@ -1156,9 +1193,9 @@ class SkinTemplate extends Skin {
$nav_urls = array();
$nav_urls['mainpage'] = array( 'href' => self::makeMainPageUrl() );
- if( $wgUploadNavigationUrl ) {
+ if ( $wgUploadNavigationUrl ) {
$nav_urls['upload'] = array( 'href' => $wgUploadNavigationUrl );
- } elseif( UploadBase::isEnabled() && UploadBase::isAllowed( $this->getUser() ) === true ) {
+ } elseif ( UploadBase::isEnabled() && UploadBase::isAllowed( $this->getUser() ) === true ) {
$nav_urls['upload'] = array( 'href' => self::makeSpecialUrl( 'Upload' ) );
} else {
$nav_urls['upload'] = false;
@@ -1178,15 +1215,15 @@ class SkinTemplate extends Skin {
// A print stylesheet is attached to all pages, but nobody ever
// figures that out. :) Add a link...
- if ( $out->isArticle() ) {
- if ( !$out->isPrintable() ) {
- $nav_urls['print'] = array(
- 'text' => $this->msg( 'printableversion' )->text(),
- 'href' => $this->getTitle()->getLocalURL(
- $request->appendQueryValue( 'printable', 'yes', true ) )
- );
- }
+ if ( !$out->isPrintable() && ( $out->isArticle() || $this->getTitle()->isSpecialPage() ) ) {
+ $nav_urls['print'] = array(
+ 'text' => $this->msg( 'printableversion' )->text(),
+ 'href' => $this->getTitle()->getLocalURL(
+ $request->appendQueryValue( 'printable', 'yes', true ) )
+ );
+ }
+ if ( $out->isArticle() ) {
// Also add a "permalink" while we're at it
$revid = $this->getRevisionId();
if ( $revid ) {
@@ -1203,7 +1240,7 @@ class SkinTemplate extends Skin {
if ( $out->isArticleRelated() ) {
$nav_urls['whatlinkshere'] = array(
- 'href' => SpecialPage::getTitleFor( 'Whatlinkshere', $this->thispage )->getLocalUrl()
+ 'href' => SpecialPage::getTitleFor( 'Whatlinkshere', $this->thispage )->getLocalURL()
);
$nav_urls['info'] = array(
@@ -1213,7 +1250,7 @@ class SkinTemplate extends Skin {
if ( $this->getTitle()->getArticleID() ) {
$nav_urls['recentchangeslinked'] = array(
- 'href' => SpecialPage::getTitleFor( 'Recentchangeslinked', $this->thispage )->getLocalUrl()
+ 'href' => SpecialPage::getTitleFor( 'Recentchangeslinked', $this->thispage )->getLocalURL()
);
}
}
@@ -1243,11 +1280,14 @@ class SkinTemplate extends Skin {
);
}
- $sur = new UserrightsPage;
- if ( $sur->userCanExecute( $this->getUser() ) ) {
- $nav_urls['userrights'] = array(
- 'href' => self::makeSpecialUrlSubpage( 'Userrights', $rootUser )
- );
+ if ( !$user->isAnon() ) {
+ $sur = new UserrightsPage;
+ $sur->setContext( $this->getContext() );
+ if ( $sur->userCanExecute( $this->getUser() ) ) {
+ $nav_urls['userrights'] = array(
+ 'href' => self::makeSpecialUrlSubpage( 'Userrights', $rootUser )
+ );
+ }
}
}
@@ -1263,10 +1303,6 @@ class SkinTemplate extends Skin {
function getNameSpaceKey() {
return $this->getTitle()->getNamespaceKey();
}
-
- public function commonPrintStylesheet() {
- return false;
- }
}
/**
@@ -1293,6 +1329,21 @@ abstract class QuickTemplate {
}
/**
+ * Gets the template data requested
+ * @since 1.22
+ * @param string $name Key for the data
+ * @param mixed $default Optional default (or null)
+ * @return mixed The value of the data requested or the deafult
+ */
+ public function get( $name, $default = null ) {
+ if ( isset( $this->data[$name] ) ) {
+ return $this->data[$name];
+ } else {
+ return $default;
+ }
+ }
+
+ /**
* @param $name
* @param $value
*/
@@ -1322,8 +1373,10 @@ abstract class QuickTemplate {
/**
* @private
+ * @deprecated since 1.21; use Xml::encodeJsVar() or Xml::encodeJsCall() instead
*/
function jstext( $str ) {
+ wfDeprecated( __METHOD__, '1.21' );
echo Xml::escapeJsString( $this->data[$str] );
}
@@ -1492,7 +1545,7 @@ abstract class BaseTemplate extends QuickTemplate {
*/
function getPersonalTools() {
$personal_tools = array();
- foreach ( $this->data['personal_urls'] as $key => $plink ) {
+ foreach ( $this->get( 'personal_urls' ) as $key => $plink ) {
# The class on a personal_urls item is meant to go on the <a> instead
# of the <li> so we have to use a single item "links" array instead
# of using most of the personal_url's keys directly.
@@ -1506,8 +1559,9 @@ abstract class BaseTemplate extends QuickTemplate {
$ptool['active'] = $plink['active'];
}
foreach ( array( 'href', 'class', 'text' ) as $k ) {
- if ( isset( $plink[$k] ) )
+ if ( isset( $plink[$k] ) ) {
$ptool['links'][0][$k] = $plink[$k];
+ }
}
$personal_tools[$key] = $ptool;
}
@@ -1546,39 +1600,39 @@ abstract class BaseTemplate extends QuickTemplate {
case 'SEARCH':
// Search is a special case, skins should custom implement this
$boxes[$boxName] = array(
- 'id' => 'p-search',
- 'header' => $this->getMsg( 'search' )->text(),
+ 'id' => 'p-search',
+ 'header' => $this->getMsg( 'search' )->text(),
'generated' => false,
- 'content' => true,
+ 'content' => true,
);
break;
case 'TOOLBOX':
$msgObj = $this->getMsg( 'toolbox' );
$boxes[$boxName] = array(
- 'id' => 'p-tb',
- 'header' => $msgObj->exists() ? $msgObj->text() : 'toolbox',
+ 'id' => 'p-tb',
+ 'header' => $msgObj->exists() ? $msgObj->text() : 'toolbox',
'generated' => false,
- 'content' => $this->getToolbox(),
+ 'content' => $this->getToolbox(),
);
break;
case 'LANGUAGES':
if ( $this->data['language_urls'] ) {
$msgObj = $this->getMsg( 'otherlanguages' );
$boxes[$boxName] = array(
- 'id' => 'p-lang',
- 'header' => $msgObj->exists() ? $msgObj->text() : 'otherlanguages',
+ 'id' => 'p-lang',
+ 'header' => $msgObj->exists() ? $msgObj->text() : 'otherlanguages',
'generated' => false,
- 'content' => $this->data['language_urls'],
+ 'content' => $this->data['language_urls'],
);
}
break;
default:
$msgObj = $this->getMsg( $boxName );
$boxes[$boxName] = array(
- 'id' => "p-$boxName",
- 'header' => $msgObj->exists() ? $msgObj->text() : $boxName,
+ 'id' => "p-$boxName",
+ 'header' => $msgObj->exists() ? $msgObj->text() : $boxName,
'generated' => true,
- 'content' => $content,
+ 'content' => $content,
);
break;
}
@@ -1618,10 +1672,10 @@ abstract class BaseTemplate extends QuickTemplate {
} else {
if ( $hookContents ) {
$boxes['TOOLBOXEND'] = array(
- 'id' => 'p-toolboxend',
- 'header' => $boxes['TOOLBOX']['header'],
+ 'id' => 'p-toolboxend',
+ 'header' => $boxes['TOOLBOX']['header'],
'generated' => false,
- 'content' => "<ul>{$hookContents}</ul>",
+ 'content' => "<ul>{$hookContents}</ul>",
);
// HACK: Make sure that TOOLBOXEND is sorted next to TOOLBOX
$boxes2 = array();
@@ -1663,6 +1717,10 @@ abstract class BaseTemplate extends QuickTemplate {
* on the link) is present it will be used to generate a tooltip and
* accesskey for the link.
*
+ * The keys "context" and "primary" are ignored; these keys are used
+ * internally by skins and are not supposed to be included in the HTML
+ * output.
+ *
* If you don't want an accesskey, set $item['tooltiponly'] = true;
*
* @param array $options can be used to affect the output of a link.
@@ -1703,7 +1761,7 @@ abstract class BaseTemplate extends QuickTemplate {
if ( isset( $item['href'] ) || isset( $options['link-fallback'] ) ) {
$attrs = $item;
- foreach ( array( 'single-id', 'text', 'msg', 'tooltiponly' ) as $k ) {
+ foreach ( array( 'single-id', 'text', 'msg', 'tooltiponly', 'context', 'primary' ) as $k ) {
unset( $attrs[$k] );
}
@@ -1807,14 +1865,15 @@ abstract class BaseTemplate extends QuickTemplate {
$realAttrs = array(
'type' => 'search',
'name' => 'search',
- 'value' => isset( $this->data['search'] ) ? $this->data['search'] : '',
+ 'placeholder' => wfMessage( 'searchsuggest-search' )->text(),
+ 'value' => $this->get( 'search', '' ),
);
$realAttrs = array_merge( $realAttrs, Linker::tooltipAndAccesskeyAttribs( 'search' ), $attrs );
return Html::element( 'input', $realAttrs );
}
function makeSearchButton( $mode, $attrs = array() ) {
- switch( $mode ) {
+ switch ( $mode ) {
case 'go':
case 'fulltext':
$realAttrs = array(
@@ -1866,14 +1925,14 @@ abstract class BaseTemplate extends QuickTemplate {
* @return array|mixed
*/
function getFooterLinks( $option = null ) {
- $footerlinks = $this->data['footerlinks'];
+ $footerlinks = $this->get( 'footerlinks' );
// Reduce footer links down to only those which are being used
$validFooterLinks = array();
- foreach( $footerlinks as $category => $links ) {
+ foreach ( $footerlinks as $category => $links ) {
$validFooterLinks[$category] = array();
- foreach( $links as $link ) {
- if( isset( $this->data[$link] ) && $this->data[$link] ) {
+ foreach ( $links as $link ) {
+ if ( isset( $this->data[$link] ) && $this->data[$link] ) {
$validFooterLinks[$category][] = $link;
}
}
@@ -1906,7 +1965,7 @@ abstract class BaseTemplate extends QuickTemplate {
*/
function getFooterIcons( $option = null ) {
// Generate additional footer icons
- $footericons = $this->data['footericons'];
+ $footericons = $this->get( 'footericons' );
if ( $option == 'icononly' ) {
// Unset any icons which don't have an image
@@ -1939,9 +1998,10 @@ abstract class BaseTemplate extends QuickTemplate {
* body and html tags.
*/
function printTrail() { ?>
+<?php echo MWDebug::getDebugHTML( $this->getSkin()->getContext() ); ?>
<?php $this->html( 'bottomscripts' ); /* JS call to runBodyOnloadHook */ ?>
<?php $this->html( 'reporttime' ) ?>
-<?php echo MWDebug::getDebugHTML( $this->getSkin()->getContext() );
+<?php
}
}
diff --git a/includes/SpecialPage.php b/includes/SpecialPage.php
index c32738f8..a6195fc4 100644
--- a/includes/SpecialPage.php
+++ b/includes/SpecialPage.php
@@ -27,7 +27,6 @@
* @ingroup SpecialPage
*/
class SpecialPage {
-
// The canonical name of this special page
// Also used for the default <h1> heading, @see getDescription()
protected $mName;
@@ -305,12 +304,14 @@ class SpecialPage {
* If you override execute(), you can recover the default behavior with userCanExecute()
* and displayRestrictionError()
*
- * @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 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
+ * @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 Callback|Bool $function Function called by execute(). By default
+ * it is constructed from $name
+ * @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,
@@ -322,12 +323,14 @@ class SpecialPage {
/**
* Do the real work for the constructor, mainly so __call() can intercept
* calls to SpecialPage()
- * @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 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
+ * @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 Callback|Bool $function Function called by execute(). By default
+ * it is constructed from $name
+ * @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;
@@ -445,7 +448,10 @@ class SpecialPage {
* @return Mixed
* @deprecated since 1.18
*/
- function name( $x = null ) { wfDeprecated( __METHOD__, '1.18' ); return wfSetVar( $this->mName, $x ); }
+ function name( $x = null ) {
+ wfDeprecated( __METHOD__, '1.18' );
+ return wfSetVar( $this->mName, $x );
+ }
/**
* These mutators are very evil, as the relevant variables should not mutate. So
@@ -454,7 +460,10 @@ class SpecialPage {
* @return Mixed
* @deprecated since 1.18
*/
- function restriction( $x = null ) { wfDeprecated( __METHOD__, '1.18' ); return wfSetVar( $this->mRestriction, $x ); }
+ function restriction( $x = null ) {
+ wfDeprecated( __METHOD__, '1.18' );
+ return wfSetVar( $this->mRestriction, $x );
+ }
/**
* These mutators are very evil, as the relevant variables should not mutate. So
@@ -463,7 +472,10 @@ class SpecialPage {
* @return Mixed
* @deprecated since 1.18
*/
- function func( $x = null ) { wfDeprecated( __METHOD__, '1.18' ); return wfSetVar( $this->mFunction, $x ); }
+ function func( $x = null ) {
+ wfDeprecated( __METHOD__, '1.18' );
+ return wfSetVar( $this->mFunction, $x );
+ }
/**
* These mutators are very evil, as the relevant variables should not mutate. So
@@ -472,7 +484,10 @@ class SpecialPage {
* @return Mixed
* @deprecated since 1.18
*/
- function file( $x = null ) { wfDeprecated( __METHOD__, '1.18' ); return wfSetVar( $this->mFile, $x ); }
+ function file( $x = null ) {
+ wfDeprecated( __METHOD__, '1.18' );
+ return wfSetVar( $this->mFile, $x );
+ }
/**
* These mutators are very evil, as the relevant variables should not mutate. So
@@ -481,7 +496,10 @@ class SpecialPage {
* @return Mixed
* @deprecated since 1.18
*/
- function includable( $x = null ) { wfDeprecated( __METHOD__, '1.18' ); return wfSetVar( $this->mIncludable, $x ); }
+ function includable( $x = null ) {
+ wfDeprecated( __METHOD__, '1.18' );
+ return wfSetVar( $this->mIncludable, $x );
+ }
/**
* Whether the special page is being evaluated via transclusion
@@ -535,7 +553,7 @@ class SpecialPage {
* pages?
*/
public function isRestricted() {
- // DWIM: If all anons can do something, then it is not restricted
+ // DWIM: If anons can do something, then it is not restricted
return $this->mRestriction != '' && !User::groupHasPermission( '*', $this->mRestriction );
}
@@ -661,7 +679,7 @@ class SpecialPage {
$func = $this->mFunction;
// only load file if the function does not exist
if ( !is_callable( $func ) && $this->mFile ) {
- require_once( $this->mFile );
+ require_once $this->mFile;
}
$this->outputHeader();
call_user_func( $func, $subPage, $this );
@@ -734,7 +752,8 @@ class SpecialPage {
if ( $this->mContext instanceof IContextSource ) {
return $this->mContext;
} else {
- wfDebug( __METHOD__ . " called and \$mContext is null. Return RequestContext::getMain(); for sanity\n" );
+ wfDebug( __METHOD__ . " called and \$mContext is null. " .
+ "Return RequestContext::getMain(); for sanity\n" );
return RequestContext::getMain();
}
}
@@ -782,7 +801,7 @@ class SpecialPage {
/**
* Shortcut to get user's language
*
- * @deprecated 1.19 Use getLanguage instead
+ * @deprecated since 1.19 Use getLanguage instead
* @return Language
* @since 1.18
*/
@@ -842,11 +861,11 @@ class SpecialPage {
protected function addFeedLinks( $params ) {
global $wgFeedClasses;
- $feedTemplate = wfScript( 'api' ) . '?';
+ $feedTemplate = wfScript( 'api' );
foreach ( $wgFeedClasses as $format => $class ) {
$theseParams = $params + array( 'feedformat' => $format );
- $url = $feedTemplate . wfArrayToCgi( $theseParams );
+ $url = wfAppendQuery( $feedTemplate, $theseParams );
$this->getOutput()->addFeedLink( $format, $url );
}
}
@@ -909,6 +928,11 @@ class SpecialPage {
* a new structure for SpecialPages
*/
abstract class FormSpecialPage extends SpecialPage {
+ /**
+ * The sub-page of the special page.
+ * @var string
+ */
+ protected $par = null;
/**
* Get an HTMLForm descriptor array
@@ -917,17 +941,27 @@ abstract class FormSpecialPage extends SpecialPage {
abstract protected function getFormFields();
/**
- * Add pre- or post-text to the form
+ * Add pre-text to the form
* @return String HTML which will be sent to $form->addPreText()
*/
- protected function preText() { return ''; }
- protected function postText() { return ''; }
+ protected function preText() {
+ return '';
+ }
+
+ /**
+ * Add post-text to the form
+ * @return String HTML which will be sent to $form->addPostText()
+ */
+ protected function postText() {
+ return '';
+ }
/**
* Play with the HTMLForm if you need to more substantially
* @param $form HTMLForm
*/
- protected function alterForm( HTMLForm $form ) {}
+ protected function alterForm( HTMLForm $form ) {
+ }
/**
* Get message prefix for HTMLForm
@@ -948,9 +982,18 @@ abstract class FormSpecialPage extends SpecialPage {
$form = new HTMLForm( $this->fields, $this->getContext(), $this->getMessagePrefix() );
$form->setSubmitCallback( array( $this, 'onSubmit' ) );
- $form->setWrapperLegend( $this->msg( $this->getMessagePrefix() . '-legend' ) );
- $form->addHeaderText(
- $this->msg( $this->getMessagePrefix() . '-text' )->parseAsBlock() );
+ // If the form is a compact vertical form, then don't output this ugly
+ // fieldset surrounding it.
+ // XXX Special pages can setDisplayFormat to 'vform' in alterForm(), but that
+ // is called after this.
+ if ( !$form->isVForm() ) {
+ $form->setWrapperLegendMsg( $this->getMessagePrefix() . '-legend' );
+ }
+
+ $headerMsg = $this->msg( $this->getMessagePrefix() . '-text' );
+ if ( !$headerMsg->isDisabled() ) {
+ $form->addHeaderText( $headerMsg->parseAsBlock() );
+ }
// Retain query parameters (uselang etc)
$params = array_diff_key(
@@ -977,8 +1020,10 @@ abstract class FormSpecialPage extends SpecialPage {
/**
* Do something exciting on successful processing of the form, most likely to show a
* confirmation message
+ * @since 1.22 Default is to do nothing
*/
- abstract public function onSuccess();
+ public function onSuccess() {
+ }
/**
* Basic SpecialPage workflow: get a form, send it to the user; get some data back,
@@ -1000,9 +1045,11 @@ abstract class FormSpecialPage extends SpecialPage {
/**
* Maybe do something interesting with the subpage parameter
- * @param $par String
+ * @param string $par
*/
- protected function setParameter( $par ) {}
+ protected function setParameter( $par ) {
+ $this->par = $par;
+ }
/**
* Called from execute() to check if the given user can perform this action.
@@ -1090,17 +1137,16 @@ abstract class RedirectSpecialPage extends UnlistedSpecialPage {
$query = $this->getRedirectQuery();
// Redirect to a page title with possible query parameters
if ( $redirect instanceof Title ) {
- $url = $redirect->getFullUrl( $query );
+ $url = $redirect->getFullURL( $query );
$this->getOutput()->redirect( $url );
return $redirect;
- // Redirect to index.php with query parameters
} elseif ( $redirect === true ) {
- global $wgScript;
- $url = $wgScript . '?' . wfArrayToCgi( $query );
+ // Redirect to index.php with query parameters
+ $url = wfAppendQuery( wfScript( 'index' ), $query );
$this->getOutput()->redirect( $url );
return $redirect;
} else {
- $class = __CLASS__;
+ $class = get_class( $this );
throw new MWException( "RedirectSpecialPage $class doesn't redirect!" );
}
}
@@ -1140,6 +1186,7 @@ abstract class RedirectSpecialPage extends UnlistedSpecialPage {
}
abstract class SpecialRedirectToSpecial extends RedirectSpecialPage {
+ // @todo FIXME: Visibility must be declared
var $redirName, $redirSubpage;
function __construct(
@@ -1186,7 +1233,16 @@ class SpecialListBots extends SpecialRedirectToSpecial {
*/
class SpecialCreateAccount extends SpecialRedirectToSpecial {
function __construct() {
- parent::__construct( 'CreateAccount', 'Userlogin', 'signup', array( 'uselang' ) );
+ parent::__construct( 'CreateAccount', 'Userlogin', 'signup', array( 'returnto', 'returntoquery', 'uselang' ) );
+ }
+
+ // No reason to hide this link on Special:Specialpages
+ public function isListed() {
+ return true;
+ }
+
+ protected function getGroupName() {
+ return 'login';
}
}
/**
@@ -1216,7 +1272,7 @@ class SpecialCreateAccount extends SpecialRedirectToSpecial {
* - limit, offset: Useful for linking to history of one's own user page or
* user talk page. For example, this would be a link to "the last edit to your
* user talk page in the year 2010":
- * http://en.wikipedia.org/w/index.php?title=Special:MyPage&offset=20110000000000&limit=1&action=history
+ * http://en.wikipedia.org/wiki/Special:MyPage?offset=20110000000000&limit=1&action=history
*
* - feed: would allow linking to the current user's RSS feed for their user
* talk page:
@@ -1270,9 +1326,9 @@ abstract class RedirectSpecialArticle extends RedirectSpecialPage {
'action',
'redirect', 'rdfrom',
# Options for preloaded edits
- 'preload', 'editintro', 'preloadtitle', 'summary',
+ 'preload', 'editintro', 'preloadtitle', 'summary', 'nosummary',
# Options for overriding user settings
- 'preview', 'internaledit', 'externaledit', 'mode',
+ 'preview', 'internaledit', 'externaledit', 'mode', 'minor', 'watchthis',
# Options for history/diffs
'section', 'oldid', 'diff', 'dir',
'limit', 'offset', 'feed',
@@ -1345,7 +1401,7 @@ class SpecialMycontributions extends RedirectSpecialPage {
class SpecialMyuploads extends RedirectSpecialPage {
function __construct() {
parent::__construct( 'Myuploads' );
- $this->mAllowedRedirectParams = array( 'limit' );
+ $this->mAllowedRedirectParams = array( 'limit', 'ilshowall', 'ilsearch' );
}
function getRedirect( $subpage ) {
@@ -1354,6 +1410,22 @@ class SpecialMyuploads extends RedirectSpecialPage {
}
/**
+ * Redirect Special:Listfiles?user=$wgUser&ilshowall=true
+ */
+class SpecialAllMyUploads extends RedirectSpecialPage {
+ function __construct() {
+ parent::__construct( 'AllMyUploads' );
+ $this->mAllowedRedirectParams = array( 'limit', 'ilsearch' );
+ }
+
+ function getRedirect( $subpage ) {
+ $this->mAddedRedirectParams['ilshowall'] = 1;
+ return SpecialPage::getTitleFor( 'Listfiles', $this->getUser()->getName() );
+ }
+}
+
+
+/**
* Redirect from Special:PermanentLink/### to index.php?oldid=###
*/
class SpecialPermanentLink extends RedirectSpecialPage {
diff --git a/includes/SpecialPageFactory.php b/includes/SpecialPageFactory.php
index 7368ab77..11edc8ac 100644
--- a/includes/SpecialPageFactory.php
+++ b/includes/SpecialPageFactory.php
@@ -78,7 +78,6 @@ class SpecialPageFactory {
'Allpages' => 'SpecialAllpages',
'Prefixindex' => 'SpecialPrefixindex',
'Categories' => 'SpecialCategories',
- 'Disambiguations' => 'DisambiguationsPage',
'Listredirects' => 'ListredirectsPage',
'PagesWithProp' => 'SpecialPagesWithProp',
@@ -94,6 +93,7 @@ class SpecialPageFactory {
'PasswordReset' => 'SpecialPasswordReset',
'DeletedContributions' => 'DeletedContributionsPage',
'Preferences' => 'SpecialPreferences',
+ 'ResetTokens' => 'SpecialResetTokens',
'Contributions' => 'SpecialContributions',
'Listgrouprights' => 'SpecialListGroupRights',
'Listusers' => 'SpecialListUsers',
@@ -130,6 +130,7 @@ class SpecialPageFactory {
// Redirecting special pages
'LinkSearch' => 'LinkSearchPage',
'Randompage' => 'Randompage',
+ 'RandomInCategory' => 'SpecialRandomInCategory',
'Randomredirect' => 'SpecialRandomredirect',
// High use pages
@@ -154,14 +155,15 @@ class SpecialPageFactory {
// Unlisted / redirects
'Blankpage' => 'SpecialBlankpage',
- 'Blockme' => 'SpecialBlockme',
'Emailuser' => 'SpecialEmailUser',
'Movepage' => 'MovePageForm',
'Mycontributions' => 'SpecialMycontributions',
'Mypage' => 'SpecialMypage',
'Mytalk' => 'SpecialMytalk',
'Myuploads' => 'SpecialMyuploads',
+ 'AllMyUploads' => 'SpecialAllMyUploads',
'PermanentLink' => 'SpecialPermanentLink',
+ 'Redirect' => 'SpecialRedirect',
'Revisiondelete' => 'SpecialRevisionDelete',
'Specialpages' => 'SpecialSpecialpages',
'Userlogout' => 'SpecialUserlogout',
@@ -200,7 +202,7 @@ class SpecialPageFactory {
self::$mList['ChangeEmail'] = 'SpecialChangeEmail';
}
- if( $wgEnableJavaScriptTest ) {
+ if ( $wgEnableJavaScriptTest ) {
self::$mList['JavaScriptTest'] = 'SpecialJavaScriptTest';
}
@@ -286,7 +288,7 @@ class SpecialPageFactory {
*
* @param $page Mixed: SpecialPage or string
* @param $group String
- * @deprecated 1.21 Override SpecialPage::getGroupName
+ * @deprecated since 1.21 Override SpecialPage::getGroupName
*/
public static function setGroup( $page, $group ) {
wfDeprecated( __METHOD__, '1.21' );
@@ -301,7 +303,7 @@ class SpecialPageFactory {
*
* @param $page SpecialPage
* @return String
- * @deprecated 1.21 Use SpecialPage::getFinalGroupName
+ * @deprecated since 1.21 Use SpecialPage::getFinalGroupName
*/
public static function getGroup( &$page ) {
wfDeprecated( __METHOD__, '1.21' );
@@ -464,9 +466,8 @@ class SpecialPageFactory {
if ( $name != $page->getLocalName() && !$context->getRequest()->wasPosted() ) {
$query = $context->getRequest()->getQueryValues();
unset( $query['title'] );
- $query = wfArrayToCgi( $query );
$title = $page->getTitle( $par );
- $url = $title->getFullUrl( $query );
+ $url = $title->getFullURL( $query );
$context->getOutput()->redirect( $url );
wfProfileOut( __METHOD__ );
return $title;
diff --git a/includes/SqlDataUpdate.php b/includes/SqlDataUpdate.php
index 79dcdc59..51188d85 100644
--- a/includes/SqlDataUpdate.php
+++ b/includes/SqlDataUpdate.php
@@ -56,7 +56,7 @@ abstract class SqlDataUpdate extends DataUpdate {
$this->mOptions = array( 'FOR UPDATE' );
}
- // @todo: get connection only when it's needed? make sure that doesn't break anything, especially transactions!
+ // @todo get connection only when it's needed? make sure that doesn't break anything, especially transactions!
$this->mDb = wfGetDB( DB_MASTER );
$this->mWithTransaction = $withTransaction;
diff --git a/includes/StatCounter.php b/includes/StatCounter.php
new file mode 100644
index 00000000..1373f3d5
--- /dev/null
+++ b/includes/StatCounter.php
@@ -0,0 +1,150 @@
+<?php
+/**
+ * @defgroup StatCounter StatCounter
+ *
+ * StatCounter is used to increment arbitrary keys for profiling reasons.
+ * The key/values are persisted in several possible ways (see $wgStatsMethod).
+ */
+
+/**
+ * Aggregator for wfIncrStats() that batches updates per request.
+ *
+ * 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 StatCounter
+ * @author Aaron Schulz
+ */
+
+/**
+ * Aggregator for wfIncrStats() that batches updates per request.
+ * This avoids spamming the collector many times for the same key.
+ *
+ * @ingroup StatCounter
+ */
+class StatCounter {
+ /** @var Array */
+ protected $deltas = array(); // (key => count)
+
+ protected function __construct() {}
+
+ /**
+ * @return StatCounter
+ */
+ public static function singleton() {
+ static $instance = null;
+ if ( !$instance ) {
+ $instance = new self();
+ }
+ return $instance;
+ }
+
+ /**
+ * Increment a key by delta $count
+ *
+ * @param string $key
+ * @param integer $count
+ * @return void
+ */
+ public function incr( $key, $count = 1 ) {
+ $this->deltas[$key] = isset( $this->deltas[$key] ) ? $this->deltas[$key] : 0;
+ $this->deltas[$key] += $count;
+ if ( PHP_SAPI === 'cli' ) {
+ $this->flush();
+ }
+ }
+
+ /**
+ * Flush all pending deltas to persistent storage
+ *
+ * @return void
+ */
+ public function flush() {
+ global $wgStatsMethod;
+
+ $deltas = array_filter( $this->deltas ); // remove 0 valued entries
+ if ( $wgStatsMethod === 'udp' ) {
+ $this->sendDeltasUDP( $deltas );
+ } elseif ( $wgStatsMethod === 'cache' ) {
+ $this->sendDeltasMemc( $deltas );
+ } else {
+ // disabled
+ }
+ $this->deltas = array();
+ }
+
+ /**
+ * @param array $deltas
+ * @return void
+ */
+ protected function sendDeltasUDP( array $deltas ) {
+ global $wgUDPProfilerHost, $wgUDPProfilerPort, $wgAggregateStatsID,
+ $wgStatsFormatString;
+
+ $id = strlen( $wgAggregateStatsID ) ? $wgAggregateStatsID : wfWikiID();
+
+ $lines = array();
+ foreach ( $deltas as $key => $count ) {
+ $lines[] = sprintf( $wgStatsFormatString, $id, $count, $key );
+ }
+
+ if ( count( $lines ) ) {
+ static $socket = null;
+ if ( !$socket ) {
+ $socket = socket_create( AF_INET, SOCK_DGRAM, SOL_UDP );
+ }
+ $packet = '';
+ $packets = array();
+ foreach ( $lines as $line ) {
+ if ( ( strlen( $packet ) + strlen( $line ) ) > 1450 ) {
+ $packets[] = $packet;
+ $packet = '';
+ }
+ $packet .= $line;
+ }
+ if ( $packet != '' ) {
+ $packets[] = $packet;
+ }
+ foreach ( $packets as $packet ) {
+ wfSuppressWarnings();
+ socket_sendto(
+ $socket,
+ $packet,
+ strlen( $packet ),
+ 0,
+ $wgUDPProfilerHost,
+ $wgUDPProfilerPort
+ );
+ wfRestoreWarnings();
+ }
+ }
+ }
+
+ /**
+ * @param array $deltas
+ * @return void
+ */
+ protected function sendDeltasMemc( array $deltas ) {
+ global $wgMemc;
+
+ foreach ( $deltas as $key => $count ) {
+ $ckey = wfMemcKey( 'stats', $key );
+ if ( $wgMemc->incr( $ckey, $count ) === null ) {
+ $wgMemc->add( $ckey, $count );
+ }
+ }
+ }
+}
diff --git a/includes/Status.php b/includes/Status.php
index 5d6236f7..928f8ebd 100644
--- a/includes/Status.php
+++ b/includes/Status.php
@@ -31,6 +31,11 @@
* An operation which is not OK should have errors so that the user can be
* informed as to what went wrong. Calling the fatal() function sets an error
* message and simultaneously switches off the OK flag.
+ *
+ * The recommended pattern for Status objects is to return a Status object
+ * unconditionally, i.e. both on success and on failure -- so that the
+ * developer of the calling code is reminded that the function can fail, and
+ * so that a lack of error-handling will be explicit.
*/
class Status {
var $ok = true;
@@ -183,15 +188,18 @@ class Status {
}
}
if ( count( $this->errors ) == 1 ) {
- $s = $this->getErrorMessage( $this->errors[0] );
+ $s = $this->getErrorMessage( $this->errors[0] )->plain();
if ( $shortContext ) {
$s = wfMessage( $shortContext, $s )->plain();
} elseif ( $longContext ) {
$s = wfMessage( $longContext, "* $s\n" )->plain();
}
} else {
- $s = '* '. implode( "\n* ",
- $this->getErrorMessageArray( $this->errors ) ) . "\n";
+ $errors = $this->getErrorMessageArray( $this->errors );
+ foreach ( $errors as &$error ) {
+ $error = $error->plain();
+ }
+ $s = '* ' . implode( "\n* ", $errors ) . "\n";
if ( $longContext ) {
$s = wfMessage( $longContext, $s )->plain();
} elseif ( $shortContext ) {
@@ -202,6 +210,56 @@ class Status {
}
/**
+ * Get the error list as a Message object
+ *
+ * @param string $shortContext a short enclosing context message name, to
+ * be used when there is a single error
+ * @param string $longContext a long enclosing context message name, for a list
+ * @return Message
+ */
+ function getMessage( $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" );
+ } else {
+ $this->fatal( 'internalerror_info',
+ __METHOD__ . ": Invalid result object: no error text but not OK\n" );
+ }
+ }
+ if ( count( $this->errors ) == 1 ) {
+ $s = $this->getErrorMessage( $this->errors[0] );
+ if ( $shortContext ) {
+ $s = wfMessage( $shortContext, $s );
+ } elseif ( $longContext ) {
+ $wrapper = new RawMessage( "* \$1\n" );
+ $wrapper->params( $s )->parse();
+ $s = wfMessage( $longContext, $wrapper );
+ }
+ } else {
+ $msgs = $this->getErrorMessageArray( $this->errors );
+ $msgCount = count( $msgs );
+
+ if ( $shortContext ) {
+ $msgCount++;
+ }
+
+ $wrapper = new RawMessage( '* $' . implode( "\n* \$", range( 1, $msgCount ) ) );
+ $s = $wrapper->params( $msgs )->parse();
+
+ if ( $longContext ) {
+ $s = wfMessage( $longContext, $wrapper );
+ } elseif ( $shortContext ) {
+ $wrapper = new RawMessage( "\n\$1\n", $wrapper );
+ $wrapper->parse();
+ $s = wfMessage( $shortContext, $wrapper );
+ }
+ }
+
+ return $s;
+ }
+
+ /**
* 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.
@@ -212,7 +270,7 @@ class Status {
*/
protected function getErrorMessage( $error ) {
if ( is_array( $error ) ) {
- if( isset( $error['message'] ) && $error['message'] instanceof Message ) {
+ if ( isset( $error['message'] ) && $error['message'] instanceof Message ) {
$msg = $error['message'];
} elseif ( isset( $error['message'] ) && isset( $error['params'] ) ) {
$msg = wfMessage( $error['message'],
@@ -225,7 +283,7 @@ class Status {
} else {
$msg = wfMessage( $error );
}
- return $msg->plain();
+ return $msg;
}
/**
@@ -234,7 +292,7 @@ class Status {
*
* @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.
+ * @todo figure out whether that is actually The Right Thing.
*/
public function getHTML( $shortContext = false, $longContext = false ) {
$text = $this->getWikiText( $shortContext, $longContext );
@@ -269,7 +327,8 @@ class Status {
/**
* Get the list of errors (but not warnings)
*
- * @return Array
+ * @return array A list in which each entry is an array with a message key as its first element.
+ * The remaining array elements are the message parameters.
*/
function getErrorsArray() {
return $this->getStatusArray( "error" );
@@ -278,7 +337,8 @@ class Status {
/**
* Get the list of warnings (but not errors)
*
- * @return Array
+ * @return array A list in which each entry is an array with a message key as its first element.
+ * The remaining array elements are the message parameters.
*/
function getWarningsArray() {
return $this->getStatusArray( "warning" );
@@ -294,9 +354,9 @@ class Status {
$result = array();
foreach ( $this->errors as $error ) {
if ( $error['type'] === $type ) {
- if( $error['message'] instanceof Message ) {
- $result[] = $error['message'];
- } elseif( $error['params'] ) {
+ if ( $error['message'] instanceof Message ) {
+ $result[] = array_merge( array( $error['message']->getKey() ), $error['message']->getParams() );
+ } elseif ( $error['params'] ) {
$result[] = array_merge( array( $error['message'] ), $error['params'] );
} else {
$result[] = array( $error['message'] );
@@ -365,15 +425,6 @@ class Status {
}
/**
- * Backward compatibility function for WikiError -> Status migration
- *
- * @return String
- */
- public function getMessage() {
- return $this->getWikiText();
- }
-
- /**
* @return mixed
*/
public function getValue() {
diff --git a/includes/StreamFile.php b/includes/StreamFile.php
index f5e4acff..1ad643ac 100644
--- a/includes/StreamFile.php
+++ b/includes/StreamFile.php
@@ -42,6 +42,7 @@ class StreamFile {
wfProfileIn( __METHOD__ );
if ( FileBackend::isStoragePath( $fname ) ) { // sanity
+ wfProfileOut( __METHOD__ );
throw new MWException( __FUNCTION__ . " given storage path '$fname'." );
}
diff --git a/includes/StringUtils.php b/includes/StringUtils.php
index f4c98f1d..c1545e6e 100644
--- a/includes/StringUtils.php
+++ b/includes/StringUtils.php
@@ -38,6 +38,9 @@ class StringUtils {
* unit testing our internal implementation.
*
* @since 1.21
+ * @note In MediaWiki 1.21, this function did not provide proper UTF-8 validation.
+ * In particular, the pure PHP code path did not in fact check for overlong forms.
+ * Beware of this when backporting code to that version of MediaWiki.
*
* @param string $value String to check
* @param boolean $disableMbstring Whether to use the pure PHP
@@ -47,26 +50,64 @@ class StringUtils {
* @return boolean Whether the given $value is a valid UTF-8 encoded string
*/
static function isUtf8( $value, $disableMbstring = false ) {
+ $value = (string)$value;
- if ( preg_match( '/[\x80-\xff]/', $value ) === 0 ) {
- # no high bit set, this is pure ASCII which is de facto
- # valid UTF-8
+ // If the mbstring extension is loaded, use it. However, before PHP 5.4, values above
+ // U+10FFFF are incorrectly allowed, so we have to check for them separately.
+ if ( !$disableMbstring && function_exists( 'mb_check_encoding' ) ) {
+ static $newPHP;
+ if ( $newPHP === null ) {
+ $newPHP = !mb_check_encoding( "\xf4\x90\x80\x80", 'UTF-8' );
+ }
+
+ return mb_check_encoding( $value, 'UTF-8' ) &&
+ ( $newPHP || preg_match( "/\xf4[\x90-\xbf]|[\xf5-\xff]/S", $value ) === 0 );
+ }
+
+ if ( preg_match( "/[\x80-\xff]/S", $value ) === 0 ) {
+ // String contains only ASCII characters, has to be valid
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 );
+ // PCRE implements repetition using recursion; to avoid a stack overflow (and segfault)
+ // for large input, we check for invalid sequences (<= 5 bytes) rather than valid
+ // sequences, which can be as long as the input string is. Multiple short regexes are
+ // used rather than a single long regex for performance.
+ static $regexes;
+ if ( $regexes === null ) {
+ $cont = "[\x80-\xbf]";
+ $after = "(?!$cont)"; // "(?:[^\x80-\xbf]|$)" would work here
+ $regexes = array(
+ // Continuation byte at the start
+ "/^$cont/",
+
+ // ASCII byte followed by a continuation byte
+ "/[\\x00-\x7f]$cont/S",
+
+ // Illegal byte
+ "/[\xc0\xc1\xf5-\xff]/S",
+
+ // Invalid 2-byte sequence, or valid one then an extra continuation byte
+ "/[\xc2-\xdf](?!$cont$after)/S",
+
+ // Invalid 3-byte sequence, or valid one then an extra continuation byte
+ "/\xe0(?![\xa0-\xbf]$cont$after)/",
+ "/[\xe1-\xec\xee\xef](?!$cont{2}$after)/S",
+ "/\xed(?![\x80-\x9f]$cont$after)/",
+
+ // Invalid 4-byte sequence, or valid one then an extra continuation byte
+ "/\xf0(?![\x90-\xbf]$cont{2}$after)/",
+ "/[\xf1-\xf3](?!$cont{3}$after)/S",
+ "/\xf4(?![\x80-\x8f]$cont{2}$after)/",
+ );
}
+
+ foreach ( $regexes as $regex ) {
+ if ( preg_match( $regex, $value ) !== 0 ) {
+ return false;
+ }
+ }
+ return true;
}
/**
@@ -217,8 +258,8 @@ class StringUtils {
/**
* More or less "markup-safe" explode()
* Ignores any instances of the separator inside <...>
- * @param $separator String
- * @param $text String
+ * @param string $separator
+ * @param string $text
* @return array
*/
static function explodeMarkup( $separator, $text ) {
@@ -233,7 +274,7 @@ class StringUtils {
// Explode, then put the replaced separators back in
$items = explode( $separator, $cleaned );
- foreach( $items as $i => $str ) {
+ foreach ( $items as $i => $str ) {
$items[$i] = str_replace( $placeholder, $separator, $str );
}
@@ -244,8 +285,8 @@ class StringUtils {
* Escape a string to make it suitable for inclusion in a preg_replace()
* replacement parameter.
*
- * @param $string String
- * @return String
+ * @param string $string
+ * @return string
*/
static function escapeRegexReplacement( $string ) {
$string = str_replace( '\\', '\\\\', $string );
@@ -256,8 +297,8 @@ class StringUtils {
/**
* Workalike for explode() with limited memory usage.
* Returns an Iterator
- * @param $separator
- * @param $subject
+ * @param string $separator
+ * @param string $subject
* @return ArrayIterator|ExplodeIterator
*/
static function explode( $separator, $subject ) {
@@ -290,14 +331,14 @@ class RegexlikeReplacer extends Replacer {
var $r;
/**
- * @param $r string
+ * @param string $r
*/
function __construct( $r ) {
$this->r = $r;
}
/**
- * @param $matches array
+ * @param array $matches
* @return string
*/
function replace( $matches ) {
@@ -318,7 +359,7 @@ class DoubleReplacer extends Replacer {
/**
* @param $from
* @param $to
- * @param $index int
+ * @param int $index
*/
function __construct( $from, $to, $index = 0 ) {
$this->from = $from;
@@ -327,7 +368,7 @@ class DoubleReplacer extends Replacer {
}
/**
- * @param $matches array
+ * @param array $matches
* @return mixed
*/
function replace( $matches ) {
@@ -343,7 +384,7 @@ class HashtableReplacer extends Replacer {
/**
* @param $table
- * @param $index int
+ * @param int $index
*/
function __construct( $table, $index = 0 ) {
$this->table = $table;
@@ -351,7 +392,7 @@ class HashtableReplacer extends Replacer {
}
/**
- * @param $matches array
+ * @param array $matches
* @return mixed
*/
function replace( $matches ) {
@@ -389,6 +430,7 @@ class ReplacementArray {
/**
* Set the whole replacement array at once
+ * @param array $data
*/
function setArray( $data ) {
$this->data = $data;
@@ -404,8 +446,8 @@ class ReplacementArray {
/**
* Set an element of the replacement array
- * @param $from string
- * @param $to string
+ * @param string $from
+ * @param string $to
*/
function setPair( $from, $to ) {
$this->data[$from] = $to;
@@ -413,7 +455,7 @@ class ReplacementArray {
}
/**
- * @param $data array
+ * @param array $data
*/
function mergeArray( $data ) {
$this->data = array_merge( $this->data, $data );
@@ -421,7 +463,7 @@ class ReplacementArray {
}
/**
- * @param $other
+ * @param ReplacementArray $other
*/
function merge( $other ) {
$this->data = array_merge( $this->data, $other->data );
@@ -429,7 +471,7 @@ class ReplacementArray {
}
/**
- * @param $from string
+ * @param string $from
*/
function removePair( $from ) {
unset( $this->data[$from] );
@@ -437,31 +479,31 @@ class ReplacementArray {
}
/**
- * @param $data array
+ * @param array $data
*/
function removeArray( $data ) {
- foreach( $data as $from => $to ) {
+ foreach ( $data as $from => $to ) {
$this->removePair( $from );
}
$this->fss = false;
}
/**
- * @param $subject string
+ * @param string $subject
* @return string
*/
function replace( $subject ) {
if ( function_exists( 'fss_prep_replace' ) ) {
- wfProfileIn( __METHOD__.'-fss' );
+ wfProfileIn( __METHOD__ . '-fss' );
if ( $this->fss === false ) {
$this->fss = fss_prep_replace( $this->data );
}
$result = fss_exec_replace( $this->fss, $subject );
- wfProfileOut( __METHOD__.'-fss' );
+ wfProfileOut( __METHOD__ . '-fss' );
} else {
- wfProfileIn( __METHOD__.'-strtr' );
+ wfProfileIn( __METHOD__ . '-strtr' );
$result = strtr( $subject, $this->data );
- wfProfileOut( __METHOD__.'-strtr' );
+ wfProfileOut( __METHOD__ . '-strtr' );
}
return $result;
}
@@ -494,15 +536,15 @@ class ExplodeIterator implements Iterator {
/**
* Construct a DelimIterator
- * @param $delim string
- * @param $s string
+ * @param string $delim
+ * @param string $subject
*/
- function __construct( $delim, $s ) {
- $this->subject = $s;
+ function __construct( $delim, $subject ) {
+ $this->subject = $subject;
$this->delim = $delim;
// Micro-optimisation (theoretical)
- $this->subjectLength = strlen( $s );
+ $this->subjectLength = strlen( $subject );
$this->delimLength = strlen( $delim );
$this->rewind();
@@ -530,6 +572,9 @@ class ExplodeIterator implements Iterator {
return $this->current;
}
+ /**
+ * @return int|bool Current position or boolean false if invalid
+ */
function key() {
return $this->curPos;
}
diff --git a/includes/StubObject.php b/includes/StubObject.php
index f0a35740..a3970f34 100644
--- a/includes/StubObject.php
+++ b/includes/StubObject.php
@@ -25,6 +25,12 @@
* their associated module code by deferring initialisation until the first
* method call.
*
+ * Note on reference parameters:
+ *
+ * If the called method takes any parameters by reference, the __call magic
+ * here won't work correctly. The solution is to unstub the object before
+ * calling the method.
+ *
* Note on unstub loops:
*
* Unstub loops (infinite recursion) sometimes occur when a constructor calls
@@ -60,7 +66,21 @@ class StubObject {
* @return Boolean: true if $obj is not an instance of StubObject class.
*/
static function isRealObject( $obj ) {
- return is_object( $obj ) && !($obj instanceof StubObject);
+ return is_object( $obj ) && !$obj instanceof StubObject;
+ }
+
+ /**
+ * Unstubs an object, if it is a stub object. Can be used to break a
+ * infinite loop when unstubbing an object or to avoid reference parameter
+ * breakage.
+ *
+ * @param $obj Object to check.
+ * @return void
+ */
+ static function unstub( $obj ) {
+ if ( $obj instanceof StubObject ) {
+ $obj->_unstub( 'unstub', 3 );
+ }
}
/**
@@ -113,7 +133,7 @@ class StubObject {
function _unstub( $name = '_unstub', $level = 2 ) {
static $recursionLevel = 0;
- if ( !($GLOBALS[$this->mGlobal] instanceof StubObject) ) {
+ if ( !$GLOBALS[$this->mGlobal] instanceof StubObject ) {
return $GLOBALS[$this->mGlobal]; // already unstubbed.
}
@@ -122,6 +142,7 @@ class StubObject {
wfProfileIn( $fname );
$caller = wfGetCaller( $level );
if ( ++$recursionLevel > 2 ) {
+ wfProfileOut( $fname );
throw new MWException( "Unstub loop detected on call of \${$this->mGlobal}->$name from $caller\n" );
}
wfDebug( "Unstubbing \${$this->mGlobal} on call of \${$this->mGlobal}::$name from $caller\n" );
diff --git a/includes/Timestamp.php b/includes/Timestamp.php
index 1cf99a79..edcd6a88 100644
--- a/includes/Timestamp.php
+++ b/includes/Timestamp.php
@@ -45,26 +45,10 @@ class MWTimestamp {
);
/**
- * Different units for human readable timestamps.
- * @see MWTimestamp::getHumanTimestamp
+ * The actual timestamp being wrapped (DateTime object).
+ * @var DateTime
*/
- private static $units = array(
- "milliseconds" => 1,
- "seconds" => 1000, // 1000 milliseconds per second
- "minutes" => 60, // 60 seconds per minute
- "hours" => 60, // 60 minutes per hour
- "days" => 24, // 24 hours per day
- "months" => 30, // approximately 30 days per month
- "years" => 12, // 12 months per year
- );
-
- /**
- * The actual timestamp being wrapped. Either a DateTime
- * object or a string with a Unix timestamp depending on
- * PHP.
- * @var string|DateTime
- */
- private $timestamp;
+ public $timestamp;
/**
* Make a new timestamp and set it to the specified time,
@@ -130,10 +114,10 @@ class MWTimestamp {
# asctime
$strtime = $ts;
} else {
- throw new TimestampException( __METHOD__ . " : Invalid timestamp - $ts" );
+ throw new TimestampException( __METHOD__ . ": Invalid timestamp - $ts" );
}
- if( !$strtime ) {
+ if ( !$strtime ) {
$da = array_map( 'intval', $da );
$da[0] = "%04d-%02d-%02dT%02d:%02d:%02d.00+00:00";
$strtime = call_user_func_array( "sprintf", $da );
@@ -141,12 +125,12 @@ class MWTimestamp {
try {
$final = new DateTime( $strtime, new DateTimeZone( 'GMT' ) );
- } catch(Exception $e) {
- throw new TimestampException( __METHOD__ . ' Invalid timestamp format.' );
+ } catch ( Exception $e ) {
+ throw new TimestampException( __METHOD__ . ': Invalid timestamp format.', $e->getCode(), $e );
}
- if( $final === false ) {
- throw new TimestampException( __METHOD__ . ' Invalid timestamp format.' );
+ if ( $final === false ) {
+ throw new TimestampException( __METHOD__ . ': Invalid timestamp format.' );
}
$this->timestamp = $final;
}
@@ -164,20 +148,11 @@ class MWTimestamp {
* @return string The formatted timestamp
*/
public function getTimestamp( $style = TS_UNIX ) {
- if( !isset( self::$formats[$style] ) ) {
- throw new TimestampException( __METHOD__ . ' : Illegal timestamp output type.' );
+ if ( !isset( self::$formats[$style] ) ) {
+ throw new TimestampException( __METHOD__ . ': Illegal timestamp output type.' );
}
- if( is_object( $this->timestamp ) ) {
- // DateTime object was used, call DateTime::format.
- $output = $this->timestamp->format( self::$formats[$style] );
- } elseif( TS_UNIX == $style ) {
- // Unix timestamp was used and is wanted, just return it.
- $output = $this->timestamp;
- } else {
- // Unix timestamp was used, use gmdate().
- $output = gmdate( self::$formats[$style], $this->timestamp );
- }
+ $output = $this->timestamp->format( self::$formats[$style] );
if ( ( $style == TS_RFC2822 ) || ( $style == TS_POSTGRES ) ) {
$output .= ' GMT';
@@ -194,31 +169,143 @@ class MWTimestamp {
* largest possible unit is used.
*
* @since 1.20
+ * @since 1.22 Uses Language::getHumanTimestamp to produce the timestamp
+ *
+ * @param MWTimestamp|null $relativeTo The base timestamp to compare to (defaults to now)
+ * @param User|null $user User the timestamp is being generated for (or null to use main context's user)
+ * @param Language|null $lang Language to use to make the human timestamp (or null to use main context's language)
+ * @return string Formatted timestamp
+ */
+ public function getHumanTimestamp( MWTimestamp $relativeTo = null, User $user = null, Language $lang = null ) {
+ if ( $relativeTo === null ) {
+ $relativeTo = new self();
+ }
+ if ( $user === null ) {
+ $user = RequestContext::getMain()->getUser();
+ }
+ if ( $lang === null ) {
+ $lang = RequestContext::getMain()->getLanguage();
+ }
+
+ // Adjust for the user's timezone.
+ $offsetThis = $this->offsetForUser( $user );
+ $offsetRel = $relativeTo->offsetForUser( $user );
+
+ $ts = '';
+ if ( wfRunHooks( 'GetHumanTimestamp', array( &$ts, $this, $relativeTo, $user, $lang ) ) ) {
+ $ts = $lang->getHumanTimestamp( $this, $relativeTo, $user );
+ }
+
+ // Reset the timezone on the objects.
+ $this->timestamp->sub( $offsetThis );
+ $relativeTo->timestamp->sub( $offsetRel );
+
+ return $ts;
+ }
+
+ /**
+ * Adjust the timestamp depending on the given user's preferences.
*
- * @return Message Formatted timestamp
+ * @since 1.22
+ *
+ * @param User $user User to take preferences from
+ * @param[out] MWTimestamp $ts Timestamp to adjust
+ * @return DateInterval Offset that was applied to the timestamp
*/
- public function getHumanTimestamp() {
- $then = $this->getTimestamp( TS_UNIX );
- $now = time();
- $timeago = ($now - $then) * 1000;
- $message = false;
-
- foreach( self::$units as $unit => $factor ) {
- $next = $timeago / $factor;
- if( $next < 1 ) {
- break;
+ public function offsetForUser( User $user ) {
+ global $wgLocalTZoffset;
+
+ $option = $user->getOption( 'timecorrection' );
+ $data = explode( '|', $option, 3 );
+
+ // First handle the case of an actual timezone being specified.
+ if ( $data[0] == 'ZoneInfo' ) {
+ try {
+ $tz = new DateTimeZone( $data[2] );
+ } catch ( Exception $e ) {
+ $tz = false;
+ }
+
+ if ( $tz ) {
+ $this->timestamp->setTimezone( $tz );
+ return new DateInterval( 'P0Y' );
} else {
- $timeago = $next;
- $message = array( $unit, floor( $timeago ) );
+ $data[0] = 'Offset';
}
}
- if( $message ) {
- $initial = call_user_func_array( 'wfMessage', $message );
- return wfMessage( 'ago', $initial->parse() );
+ $diff = 0;
+ // If $option is in fact a pipe-separated value, check the
+ // first value.
+ if ( $data[0] == 'System' ) {
+ // First value is System, so use the system offset.
+ if ( isset( $wgLocalTZoffset ) ) {
+ $diff = $wgLocalTZoffset;
+ }
+ } elseif ( $data[0] == 'Offset' ) {
+ // First value is Offset, so use the specified offset
+ $diff = (int)$data[1];
} else {
- return wfMessage( 'just-now' );
+ // $option actually isn't a pipe separated value, but instead
+ // a comma separated value. Isn't MediaWiki fun?
+ $data = explode( ':', $option );
+ if ( count( $data ) >= 2 ) {
+ // Combination hours and minutes.
+ $diff = abs( (int)$data[0] ) * 60 + (int)$data[1];
+ if ( (int)$data[0] < 0 ) {
+ $diff *= -1;
+ }
+ } else {
+ // Just hours.
+ $diff = (int)$data[0] * 60;
+ }
+ }
+
+ $interval = new DateInterval( 'PT' . abs( $diff ) . 'M' );
+ if ( $diff < 1 ) {
+ $interval->invert = 1;
}
+
+ $this->timestamp->add( $interval );
+ return $interval;
+ }
+
+ /**
+ * Generate a purely relative timestamp, i.e., represent the time elapsed between
+ * the given base timestamp and this object.
+ *
+ * @param MWTimestamp $relativeTo Relative base timestamp (defaults to now)
+ * @param User $user Use to use offset for
+ * @param Language $lang Language to use
+ * @param array $chosenIntervals Intervals to use to represent it
+ * @return string Relative timestamp
+ */
+ public function getRelativeTimestamp(
+ MWTimestamp $relativeTo = null,
+ User $user = null,
+ Language $lang = null,
+ array $chosenIntervals = array()
+ ) {
+ if ( $relativeTo === null ) {
+ $relativeTo = new self;
+ }
+ if ( $user === null ) {
+ $user = RequestContext::getMain()->getUser();
+ }
+ if ( $lang === null ) {
+ $lang = RequestContext::getMain()->getLanguage();
+ }
+
+ $ts = '';
+ $diff = $this->diff( $relativeTo );
+ if ( wfRunHooks( 'GetRelativeTimestamp', array( &$ts, &$diff, $this, $relativeTo, $user, $lang ) ) ) {
+ $seconds = ( ( ( $diff->days * 24 + $diff->h ) * 60 + $diff->i ) * 60 + $diff->s );
+ $ts = wfMessage( 'ago', $lang->formatDuration( $seconds, $chosenIntervals ) )
+ ->inLanguage( $lang )
+ ->text();
+ }
+
+ return $ts;
}
/**
@@ -229,6 +316,78 @@ class MWTimestamp {
public function __toString() {
return $this->getTimestamp();
}
+
+ /**
+ * Calculate the difference between two MWTimestamp objects.
+ *
+ * @since 1.22
+ * @param MWTimestamp $relativeTo Base time to calculate difference from
+ * @return DateInterval|bool The DateInterval object representing the difference between the two dates or false on failure
+ */
+ public function diff( MWTimestamp $relativeTo ) {
+ return $this->timestamp->diff( $relativeTo->timestamp );
+ }
+
+ /**
+ * Set the timezone of this timestamp to the specified timezone.
+ *
+ * @since 1.22
+ * @param String $timezone Timezone to set
+ * @throws TimestampException
+ */
+ public function setTimezone( $timezone ) {
+ try {
+ $this->timestamp->setTimezone( new DateTimeZone( $timezone ) );
+ } catch ( Exception $e ) {
+ throw new TimestampException( __METHOD__ . ': Invalid timezone.', $e->getCode(), $e );
+ }
+ }
+
+ /**
+ * Get the timezone of this timestamp.
+ *
+ * @since 1.22
+ * @return DateTimeZone The timezone
+ */
+ public function getTimezone() {
+ return $this->timestamp->getTimezone();
+ }
+
+ /**
+ * Format the timestamp in a given format.
+ *
+ * @since 1.22
+ * @param string $format Pattern to format in
+ * @return string The formatted timestamp
+ */
+ public function format( $format ) {
+ return $this->timestamp->format( $format );
+ }
+
+ /**
+ * Get a timestamp instance in the server local timezone ($wgLocaltimezone)
+ *
+ * @since 1.22
+ * @param bool|string $ts Timestamp to set, or false for current time
+ * @return MWTimestamp the local instance
+ */
+ public static function getLocalInstance( $ts = false ) {
+ global $wgLocaltimezone;
+ $timestamp = new self( $ts );
+ $timestamp->setTimezone( $wgLocaltimezone );
+ return $timestamp;
+ }
+
+ /**
+ * Get a timestamp instance in GMT
+ *
+ * @since 1.22
+ * @param bool|string $ts Timestamp to set, or false for current time
+ * @return MWTimestamp the instance
+ */
+ public static function getInstance( $ts = false ) {
+ return new self( $ts );
+ }
}
/**
diff --git a/includes/Title.php b/includes/Title.php
index ca66aaec..21872e45 100644
--- a/includes/Title.php
+++ b/includes/Title.php
@@ -225,7 +225,7 @@ class Title {
*
* @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
+ * @return Title|null 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 );
@@ -289,18 +289,23 @@ class Title {
*/
public function loadFromRow( $row ) {
if ( $row ) { // page found
- if ( isset( $row->page_id ) )
+ if ( isset( $row->page_id ) ) {
$this->mArticleID = (int)$row->page_id;
- if ( isset( $row->page_len ) )
+ }
+ if ( isset( $row->page_len ) ) {
$this->mLength = (int)$row->page_len;
- if ( isset( $row->page_is_redirect ) )
+ }
+ if ( isset( $row->page_is_redirect ) ) {
$this->mRedirect = (bool)$row->page_is_redirect;
- if ( isset( $row->page_latest ) )
+ }
+ if ( isset( $row->page_latest ) ) {
$this->mLatestID = (int)$row->page_latest;
- if ( isset( $row->page_content_model ) )
+ }
+ if ( isset( $row->page_content_model ) ) {
$this->mContentModel = strval( $row->page_content_model );
- else
+ } else {
$this->mContentModel = false; # initialized lazily in getContentModel()
+ }
} else { // page not found
$this->mArticleID = 0;
$this->mLength = 0;
@@ -487,6 +492,108 @@ class Title {
}
/**
+ * Utility method for converting a character sequence from bytes to Unicode.
+ *
+ * Primary usecase being converting $wgLegalTitleChars to a sequence usable in
+ * javascript, as PHP uses UTF-8 bytes where javascript uses Unicode code units.
+ *
+ * @param string $byteClass
+ * @return string
+ */
+ public static function convertByteClassToUnicodeClass( $byteClass ) {
+ $length = strlen( $byteClass );
+ // Input token queue
+ $x0 = $x1 = $x2 = '';
+ // Decoded queue
+ $d0 = $d1 = $d2 = '';
+ // Decoded integer codepoints
+ $ord0 = $ord1 = $ord2 = 0;
+ // Re-encoded queue
+ $r0 = $r1 = $r2 = '';
+ // Output
+ $out = '';
+ // Flags
+ $allowUnicode = false;
+ for ( $pos = 0; $pos < $length; $pos++ ) {
+ // Shift the queues down
+ $x2 = $x1;
+ $x1 = $x0;
+ $d2 = $d1;
+ $d1 = $d0;
+ $ord2 = $ord1;
+ $ord1 = $ord0;
+ $r2 = $r1;
+ $r1 = $r0;
+ // Load the current input token and decoded values
+ $inChar = $byteClass[$pos];
+ if ( $inChar == '\\' ) {
+ if ( preg_match( '/x([0-9a-fA-F]{2})/A', $byteClass, $m, 0, $pos + 1 ) ) {
+ $x0 = $inChar . $m[0];
+ $d0 = chr( hexdec( $m[1] ) );
+ $pos += strlen( $m[0] );
+ } elseif ( preg_match( '/[0-7]{3}/A', $byteClass, $m, 0, $pos + 1 ) ) {
+ $x0 = $inChar . $m[0];
+ $d0 = chr( octdec( $m[0] ) );
+ $pos += strlen( $m[0] );
+ } elseif ( $pos + 1 >= $length ) {
+ $x0 = $d0 = '\\';
+ } else {
+ $d0 = $byteClass[$pos + 1];
+ $x0 = $inChar . $d0;
+ $pos += 1;
+ }
+ } else {
+ $x0 = $d0 = $inChar;
+ }
+ $ord0 = ord( $d0 );
+ // Load the current re-encoded value
+ if ( $ord0 < 32 || $ord0 == 0x7f ) {
+ $r0 = sprintf( '\x%02x', $ord0 );
+ } elseif ( $ord0 >= 0x80 ) {
+ // Allow unicode if a single high-bit character appears
+ $r0 = sprintf( '\x%02x', $ord0 );
+ $allowUnicode = true;
+ } elseif ( strpos( '-\\[]^', $d0 ) !== false ) {
+ $r0 = '\\' . $d0;
+ } else {
+ $r0 = $d0;
+ }
+ // Do the output
+ if ( $x0 !== '' && $x1 === '-' && $x2 !== '' ) {
+ // Range
+ if ( $ord2 > $ord0 ) {
+ // Empty range
+ } elseif ( $ord0 >= 0x80 ) {
+ // Unicode range
+ $allowUnicode = true;
+ if ( $ord2 < 0x80 ) {
+ // Keep the non-unicode section of the range
+ $out .= "$r2-\\x7F";
+ }
+ } else {
+ // Normal range
+ $out .= "$r2-$r0";
+ }
+ // Reset state to the initial value
+ $x0 = $x1 = $d0 = $d1 = $r0 = $r1 = '';
+ } elseif ( $ord2 < 0x80 ) {
+ // ASCII character
+ $out .= $r2;
+ }
+ }
+ if ( $ord1 < 0x80 ) {
+ $out .= $r1;
+ }
+ if ( $ord0 < 0x80 ) {
+ $out .= $r0;
+ }
+ if ( $allowUnicode ) {
+ $out .= '\u0080-\uFFFF';
+ }
+ return $out;
+ }
+
+ /**
* Get a string representation of a title suitable for
* including in a search index
*
@@ -689,7 +796,7 @@ class Title {
$this->mContentModel = ContentHandler::getDefaultModelFor( $this );
}
- if( !$this->mContentModel ) {
+ if ( !$this->mContentModel ) {
throw new MWException( 'Failed to determine content model!' );
}
@@ -752,7 +859,7 @@ class Title {
*/
public function getTalkNsText() {
global $wgContLang;
- return( $wgContLang->getNsText( MWNamespace::getTalk( $this->mNamespace ) ) );
+ return $wgContLang->getNsText( MWNamespace::getTalk( $this->mNamespace ) );
}
/**
@@ -761,7 +868,7 @@ class Title {
* @return Bool TRUE or FALSE
*/
public function canTalk() {
- return( MWNamespace::canTalk( $this->mNamespace ) );
+ return MWNamespace::canTalk( $this->mNamespace );
}
/**
@@ -939,7 +1046,7 @@ class Title {
* @return Bool
*/
public function isConversionTable() {
- //@todo: ConversionTable should become a separate content model.
+ // @todo ConversionTable should become a separate content model.
return $this->getNamespace() == NS_MEDIAWIKI &&
strpos( $this->getText(), 'Conversiontable/' ) === 0;
@@ -995,10 +1102,11 @@ class Title {
*/
public function getSkinFromCssJsSubpage() {
$subpage = explode( '/', $this->mTextform );
- $subpage = $subpage[ count( $subpage ) - 1 ];
+ $subpage = $subpage[count( $subpage ) - 1];
$lastdot = strrpos( $subpage, '.' );
- if ( $lastdot === false )
+ if ( $lastdot === false ) {
return $subpage; # Never happens: only called for names ending in '.css' or '.js'
+ }
return substr( $subpage, 0, $lastdot );
}
@@ -1269,10 +1377,10 @@ class Title {
*/
public function getSubpageText() {
if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
- return( $this->mTextform );
+ return $this->mTextform;
}
$parts = explode( '/', $this->mTextform );
- return( $parts[count( $parts ) - 1] );
+ return $parts[count( $parts ) - 1];
}
/**
@@ -1312,7 +1420,7 @@ class Title {
public function getSubpageUrlForm() {
$text = $this->getSubpageText();
$text = wfUrlencode( str_replace( ' ', '_', $text ) );
- return( $text );
+ return $text;
}
/**
@@ -1327,8 +1435,8 @@ class Title {
}
/**
- * Helper to fix up the get{Local,Full,Link,Canonical}URL args
- * get{Canonical,Full,Link,Local}URL methods accepted an optional
+ * Helper to fix up the get{Canonical,Full,Link,Local,Internal}URL args
+ * get{Canonical,Full,Link,Local,Internal}URL methods accepted an optional
* 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
@@ -1340,8 +1448,10 @@ class Title {
* @return String
*/
private static function fixUrlQueryArgs( $query, $query2 = false ) {
- if( $query2 !== false ) {
- 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 ( $query2 !== false ) {
+ wfDeprecated( "Title::get{Canonical,Full,Link,Local,Internal}URL " .
+ "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 );
@@ -1399,7 +1509,6 @@ class Title {
* Get a URL with no fragment or server name. If this page is generated
* with action=render, $wgServer is prepended.
*
-
* @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).
@@ -1434,7 +1543,7 @@ class Title {
$url = str_replace( '$1', $dbkey, $wgArticlePath );
wfRunHooks( 'GetLocalURL::Article', array( &$this, &$url ) );
} else {
- global $wgVariantArticlePath, $wgActionPaths;
+ global $wgVariantArticlePath, $wgActionPaths, $wgContLang;
$url = false;
$matches = array();
@@ -1456,6 +1565,7 @@ class Title {
if ( $url === false &&
$wgVariantArticlePath &&
+ $wgContLang->getCode() === $this->getPageLanguage()->getCode() &&
$this->getPageLanguage()->hasVariants() &&
preg_match( '/^variant=([^&]*)$/', $query, $matches ) )
{
@@ -1732,6 +1842,10 @@ class Title {
* @return Array list of errors
*/
private function checkQuickPermissions( $action, $user, $errors, $doExpensiveQueries, $short ) {
+ if ( !wfRunHooks( 'TitleQuickPermissions', array( $this, $user, $action, &$errors, $doExpensiveQueries, $short ) ) ) {
+ return $errors;
+ }
+
if ( $action == 'create' ) {
if (
( $this->isTalkPage() && !$user->isAllowed( 'createtalk' ) ) ||
@@ -1880,12 +1994,19 @@ class Title {
# Protect css/js subpages of user pages
# XXX: this might be better using restrictions
# XXX: right 'editusercssjs' is deprecated, for backward compatibility only
- if ( $action != 'patrol' && !$user->isAllowed( 'editusercssjs' )
- && !preg_match( '/^' . preg_quote( $user->getName(), '/' ) . '\//', $this->mTextform ) ) {
- if ( $this->isCssSubpage() && !$user->isAllowed( 'editusercss' ) ) {
- $errors[] = array( 'customcssprotected' );
- } elseif ( $this->isJsSubpage() && !$user->isAllowed( 'edituserjs' ) ) {
- $errors[] = array( 'customjsprotected' );
+ if ( $action != 'patrol' && !$user->isAllowed( 'editusercssjs' ) ) {
+ if ( preg_match( '/^' . preg_quote( $user->getName(), '/' ) . '\//', $this->mTextform ) ) {
+ if ( $this->isCssSubpage() && !$user->isAllowedAny( 'editmyusercss', 'editusercss' ) ) {
+ $errors[] = array( 'mycustomcssprotected' );
+ } elseif ( $this->isJsSubpage() && !$user->isAllowedAny( 'editmyuserjs', 'edituserjs' ) ) {
+ $errors[] = array( 'mycustomjsprotected' );
+ }
+ } else {
+ if ( $this->isCssSubpage() && !$user->isAllowed( 'editusercss' ) ) {
+ $errors[] = array( 'customcssprotected' );
+ } elseif ( $this->isJsSubpage() && !$user->isAllowed( 'edituserjs' ) ) {
+ $errors[] = array( 'customjsprotected' );
+ }
}
}
@@ -1907,18 +2028,21 @@ class Title {
*/
private function checkPageRestrictions( $action, $user, $errors, $doExpensiveQueries, $short ) {
foreach ( $this->getRestrictions( $action ) as $right ) {
- // Backwards compatibility, rewrite sysop -> protect
+ // Backwards compatibility, rewrite sysop -> editprotected
if ( $right == 'sysop' ) {
- $right = 'protect';
+ $right = 'editprotected';
}
- if ( $right != '' && !$user->isAllowed( $right ) ) {
- // Users with 'editprotected' permission can edit protected pages
- // without cascading option turned on.
- if ( $action != 'edit' || !$user->isAllowed( 'editprotected' )
- || $this->mCascadeRestriction )
- {
- $errors[] = array( 'protectedpagetext', $right );
- }
+ // Backwards compatibility, rewrite autoconfirmed -> editsemiprotected
+ if ( $right == 'autoconfirmed' ) {
+ $right = 'editsemiprotected';
+ }
+ if ( $right == '' ) {
+ continue;
+ }
+ if ( !$user->isAllowed( $right ) ) {
+ $errors[] = array( 'protectedpagetext', $right );
+ } elseif ( $this->mCascadeRestriction && !$user->isAllowed( 'protect' ) ) {
+ $errors[] = array( 'protectedpagetext', 'protect' );
}
}
@@ -1950,11 +2074,19 @@ class Title {
# This is only for protection restrictions, not for all actions
if ( isset( $restrictions[$action] ) ) {
foreach ( $restrictions[$action] as $right ) {
- $right = ( $right == 'sysop' ) ? 'protect' : $right;
- if ( $right != '' && !$user->isAllowed( $right ) ) {
+ // Backwards compatibility, rewrite sysop -> editprotected
+ if ( $right == 'sysop' ) {
+ $right = 'editprotected';
+ }
+ // Backwards compatibility, rewrite autoconfirmed -> editsemiprotected
+ if ( $right == 'autoconfirmed' ) {
+ $right = 'editsemiprotected';
+ }
+ if ( $right != '' && !$user->isAllowedAll( 'protect', $right ) ) {
$pages = '';
- foreach ( $cascadingSources as $page )
+ foreach ( $cascadingSources as $page ) {
$pages .= '* [[:' . $page->getPrefixedText() . "]]\n";
+ }
$errors[] = array( 'cascadeprotected', count( $cascadingSources ), $pages );
}
}
@@ -1985,11 +2117,14 @@ class Title {
}
} elseif ( $action == 'create' ) {
$title_protection = $this->getTitleProtection();
- if( $title_protection ) {
- if( $title_protection['pt_create_perm'] == 'sysop' ) {
- $title_protection['pt_create_perm'] = 'protect'; // B/C
+ if ( $title_protection ) {
+ if ( $title_protection['pt_create_perm'] == 'sysop' ) {
+ $title_protection['pt_create_perm'] = 'editprotected'; // B/C
+ }
+ if ( $title_protection['pt_create_perm'] == 'autoconfirmed' ) {
+ $title_protection['pt_create_perm'] = 'editsemiprotected'; // B/C
}
- if( $title_protection['pt_create_perm'] == '' ||
+ if ( $title_protection['pt_create_perm'] == '' ||
!$user->isAllowed( $title_protection['pt_create_perm'] ) )
{
$errors[] = array( 'titleprotected', User::whoIs( $title_protection['pt_user'] ), $title_protection['pt_reason'] );
@@ -2034,11 +2169,11 @@ class Title {
private function checkUserBlock( $action, $user, $errors, $doExpensiveQueries, $short ) {
// Account creation blocks handled at userlogin.
// Unblocking handled in SpecialUnblock
- if( !$doExpensiveQueries || in_array( $action, array( 'createaccount', 'unblock' ) ) ) {
+ if ( !$doExpensiveQueries || in_array( $action, array( 'createaccount', 'unblock' ) ) ) {
return $errors;
}
- global $wgContLang, $wgLang, $wgEmailConfirmToEdit;
+ global $wgEmailConfirmToEdit;
if ( $wgEmailConfirmToEdit && !$user->isEmailConfirmed() ) {
$errors[] = array( 'confirmedittext' );
@@ -2047,39 +2182,9 @@ class Title {
if ( ( $action == 'edit' || $action == 'create' ) && !$user->isBlockedFrom( $this ) ) {
// Don't block the user from editing their own talk page unless they've been
// explicitly blocked from that too.
- } elseif( $user->isBlocked() && $user->mBlock->prevents( $action ) !== false ) {
- $block = $user->getBlock();
-
- // This is from OutputPage::blockedPage
- // Copied at r23888 by werdna
-
- $id = $user->blockedBy();
- $reason = $user->blockedFor();
- if ( $reason == '' ) {
- $reason = wfMessage( 'blockednoreason' )->text();
- }
- $ip = $user->getRequest()->getIP();
-
- if ( is_numeric( $id ) ) {
- $name = User::whoIs( $id );
- } else {
- $name = $id;
- }
-
- $link = '[[' . $wgContLang->getNsText( NS_USER ) . ":{$name}|{$name}]]";
- $blockid = $block->getId();
- $blockExpiry = $block->getExpiry();
- $blockTimestamp = $wgLang->timeanddate( wfTimestamp( TS_MW, $block->mTimestamp ), true );
- if ( $blockExpiry == 'infinity' ) {
- $blockExpiry = wfMessage( 'infiniteblock' )->text();
- } else {
- $blockExpiry = $wgLang->timeanddate( wfTimestamp( TS_MW, $blockExpiry ), true );
- }
-
- $intended = strval( $block->getTarget() );
-
- $errors[] = array( ( $block->mAuto ? 'autoblockedtext' : 'blockedtext' ), $link, $reason, $ip, $name,
- $blockid, $blockExpiry, $intended, $blockTimestamp );
+ } elseif ( $user->isBlocked() && $user->mBlock->prevents( $action ) !== false ) {
+ // @todo FIXME: Pass the relevant context into this function.
+ $errors[] = $user->getBlock()->getPermissionsError( RequestContext::getMain() );
}
return $errors;
@@ -2097,34 +2202,10 @@ class Title {
* @return Array list of errors
*/
private function checkReadPermissions( $action, $user, $errors, $doExpensiveQueries, $short ) {
- 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 ( !User::groupHasPermission( '*', 'read' ) ) {
- # Not a public wiki, so no shortcut
- $useShortcut = false;
- } elseif ( !empty( $wgRevokePermissions ) ) {
- /**
- * Iterate through each group with permissions being revoked (key not included since we don't care
- * what the group name is), then check if the read permission is being revoked. If it is, then
- * we don't use the shortcut below since the user might not be able to read, even though anon
- * reading is allowed.
- */
- foreach ( $wgRevokePermissions as $perms ) {
- if ( !empty( $perms['read'] ) ) {
- # We might be removing the read right from the user, so no shortcut
- $useShortcut = false;
- break;
- }
- }
- }
- }
+ global $wgWhitelistRead, $wgWhitelistReadRegexp;
$whitelisted = false;
- if ( $useShortcut ) {
+ if ( User::isEveryoneAllowed( 'read' ) ) {
# Shortcut for public wikis, allows skipping quite a bit of code
$whitelisted = true;
} elseif ( $user->isAllowed( 'read' ) ) {
@@ -2165,7 +2246,7 @@ class Title {
}
}
- if( !$whitelisted && is_array( $wgWhitelistReadRegexp ) && !empty( $wgWhitelistReadRegexp ) ) {
+ if ( !$whitelisted && is_array( $wgWhitelistReadRegexp ) && !empty( $wgWhitelistReadRegexp ) ) {
$name = $this->getPrefixedText();
// Check for regex whitelisting
foreach ( $wgWhitelistReadRegexp as $listItem ) {
@@ -2250,7 +2331,7 @@ class Title {
}
$errors = array();
- while( count( $checks ) > 0 &&
+ while ( count( $checks ) > 0 &&
!( $short && count( $errors ) > 0 ) ) {
$method = array_shift( $checks );
$errors = $this->$method( $action, $user, $errors, $doExpensiveQueries, $short );
@@ -2261,36 +2342,6 @@ class Title {
}
/**
- * Protect css subpages of user pages: can $wgUser edit
- * this page?
- *
- * @deprecated in 1.19; will be removed in 1.20. Use getUserPermissionsErrors() instead.
- * @return Bool
- */
- public function userCanEditCssSubpage() {
- global $wgUser;
- wfDeprecated( __METHOD__, '1.19' );
- return ( ( $wgUser->isAllowedAll( 'editusercssjs', 'editusercss' ) )
- || preg_match( '/^' . preg_quote( $wgUser->getName(), '/' ) . '\//', $this->mTextform ) );
- }
-
- /**
- * Protect js subpages of user pages: can $wgUser edit
- * this page?
- *
- * @deprecated in 1.19; will be removed in 1.20. Use getUserPermissionsErrors() instead.
- * @return Bool
- */
- public function userCanEditJsSubpage() {
- global $wgUser;
- wfDeprecated( __METHOD__, '1.19' );
- return (
- ( $wgUser->isAllowedAll( 'editusercssjs', 'edituserjs' ) )
- || preg_match( '/^' . preg_quote( $wgUser->getName(), '/' ) . '\//', $this->mTextform )
- );
- }
-
- /**
* Get a filtered list of all restriction types supported by this wiki.
* @param bool $exists True to get all restriction types that apply to
* titles that do exist, False for all restriction types that apply to
@@ -2371,7 +2422,7 @@ class Title {
/**
* Update the title protection status
*
- * @deprecated in 1.19; will be removed in 1.20. Use WikiPage::doUpdateRestrictions() instead.
+ * @deprecated in 1.19; use WikiPage::doUpdateRestrictions() instead.
* @param $create_perm String Permission required for creation
* @param string $reason Reason for protection
* @param string $expiry Expiry timestamp
@@ -2407,29 +2458,31 @@ class Title {
}
/**
- * Is this page "semi-protected" - the *only* protection is autoconfirm?
+ * Is this page "semi-protected" - the *only* protection levels are listed
+ * in $wgSemiprotectedRestrictionLevels?
*
* @param string $action Action to check (default: edit)
* @return Bool
*/
public function isSemiProtected( $action = 'edit' ) {
- if ( $this->exists() ) {
- $restrictions = $this->getRestrictions( $action );
- if ( count( $restrictions ) > 0 ) {
- foreach ( $restrictions as $restriction ) {
- if ( strtolower( $restriction ) != 'autoconfirmed' ) {
- return false;
- }
- }
- } else {
- # Not protected
- return false;
- }
- return true;
- } else {
- # If it doesn't exist, it can't be protected
+ global $wgSemiprotectedRestrictionLevels;
+
+ $restrictions = $this->getRestrictions( $action );
+ $semi = $wgSemiprotectedRestrictionLevels;
+ if ( !$restrictions || !$semi ) {
+ // Not protected, or all protection is full protection
return false;
}
+
+ // Remap autoconfirmed to editsemiprotected for BC
+ foreach ( array_keys( $semi, 'autoconfirmed' ) as $key ) {
+ $semi[$key] = 'editsemiprotected';
+ }
+ foreach ( array_keys( $restrictions, 'autoconfirmed' ) as $key ) {
+ $restrictions[$key] = 'editsemiprotected';
+ }
+
+ return !array_diff( $restrictions, $semi );
}
/**
@@ -2445,7 +2498,7 @@ class Title {
$restrictionTypes = $this->getRestrictionTypes();
# Special pages have inherent protection
- if( $this->isSpecialPage() ) {
+ if ( $this->isSpecialPage() ) {
return true;
}
@@ -2692,7 +2745,7 @@ class Title {
$this->mRestrictions['move'] = explode( ',', trim( $temp[0] ) );
} else {
$restriction = trim( $temp[1] );
- if( $restriction != '' ) { //some old entries are empty
+ if ( $restriction != '' ) { //some old entries are empty
$this->mRestrictions[$temp[0]] = explode( ',', $restriction );
}
}
@@ -2711,8 +2764,9 @@ class Title {
foreach ( $rows as $row ) {
// Don't take care of restrictions types that aren't allowed
- if ( !in_array( $row->pr_type, $restrictionTypes ) )
+ if ( !in_array( $row->pr_type, $restrictionTypes ) ) {
continue;
+ }
// This code should be refactored, now that it's being used more generally,
// But I don't really see any harm in leaving it in Block for now -werdna
@@ -2798,18 +2852,20 @@ class Title {
return;
}
+ $method = __METHOD__;
$dbw = wfGetDB( DB_MASTER );
- $dbw->delete(
- 'page_restrictions',
- array( 'pr_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ),
- __METHOD__
- );
-
- $dbw->delete(
- 'protected_titles',
- array( 'pt_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ),
- __METHOD__
- );
+ $dbw->onTransactionIdle( function() use ( $dbw, $method ) {
+ $dbw->delete(
+ 'page_restrictions',
+ array( 'pr_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ),
+ $method
+ );
+ $dbw->delete(
+ 'protected_titles',
+ array( 'pt_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ),
+ $method
+ );
+ } );
}
/**
@@ -2960,11 +3016,13 @@ class Title {
$linkCache = LinkCache::singleton();
$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() );
+ # Trust LinkCache's state over our own
+ # LinkCache is telling us that the page doesn't exist, despite there being cached
+ # data relating to an existing page in $this->mArticleID. Updaters should clear
+ # LinkCache as appropriate, or use $flags = Title::GAID_FOR_UPDATE. If that flag is
+ # set, then LinkCache will definitely be up to date here, since getArticleID() forces
+ # LinkCache to refresh its data from the master.
+ return $this->mRedirect = false;
}
$this->mRedirect = (bool)$cached;
@@ -2989,11 +3047,9 @@ class Title {
}
$linkCache = LinkCache::singleton();
$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() );
+ if ( $cached === null ) {
+ # Trust LinkCache's state over our own, as for isRedirect()
+ return $this->mLength = 0;
}
$this->mLength = intval( $cached );
@@ -3005,7 +3061,6 @@ class Title {
* What is the page_latest field for this page?
*
* @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 ) {
@@ -3019,10 +3074,9 @@ class Title {
$linkCache = LinkCache::singleton();
$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() );
+ if ( $cached === null ) {
+ # Trust LinkCache's state over our own, as for isRedirect()
+ return $this->mLatestID = 0;
}
$this->mLatestID = intval( $cached );
@@ -3111,7 +3165,7 @@ class Title {
return false;
}
- if ( false !== strpos( $dbkey, UTF8_REPLACEMENT ) ) {
+ if ( strpos( $dbkey, UTF8_REPLACEMENT ) !== false ) {
# Contained illegal UTF-8 sequences or forbidden Unicode chars.
return false;
}
@@ -3248,6 +3302,7 @@ class Title {
# Can't make a link to a namespace alone... "empty" local links can only be
# self-links with a fragment identifier.
+ # TODO: Why do we exclude NS_MAIN (bug 54044)
if ( $dbkey == '' && $this->mInterwiki == '' && $this->mNamespace != NS_MAIN ) {
return false;
}
@@ -3301,7 +3356,7 @@ class Title {
array(
"{$prefix}_from=page_id",
"{$prefix}_namespace" => $this->getNamespace(),
- "{$prefix}_title" => $this->getDBkey() ),
+ "{$prefix}_title" => $this->getDBkey() ),
__METHOD__,
$options
);
@@ -3366,7 +3421,9 @@ class Title {
$titleField = "{$prefix}_title";
$fields = array( $namespaceFiled, $titleField, 'page_id', 'page_len', 'page_is_redirect', 'page_latest' );
- if ( $wgContentHandlerUseDB ) $fields[] = 'page_content_model';
+ if ( $wgContentHandlerUseDB ) {
+ $fields[] = 'page_content_model';
+ }
$res = $db->select(
array( $table, 'page' ),
@@ -3465,6 +3522,7 @@ class Title {
}
}
+ wfRunHooks( 'TitleSquidURLs', array( $this, &$urls ) );
return $urls;
}
@@ -3585,7 +3643,13 @@ class Title {
}
} else {
$tp = $nt->getTitleProtection();
- $right = ( $tp['pt_create_perm'] == 'sysop' ) ? 'protect' : $tp['pt_create_perm'];
+ $right = $tp['pt_create_perm'];
+ if ( $right == 'sysop' ) {
+ $right = 'editprotected'; // B/C
+ }
+ if ( $right == 'autoconfirmed' ) {
+ $right = 'editsemiprotected'; // B/C
+ }
if ( $tp and !$wgUser->isAllowed( $right ) ) {
$errors[] = array( 'cantmove-titleprotected' );
}
@@ -3659,6 +3723,8 @@ class Title {
$createRedirect = true;
}
+ wfRunHooks( 'TitleMove', array( $this, $nt, $wgUser ) );
+
// If it is a file, move it first.
// It is done before all other moving stuff is done because it's hard to revert.
$dbw = wfGetDB( DB_MASTER );
@@ -3711,12 +3777,12 @@ class Title {
# Protect the redirect title as the title used to be...
$dbw->insertSelect( 'page_restrictions', 'page_restrictions',
array(
- 'pr_page' => $redirid,
- 'pr_type' => 'pr_type',
- 'pr_level' => 'pr_level',
+ 'pr_page' => $redirid,
+ 'pr_type' => 'pr_type',
+ 'pr_level' => 'pr_level',
'pr_cascade' => 'pr_cascade',
- 'pr_user' => 'pr_user',
- 'pr_expiry' => 'pr_expiry'
+ 'pr_user' => 'pr_user',
+ 'pr_expiry' => 'pr_expiry'
),
array( 'pr_page' => $pageid ),
__METHOD__,
@@ -3733,7 +3799,7 @@ class Title {
$comment .= wfMessage( 'colon-separator' )->inContentLanguage()->text() . $reason;
}
// @todo FIXME: $params?
- $log->addEntry( 'move_prot', $nt, $comment, array( $this->getPrefixedText() ) );
+ $log->addEntry( 'move_prot', $nt, $comment, array( $this->getPrefixedText() ), $wgUser );
}
# Update watchlists
@@ -3775,7 +3841,8 @@ class Title {
if ( $createRedirect ) {
$contentHandler = ContentHandler::getForTitle( $this );
- $redirectContent = $contentHandler->makeRedirectContent( $nt );
+ $redirectContent = $contentHandler->makeRedirectContent( $nt,
+ wfMessage( 'move-redirect-text' )->inContentLanguage()->plain() );
// NOTE: If this page's content model does not support redirects, $redirectContent will be null.
} else {
@@ -3830,14 +3897,20 @@ class Title {
$dbw->update( 'page',
/* SET */ array(
'page_namespace' => $nt->getNamespace(),
- 'page_title' => $nt->getDBkey(),
+ 'page_title' => $nt->getDBkey(),
),
/* WHERE */ array( 'page_id' => $oldid ),
__METHOD__
);
- $this->resetArticleID( 0 );
+ // clean up the old title before reset article id - bug 45348
+ if ( !$redirectContent ) {
+ WikiPage::onArticleDelete( $this );
+ }
+
+ $this->resetArticleID( 0 ); // 0 == non existing
$nt->resetArticleID( $oldid );
+ $newpage->loadPageData( WikiPage::READ_LOCKING ); // bug 46397
$newpage->updateRevisionOn( $dbw, $nullRevision );
@@ -3851,17 +3924,17 @@ class Title {
}
# Recreate the redirect, this time in the other direction.
- if ( !$redirectContent ) {
- WikiPage::onArticleDelete( $this );
- } else {
+ if ( $redirectContent ) {
$redirectArticle = WikiPage::factory( $this );
+ $redirectArticle->loadFromRow( false, WikiPage::READ_LOCKING ); // bug 46397
$newid = $redirectArticle->insertOn( $dbw );
if ( $newid ) { // sanity
+ $this->resetArticleID( $newid );
$redirectRevision = new Revision( array(
- 'title' => $this, // for determining the default content model
- 'page' => $newid,
+ 'title' => $this, // for determining the default content model
+ 'page' => $newid,
'comment' => $comment,
- 'content' => $redirectContent ) );
+ 'content' => $redirectContent ) );
$redirectRevision->insertOn( $dbw );
$redirectArticle->updateRevisionOn( $dbw, $redirectRevision, 0 );
@@ -3963,7 +4036,9 @@ class Title {
# Is it a redirect?
$fields = array( 'page_is_redirect', 'page_latest', 'page_id' );
- if ( $wgContentHandlerUseDB ) $fields[] = 'page_content_model';
+ if ( $wgContentHandlerUseDB ) {
+ $fields[] = 'page_content_model';
+ }
$row = $dbw->selectRow( 'page',
$fields,
@@ -4017,7 +4092,7 @@ class Title {
}
# Get the article text
$rev = Revision::newFromTitle( $nt, false, Revision::READ_LATEST );
- if( !is_object( $rev ) ) {
+ if ( !is_object( $rev ) ) {
return false;
}
$content = $rev->getContent();
@@ -4070,8 +4145,8 @@ class Title {
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();
+ // $data[] = Title::newFromText($wgContLang->getNsText ( NS_CATEGORY ).':'.$row->cl_to);
+ $data[$wgContLang->getNsText( NS_CATEGORY ) . ':' . $row->cl_to] = $this->getFullText();
}
}
return $data;
@@ -4319,7 +4394,7 @@ class Title {
// No DB query needed if $old and $new are the same or successive revisions:
if ( $old->getId() === $new->getId() ) {
return ( $old_cmp === '>' && $new_cmp === '<' ) ? 0 : 1;
- } else if ( $old->getId() === $new->getParentId() ) {
+ } elseif ( $old->getId() === $new->getParentId() ) {
if ( $old_cmp === '>' || $new_cmp === '<' ) {
return ( $old_cmp === '>' && $new_cmp === '<' ) ? 0 : 1;
}
@@ -4414,7 +4489,7 @@ class Title {
return true; // any interwiki link might be viewable, for all we know
}
- switch( $this->mNamespace ) {
+ switch ( $this->mNamespace ) {
case NS_MEDIA:
case NS_FILE:
// file exists, possibly in a foreign repo
@@ -4500,27 +4575,23 @@ class Title {
* @return Bool true if the update succeeded
*/
public function invalidateCache() {
- global $wgMemc;
if ( wfReadOnly() ) {
return false;
}
- $dbw = wfGetDB( DB_MASTER );
- $success = $dbw->update(
- 'page',
- array( 'page_touched' => $dbw->timestamp() ),
- $this->pageCond(),
- __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 );
- }
+ $method = __METHOD__;
+ $dbw = wfGetDB( DB_MASTER );
+ $conds = $this->pageCond();
+ $dbw->onTransactionIdle( function() use ( $dbw, $conds, $method ) {
+ $dbw->update(
+ 'page',
+ array( 'page_touched' => $dbw->timestamp() ),
+ $conds,
+ $method
+ );
+ } );
- return $success;
+ return true;
}
/**
@@ -4568,7 +4639,7 @@ class Title {
if ( array_key_exists( $uid, $this->mNotificationTimestamp ) ) {
return $this->mNotificationTimestamp[$uid];
}
- if ( !$uid || !$wgShowUpdatedMarker ) {
+ if ( !$uid || !$wgShowUpdatedMarker || !$user->isAllowed( 'viewmywatchlist' ) ) {
return $this->mNotificationTimestamp[$uid] = false;
}
// Don't cache too much!
@@ -4791,9 +4862,10 @@ class Title {
* they will already be wrapped in paragraphs.
*
* @since 1.21
+ * @param int oldid Revision ID that's being edited
* @return Array
*/
- public function getEditNotices() {
+ public function getEditNotices( $oldid = 0 ) {
$notices = array();
# Optional notices on a per-namespace and per-page basis
@@ -4820,6 +4892,8 @@ class Title {
$notices[$editnoticeText] = $editnoticeMsg->parseAsBlock();
}
}
+
+ wfRunHooks( 'TitleGetEditNotices', array( $this, $oldid, &$notices ) );
return $notices;
}
}
diff --git a/includes/UIDGenerator.php b/includes/UIDGenerator.php
index b042d8c7..963e51a4 100644
--- a/includes/UIDGenerator.php
+++ b/includes/UIDGenerator.php
@@ -239,9 +239,9 @@ class UIDGenerator {
$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
+ $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.
@@ -251,7 +251,7 @@ class UIDGenerator {
$time = self::millitime();
} elseif ( $time == $prevTime ) {
// Bump the counter if there are timestamp collisions
- $counter = (int) $data[3] % $counterSize;
+ $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." );
@@ -304,7 +304,7 @@ class UIDGenerator {
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 );
+ } while ( ( ( $time[0] - $ct[0] ) * 1000 + ( $time[1] - $ct[1] ) ) <= 10 );
return false;
}
@@ -315,25 +315,12 @@ class UIDGenerator {
*/
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.' );
+ $ts = 1000 * $sec + $msec;
+ if ( $ts > pow( 2, 52 ) ) {
+ throw new MWException( __METHOD__ .
+ ': sorry, this function doesn\'t work after the year 144680' );
}
- return $id_bin;
+ return substr( wfBaseConvert( $ts, 10, 2, 46 ), -46 );
}
/**
@@ -341,7 +328,7 @@ class UIDGenerator {
*/
protected static function millitime() {
list( $msec, $sec ) = explode( ' ', microtime() );
- return array( (int) $sec, (int) ( $msec * 1000 ) );
+ return array( (int)$sec, (int)( $msec * 1000 ) );
}
function __destruct() {
diff --git a/includes/User.php b/includes/User.php
index 6b7348a8..12912e1c 100644
--- a/includes/User.php
+++ b/includes/User.php
@@ -124,6 +124,12 @@ class User {
'edit',
'editinterface',
'editprotected',
+ 'editmyoptions',
+ 'editmyprivateinfo',
+ 'editmyusercss',
+ 'editmyuserjs',
+ 'editmywatchlist',
+ 'editsemiprotected',
'editusercssjs', #deprecated
'editusercss',
'edituserjs',
@@ -164,6 +170,8 @@ class User {
'upload_by_url',
'userrights',
'userrights-interwiki',
+ 'viewmyprivateinfo',
+ 'viewmywatchlist',
'writeapi',
);
/**
@@ -251,7 +259,7 @@ class User {
}
/**
- * @return String
+ * @return string
*/
function __toString() {
return $this->getName();
@@ -266,7 +274,7 @@ class User {
}
wfProfileIn( __METHOD__ );
- # Set it now to avoid infinite recursion in accessors
+ // Set it now to avoid infinite recursion in accessors
$this->mLoadedItems = true;
switch ( $this->mFrom ) {
@@ -276,7 +284,7 @@ class User {
case 'name':
$this->mId = self::idFromName( $this->mName );
if ( !$this->mId ) {
- # Nonexistent user placeholder object
+ // Nonexistent user placeholder object
$this->loadDefaults( $this->mName );
} else {
$this->loadFromId();
@@ -286,7 +294,7 @@ class User {
$this->loadFromId();
break;
case 'session':
- if( !$this->loadFromSession() ) {
+ if ( !$this->loadFromSession() ) {
// Loading from session failed. Load defaults.
$this->loadDefaults();
}
@@ -301,7 +309,7 @@ class User {
/**
* Load user table data, given mId has already been set.
- * @return Bool false if the ID does not exist, true otherwise
+ * @return bool false if the ID does not exist, true otherwise
*/
public function loadFromId() {
global $wgMemc;
@@ -310,25 +318,25 @@ class User {
return false;
}
- # Try cache
+ // Try cache
$key = wfMemcKey( 'user', 'id', $this->mId );
$data = $wgMemc->get( $key );
if ( !is_array( $data ) || $data['mVersion'] < MW_USER_VERSION ) {
- # Object is expired, load from DB
+ // Object is expired, load from DB
$data = false;
}
if ( !$data ) {
wfDebug( "User: cache miss for user {$this->mId}\n" );
- # Load from DB
+ // Load from DB
if ( !$this->loadFromDatabase() ) {
- # Can't load from ID, user is anonymous
+ // Can't load from ID, user is anonymous
return false;
}
$this->saveToCache();
} else {
wfDebug( "User: got user {$this->mId} from cache\n" );
- # Restore from cache
+ // Restore from cache
foreach ( self::$mCacheVars as $name ) {
$this->$name = $data[$name];
}
@@ -370,14 +378,14 @@ class User {
* you have both an ID and a name handy.
*
* @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.
+ * @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|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.
+ * (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.
*/
public static function newFromName( $name, $validate = 'valid' ) {
if ( $validate === true ) {
@@ -387,7 +395,7 @@ class User {
if ( $name === false ) {
return false;
} else {
- # Create unloaded user object
+ // Create unloaded user object
$u = new User;
$u->mName = $name;
$u->mFrom = 'name';
@@ -418,7 +426,7 @@ class User {
* If the code is invalid or has expired, returns NULL.
*
* @param string $code Confirmation code
- * @return User object, or null
+ * @return User|null
*/
public static function newFromConfirmationCode( $code ) {
$dbr = wfGetDB( DB_SLAVE );
@@ -426,7 +434,7 @@ class User {
'user_email_token' => md5( $code ),
'user_email_token_expires > ' . $dbr->addQuotes( $dbr->timestamp() ),
) );
- if( $id !== false ) {
+ if ( $id !== false ) {
return User::newFromId( $id );
} else {
return null;
@@ -437,7 +445,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 omitted.
+ * @param WebRequest $request Object to use; $wgRequest will be used if omitted.
* @return User object
*/
public static function newFromSession( WebRequest $request = null ) {
@@ -472,7 +480,7 @@ class User {
/**
* Get the username corresponding to a given user ID
* @param int $id User ID
- * @return String|bool The corresponding username
+ * @return string|bool The corresponding username
*/
public static function whoIs( $id ) {
return UserCache::singleton()->getProp( $id, 'name' );
@@ -482,7 +490,7 @@ class User {
* Get the real name of a user given their user ID
*
* @param int $id User ID
- * @return String|bool The corresponding user's real name
+ * @return string|bool The corresponding user's real name
*/
public static function whoIsReal( $id ) {
return UserCache::singleton()->getProp( $id, 'real_name' );
@@ -491,12 +499,12 @@ class User {
/**
* Get database id given a user name
* @param string $name Username
- * @return Int|Null The corresponding user's ID, or null if user is nonexistent
+ * @return int|null The corresponding user's ID, or null if user is nonexistent
*/
public static function idFromName( $name ) {
$nt = Title::makeTitleSafe( NS_USER, $name );
- if( is_null( $nt ) ) {
- # Illegal name
+ if ( is_null( $nt ) ) {
+ // Illegal name
return null;
}
@@ -542,8 +550,8 @@ 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 string $name to match
- * @return Bool
+ * @param string $name 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 );
@@ -557,8 +565,8 @@ class User {
* is longer than the maximum allowed username size or doesn't begin with
* a capital letter.
*
- * @param string $name to match
- * @return Bool
+ * @param string $name Name to match
+ * @return bool
*/
public static function isValidUserName( $name ) {
global $wgContLang, $wgMaxNameChars;
@@ -576,7 +584,7 @@ class User {
// 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 );
- if( is_null( $parsed )
+ if ( is_null( $parsed )
|| $parsed->getNamespace()
|| strcmp( $name, $parsed->getPrefixedText() ) ) {
wfDebugLog( 'username', __METHOD__ .
@@ -594,7 +602,7 @@ class User {
'\x{3000}' . # ideographic space
'\x{e000}-\x{f8ff}' . # private use
']/u';
- if( preg_match( $unicodeBlacklist, $name ) ) {
+ if ( preg_match( $unicodeBlacklist, $name ) ) {
wfDebugLog( 'username', __METHOD__ .
": '$name' invalid due to blacklisted characters" );
return false;
@@ -611,8 +619,8 @@ class User {
* If an account already exists in this form, login will be blocked
* by a failure to pass this function.
*
- * @param string $name to match
- * @return Bool
+ * @param string $name Name to match
+ * @return bool
*/
public static function isUsableName( $name ) {
global $wgReservedUsernames;
@@ -649,7 +657,7 @@ class User {
* isValidUserName() to avoid disrupting existing accounts.
*
* @param string $name to match
- * @return Bool
+ * @return bool
*/
public static function isCreatableName( $name ) {
global $wgInvalidUsernameCharacters;
@@ -657,15 +665,15 @@ class User {
// Ensure that the username isn't longer than 235 bytes, so that
// (at least for the builtin skins) user javascript and css files
// will work. (bug 23080)
- if( strlen( $name ) > 235 ) {
+ if ( strlen( $name ) > 235 ) {
wfDebugLog( 'username', __METHOD__ .
": '$name' invalid due to length" );
return false;
}
// Preg yells if you try to give it an empty string
- if( $wgInvalidUsernameCharacters !== '' ) {
- if( preg_match( '/[' . preg_quote( $wgInvalidUsernameCharacters, '/' ) . ']/', $name ) ) {
+ if ( $wgInvalidUsernameCharacters !== '' ) {
+ if ( preg_match( '/[' . preg_quote( $wgInvalidUsernameCharacters, '/' ) . ']/', $name ) ) {
wfDebugLog( 'username', __METHOD__ .
": '$name' invalid due to wgInvalidUsernameCharacters" );
return false;
@@ -679,7 +687,7 @@ class User {
* Is the input a valid password for this user?
*
* @param string $password Desired password
- * @return Bool
+ * @return bool
*/
public function isValidPassword( $password ) {
//simple boolean wrapper for getPasswordValidity
@@ -702,15 +710,16 @@ class User {
$result = false; //init $result to false for the internal checks
- if( !wfRunHooks( 'isValidPassword', array( $password, &$result, $this ) ) )
+ if ( !wfRunHooks( 'isValidPassword', array( $password, &$result, $this ) ) ) {
return $result;
+ }
if ( $result === false ) {
- if( strlen( $password ) < $wgMinimalPasswordLength ) {
+ if ( strlen( $password ) < $wgMinimalPasswordLength ) {
return 'passwordtooshort';
} elseif ( $wgContLang->lc( $password ) == $wgContLang->lc( $this->mName ) ) {
return 'password-name-match';
- } elseif ( isset( $blockedLogins[ $this->getName() ] ) && $password == $blockedLogins[ $this->getName() ] ) {
+ } elseif ( isset( $blockedLogins[$this->getName()] ) && $password == $blockedLogins[$this->getName()] ) {
return 'password-login-forbidden';
} else {
//it seems weird returning true here, but this is because of the
@@ -719,7 +728,7 @@ class User {
//a valid password.
return true;
}
- } elseif( $result === true ) {
+ } elseif ( $result === true ) {
return true;
} else {
return $result; //the isValidPassword hook set a string $result and returned true
@@ -730,7 +739,7 @@ class User {
* Does a string look like an e-mail address?
*
* This validates an email address using an HTML5 specification found at:
- * http://www.whatwg.org/specs/web-apps/current-work/multipage/states-of-the-type-attribute.html#valid-e-mail-address
+ * http://www.whatwg.org/html/states-of-the-type-attribute.html#valid-e-mail-address
* Which as of 2011-01-24 says:
*
* A valid e-mail address is a string that matches the ABNF production
@@ -750,7 +759,7 @@ class User {
* pass validation here.
*
* @param string $addr E-mail address
- * @return Bool
+ * @return bool
* @deprecated since 1.18 call Sanitizer::isValidEmail() directly
*/
public static function isValidEmailAddr( $addr ) {
@@ -762,7 +771,7 @@ class User {
* Given unvalidated user input, return a canonical username, or false if
* the username is invalid.
* @param string $name User input
- * @param string|Bool $validate type of validation to use:
+ * @param string|bool $validate type of validation to use:
* - false No validation
* - 'valid' Valid for batch processes
* - 'usable' Valid for batch processes and login
@@ -772,25 +781,26 @@ class User {
* @return bool|string
*/
public static function getCanonicalName( $name, $validate = 'valid' ) {
- # Force usernames to capital
+ // Force usernames to capital
global $wgContLang;
$name = $wgContLang->ucfirst( $name );
# Reject names containing '#'; these will be cleaned up
# with title normalisation, but then it's too late to
# check elsewhere
- if( strpos( $name, '#' ) !== false )
+ if ( strpos( $name, '#' ) !== false ) {
return false;
+ }
- # Clean up name according to title rules
+ // Clean up name according to title rules
$t = ( $validate === 'valid' ) ?
Title::newFromText( $name ) : Title::makeTitle( NS_USER, $name );
- # Check for invalid titles
- if( is_null( $t ) ) {
+ // Check for invalid titles
+ if ( is_null( $t ) ) {
return false;
}
- # Reject various classes of invalid names
+ // Reject various classes of invalid names
global $wgAuth;
$name = $wgAuth->getCanonicalName( $t->getText() );
@@ -822,7 +832,7 @@ class User {
* Count the number of edits of a user
*
* @param int $uid User ID to check
- * @return Int the user's edit count
+ * @return int The user's edit count
*
* @deprecated since 1.21 in favour of User::getEditCount
*/
@@ -835,7 +845,7 @@ class User {
/**
* Return a random password.
*
- * @return String new random password
+ * @return string New random password
*/
public static function randomPassword() {
global $wgMinimalPasswordLength;
@@ -870,7 +880,7 @@ class User {
$this->mOptionsLoaded = false;
$loggedOut = $this->getRequest()->getCookie( 'LoggedOut' );
- if( $loggedOut !== null ) {
+ if ( $loggedOut !== null ) {
$this->mTouched = wfTimestamp( TS_MW, $loggedOut );
} else {
$this->mTouched = '1'; # Allow any pages to be cached
@@ -898,7 +908,7 @@ class User {
* @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
+ * @return boolean
*/
public function isItemLoaded( $item, $all = 'all' ) {
return ( $this->mLoadedItems === true && $all === 'all' ) ||
@@ -908,9 +918,9 @@ class User {
/**
* Set that an item has been loaded
*
- * @param $item String
+ * @param string $item
*/
- private function setItemLoaded( $item ) {
+ protected function setItemLoaded( $item ) {
if ( is_array( $this->mLoadedItems ) ) {
$this->mLoadedItems[$item] = true;
}
@@ -918,25 +928,15 @@ class User {
/**
* Load user data from the session or login cookie.
- * @return Bool True if the user is logged in, false otherwise.
+ * @return bool True if the user is logged in, false otherwise.
*/
private function loadFromSession() {
- global $wgExternalAuthType, $wgAutocreatePolicy;
-
$result = null;
wfRunHooks( 'UserLoadFromSession', array( $this, &$result ) );
if ( $result !== null ) {
return $result;
}
- if ( $wgExternalAuthType && $wgAutocreatePolicy == 'view' ) {
- $extUser = ExternalUser::newFromCookie();
- if ( $extUser ) {
- # TODO: Automatically create the user here (or probably a bit
- # lower down, in fact)
- }
- }
-
$request = $this->getRequest();
$cookieId = $request->getCookie( 'UserID' );
@@ -944,7 +944,7 @@ class User {
if ( $cookieId !== null ) {
$sId = intval( $cookieId );
- if( $sessId !== null && $cookieId != $sessId ) {
+ if ( $sessId !== null && $cookieId != $sessId ) {
wfDebugLog( 'loginSessions', "Session user ID ($sessId) and
cookie user ID ($sId) don't match!" );
return false;
@@ -967,13 +967,13 @@ class User {
$proposedUser = User::newFromId( $sId );
if ( !$proposedUser->isLoggedIn() ) {
- # Not a valid ID
+ // Not a valid ID
return false;
}
global $wgBlockDisablesLogin;
- if( $wgBlockDisablesLogin && $proposedUser->isBlocked() ) {
- # User blocked and we've disabled blocked user logins
+ if ( $wgBlockDisablesLogin && $proposedUser->isBlocked() ) {
+ // User blocked and we've disabled blocked user logins
return false;
}
@@ -987,7 +987,7 @@ class User {
$passwordCorrect = ( strlen( $token ) && $token === $request->getCookie( 'Token' ) );
$from = 'cookie';
} else {
- # No session or persistent login cookie
+ // No session or persistent login cookie
return false;
}
@@ -997,7 +997,7 @@ class User {
wfDebug( "User: logged in from $from\n" );
return true;
} else {
- # Invalid credentials
+ // Invalid credentials
wfDebug( "User: can't log in from $from, invalid credentials\n" );
return false;
}
@@ -1007,14 +1007,14 @@ class User {
* Load user and user_group data from the database.
* $this->mId must be set, this is how the user is identified.
*
- * @return Bool True if the user exists, false if the user is anonymous
+ * @return bool True if the user exists, false if the user is anonymous
*/
public function loadFromDatabase() {
- # Paranoia
+ // Paranoia
$this->mId = intval( $this->mId );
- /** Anonymous user */
- if( !$this->mId ) {
+ // Anonymous user
+ if ( !$this->mId ) {
$this->loadDefaults();
return false;
}
@@ -1025,13 +1025,13 @@ class User {
wfRunHooks( 'UserLoadFromDatabase', array( $this, &$s ) );
if ( $s !== false ) {
- # Initialise user table data
+ // Initialise user table data
$this->loadFromRow( $s );
$this->mGroups = null; // deferred
$this->getEditCount(); // revalidation for nulls
return true;
} else {
- # Invalid user_id
+ // Invalid user_id
$this->mId = 0;
$this->loadDefaults();
return false;
@@ -1162,16 +1162,20 @@ class User {
* @see $wgAutopromoteOnce
*/
public function addAutopromoteOnceGroups( $event ) {
- global $wgAutopromoteOnceLogInRC;
+ global $wgAutopromoteOnceLogInRC, $wgAuth;
$toPromote = array();
if ( $this->getId() ) {
$toPromote = Autopromote::getAutopromoteOnceGroups( $this, $event );
if ( count( $toPromote ) ) {
$oldGroups = $this->getGroups(); // previous groups
+
foreach ( $toPromote as $group ) {
$this->addGroup( $group );
}
+ // update groups in external authentication database
+ $wgAuth->updateExternalDBGroups( $this, $toPromote );
+
$newGroups = array_merge( $oldGroups, $toPromote ); // all groups
$logEntry = new ManualLogEntry( 'rights', 'autopromote' );
@@ -1194,7 +1198,7 @@ class User {
* 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
+ * @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.
*/
@@ -1235,11 +1239,13 @@ class User {
}
$defOpt = $wgDefaultUserOptions;
- # default language setting
- $defOpt['variant'] = $wgContLang->getCode();
+ // Default language setting
$defOpt['language'] = $wgContLang->getCode();
- foreach( SearchEngine::searchableNamespaces() as $nsnum => $nsname ) {
- $defOpt['searchNs'.$nsnum] = !empty( $wgNamespacesToBeSearchedDefault[$nsnum] );
+ foreach ( LanguageConverter::$languagesWithVariants as $langCode ) {
+ $defOpt[$langCode == $wgContLang->getCode() ? 'variant' : "variant-$langCode"] = $langCode;
+ }
+ foreach ( SearchEngine::searchableNamespaces() as $nsnum => $nsname ) {
+ $defOpt['searchNs' . $nsnum] = !empty( $wgNamespacesToBeSearchedDefault[$nsnum] );
}
$defOpt['skin'] = $wgDefaultSkin;
@@ -1252,11 +1258,11 @@ class User {
* Get a given default option value.
*
* @param string $opt Name of option to retrieve
- * @return String Default option value
+ * @return string Default option value
*/
public static function getDefaultOption( $opt ) {
$defOpts = self::getDefaultOptions();
- if( isset( $defOpts[$opt] ) ) {
+ if ( isset( $defOpts[$opt] ) ) {
return $defOpts[$opt];
} else {
return null;
@@ -1271,14 +1277,14 @@ class User {
* done against master.
*/
private function getBlockedStatus( $bFromSlave = true ) {
- global $wgProxyWhitelist, $wgUser;
+ global $wgProxyWhitelist, $wgUser, $wgApplyIpBlocksToXff;
if ( -1 != $this->mBlockedby ) {
return;
}
wfProfileIn( __METHOD__ );
- wfDebug( __METHOD__.": checking...\n" );
+ wfDebug( __METHOD__ . ": checking...\n" );
// Initialize data...
// Otherwise something ends up stomping on $this->mBlockedby when
@@ -1296,14 +1302,14 @@ class User {
$ip = null;
}
- # User/IP blocking
+ // User/IP blocking
$block = Block::newFromTarget( $this, $ip, !$bFromSlave );
- # Proxy blocking
+ // Proxy blocking
if ( !$block instanceof Block && $ip !== null && !$this->isAllowed( 'proxyunbannable' )
&& !in_array( $ip, $wgProxyWhitelist ) )
{
- # Local list
+ // Local list
if ( self::isLocallyBlockedProxy( $ip ) ) {
$block = new Block;
$block->setBlocker( wfMessage( 'proxyblocker' )->text() );
@@ -1317,6 +1323,25 @@ class User {
}
}
+ // (bug 23343) Apply IP blocks to the contents of XFF headers, if enabled
+ if ( !$block instanceof Block
+ && $wgApplyIpBlocksToXff
+ && $ip !== null
+ && !$this->isAllowed( 'proxyunbannable' )
+ && !in_array( $ip, $wgProxyWhitelist )
+ ) {
+ $xff = $this->getRequest()->getHeader( 'X-Forwarded-For' );
+ $xff = array_map( 'trim', explode( ',', $xff ) );
+ $xff = array_diff( $xff, array( $ip ) );
+ $xffblocks = Block::getBlocksForIPList( $xff, $this->isAnon(), !$bFromSlave );
+ $block = Block::chooseBlock( $xffblocks, $xff );
+ if ( $block instanceof Block ) {
+ # Mangle the reason to alert the user that the block
+ # originated from matching the X-Forwarded-For header.
+ $block->mReason = wfMessage( 'xffblockreason', $block->mReason )->text();
+ }
+ }
+
if ( $block instanceof Block ) {
wfDebug( __METHOD__ . ": Found block.\n" );
$this->mBlock = $block;
@@ -1330,7 +1355,7 @@ class User {
$this->mAllowUsertalk = false;
}
- # Extensions
+ // Extensions
wfRunHooks( 'GetBlockedStatus', array( &$this ) );
wfProfileOut( __METHOD__ );
@@ -1341,17 +1366,19 @@ class User {
*
* @param string $ip IP to check
* @param bool $checkWhitelist whether to check the whitelist first
- * @return Bool True if blacklisted.
+ * @return bool True if blacklisted.
*/
public function isDnsBlacklisted( $ip, $checkWhitelist = false ) {
global $wgEnableSorbs, $wgEnableDnsBlacklist,
$wgSorbsUrl, $wgDnsBlacklistUrls, $wgProxyWhitelist;
- if ( !$wgEnableDnsBlacklist && !$wgEnableSorbs )
+ if ( !$wgEnableDnsBlacklist && !$wgEnableSorbs ) {
return false;
+ }
- if ( $checkWhitelist && in_array( $ip, $wgProxyWhitelist ) )
+ if ( $checkWhitelist && in_array( $ip, $wgProxyWhitelist ) ) {
return false;
+ }
$urls = array_merge( $wgDnsBlacklistUrls, (array)$wgSorbsUrl );
return $this->inDnsBlacklist( $ip, $urls );
@@ -1362,23 +1389,23 @@ class User {
*
* @param string $ip IP to check
* @param string|array $bases of Strings: URL of the DNS blacklist
- * @return Bool True if blacklisted.
+ * @return bool True if blacklisted.
*/
public function inDnsBlacklist( $ip, $bases ) {
wfProfileIn( __METHOD__ );
$found = false;
// @todo FIXME: IPv6 ??? (http://bugs.php.net/bug.php?id=33170)
- if( IP::isIPv4( $ip ) ) {
- # Reverse IP, bug 21255
+ if ( IP::isIPv4( $ip ) ) {
+ // Reverse IP, bug 21255
$ipReversed = implode( '.', array_reverse( explode( '.', $ip ) ) );
- foreach( (array)$bases as $base ) {
- # Make hostname
- # If we have an access key, use that too (ProjectHoneypot, etc.)
- if( is_array( $base ) ) {
- if( count( $base ) >= 2 ) {
- # Access key is 1, base URL is 0
+ foreach ( (array)$bases as $base ) {
+ // Make hostname
+ // If we have an access key, use that too (ProjectHoneypot, etc.)
+ if ( is_array( $base ) ) {
+ if ( count( $base ) >= 2 ) {
+ // Access key is 1, base URL is 0
$host = "{$base[1]}.$ipReversed.{$base[0]}";
} else {
$host = "$ipReversed.{$base[0]}";
@@ -1387,10 +1414,10 @@ class User {
$host = "$ipReversed.$base";
}
- # Send query
+ // Send query
$ipList = gethostbynamel( $host );
- if( $ipList ) {
+ if ( $ipList ) {
wfDebugLog( 'dnsblacklist', "Hostname $host is {$ipList[0]}, it's a proxy says $base!\n" );
$found = true;
break;
@@ -1420,7 +1447,7 @@ class User {
wfProfileIn( __METHOD__ );
if ( !is_array( $wgProxyList ) ) {
- # Load from the specified file
+ // Load from the specified file
$wgProxyList = array_map( 'trim', file( $wgProxyList ) );
}
@@ -1429,7 +1456,7 @@ class User {
} elseif ( array_search( $ip, $wgProxyList ) !== false ) {
$ret = true;
} elseif ( array_key_exists( $ip, $wgProxyList ) ) {
- # Old-style flipped proxy list
+ // Old-style flipped proxy list
$ret = true;
} else {
$ret = false;
@@ -1441,11 +1468,11 @@ class User {
/**
* Is this user subject to rate limiting?
*
- * @return Bool True if rate limited
+ * @return bool True if rate limited
*/
public function isPingLimitable() {
global $wgRateLimitsExcludedIPs;
- if( in_array( $this->getRequest()->getIP(), $wgRateLimitsExcludedIPs ) ) {
+ if ( in_array( $this->getRequest()->getIP(), $wgRateLimitsExcludedIPs ) ) {
// No other good way currently to disable rate limits
// for specific IPs. :P
// But this is a crappy hack and should die.
@@ -1462,23 +1489,25 @@ class User {
* last-hit counters will be shared across wikis.
*
* @param string $action Action to enforce; 'edit' if unspecified
- * @return Bool True if a rate limiter was tripped
+ * @param integer $incrBy Positive amount to increment counter by [defaults to 1]
+ * @return bool True if a rate limiter was tripped
*/
- public function pingLimiter( $action = 'edit' ) {
- # Call the 'PingLimiter' hook
+ public function pingLimiter( $action = 'edit', $incrBy = 1 ) {
+ // Call the 'PingLimiter' hook
$result = false;
- if( !wfRunHooks( 'PingLimiter', array( &$this, $action, &$result ) ) ) {
+ if ( !wfRunHooks( 'PingLimiter', array( &$this, $action, &$result, $incrBy ) ) ) {
return $result;
}
global $wgRateLimits;
- if( !isset( $wgRateLimits[$action] ) ) {
+ if ( !isset( $wgRateLimits[$action] ) ) {
return false;
}
- # Some groups shouldn't trigger the ping limiter, ever
- if( !$this->isPingLimitable() )
+ // Some groups shouldn't trigger the ping limiter, ever
+ if ( !$this->isPingLimitable() ) {
return false;
+ }
global $wgMemc, $wgRateLimitLog;
wfProfileIn( __METHOD__ );
@@ -1486,27 +1515,37 @@ class User {
$limits = $wgRateLimits[$action];
$keys = array();
$id = $this->getId();
- $ip = $this->getRequest()->getIP();
$userLimit = false;
- if( isset( $limits['anon'] ) && $id == 0 ) {
+ if ( isset( $limits['anon'] ) && $id == 0 ) {
$keys[wfMemcKey( 'limiter', $action, 'anon' )] = $limits['anon'];
}
- if( isset( $limits['user'] ) && $id != 0 ) {
+ if ( isset( $limits['user'] ) && $id != 0 ) {
$userLimit = $limits['user'];
}
- if( $this->isNewbie() ) {
- if( isset( $limits['newbie'] ) && $id != 0 ) {
+ if ( $this->isNewbie() ) {
+ if ( isset( $limits['newbie'] ) && $id != 0 ) {
$keys[wfMemcKey( 'limiter', $action, 'user', $id )] = $limits['newbie'];
}
- if( isset( $limits['ip'] ) ) {
+ if ( isset( $limits['ip'] ) ) {
+ $ip = $this->getRequest()->getIP();
$keys["mediawiki:limiter:$action:ip:$ip"] = $limits['ip'];
}
- $matches = array();
- if( isset( $limits['subnet'] ) && preg_match( '/^(\d+\.\d+\.\d+)\.\d+$/', $ip, $matches ) ) {
- $subnet = $matches[1];
- $keys["mediawiki:limiter:$action:subnet:$subnet"] = $limits['subnet'];
+ if ( isset( $limits['subnet'] ) ) {
+ $ip = $this->getRequest()->getIP();
+ $matches = array();
+ $subnet = false;
+ if ( IP::isIPv6( $ip ) ) {
+ $parts = IP::parseRange( "$ip/64" );
+ $subnet = $parts[0];
+ } elseif ( preg_match( '/^(\d+\.\d+\.\d+)\.\d+$/', $ip, $matches ) ) {
+ // IPv4
+ $subnet = $matches[1];
+ }
+ if ( $subnet !== false ) {
+ $keys["mediawiki:limiter:$action:subnet:$subnet"] = $limits['subnet'];
+ }
}
}
// Check for group-specific permissions
@@ -1520,20 +1559,21 @@ class User {
}
// Set the user limit key
if ( $userLimit !== false ) {
- wfDebug( __METHOD__ . ": effective user limit: $userLimit\n" );
- $keys[ wfMemcKey( 'limiter', $action, 'user', $id ) ] = $userLimit;
+ list( $max, $period ) = $userLimit;
+ wfDebug( __METHOD__ . ": effective user limit: $max in {$period}s\n" );
+ $keys[wfMemcKey( 'limiter', $action, 'user', $id )] = $userLimit;
}
$triggered = false;
- foreach( $keys as $key => $limit ) {
+ foreach ( $keys as $key => $limit ) {
list( $max, $period ) = $limit;
$summary = "(limit $max in {$period}s)";
$count = $wgMemc->get( $key );
// Already pinged?
- if( $count ) {
- if( $count >= $max ) {
+ if ( $count ) {
+ if ( $count >= $max ) {
wfDebug( __METHOD__ . ": tripped! $key at $count $summary\n" );
- if( $wgRateLimitLog ) {
+ if ( $wgRateLimitLog ) {
wfSuppressWarnings();
file_put_contents( $wgRateLimitLog, wfTimestamp( TS_MW ) . ' ' . wfWikiID() . ': ' . $this->getName() . " tripped $key at $count $summary\n", FILE_APPEND );
wfRestoreWarnings();
@@ -1544,9 +1584,13 @@ class User {
}
} else {
wfDebug( __METHOD__ . ": adding record for $key $summary\n" );
- $wgMemc->add( $key, 0, intval( $period ) ); // first ping
+ if ( $incrBy > 0 ) {
+ $wgMemc->add( $key, 0, intval( $period ) ); // first ping
+ }
+ }
+ if ( $incrBy > 0 ) {
+ $wgMemc->incr( $key, $incrBy );
}
- $wgMemc->incr( $key );
}
wfProfileOut( __METHOD__ );
@@ -1557,7 +1601,7 @@ class User {
* Check if user is blocked
*
* @param bool $bFromSlave Whether to check the slave database instead of the master
- * @return Bool True if blocked, false otherwise
+ * @return bool True if blocked, false otherwise
*/
public function isBlocked( $bFromSlave = true ) { // hacked from false due to horrible probs on site
return $this->getBlock( $bFromSlave ) instanceof Block && $this->getBlock()->prevents( 'edit' );
@@ -1577,9 +1621,9 @@ class User {
/**
* Check if user is blocked from editing a particular article
*
- * @param $title Title to check
+ * @param Title $title Title to check
* @param bool $bFromSlave whether to check the slave database instead of the master
- * @return Bool
+ * @return bool
*/
function isBlockedFrom( $title, $bFromSlave = false ) {
global $wgBlockAllowsUTEdit;
@@ -1587,7 +1631,7 @@ class User {
$blocked = $this->isBlocked( $bFromSlave );
$allowUsertalk = ( $wgBlockAllowsUTEdit ? $this->mAllowUsertalk : false );
- # If a user's name is suppressed, they cannot make edits anywhere
+ // If a user's name is suppressed, they cannot make edits anywhere
if ( !$this->mHideName && $allowUsertalk && $title->getText() === $this->getName() &&
$title->getNamespace() == NS_USER_TALK ) {
$blocked = false;
@@ -1602,7 +1646,7 @@ class User {
/**
* If user is blocked, return the name of the user who placed the block
- * @return String name of blocker
+ * @return string Name of blocker
*/
public function blockedBy() {
$this->getBlockedStatus();
@@ -1611,7 +1655,7 @@ class User {
/**
* If user is blocked, return the specified reason for the block
- * @return String Blocking reason
+ * @return string Blocking reason
*/
public function blockedFor() {
$this->getBlockedStatus();
@@ -1620,7 +1664,7 @@ class User {
/**
* If user is blocked, return the ID for the block
- * @return Int Block ID
+ * @return int Block ID
*/
public function getBlockId() {
$this->getBlockedStatus();
@@ -1633,16 +1677,16 @@ class User {
* This is intended for quick UI checks.
*
* @param string $ip IP address, uses current client if none given
- * @return Bool True if blocked, false otherwise
+ * @return bool True if blocked, false otherwise
*/
public function isBlockedGlobally( $ip = '' ) {
- if( $this->mBlockedGlobally !== null ) {
+ if ( $this->mBlockedGlobally !== null ) {
return $this->mBlockedGlobally;
}
// User is already an IP?
- if( IP::isIPAddress( $this->getName() ) ) {
+ if ( IP::isIPAddress( $this->getName() ) ) {
$ip = $this->getName();
- } elseif( !$ip ) {
+ } elseif ( !$ip ) {
$ip = $this->getRequest()->getIP();
}
$blocked = false;
@@ -1654,13 +1698,14 @@ class User {
/**
* Check if user account is locked
*
- * @return Bool True if locked, false otherwise
+ * @return bool True if locked, false otherwise
*/
public function isLocked() {
- if( $this->mLocked !== null ) {
+ if ( $this->mLocked !== null ) {
return $this->mLocked;
}
global $wgAuth;
+ StubObject::unstub( $wgAuth );
$authUser = $wgAuth->getUserInstance( $this );
$this->mLocked = (bool)$authUser->isLocked();
return $this->mLocked;
@@ -1669,15 +1714,16 @@ class User {
/**
* Check if user account is hidden
*
- * @return Bool True if hidden, false otherwise
+ * @return bool True if hidden, false otherwise
*/
public function isHidden() {
- if( $this->mHideName !== null ) {
+ if ( $this->mHideName !== null ) {
return $this->mHideName;
}
$this->getBlockedStatus();
- if( !$this->mHideName ) {
+ if ( !$this->mHideName ) {
global $wgAuth;
+ StubObject::unstub( $wgAuth );
$authUser = $wgAuth->getUserInstance( $this );
$this->mHideName = (bool)$authUser->isHidden();
}
@@ -1686,14 +1732,13 @@ class User {
/**
* Get the user's ID.
- * @return Int The user's ID; 0 if the user is anonymous or nonexistent
+ * @return int The user's ID; 0 if the user is anonymous or nonexistent
*/
public function getId() {
- if( $this->mId === null && $this->mName !== null
- && User::isIP( $this->mName ) ) {
+ if ( $this->mId === null && $this->mName !== null && User::isIP( $this->mName ) ) {
// Special case, we know the user is anonymous
return 0;
- } elseif( !$this->isItemLoaded( 'id' ) ) {
+ } elseif ( !$this->isItemLoaded( 'id' ) ) {
// Don't load if this was initialized from an ID
$this->load();
}
@@ -1711,16 +1756,16 @@ class User {
/**
* Get the user name, or the IP of an anonymous user
- * @return String User's name or IP address
+ * @return string User's name or IP address
*/
public function getName() {
if ( $this->isItemLoaded( 'name', 'only' ) ) {
- # Special case optimisation
+ // Special case optimisation
return $this->mName;
} else {
$this->load();
if ( $this->mName === false ) {
- # Clean up IPs
+ // Clean up IPs
$this->mName = IP::sanitizeIP( $this->getRequest()->getIP() );
}
return $this->mName;
@@ -1747,7 +1792,7 @@ class User {
/**
* Get the user's name escaped by underscores.
- * @return String Username escaped by underscores.
+ * @return string Username escaped by underscores.
*/
public function getTitleKey() {
return str_replace( ' ', '_', $this->getName() );
@@ -1755,27 +1800,27 @@ class User {
/**
* Check if the user has new messages.
- * @return Bool True if the user has new messages
+ * @return bool True if the user has new messages
*/
public function getNewtalk() {
$this->load();
- # Load the newtalk status if it is unloaded (mNewtalk=-1)
- if( $this->mNewtalk === -1 ) {
+ // Load the newtalk status if it is unloaded (mNewtalk=-1)
+ if ( $this->mNewtalk === -1 ) {
$this->mNewtalk = false; # reset talk page status
- # Check memcached separately for anons, who have no
- # entire User object stored in there.
- if( !$this->mId ) {
+ // Check memcached separately for anons, who have no
+ // entire User object stored in there.
+ if ( !$this->mId ) {
global $wgDisableAnonTalk;
- if( $wgDisableAnonTalk ) {
+ if ( $wgDisableAnonTalk ) {
// Anon newtalk disabled by configuration.
$this->mNewtalk = false;
} else {
global $wgMemc;
$key = wfMemcKey( 'newtalk', 'ip', $this->getName() );
$newtalk = $wgMemc->get( $key );
- if( strval( $newtalk ) !== '' ) {
+ if ( strval( $newtalk ) !== '' ) {
$this->mNewtalk = (bool)$newtalk;
} else {
// Since we are caching this, make sure it is up to date by getting it
@@ -1793,14 +1838,23 @@ class User {
}
/**
- * Return the talk page(s) this user has new messages on.
- * @return Array of String page URLs
+ * Return the data needed to construct links for new talk page message
+ * alerts. If there are new messages, this will return an associative array
+ * with the following data:
+ * wiki: The database name of the wiki
+ * link: Root-relative link to the user's talk page
+ * rev: The last talk page revision that the user has seen or null. This
+ * is useful for building diff links.
+ * If there are no new messages, it returns an empty array.
+ * @note This function was designed to accomodate multiple talk pages, but
+ * currently only returns a single link and revision.
+ * @return Array
*/
public function getNewMessageLinks() {
$talks = array();
- if( !wfRunHooks( 'UserRetrieveNewTalks', array( &$this, &$talks ) ) ) {
+ if ( !wfRunHooks( 'UserRetrieveNewTalks', array( &$this, &$talks ) ) ) {
return $talks;
- } elseif( !$this->getNewtalk() ) {
+ } elseif ( !$this->getNewtalk() ) {
return array();
}
$utp = $this->getTalkPage();
@@ -1815,13 +1869,36 @@ class User {
}
/**
+ * Get the revision ID for the last talk page revision viewed by the talk
+ * page owner.
+ * @return int|null Revision ID or null
+ */
+ public function getNewMessageRevisionId() {
+ $newMessageRevisionId = null;
+ $newMessageLinks = $this->getNewMessageLinks();
+ if ( $newMessageLinks ) {
+ // Note: getNewMessageLinks() never returns more than a single link
+ // and it is always for the same wiki, but we double-check here in
+ // case that changes some time in the future.
+ if ( count( $newMessageLinks ) === 1
+ && $newMessageLinks[0]['wiki'] === wfWikiID()
+ && $newMessageLinks[0]['rev']
+ ) {
+ $newMessageRevision = $newMessageLinks[0]['rev'];
+ $newMessageRevisionId = $newMessageRevision->getId();
+ }
+ }
+ return $newMessageRevisionId;
+ }
+
+ /**
* Internal uncached check for new messages
*
* @see getNewtalk()
* @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 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
+ * @return bool True if the user has new messages
*/
protected function checkNewtalk( $field, $id, $fromMaster = false ) {
if ( $fromMaster ) {
@@ -1837,9 +1914,9 @@ class User {
/**
* Add or update the new messages flag
* @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 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
+ * @return bool True if successful, false otherwise
*/
protected function updateNewtalk( $field, $id, $curRev = null ) {
// Get timestamp of the talk page revision prior to the current one
@@ -1863,8 +1940,8 @@ class User {
/**
* Clear the new messages flag for the given user
* @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
+ * @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 ) {
$dbw = wfGetDB( DB_MASTER );
@@ -1886,14 +1963,14 @@ class User {
* @param $curRev Revision new, as yet unseen revision of the user talk page. Ignored if null or !$val.
*/
public function setNewtalk( $val, $curRev = null ) {
- if( wfReadOnly() ) {
+ if ( wfReadOnly() ) {
return;
}
$this->load();
$this->mNewtalk = $val;
- if( $this->isAnon() ) {
+ if ( $this->isAnon() ) {
$field = 'user_ip';
$id = $this->getName();
} else {
@@ -1902,13 +1979,13 @@ class User {
}
global $wgMemc;
- if( $val ) {
+ if ( $val ) {
$changed = $this->updateNewtalk( $field, $id, $curRev );
} else {
$changed = $this->deleteNewtalk( $field, $id );
}
- if( $this->isAnon() ) {
+ if ( $this->isAnon() ) {
// Anons have a separate memcached space, since
// user records aren't kept for them.
$key = wfMemcKey( 'newtalk', 'ip', $id );
@@ -1922,7 +1999,7 @@ class User {
/**
* Generate a current or new-future timestamp to be stored in the
* user_touched field when we update things.
- * @return String Timestamp in TS_MW format
+ * @return string Timestamp in TS_MW format
*/
private static function newTouchedTimestamp() {
global $wgClockSkewFudge;
@@ -1938,7 +2015,7 @@ class User {
*/
private function clearSharedCache() {
$this->load();
- if( $this->mId ) {
+ if ( $this->mId ) {
global $wgMemc;
$wgMemc->delete( wfMemcKey( 'user', 'id', $this->mId ) );
}
@@ -1950,28 +2027,30 @@ class User {
* for reload on the next hit.
*/
public function invalidateCache() {
- if( wfReadOnly() ) {
+ if ( wfReadOnly() ) {
return;
}
$this->load();
- if( $this->mId ) {
+ if ( $this->mId ) {
$this->mTouched = self::newTouchedTimestamp();
$dbw = wfGetDB( DB_MASTER );
-
- // Prevent contention slams by checking user_touched first
- $now = $dbw->timestamp( $this->mTouched );
- $needsPurge = $dbw->selectField( 'user', '1',
- array( 'user_id' => $this->mId, 'user_touched < ' . $dbw->addQuotes( $now ) )
- );
- if ( $needsPurge ) {
- $dbw->update( 'user',
- array( 'user_touched' => $now ),
- array( 'user_id' => $this->mId, 'user_touched < ' . $dbw->addQuotes( $now ) ),
- __METHOD__
- );
- }
-
+ $userid = $this->mId;
+ $touched = $this->mTouched;
+ $method = __METHOD__;
+ $dbw->onTransactionIdle( function() use ( $dbw, $userid, $touched, $method ) {
+ // Prevent contention slams by checking user_touched first
+ $encTouched = $dbw->addQuotes( $dbw->timestamp( $touched ) );
+ $needsPurge = $dbw->selectField( 'user', '1',
+ array( 'user_id' => $userid, 'user_touched < ' . $encTouched ) );
+ if ( $needsPurge ) {
+ $dbw->update( 'user',
+ array( 'user_touched' => $dbw->timestamp( $touched ) ),
+ array( 'user_id' => $userid, 'user_touched < ' . $encTouched ),
+ $method
+ );
+ }
+ } );
$this->clearSharedCache();
}
}
@@ -1979,7 +2058,6 @@ class User {
/**
* Validate the cache for this account.
* @param string $timestamp A timestamp in TS_MW format
- *
* @return bool
*/
public function validateCache( $timestamp ) {
@@ -1989,7 +2067,7 @@ class User {
/**
* Get the user touched timestamp
- * @return String timestamp
+ * @return string timestamp
*/
public function getTouched() {
$this->load();
@@ -2015,12 +2093,12 @@ class User {
public function setPassword( $str ) {
global $wgAuth;
- if( $str !== null ) {
- if( !$wgAuth->allowPasswordChange() ) {
+ if ( $str !== null ) {
+ if ( !$wgAuth->allowPasswordChange() ) {
throw new PasswordError( wfMessage( 'password-change-forbidden' )->text() );
}
- if( !$this->isValidPassword( $str ) ) {
+ if ( !$this->isValidPassword( $str ) ) {
global $wgMinimalPasswordLength;
$valid = $this->getPasswordValidity( $str );
if ( is_array( $valid ) ) {
@@ -2034,7 +2112,7 @@ class User {
}
}
- if( !$wgAuth->setPassword( $this, $str ) ) {
+ if ( !$wgAuth->setPassword( $this, $str ) ) {
throw new PasswordError( wfMessage( 'externaldberror' )->text() );
}
@@ -2047,14 +2125,14 @@ class User {
* Set the password and reset the random token unconditionally.
*
* @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.
+ * password hash meaning that the user will not be able to log in
+ * through the web interface.
*/
public function setInternalPassword( $str ) {
$this->load();
$this->setToken();
- if( $str === null ) {
+ if ( $str === null ) {
// Save an invalid hash...
$this->mPassword = '';
} else {
@@ -2067,7 +2145,7 @@ class User {
/**
* Get the user's current token.
* @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
+ * @return string Token
*/
public function getToken( $forceCreation = true ) {
$this->load();
@@ -2109,7 +2187,7 @@ class User {
/**
* Has password reminder email been sent within the last
* $wgPasswordReminderResendTime hours?
- * @return Bool
+ * @return bool
*/
public function isPasswordReminderThrottled() {
global $wgPasswordReminderResendTime;
@@ -2123,7 +2201,7 @@ class User {
/**
* Get the user's e-mail address
- * @return String User's email address
+ * @return string User's email address
*/
public function getEmail() {
$this->load();
@@ -2133,7 +2211,7 @@ class User {
/**
* Get the timestamp of the user's e-mail authentication
- * @return String TS_MW timestamp
+ * @return string TS_MW timestamp
*/
public function getEmailAuthenticationTimestamp() {
$this->load();
@@ -2147,7 +2225,7 @@ class User {
*/
public function setEmail( $str ) {
$this->load();
- if( $str == $this->mEmail ) {
+ if ( $str == $this->mEmail ) {
return;
}
$this->mEmail = $str;
@@ -2177,11 +2255,11 @@ class User {
$this->setEmail( $str );
if ( $str !== '' && $wgEmailAuthentication ) {
- # Send a confirmation request to the new address if needed
+ // Send a confirmation request to the new address if needed
$type = $oldaddr != '' ? 'changed' : 'set';
$result = $this->sendConfirmationMail( $type );
if ( $result->isGood() ) {
- # Say the the caller that a confirmation mail has been sent
+ // Say the the caller that a confirmation mail has been sent
$result->value = 'eauth';
}
} else {
@@ -2193,7 +2271,7 @@ class User {
/**
* Get the user's real name
- * @return String User's real name
+ * @return string User's real name
*/
public function getRealName() {
if ( !$this->isItemLoaded( 'realname' ) ) {
@@ -2217,8 +2295,8 @@ class User {
*
* @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
+ * @param bool $ignoreHidden Whether to ignore the effects of $wgHiddenPrefs
+ * @return string User's current value for the option
* @see getBoolOption()
* @see getIntOption()
*/
@@ -2231,7 +2309,7 @@ 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
- if( in_array( $oname, $wgHiddenPrefs ) && !$ignoreHidden ) {
+ if ( !$ignoreHidden && in_array( $oname, $wgHiddenPrefs ) ) {
return self::getDefaultOption( $oname );
}
@@ -2257,9 +2335,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;
}
}
@@ -2271,7 +2349,7 @@ class User {
* Get the user's current setting for a given option, as a boolean value.
*
* @param string $oname The option to check
- * @return Bool User's current value for the option
+ * @return bool User's current value for the option
* @see getOption()
*/
public function getBoolOption( $oname ) {
@@ -2279,16 +2357,16 @@ class User {
}
/**
- * Get the user's current setting for a given option, as a boolean value.
+ * Get the user's current setting for a given option, as an integer value.
*
* @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
+ * @return int User's current value for the option
* @see getOption()
*/
public function getIntOption( $oname, $defaultOverride = 0 ) {
$val = $this->getOption( $oname );
- if( $val == '' ) {
+ if ( $val == '' ) {
$val = $defaultOverride;
}
return intval( $val );
@@ -2298,13 +2376,13 @@ class User {
* Set the given option for a user.
*
* @param string $oname The option to set
- * @param $val mixed New value to set
+ * @param mixed $val New value to set
*/
public function setOption( $oname, $val ) {
$this->loadOptions();
// Explicitly NULL values should refer to defaults
- if( is_null( $val ) ) {
+ if ( is_null( $val ) ) {
$val = self::getDefaultOption( $oname );
}
@@ -2312,6 +2390,49 @@ class User {
}
/**
+ * Get a token stored in the preferences (like the watchlist one),
+ * resetting it if it's empty (and saving changes).
+ *
+ * @param string $oname The option name to retrieve the token from
+ * @return string|bool User's current value for the option, or false if this option is disabled.
+ * @see resetTokenFromOption()
+ * @see getOption()
+ */
+ public function getTokenFromOption( $oname ) {
+ global $wgHiddenPrefs;
+ if ( in_array( $oname, $wgHiddenPrefs ) ) {
+ return false;
+ }
+
+ $token = $this->getOption( $oname );
+ if ( !$token ) {
+ $token = $this->resetTokenFromOption( $oname );
+ $this->saveSettings();
+ }
+ return $token;
+ }
+
+ /**
+ * Reset a token stored in the preferences (like the watchlist one).
+ * *Does not* save user's preferences (similarly to setOption()).
+ *
+ * @param string $oname The option name to reset the token in
+ * @return string|bool New token value, or false if this option is disabled.
+ * @see getTokenFromOption()
+ * @see setOption()
+ */
+ public function resetTokenFromOption( $oname ) {
+ global $wgHiddenPrefs;
+ if ( in_array( $oname, $wgHiddenPrefs ) ) {
+ return false;
+ }
+
+ $token = MWCryptRand::generateHex( 40 );
+ $this->setOption( $oname, $token );
+ return $token;
+ }
+
+ /**
* Return a list of the types of user options currently returned by
* User::getOptionKinds().
*
@@ -2400,9 +2521,9 @@ class User {
foreach ( $options as $key => $value ) {
if ( isset( $prefs[$key] ) ) {
$mapping[$key] = 'registered';
- } elseif( isset( $multiselectOptions[$key] ) ) {
+ } elseif ( isset( $multiselectOptions[$key] ) ) {
$mapping[$key] = 'registered-multiselect';
- } elseif( isset( $checkmatrixOptions[$key] ) ) {
+ } elseif ( isset( $checkmatrixOptions[$key] ) ) {
$mapping[$key] = 'registered-checkmatrix';
} elseif ( substr( $key, 0, 7 ) === 'userjs-' ) {
$mapping[$key] = 'userjs';
@@ -2422,11 +2543,11 @@ class User {
* 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.
+ * 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.
+ * does not contain 'all', passed to getOptionKinds().
+ * Defaults to RequestContext::getMain() when null.
*/
public function resetOptions(
$resetKinds = array( 'registered', 'registered-multiselect', 'registered-checkmatrix', 'unused' ),
@@ -2469,7 +2590,7 @@ class User {
/**
* Get the user's preferred date format.
- * @return String User's preferred date format
+ * @return string User's preferred date format
*/
public function getDatePreference() {
// Important migration for old data rows
@@ -2486,6 +2607,26 @@ class User {
}
/**
+ * Determine based on the wiki configuration and the user's options,
+ * whether this user must be over HTTPS no matter what.
+ *
+ * @return bool
+ */
+ public function requiresHTTPS() {
+ global $wgSecureLogin;
+ if ( !$wgSecureLogin ) {
+ return false;
+ } else {
+ $https = $this->getBoolOption( 'prefershttps' );
+ wfRunHooks( 'UserRequiresHTTPS', array( $this, &$https ) );
+ if ( $https ) {
+ $https = wfCanIPUseHTTPS( $this->getRequest()->getIP() );
+ }
+ return $https;
+ }
+ }
+
+ /**
* Get the user preferred stub threshold
*
* @return int
@@ -2494,8 +2635,8 @@ class User {
global $wgMaxArticleSize; # Maximum article size, in Kb
$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.
+ // If they have set an impossible value, disable the preference
+ // so we can use the parser cache again.
$threshold = 0;
}
return $threshold;
@@ -2540,7 +2681,7 @@ class User {
$this->getGroups(), // explicit groups
$this->getAutomaticGroups( $recache ) // implicit groups
) );
- # Hook for additional groups
+ // 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 ) );
@@ -2569,8 +2710,8 @@ class User {
) );
}
if ( $recache ) {
- # Assure data consistency with rights/groups,
- # as getEffectiveGroups() depends on this function
+ // Assure data consistency with rights/groups,
+ // as getEffectiveGroups() depends on this function
$this->mEffectiveGroups = null;
}
wfProfileOut( __METHOD__ );
@@ -2588,14 +2729,14 @@ class User {
* @return array Names of the groups the user has belonged to.
*/
public function getFormerGroups() {
- if( is_null( $this->mFormerGroups ) ) {
+ if ( is_null( $this->mFormerGroups ) ) {
$dbr = wfGetDB( DB_MASTER );
$res = $dbr->select( 'user_former_groups',
array( 'ufg_group' ),
array( 'ufg_user' => $this->mId ),
__METHOD__ );
$this->mFormerGroups = array();
- foreach( $res as $row ) {
+ foreach ( $res as $row ) {
$this->mFormerGroups[] = $row->ufg_group;
}
}
@@ -2604,7 +2745,7 @@ class User {
/**
* Get the user's edit count.
- * @return Int
+ * @return int, null for anonymous users
*/
public function getEditCount() {
if ( !$this->getId() ) {
@@ -2622,14 +2763,14 @@ class User {
__METHOD__
);
- if( $count === null ) {
+ if ( $count === null ) {
// it has not been initialized. do so.
$count = $this->initEditCount();
}
- $this->mEditCount = intval( $count );
+ $this->mEditCount = $count;
wfProfileOut( __METHOD__ );
}
- return $this->mEditCount;
+ return (int)$this->mEditCount;
}
/**
@@ -2638,9 +2779,9 @@ class User {
* @param string $group Name of the group to add
*/
public function addGroup( $group ) {
- if( wfRunHooks( 'UserAddGroup', array( $this, &$group ) ) ) {
+ if ( wfRunHooks( 'UserAddGroup', array( $this, &$group ) ) ) {
$dbw = wfGetDB( DB_MASTER );
- if( $this->getId() ) {
+ if ( $this->getId() ) {
$dbw->insert( 'user_groups',
array(
'ug_user' => $this->getID(),
@@ -2652,7 +2793,14 @@ class User {
}
$this->loadGroups();
$this->mGroups[] = $group;
- $this->mRights = User::getGroupPermissions( $this->getEffectiveGroups( true ) );
+ // In case loadGroups was not called before, we now have the right twice.
+ // Get rid of the duplicate.
+ $this->mGroups = array_unique( $this->mGroups );
+
+ // Refresh the groups caches, and clear the rights cache so it will be
+ // refreshed on the next call to $this->getRights().
+ $this->getEffectiveGroups( true );
+ $this->mRights = null;
$this->invalidateCache();
}
@@ -2664,7 +2812,7 @@ class User {
*/
public function removeGroup( $group ) {
$this->load();
- if( wfRunHooks( 'UserRemoveGroup', array( $this, &$group ) ) ) {
+ if ( wfRunHooks( 'UserRemoveGroup', array( $this, &$group ) ) ) {
$dbw = wfGetDB( DB_MASTER );
$dbw->delete( 'user_groups',
array(
@@ -2682,14 +2830,18 @@ class User {
}
$this->loadGroups();
$this->mGroups = array_diff( $this->mGroups, array( $group ) );
- $this->mRights = User::getGroupPermissions( $this->getEffectiveGroups( true ) );
+
+ // Refresh the groups caches, and clear the rights cache so it will be
+ // refreshed on the next call to $this->getRights().
+ $this->getEffectiveGroups( true );
+ $this->mRights = null;
$this->invalidateCache();
}
/**
* Get whether the user is logged in
- * @return Bool
+ * @return bool
*/
public function isLoggedIn() {
return $this->getID() != 0;
@@ -2697,7 +2849,7 @@ class User {
/**
* Get whether the user is anonymous
- * @return Bool
+ * @return bool
*/
public function isAnon() {
return !$this->isLoggedIn();
@@ -2707,14 +2859,14 @@ class User {
* Check if user is allowed to access a feature / make an action
*
* @internal param \String $varargs permissions to test
- * @return Boolean: True if user is allowed to perform *any* of the given actions
+ * @return boolean: True if user is allowed to perform *any* of the given actions
*
* @return bool
*/
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;
}
}
@@ -2728,8 +2880,8 @@ class User {
*/
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;
}
}
@@ -2738,27 +2890,28 @@ class User {
/**
* Internal mechanics of testing a permission
- * @param $action String
+ * @param string $action
* @return bool
*/
public function isAllowed( $action = '' ) {
if ( $action === '' ) {
return true; // In the spirit of DWIM
}
- # Patrolling may not be enabled
- if( $action === 'patrol' || $action === 'autopatrol' ) {
+ // Patrolling may not be enabled
+ if ( $action === 'patrol' || $action === 'autopatrol' ) {
global $wgUseRCPatrol, $wgUseNPPatrol;
- if( !$wgUseRCPatrol && !$wgUseNPPatrol )
+ if ( !$wgUseRCPatrol && !$wgUseNPPatrol ) {
return false;
+ }
}
- # Use strict parameter to avoid matching numeric 0 accidentally inserted
- # by misconfiguration: 0 == 'foo'
+ // Use strict parameter to avoid matching numeric 0 accidentally inserted
+ // by misconfiguration: 0 == 'foo'
return in_array( $action, $this->getRights(), true );
}
/**
* Check whether to enable recent changes patrol features for this user
- * @return Boolean: True or false
+ * @return boolean: True or false
*/
public function useRCPatrol() {
global $wgUseRCPatrol;
@@ -2767,11 +2920,14 @@ class User {
/**
* Check whether to enable new pages patrol features for this user
- * @return Bool True or false
+ * @return bool True or false
*/
public function useNPPatrol() {
global $wgUseRCPatrol, $wgUseNPPatrol;
- return( ( $wgUseRCPatrol || $wgUseNPPatrol ) && ( $this->isAllowedAny( 'patrol', 'patrolmarks' ) ) );
+ return (
+ ( $wgUseRCPatrol || $wgUseNPPatrol )
+ && ( $this->isAllowedAny( 'patrol', 'patrolmarks' ) )
+ );
}
/**
@@ -2802,11 +2958,14 @@ class User {
/**
* Get a WatchedItem for this user and $title.
*
+ * @since 1.22 $checkRights parameter added
* @param $title Title
+ * @param $checkRights int Whether to check 'viewmywatchlist'/'editmywatchlist' rights.
+ * Pass WatchedItem::CHECK_USER_RIGHTS or WatchedItem::IGNORE_USER_RIGHTS.
* @return WatchedItem
*/
- public function getWatchedItem( $title ) {
- $key = $title->getNamespace() . ':' . $title->getDBkey();
+ public function getWatchedItem( $title, $checkRights = WatchedItem::CHECK_USER_RIGHTS ) {
+ $key = $checkRights . ':' . $title->getNamespace() . ':' . $title->getDBkey();
if ( isset( $this->mWatchedItems[$key] ) ) {
return $this->mWatchedItems[$key];
@@ -2816,34 +2975,43 @@ class User {
$this->mWatchedItems = array();
}
- $this->mWatchedItems[$key] = WatchedItem::fromUserTitle( $this, $title );
+ $this->mWatchedItems[$key] = WatchedItem::fromUserTitle( $this, $title, $checkRights );
return $this->mWatchedItems[$key];
}
/**
* Check the watched status of an article.
+ * @since 1.22 $checkRights parameter added
* @param $title Title of the article to look at
- * @return Bool
+ * @param $checkRights int Whether to check 'viewmywatchlist'/'editmywatchlist' rights.
+ * Pass WatchedItem::CHECK_USER_RIGHTS or WatchedItem::IGNORE_USER_RIGHTS.
+ * @return bool
*/
- public function isWatched( $title ) {
- return $this->getWatchedItem( $title )->isWatched();
+ public function isWatched( $title, $checkRights = WatchedItem::CHECK_USER_RIGHTS ) {
+ return $this->getWatchedItem( $title, $checkRights )->isWatched();
}
/**
* Watch an article.
+ * @since 1.22 $checkRights parameter added
* @param $title Title of the article to look at
+ * @param $checkRights int Whether to check 'viewmywatchlist'/'editmywatchlist' rights.
+ * Pass WatchedItem::CHECK_USER_RIGHTS or WatchedItem::IGNORE_USER_RIGHTS.
*/
- public function addWatch( $title ) {
- $this->getWatchedItem( $title )->addWatch();
+ public function addWatch( $title, $checkRights = WatchedItem::CHECK_USER_RIGHTS ) {
+ $this->getWatchedItem( $title, $checkRights )->addWatch();
$this->invalidateCache();
}
/**
* Stop watching an article.
+ * @since 1.22 $checkRights parameter added
* @param $title Title of the article to look at
+ * @param $checkRights int Whether to check 'viewmywatchlist'/'editmywatchlist' rights.
+ * Pass WatchedItem::CHECK_USER_RIGHTS or WatchedItem::IGNORE_USER_RIGHTS.
*/
- public function removeWatch( $title ) {
- $this->getWatchedItem( $title )->removeWatch();
+ public function removeWatch( $title, $checkRights = WatchedItem::CHECK_USER_RIGHTS ) {
+ $this->getWatchedItem( $title, $checkRights )->removeWatch();
$this->invalidateCache();
}
@@ -2851,28 +3019,35 @@ class User {
* Clear the user's notification timestamp for the given title.
* If e-notif e-mails are on, they will receive notification mails on
* the next change of the page if it's watched etc.
+ * @note If the user doesn't have 'editmywatchlist', this will do nothing.
* @param $title Title of the article to look at
*/
public function clearNotification( &$title ) {
global $wgUseEnotif, $wgShowUpdatedMarker;
- # Do nothing if the database is locked to writes
- if( wfReadOnly() ) {
+ // Do nothing if the database is locked to writes
+ if ( wfReadOnly() ) {
+ return;
+ }
+
+ // Do nothing if not allowed to edit the watchlist
+ if ( !$this->isAllowed( 'editmywatchlist' ) ) {
return;
}
- if( $title->getNamespace() == NS_USER_TALK &&
+ if ( $title->getNamespace() == NS_USER_TALK &&
$title->getText() == $this->getName() ) {
- if( !wfRunHooks( 'UserClearNewTalkNotification', array( &$this ) ) )
+ if ( !wfRunHooks( 'UserClearNewTalkNotification', array( &$this ) ) ) {
return;
+ }
$this->setNewtalk( false );
}
- if( !$wgUseEnotif && !$wgShowUpdatedMarker ) {
+ if ( !$wgUseEnotif && !$wgShowUpdatedMarker ) {
return;
}
- if( $this->isAnon() ) {
+ if ( $this->isAnon() ) {
// Nothing else to do...
return;
}
@@ -2895,19 +3070,25 @@ class User {
* Resets all of the given user's page-change notification timestamps.
* If e-notif e-mails are on, they will receive notification mails on
* the next change of any watched page.
+ * @note If the user doesn't have 'editmywatchlist', this will do nothing.
*/
public function clearAllNotifications() {
if ( wfReadOnly() ) {
return;
}
+ // Do nothing if not allowed to edit the watchlist
+ if ( !$this->isAllowed( 'editmywatchlist' ) ) {
+ 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 */
@@ -2929,8 +3110,9 @@ class User {
*/
private function decodeOptions( $str ) {
wfDeprecated( __METHOD__, '1.19' );
- if( !$str )
+ if ( !$str ) {
return;
+ }
$this->mOptionsLoaded = true;
$this->mOptionOverrides = array();
@@ -2955,21 +3137,28 @@ class User {
* @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
+ * @param bool $secure
* 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
+ * @param array $params Array of options sent passed to WebResponse::setcookie()
*/
- protected function setCookie( $name, $value, $exp = 0, $secure = null ) {
- $this->getRequest()->response()->setcookie( $name, $value, $exp, null, null, $secure );
+ protected function setCookie( $name, $value, $exp = 0, $secure = null, $params = array() ) {
+ $params['secure'] = $secure;
+ $this->getRequest()->response()->setcookie( $name, $value, $exp, $params );
}
/**
* Clear a cookie on the user's client
* @param string $name Name of the cookie to clear
+ * @param bool $secure
+ * 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
+ * @param array $params Array of options sent passed to WebResponse::setcookie()
*/
- protected function clearCookie( $name ) {
- $this->setCookie( $name, '', time() - 86400 );
+ protected function clearCookie( $name, $secure = null, $params = array() ) {
+ $this->setCookie( $name, '', time() - 86400, $secure, $params );
}
/**
@@ -3027,10 +3216,22 @@ class User {
/**
* 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.
+ * it over HTTP. Bug 29898. Use an un-prefixed cookie, so it's the same
+ * as the one set by centralauth (bug 53538). Also set it to session, or
+ * standard time setting, based on if rememberme was set.
*/
- if ( $request->getCheck( 'wpStickHTTPS' ) ) {
- $this->setCookie( 'forceHTTPS', 'true', time() + 2592000, false ); //30 days
+ if ( $request->getCheck( 'wpStickHTTPS' ) || $this->requiresHTTPS() ) {
+ $time = null;
+ if ( ( 1 == $this->getOption( 'rememberpassword' ) ) ) {
+ $time = 0; // set to $wgCookieExpiration
+ }
+ $this->setCookie(
+ 'forceHTTPS',
+ 'true',
+ $time,
+ false,
+ array( 'prefix' => '' ) // no prefix
+ );
}
}
@@ -3038,7 +3239,7 @@ class User {
* Log this user out.
*/
public function logout() {
- if( wfRunHooks( 'UserLogout', array( &$this ) ) ) {
+ if ( wfRunHooks( 'UserLogout', array( &$this ) ) ) {
$this->doLogout();
}
}
@@ -3054,10 +3255,10 @@ class User {
$this->clearCookie( 'UserID' );
$this->clearCookie( 'Token' );
- $this->clearCookie( 'forceHTTPS' );
+ $this->clearCookie( 'forceHTTPS', false, array( 'prefix' => '' ) );
- # Remember when user logged out, to prevent seeing cached pages
- $this->setCookie( 'LoggedOut', wfTimestampNow(), time() + 86400 );
+ // Remember when user logged out, to prevent seeing cached pages
+ $this->setCookie( 'LoggedOut', time(), time() + 86400 );
}
/**
@@ -3068,8 +3269,12 @@ class User {
global $wgAuth;
$this->load();
- if ( wfReadOnly() ) { return; }
- if ( 0 == $this->mId ) { return; }
+ if ( wfReadOnly() ) {
+ return;
+ }
+ if ( 0 == $this->mId ) {
+ return;
+ }
$this->mTouched = self::newTouchedTimestamp();
if ( !$wgAuth->allowSetLocalPassword() ) {
@@ -3104,11 +3309,13 @@ class User {
/**
* If only this user's username is known, and it exists, return the user ID.
- * @return Int
+ * @return int
*/
public function idForName() {
$s = trim( $this->getName() );
- if ( $s === '' ) return 0;
+ if ( $s === '' ) {
+ return 0;
+ }
$dbr = wfGetDB( DB_SLAVE );
$id = $dbr->selectField( 'user', 'user_id', array( 'user_name' => $s ), __METHOD__ );
@@ -3206,6 +3413,7 @@ class User {
$this->mTouched = self::newTouchedTimestamp();
$dbw = wfGetDB( DB_MASTER );
+ $inWrite = $dbw->writesOrCallbacksPending();
$seqVal = $dbw->nextSequenceValue( 'user_user_id_seq' );
$dbw->insert( 'user',
array(
@@ -3225,6 +3433,12 @@ class User {
array( 'IGNORE' )
);
if ( !$dbw->affectedRows() ) {
+ if ( !$inWrite ) {
+ // XXX: Get out of REPEATABLE-READ so the SELECT below works.
+ // Often this case happens early in views before any writes.
+ // This shows up at least with CentralAuth.
+ $dbw->commit( __METHOD__, 'flush' );
+ }
$this->mId = $dbw->selectField( 'user', 'user_id',
array( 'user_name' => $this->mName ), __METHOD__ );
$loaded = false;
@@ -3234,8 +3448,8 @@ class User {
}
}
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!" );
+ throw new MWException( __METHOD__ . ": hit a key conflict attempting " .
+ "to insert user '{$this->mName}' row, but it was not present in select!" );
}
return Status::newFatal( 'userexists' );
}
@@ -3292,13 +3506,13 @@ class User {
* settings.
*
* @deprecated since 1.17 use the ParserOptions object to get the relevant options
- * @return String Page rendering hash
+ * @return string Page rendering hash
*/
public function getPageRenderingHash() {
wfDeprecated( __METHOD__, '1.17' );
global $wgRenderHashAppend, $wgLang, $wgContLang;
- if( $this->mHash ) {
+ if ( $this->mHash ) {
return $this->mHash;
}
@@ -3332,11 +3546,11 @@ class User {
/**
* Get whether the user is explicitly blocked from account creation.
- * @return Bool|Block
+ * @return bool|Block
*/
public function isBlockedFromCreateAccount() {
$this->getBlockedStatus();
- if( $this->mBlock && $this->mBlock->prevents( 'createaccount' ) ) {
+ if ( $this->mBlock && $this->mBlock->prevents( 'createaccount' ) ) {
return $this->mBlock;
}
@@ -3353,7 +3567,7 @@ class User {
/**
* Get whether the user is blocked from using Special:Emailuser.
- * @return Bool
+ * @return bool
*/
public function isBlockedFromEmailuser() {
$this->getBlockedStatus();
@@ -3362,7 +3576,7 @@ class User {
/**
* Get whether the user is allowed to create an account.
- * @return Bool
+ * @return bool
*/
function isAllowedToCreateAccount() {
return $this->isAllowed( 'createaccount' ) && !$this->isBlockedFromCreateAccount();
@@ -3390,7 +3604,7 @@ class User {
/**
* Determine whether the user is a newbie. Newbies are either
* anonymous IPs, or the most recently created accounts.
- * @return Bool
+ * @return bool
*/
public function isNewbie() {
return !$this->isAllowed( 'autoconfirmed' );
@@ -3399,7 +3613,7 @@ class User {
/**
* Check to see if the given clear-text password is one of the accepted passwords
* @param string $password user password.
- * @return Boolean: True if the given password is correct, otherwise False.
+ * @return boolean: True if the given password is correct, otherwise False.
*/
public function checkPassword( $password ) {
global $wgAuth, $wgLegacyEncoding;
@@ -3410,24 +3624,24 @@ class User {
// to. Certain authentication plugins do NOT want to save
// domain passwords in a mysql database, so we should
// check this (in case $wgAuth->strict() is false).
- if( !$this->isValidPassword( $password ) ) {
+ if ( !$this->isValidPassword( $password ) ) {
return false;
}
- if( $wgAuth->authenticate( $this->getName(), $password ) ) {
+ if ( $wgAuth->authenticate( $this->getName(), $password ) ) {
return true;
- } elseif( $wgAuth->strict() ) {
- /* Auth plugin doesn't allow local authentication */
+ } elseif ( $wgAuth->strict() ) {
+ // Auth plugin doesn't allow local authentication
return false;
- } elseif( $wgAuth->strictUserAuth( $this->getName() ) ) {
- /* Auth plugin doesn't allow local authentication for this user name */
+ } elseif ( $wgAuth->strictUserAuth( $this->getName() ) ) {
+ // Auth plugin doesn't allow local authentication for this user name
return false;
}
if ( self::comparePasswords( $this->mPassword, $password, $this->mId ) ) {
return true;
} elseif ( $wgLegacyEncoding ) {
- # Some wikis were converted from ISO 8859-1 to UTF-8, the passwords can't be converted
- # Check for this with iconv
+ // Some wikis were converted from ISO 8859-1 to UTF-8, the passwords can't be converted
+ // Check for this with iconv
$cp1252Password = iconv( 'UTF-8', 'WINDOWS-1252//TRANSLIT', $password );
if ( $cp1252Password != $password &&
self::comparePasswords( $this->mPassword, $cp1252Password, $this->mId ) )
@@ -3444,13 +3658,13 @@ class User {
*
* @param $plaintext string
*
- * @return Boolean: True if matches, false otherwise
+ * @return boolean: True if matches, false otherwise
*/
public function checkTemporaryPassword( $plaintext ) {
global $wgNewPasswordExpiry;
$this->load();
- if( self::comparePasswords( $this->mNewpassword, $plaintext, $this->getId() ) ) {
+ if ( self::comparePasswords( $this->mNewpassword, $plaintext, $this->getId() ) ) {
if ( is_null( $this->mNewpassTime ) ) {
return true;
}
@@ -3467,7 +3681,7 @@ class User {
*
* @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
+ * @return string The new edit token
*/
public function editToken( $salt = '', $request = null ) {
wfDeprecated( __METHOD__, '1.19' );
@@ -3484,7 +3698,7 @@ class User {
*
* @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
+ * @return string The new edit token
*/
public function getEditToken( $salt = '', $request = null ) {
if ( $request == null ) {
@@ -3499,7 +3713,7 @@ class User {
$token = MWCryptRand::generateHex( 32 );
$request->setSessionData( 'wsEditToken', $token );
}
- if( is_array( $salt ) ) {
+ if ( is_array( $salt ) ) {
$salt = implode( '|', $salt );
}
return md5( $token . $salt ) . EDIT_TOKEN_SUFFIX;
@@ -3509,8 +3723,8 @@ class User {
/**
* Generate a looking random token for various uses.
*
- * @return String The new random token
- * @deprecated since 1.20; Use MWCryptRand for secure purposes or wfRandomString for pseudo-randomness
+ * @return string The new random token
+ * @deprecated since 1.20: Use MWCryptRand for secure purposes or wfRandomString for pseudo-randomness
*/
public static function generateToken() {
return MWCryptRand::generateHex( 32 );
@@ -3524,8 +3738,8 @@ class User {
*
* @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
+ * @param WebRequest $request Object to use or null to use $wgRequest
+ * @return boolean: Whether the token matches
*/
public function matchEditToken( $val, $salt = '', $request = null ) {
$sessionToken = $this->getEditToken( $salt, $request );
@@ -3541,8 +3755,8 @@ class User {
*
* @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
+ * @param WebRequest $request object to use or null to use $wgRequest
+ * @return boolean: Whether the token matches
*/
public function matchEditTokenNoSuffix( $val, $salt = '', $request = null ) {
$sessionToken = $this->getEditToken( $salt, $request );
@@ -3569,6 +3783,7 @@ class User {
} elseif ( $type === true ) {
$message = 'confirmemail_body_changed';
} else {
+ // Messages: confirmemail_body_changed, confirmemail_body_set
$message = 'confirmemail_body_' . $type;
}
@@ -3594,7 +3809,7 @@ class User {
* @return Status
*/
public function sendMail( $subject, $body, $from = null, $replyto = null ) {
- if( is_null( $from ) ) {
+ if ( is_null( $from ) ) {
global $wgPasswordSender, $wgPasswordSenderName;
$sender = new MailAddress( $wgPasswordSender, $wgPasswordSenderName );
} else {
@@ -3613,9 +3828,9 @@ class User {
* this change to the database.
*
* @param &$expiration \mixed Accepts the expiration time
- * @return String New token
+ * @return string New token
*/
- private function confirmationToken( &$expiration ) {
+ protected function confirmationToken( &$expiration ) {
global $wgUserEmailConfirmationTokenExpiry;
$now = time();
$expires = $now + $wgUserEmailConfirmationTokenExpiry;
@@ -3631,18 +3846,18 @@ class User {
/**
* 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
+ * @return string New token URL
*/
- private function confirmationTokenUrl( $token ) {
+ protected function confirmationTokenUrl( $token ) {
return $this->getTokenUrl( 'ConfirmEmail', $token );
}
/**
* Return a URL the user can use to invalidate their email address.
* @param string $token Accepts the email confirmation token
- * @return String New token URL
+ * @return string New token URL
*/
- private function invalidationTokenUrl( $token ) {
+ protected function invalidationTokenUrl( $token ) {
return $this->getTokenUrl( 'InvalidateEmail', $token );
}
@@ -3658,12 +3873,12 @@ class User {
*
* @param string $page Special page
* @param string $token Token
- * @return String Formatted URL
+ * @return string Formatted URL
*/
protected function getTokenUrl( $page, $token ) {
// Hack to bypass localization of 'Special:'
$title = Title::makeTitle( NS_MAIN, "Special:$page/$token" );
- return $title->getCanonicalUrl();
+ return $title->getCanonicalURL();
}
/**
@@ -3674,8 +3889,12 @@ class User {
* @return bool
*/
public function confirmEmail() {
- $this->setEmailAuthenticationTimestamp( wfTimestampNow() );
- wfRunHooks( 'ConfirmEmailComplete', array( $this ) );
+ // Check if it's already confirmed, so we don't touch the database
+ // and fire the ConfirmEmailComplete hook on redundant confirmations.
+ if ( !$this->isEmailConfirmed() ) {
+ $this->setEmailAuthenticationTimestamp( wfTimestampNow() );
+ wfRunHooks( 'ConfirmEmailComplete', array( $this ) );
+ }
return true;
}
@@ -3708,11 +3927,11 @@ class User {
/**
* Is this user allowed to send e-mails within limits of current
* site configuration?
- * @return Bool
+ * @return bool
*/
public function canSendEmail() {
global $wgEnableEmail, $wgEnableUserEmail;
- if( !$wgEnableEmail || !$wgEnableUserEmail || !$this->isAllowed( 'sendemail' ) ) {
+ if ( !$wgEnableEmail || !$wgEnableUserEmail || !$this->isAllowed( 'sendemail' ) ) {
return false;
}
$canSend = $this->isEmailConfirmed();
@@ -3723,7 +3942,7 @@ class User {
/**
* Is this user allowed to receive e-mails within limits of current
* site configuration?
- * @return Bool
+ * @return bool
*/
public function canReceiveEmail() {
return $this->isEmailConfirmed() && !$this->getOption( 'disablemail' );
@@ -3737,20 +3956,20 @@ class User {
* confirmed their address by returning a code or using a password
* sent to the address from the wiki.
*
- * @return Bool
+ * @return bool
*/
public function isEmailConfirmed() {
global $wgEmailAuthentication;
$this->load();
$confirmed = true;
- if( wfRunHooks( 'EmailConfirmed', array( &$this, &$confirmed ) ) ) {
- if( $this->isAnon() ) {
+ if ( wfRunHooks( 'EmailConfirmed', array( &$this, &$confirmed ) ) ) {
+ if ( $this->isAnon() ) {
return false;
}
- if( !Sanitizer::validateEmail( $this->mEmail ) ) {
+ if ( !Sanitizer::validateEmail( $this->mEmail ) ) {
return false;
}
- if( $wgEmailAuthentication && !$this->getEmailAuthenticationTimestamp() ) {
+ if ( $wgEmailAuthentication && !$this->getEmailAuthenticationTimestamp() ) {
return false;
}
return true;
@@ -3761,7 +3980,7 @@ class User {
/**
* Check whether there is an outstanding request for e-mail confirmation.
- * @return Bool
+ * @return bool
*/
public function isEmailConfirmationPending() {
global $wgEmailAuthentication;
@@ -3774,9 +3993,9 @@ class User {
/**
* Get the timestamp of account creation.
*
- * @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.
+ * @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() ) {
@@ -3789,11 +4008,11 @@ class User {
/**
* Get the timestamp of the first edit
*
- * @return String|Bool Timestamp of first edit, or false for
- * non-existent/anonymous user accounts.
+ * @return string|bool Timestamp of first edit, or false for
+ * non-existent/anonymous user accounts.
*/
public function getFirstEditTimestamp() {
- if( $this->getId() == 0 ) {
+ if ( $this->getId() == 0 ) {
return false; // anons
}
$dbr = wfGetDB( DB_SLAVE );
@@ -3802,7 +4021,7 @@ class User {
__METHOD__,
array( 'ORDER BY' => 'rev_timestamp ASC' )
);
- if( !$time ) {
+ if ( !$time ) {
return false; // no edits
}
return wfTimestamp( TS_MW, $time );
@@ -3818,16 +4037,16 @@ class User {
global $wgGroupPermissions, $wgRevokePermissions;
$rights = array();
// grant every granted permission first
- foreach( $groups as $group ) {
- if( isset( $wgGroupPermissions[$group] ) ) {
+ foreach ( $groups as $group ) {
+ if ( isset( $wgGroupPermissions[$group] ) ) {
$rights = array_merge( $rights,
// array_filter removes empty items
array_keys( array_filter( $wgGroupPermissions[$group] ) ) );
}
}
// now revoke the revoked permissions
- foreach( $groups as $group ) {
- if( isset( $wgRevokePermissions[$group] ) ) {
+ foreach ( $groups as $group ) {
+ if ( isset( $wgRevokePermissions[$group] ) ) {
$rights = array_diff( $rights,
array_keys( array_filter( $wgRevokePermissions[$group] ) ) );
}
@@ -3855,6 +4074,11 @@ class User {
/**
* Check, if the given group has the given permission
*
+ * If you're wanting to check whether all users have a permission, use
+ * User::isEveryoneAllowed() instead. That properly checks if it's revoked
+ * from anyone.
+ *
+ * @since 1.21
* @param string $group Group to check
* @param string $role Role to check
* @return bool
@@ -3866,10 +4090,50 @@ class User {
}
/**
+ * Check if all users have the given permission
+ *
+ * @since 1.22
+ * @param string $right Right to check
+ * @return bool
+ */
+ public static function isEveryoneAllowed( $right ) {
+ global $wgGroupPermissions, $wgRevokePermissions;
+ static $cache = array();
+
+ // Use the cached results, except in unit tests which rely on
+ // being able change the permission mid-request
+ if ( isset( $cache[$right] ) && !defined( 'MW_PHPUNIT_TEST' ) ) {
+ return $cache[$right];
+ }
+
+ if ( !isset( $wgGroupPermissions['*'][$right] ) || !$wgGroupPermissions['*'][$right] ) {
+ $cache[$right] = false;
+ return false;
+ }
+
+ // If it's revoked anywhere, then everyone doesn't have it
+ foreach ( $wgRevokePermissions as $rights ) {
+ if ( isset( $rights[$right] ) && $rights[$right] ) {
+ $cache[$right] = false;
+ return false;
+ }
+ }
+
+ // Allow extensions (e.g. OAuth) to say false
+ if ( !wfRunHooks( 'UserIsEveryoneAllowed', array( $right ) ) ) {
+ $cache[$right] = false;
+ return false;
+ }
+
+ $cache[$right] = true;
+ return true;
+ }
+
+ /**
* Get the localized descriptive name for a group, if it exists
*
* @param string $group Internal group name
- * @return String Localized descriptive group name
+ * @return string Localized descriptive group name
*/
public static function getGroupName( $group ) {
$msg = wfMessage( "group-$group" );
@@ -3881,7 +4145,7 @@ class User {
*
* @param string $group Internal group name
* @param string $username Username for gender (since 1.19)
- * @return String Localized name for group member
+ * @return string Localized name for group member
*/
public static function getGroupMember( $group, $username = '#' ) {
$msg = wfMessage( "group-$group-member", $username );
@@ -3934,14 +4198,15 @@ class User {
* Get the title of a page describing a particular group
*
* @param string $group Internal group name
- * @return Title|Bool Title of the page if it exists, false otherwise
+ * @return Title|bool Title of the page if it exists, false otherwise
*/
public static function getGroupPage( $group ) {
$msg = wfMessage( 'grouppage-' . $group )->inContentLanguage();
- if( $msg->exists() ) {
+ if ( $msg->exists() ) {
$title = Title::newFromText( $msg->text() );
- if( is_object( $title ) )
+ if ( is_object( $title ) ) {
return $title;
+ }
}
return false;
}
@@ -3952,14 +4217,14 @@ class User {
*
* @param string $group Internal name of the group
* @param string $text The text of the link
- * @return String HTML link to the group
+ * @return string HTML link to the group
*/
public static function makeGroupLinkHTML( $group, $text = '' ) {
- if( $text == '' ) {
+ if ( $text == '' ) {
$text = self::getGroupName( $group );
}
$title = self::getGroupPage( $group );
- if( $title ) {
+ if ( $title ) {
return Linker::link( $title, htmlspecialchars( $text ) );
} else {
return $text;
@@ -3972,14 +4237,14 @@ class User {
*
* @param string $group Internal name of the group
* @param string $text The text of the link
- * @return String Wikilink to the group
+ * @return string Wikilink to the group
*/
public static function makeGroupLinkWiki( $group, $text = '' ) {
- if( $text == '' ) {
+ if ( $text == '' ) {
$text = self::getGroupName( $group );
}
$title = self::getGroupPage( $group );
- if( $title ) {
+ if ( $title ) {
$page = $title->getPrefixedText();
return "[[$page|$text]]";
} else {
@@ -4000,53 +4265,53 @@ class User {
global $wgAddGroups, $wgRemoveGroups, $wgGroupsAddToSelf, $wgGroupsRemoveFromSelf;
$groups = array( 'add' => array(), 'remove' => array(), 'add-self' => array(), 'remove-self' => array() );
- if( empty( $wgAddGroups[$group] ) ) {
+ if ( empty( $wgAddGroups[$group] ) ) {
// Don't add anything to $groups
- } elseif( $wgAddGroups[$group] === true ) {
+ } elseif ( $wgAddGroups[$group] === true ) {
// You get everything
$groups['add'] = self::getAllGroups();
- } elseif( is_array( $wgAddGroups[$group] ) ) {
+ } elseif ( is_array( $wgAddGroups[$group] ) ) {
$groups['add'] = $wgAddGroups[$group];
}
// Same thing for remove
- if( empty( $wgRemoveGroups[$group] ) ) {
- } elseif( $wgRemoveGroups[$group] === true ) {
+ if ( empty( $wgRemoveGroups[$group] ) ) {
+ } elseif ( $wgRemoveGroups[$group] === true ) {
$groups['remove'] = self::getAllGroups();
- } elseif( is_array( $wgRemoveGroups[$group] ) ) {
+ } elseif ( is_array( $wgRemoveGroups[$group] ) ) {
$groups['remove'] = $wgRemoveGroups[$group];
}
// Re-map numeric keys of AddToSelf/RemoveFromSelf to the 'user' key for backwards compatibility
- if( empty( $wgGroupsAddToSelf['user']) || $wgGroupsAddToSelf['user'] !== true ) {
- foreach( $wgGroupsAddToSelf as $key => $value ) {
- if( is_int( $key ) ) {
+ if ( empty( $wgGroupsAddToSelf['user'] ) || $wgGroupsAddToSelf['user'] !== true ) {
+ foreach ( $wgGroupsAddToSelf as $key => $value ) {
+ if ( is_int( $key ) ) {
$wgGroupsAddToSelf['user'][] = $value;
}
}
}
- if( empty( $wgGroupsRemoveFromSelf['user']) || $wgGroupsRemoveFromSelf['user'] !== true ) {
- foreach( $wgGroupsRemoveFromSelf as $key => $value ) {
- if( is_int( $key ) ) {
+ if ( empty( $wgGroupsRemoveFromSelf['user'] ) || $wgGroupsRemoveFromSelf['user'] !== true ) {
+ foreach ( $wgGroupsRemoveFromSelf as $key => $value ) {
+ if ( is_int( $key ) ) {
$wgGroupsRemoveFromSelf['user'][] = $value;
}
}
}
// Now figure out what groups the user can add to him/herself
- if( empty( $wgGroupsAddToSelf[$group] ) ) {
- } elseif( $wgGroupsAddToSelf[$group] === true ) {
+ if ( empty( $wgGroupsAddToSelf[$group] ) ) {
+ } elseif ( $wgGroupsAddToSelf[$group] === true ) {
// No idea WHY this would be used, but it's there
$groups['add-self'] = User::getAllGroups();
- } elseif( is_array( $wgGroupsAddToSelf[$group] ) ) {
+ } elseif ( is_array( $wgGroupsAddToSelf[$group] ) ) {
$groups['add-self'] = $wgGroupsAddToSelf[$group];
}
- if( empty( $wgGroupsRemoveFromSelf[$group] ) ) {
- } elseif( $wgGroupsRemoveFromSelf[$group] === true ) {
+ if ( empty( $wgGroupsRemoveFromSelf[$group] ) ) {
+ } elseif ( $wgGroupsRemoveFromSelf[$group] === true ) {
$groups['remove-self'] = User::getAllGroups();
- } elseif( is_array( $wgGroupsRemoveFromSelf[$group] ) ) {
+ } elseif ( is_array( $wgGroupsRemoveFromSelf[$group] ) ) {
$groups['remove-self'] = $wgGroupsRemoveFromSelf[$group];
}
@@ -4061,7 +4326,7 @@ class User {
* 'remove-self' => array( removable groups from self) )
*/
public function changeableGroups() {
- if( $this->isAllowed( 'userrights' ) ) {
+ if ( $this->isAllowed( 'userrights' ) ) {
// This group gives the right to modify everything (reverse-
// compatibility with old "userrights lets you change
// everything")
@@ -4084,7 +4349,7 @@ class User {
);
$addergroups = $this->getEffectiveGroups();
- foreach( $addergroups as $addergroup ) {
+ foreach ( $addergroups as $addergroup ) {
$groups = array_merge_recursive(
$groups, $this->changeableByGroup( $addergroup )
);
@@ -4101,7 +4366,7 @@ class User {
* Will have no effect for anonymous users.
*/
public function incEditCount() {
- if( !$this->isAnon() ) {
+ if ( !$this->isAnon() ) {
$dbw = wfGetDB( DB_MASTER );
$dbw->update(
'user',
@@ -4111,10 +4376,10 @@ class User {
);
// Lazy initialization check...
- if( $dbw->affectedRows() == 0 ) {
+ if ( $dbw->affectedRows() == 0 ) {
// Now here's a goddamn hack...
$dbr = wfGetDB( DB_SLAVE );
- if( $dbr !== $dbw ) {
+ 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.
@@ -4135,13 +4400,13 @@ 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
+ * @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(
+ $count = (int)$dbr->selectField(
'revision',
'COUNT(rev_user)',
array( 'rev_user' => $this->getId() ),
@@ -4164,7 +4429,7 @@ class User {
* Get the description of a given right
*
* @param string $right Right to query
- * @return String Localized description of the right
+ * @return string Localized description of the right
*/
public static function getRightDescription( $right ) {
$key = "right-$right";
@@ -4177,7 +4442,7 @@ class User {
*
* @param string $password Plain-text password
* @param string $userId User ID
- * @return String Password hash
+ * @return string Password hash
*/
public static function oldCrypt( $password, $userId ) {
global $wgPasswordSalt;
@@ -4193,19 +4458,18 @@ class User {
*
* @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
- * @return String Password hash
+ * If unspecified or false, will generate one automatically
+ * @return string Password hash
*/
public static function crypt( $password, $salt = false ) {
global $wgPasswordSalt;
$hash = '';
- if( !wfRunHooks( 'UserCryptPassword', array( &$password, &$salt, &$wgPasswordSalt, &$hash ) ) ) {
+ if ( !wfRunHooks( 'UserCryptPassword', array( &$password, &$salt, &$wgPasswordSalt, &$hash ) ) ) {
return $hash;
}
- if( $wgPasswordSalt ) {
+ if ( $wgPasswordSalt ) {
if ( $salt === false ) {
$salt = MWCryptRand::generateHex( 8 );
}
@@ -4223,25 +4487,25 @@ class User {
* @param string $password Plain-text password to compare
* @param string|bool $userId User ID for old-style password salt
*
- * @return Boolean
+ * @return boolean
*/
public static function comparePasswords( $hash, $password, $userId = false ) {
$type = substr( $hash, 0, 3 );
$result = false;
- if( !wfRunHooks( 'UserComparePasswords', array( &$hash, &$password, &$userId, &$result ) ) ) {
+ if ( !wfRunHooks( 'UserComparePasswords', array( &$hash, &$password, &$userId, &$result ) ) ) {
return $result;
}
if ( $type == ':A:' ) {
- # Unsalted
+ // Unsalted
return md5( $password ) === substr( $hash, 3 );
} elseif ( $type == ':B:' ) {
- # Salted
+ // Salted
list( $salt, $realHash ) = explode( ':', substr( $hash, 3 ), 2 );
- return md5( $salt.'-'.md5( $password ) ) === $realHash;
+ return md5( $salt . '-' . md5( $password ) ) === $realHash;
} else {
- # Old-style
+ // Old-style
return self::oldCrypt( $password, $userId ) === $hash;
}
}
@@ -4257,6 +4521,7 @@ class User {
* 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
+ * - 'autocreate' when the user is automatically created (such as by CentralAuth).
* - 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
@@ -4268,7 +4533,7 @@ class User {
*/
public function addNewUserLogEntry( $action = false, $reason = '' ) {
global $wgUser, $wgNewUserLog;
- if( empty( $wgNewUserLog ) ) {
+ if ( empty( $wgNewUserLog ) ) {
return true; // disabled
}
@@ -4348,11 +4613,11 @@ class User {
// Maybe load from the object
if ( !is_null( $this->mOptionOverrides ) ) {
wfDebug( "User: loading options for user " . $this->getId() . " from override cache.\n" );
- foreach( $this->mOptionOverrides as $key => $value ) {
+ foreach ( $this->mOptionOverrides as $key => $value ) {
$this->mOptions[$key] = $value;
}
} else {
- if( !is_array( $data ) ) {
+ if ( !is_array( $data ) ) {
wfDebug( "User: loading options for user " . $this->getId() . " from database.\n" );
// Load from database
$dbr = wfGetDB( DB_SLAVE );
@@ -4385,8 +4650,6 @@ class User {
* @todo document
*/
protected function saveOptions() {
- global $wgAllowPrefChange;
-
$this->loadOptions();
// Not using getOptions(), to keep hidden preferences in database
@@ -4394,15 +4657,14 @@ class User {
// Allow hooks to abort, for instance to save to a global profile.
// Reset options to default state before saving.
- if( !wfRunHooks( 'UserSaveOptions', array( $this, &$saveOptions ) ) ) {
+ if ( !wfRunHooks( 'UserSaveOptions', array( $this, &$saveOptions ) ) ) {
return;
}
- $extuser = ExternalUser::newFromUser( $this );
$userId = $this->getId();
$insert_rows = array();
- foreach( $saveOptions as $key => $value ) {
- # Don't bother storing default values
+ foreach ( $saveOptions as $key => $value ) {
+ // Don't bother storing default values
$defaultOption = self::getDefaultOption( $key );
if ( ( is_null( $defaultOption ) &&
!( $value === false || is_null( $value ) ) ) ||
@@ -4413,21 +4675,21 @@ class User {
'up_value' => $value,
);
}
- if ( $extuser && isset( $wgAllowPrefChange[$key] ) ) {
- switch ( $wgAllowPrefChange[$key] ) {
- case 'local':
- case 'message':
- break;
- case 'semiglobal':
- case 'global':
- $extuser->setPref( $key, $value );
- }
- }
}
$dbw = wfGetDB( DB_MASTER );
- $dbw->delete( 'user_properties', array( 'up_user' => $userId ), __METHOD__ );
- $dbw->insert( 'user_properties', $insert_rows, __METHOD__ );
+ $hasRows = $dbw->selectField( 'user_properties', '1',
+ array( 'up_user' => $userId ), __METHOD__ );
+
+ if ( $hasRows ) {
+ // Only do this delete if there is something there. A very large portion of
+ // calls to this function are for setting 'rememberpassword' for new accounts.
+ // Doing this delete for new accounts with no rows in the table rougly causes
+ // gap locks on [max user ID,+infinity) which causes high contention since many
+ // updates will pile up on each other since they are for higher (newer) user IDs.
+ $dbw->delete( 'user_properties', array( 'up_user' => $userId ), __METHOD__ );
+ }
+ $dbw->insert( 'user_properties', $insert_rows, __METHOD__, array( 'IGNORE' ) );
}
/**
@@ -4451,8 +4713,7 @@ class User {
*
* @return array Array of HTML attributes suitable for feeding to
* Html::element(), directly or indirectly. (Don't feed to Xml::*()!
- * That will potentially output invalid XHTML 1.0 Transitional, and will
- * get confused by the boolean attribute syntax used.)
+ * That will get confused by the boolean attribute syntax used.)
*/
public static function passwordChangeInputAttribs() {
global $wgMinimalPasswordLength;
@@ -4512,4 +4773,26 @@ class User {
'user_editcount',
);
}
+
+ /**
+ * Factory function for fatal permission-denied errors
+ *
+ * @since 1.22
+ * @param string $permission User right required
+ * @return Status
+ */
+ static function newFatalPermissionDeniedStatus( $permission ) {
+ global $wgLang;
+
+ $groups = array_map(
+ array( 'User', 'makeGroupLinkWiki' ),
+ User::getGroupsWithPermission( $permission )
+ );
+
+ if ( $groups ) {
+ return Status::newFatal( 'badaccess-groups', $wgLang->commaList( $groups ), count( $groups ) );
+ } else {
+ return Status::newFatal( 'badaccess-group0' );
+ }
+ }
}
diff --git a/includes/UserArray.php b/includes/UserArray.php
index 3b8f5c10..1f55ef35 100644
--- a/includes/UserArray.php
+++ b/includes/UserArray.php
@@ -44,7 +44,7 @@ abstract class UserArray implements Iterator {
$ids = array_map( 'intval', (array)$ids ); // paranoia
if ( !$ids ) {
// Database::select() doesn't like empty arrays
- return new ArrayIterator(array());
+ return new ArrayIterator( array() );
}
$dbr = wfGetDB( DB_SLAVE );
$res = $dbr->select( 'user', '*', array( 'user_id' => $ids ),
diff --git a/includes/UserMailer.php b/includes/UserMailer.php
index 6eb99172..8ab10b2d 100644
--- a/includes/UserMailer.php
+++ b/includes/UserMailer.php
@@ -119,7 +119,7 @@ class UserMailer {
*/
static function arrayToHeaderString( $headers, $endl = "\n" ) {
$strings = array();
- foreach( $headers as $name => $value ) {
+ foreach ( $headers as $name => $value ) {
$strings[] = "$name: $value";
}
return implode( $endl, $strings );
@@ -241,7 +241,7 @@ class UserMailer {
$headers['Reply-To'] = $replyto->toString();
}
- $headers['Date'] = date( 'r' );
+ $headers['Date'] = MWTimestamp::getLocalInstance()->format( 'r' );
$headers['Message-ID'] = self::makeMsgId();
$headers['X-Mailer'] = 'MediaWiki mailer';
@@ -258,14 +258,16 @@ class UserMailer {
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" );
+ // remove the html body for text email fall back
+ $body = $body['text'];
}
else {
- require_once( 'Mail/mime.php' );
+ 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 = new Mail_mime( array( 'eol' => $endl, 'text_charset' => 'UTF-8', 'html_charset' => 'UTF-8' ) );
$mime->setTXTBody( $body['text'] );
$mime->setHTMLBody( $body['html'] );
$body = $mime->get(); // must call get() before headers()
@@ -300,7 +302,7 @@ class UserMailer {
if ( !stream_resolve_include_path( 'Mail.php' ) ) {
throw new MWException( 'PEAR mail package is not installed' );
}
- require_once( 'Mail.php' );
+ require_once 'Mail.php';
wfSuppressWarnings();
@@ -338,7 +340,7 @@ class UserMailer {
#
# PHP mail()
#
- if( count( $to ) > 1 ) {
+ if ( count( $to ) > 1 ) {
$headers['To'] = 'undisclosed-recipients:;';
}
$headers = self::arrayToHeaderString( $headers, $endl );
@@ -409,7 +411,7 @@ class UserMailer {
*/
public static function quotedPrintable( $string, $charset = '' ) {
# Probably incomplete; see RFC 2045
- if( empty( $charset ) ) {
+ if ( empty( $charset ) ) {
$charset = 'UTF-8';
}
$charset = strtoupper( $charset );
@@ -417,7 +419,7 @@ class UserMailer {
$illegal = '\x00-\x08\x0b\x0c\x0e-\x1f\x7f-\xff=';
$replace = $illegal . '\t ?_';
- if( !preg_match( "/[$illegal]/", $string ) ) {
+ if ( !preg_match( "/[$illegal]/", $string ) ) {
return $string;
}
$out = "=?$charset?Q?";
@@ -605,6 +607,7 @@ class EmailNotification {
wfRunHooks( 'UpdateUserMailerFormattedPageStatus', array( &$formattedPageStatus ) );
if ( !in_array( $this->pageStatus, $formattedPageStatus ) ) {
+ wfProfileOut( __METHOD__ );
throw new MWException( 'Not a valid page status!' );
}
@@ -667,11 +670,13 @@ class EmailNotification {
} elseif ( $targetUser->getOption( 'enotifusertalkpages' ) &&
( !$minorEdit || $targetUser->getOption( 'enotifminoredits' ) ) )
{
- if ( $targetUser->isEmailConfirmed() ) {
+ if ( !$targetUser->isEmailConfirmed() ) {
+ wfDebug( __METHOD__ . ": talk page owner doesn't have validated email\n" );
+ } elseif ( !wfRunHooks( 'AbortTalkPageEmailNotification', array( $targetUser, $title ) ) ) {
+ wfDebug( __METHOD__ . ": talk page update notification is aborted for this user\n" );
+ } else {
wfDebug( __METHOD__ . ": sending talk page update notification\n" );
return true;
- } else {
- wfDebug( __METHOD__ . ": talk page owner doesn't have validated email\n" );
}
} else {
wfDebug( __METHOD__ . ": talk page owner doesn't want notifications\n" );
@@ -696,20 +701,20 @@ class EmailNotification {
$keys = array();
$postTransformKeys = array();
- $pageTitleUrl = $this->title->getCanonicalUrl();
+ $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'] = "\n\n" . wfMessage( 'enotif_lastdiff',
- $this->title->getCanonicalUrl( 'diff=next&oldid=' . $this->oldid ) )
+ $this->title->getCanonicalURL( array( '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\n" . wfMessage( 'enotif_lastvisited',
- $this->title->getCanonicalUrl( 'diff=0&oldid=' . $this->oldid ) )
+ $this->title->getCanonicalURL( array( 'diff' => '0', 'oldid' => $this->oldid ) ) )
->inContentLanguage()->text();
}
$keys['$OLDID'] = $this->oldid;
@@ -724,10 +729,10 @@ class EmailNotification {
}
$keys['$PAGETITLE'] = $this->title->getPrefixedText();
- $keys['$PAGETITLE_URL'] = $this->title->getCanonicalUrl();
+ $keys['$PAGETITLE_URL'] = $this->title->getCanonicalURL();
$keys['$PAGEMINOREDIT'] = $this->minorEdit ?
wfMessage( 'minoredit' )->inContentLanguage()->text() : '';
- $keys['$UNWATCHURL'] = $this->title->getCanonicalUrl( 'action=unwatch' );
+ $keys['$UNWATCHURL'] = $this->title->getCanonicalURL( 'action=unwatch' );
if ( $this->editor->isAnon() ) {
# real anon (user:xxx.xxx.xxx.xxx)
@@ -738,18 +743,25 @@ class EmailNotification {
} else {
$keys['$PAGEEDITOR'] = $wgEnotifUseRealName ? $this->editor->getRealName() : $this->editor->getName();
$emailPage = SpecialPage::getSafeTitleFor( 'Emailuser', $this->editor->getName() );
- $keys['$PAGEEDITOR_EMAIL'] = $emailPage->getCanonicalUrl();
+ $keys['$PAGEEDITOR_EMAIL'] = $emailPage->getCanonicalURL();
}
- $keys['$PAGEEDITOR_WIKI'] = $this->editor->getUserPage()->getCanonicalUrl();
+ $keys['$PAGEEDITOR_WIKI'] = $this->editor->getUserPage()->getCanonicalURL();
# Replace this after transforming the message, bug 35019
$postTransformKeys['$PAGESUMMARY'] = $this->summary == '' ? ' - ' : $this->summary;
- # Now build message's subject and body
+ // Now build message's subject and body
+
+ // Messages:
+ // enotif_subject_deleted, enotif_subject_created, enotif_subject_moved,
+ // enotif_subject_restored, enotif_subject_changed
$this->subject = wfMessage( 'enotif_subject_' . $this->pageStatus )->inContentLanguage()
->params( $pageTitle, $keys['$PAGEEDITOR'] )->text();
+ // Messages:
+ // enotif_body_intro_deleted, enotif_body_intro_created, enotif_body_intro_moved,
+ // enotif_body_intro_restored, enotif_body_intro_changed
$keys['$PAGEINTRO'] = wfMessage( 'enotif_body_intro_' . $this->pageStatus )
->inContentLanguage()->params( $pageTitle, $keys['$PAGEEDITOR'], $pageTitleUrl )
->text();
@@ -790,8 +802,9 @@ class EmailNotification {
function compose( $user ) {
global $wgEnotifImpersonal;
- if ( !$this->composed_common )
+ if ( !$this->composed_common ) {
$this->composeCommonMailtext();
+ }
if ( $wgEnotifImpersonal ) {
$this->mailTargets[] = new MailAddress( $user );
diff --git a/includes/UserRightsProxy.php b/includes/UserRightsProxy.php
index cd5dff84..a8a22be7 100644
--- a/includes/UserRightsProxy.php
+++ b/includes/UserRightsProxy.php
@@ -74,7 +74,7 @@ class UserRightsProxy {
*/
public static function whoIs( $database, $id, $ignoreInvalidDB = false ) {
$user = self::newFromId( $database, $id, $ignoreInvalidDB );
- if( $user ) {
+ if ( $user ) {
return $user->name;
} else {
return false;
@@ -114,12 +114,12 @@ class UserRightsProxy {
*/
private static function newFromLookup( $database, $field, $value, $ignoreInvalidDB = false ) {
$db = self::getDB( $database, $ignoreInvalidDB );
- if( $db ) {
+ if ( $db ) {
$row = $db->selectRow( 'user',
array( 'user_id', 'user_name' ),
array( $field => $value ),
__METHOD__ );
- if( $row !== false ) {
+ if ( $row !== false ) {
return new UserRightsProxy( $db, $database,
$row->user_name,
intval( $row->user_id ) );
@@ -138,8 +138,8 @@ class UserRightsProxy {
*/
public static function getDB( $database, $ignoreInvalidDB = false ) {
global $wgDBname;
- if( self::validDatabase( $database ) ) {
- if( $database == $wgDBname ) {
+ if ( $ignoreInvalidDB || self::validDatabase( $database ) ) {
+ if ( $database == $wgDBname ) {
// Hmm... this shouldn't happen though. :)
return wfGetDB( DB_MASTER );
} else {
diff --git a/includes/WatchedItem.php b/includes/WatchedItem.php
index 5ac92f73..1e07e7c7 100644
--- a/includes/WatchedItem.php
+++ b/includes/WatchedItem.php
@@ -27,19 +27,37 @@
* @ingroup Watchlist
*/
class WatchedItem {
- var $mTitle, $mUser;
+ /**
+ * Constant to specify that user rights 'editmywatchlist' and
+ * 'viewmywatchlist' should not be checked.
+ * @since 1.22
+ */
+ const IGNORE_USER_RIGHTS = 0;
+
+ /**
+ * Constant to specify that user rights 'editmywatchlist' and
+ * 'viewmywatchlist' should be checked.
+ * @since 1.22
+ */
+ const CHECK_USER_RIGHTS = 1;
+
+ var $mTitle, $mUser, $mCheckRights;
private $loaded = false, $watched, $timestamp;
/**
* Create a WatchedItem object with the given user and title
+ * @since 1.22 $checkRights parameter added
* @param $user User: the user to use for (un)watching
* @param $title Title: the title we're going to (un)watch
+ * @param $checkRights int: Whether to check the 'viewmywatchlist' and 'editmywatchlist' rights.
+ * Pass either WatchedItem::IGNORE_USER_RIGHTS or WatchedItem::CHECK_USER_RIGHTS.
* @return WatchedItem object
*/
- public static function fromUserTitle( $user, $title ) {
+ public static function fromUserTitle( $user, $title, $checkRights = WatchedItem::CHECK_USER_RIGHTS ) {
$wl = new WatchedItem;
$wl->mUser = $user;
$wl->mTitle = $title;
+ $wl->mCheckRights = $checkRights;
return $wl;
}
@@ -89,6 +107,12 @@ class WatchedItem {
}
$this->loaded = true;
+ // Only loggedin user can have a watchlist
+ if ( $this->mUser->isAnon() ) {
+ $this->watched = false;
+ return;
+ }
+
# Pages and their talk pages are considered equivalent for watching;
# remember that talk namespaces are numbered as page namespace+1.
@@ -105,10 +129,22 @@ class WatchedItem {
}
/**
+ * Check permissions
+ * @param $what string: 'viewmywatchlist' or 'editmywatchlist'
+ */
+ private function isAllowed( $what ) {
+ return !$this->mCheckRights || $this->mUser->isAllowed( $what );
+ }
+
+ /**
* Is mTitle being watched by mUser?
* @return bool
*/
public function isWatched() {
+ if ( !$this->isAllowed( 'viewmywatchlist' ) ) {
+ return false;
+ }
+
$this->load();
return $this->watched;
}
@@ -120,6 +156,10 @@ class WatchedItem {
* the wl_notificationtimestamp field otherwise
*/
public function getNotificationTimestamp() {
+ if ( !$this->isAllowed( 'viewmywatchlist' ) ) {
+ return false;
+ }
+
$this->load();
if ( $this->watched ) {
return $this->timestamp;
@@ -135,6 +175,11 @@ class WatchedItem {
* page is not watched or the notification timestamp is already NULL.
*/
public function resetNotificationTimestamp( $force = '' ) {
+ // Only loggedin user can have a watchlist
+ if ( wfReadOnly() || $this->mUser->isAnon() || !$this->isAllowed( 'editmywatchlist' ) ) {
+ return;
+ }
+
if ( $force != 'force' ) {
$this->load();
if ( !$this->watched || $this->timestamp === null ) {
@@ -153,11 +198,17 @@ class WatchedItem {
/**
* Given a title and user (assumes the object is setup), add the watch to the
* database.
- * @return bool (always true)
+ * @return bool
*/
public function addWatch() {
wfProfileIn( __METHOD__ );
+ // Only loggedin user can have a watchlist
+ if ( wfReadOnly() || $this->mUser->isAnon() || !$this->isAllowed( 'editmywatchlist' ) ) {
+ wfProfileOut( __METHOD__ );
+ return false;
+ }
+
// Use INSERT IGNORE to avoid overwriting the notification timestamp
// if there's already an entry for this page
$dbw = wfGetDB( DB_MASTER );
@@ -192,6 +243,12 @@ class WatchedItem {
public function removeWatch() {
wfProfileIn( __METHOD__ );
+ // Only loggedin user can have a watchlist
+ if ( wfReadOnly() || $this->mUser->isAnon() || !$this->isAllowed( 'editmywatchlist' ) ) {
+ wfProfileOut( __METHOD__ );
+ return false;
+ }
+
$success = false;
$dbw = wfGetDB( DB_MASTER );
$dbw->delete( 'watchlist',
@@ -268,7 +325,7 @@ class WatchedItem {
);
}
- if( empty( $values ) ) {
+ if ( empty( $values ) ) {
// Nothing to do
return true;
}
diff --git a/includes/WebRequest.php b/includes/WebRequest.php
index 98007ef8..b17cb9ec 100644
--- a/includes/WebRequest.php
+++ b/includes/WebRequest.php
@@ -91,11 +91,11 @@ class WebRequest {
wfSuppressWarnings();
$a = parse_url( $url );
wfRestoreWarnings();
- if( $a ) {
+ if ( $a ) {
$path = isset( $a['path'] ) ? $a['path'] : '';
global $wgScript;
- if( $path == $wgScript && $want !== 'all' ) {
+ if ( $path == $wgScript && $want !== 'all' ) {
// Script inside a rewrite path?
// Abort to keep from breaking...
return $matches;
@@ -106,7 +106,7 @@ class WebRequest {
// Raw PATH_INFO style
$router->add( "$wgScript/$1" );
- if( isset( $_SERVER['SCRIPT_NAME'] )
+ if ( isset( $_SERVER['SCRIPT_NAME'] )
&& preg_match( '/\.php5?/', $_SERVER['SCRIPT_NAME'] ) )
{
# Check for SCRIPT_NAME, we handle index.php explicitly
@@ -116,17 +116,17 @@ class WebRequest {
}
global $wgArticlePath;
- if( $wgArticlePath ) {
+ if ( $wgArticlePath ) {
$router->add( $wgArticlePath );
}
global $wgActionPaths;
- if( $wgActionPaths ) {
+ if ( $wgActionPaths ) {
$router->add( $wgActionPaths, array( 'action' => '$key' ) );
}
global $wgVariantArticlePath, $wgContLang;
- if( $wgVariantArticlePath ) {
+ if ( $wgVariantArticlePath ) {
$router->add( $wgVariantArticlePath,
array( 'variant' => '$2' ),
array( '$2' => $wgContLang->getVariants() )
@@ -224,7 +224,7 @@ class WebRequest {
}
$matches = self::getPathInfo( 'title' );
- foreach( $matches as $key => $val) {
+ foreach ( $matches as $key => $val ) {
$this->data[$key] = $_GET[$key] = $_REQUEST[$key] = $val;
}
}
@@ -240,15 +240,15 @@ class WebRequest {
* @return array of URL variables to interpolate; empty if no match
*/
static function extractTitle( $path, $bases, $key = false ) {
- foreach( (array)$bases as $keyValue => $base ) {
+ foreach ( (array)$bases as $keyValue => $base ) {
// Find the part after $wgArticlePath
$base = str_replace( '$1', '', $base );
$baseLen = strlen( $base );
- if( substr( $path, 0, $baseLen ) == $base ) {
+ if ( substr( $path, 0, $baseLen ) == $base ) {
$raw = substr( $path, $baseLen );
- if( $raw !== '' ) {
+ if ( $raw !== '' ) {
$matches = array( 'title' => rawurldecode( $raw ) );
- if( $key ) {
+ if ( $key ) {
$matches[$key] = $keyValue;
}
return $matches;
@@ -271,8 +271,8 @@ class WebRequest {
*/
private function &fix_magic_quotes( &$arr, $topLevel = true ) {
$clean = array();
- foreach( $arr as $key => $val ) {
- if( is_array( $val ) ) {
+ foreach ( $arr as $key => $val ) {
+ if ( is_array( $val ) ) {
$cleanKey = $topLevel ? stripslashes( $key ) : $key;
$clean[$cleanKey] = $this->fix_magic_quotes( $arr[$key], false );
} else {
@@ -293,7 +293,7 @@ class WebRequest {
private function checkMagicQuotes() {
$mustFixQuotes = function_exists( 'get_magic_quotes_gpc' )
&& get_magic_quotes_gpc();
- if( $mustFixQuotes ) {
+ if ( $mustFixQuotes ) {
$this->fix_magic_quotes( $_COOKIE );
$this->fix_magic_quotes( $_ENV );
$this->fix_magic_quotes( $_GET );
@@ -311,8 +311,8 @@ class WebRequest {
* @private
*/
function normalizeUnicode( $data ) {
- if( is_array( $data ) ) {
- foreach( $data as $key => $val ) {
+ if ( is_array( $data ) ) {
+ foreach ( $data as $key => $val ) {
$data[$key] = $this->normalizeUnicode( $val );
}
} else {
@@ -335,19 +335,18 @@ class WebRequest {
# http://us2.php.net/variables.external#language.variables.external.dot-in-names
# Work around PHP *feature* to avoid *bugs* elsewhere.
$name = strtr( $name, '.', '_' );
- if( isset( $arr[$name] ) ) {
+ if ( isset( $arr[$name] ) ) {
global $wgContLang;
$data = $arr[$name];
- if( isset( $_GET[$name] ) && !is_array( $data ) ) {
+ if ( isset( $_GET[$name] ) && !is_array( $data ) ) {
# Check for alternate/legacy character encoding.
- if( isset( $wgContLang ) ) {
+ if ( isset( $wgContLang ) ) {
$data = $wgContLang->checkTitleEncoding( $data );
}
}
$data = $this->normalizeUnicode( $data );
return $data;
} else {
- taint( $default );
return $default;
}
}
@@ -364,10 +363,10 @@ class WebRequest {
*/
public function getVal( $name, $default = null ) {
$val = $this->getGPCVal( $this->data, $name, $default );
- if( is_array( $val ) ) {
+ if ( is_array( $val ) ) {
$val = $default;
}
- if( is_null( $val ) ) {
+ if ( is_null( $val ) ) {
return $val;
} else {
return (string)$val;
@@ -414,7 +413,7 @@ class WebRequest {
*/
public function getArray( $name, $default = null ) {
$val = $this->getGPCVal( $this->data, $name, $default );
- if( is_null( $val ) ) {
+ if ( is_null( $val ) ) {
return null;
} else {
return (array)$val;
@@ -433,7 +432,7 @@ class WebRequest {
*/
public function getIntArray( $name, $default = null ) {
$val = $this->getArray( $name, $default );
- if( is_array( $val ) ) {
+ if ( is_array( $val ) ) {
$val = array_map( 'intval', $val );
}
return $val;
@@ -570,6 +569,44 @@ class WebRequest {
}
/**
+ * Return the contents of the Query with no decoding. Use when you need to
+ * know exactly what was sent, e.g. for an OAuth signature over the elements.
+ *
+ * @return String
+ */
+ public function getRawQueryString() {
+ return $_SERVER['QUERY_STRING'];
+ }
+
+ /**
+ * Return the contents of the POST with no decoding. Use when you need to
+ * know exactly what was sent, e.g. for an OAuth signature over the elements.
+ *
+ * @return String
+ */
+ public function getRawPostString() {
+ if ( !$this->wasPosted() ) {
+ return '';
+ }
+ return $this->getRawInput();
+ }
+
+ /**
+ * Return the raw request body, with no processing. Cached since some methods
+ * disallow reading the stream more than once. As stated in the php docs, this
+ * does not work with enctype="multipart/form-data".
+ *
+ * @return String
+ */
+ public function getRawInput() {
+ static $input = false;
+ if ( $input === false ) {
+ $input = file_get_contents( 'php://input' );
+ }
+ return $input;
+ }
+
+ /**
* Get the HTTP method used for this request.
*
* @return String
@@ -603,7 +640,7 @@ class WebRequest {
* @return Boolean
*/
public function checkSessionCookie() {
- return isset( $_COOKIE[ session_name() ] );
+ return isset( $_COOKIE[session_name()] );
}
/**
@@ -615,7 +652,7 @@ class WebRequest {
* @return Mixed: cookie value or $default if the cookie not set
*/
public function getCookie( $key, $prefix = null, $default = null ) {
- if( $prefix === null ) {
+ if ( $prefix === null ) {
global $wgCookiePrefix;
$prefix = $wgCookiePrefix;
}
@@ -630,14 +667,14 @@ class WebRequest {
* @return String
*/
public function getRequestURL() {
- if( isset( $_SERVER['REQUEST_URI'] ) && strlen( $_SERVER['REQUEST_URI'] ) ) {
+ if ( isset( $_SERVER['REQUEST_URI'] ) && strlen( $_SERVER['REQUEST_URI'] ) ) {
$base = $_SERVER['REQUEST_URI'];
} elseif ( isset( $_SERVER['HTTP_X_ORIGINAL_URL'] ) && strlen( $_SERVER['HTTP_X_ORIGINAL_URL'] ) ) {
// Probably IIS; doesn't set REQUEST_URI
$base = $_SERVER['HTTP_X_ORIGINAL_URL'];
- } elseif( isset( $_SERVER['SCRIPT_NAME'] ) ) {
+ } elseif ( isset( $_SERVER['SCRIPT_NAME'] ) ) {
$base = $_SERVER['SCRIPT_NAME'];
- if( isset( $_SERVER['QUERY_STRING'] ) && $_SERVER['QUERY_STRING'] != '' ) {
+ if ( isset( $_SERVER['QUERY_STRING'] ) && $_SERVER['QUERY_STRING'] != '' ) {
$base .= '?' . $_SERVER['QUERY_STRING'];
}
} else {
@@ -651,14 +688,16 @@ class WebRequest {
// need to strip it or we get false-positive redirect loops
// or weird output URLs
$hash = strpos( $base, '#' );
- if( $hash !== false ) {
+ if ( $hash !== false ) {
$base = substr( $base, 0, $hash );
}
- if( $base[0] == '/' ) {
- return $base;
+
+ if ( $base[0] == '/' ) {
+ // More than one slash will look like it is protocol relative
+ return preg_replace( '!^/+!', '/', $base );
} else {
// We may get paths with a host prepended; strip it.
- return preg_replace( '!^[^:]+://[^/]+/!', '/', $base );
+ return preg_replace( '!^[^:]+://[^/]+/+!', '/', $base );
}
}
@@ -737,21 +776,21 @@ class WebRequest {
global $wgUser;
$limit = $this->getInt( 'limit', 0 );
- if( $limit < 0 ) {
+ if ( $limit < 0 ) {
$limit = 0;
}
- if( ( $limit == 0 ) && ( $optionname != '' ) ) {
+ if ( ( $limit == 0 ) && ( $optionname != '' ) ) {
$limit = $wgUser->getIntOption( $optionname );
}
- if( $limit <= 0 ) {
+ if ( $limit <= 0 ) {
$limit = $deflimit;
}
- if( $limit > 5000 ) {
+ if ( $limit > 5000 ) {
$limit = 5000; # We have *some* limits...
}
$offset = $this->getInt( 'offset', 0 );
- if( $offset < 0 ) {
+ if ( $offset < 0 ) {
$offset = 0;
}
@@ -842,9 +881,10 @@ class WebRequest {
return;
}
- if ( function_exists( 'apache_request_headers' ) ) {
- foreach ( apache_request_headers() as $tempName => $tempValue ) {
- $this->headers[ strtoupper( $tempName ) ] = $tempValue;
+ $apacheHeaders = function_exists( 'apache_request_headers' ) ? apache_request_headers() : false;
+ if ( $apacheHeaders ) {
+ foreach ( $apacheHeaders as $tempName => $tempValue ) {
+ $this->headers[strtoupper( $tempName )] = $tempValue;
}
} else {
foreach ( $_SERVER as $name => $value ) {
@@ -891,7 +931,7 @@ class WebRequest {
* @return Mixed
*/
public function getSessionData( $key ) {
- if( !isset( $_SESSION[$key] ) ) {
+ if ( !isset( $_SESSION[$key] ) ) {
return null;
}
return $_SESSION[$key];
@@ -1097,19 +1137,28 @@ HTML;
array_unshift( $ipchain, $ip );
}
- # Step through XFF list and find the last address in the list which is a trusted server
- # Set $ip to the IP address given by that trusted server, unless the address is not sensible (e.g. private)
+ # Step through XFF list and find the last address in the list which is a
+ # trusted server. Set $ip to the IP address given by that trusted server,
+ # unless the address is not sensible (e.g. private). However, prefer private
+ # IP addresses over proxy servers controlled by this site (more sensible).
foreach ( $ipchain as $i => $curIP ) {
- $curIP = IP::canonicalize( $curIP );
- if ( wfIsTrustedProxy( $curIP ) ) {
- if ( isset( $ipchain[$i + 1] ) ) {
- if ( $wgUsePrivateIPs || IP::isPublic( $ipchain[$i + 1 ] ) ) {
- $ip = $ipchain[$i + 1];
+ $curIP = IP::sanitizeIP( IP::canonicalize( $curIP ) );
+ if ( wfIsTrustedProxy( $curIP ) && isset( $ipchain[$i + 1] ) ) {
+ if ( wfIsConfiguredProxy( $curIP ) || // bug 48919; treat IP as sane
+ IP::isPublic( $ipchain[$i + 1] ) ||
+ $wgUsePrivateIPs
+ ) {
+ $nextIP = IP::canonicalize( $ipchain[$i + 1] );
+ if ( !$nextIP && wfIsConfiguredProxy( $ip ) ) {
+ // We have not yet made it past CDN/proxy servers of this site,
+ // so either they are misconfigured or there is some IP spoofing.
+ throw new MWException( "Invalid IP given in XFF '$forwardedFor'." );
}
+ $ip = $nextIP;
+ continue;
}
- } else {
- break;
}
+ break;
}
}
@@ -1117,7 +1166,7 @@ HTML;
wfRunHooks( 'GetIP', array( &$ip ) );
if ( !$ip ) {
- throw new MWException( "Unable to determine IP" );
+ throw new MWException( "Unable to determine IP." );
}
wfDebug( "IP: $ip\n" );
@@ -1266,13 +1315,13 @@ class FauxRequest extends WebRequest {
* @throws MWException
*/
public function __construct( $data = array(), $wasPosted = false, $session = null ) {
- if( is_array( $data ) ) {
+ if ( is_array( $data ) ) {
$this->data = $data;
} else {
throw new MWException( "FauxRequest() got bogus data" );
}
$this->wasPosted = $wasPosted;
- if( $session ) {
+ if ( $session ) {
$this->session = $session;
}
}
@@ -1337,10 +1386,11 @@ class FauxRequest extends WebRequest {
}
/**
- * @param $name
+ * @param string $name The name of the header to get (case insensitive).
* @return bool|string
*/
public function getHeader( $name ) {
+ $name = strtoupper( $name );
return isset( $this->headers[$name] ) ? $this->headers[$name] : false;
}
@@ -1349,6 +1399,7 @@ class FauxRequest extends WebRequest {
* @param $val string
*/
public function setHeader( $name, $val ) {
+ $name = strtoupper( $name );
$this->headers[$name] = $val;
}
@@ -1357,7 +1408,7 @@ 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;
@@ -1387,6 +1438,30 @@ class FauxRequest extends WebRequest {
}
/**
+ * FauxRequests shouldn't depend on raw request data (but that could be implemented here)
+ * @return String
+ */
+ public function getRawQueryString() {
+ return '';
+ }
+
+ /**
+ * FauxRequests shouldn't depend on raw request data (but that could be implemented here)
+ * @return String
+ */
+ public function getRawPostString() {
+ return '';
+ }
+
+ /**
+ * FauxRequests shouldn't depend on raw request data (but that could be implemented here)
+ * @return String
+ */
+ public function getRawInput() {
+ return '';
+ }
+
+ /**
* @param array $extWhitelist
* @return bool
*/
diff --git a/includes/WebResponse.php b/includes/WebResponse.php
index 8e15d712..ab7524c2 100644
--- a/includes/WebResponse.php
+++ b/includes/WebResponse.php
@@ -42,56 +42,81 @@ class WebResponse {
* Set the browser cookie
* @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.
+ * @param int|null $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
+ * null causes it to be a session cookie.
+ * @param array $options Assoc of additional cookie options:
+ * prefix: string, name prefix ($wgCookiePrefix)
+ * domain: string, cookie domain ($wgCookieDomain)
+ * path: string, cookie path ($wgCookiePath)
+ * secure: bool, secure attribute ($wgCookieSecure)
+ * httpOnly: bool, httpOnly attribute ($wgCookieHttpOnly)
+ * raw: bool, if true uses PHP's setrawcookie() instead of setcookie()
+ * For backwards compatability, if $options is not an array then it and
+ * the following two parameters will be interpreted as values for
+ * 'prefix', 'domain', and 'secure'
+ * @since 1.22 Replaced $prefix, $domain, and $forceSecure with $options
*/
- public function setcookie( $name, $value, $expire = 0, $prefix = null, $domain = null, $forceSecure = null ) {
+ public function setcookie( $name, $value, $expire = 0, $options = null ) {
global $wgCookiePath, $wgCookiePrefix, $wgCookieDomain;
global $wgCookieSecure, $wgCookieExpiration, $wgCookieHttpOnly;
- if ( $expire == 0 ) {
- $expire = time() + $wgCookieExpiration;
- }
- if( $prefix === null ) {
- $prefix = $wgCookiePrefix;
+
+ if ( !is_array( $options ) ) {
+ // Backwards compatability
+ $options = array( 'prefix' => $options );
+ if ( func_num_args() >= 5 ) {
+ $options['domain'] = func_get_arg( 4 );
+ }
+ if ( func_num_args() >= 6 ) {
+ $options['secure'] = func_get_arg( 5 );
+ }
}
- if( $domain === null ) {
- $domain = $wgCookieDomain;
+ $options = array_filter( $options, function ( $a ) {
+ return $a !== null;
+ } ) + array(
+ 'prefix' => $wgCookiePrefix,
+ 'domain' => $wgCookieDomain,
+ 'path' => $wgCookiePath,
+ 'secure' => $wgCookieSecure,
+ 'httpOnly' => $wgCookieHttpOnly,
+ 'raw' => false,
+ );
+
+ if ( $expire === null ) {
+ $expire = 0; // Session cookie
+ } elseif ( $expire == 0 && $wgCookieExpiration != 0 ) {
+ $expire = time() + $wgCookieExpiration;
}
- if ( is_null( $forceSecure ) ) {
- $secureCookie = $wgCookieSecure;
- } else {
- $secureCookie = $forceSecure;
+ // Don't mark the cookie as httpOnly if the requesting user-agent is
+ // known to have trouble with httpOnly cookies.
+ if ( !wfHttpOnlySafe() ) {
+ $options['httpOnly'] = false;
}
- // 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(
- $prefix . $name,
- $value,
- $expire,
- $wgCookiePath,
- $domain,
- $secureCookie,
- $httpOnlySafe ) ) . '"' );
- setcookie( $prefix . $name,
- $value,
- $expire,
- $wgCookiePath,
- $domain,
- $secureCookie,
- $httpOnlySafe );
+ $func = $options['raw'] ? 'setrawcookie' : 'setcookie';
+
+ if ( wfRunHooks( 'WebResponseSetCookie', array( &$name, &$value, &$expire, $options ) ) ) {
+ wfDebugLog( 'cookie',
+ $func . ': "' . implode( '", "',
+ array(
+ $options['prefix'] . $name,
+ $value,
+ $expire,
+ $options['path'],
+ $options['domain'],
+ $options['secure'],
+ $options['httpOnly'] ) ) . '"' );
+
+ call_user_func( $func,
+ $options['prefix'] . $name,
+ $value,
+ $expire,
+ $options['path'],
+ $options['domain'],
+ $options['secure'],
+ $options['httpOnly'] );
+ }
}
}
@@ -116,7 +141,9 @@ class FauxResponse extends WebResponse {
} else {
list( $key, $val ) = array_map( 'trim', explode( ":", $string, 2 ) );
- if( $replace || !isset( $this->headers[$key] ) ) {
+ $key = strtoupper( $key );
+
+ if ( $replace || !isset( $this->headers[$key] ) ) {
$this->headers[$key] = $val;
}
}
@@ -127,10 +154,12 @@ class FauxResponse extends WebResponse {
}
/**
- * @param $key string
+ * @param string $key The name of the header to get (case insensitive).
* @return string
*/
public function getheader( $key ) {
+ $key = strtoupper( $key );
+
if ( isset( $this->headers[$key] ) ) {
return $this->headers[$key];
}
@@ -152,11 +181,9 @@ class FauxResponse extends WebResponse {
* @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)
+ * @param array $options ignored
*/
- public function setcookie( $name, $value, $expire = 0, $prefix = null, $domain = null, $forceSecure = null ) {
+ public function setcookie( $name, $value, $expire = 0, $options = null ) {
$this->cookies[$name] = $value;
}
diff --git a/includes/WebStart.php b/includes/WebStart.php
index e6f31355..58c953af 100644
--- a/includes/WebStart.php
+++ b/includes/WebStart.php
@@ -48,7 +48,7 @@ if ( ini_get( 'register_globals' ) ) {
'HTTP_SESSION_VARS'
);
foreach ( $_REQUEST as $name => $value ) {
- if( in_array( $name, $verboten ) ) {
+ if ( in_array( $name, $verboten ) ) {
header( "HTTP/1.1 500 Internal Server Error" );
echo "register_globals security paranoia: trying to overwrite superglobals, aborting.";
die( -1 );
@@ -84,62 +84,56 @@ define( 'MEDIAWIKI', true );
# if we don't have permissions on parent directories.
$IP = getenv( 'MW_INSTALL_PATH' );
if ( $IP === false ) {
- if( realpath( '.' ) ) {
+ if ( realpath( '.' ) ) {
$IP = realpath( '.' );
} else {
$IP = dirname( __DIR__ );
}
}
-if ( isset( $_SERVER['MW_COMPILED'] ) ) {
- define( 'MW_COMPILED', 1 );
-} else {
- # Get MWInit class
- require_once( "$IP/includes/Init.php" );
-
- # Start the autoloader, so that extensions can derive classes from core files
- require_once( "$IP/includes/AutoLoader.php" );
+# Start the autoloader, so that extensions can derive classes from core files
+require_once "$IP/includes/AutoLoader.php";
- # Load the profiler
- require_once( "$IP/includes/profiler/Profiler.php" );
+# Load the profiler
+require_once "$IP/includes/profiler/Profiler.php";
- # Load up some global defines.
- require_once( "$IP/includes/Defines.php" );
-}
+# Load up some global defines.
+require_once "$IP/includes/Defines.php";
# Start the profiler
$wgProfiler = array();
if ( file_exists( "$IP/StartProfiler.php" ) ) {
- require( "$IP/StartProfiler.php" );
+ require "$IP/StartProfiler.php";
}
wfProfileIn( 'WebStart.php-conf' );
# Load default settings
-require_once( MWInit::compiledPath( "includes/DefaultSettings.php" ) );
+require_once "$IP/includes/DefaultSettings.php";
+
+# Load composer's autoloader if present
+if ( is_readable( "$IP/vendor/autoload.php" ) ) {
+ require_once "$IP/vendor/autoload.php";
+}
if ( defined( 'MW_CONFIG_CALLBACK' ) ) {
# Use a callback function to configure MediaWiki
- MWFunction::call( MW_CONFIG_CALLBACK );
+ call_user_func( MW_CONFIG_CALLBACK );
} else {
if ( !defined( 'MW_CONFIG_FILE' ) ) {
- define( 'MW_CONFIG_FILE', MWInit::interpretedPath( 'LocalSettings.php' ) );
+ define( 'MW_CONFIG_FILE', "$IP/LocalSettings.php" );
}
# LocalSettings.php is the per site customization file. If it does not exist
# the wiki installer needs to be launched or the generated file uploaded to
# the root wiki directory
- if( !file_exists( MW_CONFIG_FILE ) ) {
- require_once( "$IP/includes/templates/NoLocalSettings.php" );
+ if ( !file_exists( MW_CONFIG_FILE ) ) {
+ require_once "$IP/includes/templates/NoLocalSettings.php";
die();
}
# Include site settings. $IP may be changed (hopefully before the AutoLoader is invoked)
- require_once( MW_CONFIG_FILE );
-}
-
-if ( $wgEnableSelenium ) {
- require_once( MWInit::compiledPath( "includes/SeleniumWebSettings.php" ) );
+ require_once MW_CONFIG_FILE;
}
wfProfileOut( 'WebStart.php-conf' );
@@ -150,13 +144,11 @@ wfProfileIn( 'WebStart.php-ob_start' );
# that would cause us to potentially mix gzip and non-gzip output, creating a
# big mess.
if ( !defined( 'MW_NO_OUTPUT_BUFFER' ) && ob_get_level() == 0 ) {
- if ( !defined( 'MW_COMPILED' ) ) {
- require_once( "$IP/includes/OutputHandler.php" );
- }
+ require_once "$IP/includes/OutputHandler.php";
ob_start( 'wfOutputHandler' );
}
wfProfileOut( 'WebStart.php-ob_start' );
if ( !defined( 'MW_NO_SETUP' ) ) {
- require_once( MWInit::compiledPath( "includes/Setup.php" ) );
+ require_once "$IP/includes/Setup.php";
}
diff --git a/includes/Wiki.php b/includes/Wiki.php
index f8f699c9..ae75bf33 100644
--- a/includes/Wiki.php
+++ b/includes/Wiki.php
@@ -85,8 +85,6 @@ class MediaWiki {
} elseif ( $curid ) {
// URLs like this are generated by RC, because rc_title isn't always accurate
$ret = Title::newFromID( $curid );
- } elseif ( $title == '' && $action != 'delete' ) {
- $ret = Title::newMainPage();
} else {
$ret = Title::newFromURL( $title );
// Alias NS_MEDIA page URLs to NS_FILE...we only use NS_MEDIA
@@ -102,8 +100,12 @@ class MediaWiki {
$wgContLang->findVariantLink( $title, $ret );
}
}
- // For non-special titles, check for implicit titles
- if ( is_null( $ret ) || !$ret->isSpecialPage() ) {
+
+ // If title is not provided, always allow oldid and diff to set the title.
+ // If title is provided, allow oldid and diff to override the title, unless
+ // we are talking about a special page which might use these parameters for
+ // other purposes.
+ if ( $ret === null || !$ret->isSpecialPage() ) {
// We can have urls with just ?diff=,?oldid= or even just ?diff=
$oldid = $request->getInt( 'oldid' );
$oldid = $oldid ? $oldid : $request->getInt( 'diff' );
@@ -114,6 +116,11 @@ class MediaWiki {
}
}
+ // Use the main page as default title if nothing else has been provided
+ if ( $ret === null && strval( $title ) === '' && $action !== 'delete' ) {
+ $ret = Title::newMainPage();
+ }
+
if ( $ret === null || ( $ret->getDBkey() == '' && $ret->getInterwiki() == '' ) ) {
$ret = SpecialPage::getTitleFor( 'Badtitle' );
}
@@ -126,7 +133,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();
@@ -227,7 +234,7 @@ class MediaWiki {
if ( $title->getInterwiki() != '' ) {
$rdfrom = $request->getVal( 'rdfrom' );
if ( $rdfrom ) {
- $url = $title->getFullURL( 'rdfrom=' . urlencode( $rdfrom ) );
+ $url = $title->getFullURL( array( 'rdfrom' => $rdfrom ) );
} else {
$query = $request->getValues();
unset( $query['title'] );
@@ -307,7 +314,8 @@ class MediaWiki {
$output->redirect( $article );
} else {
wfProfileOut( __METHOD__ );
- throw new MWException( "Shouldn't happen: MediaWiki::initializeArticle() returned neither an object nor a URL" );
+ throw new MWException( "Shouldn't happen: MediaWiki::initializeArticle()"
+ . " returned neither an object nor a URL" );
}
}
@@ -427,7 +435,8 @@ class MediaWiki {
$act = $this->getAction();
- $action = Action::factory( $act, $page );
+ $action = Action::factory( $act, $page, $this->context );
+
if ( $action instanceof Action ) {
# Let Squid cache things if we can purge them.
if ( $wgUseSquid &&
@@ -480,7 +489,7 @@ class MediaWiki {
$resp->header( 'Retry-After: ' . max( intval( $maxLag ), 5 ) );
$resp->header( 'X-Database-Lag: ' . intval( $lag ) );
$resp->header( 'Content-Type: text/plain' );
- if( $wgShowHostnames ) {
+ if ( $wgShowHostnames ) {
echo "Waiting for $host: $lag seconds lagged\n";
} else {
echo "Waiting for a database server: $lag seconds lagged\n";
@@ -502,23 +511,6 @@ 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' ) {
@@ -539,6 +531,51 @@ class MediaWiki {
$action = $this->getAction();
$wgTitle = $title;
+ // If the user has forceHTTPS set to true, or if the user
+ // is in a group requiring HTTPS, or if they have the HTTPS
+ // preference set, redirect them to HTTPS.
+ // Note: Do this after $wgTitle is setup, otherwise the hooks run from
+ // isLoggedIn() will do all sorts of weird stuff.
+ if (
+ (
+ $request->getCookie( 'forceHTTPS', '' ) ||
+ // check for prefixed version for currently logged in users
+ $request->getCookie( 'forceHTTPS' ) ||
+ // Avoid checking the user and groups unless it's enabled.
+ (
+ $this->context->getUser()->isLoggedIn()
+ && $this->context->getUser()->requiresHTTPS()
+ )
+ ) &&
+ $request->detectProtocol() == 'http'
+ ) {
+ $oldUrl = $request->getFullRequestURL();
+ $redirUrl = str_replace( 'http://', 'https://', $oldUrl );
+
+ if ( $request->wasPosted() ) {
+ // This is weird and we'd hope it almost never happens. This
+ // means that a POST came in via HTTP and policy requires us
+ // redirecting to HTTPS. It's likely such a request is going
+ // to fail due to post data being lost, but let's try anyway
+ // and just log the instance.
+ //
+ // @todo @fixme See if we could issue a 307 or 308 here, need
+ // to see how clients (automated & browser) behave when we do
+ wfDebugLog( 'RedirectedPosts', "Redirected from HTTP to HTTPS: $oldUrl" );
+ }
+
+ // Setup dummy Title, otherwise OutputPage::redirect will fail
+ $title = Title::newFromText( NS_MAIN, 'REDIR' );
+ $this->context->setTitle( $title );
+ $output = $this->context->getOutput();
+ // Since we only do this redir to change proto, always send a vary header
+ $output->addVaryHeader( 'X-Forwarded-Proto' );
+ $output->redirect( $redirUrl );
+ $output->output();
+ wfProfileOut( __METHOD__ );
+ return;
+ }
+
if ( $wgUseFileCache && $title->getNamespace() >= 0 ) {
wfProfileIn( 'main-try-filecache' );
if ( HTMLFileCache::useFileCache( $this->context ) ) {
@@ -599,7 +636,7 @@ class MediaWiki {
* Do a job from the job queue
*/
private function doJobs() {
- global $wgJobRunRate;
+ global $wgJobRunRate, $wgPhpCli, $IP;
if ( $wgJobRunRate <= 0 || wfReadOnly() ) {
return;
@@ -615,23 +652,43 @@ class MediaWiki {
$n = intval( $wgJobRunRate );
}
- $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 );
+ if ( !wfShellExecDisabled() && is_executable( $wgPhpCli ) ) {
+ // Start a background process to run some of the jobs.
+ // This will be asynchronous on *nix though not on Windows.
+ wfProfileIn( __METHOD__ . '-exec' );
+ $retVal = 1;
+ $cmd = wfShellWikiCmd( "$IP/maintenance/runJobs.php", array( '--maxjobs', $n ) );
+ wfShellExec( "$cmd &", $retVal );
+ wfProfileOut( __METHOD__ . '-exec' );
+ } else {
+ try {
+ // Fallback to running the jobs here while the user waits
+ $group = JobQueueGroup::singleton();
+ do {
+ $job = $group->pop( JobQueueGroup::USE_CACHE ); // job from any queue
+ if ( $job ) {
+ $output = $job->toString() . "\n";
+ $t = - microtime( true );
+ wfProfileIn( __METHOD__ . '-' . get_class( $job ) );
+ $success = $job->run();
+ wfProfileOut( __METHOD__ . '-' . get_class( $job ) );
+ $group->ack( $job ); // done
+ $t += microtime( true );
+ $t = round( $t * 1000 );
+ if ( $success === false ) {
+ $output .= "Error: " . $job->getLastError() . ", Time: $t ms\n";
+ } else {
+ $output .= "Success, Time: $t ms\n";
+ }
+ wfDebugLog( 'jobqueue', $output );
+ }
+ } while ( --$n && $job );
+ } catch ( MWException $e ) {
+ // We don't want exceptions thrown during job execution to
+ // be reported to the user since the output is already sent.
+ // Instead we just log them.
+ MWExceptionHandler::logException( $e );
}
- } while ( --$n && $job );
+ }
}
}
diff --git a/includes/WikiError.php b/includes/WikiError.php
index 62781213..08eb8004 100644
--- a/includes/WikiError.php
+++ b/includes/WikiError.php
@@ -143,7 +143,7 @@ class WikiXmlError extends WikiError {
}
function _extractContext( $context, $offset ) {
- if( is_null( $context ) ) {
+ if ( is_null( $context ) ) {
return null;
} else {
// Hopefully integer overflow will be handled transparently here
diff --git a/includes/WikiFilePage.php b/includes/WikiFilePage.php
index 5e603d37..fe1ff88a 100644
--- a/includes/WikiFilePage.php
+++ b/includes/WikiFilePage.php
@@ -183,6 +183,10 @@ class WikiFilePage extends WikiPage {
// to be updated (in case the cached information is wrong)
$this->mFile->purgeCache( array( 'forThumbRefresh' => true ) );
}
+ if ( $this->mRepo ) {
+ // Purge redirect cache
+ $this->mRepo->invalidateImageRedirect( $this->mTitle );
+ }
return parent::doPurge();
}
}
diff --git a/includes/WikiMap.php b/includes/WikiMap.php
index b04a7842..da4416de 100644
--- a/includes/WikiMap.php
+++ b/includes/WikiMap.php
@@ -37,13 +37,18 @@ class WikiMap {
$wgConf->loadFullData();
list( $major, $minor ) = $wgConf->siteFromDB( $wikiID );
- if( $major === null ) {
+ if ( $major === null ) {
return null;
}
- $canonicalServer = $wgConf->get( 'wgCanonicalServer', $wikiID, $major,
- array( 'lang' => $minor, 'site' => $major ) );
$server = $wgConf->get( 'wgServer', $wikiID, $major,
array( 'lang' => $minor, 'site' => $major ) );
+
+ $canonicalServer = $wgConf->get( 'wgCanonicalServer', $wikiID, $major,
+ array( 'lang' => $minor, 'site' => $major ) );
+ if ( $canonicalServer === false || $canonicalServer === null ) {
+ $canonicalServer = $server;
+ }
+
$path = $wgConf->get( 'wgArticlePath', $wikiID, $major,
array( 'lang' => $minor, 'site' => $major ) );
return new WikiReference( $major, $minor, $canonicalServer, $path, $server );
@@ -73,7 +78,7 @@ class WikiMap {
* @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 ) {
+ public static function foreignUserLink( $wikiID, $user, $text = null ) {
return self::makeForeignLink( $wikiID, "User:$user", $text );
}
@@ -85,7 +90,7 @@ class WikiMap {
* @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 ) {
+ public static function makeForeignLink( $wikiID, $page, $text = null ) {
if ( !$text ) {
$text = $page;
}
@@ -211,15 +216,14 @@ class WikiReference {
}
/**
- * Get a URL based on $wgServer, like Title::getFullUrl() would produce
+ * Get a URL based on $wgServer, like Title::getFullURL() would produce
* when called locally on the wiki.
*
* @param string $page page name (must be normalized before calling this function!)
* @return String: URL
*/
public function getFullUrl( $page ) {
- return
- $this->mServer .
+ return $this->mServer .
$this->getLocalUrl( $page );
}
}
diff --git a/includes/WikiPage.php b/includes/WikiPage.php
index de881ef6..7c3dc937 100644
--- a/includes/WikiPage.php
+++ b/includes/WikiPage.php
@@ -23,7 +23,8 @@
/**
* Abstract class for type hinting (accepts WikiPage, Article, ImagePage, CategoryPage)
*/
-interface Page {}
+interface Page {
+}
/**
* Class representing a MediaWiki article and history.
@@ -51,6 +52,11 @@ class WikiPage implements Page, IDBAccessObject {
/**@}}*/
/**
+ * @var int
+ */
+ protected $mId = null;
+
+ /**
* @var int; one of the READ_* constants
*/
protected $mDataLoadedFrom = self::READ_NONE;
@@ -182,7 +188,7 @@ class WikiPage implements Page, IDBAccessObject {
* (and only when) $wgActions[$action] === true. This allows subclasses
* to override the default behavior.
*
- * @todo: move this UI stuff somewhere else
+ * @todo Move this UI stuff somewhere else
*
* @return Array
*/
@@ -228,6 +234,7 @@ class WikiPage implements Page, IDBAccessObject {
* @return void
*/
protected function clearCacheFields() {
+ $this->mId = null;
$this->mCounter = null;
$this->mRedirectTarget = null; // Title object if set
$this->mLastRevision = null; // Latest revision
@@ -299,7 +306,7 @@ class WikiPage implements Page, IDBAccessObject {
public function pageDataFromTitle( $dbr, $title, $options = array() ) {
return $this->pageData( $dbr, array(
'page_namespace' => $title->getNamespace(),
- 'page_title' => $title->getDBkey() ), $options );
+ 'page_title' => $title->getDBkey() ), $options );
}
/**
@@ -381,10 +388,11 @@ class WikiPage implements Page, IDBAccessObject {
// Old-fashioned restrictions
$this->mTitle->loadRestrictions( $data->page_restrictions );
- $this->mCounter = intval( $data->page_counter );
- $this->mTouched = wfTimestamp( TS_MW, $data->page_touched );
- $this->mIsRedirect = intval( $data->page_is_redirect );
- $this->mLatest = intval( $data->page_latest );
+ $this->mId = intval( $data->page_id );
+ $this->mCounter = intval( $data->page_counter );
+ $this->mTouched = wfTimestamp( TS_MW, $data->page_touched );
+ $this->mIsRedirect = intval( $data->page_is_redirect );
+ $this->mLatest = intval( $data->page_latest );
// Bug 37225: $latest may no longer match the cached latest Revision object.
// Double-check the ID of any cached latest Revision object for consistency.
if ( $this->mLastRevision && $this->mLastRevision->getId() != $this->mLatest ) {
@@ -397,6 +405,8 @@ class WikiPage implements Page, IDBAccessObject {
$this->mTitle->loadFromRow( false );
$this->clearCacheFields();
+
+ $this->mId = 0;
}
$this->mDataLoaded = true;
@@ -407,14 +417,20 @@ class WikiPage implements Page, IDBAccessObject {
* @return int Page ID
*/
public function getId() {
- return $this->mTitle->getArticleID();
+ if ( !$this->mDataLoaded ) {
+ $this->loadPageData();
+ }
+ return $this->mId;
}
/**
* @return bool Whether or not the page exists in the database
*/
public function exists() {
- return $this->mTitle->exists();
+ if ( !$this->mDataLoaded ) {
+ $this->loadPageData();
+ }
+ return $this->mId > 0;
}
/**
@@ -426,7 +442,7 @@ class WikiPage implements Page, IDBAccessObject {
* @return bool
*/
public function hasViewableContent() {
- return $this->mTitle->exists() || $this->mTitle->isAlwaysKnown();
+ return $this->exists() || $this->mTitle->isAlwaysKnown();
}
/**
@@ -447,7 +463,9 @@ class WikiPage implements Page, IDBAccessObject {
*/
public function isRedirect() {
$content = $this->getContent();
- if ( !$content ) return false;
+ if ( !$content ) {
+ return false;
+ }
return $content->isRedirect();
}
@@ -524,6 +542,7 @@ class WikiPage implements Page, IDBAccessObject {
$db = wfGetDB( DB_SLAVE );
$revSelectFields = Revision::selectFields();
+ $row = null;
while ( $continue ) {
$row = $db->selectRow(
array( 'page', 'revision' ),
@@ -631,7 +650,7 @@ class WikiPage implements Page, IDBAccessObject {
* @return String|false The text of the current revision
* @deprecated as of 1.21, getContent() should be used instead.
*/
- public function getText( $audience = Revision::FOR_PUBLIC, User $user = null ) { // @todo: deprecated, replace usage!
+ public function getText( $audience = Revision::FOR_PUBLIC, User $user = null ) { // @todo deprecated, replace usage!
ContentHandler::deprecated( __METHOD__, '1.21' );
$this->loadLastEdit();
@@ -782,7 +801,7 @@ class WikiPage implements Page, IDBAccessObject {
public function setCachedLastEditTime( $timestamp ) {
global $wgMemc;
$key = wfMemcKey( 'page-lastedit', md5( $this->mTitle->getPrefixedDBkey() ) );
- $wgMemc->set( $key, wfTimestamp( TS_MW, $timestamp ), 60*15 );
+ $wgMemc->set( $key, wfTimestamp( TS_MW, $timestamp ), 60 * 15 );
}
/**
@@ -891,10 +910,10 @@ class WikiPage implements Page, IDBAccessObject {
$dbw = wfGetDB( DB_MASTER );
$dbw->replace( 'redirect', array( 'rd_from' ),
array(
- 'rd_from' => $this->getId(),
+ 'rd_from' => $this->getId(),
'rd_namespace' => $rt->getNamespace(),
- 'rd_title' => $rt->getDBkey(),
- 'rd_fragment' => $rt->getFragment(),
+ 'rd_title' => $rt->getDBkey(),
+ 'rd_fragment' => $rt->getFragment(),
'rd_interwiki' => $rt->getInterwiki(),
),
__METHOD__
@@ -929,7 +948,7 @@ class WikiPage implements Page, IDBAccessObject {
// This can be hard to reverse and may produce loops,
// so they may be disabled in the site configuration.
$source = $this->mTitle->getFullURL( 'redirect=no' );
- return $rt->getFullURL( 'rdfrom=' . urlencode( $source ) );
+ return $rt->getFullURL( array( 'rdfrom' => $source ) );
} else {
// External pages pages without "local" bit set are not valid
// redirect targets
@@ -1006,8 +1025,8 @@ class WikiPage implements Page, IDBAccessObject {
/**
* Get the last N authors
- * @param $num Integer: number of revisions to get
- * @param string $revLatest the latest rev_id, selected from the master (optional)
+ * @param int $num Number of revisions to get
+ * @param int|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 ) {
@@ -1068,7 +1087,7 @@ class WikiPage implements Page, IDBAccessObject {
return $wgEnableParserCache
&& $parserOptions->getStubThreshold() == 0
- && $this->mTitle->exists()
+ && $this->exists()
&& ( $oldid === null || $oldid === 0 || $oldid === $this->getLatest() )
&& $this->getContentHandler()->isParserCacheSupported();
}
@@ -1078,8 +1097,8 @@ class WikiPage implements Page, IDBAccessObject {
* The parser cache will be used if possible.
*
* @since 1.19
- * @param $parserOptions ParserOptions to use for the parse operation
- * @param $oldid Revision ID to get the text from, passing null or 0 will
+ * @param ParserOptions $parserOptions ParserOptions to use for the parse operation
+ * @param null|int $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
@@ -1124,7 +1143,7 @@ class WikiPage implements Page, IDBAccessObject {
}
// Don't update page view counters on views from bot users (bug 14044)
- if ( !$wgDisableCounters && !$user->isAllowed( 'bot' ) && $this->mTitle->exists() ) {
+ if ( !$wgDisableCounters && !$user->isAllowed( 'bot' ) && $this->exists() ) {
DeferredUpdates::addUpdate( new ViewCountUpdate( $this->getId() ) );
DeferredUpdates::addUpdate( new SiteStatsUpdate( 1, 0, 0 ) );
}
@@ -1140,13 +1159,12 @@ class WikiPage implements Page, IDBAccessObject {
public function doPurge() {
global $wgUseSquid;
- if( !wfRunHooks( 'ArticlePurge', array( &$this ) ) ) {
+ if ( !wfRunHooks( 'ArticlePurge', array( &$this ) ) ) {
return false;
}
// Invalidate the cache
$this->mTitle->invalidateCache();
- $this->clear();
if ( $wgUseSquid ) {
// Commit the transaction before the purge is sent
@@ -1159,16 +1177,18 @@ class WikiPage implements Page, IDBAccessObject {
}
if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
- // @todo: move this logic to MessageCache
+ // @todo move this logic to MessageCache
- if ( $this->mTitle->exists() ) {
+ if ( $this->exists() ) {
// 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;
+ if ( $text === null ) {
+ $text = false;
+ }
} else {
$text = false;
}
@@ -1210,6 +1230,7 @@ class WikiPage implements Page, IDBAccessObject {
if ( $affected ) {
$newid = $dbw->insertId();
+ $this->mId = $newid;
$this->mTitle->resetArticleID( $newid );
}
wfProfileOut( __METHOD__ );
@@ -1258,7 +1279,7 @@ class WikiPage implements Page, IDBAccessObject {
);
if ( $wgContentHandlerUseDB ) {
- $row[ 'page_content_model' ] = $revision->getContentModel();
+ $row['page_content_model'] = $revision->getContentModel();
}
$dbw->update( 'page',
@@ -1436,12 +1457,12 @@ class WikiPage implements Page, IDBAccessObject {
}
/**
- * Returns true iff this page's content model supports sections.
+ * Returns true if 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.
+ * @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();
@@ -1466,6 +1487,7 @@ class WikiPage implements Page, IDBAccessObject {
$newContent = $sectionContent;
} else {
if ( !$this->supportsSections() ) {
+ wfProfileOut( __METHOD__ );
throw new MWException( "sections not supported for content model " . $this->getContentHandler()->getModelID() );
}
@@ -1507,7 +1529,7 @@ class WikiPage implements Page, IDBAccessObject {
*/
function checkFlags( $flags ) {
if ( !( $flags & EDIT_NEW ) && !( $flags & EDIT_UPDATE ) ) {
- if ( $this->mTitle->getArticleID() ) {
+ if ( $this->exists() ) {
$flags |= EDIT_UPDATE;
} else {
$flags |= EDIT_NEW;
@@ -1602,7 +1624,7 @@ class WikiPage implements Page, IDBAccessObject {
* 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 bool|int $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
*
@@ -1682,12 +1704,18 @@ class WikiPage implements Page, IDBAccessObject {
// Provide autosummaries if one is not provided and autosummaries are enabled.
if ( $wgUseAutomaticEditSummaries && $flags & EDIT_AUTOSUMMARY && $summary == '' ) {
- if ( !$old_content ) $old_content = null;
+ if ( !$old_content ) {
+ $old_content = null;
+ }
$summary = $handler->getAutosummary( $old_content, $content, $flags );
}
$editInfo = $this->prepareContentForEdit( $content, null, $user, $serialisation_format );
$serialized = $editInfo->pst;
+
+ /**
+ * @var Content $content
+ */
$content = $editInfo->pstContent;
$newsize = $content->getSize();
@@ -1731,6 +1759,7 @@ class WikiPage implements Page, IDBAccessObject {
if ( $changed ) {
if ( !$content->isValid() ) {
+ wfProfileOut( __METHOD__ );
throw new MWException( "New content failed validity check!" );
}
@@ -1933,7 +1962,7 @@ class WikiPage implements Page, IDBAccessObject {
$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.
+ // @todo ConversionTable should become a separate content model, so we don't need special cases like this one.
$options->disableContentConversion();
}
@@ -1956,16 +1985,18 @@ class WikiPage implements Page, IDBAccessObject {
* 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
+ * @param Content $content
+ * @param int|null $revid
+ * @param User|null $user
+ * @param string|null $serialization_format
*
* @return bool|object
*
* @since 1.21
*/
- public function prepareContentForEdit( Content $content, $revid = null, User $user = null, $serialization_format = null ) {
+ public function prepareContentForEdit( Content $content, $revid = null, User $user = null,
+ $serialization_format = null
+ ) {
global $wgContLang, $wgUser;
$user = is_null( $user ) ? $wgUser : $user;
//XXX: check $user->getId() here???
@@ -2030,7 +2061,8 @@ class WikiPage implements Page, IDBAccessObject {
$content = $revision->getContent();
// Parse the text
- // Be careful not to double-PST: $text is usually already PST-ed once
+ // Be careful not to do pre-save transform twice: $text is usually
+ // already pre-save transformed once.
if ( !$this->mPreparedEdit || $this->mPreparedEdit->output->getFlag( 'vary-revision' ) ) {
wfDebug( __METHOD__ . ": No prepared edit or vary-revision is set...\n" );
$editInfo = $this->prepareContentForEdit( $content, $revision->getId(), $user );
@@ -2047,7 +2079,9 @@ class WikiPage implements Page, IDBAccessObject {
// Update the links tables and other secondary data
if ( $content ) {
- $updates = $content->getSecondaryDataUpdates( $this->getTitle(), null, true, $editInfo->output );
+ $recursive = $options['changed']; // bug 50785
+ $updates = $content->getSecondaryDataUpdates(
+ $this->getTitle(), null, $recursive, $editInfo->output );
DataUpdate::runUpdates( $updates );
}
@@ -2057,19 +2091,11 @@ class WikiPage implements Page, IDBAccessObject {
if ( 0 == mt_rand( 0, 99 ) ) {
// Flush old entries from the `recentchanges` table; we do this on
// random requests so as to avoid an increase in writes for no good reason
- global $wgRCMaxAge;
-
- $dbw = wfGetDB( DB_MASTER );
- $cutoff = $dbw->timestamp( time() - $wgRCMaxAge );
- $dbw->delete(
- 'recentchanges',
- array( 'rc_timestamp < ' . $dbw->addQuotes( $cutoff ) ),
- __METHOD__
- );
+ RecentChange::purgeExpiredChanges();
}
}
- if ( !$this->mTitle->exists() ) {
+ if ( !$this->exists() ) {
wfProfileOut( __METHOD__ );
return;
}
@@ -2093,8 +2119,7 @@ class WikiPage implements Page, IDBAccessObject {
}
DeferredUpdates::addUpdate( new SiteStatsUpdate( 0, 1, $good, $total ) );
- DeferredUpdates::addUpdate( new SearchUpdate( $id, $title, $content->getTextForSearchIndex() ) );
- // @TODO: let the search engine decide what to do with the content object
+ DeferredUpdates::addUpdate( new SearchUpdate( $id, $title, $content ) );
// If this is another user's talk page, update newtalk.
// Don't do this if $options['changed'] = false (null-edits) nor if
@@ -2104,17 +2129,20 @@ class WikiPage implements Page, IDBAccessObject {
&& $shortTitle != $user->getTitleKey()
&& !( $revision->isMinor() && $user->isAllowed( 'nominornewtalk' ) )
) {
- if ( wfRunHooks( 'ArticleEditUpdateNewTalk', array( &$this ) ) ) {
- $other = User::newFromName( $shortTitle, false );
- if ( !$other ) {
- wfDebug( __METHOD__ . ": invalid username\n" );
- } elseif ( User::isIP( $shortTitle ) ) {
- // An anonymous user
- $other->setNewtalk( true, $revision );
- } elseif ( $other->isLoggedIn() ) {
- $other->setNewtalk( true, $revision );
- } else {
- wfDebug( __METHOD__ . ": don't need to notify a nonexistent user\n" );
+ $recipient = User::newFromName( $shortTitle, false );
+ if ( !$recipient ) {
+ wfDebug( __METHOD__ . ": invalid username\n" );
+ } else {
+ // Allow extensions to prevent user notification when a new message is added to their talk page
+ if ( wfRunHooks( 'ArticleEditUpdateNewTalk', array( &$this, $recipient ) ) ) {
+ if ( User::isIP( $shortTitle ) ) {
+ // An anonymous user
+ $recipient->setNewtalk( true, $revision );
+ } elseif ( $recipient->isLoggedIn() ) {
+ $recipient->setNewtalk( true, $revision );
+ } else {
+ wfDebug( __METHOD__ . ": don't need to notify a nonexistent user\n" );
+ }
}
}
}
@@ -2122,12 +2150,14 @@ class WikiPage implements Page, IDBAccessObject {
if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
// XXX: could skip pseudo-messages like js/css here, based on content model.
$msgtext = $content ? $content->getWikitextForTransclusion() : null;
- if ( $msgtext === false || $msgtext === null ) $msgtext = '';
+ if ( $msgtext === false || $msgtext === null ) {
+ $msgtext = '';
+ }
MessageCache::singleton()->replace( $shortTitle, $msgtext );
}
- if( $options['created'] ) {
+ if ( $options['created'] ) {
self::onArticleCreate( $this->mTitle );
} else {
self::onArticleEdit( $this->mTitle );
@@ -2152,7 +2182,7 @@ class WikiPage implements Page, IDBAccessObject {
ContentHandler::deprecated( __METHOD__, "1.21" );
$content = ContentHandler::makeContent( $text, $this->getTitle() );
- return $this->doQuickEditContent( $content, $user, $comment, $minor );
+ $this->doQuickEditContent( $content, $user, $comment, $minor );
}
/**
@@ -2160,13 +2190,15 @@ class WikiPage implements Page, IDBAccessObject {
* 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 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
+ * @param string $serialisation_format Format for storing the content in the database
+ * @param bool $minor Whereas it's a minor modification
*/
- public function doQuickEditContent( Content $content, User $user, $comment = '', $minor = 0, $serialisation_format = null ) {
+ public function doQuickEditContent( Content $content, User $user, $comment = '', $minor = false,
+ $serialisation_format = null
+ ) {
wfProfileIn( __METHOD__ );
$serialized = $content->serialize( $serialisation_format );
@@ -2193,14 +2225,14 @@ class WikiPage implements Page, IDBAccessObject {
* This works for protection both existing and non-existing pages.
*
* @param array $limit set of restriction keys
- * @param $reason String
- * @param &$cascade Integer. Set to false if cascading protection isn't allowed.
* @param array $expiry per restriction type expiration
- * @param $user User The user updating the restrictions
+ * @param int &$cascade Set to false if cascading protection isn't allowed.
+ * @param string $reason
+ * @param User $user The user updating the restrictions
* @return Status
*/
public function doUpdateRestrictions( array $limit, array $expiry, &$cascade, $reason, User $user ) {
- global $wgContLang;
+ global $wgCascadingRestrictionLevels;
if ( wfReadOnly() ) {
return Status::newFatal( 'readonlytext', wfReadOnlyReason() );
@@ -2208,7 +2240,7 @@ class WikiPage implements Page, IDBAccessObject {
$restrictionTypes = $this->mTitle->getRestrictionTypes();
- $id = $this->mTitle->getArticleID();
+ $id = $this->getId();
if ( !$cascade ) {
$cascade = false;
@@ -2273,65 +2305,40 @@ class WikiPage implements Page, IDBAccessObject {
$logAction = 'protect';
}
- $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 != '' ) {
- $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' ) {
- $expiryText = wfMessage(
- 'protect-expiring',
- $wgContLang->timeanddate( $expiry[$action], false, false ),
- $wgContLang->date( $expiry[$action], false, false ),
- $wgContLang->time( $expiry[$action], false, false )
- )->inContentLanguage()->text();
- } else {
- $expiryText = wfMessage( 'protect-expiry-indefinite' )
- ->inContentLanguage()->text();
- }
-
- if ( $protectDescription !== '' ) {
- $protectDescription .= wfMessage( 'word-separator' )->inContentLanguage()->text();
- }
- $protectDescription .= wfMessage( 'protect-summary-desc' )
- ->params( $actionText, $restrictionsText, $expiryText )
- ->inContentLanguage()->text();
- $protectDescriptionLog .= $expiryText . ') ';
- }
- }
- $protectDescriptionLog = trim( $protectDescriptionLog );
-
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 certain restrictions can cascade...
$editrestriction = isset( $limit['edit'] ) ? array( $limit['edit'] ) : $this->mTitle->getRestrictions( 'edit' );
+ foreach ( array_keys( $editrestriction, 'sysop' ) as $key ) {
+ $editrestriction[$key] = 'editprotected'; // backwards compatibility
+ }
+ foreach ( array_keys( $editrestriction, 'autoconfirmed' ) as $key ) {
+ $editrestriction[$key] = 'editsemiprotected'; // backwards compatibility
+ }
+
+ $cascadingRestrictionLevels = $wgCascadingRestrictionLevels;
+ foreach ( array_keys( $cascadingRestrictionLevels, 'sysop' ) as $key ) {
+ $cascadingRestrictionLevels[$key] = 'editprotected'; // backwards compatibility
+ }
+ foreach ( array_keys( $cascadingRestrictionLevels, 'autoconfirmed' ) as $key ) {
+ $cascadingRestrictionLevels[$key] = 'editsemiprotected'; // backwards compatibility
+ }
// The schema allows multiple restrictions
- if ( !in_array( 'protect', $editrestriction ) && !in_array( 'sysop', $editrestriction ) ) {
+ if ( !array_intersect( $editrestriction, $cascadingRestrictionLevels ) ) {
$cascade = false;
}
+ // insert null revision to identify the page protection change as edit summary
+ $latest = $this->getLatest();
+ $nullRevision = $this->insertProtectNullRevision( $revCommentMsg, $limit, $expiry, $cascade, $reason );
+ if ( $nullRevision === null ) {
+ return Status::newFatal( 'no-null-revision', $this->mTitle->getPrefixedText() );
+ }
+
// Update restrictions table
foreach ( $limit as $action => $restrictions ) {
if ( $restrictions != '' ) {
@@ -2340,7 +2347,7 @@ class WikiPage implements Page, IDBAccessObject {
'pr_type' => $action,
'pr_level' => $restrictions,
'pr_cascade' => ( $cascade && $action == 'edit' ) ? 1 : 0,
- 'pr_expiry' => $encodedExpiry[$action]
+ 'pr_expiry' => $dbw->encodeExpiry( $expiry[$action] )
),
__METHOD__
);
@@ -2350,41 +2357,12 @@ class WikiPage implements Page, IDBAccessObject {
}
}
- // Prepare a null revision to be added to the history
- $editComment = $wgContLang->ucfirst(
- wfMessage(
- $revCommentMsg,
- $this->mTitle->getPrefixedText()
- )->inContentLanguage()->text()
- );
- if ( $reason ) {
- $editComment .= wfMessage( 'colon-separator' )->inContentLanguage()->text() . $reason;
- }
- if ( $protectDescription ) {
- $editComment .= wfMessage( 'word-separator' )->inContentLanguage()->text();
- $editComment .= wfMessage( 'parentheses' )->params( $protectDescription )->inContentLanguage()->text();
- }
- if ( $cascade ) {
- $editComment .= wfMessage( 'word-separator' )->inContentLanguage()->text();
- $editComment .= wfMessage( 'brackets' )->params(
- wfMessage( 'protect-summary-cascade' )->inContentLanguage()->text()
- )->inContentLanguage()->text();
- }
-
- // Insert a null revision
- $nullRevision = Revision::newNullRevision( $dbw, $id, $editComment, true );
- $nullRevId = $nullRevision->insertOn( $dbw );
-
- $latest = $this->getLatest();
- // Update page record
- $dbw->update( 'page',
- array( /* SET */
- 'page_touched' => $dbw->timestamp(),
- 'page_restrictions' => '',
- 'page_latest' => $nullRevId
- ), array( /* WHERE */
- 'page_id' => $id
- ), __METHOD__
+ // Clear out legacy restriction fields
+ $dbw->update(
+ 'page',
+ array( 'page_restrictions' => '' ),
+ array( 'page_id' => $id ),
+ __METHOD__
);
wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $nullRevision, $latest, $user ) );
@@ -2401,7 +2379,7 @@ class WikiPage implements Page, IDBAccessObject {
'pt_title' => $this->mTitle->getDBkey(),
'pt_create_perm' => $limit['create'],
'pt_timestamp' => $dbw->encodeExpiry( wfTimestampNow() ),
- 'pt_expiry' => $encodedExpiry['create'],
+ 'pt_expiry' => $dbw->encodeExpiry( $expiry['create'] ),
'pt_user' => $user->getId(),
'pt_reason' => $reason,
), __METHOD__
@@ -2417,21 +2395,154 @@ class WikiPage implements Page, IDBAccessObject {
}
$this->mTitle->flushRestrictions();
+ InfoAction::invalidateCache( $this->mTitle );
if ( $logAction == 'unprotect' ) {
- $logParams = array();
+ $params = array();
} else {
- $logParams = array( $protectDescriptionLog, $cascade ? 'cascade' : '' );
+ $protectDescriptionLog = $this->protectDescriptionLog( $limit, $expiry );
+ $params = array( $protectDescriptionLog, $cascade ? 'cascade' : '' );
}
// Update the protection log
$log = new LogPage( 'protect' );
- $log->addEntry( $logAction, $this->mTitle, trim( $reason ), $logParams, $user );
+ $log->addEntry( $logAction, $this->mTitle, trim( $reason ), $params, $user );
return Status::newGood();
}
/**
+ * Insert a new null revision for this page.
+ *
+ * @param string $revCommentMsg comment message key for the revision
+ * @param array $limit set of restriction keys
+ * @param array $expiry per restriction type expiration
+ * @param int $cascade Set to false if cascading protection isn't allowed.
+ * @param string $reason
+ * @return Revision|null on error
+ */
+ public function insertProtectNullRevision( $revCommentMsg, array $limit, array $expiry, $cascade, $reason ) {
+ global $wgContLang;
+ $dbw = wfGetDB( DB_MASTER );
+
+ // Prepare a null revision to be added to the history
+ $editComment = $wgContLang->ucfirst(
+ wfMessage(
+ $revCommentMsg,
+ $this->mTitle->getPrefixedText()
+ )->inContentLanguage()->text()
+ );
+ if ( $reason ) {
+ $editComment .= wfMessage( 'colon-separator' )->inContentLanguage()->text() . $reason;
+ }
+ $protectDescription = $this->protectDescription( $limit, $expiry );
+ if ( $protectDescription ) {
+ $editComment .= wfMessage( 'word-separator' )->inContentLanguage()->text();
+ $editComment .= wfMessage( 'parentheses' )->params( $protectDescription )->inContentLanguage()->text();
+ }
+ if ( $cascade ) {
+ $editComment .= wfMessage( 'word-separator' )->inContentLanguage()->text();
+ $editComment .= wfMessage( 'brackets' )->params(
+ wfMessage( 'protect-summary-cascade' )->inContentLanguage()->text()
+ )->inContentLanguage()->text();
+ }
+
+ $nullRev = Revision::newNullRevision( $dbw, $this->getId(), $editComment, true );
+ if ( $nullRev ) {
+ $nullRev->insertOn( $dbw );
+
+ // Update page record and touch page
+ $oldLatest = $nullRev->getParentId();
+ $this->updateRevisionOn( $dbw, $nullRev, $oldLatest );
+ }
+
+ return $nullRev;
+ }
+
+ /**
+ * @param string $expiry 14-char timestamp or "infinity", or false if the input was invalid
+ * @return string
+ */
+ protected function formatExpiry( $expiry ) {
+ global $wgContLang;
+ $dbr = wfGetDB( DB_SLAVE );
+
+ $encodedExpiry = $dbr->encodeExpiry( $expiry );
+ if ( $encodedExpiry != 'infinity' ) {
+ return wfMessage(
+ 'protect-expiring',
+ $wgContLang->timeanddate( $expiry, false, false ),
+ $wgContLang->date( $expiry, false, false ),
+ $wgContLang->time( $expiry, false, false )
+ )->inContentLanguage()->text();
+ } else {
+ return wfMessage( 'protect-expiry-indefinite' )
+ ->inContentLanguage()->text();
+ }
+ }
+
+ /**
+ * Builds the description to serve as comment for the edit.
+ *
+ * @param array $limit set of restriction keys
+ * @param array $expiry per restriction type expiration
+ * @return string
+ */
+ public function protectDescription( array $limit, array $expiry ) {
+ $protectDescription = '';
+
+ foreach ( array_filter( $limit ) as $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();
+
+ $expiryText = $this->formatExpiry( $expiry[$action] );
+
+ if ( $protectDescription !== '' ) {
+ $protectDescription .= wfMessage( 'word-separator' )->inContentLanguage()->text();
+ }
+ $protectDescription .= wfMessage( 'protect-summary-desc' )
+ ->params( $actionText, $restrictionsText, $expiryText )
+ ->inContentLanguage()->text();
+ }
+
+ return $protectDescription;
+ }
+
+ /**
+ * Builds the description to serve as comment for the log entry.
+ *
+ * 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.
+ *
+ * @param array $limit set of restriction keys
+ * @param array $expiry per restriction type expiration
+ * @return string
+ */
+ public function protectDescriptionLog( array $limit, array $expiry ) {
+ global $wgContLang;
+
+ $protectDescriptionLog = '';
+
+ foreach ( array_filter( $limit ) as $action => $restrictions ) {
+ $expiryText = $this->formatExpiry( $expiry[$action] );
+ $protectDescriptionLog .= $wgContLang->getDirMark() . "[$action=$restrictions] ($expiryText)";
+ }
+
+ return trim( $protectDescriptionLog );
+ }
+
+ /**
* Take an array of page restrictions and flatten it to a string
* suitable for insertion into the page_restrictions field.
* @param $limit Array
@@ -2446,10 +2557,8 @@ class WikiPage implements Page, IDBAccessObject {
$bits = array();
ksort( $limit );
- foreach ( $limit as $action => $restrictions ) {
- if ( $restrictions != '' ) {
- $bits[] = "$action=$restrictions";
- }
+ foreach ( array_filter( $limit ) as $action => $restrictions ) {
+ $bits[] = "$action=$restrictions";
}
return implode( ':', $bits );
@@ -2575,8 +2684,8 @@ class WikiPage implements Page, IDBAccessObject {
);
if ( $wgContentHandlerUseDB ) {
- $row[ 'ar_content_model' ] = 'rev_content_model';
- $row[ 'ar_content_format' ] = 'rev_content_format';
+ $row['ar_content_model'] = 'rev_content_model';
+ $row['ar_content_format'] = 'rev_content_format';
}
$dbw->insertSelect( 'archive', array( 'page', 'revision' ),
@@ -2589,7 +2698,7 @@ class WikiPage implements Page, IDBAccessObject {
// 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
+ $ok = ( $dbw->affectedRows() > 0 ); // $id could be laggy
if ( !$ok ) {
$dbw->rollback( __METHOD__ );
@@ -2597,6 +2706,10 @@ class WikiPage implements Page, IDBAccessObject {
return $status;
}
+ if ( !$dbw->cascadingDeletes() ) {
+ $dbw->delete( 'revision', array( 'rev_page' => $id ), __METHOD__ );
+ }
+
$this->doDeleteUpdates( $id, $content );
// Log the deletion, if the page was suppressed, log it at Oversight instead
@@ -2621,7 +2734,7 @@ class WikiPage implements Page, IDBAccessObject {
/**
* Do some database updates after deletion
*
- * @param int $id page_id value of the page being deleted (B/C, currently unused)
+ * @param int $id page_id value of the page being deleted
* @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.
*/
@@ -2636,11 +2749,11 @@ class WikiPage implements Page, IDBAccessObject {
// Clear caches
WikiPage::onArticleDelete( $this->mTitle );
- // Reset this object
- $this->clear();
+ // Reset this object and the Title object
+ $this->loadFromRow( false, self::READ_LATEST );
- // Clear the cached article id so the interface doesn't act like we exist
- $this->mTitle->resetArticleID( 0 );
+ // Search engine
+ DeferredUpdates::addUpdate( new SearchUpdate( $id, $this->mTitle ) );
}
/**
@@ -2650,7 +2763,7 @@ class WikiPage implements Page, IDBAccessObject {
* performs permissions checks on $user, then calls commitRollback()
* to do the dirty work
*
- * @todo: separate the business/permission stuff out from backend code
+ * @todo Separate the business/permission stuff out from backend code
*
* @param string $fromP Name of the user whose edits to rollback.
* @param string $summary Custom summary. Set to default summary if empty.
@@ -2794,7 +2907,7 @@ class WikiPage implements Page, IDBAccessObject {
$wgContLang->timeanddate( wfTimestamp( TS_MW, $s->rev_timestamp ) ),
$current->getId(), $wgContLang->timeanddate( $current->getTimestamp() )
);
- if( $summary instanceof Message ) {
+ if ( $summary instanceof Message ) {
$summary = $summary->params( $args )->inContentLanguage()->text();
} else {
$summary = wfMsgReplaceArgs( $summary, $args );
@@ -2835,8 +2948,8 @@ class WikiPage implements Page, IDBAccessObject {
$resultDetails = array(
'summary' => $summary,
'current' => $current,
- 'target' => $target,
- 'newid' => $revId
+ 'target' => $target,
+ 'newid' => $revId
);
return array();
@@ -2890,6 +3003,7 @@ class WikiPage implements Page, IDBAccessObject {
// File cache
HTMLFileCache::clearFileCache( $title );
+ InfoAction::invalidateCache( $title );
// Messages
if ( $title->getNamespace() == NS_MEDIAWIKI ) {
@@ -2918,7 +3032,7 @@ class WikiPage implements Page, IDBAccessObject {
* Purge caches on page update etc
*
* @param $title Title object
- * @todo: verify that $title is always a Title object (and never false or null), add Title hint to parameter $title
+ * @todo Verify that $title is always a Title object (and never false or null), add Title hint to parameter $title
*/
public static function onArticleEdit( $title ) {
// Invalidate caches of articles which include this page
@@ -2932,11 +3046,35 @@ class WikiPage implements Page, IDBAccessObject {
// Clear file cache for this page only
HTMLFileCache::clearFileCache( $title );
+ InfoAction::invalidateCache( $title );
}
/**#@-*/
/**
+ * Returns a list of categories this page is a member of.
+ * Results will include hidden categories
+ *
+ * @return TitleArray
+ */
+ public function getCategories() {
+ $id = $this->getId();
+ if ( $id == 0 ) {
+ return TitleArray::newFromResult( new FakeResultWrapper( array() ) );
+ }
+
+ $dbr = wfGetDB( DB_SLAVE );
+ $res = $dbr->select( 'categorylinks',
+ array( 'cl_to AS page_title, ' . NS_CATEGORY . ' AS page_namespace' ),
+ // Have to do that since DatabaseBase::fieldNamesWithAlias treats numeric indexes
+ // as not being aliases, and NS_CATEGORY is numeric
+ array( 'cl_from' => $id ),
+ __METHOD__ );
+
+ return TitleArray::newFromResult( $res );
+ }
+
+ /**
* Returns a list of hidden categories this page is a member of.
* Uses the page_props and categorylinks tables.
*
@@ -2944,7 +3082,7 @@ class WikiPage implements Page, IDBAccessObject {
*/
public function getHiddenCategories() {
$result = array();
- $id = $this->mTitle->getArticleID();
+ $id = $this->getId();
if ( $id == 0 ) {
return array();
@@ -3005,69 +3143,65 @@ class WikiPage implements Page, IDBAccessObject {
* @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();
+ public function updateCategoryCounts( array $added, array $deleted ) {
+ $that = $this;
+ $method = __METHOD__;
$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.
- $insertCats = array_merge( $added, $deleted );
- if ( !$insertCats ) {
- // Okay, nothing to do
- return;
- }
-
- $insertRows = array();
-
- foreach ( $insertCats as $cat ) {
- $insertRows[] = array(
- 'cat_id' => $dbw->nextSequenceValue( 'category_cat_id_seq' ),
- 'cat_title' => $cat
- );
- }
- $dbw->insert( 'category', $insertRows, __METHOD__, 'IGNORE' );
-
- $addFields = array( 'cat_pages = cat_pages + 1' );
- $removeFields = array( 'cat_pages = cat_pages - 1' );
+ // Do this at the end of the commit to reduce lock wait timeouts
+ $dbw->onTransactionPreCommitOrIdle(
+ function() use ( $dbw, $that, $method, $added, $deleted ) {
+ $ns = $that->getTitle()->getNamespace();
+
+ $addFields = array( 'cat_pages = cat_pages + 1' );
+ $removeFields = array( 'cat_pages = cat_pages - 1' );
+ if ( $ns == NS_CATEGORY ) {
+ $addFields[] = 'cat_subcats = cat_subcats + 1';
+ $removeFields[] = 'cat_subcats = cat_subcats - 1';
+ } elseif ( $ns == NS_FILE ) {
+ $addFields[] = 'cat_files = cat_files + 1';
+ $removeFields[] = 'cat_files = cat_files - 1';
+ }
- if ( $ns == NS_CATEGORY ) {
- $addFields[] = 'cat_subcats = cat_subcats + 1';
- $removeFields[] = 'cat_subcats = cat_subcats - 1';
- } elseif ( $ns == NS_FILE ) {
- $addFields[] = 'cat_files = cat_files + 1';
- $removeFields[] = 'cat_files = cat_files - 1';
- }
+ if ( count( $added ) ) {
+ $insertRows = array();
+ foreach ( $added as $cat ) {
+ $insertRows[] = array(
+ 'cat_title' => $cat,
+ 'cat_pages' => 1,
+ 'cat_subcats' => ( $ns == NS_CATEGORY ) ? 1 : 0,
+ 'cat_files' => ( $ns == NS_FILE ) ? 1 : 0,
+ );
+ }
+ $dbw->upsert(
+ 'category',
+ $insertRows,
+ array( 'cat_title' ),
+ $addFields,
+ $method
+ );
+ }
- if ( $added ) {
- $dbw->update(
- 'category',
- $addFields,
- array( 'cat_title' => $added ),
- __METHOD__
- );
- }
+ if ( count( $deleted ) ) {
+ $dbw->update(
+ 'category',
+ $removeFields,
+ array( 'cat_title' => $deleted ),
+ $method
+ );
+ }
- if ( $deleted ) {
- $dbw->update(
- 'category',
- $removeFields,
- array( 'cat_title' => $deleted ),
- __METHOD__
- );
- }
+ foreach ( $added as $catName ) {
+ $cat = Category::newFromName( $catName );
+ wfRunHooks( 'CategoryAfterPageAdded', array( $cat, $that ) );
+ }
- 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 ) );
- }
+ foreach ( $deleted as $catName ) {
+ $cat = Category::newFromName( $catName );
+ wfRunHooks( 'CategoryAfterPageRemoved', array( $cat, $that ) );
+ }
+ }
+ );
}
/**
@@ -3088,7 +3222,7 @@ class WikiPage implements Page, IDBAccessObject {
// are visible.
// Get templates from templatelinks
- $id = $this->mTitle->getArticleID();
+ $id = $this->getId();
$tlTemplates = array();
@@ -3233,7 +3367,7 @@ class WikiPage implements Page, IDBAccessObject {
public function viewUpdates() {
wfDeprecated( __METHOD__, '1.18' );
global $wgUser;
- return $this->doViewUpdates( $wgUser );
+ $this->doViewUpdates( $wgUser );
}
/**
@@ -3318,7 +3452,7 @@ class PoolWorkArticleView extends PoolCounterWork {
/**
* Constructor
*
- * @param $page Page
+ * @param $page Page|WikiPage
* @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
@@ -3373,7 +3507,7 @@ class PoolWorkArticleView extends PoolCounterWork {
function doWork() {
global $wgUseFileCache;
- // @todo: several of the methods called on $this->page are not declared in Page, but present
+ // @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();
@@ -3398,6 +3532,9 @@ class PoolWorkArticleView extends PoolCounterWork {
return false;
}
+ // Reduce effects of race conditions for slow parses (bug 46014)
+ $cacheTime = wfTimestampNow();
+
$time = - microtime( true );
$this->parserOutput = $content->getParserOutput( $this->page->getTitle(), $this->revid, $this->parserOptions );
$time += microtime( true );
@@ -3409,7 +3546,8 @@ class PoolWorkArticleView extends PoolCounterWork {
}
if ( $this->cacheable && $this->parserOutput->isCacheable() ) {
- ParserCache::singleton()->save( $this->parserOutput, $this->page, $this->parserOptions );
+ ParserCache::singleton()->save(
+ $this->parserOutput, $this->page, $this->parserOptions, $cacheTime );
}
// Make sure file cache is not used on uncacheable content.
diff --git a/includes/Xml.php b/includes/Xml.php
index 57a4fcf6..ac0539d1 100644
--- a/includes/Xml.php
+++ b/includes/Xml.php
@@ -38,13 +38,13 @@ class Xml {
*/
public static function element( $element, $attribs = null, $contents = '', $allowShortTag = true ) {
$out = '<' . $element;
- if( !is_null( $attribs ) ) {
+ if ( !is_null( $attribs ) ) {
$out .= self::expandAttributes( $attribs );
}
- if( is_null( $contents ) ) {
+ if ( is_null( $contents ) ) {
$out .= '>';
} else {
- if( $allowShortTag && $contents === '' ) {
+ if ( $allowShortTag && $contents === '' ) {
$out .= ' />';
} else {
$out .= '>' . htmlspecialchars( $contents ) . "</$element>";
@@ -64,10 +64,10 @@ class Xml {
*/
public static function expandAttributes( $attribs ) {
$out = '';
- if( is_null( $attribs ) ) {
+ if ( is_null( $attribs ) ) {
return null;
- } elseif( is_array( $attribs ) ) {
- foreach( $attribs as $name => $val ) {
+ } elseif ( is_array( $attribs ) ) {
+ foreach ( $attribs as $name => $val ) {
$out .= " {$name}=\"" . Sanitizer::encodeAttribute( $val ) . '"';
}
return $out;
@@ -88,10 +88,10 @@ class Xml {
*/
public static function elementClean( $element, $attribs = array(), $contents = '' ) {
global $wgContLang;
- if( $attribs ) {
+ if ( $attribs ) {
$attribs = array_map( array( 'UtfNormal', 'cleanUp' ), $attribs );
}
- if( $contents ) {
+ if ( $contents ) {
wfProfileIn( __METHOD__ . '-norm' );
$contents = $wgContLang->normalize( $contents );
wfProfileOut( __METHOD__ . '-norm' );
@@ -115,7 +115,9 @@ class Xml {
* @param string $element element name
* @return string
*/
- public static function closeElement( $element ) { return "</$element>"; }
+ public static function closeElement( $element ) {
+ return "</$element>";
+ }
/**
* Same as Xml::element(), but does not escape contents. Handy when the
@@ -144,11 +146,11 @@ class Xml {
wfDeprecated( __METHOD__, '1.19' );
return Html::namespaceSelector( array(
'selected' => $selected,
- 'all' => $all,
- 'label' => $label,
+ 'all' => $all,
+ 'label' => $label,
), array(
- 'name' => $element_name,
- 'id' => 'namespace',
+ 'name' => $element_name,
+ 'id' => 'namespace',
'class' => 'namespaceselector',
) );
}
@@ -164,12 +166,15 @@ class Xml {
public static function monthSelector( $selected = '', $allmonths = null, $id = 'month' ) {
global $wgLang;
$options = array();
- if( is_null( $selected ) )
+ if ( is_null( $selected ) ) {
$selected = '';
- if( !is_null( $allmonths ) )
+ }
+ if ( !is_null( $allmonths ) ) {
$options[] = self::option( wfMessage( 'monthsall' )->text(), $allmonths, $selected === $allmonths );
- for( $i = 1; $i < 13; $i++ )
+ }
+ for ( $i = 1; $i < 13; $i++ ) {
$options[] = self::option( $wgLang->getMonthName( $i ), $i, $selected === $i );
+ }
return self::openElement( 'select', array( 'id' => $id, 'name' => 'month', 'class' => 'mw-month-selector' ) )
. implode( "\n", $options )
. self::closeElement( 'select' );
@@ -182,17 +187,18 @@ class Xml {
*/
public static function dateMenu( $year, $month ) {
# Offset overrides year/month selection
- if( $month && $month !== -1 ) {
+ if ( $month && $month !== -1 ) {
$encMonth = intval( $month );
} else {
$encMonth = '';
}
- if( $year ) {
+ if ( $year ) {
$encYear = intval( $year );
- } elseif( $encMonth ) {
- $thisMonth = intval( gmdate( 'n' ) );
- $thisYear = intval( gmdate( 'Y' ) );
- if( intval( $encMonth ) > $thisMonth ) {
+ } elseif ( $encMonth ) {
+ $timestamp = MWTimestamp::getInstance();
+ $thisMonth = intval( $timestamp->format( 'n' ) );
+ $thisYear = intval( $timestamp->format( 'Y' ) );
+ if ( intval( $encMonth ) > $thisMonth ) {
$thisYear--;
}
$encYear = $thisYear;
@@ -200,9 +206,9 @@ class Xml {
$encYear = '';
}
$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' ) . ' '.
+ return self::label( wfMessage( 'year' )->text(), 'year' ) . ' ' .
+ Html::input( 'year', $encYear, 'number', $inputAttribs ) . ' ' .
+ self::label( wfMessage( 'month' )->text(), 'month' ) . ' ' .
self::monthSelector( $encMonth, -1 );
}
@@ -224,7 +230,7 @@ class Xml {
// Make sure the site language is in the list;
// a custom language code might not have a defined name...
- if( !array_key_exists( $wgLanguageCode, $languages ) ) {
+ if ( !array_key_exists( $wgLanguageCode, $languages ) ) {
$languages[$wgLanguageCode] = $wgLanguageCode;
}
@@ -237,14 +243,14 @@ class Xml {
*/
$selected = isset( $languages[$selected] ) ? $selected : $wgLanguageCode;
$options = "\n";
- foreach( $languages as $code => $name ) {
- $options .= Xml::option( "$code - $name", $code, ($code == $selected) ) . "\n";
+ foreach ( $languages as $code => $name ) {
+ $options .= Xml::option( "$code - $name", $code, $code == $selected ) . "\n";
}
$attrs = array( 'id' => 'wpUserLanguage', 'name' => 'wpUserLanguage' );
$attrs = array_merge( $attrs, $overrideAttrs );
- if( $msg === null ) {
+ if ( $msg === null ) {
$msg = wfMessage( 'yourlanguage' );
}
return array(
@@ -288,11 +294,11 @@ class Xml {
public static function input( $name, $size = false, $value = false, $attribs = array() ) {
$attributes = array( 'name' => $name );
- if( $size ) {
+ if ( $size ) {
$attributes['size'] = $size;
}
- if( $value !== false ) { // maybe 0
+ if ( $value !== false ) { // maybe 0
$attributes['value'] = $value;
}
@@ -330,7 +336,7 @@ class Xml {
* @param array $attribs other attributes
* @return string HTML
*/
- public static function check( $name, $checked = false, $attribs=array() ) {
+ public static function check( $name, $checked = false, $attribs = array() ) {
return self::element( 'input', array_merge(
array(
'name' => $name,
@@ -369,10 +375,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'];
}
@@ -389,7 +395,7 @@ class Xml {
* @param array $attribs other attributes
* @return string HTML
*/
- public static function inputLabel( $label, $name, $id, $size=false, $value=false, $attribs = array() ) {
+ public static function inputLabel( $label, $name, $id, $size = false, $value = false, $attribs = array() ) {
list( $label, $input ) = self::inputLabelSep( $label, $name, $id, $size, $value, $attribs );
return $label . '&#160;' . $input;
}
@@ -460,18 +466,18 @@ class Xml {
/**
* Convenience function to build an HTML drop-down list item.
- * @param string $text text for this item
+ * @param string $text text for this item. Will be HTML escaped
* @param string $value form submission value; if empty, use text
* @param $selected boolean: if true, will be the default selected item
* @param array $attribs optional additional HTML attributes
* @return string HTML
*/
- public static function option( $text, $value=null, $selected = false,
+ public static function option( $text, $value = null, $selected = false,
$attribs = array() ) {
- if( !is_null( $value ) ) {
+ if ( !is_null( $value ) ) {
$attribs['value'] = $value;
}
- if( $selected ) {
+ if ( $selected ) {
$attribs['selected'] = 'selected';
}
return Html::element( 'option', $attribs, $text );
@@ -488,47 +494,53 @@ class Xml {
* @param $tabindex Mixed: Value of the tabindex attribute
* @return string
*/
- public static function listDropDown( $name= '', $list = '', $other = '', $selected = '', $class = '', $tabindex = null ) {
+ public static function listDropDown( $name = '', $list = '', $other = '', $selected = '', $class = '', $tabindex = null ) {
$optgroup = false;
$options = self::option( $other, 'other', $selected === 'other' );
- foreach ( explode( "\n", $list ) as $option) {
+ foreach ( explode( "\n", $list ) as $option ) {
$value = trim( $option );
if ( $value == '' ) {
continue;
- } elseif ( substr( $value, 0, 1) == '*' && substr( $value, 1, 1) != '*' ) {
+ } 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) == '**' ) {
+ } elseif ( substr( $value, 0, 2 ) == '**' ) {
// groupmember
$value = trim( substr( $value, 2 ) );
$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();
- if( $name ) {
+ if ( $name ) {
$attribs['id'] = $name;
$attribs['name'] = $name;
}
- if( $class ) {
+ if ( $class ) {
$attribs['class'] = $class;
}
- if( $tabindex ) {
+ if ( $tabindex ) {
$attribs['tabindex'] = $tabindex;
}
@@ -590,6 +602,7 @@ class Xml {
* for JavaScript source code.
* Illegal control characters are assumed not to be present.
*
+ * @deprecated since 1.21; use Xml::encodeJsVar() or Xml::encodeJsCall() instead
* @param string $string to escape
* @return String
*/
@@ -621,72 +634,46 @@ class Xml {
}
/**
- * Encode a variable of unknown type to JavaScript.
- * Arrays are converted to JS arrays, objects are converted to JS associative
- * arrays (objects). So cast your PHP associative arrays to objects before
- * passing them to here.
+ * Encode a variable of arbitrary type to JavaScript.
+ * If the value is an XmlJsCode object, pass through the object's value verbatim.
*
- * @param $value
+ * @note Only use this function for generating JavaScript code. If generating output
+ * for a proper JSON parser, just call FormatJson::encode() directly.
*
- * @return string
+ * @param mixed $value The value being encoded. Can be any type except a resource.
+ * @param bool $pretty If true, add non-significant whitespace to improve readability.
+ * @return string|bool: String if successful; false upon failure
*/
- public static function encodeJsVar( $value ) {
- if ( is_bool( $value ) ) {
- $s = $value ? 'true' : 'false';
- } elseif ( is_null( $value ) ) {
- $s = 'null';
- } elseif ( is_int( $value ) || is_float( $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
- ) {
- $s = '[';
- foreach ( $value as $elt ) {
- if ( $s != '[' ) {
- $s .= ',';
- }
- $s .= self::encodeJsVar( $elt );
- }
- $s .= ']';
- } elseif ( $value instanceof XmlJsCode ) {
- $s = $value->value;
- } elseif ( is_object( $value ) || is_array( $value ) ) {
- // Objects and associative arrays
- $s = '{';
- foreach ( (array)$value as $name => $elt ) {
- if ( $s != '{' ) {
- $s .= ',';
- }
-
- $s .= '"' . self::escapeJsString( $name ) . '":' .
- self::encodeJsVar( $elt );
- }
- $s .= '}';
- } else {
- $s = '"' . self::escapeJsString( $value ) . '"';
+ public static function encodeJsVar( $value, $pretty = false ) {
+ if ( $value instanceof XmlJsCode ) {
+ return $value->value;
}
- return $s;
+ return FormatJson::encode( $value, $pretty, FormatJson::UTF8_OK );
}
/**
* Create a call to a JavaScript function. The supplied arguments will be
* encoded using Xml::encodeJsVar().
*
+ * @since 1.17
* @param string $name The name of the function to call, or a JavaScript expression
* which evaluates to a function object which is called.
- * @param array $args of arguments to pass to the function.
- *
- * @since 1.17
- *
- * @return string
+ * @param array $args The arguments to pass to the function.
+ * @param bool $pretty If true, add non-significant whitespace to improve readability.
+ * @return string|bool: String if successful; false upon failure
*/
- public static function encodeJsCall( $name, $args ) {
+ public static function encodeJsCall( $name, $args, $pretty = false ) {
foreach ( $args as &$arg ) {
- $arg = Xml::encodeJsVar( $arg );
+ $arg = Xml::encodeJsVar( $arg, $pretty );
+ if ( $arg === false ) {
+ return false;
+ }
}
- return "$name(" . implode( ', ', $args ) . ");\n";
+ return "$name(" . ( $pretty
+ ? ( ' ' . implode( ', ', $args ) . ' ' )
+ : implode( ',', $args )
+ ) . ");";
}
/**
@@ -704,7 +691,7 @@ class Xml {
# case folding violates XML standard, turn it off
xml_parser_set_option( $parser, XML_OPTION_CASE_FOLDING, false );
- if( !xml_parse( $parser, $text, true ) ) {
+ if ( !xml_parse( $parser, $text, true ) ) {
//$err = xml_error_string( xml_get_error_code( $parser ) );
//$position = xml_get_current_byte_index( $parser );
//$fragment = $this->extractFragment( $html, $position );
@@ -753,26 +740,32 @@ class Xml {
/**
* 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.
+ * @param array $fields Associative array, key is the name of a message that contains a description for the field, value is an HTML string containing the appropriate input.
+ * @param string $submitLabel The name of a message containing a label for the submit button.
+ * @param array $submitAttribs The attributes to add to the submit button
* @return string HTML form.
*/
- public static function buildForm( $fields, $submitLabel = null ) {
+ public static function buildForm( $fields, $submitLabel = null, $submitAttribs = array() ) {
$form = '';
$form .= "<table><tbody>";
- foreach( $fields as $labelmsg => $input ) {
+ foreach ( $fields as $labelmsg => $input ) {
$id = "mw-$labelmsg";
$form .= Xml::openElement( 'tr', array( 'id' => $id ) );
+
+ // TODO use a <label> here for accessibility purposes - will need
+ // to either not use a table to build the form, or find the ID of
+ // the input somehow.
+
$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' );
}
- if( $submitLabel ) {
+ if ( $submitLabel ) {
$form .= Xml::openElement( 'tr' );
$form .= Xml::tags( 'td', array(), '' );
- $form .= Xml::openElement( 'td', array( 'class' => 'mw-submit' ) ) . Xml::submitButton( wfMessage( $submitLabel )->text() ) . Xml::closeElement( 'td' );
+ $form .= Xml::openElement( 'td', array( 'class' => 'mw-submit' ) ) . Xml::submitButton( wfMessage( $submitLabel )->text(), $submitAttribs ) . Xml::closeElement( 'td' );
$form .= Xml::closeElement( 'tr' );
}
@@ -794,7 +787,7 @@ class Xml {
if ( is_array( $headers ) ) {
$s .= Xml::openElement( 'thead', $attribs );
- foreach( $headers as $id => $header ) {
+ foreach ( $headers as $id => $header ) {
$attribs = array();
if ( is_string( $id ) ) {
@@ -806,7 +799,7 @@ class Xml {
$s .= Xml::closeElement( 'thead' );
}
- foreach( $rows as $id => $row ) {
+ foreach ( $rows as $id => $row ) {
$attribs = array();
if ( is_string( $id ) ) {
@@ -830,8 +823,7 @@ class Xml {
public static function buildTableRow( $attribs, $cells ) {
$s = Xml::openElement( 'tr', $attribs );
- foreach( $cells as $id => $cell ) {
-
+ foreach ( $cells as $id => $cell ) {
$attribs = array();
if ( is_string( $id ) ) {
@@ -899,7 +891,7 @@ class XmlSelect {
*/
public function addOption( $name, $value = false ) {
// Stab stab stab
- $value = ($value !== false) ? $value : $name;
+ $value = $value !== false ? $value : $name;
$this->options[] = array( $name => $value );
}
@@ -927,7 +919,7 @@ class XmlSelect {
static function formatOptions( $options, $default = false ) {
$data = '';
- foreach( $options as $label => $value ) {
+ foreach ( $options as $label => $value ) {
if ( is_array( $value ) ) {
$contents = self::formatOptions( $value, $default );
$data .= Html::rawElement( 'optgroup', array( 'label' => $label ), $contents ) . "\n";
@@ -963,6 +955,11 @@ class XmlSelect {
* Xml::encodeJsVar( new XmlJsCode( 'a + b' ) );
*
* Returns "a + b".
+ *
+ * @note As of 1.21, XmlJsCode objects cannot be nested inside objects or arrays. The sole
+ * exception is the $args argument to Xml::encodeJsCall() because Xml::encodeJsVar() is
+ * called for each individual element in that array.
+ *
* @since 1.17
*/
class XmlJsCode {
diff --git a/includes/XmlTypeCheck.php b/includes/XmlTypeCheck.php
index 2e184606..92ca7d80 100644
--- a/includes/XmlTypeCheck.php
+++ b/includes/XmlTypeCheck.php
@@ -40,16 +40,52 @@ class XmlTypeCheck {
public $rootElement = '';
/**
- * @param string $file filename
- * @param $filterCallback callable (optional)
+ * @param string $input a filename or string containing the XML element
+ * @param callable $filterCallback (optional)
* Function to call to do additional custom validity checks from the
* SAX element handler event. This gives you access to the element
* namespace, name, and attributes, but not to text contents.
* Filter should return 'true' to toggle on $this->filterMatch
+ * @param boolean $isFile (optional) indicates if the first parameter is a
+ * filename (default, true) or if it is a string (false)
*/
- function __construct( $file, $filterCallback=null ) {
+ function __construct( $input, $filterCallback = null, $isFile = true ) {
$this->filterCallback = $filterCallback;
- $this->run( $file );
+ if ( $isFile ) {
+ $this->validateFromFile( $input );
+ } else {
+ $this->validateFromString( $input );
+ }
+ }
+
+ /**
+ * Alternative constructor: from filename
+ *
+ * @param string $fname the filename of an XML document
+ * @param callable $filterCallback (optional)
+ * Function to call to do additional custom validity checks from the
+ * SAX element handler event. This gives you access to the element
+ * namespace, name, and attributes, but not to text contents.
+ * Filter should return 'true' to toggle on $this->filterMatch
+ * @return XmlTypeCheck
+ */
+ public static function newFromFilename( $fname, $filterCallback = null ) {
+ return new self( $fname, $filterCallback, true );
+ }
+
+ /**
+ * Alternative constructor: from string
+ *
+ * @param string $string a string containing an XML element
+ * @param callable $filterCallback (optional)
+ * Function to call to do additional custom validity checks from the
+ * SAX element handler event. This gives you access to the element
+ * namespace, name, and attributes, but not to text contents.
+ * Filter should return 'true' to toggle on $this->filterMatch
+ * @return XmlTypeCheck
+ */
+ public static function newFromString( $string, $filterCallback = null ) {
+ return new self( $string, $filterCallback, false );
}
/**
@@ -62,15 +98,23 @@ class XmlTypeCheck {
}
/**
- * @param $fname
+ * Get an XML parser with the root element handler.
+ * @see XmlTypeCheck::rootElementOpen()
+ * @return resource a resource handle for the XML parser
*/
- private function run( $fname ) {
+ private function getParser() {
$parser = xml_parser_create_ns( 'UTF-8' );
-
// case folding violates XML standard, turn it off
xml_parser_set_option( $parser, XML_OPTION_CASE_FOLDING, false );
-
xml_set_element_handler( $parser, array( $this, 'rootElementOpen' ), false );
+ return $parser;
+ }
+
+ /**
+ * @param string $fname the filename
+ */
+ private function validateFromFile( $fname ) {
+ $parser = $this->getParser();
if ( file_exists( $fname ) ) {
$file = fopen( $fname, "rb" );
@@ -78,24 +122,38 @@ class XmlTypeCheck {
do {
$chunk = fread( $file, 32768 );
$ret = xml_parse( $parser, $chunk, feof( $file ) );
- if( $ret == 0 ) {
- // XML isn't well-formed!
+ if ( $ret == 0 ) {
+ $this->wellFormed = false;
fclose( $file );
xml_parser_free( $parser );
return;
}
- } while( !feof( $file ) );
+ } while ( !feof( $file ) );
fclose( $file );
}
}
-
$this->wellFormed = true;
xml_parser_free( $parser );
}
/**
+ *
+ * @param string $string the XML-input-string to be checked.
+ */
+ private function validateFromString( $string ) {
+ $parser = $this->getParser();
+ $ret = xml_parse( $parser, $string, true );
+ xml_parser_free( $parser );
+ if ( $ret == 0 ) {
+ $this->wellFormed = false;
+ return;
+ }
+ $this->wellFormed = true;
+ }
+
+ /**
* @param $parser
* @param $name
* @param $attribs
@@ -103,7 +161,7 @@ class XmlTypeCheck {
private function rootElementOpen( $parser, $name, $attribs ) {
$this->rootElement = $name;
- if( is_callable( $this->filterCallback ) ) {
+ if ( is_callable( $this->filterCallback ) ) {
xml_set_element_handler( $parser, array( $this, 'elementOpen' ), false );
$this->elementOpen( $parser, $name, $attribs );
} else {
@@ -118,7 +176,7 @@ class XmlTypeCheck {
* @param $attribs
*/
private function elementOpen( $parser, $name, $attribs ) {
- if( call_user_func( $this->filterCallback, $name, $attribs ) ) {
+ if ( call_user_func( $this->filterCallback, $name, $attribs ) ) {
// Filter hit!
$this->filterMatch = true;
}
diff --git a/includes/ZhClient.php b/includes/ZhClient.php
index fd03ec45..c5955aec 100644
--- a/includes/ZhClient.php
+++ b/includes/ZhClient.php
@@ -81,14 +81,14 @@ class ZhClient {
$result = fgets( $this->mFP, 1024 );
list( $status, $len ) = explode( ' ', $result );
- if( $status == 'ERROR' ) {
+ if ( $status == 'ERROR' ) {
// $len is actually the error code...
print "zhdaemon error $len<br />\n";
return false;
}
$bytesread = 0;
$data = '';
- while( !feof( $this->mFP ) && $bytesread < $len ) {
+ while ( !feof( $this->mFP ) && $bytesread < $len ) {
$str = fread( $this->mFP, $len - $bytesread );
$bytesread += strlen( $str );
$data .= $str;
@@ -131,7 +131,7 @@ class ZhClient {
$info = explode( ';', $infoline );
$ret = array();
$i = 0;
- foreach( $info as $variant ) {
+ foreach ( $info as $variant ) {
list( $code, $len ) = explode( ' ', $variant );
$ret[strtolower( $code )] = substr( $data, $i, $len );
$i += $len;
diff --git a/includes/ZipDirectoryReader.php b/includes/ZipDirectoryReader.php
index 646180d2..307efcea 100644
--- a/includes/ZipDirectoryReader.php
+++ b/includes/ZipDirectoryReader.php
@@ -162,7 +162,7 @@ class ZipDirectoryReader {
|| $this->eocdr['CD entries total'] == 0xffff )
{
$this->error( 'zip-unsupported', 'Central directory header indicates ZIP64, ' .
- 'but we are in legacy mode. Rejecting this upload is necessary to avoid '.
+ 'but we are in legacy mode. Rejecting this upload is necessary to avoid ' .
'opening vulnerabilities on clients using OpenJDK 7 or later.' );
}
diff --git a/includes/actions/CreditsAction.php b/includes/actions/CreditsAction.php
index 4d3c41be..0a2bf306 100644
--- a/includes/actions/CreditsAction.php
+++ b/includes/actions/CreditsAction.php
@@ -79,17 +79,17 @@ class CreditsAction extends FormlessAction {
/**
* Get the last author with the last modification time
- * @param $article Article object
+ * @param Page $page
* @return String HTML
*/
- protected function getAuthor( Page $article ) {
- $user = User::newFromName( $article->getUserText(), false );
+ protected function getAuthor( Page $page ) {
+ $user = User::newFromName( $page->getUserText(), false );
- $timestamp = $article->getTimestamp();
+ $timestamp = $page->getTimestamp();
if ( $timestamp ) {
$lang = $this->getLanguage();
- $d = $lang->date( $article->getTimestamp(), true );
- $t = $lang->time( $article->getTimestamp(), true );
+ $d = $lang->date( $page->getTimestamp(), true );
+ $t = $lang->time( $page->getTimestamp(), true );
} else {
$d = '';
$t = '';
@@ -114,9 +114,10 @@ class CreditsAction extends FormlessAction {
# Hmm... too many to fit!
if ( $cnt > 0 && $contributors->count() > $cnt ) {
$others_link = $this->othersLink();
- if ( !$showIfMax )
+ if ( !$showIfMax ) {
return $this->msg( 'othercontribs' )->rawParams(
$others_link )->params( $contributors->count() )->escaped();
+ }
}
$real_names = array();
diff --git a/includes/actions/EditAction.php b/includes/actions/EditAction.php
index dec3d841..3dd4c483 100644
--- a/includes/actions/EditAction.php
+++ b/includes/actions/EditAction.php
@@ -43,21 +43,11 @@ class EditAction extends FormlessAction {
public function show() {
$page = $this->page;
- $request = $this->getRequest();
$user = $this->getUser();
- $context = $this->getContext();
if ( wfRunHooks( 'CustomEditor', array( $page, $user ) ) ) {
- if ( ExternalEdit::useExternalEngine( $context, 'edit' )
- && $this->getName() == 'edit' && !$request->getVal( 'section' )
- && !$request->getVal( 'oldid' ) )
- {
- $extedit = new ExternalEdit( $context );
- $extedit->execute();
- } else {
- $editor = new EditPage( $page );
- $editor->edit();
- }
+ $editor = new EditPage( $page );
+ $editor->edit();
}
}
diff --git a/includes/actions/HistoryAction.php b/includes/actions/HistoryAction.php
index 245a5bdc..e58791ea 100644
--- a/includes/actions/HistoryAction.php
+++ b/includes/actions/HistoryAction.php
@@ -115,7 +115,7 @@ class HistoryAction extends FormlessAction {
// Setup page variables.
$out->setFeedAppendQuery( 'action=history' );
- $out->addModules( array( 'mediawiki.legacy.history', 'mediawiki.action.history' ) );
+ $out->addModules( 'mediawiki.action.history' );
// Handle atom/RSS feeds.
$feedType = $request->getVal( 'feed' );
@@ -178,7 +178,7 @@ class HistoryAction extends FormlessAction {
) .
Html::hidden( 'title', $this->getTitle()->getPrefixedDBkey() ) . "\n" .
Html::hidden( 'action', 'history' ) . "\n" .
- Xml::dateMenu( ( $year == null ? date( "Y" ) : $year ), $month ) . '&#160;' .
+ Xml::dateMenu( ( $year == null ? MWTimestamp::getLocalInstance()->format( 'Y' ) : $year ), $month ) . '&#160;' .
( $tagSelector ? ( implode( '&#160;', $tagSelector ) . '&#160;' ) : '' ) .
$checkDeleted .
Xml::submitButton( $this->msg( 'allpagessubmit' )->text() ) . "\n" .
@@ -211,7 +211,7 @@ class HistoryAction extends FormlessAction {
*/
function fetchRevisions( $limit, $offset, $direction ) {
// Fail if article doesn't exist.
- if( !$this->getTitle()->exists() ) {
+ if ( !$this->getTitle()->exists() ) {
return new FakeResultWrapper( array() );
}
@@ -256,15 +256,14 @@ class HistoryAction extends FormlessAction {
$this->getTitle()->getPrefixedText() . ' - ' .
$this->msg( 'history-feed-title' )->inContentLanguage()->text(),
$this->msg( 'history-feed-description' )->inContentLanguage()->text(),
- $this->getTitle()->getFullUrl( 'action=history' )
+ $this->getTitle()->getFullURL( 'action=history' )
);
// Get a limit on number of feed entries. Provide a sane default
// of 10 if none is defined (but limit to $wgFeedLimit max)
$limit = $request->getInt( 'limit', 10 );
- if ( $limit > $wgFeedLimit || $limit < 1 ) {
- $limit = 10;
- }
+ $limit = min( max( $limit, 1 ), $wgFeedLimit );
+
$items = $this->fetchRevisions( $limit, 0, HistoryPage::DIR_NEXT );
// Generate feed elements enclosed between header and footer.
@@ -283,10 +282,10 @@ class HistoryAction extends FormlessAction {
return new FeedItem(
$this->msg( 'nohistory' )->inContentLanguage()->text(),
$this->msg( 'history-feed-empty' )->inContentLanguage()->parseAsBlock(),
- $this->getTitle()->getFullUrl(),
+ $this->getTitle()->getFullURL(),
wfTimestamp( TS_MW ),
'',
- $this->getTitle()->getTalkPage()->getFullUrl()
+ $this->getTitle()->getTalkPage()->getFullURL()
);
}
@@ -323,10 +322,10 @@ class HistoryAction extends FormlessAction {
return new FeedItem(
$title,
$text,
- $this->getTitle()->getFullUrl( 'diff=' . $rev->getId() . '&oldid=prev' ),
+ $this->getTitle()->getFullURL( 'diff=' . $rev->getId() . '&oldid=prev' ),
$rev->getTimestamp(),
$rev->getUserText(),
- $this->getTitle()->getTalkPage()->getFullUrl()
+ $this->getTitle()->getTalkPage()->getFullURL()
);
}
}
@@ -367,15 +366,13 @@ class HistoryPager extends ReverseChronologicalPager {
function getQueryInfo() {
$queryInfo = array(
- 'tables' => array( 'revision', 'user' ),
- 'fields' => array_merge( Revision::selectFields(), Revision::selectUserFields() ),
- 'conds' => array_merge(
+ 'tables' => array( 'revision', 'user' ),
+ 'fields' => array_merge( Revision::selectFields(), Revision::selectUserFields() ),
+ 'conds' => array_merge(
array( 'rev_page' => $this->getWikiPage()->getId() ),
$this->conds ),
'options' => array( 'USE INDEX' => array( 'revision' => 'page_timestamp' ) ),
- 'join_conds' => array(
- 'user' => Revision::userJoinCond(),
- 'tag_summary' => array( 'LEFT JOIN', 'ts_rev_id=rev_id' ) ),
+ 'join_conds' => array( 'user' => Revision::userJoinCond() ),
);
ChangeTags::modifyDisplayQuery(
$queryInfo['tables'],
@@ -413,10 +410,10 @@ class HistoryPager extends ReverseChronologicalPager {
$batch = new LinkBatch();
$revIds = array();
foreach ( $this->mResult as $row ) {
- if( $row->rev_parent_id ) {
+ if ( $row->rev_parent_id ) {
$revIds[] = $row->rev_parent_id;
}
- if( !is_null( $row->user_name ) ) {
+ if ( !is_null( $row->user_name ) ) {
$batch->add( NS_USER, $row->user_name );
$batch->add( NS_USER_TALK, $row->user_name );
} else { # for anons or usernames of imported revisions
@@ -649,9 +646,9 @@ class HistoryPager extends ReverseChronologicalPager {
$this->msg( 'editundo' )->escaped(),
$undoTooltip,
array(
- 'action' => 'edit',
+ 'action' => 'edit',
'undoafter' => $prevRev->getId(),
- 'undo' => $rev->getId()
+ 'undo' => $rev->getId()
)
);
$tools[] = "<span class=\"mw-history-undo\">{$undolink}</span>";
@@ -661,7 +658,7 @@ class HistoryPager extends ReverseChronologicalPager {
wfRunHooks( 'HistoryRevisionTools', array( $rev, &$tools ) );
if ( $tools ) {
- $s2 .= ' '. $this->msg( 'parentheses' )->rawParams( $lang->pipeList( $tools ) )->escaped();
+ $s2 .= ' ' . $this->msg( 'parentheses' )->rawParams( $lang->pipeList( $tools ) )->escaped();
}
# Tags
@@ -788,13 +785,13 @@ class HistoryPager extends ReverseChronologicalPager {
function diffButtons( $rev, $firstInList ) {
if ( $this->getNumRows() > 1 ) {
$id = $rev->getId();
- $radio = array( 'type' => 'radio', 'value' => $id );
- /** @todo: move title texts to javascript */
+ $radio = array( 'type' => 'radio', 'value' => $id );
+ /** @todo Move title texts to javascript */
if ( $firstInList ) {
$first = Xml::element( 'input',
array_merge( $radio, array(
'style' => 'visibility:hidden',
- 'name' => 'oldid',
+ 'name' => 'oldid',
'id' => 'mw-oldid-null' ) )
);
$checkmark = array( 'checked' => 'checked' );
@@ -811,13 +808,13 @@ class HistoryPager extends ReverseChronologicalPager {
}
$first = Xml::element( 'input',
array_merge( $radio, $checkmark, array(
- 'name' => 'oldid',
+ 'name' => 'oldid',
'id' => "mw-oldid-$id" ) ) );
$checkmark = array();
}
$second = Xml::element( 'input',
array_merge( $radio, $checkmark, array(
- 'name' => 'diff',
+ 'name' => 'diff',
'id' => "mw-diff-$id" ) ) );
return $first . $second;
} else {
diff --git a/includes/actions/InfoAction.php b/includes/actions/InfoAction.php
index 1e312d7a..7fc90339 100644
--- a/includes/actions/InfoAction.php
+++ b/includes/actions/InfoAction.php
@@ -28,6 +28,8 @@
* @ingroup Actions
*/
class InfoAction extends FormlessAction {
+ const CACHE_VERSION = '2013-03-17';
+
/**
* Returns the name of the action this object responds to.
*
@@ -56,6 +58,22 @@ class InfoAction extends FormlessAction {
}
/**
+ * Clear the info cache for a given Title.
+ *
+ * @since 1.22
+ * @param Title $title Title to clear cache for
+ */
+ public static function invalidateCache( Title $title ) {
+ global $wgMemc;
+ // Clear page info.
+ $revision = WikiPage::factory( $title )->getRevision();
+ if ( $revision !== null ) {
+ $key = wfMemcKey( 'infoaction', sha1( $title->getPrefixedText() ), $revision->getId() );
+ $wgMemc->delete( $key );
+ }
+ }
+
+ /**
* Shows page information on GET request.
*
* @return string Page information that will be added to the output
@@ -100,12 +118,16 @@ class InfoAction extends FormlessAction {
// Render page information
foreach ( $pageInfo as $header => $infoTable ) {
+ // Messages:
+ // pageinfo-header-basic, pageinfo-header-edits, pageinfo-header-restrictions,
+ // pageinfo-header-properties, pageinfo-category-info
$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 ) . "\n";
+ $id = ( $infoRow[0] instanceof Message ) ? $infoRow[0]->getKey() : null;
+ $table = $this->addRow( $table, $name, $value, $id ) . "\n";
}
$content = $this->addTable( $content, $table ) . "\n";
}
@@ -126,7 +148,7 @@ class InfoAction extends FormlessAction {
/**
* Creates a header that can be added to the output.
*
- * @param $header The header text.
+ * @param string $header The header text.
* @return string The HTML.
*/
protected function makeHeader( $header ) {
@@ -140,10 +162,11 @@ class InfoAction extends FormlessAction {
* @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
+ * @param string $id The ID to use for the 'tr' element
* @return string The table with the row added
*/
- protected function addRow( $table, $name, $value ) {
- return $table . Html::rawElement( 'tr', array(),
+ protected function addRow( $table, $name, $value, $id ) {
+ return $table . Html::rawElement( 'tr', $id === null ? array() : array( 'id' => 'mw-' . $id ),
Html::rawElement( 'td', array( 'style' => 'vertical-align: top;' ), $name ) .
Html::rawElement( 'td', array(), $value )
);
@@ -169,18 +192,22 @@ class InfoAction extends FormlessAction {
* @return array
*/
protected function pageInfo() {
- global $wgContLang, $wgRCMaxAge, $wgMemc, $wgUnwatchedPageThreshold, $wgPageInfoTransclusionLimit;
+ global $wgContLang, $wgRCMaxAge, $wgMemc,
+ $wgUnwatchedPageThreshold, $wgPageInfoTransclusionLimit;
$user = $this->getUser();
$lang = $this->getLanguage();
$title = $this->getTitle();
$id = $title->getArticleID();
- $memcKey = wfMemcKey( 'infoaction', sha1( $title->getPrefixedText() ), $this->page->getLatest() );
+ $memcKey = wfMemcKey( 'infoaction',
+ sha1( $title->getPrefixedText() ), $this->page->getLatest() );
$pageCounts = $wgMemc->get( $memcKey );
- if ( $pageCounts === false ) {
+ $version = isset( $pageCounts['cacheversion'] ) ? $pageCounts['cacheversion'] : false;
+ if ( $pageCounts === false || $version !== self::CACHE_VERSION ) {
// Get page information that would be too "expensive" to retrieve by normal means
$pageCounts = self::pageCounts( $title );
+ $pageCounts['cacheversion'] = self::CACHE_VERSION;
$wgMemc->set( $memcKey, $pageCounts );
}
@@ -229,7 +256,7 @@ class InfoAction extends FormlessAction {
}
// Default sort key
- $sortKey = $title->getCategorySortKey();
+ $sortKey = $title->getCategorySortkey();
if ( !empty( $pageProperties['defaultsort'] ) ) {
$sortKey = $pageProperties['defaultsort'];
}
@@ -259,6 +286,7 @@ class InfoAction extends FormlessAction {
// Use robot policy logic
$policy = $this->page->getRobotPolicy( 'view', $pOutput );
$pageInfo['header-basic'][] = array(
+ // Messages: pageinfo-robot-index, pageinfo-robot-noindex
$this->msg( 'pageinfo-robot-policy' ), $this->msg( "pageinfo-robot-${policy['index']}" )
);
@@ -373,6 +401,7 @@ class InfoAction extends FormlessAction {
$message = $this->msg( 'protect-default' )->escaped();
} else {
// Administrators only
+ // Messages: protect-level-autoconfirmed, protect-level-sysop
$message = $this->msg( "protect-level-$protectionLevel" );
if ( $message->isDisabled() ) {
// Require "$1" permission
@@ -382,6 +411,8 @@ class InfoAction extends FormlessAction {
}
}
+ // Messages: restriction-edit, restriction-move, restriction-create,
+ // restriction-upload
$pageInfo['header-restrictions'][] = array(
$this->msg( "restriction-$restrictionType" ), $message
);
@@ -587,7 +618,7 @@ class InfoAction extends FormlessAction {
if ( !$wgDisableCounters ) {
// Number of views
- $views = (int) $dbr->selectField(
+ $views = (int)$dbr->selectField(
'page',
'page_counter',
array( 'page_id' => $id ),
@@ -597,19 +628,19 @@ class InfoAction extends FormlessAction {
}
// Number of page watchers
- $watchers = (int) $dbr->selectField(
+ $watchers = (int)$dbr->selectField(
'watchlist',
'COUNT(*)',
array(
'wl_namespace' => $title->getNamespace(),
- 'wl_title' => $title->getDBkey(),
+ 'wl_title' => $title->getDBkey(),
),
__METHOD__
);
$result['watchers'] = $watchers;
// Total number of edits
- $edits = (int) $dbr->selectField(
+ $edits = (int)$dbr->selectField(
'revision',
'COUNT(rev_page)',
array( 'rev_page' => $id ),
@@ -618,7 +649,7 @@ class InfoAction extends FormlessAction {
$result['edits'] = $edits;
// Total number of distinct authors
- $authors = (int) $dbr->selectField(
+ $authors = (int)$dbr->selectField(
'revision',
'COUNT(DISTINCT rev_user_text)',
array( 'rev_page' => $id ),
@@ -630,7 +661,7 @@ class InfoAction extends FormlessAction {
$threshold = $dbr->timestamp( time() - $wgRCMaxAge );
// Recent number of edits
- $edits = (int) $dbr->selectField(
+ $edits = (int)$dbr->selectField(
'revision',
'COUNT(rev_page)',
array(
@@ -642,7 +673,7 @@ class InfoAction extends FormlessAction {
$result['recent_edits'] = $edits;
// Recent number of distinct authors
- $authors = (int) $dbr->selectField(
+ $authors = (int)$dbr->selectField(
'revision',
'COUNT(DISTINCT rev_user_text)',
array(
@@ -660,7 +691,7 @@ class InfoAction extends FormlessAction {
// Subpages of this page (redirects)
$conds['page_is_redirect'] = 1;
- $result['subpages']['redirects'] = (int) $dbr->selectField(
+ $result['subpages']['redirects'] = (int)$dbr->selectField(
'page',
'COUNT(page_id)',
$conds,
@@ -668,7 +699,7 @@ class InfoAction extends FormlessAction {
// Subpages of this page (non-redirects)
$conds['page_is_redirect'] = 0;
- $result['subpages']['nonredirects'] = (int) $dbr->selectField(
+ $result['subpages']['nonredirects'] = (int)$dbr->selectField(
'page',
'COUNT(page_id)',
$conds,
@@ -681,7 +712,7 @@ class InfoAction extends FormlessAction {
}
// Counts for the number of transclusion links (to/from)
- $result['transclusion']['to'] = (int) $dbr->selectField(
+ $result['transclusion']['to'] = (int)$dbr->selectField(
'templatelinks',
'COUNT(tl_from)',
array(
@@ -691,7 +722,7 @@ class InfoAction extends FormlessAction {
__METHOD__
);
- $result['transclusion']['from'] = (int) $dbr->selectField(
+ $result['transclusion']['from'] = (int)$dbr->selectField(
'templatelinks',
'COUNT(*)',
array( 'tl_from' => $title->getArticleID() ),
diff --git a/includes/actions/PurgeAction.php b/includes/actions/PurgeAction.php
index 00bb961d..ed0bff7b 100644
--- a/includes/actions/PurgeAction.php
+++ b/includes/actions/PurgeAction.php
@@ -71,7 +71,7 @@ class PurgeAction extends FormAction {
$this->getRequest()->getQueryValues(),
array( 'title' => null, 'action' => null )
) );
- if( $this->onSubmit( array() ) ) {
+ if ( $this->onSubmit( array() ) ) {
$this->onSuccess();
}
} else {
@@ -96,6 +96,6 @@ class PurgeAction extends FormAction {
}
public function onSuccess() {
- $this->getOutput()->redirect( $this->getTitle()->getFullUrl( $this->redirectParams ) );
+ $this->getOutput()->redirect( $this->getTitle()->getFullURL( $this->redirectParams ) );
}
}
diff --git a/includes/actions/RawAction.php b/includes/actions/RawAction.php
index 12ce9f98..32751e45 100644
--- a/includes/actions/RawAction.php
+++ b/includes/actions/RawAction.php
@@ -48,7 +48,7 @@ class RawAction extends FormlessAction {
}
function onView() {
- global $wgSquidMaxage, $wgForcedRawSMaxage, $wgJsMimeType;
+ global $wgSquidMaxage, $wgForcedRawSMaxage;
$this->getOutput()->disable();
$request = $this->getRequest();
@@ -79,7 +79,7 @@ class RawAction extends FormlessAction {
# Force caching for CSS and JS raw content, default: 5 minutes
if ( $smaxage === null ) {
- if ( $contentType == 'text/css' || $contentType == $wgJsMimeType ) {
+ if ( $contentType == 'text/css' || $contentType == 'text/javascript' ) {
$smaxage = intval( $wgForcedRawSMaxage );
} else {
$smaxage = 0;
@@ -93,10 +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 = !User::groupHasPermission( '*', 'read' ) && ( $smaxage == 0 || session_id() != '' );
- // Bug 53032 - make this private if user is logged in,
- // so we don't accidentally cache cookies
- $privateCache = $privateCache ?: $this->getUser()->isLoggedIn();
+ $privateCache = !User::isEveryoneAllowed( '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 );
@@ -128,7 +125,7 @@ class RawAction extends FormlessAction {
global $wgParser;
# No longer used
- if( $this->mGen ) {
+ if ( $this->mGen ) {
return '';
}
@@ -197,18 +194,18 @@ class RawAction extends FormlessAction {
switch ( $this->getRequest()->getText( 'direction' ) ) {
case 'next':
# output next revision, or nothing if there isn't one
- if( $oldid ) {
- $oldid = $this->getTitle()->getNextRevisionId( $oldid );
+ if ( $oldid ) {
+ $oldid = $this->getTitle()->getNextRevisionID( $oldid );
}
$oldid = $oldid ? $oldid : -1;
break;
case 'prev':
# output previous revision, or nothing if there isn't one
- if( !$oldid ) {
+ if ( !$oldid ) {
# get the current revision so we can get the penultimate one
$oldid = $this->page->getLatest();
}
- $prev = $this->getTitle()->getPreviousRevisionId( $oldid );
+ $prev = $this->getTitle()->getPreviousRevisionID( $oldid );
$oldid = $prev ? $prev : -1;
break;
case 'cur':
@@ -224,20 +221,18 @@ class RawAction extends FormlessAction {
* @return String
*/
public function getContentType() {
- global $wgJsMimeType;
-
$ctype = $this->getRequest()->getVal( 'ctype' );
if ( $ctype == '' ) {
$gen = $this->getRequest()->getVal( 'gen' );
if ( $gen == 'js' ) {
- $ctype = $wgJsMimeType;
+ $ctype = 'text/javascript';
} elseif ( $gen == 'css' ) {
$ctype = 'text/css';
}
}
- $allowedCTypes = array( 'text/x-wiki', $wgJsMimeType, 'text/css', 'application/x-zope-edit' );
+ $allowedCTypes = array( 'text/x-wiki', 'text/javascript', 'text/css', 'application/x-zope-edit' );
if ( $ctype == '' || !in_array( $ctype, $allowedCTypes ) ) {
$ctype = 'text/x-wiki';
}
@@ -254,6 +249,10 @@ class RawAction extends FormlessAction {
class RawPage extends RawAction {
public $mOldId;
+ /**
+ * @param Page $page
+ * @param WebRequest|bool $request The WebRequest (default: false).
+ */
function __construct( Page $page, $request = false ) {
wfDeprecated( __CLASS__, '1.19' );
parent::__construct( $page );
diff --git a/includes/actions/WatchAction.php b/includes/actions/WatchAction.php
index ae5f76c6..929c1b5f 100644
--- a/includes/actions/WatchAction.php
+++ b/includes/actions/WatchAction.php
@@ -87,24 +87,72 @@ class WatchAction extends FormAction {
return parent::checkCanExecute( $user );
}
- public static function doWatch( Title $title, User $user ) {
+ /**
+ * Watch or unwatch a page
+ * @since 1.22
+ * @param bool $watch Whether to watch or unwatch the page
+ * @param Title $title Page to watch/unwatch
+ * @param User $user User who is watching/unwatching
+ * @return Status
+ */
+ public static function doWatchOrUnwatch( $watch, Title $title, User $user ) {
+ if ( $user->isLoggedIn() && $user->isWatched( $title, WatchedItem::IGNORE_USER_RIGHTS ) != $watch ) {
+ // If the user doesn't have 'editmywatchlist', we still want to
+ // allow them to add but not remove items via edits and such.
+ if ( $watch ) {
+ return self::doWatch( $title, $user, WatchedItem::IGNORE_USER_RIGHTS );
+ } else {
+ return self::doUnwatch( $title, $user );
+ }
+ }
+ return Status::newGood();
+ }
+
+ /**
+ * Watch a page
+ * @since 1.22 Returns Status, $checkRights parameter added
+ * @param Title $title Page to watch/unwatch
+ * @param User $user User who is watching/unwatching
+ * @param int $checkRights Passed through to $user->addWatch()
+ * @return Status
+ */
+ public static function doWatch( Title $title, User $user, $checkRights = WatchedItem::CHECK_USER_RIGHTS ) {
+ if ( $checkRights !== WatchedItem::IGNORE_USER_RIGHTS && !$user->isAllowed( 'editmywatchlist' ) ) {
+ return User::newFatalPermissionDeniedStatus( 'editmywatchlist' );
+ }
+
$page = WikiPage::factory( $title );
- if ( wfRunHooks( 'WatchArticle', array( &$user, &$page ) ) ) {
- $user->addWatch( $title );
+ $status = Status::newFatal( 'hookaborted' );
+ if ( wfRunHooks( 'WatchArticle', array( &$user, &$page, &$status ) ) ) {
+ $status = Status::newGood();
+ $user->addWatch( $title, $checkRights );
wfRunHooks( 'WatchArticleComplete', array( &$user, &$page ) );
}
- return true;
+ return $status;
}
- public static function doUnwatch( Title $title, User $user ) {
+ /**
+ * Unwatch a page
+ * @since 1.22 Returns Status
+ * @param Title $title Page to watch/unwatch
+ * @param User $user User who is watching/unwatching
+ * @return Status
+ */
+ public static function doUnwatch( Title $title, User $user ) {
+ if ( !$user->isAllowed( 'editmywatchlist' ) ) {
+ return User::newFatalPermissionDeniedStatus( 'editmywatchlist' );
+ }
+
$page = WikiPage::factory( $title );
- if ( wfRunHooks( 'UnwatchArticle', array( &$user, &$page ) ) ) {
+ $status = Status::newFatal( 'hookaborted' );
+ if ( wfRunHooks( 'UnwatchArticle', array( &$user, &$page, &$status ) ) ) {
+ $status = Status::newGood();
$user->removeWatch( $title );
wfRunHooks( 'UnwatchArticleComplete', array( &$user, &$page ) );
}
- return true;
+ return $status;
}
/**
diff --git a/includes/api/ApiBase.php b/includes/api/ApiBase.php
index 9351a8d8..ce6ecda6 100644
--- a/includes/api/ApiBase.php
+++ b/includes/api/ApiBase.php
@@ -304,15 +304,14 @@ abstract class ApiBase extends ContextSource {
}
$examples = $this->getExamples();
- if ( $examples !== false && $examples !== '' ) {
+ if ( $examples ) {
if ( !is_array( $examples ) ) {
$examples = array(
$examples
);
}
$msg .= "Example" . ( count( $examples ) > 1 ? 's' : '' ) . ":\n";
- foreach( $examples as $k => $v ) {
-
+ foreach ( $examples as $k => $v ) {
if ( is_numeric( $k ) ) {
$msg .= " $v\n";
} else {
@@ -445,7 +444,7 @@ abstract class ApiBase extends ContextSource {
$hintPipeSeparated = false;
break;
case 'limit':
- $desc .= $paramPrefix . "No more than {$paramSettings[self :: PARAM_MAX]}";
+ $desc .= $paramPrefix . "No more than {$paramSettings[self::PARAM_MAX]}";
if ( isset( $paramSettings[self::PARAM_MAX2] ) ) {
$desc .= " ({$paramSettings[self::PARAM_MAX2]} for bots)";
}
@@ -689,9 +688,9 @@ 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', 'invalidparammix' );
} elseif ( count( $intersection ) == 0 ) {
- $this->dieUsage( "One of the parameters {$p}" . implode( ", {$p}", $required ) . ' is required', "{$p}missingparam" );
+ $this->dieUsage( "One of the parameters {$p}" . implode( ", {$p}", $required ) . ' is required', 'missingparam' );
}
}
@@ -725,7 +724,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', 'invalidparammix' );
}
}
@@ -822,9 +821,9 @@ abstract class ApiBase extends ContextSource {
* If not set will magically default to either watchdefault or watchcreations
* @return bool
*/
- protected function getWatchlistValue ( $watchlist, $titleObj, $userOption = null ) {
+ protected function getWatchlistValue( $watchlist, $titleObj, $userOption = null ) {
- $userWatching = $this->getUser()->isWatched( $titleObj );
+ $userWatching = $this->getUser()->isWatched( $titleObj, WatchedItem::IGNORE_USER_RIGHTS );
switch ( $watchlist ) {
case 'watch':
@@ -866,12 +865,7 @@ abstract class ApiBase extends ContextSource {
return;
}
- $user = $this->getUser();
- if ( $value ) {
- WatchAction::doWatch( $titleObj, $user );
- } else {
- WatchAction::doUnwatch( $titleObj, $user );
- }
+ WatchAction::doWatchOrUnwatch( $value, $titleObj, $this->getUser() );
}
/**
@@ -967,9 +961,9 @@ abstract class ApiBase extends ContextSource {
}
break;
case 'integer': // Force everything using intval() and optionally validate limits
- $min = isset ( $paramSettings[self::PARAM_MIN] ) ? $paramSettings[self::PARAM_MIN] : null;
- $max = isset ( $paramSettings[self::PARAM_MAX] ) ? $paramSettings[self::PARAM_MAX] : null;
- $enforceLimits = isset ( $paramSettings[self::PARAM_RANGE_ENFORCE] )
+ $min = isset( $paramSettings[self::PARAM_MIN] ) ? $paramSettings[self::PARAM_MIN] : null;
+ $max = isset( $paramSettings[self::PARAM_MAX] ) ? $paramSettings[self::PARAM_MAX] : null;
+ $enforceLimits = isset( $paramSettings[self::PARAM_RANGE_ENFORCE] )
? $paramSettings[self::PARAM_RANGE_ENFORCE] : false;
if ( is_array( $value ) ) {
@@ -1081,7 +1075,7 @@ abstract class ApiBase extends ContextSource {
if ( !$allowMultiple && count( $valuesList ) != 1 ) {
// Bug 33482 - Allow entries with | in them for non-multiple values
- if ( in_array( $value, $allowedValues ) ) {
+ if ( in_array( $value, $allowedValues, true ) ) {
return $value;
}
@@ -1165,7 +1159,7 @@ abstract class ApiBase extends ContextSource {
/**
* Validate and normalize of parameters of type 'user'
* @param string $value Parameter value
- * @param string $encParamName Parameter value
+ * @param string $encParamName Parameter name
* @return string Validated and normalized parameter
*/
private function validateUser( $value, $encParamName ) {
@@ -1223,6 +1217,44 @@ abstract class ApiBase extends ContextSource {
}
/**
+ * Throw a UsageException based on the errors in the Status object.
+ *
+ * @since 1.22
+ * @param Status $status Status object
+ * @throws UsageException
+ */
+ public function dieStatus( $status ) {
+ if ( $status->isGood() ) {
+ throw new MWException( 'Successful status passed to ApiBase::dieStatus' );
+ }
+
+ $errors = $status->getErrorsArray();
+ if ( !$errors ) {
+ // No errors? Assume the warnings should be treated as errors
+ $errors = $status->getWarningsArray();
+ }
+ if ( !$errors ) {
+ // Still no errors? Punt
+ $errors = array( array( 'unknownerror-nocode' ) );
+ }
+
+ // Cannot use dieUsageMsg() because extensions might return custom
+ // error messages.
+ if ( $errors[0] instanceof Message ) {
+ $msg = $errors[0];
+ $code = $msg->getKey();
+ } else {
+ $code = array_shift( $errors[0] );
+ $msg = wfMessage( $code, $errors[0] );
+ }
+ if ( isset( ApiBase::$messageMap[$code] ) ) {
+ // Translate message to code, for backwards compatability
+ $code = ApiBase::$messageMap[$code]['code'];
+ }
+ $this->dieUsage( $msg->inLanguage( 'en' )->useDatabase( false )->plain(), $code );
+ }
+
+ /**
* Array that maps message keys to error messages. $1 and friends are replaced.
*/
public static $messageMap = array(
@@ -1372,6 +1404,7 @@ abstract class ApiBase extends ContextSource {
'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.' ),
+ 'copyuploadbadurl' => array( 'code' => 'copyuploadbadurl', 'info' => 'Upload not allowed from this URL.' ),
'filename-tooshort' => array( 'code' => 'filename-tooshort', 'info' => 'The filename is too short' ),
'filename-toolong' => array( 'code' => 'filename-toolong', 'info' => 'The filename is too long' ),
@@ -1397,7 +1430,7 @@ abstract class ApiBase extends ContextSource {
public function dieUsageMsg( $error ) {
# most of the time we send a 1 element, so we might as well send it as
# a string and make this an array here.
- if( is_string( $error ) ) {
+ if ( is_string( $error ) ) {
$error = array( $error );
}
$parsed = $this->parseMsg( $error );
@@ -1412,10 +1445,10 @@ abstract class ApiBase extends ContextSource {
*/
public function dieUsageMsgOrDebug( $error ) {
global $wgDebugAPI;
- if( $wgDebugAPI !== true ) {
+ if ( $wgDebugAPI !== true ) {
$this->dieUsageMsg( $error );
} else {
- if( is_string( $error ) ) {
+ if ( is_string( $error ) ) {
$error = array( $error );
}
$parsed = $this->parseMsg( $error );
@@ -1448,7 +1481,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 );
}
@@ -1470,7 +1503,7 @@ abstract class ApiBase extends ContextSource {
* @param string $message Error message
*/
protected static function dieDebug( $method, $message ) {
- wfDebugDieBacktrace( "Internal error in $method: $message" );
+ throw new MWException( "Internal error in $method: $message" );
}
/**
@@ -1535,7 +1568,7 @@ abstract class ApiBase extends ContextSource {
public function getWatchlistUser( $params ) {
if ( !is_null( $params['owner'] ) && !is_null( $params['token'] ) ) {
$user = User::newFromName( $params['owner'], false );
- if ( !($user && $user->getId()) ) {
+ if ( !( $user && $user->getId() ) ) {
$this->dieUsage( 'Specified user does not exist', 'bad_wlowner' );
}
$token = $user->getOption( 'watchlisttoken' );
@@ -1546,6 +1579,9 @@ abstract class ApiBase extends ContextSource {
if ( !$this->getUser()->isLoggedIn() ) {
$this->dieUsage( 'You must be logged-in to have a watchlist', 'notloggedin' );
}
+ if ( !$this->getUser()->isAllowed( 'viewmywatchlist' ) ) {
+ $this->dieUsage( 'You don\'t have permission to view your watchlist', 'permissiondenied' );
+ }
$user = $this->getUser();
}
return $user;
@@ -1560,6 +1596,10 @@ abstract class ApiBase extends ContextSource {
/**
* Returns a list of all possible errors returned by the module
+ *
+ * Don't call this function directly: use getFinalPossibleErrors() to allow
+ * hooks to modify parameters as needed.
+ *
* @return array in the format of array( key, param1, param2, ... ) or array( 'code' => ..., 'info' => ... )
*/
public function getPossibleErrors() {
@@ -1574,10 +1614,9 @@ abstract class ApiBase extends ContextSource {
}
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'
- ) );
+ 'code' => 'badcontinue',
+ 'info' => 'Invalid continue param. You should pass the original value returned by the previous query'
+ );
}
}
@@ -1595,7 +1634,12 @@ abstract class ApiBase extends ContextSource {
}
if ( $this->needsToken() ) {
- $ret[] = array( 'missingparam', 'token' );
+ if ( !isset( $params['token'][ApiBase::PARAM_REQUIRED] )
+ || !$params['token'][ApiBase::PARAM_REQUIRED]
+ ) {
+ // Add token as possible missing parameter, if not already done
+ $ret[] = array( 'missingparam', 'token' );
+ }
$ret[] = array( 'sessionfailure' );
}
@@ -1603,6 +1647,19 @@ abstract class ApiBase extends ContextSource {
}
/**
+ * Get final list of possible errors, after hooks have had a chance to
+ * tweak it as needed.
+ *
+ * @return array
+ * @since 1.22
+ */
+ public function getFinalPossibleErrors() {
+ $possibleErrors = $this->getPossibleErrors();
+ wfRunHooks( 'APIGetPossibleErrors', array( $this, &$possibleErrors ) );
+ return $possibleErrors;
+ }
+
+ /**
* Parses a list of errors into a standardised format
* @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' => ... )
diff --git a/includes/api/ApiBlock.php b/includes/api/ApiBlock.php
index 6f3d1e4f..975153ac 100644
--- a/includes/api/ApiBlock.php
+++ b/includes/api/ApiBlock.php
@@ -98,7 +98,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 );
diff --git a/includes/api/ApiComparePages.php b/includes/api/ApiComparePages.php
index 79ffcb0a..1e35c349 100644
--- a/includes/api/ApiComparePages.php
+++ b/includes/api/ApiComparePages.php
@@ -81,17 +81,17 @@ class ApiComparePages extends ApiBase {
* @return int
*/
private function revisionOrTitleOrId( $revision, $titleText, $titleId ) {
- if( $revision ) {
+ if ( $revision ) {
return $revision;
- } elseif( $titleText ) {
+ } elseif ( $titleText ) {
$title = Title::newFromText( $titleText );
- if( !$title || $title->isExternal() ) {
+ if ( !$title || $title->isExternal() ) {
$this->dieUsageMsg( array( 'invalidtitle', $titleText ) );
}
return $title->getLatestRevID();
} elseif ( $titleId ) {
$title = Title::newFromID( $titleId );
- if( !$title ) {
+ if ( !$title ) {
$this->dieUsageMsg( array( 'nosuchpageid', $titleId ) );
}
return $title->getLatestRevID();
diff --git a/includes/api/ApiCreateAccount.php b/includes/api/ApiCreateAccount.php
index 69748c93..0e752c56 100644
--- a/includes/api/ApiCreateAccount.php
+++ b/includes/api/ApiCreateAccount.php
@@ -47,17 +47,19 @@ class ApiCreateAccount extends ApiBase {
$params = $this->extractRequestParams();
- $result = array();
-
// Init session if necessary
if ( session_id() == '' ) {
wfSetupSession();
}
- if( $params['mailpassword'] && !$params['email'] ) {
+ if ( $params['mailpassword'] && !$params['email'] ) {
$this->dieUsageMsg( 'noemail' );
}
+ if ( $params['language'] && !Language::isSupportedLanguage( $params['language'] ) ) {
+ $this->dieUsage( 'Invalid language parameter', 'langinvalid' );
+ }
+
$context = new DerivativeContext( $this->getContext() );
$context->setRequest( new DerivativeRequest(
$this->getContext()->getRequest(),
@@ -82,22 +84,20 @@ class ApiCreateAccount extends ApiBase {
$status = $loginForm->addNewaccountInternal();
$result = array();
- if( $status->isGood() ) {
+ if ( $status->isGood() ) {
// Success!
+ global $wgEmailAuthentication;
$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'] ) {
+ if ( $params['language'] ) {
$user->setOption( 'language', $params['language'] );
}
- if( $params['mailpassword'] ) {
+ 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() ) ) {
+ } elseif ( $wgEmailAuthentication && Sanitizer::validateEmail( $user->getEmail() ) ) {
// Send out an email authentication message if needed
$status->merge( $user->sendConfirmationMail() );
}
@@ -124,33 +124,23 @@ class ApiCreateAccount extends ApiBase {
$apiResult = $this->getResult();
- if( $status->hasMessage( 'sessionfailure' ) || $status->hasMessage( 'nocookiesfornew' ) ) {
+ 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() ) {
+ } 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() ) {
+ $this->dieStatus( $status );
+ } 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 ) {
+ if ( $warnings ) {
+ foreach ( $warnings as &$warning ) {
$apiResult->setIndexedTagName( $warning['params'], 'param' );
}
$apiResult->setIndexedTagName( $warnings, 'warning' );
@@ -263,8 +253,8 @@ class ApiCreateAccount extends ApiBase {
$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() );
+ foreach ( $localErrors as $error ) {
+ $errors[] = array( 'code' => $error, 'info' => wfMessage( $error )->inLanguage( 'en' )->useDatabase( false )->parse() );
}
$errors[] = array(
@@ -279,12 +269,16 @@ class ApiCreateAccount extends ApiBase {
'code' => 'aborted',
'info' => 'Account creation aborted by hook (info may vary)'
);
+ $errors[] = array(
+ 'code' => 'langinvalid',
+ 'info' => 'Invalid language parameter'
+ );
// 'passwordtooshort' has parameters. :(
global $wgMinimalPasswordLength;
$errors[] = array(
'code' => 'passwordtooshort',
- 'info' => wfMessage( 'passwordtooshort', $wgMinimalPasswordLength )->parse()
+ 'info' => wfMessage( 'passwordtooshort', $wgMinimalPasswordLength )->inLanguage( 'en' )->useDatabase( false )->parse()
);
return $errors;
}
diff --git a/includes/api/ApiDelete.php b/includes/api/ApiDelete.php
index d1f0806e..aea10482 100644
--- a/includes/api/ApiDelete.php
+++ b/includes/api/ApiDelete.php
@@ -61,8 +61,7 @@ class ApiDelete extends ApiBase {
$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
+ $this->dieStatus( $status );
}
// Deprecated parameters
diff --git a/includes/api/ApiEditPage.php b/includes/api/ApiEditPage.php
index 4916145b..bd61895b 100644
--- a/includes/api/ApiEditPage.php
+++ b/includes/api/ApiEditPage.php
@@ -100,7 +100,7 @@ class ApiEditPage extends ApiBase {
$name = $titleObj->getPrefixedDBkey();
$model = $contentHandler->getModelID();
- $this->dieUsage( "The requested format $contentFormat is not supported for content model ".
+ $this->dieUsage( "The requested format $contentFormat is not supported for content model " .
" $model used by $name", 'badformat' );
}
@@ -146,7 +146,7 @@ class ApiEditPage extends ApiBase {
}
}
- // @todo: Add support for appending/prepending to the Content interface
+ // @todo Add support for appending/prepending to the Content interface
if ( !( $content instanceof TextContent ) ) {
$mode = $contentHandler->getModelID();
@@ -159,12 +159,17 @@ class ApiEditPage extends ApiBase {
$this->dieUsage( "Sections are not supported for this content model: $modelName.", 'sectionsnotsupported' );
}
- // Process the content for section edits
- $section = intval( $params['section'] );
- $content = $content->getSection( $section );
+ if ( $params['section'] == 'new' ) {
+ // DWIM if they're trying to prepend/append to a new section.
+ $content = null;
+ } else {
+ // Process the content for section edits
+ $section = intval( $params['section'] );
+ $content = $content->getSection( $section );
- if ( !$content ) {
- $this->dieUsage( "There is no section {$section}.", 'nosuchsection' );
+ if ( !$content ) {
+ $this->dieUsage( "There is no section {$section}.", 'nosuchsection' );
+ }
}
}
@@ -262,7 +267,7 @@ class ApiEditPage extends ApiBase {
$requestArray['wpStarttime'] = wfTimestampNow(); // Fake wpStartime
}
- if ( $params['minor'] || ( !$params['notminor'] && $user->getOption( 'minordefault' ) ) ) {
+ if ( $params['minor'] || ( !$params['notminor'] && $user->getOption( 'minordefault' ) ) ) {
$requestArray['wpMinoredit'] = '';
}
@@ -275,6 +280,10 @@ class ApiEditPage extends ApiBase {
if ( $section == 0 && $params['section'] != '0' && $params['section'] != 'new' ) {
$this->dieUsage( "The section parameter must be set to an integer or 'new'", "invalidsection" );
}
+ $content = $pageObj->getContent();
+ if ( $section !== 0 && ( !$content || !$content->getSection( $section ) ) ) {
+ $this->dieUsage( "There is no section {$section}.", 'nosuchsection' );
+ }
$requestArray['wpSection'] = $params['section'];
} else {
$requestArray['wpSection'] = '';
@@ -293,6 +302,10 @@ class ApiEditPage extends ApiBase {
$requestArray['wpWatchthis'] = '';
}
+ // Pass through anything else we might have been given, to support extensions
+ // This is kind of a hack but it's the best we can do to make extensions work
+ $requestArray += $this->getRequest()->getValues();
+
global $wgTitle, $wgRequest;
$req = new DerivativeRequest( $this->getRequest(), $requestArray, true );
@@ -316,11 +329,37 @@ class ApiEditPage extends ApiBase {
$ep->setContextTitle( $titleObj );
$ep->importFormData( $req );
+ $content = $ep->textbox1;
+
+ // The following is needed to give the hook the full content of the
+ // new revision rather than just the current section. (Bug 52077)
+ if ( !is_null( $params['section'] ) && $contentHandler->supportsSections() && $titleObj->exists() ) {
+
+ $sectionTitle = '';
+ // If sectiontitle is set, use it, otherwise use the summary as the section title (for
+ // backwards compatibility with old forms/bots).
+ if ( $ep->sectiontitle !== '' ) {
+ $sectionTitle = $ep->sectiontitle;
+ } else {
+ $sectionTitle = $ep->summary;
+ }
+
+ $contentObj = $contentHandler->unserializeContent( $content, $contentFormat );
+
+ $fullContentObj = $articleObject->replaceSectionContent( $params['section'], $contentObj, $sectionTitle );
+ if ( $fullContentObj ) {
+ $content = $fullContentObj->serialize( $contentFormat );
+ } else {
+ // This most likely means we have an edit conflict which means that the edit
+ // wont succeed anyway.
+ $this->dieUsageMsg( 'editconflict' );
+ }
+ }
// Run hooks
// Handle APIEditBeforeSave parameters
$r = array();
- if ( !wfRunHooks( 'APIEditBeforeSave', array( $ep, $ep->textbox1, &$r ) ) ) {
+ if ( !wfRunHooks( 'APIEditBeforeSave', array( $ep, $content, &$r ) ) ) {
if ( count( $r ) ) {
$r['result'] = 'Failure';
$apiResult->addValue( null, $this->getModuleName(), $r );
@@ -342,7 +381,7 @@ class ApiEditPage extends ApiBase {
$wgRequest = $oldRequest;
global $wgMaxArticleSize;
- switch( $status->value ) {
+ switch ( $status->value ) {
case EditPage::AS_HOOK_ERROR:
case EditPage::AS_HOOK_ERROR_EXPECTED:
$this->dieUsageMsg( 'hookaborted' );
diff --git a/includes/api/ApiExpandTemplates.php b/includes/api/ApiExpandTemplates.php
index f5898fb3..d5c789c3 100644
--- a/includes/api/ApiExpandTemplates.php
+++ b/includes/api/ApiExpandTemplates.php
@@ -65,14 +65,14 @@ class ApiExpandTemplates extends ApiBase {
$xml = $dom->__toString();
}
$xml_result = array();
- $result->setContent( $xml_result, $xml );
+ ApiResult::setContent( $xml_result, $xml );
$result->addValue( null, 'parsetree', $xml_result );
}
$retval = $wgParser->preprocess( $params['text'], $title_obj, $options );
// Return result
$retval_array = array();
- $result->setContent( $retval_array, $retval );
+ ApiResult::setContent( $retval_array, $retval );
$result->addValue( null, $this->getModuleName(), $retval_array );
}
diff --git a/includes/api/ApiFeedContributions.php b/includes/api/ApiFeedContributions.php
index 015a9922..05691093 100644
--- a/includes/api/ApiFeedContributions.php
+++ b/includes/api/ApiFeedContributions.php
@@ -43,11 +43,11 @@ class ApiFeedContributions extends ApiBase {
global $wgFeed, $wgFeedClasses, $wgSitename, $wgLanguageCode;
- if( !$wgFeed ) {
+ if ( !$wgFeed ) {
$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' );
}
@@ -82,7 +82,7 @@ class ApiFeedContributions extends ApiBase {
) );
$feedItems = array();
- if( $pager->getNumRows() > 0 ) {
+ if ( $pager->getNumRows() > 0 ) {
foreach ( $pager->mResult as $row ) {
$feedItems[] = $this->feedItem( $row );
}
@@ -93,7 +93,7 @@ class ApiFeedContributions extends ApiBase {
protected function feedItem( $row ) {
$title = Title::makeTitle( intval( $row->page_namespace ), $row->page_title );
- if( $title ) {
+ if ( $title && $title->userCan( 'read', $this->getUser() ) ) {
$date = $row->rev_timestamp;
$comments = $title->getTalkPage()->getFullURL();
$revision = Revision::newFromRow( $row );
@@ -106,9 +106,8 @@ class ApiFeedContributions extends ApiBase {
$this->feedItemAuthor( $revision ),
$comments
);
- } else {
- return null;
}
+ return null;
}
/**
@@ -124,7 +123,7 @@ class ApiFeedContributions extends ApiBase {
* @return string
*/
protected function feedItemDesc( $revision ) {
- if( $revision ) {
+ if ( $revision ) {
$msg = wfMessage( 'colon-separator' )->inContentLanguage()->text();
$content = $revision->getContent();
@@ -149,7 +148,7 @@ class ApiFeedContributions extends ApiBase {
public function getAllowedParams() {
global $wgFeedClasses;
$feedFormatNames = array_keys( $wgFeedClasses );
- return array (
+ return array(
'feedformat' => array(
ApiBase::PARAM_DFLT => 'rss',
ApiBase::PARAM_TYPE => $feedFormatNames
diff --git a/includes/api/ApiFeedWatchlist.php b/includes/api/ApiFeedWatchlist.php
index 6c793b36..fbb70fbc 100644
--- a/includes/api/ApiFeedWatchlist.php
+++ b/includes/api/ApiFeedWatchlist.php
@@ -33,6 +33,10 @@
*/
class ApiFeedWatchlist extends ApiBase {
+ private $watchlistModule = null;
+ private $linkToDiffs = false;
+ private $linkToSections = false;
+
/**
* This module uses a custom feed wrapper printer.
*
@@ -42,8 +46,6 @@ class ApiFeedWatchlist extends ApiBase {
return new ApiFormatFeedWrapper( $this->getMain() );
}
- private $linkToDiffs = false;
-
/**
* Make a nested call to the API to request watchlist items in the last $hours.
* Wrap the result as an RSS/Atom feed.
@@ -54,11 +56,11 @@ class ApiFeedWatchlist extends ApiBase {
try {
$params = $this->extractRequestParams();
- if( !$wgFeed ) {
+ if ( !$wgFeed ) {
$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' );
}
@@ -74,7 +76,7 @@ class ApiFeedWatchlist extends ApiBase {
'wlprop' => 'title|user|comment|timestamp',
'wldir' => 'older', // reverse order - from newest to oldest
'wlend' => $endTime, // stop at this time
- 'wllimit' => ( 50 > $wgFeedLimit ) ? $wgFeedLimit : 50
+ 'wllimit' => min( 50, $wgFeedLimit )
);
if ( $params['wlowner'] !== null ) {
@@ -86,6 +88,12 @@ class ApiFeedWatchlist extends ApiBase {
if ( $params['wlexcludeuser'] !== null ) {
$fauxReqArr['wlexcludeuser'] = $params['wlexcludeuser'];
}
+ if ( $params['wlshow'] !== null ) {
+ $fauxReqArr['wlshow'] = $params['wlshow'];
+ }
+ if ( $params['wltype'] !== null ) {
+ $fauxReqArr['wltype'] = $params['wltype'];
+ }
// Support linking to diffs instead of article
if ( $params['linktodiffs'] ) {
@@ -93,6 +101,12 @@ class ApiFeedWatchlist extends ApiBase {
$fauxReqArr['wlprop'] .= '|ids';
}
+ // Support linking directly to sections when possible
+ // (possible only if section name is present in comment)
+ if ( $params['linktosections'] ) {
+ $this->linkToSections = true;
+ }
+
// Check for 'allrev' parameter, and if found, show all revisions to each page on wl.
if ( $params['allrev'] ) {
$fauxReqArr['wlallrev'] = '';
@@ -160,6 +174,18 @@ class ApiFeedWatchlist extends ApiBase {
$titleUrl = $title->getFullURL();
}
$comment = isset( $info['comment'] ) ? $info['comment'] : null;
+
+ // Create an anchor to section.
+ // The anchor won't work for sections that have dupes on page
+ // as there's no way to strip that info from ApiWatchlist (apparently?).
+ // RegExp in the line below is equal to Linker::formatAutocomments().
+ if ( $this->linkToSections && $comment !== null && preg_match( '!(.*)/\*\s*(.*?)\s*\*/(.*)!', $comment, $matches ) ) {
+ global $wgParser;
+ $sectionTitle = $wgParser->stripSectionName( $matches[2] );
+ $sectionTitle = Sanitizer::normalizeSectionNameWhitespace( $sectionTitle );
+ $titleUrl .= Title::newFromText( '#' . $sectionTitle )->getFragmentForURL();
+ }
+
$timestamp = $info['timestamp'];
$user = $info['user'];
@@ -168,10 +194,18 @@ class ApiFeedWatchlist extends ApiBase {
return new FeedItem( $titleStr, $completeText, $titleUrl, $timestamp, $user );
}
- public function getAllowedParams() {
+ private function getWatchlistModule() {
+ if ( $this->watchlistModule === null ) {
+ $this->watchlistModule = $this->getMain()->getModuleManager()->getModule( 'query' )
+ ->getModuleManager()->getModule( 'watchlist' );
+ }
+ return $this->watchlistModule;
+ }
+
+ public function getAllowedParams( $flags = 0 ) {
global $wgFeedClasses;
$feedFormatNames = array_keys( $wgFeedClasses );
- return array (
+ $ret = array(
'feedformat' => array(
ApiBase::PARAM_DFLT => 'rss',
ApiBase::PARAM_TYPE => $feedFormatNames
@@ -182,29 +216,41 @@ class ApiFeedWatchlist extends ApiBase {
ApiBase::PARAM_MIN => 1,
ApiBase::PARAM_MAX => 72,
),
- 'allrev' => false,
- 'wlowner' => array(
- ApiBase::PARAM_TYPE => 'user'
- ),
- 'wltoken' => array(
- ApiBase::PARAM_TYPE => 'string'
- ),
- 'wlexcludeuser' => array(
- ApiBase::PARAM_TYPE => 'user'
- ),
'linktodiffs' => false,
+ 'linktosections' => false,
);
+ if ( $flags ) {
+ $wlparams = $this->getWatchlistModule()->getAllowedParams( $flags );
+ $ret['allrev'] = $wlparams['allrev'];
+ $ret['wlowner'] = $wlparams['owner'];
+ $ret['wltoken'] = $wlparams['token'];
+ $ret['wlshow'] = $wlparams['show'];
+ $ret['wltype'] = $wlparams['type'];
+ $ret['wlexcludeuser'] = $wlparams['excludeuser'];
+ } else {
+ $ret['allrev'] = null;
+ $ret['wlowner'] = null;
+ $ret['wltoken'] = null;
+ $ret['wlshow'] = null;
+ $ret['wltype'] = null;
+ $ret['wlexcludeuser'] = null;
+ }
+ return $ret;
}
public function getParamDescription() {
+ $wldescr = $this->getWatchlistModule()->getParamDescription();
return array(
'feedformat' => 'The format of the feed',
- 'hours' => 'List pages modified within this many hours from now',
- 'allrev' => 'Include multiple revisions of the same page within given timeframe',
- 'wlowner' => "The user whose watchlist you want (must be accompanied by {$this->getModulePrefix()}wltoken if it's not you)",
- 'wltoken' => 'Security token that requested user set in their preferences',
- 'wlexcludeuser' => 'A user whose edits should not be shown in the watchlist',
+ 'hours' => 'List pages modified within this many hours from now',
'linktodiffs' => 'Link to change differences instead of article pages',
+ 'linktosections' => 'Link directly to changed sections if possible',
+ 'allrev' => $wldescr['allrev'],
+ 'wlowner' => $wldescr['owner'],
+ 'wltoken' => $wldescr['token'],
+ 'wlshow' => $wldescr['show'],
+ 'wltype' => $wldescr['type'],
+ 'wlexcludeuser' => $wldescr['excludeuser'],
);
}
diff --git a/includes/api/ApiFormatBase.php b/includes/api/ApiFormatBase.php
index d8aa1634..b89fb3a7 100644
--- a/includes/api/ApiFormatBase.php
+++ b/includes/api/ApiFormatBase.php
@@ -85,7 +85,7 @@ abstract class ApiFormatBase extends ApiBase {
*
* @param bool $b Whether or not ampersands should be escaped.
*/
- public function setUnescapeAmps ( $b ) {
+ public function setUnescapeAmps( $b ) {
$this->mUnescapeAmps = $b;
}
@@ -170,12 +170,12 @@ abstract class ApiFormatBase extends ApiBase {
?>
<br />
<small>
-You are looking at the HTML representation of the <?php echo( $this->mFormat ); ?> format.<br />
+You are looking at the HTML representation of the <?php echo $this->mFormat; ?> format.<br />
HTML is good for debugging, but is unsuitable for application use.<br />
Specify the format parameter to change the output format.<br />
-To see the non HTML representation of the <?php echo( $this->mFormat ); ?> format, set format=<?php echo( strtolower( $this->mFormat ) ); ?>.<br />
+To see the non HTML representation of the <?php echo $this->mFormat; ?> format, set format=<?php echo strtolower( $this->mFormat ); ?>.<br />
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.
+<a href='<?php echo $script; ?>'>API help</a> for more information.
</small>
<pre style='white-space: pre-wrap;'>
<?php
diff --git a/includes/api/ApiFormatJson.php b/includes/api/ApiFormatJson.php
index abb63480..342a580f 100644
--- a/includes/api/ApiFormatJson.php
+++ b/includes/api/ApiFormatJson.php
@@ -56,30 +56,32 @@ class ApiFormatJson extends ApiFormatBase {
}
public function execute() {
- $prefix = $suffix = '';
-
$params = $this->extractRequestParams();
+ $json = FormatJson::encode(
+ $this->getResultData(),
+ $this->getIsHtml(),
+ $params['utf8'] ? FormatJson::ALL_OK : FormatJson::XMLMETA_OK
+ );
$callback = $params['callback'];
- if ( !is_null( $callback ) ) {
- $prefix = preg_replace( "/[^][.\\'\\\"_A-Za-z0-9]/", '', $callback ) . '(';
- $suffix = ')';
+ if ( $callback !== null ) {
+ $callback = preg_replace( "/[^][.\\'\\\"_A-Za-z0-9]/", '', $callback );
+ $this->printText( "$callback($json)" );
+ } else {
+ $this->printText( $json );
}
- $this->printText(
- $prefix .
- FormatJson::encode( $this->getResultData(), $this->getIsHtml() ) .
- $suffix
- );
}
public function getAllowedParams() {
return array(
- 'callback' => null,
+ 'callback' => null,
+ 'utf8' => false,
);
}
public function getParamDescription() {
return array(
'callback' => 'If specified, wraps the output into a given function call. For safety, all user-specific data will be restricted.',
+ 'utf8' => 'If specified, encodes most (but not all) non-ASCII characters as UTF-8 instead of replacing them with hexadecimal escape sequences.',
);
}
diff --git a/includes/api/ApiFormatWddx.php b/includes/api/ApiFormatWddx.php
index 62b69bb6..5685d937 100644
--- a/includes/api/ApiFormatWddx.php
+++ b/includes/api/ApiFormatWddx.php
@@ -46,7 +46,7 @@ class ApiFormatWddx extends ApiFormatBase {
} else {
// Don't do newlines and indentation if we weren't asked
// for pretty output
- $nl = ( $this->getIsHtml() ? '' : "\n" );
+ $nl = ( $this->getIsHtml() ? "\n" : '' );
$indstr = ' ';
$this->printText( "<?xml version=\"1.0\"?>$nl" );
$this->printText( "<wddxPacket version=\"1.0\">$nl" );
@@ -64,44 +64,43 @@ class ApiFormatWddx extends ApiFormatBase {
* @param $indent int
*/
function slowWddxPrinter( $elemValue, $indent = 0 ) {
- $indstr = ( $this->getIsHtml() ? '' : str_repeat( ' ', $indent ) );
- $indstr2 = ( $this->getIsHtml() ? '' : str_repeat( ' ', $indent + 2 ) );
- $nl = ( $this->getIsHtml() ? '' : "\n" );
- switch ( gettype( $elemValue ) ) {
- case 'array':
- // Check whether we've got an associative array (<struct>)
- // or a regular array (<array>)
- $cnt = count( $elemValue );
- if ( $cnt == 0 || array_keys( $elemValue ) === range( 0, $cnt - 1 ) ) {
- // Regular array
- $this->printText( $indstr . Xml::element( 'array', array(
- 'length' => $cnt ), null ) . $nl );
- foreach ( $elemValue as $subElemValue ) {
- $this->slowWddxPrinter( $subElemValue, $indent + 2 );
- }
- $this->printText( "$indstr</array>$nl" );
- } else {
- // Associative array (<struct>)
- $this->printText( "$indstr<struct>$nl" );
- foreach ( $elemValue as $subElemName => $subElemValue ) {
- $this->printText( $indstr2 . Xml::element( 'var', array(
- 'name' => $subElemName
- ), null ) . $nl );
- $this->slowWddxPrinter( $subElemValue, $indent + 4 );
- $this->printText( "$indstr2</var>$nl" );
- }
- $this->printText( "$indstr</struct>$nl" );
+ $indstr = ( $this->getIsHtml() ? str_repeat( ' ', $indent ) : '' );
+ $indstr2 = ( $this->getIsHtml() ? str_repeat( ' ', $indent + 2 ) : '' );
+ $nl = ( $this->getIsHtml() ? "\n" : '' );
+ if ( is_array( $elemValue ) ) {
+ // Check whether we've got an associative array (<struct>)
+ // or a regular array (<array>)
+ $cnt = count( $elemValue );
+ if ( $cnt == 0 || array_keys( $elemValue ) === range( 0, $cnt - 1 ) ) {
+ // Regular array
+ $this->printText( $indstr . Xml::element( 'array', array(
+ 'length' => $cnt ), null ) . $nl );
+ foreach ( $elemValue as $subElemValue ) {
+ $this->slowWddxPrinter( $subElemValue, $indent + 2 );
}
- break;
- case 'integer':
- case 'double':
- $this->printText( $indstr . Xml::element( 'number', null, $elemValue ) . $nl );
- break;
- case 'string':
- $this->printText( $indstr . Xml::element( 'string', null, $elemValue ) . $nl );
- break;
- default:
- ApiBase::dieDebug( __METHOD__, 'Unknown type ' . gettype( $elemValue ) );
+ $this->printText( "$indstr</array>$nl" );
+ } else {
+ // Associative array (<struct>)
+ $this->printText( "$indstr<struct>$nl" );
+ foreach ( $elemValue as $subElemName => $subElemValue ) {
+ $this->printText( $indstr2 . Xml::element( 'var', array(
+ 'name' => $subElemName
+ ), null ) . $nl );
+ $this->slowWddxPrinter( $subElemValue, $indent + 4 );
+ $this->printText( "$indstr2</var>$nl" );
+ }
+ $this->printText( "$indstr</struct>$nl" );
+ }
+ } elseif ( is_int( $elemValue ) || is_float( $elemValue ) ) {
+ $this->printText( $indstr . Xml::element( 'number', null, $elemValue ) . $nl );
+ } elseif ( is_string( $elemValue ) ) {
+ $this->printText( $indstr . Xml::element( 'string', null, $elemValue ) . $nl );
+ } elseif ( is_bool( $elemValue ) ) {
+ $this->printText( $indstr . Xml::element( 'boolean',
+ array( 'value' => $elemValue ? 'true' : 'false' ) ) . $nl
+ );
+ } else {
+ ApiBase::dieDebug( __METHOD__, 'Unknown type ' . gettype( $elemValue ) );
}
}
diff --git a/includes/api/ApiFormatXml.php b/includes/api/ApiFormatXml.php
index b4e8e330..4ec149c0 100644
--- a/includes/api/ApiFormatXml.php
+++ b/includes/api/ApiFormatXml.php
@@ -32,7 +32,6 @@ class ApiFormatXml extends ApiFormatBase {
private $mRootElemName = 'api';
public static $namespace = 'http://www.mediawiki.org/xml/api/';
- private $mDoubleQuote = false;
private $mIncludeNamespace = false;
private $mXslt = null;
@@ -50,7 +49,6 @@ class ApiFormatXml extends ApiFormatBase {
public function execute() {
$params = $this->extractRequestParams();
- $this->mDoubleQuote = $params['xmldoublequote'];
$this->mIncludeNamespace = $params['includexmlnamespace'];
$this->mXslt = $params['xslt'];
@@ -71,8 +69,7 @@ class ApiFormatXml extends ApiFormatBase {
$this->printText(
self::recXmlPrint( $this->mRootElemName,
$data,
- $this->getIsHtml() ? - 2 : null,
- $this->mDoubleQuote
+ $this->getIsHtml() ? - 2 : null
)
);
}
@@ -117,11 +114,10 @@ class ApiFormatXml extends ApiFormatBase {
* @param $elemName
* @param $elemValue
* @param $indent
- * @param $doublequote bool
*
* @return string
*/
- public static function recXmlPrint( $elemName, $elemValue, $indent, $doublequote = false ) {
+ public static function recXmlPrint( $elemName, $elemValue, $indent ) {
$retval = '';
if ( !is_null( $indent ) ) {
$indent += 2;
@@ -131,84 +127,71 @@ class ApiFormatXml extends ApiFormatBase {
}
$elemName = str_replace( ' ', '_', $elemName );
- switch ( gettype( $elemValue ) ) {
- case 'array':
- if ( isset( $elemValue['*'] ) ) {
- $subElemContent = $elemValue['*'];
- if ( $doublequote ) {
- $subElemContent = Sanitizer::encodeAttribute( $subElemContent );
- }
- unset( $elemValue['*'] );
-
- // Add xml:space="preserve" to the
- // element so XML parsers will leave
- // whitespace in the content alone
- $elemValue['xml:space'] = 'preserve';
- } else {
- $subElemContent = null;
+ if ( is_array( $elemValue ) ) {
+ if ( isset( $elemValue['*'] ) ) {
+ $subElemContent = $elemValue['*'];
+ unset( $elemValue['*'] );
+
+ // Add xml:space="preserve" to the
+ // element so XML parsers will leave
+ // whitespace in the content alone
+ $elemValue['xml:space'] = 'preserve';
+ } else {
+ $subElemContent = null;
+ }
+
+ if ( isset( $elemValue['_element'] ) ) {
+ $subElemIndName = $elemValue['_element'];
+ unset( $elemValue['_element'] );
+ } else {
+ $subElemIndName = null;
+ }
+
+ $indElements = array();
+ $subElements = array();
+ foreach ( $elemValue as $subElemId => & $subElemValue ) {
+ if ( is_int( $subElemId ) ) {
+ $indElements[] = $subElemValue;
+ unset( $elemValue[$subElemId] );
+ } elseif ( is_array( $subElemValue ) ) {
+ $subElements[$subElemId] = $subElemValue;
+ unset( $elemValue[$subElemId] );
}
+ }
- if ( isset( $elemValue['_element'] ) ) {
- $subElemIndName = $elemValue['_element'];
- unset( $elemValue['_element'] );
- } else {
- $subElemIndName = null;
- }
+ if ( is_null( $subElemIndName ) && count( $indElements ) ) {
+ ApiBase::dieDebug( __METHOD__, "($elemName, ...) has integer keys without _element value. Use ApiResult::setIndexedTagName()." );
+ }
- $indElements = array();
- $subElements = array();
- foreach ( $elemValue as $subElemId => & $subElemValue ) {
- if ( is_string( $subElemValue ) && $doublequote ) {
- $subElemValue = Sanitizer::encodeAttribute( $subElemValue );
- }
-
- if ( gettype( $subElemId ) === 'integer' ) {
- $indElements[] = $subElemValue;
- unset( $elemValue[$subElemId] );
- } elseif ( is_array( $subElemValue ) ) {
- $subElements[$subElemId] = $subElemValue;
- unset ( $elemValue[$subElemId] );
- }
- }
+ if ( count( $subElements ) && count( $indElements ) && !is_null( $subElemContent ) ) {
+ ApiBase::dieDebug( __METHOD__, "($elemName, ...) has content and subelements" );
+ }
- if ( is_null( $subElemIndName ) && count( $indElements ) ) {
- ApiBase::dieDebug( __METHOD__, "($elemName, ...) has integer keys without _element value. Use ApiResult::setIndexedTagName()." );
- }
+ if ( !is_null( $subElemContent ) ) {
+ $retval .= $indstr . Xml::element( $elemName, $elemValue, $subElemContent );
+ } elseif ( !count( $indElements ) && !count( $subElements ) ) {
+ $retval .= $indstr . Xml::element( $elemName, $elemValue );
+ } else {
+ $retval .= $indstr . Xml::element( $elemName, $elemValue, null );
- if ( count( $subElements ) && count( $indElements ) && !is_null( $subElemContent ) ) {
- ApiBase::dieDebug( __METHOD__, "($elemName, ...) has content and subelements" );
+ foreach ( $subElements as $subElemId => & $subElemValue ) {
+ $retval .= self::recXmlPrint( $subElemId, $subElemValue, $indent );
}
- if ( !is_null( $subElemContent ) ) {
- $retval .= $indstr . Xml::element( $elemName, $elemValue, $subElemContent );
- } elseif ( !count( $indElements ) && !count( $subElements ) ) {
- $retval .= $indstr . Xml::element( $elemName, $elemValue );
- } else {
- $retval .= $indstr . Xml::element( $elemName, $elemValue, null );
-
- foreach ( $subElements as $subElemId => & $subElemValue ) {
- $retval .= self::recXmlPrint( $subElemId, $subElemValue, $indent );
- }
-
- foreach ( $indElements as &$subElemValue ) {
- $retval .= self::recXmlPrint( $subElemIndName, $subElemValue, $indent );
- }
-
- $retval .= $indstr . Xml::closeElement( $elemName );
+ foreach ( $indElements as &$subElemValue ) {
+ $retval .= self::recXmlPrint( $subElemIndName, $subElemValue, $indent );
}
- break;
- case 'object':
- // ignore
- break;
- default:
- // 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;
+
+ $retval .= $indstr . Xml::closeElement( $elemName );
+ }
+ } elseif ( !is_object( $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 );
+ }
}
return $retval;
}
@@ -232,7 +215,6 @@ class ApiFormatXml extends ApiFormatBase {
public function getAllowedParams() {
return array(
- 'xmldoublequote' => false,
'xslt' => null,
'includexmlnamespace' => false,
);
@@ -240,7 +222,6 @@ class ApiFormatXml extends ApiFormatBase {
public function getParamDescription() {
return array(
- 'xmldoublequote' => 'If specified, double quotes all attributes and content',
'xslt' => 'If specified, adds <xslt> as stylesheet. This should be a wiki page '
. 'in the MediaWiki namespace whose page name ends with ".xsl"',
'includexmlnamespace' => 'If specified, adds an XML namespace'
diff --git a/includes/api/ApiImageRotate.php b/includes/api/ApiImageRotate.php
index b2d75825..7a60e831 100644
--- a/includes/api/ApiImageRotate.php
+++ b/includes/api/ApiImageRotate.php
@@ -22,7 +22,6 @@
*/
class ApiImageRotate extends ApiBase {
-
private $mPageSet = null;
public function __construct( $main, $action ) {
@@ -38,32 +37,29 @@ class ApiImageRotate extends ApiBase {
*/
private static function addValues( array &$result, $values, $flag = null, $name = null ) {
foreach ( $values as $val ) {
- if( $val instanceof Title ) {
+ if ( $val instanceof Title ) {
$v = array();
ApiQueryBase::addTitleInfo( $v, $val );
- } elseif( $name !== null ) {
+ } elseif ( $name !== null ) {
$v = array( $name => $val );
} else {
$v = $val;
}
- if( $flag !== null ) {
+ if ( $flag !== null ) {
$v[$flag] = '';
}
$result[] = $v;
}
}
-
public function execute() {
$params = $this->extractRequestParams();
- $rotation = $params[ 'rotation' ];
- $user = $this->getUser();
+ $rotation = $params['rotation'];
$pageSet = $this->getPageSet();
$pageSet->execute();
$result = array();
- $result = array();
self::addValues( $result, $pageSet->getInvalidTitles(), 'invalid', 'title' );
self::addValues( $result, $pageSet->getSpecialTitles(), 'special', 'title' );
@@ -111,15 +107,17 @@ class ApiImageRotate extends ApiBase {
continue;
}
$ext = strtolower( pathinfo( "$srcPath", PATHINFO_EXTENSION ) );
- $tmpFile = TempFSFile::factory( 'rotate_', $ext);
+ $tmpFile = TempFSFile::factory( 'rotate_', $ext );
$dstPath = $tmpFile->getPath();
$err = $handler->rotate( $file, array(
"srcPath" => $srcPath,
"dstPath" => $dstPath,
- "rotation"=> $rotation
+ "rotation" => $rotation
) );
if ( !$err ) {
- $comment = wfMessage( 'rotate-comment' )->numParams( $rotation )->text();
+ $comment = wfMessage(
+ 'rotate-comment'
+ )->numParams( $rotation )->inContentLanguage()->text();
$status = $file->upload( $dstPath,
$comment, $comment, 0, false, false, $this->getUser() );
if ( $status->isGood() ) {
@@ -152,13 +150,14 @@ class ApiImageRotate extends ApiBase {
/**
* Checks that the user has permissions to perform rotations.
- * @param $user User The user to check.
+ * @param User $user The user to check
+ * @param Title $title
* @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 )
+ $title->getUserPermissionsErrors( 'edit', $user ),
+ $title->getUserPermissionsErrors( 'upload', $user )
);
if ( $permissionErrors ) {
@@ -179,7 +178,6 @@ class ApiImageRotate extends ApiBase {
}
public function getAllowedParams( $flags = 0 ) {
- $pageSet = $this->getPageSet();
$result = array(
'rotation' => array(
ApiBase::PARAM_TYPE => array( '90', '180', '270' ),
@@ -198,7 +196,7 @@ class ApiImageRotate extends ApiBase {
public function getParamDescription() {
$pageSet = $this->getPageSet();
- return $pageSet->getParamDescription() + array(
+ return $pageSet->getFinalParamDescription() + array(
'rotation' => 'Degrees to rotate image clockwise',
'token' => 'Edit token. You can get one of these through action=tokens',
);
@@ -220,7 +218,7 @@ class ApiImageRotate extends ApiBase {
$pageSet = $this->getPageSet();
return array_merge(
parent::getPossibleErrors(),
- $pageSet->getPossibleErrors()
+ $pageSet->getFinalPossibleErrors()
);
}
diff --git a/includes/api/ApiImport.php b/includes/api/ApiImport.php
index 1f0a5fab..f48a822e 100644
--- a/includes/api/ApiImport.php
+++ b/includes/api/ApiImport.php
@@ -57,7 +57,7 @@ class ApiImport extends ApiBase {
$source = ImportStreamSource::newFromUpload( 'xml' );
}
if ( !$source->isOK() ) {
- $this->dieUsageMsg( $source->getErrorsArray() );
+ $this->dieStatus( $source );
}
$importer = new WikiImporter( $source->value );
@@ -66,8 +66,8 @@ class ApiImport extends ApiBase {
}
if ( isset( $params['rootpage'] ) ) {
$statusRootPage = $importer->setTargetRootPage( $params['rootpage'] );
- if( !$statusRootPage->isGood() ) {
- $this->dieUsageMsg( $statusRootPage->getErrorsArray() );
+ if ( !$statusRootPage->isGood() ) {
+ $this->dieStatus( $statusRootPage );
}
}
$reporter = new ApiImportReporter(
diff --git a/includes/api/ApiMain.php b/includes/api/ApiMain.php
index 7b2fd914..c11f16cb 100644
--- a/includes/api/ApiMain.php
+++ b/includes/api/ApiMain.php
@@ -83,7 +83,7 @@ class ApiMain extends ApiBase {
'import' => 'ApiImport',
'userrights' => 'ApiUserrights',
'options' => 'ApiOptions',
- 'imagerotate' =>'ApiImageRotate',
+ 'imagerotate' => 'ApiImageRotate',
);
/**
@@ -274,7 +274,7 @@ class ApiMain extends ApiBase {
return;
}
- if ( !User::groupHasPermission( '*', 'read' ) ) {
+ if ( !User::isEveryoneAllowed( 'read' ) ) {
// Private wiki, only private headers
if ( $mode !== 'private' ) {
wfDebug( __METHOD__ . ": ignoring request for $mode cache mode, private wiki\n" );
@@ -383,13 +383,8 @@ class ApiMain extends ApiBase {
wfRunHooks( 'ApiMain::onException', array( $this, $e ) );
// Log it
- if ( $e instanceof MWException && !( $e instanceof UsageException ) ) {
- global $wgLogExceptionBacktrace;
- if ( $wgLogExceptionBacktrace ) {
- wfDebugLog( 'exception', $e->getLogMessage() . "\n" . $e->getTraceAsString() . "\n" );
- } else {
- wfDebugLog( 'exception', $e->getLogMessage() );
- }
+ if ( !( $e instanceof UsageException ) ) {
+ MWExceptionHandler::logException( $e );
}
// Handle any kind of exception by outputting properly formatted error message.
@@ -418,7 +413,7 @@ class ApiMain extends ApiBase {
}
// Log the request whether or not there was an error
- $this->logRequest( microtime( true ) - $t);
+ $this->logRequest( microtime( true ) - $t );
// Send cache headers after any code which might generate an error, to
// avoid sending public cache headers for errors.
@@ -609,7 +604,7 @@ class ApiMain extends ApiBase {
$result = $this->getResult();
// Printer may not be initialized if the extractRequestParams() fails for the main module
- if ( !isset ( $this->mPrinter ) ) {
+ 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 ( !$this->mModuleMgr->isDefined( $value, 'format' ) ) {
@@ -763,7 +758,7 @@ class ApiMain extends ApiBase {
*/
protected function checkExecutePermissions( $module ) {
$user = $this->getUser();
- if ( $module->isReadMode() && !User::groupHasPermission( '*', 'read' ) &&
+ if ( $module->isReadMode() && !User::isEveryoneAllowed( 'read' ) &&
!$user->isAllowed( 'read' ) )
{
$this->dieUsageMsg( 'readrequired' );
@@ -782,7 +777,7 @@ class ApiMain extends ApiBase {
// Allow extensions to stop execution for arbitrary reasons.
$message = false;
- if( !wfRunHooks( 'ApiCheckCanExecute', array( $module, $user, &$message ) ) ) {
+ if ( !wfRunHooks( 'ApiCheckCanExecute', array( $module, $user, &$message ) ) ) {
$this->dieUsageMsg( $message );
}
}
@@ -857,7 +852,7 @@ class ApiMain extends ApiBase {
' ' . $request->getMethod() .
' ' . wfUrlencode( str_replace( ' ', '_', $this->getUser()->getName() ) ) .
' ' . $request->getIP() .
- ' T=' . $milliseconds .'ms';
+ ' T=' . $milliseconds . 'ms';
foreach ( $this->getParamsUsed() as $name ) {
$value = $request->getVal( $name );
if ( $value === null ) {
@@ -944,7 +939,7 @@ class ApiMain extends ApiBase {
$unusedParams = array_diff( $allParams, $paramsUsed );
}
- if( count( $unusedParams ) ) {
+ if ( count( $unusedParams ) ) {
$s = count( $unusedParams ) > 1 ? 's' : '';
$this->setWarning( "Unrecognized parameter$s: '" . implode( $unusedParams, "', '" ) . "'" );
}
@@ -957,7 +952,7 @@ class ApiMain extends ApiBase {
*/
protected function printResult( $isError ) {
global $wgDebugAPI;
- if( $wgDebugAPI !== false ) {
+ if ( $wgDebugAPI !== false ) {
$this->setWarning( 'SECURITY WARNING: $wgDebugAPI is enabled' );
}
@@ -1002,7 +997,7 @@ class ApiMain extends ApiBase {
ApiBase::PARAM_DFLT => 'help',
ApiBase::PARAM_TYPE => $this->mModuleMgr->getNames( 'action' )
),
- 'maxlag' => array(
+ 'maxlag' => array(
ApiBase::PARAM_TYPE => 'integer'
),
'smaxage' => array(
@@ -1014,7 +1009,7 @@ class ApiMain extends ApiBase {
ApiBase::PARAM_DFLT => 0
),
'requestid' => null,
- 'servedby' => false,
+ 'servedby' => false,
'origin' => null,
);
}
@@ -1042,6 +1037,7 @@ class ApiMain extends ApiBase {
'servedby' => 'Include the hostname that served the request in the results. Unconditionally shown on error',
'origin' => array(
'When accessing the API using a cross-domain AJAX request (CORS), set this to the originating domain.',
+ 'This must be included in any pre-flight request, and therefore must be part of the request URI (not the POST body).',
'This must match one of the origins in the Origin: header exactly, so it has to be set to something like http://en.wikipedia.org or https://meta.wikimedia.org .',
'If this parameter does not match the Origin: header, a 403 response will be returned.',
'If this parameter matches the Origin: header and the origin is whitelisted, an Access-Control-Allow-Origin header will be set.',
@@ -1114,7 +1110,7 @@ class ApiMain extends ApiBase {
return array(
'API developers:',
' Roan Kattouw "<Firstname>.<Lastname>@gmail.com" (lead developer Sep 2007-2009)',
- ' Victor Vasiliev - vasilvv at gee mail dot com',
+ ' Victor Vasiliev - vasilvv @ gmail . 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, 2012-present)',
@@ -1143,7 +1139,7 @@ class ApiMain extends ApiBase {
$this->setHelp();
// Get help text from cache if present
$key = wfMemcKey( 'apihelp', $this->getModuleName(),
- SpecialVersion::getVersion( 'nodb' ) );
+ str_replace( ' ', '_', SpecialVersion::getVersion( 'nodb' ) ) );
if ( $wgAPICacheHelpTimeout > 0 ) {
$cached = $wgMemc->get( $key );
if ( $cached ) {
diff --git a/includes/api/ApiMove.php b/includes/api/ApiMove.php
index 3e846e3b..c18036cf 100644
--- a/includes/api/ApiMove.php
+++ b/includes/api/ApiMove.php
@@ -88,7 +88,7 @@ class ApiMove extends ApiBase {
$r['redirectcreated'] = '';
}
- if( $toTitleExists ) {
+ if ( $toTitleExists ) {
$r['moveoverredirect'] = '';
}
@@ -99,7 +99,7 @@ class ApiMove extends ApiBase {
if ( $retval === true ) {
$r['talkfrom'] = $fromTalk->getPrefixedText();
$r['talkto'] = $toTalk->getPrefixedText();
- if( $toTalkExists ) {
+ if ( $toTalkExists ) {
$r['talkmoveoverredirect'] = '';
}
} else {
diff --git a/includes/api/ApiOpenSearch.php b/includes/api/ApiOpenSearch.php
index caf361ac..315ace37 100644
--- a/includes/api/ApiOpenSearch.php
+++ b/includes/api/ApiOpenSearch.php
@@ -1,7 +1,5 @@
<?php
/**
- *
- *
* Created on Oct 13, 2006
*
* Copyright © 2006 Yuri Astrakhan "<Firstname><Lastname>@gmail.com"
@@ -29,8 +27,20 @@
*/
class ApiOpenSearch extends ApiBase {
+ /**
+ * Override built-in handling of format parameter.
+ * Only JSON is supported.
+ *
+ * @return ApiFormatBase
+ */
public function getCustomPrinter() {
- return $this->getMain()->createPrinterByName( 'json' );
+ $params = $this->extractRequestParams();
+ $format = $params['format'];
+ $allowed = array( 'json', 'jsonfm' );
+ if ( in_array( $format, $allowed ) ) {
+ return $this->getMain()->createPrinterByName( $format );
+ }
+ return $this->getMain()->createPrinterByName( $allowed[0] );
}
public function execute() {
@@ -94,6 +104,10 @@ class ApiOpenSearch extends ApiBase {
ApiBase::PARAM_ISMULTI => true
),
'suggest' => false,
+ 'format' => array(
+ ApiBase::PARAM_DFLT => 'json',
+ ApiBase::PARAM_TYPE => array( 'json', 'jsonfm' ),
+ )
);
}
@@ -103,6 +117,7 @@ class ApiOpenSearch extends ApiBase {
'limit' => 'Maximum amount of results to return',
'namespace' => 'Namespaces to search',
'suggest' => 'Do nothing if $wgEnableOpenSearchSuggest is false',
+ 'format' => 'The format of the output',
);
}
diff --git a/includes/api/ApiOptions.php b/includes/api/ApiOptions.php
index 8c996a26..7256066d 100644
--- a/includes/api/ApiOptions.php
+++ b/includes/api/ApiOptions.php
@@ -42,6 +42,10 @@ class ApiOptions extends ApiBase {
$this->dieUsage( 'Anonymous users cannot change preferences', 'notloggedin' );
}
+ if ( !$user->isAllowed( 'editmyoptions' ) ) {
+ $this->dieUsage( 'You don\'t have permission to edit your options', 'permissiondenied' );
+ }
+
$params = $this->extractRequestParams();
$changed = false;
@@ -50,7 +54,7 @@ class ApiOptions extends ApiBase {
}
if ( $params['reset'] ) {
- $user->resetOptions( $params['resetkinds'] );
+ $user->resetOptions( $params['resetkinds'], $this->getContext() );
$changed = true;
}
@@ -83,7 +87,7 @@ class ApiOptions extends ApiBase {
case 'registered-checkmatrix':
// A key for a multiselect or checkmatrix option.
$validation = true;
- $value = $value !== null ? (bool) $value : null;
+ $value = $value !== null ? (bool)$value : null;
break;
case 'userjs':
// Allow non-default preferences prefixed with 'userjs-', to be set by user scripts
diff --git a/includes/api/ApiPageSet.php b/includes/api/ApiPageSet.php
index 074efe4b..b05cb2b6 100644
--- a/includes/api/ApiPageSet.php
+++ b/includes/api/ApiPageSet.php
@@ -69,6 +69,9 @@ class ApiPageSet extends ApiBase {
private $mFakePageId = -1;
private $mCacheMode = 'public';
private $mRequestedPageFields = array();
+ /**
+ * @var int
+ */
private $mDefaultNamespace = NS_MAIN;
/**
@@ -149,7 +152,6 @@ class ApiPageSet extends ApiBase {
if ( !$isDryRun ) {
$generator->executeGenerator( $this );
wfRunHooks( 'APIQueryGeneratorAfterExecute', array( &$generator, &$this ) );
- $this->resolvePendingRedirects();
} else {
// Prevent warnings from being reported on these parameters
$main = $this->getMain();
@@ -160,6 +162,10 @@ class ApiPageSet extends ApiBase {
$generator->profileOut();
$this->profileIn();
+ if ( !$isDryRun ) {
+ $this->resolvePendingRedirects();
+ }
+
if ( !$isQuery ) {
// If this pageset is not part of the query, we called profileIn() above
$dbSource->profileOut();
@@ -185,7 +191,7 @@ class ApiPageSet extends ApiBase {
if ( !$isDryRun ) {
// Populate page information with the original user input
- switch( $dataSource ) {
+ switch ( $dataSource ) {
case 'titles':
$this->initFromTitles( $this->mParams['titles'] );
break;
@@ -404,7 +410,7 @@ class ApiPageSet extends ApiBase {
* @return array of raw_prefixed_title (string) => prefixed_title (string)
* @since 1.21
*/
- public function getNormalizedTitlesAsResult( $result = null ) {
+ public function getNormalizedTitlesAsResult( $result = null ) {
$values = array();
foreach ( $this->getNormalizedTitles() as $rawTitleStr => $titleStr ) {
$values[] = array(
@@ -595,13 +601,13 @@ class ApiPageSet extends ApiBase {
}
foreach ( $this->mRequestedPageFields as $fieldName => &$fieldValues ) {
- $fieldValues[$pageId] = $row-> $fieldName;
+ $fieldValues[$pageId] = $row->$fieldName;
}
}
/**
* Do not use, does nothing, will be removed
- * @deprecated 1.21
+ * @deprecated since 1.21
*/
public function finishPageSetGeneration() {
wfDeprecated( __METHOD__, '1.21' );
@@ -859,7 +865,7 @@ class ApiPageSet extends ApiBase {
$from = $this->mPendingRedirectIDs[$rdfrom]->getPrefixedText();
$to = Title::makeTitle( $row->rd_namespace, $row->rd_title, $row->rd_fragment, $row->rd_interwiki );
unset( $this->mPendingRedirectIDs[$rdfrom] );
- if ( !isset( $this->mAllPages[$row->rd_namespace][$row->rd_title] ) ) {
+ if ( !$to->isExternal() && !isset( $this->mAllPages[$row->rd_namespace][$row->rd_title] ) ) {
$lb->add( $row->rd_namespace, $row->rd_title );
}
$this->mRedirectTitles[$from] = $to;
diff --git a/includes/api/ApiParamInfo.php b/includes/api/ApiParamInfo.php
index 27f8cefd..3e1a7531 100644
--- a/includes/api/ApiParamInfo.php
+++ b/includes/api/ApiParamInfo.php
@@ -149,7 +149,7 @@ class ApiParamInfo extends ApiBase {
$item = array();
if ( is_numeric( $k ) ) {
$retval['examples'] .= $v;
- $result->setContent( $item, $v );
+ ApiResult::setContent( $item, $v );
} else {
if ( !is_array( $v ) ) {
$item['description'] = $v;
@@ -157,7 +157,7 @@ class ApiParamInfo extends ApiBase {
$item['description'] = implode( $v, "\n" );
}
$retval['examples'] .= $item['description'] . ' ' . $k;
- $result->setContent( $item, $k );
+ ApiResult::setContent( $item, $k );
}
$retval['allexamples'][] = $item;
}
@@ -300,7 +300,7 @@ class ApiParamInfo extends ApiBase {
}
// Errors
- $retval['errors'] = $this->parseErrors( $obj->getPossibleErrors() );
+ $retval['errors'] = $this->parseErrors( $obj->getFinalPossibleErrors() );
$result->setIndexedTagName( $retval['errors'], 'error' );
return $retval;
diff --git a/includes/api/ApiParse.php b/includes/api/ApiParse.php
index 09b7a882..a369994b 100644
--- a/includes/api/ApiParse.php
+++ b/includes/api/ApiParse.php
@@ -44,6 +44,14 @@ class ApiParse extends ApiBase {
$params = $this->extractRequestParams();
$text = $params['text'];
$title = $params['title'];
+ if ( $title === null ) {
+ $titleProvided = false;
+ // A title is needed for parsing, so arbitrarily choose one
+ $title = 'API';
+ } else {
+ $titleProvided = true;
+ }
+
$page = $params['page'];
$pageid = $params['pageid'];
$oldid = $params['oldid'];
@@ -51,7 +59,7 @@ class ApiParse extends ApiBase {
$model = $params['contentmodel'];
$format = $params['contentformat'];
- if ( !is_null( $page ) && ( !is_null( $text ) || $title != 'API' ) ) {
+ if ( !is_null( $page ) && ( !is_null( $text ) || $titleProvided ) ) {
$this->dieUsage( 'The page parameter cannot be used together with the text and title parameters', 'params' );
}
@@ -94,8 +102,7 @@ class ApiParse extends ApiBase {
$titleObj = $rev->getTitle();
$wgTitle = $titleObj;
$pageObj = WikiPage::factory( $titleObj );
- $popts = $pageObj->makeParserOptions( $this->getContext() );
- $popts->enableLimitReport( !$params['disablepp'] );
+ $popts = $this->makeParserOptions( $pageObj, $params );
// If for some reason the "oldid" is actually the current revision, it may be cached
if ( $rev->isCurrent() ) {
@@ -152,8 +159,7 @@ class ApiParse extends ApiBase {
$oldid = $pageObj->getLatest();
}
- $popts = $pageObj->makeParserOptions( $this->getContext() );
- $popts->enableLimitReport( !$params['disablepp'] );
+ $popts = $this->makeParserOptions( $pageObj, $params );
// Potentially cached
$p_result = $this->getParsedContent( $pageObj, $popts, $pageid,
@@ -170,11 +176,24 @@ class ApiParse extends ApiBase {
$wgTitle = $titleObj;
$pageObj = WikiPage::factory( $titleObj );
- $popts = $pageObj->makeParserOptions( $this->getContext() );
- $popts->enableLimitReport( !$params['disablepp'] );
+ $popts = $this->makeParserOptions( $pageObj, $params );
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' );
+ if ( $titleProvided && ( $prop || $params['generatexml'] ) ) {
+ $this->setWarning(
+ "'title' used without 'text', and parsed page properties were requested " .
+ "(did you mean to use 'page' instead of 'title'?)"
+ );
+ }
+ // Prevent warning from ContentHandler::makeContent()
+ $text = '';
+ }
+
+ // If we are parsing text, do not use the content model of the default
+ // API title, but default to wikitext to keep BC.
+ if ( !$titleProvided && is_null( $model ) ) {
+ $model = CONTENT_MODEL_WIKITEXT;
+ $this->setWarning( "No 'title' or 'contentmodel' was given, assuming $model." );
}
try {
@@ -194,10 +213,10 @@ class ApiParse extends ApiBase {
// Build a result and bail out
$result_array = array();
$result_array['text'] = array();
- $result->setContent( $result_array['text'], $this->pstContent->serialize( $format ) );
+ ApiResult::setContent( $result_array['text'], $this->pstContent->serialize( $format ) );
if ( isset( $prop['wikitext'] ) ) {
$result_array['wikitext'] = array();
- $result->setContent( $result_array['wikitext'], $this->content->serialize( $format ) );
+ ApiResult::setContent( $result_array['wikitext'], $this->content->serialize( $format ) );
}
$result->addValue( null, $this->getModuleName(), $result_array );
return;
@@ -225,21 +244,35 @@ class ApiParse extends ApiBase {
if ( isset( $prop['text'] ) ) {
$result_array['text'] = array();
- $result->setContent( $result_array['text'], $p_result->getText() );
+ ApiResult::setContent( $result_array['text'], $p_result->getText() );
}
if ( !is_null( $params['summary'] ) ) {
$result_array['parsedsummary'] = array();
- $result->setContent( $result_array['parsedsummary'], Linker::formatComment( $params['summary'], $titleObj ) );
+ ApiResult::setContent( $result_array['parsedsummary'], Linker::formatComment( $params['summary'], $titleObj ) );
+ }
+
+ if ( isset( $prop['langlinks'] ) || isset( $prop['languageshtml'] ) ) {
+ $langlinks = $p_result->getLanguageLinks();
+
+ if ( $params['effectivelanglinks'] ) {
+ // Link flags are ignored for now, but may in the future be
+ // included in the result.
+ $linkFlags = array();
+ wfRunHooks( 'LanguageLinks', array( $titleObj, &$langlinks, &$linkFlags ) );
+ }
+ } else {
+ $langlinks = false;
}
if ( isset( $prop['langlinks'] ) ) {
- $result_array['langlinks'] = $this->formatLangLinks( $p_result->getLanguageLinks() );
+ $result_array['langlinks'] = $this->formatLangLinks( $langlinks );
}
if ( isset( $prop['languageshtml'] ) ) {
- $languagesHtml = $this->languagesHtml( $p_result->getLanguageLinks() );
+ $languagesHtml = $this->languagesHtml( $langlinks );
+
$result_array['languageshtml'] = array();
- $result->setContent( $result_array['languageshtml'], $languagesHtml );
+ ApiResult::setContent( $result_array['languageshtml'], $languagesHtml );
}
if ( isset( $prop['categories'] ) ) {
$result_array['categories'] = $this->formatCategoryLinks( $p_result->getCategories() );
@@ -247,7 +280,7 @@ class ApiParse extends ApiBase {
if ( isset( $prop['categorieshtml'] ) ) {
$categoriesHtml = $this->categoriesHtml( $p_result->getCategories() );
$result_array['categorieshtml'] = array();
- $result->setContent( $result_array['categorieshtml'], $categoriesHtml );
+ ApiResult::setContent( $result_array['categorieshtml'], $categoriesHtml );
}
if ( isset( $prop['links'] ) ) {
$result_array['links'] = $this->formatLinks( $p_result->getLinks() );
@@ -288,7 +321,7 @@ class ApiParse extends ApiBase {
if ( isset( $prop['headhtml'] ) ) {
$result_array['headhtml'] = array();
- $result->setContent( $result_array['headhtml'], $context->getOutput()->headElement( $context->getSkin() ) );
+ ApiResult::setContent( $result_array['headhtml'], $context->getOutput()->headElement( $context->getSkin() ) );
}
}
@@ -298,10 +331,10 @@ class ApiParse extends ApiBase {
if ( isset( $prop['wikitext'] ) ) {
$result_array['wikitext'] = array();
- $result->setContent( $result_array['wikitext'], $this->content->serialize( $format ) );
+ ApiResult::setContent( $result_array['wikitext'], $this->content->serialize( $format ) );
if ( !is_null( $this->pstContent ) ) {
$result_array['psttext'] = array();
- $result->setContent( $result_array['psttext'], $this->pstContent->serialize( $format ) );
+ ApiResult::setContent( $result_array['psttext'], $this->pstContent->serialize( $format ) );
}
}
if ( isset( $prop['properties'] ) ) {
@@ -321,7 +354,7 @@ class ApiParse extends ApiBase {
$xml = $dom->__toString();
}
$result_array['parsetree'] = array();
- $result->setContent( $result_array['parsetree'], $xml );
+ ApiResult::setContent( $result_array['parsetree'], $xml );
}
$result_mapping = array(
@@ -346,6 +379,26 @@ class ApiParse extends ApiBase {
}
/**
+ * Constructs a ParserOptions object
+ *
+ * @param WikiPage $pageObj
+ * @param array $params
+ *
+ * @return ParserOptions
+ */
+ protected function makeParserOptions( WikiPage $pageObj, array $params ) {
+ wfProfileIn( __METHOD__ );
+
+ $popts = $pageObj->makeParserOptions( $this->getContext() );
+ $popts->enableLimitReport( !$params['disablepp'] );
+ $popts->setIsPreview( $params['preview'] || $params['sectionpreview'] );
+ $popts->setIsSectionPreview( $params['sectionpreview'] );
+
+ wfProfileOut( __METHOD__ );
+ return $popts;
+ }
+
+ /**
* @param $page WikiPage
* @param $popts ParserOptions
* @param $pageId Int
@@ -400,7 +453,7 @@ class ApiParse extends ApiBase {
if ( $title ) {
$entry['url'] = wfExpandUrl( $title->getFullURL(), PROTO_CURRENT );
}
- $this->getResult()->setContent( $entry, $bits[1] );
+ ApiResult::setContent( $entry, $bits[1] );
$result[] = $entry;
}
return $result;
@@ -411,7 +464,7 @@ class ApiParse extends ApiBase {
foreach ( $links as $link => $sortkey ) {
$entry = array();
$entry['sortkey'] = $sortkey;
- $this->getResult()->setContent( $entry, $link );
+ ApiResult::setContent( $entry, $link );
$result[] = $entry;
}
return $result;
@@ -465,7 +518,7 @@ class ApiParse extends ApiBase {
foreach ( $nslinks as $title => $id ) {
$entry = array();
$entry['ns'] = $ns;
- $this->getResult()->setContent( $entry, Title::makeTitle( $ns, $title )->getFullText() );
+ ApiResult::setContent( $entry, Title::makeTitle( $ns, $title )->getFullText() );
if ( $id != 0 ) {
$entry['exists'] = '';
}
@@ -487,7 +540,7 @@ class ApiParse extends ApiBase {
$entry['url'] = wfExpandUrl( $title->getFullURL(), PROTO_CURRENT );
}
- $this->getResult()->setContent( $entry, $title->getFullText() );
+ ApiResult::setContent( $entry, $title->getFullText() );
$result[] = $entry;
}
}
@@ -499,7 +552,7 @@ class ApiParse extends ApiBase {
foreach ( $headItems as $tag => $content ) {
$entry = array();
$entry['tag'] = $tag;
- $this->getResult()->setContent( $entry, $content );
+ ApiResult::setContent( $entry, $content );
$result[] = $entry;
}
return $result;
@@ -510,7 +563,7 @@ class ApiParse extends ApiBase {
foreach ( $properties as $name => $value ) {
$entry = array();
$entry['name'] = $name;
- $this->getResult()->setContent( $entry, $value );
+ ApiResult::setContent( $entry, $value );
$result[] = $entry;
}
return $result;
@@ -521,7 +574,7 @@ class ApiParse extends ApiBase {
foreach ( $css as $file => $link ) {
$entry = array();
$entry['file'] = $file;
- $this->getResult()->setContent( $entry, $link );
+ ApiResult::setContent( $entry, $link );
$result[] = $entry;
}
return $result;
@@ -537,9 +590,7 @@ class ApiParse extends ApiBase {
public function getAllowedParams() {
return array(
- 'title' => array(
- ApiBase::PARAM_DFLT => 'API',
- ),
+ 'title' => null,
'text' => null,
'summary' => null,
'page' => null,
@@ -575,10 +626,13 @@ class ApiParse extends ApiBase {
),
'pst' => false,
'onlypst' => false,
+ 'effectivelanglinks' => false,
'uselang' => null,
'section' => null,
'disablepp' => false,
'generatexml' => false,
+ 'preview' => false,
+ 'sectionpreview' => false,
'contentformat' => array(
ApiBase::PARAM_TYPE => ContentHandler::getAllContentFormats(),
),
@@ -590,11 +644,13 @@ class ApiParse extends ApiBase {
public function getParamDescription() {
$p = $this->getModulePrefix();
+ $wikitext = CONTENT_MODEL_WIKITEXT;
return array(
- 'text' => 'Wikitext to parse',
+ 'text' => "Text to parse. Use {$p}title or {$p}contentmodel to control the content model",
'summary' => 'Summary to parse',
'redirects' => "If the {$p}page or the {$p}pageid parameter is set to a redirect, resolve it",
- 'title' => 'Title of page the text belongs to',
+ 'title' => "Title of page the text belongs to. " .
+ "If omitted, \"API\" is used as the title with content model $wikitext",
'page' => "Parse the content of this page. Cannot be used together with {$p}text and {$p}title",
'pageid' => "Parse the content of this page. Overrides {$p}page",
'oldid' => "Parse the content of this revision. Overrides {$p}page and {$p}pageid",
@@ -618,34 +674,52 @@ class ApiParse extends ApiBase {
' wikitext - Gives the original wikitext that was parsed',
' properties - Gives various properties defined in the parsed wikitext',
),
+ 'effectivelanglinks' => array(
+ 'Includes language links supplied by extensions',
+ '(for use with prop=langlinks|languageshtml)',
+ ),
'pst' => array(
'Do a pre-save transform on the input before parsing it',
- 'Ignored if page, pageid or oldid is used'
+ "Only valid when used with {$p}text",
),
'onlypst' => array(
'Do a pre-save transform (PST) on the input, but don\'t parse it',
- 'Returns the same wikitext, after a PST has been applied. Ignored if page, pageid or oldid is used'
+ 'Returns the same wikitext, after a PST has been applied.',
+ "Only valid when used with {$p}text",
),
'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 (requires prop=wikitext)',
- 'contentformat' => 'Content serialization format used for the input text',
- 'contentmodel' => 'Content model of the new content',
+ 'generatexml' => "Generate XML parse tree (requires contentmodel=$wikitext)",
+ 'preview' => 'Parse in preview mode',
+ 'sectionpreview' => 'Parse in section preview mode (enables preview mode too)',
+ 'contentformat' => array(
+ 'Content serialization format used for the input text',
+ "Only valid when used with {$p}text",
+ ),
+ 'contentmodel' => array(
+ "Content model of the input text. Default is the model of the " .
+ "specified ${p}title, or $wikitext if ${p}title is not specified",
+ "Only valid when used with {$p}text",
+ ),
);
}
public function getDescription() {
+ $p = $this->getModulePrefix();
return array(
- 'Parses wikitext and returns parser output',
+ 'Parses content and returns parser output',
'See the various prop-Modules of action=query to get information from the current version of a page',
+ 'There are several ways to specify the text to parse:',
+ "1) Specify a page or revision, using {$p}page, {$p}pageid, or {$p}oldid.",
+ "2) Specify content explicitly, using {$p}text, {$p}title, and {$p}contentmodel.",
+ "3) Specify only a summary to parse. {$p}prop should be given an empty value.",
);
}
public function getPossibleErrors() {
return array_merge( parent::getPossibleErrors(), array(
array( 'code' => 'params', 'info' => 'The page parameter cannot be used together with the text and title parameters' ),
- array( 'code' => 'params', 'info' => 'The text parameter should be passed with the title parameter. Should you be using the "page" parameter instead?' ),
array( 'code' => 'missingrev', 'info' => 'There is no revision ID oldid' ),
array( 'code' => 'permissiondenied', 'info' => 'You don\'t have permission to view deleted revisions' ),
array( 'code' => 'missingtitle', 'info' => 'The page you specified doesn\'t exist' ),
@@ -660,7 +734,10 @@ class ApiParse extends ApiBase {
public function getExamples() {
return array(
- 'api.php?action=parse&text={{Project:Sandbox}}'
+ 'api.php?action=parse&page=Project:Sandbox' => 'Parse a page',
+ 'api.php?action=parse&text={{Project:Sandbox}}' => 'Parse wikitext',
+ 'api.php?action=parse&text={{PAGENAME}}&title=Test' => 'Parse wikitext, specifying the page title',
+ 'api.php?action=parse&summary=Some+[[link]]&prop=' => 'Parse a summary',
);
}
diff --git a/includes/api/ApiPatrol.php b/includes/api/ApiPatrol.php
index 4d4fbba9..bd2fde2b 100644
--- a/includes/api/ApiPatrol.php
+++ b/includes/api/ApiPatrol.php
@@ -35,11 +35,27 @@ class ApiPatrol extends ApiBase {
*/
public function execute() {
$params = $this->extractRequestParams();
-
- $rc = RecentChange::newFromID( $params['rcid'] );
- if ( !$rc instanceof RecentChange ) {
- $this->dieUsageMsg( array( 'nosuchrcid', $params['rcid'] ) );
+ $this->requireOnlyOneParameter( $params, 'rcid', 'revid' );
+
+ if ( isset( $params['rcid'] ) ) {
+ $rc = RecentChange::newFromID( $params['rcid'] );
+ if ( !$rc ) {
+ $this->dieUsageMsg( array( 'nosuchrcid', $params['rcid'] ) );
+ }
+ } else {
+ $rev = Revision::newFromId( $params['revid'] );
+ if ( !$rev ) {
+ $this->dieUsageMsg( array( 'nosuchrevid', $params['revid'] ) );
+ }
+ $rc = $rev->getRecentChange();
+ if ( !$rc ) {
+ $this->dieUsage(
+ 'The revision ' . $params['revid'] . " can't be patrolled as it's too old",
+ 'notpatrollable'
+ );
+ }
}
+
$retval = $rc->doMarkPatrolled( $this->getUser() );
if ( $retval ) {
@@ -66,8 +82,10 @@ class ApiPatrol extends ApiBase {
ApiBase::PARAM_REQUIRED => true
),
'rcid' => array(
- ApiBase::PARAM_TYPE => 'integer',
- ApiBase::PARAM_REQUIRED => true
+ ApiBase::PARAM_TYPE => 'integer'
+ ),
+ 'revid' => array(
+ ApiBase::PARAM_TYPE => 'integer'
),
);
}
@@ -76,6 +94,7 @@ class ApiPatrol extends ApiBase {
return array(
'token' => 'Patrol token obtained from list=recentchanges',
'rcid' => 'Recentchanges ID to patrol',
+ 'revid' => 'Revision ID to patrol',
);
}
@@ -94,8 +113,16 @@ class ApiPatrol extends ApiBase {
}
public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'nosuchrcid', 'rcid' ),
+ return array_merge(
+ parent::getPossibleErrors(),
+ parent::getRequireOnlyOneParameterErrorMessages( array( 'rcid', 'revid' ) ),
+ array(
+ array( 'nosuchrcid', 'rcid' ),
+ array( 'nosuchrevid', 'revid' ),
+ array(
+ 'code' => 'notpatrollable',
+ 'info' => "The revision can't be patrolled as it's too old"
+ )
) );
}
@@ -109,7 +136,8 @@ class ApiPatrol extends ApiBase {
public function getExamples() {
return array(
- 'api.php?action=patrol&token=123abc&rcid=230672766'
+ 'api.php?action=patrol&token=123abc&rcid=230672766',
+ 'api.php?action=patrol&token=123abc&revid=230672766'
);
}
diff --git a/includes/api/ApiProtect.php b/includes/api/ApiProtect.php
index 503c6920..7830c8b4 100644
--- a/includes/api/ApiProtect.php
+++ b/includes/api/ApiProtect.php
@@ -103,8 +103,7 @@ class ApiProtect extends ApiBase {
$status = $pageObj->doUpdateRestrictions( $protections, $expiryarray, $cascade, $params['reason'], $this->getUser() );
if ( !$status->isOK() ) {
- $errors = $status->getErrorsArray();
- $this->dieUsageMsg( $errors[0] );
+ $this->dieStatus( $status );
}
$res = array(
'title' => $titleObj->getPrefixedText(),
diff --git a/includes/api/ApiPurge.php b/includes/api/ApiPurge.php
index 134f4a0d..0812ba51 100644
--- a/includes/api/ApiPurge.php
+++ b/includes/api/ApiPurge.php
@@ -42,15 +42,15 @@ class ApiPurge extends ApiBase {
*/
private static function addValues( array &$result, $values, $flag = null, $name = null ) {
foreach ( $values as $val ) {
- if( $val instanceof Title ) {
+ if ( $val instanceof Title ) {
$v = array();
ApiQueryBase::addTitleInfo( $v, $val );
- } elseif( $name !== null ) {
+ } elseif ( $name !== null ) {
$v = array( $name => $val );
} else {
$v = $val;
}
- if( $flag !== null ) {
+ if ( $flag !== null ) {
$v[$flag] = '';
}
$result[] = $v;
@@ -64,6 +64,7 @@ class ApiPurge extends ApiBase {
$params = $this->extractRequestParams();
$forceLinkUpdate = $params['forcelinkupdate'];
+ $forceRecursiveLinkUpdate = $params['forcerecursivelinkupdate'];
$pageSet = $this->getPageSet();
$pageSet->execute();
@@ -82,8 +83,8 @@ class ApiPurge extends ApiBase {
$page->doPurge(); // Directly purge and skip the UI part of purge().
$r['purged'] = '';
- if ( $forceLinkUpdate ) {
- if ( !$this->getUser()->pingLimiter() ) {
+ if ( $forceLinkUpdate || $forceRecursiveLinkUpdate ) {
+ if ( !$this->getUser()->pingLimiter( 'linkpurge' ) ) {
global $wgEnableParserCache;
$popts = $page->makeParserOptions( 'canonical' );
@@ -93,7 +94,8 @@ class ApiPurge extends ApiBase {
$p_result = $content->getParserOutput( $title, $page->getLatest(), $popts, $wgEnableParserCache );
# Update the links tables
- $updates = $content->getSecondaryDataUpdates( $title, null, true, $p_result );
+ $updates = $content->getSecondaryDataUpdates(
+ $title, null, $forceRecursiveLinkUpdate, $p_result );
DataUpdate::runUpdates( $updates );
$r['linkupdate'] = '';
@@ -150,7 +152,10 @@ class ApiPurge extends ApiBase {
}
public function getAllowedParams( $flags = 0 ) {
- $result = array( 'forcelinkupdate' => false );
+ $result = array(
+ 'forcelinkupdate' => false,
+ 'forcerecursivelinkupdate' => false
+ );
if ( $flags ) {
$result += $this->getPageSet()->getFinalParams( $flags );
}
@@ -158,8 +163,12 @@ class ApiPurge extends ApiBase {
}
public function getParamDescription() {
- return $this->getPageSet()->getParamDescription()
- + array( 'forcelinkupdate' => 'Update the links tables' );
+ return $this->getPageSet()->getFinalParamDescription()
+ + array(
+ 'forcelinkupdate' => 'Update the links tables',
+ 'forcerecursivelinkupdate' => 'Update the links table, and update ' .
+ 'the links tables for any page that uses this page as a template',
+ );
}
public function getResultProperties() {
@@ -204,7 +213,7 @@ class ApiPurge extends ApiBase {
public function getPossibleErrors() {
return array_merge(
parent::getPossibleErrors(),
- $this->getPageSet()->getPossibleErrors()
+ $this->getPageSet()->getFinalPossibleErrors()
);
}
diff --git a/includes/api/ApiQuery.php b/includes/api/ApiQuery.php
index f69ad234..e03837fc 100644
--- a/includes/api/ApiQuery.php
+++ b/includes/api/ApiQuery.php
@@ -64,6 +64,7 @@ class ApiQuery extends ApiBase {
*/
private static $QueryListModules = array(
'allcategories' => 'ApiQueryAllCategories',
+ 'allfileusages' => 'ApiQueryAllLinks',
'allimages' => 'ApiQueryAllImages',
'alllinks' => 'ApiQueryAllLinks',
'allpages' => 'ApiQueryAllPages',
@@ -102,6 +103,7 @@ class ApiQuery extends ApiBase {
'allmessages' => 'ApiQueryAllMessages',
'siteinfo' => 'ApiQuerySiteinfo',
'userinfo' => 'ApiQueryUserInfo',
+ 'filerepoinfo' => 'ApiQueryFileRepoInfo',
);
/**
@@ -382,7 +384,6 @@ class ApiQuery extends ApiBase {
$modules = $allModules;
$tmp = $completeModules;
$wasPosted = $this->getRequest()->wasPosted();
- $main = $this->getMain();
/** @var $module ApiQueryBase */
foreach ( $allModules as $moduleName => $module ) {
@@ -513,7 +514,7 @@ class ApiQuery extends ApiBase {
ApiQueryBase::addTitleInfo( $vals, $title );
$vals['special'] = '';
if ( $title->isSpecialPage() &&
- !SpecialPageFactory::exists( $title->getDbKey() ) ) {
+ !SpecialPageFactory::exists( $title->getDBkey() ) ) {
$vals['missing'] = '';
} elseif ( $title->getNamespace() == NS_MEDIA &&
!wfFindFile( $title ) ) {
@@ -697,7 +698,7 @@ class ApiQuery extends ApiBase {
}
public function getParamDescription() {
- return $this->getPageSet()->getParamDescription() + array(
+ return $this->getPageSet()->getFinalParamDescription() + 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',
@@ -723,7 +724,7 @@ class ApiQuery extends ApiBase {
public function getPossibleErrors() {
return array_merge(
parent::getPossibleErrors(),
- $this->getPageSet()->getPossibleErrors()
+ $this->getPageSet()->getFinalPossibleErrors()
);
}
@@ -736,6 +737,7 @@ class ApiQuery extends ApiBase {
public function getHelpUrls() {
return array(
+ 'https://www.mediawiki.org/wiki/API:Query',
'https://www.mediawiki.org/wiki/API:Meta',
'https://www.mediawiki.org/wiki/API:Properties',
'https://www.mediawiki.org/wiki/API:Lists',
diff --git a/includes/api/ApiQueryAllCategories.php b/includes/api/ApiQueryAllCategories.php
index 496a0eb8..3f5c6ee7 100644
--- a/includes/api/ApiQueryAllCategories.php
+++ b/includes/api/ApiQueryAllCategories.php
@@ -76,7 +76,7 @@ class ApiQueryAllCategories extends ApiQueryGeneratorBase {
if ( $dir == 'newer' ) {
$this->addWhereRange( 'cat_pages', 'newer', $min, $max );
} else {
- $this->addWhereRange( 'cat_pages', 'older', $max, $min);
+ $this->addWhereRange( 'cat_pages', 'older', $max, $min );
}
if ( isset( $params['prefix'] ) ) {
@@ -121,7 +121,7 @@ class ApiQueryAllCategories extends ApiQueryGeneratorBase {
$pages[] = $titleObj;
} else {
$item = array();
- $result->setContent( $item, $titleObj->getText() );
+ ApiResult::setContent( $item, $titleObj->getText() );
if ( isset( $prop['size'] ) ) {
$item['size'] = intval( $row->cat_pages );
$item['pages'] = $row->cat_pages - $row->cat_subcats - $row->cat_files;
diff --git a/includes/api/ApiQueryAllImages.php b/includes/api/ApiQueryAllImages.php
index e24b162c..ccc7a3a2 100644
--- a/includes/api/ApiQueryAllImages.php
+++ b/includes/api/ApiQueryAllImages.php
@@ -189,7 +189,7 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase {
if ( !is_null( $params['mime'] ) ) {
global $wgMiserMode;
- if ( $wgMiserMode ) {
+ if ( $wgMiserMode ) {
$this->dieUsage( 'MIME search disabled in Miser Mode', 'mimesearchdisabled' );
}
@@ -260,7 +260,7 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase {
}
public function getAllowedParams() {
- return array (
+ return array(
'sort' => array(
ApiBase::PARAM_DFLT => 'name',
ApiBase::PARAM_TYPE => array(
diff --git a/includes/api/ApiQueryAllLinks.php b/includes/api/ApiQueryAllLinks.php
index e355f8b0..47d1bcef 100644
--- a/includes/api/ApiQueryAllLinks.php
+++ b/includes/api/ApiQueryAllLinks.php
@@ -37,24 +37,41 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase {
$prefix = 'al';
$this->table = 'pagelinks';
$this->tablePrefix = 'pl_';
+ $this->fieldTitle = 'title';
$this->dfltNamespace = NS_MAIN;
+ $this->hasNamespace = true;
$this->indexTag = 'l';
$this->description = 'Enumerate all links that point to a given namespace';
- $this->descriptionLink = 'link';
- $this->descriptionLinked = 'linked';
+ $this->descriptionWhat = 'link';
+ $this->descriptionTargets = 'linked titles';
$this->descriptionLinking = 'linking';
break;
case 'alltransclusions':
$prefix = 'at';
$this->table = 'templatelinks';
$this->tablePrefix = 'tl_';
+ $this->fieldTitle = 'title';
$this->dfltNamespace = NS_TEMPLATE;
+ $this->hasNamespace = true;
$this->indexTag = 't';
$this->description = 'List all transclusions (pages embedded using {{x}}), including non-existing';
- $this->descriptionLink = 'transclusion';
- $this->descriptionLinked = 'transcluded';
+ $this->descriptionWhat = 'transclusion';
+ $this->descriptionTargets = 'transcluded titles';
$this->descriptionLinking = 'transcluding';
break;
+ case 'allfileusages':
+ $prefix = 'af';
+ $this->table = 'imagelinks';
+ $this->tablePrefix = 'il_';
+ $this->fieldTitle = 'to';
+ $this->dfltNamespace = NS_FILE;
+ $this->hasNamespace = false;
+ $this->indexTag = 'f';
+ $this->description = 'List all file usages, including non-existing';
+ $this->descriptionWhat = 'file';
+ $this->descriptionTargets = 'file titles';
+ $this->descriptionLinking = 'using';
+ break;
default:
ApiBase::dieDebug( __METHOD__, 'Unknown module name' );
}
@@ -83,21 +100,29 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase {
$params = $this->extractRequestParams();
$pfx = $this->tablePrefix;
+ $fieldTitle = $this->fieldTitle;
$prop = array_flip( $params['prop'] );
$fld_ids = isset( $prop['ids'] );
$fld_title = isset( $prop['title'] );
+ if ( $this->hasNamespace ) {
+ $namespace = $params['namespace'];
+ } else {
+ $namespace = $this->dfltNamespace;
+ }
if ( $params['unique'] ) {
if ( $fld_ids ) {
$this->dieUsage(
- "{$this->getModuleName()} cannot return corresponding page ids in unique {$this->descriptionLink}s mode",
+ "{$this->getModuleName()} cannot return corresponding page ids in unique {$this->descriptionWhat}s mode",
'params' );
}
$this->addOption( 'DISTINCT' );
}
$this->addTables( $this->table );
- $this->addWhereFld( $pfx . 'namespace', $params['namespace'] );
+ if ( $this->hasNamespace ) {
+ $this->addWhereFld( $pfx . 'namespace', $namespace );
+ }
$continue = !is_null( $params['continue'] );
if ( $continue ) {
@@ -106,14 +131,14 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase {
if ( $params['unique'] ) {
$this->dieContinueUsageIf( count( $continueArr ) != 1 );
$continueTitle = $db->addQuotes( $continueArr[0] );
- $this->addWhere( "{$pfx}title $op= $continueTitle" );
+ $this->addWhere( "{$pfx}{$fieldTitle} $op= $continueTitle" );
} else {
$this->dieContinueUsageIf( count( $continueArr ) != 2 );
$continueTitle = $db->addQuotes( $continueArr[0] );
$continueFrom = intval( $continueArr[1] );
$this->addWhere(
- "{$pfx}title $op $continueTitle OR " .
- "({$pfx}title = $continueTitle AND " .
+ "{$pfx}{$fieldTitle} $op $continueTitle OR " .
+ "({$pfx}{$fieldTitle} = $continueTitle AND " .
"{$pfx}from $op= $continueFrom)"
);
}
@@ -122,22 +147,24 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase {
// '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( $pfx . 'title', 'newer', $from, $to );
+ $this->addWhereRange( $pfx . $fieldTitle, 'newer', $from, $to );
if ( isset( $params['prefix'] ) ) {
- $this->addWhere( $pfx . 'title' . $db->buildLike( $this->titlePartToKey( $params['prefix'] ), $db->anyString() ) );
+ $this->addWhere( $pfx . $fieldTitle . $db->buildLike( $this->titlePartToKey( $params['prefix'] ), $db->anyString() ) );
}
- $this->addFields( array( 'pl_title' => $pfx . 'title' ) );
+ $this->addFields( array( 'pl_title' => $pfx . $fieldTitle ) );
$this->addFieldsIf( array( 'pl_from' => $pfx . 'from' ), !$params['unique'] );
- $this->addOption( 'USE INDEX', $pfx . 'namespace' );
+ if ( $this->hasNamespace ) {
+ $this->addOption( 'USE INDEX', $pfx . 'namespace' );
+ }
$limit = $params['limit'];
$this->addOption( 'LIMIT', $limit + 1 );
$sort = ( $params['dir'] == 'descending' ? ' DESC' : '' );
$orderBy = array();
- $orderBy[] = $pfx . 'title' . $sort;
+ $orderBy[] = $pfx . $fieldTitle . $sort;
if ( !$params['unique'] ) {
$orderBy[] = $pfx . 'from' . $sort;
}
@@ -166,7 +193,7 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase {
$vals['fromid'] = intval( $row->pl_from );
}
if ( $fld_title ) {
- $title = Title::makeTitle( $params['namespace'], $row->pl_title );
+ $title = Title::makeTitle( $namespace, $row->pl_title );
ApiQueryBase::addTitleInfo( $vals, $title );
}
$fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $vals );
@@ -179,7 +206,7 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase {
break;
}
} elseif ( $params['unique'] ) {
- $titles[] = Title::makeTitle( $params['namespace'], $row->pl_title );
+ $titles[] = Title::makeTitle( $namespace, $row->pl_title );
} else {
$pageids[] = $row->pl_from;
}
@@ -195,7 +222,7 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase {
}
public function getAllowedParams() {
- return array(
+ $allowedParams = array(
'continue' => null,
'from' => null,
'to' => null,
@@ -228,30 +255,39 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase {
)
),
);
+ if ( !$this->hasNamespace ) {
+ unset( $allowedParams['namespace'] );
+ }
+ return $allowedParams;
}
public function getParamDescription() {
$p = $this->getModulePrefix();
- $link = $this->descriptionLink;
+ $what = $this->descriptionWhat;
+ $targets = $this->descriptionTargets;
$linking = $this->descriptionLinking;
- return array(
- '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",
+ $paramDescription = array(
+ 'from' => "The title of the $what to start enumerating from",
+ 'to' => "The title of the $what to stop enumerating at",
+ 'prefix' => "Search for all $targets 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.',
+ "Only show distinct $targets. 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 the pageid of the $linking page (Cannot be used with {$p}unique)",
- " title - Adds the title of the $link",
+ " title - Adds the title of the $what",
),
'namespace' => 'The namespace to enumerate',
- 'limit' => "How many total items 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',
);
+ if ( !$this->hasNamespace ) {
+ unset( $paramDescription['namespace'] );
+ }
+ return $paramDescription;
}
public function getResultProperties() {
@@ -272,29 +308,31 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase {
public function getPossibleErrors() {
$m = $this->getModuleName();
- $link = $this->descriptionLink;
+ $what = $this->descriptionWhat;
return array_merge( parent::getPossibleErrors(), array(
- array( 'code' => 'params', 'info' => "{$m} cannot return corresponding page ids in unique {$link}s mode" ),
+ array( 'code' => 'params', 'info' => "{$m} cannot return corresponding page ids in unique {$what}s mode" ),
) );
}
public function getExamples() {
$p = $this->getModulePrefix();
- $link = $this->descriptionLink;
- $linked = $this->descriptionLinked;
+ $name = $this->getModuleName();
+ $what = $this->descriptionWhat;
+ $targets = $this->descriptionTargets;
return array(
- "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",
+ "api.php?action=query&list={$name}&{$p}from=B&{$p}prop=ids|title"
+ => "List $targets with page ids they are from, including missing ones. Start at B",
+ "api.php?action=query&list={$name}&{$p}unique=&{$p}from=B"
+ => "List unique $targets",
+ "api.php?action=query&generator={$name}&g{$p}unique=&g{$p}from=B"
+ => "Gets all $targets, marking the missing ones",
+ "api.php?action=query&generator={$name}&g{$p}from=B"
+ => "Gets pages containing the {$what}s",
);
}
public function getHelpUrls() {
- return "https://www.mediawiki.org/wiki/API:All{$this->descriptionLink}s";
+ $name = ucfirst( $this->getModuleName() );
+ return "https://www.mediawiki.org/wiki/API:{$name}";
}
}
diff --git a/includes/api/ApiQueryAllMessages.php b/includes/api/ApiQueryAllMessages.php
index c9811b0d..d47c7b76 100644
--- a/includes/api/ApiQueryAllMessages.php
+++ b/includes/api/ApiQueryAllMessages.php
@@ -87,7 +87,7 @@ class ApiQueryAllMessages extends ApiQueryBase {
foreach ( $messages_target as $message ) {
// === 0: must be at beginning of string (position 0)
if ( strpos( $message, $params['prefix'] ) === 0 ) {
- if( !$skip ) {
+ if ( !$skip ) {
$skip = true;
}
$messages_filtered[] = $message;
diff --git a/includes/api/ApiQueryAllPages.php b/includes/api/ApiQueryAllPages.php
index d718b967..d95980c2 100644
--- a/includes/api/ApiQueryAllPages.php
+++ b/includes/api/ApiQueryAllPages.php
@@ -174,7 +174,7 @@ class ApiQueryAllPages extends ApiQueryGeneratorBase {
$res = $this->select( __METHOD__ );
//Get gender information
- if( MWNamespace::hasGenderDistinction( $params['namespace'] ) ) {
+ if ( MWNamespace::hasGenderDistinction( $params['namespace'] ) ) {
$users = array();
foreach ( $res as $row ) {
$users[] = $row->page_title;
@@ -304,7 +304,10 @@ class ApiQueryAllPages extends ApiQueryGeneratorBase {
'prtype' => 'Limit to protected pages only',
'prlevel' => "The protection level (must be used with {$p}prtype= parameter)",
'prfiltercascade' => "Filter protections based on cascadingness (ignored when {$p}prtype isn't set)",
- 'filterlanglinks' => 'Filter based on whether a page has langlinks',
+ 'filterlanglinks' => array(
+ 'Filter based on whether a page has langlinks',
+ 'Note that this may not consider langlinks added by extensions.',
+ ),
'limit' => 'How many total pages to return.',
'prexpiry' => array(
'Which protection expiry to filter the page on',
diff --git a/includes/api/ApiQueryAllUsers.php b/includes/api/ApiQueryAllUsers.php
index 7283aa00..1948a51a 100644
--- a/includes/api/ApiQueryAllUsers.php
+++ b/includes/api/ApiQueryAllUsers.php
@@ -83,12 +83,12 @@ class ApiQueryAllUsers extends ApiQueryBase {
if ( !is_null( $params['rights'] ) && count( $params['rights'] ) ) {
$groups = array();
- foreach( $params['rights'] as $r ) {
+ 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 ) ) {
+ if ( !count( $groups ) ) {
$this->getResult()->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), '' );
return;
}
diff --git a/includes/api/ApiQueryBacklinks.php b/includes/api/ApiQueryBacklinks.php
index 3ef6b840..2d1089a7 100644
--- a/includes/api/ApiQueryBacklinks.php
+++ b/includes/api/ApiQueryBacklinks.php
@@ -229,10 +229,10 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
$orderBy = array();
$sort = ( $this->params['dir'] == 'descending' ? ' DESC' : '' );
// Don't order by namespace/title if it's constant in the WHERE clause
- if( $this->hasNS && count( array_unique( $allRedirNs ) ) != 1 ) {
+ if ( $this->hasNS && count( array_unique( $allRedirNs ) ) != 1 ) {
$orderBy[] = $this->bl_ns . $sort;
}
- if( count( array_unique( $allRedirDBkey ) ) != 1 ) {
+ if ( count( array_unique( $allRedirDBkey ) ) != 1 ) {
$orderBy[] = $this->bl_title . $sort;
}
$orderBy[] = $this->bl_from . $sort;
@@ -255,6 +255,9 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
if ( $this->params['limit'] == 'max' ) {
$this->params['limit'] = $this->getMain()->canApiHighLimits() ? $botMax : $userMax;
$result->setParsedLimit( $this->getModuleName(), $this->params['limit'] );
+ } else {
+ $this->params['limit'] = intval( $this->params['limit'] );
+ $this->validateLimit( 'limit', $this->params['limit'], 1, $userMax, $botMax );
}
$this->processContinue();
@@ -294,9 +297,9 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
// We've reached the one extra which shows that there are additional pages to be had. Stop here...
// We need to keep the parent page of this redir in
if ( $this->hasNS ) {
- $parentID = $this->pageMap[$row-> { $this->bl_ns } ][$row-> { $this->bl_title } ];
+ $parentID = $this->pageMap[$row->{$this->bl_ns}][$row->{$this->bl_title}];
} else {
- $parentID = $this->pageMap[NS_FILE][$row-> { $this->bl_title } ];
+ $parentID = $this->pageMap[NS_FILE][$row->{$this->bl_title}];
}
$this->continueStr = $this->getContinueRedirStr( $parentID, $row->page_id );
break;
@@ -377,8 +380,8 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
if ( $row->page_is_redirect ) {
$a['redirect'] = '';
}
- $ns = $this->hasNS ? $row-> { $this->bl_ns } : NS_FILE;
- $parentID = $this->pageMap[$ns][$row-> { $this->bl_title } ];
+ $ns = $this->hasNS ? $row->{$this->bl_ns} : NS_FILE;
+ $parentID = $this->pageMap[$ns][$row->{$this->bl_title}];
// Put all the results in an array first
$this->resultArr[$parentID]['redirlinks'][] = $a;
$this->getResult()->setIndexedTagName( $this->resultArr[$parentID]['redirlinks'], $this->bl_code );
diff --git a/includes/api/ApiQueryBase.php b/includes/api/ApiQueryBase.php
index 7819ead4..8668e04b 100644
--- a/includes/api/ApiQueryBase.php
+++ b/includes/api/ApiQueryBase.php
@@ -432,7 +432,7 @@ abstract class ApiQueryBase extends ApiBase {
if ( trim( $key ) == '' ) {
return '';
}
- $t = Title::newFromDbKey( $key );
+ $t = Title::newFromDBkey( $key );
// This really shouldn't happen but we gotta check anyway
if ( !$t ) {
$this->dieUsageMsg( array( 'invalidtitle', $key ) );
@@ -478,7 +478,7 @@ abstract class ApiQueryBase extends ApiBase {
* @param $protocol String
* @return null|string
*/
- public function prepareUrlQuerySearchString( $query = null, $protocol = null) {
+ public function prepareUrlQuerySearchString( $query = null, $protocol = null ) {
$db = $this->getDb();
if ( !is_null( $query ) || $query != '' ) {
if ( is_null( $protocol ) ) {
diff --git a/includes/api/ApiQueryBlocks.php b/includes/api/ApiQueryBlocks.php
index d9be9f28..e3c27f5e 100644
--- a/includes/api/ApiQueryBlocks.php
+++ b/includes/api/ApiQueryBlocks.php
@@ -63,7 +63,7 @@ class ApiQueryBlocks extends ApiQueryBase {
$this->addTables( 'ipblocks' );
$this->addFields( 'ipb_auto' );
- $this->addFieldsIf ( 'ipb_id', $fld_id );
+ $this->addFieldsIf( 'ipb_id', $fld_id );
$this->addFieldsIf( array( 'ipb_address', 'ipb_user' ), $fld_user || $fld_userid );
$this->addFieldsIf( 'ipb_by_text', $fld_by );
$this->addFieldsIf( 'ipb_by', $fld_byid );
@@ -91,17 +91,30 @@ class ApiQueryBlocks extends ApiQueryBase {
$this->addWhereFld( 'ipb_auto', 0 );
}
if ( isset( $params['ip'] ) ) {
- list( $ip, $range ) = IP::parseCIDR( $params['ip'] );
- if ( $ip && $range ) {
- // We got a CIDR range
- if ( $range < 16 )
- $this->dieUsage( 'CIDR ranges broader than /16 are not accepted', 'cidrtoobroad' );
- $lower = wfBaseConvert( $ip, 10, 16, 8, false );
- $upper = wfBaseConvert( $ip + pow( 2, 32 - $range ) - 1, 10, 16, 8, false );
+ global $wgBlockCIDRLimit;
+ if ( IP::isIPv4( $params['ip'] ) ) {
+ $type = 'IPv4';
+ $cidrLimit = $wgBlockCIDRLimit['IPv4'];
+ $prefixLen = 0;
+ } elseif ( IP::isIPv6( $params['ip'] ) ) {
+ $type = 'IPv6';
+ $cidrLimit = $wgBlockCIDRLimit['IPv6'];
+ $prefixLen = 3; // IP::toHex output is prefixed with "v6-"
} else {
- $lower = $upper = IP::toHex( $params['ip'] );
+ $this->dieUsage( 'IP parameter is not valid', 'param_ip' );
}
- $prefix = substr( $lower, 0, 4 );
+
+ # Check range validity, if it's a CIDR
+ list( $ip, $range ) = IP::parseCIDR( $params['ip'] );
+ if ( $ip !== false && $range !== false && $range < $cidrLimit ) {
+ $this->dieUsage( "$type CIDR ranges broader than /$cidrLimit are not accepted", 'cidrtoobroad' );
+ }
+
+ # Let IP::parseRange handle calculating $upper, instead of duplicating the logic here.
+ list( $lower, $upper ) = IP::parseRange( $params['ip'] );
+
+ # Extract the common prefix to any rangeblock affecting this IP/CIDR
+ $prefix = substr( $lower, 0, $prefixLen + floor( $cidrLimit / 4 ) );
# Fairly hard to make a malicious SQL statement out of hex characters,
# but it is good practice to add quotes
@@ -120,10 +133,10 @@ class ApiQueryBlocks extends ApiQueryBase {
$show = array_flip( $params['show'] );
/* Check for conflicting parameters. */
- if ( ( isset ( $show['account'] ) && isset ( $show['!account'] ) )
- || ( isset ( $show['ip'] ) && isset ( $show['!ip'] ) )
- || ( isset ( $show['range'] ) && isset ( $show['!range'] ) )
- || ( isset ( $show['temp'] ) && isset ( $show['!temp'] ) )
+ if ( ( isset( $show['account'] ) && isset( $show['!account'] ) )
+ || ( isset( $show['ip'] ) && isset( $show['!ip'] ) )
+ || ( isset( $show['range'] ) && isset( $show['!range'] ) )
+ || ( isset( $show['temp'] ) && isset( $show['!temp'] ) )
) {
$this->dieUsageMsg( 'show' );
}
@@ -294,6 +307,7 @@ class ApiQueryBlocks extends ApiQueryBase {
}
public function getParamDescription() {
+ global $wgBlockCIDRLimit;
$p = $this->getModulePrefix();
return array(
'start' => 'The timestamp to start enumerating from',
@@ -301,8 +315,12 @@ 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.',
- 'Cannot be used together with bkusers. CIDR ranges broader than /16 are not accepted' ),
+ '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 " .
+ "IPv4/{$wgBlockCIDRLimit['IPv4']} or IPv6/{$wgBlockCIDRLimit['IPv6']} " .
+ "are not accepted"
+ ),
'limit' => 'The maximum amount of blocks to list',
'prop' => array(
'Which properties to get',
@@ -383,10 +401,19 @@ class ApiQueryBlocks extends ApiQueryBase {
}
public function getPossibleErrors() {
+ global $wgBlockCIDRLimit;
return array_merge( parent::getPossibleErrors(),
$this->getRequireOnlyOneParameterErrorMessages( array( 'users', 'ip' ) ),
array(
- array( 'code' => 'cidrtoobroad', 'info' => 'CIDR ranges broader than /16 are not accepted' ),
+ array(
+ 'code' => 'cidrtoobroad',
+ 'info' => "IPv4 CIDR ranges broader than /{$wgBlockCIDRLimit['IPv4']} are not accepted"
+ ),
+ array(
+ 'code' => 'cidrtoobroad',
+ 'info' => "IPv6 CIDR ranges broader than /{$wgBlockCIDRLimit['IPv6']} are not accepted"
+ ),
+ array( 'code' => 'param_ip', 'info' => 'IP parameter is not valid' ),
array( 'code' => 'param_user', 'info' => 'User parameter may not be empty' ),
array( 'code' => 'param_user', 'info' => 'User name user is not valid' ),
array( 'show' ),
diff --git a/includes/api/ApiQueryCategories.php b/includes/api/ApiQueryCategories.php
index 69a64415..5d714f57 100644
--- a/includes/api/ApiQueryCategories.php
+++ b/includes/api/ApiQueryCategories.php
@@ -49,7 +49,6 @@ class ApiQueryCategories extends ApiQueryGeneratorBase {
/**
* @param $resultPageSet ApiPageSet
- * @return
*/
private function run( $resultPageSet = null ) {
if ( $this->getPageSet()->getGoodTitleCount() == 0 ) {
@@ -174,7 +173,7 @@ class ApiQueryCategories extends ApiQueryGeneratorBase {
break;
}
- $titles[] = Title :: makeTitle( NS_CATEGORY, $row->cl_to );
+ $titles[] = Title::makeTitle( NS_CATEGORY, $row->cl_to );
}
$resultPageSet->populateFromTitles( $titles );
}
@@ -184,7 +183,7 @@ class ApiQueryCategories extends ApiQueryGeneratorBase {
return array(
'prop' => array(
ApiBase::PARAM_ISMULTI => true,
- ApiBase::PARAM_TYPE => array (
+ ApiBase::PARAM_TYPE => array(
'sortkey',
'timestamp',
'hidden',
diff --git a/includes/api/ApiQueryCategoryMembers.php b/includes/api/ApiQueryCategoryMembers.php
index 9dbd8593..704d108a 100644
--- a/includes/api/ApiQueryCategoryMembers.php
+++ b/includes/api/ApiQueryCategoryMembers.php
@@ -217,7 +217,7 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
if ( $fld_sortkeyprefix ) {
$vals['sortkeyprefix'] = $row->cl_sortkey_prefix;
}
- if ( $fld_type ) {
+ if ( $fld_type ) {
$vals['type'] = $row->cl_type;
}
if ( $fld_timestamp ) {
@@ -258,7 +258,7 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
'prop' => array(
ApiBase::PARAM_DFLT => 'ids|title',
ApiBase::PARAM_ISMULTI => true,
- ApiBase::PARAM_TYPE => array (
+ ApiBase::PARAM_TYPE => array(
'ids',
'title',
'sortkey',
@@ -267,7 +267,7 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
'timestamp',
)
),
- 'namespace' => array (
+ 'namespace' => array(
ApiBase::PARAM_ISMULTI => true,
ApiBase::PARAM_TYPE => 'namespace',
),
diff --git a/includes/api/ApiQueryDeletedrevs.php b/includes/api/ApiQueryDeletedrevs.php
index 890e4ecf..82733133 100644
--- a/includes/api/ApiQueryDeletedrevs.php
+++ b/includes/api/ApiQueryDeletedrevs.php
@@ -50,7 +50,7 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
$fld_user = isset( $prop['user'] );
$fld_userid = isset( $prop['userid'] );
$fld_comment = isset( $prop['comment'] );
- $fld_parsedcomment = isset ( $prop['parsedcomment'] );
+ $fld_parsedcomment = isset( $prop['parsedcomment'] );
$fld_minor = isset( $prop['minor'] );
$fld_len = isset( $prop['len'] );
$fld_sha1 = isset( $prop['sha1'] );
@@ -79,13 +79,13 @@ 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', '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' );
}
}
} else {
- foreach( array( 'start', 'end' ) as $p ) {
+ foreach ( array( 'start', 'end' ) as $p ) {
if ( !is_null( $params[$p] ) ) {
$this->dieUsage( "The {$p} parameter cannot be used in mode 3", 'badparams' );
}
diff --git a/includes/api/ApiQueryDuplicateFiles.php b/includes/api/ApiQueryDuplicateFiles.php
index 18dcba85..0311fa7f 100644
--- a/includes/api/ApiQueryDuplicateFiles.php
+++ b/includes/api/ApiQueryDuplicateFiles.php
@@ -48,8 +48,7 @@ class ApiQueryDuplicateFiles extends ApiQueryGeneratorBase {
}
/**
- * @param $resultPageSet ApiPageSet
- * @return
+ * @param ApiPageSet $resultPageSet
*/
private function run( $resultPageSet = null ) {
$params = $this->extractRequestParams();
@@ -59,7 +58,7 @@ class ApiQueryDuplicateFiles extends ApiQueryGeneratorBase {
}
$images = $namespaces[NS_FILE];
- if( $params['dir'] == 'descending' ) {
+ if ( $params['dir'] == 'descending' ) {
$images = array_reverse( $images );
}
@@ -80,7 +79,7 @@ class ApiQueryDuplicateFiles extends ApiQueryGeneratorBase {
}
$filesToFind = array_keys( $images );
- if( $params['localonly'] ) {
+ if ( $params['localonly'] ) {
$files = RepoGroup::singleton()->getLocalRepo()->findFiles( $filesToFind );
} else {
$files = RepoGroup::singleton()->findFiles( $filesToFind );
@@ -98,29 +97,29 @@ class ApiQueryDuplicateFiles extends ApiQueryGeneratorBase {
// find all files with the hashes, result format is: array( hash => array( dup1, dup2 ), hash1 => ... )
$filesToFindBySha1s = array_unique( array_values( $sha1s ) );
- if( $params['localonly'] ) {
+ if ( $params['localonly'] ) {
$filesBySha1s = RepoGroup::singleton()->getLocalRepo()->findBySha1s( $filesToFindBySha1s );
} else {
$filesBySha1s = RepoGroup::singleton()->findBySha1s( $filesToFindBySha1s );
}
// iterate over $images to handle continue param correct
- foreach( $images as $image => $pageId ) {
- if( !isset( $sha1s[$image] ) ) {
+ foreach ( $images as $image => $pageId ) {
+ if ( !isset( $sha1s[$image] ) ) {
continue; //file does not exist
}
$sha1 = $sha1s[$image];
$dupFiles = $filesBySha1s[$sha1];
- if( $params['dir'] == 'descending' ) {
+ if ( $params['dir'] == 'descending' ) {
$dupFiles = array_reverse( $dupFiles );
}
/** @var $dupFile File */
foreach ( $dupFiles as $dupFile ) {
$dupName = $dupFile->getName();
- if( $image == $dupName && $dupFile->isLocal() ) {
+ if ( $image == $dupName && $dupFile->isLocal() ) {
continue; //ignore the local file itself
}
- if( $skipUntilThisDup !== false && $dupName < $skipUntilThisDup ) {
+ if ( $skipUntilThisDup !== false && $dupName < $skipUntilThisDup ) {
continue; //skip to pos after the image from continue param
}
$skipUntilThisDup = false;
@@ -139,7 +138,7 @@ class ApiQueryDuplicateFiles extends ApiQueryGeneratorBase {
'user' => $dupFile->getUser( 'text' ),
'timestamp' => wfTimestamp( TS_ISO_8601, $dupFile->getTimestamp() )
);
- if( !$dupFile->isLocal() ) {
+ if ( !$dupFile->isLocal() ) {
$r['shared'] = '';
}
$fit = $this->addPageSubItem( $pageId, $r );
@@ -149,7 +148,7 @@ class ApiQueryDuplicateFiles extends ApiQueryGeneratorBase {
}
}
}
- if( !$fit ) {
+ if ( !$fit ) {
break;
}
}
diff --git a/includes/api/ApiQueryExtLinksUsage.php b/includes/api/ApiQueryExtLinksUsage.php
index eb9cdf9e..456e87ba 100644
--- a/includes/api/ApiQueryExtLinksUsage.php
+++ b/includes/api/ApiQueryExtLinksUsage.php
@@ -123,7 +123,7 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase {
if ( $fld_url ) {
$to = $row->el_to;
// expand protocol-relative urls
- if( $params['expandurl'] ) {
+ if ( $params['expandurl'] ) {
$to = wfExpandUrl( $to, PROTO_CANONICAL );
}
$vals['url'] = $to;
@@ -218,13 +218,13 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase {
),
'offset' => 'Used for paging. Use the value returned for "continue"',
'protocol' => array(
- "Protocol of the url. If empty and {$p}query set, the protocol is http.",
+ "Protocol of the URL. If empty and {$p}query set, the protocol is http.",
"Leave both this and {$p}query empty to list all external links"
),
'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.',
- 'expandurl' => 'Expand protocol-relative urls with the canonical protocol',
+ 'expandurl' => 'Expand protocol-relative URLs with the canonical protocol',
);
if ( $wgMiserMode ) {
diff --git a/includes/api/ApiQueryExternalLinks.php b/includes/api/ApiQueryExternalLinks.php
index 761b49ea..583ef697 100644
--- a/includes/api/ApiQueryExternalLinks.php
+++ b/includes/api/ApiQueryExternalLinks.php
@@ -88,7 +88,7 @@ class ApiQueryExternalLinks extends ApiQueryBase {
$entry = array();
$to = $row->el_to;
// expand protocol-relative urls
- if( $params['expandurl'] ) {
+ if ( $params['expandurl'] ) {
$to = wfExpandUrl( $to, PROTO_CANONICAL );
}
ApiResult::setContent( $entry, $to );
@@ -131,11 +131,11 @@ class ApiQueryExternalLinks extends ApiQueryBase {
'limit' => 'How many links to return',
'offset' => 'When more results are available, use this to continue',
'protocol' => array(
- "Protocol of the url. If empty and {$p}query set, the protocol is http.",
+ "Protocol of the URL. If empty and {$p}query set, the protocol is http.",
"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',
+ 'expandurl' => 'Expand protocol-relative URLs with the canonical protocol',
);
}
@@ -148,7 +148,7 @@ class ApiQueryExternalLinks extends ApiQueryBase {
}
public function getDescription() {
- return 'Returns all external urls (not interwikis) from the given page(s)';
+ return 'Returns all external URLs (not interwikis) from the given page(s)';
}
public function getPossibleErrors() {
diff --git a/includes/api/ApiQueryFileRepoInfo.php b/includes/api/ApiQueryFileRepoInfo.php
new file mode 100644
index 00000000..3a353533
--- /dev/null
+++ b/includes/api/ApiQueryFileRepoInfo.php
@@ -0,0 +1,115 @@
+<?php
+/**
+ * Copyright © 2013 Mark Holmquist <mtraceur@member.fsf.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.22
+ */
+
+/**
+ * A query action to return meta information about the foreign file repos
+ * configured on the wiki.
+ *
+ * @ingroup API
+ */
+class ApiQueryFileRepoInfo extends ApiQueryBase {
+
+ public function __construct( $query, $moduleName ) {
+ parent::__construct( $query, $moduleName, 'fri' );
+ }
+
+ protected function getInitialisedRepoGroup() {
+ $repoGroup = RepoGroup::singleton();
+
+ if ( !$repoGroup->reposInitialised ) {
+ $repoGroup->initialiseRepos();
+ }
+
+ return $repoGroup;
+ }
+
+ public function execute() {
+ $params = $this->extractRequestParams();
+ $props = array_flip( $params['prop'] );
+
+ $repos = array();
+
+ $repoGroup = $this->getInitialisedRepoGroup();
+
+ $repoGroup->forEachForeignRepo( function ( $repo ) use ( &$repos, $props ) {
+ $repos[] = array_intersect_key( $repo->getInfo(), $props );
+ } );
+
+ $repos[] = array_intersect_key( $repoGroup->localRepo->getInfo(), $props );
+
+ $result = $this->getResult();
+ $result->setIndexedTagName( $repos, 'repo' );
+ $result->addValue( array( 'query' ), 'repos', $repos );
+ }
+
+ public function getCacheMode( $params ) {
+ return 'public';
+ }
+
+ public function getAllowedParams() {
+ $props = $this->getProps();
+
+ return array(
+ 'prop' => array(
+ ApiBase::PARAM_DFLT => join( '|', $props ),
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_TYPE => $props,
+ ),
+ );
+ }
+
+ public function getProps() {
+ $props = array();
+ $repoGroup = $this->getInitialisedRepoGroup();
+
+ $repoGroup->forEachForeignRepo( function ( $repo ) use ( &$props ) {
+ $props = array_merge( $props, array_keys( $repo->getInfo() ) );
+ } );
+
+ return array_values( array_unique( array_merge( $props, array_keys( $repoGroup->localRepo->getInfo() ) ) ) );
+ }
+
+ public function getParamDescription() {
+ $p = $this->getModulePrefix();
+ return array(
+ 'prop' => array(
+ 'Which repository properties to get (there may be more available on some wikis):',
+ ' apiurl - URL to the repository API - helpful for getting image info from the host.',
+ ' name - The key of the repository - used in e.g. $wgForeignFileRepos and imageinfo return values.',
+ ' displayname - The human-readable name of the repository wiki.',
+ ' rooturl - Root URL for image paths.',
+ ' local - Whether that repository is the local one or not.',
+ ),
+ );
+ }
+
+ public function getDescription() {
+ return 'Return meta information about image repositories configured on the wiki.';
+ }
+
+ public function getExamples() {
+ return array(
+ 'api.php?action=query&meta=filerepoinfo&friprop=apiurl|name|displayname',
+ );
+ }
+}
diff --git a/includes/api/ApiQueryFilearchive.php b/includes/api/ApiQueryFilearchive.php
index 021074a9..f53cd386 100644
--- a/includes/api/ApiQueryFilearchive.php
+++ b/includes/api/ApiQueryFilearchive.php
@@ -218,7 +218,7 @@ class ApiQueryFilearchive extends ApiQueryBase {
}
public function getAllowedParams() {
- return array (
+ return array(
'from' => null,
'continue' => null,
'to' => null,
@@ -281,7 +281,7 @@ class ApiQueryFilearchive extends ApiQueryBase {
' parseddescription - Parse the description on the version',
' mime - Adds MIME of the image',
' mediatype - Adds the media type of the image',
- ' metadata - Lists EXIF metadata for the version of the image',
+ ' metadata - Lists Exif metadata for the version of the image',
' bitdepth - Adds the bit depth of the version',
' archivename - Adds the file name of the archive version for non-latest versions'
),
@@ -373,4 +373,8 @@ class ApiQueryFilearchive extends ApiQueryBase {
),
);
}
+
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Filearchive';
+ }
}
diff --git a/includes/api/ApiQueryIWBacklinks.php b/includes/api/ApiQueryIWBacklinks.php
index b47d31f2..ebae3e76 100644
--- a/includes/api/ApiQueryIWBacklinks.php
+++ b/includes/api/ApiQueryIWBacklinks.php
@@ -239,4 +239,8 @@ class ApiQueryIWBacklinks extends ApiQueryGeneratorBase {
'api.php?action=query&generator=iwbacklinks&giwbltitle=Test&giwblprefix=wikibooks&prop=info'
);
}
+
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Iwbacklinks';
+ }
}
diff --git a/includes/api/ApiQueryIWLinks.php b/includes/api/ApiQueryIWLinks.php
index fc77b4e6..be539311 100644
--- a/includes/api/ApiQueryIWLinks.php
+++ b/includes/api/ApiQueryIWLinks.php
@@ -81,8 +81,8 @@ class ApiQueryIWLinks extends ApiQueryBase {
$this->addOption( 'ORDER BY', 'iwl_from' . $sort );
} else {
$this->addOption( 'ORDER BY', array(
- 'iwl_title' . $sort,
- 'iwl_from' . $sort
+ 'iwl_from' . $sort,
+ 'iwl_title' . $sort
));
}
} else {
@@ -90,9 +90,10 @@ class ApiQueryIWLinks extends ApiQueryBase {
if ( count( $this->getPageSet()->getGoodTitles() ) == 1 ) {
$this->addOption( 'ORDER BY', 'iwl_prefix' . $sort );
} else {
- $this->addOption( 'ORDER BY', array (
+ $this->addOption( 'ORDER BY', array(
'iwl_from' . $sort,
- 'iwl_prefix' . $sort
+ 'iwl_prefix' . $sort,
+ 'iwl_title' . $sort
));
}
}
@@ -192,4 +193,8 @@ class ApiQueryIWLinks extends ApiQueryBase {
'api.php?action=query&prop=iwlinks&titles=Main%20Page' => 'Get interwiki links from the [[Main Page]]',
);
}
+
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Iwlinks';
+ }
}
diff --git a/includes/api/ApiQueryImageInfo.php b/includes/api/ApiQueryImageInfo.php
index 95c2745a..0ea28684 100644
--- a/includes/api/ApiQueryImageInfo.php
+++ b/includes/api/ApiQueryImageInfo.php
@@ -72,7 +72,7 @@ class ApiQueryImageInfo extends ApiQueryBase {
$result = $this->getResult();
//search only inside the local repo
- if( $params['localonly'] ) {
+ if ( $params['localonly'] ) {
$images = RepoGroup::singleton()->getLocalRepo()->findFiles( $titles );
} else {
$images = RepoGroup::singleton()->findFiles( $titles );
@@ -82,12 +82,17 @@ class ApiQueryImageInfo extends ApiQueryBase {
$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;
+ if ( isset( $prop['uploadwarning'] ) ) {
+ // Uploadwarning needs info about non-existing files
+ $images[$title] = wfLocalFile( $title );
+ } else {
+ $result->addValue(
+ array( 'query', 'pages', intval( $pageId ) ),
+ 'imagerepository', ''
+ );
+ // The above can't fail because it doesn't increase the result size
+ continue;
+ }
}
/** @var $img File */
@@ -170,9 +175,12 @@ class ApiQueryImageInfo extends ApiQueryBase {
}
break;
}
- $fit = $this->addPageSubItem( $pageId,
- self::getInfo( $oldie, $prop, $result,
- $finalThumbParams, $params['metadataversion'] ) );
+ $fit = self::getTransformCount() < self::TRANSFORM_LIMIT &&
+ $this->addPageSubItem( $pageId,
+ self::getInfo( $oldie, $prop, $result,
+ $finalThumbParams, $params['metadataversion']
+ )
+ );
if ( !$fit ) {
if ( count( $pageIds[NS_FILE] ) == 1 ) {
$this->setContinueEnumParameter( 'start',
@@ -199,21 +207,20 @@ class ApiQueryImageInfo extends ApiQueryBase {
public function getScale( $params ) {
$p = $this->getModulePrefix();
- // Height and width.
- if ( $params['urlheight'] != -1 && $params['urlwidth'] == -1 ) {
- $this->dieUsage( "{$p}urlheight cannot be used without {$p}urlwidth", "{$p}urlwidth" );
- }
-
if ( $params['urlwidth'] != -1 ) {
$scale = array();
$scale['width'] = $params['urlwidth'];
$scale['height'] = $params['urlheight'];
+ } elseif ( $params['urlheight'] != -1 ) {
+ // Height is specified but width isn't
+ // Don't set $scale['width']; this signals mergeThumbParams() to fill it with the image's width
+ $scale = array();
+ $scale['height'] = $params['urlheight'];
} else {
$scale = null;
if ( $params['urlparam'] ) {
$this->dieUsage( "{$p}urlparam requires {$p}urlwidth", "urlparam_no_width" );
}
- return $scale;
}
return $scale;
@@ -228,7 +235,21 @@ class ApiQueryImageInfo extends ApiQueryBase {
* @param string $otherParams of otherParams (iiurlparam).
* @return Array of parameters for transform.
*/
- protected function mergeThumbParams ( $image, $thumbParams, $otherParams ) {
+ protected function mergeThumbParams( $image, $thumbParams, $otherParams ) {
+ global $wgThumbLimits;
+
+ if ( !isset( $thumbParams['width'] ) && isset( $thumbParams['height'] ) ) {
+ // We want to limit only by height in this situation, so pass the
+ // image's full width as the limiting width. But some file types
+ // don't have a width of their own, so pick something arbitrary so
+ // thumbnailing the default icon works.
+ if ( $image->getWidth() <= 0 ) {
+ $thumbParams['width'] = max( $wgThumbLimits );
+ } else {
+ $thumbParams['width'] = $image->getWidth();
+ }
+ }
+
if ( !$otherParams ) {
return $thumbParams;
}
@@ -254,8 +275,8 @@ class ApiQueryImageInfo extends ApiQueryBase {
if ( isset( $paramList['width'] ) ) {
if ( intval( $paramList['width'] ) != intval( $thumbParams['width'] ) ) {
- $this->dieUsage( "{$p}urlparam had width of {$paramList['width']} but "
- . "{$p}urlwidth was {$thumbParams['width']}", "urlparam_urlwidth_mismatch" );
+ $this->setWarning( "Ignoring width value set in {$p}urlparam ({$paramList['width']}) "
+ . "in favor of width value derived from {$p}urlwidth/{$p}urlheight ({$thumbParams['width']})" );
}
}
@@ -342,6 +363,7 @@ class ApiQueryImageInfo extends ApiQueryBase {
$mediatype = isset( $prop['mediatype'] );
$archive = isset( $prop['archivename'] );
$bitdepth = isset( $prop['bitdepth'] );
+ $uploadwarning = isset( $prop['uploadwarning'] );
if ( ( $url || $sha1 || $meta || $mime || $mediatype || $archive || $bitdepth )
&& $file->isDeleted( File::DELETED_FILE ) ) {
@@ -411,6 +433,10 @@ class ApiQueryImageInfo extends ApiQueryBase {
$vals['bitdepth'] = $file->getBitDepth();
}
+ if ( $uploadwarning ) {
+ $vals['html'] = SpecialUpload::getExistsWarning( UploadBase::getExistsWarning( $file ) );
+ }
+
return $vals;
}
@@ -461,7 +487,7 @@ class ApiQueryImageInfo extends ApiQueryBase {
if ( $start === null ) {
$start = $img->getTimestamp();
}
- return $img->getOriginalTitle()->getText() . '|' . $start;
+ return $img->getOriginalTitle()->getDBkey() . '|' . $start;
}
public function getAllowedParams() {
@@ -537,9 +563,10 @@ class ApiQueryImageInfo extends ApiQueryBase {
'thumbmime' => ' thumbmime - Adds MIME type of the image thumbnail' .
' (requires url and param ' . $modulePrefix . 'urlwidth)',
'mediatype' => ' mediatype - Adds the media type of the image',
- 'metadata' => ' metadata - Lists EXIF metadata for the version of the image',
+ 'metadata' => ' metadata - Lists Exif metadata for the version of the image',
'archivename' => ' archivename - Adds the file name of the archive version for non-latest versions',
'bitdepth' => ' bitdepth - Adds the bit depth of the version',
+ 'uploadwarning' => ' uploadwarning - Used by the Special:Upload page to get information about an existing file. Not intended for use outside MediaWiki core',
);
}
@@ -547,7 +574,7 @@ class ApiQueryImageInfo extends ApiQueryBase {
* Returns the descriptions for the properties provided by getPropertyNames()
*
* @param array $filter List of properties to filter out
- *
+ * @param string $modulePrefix
* @return array
*/
public static function getPropertyDescriptions( $filter = array(), $modulePrefix = '' ) {
@@ -566,11 +593,12 @@ 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' ),
- 'urlheight' => "Similar to {$p}urlwidth. Cannot be used without {$p}urlwidth",
+ 'For performance reasons if this option is used, ' .
+ 'no more than ' . self::TRANSFORM_LIMIT . ' scaled images will be returned.' ),
+ 'urlheight' => "Similar to {$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" ),
- 'limit' => 'How many image revisions to return',
+ 'limit' => 'How many image revisions to return per image',
'start' => 'Timestamp to start listing from',
'end' => 'Timestamp to stop listing at',
'metadataversion' => array( "Version of metadata to use. if 'latest' is specified, use latest version.",
@@ -714,8 +742,6 @@ 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 doesn't " .
- "match the one in {$p}urlwidth" ),
) );
}
diff --git a/includes/api/ApiQueryInfo.php b/includes/api/ApiQueryInfo.php
index 37cd9159..017684ed 100644
--- a/includes/api/ApiQueryInfo.php
+++ b/includes/api/ApiQueryInfo.php
@@ -56,11 +56,11 @@ class ApiQueryInfo extends ApiQueryBase {
* @return void
*/
public function requestExtraData( $pageSet ) {
- global $wgDisableCounters;
+ global $wgDisableCounters, $wgContentHandlerUseDB;
$pageSet->requestField( 'page_restrictions' );
// when resolving redirects, no page will have this field
- if( !$pageSet->isResolvingRedirects() ) {
+ if ( !$pageSet->isResolvingRedirects() ) {
$pageSet->requestField( 'page_is_redirect' );
}
$pageSet->requestField( 'page_is_new' );
@@ -70,6 +70,9 @@ class ApiQueryInfo extends ApiQueryBase {
$pageSet->requestField( 'page_touched' );
$pageSet->requestField( 'page_latest' );
$pageSet->requestField( 'page_len' );
+ if ( $wgContentHandlerUseDB ) {
+ $pageSet->requestField( 'page_content_model' );
+ }
}
/**
@@ -348,6 +351,10 @@ class ApiQueryInfo extends ApiQueryBase {
$titleExists = $pageid > 0; //$title->exists() needs pageid, which is not set for all title objects
$ns = $title->getNamespace();
$dbkey = $title->getDBkey();
+
+ $pageInfo['contentmodel'] = $title->getContentModel();
+ $pageInfo['pagelanguage'] = $title->getPageLanguage()->getCode();
+
if ( $titleExists ) {
global $wgDisableCounters;
@@ -476,7 +483,7 @@ class ApiQueryInfo extends ApiQueryBase {
$this->protections[$title->getNamespace()][$title->getDBkey()][] = $a;
}
// Also check old restrictions
- foreach( $this->titles as $pageId => $title ) {
+ foreach ( $this->titles as $pageId => $title ) {
if ( $this->pageRestrictions[$pageId] ) {
$namespace = $title->getNamespace();
$dbKey = $title->getDBkey();
@@ -662,7 +669,9 @@ class ApiQueryInfo extends ApiQueryBase {
private function getWatchedInfo() {
$user = $this->getUser();
- if ( $user->isAnon() || count( $this->everything ) == 0 ) {
+ if ( $user->isAnon() || count( $this->everything ) == 0
+ || !$user->isAllowed( 'viewmywatchlist' )
+ ) {
return;
}
@@ -819,7 +828,8 @@ class ApiQueryInfo extends ApiQueryBase {
'starttimestamp' => array(
ApiBase::PROP_TYPE => 'timestamp',
ApiBase::PROP_NULLABLE => true
- )
+ ),
+ 'contentmodel' => 'string',
),
'watched' => array(
'watched' => 'boolean'
diff --git a/includes/api/ApiQueryLangBacklinks.php b/includes/api/ApiQueryLangBacklinks.php
index 7a4880a4..5bd451b6 100644
--- a/includes/api/ApiQueryLangBacklinks.php
+++ b/includes/api/ApiQueryLangBacklinks.php
@@ -195,7 +195,7 @@ class ApiQueryLangBacklinks extends ApiQueryGeneratorBase {
'prop' => array(
'Which properties to get',
' lllang - Adds the language code of the language link',
- ' lltitle - Adds the title of the language ink',
+ ' lltitle - Adds the title of the language link',
),
'limit' => 'How many total pages to return',
'dir' => 'The direction in which to list',
@@ -223,7 +223,8 @@ class ApiQueryLangBacklinks extends ApiQueryGeneratorBase {
return array( 'Find all pages that link to the given language link.',
'Can be used to find all links with a language code, or',
'all links to a title (with a given language).',
- 'Using neither parameter is effectively "All Language Links"',
+ 'Using neither parameter is effectively "All Language Links".',
+ 'Note that this may not consider language links added by extensions.',
);
}
@@ -239,4 +240,8 @@ class ApiQueryLangBacklinks extends ApiQueryGeneratorBase {
'api.php?action=query&generator=langbacklinks&glbltitle=Test&glbllang=fr&prop=info'
);
}
+
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Langbacklinks';
+ }
}
diff --git a/includes/api/ApiQueryLangLinks.php b/includes/api/ApiQueryLangLinks.php
index ac65d2d2..aa796e31 100644
--- a/includes/api/ApiQueryLangLinks.php
+++ b/includes/api/ApiQueryLangLinks.php
@@ -67,18 +67,19 @@ class ApiQueryLangLinks extends ApiQueryBase {
);
}
- $sort = ( $params['dir'] == 'descending' ? ' DESC' : '' );
- if ( isset( $params['lang'] ) ) {
+ //FIXME: (follow-up) To allow extensions to add to the language links, we need
+ // to load them all, add the extra links, then apply paging.
+ // Should not be terrible, it's not going to be more than a few hundred links.
+
+ // Note that, since (ll_from, ll_lang) is a unique key, we don't need
+ // to sort by ll_title to ensure deterministic ordering.
+ $sort = ( $params['dir'] == 'descending' ? ' DESC' : '' );
+ if ( isset( $params['lang'] ) ) {
$this->addWhereFld( 'll_lang', $params['lang'] );
if ( isset( $params['title'] ) ) {
$this->addWhereFld( 'll_title', $params['title'] );
- $this->addOption( 'ORDER BY', 'll_from' . $sort );
- } else {
- $this->addOption( 'ORDER BY', array(
- 'll_title' . $sort,
- 'll_from' . $sort
- ));
}
+ $this->addOption( 'ORDER BY', 'll_from' . $sort );
} else {
// Don't order by ll_from if it's constant in the WHERE clause
if ( count( $this->getPageSet()->getGoodTitles() ) == 1 ) {
diff --git a/includes/api/ApiQueryLogEvents.php b/includes/api/ApiQueryLogEvents.php
index 73dcea49..1a2719ed 100644
--- a/includes/api/ApiQueryLogEvents.php
+++ b/includes/api/ApiQueryLogEvents.php
@@ -49,12 +49,12 @@ class ApiQueryLogEvents extends ApiQueryBase {
$this->fld_ids = isset( $prop['ids'] );
$this->fld_title = isset( $prop['title'] );
$this->fld_type = isset( $prop['type'] );
- $this->fld_action = isset ( $prop['action'] );
+ $this->fld_action = isset( $prop['action'] );
$this->fld_user = isset( $prop['user'] );
$this->fld_userid = isset( $prop['userid'] );
$this->fld_timestamp = isset( $prop['timestamp'] );
$this->fld_comment = isset( $prop['comment'] );
- $this->fld_parsedcomment = isset ( $prop['parsedcomment'] );
+ $this->fld_parsedcomment = isset( $prop['parsedcomment'] );
$this->fld_details = isset( $prop['details'] );
$this->fld_tags = isset( $prop['tags'] );
@@ -67,7 +67,7 @@ class ApiQueryLogEvents extends ApiQueryBase {
$this->addTables( array( 'logging', 'user', 'page' ) );
$this->addOption( 'STRAIGHT_JOIN' );
$this->addJoinConds( array(
- 'user' => array( 'JOIN',
+ 'user' => array( 'LEFT JOIN',
'user_id=log_user' ),
'page' => array( 'LEFT JOIN',
array( 'log_namespace=page_namespace',
@@ -82,8 +82,8 @@ class ApiQueryLogEvents extends ApiQueryBase {
) );
$this->addFieldsIf( array( 'log_id', 'page_id' ), $this->fld_ids );
- $this->addFieldsIf( array( 'log_user', 'user_name' ), $this->fld_user );
- $this->addFieldsIf( 'user_id', $this->fld_userid );
+ $this->addFieldsIf( array( 'log_user', 'log_user_text', 'user_name' ), $this->fld_user );
+ $this->addFieldsIf( 'log_user', $this->fld_userid );
$this->addFieldsIf( array( 'log_namespace', 'log_title' ), $this->fld_title || $this->fld_parsedcomment );
$this->addFieldsIf( 'log_comment', $this->fld_comment || $this->fld_parsedcomment );
$this->addFieldsIf( 'log_params', $this->fld_details );
@@ -98,8 +98,7 @@ class ApiQueryLogEvents extends ApiQueryBase {
$this->addTables( 'change_tag' );
$this->addJoinConds( array( 'change_tag' => array( 'INNER JOIN', array( 'log_id=ct_log_id' ) ) ) );
$this->addWhereFld( 'ct_tag', $params['tag'] );
- global $wgOldChangeTagsIndex;
- $index['change_tag'] = $wgOldChangeTagsIndex ? 'ct_tag' : 'change_tag_tag_id';
+ $index['change_tag'] = 'change_tag_tag_id';
}
if ( !is_null( $params['action'] ) ) {
@@ -241,7 +240,7 @@ class ApiQueryLogEvents extends ApiQueryBase {
break;
case 'rights':
$vals2 = array();
- if( $legacy ) {
+ if ( $legacy ) {
list( $vals2['old'], $vals2['new'] ) = $params;
} else {
$vals2['new'] = implode( ', ', $params['5::newgroups'] );
@@ -265,6 +264,11 @@ class ApiQueryLogEvents extends ApiQueryBase {
$vals[$type] = $vals2;
$params = null;
break;
+ case 'upload':
+ if ( isset( $params['img_timestamp'] ) ) {
+ $params['img_timestamp'] = wfTimestamp( TS_ISO_8601, $params['img_timestamp'] );
+ }
+ break;
}
if ( !is_null( $params ) ) {
$logParams = array();
@@ -331,10 +335,10 @@ class ApiQueryLogEvents extends ApiQueryBase {
$vals['userhidden'] = '';
} else {
if ( $this->fld_user ) {
- $vals['user'] = $row->user_name;
+ $vals['user'] = $row->user_name === null ? $row->log_user_text : $row->user_name;
}
if ( $this->fld_userid ) {
- $vals['userid'] = $row->user_id;
+ $vals['userid'] = $row->log_user;
}
if ( !$row->log_user ) {
diff --git a/includes/api/ApiQueryORM.php b/includes/api/ApiQueryORM.php
index 41d8f11c..a23ff06b 100644
--- a/includes/api/ApiQueryORM.php
+++ b/includes/api/ApiQueryORM.php
@@ -228,7 +228,7 @@ abstract class ApiQueryORM extends ApiQueryBase {
* @return array
*/
public function getAllowedParams() {
- $params = array (
+ $params = array(
'props' => array(
ApiBase::PARAM_TYPE => $this->getTable()->getFieldNames(),
ApiBase::PARAM_ISMULTI => true,
@@ -252,7 +252,7 @@ abstract class ApiQueryORM extends ApiQueryBase {
* @return array
*/
public function getParamDescription() {
- $descriptions = array (
+ $descriptions = array(
'props' => 'Fields to query',
'continue' => 'Offset number from where to continue the query',
'limit' => 'Max amount of rows to return',
diff --git a/includes/api/ApiQueryPagesWithProp.php b/includes/api/ApiQueryPagesWithProp.php
index 0132fc3e..6f2f02e4 100644
--- a/includes/api/ApiQueryPagesWithProp.php
+++ b/includes/api/ApiQueryPagesWithProp.php
@@ -133,7 +133,7 @@ class ApiQueryPagesWithProp extends ApiQueryGeneratorBase {
'prop' => array(
ApiBase::PARAM_DFLT => 'ids|title',
ApiBase::PARAM_ISMULTI => true,
- ApiBase::PARAM_TYPE => array (
+ ApiBase::PARAM_TYPE => array(
'ids',
'title',
'value',
diff --git a/includes/api/ApiQueryProtectedTitles.php b/includes/api/ApiQueryProtectedTitles.php
index 4aa00007..222ad074 100644
--- a/includes/api/ApiQueryProtectedTitles.php
+++ b/includes/api/ApiQueryProtectedTitles.php
@@ -157,7 +157,7 @@ class ApiQueryProtectedTitles extends ApiQueryGeneratorBase {
ApiBase::PARAM_ISMULTI => true,
ApiBase::PARAM_TYPE => array_diff( $wgRestrictionLevels, array( '' ) )
),
- 'limit' => array (
+ 'limit' => array(
ApiBase::PARAM_DFLT => 10,
ApiBase::PARAM_TYPE => 'limit',
ApiBase::PARAM_MIN => 1,
diff --git a/includes/api/ApiQueryQueryPage.php b/includes/api/ApiQueryQueryPage.php
index b03bdfb8..79fe0498 100644
--- a/includes/api/ApiQueryQueryPage.php
+++ b/includes/api/ApiQueryQueryPage.php
@@ -32,27 +32,18 @@
class ApiQueryQueryPage extends ApiQueryGeneratorBase {
private $qpMap;
- /**
- * Some query pages are useless because they're available elsewhere in the API
- */
- private $uselessQueryPages = array(
- 'MIMEsearch', // aiprop=mime
- 'LinkSearch', // list=exturlusage
- 'FileDuplicateSearch', // prop=duplicatefiles
- );
-
public function __construct( $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'qp' );
// We need to do this to make sure $wgQueryPages is set up
// This SUCKS
global $IP;
- require_once( "$IP/includes/QueryPage.php" );
+ require_once "$IP/includes/QueryPage.php";
// Build mapping from special page names to QueryPage classes
- global $wgQueryPages;
+ global $wgQueryPages, $wgAPIUselessQueryPages;
$this->qpMap = array();
foreach ( $wgQueryPages as $page ) {
- if( !in_array( $page[1], $this->uselessQueryPages ) ) {
+ if ( !in_array( $page[1], $wgAPIUselessQueryPages ) ) {
$this->qpMap[$page[1]] = $page[0];
}
}
@@ -222,4 +213,8 @@ class ApiQueryQueryPage extends ApiQueryGeneratorBase {
'api.php?action=query&list=querypage&qppage=Ancientpages'
);
}
+
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Querypage';
+ }
}
diff --git a/includes/api/ApiQueryRandom.php b/includes/api/ApiQueryRandom.php
index ae3bb893..2754bdae 100644
--- a/includes/api/ApiQueryRandom.php
+++ b/includes/api/ApiQueryRandom.php
@@ -185,4 +185,8 @@ class ApiQueryRandom extends ApiQueryGeneratorBase {
public function getExamples() {
return 'api.php?action=query&list=random&rnnamespace=0&rnlimit=2';
}
+
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Random';
+ }
}
diff --git a/includes/api/ApiQueryRecentChanges.php b/includes/api/ApiQueryRecentChanges.php
index 8aceab22..6b10bdc6 100644
--- a/includes/api/ApiQueryRecentChanges.php
+++ b/includes/api/ApiQueryRecentChanges.php
@@ -39,7 +39,7 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
private $fld_comment = false, $fld_parsedcomment = false, $fld_user = false, $fld_userid = false,
$fld_flags = false, $fld_timestamp = false, $fld_title = false, $fld_ids = false,
$fld_sizes = false, $fld_redirect = false, $fld_patrolled = false, $fld_loginfo = false,
- $fld_tags = false, $token = array();
+ $fld_tags = false, $fld_sha1 = false, $token = array();
private $tokenFunctions;
@@ -121,6 +121,7 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
$this->fld_patrolled = isset( $prop['patrolled'] );
$this->fld_loginfo = isset( $prop['loginfo'] );
$this->fld_tags = isset( $prop['tags'] );
+ $this->fld_sha1 = isset( $prop['sha1'] );
}
public function execute() {
@@ -273,6 +274,12 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
$this->addFields( 'ts_tags' );
}
+ if ( $this->fld_sha1 ) {
+ $this->addTables( 'revision' );
+ $this->addJoinConds( array( 'revision' => array( 'LEFT JOIN', array( 'rc_this_oldid=rev_id' ) ) ) );
+ $this->addFields( array( 'rev_sha1', 'rev_deleted' ) );
+ }
+
if ( $params['toponly'] || $showRedirects ) {
$this->addTables( 'page' );
$this->addJoinConds( array( 'page' => array( 'LEFT JOIN', array( 'rc_namespace=page_namespace', 'rc_title=page_title' ) ) ) );
@@ -287,8 +294,7 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
$this->addTables( 'change_tag' );
$this->addJoinConds( array( 'change_tag' => array( 'INNER JOIN', array( 'rc_id=ct_rc_id' ) ) ) );
$this->addWhereFld( 'ct_tag', $params['tag'] );
- global $wgOldChangeTagsIndex;
- $index['change_tag'] = $wgOldChangeTagsIndex ? 'ct_tag' : 'change_tag_tag_id';
+ $index['change_tag'] = 'change_tag_tag_id';
}
$this->token = $params['token'];
@@ -475,6 +481,19 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
}
}
+ if ( $this->fld_sha1 && $row->rev_sha1 !== null ) {
+ // The RevDel check should currently never pass due to the
+ // rc_deleted = 0 condition in the WHERE clause, but in case that
+ // ever changes we check it here too.
+ if ( $row->rev_deleted & Revision::DELETED_TEXT ) {
+ $vals['sha1hidden'] = '';
+ } elseif ( $row->rev_sha1 !== '' ) {
+ $vals['sha1'] = wfBaseConvert( $row->rev_sha1, 36, 16, 40 );
+ } else {
+ $vals['sha1'] = '';
+ }
+ }
+
if ( !is_null( $this->token ) ) {
$tokenFunctions = $this->getTokenFunctions();
foreach ( $this->token as $t ) {
@@ -499,7 +518,7 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
}
return $retval;
}
- switch( $type ) {
+ switch ( $type ) {
case 'edit':
return RC_EDIT;
case 'new':
@@ -571,7 +590,8 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
'redirect',
'patrolled',
'loginfo',
- 'tags'
+ 'tags',
+ 'sha1',
)
),
'token' => array(
@@ -638,6 +658,7 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
' patrolled - Tags edits that have been patrolled',
' loginfo - Adds log information (logid, logtype, etc) to log entries',
' tags - Lists tags for the entry',
+ ' sha1 - Adds the content checksum for entries associated with a revision',
),
'token' => 'Which tokens to obtain for each change',
'show' => array(
@@ -735,7 +756,17 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
ApiBase::PROP_TYPE => 'string',
ApiBase::PROP_NULLABLE => true
)
- )
+ ),
+ 'sha1' => array(
+ 'sha1' => array(
+ ApiBase::PROP_TYPE => 'string',
+ ApiBase::PROP_NULLABLE => true
+ ),
+ 'sha1hidden' => array(
+ ApiBase::PROP_TYPE => 'boolean',
+ ApiBase::PROP_NULLABLE => true
+ ),
+ ),
);
self::addTokenProperties( $props, $this->getTokenFunctions() );
diff --git a/includes/api/ApiQueryRevisions.php b/includes/api/ApiQueryRevisions.php
index 192fe873..415288ef 100644
--- a/includes/api/ApiQueryRevisions.php
+++ b/includes/api/ApiQueryRevisions.php
@@ -146,17 +146,17 @@ class ApiQueryRevisions extends ApiQueryBase {
$prop = array_flip( $params['prop'] );
// Optional fields
- $this->fld_ids = isset ( $prop['ids'] );
+ $this->fld_ids = isset( $prop['ids'] );
// $this->addFieldsIf('rev_text_id', $this->fld_ids); // should this be exposed?
- $this->fld_flags = isset ( $prop['flags'] );
- $this->fld_timestamp = isset ( $prop['timestamp'] );
- $this->fld_comment = isset ( $prop['comment'] );
- $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_flags = isset( $prop['flags'] );
+ $this->fld_timestamp = isset( $prop['timestamp'] );
+ $this->fld_comment = isset( $prop['comment'] );
+ $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->fld_user = isset( $prop['user'] );
$this->token = $params['token'];
if ( !empty( $params['contentformat'] ) ) {
@@ -189,8 +189,7 @@ class ApiQueryRevisions extends ApiQueryBase {
$this->addTables( 'change_tag' );
$this->addJoinConds( array( 'change_tag' => array( 'INNER JOIN', array( 'rev_id=ct_rev_id' ) ) ) );
$this->addWhereFld( 'ct_tag', $params['tag'] );
- global $wgOldChangeTagsIndex;
- $index['change_tag'] = $wgOldChangeTagsIndex ? 'ct_tag' : 'change_tag_tag_id';
+ $index['change_tag'] = 'change_tag_tag_id';
}
if ( isset( $prop['content'] ) || !is_null( $this->difftotext ) ) {
@@ -520,7 +519,7 @@ class ApiQueryRevisions extends ApiQueryBase {
} else {
$this->setWarning( "Conversion to XML is supported for wikitext only, " .
$title->getPrefixedDBkey() .
- " uses content model " . $content->getModel() . ")" );
+ " uses content model " . $content->getModel() );
}
}
@@ -533,7 +532,7 @@ class ApiQueryRevisions extends ApiQueryBase {
} else {
$this->setWarning( "Template expansion is supported for wikitext only, " .
$title->getPrefixedDBkey() .
- " uses content model " . $content->getModel() . ")" );
+ " uses content model " . $content->getModel() );
$text = false;
}
@@ -550,7 +549,7 @@ class ApiQueryRevisions extends ApiQueryBase {
if ( !$content->isSupportedFormat( $format ) ) {
$name = $title->getPrefixedDBkey();
- $this->dieUsage( "The requested format {$this->contentFormat} is not supported ".
+ $this->dieUsage( "The requested format {$this->contentFormat} is not supported " .
"for content model $model used by $name", 'badformat' );
}
@@ -593,7 +592,7 @@ class ApiQueryRevisions extends ApiQueryBase {
$name = $title->getPrefixedDBkey();
- $this->dieUsage( "The requested format {$this->contentFormat} is not supported for ".
+ $this->dieUsage( "The requested format {$this->contentFormat} is not supported for " .
"content model $model used by $name", 'badformat' );
}
diff --git a/includes/api/ApiQuerySearch.php b/includes/api/ApiQuerySearch.php
index 86183391..36b55979 100644
--- a/includes/api/ApiQuerySearch.php
+++ b/includes/api/ApiQuerySearch.php
@@ -31,6 +31,14 @@
*/
class ApiQuerySearch extends ApiQueryGeneratorBase {
+ /**
+ * When $wgSearchType is null, $wgSearchAlternatives[0] is null. Null isn't
+ * a valid option for an array for PARAM_TYPE, so we'll use a fake name
+ * that can't possibly be a class name and describes what the null behavior
+ * does
+ */
+ const BACKEND_NULL_PARAM = 'database-backed';
+
public function __construct( $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'sr' );
}
@@ -59,7 +67,8 @@ class ApiQuerySearch extends ApiQueryGeneratorBase {
$prop = array_flip( $params['prop'] );
// Create search engine instance and set options
- $search = SearchEngine::create();
+ $search = isset( $params['backend'] ) && $params['backend'] != self::BACKEND_NULL_PARAM ?
+ SearchEngine::create( $params['backend'] ) : SearchEngine::create();
$search->setLimitOffset( $limit + 1, $params['offset'] );
$search->setNamespaces( $params['namespace'] );
$search->showRedirects = $params['redirects'];
@@ -93,6 +102,8 @@ class ApiQuerySearch extends ApiQueryGeneratorBase {
}
if ( is_null( $matches ) ) {
$this->dieUsage( "{$what} search is disabled", "search-{$what}-disabled" );
+ } elseif ( $matches instanceof Status && !$matches->isGood() ) {
+ $this->dieUsage( $matches->getWikiText(), 'search-error' );
}
$apiResult = $this->getResult();
@@ -199,7 +210,9 @@ class ApiQuerySearch extends ApiQueryGeneratorBase {
}
public function getAllowedParams() {
- return array(
+ global $wgSearchType;
+
+ $params = array(
'search' => array(
ApiBase::PARAM_TYPE => 'string',
ApiBase::PARAM_REQUIRED => true
@@ -252,10 +265,23 @@ class ApiQuerySearch extends ApiQueryGeneratorBase {
ApiBase::PARAM_MAX2 => ApiBase::LIMIT_SML2
)
);
+
+ $alternatives = SearchEngine::getSearchTypes();
+ if ( count( $alternatives ) > 1 ) {
+ if ( $alternatives[0] === null ) {
+ $alternatives[0] = self::BACKEND_NULL_PARAM;
+ }
+ $params['backend'] = array(
+ ApiBase::PARAM_DFLT => $wgSearchType,
+ ApiBase::PARAM_TYPE => $alternatives,
+ );
+ }
+
+ return $params;
}
public function getParamDescription() {
- return array(
+ $descriptions = array(
'search' => 'Search for all page titles (or content) that has this value',
'namespace' => 'The namespace(s) to enumerate',
'what' => 'Search inside the text or titles',
@@ -278,6 +304,12 @@ class ApiQuerySearch extends ApiQueryGeneratorBase {
'offset' => 'Use this value to continue paging (return by query)',
'limit' => 'How many total pages to return'
);
+
+ if ( count( SearchEngine::getSearchTypes() ) > 1 ) {
+ $descriptions['backend'] = 'Which search backend to use, if not the default';
+ }
+
+ return $descriptions;
}
public function getResultProperties() {
@@ -345,6 +377,7 @@ class ApiQuerySearch extends ApiQueryGeneratorBase {
return array_merge( parent::getPossibleErrors(), array(
array( 'code' => 'search-text-disabled', 'info' => 'text search is disabled' ),
array( 'code' => 'search-title-disabled', 'info' => 'title search is disabled' ),
+ array( 'code' => 'search-error', 'info' => 'search error has occurred' ),
) );
}
diff --git a/includes/api/ApiQuerySiteinfo.php b/includes/api/ApiQuerySiteinfo.php
index 810e1d6b..a7767062 100644
--- a/includes/api/ApiQuerySiteinfo.php
+++ b/includes/api/ApiQuerySiteinfo.php
@@ -121,14 +121,29 @@ class ApiQuerySiteinfo extends ApiQueryBase {
$data = array();
$mainPage = Title::newMainPage();
$data['mainpage'] = $mainPage->getPrefixedText();
- $data['base'] = wfExpandUrl( $mainPage->getFullUrl(), PROTO_CURRENT );
+ $data['base'] = wfExpandUrl( $mainPage->getFullURL(), PROTO_CURRENT );
$data['sitename'] = $GLOBALS['wgSitename'];
+ $data['logo'] = $GLOBALS['wgLogo'];
$data['generator'] = "MediaWiki {$GLOBALS['wgVersion']}";
$data['phpversion'] = phpversion();
$data['phpsapi'] = PHP_SAPI;
$data['dbtype'] = $GLOBALS['wgDBtype'];
$data['dbversion'] = $this->getDB()->getServerVersion();
+ $allowFrom = array( '' );
+ $allowException = true;
+ if ( !$GLOBALS['wgAllowExternalImages'] ) {
+ if ( $GLOBALS['wgEnableImageWhitelist'] ) {
+ $data['imagewhitelistenabled'] = '';
+ }
+ $allowFrom = $GLOBALS['wgAllowExternalImagesFrom'];
+ $allowException = !empty( $allowFrom );
+ }
+ if ( $allowException ) {
+ $data['externalimages'] = (array)$allowFrom;
+ $this->getResult()->setIndexedTagName( $data['externalimages'], 'prefix' );
+ }
+
if ( !$wgDisableLangConversion ) {
$data['langconversion'] = '';
}
@@ -170,15 +185,15 @@ class ApiQuerySiteinfo extends ApiQueryBase {
$data['lang'] = $GLOBALS['wgLanguageCode'];
$fallbacks = array();
- foreach( $wgContLang->getFallbackLanguages() as $code ) {
+ foreach ( $wgContLang->getFallbackLanguages() as $code ) {
$fallbacks[] = array( 'code' => $code );
}
$data['fallback'] = $fallbacks;
$this->getResult()->setIndexedTagName( $data['fallback'], 'lang' );
- if( $wgContLang->hasVariants() ) {
+ if ( $wgContLang->hasVariants() ) {
$variants = array();
- foreach( $wgContLang->getVariants() as $code ) {
+ foreach ( $wgContLang->getVariants() as $code ) {
$variants[] = array( 'code' => $code );
}
$data['variants'] = $variants;
@@ -281,6 +296,8 @@ class ApiQuerySiteinfo extends ApiQueryBase {
$data[] = $item;
}
+ sort( $data );
+
$this->getResult()->setIndexedTagName( $data, 'ns' );
return $this->getResult()->addValue( 'query', $property, $data );
}
@@ -345,10 +362,10 @@ class ApiQuerySiteinfo extends ApiQueryBase {
$val['language'] = $langNames[$prefix];
}
$val['url'] = wfExpandUrl( $row['iw_url'], PROTO_CURRENT );
- if( isset( $row['iw_wikiid'] ) ) {
+ if ( isset( $row['iw_wikiid'] ) ) {
$val['wikiid'] = $row['iw_wikiid'];
}
- if( isset( $row['iw_api'] ) ) {
+ if ( isset( $row['iw_api'] ) ) {
$val['api'] = $row['iw_api'];
}
@@ -404,11 +421,15 @@ class ApiQuerySiteinfo extends ApiQueryBase {
$data['activeusers'] = intval( SiteStats::activeUsers() );
$data['admins'] = intval( SiteStats::numberingroup( 'sysop' ) );
$data['jobs'] = intval( SiteStats::jobs() );
+
+ wfRunHooks( 'APIQuerySiteInfoStatisticsInfo', array( &$data ) );
+
return $this->getResult()->addValue( 'query', $property, $data );
}
protected function appendUserGroups( $property, $numberInGroup ) {
- global $wgGroupPermissions, $wgAddGroups, $wgRemoveGroups, $wgGroupsAddToSelf, $wgGroupsRemoveFromSelf;
+ global $wgGroupPermissions, $wgAddGroups, $wgRemoveGroups;
+ global $wgGroupsAddToSelf, $wgGroupsRemoveFromSelf;
$data = array();
$result = $this->getResult();
@@ -456,7 +477,7 @@ class ApiQuerySiteinfo extends ApiQueryBase {
global $wgFileExtensions;
$data = array();
- foreach ( $wgFileExtensions as $ext ) {
+ foreach ( array_unique( $wgFileExtensions ) as $ext ) {
$data[] = array( 'ext' => $ext );
}
$this->getResult()->setIndexedTagName( $data, 'fe' );
@@ -520,7 +541,7 @@ class ApiQuerySiteinfo extends ApiQueryBase {
$data = array(
'url' => $url ? $url : '',
- 'text' => $text ? $text : ''
+ 'text' => $text ? $text : ''
);
return $this->getResult()->addValue( 'query', $property, $data );
@@ -544,9 +565,17 @@ class ApiQuerySiteinfo extends ApiQueryBase {
public function appendSkins( $property ) {
$data = array();
+ $usable = Skin::getUsableSkins();
+ $default = Skin::normalizeKey( 'default' );
foreach ( Skin::getSkinNames() as $name => $displayName ) {
$skin = array( 'code' => $name );
ApiResult::setContent( $skin, $displayName );
+ if ( !isset( $usable[$name] ) ) {
+ $skin['unusable'] = '';
+ }
+ if ( $name === $default ) {
+ $skin['default'] = '';
+ }
$data[] = $skin;
}
$this->getResult()->setIndexedTagName( $data, 'skin' );
@@ -661,13 +690,15 @@ class ApiQuerySiteinfo extends ApiQueryBase {
' specialpagealiases - List of special page aliases',
' magicwords - List of magic words and their aliases',
' statistics - Returns site statistics',
- " interwikimap - Returns interwiki map (optionally filtered, (optionally localised by using {$p}inlanguagecode))",
+ " interwikimap - Returns interwiki map " .
+ "(optionally filtered, (optionally localised by using {$p}inlanguagecode))",
' dbrepllag - Returns database server with the highest replication lag',
' usergroups - Returns user groups and the associated permissions',
' extensions - Returns extensions installed on the wiki',
' fileextensions - Returns list of file extensions allowed to be uploaded',
' rightsinfo - Returns wiki rights (license) information if available',
- " languages - Returns a list of languages MediaWiki supports (optionally localised by using {$p}inlanguagecode)",
+ " languages - Returns a list of languages MediaWiki supports" .
+ "(optionally localised by using {$p}inlanguagecode)",
' skins - Returns a list of all enabled skins',
' extensiontags - Returns a list of parser extension tags',
' functionhooks - Returns a list of parser function hooks',
@@ -675,7 +706,7 @@ class ApiQuerySiteinfo extends ApiQueryBase {
' 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',
+ 'filteriw' => 'Return only local or only nonlocal entries of the interwiki map',
'showalldb' => 'List all database servers, not just the one lagging the most',
'numberingroup' => 'Lists the number of users in user groups',
'inlanguagecode' => 'Language code for localised language names (best effort, use CLDR extension)',
@@ -687,9 +718,10 @@ class ApiQuerySiteinfo extends ApiQueryBase {
}
public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'code' => 'includeAllDenied', 'info' => 'Cannot view all servers info unless $wgShowHostnames is true' ),
- ) );
+ return array_merge( parent::getPossibleErrors(), array( array(
+ 'code' => 'includeAllDenied',
+ 'info' => 'Cannot view all servers info unless $wgShowHostnames is true'
+ ), ) );
}
public function getExamples() {
diff --git a/includes/api/ApiQueryTags.php b/includes/api/ApiQueryTags.php
index e0637ff7..732df9a4 100644
--- a/includes/api/ApiQueryTags.php
+++ b/includes/api/ApiQueryTags.php
@@ -133,8 +133,7 @@ class ApiQueryTags extends ApiQueryBase {
public function getAllowedParams() {
return array(
- 'continue' => array(
- ),
+ 'continue' => null,
'limit' => array(
ApiBase::PARAM_DFLT => 10,
ApiBase::PARAM_TYPE => 'limit',
@@ -195,4 +194,8 @@ class ApiQueryTags extends ApiQueryBase {
'api.php?action=query&list=tags&tgprop=displayname|description|hitcount'
);
}
+
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Tags';
+ }
}
diff --git a/includes/api/ApiQueryUserContributions.php b/includes/api/ApiQueryUserContributions.php
index 597c412d..9a9be7b2 100644
--- a/includes/api/ApiQueryUserContributions.php
+++ b/includes/api/ApiQueryUserContributions.php
@@ -48,7 +48,7 @@ class ApiQueryContributions extends ApiQueryBase {
$this->fld_ids = isset( $prop['ids'] );
$this->fld_title = isset( $prop['title'] );
$this->fld_comment = isset( $prop['comment'] );
- $this->fld_parsedcomment = isset ( $prop['parsedcomment'] );
+ $this->fld_parsedcomment = isset( $prop['parsedcomment'] );
$this->fld_size = isset( $prop['size'] );
$this->fld_sizediff = isset( $prop['sizediff'] );
$this->fld_flags = isset( $prop['flags'] );
@@ -83,10 +83,10 @@ class ApiQueryContributions extends ApiQueryBase {
// Do the actual query.
$res = $this->select( __METHOD__ );
- if( $this->fld_sizediff ) {
+ if ( $this->fld_sizediff ) {
$revIds = array();
foreach ( $res as $row ) {
- if( $row->rev_parent_id ) {
+ if ( $row->rev_parent_id ) {
$revIds[] = $row->rev_parent_id;
}
}
@@ -255,7 +255,7 @@ class ApiQueryContributions extends ApiQueryBase {
$this->addFieldsIf( 'rev_comment', $this->fld_comment || $this->fld_parsedcomment );
$this->addFieldsIf( 'rev_len', $this->fld_size || $this->fld_sizediff );
$this->addFieldsIf( 'rev_minor_edit', $this->fld_flags );
- $this->addFieldsIf( 'rev_parent_id', $this->fld_flags || $this->fld_sizediff );
+ $this->addFieldsIf( 'rev_parent_id', $this->fld_flags || $this->fld_sizediff || $this->fld_ids );
$this->addFieldsIf( 'rc_patrolled', $this->fld_patrolled );
if ( $this->fld_tags ) {
@@ -268,8 +268,7 @@ class ApiQueryContributions extends ApiQueryBase {
$this->addTables( 'change_tag' );
$this->addJoinConds( array( 'change_tag' => array( 'INNER JOIN', array( 'rev_id=ct_rev_id' ) ) ) );
$this->addWhereFld( 'ct_tag', $this->params['tag'] );
- global $wgOldChangeTagsIndex;
- $index['change_tag'] = $wgOldChangeTagsIndex ? 'ct_tag' : 'change_tag_tag_id';
+ $index['change_tag'] = 'change_tag_tag_id';
}
if ( $this->params['toponly'] ) {
@@ -297,6 +296,10 @@ class ApiQueryContributions extends ApiQueryBase {
$vals['pageid'] = intval( $row->rev_page );
$vals['revid'] = intval( $row->rev_id );
// $vals['textid'] = intval( $row->rev_text_id ); // todo: Should this field be exposed?
+
+ if ( !is_null( $row->rev_parent_id ) ) {
+ $vals['parentid'] = intval( $row->rev_parent_id );
+ }
}
$title = Title::makeTitle( $row->page_namespace, $row->page_title );
@@ -474,7 +477,11 @@ class ApiQueryContributions extends ApiQueryBase {
),
'ids' => array(
'pageid' => 'integer',
- 'revid' => 'integer'
+ 'revid' => 'integer',
+ 'parentid' => array(
+ ApiBase::PROP_TYPE => 'integer',
+ ApiBase::PROP_NULLABLE => true
+ )
),
'title' => array(
'ns' => 'namespace',
diff --git a/includes/api/ApiQueryUserInfo.php b/includes/api/ApiQueryUserInfo.php
index 1a491eca..3c85ea69 100644
--- a/includes/api/ApiQueryUserInfo.php
+++ b/includes/api/ApiQueryUserInfo.php
@@ -104,12 +104,15 @@ class ApiQueryUserInfo extends ApiQueryBase {
}
if ( isset( $this->prop['preferencestoken'] ) &&
- is_null( $this->getMain()->getRequest()->getVal( 'callback' ) )
+ is_null( $this->getMain()->getRequest()->getVal( 'callback' ) ) &&
+ $user->isAllowed( 'editmyoptions' )
) {
$vals['preferencestoken'] = $user->getEditToken( '', $this->getMain()->getRequest() );
}
if ( isset( $this->prop['editcount'] ) ) {
+ // use intval to prevent null if a non-logged-in user calls
+ // api.php?format=jsonfm&action=query&meta=userinfo&uiprop=editcount
$vals['editcount'] = intval( $user->getEditCount() );
}
@@ -121,11 +124,13 @@ class ApiQueryUserInfo extends ApiQueryBase {
$vals['realname'] = $user->getRealName();
}
- if ( isset( $this->prop['email'] ) ) {
- $vals['email'] = $user->getEmail();
- $auth = $user->getEmailAuthenticationTimestamp();
- if ( !is_null( $auth ) ) {
- $vals['emailauthenticated'] = wfTimestamp( TS_ISO_8601, $auth );
+ if ( $user->isAllowed( 'viewmyprivateinfo' ) ) {
+ if ( isset( $this->prop['email'] ) ) {
+ $vals['email'] = $user->getEmail();
+ $auth = $user->getEmailAuthenticationTimestamp();
+ if ( !is_null( $auth ) ) {
+ $vals['emailauthenticated'] = wfTimestamp( TS_ISO_8601, $auth );
+ }
}
}
@@ -167,8 +172,9 @@ class ApiQueryUserInfo extends ApiQueryBase {
if ( $user->isNewbie() ) {
$categories[] = 'ip';
$categories[] = 'subnet';
- if ( !$user->isAnon() )
+ if ( !$user->isAnon() ) {
$categories[] = 'newbie';
+ }
}
$categories = array_merge( $categories, $user->getGroups() );
diff --git a/includes/api/ApiQueryUsers.php b/includes/api/ApiQueryUsers.php
index 72ab7866..dccfee67 100644
--- a/includes/api/ApiQueryUsers.php
+++ b/includes/api/ApiQueryUsers.php
@@ -127,7 +127,7 @@ class ApiQueryUsers extends ApiQueryBase {
$this->addFields( array( 'user_name', 'ug_group' ) );
$userGroupsRes = $this->select( __METHOD__ );
- foreach( $userGroupsRes as $row ) {
+ foreach ( $userGroupsRes as $row ) {
$userGroups[$row->user_name][] = $row->ug_group;
}
}
@@ -149,7 +149,7 @@ class ApiQueryUsers extends ApiQueryBase {
$data[$name]['name'] = $name;
if ( isset( $this->prop['editcount'] ) ) {
- $data[$name]['editcount'] = intval( $user->getEditCount() );
+ $data[$name]['editcount'] = $user->getEditCount();
}
if ( isset( $this->prop['registration'] ) ) {
@@ -204,11 +204,13 @@ class ApiQueryUsers extends ApiQueryBase {
}
}
+ $context = $this->getContext();
// Second pass: add result data to $retval
foreach ( $goodNames as $u ) {
if ( !isset( $data[$u] ) ) {
$data[$u] = array( 'name' => $u );
$urPage = new UserrightsPage;
+ $urPage->setContext( $context );
$iwUser = $urPage->fetchUser( $u );
if ( $iwUser instanceof UserRightsProxy ) {
diff --git a/includes/api/ApiQueryWatchlist.php b/includes/api/ApiQueryWatchlist.php
index 90b12c14..22843f50 100644
--- a/includes/api/ApiQueryWatchlist.php
+++ b/includes/api/ApiQueryWatchlist.php
@@ -68,7 +68,7 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
$this->fld_user = isset( $prop['user'] );
$this->fld_userid = isset( $prop['userid'] );
$this->fld_comment = isset( $prop['comment'] );
- $this->fld_parsedcomment = isset ( $prop['parsedcomment'] );
+ $this->fld_parsedcomment = isset( $prop['parsedcomment'] );
$this->fld_timestamp = isset( $prop['timestamp'] );
$this->fld_sizes = isset( $prop['sizes'] );
$this->fld_patrol = isset( $prop['patrol'] );
@@ -135,7 +135,7 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
if ( !$params['allrev'] ) {
$this->addTables( 'page' );
- $this->addJoinConds( array( 'page' => array( 'LEFT JOIN','rc_cur_id=page_id' ) ) );
+ $this->addJoinConds( array( 'page' => array( 'LEFT JOIN', 'rc_cur_id=page_id' ) ) );
$this->addWhere( 'rc_this_oldid=page_latest OR rc_type=' . RC_LOG );
}
@@ -143,12 +143,11 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
$show = array_flip( $params['show'] );
/* Check for conflicting parameters. */
- if ( ( isset ( $show['minor'] ) && isset ( $show['!minor'] ) )
- || ( isset ( $show['bot'] ) && isset ( $show['!bot'] ) )
- || ( isset ( $show['anon'] ) && isset ( $show['!anon'] ) )
- || ( isset ( $show['patrolled'] ) && isset ( $show['!patrolled'] ) )
- )
- {
+ if ( ( isset( $show['minor'] ) && isset( $show['!minor'] ) )
+ || ( isset( $show['bot'] ) && isset( $show['!bot'] ) )
+ || ( isset( $show['anon'] ) && isset( $show['!anon'] ) )
+ || ( isset( $show['patrolled'] ) && isset( $show['!patrolled'] ) )
+ ) {
$this->dieUsageMsg( 'show' );
}
@@ -171,6 +170,10 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
$this->addWhereIf( 'rc_patrolled != 0', isset( $show['patrolled'] ) );
}
+ if ( !is_null( $params['type'] ) ) {
+ $this->addWhereFld( 'rc_type', $this->parseRCType( $params['type'] ) );
+ }
+
if ( !is_null( $params['user'] ) && !is_null( $params['excludeuser'] ) ) {
$this->dieUsage( 'user and excludeuser cannot be used together', 'user-excludeuser' );
}
@@ -226,6 +229,32 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
private function extractRowInfo( $row ) {
$vals = array();
+ $type = intval( $row->rc_type );
+
+ /* Determine what kind of change this was. */
+ switch ( $type ) {
+ case RC_EDIT:
+ $vals['type'] = 'edit';
+ break;
+ case RC_NEW:
+ $vals['type'] = 'new';
+ break;
+ case RC_MOVE:
+ $vals['type'] = 'move';
+ break;
+ 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;
+ default:
+ $vals['type'] = $type;
+ }
+
if ( $this->fld_ids ) {
$vals['pageid'] = intval( $row->rc_cur_id );
$vals['revid'] = intval( $row->rc_this_oldid );
@@ -312,6 +341,27 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
return $vals;
}
+ /* Copied from ApiQueryRecentChanges. */
+ private function parseRCType( $type ) {
+ if ( is_array( $type ) ) {
+ $retval = array();
+ foreach ( $type as $t ) {
+ $retval[] = $this->parseRCType( $t );
+ }
+ return $retval;
+ }
+ switch ( $type ) {
+ case 'edit':
+ return RC_EDIT;
+ case 'new':
+ return RC_NEW;
+ case 'log':
+ return RC_LOG;
+ case 'external':
+ return RC_EXTERNAL;
+ }
+ }
+
public function getAllowedParams() {
return array(
'allrev' => false,
@@ -321,7 +371,7 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
'end' => array(
ApiBase::PARAM_TYPE => 'timestamp'
),
- 'namespace' => array (
+ 'namespace' => array(
ApiBase::PARAM_ISMULTI => true,
ApiBase::PARAM_TYPE => 'namespace'
),
@@ -376,6 +426,15 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
'!patrolled',
)
),
+ 'type' => array(
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_TYPE => array(
+ 'edit',
+ 'external',
+ 'new',
+ 'log',
+ )
+ ),
'owner' => array(
ApiBase::PARAM_TYPE => 'user'
),
@@ -415,6 +474,13 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
'Show only items that meet this criteria.',
"For example, to see only minor edits done by logged-in users, set {$p}show=minor|!anon"
),
+ 'type' => array(
+ 'Which types of changes to show',
+ ' edit - Regular page edits',
+ ' external - External changes',
+ ' new - Page creations',
+ ' log - Log entries',
+ ),
'owner' => 'The name of the user whose watchlist you\'d like to access',
'token' => 'Give a security token (settable in preferences) to allow access to another user\'s watchlist'
);
@@ -423,6 +489,17 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
public function getResultProperties() {
global $wgLogTypes;
return array(
+ '' => array(
+ 'type' => array(
+ ApiBase::PROP_TYPE => array(
+ 'edit',
+ 'new',
+ 'move',
+ 'log',
+ 'move over redirect'
+ )
+ )
+ ),
'ids' => array(
'pageid' => 'integer',
'revid' => 'integer',
diff --git a/includes/api/ApiQueryWatchlistRaw.php b/includes/api/ApiQueryWatchlistRaw.php
index 2cb4d9eb..ea4e724a 100644
--- a/includes/api/ApiQueryWatchlistRaw.php
+++ b/includes/api/ApiQueryWatchlistRaw.php
@@ -222,4 +222,8 @@ class ApiQueryWatchlistRaw extends ApiQueryGeneratorBase {
'api.php?action=query&generator=watchlistraw&gwrshow=changed&prop=revisions',
);
}
+
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Watchlistraw';
+ }
}
diff --git a/includes/api/ApiRsd.php b/includes/api/ApiRsd.php
index c4a1328c..d219c91c 100644
--- a/includes/api/ApiRsd.php
+++ b/includes/api/ApiRsd.php
@@ -40,7 +40,7 @@ class ApiRsd extends ApiBase {
$service = array( 'apis' => $this->formatRsdApiList() );
ApiResult::setContent( $service, 'MediaWiki', 'engineName' );
ApiResult::setContent( $service, 'https://www.mediawiki.org/', 'engineLink' );
- ApiResult::setContent( $service, Title::newMainPage()->getCanonicalUrl(), 'homePageLink' );
+ ApiResult::setContent( $service, Title::newMainPage()->getCanonicalURL(), 'homePageLink' );
$result->setIndexedTagName( $service['apis'], 'api' );
diff --git a/includes/api/ApiSetNotificationTimestamp.php b/includes/api/ApiSetNotificationTimestamp.php
index 58d5d9ab..53a68fde 100644
--- a/includes/api/ApiSetNotificationTimestamp.php
+++ b/includes/api/ApiSetNotificationTimestamp.php
@@ -39,6 +39,9 @@ class ApiSetNotificationTimestamp extends ApiBase {
if ( $user->isAnon() ) {
$this->dieUsage( 'Anonymous users cannot use watchlist change notifications', 'notloggedin' );
}
+ if ( !$user->isAllowed( 'editmywatchlist' ) ) {
+ $this->dieUsage( 'You don\'t have permission to edit your watchlist', 'permissiondenied' );
+ }
$params = $this->extractRequestParams();
$this->requireMaxOneParameter( $params, 'timestamp', 'torevid', 'newerthanrevid' );
@@ -212,7 +215,7 @@ class ApiSetNotificationTimestamp extends ApiBase {
}
public function getParamDescription() {
- return $this->getPageSet()->getParamDescription() + array(
+ return $this->getPageSet()->getFinalParamDescription() + 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)',
@@ -270,7 +273,7 @@ class ApiSetNotificationTimestamp extends ApiBase {
$ps = $this->getPageSet();
return array_merge(
parent::getPossibleErrors(),
- $ps->getPossibleErrors(),
+ $ps->getFinalPossibleErrors(),
$this->getRequireMaxOneParameterErrorMessages(
array( 'timestamp', 'torevid', 'newerthanrevid' ) ),
$this->getRequireOnlyOneParameterErrorMessages(
diff --git a/includes/api/ApiUpload.php b/includes/api/ApiUpload.php
index 7d67aa6e..467eccf8 100644
--- a/includes/api/ApiUpload.php
+++ b/includes/api/ApiUpload.php
@@ -56,15 +56,19 @@ class ApiUpload extends ApiBase {
$this->mParams['chunk'] = $request->getFileName( 'chunk' );
// Copy the session key to the file key, for backward compatibility.
- if( !$this->mParams['filekey'] && $this->mParams['sessionkey'] ) {
+ if ( !$this->mParams['filekey'] && $this->mParams['sessionkey'] ) {
$this->mParams['filekey'] = $this->mParams['sessionkey'];
}
// Select an upload module
- if ( !$this->selectUploadModule() ) {
- return; // not a true upload, but a status request or similar
- } elseif ( !isset( $this->mUpload ) ) {
- $this->dieUsage( 'No upload module set', 'nomodule' );
+ try {
+ if ( !$this->selectUploadModule() ) {
+ return; // not a true upload, but a status request or similar
+ } elseif ( !isset( $this->mUpload ) ) {
+ $this->dieUsage( 'No upload module set', 'nomodule' );
+ }
+ } catch ( UploadStashException $e ) { // XXX: don't spam exception log
+ $this->dieUsage( get_class( $e ) . ": " . $e->getMessage(), 'stasherror' );
}
// First check permission to upload
@@ -82,7 +86,7 @@ class ApiUpload extends ApiBase {
// Check if the uploaded file is sane
if ( $this->mParams['chunk'] ) {
$maxSize = $this->mUpload->getMaxUploadSize();
- if( $this->mParams['filesize'] > $maxSize ) {
+ if ( $this->mParams['filesize'] > $maxSize ) {
$this->dieUsage( 'The file you submitted was too large', 'file-too-large' );
}
if ( !$this->mUpload->getTitle() ) {
@@ -106,9 +110,13 @@ class ApiUpload extends ApiBase {
}
// Get the result based on the current upload context:
- $result = $this->getContextResult();
- if ( $result['result'] === 'Success' ) {
- $result['imageinfo'] = $this->mUpload->getImageInfo( $this->getResult() );
+ try {
+ $result = $this->getContextResult();
+ if ( $result['result'] === 'Success' ) {
+ $result['imageinfo'] = $this->mUpload->getImageInfo( $this->getResult() );
+ }
+ } catch ( UploadStashException $e ) { // XXX: don't spam exception log
+ $this->dieUsage( get_class( $e ) . ": " . $e->getMessage(), 'stasherror' );
}
$this->getResult()->addValue( null, $this->getModuleName(), $result );
@@ -144,7 +152,7 @@ class ApiUpload extends ApiBase {
* @return array
*/
private function getStashResult( $warnings ) {
- $result = array ();
+ $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
try {
@@ -216,27 +224,27 @@ class ApiUpload extends ApiBase {
// Check we added the last chunk:
if ( $this->mParams['offset'] + $chunkSize == $this->mParams['filesize'] ) {
if ( $this->mParams['async'] ) {
- $progress = UploadBase::getSessionStatus( $this->mParams['filekey'] );
+ $progress = UploadBase::getSessionStatus( $filekey );
if ( $progress && $progress['result'] === 'Poll' ) {
$this->dieUsage( "Chunk assembly already in progress.", 'stashfailed' );
}
UploadBase::setSessionStatus(
- $this->mParams['filekey'],
+ $filekey,
array( 'result' => 'Poll',
'stage' => 'queued', 'status' => Status::newGood() )
);
$ok = JobQueueGroup::singleton()->push( new AssembleUploadChunksJob(
- Title::makeTitle( NS_FILE, $this->mParams['filekey'] ),
+ Title::makeTitle( NS_FILE, $filekey ),
array(
- 'filename' => $this->mParams['filename'],
- 'filekey' => $this->mParams['filekey'],
- 'session' => $this->getContext()->exportSession()
+ 'filename' => $this->mParams['filename'],
+ 'filekey' => $filekey,
+ 'session' => $this->getContext()->exportSession()
)
) );
if ( $ok ) {
$result['result'] = 'Poll';
} else {
- UploadBase::setSessionStatus( $this->mParams['filekey'], false );
+ UploadBase::setSessionStatus( $filekey, false );
$this->dieUsage(
"Failed to start AssembleUploadChunks.php", 'stashfailed' );
}
@@ -358,11 +366,9 @@ 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'],
@@ -404,6 +410,10 @@ class ApiUpload extends ApiBase {
$this->dieUsageMsg( 'copyuploadbaddomain' );
}
+ if ( !UploadFromUrl::isAllowedUrl( $this->mParams['url'] ) ) {
+ $this->dieUsageMsg( 'copyuploadbadurl' );
+ }
+
$async = false;
if ( $this->mParams['asyncdownload'] ) {
$this->checkAsyncDownloadEnabled();
@@ -452,9 +462,9 @@ class ApiUpload extends ApiBase {
$verification = $this->mUpload->verifyUpload();
if ( $verification['status'] === UploadBase::OK ) {
return;
- } else {
- return $this->checkVerification( $verification );
}
+
+ $this->checkVerification( $verification );
}
/**
@@ -463,8 +473,8 @@ class ApiUpload extends ApiBase {
protected function checkVerification( array $verification ) {
global $wgFileExtensions;
- // TODO: Move them to ApiBase's message map
- switch( $verification['status'] ) {
+ // @todo Move them to ApiBase's message map
+ switch ( $verification['status'] ) {
// Recoverable errors
case UploadBase::MIN_LENGTH_PARTNAME:
$this->dieRecoverableError( 'filename-tooshort', 'filename' );
@@ -494,7 +504,7 @@ class ApiUpload extends ApiBase {
case UploadBase::FILETYPE_BADTYPE:
$extradata = array(
'filetype' => $verification['finalExt'],
- 'allowed' => $wgFileExtensions
+ 'allowed' => array_values( array_unique( $wgFileExtensions ) )
);
$this->getResult()->setIndexedTagName( $extradata['allowed'], 'ext' );
@@ -555,7 +565,8 @@ class ApiUpload extends ApiBase {
if ( isset( $warnings['exists'] ) ) {
$warning = $warnings['exists'];
unset( $warnings['exists'] );
- $warnings[$warning['warning']] = $warning['file']->getName();
+ $localFile = isset( $warning['normalizedFile'] ) ? $warning['normalizedFile'] : $warning['file'];
+ $warnings[$warning['warning']] = $localFile->getName();
}
}
return $warnings;
@@ -596,12 +607,12 @@ class ApiUpload extends ApiBase {
$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()
+ 'filename' => $this->mParams['filename'],
+ 'filekey' => $this->mParams['filekey'],
+ 'comment' => $this->mParams['comment'],
+ 'text' => $this->mParams['text'],
+ 'watch' => $watch,
+ 'session' => $this->getContext()->exportSession()
)
) );
if ( $ok ) {
@@ -653,13 +664,6 @@ class ApiUpload extends ApiBase {
}
}
- protected function checkChunkedEnabled() {
- global $wgAllowChunkedUploads;
- if ( !$wgAllowChunkedUploads ) {
- $this->dieUsage( 'Chunked uploads disabled', 'chunkeduploaddisabled' );
- }
- }
-
public function mustBePosted() {
return true;
}
@@ -816,7 +820,7 @@ class ApiUpload extends ApiBase {
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( 'code' => 'stasherror', 'info' => 'An upload stash error occurred' ),
array( 'fileexists-forbidden' ),
array( 'fileexists-shared-forbidden' ),
)
diff --git a/includes/api/ApiUserrights.php b/includes/api/ApiUserrights.php
index b9b1eeda..7d308285 100644
--- a/includes/api/ApiUserrights.php
+++ b/includes/api/ApiUserrights.php
@@ -38,6 +38,7 @@ class ApiUserrights extends ApiBase {
$user = $this->getUrUser();
$form = new UserrightsPage;
+ $form->setContext( $this->getContext() );
$r['user'] = $user->getName();
$r['userid'] = $user->getId();
list( $r['added'], $r['removed'] ) =
@@ -62,10 +63,10 @@ class ApiUserrights extends ApiBase {
$params = $this->extractRequestParams();
$form = new UserrightsPage;
+ $form->setContext( $this->getContext() );
$status = $form->fetchUser( $params['user'] );
if ( !$status->isOK() ) {
- $errors = $status->getErrorsArray();
- $this->dieUsageMsg( $errors[0] );
+ $this->dieStatus( $status );
} else {
$user = $status->value;
}
@@ -83,7 +84,7 @@ class ApiUserrights extends ApiBase {
}
public function getAllowedParams() {
- return array (
+ return array(
'user' => array(
ApiBase::PARAM_TYPE => 'string',
ApiBase::PARAM_REQUIRED => true
diff --git a/includes/api/ApiWatch.php b/includes/api/ApiWatch.php
index 3e51299f..c7d636a1 100644
--- a/includes/api/ApiWatch.php
+++ b/includes/api/ApiWatch.php
@@ -36,6 +36,9 @@ class ApiWatch extends ApiBase {
if ( !$user->isLoggedIn() ) {
$this->dieUsage( 'You must be logged-in to have a watchlist', 'notloggedin' );
}
+ if ( !$user->isAllowed( 'editmywatchlist' ) ) {
+ $this->dieUsage( 'You don\'t have permission to edit your watchlist', 'permissiondenied' );
+ }
$params = $this->extractRequestParams();
$title = Title::newFromText( $params['title'] );
@@ -57,19 +60,19 @@ class ApiWatch extends ApiBase {
if ( $params['unwatch'] ) {
$res['unwatched'] = '';
$res['message'] = $this->msg( 'removedwatchtext', $title->getPrefixedText() )->title( $title )->parseAsBlock();
- $success = UnwatchAction::doUnwatch( $title, $user );
+ $status = UnwatchAction::doUnwatch( $title, $user );
} else {
$res['watched'] = '';
$res['message'] = $this->msg( 'addedwatchtext', $title->getPrefixedText() )->title( $title )->parseAsBlock();
- $success = WatchAction::doWatch( $title, $user );
+ $status = WatchAction::doWatch( $title, $user );
}
if ( !is_null( $oldLang ) ) {
$this->getContext()->setLanguage( $oldLang ); // Reset language to $oldLang
}
- if ( !$success ) {
- $this->dieUsageMsg( 'hookaborted' );
+ if ( !$status->isOK() ) {
+ $this->dieStatus( $status );
}
$this->getResult()->addValue( null, $this->getModuleName(), $res );
}
diff --git a/includes/cache/BacklinkCache.php b/includes/cache/BacklinkCache.php
index a59cc9a2..193f20fe 100644
--- a/includes/cache/BacklinkCache.php
+++ b/includes/cache/BacklinkCache.php
@@ -20,6 +20,7 @@
*
* @file
* @author Tim Starling
+ * @author Aaron Schulz
* @copyright © 2009, Tim Starling, Domas Mituzas
* @copyright © 2010, Max Sem
* @copyright © 2011, Antoine Musso
@@ -103,7 +104,7 @@ class BacklinkCache {
self::$cache = new ProcessCacheLRU( 1 );
}
$dbKey = $title->getPrefixedDBkey();
- if ( !self::$cache->has( $dbKey, 'obj' ) ) {
+ if ( !self::$cache->has( $dbKey, 'obj', 3600 ) ) {
self::$cache->set( $dbKey, 'obj', new self( $title ) );
}
return self::$cache->get( $dbKey, 'obj' );
@@ -147,71 +148,73 @@ class BacklinkCache {
if ( !isset( $this->db ) ) {
$this->db = wfGetDB( DB_SLAVE );
}
-
return $this->db;
}
/**
* Get the backlinks for a given table. Cached in process memory only.
* @param $table String
- * @param $startId Integer or false
- * @param $endId Integer or false
+ * @param $startId Integer|false
+ * @param $endId Integer|false
+ * @param $max Integer|INF
* @return TitleArrayFromResult
*/
- public function getLinks( $table, $startId = false, $endId = false ) {
+ public function getLinks( $table, $startId = false, $endId = false, $max = INF ) {
+ return TitleArray::newFromResult( $this->queryLinks( $table, $startId, $endId, $max ) );
+ }
+
+ /**
+ * Get the backlinks for a given table. Cached in process memory only.
+ * @param $table String
+ * @param $startId Integer|false
+ * @param $endId Integer|false
+ * @param $max Integer|INF
+ * @return ResultWrapper
+ */
+ protected function queryLinks( $table, $startId, $endId, $max ) {
wfProfileIn( __METHOD__ );
$fromField = $this->getPrefix( $table ) . '_from';
- if ( $startId || $endId ) {
- // Partial range, not cached
- wfDebug( __METHOD__ . ": from DB (uncacheable range)\n" );
+ if ( !$startId && !$endId && is_infinite( $max )
+ && isset( $this->fullResultCache[$table] ) )
+ {
+ wfDebug( __METHOD__ . ": got results from cache\n" );
+ $res = $this->fullResultCache[$table];
+ } else {
+ wfDebug( __METHOD__ . ": got results from DB\n" );
$conds = $this->getConditions( $table );
-
// Use the from field in the condition rather than the joined page_id,
// because databases are stupid and don't necessarily propagate indexes.
if ( $startId ) {
$conds[] = "$fromField >= " . intval( $startId );
}
-
if ( $endId ) {
$conds[] = "$fromField <= " . intval( $endId );
}
+ $options = array( 'STRAIGHT_JOIN', 'ORDER BY' => $fromField );
+ if ( is_finite( $max ) && $max > 0 ) {
+ $options['LIMIT'] = $max;
+ }
$res = $this->getDB()->select(
array( $table, 'page' ),
array( 'page_namespace', 'page_title', 'page_id' ),
$conds,
__METHOD__,
- array(
- 'STRAIGHT_JOIN',
- 'ORDER BY' => $fromField
- ) );
- $ta = TitleArray::newFromResult( $res );
-
- wfProfileOut( __METHOD__ );
- return $ta;
- }
+ $options
+ );
- // @todo FIXME: Make this a function?
- if ( !isset( $this->fullResultCache[$table] ) ) {
- wfDebug( __METHOD__ . ": from DB\n" );
- $res = $this->getDB()->select(
- array( $table, 'page' ),
- array( 'page_namespace', 'page_title', 'page_id' ),
- $this->getConditions( $table ),
- __METHOD__,
- array(
- 'STRAIGHT_JOIN',
- 'ORDER BY' => $fromField,
- ) );
- $this->fullResultCache[$table] = $res;
+ if ( !$startId && !$endId && $res->numRows() < $max ) {
+ // The full results fit within the limit, so cache them
+ $this->fullResultCache[$table] = $res;
+ } else {
+ wfDebug( __METHOD__ . ": results from DB were uncacheable\n" );
+ }
}
- $ta = TitleArray::newFromResult( $this->fullResultCache[$table] );
-
wfProfileOut( __METHOD__ );
- return $ta;
+ return $res;
}
/**
@@ -234,7 +237,7 @@ class BacklinkCache {
} else {
$prefix = null;
wfRunHooks( 'BacklinkCacheGetPrefix', array( $table, &$prefix ) );
- if( $prefix ) {
+ if ( $prefix ) {
return $prefix;
} else {
throw new MWException( "Invalid table \"$table\" in " . __CLASS__ );
@@ -289,7 +292,7 @@ class BacklinkCache {
default:
$conds = null;
wfRunHooks( 'BacklinkCacheGetConditions', array( $table, $this->title, &$conds ) );
- if( !$conds ) {
+ if ( !$conds ) {
throw new MWException( "Invalid table \"$table\" in " . __CLASS__ );
}
}
@@ -309,7 +312,7 @@ class BacklinkCache {
/**
* Get the approximate number of backlinks
* @param $table String
- * @param $max integer Only count up to this many backlinks
+ * @param $max integer|INF Only count up to this many backlinks
* @return integer
*/
public function getNumLinks( $table, $max = INF ) {
@@ -335,20 +338,12 @@ class BacklinkCache {
}
// 4) fetch from the database ...
- if ( is_infinite( $max ) ) { // full count
- $count = $this->getLinks( $table )->count();
+ $count = $this->getLinks( $table, false, false, $max )->count();
+ if ( $count < $max ) { // full 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;
+ return min( $max, $count );
}
/**
@@ -395,8 +390,28 @@ class BacklinkCache {
}
// 4) ... finally fetch from the slow database :(
- $this->getLinks( $table );
- $cacheEntry = $this->partitionResult( $this->fullResultCache[$table], $batchSize );
+ $cacheEntry = array( 'numRows' => 0, 'batches' => array() ); // final result
+ // Do the selects in batches to avoid client-side OOMs (bug 43452).
+ // Use a LIMIT that plays well with $batchSize to keep equal sized partitions.
+ $selectSize = max( $batchSize, 200000 - ( 200000 % $batchSize ) );
+ $start = false;
+ do {
+ $res = $this->queryLinks( $table, $start, false, $selectSize );
+ $partitions = $this->partitionResult( $res, $batchSize, false );
+ // Merge the link count and range partitions for this chunk
+ $cacheEntry['numRows'] += $partitions['numRows'];
+ $cacheEntry['batches'] = array_merge( $cacheEntry['batches'], $partitions['batches'] );
+ if ( count( $partitions['batches'] ) ) {
+ list( $lStart, $lEnd ) = end( $partitions['batches'] );
+ $start = $lEnd + 1; // pick up after this inclusive range
+ }
+ } while ( $partitions['numRows'] >= $selectSize );
+ // Make sure the first range has start=false and the last one has end=false
+ if ( count( $cacheEntry['batches'] ) ) {
+ $cacheEntry['batches'][0][0] = false;
+ $cacheEntry['batches'][count( $cacheEntry['batches'] ) - 1][1] = false;
+ }
+
// Save partitions to memcached
$wgMemc->set( $memcKey, $cacheEntry, self::CACHE_EXPIRY );
@@ -412,31 +427,32 @@ class BacklinkCache {
* Partition a DB result with backlinks in it into batches
* @param $res ResultWrapper database result
* @param $batchSize integer
+ * @param $isComplete bool Whether $res includes all the backlinks
* @throws MWException
- * @return array @see
+ * @return array
*/
- protected function partitionResult( $res, $batchSize ) {
+ protected function partitionResult( $res, $batchSize, $isComplete = true ) {
$batches = array();
$numRows = $res->numRows();
$numBatches = ceil( $numRows / $batchSize );
for ( $i = 0; $i < $numBatches; $i++ ) {
- if ( $i == 0 ) {
+ if ( $i == 0 && $isComplete ) {
$start = false;
} else {
- $rowNum = intval( $numRows * $i / $numBatches );
+ $rowNum = $i * $batchSize;
$res->seek( $rowNum );
$row = $res->fetchObject();
- $start = $row->page_id;
+ $start = (int)$row->page_id;
}
- if ( $i == $numBatches - 1 ) {
+ if ( $i == ( $numBatches - 1 ) && $isComplete ) {
$end = false;
} else {
- $rowNum = intval( $numRows * ( $i + 1 ) / $numBatches );
+ $rowNum = min( $numRows - 1, ( $i + 1 ) * $batchSize - 1 );
$res->seek( $rowNum );
$row = $res->fetchObject();
- $end = $row->page_id - 1;
+ $end = (int)$row->page_id;
}
# Sanity check order
diff --git a/includes/cache/CacheDependency.php b/includes/cache/CacheDependency.php
index 0f047e80..32bcdf7f 100644
--- a/includes/cache/CacheDependency.php
+++ b/includes/cache/CacheDependency.php
@@ -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 30a72174..d4bf5ee6 100644
--- a/includes/cache/FileCacheBase.php
+++ b/includes/cache/FileCacheBase.php
@@ -35,7 +35,7 @@ abstract class FileCacheBase {
/* lazy loaded */
protected $mCached;
- /* @TODO: configurable? */
+ /* @todo configurable? */
const MISS_FACTOR = 15; // log 1 every MISS_FACTOR cache misses
const MISS_TTL_SEC = 3600; // how many seconds ago is "recent"
@@ -138,7 +138,7 @@ abstract class FileCacheBase {
* @return string
*/
public function fetchText() {
- if( $this->useGzip() ) {
+ if ( $this->useGzip() ) {
$fh = gzopen( $this->cachePath(), 'rb' );
return stream_get_contents( $fh );
} else {
@@ -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;
}
diff --git a/includes/cache/GenderCache.php b/includes/cache/GenderCache.php
index 63e4226d..a933527a 100644
--- a/includes/cache/GenderCache.php
+++ b/includes/cache/GenderCache.php
@@ -66,15 +66,14 @@ class GenderCache {
public function getGenderOf( $username, $caller = '' ) {
global $wgUser;
- if( $username instanceof User ) {
+ if ( $username instanceof User ) {
$username = $username->getName();
}
$username = self::normalizeUsername( $username );
if ( !isset( $this->cache[$username] ) ) {
-
if ( $this->misses >= $this->missLimit && $wgUser->getName() !== $username ) {
- if( $this->misses === $this->missLimit ) {
+ if ( $this->misses === $this->missLimit ) {
$this->misses++;
wfDebug( __METHOD__ . ": too many misses, returning default onwards\n" );
}
@@ -84,7 +83,6 @@ class GenderCache {
$this->misses++;
$this->doQuery( $username, $caller );
}
-
}
/* Undefined if there is a valid username which for some reason doesn't
@@ -102,7 +100,9 @@ class GenderCache {
public function doLinkBatch( $data, $caller = '' ) {
$users = array();
foreach ( $data as $ns => $pagenames ) {
- if ( !MWNamespace::hasGenderDistinction( $ns ) ) continue;
+ if ( !MWNamespace::hasGenderDistinction( $ns ) ) {
+ continue;
+ }
foreach ( array_keys( $pagenames ) as $username ) {
$users[$username] = true;
}
@@ -143,14 +143,14 @@ class GenderCache {
$default = $this->getDefault();
$usersToCheck = array();
- foreach ( (array) $users as $value ) {
+ foreach ( (array)$users as $value ) {
$name = self::normalizeUsername( $value );
// Skip users whose gender setting we already know
if ( !isset( $this->cache[$name] ) ) {
// For existing users, this value will be overwritten by the correct value
$this->cache[$name] = $default;
// query only for valid names, which can be in the database
- if( User::isValidUserName( $name ) ) {
+ if ( User::isValidUserName( $name ) ) {
$usersToCheck[] = $name;
}
}
diff --git a/includes/cache/HTMLCacheUpdate.php b/includes/cache/HTMLCacheUpdate.php
index 88e79281..992809ef 100644
--- a/includes/cache/HTMLCacheUpdate.php
+++ b/includes/cache/HTMLCacheUpdate.php
@@ -46,8 +46,6 @@ class HTMLCacheUpdate implements DeferrableUpdate {
}
public function doUpdate() {
- global $wgMaxBacklinksInvalidate;
-
wfProfileIn( __METHOD__ );
$job = new HTMLCacheUpdateJob(
@@ -60,13 +58,14 @@ class HTMLCacheUpdate implements DeferrableUpdate {
);
$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
+ if ( $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
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->onTransactionIdle( function() use ( $job ) {
+ $job->run(); // just do the purge query now
+ } );
}
wfProfileOut( __METHOD__ );
diff --git a/includes/cache/HTMLFileCache.php b/includes/cache/HTMLFileCache.php
index 055fd685..ab379116 100644
--- a/includes/cache/HTMLFileCache.php
+++ b/includes/cache/HTMLFileCache.php
@@ -163,7 +163,7 @@ class HTMLFileCache extends FileCacheBase {
return $text;
}
- wfDebug( __METHOD__ . "()\n", false);
+ wfDebug( __METHOD__ . "()\n", false );
$now = wfTimestampNow();
if ( $this->useGzip() ) {
@@ -182,7 +182,7 @@ class HTMLFileCache extends FileCacheBase {
// gzip output to buffer as needed and set headers...
if ( $this->useGzip() ) {
- // @TODO: ugly wfClientAcceptsGzip() function - use context!
+ // @todo Ugly wfClientAcceptsGzip() function - use context!
if ( wfClientAcceptsGzip() ) {
header( 'Content-Encoding: gzip' );
return $compressed;
diff --git a/includes/cache/LinkBatch.php b/includes/cache/LinkBatch.php
index 72a2e8e5..48b60aa9 100644
--- a/includes/cache/LinkBatch.php
+++ b/includes/cache/LinkBatch.php
@@ -39,7 +39,7 @@ class LinkBatch {
protected $caller;
function __construct( $arr = array() ) {
- foreach( $arr as $item ) {
+ foreach ( $arr as $item ) {
$this->addObj( $item );
}
}
@@ -98,7 +98,7 @@ class LinkBatch {
* @return bool
*/
public function isEmpty() {
- return ($this->getSize() == 0);
+ return $this->getSize() == 0;
}
/**
diff --git a/includes/cache/LinkCache.php b/includes/cache/LinkCache.php
index 0e41e265..54de1989 100644
--- a/includes/cache/LinkCache.php
+++ b/includes/cache/LinkCache.php
@@ -35,18 +35,44 @@ class LinkCache {
private $mGoodLinkFields = array();
private $mBadLinks = array();
private $mForUpdate = false;
+ private $useDatabase = true;
/**
- * Get an instance of this class
+ * @var LinkCache
+ */
+ protected static $instance;
+
+ /**
+ * Get an instance of this class.
*
* @return LinkCache
*/
static function &singleton() {
- static $instance;
- if ( !isset( $instance ) ) {
- $instance = new LinkCache;
+ if ( self::$instance ) {
+ return self::$instance;
}
- return $instance;
+ self::$instance = new LinkCache;
+ return self::$instance;
+ }
+
+ /**
+ * Destroy the singleton instance, a new one will be created next time
+ * singleton() is called.
+ * @since 1.22
+ */
+ static function destroySingleton() {
+ self::$instance = null;
+ }
+
+ /**
+ * Set the singleton instance to a given object.
+ * Since we do not have an interface for LinkCache, you have to be sure the
+ * given object implements all the LinkCache public methods.
+ * @param LinkCache $instance
+ * @since 1.22
+ */
+ static function setSingleton( LinkCache $instance ) {
+ self::$instance = $instance;
}
/**
@@ -156,8 +182,13 @@ class LinkCache {
unset( $this->mGoodLinkFields[$dbkey] );
}
- public function getGoodLinks() { return $this->mGoodLinks; }
- public function getBadLinks() { return array_keys( $this->mBadLinks ); }
+ public function getGoodLinks() {
+ return $this->mGoodLinks;
+ }
+
+ public function getBadLinks() {
+ return array_keys( $this->mBadLinks );
+ }
/**
* Add a title to the link cache, return the page_id or zero if non-existent
@@ -167,7 +198,7 @@ class LinkCache {
*/
public function addLink( $title ) {
$nt = Title::newFromDBkey( $title );
- if( $nt ) {
+ if ( $nt ) {
return $this->addLinkObj( $nt );
} else {
return 0;
@@ -175,6 +206,19 @@ class LinkCache {
}
/**
+ * Enable or disable database use.
+ * @since 1.22
+ * @param $value Boolean
+ * @return Boolean
+ */
+ public function useDatabase( $value = null ) {
+ if ( $value !== null ) {
+ $this->useDatabase = (bool)$value;
+ }
+ return $this->useDatabase;
+ }
+
+ /**
* Add a title to the link cache, return the page_id or zero if non-existent
*
* @param $nt Title object to add
@@ -201,6 +245,10 @@ class LinkCache {
return 0;
}
+ if( !$this->useDatabase ) {
+ return 0;
+ }
+
# Some fields heavily used for linking...
if ( $this->mForUpdate ) {
$db = wfGetDB( DB_MASTER );
@@ -215,7 +263,9 @@ class LinkCache {
}
$f = array( 'page_id', 'page_len', 'page_is_redirect', 'page_latest' );
- if ( $wgContentHandlerUseDB ) $f[] = 'page_content_model';
+ if ( $wgContentHandlerUseDB ) {
+ $f[] = 'page_content_model';
+ }
$s = $db->selectRow( 'page', $f,
array( 'page_namespace' => $nt->getNamespace(), 'page_title' => $nt->getDBkey() ),
diff --git a/includes/cache/LocalisationCache.php b/includes/cache/LocalisationCache.php
index 009b9507..1bfd17bd 100644
--- a/includes/cache/LocalisationCache.php
+++ b/includes/cache/LocalisationCache.php
@@ -110,7 +110,7 @@ class LocalisationCache {
'dateFormats', 'datePreferences', 'datePreferenceMigrationMap',
'defaultDateFormat', 'extraUserToggles', 'specialPageAliases',
'imageFiles', 'preloadedMessages', 'namespaceGenderAliases',
- 'digitGroupingPattern', 'pluralRules', 'compiledPluralRules',
+ 'digitGroupingPattern', 'pluralRules', 'pluralRuleTypes', 'compiledPluralRules',
);
/**
@@ -160,6 +160,20 @@ class LocalisationCache {
*/
var $pluralRules = null;
+ /**
+ * Associative array of cached plural rule types. The key is the language
+ * code, the value is an array of plural rule types for that language. For
+ * example, $pluralRuleTypes['ar'] = ['zero', 'one', 'two', 'few', 'many'].
+ * The index for each rule type matches the index for the rule in
+ * $pluralRules, thus allowing correlation between the two. The reason we
+ * don't just use the type names as the keys in $pluralRules is because
+ * Language::convertPlural applies the rules based on numeric order (or
+ * explicit numeric parameter), not based on the name of the rule type. For
+ * example, {{plural:count|wordform1|wordform2|wordform3}}, rather than
+ * {{plural:count|one=wordform1|two=wordform2|many=wordform3}}.
+ */
+ var $pluralRuleTypes = null;
+
var $mergeableKeys = null;
/**
@@ -340,7 +354,6 @@ class LocalisationCache {
* @param $code
* @param $key
* @param $subkey
- * @return
*/
protected function loadSubitem( $code, $key, $subkey ) {
if ( !in_array( $key, self::$splitKeys ) ) {
@@ -370,6 +383,9 @@ class LocalisationCache {
/**
* Returns true if the cache identified by $code is missing or expired.
+ *
+ * @param string $code
+ *
* @return bool
*/
public function isExpired( $code ) {
@@ -480,9 +496,10 @@ class LocalisationCache {
* @return array
*/
protected function readPHPFile( $_fileName, $_fileType ) {
+ wfProfileIn( __METHOD__ );
// Disable APC caching
$_apcEnabled = ini_set( 'apc.cache_by_default', '0' );
- include( $_fileName );
+ include $_fileName;
ini_set( 'apc.cache_by_default', $_apcEnabled );
if ( $_fileType == 'core' || $_fileType == 'extension' ) {
@@ -490,8 +507,10 @@ class LocalisationCache {
} elseif ( $_fileType == 'aliases' ) {
$data = compact( 'aliases' );
} else {
+ wfProfileOut( __METHOD__ );
throw new MWException( __METHOD__ . ": Invalid file type: $_fileType" );
}
+ wfProfileOut( __METHOD__ );
return $data;
}
@@ -506,7 +525,7 @@ class LocalisationCache {
}
try {
$compiledRules = CLDRPluralRuleEvaluator::compile( $rules );
- } catch( CLDRPluralRuleError $e ) {
+ } catch ( CLDRPluralRuleError $e ) {
wfDebugLog( 'l10n', $e->getMessage() . "\n" );
return array();
}
@@ -519,17 +538,8 @@ class LocalisationCache {
* @since 1.20
*/
public function getPluralRules( $code ) {
- global $IP;
-
if ( $this->pluralRules === null ) {
- $cldrPlural = "$IP/languages/data/plurals.xml";
- $mwPlural = "$IP/languages/data/plurals-mediawiki.xml";
- // Load CLDR plural rules
- $this->loadPluralFile( $cldrPlural );
- if ( file_exists( $mwPlural ) ) {
- // Override or extend
- $this->loadPluralFile( $mwPlural );
- }
+ $this->loadPluralFiles();
}
if ( !isset( $this->pluralRules[$code] ) ) {
return null;
@@ -539,6 +549,37 @@ class LocalisationCache {
}
/**
+ * Get the plural rule types for a given language from the XML files.
+ * Cached.
+ * @since 1.22
+ */
+ public function getPluralRuleTypes( $code ) {
+ if ( $this->pluralRuleTypes === null ) {
+ $this->loadPluralFiles();
+ }
+ if ( !isset( $this->pluralRuleTypes[$code] ) ) {
+ return null;
+ } else {
+ return $this->pluralRuleTypes[$code];
+ }
+ }
+
+ /**
+ * Load the plural XML files.
+ */
+ protected function loadPluralFiles() {
+ global $IP;
+ $cldrPlural = "$IP/languages/data/plurals.xml";
+ $mwPlural = "$IP/languages/data/plurals-mediawiki.xml";
+ // Load CLDR plural rules
+ $this->loadPluralFile( $cldrPlural );
+ if ( file_exists( $mwPlural ) ) {
+ // Override or extend
+ $this->loadPluralFile( $mwPlural );
+ }
+ }
+
+ /**
* Load a plural XML file with the given filename, compile the relevant
* rules, and save the compiled rules in a process-local cache.
*/
@@ -549,12 +590,16 @@ class LocalisationCache {
foreach ( $rulesets as $ruleset ) {
$codes = $ruleset->getAttribute( 'locales' );
$rules = array();
+ $ruleTypes = array();
$ruleElements = $ruleset->getElementsByTagName( "pluralRule" );
foreach ( $ruleElements as $elt ) {
+ $ruleType = $elt->getAttribute( 'count' );
$rules[] = $elt->nodeValue;
+ $ruleTypes[] = $ruleType;
}
foreach ( explode( ' ', $codes ) as $code ) {
$this->pluralRules[$code] = $rules;
+ $this->pluralRuleTypes[$code] = $ruleTypes;
}
}
}
@@ -566,9 +611,11 @@ class LocalisationCache {
*/
protected function readSourceFilesAndRegisterDeps( $code, &$deps ) {
global $IP;
+ wfProfileIn( __METHOD__ );
$fileName = Language::getMessagesFileName( $code );
if ( !file_exists( $fileName ) ) {
+ wfProfileOut( __METHOD__ );
return false;
}
@@ -579,10 +626,13 @@ class LocalisationCache {
$data['pluralRules'] = $this->getPluralRules( $code );
# And for PHP
$data['compiledPluralRules'] = $this->getCompiledPluralRules( $code );
+ # Load plural rule types
+ $data['pluralRuleTypes'] = $this->getPluralRuleTypes( $code );
$deps['plurals'] = new FileDependency( "$IP/languages/data/plurals.xml" );
$deps['plurals-mw'] = new FileDependency( "$IP/languages/data/plurals-mediawiki.xml" );
+ wfProfileOut( __METHOD__ );
return $data;
}
@@ -673,6 +723,7 @@ class LocalisationCache {
wfProfileIn( __METHOD__ );
if ( !$code ) {
+ wfProfileOut( __METHOD__ );
throw new MWException( "Invalid language code requested" );
}
$this->recachedLangs[$code] = true;
@@ -741,6 +792,7 @@ class LocalisationCache {
# This is done after the core because we know the fallback sequence now.
# But it has a higher precedence for merging so that we can support things
# like site-specific message overrides.
+ wfProfileIn( __METHOD__ . '-extensions' );
$allData = $initialData;
foreach ( $wgExtensionMessagesFiles as $fileName ) {
$data = $this->readPHPFile( $fileName, 'extension' );
@@ -761,6 +813,7 @@ class LocalisationCache {
foreach ( $coreData as $key => $item ) {
$this->mergeItem( $key, $allData[$key], $item );
}
+ wfProfileOut( __METHOD__ . '-extensions' );
# Add cache dependencies for any referenced globals
$deps['wgExtensionMessagesFiles'] = new GlobalDependency( 'wgExtensionMessagesFiles' );
@@ -786,6 +839,10 @@ class LocalisationCache {
if ( $allData['compiledPluralRules'] === null ) {
$allData['compiledPluralRules'] = array();
}
+ # If there were no plural rule types, return an empty array
+ if ( $allData['pluralRuleTypes'] === null ) {
+ $allData['pluralRuleTypes'] = array();
+ }
# Set the list keys
$allData['list'] = array();
@@ -796,6 +853,7 @@ class LocalisationCache {
wfRunHooks( 'LocalisationCacheRecache', array( $this, $code, &$allData ) );
if ( is_null( $allData['namespaceNames'] ) ) {
+ wfProfileOut( __METHOD__ );
throw new MWException( __METHOD__ . ': Localisation data failed sanity check! ' .
'Check that your languages/messages/MessagesEn.php file is intact.' );
}
@@ -810,6 +868,7 @@ class LocalisationCache {
}
# Save to the persistent cache
+ wfProfileIn( __METHOD__ . '-write' );
$this->store->startWrite( $code );
foreach ( $allData as $key => $value ) {
if ( in_array( $key, self::$splitKeys ) ) {
@@ -821,6 +880,7 @@ class LocalisationCache {
}
}
$this->store->finishWrite();
+ wfProfileOut( __METHOD__ . '-write' );
# Clear out the MessageBlobStore
# HACK: If using a null (i.e. disabled) storage backend, we
@@ -868,6 +928,7 @@ class LocalisationCache {
unset( $this->loadedItems[$code] );
unset( $this->loadedSubitems[$code] );
unset( $this->initialisedLangs[$code] );
+ unset( $this->shallowFallbacks[$code] );
foreach ( $this->shallowFallbacks as $shallowCode => $fbCode ) {
if ( $fbCode === $code ) {
@@ -921,7 +982,7 @@ interface LCStore {
/**
* Start a write transaction.
- * @param $code Language code
+ * @param string $code Language code
*/
function startWrite( $code );
@@ -933,8 +994,8 @@ interface LCStore {
/**
* Set a key to a given value. startWrite() must be called before this
* is called, and finishWrite() must be called afterwards.
- * @param $key
- * @param $value
+ * @param string $key
+ * @param mixed $value
*/
function set( $key, $value );
}
@@ -1284,5 +1345,4 @@ class LocalisationCache_BulkLoad extends LocalisationCache {
$this->unload( $code );
}
}
-
}
diff --git a/includes/cache/MessageCache.php b/includes/cache/MessageCache.php
index c406b5c3..a92c87f4 100644
--- a/includes/cache/MessageCache.php
+++ b/includes/cache/MessageCache.php
@@ -22,14 +22,29 @@
*/
/**
- *
+ * MediaWiki message cache structure version.
+ * Bump this whenever the message cache format has changed.
*/
-define( 'MSG_LOAD_TIMEOUT', 60 );
-define( 'MSG_LOCK_TIMEOUT', 10 );
-define( 'MSG_WAIT_TIMEOUT', 10 );
define( 'MSG_CACHE_VERSION', 1 );
/**
+ * Memcached timeout when loading a key.
+ * See MessageCache::load()
+ */
+define( 'MSG_LOAD_TIMEOUT', 60 );
+
+/**
+ * Memcached timeout when locking a key for a writing operation.
+ * See MessageCache::lock()
+ */
+define( 'MSG_LOCK_TIMEOUT', 30 );
+/**
+ * Number of times we will try to acquire a lock from Memcached.
+ * This comes in addition to MSG_LOCK_TIMEOUT.
+ */
+define( 'MSG_WAIT_TIMEOUT', 30 );
+
+/**
* Message cache
* Performs various MediaWiki namespace-related functions
* @ingroup Cache
@@ -44,10 +59,16 @@ class MessageCache {
*/
protected $mCache;
- // Should mean that database cannot be used, but check
+ /**
+ * Should mean that database cannot be used, but check
+ * @var bool $mDisable
+ */
protected $mDisable;
- /// Lifetime for cache, used by object caching
+ /**
+ * Lifetime for cache, used by object caching.
+ * Set on construction, see __construct().
+ */
protected $mExpiry;
/**
@@ -56,18 +77,21 @@ class MessageCache {
*/
protected $mParserOptions, $mParser;
- /// Variable for tracking which variables are already loaded
+ /**
+ * Variable for tracking which variables are already loaded
+ * @var array $mLoadedLanguages
+ */
protected $mLoadedLanguages = array();
/**
* Singleton instance
*
- * @var MessageCache
+ * @var MessageCache $instance
*/
private static $instance;
/**
- * @var bool
+ * @var bool $mInParser
*/
protected $mInParser = false;
@@ -75,12 +99,16 @@ class MessageCache {
* Get the signleton instance of this class
*
* @since 1.18
- * @return MessageCache object
+ * @return MessageCache
*/
public static function singleton() {
if ( is_null( self::$instance ) ) {
global $wgUseDatabaseMessages, $wgMsgCacheExpiry;
- self::$instance = new self( wfGetMessageCacheStorage(), $wgUseDatabaseMessages, $wgMsgCacheExpiry );
+ self::$instance = new self(
+ wfGetMessageCacheStorage(),
+ $wgUseDatabaseMessages,
+ $wgMsgCacheExpiry
+ );
}
return self::$instance;
}
@@ -94,6 +122,11 @@ class MessageCache {
self::$instance = null;
}
+ /**
+ * @param ObjectCache $memCached A cache instance. If none, fall back to CACHE_NONE.
+ * @param bool $useDB
+ * @param int $expiry Lifetime for cache. @see $mExpiry.
+ */
function __construct( $memCached, $useDB, $expiry ) {
if ( !$memCached ) {
$memCached = wfGetCache( CACHE_NONE );
@@ -119,15 +152,13 @@ class MessageCache {
/**
* Try to load the cache from a local file.
- * Actual format of the file depends on the $wgLocalMessageCacheSerialized
- * setting.
*
* @param string $hash the hash of contents, to check validity.
- * @param $code Mixed: Optional language code, see documenation of load().
- * @return bool on failure.
+ * @param Mixed $code Optional language code, see documenation of load().
+ * @return array The cache array
*/
- function loadFromLocal( $hash, $code ) {
- global $wgCacheDirectory, $wgLocalMessageCacheSerialized;
+ function getLocalCache( $hash, $code ) {
+ global $wgCacheDirectory;
$filename = "$wgCacheDirectory/messages-" . wfWikiID() . "-$code";
@@ -139,31 +170,19 @@ class MessageCache {
return false; // No cache file
}
- if ( $wgLocalMessageCacheSerialized ) {
- // Check to see if the file has the hash specified
- $localHash = fread( $file, 32 );
- if ( $hash === $localHash ) {
- // All good, get the rest of it
- $serialized = '';
- while ( !feof( $file ) ) {
- $serialized .= fread( $file, 100000 );
- }
- fclose( $file );
- return $this->setCache( unserialize( $serialized ), $code );
- } else {
- fclose( $file );
- return false; // Wrong hash
+ // Check to see if the file has the hash specified
+ $localHash = fread( $file, 32 );
+ if ( $hash === $localHash ) {
+ // All good, get the rest of it
+ $serialized = '';
+ while ( !feof( $file ) ) {
+ $serialized .= fread( $file, 100000 );
}
+ fclose( $file );
+ return unserialize( $serialized );
} else {
- $localHash = substr( fread( $file, 40 ), 8 );
fclose( $file );
- if ( $hash != $localHash ) {
- return false; // Wrong hash
- }
-
- # Require overwrites the member variable or just shadows it?
- require( $filename );
- return $this->setCache( $this->mCache, $code );
+ return false; // Wrong hash
}
}
@@ -192,55 +211,6 @@ class MessageCache {
wfRestoreWarnings();
}
- function saveToScript( $array, $hash, $code ) {
- global $wgCacheDirectory;
-
- $filename = "$wgCacheDirectory/messages-" . wfWikiID() . "-$code";
- $tempFilename = $filename . '.tmp';
- wfMkdirParents( $wgCacheDirectory, null, __METHOD__ ); // might fail
-
- wfSuppressWarnings();
- $file = fopen( $tempFilename, 'w' );
- wfRestoreWarnings();
-
- if ( !$file ) {
- wfDebug( "Unable to open local cache file for writing\n" );
- return;
- }
-
- fwrite( $file, "<?php\n//$hash\n\n \$this->mCache = array(" );
-
- foreach ( $array as $key => $message ) {
- $key = $this->escapeForScript( $key );
- $message = $this->escapeForScript( $message );
- fwrite( $file, "'$key' => '$message',\n" );
- }
-
- fwrite( $file, ");\n?>" );
- fclose( $file);
- rename( $tempFilename, $filename );
- }
-
- function escapeForScript( $string ) {
- $string = str_replace( '\\', '\\\\', $string );
- $string = str_replace( '\'', '\\\'', $string );
- return $string;
- }
-
- /**
- * Set the cache to $cache, if it is valid. Otherwise set the cache to false.
- *
- * @return bool
- */
- function setCache( $cache, $code ) {
- if ( isset( $cache['VERSION'] ) && $cache['VERSION'] == MSG_CACHE_VERSION ) {
- $this->mCache[$code] = $cache;
- return true;
- } else {
- return false;
- }
- }
-
/**
* Loads messages from caches or from database in this order:
* (1) local message cache (if $wgUseLocalMessageCache is enabled)
@@ -257,16 +227,14 @@ class MessageCache {
* or false if populating empty cache fails. Also returns true if MessageCache
* is disabled.
*
- * @param bool|String $code String: language to which load messages
+ * @param bool|String $code Language to which load messages
* @throws MWException
* @return bool
*/
function load( $code = false ) {
global $wgUseLocalMessageCache;
- $exception = null; // deferred error
-
- if( !is_string( $code ) ) {
+ if ( !is_string( $code ) ) {
# This isn't really nice, so at least make a note about it and try to
# fall back
wfDebug( __METHOD__ . " called without providing a language code\n" );
@@ -291,94 +259,161 @@ class MessageCache {
# Loading code starts
wfProfileIn( __METHOD__ );
$success = false; # Keep track of success
+ $staleCache = false; # a cache array with expired data, or false if none has been loaded
$where = array(); # Debug info, delayed to avoid spamming debug log too much
$cacheKey = wfMemcKey( 'messages', $code ); # Key in memc for messages
- # (1) local cache
+ # Local cache
# Hash of the contents is stored in memcache, to detect if local cache goes
- # out of date (due to update in other thread?)
+ # out of date (e.g. due to replace() on some other server)
if ( $wgUseLocalMessageCache ) {
wfProfileIn( __METHOD__ . '-fromlocal' );
$hash = $this->mMemc->get( wfMemcKey( 'messages', $code, 'hash' ) );
if ( $hash ) {
- $success = $this->loadFromLocal( $hash, $code );
- if ( $success ) $where[] = 'got from local cache';
+ $cache = $this->getLocalCache( $hash, $code );
+ if ( !$cache ) {
+ $where[] = 'local cache is empty or has the wrong hash';
+ } elseif ( $this->isCacheExpired( $cache ) ) {
+ $where[] = 'local cache is expired';
+ $staleCache = $cache;
+ } else {
+ $where[] = 'got from local cache';
+ $success = true;
+ $this->mCache[$code] = $cache;
+ }
}
wfProfileOut( __METHOD__ . '-fromlocal' );
}
- # (2) memcache
- # Fails if nothing in cache, or in the wrong version.
if ( !$success ) {
- wfProfileIn( __METHOD__ . '-fromcache' );
- $cache = $this->mMemc->get( $cacheKey );
- $success = $this->setCache( $cache, $code );
- if ( $success ) {
- $where[] = 'got from global cache';
- $this->saveToCaches( $cache, false, $code );
- }
- wfProfileOut( __METHOD__ . '-fromcache' );
- }
+ # Try the global cache. If it is empty, try to acquire a lock. If
+ # the lock can't be acquired, wait for the other thread to finish
+ # and then try the global cache a second time.
+ for ( $failedAttempts = 0; $failedAttempts < 2; $failedAttempts++ ) {
+ wfProfileIn( __METHOD__ . '-fromcache' );
+ $cache = $this->mMemc->get( $cacheKey );
+ if ( !$cache ) {
+ $where[] = 'global cache is empty';
+ } elseif ( $this->isCacheExpired( $cache ) ) {
+ $where[] = 'global cache is expired';
+ $staleCache = $cache;
+ } else {
+ $where[] = 'got from global cache';
+ $this->mCache[$code] = $cache;
+ $this->saveToCaches( $cache, 'local-only', $code );
+ $success = true;
+ }
- # (3)
- # Nothing in caches... so we need create one and store it in caches
- if ( !$success ) {
- $where[] = 'cache is empty';
- $where[] = 'loading from database';
-
- 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 ) { // 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 ) { // 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'." );
+ wfProfileOut( __METHOD__ . '-fromcache' );
+
+ if ( $success ) {
+ # Done, no need to retry
+ break;
+ }
+
+ # We need to call loadFromDB. Limit the concurrency to a single
+ # process. This prevents the site from going down when the cache
+ # expires.
+ $statusKey = wfMemcKey( 'messages', $code, 'status' );
+ $acquired = $this->mMemc->add( $statusKey, 'loading', MSG_LOAD_TIMEOUT );
+ if ( $acquired ) {
+ # Unlock the status key if there is an exception
+ $that = $this;
+ $statusUnlocker = new ScopedCallback( function () use ( $that, $statusKey ) {
+ $that->mMemc->delete( $statusKey );
+ } );
+
+ # Now let's regenerate
+ $where[] = 'loading from database';
+
+ # Lock the cache to prevent conflicting writes
+ # If this lock fails, it doesn't really matter, it just means the
+ # write is potentially non-atomic, e.g. the results of a replace()
+ # may be discarded.
+ if ( $this->lock( $cacheKey ) ) {
+ $mainUnlocker = new ScopedCallback( function () use ( $that, $cacheKey ) {
+ $that->unlock( $cacheKey );
+ } );
+ } else {
+ $mainUnlocker = null;
+ $where[] = 'could not acquire main lock';
+ }
+
+ $cache = $this->loadFromDB( $code );
+ $this->mCache[$code] = $cache;
+ $success = true;
+ $saveSuccess = $this->saveToCaches( $cache, 'all', $code );
+
+ # Unlock
+ ScopedCallback::consume( $mainUnlocker );
+ ScopedCallback::consume( $statusUnlocker );
+
+ if ( !$saveSuccess ) {
+ # Cache save has failed.
+ # There are two main scenarios where this could be a problem:
+ #
+ # - The cache is more than the maximum size (typically
+ # 1MB compressed).
+ #
+ # - Memcached has no space remaining in the relevant slab
+ # class. This is unlikely with recent versions of
+ # memcached.
+ #
+ # Either way, if there is a local cache, nothing bad will
+ # happen. If there is no local cache, disabling the message
+ # cache for all requests avoids incurring a loadFromDB()
+ # overhead on every request, and thus saves the wiki from
+ # complete downtime under moderate traffic conditions.
+ if ( !$wgUseLocalMessageCache ) {
+ $this->mMemc->set( $statusKey, 'error', 60 * 5 );
+ $where[] = 'could not save cache, disabled globally for 5 minutes';
+ } else {
+ $where[] = "could not save global cache";
+ }
}
+
+ # Load from DB complete, no need to retry
+ break;
+ } elseif ( $staleCache ) {
+ # Use the stale cache while some other thread constructs the new one
+ $where[] = 'using stale cache';
+ $this->mCache[$code] = $staleCache;
+ $success = true;
+ break;
+ } elseif ( $failedAttempts > 0 ) {
+ # Already retried once, still failed, so don't do another lock/unlock cycle
+ # This case will typically be hit if memcached is down, or if
+ # loadFromDB() takes longer than MSG_WAIT_TIMEOUT
+ $where[] = "could not acquire status key.";
+ break;
} else {
- $isc = null; // unlock
- $exception = new MWException( "Could not load cache from DB for '$code'." );
+ $status = $this->mMemc->get( $statusKey );
+ if ( $status === 'error' ) {
+ # Disable cache
+ break;
+ } else {
+ # Wait for the other thread to finish, then retry
+ $where[] = 'waited for other thread to complete';
+ $this->lock( $cacheKey );
+ $this->unlock( $cacheKey );
+ }
}
- } else {
- $exception = new MWException( "Could not acquire '$statusKey' lock." );
}
- $osc = null; // unlock
}
if ( !$success ) {
+ $where[] = 'loading FAILED - cache is disabled';
$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" );
- }
+ # This used to throw an exception, but that led to nasty side effects like
+ # the whole wiki being instantly down if the memcached server died
} else {
# All good, just record the success
- $info = implode( ', ', $where );
- wfDebug( __METHOD__ . ": Loading $code... $info\n" );
$this->mLoadedLanguages[$code] = true;
}
+ $info = implode( ', ', $where );
+ wfDebug( __METHOD__ . ": Loading $code... $info\n" );
wfProfileOut( __METHOD__ );
return $success;
}
@@ -388,8 +423,8 @@ class MessageCache {
* $wgMaxMsgCacheEntrySize are assigned a special value, and are loaded
* on-demand from the database later.
*
- * @param string $code language code.
- * @return Array: loaded messages for storing in caches.
+ * @param string $code Language code.
+ * @return array Loaded messages for storing in caches.
*/
function loadFromDB( $code ) {
wfProfileIn( __METHOD__ );
@@ -449,11 +484,15 @@ class MessageCache {
foreach ( $res as $row ) {
$text = Revision::getRevisionText( $row );
- if( $text === false ) {
+ if ( $text === false ) {
// Failed to fetch data; possible ES errors?
// Store a marker to fetch on-demand as a workaround...
$entry = '!TOO BIG';
- wfDebugLog( 'MessageCache', __METHOD__ . ": failed to load message page text for {$row->page_title} ($code)" );
+ wfDebugLog(
+ 'MessageCache',
+ __METHOD__
+ . ": failed to load message page text for {$row->page_title} ($code)"
+ );
} else {
$entry = ' ' . $text;
}
@@ -461,6 +500,7 @@ class MessageCache {
}
$cache['VERSION'] = MSG_CACHE_VERSION;
+ $cache['EXPIRY'] = wfTimestamp( TS_MW, time() + $this->mExpiry );
wfProfileOut( __METHOD__ );
return $cache;
}
@@ -468,8 +508,8 @@ class MessageCache {
/**
* Updates cache as necessary when message page is changed
*
- * @param string $title name of the page changed.
- * @param $text Mixed: new contents of the page.
+ * @param string $title Name of the page changed.
+ * @param mixed $text New contents of the page.
*/
public function replace( $title, $text ) {
global $wgMaxMsgCacheEntrySize;
@@ -502,7 +542,7 @@ class MessageCache {
}
# Update caches
- $this->saveToCaches( $this->mCache[$code], true, $code );
+ $this->saveToCaches( $this->mCache[$code], 'all', $code );
$this->unlock( $cacheKey );
// Also delete cached sidebar... just in case it is affected
@@ -529,21 +569,41 @@ class MessageCache {
}
/**
+ * Is the given cache array expired due to time passing or a version change?
+ *
+ * @param $cache
+ * @return bool
+ */
+ protected function isCacheExpired( $cache ) {
+ if ( !isset( $cache['VERSION'] ) || !isset( $cache['EXPIRY'] ) ) {
+ return true;
+ }
+ if ( $cache['VERSION'] != MSG_CACHE_VERSION ) {
+ return true;
+ }
+ if ( wfTimestampNow() >= $cache['EXPIRY'] ) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
* Shortcut to update caches.
*
- * @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.
+ * @param array $cache Cached messages with a version.
+ * @param string $dest Either "local-only" to save to local caches only
+ * or "all" to save to all caches.
+ * @param string|bool $code Language code (default: false)
+ * @return bool
*/
- protected function saveToCaches( $cache, $memc = true, $code = false ) {
+ protected function saveToCaches( $cache, $dest, $code = false ) {
wfProfileIn( __METHOD__ );
- global $wgUseLocalMessageCache, $wgLocalMessageCacheSerialized;
+ global $wgUseLocalMessageCache;
$cacheKey = wfMemcKey( 'messages', $code );
- if ( $memc ) {
- $success = $this->mMemc->set( $cacheKey, $cache, $this->mExpiry );
+ if ( $dest === 'all' ) {
+ $success = $this->mMemc->set( $cacheKey, $cache );
} else {
$success = true;
}
@@ -552,12 +612,8 @@ class MessageCache {
if ( $wgUseLocalMessageCache ) {
$serialized = serialize( $cache );
$hash = md5( $serialized );
- $this->mMemc->set( wfMemcKey( 'messages', $code, 'hash' ), $hash, $this->mExpiry );
- if ( $wgLocalMessageCacheSerialized ) {
- $this->saveToLocal( $serialized, $hash, $code );
- } else {
- $this->saveToScript( $cache, $hash, $code );
- }
+ $this->mMemc->set( wfMemcKey( 'messages', $code, 'hash' ), $hash );
+ $this->saveToLocal( $serialized, $hash, $code );
}
wfProfileOut( __METHOD__ );
@@ -565,19 +621,35 @@ class MessageCache {
}
/**
- * Represents a write lock on the messages key
+ * Represents a write lock on the messages key.
*
- * @param $key string
+ * Will retry MessageCache::MSG_WAIT_TIMEOUT times, each operations having
+ * a timeout of MessageCache::MSG_LOCK_TIMEOUT.
*
+ * @param string $key
* @return Boolean: success
*/
function lock( $key ) {
$lockKey = $key . ':lock';
- for ( $i = 0; $i < MSG_WAIT_TIMEOUT && !$this->mMemc->add( $lockKey, 1, MSG_LOCK_TIMEOUT ); $i++ ) {
+ $acquired = false;
+ $testDone = false;
+ for ( $i = 0; $i < MSG_WAIT_TIMEOUT && !$acquired; $i++ ) {
+ $acquired = $this->mMemc->add( $lockKey, 1, MSG_LOCK_TIMEOUT );
+ if ( $acquired ) {
+ break;
+ }
+
+ # Fail fast if memcached is totally down
+ if ( !$testDone ) {
+ $testDone = true;
+ if ( !$this->mMemc->set( wfMemcKey( 'test' ), 'test', 1 ) ) {
+ break;
+ }
+ }
sleep( 1 );
}
- return $i >= MSG_WAIT_TIMEOUT;
+ return $acquired;
}
function unlock( $key ) {
@@ -588,51 +660,62 @@ class MessageCache {
/**
* Get a message from either the content language or the user language.
*
- * @param string $key the message cache key
- * @param $useDB Boolean: get the message from the DB, false to use only
- * the localisation
- * @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
- * current users language (as a fallback for the old
- * parameter functionality), or if it is a true boolean
- * then use the wikis content language (also as a
- * fallback).
- * @param $isFullKey Boolean: specifies whether $key is a two part key
+ * First, assemble a list of languages to attempt getting the message from. This
+ * chain begins with the requested language and its fallbacks and then continues with
+ * the content language and its fallbacks. For each language in the chain, the following
+ * process will occur (in this order):
+ * 1. If a language-specific override, i.e., [[MW:msg/lang]], is available, use that.
+ * Note: for the content language, there is no /lang subpage.
+ * 2. Fetch from the static CDB cache.
+ * 3. If available, check the database for fallback language overrides.
+ *
+ * This process provides a number of guarantees. When changing this code, make sure all
+ * of these guarantees are preserved.
+ * * If the requested language is *not* the content language, then the CDB cache for that
+ * specific language will take precedence over the root database page ([[MW:msg]]).
+ * * Fallbacks will be just that: fallbacks. A fallback language will never be reached if
+ * the message is available *anywhere* in the language for which it is a fallback.
+ *
+ * @param string $key the message key
+ * @param bool $useDB If true, look for the message in the DB, false
+ * to use only the compiled l10n cache.
+ * @param bool|string|object $langcode Code of the language to get the message for.
+ * - If string and a valid code, will create a standard language object
+ * - If string but not a valid code, will create a basic language object
+ * - If boolean and false, create object from the current users language
+ * - If boolean and true, create object from the wikis content language
+ * - If language object, use it as given
+ * @param bool $isFullKey specifies whether $key is a two part key
* "msg/lang".
*
- * @throws MWException
- * @return string|bool
+ * @throws MWException when given an invalid key
+ * @return string|bool False if the message doesn't exist, otherwise the message (which can be empty)
*/
function get( $key, $useDB = true, $langcode = true, $isFullKey = false ) {
- global $wgLanguageCode, $wgContLang;
+ global $wgContLang;
- if ( is_int( $key ) ) {
- // "Non-string key given" exception sometimes happens for numerical strings that become ints somewhere on their way here
- $key = strval( $key );
- }
+ $section = new ProfileSection( __METHOD__ );
- if ( !is_string( $key ) ) {
+ if ( is_int( $key ) ) {
+ // Fix numerical strings that somehow become ints
+ // on their way here
+ $key = (string)$key;
+ } elseif ( !is_string( $key ) ) {
throw new MWException( 'Non-string key given' );
- }
-
- if ( strval( $key ) === '' ) {
- # Shortcut: the empty key is always missing
+ } elseif ( $key === '' ) {
+ // Shortcut: the empty key is always missing
return false;
}
- $lang = wfGetLangObj( $langcode );
- if ( !$lang ) {
- throw new MWException( "Bad lang code $langcode given" );
+ // For full keys, get the language code from the key
+ $pos = strrpos( $key, '/' );
+ if ( $isFullKey && $pos !== false ) {
+ $langcode = substr( $key, $pos + 1 );
+ $key = substr( $key, 0, $pos );
}
- $langcode = $lang->getCode();
-
- $message = false;
-
- # Normalise title-case input (with some inlining)
- $lckey = str_replace( ' ', '_', $key );
+ // Normalise title-case input (with some inlining)
+ $lckey = strtr( $key, ' ', '_' );
if ( ord( $key ) < 128 ) {
$lckey[0] = strtolower( $lckey[0] );
$uckey = ucfirst( $lckey );
@@ -641,80 +724,160 @@ class MessageCache {
$uckey = $wgContLang->ucfirst( $lckey );
}
- # Try the MediaWiki namespace
- if( !$this->mDisable && $useDB ) {
- $title = $uckey;
- if( !$isFullKey && ( $langcode != $wgLanguageCode ) ) {
- $title .= '/' . $langcode;
- }
- $message = $this->getMsgFromNamespace( $title, $langcode );
- }
+ // Loop through each language in the fallback list until we find something useful
+ $lang = wfGetLangObj( $langcode );
+ $message = $this->getMessageFromFallbackChain( $lang, $lckey, $uckey, !$this->mDisable && $useDB );
- # Try the array in the language object
+ // If we still have no message, maybe the key was in fact a full key so try that
if ( $message === false ) {
- $message = $lang->getMessage( $lckey );
- if ( is_null( $message ) ) {
- $message = false;
- }
- }
-
- # Try the array of another language
- if( $message === false ) {
$parts = explode( '/', $lckey );
- # We may get calls for things that are http-urls from sidebar
- # Let's not load nonexistent languages for those
- # They usually have more than one slash.
+ // We may get calls for things that are http-urls from sidebar
+ // Let's not load nonexistent languages for those
+ // They usually have more than one slash.
if ( count( $parts ) == 2 && $parts[1] !== '' ) {
$message = Language::getMessageFor( $parts[0], $parts[1] );
- if ( is_null( $message ) ) {
+ if ( $message === null ) {
$message = false;
}
}
}
- # Is this a custom message? Try the default language in the db...
- if( ( $message === false || $message === '-' ) &&
- !$this->mDisable && $useDB &&
- !$isFullKey && ( $langcode != $wgLanguageCode ) ) {
+ // Post-processing if the message exists
+ if ( $message !== false ) {
+ // Fix whitespace
+ $message = str_replace(
+ array(
+ # Fix for trailing whitespace, removed by textarea
+ '&#32;',
+ # Fix for NBSP, converted to space by firefox
+ '&nbsp;',
+ '&#160;',
+ ),
+ array(
+ ' ',
+ "\xc2\xa0",
+ "\xc2\xa0"
+ ),
+ $message
+ );
+ }
+
+ return $message;
+ }
+
+ /**
+ * Given a language, try and fetch a message from that language, then the
+ * fallbacks of that language, then the site language, then the fallbacks for the
+ * site language.
+ *
+ * @param Language $lang Requested language
+ * @param string $lckey Lowercase key for the message
+ * @param string $uckey Uppercase key for the message
+ * @param bool $useDB Whether to use the database
+ *
+ * @see MessageCache::get
+ * @return string|bool The message, or false if not found
+ */
+ protected function getMessageFromFallbackChain( $lang, $lckey, $uckey, $useDB ) {
+ global $wgLanguageCode, $wgContLang;
+
+ $langcode = $lang->getCode();
+ $message = false;
+
+ // First try the requested language.
+ if ( $useDB ) {
+ if ( $langcode === $wgLanguageCode ) {
+ // Messages created in the content language will not have the /lang extension
+ $message = $this->getMsgFromNamespace( $uckey, $langcode );
+ } else {
+ $message = $this->getMsgFromNamespace( "$uckey/$langcode", $langcode );
+ }
+ }
+
+ if ( $message !== false ) {
+ return $message;
+ }
+
+ // Check the CDB cache
+ $message = $lang->getMessage( $lckey );
+ if ( $message !== null ) {
+ return $message;
+ }
+
+ list( $fallbackChain, $siteFallbackChain ) = Language::getFallbacksIncludingSiteLanguage( $langcode );
+
+ // Next try checking the database for all of the fallback languages of the requested language.
+ if ( $useDB ) {
+ foreach ( $fallbackChain as $code ) {
+ if ( $code === $wgLanguageCode ) {
+ // Messages created in the content language will not have the /lang extension
+ $message = $this->getMsgFromNamespace( $uckey, $code );
+ } else {
+ $message = $this->getMsgFromNamespace( "$uckey/$code", $code );
+ }
+
+ if ( $message !== false ) {
+ // Found the message.
+ return $message;
+ }
+ }
+ }
+
+ // Now try checking the site language.
+ if ( $useDB ) {
$message = $this->getMsgFromNamespace( $uckey, $wgLanguageCode );
+ if ( $message !== false ) {
+ return $message;
+ }
}
- # Final fallback
- if( $message === false ) {
- return false;
+ $message = $wgContLang->getMessage( $lckey );
+ if ( $message !== null ) {
+ return $message;
}
- # Fix whitespace
- $message = strtr( $message,
- array(
- # Fix for trailing whitespace, removed by textarea
- '&#32;' => ' ',
- # Fix for NBSP, converted to space by firefox
- '&nbsp;' => "\xc2\xa0",
- '&#160;' => "\xc2\xa0",
- ) );
+ // Finally try the DB for the site language's fallbacks.
+ if ( $useDB ) {
+ foreach ( $siteFallbackChain as $code ) {
+ $message = $this->getMsgFromNamespace( "$uckey/$code", $code );
+ if ( $message === false && $code === $wgLanguageCode ) {
+ // Messages created in the content language will not have the /lang extension
+ $message = $this->getMsgFromNamespace( $uckey, $code );
+ }
- return $message;
+ if ( $message !== false ) {
+ // Found the message.
+ return $message;
+ }
+ }
+ }
+
+ return false;
}
/**
* Get a message from the MediaWiki namespace, with caching. The key must
* first be converted to two-part lang/msg form if necessary.
*
- * @param string $title Message cache key with initial uppercase letter.
- * @param string $code code denoting the language to try.
+ * Unlike self::get(), this function doesn't resolve fallback chains, and
+ * some callers require this behavior. LanguageConverter::parseCachedTable()
+ * and self::get() are some examples in core.
*
- * @return string|bool False on failure
+ * @param string $title Message cache key with initial uppercase letter.
+ * @param string $code Code denoting the language to try.
+ * @return string|bool The message, or false if it does not exist or on error
*/
function getMsgFromNamespace( $title, $code ) {
$this->load( $code );
if ( isset( $this->mCache[$code][$title] ) ) {
$entry = $this->mCache[$code][$title];
if ( substr( $entry, 0, 1 ) === ' ' ) {
- return substr( $entry, 1 );
+ // The message exists, so make sure a string
+ // is returned.
+ return (string)substr( $entry, 1 );
} elseif ( $entry === '!NONEXISTENT' ) {
return false;
- } elseif( $entry === '!TOO BIG' ) {
+ } elseif ( $entry === '!TOO BIG' ) {
// Fall through and try invididual message cache below
}
} else {
@@ -734,7 +897,9 @@ class MessageCache {
if ( $entry ) {
if ( substr( $entry, 0, 1 ) === ' ' ) {
$this->mCache[$code][$title] = $entry;
- return substr( $entry, 1 );
+ // The message exists, so make sure a string
+ // is returned.
+ return (string)substr( $entry, 1 );
} elseif ( $entry === '!NONEXISTENT' ) {
$this->mCache[$code][$title] = '!NONEXISTENT';
return false;
@@ -752,17 +917,24 @@ class MessageCache {
$content = $revision->getContent();
if ( !$content ) {
// A possibly temporary loading failure.
- wfDebugLog( 'MessageCache', __METHOD__ . ": failed to load message page text for {$title} ($code)" );
+ wfDebugLog(
+ 'MessageCache',
+ __METHOD__ . ": failed to load message page text for {$title} ($code)"
+ );
$message = null; // no negative caching
} else {
// 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.
+ // 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() . ")" );
+ wfDebugLog(
+ 'MessageCache',
+ __METHOD__ . ": message content doesn't provide wikitext "
+ . "(content model: " . $content->getContentHandler() . ")"
+ );
$message = false; // negative caching
} else {
@@ -783,15 +955,15 @@ class MessageCache {
}
/**
- * @param $message string
- * @param $interface bool
- * @param $language
- * @param $title Title
+ * @param string $message
+ * @param bool $interface
+ * @param string $language Language code
+ * @param Title $title
* @return string
*/
function transform( $message, $interface = false, $language = null, $title = null ) {
// Avoid creating parser if nothing to transform
- if( strpos( $message, '{{' ) === false ) {
+ if ( strpos( $message, '{{' ) === false ) {
return $message;
}
@@ -835,14 +1007,16 @@ class MessageCache {
}
/**
- * @param $text string
- * @param $title Title
- * @param $linestart bool
- * @param $interface bool
- * @param $language
+ * @param string $text
+ * @param Title $title
+ * @param bool $linestart Whether or not this is at the start of a line
+ * @param bool $interface Whether this is an interface message
+ * @param string $language Language code
* @return ParserOutput|string
*/
- public function parse( $text, $title = null, $linestart = true, $interface = false, $language = null ) {
+ public function parse( $text, $title = null, $linestart = true,
+ $interface = false, $language = null
+ ) {
if ( $this->mInParser ) {
return htmlspecialchars( $text );
}
@@ -901,12 +1075,12 @@ class MessageCache {
public function figureMessage( $key ) {
global $wgLanguageCode;
$pieces = explode( '/', $key );
- if( count( $pieces ) < 2 ) {
+ if ( count( $pieces ) < 2 ) {
return array( $key, $wgLanguageCode );
}
$lang = array_pop( $pieces );
- if( !Language::fetchLanguageName( $lang, null, 'mw' ) ) {
+ if ( !Language::fetchLanguageName( $lang, null, 'mw' ) ) {
return array( $key, $wgLanguageCode );
}
@@ -919,7 +1093,7 @@ class MessageCache {
* If $code is the content language code, this will return all message keys
* for which MediaWiki:msgkey exists. If $code is another language code, this
* will ONLY return message keys for which MediaWiki:msgkey/$code exists.
- * @param $code string
+ * @param string $code Language code
* @return array of message keys (strings)
*/
public function getAllMessageKeys( $code ) {
@@ -929,9 +1103,12 @@ class MessageCache {
// Apparently load() failed
return null;
}
- $cache = $this->mCache[$code]; // Copy the cache
- unset( $cache['VERSION'] ); // Remove the VERSION key
- $cache = array_diff( $cache, array( '!NONEXISTENT' ) ); // Remove any !NONEXISTENT keys
+ // Remove administrative keys
+ $cache = $this->mCache[$code];
+ unset( $cache['VERSION'] );
+ unset( $cache['EXPIRY'] );
+ // Remove any !NONEXISTENT keys
+ $cache = array_diff( $cache, array( '!NONEXISTENT' ) );
// Keys may appear with a capital first letter. lcfirst them.
return array_map( array( $wgContLang, 'lcfirst' ), array_keys( $cache ) );
}
diff --git a/includes/cache/ResourceFileCache.php b/includes/cache/ResourceFileCache.php
index 61f1e8c3..2ad7b853 100644
--- a/includes/cache/ResourceFileCache.php
+++ b/includes/cache/ResourceFileCache.php
@@ -29,7 +29,7 @@
class ResourceFileCache extends FileCacheBase {
protected $mCacheWorthy;
- /* @TODO: configurable? */
+ /* @todo configurable? */
const MISS_THRESHOLD = 360; // 6/min * 60 min
/**
diff --git a/includes/cache/SquidUpdate.php b/includes/cache/SquidUpdate.php
index 39bf4c9f..71afeba9 100644
--- a/includes/cache/SquidUpdate.php
+++ b/includes/cache/SquidUpdate.php
@@ -26,32 +26,42 @@
* @ingroup Cache
*/
class SquidUpdate {
- var $urlArr, $mMaxTitles;
/**
- * @param $urlArr array
- * @param $maxTitles bool|int
+ * Collection of URLs to purge.
+ * @var array
*/
- function __construct( $urlArr = array(), $maxTitles = false ) {
+ protected $urlArr;
+
+ /**
+ * @param array $urlArr Collection of URLs to purge
+ * @param bool|int $maxTitles Maximum number of unique URLs to purge
+ */
+ public function __construct( $urlArr = array(), $maxTitles = false ) {
global $wgMaxSquidPurgeTitles;
if ( $maxTitles === false ) {
- $this->mMaxTitles = $wgMaxSquidPurgeTitles;
- } else {
- $this->mMaxTitles = $maxTitles;
+ $maxTitles = $wgMaxSquidPurgeTitles;
}
- $urlArr = array_unique( $urlArr ); // Remove duplicates
- if ( count( $urlArr ) > $this->mMaxTitles ) {
- $urlArr = array_slice( $urlArr, 0, $this->mMaxTitles );
+
+ // Remove duplicate URLs from list
+ $urlArr = array_unique( $urlArr );
+ if ( count( $urlArr ) > $maxTitles ) {
+ // Truncate to desired maximum URL count
+ $urlArr = array_slice( $urlArr, 0, $maxTitles );
}
$this->urlArr = $urlArr;
}
/**
- * @param $title Title
+ * Create a SquidUpdate from the given Title object.
+ *
+ * The resulting SquidUpdate will purge the given Title's URLs as well as
+ * the pages that link to it. Capped at $wgMaxSquidPurgeTitles total URLs.
*
+ * @param Title $title
* @return SquidUpdate
*/
- static function newFromLinksTo( &$title ) {
+ public static function newFromLinksTo( Title $title ) {
global $wgMaxSquidPurgeTitles;
wfProfileIn( __METHOD__ );
@@ -79,12 +89,11 @@ class SquidUpdate {
/**
* Create a SquidUpdate from an array of Title objects, or a TitleArray object
*
- * @param $titles array
- * @param $urlArr array
- *
+ * @param array $titles
+ * @param array $urlArr
* @return SquidUpdate
*/
- static function newFromTitles( $titles, $urlArr = array() ) {
+ public static function newFromTitles( $titles, $urlArr = array() ) {
global $wgMaxSquidPurgeTitles;
$i = 0;
foreach ( $titles as $title ) {
@@ -97,20 +106,19 @@ class SquidUpdate {
}
/**
- * @param $title Title
- *
+ * @param Title $title
* @return SquidUpdate
*/
- static function newSimplePurge( &$title ) {
+ public static function newSimplePurge( Title $title ) {
$urlArr = $title->getSquidURLs();
return new SquidUpdate( $urlArr );
}
/**
- * Purges the list of URLs passed to the constructor
+ * Purges the list of URLs passed to the constructor.
*/
- function doUpdate() {
- SquidUpdate::purge( $this->urlArr );
+ public function doUpdate() {
+ self::purge( $this->urlArr );
}
/**
@@ -119,27 +127,30 @@ class SquidUpdate {
* (example: $urlArr[] = 'http://my.host/something')
* XXX report broken Squids per mail or log
*
- * @param $urlArr array
- * @return void
+ * @param array $urlArr List of full URLs to purge
*/
- static function purge( $urlArr ) {
- global $wgSquidServers, $wgHTCPMulticastRouting;
+ public static function purge( $urlArr ) {
+ global $wgSquidServers, $wgHTCPRouting;
- if( !$urlArr ) {
+ if ( !$urlArr ) {
return;
}
- wfDebug( "Squid purge: " . implode( ' ', $urlArr ) . "\n" );
+ wfDebugLog( 'squid', __METHOD__ . ': ' . implode( ' ', $urlArr ) . "\n" );
- if ( $wgHTCPMulticastRouting ) {
- SquidUpdate::HTCPPurge( $urlArr );
+ if ( $wgHTCPRouting ) {
+ self::HTCPPurge( $urlArr );
}
wfProfileIn( __METHOD__ );
- $urlArr = array_unique( $urlArr ); // Remove duplicates
- $maxSocketsPerSquid = 8; // socket cap per Squid
- $urlsPerSocket = 400; // 400 seems to be a good tradeoff, opening a socket takes a while
+ // Remove duplicate URLs
+ $urlArr = array_unique( $urlArr );
+ // Maximum number of parallel connections per squid
+ $maxSocketsPerSquid = 8;
+ // Number of requests to send per socket
+ // 400 seems to be a good tradeoff, opening a socket takes a while
+ $urlsPerSocket = 400;
$socketsPerSquid = ceil( count( $urlArr ) / $urlsPerSocket );
if ( $socketsPerSquid > $maxSocketsPerSquid ) {
$socketsPerSquid = $maxSocketsPerSquid;
@@ -162,17 +173,20 @@ class SquidUpdate {
}
/**
+ * Send Hyper Text Caching Protocol (HTCP) CLR requests.
+ *
* @throws MWException
- * @param $urlArr array
+ * @param array $urlArr Collection of URLs to purge
*/
- static function HTCPPurge( $urlArr ) {
- global $wgHTCPMulticastRouting, $wgHTCPMulticastTTL;
+ public static function HTCPPurge( $urlArr ) {
+ global $wgHTCPRouting, $wgHTCPMulticastTTL;
wfProfileIn( __METHOD__ );
- $htcpOpCLR = 4; // HTCP CLR
+ // HTCP CLR operation
+ $htcpOpCLR = 4;
// @todo FIXME: PHP doesn't support these socket constants (include/linux/in.h)
- if( !defined( "IPPROTO_IP" ) ) {
+ if ( !defined( "IPPROTO_IP" ) ) {
define( "IPPROTO_IP", 0 );
define( "IP_MULTICAST_LOOP", 34 );
define( "IP_MULTICAST_TTL", 33 );
@@ -180,55 +194,73 @@ class SquidUpdate {
// pfsockopen doesn't work because we need set_sock_opt
$conn = socket_create( AF_INET, SOCK_DGRAM, SOL_UDP );
- if ( $conn ) {
- // Set socket options
- socket_set_option( $conn, IPPROTO_IP, IP_MULTICAST_LOOP, 0 );
- if ( $wgHTCPMulticastTTL != 1 )
- socket_set_option( $conn, IPPROTO_IP, IP_MULTICAST_TTL,
- $wgHTCPMulticastTTL );
-
- $urlArr = array_unique( $urlArr ); // Remove duplicates
- foreach ( $urlArr as $url ) {
- if( !is_string( $url ) ) {
- throw new MWException( 'Bad purge URL' );
- }
- $url = SquidUpdate::expand( $url );
- $conf = self::getRuleForURL( $url, $wgHTCPMulticastRouting );
- if ( !$conf ) {
- wfDebug( "No HTCP rule configured for URL $url , skipping\n" );
- continue;
- }
- if ( !isset( $conf['host'] ) || !isset( $conf['port'] ) ) {
+ if ( ! $conn ) {
+ $errstr = socket_strerror( socket_last_error() );
+ wfDebugLog( 'squid', __METHOD__ .
+ ": Error opening UDP socket: $errstr\n" );
+ wfProfileOut( __METHOD__ );
+ return;
+ }
+
+ // Set socket options
+ socket_set_option( $conn, IPPROTO_IP, IP_MULTICAST_LOOP, 0 );
+ if ( $wgHTCPMulticastTTL != 1 ) {
+ // Set multicast time to live (hop count) option on socket
+ socket_set_option( $conn, IPPROTO_IP, IP_MULTICAST_TTL,
+ $wgHTCPMulticastTTL );
+ }
+
+ // Remove duplicate URLs from collection
+ $urlArr = array_unique( $urlArr );
+ foreach ( $urlArr as $url ) {
+ if ( !is_string( $url ) ) {
+ wfProfileOut( __METHOD__ );
+ throw new MWException( 'Bad purge URL' );
+ }
+ $url = self::expand( $url );
+ $conf = self::getRuleForURL( $url, $wgHTCPRouting );
+ if ( !$conf ) {
+ wfDebugLog( 'squid', __METHOD__ .
+ "No HTCP rule configured for URL {$url} , skipping\n" );
+ continue;
+ }
+
+ if ( isset( $conf['host'] ) && isset( $conf['port'] ) ) {
+ // Normalize single entries
+ $conf = array( $conf );
+ }
+ foreach ( $conf as $subconf ) {
+ if ( !isset( $subconf['host'] ) || !isset( $subconf['port'] ) ) {
+ wfProfileOut( __METHOD__ );
throw new MWException( "Invalid HTCP rule for URL $url\n" );
}
+ }
- // Construct a minimal HTCP request diagram
- // as per RFC 2756
- // Opcode 'CLR', no response desired, no auth
- $htcpTransID = rand();
+ // Construct a minimal HTCP request diagram
+ // as per RFC 2756
+ // Opcode 'CLR', no response desired, no auth
+ $htcpTransID = rand();
- $htcpSpecifier = pack( 'na4na*na8n',
- 4, 'HEAD', strlen( $url ), $url,
- 8, 'HTTP/1.0', 0 );
+ $htcpSpecifier = pack( 'na4na*na8n',
+ 4, 'HEAD', strlen( $url ), $url,
+ 8, 'HTTP/1.0', 0 );
- $htcpDataLen = 8 + 2 + strlen( $htcpSpecifier );
- $htcpLen = 4 + $htcpDataLen + 2;
+ $htcpDataLen = 8 + 2 + strlen( $htcpSpecifier );
+ $htcpLen = 4 + $htcpDataLen + 2;
- // Note! Squid gets the bit order of the first
- // word wrong, wrt the RFC. Apparently no other
- // implementation exists, so adapt to Squid
- $htcpPacket = pack( 'nxxnCxNxxa*n',
- $htcpLen, $htcpDataLen, $htcpOpCLR,
- $htcpTransID, $htcpSpecifier, 2);
+ // Note! Squid gets the bit order of the first
+ // word wrong, wrt the RFC. Apparently no other
+ // implementation exists, so adapt to Squid
+ $htcpPacket = pack( 'nxxnCxNxxa*n',
+ $htcpLen, $htcpDataLen, $htcpOpCLR,
+ $htcpTransID, $htcpSpecifier, 2 );
- // Send out
- wfDebug( "Purging URL $url via HTCP\n" );
+ wfDebugLog( 'squid', __METHOD__ .
+ "Purging URL $url via HTCP\n" );
+ foreach ( $conf as $subconf ) {
socket_sendto( $conn, $htcpPacket, $htcpLen, 0,
- $conf['host'], $conf['port'] );
+ $subconf['host'], $subconf['port'] );
}
- } else {
- $errstr = socket_strerror( socket_last_error() );
- wfDebug( __METHOD__ . "(): Error opening UDP socket: $errstr\n" );
}
wfProfileOut( __METHOD__ );
}
@@ -244,21 +276,20 @@ class SquidUpdate {
*
* Client functions should not need to call this.
*
- * @param $url string
- *
+ * @param string $url
* @return string
*/
- static function expand( $url ) {
+ public static function expand( $url ) {
return wfExpandUrl( $url, PROTO_INTERNAL );
}
/**
* Find the HTCP routing rule to use for a given URL.
* @param string $url URL to match
- * @param array $rules Array of rules, see $wgHTCPMulticastRouting for format and behavior
+ * @param array $rules Array of rules, see $wgHTCPRouting for format and behavior
* @return mixed Element of $rules that matched, or false if nothing matched
*/
- static function getRuleForURL( $url, $rules ) {
+ private static function getRuleForURL( $url, $rules ) {
foreach ( $rules as $regex => $routing ) {
if ( $regex === '' || preg_match( $regex, $url ) ) {
return $routing;
diff --git a/includes/cache/UserCache.php b/includes/cache/UserCache.php
index bfbacfaa..6085f586 100644
--- a/includes/cache/UserCache.php
+++ b/includes/cache/UserCache.php
@@ -59,6 +59,17 @@ class UserCache {
}
/**
+ * Get the name of a user or return $ip if the user ID is 0
+ *
+ * @param integer $userId
+ * @param string $ip
+ * @since 1.22
+ */
+ public function getUserName( $userId, $ip ) {
+ return $userId > 0 ? $this->getProp( $userId, 'name' ) : $ip;
+ }
+
+ /**
* Preloads user names for given list of users.
* @param array $userIds List of user IDs
* @param array $options Option flags; include 'userpage' and 'usertalk'
@@ -70,6 +81,8 @@ class UserCache {
$usersToCheck = array();
$usersToQuery = array();
+ $userIds = array_unique( $userIds );
+
foreach ( $userIds as $userId ) {
$userId = (int)$userId;
if ( $userId <= 0 ) {
diff --git a/includes/changes/ChangesList.php b/includes/changes/ChangesList.php
new file mode 100644
index 00000000..bf800c46
--- /dev/null
+++ b/includes/changes/ChangesList.php
@@ -0,0 +1,552 @@
+<?php
+/**
+ * Base class for all changes lists.
+ *
+ * The class is used for formatting recent changes, related changes and watchlist.
+ *
+ * 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 ChangesList extends ContextSource {
+
+ /**
+ * @var Skin
+ */
+ public $skin;
+
+ protected $watchlist = false;
+
+ protected $message;
+
+ /**
+ * Changeslist constructor
+ *
+ * @param $obj Skin or IContextSource
+ */
+ public function __construct( $obj ) {
+ if ( $obj instanceof IContextSource ) {
+ $this->setContext( $obj );
+ $this->skin = $obj->getSkin();
+ } else {
+ $this->setContext( $obj->getContext() );
+ $this->skin = $obj;
+ }
+ $this->preCacheMessages();
+ }
+
+ /**
+ * Fetch an appropriate changes list class for the main context
+ * This first argument used to be an User object.
+ *
+ * @deprecated in 1.18; use newFromContext() instead
+ * @param string|User $unused Unused
+ * @return ChangesList|EnhancedChangesList|OldChangesList derivative
+ */
+ public static function newFromUser( $unused ) {
+ wfDeprecated( __METHOD__, '1.18' );
+ return self::newFromContext( RequestContext::getMain() );
+ }
+
+ /**
+ * Fetch an appropriate changes list class for the specified context
+ * Some users might want to use an enhanced list format, for instance
+ *
+ * @param $context IContextSource to use
+ * @return ChangesList|EnhancedChangesList|OldChangesList derivative
+ */
+ public static function newFromContext( IContextSource $context ) {
+ $user = $context->getUser();
+ $sk = $context->getSkin();
+ $list = null;
+ if ( wfRunHooks( 'FetchChangesList', array( $user, &$sk, &$list ) ) ) {
+ $new = $context->getRequest()->getBool( 'enhanced', $user->getOption( 'usenewrc' ) );
+ return $new ? new EnhancedChangesList( $context ) : new OldChangesList( $context );
+ } else {
+ return $list;
+ }
+ }
+
+ /**
+ * Sets the list to use a "<li class='watchlist-(namespace)-(page)'>" tag
+ * @param $value Boolean
+ */
+ public function setWatchlistDivs( $value = true ) {
+ $this->watchlist = $value;
+ }
+
+ /**
+ * As we use the same small set of messages in various methods and that
+ * they are called often, we call them once and save them in $this->message
+ */
+ private function preCacheMessages() {
+ if ( !isset( $this->message ) ) {
+ foreach ( array(
+ 'cur', 'diff', 'hist', 'enhancedrc-history', 'last', 'blocklink', 'history',
+ 'semicolon-separator', 'pipe-separator' ) as $msg
+ ) {
+ $this->message[$msg] = $this->msg( $msg )->escaped();
+ }
+ }
+ }
+
+ /**
+ * Returns the appropriate flags for new page, minor change and patrolling
+ * @param array $flags Associative array of 'flag' => Bool
+ * @param string $nothing to use for empty space
+ * @return String
+ */
+ public function recentChangesFlags( $flags, $nothing = '&#160;' ) {
+ global $wgRecentChangesFlags;
+ $f = '';
+ foreach ( array_keys( $wgRecentChangesFlags ) as $flag ) {
+ $f .= isset( $flags[$flag] ) && $flags[$flag]
+ ? self::flag( $flag )
+ : $nothing;
+ }
+ return $f;
+ }
+
+ /**
+ * Provide the "<abbr>" element appropriate to a given abbreviated flag,
+ * namely the flag indicating a new page, a minor edit, a bot edit, or an
+ * unpatrolled edit. By default in English it will contain "N", "m", "b",
+ * "!" respectively, plus it will have an appropriate title and class.
+ *
+ * @param string $flag One key of $wgRecentChangesFlags
+ * @return String: Raw HTML
+ */
+ public static function flag( $flag ) {
+ static $flagInfos = null;
+ if ( is_null( $flagInfos ) ) {
+ global $wgRecentChangesFlags;
+ $flagInfos = array();
+ foreach ( $wgRecentChangesFlags as $key => $value ) {
+ $flagInfos[$key]['letter'] = wfMessage( $value['letter'] )->escaped();
+ $flagInfos[$key]['title'] = wfMessage( $value['title'] )->escaped();
+ // Allow customized class name, fall back to flag name
+ $flagInfos[$key]['class'] = Sanitizer::escapeClass(
+ isset( $value['class'] ) ? $value['class'] : $key );
+ }
+ }
+
+ // Inconsistent naming, bleh, kepted for b/c
+ $map = array(
+ 'minoredit' => 'minor',
+ 'botedit' => 'bot',
+ );
+ if ( isset( $map[$flag] ) ) {
+ $flag = $map[$flag];
+ }
+
+ return "<abbr class='" . $flagInfos[$flag]['class'] . "' title='" . $flagInfos[$flag]['title'] . "'>" .
+ $flagInfos[$flag]['letter'] .
+ '</abbr>';
+ }
+
+ /**
+ * Returns text for the start of the tabular part of RC
+ * @return String
+ */
+ public function beginRecentChangesList() {
+ $this->rc_cache = array();
+ $this->rcMoveIndex = 0;
+ $this->rcCacheIndex = 0;
+ $this->lastdate = '';
+ $this->rclistOpen = false;
+ $this->getOutput()->addModuleStyles( 'mediawiki.special.changeslist' );
+ return '';
+ }
+
+ /**
+ * Show formatted char difference
+ * @param $old Integer: bytes
+ * @param $new Integer: bytes
+ * @param $context IContextSource context to use
+ * @return String
+ */
+ public static function showCharacterDifference( $old, $new, IContextSource $context = null ) {
+ global $wgRCChangedSizeThreshold, $wgMiserMode;
+
+ if ( !$context ) {
+ $context = RequestContext::getMain();
+ }
+
+ $new = (int)$new;
+ $old = (int)$old;
+ $szdiff = $new - $old;
+
+ $lang = $context->getLanguage();
+ $code = $lang->getCode();
+ static $fastCharDiff = array();
+ if ( !isset( $fastCharDiff[$code] ) ) {
+ $fastCharDiff[$code] = $wgMiserMode || $context->msg( 'rc-change-size' )->plain() === '$1';
+ }
+
+ $formattedSize = $lang->formatNum( $szdiff );
+
+ if ( !$fastCharDiff[$code] ) {
+ $formattedSize = $context->msg( 'rc-change-size', $formattedSize )->text();
+ }
+
+ if ( abs( $szdiff ) > abs( $wgRCChangedSizeThreshold ) ) {
+ $tag = 'strong';
+ } else {
+ $tag = 'span';
+ }
+
+ if ( $szdiff === 0 ) {
+ $formattedSizeClass = 'mw-plusminus-null';
+ }
+ if ( $szdiff > 0 ) {
+ $formattedSize = '+' . $formattedSize;
+ $formattedSizeClass = 'mw-plusminus-pos';
+ }
+ if ( $szdiff < 0 ) {
+ $formattedSizeClass = 'mw-plusminus-neg';
+ }
+
+ $formattedTotalSize = $context->msg( 'rc-change-size-new' )->numParams( $new )->text();
+
+ return Html::element( $tag,
+ array( 'dir' => 'ltr', 'class' => $formattedSizeClass, 'title' => $formattedTotalSize ),
+ $context->msg( 'parentheses', $formattedSize )->plain() ) . $lang->getDirMark();
+ }
+
+ /**
+ * Format the character difference of one or several changes.
+ *
+ * @param $old RecentChange
+ * @param $new RecentChange last change to use, if not provided, $old will be used
+ * @return string HTML fragment
+ */
+ public function formatCharacterDifference( RecentChange $old, RecentChange $new = null ) {
+ $oldlen = $old->mAttribs['rc_old_len'];
+
+ if ( $new ) {
+ $newlen = $new->mAttribs['rc_new_len'];
+ } else {
+ $newlen = $old->mAttribs['rc_new_len'];
+ }
+
+ if ( $oldlen === null || $newlen === null ) {
+ return '';
+ }
+
+ return self::showCharacterDifference( $oldlen, $newlen, $this->getContext() );
+ }
+
+ /**
+ * Returns text for the end of RC
+ * @return String
+ */
+ public function endRecentChangesList() {
+ if ( $this->rclistOpen ) {
+ return "</ul>\n";
+ } else {
+ return '';
+ }
+ }
+
+ /**
+ * @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() );
+ if ( $date != $this->lastdate ) {
+ if ( $this->lastdate != '' ) {
+ $s .= "</ul>\n";
+ }
+ $s .= Xml::element( 'h4', null, $date ) . "\n<ul class=\"special\">";
+ $this->lastdate = $date;
+ $this->rclistOpen = true;
+ }
+ }
+
+ /**
+ * @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();
+ $s .= $this->msg( 'parentheses' )->rawParams( Linker::linkKnown( $title, $logname ) )->escaped();
+ }
+
+ /**
+ * @param string $s HTML to update
+ * @param $rc RecentChange
+ * @param $unpatrolled
+ */
+ public function insertDiffHist( &$s, &$rc, $unpatrolled ) {
+ # Diff link
+ if ( $rc->mAttribs['rc_type'] == RC_NEW || $rc->mAttribs['rc_type'] == RC_LOG ) {
+ $diffLink = $this->message['diff'];
+ } elseif ( !self::userCan( $rc, Revision::DELETED_TEXT, $this->getUser() ) ) {
+ $diffLink = $this->message['diff'];
+ } else {
+ $query = array(
+ 'curid' => $rc->mAttribs['rc_cur_id'],
+ 'diff' => $rc->mAttribs['rc_this_oldid'],
+ 'oldid' => $rc->mAttribs['rc_last_oldid']
+ );
+
+ $diffLink = Linker::linkKnown(
+ $rc->getTitle(),
+ $this->message['diff'],
+ array( 'tabindex' => $rc->counter ),
+ $query
+ );
+ }
+ $diffhist = $diffLink . $this->message['pipe-separator'];
+ # History link
+ $diffhist .= Linker::linkKnown(
+ $rc->getTitle(),
+ $this->message['hist'],
+ array(),
+ array(
+ 'curid' => $rc->mAttribs['rc_cur_id'],
+ 'action' => 'history'
+ )
+ );
+ $s .= $this->msg( 'parentheses' )->rawParams( $diffhist )->escaped() . ' <span class="mw-changeslist-separator">. .</span> ';
+ }
+
+ /**
+ * @param string $s HTML to update
+ * @param $rc RecentChange
+ * @param $unpatrolled
+ * @param $watched
+ */
+ public function insertArticleLink( &$s, &$rc, $unpatrolled, $watched ) {
+ $params = array();
+
+ $articlelink = Linker::linkKnown(
+ $rc->getTitle(),
+ null,
+ array( 'class' => 'mw-changeslist-title' ),
+ $params
+ );
+ if ( $this->isDeleted( $rc, Revision::DELETED_TEXT ) ) {
+ $articlelink = '<span class="history-deleted">' . $articlelink . '</span>';
+ }
+ # To allow for boldening pages watched by this user
+ $articlelink = "<span class=\"mw-title\">{$articlelink}</span>";
+ # RTL/LTR marker
+ $articlelink .= $this->getLanguage()->getDirMark();
+
+ wfRunHooks( 'ChangesListInsertArticleLink',
+ array( &$this, &$articlelink, &$s, &$rc, $unpatrolled, $watched ) );
+
+ $s .= " $articlelink";
+ }
+
+ /**
+ * Get the timestamp from $rc formatted with current user's settings
+ * and a separator
+ *
+ * @param $rc RecentChange
+ * @return string HTML fragment
+ */
+ 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
+ * @param &$rc RecentChange
+ */
+ public function insertUserRelatedLinks( &$s, &$rc ) {
+ if ( $this->isDeleted( $rc, Revision::DELETED_USER ) ) {
+ $s .= ' <span class="history-deleted">' . $this->msg( 'rev-deleted-user' )->escaped() . '</span>';
+ } else {
+ $s .= $this->getLanguage()->getDirMark() . Linker::userLink( $rc->mAttribs['rc_user'],
+ $rc->mAttribs['rc_user_text'] );
+ $s .= Linker::userToolLinks( $rc->mAttribs['rc_user'], $rc->mAttribs['rc_user_text'] );
+ }
+ }
+
+ /**
+ * Insert a formatted action
+ *
+ * @param $rc RecentChange
+ * @return string
+ */
+ public function insertLogEntry( $rc ) {
+ $formatter = LogFormatter::newFromRow( $rc->mAttribs );
+ $formatter->setContext( $this->getContext() );
+ $formatter->setShowUserToolLinks( true );
+ $mark = $this->getLanguage()->getDirMark();
+ return $formatter->getActionText() . " $mark" . $formatter->getComment();
+ }
+
+ /**
+ * Insert a formatted comment
+ * @param $rc RecentChange
+ * @return string
+ */
+ public function insertComment( $rc ) {
+ if ( $rc->mAttribs['rc_type'] != RC_MOVE && $rc->mAttribs['rc_type'] != RC_MOVE_OVER_REDIRECT ) {
+ if ( $this->isDeleted( $rc, Revision::DELETED_COMMENT ) ) {
+ return ' <span class="history-deleted">' . $this->msg( 'rev-deleted-comment' )->escaped() . '</span>';
+ } else {
+ return Linker::commentBlock( $rc->mAttribs['rc_comment'], $rc->getTitle() );
+ }
+ }
+ return '';
+ }
+
+ /**
+ * Check whether to enable recent changes patrol features
+ *
+ * @deprecated since 1.22
+ * @return Boolean
+ */
+ public static function usePatrol() {
+ global $wgUser;
+
+ wfDeprecated( __METHOD__, '1.22' );
+
+ return $wgUser->useRCPatrol();
+ }
+
+ /**
+ * Returns the string which indicates the number of watching users
+ * @return string
+ */
+ protected function numberofWatchingusers( $count ) {
+ static $cache = array();
+ if ( $count > 0 ) {
+ if ( !isset( $cache[$count] ) ) {
+ $cache[$count] = $this->msg( 'number_of_watching_users_RCview' )->numParams( $count )->escaped();
+ }
+ return $cache[$count];
+ } else {
+ return '';
+ }
+ }
+
+ /**
+ * Determine if said field of a revision is hidden
+ * @param $rc RCCacheEntry
+ * @param $field Integer: one of DELETED_* bitfield constants
+ * @return Boolean
+ */
+ public static function isDeleted( $rc, $field ) {
+ return ( $rc->mAttribs['rc_deleted'] & $field ) == $field;
+ }
+
+ /**
+ * Determine if the current user is allowed to view a particular
+ * field of this revision, if it's marked as deleted.
+ * @param $rc RCCacheEntry
+ * @param $field Integer
+ * @param $user User object to check, or null to use $wgUser
+ * @return Boolean
+ */
+ public static function userCan( $rc, $field, User $user = null ) {
+ if ( $rc->mAttribs['rc_type'] == RC_LOG ) {
+ return LogEventsList::userCanBitfield( $rc->mAttribs['rc_deleted'], $field, $user );
+ } else {
+ return Revision::userCanBitfield( $rc->mAttribs['rc_deleted'], $field, $user );
+ }
+ }
+
+ /**
+ * @param $link string
+ * @param $watched bool
+ * @return string
+ */
+ protected function maybeWatchedLink( $link, $watched = false ) {
+ if ( $watched ) {
+ return '<strong class="mw-watched">' . $link . '</strong>';
+ } else {
+ return '<span class="mw-rc-unwatched">' . $link . '</span>';
+ }
+ }
+
+ /** Inserts a rollback link
+ *
+ * @param $s string
+ * @param $rc RecentChange
+ */
+ public function insertRollback( &$s, &$rc ) {
+ if ( $rc->mAttribs['rc_type'] == RC_EDIT && $rc->mAttribs['rc_this_oldid'] && $rc->mAttribs['rc_cur_id'] ) {
+ $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'] )
+ {
+ $rev = new Revision( array(
+ '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']
+ ) );
+ $s .= ' ' . Linker::generateRollback( $rev, $this->getContext() );
+ }
+ }
+ }
+
+ /**
+ * @param $s string
+ * @param $rc RecentChange
+ * @param $classes
+ */
+ public function insertTags( &$s, &$rc, &$classes ) {
+ if ( empty( $rc->mAttribs['ts_tags'] ) ) {
+ return;
+ }
+
+ 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 subclasses to add anything special.
+ }
+
+ protected function showAsUnpatrolled( RecentChange $rc ) {
+ $unpatrolled = false;
+ if ( !$rc->mAttribs['rc_patrolled'] ) {
+ if ( $this->getUser()->useRCPatrol() ) {
+ $unpatrolled = true;
+ } elseif ( $this->getUser()->useNPPatrol() && $rc->mAttribs['rc_type'] == RC_NEW ) {
+ $unpatrolled = true;
+ }
+ }
+ return $unpatrolled;
+ }
+}
diff --git a/includes/changes/EnhancedChangesList.php b/includes/changes/EnhancedChangesList.php
new file mode 100644
index 00000000..433adb37
--- /dev/null
+++ b/includes/changes/EnhancedChangesList.php
@@ -0,0 +1,662 @@
+<?php
+/**
+ * Generates a list of changes using an Enhanced system (uses javascript).
+ *
+ * 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 EnhancedChangesList extends ChangesList {
+
+ protected $rc_cache;
+
+ /**
+ * Add the JavaScript file for enhanced changeslist
+ * @return String
+ */
+ public function beginRecentChangesList() {
+ $this->rc_cache = array();
+ $this->rcMoveIndex = 0;
+ $this->rcCacheIndex = 0;
+ $this->lastdate = '';
+ $this->rclistOpen = false;
+ $this->getOutput()->addModuleStyles( array(
+ 'mediawiki.special.changeslist',
+ 'mediawiki.special.changeslist.enhanced',
+ ) );
+ $this->getOutput()->addModules( array(
+ 'jquery.makeCollapsible',
+ 'mediawiki.icon',
+ ) );
+ return '';
+ }
+ /**
+ * Format a line for enhanced recentchange (aka with javascript and block of lines).
+ *
+ * @param $baseRC RecentChange
+ * @param $watched bool
+ *
+ * @return string
+ */
+ public function recentChangesLine( &$baseRC, $watched = false ) {
+ wfProfileIn( __METHOD__ );
+
+ # Create a specialised object
+ $rc = RCCacheEntry::newFromParent( $baseRC );
+
+ $curIdEq = array( 'curid' => $rc->mAttribs['rc_cur_id'] );
+
+ # If it's a new day, add the headline and flush the cache
+ $date = $this->getLanguage()->userDate( $rc->mAttribs['rc_timestamp'], $this->getUser() );
+ $ret = '';
+ if ( $date != $this->lastdate ) {
+ # Process current cache
+ $ret = $this->recentChangesBlock();
+ $this->rc_cache = array();
+ $ret .= Xml::element( 'h4', null, $date ) . "\n";
+ $this->lastdate = $date;
+ }
+
+ # Should patrol-related stuff be shown?
+ $rc->unpatrolled = $this->showAsUnpatrolled( $rc );
+
+ $showdifflinks = true;
+ # Make article link
+ $type = $rc->mAttribs['rc_type'];
+ $logType = $rc->mAttribs['rc_log_type'];
+ // Page moves, very old style, not supported anymore
+ if ( $type == RC_MOVE || $type == RC_MOVE_OVER_REDIRECT ) {
+ // New unpatrolled pages
+ } elseif ( $rc->unpatrolled && $type == RC_NEW ) {
+ $clink = Linker::linkKnown( $rc->getTitle() );
+ // Log entries
+ } elseif ( $type == RC_LOG ) {
+ if ( $logType ) {
+ $logtitle = SpecialPage::getTitleFor( 'Log', $logType );
+ $logpage = new LogPage( $logType );
+ $logname = $logpage->getName()->escaped();
+ $clink = $this->msg( 'parentheses' )->rawParams( Linker::linkKnown( $logtitle, $logname ) )->escaped();
+ } else {
+ $clink = Linker::link( $rc->getTitle() );
+ }
+ $watched = false;
+ // Log entries (old format) and special pages
+ } elseif ( $rc->mAttribs['rc_namespace'] == NS_SPECIAL ) {
+ wfDebug( "Unexpected special page in recentchanges\n" );
+ $clink = '';
+ // Edits
+ } else {
+ $clink = Linker::linkKnown( $rc->getTitle() );
+ }
+
+ # Don't show unusable diff links
+ if ( !ChangesList::userCan( $rc, Revision::DELETED_TEXT, $this->getUser() ) ) {
+ $showdifflinks = false;
+ }
+
+ $time = $this->getLanguage()->userTime( $rc->mAttribs['rc_timestamp'], $this->getUser() );
+ $rc->watched = $watched;
+ $rc->link = $clink;
+ $rc->timestamp = $time;
+ $rc->numberofWatchingusers = $baseRC->numberofWatchingusers;
+
+ # Make "cur" and "diff" links. Do not use link(), it is too slow if
+ # called too many times (50% of CPU time on RecentChanges!).
+ $thisOldid = $rc->mAttribs['rc_this_oldid'];
+ $lastOldid = $rc->mAttribs['rc_last_oldid'];
+
+ $querycur = $curIdEq + array( 'diff' => '0', 'oldid' => $thisOldid );
+ $querydiff = $curIdEq + array( 'diff' => $thisOldid, 'oldid' => $lastOldid );
+
+ if ( !$showdifflinks ) {
+ $curLink = $this->message['cur'];
+ $diffLink = $this->message['diff'];
+ } elseif ( in_array( $type, array( RC_NEW, RC_LOG, RC_MOVE, RC_MOVE_OVER_REDIRECT ) ) ) {
+ if ( $type != RC_NEW ) {
+ $curLink = $this->message['cur'];
+ } else {
+ $curUrl = htmlspecialchars( $rc->getTitle()->getLinkURL( $querycur ) );
+ $curLink = "<a href=\"$curUrl\" tabindex=\"{$baseRC->counter}\">{$this->message['cur']}</a>";
+ }
+ $diffLink = $this->message['diff'];
+ } else {
+ $diffUrl = htmlspecialchars( $rc->getTitle()->getLinkURL( $querydiff ) );
+ $curUrl = htmlspecialchars( $rc->getTitle()->getLinkURL( $querycur ) );
+ $diffLink = "<a href=\"$diffUrl\" tabindex=\"{$baseRC->counter}\">{$this->message['diff']}</a>";
+ $curLink = "<a href=\"$curUrl\" tabindex=\"{$baseRC->counter}\">{$this->message['cur']}</a>";
+ }
+
+ # Make "last" link
+ if ( !$showdifflinks || !$lastOldid ) {
+ $lastLink = $this->message['last'];
+ } elseif ( in_array( $type, array( RC_LOG, RC_MOVE, RC_MOVE_OVER_REDIRECT ) ) ) {
+ $lastLink = $this->message['last'];
+ } else {
+ $lastLink = Linker::linkKnown( $rc->getTitle(), $this->message['last'],
+ array(), $curIdEq + array( 'diff' => $thisOldid, 'oldid' => $lastOldid ) );
+ }
+
+ # Make user links
+ if ( $this->isDeleted( $rc, Revision::DELETED_USER ) ) {
+ $rc->userlink = ' <span class="history-deleted">' . $this->msg( 'rev-deleted-user' )->escaped() . '</span>';
+ } else {
+ $rc->userlink = Linker::userLink( $rc->mAttribs['rc_user'], $rc->mAttribs['rc_user_text'] );
+ $rc->usertalklink = Linker::userToolLinks( $rc->mAttribs['rc_user'], $rc->mAttribs['rc_user_text'] );
+ }
+
+ $rc->lastlink = $lastLink;
+ $rc->curlink = $curLink;
+ $rc->difflink = $diffLink;
+
+ # Put accumulated information into the cache, for later display
+ # Page moves go on their own line
+ $title = $rc->getTitle();
+ $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 );
+ } else {
+ # Logs are grouped by type
+ if ( $type == RC_LOG ) {
+ $secureName = SpecialPage::getTitleFor( 'Log', $logType )->getPrefixedDBkey();
+ }
+ if ( !isset( $this->rc_cache[$secureName] ) ) {
+ $this->rc_cache[$secureName] = array();
+ }
+
+ array_push( $this->rc_cache[$secureName], $rc );
+ }
+
+ wfProfileOut( __METHOD__ );
+
+ return $ret;
+ }
+
+ /**
+ * Enhanced RC group
+ * @return string
+ */
+ protected function recentChangesBlockGroup( $block ) {
+ global $wgRCShowChangedSize;
+
+ wfProfileIn( __METHOD__ );
+
+ # Add the namespace and title of the block as part of the class
+ $classes = array( 'mw-collapsible', 'mw-collapsed', 'mw-enhanced-rc' );
+ if ( $block[0]->mAttribs['rc_log_type'] ) {
+ # Log entry
+ $classes[] = Sanitizer::escapeClass( 'mw-changeslist-log-'
+ . $block[0]->mAttribs['rc_log_type'] . '-' . $block[0]->mAttribs['rc_title'] );
+ } else {
+ $classes[] = Sanitizer::escapeClass( 'mw-changeslist-ns'
+ . $block[0]->mAttribs['rc_namespace'] . '-' . $block[0]->mAttribs['rc_title'] );
+ }
+ $classes[] = $block[0]->watched && $block[0]->mAttribs['rc_timestamp'] >= $block[0]->watched
+ ? 'mw-changeslist-line-watched' : 'mw-changeslist-line-not-watched';
+ $r = Html::openElement( 'table', array( 'class' => $classes ) ) .
+ Html::openElement( 'tr' );
+
+ # Collate list of users
+ $userlinks = array();
+ # Other properties
+ $unpatrolled = false;
+ $isnew = false;
+ $allBots = true;
+ $allMinors = true;
+ $curId = $currentRevision = 0;
+ # Some catalyst variables...
+ $namehidden = true;
+ $allLogs = true;
+ foreach ( $block as $rcObj ) {
+ $oldid = $rcObj->mAttribs['rc_last_oldid'];
+ if ( $rcObj->mAttribs['rc_type'] == RC_NEW ) {
+ $isnew = true;
+ }
+ // If all log actions to this page were hidden, then don't
+ // give the name of the affected page for this block!
+ if ( !$this->isDeleted( $rcObj, LogPage::DELETED_ACTION ) ) {
+ $namehidden = false;
+ }
+ $u = $rcObj->userlink;
+ if ( !isset( $userlinks[$u] ) ) {
+ $userlinks[$u] = 0;
+ }
+ if ( $rcObj->unpatrolled ) {
+ $unpatrolled = true;
+ }
+ if ( $rcObj->mAttribs['rc_type'] != RC_LOG ) {
+ $allLogs = false;
+ }
+ # Get the latest entry with a page_id and oldid
+ # since logs may not have these.
+ if ( !$curId && $rcObj->mAttribs['rc_cur_id'] ) {
+ $curId = $rcObj->mAttribs['rc_cur_id'];
+ }
+ if ( !$currentRevision && $rcObj->mAttribs['rc_this_oldid'] ) {
+ $currentRevision = $rcObj->mAttribs['rc_this_oldid'];
+ }
+
+ if ( !$rcObj->mAttribs['rc_bot'] ) {
+ $allBots = false;
+ }
+ if ( !$rcObj->mAttribs['rc_minor'] ) {
+ $allMinors = false;
+ }
+
+ $userlinks[$u]++;
+ }
+
+ # Sort the list and convert to text
+ krsort( $userlinks );
+ asort( $userlinks );
+ $users = array();
+ foreach ( $userlinks as $userlink => $count ) {
+ $text = $userlink;
+ $text .= $this->getLanguage()->getDirMark();
+ if ( $count > 1 ) {
+ $text .= ' ' . $this->msg( 'parentheses' )->rawParams( $this->getLanguage()->formatNum( $count ) . '×' )->escaped();
+ }
+ array_push( $users, $text );
+ }
+
+ $users = ' <span class="changedby">'
+ . $this->msg( 'brackets' )->rawParams(
+ implode( $this->message['semicolon-separator'], $users )
+ )->escaped() . '</span>';
+
+ $tl = '<span class="mw-collapsible-toggle mw-collapsible-arrow 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, # 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>';
+
+ # Article link
+ if ( $namehidden ) {
+ $r .= ' <span class="history-deleted">' . $this->msg( 'rev-deleted-event' )->escaped() . '</span>';
+ } elseif ( $allLogs ) {
+ $r .= $this->maybeWatchedLink( $block[0]->link, $block[0]->watched );
+ } else {
+ $this->insertArticleLink( $r, $block[0], $block[0]->unpatrolled, $block[0]->watched );
+ }
+
+ $r .= $this->getLanguage()->getDirMark();
+
+ $queryParams['curid'] = $curId;
+
+ # Changes message
+ static $nchanges = array();
+ static $sinceLastVisitMsg = array();
+
+ $n = count( $block );
+ if ( !isset( $nchanges[$n] ) ) {
+ $nchanges[$n] = $this->msg( 'nchanges' )->numParams( $n )->escaped();
+ }
+
+ $sinceLast = 0;
+ $unvisitedOldid = null;
+ foreach ( $block as $rcObj ) {
+ // Same logic as below inside main foreach
+ if ( $rcObj->watched && $rcObj->mAttribs['rc_timestamp'] >= $rcObj->watched ) {
+ $sinceLast++;
+ $unvisitedOldid = $rcObj->mAttribs['rc_last_oldid'];
+ }
+ }
+ if ( !isset( $sinceLastVisitMsg[$sinceLast] ) ) {
+ $sinceLastVisitMsg[$sinceLast] =
+ $this->msg( 'enhancedrc-since-last-visit' )->numParams( $sinceLast )->escaped();
+ }
+
+ # Total change link
+ $r .= ' ';
+ $logtext = '';
+ if ( !$allLogs ) {
+ if ( !ChangesList::userCan( $rcObj, Revision::DELETED_TEXT, $this->getUser() ) ) {
+ $logtext .= $nchanges[$n];
+ } elseif ( $isnew ) {
+ $logtext .= $nchanges[$n];
+ } else {
+ $logtext .= Linker::link(
+ $block[0]->getTitle(),
+ $nchanges[$n],
+ array(),
+ $queryParams + array(
+ 'diff' => $currentRevision,
+ 'oldid' => $oldid,
+ ),
+ array( 'known', 'noclasses' )
+ );
+ if ( $sinceLast > 0 && $sinceLast < $n ) {
+ $logtext .= $this->message['pipe-separator'] . Linker::link(
+ $block[0]->getTitle(),
+ $sinceLastVisitMsg[$sinceLast],
+ array(),
+ $queryParams + array(
+ 'diff' => $currentRevision,
+ 'oldid' => $unvisitedOldid,
+ ),
+ array( 'known', 'noclasses' )
+ );
+ }
+ }
+ }
+
+ # History
+ if ( $allLogs ) {
+ // don't show history link for logs
+ } elseif ( $namehidden || !$block[0]->getTitle()->exists() ) {
+ $logtext .= $this->message['pipe-separator'] . $this->message['enhancedrc-history'];
+ } else {
+ $params = $queryParams;
+ $params['action'] = 'history';
+
+ $logtext .= $this->message['pipe-separator'] .
+ Linker::linkKnown(
+ $block[0]->getTitle(),
+ $this->message['enhancedrc-history'],
+ array(),
+ $params
+ );
+ }
+
+ if ( $logtext !== '' ) {
+ $r .= $this->msg( 'parentheses' )->rawParams( $logtext )->escaped();
+ }
+
+ $r .= ' <span class="mw-changeslist-separator">. .</span> ';
+
+ # Character difference (does not apply if only log items)
+ if ( $wgRCShowChangedSize && !$allLogs ) {
+ $last = 0;
+ $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++;
+ }
+ while ( $first > $last && $block[$first]->mAttribs['rc_old_len'] === null ) {
+ $first--;
+ }
+ # Get net change
+ $chardiff = $this->formatCharacterDifference( $block[$first], $block[$last] );
+
+ if ( $chardiff == '' ) {
+ $r .= ' ';
+ } else {
+ $r .= ' ' . $chardiff . ' <span class="mw-changeslist-separator">. .</span> ';
+ }
+ }
+
+ $r .= $users;
+ $r .= $this->numberofWatchingusers( $block[0]->numberofWatchingusers );
+
+ # Sub-entries
+ foreach ( $block as $rcObj ) {
+ # Classes to apply -- TODO implement
+ $classes = array();
+ $type = $rcObj->mAttribs['rc_type'];
+
+ $trClass = $rcObj->watched && $rcObj->mAttribs['rc_timestamp'] >= $rcObj->watched
+ ? ' class="mw-enhanced-watched"' : '';
+
+ $r .= '<tr' . $trClass . '><td></td><td class="mw-enhanced-rc">';
+ $r .= $this->recentChangesFlags( array(
+ 'newpage' => $type == RC_NEW,
+ 'minor' => $rcObj->mAttribs['rc_minor'],
+ 'unpatrolled' => $rcObj->unpatrolled,
+ 'bot' => $rcObj->mAttribs['rc_bot'],
+ ) );
+ $r .= '&#160;</td><td class="mw-enhanced-rc-nested"><span class="mw-enhanced-rc-time">';
+
+ $params = $queryParams;
+
+ if ( $rcObj->mAttribs['rc_this_oldid'] != 0 ) {
+ $params['oldid'] = $rcObj->mAttribs['rc_this_oldid'];
+ }
+
+ # Log timestamp
+ if ( $type == RC_LOG ) {
+ $link = $rcObj->timestamp;
+ # Revision link
+ } elseif ( !ChangesList::userCan( $rcObj, Revision::DELETED_TEXT, $this->getUser() ) ) {
+ $link = '<span class="history-deleted">' . $rcObj->timestamp . '</span> ';
+ } else {
+
+ $link = Linker::linkKnown(
+ $rcObj->getTitle(),
+ $rcObj->timestamp,
+ array(),
+ $params
+ );
+ if ( $this->isDeleted( $rcObj, Revision::DELETED_TEXT ) ) {
+ $link = '<span class="history-deleted">' . $link . '</span> ';
+ }
+ }
+ $r .= $link . '</span>';
+
+ if ( !$type == RC_LOG || $type == RC_NEW ) {
+ $r .= ' ' . $this->msg( 'parentheses' )->rawParams( $rcObj->curlink . $this->message['pipe-separator'] . $rcObj->lastlink )->escaped();
+ }
+ $r .= ' <span class="mw-changeslist-separator">. .</span> ';
+
+ # Character diff
+ if ( $wgRCShowChangedSize ) {
+ $cd = $this->formatCharacterDifference( $rcObj );
+ if ( $cd !== '' ) {
+ $r .= $cd . ' <span class="mw-changeslist-separator">. .</span> ';
+ }
+ }
+
+ if ( $rcObj->mAttribs['rc_type'] == RC_LOG ) {
+ $r .= $this->insertLogEntry( $rcObj );
+ } else {
+ # User links
+ $r .= $rcObj->userlink;
+ $r .= $rcObj->usertalklink;
+ $r .= $this->insertComment( $rcObj );
+ }
+
+ # Rollback
+ $this->insertRollback( $r, $rcObj );
+ # Tags
+ $this->insertTags( $r, $rcObj, $classes );
+
+ $r .= "</td></tr>\n";
+ }
+ $r .= "</table>\n";
+
+ $this->rcCacheIndex++;
+
+ wfProfileOut( __METHOD__ );
+
+ return $r;
+ }
+
+ /**
+ * Generate HTML for an arrow or placeholder graphic
+ * @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 = '' ) {
+ global $wgStylePath;
+ $encUrl = htmlspecialchars( $wgStylePath . '/common/images/Arr_' . $dir . '.png' );
+ $encAlt = htmlspecialchars( $alt );
+ $encTitle = htmlspecialchars( $title );
+ return "<img src=\"$encUrl\" width=\"12\" height=\"12\" alt=\"$encAlt\" title=\"$encTitle\" />";
+ }
+
+ /**
+ * Generate HTML for a right- or left-facing arrow,
+ * depending on language direction.
+ * @return String: HTML "<img>" tag
+ */
+ protected function sideArrow() {
+ $dir = $this->getLanguage()->isRTL() ? 'l' : 'r';
+ return $this->arrow( $dir, '+', $this->msg( 'rc-enhanced-expand' )->text() );
+ }
+
+ /**
+ * Generate HTML for a down-facing arrow
+ * depending on language direction.
+ * @return String: HTML "<img>" tag
+ */
+ protected function downArrow() {
+ return $this->arrow( 'd', '-', $this->msg( 'rc-enhanced-hide' )->text() );
+ }
+
+ /**
+ * Generate HTML for a spacer image
+ * @return String: HTML "<img>" tag
+ */
+ protected function spacerArrow() {
+ return $this->arrow( '', codepointToUtf8( 0xa0 ) ); // non-breaking space
+ }
+
+ /**
+ * Enhanced RC ungrouped line.
+ *
+ * @param $rcObj RecentChange
+ * @return String: a HTML formatted line (generated using $r)
+ */
+ protected function recentChangesBlockLine( $rcObj ) {
+ global $wgRCShowChangedSize;
+
+ wfProfileIn( __METHOD__ );
+ $query['curid'] = $rcObj->mAttribs['rc_cur_id'];
+
+ $type = $rcObj->mAttribs['rc_type'];
+ $logType = $rcObj->mAttribs['rc_log_type'];
+ $classes = array( 'mw-enhanced-rc' );
+ if ( $logType ) {
+ # Log entry
+ $classes[] = Sanitizer::escapeClass( 'mw-changeslist-log-'
+ . $logType . '-' . $rcObj->mAttribs['rc_title'] );
+ } else {
+ $classes[] = Sanitizer::escapeClass( 'mw-changeslist-ns' .
+ $rcObj->mAttribs['rc_namespace'] . '-' . $rcObj->mAttribs['rc_title'] );
+ }
+ $classes[] = $rcObj->watched && $rcObj->mAttribs['rc_timestamp'] >= $rcObj->watched
+ ? 'mw-changeslist-line-watched' : 'mw-changeslist-line-not-watched';
+ $r = Html::openElement( 'table', array( 'class' => $classes ) ) .
+ Html::openElement( 'tr' );
+
+ $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 .= $this->recentChangesFlags( array() ); // no flags, but need the placeholders
+ } else {
+ $r .= $this->recentChangesFlags( array(
+ 'newpage' => $type == RC_NEW,
+ 'minor' => $rcObj->mAttribs['rc_minor'],
+ 'unpatrolled' => $rcObj->unpatrolled,
+ 'bot' => $rcObj->mAttribs['rc_bot'],
+ ) );
+ }
+ $r .= '&#160;' . $rcObj->timestamp . '&#160;</td><td>';
+ # Article or log link
+ if ( $logType ) {
+ $logPage = new LogPage( $logType );
+ $logTitle = SpecialPage::getTitleFor( 'Log', $logType );
+ $logName = $logPage->getName()->escaped();
+ $r .= $this->msg( 'parentheses' )->rawParams( Linker::linkKnown( $logTitle, $logName ) )->escaped();
+ } else {
+ $this->insertArticleLink( $r, $rcObj, $rcObj->unpatrolled, $rcObj->watched );
+ }
+ # Diff and hist links
+ if ( $type != RC_LOG ) {
+ $query['action'] = 'history';
+ $r .= ' ' . $this->msg( 'parentheses' )->rawParams( $rcObj->difflink . $this->message['pipe-separator'] . Linker::linkKnown(
+ $rcObj->getTitle(),
+ $this->message['hist'],
+ array(),
+ $query
+ ) )->escaped();
+ }
+ $r .= ' <span class="mw-changeslist-separator">. .</span> ';
+ # Character diff
+ if ( $wgRCShowChangedSize ) {
+ $cd = $this->formatCharacterDifference( $rcObj );
+ if ( $cd !== '' ) {
+ $r .= $cd . ' <span class="mw-changeslist-separator">. .</span> ';
+ }
+ }
+
+ if ( $type == RC_LOG ) {
+ $r .= $this->insertLogEntry( $rcObj );
+ } else {
+ $r .= ' ' . $rcObj->userlink . $rcObj->usertalklink;
+ $r .= $this->insertComment( $rcObj );
+ $this->insertRollback( $r, $rcObj );
+ }
+
+ # Tags
+ $this->insertTags( $r, $rcObj, $classes );
+ # Show how many people are watching this if enabled
+ $r .= $this->numberofWatchingusers( $rcObj->numberofWatchingusers );
+
+ $r .= "</td></tr></table>\n";
+
+ wfProfileOut( __METHOD__ );
+
+ return $r;
+ }
+
+ /**
+ * If enhanced RC is in use, this function takes the previously cached
+ * RC lines, arranges them, and outputs the HTML
+ *
+ * @return string
+ */
+ protected function recentChangesBlock() {
+ if ( count ( $this->rc_cache ) == 0 ) {
+ return '';
+ }
+
+ wfProfileIn( __METHOD__ );
+
+ $blockOut = '';
+ foreach ( $this->rc_cache as $block ) {
+ if ( count( $block ) < 2 ) {
+ $blockOut .= $this->recentChangesBlockLine( array_shift( $block ) );
+ } else {
+ $blockOut .= $this->recentChangesBlockGroup( $block );
+ }
+ }
+
+ wfProfileOut( __METHOD__ );
+
+ return '<div>' . $blockOut . '</div>';
+ }
+
+ /**
+ * Returns text for the end of RC
+ * If enhanced RC is in use, returns pretty much all the text
+ * @return string
+ */
+ public function endRecentChangesList() {
+ return $this->recentChangesBlock() . parent::endRecentChangesList();
+ }
+
+}
diff --git a/includes/changes/OldChangesList.php b/includes/changes/OldChangesList.php
new file mode 100644
index 00000000..a7fe9342
--- /dev/null
+++ b/includes/changes/OldChangesList.php
@@ -0,0 +1,130 @@
+<?php
+/**
+ * Generate a list of changes using the good old system (no javascript).
+ *
+ * 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 OldChangesList extends ChangesList {
+
+ /**
+ * Format a line using the old system (aka without any javascript).
+ *
+ * @param $rc RecentChange, passed by reference
+ * @param bool $watched (default false)
+ * @param int $linenumber (default null)
+ *
+ * @return string|bool
+ */
+ public function recentChangesLine( &$rc, $watched = false, $linenumber = null ) {
+ global $wgRCShowChangedSize;
+ wfProfileIn( __METHOD__ );
+
+ # Should patrol-related stuff be shown?
+ $unpatrolled = $this->showAsUnpatrolled( $rc );
+
+ $dateheader = ''; // $s now contains only <li>...</li>, for hooks' convenience.
+ $this->insertDateHeader( $dateheader, $rc->mAttribs['rc_timestamp'] );
+
+ $s = '';
+ $classes = array();
+ // use mw-line-even/mw-line-odd class only if linenumber is given (feature from bug 14468)
+ if ( $linenumber ) {
+ if ( $linenumber & 1 ) {
+ $classes[] = 'mw-line-odd';
+ } else {
+ $classes[] = 'mw-line-even';
+ }
+ }
+
+ // Indicate watched status on the line to allow for more
+ // comprehensive styling.
+ $classes[] = $watched && $rc->mAttribs['rc_timestamp'] >= $watched
+ ? 'mw-changeslist-line-watched' : 'mw-changeslist-line-not-watched';
+
+ // Moved pages (very very old, not supported anymore)
+ if ( $rc->mAttribs['rc_type'] == RC_MOVE || $rc->mAttribs['rc_type'] == RC_MOVE_OVER_REDIRECT ) {
+ // Log entries
+ } elseif ( $rc->mAttribs['rc_log_type'] ) {
+ $logtitle = SpecialPage::getTitleFor( 'Log', $rc->mAttribs['rc_log_type'] );
+ $this->insertLog( $s, $logtitle, $rc->mAttribs['rc_log_type'] );
+ // Log entries (old format) or log targets, and special pages
+ } elseif ( $rc->mAttribs['rc_namespace'] == NS_SPECIAL ) {
+ list( $name, $subpage ) = SpecialPageFactory::resolveAlias( $rc->mAttribs['rc_title'] );
+ if ( $name == 'Log' ) {
+ $this->insertLog( $s, $rc->getTitle(), $subpage );
+ }
+ // Regular entries
+ } else {
+ $this->insertDiffHist( $s, $rc, $unpatrolled );
+ # M, N, b and ! (minor, new, bot and unpatrolled)
+ $s .= $this->recentChangesFlags(
+ array(
+ 'newpage' => $rc->mAttribs['rc_type'] == RC_NEW,
+ 'minor' => $rc->mAttribs['rc_minor'],
+ 'unpatrolled' => $unpatrolled,
+ 'bot' => $rc->mAttribs['rc_bot']
+ ),
+ ''
+ );
+ $this->insertArticleLink( $s, $rc, $unpatrolled, $watched );
+ }
+ # Edit/log timestamp
+ $this->insertTimestamp( $s, $rc );
+ # Bytes added or removed
+ if ( $wgRCShowChangedSize ) {
+ $cd = $this->formatCharacterDifference( $rc );
+ if ( $cd !== '' ) {
+ $s .= $cd . ' <span class="mw-changeslist-separator">. .</span> ';
+ }
+ }
+
+ if ( $rc->mAttribs['rc_type'] == RC_LOG ) {
+ $s .= $this->insertLogEntry( $rc );
+ } else {
+ # User tool links
+ $this->insertUserRelatedLinks( $s, $rc );
+ # LTR/RTL direction mark
+ $s .= $this->getLanguage()->getDirMark();
+ $s .= $this->insertComment( $rc );
+ }
+
+ # Tags
+ $this->insertTags( $s, $rc, $classes );
+ # Rollback
+ $this->insertRollback( $s, $rc );
+ # For subclasses
+ $this->insertExtra( $s, $rc, $classes );
+
+ # How many users watch this page
+ if ( $rc->numberofWatchingusers > 0 ) {
+ $s .= ' ' . $this->numberofWatchingusers( $rc->numberofWatchingusers );
+ }
+
+ if ( $this->watchlist ) {
+ $classes[] = Sanitizer::escapeClass( 'watchlist-' . $rc->mAttribs['rc_namespace'] . '-' . $rc->mAttribs['rc_title'] );
+ }
+
+ if ( !wfRunHooks( 'OldChangesListRecentChangesLine', array( &$this, &$s, $rc, &$classes ) ) ) {
+ wfProfileOut( __METHOD__ );
+ return false;
+ }
+
+ wfProfileOut( __METHOD__ );
+ return "$dateheader<li class=\"" . implode( ' ', $classes ) . "\">" . $s . "</li>\n";
+ }
+}
diff --git a/includes/changes/RCCacheEntry.php b/includes/changes/RCCacheEntry.php
new file mode 100644
index 00000000..9aef3d30
--- /dev/null
+++ b/includes/changes/RCCacheEntry.php
@@ -0,0 +1,35 @@
+<?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
+ */
+class RCCacheEntry extends RecentChange {
+ var $secureName, $link;
+ var $curlink, $difflink, $lastlink, $usertalklink, $versionlink;
+ var $userlink, $timestamp, $watched;
+
+ /**
+ * @param $rc RecentChange
+ * @return RCCacheEntry
+ */
+ static function newFromParent( $rc ) {
+ $rc2 = new RCCacheEntry;
+ $rc2->mAttribs = $rc->mAttribs;
+ $rc2->mExtra = $rc->mExtra;
+ return $rc2;
+ }
+}
diff --git a/includes/RecentChange.php b/includes/changes/RecentChange.php
index d7cf995c..980bd0a0 100644
--- a/includes/RecentChange.php
+++ b/includes/changes/RecentChange.php
@@ -96,10 +96,12 @@ class RecentChange {
}
/**
+ * @deprecated in 1.22
* @param $row
* @return RecentChange
*/
public static function newFromCurRow( $row ) {
+ wfDeprecated( __METHOD__, '1.22' );
$rc = new RecentChange;
$rc->loadFromCurRow( $row );
$rc->notificationtimestamp = false;
@@ -122,11 +124,12 @@ class RecentChange {
*
* @param array $conds of conditions
* @param $fname Mixed: override the method name in profiling/logs
+ * @param $options Array Query options
* @return RecentChange
*/
- public static function newFromConds( $conds, $fname = __METHOD__ ) {
+ public static function newFromConds( $conds, $fname = __METHOD__, $options = array() ) {
$dbr = wfGetDB( DB_SLAVE );
- $row = $dbr->selectRow( 'recentchanges', self::selectFields(), $conds, $fname );
+ $row = $dbr->selectRow( 'recentchanges', self::selectFields(), $conds, $fname, $options );
if ( $row !== false ) {
return self::newFromRow( $row );
} else {
@@ -260,7 +263,7 @@ class RecentChange {
# Notify external application via UDP
if ( !$noudp ) {
- $this->notifyRC2UDP();
+ $this->notifyRCFeeds();
}
# E-mail notifications
@@ -281,52 +284,96 @@ class RecentChange {
}
}
+ /**
+ * @deprecated since 1.22, use notifyRCFeeds instead.
+ */
public function notifyRC2UDP() {
- global $wgRC2UDPAddress, $wgRC2UDPOmitBots;
- # Notify external application via UDP
- if ( $wgRC2UDPAddress && ( !$this->mAttribs['rc_bot'] || !$wgRC2UDPOmitBots ) ) {
- self::sendToUDP( $this->getIRCLine() );
- }
+ wfDeprecated( __METHOD__, '1.22' );
+ $this->notifyRCFeeds();
}
/**
* Send some text to UDP.
- * @see RecentChange::cleanupForIRC
- * @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
+ * @deprecated since 1.22
*/
public static function sendToUDP( $line, $address = '', $prefix = '', $port = '' ) {
- global $wgRC2UDPAddress, $wgRC2UDPPrefix, $wgRC2UDPPort;
+ global $wgRC2UDPAddress, $wgRC2UDPInterwikiPrefix, $wgRC2UDPPort, $wgRC2UDPPrefix;
+
+ wfDeprecated( __METHOD__, '1.22' );
+
# Assume default for standard RC case
$address = $address ? $address : $wgRC2UDPAddress;
$prefix = $prefix ? $prefix : $wgRC2UDPPrefix;
$port = $port ? $port : $wgRC2UDPPort;
- # Notify external application via UDP
- if ( $address ) {
- $conn = socket_create( AF_INET, SOCK_DGRAM, SOL_UDP );
- if ( $conn ) {
- $line = $prefix . $line;
- wfDebug( __METHOD__ . ": sending UDP line: $line\n" );
- socket_sendto( $conn, $line, strlen( $line ), 0, $address, $port );
- socket_close( $conn );
- return true;
+
+ $engine = new UDPRCFeedEngine();
+ $feed = array(
+ 'uri' => "udp://$address:$port/$prefix",
+ 'formatter' => 'IRCColourfulRCFeedFormatter',
+ 'add_interwiki_prefix' => $wgRC2UDPInterwikiPrefix,
+ );
+
+ return $engine->send( $feed, $line );
+ }
+
+ /**
+ * Notify all the feeds about the change.
+ */
+ public function notifyRCFeeds() {
+ global $wgRCFeeds;
+
+ foreach ( $wgRCFeeds as $feed ) {
+ $engine = self::getEngine( $feed['uri'] );
+
+ if ( isset( $this->mExtra['actionCommentIRC'] ) ) {
+ $actionComment = $this->mExtra['actionCommentIRC'];
} else {
- wfDebug( __METHOD__ . ": failed to create UDP socket\n" );
+ $actionComment = null;
+ }
+
+ $omitBots = isset( $feed['omit_bots'] ) ? $feed['omit_bots'] : false;
+
+ if (
+ ( $omitBots && $this->mAttribs['rc_bot'] ) ||
+ $this->mAttribs['rc_type'] == RC_EXTERNAL
+ ) {
+ continue;
}
+
+ $formatter = new $feed['formatter']();
+ $line = $formatter->getLine( $feed, $this, $actionComment );
+
+ $engine->send( $feed, $line );
+ }
+ }
+
+ /**
+ * Gets the stream engine object for a given URI from $wgRCEngines
+ *
+ * @param $uri string URI to get the engine object for
+ * @return object The engine object
+ */
+ private static function getEngine( $uri ) {
+ global $wgRCEngines;
+
+ $scheme = parse_url( $uri, PHP_URL_SCHEME );
+ if ( !$scheme ) {
+ throw new MWException( __FUNCTION__ . ": Invalid stream logger URI: '$uri'" );
}
- return false;
+
+ if ( !isset( $wgRCEngines[$scheme] ) ) {
+ throw new MWException( __FUNCTION__ . ": Unknown stream logger URI scheme: $scheme" );
+ }
+
+ return new $wgRCEngines[$scheme];
}
/**
- * Remove newlines, carriage returns and decode html entities
- * @param $text String
- * @return String
+ * @deprecated since 1.22, moved to IRCColourfulRCFeedFormatter
*/
public static function cleanupForIRC( $text ) {
- return Sanitizer::decodeCharReferences( str_replace( array( "\n", "\r" ), array( "", "" ), $text ) );
+ wfDeprecated( __METHOD__, '1.22' );
+ return IRCColourfulRCFeedFormatter::cleanupForIRC( $text );
}
/**
@@ -407,6 +454,9 @@ class RecentChange {
),
__METHOD__
);
+ // Invalidate the page cache after the page has been patrolled
+ // to make sure that the Patrol link isn't visible any longer!
+ $this->getTitle()->invalidateCache();
return $dbw->affectedRows();
}
@@ -651,9 +701,11 @@ class RecentChange {
/**
* Makes a pseudo-RC entry from a cur row
*
+ * @deprected in 1.22
* @param $row
*/
public function loadFromCurRow( $row ) {
+ wfDeprecated( __METHOD__, '1.22' );
$this->mAttribs = array(
'rc_timestamp' => wfTimestamp( TS_MW, $row->rev_timestamp ),
'rc_cur_time' => $row->rev_timestamp,
@@ -721,89 +773,6 @@ class RecentChange {
}
/**
- * @return string
- */
- public function getIRCLine() {
- global $wgUseRCPatrol, $wgUseNPPatrol, $wgRC2UDPInterwikiPrefix, $wgLocalInterwiki,
- $wgCanonicalServer, $wgScript;
-
- 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 );
- } else {
- $titleObj =& $this->getTitle();
- }
- $title = $titleObj->getPrefixedText();
- $title = self::cleanupForIRC( $title );
-
- if ( $this->mAttribs['rc_type'] == RC_LOG ) {
- $url = '';
- } else {
- $url = $wgCanonicalServer . $wgScript;
- 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'];
- }
- if ( $wgUseRCPatrol || ( $this->mAttribs['rc_type'] == RC_NEW && $wgUseNPPatrol ) ) {
- $query .= '&rcid=' . $this->mAttribs['rc_id'];
- }
- // HACK: We need this hook for WMF's secure server setup
- wfRunHooks( 'IRCLineURL', array( &$url, &$query ) );
- $url .= $query;
- }
-
- 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 ) {
- $szdiff = "\002$szdiff\002";
- } elseif ( $szdiff >= 0 ) {
- $szdiff = '+' . $szdiff;
- }
- // @todo i18n with parentheses in content language?
- $szdiff = '(' . $szdiff . ')';
- } else {
- $szdiff = '';
- }
-
- $user = self::cleanupForIRC( $this->mAttribs['rc_user_text'] );
-
- if ( $this->mAttribs['rc_type'] == RC_LOG ) {
- $targetText = $this->getTitle()->getPrefixedText();
- $comment = self::cleanupForIRC( str_replace( "[[$targetText]]", "[[\00302$targetText\00310]]", $this->mExtra['actionCommentIRC'] ) );
- $flag = $this->mAttribs['rc_log_action'];
- } else {
- $comment = self::cleanupForIRC( $this->mAttribs['rc_comment'] );
- $flag = '';
- if ( !$this->mAttribs['rc_patrolled'] && ( $wgUseRCPatrol || $this->mAttribs['rc_type'] == RC_NEW && $wgUseNPPatrol ) ) {
- $flag .= '!';
- }
- $flag .= ( $this->mAttribs['rc_type'] == RC_NEW ? "N" : "" ) . ( $this->mAttribs['rc_minor'] ? "M" : "" ) . ( $this->mAttribs['rc_bot'] ? "B" : "" );
- }
-
- if ( $wgRC2UDPInterwikiPrefix === true && $wgLocalInterwiki !== false ) {
- $prefix = $wgLocalInterwiki;
- } elseif ( $wgRC2UDPInterwikiPrefix ) {
- $prefix = $wgRC2UDPInterwikiPrefix;
- } else {
- $prefix = false;
- }
- if ( $prefix !== false ) {
- $titleString = "\00314[[\00303$prefix:\00307$title\00314]]";
- } else {
- $titleString = "\00314[[\00307$title\00314]]";
- }
-
- # 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";
-
- return $fullString;
- }
-
- /**
* Returns the change size (HTML).
* The lengths can be given optionally.
* @param $old int
@@ -823,6 +792,29 @@ class RecentChange {
return ChangesList::showCharacterDifference( $old, $new );
}
+ /**
+ * Purge expired changes from the recentchanges table
+ * @since 1.22
+ */
+ public static function purgeExpiredChanges() {
+ if ( wfReadOnly() ) {
+ return;
+ }
+
+ $method = __METHOD__;
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->onTransactionIdle( function() use ( $dbw, $method ) {
+ global $wgRCMaxAge;
+
+ $cutoff = $dbw->timestamp( time() - $wgRCMaxAge );
+ $dbw->delete(
+ 'recentchanges',
+ array( 'rc_timestamp < ' . $dbw->addQuotes( $cutoff ) ),
+ $method
+ );
+ } );
+ }
+
private static function checkIPAddress( $ip ) {
global $wgRequest;
if ( $ip ) {
@@ -837,4 +829,18 @@ class RecentChange {
}
return $ip;
}
+
+ /**
+ * Check whether the given timestamp is new enough to have a RC row with a given tolerance
+ * as the recentchanges table might not be cleared out regularly (so older entries might exist)
+ * or rows which will be deleted soon shouldn't be included.
+ *
+ * @param $timestamp mixed MWTimestamp compatible timestamp
+ * @param $tolerance integer Tolerance in seconds
+ * @return bool
+ */
+ public static function isInRCLifespan( $timestamp, $tolerance = 0 ) {
+ global $wgRCMaxAge;
+ return wfTimestamp( TS_UNIX, $timestamp ) > time() - $tolerance - $wgRCMaxAge;
+ }
}
diff --git a/includes/clientpool/RedisConnectionPool.php b/includes/clientpool/RedisConnectionPool.php
index 5c7c4f20..ef71b182 100644
--- a/includes/clientpool/RedisConnectionPool.php
+++ b/includes/clientpool/RedisConnectionPool.php
@@ -1,6 +1,6 @@
<?php
/**
- * PhpRedis client connection pooling manager.
+ * Redis 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
@@ -23,7 +23,7 @@
*/
/**
- * Helper class to manage redis connections using PhpRedis.
+ * Helper class to manage Redis connections.
*
* 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.
@@ -36,39 +36,43 @@
* @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_*)
+ /**
+ * @name Pool settings.
+ * Settings there are shared for any connection made in this pool.
+ * See the singleton() method documentation for more details.
+ * @{
+ */
+ /** @var string Connection timeout in seconds */
+ protected $connectTimeout;
+ /** @var string Plaintext auth password */
+ protected $password;
+ /** @var bool Whether connections persist */
+ protected $persistent;
+ /** @var integer Serializer to use (Redis::SERIALIZER_*) */
+ protected $serializer;
+ /** @} */
- protected $idlePoolSize = 0; // integer; current idle pool size
+ /** @var integer Current idle pool size */
+ protected $idlePoolSize = 0;
/** @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)
+ /** @var Array (pool ID => RedisConnectionPool) */
+ protected static $instances = array();
- const SERVER_DOWN_TTL = 30; // integer; seconds to cache servers as "down"
+ /** integer; seconds to cache servers as "down". */
+ const SERVER_DOWN_TTL = 30;
/**
- * $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' );
+ if ( !class_exists( 'Redis' ) ) {
+ throw new MWException( __CLASS__ . ' requires a Redis client library. ' .
+ 'See https://www.mediawiki.org/wiki/Redis#Setup' );
}
$this->connectTimeout = $options['connectTimeout'];
$this->persistent = $options['persistent'];
@@ -103,6 +107,14 @@ class RedisConnectionPool {
/**
* @param $options Array
+ * $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".
* @return RedisConnectionPool
*/
public static function singleton( array $options ) {
@@ -164,7 +176,7 @@ class RedisConnectionPool {
// TCP connection
$hostPort = IP::splitHostAndPort( $server );
if ( !$hostPort ) {
- throw new MWException( __CLASS__.": invalid configured server \"$server\"" );
+ throw new MWException( __CLASS__ . ": invalid configured server \"$server\"" );
}
list( $host, $port ) = $hostPort;
if ( $port === false ) {
@@ -302,6 +314,38 @@ class RedisConnRef {
return call_user_func_array( array( $this->conn, $name ), $arguments );
}
+ /**
+ * @param string $script
+ * @param array $params
+ * @param integer $numKeys
+ * @return mixed
+ * @throws RedisException
+ */
+ public function luaEval( $script, array $params, $numKeys ) {
+ $sha1 = sha1( $script ); // 40 char hex
+ $conn = $this->conn; // convenience
+
+ // Try to run the server-side cached copy of the script
+ $conn->clearLastError();
+ $res = $conn->evalSha( $sha1, $params, $numKeys );
+ // If the script is not in cache, use eval() to retry and cache it
+ if ( preg_match( '/^NOSCRIPT/', $conn->getLastError() ) ) {
+ $conn->clearLastError();
+ $res = $conn->eval( $script, $params, $numKeys );
+ wfDebugLog( 'redis', "Used eval() for Lua script $sha1." );
+ }
+
+ if ( $conn->getLastError() ) { // script bug?
+ wfDebugLog( 'redis', "Lua script error: " . $conn->getLastError() );
+ }
+
+ return $res;
+ }
+
+ /**
+ * @param RedisConnRef $conn
+ * @return bool
+ */
public function isConnIdentical( Redis $conn ) {
return $this->conn === $conn;
}
diff --git a/includes/content/Content.php b/includes/content/Content.php
index 72729b09..5a90e092 100644
--- a/includes/content/Content.php
+++ b/includes/content/Content.php
@@ -40,8 +40,8 @@ interface Content {
* 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
+ * @todo Test that this actually works
+ * @todo Make sure this also works with LuceneSearch / WikiSearch
*/
public function getTextForSearchIndex();
@@ -51,11 +51,11 @@ interface Content {
* @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?!
+ * @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();
diff --git a/includes/content/ContentHandler.php b/includes/content/ContentHandler.php
index 9c201955..2a92e233 100644
--- a/includes/content/ContentHandler.php
+++ b/includes/content/ContentHandler.php
@@ -319,7 +319,7 @@ abstract class ContentHandler {
wfRunHooks( 'ContentHandlerForModelID', array( $modelId, &$handler ) );
if ( $handler === null ) {
- throw new MWException( "No handler for model '$modelId'' registered in \$wgContentHandlers" );
+ throw new MWException( "No handler for model '$modelId' registered in \$wgContentHandlers" );
}
if ( !( $handler instanceof ContentHandler ) ) {
@@ -449,10 +449,11 @@ abstract class ContentHandler {
* @since 1.21
*
* @param Title $destination the page to redirect to.
+ * @param string $text text to include in the redirect, if possible.
*
* @return Content
*/
- public function makeRedirectContent( Title $destination ) {
+ public function makeRedirectContent( Title $destination, $text = '' ) {
return null;
}
@@ -937,7 +938,7 @@ abstract class ContentHandler {
* @return ParserOptions
*/
public function makeParserOptions( $context ) {
- global $wgContLang;
+ global $wgContLang, $wgEnableParserLimitReporting;
if ( $context instanceof IContextSource ) {
$options = ParserOptions::newFromContext( $context );
@@ -949,7 +950,7 @@ abstract class ContentHandler {
throw new MWException( "Bad context for parser options: $context" );
}
- $options->enableLimitReport(); // show inclusion/loop reports
+ $options->enableLimitReport( $wgEnableParserLimitReporting ); // show inclusion/loop reports
$options->setTidy( true ); // fix bad HTML
return $options;
@@ -1060,7 +1061,7 @@ abstract class ContentHandler {
if ( isset( $handler[1] ) ) {
$info .= '::' . $handler[1];
}
- } else if ( is_object( $handler ) ) {
+ } elseif ( is_object( $handler ) ) {
$info = get_class( $handler[0] );
$info .= '::on' . $event;
} else {
@@ -1086,8 +1087,8 @@ abstract class ContentHandler {
$contentObjects[$k] = $v;
$v = $v->serialize();
- $contentTexts[ $k ] = $v;
- $args[ $k ] = $v;
+ $contentTexts[$k] = $v;
+ $args[$k] = $v;
}
}
@@ -1098,7 +1099,7 @@ abstract class ContentHandler {
foreach ( $contentTexts as $k => $orig ) {
/* @var Content $content */
- $modified = $args[ $k ];
+ $modified = $args[$k];
$content = $contentObjects[$k];
if ( $modified !== $orig ) {
@@ -1106,7 +1107,7 @@ abstract class ContentHandler {
$content = $content->getContentHandler()->unserializeContent( $modified );
}
- $args[ $k ] = $content;
+ $args[$k] = $content;
}
return $ok;
diff --git a/includes/content/CssContent.php b/includes/content/CssContent.php
index 569d122d..03cc2d00 100644
--- a/includes/content/CssContent.php
+++ b/includes/content/CssContent.php
@@ -46,7 +46,7 @@ class CssContent extends TextContent {
*/
public function preSaveTransform( Title $title, User $user, ParserOptions $popts ) {
global $wgParser;
- // @todo: make pre-save transformation optional for script pages
+ // @todo Make pre-save transformation optional for script pages
$text = $this->getNativeData();
$pst = $wgParser->preSaveTransform( $text, $title, $user, $popts );
diff --git a/includes/content/JavaScriptContent.php b/includes/content/JavaScriptContent.php
index 9cd947f9..2ae572be 100644
--- a/includes/content/JavaScriptContent.php
+++ b/includes/content/JavaScriptContent.php
@@ -46,7 +46,7 @@ class JavaScriptContent extends TextContent {
*/
public function preSaveTransform( Title $title, User $user, ParserOptions $popts ) {
global $wgParser;
- // @todo: make pre-save transformation optional for script pages
+ // @todo Make pre-save transformation optional for script pages
// See bug #32858
$text = $this->getNativeData();
diff --git a/includes/content/TextContent.php b/includes/content/TextContent.php
index 8fafcb63..f66dacd7 100644
--- a/includes/content/TextContent.php
+++ b/includes/content/TextContent.php
@@ -171,7 +171,7 @@ class TextContent extends AbstractContent {
$this->checkModelID( $that->getModel() );
- # @todo: could implement this in DifferenceEngine and just delegate here?
+ // @todo could implement this in DifferenceEngine and just delegate here?
if ( !$lang ) {
$lang = $wgContLang;
diff --git a/includes/content/WikitextContent.php b/includes/content/WikitextContent.php
index 8be4ebab..26337db9 100644
--- a/includes/content/WikitextContent.php
+++ b/includes/content/WikitextContent.php
@@ -61,7 +61,8 @@ class WikitextContent extends TextContent {
$myModelId = $this->getModel();
$sectionModelId = $with->getModel();
- if ( $sectionModelId != $myModelId ) {
+ if ( $sectionModelId != $myModelId ) {
+ wfProfileOut( __METHOD__ );
throw new MWException( "Incompatible content model for section: " .
"document uses $myModelId but " .
"section uses $sectionModelId." );
diff --git a/includes/content/WikitextContentHandler.php b/includes/content/WikitextContentHandler.php
index e1dcc668..b1b461fb 100644
--- a/includes/content/WikitextContentHandler.php
+++ b/includes/content/WikitextContentHandler.php
@@ -55,12 +55,27 @@ class WikitextContentHandler extends TextContentHandler {
* @see ContentHandler::makeRedirectContent
*
* @param Title $destination the page to redirect to.
+ * @param string $text text to include in the redirect, if possible.
*
* @return Content
*/
- public function makeRedirectContent( Title $destination ) {
+ public function makeRedirectContent( Title $destination, $text = '' ) {
+ $optionalColon = '';
+
+ if ( $destination->getNamespace() == NS_CATEGORY ) {
+ $optionalColon = ':';
+ } else {
+ $iw = $destination->getInterwiki();
+ if ( $iw && Language::fetchLanguageName( $iw, null, 'mw' ) ) {
+ $optionalColon = ':';
+ }
+ }
+
$mwRedir = MagicWord::get( 'redirect' );
- $redirectText = $mwRedir->getSynonym( 0 ) . ' [[' . $destination->getPrefixedText() . ']]';
+ $redirectText = $mwRedir->getSynonym( 0 ) . ' [[' . $optionalColon . $destination->getFullText() . ']]';
+ if ( $text != '' ) {
+ $redirectText .= "\n" . $text;
+ }
return new WikitextContent( $redirectText );
}
diff --git a/includes/context/ContextSource.php b/includes/context/ContextSource.php
index 33f51cb9..e13cfa88 100644
--- a/includes/context/ContextSource.php
+++ b/includes/context/ContextSource.php
@@ -125,7 +125,7 @@ abstract class ContextSource implements IContextSource {
/**
* Get the Language object
*
- * @deprecated 1.19 Use getLanguage instead
+ * @deprecated since 1.19 Use getLanguage instead
* @return Language
*/
public function getLang() {
diff --git a/includes/context/DerivativeContext.php b/includes/context/DerivativeContext.php
index b9a70068..fd9bf963 100644
--- a/includes/context/DerivativeContext.php
+++ b/includes/context/DerivativeContext.php
@@ -100,7 +100,10 @@ class DerivativeContext extends ContextSource {
*
* @param Title $t
*/
- public function setTitle( Title $t ) {
+ public function setTitle( $t ) {
+ if ( $t !== null && !$t instanceof Title ) {
+ throw new MWException( __METHOD__ . " expects an instance of Title" );
+ }
$this->title = $t;
}
@@ -209,7 +212,7 @@ class DerivativeContext extends ContextSource {
/**
* Set the Language object
*
- * @deprecated 1.19 Use setLanguage instead
+ * @deprecated since 1.19 Use setLanguage instead
* @param Language|string $l Language instance or language code
*/
public function setLang( $l ) {
@@ -237,7 +240,7 @@ class DerivativeContext extends ContextSource {
}
/**
- * @deprecated 1.19 Use getLanguage instead
+ * @deprecated since 1.19 Use getLanguage instead
* @return Language
*/
public function getLang() {
@@ -281,4 +284,20 @@ class DerivativeContext extends ContextSource {
return $this->getContext()->getSkin();
}
}
+
+ /**
+ * Get a message using the current context.
+ *
+ * This can't just inherit from ContextSource, since then
+ * it would set only the original context, and not take
+ * into account any changes.
+ *
+ * @param String Message name
+ * @param Variable number of message arguments
+ * @return Message
+ */
+ public function msg() {
+ $args = func_get_args();
+ return call_user_func_array( 'wfMessage', $args )->setContext( $this );
+ }
}
diff --git a/includes/context/IContextSource.php b/includes/context/IContextSource.php
index c7b221b9..35d5aed6 100644
--- a/includes/context/IContextSource.php
+++ b/includes/context/IContextSource.php
@@ -79,7 +79,7 @@ interface IContextSource {
/**
* Get the Language object
*
- * @deprecated 1.19 Use getLanguage instead
+ * @deprecated since 1.19 Use getLanguage instead
* @return Language
*/
public function getLang();
diff --git a/includes/context/RequestContext.php b/includes/context/RequestContext.php
index 6aefc98e..01ec57c0 100644
--- a/includes/context/RequestContext.php
+++ b/includes/context/RequestContext.php
@@ -90,7 +90,10 @@ class RequestContext implements IContextSource {
*
* @param Title $t
*/
- public function setTitle( Title $t ) {
+ public function setTitle( $t ) {
+ if ( $t !== null && !$t instanceof Title ) {
+ throw new MWException( __METHOD__ . " expects an instance of Title" );
+ }
$this->title = $t;
// Erase the WikiPage so a new one with the new title gets created.
$this->wikipage = null;
@@ -233,7 +236,7 @@ class RequestContext implements IContextSource {
/**
* Set the Language object
*
- * @deprecated 1.19 Use setLanguage instead
+ * @deprecated since 1.19 Use setLanguage instead
* @param Language|string $l Language instance or language code
*/
public function setLang( $l ) {
@@ -261,7 +264,7 @@ class RequestContext implements IContextSource {
}
/**
- * @deprecated 1.19 Use getLanguage instead
+ * @deprecated since 1.19 Use getLanguage instead
* @return Language
*/
public function getLang() {
@@ -401,10 +404,10 @@ class RequestContext implements IContextSource {
*/
public function exportSession() {
return array(
- 'ip' => $this->getRequest()->getIP(),
- 'headers' => $this->getRequest()->getAllHeaders(),
+ 'ip' => $this->getRequest()->getIP(),
+ 'headers' => $this->getRequest()->getAllHeaders(),
'sessionId' => session_id(),
- 'userId' => $this->getUser()->getId()
+ 'userId' => $this->getUser()->getId()
);
}
@@ -417,7 +420,9 @@ class RequestContext implements IContextSource {
* 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()
+ * @note suhosin.session.encrypt may interfere with this method.
+ *
+ * @param array $params Result of RequestContext::exportSession()
* @return ScopedCallback
* @throws MWException
* @since 1.21
diff --git a/includes/db/ChronologyProtector.php b/includes/db/ChronologyProtector.php
new file mode 100644
index 00000000..de5e72c3
--- /dev/null
+++ b/includes/db/ChronologyProtector.php
@@ -0,0 +1,106 @@
+<?php
+/**
+ * Generator of database load balancing 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
+ *
+ * @file
+ * @ingroup Database
+ */
+
+/**
+ * Class for ensuring a consistent ordering of events as seen by the user, despite replication.
+ * Kind of like Hawking's [[Chronology Protection Agency]].
+ */
+class ChronologyProtector {
+ /** @var Array (DB master name => position) */
+ protected $startupPositions = array();
+ /** @var Array (DB master name => position) */
+ protected $shutdownPositions = array();
+
+ protected $initialized = false; // bool; whether the session data was loaded
+
+ /**
+ * Initialise a LoadBalancer to give it appropriate chronology protection.
+ *
+ * If the session has a previous master position recorded, this will try to
+ * make sure that the next query to a slave of that master will see changes up
+ * to that position by delaying execution. The delay may timeout and allow stale
+ * data if no non-lagged slaves are available.
+ *
+ * @param $lb LoadBalancer
+ * @return void
+ */
+ public function initLB( LoadBalancer $lb ) {
+ if ( $lb->getServerCount() <= 1 ) {
+ return; // non-replicated setup
+ }
+ if ( !$this->initialized ) {
+ $this->initialized = true;
+ if ( isset( $_SESSION[__CLASS__] ) && is_array( $_SESSION[__CLASS__] ) ) {
+ $this->startupPositions = $_SESSION[__CLASS__];
+ }
+ }
+ $masterName = $lb->getServerName( 0 );
+ if ( !empty( $this->startupPositions[$masterName] ) ) {
+ $info = $lb->parentInfo();
+ $pos = $this->startupPositions[$masterName];
+ wfDebug( __METHOD__ . ": LB " . $info['id'] . " waiting for master pos $pos\n" );
+ $lb->waitFor( $pos );
+ }
+ }
+
+ /**
+ * Notify the ChronologyProtector that the LoadBalancer is about to shut
+ * down. Saves replication positions.
+ *
+ * @param $lb LoadBalancer
+ * @return void
+ */
+ public function shutdownLB( LoadBalancer $lb ) {
+ if ( session_id() == '' || $lb->getServerCount() <= 1 ) {
+ return; // don't start a session; don't bother with non-replicated setups
+ }
+ $masterName = $lb->getServerName( 0 );
+ if ( isset( $this->shutdownPositions[$masterName] ) ) {
+ return; // already done
+ }
+ // Only save the position if writes have been done on the connection
+ $db = $lb->getAnyOpenConnection( 0 );
+ $info = $lb->parentInfo();
+ if ( !$db || !$db->doneWrites() ) {
+ wfDebug( __METHOD__ . ": LB {$info['id']}, no writes done\n" );
+ return;
+ }
+ $pos = $db->getMasterPos();
+ wfDebug( __METHOD__ . ": LB {$info['id']} has master pos $pos\n" );
+ $this->shutdownPositions[$masterName] = $pos;
+ }
+
+ /**
+ * Notify the ChronologyProtector that the LBFactory is done calling shutdownLB() for now.
+ * May commit chronology data to persistent storage.
+ *
+ * @return void
+ */
+ public function shutdown() {
+ if ( session_id() != '' && count( $this->shutdownPositions ) ) {
+ wfDebug( __METHOD__ . ": saving master pos for " .
+ count( $this->shutdownPositions ) . " master(s)\n" );
+ $_SESSION[__CLASS__] = $this->shutdownPositions;
+ }
+ }
+}
diff --git a/includes/db/CloneDatabase.php b/includes/db/CloneDatabase.php
index 4e443741..819925cb 100644
--- a/includes/db/CloneDatabase.php
+++ b/includes/db/CloneDatabase.php
@@ -87,7 +87,7 @@ class CloneDatabase {
* Clone the table structure
*/
public function cloneTableStructure() {
- foreach( $this->tablesToClone as $tbl ) {
+ 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.
@@ -98,7 +98,7 @@ class CloneDatabase {
self::changePrefix( $this->newTablePrefix );
$newTableName = $this->db->tableName( $tbl, 'raw' );
- if( $this->dropCurrentTables && !in_array( $this->db->getType(), array( 'postgres', 'oracle' ) ) ) {
+ if ( $this->dropCurrentTables && !in_array( $this->db->getType(), array( 'postgres', 'oracle' ) ) ) {
$this->db->dropTable( $tbl, __METHOD__ );
wfDebug( __METHOD__ . " dropping {$newTableName}\n", true );
//Dropping the oldTable because the prefix was changed
@@ -115,9 +115,9 @@ class CloneDatabase {
* @param bool $dropTables Optionally drop the tables we created
*/
public function destroy( $dropTables = false ) {
- if( $dropTables ) {
+ if ( $dropTables ) {
self::changePrefix( $this->newTablePrefix );
- foreach( $this->tablesToClone as $tbl ) {
+ foreach ( $this->tablesToClone as $tbl ) {
$this->db->dropTable( $tbl );
}
}
@@ -127,7 +127,7 @@ class CloneDatabase {
/**
* Change the table prefix on all open DB connections/
*
- * @param $prefix
+ * @param $prefix
* @return void
*/
public static function changePrefix( $prefix ) {
@@ -137,8 +137,8 @@ class CloneDatabase {
}
/**
- * @param $lb LoadBalancer
- * @param $prefix
+ * @param $lb LoadBalancer
+ * @param $prefix
* @return void
*/
public static function changeLBPrefix( $lb, $prefix ) {
@@ -146,8 +146,8 @@ class CloneDatabase {
}
/**
- * @param $db DatabaseBase
- * @param $prefix
+ * @param $db DatabaseBase
+ * @param $prefix
* @return void
*/
public static function changeDBPrefix( $db, $prefix ) {
diff --git a/includes/db/Database.php b/includes/db/Database.php
index 65a74abf..10645608 100644
--- a/includes/db/Database.php
+++ b/includes/db/Database.php
@@ -24,13 +24,6 @@
* @ingroup Database
*/
-/** Number of times to re-try an operation in case of deadlock */
-define( 'DEADLOCK_TRIES', 4 );
-/** Minimum time to wait before retry, in microseconds */
-define( 'DEADLOCK_DELAY_MIN', 500000 );
-/** Maximum time to wait before retry */
-define( 'DEADLOCK_DELAY_MAX', 1500000 );
-
/**
* Base interface for all DBMS-specific code. At a bare minimum, all of the
* following must be implemented to support MediaWiki
@@ -165,7 +158,7 @@ interface DatabaseType {
* @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' );
+ function indexInfo( $table, $index, $fname = __METHOD__ );
/**
* Get the number of rows affected by the last write query
@@ -191,7 +184,7 @@ interface DatabaseType {
*
* @return string: wikitext of a link to the server software's web site
*/
- static function getSoftwareLink();
+ function getSoftwareLink();
/**
* A string describing the current software version, like from
@@ -212,10 +205,22 @@ interface DatabaseType {
}
/**
+ * Interface for classes that implement or wrap DatabaseBase
+ * @ingroup Database
+ */
+interface IDatabase {}
+
+/**
* Database abstraction object
* @ingroup Database
*/
-abstract class DatabaseBase implements DatabaseType {
+abstract class DatabaseBase implements IDatabase, DatabaseType {
+ /** Number of times to re-try an operation in case of deadlock */
+ const DEADLOCK_TRIES = 4;
+ /** Minimum time to wait before retry, in microseconds */
+ const DEADLOCK_DELAY_MIN = 500000;
+ /** Maximum time to wait before retry */
+ const DEADLOCK_DELAY_MAX = 1500000;
# ------------------------------------------------------------------------------
# Variables
@@ -230,14 +235,14 @@ abstract class DatabaseBase implements DatabaseType {
protected $mConn = null;
protected $mOpened = false;
- /**
- * @since 1.20
- * @var array of Closure
- */
+ /** @var callable[] */
protected $mTrxIdleCallbacks = array();
+ /** @var callable[] */
+ protected $mTrxPreCommitCallbacks = array();
protected $mTablePrefix;
protected $mFlags;
+ protected $mForeign;
protected $mTrxLevel = 0;
protected $mErrorCount = 0;
protected $mLBInfo = array();
@@ -282,6 +287,12 @@ abstract class DatabaseBase implements DatabaseType {
*/
protected $fileHandle = null;
+ /**
+ * @since 1.22
+ * @var Process cache of VIEWs names in the database
+ */
+ protected $allViews = null;
+
# ------------------------------------------------------------------------------
# Accessors
# ------------------------------------------------------------------------------
@@ -355,6 +366,8 @@ abstract class DatabaseBase implements DatabaseType {
* code should use lastErrno() and lastError() to handle the
* situation as appropriate.
*
+ * Do not use this function outside of the Database classes.
+ *
* @param $ignoreErrors bool|null
*
* @return bool The previous value of the flag.
@@ -553,12 +566,14 @@ abstract class DatabaseBase implements DatabaseType {
/**
* Returns true if there is a transaction open with possible write
- * queries or transaction idle callbacks waiting on it to finish.
+ * queries or transaction pre-commit/idle callbacks waiting on it to finish.
*
* @return bool
*/
public function writesOrCallbacksPending() {
- return $this->mTrxLevel && ( $this->mTrxDoneWrites || $this->mTrxIdleCallbacks );
+ return $this->mTrxLevel && (
+ $this->mTrxDoneWrites || $this->mTrxIdleCallbacks || $this->mTrxPreCommitCallbacks
+ );
}
/**
@@ -575,7 +590,6 @@ abstract class DatabaseBase implements DatabaseType {
* @param $flag Integer: DBO_* constants from Defines.php:
* - DBO_DEBUG: output some debug info (same as debug())
* - DBO_NOBUFFER: don't buffer results (inverse of bufferResults())
- * - DBO_IGNORE: ignore errors (same as ignoreErrors())
* - DBO_TRX: automatically start transactions
* - DBO_DEFAULT: automatically sets DBO_TRX if not in command line mode
* and removes it in command line mode
@@ -584,7 +598,7 @@ abstract class DatabaseBase implements DatabaseType {
public function setFlag( $flag ) {
global $wgDebugDBTransactions;
$this->mFlags |= $flag;
- if ( ( $flag & DBO_TRX) & $wgDebugDBTransactions ) {
+ if ( ( $flag & DBO_TRX ) & $wgDebugDBTransactions ) {
wfDebug( "Implicit transactions are now disabled.\n" );
}
}
@@ -654,15 +668,28 @@ abstract class DatabaseBase implements DatabaseType {
/**
* Constructor.
+ *
+ * FIXME: It is possible to construct a Database object with no associated
+ * connection object, by specifying no parameters to __construct(). This
+ * feature is deprecated and should be removed.
+ *
+ * FIXME: The long list of formal parameters here is not really appropriate
+ * for MySQL, and not at all appropriate for any other DBMS. It should be
+ * replaced by named parameters as in DatabaseBase::factory().
+ *
+ * DatabaseBase subclasses should not be constructed directly in external
+ * code. DatabaseBase::factory() should be used instead.
+ *
* @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 string $tablePrefix database table prefixes. By default use the prefix gave in LocalSettings.php
+ * @param bool $foreign disable some operations specific to local databases
*/
function __construct( $server = false, $user = false, $password = false, $dbName = false,
- $flags = 0, $tablePrefix = 'get from global'
+ $flags = 0, $tablePrefix = 'get from global', $foreign = false
) {
global $wgDBprefix, $wgCommandLineMode, $wgDebugDBTransactions;
@@ -689,6 +716,8 @@ abstract class DatabaseBase implements DatabaseType {
$this->mTablePrefix = $tablePrefix;
}
+ $this->mForeign = $foreign;
+
if ( $user ) {
$this->open( $server, $user, $password, $dbName );
}
@@ -706,7 +735,7 @@ abstract class DatabaseBase implements DatabaseType {
/**
* Given a DB type, construct the name of the appropriate child class of
* DatabaseBase. This is designed to replace all of the manual stuff like:
- * $class = 'Database' . ucfirst( strtolower( $type ) );
+ * $class = 'Database' . ucfirst( strtolower( $dbType ) );
* as well as validate against the canonical list of DB types we have
*
* This factory function is mostly useful for when you need to connect to a
@@ -714,7 +743,6 @@ abstract class DatabaseBase implements DatabaseType {
* an extension, et cetera). Do not use this to connect to the MediaWiki
* database. Example uses in core:
* @see LoadBalancer::reallyOpenConnection()
- * @see ExternalUser_MediaWiki::initFromCond()
* @see ForeignDBRepo::getMasterDB()
* @see WebInstaller_DBConnect::execute()
*
@@ -722,24 +750,55 @@ abstract class DatabaseBase implements DatabaseType {
*
* @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
+ * Valid options are: host, user, password, dbname, flags, tablePrefix, driver
* @return DatabaseBase subclass or null
*/
final public static function factory( $dbType, $p = array() ) {
$canonicalDBTypes = array(
- 'mysql', 'postgres', 'sqlite', 'oracle', 'mssql'
+ 'mysql' => array( 'mysqli', 'mysql' ),
+ 'postgres' => array(),
+ 'sqlite' => array(),
+ 'oracle' => array(),
+ 'mssql' => array(),
);
+
+ $driver = false;
$dbType = strtolower( $dbType );
- $class = 'Database' . ucfirst( $dbType );
+ if ( isset( $canonicalDBTypes[$dbType] ) && $canonicalDBTypes[$dbType] ) {
+ $possibleDrivers = $canonicalDBTypes[$dbType];
+ if ( !empty( $p['driver'] ) ) {
+ if ( in_array( $p['driver'], $possibleDrivers ) ) {
+ $driver = $p['driver'];
+ } else {
+ throw new MWException( __METHOD__ .
+ " cannot construct Database with type '$dbType' and driver '{$p['driver']}'" );
+ }
+ } else {
+ foreach ( $possibleDrivers as $posDriver ) {
+ if ( extension_loaded( $posDriver ) ) {
+ $driver = $posDriver;
+ break;
+ }
+ }
+ }
+ } else {
+ $driver = $dbType;
+ }
+ if ( $driver === false ) {
+ throw new MWException( __METHOD__ .
+ " no viable database extension found for type '$dbType'" );
+ }
- if( in_array( $dbType, $canonicalDBTypes ) || ( class_exists( $class ) && is_subclass_of( $class, 'DatabaseBase' ) ) ) {
+ $class = 'Database' . ucfirst( $driver );
+ if ( class_exists( $class ) && is_subclass_of( $class, 'DatabaseBase' ) ) {
return new $class(
isset( $p['host'] ) ? $p['host'] : false,
isset( $p['user'] ) ? $p['user'] : false,
isset( $p['password'] ) ? $p['password'] : false,
isset( $p['dbname'] ) ? $p['dbname'] : false,
isset( $p['flags'] ) ? $p['flags'] : 0,
- isset( $p['tablePrefix'] ) ? $p['tablePrefix'] : 'get from global'
+ isset( $p['tablePrefix'] ) ? $p['tablePrefix'] : 'get from global',
+ isset( $p['foreign'] ) ? $p['foreign'] : false
);
} else {
return null;
@@ -772,8 +831,9 @@ abstract class DatabaseBase implements DatabaseType {
/**
* @param $errno
* @param $errstr
+ * @access private
*/
- protected function connectionErrorHandler( $errno, $errstr ) {
+ public function connectionErrorHandler( $errno, $errstr ) {
$this->mPHPError = $errstr;
}
@@ -870,23 +930,8 @@ abstract class DatabaseBase implements DatabaseType {
* @return boolean|ResultWrapper. true for a successful write query, ResultWrapper object
* for a successful read query, or false on failure if $tempIgnore set
*/
- public function query( $sql, $fname = '', $tempIgnore = false ) {
- $isMaster = !is_null( $this->getLBInfo( 'master' ) );
- if ( !Profiler::instance()->isStub() ) {
- # generalizeSQL will probably cut down the query to reasonable
- # logging size most of the time. The substr is really just a sanity check.
-
- if ( $isMaster ) {
- $queryProf = 'query-m: ' . substr( DatabaseBase::generalizeSQL( $sql ), 0, 255 );
- $totalProf = 'DatabaseBase::query-master';
- } else {
- $queryProf = 'query: ' . substr( DatabaseBase::generalizeSQL( $sql ), 0, 255 );
- $totalProf = 'DatabaseBase::query';
- }
-
- wfProfileIn( $totalProf );
- wfProfileIn( $queryProf );
- }
+ public function query( $sql, $fname = __METHOD__, $tempIgnore = false ) {
+ global $wgUser, $wgDebugDBTransactions;
$this->mLastQuery = $sql;
if ( !$this->mDoneWrites && $this->isWriteQuery( $sql ) ) {
@@ -896,7 +941,6 @@ abstract class DatabaseBase implements DatabaseType {
}
# Add a comment for easy SHOW PROCESSLIST interpretation
- global $wgUser;
if ( is_object( $wgUser ) && $wgUser->isItemLoaded( 'name' ) ) {
$userName = $wgUser->getName();
if ( mb_strlen( $userName ) > 15 ) {
@@ -920,7 +964,6 @@ abstract class DatabaseBase implements DatabaseType {
# 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" );
}
@@ -932,6 +975,22 @@ abstract class DatabaseBase implements DatabaseType {
# Keep track of whether the transaction has write queries pending
if ( $this->mTrxLevel && !$this->mTrxDoneWrites && $this->isWriteQuery( $sql ) ) {
$this->mTrxDoneWrites = true;
+ Profiler::instance()->transactionWritingIn( $this->mServer, $this->mDBname );
+ }
+
+ $isMaster = !is_null( $this->getLBInfo( 'master' ) );
+ if ( !Profiler::instance()->isStub() ) {
+ # generalizeSQL will probably cut down the query to reasonable
+ # logging size most of the time. The substr is really just a sanity check.
+ if ( $isMaster ) {
+ $queryProf = 'query-m: ' . substr( DatabaseBase::generalizeSQL( $sql ), 0, 255 );
+ $totalProf = 'DatabaseBase::query-master';
+ } else {
+ $queryProf = 'query: ' . substr( DatabaseBase::generalizeSQL( $sql ), 0, 255 );
+ $totalProf = 'DatabaseBase::query';
+ }
+ wfProfileIn( $totalProf );
+ wfProfileIn( $queryProf );
}
if ( $this->debug() ) {
@@ -945,10 +1004,6 @@ abstract class DatabaseBase implements DatabaseType {
wfDebug( "Query {$this->mDBname} ($cnt) ($master): $sqlx\n" );
}
- if ( istainted( $sql ) & TC_MYSQL ) {
- throw new MWException( 'Tainted query found' );
- }
-
$queryId = MWDebug::query( $sql, $fname, $isMaster );
# Do the query and handle errors
@@ -961,6 +1016,7 @@ abstract class DatabaseBase implements DatabaseType {
# Transaction is gone, like it or not
$this->mTrxLevel = 0;
$this->mTrxIdleCallbacks = array(); // cancel
+ $this->mTrxPreCommitCallbacks = array(); // cancel
wfDebug( "Connection lost, reconnecting...\n" );
if ( $this->ping() ) {
@@ -1091,17 +1147,22 @@ abstract class DatabaseBase implements DatabaseType {
* @return String
*/
protected function fillPreparedArg( $matches ) {
- switch( $matches[1] ) {
- case '\\?': return '?';
- case '\\!': return '!';
- case '\\&': return '&';
+ switch ( $matches[1] ) {
+ case '\\?':
+ return '?';
+ case '\\!':
+ return '!';
+ case '\\&':
+ return '&';
}
list( /* $n */, $arg ) = each( $this->preparedArgs );
- switch( $matches[1] ) {
- case '?': return $this->addQuotes( $arg );
- case '!': return $arg;
+ switch ( $matches[1] ) {
+ case '?':
+ return $this->addQuotes( $arg );
+ case '!':
+ return $arg;
case '&':
# return $this->addQuotes( file_get_contents( $arg ) );
throw new DBUnexpectedError( $this, '& mode is not implemented. If it\'s really needed, uncomment the line above.' );
@@ -1117,7 +1178,8 @@ abstract class DatabaseBase implements DatabaseType {
*
* @param $res Mixed: A SQL result
*/
- public function freeResult( $res ) {}
+ public function freeResult( $res ) {
+ }
/**
* A SELECT wrapper which returns a single field from a single result row.
@@ -1136,9 +1198,9 @@ abstract class DatabaseBase implements DatabaseType {
*
* @return bool|mixed The value from the field, or false on failure.
*/
- public function selectField( $table, $var, $cond = '', $fname = 'DatabaseBase::selectField',
- $options = array() )
- {
+ public function selectField( $table, $var, $cond = '', $fname = __METHOD__,
+ $options = array()
+ ) {
if ( !is_array( $options ) ) {
$options = array( $options );
}
@@ -1236,7 +1298,7 @@ abstract class DatabaseBase implements DatabaseType {
$startOpts .= ' SQL_NO_CACHE';
}
- if ( isset( $options['USE INDEX'] ) && ! is_array( $options['USE INDEX'] ) ) {
+ if ( isset( $options['USE INDEX'] ) && is_string( $options['USE INDEX'] ) ) {
$useIndex = $this->useIndexClause( $options['USE INDEX'] );
} else {
$useIndex = '';
@@ -1427,7 +1489,7 @@ abstract class DatabaseBase implements DatabaseType {
* DBQueryError exception will be thrown, except if the "ignore errors"
* option was set, in which case false will be returned.
*/
- public function select( $table, $vars, $conds = '', $fname = 'DatabaseBase::select',
+ public function select( $table, $vars, $conds = '', $fname = __METHOD__,
$options = array(), $join_conds = array() ) {
$sql = $this->selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
@@ -1450,7 +1512,7 @@ abstract class DatabaseBase implements DatabaseType {
* @return string SQL query string.
* @see DatabaseBase::select()
*/
- public function selectSQLText( $table, $vars, $conds = '', $fname = 'DatabaseBase::select',
+ public function selectSQLText( $table, $vars, $conds = '', $fname = __METHOD__,
$options = array(), $join_conds = array() )
{
if ( is_array( $vars ) ) {
@@ -1458,28 +1520,26 @@ abstract class DatabaseBase implements DatabaseType {
}
$options = (array)$options;
+ $useIndexes = ( isset( $options['USE INDEX'] ) && is_array( $options['USE INDEX'] ) )
+ ? $options['USE INDEX']
+ : array();
if ( is_array( $table ) ) {
- $useIndex = ( isset( $options['USE INDEX'] ) && is_array( $options['USE INDEX'] ) )
- ? $options['USE INDEX']
- : array();
- if ( count( $join_conds ) || count( $useIndex ) ) {
- $from = ' FROM ' .
- $this->tableNamesWithUseIndexOrJOIN( $table, $useIndex, $join_conds );
- } else {
- $from = ' FROM ' . implode( ',', $this->tableNamesWithAlias( $table ) );
- }
+ $from = ' FROM ' .
+ $this->tableNamesWithUseIndexOrJOIN( $table, $useIndexes, $join_conds );
} elseif ( $table != '' ) {
if ( $table[0] == ' ' ) {
$from = ' FROM ' . $table;
} else {
- $from = ' FROM ' . $this->tableName( $table );
+ $from = ' FROM ' .
+ $this->tableNamesWithUseIndexOrJOIN( array( $table ), $useIndexes, array() );
}
} else {
$from = '';
}
- list( $startOpts, $useIndex, $preLimitTail, $postLimitTail ) = $this->makeSelectOptions( $options );
+ list( $startOpts, $useIndex, $preLimitTail, $postLimitTail ) =
+ $this->makeSelectOptions( $options );
if ( !empty( $conds ) ) {
if ( is_array( $conds ) ) {
@@ -1517,7 +1577,7 @@ abstract class DatabaseBase implements DatabaseType {
*
* @return object|bool
*/
- public function selectRow( $table, $vars, $conds, $fname = 'DatabaseBase::selectRow',
+ public function selectRow( $table, $vars, $conds, $fname = __METHOD__,
$options = array(), $join_conds = array() )
{
$options = (array)$options;
@@ -1558,7 +1618,7 @@ abstract class DatabaseBase implements DatabaseType {
* @return Integer: row count
*/
public function estimateRowCount( $table, $vars = '*', $conds = '',
- $fname = 'DatabaseBase::estimateRowCount', $options = array() )
+ $fname = __METHOD__, $options = array() )
{
$rows = 0;
$res = $this->select( $table, array( 'rowcount' => 'COUNT(*)' ), $conds, $fname, $options );
@@ -1582,19 +1642,20 @@ abstract class DatabaseBase implements DatabaseType {
static function generalizeSQL( $sql ) {
# This does the same as the regexp below would do, but in such a way
# as to avoid crashing php on some large strings.
- # $sql = preg_replace ( "/'([^\\\\']|\\\\.)*'|\"([^\\\\\"]|\\\\.)*\"/", "'X'", $sql);
+ # $sql = preg_replace( "/'([^\\\\']|\\\\.)*'|\"([^\\\\\"]|\\\\.)*\"/", "'X'", $sql );
- $sql = str_replace ( "\\\\", '', $sql );
- $sql = str_replace ( "\\'", '', $sql );
- $sql = str_replace ( "\\\"", '', $sql );
- $sql = preg_replace ( "/'.*'/s", "'X'", $sql );
- $sql = preg_replace ( '/".*"/s', "'X'", $sql );
+ $sql = str_replace( "\\\\", '', $sql );
+ $sql = str_replace( "\\'", '', $sql );
+ $sql = str_replace( "\\\"", '', $sql );
+ $sql = preg_replace( "/'.*'/s", "'X'", $sql );
+ $sql = preg_replace( '/".*"/s', "'X'", $sql );
# All newlines, tabs, etc replaced by single space
- $sql = preg_replace ( '/\s+/', ' ', $sql );
+ $sql = preg_replace( '/\s+/', ' ', $sql );
# All numbers => N
- $sql = preg_replace ( '/-?[0-9]+/s', 'N', $sql );
+ $sql = preg_replace( '/-?\d+(,-?\d+)+/s', 'N,...,N', $sql );
+ $sql = preg_replace( '/-?\d+/s', 'N', $sql );
return $sql;
}
@@ -1607,7 +1668,7 @@ abstract class DatabaseBase implements DatabaseType {
* @param string $fname calling function name (optional)
* @return Boolean: whether $table has filed $field
*/
- public function fieldExists( $table, $field, $fname = 'DatabaseBase::fieldExists' ) {
+ public function fieldExists( $table, $field, $fname = __METHOD__ ) {
$info = $this->fieldInfo( $table, $field );
return (bool)$info;
@@ -1624,8 +1685,8 @@ abstract class DatabaseBase implements DatabaseType {
*
* @return bool|null
*/
- public function indexExists( $table, $index, $fname = 'DatabaseBase::indexExists' ) {
- if( !$this->tableExists( $table ) ) {
+ public function indexExists( $table, $index, $fname = __METHOD__ ) {
+ if ( !$this->tableExists( $table ) ) {
return null;
}
@@ -1729,7 +1790,7 @@ abstract class DatabaseBase implements DatabaseType {
*
* @return bool
*/
- public function insert( $table, $a, $fname = 'DatabaseBase::insert', $options = array() ) {
+ public function insert( $table, $a, $fname = __METHOD__, $options = array() ) {
# No rows to insert, easy just return now
if ( !count( $a ) ) {
return true;
@@ -1828,7 +1889,7 @@ abstract class DatabaseBase implements DatabaseType {
* - LOW_PRIORITY: MySQL-specific, see MySQL manual.
* @return Boolean
*/
- function update( $table, $values, $conds, $fname = 'DatabaseBase::update', $options = array() ) {
+ function update( $table, $values, $conds, $fname = __METHOD__, $options = array() ) {
$table = $this->tableName( $table );
$opts = $this->makeUpdateOptions( $options );
$sql = "UPDATE $opts $table SET " . $this->makeList( $values, LIST_SET );
@@ -2065,6 +2126,7 @@ abstract class DatabaseBase implements DatabaseType {
} else {
list( $table ) = $dbDetails;
if ( $wgSharedDB !== null # We have a shared database
+ && $this->mForeign == false # We're not working on a foreign database
&& !$this->isQuotedIdentifier( $table ) # Paranoia check to prevent shared tables listing '`table`'
&& in_array( $table, $wgSharedTables ) # A shared table is selected
) {
@@ -2257,11 +2319,11 @@ abstract class DatabaseBase implements DatabaseType {
}
// We can't separate explicit JOIN clauses with ',', use ' ' for those
- $straightJoins = !empty( $ret ) ? implode( ',', $ret ) : "";
- $otherJoins = !empty( $retJOIN ) ? implode( ' ', $retJOIN ) : "";
+ $implicitJoins = !empty( $ret ) ? implode( ',', $ret ) : "";
+ $explicitJoins = !empty( $retJOIN ) ? implode( ' ', $retJOIN ) : "";
// Compile our final table clause
- return implode( ' ', array( $straightJoins, $otherJoins ) );
+ return implode( ' ', array( $implicitJoins, $explicitJoins ) );
}
/**
@@ -2274,9 +2336,9 @@ abstract class DatabaseBase implements DatabaseType {
protected function indexName( $index ) {
// Backwards-compatibility hack
$renamed = array(
- 'ar_usertext_timestamp' => 'usertext_timestamp',
- 'un_user_id' => 'user_id',
- 'un_user_ip' => 'user_ip',
+ 'ar_usertext_timestamp' => 'usertext_timestamp',
+ 'un_user_id' => 'user_id',
+ 'un_user_ip' => 'user_ip',
);
if ( isset( $renamed[$index] ) ) {
@@ -2287,8 +2349,7 @@ abstract class DatabaseBase implements DatabaseType {
}
/**
- * If it's a string, adds quotes and backslashes
- * Otherwise returns as-is
+ * Adds quotes and backslashes.
*
* @param $s string
*
@@ -2445,7 +2506,7 @@ abstract class DatabaseBase implements DatabaseType {
* a field name or an array of field names
* @param string $fname Calling function name (use __METHOD__) for logs/profiling
*/
- public function replace( $table, $uniqueIndexes, $rows, $fname = 'DatabaseBase::replace' ) {
+ public function replace( $table, $uniqueIndexes, $rows, $fname = __METHOD__ ) {
$quotedTable = $this->tableName( $table );
if ( count( $rows ) == 0 ) {
@@ -2457,7 +2518,7 @@ abstract class DatabaseBase implements DatabaseType {
$rows = array( $rows );
}
- foreach( $rows as $row ) {
+ foreach ( $rows as $row ) {
# Delete rows which collide
if ( $uniqueIndexes ) {
$sql = "DELETE FROM $quotedTable WHERE ";
@@ -2488,7 +2549,7 @@ abstract class DatabaseBase implements DatabaseType {
}
# Now insert the row
- $this->insert( $table, $row );
+ $this->insert( $table, $row, $fname );
}
}
@@ -2527,6 +2588,92 @@ abstract class DatabaseBase implements DatabaseType {
}
/**
+ * INSERT ON DUPLICATE KEY UPDATE wrapper, upserts an array into a table.
+ *
+ * This updates any conflicting rows (according to the unique indexes) using
+ * the provided SET clause and inserts any remaining (non-conflicted) rows.
+ *
+ * $rows may be either:
+ * - A single associative array. The array keys are the field names, and
+ * the values are the values to insert. The values are treated as data
+ * and will be quoted appropriately. If NULL is inserted, this will be
+ * converted to a database NULL.
+ * - An array with numeric keys, holding a list of associative arrays.
+ * This causes a multi-row INSERT on DBMSs that support it. The keys in
+ * each subarray must be identical to each other, and in the same order.
+ *
+ * It may be more efficient to leave off unique indexes which are unlikely
+ * to collide. However if you do this, you run the risk of encountering
+ * errors which wouldn't have occurred in MySQL.
+ *
+ * Usually throws a DBQueryError on failure. If errors are explicitly ignored,
+ * returns success.
+ *
+ * @param string $table Table name. This will be passed through DatabaseBase::tableName().
+ * @param array $rows A single row or list of rows to insert
+ * @param array $uniqueIndexes List of single field names or field name tuples
+ * @param array $set 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().
+ * @param string $fname Calling function name (use __METHOD__) for logs/profiling
+ * @param array $options of options
+ *
+ * @return bool
+ * @since 1.22
+ */
+ public function upsert(
+ $table, array $rows, array $uniqueIndexes, array $set, $fname = __METHOD__
+ ) {
+ if ( !count( $rows ) ) {
+ return true; // nothing to do
+ }
+ $rows = is_array( reset( $rows ) ) ? $rows : array( $rows );
+
+ if ( count( $uniqueIndexes ) ) {
+ $clauses = array(); // list WHERE clauses that each identify a single row
+ foreach ( $rows as $row ) {
+ foreach ( $uniqueIndexes as $index ) {
+ $index = is_array( $index ) ? $index : array( $index ); // columns
+ $rowKey = array(); // unique key to this row
+ foreach ( $index as $column ) {
+ $rowKey[$column] = $row[$column];
+ }
+ $clauses[] = $this->makeList( $rowKey, LIST_AND );
+ }
+ }
+ $where = array( $this->makeList( $clauses, LIST_OR ) );
+ } else {
+ $where = false;
+ }
+
+ $useTrx = !$this->mTrxLevel;
+ if ( $useTrx ) {
+ $this->begin( $fname );
+ }
+ try {
+ # Update any existing conflicting row(s)
+ if ( $where !== false ) {
+ $ok = $this->update( $table, $set, $where, $fname );
+ } else {
+ $ok = true;
+ }
+ # Now insert any non-conflicting row(s)
+ $ok = $this->insert( $table, $rows, $fname, array( 'IGNORE' ) ) && $ok;
+ } catch ( Exception $e ) {
+ if ( $useTrx ) {
+ $this->rollback( $fname );
+ }
+ throw $e;
+ }
+ if ( $useTrx ) {
+ $this->commit( $fname );
+ }
+
+ return $ok;
+ }
+
+ /**
* DELETE where the condition is a join.
*
* MySQL overrides this to use a multi-table DELETE syntax, in other databases
@@ -2548,7 +2695,7 @@ abstract class DatabaseBase implements DatabaseType {
* @throws DBUnexpectedError
*/
public function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds,
- $fname = 'DatabaseBase::deleteJoin' )
+ $fname = __METHOD__ )
{
if ( !$conds ) {
throw new DBUnexpectedError( $this,
@@ -2614,7 +2761,7 @@ abstract class DatabaseBase implements DatabaseType {
* @throws DBUnexpectedError
* @return bool|ResultWrapper
*/
- public function delete( $table, $conds, $fname = 'DatabaseBase::delete' ) {
+ public function delete( $table, $conds, $fname = __METHOD__ ) {
if ( !$conds ) {
throw new DBUnexpectedError( $this, 'DatabaseBase::delete() called with no conditions' );
}
@@ -2623,7 +2770,10 @@ abstract class DatabaseBase implements DatabaseType {
$sql = "DELETE FROM $table";
if ( $conds != '*' ) {
- $sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND );
+ if ( is_array( $conds ) ) {
+ $conds = $this->makeList( $conds, LIST_AND );
+ }
+ $sql .= ' WHERE ' . $conds;
}
return $this->query( $sql, $fname );
@@ -2656,7 +2806,7 @@ abstract class DatabaseBase implements DatabaseType {
* @return ResultWrapper
*/
public function insertSelect( $destTable, $srcTable, $varMap, $conds,
- $fname = 'DatabaseBase::insertSelect',
+ $fname = __METHOD__,
$insertOptions = array(), $selectOptions = array() )
{
$destTable = $this->tableName( $destTable );
@@ -2848,7 +2998,7 @@ abstract class DatabaseBase implements DatabaseType {
$args = func_get_args();
$function = array_shift( $args );
$oldIgnore = $this->ignoreErrors( true );
- $tries = DEADLOCK_TRIES;
+ $tries = self::DEADLOCK_TRIES;
if ( is_array( $function ) ) {
$fname = $function[0];
@@ -2865,7 +3015,7 @@ abstract class DatabaseBase implements DatabaseType {
if ( $errno ) {
if ( $this->wasDeadlock() ) {
# Retry
- usleep( mt_rand( DEADLOCK_DELAY_MIN, DEADLOCK_DELAY_MAX ) );
+ usleep( mt_rand( self::DEADLOCK_DELAY_MIN, self::DEADLOCK_DELAY_MAX ) );
} else {
$this->reportQueryError( $error, $errno, $sql, $fname );
}
@@ -2955,24 +3105,45 @@ abstract class DatabaseBase implements DatabaseType {
/**
* Run an anonymous function as soon as there is no transaction pending.
* If there is a transaction and it is rolled back, then the callback is cancelled.
+ * Queries in the function will run in AUTO-COMMIT mode unless there are begin() calls.
* Callbacks must commit any transactions that they begin.
*
- * This is useful for updates to different systems or separate transactions are needed.
+ * This is useful for updates to different systems or when separate transactions are needed.
+ * For example, one might want to enqueue jobs into a system outside the database, but only
+ * after the database is updated so that the jobs will see the data when they actually run.
+ * It can also be used for updates that easily cause deadlocks if locks are held too long.
*
+ * @param callable $callback
* @since 1.20
+ */
+ final public function onTransactionIdle( $callback ) {
+ $this->mTrxIdleCallbacks[] = array( $callback, wfGetCaller() );
+ if ( !$this->mTrxLevel ) {
+ $this->runOnTransactionIdleCallbacks();
+ }
+ }
+
+ /**
+ * Run an anonymous function before the current transaction commits or now if there is none.
+ * If there is a transaction and it is rolled back, then the callback is cancelled.
+ * Callbacks must not start nor commit any transactions.
+ *
+ * This is useful for updates that easily cause deadlocks if locks are held too long
+ * but where atomicity is strongly desired for these updates and some related updates.
*
- * @param Closure $callback
+ * @param callable $callback
+ * @since 1.22
*/
- final public function onTransactionIdle( Closure $callback ) {
+ final public function onTransactionPreCommitOrIdle( $callback ) {
if ( $this->mTrxLevel ) {
- $this->mTrxIdleCallbacks[] = $callback;
+ $this->mTrxPreCommitCallbacks[] = array( $callback, wfGetCaller() );
} else {
- $callback();
+ $this->onTransactionIdle( $callback ); // this will trigger immediately
}
}
/**
- * Actually run the "on transaction idle" callbacks.
+ * Actually any "on transaction idle" callbacks.
*
* @since 1.20
*/
@@ -2985,8 +3156,9 @@ abstract class DatabaseBase implements DatabaseType {
$this->mTrxIdleCallbacks = array(); // recursion guard
foreach ( $callbacks as $callback ) {
try {
+ list( $phpCallback ) = $callback;
$this->clearFlag( DBO_TRX ); // make each query its own transaction
- $callback();
+ call_user_func( $phpCallback );
$this->setFlag( $autoTrx ? DBO_TRX : 0 ); // restore automatic begin()
} catch ( Exception $e ) {}
}
@@ -2998,6 +3170,29 @@ abstract class DatabaseBase implements DatabaseType {
}
/**
+ * Actually any "on transaction pre-commit" callbacks.
+ *
+ * @since 1.22
+ */
+ protected function runOnTransactionPreCommitCallbacks() {
+ $e = null; // last exception
+ do { // callbacks may add callbacks :)
+ $callbacks = $this->mTrxPreCommitCallbacks;
+ $this->mTrxPreCommitCallbacks = array(); // recursion guard
+ foreach ( $callbacks as $callback ) {
+ try {
+ list( $phpCallback ) = $callback;
+ call_user_func( $phpCallback );
+ } catch ( Exception $e ) {}
+ }
+ } while ( count( $this->mTrxPreCommitCallbacks ) );
+
+ if ( $e instanceof Exception ) {
+ throw $e; // re-throw any last exception
+ }
+ }
+
+ /**
* Begin a transaction. If a transaction is already in progress, that transaction will be committed before the
* new transaction is started.
*
@@ -3009,7 +3204,7 @@ abstract class DatabaseBase implements DatabaseType {
*
* @param $fname string
*/
- final public function begin( $fname = 'DatabaseBase::begin' ) {
+ final public function begin( $fname = __METHOD__ ) {
global $wgDebugDBTransactions;
if ( $this->mTrxLevel ) { // implicit commit
@@ -3025,11 +3220,16 @@ abstract class DatabaseBase implements DatabaseType {
// 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" );
+ " (from {$this->mTrxFname}), performing implicit commit!\n"
+ );
}
}
+ $this->runOnTransactionPreCommitCallbacks();
$this->doCommit( $fname );
+ if ( $this->mTrxDoneWrites ) {
+ Profiler::instance()->transactionWritingOut( $this->mServer, $this->mDBname );
+ }
$this->runOnTransactionIdleCallbacks();
}
@@ -3062,22 +3262,27 @@ abstract class DatabaseBase implements DatabaseType {
* 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 = '' ) {
+ final public function commit( $fname = __METHOD__, $flush = '' ) {
if ( $flush != 'flush' ) {
if ( !$this->mTrxLevel ) {
wfWarn( "$fname: No transaction to commit, something got out of sync!" );
- } elseif( $this->mTrxAutomatic ) {
+ } 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 ) {
+ } elseif ( !$this->mTrxAutomatic ) {
wfWarn( "$fname: Flushing an explicit transaction, getting out of sync!" );
}
}
+ $this->runOnTransactionPreCommitCallbacks();
$this->doCommit( $fname );
+ if ( $this->mTrxDoneWrites ) {
+ Profiler::instance()->transactionWritingOut( $this->mServer, $this->mDBname );
+ }
+ $this->mTrxDoneWrites = false;
$this->runOnTransactionIdleCallbacks();
}
@@ -3102,12 +3307,17 @@ abstract class DatabaseBase implements DatabaseType {
*
* @param $fname string
*/
- final public function rollback( $fname = 'DatabaseBase::rollback' ) {
+ final public function rollback( $fname = __METHOD__ ) {
if ( !$this->mTrxLevel ) {
wfWarn( "$fname: No transaction to rollback, something got out of sync!" );
}
$this->doRollback( $fname );
$this->mTrxIdleCallbacks = array(); // cancel
+ $this->mTrxPreCommitCallbacks = array(); // cancel
+ if ( $this->mTrxDoneWrites ) {
+ Profiler::instance()->transactionWritingOut( $this->mServer, $this->mDBname );
+ }
+ $this->mTrxDoneWrites = false;
}
/**
@@ -3139,8 +3349,8 @@ abstract class DatabaseBase implements DatabaseType {
* @return Boolean: true if operation was successful
*/
public function duplicateTableStructure( $oldName, $newName, $temporary = false,
- $fname = 'DatabaseBase::duplicateTableStructure' )
- {
+ $fname = __METHOD__
+ ) {
throw new MWException(
'DatabaseBase::duplicateTableStructure is not implemented in descendant class' );
}
@@ -3152,11 +3362,45 @@ abstract class DatabaseBase implements DatabaseType {
* @param string $fname calling function name
* @throws MWException
*/
- function listTables( $prefix = null, $fname = 'DatabaseBase::listTables' ) {
+ function listTables( $prefix = null, $fname = __METHOD__ ) {
throw new MWException( 'DatabaseBase::listTables is not implemented in descendant class' );
}
/**
+ * Reset the views process cache set by listViews()
+ * @since 1.22
+ */
+ final public function clearViewsCache() {
+ $this->allViews = null;
+ }
+
+ /**
+ * Lists all the VIEWs in the database
+ *
+ * For caching purposes the list of all views should be stored in
+ * $this->allViews. The process cache can be cleared with clearViewsCache()
+ *
+ * @param string $prefix Only show VIEWs with this prefix, eg. unit_test_
+ * @param string $fname Name of calling function
+ * @throws MWException
+ * @since 1.22
+ */
+ public function listViews( $prefix = null, $fname = __METHOD__ ) {
+ throw new MWException( 'DatabaseBase::listViews is not implemented in descendant class' );
+ }
+
+ /**
+ * Differentiates between a TABLE and a VIEW
+ *
+ * @param $name string: Name of the database-structure to test.
+ * @throws MWException
+ * @since 1.22
+ */
+ public function isView( $name ) {
+ throw new MWException( 'DatabaseBase::isView is not implemented in descendant class' );
+ }
+
+ /**
* Convert a timestamp in one of the formats accepted by wfTimestamp()
* to the format used for inserting into timestamp fields in this DBMS.
*
@@ -3285,7 +3529,8 @@ abstract class DatabaseBase implements DatabaseType {
* @param $options Array
* @return void
*/
- public function setSessionOptions( array $options ) {}
+ public function setSessionOptions( array $options ) {
+ }
/**
* Read and execute SQL commands from a file.
@@ -3375,7 +3620,7 @@ abstract class DatabaseBase implements DatabaseType {
* @return bool|string
*/
public function sourceStream( $fp, $lineCallback = false, $resultCallback = false,
- $fname = 'DatabaseBase::sourceStream', $inputCallback = false )
+ $fname = __METHOD__, $inputCallback = false )
{
$cmd = '';
@@ -3614,12 +3859,12 @@ abstract class DatabaseBase implements DatabaseType {
* @return bool|ResultWrapper
* @since 1.18
*/
- public function dropTable( $tableName, $fName = 'DatabaseBase::dropTable' ) {
- if( !$this->tableExists( $tableName, $fName ) ) {
+ public function dropTable( $tableName, $fName = __METHOD__ ) {
+ if ( !$this->tableExists( $tableName, $fName ) ) {
return false;
}
$sql = "DROP TABLE " . $this->tableName( $tableName );
- if( $this->cascadingDeletes() ) {
+ if ( $this->cascadingDeletes() ) {
$sql .= " CASCADE";
}
return $this->query( $sql, $fName );
@@ -3691,9 +3936,21 @@ abstract class DatabaseBase implements DatabaseType {
return (string)$this->mConn;
}
+ /**
+ * Run a few simple sanity checks
+ */
public function __destruct() {
- if ( count( $this->mTrxIdleCallbacks ) ) { // sanity
- trigger_error( "Transaction idle callbacks still pending." );
+ if ( $this->mTrxLevel && $this->mTrxDoneWrites ) {
+ trigger_error( "Uncommitted DB writes (transaction from {$this->mTrxFname})." );
+ }
+ if ( count( $this->mTrxIdleCallbacks ) || count( $this->mTrxPreCommitCallbacks ) ) {
+ $callers = array();
+ foreach ( $this->mTrxIdleCallbacks as $callbackInfo ) {
+ $callers[] = $callbackInfo[1];
+
+ }
+ $callers = implode( ', ', $callers );
+ trigger_error( "DB transaction callbacks still pending (from $callers)." );
}
}
}
diff --git a/includes/db/DatabaseError.php b/includes/db/DatabaseError.php
index 628a2afc..0875695f 100644
--- a/includes/db/DatabaseError.php
+++ b/includes/db/DatabaseError.php
@@ -43,24 +43,12 @@ class DBError extends MWException {
}
/**
- * @param $html string
- * @return string
- */
- protected function getContentMessage( $html ) {
- if ( $html ) {
- return nl2br( htmlspecialchars( $this->getMessage() ) );
- } else {
- return $this->getMessage();
- }
- }
-
- /**
* @return string
*/
function getText() {
global $wgShowDBErrorBacktrace;
- $s = $this->getContentMessage( false ) . "\n";
+ $s = $this->getTextContent() . "\n";
if ( $wgShowDBErrorBacktrace ) {
$s .= "Backtrace:\n" . $this->getTraceAsString() . "\n";
@@ -75,14 +63,29 @@ class DBError extends MWException {
function getHTML() {
global $wgShowDBErrorBacktrace;
- $s = $this->getContentMessage( true );
+ $s = $this->getHTMLContent();
if ( $wgShowDBErrorBacktrace ) {
- $s .= '<p>Backtrace:</p><p>' . nl2br( htmlspecialchars( $this->getTraceAsString() ) );
+ $s .= '<p>Backtrace:</p><p>' .
+ nl2br( htmlspecialchars( $this->getTraceAsString() ) ) . '</p>';
}
return $s;
}
+
+ /**
+ * @return string
+ */
+ protected function getTextContent() {
+ return $this->getMessage();
+ }
+
+ /**
+ * @return string
+ */
+ protected function getHTMLContent() {
+ return '<p>' . nl2br( htmlspecialchars( $this->getMessage() ) ) . '</p>';
+ }
}
/**
@@ -96,11 +99,12 @@ class DBConnectionError extends DBError {
if ( trim( $error ) != '' ) {
$msg .= ": $error";
+ } elseif ( $db ) {
+ $error = $this->db->getServer();
}
- $this->error = $error;
-
parent::__construct( $db, $msg );
+ $this->error = $error;
}
/**
@@ -130,10 +134,10 @@ class DBConnectionError extends DBError {
}
/**
- * @return bool
+ * @return boolean
*/
- function getLogMessage() {
- # Don't send to the exception log
+ function isLoggable() {
+ // Don't send to the exception log, already in dberror log
return false;
}
@@ -141,39 +145,51 @@ class DBConnectionError extends DBError {
* @return string
*/
function getPageTitle() {
- global $wgSitename;
- return htmlspecialchars( $this->msg( 'dberr-header', "$wgSitename has a problem" ) );
+ return $this->msg( 'dberr-header', 'This wiki has a problem' );
}
/**
* @return string
*/
function getHTML() {
- global $wgShowDBErrorBacktrace;
+ global $wgShowDBErrorBacktrace, $wgShowHostnames, $wgShowSQLErrors;
- $sorry = htmlspecialchars( $this->msg( 'dberr-problems', 'Sorry! This site is experiencing technical difficulties.' ) );
+ $sorry = htmlspecialchars( $this->msg( 'dberr-problems', "Sorry!\nThis 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)' ) );
-
- # No database access
- MessageCache::singleton()->disable();
- if ( trim( $this->error ) == '' && $this->db ) {
- $this->error = $this->db->getProperty( 'mServer' );
+ if ( $wgShowHostnames || $wgShowSQLErrors ) {
+ $info = str_replace(
+ '$1', Html::element( 'span', array( 'dir' => 'ltr' ), $this->error ),
+ htmlspecialchars( $this->msg( 'dberr-info', '(Cannot contact the database server: $1)' ) )
+ );
+ } else {
+ $info = htmlspecialchars( $this->msg( 'dberr-info-hidden', '(Cannot contact the database server)' ) );
}
- $this->error = Html::element( 'span', array( 'dir' => 'ltr' ), $this->error );
+ # No database access
+ MessageCache::singleton()->disable();
- $noconnect = "<h1>$sorry</h1><p>$again</p><p><small>$info</small></p>";
- $text = str_replace( '$1', $this->error, $noconnect );
+ $text = "<h1>$sorry</h1><p>$again</p><p><small>$info</small></p>";
if ( $wgShowDBErrorBacktrace ) {
- $text .= '<p>Backtrace:</p><p>' . nl2br( htmlspecialchars( $this->getTraceAsString() ) );
+ $text .= '<p>Backtrace:</p><p>' .
+ nl2br( htmlspecialchars( $this->getTraceAsString() ) ) . '</p>';
}
- $extra = $this->searchForm();
+ $text .= '<hr />';
+ $text .= $this->searchForm();
- return "$text<hr />$extra";
+ return $text;
+ }
+
+ protected function getTextContent() {
+ global $wgShowHostnames, $wgShowSQLErrors;
+
+ if ( $wgShowHostnames || $wgShowSQLErrors ) {
+ return $this->getMessage();
+ } else {
+ return 'DB connection error';
+ }
}
public function reportHTML() {
@@ -188,7 +204,7 @@ class DBConnectionError extends DBError {
# Hack: extend the body for error messages
$cache = str_replace( array( '</html>', '</body>' ), '', $cache );
# Add cache notice...
- $cache .= '<div style="color:red;font-size:150%;font-weight:bold;">'.
+ $cache .= '<div style="color:red;font-size:150%;font-weight:bold;">' .
htmlspecialchars( $this->msg( 'dberr-cachederror',
'This is a cached copy of the requested page, and may not be up to date. ' ) ) .
'</div>';
@@ -302,55 +318,107 @@ class DBQueryError extends DBError {
}
/**
- * @param $html string
+ * @return boolean
+ */
+ function isLoggable() {
+ // Don't send to the exception log, already in dberror log
+ return false;
+ }
+
+ /**
* @return string
*/
- function getContentMessage( $html ) {
- if ( $this->useMessageCache() ) {
- if ( $html ) {
- $msg = 'dberrortext';
- $sql = htmlspecialchars( $this->getSQL() );
- $fname = htmlspecialchars( $this->fname );
- $error = htmlspecialchars( $this->error );
- } else {
- $msg = 'dberrortextcl';
- $sql = $this->getSQL();
- $fname = $this->fname;
- $error = $this->error;
+ function getPageTitle() {
+ return $this->msg( 'databaseerror', 'Database error' );
+ }
+
+ /**
+ * @return string
+ */
+ protected function getHTMLContent() {
+ $key = 'databaseerror-text';
+ $s = Html::element( 'p', array(), $this->msg( $key, $this->getFallbackMessage( $key ) ) );
+
+ $details = $this->getTechnicalDetails();
+ if ( $details ) {
+ $s .= '<ul>';
+ foreach ( $details as $key => $detail ) {
+ $s .= str_replace(
+ '$1', call_user_func_array( 'Html::element', $detail ),
+ Html::element( 'li', array(),
+ $this->msg( $key, $this->getFallbackMessage( $key ) )
+ )
+ );
}
- return wfMessage( $msg )->rawParams( $sql, $fname, $this->errno, $error )->text();
- } else {
- return parent::getContentMessage( $html );
+ $s .= '</ul>';
}
+
+ return $s;
}
/**
- * @return String
+ * @return string
*/
- function getSQL() {
- global $wgShowSQLErrors;
+ protected function getTextContent() {
+ $key = 'databaseerror-textcl';
+ $s = $this->msg( $key, $this->getFallbackMessage( $key ) ) . "\n";
- if ( !$wgShowSQLErrors ) {
- return $this->msg( 'sqlhidden', 'SQL hidden' );
- } else {
- return $this->sql;
+ foreach ( $this->getTechnicalDetails() as $key => $detail ) {
+ $s .= $this->msg( $key, $this->getFallbackMessage( $key ), $detail[2] ) . "\n";
}
+
+ return $s;
}
/**
- * @return bool
+ * Make a list of technical details that can be shown to the user. This information can
+ * aid in debugging yet may be useful to an attacker trying to exploit a security weakness
+ * in the software or server configuration.
+ *
+ * Thus no such details are shown by default, though if $wgShowHostnames is true, only the
+ * full SQL query is hidden; in fact, the error message often does contain a hostname, and
+ * sites using this option probably don't care much about "security by obscurity". Of course,
+ * if $wgShowSQLErrors is true, the SQL query *is* shown.
+ *
+ * @return array: Keys are message keys; values are arrays of arguments for Html::element().
+ * Array will be empty if users are not allowed to see any of these details at all.
*/
- function getLogMessage() {
- # Don't send to the exception log
- return false;
+ protected function getTechnicalDetails() {
+ global $wgShowHostnames, $wgShowSQLErrors;
+
+ $attribs = array( 'dir' => 'ltr' );
+ $details = array();
+
+ if ( $wgShowSQLErrors ) {
+ $details['databaseerror-query'] = array(
+ 'div', array( 'class' => 'mw-code' ) + $attribs, $this->sql );
+ }
+
+ if ( $wgShowHostnames || $wgShowSQLErrors ) {
+ $errorMessage = $this->errno . ' ' . $this->error;
+ $details['databaseerror-function'] = array( 'code', $attribs, $this->fname );
+ $details['databaseerror-error'] = array( 'samp', $attribs, $errorMessage );
+ }
+
+ return $details;
}
/**
- * @return String
+ * @param string $key Message key
+ * @return string: English message text
*/
- function getPageTitle() {
- return $this->msg( 'databaseerror', 'Database error' );
+ private function getFallbackMessage( $key ) {
+ $messages = array(
+ 'databaseerror-text' => 'A database query error has occurred.
+This may indicate a bug in the software.',
+ 'databaseerror-textcl' => 'A database query error has occurred.',
+ 'databaseerror-query' => 'Query: $1',
+ 'databaseerror-function' => 'Function: $1',
+ 'databaseerror-error' => 'Error: $1',
+ );
+ return $messages[$key];
}
+
}
/**
diff --git a/includes/db/DatabaseMssql.php b/includes/db/DatabaseMssql.php
index 6c45ffaf..240a097c 100644
--- a/includes/db/DatabaseMssql.php
+++ b/includes/db/DatabaseMssql.php
@@ -37,24 +37,31 @@ class DatabaseMssql extends DatabaseBase {
function cascadingDeletes() {
return true;
}
+
function cleanupTriggers() {
return true;
}
+
function strictIPs() {
return true;
}
+
function realTimestamps() {
return true;
}
+
function implicitGroupby() {
return false;
}
+
function implicitOrderby() {
return false;
}
+
function functionalIndexes() {
return true;
}
+
function unionSupportsOrderAndLimit() {
return false;
}
@@ -89,7 +96,7 @@ class DatabaseMssql extends DatabaseBase {
$connectionInfo = array();
- if( $dbName ) {
+ if ( $dbName ) {
$connectionInfo['Database'] = $dbName;
}
@@ -102,7 +109,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;
@@ -148,7 +155,7 @@ class DatabaseMssql extends DatabaseBase {
}
// MSSQL doesn't have EXTRACT(epoch FROM XXX)
- if ( preg_match('#\bEXTRACT\s*?\(\s*?EPOCH\s+FROM\b#i', $sql, $matches ) ) {
+ if ( preg_match( '#\bEXTRACT\s*?\(\s*?EPOCH\s+FROM\b#i', $sql, $matches ) ) {
// This is same as UNIX_TIMESTAMP, we need to calc # of seconds from 1970
$sql = str_replace( $matches[0], "DATEDIFF(s,CONVERT(datetime,'1/1/1970'),", $sql );
}
@@ -202,9 +209,9 @@ class DatabaseMssql extends DatabaseBase {
$retErrors = sqlsrv_errors( SQLSRV_ERR_ALL );
if ( $retErrors != null ) {
foreach ( $retErrors as $arrError ) {
- $strRet .= "SQLState: " . $arrError[ 'SQLSTATE'] . "\n";
- $strRet .= "Error Code: " . $arrError[ 'code'] . "\n";
- $strRet .= "Message: " . $arrError[ 'message'] . "\n";
+ $strRet .= "SQLState: " . $arrError['SQLSTATE'] . "\n";
+ $strRet .= "Error Code: " . $arrError['code'] . "\n";
+ $strRet .= "Message: " . $arrError['message'] . "\n";
}
} else {
$strRet = "No errors found";
@@ -290,7 +297,7 @@ class DatabaseMssql extends DatabaseBase {
* (e.g. array( 'page' => array('LEFT JOIN','page_latest=rev_id') )
* @return Mixed: database result resource (feed to Database::fetchObject or whatever), or false on failure
*/
- function select( $table, $vars, $conds = '', $fname = 'DatabaseMssql::select', $options = array(), $join_conds = array() )
+ function select( $table, $vars, $conds = '', $fname = __METHOD__, $options = array(), $join_conds = array() )
{
$sql = $this->selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
if ( isset( $options['EXPLAIN'] ) ) {
@@ -315,7 +322,7 @@ class DatabaseMssql extends DatabaseBase {
* (e.g. array( 'page' => array('LEFT JOIN','page_latest=rev_id') )
* @return string, the SQL text
*/
- function selectSQLText( $table, $vars, $conds = '', $fname = 'DatabaseMssql::select', $options = array(), $join_conds = array() ) {
+ function selectSQLText( $table, $vars, $conds = '', $fname = __METHOD__, $options = array(), $join_conds = array() ) {
if ( isset( $options['EXPLAIN'] ) ) {
unset( $options['EXPLAIN'] );
}
@@ -330,14 +337,16 @@ class DatabaseMssql extends DatabaseBase {
* Takes same arguments as Database::select()
* @return int
*/
- function estimateRowCount( $table, $vars = '*', $conds = '', $fname = 'DatabaseMssql::estimateRowCount', $options = array() ) {
+ function estimateRowCount( $table, $vars = '*', $conds = '', $fname = __METHOD__, $options = array() ) {
$options['EXPLAIN'] = true;// http://msdn2.microsoft.com/en-us/library/aa259203.aspx
$res = $this->select( $table, $vars, $conds, $fname, $options );
$rows = -1;
if ( $res ) {
$row = $this->fetchRow( $res );
- if ( isset( $row['EstimateRows'] ) ) $rows = $row['EstimateRows'];
+ if ( isset( $row['EstimateRows'] ) ) {
+ $rows = $row['EstimateRows'];
+ }
}
return $rows;
}
@@ -347,7 +356,7 @@ class DatabaseMssql extends DatabaseBase {
* If errors are explicitly ignored, returns NULL on failure
* @return array|bool|null
*/
- function indexInfo( $table, $index, $fname = 'DatabaseMssql::indexExists' ) {
+ function indexInfo( $table, $index, $fname = __METHOD__ ) {
# This does not return the same info as MYSQL would, but that's OK because MediaWiki never uses the
# returned value except to check for the existance of indexes.
$sql = "sp_helpindex '" . $table . "'";
@@ -392,7 +401,7 @@ class DatabaseMssql extends DatabaseBase {
* @throws DBQueryError
* @return bool
*/
- function insert( $table, $arrToInsert, $fname = 'DatabaseMssql::insert', $options = array() ) {
+ function insert( $table, $arrToInsert, $fname = __METHOD__, $options = array() ) {
# No rows to insert, easy just return now
if ( !count( $arrToInsert ) ) {
return true;
@@ -414,7 +423,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 ) );
}
@@ -427,11 +436,11 @@ class DatabaseMssql extends DatabaseBase {
$identityClause = '';
// if we have an identity column
- if( $identity ) {
+ if ( $identity ) {
// iterate through
- foreach ($a as $k => $v ) {
+ 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;";
$sqlPost = ";SET IDENTITY_INSERT $table OFF;";
@@ -501,7 +510,7 @@ class DatabaseMssql extends DatabaseBase {
} elseif ( $ret != null ) {
// remember number of rows affected
$this->mAffectedRows = sqlsrv_rows_affected( $ret );
- if ( !is_null($identity) ) {
+ if ( !is_null( $identity ) ) {
// then we want to get the identity column value we were assigned and save it off
$row = sqlsrv_fetch_object( $ret );
$this->mInsertId = $row->$identity;
@@ -530,7 +539,7 @@ class DatabaseMssql extends DatabaseBase {
* @throws DBQueryError
* @return null|ResultWrapper
*/
- function insertSelect( $destTable, $srcTable, $varMap, $conds, $fname = 'DatabaseMssql::insertSelect',
+ function insertSelect( $destTable, $srcTable, $varMap, $conds, $fname = __METHOD__,
$insertOptions = array(), $selectOptions = array() ) {
$ret = parent::insertSelect( $destTable, $srcTable, $varMap, $conds, $fname, $insertOptions, $selectOptions );
@@ -645,7 +654,7 @@ class DatabaseMssql extends DatabaseBase {
/**
* @return string wikitext of a link to the server software's web site
*/
- public static function getSoftwareLink() {
+ public function getSoftwareLink() {
return "[http://www.microsoft.com/sql/ MS SQL Server]";
}
@@ -661,11 +670,11 @@ class DatabaseMssql extends DatabaseBase {
return $version;
}
- function tableExists ( $table, $fname = __METHOD__, $schema = false ) {
+ function tableExists( $table, $fname = __METHOD__, $schema = false ) {
$res = sqlsrv_query( $this->mConn, "SELECT * FROM information_schema.tables
WHERE table_type='BASE TABLE' AND table_name = '$table'" );
if ( $res === false ) {
- print( "Error in tableExists query: " . $this->getErrors() );
+ print "Error in tableExists query: " . $this->getErrors();
return false;
}
if ( sqlsrv_fetch( $res ) ) {
@@ -679,12 +688,12 @@ class DatabaseMssql extends DatabaseBase {
* Query whether a given column exists in the mediawiki schema
* @return bool
*/
- function fieldExists( $table, $field, $fname = 'DatabaseMssql::fieldExists' ) {
+ function fieldExists( $table, $field, $fname = __METHOD__ ) {
$table = $this->tableName( $table );
$res = sqlsrv_query( $this->mConn, "SELECT DATA_TYPE FROM INFORMATION_SCHEMA.Columns
WHERE TABLE_NAME = '$table' AND COLUMN_NAME = '$field'" );
if ( $res === false ) {
- print( "Error in fieldExists query: " . $this->getErrors() );
+ print "Error in fieldExists query: " . $this->getErrors();
return false;
}
if ( sqlsrv_fetch( $res ) ) {
@@ -699,7 +708,7 @@ class DatabaseMssql extends DatabaseBase {
$res = sqlsrv_query( $this->mConn, "SELECT * FROM INFORMATION_SCHEMA.Columns
WHERE TABLE_NAME = '$table' AND COLUMN_NAME = '$field'" );
if ( $res === false ) {
- print( "Error in fieldInfo query: " . $this->getErrors() );
+ print "Error in fieldInfo query: " . $this->getErrors();
return false;
}
$meta = $this->fetchRow( $res );
@@ -712,7 +721,7 @@ class DatabaseMssql extends DatabaseBase {
/**
* Begin a transaction, committing any previously open transaction
*/
- protected function doBegin( $fname = 'DatabaseMssql::begin' ) {
+ protected function doBegin( $fname = __METHOD__ ) {
sqlsrv_begin_transaction( $this->mConn );
$this->mTrxLevel = 1;
}
@@ -720,7 +729,7 @@ class DatabaseMssql extends DatabaseBase {
/**
* End a transaction
*/
- protected function doCommit( $fname = 'DatabaseMssql::commit' ) {
+ protected function doCommit( $fname = __METHOD__ ) {
sqlsrv_commit( $this->mConn );
$this->mTrxLevel = 0;
}
@@ -729,7 +738,7 @@ class DatabaseMssql extends DatabaseBase {
* Rollback a transaction.
* No-op on non-transactional databases.
*/
- protected function doRollback( $fname = 'DatabaseMssql::rollback' ) {
+ protected function doRollback( $fname = __METHOD__ ) {
sqlsrv_rollback( $this->mConn );
$this->mTrxLevel = 0;
}
@@ -952,7 +961,7 @@ class DatabaseMssql extends DatabaseBase {
*/
class MssqlField implements Field {
private $name, $tablename, $default, $max_length, $nullable, $type;
- function __construct ( $info ) {
+ function __construct( $info ) {
$this->name = $info['COLUMN_NAME'];
$this->tablename = $info['TABLE_NAME'];
$this->default = $info['COLUMN_DEFAULT'];
@@ -1002,7 +1011,7 @@ class MssqlResult {
$rows = sqlsrv_fetch_array( $queryresult, SQLSRV_FETCH_ASSOC );
- foreach( $rows as $row ) {
+ foreach ( $rows as $row ) {
if ( $row !== null ) {
foreach ( $row as $k => $v ) {
if ( is_object( $v ) && method_exists( $v, 'format' ) ) {// DateTime Object
@@ -1040,7 +1049,7 @@ class MssqlResult {
$arrNum[] = $value;
}
}
- switch( $mode ) {
+ switch ( $mode ) {
case SQLSRV_FETCH_ASSOC:
$ret = $this->mRows[$this->mCursor];
break;
@@ -1093,37 +1102,96 @@ class MssqlResult {
$i++;
}
// http://msdn.microsoft.com/en-us/library/cc296183.aspx contains type table
- switch( $intType ) {
- case SQLSRV_SQLTYPE_BIGINT: $strType = 'bigint'; break;
- case SQLSRV_SQLTYPE_BINARY: $strType = 'binary'; break;
- case SQLSRV_SQLTYPE_BIT: $strType = 'bit'; break;
- case SQLSRV_SQLTYPE_CHAR: $strType = 'char'; break;
- case SQLSRV_SQLTYPE_DATETIME: $strType = 'datetime'; break;
- case SQLSRV_SQLTYPE_DECIMAL/*($precision, $scale)*/: $strType = 'decimal'; break;
- case SQLSRV_SQLTYPE_FLOAT: $strType = 'float'; break;
- case SQLSRV_SQLTYPE_IMAGE: $strType = 'image'; break;
- case SQLSRV_SQLTYPE_INT: $strType = 'int'; break;
- case SQLSRV_SQLTYPE_MONEY: $strType = 'money'; break;
- case SQLSRV_SQLTYPE_NCHAR/*($charCount)*/: $strType = 'nchar'; break;
- case SQLSRV_SQLTYPE_NUMERIC/*($precision, $scale)*/: $strType = 'numeric'; break;
- case SQLSRV_SQLTYPE_NVARCHAR/*($charCount)*/: $strType = 'nvarchar'; break;
- // case SQLSRV_SQLTYPE_NVARCHAR('max'): $strType = 'nvarchar(MAX)'; break;
- case SQLSRV_SQLTYPE_NTEXT: $strType = 'ntext'; break;
- case SQLSRV_SQLTYPE_REAL: $strType = 'real'; break;
- case SQLSRV_SQLTYPE_SMALLDATETIME: $strType = 'smalldatetime'; break;
- case SQLSRV_SQLTYPE_SMALLINT: $strType = 'smallint'; break;
- case SQLSRV_SQLTYPE_SMALLMONEY: $strType = 'smallmoney'; break;
- case SQLSRV_SQLTYPE_TEXT: $strType = 'text'; break;
- case SQLSRV_SQLTYPE_TIMESTAMP: $strType = 'timestamp'; break;
- case SQLSRV_SQLTYPE_TINYINT: $strType = 'tinyint'; break;
- case SQLSRV_SQLTYPE_UNIQUEIDENTIFIER: $strType = 'uniqueidentifier'; break;
- case SQLSRV_SQLTYPE_UDT: $strType = 'UDT'; break;
- case SQLSRV_SQLTYPE_VARBINARY/*($byteCount)*/: $strType = 'varbinary'; break;
- // case SQLSRV_SQLTYPE_VARBINARY('max'): $strType = 'varbinary(MAX)'; break;
- case SQLSRV_SQLTYPE_VARCHAR/*($charCount)*/: $strType = 'varchar'; break;
- // case SQLSRV_SQLTYPE_VARCHAR('max'): $strType = 'varchar(MAX)'; break;
- case SQLSRV_SQLTYPE_XML: $strType = 'xml'; break;
- default: $strType = $intType;
+ switch ( $intType ) {
+ case SQLSRV_SQLTYPE_BIGINT:
+ $strType = 'bigint';
+ break;
+ case SQLSRV_SQLTYPE_BINARY:
+ $strType = 'binary';
+ break;
+ case SQLSRV_SQLTYPE_BIT:
+ $strType = 'bit';
+ break;
+ case SQLSRV_SQLTYPE_CHAR:
+ $strType = 'char';
+ break;
+ case SQLSRV_SQLTYPE_DATETIME:
+ $strType = 'datetime';
+ break;
+ case SQLSRV_SQLTYPE_DECIMAL: // ($precision, $scale)
+ $strType = 'decimal';
+ break;
+ case SQLSRV_SQLTYPE_FLOAT:
+ $strType = 'float';
+ break;
+ case SQLSRV_SQLTYPE_IMAGE:
+ $strType = 'image';
+ break;
+ case SQLSRV_SQLTYPE_INT:
+ $strType = 'int';
+ break;
+ case SQLSRV_SQLTYPE_MONEY:
+ $strType = 'money';
+ break;
+ case SQLSRV_SQLTYPE_NCHAR: // ($charCount):
+ $strType = 'nchar';
+ break;
+ case SQLSRV_SQLTYPE_NUMERIC: // ($precision, $scale):
+ $strType = 'numeric';
+ break;
+ case SQLSRV_SQLTYPE_NVARCHAR: // ($charCount)
+ $strType = 'nvarchar';
+ break;
+ // case SQLSRV_SQLTYPE_NVARCHAR('max'):
+ // $strType = 'nvarchar(MAX)';
+ // break;
+ case SQLSRV_SQLTYPE_NTEXT:
+ $strType = 'ntext';
+ break;
+ case SQLSRV_SQLTYPE_REAL:
+ $strType = 'real';
+ break;
+ case SQLSRV_SQLTYPE_SMALLDATETIME:
+ $strType = 'smalldatetime';
+ break;
+ case SQLSRV_SQLTYPE_SMALLINT:
+ $strType = 'smallint';
+ break;
+ case SQLSRV_SQLTYPE_SMALLMONEY:
+ $strType = 'smallmoney';
+ break;
+ case SQLSRV_SQLTYPE_TEXT:
+ $strType = 'text';
+ break;
+ case SQLSRV_SQLTYPE_TIMESTAMP:
+ $strType = 'timestamp';
+ break;
+ case SQLSRV_SQLTYPE_TINYINT:
+ $strType = 'tinyint';
+ break;
+ case SQLSRV_SQLTYPE_UNIQUEIDENTIFIER:
+ $strType = 'uniqueidentifier';
+ break;
+ case SQLSRV_SQLTYPE_UDT:
+ $strType = 'UDT';
+ break;
+ case SQLSRV_SQLTYPE_VARBINARY: // ($byteCount)
+ $strType = 'varbinary';
+ break;
+ // case SQLSRV_SQLTYPE_VARBINARY('max'):
+ // $strType = 'varbinary(MAX)';
+ // break;
+ case SQLSRV_SQLTYPE_VARCHAR: // ($charCount)
+ $strType = 'varchar';
+ break;
+ // case SQLSRV_SQLTYPE_VARCHAR('max'):
+ // $strType = 'varchar(MAX)';
+ // break;
+ case SQLSRV_SQLTYPE_XML:
+ $strType = 'xml';
+ break;
+ default:
+ $strType = $intType;
}
return $strType;
}
diff --git a/includes/db/DatabaseMysql.php b/includes/db/DatabaseMysql.php
index 27aae188..956bb694 100644
--- a/includes/db/DatabaseMysql.php
+++ b/includes/db/DatabaseMysql.php
@@ -22,27 +22,19 @@
*/
/**
- * Database abstraction object for mySQL
- * Inherit all methods and properties of Database::Database()
+ * Database abstraction object for PHP extension mysql.
*
* @ingroup Database
* @see Database
*/
-class DatabaseMysql extends DatabaseBase {
-
- /**
- * @return string
- */
- function getType() {
- return 'mysql';
- }
+class DatabaseMysql extends DatabaseMysqlBase {
/**
* @param $sql string
* @return resource
*/
protected function doQuery( $sql ) {
- if( $this->bufferResults() ) {
+ if ( $this->bufferResults() ) {
$ret = mysql_query( $sql, $this->mConn );
} else {
$ret = mysql_unbuffered_query( $sql, $this->mConn );
@@ -50,39 +42,13 @@ class DatabaseMysql extends DatabaseBase {
return $ret;
}
- /**
- * @param $server string
- * @param $user string
- * @param $password string
- * @param $dbName string
- * @return bool
- * @throws DBConnectionError
- */
- function open( $server, $user, $password, $dbName ) {
- global $wgAllDBsAreLocalhost, $wgDBmysql5, $wgSQLMode;
- wfProfileIn( __METHOD__ );
-
- # Load mysql.so if we don't have it
- wfDl( 'mysql' );
-
+ protected function mysqlConnect( $realServer ) {
# Fail now
# Otherwise we get a suppressed fatal error, which is very hard to track down
- if ( !function_exists( 'mysql_connect' ) ) {
+ if ( !extension_loaded( 'mysql' ) ) {
throw new DBConnectionError( $this, "MySQL functions missing, have you compiled PHP with the --with-mysql option?\n" );
}
- # Debugging hack -- fake cluster
- if ( $wgAllDBsAreLocalhost ) {
- $realServer = 'localhost';
- } else {
- $realServer = $server;
- }
- $this->close();
- $this->mServer = $server;
- $this->mUser = $user;
- $this->mPassword = $password;
- $this->mDBname = $dbName;
-
$connFlags = 0;
if ( $this->mFlags & DBO_SSL ) {
$connFlags |= MYSQL_CLIENT_SSL;
@@ -91,81 +57,27 @@ class DatabaseMysql extends DatabaseBase {
$connFlags |= MYSQL_CLIENT_COMPRESS;
}
- wfProfileIn( "dbconnect-$server" );
-
- # The kernel's default SYN retransmission period is far too slow for us,
- # so we use a short timeout plus a manual retry. Retrying means that a small
- # but finite rate of SYN packet loss won't cause user-visible errors.
- $this->mConn = false;
if ( ini_get( 'mysql.connect_timeout' ) <= 3 ) {
$numAttempts = 2;
} else {
$numAttempts = 1;
}
- $this->installErrorHandler();
- for ( $i = 0; $i < $numAttempts && !$this->mConn; $i++ ) {
+
+ $conn = false;
+
+ for ( $i = 0; $i < $numAttempts && !$conn; $i++ ) {
if ( $i > 1 ) {
usleep( 1000 );
}
if ( $this->mFlags & DBO_PERSISTENT ) {
- $this->mConn = mysql_pconnect( $realServer, $user, $password, $connFlags );
+ $conn = mysql_pconnect( $realServer, $this->mUser, $this->mPassword, $connFlags );
} else {
# Create a new connection...
- $this->mConn = mysql_connect( $realServer, $user, $password, true, $connFlags );
- }
- #if ( $this->mConn === false ) {
- #$iplus = $i + 1;
- #wfLogDBError("Connect loop error $iplus of $max ($server): " . mysql_errno() . " - " . mysql_error()."\n");
- #}
- }
- $error = $this->restoreErrorHandler();
-
- wfProfileOut( "dbconnect-$server" );
-
- # Always log connection errors
- if ( !$this->mConn ) {
- if ( !$error ) {
- $error = $this->lastError();
- }
- wfLogDBError( "Error connecting to {$this->mServer}: $error\n" );
- wfDebug( "DB connection error\n" .
- "Server: $server, User: $user, Password: " .
- substr( $password, 0, 3 ) . "..., error: " . $error . "\n" );
-
- wfProfileOut( __METHOD__ );
- return $this->reportConnectionError( $error );
- }
-
- if ( $dbName != '' ) {
- wfSuppressWarnings();
- $success = mysql_select_db( $dbName, $this->mConn );
- wfRestoreWarnings();
- if ( !$success ) {
- wfLogDBError( "Error selecting database $dbName on server {$this->mServer}\n" );
- wfDebug( "Error selecting database $dbName on server {$this->mServer} " .
- "from client host " . wfHostname() . "\n" );
-
- wfProfileOut( __METHOD__ );
- return $this->reportConnectionError( "Error selecting database $dbName" );
+ $conn = mysql_connect( $realServer, $this->mUser, $this->mPassword, true, $connFlags );
}
}
- // Tell the server we're communicating with it in UTF-8.
- // This may engage various charset conversions.
- if( $wgDBmysql5 ) {
- $this->query( 'SET NAMES utf8', __METHOD__ );
- } else {
- $this->query( 'SET NAMES binary', __METHOD__ );
- }
- // Set SQL mode, default is turning them all off, can be overridden or skipped with null
- if ( is_string( $wgSQLMode ) ) {
- $mode = $this->addQuotes( $wgSQLMode );
- $this->query( "SET sql_mode = $mode", __METHOD__ );
- }
-
- $this->mOpened = true;
- wfProfileOut( __METHOD__ );
- return true;
+ return $conn;
}
/**
@@ -176,113 +88,6 @@ class DatabaseMysql extends DatabaseBase {
}
/**
- * @param $res ResultWrapper
- * @throws DBUnexpectedError
- */
- function freeResult( $res ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
- wfSuppressWarnings();
- $ok = mysql_free_result( $res );
- wfRestoreWarnings();
- if ( !$ok ) {
- throw new DBUnexpectedError( $this, "Unable to free MySQL result" );
- }
- }
-
- /**
- * @param $res ResultWrapper
- * @return object|bool
- * @throws DBUnexpectedError
- */
- function fetchObject( $res ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
- wfSuppressWarnings();
- $row = mysql_fetch_object( $res );
- wfRestoreWarnings();
-
- $errno = $this->lastErrno();
- // 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/en/mysql-fetch-row.html.
- if( $errno == 2000 || $errno == 2013 ) {
- throw new DBUnexpectedError( $this, 'Error in fetchObject(): ' . htmlspecialchars( $this->lastError() ) );
- }
- return $row;
- }
-
- /**
- * @param $res ResultWrapper
- * @return array|bool
- * @throws DBUnexpectedError
- */
- function fetchRow( $res ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
- wfSuppressWarnings();
- $row = mysql_fetch_array( $res );
- wfRestoreWarnings();
-
- $errno = $this->lastErrno();
- // 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/en/mysql-fetch-row.html.
- if( $errno == 2000 || $errno == 2013 ) {
- throw new DBUnexpectedError( $this, 'Error in fetchRow(): ' . htmlspecialchars( $this->lastError() ) );
- }
- return $row;
- }
-
- /**
- * @throws DBUnexpectedError
- * @param $res ResultWrapper
- * @return int
- */
- function numRows( $res ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
- wfSuppressWarnings();
- $n = mysql_num_rows( $res );
- wfRestoreWarnings();
- // 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;
- }
-
- /**
- * @param $res ResultWrapper
- * @return int
- */
- function numFields( $res ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
- return mysql_num_fields( $res );
- }
-
- /**
- * @param $res ResultWrapper
- * @param $n string
- * @return string
- */
- function fieldName( $res, $n ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
- return mysql_field_name( $res, $n );
- }
-
- /**
* @return int
*/
function insertId() {
@@ -290,18 +95,6 @@ class DatabaseMysql extends DatabaseBase {
}
/**
- * @param $res ResultWrapper
- * @param $row
- * @return bool
- */
- function dataSeek( $res, $row ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
- return mysql_data_seek( $res, $row );
- }
-
- /**
* @return int
*/
function lastErrno() {
@@ -313,27 +106,6 @@ class DatabaseMysql extends DatabaseBase {
}
/**
- * @return string
- */
- function lastError() {
- if ( $this->mConn ) {
- # Even if it's non-zero, it can still be invalid
- wfSuppressWarnings();
- $error = mysql_error( $this->mConn );
- if ( !$error ) {
- $error = mysql_error();
- }
- wfRestoreWarnings();
- } else {
- $error = mysql_error();
- }
- if( $error ) {
- $error .= ' (' . $this->mServer . ')';
- }
- return $error;
- }
-
- /**
* @return int
*/
function affectedRows() {
@@ -341,100 +113,6 @@ class DatabaseMysql extends DatabaseBase {
}
/**
- * @param $table string
- * @param $uniqueIndexes
- * @param $rows array
- * @param $fname string
- * @return ResultWrapper
- */
- function replace( $table, $uniqueIndexes, $rows, $fname = 'DatabaseMysql::replace' ) {
- return $this->nativeReplace( $table, $rows, $fname );
- }
-
- /**
- * Estimate rows in dataset
- * Returns estimated count, based on EXPLAIN output
- * Takes same arguments as Database::select()
- *
- * @param $table string|array
- * @param $vars string|array
- * @param $conds string|array
- * @param $fname string
- * @param $options string|array
- * @return int
- */
- 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 ) {
- return false;
- }
- if ( !$this->numRows( $res ) ) {
- return 0;
- }
-
- $rows = 1;
- foreach ( $res as $plan ) {
- $rows *= $plan->rows > 0 ? $plan->rows : 1; // avoid resetting to zero
- }
- return $rows;
- }
-
- /**
- * @param $table string
- * @param $field string
- * @return bool|MySQLField
- */
- function fieldInfo( $table, $field ) {
- $table = $this->tableName( $table );
- $res = $this->query( "SELECT * FROM $table LIMIT 1", __METHOD__, true );
- if ( !$res ) {
- return false;
- }
- $n = mysql_num_fields( $res->result );
- for( $i = 0; $i < $n; $i++ ) {
- $meta = mysql_fetch_field( $res->result, $i );
- if( $field == $meta->name ) {
- return new MySQLField( $meta );
- }
- }
- return false;
- }
-
- /**
- * Get information about an index into an object
- * Returns false if the index does not exist
- *
- * @param $table string
- * @param $index string
- * @param $fname string
- * @return bool|array|null False or null on failure
- */
- function indexInfo( $table, $index, $fname = 'DatabaseMysql::indexInfo' ) {
- # SHOW INDEX works in MySQL 3.23.58, but SHOW INDEXES does not.
- # SHOW INDEX should work for 3.x and up:
- # 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 );
-
- if ( !$res ) {
- return null;
- }
-
- $result = array();
-
- foreach ( $res as $row ) {
- if ( $row->Key_name == $index ) {
- $result[] = $row;
- }
- }
- return empty( $result ) ? false : $result;
- }
-
- /**
* @param $db
* @return bool
*/
@@ -444,596 +122,53 @@ class DatabaseMysql extends DatabaseBase {
}
/**
- * @param $s string
- *
- * @return string
- */
- function strencode( $s ) {
- $sQuoted = mysql_real_escape_string( $s, $this->mConn );
-
- if( $sQuoted === false ) {
- $this->ping();
- $sQuoted = mysql_real_escape_string( $s, $this->mConn );
- }
- return $sQuoted;
- }
-
- /**
- * MySQL uses `backticks` for identifier quoting instead of the sql standard "double quotes".
- *
- * @param $s string
- *
- * @return string
- */
- public function addIdentifierQuotes( $s ) {
- return "`" . $this->strencode( $s ) . "`";
- }
-
- /**
- * @param $name string
- * @return bool
- */
- public function isQuotedIdentifier( $name ) {
- return strlen( $name ) && $name[0] == '`' && substr( $name, -1, 1 ) == '`';
- }
-
- /**
- * @return bool
- */
- function ping() {
- $ping = mysql_ping( $this->mConn );
- if ( $ping ) {
- return true;
- }
-
- mysql_close( $this->mConn );
- $this->mOpened = false;
- $this->mConn = false;
- $this->open( $this->mServer, $this->mUser, $this->mPassword, $this->mDBname );
- return true;
- }
-
- /**
- * Returns slave lag.
- *
- * This will do a SHOW SLAVE STATUS
- *
- * @return int
- */
- function getLag() {
- if ( !is_null( $this->mFakeSlaveLag ) ) {
- wfDebug( "getLag: fake slave lagged {$this->mFakeSlaveLag} seconds\n" );
- return $this->mFakeSlaveLag;
- }
-
- return $this->getLagFromSlaveStatus();
- }
-
- /**
- * @return bool|int
- */
- function getLagFromSlaveStatus() {
- $res = $this->query( 'SHOW SLAVE STATUS', __METHOD__ );
- if ( !$res ) {
- return false;
- }
- $row = $res->fetchObject();
- if ( !$row ) {
- return false;
- }
- if ( strval( $row->Seconds_Behind_Master ) === '' ) {
- return false;
- } else {
- return intval( $row->Seconds_Behind_Master );
- }
- }
-
- /**
- * @deprecated in 1.19, use getLagFromSlaveStatus
- *
- * @return bool|int
- */
- function getLagFromProcesslist() {
- wfDeprecated( __METHOD__, '1.19' );
- $res = $this->query( 'SHOW PROCESSLIST', __METHOD__ );
- if( !$res ) {
- return false;
- }
- # Find slave SQL thread
- foreach( $res as $row ) {
- /* This should work for most situations - when default db
- * for thread is not specified, it had no events executed,
- * and therefore it doesn't know yet how lagged it is.
- *
- * Relay log I/O thread does not select databases.
- */
- if ( $row->User == 'system user' &&
- $row->State != 'Waiting for master to send event' &&
- $row->State != 'Connecting to master' &&
- $row->State != 'Queueing master event to the relay log' &&
- $row->State != 'Waiting for master update' &&
- $row->State != 'Requesting binlog dump' &&
- $row->State != 'Waiting to reconnect after a failed master event read' &&
- $row->State != 'Reconnecting after a failed master event read' &&
- $row->State != 'Registering slave on master'
- ) {
- # This is it, return the time (except -ve)
- if ( $row->Time > 0x7fffffff ) {
- return false;
- } else {
- return $row->Time;
- }
- }
- }
- return false;
- }
-
- /**
- * Wait for the slave to catch up to a given master position.
- *
- * @param $pos DBMasterPos object
- * @param $timeout Integer: the maximum number of seconds to wait for synchronisation
- * @return bool|string
- */
- function masterPosWait( DBMasterPos $pos, $timeout ) {
- $fname = 'DatabaseBase::masterPosWait';
- wfProfileIn( $fname );
-
- # Commit any open transactions
- if ( $this->mTrxLevel ) {
- $this->commit( __METHOD__ );
- }
-
- if ( !is_null( $this->mFakeSlaveLag ) ) {
- $status = parent::masterPosWait( $pos, $timeout );
- wfProfileOut( $fname );
- return $status;
- }
-
- # Call doQuery() directly, to avoid opening a transaction if DBO_TRX is set
- $encFile = $this->addQuotes( $pos->file );
- $encPos = intval( $pos->pos );
- $sql = "SELECT MASTER_POS_WAIT($encFile, $encPos, $timeout)";
- $res = $this->doQuery( $sql );
-
- if ( $res && $row = $this->fetchRow( $res ) ) {
- wfProfileOut( $fname );
- return $row[0];
- }
- wfProfileOut( $fname );
- return false;
- }
-
- /**
- * Get the position of the master from SHOW SLAVE STATUS
- *
- * @return MySQLMasterPos|bool
- */
- function getSlavePos() {
- if ( !is_null( $this->mFakeSlaveLag ) ) {
- return parent::getSlavePos();
- }
-
- $res = $this->query( 'SHOW SLAVE STATUS', 'DatabaseBase::getSlavePos' );
- $row = $this->fetchObject( $res );
-
- if ( $row ) {
- $pos = isset( $row->Exec_master_log_pos ) ? $row->Exec_master_log_pos : $row->Exec_Master_Log_Pos;
- return new MySQLMasterPos( $row->Relay_Master_Log_File, $pos );
- } else {
- return false;
- }
- }
-
- /**
- * Get the position of the master from SHOW MASTER STATUS
- *
- * @return MySQLMasterPos|bool
- */
- function getMasterPos() {
- if ( $this->mFakeMaster ) {
- return parent::getMasterPos();
- }
-
- $res = $this->query( 'SHOW MASTER STATUS', 'DatabaseBase::getMasterPos' );
- $row = $this->fetchObject( $res );
-
- if ( $row ) {
- return new MySQLMasterPos( $row->File, $row->Position );
- } else {
- return false;
- }
- }
-
- /**
* @return string
*/
function getServerVersion() {
return mysql_get_server_info( $this->mConn );
}
- /**
- * @param $index
- * @return string
- */
- function useIndexClause( $index ) {
- return "FORCE INDEX (" . $this->indexName( $index ) . ")";
- }
-
- /**
- * @return string
- */
- function lowPriorityOption() {
- return 'LOW_PRIORITY';
+ protected function mysqlFreeResult( $res ) {
+ return mysql_free_result( $res );
}
- /**
- * @return string
- */
- public static function getSoftwareLink() {
- return '[http://www.mysql.com/ MySQL]';
- }
-
- /**
- * @param $options array
- */
- public function setSessionOptions( array $options ) {
- if ( isset( $options['connTimeout'] ) ) {
- $timeout = (int)$options['connTimeout'];
- $this->query( "SET net_read_timeout=$timeout" );
- $this->query( "SET net_write_timeout=$timeout" );
- }
+ protected function mysqlFetchObject( $res ) {
+ return mysql_fetch_object( $res );
}
- public function streamStatementEnd( &$sql, &$newLine ) {
- if ( strtoupper( substr( $newLine, 0, 9 ) ) == 'DELIMITER' ) {
- preg_match( '/^DELIMITER\s+(\S+)/', $newLine, $m );
- $this->delimiter = $m[1];
- $newLine = '';
- }
- return parent::streamStatementEnd( $sql, $newLine );
+ protected function mysqlFetchArray( $res ) {
+ return mysql_fetch_array( $res );
}
- /**
- * Check to see if a named lock is available. This is non-blocking.
- *
- * @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 ) {
- $lockName = $this->addQuotes( $lockName );
- $result = $this->query( "SELECT IS_FREE_LOCK($lockName) AS lockstatus", $method );
- $row = $this->fetchObject( $result );
- return ( $row->lockstatus == 1 );
+ protected function mysqlNumRows( $res ) {
+ return mysql_num_rows( $res );
}
- /**
- * @param $lockName string
- * @param $method string
- * @param $timeout int
- * @return bool
- */
- public function lock( $lockName, $method, $timeout = 5 ) {
- $lockName = $this->addQuotes( $lockName );
- $result = $this->query( "SELECT GET_LOCK($lockName, $timeout) AS lockstatus", $method );
- $row = $this->fetchObject( $result );
-
- if( $row->lockstatus == 1 ) {
- return true;
- } else {
- wfDebug( __METHOD__ . " failed to acquire lock\n" );
- return false;
- }
- }
-
- /**
- * FROM MYSQL DOCS: http://dev.mysql.com/doc/refman/5.0/en/miscellaneous-functions.html#function_release-lock
- * @param $lockName string
- * @param $method string
- * @return bool
- */
- public function unlock( $lockName, $method ) {
- $lockName = $this->addQuotes( $lockName );
- $result = $this->query( "SELECT RELEASE_LOCK($lockName) as lockstatus", $method );
- $row = $this->fetchObject( $result );
- return ( $row->lockstatus == 1 );
- }
-
- /**
- * @param $read array
- * @param $write array
- * @param $method string
- * @param $lowPriority bool
- * @return bool
- */
- public function lockTables( $read, $write, $method, $lowPriority = true ) {
- $items = array();
-
- foreach( $write as $table ) {
- $tbl = $this->tableName( $table ) .
- ( $lowPriority ? ' LOW_PRIORITY' : '' ) .
- ' WRITE';
- $items[] = $tbl;
- }
- foreach( $read as $table ) {
- $items[] = $this->tableName( $table ) . ' READ';
- }
- $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;
- }
-
- /**
- * Get search engine class. All subclasses of this
- * need to implement this if they wish to use searching.
- *
- * @return String
- */
- public function getSearchEngine() {
- return 'SearchMySQL';
- }
-
- /**
- * @param bool $value
- * @return mixed
- */
- public function setBigSelects( $value = true ) {
- if ( $value === 'default' ) {
- if ( $this->mDefaultBigSelects === null ) {
- # Function hasn't been called before so it must already be set to the default
- return;
- } else {
- $value = $this->mDefaultBigSelects;
- }
- } elseif ( $this->mDefaultBigSelects === null ) {
- $this->mDefaultBigSelects = (bool)$this->selectField( false, '@@sql_big_selects' );
- }
- $encValue = $value ? '1' : '0';
- $this->query( "SET sql_big_selects=$encValue", __METHOD__ );
- }
-
- /**
- * DELETE where the condition is a join. MySql uses multi-table deletes.
- * @param $delTable string
- * @param $joinTable string
- * @param $delVar string
- * @param $joinVar string
- * @param $conds array|string
- * @param bool|string $fname bool
- * @throws DBUnexpectedError
- * @return bool|ResultWrapper
- */
- function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = 'DatabaseBase::deleteJoin' ) {
- if ( !$conds ) {
- throw new DBUnexpectedError( $this, 'DatabaseBase::deleteJoin() called with empty $conds' );
- }
-
- $delTable = $this->tableName( $delTable );
- $joinTable = $this->tableName( $joinTable );
- $sql = "DELETE $delTable FROM $delTable, $joinTable WHERE $delVar=$joinVar ";
-
- if ( $conds != '*' ) {
- $sql .= ' AND ' . $this->makeList( $conds, LIST_AND );
- }
-
- return $this->query( $sql, $fname );
- }
-
- /**
- * Determines how long the server has been up
- *
- * @return int
- */
- function getServerUptime() {
- $vars = $this->getMysqlStatus( 'Uptime' );
- return (int)$vars['Uptime'];
- }
-
- /**
- * Determines if the last failure was due to a deadlock
- *
- * @return bool
- */
- function wasDeadlock() {
- return $this->lastErrno() == 1213;
- }
-
- /**
- * Determines if the last failure was due to a lock timeout
- *
- * @return bool
- */
- function wasLockTimeout() {
- return $this->lastErrno() == 1205;
- }
-
- /**
- * Determines if the last query error was something that should be dealt
- * with by pinging the connection and reissuing the query
- *
- * @return bool
- */
- function wasErrorReissuable() {
- return $this->lastErrno() == 2013 || $this->lastErrno() == 2006;
- }
-
- /**
- * Determines if the last failure was due to the database being read-only.
- *
- * @return bool
- */
- function wasReadOnlyError() {
- return $this->lastErrno() == 1223 ||
- ( $this->lastErrno() == 1290 && strpos( $this->lastError(), '--read-only' ) !== false );
- }
-
- /**
- * @param $oldName
- * @param $newName
- * @param $temporary bool
- * @param $fname string
- */
- function duplicateTableStructure( $oldName, $newName, $temporary = false, $fname = 'DatabaseMysql::duplicateTableStructure' ) {
- $tmp = $temporary ? 'TEMPORARY ' : '';
- $newName = $this->addIdentifierQuotes( $newName );
- $oldName = $this->addIdentifierQuotes( $oldName );
- $query = "CREATE $tmp TABLE $newName (LIKE $oldName)";
- $this->query( $query, $fname );
- }
-
- /**
- * List all tables on the database
- *
- * @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' ) {
- $result = $this->query( "SHOW TABLES", $fname);
-
- $endArray = array();
-
- foreach( $result as $table ) {
- $vars = get_object_vars( $table );
- $table = array_pop( $vars );
-
- if( !$prefix || strpos( $table, $prefix ) === 0 ) {
- $endArray[] = $table;
- }
- }
-
- return $endArray;
- }
-
- /**
- * @param $tableName
- * @param $fName string
- * @return bool|ResultWrapper
- */
- public function dropTable( $tableName, $fName = 'DatabaseMysql::dropTable' ) {
- if( !$this->tableExists( $tableName, $fName ) ) {
- return false;
- }
- return $this->query( "DROP TABLE IF EXISTS " . $this->tableName( $tableName ), $fName );
- }
-
- /**
- * @return array
- */
- protected function getDefaultSchemaVars() {
- $vars = parent::getDefaultSchemaVars();
- $vars['wgDBTableOptions'] = str_replace( 'TYPE', 'ENGINE', $GLOBALS['wgDBTableOptions'] );
- $vars['wgDBTableOptions'] = str_replace( 'CHARSET=mysql4', 'CHARSET=binary', $vars['wgDBTableOptions'] );
- return $vars;
- }
-
- /**
- * Get status information from SHOW STATUS in an associative array
- *
- * @param $which string
- * @return array
- */
- function getMysqlStatus( $which = "%" ) {
- $res = $this->query( "SHOW STATUS LIKE '{$which}'" );
- $status = array();
-
- foreach ( $res as $row ) {
- $status[$row->Variable_name] = $row->Value;
- }
-
- return $status;
- }
-
-}
-
-/**
- * Utility class.
- * @ingroup Database
- */
-class MySQLField implements Field {
- private $name, $tablename, $default, $max_length, $nullable,
- $is_pk, $is_unique, $is_multiple, $is_key, $type;
-
- function __construct ( $info ) {
- $this->name = $info->name;
- $this->tablename = $info->table;
- $this->default = $info->def;
- $this->max_length = $info->max_length;
- $this->nullable = !$info->not_null;
- $this->is_pk = $info->primary_key;
- $this->is_unique = $info->unique_key;
- $this->is_multiple = $info->multiple_key;
- $this->is_key = ( $this->is_pk || $this->is_unique || $this->is_multiple );
- $this->type = $info->type;
- }
-
- /**
- * @return string
- */
- function name() {
- return $this->name;
- }
-
- /**
- * @return string
- */
- function tableName() {
- return $this->tableName;
- }
-
- /**
- * @return string
- */
- function type() {
- return $this->type;
+ protected function mysqlNumFields( $res ) {
+ return mysql_num_fields( $res );
}
- /**
- * @return bool
- */
- function isNullable() {
- return $this->nullable;
+ protected function mysqlFetchField( $res, $n ) {
+ return mysql_fetch_field( $res, $n );
}
- function defaultValue() {
- return $this->default;
+ protected function mysqlFieldName( $res, $n ) {
+ return mysql_field_name( $res, $n );
}
- /**
- * @return bool
- */
- function isKey() {
- return $this->is_key;
+ protected function mysqlDataSeek( $res, $row ) {
+ return mysql_data_seek( $res, $row );
}
- /**
- * @return bool
- */
- function isMultipleKey() {
- return $this->is_multiple;
+ protected function mysqlError( $conn = null ) {
+ return ( $conn !== null ) ? mysql_error( $conn ) : mysql_error(); // avoid warning
}
-}
-
-class MySQLMasterPos implements DBMasterPos {
- var $file, $pos;
- function __construct( $file, $pos ) {
- $this->file = $file;
- $this->pos = $pos;
+ protected function mysqlRealEscapeString( $s ) {
+ return mysql_real_escape_string( $s, $this->mConn );
}
- function __toString() {
- return "{$this->file}/{$this->pos}";
+ protected function mysqlPing() {
+ return mysql_ping( $this->mConn );
}
}
diff --git a/includes/db/DatabaseMysqlBase.php b/includes/db/DatabaseMysqlBase.php
new file mode 100644
index 00000000..26c9d247
--- /dev/null
+++ b/includes/db/DatabaseMysqlBase.php
@@ -0,0 +1,1154 @@
+<?php
+/**
+ * This is the MySQL database abstraction layer.
+ *
+ * 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
+ */
+
+/**
+ * Database abstraction object for MySQL.
+ * Defines methods independent on used MySQL extension.
+ *
+ * @ingroup Database
+ * @since 1.22
+ * @see Database
+ */
+abstract class DatabaseMysqlBase extends DatabaseBase {
+ /** @var MysqlMasterPos */
+ protected $lastKnownSlavePos;
+
+ /**
+ * @return string
+ */
+ function getType() {
+ return 'mysql';
+ }
+
+ /**
+ * @param $server string
+ * @param $user string
+ * @param $password string
+ * @param $dbName string
+ * @return bool
+ * @throws DBConnectionError
+ */
+ function open( $server, $user, $password, $dbName ) {
+ global $wgAllDBsAreLocalhost, $wgDBmysql5, $wgSQLMode;
+ wfProfileIn( __METHOD__ );
+
+ # Debugging hack -- fake cluster
+ if ( $wgAllDBsAreLocalhost ) {
+ $realServer = 'localhost';
+ } else {
+ $realServer = $server;
+ }
+ $this->close();
+ $this->mServer = $server;
+ $this->mUser = $user;
+ $this->mPassword = $password;
+ $this->mDBname = $dbName;
+
+ wfProfileIn( "dbconnect-$server" );
+
+ # The kernel's default SYN retransmission period is far too slow for us,
+ # so we use a short timeout plus a manual retry. Retrying means that a small
+ # but finite rate of SYN packet loss won't cause user-visible errors.
+ $this->mConn = false;
+ $this->installErrorHandler();
+ try {
+ $this->mConn = $this->mysqlConnect( $realServer );
+ } catch ( Exception $ex ) {
+ wfProfileOut( "dbconnect-$server" );
+ wfProfileOut( __METHOD__ );
+ throw $ex;
+ }
+ $error = $this->restoreErrorHandler();
+
+ wfProfileOut( "dbconnect-$server" );
+
+ # Always log connection errors
+ if ( !$this->mConn ) {
+ if ( !$error ) {
+ $error = $this->lastError();
+ }
+ wfLogDBError( "Error connecting to {$this->mServer}: $error\n" );
+ wfDebug( "DB connection error\n" .
+ "Server: $server, User: $user, Password: " .
+ substr( $password, 0, 3 ) . "..., error: " . $error . "\n" );
+
+ wfProfileOut( __METHOD__ );
+ return $this->reportConnectionError( $error );
+ }
+
+ if ( $dbName != '' ) {
+ wfSuppressWarnings();
+ $success = $this->selectDB( $dbName );
+ wfRestoreWarnings();
+ if ( !$success ) {
+ wfLogDBError( "Error selecting database $dbName on server {$this->mServer}\n" );
+ wfDebug( "Error selecting database $dbName on server {$this->mServer} " .
+ "from client host " . wfHostname() . "\n" );
+
+ wfProfileOut( __METHOD__ );
+ return $this->reportConnectionError( "Error selecting database $dbName" );
+ }
+ }
+
+ // Tell the server we're communicating with it in UTF-8.
+ // This may engage various charset conversions.
+ if ( $wgDBmysql5 ) {
+ $this->query( 'SET NAMES utf8', __METHOD__ );
+ } else {
+ $this->query( 'SET NAMES binary', __METHOD__ );
+ }
+ // Set SQL mode, default is turning them all off, can be overridden or skipped with null
+ if ( is_string( $wgSQLMode ) ) {
+ $mode = $this->addQuotes( $wgSQLMode );
+ $this->query( "SET sql_mode = $mode", __METHOD__ );
+ }
+
+ $this->mOpened = true;
+ wfProfileOut( __METHOD__ );
+ return true;
+ }
+
+ /**
+ * Open a connection to a MySQL server
+ *
+ * @param $realServer string
+ * @return mixed Raw connection
+ * @throws DBConnectionError
+ */
+ abstract protected function mysqlConnect( $realServer );
+
+ /**
+ * @param $res ResultWrapper
+ * @throws DBUnexpectedError
+ */
+ function freeResult( $res ) {
+ if ( $res instanceof ResultWrapper ) {
+ $res = $res->result;
+ }
+ wfSuppressWarnings();
+ $ok = $this->mysqlFreeResult( $res );
+ wfRestoreWarnings();
+ if ( !$ok ) {
+ throw new DBUnexpectedError( $this, "Unable to free MySQL result" );
+ }
+ }
+
+ /**
+ * Free result memory
+ *
+ * @param $res Raw result
+ * @return bool
+ */
+ abstract protected function mysqlFreeResult( $res );
+
+ /**
+ * @param $res ResultWrapper
+ * @return object|bool
+ * @throws DBUnexpectedError
+ */
+ function fetchObject( $res ) {
+ if ( $res instanceof ResultWrapper ) {
+ $res = $res->result;
+ }
+ wfSuppressWarnings();
+ $row = $this->mysqlFetchObject( $res );
+ wfRestoreWarnings();
+
+ $errno = $this->lastErrno();
+ // 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/en/mysql-fetch-row.html.
+ if ( $errno == 2000 || $errno == 2013 ) {
+ throw new DBUnexpectedError( $this, 'Error in fetchObject(): ' . htmlspecialchars( $this->lastError() ) );
+ }
+ return $row;
+ }
+
+ /**
+ * Fetch a result row as an object
+ *
+ * @param $res Raw result
+ * @return stdClass
+ */
+ abstract protected function mysqlFetchObject( $res );
+
+ /**
+ * @param $res ResultWrapper
+ * @return array|bool
+ * @throws DBUnexpectedError
+ */
+ function fetchRow( $res ) {
+ if ( $res instanceof ResultWrapper ) {
+ $res = $res->result;
+ }
+ wfSuppressWarnings();
+ $row = $this->mysqlFetchArray( $res );
+ wfRestoreWarnings();
+
+ $errno = $this->lastErrno();
+ // 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_array can cause.
+ // 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() ) );
+ }
+ return $row;
+ }
+
+ /**
+ * Fetch a result row as an associative and numeric array
+ *
+ * @param $res Raw result
+ * @return array
+ */
+ abstract protected function mysqlFetchArray( $res );
+
+ /**
+ * @throws DBUnexpectedError
+ * @param $res ResultWrapper
+ * @return int
+ */
+ function numRows( $res ) {
+ if ( $res instanceof ResultWrapper ) {
+ $res = $res->result;
+ }
+ wfSuppressWarnings();
+ $n = $this->mysqlNumRows( $res );
+ wfRestoreWarnings();
+ // 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;
+ }
+
+ /**
+ * Get number of rows in result
+ *
+ * @param $res Raw result
+ * @return int
+ */
+ abstract protected function mysqlNumRows( $res );
+
+ /**
+ * @param $res ResultWrapper
+ * @return int
+ */
+ function numFields( $res ) {
+ if ( $res instanceof ResultWrapper ) {
+ $res = $res->result;
+ }
+ return $this->mysqlNumFields( $res );
+ }
+
+ /**
+ * Get number of fields in result
+ *
+ * @param $res Raw result
+ * @return int
+ */
+ abstract protected function mysqlNumFields( $res );
+
+ /**
+ * @param $res ResultWrapper
+ * @param $n string
+ * @return string
+ */
+ function fieldName( $res, $n ) {
+ if ( $res instanceof ResultWrapper ) {
+ $res = $res->result;
+ }
+ return $this->mysqlFieldName( $res, $n );
+ }
+
+ /**
+ * Get the name of the specified field in a result
+ *
+ * @param $res Raw result
+ * @param $n int
+ * @return string
+ */
+ abstract protected function mysqlFieldName( $res, $n );
+
+ /**
+ * @param $res ResultWrapper
+ * @param $row
+ * @return bool
+ */
+ function dataSeek( $res, $row ) {
+ if ( $res instanceof ResultWrapper ) {
+ $res = $res->result;
+ }
+ return $this->mysqlDataSeek( $res, $row );
+ }
+
+ /**
+ * Move internal result pointer
+ *
+ * @param $res Raw result
+ * @param $row int
+ * @return bool
+ */
+ abstract protected function mysqlDataSeek( $res, $row );
+
+ /**
+ * @return string
+ */
+ function lastError() {
+ if ( $this->mConn ) {
+ # Even if it's non-zero, it can still be invalid
+ wfSuppressWarnings();
+ $error = $this->mysqlError( $this->mConn );
+ if ( !$error ) {
+ $error = $this->mysqlError();
+ }
+ wfRestoreWarnings();
+ } else {
+ $error = $this->mysqlError();
+ }
+ if ( $error ) {
+ $error .= ' (' . $this->mServer . ')';
+ }
+ return $error;
+ }
+
+ /**
+ * Returns the text of the error message from previous MySQL operation
+ *
+ * @param $conn Raw connection
+ * @return string
+ */
+ abstract protected function mysqlError( $conn = null );
+
+ /**
+ * @param $table string
+ * @param $uniqueIndexes
+ * @param $rows array
+ * @param $fname string
+ * @return ResultWrapper
+ */
+ function replace( $table, $uniqueIndexes, $rows, $fname = __METHOD__ ) {
+ return $this->nativeReplace( $table, $rows, $fname );
+ }
+
+ /**
+ * Estimate rows in dataset
+ * Returns estimated count, based on EXPLAIN output
+ * Takes same arguments as Database::select()
+ *
+ * @param $table string|array
+ * @param $vars string|array
+ * @param $conds string|array
+ * @param $fname string
+ * @param $options string|array
+ * @return int
+ */
+ public function estimateRowCount( $table, $vars = '*', $conds = '', $fname = __METHOD__, $options = array() ) {
+ $options['EXPLAIN'] = true;
+ $res = $this->select( $table, $vars, $conds, $fname, $options );
+ if ( $res === false ) {
+ return false;
+ }
+ if ( !$this->numRows( $res ) ) {
+ return 0;
+ }
+
+ $rows = 1;
+ foreach ( $res as $plan ) {
+ $rows *= $plan->rows > 0 ? $plan->rows : 1; // avoid resetting to zero
+ }
+ return $rows;
+ }
+
+ /**
+ * @param $table string
+ * @param $field string
+ * @return bool|MySQLField
+ */
+ function fieldInfo( $table, $field ) {
+ $table = $this->tableName( $table );
+ $res = $this->query( "SELECT * FROM $table LIMIT 1", __METHOD__, true );
+ if ( !$res ) {
+ return false;
+ }
+ $n = $this->mysqlNumFields( $res->result );
+ for ( $i = 0; $i < $n; $i++ ) {
+ $meta = $this->mysqlFetchField( $res->result, $i );
+ if ( $field == $meta->name ) {
+ return new MySQLField( $meta );
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Get column information from a result
+ *
+ * @param $res Raw result
+ * @param $n int
+ * @return stdClass
+ */
+ abstract protected function mysqlFetchField( $res, $n );
+
+ /**
+ * Get information about an index into an object
+ * Returns false if the index does not exist
+ *
+ * @param $table string
+ * @param $index string
+ * @param $fname string
+ * @return bool|array|null False or null on failure
+ */
+ function indexInfo( $table, $index, $fname = __METHOD__ ) {
+ # SHOW INDEX works in MySQL 3.23.58, but SHOW INDEXES does not.
+ # SHOW INDEX should work for 3.x and up:
+ # 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 );
+
+ if ( !$res ) {
+ return null;
+ }
+
+ $result = array();
+
+ foreach ( $res as $row ) {
+ if ( $row->Key_name == $index ) {
+ $result[] = $row;
+ }
+ }
+ return empty( $result ) ? false : $result;
+ }
+
+ /**
+ * @param $s string
+ *
+ * @return string
+ */
+ function strencode( $s ) {
+ $sQuoted = $this->mysqlRealEscapeString( $s );
+
+ if ( $sQuoted === false ) {
+ $this->ping();
+ $sQuoted = $this->mysqlRealEscapeString( $s );
+ }
+ return $sQuoted;
+ }
+
+ /**
+ * MySQL uses `backticks` for identifier quoting instead of the sql standard "double quotes".
+ *
+ * @param $s string
+ *
+ * @return string
+ */
+ public function addIdentifierQuotes( $s ) {
+ // Characters in the range \u0001-\uFFFF are valid in a quoted identifier
+ // Remove NUL bytes and escape backticks by doubling
+ return '`' . str_replace( array( "\0", '`' ), array( '', '``' ), $s ) . '`';
+ }
+
+ /**
+ * @param $name string
+ * @return bool
+ */
+ public function isQuotedIdentifier( $name ) {
+ return strlen( $name ) && $name[0] == '`' && substr( $name, -1, 1 ) == '`';
+ }
+
+ /**
+ * @return bool
+ */
+ function ping() {
+ $ping = $this->mysqlPing();
+ if ( $ping ) {
+ return true;
+ }
+
+ $this->closeConnection();
+ $this->mOpened = false;
+ $this->mConn = false;
+ $this->open( $this->mServer, $this->mUser, $this->mPassword, $this->mDBname );
+ return true;
+ }
+
+ /**
+ * Ping a server connection or reconnect if there is no connection
+ *
+ * @return bool
+ */
+ abstract protected function mysqlPing();
+
+ /**
+ * Returns slave lag.
+ *
+ * This will do a SHOW SLAVE STATUS
+ *
+ * @return int
+ */
+ function getLag() {
+ if ( !is_null( $this->mFakeSlaveLag ) ) {
+ wfDebug( "getLag: fake slave lagged {$this->mFakeSlaveLag} seconds\n" );
+ return $this->mFakeSlaveLag;
+ }
+
+ return $this->getLagFromSlaveStatus();
+ }
+
+ /**
+ * @return bool|int
+ */
+ function getLagFromSlaveStatus() {
+ $res = $this->query( 'SHOW SLAVE STATUS', __METHOD__ );
+ if ( !$res ) {
+ return false;
+ }
+ $row = $res->fetchObject();
+ if ( !$row ) {
+ return false;
+ }
+ if ( strval( $row->Seconds_Behind_Master ) === '' ) {
+ return false;
+ } else {
+ return intval( $row->Seconds_Behind_Master );
+ }
+ }
+
+ /**
+ * @deprecated in 1.19, use getLagFromSlaveStatus
+ *
+ * @return bool|int
+ */
+ function getLagFromProcesslist() {
+ wfDeprecated( __METHOD__, '1.19' );
+ $res = $this->query( 'SHOW PROCESSLIST', __METHOD__ );
+ if ( !$res ) {
+ return false;
+ }
+ # Find slave SQL thread
+ foreach ( $res as $row ) {
+ /* This should work for most situations - when default db
+ * for thread is not specified, it had no events executed,
+ * and therefore it doesn't know yet how lagged it is.
+ *
+ * Relay log I/O thread does not select databases.
+ */
+ if ( $row->User == 'system user' &&
+ $row->State != 'Waiting for master to send event' &&
+ $row->State != 'Connecting to master' &&
+ $row->State != 'Queueing master event to the relay log' &&
+ $row->State != 'Waiting for master update' &&
+ $row->State != 'Requesting binlog dump' &&
+ $row->State != 'Waiting to reconnect after a failed master event read' &&
+ $row->State != 'Reconnecting after a failed master event read' &&
+ $row->State != 'Registering slave on master'
+ ) {
+ # This is it, return the time (except -ve)
+ if ( $row->Time > 0x7fffffff ) {
+ return false;
+ } else {
+ return $row->Time;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Wait for the slave to catch up to a given master position.
+ * @TODO: return values for this and base class are rubbish
+ *
+ * @param $pos DBMasterPos object
+ * @param $timeout Integer: the maximum number of seconds to wait for synchronisation
+ * @return bool|string
+ */
+ function masterPosWait( DBMasterPos $pos, $timeout ) {
+ if ( $this->lastKnownSlavePos && $this->lastKnownSlavePos->hasReached( $pos ) ) {
+ return '0'; // http://dev.mysql.com/doc/refman/5.0/en/miscellaneous-functions.html
+ }
+
+ wfProfileIn( __METHOD__ );
+ # Commit any open transactions
+ $this->commit( __METHOD__, 'flush' );
+
+ if ( !is_null( $this->mFakeSlaveLag ) ) {
+ $status = parent::masterPosWait( $pos, $timeout );
+ wfProfileOut( __METHOD__ );
+ return $status;
+ }
+
+ # Call doQuery() directly, to avoid opening a transaction if DBO_TRX is set
+ $encFile = $this->addQuotes( $pos->file );
+ $encPos = intval( $pos->pos );
+ $sql = "SELECT MASTER_POS_WAIT($encFile, $encPos, $timeout)";
+ $res = $this->doQuery( $sql );
+
+ $status = false;
+ if ( $res && $row = $this->fetchRow( $res ) ) {
+ $status = $row[0]; // can be NULL, -1, or 0+ per the MySQL manual
+ if ( ctype_digit( $status ) ) { // success
+ $this->lastKnownSlavePos = $pos;
+ }
+ }
+
+ wfProfileOut( __METHOD__ );
+ return $status;
+ }
+
+ /**
+ * Get the position of the master from SHOW SLAVE STATUS
+ *
+ * @return MySQLMasterPos|bool
+ */
+ function getSlavePos() {
+ if ( !is_null( $this->mFakeSlaveLag ) ) {
+ return parent::getSlavePos();
+ }
+
+ $res = $this->query( 'SHOW SLAVE STATUS', 'DatabaseBase::getSlavePos' );
+ $row = $this->fetchObject( $res );
+
+ if ( $row ) {
+ $pos = isset( $row->Exec_master_log_pos ) ? $row->Exec_master_log_pos : $row->Exec_Master_Log_Pos;
+ return new MySQLMasterPos( $row->Relay_Master_Log_File, $pos );
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Get the position of the master from SHOW MASTER STATUS
+ *
+ * @return MySQLMasterPos|bool
+ */
+ function getMasterPos() {
+ if ( $this->mFakeMaster ) {
+ return parent::getMasterPos();
+ }
+
+ $res = $this->query( 'SHOW MASTER STATUS', 'DatabaseBase::getMasterPos' );
+ $row = $this->fetchObject( $res );
+
+ if ( $row ) {
+ return new MySQLMasterPos( $row->File, $row->Position );
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * @param $index
+ * @return string
+ */
+ function useIndexClause( $index ) {
+ return "FORCE INDEX (" . $this->indexName( $index ) . ")";
+ }
+
+ /**
+ * @return string
+ */
+ function lowPriorityOption() {
+ return 'LOW_PRIORITY';
+ }
+
+ /**
+ * @return string
+ */
+ public function getSoftwareLink() {
+ return '[http://www.mysql.com/ MySQL]';
+ }
+
+ /**
+ * @param $options array
+ */
+ public function setSessionOptions( array $options ) {
+ if ( isset( $options['connTimeout'] ) ) {
+ $timeout = (int)$options['connTimeout'];
+ $this->query( "SET net_read_timeout=$timeout" );
+ $this->query( "SET net_write_timeout=$timeout" );
+ }
+ }
+
+ public function streamStatementEnd( &$sql, &$newLine ) {
+ if ( strtoupper( substr( $newLine, 0, 9 ) ) == 'DELIMITER' ) {
+ preg_match( '/^DELIMITER\s+(\S+)/', $newLine, $m );
+ $this->delimiter = $m[1];
+ $newLine = '';
+ }
+ return parent::streamStatementEnd( $sql, $newLine );
+ }
+
+ /**
+ * Check to see if a named lock is available. This is non-blocking.
+ *
+ * @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 ) {
+ $lockName = $this->addQuotes( $lockName );
+ $result = $this->query( "SELECT IS_FREE_LOCK($lockName) AS lockstatus", $method );
+ $row = $this->fetchObject( $result );
+ return ( $row->lockstatus == 1 );
+ }
+
+ /**
+ * @param $lockName string
+ * @param $method string
+ * @param $timeout int
+ * @return bool
+ */
+ public function lock( $lockName, $method, $timeout = 5 ) {
+ $lockName = $this->addQuotes( $lockName );
+ $result = $this->query( "SELECT GET_LOCK($lockName, $timeout) AS lockstatus", $method );
+ $row = $this->fetchObject( $result );
+
+ if ( $row->lockstatus == 1 ) {
+ return true;
+ } else {
+ wfDebug( __METHOD__ . " failed to acquire lock\n" );
+ return false;
+ }
+ }
+
+ /**
+ * FROM MYSQL DOCS: http://dev.mysql.com/doc/refman/5.0/en/miscellaneous-functions.html#function_release-lock
+ * @param $lockName string
+ * @param $method string
+ * @return bool
+ */
+ public function unlock( $lockName, $method ) {
+ $lockName = $this->addQuotes( $lockName );
+ $result = $this->query( "SELECT RELEASE_LOCK($lockName) as lockstatus", $method );
+ $row = $this->fetchObject( $result );
+ return ( $row->lockstatus == 1 );
+ }
+
+ /**
+ * @param $read array
+ * @param $write array
+ * @param $method string
+ * @param $lowPriority bool
+ * @return bool
+ */
+ public function lockTables( $read, $write, $method, $lowPriority = true ) {
+ $items = array();
+
+ foreach ( $write as $table ) {
+ $tbl = $this->tableName( $table ) .
+ ( $lowPriority ? ' LOW_PRIORITY' : '' ) .
+ ' WRITE';
+ $items[] = $tbl;
+ }
+ foreach ( $read as $table ) {
+ $items[] = $this->tableName( $table ) . ' READ';
+ }
+ $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;
+ }
+
+ /**
+ * Get search engine class. All subclasses of this
+ * need to implement this if they wish to use searching.
+ *
+ * @return String
+ */
+ public function getSearchEngine() {
+ return 'SearchMySQL';
+ }
+
+ /**
+ * @param bool $value
+ * @return mixed
+ */
+ public function setBigSelects( $value = true ) {
+ if ( $value === 'default' ) {
+ if ( $this->mDefaultBigSelects === null ) {
+ # Function hasn't been called before so it must already be set to the default
+ return;
+ } else {
+ $value = $this->mDefaultBigSelects;
+ }
+ } elseif ( $this->mDefaultBigSelects === null ) {
+ $this->mDefaultBigSelects = (bool)$this->selectField( false, '@@sql_big_selects' );
+ }
+ $encValue = $value ? '1' : '0';
+ $this->query( "SET sql_big_selects=$encValue", __METHOD__ );
+ }
+
+ /**
+ * DELETE where the condition is a join. MySql uses multi-table deletes.
+ * @param $delTable string
+ * @param $joinTable string
+ * @param $delVar string
+ * @param $joinVar string
+ * @param $conds array|string
+ * @param bool|string $fname bool
+ * @throws DBUnexpectedError
+ * @return bool|ResultWrapper
+ */
+ function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = __METHOD__ ) {
+ if ( !$conds ) {
+ throw new DBUnexpectedError( $this, 'DatabaseBase::deleteJoin() called with empty $conds' );
+ }
+
+ $delTable = $this->tableName( $delTable );
+ $joinTable = $this->tableName( $joinTable );
+ $sql = "DELETE $delTable FROM $delTable, $joinTable WHERE $delVar=$joinVar ";
+
+ if ( $conds != '*' ) {
+ $sql .= ' AND ' . $this->makeList( $conds, LIST_AND );
+ }
+
+ return $this->query( $sql, $fname );
+ }
+
+ /**
+ * @param string $table
+ * @param array $rows
+ * @param array $uniqueIndexes
+ * @param array $set
+ * @param string $fname
+ * @param array $options
+ * @return bool
+ */
+ public function upsert(
+ $table, array $rows, array $uniqueIndexes, array $set, $fname = __METHOD__
+ ) {
+ if ( !count( $rows ) ) {
+ return true; // nothing to do
+ }
+ $rows = is_array( reset( $rows ) ) ? $rows : array( $rows );
+
+ $table = $this->tableName( $table );
+ $columns = array_keys( $rows[0] );
+
+ $sql = "INSERT INTO $table (" . implode( ',', $columns ) . ') VALUES ';
+ $rowTuples = array();
+ foreach ( $rows as $row ) {
+ $rowTuples[] = '(' . $this->makeList( $row ) . ')';
+ }
+ $sql .= implode( ',', $rowTuples );
+ $sql .= " ON DUPLICATE KEY UPDATE " . $this->makeList( $set, LIST_SET );
+
+ return (bool)$this->query( $sql, $fname );
+ }
+
+ /**
+ * Determines how long the server has been up
+ *
+ * @return int
+ */
+ function getServerUptime() {
+ $vars = $this->getMysqlStatus( 'Uptime' );
+ return (int)$vars['Uptime'];
+ }
+
+ /**
+ * Determines if the last failure was due to a deadlock
+ *
+ * @return bool
+ */
+ function wasDeadlock() {
+ return $this->lastErrno() == 1213;
+ }
+
+ /**
+ * Determines if the last failure was due to a lock timeout
+ *
+ * @return bool
+ */
+ function wasLockTimeout() {
+ return $this->lastErrno() == 1205;
+ }
+
+ /**
+ * Determines if the last query error was something that should be dealt
+ * with by pinging the connection and reissuing the query
+ *
+ * @return bool
+ */
+ function wasErrorReissuable() {
+ return $this->lastErrno() == 2013 || $this->lastErrno() == 2006;
+ }
+
+ /**
+ * Determines if the last failure was due to the database being read-only.
+ *
+ * @return bool
+ */
+ function wasReadOnlyError() {
+ return $this->lastErrno() == 1223 ||
+ ( $this->lastErrno() == 1290 && strpos( $this->lastError(), '--read-only' ) !== false );
+ }
+
+ /**
+ * @param $oldName
+ * @param $newName
+ * @param $temporary bool
+ * @param $fname string
+ */
+ function duplicateTableStructure( $oldName, $newName, $temporary = false, $fname = __METHOD__ ) {
+ $tmp = $temporary ? 'TEMPORARY ' : '';
+ $newName = $this->addIdentifierQuotes( $newName );
+ $oldName = $this->addIdentifierQuotes( $oldName );
+ $query = "CREATE $tmp TABLE $newName (LIKE $oldName)";
+ $this->query( $query, $fname );
+ }
+
+ /**
+ * List all tables on the database
+ *
+ * @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 = __METHOD__ ) {
+ $result = $this->query( "SHOW TABLES", $fname );
+
+ $endArray = array();
+
+ foreach ( $result as $table ) {
+ $vars = get_object_vars( $table );
+ $table = array_pop( $vars );
+
+ if ( !$prefix || strpos( $table, $prefix ) === 0 ) {
+ $endArray[] = $table;
+ }
+ }
+
+ return $endArray;
+ }
+
+ /**
+ * @param $tableName
+ * @param $fName string
+ * @return bool|ResultWrapper
+ */
+ public function dropTable( $tableName, $fName = __METHOD__ ) {
+ if ( !$this->tableExists( $tableName, $fName ) ) {
+ return false;
+ }
+ return $this->query( "DROP TABLE IF EXISTS " . $this->tableName( $tableName ), $fName );
+ }
+
+ /**
+ * @return array
+ */
+ protected function getDefaultSchemaVars() {
+ $vars = parent::getDefaultSchemaVars();
+ $vars['wgDBTableOptions'] = str_replace( 'TYPE', 'ENGINE', $GLOBALS['wgDBTableOptions'] );
+ $vars['wgDBTableOptions'] = str_replace( 'CHARSET=mysql4', 'CHARSET=binary', $vars['wgDBTableOptions'] );
+ return $vars;
+ }
+
+ /**
+ * Get status information from SHOW STATUS in an associative array
+ *
+ * @param $which string
+ * @return array
+ */
+ function getMysqlStatus( $which = "%" ) {
+ $res = $this->query( "SHOW STATUS LIKE '{$which}'" );
+ $status = array();
+
+ foreach ( $res as $row ) {
+ $status[$row->Variable_name] = $row->Value;
+ }
+
+ return $status;
+ }
+
+ /**
+ * Lists VIEWs in the database
+ *
+ * @param string $prefix Only show VIEWs with this prefix, eg.
+ * unit_test_, or $wgDBprefix. Default: null, would return all views.
+ * @param string $fname Name of calling function
+ * @return array
+ * @since 1.22
+ */
+ public function listViews( $prefix = null, $fname = __METHOD__ ) {
+
+ if ( !isset( $this->allViews ) ) {
+
+ // The name of the column containing the name of the VIEW
+ $propertyName = 'Tables_in_' . $this->mDBname;
+
+ // Query for the VIEWS
+ $result = $this->query( 'SHOW FULL TABLES WHERE TABLE_TYPE = "VIEW"' );
+ $this->allViews = array();
+ while ( ($row = $this->fetchRow($result)) !== false ) {
+ array_push( $this->allViews, $row[$propertyName] );
+ }
+ }
+
+ if ( is_null($prefix) || $prefix === '' ) {
+ return $this->allViews;
+ }
+
+ $filteredViews = array();
+ foreach ( $this->allViews as $viewName ) {
+ // Does the name of this VIEW start with the table-prefix?
+ if ( strpos( $viewName, $prefix ) === 0 ) {
+ array_push( $filteredViews, $viewName );
+ }
+ }
+ return $filteredViews;
+ }
+
+ /**
+ * Differentiates between a TABLE and a VIEW.
+ *
+ * @param $name string: Name of the TABLE/VIEW to test
+ * @return bool
+ * @since 1.22
+ */
+ public function isView( $name, $prefix = null ) {
+ return in_array( $name, $this->listViews( $prefix ) );
+ }
+
+}
+
+
+
+/**
+ * Utility class.
+ * @ingroup Database
+ */
+class MySQLField implements Field {
+ private $name, $tablename, $default, $max_length, $nullable,
+ $is_pk, $is_unique, $is_multiple, $is_key, $type, $binary;
+
+ function __construct( $info ) {
+ $this->name = $info->name;
+ $this->tablename = $info->table;
+ $this->default = $info->def;
+ $this->max_length = $info->max_length;
+ $this->nullable = !$info->not_null;
+ $this->is_pk = $info->primary_key;
+ $this->is_unique = $info->unique_key;
+ $this->is_multiple = $info->multiple_key;
+ $this->is_key = ( $this->is_pk || $this->is_unique || $this->is_multiple );
+ $this->type = $info->type;
+ $this->binary = isset( $info->binary ) ? $info->binary : false;
+ }
+
+ /**
+ * @return string
+ */
+ function name() {
+ return $this->name;
+ }
+
+ /**
+ * @return string
+ */
+ function tableName() {
+ return $this->tableName;
+ }
+
+ /**
+ * @return string
+ */
+ function type() {
+ return $this->type;
+ }
+
+ /**
+ * @return bool
+ */
+ function isNullable() {
+ return $this->nullable;
+ }
+
+ function defaultValue() {
+ return $this->default;
+ }
+
+ /**
+ * @return bool
+ */
+ function isKey() {
+ return $this->is_key;
+ }
+
+ /**
+ * @return bool
+ */
+ function isMultipleKey() {
+ return $this->is_multiple;
+ }
+
+ function isBinary() {
+ return $this->binary;
+ }
+}
+
+class MySQLMasterPos implements DBMasterPos {
+ var $file, $pos;
+
+ function __construct( $file, $pos ) {
+ $this->file = $file;
+ $this->pos = $pos;
+ }
+
+ function __toString() {
+ // e.g db1034-bin.000976/843431247
+ return "{$this->file}/{$this->pos}";
+ }
+
+ /**
+ * @return array|false (int, int)
+ */
+ protected function getCoordinates() {
+ $m = array();
+ if ( preg_match( '!\.(\d+)/(\d+)$!', (string)$this, $m ) ) {
+ return array( (int)$m[1], (int)$m[2] );
+ }
+ return false;
+ }
+
+ function hasReached( MySQLMasterPos $pos ) {
+ $thisPos = $this->getCoordinates();
+ $thatPos = $pos->getCoordinates();
+ return ( $thisPos && $thatPos && $thisPos >= $thatPos );
+ }
+}
diff --git a/includes/db/DatabaseMysqli.php b/includes/db/DatabaseMysqli.php
new file mode 100644
index 00000000..7761abe9
--- /dev/null
+++ b/includes/db/DatabaseMysqli.php
@@ -0,0 +1,194 @@
+<?php
+/**
+ * This is the MySQLi database abstraction layer.
+ *
+ * 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
+ */
+
+/**
+ * Database abstraction object for PHP extension mysqli.
+ *
+ * @ingroup Database
+ * @since 1.22
+ * @see Database
+ */
+class DatabaseMysqli extends DatabaseMysqlBase {
+
+ /**
+ * @param $sql string
+ * @return resource
+ */
+ protected function doQuery( $sql ) {
+ if ( $this->bufferResults() ) {
+ $ret = $this->mConn->query( $sql );
+ } else {
+ $ret = $this->mConn->query( $sql, MYSQLI_USE_RESULT );
+ }
+ return $ret;
+ }
+
+ protected function mysqlConnect( $realServer ) {
+ # Fail now
+ # Otherwise we get a suppressed fatal error, which is very hard to track down
+ if ( !function_exists( 'mysqli_init' ) ) {
+ throw new DBConnectionError( $this, "MySQLi functions missing,"
+ . " have you compiled PHP with the --with-mysqli option?\n" );
+ }
+
+ $connFlags = 0;
+ if ( $this->mFlags & DBO_SSL ) {
+ $connFlags |= MYSQLI_CLIENT_SSL;
+ }
+ if ( $this->mFlags & DBO_COMPRESS ) {
+ $connFlags |= MYSQLI_CLIENT_COMPRESS;
+ }
+ if ( $this->mFlags & DBO_PERSISTENT ) {
+ $realServer = 'p:' . $realServer;
+ }
+
+ $mysqli = mysqli_init();
+ $numAttempts = 2;
+
+ for ( $i = 0; $i < $numAttempts; $i++ ) {
+ if ( $i > 1 ) {
+ usleep( 1000 );
+ }
+ if ( $mysqli->real_connect( $realServer, $this->mUser,
+ $this->mPassword, $this->mDBname, null, null, $connFlags ) )
+ {
+ return $mysqli;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * @return bool
+ */
+ protected function closeConnection() {
+ return $this->mConn->close();
+ }
+
+ /**
+ * @return int
+ */
+ function insertId() {
+ return $this->mConn->insert_id;
+ }
+
+ /**
+ * @return int
+ */
+ function lastErrno() {
+ if ( $this->mConn ) {
+ return $this->mConn->errno;
+ } else {
+ return mysqli_connect_errno();
+ }
+ }
+
+ /**
+ * @return int
+ */
+ function affectedRows() {
+ return $this->mConn->affected_rows;
+ }
+
+ /**
+ * @param $db
+ * @return bool
+ */
+ function selectDB( $db ) {
+ $this->mDBname = $db;
+ return $this->mConn->select_db( $db );
+ }
+
+ /**
+ * @return string
+ */
+ function getServerVersion() {
+ return $this->mConn->server_info;
+ }
+
+ protected function mysqlFreeResult( $res ) {
+ $res->free_result();
+ return true;
+ }
+
+ protected function mysqlFetchObject( $res ) {
+ $object = $res->fetch_object();
+ if ( $object === null ) {
+ return false;
+ }
+ return $object;
+ }
+
+ protected function mysqlFetchArray( $res ) {
+ $array = $res->fetch_array();
+ if ( $array === null ) {
+ return false;
+ }
+ return $array;
+ }
+
+ protected function mysqlNumRows( $res ) {
+ return $res->num_rows;
+ }
+
+ protected function mysqlNumFields( $res ) {
+ return $res->field_count;
+ }
+
+ protected function mysqlFetchField( $res, $n ) {
+ $field = $res->fetch_field_direct( $n );
+ $field->not_null = $field->flags & MYSQLI_NOT_NULL_FLAG;
+ $field->primary_key = $field->flags & MYSQLI_PRI_KEY_FLAG;
+ $field->unique_key = $field->flags & MYSQLI_UNIQUE_KEY_FLAG;
+ $field->multiple_key = $field->flags & MYSQLI_MULTIPLE_KEY_FLAG;
+ $field->binary = $field->flags & MYSQLI_BINARY_FLAG;
+ return $field;
+ }
+
+ protected function mysqlFieldName( $res, $n ) {
+ $field = $res->fetch_field_direct( $n );
+ return $field->name;
+ }
+
+ protected function mysqlDataSeek( $res, $row ) {
+ return $res->data_seek( $row );
+ }
+
+ protected function mysqlError( $conn = null ) {
+ if ($conn === null) {
+ return mysqli_connect_error();
+ } else {
+ return $conn->error;
+ }
+ }
+
+ protected function mysqlRealEscapeString( $s ) {
+ return $this->mConn->real_escape_string( $s );
+ }
+
+ protected function mysqlPing() {
+ return $this->mConn->ping();
+ }
+
+}
diff --git a/includes/db/DatabaseOracle.php b/includes/db/DatabaseOracle.php
index 75b3550a..fbaa4da5 100644
--- a/includes/db/DatabaseOracle.php
+++ b/includes/db/DatabaseOracle.php
@@ -206,7 +206,7 @@ class DatabaseOracle extends DatabaseBase {
}
function __destruct() {
- if ($this->mOpened) {
+ if ( $this->mOpened ) {
wfSuppressWarnings();
$this->close();
wfRestoreWarnings();
@@ -249,6 +249,7 @@ class DatabaseOracle extends DatabaseBase {
* @return DatabaseBase|null
*/
function open( $server, $user, $password, $dbName ) {
+ global $wgDBOracleDRCP;
if ( !function_exists( 'oci_connect' ) ) {
throw new DBConnectionError( $this, "Oracle functions missing, have you compiled PHP with the --with-oci8 option?\n (Note: if you recently installed PHP, you may need to restart your webserver and database)\n" );
}
@@ -276,9 +277,16 @@ class DatabaseOracle extends DatabaseBase {
return;
}
+ if ( $wgDBOracleDRCP ) {
+ $this->setFlag( DBO_PERSISTENT );
+ }
+
$session_mode = $this->mFlags & DBO_SYSDBA ? OCI_SYSDBA : OCI_DEFAULT;
+
wfSuppressWarnings();
- if ( $this->mFlags & DBO_DEFAULT ) {
+ if ( $this->mFlags & DBO_PERSISTENT ) {
+ $this->mConn = oci_pconnect( $this->mUser, $this->mPassword, $this->mServer, $this->defaultCharset, $session_mode );
+ } elseif ( $this->mFlags & DBO_DEFAULT ) {
$this->mConn = oci_new_connect( $this->mUser, $this->mPassword, $this->mServer, $this->defaultCharset, $session_mode );
} else {
$this->mConn = oci_connect( $this->mUser, $this->mPassword, $this->mServer, $this->defaultCharset, $session_mode );
@@ -324,7 +332,7 @@ class DatabaseOracle extends DatabaseBase {
// handle some oracle specifics
// remove AS column/table/subquery namings
- if( !$this->getFlag( DBO_DDLMODE ) ) {
+ if ( !$this->getFlag( DBO_DDLMODE ) ) {
$sql = preg_replace( '/ as /i', ' ', $sql );
}
@@ -333,7 +341,7 @@ class DatabaseOracle extends DatabaseBase {
$union_unique = ( preg_match( '/\/\* UNION_UNIQUE \*\/ /', $sql ) != 0 );
// EXPLAIN syntax in Oracle is EXPLAIN PLAN FOR and it return nothing
// you have to select data from plan table after explain
- $explain_id = date( 'dmYHis' );
+ $explain_id = MWTimestamp::getLocalInstance()->format( 'dmYHis' );
$sql = preg_replace( '/^EXPLAIN /', 'EXPLAIN PLAN SET STATEMENT_ID = \'' . $explain_id . '\' FOR', $sql, 1, $explain_count );
@@ -456,15 +464,15 @@ class DatabaseOracle extends DatabaseBase {
* If errors are explicitly ignored, returns NULL on failure
* @return bool
*/
- function indexInfo( $table, $index, $fname = 'DatabaseOracle::indexExists' ) {
+ function indexInfo( $table, $index, $fname = __METHOD__ ) {
return false;
}
- function indexUnique( $table, $index, $fname = 'DatabaseOracle::indexUnique' ) {
+ function indexUnique( $table, $index, $fname = __METHOD__ ) {
return false;
}
- function insert( $table, $a, $fname = 'DatabaseOracle::insert', $options = array() ) {
+ function insert( $table, $a, $fname = __METHOD__, $options = array() ) {
if ( !count( $a ) ) {
return true;
}
@@ -493,7 +501,7 @@ class DatabaseOracle extends DatabaseBase {
return $retVal;
}
- private function fieldBindStatement ( $table, $col, &$val, $includeCol = false ) {
+ private function fieldBindStatement( $table, $col, &$val, $includeCol = false ) {
$col_info = $this->fieldInfoMulti( $table, $col );
$col_type = $col_info != false ? $col_info->type() : 'CONSTANT';
@@ -624,7 +632,7 @@ class DatabaseOracle extends DatabaseBase {
oci_free_statement( $stmt );
}
- function insertSelect( $destTable, $srcTable, $varMap, $conds, $fname = 'DatabaseOracle::insertSelect',
+ function insertSelect( $destTable, $srcTable, $varMap, $conds, $fname = __METHOD__,
$insertOptions = array(), $selectOptions = array() )
{
$destTable = $this->tableName( $destTable );
@@ -677,7 +685,7 @@ class DatabaseOracle extends DatabaseBase {
Using uppercase because that's the only way Oracle can handle
quoted tablenames
*/
- switch( $name ) {
+ switch ( $name ) {
case 'user':
$name = 'MWUSER';
break;
@@ -686,12 +694,12 @@ class DatabaseOracle extends DatabaseBase {
break;
}
- return parent::tableName( strtoupper( $name ), $format );
+ return strtoupper( parent::tableName( $name, $format ) );
}
function tableNameInternal( $name ) {
$name = $this->tableName( $name );
- return preg_replace( '/.*\.(.*)/', '$1', $name);
+ return preg_replace( '/.*\.(.*)/', '$1', $name );
}
/**
* Return the next in a sequence, save the value for retrieval via insertId()
@@ -756,14 +764,14 @@ 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() {
return $this->lastErrno() == 'OCI-00060';
}
- function duplicateTableStructure( $oldName, $newName, $temporary = false, $fname = 'DatabaseOracle::duplicateTableStructure' ) {
+ function duplicateTableStructure( $oldName, $newName, $temporary = false, $fname = __METHOD__ ) {
$temporary = $temporary ? 'TRUE' : 'FALSE';
$newName = strtoupper( $newName );
@@ -776,7 +784,7 @@ class DatabaseOracle extends DatabaseBase {
return $this->doQuery( "BEGIN DUPLICATE_TABLE( '$tabName', '$oldPrefix', '$newPrefix', $temporary ); END;" );
}
- function listTables( $prefix = null, $fname = 'DatabaseOracle::listTables' ) {
+ function listTables( $prefix = null, $fname = __METHOD__ ) {
$listWhere = '';
if ( !empty( $prefix ) ) {
$listWhere = ' AND table_name LIKE \'' . strtoupper( $prefix ) . '%\'';
@@ -791,17 +799,18 @@ class DatabaseOracle extends DatabaseBase {
$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'];
+ }
}
return $endArray;
}
- public function dropTable( $tableName, $fName = 'DatabaseOracle::dropTable' ) {
+ public function dropTable( $tableName, $fName = __METHOD__ ) {
$tableName = $this->tableName( $tableName );
- if( !$this->tableExists( $tableName ) ) {
+ if ( !$this->tableExists( $tableName ) ) {
return false;
}
@@ -836,7 +845,7 @@ class DatabaseOracle extends DatabaseBase {
/**
* @return string wikitext of a link to the server software's web site
*/
- public static function getSoftwareLink() {
+ public function getSoftwareLink() {
return '[http://www.oracle.com/ Oracle]';
}
@@ -856,7 +865,7 @@ class DatabaseOracle extends DatabaseBase {
* Query whether a given index exists
* @return bool
*/
- function indexExists( $table, $index, $fname = 'DatabaseOracle::indexExists' ) {
+ function indexExists( $table, $index, $fname = __METHOD__ ) {
$table = $this->tableName( $table );
$table = strtoupper( $this->removeIdentifierQuotes( $table ) );
$index = strtoupper( $index );
@@ -874,7 +883,7 @@ class DatabaseOracle extends DatabaseBase {
/**
* Query whether a given table exists (in the given schema, or the default mw one if not given)
- * @return int
+ * @return bool
*/
function tableExists( $table, $fname = __METHOD__ ) {
$table = $this->tableName( $table );
@@ -882,13 +891,14 @@ class DatabaseOracle extends DatabaseBase {
$owner = $this->addQuotes( strtoupper( $this->mDBname ) );
$SQL = "SELECT 1 FROM all_tables WHERE owner=$owner AND table_name=$table";
$res = $this->doQuery( $SQL );
- if ( $res ) {
- $count = $res->numRows();
- $res->free();
+ if ( $res && $res->numRows() > 0 ) {
+ $exists = true;
} else {
- $count = 0;
+ $exists = false;
}
- return $count;
+
+ $res->free();
+ return $exists;
}
/**
@@ -906,7 +916,7 @@ class DatabaseOracle extends DatabaseBase {
if ( is_array( $table ) ) {
$table = array_map( array( &$this, 'tableNameInternal' ), $table );
$tableWhere = 'IN (';
- foreach( $table as &$singleTable ) {
+ foreach ( $table as &$singleTable ) {
$singleTable = $this->removeIdentifierQuotes( $singleTable );
if ( isset( $this->mFieldInfoCache["$singleTable.$field"] ) ) {
return $this->mFieldInfoCache["$singleTable.$field"];
@@ -919,7 +929,7 @@ class DatabaseOracle extends DatabaseBase {
if ( isset( $this->mFieldInfoCache["$table.$field"] ) ) {
return $this->mFieldInfoCache["$table.$field"];
}
- $tableWhere = '= \''.$table.'\'';
+ $tableWhere = '= \'' . $table . '\'';
}
$fieldInfoStmt = oci_parse( $this->mConn, 'SELECT * FROM wiki_field_info_full WHERE table_name ' . $tableWhere . ' and column_name = \'' . $field . '\'' );
@@ -931,7 +941,7 @@ class DatabaseOracle extends DatabaseBase {
$res = new ORAResult( $this, $fieldInfoStmt );
if ( $res->numRows() == 0 ) {
if ( is_array( $table ) ) {
- foreach( $table as &$singleTable ) {
+ foreach ( $table as &$singleTable ) {
$this->mFieldInfoCache["$singleTable.$field"] = false;
}
} else {
@@ -960,12 +970,12 @@ class DatabaseOracle extends DatabaseBase {
return $this->fieldInfoMulti( $table, $field );
}
- protected function doBegin( $fname = 'DatabaseOracle::begin' ) {
+ protected function doBegin( $fname = __METHOD__ ) {
$this->mTrxLevel = 1;
$this->doQuery( 'SET CONSTRAINTS ALL DEFERRED' );
}
- protected function doCommit( $fname = 'DatabaseOracle::commit' ) {
+ protected function doCommit( $fname = __METHOD__ ) {
if ( $this->mTrxLevel ) {
$ret = oci_commit( $this->mConn );
if ( !$ret ) {
@@ -976,7 +986,7 @@ class DatabaseOracle extends DatabaseBase {
}
}
- protected function doRollback( $fname = 'DatabaseOracle::rollback' ) {
+ protected function doRollback( $fname = __METHOD__ ) {
if ( $this->mTrxLevel ) {
oci_rollback( $this->mConn );
$this->mTrxLevel = 0;
@@ -986,7 +996,7 @@ class DatabaseOracle extends DatabaseBase {
/* defines must comply with ^define\s*([^\s=]*)\s*=\s?'\{\$([^\}]*)\}'; */
function sourceStream( $fp, $lineCallback = false, $resultCallback = false,
- $fname = 'DatabaseOracle::sourceStream', $inputCallback = false ) {
+ $fname = __METHOD__, $inputCallback = false ) {
$cmd = '';
$done = false;
$dollarquote = false;
@@ -1121,16 +1131,16 @@ class DatabaseOracle extends DatabaseBase {
}
}
- private function wrapConditionsForWhere ( $table, $conds, $parentCol = null ) {
+ private function wrapConditionsForWhere( $table, $conds, $parentCol = null ) {
$conds2 = array();
foreach ( $conds as $col => $val ) {
if ( is_array( $val ) ) {
- $conds2[$col] = $this->wrapConditionsForWhere ( $table, $val, $col );
+ $conds2[$col] = $this->wrapConditionsForWhere( $table, $val, $col );
} else {
if ( is_numeric( $col ) && $parentCol != null ) {
- $this->wrapFieldForWhere ( $table, $parentCol, $val );
+ $this->wrapFieldForWhere( $table, $parentCol, $val );
} else {
- $this->wrapFieldForWhere ( $table, $col, $val );
+ $this->wrapFieldForWhere( $table, $col, $val );
}
$conds2[$col] = $val;
}
@@ -1138,7 +1148,7 @@ class DatabaseOracle extends DatabaseBase {
return $conds2;
}
- function selectRow( $table, $vars, $conds, $fname = 'DatabaseOracle::selectRow', $options = array(), $join_conds = array() ) {
+ function selectRow( $table, $vars, $conds, $fname = __METHOD__, $options = array(), $join_conds = array() ) {
if ( is_array( $conds ) ) {
$conds = $this->wrapConditionsForWhere( $table, $conds );
}
@@ -1187,7 +1197,7 @@ class DatabaseOracle extends DatabaseBase {
return array( $startOpts, $useIndex, $preLimitTail, $postLimitTail );
}
- public function delete( $table, $conds, $fname = 'DatabaseOracle::delete' ) {
+ public function delete( $table, $conds, $fname = __METHOD__ ) {
if ( is_array( $conds ) ) {
$conds = $this->wrapConditionsForWhere( $table, $conds );
}
@@ -1210,7 +1220,7 @@ class DatabaseOracle extends DatabaseBase {
return parent::delete( $table, $conds, $fname );
}
- function update( $table, $values, $conds, $fname = 'DatabaseOracle::update', $options = array() ) {
+ function update( $table, $values, $conds, $fname = __METHOD__, $options = array() ) {
global $wgContLang;
$table = $this->tableName( $table );
diff --git a/includes/db/DatabasePostgres.php b/includes/db/DatabasePostgres.php
index f32d7758..e564a162 100644
--- a/includes/db/DatabasePostgres.php
+++ b/includes/db/DatabasePostgres.php
@@ -117,7 +117,7 @@ SQL;
* @since 1.19
*/
function defaultValue() {
- if( $this->has_default ) {
+ if ( $this->has_default ) {
return $this->default;
} else {
return false;
@@ -139,15 +139,15 @@ class PostgresTransactionState {
array(
"desc" => "%s: Connection state changed from %s -> %s\n",
"states" => array(
- PGSQL_CONNECTION_OK => "OK",
- PGSQL_CONNECTION_BAD => "BAD"
+ PGSQL_CONNECTION_OK => "OK",
+ PGSQL_CONNECTION_BAD => "BAD"
)
),
array(
"desc" => "%s: Transaction state changed from %s -> %s\n",
"states" => array(
- PGSQL_TRANSACTION_IDLE => "IDLE",
- PGSQL_TRANSACTION_ACTIVE => "ACTIVE",
+ PGSQL_TRANSACTION_IDLE => "IDLE",
+ PGSQL_TRANSACTION_ACTIVE => "ACTIVE",
PGSQL_TRANSACTION_INTRANS => "TRANS",
PGSQL_TRANSACTION_INERROR => "ERROR",
PGSQL_TRANSACTION_UNKNOWN => "UNKNOWN"
@@ -189,7 +189,7 @@ class PostgresTransactionState {
}
protected function describe_changed( $status, $desc_table ) {
- if( isset( $desc_table[$status] ) ) {
+ if ( isset( $desc_table[$status] ) ) {
return $desc_table[$status];
} else {
return "STATUS " . $status;
@@ -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;
@@ -320,7 +320,7 @@ class DatabasePostgres extends DatabaseBase {
function hasConstraint( $name ) {
$SQL = "SELECT 1 FROM pg_catalog.pg_constraint c, pg_catalog.pg_namespace n WHERE c.connamespace = n.oid AND conname = '" .
- pg_escape_string( $this->mConn, $name ) . "' AND n.nspname = '" . pg_escape_string( $this->mConn, $this->getCoreSchema() ) ."'";
+ pg_escape_string( $this->mConn, $name ) . "' AND n.nspname = '" . pg_escape_string( $this->mConn, $this->getCoreSchema() ) . "'";
$res = $this->doQuery( $SQL );
return $this->numRows( $res );
}
@@ -438,7 +438,7 @@ class DatabasePostgres extends DatabaseBase {
$sql = mb_convert_encoding( $sql, 'UTF-8' );
}
$this->mTransactionState->check();
- if( pg_send_query( $this->mConn, $sql ) === false ) {
+ if ( pg_send_query( $this->mConn, $sql ) === false ) {
throw new DBUnexpectedError( $this, "Unable to post new query to PostgreSQL\n" );
}
$this->mLastResult = pg_get_result( $this->mConn );
@@ -450,7 +450,7 @@ class DatabasePostgres extends DatabaseBase {
return $this->mLastResult;
}
- protected function dumpError () {
+ protected function dumpError() {
$diags = array( PGSQL_DIAG_SEVERITY,
PGSQL_DIAG_SQLSTATE,
PGSQL_DIAG_MESSAGE_PRIMARY,
@@ -464,7 +464,7 @@ class DatabasePostgres extends DatabaseBase {
PGSQL_DIAG_SOURCE_LINE,
PGSQL_DIAG_SOURCE_FUNCTION );
foreach ( $diags as $d ) {
- wfDebug( sprintf("PgSQL ERROR(%d): %s\n", $d, pg_result_error_field( $this->mLastResult, $d ) ) );
+ wfDebug( sprintf( "PgSQL ERROR(%d): %s\n", $d, pg_result_error_field( $this->mLastResult, $d ) ) );
}
}
@@ -482,7 +482,7 @@ class DatabasePostgres extends DatabaseBase {
parent::reportQueryError( $error, $errno, $sql, $fname, false );
}
- function queryIgnore( $sql, $fname = 'DatabasePostgres::queryIgnore' ) {
+ function queryIgnore( $sql, $fname = __METHOD__ ) {
return $this->query( $sql, $fname, true );
}
@@ -509,7 +509,7 @@ class DatabasePostgres extends DatabaseBase {
# @todo hashar: not sure if the following test really trigger if the object
# fetching failed.
- if( pg_last_error( $this->mConn ) ) {
+ if ( pg_last_error( $this->mConn ) ) {
throw new DBUnexpectedError( $this, 'SQL error: ' . htmlspecialchars( pg_last_error( $this->mConn ) ) );
}
return $row;
@@ -522,7 +522,7 @@ class DatabasePostgres extends DatabaseBase {
wfSuppressWarnings();
$row = pg_fetch_array( $res );
wfRestoreWarnings();
- if( pg_last_error( $this->mConn ) ) {
+ if ( pg_last_error( $this->mConn ) ) {
throw new DBUnexpectedError( $this, 'SQL error: ' . htmlspecialchars( pg_last_error( $this->mConn ) ) );
}
return $row;
@@ -535,7 +535,7 @@ class DatabasePostgres extends DatabaseBase {
wfSuppressWarnings();
$n = pg_num_rows( $res );
wfRestoreWarnings();
- if( pg_last_error( $this->mConn ) ) {
+ if ( pg_last_error( $this->mConn ) ) {
throw new DBUnexpectedError( $this, 'SQL error: ' . htmlspecialchars( pg_last_error( $this->mConn ) ) );
}
return $n;
@@ -556,8 +556,10 @@ class DatabasePostgres extends DatabaseBase {
}
/**
- * This must be called after nextSequenceVal
- * @return null
+ * Return the result of the last call to nextSequenceValue();
+ * This must be called after nextSequenceValue().
+ *
+ * @return integer|null
*/
function insertId() {
return $this->mInsertId;
@@ -594,7 +596,7 @@ class DatabasePostgres extends DatabaseBase {
// Forced result for simulated queries
return $this->mAffectedRows;
}
- if( empty( $this->mLastResult ) ) {
+ if ( empty( $this->mLastResult ) ) {
return 0;
}
return pg_affected_rows( $this->mLastResult );
@@ -608,14 +610,14 @@ 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 = __METHOD__, $options = array() ) {
$options['EXPLAIN'] = true;
$res = $this->select( $table, $vars, $conds, $fname, $options );
$rows = -1;
if ( $res ) {
$row = $this->fetchRow( $res );
$count = array();
- if( preg_match( '/rows=(\d+)/', $row[0], $count ) ) {
+ if ( preg_match( '/rows=(\d+)/', $row[0], $count ) ) {
$rows = $count[1];
}
}
@@ -627,7 +629,7 @@ class DatabasePostgres extends DatabaseBase {
* If errors are explicitly ignored, returns NULL on failure
* @return bool|null
*/
- function indexInfo( $table, $index, $fname = 'DatabasePostgres::indexInfo' ) {
+ function indexInfo( $table, $index, $fname = __METHOD__ ) {
$sql = "SELECT indexname FROM pg_indexes WHERE tablename='$table'";
$res = $this->query( $sql, $fname );
if ( !$res ) {
@@ -647,9 +649,10 @@ class DatabasePostgres extends DatabaseBase {
* @since 1.19
* @return Array
*/
- function indexAttributes ( $index, $schema = false ) {
- if ( $schema === false )
+ function indexAttributes( $index, $schema = false ) {
+ if ( $schema === false ) {
$schema = $this->getCoreSchema();
+ }
/*
* A subquery would be not needed if we didn't care about the order
* of attributes, but we do
@@ -702,8 +705,8 @@ __INDEXATTR__;
return $a;
}
- function indexUnique( $table, $index, $fname = 'DatabasePostgres::indexUnique' ) {
- $sql = "SELECT indexname FROM pg_indexes WHERE tablename='{$table}'".
+ function indexUnique( $table, $index, $fname = __METHOD__ ) {
+ $sql = "SELECT indexname FROM pg_indexes WHERE tablename='{$table}'" .
" AND indexdef LIKE 'CREATE UNIQUE%(" .
$this->strencode( $this->indexName( $index ) ) .
")'";
@@ -730,7 +733,7 @@ __INDEXATTR__;
*
* @return bool Success of insert operation. IGNORE always returns true.
*/
- function insert( $table, $args, $fname = 'DatabasePostgres::insert', $options = array() ) {
+ function insert( $table, $args, $fname = __METHOD__, $options = array() ) {
if ( !count( $args ) ) {
return true;
}
@@ -847,12 +850,12 @@ __INDEXATTR__;
* @todo FIXME: Implement this a little better (seperate select/insert)?
* @return bool
*/
- function insertSelect( $destTable, $srcTable, $varMap, $conds, $fname = 'DatabasePostgres::insertSelect',
+ function insertSelect( $destTable, $srcTable, $varMap, $conds, $fname = __METHOD__,
$insertOptions = array(), $selectOptions = array() )
{
$destTable = $this->tableName( $destTable );
- if( !is_array( $insertOptions ) ) {
+ if ( !is_array( $insertOptions ) ) {
$insertOptions = array( $insertOptions );
}
@@ -868,11 +871,11 @@ __INDEXATTR__;
$savepoint->savepoint();
}
- if( !is_array( $selectOptions ) ) {
+ if ( !is_array( $selectOptions ) ) {
$selectOptions = array( $selectOptions );
}
list( $startOpts, $useIndex, $tailOpts ) = $this->makeSelectOptions( $selectOptions );
- if( is_array( $srcTable ) ) {
+ if ( is_array( $srcTable ) ) {
$srcTable = implode( ',', array_map( array( &$this, 'tableName' ), $srcTable ) );
} else {
$srcTable = $this->tableName( $srcTable );
@@ -889,9 +892,9 @@ __INDEXATTR__;
$sql .= " $tailOpts";
$res = (bool)$this->query( $sql, $fname, $savepoint );
- if( $savepoint ) {
+ if ( $savepoint ) {
$bar = pg_last_error();
- if( $bar != false ) {
+ if ( $bar != false ) {
$savepoint->rollback();
} else {
$savepoint->release();
@@ -912,7 +915,7 @@ __INDEXATTR__;
function tableName( $name, $format = 'quoted' ) {
# Replace reserved words with better ones
- switch( $name ) {
+ switch ( $name ) {
case 'user':
return $this->realTableName( 'mwuser', $format );
case 'text':
@@ -958,7 +961,7 @@ __INDEXATTR__;
FROM pg_class c, pg_attribute a, pg_type t
WHERE relname='$table' AND a.attrelid=c.oid AND
a.atttypid=t.oid and a.attname='$field'";
- $res =$this->query( $sql );
+ $res = $this->query( $sql );
$row = $this->fetchObject( $res );
if ( $row->ftype == 'varchar' ) {
$size = $row->size - 4;
@@ -976,21 +979,21 @@ __INDEXATTR__;
return $this->lastErrno() == '40P01';
}
- function duplicateTableStructure( $oldName, $newName, $temporary = false, $fname = 'DatabasePostgres::duplicateTableStructure' ) {
+ function duplicateTableStructure( $oldName, $newName, $temporary = false, $fname = __METHOD__ ) {
$newName = $this->addIdentifierQuotes( $newName );
$oldName = $this->addIdentifierQuotes( $oldName );
return $this->query( 'CREATE ' . ( $temporary ? 'TEMPORARY ' : '' ) . " TABLE $newName (LIKE $oldName INCLUDING DEFAULTS)", $fname );
}
- function listTables( $prefix = null, $fname = 'DatabasePostgres::listTables' ) {
+ function listTables( $prefix = null, $fname = __METHOD__ ) {
$eschema = $this->addQuotes( $this->getCoreSchema() );
$result = $this->query( "SELECT tablename FROM pg_tables WHERE schemaname = $eschema", $fname );
$endArray = array();
- foreach( $result as $table ) {
+ foreach ( $result as $table ) {
$vars = get_object_vars( $table );
$table = array_pop( $vars );
- if( !$prefix || strpos( $table, $prefix ) === 0 ) {
+ if ( !$prefix || strpos( $table, $prefix ) === 0 ) {
$endArray[] = $table;
}
}
@@ -1021,26 +1024,26 @@ __INDEXATTR__;
* @return string
*/
function pg_array_parse( $text, &$output, $limit = false, $offset = 1 ) {
- if( false === $limit ) {
- $limit = strlen( $text )-1;
+ if ( false === $limit ) {
+ $limit = strlen( $text ) - 1;
$output = array();
}
- if( '{}' == $text ) {
+ if ( '{}' == $text ) {
return $output;
}
do {
- if ( '{' != $text{$offset} ) {
+ if ( '{' != $text[$offset] ) {
preg_match( "/(\\{?\"([^\"\\\\]|\\\\.)*\"|[^,{}]+)+([,}]+)/",
$text, $match, 0, $offset );
$offset += strlen( $match[0] );
- $output[] = ( '"' != $match[1]{0}
+ $output[] = ( '"' != $match[1][0]
? $match[1]
: stripcslashes( substr( $match[1], 1, -1 ) ) );
if ( '},' == $match[3] ) {
return $output;
}
} else {
- $offset = $this->pg_array_parse( $text, $output, $limit, $offset+1 );
+ $offset = $this->pg_array_parse( $text, $output, $limit, $offset + 1 );
}
} while ( $limit > $offset );
return $output;
@@ -1056,7 +1059,7 @@ __INDEXATTR__;
/**
* @return string wikitext of a link to the server software's web site
*/
- public static function getSoftwareLink() {
+ public function getSoftwareLink() {
return '[http://www.postgresql.org/ PostgreSQL]';
}
@@ -1406,7 +1409,8 @@ SQL;
return array( $startOpts, $useIndex, $preLimitTail, $postLimitTail );
}
- function setFakeMaster( $enabled = true ) {}
+ function setFakeMaster( $enabled = true ) {
+ }
function getDBname() {
return $this->mDBname;
@@ -1473,7 +1477,7 @@ SQL;
sleep( 1 );
}
}
- wfDebug( __METHOD__." failed to acquire lock\n" );
+ wfDebug( __METHOD__ . " failed to acquire lock\n" );
return false;
}
diff --git a/includes/db/DatabaseSqlite.php b/includes/db/DatabaseSqlite.php
index 0789e1b1..4a51226f 100644
--- a/includes/db/DatabaseSqlite.php
+++ b/includes/db/DatabaseSqlite.php
@@ -52,9 +52,9 @@ class DatabaseSqlite extends DatabaseBase {
$this->mName = $dbName;
parent::__construct( $server, $user, $password, $dbName, $flags );
// parent doesn't open when $user is false, but we can work with $dbName
- if( $dbName ) {
+ if ( $dbName && !$this->isOpen() ) {
global $wgSharedDB;
- if( $this->open( $server, $user, $password, $dbName ) && $wgSharedDB ) {
+ if ( $this->open( $server, $user, $password, $dbName ) && $wgSharedDB ) {
$this->attachDatabase( $wgSharedDB );
}
}
@@ -68,7 +68,7 @@ class DatabaseSqlite extends DatabaseBase {
}
/**
- * @todo: check if it should be true like parent class
+ * @todo Check if it should be true like parent class
*
* @return bool
*/
@@ -90,6 +90,7 @@ class DatabaseSqlite extends DatabaseBase {
function open( $server, $user, $pass, $dbName ) {
global $wgSQLiteDataDir;
+ $this->close();
$fileName = self::generateFileName( $wgSQLiteDataDir, $dbName );
if ( !is_readable( $fileName ) ) {
$this->mConn = false;
@@ -200,7 +201,7 @@ class DatabaseSqlite extends DatabaseBase {
*
* @return ResultWrapper
*/
- function attachDatabase( $name, $file = false, $fname = 'DatabaseSqlite::attachDatabase' ) {
+ function attachDatabase( $name, $file = false, $fname = __METHOD__ ) {
global $wgSQLiteDataDir;
if ( !$file ) {
$file = self::generateFileName( $wgSQLiteDataDir, $name );
@@ -420,7 +421,7 @@ class DatabaseSqlite extends DatabaseBase {
*
* @return array
*/
- function indexInfo( $table, $index, $fname = 'DatabaseSqlite::indexExists' ) {
+ function indexInfo( $table, $index, $fname = __METHOD__ ) {
$sql = 'PRAGMA index_info(' . $this->addQuotes( $this->indexName( $index ) ) . ')';
$res = $this->query( $sql, $fname );
if ( !$res ) {
@@ -442,7 +443,7 @@ class DatabaseSqlite extends DatabaseBase {
* @param $fname string
* @return bool|null
*/
- function indexUnique( $table, $index, $fname = 'DatabaseSqlite::indexUnique' ) {
+ function indexUnique( $table, $index, $fname = __METHOD__ ) {
$row = $this->selectRow( 'sqlite_master', '*',
array(
'type' => 'index',
@@ -471,7 +472,7 @@ class DatabaseSqlite extends DatabaseBase {
*/
function makeSelectOptions( $options ) {
foreach ( $options as $k => $v ) {
- if ( is_numeric( $k ) && ($v == 'FOR UPDATE' || $v == 'LOCK IN SHARE MODE') ) {
+ if ( is_numeric( $k ) && ( $v == 'FOR UPDATE' || $v == 'LOCK IN SHARE MODE' ) ) {
$options[$k] = '';
}
}
@@ -514,7 +515,7 @@ class DatabaseSqlite extends DatabaseBase {
* Based on generic method (parent) with some prior SQLite-sepcific adjustments
* @return bool
*/
- function insert( $table, $a, $fname = 'DatabaseSqlite::insert', $options = array() ) {
+ function insert( $table, $a, $fname = __METHOD__, $options = array() ) {
if ( !count( $a ) ) {
return true;
}
@@ -541,8 +542,10 @@ class DatabaseSqlite extends DatabaseBase {
* @param $fname string
* @return bool|ResultWrapper
*/
- function replace( $table, $uniqueIndexes, $rows, $fname = 'DatabaseSqlite::replace' ) {
- if ( !count( $rows ) ) return true;
+ function replace( $table, $uniqueIndexes, $rows, $fname = __METHOD__ ) {
+ if ( !count( $rows ) ) {
+ return true;
+ }
# SQLite can't handle multi-row replaces, so divide up into multiple single-row queries
if ( isset( $rows[0] ) && is_array( $rows[0] ) ) {
@@ -610,7 +613,7 @@ class DatabaseSqlite extends DatabaseBase {
/**
* @return string wikitext of a link to the server software's web site
*/
- public static function getSoftwareLink() {
+ public function getSoftwareLink() {
return "[http://sqlite.org/ SQLite]";
}
@@ -653,7 +656,11 @@ class DatabaseSqlite extends DatabaseBase {
if ( $this->mTrxLevel == 1 ) {
$this->commit( __METHOD__ );
}
- $this->mConn->beginTransaction();
+ try {
+ $this->mConn->beginTransaction();
+ } catch ( PDOException $e ) {
+ throw new DBUnexpectedError( $this, 'Error in BEGIN query: ' . $e->getMessage() );
+ }
$this->mTrxLevel = 1;
}
@@ -661,7 +668,11 @@ class DatabaseSqlite extends DatabaseBase {
if ( $this->mTrxLevel == 0 ) {
return;
}
- $this->mConn->commit();
+ try {
+ $this->mConn->commit();
+ } catch ( PDOException $e ) {
+ throw new DBUnexpectedError( $this, 'Error in COMMIT query: ' . $e->getMessage() );
+ }
$this->mTrxLevel = 0;
}
@@ -707,7 +718,9 @@ class DatabaseSqlite extends DatabaseBase {
function addQuotes( $s ) {
if ( $s instanceof Blob ) {
return "x'" . bin2hex( $s->fetch() ) . "'";
- } else if ( strpos( $s, "\0" ) !== false ) {
+ } elseif ( is_bool( $s ) ) {
+ return (int)$s;
+ } elseif ( 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:
@@ -813,7 +826,7 @@ class DatabaseSqlite extends DatabaseBase {
* @param $fname string
* @return bool|ResultWrapper
*/
- function duplicateTableStructure( $oldName, $newName, $temporary = false, $fname = 'DatabaseSqlite::duplicateTableStructure' ) {
+ function duplicateTableStructure( $oldName, $newName, $temporary = false, $fname = __METHOD__ ) {
$res = $this->query( "SELECT sql FROM sqlite_master WHERE tbl_name=" . $this->addQuotes( $oldName ) . " AND type='table'", $fname );
$obj = $this->fetchObject( $res );
if ( !$obj ) {
@@ -839,7 +852,7 @@ class DatabaseSqlite extends DatabaseBase {
*
* @return array
*/
- function listTables( $prefix = null, $fname = 'DatabaseSqlite::listTables' ) {
+ function listTables( $prefix = null, $fname = __METHOD__ ) {
$result = $this->select(
'sqlite_master',
'name',
@@ -848,11 +861,11 @@ class DatabaseSqlite extends DatabaseBase {
$endArray = array();
- foreach( $result as $table ) {
+ foreach ( $result as $table ) {
$vars = get_object_vars( $table );
$table = array_pop( $vars );
- if( !$prefix || strpos( $table, $prefix ) === 0 ) {
+ if ( !$prefix || strpos( $table, $prefix ) === 0 ) {
if ( strpos( $table, 'sqlite_' ) !== 0 ) {
$endArray[] = $table;
}
diff --git a/includes/db/DatabaseUtility.php b/includes/db/DatabaseUtility.php
index 9a1c8bde..de58bab6 100644
--- a/includes/db/DatabaseUtility.php
+++ b/includes/db/DatabaseUtility.php
@@ -253,7 +253,8 @@ class FakeResultWrapper extends ResultWrapper {
$this->pos = $row;
}
- function free() {}
+ function free() {
+ }
// Callers want to be able to access fields with $this->fieldName
function fetchObject() {
diff --git a/includes/db/IORMRow.php b/includes/db/IORMRow.php
index 6bc0cdd2..39411791 100644
--- a/includes/db/IORMRow.php
+++ b/includes/db/IORMRow.php
@@ -34,20 +34,10 @@
interface IORMRow {
/**
- * Constructor.
- *
- * @since 1.20
- *
- * @param IORMTable $table
- * @param array|null $fields
- * @param boolean $loadDefaults
- */
- public function __construct( IORMTable $table, $fields = null, $loadDefaults = false );
-
- /**
* Load the specified fields from the database.
*
* @since 1.20
+ * @deprecated since 1.22
*
* @param array|null $fields
* @param boolean $override
@@ -74,8 +64,9 @@ interface IORMRow {
* Gets the value of a field but first loads it if not done so already.
*
* @since 1.20
+ * @deprecated since 1.22
*
- * @param string$name
+ * @param string $name
*
* @return mixed
*/
@@ -155,6 +146,7 @@ interface IORMRow {
* Load the default values, via getDefaults.
*
* @since 1.20
+ * @deprecated since 1.22
*
* @param boolean $override
*/
@@ -167,6 +159,7 @@ interface IORMRow {
* @since 1.20
*
* @param string|null $functionName
+ * @deprecated since 1.22
*
* @return boolean Success indicator
*/
@@ -176,6 +169,7 @@ interface IORMRow {
* Removes the object from the database.
*
* @since 1.20
+ * @deprecated since 1.22
*
* @return boolean Success indicator
*/
@@ -215,9 +209,9 @@ interface IORMRow {
/**
* Add an amount (can be negative) to the specified field (needs to be numeric).
- * TODO: most off this stuff makes more sense in the table class
*
* @since 1.20
+ * @deprecated since 1.22
*
* @param string $field
* @param integer $amount
@@ -239,6 +233,7 @@ interface IORMRow {
* Computes and updates the values of the summary fields.
*
* @since 1.20
+ * @deprecated since 1.22
*
* @param array|string|null $summaryFields
*/
@@ -248,6 +243,7 @@ interface IORMRow {
* Sets the value for the @see $updateSummaries field.
*
* @since 1.20
+ * @deprecated since 1.22
*
* @param boolean $update
*/
@@ -257,6 +253,7 @@ interface IORMRow {
* Sets the value for the @see $inSummaryMode field.
*
* @since 1.20
+ * @deprecated since 1.22
*
* @param boolean $summaryMode
*/
@@ -266,6 +263,7 @@ interface IORMRow {
* Returns the table this IORMRow is a row in.
*
* @since 1.20
+ * @deprecated since 1.22
*
* @return IORMTable
*/
diff --git a/includes/db/LBFactory.php b/includes/db/LBFactory.php
index d469e867..16c43a00 100644
--- a/includes/db/LBFactory.php
+++ b/includes/db/LBFactory.php
@@ -134,7 +134,8 @@ abstract class LBFactory {
* Prepare all tracked load balancers for shutdown
* STUB
*/
- function shutdown() {}
+ function shutdown() {
+ }
/**
* Call a method of each tracked load balancer
@@ -201,7 +202,7 @@ class LBFactory_Simple extends LBFactory {
$flags |= DBO_COMPRESS;
}
- $servers = array(array(
+ $servers = array( array(
'host' => $wgDBserver,
'user' => $wgDBuser,
'password' => $wgDBpassword,
@@ -256,6 +257,7 @@ class LBFactory_Simple extends LBFactory {
if ( !isset( $this->extLBs[$cluster] ) ) {
$this->extLBs[$cluster] = $this->newExternalLB( $cluster, $wiki );
$this->extLBs[$cluster]->parentInfo( array( 'id' => "ext-$cluster" ) );
+ $this->chronProt->initLB( $this->extLBs[$cluster] );
}
return $this->extLBs[$cluster];
}
@@ -280,6 +282,9 @@ class LBFactory_Simple extends LBFactory {
if ( $this->mainLB ) {
$this->chronProt->shutdownLB( $this->mainLB );
}
+ foreach ( $this->extLBs as $extLB ) {
+ $this->chronProt->shutdownLB( $extLB );
+ }
$this->chronProt->shutdown();
$this->commitMasterChanges();
}
@@ -292,21 +297,27 @@ class LBFactory_Simple extends LBFactory {
* LBFactory::enableBackend() to return to normal behavior
*/
class LBFactory_Fake extends LBFactory {
- function __construct( $conf ) {}
+ function __construct( $conf ) {
+ }
- function newMainLB( $wiki = false) {
+ function newMainLB( $wiki = false ) {
throw new DBAccessError;
}
+
function getMainLB( $wiki = false ) {
throw new DBAccessError;
}
+
function newExternalLB( $cluster, $wiki = false ) {
throw new DBAccessError;
}
+
function &getExternalLB( $cluster, $wiki = false ) {
throw new DBAccessError;
}
- function forEachLB( $callback, $params = array() ) {}
+
+ function forEachLB( $callback, $params = array() ) {
+ }
}
/**
@@ -317,76 +328,3 @@ class DBAccessError extends MWException {
parent::__construct( "Mediawiki tried to access the database via wfGetDB(). This is not allowed." );
}
}
-
-/**
- * Class for ensuring a consistent ordering of events as seen by the user, despite replication.
- * Kind of like Hawking's [[Chronology Protection Agency]].
- */
-class ChronologyProtector {
- var $startupPos;
- var $shutdownPos = array();
-
- /**
- * Initialise a LoadBalancer to give it appropriate chronology protection.
- *
- * @param $lb LoadBalancer
- */
- function initLB( $lb ) {
- if ( $this->startupPos === null ) {
- if ( !empty( $_SESSION[__CLASS__] ) ) {
- $this->startupPos = $_SESSION[__CLASS__];
- }
- }
- if ( !$this->startupPos ) {
- return;
- }
- $masterName = $lb->getServerName( 0 );
-
- 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" );
- $lb->waitFor( $this->startupPos[$masterName] );
- }
- }
-
- /**
- * Notify the ChronologyProtector that the LoadBalancer is about to shut
- * down. Saves replication positions.
- *
- * @param $lb LoadBalancer
- */
- function shutdownLB( $lb ) {
- // Don't start a session, don't bother with non-replicated setups
- if ( strval( session_id() ) == '' || $lb->getServerCount() <= 1 ) {
- return;
- }
- $masterName = $lb->getServerName( 0 );
- if ( isset( $this->shutdownPos[$masterName] ) ) {
- // Already done
- return;
- }
- // Only save the position if writes have been done on the connection
- $db = $lb->getAnyOpenConnection( 0 );
- $info = $lb->parentInfo();
- if ( !$db || !$db->doneWrites() ) {
- wfDebug( __METHOD__ . ": LB {$info['id']}, no writes done\n" );
- return;
- }
- $pos = $db->getMasterPos();
- wfDebug( __METHOD__ . ": LB {$info['id']} has master pos $pos\n" );
- $this->shutdownPos[$masterName] = $pos;
- }
-
- /**
- * Notify the ChronologyProtector that the LBFactory is done calling shutdownLB() for now.
- * May commit chronology data to persistent storage.
- */
- function shutdown() {
- if ( session_id() != '' && count( $this->shutdownPos ) ) {
- 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 2e4963d4..3043946a 100644
--- a/includes/db/LBFactory_Multi.php
+++ b/includes/db/LBFactory_Multi.php
@@ -145,8 +145,8 @@ class LBFactory_Multi extends LBFactory {
$section = $this->getSectionForWiki( $wiki );
if ( !isset( $this->mainLBs[$section] ) ) {
$lb = $this->newMainLB( $wiki, $section );
- $this->chronProt->initLB( $lb );
$lb->parentInfo( array( 'id' => "main-$section" ) );
+ $this->chronProt->initLB( $lb );
$this->mainLBs[$section] = $lb;
}
return $this->mainLBs[$section];
@@ -181,6 +181,7 @@ class LBFactory_Multi extends LBFactory {
if ( !isset( $this->extLBs[$cluster] ) ) {
$this->extLBs[$cluster] = $this->newExternalLB( $cluster, $wiki );
$this->extLBs[$cluster]->parentInfo( array( 'id' => "ext-$cluster" ) );
+ $this->chronProt->initLB( $this->extLBs[$cluster] );
}
return $this->extLBs[$cluster];
}
@@ -296,6 +297,9 @@ class LBFactory_Multi extends LBFactory {
foreach ( $this->mainLBs as $lb ) {
$this->chronProt->shutdownLB( $lb );
}
+ foreach ( $this->extLBs as $extLB ) {
+ $this->chronProt->shutdownLB( $extLB );
+ }
$this->chronProt->shutdown();
$this->commitMasterChanges();
}
diff --git a/includes/db/LoadBalancer.php b/includes/db/LoadBalancer.php
index 1e859278..857109db 100644
--- a/includes/db/LoadBalancer.php
+++ b/includes/db/LoadBalancer.php
@@ -78,7 +78,7 @@ class LoadBalancer {
}
}
- foreach( $params['servers'] as $i => $server ) {
+ foreach ( $params['servers'] as $i => $server ) {
$this->mLoads[$i] = $server['load'];
if ( isset( $server['groupLoads'] ) ) {
foreach ( $server['groupLoads'] as $group => $ratio ) {
@@ -117,7 +117,7 @@ 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()
+ * @deprecated since 1.21, use ArrayUtils::pickRandom()
*
* @param $weights array
*
@@ -167,7 +167,7 @@ class LoadBalancer {
#wfDebugLog( 'connect', var_export( $loads, true ) );
# Return a random representative of the remainder
- return $this->pickRandom( $loads );
+ return ArrayUtils::pickRandom( $loads );
}
/**
@@ -219,6 +219,7 @@ class LoadBalancer {
}
if ( !$nonErrorLoads ) {
+ wfProfileOut( __METHOD__ );
throw new MWException( "Empty server array given to LoadBalancer" );
}
@@ -235,7 +236,7 @@ class LoadBalancer {
$currentLoads = $nonErrorLoads;
while ( count( $currentLoads ) ) {
if ( $wgReadOnly || $this->mAllowLagged || $laggedSlaveMode ) {
- $i = $this->pickRandom( $currentLoads );
+ $i = ArrayUtils::pickRandom( $currentLoads );
} else {
$i = $this->getRandomNonLagged( $currentLoads, $wiki );
if ( $i === false && count( $currentLoads ) != 0 ) {
@@ -243,7 +244,7 @@ class LoadBalancer {
wfDebugLog( 'replication', "All slaves lagged. Switch to read-only mode\n" );
$wgReadOnly = 'The database has been automatically locked ' .
'while the slave database servers catch up to the master';
- $i = $this->pickRandom( $currentLoads );
+ $i = ArrayUtils::pickRandom( $currentLoads );
$laggedSlaveMode = true;
}
}
@@ -348,7 +349,7 @@ class LoadBalancer {
* Set the master wait position
* If a DB_SLAVE connection has been opened already, waits
* Otherwise sets a variable telling it to wait if such a connection is opened
- * @param $pos int
+ * @param $pos DBMasterPos
*/
public function waitFor( $pos ) {
wfProfileIn( __METHOD__ );
@@ -366,7 +367,7 @@ class LoadBalancer {
/**
* Set the master wait position and wait for ALL slaves to catch up to it
- * @param $pos int
+ * @param $pos DBMasterPos
*/
public function waitForAll( $pos ) {
wfProfileIn( __METHOD__ );
@@ -399,7 +400,7 @@ class LoadBalancer {
* @param $open bool
* @return bool
*/
- function doWait( $index, $open = false ) {
+ protected function doWait( $index, $open = false ) {
# Find a connection to wait on
$conn = $this->getAnyOpenConnection( $index );
if ( !$conn ) {
@@ -407,7 +408,7 @@ class LoadBalancer {
wfDebug( __METHOD__ . ": no connection open\n" );
return false;
} else {
- $conn = $this->openConnection( $index );
+ $conn = $this->openConnection( $index, '' );
if ( !$conn ) {
wfDebug( __METHOD__ . ": failed to open connection\n" );
return false;
@@ -443,8 +444,10 @@ class LoadBalancer {
wfProfileIn( __METHOD__ );
if ( $i == DB_LAST ) {
+ wfProfileOut( __METHOD__ );
throw new MWException( 'Attempt to call ' . __METHOD__ . ' with deprecated server index DB_LAST' );
} elseif ( $i === null || $i === false ) {
+ wfProfileOut( __METHOD__ );
throw new MWException( 'Attempt to call ' . __METHOD__ . ' with invalid server index' );
}
@@ -476,6 +479,7 @@ class LoadBalancer {
# Operation-based index
if ( $i == DB_SLAVE ) {
+ $this->mLastError = 'Unknown error'; // reset error string
$i = $this->getReaderIndex( false, $wiki );
# Couldn't find a working server in getReaderIndex()?
if ( $i === false ) {
@@ -542,6 +546,38 @@ class LoadBalancer {
}
/**
+ * Get a database connection handle reference
+ *
+ * The handle's methods wrap simply wrap those of a DatabaseBase handle
+ *
+ * @see LoadBalancer::getConnection() for parameter information
+ *
+ * @param integer $db
+ * @param mixed $groups
+ * @param string $wiki
+ * @return DBConnRef
+ */
+ public function getConnectionRef( $db, $groups = array(), $wiki = false ) {
+ return new DBConnRef( $this, $this->getConnection( $db, $groups, $wiki ) );
+ }
+
+ /**
+ * Get a database connection handle reference without connecting yet
+ *
+ * The handle's methods wrap simply wrap those of a DatabaseBase handle
+ *
+ * @see LoadBalancer::getConnection() for parameter information
+ *
+ * @param integer $db
+ * @param mixed $groups
+ * @param string $wiki
+ * @return DBConnRef
+ */
+ public function getLazyConnectionRef( $db, $groups = array(), $wiki = false ) {
+ return new DBConnRef( $this, array( $db, $groups, $wiki ) );
+ }
+
+ /**
* Open a connection to the server given by the specified index
* Index must be an actual index into the array.
* If the server is already open, returns it.
@@ -633,6 +669,7 @@ class LoadBalancer {
$server = $this->mServers[$i];
$server['serverIndex'] = $i;
$server['foreignPoolRefCount'] = 0;
+ $server['foreign'] = true;
$conn = $this->reallyOpenConnection( $server, $dbName );
if ( !$conn->isOpen() ) {
wfDebug( __METHOD__ . ": error opening connection for $i/$wiki\n" );
@@ -662,7 +699,7 @@ class LoadBalancer {
* @return bool
*/
function isOpen( $index ) {
- if( !is_integer( $index ) ) {
+ if ( !is_integer( $index ) ) {
return false;
}
return (bool)$this->getAnyOpenConnection( $index );
@@ -679,7 +716,7 @@ class LoadBalancer {
* @return DatabaseBase
*/
function reallyOpenConnection( $server, $dbNameOverride = false ) {
- if( !is_array( $server ) ) {
+ if ( !is_array( $server ) ) {
throw new MWException( 'You must update your load-balancing configuration. ' .
'See DefaultSettings.php entry for $wgDBservers.' );
}
@@ -830,7 +867,7 @@ class LoadBalancer {
*/
function closeAll() {
foreach ( $this->mConns as $conns2 ) {
- foreach ( $conns2 as $conns3 ) {
+ foreach ( $conns2 as $conns3 ) {
foreach ( $conns3 as $conn ) {
$conn->close();
}
@@ -1064,3 +1101,46 @@ class LoadBalancer {
$this->mLagTimes = null;
}
}
+
+/**
+ * Helper class to handle automatically marking connectons as reusable (via RAII pattern)
+ * as well handling deferring the actual network connection until the handle is used
+ *
+ * @ingroup Database
+ * @since 1.22
+ */
+class DBConnRef implements IDatabase {
+ /** @var LoadBalancer */
+ protected $lb;
+ /** @var DatabaseBase|null */
+ protected $conn;
+ /** @var Array|null */
+ protected $params;
+
+ /**
+ * @param $lb LoadBalancer
+ * @param $conn DatabaseBase|array Connection or (server index, group, wiki ID) array
+ */
+ public function __construct( LoadBalancer $lb, $conn ) {
+ $this->lb = $lb;
+ if ( $conn instanceof DatabaseBase ) {
+ $this->conn = $conn;
+ } else {
+ $this->params = $conn;
+ }
+ }
+
+ public function __call( $name, $arguments ) {
+ if ( $this->conn === null ) {
+ list( $db, $groups, $wiki ) = $this->params;
+ $this->conn = $this->lb->getConnection( $db, $groups, $wiki );
+ }
+ return call_user_func_array( array( $this->conn, $name ), $arguments );
+ }
+
+ function __destruct() {
+ if ( $this->conn !== null ) {
+ $this->lb->reuseConnection( $this->conn );
+ }
+ }
+}
diff --git a/includes/db/LoadMonitor.php b/includes/db/LoadMonitor.php
index ad7b3b2f..519e2dfd 100644
--- a/includes/db/LoadMonitor.php
+++ b/includes/db/LoadMonitor.php
@@ -135,18 +135,19 @@ class LoadMonitor_MySQL implements LoadMonitor {
$requestRate = 10;
global $wgMemc;
- if ( empty( $wgMemc ) )
+ if ( empty( $wgMemc ) ) {
$wgMemc = wfGetMainCache();
+ }
$masterName = $this->parent->getServerName( 0 );
$memcKey = wfMemcKey( 'lag_times', $masterName );
$times = $wgMemc->get( $memcKey );
- if ( $times ) {
+ if ( is_array( $times ) ) {
# Randomly recache with probability rising over $expiry
$elapsed = time() - $times['timestamp'];
$chance = max( 0, ( $expiry - $elapsed ) * $requestRate );
if ( mt_rand( 0, $chance ) != 0 ) {
- unset( $times['timestamp'] );
+ unset( $times['timestamp'] ); // hide from caller
wfProfileOut( __METHOD__ );
return $times;
}
@@ -156,6 +157,17 @@ class LoadMonitor_MySQL implements LoadMonitor {
}
# Cache key missing or expired
+ if ( $wgMemc->add( "$memcKey:lock", 1, 10 ) ) {
+ # Let this process alone update the cache value
+ $unlocker = new ScopedCallback( function() use ( $wgMemc, $memcKey ) {
+ $wgMemc->delete( $memcKey );
+ } );
+ } elseif ( is_array( $times ) ) {
+ # Could not acquire lock but an old cache exists, so use it
+ unset( $times['timestamp'] ); // hide from caller
+ wfProfileOut( __METHOD__ );
+ return $times;
+ }
$times = array();
foreach ( $serverIndexes as $i ) {
@@ -170,14 +182,11 @@ class LoadMonitor_MySQL implements LoadMonitor {
# Add a timestamp key so we know when it was cached
$times['timestamp'] = time();
- $wgMemc->set( $memcKey, $times, $expiry );
-
- # But don't give the timestamp to the caller
- unset( $times['timestamp'] );
- $lagTimes = $times;
+ $wgMemc->set( $memcKey, $times, $expiry + 10 );
+ unset( $times['timestamp'] ); // hide from caller
wfProfileOut( __METHOD__ );
- return $lagTimes;
+ return $times;
}
/**
diff --git a/includes/db/ORMRow.php b/includes/db/ORMRow.php
index 6c1f27ff..5ce3794d 100644
--- a/includes/db/ORMRow.php
+++ b/includes/db/ORMRow.php
@@ -43,17 +43,12 @@ class ORMRow implements IORMRow {
protected $fields = array( 'id' => null );
/**
- * @since 1.20
- * @var ORMTable
- */
- protected $table;
-
- /**
* If the object should update summaries of linked items when changed.
* For example, update the course_count field in universities when a course in courses is deleted.
* Settings this to false can prevent needless updating work in situations
* such as deleting a university, which will then delete all it's courses.
*
+ * @deprecated since 1.22
* @since 1.20
* @var bool
*/
@@ -64,21 +59,29 @@ class ORMRow implements IORMRow {
* This mode indicates that only summary fields got updated,
* which allows for optimizations.
*
+ * @deprecated since 1.22
* @since 1.20
* @var bool
*/
protected $inSummaryMode = false;
/**
+ * @deprecated since 1.22
+ * @since 1.20
+ * @var ORMTable|null
+ */
+ protected $table;
+
+ /**
* Constructor.
*
* @since 1.20
*
- * @param IORMTable $table
+ * @param IORMTable|null $table Deprecated since 1.22
* @param array|null $fields
- * @param boolean $loadDefaults
+ * @param boolean $loadDefaults Deprecated since 1.22
*/
- public function __construct( IORMTable $table, $fields = null, $loadDefaults = false ) {
+ public function __construct( IORMTable $table = null, $fields = null, $loadDefaults = false ) {
$this->table = $table;
if ( !is_array( $fields ) ) {
@@ -96,6 +99,7 @@ class ORMRow implements IORMRow {
* Load the specified fields from the database.
*
* @since 1.20
+ * @deprecated since 1.22
*
* @param array|null $fields
* @param boolean $override
@@ -160,6 +164,7 @@ class ORMRow implements IORMRow {
* Gets the value of a field but first loads it if not done so already.
*
* @since 1.20
+ * @deprecated since 1.22
*
* @param $name string
*
@@ -232,25 +237,10 @@ class ORMRow implements IORMRow {
}
/**
- * Sets multiple fields.
- *
- * @since 1.20
- *
- * @param array $fields The fields to set
- * @param boolean $override Override already set fields with the provided values?
- */
- public function setFields( array $fields, $override = true ) {
- foreach ( $fields as $name => $value ) {
- if ( $override || !$this->hasField( $name ) ) {
- $this->setField( $name, $value );
- }
- }
- }
-
- /**
* Gets the fields => values to write to the table.
*
* @since 1.20
+ * @deprecated since 1.22
*
* @return array
*/
@@ -269,10 +259,10 @@ class ORMRow implements IORMRow {
switch ( $type ) {
case 'array':
$value = (array)$value;
- // fall-through!
+ // fall-through!
case 'blob':
$value = serialize( $value );
- // fall-through!
+ // fall-through!
}
$values[$this->table->getPrefixedField( $name )] = $value;
@@ -283,6 +273,22 @@ class ORMRow implements IORMRow {
}
/**
+ * Sets multiple fields.
+ *
+ * @since 1.20
+ *
+ * @param array $fields The fields to set
+ * @param boolean $override Override already set fields with the provided values?
+ */
+ public function setFields( array $fields, $override = true ) {
+ foreach ( $fields as $name => $value ) {
+ if ( $override || !$this->hasField( $name ) ) {
+ $this->setField( $name, $value );
+ }
+ }
+ }
+
+ /**
* Serializes the object to an associative array which
* can then easily be converted into JSON or similar.
*
@@ -320,6 +326,7 @@ class ORMRow implements IORMRow {
* Load the default values, via getDefaults.
*
* @since 1.20
+ * @deprecated since 1.22
*
* @param boolean $override
*/
@@ -332,6 +339,7 @@ class ORMRow implements IORMRow {
* when it already exists, or inserting it when it doesn't.
*
* @since 1.20
+ * @deprecated since 1.22 Use IORMTable->updateRow or ->insertRow
*
* @param string|null $functionName
*
@@ -339,9 +347,9 @@ class ORMRow implements IORMRow {
*/
public function save( $functionName = null ) {
if ( $this->hasIdField() ) {
- return $this->saveExisting( $functionName );
+ return $this->table->updateRow( $this, $functionName );
} else {
- return $this->insert( $functionName );
+ return $this->table->insertRow( $this, $functionName );
}
}
@@ -349,6 +357,7 @@ class ORMRow implements IORMRow {
* Updates the object in the database.
*
* @since 1.20
+ * @deprecated since 1.22
*
* @param string|null $functionName
*
@@ -386,6 +395,7 @@ class ORMRow implements IORMRow {
* Inserts the object into the database.
*
* @since 1.20
+ * @deprecated since 1.22
*
* @param string|null $functionName
* @param array|null $options
@@ -418,16 +428,14 @@ class ORMRow implements IORMRow {
* Removes the object from the database.
*
* @since 1.20
+ * @deprecated since 1.22, use IORMTable->removeRow
*
* @return boolean Success indicator
*/
public function remove() {
$this->beforeRemove();
- $success = $this->table->delete( array( 'id' => $this->getId() ), __METHOD__ );
-
- // DatabaseBase::delete does not always return true for success as documented...
- $success = $success !== false;
+ $success = $this->table->removeRow( $this, __METHOD__ );
if ( $success ) {
$this->onRemoved();
@@ -440,6 +448,7 @@ class ORMRow implements IORMRow {
* Gets called before an object is removed from the database.
*
* @since 1.20
+ * @deprecated since 1.22
*/
protected function beforeRemove() {
$this->loadFields( $this->getBeforeRemoveFields(), false, true );
@@ -463,6 +472,7 @@ class ORMRow implements IORMRow {
* Can be overridden to get rid of linked data.
*
* @since 1.20
+ * @deprecated since 1.22
*/
protected function onRemoved() {
$this->setField( 'id', null );
@@ -503,51 +513,14 @@ class ORMRow implements IORMRow {
* @throws MWException
*/
public function setField( $name, $value ) {
- $fields = $this->table->getFields();
-
- if ( array_key_exists( $name, $fields ) ) {
- switch ( $fields[$name] ) {
- case 'int':
- $value = (int)$value;
- break;
- case 'float':
- $value = (float)$value;
- break;
- case 'bool':
- $value = (bool)$value;
- break;
- case 'array':
- if ( is_string( $value ) ) {
- $value = unserialize( $value );
- }
-
- if ( !is_array( $value ) ) {
- $value = array();
- }
- break;
- case 'blob':
- if ( is_string( $value ) ) {
- $value = unserialize( $value );
- }
- break;
- case 'id':
- if ( is_string( $value ) ) {
- $value = (int)$value;
- }
- break;
- }
-
- $this->fields[$name] = $value;
- } else {
- throw new MWException( 'Attempted to set unknown field ' . $name );
- }
+ $this->fields[$name] = $value;
}
/**
* Add an amount (can be negative) to the specified field (needs to be numeric).
- * TODO: most off this stuff makes more sense in the table class
*
* @since 1.20
+ * @deprecated since 1.22, use IORMTable->addToField
*
* @param string $field
* @param integer $amount
@@ -555,41 +528,14 @@ class ORMRow implements IORMRow {
* @return boolean Success indicator
*/
public function addToField( $field, $amount ) {
- if ( $amount == 0 ) {
- return true;
- }
-
- if ( !$this->hasIdField() ) {
- return false;
- }
-
- $absoluteAmount = abs( $amount );
- $isNegative = $amount < 0;
-
- $dbw = $this->table->getWriteDbConnection();
-
- $fullField = $this->table->getPrefixedField( $field );
-
- $success = $dbw->update(
- $this->table->getName(),
- array( "$fullField=$fullField" . ( $isNegative ? '-' : '+' ) . $absoluteAmount ),
- array( $this->table->getPrefixedField( 'id' ) => $this->getId() ),
- __METHOD__
- );
-
- if ( $success && $this->hasField( $field ) ) {
- $this->setField( $field, $this->getField( $field ) + $amount );
- }
-
- $this->table->releaseConnection( $dbw );
-
- return $success;
+ return $this->table->addToField( $this->getUpdateConditions(), $field, $amount );
}
/**
* Return the names of the fields.
*
* @since 1.20
+ * @deprecated since 1.22
*
* @return array
*/
@@ -601,6 +547,7 @@ class ORMRow implements IORMRow {
* Computes and updates the values of the summary fields.
*
* @since 1.20
+ * @deprecated since 1.22
*
* @param array|string|null $summaryFields
*/
@@ -612,6 +559,7 @@ class ORMRow implements IORMRow {
* Sets the value for the @see $updateSummaries field.
*
* @since 1.20
+ * @deprecated since 1.22
*
* @param boolean $update
*/
@@ -623,6 +571,7 @@ class ORMRow implements IORMRow {
* Sets the value for the @see $inSummaryMode field.
*
* @since 1.20
+ * @deprecated since 1.22
*
* @param boolean $summaryMode
*/
@@ -631,39 +580,10 @@ class ORMRow implements IORMRow {
}
/**
- * Return if any fields got changed.
- *
- * @since 1.20
- *
- * @param IORMRow $object
- * @param boolean|array $excludeSummaryFields
- * When set to true, summary field changes are ignored.
- * Can also be an array of fields to ignore.
- *
- * @return boolean
- */
- protected function fieldsChanged( IORMRow $object, $excludeSummaryFields = false ) {
- $exclusionFields = array();
-
- if ( $excludeSummaryFields !== false ) {
- $exclusionFields = is_array( $excludeSummaryFields ) ? $excludeSummaryFields : $this->table->getSummaryFields();
- }
-
- foreach ( $this->fields as $name => $value ) {
- $excluded = $excludeSummaryFields && in_array( $name, $exclusionFields );
-
- if ( !$excluded && $object->getField( $name ) !== $value ) {
- return true;
- }
- }
-
- return false;
- }
-
- /**
* Returns the table this IORMRow is a row in.
*
* @since 1.20
+ * @deprecated since 1.22
*
* @return IORMTable
*/
diff --git a/includes/db/ORMTable.php b/includes/db/ORMTable.php
index bcbe94a3..5f6723b9 100644
--- a/includes/db/ORMTable.php
+++ b/includes/db/ORMTable.php
@@ -814,10 +814,59 @@ class ORMTable extends DBAccessBase implements IORMTable {
*/
public function getFieldsFromDBResult( stdClass $result ) {
$result = (array)$result;
- return array_combine(
+
+ $rawFields = array_combine(
$this->unprefixFieldNames( array_keys( $result ) ),
array_values( $result )
);
+
+ $fieldDefinitions = $this->getFields();
+ $fields = array();
+
+ foreach ( $rawFields as $name => $value ) {
+ if ( array_key_exists( $name, $fieldDefinitions ) ) {
+ switch ( $fieldDefinitions[$name] ) {
+ case 'int':
+ $value = (int)$value;
+ break;
+ case 'float':
+ $value = (float)$value;
+ break;
+ case 'bool':
+ if ( is_string( $value ) ) {
+ $value = $value !== '0';
+ } elseif ( is_int( $value ) ) {
+ $value = $value !== 0;
+ }
+ break;
+ case 'array':
+ if ( is_string( $value ) ) {
+ $value = unserialize( $value );
+ }
+
+ if ( !is_array( $value ) ) {
+ $value = array();
+ }
+ break;
+ case 'blob':
+ if ( is_string( $value ) ) {
+ $value = unserialize( $value );
+ }
+ break;
+ case 'id':
+ if ( is_string( $value ) ) {
+ $value = (int)$value;
+ }
+ break;
+ }
+
+ $fields[$name] = $value;
+ } else {
+ throw new MWException( 'Attempted to set unknown field ' . $name );
+ }
+ }
+
+ return $fields;
}
/**
@@ -867,14 +916,15 @@ class ORMTable extends DBAccessBase implements IORMTable {
*
* @since 1.20
*
- * @param array $data
+ * @param array $fields
* @param boolean $loadDefaults
*
* @return IORMRow
*/
- public function newRow( array $data, $loadDefaults = false ) {
+ public function newRow( array $fields, $loadDefaults = false ) {
$class = $this->getRowClass();
- return new $class( $this, $data, $loadDefaults );
+
+ return new $class( $this, $fields, $loadDefaults );
}
/**
@@ -901,4 +951,157 @@ class ORMTable extends DBAccessBase implements IORMTable {
return array_key_exists( $name, $this->getFields() );
}
+ /**
+ * Updates the provided row in the database.
+ *
+ * @since 1.22
+ *
+ * @param IORMRow $row The row to save
+ * @param string|null $functionName
+ *
+ * @return boolean Success indicator
+ */
+ public function updateRow( IORMRow $row, $functionName = null ) {
+ $dbw = $this->getWriteDbConnection();
+
+ $success = $dbw->update(
+ $this->getName(),
+ $this->getWriteValues( $row ),
+ $this->getPrefixedValues( array( 'id' => $row->getId() ) ),
+ is_null( $functionName ) ? __METHOD__ : $functionName
+ );
+
+ $this->releaseConnection( $dbw );
+
+ // DatabaseBase::update does not always return true for success as documented...
+ return $success !== false;
+ }
+
+ /**
+ * Inserts the provided row into the database.
+ *
+ * @since 1.22
+ *
+ * @param IORMRow $row
+ * @param string|null $functionName
+ * @param array|null $options
+ *
+ * @return boolean Success indicator
+ */
+ public function insertRow( IORMRow $row, $functionName = null, array $options = null ) {
+ $dbw = $this->getWriteDbConnection();
+
+ $success = $dbw->insert(
+ $this->getName(),
+ $this->getWriteValues( $row ),
+ is_null( $functionName ) ? __METHOD__ : $functionName,
+ $options
+ );
+
+ // DatabaseBase::insert does not always return true for success as documented...
+ $success = $success !== false;
+
+ if ( $success ) {
+ $row->setField( 'id', $dbw->insertId() );
+ }
+
+ $this->releaseConnection( $dbw );
+
+ return $success;
+ }
+
+ /**
+ * Gets the fields => values to write to the table.
+ *
+ * @since 1.22
+ *
+ * @param IORMRow $row
+ *
+ * @return array
+ */
+ protected function getWriteValues( IORMRow $row ) {
+ $values = array();
+
+ $rowFields = $row->getFields();
+
+ foreach ( $this->getFields() as $name => $type ) {
+ if ( array_key_exists( $name, $rowFields ) ) {
+ $value = $rowFields[$name];
+
+ switch ( $type ) {
+ case 'array':
+ $value = (array)$value;
+ // fall-through!
+ case 'blob':
+ $value = serialize( $value );
+ // fall-through!
+ }
+
+ $values[$this->getPrefixedField( $name )] = $value;
+ }
+ }
+
+ return $values;
+ }
+
+ /**
+ * Removes the provided row from the database.
+ *
+ * @since 1.22
+ *
+ * @param IORMRow $row
+ * @param string|null $functionName
+ *
+ * @return boolean Success indicator
+ */
+ public function removeRow( IORMRow $row, $functionName = null ) {
+ $success = $this->delete(
+ array( 'id' => $row->getId() ),
+ is_null( $functionName ) ? __METHOD__ : $functionName
+ );
+
+ // DatabaseBase::delete does not always return true for success as documented...
+ return $success !== false;
+ }
+
+ /**
+ * Add an amount (can be negative) to the specified field (needs to be numeric).
+ *
+ * @since 1.22
+ *
+ * @param array $conditions
+ * @param string $field
+ * @param integer $amount
+ *
+ * @return boolean Success indicator
+ * @throws MWException
+ */
+ public function addToField( array $conditions, $field, $amount ) {
+ if ( !array_key_exists( $field, $this->fields ) ) {
+ throw new MWException( 'Unknown field "' . $field . '" provided' );
+ }
+
+ if ( $amount == 0 ) {
+ return true;
+ }
+
+ $absoluteAmount = abs( $amount );
+ $isNegative = $amount < 0;
+
+ $fullField = $this->getPrefixedField( $field );
+
+ $dbw = $this->getWriteDbConnection();
+
+ $success = $dbw->update(
+ $this->getName(),
+ array( "$fullField=$fullField" . ( $isNegative ? '-' : '+' ) . $absoluteAmount ),
+ $this->getPrefixedValues( $conditions ),
+ __METHOD__
+ ) !== false; // DatabaseBase::update does not always return true for success as documented...
+
+ $this->releaseConnection( $dbw );
+
+ return $success;
+ }
+
}
diff --git a/includes/debug/Debug.php b/includes/debug/Debug.php
index 8c39e1a1..6e9ccc41 100644
--- a/includes/debug/Debug.php
+++ b/includes/debug/Debug.php
@@ -1,6 +1,6 @@
<?php
/**
- * Debug toolbar related code
+ * Debug toolbar related 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
@@ -21,7 +21,7 @@
*/
/**
- * New debugger system that outputs a toolbar on page view
+ * New debugger system that outputs a toolbar on page view.
*
* By default, most methods do nothing ( self::$enabled = false ). You have
* to explicitly call MWDebug::init() to enabled them.
@@ -35,28 +35,28 @@ class MWDebug {
/**
* Log lines
*
- * @var array
+ * @var array $log
*/
protected static $log = array();
/**
- * Debug messages from wfDebug()
+ * Debug messages from wfDebug().
*
- * @var array
+ * @var array $debug
*/
protected static $debug = array();
/**
- * Queries
+ * SQL statements of the databses queries.
*
- * @var array
+ * @var array $query
*/
protected static $query = array();
/**
* Is the debugger enabled?
*
- * @var bool
+ * @var bool $enabled
*/
protected static $enabled = false;
@@ -64,7 +64,7 @@ class MWDebug {
* Array of functions that have already been warned, formatted
* function-caller to prevent a buttload of warnings
*
- * @var array
+ * @var array $deprecationWarnings
*/
protected static $deprecationWarnings = array();
@@ -135,10 +135,24 @@ class MWDebug {
* @since 1.19
* @param $msg string
* @param $callerOffset int
- * @param int $level A PHP error level. See sendWarning()
+ * @param $level int A PHP error level. See sendWarning()
+ * @param $log string: 'production' will always trigger a php error, 'auto'
+ * will trigger an error if $wgDevelopmentWarnings is true, and 'debug'
+ * will only write to the debug log(s).
+ *
* @return mixed
*/
- public static function warning( $msg, $callerOffset = 1, $level = E_USER_NOTICE ) {
+ public static function warning( $msg, $callerOffset = 1, $level = E_USER_NOTICE, $log = 'auto' ) {
+ global $wgDevelopmentWarnings;
+
+ if ( $log === 'auto' && !$wgDevelopmentWarnings ) {
+ $log = 'debug';
+ }
+
+ if ( $log === 'debug' ) {
+ $level = false;
+ }
+
$callerDescription = self::getCallerDescription( $callerOffset );
self::sendWarning( $msg, $callerDescription, $level );
@@ -212,7 +226,8 @@ class MWDebug {
}
if ( $sendToLog ) {
- self::sendWarning( $msg, $callerDescription, E_USER_DEPRECATED );
+ global $wgDevelopmentWarnings; // we could have a more specific $wgDeprecationWarnings setting.
+ self::sendWarning( $msg, $callerDescription, $wgDevelopmentWarnings ? E_USER_DEPRECATED : false );
}
if ( self::$enabled ) {
@@ -267,23 +282,21 @@ class MWDebug {
}
/**
- * Send a warning either to the debug log or by triggering an user PHP
- * error depending on $wgDevelopmentWarnings.
+ * Send a warning to the debug log and optionally also trigger a PHP
+ * error, depending on the $level argument.
*
- * @param string $msg Message to send
- * @param array $caller caller description get from getCallerDescription()
- * @param $level error level to use if $wgDevelopmentWarnings is true
+ * @param $msg string Message to send
+ * @param $caller array caller description get from getCallerDescription()
+ * @param $level int|bool error level to use; set to false to not trigger an error
*/
private static function sendWarning( $msg, $caller, $level ) {
- global $wgDevelopmentWarnings;
-
$msg .= ' [Called from ' . $caller['func'] . ' in ' . $caller['file'] . ']';
- if ( $wgDevelopmentWarnings ) {
+ if ( $level !== false ) {
trigger_error( $msg, $level );
- } else {
- wfDebug( "$msg\n" );
}
+
+ wfDebug( "$msg\n" );
}
/**
@@ -297,7 +310,7 @@ class MWDebug {
global $wgDebugComments, $wgShowDebug;
if ( self::$enabled || $wgDebugComments || $wgShowDebug ) {
- self::$debug[] = rtrim( $str );
+ self::$debug[] = rtrim( UtfNormal::cleanUp( $str ) );
}
}
@@ -319,7 +332,7 @@ class MWDebug {
self::$query[] = array(
'sql' => $sql,
'function' => $function,
- 'master' => (bool) $isMaster,
+ 'master' => (bool)$isMaster,
'time' => 0.0,
'_start' => microtime( true ),
);
@@ -471,10 +484,10 @@ class MWDebug {
// output errors as debug info, when display_errors is on
// this is necessary for all non html output of the api, because that clears all errors first
$obContents = ob_get_contents();
- if( $obContents ) {
+ if ( $obContents ) {
$obContentArray = explode( '<br />', $obContents );
- foreach( $obContentArray as $obContent ) {
- if( trim( $obContent ) ) {
+ foreach ( $obContentArray as $obContent ) {
+ if ( trim( $obContent ) ) {
self::debugMsg( Sanitizer::stripAllTags( $obContent ) );
}
}
@@ -485,15 +498,10 @@ class MWDebug {
$result->setIndexedTagName( $debugInfo, 'debuginfo' );
$result->setIndexedTagName( $debugInfo['log'], 'line' );
- foreach( $debugInfo['debugLog'] as $index => $debugLogText ) {
- $vals = array();
- ApiResult::setContent( $vals, $debugLogText );
- $debugInfo['debugLog'][$index] = $vals; //replace
- }
$result->setIndexedTagName( $debugInfo['debugLog'], 'msg' );
$result->setIndexedTagName( $debugInfo['queries'], 'query' );
$result->setIndexedTagName( $debugInfo['includes'], 'queries' );
- $result->addValue( array(), 'debuginfo', $debugInfo );
+ $result->addValue( null, 'debuginfo', $debugInfo );
}
/**
diff --git a/includes/diff/DairikiDiff.php b/includes/diff/DairikiDiff.php
index 94ffc066..298d7240 100644
--- a/includes/diff/DairikiDiff.php
+++ b/includes/diff/DairikiDiff.php
@@ -183,7 +183,7 @@ class _DiffEngine {
* @param $to_lines
* @return array
*/
- function diff ( $from_lines, $to_lines ) {
+ function diff( $from_lines, $to_lines ) {
wfProfileIn( __METHOD__ );
// Diff and store locally
@@ -241,7 +241,7 @@ class _DiffEngine {
* @param $from_lines
* @param $to_lines
*/
- function diff_local ( $from_lines, $to_lines ) {
+ function diff_local( $from_lines, $to_lines ) {
global $wgExternalDiffEngine;
wfProfileIn( __METHOD__ );
@@ -271,7 +271,8 @@ class _DiffEngine {
$this->xchanged[$skip] = $this->ychanged[$skip] = false;
}
// Skip trailing common lines.
- $xi = $n_from; $yi = $n_to;
+ $xi = $n_from;
+ $yi = $n_to;
for ( $endskip = 0; --$xi > $skip && --$yi > $skip; $endskip++ ) {
if ( $from_lines[$xi] !== $to_lines[$yi] ) {
break;
@@ -394,7 +395,7 @@ class _DiffEngine {
break;
}
}
- while ( list ( , $y ) = each( $matches ) ) {
+ while ( list( , $y ) = each( $matches ) ) {
if ( $y > $this->seq[$k -1] ) {
assert( '$y < $this->seq[$k]' );
// Optimization: this is a common case:
@@ -469,10 +470,9 @@ class _DiffEngine {
* @param $yoff
* @param $ylim
*/
- function _compareseq ( $xoff, $xlim, $yoff, $ylim ) {
+ function _compareseq( $xoff, $xlim, $yoff, $ylim ) {
// Slide down the bottom initial diagonal.
- while ( $xoff < $xlim && $yoff < $ylim
- && $this->xv[$xoff] == $this->yv[$yoff] ) {
+ while ( $xoff < $xlim && $yoff < $ylim && $this->xv[$xoff] == $this->yv[$yoff] ) {
++$xoff;
++$yoff;
}
@@ -491,7 +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 ) {
@@ -508,7 +508,7 @@ class _DiffEngine {
reset( $seps );
$pt1 = $seps[0];
while ( $pt2 = next( $seps ) ) {
- $this->_compareseq ( $pt1[0], $pt2[0], $pt1[1], $pt2[1] );
+ $this->_compareseq( $pt1[0], $pt2[0], $pt1[1], $pt2[1] );
$pt1 = $pt2;
}
}
@@ -554,9 +554,11 @@ class _DiffEngine {
while ( $i < $len && ! $changed[$i] ) {
assert( '$j < $other_len && ! $other_changed[$j]' );
- $i++; $j++;
- while ( $j < $other_len && $other_changed[$j] )
+ $i++;
$j++;
+ while ( $j < $other_len && $other_changed[$j] ) {
+ $j++;
+ }
}
if ( $i == $len ) {
@@ -690,7 +692,7 @@ class Diff {
/**
* Check for empty diff.
*
- * @return bool True iff two sequences were identical.
+ * @return bool True if two sequences were identical.
*/
function isEmpty() {
foreach ( $this->edits as $edit ) {
@@ -1109,7 +1111,7 @@ class ArrayDiffFormatter extends DiffFormatter {
$newline = 1;
$retval = array();
foreach ( $diff->edits as $edit ) {
- switch( $edit->type ) {
+ switch ( $edit->type ) {
case 'add':
foreach ( $edit->closing as $l ) {
$retval[] = array(
@@ -1204,7 +1206,7 @@ class _HWLDF_WordAccumulator {
* @param $words
* @param $tag string
*/
- function addWords ( $words, $tag = '' ) {
+ function addWords( $words, $tag = '' ) {
if ( $tag != $this->_tag ) {
$this->_flushGroup( $tag );
}
@@ -1244,7 +1246,7 @@ class WordLevelDiff extends MappedDiff {
* @param $orig_lines
* @param $closing_lines
*/
- function __construct ( $orig_lines, $closing_lines ) {
+ function __construct( $orig_lines, $closing_lines ) {
wfProfileIn( __METHOD__ );
list( $orig_words, $orig_stripped ) = $this->_split( $orig_lines );
@@ -1282,8 +1284,12 @@ class WordLevelDiff extends MappedDiff {
if ( preg_match_all( '/ ( [^\S\n]+ | [0-9_A-Za-z\x80-\xff]+ | . ) (?: (?!< \n) [^\S\n])? /xs',
$line, $m ) )
{
- $words = array_merge( $words, $m[0] );
- $stripped = array_merge( $stripped, $m[1] );
+ foreach ( $m[0] as $word ) {
+ $words[] = $word;
+ }
+ foreach ( $m[1] as $stripped_word ) {
+ $stripped[] = $stripped_word;
+ }
}
}
}
diff --git a/includes/diff/DifferenceEngine.php b/includes/diff/DifferenceEngine.php
index 0f3c77ff..e436f58d 100644
--- a/includes/diff/DifferenceEngine.php
+++ b/includes/diff/DifferenceEngine.php
@@ -38,6 +38,7 @@ class DifferenceEngine extends ContextSource {
* @private
*/
var $mOldid, $mNewid;
+ var $mOldTags, $mNewTags;
/**
* @var Content
*/
@@ -48,7 +49,6 @@ class DifferenceEngine extends ContextSource {
* @var Title
*/
var $mOldPage, $mNewPage;
- var $mRcidMarkPatrolled;
/**
* @var Revision
@@ -80,8 +80,8 @@ 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 string $new either 'prev' or 'next'.
- * @param $rcid Integer ??? FIXME (default 0)
+ * @param $new String either 'prev' or 'next'.
+ * @param $rcid Integer Deprecated, no longer used!
* @param $refreshCache boolean If set, refreshes the diff cache
* @param $unhide boolean If set, allow viewing deleted revs
*/
@@ -96,7 +96,6 @@ class DifferenceEngine extends ContextSource {
$this->mOldid = $old;
$this->mNewid = $new;
- $this->mRcidMarkPatrolled = intval( $rcid ); # force it to be an integer
$this->mRefreshCache = $refreshCache;
$this->unhide = $unhide;
}
@@ -186,10 +185,14 @@ class DifferenceEngine extends ContextSource {
$out = $this->getOutput();
$missing = array();
- if ( $this->mOldRev === null ) {
+ if ( $this->mOldRev === null ||
+ ( $this->mOldRev && $this->mOldContent === null )
+ ) {
$missing[] = $this->deletedIdMarker( $this->mOldid );
}
- if ( $this->mNewRev === null ) {
+ if ( $this->mNewRev === null ||
+ ( $this->mNewRev && $this->mNewContent === null )
+ ) {
$missing[] = $this->deletedIdMarker( $this->mNewid );
}
@@ -223,33 +226,6 @@ class DifferenceEngine extends ContextSource {
throw new PermissionsError( 'read', $permErrors );
}
- # If external diffs are enabled both globally and for the user,
- # 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.
- $this->mNewPage->getCanonicalURL( array(
- 'action' => 'raw', 'oldid' => $this->mOldid ) )
- ),
- 'File2' => array( 'Extension' => 'wiki', 'URL' =>
- $this->mNewPage->getCanonicalURL( array(
- 'action' => 'raw', 'oldid' => $this->mNewid ) )
- ),
- );
-
- $externalEditor = new ExternalEdit( $this->getContext(), $urls );
- $externalEditor->execute();
-
- wfProfileOut( __METHOD__ );
- return;
- }
-
$rollback = '';
$undoLink = '';
@@ -279,11 +255,6 @@ class DifferenceEngine extends ContextSource {
} else {
wfRunHooks( 'DiffViewHeader', array( $this, $this->mOldRev, $this->mNewRev ) );
- $sk = $this->getSkin();
- if ( method_exists( $sk, 'suppressQuickbar' ) ) {
- $sk->suppressQuickbar();
- }
-
if ( $this->mNewPage->equals( $this->mOldPage ) ) {
$out->setPageTitle( $this->msg( 'difference-title', $this->mNewPage->getPrefixedText() ) );
$samePage = true;
@@ -304,7 +275,7 @@ class DifferenceEngine extends ContextSource {
}
if ( !$this->mOldRev->isDeleted( Revision::DELETED_TEXT ) && !$this->mNewRev->isDeleted( Revision::DELETED_TEXT ) ) {
$undoLink = Html::element( 'a', array(
- 'href' => $this->mNewPage->getLocalUrl( array(
+ 'href' => $this->mNewPage->getLocalURL( array(
'action' => 'edit',
'undoafter' => $this->mOldid,
'undo' => $this->mNewid ) ),
@@ -336,12 +307,14 @@ class DifferenceEngine extends ContextSource {
$ldel = $this->revisionDeleteLink( $this->mOldRev );
$oldRevisionHeader = $this->getRevisionHeader( $this->mOldRev, 'complete' );
+ $oldChangeTags = ChangeTags::formatSummaryRow( $this->mOldTags, 'diff' );
$oldHeader = '<div id="mw-diff-otitle1"><strong>' . $oldRevisionHeader . '</strong></div>' .
'<div id="mw-diff-otitle2">' .
Linker::revUserTools( $this->mOldRev, !$this->unhide ) . '</div>' .
'<div id="mw-diff-otitle3">' . $oldminor .
Linker::revComment( $this->mOldRev, !$diffOnly, !$this->unhide ) . $ldel . '</div>' .
+ '<div id="mw-diff-otitle5">' . $oldChangeTags[0] . '</div>' .
'<div id="mw-diff-otitle4">' . $prevlink . '</div>';
if ( $this->mOldRev->isDeleted( Revision::DELETED_TEXT ) ) {
@@ -387,18 +360,21 @@ class DifferenceEngine extends ContextSource {
$formattedRevisionTools[] = $this->msg( 'parentheses' )->rawParams( $tool )->escaped();
}
$newRevisionHeader = $this->getRevisionHeader( $this->mNewRev, 'complete' ) . ' ' . implode( ' ', $formattedRevisionTools );
+ $newChangeTags = ChangeTags::formatSummaryRow( $this->mNewTags, 'diff' );
$newHeader = '<div id="mw-diff-ntitle1"><strong>' . $newRevisionHeader . '</strong></div>' .
'<div id="mw-diff-ntitle2">' . Linker::revUserTools( $this->mNewRev, !$this->unhide ) .
" $rollback</div>" .
'<div id="mw-diff-ntitle3">' . $newminor .
Linker::revComment( $this->mNewRev, !$diffOnly, !$this->unhide ) . $rdel . '</div>' .
+ '<div id="mw-diff-ntitle5">' . $newChangeTags[0] . '</div>' .
'<div id="mw-diff-ntitle4">' . $nextlink . $this->markPatrolledLink() . '</div>';
if ( $this->mNewRev->isDeleted( Revision::DELETED_TEXT ) ) {
$deleted = true; // new revisions text is hidden
- if ( $this->mNewRev->isDeleted( Revision::DELETED_RESTRICTED ) )
+ if ( $this->mNewRev->isDeleted( Revision::DELETED_RESTRICTED ) ) {
$suppressed = true; // also suppressed
+ }
}
# If the diff cannot be shown due to a deleted revision, then output
@@ -414,7 +390,7 @@ class DifferenceEngine extends ContextSource {
array( $msg ) );
} else {
# Give explanation and add a link to view the diff...
- $link = $this->getTitle()->getFullUrl( $this->getRequest()->appendQueryValue( 'unhide', '1', true ) );
+ $link = $this->getTitle()->getFullURL( $this->getRequest()->appendQueryValue( 'unhide', '1', true ) );
$msg = $suppressed ? 'rev-suppressed-unhide-diff' : 'rev-deleted-unhide-diff';
$out->wrapWikiMsg( "<div id='mw-$msg' class='mw-warning plainlinks'>\n$1\n</div>\n", array( $msg, $link ) );
}
@@ -443,45 +419,48 @@ class DifferenceEngine extends ContextSource {
* @return String
*/
protected function markPatrolledLink() {
- global $wgUseRCPatrol;
+ global $wgUseRCPatrol, $wgEnableAPI, $wgEnableWriteAPI;
+ $user = $this->getUser();
if ( $this->mMarkPatrolledLink === null ) {
// Prepare a change patrol link, if applicable
- if ( $wgUseRCPatrol && $this->mNewPage->quickUserCan( 'patrol', $this->getUser() ) ) {
- // If we've been given an explicit change identifier, use it; saves time
- if ( $this->mRcidMarkPatrolled ) {
- $rcid = $this->mRcidMarkPatrolled;
- $rc = RecentChange::newFromId( $rcid );
- // Already patrolled?
- $rcid = is_object( $rc ) && !$rc->getAttribute( 'rc_patrolled' ) ? $rcid : 0;
+ if (
+ // Is patrolling enabled and the user allowed to?
+ $wgUseRCPatrol && $this->mNewPage->quickUserCan( 'patrol', $user ) &&
+ // Only do this if the revision isn't more than 6 hours older
+ // than the Max RC age (6h because the RC might not be cleaned out regularly)
+ RecentChange::isInRCLifespan( $this->mNewRev->getTimestamp(), 21600 )
+ ) {
+ // Look for an unpatrolled change corresponding to this diff
+
+ $db = wfGetDB( DB_SLAVE );
+ $change = RecentChange::newFromConds(
+ array(
+ 'rc_timestamp' => $db->timestamp( $this->mNewRev->getTimestamp() ),
+ 'rc_this_oldid' => $this->mNewid,
+ 'rc_patrolled' => 0
+ ),
+ __METHOD__,
+ array( 'USE INDEX' => 'rc_timestamp' )
+ );
+
+ if ( $change && $change->getPerformer()->getName() !== $user->getName() ) {
+ $rcid = $change->getAttribute( 'rc_id' );
} else {
- // Look for an unpatrolled change corresponding to this diff
- $db = wfGetDB( DB_SLAVE );
- $change = RecentChange::newFromConds(
- array(
- // Redundant user,timestamp condition so we can use the existing index
- 'rc_user_text' => $this->mNewRev->getRawUserText(),
- 'rc_timestamp' => $db->timestamp( $this->mNewRev->getTimestamp() ),
- 'rc_this_oldid' => $this->mNewid,
- 'rc_last_oldid' => $this->mOldid,
- 'rc_patrolled' => 0
- ),
- __METHOD__
- );
- if ( $change instanceof RecentChange ) {
- $rcid = $change->mAttribs['rc_id'];
- $this->mRcidMarkPatrolled = $rcid;
- } else {
- // None found
- $rcid = 0;
- }
+ // None found or the page has been created by the current user.
+ // If the user could patrol this it already would be patrolled
+ $rcid = 0;
}
// Build the link
if ( $rcid ) {
$this->getOutput()->preventClickjacking();
- $this->getOutput()->addModules( 'mediawiki.page.patrol.ajax' );
+ if ( $wgEnableAPI && $wgEnableWriteAPI
+ && $user->isAllowed( 'writeapi' )
+ ) {
+ $this->getOutput()->addModules( 'mediawiki.page.patrol.ajax' );
+ }
- $token = $this->getUser()->getEditToken( $rcid );
+ $token = $user->getEditToken( $rcid );
$this->mMarkPatrolledLink = ' <span class="patrollink">[' . Linker::linkKnown(
$this->mNewPage,
$this->msg( 'markaspatrolleddiff' )->escaped(),
@@ -536,7 +515,7 @@ class DifferenceEngine extends ContextSource {
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
+ // @todo standardize this crap into one function
if ( ContentHandler::runLegacyHooks( 'ShowRawCssJs', array( $this->mNewContent, $this->mNewPage, $out ) ) ) {
// NOTE: deprecated hook, B/C only
// use the content object's own rendering
@@ -545,9 +524,9 @@ class DifferenceEngine extends ContextSource {
$txt = $po ? $po->getText() : '';
$out->addHTML( $txt );
}
- } elseif( !wfRunHooks( 'ArticleContentViewCustom', array( $this->mNewContent, $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 ) ) ) {
+ } elseif ( !ContentHandler::runLegacyHooks( 'ArticleViewCustom', array( $this->mNewContent, $this->mNewPage, $out ) ) ) {
// NOTE: deprecated hook, B/C only
// Handled by extension
} else {
@@ -576,7 +555,7 @@ class DifferenceEngine extends ContextSource {
# Show categories etc.
$out->addParserOutputNoText( $parserOutput );
}
- } else if ( $parserOutput ) {
+ } elseif ( $parserOutput ) {
$out->addParserOutput( $parserOutput );
}
}
@@ -637,6 +616,10 @@ class DifferenceEngine extends ContextSource {
return false;
} else {
$multi = $this->getMultiNotice();
+ // Display a message when the diff is empty
+ if ( $body === '' ) {
+ $notice .= '<div class="mw-diff-empty">' . $this->msg( 'diff-empty' )->parse() . "</div>\n";
+ }
return $this->addHeader( $body, $otitle, $ntitle, $multi, $notice );
}
}
@@ -662,7 +645,6 @@ class DifferenceEngine extends ContextSource {
return false;
}
// Short-circuit
- // If mOldRev is false, it means that the
if ( $this->mOldRev === false || ( $this->mOldRev && $this->mNewRev
&& $this->mOldRev->getID() == $this->mNewRev->getID() ) )
{
@@ -714,24 +696,6 @@ class DifferenceEngine extends ContextSource {
}
/**
- * Make sure the proper modules are loaded before we try to
- * make the diff
- */
- private function initDiffEngines() {
- global $wgExternalDiffEngine;
- if ( $wgExternalDiffEngine == 'wikidiff' && !function_exists( 'wikidiff_do_diff' ) ) {
- wfProfileIn( __METHOD__ . '-php_wikidiff.so' );
- wfDl( 'php_wikidiff' );
- wfProfileOut( __METHOD__ . '-php_wikidiff.so' );
- }
- elseif ( $wgExternalDiffEngine == 'wikidiff2' && !function_exists( 'wikidiff2_do_diff' ) ) {
- wfProfileIn( __METHOD__ . '-php_wikidiff2.so' );
- wfDl( 'wikidiff2' );
- wfProfileOut( __METHOD__ . '-php_wikidiff2.so' );
- }
- }
-
- /**
* Generate a diff, no caching.
*
* This implementation uses generateTextDiffBody() to generate a diff based on the default
@@ -797,8 +761,6 @@ class DifferenceEngine extends ContextSource {
$otext = str_replace( "\r\n", "\n", $otext );
$ntext = str_replace( "\r\n", "\n", $ntext );
- $this->initDiffEngines();
-
if ( $wgExternalDiffEngine == 'wikidiff' && function_exists( 'wikidiff_do_diff' ) ) {
# For historical reasons, external diff engine expects
# input text to be HTML-escaped already
@@ -893,7 +855,9 @@ class DifferenceEngine extends ContextSource {
}
function localiseLineNumbersCb( $matches ) {
- if ( $matches[1] === '1' && $this->mReducedLineNumbers ) return '';
+ if ( $matches[1] === '1' && $this->mReducedLineNumbers ) {
+ return '';
+ }
return $this->msg( 'lineno' )->numParams( $matches[1] )->escaped();
}
@@ -1024,11 +988,13 @@ class DifferenceEngine extends ContextSource {
$colspan = 1;
$multiColspan = 2;
}
- $header .= "
- <tr style='vertical-align: top;'>
- <td colspan='$colspan' class='diff-otitle'>{$otitle}</td>
- <td colspan='$colspan' class='diff-ntitle'>{$ntitle}</td>
- </tr>";
+ if ( $otitle || $ntitle ) {
+ $header .= "
+ <tr style='vertical-align: top;'>
+ <td colspan='$colspan' class='diff-otitle'>{$otitle}</td>
+ <td colspan='$colspan' class='diff-ntitle'>{$ntitle}</td>
+ </tr>";
+ }
}
if ( $multi != '' ) {
@@ -1169,6 +1135,25 @@ class DifferenceEngine extends ContextSource {
$this->mOldPage = $this->mOldRev->getTitle();
}
+ // Load tags information for both revisions
+ $dbr = wfGetDB( DB_SLAVE );
+ if ( $this->mOldid !== false ) {
+ $this->mOldTags = $dbr->selectField(
+ 'tag_summary',
+ 'ts_tags',
+ array( 'ts_rev_id' => $this->mOldid ),
+ __METHOD__
+ );
+ } else {
+ $this->mOldTags = false;
+ }
+ $this->mNewTags = $dbr->selectField(
+ 'tag_summary',
+ 'ts_tags',
+ array( 'ts_rev_id' => $this->mNewid ),
+ __METHOD__
+ );
+
return true;
}
@@ -1180,26 +1165,29 @@ class DifferenceEngine extends ContextSource {
function loadText() {
if ( $this->mTextLoaded == 2 ) {
return true;
- } else {
- // Whether it succeeds or fails, we don't want to try again
- $this->mTextLoaded = 2;
}
+ // Whether it succeeds or fails, we don't want to try again
+ $this->mTextLoaded = 2;
+
if ( !$this->loadRevisionData() ) {
return false;
}
+
if ( $this->mOldRev ) {
$this->mOldContent = $this->mOldRev->getContent( Revision::FOR_THIS_USER, $this->getUser() );
if ( $this->mOldContent === null ) {
return false;
}
}
+
if ( $this->mNewRev ) {
$this->mNewContent = $this->mNewRev->getContent( Revision::FOR_THIS_USER, $this->getUser() );
if ( $this->mNewContent === null ) {
return false;
}
}
+
return true;
}
@@ -1211,13 +1199,16 @@ class DifferenceEngine extends ContextSource {
function loadNewText() {
if ( $this->mTextLoaded >= 1 ) {
return true;
- } else {
- $this->mTextLoaded = 1;
}
+
+ $this->mTextLoaded = 1;
+
if ( !$this->loadRevisionData() ) {
return false;
}
+
$this->mNewContent = $this->mNewRev->getContent( Revision::FOR_THIS_USER, $this->getUser() );
+
return true;
}
}
diff --git a/includes/extauth/Hardcoded.php b/includes/extauth/Hardcoded.php
deleted file mode 100644
index dfb46742..00000000
--- a/includes/extauth/Hardcoded.php
+++ /dev/null
@@ -1,84 +0,0 @@
-<?php
-/**
- * External authentication with hardcoded user names and passwords
- *
- * Copyright © 2009 Aryeh Gregor
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- */
-
-/**
- * This class supports external authentication from a literal array dumped in
- * LocalSettings.php. It's mostly useful for testing. Example configuration:
- *
- * $wgExternalAuthType = 'ExternalUser_Hardcoded';
- * $wgExternalAuthConf = array(
- * 'Bob Smith' => array(
- * 'password' => 'literal string',
- * 'emailaddress' => 'bob@example.com',
- * ),
- * );
- *
- * Multiple names may be provided. The keys of the inner arrays can be either
- * 'password', or the name of any preference.
- *
- * @ingroup ExternalUser
- */
-class ExternalUser_Hardcoded extends ExternalUser {
- private $mName;
-
- protected function initFromName( $name ) {
- global $wgExternalAuthConf;
-
- if ( isset( $wgExternalAuthConf[$name] ) ) {
- $this->mName = $name;
- return true;
- }
- return false;
- }
-
- protected function initFromId( $id ) {
- return $this->initFromName( $id );
- }
-
- public function getId() {
- return $this->mName;
- }
-
- public function getName() {
- return $this->mName;
- }
-
- public function authenticate( $password ) {
- global $wgExternalAuthConf;
-
- return isset( $wgExternalAuthConf[$this->mName]['password'] )
- && $wgExternalAuthConf[$this->mName]['password'] == $password;
- }
-
- public function getPref( $pref ) {
- global $wgExternalAuthConf;
-
- if ( isset( $wgExternalAuthConf[$this->mName][$pref] ) ) {
- return $wgExternalAuthConf[$this->mName][$pref];
- }
- return null;
- }
-
- # TODO: Implement setPref() via regex on LocalSettings. (Just kidding.)
-}
diff --git a/includes/extauth/MediaWiki.php b/includes/extauth/MediaWiki.php
deleted file mode 100644
index c7f6a204..00000000
--- a/includes/extauth/MediaWiki.php
+++ /dev/null
@@ -1,168 +0,0 @@
-<?php
-/**
- * External authentication with external MediaWiki database.
- *
- * Copyright © 2009 Aryeh Gregor
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- */
-
-/**
- * This class supports authentication against an external MediaWiki database,
- * probably any version back to 1.5 or something. Example configuration:
- *
- * $wgExternalAuthType = 'ExternalUser_MediaWiki';
- * $wgExternalAuthConf = array(
- * 'DBtype' => 'mysql',
- * 'DBserver' => 'localhost',
- * 'DBname' => 'wikidb',
- * 'DBuser' => 'quasit',
- * 'DBpassword' => 'a5Cr:yf9u-6[{`g',
- * '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
- * 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
- * should be unlikely.
- *
- * @ingroup ExternalUser
- */
-class ExternalUser_MediaWiki extends ExternalUser {
- private $mRow;
-
- /**
- * @var DatabaseBase
- */
- private $mDb;
-
- /**
- * @param $name string
- * @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
- # good enough.
- $name = User::getCanonicalName( $name, 'usable' );
-
- if ( !is_string( $name ) ) {
- return false;
- }
-
- return $this->initFromCond( array( 'user_name' => $name ) );
- }
-
- /**
- * @param $id int
- * @return bool
- */
- protected function initFromId( $id ) {
- return $this->initFromCond( array( 'user_id' => $id ) );
- }
-
- /**
- * @param $cond array
- * @return bool
- */
- private function initFromCond( $cond ) {
- global $wgExternalAuthConf;
-
- $this->mDb = DatabaseBase::factory( $wgExternalAuthConf['DBtype'],
- array(
- 'host' => $wgExternalAuthConf['DBserver'],
- 'user' => $wgExternalAuthConf['DBuser'],
- 'password' => $wgExternalAuthConf['DBpassword'],
- 'dbname' => $wgExternalAuthConf['DBname'],
- 'tablePrefix' => $wgExternalAuthConf['DBprefix'],
- )
- );
-
- $row = $this->mDb->selectRow(
- 'user',
- array(
- 'user_name', 'user_id', 'user_password', 'user_email',
- 'user_email_authenticated'
- ),
- $cond,
- __METHOD__
- );
- if ( !$row ) {
- return false;
- }
- $this->mRow = $row;
-
- return true;
- }
-
- # TODO: Implement initFromCookie().
-
- public function getId() {
- return $this->mRow->user_id;
- }
-
- /**
- * @return string
- */
- public function getName() {
- return $this->mRow->user_name;
- }
-
- public function authenticate( $password ) {
- # 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 );
- }
-
- public function getPref( $pref ) {
- # @todo FIXME: Return other prefs too. Lots of global-riddled code that does
- # this normally.
- if ( $pref === 'emailaddress'
- && $this->row->user_email_authenticated !== null ) {
- return $this->mRow->user_email;
- }
- return null;
- }
-
- /**
- * @return array
- */
- public function getGroups() {
- # @todo FIXME: Untested.
- $groups = array();
- $res = $this->mDb->select(
- 'user_groups',
- 'ug_group',
- array( 'ug_user' => $this->mRow->user_id ),
- __METHOD__
- );
- foreach ( $res as $row ) {
- $groups[] = $row->ug_group;
- }
- return $groups;
- }
-
- # TODO: Implement setPref().
-}
diff --git a/includes/extauth/vB.php b/includes/extauth/vB.php
deleted file mode 100644
index 0565a2e3..00000000
--- a/includes/extauth/vB.php
+++ /dev/null
@@ -1,146 +0,0 @@
-<?php
-/**
- * External authentication with a vBulletin database.
- *
- * Copyright © 2009 Aryeh Gregor
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- */
-
-/**
- * This class supports the proprietary vBulletin forum system
- * <http://www.vbulletin.com>, versions 3.5 and up. It calls no functions or
- * code, only reads from the database. Example lines to put in
- * LocalSettings.php:
- *
- * $wgExternalAuthType = 'ExternalUser_vB';
- * $wgExternalAuthConf = array(
- * 'server' => 'localhost',
- * 'username' => 'forum',
- * 'password' => 'udE,jSqDJ<""p=fI.K9',
- * 'dbname' => 'forum',
- * 'tablePrefix' => '',
- * 'cookieprefix' => 'bb'
- * );
- *
- * @ingroup ExternalUser
- */
-class ExternalUser_vB extends ExternalUser {
- private $mRow;
-
- protected function initFromName( $name ) {
- return $this->initFromCond( array( 'username' => $name ) );
- }
-
- protected function initFromId( $id ) {
- return $this->initFromCond( array( 'userid' => $id ) );
- }
-
- protected function initFromCookie() {
- # Try using the session table. It will only have a row if the user has
- # an active session, so it might not always work, but it's a lot easier
- # than trying to convince PHP to give us vB's $_SESSION.
- global $wgExternalAuthConf, $wgRequest;
- if ( !isset( $wgExternalAuthConf['cookieprefix'] ) ) {
- $prefix = 'bb';
- } else {
- $prefix = $wgExternalAuthConf['cookieprefix'];
- }
- if ( $wgRequest->getCookie( 'sessionhash', $prefix ) === null ) {
- return false;
- }
-
- $db = $this->getDb();
-
- $row = $db->selectRow(
- array( 'session', 'user' ),
- $this->getFields(),
- array(
- 'session.userid = user.userid',
- 'sessionhash' => $wgRequest->getCookie( 'sessionhash', $prefix ),
- ),
- __METHOD__
- );
- if ( !$row ) {
- return false;
- }
- $this->mRow = $row;
-
- return true;
- }
-
- private function initFromCond( $cond ) {
- $db = $this->getDb();
-
- $row = $db->selectRow(
- 'user',
- $this->getFields(),
- $cond,
- __METHOD__
- );
- if ( !$row ) {
- return false;
- }
- $this->mRow = $row;
-
- return true;
- }
-
- private function getDb() {
- global $wgExternalAuthConf;
- return DatabaseBase::factory( 'mysql',
- array(
- 'host' => $wgExternalAuthConf['server'],
- 'user' => $wgExternalAuthConf['username'],
- 'password' => $wgExternalAuthConf['password'],
- 'dbname' => $wgExternalAuthConf['dbname'],
- 'tablePrefix' => $wgExternalAuthConf['tablePrefix'],
- )
- );
- }
-
- private function getFields() {
- return array( 'user.userid', 'username', 'password', 'salt', 'email',
- 'usergroupid', 'membergroupids' );
- }
-
- public function getId() { return $this->mRow->userid; }
- public function getName() { return $this->mRow->username; }
-
- public function authenticate( $password ) {
- # vBulletin seemingly strips whitespace from passwords
- $password = trim( $password );
- return $this->mRow->password == md5( md5( $password )
- . $this->mRow->salt );
- }
-
- public function getPref( $pref ) {
- if ( $pref == 'emailaddress' && $this->mRow->email ) {
- # TODO: only return if validated?
- return $this->mRow->email;
- }
- return null;
- }
-
- public function getGroups() {
- $groups = array( $this->mRow->usergroupid );
- $groups = array_merge( $groups, explode( ',', $this->mRow->membergroupids ) );
- $groups = array_unique( $groups );
- return $groups;
- }
-}
diff --git a/includes/externalstore/ExternalStore.php b/includes/externalstore/ExternalStore.php
index 4ca193d4..462b0b90 100644
--- a/includes/externalstore/ExternalStore.php
+++ b/includes/externalstore/ExternalStore.php
@@ -60,7 +60,7 @@ class ExternalStore {
$class = 'ExternalStore' . ucfirst( $proto );
// Any custom modules should be added to $wgAutoLoadClasses for on-demand loading
- return MWInit::classExists( $class ) ? new $class( $params ) : false;
+ return class_exists( $class ) ? new $class( $params ) : false;
}
/**
@@ -91,6 +91,39 @@ class ExternalStore {
}
/**
+ * Fetch data from multiple URLs with a minimum of round trips
+ *
+ * @param array $urls The URLs of the text to get
+ * @return array Map from url to its data. Data is either string when found
+ * or false on failure.
+ */
+ public static function batchFetchFromURLs( array $urls ) {
+ $batches = array();
+ foreach ( $urls as $url ) {
+ $scheme = parse_url( $url, PHP_URL_SCHEME );
+ if ( $scheme ) {
+ $batches[$scheme][] = $url;
+ }
+ }
+ $retval = array();
+ foreach ( $batches as $proto => $batchedUrls ) {
+ $store = self::getStoreObject( $proto );
+ if ( $store === false ) {
+ continue;
+ }
+ $retval += $store->batchFetchFromURLs( $batchedUrls );
+ }
+ // invalid, not found, db dead, etc.
+ $missing = array_diff( $urls, array_keys( $retval ) );
+ if ( $missing ) {
+ foreach ( $missing as $url ) {
+ $retval[$url] = false;
+ }
+ }
+ return $retval;
+ }
+
+ /**
* 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.
@@ -123,9 +156,10 @@ class ExternalStore {
/**
* 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.
+ * itself. It also fails-over to the next possible clusters
+ * provided by $wgDefaultExternalStore.
*
- * @param $data string
+ * @param string $data
* @param array $params Associative array of ExternalStoreMedium parameters
* @return string|bool The URL of the stored data item, or false on error
* @throws MWException
@@ -133,8 +167,23 @@ class ExternalStore {
public static function insertToDefault( $data, array $params = array() ) {
global $wgDefaultExternalStore;
+ return self::insertWithFallback( (array)$wgDefaultExternalStore, $data, $params );
+ }
+
+ /**
+ * 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
+ * as provided in the first parameter.
+ *
+ * @param array $tryStores refer to $wgDefaultExternalStore
+ * @param string $data
+ * @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 insertWithFallback( array $tryStores, $data, array $params = array() ) {
$error = false;
- $tryStores = (array)$wgDefaultExternalStore;
while ( count( $tryStores ) > 0 ) {
$index = mt_rand( 0, count( $tryStores ) - 1 );
$storeUrl = $tryStores[$index];
diff --git a/includes/externalstore/ExternalStoreDB.php b/includes/externalstore/ExternalStoreDB.php
index 196e7f2c..46a89379 100644
--- a/includes/externalstore/ExternalStoreDB.php
+++ b/includes/externalstore/ExternalStoreDB.php
@@ -30,21 +30,13 @@
*/
class ExternalStoreDB extends ExternalStoreMedium {
/**
- * The URL returned is of the form of the form DB://cluster/id
+ * The provided URL is in the form of 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;
- }
-
+ list( $cluster, $id, $itemID ) = $this->parseURL( $url );
$ret =& $this->fetchBlob( $cluster, $id, $itemID );
if ( $itemID !== false && $ret !== false ) {
@@ -54,6 +46,41 @@ class ExternalStoreDB extends ExternalStoreMedium {
}
/**
+ * Fetch data from given external store URLs.
+ * The provided URLs are in the form of DB://cluster/id
+ * or DB://cluster/id/itemid for concatened storage.
+ *
+ * @param array $urls An array of external store URLs
+ * @return array A map from url to stored content. Failed results
+ * are not represented.
+ */
+ public function batchFetchFromURLs( array $urls ) {
+ $batched = $inverseUrlMap = array();
+ foreach ( $urls as $url ) {
+ list( $cluster, $id, $itemID ) = $this->parseURL( $url );
+ $batched[$cluster][$id][] = $itemID;
+ // false $itemID gets cast to int, but should be ok
+ // since we do === from the $itemID in $batched
+ $inverseUrlMap[$cluster][$id][$itemID] = $url;
+ }
+ $ret = array();
+ foreach ( $batched as $cluster => $batchByCluster ) {
+ $res = $this->batchFetchBlobs( $cluster, $batchByCluster );
+ foreach ( $res as $id => $blob ) {
+ foreach ( $batchByCluster[$id] as $itemID ) {
+ $url = $inverseUrlMap[$cluster][$id][$itemID];
+ if ( $itemID === false ) {
+ $ret[$url] = $blob;
+ } else {
+ $ret[$url] = $blob->getItem( $itemID );
+ }
+ }
+ }
+ }
+ return $ret;
+ }
+
+ /**
* @see ExternalStoreMedium::store()
*/
public function store( $cluster, $data ) {
@@ -64,7 +91,7 @@ class ExternalStoreDB extends ExternalStoreMedium {
__METHOD__ );
$id = $dbw->insertId();
if ( !$id ) {
- throw new MWException( __METHOD__.': no insert ID' );
+ throw new MWException( __METHOD__ . ': no insert ID' );
}
if ( $dbw->getFlag( DBO_TRX ) ) {
$dbw->commit( __METHOD__ );
@@ -152,25 +179,31 @@ class ExternalStoreDB extends ExternalStoreMedium {
static $externalBlobCache = array();
$cacheID = ( $itemID === false ) ? "$cluster/$id" : "$cluster/$id/";
- if( isset( $externalBlobCache[$cacheID] ) ) {
- wfDebugLog( 'ExternalStoreDB-cache', "ExternalStoreDB::fetchBlob cache hit on $cacheID\n" );
+ if ( isset( $externalBlobCache[$cacheID] ) ) {
+ wfDebugLog( 'ExternalStoreDB-cache',
+ "ExternalStoreDB::fetchBlob cache hit on $cacheID\n" );
return $externalBlobCache[$cacheID];
}
- wfDebugLog( 'ExternalStoreDB-cache', "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__ );
+ $ret = $dbr->selectField( $this->getTable( $dbr ),
+ 'blob_text', array( 'blob_id' => $id ), __METHOD__ );
if ( $ret === false ) {
- wfDebugLog( 'ExternalStoreDB', "ExternalStoreDB::fetchBlob master fallback on $cacheID\n" );
+ wfDebugLog( 'ExternalStoreDB',
+ "ExternalStoreDB::fetchBlob master fallback on $cacheID\n" );
// Try the master
$dbw =& $this->getMaster( $cluster );
- $ret = $dbw->selectField( $this->getTable( $dbw ), 'blob_text', array( 'blob_id' => $id ), __METHOD__ );
- if( $ret === false) {
- wfDebugLog( 'ExternalStoreDB', "ExternalStoreDB::fetchBlob master failed to find $cacheID\n" );
+ $ret = $dbw->selectField( $this->getTable( $dbw ),
+ 'blob_text', array( 'blob_id' => $id ), __METHOD__ );
+ if ( $ret === false ) {
+ wfDebugLog( 'ExternalStoreDB',
+ "ExternalStoreDB::fetchBlob master failed to find $cacheID\n" );
}
}
- if( $itemID !== false && $ret !== false ) {
+ if ( $itemID !== false && $ret !== false ) {
// Unserialise object; caller extracts item
$ret = unserialize( $ret );
}
@@ -178,4 +211,73 @@ class ExternalStoreDB extends ExternalStoreMedium {
$externalBlobCache = array( $cacheID => &$ret );
return $ret;
}
+
+ /**
+ * Fetch multiple blob items out of the database
+ *
+ * @param string $cluster A cluster name valid for use with LBFactory
+ * @param array $ids A map from the blob_id's to look for to the requested itemIDs in the blobs
+ * @return array A map from the blob_id's requested to their content. Unlocated ids are not represented
+ */
+ function batchFetchBlobs( $cluster, array $ids ) {
+ $dbr = $this->getSlave( $cluster );
+ $res = $dbr->select( $this->getTable( $dbr ),
+ array( 'blob_id', 'blob_text' ), array( 'blob_id' => array_keys( $ids ) ), __METHOD__ );
+ $ret = array();
+ if ( $res !== false ) {
+ $this->mergeBatchResult( $ret, $ids, $res );
+ }
+ if ( $ids ) {
+ wfDebugLog( __CLASS__, __METHOD__ .
+ " master fallback on '$cluster' for: " .
+ implode( ',', array_keys( $ids ) ) . "\n" );
+ // Try the master
+ $dbw = $this->getMaster( $cluster );
+ $res = $dbw->select( $this->getTable( $dbr ),
+ array( 'blob_id', 'blob_text' ),
+ array( 'blob_id' => array_keys( $ids ) ),
+ __METHOD__ );
+ if ( $res === false ) {
+ wfDebugLog( __CLASS__, __METHOD__ . " master failed on '$cluster'\n" );
+ } else {
+ $this->mergeBatchResult( $ret, $ids, $res );
+ }
+ }
+ if ( $ids ) {
+ wfDebugLog( __CLASS__, __METHOD__ .
+ " master on '$cluster' failed locating items: " .
+ implode( ',', array_keys( $ids ) ) . "\n" );
+ }
+ return $ret;
+ }
+
+ /**
+ * Helper function for self::batchFetchBlobs for merging master/slave results
+ * @param array &$ret Current self::batchFetchBlobs return value
+ * @param array &$ids Map from blob_id to requested itemIDs
+ * @param mixed $res DB result from DatabaseBase::select
+ */
+ private function mergeBatchResult( array &$ret, array &$ids, $res ) {
+ foreach ( $res as $row ) {
+ $id = $row->blob_id;
+ $itemIDs = $ids[$id];
+ unset( $ids[$id] ); // to track if everything is found
+ if ( count( $itemIDs ) === 1 && reset( $itemIDs ) === false ) {
+ // single result stored per blob
+ $ret[$id] = $row->blob_text;
+ } else {
+ // multi result stored per blob
+ $ret[$id] = unserialize( $row->blob_text );
+ }
+ }
+ }
+
+ protected function parseURL( $url ) {
+ $path = explode( '/', $url );
+ return array(
+ $path[2], // cluster
+ $path[3], // id
+ isset( $path[4] ) ? $path[4] : false // itemID
+ );
+ }
}
diff --git a/includes/externalstore/ExternalStoreMedium.php b/includes/externalstore/ExternalStoreMedium.php
index 41af7d87..02bdcb51 100644
--- a/includes/externalstore/ExternalStoreMedium.php
+++ b/includes/externalstore/ExternalStoreMedium.php
@@ -49,6 +49,25 @@ abstract class ExternalStoreMedium {
abstract public function fetchFromURL( $url );
/**
+ * Fetch data from given external store URLs.
+ *
+ * @param array $urls A list of external store URLs
+ * @return array Map from the url to the text stored. Unfound data is not represented
+ */
+ public function batchFetchFromURLs( array $urls ) {
+ $retval = array();
+ foreach ( $urls as $url ) {
+ $data = $this->fetchFromURL( $url );
+ // Dont return when false to allow for simpler implementations.
+ // errored urls are handled in ExternalStore::batchFetchFromURLs
+ if ( $data !== false ) {
+ $retval[$urls] = $data;
+ }
+ }
+ return $retval;
+ }
+
+ /**
* Insert a data item into a given location
*
* @param string $location the location name
diff --git a/includes/externalstore/ExternalStoreMwstore.php b/includes/externalstore/ExternalStoreMwstore.php
index 0911cca1..aa486796 100644
--- a/includes/externalstore/ExternalStoreMwstore.php
+++ b/includes/externalstore/ExternalStoreMwstore.php
@@ -47,6 +47,29 @@ class ExternalStoreMwstore extends ExternalStoreMedium {
}
/**
+ * Fetch data from given external store URLs.
+ * The URL returned is of the form of the form mwstore://backend/container/wiki/id
+ *
+ * @param array $urls An array of external store URLs
+ * @return array A map from url to stored content. Failed results are not represented.
+ */
+ public function batchFetchFromURLs( array $urls ) {
+ $pathsByBackend = array();
+ foreach ( $urls as $url ) {
+ $be = FileBackendGroup::singleton()->backendFromPath( $url );
+ if ( $be instanceof FileBackend ) {
+ $pathsByBackend[$be->getName()][] = $url;
+ }
+ }
+ $blobs = array();
+ foreach ( $pathsByBackend as $backendName => $paths ) {
+ $be = FileBackendGroup::get( $backendName );
+ $blobs = $blobs + $be->getFileContentsMulti( array( 'srcs' => $paths ) );
+ }
+ return $blobs;
+ }
+
+ /**
* @see ExternalStoreMedium::store()
*/
public function store( $backend, $data ) {
diff --git a/includes/filebackend/FSFile.php b/includes/filebackend/FSFile.php
index 7d0dbd52..8f0a1334 100644
--- a/includes/filebackend/FSFile.php
+++ b/includes/filebackend/FSFile.php
@@ -28,7 +28,7 @@
*/
class FSFile {
protected $path; // path to file
- private $sha1Base36 = null; // File Sha1Base36
+ protected $sha1Base36; // file SHA-1 in base 36
/**
* Sets up the file object
@@ -98,7 +98,7 @@ class FSFile {
* Get an associative array containing information about
* a file with the given storage path.
*
- * @param $ext Mixed: the file extension, or true to extract it from the filename.
+ * @param Mixed $ext: the file extension, or true to extract it from the filename.
* Set it to false to ignore the extension.
*
* @return array
@@ -171,7 +171,7 @@ class FSFile {
/**
* Exract image size information
*
- * @param $gis array
+ * @param array $gis
* @return Array
*/
protected function extractImageSizeInfo( array $gis ) {
@@ -194,7 +194,7 @@ 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
+ * @param bool $recache
* @return bool|string False on failure
*/
public function getSha1Base36( $recache = false ) {
@@ -220,7 +220,7 @@ class FSFile {
/**
* Get the final file extension from a file system path
*
- * @param $path string
+ * @param string $path
* @return string
*/
public static function extensionFromPath( $path ) {
@@ -232,9 +232,8 @@ class FSFile {
* Get an associative array containing information about a file in the local filesystem.
*
* @param string $path absolute local filesystem path
- * @param $ext Mixed: the file extension, or true to extract it from the filename.
+ * @param Mixed $ext: the file extension, or true to extract it from the filename.
* Set it to false to ignore the extension.
- *
* @return array
*/
public static function getPropsFromPath( $path, $ext = true ) {
@@ -249,19 +248,11 @@ class FSFile {
* 160 log 2 / log 36 = 30.95, so the 160-bit hash fills 31 digits in base 36
* fairly neatly.
*
- * @param $path string
- * @param $recache bool
- *
+ * @param string $path
* @return bool|string False on failure
*/
- 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];
+ public static function getSha1Base36FromPath( $path ) {
+ $fsFile = new self( $path );
+ return $fsFile->getSha1Base36();
}
}
diff --git a/includes/filebackend/FSFileBackend.php b/includes/filebackend/FSFileBackend.php
index c9769989..6d642162 100644
--- a/includes/filebackend/FSFileBackend.php
+++ b/includes/filebackend/FSFileBackend.php
@@ -82,12 +82,6 @@ class FSFileBackend extends FileBackendStore {
}
}
- /**
- * @see FileBackendStore::resolveContainerPath()
- * @param $container string
- * @param $relStoragePath string
- * @return null|string
- */
protected function resolveContainerPath( $container, $relStoragePath ) {
// Check that container has a root directory
if ( isset( $this->containerPaths[$container] ) || isset( $this->basePath ) ) {
@@ -121,8 +115,8 @@ class FSFileBackend extends FileBackendStore {
* Given the short (unresolved) and full (resolved) name of
* a container, return the file system path of the container.
*
- * @param $shortCont string
- * @param $fullCont string
+ * @param string $shortCont
+ * @param string $fullCont
* @return string|null
*/
protected function containerFSRoot( $shortCont, $fullCont ) {
@@ -153,10 +147,6 @@ class FSFileBackend extends FileBackendStore {
return $fsPath;
}
- /**
- * @see FileBackendStore::isPathUsableInternal()
- * @return bool
- */
public function isPathUsableInternal( $storagePath ) {
$fsPath = $this->resolveToFSPath( $storagePath );
if ( $fsPath === null ) {
@@ -178,10 +168,6 @@ class FSFileBackend extends FileBackendStore {
return $ok;
}
- /**
- * @see FileBackendStore::doCreateInternal()
- * @return Status
- */
protected function doCreateInternal( array $params ) {
$status = Status::newGood();
@@ -235,10 +221,6 @@ class FSFileBackend extends FileBackendStore {
}
}
- /**
- * @see FileBackendStore::doStoreInternal()
- * @return Status
- */
protected function doStoreInternal( array $params ) {
$status = Status::newGood();
@@ -284,10 +266,6 @@ class FSFileBackend extends FileBackendStore {
}
}
- /**
- * @see FileBackendStore::doCopyInternal()
- * @return Status
- */
protected function doCopyInternal( array $params ) {
$status = Status::newGood();
@@ -319,7 +297,7 @@ class FSFileBackend extends FileBackendStore {
$status->value = new FSFileOpHandle( $this, $params, 'Copy', $cmd, $dest );
} else { // immediate write
$this->trapWarnings();
- $ok = copy( $source, $dest );
+ $ok = ( $source === $dest ) ? true : copy( $source, $dest );
$this->untrapWarnings();
// In some cases (at least over NFS), copy() returns true when it fails
if ( !$ok || ( filesize( $source ) !== filesize( $dest ) ) ) {
@@ -348,10 +326,6 @@ class FSFileBackend extends FileBackendStore {
}
}
- /**
- * @see FileBackendStore::doMoveInternal()
- * @return Status
- */
protected function doMoveInternal( array $params ) {
$status = Status::newGood();
@@ -383,7 +357,7 @@ class FSFileBackend extends FileBackendStore {
$status->value = new FSFileOpHandle( $this, $params, 'Move', $cmd );
} else { // immediate write
$this->trapWarnings();
- $ok = rename( $source, $dest );
+ $ok = ( $source === $dest ) ? true : rename( $source, $dest );
$this->untrapWarnings();
clearstatcache(); // file no longer at source
if ( !$ok ) {
@@ -405,10 +379,6 @@ class FSFileBackend extends FileBackendStore {
}
}
- /**
- * @see FileBackendStore::doDeleteInternal()
- * @return Status
- */
protected function doDeleteInternal( array $params ) {
$status = Status::newGood();
@@ -454,10 +424,6 @@ class FSFileBackend extends FileBackendStore {
}
}
- /**
- * @see FileBackendStore::doPrepareInternal()
- * @return Status
- */
protected function doPrepareInternal( $fullCont, $dirRel, array $params ) {
$status = Status::newGood();
list( , $shortCont, ) = FileBackend::splitStoragePath( $params['dir'] );
@@ -481,10 +447,6 @@ class FSFileBackend extends FileBackendStore {
return $status;
}
- /**
- * @see FileBackendStore::doSecureInternal()
- * @return Status
- */
protected function doSecureInternal( $fullCont, $dirRel, array $params ) {
$status = Status::newGood();
list( , $shortCont, ) = FileBackend::splitStoragePath( $params['dir'] );
@@ -512,10 +474,6 @@ class FSFileBackend extends FileBackendStore {
return $status;
}
- /**
- * @see FileBackendStore::doPublishInternal()
- * @return Status
- */
protected function doPublishInternal( $fullCont, $dirRel, array $params ) {
$status = Status::newGood();
list( , $shortCont, ) = FileBackend::splitStoragePath( $params['dir'] );
@@ -543,10 +501,6 @@ class FSFileBackend extends FileBackendStore {
return $status;
}
- /**
- * @see FileBackendStore::doCleanInternal()
- * @return Status
- */
protected function doCleanInternal( $fullCont, $dirRel, array $params ) {
$status = Status::newGood();
list( , $shortCont, ) = FileBackend::splitStoragePath( $params['dir'] );
@@ -560,10 +514,6 @@ class FSFileBackend extends FileBackendStore {
return $status;
}
- /**
- * @see FileBackendStore::doFileExists()
- * @return array|bool|null
- */
protected function doGetFileStat( array $params ) {
$source = $this->resolveToFSPath( $params['src'] );
if ( $source === null ) {
@@ -577,7 +527,7 @@ class FSFileBackend extends FileBackendStore {
if ( $stat ) {
return array(
'mtime' => wfTimestamp( TS_MW, $stat['mtime'] ),
- 'size' => $stat['size']
+ 'size' => $stat['size']
);
} elseif ( !$hadError ) {
return false; // file does not exist
@@ -593,10 +543,6 @@ class FSFileBackend extends FileBackendStore {
clearstatcache(); // clear the PHP file stat cache
}
- /**
- * @see FileBackendStore::doDirectoryExists()
- * @return bool|null
- */
protected function doDirectoryExists( $fullCont, $dirRel, array $params ) {
list( , $shortCont, ) = FileBackend::splitStoragePath( $params['dir'] );
$contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
@@ -647,10 +593,6 @@ class FSFileBackend extends FileBackendStore {
return new FSFileBackendFileList( $dir, $params );
}
- /**
- * @see FileBackendStore::doGetLocalReferenceMulti()
- * @return Array
- */
protected function doGetLocalReferenceMulti( array $params ) {
$fsFiles = array(); // (path => FSFile)
@@ -666,10 +608,6 @@ class FSFileBackend extends FileBackendStore {
return $fsFiles;
}
- /**
- * @see FileBackendStore::doGetLocalCopyMulti()
- * @return Array
- */
protected function doGetLocalCopyMulti( array $params ) {
$tmpFiles = array(); // (path => TempFSFile)
@@ -702,18 +640,10 @@ class FSFileBackend extends FileBackendStore {
return $tmpFiles;
}
- /**
- * @see FileBackendStore::directoriesAreVirtual()
- * @return bool
- */
protected function directoriesAreVirtual() {
return false;
}
- /**
- * @see FileBackendStore::doExecuteOpHandlesInternal()
- * @return Array List of corresponding Status objects
- */
protected function doExecuteOpHandlesInternal( array $fileOpHandles ) {
$statuses = array();
@@ -807,11 +737,12 @@ class FSFileBackend extends FileBackendStore {
}
/**
- * @param $errno integer
- * @param $errstr string
+ * @param integer $errno
+ * @param string $errstr
* @return bool
+ * @access private
*/
- private function handleWarning( $errno, $errstr ) {
+ public function handleWarning( $errno, $errstr ) {
wfDebugLog( 'FSFileBackend', $errstr ); // more detailed error logging
$this->hadWarningErrors[count( $this->hadWarningErrors ) - 1] = true;
return true; // suppress from PHP handler
@@ -826,13 +757,15 @@ class FSFileOpHandle extends FileBackendStoreOpHandle {
public $chmodPath; // string; file to chmod
/**
- * @param $backend
- * @param $params array
- * @param $call
- * @param $cmd
- * @param $chmodPath null
+ * @param FSFileBackend $backend
+ * @param array $params
+ * @param string $call
+ * @param string $cmd
+ * @param integer|null $chmodPath
*/
- public function __construct( $backend, array $params, $call, $cmd, $chmodPath = null ) {
+ public function __construct(
+ FSFileBackend $backend, array $params, $call, $cmd, $chmodPath = null
+ ) {
$this->backend = $backend;
$this->params = $params;
$this->call = $call;
@@ -858,11 +791,11 @@ abstract class FSFileBackendList implements Iterator {
/**
* @param string $dir file system directory
- * @param $params array
+ * @param array $params
*/
public function __construct( $dir, array $params ) {
$path = realpath( $dir ); // normalize
- if( $path === false ) {
+ if ( $path === false ) {
$path = $dir;
}
$this->suffixStart = strlen( $path ) + 1; // size of "path/to/dir/"
@@ -921,8 +854,8 @@ abstract class FSFileBackendList implements Iterator {
try {
$this->iter->next();
$this->filterViaNext();
- } catch ( UnexpectedValueException $e ) {
- $this->iter = null;
+ } catch ( UnexpectedValueException $e ) { // bad permissions? deleted?
+ throw new FileBackendError( "File iterator gave UnexpectedValueException." );
}
++$this->pos;
}
@@ -936,8 +869,8 @@ abstract class FSFileBackendList implements Iterator {
try {
$this->iter->rewind();
$this->filterViaNext();
- } catch ( UnexpectedValueException $e ) {
- $this->iter = null;
+ } catch ( UnexpectedValueException $e ) { // bad permissions? deleted?
+ throw new FileBackendError( "File iterator gave UnexpectedValueException." );
}
}
@@ -958,12 +891,12 @@ abstract class FSFileBackendList implements Iterator {
* Return only the relative path and normalize slashes to FileBackend-style.
* Uses the "real path" since the suffix is based upon that.
*
- * @param $path string
+ * @param string $path
* @return string
*/
protected function getRelPath( $dir ) {
$path = realpath( $dir );
- if( $path === false ) {
+ if ( $path === false ) {
$path = $dir;
}
return strtr( substr( $path, $this->suffixStart ), '\\', '/' );
diff --git a/includes/filebackend/FileBackend.php b/includes/filebackend/FileBackend.php
index f40b8c16..f586578b 100644
--- a/includes/filebackend/FileBackend.php
+++ b/includes/filebackend/FileBackend.php
@@ -1,7 +1,6 @@
<?php
/**
* @defgroup FileBackend File backend
- * @ingroup FileRepo
*
* File backend is used to interact with file storage systems,
* such as the local file system, NFS, or cloud storage systems.
@@ -95,7 +94,7 @@ abstract class FileBackend {
* Allowed values are "implicit", "explicit" and "off".
* - concurrency : How many file operations can be done in parallel.
*
- * @param $config Array
+ * @param array $config
* @throws MWException
*/
public function __construct( array $config ) {
@@ -191,7 +190,6 @@ abstract class FileBackend {
* 'content' => <string of new file contents>,
* 'overwrite' => <boolean>,
* 'overwriteSame' => <boolean>,
- * 'disposition' => <Content-Disposition header value>,
* 'headers' => <HTTP header name/value map> # since 1.21
* );
* @endcode
@@ -204,7 +202,6 @@ abstract class FileBackend {
* 'dst' => <storage path>,
* 'overwrite' => <boolean>,
* 'overwriteSame' => <boolean>,
- * 'disposition' => <Content-Disposition header value>,
* 'headers' => <HTTP header name/value map> # since 1.21
* )
* @endcode
@@ -218,7 +215,7 @@ abstract class FileBackend {
* 'overwrite' => <boolean>,
* 'overwriteSame' => <boolean>,
* 'ignoreMissingSource' => <boolean>, # since 1.21
- * 'disposition' => <Content-Disposition header value>
+ * 'headers' => <HTTP header name/value map> # since 1.21
* )
* @endcode
*
@@ -231,7 +228,7 @@ abstract class FileBackend {
* 'overwrite' => <boolean>,
* 'overwriteSame' => <boolean>,
* 'ignoreMissingSource' => <boolean>, # since 1.21
- * 'disposition' => <Content-Disposition header value>
+ * 'headers' => <HTTP header name/value map> # since 1.21
* )
* @endcode
*
@@ -249,7 +246,6 @@ abstract class FileBackend {
* array(
* 'op' => 'describe',
* 'src' => <storage path>,
- * 'disposition' => <Content-Disposition header value>,
* 'headers' => <HTTP header name/value map>
* )
* @endcode
@@ -265,19 +261,19 @@ abstract class FileBackend {
* - ignoreMissingSource : The operation will simply succeed and do
* nothing if the source file does not exist.
* - overwrite : Any destination file will be overwritten.
- * - 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 : If supplied, the backend will return a Content-Disposition
- * header when GETs/HEADs of the destination file are made.
- * 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.
+ * - overwriteSame : If a file already exists at the destination with the
+ * same contents, then do nothing to the destination file
+ * instead of giving an error. This does not compare headers.
+ * This option is ignored if 'overwrite' is already provided.
+ * - headers : If supplied, the result of merging these headers with any
+ * existing source file headers (replacing conflicting ones)
+ * will be set as the destination file headers. Headers are
+ * deleted if their value is set to the empty string. When a
+ * file has headers they are included in responses to GET and
+ * HEAD requests to the backing store for that file.
+ * Header values should be no larger than 255 bytes, except for
+ * Content-Disposition. The system might ignore or truncate any
+ * headers that are too long to store (exact limits will vary).
* Backends that don't support metadata ignore this. (since 1.21)
*
* $opts is an associative of boolean flags, including:
@@ -318,9 +314,17 @@ abstract class FileBackend {
if ( empty( $opts['bypassReadOnly'] ) && $this->isReadOnly() ) {
return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly );
}
+ if ( !count( $ops ) ) {
+ return Status::newGood(); // nothing to do
+ }
if ( empty( $opts['force'] ) ) { // sanity
unset( $opts['nonLocking'] );
}
+ foreach ( $ops as &$op ) {
+ if ( isset( $op['disposition'] ) ) { // b/c (MW 1.20)
+ $op['headers']['Content-Disposition'] = $op['disposition'];
+ }
+ }
$scope = $this->getScopedPHPBehaviorForOps(); // try to ignore client aborts
return $this->doOperationsInternal( $ops, $opts );
}
@@ -452,7 +456,6 @@ abstract class FileBackend {
* 'op' => 'create',
* 'dst' => <storage path>,
* 'content' => <string of new file contents>,
- * 'disposition' => <Content-Disposition header value>,
* 'headers' => <HTTP header name/value map> # since 1.21
* )
* @endcode
@@ -463,7 +466,6 @@ abstract class FileBackend {
* 'op' => 'store',
* 'src' => <file system path>,
* 'dst' => <storage path>,
- * 'disposition' => <Content-Disposition header value>,
* 'headers' => <HTTP header name/value map> # since 1.21
* )
* @endcode
@@ -475,7 +477,7 @@ abstract class FileBackend {
* 'src' => <storage path>,
* 'dst' => <storage path>,
* 'ignoreMissingSource' => <boolean>, # since 1.21
- * 'disposition' => <Content-Disposition header value>
+ * 'headers' => <HTTP header name/value map> # since 1.21
* )
* @endcode
*
@@ -486,7 +488,7 @@ abstract class FileBackend {
* 'src' => <storage path>,
* 'dst' => <storage path>,
* 'ignoreMissingSource' => <boolean>, # since 1.21
- * 'disposition' => <Content-Disposition header value>
+ * 'headers' => <HTTP header name/value map> # since 1.21
* )
* @endcode
*
@@ -504,7 +506,6 @@ abstract class FileBackend {
* array(
* 'op' => 'describe',
* 'src' => <storage path>,
- * 'disposition' => <Content-Disposition header value>,
* 'headers' => <HTTP header name/value map>
* )
* @endcode
@@ -519,13 +520,11 @@ abstract class FileBackend {
* @par Boolean flags for operations (operation-specific):
* - ignoreMissingSource : The operation will simply succeed and do
* nothing if the source file does not exist.
- * - disposition : When supplied, the backend will add 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).
* - 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.
+ * Content-Disposition headers can be longer, though the system
+ * might ignore or truncate ones that are too long to store.
* 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.
@@ -549,8 +548,14 @@ abstract class FileBackend {
if ( empty( $opts['bypassReadOnly'] ) && $this->isReadOnly() ) {
return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly );
}
+ if ( !count( $ops ) ) {
+ return Status::newGood(); // nothing to do
+ }
foreach ( $ops as &$op ) {
$op['overwrite'] = true; // avoids RTTs in key/value stores
+ if ( isset( $op['disposition'] ) ) { // b/c (MW 1.20)
+ $op['headers']['Content-Disposition'] = $op['disposition'];
+ }
}
$scope = $this->getScopedPHPBehaviorForOps(); // try to ignore client aborts
return $this->doQuickOperationsInternal( $ops );
@@ -683,6 +688,8 @@ abstract class FileBackend {
* The 'noAccess' and 'noListing' parameters works the same as in secure(),
* 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.
+ * However, setting them is not guaranteed to actually do anything.
+ * Additional server configuration may be needed to achieve the desired effect.
*
* @param array $params
* $params include:
@@ -710,7 +717,9 @@ abstract class FileBackend {
* the container it belongs to. FS backends might add .htaccess
* files whereas key/value store backends might revoke container
* access to the storage user representing end-users in web requests.
- * This is not guaranteed to actually do anything.
+ *
+ * This is not guaranteed to actually make files or listings publically hidden.
+ * Additional server configuration may be needed to achieve the desired effect.
*
* @param array $params
* $params include:
@@ -740,6 +749,9 @@ abstract class FileBackend {
* access to the storage user representing end-users in web requests.
* This essentially can undo the result of secure() calls.
*
+ * This is not guaranteed to actually make files or listings publically viewable.
+ * Additional server configuration may be needed to achieve the desired effect.
+ *
* @param array $params
* $params include:
* - dir : storage directory
@@ -797,7 +809,9 @@ abstract class FileBackend {
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 new ScopedCallback( function() use ( $old ) {
+ ignore_user_abort( $old );
+ } );
}
return null;
}
@@ -1029,7 +1043,7 @@ abstract class FileBackend {
*
* Storage backends with eventual consistency might return stale data.
*
- * @param $params array
+ * @param array $params
* $params include:
* - dir : storage directory
* @return bool|null Returns null on failure
@@ -1047,7 +1061,9 @@ abstract class FileBackend {
*
* Storage backends with eventual consistency might return stale data.
*
- * @param $params array
+ * Failures during iteration can result in FileBackendError exceptions (since 1.22).
+ *
+ * @param array $params
* $params include:
* - dir : storage directory
* - topOnly : only return direct child dirs of the directory
@@ -1062,7 +1078,9 @@ abstract class FileBackend {
*
* Storage backends with eventual consistency might return stale data.
*
- * @param $params array
+ * Failures during iteration can result in FileBackendError exceptions (since 1.22).
+ *
+ * @param array $params
* $params include:
* - dir : storage directory
* @return Traversable|Array|null Returns null on failure
@@ -1082,10 +1100,13 @@ abstract class FileBackend {
*
* Storage backends with eventual consistency might return stale data.
*
- * @param $params array
+ * Failures during iteration can result in FileBackendError exceptions (since 1.22).
+ *
+ * @param array $params
* $params include:
- * - dir : storage directory
- * - topOnly : only return direct child files of the directory (since 1.20)
+ * - dir : storage directory
+ * - topOnly : only return direct child files of the directory (since 1.20)
+ * - adviseStat : set to true if stat requests will be made on the files (since 1.22)
* @return Traversable|Array|null Returns null on failure
*/
abstract public function getFileList( array $params );
@@ -1096,9 +1117,12 @@ abstract class FileBackend {
*
* Storage backends with eventual consistency might return stale data.
*
- * @param $params array
+ * Failures during iteration can result in FileBackendError exceptions (since 1.22).
+ *
+ * @param array $params
* $params include:
- * - dir : storage directory
+ * - dir : storage directory
+ * - adviseStat : set to true if stat requests will be made on the files (since 1.22)
* @return Traversable|Array|null Returns null on failure
* @since 1.20
*/
@@ -1131,10 +1155,11 @@ abstract class FileBackend {
* Callers should consider using getScopedFileLocks() instead.
*
* @param array $paths Storage paths
- * @param $type integer LockManager::LOCK_* constant
+ * @param integer $type LockManager::LOCK_* constant
* @return Status
*/
final public function lockFiles( array $paths, $type ) {
+ $paths = array_map( 'FileBackend::normalizeStoragePath', $paths );
return $this->lockManager->lock( $paths, $type );
}
@@ -1142,10 +1167,11 @@ abstract class FileBackend {
* Unlock the files at the given storage paths in the backend.
*
* @param array $paths Storage paths
- * @param $type integer LockManager::LOCK_* constant
+ * @param integer $type LockManager::LOCK_* constant
* @return Status
*/
final public function unlockFiles( array $paths, $type ) {
+ $paths = array_map( 'FileBackend::normalizeStoragePath', $paths );
return $this->lockManager->unlock( $paths, $type );
}
@@ -1157,12 +1183,21 @@ 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 array $paths Storage paths
- * @param $type integer LockManager::LOCK_* constant
- * @param $status Status Status to update on lock/unlock
+ * @see ScopedLock::factory()
+ *
+ * @param array $paths List of storage paths or map of lock types to path lists
+ * @param integer|string $type LockManager::LOCK_* constant or "mixed"
+ * @param Status $status Status to update on lock/unlock
* @return ScopedLock|null Returns null on failure
*/
final public function getScopedFileLocks( array $paths, $type, Status $status ) {
+ if ( $type === 'mixed' ) {
+ foreach ( $paths as &$typePaths ) {
+ $typePaths = array_map( 'FileBackend::normalizeStoragePath', $typePaths );
+ }
+ } else {
+ $paths = array_map( 'FileBackend::normalizeStoragePath', $paths );
+ }
return ScopedLock::factory( $this->lockManager, $paths, $type, $status );
}
@@ -1178,7 +1213,7 @@ abstract class FileBackend {
* @see FileBackend::doOperations()
*
* @param array $ops List of file operations to FileBackend::doOperations()
- * @param $status Status Status to update on lock/unlock
+ * @param Status $status Status to update on lock/unlock
* @return Array List of ScopedFileLocks or null values
* @since 1.20
*/
@@ -1219,7 +1254,7 @@ abstract class FileBackend {
* Check if a given path is a "mwstore://" path.
* This does not do any further validation or any existence checks.
*
- * @param $path string
+ * @param string $path
* @return bool
*/
final public static function isStoragePath( $path ) {
@@ -1231,7 +1266,7 @@ abstract class FileBackend {
* and a relative file path. The relative path may be the empty string.
* This does not do any path normalization or traversal checks.
*
- * @param $storagePath string
+ * @param string $storagePath
* @return Array (backend, container, rel object) or (null, null, null)
*/
final public static function splitStoragePath( $storagePath ) {
@@ -1253,7 +1288,7 @@ abstract class FileBackend {
* Normalize a storage path by cleaning up directory separators.
* Returns null if the path is not of the format of a valid storage path.
*
- * @param $storagePath string
+ * @param string $storagePath
* @return string|null
*/
final public static function normalizeStoragePath( $storagePath ) {
@@ -1274,7 +1309,7 @@ abstract class FileBackend {
* This returns a path like "mwstore://backend/container",
* "mwstore://backend/container/...", or null if there is no parent.
*
- * @param $storagePath string
+ * @param string $storagePath
* @return string|null
*/
final public static function parentStoragePath( $storagePath ) {
@@ -1286,7 +1321,7 @@ abstract class FileBackend {
/**
* Get the final extension from a storage or FS path
*
- * @param $path string
+ * @param string $path
* @return string
*/
final public static function extensionFromPath( $path ) {
@@ -1297,7 +1332,7 @@ abstract class FileBackend {
/**
* Check if a relative path has no directory traversals
*
- * @param $path string
+ * @param string $path
* @return bool
* @since 1.20
*/
@@ -1363,3 +1398,9 @@ abstract class FileBackend {
return $path;
}
}
+
+/**
+ * @ingroup FileBackend
+ * @since 1.22
+ */
+class FileBackendError extends MWException {}
diff --git a/includes/filebackend/FileBackendGroup.php b/includes/filebackend/FileBackendGroup.php
index d790a996..be8a2076 100644
--- a/includes/filebackend/FileBackendGroup.php
+++ b/includes/filebackend/FileBackendGroup.php
@@ -95,17 +95,17 @@ class FileBackendGroup {
: 0644;
// Get the FS backend configuration
$autoBackends[] = array(
- 'name' => $backendName,
- 'class' => 'FSFileBackend',
- 'lockManager' => 'fsLockManager',
+ 'name' => $backendName,
+ 'class' => 'FSFileBackend',
+ 'lockManager' => 'fsLockManager',
'containerPaths' => array(
- "{$repoName}-public" => "{$directory}",
- "{$repoName}-thumb" => $thumbDir,
- "{$repoName}-transcoded" => $transcodedDir,
+ "{$repoName}-public" => "{$directory}",
+ "{$repoName}-thumb" => $thumbDir,
+ "{$repoName}-transcoded" => $transcodedDir,
"{$repoName}-deleted" => $deletedDir,
- "{$repoName}-temp" => "{$directory}/temp"
+ "{$repoName}-temp" => "{$directory}/temp"
),
- 'fileMode' => $fileMode,
+ 'fileMode' => $fileMode,
);
}
@@ -116,7 +116,7 @@ class FileBackendGroup {
/**
* Register an array of file backend configurations
*
- * @param $configs Array
+ * @param Array $configs
* @return void
* @throws MWException
*/
@@ -135,8 +135,8 @@ class FileBackendGroup {
unset( $config['class'] ); // backend won't need this
$this->backends[$name] = array(
- 'class' => $class,
- 'config' => $config,
+ 'class' => $class,
+ 'config' => $config,
'instance' => null
);
}
@@ -145,7 +145,7 @@ class FileBackendGroup {
/**
* Get the backend object with a given name
*
- * @param $name string
+ * @param string $name
* @return FileBackend
* @throws MWException
*/
@@ -165,7 +165,7 @@ class FileBackendGroup {
/**
* Get the config array for a backend object with a given name
*
- * @param $name string
+ * @param string $name
* @return Array
* @throws MWException
*/
@@ -180,7 +180,7 @@ class FileBackendGroup {
/**
* Get an appropriate backend object from a storage path
*
- * @param $storagePath string
+ * @param string $storagePath
* @return FileBackend|null Backend or null on failure
*/
public function backendFromPath( $storagePath ) {
diff --git a/includes/filebackend/FileBackendMultiWrite.php b/includes/filebackend/FileBackendMultiWrite.php
index 939315d1..97584a71 100644
--- a/includes/filebackend/FileBackendMultiWrite.php
+++ b/includes/filebackend/FileBackendMultiWrite.php
@@ -75,10 +75,13 @@ class FileBackendMultiWrite extends FileBackend {
* - autoResync : Automatically resync the clone backends to the master backend
* when pre-operation sync checks fail. This should only be used
* if the master backend is stable and not missing any files.
+ * Use "conservative" to limit resyncing to copying newer master
+ * backend files over older (or non-existing) clone backend files.
+ * Cases that cannot be handled will result in operation abortion.
* - noPushQuickOps : (hack) Only apply doQuickOperations() to the master backend.
* - noPushDirConts : (hack) Only apply directory functions to the master backend.
*
- * @param $config Array
+ * @param Array $config
* @throws MWException
*/
public function __construct( array $config ) {
@@ -86,7 +89,9 @@ class FileBackendMultiWrite extends FileBackend {
$this->syncChecks = isset( $config['syncChecks'] )
? $config['syncChecks']
: self::CHECK_SIZE;
- $this->autoResync = !empty( $config['autoResync'] );
+ $this->autoResync = isset( $config['autoResync'] )
+ ? $config['autoResync']
+ : false;
$this->noPushQuickOps = isset( $config['noPushQuickOps'] )
? $config['noPushQuickOps']
: false;
@@ -131,26 +136,15 @@ class FileBackendMultiWrite extends FileBackend {
}
}
- /**
- * @see FileBackend::doOperationsInternal()
- * @return Status
- */
final protected function doOperationsInternal( array $ops, array $opts ) {
$status = Status::newGood();
$mbe = $this->backends[$this->masterIndex]; // convenience
- // Get the paths to lock from the master backend
- $realOps = $this->substOpBatchPaths( $ops, $mbe );
- $paths = $mbe->getPathsToLockForOpsInternal( $mbe->getOperationsInternal( $realOps ) );
- // Get the paths under the proxy backend's name
- $paths['sh'] = $this->unsubstPaths( $paths['sh'] );
- $paths['ex'] = $this->unsubstPaths( $paths['ex'] );
// Try to lock those files for the scope of this function...
if ( empty( $opts['nonLocking'] ) ) {
// Try to lock those files for the scope of this function...
- $scopeLockS = $this->getScopedFileLocks( $paths['sh'], LockManager::LOCK_UW, $status );
- $scopeLockE = $this->getScopedFileLocks( $paths['ex'], LockManager::LOCK_EX, $status );
+ $scopeLock = $this->getScopedLocksForOps( $ops, $status );
if ( !$status->isOK() ) {
return $status; // abort
}
@@ -177,6 +171,7 @@ class FileBackendMultiWrite extends FileBackend {
}
}
// Actually attempt the operation batch on the master backend...
+ $realOps = $this->substOpBatchPaths( $ops, $mbe );
$masterStatus = $mbe->doOperations( $realOps, $opts );
$status->merge( $masterStatus );
// Propagate the operations to the clone backends if there were no unexpected errors
@@ -304,11 +299,11 @@ class FileBackendMultiWrite extends FileBackend {
$mBackend = $this->backends[$this->masterIndex];
foreach ( $paths as $path ) {
$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 ) {
+ $mSha1 = $mBackend->getFileSha1Base36( array( 'src' => $mPath, 'latest' => true ) );
+ $mStat = $mBackend->getFileStat( array( 'src' => $mPath, 'latest' => true ) );
+ if ( $mStat === null || ( $mSha1 !== false && !$mStat ) ) { // sanity
$status->fatal( 'backend-fail-internal', $this->name );
+ continue; // file is not available on the master backend...
}
// Check of all clone backends agree with the master...
foreach ( $this->backends as $index => $cBackend ) {
@@ -316,15 +311,31 @@ class FileBackendMultiWrite extends FileBackend {
continue; // master
}
$cPath = $this->substPaths( $path, $cBackend );
- $cSha1 = $cBackend->getFileSha1Base36( array( 'src' => $cPath ) );
+ $cSha1 = $cBackend->getFileSha1Base36( array( 'src' => $cPath, 'latest' => true ) );
+ $cStat = $cBackend->getFileStat( array( 'src' => $cPath, 'latest' => true ) );
+ if ( $cStat === null || ( $cSha1 !== false && !$cStat ) ) { // sanity
+ $status->fatal( 'backend-fail-internal', $cBackend->getName() );
+ continue; // file is not available on the clone backend...
+ }
if ( $mSha1 === $cSha1 ) {
// already synced; nothing to do
- } elseif ( $mSha1 ) { // file is in master
- $fsFile = $mBackend->getLocalReference( array( 'src' => $mPath ) );
+ } elseif ( $mSha1 !== false ) { // file is in master
+ if ( $this->autoResync === 'conservative'
+ && $cStat && $cStat['mtime'] > $mStat['mtime'] )
+ {
+ $status->fatal( 'backend-fail-synced', $path );
+ continue; // don't rollback data
+ }
+ $fsFile = $mBackend->getLocalReference(
+ array( 'src' => $mPath, 'latest' => true ) );
$status->merge( $cBackend->quickStore(
array( 'src' => $fsFile->getPath(), 'dst' => $cPath )
) );
- } elseif ( $mExist === false ) { // file is not in master
+ } elseif ( $mStat === false ) { // file is not in master
+ if ( $this->autoResync === 'conservative' ) {
+ $status->fatal( 'backend-fail-synced', $path );
+ continue; // don't delete data
+ }
$status->merge( $cBackend->quickDelete( array( 'src' => $cPath ) ) );
}
}
@@ -366,7 +377,7 @@ class FileBackendMultiWrite extends FileBackend {
* for a set of operations with that of a given internal backend.
*
* @param array $ops List of file operation arrays
- * @param $backend FileBackendStore
+ * @param FileBackendStore $backend
* @return Array
*/
protected function substOpBatchPaths( array $ops, FileBackendStore $backend ) {
@@ -387,7 +398,7 @@ class FileBackendMultiWrite extends FileBackend {
* Same as substOpBatchPaths() but for a single operation
*
* @param array $ops File operation array
- * @param $backend FileBackendStore
+ * @param FileBackendStore $backend
* @return Array
*/
protected function substOpPaths( array $ops, FileBackendStore $backend ) {
@@ -399,7 +410,7 @@ class FileBackendMultiWrite extends FileBackend {
* Substitute the backend of storage paths with an internal backend's name
*
* @param array|string $paths List of paths or single string path
- * @param $backend FileBackendStore
+ * @param FileBackendStore $backend
* @return Array|string
*/
protected function substPaths( $paths, FileBackendStore $backend ) {
@@ -424,10 +435,6 @@ class FileBackendMultiWrite extends FileBackend {
);
}
- /**
- * @see FileBackend::doQuickOperationsInternal()
- * @return Status
- */
protected function doQuickOperationsInternal( array $ops ) {
$status = Status::newGood();
// Do the operations on the master backend; setting Status fields...
@@ -457,14 +464,10 @@ class FileBackendMultiWrite extends FileBackend {
* @return bool Path container should have dir changes pushed to all backends
*/
protected function replicateContainerDirChanges( $path ) {
- list( , $shortCont, ) = self::splitStoragePath( $path );
+ list( , $shortCont, ) = self::splitStoragePath( $path );
return !in_array( $shortCont, $this->noPushDirConts );
}
- /**
- * @see FileBackend::doPrepare()
- * @return Status
- */
protected function doPrepare( array $params ) {
$status = Status::newGood();
$replicate = $this->replicateContainerDirChanges( $params['dir'] );
@@ -477,11 +480,6 @@ class FileBackendMultiWrite extends FileBackend {
return $status;
}
- /**
- * @see FileBackend::doSecure()
- * @param $params array
- * @return Status
- */
protected function doSecure( array $params ) {
$status = Status::newGood();
$replicate = $this->replicateContainerDirChanges( $params['dir'] );
@@ -494,11 +492,6 @@ class FileBackendMultiWrite extends FileBackend {
return $status;
}
- /**
- * @see FileBackend::doPublish()
- * @param $params array
- * @return Status
- */
protected function doPublish( array $params ) {
$status = Status::newGood();
$replicate = $this->replicateContainerDirChanges( $params['dir'] );
@@ -511,11 +504,6 @@ class FileBackendMultiWrite extends FileBackend {
return $status;
}
- /**
- * @see FileBackend::doClean()
- * @param $params array
- * @return Status
- */
protected function doClean( array $params ) {
$status = Status::newGood();
$replicate = $this->replicateContainerDirChanges( $params['dir'] );
@@ -528,62 +516,32 @@ class FileBackendMultiWrite extends FileBackend {
return $status;
}
- /**
- * @see FileBackend::concatenate()
- * @param $params array
- * @return Status
- */
public function concatenate( array $params ) {
// We are writing to an FS file, so we don't need to do this per-backend
$realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
return $this->backends[$this->masterIndex]->concatenate( $realParams );
}
- /**
- * @see FileBackend::fileExists()
- * @param $params array
- * @return bool|null
- */
public function fileExists( array $params ) {
$realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
return $this->backends[$this->masterIndex]->fileExists( $realParams );
}
- /**
- * @see FileBackend::getFileTimestamp()
- * @param $params array
- * @return bool|string
- */
public function getFileTimestamp( array $params ) {
$realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
return $this->backends[$this->masterIndex]->getFileTimestamp( $realParams );
}
- /**
- * @see FileBackend::getFileSize()
- * @param $params array
- * @return bool|int
- */
public function getFileSize( array $params ) {
$realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
return $this->backends[$this->masterIndex]->getFileSize( $realParams );
}
- /**
- * @see FileBackend::getFileStat()
- * @param $params array
- * @return Array|bool|null
- */
public function getFileStat( array $params ) {
$realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
return $this->backends[$this->masterIndex]->getFileStat( $realParams );
}
- /**
- * @see FileBackend::getFileContentsMulti()
- * @param $params array
- * @return bool|string
- */
public function getFileContentsMulti( array $params ) {
$realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
$contentsM = $this->backends[$this->masterIndex]->getFileContentsMulti( $realParams );
@@ -595,41 +553,21 @@ class FileBackendMultiWrite extends FileBackend {
return $contents;
}
- /**
- * @see FileBackend::getFileSha1Base36()
- * @param $params array
- * @return bool|string
- */
public function getFileSha1Base36( array $params ) {
$realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
return $this->backends[$this->masterIndex]->getFileSha1Base36( $realParams );
}
- /**
- * @see FileBackend::getFileProps()
- * @param $params array
- * @return Array
- */
public function getFileProps( array $params ) {
$realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
return $this->backends[$this->masterIndex]->getFileProps( $realParams );
}
- /**
- * @see FileBackend::streamFile()
- * @param $params array
- * @return \Status
- */
public function streamFile( array $params ) {
$realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
return $this->backends[$this->masterIndex]->streamFile( $realParams );
}
- /**
- * @see FileBackend::getLocalReferenceMulti()
- * @param $params array
- * @return FSFile|null
- */
public function getLocalReferenceMulti( array $params ) {
$realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
$fsFilesM = $this->backends[$this->masterIndex]->getLocalReferenceMulti( $realParams );
@@ -641,11 +579,6 @@ class FileBackendMultiWrite extends FileBackend {
return $fsFiles;
}
- /**
- * @see FileBackend::getLocalCopyMulti()
- * @param $params array
- * @return null|TempFSFile
- */
public function getLocalCopyMulti( array $params ) {
$realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
$tempFilesM = $this->backends[$this->masterIndex]->getLocalCopyMulti( $realParams );
@@ -657,48 +590,26 @@ class FileBackendMultiWrite extends FileBackend {
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]->getFileHttpUrl( $realParams );
}
- /**
- * @see FileBackend::directoryExists()
- * @param $params array
- * @return bool|null
- */
public function directoryExists( array $params ) {
$realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
return $this->backends[$this->masterIndex]->directoryExists( $realParams );
}
- /**
- * @see FileBackend::getSubdirectoryList()
- * @param $params array
- * @return Array|null|Traversable
- */
public function getDirectoryList( array $params ) {
$realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
return $this->backends[$this->masterIndex]->getDirectoryList( $realParams );
}
- /**
- * @see FileBackend::getFileList()
- * @param $params array
- * @return Array|null|\Traversable
- */
public function getFileList( array $params ) {
$realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
return $this->backends[$this->masterIndex]->getFileList( $realParams );
}
- /**
- * @see FileBackend::clearCache()
- */
public function clearCache( array $paths = null ) {
foreach ( $this->backends as $backend ) {
$realPaths = is_array( $paths ) ? $this->substPaths( $paths, $backend ) : null;
@@ -706,19 +617,17 @@ class FileBackendMultiWrite extends FileBackend {
}
}
- /**
- * @see FileBackend::getScopedLocksForOps()
- */
public function getScopedLocksForOps( array $ops, Status $status ) {
- $fileOps = $this->backends[$this->masterIndex]->getOperationsInternal( $ops );
+ $realOps = $this->substOpBatchPaths( $ops, $this->backends[$this->masterIndex] );
+ $fileOps = $this->backends[$this->masterIndex]->getOperationsInternal( $realOps );
// Get the paths to lock from the master backend
$paths = $this->backends[$this->masterIndex]->getPathsToLockForOpsInternal( $fileOps );
// Get the paths under the proxy backend's name
- $paths['sh'] = $this->unsubstPaths( $paths['sh'] );
- $paths['ex'] = $this->unsubstPaths( $paths['ex'] );
- return array(
- $this->getScopedFileLocks( $paths['sh'], LockManager::LOCK_UW, $status ),
- $this->getScopedFileLocks( $paths['ex'], LockManager::LOCK_EX, $status )
+ $pbPaths = array(
+ LockManager::LOCK_UW => $this->unsubstPaths( $paths[LockManager::LOCK_UW] ),
+ LockManager::LOCK_EX => $this->unsubstPaths( $paths[LockManager::LOCK_EX] )
);
+ // Actually acquire the locks
+ return array( $this->getScopedFileLocks( $pbPaths, 'mixed', $status ) );
}
}
diff --git a/includes/filebackend/FileBackendStore.php b/includes/filebackend/FileBackendStore.php
index 3f1d1857..0921e99f 100644
--- a/includes/filebackend/FileBackendStore.php
+++ b/includes/filebackend/FileBackendStore.php
@@ -38,28 +38,43 @@
abstract class FileBackendStore extends FileBackend {
/** @var BagOStuff */
protected $memCache;
- /** @var ProcessCacheLRU */
- protected $cheapCache; // Map of paths to small (RAM/disk) cache items
- /** @var ProcessCacheLRU */
- protected $expensiveCache; // Map of paths to large (RAM/disk) cache items
+ /** @var ProcessCacheLRU Map of paths to small (RAM/disk) cache items */
+ protected $cheapCache;
+ /** @var ProcessCacheLRU Map of paths to large (RAM/disk) cache items */
+ protected $expensiveCache;
- /** @var Array Map of container names to sharding settings */
- protected $shardViaHashLevels = array(); // (container name => config array)
+ /** @var Array Map of container names to sharding config */
+ protected $shardViaHashLevels = array();
+
+ /** @var callback Method to get the MIME type of files */
+ protected $mimeCallback;
protected $maxFileSize = 4294967296; // integer bytes (4GiB)
const CACHE_TTL = 10; // integer; TTL in seconds for process cache entries
+ const CACHE_CHEAP_SIZE = 300; // integer; max entries in "cheap cache"
+ const CACHE_EXPENSIVE_SIZE = 5; // integer; max entries in "expensive cache"
/**
* @see FileBackend::__construct()
+ * Additional $config params include:
+ * - mimeCallback : Callback that takes (storage path, content, file system path) and
+ * returns the MIME type of the file or 'unknown/unknown'. The file
+ * system path parameter should be used if the content one is null.
*
- * @param $config Array
+ * @param array $config
*/
public function __construct( array $config ) {
parent::__construct( $config );
+ $this->mimeCallback = isset( $config['mimeCallback'] )
+ ? $config['mimeCallback']
+ : function( $storagePath, $content, $fsPath ) {
+ // @TODO: handle the case of extension-less files using the contents
+ return StreamFile::contentTypeFromPath( $storagePath ) ?: 'unknown/unknown';
+ };
$this->memCache = new EmptyBagOStuff(); // disabled by default
- $this->cheapCache = new ProcessCacheLRU( 300 );
- $this->expensiveCache = new ProcessCacheLRU( 5 );
+ $this->cheapCache = new ProcessCacheLRU( self::CACHE_CHEAP_SIZE );
+ $this->expensiveCache = new ProcessCacheLRU( self::CACHE_EXPENSIVE_SIZE );
}
/**
@@ -79,7 +94,7 @@ abstract class FileBackendStore extends FileBackend {
* 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
+ * @param string $storagePath
* @return bool
*/
abstract public function isPathUsableInternal( $storagePath );
@@ -92,7 +107,6 @@ abstract class FileBackendStore extends FileBackend {
* $params include:
* - 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
@@ -104,8 +118,7 @@ abstract class FileBackendStore extends FileBackend {
* @return Status
*/
final public function createInternal( array $params ) {
- wfProfileIn( __METHOD__ );
- wfProfileIn( __METHOD__ . '-' . $this->name );
+ $section = new ProfileSection( __METHOD__ . "-{$this->name}" );
if ( strlen( $params['content'] ) > $this->maxFileSizeInternal() ) {
$status = Status::newFatal( 'backend-fail-maxsize',
$params['dst'], $this->maxFileSizeInternal() );
@@ -116,8 +129,6 @@ abstract class FileBackendStore extends FileBackend {
$this->deleteFileCache( $params['dst'] ); // persistent cache
}
}
- wfProfileOut( __METHOD__ . '-' . $this->name );
- wfProfileOut( __METHOD__ );
return $status;
}
@@ -135,7 +146,6 @@ abstract class FileBackendStore extends FileBackend {
* $params include:
* - 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
@@ -147,8 +157,7 @@ abstract class FileBackendStore extends FileBackend {
* @return Status
*/
final public function storeInternal( array $params ) {
- wfProfileIn( __METHOD__ );
- wfProfileIn( __METHOD__ . '-' . $this->name );
+ $section = new ProfileSection( __METHOD__ . "-{$this->name}" );
if ( filesize( $params['src'] ) > $this->maxFileSizeInternal() ) {
$status = Status::newFatal( 'backend-fail-maxsize',
$params['dst'], $this->maxFileSizeInternal() );
@@ -159,8 +168,6 @@ abstract class FileBackendStore extends FileBackend {
$this->deleteFileCache( $params['dst'] ); // persistent cache
}
}
- wfProfileOut( __METHOD__ . '-' . $this->name );
- wfProfileOut( __METHOD__ );
return $status;
}
@@ -179,7 +186,7 @@ abstract class FileBackendStore extends FileBackend {
* - 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
+ * - 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.
@@ -190,15 +197,12 @@ abstract class FileBackendStore extends FileBackend {
* @return Status
*/
final public function copyInternal( array $params ) {
- wfProfileIn( __METHOD__ );
- wfProfileIn( __METHOD__ . '-' . $this->name );
+ $section = new ProfileSection( __METHOD__ . "-{$this->name}" );
$status = $this->doCopyInternal( $params );
$this->clearCache( array( $params['dst'] ) );
if ( !isset( $params['dstExists'] ) || $params['dstExists'] ) {
$this->deleteFileCache( $params['dst'] ); // persistent cache
}
- wfProfileOut( __METHOD__ . '-' . $this->name );
- wfProfileOut( __METHOD__ );
return $status;
}
@@ -223,13 +227,10 @@ abstract class FileBackendStore extends FileBackend {
* @return Status
*/
final public function deleteInternal( array $params ) {
- wfProfileIn( __METHOD__ );
- wfProfileIn( __METHOD__ . '-' . $this->name );
+ $section = new ProfileSection( __METHOD__ . "-{$this->name}" );
$status = $this->doDeleteInternal( $params );
$this->clearCache( array( $params['src'] ) );
$this->deleteFileCache( $params['src'] ); // persistent cache
- wfProfileOut( __METHOD__ . '-' . $this->name );
- wfProfileOut( __METHOD__ );
return $status;
}
@@ -248,7 +249,7 @@ abstract class FileBackendStore extends FileBackend {
* - 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
+ * - 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.
@@ -259,16 +260,13 @@ abstract class FileBackendStore extends FileBackend {
* @return Status
*/
final public function moveInternal( array $params ) {
- wfProfileIn( __METHOD__ );
- wfProfileIn( __METHOD__ . '-' . $this->name );
+ $section = new ProfileSection( __METHOD__ . "-{$this->name}" );
$status = $this->doMoveInternal( $params );
$this->clearCache( array( $params['src'], $params['dst'] ) );
$this->deleteFileCache( $params['src'] ); // persistent cache
if ( !isset( $params['dstExists'] ) || $params['dstExists'] ) {
$this->deleteFileCache( $params['dst'] ); // persistent cache
}
- wfProfileOut( __METHOD__ . '-' . $this->name );
- wfProfileOut( __METHOD__ );
return $status;
}
@@ -278,10 +276,12 @@ abstract class FileBackendStore extends FileBackend {
*/
protected function doMoveInternal( array $params ) {
unset( $params['async'] ); // two steps, won't work here :)
+ $nsrc = FileBackend::normalizeStoragePath( $params['src'] );
+ $ndst = FileBackend::normalizeStoragePath( $params['dst'] );
// Copy source to dest
$status = $this->copyInternal( $params );
- if ( $status->isOK() ) {
- // Delete source (only fails due to races or medium going down)
+ if ( $nsrc !== $ndst && $status->isOK() ) {
+ // Delete source (only fails due to races or network problems)
$status->merge( $this->deleteInternal( array( 'src' => $params['src'] ) ) );
$status->setResult( true, $status->value ); // ignore delete() errors
}
@@ -294,7 +294,6 @@ abstract class FileBackendStore extends FileBackend {
*
* $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
@@ -304,13 +303,14 @@ abstract class FileBackendStore extends FileBackend {
* @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__ );
+ $section = new ProfileSection( __METHOD__ . "-{$this->name}" );
+ if ( count( $params['headers'] ) ) {
+ $status = $this->doDescribeInternal( $params );
+ $this->clearCache( array( $params['src'] ) );
+ $this->deleteFileCache( $params['src'] ); // persistent cache
+ } else {
+ $status = Status::newGood(); // nothing to do
+ }
return $status;
}
@@ -333,13 +333,8 @@ abstract class FileBackendStore extends FileBackend {
return Status::newGood();
}
- /**
- * @see FileBackend::concatenate()
- * @return Status
- */
final public function concatenate( array $params ) {
- wfProfileIn( __METHOD__ );
- wfProfileIn( __METHOD__ . '-' . $this->name );
+ $section = new ProfileSection( __METHOD__ . "-{$this->name}" );
$status = Status::newGood();
// Try to lock the source files for the scope of this function
@@ -355,8 +350,6 @@ abstract class FileBackendStore extends FileBackend {
}
}
- wfProfileOut( __METHOD__ . '-' . $this->name );
- wfProfileOut( __METHOD__ );
return $status;
}
@@ -426,20 +419,13 @@ abstract class FileBackendStore extends FileBackend {
return $status;
}
- /**
- * @see FileBackend::doPrepare()
- * @return Status
- */
final protected function doPrepare( array $params ) {
- wfProfileIn( __METHOD__ );
- wfProfileIn( __METHOD__ . '-' . $this->name );
-
+ $section = new ProfileSection( __METHOD__ . "-{$this->name}" );
$status = Status::newGood();
+
list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] );
if ( $dir === null ) {
$status->fatal( 'backend-fail-invalidpath', $params['dir'] );
- wfProfileOut( __METHOD__ . '-' . $this->name );
- wfProfileOut( __METHOD__ );
return $status; // invalid storage path
}
@@ -453,8 +439,6 @@ abstract class FileBackendStore extends FileBackend {
}
}
- wfProfileOut( __METHOD__ . '-' . $this->name );
- wfProfileOut( __METHOD__ );
return $status;
}
@@ -466,20 +450,13 @@ abstract class FileBackendStore extends FileBackend {
return Status::newGood();
}
- /**
- * @see FileBackend::doSecure()
- * @return Status
- */
final protected function doSecure( array $params ) {
- wfProfileIn( __METHOD__ );
- wfProfileIn( __METHOD__ . '-' . $this->name );
+ $section = new ProfileSection( __METHOD__ . "-{$this->name}" );
$status = Status::newGood();
list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] );
if ( $dir === null ) {
$status->fatal( 'backend-fail-invalidpath', $params['dir'] );
- wfProfileOut( __METHOD__ . '-' . $this->name );
- wfProfileOut( __METHOD__ );
return $status; // invalid storage path
}
@@ -493,8 +470,6 @@ abstract class FileBackendStore extends FileBackend {
}
}
- wfProfileOut( __METHOD__ . '-' . $this->name );
- wfProfileOut( __METHOD__ );
return $status;
}
@@ -506,20 +481,13 @@ abstract class FileBackendStore extends FileBackend {
return Status::newGood();
}
- /**
- * @see FileBackend::doPublish()
- * @return Status
- */
final protected function doPublish( array $params ) {
- wfProfileIn( __METHOD__ );
- wfProfileIn( __METHOD__ . '-' . $this->name );
+ $section = new ProfileSection( __METHOD__ . "-{$this->name}" );
$status = Status::newGood();
list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] );
if ( $dir === null ) {
$status->fatal( 'backend-fail-invalidpath', $params['dir'] );
- wfProfileOut( __METHOD__ . '-' . $this->name );
- wfProfileOut( __METHOD__ );
return $status; // invalid storage path
}
@@ -533,8 +501,6 @@ abstract class FileBackendStore extends FileBackend {
}
}
- wfProfileOut( __METHOD__ . '-' . $this->name );
- wfProfileOut( __METHOD__ );
return $status;
}
@@ -546,13 +512,8 @@ abstract class FileBackendStore extends FileBackend {
return Status::newGood();
}
- /**
- * @see FileBackend::doClean()
- * @return Status
- */
final protected function doClean( array $params ) {
- wfProfileIn( __METHOD__ );
- wfProfileIn( __METHOD__ . '-' . $this->name );
+ $section = new ProfileSection( __METHOD__ . "-{$this->name}" );
$status = Status::newGood();
// Recursive: first delete all empty subdirs recursively
@@ -570,8 +531,6 @@ abstract class FileBackendStore extends FileBackend {
list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] );
if ( $dir === null ) {
$status->fatal( 'backend-fail-invalidpath', $params['dir'] );
- wfProfileOut( __METHOD__ . '-' . $this->name );
- wfProfileOut( __METHOD__ );
return $status; // invalid storage path
}
@@ -579,8 +538,6 @@ abstract class FileBackendStore extends FileBackend {
$filesLockEx = array( $params['dir'] );
$scopedLockE = $this->getScopedFileLocks( $filesLockEx, LockManager::LOCK_EX, $status );
if ( !$status->isOK() ) {
- wfProfileOut( __METHOD__ . '-' . $this->name );
- wfProfileOut( __METHOD__ );
return $status; // abort
}
@@ -596,8 +553,6 @@ abstract class FileBackendStore extends FileBackend {
}
}
- wfProfileOut( __METHOD__ . '-' . $this->name );
- wfProfileOut( __METHOD__ );
return $status;
}
@@ -609,56 +564,30 @@ abstract class FileBackendStore extends FileBackend {
return Status::newGood();
}
- /**
- * @see FileBackend::fileExists()
- * @return bool|null
- */
final public function fileExists( array $params ) {
- wfProfileIn( __METHOD__ );
- wfProfileIn( __METHOD__ . '-' . $this->name );
+ $section = new ProfileSection( __METHOD__ . "-{$this->name}" );
$stat = $this->getFileStat( $params );
- wfProfileOut( __METHOD__ . '-' . $this->name );
- wfProfileOut( __METHOD__ );
return ( $stat === null ) ? null : (bool)$stat; // null => failure
}
- /**
- * @see FileBackend::getFileTimestamp()
- * @return bool
- */
final public function getFileTimestamp( array $params ) {
- wfProfileIn( __METHOD__ );
- wfProfileIn( __METHOD__ . '-' . $this->name );
+ $section = new ProfileSection( __METHOD__ . "-{$this->name}" );
$stat = $this->getFileStat( $params );
- wfProfileOut( __METHOD__ . '-' . $this->name );
- wfProfileOut( __METHOD__ );
return $stat ? $stat['mtime'] : false;
}
- /**
- * @see FileBackend::getFileSize()
- * @return bool
- */
final public function getFileSize( array $params ) {
- wfProfileIn( __METHOD__ );
- wfProfileIn( __METHOD__ . '-' . $this->name );
+ $section = new ProfileSection( __METHOD__ . "-{$this->name}" );
$stat = $this->getFileStat( $params );
- wfProfileOut( __METHOD__ . '-' . $this->name );
- wfProfileOut( __METHOD__ );
return $stat ? $stat['size'] : false;
}
- /**
- * @see FileBackend::getFileStat()
- * @return bool
- */
final public function getFileStat( array $params ) {
$path = self::normalizeStoragePath( $params['src'] );
if ( $path === null ) {
return false; // invalid storage path
}
- wfProfileIn( __METHOD__ );
- wfProfileIn( __METHOD__ . '-' . $this->name );
+ $section = new ProfileSection( __METHOD__ . "-{$this->name}" );
$latest = !empty( $params['latest'] ); // use latest data?
if ( !$this->cheapCache->has( $path, 'stat', self::CACHE_TTL ) ) {
$this->primeFileCache( array( $path ) ); // check persistent cache
@@ -669,14 +598,10 @@ abstract class FileBackendStore extends FileBackend {
// value was in fact fetched with the latest available data.
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;
}
}
@@ -696,12 +621,12 @@ abstract class FileBackendStore extends FileBackend {
}
} elseif ( $stat === false ) { // file does not exist
$this->cheapCache->set( $path, 'stat', $latest ? 'NOT_EXIST_LATEST' : 'NOT_EXIST' );
+ $this->cheapCache->set( $path, 'sha1', // the SHA-1 must be false too
+ array( 'hash' => false, 'latest' => $latest ) );
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__ );
return $stat;
}
@@ -710,19 +635,12 @@ abstract class FileBackendStore extends FileBackend {
*/
abstract protected function doGetFileStat( array $params );
- /**
- * @see FileBackend::getFileContentsMulti()
- * @return Array
- */
public function getFileContentsMulti( array $params ) {
- wfProfileIn( __METHOD__ );
- wfProfileIn( __METHOD__ . '-' . $this->name );
+ $section = new ProfileSection( __METHOD__ . "-{$this->name}" );
$params = $this->setConcurrencyFlags( $params );
$contents = $this->doGetFileContentsMulti( $params );
- wfProfileOut( __METHOD__ . '-' . $this->name );
- wfProfileOut( __METHOD__ );
return $contents;
}
@@ -740,25 +658,18 @@ abstract class FileBackendStore extends FileBackend {
return $contents;
}
- /**
- * @see FileBackend::getFileSha1Base36()
- * @return bool|string
- */
final public function getFileSha1Base36( array $params ) {
$path = self::normalizeStoragePath( $params['src'] );
if ( $path === null ) {
return false; // invalid storage path
}
- wfProfileIn( __METHOD__ );
- wfProfileIn( __METHOD__ . '-' . $this->name );
+ $section = new ProfileSection( __METHOD__ . "-{$this->name}" );
$latest = !empty( $params['latest'] ); // use latest data?
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.
if ( !$latest || $stat['latest'] ) {
- wfProfileOut( __METHOD__ . '-' . $this->name );
- wfProfileOut( __METHOD__ );
return $stat['hash'];
}
}
@@ -768,8 +679,6 @@ abstract class FileBackendStore extends FileBackend {
wfProfileOut( __METHOD__ . '-miss-' . $this->name );
wfProfileOut( __METHOD__ . '-miss' );
$this->cheapCache->set( $path, 'sha1', array( 'hash' => $hash, 'latest' => $latest ) );
- wfProfileOut( __METHOD__ . '-' . $this->name );
- wfProfileOut( __METHOD__ );
return $hash;
}
@@ -786,27 +695,15 @@ abstract class FileBackendStore extends FileBackend {
}
}
- /**
- * @see FileBackend::getFileProps()
- * @return Array
- */
final public function getFileProps( array $params ) {
- wfProfileIn( __METHOD__ );
- wfProfileIn( __METHOD__ . '-' . $this->name );
+ $section = new ProfileSection( __METHOD__ . "-{$this->name}" );
$fsFile = $this->getLocalReference( $params );
$props = $fsFile ? $fsFile->getProps() : FSFile::placeholderProps();
- wfProfileOut( __METHOD__ . '-' . $this->name );
- wfProfileOut( __METHOD__ );
return $props;
}
- /**
- * @see FileBackend::getLocalReferenceMulti()
- * @return Array
- */
final public function getLocalReferenceMulti( array $params ) {
- wfProfileIn( __METHOD__ );
- wfProfileIn( __METHOD__ . '-' . $this->name );
+ $section = new ProfileSection( __METHOD__ . "-{$this->name}" );
$params = $this->setConcurrencyFlags( $params );
@@ -836,8 +733,6 @@ abstract class FileBackendStore extends FileBackend {
}
}
- wfProfileOut( __METHOD__ . '-' . $this->name );
- wfProfileOut( __METHOD__ );
return $fsFiles;
}
@@ -849,19 +744,12 @@ abstract class FileBackendStore extends FileBackend {
return $this->doGetLocalCopyMulti( $params );
}
- /**
- * @see FileBackend::getLocalCopyMulti()
- * @return Array
- */
final public function getLocalCopyMulti( array $params ) {
- wfProfileIn( __METHOD__ );
- wfProfileIn( __METHOD__ . '-' . $this->name );
+ $section = new ProfileSection( __METHOD__ . "-{$this->name}" );
$params = $this->setConcurrencyFlags( $params );
$tmpFiles = $this->doGetLocalCopyMulti( $params );
- wfProfileOut( __METHOD__ . '-' . $this->name );
- wfProfileOut( __METHOD__ );
return $tmpFiles;
}
@@ -879,13 +767,8 @@ abstract class FileBackendStore extends FileBackend {
return null; // not supported
}
- /**
- * @see FileBackend::streamFile()
- * @return Status
- */
final public function streamFile( array $params ) {
- wfProfileIn( __METHOD__ );
- wfProfileIn( __METHOD__ . '-' . $this->name );
+ $section = new ProfileSection( __METHOD__ . "-{$this->name}" );
$status = Status::newGood();
$info = $this->getFileStat( $params );
@@ -916,8 +799,6 @@ abstract class FileBackendStore extends FileBackend {
$status->fatal( 'backend-fail-stream', $params['src'] );
}
- wfProfileOut( __METHOD__ . '-' . $this->name );
- wfProfileOut( __METHOD__ );
return $status;
}
@@ -938,10 +819,6 @@ abstract class FileBackendStore extends FileBackend {
return $status;
}
- /**
- * @see FileBackend::directoryExists()
- * @return bool|null
- */
final public function directoryExists( array $params ) {
list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] );
if ( $dir === null ) {
@@ -976,10 +853,6 @@ abstract class FileBackendStore extends FileBackend {
*/
abstract protected function doDirectoryExists( $container, $dir, array $params );
- /**
- * @see FileBackend::getDirectoryList()
- * @return Traversable|Array|null Returns null on failure
- */
final public function getDirectoryList( array $params ) {
list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] );
if ( $dir === null ) { // invalid storage path
@@ -1009,10 +882,6 @@ abstract class FileBackendStore extends FileBackend {
*/
abstract public function getDirectoryListInternal( $container, $dir, array $params );
- /**
- * @see FileBackend::getFileList()
- * @return Traversable|Array|null Returns null on failure
- */
final public function getFileList( array $params ) {
list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] );
if ( $dir === null ) { // invalid storage path
@@ -1055,13 +924,13 @@ abstract class FileBackendStore extends FileBackend {
*/
final public function getOperationsInternal( array $ops ) {
$supportedOps = array(
- 'store' => 'StoreFileOp',
- 'copy' => 'CopyFileOp',
- 'move' => 'MoveFileOp',
- 'delete' => 'DeleteFileOp',
- 'create' => 'CreateFileOp',
+ 'store' => 'StoreFileOp',
+ 'copy' => 'CopyFileOp',
+ 'move' => 'MoveFileOp',
+ 'delete' => 'DeleteFileOp',
+ 'create' => 'CreateFileOp',
'describe' => 'DescribeFileOp',
- 'null' => 'NullFileOp'
+ 'null' => 'NullFileOp'
);
$performOps = array(); // array of FileOp objects
@@ -1084,12 +953,13 @@ 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.
+ * Returns an array with LockManager::LOCK_UW (shared locks) and
+ * LockManager::LOCK_EX (exclusive locks) keys, each corresponding
+ * to a list of storage paths to be locked. All returned paths are
+ * normalized.
*
* @param array $performOps List of FileOp objects
- * @return Array ('sh' => list of paths, 'ex' => list of paths)
+ * @return Array (LockManager::LOCK_UW => path list, LockManager::LOCK_EX => path list)
*/
final public function getPathsToLockForOpsInternal( array $performOps ) {
// Build up a list of files to lock...
@@ -1103,28 +973,19 @@ abstract class FileBackendStore extends FileBackend {
// Get a shared lock on the parent directory of each path changed
$paths['sh'] = array_merge( $paths['sh'], array_map( 'dirname', $paths['ex'] ) );
- return $paths;
+ return array(
+ LockManager::LOCK_UW => $paths['sh'],
+ LockManager::LOCK_EX => $paths['ex']
+ );
}
- /**
- * @see FileBackend::getScopedLocksForOps()
- * @return Array
- */
public function getScopedLocksForOps( array $ops, Status $status ) {
$paths = $this->getPathsToLockForOpsInternal( $this->getOperationsInternal( $ops ) );
- return array(
- $this->getScopedFileLocks( $paths['sh'], LockManager::LOCK_UW, $status ),
- $this->getScopedFileLocks( $paths['ex'], LockManager::LOCK_EX, $status )
- );
+ return array( $this->getScopedFileLocks( $paths, 'mixed', $status ) );
}
- /**
- * @see FileBackend::doOperationsInternal()
- * @return Status
- */
final protected function doOperationsInternal( array $ops, array $opts ) {
- wfProfileIn( __METHOD__ );
- wfProfileIn( __METHOD__ . '-' . $this->name );
+ $section = new ProfileSection( __METHOD__ . "-{$this->name}" );
$status = Status::newGood();
// Fix up custom header name/value pairs...
@@ -1138,11 +999,8 @@ abstract class FileBackendStore extends FileBackend {
// Build up a list of files to lock...
$paths = $this->getPathsToLockForOpsInternal( $performOps );
// Try to lock those files for the scope of this function...
- $scopeLockS = $this->getScopedFileLocks( $paths['sh'], LockManager::LOCK_UW, $status );
- $scopeLockE = $this->getScopedFileLocks( $paths['ex'], LockManager::LOCK_EX, $status );
+ $scopeLock = $this->getScopedFileLocks( $paths, 'mixed', $status );
if ( !$status->isOK() ) {
- wfProfileOut( __METHOD__ . '-' . $this->name );
- wfProfileOut( __METHOD__ );
return $status; // abort
}
}
@@ -1164,19 +1022,11 @@ abstract class FileBackendStore extends FileBackend {
$status->merge( $subStatus );
$status->success = $subStatus->success; // not done in merge()
- wfProfileOut( __METHOD__ . '-' . $this->name );
- wfProfileOut( __METHOD__ );
return $status;
}
- /**
- * @see FileBackend::doQuickOperationsInternal()
- * @return Status
- * @throws MWException
- */
final protected function doQuickOperationsInternal( array $ops ) {
- wfProfileIn( __METHOD__ );
- wfProfileIn( __METHOD__ . '-' . $this->name );
+ $section = new ProfileSection( __METHOD__ . "-{$this->name}" );
$status = Status::newGood();
// Fix up custom header name/value pairs...
@@ -1186,7 +1036,7 @@ abstract class FileBackendStore extends FileBackend {
$this->clearCache();
$supportedOps = array( 'create', 'store', 'copy', 'move', 'delete', 'null' );
- $async = ( $this->parallelize === 'implicit' );
+ $async = ( $this->parallelize === 'implicit' && count( $ops ) > 1 );
$maxConcurrency = $this->concurrency; // throttle
$statuses = array(); // array of (index => Status)
@@ -1195,8 +1045,6 @@ abstract class FileBackendStore extends FileBackend {
// Perform the sync-only ops and build up op handles for the async ops...
foreach ( $ops as $index => $params ) {
if ( !in_array( $params['op'], $supportedOps ) ) {
- wfProfileOut( __METHOD__ . '-' . $this->name );
- wfProfileOut( __METHOD__ );
throw new MWException( "Operation '{$params['op']}' is not supported." );
}
$method = $params['op'] . 'Internal'; // e.g. "storeInternal"
@@ -1230,8 +1078,6 @@ abstract class FileBackendStore extends FileBackend {
}
}
- wfProfileOut( __METHOD__ . '-' . $this->name );
- wfProfileOut( __METHOD__ );
return $status;
}
@@ -1245,8 +1091,7 @@ abstract class FileBackendStore extends FileBackend {
* @throws MWException
*/
final public function executeOpHandlesInternal( array $fileOpHandles ) {
- wfProfileIn( __METHOD__ );
- wfProfileIn( __METHOD__ . '-' . $this->name );
+ $section = new ProfileSection( __METHOD__ . "-{$this->name}" );
foreach ( $fileOpHandles as $fileOpHandle ) {
if ( !( $fileOpHandle instanceof FileBackendStoreOpHandle ) ) {
throw new MWException( "Given a non-FileBackendStoreOpHandle object." );
@@ -1258,8 +1103,6 @@ abstract class FileBackendStore extends FileBackend {
foreach ( $fileOpHandles as $fileOpHandle ) {
$fileOpHandle->closeResources();
}
- wfProfileOut( __METHOD__ . '-' . $this->name );
- wfProfileOut( __METHOD__ );
return $res;
}
@@ -1277,15 +1120,20 @@ abstract class FileBackendStore extends FileBackend {
}
/**
- * Strip long HTTP headers from a file operation
+ * Strip long HTTP headers from a file operation.
+ * Most headers are just numbers, but some are allowed to be long.
+ * This function is useful for cleaning up headers and avoiding backend
+ * specific errors, especially in the middle of batch file operations.
*
* @param array $op Same format as doOperation()
* @return Array
*/
protected function stripInvalidHeadersFromOp( array $op ) {
- if ( isset( $op['headers'] ) ) {
+ static $longs = array( 'Content-Disposition' );
+ if ( isset( $op['headers'] ) ) { // op sets HTTP headers
foreach ( $op['headers'] as $name => $value ) {
- if ( strlen( $name ) > 255 || strlen( $value ) > 255 ) {
+ $maxHVLen = in_array( $name, $longs ) ? INF : 255;
+ if ( strlen( $name ) > 255 || strlen( $value ) > $maxHVLen ) {
trigger_error( "Header '$name: $value' is too long." );
unset( $op['headers'][$name] );
} elseif ( !strlen( $value ) ) {
@@ -1296,9 +1144,6 @@ abstract class FileBackendStore extends FileBackend {
return $op;
}
- /**
- * @see FileBackend::preloadCache()
- */
final public function preloadCache( array $paths ) {
$fullConts = array(); // full container names
foreach ( $paths as $path ) {
@@ -1310,9 +1155,6 @@ abstract class FileBackendStore extends FileBackend {
$this->primeFileCache( $paths );
}
- /**
- * @see FileBackend::clearCache()
- */
final public function clearCache( array $paths = null ) {
if ( is_array( $paths ) ) {
$paths = array_map( 'FileBackend::normalizeStoragePath', $paths );
@@ -1353,7 +1195,7 @@ abstract class FileBackendStore extends FileBackend {
* Check if a container name is valid.
* This checks for for length and illegal characters.
*
- * @param $container string
+ * @param string $container
* @return bool
*/
final protected static function isValidContainerName( $container ) {
@@ -1375,7 +1217,7 @@ abstract class FileBackendStore extends FileBackend {
* this means that the path can only refer to a directory and can only
* be scanned by looking in all the container shards.
*
- * @param $storagePath string
+ * @param string $storagePath
* @return Array (container, path, container suffix) or (null, null, null) if invalid
*/
final protected function resolveStoragePath( $storagePath ) {
@@ -1405,16 +1247,22 @@ abstract class FileBackendStore extends FileBackend {
/**
* Like resolveStoragePath() except null values are returned if
- * the container is sharded and the shard could not be determined.
+ * the container is sharded and the shard could not be determined
+ * or if the path ends with '/'. The later case is illegal for FS
+ * backends and can confuse listings for object store backends.
+ *
+ * This function is used when resolving paths that must be valid
+ * locations for files. Directory and listing functions should
+ * generally just use resolveStoragePath() instead.
*
* @see FileBackendStore::resolveStoragePath()
*
- * @param $storagePath string
+ * @param string $storagePath
* @return Array (container, path) or (null, null) if invalid
*/
final protected function resolveStoragePathReal( $storagePath ) {
list( $container, $relPath, $cShard ) = $this->resolveStoragePath( $storagePath );
- if ( $cShard !== null ) {
+ if ( $cShard !== null && substr( $relPath, -1 ) !== '/' ) {
return array( $container, $relPath );
}
return array( null, null );
@@ -1474,7 +1322,7 @@ abstract class FileBackendStore extends FileBackend {
* If greater than 0, then all file storage paths within
* the container are required to be hashed accordingly.
*
- * @param $container string
+ * @param string $container
* @return Array (integer levels, integer base, repeat flag) or (0, 0, false)
*/
final protected function getContainerHashLevels( $container ) {
@@ -1494,7 +1342,7 @@ abstract class FileBackendStore extends FileBackend {
/**
* Get a list of full container shard suffixes for a container
*
- * @param $container string
+ * @param string $container
* @return Array
*/
final protected function getContainerSuffixes( $container ) {
@@ -1512,7 +1360,7 @@ abstract class FileBackendStore extends FileBackend {
/**
* Get the full container name, including the wiki ID prefix
*
- * @param $container string
+ * @param string $container
* @return string
*/
final protected function fullContainerName( $container ) {
@@ -1528,7 +1376,7 @@ abstract class FileBackendStore extends FileBackend {
* This is intended for internal use, such as encoding illegal chars.
* Subclasses can override this to be more restrictive.
*
- * @param $container string
+ * @param string $container
* @return string|null
*/
protected function resolveContainerName( $container ) {
@@ -1563,10 +1411,11 @@ abstract class FileBackendStore extends FileBackend {
* Set the cached info for a container
*
* @param string $container Resolved container name
- * @param $val mixed Information to cache
+ * @param array $val Information to cache
+ * @return void
*/
- final protected function setContainerCache( $container, $val ) {
- $this->memCache->add( $this->containerCacheKey( $container ), $val, 14*86400 );
+ final protected function setContainerCache( $container, array $val ) {
+ $this->memCache->add( $this->containerCacheKey( $container ), $val, 14 * 86400 );
}
/**
@@ -1574,6 +1423,7 @@ abstract class FileBackendStore extends FileBackend {
* The cache key is salted for a while to prevent race conditions.
*
* @param string $container Resolved container name
+ * @return void
*/
final protected function deleteContainerCache( $container ) {
if ( !$this->memCache->set( $this->containerCacheKey( $container ), 'PURGED', 300 ) ) {
@@ -1586,12 +1436,11 @@ abstract class FileBackendStore extends FileBackend {
* 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
+ * @param Array $items
* @return void
*/
final protected function primeContainerCache( array $items ) {
- wfProfileIn( __METHOD__ );
- wfProfileIn( __METHOD__ . '-' . $this->name );
+ $section = new ProfileSection( __METHOD__ . "-{$this->name}" );
$paths = array(); // list of storage paths
$contNames = array(); // (cache key => resolved container name)
@@ -1623,9 +1472,6 @@ abstract class FileBackendStore extends FileBackend {
// Populate the container process cache for the backend...
$this->doPrimeContainerCache( array_filter( $contInfo, 'is_array' ) );
-
- wfProfileOut( __METHOD__ . '-' . $this->name );
- wfProfileOut( __METHOD__ );
}
/**
@@ -1654,14 +1500,17 @@ abstract class FileBackendStore extends FileBackend {
* salting for the case when a file is created at a path were there was none before.
*
* @param string $path Storage path
- * @param $val mixed Information to cache
+ * @param array $val Stat information to cache
+ * @return void
*/
- final protected function setFileCache( $path, $val ) {
+ final protected function setFileCache( $path, array $val ) {
$path = FileBackend::normalizeStoragePath( $path );
if ( $path === null ) {
return; // invalid storage path
}
- $this->memCache->add( $this->fileCacheKey( $path ), $val, 7*86400 );
+ $age = time() - wfTimestamp( TS_UNIX, $val['mtime'] );
+ $ttl = min( 7 * 86400, max( 300, floor( .1 * $age ) ) );
+ $this->memCache->add( $this->fileCacheKey( $path ), $val, $ttl );
}
/**
@@ -1671,6 +1520,7 @@ abstract class FileBackendStore extends FileBackend {
* a file is created at a path were there was none before.
*
* @param string $path Storage path
+ * @return void
*/
final protected function deleteFileCache( $path ) {
$path = FileBackend::normalizeStoragePath( $path );
@@ -1691,8 +1541,7 @@ abstract class FileBackendStore extends FileBackend {
* @return void
*/
final protected function primeFileCache( array $items ) {
- wfProfileIn( __METHOD__ );
- wfProfileIn( __METHOD__ . '-' . $this->name );
+ $section = new ProfileSection( __METHOD__ . "-{$this->name}" );
$paths = array(); // list of storage paths
$pathNames = array(); // (cache key => storage path)
@@ -1726,9 +1575,6 @@ abstract class FileBackendStore extends FileBackend {
}
}
}
-
- wfProfileOut( __METHOD__ . '-' . $this->name );
- wfProfileOut( __METHOD__ );
}
/**
@@ -1750,6 +1596,18 @@ abstract class FileBackendStore extends FileBackend {
}
return $opts;
}
+
+ /**
+ * Get the content type to use in HEAD/GET requests for a file
+ *
+ * @param string $storagePath
+ * @param string|null $content File data
+ * @param string|null $fsPath File system path
+ * @return MIME type
+ */
+ protected function getContentType( $storagePath, $content, $fsPath ) {
+ return call_user_func_array( $this->mimeCallback, func_get_args() );
+ }
}
/**
@@ -1786,26 +1644,20 @@ abstract class FileBackendStoreOpHandle {
*
* @ingroup FileBackend
*/
-abstract class FileBackendStoreShardListIterator implements Iterator {
+abstract class FileBackendStoreShardListIterator extends FilterIterator {
/** @var FileBackendStore */
protected $backend;
/** @var Array */
protected $params;
- /** @var Array */
- protected $shardSuffixes;
+
protected $container; // string; full container name
protected $directory; // string; resolved relative path
- /** @var Traversable */
- protected $iter;
- protected $curShard = 0; // integer
- protected $pos = 0; // integer
-
/** @var Array */
protected $multiShardPaths = array(); // (rel path => 1)
/**
- * @param $backend FileBackendStore
+ * @param FileBackendStore $backend
* @param string $container Full storage container name
* @param string $dir Storage directory relative to container
* @param array $suffixes List of container shard suffixes
@@ -1817,142 +1669,56 @@ abstract class FileBackendStoreShardListIterator implements Iterator {
$this->backend = $backend;
$this->container = $container;
$this->directory = $dir;
- $this->shardSuffixes = $suffixes;
$this->params = $params;
- }
-
- /**
- * @see Iterator::key()
- * @return integer
- */
- public function key() {
- return $this->pos;
- }
- /**
- * @see Iterator::valid()
- * @return bool
- */
- public function valid() {
- if ( $this->iter instanceof Iterator ) {
- return $this->iter->valid();
- } elseif ( is_array( $this->iter ) ) {
- return ( current( $this->iter ) !== false ); // no paths can have this value
+ $iter = new AppendIterator();
+ foreach ( $suffixes as $suffix ) {
+ $iter->append( $this->listFromShard( $this->container . $suffix ) );
}
- return false; // some failure?
- }
-
- /**
- * @see Iterator::current()
- * @return string|bool String or false
- */
- public function current() {
- return ( $this->iter instanceof Iterator )
- ? $this->iter->current()
- : current( $this->iter );
- }
- /**
- * @see Iterator::next()
- * @return void
- */
- public function next() {
- ++$this->pos;
- ( $this->iter instanceof Iterator ) ? $this->iter->next() : next( $this->iter );
- do {
- $continue = false; // keep scanning shards?
- $this->filterViaNext(); // filter out duplicates
- // Find the next non-empty shard if no elements are left
- if ( !$this->valid() ) {
- $this->nextShardIteratorIfNotValid();
- $continue = $this->valid(); // re-filter unless we ran out of shards
- }
- } while ( $continue );
- }
-
- /**
- * @see Iterator::rewind()
- * @return void
- */
- public function rewind() {
- $this->pos = 0;
- $this->curShard = 0;
- $this->setIteratorFromCurrentShard();
- do {
- $continue = false; // keep scanning shards?
- $this->filterViaNext(); // filter out duplicates
- // Find the next non-empty shard if no elements are left
- if ( !$this->valid() ) {
- $this->nextShardIteratorIfNotValid();
- $continue = $this->valid(); // re-filter unless we ran out of shards
- }
- } while ( $continue );
- }
-
- /**
- * Filter out duplicate items by advancing to the next ones
- */
- protected function filterViaNext() {
- while ( $this->valid() ) {
- $rel = $this->iter->current(); // path relative to given directory
- $path = $this->params['dir'] . "/{$rel}"; // full storage path
- if ( $this->backend->isSingleShardPathInternal( $path ) ) {
- break; // path is only on one shard; no issue with duplicates
- } elseif ( isset( $this->multiShardPaths[$rel] ) ) {
- // Don't keep listing paths that are on multiple shards
- ( $this->iter instanceof Iterator ) ? $this->iter->next() : next( $this->iter );
- } else {
- $this->multiShardPaths[$rel] = 1;
- break;
- }
- }
+ parent::__construct( $iter );
}
- /**
- * If the list iterator for this container shard is out of items,
- * then move on to the next container that has items.
- * If there are none, then it advances to the last container.
- */
- protected function nextShardIteratorIfNotValid() {
- while ( !$this->valid() && ++$this->curShard < count( $this->shardSuffixes ) ) {
- $this->setIteratorFromCurrentShard();
+ public function accept() {
+ $rel = $this->getInnerIterator()->current(); // path relative to given directory
+ $path = $this->params['dir'] . "/{$rel}"; // full storage path
+ if ( $this->backend->isSingleShardPathInternal( $path ) ) {
+ return true; // path is only on one shard; no issue with duplicates
+ } elseif ( isset( $this->multiShardPaths[$rel] ) ) {
+ // Don't keep listing paths that are on multiple shards
+ return false;
+ } else {
+ $this->multiShardPaths[$rel] = 1;
+ return true;
}
}
- /**
- * Set the list iterator to that of the current container shard
- */
- protected function setIteratorFromCurrentShard() {
- $this->iter = $this->listFromShard(
- $this->container . $this->shardSuffixes[$this->curShard],
- $this->directory, $this->params );
- // Start loading results so that current() works
- if ( $this->iter ) {
- ( $this->iter instanceof Iterator ) ? $this->iter->rewind() : reset( $this->iter );
- }
+ public function rewind() {
+ parent::rewind();
+ $this->multiShardPaths = array();
}
/**
* Get the list for a given container shard
*
* @param string $container Resolved container name
- * @param string $dir Resolved path relative to container
- * @param array $params
- * @return Traversable|Array|null
+ * @return Iterator
*/
- abstract protected function listFromShard( $container, $dir, array $params );
+ abstract protected function listFromShard( $container );
}
/**
* Iterator for listing directories
*/
class FileBackendStoreShardDirIterator extends FileBackendStoreShardListIterator {
- /**
- * @see FileBackendStoreShardListIterator::listFromShard()
- * @return Array|null|Traversable
- */
- protected function listFromShard( $container, $dir, array $params ) {
- return $this->backend->getDirectoryListInternal( $container, $dir, $params );
+ protected function listFromShard( $container ) {
+ $list = $this->backend->getDirectoryListInternal(
+ $container, $this->directory, $this->params );
+ if ( $list === null ) {
+ return new ArrayIterator( array() );
+ } else {
+ return is_array( $list ) ? new ArrayIterator( $list ) : $list;
+ }
}
}
@@ -1960,11 +1726,13 @@ class FileBackendStoreShardDirIterator extends FileBackendStoreShardListIterator
* Iterator for listing regular files
*/
class FileBackendStoreShardFileIterator extends FileBackendStoreShardListIterator {
- /**
- * @see FileBackendStoreShardListIterator::listFromShard()
- * @return Array|null|Traversable
- */
- protected function listFromShard( $container, $dir, array $params ) {
- return $this->backend->getFileListInternal( $container, $dir, $params );
+ protected function listFromShard( $container ) {
+ $list = $this->backend->getFileListInternal(
+ $container, $this->directory, $this->params );
+ if ( $list === null ) {
+ return new ArrayIterator( array() );
+ } else {
+ return is_array( $list ) ? new ArrayIterator( $list ) : $list;
+ }
}
}
diff --git a/includes/filebackend/FileOp.php b/includes/filebackend/FileOp.php
index bb0ab578..fe833084 100644
--- a/includes/filebackend/FileOp.php
+++ b/includes/filebackend/FileOp.php
@@ -46,7 +46,7 @@ abstract class FileOp {
protected $doOperation = true; // boolean; operation is not a no-op
protected $sourceSha1; // string
- protected $destSameAsSource; // boolean
+ protected $overwriteSameCase; // boolean
protected $destExists; // boolean
/* Object life-cycle */
@@ -55,17 +55,19 @@ abstract class FileOp {
const STATE_ATTEMPTED = 3;
/**
- * Build a new file operation transaction
+ * Build a new batch file operation transaction
*
- * @param $backend FileBackendStore
- * @param $params Array
+ * @param FileBackendStore $backend
+ * @param Array $params
* @throws MWException
*/
final public function __construct( FileBackendStore $backend, array $params ) {
$this->backend = $backend;
list( $required, $optional ) = $this->allowedParams();
+ // @todo normalizeAnyStoragePaths() calls are overzealous, use a parameter list
foreach ( $required as $name ) {
if ( isset( $params[$name] ) ) {
+ // Normalize paths so the paths to the same file have the same string
$this->params[$name] = self::normalizeAnyStoragePaths( $params[$name] );
} else {
throw new MWException( "File operation missing parameter '$name'." );
@@ -73,6 +75,7 @@ abstract class FileOp {
}
foreach ( $optional as $name ) {
if ( isset( $params[$name] ) ) {
+ // Normalize paths so the paths to the same file have the same string
$this->params[$name] = self::normalizeAnyStoragePaths( $params[$name] );
}
}
@@ -82,7 +85,7 @@ abstract class FileOp {
/**
* Normalize $item or anything in $item that is a valid storage path
*
- * @param $item string|array
+ * @param string $item|array
* @return string|Array
*/
protected function normalizeAnyStoragePaths( $item ) {
@@ -102,7 +105,7 @@ abstract class FileOp {
/**
* Normalize a string if it is a valid storage path
*
- * @param $path string
+ * @param string $path
* @return string
*/
protected static function normalizeIfValidStoragePath( $path ) {
@@ -116,7 +119,7 @@ abstract class FileOp {
/**
* Set the batch UUID this operation belongs to
*
- * @param $batchId string
+ * @param string $batchId
* @return void
*/
final public function setBatchId( $batchId ) {
@@ -126,7 +129,7 @@ abstract class FileOp {
/**
* Get the value of the parameter with the given name
*
- * @param $name string
+ * @param string $name
* @return mixed Returns null if the parameter is not set
*/
final public function getParam( $name ) {
@@ -209,22 +212,22 @@ abstract class FileOp {
$pathsUsed = array_merge( $this->storagePathsRead(), $this->storagePathsChanged() );
foreach ( array_unique( $pathsUsed ) as $path ) {
$nullEntries[] = array( // assertion for recovery
- 'op' => 'null',
- 'path' => $path,
+ 'op' => 'null',
+ 'path' => $path,
'newSha1' => $this->fileSha1( $path, $oPredicates )
);
}
foreach ( $this->storagePathsChanged() as $path ) {
if ( $nPredicates['sha1'][$path] === false ) { // deleted
$deleteEntries[] = array(
- 'op' => 'delete',
- 'path' => $path,
+ 'op' => 'delete',
+ 'path' => $path,
'newSha1' => ''
);
} else { // created/updated
$updateEntries[] = array(
- 'op' => $this->fileExists( $path, $oPredicates ) ? 'update' : 'create',
- 'path' => $path,
+ 'op' => $this->fileExists( $path, $oPredicates ) ? 'update' : 'create',
+ 'path' => $path,
'newSha1' => $nPredicates['sha1'][$path]
);
}
@@ -237,7 +240,7 @@ abstract class FileOp {
* This must update $predicates for each path that the op can change
* except when a failing status object is returned.
*
- * @param $predicates Array
+ * @param Array $predicates
* @return Status
*/
final public function precheck( array &$predicates ) {
@@ -314,7 +317,7 @@ abstract class FileOp {
/**
* Adjust params to FileBackendStore internal file calls
*
- * @param $params Array
+ * @param Array $params
* @return Array (required params list, optional params list)
*/
protected function setFlags( array $params ) {
@@ -341,10 +344,10 @@ abstract class FileOp {
/**
* Check for errors with regards to the destination file already existing.
- * Also set the destExists, destSameAsSource and sourceSha1 member variables.
+ * Also set the destExists, overwriteSameCase and sourceSha1 member variables.
* A bad status will be returned if there is no chance it can be overwritten.
*
- * @param $predicates Array
+ * @param Array $predicates
* @return Status
*/
protected function precheckDestExistence( array $predicates ) {
@@ -354,7 +357,7 @@ abstract class FileOp {
if ( $this->sourceSha1 === null ) { // file in storage?
$this->sourceSha1 = $this->fileSha1( $this->params['src'], $predicates );
}
- $this->destSameAsSource = false;
+ $this->overwriteSameCase = false;
$this->destExists = $this->fileExists( $this->params['dst'], $predicates );
if ( $this->destExists ) {
if ( $this->getParam( 'overwrite' ) ) {
@@ -368,7 +371,7 @@ abstract class FileOp {
// Give an error if the files are not identical
$status->fatal( 'backend-fail-notsame', $this->params['dst'] );
} else {
- $this->destSameAsSource = true; // OK
+ $this->overwriteSameCase = true; // OK
}
return $status; // do nothing; either OK or bad status
} else {
@@ -381,7 +384,7 @@ abstract class FileOp {
/**
* precheckDestExistence() helper function to get the source file SHA-1.
- * Subclasses should overwride this iff the source is not in storage.
+ * Subclasses should overwride this if the source is not in storage.
*
* @return string|bool Returns false on failure
*/
@@ -393,7 +396,7 @@ abstract class FileOp {
* Check if a file will exist in storage when this operation is attempted
*
* @param string $source Storage path
- * @param $predicates Array
+ * @param Array $predicates
* @return bool
*/
final protected function fileExists( $source, array $predicates ) {
@@ -409,7 +412,7 @@ abstract class FileOp {
* Get the SHA-1 of a file in storage when this operation is attempted
*
* @param string $source Storage path
- * @param $predicates Array
+ * @param Array $predicates
* @return string|bool False on failure
*/
final protected function fileSha1( $source, array $predicates ) {
@@ -435,7 +438,7 @@ abstract class FileOp {
/**
* Log a file operation failure and preserve any temp files
*
- * @param $action string
+ * @param string $action
* @return void
*/
final public function logFailure( $action ) {
@@ -457,7 +460,7 @@ abstract class FileOp {
class CreateFileOp extends FileOp {
protected function allowedParams() {
return array( array( 'content', 'dst' ),
- array( 'overwrite', 'overwriteSame', 'disposition', 'headers' ) );
+ array( 'overwrite', 'overwriteSame', 'headers' ) );
}
protected function doPrecheck( array &$predicates ) {
@@ -485,27 +488,18 @@ class CreateFileOp extends FileOp {
return $status; // safe to call attempt()
}
- /**
- * @return Status
- */
protected function doAttempt() {
- if ( !$this->destSameAsSource ) {
+ if ( !$this->overwriteSameCase ) {
// Create the file at the destination
return $this->backend->createInternal( $this->setFlags( $this->params ) );
}
return Status::newGood();
}
- /**
- * @return bool|String
- */
protected function getSourceSha1Base36() {
return wfBaseConvert( sha1( $this->params['content'] ), 16, 36, 31 );
}
- /**
- * @return array
- */
public function storagePathsChanged() {
return array( $this->params['dst'] );
}
@@ -516,18 +510,11 @@ class CreateFileOp extends FileOp {
* Parameters for this operation are outlined in FileBackend::doOperations().
*/
class StoreFileOp extends FileOp {
- /**
- * @return array
- */
protected function allowedParams() {
return array( array( 'src', 'dst' ),
- array( 'overwrite', 'overwriteSame', 'disposition', 'headers' ) );
+ array( 'overwrite', 'overwriteSame', 'headers' ) );
}
- /**
- * @param $predicates array
- * @return Status
- */
protected function doPrecheck( array &$predicates ) {
$status = Status::newGood();
// Check if the source file exists on the file system
@@ -557,20 +544,14 @@ class StoreFileOp extends FileOp {
return $status; // safe to call attempt()
}
- /**
- * @return Status
- */
protected function doAttempt() {
- // Store the file at the destination
- if ( !$this->destSameAsSource ) {
+ if ( !$this->overwriteSameCase ) {
+ // Store the file at the destination
return $this->backend->storeInternal( $this->setFlags( $this->params ) );
}
return Status::newGood();
}
- /**
- * @return bool|string
- */
protected function getSourceSha1Base36() {
wfSuppressWarnings();
$hash = sha1_file( $this->params['src'] );
@@ -591,18 +572,11 @@ class StoreFileOp extends FileOp {
* Parameters for this operation are outlined in FileBackend::doOperations().
*/
class CopyFileOp extends FileOp {
- /**
- * @return array
- */
protected function allowedParams() {
return array( array( 'src', 'dst' ),
- array( 'overwrite', 'overwriteSame', 'ignoreMissingSource', 'disposition' ) );
+ array( 'overwrite', 'overwriteSame', 'ignoreMissingSource', 'headers' ) );
}
- /**
- * @param $predicates array
- * @return Status
- */
protected function doPrecheck( array &$predicates ) {
$status = Status::newGood();
// Check if the source file exists
@@ -634,30 +608,26 @@ class CopyFileOp extends FileOp {
return $status; // safe to call attempt()
}
- /**
- * @return Status
- */
protected function doAttempt() {
- // Do nothing if the src/dst paths are the same
- if ( $this->params['src'] !== $this->params['dst'] ) {
- // Copy the file into the destination
- if ( !$this->destSameAsSource ) {
- return $this->backend->copyInternal( $this->setFlags( $this->params ) );
- }
+ if ( $this->overwriteSameCase ) {
+ $status = Status::newGood(); // nothing to do
+ } elseif ( $this->params['src'] === $this->params['dst'] ) {
+ // Just update the destination file headers
+ $headers = $this->getParam( 'headers' ) ?: array();
+ $status = $this->backend->describeInternal( $this->setFlags( array(
+ 'src' => $this->params['dst'], 'headers' => $headers
+ ) ) );
+ } else {
+ // Copy the file to the destination
+ $status = $this->backend->copyInternal( $this->setFlags( $this->params ) );
}
- return Status::newGood();
+ return $status;
}
- /**
- * @return array
- */
public function storagePathsRead() {
return array( $this->params['src'] );
}
- /**
- * @return array
- */
public function storagePathsChanged() {
return array( $this->params['dst'] );
}
@@ -668,18 +638,11 @@ class CopyFileOp extends FileOp {
* Parameters for this operation are outlined in FileBackend::doOperations().
*/
class MoveFileOp extends FileOp {
- /**
- * @return array
- */
protected function allowedParams() {
return array( array( 'src', 'dst' ),
- array( 'overwrite', 'overwriteSame', 'ignoreMissingSource', 'disposition' ) );
+ array( 'overwrite', 'overwriteSame', 'ignoreMissingSource', 'headers' ) );
}
- /**
- * @param $predicates array
- * @return Status
- */
protected function doPrecheck( array &$predicates ) {
$status = Status::newGood();
// Check if the source file exists
@@ -713,34 +676,34 @@ class MoveFileOp extends FileOp {
return $status; // safe to call attempt()
}
- /**
- * @return Status
- */
protected function doAttempt() {
- // Do nothing if the src/dst paths are the same
- if ( $this->params['src'] !== $this->params['dst'] ) {
- if ( !$this->destSameAsSource ) {
- // Move the file into the destination
- return $this->backend->moveInternal( $this->setFlags( $this->params ) );
+ if ( $this->overwriteSameCase ) {
+ if ( $this->params['src'] === $this->params['dst'] ) {
+ // Do nothing to the destination (which is also the source)
+ $status = Status::newGood();
} else {
- // Just delete source as the destination needs no changes
- $params = array( 'src' => $this->params['src'] );
- return $this->backend->deleteInternal( $this->setFlags( $params ) );
+ // Just delete the source as the destination file needs no changes
+ $status = $this->backend->deleteInternal( $this->setFlags(
+ array( 'src' => $this->params['src'] )
+ ) );
}
+ } elseif ( $this->params['src'] === $this->params['dst'] ) {
+ // Just update the destination file headers
+ $headers = $this->getParam( 'headers' ) ?: array();
+ $status = $this->backend->describeInternal( $this->setFlags(
+ array( 'src' => $this->params['dst'], 'headers' => $headers )
+ ) );
+ } else {
+ // Move the file to the destination
+ $status = $this->backend->moveInternal( $this->setFlags( $this->params ) );
}
- return Status::newGood();
+ return $status;
}
- /**
- * @return array
- */
public function storagePathsRead() {
return array( $this->params['src'] );
}
- /**
- * @return array
- */
public function storagePathsChanged() {
return array( $this->params['src'], $this->params['dst'] );
}
@@ -751,17 +714,10 @@ class MoveFileOp extends FileOp {
* Parameters for this operation are outlined in FileBackend::doOperations().
*/
class DeleteFileOp extends FileOp {
- /**
- * @return array
- */
protected function allowedParams() {
return array( array( 'src' ), array( 'ignoreMissingSource' ) );
}
- /**
- * @param $predicates array
- * @return Status
- */
protected function doPrecheck( array &$predicates ) {
$status = Status::newGood();
// Check if the source file exists
@@ -788,17 +744,11 @@ class DeleteFileOp extends FileOp {
return $status; // safe to call attempt()
}
- /**
- * @return Status
- */
protected function doAttempt() {
// Delete the source file
return $this->backend->deleteInternal( $this->setFlags( $this->params ) );
}
- /**
- * @return array
- */
public function storagePathsChanged() {
return array( $this->params['src'] );
}
@@ -809,17 +759,10 @@ class DeleteFileOp extends FileOp {
* 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' ) );
+ return array( array( 'src' ), array( 'headers' ) );
}
- /**
- * @param $predicates array
- * @return Status
- */
protected function doPrecheck( array &$predicates ) {
$status = Status::newGood();
// Check if the source file exists
@@ -840,17 +783,11 @@ class DescribeFileOp extends FileOp {
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
- */
public function storagePathsChanged() {
return array( $this->params['src'] );
}
diff --git a/includes/filebackend/FileOpBatch.php b/includes/filebackend/FileOpBatch.php
index fc51d78a..785c0bc9 100644
--- a/includes/filebackend/FileOpBatch.php
+++ b/includes/filebackend/FileOpBatch.php
@@ -51,7 +51,7 @@ class FileOpBatch {
*
* @param array $performOps List of FileOp operations
* @param array $opts Batch operation options
- * @param $journal FileJournal Journal to log operations to
+ * @param FileJournal $journal Journal to log operations to
* @return Status
*/
public static function attempt( array $performOps, array $opts, FileJournal $journal ) {
@@ -145,8 +145,8 @@ class FileOpBatch {
* within any given sub-batch do not depend on each other.
* This will abort remaining ops on failure.
*
- * @param $pPerformOps Array
- * @param $status Status
+ * @param Array $pPerformOps
+ * @param Status $status
* @return bool Success
*/
protected static function runParallelBatches( array $pPerformOps, Status $status ) {
diff --git a/includes/filebackend/README b/includes/filebackend/README
index 6ab54810..569f3376 100644
--- a/includes/filebackend/README
+++ b/includes/filebackend/README
@@ -47,7 +47,7 @@ directories. See FileBackend.php for full documentation for each function.
The following basic operations are supported for reading from a backend:
On files:
-* state a file for basic information (timestamp, size)
+* stat 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
diff --git a/includes/filebackend/SwiftFileBackend.php b/includes/filebackend/SwiftFileBackend.php
index 0f3d97a3..db090a98 100644
--- a/includes/filebackend/SwiftFileBackend.php
+++ b/includes/filebackend/SwiftFileBackend.php
@@ -24,7 +24,7 @@
*/
/**
- * @brief Class for an OpenStack Swift based file backend.
+ * @brief Class for an OpenStack Swift (or Ceph RGW) based file backend.
*
* This requires the SwiftCloudFiles MediaWiki extension, which includes
* the php-cloudfiles library (https://github.com/rackspace/php-cloudfiles).
@@ -104,7 +104,7 @@ class SwiftFileBackend extends FileBackendStore {
*/
public function __construct( array $config ) {
parent::__construct( $config );
- if ( !MWInit::classExists( 'CF_Constants' ) ) {
+ if ( !class_exists( 'CF_Constants' ) ) {
throw new MWException( 'SwiftCloudFiles extension not installed.' );
}
// Required settings
@@ -132,7 +132,7 @@ class SwiftFileBackend extends FileBackendStore {
: false;
$this->swiftCDNExpiry = isset( $config['swiftCDNExpiry'] )
? $config['swiftCDNExpiry']
- : 12*3600; // 12 hours is safe (tokens last 24 hours per http://docs.openstack.org)
+ : 12 * 3600; // 12 hours is safe (tokens last 24 hours per http://docs.openstack.org)
$this->swiftCDNPurgable = isset( $config['swiftCDNPurgable'] )
? $config['swiftCDNPurgable']
: true;
@@ -172,10 +172,6 @@ class SwiftFileBackend extends FileBackendStore {
return $relStoragePath;
}
- /**
- * @see FileBackendStore::isPathUsableInternal()
- * @return bool
- */
public function isPathUsableInternal( $storagePath ) {
list( $container, $rel ) = $this->resolveStoragePathReal( $storagePath );
if ( $rel === null ) {
@@ -194,6 +190,18 @@ class SwiftFileBackend extends FileBackendStore {
}
/**
+ * @param array $headers
+ * @return array
+ */
+ protected function sanitizeHdrs( array $headers ) {
+ // By default, Swift has annoyingly low maximum header value limits
+ if ( isset( $headers['Content-Disposition'] ) ) {
+ $headers['Content-Disposition'] = $this->truncDisp( $headers['Content-Disposition'] );
+ }
+ return $headers;
+ }
+
+ /**
* @param string $disposition Content-Disposition header value
* @return string Truncated Content-Disposition header value to meet Swift limits
*/
@@ -211,10 +219,6 @@ class SwiftFileBackend extends FileBackendStore {
return $res;
}
- /**
- * @see FileBackendStore::doCreateInternal()
- * @return Status
- */
protected function doCreateInternal( array $params ) {
$status = Status::newGood();
@@ -248,17 +252,10 @@ class SwiftFileBackend extends FileBackendStore {
// The MD5 here will be checked within Swift against its own MD5.
$obj->set_etag( md5( $params['content'] ) );
// Use the same content type as StreamFile for security
- $obj->content_type = StreamFile::contentTypeFromPath( $params['dst'] );
- if ( !strlen( $obj->content_type ) ) { // special case
- $obj->content_type = 'unknown/unknown';
- }
- // Set the Content-Disposition header if requested
- if ( isset( $params['disposition'] ) ) {
- $obj->headers['Content-Disposition'] = $this->truncDisp( $params['disposition'] );
- }
+ $obj->content_type = $this->getContentType( $params['dst'], $params['content'], null );
// Set any other custom headers if requested
if ( isset( $params['headers'] ) ) {
- $obj->headers += $params['headers'];
+ $obj->headers += $this->sanitizeHdrs( $params['headers'] );
}
if ( !empty( $params['async'] ) ) { // deferred
$op = $obj->write_async( $params['content'] );
@@ -290,10 +287,6 @@ class SwiftFileBackend extends FileBackendStore {
}
}
- /**
- * @see FileBackendStore::doStoreInternal()
- * @return Status
- */
protected function doStoreInternal( array $params ) {
$status = Status::newGood();
@@ -333,17 +326,10 @@ class SwiftFileBackend extends FileBackendStore {
// 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
- $obj->content_type = StreamFile::contentTypeFromPath( $params['dst'] );
- if ( !strlen( $obj->content_type ) ) { // special case
- $obj->content_type = 'unknown/unknown';
- }
- // Set the Content-Disposition header if requested
- if ( isset( $params['disposition'] ) ) {
- $obj->headers['Content-Disposition'] = $this->truncDisp( $params['disposition'] );
- }
+ $obj->content_type = $this->getContentType( $params['dst'], null, $params['src'] );
// Set any other custom headers if requested
if ( isset( $params['headers'] ) ) {
- $obj->headers += $params['headers'];
+ $obj->headers += $this->sanitizeHdrs( $params['headers'] );
}
if ( !empty( $params['async'] ) ) { // deferred
wfSuppressWarnings();
@@ -387,10 +373,6 @@ class SwiftFileBackend extends FileBackendStore {
}
}
- /**
- * @see FileBackendStore::doCopyInternal()
- * @return Status
- */
protected function doCopyInternal( array $params ) {
$status = Status::newGood();
@@ -424,8 +406,9 @@ class SwiftFileBackend extends FileBackendStore {
try {
$dstObj = new CF_Object( $dContObj, $dstRel, false, false ); // skip HEAD
$hdrs = array(); // source file headers to override with new values
- if ( isset( $params['disposition'] ) ) {
- $hdrs['Content-Disposition'] = $this->truncDisp( $params['disposition'] );
+ // Set any other custom headers if requested
+ if ( isset( $params['headers'] ) ) {
+ $hdrs += $this->sanitizeHdrs( $params['headers'] );
}
if ( !empty( $params['async'] ) ) { // deferred
$op = $sContObj->copy_object_to_async( $srcRel, $dContObj, $dstRel, null, $hdrs );
@@ -459,10 +442,6 @@ class SwiftFileBackend extends FileBackendStore {
}
}
- /**
- * @see FileBackendStore::doMoveInternal()
- * @return Status
- */
protected function doMoveInternal( array $params ) {
$status = Status::newGood();
@@ -497,8 +476,9 @@ class SwiftFileBackend extends FileBackendStore {
$srcObj = new CF_Object( $sContObj, $srcRel, false, false ); // skip HEAD
$dstObj = new CF_Object( $dContObj, $dstRel, false, false ); // skip HEAD
$hdrs = array(); // source file headers to override with new values
- if ( isset( $params['disposition'] ) ) {
- $hdrs['Content-Disposition'] = $this->truncDisp( $params['disposition'] );
+ // Set any other custom headers if requested
+ if ( isset( $params['headers'] ) ) {
+ $hdrs += $this->sanitizeHdrs( $params['headers'] );
}
if ( !empty( $params['async'] ) ) { // deferred
$op = $sContObj->move_object_to_async( $srcRel, $dContObj, $dstRel, null, $hdrs );
@@ -534,10 +514,6 @@ class SwiftFileBackend extends FileBackendStore {
}
}
- /**
- * @see FileBackendStore::doDeleteInternal()
- * @return Status
- */
protected function doDeleteInternal( array $params ) {
$status = Status::newGood();
@@ -590,10 +566,6 @@ class SwiftFileBackend extends FileBackendStore {
}
}
- /**
- * @see FileBackendStore::doDescribeInternal()
- * @return Status
- */
protected function doDescribeInternal( array $params ) {
$status = Status::newGood();
@@ -603,19 +575,15 @@ class SwiftFileBackend extends FileBackendStore {
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;
+ if ( isset( $params['headers'] ) ) {
+ $srcObj->headers = $this->sanitizeHdrs( $params['headers'] ) + $srcObj->headers;
+ }
$srcObj->sync_metadata(); // save to Swift
$this->purgeCDNCache( array( $srcObj ) );
} catch ( CDNNotEnabledException $e ) {
@@ -631,10 +599,6 @@ class SwiftFileBackend extends FileBackendStore {
return $status;
}
- /**
- * @see FileBackendStore::doPrepareInternal()
- * @return Status
- */
protected function doPrepareInternal( $fullCont, $dir, array $params ) {
$status = Status::newGood();
@@ -748,10 +712,6 @@ class SwiftFileBackend extends FileBackendStore {
return $status;
}
- /**
- * @see FileBackendStore::doCleanInternal()
- * @return Status
- */
protected function doCleanInternal( $fullCont, $dir, array $params ) {
$status = Status::newGood();
@@ -787,10 +747,6 @@ class SwiftFileBackend extends FileBackendStore {
return $status;
}
- /**
- * @see FileBackendStore::doFileExists()
- * @return array|bool|null
- */
protected function doGetFileStat( array $params ) {
list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $params['src'] );
if ( $srcRel === null ) {
@@ -805,8 +761,8 @@ class SwiftFileBackend extends FileBackendStore {
$stat = array(
// 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->getMetadataValue( 'Sha1base36' )
+ 'size' => (int)$srcObj->content_length,
+ 'sha1' => $srcObj->getMetadataValue( 'Sha1base36' )
);
} catch ( NoSuchContainerException $e ) {
} catch ( NoSuchObjectException $e ) {
@@ -821,7 +777,7 @@ class SwiftFileBackend extends FileBackendStore {
/**
* Fill in any missing object metadata and save it to Swift
*
- * @param $obj CF_Object
+ * @param CF_Object $obj
* @param string $path Storage path to object
* @return bool Success
* @throws Exception cloudfiles exceptions
@@ -852,10 +808,6 @@ class SwiftFileBackend extends FileBackendStore {
return false; // failed
}
- /**
- * @see FileBackendStore::doGetFileContentsMulti()
- * @return Array
- */
protected function doGetFileContentsMulti( array $params ) {
$contents = array();
@@ -965,24 +917,25 @@ class SwiftFileBackend extends FileBackendStore {
* @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 array $params Includes flag for 'topOnly'
- * @return Array List of relative paths of dirs directly under $dir
+ * @param integer $limit Max number of items to list
+ * @param array $params Parameters for getDirectoryList()
+ * @return Array List of resolved paths of directories directly under $dir
+ * @throws FileBackendError
*/
public function getDirListPageInternal( $fullCont, $dir, &$after, $limit, array $params ) {
$dirs = array();
if ( $after === INF ) {
return $dirs; // nothing more
}
- wfProfileIn( __METHOD__ . '-' . $this->name );
+ $section = new ProfileSection( __METHOD__ . '-' . $this->name );
try {
$container = $this->getContainer( $fullCont );
$prefix = ( $dir == '' ) ? null : "{$dir}/";
// Non-recursive: only list dirs right under $dir
if ( !empty( $params['topOnly'] ) ) {
$objects = $container->list_objects( $limit, $after, $prefix, null, '/' );
- foreach ( $objects as $object ) { // files and dirs
+ foreach ( $objects as $object ) { // files and directories
if ( substr( $object, -1 ) === '/' ) {
$dirs[] = $object; // directories end in '/'
}
@@ -1013,6 +966,7 @@ class SwiftFileBackend extends FileBackendStore {
}
}
}
+ // Page on the unfiltered directory listing (what is returned may be filtered)
if ( count( $objects ) < $limit ) {
$after = INF; // avoid a second RTT
} else {
@@ -1022,9 +976,9 @@ class SwiftFileBackend extends FileBackendStore {
} catch ( CloudFilesException $e ) { // some other exception?
$this->handleException( $e, null, __METHOD__,
array( 'cont' => $fullCont, 'dir' => $dir ) );
+ throw new FileBackendError( "Got " . get_class( $e ) . " exception." );
}
- wfProfileOut( __METHOD__ . '-' . $this->name );
return $dirs;
}
@@ -1038,33 +992,49 @@ class SwiftFileBackend extends FileBackendStore {
* @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 array $params Includes flag for 'topOnly'
- * @return Array List of relative paths of files under $dir
+ * @param integer $limit Max number of items to list
+ * @param array $params Parameters for getDirectoryList()
+ * @return Array List of resolved paths of files under $dir
+ * @throws FileBackendError
*/
public function getFileListPageInternal( $fullCont, $dir, &$after, $limit, array $params ) {
$files = array();
if ( $after === INF ) {
return $files; // nothing more
}
- wfProfileIn( __METHOD__ . '-' . $this->name );
+ $section = new ProfileSection( __METHOD__ . '-' . $this->name );
try {
$container = $this->getContainer( $fullCont );
$prefix = ( $dir == '' ) ? null : "{$dir}/";
// Non-recursive: only list files right under $dir
if ( !empty( $params['topOnly'] ) ) { // files and dirs
- $objects = $container->list_objects( $limit, $after, $prefix, null, '/' );
- foreach ( $objects as $object ) {
- if ( substr( $object, -1 ) !== '/' ) {
- $files[] = $object; // directories end in '/'
+ if ( !empty( $params['adviseStat'] ) ) {
+ $limit = min( $limit, self::CACHE_CHEAP_SIZE );
+ // Note: get_objects() does not include directories
+ $objects = $this->loadObjectListing( $params, $dir,
+ $container->get_objects( $limit, $after, $prefix, null, '/' ) );
+ $files = $objects;
+ } else {
+ $objects = $container->list_objects( $limit, $after, $prefix, null, '/' );
+ foreach ( $objects as $object ) { // files and directories
+ if ( substr( $object, -1 ) !== '/' ) {
+ $files[] = $object; // directories end in '/'
+ }
}
}
// Recursive: list all files under $dir and its subdirs
} else { // files
- $objects = $container->list_objects( $limit, $after, $prefix );
+ if ( !empty( $params['adviseStat'] ) ) {
+ $limit = min( $limit, self::CACHE_CHEAP_SIZE );
+ $objects = $this->loadObjectListing( $params, $dir,
+ $container->get_objects( $limit, $after, $prefix ) );
+ } else {
+ $objects = $container->list_objects( $limit, $after, $prefix );
+ }
$files = $objects;
}
+ // Page on the unfiltered object listing (what is returned may be filtered)
if ( count( $objects ) < $limit ) {
$after = INF; // avoid a second RTT
} else {
@@ -1074,29 +1044,57 @@ class SwiftFileBackend extends FileBackendStore {
} catch ( CloudFilesException $e ) { // some other exception?
$this->handleException( $e, null, __METHOD__,
array( 'cont' => $fullCont, 'dir' => $dir ) );
+ throw new FileBackendError( "Got " . get_class( $e ) . " exception." );
}
- wfProfileOut( __METHOD__ . '-' . $this->name );
return $files;
}
/**
- * @see FileBackendStore::doGetFileSha1base36()
- * @return bool
+ * Load a list of objects that belong under $dir into stat cache
+ * and return a list of the names of the objects in the same order.
+ *
+ * @param array $params Parameters for getDirectoryList()
+ * @param string $dir Resolved container directory path
+ * @param array $cfObjects List of CF_Object items
+ * @return array List of object names
*/
+ private function loadObjectListing( array $params, $dir, array $cfObjects ) {
+ $names = array();
+ $storageDir = rtrim( $params['dir'], '/' );
+ $suffixStart = ( $dir === '' ) ? 0 : strlen( $dir ) + 1; // size of "path/to/dir/"
+ // Iterate over the list *backwards* as this primes the stat cache, which is LRU.
+ // If this fills the cache and the caller stats an uncached file before stating
+ // the ones on the listing, there would be zero cache hits if this went forwards.
+ for ( end( $cfObjects ); key( $cfObjects ) !== null; prev( $cfObjects ) ) {
+ $object = current( $cfObjects );
+ $path = "{$storageDir}/" . substr( $object->name, $suffixStart );
+ $val = array(
+ // Convert dates like "Tue, 03 Jan 2012 22:01:04 GMT" to TS_MW
+ 'mtime' => wfTimestamp( TS_MW, $object->last_modified ),
+ 'size' => (int)$object->content_length,
+ 'latest' => false // eventually consistent
+ );
+ $this->cheapCache->set( $path, 'stat', $val );
+ $names[] = $object->name;
+ }
+ return array_reverse( $names ); // keep the paths in original order
+ }
+
protected function doGetFileSha1base36( array $params ) {
$stat = $this->getFileStat( $params );
if ( $stat ) {
+ if ( !isset( $stat['sha1'] ) ) {
+ // Stat entries filled by file listings don't include SHA1
+ $this->clearCache( array( $params['src'] ) );
+ $stat = $this->getFileStat( $params );
+ }
return $stat['sha1'];
} else {
return false;
}
}
- /**
- * @see FileBackendStore::doStreamFile()
- * @return Status
- */
protected function doStreamFile( array $params ) {
$status = Status::newGood();
@@ -1128,10 +1126,6 @@ class SwiftFileBackend extends FileBackendStore {
return $status;
}
- /**
- * @see FileBackendStore::doGetLocalCopyMulti()
- * @return null|TempFSFile
- */
protected function doGetLocalCopyMulti( array $params ) {
$tmpFiles = array();
@@ -1201,10 +1195,6 @@ class SwiftFileBackend extends FileBackendStore {
return $tmpFiles;
}
- /**
- * @see FileBackendStore::getFileHttpUrl()
- * @return string|null
- */
public function getFileHttpUrl( array $params ) {
if ( $this->swiftTempUrlKey != '' ||
( $this->rgwS3AccessKey != '' && $this->rgwS3SecretKey != '' ) )
@@ -1237,8 +1227,8 @@ class SwiftFileBackend extends FileBackendStore {
str_replace( '/swift/v1', '', // S3 API is the rgw default
$sContObj->cfs_http->getStorageUrl() . $spath ),
array(
- 'Signature' => $signature,
- 'Expires' => $expires,
+ 'Signature' => $signature,
+ 'Expires' => $expires,
'AWSAccessKeyId' => $this->rgwS3AccessKey )
);
}
@@ -1250,10 +1240,6 @@ class SwiftFileBackend extends FileBackendStore {
return null;
}
- /**
- * @see FileBackendStore::directoriesAreVirtual()
- * @return bool
- */
protected function directoriesAreVirtual() {
return true;
}
@@ -1274,10 +1260,6 @@ class SwiftFileBackend extends FileBackendStore {
return $hdrs;
}
- /**
- * @see FileBackendStore::doExecuteOpHandlesInternal()
- * @return Array List of corresponding Status objects
- */
protected function doExecuteOpHandlesInternal( array $fileOpHandles ) {
$statuses = array();
@@ -1314,7 +1296,7 @@ class SwiftFileBackend extends FileBackendStore {
* 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.
+ * matches the expression and the request is 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:
@@ -1325,7 +1307,7 @@ class SwiftFileBackend extends FileBackendStore {
* In general, we don't allow listings to end-users. It's not useful, isn't well-defined
* (lists are truncated to 10000 item with no way to page), and is just a performance risk.
*
- * @param $contObj CF_Container Swift container
+ * @param CF_Container $contObj Swift container
* @param array $readGrps List of read access routes
* @param array $writeGrps List of write access routes
* @return Status
@@ -1395,12 +1377,12 @@ class SwiftFileBackend extends FileBackendStore {
if ( is_array( $creds ) ) { // cache hit
$this->auth->load_cached_credentials(
$creds['auth_token'], $creds['storage_url'], $creds['cdnm_url'] );
- $this->sessionStarted = time() - ceil( $this->authTTL/2 ); // skew for worst case
+ $this->sessionStarted = time() - ceil( $this->authTTL / 2 ); // skew for worst case
} else { // cache miss
try {
$this->auth->authenticate();
$creds = $this->auth->export_credentials();
- $this->srvCache->add( $cacheKey, $creds, ceil( $this->authTTL/2 ) ); // cache
+ $this->srvCache->add( $cacheKey, $creds, ceil( $this->authTTL / 2 ) ); // cache
$this->sessionStarted = time();
} catch ( CloudFilesException $e ) {
$this->connException = $e; // don't keep re-trying
@@ -1433,7 +1415,7 @@ class SwiftFileBackend extends FileBackendStore {
/**
* Get the cache key for a container
*
- * @param $username string
+ * @param string $username
* @return string
*/
private function getCredsCacheKey( $username ) {
@@ -1496,10 +1478,6 @@ class SwiftFileBackend extends FileBackendStore {
$conn->delete_container( $container );
}
- /**
- * @see FileBackendStore::doPrimeContainerCache()
- * @return void
- */
protected function doPrimeContainerCache( array $containerInfo ) {
try {
$conn = $this->getConnection(); // Swift proxy connection
@@ -1517,9 +1495,9 @@ class SwiftFileBackend extends FileBackendStore {
* Log an unexpected exception for this backend.
* This also sets the Status object to have a fatal error.
*
- * @param $e Exception
- * @param $status Status|null
- * @param $func string
+ * @param Exception $e
+ * @param Status $status|null
+ * @param string $func
* @param array $params
* @return void
*/
@@ -1554,7 +1532,15 @@ class SwiftFileOpHandle extends FileBackendStoreOpHandle {
/** @var Array */
public $affectedObjects = array();
- public function __construct( $backend, array $params, $call, CF_Async_Op $cfOp ) {
+ /**
+ * @param SwiftFileBackend $backend
+ * @param array $params
+ * @param string $call
+ * @param CF_Async_Op $cfOp
+ */
+ public function __construct(
+ SwiftFileBackend $backend, array $params, $call, CF_Async_Op $cfOp
+ ) {
$this->backend = $backend;
$this->params = $params;
$this->call = $call;
@@ -1586,7 +1572,7 @@ abstract class SwiftFileBackendList implements Iterator {
const PAGE_SIZE = 9000; // file listing buffer size
/**
- * @param $backend SwiftFileBackend
+ * @param SwiftFileBackend $backend
* @param string $fullCont Resolved container name
* @param string $dir Resolved directory relative to container
* @param array $params
@@ -1660,10 +1646,10 @@ abstract class SwiftFileBackendList implements Iterator {
*
* @param string $container Resolved container name
* @param string $dir Resolved path relative to container
- * @param $after string|null
- * @param $limit integer
+ * @param string $after|null
+ * @param integer $limit
* @param array $params
- * @return Traversable|Array|null Returns null on failure
+ * @return Traversable|Array
*/
abstract protected function pageFromList( $container, $dir, &$after, $limit, array $params );
}
@@ -1682,7 +1668,7 @@ class SwiftFileBackendDirList extends SwiftFileBackendList {
/**
* @see SwiftFileBackendList::pageFromList()
- * @return Array|null
+ * @return Array
*/
protected function pageFromList( $container, $dir, &$after, $limit, array $params ) {
return $this->backend->getDirListPageInternal( $container, $dir, $after, $limit, $params );
@@ -1703,7 +1689,7 @@ class SwiftFileBackendFileList extends SwiftFileBackendList {
/**
* @see SwiftFileBackendList::pageFromList()
- * @return Array|null
+ * @return Array
*/
protected function pageFromList( $container, $dir, &$after, $limit, array $params ) {
return $this->backend->getFileListPageInternal( $container, $dir, $after, $limit, $params );
diff --git a/includes/filebackend/TempFSFile.php b/includes/filebackend/TempFSFile.php
index 11e125c1..8266e420 100644
--- a/includes/filebackend/TempFSFile.php
+++ b/includes/filebackend/TempFSFile.php
@@ -37,8 +37,8 @@ class TempFSFile extends FSFile {
* Make a new temporary file on the file system.
* Temporary files may be purged when the file object falls out of scope.
*
- * @param $prefix string
- * @param $extension string
+ * @param string $prefix
+ * @param string $extension
* @return TempFSFile|null
*/
public static function factory( $prefix, $extension = '' ) {
@@ -81,7 +81,7 @@ class TempFSFile extends FSFile {
/**
* Clean up the temporary file only after an object goes out of scope
*
- * @param $object Object
+ * @param Object $object
* @return TempFSFile This object
*/
public function bind( $object ) {
diff --git a/includes/filebackend/filejournal/DBFileJournal.php b/includes/filebackend/filejournal/DBFileJournal.php
index 73f29a95..9250aa5e 100644
--- a/includes/filebackend/filejournal/DBFileJournal.php
+++ b/includes/filebackend/filejournal/DBFileJournal.php
@@ -65,11 +65,11 @@ class DBFileJournal extends FileJournal {
foreach ( $entries as $entry ) {
$data[] = array(
'fj_batch_uuid' => $batchId,
- 'fj_backend' => $this->backend,
- 'fj_op' => $entry['op'],
- 'fj_path' => $entry['path'],
- 'fj_new_sha1' => $entry['newSha1'],
- 'fj_timestamp' => $dbw->timestamp( $now )
+ 'fj_backend' => $this->backend,
+ 'fj_op' => $entry['op'],
+ 'fj_path' => $entry['path'],
+ 'fj_new_sha1' => $entry['newSha1'],
+ 'fj_timestamp' => $dbw->timestamp( $now )
);
}
diff --git a/includes/filebackend/lockmanager/DBLockManager.php b/includes/filebackend/lockmanager/DBLockManager.php
index f02387dc..3e934ba5 100644
--- a/includes/filebackend/lockmanager/DBLockManager.php
+++ b/includes/filebackend/lockmanager/DBLockManager.php
@@ -110,6 +110,19 @@ abstract class DBLockManager extends QuorumLockManager {
$this->session = wfRandomString( 31 );
}
+ // @TODO: change this code to work in one batch
+ protected function getLocksOnServer( $lockSrv, array $pathsByType ) {
+ $status = Status::newGood();
+ foreach ( $pathsByType as $type => $paths ) {
+ $status->merge( $this->doGetLocksOnServer( $lockSrv, $paths, $type ) );
+ }
+ return $status;
+ }
+
+ protected function freeLocksOnServer( $lockSrv, array $pathsByType ) {
+ return Status::newGood();
+ }
+
/**
* @see QuorumLockManager::isServerUp()
* @return bool
@@ -252,7 +265,7 @@ class MySqlLockManager extends DBLockManager {
* @see DBLockManager::getLocksOnServer()
* @return Status
*/
- protected function getLocksOnServer( $lockSrv, array $paths, $type ) {
+ protected function doGetLocksOnServer( $lockSrv, array $paths, $type ) {
$status = Status::newGood();
$db = $this->getConnection( $lockSrv ); // checked in isServerUp()
@@ -319,14 +332,6 @@ class MySqlLockManager extends DBLockManager {
}
/**
- * @see QuorumLockManager::freeLocksOnServer()
- * @return Status
- */
- protected function freeLocksOnServer( $lockSrv, array $paths, $type ) {
- return Status::newGood(); // not supported
- }
-
- /**
* @see QuorumLockManager::releaseAllLocks()
* @return Status
*/
@@ -361,7 +366,7 @@ class PostgreSqlLockManager extends DBLockManager {
self::LOCK_EX => self::LOCK_EX
);
- protected function getLocksOnServer( $lockSrv, array $paths, $type ) {
+ protected function doGetLocksOnServer( $lockSrv, array $paths, $type ) {
$status = Status::newGood();
if ( !count( $paths ) ) {
return $status; // nothing to lock
@@ -369,7 +374,9 @@ class PostgreSqlLockManager extends DBLockManager {
$db = $this->getConnection( $lockSrv ); // checked in isServerUp()
$bigints = array_unique( array_map(
- function( $key ) { return wfBaseConvert( substr( $key, 0, 15 ), 16, 10 ); },
+ function( $key ) {
+ return wfBaseConvert( substr( $key, 0, 15 ), 16, 10 );
+ },
array_map( array( $this, 'sha1Base16Absolute' ), $paths )
) );
@@ -406,14 +413,6 @@ class PostgreSqlLockManager extends DBLockManager {
}
/**
- * @see QuorumLockManager::freeLocksOnServer()
- * @return Status
- */
- protected function freeLocksOnServer( $lockSrv, array $paths, $type ) {
- return Status::newGood(); // not supported
- }
-
- /**
* @see QuorumLockManager::releaseAllLocks()
* @return Status
*/
diff --git a/includes/filebackend/lockmanager/LockManager.php b/includes/filebackend/lockmanager/LockManager.php
index 0512a01b..dad8a624 100644
--- a/includes/filebackend/lockmanager/LockManager.php
+++ b/includes/filebackend/lockmanager/LockManager.php
@@ -56,7 +56,7 @@ abstract class LockManager {
protected $domain; // string; domain (usually wiki ID)
protected $lockTTL; // integer; maximum time locks can be held
- /* Lock types; stronger locks have higher values */
+ /** 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)
const LOCK_EX = 3; // exclusive lock (for writes)
@@ -76,10 +76,10 @@ abstract class LockManager {
if ( isset( $config['lockTTL'] ) ) {
$this->lockTTL = max( 1, $config['lockTTL'] );
} elseif ( PHP_SAPI === 'cli' ) {
- $this->lockTTL = 2*3600;
+ $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 );
+ $this->lockTTL = max( 5 * 60, 2 * (int)$met );
}
}
@@ -88,11 +88,36 @@ abstract class LockManager {
*
* @param array $paths List of resource names
* @param $type integer LockManager::LOCK_* constant
+ * @param integer $timeout Timeout in seconds (0 means non-blocking) (since 1.21)
* @return Status
*/
- final public function lock( array $paths, $type = self::LOCK_EX ) {
+ final public function lock( array $paths, $type = self::LOCK_EX, $timeout = 0 ) {
+ return $this->lockByType( array( $type => $paths ), $timeout );
+ }
+
+ /**
+ * Lock the resources at the given abstract paths
+ *
+ * @param array $pathsByType Map of LockManager::LOCK_* constants to lists of paths
+ * @param integer $timeout Timeout in seconds (0 means non-blocking) (since 1.21)
+ * @return Status
+ * @since 1.22
+ */
+ final public function lockByType( array $pathsByType, $timeout = 0 ) {
wfProfileIn( __METHOD__ );
- $status = $this->doLock( array_unique( $paths ), $this->lockTypeMap[$type] );
+ $status = Status::newGood();
+ $pathsByType = $this->normalizePathsByType( $pathsByType );
+ $msleep = array( 0, 50, 100, 300, 500 ); // retry backoff times
+ $start = microtime( true );
+ do {
+ $status = $this->doLockByType( $pathsByType );
+ $elapsed = microtime( true ) - $start;
+ if ( $status->isOK() || $elapsed >= $timeout || $elapsed < 0 ) {
+ break; // success, timeout, or clock set back
+ }
+ usleep( 1e3 * ( next( $msleep ) ?: 1000 ) ); // use 1 sec after enough times
+ $elapsed = microtime( true ) - $start;
+ } while ( $elapsed < $timeout && $elapsed >= 0 );
wfProfileOut( __METHOD__ );
return $status;
}
@@ -100,13 +125,25 @@ abstract class LockManager {
/**
* Unlock the resources at the given abstract paths
*
- * @param array $paths List of storage paths
+ * @param array $paths List of paths
* @param $type integer LockManager::LOCK_* constant
* @return Status
*/
final public function unlock( array $paths, $type = self::LOCK_EX ) {
+ return $this->unlockByType( array( $type => $paths ) );
+ }
+
+ /**
+ * Unlock the resources at the given abstract paths
+ *
+ * @param array $pathsByType Map of LockManager::LOCK_* constants to lists of paths
+ * @return Status
+ * @since 1.22
+ */
+ final public function unlockByType( array $pathsByType ) {
wfProfileIn( __METHOD__ );
- $status = $this->doUnlock( array_unique( $paths ), $this->lockTypeMap[$type] );
+ $pathsByType = $this->normalizePathsByType( $pathsByType );
+ $status = $this->doUnlockByType( $pathsByType );
wfProfileOut( __METHOD__ );
return $status;
}
@@ -136,20 +173,74 @@ abstract class LockManager {
}
/**
+ * Normalize the $paths array by converting LOCK_UW locks into the
+ * appropriate type and removing any duplicated paths for each lock type.
+ *
+ * @param array $paths Map of LockManager::LOCK_* constants to lists of paths
+ * @return Array
+ * @since 1.22
+ */
+ final protected function normalizePathsByType( array $pathsByType ) {
+ $res = array();
+ foreach ( $pathsByType as $type => $paths ) {
+ $res[$this->lockTypeMap[$type]] = array_unique( $paths );
+ }
+ return $res;
+ }
+
+ /**
+ * @see LockManager::lockByType()
+ * @param array $paths Map of LockManager::LOCK_* constants to lists of paths
+ * @return Status
+ * @since 1.22
+ */
+ protected function doLockByType( array $pathsByType ) {
+ $status = Status::newGood();
+ $lockedByType = array(); // map of (type => paths)
+ foreach ( $pathsByType as $type => $paths ) {
+ $status->merge( $this->doLock( $paths, $type ) );
+ if ( $status->isOK() ) {
+ $lockedByType[$type] = $paths;
+ } else {
+ // Release the subset of locks that were acquired
+ foreach ( $lockedByType as $type => $paths ) {
+ $status->merge( $this->doUnlock( $paths, $type ) );
+ }
+ break;
+ }
+ }
+ return $status;
+ }
+
+ /**
* Lock resources with the given keys and lock type
*
- * @param array $paths List of storage paths
+ * @param array $paths List of paths
* @param $type integer LockManager::LOCK_* constant
- * @return string
+ * @return Status
*/
abstract protected function doLock( array $paths, $type );
/**
+ * @see LockManager::unlockByType()
+ * @param array $paths Map of LockManager::LOCK_* constants to lists of paths
+ * @return Status
+ * @since 1.22
+ */
+ protected function doUnlockByType( array $pathsByType ) {
+ $status = Status::newGood();
+ foreach ( $pathsByType as $type => $paths ) {
+ $status->merge( $this->doUnlock( $paths, $type ) );
+ }
+ return $status;
+ }
+
+ /**
* Unlock resources with the given keys and lock type
*
- * @param array $paths List of storage paths
+ * @param array $paths List of paths
* @param $type integer LockManager::LOCK_* constant
- * @return string
+ * @return Status
*/
abstract protected function doUnlock( array $paths, $type );
}
@@ -159,22 +250,10 @@ abstract class LockManager {
* @since 1.19
*/
class NullLockManager extends LockManager {
- /**
- * @see LockManager::doLock()
- * @param $paths array
- * @param $type int
- * @return Status
- */
protected function doLock( array $paths, $type ) {
return Status::newGood();
}
- /**
- * @see LockManager::doUnlock()
- * @param $paths array
- * @param $type int
- * @return Status
- */
protected function doUnlock( array $paths, $type ) {
return Status::newGood();
}
diff --git a/includes/filebackend/lockmanager/LockManagerGroup.php b/includes/filebackend/lockmanager/LockManagerGroup.php
index ac0bd49b..9aff2415 100644
--- a/includes/filebackend/lockmanager/LockManagerGroup.php
+++ b/includes/filebackend/lockmanager/LockManagerGroup.php
@@ -97,8 +97,8 @@ class LockManagerGroup {
$class = $config['class'];
unset( $config['class'] ); // lock manager won't need this
$this->managers[$name] = array(
- 'class' => $class,
- 'config' => $config,
+ 'class' => $class,
+ 'config' => $config,
'instance' => null
);
}
diff --git a/includes/filebackend/lockmanager/MemcLockManager.php b/includes/filebackend/lockmanager/MemcLockManager.php
index fafc588a..5eab03ee 100644
--- a/includes/filebackend/lockmanager/MemcLockManager.php
+++ b/includes/filebackend/lockmanager/MemcLockManager.php
@@ -88,11 +88,44 @@ class MemcLockManager extends QuorumLockManager {
$this->session = wfRandomString( 32 );
}
+ // @TODO: change this code to work in one batch
+ protected function getLocksOnServer( $lockSrv, array $pathsByType ) {
+ $status = Status::newGood();
+
+ $lockedPaths = array();
+ foreach ( $pathsByType as $type => $paths ) {
+ $status->merge( $this->doGetLocksOnServer( $lockSrv, $paths, $type ) );
+ if ( $status->isOK() ) {
+ $lockedPaths[$type] = isset( $lockedPaths[$type] )
+ ? array_merge( $lockedPaths[$type], $paths )
+ : $paths;
+ } else {
+ foreach ( $lockedPaths as $type => $paths ) {
+ $status->merge( $this->doFreeLocksOnServer( $lockSrv, $paths, $type ) );
+ }
+ break;
+ }
+ }
+
+ return $status;
+ }
+
+ // @TODO: change this code to work in one batch
+ protected function freeLocksOnServer( $lockSrv, array $pathsByType ) {
+ $status = Status::newGood();
+
+ foreach ( $pathsByType as $type => $paths ) {
+ $status->merge( $this->doFreeLocksOnServer( $lockSrv, $paths, $type ) );
+ }
+
+ return $status;
+ }
+
/**
* @see QuorumLockManager::getLocksOnServer()
* @return Status
*/
- protected function getLocksOnServer( $lockSrv, array $paths, $type ) {
+ protected function doGetLocksOnServer( $lockSrv, array $paths, $type ) {
$status = Status::newGood();
$memc = $this->getCache( $lockSrv );
@@ -145,7 +178,7 @@ class MemcLockManager extends QuorumLockManager {
foreach ( $paths as $path ) {
$locksKey = $this->recordKeyForPath( $path );
$locksHeld = $lockRecords[$locksKey];
- $ok = $memc->set( $locksKey, $locksHeld, 7*86400 );
+ $ok = $memc->set( $locksKey, $locksHeld, 7 * 86400 );
if ( !$ok ) {
$status->fatal( 'lockmanager-fail-acquirelock', $path );
} else {
@@ -164,7 +197,7 @@ class MemcLockManager extends QuorumLockManager {
* @see QuorumLockManager::freeLocksOnServer()
* @return Status
*/
- protected function freeLocksOnServer( $lockSrv, array $paths, $type ) {
+ protected function doFreeLocksOnServer( $lockSrv, array $paths, $type ) {
$status = Status::newGood();
$memc = $this->getCache( $lockSrv );
@@ -297,7 +330,7 @@ class MemcLockManager extends QuorumLockManager {
$start = microtime( true );
do {
if ( ( ++$rounds % 4 ) == 0 ) {
- usleep( 1000*50 ); // 50 ms
+ usleep( 1000 * 50 ); // 50 ms
}
foreach ( array_diff( $keys, $lockedKeys ) as $key ) {
if ( $memc->add( "$key:mutex", 1, 180 ) ) { // lock record
diff --git a/includes/filebackend/lockmanager/QuorumLockManager.php b/includes/filebackend/lockmanager/QuorumLockManager.php
index b331b540..8356d32a 100644
--- a/includes/filebackend/lockmanager/QuorumLockManager.php
+++ b/includes/filebackend/lockmanager/QuorumLockManager.php
@@ -31,81 +31,86 @@
abstract class QuorumLockManager extends LockManager {
/** @var Array Map of bucket indexes to peer server lists */
protected $srvsByBucket = array(); // (bucket index => (lsrv1, lsrv2, ...))
+ /** @var Array Map of degraded buckets */
+ protected $degradedBuckets = array(); // (buckey index => UNIX timestamp)
- /**
- * @see LockManager::doLock()
- * @param $paths array
- * @param $type int
- * @return Status
- */
final protected function doLock( array $paths, $type ) {
+ return $this->doLockByType( array( $type => $paths ) );
+ }
+
+ final protected function doUnlock( array $paths, $type ) {
+ return $this->doUnlockByType( array( $type => $paths ) );
+ }
+
+ protected function doLockByType( array $pathsByType ) {
$status = Status::newGood();
- $pathsToLock = array(); // (bucket => paths)
+ $pathsToLock = array(); // (bucket => type => 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;
+ foreach ( $pathsByType as $type => $paths ) {
+ foreach ( $paths as $path ) {
+ if ( isset( $this->locksHeld[$path][$type] ) ) {
+ ++$this->locksHeld[$path][$type];
+ } else {
+ $bucket = $this->getBucketFromPath( $path );
+ $pathsToLock[$bucket][$type][] = $path;
+ }
}
}
- $lockedPaths = array(); // files locked in this attempt
+ $lockedPaths = array(); // files locked in this attempt (type => paths)
// Attempt to acquire these locks...
- foreach ( $pathsToLock as $bucket => $paths ) {
+ foreach ( $pathsToLock as $bucket => $pathsToLockByType ) {
// Try to acquire the locks for this bucket
- $status->merge( $this->doLockingRequestBucket( $bucket, $paths, $type ) );
+ $status->merge( $this->doLockingRequestBucket( $bucket, $pathsToLockByType ) );
if ( !$status->isOK() ) {
- $status->merge( $this->doUnlock( $lockedPaths, $type ) );
+ $status->merge( $this->doUnlockByType( $lockedPaths ) );
return $status;
}
// Record these locks as active
- foreach ( $paths as $path ) {
- $this->locksHeld[$path][$type] = 1; // locked
+ foreach ( $pathsToLockByType as $type => $paths ) {
+ foreach ( $paths as $path ) {
+ $this->locksHeld[$path][$type] = 1; // locked
+ // Keep track of what locks were made in this attempt
+ $lockedPaths[$type][] = $path;
+ }
}
- // 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 ) {
+ protected function doUnlockByType( array $pathsByType ) {
$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
+ $pathsToUnlock = array(); // (bucket => type => paths)
+ foreach ( $pathsByType as $type => $paths ) {
+ 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][$type][] = $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 ) );
+ foreach ( $pathsToUnlock as $bucket => $pathsToUnlockByType ) {
+ $status->merge( $this->doUnlockingRequestBucket( $bucket, $pathsToUnlockByType ) );
}
if ( !count( $this->locksHeld ) ) {
$status->merge( $this->releaseAllLocks() );
+ $this->degradedBuckets = array(); // safe to retry the normal quorum
}
return $status;
@@ -116,25 +121,25 @@ abstract class QuorumLockManager extends LockManager {
* 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
+ * @param array $pathsByType Map of LockManager::LOCK_* constants to lists of paths
* @return Status
*/
- final protected function doLockingRequestBucket( $bucket, array $paths, $type ) {
+ final protected function doLockingRequestBucket( $bucket, array $pathsByType ) {
$status = Status::newGood();
$yesVotes = 0; // locks made on trustable servers
$votesLeft = count( $this->srvsByBucket[$bucket] ); // remaining peers
- $quorum = floor( $votesLeft/2 + 1 ); // simple majority
+ $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 );
+ $this->degradedBuckets[$bucket] = time();
continue; // server down?
}
// Attempt to acquire the lock on this peer
- $status->merge( $this->getLocksOnServer( $lockSrv, $paths, $type ) );
+ $status->merge( $this->getLocksOnServer( $lockSrv, $pathsByType ) );
if ( !$status->isOK() ) {
return $status; // vetoed; resource locked
}
@@ -158,21 +163,33 @@ abstract class QuorumLockManager extends LockManager {
* 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
+ * @param array $pathsByType Map of LockManager::LOCK_* constants to lists of paths
* @return Status
*/
- final protected function doUnlockingRequestBucket( $bucket, array $paths, $type ) {
+ final protected function doUnlockingRequestBucket( $bucket, array $pathsByType ) {
$status = Status::newGood();
+ $yesVotes = 0; // locks freed on trustable servers
+ $votesLeft = count( $this->srvsByBucket[$bucket] ); // remaining peers
+ $quorum = floor( $votesLeft / 2 + 1 ); // simple majority
+ $isDegraded = isset( $this->degradedBuckets[$bucket] ); // not the normal quorum?
foreach ( $this->srvsByBucket[$bucket] as $lockSrv ) {
if ( !$this->isServerUp( $lockSrv ) ) {
- $status->fatal( 'lockmanager-fail-svr-release', $lockSrv );
+ $status->warning( 'lockmanager-fail-svr-release', $lockSrv );
// Attempt to release the lock on this peer
} else {
- $status->merge( $this->freeLocksOnServer( $lockSrv, $paths, $type ) );
+ $status->merge( $this->freeLocksOnServer( $lockSrv, $pathsByType ) );
+ ++$yesVotes; // success for this peer
+ // Normally the first peers form the quorum, and the others are ignored.
+ // Ignore them in this case, but not when an alternative quorum was used.
+ if ( $yesVotes >= $quorum && !$isDegraded ) {
+ break; // lock released
+ }
}
}
+ // Set a bad status if the quorum was not met.
+ // Assumes the same "up" servers as during the acquire step.
+ $status->setResult( $yesVotes >= $quorum );
return $status;
}
@@ -190,7 +207,8 @@ abstract class QuorumLockManager extends LockManager {
}
/**
- * Check if a lock server is up
+ * Check if a lock server is up.
+ * This should process cache results to reduce RTT.
*
* @param $lockSrv string
* @return bool
@@ -198,14 +216,13 @@ abstract class QuorumLockManager extends LockManager {
abstract protected function isServerUp( $lockSrv );
/**
- * Get a connection to a lock server and acquire locks on $paths
+ * Get a connection to a lock server and acquire locks
*
* @param $lockSrv string
- * @param $paths array
- * @param $type integer
+ * @param array $pathsByType Map of LockManager::LOCK_* constants to lists of paths
* @return Status
*/
- abstract protected function getLocksOnServer( $lockSrv, array $paths, $type );
+ abstract protected function getLocksOnServer( $lockSrv, array $pathsByType );
/**
* Get a connection to a lock server and release locks on $paths.
@@ -213,11 +230,10 @@ abstract class QuorumLockManager extends LockManager {
* Subclasses must effectively implement this or releaseAllLocks().
*
* @param $lockSrv string
- * @param $paths array
- * @param $type integer
+ * @param array $pathsByType Map of LockManager::LOCK_* constants to lists of paths
* @return Status
*/
- abstract protected function freeLocksOnServer( $lockSrv, array $paths, $type );
+ abstract protected function freeLocksOnServer( $lockSrv, array $pathsByType );
/**
* Release all locks that this session is holding.
diff --git a/includes/filebackend/lockmanager/RedisLockManager.php b/includes/filebackend/lockmanager/RedisLockManager.php
new file mode 100644
index 00000000..43b0198a
--- /dev/null
+++ b/includes/filebackend/lockmanager/RedisLockManager.php
@@ -0,0 +1,288 @@
+<?php
+/**
+ * Version of LockManager based on using redis servers.
+ *
+ * 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
+ */
+
+/**
+ * Manage locks using redis servers.
+ *
+ * Version of LockManager based on using redis servers.
+ * 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 redis.
+ * A majority of peers must agree for a lock to be acquired.
+ *
+ * This class requires Redis 2.6 as it makes use Lua scripts for fast atomic operations.
+ *
+ * @ingroup LockManager
+ * @since 1.22
+ */
+class RedisLockManager extends QuorumLockManager {
+ /** @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
+ );
+
+ /** @var RedisConnectionPool */
+ protected $redisPool;
+ /** @var Array Map server names to hostname/IP and port numbers */
+ protected $lockServers = array();
+
+ protected $session = ''; // string; random UUID
+
+ /**
+ * Construct a new instance from configuration.
+ *
+ * $config paramaters include:
+ * - lockServers : Associative array of server names to "<IP>:<port>" strings.
+ * - srvsByBucket : Array of 1-16 consecutive integer keys, starting from 0,
+ * each having an odd-numbered list of server names (peers) as values.
+ * - redisConfig : Configuration for RedisConnectionPool::__construct().
+ *
+ * @param Array $config
+ * @throws MWException
+ */
+ public function __construct( array $config ) {
+ parent::__construct( $config );
+
+ $this->lockServers = $config['lockServers'];
+ // Sanitize srvsByBucket config to prevent PHP errors
+ $this->srvsByBucket = array_filter( $config['srvsByBucket'], 'is_array' );
+ $this->srvsByBucket = array_values( $this->srvsByBucket ); // consecutive
+
+ $config['redisConfig']['serializer'] = 'none';
+ $this->redisPool = RedisConnectionPool::singleton( $config['redisConfig'] );
+
+ $this->session = wfRandomString( 32 );
+ }
+
+ // @TODO: change this code to work in one batch
+ protected function getLocksOnServer( $lockSrv, array $pathsByType ) {
+ $status = Status::newGood();
+
+ $lockedPaths = array();
+ foreach ( $pathsByType as $type => $paths ) {
+ $status->merge( $this->doGetLocksOnServer( $lockSrv, $paths, $type ) );
+ if ( $status->isOK() ) {
+ $lockedPaths[$type] = isset( $lockedPaths[$type] )
+ ? array_merge( $lockedPaths[$type], $paths )
+ : $paths;
+ } else {
+ foreach ( $lockedPaths as $type => $paths ) {
+ $status->merge( $this->doFreeLocksOnServer( $lockSrv, $paths, $type ) );
+ }
+ break;
+ }
+ }
+
+ return $status;
+ }
+
+ // @TODO: change this code to work in one batch
+ protected function freeLocksOnServer( $lockSrv, array $pathsByType ) {
+ $status = Status::newGood();
+
+ foreach ( $pathsByType as $type => $paths ) {
+ $status->merge( $this->doFreeLocksOnServer( $lockSrv, $paths, $type ) );
+ }
+
+ return $status;
+ }
+
+ protected function doGetLocksOnServer( $lockSrv, array $paths, $type ) {
+ $status = Status::newGood();
+
+ $server = $this->lockServers[$lockSrv];
+ $conn = $this->redisPool->getConnection( $server );
+ if ( !$conn ) {
+ foreach ( $paths as $path ) {
+ $status->fatal( 'lockmanager-fail-acquirelock', $path );
+ }
+ return $status;
+ }
+
+ $keys = array_map( array( $this, 'recordKeyForPath' ), $paths ); // lock records
+
+ try {
+ static $script =
+<<<LUA
+ if ARGV[1] ~= 'EX' and ARGV[1] ~= 'SH' then
+ return redis.error_reply('Unrecognized lock type given (must be EX or SH)')
+ end
+ local failed = {}
+ -- Check that all the locks can be acquired
+ for i,resourceKey in ipairs(KEYS) do
+ local keyIsFree = true
+ local currentLocks = redis.call('hKeys',resourceKey)
+ for i,lockKey in ipairs(currentLocks) do
+ local _, _, type, session = string.find(lockKey,"(%w+):(%w+)")
+ -- Check any locks that are not owned by this session
+ if session ~= ARGV[2] then
+ local lockTimestamp = redis.call('hGet',resourceKey,lockKey)
+ if 1*lockTimestamp < ( ARGV[4] - ARGV[3] ) then
+ -- Lock is stale, so just prune it out
+ redis.call('hDel',resourceKey,lockKey)
+ elseif ARGV[1] == 'EX' or type == 'EX' then
+ keyIsFree = false
+ break
+ end
+ end
+ end
+ if not keyIsFree then
+ failed[#failed+1] = resourceKey
+ end
+ end
+ -- If all locks could be acquired, then do so
+ if #failed == 0 then
+ for i,resourceKey in ipairs(KEYS) do
+ redis.call('hSet',resourceKey,ARGV[1] .. ':' .. ARGV[2],ARGV[4])
+ -- In addition to invalidation logic, be sure to garbage collect
+ redis.call('expire',resourceKey,ARGV[3])
+ end
+ end
+ return failed
+LUA;
+ $res = $conn->luaEval( $script,
+ array_merge(
+ $keys, // KEYS[0], KEYS[1],...KEYS[N]
+ array(
+ $type === self::LOCK_SH ? 'SH' : 'EX', // ARGV[1]
+ $this->session, // ARGV[2]
+ $this->lockTTL, // ARGV[3]
+ time() // ARGV[4]
+ )
+ ),
+ count( $keys ) # number of first argument(s) that are keys
+ );
+ } catch ( RedisException $e ) {
+ $res = false;
+ $this->redisPool->handleException( $server, $conn, $e );
+ }
+
+ if ( $res === false ) {
+ foreach ( $paths as $path ) {
+ $status->fatal( 'lockmanager-fail-acquirelock', $path );
+ }
+ } else {
+ $pathsByKey = array_combine( $keys, $paths );
+ foreach ( $res as $key ) {
+ $status->fatal( 'lockmanager-fail-acquirelock', $pathsByKey[$key] );
+ }
+ }
+
+ return $status;
+ }
+
+ protected function doFreeLocksOnServer( $lockSrv, array $paths, $type ) {
+ $status = Status::newGood();
+
+ $server = $this->lockServers[$lockSrv];
+ $conn = $this->redisPool->getConnection( $server );
+ if ( !$conn ) {
+ foreach ( $paths as $path ) {
+ $status->fatal( 'lockmanager-fail-releaselock', $path );
+ }
+ return $status;
+ }
+
+ $keys = array_map( array( $this, 'recordKeyForPath' ), $paths ); // lock records
+
+ try {
+ static $script =
+<<<LUA
+ if ARGV[1] ~= 'EX' and ARGV[1] ~= 'SH' then
+ return redis.error_reply('Unrecognized lock type given (must be EX or SH)')
+ end
+ local failed = {}
+ for i,resourceKey in ipairs(KEYS) do
+ local released = redis.call('hDel',resourceKey,ARGV[1] .. ':' .. ARGV[2])
+ if released > 0 then
+ -- Remove the whole structure if it is now empty
+ if redis.call('hLen',resourceKey) == 0 then
+ redis.call('del',resourceKey)
+ end
+ else
+ failed[#failed+1] = resourceKey
+ end
+ end
+ return failed
+LUA;
+ $res = $conn->luaEval( $script,
+ array_merge(
+ $keys, // KEYS[0], KEYS[1],...KEYS[N]
+ array(
+ $type === self::LOCK_SH ? 'SH' : 'EX', // ARGV[1]
+ $this->session // ARGV[2]
+ )
+ ),
+ count( $keys ) # number of first argument(s) that are keys
+ );
+ } catch ( RedisException $e ) {
+ $res = false;
+ $this->redisPool->handleException( $server, $conn, $e );
+ }
+
+ if ( $res === false ) {
+ foreach ( $paths as $path ) {
+ $status->fatal( 'lockmanager-fail-releaselock', $path );
+ }
+ } else {
+ $pathsByKey = array_combine( $keys, $paths );
+ foreach ( $res as $key ) {
+ $status->fatal( 'lockmanager-fail-releaselock', $pathsByKey[$key] );
+ }
+ }
+
+ return $status;
+ }
+
+ protected function releaseAllLocks() {
+ return Status::newGood(); // not supported
+ }
+
+ protected function isServerUp( $lockSrv ) {
+ return (bool)$this->redisPool->getConnection( $this->lockServers[$lockSrv] );
+ }
+
+ /**
+ * @param $path string
+ * @return string
+ */
+ protected function recordKeyForPath( $path ) {
+ return implode( ':', array( __CLASS__, 'locks', $this->sha1Base36Absolute( $path ) ) );
+ }
+
+ /**
+ * Make sure remaining locks get cleared for sanity
+ */
+ function __destruct() {
+ while ( count( $this->locksHeld ) ) {
+ foreach ( $this->locksHeld as $path => $locks ) {
+ $this->doUnlock( array( $path ), self::LOCK_EX );
+ $this->doUnlock( array( $path ), self::LOCK_SH );
+ }
+ }
+ }
+}
diff --git a/includes/filebackend/lockmanager/ScopedLock.php b/includes/filebackend/lockmanager/ScopedLock.php
index edcb1d65..5faad4a6 100644
--- a/includes/filebackend/lockmanager/ScopedLock.php
+++ b/includes/filebackend/lockmanager/ScopedLock.php
@@ -36,24 +36,18 @@ class ScopedLock {
protected $manager;
/** @var Status */
protected $status;
- /** @var Array List of resource paths*/
- protected $paths;
-
- protected $type; // integer lock type
+ /** @var Array Map of lock types to resource paths */
+ protected $pathsByType;
/**
- * @param $manager LockManager
- * @param array $paths List of storage paths
- * @param $type integer LockManager::LOCK_* constant
- * @param $status Status
+ * @param LockManager $manager
+ * @param array $pathsByType Map of lock types to path lists
+ * @param Status $status
*/
- protected function __construct(
- LockManager $manager, array $paths, $type, Status $status
- ) {
+ protected function __construct( LockManager $manager, array $pathsByType, Status $status ) {
$this->manager = $manager;
- $this->paths = $paths;
+ $this->pathsByType = $pathsByType;
$this->status = $status;
- $this->type = $type;
}
/**
@@ -61,19 +55,24 @@ class ScopedLock {
* 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
+ * $type can be "mixed" and $paths can be a map of types to paths (since 1.22).
+ * Otherwise $type should be an integer and $paths should be a list of paths.
+ *
+ * @param LockManager $manager
+ * @param array $paths List of storage paths or map of lock types to path lists
+ * @param integer|string $type LockManager::LOCK_* constant or "mixed"
+ * @param Status $status
+ * @param integer $timeout Timeout in seconds (0 means non-blocking) (since 1.22)
* @return ScopedLock|null Returns null on failure
*/
public static function factory(
- LockManager $manager, array $paths, $type, Status $status
+ LockManager $manager, array $paths, $type, Status $status, $timeout = 0
) {
- $lockStatus = $manager->lock( $paths, $type );
+ $pathsByType = is_integer( $type ) ? array( $type => $paths ) : $paths;
+ $lockStatus = $manager->lockByType( $pathsByType, $timeout );
$status->merge( $lockStatus );
if ( $lockStatus->isOK() ) {
- return new self( $manager, $paths, $type, $status );
+ return new self( $manager, $pathsByType, $status );
}
return null;
}
@@ -91,9 +90,12 @@ class ScopedLock {
$lock = null;
}
+ /**
+ * Release the locks when this goes out of scope
+ */
function __destruct() {
$wasOk = $this->status->isOK();
- $this->status->merge( $this->manager->unlock( $this->paths, $this->type ) );
+ $this->status->merge( $this->manager->unlockByType( $this->pathsByType ) );
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 e49f37d2..42c9c945 100644
--- a/includes/filerepo/FSRepo.php
+++ b/includes/filerepo/FSRepo.php
@@ -56,16 +56,16 @@ class FSRepo extends FileRepo {
$repoName = $info['name'];
// Get the FS backend configuration
$backend = new FSFileBackend( array(
- 'name' => $info['name'] . '-backend',
- 'lockManager' => 'fsLockManager',
+ 'name' => $info['name'] . '-backend',
+ 'lockManager' => 'fsLockManager',
'containerPaths' => array(
- "{$repoName}-public" => "{$directory}",
- "{$repoName}-temp" => "{$directory}/temp",
- "{$repoName}-thumb" => $thumbDir,
- "{$repoName}-transcoded" => $transcodedDir,
+ "{$repoName}-public" => "{$directory}",
+ "{$repoName}-temp" => "{$directory}/temp",
+ "{$repoName}-thumb" => $thumbDir,
+ "{$repoName}-transcoded" => $transcodedDir,
"{$repoName}-deleted" => $deletedDir
),
- 'fileMode' => $fileMode,
+ 'fileMode' => $fileMode,
) );
// Update repo config to use this backend
$info['backend'] = $backend;
diff --git a/includes/filerepo/FileRepo.php b/includes/filerepo/FileRepo.php
index 366dd8a5..1195d5f8 100644
--- a/includes/filerepo/FileRepo.php
+++ b/includes/filerepo/FileRepo.php
@@ -67,7 +67,7 @@ class FileRepo {
*/
public function __construct( array $info = null ) {
// Verify required settings presence
- if(
+ if (
$info === null
|| !array_key_exists( 'name', $info )
|| !array_key_exists( 'backend', $info )
@@ -259,19 +259,19 @@ class FileRepo {
*/
public function resolveVirtualUrl( $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" );
}
list( $repo, $zone, $rel ) = $bits;
if ( $repo !== $this->name ) {
- throw new MWException( __METHOD__.": fetching from a foreign repo is not supported" );
+ throw new MWException( __METHOD__ . ": fetching from a foreign repo is not supported" );
}
$base = $this->getZonePath( $zone );
if ( !$base ) {
- throw new MWException( __METHOD__.": invalid zone: $zone" );
+ throw new MWException( __METHOD__ . ": invalid zone: $zone" );
}
return $base . '/' . rawurldecode( $rel );
}
@@ -383,7 +383,7 @@ class FileRepo {
return false;
}
$redir = $this->checkRedirect( $title );
- if ( $redir && $title->getNamespace() == NS_FILE) {
+ if ( $redir && $title->getNamespace() == NS_FILE ) {
$img = $this->newFile( $redir );
if ( !$img ) {
return false;
@@ -794,10 +794,10 @@ class FileRepo {
}
}
$operations[] = array(
- 'op' => $opName,
- 'src' => $srcPath,
- 'dst' => $dstPath,
- 'overwrite' => $flags & self::OVERWRITE,
+ 'op' => $opName,
+ 'src' => $srcPath,
+ 'dst' => $dstPath,
+ 'overwrite' => $flags & self::OVERWRITE,
'overwriteSame' => $flags & self::OVERWRITE_SAME,
);
}
@@ -917,9 +917,9 @@ class FileRepo {
$src = $this->resolveToStoragePath( $src );
$dst = $this->resolveToStoragePath( $dst );
$operations[] = array(
- 'op' => FileBackend::isStoragePath( $src ) ? 'copy' : 'store',
- 'src' => $src,
- 'dst' => $dst,
+ 'op' => FileBackend::isStoragePath( $src ) ? 'copy' : 'store',
+ 'src' => $src,
+ 'dst' => $dst,
'disposition' => isset( $triple[2] ) ? $triple[2] : null
);
$status->merge( $this->initDirectory( dirname( $dst ) ) );
@@ -942,8 +942,8 @@ class FileRepo {
$operations = array();
foreach ( $paths as $path ) {
$operations[] = array(
- 'op' => 'delete',
- 'src' => $this->resolveToStoragePath( $path ),
+ 'op' => 'delete',
+ 'src' => $this->resolveToStoragePath( $path ),
'ignoreMissingSource' => true
);
}
@@ -965,7 +965,7 @@ class FileRepo {
public function storeTemp( $originalName, $srcPath ) {
$this->assertWritableRepo(); // fail out if read-only
- $date = gmdate( "YmdHis" );
+ $date = MWTimestamp::getInstance()->format( 'YmdHis' );
$hashPath = $this->getHashPath( $originalName );
$dstUrlRel = $hashPath . $date . '!' . rawurlencode( $originalName );
$virtualUrl = $this->getVirtualUrl( 'temp' ) . '/' . $dstUrlRel;
@@ -987,7 +987,7 @@ class FileRepo {
$temp = $this->getVirtualUrl( 'temp' );
if ( substr( $virtualUrl, 0, strlen( $temp ) ) != $temp ) {
- wfDebug( __METHOD__.": Invalid temp virtual URL\n" );
+ wfDebug( __METHOD__ . ": Invalid temp virtual URL\n" );
return false;
}
@@ -1132,9 +1132,9 @@ class FileRepo {
// race conditions unless an functioning LockManager is used.
// LocalFile also uses SELECT FOR UPDATE for synchronization.
$operations[] = array(
- 'op' => 'copy',
- 'src' => $dstPath,
- 'dst' => $archivePath,
+ 'op' => 'copy',
+ 'src' => $dstPath,
+ 'dst' => $archivePath,
'ignoreMissingSource' => true
);
@@ -1142,28 +1142,28 @@ class FileRepo {
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
+ 'headers' => $headers
);
} else {
$operations[] = array(
- 'op' => 'copy',
- 'src' => $srcPath,
- 'dst' => $dstPath,
+ 'op' => 'copy',
+ 'src' => $srcPath,
+ 'dst' => $dstPath,
'overwrite' => true, // replace current
- 'headers' => $headers
+ '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
+ 'headers' => $headers
);
if ( $flags & self::DELETE_SOURCE ) {
$sourceFSFilesToDelete[] = $srcPath;
@@ -1306,9 +1306,9 @@ class FileRepo {
foreach ( $sourceDestPairs as $pair ) {
list( $srcRel, $archiveRel ) = $pair;
if ( !$this->validateFilename( $srcRel ) ) {
- throw new MWException( __METHOD__.':Validation error in $srcRel' );
+ throw new MWException( __METHOD__ . ':Validation error in $srcRel' );
} elseif ( !$this->validateFilename( $archiveRel ) ) {
- throw new MWException( __METHOD__.':Validation error in $archiveRel' );
+ throw new MWException( __METHOD__ . ':Validation error in $archiveRel' );
}
$publicRoot = $this->getZonePath( 'public' );
@@ -1324,9 +1324,9 @@ class FileRepo {
}
$operations[] = array(
- 'op' => 'move',
- 'src' => $srcPath,
- 'dst' => $archivePath,
+ 'op' => 'move',
+ 'src' => $srcPath,
+ 'dst' => $archivePath,
// We may have 2+ identical files being deleted,
// all of which will map to the same destination file
'overwriteSame' => true // also see bug 31792
@@ -1564,7 +1564,7 @@ class FileRepo {
public function newFatal( $message /*, parameters...*/ ) {
$params = func_get_args();
array_unshift( $params, $this );
- return MWInit::callStaticMethod( 'FileRepoStatus', 'newFatal', $params );
+ return call_user_func_array( array( 'FileRepoStatus', 'newFatal' ), $params );
}
/**
@@ -1671,29 +1671,29 @@ class FileRepo {
*/
public function getTempRepo() {
return new TempFileRepo( array(
- 'name' => "{$this->name}-temp",
- 'backend' => $this->backend,
- 'zones' => array(
+ 'name' => "{$this->name}-temp",
+ 'backend' => $this->backend,
+ 'zones' => array(
'public' => array(
'container' => $this->zones['temp']['container'],
'directory' => $this->zones['temp']['directory']
),
- 'thumb' => array(
+ 'thumb' => array(
'container' => $this->zones['thumb']['container'],
'directory' => ( $this->zones['thumb']['directory'] == '' )
? 'temp'
: $this->zones['thumb']['directory'] . '/temp'
),
- 'transcoded' => array(
+ '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',
+ 'url' => $this->getZoneUrl( 'temp' ),
+ 'thumbUrl' => $this->getZoneUrl( 'thumb' ) . '/temp',
+ 'transcodedUrl' => $this->getZoneUrl( 'transcoded' ) . '/temp',
'hashLevels' => $this->hashLevels // performance
) );
}
@@ -1716,6 +1716,22 @@ class FileRepo {
* @throws MWException
*/
protected function assertWritableRepo() {}
+
+
+ /**
+ * Return information about the repository.
+ *
+ * @return array
+ * @since 1.22
+ */
+ public function getInfo() {
+ return array(
+ 'name' => $this->getName(),
+ 'displayname' => $this->getDisplayName(),
+ 'rootUrl' => $this->getRootUrl(),
+ 'local' => $this->isLocal(),
+ );
+ }
}
/**
diff --git a/includes/filerepo/ForeignAPIRepo.php b/includes/filerepo/ForeignAPIRepo.php
index ba574da1..5eec9a50 100644
--- a/includes/filerepo/ForeignAPIRepo.php
+++ b/includes/filerepo/ForeignAPIRepo.php
@@ -61,26 +61,34 @@ class ForeignAPIRepo extends FileRepo {
// http://commons.wikimedia.org/w/api.php
$this->mApiBase = isset( $info['apibase'] ) ? $info['apibase'] : null;
- if( isset( $info['apiThumbCacheExpiry'] ) ) {
+ if ( isset( $info['apiThumbCacheExpiry'] ) ) {
$this->apiThumbCacheExpiry = $info['apiThumbCacheExpiry'];
}
- if( isset( $info['fileCacheExpiry'] ) ) {
+ if ( isset( $info['fileCacheExpiry'] ) ) {
$this->fileCacheExpiry = $info['fileCacheExpiry'];
}
- if( !$this->scriptDirUrl ) {
+ if ( !$this->scriptDirUrl ) {
// hack for description fetches
$this->scriptDirUrl = dirname( $this->mApiBase );
}
// If we can cache thumbs we can guess sane defaults for these
- if( $this->canCacheThumbs() && !$this->url ) {
+ if ( $this->canCacheThumbs() && !$this->url ) {
$this->url = $wgLocalFileRepo['url'];
}
- if( $this->canCacheThumbs() && !$this->thumbUrl ) {
+ if ( $this->canCacheThumbs() && !$this->thumbUrl ) {
$this->thumbUrl = $this->url . '/thumb';
}
}
/**
+ * @return string
+ * @since 1.22
+ */
+ function getApiUrl() {
+ return $this->mApiBase;
+ }
+
+ /**
* Per docs in FileRepo, this needs to return false if we don't support versioned
* files. Well, we don't.
*
@@ -102,10 +110,10 @@ class ForeignAPIRepo extends FileRepo {
function fileExistsBatch( array $files ) {
$results = array();
foreach ( $files as $k => $f ) {
- if ( isset( $this->mFileExists[$k] ) ) {
- $results[$k] = true;
+ if ( isset( $this->mFileExists[$f] ) ) {
+ $results[$k] = $this->mFileExists[$f];
unset( $files[$k] );
- } elseif( self::isVirtualUrl( $f ) ) {
+ } elseif ( self::isVirtualUrl( $f ) ) {
# @todo FIXME: We need to be able to handle virtual
# URLs better, at least when we know they refer to the
# same repo.
@@ -120,11 +128,27 @@ class ForeignAPIRepo extends FileRepo {
$data = $this->fetchImageQuery( array( 'titles' => implode( $files, '|' ),
'prop' => 'imageinfo' ) );
- if( isset( $data['query']['pages'] ) ) {
- $i = 0;
- foreach( $files as $key => $file ) {
- $results[$key] = $this->mFileExists[$key] = !isset( $data['query']['pages'][$i]['missing'] );
- $i++;
+ if ( isset( $data['query']['pages'] ) ) {
+ # First, get results from the query. Note we only care whether the image exists,
+ # not whether it has a description page.
+ foreach ( $data['query']['pages'] as $p ) {
+ $this->mFileExists[$p['title']] = ( $p['imagerepository'] !== '' );
+ }
+ # Second, copy the results to any redirects that were queried
+ if ( isset( $data['query']['redirects'] ) ) {
+ foreach ( $data['query']['redirects'] as $r ) {
+ $this->mFileExists[$r['from']] = $this->mFileExists[$r['to']];
+ }
+ }
+ # Third, copy the results to any non-normalized titles that were queried
+ if ( isset( $data['query']['normalized'] ) ) {
+ foreach ( $data['query']['normalized'] as $n ) {
+ $this->mFileExists[$n['from']] = $this->mFileExists[$n['to']];
+ }
+ }
+ # Finally, copy the results to the output
+ foreach ( $files as $key => $file ) {
+ $results[$key] = $this->mFileExists[$file];
}
}
return $results;
@@ -143,38 +167,26 @@ class ForeignAPIRepo extends FileRepo {
* @return string
*/
function fetchImageQuery( $query ) {
- global $wgMemc;
+ global $wgMemc, $wgLanguageCode;
$query = array_merge( $query,
array(
- 'format' => 'json',
- 'action' => 'query',
+ 'format' => 'json',
+ 'action' => 'query',
'redirects' => 'true'
) );
- if ( $this->mApiBase ) {
- $url = wfAppendQuery( $this->mApiBase, $query );
- } else {
- $url = $this->makeUrl( $query, 'api' );
+
+ if ( !isset( $query['uselang'] ) ) { // uselang is unset or null
+ $query['uselang'] = $wgLanguageCode;
}
- if( !isset( $this->mQueryCache[$url] ) ) {
- $key = $this->getLocalCacheKey( 'ForeignAPIRepo', 'Metadata', md5( $url ) );
- $data = $wgMemc->get( $key );
- if( !$data ) {
- $data = self::httpGet( $url );
- if ( !$data ) {
- return null;
- }
- $wgMemc->set( $key, $data, 3600 );
- }
+ $data = $this->httpGetCached( 'Metadata', $query );
- if( count( $this->mQueryCache ) > 100 ) {
- // Keep the cache from growing infinitely
- $this->mQueryCache = array();
- }
- $this->mQueryCache[$url] = $data;
+ if ( $data ) {
+ return FormatJson::decode( $data, true );
+ } else {
+ return null;
}
- return FormatJson::decode( $this->mQueryCache[$url], true );
}
/**
@@ -182,9 +194,9 @@ class ForeignAPIRepo extends FileRepo {
* @return bool|array
*/
function getImageInfo( $data ) {
- if( $data && isset( $data['query']['pages'] ) ) {
- foreach( $data['query']['pages'] as $info ) {
- if( isset( $info['imageinfo'][0] ) ) {
+ if ( $data && isset( $data['query']['pages'] ) ) {
+ foreach ( $data['query']['pages'] as $info ) {
+ if ( isset( $info['imageinfo'][0] ) ) {
return $info['imageinfo'][0];
}
}
@@ -198,14 +210,15 @@ class ForeignAPIRepo extends FileRepo {
*/
function findBySha1( $hash ) {
$results = $this->fetchImageQuery( array(
- 'aisha1base36' => $hash,
- 'aiprop' => ForeignAPIFile::getProps(),
- 'list' => 'allimages', ) );
+ 'aisha1base36' => $hash,
+ 'aiprop' => ForeignAPIFile::getProps(),
+ 'list' => 'allimages',
+ ) );
$ret = array();
if ( isset( $results['query']['allimages'] ) ) {
foreach ( $results['query']['allimages'] as $img ) {
// 1.14 was broken, doesn't return name attribute
- if( !isset( $img['name'] ) ) {
+ if ( !isset( $img['name'] ) ) {
continue;
}
$ret[] = new ForeignAPIFile( Title::makeTitle( NS_FILE, $img['name'] ), $this, $img );
@@ -228,11 +241,11 @@ class ForeignAPIRepo extends FileRepo {
'iiprop' => 'url|timestamp',
'iiurlwidth' => $width,
'iiurlheight' => $height,
- 'iiurlparam' => $otherParams,
+ 'iiurlparam' => $otherParams,
'prop' => 'imageinfo' ) );
$info = $this->getImageInfo( $data );
- if( $data && $info && isset( $info['thumburl'] ) ) {
+ if ( $data && $info && isset( $info['thumburl'] ) ) {
wfDebug( __METHOD__ . " got remote thumb " . $info['thumburl'] . "\n" );
$result = $info;
return $info['thumburl'];
@@ -242,6 +255,40 @@ class ForeignAPIRepo extends FileRepo {
}
/**
+ * @param $name string
+ * @param $width int
+ * @param $height int
+ * @param $otherParams string
+ * @return bool|MediaTransformError
+ * @since 1.22
+ */
+ function getThumbError( $name, $width = -1, $height = -1, $otherParams = '', $lang = null ) {
+ $data = $this->fetchImageQuery( array(
+ 'titles' => 'File:' . $name,
+ 'iiprop' => 'url|timestamp',
+ 'iiurlwidth' => $width,
+ 'iiurlheight' => $height,
+ 'iiurlparam' => $otherParams,
+ 'prop' => 'imageinfo',
+ 'uselang' => $lang,
+ ) );
+ $info = $this->getImageInfo( $data );
+
+ if ( $data && $info && isset( $info['thumberror'] ) ) {
+ wfDebug( __METHOD__ . " got remote thumb error " . $info['thumberror'] . "\n" );
+ return new MediaTransformError(
+ 'thumbnail_error_remote',
+ $width,
+ $height,
+ $this->getDisplayName(),
+ $info['thumberror'] // already parsed message from foreign repo
+ );
+ } else {
+ return false;
+ }
+ }
+
+ /**
* Return the imageurl from cache if possible
*
* If the url has been requested today, get it from cache
@@ -268,11 +315,11 @@ class ForeignAPIRepo extends FileRepo {
/* Get the array of urls that we already know */
$knownThumbUrls = $wgMemc->get( $key );
- if( !$knownThumbUrls ) {
+ if ( !$knownThumbUrls ) {
/* No knownThumbUrls for this file */
$knownThumbUrls = array();
} else {
- if( isset( $knownThumbUrls[$sizekey] ) ) {
+ if ( isset( $knownThumbUrls[$sizekey] ) ) {
wfDebug( __METHOD__ . ': Got thumburl from local cache: ' .
"{$knownThumbUrls[$sizekey]} \n" );
return $knownThumbUrls[$sizekey];
@@ -283,14 +330,14 @@ class ForeignAPIRepo extends FileRepo {
$metadata = null;
$foreignUrl = $this->getThumbUrl( $name, $width, $height, $metadata, $params );
- if( !$foreignUrl ) {
+ if ( !$foreignUrl ) {
wfDebug( __METHOD__ . " Could not find thumburl\n" );
return false;
}
// We need the same filename as the remote one :)
$fileName = rawurldecode( pathinfo( $foreignUrl, PATHINFO_BASENAME ) );
- if( !$this->validateFilename( $fileName ) ) {
+ if ( !$this->validateFilename( $fileName ) ) {
wfDebug( __METHOD__ . " The deduced filename $fileName is not safe\n" );
return false;
}
@@ -298,15 +345,14 @@ class ForeignAPIRepo extends FileRepo {
$localFilename = $localPath . "/" . $fileName;
$localUrl = $this->getZoneUrl( 'thumb' ) . "/" . $this->getHashPath( $name ) . rawurlencode( $name ) . "/" . rawurlencode( $fileName );
- if( $backend->fileExists( array( 'src' => $localFilename ) )
- && isset( $metadata['timestamp'] ) )
- {
+ if ( $backend->fileExists( array( 'src' => $localFilename ) )
+ && isset( $metadata['timestamp'] ) ) {
wfDebug( __METHOD__ . " Thumbnail was already downloaded before\n" );
$modified = $backend->getFileTimestamp( array( 'src' => $localFilename ) );
$remoteModified = strtotime( $metadata['timestamp'] );
$current = time();
$diff = abs( $modified - $current );
- if( $remoteModified < $modified && $diff < $this->fileCacheExpiry ) {
+ if ( $remoteModified < $modified && $diff < $this->fileCacheExpiry ) {
/* Use our current and already downloaded thumbnail */
$knownThumbUrls[$sizekey] = $localUrl;
$wgMemc->set( $key, $knownThumbUrls, $this->apiThumbCacheExpiry );
@@ -315,7 +361,7 @@ class ForeignAPIRepo extends FileRepo {
/* There is a new Commons file, or existing thumbnail older than a month */
}
$thumb = self::httpGet( $foreignUrl );
- if( !$thumb ) {
+ if ( !$thumb ) {
wfDebug( __METHOD__ . " Could not download thumb\n" );
return false;
}
@@ -323,7 +369,7 @@ class ForeignAPIRepo extends FileRepo {
# @todo FIXME: Delete old thumbs that aren't being used. Maintenance script?
$backend->prepare( array( 'dir' => dirname( $localFilename ) ) );
$params = array( 'dst' => $localFilename, 'content' => $thumb );
- if( !$backend->quickCreate( $params )->isOK() ) {
+ if ( !$backend->quickCreate( $params )->isOK() ) {
wfDebug( __METHOD__ . " could not write to thumb path '$localFilename'\n" );
return $foreignUrl;
}
@@ -380,6 +426,36 @@ class ForeignAPIRepo extends FileRepo {
}
/**
+ * Get information about the repo - overrides/extends the parent
+ * class's information.
+ * @return array
+ * @since 1.22
+ */
+ function getInfo() {
+ $info = parent::getInfo();
+ $info['apiurl'] = $this->getApiUrl();
+
+ $query = array(
+ 'format' => 'json',
+ 'action' => 'query',
+ 'meta' => 'siteinfo',
+ 'siprop' => 'general',
+ );
+
+ $data = $this->httpGetCached( 'SiteInfo', $query, 7200 );
+
+ if ( $data ) {
+ $siteInfo = FormatJson::decode( $data, true );
+ $general = $siteInfo['query']['general'];
+
+ $info['articlepath'] = $general['articlepath'];
+ $info['server'] = $general['server'];
+ }
+
+ return $info;
+ }
+
+ /**
* Like a Http:get request, but with custom User-Agent.
* @see Http:get
* @param $url string
@@ -410,6 +486,46 @@ class ForeignAPIRepo extends FileRepo {
}
/**
+ * HTTP GET request to a mediawiki API (with caching)
+ * @param $target string Used in cache key creation, mostly
+ * @param $query array The query parameters for the API request
+ * @param $cacheTTL int Time to live for the memcached caching
+ */
+ public function httpGetCached( $target, $query, $cacheTTL = 3600 ) {
+ if ( $this->mApiBase ) {
+ $url = wfAppendQuery( $this->mApiBase, $query );
+ } else {
+ $url = $this->makeUrl( $query, 'api' );
+ }
+
+ if ( !isset( $this->mQueryCache[$url] ) ) {
+ global $wgMemc;
+
+ $key = $this->getLocalCacheKey( get_class( $this ), $target, md5( $url ) );
+ $data = $wgMemc->get( $key );
+
+ if ( !$data ) {
+ $data = self::httpGet( $url );
+
+ if ( !$data ) {
+ return null;
+ }
+
+ $wgMemc->set( $key, $data, $cacheTTL );
+ }
+
+ if ( count( $this->mQueryCache ) > 100 ) {
+ // Keep the cache from growing infinitely
+ $this->mQueryCache = array();
+ }
+
+ $this->mQueryCache[$url] = $data;
+ }
+
+ return $this->mQueryCache[$url];
+ }
+
+ /**
* @param $callback Array|string
* @throws MWException
*/
diff --git a/includes/filerepo/ForeignDBRepo.php b/includes/filerepo/ForeignDBRepo.php
index 18659852..37c65723 100644
--- a/includes/filerepo/ForeignDBRepo.php
+++ b/includes/filerepo/ForeignDBRepo.php
@@ -59,11 +59,12 @@ class ForeignDBRepo extends LocalRepo {
$this->dbConn = DatabaseBase::factory( $this->dbType,
array(
'host' => $this->dbServer,
- 'user' => $this->dbUser,
+ 'user' => $this->dbUser,
'password' => $this->dbPassword,
'dbname' => $this->dbName,
'flags' => $this->dbFlags,
- 'tablePrefix' => $this->tablePrefix
+ 'tablePrefix' => $this->tablePrefix,
+ 'foreign' => true,
)
);
}
diff --git a/includes/filerepo/LocalRepo.php b/includes/filerepo/LocalRepo.php
index be11b233..9b62243b 100644
--- a/includes/filerepo/LocalRepo.php
+++ b/includes/filerepo/LocalRepo.php
@@ -47,7 +47,7 @@ class LocalRepo extends FileRepo {
} elseif ( isset( $row->oi_name ) ) {
return call_user_func( $this->oldFileFromRowFactory, $row, $this );
} else {
- throw new MWException( __METHOD__.': invalid row' );
+ throw new MWException( __METHOD__ . ': invalid row' );
}
}
@@ -171,13 +171,13 @@ class LocalRepo extends FileRepo {
if ( $cachedValue === ' ' || $cachedValue === '' ) {
// Does not exist
return false;
- } elseif ( strval( $cachedValue ) !== '' ) {
+ } elseif ( strval( $cachedValue ) !== '' && $cachedValue !== ' PURGED' ) {
return Title::newFromText( $cachedValue, NS_FILE );
} // else $cachedValue is false or null: cache miss
$id = $this->getArticleID( $title );
- if( !$id ) {
- $wgMemc->set( $memcKey, " ", $expiry );
+ if ( !$id ) {
+ $wgMemc->add( $memcKey, " ", $expiry );
return false;
}
$dbr = $this->getSlaveDB();
@@ -188,12 +188,12 @@ class LocalRepo extends FileRepo {
__METHOD__
);
- if( $row && $row->rd_namespace == NS_FILE ) {
+ if ( $row && $row->rd_namespace == NS_FILE ) {
$targetTitle = Title::makeTitle( $row->rd_namespace, $row->rd_title );
- $wgMemc->set( $memcKey, $targetTitle->getDBkey(), $expiry );
+ $wgMemc->add( $memcKey, $targetTitle->getDBkey(), $expiry );
return $targetTitle;
} else {
- $wgMemc->set( $memcKey, '', $expiry );
+ $wgMemc->add( $memcKey, '', $expiry );
return false;
}
}
@@ -206,7 +206,7 @@ class LocalRepo extends FileRepo {
* @return bool|int|mixed
*/
protected function getArticleID( $title ) {
- if( !$title instanceof Title ) {
+ if ( !$title instanceof Title ) {
return 0;
}
$dbr = $this->getSlaveDB();
@@ -258,7 +258,7 @@ class LocalRepo extends FileRepo {
* @return array An Array of arrays or iterators of file objects and the hash as key
*/
function findBySha1s( array $hashes ) {
- if( !count( $hashes ) ) {
+ if ( !count( $hashes ) ) {
return array(); //empty parameter
}
@@ -281,17 +281,17 @@ 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
- */
+ /**
+ * 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
+ // Query database
$dbr = $this->getSlaveDB();
$res = $dbr->select(
'image',
@@ -306,7 +306,7 @@ class LocalRepo extends FileRepo {
foreach ( $res as $row ) {
$files[] = $this->newFileFromRow( $row );
}
- return $files;
+ return $files;
}
/**
@@ -347,7 +347,11 @@ class LocalRepo extends FileRepo {
global $wgMemc;
$memcKey = $this->getSharedCacheKey( 'image_redirect', md5( $title->getDBkey() ) );
if ( $memcKey ) {
- $wgMemc->delete( $memcKey );
+ // Set a temporary value for the cache key, to ensure
+ // that this value stays purged long enough so that
+ // it isn't refreshed with a stale value due to a
+ // lagged slave.
+ $wgMemc->set( $memcKey, ' PURGED', 12 );
}
}
}
diff --git a/includes/filerepo/RepoGroup.php b/includes/filerepo/RepoGroup.php
index 02dfdad6..b2b9477a 100644
--- a/includes/filerepo/RepoGroup.php
+++ b/includes/filerepo/RepoGroup.php
@@ -209,7 +209,7 @@ class RepoGroup {
}
$redir = $this->localRepo->checkRedirect( $title );
- if( $redir ) {
+ if ( $redir ) {
return $redir;
}
foreach ( $this->foreignRepos as $repo ) {
@@ -238,7 +238,9 @@ class RepoGroup {
if ( !$file ) {
foreach ( $this->foreignRepos as $repo ) {
$file = $repo->findFileFromKey( $hash, $options );
- if ( $file ) break;
+ if ( $file ) {
+ break;
+ }
}
}
return $file;
@@ -279,7 +281,7 @@ class RepoGroup {
$result = array_merge_recursive( $result, $repo->findBySha1s( $hashes ) );
}
//sort the merged (and presorted) sublist of each hash
- foreach( $result as $hash => $files ) {
+ foreach ( $result as $hash => $files ) {
usort( $result[$hash], 'File::compare' );
}
return $result;
@@ -339,9 +341,9 @@ class RepoGroup {
* @return bool
*/
function forEachForeignRepo( $callback, $params = array() ) {
- foreach( $this->foreignRepos as $repo ) {
+ foreach ( $this->foreignRepos as $repo ) {
$args = array_merge( array( $repo ), $params );
- if( call_user_func_array( $callback, $args ) ) {
+ if ( call_user_func_array( $callback, $args ) ) {
return true;
}
}
diff --git a/includes/filerepo/file/ArchivedFile.php b/includes/filerepo/file/ArchivedFile.php
index 3f786197..749f11a5 100644
--- a/includes/filerepo/file/ArchivedFile.php
+++ b/includes/filerepo/file/ArchivedFile.php
@@ -90,7 +90,7 @@ class ArchivedFile {
$this->exists = false;
$this->sha1 = '';
- if( $title instanceof Title ) {
+ if ( $title instanceof Title ) {
$this->title = File::normalizeTitle( $title, 'exception' );
$this->name = $title->getDBkey();
}
@@ -119,22 +119,22 @@ class ArchivedFile {
}
$conds = array();
- if( $this->id > 0 ) {
+ if ( $this->id > 0 ) {
$conds['fa_id'] = $this->id;
}
- if( $this->key ) {
+ if ( $this->key ) {
$conds['fa_storage_group'] = $this->group;
$conds['fa_storage_key'] = $this->key;
}
- if( $this->title ) {
+ if ( $this->title ) {
$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 ) {
+ if ( !$this->title || $this->title->getNamespace() == NS_FILE ) {
$this->dataLoaded = true; // set it here, to have also true on miss
$dbr = wfGetDB( DB_SLAVE );
$row = $dbr->selectRow(
@@ -196,6 +196,7 @@ class ArchivedFile {
'fa_user_text',
'fa_timestamp',
'fa_deleted',
+ 'fa_deleted_timestamp', /* Used by LocalFileRestoreBatch */
'fa_sha1',
);
}
@@ -224,7 +225,7 @@ class ArchivedFile {
$this->user_text = $row->fa_user_text;
$this->timestamp = $row->fa_timestamp;
$this->deleted = $row->fa_deleted;
- if( isset( $row->fa_sha1 ) ) {
+ if ( isset( $row->fa_sha1 ) ) {
$this->sha1 = $row->fa_sha1;
} else {
// old row, populate from key
@@ -409,7 +410,7 @@ class ArchivedFile {
*/
public function getUser() {
$this->load();
- if( $this->isDeleted( File::DELETED_USER ) ) {
+ if ( $this->isDeleted( File::DELETED_USER ) ) {
return 0;
} else {
return $this->user;
@@ -423,7 +424,7 @@ class ArchivedFile {
*/
public function getUserText() {
$this->load();
- if( $this->isDeleted( File::DELETED_USER ) ) {
+ if ( $this->isDeleted( File::DELETED_USER ) ) {
return 0;
} else {
return $this->user_text;
@@ -437,7 +438,7 @@ class ArchivedFile {
*/
public function getDescription() {
$this->load();
- if( $this->isDeleted( File::DELETED_COMMENT ) ) {
+ if ( $this->isDeleted( File::DELETED_COMMENT ) ) {
return 0;
} else {
return $this->description;
@@ -491,7 +492,7 @@ class ArchivedFile {
*/
public function isDeleted( $field ) {
$this->load();
- return ($this->deleted & $field) == $field;
+ return ( $this->deleted & $field ) == $field;
}
/**
diff --git a/includes/filerepo/file/File.php b/includes/filerepo/file/File.php
index cecd0aee..ec5f927b 100644
--- a/includes/filerepo/file/File.php
+++ b/includes/filerepo/file/File.php
@@ -48,6 +48,7 @@
* @ingroup FileAbstraction
*/
abstract class File {
+ // Bitfield values akin to the Revision deletion constants
const DELETED_FILE = 1;
const DELETED_COMMENT = 2;
const DELETED_USER = 4;
@@ -201,9 +202,9 @@ abstract class File {
'mpeg' => 'mpg',
'tiff' => 'tif',
'ogv' => 'ogg' );
- if( isset( $squish[$lower] ) ) {
+ if ( isset( $squish[$lower] ) ) {
return $squish[$lower];
- } elseif( preg_match( '/^[0-9a-z]+$/', $lower ) ) {
+ } elseif ( preg_match( '/^[0-9a-z]+$/', $lower ) ) {
return $lower;
} else {
return '';
@@ -241,7 +242,7 @@ abstract class File {
* @return array ("text", "html") etc
*/
public static function splitMime( $mime ) {
- if( strpos( $mime, '/' ) !== false ) {
+ if ( strpos( $mime, '/' ) !== false ) {
return explode( '/', $mime, 2 );
} else {
return array( $mime, 'unknown' );
@@ -518,7 +519,7 @@ abstract class File {
* @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) {
+ public function convertMetadataVersion( $metadata, $version ) {
$handler = $this->getHandler();
if ( !is_array( $metadata ) ) {
// Just to make the return type consistent
@@ -681,7 +682,7 @@ abstract class File {
if ( $mime === "unknown/unknown" ) {
return false; #unknown type, not trusted
}
- if ( in_array( $mime, $wgTrustedMediaFormats) ) {
+ if ( in_array( $mime, $wgTrustedMediaFormats ) ) {
return true;
}
@@ -737,7 +738,7 @@ abstract class File {
if ( $this->repo ) {
$script = $this->repo->getThumbScriptUrl();
if ( $script ) {
- $this->transformScript = "$script?f=" . urlencode( $this->getName() );
+ $this->transformScript = wfAppendQuery( $script, array( 'f' => $this->getName() ) );
}
}
}
@@ -841,8 +842,9 @@ abstract class File {
protected function transformErrorOutput( $thumbPath, $thumbUrl, $params, $flags ) {
global $wgIgnoreImageErrors;
- if ( $wgIgnoreImageErrors && !( $flags & self::RENDER_NOW ) ) {
- return $this->getHandler()->getTransform( $this, $thumbPath, $thumbUrl, $params );
+ $handler = $this->getHandler();
+ if ( $handler && $wgIgnoreImageErrors && !( $flags & self::RENDER_NOW ) ) {
+ return $handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
} else {
return new MediaTransformError( 'thumbnail_error',
$params['width'], 0, wfMessage( 'thumbnail-dest-create' )->text() );
@@ -910,8 +912,7 @@ 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 = $handler->getTransform(
- $this, $thumbPath, $thumbUrl, $params );
+ $thumb = $handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
$thumb->setStoragePath( $thumbPath );
break;
}
@@ -1000,7 +1001,7 @@ abstract class File {
/**
* Get a MediaHandler instance for this file
*
- * @return MediaHandler
+ * @return MediaHandler|boolean Registered MediaHandler for file's mime type or false if none found
*/
function getHandler() {
if ( !isset( $this->handler ) ) {
@@ -1097,7 +1098,7 @@ abstract class File {
*
* @return array
*/
- function getHistory( $limit = null, $start = null, $end = null, $inc=true ) {
+ function getHistory( $limit = null, $start = null, $end = null, $inc = true ) {
return array();
}
@@ -1500,7 +1501,7 @@ abstract class File {
* Is this file a "deleted" file in a private archive?
* STUB
*
- * @param $field
+ * @param integer $field one of DELETED_* bitfield constants
*
* @return bool
*/
@@ -1656,18 +1657,22 @@ abstract class File {
/**
* Get the HTML text of the description page, if available
*
+ * @param $lang Language Optional language to fetch description in
* @return string
*/
- function getDescriptionText() {
+ function getDescriptionText( $lang = false ) {
global $wgMemc, $wgLang;
if ( !$this->repo || !$this->repo->fetchDescription ) {
return false;
}
- $renderUrl = $this->repo->getDescriptionRenderUrl( $this->getName(), $wgLang->getCode() );
+ if ( !$lang ) {
+ $lang = $wgLang;
+ }
+ $renderUrl = $this->repo->getDescriptionRenderUrl( $this->getName(), $lang->getCode() );
if ( $renderUrl ) {
if ( $this->repo->descriptionCacheExpiry > 0 ) {
wfDebug( "Attempting to get the description from cache..." );
- $key = $this->repo->getLocalCacheKey( 'RemoteFileDescription', 'url', $wgLang->getCode(),
+ $key = $this->repo->getLocalCacheKey( 'RemoteFileDescription', 'url', $lang->getCode(),
$this->getName() );
$obj = $wgMemc->get( $key );
if ( $obj ) {
diff --git a/includes/filerepo/file/ForeignAPIFile.php b/includes/filerepo/file/ForeignAPIFile.php
index a96c1f3f..ed96d446 100644
--- a/includes/filerepo/file/ForeignAPIFile.php
+++ b/includes/filerepo/file/ForeignAPIFile.php
@@ -54,22 +54,22 @@ class ForeignAPIFile extends File {
*/
static function newFromTitle( Title $title, $repo ) {
$data = $repo->fetchImageQuery( array(
- 'titles' => 'File:' . $title->getDBKey(),
- 'iiprop' => self::getProps(),
- 'prop' => 'imageinfo',
+ 'titles' => 'File:' . $title->getDBkey(),
+ 'iiprop' => self::getProps(),
+ 'prop' => 'imageinfo',
'iimetadataversion' => MediaHandler::getMetadataVersion()
) );
$info = $repo->getImageInfo( $data );
- if( $info ) {
+ if ( $info ) {
$lastRedirect = isset( $data['query']['redirects'] )
? count( $data['query']['redirects'] ) - 1
: -1;
- if( $lastRedirect >= 0 ) {
- $newtitle = Title::newFromText( $data['query']['redirects'][$lastRedirect]['to']);
+ if ( $lastRedirect >= 0 ) {
+ $newtitle = Title::newFromText( $data['query']['redirects'][$lastRedirect]['to'] );
$img = new self( $newtitle, $repo, $info, true );
- if( $img ) {
+ if ( $img ) {
$img->redirectedFrom( $title->getDBkey() );
}
} else {
@@ -86,7 +86,7 @@ class ForeignAPIFile extends File {
* @return string
*/
static function getProps() {
- return 'timestamp|user|comment|url|size|sha1|metadata|mime';
+ return 'timestamp|user|comment|url|size|sha1|metadata|mime|mediatype';
}
// Dummy functions...
@@ -111,7 +111,7 @@ class ForeignAPIFile extends File {
* @return bool|MediaTransformOutput
*/
function transform( $params, $flags = 0 ) {
- if( !$this->canRender() ) {
+ if ( !$this->canRender() ) {
// show icon
return parent::transform( $params, $flags );
}
@@ -119,12 +119,25 @@ class ForeignAPIFile extends File {
// Note, the this->canRender() check above implies
// that we have a handler, and it can do makeParamString.
$otherParams = $this->handler->makeParamString( $params );
+ $width = isset( $params['width'] ) ? $params['width'] : -1;
+ $height = isset( $params['height'] ) ? $params['height'] : -1;
$thumbUrl = $this->repo->getThumbUrlFromCache(
$this->getName(),
- isset( $params['width'] ) ? $params['width'] : -1,
- isset( $params['height'] ) ? $params['height'] : -1,
- $otherParams );
+ $width,
+ $height,
+ $otherParams
+ );
+ if ( $thumbUrl === false ) {
+ global $wgLang;
+ return $this->repo->getThumbError(
+ $this->getName(),
+ $width,
+ $height,
+ $otherParams,
+ $wgLang->getCode()
+ );
+ }
return $this->handler->getTransform( $this, 'bogus', $thumbUrl, $params );
}
@@ -161,12 +174,12 @@ class ForeignAPIFile extends File {
* @return array
*/
public static function parseMetadata( $metadata ) {
- if( !is_array( $metadata ) ) {
+ if ( !is_array( $metadata ) ) {
return $metadata;
}
$ret = array();
- foreach( $metadata as $meta ) {
- $ret[ $meta['name'] ] = self::parseMetadata( $meta['value'] );
+ foreach ( $metadata as $meta ) {
+ $ret[$meta['name']] = self::parseMetadata( $meta['value'] );
}
return $ret;
}
@@ -224,7 +237,7 @@ class ForeignAPIFile extends File {
* @return string
*/
function getMimeType() {
- if( !isset( $this->mInfo['mime'] ) ) {
+ if ( !isset( $this->mInfo['mime'] ) ) {
$magic = MimeMagic::singleton();
$this->mInfo['mime'] = $magic->guessTypesForExtension( $this->getExtension() );
}
@@ -232,10 +245,12 @@ class ForeignAPIFile extends File {
}
/**
- * @todo FIXME: May guess wrong on file types that can be eg audio or video
* @return int|string
*/
function getMediaType() {
+ if ( isset( $this->mInfo['mediatype'] ) ) {
+ return $this->mInfo['mediatype'];
+ }
$magic = MimeMagic::singleton();
return $magic->getMediaType( null, $this->getMimeType() );
}
diff --git a/includes/filerepo/file/ForeignDBFile.php b/includes/filerepo/file/ForeignDBFile.php
index ee5883c4..01d6b0f5 100644
--- a/includes/filerepo/file/ForeignDBFile.php
+++ b/includes/filerepo/file/ForeignDBFile.php
@@ -120,10 +120,11 @@ class ForeignDBFile extends LocalFile {
}
/**
+ * @param $lang Language Optional language to fetch description in.
* @return string
*/
- function getDescriptionText() {
+ function getDescriptionText( $lang = false ) {
// Restore remote behavior
- return File::getDescriptionText();
+ return File::getDescriptionText( $lang );
}
}
diff --git a/includes/filerepo/file/LocalFile.php b/includes/filerepo/file/LocalFile.php
index 4f50bfaa..fe769be2 100644
--- a/includes/filerepo/file/LocalFile.php
+++ b/includes/filerepo/file/LocalFile.php
@@ -382,6 +382,7 @@ class LocalFile extends File {
$this->$name = $value;
}
} else {
+ wfProfileOut( $fname );
throw new MWException( "Could not find data for image '{$this->getName()}'." );
}
@@ -531,15 +532,15 @@ class LocalFile extends File {
$dbw->update( 'image',
array(
- 'img_size' => $this->size, // sanity
- 'img_width' => $this->width,
- 'img_height' => $this->height,
- 'img_bits' => $this->bits,
+ 'img_size' => $this->size, // sanity
+ 'img_width' => $this->width,
+ 'img_height' => $this->height,
+ 'img_bits' => $this->bits,
'img_media_type' => $this->media_type,
'img_major_mime' => $major,
'img_minor_mime' => $minor,
- 'img_metadata' => $this->metadata,
- 'img_sha1' => $this->sha1,
+ 'img_metadata' => $dbw->encodeBlob($this->metadata),
+ 'img_sha1' => $this->sha1,
),
array( 'img_name' => $this->getName() ),
__METHOD__
@@ -603,17 +604,23 @@ class LocalFile extends File {
* Return the width of the image
*
* @param $page int
- * @return bool|int Returns false on error
+ * @return int
*/
public function getWidth( $page = 1 ) {
$this->load();
if ( $this->isMultipage() ) {
- $dim = $this->getHandler()->getPageDimensions( $this, $page );
+ $handler = $this->getHandler();
+ if ( !$handler ) {
+ return 0;
+ }
+ $dim = $handler->getPageDimensions( $this, $page );
if ( $dim ) {
return $dim['width'];
} else {
- return false;
+ // For non-paged media, the false goes through an
+ // intval, turning failure into 0, so do same here.
+ return 0;
}
} else {
return $this->width;
@@ -624,17 +631,23 @@ class LocalFile extends File {
* Return the height of the image
*
* @param $page int
- * @return bool|int Returns false on error
+ * @return int
*/
public function getHeight( $page = 1 ) {
$this->load();
if ( $this->isMultipage() ) {
- $dim = $this->getHandler()->getPageDimensions( $this, $page );
+ $handler = $this->getHandler();
+ if ( !$handler ) {
+ return 0;
+ }
+ $dim = $handler->getPageDimensions( $this, $page );
if ( $dim ) {
return $dim['height'];
} else {
- return false;
+ // For non-paged media, the false goes through an
+ // intval, turning failure into 0, so do same here.
+ return 0;
}
} else {
return $this->height;
@@ -775,10 +788,12 @@ class LocalFile extends File {
$backend = $this->repo->getBackend();
$files = array( $dir );
- $iterator = $backend->getFileList( array( 'dir' => $dir ) );
- foreach ( $iterator as $file ) {
- $files[] = $file;
- }
+ try {
+ $iterator = $backend->getFileList( array( 'dir' => $dir ) );
+ foreach ( $iterator as $file ) {
+ $files[] = $file;
+ }
+ } catch ( FileBackendError $e ) {} // suppress (bug 54674)
return $files;
}
@@ -793,7 +808,9 @@ class LocalFile extends File {
}
/**
- * Purge the shared history (OldLocalFile) cache
+ * Purge the shared history (OldLocalFile) cache.
+ *
+ * @note This used to purge old thumbnails as well.
*/
function purgeHistory() {
global $wgMemc;
@@ -801,20 +818,20 @@ class LocalFile extends File {
$hashedName = md5( $this->getName() );
$oldKey = $this->repo->getSharedCacheKey( 'oldfile', $hashedName );
- // Must purge thumbnails for old versions too! bug 30192
- foreach( $this->getHistory() as $oldFile ) {
- $oldFile->purgeThumbnails();
- }
-
if ( $oldKey ) {
$wgMemc->delete( $oldKey );
}
}
/**
- * Delete all previously generated thumbnails, refresh metadata in memcached and purge the squid
+ * Delete all previously generated thumbnails, refresh metadata in memcached and purge the squid.
+ *
+ * @param Array $options An array potentially with the key forThumbRefresh.
+ *
+ * @note This used to purge old thumbnails by default as well, but doesn't anymore.
*/
function purgeCache( $options = array() ) {
+ wfProfileIn( __METHOD__ );
// Refresh metadata cache
$this->purgeMetadataCache();
@@ -823,6 +840,7 @@ class LocalFile extends File {
// Purge squid cache for this file
SquidUpdate::purge( array( $this->getURL() ) );
+ wfProfileOut( __METHOD__ );
}
/**
@@ -844,7 +862,7 @@ class LocalFile extends File {
// Purge the squid
if ( $wgUseSquid ) {
$urls = array();
- foreach( $files as $file ) {
+ foreach ( $files as $file ) {
$urls[] = $this->getArchiveThumbUrl( $archiveName, $file );
}
SquidUpdate::purge( $urls );
@@ -865,7 +883,7 @@ class LocalFile extends File {
// Always purge all files from squid regardless of handler filters
if ( $wgUseSquid ) {
$urls = array();
- foreach( $files as $file ) {
+ foreach ( $files as $file ) {
$urls[] = $this->getThumbUrl( $file );
}
array_shift( $urls ); // don't purge directory
@@ -1195,20 +1213,20 @@ class LocalFile extends File {
# doesn't deadlock. SELECT FOR UPDATE causes a deadlock for every race condition.
$dbw->insert( 'image',
array(
- 'img_name' => $this->getName(),
- 'img_size' => $this->size,
- 'img_width' => intval( $this->width ),
- 'img_height' => intval( $this->height ),
- 'img_bits' => $this->bits,
- 'img_media_type' => $this->media_type,
- 'img_major_mime' => $this->major_mime,
- 'img_minor_mime' => $this->minor_mime,
- 'img_timestamp' => $timestamp,
+ 'img_name' => $this->getName(),
+ 'img_size' => $this->size,
+ 'img_width' => intval( $this->width ),
+ 'img_height' => intval( $this->height ),
+ 'img_bits' => $this->bits,
+ 'img_media_type' => $this->media_type,
+ 'img_major_mime' => $this->major_mime,
+ 'img_minor_mime' => $this->minor_mime,
+ 'img_timestamp' => $timestamp,
'img_description' => $comment,
- 'img_user' => $user->getId(),
- 'img_user_text' => $user->getName(),
- 'img_metadata' => $this->metadata,
- 'img_sha1' => $this->sha1
+ 'img_user' => $user->getId(),
+ 'img_user_text' => $user->getName(),
+ 'img_metadata' => $dbw->encodeBlob($this->metadata),
+ 'img_sha1' => $this->sha1
),
__METHOD__,
'IGNORE'
@@ -1258,7 +1276,7 @@ class LocalFile extends File {
'img_description' => $comment,
'img_user' => $user->getId(),
'img_user_text' => $user->getName(),
- 'img_metadata' => $this->metadata,
+ 'img_metadata' => $dbw->encodeBlob($this->metadata),
'img_sha1' => $this->sha1
),
array( 'img_name' => $this->getName() ),
@@ -1274,20 +1292,45 @@ class LocalFile extends File {
$wikiPage->setFile( $this );
# Add the log entry
- $log = new LogPage( 'upload' );
$action = $reupload ? 'overwrite' : 'upload';
- $logId = $log->addEntry( $action, $descTitle, $comment, array(), $user );
- wfProfileIn( __METHOD__ . '-edit' );
+ $logEntry = new ManualLogEntry( 'upload', $action );
+ $logEntry->setPerformer( $user );
+ $logEntry->setComment( $comment );
+ $logEntry->setTarget( $descTitle );
+
+ // Allow people using the api to associate log entries with the upload.
+ // Log has a timestamp, but sometimes different from upload timestamp.
+ $logEntry->setParameters(
+ array(
+ 'img_sha1' => $this->sha1,
+ 'img_timestamp' => $timestamp,
+ )
+ );
+ // Note we keep $logId around since during new image
+ // creation, page doesn't exist yet, so log_page = 0
+ // but we want it to point to the page we're making,
+ // so we later modify the log entry.
+ // For a similar reason, we avoid making an RC entry
+ // now and wait until the page exists.
+ $logId = $logEntry->insert();
+
$exists = $descTitle->exists();
+ if ( $exists ) {
+ // Page exists, do RC entry now (otherwise we wait for later).
+ $logEntry->publish( $logId );
+ }
+ wfProfileIn( __METHOD__ . '-edit' );
if ( $exists ) {
# Create a null revision
$latest = $descTitle->getLatestRevID();
+ $editSummary = LogFormatter::newFromEntry( $logEntry )->getPlainActionText();
+
$nullRevision = Revision::newNullRevision(
$dbw,
$descTitle->getArticleID(),
- $log->getRcComment(),
+ $editSummary,
false
);
if ( !is_null( $nullRevision ) ) {
@@ -1315,16 +1358,20 @@ class LocalFile extends File {
$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( __METHOD__ );
+ $dbw->begin( __METHOD__ ); // XXX; doEdit() uses a transaction
+ // Now that the page exists, make an RC entry.
+ $logEntry->publish( $logId );
+ if ( isset( $status->value['revision'] ) ) {
$dbw->update( 'logging',
array( 'log_page' => $status->value['revision']->getPage() ),
array( 'log_id' => $logId ),
__METHOD__
);
- $dbw->commit( __METHOD__ ); // commit before anything bad can happen
}
+ $dbw->commit( __METHOD__ ); // commit before anything bad can happen
}
+
+
wfProfileOut( __METHOD__ . '-edit' );
# Save to cache and purge the squid
@@ -1351,11 +1398,17 @@ class LocalFile extends File {
# Invalidate cache for all pages using this file
$update = new HTMLCacheUpdate( $this->getTitle(), 'imagelinks' );
$update->doUpdate();
+ if ( !$reupload ) {
+ LinksUpdate::queueRecursiveJobsForTable( $this->getTitle(), 'imagelinks' );
+ }
# Invalidate cache for all pages that redirects on this page
$redirs = $this->getTitle()->getRedirectsHere();
foreach ( $redirs as $redir ) {
+ if ( !$reupload && $redir->getNamespace() === NS_FILE ) {
+ LinksUpdate::queueRecursiveJobsForTable( $redir, 'imagelinks' );
+ }
$update = new HTMLCacheUpdate( $redir, 'imagelinks' );
$update->doUpdate();
}
@@ -1405,7 +1458,7 @@ class LocalFile extends File {
$this->lock(); // begin
- $archiveName = wfTimestamp( TS_MW ) . '!'. $this->getName();
+ $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, $options );
@@ -1454,18 +1507,27 @@ class LocalFile extends File {
wfDebugLog( 'imagemove', "Finished moving {$this->name}" );
- $this->purgeEverything();
- foreach ( $archiveNames as $archiveName ) {
- $this->purgeOldThumbnails( $archiveName );
- }
+ // Purge the source and target files...
+ $oldTitleFile = wfLocalFile( $this->title );
+ $newTitleFile = wfLocalFile( $target );
+ // Hack: the lock()/unlock() pair is nested in a transaction so the locking is not
+ // tied to BEGIN/COMMIT. To avoid slow purges in the transaction, move them outside.
+ $this->getRepo()->getMasterDB()->onTransactionIdle(
+ function() use ( $oldTitleFile, $newTitleFile, $archiveNames ) {
+ $oldTitleFile->purgeEverything();
+ foreach ( $archiveNames as $archiveName ) {
+ $oldTitleFile->purgeOldThumbnails( $archiveName );
+ }
+ $newTitleFile->purgeEverything();
+ }
+ );
+
if ( $status->isOK() ) {
// Now switch the object
$this->title = $target;
// Force regeneration of the name and hashpath
unset( $this->name );
unset( $this->hashPath );
- // Purge the new image
- $this->purgeEverything();
}
return $status;
@@ -1484,7 +1546,6 @@ class LocalFile extends File {
* @return FileRepoStatus object.
*/
function delete( $reason, $suppress = false ) {
- global $wgUseSquid;
if ( $this->getRepo()->getReadOnlyReason() !== false ) {
return $this->readOnlyFatalStatus();
}
@@ -1502,19 +1563,28 @@ class LocalFile extends File {
DeferredUpdates::addUpdate( SiteStatsUpdate::factory( array( 'images' => -1 ) ) );
}
- $this->purgeEverything();
- foreach ( $archiveNames as $archiveName ) {
- $this->purgeOldThumbnails( $archiveName );
- }
+ // Hack: the lock()/unlock() pair is nested in a transaction so the locking is not
+ // tied to BEGIN/COMMIT. To avoid slow purges in the transaction, move them outside.
+ $file = $this;
+ $this->getRepo()->getMasterDB()->onTransactionIdle(
+ function() use ( $file, $archiveNames ) {
+ global $wgUseSquid;
- if ( $wgUseSquid ) {
- // Purge the squid
- $purgeUrls = array();
- foreach ($archiveNames as $archiveName ) {
- $purgeUrls[] = $this->getArchiveUrl( $archiveName );
+ $file->purgeEverything();
+ foreach ( $archiveNames as $archiveName ) {
+ $file->purgeOldThumbnails( $archiveName );
+ }
+
+ if ( $wgUseSquid ) {
+ // Purge the squid
+ $purgeUrls = array();
+ foreach ( $archiveNames as $archiveName ) {
+ $purgeUrls[] = $file->getArchiveUrl( $archiveName );
+ }
+ SquidUpdate::purge( $purgeUrls );
+ }
}
- SquidUpdate::purge( $purgeUrls );
- }
+ );
return $status;
}
@@ -1606,21 +1676,27 @@ class LocalFile extends File {
* @return String
*/
function getDescriptionUrl() {
- return $this->title->getLocalUrl();
+ return $this->title->getLocalURL();
}
/**
* Get the HTML text of the description page
* This is not used by ImagePage for local files, since (among other things)
* it skips the parser cache.
+ *
+ * @param $lang Language What language to get description in (Optional)
* @return bool|mixed
*/
- function getDescriptionText() {
+ function getDescriptionText( $lang = null ) {
$revision = Revision::newFromTitle( $this->title, false, Revision::READ_NORMAL );
- if ( !$revision ) return false;
+ if ( !$revision ) {
+ return false;
+ }
$content = $revision->getContent();
- if ( !$content ) return false;
- $pout = $content->getParserOutput( $this->title, null, new ParserOptions() );
+ if ( !$content ) {
+ return false;
+ }
+ $pout = $content->getParserOutput( $this->title, null, new ParserOptions( null, $lang ) );
return $pout->getText();
}
@@ -1674,11 +1750,13 @@ class LocalFile extends File {
}
/**
- * @return bool
+ * @return bool Whether to cache in RepoGroup (this avoids OOMs)
*/
function isCacheable() {
$this->load();
- return strlen( $this->metadata ) <= self::CACHE_FIELD_MAX_LEN; // avoid OOMs
+ // If extra data (metadata) was not loaded then it must have been large
+ return $this->extraDataLoaded
+ && strlen( serialize( $this->metadata ) ) <= self::CACHE_FIELD_MAX_LEN;
}
/**
@@ -1695,6 +1773,16 @@ class LocalFile extends File {
$this->lockedOwnTrx = true;
}
$this->locked++;
+ // Bug 54736: use simple lock to handle when the file does not exist.
+ // SELECT FOR UPDATE only locks records not the gaps where there are none.
+ $cache = wfGetMainCache();
+ $key = $this->getCacheKey();
+ if ( !$cache->lock( $key, 60 ) ) {
+ throw new MWException( "Could not acquire lock for '{$this->getName()}.'" );
+ }
+ $dbw->onTransactionIdle( function() use ( $cache, $key ) {
+ $cache->unlock( $key ); // release on commit
+ } );
}
return $dbw->selectField( 'image', '1',
@@ -2189,7 +2277,7 @@ class LocalFileRestoreBatch {
$deletedRel = $this->file->repo->getDeletedHashPath( $row->fa_storage_key ) . $row->fa_storage_key;
$deletedUrl = $this->file->repo->getVirtualUrl() . '/deleted/' . $deletedRel;
- if( isset( $row->fa_sha1 ) ) {
+ if ( isset( $row->fa_sha1 ) ) {
$sha1 = $row->fa_sha1;
} else {
// old row, populate from key
diff --git a/includes/filerepo/file/OldLocalFile.php b/includes/filerepo/file/OldLocalFile.php
index 5c505928..2c545963 100644
--- a/includes/filerepo/file/OldLocalFile.php
+++ b/includes/filerepo/file/OldLocalFile.php
@@ -218,6 +218,7 @@ class OldLocalFile extends LocalFile {
$this->$name = $value;
}
} else {
+ wfProfileOut( __METHOD__ );
throw new MWException( "Could not find data for image '{$this->archive_name}'." );
}
@@ -290,7 +291,7 @@ class OldLocalFile extends LocalFile {
*/
function isDeleted( $field ) {
$this->load();
- return ($this->deleted & $field) == $field;
+ return ( $this->deleted & $field ) == $field;
}
/**
diff --git a/includes/ImageGallery.php b/includes/gallery/ImageGalleryBase.php
index 1556ad94..f8b8c505 100644
--- a/includes/ImageGallery.php
+++ b/includes/gallery/ImageGalleryBase.php
@@ -27,8 +27,8 @@
*
* @ingroup Media
*/
-class ImageGallery {
- var $mImages, $mShowBytes, $mShowFilename;
+abstract class ImageGalleryBase extends ContextSource {
+ var $mImages, $mShowBytes, $mShowFilename, $mMode;
var $mCaption = false;
/**
@@ -50,18 +50,51 @@ class ImageGallery {
protected $mAttribs = array();
+ static private $modeMapping = false;
+
/**
- * Fixed margins
+ * Get a new image gallery. This is the method other callers
+ * should use to get a gallery.
+ *
+ * @param String|bool $mode Mode to use. False to use the default.
*/
- const THUMB_PADDING = 30;
- const GB_PADDING = 5;
- // 2px borders on each side + 2px implied padding on each side
- const GB_BORDERS = 8;
+ static function factory( $mode = false ) {
+ global $wgGalleryOptions, $wgContLang;
+ self::loadModes();
+ if ( !$mode ) {
+ $mode = $wgGalleryOptions['mode'];
+ }
+
+ $mode = $wgContLang->lc( $mode );
+
+ if ( isset( self::$modeMapping[$mode] ) ) {
+ return new self::$modeMapping[$mode]( $mode );
+ } else {
+ throw new MWException( "No gallery class registered for mode $mode" );
+ }
+ }
+
+ static private function loadModes() {
+ if ( self::$modeMapping === false ) {
+ self::$modeMapping = array(
+ 'traditional' => 'TraditionalImageGallery',
+ 'nolines' => 'NolinesImageGallery',
+ 'packed' => 'PackedImageGallery',
+ 'packed-hover' => 'PackedHoverImageGallery',
+ 'packed-overlay' => 'PackedOverlayImageGallery',
+ );
+ // Allow extensions to make a new gallery format.
+ wfRunHooks( 'GalleryGetModes', self::$modeMapping );
+ }
+ }
/**
* Create a new image gallery object.
+ *
+ * You should not call this directly, but instead use
+ * ImageGalleryBase::factory().
*/
- function __construct() {
+ function __construct( $mode = 'traditional' ) {
global $wgGalleryOptions;
$this->mImages = array();
$this->mShowBytes = $wgGalleryOptions['showBytes'];
@@ -72,10 +105,16 @@ class ImageGallery {
$this->mWidths = $wgGalleryOptions['imageWidth'];
$this->mHeights = $wgGalleryOptions['imageHeight'];
$this->mCaptionLength = $wgGalleryOptions['captionLength'];
+ $this->mMode = $mode;
}
/**
- * Register a parser object
+ * Register a parser object. If you do not set this
+ * and the output of this gallery ends up in parser
+ * cache, the javascript will break!
+ *
+ * @note This also triggers using the page's target
+ * language instead of the user language.
*
* @param $parser Parser
*/
@@ -143,6 +182,15 @@ class ImageGallery {
}
/**
+ * Allow setting additional options. This is meant
+ * to allow extensions to add additional parameters to
+ * <gallery> parser tag.
+ *
+ * @param Array $options Attributes of gallery tag.
+ */
+ public function setAdditionalOptions( $options ) { }
+
+ /**
* Instruct the class to use a specific skin for rendering
*
* @param $skin Skin object
@@ -160,13 +208,14 @@ class ImageGallery {
* @param $html String: Additional HTML text to be shown. The name and size of the image are always shown.
* @param $alt String: Alt text for the image
* @param $link String: Override image link (optional)
+ * @param $handlerOpts Array: Array of options for image handler (aka page number)
*/
- function add( $title, $html = '', $alt = '', $link = '' ) {
+ function add( $title, $html = '', $alt = '', $link = '', $handlerOpts = array() ) {
if ( $title instanceof File ) {
// Old calling convention
$title = $title->getTitle();
}
- $this->mImages[] = array( $title, $html, $alt, $link );
+ $this->mImages[] = array( $title, $html, $alt, $link, $handlerOpts );
wfDebug( 'ImageGallery::add ' . $title->getText() . "\n" );
}
@@ -176,13 +225,15 @@ class ImageGallery {
* @param $title Title object of the image that is added to the gallery
* @param $html String: Additional HTML text to be shown. The name and size of the image are always shown.
* @param $alt String: Alt text for the image
+ * @param $link String: Override image link (optional)
+ * @param $handlerOpts Array: Array of options for image handler (aka page number)
*/
- function insert( $title, $html = '', $alt = '' ) {
+ function insert( $title, $html = '', $alt = '', $link = '', $handlerOpts = array() ) {
if ( $title instanceof File ) {
// Old calling convention
$title = $title->getTitle();
}
- array_unshift( $this->mImages, array( &$title, $html, $alt ) );
+ array_unshift( $this->mImages, array( &$title, $html, $alt, $link, $handlerOpts ) );
}
/**
@@ -227,153 +278,11 @@ class ImageGallery {
}
/**
- * Return a HTML representation of the image gallery
+ * Display an html representation of the gallery
*
- * For each image in the gallery, display
- * - a thumbnail
- * - the image name
- * - the additional text provided when adding the image
- * - the size of the image
- *
- * @return string
+ * @return String The html
*/
- function toHTML() {
- 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'] : '';
- # _width is ignored by any sane browser. IE6 doesn't know max-width so it uses _width instead
- $this->mAttribs['style'] = "max-width: {$maxwidth}px;_width: {$maxwidth}px;" . $oldStyle;
- }
-
- $attribs = Sanitizer::mergeAttributes(
- array( 'class' => 'gallery' ), $this->mAttribs );
-
- $output = Xml::openElement( 'ul', $attribs );
- if ( $this->mCaption ) {
- $output .= "\n\t<li class='gallerycaption'>{$this->mCaption}</li>";
- }
-
- $lang = $this->getLang();
- $params = array(
- 'width' => $this->mWidths,
- 'height' => $this->mHeights
- );
- # Output each image...
- foreach ( $this->mImages as $pair ) {
- $nt = $pair[0];
- $text = $pair[1]; # "text" means "caption" here
- $alt = $pair[2];
- $link = $pair[3];
-
- $descQuery = false;
- if ( $nt->getNamespace() == NS_FILE ) {
- # Get the file...
- if ( $this->mParser instanceof Parser ) {
- # Give extensions a chance to select the file revision for us
- $options = array();
- wfRunHooks( 'BeforeParserFetchFileAndTitle',
- array( $this->mParser, $nt, &$options, &$descQuery ) );
- # Fetch and register the file (file title may be different via hooks)
- list( $img, $nt ) = $this->mParser->fetchFileAndTitle( $nt, $options );
- } else {
- $img = wfFindFile( $nt );
- }
- } else {
- $img = false;
- }
-
- if( !$img ) {
- # We're dealing with a non-image, spit out the name and be done with it.
- $thumbhtml = "\n\t\t\t" . '<div style="height: ' . ( self::THUMB_PADDING + $this->mHeights ) . 'px;">'
- . htmlspecialchars( $nt->getText() ) . '</div>';
- } elseif( $this->mHideBadImages && wfIsBadImage( $nt->getDBkey(), $this->getContextTitle() ) ) {
- # The image is blacklisted, just show it as a text link.
- $thumbhtml = "\n\t\t\t" . '<div style="height: ' . ( self::THUMB_PADDING + $this->mHeights ) . 'px;">' .
- Linker::link(
- $nt,
- htmlspecialchars( $nt->getText() ),
- array(),
- array(),
- array( 'known', 'noclasses' )
- ) .
- '</div>';
- } elseif( !( $thumb = $img->transform( $params ) ) ) {
- # Error generating thumbnail.
- $thumbhtml = "\n\t\t\t" . '<div style="height: ' . ( self::THUMB_PADDING + $this->mHeights ) . 'px;">'
- . htmlspecialchars( $img->getLastError() ) . '</div>';
- } else {
- $vpad = ( self::THUMB_PADDING + $this->mHeights - $thumb->height ) /2;
-
- $imageParameters = array(
- 'desc-link' => true,
- 'desc-query' => $descQuery,
- 'alt' => $alt,
- 'custom-url-link' => $link
- );
- # In the absence of both alt text and caption, fall back on providing screen readers with the filename as alt text
- if ( $alt == '' && $text == '' ) {
- $imageParameters['alt'] = $nt->getText();
- }
-
- # Set both fixed width and min-height.
- $thumbhtml = "\n\t\t\t" .
- '<div class="thumb" style="width: ' . ( $this->mWidths + self::THUMB_PADDING ) . 'px;">'
- # Auto-margin centering for block-level elements. Needed now that we have video
- # handlers since they may emit block-level elements as opposed to simple <img> tags.
- # ref http://css-discuss.incutio.com/?page=CenteringBlockElement
- . '<div style="margin:' . $vpad . 'px auto;">'
- . $thumb->toHtml( $imageParameters ) . '</div></div>';
-
- // Call parser transform hook
- if ( $this->mParser && $img->getHandler() ) {
- $img->getHandler()->parserTransformHook( $this->mParser, $img );
- }
- }
-
- //TODO
- // $linkTarget = Title::newFromText( $wgContLang->getNsText( MWNamespace::getUser() ) . ":{$ut}" );
- // $ul = Linker::link( $linkTarget, $ut );
-
- if( $this->mShowBytes ) {
- if( $img ) {
- $fileSize = htmlspecialchars( $lang->formatSize( $img->getSize() ) );
- } else {
- $fileSize = wfMessage( 'filemissing' )->escaped();
- }
- $fileSize = "$fileSize<br />\n";
- } else {
- $fileSize = '';
- }
-
- $textlink = $this->mShowFilename ?
- Linker::link(
- $nt,
- 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 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">'
- . $thumbhtml
- . "\n\t\t\t" . '<div class="gallerytext">' . "\n"
- . $textlink . $text . $fileSize
- . "\n\t\t\t</div>"
- . "\n\t\t</div></li>";
- }
- $output .= "\n</ul>";
-
- return $output;
- }
+ abstract public function toHTML();
/**
* @return Integer: number of images in the gallery
@@ -406,11 +315,17 @@ class ImageGallery {
* Determines the correct language to be used for this image gallery
* @return Language object
*/
- private function getLang() {
- global $wgLang;
+ protected function getRenderLang() {
return $this->mParser
? $this->mParser->getTargetLanguage()
- : $wgLang;
+ : $this->getLanguage();
}
-} //class
+ /* Old constants no longer used.
+ const THUMB_PADDING = 30;
+ const GB_PADDING = 5;
+ const GB_BORDERS = 8;
+ */
+
+}
+
diff --git a/includes/gallery/NolinesImageGallery.php b/includes/gallery/NolinesImageGallery.php
new file mode 100644
index 00000000..6b0d0fa6
--- /dev/null
+++ b/includes/gallery/NolinesImageGallery.php
@@ -0,0 +1,38 @@
+<?php
+/**
+ * Nolines image gallery. Like "traditional" but without borders and
+ * less padding.
+ *
+ * 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 NolinesImageGallery extends TraditionalImageGallery {
+
+ protected function getThumbPadding() {
+ return 0;
+ }
+
+ protected function getGBBorders() {
+ // This accounts for extra space between <li> elements.
+ return 4;
+ }
+
+ protected function getVPad( $boxHeight, $thumbHeight ) {
+ return 0;
+ }
+}
diff --git a/includes/gallery/PackedImageGallery.php b/includes/gallery/PackedImageGallery.php
new file mode 100644
index 00000000..963ee6b9
--- /dev/null
+++ b/includes/gallery/PackedImageGallery.php
@@ -0,0 +1,105 @@
+<?php
+/**
+ * Packed image gallery. All images adjusted to be same height.
+ *
+ * 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 PackedImageGallery extends TraditionalImageGallery {
+
+ function __construct( $mode = 'traditional' ) {
+ parent::__construct( $mode );
+ // Does not support per row option.
+ $this->mPerRow = 0;
+ }
+
+ /**
+ * We artificially have 1.5 the resolution neccessary so that
+ * we can scale it up by that much on the client side, without
+ * worrying about requesting a new image.
+ */
+ const SCALE_FACTOR = 1.5;
+
+ protected function getVPad( $boxHeight, $thumbHeight ) {
+ return ( $this->getThumbPadding() + $boxHeight - $thumbHeight / self::SCALE_FACTOR ) / 2;
+ }
+
+ protected function getThumbPadding() {
+ return 0;
+ }
+
+ protected function getGBPadding() {
+ return 2;
+ }
+
+ /**
+ * @param File $img The file being transformed. May be false
+ */
+ protected function getThumbParams( $img ) {
+ if ( $img && $img->getMediaType() === MEDIATYPE_AUDIO ) {
+ $width = $this->mWidths;
+ } else {
+ // We want the width not to be the constraining
+ // factor, so use random big number.
+ $width = $this->mHeights * 10 + 100;
+ }
+ // self::SCALE_FACTOR so the js has some room to manipulate sizes.
+ return array(
+ 'width' => $width * self::SCALE_FACTOR,
+ 'height' => $this->mHeights * self::SCALE_FACTOR,
+ );
+ }
+
+ protected function getThumbDivWidth( $thumbWidth ) {
+ // Require at least 60px wide, so caption is wide enough to work.
+ if ( $thumbWidth < 60 * self::SCALE_FACTOR ) {
+ $thumbWidth = 60 * self::SCALE_FACTOR;
+ }
+ return $thumbWidth / self::SCALE_FACTOR + $this->getThumbPadding();
+ }
+
+ /**
+ * @param MediaTransformOutput|bool $thumb the thumbnail, or false if no thumb (which can happen)
+ */
+ protected function getGBWidth( $thumb ) {
+ $thumbWidth = $thumb ? $thumb->getWidth() : $this->mWidths * self::SCALE_FACTOR;
+ return $this->getThumbDivWidth( $thumbWidth ) + $this->getGBPadding();
+ }
+
+ protected function adjustImageParameters( $thumb, &$imageParameters ) {
+ // Re-adjust back to normal size.
+ $imageParameters['override-width'] = ceil( $thumb->getWidth() / self::SCALE_FACTOR );
+ $imageParameters['override-height'] = ceil( $thumb->getHeight() / self::SCALE_FACTOR );
+ }
+
+ /**
+ * Add javascript which auto-justifies the rows by manipulating the image sizes.
+ * Also ensures that the hover version of this degrades gracefully.
+ */
+ protected function getModules() {
+ return array( 'mediawiki.page.gallery' );
+ }
+
+ /**
+ * Do not support per-row on packed. It really doesn't work
+ * since the images have varying widths.
+ */
+ public function setPerRow( $num ) {
+ return;
+ }
+}
diff --git a/includes/gallery/PackedOverlayImageGallery.php b/includes/gallery/PackedOverlayImageGallery.php
new file mode 100644
index 00000000..bba06fcf
--- /dev/null
+++ b/includes/gallery/PackedOverlayImageGallery.php
@@ -0,0 +1,60 @@
+<?php
+/**
+ * Packed overlay image gallery. All images adjusted to be same height and
+ * image caption being placed over top of image.
+ *
+ * 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 PackedOverlayImageGallery extends PackedImageGallery {
+
+ /**
+ * Add the wrapper html around the thumb's caption
+ *
+ * @param String $galleryText The caption
+ * @param MediaTransformOutput|boolean $thumb The thumb this caption is for or false for bad image.
+ */
+ protected function wrapGalleryText( $galleryText, $thumb ) {
+
+ // If we have no text, do not output anything to avoid
+ // ugly white overlay.
+ if ( trim( $galleryText ) === '' ) {
+ return '';
+ }
+
+ # 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
+
+ $thumbWidth = $this->getGBWidth( $thumb ) - $this->getThumbPadding() - $this->getGBPadding();
+ $captionWidth = ceil( $thumbWidth - 20 );
+
+ $outerWrapper = '<div class="gallerytextwrapper" style="width: ' . $captionWidth . 'px">';
+ return "\n\t\t\t" . $outerWrapper . '<div class="gallerytext">' . "\n"
+ . $galleryText
+ . "\n\t\t\t</div>";
+ }
+}
+
+/**
+ * Same as Packed except different CSS is applied to make the
+ * caption only show up on hover. If a touch screen is detected,
+ * falls back to PackedHoverGallery. Degrades gracefully for
+ * screen readers.
+ */
+class PackedHoverImageGallery extends PackedOverlayImageGallery { }
diff --git a/includes/gallery/TraditionalImageGallery.php b/includes/gallery/TraditionalImageGallery.php
new file mode 100644
index 00000000..1f60fa69
--- /dev/null
+++ b/includes/gallery/TraditionalImageGallery.php
@@ -0,0 +1,328 @@
+<?php
+/**
+ * Image gallery.
+ *
+ * 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 TraditionalImageGallery extends ImageGalleryBase {
+
+
+ /**
+ * Return a HTML representation of the image gallery
+ *
+ * For each image in the gallery, display
+ * - a thumbnail
+ * - the image name
+ * - the additional text provided when adding the image
+ * - the size of the image
+ *
+ * @return string
+ */
+ function toHTML() {
+ if ( $this->mPerRow > 0 ) {
+ $maxwidth = $this->mPerRow * ( $this->mWidths + $this->getAllPadding() );
+ $oldStyle = isset( $this->mAttribs['style'] ) ? $this->mAttribs['style'] : '';
+ # _width is ignored by any sane browser. IE6 doesn't know max-width so it uses _width instead
+ $this->mAttribs['style'] = "max-width: {$maxwidth}px;_width: {$maxwidth}px;" . $oldStyle;
+ }
+
+ $attribs = Sanitizer::mergeAttributes(
+ array( 'class' => 'gallery mw-gallery-' . $this->mMode ), $this->mAttribs );
+
+ $modules = $this->getModules();
+
+ if ( $this->mParser ) {
+ $this->mParser->getOutput()->addModules( $modules );
+ } else {
+ $this->getOutput()->addModules( $modules );
+ }
+ $output = Xml::openElement( 'ul', $attribs );
+ if ( $this->mCaption ) {
+ $output .= "\n\t<li class='gallerycaption'>{$this->mCaption}</li>";
+ }
+
+ $lang = $this->getRenderLang();
+ # Output each image...
+ foreach ( $this->mImages as $pair ) {
+ $nt = $pair[0];
+ $text = $pair[1]; # "text" means "caption" here
+ $alt = $pair[2];
+ $link = $pair[3];
+
+ $descQuery = false;
+ if ( $nt->getNamespace() === NS_FILE ) {
+ # Get the file...
+ if ( $this->mParser instanceof Parser ) {
+ # Give extensions a chance to select the file revision for us
+ $options = array();
+ wfRunHooks( 'BeforeParserFetchFileAndTitle',
+ array( $this->mParser, $nt, &$options, &$descQuery ) );
+ # Fetch and register the file (file title may be different via hooks)
+ list( $img, $nt ) = $this->mParser->fetchFileAndTitle( $nt, $options );
+ } else {
+ $img = wfFindFile( $nt );
+ }
+ } else {
+ $img = false;
+ }
+
+ $params = $this->getThumbParams( $img );
+ // $pair[4] is per image handler options
+ $transformOptions = $params + $pair[4];
+
+ $thumb = false;
+
+ if ( !$img ) {
+ # We're dealing with a non-image, spit out the name and be done with it.
+ $thumbhtml = "\n\t\t\t" . '<div class="thumb" style="height: ' . ( $this->getThumbPadding() + $this->mHeights ) . 'px;">'
+ . htmlspecialchars( $nt->getText() ) . '</div>';
+
+ if ( $this->mParser instanceof Parser ) {
+ $this->mParser->addTrackingCategory( 'broken-file-category' );
+ }
+ } elseif ( $this->mHideBadImages && wfIsBadImage( $nt->getDBkey(), $this->getContextTitle() ) ) {
+ # The image is blacklisted, just show it as a text link.
+ $thumbhtml = "\n\t\t\t" . '<div class="thumb" style="height: ' . ( $this->getThumbPadding() + $this->mHeights ) . 'px;">' .
+ Linker::link(
+ $nt,
+ htmlspecialchars( $nt->getText() ),
+ array(),
+ array(),
+ array( 'known', 'noclasses' )
+ ) .
+ '</div>';
+ } elseif ( !( $thumb = $img->transform( $transformOptions ) ) ) {
+ # Error generating thumbnail.
+ $thumbhtml = "\n\t\t\t" . '<div class="thumb" style="height: ' . ( $this->getThumbPadding() + $this->mHeights ) . 'px;">'
+ . htmlspecialchars( $img->getLastError() ) . '</div>';
+ } else {
+ $vpad = $this->getVPad( $this->mHeights, $thumb->getHeight() );
+
+ $imageParameters = array(
+ 'desc-link' => true,
+ 'desc-query' => $descQuery,
+ 'alt' => $alt,
+ 'custom-url-link' => $link
+ );
+ # In the absence of both alt text and caption, fall back on providing screen readers with the filename as alt text
+ if ( $alt == '' && $text == '' ) {
+ $imageParameters['alt'] = $nt->getText();
+ }
+
+ $this->adjustImageParameters( $thumb, $imageParameters );
+
+ # Set both fixed width and min-height.
+ $thumbhtml = "\n\t\t\t" .
+ '<div class="thumb" style="width: ' . $this->getThumbDivWidth( $thumb->getWidth() ) . 'px;">'
+ # Auto-margin centering for block-level elements. Needed now that we have video
+ # handlers since they may emit block-level elements as opposed to simple <img> tags.
+ # ref http://css-discuss.incutio.com/?page=CenteringBlockElement
+ . '<div style="margin:' . $vpad . 'px auto;">'
+ . $thumb->toHtml( $imageParameters ) . '</div></div>';
+
+ // Call parser transform hook
+ if ( $this->mParser && $img->getHandler() ) {
+ $img->getHandler()->parserTransformHook( $this->mParser, $img );
+ }
+ }
+
+ //TODO
+ // $linkTarget = Title::newFromText( $wgContLang->getNsText( MWNamespace::getUser() ) . ":{$ut}" );
+ // $ul = Linker::link( $linkTarget, $ut );
+
+ if ( $this->mShowBytes ) {
+ if ( $img ) {
+ $fileSize = htmlspecialchars( $lang->formatSize( $img->getSize() ) );
+ } else {
+ $fileSize = $this->msg( 'filemissing' )->escaped();
+ }
+ $fileSize = "$fileSize<br />\n";
+ } else {
+ $fileSize = '';
+ }
+
+ $textlink = $this->mShowFilename ?
+ Linker::link(
+ $nt,
+ htmlspecialchars( $lang->truncate( $nt->getText(), $this->mCaptionLength ) ),
+ array(),
+ array(),
+ array( 'known', 'noclasses' )
+ ) . "<br />\n" :
+ '';
+
+
+ $galleryText = $textlink . $text . $fileSize;
+ $galleryText = $this->wrapGalleryText( $galleryText, $thumb );
+
+ # Weird double wrapping (the extra div inside the li) needed due to FF2 bug
+ # Can be safely removed if FF2 falls completely out of existence
+ $output .=
+ "\n\t\t" . '<li class="gallerybox" style="width: ' . $this->getGBWidth( $thumb ) . 'px">'
+ . '<div style="width: ' . $this->getGBWidth( $thumb ) . 'px">'
+ . $thumbhtml
+ . $galleryText
+ . "\n\t\t</div></li>";
+ }
+ $output .= "\n</ul>";
+
+ return $output;
+ }
+
+
+ /**
+ * Add the wrapper html around the thumb's caption
+ *
+ * @param String $galleryText The caption
+ * @param MediaTransformOutput|boolean $thumb The thumb this caption is for or false for bad image.
+ */
+ protected function wrapGalleryText( $galleryText, $thumb ) {
+ # 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
+
+ return "\n\t\t\t" . '<div class="gallerytext">' . "\n"
+ . $galleryText
+ . "\n\t\t\t</div>";
+ }
+
+ /**
+ * How much padding such the thumb have between image and inner div that
+ * that contains the border. This is both for verical and horizontal
+ * padding. (However, it is cut in half in the vertical direction).
+ * @return int
+ */
+ protected function getThumbPadding() {
+ return 30;
+ }
+
+ /**
+ *
+ * @note GB stands for gallerybox (as in the <li class="gallerybox"> element)
+ *
+ * @return int
+ */
+ protected function getGBPadding() {
+ return 5;
+ }
+
+ /**
+ * Get how much extra space the borders around the image takes up.
+ *
+ * For this mode, it is 2px borders on each side + 2px implied padding on
+ * each side from the stylesheet, giving us 2*2+2*2 = 8.
+ * @return int
+ */
+ protected function getGBBorders() {
+ return 8;
+ }
+
+ /**
+ * Get total padding.
+ *
+ * @return int How many pixels of whitespace surround the thumbnail.
+ */
+ protected function getAllPadding() {
+ return $this->getThumbPadding() + $this->getGBPadding() + $this->getGBBorders();
+ }
+
+ /**
+ * Get vertical padding for a thumbnail
+ *
+ * Generally this is the total height minus how high the thumb is.
+ *
+ * @param int $boxHeight How high we want the box to be.
+ * @param int $thumbHeight How high the thumbnail is.
+ * @return int How many vertical padding to add on each side.
+ */
+ protected function getVPad( $boxHeight, $thumbHeight ) {
+ return ( $this->getThumbPadding() + $boxHeight - $thumbHeight ) / 2;
+ }
+
+ /**
+ * Get the transform parameters for a thumbnail.
+ *
+ * @param File $img The file in question. May be false for invalid image
+ */
+ protected function getThumbParams( $img ) {
+ return array(
+ 'width' => $this->mWidths,
+ 'height' => $this->mHeights
+ );
+ }
+
+ /**
+ * Get the width of the inner div that contains the thumbnail in
+ * question. This is the div with the class of "thumb".
+ *
+ * @param int $thumbWidth The width of the thumbnail.
+ * @return int Width of inner thumb div.
+ */
+ protected function getThumbDivWidth( $thumbWidth ) {
+ return $this->mWidths + $this->getThumbPadding();
+ }
+
+ /**
+ * Width of gallerybox <li>.
+ *
+ * Generally is the width of the image, plus padding on image
+ * plus padding on gallerybox.
+ *
+ * @note Important: parameter will be false if no thumb used.
+ * @param Mixed $thumb MediaTransformObject object or false.
+ * @return int width of gallerybox element
+ */
+ protected function getGBWidth( $thumb ) {
+ return $this->mWidths + $this->getThumbPadding() + $this->getGBPadding();
+ }
+
+ /**
+ * Get a list of modules to include in the page.
+ *
+ * Primarily intended for subclasses.
+ *
+ * @return Array modules to include
+ */
+ protected function getModules() {
+ return array();
+ }
+
+ /**
+ * Adjust the image parameters for a thumbnail.
+ *
+ * Used by a subclass to insert extra high resolution images.
+ * @param MediaTransformOutput $thumb The thumbnail
+ * @param Array $imageParameters Array of options
+ */
+ protected function adjustImageParameters( $thumb, &$imageParameters ) { }
+}
+
+/**
+ * Backwards compatibility. This always uses traditional mode
+ * if called the old way, for extensions that may expect traditional
+ * mode.
+ *
+ * @deprecated 1.22 Use ImageGalleryBase::factory instead.
+ */
+class ImageGallery extends TraditionalImageGallery {
+ function __construct( $mode = 'traditional' ) {
+ wfDeprecated( __METHOD__, '1.22' );
+ parent::__construct( $mode );
+ }
+}
diff --git a/includes/installer/CliInstaller.php b/includes/installer/CliInstaller.php
index bb7e8776..f944fbed 100644
--- a/includes/installer/CliInstaller.php
+++ b/includes/installer/CliInstaller.php
@@ -114,7 +114,7 @@ class CliInstaller extends Installer {
*/
public function execute() {
$vars = Installer::getExistingLocalSettings();
- if( $vars ) {
+ if ( $vars ) {
$this->showStatusMessage(
Status::newFatal( "config-localsettings-cli-upgrade" )
);
@@ -137,6 +137,8 @@ class CliInstaller extends Installer {
}
public function startStage( $step ) {
+ // Messages: config-install-database, config-install-tables, config-install-interwiki,
+ // config-install-stats, config-install-keys, config-install-sysop, config-install-mainpage
$this->showMessage( "config-install-$step" );
}
@@ -166,6 +168,7 @@ class CliInstaller extends Installer {
$text = wfMessage( $msg, $params )->parse();
$text = preg_replace( '/<a href="(.*?)".*?>(.*?)<\/a>/', '$2 &lt;$1&gt;', $text );
+
return html_entity_decode( strip_tags( $text ), ENT_QUOTES );
}
@@ -195,15 +198,17 @@ class CliInstaller extends Installer {
if ( !$this->specifiedScriptPath ) {
$this->showMessage( 'config-no-cli-uri', $this->getVar( "wgScriptPath" ) );
}
+
return parent::envCheckPath();
}
protected function envGetDefaultServer() {
- return $this->getVar( 'wgServer' );
+ return null; // Do not guess if installing from CLI
}
public function dirIsExecutable( $dir, $url ) {
$this->showMessage( 'config-no-cli-uploads-check', $dir );
+
return false;
}
}
diff --git a/includes/installer/DatabaseInstaller.php b/includes/installer/DatabaseInstaller.php
index 3472b7ff..0110ac52 100644
--- a/includes/installer/DatabaseInstaller.php
+++ b/includes/installer/DatabaseInstaller.php
@@ -32,7 +32,7 @@ abstract class DatabaseInstaller {
/**
* The Installer object.
*
- * TODO: naming this parent is confusing, 'installer' would be clearer.
+ * @todo Naming this parent is confusing, 'installer' would be clearer.
*
* @var WebInstaller
*/
@@ -158,6 +158,7 @@ abstract class DatabaseInstaller {
$this->db->clearFlag( DBO_TRX );
$this->db->commit( __METHOD__ );
}
+
return $status;
}
@@ -173,9 +174,10 @@ abstract class DatabaseInstaller {
}
$this->db->selectDB( $this->getVar( 'wgDBname' ) );
- if( $this->db->tableExists( 'archive', __METHOD__ ) ) {
+ if ( $this->db->tableExists( 'archive', __METHOD__ ) ) {
$status->warning( 'config-install-tables-exist' );
$this->enableLB();
+
return $status;
}
@@ -183,7 +185,7 @@ abstract class DatabaseInstaller {
$this->db->begin( __METHOD__ );
$error = $this->db->sourceFile( $this->db->getSchemaPath() );
- if( $error !== true ) {
+ if ( $error !== true ) {
$this->db->reportQueryError( $error, 0, '', __METHOD__ );
$this->db->rollback( __METHOD__ );
$status->fatal( 'config-install-tables-failed', $error );
@@ -191,9 +193,10 @@ abstract class DatabaseInstaller {
$this->db->commit( __METHOD__ );
}
// Resume normal operations
- if( $status->isOk() ) {
+ if ( $status->isOk() ) {
$this->enableLB();
}
+
return $status;
}
@@ -279,6 +282,7 @@ abstract class DatabaseInstaller {
}
$up->purgeCache();
ob_end_flush();
+
return $ret;
}
@@ -288,14 +292,12 @@ abstract class DatabaseInstaller {
* long after the constructor. Helpful for things like modifying setup steps :)
*/
public function preInstall() {
-
}
/**
* Allow DB installers a chance to make checks before upgrade.
*/
public function preUpgrade() {
-
}
/**
@@ -319,15 +321,11 @@ abstract class DatabaseInstaller {
* Convenience function.
* Check if a named extension is present.
*
- * @see wfDl
* @param $name
* @return bool
*/
protected static function checkExtension( $name ) {
- wfSuppressWarnings();
- $compiled = wfDl( $name );
- wfRestoreWarnings();
- return $compiled;
+ return extension_loaded( $name );
}
/**
@@ -335,6 +333,8 @@ abstract class DatabaseInstaller {
* @return String
*/
public function getReadableName() {
+ // Messages: config-type-mysql, config-type-postgres, config-type-sqlite,
+ // config-type-oracle
return wfMessage( 'config-type-' . $this->getName() )->text();
}
@@ -369,6 +369,7 @@ abstract class DatabaseInstaller {
} elseif ( isset( $internal[$var] ) ) {
$default = $internal[$var];
}
+
return $this->parent->getVar( $var, $default );
}
@@ -396,6 +397,7 @@ abstract class DatabaseInstaller {
if ( !isset( $attribs ) ) {
$attribs = array();
}
+
return $this->parent->getTextBox( array(
'var' => $var,
'label' => $label,
@@ -422,6 +424,7 @@ abstract class DatabaseInstaller {
if ( !isset( $attribs ) ) {
$attribs = array();
}
+
return $this->parent->getPasswordBox( array(
'var' => $var,
'label' => $label,
@@ -440,6 +443,7 @@ abstract class DatabaseInstaller {
public function getCheckBox( $var, $label, $attribs = array(), $helpData = "" ) {
$name = $this->getName() . '_' . $var;
$value = $this->getVar( $var );
+
return $this->parent->getCheckBox( array(
'var' => $var,
'label' => $label,
@@ -447,7 +451,7 @@ abstract class DatabaseInstaller {
'controlName' => $name,
'value' => $value,
'help' => $helpData
- ));
+ ) );
}
/**
@@ -466,6 +470,7 @@ abstract class DatabaseInstaller {
public function getRadioSet( $params ) {
$params['controlName'] = $this->getName() . '_' . $params['var'];
$params['value'] = $this->getVar( $params['var'] );
+
return $this->parent->getRadioSet( $params );
}
@@ -499,7 +504,9 @@ abstract class DatabaseInstaller {
if ( !$this->db->selectDB( $this->getVar( 'wgDBname' ) ) ) {
return false;
}
- return $this->db->tableExists( 'cur', __METHOD__ ) || $this->db->tableExists( 'revision', __METHOD__ );
+
+ return $this->db->tableExists( 'cur', __METHOD__ ) ||
+ $this->db->tableExists( 'revision', __METHOD__ );
}
/**
@@ -508,11 +515,20 @@ abstract class DatabaseInstaller {
* @return String
*/
public function getInstallUserBox() {
- return
- Html::openElement( 'fieldset' ) .
+ return Html::openElement( 'fieldset' ) .
Html::element( 'legend', array(), wfMessage( 'config-db-install-account' )->text() ) .
- $this->getTextBox( '_InstallUser', 'config-db-username', array( 'dir' => 'ltr' ), $this->parent->getHelpBox( 'config-db-install-username' ) ) .
- $this->getPasswordBox( '_InstallPassword', 'config-db-password', array( 'dir' => 'ltr' ), $this->parent->getHelpBox( 'config-db-install-password' ) ) .
+ $this->getTextBox(
+ '_InstallUser',
+ 'config-db-username',
+ array( 'dir' => 'ltr' ),
+ $this->parent->getHelpBox( 'config-db-install-username' )
+ ) .
+ $this->getPasswordBox(
+ '_InstallPassword',
+ 'config-db-password',
+ array( 'dir' => 'ltr' ),
+ $this->parent->getHelpBox( 'config-db-install-password' )
+ ) .
Html::closeElement( 'fieldset' );
}
@@ -522,6 +538,7 @@ abstract class DatabaseInstaller {
*/
public function submitInstallUserBox() {
$this->setVarsFromRequest( array( '_InstallUser', '_InstallPassword' ) );
+
return Status::newGood();
}
@@ -550,6 +567,7 @@ abstract class DatabaseInstaller {
$s .= $this->getCheckBox( '_CreateDBAccount', 'config-db-web-create' );
}
$s .= Html::closeElement( 'div' ) . Html::closeElement( 'fieldset' );
+
return $s;
}
@@ -568,7 +586,7 @@ abstract class DatabaseInstaller {
$this->setVar( 'wgDBpassword', $this->getVar( '_InstallPassword' ) );
}
- if( $this->getVar( '_CreateDBAccount' ) && strval( $this->getVar( 'wgDBpassword' ) ) == '' ) {
+ if ( $this->getVar( '_CreateDBAccount' ) && strval( $this->getVar( 'wgDBpassword' ) ) == '' ) {
return Status::newFatal( 'config-db-password-empty', $this->getVar( 'wgDBuser' ) );
}
@@ -587,8 +605,9 @@ abstract class DatabaseInstaller {
}
$this->db->selectDB( $this->getVar( 'wgDBname' ) );
- if( $this->db->selectRow( 'interwiki', '*', array(), __METHOD__ ) ) {
+ if ( $this->db->selectRow( 'interwiki', '*', array(), __METHOD__ ) ) {
$status->warning( 'config-install-interwiki-exists' );
+
return $status;
}
global $IP;
@@ -600,9 +619,11 @@ abstract class DatabaseInstaller {
if ( !$rows ) {
return Status::newFatal( 'config-install-interwiki-list' );
}
- foreach( $rows as $row ) {
+ foreach ( $rows as $row ) {
$row = preg_replace( '/^\s*([^#]*?)\s*(#.*)?$/', '\\1', $row ); // strip comments - whee
- if ( $row == "" ) continue;
+ if ( $row == "" ) {
+ continue;
+ }
$row .= "||";
$interwikis[] = array_combine(
array( 'iw_prefix', 'iw_url', 'iw_local', 'iw_api', 'iw_wikiid' ),
@@ -610,6 +631,7 @@ abstract class DatabaseInstaller {
);
}
$this->db->insert( 'interwiki', $interwikis, __METHOD__ );
+
return Status::newGood();
}
diff --git a/includes/installer/DatabaseUpdater.php b/includes/installer/DatabaseUpdater.php
index 25f751c7..267b6c5a 100644
--- a/includes/installer/DatabaseUpdater.php
+++ b/includes/installer/DatabaseUpdater.php
@@ -21,7 +21,7 @@
* @ingroup Deployment
*/
-require_once( __DIR__ . '/../../maintenance/Maintenance.php' );
+require_once __DIR__ . '/../../maintenance/Maintenance.php';
/**
* Class for handling database updates. Roughly based off of updaters.inc, with
@@ -89,11 +89,6 @@ abstract class DatabaseUpdater {
protected $skipSchema = false;
/**
- * Hold the value of $wgContentHandlerUseDB during the upgrade.
- */
- protected $wgContentHandlerUseDB = true;
-
- /**
* Constructor
*
* @param $db DatabaseBase object to perform updates on
@@ -135,7 +130,8 @@ abstract class DatabaseUpdater {
}
/**
- * Loads LocalSettings.php, if needed, and initialises everything needed for LoadExtensionSchemaUpdates hook
+ * Loads LocalSettings.php, if needed, and initialises everything needed for
+ * LoadExtensionSchemaUpdates hook.
*/
private function loadExtensions() {
if ( !defined( 'MEDIAWIKI_INSTALL' ) ) {
@@ -162,8 +158,9 @@ abstract class DatabaseUpdater {
*/
public static function newForDB( &$db, $shared = false, $maintenance = null ) {
$type = $db->getType();
- if( in_array( $type, Installer::getDBTypes() ) ) {
+ if ( in_array( $type, Installer::getDBTypes() ) ) {
$class = ucfirst( $type ) . 'Updater';
+
return new $class( $db, $shared, $maintenance );
} else {
throw new MWException( __METHOD__ . ' called for unsupported $wgDBtype' );
@@ -189,7 +186,7 @@ abstract class DatabaseUpdater {
return;
}
global $wgCommandLineMode;
- if( !$wgCommandLineMode ) {
+ if ( !$wgCommandLineMode ) {
$str = htmlspecialchars( $str );
}
echo $str;
@@ -293,11 +290,22 @@ abstract class DatabaseUpdater {
* @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 $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 );
+ public function renameExtensionIndex( $tableName, $oldIndexName, $newIndexName,
+ $sqlPath, $skipBothIndexExistWarning = false
+ ) {
+ $this->extensionUpdates[] = array(
+ 'renameIndex',
+ $tableName,
+ $oldIndexName,
+ $newIndexName,
+ $skipBothIndexExistWarning,
+ $sqlPath,
+ true
+ );
}
/**
@@ -307,7 +315,7 @@ abstract class DatabaseUpdater {
* @param string $fieldName The field to be modified
* @param string $sqlPath The path to the SQL change path
*/
- public function modifyExtensionField( $tableName, $fieldName, $sqlPath) {
+ public function modifyExtensionField( $tableName, $fieldName, $sqlPath ) {
$this->extensionUpdates[] = array( 'modifyField', $tableName, $fieldName, $sqlPath, true );
}
@@ -362,7 +370,7 @@ abstract class DatabaseUpdater {
$updates = $this->updatesSkipped;
$this->updatesSkipped = array();
- foreach( $updates as $funcList ) {
+ foreach ( $updates as $funcList ) {
$func = $funcList[0];
$arg = $funcList[1];
$origParams = $funcList[2];
@@ -378,7 +386,7 @@ abstract class DatabaseUpdater {
* @param array $what what updates to perform
*/
public function doUpdates( $what = array( 'core', 'extensions', 'stats' ) ) {
- global $wgVersion, $wgLocalisationCacheConf;
+ global $wgVersion;
$this->db->begin( __METHOD__ );
$what = array_flip( $what );
@@ -395,17 +403,9 @@ abstract class DatabaseUpdater {
$this->checkStats();
}
- if ( isset( $what['purge'] ) ) {
- $this->purgeCache();
-
- if ( $wgLocalisationCacheConf['manualRecache'] ) {
- $this->rebuildLocalisationCache();
- }
- }
-
$this->setAppliedUpdates( $wgVersion, $this->updates );
- if( $this->fileHandle ) {
+ if ( $this->fileHandle ) {
$this->skipSchema = false;
$this->writeSchemaUpdateFile();
$this->setAppliedUpdates( "$wgVersion-schema", $this->updatesSkipped );
@@ -427,14 +427,14 @@ abstract class DatabaseUpdater {
foreach ( $updates as $params ) {
$origParams = $params;
$func = array_shift( $params );
- if( !is_array( $func ) && method_exists( $this, $func ) ) {
+ if ( !is_array( $func ) && method_exists( $this, $func ) ) {
$func = array( $this, $func );
} elseif ( $passSelf ) {
array_unshift( $params, $this );
}
$ret = call_user_func_array( $func, $params );
flush();
- if( $ret !== false ) {
+ if ( $ret !== false ) {
$updatesDone[] = $origParams;
} else {
$updatesSkipped[] = array( $func, $params, $origParams );
@@ -450,7 +450,7 @@ abstract class DatabaseUpdater {
*/
protected function setAppliedUpdates( $version, $updates = array() ) {
$this->db->clearFlag( DBO_DDLMODE );
- if( !$this->canUseNewUpdatelog() ) {
+ if ( !$this->canUseNewUpdatelog() ) {
return;
}
$key = "updatelist-$version-" . time();
@@ -475,6 +475,7 @@ abstract class DatabaseUpdater {
array( 'ul_key' => $key ),
__METHOD__
);
+
return (bool)$row;
}
@@ -488,7 +489,7 @@ abstract class DatabaseUpdater {
public function insertUpdateRow( $key, $val = null ) {
$this->db->clearFlag( DBO_DDLMODE );
$values = array( 'ul_key' => $key );
- if( $val && $this->canUseNewUpdatelog() ) {
+ if ( $val && $this->canUseNewUpdatelog() ) {
$values['ul_value'] = $val;
}
$this->db->insert( 'updatelog', $values, __METHOD__, 'IGNORE' );
@@ -551,21 +552,21 @@ abstract class DatabaseUpdater {
foreach ( $wgExtNewFields as $fieldRecord ) {
$updates[] = array(
'addField', $fieldRecord[0], $fieldRecord[1],
- $fieldRecord[2], true
+ $fieldRecord[2], true
);
}
foreach ( $wgExtNewIndexes as $fieldRecord ) {
$updates[] = array(
'addIndex', $fieldRecord[0], $fieldRecord[1],
- $fieldRecord[2], true
+ $fieldRecord[2], true
);
}
foreach ( $wgExtModifiedFields as $fieldRecord ) {
$updates[] = array(
'modifyField', $fieldRecord[0], $fieldRecord[1],
- $fieldRecord[2], true
+ $fieldRecord[2], true
);
}
@@ -605,9 +606,10 @@ abstract class DatabaseUpdater {
*/
public function appendLine( $line ) {
$line = rtrim( $line ) . ";\n";
- if( fwrite( $this->fileHandle, $line ) === false ) {
+ if ( fwrite( $this->fileHandle, $line ) === false ) {
throw new MWException( "trouble writing file" );
}
+
return false;
}
@@ -625,6 +627,7 @@ abstract class DatabaseUpdater {
}
if ( $this->skipSchema ) {
$this->output( "...skipping schema change ($msg).\n" );
+
return false;
}
@@ -633,12 +636,13 @@ abstract class DatabaseUpdater {
if ( !$isFullPath ) {
$path = $this->db->patchPath( $path );
}
- if( $this->fileHandle !== null ) {
+ if ( $this->fileHandle !== null ) {
$this->copyFile( $path );
} else {
$this->db->sourceFile( $path );
}
$this->output( "done.\n" );
+
return true;
}
@@ -660,6 +664,7 @@ abstract class DatabaseUpdater {
} else {
return $this->applyPatch( $patch, $fullpath, "Creating $name table" );
}
+
return true;
}
@@ -684,6 +689,7 @@ abstract class DatabaseUpdater {
} else {
return $this->applyPatch( $patch, $fullpath, "Adding $field field to table $table" );
}
+
return true;
}
@@ -703,11 +709,12 @@ abstract class DatabaseUpdater {
if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
$this->output( "...skipping: '$table' table doesn't exist yet.\n" );
- } else if ( $this->db->indexExists( $table, $index, __METHOD__ ) ) {
+ } elseif ( $this->db->indexExists( $table, $index, __METHOD__ ) ) {
$this->output( "...index $index already set on $table table.\n" );
} else {
return $this->applyPatch( $patch, $fullpath, "Adding index $index to table $table" );
}
+
return true;
}
@@ -730,6 +737,7 @@ abstract class DatabaseUpdater {
} else {
$this->output( "...$table table does not contain $field field.\n" );
}
+
return true;
}
@@ -752,6 +760,7 @@ abstract class DatabaseUpdater {
} else {
$this->output( "...$index key doesn't exist.\n" );
}
+
return true;
}
@@ -761,12 +770,15 @@ abstract class DatabaseUpdater {
* @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 $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 ) {
+ protected function renameIndex( $table, $oldIndex, $newIndex,
+ $skipBothIndexExistWarning, $patch, $fullpath = false
+ ) {
if ( !$this->doTable( $table ) ) {
return true;
}
@@ -774,27 +786,37 @@ abstract class DatabaseUpdater {
// 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" .
+ 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" );
+ return $this->applyPatch(
+ $patch,
+ $fullpath,
+ "Renaming index $oldIndex into $newIndex to table $table"
+ );
}
/**
@@ -820,13 +842,13 @@ abstract class DatabaseUpdater {
$this->output( "$msg ..." );
$this->db->dropTable( $table, __METHOD__ );
$this->output( "done.\n" );
- }
- else {
+ } else {
return $this->applyPatch( $patch, $fullpath, $msg );
}
} else {
$this->output( "...$table doesn't exist.\n" );
}
+
return true;
}
@@ -848,13 +870,16 @@ abstract class DatabaseUpdater {
if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
$this->output( "...$table table does not exist, skipping modify field patch.\n" );
} elseif ( !$this->db->fieldExists( $table, $field, __METHOD__ ) ) {
- $this->output( "...$field field does not exist in $table table, skipping modify field patch.\n" );
- } elseif( $this->updateRowExists( $updateKey ) ) {
+ $this->output( "...$field field does not exist in $table table, " .
+ "skipping modify field patch.\n" );
+ } elseif ( $this->updateRowExists( $updateKey ) ) {
$this->output( "...$field in table $table already modified by patch $patch.\n" );
} else {
$this->insertUpdateRow( $updateKey );
+
return $this->applyPatch( $patch, $fullpath, "Modifying $field field of table $table" );
}
+
return true;
}
@@ -886,6 +911,7 @@ abstract class DatabaseUpdater {
$this->output( "missing ss_total_pages, rebuilding...\n" );
} else {
$this->output( "done.\n" );
+
return;
}
SiteStatsInit::doAllAndCommit( $this->db );
@@ -917,9 +943,10 @@ abstract class DatabaseUpdater {
protected function doLogUsertextPopulation() {
if ( !$this->updateRowExists( 'populate log_usertext' ) ) {
$this->output(
- "Populating log_user_text field, printing progress markers. For large\n" .
- "databases, you may want to hit Ctrl-C and do this manually with\n" .
- "maintenance/populateLogUsertext.php.\n" );
+ "Populating log_user_text field, printing progress markers. For large\n" .
+ "databases, you may want to hit Ctrl-C and do this manually with\n" .
+ "maintenance/populateLogUsertext.php.\n"
+ );
$task = $this->maintenance->runChild( 'PopulateLogUsertext' );
$task->execute();
@@ -949,6 +976,7 @@ abstract class DatabaseUpdater {
protected function doUpdateTranscacheField() {
if ( $this->updateRowExists( 'convert transcache field' ) ) {
$this->output( "...transcache tc_time already converted.\n" );
+
return true;
}
@@ -967,9 +995,11 @@ abstract class DatabaseUpdater {
'COUNT(*)',
'cl_collation != ' . $this->db->addQuotes( $wgCategoryCollation ),
__METHOD__
- ) == 0 ) {
- $this->output( "...collations up-to-date.\n" );
- return;
+ ) == 0
+ ) {
+ $this->output( "...collations up-to-date.\n" );
+
+ return;
}
$this->output( "Updating category collations..." );
@@ -983,7 +1013,7 @@ abstract class DatabaseUpdater {
* Migrates user options from the user table blob to user_properties
*/
protected function doMigrateUserOptions() {
- if( $this->db->tableExists( 'user_properties' ) ) {
+ if ( $this->db->tableExists( 'user_properties' ) ) {
$cl = $this->maintenance->runChild( 'ConvertUserOptions', 'convertUserOptions.php' );
$cl->execute();
$this->output( "done.\n" );
@@ -1003,30 +1033,4 @@ 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/InstallDocFormatter.php b/includes/installer/InstallDocFormatter.php
index a508e24c..6d3819cd 100644
--- a/includes/installer/InstallDocFormatter.php
+++ b/includes/installer/InstallDocFormatter.php
@@ -23,6 +23,7 @@
class InstallDocFormatter {
static function format( $text ) {
$obj = new self( $text );
+
return $obj->execute();
}
@@ -33,8 +34,8 @@ class InstallDocFormatter {
protected function execute() {
$text = $this->text;
// Use Unix line endings, escape some wikitext stuff
- $text = str_replace( array( '<', '{{', '[[', "\r" ),
- array( '&lt;', '&#123;&#123;', '&#91;&#91;', '' ), $text );
+ $text = str_replace( array( '<', '{{', '[[', '__', "\r" ),
+ array( '&lt;', '&#123;&#123;', '&#91;&#91;', '&#95;&#95;', '' ), $text );
// join word-wrapped lines into one
do {
$prev = $text;
@@ -46,7 +47,12 @@ class InstallDocFormatter {
// turn (bug nnnn) into links
$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 85b877a8..edf5ff25 100644
--- a/includes/installer/Installer.i18n.php
+++ b/includes/installer/Installer.i18n.php
@@ -63,8 +63,8 @@ Check your php.ini and make sure <code>session.save_path</code> is set to an app
'config-help-restart' => 'Do you want to clear all saved data that you have entered and restart the installation process?',
'config-restart' => 'Yes, restart it',
'config-welcome' => "=== Environmental checks ===
-Basic checks are performed to see if this environment is suitable for MediaWiki installation.
-You should provide the results of these checks if you need help during installation.",
+Basic checks will now be performed to see if this environment is suitable for MediaWiki installation.
+Remember to include this information if you seek support on how to complete the installation.",
'config-copyright' => "=== Copyright and Terms ===
$1
@@ -134,6 +134,10 @@ MediaWiki requires UTF-8 support to function correctly.",
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-json' => "'''Fatal:''' PHP was compiled without JSON support.
+You must install either the PHP JSON extension or the [http://pecl.php.net/package/jsonc PECL jsonc] extension before installing MediaWiki.
+* The PHP extension is included in Red Hat Enterprise Linux (CentOS) 5 and 6, though must be enabled in <code>/etc/php.ini</code> or <code>/etc/php.d/json.ini</code>.
+* Some Linux distributions released after May 2013 omit the PHP extension, instead packaging the PECL extension as <code>php5-json</code> or <code>php-pecl-jsonc</code>.",
'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',
@@ -142,6 +146,8 @@ 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.
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-git' => 'Found the Git version control software: <code>$1</code>.',
+ 'config-git-bad' => 'Git version control software not found.',
'config-imagemagick' => 'Found ImageMagick: <code>$1</code>.
Image thumbnailing will be enabled if you enable uploads.',
'config-gd' => 'Found GD graphics library built-in.
@@ -259,7 +265,7 @@ If you do not see the database system you are trying to use listed below, then f
'config-missing-db-host' => 'You must enter a value for "Database host"',
'config-missing-db-server-oracle' => 'You must enter a value for "Database TNS"',
'config-invalid-db-server-oracle' => 'Invalid database TNS "$1".
-Use only ASCII letters (a-z, A-Z), numbers (0-9), underscores (_) and dots (.).',
+Use either "TNS Name" or an "Easy Connect" string ([http://docs.oracle.com/cd/E11882_01/network.112/e10836/naming.htm Oracle Naming Methods])',
'config-invalid-db-name' => 'Invalid database name "$1".
Use only ASCII letters (a-z, A-Z), numbers (0-9), underscores (_) and hyphens (-).',
'config-invalid-db-prefix' => 'Invalid database prefix "$1".
@@ -315,7 +321,7 @@ 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 <code>LocalSettings.php</code> →',
+ 'config-regenerate' => 'Regenerate LocalSettings.php →',
'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',
@@ -334,6 +340,12 @@ The account you specify here must already exist.',
If your MySQL installation supports InnoDB, it is highly recommended that you choose that instead.
If your MySQL installation does not support InnoDB, maybe it's time for an upgrade.",
+ 'config-mysql-only-myisam-dep' => "'''Warning:''' MyISAM is the only available 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
+
+Your MySQL installation does not support InnoDB, maybe it's time for an upgrade.",
'config-mysql-engine-help' => "'''InnoDB''' is almost always the best option, since it has good concurrency support.
'''MyISAM''' may be faster in single-user or read-only installations.
@@ -550,6 +562,9 @@ When that has been done, you can '''[$2 enter your wiki]'''.",
'config-download-localsettings' => 'Download <code>LocalSettings.php</code>',
'config-help' => 'help',
'config-nofile' => 'File "$1" could not be found. Has it been deleted?',
+ 'config-extension-link' => 'Did you know that your wiki supports [//www.mediawiki.org/wiki/Manual:Extensions extensions]?
+
+You can browse [//www.mediawiki.org/wiki/Category:Extensions_by_category extensions by category] or the [//www.mediawiki.org/wiki/Extension_Matrix Extension Matrix] to see the full list of extensions.',
'mainpagetext' => "'''MediaWiki has been successfully installed.'''",
'mainpagedocfooter' => "Consult the [//meta.wikimedia.org/wiki/Help:Contents User's Guide] for information on using the wiki software.
@@ -576,9 +591,10 @@ When that has been done, you can '''[$2 enter your wiki]'''.",
* @author Shirayuki
* @author Siebrand
* @author Umherirrender
+ * @author Waldir
*/
$messages['qqq'] = array(
- 'config-desc' => '{{desc}}',
+ 'config-desc' => 'Short description of the installer.',
'config-title' => 'Parameters:
* $1 is the version of MediaWiki that is being installed.',
'config-information' => '{{Identical|Information}}',
@@ -604,16 +620,32 @@ Used as error message.',
'config-page-name' => '{{Identical|Name}}',
'config-page-options' => '{{Identical|Options}}',
'config-page-install' => '{{Identical|Install}}',
+ 'config-page-complete' => '{{Identical|Complete}}',
+ 'config-page-releasenotes' => '{{Identical|Release notes}}',
'config-page-copying' => 'This is a link to the full GPL text',
'config-restart' => 'Button text to confirm the installation procedure has to be restarted.',
+ 'config-copyright' => 'This message follows {{msg-mw|config-env-good}}.
+
+Parameters:
+* $1 - copyright and author list',
'config-sidebar' => 'Maximum width for words is 24 characters. Only visible part of the translation counts to this limit.',
'config-env-php' => 'Parameters:
-* $1 is the version of PHP that has been installed.',
+* $1 - the version of PHP that has been installed
+See also:
+* {{msg-mw|config-env-php-toolow}}',
+ 'config-env-php-toolow' => 'Parameters:
+* $1 - the version of PHP that has been installed
+* $2 - minimum PHP version number
+See also:
+* {{msg-mw|config-env-php}}',
'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' => '{{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-outdated-sqlite' => 'Used as warning. Parameters:
+* $1 - the version of SQLite that has been installed
+* $2 - minimum version',
'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}}',
@@ -628,22 +660,46 @@ Parameters:
'config-memory-bad' => 'Parameters:
* $1 is the configured <code>memory_limit</code>.',
'config-ctype' => 'Message if support for [http://www.php.net/manual/en/ctype.installation.php Ctype] is missing from PHP',
+ 'config-json' => 'Message if support for [[wikipedia:JSON|JSON]] is missing from PHP.
+* "[[wikipedia:Red Hat Enterprise Linux|Red Hat Enterprise Linux]]" (RHEL) and "[[wikipedia:CentOS|CentOS]]" refer to two almost-identical Linux distributions. "5 and 6" refers to version 5 or 6 of either distribution. Because RHEL 7 likely will not include the PHP extension, do not translate as "5 or newer".
+* "The [http://www.php.net/json PHP extension]" is the JSON extension included with PHP 5.2 and newer.
+* "The [http://pecl.php.net/package/jsonc PECL extension]" is based on the PHP extension, though excludes code some distributions have found unacceptable (see [[bugzilla:47431]]).',
'config-xcache' => 'Message indicates if this program is available',
'config-apc' => 'Message indicates if this program is available',
'config-wincache' => 'Message indicates if this program is available',
+ 'config-git' => 'Message if Git version control software is available.
+Parameter:
+* $1 is the <code>Git</code> executable file name.',
+ 'config-git-bad' => 'Message if Git version control software is not found.',
'config-imagemagick' => '$1 is ImageMagick\'s <code>convert</code> executable file name.
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-using-server' => 'Used as a part of environment check result. Parameters:
+* $1 - default server name',
+ 'config-using-uri' => 'Used as a part of environment check result. Parameters:
+* $1 - server name
+* $2 - script path',
+ 'config-uploads-not-safe' => 'Used as a part of environment check result. Parameters:
+* $1 - name of directory for images: <code>$IP/images/</code>',
'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-using531' => 'Used as error message. Parameters:
+* $1 - the version of PHP that has been installed',
'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-host-oracle-help' => 'See also:
+* {{msg-mw|Config-invalid-db-server-oracle}}',
'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-password-empty' => 'Used as error message. Parameters:
+* $1 - database username',
'config-db-account-lock' => "It might be easier to translate ''normal operation'' as \"also after the installation process\"",
+ 'config-mysql-old' => 'Used as error message. Parameters:
+* $1 - minimum version
+* $2 - the version of MySQL that has been installed',
'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>.}}
@@ -662,20 +718,105 @@ Used in help box.',
* $1 - a link to the SQLite home page having the anchor text "SQLite".',
'config-support-oracle' => 'Parameters:
* $1 - a link to the Oracle home page, the anchor text of which is "Oracle".',
+ 'config-invalid-db-server-oracle' => 'Used as error message. Parameters:
+* $1 - database server name
+See also:
+* {{msg-mw|Config-db-host-oracle-help}}',
+ 'config-invalid-db-name' => 'Used as error message. Parameters:
+* $1 - database name
+See also:
+* {{msg-mw|Config-invalid-db-prefix}}',
+ 'config-invalid-db-prefix' => 'Used as error message. Parameters:
+* $1 - database prefix
+See also:
+* {{msg-mw|Config-invalid-db-name}}',
'config-connection-error' => '$1 is the external error from the database, such as "DB connection error: Access denied for user \'dba\'@\'localhost\' (using password: YES) (localhost)."
If you\'re translating this message to a right-to-left language, consider writing <nowiki><div dir="ltr">$1.</div></nowiki>. (When the bidi features for HTML5 will be implemented in the browsers, it will probably be a good idea to write it as <nowiki><div dir="auto">$1.</div></nowiki>.)',
'config-invalid-schema' => '*$1 - schema name',
+ 'config-db-sys-user-exists-oracle' => 'Used as error message. Parameters:
+* $1 - database username',
+ 'config-postgres-old' => 'Used as error message. Used as warning. Parameters:
+* $1 - minimum version
+* $2 - the version of PostgreSQL that has been installed',
+ 'config-sqlite-parent-unwritable-group' => 'Used as SQLite error message. Parameters:
+* $1 - data directory
+* $2 - "dirname" part of $1
+* $3 - "basename" part of $1
+* $4 - web server\'s primary group name
+See also:
+* {{msg-mw|Config-sqlite-parent-unwritable-nogroup}}',
+ 'config-sqlite-parent-unwritable-nogroup' => 'Used as SQLite error message. Parameters:
+* $1 - data directory
+* $2 - "dirname" part of $1
+* $3 - "basename" part of $1
+See also:
+* {{msg-mw|Config-sqlite-parent-unwritable-group}}',
+ 'config-sqlite-mkdir-error' => 'Used as SQLite error message. Parameters:
+* $1 - data directory name',
'config-sqlite-dir-unwritable' => 'webserver refers to a software like Apache or Lighttpd.',
+ 'config-sqlite-connection-error' => 'Used as SQLite error message. Parameters:
+* $1 - error message which SQLite server returned',
+ 'config-sqlite-readonly' => 'Used as SQLite error message. Parameters:
+* $1 - filename',
+ 'config-sqlite-cant-create-db' => 'Used as SQLite error message. Parameters:
+* $1 - filename',
'config-can-upgrade' => 'Parameters:
* $1 - Version or Revision indicator.',
+ 'config-upgrade-done' => 'Used as success message. Parameters:
+* $1 - full URL of index.php
+See also:
+* {{msg-mw|config-upgrade-done-no-regenerate}}',
+ 'config-upgrade-done-no-regenerate' => 'Used as success message. Parameters:
+* $1 - full URL of index.php
+See also:
+* {{msg-mw|config-upgrade-done}}',
+ 'config-regenerate' => 'This message appears in a button after LocalSettings.php is generated and downloaded at the end of the MediaWiki installation process.',
'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}}',
+ 'config-mysql-only-myisam-dep' => 'Used as warning message when mysql does not support the minimum suggested feature set.',
+ 'config-mysql-binary' => '{{Identical|Binary}}',
+ 'config-ns-generic' => 'Used as label for "namespace type" radio button.
+
+See also:
+* {{msg-mw|Config-ns-site-name}}
+* {{msg-mw|Config-ns-other}}
+{{Identical|Project}}',
+ 'config-ns-site-name' => 'Used as label for "namespace type" radio button. Parameters:
+* $1 - wiki name
+See also:
+* {{msg-mw|Config-ns-generic}}
+* {{msg-mw|Config-ns-other}}',
+ 'config-ns-other' => "Used as label for \"namespace type\" radio button.
+
+This message is followed by the input box which enables to '''specify''' a namespace name.
+
+See also:
+* {{msg-mw|Config-ns-site-name}}
+* {{msg-mw|Config-ns-generic}}",
+ 'config-ns-invalid' => 'Used as error message. Parameters:
+* $1 - namespace name
+See also:
+* {{msg-mw|Config-ns-conflict}}',
+ 'config-ns-conflict' => 'Used as error message. Parameters:
+* $1 - namespace name
+See also:
+* {{msg-mw|Config-ns-invalid}}',
'config-admin-name' => '{{Identical|Your name}}',
'config-admin-password' => '{{Identical|Password}}',
+ 'config-admin-name-invalid' => 'Used as error message. Parameters:
+* $1 - username of administrator',
'config-admin-email' => '{{Identical|E-mail address}}',
+ 'config-admin-error-user' => 'Used as error message. Parameters:
+* $1 - username of administrator
+See also:
+* {{msg-mw|Config-admin-error-password}}',
+ 'config-admin-error-password' => 'Used as error message. Parameters:
+* $1 - username of administrator
+* $2 - error message
+See also:
+* {{msg-mw|Config-admin-error-user}}',
'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:
@@ -687,10 +828,31 @@ If you\'re translating this message to a right-to-left language, consider writin
'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-instantcommons' => 'Used as label for the checkbox.
+
+The help message for this checkbox is:
+* {{msg-mw|Config-instantcommons-help}}',
+ 'config-instantcommons-help' => 'Used as help message for the checkbox which is labeled {{msg-mw|config-instantcommons}}.',
'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-memcache-badip' => 'Used as error message. Parameters:
+* $1 - IP address for Memcached
+See also:
+* {{msg-mw|Config-memcache-noport}}
+* {{msg-mw|Config-memcache-badport}}',
+ 'config-memcache-noport' => 'Used as error message. Parameters:
+* $1 - Memcached server name
+See also:
+* {{msg-mw|Config-memcache-badip}}
+* {{msg-mw|Config-memcache-badport}}',
+ 'config-memcache-badport' => 'Used as error message. Parameters:
+* $1 - 1 (hard-coded)
+* $2 - 65535 (hard-coded)
+See also:
+* {{msg-mw|Config-memcache-badip}}
+* {{msg-mw|Config-memcache-noport}}',
'config-extensions' => '{{Identical|Extension}}',
'config-extensions-help' => '{{doc-important|Do not translate <code>./extensions</code>.}}
Used in help box.',
@@ -717,6 +879,8 @@ Used in help box.',
'config-install-pg-schema-failed' => 'Parameters:
* $1 = database user name (usernames in the database are unrelated to wiki user names)
* $2 =',
+ 'config-pg-no-plpgsql' => 'Used as error message. Parameters:
+* $1 - database name',
'config-install-user' => 'Message indicates that the user is being created
See also:
@@ -729,9 +893,22 @@ See also:
*{{msg-mw|Config-install-keys}}
*{{msg-mw|Config-install-sysop}}
*{{msg-mw|Config-install-mainpage}}',
+ 'config-install-user-alreadyexists' => 'Used as warning. Parameters:
+* $1 - database username',
+ 'config-install-user-create-failed' => 'Used as MySQL warning and as PostgreSQL error. Parameters:
+* $1 - database username
+* $2 - detailed warning/error message',
'config-install-user-grant-failed' => 'Parameters:
* $1 is the database username for which granting rights failed
* $2 is the error message',
+ 'config-install-user-missing' => 'Used as PostgreSQL error message. Parameters:
+* $1 - database username
+See also:
+* {{msg-mw|Config-install-user-missing-create}}',
+ 'config-install-user-missing-create' => 'Used as PostgreSQL error message. Parameters:
+* $1 - database username
+See also:
+* {{msg-mw|Config-install-user-missing}}',
'config-install-tables' => 'Message indicates that the tables are being created
See also:
@@ -744,6 +921,8 @@ See also:
*{{msg-mw|Config-install-keys}}
*{{msg-mw|Config-install-sysop}}
*{{msg-mw|Config-install-mainpage}}',
+ 'config-install-tables-failed' => 'Used as PostgreSQL error message. Parameters:
+* $1 - detailed error message',
'config-install-interwiki' => 'Message indicates that the interwikitables are being populated
See also:
@@ -801,6 +980,8 @@ See also:
*{{msg-mw|Config-install-keys}}
*{{msg-mw|Config-install-sysop}}
*{{msg-mw|Config-install-mainpage}}',
+ 'config-install-mainpage-failed' => 'Used as error message. Parameters:
+* $1 - detailed error message',
'config-install-done' => 'Parameters:
* $1 is the URL to LocalSettings download
* $2 is a link to the wiki.
@@ -808,6 +989,9 @@ 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}}',
+ 'config-nofile' => 'Used as failure message. Parameters:
+* $1 - filename',
+ 'config-extension-link' => 'Shown on last page of installation to inform about possible extensions.',
'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.',
@@ -890,12 +1074,10 @@ U gebruik tans $2.',
'config-sqlite-dir' => 'Gids vir SQLite se data:',
'config-oracle-def-ts' => 'Standaard tabelruimte:',
'config-oracle-temp-ts' => 'Tydelike tabelruimte:',
- 'config-type-ibm_db2' => 'IBM DB2',
'config-header-mysql' => 'MySQL-instellings',
'config-header-postgres' => 'PostgreSQL-instellings',
'config-header-sqlite' => 'SQLite-instellings',
'config-header-oracle' => 'Oracle-instellings',
- 'config-header-ibm_db2' => 'Instellings vir IBM DB2',
'config-invalid-db-type' => 'Ongeldige databasistipe',
'config-missing-db-name' => 'U moet \'n waarde vir "Databasnaam" verskaf',
'config-sqlite-readonly' => 'Die lêer <code>$1</code> kan nie geskryf word nie.',
@@ -903,7 +1085,7 @@ U gebruik tans $2.',
'config-upgrade-done-no-regenerate' => 'Opgradering is voltooi.
U kan nou [$1 u wiki gebruik].',
- 'config-regenerate' => 'Herskep <code>LocalSettings.php</code> →',
+ 'config-regenerate' => 'Herskep LocalSettings.php →',
'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:',
@@ -1009,7 +1191,7 @@ $messages['aln'] = array(
* [//www.mediawiki.org/wiki/Help:Configuration_settings Konfigurimi i MediaWikit]
* [//www.mediawiki.org/wiki/Help:FAQ Pyetjet e shpeshta rreth MediaWikit]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Njoftime rreth MediaWikit]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Njoftime rreth MediaWikit]', # Fuzzy
);
/** Amharic (አማርኛ)
@@ -1022,7 +1204,7 @@ $messages['am'] = array(
* [//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
);
/** Aragonese (aragonés)
@@ -1036,20 +1218,20 @@ $messages['an'] = array(
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lista de caracteristicas confegurables]
* [//www.mediawiki.org/wiki/Manual:FAQ Preguntas cutianas sobre MediaWiki (FAQ)]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista de correu sobre ta anuncios de MediaWiki]",
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista de correu sobre ta anuncios de MediaWiki]", # Fuzzy
);
/** Old English (Ænglisc)
* @author Gott wisst
*/
$messages['ang'] = array(
- 'mainpagetext' => "'''MediaÇ·iki hafaþ ÈeÆ¿orden spÄ“diÈe inseted.'''",
+ 'mainpagetext' => "'''MediaWiki hafaþ geworden spēdige inseted.'''",
'mainpagedocfooter' => 'Þeahta þone [//meta.wikimedia.org/wiki/Help:Contents BrÅ«cenda LÇ£dend] on helpe mid þǣre nytte of Æ¿ikisÅftÆ¿are.
== BeÈinnunÈ ==
* [//www.mediawiki.org/wiki/Manual:Configuration_settings OnfæstnunÈa Èesetednessa Èetæl]
* [//www.mediawiki.org/wiki/Manual:FAQ Èœetæl oft ascodra ascunÈa ymb MediaÇ·iki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Ç¢rendunÈÈetæl nÄ«Æ¿ra MediaÇ·iki forþsendnessa]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Ç¢rendunÈÈetæl nÄ«Æ¿ra MediaÇ·iki forþsendnessa]', # Fuzzy
);
/** Arabic (العربية)
@@ -1115,7 +1297,7 @@ $messages['ary'] = array(
== L-bdaya mÄa MediaWiki ==
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lista dyal l-paramétraṫ dyal l-konfigurasyon]
* [//www.mediawiki.org/wiki/Manual:FAQ/fr FAQ fe MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista dyal l-modakaraṫ Äla versyonaṫ jdad dyal MediaWiki]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista dyal l-modakaraṫ Äla versyonaṫ jdad dyal MediaWiki]', # Fuzzy
);
/** Egyptian Spoken Arabic (مصرى)
@@ -1128,10 +1310,10 @@ $messages['arz'] = 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
);
-/** Assamese (অসমীয়া)
+/** Assamese (অসমীয়া)
* @author Chaipau
* @author Gitartha.bordoloi
*/
@@ -1142,7 +1324,7 @@ $messages['as'] = array(
== আৰমà§à¦­à¦£à¦¿ কৰিবলৈ ==
* [//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
);
/** Asturian (asturianu)
@@ -1228,7 +1410,6 @@ $messages['az'] = array(
'config-page-install' => 'Nizamlama',
'config-page-complete' => 'Komplektləşdir!',
'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
- 'config-type-ibm_db2' => 'IBM DB2',
'config-mysql-myisam' => 'MyISAM',
'config-mysql-utf8' => 'UTF-8',
'config-ns-generic' => 'LayihÉ™',
@@ -1242,7 +1423,7 @@ $messages['az'] = array(
== Faydalı keçidlər ==
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Tənzimləmələrin siyahısı]
* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki haqqında tez-tez soruşulan suallar]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki e-poçt siyahısı]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki e-poçt siyahısı]', # Fuzzy
);
/** Bashkir (башҡортÑа)
@@ -1255,7 +1436,7 @@ $messages['ba'] = array(
== Файҙалы Ñығанаҡтар ==
* [//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
);
/** Bavarian (Boarisch)
@@ -1269,7 +1450,7 @@ $messages['bar'] = array(
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Listen voh de Konfigurazionsvariaablen]
* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki-FAQ]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mailinglisten voh de neichen MediaWiki-Versionen]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mailinglisten voh de neichen MediaWiki-Versionen]', # Fuzzy
);
/** Southern Balochi (بلوچی مکرانی)
@@ -1281,7 +1462,7 @@ $messages['bcc'] = array(
== شروع بیت ==
* [//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
);
/** Bikol Central (Bikol Central)
@@ -1294,7 +1475,7 @@ $messages['bcl'] = array(
* [//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
);
/** Belarusian (беларуÑкаÑ)
@@ -1307,7 +1488,7 @@ $messages['be'] = array(
* [//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
);
/** Belarusian (TaraÅ¡kievica orthography) (беларуÑÐºÐ°Ñ (тарашкевіца)‎)
@@ -1448,6 +1629,8 @@ MediaWiki патрабуе падтрымкі UTF-8 Ð´Ð»Ñ Ñлушнай пра
'config-mod-security' => "'''ПапÑÑ€Ñджаньне''': на Вашым ÑžÑб-ÑÑрверы ўключаны [http://modsecurity.org/ mod_security]. У выпадку нÑÑлушнай наладцы, ён можа Ñтаць прычынай праблемаў Ð´Ð»Ñ MediaWiki ці іншага праграмнага забеÑьпÑчÑньнÑ, Ñкое дазвалÑе ўдзельнікам даÑылаць на ÑÑрвÑÑ€ любы зьмеÑÑ‚.
ГлÑдзіце [http://modsecurity.org/documentation/ дакумÑнтацыю mod_security] ці зьвÑрніцеÑÑ Ñž падтрымку Вашага хоÑту, калі Ñž Ð’Ð°Ñ ÑƒÐ·ÑŒÐ½Ñ–ÐºÐ°ÑŽÑ†ÑŒ Ð²Ñ‹Ð¿Ð°Ð´ÐºÐ¾Ð²Ñ‹Ñ Ð¿Ñ€Ð°Ð±Ð»ÐµÐ¼Ñ‹.",
'config-diff3-bad' => 'GNU diff3 Ð½Ñ Ð·Ð½Ð¾Ð¹Ð´Ð·ÐµÐ½Ñ‹.',
+ 'config-git' => 'Ð—Ð½Ð¾Ð¹Ð´Ð·ÐµÐ½Ð°Ñ ÑÑ‹ÑÑ‚Ñма канÑтролю вÑÑ€ÑÑ–ÑÑž Git: <code>$1</code>',
+ 'config-git-bad' => 'СыÑÑ‚Ñма кантролю вÑÑ€ÑÑ–ÑÑž Git Ð½Ñ Ð·Ð½Ð¾Ð¹Ð´Ð·ÐµÐ½Ð°Ñ.',
'config-imagemagick' => 'Знойдзены ImageMagick: <code>$1</code>.
ПаÑÑŒÐ»Ñ ÑžÐºÐ»ÑŽÑ‡ÑÐ½ÑŒÐ½Ñ Ð·Ð°Ð³Ñ€ÑƒÐ·Ð°Ðº будзе ўключанае маштабаваньне выÑваў.',
'config-gd' => 'GD падтрымліваецца ўбудавана.
@@ -1545,7 +1728,6 @@ ResourceLoader, Ñкладнік MediaWiki, будзе абходзіць гÑÑ‚
'config-type-postgres' => 'PostgreSQL',
'config-type-sqlite' => 'SQLite',
'config-type-oracle' => 'Oracle',
- 'config-type-ibm_db2' => 'IBM DB2',
'config-support-info' => 'MediaWiki падтрымлівае наÑÑ‚ÑƒÐ¿Ð½Ñ‹Ñ ÑÑ‹ÑÑ‚Ñмы базаў зьвеÑтак:
$1
@@ -1555,18 +1737,16 @@ $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 — база зьвеÑтак маштабу прадпрыемÑтва. ([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',
'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), Ñымбалі падкрÑÑьліваньнÑ(_) Ñ– кропкі (.).',
+Ðазва можа ўтрымліваць толькі ASCII-літары (a-z, A-Z), лічбы (0-9), Ñымбалі падкрÑÑьліваньнÑ(_) Ñ– кропкі (.).', # Fuzzy
'config-invalid-db-name' => 'ÐÑÑÐ»ÑƒÑˆÐ½Ð°Ñ Ð½Ð°Ð·Ð²Ð° базы зьвеÑтак «$1».
Ðазва можа ўтрымліваць толькі ASCII-літары (a-z, A-Z), лічбы (0-9), Ñымбалі падкрÑÑьліваньнÑ(_) Ñ– працÑжнікі (-).',
'config-invalid-db-prefix' => 'ÐÑÑлушны прÑÑ„Ñ–ÐºÑ Ð±Ð°Ð·Ñ‹ зьвеÑтак «$1».
@@ -1622,7 +1802,7 @@ chmod a+w $3</pre>',
'config-upgrade-done-no-regenerate' => 'Ðбнаўленьне Ñкончанае.
ЦÑпер Ð’Ñ‹ можаце [$1 пачаць працу з вікі].',
- 'config-regenerate' => 'РÑгенÑраваць <code>LocalSettings.php</code> →',
+ 'config-regenerate' => 'РÑгенÑраваць LocalSettings.php →',
'config-show-table-status' => "Запыт '<code>SHOW TABLE STATUS</code>' не атрымаўÑÑ!",
'config-unknown-collation' => "'''ПапÑÑ€Ñджаньне:''' база зьвеÑтак выкарыÑтоўвае нераÑпазнанае ÑупаÑтаўленьне.",
'config-db-web-account' => 'Рахунак базы зьвеÑтак Ð´Ð»Ñ Ð²Ñб-доÑтупу',
@@ -1652,7 +1832,6 @@ chmod a+w $3</pre>',
ГÑта болей ÑÑ„Ñктыўна за Ñ€Ñжым MySQL UTF-8, Ñ– дазвалÑе Вам выкарыÑтоўваць увеÑÑŒ дыÑпазон ÑымбалÑÑž Unicode.
У '''Ñ€Ñжыме UTF-8''', MySQL ведае, ÑÐºÐ°Ñ Ñ‚Ð°Ð±Ð»Ñ–Ñ†Ñ‹ ÑымбалÑÑž выкарыÑтоўваецца Ñž Вашых зьвеÑтках, Ñ– можа адпаведна прадÑтаўлÑць Ñ– канвÑртаваць Ñ–Ñ…, але гÑта не дазволіць Вам захоўваць Ñымбалі па-за межамі [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Базавага шматмоўнага дыÑпазону].",
- 'config-ibm_db2-low-db-pagesize' => "Ð’Ð°ÑˆÐ°Ñ Ð±Ð°Ð·Ð° зьвеÑтак DB2 мае таблічную праÑторну зь недаÑтатковым памерам Ñтаронкі. Памер Ñтаронкі муÑіць быць Ð½Ñ Ð¼ÐµÐ½Ñˆ за '''32к'''.",
'config-site-name' => 'Ðазва вікі:',
'config-site-name-help' => 'Ðазва будзе паказвацца Ñž загалоўку браўзÑра Ñ– Ñž некаторых іншых меÑцах.',
'config-site-name-blank' => 'УвÑдзіце назву Ñайта.',
@@ -1762,7 +1941,7 @@ chmod a+w $3</pre>',
'config-logo-help' => 'Ðфармленьне MediaWiki па змоўчваньні уключае праÑтору Ð´Ð»Ñ Ð»Ñгатыпу памерам 135×160 пікÑÑлÑÑž у верхнім левым куце.
Загрузіце выÑву адпаведнага памеру Ñ– увÑдзіце тут URL-адраÑ.
-Калі Ð’Ñ‹ не жадаеце мець ніÑкага лÑгатыпу, пакіньце поле пуÑтым.',
+Калі Ð’Ñ‹ не жадаеце мець ніÑкага лÑгатыпу, пакіньце поле пуÑтым.', # Fuzzy
'config-instantcommons' => 'Дазволіць Instant Commons',
'config-instantcommons-help' => '[//www.mediawiki.org/wiki/InstantCommons Instant Commons] — магчымаÑьць, ÑÐºÐ°Ñ Ð´Ð°Ð·Ð²Ð°Ð»Ñе вікі выкарыÑтоўваць выÑвы, гукі Ñ– Ñ–Ð½ÑˆÑ‹Ñ Ð¼ÑдыÑ, ÑÐºÑ–Ñ Ð·Ð½Ð°Ñ…Ð¾Ð´Ð·Ñцца на Ñайце [//commons.wikimedia.org/ Wikimedia Commons].
Каб гÑта зрабіць, MediaWiki патрабуе доÑтупу да ІнтÑрнÑту.
@@ -2071,7 +2250,6 @@ $1
Това включва Ñурови данни за потребителите (адреÑи за е-поща, хеширани пароли), както и изтрити верÑии на Ñтраници и друга чувÑтвителна и Ñ Ð¾Ð³Ñ€Ð°Ð½Ð¸Ñ‡ÐµÐ½ доÑтъп Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾Ñ‚ и за уикито.
Базата от данни е препоръчително да Ñе разположи на друго мÑÑто, например в <code>/var/lib/mediawiki/yourwiki</code>.",
- 'config-type-ibm_db2' => 'IBM DB2',
'config-support-info' => 'МедиÑУики поддържа Ñледните ÑиÑтеми за бази от данни:
$1
@@ -2081,12 +2259,10 @@ $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 е комерÑиална фирмена база от данни. ([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',
'config-header-oracle' => 'ÐаÑтройки за Oracle',
- 'config-header-ibm_db2' => 'ÐаÑтройки за IBM DB2',
'config-invalid-db-type' => 'Ðевалиден тип база от данни',
'config-missing-db-name' => 'Ðеобходимо е да Ñе въведе ÑтойноÑÑ‚ за "Име на базата от данни"',
'config-missing-db-host' => 'Ðеобходимо е да Ñе въведе ÑтойноÑÑ‚ за "ХоÑÑ‚ на базата от данни"',
@@ -2148,7 +2324,7 @@ chmod a+w $3</pre>',
'config-upgrade-done-no-regenerate' => 'ОбновÑването приключи.
Вече е възможно [$1 да използвате уикито].',
- 'config-regenerate' => 'Създаване на <code>LocalSettings.php</code> →',
+ 'config-regenerate' => 'Създаване на LocalSettings.php →',
'config-show-table-status' => 'ЗаÑвката <code>SHOW TABLE STATUS</code> не Ñполучи!',
'config-unknown-collation' => "'''Предупреждение:''' Базата от данни използва неразпозната колациÑ.",
'config-db-web-account' => 'Сметка за уеб доÑтъп до базата от данни',
@@ -2286,7 +2462,7 @@ chmod a+w $3</pre>',
'config-logo-help' => 'Обликът по подразбиране на МедиÑУики вклчва мÑÑто Ñ Ñ€Ð°Ð·Ð¼ÐµÑ€Ð¸ 135Ñ…160 пикÑела за лого над Ñтраничното меню.
Ðко има наличен файл Ñ Ð¿Ð¾Ð´Ñ…Ð¾Ð´Ñщ размер, неговиÑÑ‚ Ð°Ð´Ñ€ÐµÑ Ð¼Ð¾Ð¶Ðµ да бъде поÑочен тук.
-Ðко не е необходимо лого, полето може да Ñе оÑтави празно.',
+Ðко не е необходимо лого, полето може да Ñе оÑтави празно.', # Fuzzy
'config-instantcommons' => 'Включване на Instant Commons',
'config-instantcommons-help' => '[//www.mediawiki.org/wiki/InstantCommons Instant Commons] е функционалноÑÑ‚, коÑто позволÑва на уикитата да използват картинки, звуци и друга медиа от Ñайта на Ð£Ð¸ÐºÐ¸Ð¼ÐµÐ´Ð¸Ñ [//commons.wikimedia.org/ ОбщомедиÑ].
За да е възможно това, МедиÑУики изиÑква доÑтъп до Интернет.
@@ -2399,7 +2575,7 @@ $messages['bjn'] = array(
== Gasan bamula ==
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Daptar konpigurasi setélan]
* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki nang rancak ditakunakan]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki rilis milis]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki rilis milis]', # Fuzzy
);
/** Bengali (বাংলা)
@@ -2445,12 +2621,10 @@ $messages['bn'] = array(
'config-sqlite-dir' => 'à¦à¦¸à¦•à¦¿à¦‰à¦²à¦¾à¦‡à¦Ÿ ডেটা ডিরেকà§à¦Ÿà¦°à¦¿:',
'config-oracle-def-ts' => 'পূরà§à¦¬à¦¨à¦¿à¦°à§à¦§à¦¾à¦°à¦¿à¦¤ টেবিলসà§à¦ªà§‡à¦¸',
'config-oracle-temp-ts' => 'সাময়কি টেবিলসà§à¦ªà§‡à¦¸:',
- 'config-type-ibm_db2' => 'আইবিà¦à¦® ডিবি২',
'config-header-mysql' => 'মাইà¦à¦¸à¦•à¦¿à¦‰à¦à¦² সেটিংস',
'config-header-postgres' => 'পোসà§à¦Ÿà¦—à§à¦°à§‡à¦à¦¸à¦•à¦¿à¦‰à¦à¦² সেটিংস',
'config-header-sqlite' => 'à¦à¦¸à¦•à¦¿à¦‰à¦²à¦¾à¦‡à¦Ÿ সেটিংস',
'config-header-oracle' => 'ওরাকল সেটিংস',
- 'config-header-ibm_db2' => 'আইবিà¦à¦® ডিবি২ সেটিংস',
'config-invalid-db-type' => 'ডেটাবেজের ধরন অগà§à¦°à¦¹à¦¯à§‹à¦—à§à¦¯',
'config-missing-db-name' => 'আপনাকে অবশà§à¦¯à¦‡ "ডেটাবেজ নাম"-à¦à¦° জনà§à¦¯ à¦à¦•à¦Ÿà¦¿ মান পà§à¦°à¦¬à§‡à¦¶ করাতে হবে',
'config-missing-db-host' => 'আপনাকে অবশà§à¦¯à¦‡ "ডেটাবেজ হোসà§à¦Ÿ"-à¦à¦° জনà§à¦¯ à¦à¦•à¦Ÿà¦¿ মান পà§à¦°à¦¬à§‡à¦¶ করাতে হবে',
@@ -2480,7 +2654,7 @@ $messages['bn'] = array(
'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' => 'বà§à¦¯à¦•à§à¦¤à¦¿à¦—ত উইকি',
@@ -2514,7 +2688,7 @@ $messages['bn'] = 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
);
/** Bishnupria Manipuri (বিষà§à¦£à§à¦ªà§à¦°à¦¿à¦¯à¦¼à¦¾ মণিপà§à¦°à§€)
@@ -2527,7 +2701,7 @@ $messages['bpy'] = 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
);
/** Breton (brezhoneg)
@@ -2773,7 +2947,7 @@ 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 <code>LocalSettings.php</code> →',
+ 'config-regenerate' => 'Adgenel LocalSettings.php →',
'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',
@@ -3036,16 +3210,26 @@ $messages['ca'] = array(
/** Chechen (нохчийн)
* @author Sasan700
+ * @author Умар
*/
$messages['ce'] = array(
+ 'config-your-language' => 'Хьан мотт:',
+ 'config-continue' => 'Кхин дӀа →',
+ 'config-page-language' => 'Мотт',
+ 'config-page-name' => 'ЦӀе',
'config-no-fts3' => "'''Тергам бе''': SQLite гулйина хуттург йоцуш [//sqlite.org/fts3.html FTS3] — лахар болхбеш хир дац оцу бухца.",
+ 'config-site-name' => 'Викин цӀе:',
+ 'config-site-name-blank' => 'Язъе Ñайтан цӀе.',
+ 'config-license' => 'Ðвторан бакъонаш а лицензи а:',
+ 'config-license-pd' => 'Юкъараллин хьал',
+ 'config-help' => 'гӀо',
'mainpagetext' => "'''Вики-белха гlÐ¸Ñ€Ñ Â«MediaWiki» кхочуш дика дlахlоттийна.'''",
'mainpagedocfooter' => 'Викийца болх бан хаамаш карор бу Ñ…lокху чохь [//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 ниÑвохааман куьйгаллица].
== Цхьаболу пайде гlирÑаш ==
* [//www.mediawiki.org/wiki/Manual:Configuration_settings ГlÐ¸Ñ€Ñ Ð½Ð¸Ñбан тарлушболу могlам];
* [//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
);
/** Cebuano (Cebuano)
@@ -3057,17 +3241,36 @@ $messages['ceb'] = array(
== Pagsugod ==
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Listahan sa mga setting sa kompigurasyon]
* [//www.mediawiki.org/wiki/Manual:FAQ FAQ sa MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mailing list sa mga release sa MediaWiki]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mailing list sa mga release sa MediaWiki]', # Fuzzy
);
/** Sorani Kurdish (کوردی)
* @author Asoxor
* @author Calak
+ * @author Muhammed taha
*/
$messages['ckb'] = array(
'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-copying' => 'لەبەردەگیرێتەوە',
+ 'config-page-upgradedoc' => 'نوێدەکرێتەوە',
+ 'config-page-existingwiki' => 'ویکی پێشوو',
+ 'config-restart' => 'بەڵێ، دەستی پێ بکەرەوە',
+ 'config-env-php' => 'PHP $1 دابەزێندرا.',
+ 'config-env-php-toolow' => 'PHP $1 دابەزێندرا.
+ھەرچۆنێک بێت میدیاویکی پێویستی بە PHP $2 یان بەرزتر ھەیە.',
'mainpagetext' => "'''میدیاویکی بە سەرکەوتوویی دامەزرا.'''",
'mainpagedocfooter' => 'لە [//meta.wikimedia.org/wiki/Help:Contents ڕێنوێنیی بەکارھێنەران] بۆ زانیاری سەبارەت بە بەکارھێنانی نەرمامێری ویکی کەڵک وەربگرە.
@@ -3089,7 +3292,7 @@ $messages['cps'] = array(
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lista sang mga setting sang konpigurayon]
* [//www.mediawiki.org/wiki/Manual:FAQ Mga perme napangkot sa MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista sang mga ginapadal-an sang sulat sang MediaWiki]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista sang mga ginapadal-an sang sulat sang MediaWiki]', # Fuzzy
);
/** Crimean Turkish (Cyrillic script) (къырымтатарджа (Кирилл)‎)
@@ -3101,7 +3304,7 @@ $messages['crh-cyrl'] = array(
== Базы файдалы Ñайтлар ==
* [//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
);
/** Crimean Turkish (Latin script) (qırımtatarca (Latin)‎)
@@ -3113,11 +3316,12 @@ $messages['crh-latn'] = array(
== Bazı faydalı saytlar ==
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Olucı sazlamalar cedveli];
* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki boyunca sıq berilgen suallernen cevaplar];
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki-niñ yañı versiyalarınıñ çıquvından haber yiberüv].",
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki-niñ yañı versiyalarınıñ çıquvından haber yiberüv].", # Fuzzy
);
/** Czech (Äesky)
* @author Danny B.
+ * @author Jezevec
* @author Mormegil
* @author ì•„ë¼
*/
@@ -3172,8 +3376,8 @@ Zkontrolujte svůj soubor php.ini a ujistěte se, že <code>session.save_path</c
'config-help-restart' => 'Chcete smazat vÅ¡echny údaje, které jste zadali, a spustit proces instalace znovu od zaÄátku?',
'config-restart' => 'Ano, restartovat',
'config-welcome' => '=== Kontrola prostředí ===
-Provedou se základní kontroly, aby se zjistilo, zda je toto prostředí použitelné k instalaci MediaWiki.
-Pokud budete potřebovat při instalaci pomoc, měli byste sdělit výsledky těchto testů.',
+Nyní se provedou základní kontroly, aby se zjistilo, zda je toto prostředí použitelné k instalaci MediaWiki.
+Pokud budete potÅ™ebovat k dokonÄení instalace pomoc, nezapomeňte sdÄ›lit výsledky tÄ›chto testů.',
'config-copyright' => "=== Licence a podmínky ===
$1
@@ -3242,6 +3446,10 @@ MediaWiki vyžaduje ke správné funkci podporu UTF-8.",
To je pravděpodobně příliš málo.
Instalace může selhat!",
'config-ctype' => "'''Kritická chyba''': PHP musí být přeloženo s podporou pro [http://www.php.net/manual/en/ctype.installation.php rozšíření Ctype].",
+ 'config-json' => "'''Kritická chyba:''' PHP bylo přeloženo bez podpory JSON.
+PÅ™ed instalací MediaWiki musíte buÄ nainstalovat rozšíření PHP JSON nebo rozšíření [http://pecl.php.net/package/jsonc PECL jsonc].
+* Rozšíření PHP je souÄástí Red Hat Enterprise Linux (CentOS) 5 a 6, avÅ¡ak musí se povolit v <code>/etc/php.ini</code> nebo <code>/etc/php.d/json.ini</code>.
+* V některých linuxových distribucích vydaných po květnu 2013 může toto rozšíření PHP chybět a místo toho mohou používat rozšíření PECL jako <code>php5-json</code> nebo <code>php-pecl-jsonc</code>.",
'config-xcache' => 'Je nainstalována [http://xcache.lighttpd.net/ XCache]',
'config-apc' => 'Je nainstalováno [http://www.php.net/apc APC]',
'config-wincache' => 'Je nainstalována [http://www.iis.net/download/WinCacheForPhp WinCache]',
@@ -3250,6 +3458,8 @@ Kešování objektů bude vypnuto.",
'config-mod-security' => "'''UpozornÄ›ní''': váš webový server má zapnuto [http://modsecurity.org/ mod_security]. PÅ™i chybné konfiguraci může způsobovat potíže MediaWiki Äi dalším programům, které umožňují ukládat libovolný obsah.
Pokud narazíte na náhodné chyby, podívejte se do [http://modsecurity.org/documentation/ dokumentace mod_security] nebo kontaktujte technickou podporu vašeho poskytovatele.",
'config-diff3-bad' => 'Nebyl nalezen GNU diff3.',
+ 'config-git' => 'Nalezen software pro správu verzí Git: <code>$1</code>.',
+ 'config-git-bad' => 'Software pro správu verzí Git nebyl nalezen.',
'config-imagemagick' => 'Nalezen ImageMagick: <code>$1</code>.
Pokud povolíte naÄítání souborů, bude zapnuto vytváření náhledů.',
'config-gd' => 'Nalezena vestavěná grafická knihovna GD.
@@ -3348,7 +3558,6 @@ Zvažte umístění databáze někam zcela jinam, například do <code>/var/lib/
'config-type-postgres' => 'PostgreSQL',
'config-type-sqlite' => 'SQLite',
'config-type-oracle' => 'Věštba',
- 'config-type-ibm_db2' => 'IBM DB2',
'config-support-info' => 'MediaWiki podporuje následující databázové systémy:
$1
@@ -3358,18 +3567,16 @@ 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. ([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',
'config-header-oracle' => 'Nastavení Oracle',
- 'config-header-ibm_db2' => 'Nastavení IBM DB2',
'config-invalid-db-type' => 'Chybný typ databáze',
'config-missing-db-name' => 'Musíte zadat hodnotu pro „Jméno databáze“',
'config-missing-db-host' => 'Musíte zadat hodnotu pro „Databázový server“',
'config-missing-db-server-oracle' => 'Musíte zadat hodnotu pro „Databázové TNS“',
'config-invalid-db-server-oracle' => 'Chybné databázové TNS „$1“.
-Používejte pouze ASCII písmena (a-z, A-Z), Äísla (0-9), podtržítko (_) a teÄku (.).',
+Používejte buÄ â€žTNS Name“ nebo „Easy Connect“ (vizte [http://docs.oracle.com/cd/E11882_01/network.112/e10836/naming.htm Oracle Naming Methods]).',
'config-invalid-db-name' => 'Chybné jméno databáze „$1“.
Používejte pouze ASCII písmena (a-z, A-Z), Äísla (0-9), podtržítko (_) a spojovník (-).',
'config-invalid-db-prefix' => 'Chybný databázový prefix „$1“.
@@ -3425,7 +3632,7 @@ 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 <code>LocalSettings.php</code> →',
+ 'config-regenerate' => 'Přegenerovat LocalSettings.php →',
'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',
@@ -3444,6 +3651,12 @@ Svou wiki teÄ můžete [$1 zaÄít používat].',
Pokud vaÅ¡e instalace MySQL podporuje InnoDB, důraznÄ› doporuÄujeme použít spíše to.
Pokud vaÅ¡e instalace MySQL InnoDB nepodporuje, možná je Äas na aktualizaci.",
+ 'config-mysql-only-myisam-dep' => "'''UpozornÄ›ní:''' Jediným dostupným formátem dat pro MySQL je MyISAM, který se k užití pro MediaWiki nedoporuÄuje, protože:
+* téměř nepodporuje souÄasný přístup kvůli zamykání tabulek,
+* oproti jiným formátům je náchylnější k poškození,
+* kód MediaWiki nepodporuje MyISAM tak dobře, jak by bylo potřeba.
+
+VaÅ¡e instalace MySQL nepodporuje InnoDB, možná je na Äase upgradovat.",
'config-mysql-engine-help' => "'''InnoDB''' je téměř vždy nejlepší volba, neboÅ¥ má dobrou podporu souÄasného přístupu.
'''MyISAM''' může být rychlejší u instalací pro jednoho uživatele nebo jen pro Ätení.
@@ -3455,7 +3668,6 @@ Databáze MyISAM bývají poÅ¡kozeny ÄastÄ›ji než databáze InnoDB.",
To je výkonnější než UTF-8 režim MySQL a umožňuje využít plný rozsah znaků Unicode.
V '''režimu UTF-8''' bude MySQL znát znakovou sadu vašich dat a může je příslušně zobrazovat a převádět, ale neumožní vám uložit znaky mimo [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Basic Multilingual Plane].",
- 'config-ibm_db2-low-db-pagesize' => "VaÅ¡e DB2 databáze má implicitní tabulkový prostor s nedostateÄnou velikostí stránky. Velikost stránky musí být minimálnÄ› '''32K'''.",
'config-site-name' => 'Název wiki:',
'config-site-name-help' => 'Bude se zobrazovat v titulku prohlížeÄe a na dalších místech.',
'config-site-name-blank' => 'Zadejte název serveru.',
@@ -3565,6 +3777,8 @@ Tento adresář by ideálně neměl být dostupný z webu.',
'config-logo-help' => 'Základní vzhled MediaWiki zahrnuje místo pro logo o velikosti 135×160 pixelů nad boÄním menu.
NaÄtÄ›te obrázek odpovídající velikosti a zadejte sem jeho URL.
+Pokud je vaÅ¡e logo umístÄ›no relativnÄ› vůÄi <code>$wgStylePath</code> nebo <code>$wgScriptPath</code>, můžete zde tyto promÄ›nné použít.
+
Pokud logo nechcete, ponechte toto pole prázdné.',
'config-instantcommons' => 'Zapnout Instant Commons',
'config-instantcommons-help' => '[//www.mediawiki.org/wiki/InstantCommons Instant Commons] je funkce, která umožňuje wiki používat obrázky, zvuky a další mediální soubory ze serveru [//commons.wikimedia.org/wiki/Hlavn%C3%AD_strana Wikimedia Commons].
@@ -3658,6 +3872,9 @@ Až to dokonÄíte, můžete '''[$2 vstoupit do své wiki]'''.",
'config-download-localsettings' => 'Stáhnout <code>LocalSettings.php</code>',
'config-help' => 'nápověda',
'config-nofile' => 'Soubor „$1“ nelze nalézt. Byl smazán?',
+ 'config-extension-link' => 'Věděli jste, že vaše wiki podporuje [//www.mediawiki.org/wiki/Manual:Extensions rozšíření]?
+
+Můžete procházet [//www.mediawiki.org/wiki/Category:Extensions_by_category rozšíření po kategoriích] nebo si prohlédnout [//www.mediawiki.org/wiki/Extension_Matrix Matici rozšíření] obsahující úplný seznam.',
'mainpagetext' => "'''MediaWiki byla úspěšně nainstalována.'''",
'mainpagedocfooter' => '[//meta.wikimedia.org/wiki/Help:Contents Uživatelská příruÄka] vám napoví, jak MediaWiki používat.
@@ -3675,6 +3892,15 @@ $messages['csb'] = array(
'mainpagetext' => "'''MediaWiki òsta zainstalowónô.'''",
);
+/** Church Slavic (ÑловѣÌньÑкъ / ⰔⰎⰑⰂⰡâ°â° â°”â°â°Ÿ)
+ * @author ОйЛ
+ */
+$messages['cu'] = array(
+ 'config-page-language' => 'Ñ©ê™ê™‘къ',
+ 'config-page-name' => 'имѧ',
+ 'config-help' => 'помощь',
+);
+
/** Chuvash (Чӑвашла)
*/
$messages['cv'] = array(
@@ -3684,21 +3910,23 @@ $messages['cv'] = array(
== Пулăшма пултарĕç ==
* [//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
);
/** Welsh (Cymraeg)
+ * @author Lloffiwr
* @author Xxglennxx
*/
$messages['cy'] = array(
'mainpagetext' => "'''Wedi llwyddo gosod meddalwedd MediaWiki yma'''",
- 'mainpagedocfooter' => 'Ceir cymorth (yn Saesneg) ar ddefnyddio meddalwedd wici yn y [//meta.wikimedia.org/wiki/Help:Contents Canllaw Defnyddwyr] ar wefan Wikimedia.
+ 'mainpagedocfooter' => "Ceir cymorth (yn Saesneg) ar ddefnyddio meddalwedd wici yn y [//meta.wikimedia.org/wiki/Help:Contents Canllaw Defnyddwyr] ar wefan Wikimedia.
==Cychwyn arni==
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Rhestr osodiadau wrth gyflunio]
* [//www.mediawiki.org/wiki/Manual:FAQ Cwestiynau poblogaidd ar MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Rhestr postio datganiadau MediaWiki]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Rhestr postio datganiadau MediaWiki]
+* [//www.mediawiki.org/wiki/Localisation#Translation_resources Cyfieithu MediaWici i'ch iaith chi]",
);
/** Danish (dansk)
@@ -3711,10 +3939,11 @@ $messages['da'] = array(
== At komme i gang ==
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Listen over opsætningsmuligheder]
* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki ofte stillede spørgsmål]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Postliste angående udgivelser af MediaWiki]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Postliste angående udgivelser af MediaWiki]', # Fuzzy
);
/** German (Deutsch)
+ * @author Geitost
* @author Kghbln
* @author LWChris
* @author Metalhead64
@@ -3777,8 +4006,8 @@ Die Datei <code>php.ini</code> muss geprüft und es muss dabei sichergestellt we
'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.
-Die Ergebnisse dieser Prüfung sollten angegeben werden, sofern während des Installationsvorgangs Hilfe benötigt und erfragt wird.',
+Die Basisprüfungen werden jetzt durchgeführt, um festzustellen, ob die Installationsumgebung für die Installation von MediaWiki geeignet ist.
+Nimm diese Information auf, falls du Unterstützung bei der Vervollständigung der Installation benötigst.',
'config-copyright' => "=== Lizenz und Nutzungsbedingungen ===
$1
@@ -3787,7 +4016,7 @@ Dieses Programm ist freie Software, d. h. es kann, gemäß den Bedingungen der v
Dieses Programm wird in der Hoffnung verteilt, dass es nützlich sein wird, allerdings '''ohne jegliche Garantie''' und sogar ohne die implizierte Garantie einer '''Marktgängigkeit''' oder '''Eignung für einen bestimmten Zweck'''. Hierzu sind weitere Hinweise in der ''GNU General Public License'' enthalten.
-Eine <doclink href=Copying>Kopie der ''GNU General Public License''</doclink> sollte zusammen mit diesem Programm verteilt worden sein. Sofern dies nicht der Fall war, kann eine Kopie bei der Free Software Foundation Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA, schriftlich angefordert oder auf deren Website [http://www.gnu.org/copyleft/gpl.html online gelesen] werden.",
+Eine <doclink href=Copying>Kopie der GNU General Public License</doclink> sollte zusammen mit diesem Programm verteilt worden sein. Sofern dies nicht der Fall war, kann eine Kopie bei der Free Software Foundation Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA, schriftlich angefordert oder auf deren Website [http://www.gnu.org/copyleft/gpl.html online gelesen] werden.",
'config-sidebar' => '* [//www.mediawiki.org/wiki/MediaWiki/de Website von MediaWiki]
* [//www.mediawiki.org/wiki/Help:Contents/de Benutzeranleitung]
* [//www.mediawiki.org/wiki/Manual:Contents/de Administratorenanleitung]
@@ -3847,14 +4076,20 @@ MediaWiki benötigt die UTF-8-Unterstützung, um fehlerfrei lauffähig zu sein."
Dieser Wert ist wahrscheinlich zu niedrig.
Der Installationsvorgang könnte daher scheitern!",
'config-ctype' => "'''Fataler Fehler:''' PHP muss mit Unterstützung für das [http://www.php.net/manual/de/ctype.installation.php Modul ctype] kompiliert werden.",
+ 'config-json' => "'''Fatal:''' PHP wurde ohne JSON-Support kompiliert.
+Du musst entweder die PHP-JSON- oder die [http://pecl.php.net/package/jsonc PECL-jsonc]-Erweiterung installieren, bevor du MediaWiki installierst.
+* Die PHP-Erweiterung ist in Red Hat Enterprise Linux (CentOS) 5 und 6 enthalten, muss jedoch in <code>/etc/php.ini</code> oder <code>/etc/php.d/json.ini</code> aktiviert werden.
+* Einige Linux-Distributionen, die nach Mai 2013 veröffentlicht wurden, lassen die PHP-Erweiterung weg, stattdessen wird die PECL-Erweiterung als <code>php5-json</code> oder <code>php-pecl-jsonc</code> mitgeliefert.",
'config-xcache' => '[http://xcache.lighttpd.net/ XCache] ist installiert',
'config-apc' => '[http://www.php.net/apc APC] ist installiert',
'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache] ist installiert',
- 'config-no-cache' => "'''Warnung:''' [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] oder [http://www.iis.net/download/WinCacheForPhp WinCache] konnten nicht gefunden werden.
+ 'config-no-cache' => "'''Warnung:''' [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] oder [http://www.iis.net/download/WinCacheForPhp WinCache] wurden nicht gefunden.
Das Objektcaching kann daher nicht aktiviert werden.",
'config-mod-security' => "'''Warnung:''' Auf dem Webserver wurde [http://modsecurity.org/ ModSecurity] aktiviert. Sofern falsch konfiguriert, kann dies zu Problemen mit MediaWiki sowie anderer Software auf dem Server führen und es Benutzern ermöglichen beliebige Inhalte im Wiki einzustellen.
Für weitere Informationen empfehlen wir die [http://modsecurity.org/documentation/ Dokumentation zu ModSecurity] oder den Kontakt zum Hoster, sofern Fehler auftreten.",
'config-diff3-bad' => 'GNU diff3 wurde nicht gefunden.',
+ 'config-git' => 'Die Git-Versionsverwaltungssoftware wurde gefunden: <code>$1</code>.',
+ 'config-git-bad' => 'Die Git-Versionsverwaltungssoftware wurde nicht gefunden.',
'config-imagemagick' => 'ImageMagick wurde gefunden: <code>$1</code>.
Miniaturansichten von Bildern werden möglich sein, sobald das Hochladen von Dateien aktiviert wurde.',
'config-gd' => 'Die im System integrierte GD-Grafikbibliothek wurde gefunden.
@@ -3939,19 +4174,18 @@ Nur Änderungen daran vornehmen, sofern es gute Gründe dafür gibt.',
Das für sie vorgesehene Verzeichnis muss während des Installationsvorgangs beschreibbar sein.
-Es sollte '''nicht'' über das Web zugänglich sein, was der Grund ist, warum die Datei nicht dort abgelegt wird, wo sich die PHP-Dateien befinden.
+Es sollte '''nicht''' über das Web zugänglich sein, was der Grund ist, warum die Datei nicht dort abgelegt wird, wo sich die PHP-Dateien befinden.
Das Installationsprogramm wird mit der Datei zusammen eine zusätzliche <code>.htaccess</code>-Datei erstellen. Sofern dies scheitert, können Dritte auf die Datendatei zugreifen.
Dies umfasst die Nutzerdaten (E-Mail-Adressen, Passwörter, etc.) wie auch gelöschte Seitenversionen und andere vertrauliche Daten, die im Wiki gespeichert sind.
-Es ist daher zu erwägen die Datendatei an gänzlich anderer Stelle abzulegen, beispielsweise im Verzeichnis <code>./var/lib/mediawiki/yourwiki</code>.",
+Es ist daher zu erwägen, die Datendatei an gänzlich anderer Stelle abzulegen, beispielsweise im Verzeichnis <code>./var/lib/mediawiki/yourwiki</code>.",
'config-oracle-def-ts' => 'Standardtabellenraum:',
'config-oracle-temp-ts' => 'Temporärer Tabellenraum:',
'config-type-mysql' => 'MySQL',
'config-type-postgres' => 'PostgreSQL',
'config-type-sqlite' => 'SQLite',
'config-type-oracle' => 'Oracle',
- 'config-type-ibm_db2' => 'IBM DB2',
'config-support-info' => 'MediaWiki unterstützt die folgenden Datenbanksysteme:
$1
@@ -3961,18 +4195,16 @@ 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 ([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',
'config-header-oracle' => 'Oracle-Einstellungen',
- 'config-header-ibm_db2' => 'IBM DB2-Einstellungen',
'config-invalid-db-type' => 'Unzulässiges Datenbanksystem',
'config-missing-db-name' => 'Bei „Datenbankname“ muss ein Wert angegeben werden.',
'config-missing-db-host' => 'Bei „Datenbankhost“ muss ein Wert angegeben werden.',
'config-missing-db-server-oracle' => 'Für das „Datenbank-TNS“ muss ein Wert eingegeben werden',
'config-invalid-db-server-oracle' => 'Ungültiges Datenbank-TNS „$1“.
-Es dürfen nur ASCII-codierte Buchstaben (a-z, A-Z), Zahlen (0-9) und Unterstriche (_) und Punkte (.) verwendet werden.',
+Entweder „TNS Name“ oder eine „Easy Connect“-Zeichenfolge verwenden ([http://docs.oracle.com/cd/E11882_01/network.112/e10836/naming.htm Oracle-Benennungsmethoden])',
'config-invalid-db-name' => 'Ungültiger Datenbankname „$1“.
Es dürfen nur ASCII-codierte Buchstaben (a-z, A-Z), Zahlen (0-9), Unter- (_) sowie Bindestriche (-) verwendet werden.',
'config-invalid-db-prefix' => 'Ungültiger Datenbanktabellenpräfix „$1“.
@@ -4029,7 +4261,7 @@ 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' => '<code>LocalSettings.php</code> neu erstellen →',
+ 'config-regenerate' => 'LocalSettings.php 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',
@@ -4048,6 +4280,12 @@ Das hier angegebene Datenbankkonto muss daher bereits vorhanden sein.',
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.",
+ 'config-mysql-only-myisam-dep' => "'''Warnung:''' MyISAM ist die einzige verfügbare Speicher-Engine für MySQL, die nicht für die Verwendung mit MediaWiki empfohlen wird, da sie
+* aufgrund von Tabellensperrungen kaum die nebenläufige Ausführung von Aktionen unterstützt,
+* anfälliger für Datenprobleme ist und
+* von MediaWiki nicht immer adäquat unterstützt wird.
+
+Deine MySQL-Installation unterstützt nicht InnoDB. Eventuell muss eine Aktualisierung durchgeführt werden.",
'config-mysql-engine-help' => "'''InnoDB''' ist fast immer die bessere Wahl, da es gleichzeitige Zugriffe gut unterstützt.
'''MyISAM''' ist in Einzelnutzerumgebungen sowie bei schreibgeschützten Wikis schneller.
@@ -4060,7 +4298,6 @@ Dies ist effizienter als der UTF-8-Modus von MySQL und ermöglicht so die Verwen
Im '''UTF-8-Modus''' wird MySQL den Zeichensatz der Daten erkennen und sie richtig anzeigen und konvertieren,
allerdings können keine Zeichen außerhalb des [//de.wikipedia.org/wiki/Basic_Multilingual_Plane#Gliederung_in_Ebenen_und_Bl.C3.B6cke ''Basic Multilingual Plane'' (BMP)] gespeichert werden.",
- 'config-ibm_db2-low-db-pagesize' => "Die DB2-Datenbank verfügt über einen Standardtabellenraum mit einer unzureichenden Seitengröße. Die Seitengröße muss '''32 000'' oder größer sein.",
'config-site-name' => 'Name des Wikis:',
'config-site-name-help' => 'Er wird in der Titelleiste des Browsers, wie auch verschiedenen anderen Stellen, genutzt.',
'config-site-name-blank' => 'Den Namen des Wikis angeben.',
@@ -4147,11 +4384,11 @@ Für den Fall, dass die E-Mail-Funktionen nicht benötigt werden, können sie hi
'config-email-auth' => 'E-Mail-Authentifizierung ermöglichen',
'config-email-auth-help' => "Sofern diese Funktion aktiviert ist, müssen Benutzer ihre E-Mail-Adresse bestätigen, indem sie den Bestätigungslink nutzen, der ihnen immer dann zugesandt wird, wenn sie ihre E-Mail-Adresse angeben oder ändern.
Nur bestätigte E-Mail-Adressen können Nachrichten von anderen Benutzer oder Benachrichtigungsmitteilungen erhalten.
-Die Aktivierung dieser Funktion wird bei offenen Wikis, mit Hinblick auf möglichen Missbrauch der E-Mailfunktionen, '''empfohlen'''.",
+Die Aktivierung dieser Funktion wird bei offenen Wikis, mit Hinblick auf möglichen Missbrauch der E-Mail-Funktionen, '''empfohlen.'''",
'config-email-sender' => 'E-Mail-Adresse für Antworten:',
'config-email-sender-help' => 'Bitte hier die E-Mail-Adresse angeben, die als Absenderadresse bei ausgehenden E-Mails eingesetzt werden soll.
Rücklaufende E-Mails werden an diese E-Mail-Adresse gesandt.
-Bei viele E-Mail-Servern muss der Teil der E-Mail-Adresse mit der Domainangabe korrekt sein.',
+Bei vielen E-Mail-Servern muss der Teil der E-Mail-Adresse mit der Domainangabe korrekt sein.',
'config-upload-settings' => 'Hochladen von Bildern und Dateien',
'config-upload-enable' => 'Das Hochladen von Dateien ermöglichen',
'config-upload-help' => 'Das Hochladen von Dateien macht den Server für potentielle Sicherheitsprobleme anfällig.
@@ -4163,9 +4400,11 @@ Hernach kann diese Option aktiviert werden.',
'config-upload-deleted-help' => 'Bitte ein Verzeichnis auswählen, in dem gelöschte Dateien archiviert werden sollen.
Idealerweise sollte es nicht über das Internet zugänglich sein.',
'config-logo' => 'URL des Logos:',
- 'config-logo-help' => 'Die Standardoberfläche von MediaWiki verfügt links oberhalb der Seitenleiste über Platz für eine Logo mit den Maßen 135x160 Pixel.
+ 'config-logo-help' => 'Die Standardoberfläche von MediaWiki verfügt links oberhalb der Seitenleiste über Platz für ein Logo mit den Maßen 135x160 Pixel.
Bitte ein Logo in entsprechender Größe hochladen und die zugehörige URL an dieser Stelle angeben.
+Du kannst <code>$wgStylePath</code> oder <code>$wgScriptPath</code> verwenden, falls dein Logo relativ zu diesen Pfaden ist.
+
Sofern kein Logo benötigt wird, kann dieses Datenfeld leer bleiben.',
'config-instantcommons' => '„InstantCommons“ aktivieren',
'config-instantcommons-help' => '[//www.mediawiki.org/wiki/InstantCommons InstantCommons] ist eine Funktion, die es Wikis ermöglicht, Bild-, Klang- und andere Mediendateien zu nutzen, die auf der Website [//commons.wikimedia.org/ Wikimedia Commons] verfügbar sind.
@@ -4182,7 +4421,7 @@ Die Lizenz ist daher jetzt manuell einzugeben.',
Es wird sehr empfohlen es für mittelgroße bis große Wikis zu nutzen, aber auch für kleine Wikis ergeben sich erkennbare Geschwindigkeitsverbesserungen.',
'config-cache-none' => 'Kein Objektcaching (es wird keine Funktion entfernt, allerdings kann dies die Geschwindigkeit größerer Wikis negativ beeinflussen)',
'config-cache-accel' => 'Objektcaching von PHP (APC, XCache oder WinCache)',
- 'config-cache-memcached' => 'Memchached Cacheserver nutzen (erfordert einen zusätzliche Installationsvorgang mitsamt Konfiguration)',
+ 'config-cache-memcached' => 'Memcached Cacheserver nutzen (erfordert einen zusätzlichen Installationsvorgang mitsamt Konfiguration)',
'config-memcached-servers' => 'Memcached Cacheserver',
'config-memcached-help' => 'Liste der für Memcached nutzbaren IP-Adressen.
Es sollte eine je Zeile mitsamt des vorgesehenen Ports angegeben werden. Beispiele:
@@ -4259,6 +4498,9 @@ Sobald alles erledigt wurde, kann auf das '''[$2 Wiki zugegriffen werden]'''. Wi
'config-download-localsettings' => '<code>LocalSettings.php</code> herunterladen',
'config-help' => 'Hilfe',
'config-nofile' => 'Die Datei „$1“ konnte nicht gefunden werden. Wurde sie gelöscht?',
+ 'config-extension-link' => 'Wusstest du, dass dein Wiki [//www.mediawiki.org/wiki/Manual:Extensions Erweiterungen] unterstützt?
+
+Du kannst [//www.mediawiki.org/wiki/Category:Extensions_by_category Erweiterungen nach Kategorie] durchsuchen oder die [//www.mediawiki.org/wiki/Extension_Matrix Erweiterungstabelle] ansehen, um eine volle Erweiterungsliste zu erhalten.',
'mainpagetext' => "'''MediaWiki wurde erfolgreich installiert.'''",
'mainpagedocfooter' => 'Hilfe zur Benutzung und Konfiguration der Wiki-Software findest du im [//meta.wikimedia.org/wiki/Help:Contents Benutzerhandbuch].
@@ -4279,7 +4521,7 @@ $messages['de-formal'] = array(
== Starthilfen ==
* [//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]', # Fuzzy
);
/** Zazaki (Zazaki)
@@ -4317,7 +4559,6 @@ $messages['diq'] = array(
'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 dılet',
'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
'config-db-port' => 'Portê database:',
- 'config-type-ibm_db2' => 'IBM DB2',
'config-header-mysql' => 'Sazkardışê MySQL',
'config-mysql-innodb' => 'InnoDB',
'config-mysql-myisam' => 'MyISAM',
@@ -4346,7 +4587,7 @@ $messages['diq'] = array(
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lista eyaranê vıraştışi]
* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki de ÇZP]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki ra lista serbest-dayışê postey]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki ra lista serbest-dayışê postey]', # Fuzzy
);
/** Lower Sorbian (dolnoserbski)
@@ -4376,7 +4617,7 @@ $messages['dtp'] = array(
== Kopotimpuunan ==
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lis papatantu nuludan]
* [//www.mediawiki.org/wiki/Manual:FAQ Ponguhatan Koinsoruan om Simbar ModiaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lis pininsuratan pinolabus do ModiaWiki]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lis pininsuratan pinolabus do ModiaWiki]', # Fuzzy
);
/** Greek (Ελληνικά)
@@ -4416,7 +4657,6 @@ $messages['el'] = array(
'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' => 'Όνομα του βίκι:',
@@ -4484,13 +4724,15 @@ $messages['eo'] = array(
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Listo de konfiguraĵoj] (angla)
* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki Oftaj Demandoj] (angla)
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki dissendolisto pri anoncoj] (angla)",
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki dissendolisto pri anoncoj] (angla)", # Fuzzy
);
/** Spanish (español)
* @author Armando-Martin
+ * @author Ciencia Al Poder
* @author Crazymadlover
* @author Danke7
+ * @author Fitoschido
* @author Locos epraix
* @author Od1n
* @author Platonides
@@ -4552,7 +4794,7 @@ Verifica tu php.ini y comprueba que <code>session.save_path</code> está estable
'config-restart' => 'Sí, reiniciarlo',
'config-welcome' => '=== Comprobación del entorno ===
Se realiza comprobaciones básicas para ver si el entorno es adecuado para la instalación de MediaWiki.
-Deberás suministrar los resultados de tales comprobaciones si necesitas ayuda durante la instalación.',
+Deberás suministrar los resultados de tales comprobaciones si necesitas ayuda durante la instalación.', # Fuzzy
'config-copyright' => "=== Derechos de autor y Términos de uso ===
$1
@@ -4629,6 +4871,7 @@ El caché de objetos no está habilitado.",
'config-mod-security' => "''' Advertencia ''': Su servidor web tiene [http://modsecurity.org/ mod_security] habilitado. Si la configuración es incorrecta, puede causar problemas a MediaWiki u otro software que permita a los usuarios publicar contenido arbitrarios.
Consulte la [http://modsecurity.org/documentation/ documentación de mod_security] o contacte con el soporte de su servidor (''host'') si encuentra errores aleatorios.",
'config-diff3-bad' => 'GNU diff3 no se encuentra.',
+ 'config-git-bad' => 'No se encontró el software de control de versiones Git.',
'config-imagemagick' => 'ImageMagick encontrado: <code>$1</code>.
La miniaturización de imágenes se habilitará si habilitas las cargas.',
'config-gd' => 'Se ha encontrado una biblioteca de gráficos GD integrada.
@@ -4725,7 +4968,6 @@ Considere la posibilidad de poner la base de datos en algún otro sitio, por eje
'config-type-postgres' => 'PostgreSQL',
'config-type-sqlite' => 'SQLite',
'config-type-oracle' => 'Oracle',
- 'config-type-ibm_db2' => 'IBM DB2',
'config-support-info' => 'MediaWiki es compatible con los siguientes sistemas de bases de datos:
$1
@@ -4735,17 +4977,15 @@ Si no encuentras en el listado el sistema de base de datos que estás intentando
'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 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 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',
'config-header-oracle' => 'Configuración de Oracle',
- 'config-header-ibm_db2' => 'Configuración de IBM DB2',
'config-invalid-db-type' => 'Tipo de base de datos inválida',
'config-missing-db-name' => 'Debes introducir un valor para "Nombre de la base de datos"',
'config-missing-db-host' => 'Debe introducir un valor para "Servidor (host) de base de datos"',
'config-missing-db-server-oracle' => 'Debe introducir un valor para "TNS de la base de datos"',
- 'config-invalid-db-server-oracle' => 'El TNS de la base de datos, "$1", es inválido.Use sólo carateres ASCII: letras (a-z, A-Z), números (0-9), guiones bajos (_) y guiones (-).Usa sólo caracteres ASCII: letras (a-z, A-Z), dígitos (0-9), guiones bajos (_) y puntos (.).',
+ 'config-invalid-db-server-oracle' => 'El TNS de la base de datos, "$1", es inválido.Use sólo carateres ASCII: letras (a-z, A-Z), números (0-9), guiones bajos (_) y guiones (-).Usa sólo caracteres ASCII: letras (a-z, A-Z), dígitos (0-9), guiones bajos (_) y puntos (.).', # Fuzzy
'config-invalid-db-name' => 'El nombre de la base de datos "$1" es inválido.
Usa sólo caracteres ASCII: letras (a-z, A-Z), números (0-9), guiones bajos (_)y guiones (-).',
'config-invalid-db-prefix' => 'El prefijo de la base de datos "$1" es inválido.
@@ -4801,7 +5041,7 @@ 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 <code>LocalSettings.php</code> →',
+ 'config-regenerate' => 'Regenerar LocalSettings.php →',
'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',
@@ -4831,7 +5071,6 @@ Las bases de datos MyISAM tienden a corromperse más a menudo que las bases de d
Esto es más eficiente que el modo UTF-8 de MySQL y le permite utilizar la gama completa de caracteres Unicode.
En '''modo UTF-8''', MySQL sabrá qué conjunto de caracteres emplean sus datos y puede presentarlos y convertirlos adecuadamente, pero no le permitirá almacenar caracteres por encima del [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes plano multilingüe básico].",
- 'config-ibm_db2-low-db-pagesize' => "Su base de datos DB2 tiene un espacio de tablas por defecto con un tamaño de página insuficiente. El tamaño de página tiene que ser '''32 K''' o superior.",
'config-site-name' => 'Nombre del wiki:',
'config-site-name-help' => 'Esto aparecerá en la barra de título del navegador y en varios otros lugares.',
'config-site-name-blank' => 'Ingresar un nombre de sitio.',
@@ -4875,7 +5114,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', # Fuzzy
+ 'config-profile-wiki' => 'Wiki abierto',
'config-profile-no-anon' => 'Creación de cuenta requerida',
'config-profile-fishbowl' => 'Sólo editores autorizados',
'config-profile-private' => 'Wiki privado',
@@ -4934,14 +5173,14 @@ Para obtener más información, lea la [//www.mediawiki.org/wiki/Manual:Security
Para habilitar la carga de archivos, cambie el modo en el subdirectorio <code>images</code> bajo el directorio raíz de MediaWiki para que el servidor web pueda escribir en él.
A continuación, habilite esta opción.',
- 'config-upload-deleted' => '*Directório para los archivos eliminados:',
+ 'config-upload-deleted' => '*Directorio para los archivos eliminados:',
'config-upload-deleted-help' => 'Elige un directorio en el que guardar los archivos eliminados.
Lo ideal es una carpeta no accesible desde la red.',
'config-logo' => 'URL del logo :',
'config-logo-help' => 'La apariencia por defecto de MediaWiki incluye espacio para un logotipo de 135x160 píxeles encima del menú de la barra lateral.
Cargue una imagen de tamaño adecuado e introduzca la dirección URL aquí.
-Si no desea un logotipo, deje esta casilla en blanco.',
+Si no desea un logotipo, deje esta casilla en blanco.', # Fuzzy
'config-instantcommons' => 'Habilitar Instant Commons',
'config-instantcommons-help' => '[//www.mediawiki.org/wiki/InstantCommons Instant Commons] es una característica que permite que los wikis puedan utilizar imágenes, sonidos y otros archivos multimedia que se encuentran en el sitio [//commons.wikimedia.org/ Wikimedia Commons].
Para ello, MediaWiki requiere acceso a Internet.
@@ -5051,7 +5290,7 @@ $messages['es-formal'] = array(
== 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]', # Fuzzy
);
/** Estonian (eesti)
@@ -5196,7 +5435,7 @@ $messages['eu'] = array(
'config-admin-password' => 'Pasahitza:',
'config-admin-password-confirm' => 'Pasahitza berriz:',
'config-admin-email' => 'E-posta helbidea:',
- 'config-profile-wiki' => 'Wiki tradizionala',
+ 'config-profile-wiki' => 'Wiki tradizionala', # Fuzzy
'config-profile-private' => 'Wiki pribatua',
'config-license' => 'Copyright eta lizentzia:',
'config-license-pd' => 'Domeinu Askea',
@@ -5212,7 +5451,7 @@ $messages['eu'] = array(
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Konfigurazio balioen zerrenda]
* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ (Maiz egindako galderak)]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWikiren argitalpenen posta zerrenda]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWikiren argitalpenen posta zerrenda]', # Fuzzy
);
/** Extremaduran (estremeñu)
@@ -5225,7 +5464,7 @@ $messages['ext'] = array(
* [//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
);
/** Persian (Ùارسی)
@@ -5316,6 +5555,7 @@ $messages['fa'] = array(
* @author Crt
* @author Nike
* @author Olli
+ * @author Silvonen
* @author Str4nd
* @author VezonThunder
* @author ì•„ë¼
@@ -5411,6 +5651,7 @@ Asennus saattaa epäonnistua!",
'config-db-install-help' => 'Anna käyttäjätunnus ja salasana, joita käytetään asennuksen aikana.',
'config-db-account-lock' => 'Käytä samaa tunnusta ja salasanaa myös asennuksen jälkeen',
'config-db-prefix' => 'Tietokantataulujen etuliite',
+ 'config-db-charset' => 'Tietokannan merkistö',
'config-charset-mysql5-binary' => 'MySQL 4.1/5.0, binääri',
'config-charset-mysql5' => 'MySQL 4.1/5.0, UTF-8',
'config-charset-mysql4' => 'MySQL 4.0, taaksepäin yhteensopiva UTF-8',
@@ -5419,13 +5660,10 @@ Asennus saattaa epäonnistua!",
'config-type-postgres' => 'PostgreSQL',
'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.', # Fuzzy
'config-header-mysql' => 'MySQL-asetukset',
'config-header-postgres' => 'PostgreSQL-asetukset',
'config-header-sqlite' => 'SQLite-asetukset',
'config-header-oracle' => 'Oracle-asetukset',
- 'config-header-ibm_db2' => 'IBM DB2 -asetukset',
'config-invalid-db-type' => 'Virheellinen tietokantatyyppi',
'config-missing-db-name' => 'Kenttä »Tietokannan nimi» on pakollinen',
'config-invalid-db-name' => 'â€$1†ei kelpaa tietokannan nimeksi.
@@ -5449,7 +5687,7 @@ 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 <code>LocalSettings.php</code> uudelleen →',
+ 'config-regenerate' => 'Luo LocalSettings.php uudelleen →',
'config-show-table-status' => 'Kysely <code>SHOW TABLE STATUS</code> epäonnistui!',
'config-mysql-engine' => 'Tallennusmoottori',
'config-mysql-innodb' => 'InnoDB',
@@ -5582,9 +5820,9 @@ Vérifiez votre fichier php.ini et assurez-vous que <code>session.save_path</cod
'config-page-existingwiki' => 'Wiki existant',
'config-help-restart' => "Voulez-vous effacer toutes les données enregistrées que vous avez entrées et relancer le processus d'installation ?",
'config-restart' => 'Oui, le relancer',
- 'config-welcome' => "=== Vérifications liées à l’environnement ===
-Des vérifications de base sont effectuées pour voir si cet environnement est adapté à l'installation de MediaWiki.
-Vous devriez indiquer les résultats de ces vérifications si vous avez besoin d’aide lors de l’installation.",
+ 'config-welcome' => '=== Vérifications liées à l’environnement ===
+Des vérifications de base vont maintenant être effectuées pour voir si cet environnement est adapté à l’installation de MediaWiki.
+Rappelez-vous d’inclure ces informations si vous recherchez de l’aide sur la manière de terminer l’installation.',
'config-copyright' => "=== Droit d'auteur et conditions ===
$1
@@ -5653,6 +5891,10 @@ MédiaWiki nécessite la gestion d’UTF-8 pour fonctionner correctement.",
Cette valeur est probablement trop faible.
Il est possible que l’installation échoue !",
'config-ctype' => "'''Fatal ''': PHP doit être compilé avec le support pour l'[http://www.php.net/manual/en/ctype.installation.php extension Ctype].",
+ 'config-json' => "'''Erreur fatale :''' PHP a été compilé sans le support de JSON.
+Vous devez soit installez l’extension JSON de PHP ou l’extension [http://pecl.php.net/package/jsonc PECL jsonc] avant d’installer MediaWiki.
+* L’extension PHP est comprise dans Red Hat Enterprise Linux (CentOS) 5 et 6, mais doit être activée dans <code>/etc/php.ini</code> ou <code>/etc/php.d/json.ini</code>.
+* Certaines distributions Linux après mai 2013 ne comprennent pas l’extension PHP, mais oint mis à la place l’extension PECL sous al forme <code>php5-json</code> ou <code>php-pecl-jsonc</code>.",
'config-xcache' => '[http://xcache.lighttpd.net/ XCache] est installé',
'config-apc' => '[http://www.php.net/apc APC] est installé',
'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache] est installé',
@@ -5661,6 +5903,8 @@ La mise en cache d'objets n'est pas activée.",
'config-mod-security' => "'''Attention''': Votre serveur web a [http://modsecurity.org/ mod_security] activé. S&il est mal configuré, cela peut poser des problèmes à MediaWiki ou à d'autres applications qui permettent aux utilisateurs de publier un contenu quelconque.
Reportez-vous à [http://modsecurity.org/documentation/ la documentation de mod_security] ou contactez le support de votre hébergeur si vous rencontrez des erreurs aléatoires.",
'config-diff3-bad' => 'GNU diff3 introuvable.',
+ 'config-git' => 'Logiciel de contrôle de version Git trouvé : <code>$1</code>.',
+ 'config-git-bad' => 'Logiciel de contrôle de version Git non trouvé.',
'config-imagemagick' => "ImageMagick trouvé : <code>$1</code>.
La miniaturisation d'images sera activée si vous activez le téléversement de fichiers.",
'config-gd' => "La bibliothèque graphique GD intégrée a été trouvée.
@@ -5754,7 +5998,6 @@ Envisagez de placer la base de données ailleurs, par exemple dans <code>/var/li
'config-type-postgres' => 'PostgreSQL',
'config-type-sqlite' => 'SQLite',
'config-type-oracle' => 'Oracle',
- 'config-type-ibm_db2' => 'IBM DB2',
'config-support-info' => "MediaWiki supporte ces systèmes de bases de données :
$1
@@ -5764,18 +6007,16 @@ 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. ([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',
'config-header-oracle' => 'Paramètres d’Oracle',
- 'config-header-ibm_db2' => 'paramètres de IBM DB2',
'config-invalid-db-type' => 'Type de base de données non valide',
'config-missing-db-name' => 'Vous devez saisir une valeur pour « Nom de la base de données »',
'config-missing-db-host' => "Vous devez entrer une valeur pour « l'hôte de la base de données »",
'config-missing-db-server-oracle' => 'Vous devez saisir une valeur pour le « Nom TNS de la base de données »',
'config-invalid-db-server-oracle' => 'Le nom TNS de la base de données (« $1 ») est invalide.
-Il ne peut contenir que des lettres latines de base (a-z, A-Z), des chiffres (0-9), des caractères de soulignement (_) et des points (.).',
+Utilisez uniquement la chaîne "TNS Name" ou "Easy Connect" ([http://docs.oracle.com/cd/E11882_01/network.112/e10836/naming.htm Méthodes de nommage Oracle])',
'config-invalid-db-name' => 'Nom de la base de données invalide (« $1 »).
Il ne peut contenir que des lettres latines (a-z, A-Z), des chiffres (0-9), des caractères de soulignement (_) et des tirets (-).',
'config-invalid-db-prefix' => 'Préfixe de la base de données non valide « $1 ».
@@ -5831,7 +6072,7 @@ 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 <code>LocalSettings.php</code> →',
+ 'config-regenerate' => 'Regénérer LocalSettings.php →',
'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",
@@ -5848,6 +6089,12 @@ Le compte que vous spécifiez ici doit déjà exister.",
* il est plus sujet à la corruption que les autres moteurs
* le codebase MediaWiki ne gère pas toujours MyISAM comme il se doit
Si votre installation MySQL supporte InnoDB, il est fortement recommandé que vous le choisissez plutôt. Si votre installation MySQL ne supporte pas les tables InnoDB, il est peut-être temps de faire une mise à niveau.",
+ 'config-mysql-only-myisam-dep' => "'''Attention :''' MyISAM est le seul moteur de stockage disponible pour MySQL qui ne soit pas recommandé pour une utilsiation avec MédiaWiki, car :
+* il supporte très peu les accès concurrents à cause du verrouillage des tables
+* il est plus sujet à corruption que les autres moteurs
+* le code de base de MédiaWiki ne gère pas toujours MyISAM comme il faudrait
+
+Votre installation MySQL ne supporte pas InnoDB ; il est peut-être temps de la mettre à jour.",
'config-mysql-engine-help' => "'''InnoDB''' est presque toujours la meilleure option, car il supporte bien l'[http://fr.wikipedia.org/wiki/Ordonnancement_dans_les_syst%C3%A8mes_d%27exploitation ordonnancement].
'''MyISAM''' peut être plus rapide dans les installations monoposte ou en lecture seule. Les bases de données MyISAM ont tendance à se corrompre plus souvent que celles d'InnoDB.",
@@ -5858,7 +6105,6 @@ Si votre installation MySQL supporte InnoDB, il est fortement recommandé que vo
En ''mode binaire'', MediaWiki stocke le texte UTF-8 dans des champs binaires de la base de données. C'est plus efficace que le ''mode UTF-8'' de MySQL, et vous permet d'utiliser toute la gamme des caractères Unicode.
En ''mode UTF-8'', MySQL connaîtra le jeu de caractères de vos données et pourra présenter et convertir les données de manière appropriée, mais il ne vous laissera pas stocker les caractères au-dessus du [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes plan multilingue de base] (en anglais).",
- 'config-ibm_db2-low-db-pagesize' => "Votre base de données DB2 a un espace de stockage par défaut avec un pagesize insuffisant. Le pagesize doit être au minimum '''32K'''.",
'config-site-name' => 'Nom du wiki :',
'config-site-name-help' => 'Il apparaîtra dans la barre de titre du navigateur et en divers autres endroits.',
'config-site-name-blank' => 'Entrez un nom de site.',
@@ -5963,10 +6209,12 @@ Ensuite, activez cette option.",
'config-upload-deleted-help' => 'Choisissez un répertoire qui servira à archiver les fichiers supprimés.
Idéalement, il ne devrait pas être accessible depuis le web.',
'config-logo' => 'URL du logo :',
- 'config-logo-help' => "L'habillage (''skin'') par défaut de MediaWiki comprend l'espace pour un logo de 135x160 pixels dans le coin supérieur gauche.
-Téléchargez une image de la taille appropriée, et entrez l'URL ici.
+ 'config-logo-help' => 'L’habillage par défaut de MediaWiki comprend l’espace pour un logo de 135x160 pixels au-dessus de la barre de menu latérale.
+Téléchargez une image de la taille appropriée, et entrez son URL ici.
+
+Vous pouvez utiliser <code>$wgStylePath</code> ou <code>$wgScriptPath</code> si votre logo est relatif à ces chemins.
-Si vous ne voulez pas d'un logo, laissez cette case vide.",
+Si vous ne voulez pas de logo, laissez cette case vide.',
'config-instantcommons' => "Activer ''InstantCommons''",
'config-instantcommons-help' => "[//www.mediawiki.org/wiki/InstantCommons InstantCommons] est un service qui permet d'utiliser les images, les sons et les autres médias disponibles sur le site [//commons.wikimedia.org/ Wikimedia Commons].
Pour se faire, il faut que MediaWiki accède à Internet.
@@ -6056,6 +6304,9 @@ Lorsque c'est fait, vous pouvez '''[$2 accéder à votre wiki]'''.",
'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é ?',
+ 'config-extension-link' => 'Saviez-vous que votre wiki supporte [//www.mediawiki.org/wiki/Manual:Extensions des extensions] ?
+
+Vous pouvez consulter les [//www.mediawiki.org/wiki/Category:Extensions_by_category extensions par catégorie] ou la [//www.mediawiki.org/wiki/Extension_Matrix Matrice des extensions] pourvoir la liste complète des extensions.',
'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 de wiki.
@@ -6076,7 +6327,7 @@ $messages['frc'] = array(
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Réglage]
* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki: Questions Souvent Posées]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki Liste à Malle]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki Liste à Malle]', # Fuzzy
);
/** Franco-Provençal (arpetan)
@@ -6137,18 +6388,16 @@ Portant, MediaWiki at fôta de PHP $2 ou ben ples hôt.',
'config-sqlite-dir' => 'Dossiér de les balyês SQLite :',
'config-oracle-def-ts' => "Èspâço de stocâjo (''tablespace'') per dèfôt :",
'config-oracle-temp-ts' => "Èspâço de stocâjo (''tablespace'') temporèro :",
- 'config-type-ibm_db2' => 'IBM DB2',
'config-header-mysql' => 'Paramètres de MySQL',
'config-header-postgres' => 'Paramètres de PostgreSQL',
'config-header-sqlite' => 'Paramètres de SQLite',
'config-header-oracle' => 'Paramètres d’Oracle',
- 'config-header-ibm_db2' => 'Paramètres d’IBM DB2',
'config-invalid-db-type' => 'Tipo de bâsa de balyês envalido',
'config-missing-db-name' => 'Vos dête buchiér una valor por « Nom de la bâsa de balyês »',
'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 <code>LocalSettings.php</code> →',
+ 'config-regenerate' => 'Refâre LocalSettings.php →',
'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',
@@ -6240,18 +6489,18 @@ Portant, MediaWiki at fôta de PHP $2 ou ben ples hôt.',
);
/** Northern Frisian (Nordfriisk)
+ * @author Murma174
* @author Pyt
*/
$messages['frr'] = array(
'mainpagetext' => "'''MediaWiki wörd ma erfolch instaliird.'''",
- 'mainpagedocfooter' => 'Heelp tu jü benjüting än konfigurasjoon foon e Wiki-software fanst dü önj dåt [//meta.wikimedia.org/wiki/Help:Contents Benutzerhandbuch].
-
-
-== Startheelpe ==
+ 'mainpagedocfooter' => "Consult the [//meta.wikimedia.org/wiki/Help:Contents User's Guide] for information on using the wiki software.
-* [//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]',
+== 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]
+* [//www.mediawiki.org/wiki/Localisation#Translation_resources Localise MediaWiki for your language]",
);
/** Friulian (furlan)
@@ -6270,7 +6519,7 @@ $messages['fy'] = array(
* [//www.mediawiki.org/wiki/Manual:Configuration_settings List mei ynstellings]
* [//www.mediawiki.org/wiki/Manual:FAQ Faak stelde fragen (FAQ)]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mailinglist foar oankundigings fan nije ferzjes]",
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mailinglist foar oankundigings fan nije ferzjes]", # Fuzzy
);
/** Irish (Gaeilge)
@@ -6283,7 +6532,7 @@ $messages['ga'] = array(
'config-help' => 'Cuidiú',
'mainpagetext' => "'''D'éirigh le suiteáil MediaWiki.'''",
'mainpagedocfooter' => 'Féach ar [//meta.wikimedia.org/wiki/MediaWiki_localisation doiciméid um conas an chomhéadán a athrú]
-agus an [//meta.wikimedia.org/wiki/MediaWiki_User%27s_Guide Lámhleabhar úsáideora] chun cabhair úsáide agus fíoraíochta a fháil.',
+agus an [//meta.wikimedia.org/wiki/MediaWiki_User%27s_Guide Lámhleabhar úsáideora] chun cabhair úsáide agus fíoraíochta a fháil.', # Fuzzy
);
/** Gagauz (Gagauz)
@@ -6296,7 +6545,7 @@ $messages['gag'] = array(
* [//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
);
/** Simplified Gan script (赣语(简体)‎)
@@ -6309,20 +6558,21 @@ $messages['gan-hans'] = array(
* [//www.mediawiki.org/wiki/Manual:Configuration_settings MediaWiki é…置设定列表]
* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki 平常问题解答]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki å‘布email清å•]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki å‘布email清å•]', # Fuzzy
);
/** Traditional Gan script (贛語(ç¹é«”)‎)
+ * @author Symane
*/
$messages['gan-hant'] = array(
- 'mainpagetext' => "'''安è£æ­£MediaWikiå˜ã€‚'''",
+ 'mainpagetext' => "'''安è£æ­£MediaWiki哩。'''",
'mainpagedocfooter' => 'åƒçœ‹[//meta.wikimedia.org/wiki/Help:Contents 用戶指å—]裡頭會話到啷用wiki軟件
== 開始使用 ==
* [//www.mediawiki.org/wiki/Manual:Configuration_settings MediaWiki é…置設定列表]
* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki 平常å•é¡Œè§£ç­”]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki 發佈email清單]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki 發佈email清單]', # Fuzzy
);
/** Scottish Gaelic (Gàidhlig)
@@ -6396,8 +6646,8 @@ Comprobe o seu php.ini e asegúrese de que en <code>session.save_path</code> est
'config-help-restart' => 'Quere eliminar todos os datos gardados e reiniciar o proceso de instalación?',
'config-restart' => 'Si, reiniciala',
'config-welcome' => '=== Comprobación da contorna ===
-Cómpre realizar unhas comprobacións básicas para ver se a contorna é axeitada para a instalación de MediaWiki.
-Deberá proporcionar os resultados destas comprobacións se necesita axuda durante a instalación.',
+Cómpre realizar agora unhas comprobacións básicas para ver se a contorna é axeitada para a instalación de MediaWiki.
+Lembre incluír esta información se necesita axuda para completar a instalación.',
'config-copyright' => "=== Dereitos de autor e termos de uso ===
$1
@@ -6466,7 +6716,11 @@ MediaWiki necesita soporte UTF-8 para funcionar correctamente.",
'config-memory-bad' => "'''Atención:''' O parámetro <code>memory_limit</code> do PHP é $1.
Probablemente é un valor baixo de máis.
A instalación pode fallar!",
- 'config-ctype' => "'''Fatal:''' O PHP debe compilarse co soporte para a [http://www.php.net/manual/en/ctype.installation.php extensión Ctype].",
+ 'config-ctype' => "'''Erro fatal:''' O PHP debe compilarse co soporte para a [http://www.php.net/manual/en/ctype.installation.php extensión Ctype].",
+ 'config-json' => "'''Erro fatal:''' O PHP compilouse sen o soporte de JSON.
+Debe instalar ben a extensión JSON do PHP ou a extensión [http://pecl.php.net/package/jsonc PECL jsonc] antes de instalar MediaWiki.
+* A extensión do PHP está incluída en Red Hat Enterprise Linux (CentOS) 5 e 6, mais debe activarse <code>/etc/php.ini</code> ou <code>/etc/php.d/json.ini</code>.
+* Algunhas distribucións do Linux lanzadas despois de maio de 2013 omiten a extensión do PHP, pero inclúen a extensión PECL como <code>php5-json</code> ou <code>php-pecl-jsonc</code>.",
'config-xcache' => '[http://xcache.lighttpd.net/ XCache] está instalado',
'config-apc' => '[http://www.php.net/apc APC] está instalado',
'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache] está instalado',
@@ -6475,6 +6729,8 @@ A caché de obxectos está desactivada.",
'config-mod-security' => "'''Atención:''' O seu servidor web ten o [http://modsecurity.org/ mod_security] activado. Se estivese mal configurado, pode causar problemas a MediaWiki ou calquera outro software que permita aos usuarios publicar contidos arbitrarios.
Olle a [http://modsecurity.org/documentation/ documentación do mod_security] ou póñase en contacto co soporte do seu servidor se atopa erros aleatorios.",
'config-diff3-bad' => 'GNU diff3 non se atopou.',
+ 'config-git' => 'Atopouse o software de control da versión de Git: <code>$1</code>.',
+ 'config-git-bad' => 'Non se atopou o software de control da versión de Git.',
'config-imagemagick' => 'ImageMagick atopado: <code>$1</code>.
As miniaturas de imaxes estarán dispoñibles se activa as cargas.',
'config-gd' => 'Atopouse a biblioteca gráfica GD integrada.
@@ -6571,7 +6827,6 @@ Considere poñer a base de datos nun só lugar, por exemplo en <code>/var/lib/me
'config-type-postgres' => 'PostgreSQL',
'config-type-sqlite' => 'SQLite',
'config-type-oracle' => 'Oracle',
- 'config-type-ibm_db2' => 'IBM DB2',
'config-support-info' => 'MediaWiki soporta os seguintes sistemas de bases de datos:
$1
@@ -6581,18 +6836,16 @@ Se non ve listado a continuación o sistema de base de datos que intenta usar, s
'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 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',
'config-header-oracle' => 'Configuración do Oracle',
- 'config-header-ibm_db2' => 'Configuración de IBM DB2',
'config-invalid-db-type' => 'Tipo de base de datos incorrecto',
'config-missing-db-name' => 'Debe escribir un valor "Nome da base de datos"',
'config-missing-db-host' => 'Debe escribir un valor "Servidor da base de datos"',
'config-missing-db-server-oracle' => 'Debe escribir un valor "TNS da base de datos"',
'config-invalid-db-server-oracle' => 'O TNS da base de datos, "$1", é incorrecto.
-Só pode conter letras ASCII (a-z, A-Z), números (0-9), guións baixos (_) e puntos (.).',
+Utilice só "TNS Name" ou unha cadea de texto "Easy Connect" ([http://docs.oracle.com/cd/E11882_01/network.112/e10836/naming.htm métodos de nomeamento de Oracle])',
'config-invalid-db-name' => 'O nome da base de datos, "$1", é incorrecto.
Só pode conter letras ASCII (a-z, A-Z), números (0-9), guións baixos (_) e guións (-).',
'config-invalid-db-prefix' => 'O prefixo da base de datos, "$1", é incorrecto.
@@ -6648,7 +6901,7 @@ 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 <code>LocalSettings.php</code> →',
+ 'config-regenerate' => 'Rexenerar LocalSettings.php →',
'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',
@@ -6667,6 +6920,12 @@ A conta que se especifique aquí xa debe existir.',
Se a súa instalación MySQL soporta InnoDB, recoméndase elixilo no canto de MyISAM.
Se a súa instalación MySQL non soporta InnoDB, quizais sexa boa idea realizar unha actualización.",
+ 'config-mysql-only-myisam-dep' => "'''Atención:''' MyISAM é o único motor de almacenamento para MySQL, unha combinación non recomendada para MediaWiki, porque:
+* practicamente non soporta os accesos simultáneos debido ao bloqueo de táboas
+* é máis propenso a corromperse ca outros motores
+* o código base de MediaWiki non sempre manexa o MyISAM como debera
+
+A súa instalación MySQL non soporta InnoDB, quizais sexa boa idea realizar unha actualización.",
'config-mysql-engine-help' => "'''InnoDB''' é case sempre a mellor opción, dado que soporta ben os accesos simultáneos.
'''MyISAM''' é máis rápido en instalacións de usuario único e de só lectura.
@@ -6679,7 +6938,6 @@ Isto é máis eficaz ca o modo UTF-8 de MySQL e permítelle usar o rango complet
No '''modo UTF-8''', MySQL saberá o xogo de caracteres dos seus datos e pode presentar e converter os datos de maneira axeitada,
pero non lle deixará gardar caracteres por riba do [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes plan multilingüe básico].",
- 'config-ibm_db2-low-db-pagesize' => "A súa base de datos DB2 ten un espazo de táboa cun tamaño de páxina insuficiente. O tamaño de páxina debe ser '''32k''' ou maior.",
'config-site-name' => 'Nome do wiki:',
'config-site-name-help' => 'Isto aparecerá na barra de títulos do navegador e noutros lugares.',
'config-site-name-blank' => 'Escriba o nome do sitio.',
@@ -6788,6 +7046,8 @@ O ideal é que non sexa accesible desde a web.',
'config-logo-help' => 'A aparencia de MediaWiki por defecto inclúe espazo para un logo de 135x160 píxeles por riba do menú lateral.
Cargue unha imaxe do tamaño axeitado e introduza o enderezo URL aquí.
+Pode utilizar <code>$wgStylePath</code> ou <code>$wgScriptPath</code> se o seu logo está relacionado con esas rutas.
+
Se non quere un logo, deixe esta caixa en branco.',
'config-instantcommons' => 'Activar Instant Commons',
'config-instantcommons-help' => '[//www.mediawiki.org/wiki/InstantCommons InstantCommons] é unha característica que permite aos wikis usar imaxes, sons e outros ficheiros multimedia atopados no sitio da [//commons.wikimedia.org/wiki/Portada_galega Wikimedia Commons].
@@ -6880,7 +7140,10 @@ $3
Cando faga todo isto, xa poderá '''[$2 entrar no seu wiki]'''.",
'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?',
+ 'config-nofile' => 'Non se puido atopar o ficheiro "$1". Se cadra, foi borrado.',
+ 'config-extension-link' => 'Sabía que o seu wiki soporta [//www.mediawiki.org/wiki/Manual:Extensions extensións]?
+
+Pode explorar as [//www.mediawiki.org/wiki/Category:Extensions_by_category extensións por categoría] ou a [//www.mediawiki.org/wiki/Extension_Matrix matriz de extensións] para ollar a lista completa de extensións.',
'mainpagetext' => "'''MediaWiki instalouse correctamente.'''",
'mainpagedocfooter' => 'Consulte a [//meta.wikimedia.org/wiki/Help:Contents guía de usuario] para obter máis información sobre como usar o software wiki.
@@ -6910,7 +7173,7 @@ $messages['grc'] = array(
== ἌÏξασθε ==
* [//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
);
/** Swiss German (Alemannisch)
@@ -7025,6 +7288,7 @@ Miniaturaasichte vu Bilder sin megli, sobald s Uffelade vu Dateie aktiviert isch
);
/** Gujarati (ગà«àªœàª°àª¾àª¤à«€)
+ * @author Ashok modhvadia
* @author Dineshjk
*/
$messages['gu'] = array(
@@ -7034,7 +7298,8 @@ $messages['gu'] = 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 Localise MediaWiki for your language]',
);
/** Manx (Gaelg)
@@ -7053,7 +7318,7 @@ $messages['hak'] = array(
* [//www.mediawiki.org/wiki/Manual:Configuration_settings MediaWiki Phi-chṳ sat-thin chhîn-tân]
* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki Phìn-sòng mun-thì kié-tap]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki fat-phu email chhîn-tân]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki fat-phu email chhîn-tân]', # Fuzzy
);
/** Hawaiian (Hawai`i)
@@ -7289,7 +7554,6 @@ $1
כד××™ לשקול ×œ×©×™× ×ת מסד ×”× ×ª×•× ×™× ×‘×ž×§×•× ×חר לגמרי, למשל ב־<code dir="ltr">/var/lib/mediawiki/yourwik</code>.',
'config-oracle-def-ts' => 'מרחב טבל×ות לפי בררת מחדל (default tablespace):',
'config-oracle-temp-ts' => 'מרחב טבל×ות זמני (temporary tablespace):',
- 'config-type-ibm_db2' => 'IBM DB2',
'config-support-info' => 'מדיה־ויקי תומכת במערכות מסדי ×”× ×ª×•× ×™× ×”×‘×ות:
$1
@@ -7299,12 +7563,10 @@ $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 ×”×•× ×ž×¡×“ × ×ª×•× ×™× ×ž×¡×—×¨×™ ×רגוני. ([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',
'config-header-oracle' => 'הגדרות Oracle',
- 'config-header-ibm_db2' => 'תצורת IBM DB2',
'config-invalid-db-type' => 'סוג מסד ×”× ×ª×•× ×™× ×©×’×•×™',
'config-missing-db-name' => 'עליך להזין ערך עבור "×©× ×ž×¡×“ הנתוני×"',
'config-missing-db-host' => 'יש להכניס ערך לשדה "שרת מסד הנתוני×"',
@@ -7366,7 +7628,7 @@ chmod a+w $3</pre></div>',
'config-upgrade-done-no-regenerate' => 'השדרוג הושל×.
עכשיו ×פשר [$1 להתחיל להשתמש בוויקי שלכ×].',
- 'config-regenerate' => 'לחולל מחדש ×ת <code>LocalSettings.php</code> â†',
+ 'config-regenerate' => 'לחולל מחדש ×ת LocalSettings.php â†',
'config-show-table-status' => 'ש×ילתת <code>SHOW TABLE STATUS</code> נכשלה!',
'config-unknown-collation' => "'''×זהרה:''' מסד ×”× ×ª×•× ×™× ×ž×©×ª×ž×© בשיטת מיון ש××™× ×” מוּכּרת.",
'config-db-web-account' => 'חשבון במסד ×”× ×ª×•× ×™× ×œ×’×™×©×” מהרשת',
@@ -7396,7 +7658,6 @@ chmod a+w $3</pre></div>',
×–×” יעיל יותר ממצב UTF-8 של MySQL ומ×פשר ×œ×›× ×œ×”×©×ª×ž×© בכל הטווח של תווי יוניקוד.
ב'''מצב UTF-8'''&rlm; (UTF-8 mode)&rlm; MySQL יֵדַע מה קבוצת ×”×ª×•×•×™× (character set) של הטקסט ×©×œ×›× ×•×™×¦×™×’ וימיר ×ותו בהת××, ×בל ×œ× ×™×פשר ×œ×›× ×œ×©×ž×•×¨ ×ª×•×•×™× ×©××™× × × ×ž×¦××™× ×‘×˜×•×•×— הרב־לשוני הבסיסי ([//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' => '× × ×œ×”×–×™×Ÿ ×©× ×œ×תר.',
@@ -7505,7 +7766,7 @@ chmod a+w $3</pre></div>',
'config-logo-help' => 'המר××” ההתחלתי של מדיה־ויקי מכיל ×ž×§×•× ×œ×¡×ž×œ של 135 על 160 ×¤×™×§×¡×œ×™× ×‘×¤×™× ×” העליונה מעל תפריט הצד.
יש להעלות תמונה בגודל מת××™× ×•×œ×”×›× ×™×¡ ×ת הכתובת ×›×ן.
-×× ××™× ×›× ×¨×•×¦×™× ×¡×ž×œ, הש×ירו ×ת התיבה ×”×–×ת ריקה.',
+×× ××™× ×›× ×¨×•×¦×™× ×¡×ž×œ, הש×ירו ×ת התיבה ×”×–×ת ריקה.', # Fuzzy
'config-instantcommons' => 'להפעיל ×ת Instant Commons',
'config-instantcommons-help' => '[//www.mediawiki.org/wiki/InstantCommons Instant Commons] ×”×™× ×ª×›×•× ×” שמ×פשרת ל×תרי ויקי להשתמש בתמונות, ×‘×¦×œ×™×œ×™× ×•×‘×ž×“×™×” ×חרת שנמצ×ת ב×תר [//commons.wikimedia.org/ ויקישיתוף] (Wikimedia Commons).
כדי לעשות ×ת ×–×”, מדיה־ויקי צריך לגשת ל×ינטרנט.
@@ -7617,7 +7878,7 @@ $messages['hi'] = 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
);
/** Fiji Hindi (Latin script) (Fiji Hindi)
@@ -7630,7 +7891,7 @@ $messages['hif-latn'] = array(
== 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]", # Fuzzy
);
/** Hiligaynon (Ilonggo)
@@ -7643,7 +7904,7 @@ $messages['hil'] = array(
== Pag-umpisa ==
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lista sang mga konpigorasyon sang pagkay-o]
* [//www.mediawiki.org/wiki/Manual:FAQ Mga Masami Pamangkoton sa MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista sang mga ginapadal-an sang sulat kon may paguha-on nga MediaWiki]",
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista sang mga ginapadal-an sang sulat kon may paguha-on nga MediaWiki]", # Fuzzy
);
/** Croatian (hrvatski)
@@ -7651,7 +7912,7 @@ $messages['hil'] = array(
$messages['hr'] = array(
'mainpagetext' => "'''Softver MediaWiki je uspješno instaliran.'''",
'mainpagedocfooter' => 'Pogledajte [//meta.wikimedia.org/wiki/MediaWiki_localisation dokumentaciju o prilagodbi suÄelja]
-i [//meta.wikimedia.org/wiki/MediaWiki_User%27s_Guide VodiÄ za suradnike] za pomoć pri uporabi i podeÅ¡avanju.',
+i [//meta.wikimedia.org/wiki/MediaWiki_User%27s_Guide VodiÄ za suradnike] za pomoć pri uporabi i podeÅ¡avanju.', # Fuzzy
);
/** Upper Sorbian (hornjoserbsce)
@@ -7843,7 +8104,7 @@ 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' => '<code>LocalSettings.php</code> znowa wutworić →',
+ 'config-regenerate' => 'LocalSettings.php 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',
@@ -8012,7 +8273,7 @@ $messages['ht'] = array(
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lis paramèt yo pou konfigirasyon]
* [//www.mediawiki.org/wiki/Manyèl:FAQ MediaWiki FAQ]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lis diskisyon ki parèt sou MediaWiki]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lis diskisyon ki parèt sou MediaWiki]', # Fuzzy
);
/** Hungarian (magyar)
@@ -8230,7 +8491,6 @@ A telepítő készít egy <code>.htaccess</code> fájlt az adatbázis mellé, az
Fontold meg az adatbázis más helyre történő elhelyezését, például a <code>/var/lib/mediawiki/tewikid</code> könyvtárba.",
'config-oracle-def-ts' => 'Alapértelmezett táblatér:',
'config-oracle-temp-ts' => 'Ideiglenes táblatér:',
- 'config-type-ibm_db2' => 'IBM DB2',
'config-support-info' => 'A MediaWiki a következő adatbázisrendszereket támogatja:
$1
@@ -8240,12 +8500,10 @@ 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.', # 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',
'config-header-oracle' => 'Oracle-beállítások',
- 'config-header-ibm_db2' => 'IBM DB2-beállítások',
'config-invalid-db-type' => 'Érvénytelen adatbázistípus',
'config-missing-db-name' => 'Meg kell adnod az „Adatbázisnév†értékét',
'config-missing-db-host' => 'Meg kell adnod az „Adatbázis hosztneve†értékét',
@@ -8307,7 +8565,7 @@ 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' => '<code>LocalSettings.php</code> elkészítése újra →',
+ 'config-regenerate' => 'LocalSettings.php 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',
@@ -8336,7 +8594,6 @@ A '''MyISAM''' gyorsabb megoldás lehet egyfelhasználós vagy csak olvasható k
Ez sokkal hatékonyabb a MySQL UTF-8-as módjánál, és lehetővé teszi a teljes Unicode-karakterkészlet használatát.
'''UTF-8-as módban''' a MySQL tudni fogja,hogy az adatok milyen karakterkészlettel rendelkeznek, és megfelelően átalakítja őket, azonban nem tárolhatóak olyan karakterek, melyek a [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Basic Multilingual Plane] felett vannak.",
- 'config-ibm_db2-low-db-pagesize' => "A DB2 adatbázisodnak alapértelmezett táblatere van elégtelen lapmérettel. A lapméretnek legalább '''32K'''-nak kell lennie.",
'config-site-name' => 'A wiki neve:',
'config-site-name-help' => 'A böngésző címsorában és még számos más helyen jelenik meg.',
'config-site-name-blank' => 'Add meg az oldal nevét.',
@@ -8443,7 +8700,7 @@ Normális esetben ennek nem szabad elérhetőnek lennie az internetről.',
'config-logo-help' => 'A MediaWiki alapértelmezett felülete helyet ad egy 135×160 pixeles logónak a bal felső sarokban.
Tölts fel egy megfelelő méretű képet, majd írd be ide az URL-címét!
-Ha nem szeretnél logót használni, egyszerűen hagyd üresen a mezőt.',
+Ha nem szeretnél logót használni, egyszerűen hagyd üresen a mezőt.', # Fuzzy
'config-instantcommons' => 'Instant Commons engedélyezése',
'config-instantcommons-help' => 'Az [//www.mediawiki.org/wiki/InstantCommons Instant Commons] lehetővé teszi, hogy a wikin használhassák a [//commons.wikimedia.org/ Wikimedia Commons] oldalon található képeket, hangokat és más médiafájlokat.
A használatához a MediaWikinek internethozzáférésre van szüksége.
@@ -8628,7 +8885,7 @@ $messages['hy'] = array(
* [//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
);
/** Interlingua (interlingua)
@@ -8787,7 +9044,9 @@ 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 <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-suhosin-max-value-length' => 'Suhosin es installate e limita parametro <code>length</code> de GET a $1 bytes.
+Le componente ResourceLoader de MediaWiki va contornar iste limite, ma isto prejudicara le rendimento.
+Si possibile, tu deberea mitter <code>suhosin.get.max_value_length</code> a 1024 o superior in <code>php.ini</code>, e mitter <code>$wgResourceLoaderMaxQueryLength</code> al mesme valor in <code>LocalSettings.php</code>.',
'config-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.
@@ -8861,7 +9120,6 @@ Considera poner le base de datos in un loco completemente differente, per exempl
'config-type-postgres' => 'PostgreSQL',
'config-type-sqlite' => 'SQLite',
'config-type-oracle' => 'Oracle',
- 'config-type-ibm_db2' => 'IBM DB2',
'config-support-info' => 'MediaWiki supporta le sequente systemas de base de datos:
$1
@@ -8871,18 +9129,16 @@ 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.', # Fuzzy
'config-header-mysql' => 'Configuration de MySQL',
'config-header-postgres' => 'Configuration de PostgreSQL',
'config-header-sqlite' => 'Configuration de SQLite',
'config-header-oracle' => 'Configuration de Oracle',
- 'config-header-ibm_db2' => 'Configurationes pro IBM DB2',
'config-invalid-db-type' => 'Typo de base de datos invalide',
'config-missing-db-name' => 'Tu debe entrar un valor pro "Nomine de base de datos"',
'config-missing-db-host' => 'Tu debe entrar un valor pro "Host del base de datos"',
'config-missing-db-server-oracle' => 'You must enter a value for "TNS del base de datos"',
'config-invalid-db-server-oracle' => 'TNS de base de datos "$1" invalide.
-Usa solmente litteras ASCII (a-z, A-Z), numeros (0-9), characteres de sublineamento (_) e punctos (.).',
+Usa o "TNS Name" o un catena "Easy Connect". ([http://docs.oracle.com/cd/E11882_01/network.112/e10836/naming.htm Methodos de nomenclatura de Oracle])',
'config-invalid-db-name' => 'Nomine de base de datos "$1" invalide.
Usa solmente litteras ASCII (a-z, A-Z), numeros (0-9), characteres de sublineamento (_) e tractos de union (-).',
'config-invalid-db-prefix' => 'Prefixo de base de datos "$1" invalide.
@@ -8938,7 +9194,7 @@ 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 <code>LocalSettings.php</code> →',
+ 'config-regenerate' => 'Regenerar LocalSettings.php →',
'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',
@@ -8968,7 +9224,6 @@ Le bases de datos MyISAM tende a esser corrumpite plus frequentemente que le bas
Isto es plus efficiente que le modo UTF-8 de MySQL, e permitte usar le rango complete de characteres Unicode.
In '''modo UTF-8''', MySQL cognoscera le codification de characteres usate pro tu dats, e pote presentar e converter lo appropriatemente, ma illo non permittera immagazinar characteres supra le [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Plano Multilingue Basic].",
- 'config-ibm_db2-low-db-pagesize' => 'Tu base de datos DB2 ha un "tablespace" (spatio de tabella) predefinite con un "pagesize" (dimension de pagina) insufficiente. Le "pagesize" debe esser \'\'\'32K\'\'\' o plus.',
'config-site-name' => 'Nomine del wiki:',
'config-site-name-help' => 'Isto apparera in le barra de titulo del navigator e in varie altere locos.',
'config-site-name-blank' => 'Entra un nomine de sito.',
@@ -9011,7 +9266,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', # Fuzzy
+ 'config-profile-wiki' => 'Wiki aperte',
'config-profile-no-anon' => 'Creation de conto obligatori',
'config-profile-fishbowl' => 'Modificatores autorisate solmente',
'config-profile-private' => 'Wiki private',
@@ -9021,13 +9276,13 @@ In MediaWiki, il es facile revider le modificationes recente, e reverter omne da
Nonobstante, multes ha trovate MediaWiki utile in un grande varietate de rolos, e alcun vices il non es facile convincer omnes del beneficios del principio wiki.
Dunque, a te le option.
-Un '''{{int:config-profile-wiki}}''' permitte a omnes de modificar, sin mesmo aperir un session.
+Le modello '''{{int:config-profile-wiki}}''' permitte a omnes de modificar, sin mesmo aperir un session.
Un wiki con '''{{int:config-profile-no-anon}}''' attribue additional responsabilitate, ma pote dissuader contributores occasional.
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].", # Fuzzy
+Configurationes de derectos de usator plus complexe es disponibile post installation, vide le [//www.mediawiki.org/wiki/Manual:User_rights pertinente section del manual].",
'config-license' => 'Copyright e licentia:',
'config-license-none' => 'Nulle licentia in pede de paginas',
'config-license-cc-by-sa' => 'Creative Commons Attribution Share Alike',
@@ -9078,6 +9333,8 @@ Idealmente, isto non debe esser accessibile ab le web.',
'config-logo-help' => 'Le apparentia predefinite de MediaWiki include spatio pro un logotypo de 135×160 pixels supra le menu del barra lateral.
Incarga un imagine con le dimensiones appropriate, e entra le URL hic.
+Tu pote usar <code>$wgStylePath</code> o <code>$wgScriptPath</code> si le loco de tu logotypo es relative a iste camminos.
+
Si tu non vole un logotypo, lassa iste quadro vacue.',
'config-instantcommons' => 'Activar "Instant Commons"',
'config-instantcommons-help' => '[//www.mediawiki.org/wiki/InstantCommons Instant Commons] es un function que permitte a wikis de usar imagines, sonos e altere multimedia trovate in le sito [//commons.wikimedia.org/ Wikimedia Commons].
@@ -9112,7 +9369,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".', # Fuzzy
+Pro facer alterationes, clicca sur "{{int:config-back}}".',
'config-install-step-done' => 'finite',
'config-install-step-failed' => 'fallite',
'config-install-extensions' => 'Include le extensiones',
@@ -9178,7 +9435,8 @@ 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]', # Fuzzy
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista de diffusion pro annuncios de nove versiones de MediaWiki]
+* [//www.mediawiki.org/wiki/Localisation#Translation_resources Traducer MediaWiki in tu lingua]',
);
/** Indonesian (Bahasa Indonesia)
@@ -9476,7 +9734,7 @@ 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 <code>LocalSettings.php</code> →',
+ 'config-regenerate' => 'Regenerasi LocalSettings.php →',
'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',
@@ -9604,7 +9862,7 @@ Idealnya, direktori ini tidak boleh dapat diakses dari web.',
'config-logo-help' => 'Kulit bawaan MediaWiki memberikan ruang untuk logo berukuran 135x160 piksel di atas menu bilah samping.
Unggah gambar dengan ukuran yang sesuai, lalu masukkan URL di sini.
-Jika Anda tidak ingin menyertakan logo, biarkan kotak ini kosong.',
+Jika Anda tidak ingin menyertakan logo, biarkan kotak ini kosong.', # Fuzzy
'config-instantcommons' => 'Aktifkan Instant Commons',
'config-instantcommons-help' => '[//www.mediawiki.org/wiki/InstantCommons Instant Commons] adalah fitur yang memungkinkan wiki untuk menggunakan gambar, suara, dan media lain dari [//commons.wikimedia.org/ Wikimedia Commons].
Untuk melakukannya, MediaWiki memerlukan akses ke Internet.
@@ -9742,7 +10000,7 @@ $messages['io'] = array(
== Komencar ==
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Listo di ''Configuration setting'']
* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki OQQ]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki nova versioni posto-listo]",
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki nova versioni posto-listo]", # Fuzzy
);
/** Icelandic (íslenska)
@@ -9755,7 +10013,7 @@ $messages['is'] = array(
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Listi yfir uppsetningarstillingar]
* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki Algengar spurningar MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Póstlisti MediaWiki-útgáfa]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Póstlisti MediaWiki-útgáfa]', # Fuzzy
);
/** Italian (italiano)
@@ -9817,8 +10075,8 @@ Controlla il tuo file php.ini ed assicurati che <code>session.save_path</code> Ã
'config-help-restart' => 'Vuoi cancellare tutti i dati salvati che hai inserito e riavviare il processo di installazione?',
'config-restart' => 'Sì, riavvia',
'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.",
+Saranno eseguiti controlli di base per vedere se questo ambiente è adatto per l'installazione di MediaWiki.
+Ricordati di includere queste informazioni se chiedi assistenza su come completare l'installazione.",
'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]
@@ -9854,6 +10112,8 @@ L'installazione potrebbe non riuscire!",
'config-no-cache' => "'''Attenzione:''' [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] o [http://www.iis.net/download/WinCacheForPhp WinCache] non sono stati trovati.
La caching degli oggetti non è attivata.",
'config-diff3-bad' => 'GNU diff3 non trovato.',
+ 'config-git' => 'Trovato software di controllo della versione Git: <code>$1</code>.',
+ 'config-git-bad' => 'Software di controllo della versione Git non trovato.',
'config-imagemagick' => 'Trovato ImageMagick: <code>$1</code>.
Le miniature delle immagini saranno presenti se gli upload vengono abilitati.',
'config-gd' => 'Trovata la GD Graphics Library built-in.
@@ -9865,16 +10125,50 @@ Installazione interrotta.",
'config-no-cli-uri' => "'''Attenzione''': --scriptpath non specificato, si utilizza il valore predefinito: <code>$1</code>.",
'config-using-server' => 'Nome server in uso "<nowiki>$1</nowiki>".',
'config-using-uri' => 'URL del server in uso "<nowiki>$1$2</nowiki>".',
+ 'config-brokenlibxml' => 'Il tuo sistema ha una combinazione di versioni di PHP e libxml2 che è difettosa e che può provocare un danneggiamento non visibile di dati in MediaWiki ed in altre applicazioni per il web.
+Aggiorna a PHP 5.2.9 o successivo, ed a libxml2 2.7.3 o successivo ([//bugs.php.net/bug.php?id=45996 il bug è studiato dal lato PHP]).
+Installazione interrotta.',
+ 'config-using531' => 'MediaWiki non può essere usato con il PHP $1 a causa di un bug che coinvolge i parametri di riferimento a <code>__call()</code>.
+Aggiorna a PHP 5.3.2 o superiore, o fai un downgrade tornando a PHP 5.3.0 per risolvere il problema.
+Installazione interrotta.',
'config-db-type' => 'Tipo di database:',
+ 'config-db-host' => 'Host del database:',
+ 'config-db-host-help' => 'Se il server del tuo database è su un server diverso, immetti qui il nome dell\'host o il suo indirizzo IP.
+
+Se stai utilizzando un web hosting condiviso, il tuo hosting provider dovrebbe fornirti il nome host corretto nella sua documentazione.
+
+Se stai installando su un server Windows con uso di MySQL, l\'uso di "localhost" potrebbe non funzionare correttamente come nome del server. In caso di problemi, prova a impostare "127.0.0.1" come indirizzo IP locale.
+
+Se usi PostgreSQL, lascia questo campo vuoto per consentire di connettersi tramite un socket Unix.',
+ 'config-db-host-oracle' => 'TNS del database:',
'config-db-wiki-settings' => 'Identifica questo wiki',
'config-db-name' => 'Nome del database:',
+ 'config-db-name-help' => 'Scegli un nome che identifica il tuo wiki.
+Non deve contenere spazi.
+
+Se utilizzi un web hosting condiviso, il tuo hosting provider o ti fornisce uno specifico nome di database da utilizzare, oppure ti consentirà di creare il database tramite un pannello di controllo.',
'config-db-name-oracle' => 'Schema del database:',
+ 'config-db-install-account' => "Account utente per l'installazione",
'config-db-username' => 'Nome utente del database:',
+ 'config-db-password' => 'Password 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-username' => "Inserisci il nome utente che verrà utilizzato per connettersi al database durante il processo di installazione.
+Questo non è il nome utente dell'account MediaWiki; ma quello per il tuo database.",
+ 'config-db-install-password' => "Inserisci la password che verrà utilizzato per connettersi al database durante il processo di installazione.
+Questa non è la password dell'account MediaWiki; ma quella per il tuo database.",
'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-account-lock' => 'Utilizza lo stesso nome utente e password durante il normale funzionamento',
+ 'config-db-wiki-account' => 'Account utente per il normale funzionamento',
+ 'config-db-wiki-help' => "Inserisci il nome utente e la password che verrà utilizzato per connettersi al database durante il normale funzionamento del wiki.
+Se l'account non esiste, e l'account di installazione dispone di privilegi sufficienti, verrà creato con privilegi minimi necessari per operare sul wiki.",
'config-db-prefix' => 'Prefisso tabella del database:',
+ 'config-db-prefix-help' => "Se hai bisogno di condividere un database tra più wiki, o tra MediaWiki e un'altra applicazione web, puoi scegliere di aggiungere un prefisso a tutti i nomi di tabella, per evitare conflitti.
+Non utilizzare spazi.
+
+Solitamente, questo campo viene lasciato vuoto.",
'config-db-charset' => 'Set di caratteri del database',
+ 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 binario',
'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
'config-charset-mysql4' => 'MySQL 4.0 con compatibilità UTF-8',
'config-mysql-old' => 'MySQL $1 o una versione successiva è necessaria, rilevata la $2.',
@@ -9884,25 +10178,77 @@ Anche se può essere possibile creare utenti senza password, questo non è sicur
Da cambiare solamente se si è sicuri di averne bisogno.',
'config-pg-test-error' => "Impossibile connettersi al database '''$1''': $2",
'config-sqlite-dir' => 'Directory data di SQLite:',
- 'config-type-ibm_db2' => 'IBM DB2',
+ 'config-oracle-def-ts' => 'Tablespace di default:',
+ 'config-oracle-temp-ts' => 'Tablespace temporaneo:',
+ 'config-support-info' => 'MediaWiki supporta i seguenti sistemi di database:
+
+$1
+
+Se fra quelli elencati qui sotto non vedi il sistema di database che vorresti utilizzare, seguire le istruzioni linkate sopra per abilitare il supporto.',
+ 'config-support-mysql' => '* $1 è la configurazione preferibile per MediaWiki ed è quella meglio supportata ([http://www.php.net/manual/en/mysql.installation.php come compilare PHP con supporto MySQL])',
'config-header-mysql' => 'Impostazioni MySQL',
'config-header-postgres' => 'Impostazioni PostgreSQL',
'config-header-sqlite' => 'Impostazioni SQLite',
'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-missing-db-host' => 'È necessario immettere un valore per "Host del database"',
+ 'config-missing-db-server-oracle' => 'È necessario immettere un valore per "TNS del database"',
+ 'config-invalid-db-name' => 'Nome di database "$1" non valido.
+Utilizza soltanto caratteri ASCII come lettere (a-z, A-Z), numeri (0-9), sottolineatura (_) e trattini (-).',
+ 'config-invalid-db-prefix' => 'Prefisso database "$1" non valido.
+Utilizza soltanto caratteri ASCII come lettere (a-z, A-Z), numeri (0-9), sottolineatura (_) e trattini (-).',
+ 'config-connection-error' => '$1.
+
+Controlla host, nome utente e password e prova ancora.',
+ 'config-db-sys-create-oracle' => "Il programma di installazione supporta solo l'utilizzo di un account SYSDBA per la creazione di un nuovo account.",
+ 'config-db-sys-user-exists-oracle' => 'L\'account utente "$1" esiste già. SYSDBA può essere usato solo per la creazione di un nuovo account!',
+ 'config-postgres-old' => 'PostgreSQL $1 o una versione successiva è necessaria, rilevata la $2.',
+ 'config-sqlite-name-help' => 'Scegli un nome che identifichi il tuo wiki.
+Non utilizzare spazi o trattini.
+Questo servirà per il nome del file di dati SQLite.',
+ 'config-sqlite-mkdir-error' => 'Errore durante la creazione della directory dati "$1".
+Controlla la posizione e riprova.',
+ 'config-sqlite-dir-unwritable' => 'Impossibile scrivere nella directory "$1".
+Modifica le autorizzazioni in modo che il webserver possa scrivere in essa e riprova.',
+ 'config-sqlite-connection-error' => '$1.
+
+Controlla la directory dati e il nome del database qui sotto, poi riprova.',
+ 'config-sqlite-readonly' => 'Il file <code>$1</code> non è scrivibile.',
+ 'config-sqlite-cant-create-db' => 'Impossibile creare il file di database <code>$1</code> .',
+ 'config-sqlite-fts3-downgrade' => 'Il PHP è mancante del supporto FTS3, declassamento tabelle in corso',
+ 'config-can-upgrade' => "Ci sono tabelle di MediaWiki in questo database.
+Per aggiornarle a MediaWiki $1, fai clic su '''continua'''.",
+ 'config-upgrade-done' => "Aggiornamento completo.
+
+Puoi [$1 iniziare ad usare il tuo wiki].
+
+Se vuoi rigenerare il tuo file <code>LocalSettings.php</code>, clicca sul pulsante sotto. Questa operazione '''non è raccomandata''', a meno che non hai problemi con il tuo wiki.",
+ 'config-upgrade-done-no-regenerate' => 'Aggiornamento completo.
+
+Puoi [$1 iniziare ad usare il tuo wiki].',
+ 'config-regenerate' => 'Rigenera LocalSettings.php →',
+ 'config-show-table-status' => 'La query <code>SHOW TABLE STATUS</code> è fallita!',
+ 'config-unknown-collation' => "'''Attenzione:''' il database utilizza regole di confronto non riconosciute.",
'config-db-web-account' => "Account del database per l'accesso web",
+ 'config-db-web-help' => 'Seleziona il nome utente e la password che il server web utilizzerà per connettersi al server di database, durante il normale funzionamento del wiki.',
+ 'config-db-web-account-same' => "Utilizza lo stesso account dell'installazione",
'config-db-web-create' => "Crea l'account se non esiste già",
+ 'config-db-web-no-create-privs' => "L'account usato per l'installazione non dispone dei privilegi necessari per creare un altro account.
+L'account indicato qui deve già esistere.",
'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-site-name' => 'Nome del wiki:',
+ 'config-site-name-help' => 'Questo verrà visualizzato nella barra del titolo del browser e in vari altri posti.',
+ 'config-site-name-blank' => 'Inserisci il nome del sito.',
+ 'config-project-namespace' => 'Namespace del progetto:',
'config-ns-generic' => 'Progetto',
- 'config-ns-site-name' => 'Stesso nome wiki: $1',
+ 'config-ns-site-name' => 'Stesso nome del wiki: $1',
+ 'config-ns-other' => 'Altro (specificare)',
'config-ns-other-default' => 'MyWiki',
'config-admin-box' => 'Account amministratore',
'config-admin-name' => 'Tuo nome:',
@@ -9925,7 +10271,9 @@ Inserire un indirizzo email se si desidera effettuare l'iscrizione alla mailing
'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-optional-skip' => 'Sono già stanco, installa solo il wiki.',
+ 'config-profile' => 'Profilo dei diritti utente:',
+ 'config-profile-wiki' => 'Wiki aperto',
'config-profile-no-anon' => 'Creazione utenza obbligatoria',
'config-profile-fishbowl' => 'Solo editori autorizzati',
'config-profile-private' => 'Wiki privata',
@@ -9944,8 +10292,13 @@ Se vuoi usare testi da Wikipedia, o desideri che Wikipedia possa essere in grado
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-enable-email' => 'Abilita la posta elettronica in uscita',
'config-email-user' => 'Abilita invio email fra utenti',
+ 'config-email-usertalk' => 'Abilita le notifiche per le pagine di discussione utente',
+ 'config-email-watchlist' => 'Abilita le notifiche per gli osservati speciali',
'config-email-auth' => 'Abilita autenticazione via email',
+ 'config-email-sender' => 'Indirizzo email di ritorno:',
+ 'config-upload-settings' => 'Caricamenti di immagini e file',
'config-upload-enable' => 'Consentire il caricamento di file',
'config-upload-deleted' => 'Directory per i file cancellati:',
'config-logo' => 'URL del logo:',
@@ -9953,16 +10306,20 @@ In precedenza Wikipedia ha utilizzato la GNU Free Documentation License. La GFDL
'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-memcached-servers' => 'Server di memcached:',
'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-memcache-badport' => 'I numeri di porta per memcached dovrebbero essere tra $1 e $2.',
'config-extensions' => 'Estensioni',
'config-install-step-done' => 'fatto',
'config-install-step-failed' => 'non riuscito',
+ 'config-install-database' => 'Configurazione database',
'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' => 'Creazione tabelle',
'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>.',
@@ -9972,6 +10329,7 @@ In precedenza Wikipedia ha utilizzato la GNU Free Documentation License. La GFDL
'config-install-subscribe-fail' => 'Impossibile sottoscrivere mediawiki-announce: $1',
'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-extension-tables' => 'Creazione delle tabelle per le estensioni attivate',
'config-install-mainpage-failed' => 'Impossibile inserire la pagina principale: $1',
'config-download-localsettings' => 'Scarica <code>LocalSettings.php</code>',
'config-help' => 'aiuto',
@@ -9990,6 +10348,7 @@ I seguenti collegamenti sono in lingua inglese:
/** Japanese (日本語)
* @author Aphaia
+ * @author Fryed-peach
* @author Iwai.masaharu
* @author Mizusumashi
* @author Ninomy
@@ -9998,6 +10357,7 @@ I seguenti collegamenti sono in lingua inglese:
* @author Whym
* @author Yanajin66
* @author é’å­å®ˆæ­Œ
+ * @author ì•„ë¼
*/
$messages['ja'] = array(
'config-desc' => 'MediaWiki ã®ã‚¤ãƒ³ã‚¹ãƒˆãƒ¼ãƒ©ãƒ¼',
@@ -10052,8 +10412,8 @@ php.ini 内㧠<code>session.save_path</code> ãŒé©åˆ‡ãªãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã«è
'config-help-restart' => '入力ã—ãŸä¿å­˜ãƒ‡ãƒ¼ã‚¿ã‚’ã™ã¹ã¦æ¶ˆåŽ»ã—ã¦ã€ã‚¤ãƒ³ã‚¹ãƒˆãƒ¼ãƒ«ä½œæ¥­ã‚’å†èµ·å‹•ã—ã¾ã™ã‹?',
'config-restart' => 'ã¯ã„ã€å†èµ·å‹•ã—ã¾ã™',
'config-welcome' => '=== 環境ã®ç¢ºèª ===
-基本的ãªç¢ºèªã§ã¯ã€ç¾åœ¨ã®ç’°å¢ƒãŒMediaWikiã®ã‚¤ãƒ³ã‚¹ãƒˆãƒ¼ãƒ«ã«é©ã—ã¦ã„ã‚‹ã‹ã‚’確èªã—ã¾ã™ã€‚
-インストール中ã«åŠ©ã‘ãŒå¿…è¦ã«ãªã£ãŸå ´åˆã¯ã€ã“ã®ç¢ºèªçµæžœã‚’æä¾›ã—ã¦ãã ã•ã„。',
+基本的ãªç¢ºèªã§ã¯ã€ç¾åœ¨ã®ç’°å¢ƒãŒ MediaWiki ã®ã‚¤ãƒ³ã‚¹ãƒˆãƒ¼ãƒ«ã«é©ã—ã¦ã„ã‚‹ã‹ã‚’確èªã—ã¾ã™ã€‚
+インストール方法ã«ã¤ã„ã¦åŠ©ã‘ãŒå¿…è¦ã«ãªã£ãŸå ´åˆã¯ã€å¿…ãšã“ã®ç¢ºèªçµæžœã‚’æ·»ãˆã¦ãã ã•ã„。',
'config-copyright' => '=== 著作権ãŠã‚ˆã³è¦ç´„ ===
$1
@@ -10091,6 +10451,7 @@ Unicode ã‚’å°‘ã—ã§ã‚‚利用ã™ã‚‹å¯èƒ½æ€§ãŒã‚ã‚‹å ´åˆã¯ã€[//www.mediawik
共有サーãƒãƒ¼ã‚’使用ã—ã¦ã„ã‚‹å ´åˆã¯ã€é©åˆ‡ãªãƒ‡ãƒ¼ã‚¿ãƒ™ãƒ¼ã‚¹ ドライãƒãƒ¼ã®ã‚¤ãƒ³ã‚¹ãƒˆãƒ¼ãƒ«ã‚’ã€ã‚µãƒ¼ãƒãƒ¼ã®ç®¡ç†è€…ã«ä¾é ¼ã—ã¦ãã ã•ã„。
PHP を自分ã§ã‚³ãƒ³ãƒ‘イルã—ãŸå ´åˆã¯ã€ä¾‹ãˆã° <code>./configure --with-mysql</code> を実行ã—ã¦ã€ãƒ‡ãƒ¼ã‚¿ãƒ™ãƒ¼ã‚¹ クライアントを使用ã§ãるよã†ã«å†è¨­å®šã—ã¦ãã ã•ã„。
Debian ã¾ãŸã¯ Ubuntu ã®ãƒ‘ッケージã‹ã‚‰ PHP をインストールã—ãŸå ´åˆã¯ã€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> オプションãŒæœ‰åŠ¹ã«ãªã£ã¦ã„ã¾ã™ã€‚'''
'''å¯èƒ½ãªã‚‰ç„¡åŠ¹åŒ–ã—ã¦ãã ã•ã„。'''
@@ -10126,6 +10487,8 @@ MediaWiki ã‚’æ­£ã—ã動作ã•ã›ã‚‹ã«ã¯ã€UTF-8 対応ãŒå¿…è¦ã§ã™ã€‚",
'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-git' => 'ãƒãƒ¼ã‚¸ãƒ§ãƒ³ç®¡ç†ã‚½ãƒ•ãƒˆã‚¦ã‚§ã‚¢ Git ãŒè¦‹ã¤ã‹ã‚Šã¾ã—ãŸ: <code>$1</code>',
+ 'config-git-bad' => 'ãƒãƒ¼ã‚¸ãƒ§ãƒ³ç®¡ç†ã‚½ãƒ•ãƒˆã‚¦ã‚§ã‚¢ Git ãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“。',
'config-imagemagick' => 'ImageMagickãŒè¦‹ã¤ã‹ã‚Šã¾ã—ãŸ: <code>$1</code>。
アップロードãŒæœ‰åŠ¹ã§ã‚ã‚Œã°ã€ç”»åƒã®ã‚µãƒ ãƒã‚¤ãƒ«ã‚’利用ã§ãã¾ã™ã€‚',
'config-gd' => 'GDç”»åƒãƒ©ã‚¤ãƒ–ラリãŒå†…蔵ã•ã‚Œã¦ã„ã‚‹ã“ã¨ãŒç¢ºèªã•ã‚Œã¾ã—ãŸã€‚
@@ -10158,7 +10521,7 @@ Windowsã§MySQLを使用ã—ã¦ã„ã‚‹å ´åˆã«ã€ã€Œlocalhostã€ã¯ã€ã‚µãƒ¼ãƒãƒ
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 ローカル接続å]を入力ã—ã¦ãã ã•ã„。tnsnames.oraファイルã¯ã€ã“ã®ã‚¤ãƒ³ã‚¹ãƒˆãƒ¼ãƒ«ã«å¯¾ã—ã¦è¡¨ç¤ºã•ã‚Œã¦ãªã‘ã‚Œã°ãªã‚Šã¾ã›ã‚“ã€<br />ã‚‚ã—クライアントライブラリ10gã‚‚ã—ãã¯ãれ以上を使用ã—ã¦ã„ã‚‹å ´åˆã€ãƒ¡ã‚½ãƒƒãƒ‰ã®åå‰ã‚’[http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm 簡易接続]ã§åˆ©ç”¨ã§ãã¾ã™ã€‚',
+ '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 Easy Connect] ãƒãƒ¼ãƒŸãƒ³ã‚° メソッドを使用ã§ãã¾ã™ã€‚',
'config-db-wiki-settings' => 'ã“ã®ã‚¦ã‚£ã‚­ã®è­˜åˆ¥æƒ…å ±',
'config-db-name' => 'データベースå:',
'config-db-name-help' => 'ã“ã®ã‚¦ã‚£ã‚­ã‚’識別ã™ã‚‹åå‰ã‚’入力ã—ã¦ãã ã•ã„。
@@ -10236,16 +10599,16 @@ $1
'config-missing-db-host' => '「データベースã®ãƒ›ã‚¹ãƒˆã€ã‚’入力ã—ã¦ãã ã•ã„',
'config-missing-db-server-oracle' => '「データベース TNSã€ã®å€¤ã‚’入力ã—ã¦ãã ã•ã„',
'config-invalid-db-server-oracle' => '「$1ã€ã¯ç„¡åŠ¹ãªãƒ‡ãƒ¼ã‚¿ãƒ™ãƒ¼ã‚¹ TNS ã§ã™ã€‚
-アスキー英字(a-zã€A-Z)ã€æ•°å­—(0-9)ã€ã‚¢ãƒ³ãƒ€ãƒ¼ãƒãƒ¼(_)ã€ãƒ‰ãƒƒãƒˆ(.)ã®ã¿ã‚’使用ã—ã¦ãã ã•ã„。',
+「TNS åã€ã€ŒEasy Connectã€æ–‡å­—列ã®ã„ãšã‚Œã‹ã‚’使用ã—ã¦ãã ã•ã„ ([http://docs.oracle.com/cd/E11882_01/network.112/e10836/naming.htm Oracle ãƒãƒ¼ãƒŸãƒ³ã‚° メソッド])',
'config-invalid-db-name' => '「$1ã€ã¯ç„¡åŠ¹ãªãƒ‡ãƒ¼ã‚¿ãƒ™ãƒ¼ã‚¹åã§ã™ã€‚
-アスキー英字(a-zã€A-Z)ã€æ•°å­—(0-9)ã€ã‚¢ãƒ³ãƒ€ãƒ¼ãƒãƒ¼(_)ã€ãƒã‚¤ãƒ•ãƒ³(-)ã®ã¿ã‚’使用ã—ã¦ãã ã•ã„。',
- 'config-invalid-db-prefix' => '「$1ã€ã¯ç„¡åŠ¹ãªãƒ‡ãƒ¼ã‚¿ãƒ™ãƒ¼ã‚¹æŽ¥é ­èªžã§ã™ã€‚
-アスキー英字(a-z, A-Z)ã€æ•°å­—(0-9)ã€ä¸‹ç·š(_)ã€ãƒã‚¤ãƒ•ãƒ³(-)ã®ã¿ã‚’使用ã—ã¦ãã ã•ã„。',
+åŠè§’ã®è‹±æ•°å­— (a-zã€A-Zã€0-9)ã€ã‚¢ãƒ³ãƒ€ãƒ¼ã‚¹ã‚³ã‚¢ (_)ã€ãƒã‚¤ãƒ•ãƒ³ (-) ã®ã¿ã‚’使用ã—ã¦ãã ã•ã„。',
+ 'config-invalid-db-prefix' => '「$1ã€ã¯ç„¡åŠ¹ãªãƒ‡ãƒ¼ã‚¿ãƒ™ãƒ¼ã‚¹æŽ¥é ­è¾žã§ã™ã€‚
+åŠè§’ã®è‹±æ•°å­— (a-zã€A-Zã€0-9)ã€ã‚¢ãƒ³ãƒ€ãƒ¼ã‚¹ã‚³ã‚¢ (_)ã€ãƒã‚¤ãƒ•ãƒ³ (-) ã®ã¿ã‚’使用ã—ã¦ãã ã•ã„。',
'config-connection-error' => '$1。
以下ã®ãƒ›ã‚¹ãƒˆåã€ãƒ¦ãƒ¼ã‚¶ãƒ¼åã€ãƒ‘スワードを確èªã—ã¦ã‹ã‚‰å†åº¦è©¦ã—ã¦ãã ã•ã„。',
'config-invalid-schema' => '「$1ã€ã¯ MediaWiki ã®ã‚¹ã‚­ãƒ¼ãƒžã¨ã—ã¦ç„¡åŠ¹ã§ã™ã€‚
-ASCII ã®è‹±æ•°å­— (a-zã€A-Zã€0-9)ã€ä¸‹ç·š (_) ã®ã¿ã‚’使用ã—ã¦ãã ã•ã„。',
+åŠè§’ã®è‹±æ•°å­— (a-zã€A-Zã€0-9)ã€ã‚¢ãƒ³ãƒ€ãƒ¼ã‚¹ã‚³ã‚¢ (_) ã®ã¿ã‚’使用ã—ã¦ãã ã•ã„。',
'config-postgres-old' => 'PostgreSQL $1 以é™ãŒå¿…è¦ã§ã™ã€‚ã”使用中㮠PostgreSQL 㯠$2 ã§ã™ã€‚',
'config-sqlite-name-help' => 'ã‚ãªãŸã®ã‚¦ã‚§ã‚­ã¨åŒä¸€æ€§ã®ã‚ã‚‹åå‰ã‚’é¸ã‚“ã§ãã ã•ã„。
空白ãŠã‚ˆã³ãƒã‚¤ãƒ•ãƒ³ã¯ä½¿ç”¨ã—ãªã„ã§ãã ã•ã„。
@@ -10285,12 +10648,12 @@ chmod a+w $3</pre>',
[$1 ウィキを使ã„始ã‚ã‚‹]ã“ã¨ãŒã§ãã¾ã™ã€‚
-ã‚‚ã—ã€<code>LocalSettings.php</code>ファイルをå†ç”Ÿæˆã—ãŸã„ã®ãªã‚‰ã°ã€ä¸‹ã®ãƒœã‚¿ãƒ³ã‚’押ã—ã¦ãã ã•ã„。
-ウィキã«å•é¡ŒãŒãªã„ã®ã§ã‚ã‚Œã°ã€ã“ã‚Œã¯'''推奨ã•ã‚Œã¾ã›ã‚“'''。",
+<code>LocalSettings.php</code> ファイルをå†ç”Ÿæˆã—ãŸã„å ´åˆã¯ã€ä¸‹ã®ãƒœã‚¿ãƒ³ã‚’押ã—ã¦ãã ã•ã„。
+ウィキã«å•é¡ŒãŒã‚ã‚‹å ´åˆã‚’除ãã€å†ç”Ÿæˆã¯'''推奨ã•ã‚Œã¾ã›ã‚“'''。",
'config-upgrade-done-no-regenerate' => 'アップグレードãŒå®Œäº†ã—ã¾ã—ãŸã€‚
[$1 ウィキã®ä½¿ç”¨ã‚’開始]ã™ã‚‹ã“ã¨ãŒã§ãã¾ã™ã€‚',
- 'config-regenerate' => '<code>LocalSettings.php</code> ã‚’å†ç”Ÿæˆâ†’',
+ 'config-regenerate' => 'LocalSettings.php ã‚’å†ç”Ÿæˆâ†’',
'config-show-table-status' => '<code>SHOW TABLE STATUS</code> クエリãŒå¤±æ•—ã—ã¾ã—ãŸ!',
'config-unknown-collation' => "'''警告:''' データベースã¯èªè­˜ã•ã‚Œãªã„ç…§åˆã‚’使用ã—ã¦ã„ã¾ã™ã€‚",
'config-db-web-account' => 'ウェブアクセスã®ãŸã‚ã®ãƒ‡ãƒ¼ã‚¿ãƒ™ãƒ¼ã‚¹ã‚¢ã‚«ã‚¦ãƒ³ãƒˆ',
@@ -10352,7 +10715,7 @@ chmod a+w $3</pre>',
'config-optional-continue' => 'ç§ã«ã‚‚ã£ã¨è³ªå•ã—ã¦ãã ã•ã„。',
'config-optional-skip' => 'ã‚‚ã†é£½ãã¦ã—ã¾ã£ãŸã®ã§ã€ã¨ã«ã‹ãウィキをインストールã—ã¦ãã ã•ã„。',
'config-profile' => '利用者権é™ã®ãƒ—ロファイル:',
- 'config-profile-wiki' => 'ä¼çµ±çš„ãªã‚¦ã‚£ã‚­', # Fuzzy
+ 'config-profile-wiki' => '公開ウィキ',
'config-profile-no-anon' => 'アカウントã®ä½œæˆãŒå¿…è¦',
'config-profile-fishbowl' => '承èªã•ã‚ŒãŸç·¨é›†è€…ã®ã¿',
'config-profile-private' => 'éžå…¬é–‹ã‚¦ã‚£ã‚­',
@@ -10415,15 +10778,17 @@ GFDLã¯æœ‰åŠ¹ãªãƒ©ã‚¤ã‚»ãƒ³ã‚¹ã§ã™ãŒã€å†…容をç†è§£ã™ã‚‹ã®ã¯å›°é›£ã§ã
'config-upload-deleted-help' => '削除ã•ã‚Œã‚‹ãƒ•ã‚¡ã‚¤ãƒ«ã‚’ä¿å­˜ã™ã‚‹ãŸã‚ã®ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã‚’é¸æŠžã—ã¦ãã ã•ã„。
ã“ã‚ŒãŒã‚¦ã‚§ãƒ–ã‹ã‚‰ã‚¢ã‚¯ã‚»ã‚¹ã§ããªã„ã“ã¨ãŒç†æƒ³ã§ã™ã€‚',
'config-logo' => 'ロゴ ã®URL:',
- 'config-logo-help' => 'MediaWikiã®æ—¢å®šã®å¤–装ã§ã¯ã€ã‚µã‚¤ãƒ‰ãƒãƒ¼ä¸Šéƒ¨ã«135x160ピクセルã®ãƒ­ã‚´ç”¨ã®ä½™ç™½ãŒã‚ã‚Šã¾ã™ã€‚
-é©åˆ‡ãªã‚µã‚¤ã‚ºã®ç”»åƒã‚’アップロードã—ã¦ã€ãã®URLã‚’ã“ã“ã«å…¥åŠ›ã—ã¦ãã ã•ã„。
+ 'config-logo-help' => 'MediaWiki ã®æ—¢å®šã®å¤–装ã§ã¯ã€ã‚µã‚¤ãƒ‰ãƒãƒ¼ä¸Šéƒ¨ã«135x160ピクセルã®ãƒ­ã‚´ç”¨ã®ä½™ç™½ãŒã‚ã‚Šã¾ã™ã€‚
+é©åˆ‡ãªã‚µã‚¤ã‚ºã®ç”»åƒã‚’アップロードã—ã¦ã€ãã® URL ã‚’ã“ã“ã«å…¥åŠ›ã—ã¦ãã ã•ã„。
+
+ロゴãŒç›¸å¯¾ãƒ‘スã®å ´åˆã¯ã€<code>$wgStylePath</code> ã‚„ <code>$wgScriptPath</code> を使用ã§ãã¾ã™ã€‚
-ロゴãŒä¸è¦ã®å ´åˆã¯ã€ã“ã®ãƒœãƒƒã‚¯ã‚¹ã‚’空白ã®ã¾ã¾ã«ã—ã¦ãã ã•ã„。',
+ロゴãŒä¸è¦ã®å ´åˆã¯ã€ã“ã®æ¬„を空白ã®ã¾ã¾ã«ã—ã¦ãã ã•ã„。',
'config-instantcommons' => 'Instant Commons 機能を有効ã«ã™ã‚‹',
- 'config-instantcommons-help' => '[//www.mediawiki.org/wiki/InstantCommons InstantCommons]ã¯ã€[//commons.wikimedia.org/ ウィキメディア・コモンズ]ã®ã‚µã‚¤ãƒˆã§è¦‹ã¤ã‹ã£ãŸç”»åƒã‚„音声ã€ãã®ä»–ã®ãƒ¡ãƒ‡ã‚£ã‚¢ã‚’ウィキ上ã§åˆ©ç”¨ã™ã‚‹ã“ã¨ãŒã§ãるよã†ã«ãªã‚‹æ©Ÿèƒ½ã§ã™ã€‚
-ã“れを有効化ã™ã‚‹ã«ã¯ã€MediaWikiã¯ã‚¤ãƒ³ã‚¿ãƒ¼ãƒãƒƒãƒˆã«æŽ¥ç¶šã§ããªã‘ã‚Œã°ãªã‚Šã¾ã›ã‚“。
+ 'config-instantcommons-help' => '[//www.mediawiki.org/wiki/InstantCommons Instant Commons] ã¯ã€[//commons.wikimedia.org/ ウィキメディア・コモンズ]ã®ã‚µã‚¤ãƒˆã«ã‚ã‚‹ç”»åƒã€éŸ³å£°ã€ãã®ä»–ã®ãƒ¡ãƒ‡ã‚£ã‚¢ã‚’ウィキ上ã§åˆ©ç”¨ã§ãるよã†ã«ã™ã‚‹æ©Ÿèƒ½ã§ã™ã€‚
+ã“れを使用ã™ã‚‹ã«ã¯ã€MediaWiki ãŒã‚¤ãƒ³ã‚¿ãƒ¼ãƒãƒƒãƒˆã«æŽ¥ç¶šã§ãã‚‹å¿…è¦ãŒã‚ã‚Šã¾ã™ã€‚
-ウィキメディア・コモンズ以外ã®ã‚¦ã‚£ã‚­ã‚’åŒã˜ã‚ˆã†ã«è¨­å®šã™ã‚‹æ–¹æ³•ãªã©ã€ã“ã®æ©Ÿèƒ½ã«é–¢ã™ã‚‹è©³ç´°ãªæƒ…å ±ã¯ã€[//mediawiki.org/wiki/Manual:$wgForeignFileRepos マニュアル]ã‚’ã”覧ãã ã•ã„。',
+ウィキメディア・コモンズ以外ã®ã‚¦ã‚£ã‚­ã‚’åŒæ§˜ã«è¨­å®šã™ã‚‹æ‰‹é †ãªã©ã€ã“ã®æ©Ÿèƒ½ã«é–¢ã™ã‚‹è©³ç´°ãªæƒ…å ±ã¯ã€[//mediawiki.org/wiki/Manual:$wgForeignFileRepos マニュアル]ã‚’ã”覧ãã ã•ã„。',
'config-cc-error' => 'クリエイティブ・コモンズ・ライセンスã®é¸æŠžå™¨ã‹ã‚‰çµæžœãŒå¾—られã¾ã›ã‚“ã§ã—ãŸã€‚
ライセンスã®åå‰ã‚’手動ã§å…¥åŠ›ã—ã¦ãã ã•ã„。',
'config-cc-again' => 'ã‚‚ã†ä¸€åº¦é¸æŠžã—ã¦ãã ã•ã„...',
@@ -10440,6 +10805,9 @@ GFDLã¯æœ‰åŠ¹ãªãƒ©ã‚¤ã‚»ãƒ³ã‚¹ã§ã™ãŒã€å†…容をç†è§£ã™ã‚‹ã®ã¯å›°é›£ã§ã
カンマ区切りã§ã€åˆ©ç”¨ã™ã‚‹ç‰¹å®šã®ãƒãƒ¼ãƒˆã®æŒ‡å®šãŒå¿…è¦ã§ã™ã€‚例:
127.0.0.1:11211
192.168.1.25:1234',
+ 'config-memcache-noport' => 'Memcached サーãƒãƒ¼ $1 ã§ä½¿ç”¨ã™ã‚‹ãƒãƒ¼ãƒˆç•ªå·ã‚’指定ã—ã¦ã„ã¾ã›ã‚“。
+ãƒãƒ¼ãƒˆç•ªå·ãŒåˆ†ã‹ã‚‰ãªã„å ´åˆã€æ—¢å®šå€¤ã¯ 11211 ã§ã™ã€‚',
+ 'config-memcache-badport' => 'Memcached ã®ãƒãƒ¼ãƒˆç•ªå·ã¯ $1 ã‹ã‚‰ $2 ã®ç¯„囲ã«ã—ã¦ãã ã•ã„。',
'config-extensions' => '拡張機能',
'config-extensions-help' => '<code>./extensions</code>ディレクトリ内ã§ã€ä¸Šè¨˜ãƒªã‚¹ãƒˆã®æ‹¡å¼µæ©Ÿèƒ½ãŒç™ºè¦‹ã•ã‚Œã¾ã—ãŸã€‚
@@ -10457,6 +10825,7 @@ GFDLã¯æœ‰åŠ¹ãªãƒ©ã‚¤ã‚»ãƒ³ã‚¹ã§ã™ãŒã€å†…容をç†è§£ã™ã‚‹ã®ã¯å›°é›£ã§ã
'config-install-pg-schema-failed' => 'テーブルã®ä½œæˆã«å¤±æ•—ã—ã¾ã—ãŸã€‚
利用者「$1ã€ãŒã‚¹ã‚­ãƒ¼ãƒžã€Œ$2ã€ã«æ›¸ãè¾¼ã‚るよã†ã«ã—ã¦ãã ã•ã„。',
'config-install-pg-commit' => '変更をé€ä¿¡',
+ 'config-pg-no-plpgsql' => 'データベース $1 内㫠PL/pgSQL 言語をインストールã™ã‚‹å¿…è¦ãŒã‚ã‚Šã¾ã™ã€‚',
'config-install-user' => 'データベースユーザーã®ä½œæˆ',
'config-install-user-alreadyexists' => 'ユーザー「$1ã€ã¯æ—¢ã«å­˜åœ¨ã—ã¾ã™',
'config-install-user-create-failed' => 'ユーザー「$1ã€ã®ä½œæˆã«å¤±æ•—ã—ã¾ã—ãŸ: $2',
@@ -10515,7 +10884,7 @@ $messages['jam'] = array(
== Taatop ==
* [//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
);
/** Jutish (jysk)
@@ -10523,7 +10892,7 @@ $messages['jam'] = array(
*/
$messages['jut'] = array(
'mainpagetext' => "'''MediaWiki er nu installeret.'''",
- 'mainpagedocfooter' => "Se vores engelsksprÃ¥Äede [//meta.wikimedia.org/wiki/MediaWiki_localisation dokumentÃ¥sje tilpasnenge'm Ã¥f æ brugergrænseflade] og [//meta.wikimedia.org/wiki/MediaWiki_User%27s_Guide æ brugervejlednenge] før Ã¥plysnenger Ã¥psætnenge'm og anvendelse.",
+ 'mainpagedocfooter' => "Se vores engelsksprÃ¥Äede [//meta.wikimedia.org/wiki/MediaWiki_localisation dokumentÃ¥sje tilpasnenge'm Ã¥f æ brugergrænseflade] og [//meta.wikimedia.org/wiki/MediaWiki_User%27s_Guide æ brugervejlednenge] før Ã¥plysnenger Ã¥psætnenge'm og anvendelse.", # Fuzzy
);
/** Javanese (Basa Jawa)
@@ -10534,7 +10903,7 @@ $messages['jv'] = array(
== Miwiti panggunan ==
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Daftar pangaturan préférènsi]
* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Milis rilis MediaWiki]",
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Milis rilis MediaWiki]", # Fuzzy
);
/** Georgian (ქáƒáƒ áƒ—ული)
@@ -10582,7 +10951,6 @@ $messages['ka'] = array(
'config-header-postgres' => 'PostgreSQL-ის პáƒáƒ áƒáƒ›áƒ”ტრები',
'config-header-sqlite' => 'SQLite-ის პáƒáƒ áƒáƒ›áƒ”ტრები',
'config-header-oracle' => 'Oracle-ის პáƒáƒ áƒáƒ›áƒ”ტრები',
- 'config-header-ibm_db2' => 'IBM DB2-ის პáƒáƒ áƒáƒ›áƒ”ტრები',
'config-invalid-db-type' => 'áƒáƒ áƒáƒ¡áƒ¬áƒáƒ áƒ˜ მáƒáƒœáƒáƒªáƒ”მთრბáƒáƒ–ის ტიპი',
'config-mysql-innodb' => 'InnoDB',
'config-mysql-myisam' => 'MyISAM',
@@ -10643,7 +11011,7 @@ $messages['kaa'] = array(
== Baslaw ushın ==
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Konfiguratsiya sazlaw dizimi]
* [//www.mediawiki.org/wiki/Manual:FAQ MediaWikidin' Ko'p Soralatug'ın Sorawları]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki haqqında xat tarqatıw dizimi]",
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki haqqında xat tarqatıw dizimi]", # Fuzzy
);
/** ÐдыгÑÐ±Ð·Ñ (ÐдыгÑбзÑ)
@@ -10657,14 +11025,14 @@ $messages['kbd-cyrl'] = array(
== КъыщхьÑпÑÐ³ÑŠÑƒÑ Ñ…ÑŠÑƒÑ„Ñ‹Ð½ÑƒÑ…ÑÑ€ ==
* [//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
);
/** Khowar (کھوار)
* @author Rachitrali
*/
$messages['khw'] = array(
- 'mainpagetext' => "\"<big>'''میڈیاوکیو کامیابیو سورا چالو کورونو بیتی شیر۔.'''</big>\"",
+ 'mainpagetext' => "'''میڈیاوکیو کامیابیو سورا چالو کورونو بیتی شیر۔.'''",
);
/** Kirmanjki (Kırmancki)
@@ -10677,7 +11045,7 @@ $messages['kiu'] = array(
== Gamê verêni ==
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lista ayarunê vırastene]
* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki de ÇZP]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki ra lista serbest-daena postey]",
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki ra lista serbest-daena postey]", # Fuzzy
);
/** Kazakh (Arabic script) (قازاقشا (تٴوتە)â€)
@@ -10689,7 +11057,7 @@ $messages['kk-arab'] = 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
);
/** Kazakh (Cyrillic script) (қазақша (кирил)‎)
@@ -10701,7 +11069,7 @@ $messages['kk-cyrl'] = 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
);
/** Kazakh (Latin script) (qazaqşa (latın)‎)
@@ -10713,7 +11081,7 @@ $messages['kk-latn'] = array(
== Bastaw üşin ==
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Baptalım qalawlarınıñ tizimi]
* [//www.mediawiki.org/wiki/Manual:FAQ MedïaWïkïdiñ Jïı Qoýılğan Sawaldarı]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MedïaWïkï şığw twralı xat taratw tizimi]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MedïaWïkï şığw twralı xat taratw tizimi]', # Fuzzy
);
/** Khmer (ភាសាážáŸ’មែរ)
@@ -10743,7 +11111,7 @@ $messages['km'] = array(
* [//www.mediawiki.org/wiki/Manual:Configuration_settings បញ្ជីកំណážáŸ‹áž‘ម្រង់]
* [//www.mediawiki.org/wiki/Manual:FAQ/km សំណួរញឹកញាប់​មáŸážŒáž¶ážœáž·áž‚ី]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce បញ្ជី​ពិភាក្សា​ការផ្សព្វផ្សាយ​របស់​មáŸážŒáž¶ážœáž·áž‚ី]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce បញ្ជី​ពិភាក្សា​ការផ្សព្វផ្សាយ​របស់​មáŸážŒáž¶ážœáž·áž‚ី]', # Fuzzy
);
/** Kannada (ಕನà³à²¨à²¡)
@@ -10756,7 +11124,7 @@ $messages['kn'] = array(
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Configuration settings list]
* [//www.mediawiki.org/wiki/Manual:FAQ ಮೀಡಿಯವಿಕಿ 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
);
/** Korean (한국어)
@@ -10815,8 +11183,8 @@ php.ini를 확ì¸í•˜ê³  <code>session.save_path</code>ê°€ ì ì ˆí•œ 디렉토리
'config-help-restart' => '입력한 모든 ì €ìž¥ëœ ë°ì´í„°ë¥¼ 지우고 설치 ê³¼ì •ì„ ë‹¤ì‹œ 시작하겠습니까?',
'config-restart' => '예, 다시 시작합니다',
'config-welcome' => '=== 사용 환경 검사 ===
-ì´ í™˜ê²½ì´ ë¯¸ë””ì–´ìœ„í‚¤ ì„¤ì¹˜ì— ì í•©í•œì§€ 기본 검사를 실행합니다.
-설치 중 ë„ì›€ì´ í•„ìš”í•˜ë‹¤ë©´ ì´ ê²€ì‚¬ 결과를 함께 제공해주어야 합니다.',
+기본 검사는 지금 ì´ í™˜ê²½ì´ ë¯¸ë””ì–´ìœ„í‚¤ ì„¤ì¹˜ì— ì í•©í•œì§€ 수행합니다.
+설치를 완료하는 ë°©ë²•ì— ëŒ€í•œ 지ì›ì„ 찾는다면 ì´ ì •ë³´ë¥¼ í¬í•¨í•´ì•¼ 하는 ê²ƒì„ ê¸°ì–µí•˜ì„¸ìš”.',
'config-copyright' => "=== 저작권 ë° ì´ìš© 약관 ===
$1
@@ -10894,6 +11262,8 @@ Mandrake를 실행하고 있다면 php-xml 패키지를 설치하세요.',
'config-mod-security' => "'''경고''': 웹 ì„œë²„ì— [http://modsecurity.org/ mod_security]ê°€ 허용ë˜ì—ˆìŠµë‹ˆë‹¤. 잘못 ì„¤ì •ëœ ê²½ìš° 미디어위키나 사용ìžê°€ ìž„ì˜ì˜ 콘í…츠를 게시할 수 있는 다른 ì†Œí”„íŠ¸ì›¨ì–´ì— ëŒ€í•œ 문제를 ì¼ìœ¼í‚¬ 수 있습니다.
[http://modsecurity.org/documentation/ mod_security] 문서를 참고하거나 ìž„ì˜ì˜ 오류가 ë°œìƒí•  경우 í˜¸ìŠ¤íŠ¸ì˜ ì§€ì› ìš”ì²­ì— ë¬¸ì˜í•˜ì‹­ì‹œì˜¤.",
'config-diff3-bad' => 'GNU diff3를 ì°¾ì„ ìˆ˜ 없습니다.',
+ 'config-git' => 'Git 버전 관리 소프트웨어를 찾았습니다: <code>$1</code>.',
+ 'config-git-bad' => 'Git 버전 관리 소프트웨어를 ì°¾ì„ ìˆ˜ 없습니다.',
'config-imagemagick' => 'ImageMagick를 찾았습니다: <code>$1</code>.
올리기를 활성화할 경우 그림 섬네ì¼ì´ 활성화ë©ë‹ˆë‹¤.',
'config-gd' => 'ë‚´ìž¥ëœ GD 그래픽 ë¼ì´ë¸ŒëŸ¬ë¦¬ë¥¼ 찾았습니다.
@@ -10905,16 +11275,16 @@ Mandrake를 실행하고 있다면 php-xml 패키지를 설치하세요.',
'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>)는 ìž„ì˜ì˜ 스í¬ë¦½íŠ¸ ì‹¤í–‰ì— ì·¨ì•½í•©ë‹ˆë‹¤.
+ 'config-uploads-not-safe' => "'''경고:''' ì˜¬ë¦¬ê¸°ì— ëŒ€í•œ 기본 디렉터리(<code>$1</code>)는 ìž„ì˜ì˜ 스í¬ë¦½íŠ¸ ì‹¤í–‰ì— ì·¨ì•½í•©ë‹ˆë‹¤.
미디어위키는 보안 ìœ„í˜‘ì— ëŒ€í•œ 모든 올린 파ì¼ì„ 검사하지만, 올리기를 활성화하기 ì „ì— [//www.mediawiki.org/wiki/Manual:Security#Upload_security ì´ ë³´ì•ˆ 취약ì ì„ í•´ê²°í•  것]ì„ ë§¤ìš° 권장합니다.",
- 'config-no-cli-uploads-check' => "'''경고:''' ì˜¬ë¦¬ê¸°ì— ëŒ€í•œ 기본 디렉토리(<code>$1</code>)는 CLI를 설치하는 ë™ì•ˆ ìž„ì˜ì˜ 스í¬ë¦½íŠ¸ ì‹¤í–‰ì— ëŒ€í•œ 취약ì ì— 대해 검사ë˜ì§€ 않습니다.",
+ '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ì— ì œê¸°í•œ 버그])
설치가 중단ë˜ì—ˆìŠµë‹ˆë‹¤.',
'config-using531' => '미디어위키는 <code>__call()</code>ì„ ì°¸ê³ ë¡œ 매개 변수를 í¬í•¨í•˜ëŠ” 버그로 ì¸í•´ PHP $1(와)ê³¼ 함께 사용할 수 없습니다.
문제를 해결하려면 PHP 5.3.2 ì´ìƒë¡œ 업그레ì´ë“œí•˜ê±°ë‚˜ PHP 5.3.0으로 다운그레ì´ë“œë¥¼ 하세요.
설치가 중단ë˜ì—ˆìŠµë‹ˆë‹¤.',
- 'config-suhosin-max-value-length' => 'Suhosinì´ ì„¤ì¹˜ë˜ì—ˆê³  $1 ë°”ì´íŠ¸ë¡œ GET 매개 ë³€ìˆ˜ì¸ <code>length</code>를 제한하고 있습니다.
+ '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' => 'ë°ì´í„°ë² ì´ìŠ¤ 종류:',
@@ -10927,7 +11297,7 @@ PHP 5.2.9 ì´í›„와 libxml2 2.7.3 ì´í›„ë¡œ 업그레ì´ë“œí•˜ì„¸ìš”. ([//bugs.p
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-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' => '위키를 ì‹ë³„하기 위한 ì´ë¦„ì„ ì„ íƒí•˜ì„¸ìš”.
@@ -10975,7 +11345,7 @@ MySQLì˜ UTF-8 모드를 보다 ë” íš¨ìœ¨ì ì´ê³  유니코드 문ìžì˜ ì „ì²
'config-db-schema-help' => '보통 ì´ ìŠ¤í‚¤ë§ˆëŠ” 문제가 없습니다.
필요한 경우ì—만 바꾸세요.',
'config-pg-test-error' => "'''$1''' ë°ì´í„°ë² ì´ìŠ¤ì— ì—°ê²°í•  수 없습니다: $2",
- 'config-sqlite-dir' => 'SQLite ë°ì´í„° 디렉토리:',
+ 'config-sqlite-dir' => 'SQLite ë°ì´í„° 디렉터리:',
'config-sqlite-dir-help' => "SQLite는 í•˜ë‚˜ì˜ íŒŒì¼ì— 모든 ë°ì´í„°ë¥¼ 저장합니다.
제공하는 디렉토리는 설치하는 ë™ì•ˆ 웹 서버가 쓸 수 있어야 합니다.
@@ -10988,8 +11358,7 @@ MySQLì˜ UTF-8 모드를 보다 ë” íš¨ìœ¨ì ì´ê³  유니코드 문ìžì˜ ì „ì²
예를 들어 <code>/var/lib/mediawiki/yourwiki</code>와 ê°™ì´ ë‹¤ë¥¸ ê³³ì— ë°ì´í„°ë² ì´ìŠ¤ë¥¼ 넣는 ê²ƒì´ ì¢‹ìŠµë‹ˆë‹¤.",
'config-oracle-def-ts' => '기본 í…Œì´ë¸”공간:',
'config-oracle-temp-ts' => 'ìž„ì‹œ í…Œì´ë¸”공간:',
- 'config-type-oracle' => '오ë¼í´',
- 'config-type-ibm_db2' => 'IBM DB2',
+ 'config-type-oracle' => 'Oracle',
'config-support-info' => '미디어위키는 다ìŒì˜ ë°ì´í„°ë² ì´ìŠ¤ ì‹œìŠ¤í…œì„ ì§€ì›í•©ë‹ˆë‹¤:
$1
@@ -10999,18 +11368,16 @@ $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는 ìƒìš© 엔터프ë¼ì´ì¦ˆ ë°ì´í„°ë² ì´ìŠ¤ìž…니다.([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 설정',
- 'config-header-oracle' => '오ë¼í´ 설정',
- 'config-header-ibm_db2' => 'IBM DB2 설정',
+ 'config-header-oracle' => 'Oracle 설정',
'config-invalid-db-type' => 'ìž˜ëª»ëœ ë°ì´í„°ë² ì´ìŠ¤ 종류',
'config-missing-db-name' => '"ë°ì´í„°ë² ì´ìŠ¤ ì´ë¦„"ì— ëŒ€í•œ ê°’ì„ ìž…ë ¥í•´ì•¼ 합니다',
'config-missing-db-host' => '"ë°ì´í„°ë² ì´ìŠ¤ 호스트"ì— ëŒ€í•œ ê°’ì„ ìž…ë ¥í•´ì•¼ 합니다',
'config-missing-db-server-oracle' => '"ë°ì´í„°ë² ì´ìŠ¤ TNS"ì— ëŒ€í•œ ê°’ì„ ìž…ë ¥í•´ì•¼ 합니다',
'config-invalid-db-server-oracle' => '"$1" ë°ì´í„°ë² ì´ìŠ¤ TNSê°€ 잘못ë습니다.
-ASCII ê¸€ìž (a-z, A-Z), ìˆ«ìž (0-9), 밑줄 (_)ê³¼ 하ì´í”ˆ (-)만 사용하세요.',
+"TNS Name"ì´ë‚˜ "Easy Connect" 문ìžì—´ 중 하나를 사용하세요 ([http://docs.oracle.com/cd/E11882_01/network.112/e10836/naming.htm Oracle 네ì´ë° 메서드])',
'config-invalid-db-name' => '"$1" ë°ì´í„°ë² ì´ìŠ¤ ì´ë¦„ì´ ìž˜ëª»ë˜ì—ˆìŠµë‹ˆë‹¤.
ASCII ê¸€ìž (a-z, A-Z), ìˆ«ìž (0-9), 밑줄 (_)ê³¼ 하ì´í”ˆ (-)만 사용하세요.',
'config-invalid-db-prefix' => '"$1" ë°ì´í„°ë² ì´ìŠ¤ ì ‘ë‘ì–´ê°€ 잘못ë습니다.
@@ -11066,7 +11433,7 @@ chmod a+w $3</pre>',
'config-upgrade-done-no-regenerate' => '업그레ì´ë“œê°€ 완료ë˜ì—ˆìŠµë‹ˆë‹¤.
ì´ì œ [$1 위키를 시작]í•  수 있습니다.',
- 'config-regenerate' => '<code>LocalSettings.php</code> 다시 만들기 →',
+ 'config-regenerate' => 'LocalSettings.php 다시 만들기 →',
'config-show-table-status' => '<code>SHOW TABLE STATUS</code> 쿼리를 실패했습니다!',
'config-unknown-collation' => "'''경고:''' ë°ì´í„°ë² ì´ìŠ¤ê°€ ì¸ì‹í•˜ì§€ 않는 ì •ë ¬ì„ ì‚¬ìš©í•˜ê³  있습니다.",
'config-db-web-account' => '웹 ì ‘ê·¼ì„ ìœ„í•œ ë°ì´í„°ë² ì´ìŠ¤ 계정',
@@ -11078,13 +11445,19 @@ chmod a+w $3</pre>',
'config-mysql-engine' => '스토리지 엔진:',
'config-mysql-innodb' => 'InnoDB',
'config-mysql-myisam' => 'MyISAM',
- 'config-mysql-myisam-dep' => "'''경고''': 미디어위키와 함께 사용하ë„ë¡ ê¶Œìž¥í•˜ì§€ 않는 MySQLì— ëŒ€í•œ 스토리지 엔진으로 MyISAMì„ ì„ íƒí•˜ì˜€ìŠµë‹ˆë‹¤. ì´ìœ ëŠ”:
-* í…Œì´ë¸”ì´ ìž ê²¨ìžˆì–´ ë™ì‹œì„±ì„ ê±°ì˜ ì§€ì›í•˜ì§€ 않습니다
-* 다른 엔진보다 ì†ìƒì´ ë” ìžì£¼ ë°œìƒí•©ë‹ˆë‹¤
-* 미디어위키 바탕 코드가 í•­ìƒ ì •ìƒì ìœ¼ë¡œ MyISAMì„ ì²˜ë¦¬í•˜ì§€ 않습니다
-
-MySQL 설치가 InnoDB를 지ì›í•œë‹¤ë©´ ê·¸ ì„ íƒ ëŒ€ì‹ ì— InnoDB를 ì„ íƒí•  ê²ƒì„ ë§¤ìš° 권장합니다.
-MySQL 설치가 InnoDB를 지ì›í•˜ì§€ 않는다면 ì•„ë§ˆë„ ì—…ê·¸ë ˆì´ë“œë¥¼ 해야 í•  ìˆ˜ë„ ìžˆìŠµë‹ˆë‹¤.",
+ 'config-mysql-myisam-dep' => "'''경고''': ë¯¸ë””ì–´ìœ„í‚¤ì— ì‚¬ìš©í•˜ì§€ 않는 ê²ƒì´ ì¢‹ì€ MySQLì— ëŒ€í•œ 스토리지 엔진으로 MyISAMì„ ì„ íƒí•˜ì˜€ìŠµë‹ˆë‹¤. ì´ìœ ëŠ”:
+* í…Œì´ë¸” ìž ê¸ˆì— ì˜í•´ 간신히 ë™ì‹œì„±ì„ 지ì›í•©ë‹ˆë‹¤
+* 다른 엔진보다 ì†ìƒí•˜ëŠ” ê²½í–¥ì´ ìžˆìŠµë‹ˆë‹¤
+* 미디어위키 코드베ì´ìŠ¤ê°€ í•­ìƒ ì •ìƒì ìœ¼ë¡œ MyISAMì„ ì²˜ë¦¬í•˜ì§€ 않습니다
+
+MySQL 설치가 InnoDB를 지ì›í•œë‹¤ë©´, ê·¸ ì„ íƒ ëŒ€ì‹ ì— InnoDB를 ì„ íƒí•  ê²ƒì„ ë§¤ìš° 권장합니다.
+MySQL 설치가 InnoDB를 지ì›í•˜ì§€ 않는다면, ì•„ë§ˆë„ ì—…ê·¸ë ˆì´ë“œë¥¼ í•  시간입니다.",
+ 'config-mysql-only-myisam-dep' => "'''경고''': ë¯¸ë””ì–´ìœ„í‚¤ì— ì‚¬ìš©í•˜ì§€ 않는 ê²ƒì´ ì¢‹ì€ MySQLì— ëŒ€í•œ 유ì¼í•˜ê²Œ 사용할 수 있는 스토리지 엔진입니다. ì´ìœ ëŠ”:
+* í…Œì´ë¸” ìž ê¸ˆì— ì˜í•´ 간신히 ë™ì‹œì„±ì„ 지ì›í•©ë‹ˆë‹¤
+* 다른 엔진보다 ì†ìƒí•˜ëŠ” ê²½í–¥ì´ ìžˆìŠµë‹ˆë‹¤
+* 미디어위키 코드베ì´ìŠ¤ê°€ í•­ìƒ ì •ìƒì ìœ¼ë¡œ MyISAMì„ ì²˜ë¦¬í•˜ì§€ 않습니다
+
+MySQL 설치가 InnoDB를 지ì›í•˜ì§€ 않으며, ì•„ë§ˆë„ ì—…ê·¸ë ˆì´ë“œë¥¼ í•  시간입니다.",
'config-mysql-engine-help' => "'''InnoDB'''는 ë™ì‹œì ì¸ 지ì›ì— 좋기 ë•Œë¬¸ì— ëŒ€ë¶€ë¶„ ìµœê³ ì˜ ì˜µì…˜ìž…ë‹ˆë‹¤.
'''MyISAM'''ì€ ë‹¨ì¼ ì‚¬ìš©ìž ë˜ëŠ” ì½ê¸° ì „ìš© ì„¤ì¹˜ì— ë¹ ë¥¼ 수 있습니다.
@@ -11096,7 +11469,6 @@ MyISAM ë°ì´í„°ë² ì´ìŠ¤ëŠ” InnoDB ë°ì´í„°ë² ì´ìŠ¤ë³´ë‹¤ ë” ìžì£¼ ì†ì‹¤ë
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-blank' => '사ì´íŠ¸ ì´ë¦„ì„ ìž…ë ¥í•˜ì„¸ìš”.',
@@ -11165,7 +11537,7 @@ MySQLì˜ UTF-8 모드를 보다 ë” íš¨ìœ¨ì ì´ê³  유니코드 문ìžì˜ ì „ì²
'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 ìžìœ  ë¼ì´ì„ ìŠ¤]ì— ë”°ë¼ ë„£ìŠµë‹ˆë‹¤.
ì´ë ‡ê²Œ 하면 커뮤니티 ì†Œìœ ê¶Œì˜ ì´í•´ë¥¼ í•  수 있ë„ë¡ í•˜ê³  장기ì ì¸ 기여를 장려합니다.
ì¼ë°˜ì ìœ¼ë¡œ ê°œì¸ ë˜ëŠ” 회사 ìœ„í‚¤ì— ëŒ€í•´ì„œëŠ” 필요하지 않습니다.
@@ -11173,7 +11545,7 @@ MySQLì˜ UTF-8 모드를 보다 ë” íš¨ìœ¨ì ì´ê³  유니코드 문ìžì˜ ì „ì²
위키백과는 ì´ì „ì— GNU ìžìœ  문서 사용 허가서를 사용했습니다.
GFDLì€ ìœ íš¨í•œ ë¼ì´ì„ ìŠ¤ì´ì§€ë§Œ ë‚´ìš©ì„ ì´í•´í•˜ê¸° 어렵습니다.
-GFDL í•˜ì— ì‚¬ìš©ì„ í—ˆê°€í•œ ë‚´ìš©ì„ ìž¬ì‚¬ìš©í•˜ëŠ” ê²ƒë„ ì–´ë µìŠµë‹ˆë‹¤.",
+GFDLì— ë”°ë¼ ì‚¬ìš©ì´ í—ˆê°€ëœ ë‚´ìš©ì„ ìž¬ì‚¬ìš©í•˜ëŠ” ê²ƒë„ ì–´ë µìŠµë‹ˆë‹¤.",
'config-email-settings' => 'ì´ë©”ì¼ ì„¤ì •',
'config-enable-email' => '발신 ì´ë©”ì¼ í™œì„±í™”',
'config-enable-email-help' => 'ì´ë©”ì¼ì„ ìž‘ë™í•˜ë ¤ë©´ [http://www.php.net/manual/en/mail.configuration.php PHPì˜ ë©”ì¼ ì„¤ì •]ì„ ì˜¬ë°”ë¥´ê²Œ 설정해야 합니다.
@@ -11199,12 +11571,14 @@ GFDL í•˜ì— ì‚¬ìš©ì„ í—ˆê°€í•œ ë‚´ìš©ì„ ìž¬ì‚¬ìš©í•˜ëŠ” ê²ƒë„ ì–´ë µìŠµë‹ˆë‹¤
íŒŒì¼ ì˜¬ë¦¬ê¸°ë¥¼ 활성화하려면 ë¯¸ë””ì–´ìœ„í‚¤ì˜ ë£¨íŠ¸ ë””ë ‰í† ë¦¬ì— ìžˆëŠ” <code>images</code> 하위 디렉토리ì—ì„œ 웹 서버가 기ë¡í•  수 있ë„ë¡ ëª¨ë“œë¥¼ 바꿉니다.
ê·¸ ë‹¤ìŒ ì´ ì˜µì…˜ì„ í™œì„±í™”í•©ë‹ˆë‹¤.',
- 'config-upload-deleted' => 'ì‚­ì œëœ íŒŒì¼ì— 대한 디렉토리:',
+ 'config-upload-deleted' => 'ì‚­ì œëœ íŒŒì¼ì— 대한 디렉터리:',
'config-upload-deleted-help' => 'ì‚­ì œëœ íŒŒì¼ì„ 보관할 디렉토리를 ì„ íƒí•˜ì„¸ìš”.
ì´ìƒì ìœ¼ë¡œ 웹ì—ì„œ 접근할 수 없게 해야 합니다.',
'config-logo' => '로고 URL:',
- 'config-logo-help' => 'ë¯¸ë””ì–´ìœ„í‚¤ì˜ ê¸°ë³¸ ìŠ¤í‚¨ì€ ì‚¬ì´ë“œë°” 메뉴 ìœ„ì— 135×160 í”½ì…€ì˜ ë¡œê³ ë¥¼ í¬í•¨í•˜ê³  있습니다.
-ì ë‹¹í•œ í¬ê¸°ë¡œ ì´ë¯¸ì§€ë¥¼ 올리고 URLì„ ì—¬ê¸°ì— ìž…ë ¥í•˜ì„¸ìš”.
+ 'config-logo-help' => 'ë¯¸ë””ì–´ìœ„í‚¤ì˜ ê¸°ë³¸ ìŠ¤í‚¨ì€ ì‚¬ì´ë“œë°” 메뉴 ìœ„ì— 135×160 í”½ì…€ì˜ ë¡œê³ ì˜ ê³µê°„ì„ í¬í•¨í•˜ê³  있습니다.
+ì ë‹¹í•œ í¬ê¸°ë¡œ ê·¸ë¦¼ì„ ì˜¬ë¦¬ê³  ì—¬ê¸°ì— URLì„ ìž…ë ¥í•˜ì„¸ìš”.
+
+로고가 ìƒëŒ€ì ì¸ ê²½ë¡œì— ìžˆìœ¼ë©´ <code>$wgStylePath</code>나 <code>$wgScriptPath</code>를 사용할 수 있습니다.
로고 ì‚¬ìš©ì„ ì›í•˜ì§€ 않으면 ì´ ìƒìžë¥¼ 비우세요.',
'config-instantcommons' => 'ì¸ìŠ¤í„´íŠ¸ 공용 활성화',
@@ -11258,7 +11632,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" 사용ìžê°€ 존재하지 않습니다.',
@@ -11287,18 +11661,21 @@ GFDL í•˜ì— ì‚¬ìš©ì„ í—ˆê°€í•œ ë‚´ìš©ì„ ìž¬ì‚¬ìš©í•˜ëŠ” ê²ƒë„ ì–´ë µìŠµë‹ˆë‹¤
설치 í”„ë¡œê·¸ëž¨ì´ <code>LocalSettings.php</code> 파ì¼ì„ 만들었습니다.
모든 ì„¤ì •ì´ í¬í•¨ë˜ì–´ 있습니다.
-파ì¼ì„ 다운로드하여 위키 ì„¤ì¹˜ì˜ ê±°ì ì— 넣어야 합니다. (index.php와 ê°™ì€ ë””ë ‰í† ë¦¬) 다운로드가 ìžë™ìœ¼ë¡œ 시작ë©ë‹ˆë‹¤.
+파ì¼ì„ 다운로드하여 위키 ì„¤ì¹˜ì˜ ê±°ì ì— 넣어야 합니다. (index.php와 ê°™ì€ ë””ë ‰í„°ë¦¬) 다운로드가 ìžë™ìœ¼ë¡œ 시작ë©ë‹ˆë‹¤.
다운로드가 제공ë˜ì§€ ì•Šì„ ê²½ìš°ë‚˜ ê·¸ê²ƒì„ ì·¨ì†Œí•œ 경우ì—는 ì•„ëž˜ì˜ ë§í¬ë¥¼ í´ë¦­í•˜ì—¬ 다운로드를 다시 시작할 수 있습니다:
$3
-'''참고''': ì´ ìƒì„±í•œ 설정 파ì¼ì„ 다운로드하지 ì•Šê³  설치를 ëë‚´ë©´ ì´ íŒŒì¼ì€ ë‚˜ì¤‘ì— ì‚¬ìš©í•  수 없습니다.
+'''참고:''' ì´ ìƒì„±í•œ 설정 파ì¼ì„ 다운로드하지 ì•Šê³  설치를 ëë‚´ë©´ ì´ íŒŒì¼ì€ ë‚˜ì¤‘ì— ì‚¬ìš©í•  수 없습니다.
완료ë˜ì—ˆìœ¼ë©´ '''[$2 ìœ„í‚¤ì— ë“¤ì–´ê°ˆ 수 있습니다]'''.",
'config-download-localsettings' => '<code>LocalSettings.php</code> 다운로드',
'config-help' => 'ë„움ë§',
'config-nofile' => '"$1" 파ì¼ì„ ì°¾ì„ ìˆ˜ 없습니다. ì´ë¯¸ ì‚­ì œë˜ì—ˆë‚˜ìš”?',
+ 'config-extension-link' => 'ë‹¹ì‹ ì˜ ìœ„í‚¤ê°€ [//www.mediawiki.org/wiki/Manual:Extensions 확장 기능]ì„ ì§€ì›í•œë‹¤ëŠ” ê²ƒì„ ì•Œê³  계십니까?
+
+ì „ì²´ 확장 ê¸°ëŠ¥ì˜ ëª©ë¡ì„ 확ì¸í•˜ë ¤ë©´ [//www.mediawiki.org/wiki/Category:Extensions_by_category 분류별 확장 기능]ì´ë‚˜ [//www.mediawiki.org/wiki/Extension_Matrix 확장 기능 í‘œ]를 찾아보실 수 있습니다.',
'mainpagetext' => "'''미디어위키가 성공ì ìœ¼ë¡œ 설치ë˜ì—ˆìŠµë‹ˆë‹¤.'''",
'mainpagedocfooter' => '[//meta.wikimedia.org/wiki/Help:Contents ì´ê³³]ì—ì„œ 위키 ì†Œí”„íŠ¸ì›¨ì–´ì— ëŒ€í•œ 정보를 ì–»ì„ ìˆ˜ 있습니다.
@@ -11320,7 +11697,7 @@ $messages['krc'] = array(
== Файдалы реÑурÑла ==
* [//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
);
/** Colognian (Ripoarisch)
@@ -11450,7 +11827,7 @@ Dat heiß, mer moß en affschallde, söns jeiht nix.",
MediaWiki bruch Funxjohne en däm Modul un deiht et esu nit.
Wann De <i lang="en">Mandrake</i> aam loufehäs, donn dat Pakätt <code lang="en">php-xml</code> enstalleere.',
'config-pcre' => 'Dem PHP sing Modul för <i lang="en">PCRE</i> schingk ze fähle.
-MediaWiki deiht et nit ohne de Funxjohne för de <i lang="en">Perl-compatible regular expressions</i>.',
+MediaWiki deiht et nit der ohne de Funxjohne för de rejolähre Ußdrök vun dä Zoot, wi <i lang="en">Perl</i> se kännt.',
'config-pcre-no-utf8' => "'''Dä:''' Et PHP-Modul <i lang=\"en\">PCRE</i> schingk ohne de <i lang=\"en\">PCRE_UTF8</i>-Aandeile övversaz ze sin.
MediaWiki bruch dä UTF-8-Krohm ävver, öm ohne Fähler loufe ze künne.",
'config-memory-raised' => 'Der jrühzte zohjelasse Shpeisherbedarf vum PHP, et <code lang="en">memory_limit</code>, shtund op $1 un es op $2 erop jesaz woode.',
@@ -11465,6 +11842,8 @@ Et Enreeschte kunnt doh draan kappott jon!",
Et <i lang="en">object caching</i> es nit müjjelesh un ußjeschalldt.',
'config-mod-security' => "'''Opjepaß''': Dinge Webßööver hät <code lang=\"en\">[http://modsecurity.org/ mod_security]</code> enjeschalldt. Wann doh derbei en Enschtällong nit janz akeraat paßß, dann kann et goot sin, dat mer Probleme met MeedijaWiki un oc met ander Projramme kritt, die zohlööt, dat vun ußerhallef öhndsene Krohm op dä Webßööver jebraat wääde künnt.Beloor Der di Sigg <code lang=\"en\">[http://modsecurity.org/documentation/ mod_security documentation]</code> udder donn met dä Fachlück för Dinge Webßööver kalle, wann zohfälleje un koomijje Fähler bemerke deihß.",
'config-diff3-bad' => 'Mer han <i lang="en">GNU</i> <code lang="en">diff3</code> nit jefonge.',
+ 'config-git' => 'Mer han de Väsjohn <code>$1</code> vun däm Väsjohnsverwalldongsprojamm <i lang="en">Git</i> jefonge.',
+ 'config-git-bad' => 'Dat Väsjohnsverwalldongsprojamm <i lang="en">Git</i> ham_mer nit jefonge.',
'config-imagemagick' => 'Mer han <i lang="en">ImageMagick</i> jefonge: <code>$1</code>.
Et Ömrääschne en Minni-Beldsche weed müjjelesch sin, wann De et Belder Huhlaade zohlöhß.',
'config-gd' => 'Mer han de ennjeboute GD-Jrafik-Projramm-Biblijotheek jefonge.
@@ -11484,7 +11863,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.', # Fuzzy
+ '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|$1 Bytes|noll Byte}} lang wääde. Dem MediaWiki singe <i lang="en">ResourceLoader</i> kütt doh zwa drömeröm, ävver dat bräms. 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-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.
@@ -11566,7 +11945,6 @@ Donn Ding Daatebangk et beß janz woh anders hen, noh <code lang="en">/var/lib/m
'config-type-postgres' => '<i lang="en">PostgreSQL</i>',
'config-type-sqlite' => '<i lang="en">SQLite</i>',
'config-type-oracle' => '<i lang="en">Oracle</i>',
- 'config-type-ibm_db2' => 'Dä <i lang="en">IBM</i> ier <i lang="en">DB2</i>',
'config-support-info' => 'MediaWiki kann met heh dä Daatebangk_Süßteeme zosamme jonn:
$1
@@ -11576,12 +11954,10 @@ 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.', # 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',
'config-header-oracle' => 'De Enshtällunge för de <i lang="en">Oracle</i> Daatebangk',
- 'config-header-ibm_db2' => 'De Enshtällunge för de <i lang="en">IBM</i> ier <i lang="en">DB2</i>',
'config-invalid-db-type' => 'Dat es en onjöltijje Zoot Daatebangk.',
'config-missing-db-name' => 'Do moß jät enjävve för dä Name vun dä Daatebangk.',
'config-missing-db-host' => 'Do moß jät enjävve för dä Name vun däm Rääschner met dä Daatebangk.',
@@ -11658,6 +12034,13 @@ Dä aanjejovve Zohjang för der Nomaalbedrief moß dröm schunn enjersht sen!',
Wann Ding <i lang="en">MySQL</i> et Schpeischere en <i lang="en">InnoDB</i>-Datteije ongerschtöze deiht, dom_mer dat nohdröcklesch ämfähle.
Kann dä ẞööver dat nit, künnd et joode jelääjeheit sin, dä ens op der neuste Schtand ze bränge.',
+ 'config-mysql-only-myisam-dep' => '\'\'\'Opjepaß:\'\'\' <i lang="en">MyISAM</i> es de einzeje Zoot Speischerprojramm för <i lang="en">MySQL</i>, di nit för MediaWiki ze ämfähle es, weil:
+* wääje dem Schpärre vun jannze Tabälle sin koum paralleele Axjuhne en dä Daatebangk möjjelesch,
+* et es aanfällesch för Probleeme met de Daate es, un
+* et weed vun MediaWiki nit emmer jood ongerschtöz.
+
+Ding Enschtallazjuhn vum <i lang="en">MySQL</i> kann nit met <i lang="en">InnoDB</i> ömjonn.
+Wi wöhr et met ener neuere väsjohn vum <i lang="en">MySQL</i>?',
'config-mysql-engine-help' => "'''InnoDB''' es fö jewöhnlesch et beß, weil vill Zohjreffe op eijmohl joot ongershtöz wääde.
'''MyISAM''' es flöcker op Rääschnere met bloß einem Minsch draan, un bei Wikis, di mer bloß lässe un nit schrieeve kann.
@@ -11670,7 +12053,6 @@ Dat es flöcker un spaasaamer wi et UTF-8 Fommaat vum <i lang=\"en\">MySQL</i> u
Beim Shpeishere em '''UTF-8 Fomaat''' deiht et <i lang=\"en\">MySQL</i> der Zeishesaz un de Kodeerung vun dä Daate känne, un kann se akeraat aanzeije un ömwandelle,
allerdengs künne kein Zeishe ußerhalv vum [//de.wikipedia.org/wiki/Basic_Multilingual_Plane#Gliederung_in_Ebenen_und_Bl.C3.B6cke jrundlääje Knubbel för vill Shprooche (<i lang=\"en\">Basic Multilingual Plane — BMP</i>)] afjeshpeishert wääde.",
- 'config-ibm_db2-low-db-pagesize' => "De <i lang=\"en\">DB2</i> Daatebangk heh hät ene standattmääßeje Plaz för Tabälle met zoh klein Sigge. Dä Plaz en de Sigge moß '''32K''' udder mieh sin.",
'config-site-name' => 'Däm Wiki singe Name:',
'config-site-name-help' => 'Dä douch em Tittel vun de Brauserfinstere un aan ätlije andere Shtälle op.',
'config-site-name-blank' => 'Donn ene Name för di Sait aanjävve.',
@@ -11713,25 +12095,25 @@ 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', # Fuzzy
+ 'config-profile-wiki' => 'En offe Wiki',
'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',
- 'config-profile-help' => "Wikis loufe et beß, wam_mer esu vill Lück wi müjjelesch draan metmaache un schrieve löht.
-Met MediaWiki es et ejfach, de neuste Änderunge ze beloore un wat ahnungslose udder fiese Lück kapott jemaat han wider retuur ze maache.
+ 'config-profile-help' => "Wikis loufe et bäß, wam_mer esu vill Lück wi möjjelesch draan metmaache un schrieve löht.
+Met MediaWiki es et ejfach, de neuste Änderonge ze beloore un wat ahnungslose udder fiese Lück kapott jemaat han wider retuur ze maache.
-Bloß, mänsh eine häd_eruß jefonge, dat mer MediaWiki jood en en jruuße Zahl ongerscheidlijje Rolle bruche kann, un nit emmer es et leish, ene vum onverfälschte Wiki_Wääsch ze övverzeuje.
+Bloß, mänsch eine häd_eruß jefonge, dat mer MediaWiki jood en en jruuße Zahl ongerscheidlijje Rolle bruche kann, un nit emmer es et leisch, ene vum onverfälschte Wiki_Wääsch ze övverzeuje.
Esu häß De de Wahl:
-'''{{int:config-profile-wiki}}''' löht jeder_ein metschrieve, och ohne enzelogge.
+'''{{int:config-profile-wiki}}''' löht jeder_ein metschrieve, och ohne sesch enzelogge.
-'''{{int:config-profile-no-anon}}''', dat sorsh för mieh seeshbaa Verantwootlishkeite, künnt ävver zohfällije Methellefer verschrecke.
+'''{{int:config-profile-no-anon}}''', dat sorsch för mieh seeschbaa Verantwootlischkeite, künnt ävver zohfällije Methellefer verschrecke.
'''{{int:config-profile-fishbowl}}''' löht nor de ußjesöhk Metmaacher schrieve, ävver de janze Öffentleshkeit kann et lässe un süht och de ällder Versione, un wat wää wann draan jedonn hät.
'''{{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.", # Fuzzy
+Noch ander un un opwändijere Enschtellunge 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.",
'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',
@@ -11780,6 +12162,8 @@ Et bäß es, wam_mer vum <i lang="en">world wide web</i> doh nit drahn kumme kan
'config-logo-help' => 'De Schtandart_Bedeen_Bovverfläsch vum MediaWiki hät e Logo bovve en der Eck met 135x160 Pixele.
Donn e zopaß Logo huh laade, un donn däm sing URL heh endraare.
+Do kanns <code lang="en">$wgStylePath</code> udder <code lang="en">$wgScriptPath</code> nämme, wann Ding Logo en einem vun dänne Pahde litt.
+
Wells De kei Logo han, draach heh nix en.',
'config-instantcommons' => 'Donn <i lang="en">InstantCommons</i> zohlohße.',
'config-instantcommons-help' => '<i lang="en">[//www.mediawiki.org/wiki/InstantCommons InstantCommons]</i> es en Eijeschaff, di et för Wikis müjjelesch määt, Belder, Tondatteie un ander Meedijedatteie enzebenge, di op dä Webßait vun de <i lang="en">[//commons.wikimedia.org/ Wikimedia Commons]</i> ongerjebraat sin. Öm dat noze ze künne, moß dä ẞööver vum MediaWiki en Verbendung nohm Internet opnämme künne.
@@ -11806,7 +12190,7 @@ Se sullte ein pro Reih opjeschrevve sin, un en Pooz (<i lang="en">port</i>) ier
'config-memcache-noport' => 'Do has kein Pooz (<code lang="en">port</code>) Nommer aanjejovve för mem <code lang="en">memcached</code> ẞööver ze bruche: $1.
Wann De di Nommer nit weiß, der Shtandatt es 11211.',
'config-memcache-badport' => 'Dem <code lang="en">memcached</code> ẞööver singe Pooz (<code lang="en">port</code>) Nommere sullte zwesche $1 un $2 sin.',
- 'config-extensions' => 'Projramm-Zosätz (<i lang="en">extensions</i>)',
+ 'config-extensions' => 'Projramm-Zohsäz (<i lang="en">Extensions</i>)',
'config-extensions-help' => 'Di bovve opjeleß Zohsazprojramme för et MediaWiki sin em Verzeischneß <code lang="en">./extensions</code> ald ze fenge.
Do kann se heh un jez aanschallde, ävver se künnte noch zohsäzlesch Enshtellunge bruche.',
@@ -11814,7 +12198,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}}“.', # Fuzzy
+Wann De noch Änderonge maache wells, dann kleck op „{{int:config-back}}“.',
'config-install-step-done' => 'jedonn',
'config-install-step-failed' => 'donävve jejange',
'config-install-extensions' => 'Zohsazprojramme enjeschloße',
@@ -11874,14 +12258,18 @@ Wann De mem Ronger- un widder Huhlaade fäädesh bes, kanns De '''[\$2 en Ding W
'config-download-localsettings' => 'Donn di Dattei <code lang="en">LocalSettings.php</code> eronger laade',
'config-help' => 'Hölp',
'config-nofile' => 'De Dattei „$1“ ham_mer nit jefonge. Es di fottjeschmeße?',
+ 'config-extension-link' => 'Häs De jewoß, dat et Wiki [//www.mediawiki.org/wiki/Manual:Extensions Zohsazprojramme] hann kann?
+
+Do kanns [//www.mediawiki.org/wiki/Category:Extensions_by_category Zohsazprojramme noh Saachjroppe] söhke udder en de [//www.mediawiki.org/wiki/Extension_Matrix Tabäll met de Zohsazprojramme] kike, öm de kumplätte Leß met de Zohsazprojramme ze krijje.',
'mainpagetext' => "'''MediaWiki es jetz enstalleet.'''",
- 'mainpagedocfooter' => 'Luur en et (änglesche) [//meta.wikimedia.org/wiki/Help:Contents Handboch] wann De wesse wells wie de Wiki-Soffwär jebruch un bedeent wääde muss.
+ 'mainpagedocfooter' => 'Luur en et (änglesche) [//meta.wikimedia.org/wiki/Help:Contents Handbooch] wann De wesse wells wie de Wiki-Soffwär jebruch un bedeent wääde moß.
== För dä Aanfang ==
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]', # Fuzzy
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]
+* [//www.mediawiki.org/wiki/Localisation#Translation_resources Donn MediaWiki op Ding Schprooch aanpaße]',
);
/** Kurdish (Latin script) (Kurdî (latînî)‎)
@@ -11903,7 +12291,7 @@ $messages['ku-latn'] = array(
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lîsteya varîyablên konfîgûrasîyonê]
* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lîsteya e-nameyên versyonên nuh yê MediaWiki]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lîsteya e-nameyên versyonên nuh yê MediaWiki]', # Fuzzy
);
/** Ladino (Ladino)
@@ -11916,11 +12304,12 @@ $messages['lad'] = array(
== En Empeçando ==
* [//www.mediawiki.org/wiki/Manual:Configuration_settings La lista de los arreglamientos de la konfiggurasyón]
* [//www.mediawiki.org/wiki/Manual:FAQ/lad DDS de MedyaViki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce La lista de las letrales (e-mail) de MedyaViki]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce La lista de las letrales (e-mail) de MedyaViki]', # Fuzzy
);
/** Luxembourgish (Lëtzebuergesch)
* @author Robby
+ * @author Soued031
* @author ì•„ë¼
*/
$messages['lb'] = array(
@@ -11930,6 +12319,8 @@ $messages['lb'] = array(
'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 <code>LocalSettings.php</code>.",
+ 'config-localsettings-cli-upgrade' => "E Fichier <code>LocalSettings.php</code> gouf fonnt.
+Fir dës Installatioun z'aktuaéliséieren start w.e.g. <code>update.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 <code>LocalSettings.php</code> schéngt net komplett ze sinn.
@@ -11963,7 +12354,7 @@ Kuckt Är php.ini no a vergewëssert Iech datt <code>session.save_path</code> o
'config-restart' => 'Jo, neistarten',
'config-welcome' => "=== Iwwerpréifung vum Installatiounsenvironnement ===
Et gi grondsätzlech Iwwerpréifunge gemaach fir ze kucken ob den Environnment gëeegent ass fir MediaWiki z'installéieren.
-Dir sollt d'Resultater vun dëser Iwwerpréifung ugi wann Dir während der Installatioun Hëllef braucht.",
+Dir sollt d'Resultater vun dëser Iwwerpréifung ugi wann Dir während der Installatioun Hëllef frot wéi Dir D'Installatioun ofschléisse kënnt.",
'config-sidebar' => '* [//www.mediawiki.org MediaWiki Haaptsäit]
* [//www.mediawiki.org/wiki/Help:Contents Benotzerguide]
* [//www.mediawiki.org/wiki/Manual:Contents Guide fir Administrateuren]
@@ -11987,9 +12378,10 @@ Dës Datebank-Type ginn ënnerstëtzt: $1.
Wann Dir op engem gesharte Server sidd, da frot Ären Hosting-Provider fir de passenden Datebank-Driver z'installéieren.
Wann Dir PHP selwer compiléiert hutt, da reconfiguréiert en mat dem ageschalten Datebank-Client, zum Beispill an deem Dir <code>./configure --with-mysql</code> benotzt.
Wann Dir PHP vun engem Debian oder Ubuntu Package aus installéiert hutt, da musst Dir och den php5-mysql Modul installéieren.",
+ 'config-outdated-sqlite' => "'''Warnung:''' SQLite $1 ass installéiert. Allerdengs brauch MediaWiki SQLite $2 oder méi nei. SQLite ass dofir net disponibel.",
'config-memory-bad' => "'''Opgepasst:''' De Parameter <code>memory_limit</code> vu PHP ass $1.
Dat ass wahrscheinlech ze niddreg.
-D'Installatioun kéint net fonctionnéieren.",
+D'Installatioun kéint net funktionéieren.",
'config-xcache' => '[http://xcache.lighttpd.net/ XCache] ass installéiert',
'config-apc' => '[http://www.php.net/apc APC] ass installéiert',
'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache] ass installéiert',
@@ -11997,7 +12389,9 @@ D'Installatioun kéint net fonctionnéieren.",
'config-no-uri' => "'''Feeler:''' Déi aktuell URI konnt net festgestallt ginn.
Installatioun ofgebrach.",
'config-using-server' => 'De Servernumm "<nowiki>$1</nowiki>" gëtt benotzt.',
+ 'config-using-uri' => 'D\'Server URL "<nowiki>$1$2</nowiki>" gëtt benotzt.',
'config-db-type' => 'Datebanktyp:',
+ 'config-db-host' => 'Host vun der Datebank:',
'config-db-host-oracle' => 'Datebank-TNS:',
'config-db-wiki-settings' => 'Dës Wiki identifizéieren',
'config-db-name' => 'Numm vun der Datebank:',
@@ -12026,13 +12420,10 @@ Wann et de Kont net gëtt, a wann den Installatiouns-Kont genuch Rechter huet, g
'config-type-postgres' => 'PostgreSQL',
'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', # Fuzzy
'config-header-mysql' => 'MySQL-Astellungen',
'config-header-postgres' => 'PostgreSQL-Astellungen',
'config-header-sqlite' => 'SQLite-Astellungen',
'config-header-oracle' => 'Oracle-Astellungen',
- 'config-header-ibm_db2' => 'IBM DB2-Astellungen',
'config-invalid-db-type' => 'Net valabelen Datebank-Typ',
'config-missing-db-name' => 'Dir musst en Numm fir de Wäert "Numm vun der Datebank" uginn',
'config-missing-db-host' => 'Dir musst e Wäert fir "Database host" uginn',
@@ -12047,7 +12438,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' => '<code>LocalSettings.php</code> regeneréieren →',
+ 'config-regenerate' => 'LocalSettings.php 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',
@@ -12074,18 +12465,18 @@ Dësen Numm gëtt da gebraucht fir sech an d\'Wiki anzeloggen.',
Spezifizéiert en anere Benotzernumm.',
'config-admin-password-blank' => 'Gitt e Passwuert fir den Adminstateur-Kont an.',
'config-admin-password-same' => "D'Passwuert däerf net dat selwecht si wéi de Benotzernumm.",
- 'config-admin-password-mismatch' => 'Déi zwee Passwierder Déi dir aginn stëmmen net iwwerteneen.',
- 'config-admin-email' => 'E-Mailadress:',
+ 'config-admin-password-mismatch' => 'Déi zwee Passwierder Déi Dir aginn hutt stëmmen net iwwereneen.',
+ 'config-admin-email' => 'E-Mail-Adress:',
'config-admin-error-user' => 'Interne Feeler beim uleeë vun engem Administrateur mam Numm "<nowiki>$1</nowiki>".',
'config-admin-error-password' => 'Interne Feeler beim Setze vum Passwuert fir den Admin "<nowiki>$1</nowiki>": <pre>$2</pre>',
- 'config-admin-error-bademail' => 'Dir hutt eng E-Mailadress aginn déi net valabel ass',
+ 'config-admin-error-bademail' => 'Dir hutt eng E-Mail-Adress aginn déi net valabel ass',
'config-subscribe' => "Sech op d'[https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Ukënnegunge vun neie Versiounen] abonnéieren.",
'config-almost-done' => "Dir sidd bal fäerdeg!
Dir kënnt elo déi Astellungen déi nach iwwreg sinn iwwersprangen an d'Wiki elo direkt installéieren.",
'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', # Fuzzy
+ 'config-profile-wiki' => 'Oppe Wiki',
'config-profile-no-anon' => 'Uleeë vun engem Benotzerkont verlaangt',
'config-profile-fishbowl' => 'Nëmmen autoriséiert Editeuren',
'config-profile-private' => 'Privat Wiki',
@@ -12101,39 +12492,45 @@ Dir kënnt elo déi Astellungen déi nach iwwreg sinn iwwersprangen an d'Wiki el
'config-email-sender' => 'E-Mailadress fir Äntwerten:',
'config-upload-settings' => 'Eropgeluede Biller a Fichieren',
'config-upload-enable' => 'Eropluede vu Fichieren aschalten',
- 'config-upload-deleted' => 'Repertoire fir geläschte Fichieren:',
+ 'config-upload-deleted' => 'Repertoire fir geläscht Fichieren:',
'config-logo' => 'URL vum Logo:',
'config-instantcommons' => '"Instant Commons" aktivéieren',
'config-cc-again' => 'Nach eng kéier eraussichen...',
'config-advanced-settings' => 'Erweidert Astellungen',
'config-extensions' => 'Erweiderungen',
'config-install-step-done' => 'fäerdeg',
- 'config-install-step-failed' => 'huet net fonctionnéiert',
+ 'config-install-step-failed' => 'huet net funktionéiert',
'config-install-extensions' => 'Mat den Ereiderungen',
'config-install-database' => 'Datebank gëtt installéiert',
+ 'config-install-pg-plpgsql' => 'No der Sprooch PL/pgSQL sichen',
'config-pg-no-plpgsql' => "Fir d'Datebank $1 muss d'Datebanksprooch PL/pgSQL installéiert ginn",
'config-install-user' => 'Datebank Benotzer uleeën',
'config-install-user-alreadyexists' => 'De Benotzer "$1" gëtt et schonn!',
- 'config-install-user-create-failed' => 'D\'Opmaache vum Benotzer "$1" huet net fonctionnéiert: $2',
+ 'config-install-user-create-failed' => 'D\'Opmaache vum Benotzer "$1" huet net funktionéiert: $2',
+ 'config-install-user-grant-failed' => 'D\'Bäisetze vu Rechter fir de Benotzer "$1" huet net funktionéiert: $2',
'config-install-user-missing' => 'De Benotzer "$1" deen ugi gouf gëtt et net.',
+ 'config-install-user-missing-create' => 'De spezifizéierte Benotzer "$1" gëtt et net.
+Klickt d\'Checkbox "Benotzerkont uleeën" wann Dir dee Benotzer uleeë wëllt.',
'config-install-tables' => 'Tabelle ginn ugeluecht',
'config-install-interwiki' => 'Standard Interwiki-Tabell gëtt ausgefëllt',
'config-install-interwiki-list' => 'De Fichier <code>interwiki.list</code> gouf net fonnt.',
'config-install-stats' => 'Initialisatioun vun de Statistiken',
'config-install-keys' => 'Generéiere vum Geheimschlëssel',
'config-install-sysop' => 'Administrateur Benotzerkont gëtt ugeluecht',
+ 'config-install-mainpage' => 'Haaptsäit mat Standard-Inhalt 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' => '<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.'''",
- 'mainpagedocfooter' => "Kuckt w.e.g. [//meta.wikimedia.org/wiki/Help:Contents d'Benotzerhandbuch] fir den Interface ze personnaliséieren.
+ 'mainpagedocfooter' => "Kuckt w.e.g. [//meta.wikimedia.org/wiki/Help:Contents d'Benotzerhandbuch] fir Informatiounen iwwer de Gebruach vun der Wiki Software.
-== Starthëllefen ==
+== Fir unzefänken ==
* [//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]", # Fuzzy
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mailinglëscht vun neie MediaWiki-Versiounen]
+* [//www.mediawiki.org/wiki/Localisation#Translation_resources Lokaliséiert MediaWiki fir Är Sprooch]",
);
/** Lingua Franca Nova (Lingua Franca Nova)
@@ -12146,7 +12543,7 @@ $messages['lfn'] = array(
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lista de ajustas de la desinia]
* [//www.mediawiki.org/wiki/Manual:FAQ Demandas comun de MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista per receta anunsias de novas supra MediaWiki]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista per receta anunsias de novas supra MediaWiki]', # Fuzzy
);
/** Ganda (Luganda)
@@ -12159,7 +12556,7 @@ $messages['lg'] = array(
== Amagezi agakuyamba okutandika ==
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lukalala lw'eby'enteekateeka yo]
* [//www.mediawiki.org/wiki/Manual:FAQ Ebiter'okubuuzibwa ku MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Wewandise ofunenga amawulire aga email ag'ebifa ku MediaWiki]",
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Wewandise ofunenga amawulire aga email ag'ebifa ku MediaWiki]", # Fuzzy
);
/** Limburgish (Limburgs)
@@ -12172,7 +12569,7 @@ $messages['li'] = array(
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lies mit instellinge]
* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki VGV (FAQ)]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki mailinglies veur nuuj versies]",
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki mailinglies veur nuuj versies]", # Fuzzy
);
/** Lao (ລາວ)
@@ -12183,8 +12580,11 @@ $messages['lo'] = array(
/** Lithuanian (lietuvių)
* @author Eitvys200
+ * @author Mantak111
*/
$messages['lt'] = array(
+ 'config-desc' => 'MediaWiki diegimas',
+ 'config-title' => 'MediaWiki $1 diegimas',
'config-information' => 'Informacija',
'config-your-language' => 'Jūsų kalba:',
'config-wiki-language' => 'Viki kalba:',
@@ -12192,6 +12592,8 @@ $messages['lt'] = array(
'config-continue' => 'Toliau →',
'config-page-language' => 'Kalba',
'config-page-welcome' => 'Sveiki atvykę į MediaWiki!',
+ 'config-page-dbconnect' => 'Prisijungti prie duomenų bazės',
+ 'config-page-dbsettings' => 'Duomenų bazės nustatymai',
'config-page-name' => 'Vardas',
'config-page-options' => 'Parinktys',
'config-page-install' => 'Įdiegti',
@@ -12201,13 +12603,22 @@ $messages['lt'] = array(
'config-page-copying' => 'Kopijuojama',
'config-page-upgradedoc' => 'Atnaujinama',
'config-restart' => 'Taip, paleiskite jį iš naujo',
+ 'config-env-php' => 'PHP $1 yra įdiegtas.',
+ 'config-env-php-toolow' => 'PHP $1 įdiegta.
+TaÄiau, MediaWiki reikia PHP $2 ar naujesnÄ—s.',
+ 'config-db-type' => 'Duomenų bazės tipas:',
+ 'config-db-host' => 'Duomenų bazės serveris:',
+ 'config-db-name' => 'Duomenų bazės pavadinimas:',
+ 'config-db-name-oracle' => 'Duomenų bazės schema:',
+ 'config-db-username' => 'Duomenų bazės vartotojo vardas:',
+ 'config-db-password' => 'Duomenų bazės slaptažodis:',
'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
- 'config-type-ibm_db2' => 'IBM DB2',
+ 'config-db-port' => 'Duomenų bazės prievadas:',
+ 'config-db-schema' => 'MediaWiki schema:',
'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',
@@ -12216,6 +12627,7 @@ $messages['lt'] = array(
'config-project-namespace' => 'Projekto pavadinimas:',
'config-ns-generic' => 'Projektas',
'config-ns-site-name' => 'Toks pat kaip viki pavadinimas: $1',
+ 'config-ns-other-default' => 'ManoWiki',
'config-admin-box' => 'Administratoriaus paskyra',
'config-admin-name' => 'Jūsų vardas:',
'config-admin-password' => 'Slaptažodis:',
@@ -12228,7 +12640,7 @@ $messages['lt'] = array(
'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-wiki' => 'TradicinÄ— viki', # Fuzzy
'config-profile-private' => 'Privati viki',
'config-license-pd' => 'Viešas Domenas',
'config-email-settings' => 'El. pašto nustatymai',
@@ -12239,7 +12651,25 @@ $messages['lt'] = array(
'config-install-step-done' => 'atlikta',
'config-install-step-failed' => 'nepavyko',
'config-install-schema' => 'Kuriama schema',
+ 'config-install-tables' => 'Kuriamos lentelÄ—s',
+ 'config-install-stats' => 'Inicijuojamos statistikos',
'config-install-keys' => 'Generuojami slapti raktai',
+ 'config-install-done' => "'''Sveikiname!'''
+Jūs sėkmingai įdiegėte MediaWiki.
+
+Įdiegimo programa sukūrė <code>LocalSettings.php</code> failą.
+Jame yra visos jūsų konfigūracijos.
+
+Jums reikÄ—s atsisiųsti ir įdÄ—ti jį į savo wiki įdiegimo bazÄ™ (paÄiame kataloge, kaip index.php). Atsisiuntimas turÄ—tų prasidÄ—ti automatiÅ¡kai.
+
+Jei atsisiuntimas nebuvo pasiÅ«lytas, arba jį atÅ¡aukÄ—te, galite iÅ¡ naujo atsisiųsti paspaudÄ™ žemiau esanÄiÄ… nuorodÄ…:
+
+$3
+
+'''Pastaba:''' Jei jūs to nepadarysite dabar, tada šis sukurtas konfigūracijos failas nebus galimas vėliau, jei išeisite iš įdiegimo be atsisiuntimo.
+
+Kai baigsite, jūs galėsite '''[$2 įeiti į savo wiki]'''.",
+ 'config-download-localsettings' => 'Atsisiųsti <code>LocalSettings.php</code>',
'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].
@@ -12275,7 +12705,7 @@ $messages['lv'] = array(
== Pirmie soļi ==
* [//www.mediawiki.org/wiki/Manual:Configuration_settings KonfigurÄcijas iespÄ“ju saraksts]
* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki J&A]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce ParakstÄ«ties uz paziņojumiem par jaunÄm MediaWiki versijÄm]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce ParakstÄ«ties uz paziņojumiem par jaunÄm MediaWiki versijÄm]', # Fuzzy
);
/** Literary Chinese (文言)
@@ -12288,7 +12718,7 @@ $messages['lzh'] = array(
* [//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
);
/** Lazuri (Lazuri)
@@ -12301,7 +12731,7 @@ $messages['lzz'] = array(
== Ağani na gyoç’k’u maxmarepe ==
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Ok'iduÅŸi ayarepeÅŸi liste]
* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki P'anda Na-k'itxu K'itxalape]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki e-mailepeÅŸiÅŸ liste]",
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki e-mailepeÅŸiÅŸ liste]", # Fuzzy
);
/** Maithili (मैथिली)
@@ -12314,7 +12744,7 @@ $messages['mai'] = array(
==पà¥à¤°à¤¾à¤°à¤®à¥à¤­ कोना करी==
* [//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
);
/** Moksha (мокшень)
@@ -12327,7 +12757,7 @@ $messages['mdf'] = 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
);
/** Malagasy (Malagasy)
@@ -12361,7 +12791,7 @@ $messages['mg'] = array(
'config-admin-name' => 'Ny anaranao :',
'config-admin-password' => 'Tenimiafina :',
'config-admin-email' => 'Adiresy imailaka :',
- 'config-profile-wiki' => 'Wiki tsotra',
+ 'config-profile-wiki' => 'Wiki tsotra', # Fuzzy
'config-profile-no-anon' => 'Mila mamorona kaonty',
'config-profile-fishbowl' => 'Mpanova mahazo alalana ihany',
'config-profile-private' => 'Wiki tsy sarababem-bahoaka',
@@ -12385,7 +12815,7 @@ $messages['mg'] = array(
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lisitra ny paramètre de configuration]
* [//www.mediawiki.org/wiki/Manual:FAQ/fr FAQ momba ny MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Resaka momba ny fizaràn'ny MediaWiki]",
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Resaka momba ny fizaràn'ny MediaWiki]", # Fuzzy
);
/** Eastern Mari (олык марий)
@@ -12395,17 +12825,18 @@ $messages['mhr'] = array(
);
/** Minangkabau (Baso Minangkabau)
+ * @author Iwan Novirion
* @author Luthfi94
*/
$messages['min'] = array(
'mainpagetext' => "'''MediaWiki alah tapasang jo sukses'''.",
- 'mainpagedocfooter' => 'Silakan baco [//www.mediawiki.org/wiki/Help:Contents/id Panduan Pangguno] untuak caro panggunoan parangkaik lunak wiki iko.
+ 'mainpagedocfooter' => 'Konsultasian [//meta.wikimedia.org/wiki/Help:Contents/min Panduan Panggunoan] untuak informasi caro panggunoan parangkaik lunak wiki.
== Mamulai panggunoan ==
-
-* [//www.mediawiki.org/wiki/Manual:Configuration_settings/id Dafta pangaturan konfigurasi]
-* [//www.mediawiki.org/wiki/Manual:FAQ/id Dafta patanyoan nan acok diajukan manganai MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Milis rilis MediaWiki]',
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings/id Daftar pangaturan konfigurasi]
+* [//www.mediawiki.org/wiki/Manual:FAQ/id Daftar patanyoan nan acok diajukan manganai MediaWiki]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Milis rilis MediaWiki]
+* [//www.mediawiki.org/wiki/Localisation#Translation_resources Palokalan MediaWiki untuak bahaso Sanak]',
);
/** Macedonian (македонÑки)
@@ -12463,9 +12894,8 @@ $1',
'config-page-existingwiki' => 'ПоÑтоечко вики',
'config-help-restart' => 'Дали Ñакате да ги иÑчиÑтите Ñите зачувани податоци што ги внеÑовте и да ја започнете инÑталацијата одново?',
'config-restart' => 'Да, почни одново',
- 'config-welcome' => '=== Environmental checks ===
-Се вршат оÑновни проверки за да Ñе воÑтанови дали околината е погодна за инÑталирање на МедијаВики.
-Ðко ви затреба помош при инÑталацијата, ќе треба да ги наведете резултатите од овие проверки.',
+ 'config-welcome' => '=== Проверки на околината ===
+Сега ќе Ñе извршиме оÑновни проверки за да Ñе воÑтанови дали околината е погодна за инÑталирање на МедијаВики. Ðе заборавајте да ги приложите овие информации ако барате помош Ñо довршување на инÑталацијата.',
'config-copyright' => "=== ÐвторÑки права и уÑлови ===
$1
@@ -12536,6 +12966,10 @@ $1
Ова е веројатно премалку.
ИнÑталацијата може да не уÑпее!",
'config-ctype' => "'''Фатална грешка''': PHP мора да Ñе ÑоÑтави Ñо поддршка за [http://www.php.net/manual/en/ctype.installation.php додатокот Ctype].",
+ 'config-json' => "'''Кобно:''' PHP беше Ñрочен без поддршка од JSON.
+Ќе мора да го инÑталирате додатокот за JSON во PHP, или додатокот [http://pecl.php.net/package/jsonc PECL jsonc] пред да го инÑталирате МедијаВики.
+* Додатокот за PHP е вклучен во верзиите 5 и 6 на Linux (од Red Hat Enterprise) (CentOS), но мора да Ñе активира во <code>/etc/php.ini</code> или <code>/etc/php.d/json.ini</code>.
+* Ðекои варијанти на Linux излезени по мај 2013 г. не го Ñодржат додатокот за PHP, туку го пакуваат додатокот PECL како <code>php5-json</code> или <code>php-pecl-jsonc</code>.",
'config-xcache' => '[http://xcache.lighttpd.net/ XCache] е инÑталиран',
'config-apc' => '[http://www.php.net/apc APC] е инÑталиран',
'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache] е инÑталиран',
@@ -12544,6 +12978,8 @@ $1
'config-mod-security' => "'''Предупредување''': на вашиот опÑлужувач има овозможено [http://modsecurity.org/ mod_security]. Ðко не е поÑтавено како што треба, ова може да предизвика проблеми кај МедијаВики и други програми што им овозможуваат на кориÑниците да објавуваат произволни Ñодржини.
Погледнете ја [http://modsecurity.org/documentation/ mod_security документацијата] или обратете Ñе кај домаќинот ако наидете на Ñлучајни грешки.",
'config-diff3-bad' => 'GNU diff3 не е пронајден.',
+ 'config-git' => 'Го пронајдов Git програмот за контрола на верзии: <code>$1</code>.',
+ 'config-git-bad' => 'Ðе го пронајдов Git-програмот за контрола на верзии.',
'config-imagemagick' => 'Пронајден е ImageMagick: <code>$1</code>.
Ðко овозможите подигање, тогаш ќе биде овозможена минијатуризација на Ñликите.',
'config-gd' => 'Утврдив дека има вградена GD графичка библиотека.
@@ -12637,7 +13073,6 @@ $1
'config-type-postgres' => 'PostgreSQL',
'config-type-sqlite' => 'SQLite',
'config-type-oracle' => 'Oracle',
- 'config-type-ibm_db2' => 'IBM DB2',
'config-support-info' => 'МедијаВики ги поддржува Ñледниве ÑиÑтеми на бази на податоци:
$1
@@ -12647,18 +13082,16 @@ $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 е комерцијална база на податоциза фирми. ([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',
'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-server-oracle' => 'Ðеважечки TNS „$1“.
+КориÑтете или „TNS Name“ или низата „Easy Connect“ ([http://docs.oracle.com/cd/E11882_01/network.112/e10836/naming.htm Методи на именување за Oracle])',
'config-invalid-db-name' => 'Ðеважечко име на базата „$1“.
КориÑтете Ñамо ASCII-букви (a-z, A-Z), бројки (0-9), долни црти (_) и цртички (-).',
'config-invalid-db-prefix' => 'Ðеважечки Ð¿Ñ€ÐµÑ„Ð¸ÐºÑ Ð·Ð° базата „$1“.
@@ -12714,7 +13147,7 @@ chmod a+w $3</pre>',
'config-upgrade-done-no-regenerate' => 'Ðадградбата заврши.
Сега можете да [$1 почнете да го кориÑтите викито].',
- 'config-regenerate' => 'ПреÑоздај <code>LocalSettings.php</code> →',
+ 'config-regenerate' => 'ПреÑоздај LocalSettings.php →',
'config-show-table-status' => 'Барањето <code>SHOW TABLE STATUS</code> не уÑпеа!',
'config-unknown-collation' => "'''Предупредување:''' Базата кориÑни непрепознаена упатна Ñпоредба.",
'config-db-web-account' => 'Сметка на базата за мрежен приÑтап',
@@ -12733,6 +13166,11 @@ chmod a+w $3</pre>',
Ðко вашата инÑталација на MySQL поддржува InnoDB, тогаш Ñериозно препорачуваме да го кориÑтите него намеÑто MyISAM.
Ðко вашата инÑталација на MySQL не поддржува InnoDB, веројатно дошло време за надградба.",
+ 'config-mysql-only-myisam-dep' => "'''Предупредување:''' MyISAM е единÑтвениот доÑтапен Ñкладишен погон за MySQL, што не Ñе препорачува за употреба Ñо МедијаВики, бидејќи:
+* речиÑи не поддржува иÑтовремено извршување на задачите поради заклучувањето на табелите
+* поподложен е на раÑипувања од другите погони
+* кодната база на МедијаВИки не Ñекогаш работи иÑправно Ñо MyISAM
+Вашата инÑталација на MySQL не поддржува InnoDB. Можеби е време да ја надградите.",
'config-mysql-engine-help' => "'''InnoDB''' речиÑи Ñекогаш е најдобар избор, бидејќи има добра поддршка за едновременоÑÑ‚.
'''MyISAM''' може да е побрз кај инÑталациите наменети за Ñамо еден кориÑник или незапиÑни инÑталации (Ñамо читање).
@@ -12744,7 +13182,6 @@ chmod a+w $3</pre>',
Ова е поефикаÑно отколку TF-8 режимот на MySQL, и ви овозможува да ја кориÑтите целата палета на уникодни знаци.
Во '''UTF-8 режим''', MySQL ќе знае на кој збир знаци припаѓаат вашите податоци, и може Ñоодветно да ги претÑтави и претвори, но нема да ви дозволи да Ñкладиратезнаци над [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes ОÑновната повеќејазична рамнина].",
- 'config-ibm_db2-low-db-pagesize' => "Вашата база на податоци DB2 има оÑновно-зададен табеларен проÑтор Ñо недоволна големина на Ñтраниците. Таа треба да изнеÑува барем '''32 килобајти'''.",
'config-site-name' => 'Име на викито:',
'config-site-name-help' => 'Ова ќе Ñе појавува во заглавната лента на прелиÑтувачот и на разни други меÑта.',
'config-site-name-blank' => 'ВнеÑете име на мрежното меÑто.',
@@ -12754,8 +13191,8 @@ chmod a+w $3</pre>',
'config-ns-other' => 'Друго (наведете)',
'config-ns-other-default' => 'МоеВики',
'config-project-namespace-help' => "По примерот на Википедија, многу викија ги чуваат Ñтраниците Ñо правила на поÑебно меÑто од Ñамите Ñодржини, Ñ‚.е. во „'''проектен именÑки проÑтор'''“.
-Сите наÑлови на Ñтраниците во овој именÑки проÑтор почнуваат Ñо извеÑен префикÑ, којшто можете да го укажете тука.
-По традиција префикÑот произлегува од името на викито, но не Ñмее да Ñодржи интерпункциÑки знаци како „#“ или „:“.",
+Сите наÑлови на Ñтраниците во овој именÑки проÑтор почнуваат Ñо извеÑна претÑтавка, којшто можете да го укажете тука.
+По традиција претÑтавката произлегува од името на викито, но не Ñмее да Ñодржи интерпункциÑки знаци како „#“ или „:“.",
'config-ns-invalid' => 'Ðазначениот именÑки проÑтор „<nowiki>$1</nowiki>“ е неважечки.
Ðазначете друг проектен именÑки проÑтор.',
'config-ns-conflict' => 'Ðаведениот именÑки проÑтор „<nowiki>$1</nowiki>“ Ñе коÑи Ñо оÑновниот именÑки проÑтор на МедијаВики.
@@ -12851,8 +13288,9 @@ chmod a+w $3</pre>',
'config-upload-deleted-help' => 'Одберете во која папка да Ñе архивираат избришаните податотеки.
Ðајдобро би било ако таа не е доÑтапна преку интернет.',
'config-logo' => 'URL за логото:',
- 'config-logo-help' => 'Матичното руво на МедијаВики има проÑтор за лого од 135 x 160 пикÑели над Ñтраничната лента.
-Подигнете Ñлика Ñо Ñоодветна големина, и тука внеÑете ја URL-адреÑата.
+ 'config-logo-help' => 'Матичното руво на МедијаВики има проÑтор за лого од 135x160 пикÑели над Ñтраничната лента.
+
+Можете да употребите <code>$wgStylePath</code> или <code>$wgScriptPath</code> ако вашето лого е релативно на тие патеки.
Ðко не Ñакате да имате лого, тогаш оÑтавете го ова поле празно.',
'config-instantcommons' => 'Овозможи Instant Commons',
@@ -12947,6 +13385,9 @@ $3
'config-download-localsettings' => 'Преземи го <code>LocalSettings.php</code>',
'config-help' => 'помош',
'config-nofile' => 'Податотеката „$1“ не е пронајдена. Да не е избришана?',
+ 'config-extension-link' => 'Дали Ñте знаеле дека вашето вики поддржува [//www.mediawiki.org/wiki/Manual:Extensions додатоци]?
+
+Можете да ги прелиÑтате [//www.mediawiki.org/wiki/Category:Extensions_by_category по категории] или да ја поÑетите [//www.mediawiki.org/wiki/Extension_Matrix матрицата], каде ќе најдете полн ÑпиÑок на додатоци.',
'mainpagetext' => "'''МедијаВики е уÑпешно инÑталиран.'''",
'mainpagedocfooter' => 'Погледнете го [//meta.wikimedia.org/wiki/Help:Contents УпатÑтвото за кориÑници] за подетални иформации како Ñе кориÑти вики-програмот.
@@ -13024,7 +13465,7 @@ $1
'config-connection-error' => '$1.
താഴെ നൽകിയിരികàµà´•àµà´¨àµà´¨ ഹോസàµà´±àµà´±àµ, ഉപയോകàµà´¤àµƒà´¨à´¾à´®à´‚, രഹസàµà´¯à´µà´¾à´•àµà´•àµ à´Žà´¨àµà´¨à´¿à´µ പരിശോധിചàµà´šàµ വീണàµà´Ÿàµà´‚ à´¶àµà´°à´®à´¿à´•àµà´•àµà´•.',
- 'config-regenerate' => '<code>LocalSettings.php</code> à´ªàµà´¨à´ƒà´¸àµƒà´·àµà´Ÿà´¿à´•àµà´•àµà´• →',
+ 'config-regenerate' => 'LocalSettings.php à´ªàµà´¨à´ƒà´¸àµƒà´·àµà´Ÿà´¿à´•àµà´•àµà´• →',
'config-mysql-engine' => 'à´¸àµà´±àµà´±àµ‹à´±àµ‡à´œàµ എൻജിൻ:',
'config-site-name' => 'വികàµà´•à´¿à´¯àµà´Ÿàµ† പേരàµ:',
'config-site-name-help' => 'ഇതൠബàµà´°àµ—സറിനàµà´±àµ† ടൈറàµà´±à´¿àµ½ ബാറിലàµà´‚ മറàµà´±à´¨àµ‡à´•à´‚ ഇടങàµà´™à´³à´¿à´²àµà´‚ à´ªàµà´°à´¦àµ¼à´¶à´¿à´ªàµà´ªà´¿à´•àµà´•à´ªàµà´ªàµ†à´Ÿàµà´‚.',
@@ -13124,7 +13565,7 @@ $messages['mn'] = 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
);
/** Marathi (मराठी)
@@ -13137,7 +13578,7 @@ $messages['mr'] = 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
);
/** Malay (Bahasa Melayu)
@@ -13151,7 +13592,7 @@ $messages['ms'] = array(
'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' => 'Bahasa anda:',
'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.',
@@ -13178,12 +13619,10 @@ Bagaimanapun, MediaWiki memerlukan PHP $2 ke atas.',
'config-unicode-using-utf8' => 'utf8_normalize.so oleh Brion Vibber digunakan untuk penormalan Unicode.',
'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',
@@ -13225,11 +13664,12 @@ Bagaimanapun, MediaWiki memerlukan PHP $2 ke atas.',
'mainpagetext' => "'''MediaWiki telah berjaya dipasang.'''",
'mainpagedocfooter' => 'Sila rujuk [//meta.wikimedia.org/wiki/Help:Contents Panduan Penggunaan] untuk maklumat mengenai penggunaan perisian wiki ini.
-== Untuk bermula ==
+== Permulaan ==
* [//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]', # Fuzzy
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Senarai surat keluaran MediaWiki]
+* [//www.mediawiki.org/wiki/Localisation#Translation_resources Terjemahkan MediaWiki ke dalam bahasa anda]',
);
/** Maltese (Malti)
@@ -13376,10 +13816,10 @@ $messages['nan'] = array(
== 入門 ==
* [//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
);
-/** Norwegian Bokmål (norsk (bokmål)‎)
+/** Norwegian Bokmål (norsk bokmål)
* @author Event
* @author Nghtwlkr
* @author ì•„ë¼
@@ -13610,7 +14050,6 @@ Vurder å plassere databasen et helt annet sted, for eksempel i <code>/var/lib/m
'config-type-postgres' => 'PostgreSQL',
'config-type-sqlite' => 'SQLite',
'config-type-oracle' => 'Oracle',
- 'config-type-ibm_db2' => 'IBM DB2',
'config-support-info' => 'MediaWiki støtter følgende databasesystem:
$1
@@ -13620,12 +14059,10 @@ 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.', # Fuzzy
'config-header-mysql' => 'MySQL-innstillinger',
'config-header-postgres' => 'PostgreSQL-innstillinger',
'config-header-sqlite' => 'SQLite-innstillinger',
'config-header-oracle' => 'Oracle-innstillinger',
- 'config-header-ibm_db2' => 'IBM DB2-innstillinger',
'config-invalid-db-type' => 'Ugyldig databasetype',
'config-missing-db-name' => 'Du må skrive inn en verdi for «Databasenavn»',
'config-missing-db-host' => 'Du må skrive inn en verdi for «Databasevert»',
@@ -13687,7 +14124,7 @@ 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 <code>LocalSettings.php</code> →',
+ 'config-regenerate' => 'Regenerer LocalSettings.php →',
'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',
@@ -13711,7 +14148,6 @@ Dette er mer effektivt enn MySQLs UTF-8 modus og tillater deg å bruke hele spek
I '''UTF-8 mode''' vil MySQL vite hvilket tegnsett dataene dine er i og kan presentere og konvertere det på en riktig måte,
men det vil ikke la deg lagre tegn over «[//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes the Basic Multilingual Plane]».",
- 'config-ibm_db2-low-db-pagesize' => "DB2-databasen din har et standard tabellområde med en utilstrekkelig pagesize. Pagesize må være '''32K''' eller større.",
'config-site-name' => 'Navn på wiki:',
'config-site-name-help' => 'Dette vil vises i tittellinjen i nettleseren og diverse andre steder.',
'config-site-name-blank' => 'Skriv inn et nettstedsnavn.',
@@ -13842,14 +14278,20 @@ For mer informasjon om denne funksjonen, inklusive instruksjoner om hvordan man
);
/** Low German (Plattdüütsch)
+ * @author Joachim Mos
*/
$messages['nds'] = array(
+ 'config-page-name' => 'Naam',
+ 'config-ns-generic' => 'Projekt',
+ 'config-admin-name' => 'Dien Naam:',
+ 'config-admin-password' => 'Passwoord:',
+ 'config-help' => 'Hülp',
'mainpagetext' => "'''De MediaWiki-Software is mit Spood installeert worrn.'''",
'mainpagedocfooter' => 'Kiek de [//meta.wikimedia.org/wiki/MediaWiki_localisation Dokumentatschoon för dat Anpassen vun de Brukerböversiet]
-un dat [//meta.wikimedia.org/wiki/MediaWiki_User%27s_Guide Brukerhandbook] för Hülp to de Bruuk un Konfiguratschoon.',
+un dat [//meta.wikimedia.org/wiki/MediaWiki_User%27s_Guide Brukerhandbook] för Hülp to de Bruuk un Konfiguratschoon.', # Fuzzy
);
-/** Nedersaksisch (Nedersaksisch)
+/** Low Saxon (Netherlands) (Nedersaksies)
* @author Servien
*/
$messages['nds-nl'] = array(
@@ -13859,7 +14301,8 @@ $messages['nds-nl'] = array(
== Meer hulpe ==
* [//www.mediawiki.org/wiki/Help:Configuration_settings Lieste mit instellingen]
* [//www.mediawiki.org/wiki/Help:FAQ MediaWiki-vragen die vake esteld wörden]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki-postlieste veur nieje versies]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki-postlieste veur nieje versies]
+* [//www.mediawiki.org/wiki/Localisation#Translation_resources Maak MediaWiki beschikbaor in joew taal]',
);
/** Nepali (नेपाली)
@@ -13873,7 +14316,7 @@ $messages['ne'] = 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
);
/** Dutch (Nederlands)
@@ -14018,6 +14461,8 @@ 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.",
'config-diff3-bad' => 'GNU diff3 niet aangetroffen.',
+ 'config-git' => 'Versiecontrolesoftware git is aangetroffen: <code>$1</code>',
+ 'config-git-bad' => 'Geen git versiecontrolesoftware aangetroffen.',
'config-imagemagick' => 'ImageMagick aangetroffen: <code>$1</code>.
Het aanmaken van miniaturen van afbeeldingen wordt ingeschakeld als u uploaden inschakelt.',
'config-gd' => 'Ingebouwde GD grafische bibliotheek aangetroffen.
@@ -14115,7 +14560,6 @@ Overweeg om de database op een totaal andere plaats neer te zetten, bijvoorbeeld
'config-type-postgres' => 'PostgreSQL',
'config-type-sqlite' => 'SQLite',
'config-type-oracle' => 'Oracle',
- 'config-type-ibm_db2' => 'IBM DB2',
'config-support-info' => 'MediaWiki ondersteunt de volgende databasesystemen:
$1
@@ -14125,18 +14569,16 @@ 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. ([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',
'config-header-oracle' => 'Oracle-instellingen',
- 'config-header-ibm_db2' => 'Instellingen voor IBM DB2',
'config-invalid-db-type' => 'Ongeldig databasetype',
- 'config-missing-db-name' => 'U moet een waarde ingeven voor "Databasenaam"',
+ 'config-missing-db-name' => 'U moet een waarde opgeven voor "Databasenaam"',
'config-missing-db-host' => 'U moet een waarde invoeren voor "Databaseserver"',
- 'config-missing-db-server-oracle' => 'U moet een waarde voor "Database-TNS" ingeven',
- 'config-invalid-db-server-oracle' => 'Ongeldige database-TMS "$1".
-Gebruik alleen letters (a-z, A-Z), cijfers (0-9) en liggende streepjes (_).',
+ 'config-missing-db-server-oracle' => 'U moet een waarde opgeven voor "Database-TNS"',
+ 'config-invalid-db-server-oracle' => 'Ongeldige database-TNS "$1".
+Gebruik "TNS Names" of een "Easy Connect" tekst ([http://docs.oracle.com/cd/E11882_01/network.112/e10836/naming.htm Oracle naamgevingsmethoden])',
'config-invalid-db-name' => 'Ongeldige databasenaam "$1".
Gebruik alleen letters (a-z, A-Z), cijfers (0-9) en liggende streepjes (_) en streepjes (-).',
'config-invalid-db-prefix' => 'Ongeldig databasevoorvoegsel "$1".
@@ -14194,7 +14636,7 @@ Dit is '''niet aan te raden''' tenzij u problemen hebt met uw wiki.",
'config-upgrade-done-no-regenerate' => 'Het bijwerken is afgerond.
U kunt nu [$1 uw wiki gebruiken].',
- 'config-regenerate' => '<code>LocalSettings.php</code> opnieuw aanmaken →',
+ 'config-regenerate' => 'LocalSettings.php 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',
@@ -14213,6 +14655,12 @@ De gebruiker die u hier opgeeft moet al bestaan.',
Als uw installatie van MySQL InnoDB ondersteunt, gebruik dat dan vooral.
Als uw installatie van MySQL geen ondersteuning heeft voor InnoDB, denk dan na over upgraden.",
+ 'config-mysql-only-myisam-dep' => "'''Waarschuwing:''' MyISAM is enige beschikbare opslagmethode voor MySQL, en deze wordt niet aangeraden voor gebruik met MediaWiki, omdat:
+* er nauwelijks ondersteuning is voor meerdere gelijktijdige transacties omdat tabellen op slot gezet worden;
+* tabellen makkelijker stuk kunnen gaan;
+* de code van MediaWiki niet altijd op de juiste wijze omgaat met MyISAM.
+
+Uw installatie van MySQL heeft geen ondersteuning voor InnoDB. We raden u aan om een meer recente versie te gebruiken.",
'config-mysql-engine-help' => "'''InnoDB''' is vrijwel altijd de beste instelling, omdat deze goed omgaat met meerdere verzoeken tegelijkertijd.
'''MyISAM''' is bij een zeer beperkt aantal gebruikers mogelijk sneller, of als de wiki alleen-lezen is.
@@ -14225,7 +14673,6 @@ Dit is efficiënter dan de UTF-8-modus van MySQL en stelt u in staat de volledig
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.",
- 'config-ibm_db2-low-db-pagesize' => "Uw DB2-database heeft een standaard tablespace met een onvoldoende grote pagesize. De pagesize moet tenminste '''32K''' zijn.",
'config-site-name' => 'Naam van de wiki:',
'config-site-name-help' => 'Deze naam verschijnt in de titelbalk van browsers en op andere plaatsen.',
'config-site-name-blank' => 'Geef een naam op voor de site.',
@@ -14235,11 +14682,11 @@ Het is dat niet mogelijk tekens op te slaan die de \"[//nl.wikipedia.org/wiki/Li
'config-ns-other' => 'Andere (geef aan welke)',
'config-ns-other-default' => 'MijnWiki',
'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 u hier kunt aangeven.
+Alle paginanamen in deze naamruimte beginnen met een bepaald voorvoegsel dat u hier kunt opgeven.
Dit voorvoegsel wordt meestal afgeleid van de naam van de wiki, maar het kan geen bijzondere tekens bevatten als \"#\" of \":\".",
- 'config-ns-invalid' => 'De aangegeven naamruimte "<nowiki>$1</nowiki>" is ongeldig.
+ 'config-ns-invalid' => 'De opgegeven naamruimte "<nowiki>$1</nowiki>" is ongeldig.
Geef een andere naamruimte op.',
- 'config-ns-conflict' => 'De aangegeven naamruimte "<nowiki>$1</nowiki>" conflicteert met een standaard naamruimte in MediaWiki.
+ 'config-ns-conflict' => 'De opgegeven naamruimte "<nowiki>$1</nowiki>" conflicteert met een standaard naamruimte in MediaWiki.
Geef een andere naam op voor de projectnaamruimte.',
'config-admin-box' => 'Beheerdersgebruiker',
'config-admin-name' => 'Uw naam:',
@@ -14335,6 +14782,8 @@ Idealiter is deze map niet via het web te benaderen.',
'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.
+U kunt <code>$wgStylePath</code> of <code>$wgScriptPath</code> gebruiken als uw logo relatief is aan een van deze paden.
+
Als u geen logo wilt gebruiken, kunt u dit veld leeg laten.',
'config-instantcommons' => 'Instant Commons inschakelen',
'config-instantcommons-help' => '[//www.mediawiki.org/wiki/InstantCommons Instant Commons] is functie die het mogelijk maakt om afbeeldingen, geluidsbestanden en andere mediabestanden te gebruiken van de website [//commons.wikimedia.org/ Wikimedia Commons].
@@ -14344,7 +14793,7 @@ 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 Commonslicentie die u wilt gebruiken en klik op "doorgaan".',
+ 'config-cc-not-chosen' => 'Kies 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.
@@ -14369,7 +14818,7 @@ De standaardpoort is 11211.',
Mogelijk moet u aanvullende instellingen maken, maar u kunt deze uitbreidingen nu inschakelen.',
'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.",
+Ga 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 "{{int:config-back}}".',
'config-install-step-done' => 'afgerond',
@@ -14431,6 +14880,8 @@ Na het plaatsen van het bestand met instellingen kunt u '''[$2 uw wiki betreden]
'config-download-localsettings' => '<code>LocalSettings.php</code> downloaden',
'config-help' => 'hulp',
'config-nofile' => 'Het bestand "$1" is niet gevonden. Is het verwijderd?',
+ 'config-extension-link' => 'Weet u dat u [//www.mediawiki.org/wiki/Manual:Extensions uitbreidingen] kunt gebruiken voor uw wiki?
+U kunt [//www.mediawiki.org/wiki/Category:Extensions_by_category uitbreidingen op categorie] bekijken of ga naar de [//www.mediawiki.org/wiki/Extension_Matrix Uitbreidingenmatrix] om de volledige lijst met uitbreidingen te bekijken.',
'mainpagetext' => "'''De installatie van MediaWiki is geslaagd.'''",
'mainpagedocfooter' => 'Raadpleeg de [//meta.wikimedia.org/wiki/NL_Help:Inhoudsopgave handleiding] voor informatie over het gebruik van de wikisoftware.
@@ -14564,9 +15015,9 @@ Overweeg om de database op een totaal andere plaats neer te zetten, bijvoorbeeld
$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-name' => 'Je moet een waarde opgeven 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-missing-db-server-oracle' => 'Je moet een waarde opgeven voor "Database-TNS"',
'config-postgres-old' => 'PostgreSQL $1 of hoger is vereist.
Jij gebruikt $2.',
'config-sqlite-name-help' => 'Kies een naam die je wiki identificeert.
@@ -14595,9 +15046,8 @@ Dit is efficiënter dan de UTF-8-modus van MySQL en stelt je in staat de volledi
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.
+Alle paginanamen in deze naamruimte beginnen met een bepaald voorvoegsel dat je hier kunt opgeven.
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.',
@@ -14641,8 +15091,8 @@ 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".',
+Als je geen logo wilt gebruiken, kan je dit veld leeg laten.', # Fuzzy
+ 'config-cc-not-chosen' => 'Kies 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.
@@ -14651,7 +15101,7 @@ De standaardpoort is 11211.',
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.",
+Ga 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',
@@ -14688,7 +15138,7 @@ Na het plaatsen van het bestand met instellingen kan je '''[$2 je wiki betreden]
* [//www.mediawiki.org/wiki/Localisation#Translation_resources Maak MediaWiki beschikbaar in jouw taal]',
);
-/** Norwegian Nynorsk (norsk (nynorsk)‎)
+/** Norwegian Nynorsk (norsk nynorsk)
* @author Harald Khan
* @author Nghtwlkr
*/
@@ -14735,7 +15185,7 @@ Berre bruk ASCII-bokstavar (a-z, A-Z), tal (0-9) og undestrekar (_).',
==Kome i gang==
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Liste over oppsettsinnstillingar]
* [//www.mediawiki.org/wiki/Manual:FAQ Spørsmål og svar om MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce E-postliste med informasjon om nye MediaWiki-versjonar]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce E-postliste med informasjon om nye MediaWiki-versjonar]', # Fuzzy
);
/** Norwegian (bokmål)‬ (‪norsk (bokmål)‬) */
@@ -14744,8 +15194,61 @@ $messages['no'] = array(
);
/** Occitan (occitan)
+ * @author Cedric31
*/
$messages['oc'] = array(
+ 'config-desc' => 'Lo programa d’installacion de MediaWiki',
+ 'config-title' => 'Installacion de MediaWiki $1',
+ 'config-information' => 'Informacions',
+ 'config-localsettings-key' => 'Clau de mesa a jorn :',
+ 'config-localsettings-badkey' => "La clau qu'avètz provesida es incorrècta",
+ 'config-session-error' => "Error al moment de l'aviada de la sesilha : $1",
+ 'config-your-language' => 'Vòstra lenga :',
+ 'config-wiki-language' => 'Lenga del wiki :',
+ 'config-back' => '↠Retorn',
+ 'config-continue' => 'Contunhar →',
+ 'config-page-language' => 'Lenga',
+ 'config-page-welcome' => 'Benvenguda sus MediaWiki !',
+ 'config-page-dbconnect' => 'Se connectar a la banca de donadas',
+ 'config-page-dbsettings' => 'Paramètres de la banca de donadas',
+ 'config-page-name' => 'Nom',
+ 'config-page-options' => 'Opcions',
+ 'config-page-install' => 'Installar',
+ 'config-page-complete' => 'Acabat !',
+ 'config-page-restart' => 'Reaviar l’installacion',
+ 'config-page-readme' => 'Legissètz-me',
+ 'config-page-releasenotes' => 'Nòtas de version',
+ 'config-page-copying' => 'Còpia',
+ 'config-page-upgradedoc' => 'Mesa a jorn',
+ 'config-page-existingwiki' => 'Wiki existent',
+ 'config-restart' => 'Ã’c, lo reaviar',
+ 'config-env-php' => 'PHP $1 es installat.',
+ 'config-db-host-oracle' => 'Nom TNS de la banca de donadas :',
+ 'config-db-wiki-settings' => 'Identificar aqueste wiki',
+ 'config-db-name' => 'Nom de la banca de donadas :',
+ 'config-db-name-oracle' => 'Esquèma de banca de donadas :',
+ 'config-db-install-account' => "Compte d'utilizaire per l'installacion",
+ 'config-db-username' => "Nom d'utilizaire de la banca de donadas :",
+ 'config-db-password' => 'Senhal de la banca de donadas :',
+ 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 binari',
+ 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
+ 'config-charset-mysql4' => 'MySQL 4.0 retrocompatible UTF-8',
+ 'config-db-port' => 'Pòrt de la banca de donadas :',
+ 'config-db-schema' => 'Esquèma per MediaWiki',
+ 'config-header-mysql' => 'Paramètres de MySQL',
+ 'config-header-postgres' => 'Paramètres de PostgreSQL',
+ 'config-header-sqlite' => 'Paramètres de SQLite',
+ 'config-header-oracle' => 'Paramètres d’Oracle',
+ 'config-mysql-engine' => "Motor d'emmagazinatge :",
+ 'config-mysql-innodb' => 'InnoDB',
+ 'config-mysql-myisam' => 'MyISAM',
+ 'config-mysql-binary' => 'Binari',
+ 'config-mysql-utf8' => 'UTF-8',
+ 'config-site-name' => 'Nom del wiki :',
+ 'config-ns-generic' => 'Projècte',
+ 'config-ns-other-default' => 'MonWiki',
+ 'config-admin-name' => 'Vòstre nom :',
+ 'config-admin-password' => 'Senhal :',
'mainpagetext' => "'''MediaWiki es estat installat amb succès.'''",
'mainpagedocfooter' => "Consultatz lo [//meta.wikimedia.org/wiki/Ajuda:Contengut Guida de l'utilizaire] per mai d'entresenhas sus l'utilizacion d'aqueste logicial.
@@ -14753,10 +15256,10 @@ $messages['oc'] = array(
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lista dels paramètres de configuracion]
* [//www.mediawiki.org/wiki/Manual:FAQ/fr FAQ MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista de discussions de las parucions de MediaWiki]",
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista de discussions de las parucions de MediaWiki]", # Fuzzy
);
-/** Oriya (ଓଡ଼ିଆ)
+/** Oriya (ଓଡ଼ିଆ)
* @author Jnanaranjan Sahu
*/
$messages['or'] = array(
@@ -14791,7 +15294,7 @@ $messages['pam'] = array(
== Pamagumpisa ==
* [//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
);
/** Picard (Picard)
@@ -14815,13 +15318,16 @@ $messages['pdc'] = array(
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lischt vun Gnepp zum Konfiguriere]
* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki-FAQ]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Eposchde-Lischt fer neie MediaWiki-Versione]",
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Eposchde-Lischt fer neie MediaWiki-Versione]", # Fuzzy
);
/** Polish (polski)
* @author Beau
* @author BeginaFelicysym
+ * @author Chrumps
* @author Holek
+ * @author Matma Rex
+ * @author Michał Roszka
* @author Saper
* @author Sp5uhe
* @author Woytecr
@@ -14880,8 +15386,8 @@ Sprawdź plik php.ini i upewnij się, że <code>session.save_path</code> wskazuj
'config-help-restart' => 'Czy chcesz usunąć wszystkie zapisane dane i uruchomić ponownie proces instalacji?',
'config-restart' => 'Tak, zacznij od nowa',
'config-welcome' => '=== Sprawdzenie środowiska instalacji ===
-Wykonywane są podstawowe testy sprawdzające czy to środowisko jest odpowiednie dla instalacji MediaWiki.
-Jeśli potrzebujesz pomocy podczas instalacji załącz wyniki tych testów.',
+Teraz zostaną wykonane podstawowe testy sprawdzające czy to środowisko jest odpowiednie dla instalacji MediaWiki.
+Jeśli potrzebujesz pomocy podczas instalacji, załącz wyniki tych testów.',
'config-copyright' => "=== Prawa autorskie i warunki użytkowania ===
$1
@@ -14958,8 +15464,10 @@ Instalacja może się nie udać!",
'config-mod-security' => "''' Ostrzeżenie ''': Serwer sieci web ma włączone [http://modsecurity.org/ mod_security]. Jeśli niepoprawnie skonfigurowane, może być przyczyną problemów MediaWiki lub innego oprogramowania, które pozwala użytkownikom na wysyłanie dowolnej zawartości.
Sprawdź w [http://modsecurity.org/documentation/ dokumentacji mod_security] lub skontaktuj się z obsługa hosta, jeśli wystąpią losowe błędy.",
'config-diff3-bad' => 'Nie znaleziono GNU diff3.',
+ 'config-git' => 'Znaleziono oprogramowanie kontroli wersji Git: <code>$1</code>.',
+ 'config-git-bad' => 'Oprogramowanie systemu kontroli wersji Git nie zostało znalezione.',
'config-imagemagick' => 'Mamy zainstalowany ImageMagick <code>$1</code>, dzięki czemu będzie można pomniejszać załadowane grafiki.',
- 'config-gd' => 'Mamy wbudowaną bibliotekę graficzną GD, dzięki ceymu będzie można pomniejszać załadowane grafiki.',
+ 'config-gd' => 'Mamy wbudowaną bibliotekę graficzną GD, dzięki czemu będzie można pomniejszać załadowane grafiki.',
'config-no-scaling' => 'Nie można odnaleźć biblioteki GD lub ImageMagick. Nie będzie działać pomniejszanie załadowane grafiki.',
'config-no-uri' => "'''Błąd.''' Nie można określić aktualnego URI.
Instalacja została przerwana.",
@@ -14976,7 +15484,8 @@ 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ć <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-suhosin-max-value-length' => 'Jest zainstalowany Suhosin i ogranicza długość parametru GET <code>length</code> 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żej w <code>php.ini</code> oraz ustawić <code>$wgResourceLoaderMaxQueryLength</code> w <code>LocalSettings.php</code> na tę samą wartość.',
'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.
@@ -15048,7 +15557,6 @@ Zawiera ona nieopracowane dane użytkownika (adresy e-mail, zahaszowane hasła)
Warto rozważyć umieszczenie w bazie danych zupełnie gdzie indziej, na przykład w <code>/var/lib/mediawiki/yourwiki</code> .",
'config-oracle-def-ts' => 'Domyślna przestrzeń tabel',
'config-oracle-temp-ts' => 'Przestrzeń tabel tymczasowych',
- 'config-type-ibm_db2' => 'IBM DB2',
'config-support-info' => 'MediaWiki może współpracować z następującymi systemami baz danych:
$1
@@ -15058,18 +15566,16 @@ 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.', # Fuzzy
'config-header-mysql' => 'Ustawienia MySQL',
'config-header-postgres' => 'Ustawienia PostgreSQL',
'config-header-sqlite' => 'Ustawienia SQLite',
'config-header-oracle' => 'Ustawienia Oracle',
- 'config-header-ibm_db2' => 'ustawienia dla IBM DB2',
'config-invalid-db-type' => 'Nieprawidłowy typ bazy danych',
'config-missing-db-name' => 'Należy wpisać wartość w polu „Nazwa bazy danychâ€',
'config-missing-db-host' => 'Musisz wpisać wartość w polu „Serwer bazy danychâ€',
'config-missing-db-server-oracle' => 'Należy wpisać wartość w polu „Nazwa instancji bazy danych (TNS)â€',
'config-invalid-db-server-oracle' => 'NieprawidÅ‚owa nazwa instancji bazy danych (TNS) „$1â€.
-W nazwie można użyć wyłącznie liter ASCII (a-z, A-Z), cyfr (0-9), podkreślenia (_) i kropek (.).',
+Użyj "TNS Name" lub "Easy Connect" ([http://docs.oracle.com/cd/E11882_01/network.112/e10836/naming.htm Oracle Naming Methods])',
'config-invalid-db-name' => 'NieprawidÅ‚owa nazwa bazy danych „$1â€.
Używaj wyłącznie liter ASCII (a-z, A-Z), cyfr (0-9), podkreślenia (_) lub znaku odejmowania (-).',
'config-invalid-db-prefix' => 'NieprawidÅ‚owy prefiks bazy danych „$1â€.
@@ -15125,7 +15631,7 @@ 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 <code>LocalSettings.php</code> →',
+ 'config-regenerate' => 'Ponowne generowanie LocalSettings.php →',
'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',
@@ -15144,6 +15650,12 @@ Konto, które wskazałeś tutaj musi już istnieć.',
Jeśli instalacja MySQL obsługuje InnoDB, jest wysoce zalecane, by to je wybrać.
Jeśli instalacja MySQL nie obsługuje InnoDB, być może nadszedł czas na jej uaktualnienie.",
+ 'config-mysql-only-myisam-dep' => "'''Ostrzeżenie:''' MyISAM jest jedynym dostępnym mechanizmem składowania dla MySQL, który nie jest zalecany do używania z MediaWiki, ponieważ:
+* słabo obsługuje współbieżność z powodu blokowania tabel
+* jest bardziej skłonny do uszkodzeń niż inne silniki
+* kod MediaWiki nie zawsze traktuje MyISAM jak powinien
+
+Twoja instalacja MySQL nie obsługuje InnoDB, być może jest to czas na aktualizację.",
'config-mysql-engine-help' => "'''InnoDB''' jest prawie zawsze najlepszą opcją, ponieważ posiada dobrą obsługę współbieżności.
'''MyISAM''' może być szybsze w instalacjach pojedynczego użytkownika lub tylko do odczytu.
@@ -15155,7 +15667,6 @@ Bazy danych MyISAM mają tendencję do ulegania uszkodzeniom częściej niż baz
Jest on bardziej wydajny niż tryb UTF-8 w MySQL i pozwala na używanie znaków pełnego zakresu Unicode.
W '''trybie UTF-8''', MySQL będzie znać zestaw znaków w jakim zakodowano dane, można też przedstawić i przekonwertuj je odpowiednio, ale nie pozwoli Ci przechowywać znaków spoza [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes podstawowej płaszczyzny wielojęzyczności].",
- 'config-ibm_db2-low-db-pagesize' => "Baza danych DB2 posiada domyślny obszar tabel z niewystarczającym rozmiarem strony. Wartość rozmiaru strony musi być równa lub powyżej '''32 K'''.",
'config-site-name' => 'Nazwa wiki',
'config-site-name-help' => 'Ten napis pojawi się w pasku tytułowym przeglądarki oraz w różnych innych miejscach.',
'config-site-name-blank' => 'Wprowadź nazwę witryny.',
@@ -15188,7 +15699,7 @@ Podaj innÄ… nazwÄ™.',
'config-admin-error-user' => 'BÅ‚Ä…d wewnÄ™trzny podczas tworzenia konta administratora o nazwie „<nowiki>$1</nowiki>â€.',
'config-admin-error-password' => 'WewnÄ™trzny bÅ‚Ä…d podczas ustawiania hasÅ‚a dla administratora „<nowiki>$1</nowiki>â€: <pre>$2</pre>',
'config-admin-error-bademail' => 'WpisaÅ‚eÅ› nieprawidÅ‚owy adres eâ€mail',
- 'config-subscribe' => 'Zapisz się na [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce listę pocztową z ogłaszaniami o nowych wersjach].',
+ 'config-subscribe' => 'Zapisz się na [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce listę pocztową z ogłoszeniami o nowych wersjach].',
'config-subscribe-help' => 'Jest to lista o małej liczbie wiadomości, wykorzystywana do przesyłania informacji o udostępnieniu nowej wersji oraz istotnych sprawach dotyczących bezpieczeństwa.
Powinieneś zapisać się na tę listę i aktualizować zainstalowane oprogramowanie MediaWiki gdy pojawia się nowa wersja.',
'config-subscribe-noemail' => 'Próbowano subskrybować listę mailingową ogłoszeń wersji bez podania adresu e-mail.
@@ -15198,22 +15709,22 @@ 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', # Fuzzy
+ 'config-profile-wiki' => 'Otwarte wiki',
'config-profile-no-anon' => 'Wymagane utworzenie konta',
'config-profile-fishbowl' => 'Wyłącznie zatwierdzeni edytorzy',
'config-profile-private' => 'Prywatna wiki',
'config-profile-help' => "Strony typu wiki działają najlepiej, gdy pozwolisz je edytować tak wielu osobom, jak to możliwie.
-W MediaWiki, jest łatwe sprawdzenie ostatnich zmian i wycofać szkody, które są spowodowane przez naiwnych lub złośliwych użytkowników.
+W MediaWiki, można łatwo sprawdzić ostatnie zmiany i wycofać szkody, które są spowodowane przez naiwnych lub złośliwych użytkowników.
-Jednakże wielu uznało MediaWiki użytecznymi w różnorodnych rolach, a czasami nie jest łatwo przekonać wszystkich do korzyści ze sposobu działania wiki.
+Jednakże wielu uznało MediaWiki użytecznym w różnorodnych rolach, a czasami nie jest łatwo przekonać wszystkich do korzyści ze sposobu działania wiki. Masz więc wybór.
-Ustawienie '''{{int:config-profil-wiki}}''' pozwala każdemu na edycję, nawet bez logowania się.
-Wiki z '''{{int:config-profile-no-anon}}''' zawiera dodatkowe funkcje rozliczania się, ale może powstrzymać dorywczych współpracowników.
+Ustawienie '''{{int:config-profile-wiki}}''' pozwala każdemu na edycję, nawet bez logowania się.
+Wiki z '''{{int:config-profile-no-anon}}''' zawiera dodatkowe możliwości ale może powstrzymywać potencjalnych edytorów.
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ć.
+Ustawienie '''{{int:config-profile-private}}'' ' pozwala na wyświetlanie stron tylko zatwierdzonym użytkownikom, ta sama grupa może je 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].", # Fuzzy
+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].",
'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',
@@ -15264,6 +15775,8 @@ Najlepiej, aby nie był on dostępny z internetu.',
'config-logo-help' => 'Domyślny motyw MediaWiki zawiera miejsce na logo wielkości 135 x 160 pikseli powyżej menu na pasku bocznym.
Prześlij obrazek o odpowiednim rozmiarze, a następnie wpisz jego URL tutaj.
+Możesz użyć <code>$wgStylePath</code> lub <code>$wgScriptPath</code> jeżeli twoje logo jest relatywne do tych ścieżek.
+
Jeśli nie chcesz logo, pozostaw to pole puste.',
'config-instantcommons' => 'WÅ‚Ä…cz Instant Commons',
'config-instantcommons-help' => '[//www.mediawiki.org/wiki/InstantCommons Instant Commons] jest funkcją, która pozwala wiki używać obrazów, dźwięków i innych mediów znalezionych na witrynie [//commons.wikimedia.org/ Wikimedia Commons].
@@ -15297,8 +15810,8 @@ Jeśli nie znasz numeru portu, wartością domyślną jest 11211.',
Mogą one wymagać dodatkowych czynności konfiguracyjnych, ale można je teraz włączyć',
'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.', # Fuzzy
+ 'config-install-begin' => 'Po naciśnięciu "{{int:config-continue}}", rozpocznie się instalacja MediaWiki.
+Jeśli nadal chcesz dokonać zmian, naciśnij "{{int:config-back}}".',
'config-install-step-done' => 'gotowe',
'config-install-step-failed' => 'nieudane',
'config-install-extensions' => 'WÅ‚Ä…cznie z rozszerzeniami',
@@ -15340,29 +15853,33 @@ Tworzenie domyślnej listy pominięto.",
'config-install-extension-tables' => 'Tworzenie tabel dla aktywnych rozszerzeń',
'config-install-mainpage-failed' => 'Nie udało się wstawić strony głównej – $1',
'config-install-done' => "'''Gratulacje!'''
-Udało ci się zainstalować MediaWiki.
+Udało Ci się zainstalować MediaWiki.
Instalator wygenerował plik konfiguracyjny <code>LocalSettings.php</code>.
-Musisz go pobrać i umieścić go w korzeniu twojej instalacji wiki (tym samym katalogu co index.php). Pobieranie powinno zacząć się automatycznie.
+Musisz go pobrać i umieścić w katalogu głównym Twojej instalacji wiki (tym samym katalogu co index.php). Pobieranie powinno zacząć się automatycznie.
-Jeżeli pobieranie nie zostało zaproponowane, lub jeśli użytkownik anulował je, można ponownie uruchomić pobranie klikając poniższe łącze:
+Jeżeli pobieranie nie zostało zaproponowane lub jeśli użytkownik je anulował, można ponownie uruchomić pobranie klikając poniższe łącze:
$3
-'''Uwaga''': Jeśli tego nie zrobisz tego teraz, wygenerowany plik konfiguracyjny nie będzie już dostępny po zakończeniu instalacji.
+'''Uwaga''': Jeśli 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]'''.",
+Po załadowaniu pliku konfiguracyjnego możesz '''[$2 wejść na wiki]'''.",
'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?',
+ 'config-extension-link' => 'Czy wiesz, że twoja wiki obsługuje [//www.mediawiki.org/wiki/Manual:Extensions/pl rozszerzenia]?
+
+Możesz przejrzeć [//www.mediawiki.org/wiki/Category:Extensions_by_category rozszerzenia według kategorii] lub [//www.mediawiki.org/wiki/Extension_Matrix Extension Matrix] aby zobaczyć pełną listę rozszerzeń.',
'mainpagetext' => "'''Instalacja MediaWiki powiodła się.'''",
'mainpagedocfooter' => 'Zobacz [//meta.wikimedia.org/wiki/Help:Contents przewodnik użytkownika] w celu uzyskania informacji o działaniu oprogramowania 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]', # Fuzzy
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings/pl Lista ustawień konfiguracyjnych]
+* [//www.mediawiki.org/wiki/Manual:FAQ/pl MediaWiki FAQ]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Komunikaty o nowych wersjach MediaWiki]
+* [//www.mediawiki.org/wiki/Localisation#Translation_resources Przetłumacz MediaWiki na swój język]',
);
/** Piedmontese (Piemontèis)
@@ -15592,7 +16109,6 @@ 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
@@ -15602,12 +16118,10 @@ S'a vëd pa listà sì-sota ël sistema ëd base ëd dàit ch'a preuva a dovré,
'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"',
@@ -15667,7 +16181,7 @@ Sòn a l'è '''pa arcomandà''' gavà ch'a rancontra dij problema con soa wiki."
'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-regenerate' => 'Generé torna LocalSettings.php →',
'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à",
@@ -15697,7 +16211,6 @@ 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.",
@@ -15740,18 +16253,18 @@ 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' => 'Deurb wiki',
+ 'config-profile-wiki' => 'Wiki duverta',
'config-profile-no-anon' => 'A venta creé un cont',
'config-profile-fishbowl' => 'Mach editor autorisà',
'config-profile-private' => 'Wiki privà',
'config-profile-help' => "Le wiki a marcio mej quand ch'a lassa che pì përsone possìbij a-j modìfico.
-An MediaWiki, a l'é bel fé revisioné ij cambi recent, e buté andré minca dann che a sia fàit da utent noviss o malissios.
+An MediaWiki, a l'é bel fé revisioné j'ùltime modìfiche, e buté andré qualsëssìa dann che a sia fàit da dj'utent noviss o malissios.
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à.
Ë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.
+Na wiki con '''{{int:config-profile-no-anon}}''' a dà pì 'd contròl, ma a peul slontané dij contributor ocasionaj.
Ë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.
Un '''{{int:config-profile-private}}''' a përmët mach a j'utent aprovà ëd vëdde le pàgine, con la midema partìa ch'a peul modifiché.
@@ -15806,7 +16319,7 @@ Idealment, sòn a dovrìa pa esse acessìbil an sl'aragnà.",
'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.",
+S'a veul gnun-e marche, ch'a lassa ës camp bianch.", # Fuzzy
'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à.
@@ -15839,8 +16352,8 @@ S'a conòsse nen la pòrta, cola predefinìa a l'é 11211.",
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-begin' => "An sgnacand su «{{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",
@@ -15926,7 +16439,7 @@ $messages['prg'] = array(
== En pagaūseņu ==
* [//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
);
/** Pashto (پښتو)
@@ -15940,7 +16453,7 @@ $messages['ps'] = array(
'config-page-welcome' => 'Ù…Ûډياويکي ته ÚšÙ‡ راغلاست!',
'config-page-name' => 'نوم',
'config-page-options' => 'خوښنÛ',
- 'config-page-install' => 'لګول',
+ 'config-page-install' => 'لگول',
'config-page-complete' => 'بشپړ!',
'config-env-php' => 'د $1 PHP نصب شو.',
'config-db-type' => 'د توکبنسټ ډول:',
@@ -15953,20 +16466,19 @@ $messages['ps'] = array(
'config-header-postgres' => 'د PostgreSQL امستنÛ',
'config-header-sqlite' => 'د SQLite امستنÛ',
'config-header-oracle' => 'د اورÛÚ©Ù„ امستنÛ',
- 'config-header-ibm_db2' => 'د IBM DB2 امستنÛ',
'config-sqlite-readonly' => 'د <code>$1</code> دوتنه د ليکلو وړ نه ده.',
'config-sqlite-cant-create-db' => 'د توکبنسټ دوتنه <code>$1</code> جوړه نه شوه.',
'config-site-name' => 'د ويکي نوم:',
'config-site-name-blank' => 'د ÙˆÛبÚÙŠ نوم وليکÛ.',
'config-project-namespace' => 'د Ù¾Ø±ÙˆÚ˜Û Ù†ÙˆÙ…-تشيال:',
'config-ns-generic' => 'پروژه',
- 'config-admin-box' => 'د پازوال ګڼون',
+ 'config-admin-box' => 'د پازوال گڼون',
'config-admin-name' => 'Ø³ØªØ§Ø³Û Ù†ÙˆÙ…:',
'config-admin-password' => 'پټنوم:',
'config-admin-password-confirm' => 'پټنوم يو ÚÙ„ بيا:',
'config-admin-email' => 'برÛښليک پته:',
- 'config-profile-wiki' => 'دوديزه ويکي', # Fuzzy
- 'config-license-pd' => 'ټولګړی شپول',
+ 'config-profile-wiki' => 'Ù¾Ø±Ø§Ù†ÙŠØ³ØªÛ ÙˆÙŠÚ©ÙŠ',
+ 'config-license-pd' => 'ټولگړی شپول',
'config-email-settings' => 'د برÛښليک امستنÛ',
'config-install-step-done' => 'ترسره شو',
'config-install-tables' => 'لښتيالونه جوړول',
@@ -15984,25 +16496,27 @@ $messages['ps'] = array(
/** Portuguese (português)
* @author Crazymadlover
* @author Hamilton Abreu
+ * @author Luckas
* @author Mormegil
* @author Platonides
* @author SandroHc
* @author Waldir
* @author ì•„ë¼
+ * @author 555
*/
$messages['pt'] = array(
'config-desc' => 'O instalador do MediaWiki',
'config-title' => 'Instalação MediaWiki $1',
'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.
+Para atualizar esta instalação, por favor introduza o valor de <code>$wgUpgradeKey</code> na caixa abaixo.
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:',
+Para atualizar esta instalação execute o <code>update.php</code>, por favor.',
+ 'config-localsettings-key' => 'Chave de atualizaçã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 <code>LocalSettings.php</code>:
+Para atualizar esta instalação, por favor coloque a seguinte linha no final do seu <code>LocalSettings.php</code>:
$1',
'config-localsettings-incomplete' => 'O ficheiro <code>LocalSettings.php</code> existente parece estar incompleto.
@@ -16017,17 +16531,17 @@ As sessões estão configuradas para uma duração de $1.
Pode aumentar esta duração configurando <code>session.gc_maxlifetime</code> no php.ini.
Reinicie o processo de instalação.',
'config-no-session' => 'Os seus dados de sessão foram perdidos!
-Verifique o seu php.ini e certifique-se de que em <code>session.save_path</code> está definido um directório apropriado.',
+Verifique o seu php.ini e certifique-se de que em <code>session.save_path</code> está definido um diretório apropriado.',
'config-your-language' => 'A sua língua:',
- 'config-your-language-help' => 'Seleccione a língua que será usada durante o processo de instalação.',
+ 'config-your-language-help' => 'Selecione a língua que será usada durante o processo de instalação.',
'config-wiki-language' => 'Língua da wiki:',
- 'config-wiki-language-help' => 'Seleccione a língua que será predominante na wiki.',
+ 'config-wiki-language-help' => 'Selecione a língua que será predominante na wiki.',
'config-back' => '↠Voltar',
'config-continue' => 'Continuar →',
'config-page-language' => 'Língua',
'config-page-welcome' => 'Bem-vindo(a) ao MediaWiki!',
'config-page-dbconnect' => 'Ligar à base de dados',
- 'config-page-upgrade' => 'Actualizar a instalação existente',
+ 'config-page-upgrade' => 'Atualizar a instalação existente',
'config-page-dbsettings' => 'Configurações da base de dados',
'config-page-name' => 'Nome',
'config-page-options' => 'Opções',
@@ -16037,13 +16551,13 @@ Verifique o seu php.ini e certifique-se de que em <code>session.save_path</code>
'config-page-readme' => 'Leia-me',
'config-page-releasenotes' => 'Notas de lançamento',
'config-page-copying' => 'A copiar',
- 'config-page-upgradedoc' => 'A actualizar',
+ 'config-page-upgradedoc' => 'Atualizando',
'config-page-existingwiki' => 'Wiki existente',
'config-help-restart' => 'Deseja limpar todos os dados gravados que introduziu e reiniciar o processo de instalação?',
'config-restart' => 'Sim, reiniciar',
'config-welcome' => '=== Verificações do ambiente ===
São realizadas verificações básicas para determinar se este ambiente é apropriado para instalação do MediaWiki.
-Se necessitar de pedir ajuda durante a instalação, deve fornecer os resultados destas verificações.',
+Se necessitar de pedir ajuda durante a instalação, deve fornecer os resultados destas verificações.', # Fuzzy
'config-copyright' => "=== Direitos de autor e Condições de uso ===
$1
@@ -16070,35 +16584,35 @@ Não pode instalar o MediaWiki.',
'config-env-php' => 'O PHP $1 está instalado.',
'config-env-php-toolow' => 'O PHP $1 está instalado.
No entanto, o MediaWiki requer o PHP $2 ou superior.',
- 'config-unicode-using-utf8' => 'A usar o utf8_normalize.so, por Brian Viper, para a normalização Unicode.',
+ 'config-unicode-using-utf8' => 'A usar o utf8_normalize.so, por Brion Vibber, para a normalização Unicode.',
'config-unicode-using-intl' => 'A usar a [http://pecl.php.net/intl extensão intl PECL] para a normalização Unicode.',
- 'config-unicode-pure-php-warning' => "'''Aviso''': A [http://pecl.php.net/intl extensão intl PECL] não está disponível para efectuar a normalização Unicode. Irá recorrer-se à implementação em PHP puro, que é mais lenta.
+ 'config-unicode-pure-php-warning' => "'''Aviso''': A [http://pecl.php.net/intl extensão intl PECL] não está disponível para efetuar a normalização Unicode. Irá recorrer-se à implementação em PHP puro, que é mais lenta.
Se o seu site tem alto volume de tráfego, devia informar-se um pouco sobre a [//www.mediawiki.org/wiki/Unicode_normalization_considerations/pt normalização Unicode].",
- 'config-unicode-update-warning' => "'''Aviso''': A versão instalada do wrapper de normalização Unicode usa uma versão mais antiga da biblioteca do [http://site.icu-project.org/ projecto ICU].
-Devia [//www.mediawiki.org/wiki/Unicode_normalization_considerations actualizá-la] se tem quaisquer preocupações sobre o uso do Unicode.",
+ 'config-unicode-update-warning' => "'''Aviso''': A versão instalada do wrapper de normalização Unicode usa uma versão mais antiga da biblioteca do [http://site.icu-project.org/ projeto ICU].
+Devia [//www.mediawiki.org/wiki/Unicode_normalization_considerations atualizá-la] se tem quaisquer preocupações sobre o uso do Unicode.",
'config-no-db' => "Não foi possível encontrar um controlador ''(driver)'' apropriado para a base de dados! Precisa de instalar um controlador para o PHP. São aceites os seguintes tipos de base de dados: $1.
Se usa alojamento partilhado, peça ao fornecedor do alojamento para instalar um controlador apropriado.
-Se foi você quem compilou o PHP, reconfigure-o com um cliente de base de dados activado; por exemplo, usando <code>./configure --with-mysql</code>.
+Se foi você quem compilou o PHP, reconfigure-o com um cliente de base de dados ativado; por exemplo, usando <code>./configure --with-mysql</code>.
Se instalou o PHP a partir de um pacote Debian ou Ubuntu, então precisa de instalar também o módulo php5-mysql.",
'config-outdated-sqlite' => "'''Aviso''': Tem a versão $1 do SQLite, que é anterior à versão mínima necessária, a $2. O SQLite não estará disponível.",
'config-no-fts3' => "'''Aviso''': O SQLite foi compilado sem o módulo [//sqlite.org/fts3.html FTS3]; as funcionalidades de pesquisa não estarão disponíveis nesta instalação.",
- 'config-register-globals' => "'''Aviso: A opção <code>[http://php.net/register_globals register_globals]</code> do PHP está activada.'''
-'''Desactive-a, se puder.'''
+ 'config-register-globals' => "'''Aviso: A opção <code>[http://php.net/register_globals register_globals]</code> do PHP está ativada.'''
+'''Desative-a, se puder.'''
O MediaWiki funciona mesmo assim, mas o seu servidor está exposto a potenciais vulnerabilidades de segurança.",
- 'config-magic-quotes-runtime' => "'''Fatal: A opção [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime] está activada!'''
+ 'config-magic-quotes-runtime' => "'''Fatal: A opção [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime] está ativada!'''
Esta opção causa corrupção dos dados de entrada, de uma forma imprevisível.
-Não pode instalar ou usar o MediaWiki a menos que esta opção seja desactivada.",
- 'config-magic-quotes-sybase' => "'''Fatal: A opção [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase] está activada!'''
+Não pode instalar ou usar o MediaWiki a menos que esta opção seja desativada.",
+ 'config-magic-quotes-sybase' => "'''Fatal: A opção [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase] está ativada!'''
Esta opção causa corrupção dos dados de entrada, de uma forma imprevisível.
-Não pode instalar ou usar o MediaWiki a menos que esta opção seja desactivada.",
- 'config-mbstring' => "'''Fatal: A opção [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload] está activada!'''
+Não pode instalar ou usar o MediaWiki a menos que esta opção seja desativada.",
+ 'config-mbstring' => "'''Fatal: A opção [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload] está ativada!'''
Esta opção causa erros e pode corromper os dados de uma forma imprevisível.
-Não pode instalar ou usar o MediaWiki a menos que esta opção seja desactivada.",
- 'config-ze1' => "'''Fatal: A opção [http://www.php.net/manual/en/ini.core.php zend.ze1_compatibility_mode] está activada!'''
+Não pode instalar ou usar o MediaWiki a menos que esta opção seja desativada.",
+ 'config-ze1' => "'''Fatal: A opção [http://www.php.net/manual/en/ini.core.php zend.ze1_compatibility_mode] está ativada!'''
Esta opção causa problemas significativos no MediaWiki.
-Não pode instalar ou usar o MediaWiki a menos que esta opção seja desactivada.",
- 'config-safe-mode' => "'''Aviso:''' O [http://www.php.net/features.safe-mode safe mode] do PHP está activo.
+Não pode instalar ou usar o MediaWiki a menos que esta opção seja desativada.",
+ 'config-safe-mode' => "'''Aviso:''' O [http://www.php.net/features.safe-mode safe mode] do PHP está ativo.
Este modo pode causar problemas, especialmente no upload de ficheiros e no suporte a <code>math</code>.",
'config-xml-bad' => 'Falta o módulo XML do PHP.
O MediaWiki necessita de funções deste módulo e não funcionará com esta configuração.
@@ -16106,7 +16620,7 @@ Se está a executar o Mandrake, instale o pacote php-xml.',
'config-pcre' => 'Parece faltar o módulo de suporte PCRE.
Para funcionar, o MediaWiki necessita das funções de expressões regulares compatíveis com Perl.',
'config-pcre-no-utf8' => "'''Fatal''': O módulo PCRE do PHP parece ter sido compilado sem suporte PCRE_UTF8.
-O MediaWiki necessita do suporte UTF-8 para funcionar correctamente.",
+O MediaWiki necessita do suporte UTF-8 para funcionar corretamente.",
'config-memory-raised' => 'A configuração <code>memory_limit</code> do PHP era $1; foi aumentada para $2.',
'config-memory-bad' => "'''Aviso:''' A configuração <code>memory_limit</code> do PHP é $1.
Isto é provavelmente demasiado baixo.
@@ -16116,29 +16630,29 @@ A instalação poderá falhar!",
'config-apc' => '[http://www.php.net/apc APC] instalada',
'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache] instalada',
'config-no-cache' => "'''Aviso:''' Não foi possível encontrar: [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache], nem [http://www.iis.net/download/WinCacheForPhp WinCache].
-A cache de objectos não será activada.",
+A cache de objetos não está ativada.",
'config-mod-security' => "'''Aviso''': O seu servidor de internet tem o [http://modsecurity.org/ mod_security] ativado. Se este estiver mal configurado, pode causar problemas ao MediaWiki ou a outros programas, permitindo que os utilizadores publiquem conteúdos arbitrários.
Consulte a [http://modsecurity.org/documentation/ mod_security documentação] ou peça apoio ao fornecedor do alojamento do seu servidor se encontrar erros aleatórios.",
'config-diff3-bad' => 'O GNU diff3 não foi encontrado.',
'config-imagemagick' => 'Foi encontrado o ImageMagick: <code>$1</code>.
-Se possibilitar uploads, a miniaturização de imagens será activada.',
+Se possibilitar uploads, a miniaturização de imagens será ativada.',
'config-gd' => 'Foi encontrada a biblioteca gráfica GD.
-Se possibilitar uploads, a miniaturização de imagens será activada.',
+Se possibilitar uploads, a miniaturização de imagens será ativada.',
'config-no-scaling' => 'Não foi encontrada a biblioteca gráfica GD nem o ImageMagick.
-A miniaturização de imagens será desactivada.',
- 'config-no-uri' => "'''Erro:''' Não foi possível determinar a URI actual.
+A miniaturização de imagens será desativada.',
+ 'config-no-uri' => "'''Erro:''' Não foi possível determinar a URI atual.
A instalação foi abortada.",
'config-no-cli-uri' => "'''Aviso''': Não foi especificado um --scriptpath; por omissão, será usado: <code>$1</code>.",
'config-using-server' => 'Será usado o nome do servidor "<nowiki>$1</nowiki>".',
'config-using-uri' => 'Será usada a URL do servidor "<nowiki>$1$2</nowiki>".',
- 'config-uploads-not-safe' => "'''Aviso:''' O directório por omissão para uploads <code>$1</code>, está vulnerável à execução arbitrária de scripts.
+ 'config-uploads-not-safe' => "'''Aviso:''' O diretório por omissão para uploads <code>$1</code>, está vulnerável à execução arbitrária de scripts.
Embora o MediaWiki verifique a existência de ameaças de segurança em todos os ficheiros enviados, é altamente recomendado que [//www.mediawiki.org/wiki/Manual:Security#Upload_security vede esta vulnerabilidade de segurança] antes de possibilitar uploads.",
- 'config-no-cli-uploads-check' => "'''Aviso:''' O directório por omissão para uploads, <code>\$1</code>, não é verificado para determinar se é vulnerável à execução de código arbitrário durante a instalação por CLI (\"Command-line Interface\").",
+ 'config-no-cli-uploads-check' => "'''Aviso:''' O diretório por omissão para uploads, <code>\$1</code>, não é verificado para determinar se é vulnerável à execução de código arbitrário durante a instalação por CLI (\"Command-line Interface\").",
'config-brokenlibxml' => 'O seu sistema tem uma combinação de versões de PHP e libxml2 conhecida por ser problemática, podendo causar corrupção de dados no MediaWiki e outras aplicações da internet.
-Actualize para o PHP versão 5.2.9 ou posterior e libxml2 versão 2.7.3 ou posterior ([//bugs.php.net/bug.php?id=45996 incidência reportada no PHP]).
+Atualize para o PHP versão 5.2.9 ou posterior e libxml2 versão 2.7.3 ou posterior ([//bugs.php.net/bug.php?id=45996 incidência reportada no PHP]).
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.
+Para resolver este problema, atualize 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 <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:',
@@ -16161,9 +16675,9 @@ Se estiver a usar um servidor partilhado, o fornecedor do alojamento deve poder
'config-db-name-oracle' => "Esquema ''(schema)'' da base de dados:",
'config-db-account-oracle-warn' => "Há três cenários suportados na instalação do servidor de base de dados Oracle:
-Se pretende criar a conta de acesso pela internet na base de dados durante o processo de instalação, forneça como conta para a instalação uma conta com o papel de SYSDBA na base de dados e especifique as credenciais desejadas para a conta de acesso pela internet. Se não pretende criar a conta de acesso pela internet durante a instalação, pode criá-la manualmente e fornecer só essa conta para a instalação (se ela tiver as permissões necessárias para criar os objectos do esquema ''(schema)''). A terceira alternativa é fornecer duas contas diferentes; uma com privilégios de criação e outra com privilégios limitados para o acesso pela internet.
+Se pretende criar a conta de acesso pela internet na base de dados durante o processo de instalação, forneça como conta para a instalação uma conta com o papel de SYSDBA na base de dados e especifique as credenciais desejadas para a conta de acesso pela internet. Se não pretende criar a conta de acesso pela internet durante a instalação, pode criá-la manualmente e fornecer só essa conta para a instalação (se ela tiver as permissões necessárias para criar os objetos do esquema ''(schema)''). A terceira alternativa é fornecer duas contas diferentes; uma com privilégios de criação e outra com privilégios limitados para o acesso pela internet.
-Existe um script para criação de uma conta com os privilégios necessários no directório \"maintenance/oracle/\" desta instalação. Mantenha em mente que usar uma conta com privilégios limitados impossibilita todas as operações de manutenção com a conta padrão.",
+Existe um script para criação de uma conta com os privilégios necessários no diretório \"maintenance/oracle/\" desta instalação. Mantenha em mente que usar uma conta com privilégios limitados impossibilita todas as operações de manutenção com a conta padrão.",
'config-db-install-account' => 'Conta do utilizador para a instalação',
'config-db-username' => 'Nome do utilizador da base de dados:',
'config-db-password' => 'Palavra-chave do utilizador da base de dados:',
@@ -16194,18 +16708,18 @@ mas não lhe permitirá armazenar caracteres acima do [//en.wikipedia.org/wiki/M
'config-mysql-old' => 'É necessário o MySQL $1 ou posterior; tem a versão $2.',
'config-db-port' => 'Porta da base de dados:',
'config-db-schema' => "Esquema ''(schema)'' do MediaWiki",
- 'config-db-schema-help' => 'Normalmente, este esquema ("schema") estará correcto.
+ 'config-db-schema-help' => 'Normalmente, este esquema estará correto.
Altere-o só se souber que precisa de o fazer.',
'config-pg-test-error' => "Não foi possível criar uma ligação à base de dados '''$1''': $2",
- 'config-sqlite-dir' => 'Directório de dados do SQLite:',
+ 'config-sqlite-dir' => 'Diretório de dados do SQLite:',
'config-sqlite-dir-help' => "O SQLite armazena todos os dados num único ficheiro.
-Durante a instalação, o servidor de internet precisa de ter permissão de escrita no directório que especificar.
+Durante a instalação, o servidor de internet precisa de ter permissão de escrita no diretório que especificar.
-Este directório '''não''' deve poder ser acedido directamente da internet, por isso está a ser colocado onde estão os seus ficheiros PHP.
+Este diretório '''não''' deve poder ser acedido diretamente da internet, por isso está a ser colocado onde estão os seus ficheiros PHP.
-Juntamente com o directório, o instalador irá criar um ficheiro <code>.htaccess</code>, mas se esta operação falhar é possível que alguém venha a ter acesso directo à base de dados.
-Isto inclui acesso aos dados dos utilizadores (endereços de correio electrónico, palavras-chave encriptadas), às revisões eliminadas e a outros dados de acesso restrito na wiki.
+Juntamente com o diretório, o instalador irá criar um ficheiro <code>.htaccess</code>, mas se esta operação falhar é possível que alguém venha a ter acesso direto à base de dados.
+Isto inclui acesso aos dados dos utilizadores (endereços de correio eletrónico, palavras-chave encriptadas), às revisões eliminadas e a outros dados de acesso restrito na wiki.
Considere colocar a base de dados num local completamente diferente, como, por exemplo, em <code>/var/lib/mediawiki/asuawiki</code>.",
'config-oracle-def-ts' => 'Tablespace padrão:',
@@ -16214,28 +16728,25 @@ Considere colocar a base de dados num local completamente diferente, como, por e
'config-type-postgres' => 'PostgreSQL',
'config-type-sqlite' => 'SQLite',
'config-type-oracle' => 'Oracle',
- 'config-type-ibm_db2' => 'IBM DB2',
'config-support-info' => 'O MediaWiki suporta as seguintes plataformas de base de dados:
$1
-Se a plataforma que pretende usar não está listada abaixo, siga as instruções nos links acima para activar o suporte.',
+Se a plataforma que pretende usar não está listada abaixo, siga as instruções nos links acima para ativar o suporte.',
'config-support-mysql' => '* $1 é a plataforma primária do MediaWiki e a melhor suportada ([http://www.php.net/manual/en/mysql.installation.php como compilar PHP com suporte MySQL])',
'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.', # Fuzzy
'config-header-mysql' => 'Definições MySQL',
'config-header-postgres' => 'Definições PostgreSQL',
'config-header-sqlite' => 'Definições SQLite',
'config-header-oracle' => 'Definições Oracle',
- 'config-header-ibm_db2' => 'Configurações da IBM DB2',
'config-invalid-db-type' => 'O tipo de base de dados é inválido',
'config-missing-db-name' => 'Tem de introduzir um valor para "Nome da base de dados"',
'config-missing-db-host' => 'Tem de introduzir um valor para "Servidor da base de dados"',
'config-missing-db-server-oracle' => 'Tem de introduzir um valor para "TNS da base de dados"',
'config-invalid-db-server-oracle' => 'O TNS da base de dados, "$1", é inválido.
-Use só letras (a-z, A-Z), algarismos (0-9), sublinhados (_) e pontos (.) dos caracteres ASCII.',
+Use só letras (a-z, A-Z), algarismos (0-9), sublinhados (_) e pontos (.) dos caracteres ASCII.', # Fuzzy
'config-invalid-db-name' => 'O nome da base de dados, "$1", é inválido.
Use só letras (a-z, A-Z), algarismos (0-9), sublinhados (_) e hífens (-) dos caracteres ASCII.',
'config-invalid-db-prefix' => 'O prefixo da base de dados, "$1", é inválido.
@@ -16251,51 +16762,51 @@ Use só letras (a-z, A-Z), algarismos (0-9) e sublinhados (_) dos caracteres ASC
'config-sqlite-name-help' => 'Escolha o nome que identificará a sua wiki.
Não use espaços ou hífens.
Este nome será usado como nome do ficheiro de dados do SQLite.',
- 'config-sqlite-parent-unwritable-group' => 'Não é possível criar o directório de dados <code><nowiki>$1</nowiki></code>, porque o servidor de internet não tem permissão de escrita no directório que o contém <code><nowiki>$2</nowiki></code>.
+ 'config-sqlite-parent-unwritable-group' => 'Não é possível criar o diretório de dados <code><nowiki>$1</nowiki></code>, porque o servidor de internet não tem permissão de escrita no diretório que o contém <code><nowiki>$2</nowiki></code>.
O instalador determinou em que nome de utilizador o seu servidor de internet está a correr.
-Para continuar, configure o directório <code><nowiki>$3</nowiki></code> para poder ser escrito por este utilizador.
+Para continuar, configure o diretório <code><nowiki>$3</nowiki></code> para poder ser escrito por este utilizador.
Para fazê-lo em sistemas Unix ou Linux, use:
<pre>cd $2
mkdir $3
chgrp $4 $3
chmod g+w $3</pre>',
- 'config-sqlite-parent-unwritable-nogroup' => 'Não é possível criar o directório de dados <code><nowiki>$1</nowiki></code>, porque o servidor de internet não tem permissão de escrita no directório que o contém <code><nowiki>$2</nowiki></code>.
+ 'config-sqlite-parent-unwritable-nogroup' => 'Não é possível criar o diretório de dados <code><nowiki>$1</nowiki></code>, porque o servidor de internet não tem permissão de escrita no diretório que o contém <code><nowiki>$2</nowiki></code>.
Não foi possível determinar em que nome de utilizador o seu servidor de internet está a correr.
-Para continuar, configure o directório <code><nowiki>$3</nowiki></code> para que este possa ser globalmente escrito por esse utilizador (e por outros!).
+Para continuar, configure o diretório <code><nowiki>$3</nowiki></code> para que este possa ser globalmente escrito por esse utilizador (e por outros!).
Para fazê-lo em sistemas Unix ou Linux, use:
<pre>cd $2
mkdir $3
chmod a+w $3</pre>',
- 'config-sqlite-mkdir-error' => 'Ocorreu um erro ao criar o directório de dados "$1".
+ 'config-sqlite-mkdir-error' => 'Ocorreu um erro ao criar o diretório de dados "$1".
Verifique a localização e tente novamente.',
- 'config-sqlite-dir-unwritable' => 'Não foi possível escrever no directório "$1".
+ 'config-sqlite-dir-unwritable' => 'Não foi possível escrever no diretório "$1".
Altere as permissões para que ele possa ser escrito pelo servidor de internet e tente novamente.',
'config-sqlite-connection-error' => '$1.
-Verifique o directório de dados e o nome da base de dados abaixo e tente novamente.',
+Verifique o diretório de dados e o nome da base de dados abaixo e tente novamente.',
'config-sqlite-readonly' => 'Não é possivel escrever no ficheiro <code>$1</code>.',
'config-sqlite-cant-create-db' => 'Não foi possível criar o ficheiro da base de dados <code>$1</code>.',
'config-sqlite-fts3-downgrade' => 'O PHP não tem suporte FTS3; a reverter o esquema das tabelas para o anterior',
'config-can-upgrade' => "Esta base de dados contém tabelas do MediaWiki.
-Para actualizá-las para o MediaWiki $1, clique '''Continuar'''.",
- 'config-upgrade-done' => "Actualização terminada.
+Para atualizá-las para o MediaWiki $1, clique '''Continuar'''.",
+ 'config-upgrade-done' => "Atualização terminada.
Agora pode [$1 começar a usar a sua wiki].
Se quiser regenerar o seu ficheiro <code>LocalSettings.php</code>, clique o botão abaixo.
Esta operação '''não é recomendada''' a menos que esteja a ter problemas com a sua wiki.",
- 'config-upgrade-done-no-regenerate' => 'Actualização terminada.
+ 'config-upgrade-done-no-regenerate' => 'Atualização terminada.
Agora pode [$1 começar a usar a sua wiki].',
- 'config-regenerate' => 'Regenerar o <code>LocalSettings.php</code> →',
+ 'config-regenerate' => 'Regenerar o LocalSettings.php →',
'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.',
+ 'config-db-web-help' => 'Selecione 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.',
'config-db-web-account-same' => 'Usar a mesma conta usada na instalação',
'config-db-web-create' => 'Criar a conta se ainda não existir',
'config-db-web-no-create-privs' => 'A conta que especificou para a instalação não tem privilégios suficientes para criar uma conta.
@@ -16303,13 +16814,13 @@ A conta que especificar aqui já tem de existir.',
'config-mysql-engine' => 'Motor de armazenamento:',
'config-mysql-innodb' => 'InnoDB',
'config-mysql-myisam' => 'MyISAM',
- 'config-mysql-myisam-dep' => "'''Aviso''': Seleccionou o MyISAM para motor de armazenamento do MySQL, uma combinação desaconselhada para usar com o MediaWiki porque:
+ 'config-mysql-myisam-dep' => "'''Aviso''': Selecionou o MyISAM para motor de armazenamento do MySQL, uma combinação desaconselhada para usar com o MediaWiki porque:
* praticamente não permite acessos simultâneos, devido aos bloqueios de tabelas
-* o MyISAM é mais susceptível a perdas da integridade dos dados do que outros motores
+* o MyISAM é mais suscetível a perdas da integridade dos dados do que outros motores
* o código do MediaWiki não trabalha devidamente com o MyISAM
Se a sua instalação do MySQL suporta InnoDB, é altamente recomendado que o escolha em vez do MyISAM.
-Se não suporta o InnoDB, talvez esta seja uma boa altura para fazer a actualização para a versão mais recente do MySQL.",
+Se não suporta o InnoDB, talvez esta seja uma boa altura para fazer a atualização para a versão mais recente do MySQL.",
'config-mysql-engine-help' => "'''InnoDB''' é quase sempre a melhor opção, porque suporta bem acessos simultâneos ''(concurrency)''.
'''MyISAM''' pode ser mais rápido no modo de utilizador único ou em instalações somente para leitura.
@@ -16322,22 +16833,21 @@ Isto é mais eficiente do que o modo UTF-8 do MySQL e permite que sejam usados t
No modo '''UTF-8''', o MySQL saberá em que conjunto de caracteres os seus dados estão e pode apresentá-los e convertê-los da forma mais adequada,
mas não lhe permitirá armazenar caracteres acima do [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Plano Multilingue Básico].",
- 'config-ibm_db2-low-db-pagesize' => "A sua base de dados DB2 tem um tablespace padrão com um pagesize insuficiente. O pagesize tem de ser '''32K'' ou maior.",
'config-site-name' => 'Nome da wiki:',
'config-site-name-help' => 'Este nome aparecerá no título da janela do seu browser e em vários outros sítios.',
'config-site-name-blank' => 'Introduza o nome do site.',
- 'config-project-namespace' => 'Espaço nominal do projecto:',
- 'config-ns-generic' => 'Projecto',
+ 'config-project-namespace' => 'Espaço nominal do projeto:',
+ 'config-ns-generic' => 'Projeto',
'config-ns-site-name' => 'O mesmo que o nome da wiki: $1',
'config-ns-other' => 'Outro (especifique)',
'config-ns-other-default' => 'AMinhaWiki',
- 'config-project-namespace-help' => 'Seguindo o exemplo da Wikipedia, muitas wikis mantêm as páginas das suas normas e políticas, separadas das páginas de conteúdo, num "\'\'\'espaço nominal do projecto\'\'\'".
+ 'config-project-namespace-help' => 'Seguindo o exemplo da Wikipedia, muitas wikis mantêm as páginas das suas normas e políticas, separadas das páginas de conteúdo, num "\'\'\'espaço nominal do projeto\'\'\'".
Todos os nomes das páginas neste espaço nominal começam com um determinado prefixo, que pode especificar aqui.
Tradicionalmente, este prefixo deriva do nome da wiki, mas não pode conter caracteres de pontuação, como "#" ou ":".',
'config-ns-invalid' => 'O espaço nominal especificado "<nowiki>$1</nowiki>" é inválido.
-Introduza um espaço nominal de projecto diferente.',
+Introduza um espaço nominal de projeto diferente.',
'config-ns-conflict' => 'O espaço nominal que especificou, "<nowiki>$1</nowiki>", cria um conflito com um dos espaços nominais padrão do MediaWiki.
-Especifique um espaço nominal do projecto diferente.',
+Especifique um espaço nominal do projeto diferente.',
'config-admin-box' => 'Conta de administrador',
'config-admin-name' => 'O seu nome:',
'config-admin-password' => 'Palavra-chave:',
@@ -16357,7 +16867,7 @@ Introduza um nome de utilizador diferente.',
'config-admin-error-bademail' => 'Introduziu um correio electrónico inválido',
'config-subscribe' => 'Subscreva a [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce lista de divulgação de anúncios de lançamento].',
'config-subscribe-help' => 'Esta é uma lista de divulgação de baixo volume para anúncios de lançamento de versões novas, incluindo anúncios de segurança importantes.
-Deve subscrevê-la e actualizar a sua instalação MediaWiki quando são lançadas versões novas.',
+Deve subscrevê-la e atualizar a sua instalação MediaWiki quando são lançadas versões novas.',
'config-subscribe-noemail' => 'Tentou subscrever a lista de divulgação dos anúncios de novas versões, sem fornecer um endereço de correio electrónico.
Para subscrever esta lista de divulgação tem de fornecer um endereço de correio electrónico.',
'config-almost-done' => 'Está quase a terminar!
@@ -16390,7 +16900,7 @@ Após a instalação, estarão disponíveis mais configurações de privilégios
'config-license-cc-0' => 'Creative Commons Zero (Domínio Público)',
'config-license-gfdl' => 'GNU Free Documentation License 1.3 ou posterior',
'config-license-pd' => 'Domínio Público',
- 'config-license-cc-choose' => 'Seleccione uma licença personalizada da Creative Commons',
+ 'config-license-cc-choose' => 'Selecione uma licença personalizada Creative Commons',
'config-license-help' => 'Muitas wikis de acesso público licenciam todas as colaborações com uma [http://freedomdefined.org/Definition licença livre].
Isto ajuda a criar um sentido de propriedade da comunidade e encoraja as colaborações a longo prazo.
Tal não é geralmente necessário nas wikis privadas ou corporativas.
@@ -16401,19 +16911,19 @@ A licença anterior da Wikipédia era a licença GNU Free Documentation License.
A GFDL é uma licença válida, mas de difícil compreensão.
Também é difícil reutilizar conteúdos licenciados com a GFDL.',
'config-email-settings' => 'Definições do correio electrónico',
- 'config-enable-email' => 'Activar mensagens electrónicas de saída',
- 'config-enable-email-help' => 'Se quer que o correio electrónico funcione, as [http://www.php.net/manual/en/mail.configuration.php definições de correio electrónico do PHP] têm de estar configuradas correctamente.
-Se não pretende viabilizar qualquer funcionalidade de correio electrónico, pode desactivá-lo aqui.',
- 'config-email-user' => 'Activar mensagens electrónicas entre utilizadores',
- 'config-email-user-help' => 'Permitir que todos os utilizadores troquem entre si mensagens de correio electrónico, se tiverem activado esta funcionalidade nas suas preferências.',
- 'config-email-usertalk' => 'Activar notificações de alterações à página de discussão dos utilizadores',
- 'config-email-usertalk-help' => 'Permitir que os utilizadores recebam notificações de alterações à sua página de discussão, se tiverem activado esta funcionalidade nas suas preferências.',
- 'config-email-watchlist' => 'Activar notificação de alterações às páginas vigiadas',
- 'config-email-watchlist-help' => 'Permitir que os utilizadores recebam notificações de alterações às suas páginas vigiadas, se tiverem activado esta funcionalidade nas suas preferências.',
- 'config-email-auth' => 'Activar autenticação do correio electrónico',
- 'config-email-auth-help' => "Se esta opção for activada, os utilizadores têm de confirmar o seu endereço de correio electrónico usando um link que lhes é enviado sempre que o definirem ou alterarem.
-Só os endereços de correio electrónico autenticados podem receber mensagens electrónicas dos outros utilizadores ou alterar as mensagens de notificação.
-É '''recomendado''' que esta opção seja activada nas wikis de acesso público para impedir o uso abusivo das funcionalidades de correio electrónico.",
+ 'config-enable-email' => 'Ativar mensagens eletrónicas de saída',
+ 'config-enable-email-help' => 'Se quer que o correio eletrónico funcione, as [http://www.php.net/manual/en/mail.configuration.php definições de correio eletrónico do PHP] têm de estar configuradas corretamente.
+Se não pretende viabilizar qualquer funcionalidade de correio eletrónico, pode desativá-lo aqui.',
+ 'config-email-user' => 'Ativar mensagens eletrónicas entre utilizadores',
+ 'config-email-user-help' => 'Permitir que todos os utilizadores troquem entre si mensagens de correio eletrónico, se tiverem ativado esta funcionalidade nas suas preferências.',
+ 'config-email-usertalk' => 'Ativar notificações de alterações à página de discussão dos utilizadores',
+ 'config-email-usertalk-help' => 'Permitir que os utilizadores recebam notificações de alterações à sua página de discussão, se tiverem ativado esta funcionalidade nas suas preferências.',
+ 'config-email-watchlist' => 'Ativar notificação de alterações às páginas vigiadas',
+ 'config-email-watchlist-help' => 'Permitir que os utilizadores recebam notificações de alterações às suas páginas vigiadas, se tiverem ativado esta funcionalidade nas suas preferências.',
+ 'config-email-auth' => 'Ativar autenticação do correio eletrónico',
+ 'config-email-auth-help' => "Se esta opção for ativada, os utilizadores têm de confirmar o seu endereço de correio eletrónico usando um link que lhes é enviado sempre que o definirem ou alterarem.
+Só os endereços de correio eletrónico autenticados podem receber mensagens eletrónicas dos outros utilizadores ou alterar as mensagens de notificação.
+É '''recomendado''' que esta opção seja ativada nas wikis de acesso público para impedir o uso abusivo das funcionalidades de correio eletrónico.",
'config-email-sender' => 'Endereço de correio electrónico de retorno:',
'config-email-sender-help' => 'Introduza o endereço de correio electrónico que será usado como endereço de retorno nas mensagens electrónicas de saída.
É para este endereço que serão enviadas as mensagens que não podem ser entregues.
@@ -16421,19 +16931,19 @@ Muitos servidores de correio electrónico exigem que pelo menos a parte do nome
'config-upload-settings' => 'Upload de imagens e ficheiros',
'config-upload-enable' => 'Possibilitar o upload de ficheiros',
'config-upload-help' => 'O upload de ficheiros expõe o seu servidor a riscos de segurança.
-Para mais informações, leia a [//www.mediawiki.org/wiki/Manual:Security secção sobre segurança] do Manual Técnico.
+Para mais informações, leia a [//www.mediawiki.org/wiki/Manual:Security seção sobre segurança] do Manual Técnico.
-Para permitir o upload de ficheiros, altere as permissões do subdirectório <code>images</code> no directório de raiz do MediaWik para que o servidor de internet possa escrever nele.
-Depois active esta opção.',
- 'config-upload-deleted' => 'Directório para os ficheiros apagados:',
- 'config-upload-deleted-help' => 'Escolha um directório onde serão arquivados os ficheiros apagados.
-O ideal é que este directório não possa ser directamente acedido a partir da internet.',
+Para permitir o upload de ficheiros, altere as permissões do subdiretório <code>images</code> no diretório de raiz do MediaWiki para que o servidor de internet possa escrever nele.
+Depois ative esta opção.',
+ 'config-upload-deleted' => 'Diretório para os ficheiros apagados:',
+ 'config-upload-deleted-help' => 'Escolha um diretório onde serão arquivados os ficheiros apagados.
+O ideal é que este diretório não possa ser diretamente acedido a partir da internet.',
'config-logo' => 'URL do logótipo:',
'config-logo-help' => 'O tema padrão do MediaWiki inclui espaço para um logótipo de 135x160 pixels acima do menu da barra lateral.
Coloque na wiki uma imagem com estas dimensões e introduza aqui a URL dessa imagem.
-Se não pretende usar um logótipo, deixe este campo em branco.',
- 'config-instantcommons' => 'Activar a funcionalidade Instant Commons',
+Se não pretende usar um logótipo, deixe este campo em branco.', # Fuzzy
+ 'config-instantcommons' => 'Ativar Instant Commons',
'config-instantcommons-help' => 'O [//www.mediawiki.org/wiki/InstantCommons Instant Commons] é uma funcionalidade que permite que as wikis usem imagens, áudio e outros ficheiros multimédia disponíveis no site [//commons.wikimedia.org/ Wikimedia Commons].
Para poder usá-los, o MediaWiki necessita de acesso à internet.
@@ -16443,26 +16953,26 @@ Introduza o nome da licença manualmente.',
'config-cc-again' => 'Escolha outra vez...',
'config-cc-not-chosen' => 'Escolha a licença da Creative Commons que pretende e clique "continuar".',
'config-advanced-settings' => 'Configuração avançada',
- 'config-cache-options' => 'Definições da cache de objectos:',
- 'config-cache-help' => 'A cache de objectos é usada para melhorar o desempenho do MediaWiki. Armazena dados usados com frequência.
-Sites de tamanho médio ou grande são altamente encorajados a activar esta funcionalidade e os sites pequenos também terão alguns benefícios em fazê-lo.',
+ 'config-cache-options' => 'Configuração da cache de objetos:',
+ 'config-cache-help' => 'A cache de objetos é usada para melhorar o desempenho do MediaWiki. Armazena dados usados com frequência.
+Sites de tamanho médio ou grande são altamente encorajados a ativar esta funcionalidade e os sites pequenos também terão alguns benefícios em fazê-lo.',
'config-cache-none' => 'Sem cache (não é removida nenhuma funcionalidade, mas a velocidade de operação pode ser afectada nas wikis grandes)',
- 'config-cache-accel' => 'Cache de objectos do PHP (APC, XCache ou WinCache)',
+ 'config-cache-accel' => 'Cache de objetos do PHP (APC, XCache ou WinCache)',
'config-cache-memcached' => 'Usar Memcached (requer instalação e configurações adicionais)',
'config-memcached-servers' => 'Servidores Memcached:',
'config-memcached-help' => 'Lista de endereços IP que serão usados para o Memcached.
Deve-se colocar um por linha e indicar a porta a utilizar. Por exemplo:
127.0.0.1:11211
192.168.1.25:1234',
- 'config-memcache-needservers' => 'Seleccionou o Memcached como tipo de chache, mas não especificou nenhum servidor.',
+ 'config-memcache-needservers' => 'Selecionou o Memcached como tipo de chache, mas não especificou nenhum servidor.',
'config-memcache-badip' => 'Introduziu um endereço IP inválido para o Memcached: $1.',
'config-memcache-noport' => 'Não especificou a porta a usar para o servidor Memcached: $1.
Se não sabe qual é a porta, a predefinida é a 11211.',
'config-memcache-badport' => 'Os números das portas do Memcached devem estar entre $1 e $2.',
'config-extensions' => 'Extensões',
- 'config-extensions-help' => 'Foi detectada a existência das extensões listadas acima, no seu directório <code>./extensions</code>.
+ 'config-extensions-help' => 'Foi detectada a existência das extensões listadas acima, no seu diretório <code>./extensions</code>.
-Estas talvez necessitem de configurações adicionais, mas pode activá-las agora',
+Estas talvez necessitem de configurações adicionais, mas pode ativá-las agora',
'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.
@@ -16480,7 +16990,7 @@ Certifique-se de que o utilizador "$1" pode escrever no esquema \'\'(schema)\'\'
'config-pg-no-plpgsql' => 'É preciso instalar a linguagem PL/pgSQL na base de dados $1',
'config-pg-no-create-privs' => 'A conta que especificou para a instalação não tem privilégios suficientes para criar uma conta.',
'config-pg-not-in-role' => 'A conta que especificou para o utilizador da internet já existe.
-A conta que especificou para a instalação não é a de um super-utilizador e não pertence ao grupo de utilizadores de acesso pela internet, por isso não pode criar objectos que pertencem ao utilizador da internet.
+A conta que especificou para a instalação não é a de um super-utilizador e não pertence ao grupo de utilizadores de acesso pela internet, por isso não pode criar objetos que pertencem ao utilizador da internet.
O MediaWiki necessita que as tabelas pertençam ao utilizador da internet. Especifique outra conta de internet, ou clique "voltar" e especifique um utilizador com os privilégios necessários para a instalação.',
'config-install-user' => 'A criar o utilizador da base de dados',
@@ -16489,7 +16999,7 @@ O MediaWiki necessita que as tabelas pertençam ao utilizador da internet. Espec
'config-install-user-grant-failed' => 'A atribuição das permissões ao utilizador "$1" falhou: $2',
'config-install-user-missing' => 'O utilizador especificado, "$1", não existe.',
'config-install-user-missing-create' => 'O utilizador especificado, "$1", não existe.
-Marque a caixa de selecção "criar conta" abaixo se pretende criá-la, por favor.',
+Marque a caixa de seleção "criar conta" abaixo se pretende criá-la, por favor.',
'config-install-tables' => 'A criar as tabelas',
'config-install-tables-exist' => "'''Aviso''': As tabelas do MediaWiki parecem já existir.
A criação das tabelas será saltada.",
@@ -16505,7 +17015,7 @@ O preenchimento padrão desta tabela será saltado.",
'config-install-subscribe-fail' => 'Não foi possível subscrever a lista mediawiki-announce: $1',
'config-install-subscribe-notpossible' => 'cURL não está instalado e allow_url_fopen não está disponível.',
'config-install-mainpage' => 'A criar a página principal com o conteúdo padrão.',
- 'config-install-extension-tables' => 'A criar as tabelas das extensões activadas',
+ 'config-install-extension-tables' => 'A criar as tabelas das extensões ativadas',
'config-install-mainpage-failed' => 'Não foi possível inserir a página principal: $1',
'config-install-done' => "'''Parabéns!'''
Terminou a instalação do MediaWiki.
@@ -16513,7 +17023,7 @@ Terminou a instalação do MediaWiki.
O instalador gerou um ficheiro <code>LocalSettings.php</code>.
Este ficheiro contém todas as configurações.
-Precisa de fazer o download do ficheiro e colocá-lo no directório de raiz da sua instalação (o mesmo directório onde está o ficheiro index.php). Este download deverá ter sido iniciado automaticamente.
+Precisa de fazer o download do ficheiro e colocá-lo no diretório de raiz da sua instalação (o mesmo diretório onde está o ficheiro index.php). Este download deverá ter sido iniciado automaticamente.
Se o download não foi iniciado, ou se o cancelou, pode recomeçá-lo clicando o link abaixo:
@@ -16536,8 +17046,10 @@ Depois de terminar o passo anterior, pode '''[$2 entrar na wiki]'''.",
);
/** Brazilian Portuguese (português do Brasil)
+ * @author Cainamarques
* @author Giro720
* @author Gustavo
+ * @author Luckas
* @author Marcionunes
* @author 555
*/
@@ -16549,13 +17061,19 @@ $messages['pt-br'] = array(
Para atualizar esta instalação, insira no box abaixo o valor de <code>$wgUpgradeKey</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>',
+Atualize esta instalação executando o arquivo <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 <code>LocalSettings.php</code>:
+Para atualizar esta instalação, insira a seguinte linha na parte inferior do seu <code>LocalSettings.php</code>:
+
+$1',
+ 'config-localsettings-incomplete' => 'O arquivo <code>LocalSettings.php</code> parece incompleto.
+A variável $1 não está definida.
+Altere seu <code>LocalSettings.php</code> com a definição dessa variável e clique em "{{int:Config-continue}}".',
+ 'config-localsettings-connection-error' => 'Ocorreu um erro ao conectar ao banco de dados através das configurações presentes ou no <code>LocalSettings.php</code> ou no <code>AdminSettings.php</code>. Corrija essas configurações e tente novamente.
-$ 1', # Fuzzy
+$1',
'config-session-error' => 'Erro ao iniciar a sessão: $1',
'config-session-expired' => 'Os seus dados de sessão parecem ter expirado.
As sessões estão configuradas para uma duração de $1.
@@ -16563,31 +17081,32 @@ Você pode aumentar esta duração configurando <code>session.gc_maxlifetime</co
Reinicie o processo de instalação.',
'config-no-session' => 'Os seus dados de sessão foram perdidos!
Verifique o seu php.ini e certifique-se de que em <code>session.save_path</code> está definido um diretório apropriado.',
- 'config-your-language' => 'A sua língua:',
- 'config-your-language-help' => 'Selecione a língua que será usada durante o processo de instalação.',
- 'config-wiki-language' => 'Língua da wiki:',
- 'config-wiki-language-help' => 'Selecione a língua que será predominante na wiki.',
+ 'config-your-language' => 'Seu idioma:',
+ 'config-your-language-help' => 'Selecione o idioma que será usado durante o processo de instalação.',
+ 'config-wiki-language' => 'Idioma do wiki:',
+ 'config-wiki-language-help' => 'Selecione o idioma em que o wiki será predominantemente escrito.',
'config-back' => '↠Voltar',
'config-continue' => 'Continuar →',
- 'config-page-language' => 'Língua',
+ 'config-page-language' => 'Idioma',
'config-page-welcome' => 'Bem-vindo(a) ao MediaWiki!',
- 'config-page-dbconnect' => 'Ligar à base de dados',
+ 'config-page-dbconnect' => 'Conectar ao banco de dados',
'config-page-upgrade' => 'Atualizar a instalação existente',
- 'config-page-dbsettings' => 'Configurações da base de dados',
+ 'config-page-dbsettings' => 'Configurações do banco de dados',
'config-page-name' => 'Nome',
'config-page-options' => 'Opções',
'config-page-install' => 'Instalar',
- 'config-page-complete' => 'Terminado!',
+ 'config-page-complete' => 'Concluído!',
'config-page-restart' => 'Reiniciar a instalação',
'config-page-readme' => 'Leia-me',
'config-page-releasenotes' => 'Notas de lançamento',
'config-page-copying' => 'Copiando',
'config-page-upgradedoc' => 'Atualizando',
+ 'config-page-existingwiki' => 'Wiki existente',
'config-help-restart' => 'Deseja limpar todos os dados salvos que você introduziu e reiniciar o processo de instalação?',
'config-restart' => 'Sim, reiniciar',
- 'config-welcome' => '=== Verificações do ambiente ===
-São realizadas verificações básicas para determinar se este ambiente é apropriado para instalação do MediaWiki.
-Você deverá fornecer os resultados destas verificações se você precisar de ajuda durante a instalação.',
+ 'config-welcome' => '=== Verificações de ambiente ===
+São realizadas verificações básicas para determinar se este ambiente é apropriado para a instalação do MediaWiki.
+Lembre-se de incluir estas informações se for procurar por suporte para a conclusão da instalação.',
'config-copyright' => "=== Direitos autorais e Termos de uso ===
$1
@@ -16598,28 +17117,52 @@ Este programa é distribuído na esperança de que seja útil, mas '''sem qualqu
Consulte a licença GNU General Public License para mais detalhes.
Em conjunto com este programa você deve ter recebido <doclink href=Copying>uma cópia da licença GNU General Public License</doclink>; se não a recebeu, peça-a por escrito para Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA ou [http://www.gnu.org/copyleft/gpl.html leia-a na internet].",
- 'config-sidebar' => '* [//www.mediawiki.org/wiki/MediaWiki/pt Página principal do MediaWiki]
-* [//www.mediawiki.org/wiki/Help:Contents/pt Ajuda]
-* [//www.mediawiki.org/wiki/Manual:Contents/pt Manual técnico]
-* [//www.mediawiki.org/wiki/Manual:FAQ FAQ]', # Fuzzy
+ 'config-sidebar' => '* [//www.mediawiki.org/wiki/MediaWiki Página principal do MediaWiki]
+* [//www.mediawiki.org/wiki/Help:Contents Manual de uso]
+* [//www.mediawiki.org/wiki/Manual:Contents Manual administrativo]
+* [//www.mediawiki.org/wiki/Manual:FAQ FAQ]
+----
+* <doclink href=Readme>Leia-me</doclink>
+* <doclink href=ReleaseNotes>Notas de lançamento</doclink>
+* <doclink href=Copying>Licença</doclink>
+* <doclink href=UpgradeDoc>Como fazer upgrade</doclink>',
'config-env-good' => 'O ambiente foi verificado.
Você pode instalar o MediaWiki.',
'config-env-bad' => 'O ambiente foi verificado.
Você não pode instalar o MediaWiki.',
'config-env-php' => 'O PHP $1 está instalado.',
- 'config-unicode-using-utf8' => 'A usar o utf8_normalize.so, de Brian Viper, para a normalização Unicode.',
+ 'config-unicode-using-utf8' => 'Usando o utf8_normalize.so, de Brion Vibber, para a normalização Unicode.',
'config-unicode-using-intl' => 'Usando a [http://pecl.php.net/intl extensão intl PECL] para a normalização Unicode.',
- 'config-unicode-pure-php-warning' => "'''Aviso''': A [http://pecl.php.net/intl extensão intl PECL] não está disponível para efetuar a normalização Unicode.
-Se o seu site tem um alto volume de tráfego, devia informar-se um pouco sobre a [//www.mediawiki.org/wiki/Unicode_normalization_considerations normalização Unicode].", # Fuzzy
- 'config-no-db' => 'Não foi possível encontrar um driver de banco de dados adequado!', # Fuzzy
+ 'config-unicode-pure-php-warning' => "'''Aviso''': A [http://pecl.php.net/intl extensão intl PECL] não está disponível para efetuar a normalização Unicode sendo usada, em seu lugar, a lenta implementação de PHP puro.
+Se o seu site tem um alto volume de tráfego, informe-se sobre a [//www.mediawiki.org/wiki/Unicode_normalization_considerations normalização Unicode].",
+ 'config-no-db' => 'Não foi possível encontrar um driver de banco de dados adequado! É necessário instalar um driver de banco de dados para o PHP.
+São suportados os seguintes tipos de bancos de dados: $1.
+
+Se estiver em uma hospedagem partilhada, peça à sua empresa de hospedagem para instalar um driver de banco de dados adequado.
+Se você mesmo tiver compilado o PHP, reconfigure-o com um cliente de banco de dados ativado usando, por exemplo, <code>./configure --with-mysql</code>.
+Se você instalou o PHP a partir de um pacote do Debian ou do Ubuntu, instale também o módulo php5-mysql.',
'config-no-fts3' => "' ' 'Aviso' ' ': O SQLite foi compilado sem o módulo [//sqlite.org/fts3.html FTS3]; as funcionalidades de pesquisa não estarão disponíveis nesta instalação.",
'config-register-globals' => "' ' 'Aviso: A opção <code>[http://php.net/register_globals register_globals]</code> do PHP está ativada.'''
' ' 'Desative-a, se puder.'''
O MediaWiki funcionará mesmo assim, mas o seu servidor ficará exposto a potenciais vulnerabilidades de segurança.",
- 'config-logo-help' => 'O tema padrão do MediaWiki inclui espaço para um logotipo de 135x160 pixels no canto superior esquerdo.
-Faça o upload de uma imagem com estas dimensões e introduza aqui a URL dessa imagem.
+ 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 binary',
+ 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
+ 'config-mysql-innodb' => 'InnoDB',
+ 'config-mysql-myisam' => 'MyISAM',
+ 'config-mysql-binary' => 'Binary',
+ 'config-mysql-utf8' => 'UTF-8',
+ 'config-ns-generic' => 'Projeto',
+ 'config-admin-box' => 'Conta de administrador',
+ 'config-admin-name' => 'Seu nome:',
+ 'config-admin-password' => 'Senha:',
+ 'config-license-pd' => 'Domínio público',
+ 'config-logo-help' => 'Faça o upload de uma imagem de tamanho adequado e insira seu URL aqui.
-Se você não pretende usar um logotipo, deixe este campo em branco.', # Fuzzy
+Você pode usar <code>$wgStylePath</code> ou <code>$wgScriptPath</code> se o seu logotipo for associado a esses diretórios.',
+ 'config-advanced-settings' => 'Configuração avançada',
+ 'config-extensions' => 'Extensões',
+ 'config-install-step-done' => 'feito',
+ 'config-help' => 'ajuda',
'mainpagetext' => "'''MediaWiki instalado com sucesso.'''",
'mainpagedocfooter' => 'Consulte o [//meta.wikimedia.org/wiki/Help:Contents Manual de Usuário] para informações de como usar o software wiki.
@@ -16627,7 +17170,8 @@ 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]', # Fuzzy
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista de discussão com avisos de novas versões do MediaWiki]
+* [//www.mediawiki.org/wiki/Localisation#Translation_resources Traduza o MediaWiki para seu idioma]',
);
/** Quechua (Runa Simi)
@@ -16672,7 +17216,7 @@ $messages['rm'] = array(
== Cumenzar ==
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Glista da las opziuns per la configuraziun]
* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Glista da mail da MediaWiki cun annunzias da novas versiuns]",
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Glista da mail da MediaWiki cun annunzias da novas versiuns]", # Fuzzy
);
/** Romanian (română)
@@ -16729,12 +17273,10 @@ Totuși, MediaWiki necesită PHP $2 sau mai nou.',
'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.
@@ -16743,7 +17285,7 @@ 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-regenerate' => 'Regenerare LocalSettings.php →',
'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',
@@ -16829,8 +17371,29 @@ $messages['roa-tara'] = array(
'config-title' => 'Installazzione de MediaUicchi $1',
'config-information' => "'Mbormaziune",
'config-localsettings-key' => 'Chiave de aggiornamende:',
+ 'config-localsettings-badkey' => "'A chiave ca è date non g'è corrette.",
+ 'config-session-error' => "Errore facenne accumenzà 'a sessione: $1",
+ 'config-your-language' => "'A lènga toje:",
+ 'config-your-language-help' => "Scacchie 'na lènghe da ausà duranne 'u processe de installazzione:",
+ 'config-wiki-language' => 'Lènga de Uicchi:',
+ 'config-back' => '↠Rrète',
+ 'config-continue' => 'Condinue →',
'config-page-language' => 'Lènghe',
+ 'config-page-welcome' => "Bovègne jndr'à MediaUicchi!",
+ 'config-page-dbconnect' => "Collegate a 'u database",
+ 'config-page-upgrade' => "Aggiorne l'installazzione esistende",
+ 'config-page-dbsettings' => "'Mbostaziune d'u database",
'config-page-name' => 'Nome',
+ 'config-page-options' => 'Opziune',
+ 'config-page-install' => 'Installe',
+ 'config-page-complete' => 'Combletate!',
+ 'config-page-restart' => "Riavvie l'installazzione",
+ 'config-page-readme' => 'Liggeme',
+ 'config-page-releasenotes' => 'Note de rilasce',
+ 'config-page-copying' => 'Stoche a copie',
+ 'config-page-upgradedoc' => 'Aggiornamende',
+ 'config-page-existingwiki' => 'Uicchi esistende',
+ 'config-db-type' => 'Tipe de database:',
'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',
@@ -16841,6 +17404,8 @@ $messages['roa-tara'] = array(
'config-type-postgres' => 'PostgreSQL',
'config-type-sqlite' => 'SQLite',
'config-type-oracle' => 'Oracle',
+ 'config-mysql-innodb' => 'InnoDB',
+ 'config-mysql-myisam' => 'MyISAM',
'config-admin-email' => 'Indirizze e-mail:',
'config-install-step-done' => 'fatte',
'config-install-step-failed' => 'fallite',
@@ -16864,10 +17429,12 @@ $messages['roa-tara'] = array(
* @author DCamer
* @author Eleferen
* @author Express2000
+ * @author KPu3uC B Poccuu
* @author Kaganer
* @author Krinkle
* @author Lockal
* @author MaxSem
+ * @author Okras
* @author Yuriy Apostol
* @author ÐлекÑандр Сигачёв
* @author Сrower
@@ -16925,8 +17492,8 @@ $1',
'config-help-restart' => 'Ð’Ñ‹ хотите удалить вÑе Ñохранённые данные, которые вы ввели, и запуÑтить процеÑÑ ÑƒÑтановки заново?',
'config-restart' => 'Да, начать заново',
'config-welcome' => '=== Проверка Ð¾ÐºÑ€ÑƒÐ¶ÐµÐ½Ð¸Ñ ===
-ПроводÑÑ‚ÑÑ Ð±Ð°Ð·Ð¾Ð²Ñ‹Ðµ проверки Ñ Ñ†ÐµÐ»ÑŒÑŽ определить, подходит ли Ð´Ð°Ð½Ð½Ð°Ñ ÑиÑтема Ð´Ð»Ñ ÑƒÑтановки MediaWiki.
-Укажите результаты Ñтих проверок при обращении за помощью Ñ ÑƒÑтановкой.',
+Будут проведены базовые проверки Ñ Ñ†ÐµÐ»ÑŒÑŽ определить, подходит ли Ð´Ð°Ð½Ð½Ð°Ñ ÑиÑтема Ð´Ð»Ñ ÑƒÑтановки MediaWiki.
+Ðе забудьте включить Ñту информацию, еÑли вам потребуетÑÑ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒ Ð´Ð»Ñ Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð¸Ñ ÑƒÑтановки.',
'config-copyright' => "=== ÐвторÑкие права и уÑÐ»Ð¾Ð²Ð¸Ñ ===
$1
@@ -16993,6 +17560,11 @@ MediaWiki требует поддержки UTF-8 Ð´Ð»Ñ ÐºÐ¾Ñ€Ñ€ÐµÐºÑ‚Ð½Ð¾Ð¹ Ñ€
'config-memory-bad' => "'''Внимание:''' размер PHP <code>memory_limit</code> ÑоÑтавлÑет $1.
ВероÑтно, Ñтого Ñлишком мало.
УÑтановка может потерпеть неудачу!",
+ 'config-ctype' => "'''Ð¤Ð°Ñ‚Ð°Ð»ÑŒÐ½Ð°Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ°:''' PHP должен быть Ñкомпилирован Ñ Ð¿Ð¾Ð´Ð´ÐµÑ€Ð¶ÐºÐ¾Ð¹ [http://www.php.net/manual/ru/ctype.installation.php раÑÑˆÐ¸Ñ€ÐµÐ½Ð¸Ñ Ctype].",
+ 'config-json' => "'''Ð¤Ð°Ñ‚Ð°Ð»ÑŒÐ½Ð°Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ°:''' PHP был Ñкомпилирован без поддержка JSON.
+Вам необходимо уÑтановить либо раÑширение PHP JSON, либо раÑширение [http://pecl.php.net/package/jsonc PECL jsonc] перед уÑтановкой MediaWiki.
+* PHP-раÑширение входит в ÑоÑтав Red Hat Enterprise Linux (CentOS) 5 и 6, Ñ…Ð¾Ñ‚Ñ Ð´Ð¾Ð»Ð¶Ð½Ð° быть включено в <code>/etc/php.ini</code> или <code>/etc/php.d/json.ini</code>.
+* Ðекоторые диÑтрибутивы Linux, выпущенные поÑле Ð¼Ð°Ñ 2013 года, не включают раÑширение PHP, вмеÑто того, чтобы упаковывать раÑширение PECL как <code>php5-json</code> или <code>php-pecl-jsonc</code>.",
'config-xcache' => '[http://xcache.lighttpd.net/ XCache] уÑтановлен',
'config-apc' => '[http://www.php.net/apc APC] уÑтановлен',
'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache] уÑтановлен',
@@ -17001,6 +17573,8 @@ MediaWiki требует поддержки UTF-8 Ð´Ð»Ñ ÐºÐ¾Ñ€Ñ€ÐµÐºÑ‚Ð½Ð¾Ð¹ Ñ€
'config-mod-security' => "'''Внимание''': на вашем веб-Ñервере включен [http://modsecurity.org/ mod_security]. При неправильной наÑтройке он может вызывать проблемы Ð´Ð»Ñ MediaWiki или другого ПО, позволÑющего пользователÑм отправлÑÑ‚ÑŒ на Ñервер произвольный текÑÑ‚.
ОбратитеÑÑŒ к [http://modsecurity.org/documentation/ документации mod_security] или в поддержку вашего хоÑтера, еÑли при работе возникают непонÑтные ошибки.",
'config-diff3-bad' => 'GNU diff3 не найден.',
+ 'config-git' => 'Ðайдена ÑиÑтема ÐºÐ¾Ð½Ñ‚Ñ€Ð¾Ð»Ñ Ð²ÐµÑ€Ñий Git: <code>$1</code>.',
+ 'config-git-bad' => 'Программное обеÑпечение по управлению верÑиÑми Git не найдено.',
'config-imagemagick' => 'Обнаружен ImageMagick: <code>$1</code>.
Возможно отображение миниатюр изображений, еÑли вы разрешите закачки файлов.',
'config-gd' => 'Ðайдена вÑÑ‚Ñ€Ð¾ÐµÐ½Ð½Ð°Ñ Ð³Ñ€Ð°Ñ„Ð¸Ñ‡ÐµÑÐºÐ°Ñ Ð±Ð¸Ð±Ð»Ð¸Ð¾Ñ‚ÐµÐºÐ° GD.
@@ -17022,7 +17596,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 будет обходить Ñто ограничение, но Ñто Ñнизит производительноÑÑ‚ÑŒ. ЕÑли Ñто возможно, Ñледует уÑтановить <code>suhosin.get.max_value_length</code> 1024 или выше в <code>php.ini</code>, а также уÑтановить Ð´Ð»Ñ <code>$wgResourceLoaderMaxQueryLength</code> такое же значение в LocalSettings.php.', # Fuzzy
+ 'config-suhosin-max-value-length' => 'Suhosin уÑтановлен и ограничивает параметр GET <code>length</code> до $1 байт. Компонент MediaWiki 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-адреÑ.
@@ -17098,7 +17672,6 @@ MediaWiki требует поддержки UTF-8 Ð´Ð»Ñ ÐºÐ¾Ñ€Ñ€ÐµÐºÑ‚Ð½Ð¾Ð¹ Ñ€
'config-type-postgres' => 'PostgreSQL',
'config-type-sqlite' => 'SQLite',
'config-type-oracle' => 'Oracle',
- 'config-type-ibm_db2' => 'IBM DB2',
'config-support-info' => 'MediaWiki поддерживает Ñледующие СУБД:
$1
@@ -17108,18 +17681,16 @@ $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 — коммерчеÑÐºÐ°Ñ Ð±Ð°Ð·Ð° данных маÑштаба предприÑтиÑ.', # 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-server-oracle' => 'Ðеверное TNS базы данных «$1».
+ИÑпользуйте либо «TNS Name», либо Ñтроку «Easy Connect» ([http://docs.oracle.com/cd/E11882_01/network.112/e10836/naming.htm Методы Ð½Ð°Ð¸Ð¼ÐµÐ½Ð¾Ð²Ð°Ð½Ð¸Ñ Oracle])',
'config-invalid-db-name' => 'Ðеверное Ð¸Ð¼Ñ Ð±Ð°Ð·Ñ‹ данных «$1».
ИÑпользуйте только ASCII-Ñимволы (a-z, A-Z), цифры (0-9), знак Ð¿Ð¾Ð´Ñ‡Ñ‘Ñ€ÐºÐ¸Ð²Ð°Ð½Ð¸Ñ (_) и дефиÑ(-).',
'config-invalid-db-prefix' => 'Ðеверный Ð¿Ñ€ÐµÑ„Ð¸ÐºÑ Ð±Ð°Ð·Ñ‹ данных «$1».
@@ -17175,7 +17746,7 @@ chmod a+w $3</pre>',
'config-upgrade-done-no-regenerate' => 'Обновление завершено.
Теперь вы можете [$1 начать работу Ñ Ð²Ð¸ÐºÐ¸].',
- 'config-regenerate' => 'Создать <code>LocalSettings.php</code> заново →',
+ 'config-regenerate' => 'Создать LocalSettings.php заново →',
'config-show-table-status' => 'Ð—Ð°Ð¿Ñ€Ð¾Ñ Â«<code>SHOW TABLE STATUS</code>» не выполнен!',
'config-unknown-collation' => "'''Внимание:''' База данных иÑпользует нераÑпознанные правила Ñортировки.",
'config-db-web-account' => 'Ð£Ñ‡Ñ‘Ñ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ Ð´Ð»Ñ Ð´Ð¾Ñтупа к базе данных из веб-Ñервера',
@@ -17194,6 +17765,12 @@ chmod a+w $3</pre>',
ЕÑли ваша уÑтановка MySQL поддерживает InnoDB, наÑтоÑтельно рекомендуетÑÑ Ð²Ñ‹Ð±Ñ€Ð°Ñ‚ÑŒ Ñтот механизм.
ЕÑли ваша уÑтановка MySQL не поддерживает InnoDB, возможно, наÑтало Ð²Ñ€ÐµÐ¼Ñ Ð¾Ð±Ð½Ð¾Ð²Ð¸Ñ‚ÑŒÑÑ.",
+ 'config-mysql-only-myisam-dep' => "'''Предупреждение:''' MyISAM ÑвлÑетÑÑ ÐµÐ´Ð¸Ð½Ñтвенной доÑтупной ÑиÑтемой Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð´Ð°Ð½Ð½Ñ‹Ñ… Ð´Ð»Ñ MySQL, котораÑ, однако, не рекомендуетÑÑ Ð´Ð»Ñ Ð¸ÑÐ¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ Ñ MediaWiki, потому что:
+ * он Ñлабо поддерживает параллелизм из-за блокировки таблиц
+ * она больше других ÑиÑтем подвержена повреждению
+ * ÐºÐ¾Ð´Ð¾Ð²Ð°Ñ Ð±Ð°Ð·Ð° MediaWiki не вÑегда обрабатывает MyISAM так, как Ñледует
+
+Ваша MySQL не поддерживает InnoDB, так что, возможно, наÑтало Ð²Ñ€ÐµÐ¼Ñ Ð´Ð»Ñ Ð¾Ð±Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ñ.",
'config-mysql-engine-help' => "'''InnoDB''' почти вÑегда предпочтительнее, так как он лучше ÑправлÑетÑÑ Ñ Ð¿Ð°Ñ€Ð°Ð»Ð»ÐµÐ»ÑŒÐ½Ñ‹Ð¼ доÑтупом.
'''MyISAM''' может оказатьÑÑ Ð±Ñ‹Ñтрее Ð´Ð»Ñ Ð²Ð¸ÐºÐ¸ Ñ Ð¾Ð´Ð½Ð¸Ð¼ пользователем или Ñ Ð¼Ð¸Ð½Ð¸Ð¼Ð°Ð»ÑŒÐ½Ñ‹Ð¼ количеÑтвом поÑтупающих правок, однако базы данных на нём портÑÑ‚ÑÑ Ñ‡Ð°Ñ‰Ðµ, чем на InnoDB.",
@@ -17204,7 +17781,6 @@ chmod a+w $3</pre>',
Это более Ñффективно, чем ''UTF-8 режим'' MySQL, и позволÑет иÑпользовать полный набор Ñимволов Unicode.
Ð’ '''режиме UTF-8''' MySQL будет знать в какой кодировке находÑÑ‚ÑÑ Ð’Ð°ÑˆÐ¸ данные и может отображать и преобразовывать их ÑоответÑтвующим образом, но Ñто не позволит вам хранить Ñимволы выше [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Базовой МногоÑзыковой ПлоÑкоÑти].",
- 'config-ibm_db2-low-db-pagesize' => "Ð’ вашей базе данных DB2 по умолчанию задано табличное проÑтранÑтво Ñ Ð½ÐµÐ´Ð¾Ñтаточным размером Ñтраницы. Размер Ñтраницы должен быть не менее '''32K'''.",
'config-site-name' => 'Ðазвание вики:',
'config-site-name-help' => 'Ðазвание будет отображатьÑÑ Ð² заголовке окна браузера и в некоторых других меÑтах вики.',
'config-site-name-blank' => 'Введите название Ñайта.',
@@ -17311,6 +17887,8 @@ GFDL может быть иÑпользована, но она Ñложна дл
'config-logo-help' => 'Ð¡Ñ‚Ð°Ð½Ð´Ð°Ñ€Ñ‚Ð½Ð°Ñ Ñ‚ÐµÐ¼Ð° Ð¾Ñ„Ð¾Ñ€Ð¼Ð»ÐµÐ½Ð¸Ñ MediaWiki Ñодержит над боковой панелью проÑтранÑтво Ð´Ð»Ñ Ð»Ð¾Ð³Ð¾Ñ‚Ð¸Ð¿Ð° размером 135x160 пикÑелей.
Загрузите изображение ÑоответÑтвующего размера, и введите его URL здеÑÑŒ.
+Ð’Ñ‹ можете иÑпользовать <code>$wgStylePath</code> или <code>$wgScriptPath</code>, еÑли ваш логотип находитÑÑ Ð¾Ñ‚Ð½Ð¾Ñительно к Ñтим путÑм.
+
ЕÑли вам не нужен логотип, оÑтавьте Ñто поле пуÑтым.',
'config-instantcommons' => 'Включить Instant Commons',
'config-instantcommons-help' => '[//www.mediawiki.org/wiki/InstantCommons Instant Commons] — Ñто функциÑ, позволÑÑŽÑ‰Ð°Ñ Ð¸Ñпользовать изображениÑ, звуки и другие медиафайлы Ñ Ð’Ð¸ÐºÐ¸Ñклада ([//commons.wikimedia.org/ Wikimedia Commons]).
@@ -17404,6 +17982,9 @@ $3
'config-download-localsettings' => 'Загрузить <code>LocalSettings.php</code>',
'config-help' => 'Ñправка',
'config-nofile' => 'Файл "$1" не удаетÑÑ Ð½Ð°Ð¹Ñ‚Ð¸. Он был удален?',
+ 'config-extension-link' => 'Знаете ли вы, что ваш вики-проект поддерживает [//www.mediawiki.org/wiki/Manual:Extensions раÑширениÑ]?
+
+Ð’Ñ‹ можете проÑмотреть [//www.mediawiki.org/wiki/Category:Extensions_by_category раÑÑˆÐ¸Ñ€ÐµÐ½Ð¸Ñ Ð¿Ð¾ категориÑм] или [//www.mediawiki.org/wiki/Extension_Matrix матрицу раÑширений], чтобы увидеть их полный ÑпиÑок.',
'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 Ñправочном руководÑтве].
@@ -17425,7 +18006,7 @@ $messages['rue'] = array(
* [//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
);
/** Sanskrit (संसà¥à¤•à¥ƒà¤¤à¤®à¥)
@@ -17445,7 +18026,7 @@ $messages['sah'] = array(
* [//www.mediawiki.org/wiki/Manual:Configuration_settings ÐšÐ¾Ð½Ñ„Ð¸Ð³ÑƒÑ€Ð°Ñ†Ð¸Ñ ÑƒÐ»Ð°Ñ€Ñ‹Ñ‚Ñ‹Ñ‹Ñ‚Ñ‹Ð½ параметрдара]
* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki релизтарын почтовай иÑпииһÑгÑ]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki релизтарын почтовай иÑпииһÑгÑ]', # Fuzzy
);
/** Sardinian (sardu)
@@ -17464,7 +18045,7 @@ $messages['scn'] = array(
== P'accuminzari ==
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Alencu di mpustazzioni di cunfigurazzioni]
* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mailing list dî rilassi di MediaWiki]",
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mailing list dî rilassi di MediaWiki]", # Fuzzy
);
/** Scots (Scots)
@@ -17477,7 +18058,7 @@ $messages['sco'] = array(
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Configuration settins leet]
* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki releese mailin leet]",
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki releese mailin leet]", # Fuzzy
);
/** Sassaresu (Sassaresu)
@@ -17491,7 +18072,7 @@ Li sighenti cullegamenti so in linga ingrese:
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Impusthazioni di cunfigurazioni]
* [//www.mediawiki.org/wiki/Manual:FAQ Prigonti friquenti i MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mailing list annùnzii MediaWiki]",
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mailing list annùnzii MediaWiki]", # Fuzzy
);
/** Cmique Itom (Cmique Itom)
@@ -17510,7 +18091,7 @@ $messages['sh'] = array(
== 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
);
/** Tachelhit (TaÅ¡lḥiyt/ⵜⴰⵛâµâµƒâµ‰âµœ)
@@ -17523,7 +18104,7 @@ $messages['shi'] = array(
== Izwir d MediaWiki ==
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Umuɣ n iɣwwarn n usgadda ]
* [//www.mediawiki.org/wiki/Manual:FAQ/fr Isqqsitn f MidyWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce UmuÉ£ n imsgdaln f imbá¸itn n MidyaWiki]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce UmuÉ£ n imsgdaln f imbá¸itn n MidyaWiki]', # Fuzzy
);
/** Sinhala (සිංහල)
@@ -17577,17 +18158,15 @@ $messages['si'] = array(
'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-regenerate' => 'නà·à·€à¶­ ජනිත කරන්න LocalSettings.php →',
'config-db-web-account' => 'ජà·à¶½ ප්â€à¶»à·€à·šà·à¶±à¶º සඳහ෠දත්ත සංචිත ගිණුම',
'config-mysql-engine' => 'ආචයන එන්ජිම:',
'config-mysql-innodb' => 'InnoDB',
@@ -17741,8 +18320,8 @@ Preverite vaÅ¡ php.ini in se prepriÄajte, da je <code>session.save_path</code>
'config-help-restart' => 'Želite poÄistiti vse shranjene podatke, ki ste jih vnesti, in ponovno zaÄeti s postopkom namestitve?',
'config-restart' => 'Da, ponovno zaženi',
'config-welcome' => '=== Pregledi okolja ===
-Izvedeni so osnovni pregledi, da vidimo, Äe je okolje primerno za namestitev MediaWiki.
-ÄŒe med namestitvijo potrebujete pomoÄ, posredujte tudi rezultate teh pregledov.',
+Izvedli bomo osnovne preglede, da vidimo, Äe je okolje primerno za namestitev MediaWiki.
+Posredujte rezultate teh pregledov, Äe med namestitvijo potrebujete pomoÄ.',
'config-sidebar' => '* [//www.mediawiki.org DomaÄa stran MediaWiki]
* [//www.mediawiki.org/wiki/Help:Contents Vodnik za uporabnike]
* [//www.mediawiki.org/wiki/Manual:Contents Vodnik za administratorje]
@@ -17785,7 +18364,6 @@ Vendar pa MediaWiki zahteva PHP $2 ali višji.',
'config-db-schema-help' => 'Ta shema je po navadi v redu.
Spremenite jo samo, Äe veste, da jo morate.',
'config-sqlite-dir' => 'Mapa podatkov SQLite:',
- 'config-type-ibm_db2' => 'IBM DB2',
'config-support-info' => 'MediaWiki podpira naslednje sisteme zbirk podatkov:
$1
@@ -17795,13 +18373,12 @@ $1
'config-header-postgres' => 'Nastavitve PostgreSQL',
'config-header-sqlite' => 'Nastavitve SQLite',
'config-header-oracle' => 'Nastavitve Oracle',
- 'config-header-ibm_db2' => 'Nastavitve IBM DB2',
'config-invalid-db-type' => 'Neveljavna vrsta zbirke podatkov',
'config-missing-db-name' => 'Vnesti morate vrednost za »Ime zbirke podatkov«',
'config-missing-db-host' => 'Vnesti morate vrednost za »Gostitelj zbirke podatkov«',
'config-missing-db-server-oracle' => 'Vnesti morate vrednost za »TNS zbirke podatkov«',
'config-invalid-db-server-oracle' => 'Neveljaven TNS zbirke podatkov »$1«.
-Uporabljajte samo Ärke ASCII (a-z, A-Z), Å¡tevilke (0-9), podÄrtaje (_) in pike (.).',
+Uporabite ali "ime TNS" ali niz "Easy Connect" ([http://docs.oracle.com/cd/E11882_01/network.112/e10836/naming.htm NaÄini poimenovanja Oracle])',
'config-invalid-db-name' => 'Neveljavno ime zbirke podatkov »$1«.
Uporabljajte samo Ärke ASCII (a-z, A-Z), Å¡tevilke (0-9), podÄrtaje (_) in vezaje (-).',
'config-invalid-db-prefix' => 'Neveljavna predpona zbirke podatkov »$1«.
@@ -17818,7 +18395,7 @@ 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 <code>LocalSettings.php</code> →',
+ 'config-regenerate' => 'Ponovno ustvari LocalSettings.php →',
'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',
@@ -17860,7 +18437,7 @@ DoloÄite drugo uporabniÅ¡ko ime.',
'config-admin-error-bademail' => 'Vnesli ste neveljaven e-poštni naslov.',
'config-subscribe' => 'NaroÄite se na [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce poÅ¡tni seznam obvestil o izdajah].',
'config-almost-done' => 'Skoraj ste že konÄali!
-Sedaj lahko preskoÄite preostalo konfiguriranje in zdaj namestite wiki.',
+Preostalo konfiguriranje lahko zdaj preskoÄite in wiki takoj namestite.',
'config-optional-continue' => 'Zastavi mi veÄ vpraÅ¡anj.',
'config-optional-skip' => 'Se že dolgoÄasim; samo namesti wiki.',
'config-profile' => 'Profil uporabniških pravic:',
@@ -17906,8 +18483,13 @@ Vnesite ime dovoljenja roÄno.',
'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].', # Fuzzy
+ 'mainpagedocfooter' => 'Oglejte si [//meta.wikimedia.org/wiki/Help:Contents UporabniÅ¡ki priroÄnik] za informacije o uporabi programja wiki.
+
+== Kako zaÄeti ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Seznam konfiguracijskih nastavitev]
+* [//www.mediawiki.org/wiki/Manual:FAQ Poogsto zastavljena vprašanja MediaWiki]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Poštni seznam izdaj MediaWiki]
+* [//www.mediawiki.org/wiki/Localisation#Translation_resources Prevedite MediaWiki v svoj jezik]',
);
/** Lower Silesian (Schläsch)
@@ -17921,7 +18503,7 @@ $messages['sli'] = array(
* [//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]', # Fuzzy
);
/** Somali (Soomaaliga)
@@ -17933,7 +18515,7 @@ $messages['so'] = array(
== Bilaaw ==
* [//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
);
/** Albanian (shqip)
@@ -17945,7 +18527,7 @@ $messages['sq'] = array(
== Sa për fillim==
* [//www.mediawiki.org/wiki/Help:Configuration_settings Parazgjedhjet e MediaWiki-t]
* [//www.mediawiki.org/wiki/Help:FAQ Pyetjet e shpeshta rreth MediaWiki-t]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Njoftime rreth MediaWiki-t]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Njoftime rreth MediaWiki-t]', # Fuzzy
);
/** Serbian (Cyrillic script) (ÑрпÑки (ћирилица)‎)
@@ -17991,7 +18573,7 @@ $messages['sr-ec'] = 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
);
/** Serbian (Latin script) (srpski (latinica)‎)
@@ -18035,7 +18617,7 @@ Proverite Vaš php.ini i obezbedite da je <code>session.save_path</code> postavl
== Za poÄetak ==
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Pomoć u vezi sa podešavanjima]
* [//www.mediawiki.org/wiki/Manual:FAQ NajÄešće postavljena pitanja]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mejling lista o izdanjima MedijaVikija]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mejling lista o izdanjima MedijaVikija]', # Fuzzy
);
/** Sranan Tongo (Sranantongo)
@@ -18048,7 +18630,7 @@ $messages['srn'] = array(
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Den seti]
* [//www.mediawiki.org/wiki/Manual:FAQ Sani di ben aksi furu (FAQ)]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Boskopu grupu gi nyun meki]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Boskopu grupu gi nyun meki]', # Fuzzy
);
/** Swati (SiSwati)
@@ -18062,17 +18644,18 @@ $messages['ss'] = array(
*/
$messages['stq'] = array(
'mainpagetext' => "'''Ju MediaWiki Software wuude mäd Ärfoulch installierd.'''",
- 'mainpagedocfooter' => 'Sjuch ju [//meta.wikimedia.org/wiki/MediaWiki_localization Dokumentation tou de Anpaasenge fon dän Benutseruurfläche] un dät [//meta.wikimedia.org/wiki/Help:Contents Benutserhondbouk] foar Hälpe tou ju Benutsenge un Konfiguration.',
+ 'mainpagedocfooter' => 'Sjuch ju [//meta.wikimedia.org/wiki/MediaWiki_localization Dokumentation tou de Anpaasenge fon dän Benutseruurfläche] un dät [//meta.wikimedia.org/wiki/Help:Contents Benutserhondbouk] foar Hälpe tou ju Benutsenge un Konfiguration.', # Fuzzy
);
/** Sundanese (Basa Sunda)
*/
$messages['su'] = array(
'mainpagetext' => "'''''Software'' MediaWiki geus diinstal.'''",
- 'mainpagedocfooter' => "Mangga tingal ''[//meta.wikimedia.org/wiki/MediaWiki_localisation documentation on customizing the interface]'' jeung [//meta.wikimedia.org/wiki/MediaWiki_User%27s_Guide Tungtunan Pamaké] pikeun pitulung maké jeung konfigurasi.",
+ 'mainpagedocfooter' => "Mangga tingal ''[//meta.wikimedia.org/wiki/MediaWiki_localisation documentation on customizing the interface]'' jeung [//meta.wikimedia.org/wiki/MediaWiki_User%27s_Guide Tungtunan Pamaké] pikeun pitulung maké jeung konfigurasi.", # Fuzzy
);
/** Swedish (svenska)
+ * @author Jopparn
* @author Skalman
* @author WikiPhoenix
*/
@@ -18080,8 +18663,20 @@ $messages['sv'] = array(
'config-desc' => 'Installationsprogram för MediaWiki',
'config-title' => 'Installation av MediaWiki $1',
'config-information' => 'Information',
+ 'config-localsettings-upgrade' => 'A <code>LocalSettings.php</code>-fil har upptäckts.
+För att uppgradera den här installationen, vänligen ange värdet för <code>$wgUpgradeKey</code> i rutan nedan.
+Du hittar den i <code>LocalSettings.php</code>.',
+ 'config-localsettings-cli-upgrade' => 'En <code>LocalSettings.php</code>-fil har upptäckts.
+För att uppgradera denna installation, kör <code>update.php</code> istället',
'config-localsettings-key' => 'Uppgraderingsnyckel:',
'config-localsettings-badkey' => 'Nyckeln du angav är inkorrekt.',
+ 'config-upgrade-key-missing' => 'En nuvarande installerade av MediaWiki har upptäckts.
+För att uppgradera installationen, lägg till följande rad i slutet av din <code>LocalSettings.php</code>:
+
+$1',
+ 'config-localsettings-incomplete' => 'De befintliga <code>LocalSettings.php</code> verkar vara ofullständig.
+Variabeln $1 är inte inställd.
+Ändra <code>LocalSettings.php</code> så att denna variabel är inställd och klicka på "{{int:Config-continue}}".',
'config-session-error' => 'Fel vid uppstart av session: $1',
'config-your-language' => 'Ditt språk:',
'config-your-language-help' => 'Välj ett språk som ska användas under installationen.',
@@ -18122,21 +18717,50 @@ Du kan inte installera MediaWiki.',
'config-env-php' => 'PHP $1 är installerad.',
'config-env-php-toolow' => 'PHP $1 är installerad.
MediaWiki kräver PHP $2 eller högre.',
+ 'config-outdated-sqlite' => "'''Varning:''' du har SQLite $1, vilket är lägre än minimikravet version $2. SQLite kommer inte att vara tillgänglig.",
+ 'config-register-globals' => "'''Varning: PHP:s <code>[http://php.net/register_globals register_globals]</code> tillval är aktiverat.'''
+'''Inaktivera den om du kan.'''
+MediaWiki kommer att fungera, men din server exponeras för potentiella säkerhetsluckor.",
+ 'config-safe-mode' => "''' Varning:''' PHP:s [http://www.php.net/features.safe-mode felsäkert läge] är aktivt.
+Det kan orsaka problem, särskilt om du använder filöverföringar och <code>math</code>-stöd.",
+ 'config-xml-bad' => 'PHP:s XML-modul saknas.
+MediaWiki kräver funktioner i denna modul och kommer inte att fungera i den här konfigurationen.
+Om du kör Mandrake, installera php-xml-paketet.',
+ 'config-memory-raised' => 'PHPs <code>memory_limit</code> är $1, ökar till $2.',
+ 'config-memory-bad' => "''' Varning:''' PHP:s <code>memory_limit</code> är $1.
+Detta är förmodligen för lågt.
+Installationen kan misslyckas!",
'config-xcache' => '[http://xcache.lighttpd.net/ XCache] är installerad',
'config-apc' => '[http://www.php.net/apc APC] är installerad',
'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache] är installerad',
'config-diff3-bad' => 'GNU diff3 hittades inte.',
+ 'config-git-bad' => 'Git-versionen av kontrollmjukvaran hittades inte.',
+ 'config-no-uri' => "'''Fel:''' Kunde inte fastställa det nuvarande URI:et.
+Installationen avbröts.",
+ 'config-no-cli-uri' => "'''Varning:''' inget --scriptpath är anget, med standarden: <code>$1</code> .",
'config-using-server' => 'Använder servernamn "<nowiki>$1</nowiki>".',
'config-using-uri' => 'Använder server-URL "<nowiki>$1$2</nowiki>".',
+ 'config-no-cli-uploads-check' => "'''Varning:''' Din standardkatalog för uppladdningar (<code>$1</code>) inte är kontrollerad för sårbarhet från godtyckliga skriptkörning under CLI-installationen.",
'config-db-type' => 'Databastyp:',
+ 'config-db-host' => 'Databasvärd:',
'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-password-empty' => 'Ange ett lösenord för den nya databasanvändaren: $1.
+Även om det kan vara möjligt att skapa användare utan lösenord är det inte säkert.',
+ 'config-db-account-lock' => 'Använda samma användarnamn och lösenord under normal drift',
+ 'config-db-wiki-account' => 'Användarkonto för normal drift',
+ 'config-db-prefix' => 'Prefix för tabellerna i databasen:',
+ 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 binär',
+ 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
+ 'config-charset-mysql4' => 'MySQL 4.0 bakåtkompatibel UTF-8',
'config-db-port' => 'Databasport:',
'config-db-schema' => 'Schema för MediaWiki',
+ 'config-pg-test-error' => "Kan inte ansluta till databas '''$1''': $2",
+ 'config-sqlite-dir' => 'SQLite data-katalog:',
'config-header-mysql' => 'MySQL-inställningar',
'config-header-postgres' => 'PostgreSQL-inställningar',
'config-header-sqlite' => 'SQLite-inställningar',
@@ -18144,6 +18768,7 @@ MediaWiki kräver PHP $2 eller högre.',
'config-invalid-db-type' => 'Ogiltig databastyp',
'config-missing-db-name' => 'Du måste ange ett värde för "Databasnamn"',
'config-missing-db-host' => 'Du måste ange ett värde för "Databasvärd"',
+ 'config-missing-db-server-oracle' => 'Du måste ange ett värde för "Databas TNS"',
'config-invalid-db-name' => '"$1" är ett ogiltigt databasnamn.
Använd bara ASCII-bokstäver (a-z, A-Z), siffror (0-9), understreck (_) och bindestreck (-).',
'config-invalid-db-prefix' => '"$1" är ett ogiltigt databasprefix.
@@ -18153,8 +18778,17 @@ Använd bara ASCII-bokstäver (a-z, A-Z), siffror (0-9), understreck (_) och bin
Kontrollera värden, användarnamnet och lösenordet nedan och försök igen',
'config-invalid-schema' => '"$1" är ett ogiltigt schema för MediaWiki.
Använd bara ASCII-bokstäver (a-z, A-Z), siffror (0-9), understreck (_) och bindestreck (-).',
+ 'config-db-sys-create-oracle' => 'Installationsprogrammet stöder endast med ett SYSDBA-konto för att skapa ett nytt konto.',
'config-db-sys-user-exists-oracle' => 'Användarkontot "$1" finns redan. SYSDBA kan endast användas för att skapa ett nytt konto!',
'config-postgres-old' => 'PostgreSQL $1 eller senare krävs, du har $2.',
+ 'config-sqlite-name-help' => 'Välja ett namn som identifierar din wiki.
+Använd inte mellanslag eller bindestreck.
+Detta kommer att användas för SQLite-data filnamnet.',
+ 'config-sqlite-readonly' => 'Filen <code>$1</code> är inte skrivbar.',
+ 'config-sqlite-cant-create-db' => 'Kunde inte skapa databasfilen <code>$1</code>.',
+ 'config-sqlite-fts3-downgrade' => 'PHP saknar stöd för FTS3, nedgraderar tabeller',
+ 'config-can-upgrade' => "Det finns MediaWiki-tabeller i den här databasen.
+För att uppgradera dem till MediaWiki $1, klicka på '''Fortsätt'''.",
'config-upgrade-done' => "Uppgraderingen slutfördes.
Du kan nu [$1 börja använda din wiki].
@@ -18164,6 +18798,8 @@ 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-regenerate' => 'Återskapa LocalSettings.php →',
+ 'config-db-web-account' => 'Databaskonto för webbaccess',
'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:',
@@ -18172,6 +18808,8 @@ Du kan nu [$1 börja använda din wiki].',
'config-site-name' => 'Namnet på wikin:',
'config-site-name-blank' => 'Ange ett sidnamn.',
'config-ns-generic' => 'Projekt',
+ 'config-ns-site-name' => 'Samma som wikinamnet: $1',
+ 'config-ns-other' => 'Annan (specificera)',
'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.
@@ -18189,12 +18827,16 @@ Ange ett annat användarnamn.',
'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-user' => 'Internt fel när du skapar en administratör med namnet "<nowiki>$1</nowiki>".',
+ 'config-admin-error-password' => 'Internt fel när du väljer ett lösenord för administratören "<nowiki>$1</nowiki>": <pre>$2</pre>',
'config-admin-error-bademail' => 'Du har angivit en felaktigt e-postadress.',
+ 'config-subscribe' => 'Prenumerera på [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce e-postlistan för tillkännagivanden].',
'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-no-anon' => 'Kontoskapande krävs',
'config-profile-fishbowl' => 'Endast auktoriserade redigerare',
'config-profile-private' => 'Privat wiki',
'config-license' => 'Upphovsrätt och licens:',
@@ -18207,31 +18849,54 @@ Du kan nu hoppa över återstående konfigurationer och installera wikin nu.',
'config-license-pd' => 'Allmän egendom',
'config-license-cc-choose' => 'Välj en anpassad Creative Commons-licens',
'config-email-settings' => 'E-postinställningar',
+ 'config-enable-email' => 'Aktivera utgående e-post',
+ 'config-email-user' => 'Aktivera e-post mellan användare',
+ 'config-email-user-help' => 'Tillåta alla användare att skicka mail till varandra om de har aktiverat det i deras preferenser.',
+ 'config-email-usertalk' => 'Aktivera underrättelse för användardiskussionssidan',
'config-email-watchlist' => 'Aktivera meddelanden för bevakningslistan',
+ 'config-email-auth' => 'Aktivera autentisering via e-post',
'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-instantcommons' => 'Aktivera Instant Commons',
+ 'config-cc-error' => 'Creative Commons-licens-väljaren gav inget resultat.
+Ange licensnamnet manuellt.',
'config-cc-again' => 'Välj igen...',
+ 'config-cc-not-chosen' => 'Välj vilken Creative Commons-licens du vill ha och klicka på "gå vidare".',
'config-advanced-settings' => 'Avancerad konfiguration',
'config-extensions' => 'Tillägg',
+ 'config-install-alreadydone' => "''' Varning:''' Du verkar redan ha installerat MediaWiki och försöker installera det igen.
+Vänligen fortsätt till nästa sida.",
+ 'config-install-begin' => 'Genom att trycka på "{{int:config-continue}}", påbörjar du installationen av MediaWiki.
+Om du fortfarande vill göra ändringar trycker du på "{{int:config-back}}".',
'config-install-step-done' => 'klar',
'config-install-step-failed' => 'misslyckades',
'config-install-database' => 'Konfigurerar databas',
'config-install-schema' => 'Skapar schema',
+ 'config-pg-no-plpgsql' => 'Du måste installera språket PL/pgSQL i databasen $1',
'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-grant-failed' => 'Beviljandet av behörighet till användaren "$1" misslyckades: $2',
'config-install-user-missing' => 'Den angivna användaren "$1" existerar inte.',
+ 'config-install-user-missing-create' => 'Den angivna användaren "$1" existerar inte.
+Vänligen klicka på kryssrutan "skapa konto" nedan om du vill skapa den.',
'config-install-tables' => 'Skapar tabeller',
+ 'config-install-tables-exist' => "''' Varning:''' MediaWiki-tabeller verkar redan finnas.
+Hoppar över skapandet.",
+ 'config-install-tables-failed' => "''' Fel:''' Skapandet av tabell misslyckades med följande fel: $1",
'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-install-sysop' => 'Skapar administratörskonto',
+ 'config-install-subscribe-fail' => 'Det gick inte att prenumerera på mediawiki-announce: $1',
+ 'config-install-subscribe-notpossible' => 'cURL är inte installerad och allow_url_fopen är inte tillgänglig.',
'config-install-mainpage' => 'Skapa huvudsida med standardinnehåll',
'config-install-extension-tables' => 'Skapar tabeller för aktiverade tillägg',
+ 'config-install-mainpage-failed' => 'Kunde inte infoga huvudsidan: $1',
'config-install-done' => "'''Grattis!'''
Du har installerat MediaWiki.
@@ -18270,7 +18935,7 @@ $messages['sw'] = array(
== Msaada wa kianzio ==
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Orodha ya mipangilio ya msingi]
* [//www.mediawiki.org/wiki/Manual:FAQ FAQ ya MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Orodha ya utoaji wa habari za MediaWiki]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Orodha ya utoaji wa habari za MediaWiki]', # Fuzzy
);
/** Silesian (ślůnski)
@@ -18283,7 +18948,7 @@ $messages['szl'] = array(
== Na sztart ==
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lista sztalowań konfiguracyje]
* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Komuńikaty uo nowych wersyjach MediaWiki]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Komuńikaty uo nowych wersyjach MediaWiki]', # Fuzzy
);
/** Tamil (தமிழà¯)
@@ -18400,7 +19065,7 @@ $messages['tcy'] = array(
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Configuration settings list]
* [//www.mediawiki.org/wiki/Manual:FAQ ಮೀಡಿಯವಿಕಿ 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
);
/** Telugu (తెలà±à°—à±)
@@ -18446,7 +19111,7 @@ $messages['te'] = array(
'config-admin-password-confirm' => 'సంకేతపదం మళà±à°³à±€:',
'config-admin-email' => 'à°ˆ-మెయిలౠచిరà±à°¨à°¾à°®à°¾:',
'config-optional-continue' => 'ననà±à°¨à± మరినà±à°¨à°¿ à°ªà±à°°à°¶à±à°¨à°²à± à°…à°¡à±à°—à±.',
- 'config-profile-wiki' => 'సంపà±à°°à°¦à°¾à°¯ వికీ',
+ 'config-profile-wiki' => 'సంపà±à°°à°¦à°¾à°¯ వికీ', # Fuzzy
'config-profile-no-anon' => 'ఖాతా సృషà±à°Ÿà°¿à°‚పౠతపà±à°ªà°¨à°¿à°¸à°°à°¿',
'config-profile-private' => 'అంతరంగిక వికీ',
'config-license' => 'కాపీహకà±à°•à±à°²à± మరియౠలైసెనà±à°¸à±:',
@@ -18464,7 +19129,7 @@ $messages['te'] = 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
);
/** Tetum (tetun)
@@ -18485,7 +19150,7 @@ $messages['tg-cyrl'] = 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
);
/** Tajik (Latin script) (tojikī)
@@ -18499,7 +19164,7 @@ $messages['tg-latn'] = array(
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Fehristi tanzimoti pajgirbandī]
* [//www.mediawiki.org/wiki/Manual:FAQ PursiÅŸhoi MediaViki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Fehristi rojnomahoi nusxahoi MediaViki]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Fehristi rojnomahoi nusxahoi MediaViki]', # Fuzzy
);
/** Thai (ไทย)
@@ -18513,7 +19178,7 @@ $messages['th'] = 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
);
/** Turkmen (Türkmençe)
@@ -18526,7 +19191,7 @@ $messages['tk'] = array(
== Öwrenjeler ==
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Konfigurasiýa sazlamalary]
* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki SSS]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki e-poçta sanawy]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki e-poçta sanawy]', # Fuzzy
);
/** Tagalog (Tagalog)
@@ -18757,7 +19422,6 @@ Isaalang-alang ang paglalagay na magkakasama ang kalipunan ng dato sa ibang luga
'config-type-postgres' => 'PostgreSQL',
'config-type-sqlite' => 'SQLite',
'config-type-oracle' => 'Oracle',
- 'config-type-ibm_db2' => 'DB2 ng IBM',
'config-support-info' => 'Sinusuportahan ng MediaWiki ang sumusunod na mga sistema ng kalipunan ng dato:
$1
@@ -18767,12 +19431,10 @@ 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.', # Fuzzy
'config-header-mysql' => 'Mga katakdaan ng MySQL',
'config-header-postgres' => 'Mga katakdaan ng PostgreSQL',
'config-header-sqlite' => 'Mga katakdaan ng SQLite',
'config-header-oracle' => 'Mga katakdaan ng Oracle',
- 'config-header-ibm_db2' => 'Mga katakdaan ng DB2 ng IBM',
'config-invalid-db-type' => 'Hindi tanggap na uri ng kalipunan ng dato',
'config-missing-db-name' => 'Dapat kang magpasok ng isang halaga para sa "Pangalan ng kalipunan ng dato"',
'config-missing-db-host' => 'Dapat kang magpasok ng isang halaga para sa "Tagapagpasinaya ng kalipunan ng dato"',
@@ -18834,7 +19496,7 @@ 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 <code>LocalSettings.php</code> →',
+ 'config-regenerate' => 'Muling likhain ang LocalSettings.php →',
'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',
@@ -18864,7 +19526,6 @@ May gawi ang mga kalipunan ng dato ng MyISAM na masira nang mas madalas kaysa sa
Mas kapaki-pakinabang ito kaysa sa gawi na UTF-8 ng MySQL, at nagpapahintulot sa iyo upang magamit ang buong kasaklawan ng mga panitik ng Unikodigo.
Sa ''gawi na UTF-8''', malalaman ng MySQL kung sa anong pangkat ng panitik nakapaloob ang iyong dato, at angkop na makakapagharap at makapapagpalit nito, subalit hindi ka nito papayagan na mag-imbak ng mga panitik na nasa itaas ng [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Basic Multilingual Plane] o Saligang Tapyas na Pangmaramihang Wika.",
- 'config-ibm_db2-low-db-pagesize' => "Ang kalipunan mo ng dato na DB2 ay mayroong isang likas na nakatakdang puwang ng talahanayan na mayroong hindi sapat na sukat ng pahina. Ang sukat ng pahina ay dapat na maging '''32K''' o mas mataas.",
'config-site-name' => 'Pangalan ng wiki:',
'config-site-name-help' => "Lilitaw ito sa bareta ng pamagat ng pantingin-tingin at sa samu't saring ibang mga lugar.",
'config-site-name-blank' => 'Magpasok ng isang pangalan ng sityo.',
@@ -18972,7 +19633,7 @@ Ideyal na dapat itong hindi mapupuntahan mula sa web.',
'config-logo-help' => 'Ang likas na nakatakdang pabalat ng MediaWiki ay nagsasama ng puwang para sa isang logong 135x160 ang piksel na nasa itaas ng menu ng panggilid na bareta.
Magkargang papaitaas ng isang imahe na mayroong naaangkop na sukat, at ipasok dito ang URL.
-Kung ayaw mo ng isang logo, iwanang walang laman ang kahong ito.',
+Kung ayaw mo ng isang logo, iwanang walang laman ang kahong ito.', # Fuzzy
'config-instantcommons' => 'Paganahin ang Mga Pangkaraniwang Biglaan',
'config-instantcommons-help' => 'Ang [//www.mediawiki.org/wiki/InstantCommons Instant Commons] ay isang tampok na nagpapahintulot sa mga wiki upang gumamit ng mga imahe, mga tunog at iba pang mga midyang matatagpuan sa pook ng [//commons.wikimedia.org/ Wikimedia Commons].
Upang magawa ito, nangangailangan ang MediaWiki ng pagka nakakapunta sa Internet.
@@ -19091,7 +19752,7 @@ $messages['tr'] = array(
== Yeni BaÅŸlayanlar ==
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Yapılandırma ayarlarının listesi]
* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki SSS]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki e-posta listesi]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki e-posta listesi]', # Fuzzy
);
/** Tatar (Cyrillic script) (татарча)
@@ -19104,7 +19765,7 @@ $messages['tt-cyrl'] = array(
== Кайбер файдалы реÑурÑлар ==
* [//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
);
/** Tatar (Latin script) (tatarça)
@@ -19117,7 +19778,7 @@ $messages['tt-latn'] = array(
== Qayber faydalı resurslar ==
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Köylänmälär isemlege (ing.)];
* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki turında yış birelgän sorawlar häm cawaplar (ing.)];
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki'nıñ yaña versiäläre turında xäbärlär yazdırıp alu].",
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki'nıñ yaña versiäläre turında xäbärlär yazdırıp alu].", # Fuzzy
);
/** Udmurt (удмурт)
@@ -19137,13 +19798,14 @@ $messages['ug-arab'] = array(
== دەسلەپكى ساۋات ==
* [//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
);
/** Ukrainian (українÑька)
* @author AS
* @author Ahonc
* @author Alex Khimich
+ * @author Andriykopanytsia
* @author Base
* @author Diemon.ukr
* @author Ðта
@@ -19201,8 +19863,8 @@ $1',
'config-help-restart' => 'Ви бажаєте видалити вÑÑ– введені та збережені вами дані Ñ– запуÑтити Ð¿Ñ€Ð¾Ñ†ÐµÑ ÑƒÑтановки Ñпочатку?',
'config-restart' => 'Так, перезапуÑтити уÑтановку',
'config-welcome' => '=== Перевірка Ð¾Ñ‚Ð¾Ñ‡ÐµÐ½Ð½Ñ ===
-ПроводÑÑ‚ÑŒÑÑ Ð±Ð°Ð·Ð¾Ð²Ñ– перевірки, щоб виÑвити, чи можлива уÑтановка MediaWiki у даній ÑиÑтемі.
-Вкажіть результати цих перевірок при зверненні за допомогою під Ñ‡Ð°Ñ ÑƒÑтановки.',
+Будуть проведені базові перевірки, щоб виÑвити, чи можлива уÑтановка MediaWiki у даній ÑиÑтемі.
+Ðе забудьте включити цю інформацію, Ñкщо ви звернетеÑÑ Ð¿Ð¾ підтримку, Ñк завершити уÑтановку.',
'config-copyright' => "=== ÐвторÑьке право Ñ– умови ===
$1
@@ -19270,6 +19932,10 @@ MediaWiki вимагає підтримку UTF-8 Ð´Ð»Ñ ÐºÐ¾Ñ€ÐµÐºÑ‚Ð½Ð¾Ñ— ро
Імовірно, це замало.
Ð’ÑÑ‚Ð°Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð¼Ð¾Ð¶Ðµ не вдатиÑÑŒ!",
'config-ctype' => "'''Помилка''': PHP має бути зібраним з підтримкою [http://www.php.net/manual/en/ctype.installation.php Ñ€Ð¾Ð·ÑˆÐ¸Ñ€ÐµÐ½Ð½Ñ Ctype].",
+ 'config-json' => "'''Fatal:''' PHP був Ñкомпільований без підтримки JSON.
+Вам потрібно вÑтановити або Ñ€Ð¾Ð·ÑˆÐ¸Ñ€ÐµÐ½Ð½Ñ PHP JSON або розширеннÑ[http://pecl.php.net/package/jsonc PECL jsonc] перед вÑÑ‚Ð°Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ ÐœÐµÐ´Ñ–Ð°Ð²Ñ–ÐºÑ–.
+* Ð Ð¾Ð·ÑˆÐ¸Ñ€ÐµÐ½Ð½Ñ PHP включено у Red Hat Enterprise Linux (CentOS) 5 та 6, хоча має бути доÑтупним у <code>/etc/php.ini</code> або <code>/etc/php.d/json.ini</code>.
+* ДеÑкі диÑтрибутиви ЛінукÑа, випущені піÑÐ»Ñ Ñ‚Ñ€Ð°Ð²Ð½Ñ 2013, пропуÑтили Ñ€Ð¾Ð·ÑˆÐ¸Ñ€ÐµÐ½Ð½Ñ PHP, натоміÑÑ‚ÑŒ упакували Ñ€Ð¾Ð·ÑˆÐ¸Ñ€ÐµÐ½Ð½Ñ PECL Ñк <code>php5-json</code> або <code>php-pecl-jsonc</code>.",
'config-xcache' => '[http://xcache.lighttpd.net/ XCache] вÑтановлено',
'config-apc' => '[http://www.php.net/apc APC] вÑтановлено',
'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache] вÑтановлено',
@@ -19278,6 +19944,8 @@ MediaWiki вимагає підтримку UTF-8 Ð´Ð»Ñ ÐºÐ¾Ñ€ÐµÐºÑ‚Ð½Ð¾Ñ— ро
'config-mod-security' => "'''Увага''': на Вашому веб-Ñервері увімкнено [http://modsecurity.org/ mod_security]. У разі неправильних налаштувать, він може викликати проблеми MediaWiki або іншого ПЗ, Ñке дозволÑÑ” кориÑтувачам надÑилати довільний вміÑÑ‚.
ЗвернітьÑÑ Ð´Ð¾ [http://modsecurity.org/documentation/ документації mod_security] або підтримки Вашого хоÑтера, Ñкщо під Ñ‡Ð°Ñ Ñ€Ð¾Ð±Ð¾Ñ‚Ð¸ виникають незрозумілі помилки.",
'config-diff3-bad' => 'GNU diff3 не знайдено.',
+ 'config-git' => 'Знайшов програму ÑƒÐ¿Ñ€Ð°Ð²Ð»Ñ–Ð½Ð½Ñ Ð²ÐµÑ€ÑÑ–Ñми Git: <code>$1</code>.',
+ 'config-git-bad' => 'Програму ÑƒÐ¿Ñ€Ð°Ð²Ð»Ñ–Ð½Ð½Ñ Ð²ÐµÑ€ÑÑ–Ñми Git не знайдено.',
'config-imagemagick' => 'ВиÑвлено ImageMagick: <code>$1</code>.
Буде ввімкнуто Ð²Ñ–Ð´Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ Ð¼Ñ–Ð½Ñ–Ð°Ñ‚ÑŽÑ€, Ñкщо ви дозволите Ð·Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ Ñ„Ð°Ð¹Ð»Ñ–Ð².',
'config-gd' => 'ВиÑвлено вбудовано графічну бібліотеку GD.
@@ -19298,7 +19966,7 @@ MediaWiki вимагає підтримку UTF-8 Ð´Ð»Ñ ÐºÐ¾Ñ€ÐµÐºÑ‚Ð½Ð¾Ñ— ро
'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-suhosin-max-value-length' => 'Suhosin вÑтановлено Ñ– обмежує параметра GET <code>length</code> до $1 байта. Компонент MediaWiki 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 адреÑу.
@@ -19369,7 +20037,6 @@ MediaWiki вимагає підтримку UTF-8 Ð´Ð»Ñ ÐºÐ¾Ñ€ÐµÐºÑ‚Ð½Ð¾Ñ— ро
За можливоÑÑ‚Ñ– розташуйте базу даних деÑÑŒ окремо, наприклад в <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
@@ -19379,18 +20046,16 @@ $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 — комерційна база даних маÑштабу підприємÑтва.', # 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), знаки підкреÑÐ»ÐµÐ½Ð½Ñ (_) Ñ– крапки (.).',
+ВикориÑтовуйте "TNS Name" або Ñ€Ñдок "Easy Connect" ([http://docs.oracle.com/cd/E11882_01/network.112/e10836/naming.htm Методи Ð½Ð°Ð¹Ð¼ÐµÐ½ÑƒÐ²Ð°Ð½Ð½Ñ Oracle])',
'config-invalid-db-name' => 'ÐеприпуÑтима назва бази даних "$1".
ВикориÑтовуйте тільки ASCII букви (a-z, A-Z), цифри (0-9), знаки підкреÑÐ»ÐµÐ½Ð½Ñ (_) Ñ– дефіÑи (-).',
'config-invalid-db-prefix' => 'ÐеприпуÑтимий Ð¿Ñ€ÐµÑ„Ñ–ÐºÑ Ð±Ð°Ð·Ð¸ даних "$1".
@@ -19446,7 +20111,7 @@ chmod a+w $3</pre>',
'config-upgrade-done-no-regenerate' => 'ÐžÐ½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð¾.
Ви можете зараз [$1 починати викориÑтовувати Ñвою вікі].',
- 'config-regenerate' => 'Повторно згенерувати <code>LocalSettings.php</code> →',
+ 'config-regenerate' => 'Повторно згенерувати LocalSettings.php →',
'config-show-table-status' => 'Запит <code>SHOW TABLE STATUS</code> не виконано!',
'config-unknown-collation' => "'''Увага:''' База даних викориÑтовує нерозпізнане ÑортуваннÑ.",
'config-db-web-account' => 'Обліковий Ð·Ð°Ð¿Ð¸Ñ Ð±Ð°Ð·Ð¸ даних Ð´Ð»Ñ Ñ–Ð½Ñ‚ÐµÑ€Ð½ÐµÑ‚-доÑтупу',
@@ -19465,6 +20130,12 @@ chmod a+w $3</pre>',
Якщо Ваша інÑталÑÑ†Ñ–Ñ MySQL підтримує InnoDB, дуже рекомендуєтьÑÑ Ð²Ð¸Ð±Ñ€Ð°Ñ‚Ð¸ цей двигун.
Якщо Ваша інÑталÑÑ†Ñ–Ñ MySQL не підтримує InnoDB, можливо наÑтав Ñ‡Ð°Ñ Ñ—Ñ— оновити.",
+ 'config-mysql-only-myisam-dep' => '"\'ЗауваженнÑ:"\' MyISAM Ñ” єдиним механізмом Ð´Ð»Ñ Ð·Ð±ÐµÑ€Ñ–Ð³Ð°Ð½Ð½Ñ MySQL, Ñкий не рекомендуєтьÑÑ Ð´Ð»Ñ Ð²Ð¸ÐºÐ¾Ñ€Ð¸ÑÑ‚Ð°Ð½Ð½Ñ Ð· MediaWiki, оÑкільки:
+* Ñлабо підтримує паралелізм через Ð±Ð»Ð¾ÐºÑƒÐ²Ð°Ð½Ð½Ñ Ñ‚Ð°Ð±Ð»Ð¸Ñ†ÑŒ
+* більш Ñхильний до пошкоджень, ніж інші двигуни
+* код MediaWiki не завжди розглÑдає MyISAM, Ñк повинен
+
+Твоє вÑÑ‚Ð°Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ MySQL не підтримує InnoDB, можливо, потрібно оновити.',
'config-mysql-engine-help' => "'''InnoDB''' Ñ” завжди кращим вибором, оÑкільки краще підтримує паралельний доÑтуп.
'''MyISAM''' може бути швидшим Ð´Ð»Ñ Ð¾Ð´Ð½Ð¾Ð³Ð¾ кориÑтувача або в інÑталÑціÑÑ… read-only.
@@ -19476,7 +20147,6 @@ chmod a+w $3</pre>',
Це більш ефективно, ніж 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' => 'Введіть назву Ñайту.',
@@ -19519,7 +20189,7 @@ chmod a+w $3</pre>',
'config-optional-continue' => 'Запитуйте ще.',
'config-optional-skip' => 'Це вже втомлює, проÑто вÑтановити вікі.',
'config-profile' => 'Профіль прав кориÑтувача:',
- 'config-profile-wiki' => 'Традиційна вікі', # Fuzzy
+ 'config-profile-wiki' => 'Відкрита вікі',
'config-profile-no-anon' => 'Ðеобхідно Ñтворити обліковий запиÑ',
'config-profile-fishbowl' => 'Тільки Ð´Ð»Ñ Ð°Ð²Ñ‚Ð¾Ñ€Ð¸Ð·Ð¾Ð²Ð°Ð½Ð¸Ñ… редакторів',
'config-profile-private' => 'Приватна вікі',
@@ -19529,12 +20199,12 @@ chmod a+w $3</pre>',
Одначе, MediaWiki може бути кориÑна по-різному, й інколи важко переконати у вигідноÑÑ‚Ñ– відкритої вікі-роботи.
Тож у Ð’Ð°Ñ Ñ” вибір.
-'''{{int:config-profile-wiki}}''' дозволÑÑ” редагувати будь-кому, навіть без Ð²Ñ…Ð¾Ð´Ð¶ÐµÐ½Ð½Ñ Ð² ÑиÑтему.
+Модель '''{{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
+Детальніші конфігурації прав кориÑтувачів доÑтупні піÑÐ»Ñ Ð²ÑтановленнÑ, див. [//www.mediawiki.org/wiki/Manual:User_rights відповідний розділ поÑібника].",
'config-license' => 'ÐвторÑькі права Ñ– ліцензіÑ:',
'config-license-none' => 'Без ліцензії у нижньому колонтитулі',
'config-license-cc-by-sa' => 'Creative Commons Attribution Share Alike',
@@ -19580,8 +20250,11 @@ GFDL — допуÑтима ліцензіÑ, але у ній важко роз
Ð’ ідеалі, вона не має бути доÑтупною через інтернет.',
'config-logo' => 'URL логотипу:',
'config-logo-help' => 'Стандартна Ñхема Ð¾Ñ„Ð¾Ñ€Ð¼Ð»ÐµÐ½Ð½Ñ MediaWiki міÑтить вільне Ð´Ð»Ñ Ð»Ð¾Ð³Ð¾Ñ‚Ð¸Ð¿Ñƒ міÑце над бічною панеллю розміром 135x160 пікÑелів.
+
Завантажте Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ Ð²Ñ–Ð´Ð¿Ð¾Ð²Ñ–Ð´Ð½Ð¾Ð³Ð¾ розміру Ñ– введіть тут його URL.
+Ви можете викориÑтати <code>$wgStylePath</code> або <code>$wgScriptPath</code>, Ñкщо ваш логотип пов\'Ñзаний з цими шлÑхами.
+
Якщо Вам не потрібен логотип, залиште це поле пуÑтим.',
'config-instantcommons' => 'Увімкнути Instant Commons',
'config-instantcommons-help' => '[//www.mediawiki.org/wiki/InstantCommons Instant Commons] це функціÑ, що дозволÑÑ” вікі викориÑтовувати зображеннÑ, звуки та інші медіа, розміщені на [//commons.wikimedia.org/ ВікіÑховищі].
@@ -19616,7 +20289,7 @@ GFDL — допуÑтима ліцензіÑ, але у ній важко роз
'config-install-alreadydone' => "'''Увага:''' ЗдаєтьÑÑ, Ви вже вÑтановлювали MediaWiki Ñ– зараз намагаєтеÑÑŒ вÑтановити Ñ—Ñ— знову.
Будь лаÑка, перейдіть на наÑтупну Ñторінку.",
'config-install-begin' => 'ÐатиÑкаючи "{{int:config-continue}}", Ви розпочинаєте вÑÑ‚Ð°Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ MediaWiki.
-Якщо Ви вÑе ще хочете внеÑти зміни, натиÑніть "Ðазад".', # Fuzzy
+Якщо Ви вÑе ще хочете внеÑти зміни, натиÑніть "{{int:config-back}}".',
'config-install-step-done' => 'виконано',
'config-install-step-failed' => 'не вдалоÑÑ',
'config-install-extensions' => 'У тому чиÑлі розширеннÑ',
@@ -19674,6 +20347,9 @@ $3
'config-download-localsettings' => 'Завантажити <code>LocalSettings.php</code>',
'config-help' => 'допомога',
'config-nofile' => 'Файл "$1" не знайдено. Його видалено?',
+ 'config-extension-link' => 'Чи знаєте ви, що ваше вікі підтримує [//www.mediawiki.org/wiki/Manual:Extensions розширеннÑ]?
+
+Ви можете переглÑдати [//www.mediawiki.org/wiki/Category:Extensions_by_category Ñ€Ð¾Ð·ÑˆÐ¸Ñ€ÐµÐ½Ð½Ñ Ð¿Ð¾ категорії] або в [//www.mediawiki.org/wiki/Extension_Matrix матрицю розширень] щоб побачити повний ÑпиÑок розширень.',
'mainpagetext' => 'Програмне Ð·Ð°Ð±ÐµÐ·Ð¿ÐµÑ‡ÐµÐ½Ð½Ñ Â«MediaWiki» уÑпішно вÑтановлене.',
'mainpagedocfooter' => 'Інформацію про роботу з цією вікі можна знайти в [//meta.wikimedia.org/wiki/Help:Contents поÑібнику кориÑтувача].
@@ -19685,10 +20361,19 @@ $3
);
/** Urdu (اردو)
+ * @author Noor2020
* @author පසිඳු කà·à·€à·’න්ද
*/
$messages['ur'] = array(
'config-information' => 'معلومات',
+ 'config-git' => 'Git ورژن کنٹرول مصنع لطی٠ملا: <code>$1</code> ۔',
+ 'config-git-bad' => 'GIT ورژن کنٹرول مصنع لطی٠نÛÙŠÚº ملا Û”',
+ 'config-mysql-only-myisam-dep' => "' ' تنبیÛ: ' '[[MyISAM|مائ اسام]] واحد دستیاب 'Ø°Ø®ÛŒØ±Û Ø¬Ø§ØªÛŒ انجن' ÛÛ’ جو مائی ایس کیو ایل Ú©Û’ لیے ÛÛ’ ØŒ جو Ú©Û Ù†Ø§Ù…ÙˆØ²ÙˆÚº ÛÛ’ میڈیا ÙˆÚ©ÛŒ Ú©Û’ لیے ،کیوں Ú©Û :
+* ÛŒÛ Ûموار قطاروں Ú©ÛŒ سÛولت بمشکل ÙراÛÙ… کرتا ÛÛ’
+* ÛŒÛ Ø¯ÙˆØ³Ø±Û’ انجنوں Ú©Û’ مقابلے Ø²ÛŒØ§Ø¯Û Ø¨Ú¯Ú‘ جاتا ÛÛ’
+* میڈیا ÙˆÚ©ÛŒ Ú©ÙˆÚˆ بیس ÛÙ…ÛŒØ´Û Ø³Ù†Ø¨Ú¾Ø§Ù„ Ù†ÛÙŠÚº پاتا مائی اسام Ú©Ùˆ Û”
+
+آپ کا مائی ایس کیو ایل کا نصب ÛÙ…ÛŒØ´Û Ø§Ù†Ù†Ùˆ ÚˆÛŒ بی Ú©ÛŒ سÛولت Ù†ÛÙŠÚº دے سکتا ØŒ ÛÙˆ سکتا ÛÛ’ ÛŒÛ Ù…Ø²ÛŒØ¯ ترقیاتی کام چاÛÛ’", # Fuzzy
'config-profile-fishbowl' => 'صر٠مجاز ایڈیٹرز',
'config-license-pd' => 'پبلک ڈومین',
'config-email-settings' => 'ای میل کی ترتیبات',
@@ -19714,8 +20399,10 @@ $messages['ur'] = array(
);
/** Uzbek (oʻzbekcha)
+ * @author Sociologist
*/
$messages['uz'] = array(
+ 'config-admin-password-blank' => 'Administrator hisob yozuvi uchun maxfiy soʻz kiriting.',
'mainpagetext' => "'''MediaWiki muvaffaqiyatli o'rnatildi.'''",
'mainpagedocfooter' => "Wiki dasturini ishlatish haqida ma'lumot olish uchun [//meta.wikimedia.org/wiki/Help:Contents Foydalanuvchi qo'llanmasi] sahifasiga murojaat qiling.
@@ -19723,7 +20410,7 @@ $messages['uz'] = array(
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Moslamalar ro'yxati]
* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki haqida ko'p so'raladigan savollar]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki yangi versiyasi chiqqanda xabar berish ro'yxati]",
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki yangi versiyasi chiqqanda xabar berish ro'yxati]", # Fuzzy
);
/** vèneto (vèneto)
@@ -19739,7 +20426,7 @@ I seguenti cołegamenti i xé en łengua inglese:
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Inpostasion de configurasion]
* [//www.mediawiki.org/wiki/Manual:FAQ Domande frequenti so MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mailing list anunsi MediaWiki]",
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mailing list anunsi MediaWiki]", # Fuzzy
);
/** Veps (vepsän kel’)
@@ -19752,7 +20439,7 @@ $messages['vep'] = array(
== Erased tarbhaižed resursad ==
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Järgendusiden nimikirjutez]
* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce poÄtnimikirjutez]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce poÄtnimikirjutez]', # Fuzzy
);
/** Vietnamese (Tiếng Việt)
@@ -19774,7 +20461,7 @@ $messages['vi'] = array(
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Danh sách các thiết lập cấu hình]
* [//www.mediawiki.org/wiki/Manual:FAQ Các câu há»i thÆ°á»ng gặp MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Danh sách gửi thư vỠviệc phát hành MediaWiki]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Danh sách gửi thư vỠviệc phát hành MediaWiki]', # Fuzzy
);
/** Volapük (Volapük)
@@ -19787,7 +20474,7 @@ $messages['vo'] = array(
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Parametalised]
* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki: SSP]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Potalised tefü fomams nulik ela MediaWiki]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Potalised tefü fomams nulik ela MediaWiki]', # Fuzzy
);
/** Võro (Võro)
@@ -19798,7 +20485,7 @@ $messages['vro'] = array(
* [//meta.wikimedia.org/wiki/MediaWiki_User%27s_Guide MediaWiki pruukmisoppus (inglüse keelen)].
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Säädmiisi oppus (inglüse keelen)]
* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki kõgõ küsütümbäq küsümiseq (inglüse keelen)]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce E-postilist, minka andas teedäq MediaWiki vahtsist kujõst].',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce E-postilist, minka andas teedäq MediaWiki vahtsist kujõst].', # Fuzzy
);
/** Walloon (walon)
@@ -19818,7 +20505,7 @@ $messages['war'] = array(
== Ha pagtikang==
* [//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
);
/** Wolof (Wolof)
@@ -19831,7 +20518,7 @@ $messages['wo'] = array(
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Limu jumtukaayi kocc-koccal gi]
* [//www.mediawiki.org/wiki/Manual:FAQ FAQ MediaWiki]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Limu waxtaan ci liy-génn ci MediaWiki]',
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Limu waxtaan ci liy-génn ci MediaWiki]', # Fuzzy
);
/** Wu (å´è¯­)
@@ -19844,7 +20531,7 @@ $messages['wuu'] = array(
== 入门 ==
* [//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 å‘布邮件列表]', # Fuzzy
);
/** Kalmyk (хальмг)
@@ -19857,7 +20544,7 @@ $messages['xal'] = array(
== ТуÑта заавр ==
* [//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
);
/** Yiddish (ייִדיש)
@@ -19907,7 +20594,7 @@ $messages['yo'] = array(
== Láti bẹ̀rẹ̀ ==
* [//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
);
/** Cantonese (粵語)
@@ -19919,7 +20606,7 @@ $messages['yue'] = array(
==開始使用==
* [//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
);
/** Zeeuws (Zeêuws)
@@ -19932,14 +20619,16 @@ $messages['zea'] = array(
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lieste mie instelliengen]
* [//www.mediawiki.org/wiki/Manual:FAQ Veehestelde vraehen (FAQ)]
-* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mailienglieste voe ankondigiengen van nieuwe versies]",
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mailienglieste voe ankondigiengen van nieuwe versies]", # Fuzzy
);
/** Simplified Chinese (中文(简体)‎)
* @author Anthony Fok
+ * @author Cwek
* @author Hydra
* @author Hzy980512
* @author Liangent
+ * @author Makecat
* @author PhiLiP
* @author Xiaomingyan
* @author Yfdyh000
@@ -20045,6 +20734,8 @@ $1',
Object caching is not enabled.",
'config-mod-security' => "'''警告''':您的æœåŠ¡å™¨å·²å¯åŠ¨[http://modsecurity.org/ mod_security]。若其é…置错误, 会导致MediaWiki和其他软件的错误并å…许用户任æ„å‘布内容。如果您é‡åˆ°ä»»ä½•é”™è¯¯ï¼Œè¯·æŸ¥é˜…[http://modsecurity.org/documentation/ mod_security文档]或è”系您的客æœã€‚",
'config-diff3-bad' => '找ä¸åˆ°GNU diff3。',
+ 'config-git' => 'å‘现Git版本控制软件:<code>$1</code>',
+ 'config-git-bad' => 'Git版本控制软件未找到。',
'config-imagemagick' => '已找到ImageMagick:<code>$1</code>。如果你å¯ç”¨äº†ä¸Šä¼ åŠŸèƒ½ï¼Œç¼©ç•¥å›¾åŠŸèƒ½ä¹Ÿå°†è¢«å¯ç”¨ã€‚',
'config-gd' => '已找到内建的GD图形库。如果你å¯ç”¨äº†ä¸Šä¼ åŠŸèƒ½ï¼Œç¼©ç•¥å›¾åŠŸèƒ½ä¹Ÿå°†è¢«å¯ç”¨ã€‚',
'config-no-scaling' => '找ä¸åˆ°GD库或ImageMagick。缩略图功能将ä¸å¯ç”¨ã€‚',
@@ -20056,7 +20747,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部件å¯ä»¥åœ¨æ­¤é™åˆ¶ä¸‹æ­£å¸¸å·¥ä½œï¼Œä½†å…¶æ€§èƒ½ä¼šè¢«é™ä½Žã€‚如果å¯èƒ½ï¼Œè¯·åœ¨<code>php.ini</code>中将<code>suhosin.get.max_value_length</code>设为1024或更高值,并在LocalSettings.php中将<code>$wgResourceLoaderMaxQueryLength</code>设为åŒä¸€å€¼ã€‚', # Fuzzy
+ '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>设为åŒä¸€å€¼ã€‚',
'config-db-type' => 'æ•°æ®åº“类型:',
'config-db-host' => 'æ•°æ®åº“主机:',
'config-db-host-help' => '如果您的数æ®åº“在别的æœåŠ¡å™¨ä¸Šï¼Œè¯·åœ¨è¿™é‡Œè¾“入它的域å或IP地å€ã€‚
@@ -20179,7 +20870,7 @@ chmod a+w $3</pre>',
'config-upgrade-done-no-regenerate' => 'å‡çº§å®Œæˆã€‚
现在您å¯ä»¥[$1 开始使用您的wiki]了。',
- 'config-regenerate' => 'é‡æ–°ç”Ÿæˆ<code>LocalSettings.php</code> →',
+ 'config-regenerate' => 'é‡æ–°ç”ŸæˆLocalSettings.php →',
'config-show-table-status' => '<code>SHOW TABLE STATUS</code>语å¥æ‰§è¡Œå¤±è´¥ï¼',
'config-unknown-collation' => "'''警告:'''æ•°æ®åº“使用了无法识别的整ç†ã€‚",
'config-db-web-account' => '供网页访问使用的数æ®åº“å¸å·',
@@ -20197,6 +20888,12 @@ chmod a+w $3</pre>',
如果您的MySQL程åºæ”¯æŒInnoDB,我们高度推è您使用该引擎替代MyISAM。
如果您的MySQL程åºä¸æ”¯æŒInnoDB,请考虑å‡çº§ã€‚",
+ 'config-mysql-only-myisam-dep' => "''''警告:'''MyISAM是MySQL唯一å¯ç”¨çš„存储引擎,但ä¸é€‚åˆç”¨äºŽMediaWiki,是由于:
+*由于åªæ”¯æŒè¡¨çº§é”定,几乎ä¸æ”¯æŒå¹¶å‘。
+*它比其他引擎更容易æŸå。
+*MediaWiki代ç ä¸èƒ½æ€»æ˜¯æŒ‰ç…§é¢„设地æ“作MyISAM。
+
+ä½ çš„MySQLä¸æ”¯æŒInnoDB,是时候å‡çº§äº†ã€‚",
'config-mysql-engine-help' => "'''InnoDB'''通常是最佳选项,因为它对并å‘æ“作有ç€è‰¯å¥½çš„支æŒã€‚
'''MyISAM'''在å•ç”¨æˆ·æˆ–åªè¯»çŽ¯å¢ƒä¸‹å¯èƒ½ä¼šæœ‰æ›´å¿«çš„性能表现。但MyISAMæ•°æ®åº“出错的概率一般è¦å¤§äºŽInnoDBæ•°æ®åº“。",
@@ -20289,6 +20986,8 @@ GNU自由文档许å¯è¯æ˜¯ç»´åŸºç™¾ç§‘曾ç»ä½¿ç”¨è¿‡çš„许å¯è¯ï¼Œå¹¶è¿„今æœ
'config-logo' => '标志URL:',
'config-logo-help' => '在MediaWiki的默认外观中,左侧æ èœå•ä¹‹ä¸Šæœ‰ä¸€å—135x160åƒç´ çš„标志区。请上传一幅相应大å°çš„图åƒï¼Œå¹¶åœ¨æ­¤è¾“å…¥URL。
+ä½ å¯ä»¥ç”¨<code>$wgStylePath</code>或<code>$wgScriptPath</code>æ¥è¡¨ç¤ºç›¸å¯¹äºŽè¿™äº›ä½ç½®çš„路径。
+
如果您ä¸å¸Œæœ›ä½¿ç”¨æ ‡å¿—,请将本处留空。',
'config-instantcommons' => 'å¯ç”¨å³æ—¶å…±äº«èµ„æº',
'config-instantcommons-help' => '[//www.mediawiki.org/wiki/InstantCommons å³æ—¶å…±äº«èµ„æº]å¯ä»¥è®©wiki使用æ¥è‡ª[//commons.wikimedia.org/ 维基共享资æº]网站的图åƒã€éŸ³é¢‘和其他媒体文件。è¦å¯ç”¨è¯¥åŠŸèƒ½ï¼ŒMediaWiki必须能够访问互è”网。
@@ -20383,6 +21082,7 @@ $3
/** Traditional Chinese (中文(ç¹é«”)‎)
* @author Anthony Fok
* @author Hzy980512
+ * @author Justincheng12345
* @author Liangent
* @author Mark85296341
* @author Simon Shek
@@ -20485,6 +21185,8 @@ $1',
Object caching is not enabled.",
'config-mod-security' => "'''警告''':您的æœå‹™å™¨å·²å•Ÿå‹•[http://modsecurity.org/ mod_security]。若其é…置錯誤, 會導致MediaWiki和其他軟件的錯誤並å…許用戶任æ„發布內容。如果您é‡åˆ°ä»»ä½•éŒ¯èª¤ï¼Œè«‹æŸ¥é–±[http://modsecurity.org/documentation/ mod_security文檔]或è¯ç¹«æ‚¨çš„客æœã€‚",
'config-diff3-bad' => '找ä¸åˆ°GNU diff3。',
+ 'config-git' => '發ç¾Git版本控制軟件:<code>$1</code>。',
+ 'config-git-bad' => '無法找到Git版本控制軟件。',
'config-imagemagick' => '已找到ImageMagick:<code>$1</code>。如果你啟用了上傳功能,縮略圖功能也將被啟用。',
'config-gd' => '已找到內建的GD圖形庫。如果你啟用了上傳功能,縮略圖功能也將被啟用。',
'config-no-scaling' => '找ä¸åˆ°GD庫或ImageMagick。縮略圖功能將ä¸å¯ç”¨ã€‚',
@@ -20576,7 +21278,8 @@ $1
'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-server-oracle' => '無效的數據庫TNS「$1ã€ã€‚
+è«‹åªä½¿ç”¨ã€ŒTNS Nameã€æˆ–「Easy Connect〠字串([http://docs.oracle.com/cd/E11882_01/network.112/e10836/naming.htm Oracle命å法])',
'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。
@@ -20619,7 +21322,7 @@ chmod a+w $3</pre>',
'config-upgrade-done-no-regenerate' => 'å‡ç´šå®Œæˆã€‚
ç¾åœ¨æ‚¨å¯ä»¥[$1 開始使用您的wiki]了。',
- 'config-regenerate' => 'é‡æ–°ç”Ÿæˆ<code>LocalSettings.php</code> →',
+ 'config-regenerate' => 'é‡æ–°ç”ŸæˆLocalSettings.php →',
'config-show-table-status' => '查詢<code>SHOW TABLE STATUS</code>失敗ï¼',
'config-unknown-collation' => "'''警告:'''數據庫使用了無法識別的整ç†ã€‚",
'config-db-web-account' => '供網é è¨ªå•ä½¿ç”¨çš„數據庫帳號',
@@ -20679,7 +21382,7 @@ chmod a+w $3</pre>',
'config-optional-continue' => '多å•æˆ‘一些å•é¡Œå§ã€‚',
'config-optional-skip' => '我已經ä¸è€ç…©äº†ï¼Œè¶•ç·Šå®‰è£æˆ‘çš„wiki。',
'config-profile' => '用戶權é™é…置:',
- 'config-profile-wiki' => '傳統wiki', # Fuzzy
+ 'config-profile-wiki' => '開放的wiki',
'config-profile-no-anon' => '需è¦è¨»å†Šå¸³è™Ÿ',
'config-profile-fishbowl' => '編輯å—é™',
'config-profile-private' => 'éžå…¬é–‹wiki',
@@ -20729,7 +21432,7 @@ GNU自由文檔許å¯è­‰æ˜¯ç¶­åŸºç™¾ç§‘曾經使用éŽçš„許å¯è­‰ï¼Œä¸¦è¿„今æœ
'config-logo' => '標誌URL:',
'config-logo-help' => '在MediaWiki的默èªå¤–觀中,左å´æ¬„èœå–®ä¹‹ä¸Šæœ‰ä¸€å¡Š135x160åƒç´ çš„標誌å€ã€‚請上傳一幅相應大å°çš„圖åƒï¼Œä¸¦åœ¨æ­¤è¼¸å…¥URL。
-如果您ä¸å¸Œæœ›ä½¿ç”¨æ¨™èªŒï¼Œè«‹å°‡æœ¬è™•ç•™ç©ºã€‚',
+如果您ä¸å¸Œæœ›ä½¿ç”¨æ¨™èªŒï¼Œè«‹å°‡æœ¬è™•ç•™ç©ºã€‚', # Fuzzy
'config-instantcommons' => '啟用å³æ™‚共享資æº',
'config-instantcommons-help' => '[//www.mediawiki.org/wiki/InstantCommons å³æ™‚共享資æº]å¯ä»¥è®“wiki使用來自[//commons.wikimedia.org/ 維基共享資æº]網站的圖åƒã€éŸ³é »å’Œå…¶ä»–媒體文件。è¦å•Ÿç”¨è©²åŠŸèƒ½ï¼ŒMediaWiki必須能夠訪å•äº’è¯ç¶²ã€‚
diff --git a/includes/installer/Installer.php b/includes/installer/Installer.php
index 9fd67c93..36c41910 100644
--- a/includes/installer/Installer.php
+++ b/includes/installer/Installer.php
@@ -46,7 +46,6 @@ abstract class Installer {
*/
protected $settings;
-
/**
* List of detected DBs, access using getCompiledDBs().
*
@@ -122,6 +121,7 @@ abstract class Installer {
'envCheckModSecurity',
'envCheckDiff3',
'envCheckGraphics',
+ 'envCheckGit',
'envCheckServer',
'envCheckPath',
'envCheckExtension',
@@ -130,6 +130,7 @@ abstract class Installer {
'envCheckLibicu',
'envCheckSuhosinMaxValueLength',
'envCheckCtype',
+ 'envCheckJSON',
);
/**
@@ -155,8 +156,8 @@ abstract class Installer {
'wgDBtype',
'wgDiff3',
'wgImageMagickConvertCommand',
+ 'wgGitBin',
'IP',
- 'wgServer',
'wgScriptPath',
'wgScriptExtension',
'wgMetaNamespace',
@@ -301,7 +302,8 @@ abstract class Installer {
/**
* URL to mediawiki-announce subscription
*/
- protected $mediaWikiAnnounceUrl = 'https://lists.wikimedia.org/mailman/subscribe/mediawiki-announce';
+ protected $mediaWikiAnnounceUrl =
+ 'https://lists.wikimedia.org/mailman/subscribe/mediawiki-announce';
/**
* Supported language codes for Mailman
@@ -406,7 +408,7 @@ abstract class Installer {
*/
public function doEnvironmentChecks() {
$phpVersion = phpversion();
- if( version_compare( $phpVersion, self::MINIMUM_PHP_VERSION, '>=' ) ) {
+ if ( version_compare( $phpVersion, self::MINIMUM_PHP_VERSION, '>=' ) ) {
$this->showMessage( 'config-env-php', $phpVersion );
$good = true;
} else {
@@ -414,7 +416,7 @@ abstract class Installer {
$good = false;
}
- if( $good ) {
+ if ( $good ) {
foreach ( $this->envChecks as $check ) {
$status = $this->$check();
if ( $status === false ) {
@@ -480,7 +482,7 @@ abstract class Installer {
$type = strtolower( $type );
if ( !isset( $this->dbInstallers[$type] ) ) {
- $class = ucfirst( $type ). 'Installer';
+ $class = ucfirst( $type ) . 'Installer';
$this->dbInstallers[$type] = new $class( $this );
}
@@ -500,16 +502,17 @@ abstract class Installer {
$_lsExists = file_exists( "$IP/LocalSettings.php" );
wfRestoreWarnings();
- if( !$_lsExists ) {
+ if ( !$_lsExists ) {
return false;
}
unset( $_lsExists );
- require( "$IP/includes/DefaultSettings.php" );
- require( "$IP/LocalSettings.php" );
+ require "$IP/includes/DefaultSettings.php";
+ require "$IP/LocalSettings.php";
if ( file_exists( "$IP/AdminSettings.php" ) ) {
- require( "$IP/AdminSettings.php" );
+ require "$IP/AdminSettings.php";
}
+
return get_defined_vars();
}
@@ -636,6 +639,7 @@ abstract class Installer {
'ss_users' => 0,
'ss_images' => 0 ),
__METHOD__, 'IGNORE' );
+
return Status::newGood();
}
@@ -659,13 +663,15 @@ abstract class Installer {
$allNames = array();
+ // Messages: config-type-mysql, config-type-postgres, config-type-oracle,
+ // config-type-sqlite
foreach ( self::getDBTypes() as $name ) {
$allNames[] = wfMessage( "config-type-$name" )->text();
}
$databases = $this->getCompiledDBs();
- $databases = array_flip ( $databases );
+ $databases = array_flip( $databases );
foreach ( array_keys( $databases ) as $db ) {
$installer = $this->getDBInstaller( $db );
$status = $installer->checkPrerequisites();
@@ -679,9 +685,11 @@ abstract class Installer {
$databases = array_flip( $databases );
if ( !$databases ) {
$this->showError( 'config-no-db', $wgLang->commaList( $allNames ) );
+
// @todo FIXME: This only works for the web installer!
return false;
}
+
return true;
}
@@ -689,7 +697,7 @@ abstract class Installer {
* Environment check for register_globals.
*/
protected function envCheckRegisterGlobals() {
- if( wfIniGetBool( 'register_globals' ) ) {
+ if ( wfIniGetBool( 'register_globals' ) ) {
$this->showMessage( 'config-register-globals' );
}
}
@@ -702,8 +710,10 @@ abstract class Installer {
$test = new PhpXmlBugTester();
if ( !$test->ok ) {
$this->showError( 'config-brokenlibxml' );
+
return false;
}
+
return true;
}
@@ -717,8 +727,10 @@ abstract class Installer {
$test->execute();
if ( !$test->ok ) {
$this->showError( 'config-using531', phpversion() );
+
return false;
}
+
return true;
}
@@ -727,10 +739,12 @@ abstract class Installer {
* @return bool
*/
protected function envCheckMagicQuotes() {
- if( wfIniGetBool( "magic_quotes_runtime" ) ) {
+ if ( wfIniGetBool( "magic_quotes_runtime" ) ) {
$this->showError( 'config-magic-quotes-runtime' );
+
return false;
}
+
return true;
}
@@ -741,8 +755,10 @@ abstract class Installer {
protected function envCheckMagicSybase() {
if ( wfIniGetBool( 'magic_quotes_sybase' ) ) {
$this->showError( 'config-magic-quotes-sybase' );
+
return false;
}
+
return true;
}
@@ -753,8 +769,10 @@ abstract class Installer {
protected function envCheckMbstring() {
if ( wfIniGetBool( 'mbstring.func_overload' ) ) {
$this->showError( 'config-mbstring' );
+
return false;
}
+
return true;
}
@@ -765,8 +783,10 @@ abstract class Installer {
protected function envCheckZE1() {
if ( wfIniGetBool( 'zend.ze1_compatibility_mode' ) ) {
$this->showError( 'config-ze1' );
+
return false;
}
+
return true;
}
@@ -779,6 +799,7 @@ abstract class Installer {
$this->setVar( '_SafeMode', true );
$this->showMessage( 'config-safe-mode' );
}
+
return true;
}
@@ -789,8 +810,10 @@ abstract class Installer {
protected function envCheckXML() {
if ( !function_exists( "utf8_encode" ) ) {
$this->showError( 'config-xml-bad' );
+
return false;
}
+
return true;
}
@@ -805,6 +828,7 @@ abstract class Installer {
protected function envCheckPCRE() {
if ( !function_exists( 'preg_match' ) ) {
$this->showError( 'config-pcre' );
+
return false;
}
wfSuppressWarnings();
@@ -817,8 +841,10 @@ abstract class Installer {
wfRestoreWarnings();
if ( $regexd != '--' || $regexprop != '--' ) {
$this->showError( 'config-pcre-no-utf8' );
+
return false;
}
+
return true;
}
@@ -835,16 +861,17 @@ abstract class Installer {
$n = wfShorthandToInteger( $limit );
- if( $n < $this->minMemorySize * 1024 * 1024 ) {
+ if ( $n < $this->minMemorySize * 1024 * 1024 ) {
$newLimit = "{$this->minMemorySize}M";
- if( ini_set( "memory_limit", $newLimit ) === false ) {
+ if ( ini_set( "memory_limit", $newLimit ) === false ) {
$this->showMessage( 'config-memory-bad', $limit );
} else {
$this->showMessage( 'config-memory-raised', $limit, $newLimit );
$this->setVar( '_RaiseMemory', true );
}
}
+
return true;
}
@@ -877,6 +904,7 @@ abstract class Installer {
if ( self::apacheModulePresent( 'mod_security' ) ) {
$this->showMessage( 'config-mod-security' );
}
+
return true;
}
@@ -896,6 +924,7 @@ abstract class Installer {
$this->setVar( 'wgDiff3', false );
$this->showMessage( 'config-diff3-bad' );
}
+
return true;
}
@@ -905,19 +934,44 @@ abstract class Installer {
*/
protected function envCheckGraphics() {
$names = array( wfIsWindows() ? 'convert.exe' : 'convert' );
- $convert = self::locateExecutableInDefaultPaths( $names, array( '$1 -version', 'ImageMagick' ) );
+ $versionInfo = array( '$1 -version', 'ImageMagick' );
+ $convert = self::locateExecutableInDefaultPaths( $names, $versionInfo );
$this->setVar( 'wgImageMagickConvertCommand', '' );
if ( $convert ) {
$this->setVar( 'wgImageMagickConvertCommand', $convert );
$this->showMessage( 'config-imagemagick', $convert );
+
return true;
} elseif ( function_exists( 'imagejpeg' ) ) {
$this->showMessage( 'config-gd' );
-
} else {
$this->showMessage( 'config-no-scaling' );
}
+
+ return true;
+ }
+
+ /**
+ * Search for git.
+ *
+ * @since 1.22
+ * @return bool
+ */
+ protected function envCheckGit() {
+ $names = array( wfIsWindows() ? 'git.exe' : 'git' );
+ $versionInfo = array( '$1 --version', 'git version' );
+
+ $git = self::locateExecutableInDefaultPaths( $names, $versionInfo );
+
+ if ( $git ) {
+ $this->setVar( 'wgGitBin', $git );
+ $this->showMessage( 'config-git', $git );
+ } else {
+ $this->setVar( 'wgGitBin', false );
+ $this->showMessage( 'config-git-bad' );
+ }
+
return true;
}
@@ -926,8 +980,11 @@ abstract class Installer {
*/
protected function envCheckServer() {
$server = $this->envGetDefaultServer();
- $this->showMessage( 'config-using-server', $server );
- $this->setVar( 'wgServer', $server );
+ if ( $server !== null ) {
+ $this->showMessage( 'config-using-server', $server );
+ $this->setVar( 'wgServer', $server );
+ }
+
return true;
}
@@ -946,12 +1003,18 @@ abstract class Installer {
$IP = dirname( dirname( __DIR__ ) );
$this->setVar( 'IP', $IP );
- $this->showMessage( 'config-using-uri', $this->getVar( 'wgServer' ), $this->getVar( 'wgScriptPath' ) );
+ $this->showMessage(
+ 'config-using-uri',
+ $this->getVar( 'wgServer' ),
+ $this->getVar( 'wgScriptPath' )
+ );
+
return true;
}
/**
* Environment check for setting the preferred PHP file extension.
+ * @return bool
*/
protected function envCheckExtension() {
// @todo FIXME: Detect this properly
@@ -961,11 +1024,12 @@ abstract class Installer {
$ext = 'php';
}
$this->setVar( 'wgScriptExtension', ".$ext" );
+
return true;
}
/**
- * TODO: document
+ * Environment check for preferred locale in shell
* @return bool
*/
protected function envCheckShellLocale() {
@@ -984,7 +1048,7 @@ abstract class Installer {
return true;
}
- $lines = wfArrayMap( 'trim', explode( "\n", $lines ) );
+ $lines = array_map( 'trim', explode( "\n", $lines ) );
$candidatesByLocale = array();
$candidatesByLang = array();
@@ -1004,8 +1068,9 @@ abstract class Installer {
}
# Try the current value of LANG.
- if ( isset( $candidatesByLocale[ getenv( 'LANG' ) ] ) ) {
+ if ( isset( $candidatesByLocale[getenv( 'LANG' )] ) ) {
$this->setVar( 'wgShellLocale', getenv( 'LANG' ) );
+
return true;
}
@@ -1014,6 +1079,7 @@ abstract class Installer {
foreach ( $commonLocales as $commonLocale ) {
if ( isset( $candidatesByLocale[$commonLocale] ) ) {
$this->setVar( 'wgShellLocale', $commonLocale );
+
return true;
}
}
@@ -1024,6 +1090,7 @@ abstract class Installer {
if ( isset( $candidatesByLang[$wikiLang] ) ) {
$m = reset( $candidatesByLang[$wikiLang] );
$this->setVar( 'wgShellLocale', $m[0] );
+
return true;
}
@@ -1031,6 +1098,7 @@ abstract class Installer {
if ( count( $candidatesByLocale ) ) {
$m = reset( $candidatesByLocale );
$this->setVar( 'wgShellLocale', $m[0] );
+
return true;
}
@@ -1039,7 +1107,7 @@ abstract class Installer {
}
/**
- * TODO: document
+ * Environment check for the permissions of the uploads directory
* @return bool
*/
protected function envCheckUploadsDirectory() {
@@ -1052,26 +1120,22 @@ abstract class Installer {
if ( !$safe ) {
$this->showMessage( 'config-uploads-not-safe', $dir );
}
+
return true;
}
/**
- * Checks if suhosin.get.max_value_length is set, and if so, sets
- * $wgResourceLoaderMaxQueryLength to that value in the generated
- * LocalSettings file
+ * Checks if suhosin.get.max_value_length is set, and if so generate
+ * a warning because it decreases ResourceLoader performance.
* @return bool
*/
protected function envCheckSuhosinMaxValueLength() {
$maxValueLength = ini_get( 'suhosin.get.max_value_length' );
- if ( $maxValueLength > 0 ) {
- if( $maxValueLength < 1024 ) {
- # Only warn if the value is below the sane 1024
- $this->showMessage( 'config-suhosin-max-value-length', $maxValueLength );
- }
- } else {
- $maxValueLength = -1;
+ if ( $maxValueLength > 0 && $maxValueLength < 1024 ) {
+ // Only warn if the value is below the sane 1024
+ $this->showMessage( 'config-suhosin-max-value-length', $maxValueLength );
}
- $this->setVar( 'wgResourceLoaderMaxQueryLength', $maxValueLength );
+
return true;
}
@@ -1122,14 +1186,14 @@ abstract class Installer {
* We're going to prefer the pecl extension here unless
* utf8_normalize is more up to date.
*/
- if( $utf8 ) {
+ if ( $utf8 ) {
$useNormalizer = 'utf8';
$utf8 = utf8_normalize( $not_normal_c, UtfNormal::UNORM_NFC );
if ( $utf8 !== $normal_c ) {
$needsUpdate = true;
}
}
- if( $intl ) {
+ if ( $intl ) {
$useNormalizer = 'intl';
$intl = normalizer_normalize( $not_normal_c, Normalizer::FORM_C );
if ( $intl !== $normal_c ) {
@@ -1137,12 +1201,13 @@ abstract class Installer {
}
}
- // Uses messages 'config-unicode-using-php', 'config-unicode-using-utf8', 'config-unicode-using-intl'
- if( $useNormalizer === 'php' ) {
+ // Uses messages 'config-unicode-using-php', 'config-unicode-using-utf8',
+ // 'config-unicode-using-intl'
+ if ( $useNormalizer === 'php' ) {
$this->showMessage( 'config-unicode-pure-php-warning' );
} else {
$this->showMessage( 'config-unicode-using-' . $useNormalizer );
- if( $needsUpdate ) {
+ if ( $needsUpdate ) {
$this->showMessage( 'config-unicode-update-warning' );
}
}
@@ -1154,8 +1219,23 @@ abstract class Installer {
protected function envCheckCtype() {
if ( !function_exists( 'ctype_digit' ) ) {
$this->showError( 'config-ctype' );
+
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * @return bool
+ */
+ protected function envCheckJSON() {
+ if ( !function_exists( 'json_decode' ) ) {
+ $this->showError( 'config-json' );
+
return false;
}
+
return true;
}
@@ -1184,8 +1264,8 @@ abstract class Installer {
* @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
+ * 0 => Command to run for version check, with $1 for the full executable name
+ * 1 => String to compare the output with
*
* If $versionInfo is not false, only executables with a version
* matching $versionInfo[1] will be returned.
@@ -1214,6 +1294,7 @@ abstract class Installer {
}
}
}
+
return false;
}
@@ -1225,12 +1306,13 @@ abstract class Installer {
* @return bool|string
*/
public static function locateExecutableInDefaultPaths( $names, $versionInfo = false ) {
- foreach( self::getPossibleBinPaths() as $path ) {
+ foreach ( self::getPossibleBinPaths() as $path ) {
$exe = self::locateExecutable( $path, $names, $versionInfo );
- if( $exe !== false ) {
+ if ( $exe !== false ) {
return $exe;
}
}
+
return false;
}
@@ -1264,8 +1346,7 @@ abstract class Installer {
try {
$text = Http::get( $url . $file, array( 'timeout' => 3 ) );
- }
- catch( MWException $e ) {
+ } catch ( MWException $e ) {
// Http::get throws with allow_url_fopen = false and no curl extension.
$text = null;
}
@@ -1273,6 +1354,7 @@ abstract class Installer {
if ( $text == 'exec' ) {
wfRestoreWarnings();
+
return $ext;
}
}
@@ -1297,6 +1379,7 @@ abstract class Installer {
ob_start();
phpinfo( INFO_MODULES );
$info = ob_get_clean();
+
return strpos( $info, $moduleName ) !== false;
}
@@ -1320,30 +1403,61 @@ abstract class Installer {
}
/**
+ * Load the extension credits for i18n strings. Very hacky for
+ * now, but I expect it only be used for 1.22.0 at the most.
+ */
+ public function getExtensionInfo( $file ) {
+ global $wgExtensionCredits, $wgVersion, $wgResourceModules;
+
+ $wgVersion = "1.22";
+ $wgResourceModules = array();
+ require_once( $file );
+ $e = array_values( $wgExtensionCredits );
+ if( $e ) {
+ $ext = array_values( $e[0] );
+ $wgExtensionCredits = array();
+ return $ext[0];
+ }
+ }
+
+ /**
* Finds extensions that follow the format /extensions/Name/Name.php,
* and returns an array containing the value for 'Name' for each found extension.
*
* @return array
*/
public function findExtensions() {
- if( $this->getVar( 'IP' ) === null ) {
- return false;
+ if ( $this->getVar( 'IP' ) === null ) {
+ return array();
}
- $exts = array();
$extDir = $this->getVar( 'IP' ) . '/extensions';
- $dh = opendir( $extDir );
+ if ( !is_readable( $extDir ) || !is_dir( $extDir ) ) {
+ return array();
+ }
+ $dh = opendir( $extDir );
+ $exts = array();
while ( ( $file = readdir( $dh ) ) !== false ) {
- if( !is_dir( "$extDir/$file" ) ) {
+ if ( !is_dir( "$extDir/$file" ) ) {
continue;
}
- if( file_exists( "$extDir/$file/$file.php" ) ) {
- $exts[] = $file;
+
+ $extFile = "$extDir/$file/$file.php";
+ $extI18NFile = "$extDir/$file/$file.i18n.php";
+ if ( file_exists( $extFile ) ) {
+ if ( $info = $this->getExtensionInfo( $extFile ) ) {
+ $exts[$info['name']] = $info;
+
+ if ( file_exists( $extI18NFile ) ) {
+ global $wgExtensionMessagesFiles;
+ $wgExtensionMessagesFiles[$file] = $extI18NFile;
+ }
+ }
}
}
- natcasesort( $exts );
-
+ closedir( $dh );
+ uksort( $exts, 'strnatcasecmp' );
return $exts;
}
@@ -1368,10 +1482,11 @@ abstract class Installer {
global $wgAutoloadClasses;
$wgAutoloadClasses = array();
- require( "$IP/includes/DefaultSettings.php" );
+ require "$IP/includes/DefaultSettings.php";
- foreach( $exts as $e ) {
- require_once( "$IP/extensions/$e/$e.php" );
+ $extensions = $this->findExtensions();
+ foreach ( $exts as $e ) {
+ require_once $extensions[$e]['path'];
}
$hooksWeWant = isset( $wgHooks['LoadExtensionSchemaUpdates'] ) ?
@@ -1398,29 +1513,29 @@ abstract class Installer {
*/
protected function getInstallSteps( DatabaseInstaller $installer ) {
$coreInstallSteps = array(
- array( 'name' => 'database', 'callback' => array( $installer, 'setupDatabase' ) ),
- array( 'name' => 'tables', 'callback' => array( $installer, 'createTables' ) ),
- array( 'name' => 'interwiki', 'callback' => array( $installer, 'populateInterwikiTable' ) ),
- array( 'name' => 'stats', 'callback' => array( $this, 'populateSiteStats' ) ),
- array( 'name' => 'keys', 'callback' => array( $this, 'generateKeys' ) ),
- array( 'name' => 'sysop', 'callback' => array( $this, 'createSysop' ) ),
- array( 'name' => 'mainpage', 'callback' => array( $this, 'createMainpage' ) ),
+ array( 'name' => 'database', 'callback' => array( $installer, 'setupDatabase' ) ),
+ array( 'name' => 'tables', 'callback' => array( $installer, 'createTables' ) ),
+ array( 'name' => 'interwiki', 'callback' => array( $installer, 'populateInterwikiTable' ) ),
+ array( 'name' => 'stats', 'callback' => array( $this, 'populateSiteStats' ) ),
+ array( 'name' => 'keys', 'callback' => array( $this, 'generateKeys' ) ),
+ array( 'name' => 'sysop', 'callback' => array( $this, 'createSysop' ) ),
+ array( 'name' => 'mainpage', 'callback' => array( $this, 'createMainpage' ) ),
);
// Build the array of install steps starting from the core install list,
// then adding any callbacks that wanted to attach after a given step
- foreach( $coreInstallSteps as $step ) {
+ foreach ( $coreInstallSteps as $step ) {
$this->installSteps[] = $step;
- if( isset( $this->extraInstallSteps[ $step['name'] ] ) ) {
+ if ( isset( $this->extraInstallSteps[$step['name']] ) ) {
$this->installSteps = array_merge(
$this->installSteps,
- $this->extraInstallSteps[ $step['name'] ]
+ $this->extraInstallSteps[$step['name']]
);
}
}
// Prepend any steps that want to be at the beginning
- if( isset( $this->extraInstallSteps['BEGINNING'] ) ) {
+ if ( isset( $this->extraInstallSteps['BEGINNING'] ) ) {
$this->installSteps = array_merge(
$this->extraInstallSteps['BEGINNING'],
$this->installSteps
@@ -1428,7 +1543,7 @@ abstract class Installer {
}
// Extensions should always go first, chance to tie into hooks and such
- if( count( $this->getVar( '_Extensions' ) ) ) {
+ if ( count( $this->getVar( '_Extensions' ) ) ) {
array_unshift( $this->installSteps,
array( 'name' => 'extensions', 'callback' => array( $this, 'includeExtensions' ) )
);
@@ -1437,6 +1552,7 @@ abstract class Installer {
'callback' => array( $installer, 'createExtensionTables' )
);
}
+
return $this->installSteps;
}
@@ -1453,7 +1569,7 @@ abstract class Installer {
$installer = $this->getDBInstaller();
$installer->preInstall();
$steps = $this->getInstallSteps( $installer );
- foreach( $steps as $stepObj ) {
+ foreach ( $steps as $stepObj ) {
$name = $stepObj['name'];
call_user_func_array( $startCB, array( $name ) );
@@ -1466,13 +1582,14 @@ abstract class Installer {
// If we've hit some sort of fatal, we need to bail.
// Callback already had a chance to do output above.
- if( !$status->isOk() ) {
+ if ( !$status->isOk() ) {
break;
}
}
- if( $status->isOk() ) {
+ if ( $status->isOk() ) {
$this->setVar( '_InstallDone', true );
}
+
return $installResults;
}
@@ -1486,6 +1603,7 @@ abstract class Installer {
if ( strval( $this->getVar( 'wgUpgradeKey' ) ) === '' ) {
$keys['wgUpgradeKey'] = 16;
}
+
return $this->doGenerateKeys( $keys );
}
@@ -1538,13 +1656,13 @@ abstract class Installer {
try {
$user->setPassword( $this->getVar( '_AdminPassword' ) );
- } catch( PasswordError $pwe ) {
+ } catch ( PasswordError $pwe ) {
return Status::newFatal( 'config-admin-error-password', $name, $pwe->getMessage() );
}
$user->addGroup( 'sysop' );
$user->addGroup( 'bureaucrat' );
- if( $this->getVar( '_AdminEmail' ) ) {
+ if ( $this->getVar( '_AdminEmail' ) ) {
$user->setEmail( $this->getVar( '_AdminEmail' ) );
}
$user->saveSettings();
@@ -1555,7 +1673,7 @@ abstract class Installer {
}
$status = Status::newGood();
- if( $this->getVar( '_Subscribe' ) && $this->getVar( '_AdminEmail' ) ) {
+ if ( $this->getVar( '_Subscribe' ) && $this->getVar( '_AdminEmail' ) ) {
$this->subscribeToMediaWikiAnnounce( $status );
}
@@ -1567,23 +1685,23 @@ abstract class Installer {
*/
private function subscribeToMediaWikiAnnounce( Status $s ) {
$params = array(
- 'email' => $this->getVar( '_AdminEmail' ),
+ 'email' => $this->getVar( '_AdminEmail' ),
'language' => 'en',
- 'digest' => 0
+ 'digest' => 0
);
// Mailman doesn't support as many languages as we do, so check to make
// sure their selected language is available
$myLang = $this->getVar( '_UserLang' );
- if( in_array( $myLang, $this->mediaWikiAnnounceLanguages ) ) {
+ if ( in_array( $myLang, $this->mediaWikiAnnounceLanguages ) ) {
$myLang = $myLang == 'pt-br' ? 'pt_BR' : $myLang; // rewrite to Mailman's pt_BR
$params['language'] = $myLang;
}
- if( MWHttpRequest::canMakeRequests() ) {
+ if ( MWHttpRequest::canMakeRequests() ) {
$res = MWHttpRequest::factory( $this->mediaWikiAnnounceUrl,
array( 'method' => 'POST', 'postData' => $params ) )->execute();
- if( !$res->isOK() ) {
+ if ( !$res->isOK() ) {
$s->warning( 'config-install-subscribe-fail', $res->getMessage() );
}
} else {
@@ -1601,17 +1719,18 @@ abstract class Installer {
$status = Status::newGood();
try {
$page = WikiPage::factory( Title::newMainPage() );
- $content = new WikitextContent (
+ $content = new WikitextContent(
wfMessage( 'mainpagetext' )->inContentLanguage()->text() . "\n\n" .
wfMessage( 'mainpagedocfooter' )->inContentLanguage()->text()
);
$page->doEditContent( $content,
- '',
- EDIT_NEW,
- false,
- User::newFromName( 'MediaWiki default' ) );
- } catch (MWException $e) {
+ '',
+ EDIT_NEW,
+ false,
+ User::newFromName( 'MediaWiki default' )
+ );
+ } catch ( MWException $e ) {
//using raw, because $wgShowExceptionDetails can not be set yet
$status->fatal( 'config-install-mainpage-failed', $e->getMessage() );
}
@@ -1646,6 +1765,11 @@ abstract class Installer {
// Some of the environment checks make shell requests, remove limits
$GLOBALS['wgMaxShellMemory'] = 0;
+
+ // Don't bother embedding images into generated CSS, which is not cached
+ $GLOBALS['wgResourceLoaderLESSFunctions']['embeddable'] = function( $frame, $less ) {
+ return $less->toBool( false );
+ };
}
/**
diff --git a/includes/installer/LocalSettingsGenerator.php b/includes/installer/LocalSettingsGenerator.php
index 72ea3db7..5c803d3e 100644
--- a/includes/installer/LocalSettingsGenerator.php
+++ b/includes/installer/LocalSettingsGenerator.php
@@ -73,14 +73,14 @@ class LocalSettingsGenerator {
'wgEnotifWatchlist', 'wgEmailAuthentication', 'wgEnableUploads', 'wgUseInstantCommons'
);
- foreach( $confItems as $c ) {
+ foreach ( $confItems as $c ) {
$val = $installer->getVar( $c );
- if( in_array( $c, $boolItems ) ) {
+ if ( in_array( $c, $boolItems ) ) {
$val = wfBoolToStr( $val );
}
- if ( !in_array( $c, $unescaped ) ) {
+ if ( !in_array( $c, $unescaped ) && $val !== null ) {
$val = self::escapePhpString( $val );
}
@@ -136,15 +136,17 @@ class LocalSettingsGenerator {
public function getText() {
$localSettings = $this->getDefaultText();
- if( count( $this->extensions ) ) {
+ if ( count( $this->extensions ) ) {
+ $extensions = $this->installer->findExtensions();
$localSettings .= "
# Enabled Extensions. Most extensions are enabled by including the base extension file here
# but check specific extension documentation for more details
# The following extensions were automatically enabled:\n";
- foreach( $this->extensions as $extName ) {
- $encExtName = self::escapePhpString( $extName );
- $localSettings .= "require_once( \"\$IP/extensions/$encExtName/$encExtName.php\" );\n";
+ $ip = $this->installer->getVar( 'IP' );
+ foreach ( $this->extensions as $ext) {
+ $path = str_replace( $ip, '$IP', $extensions[$ext]['path'] );
+ $localSettings .= "require_once \"$path\";\n";
}
}
@@ -169,13 +171,13 @@ class LocalSettingsGenerator {
protected function buildMemcachedServerList() {
$servers = $this->values['_MemCachedServers'];
- if( !$servers ) {
+ if ( !$servers ) {
return 'array()';
} else {
$ret = 'array( ';
$servers = explode( ',', $servers );
- foreach( $servers as $srv ) {
+ foreach ( $servers as $srv ) {
$srv = trim( $srv );
$ret .= "'$srv', ";
}
@@ -188,33 +190,33 @@ class LocalSettingsGenerator {
* @return String
*/
protected function getDefaultText() {
- if( !$this->values['wgImageMagickConvertCommand'] ) {
+ if ( !$this->values['wgImageMagickConvertCommand'] ) {
$this->values['wgImageMagickConvertCommand'] = '/usr/bin/convert';
$magic = '#';
} else {
$magic = '';
}
- if( !$this->values['wgShellLocale'] ) {
+ if ( !$this->values['wgShellLocale'] ) {
$this->values['wgShellLocale'] = 'en_US.UTF-8';
$locale = '#';
} else {
$locale = '';
}
- //$rightsUrl = $this->values['wgRightsUrl'] ? '' : '#'; // TODO: Fixme, I'm unused!
+ //$rightsUrl = $this->values['wgRightsUrl'] ? '' : '#'; // @todo FIXME: I'm unused!
$hashedUploads = $this->safeMode ? '' : '#';
$metaNamespace = '';
- if( $this->values['wgMetaNamespace'] !== $this->values['wgSitename'] ) {
+ if ( $this->values['wgMetaNamespace'] !== $this->values['wgSitename'] ) {
$metaNamespace = "\$wgMetaNamespace = \"{$this->values['wgMetaNamespace']}\";\n";
}
$groupRights = '';
- if( $this->groupPermissions ) {
+ if ( $this->groupPermissions ) {
$groupRights .= "# The following permissions were set based on your choice in the installer\n";
- foreach( $this->groupPermissions as $group => $rightArr ) {
+ foreach ( $this->groupPermissions as $group => $rightArr ) {
$group = self::escapePhpString( $group );
- foreach( $rightArr as $right => $perm ) {
+ foreach ( $rightArr as $right => $perm ) {
$right = self::escapePhpString( $right );
$groupRights .= "\$wgGroupPermissions['$group']['$right'] = " .
wfBoolToStr( $perm ) . ";\n";
@@ -222,12 +224,18 @@ class LocalSettingsGenerator {
}
}
- switch( $this->values['wgMainCacheType'] ) {
+ $wgServerSetting = "";
+ if ( array_key_exists( 'wgServer', $this->values ) && $this->values['wgServer'] !== null ) {
+ $wgServerSetting = "\n## The protocol and server name to use in fully-qualified URLs\n";
+ $wgServerSetting .= "\$wgServer = \"{$this->values['wgServer']}\";\n";
+ }
+
+ switch ( $this->values['wgMainCacheType'] ) {
case 'anything':
case 'db':
case 'memcached':
case 'accel':
- $cacheType = 'CACHE_' . strtoupper( $this->values['wgMainCacheType']);
+ $cacheType = 'CACHE_' . strtoupper( $this->values['wgMainCacheType'] );
break;
case 'none':
default:
@@ -235,6 +243,7 @@ class LocalSettingsGenerator {
}
$mcservers = $this->buildMemcachedServerList();
+
return "<?php
# This file was automatically generated by the MediaWiki {$GLOBALS['wgVersion']}
# installer. If you make manual changes, please keep track in case you
@@ -264,10 +273,7 @@ if ( !defined( 'MEDIAWIKI' ) ) {
## http://www.mediawiki.org/wiki/Manual:Short_URL
\$wgScriptPath = \"{$this->values['wgScriptPath']}\";
\$wgScriptExtension = \"{$this->values['wgScriptExtension']}\";
-
-## The protocol and server name to use in fully-qualified URLs
-\$wgServer = \"{$this->values['wgServer']}\";
-
+${wgServerSetting}
## The relative URL path to the skins directory
\$wgStylePath = \"\$wgScriptPath/skins\";
@@ -335,7 +341,7 @@ if ( !defined( 'MEDIAWIKI' ) ) {
\$wgUpgradeKey = \"{$this->values['wgUpgradeKey']}\";
## Default skin: you can change the default skin. Use the internal symbolic
-## names, ie 'standard', 'nostalgia', 'cologneblue', 'monobook', 'vector':
+## names, ie 'cologneblue', 'monobook', 'vector':
\$wgDefaultSkin = \"{$this->values['wgDefaultSkin']}\";
## For attaching licensing metadata to pages, and displaying an
@@ -349,13 +355,6 @@ if ( !defined( 'MEDIAWIKI' ) ) {
# Path to the GNU diff3 utility. Used for conflict resolution.
\$wgDiff3 = \"{$this->values['wgDiff3']}\";
-# Query string length limit for ResourceLoader. You should only set this if
-# your web server has a query string length limit (then set it to that limit),
-# or if you have suhosin.get.max_value_length set in php.ini (then set it to
-# that value)
-\$wgResourceLoaderMaxQueryLength = {$this->values['wgResourceLoaderMaxQueryLength']};
-
{$groupRights}";
}
-
}
diff --git a/includes/installer/MysqlInstaller.php b/includes/installer/MysqlInstaller.php
index f9a8ce75..5f76972c 100644
--- a/includes/installer/MysqlInstaller.php
+++ b/includes/installer/MysqlInstaller.php
@@ -64,15 +64,11 @@ class MysqlInstaller extends DatabaseInstaller {
return 'mysql';
}
- public function __construct( $parent ) {
- parent::__construct( $parent );
- }
-
/**
* @return Bool
*/
public function isCompiled() {
- return self::checkExtension( 'mysql' );
+ return self::checkExtension( 'mysql' ) || self::checkExtension( 'mysqli' );
}
/**
@@ -86,12 +82,18 @@ class MysqlInstaller extends DatabaseInstaller {
* @return string
*/
public function getConnectForm() {
- return
- $this->getTextBox( 'wgDBserver', 'config-db-host', array(), $this->parent->getHelpBox( 'config-db-host-help' ) ) .
+ return $this->getTextBox(
+ 'wgDBserver',
+ 'config-db-host',
+ array(),
+ $this->parent->getHelpBox( 'config-db-host-help' )
+ ) .
Html::openElement( 'fieldset' ) .
Html::element( 'legend', array(), wfMessage( 'config-db-wiki-settings' )->text() ) .
- $this->getTextBox( 'wgDBname', 'config-db-name', array( 'dir' => 'ltr' ), $this->parent->getHelpBox( 'config-db-name-help' ) ) .
- $this->getTextBox( 'wgDBprefix', 'config-db-prefix', array( 'dir' => 'ltr' ), $this->parent->getHelpBox( 'config-db-prefix-help' ) ) .
+ $this->getTextBox( 'wgDBname', 'config-db-name', array( 'dir' => 'ltr' ),
+ $this->parent->getHelpBox( 'config-db-name-help' ) ) .
+ $this->getTextBox( 'wgDBprefix', 'config-db-prefix', array( 'dir' => 'ltr' ),
+ $this->parent->getHelpBox( 'config-db-prefix-help' ) ) .
Html::closeElement( 'fieldset' ) .
$this->getInstallUserBox();
}
@@ -148,19 +150,18 @@ class MysqlInstaller extends DatabaseInstaller {
public function openConnection() {
$status = Status::newGood();
try {
- $db = new DatabaseMysql(
- $this->getVar( 'wgDBserver' ),
- $this->getVar( '_InstallUser' ),
- $this->getVar( '_InstallPassword' ),
- false,
- false,
- 0,
- $this->getVar( 'wgDBprefix' )
- );
+ $db = DatabaseBase::factory( 'mysql', array(
+ 'host' => $this->getVar( 'wgDBserver' ),
+ 'user' => $this->getVar( '_InstallUser' ),
+ 'password' => $this->getVar( '_InstallPassword' ),
+ 'dbname' => false,
+ 'flags' => 0,
+ 'tablePrefix' => $this->getVar( 'wgDBprefix' ) ) );
$status->value = $db;
} catch ( DBConnectionError $e ) {
$status->fatal( 'config-connection-error', $e->getMessage() );
}
+
return $status;
}
@@ -170,6 +171,7 @@ class MysqlInstaller extends DatabaseInstaller {
$status = $this->getConnection();
if ( !$status->isOK() ) {
$this->parent->showStatusError( $status );
+
return;
}
/**
@@ -243,6 +245,7 @@ class MysqlInstaller extends DatabaseInstaller {
}
}
$engines = array_intersect( $this->supportedEngines, $engines );
+
return $engines;
}
@@ -327,6 +330,7 @@ class MysqlInstaller extends DatabaseInstaller {
// Can't grant everything
return false;
}
+
return true;
}
@@ -350,17 +354,24 @@ class MysqlInstaller extends DatabaseInstaller {
$s .= Xml::openElement( 'div', array(
'id' => 'dbMyisamWarning'
- ));
- $s .= $this->parent->getWarningBox( wfMessage( 'config-mysql-myisam-dep' )->text() );
+ ) );
+ $myisamWarning = 'config-mysql-myisam-dep';
+ if ( count( $engines ) === 1 ) {
+ $myisamWarning = 'config-mysql-only-myisam-dep';
+ }
+ $s .= $this->parent->getWarningBox( wfMessage( $myisamWarning )->text() );
$s .= Xml::closeElement( 'div' );
- if( $this->getVar( '_MysqlEngine' ) != 'MyISAM' ) {
+ if ( $this->getVar( '_MysqlEngine' ) != 'MyISAM' ) {
$s .= Xml::openElement( 'script', array( 'type' => 'text/javascript' ) );
$s .= '$(\'#dbMyisamWarning\').hide();';
$s .= Xml::closeElement( 'script' );
}
if ( count( $engines ) >= 2 ) {
+ // getRadioSet() builds a set of labeled radio buttons.
+ // For grep: The following messages are used as the item labels:
+ // config-mysql-innodb, config-mysql-myisam
$s .= $this->getRadioSet( array(
'var' => '_MysqlEngine',
'label' => 'config-mysql-engine',
@@ -369,11 +380,14 @@ class MysqlInstaller extends DatabaseInstaller {
'itemAttribs' => array(
'MyISAM' => array(
'class' => 'showHideRadio',
- 'rel' => 'dbMyisamWarning'),
+ 'rel' => 'dbMyisamWarning'
+ ),
'InnoDB' => array(
'class' => 'hideShowRadio',
- 'rel' => 'dbMyisamWarning')
- )));
+ 'rel' => 'dbMyisamWarning'
+ )
+ )
+ ) );
$s .= $this->parent->getHelpBox( 'config-mysql-engine-help' );
}
@@ -385,12 +399,15 @@ class MysqlInstaller extends DatabaseInstaller {
// Do charset selector
if ( count( $charsets ) >= 2 ) {
+ // getRadioSet() builds a set of labeled radio buttons.
+ // For grep: The following messages are used as the item labels:
+ // config-mysql-binary, config-mysql-utf8
$s .= $this->getRadioSet( array(
'var' => '_MysqlCharset',
'label' => 'config-mysql-charset',
'itemLabelPrefix' => 'config-mysql-',
'values' => $charsets
- ));
+ ) );
$s .= $this->parent->getHelpBox( 'config-mysql-charset-help' );
}
@@ -419,15 +436,13 @@ class MysqlInstaller extends DatabaseInstaller {
if ( !$create ) {
// Test the web account
try {
- new DatabaseMysql(
- $this->getVar( 'wgDBserver' ),
- $this->getVar( 'wgDBuser' ),
- $this->getVar( 'wgDBpassword' ),
- false,
- false,
- 0,
- $this->getVar( 'wgDBprefix' )
- );
+ $db = DatabaseBase::factory( 'mysql', array(
+ 'host' => $this->getVar( 'wgDBserver' ),
+ 'user' => $this->getVar( 'wgDBuser' ),
+ 'password' => $this->getVar( 'wgDBpassword' ),
+ 'dbname' => false,
+ 'flags' => 0,
+ 'tablePrefix' => $this->getVar( 'wgDBprefix' ) ) );
} catch ( DBConnectionError $e ) {
return Status::newFatal( 'config-connection-error', $e->getMessage() );
}
@@ -443,6 +458,7 @@ class MysqlInstaller extends DatabaseInstaller {
if ( !in_array( $this->getVar( '_MysqlCharset' ), $charsets ) ) {
$this->setVar( '_MysqlCharset', reset( $charsets ) );
}
+
return Status::newGood();
}
@@ -465,11 +481,15 @@ class MysqlInstaller extends DatabaseInstaller {
}
$conn = $status->value;
$dbName = $this->getVar( 'wgDBname' );
- if( !$conn->selectDB( $dbName ) ) {
- $conn->query( "CREATE DATABASE " . $conn->addIdentifierQuotes( $dbName ), __METHOD__ );
+ if ( !$conn->selectDB( $dbName ) ) {
+ $conn->query(
+ "CREATE DATABASE " . $conn->addIdentifierQuotes( $dbName ) . "CHARACTER SET utf8",
+ __METHOD__
+ );
$conn->selectDB( $dbName );
}
$this->setupSchemaVars();
+
return $status;
}
@@ -478,7 +498,7 @@ class MysqlInstaller extends DatabaseInstaller {
*/
public function setupUser() {
$dbUser = $this->getVar( 'wgDBuser' );
- if( $dbUser == $this->getVar( '_InstallUser' ) ) {
+ if ( $dbUser == $this->getVar( '_InstallUser' ) ) {
return Status::newGood();
}
$status = $this->getConnection();
@@ -496,15 +516,13 @@ class MysqlInstaller extends DatabaseInstaller {
if ( $this->getVar( '_CreateDBAccount' ) ) {
// Before we blindly try to create a user that already has access,
try { // first attempt to connect to the database
- new DatabaseMysql(
- $server,
- $dbUser,
- $password,
- false,
- false,
- 0,
- $this->getVar( 'wgDBprefix' )
- );
+ $db = DatabaseBase::factory( 'mysql', array(
+ 'host' => $server,
+ 'user' => $dbUser,
+ 'password' => $password,
+ 'dbname' => false,
+ 'flags' => 0,
+ 'tablePrefix' => $this->getVar( 'wgDBprefix' ) ) );
$grantableNames[] = $this->buildFullUserName( $dbUser, $server );
$tryToCreate = false;
} catch ( DBConnectionError $e ) {
@@ -515,7 +533,7 @@ class MysqlInstaller extends DatabaseInstaller {
$tryToCreate = false;
}
- if( $tryToCreate ) {
+ if ( $tryToCreate ) {
$createHostList = array(
$server,
'localhost',
@@ -526,16 +544,16 @@ class MysqlInstaller extends DatabaseInstaller {
$createHostList = array_unique( $createHostList );
$escPass = $this->db->addQuotes( $password );
- foreach( $createHostList as $host ) {
+ foreach ( $createHostList as $host ) {
$fullName = $this->buildFullUserName( $dbUser, $host );
- if( !$this->userDefinitelyExists( $dbUser, $host ) ) {
- try{
+ if ( !$this->userDefinitelyExists( $dbUser, $host ) ) {
+ try {
$this->db->begin( __METHOD__ );
$this->db->query( "CREATE USER $fullName IDENTIFIED BY $escPass", __METHOD__ );
$this->db->commit( __METHOD__ );
$grantableNames[] = $fullName;
- } catch( DBQueryError $dqe ) {
- if( $this->db->lastErrno() == 1396 /* ER_CANNOT_USER */ ) {
+ } catch ( DBQueryError $dqe ) {
+ if ( $this->db->lastErrno() == 1396 /* ER_CANNOT_USER */ ) {
// User (probably) already exists
$this->db->rollback( __METHOD__ );
$status->warning( 'config-install-user-alreadyexists', $dbUser );
@@ -558,12 +576,12 @@ class MysqlInstaller extends DatabaseInstaller {
// Try to grant to all the users we know exist or we were able to create
$dbAllTables = $this->db->addIdentifierQuotes( $dbName ) . '.*';
- foreach( $grantableNames as $name ) {
+ foreach ( $grantableNames as $name ) {
try {
$this->db->begin( __METHOD__ );
$this->db->query( "GRANT ALL PRIVILEGES ON $dbAllTables TO $name", __METHOD__ );
$this->db->commit( __METHOD__ );
- } catch( DBQueryError $dqe ) {
+ } catch ( DBQueryError $dqe ) {
$this->db->rollback( __METHOD__ );
$status->fatal( 'config-install-user-grant-failed', $dbUser, $dqe->getText() );
}
@@ -593,11 +611,11 @@ class MysqlInstaller extends DatabaseInstaller {
try {
$res = $this->db->selectRow( 'mysql.user', array( 'Host', 'User' ),
array( 'Host' => $host, 'User' => $user ), __METHOD__ );
+
return (bool)$res;
- } catch( DBQueryError $dqe ) {
+ } catch ( DBQueryError $dqe ) {
return false;
}
-
}
/**
@@ -614,6 +632,7 @@ class MysqlInstaller extends DatabaseInstaller {
if ( $this->getVar( '_MysqlCharset' ) !== null ) {
$options[] = 'DEFAULT CHARSET=' . $this->getVar( '_MysqlCharset' );
}
+
return implode( ', ', $options );
}
@@ -635,8 +654,8 @@ class MysqlInstaller extends DatabaseInstaller {
$dbmysql5 = wfBoolToStr( $this->getVar( 'wgDBmysql5', true ) );
$prefix = LocalSettingsGenerator::escapePhpString( $this->getVar( 'wgDBprefix' ) );
$tblOpts = LocalSettingsGenerator::escapePhpString( $this->getTableOptions() );
- return
-"# MySQL specific settings
+
+ return "# MySQL specific settings
\$wgDBprefix = \"{$prefix}\";
# MySQL table options to use during installation or update
diff --git a/includes/installer/MysqlUpdater.php b/includes/installer/MysqlUpdater.php
index 030c57fc..f348ecd4 100644
--- a/includes/installer/MysqlUpdater.php
+++ b/includes/installer/MysqlUpdater.php
@@ -31,209 +31,214 @@ 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' ),
+ array( 'addField', 'ipblocks', 'ipb_id', 'patch-ipblocks.sql' ),
+ array( 'addField', 'ipblocks', 'ipb_expiry', 'patch-ipb_expiry.sql' ),
array( 'doInterwikiUpdate' ),
array( 'doIndexUpdate' ),
- array( 'addTable', 'hitcounter', 'patch-hitcounter.sql' ),
- array( 'addField', 'recentchanges', 'rc_type', 'patch-rc_type.sql' ),
+ array( 'addTable', 'hitcounter', 'patch-hitcounter.sql' ),
+ array( 'addField', 'recentchanges', 'rc_type', 'patch-rc_type.sql' ),
// 1.3
- array( 'addField', 'user', 'user_real_name', 'patch-user-realname.sql' ),
- array( 'addTable', 'querycache', 'patch-querycache.sql' ),
- array( 'addTable', 'objectcache', 'patch-objectcache.sql' ),
- array( 'addTable', 'categorylinks', 'patch-categorylinks.sql' ),
+ array( 'addField', 'user', 'user_real_name', 'patch-user-realname.sql' ),
+ array( 'addTable', 'querycache', 'patch-querycache.sql' ),
+ array( 'addTable', 'objectcache', 'patch-objectcache.sql' ),
+ array( 'addTable', 'categorylinks', 'patch-categorylinks.sql' ),
array( 'doOldLinksUpdate' ),
array( 'doFixAncientImagelinks' ),
- array( 'addField', 'recentchanges', 'rc_ip', 'patch-rc_ip.sql' ),
+ array( 'addField', 'recentchanges', 'rc_ip', 'patch-rc_ip.sql' ),
// 1.4
- array( 'addIndex', 'image', 'PRIMARY', 'patch-image_name_primary.sql' ),
- array( 'addField', 'recentchanges', 'rc_id', 'patch-rc_id.sql' ),
- array( 'addField', 'recentchanges', 'rc_patrolled', 'patch-rc-patrol.sql' ),
- array( 'addTable', 'logging', 'patch-logging.sql' ),
- array( 'addField', 'user', 'user_token', 'patch-user_token.sql' ),
- array( 'addField', 'watchlist', 'wl_notificationtimestamp', 'patch-email-notification.sql' ),
+ array( 'addIndex', 'image', 'PRIMARY', 'patch-image_name_primary.sql' ),
+ array( 'addField', 'recentchanges', 'rc_id', 'patch-rc_id.sql' ),
+ array( 'addField', 'recentchanges', 'rc_patrolled', 'patch-rc-patrol.sql' ),
+ array( 'addTable', 'logging', 'patch-logging.sql' ),
+ array( 'addField', 'user', 'user_token', 'patch-user_token.sql' ),
+ array( 'addField', 'watchlist', 'wl_notificationtimestamp', 'patch-email-notification.sql' ),
array( 'doWatchlistUpdate' ),
- array( 'dropField', 'user', 'user_emailauthenticationtimestamp', 'patch-email-authentication.sql' ),
+ array( 'dropField', 'user', 'user_emailauthenticationtimestamp',
+ 'patch-email-authentication.sql' ),
// 1.5
array( 'doSchemaRestructuring' ),
- array( 'addField', 'logging', 'log_params', 'patch-log_params.sql' ),
- array( 'checkBin', 'logging', 'log_title', 'patch-logging-title.sql', ),
- array( 'addField', 'archive', 'ar_rev_id', 'patch-archive-rev_id.sql' ),
- array( 'addField', 'page', 'page_len', 'patch-page_len.sql' ),
- array( 'dropField', 'revision', 'inverse_timestamp', 'patch-inverse_timestamp.sql' ),
- array( 'addField', 'revision', 'rev_text_id', 'patch-rev_text_id.sql' ),
- array( 'addField', 'revision', 'rev_deleted', 'patch-rev_deleted.sql' ),
- array( 'addField', 'image', 'img_width', 'patch-img_width.sql' ),
- array( 'addField', 'image', 'img_metadata', 'patch-img_metadata.sql' ),
- array( 'addField', 'user', 'user_email_token', 'patch-user_email_token.sql' ),
- array( 'addField', 'archive', 'ar_text_id', 'patch-archive-text_id.sql' ),
+ array( 'addField', 'logging', 'log_params', 'patch-log_params.sql' ),
+ array( 'checkBin', 'logging', 'log_title', 'patch-logging-title.sql', ),
+ array( 'addField', 'archive', 'ar_rev_id', 'patch-archive-rev_id.sql' ),
+ array( 'addField', 'page', 'page_len', 'patch-page_len.sql' ),
+ array( 'dropField', 'revision', 'inverse_timestamp', 'patch-inverse_timestamp.sql' ),
+ array( 'addField', 'revision', 'rev_text_id', 'patch-rev_text_id.sql' ),
+ array( 'addField', 'revision', 'rev_deleted', 'patch-rev_deleted.sql' ),
+ array( 'addField', 'image', 'img_width', 'patch-img_width.sql' ),
+ array( 'addField', 'image', 'img_metadata', 'patch-img_metadata.sql' ),
+ array( 'addField', 'user', 'user_email_token', 'patch-user_email_token.sql' ),
+ array( 'addField', 'archive', 'ar_text_id', 'patch-archive-text_id.sql' ),
array( 'doNamespaceSize' ),
- array( 'addField', 'image', 'img_media_type', 'patch-img_media_type.sql' ),
+ array( 'addField', 'image', 'img_media_type', 'patch-img_media_type.sql' ),
array( 'doPagelinksUpdate' ),
- array( 'dropField', 'image', 'img_type', 'patch-drop_img_type.sql' ),
+ array( 'dropField', 'image', 'img_type', 'patch-drop_img_type.sql' ),
array( 'doUserUniqueUpdate' ),
array( 'doUserGroupsUpdate' ),
- array( 'addField', 'site_stats', 'ss_total_pages', 'patch-ss_total_articles.sql' ),
- array( 'addTable', 'user_newtalk', 'patch-usernewtalk2.sql' ),
- array( 'addTable', 'transcache', 'patch-transcache.sql' ),
- array( 'addField', 'interwiki', 'iw_trans', 'patch-interwiki-trans.sql' ),
+ array( 'addField', 'site_stats', 'ss_total_pages', 'patch-ss_total_articles.sql' ),
+ array( 'addTable', 'user_newtalk', 'patch-usernewtalk2.sql' ),
+ array( 'addTable', 'transcache', 'patch-transcache.sql' ),
+ array( 'addField', 'interwiki', 'iw_trans', 'patch-interwiki-trans.sql' ),
// 1.6
array( 'doWatchlistNull' ),
- array( 'addIndex', 'logging', 'times', 'patch-logging-times-index.sql' ),
- array( 'addField', 'ipblocks', 'ipb_range_start', 'patch-ipb_range_start.sql' ),
+ array( 'addIndex', 'logging', 'times', 'patch-logging-times-index.sql' ),
+ array( 'addField', 'ipblocks', 'ipb_range_start', 'patch-ipb_range_start.sql' ),
array( 'doPageRandomUpdate' ),
- array( 'addField', 'user', 'user_registration', 'patch-user_registration.sql' ),
+ array( 'addField', 'user', 'user_registration', 'patch-user_registration.sql' ),
array( 'doTemplatelinksUpdate' ),
- array( 'addTable', 'externallinks', 'patch-externallinks.sql' ),
- array( 'addTable', 'job', 'patch-job.sql' ),
- array( 'addField', 'site_stats', 'ss_images', 'patch-ss_images.sql' ),
- array( 'addTable', 'langlinks', 'patch-langlinks.sql' ),
- array( 'addTable', 'querycache_info', 'patch-querycacheinfo.sql' ),
- array( 'addTable', 'filearchive', 'patch-filearchive.sql' ),
- array( 'addField', 'ipblocks', 'ipb_anon_only', 'patch-ipb_anon_only.sql' ),
- array( 'addIndex', 'recentchanges', 'rc_ns_usertext', 'patch-recentchanges-utindex.sql' ),
- array( 'addIndex', 'recentchanges', 'rc_user_text', 'patch-rc_user_text-index.sql' ),
+ array( 'addTable', 'externallinks', 'patch-externallinks.sql' ),
+ array( 'addTable', 'job', 'patch-job.sql' ),
+ array( 'addField', 'site_stats', 'ss_images', 'patch-ss_images.sql' ),
+ array( 'addTable', 'langlinks', 'patch-langlinks.sql' ),
+ array( 'addTable', 'querycache_info', 'patch-querycacheinfo.sql' ),
+ array( 'addTable', 'filearchive', 'patch-filearchive.sql' ),
+ array( 'addField', 'ipblocks', 'ipb_anon_only', 'patch-ipb_anon_only.sql' ),
+ array( 'addIndex', 'recentchanges', 'rc_ns_usertext', 'patch-recentchanges-utindex.sql' ),
+ array( 'addIndex', 'recentchanges', 'rc_user_text', 'patch-rc_user_text-index.sql' ),
// 1.9
- array( 'addField', 'user', 'user_newpass_time', 'patch-user_newpass_time.sql' ),
- array( 'addTable', 'redirect', 'patch-redirect.sql' ),
- array( 'addTable', 'querycachetwo', 'patch-querycachetwo.sql' ),
- array( 'addField', 'ipblocks', 'ipb_enable_autoblock', 'patch-ipb_optional_autoblock.sql' ),
+ array( 'addField', 'user', 'user_newpass_time', 'patch-user_newpass_time.sql' ),
+ array( 'addTable', 'redirect', 'patch-redirect.sql' ),
+ array( 'addTable', 'querycachetwo', 'patch-querycachetwo.sql' ),
+ array( 'addField', 'ipblocks', 'ipb_enable_autoblock', 'patch-ipb_optional_autoblock.sql' ),
array( 'doBacklinkingIndicesUpdate' ),
- array( 'addField', 'recentchanges', 'rc_old_len', 'patch-rc_len.sql' ),
- array( 'addField', 'user', 'user_editcount', 'patch-user_editcount.sql' ),
+ array( 'addField', 'recentchanges', 'rc_old_len', 'patch-rc_len.sql' ),
+ array( 'addField', 'user', 'user_editcount', 'patch-user_editcount.sql' ),
// 1.10
array( 'doRestrictionsUpdate' ),
- array( 'addField', 'logging', 'log_id', 'patch-log_id.sql' ),
- array( 'addField', 'revision', 'rev_parent_id', 'patch-rev_parent_id.sql' ),
- array( 'addField', 'page_restrictions', 'pr_id', 'patch-page_restrictions_sortkey.sql' ),
- array( 'addField', 'revision', 'rev_len', 'patch-rev_len.sql' ),
- array( 'addField', 'recentchanges', 'rc_deleted', 'patch-rc_deleted.sql' ),
- array( 'addField', 'logging', 'log_deleted', 'patch-log_deleted.sql' ),
- array( 'addField', 'archive', 'ar_deleted', 'patch-ar_deleted.sql' ),
- array( 'addField', 'ipblocks', 'ipb_deleted', 'patch-ipb_deleted.sql' ),
- array( 'addField', 'filearchive', 'fa_deleted', 'patch-fa_deleted.sql' ),
- array( 'addField', 'archive', 'ar_len', 'patch-ar_len.sql' ),
+ array( 'addField', 'logging', 'log_id', 'patch-log_id.sql' ),
+ array( 'addField', 'revision', 'rev_parent_id', 'patch-rev_parent_id.sql' ),
+ array( 'addField', 'page_restrictions', 'pr_id', 'patch-page_restrictions_sortkey.sql' ),
+ array( 'addField', 'revision', 'rev_len', 'patch-rev_len.sql' ),
+ array( 'addField', 'recentchanges', 'rc_deleted', 'patch-rc_deleted.sql' ),
+ array( 'addField', 'logging', 'log_deleted', 'patch-log_deleted.sql' ),
+ array( 'addField', 'archive', 'ar_deleted', 'patch-ar_deleted.sql' ),
+ array( 'addField', 'ipblocks', 'ipb_deleted', 'patch-ipb_deleted.sql' ),
+ array( 'addField', 'filearchive', 'fa_deleted', 'patch-fa_deleted.sql' ),
+ array( 'addField', 'archive', 'ar_len', 'patch-ar_len.sql' ),
// 1.11
- array( 'addField', 'ipblocks', 'ipb_block_email', 'patch-ipb_emailban.sql' ),
+ array( 'addField', 'ipblocks', 'ipb_block_email', 'patch-ipb_emailban.sql' ),
array( 'doCategorylinksIndicesUpdate' ),
- array( 'addField', 'oldimage', 'oi_metadata', 'patch-oi_metadata.sql' ),
- array( 'addIndex', 'archive', 'usertext_timestamp', 'patch-archive-user-index.sql' ),
- array( 'addIndex', 'image', 'img_usertext_timestamp', 'patch-image-user-index.sql' ),
- array( 'addIndex', 'oldimage', 'oi_usertext_timestamp', 'patch-oldimage-user-index.sql' ),
- array( 'addField', 'archive', 'ar_page_id', 'patch-archive-page_id.sql' ),
- array( 'addField', 'image', 'img_sha1', 'patch-img_sha1.sql' ),
+ array( 'addField', 'oldimage', 'oi_metadata', 'patch-oi_metadata.sql' ),
+ array( 'addIndex', 'archive', 'usertext_timestamp', 'patch-archive-user-index.sql' ),
+ array( 'addIndex', 'image', 'img_usertext_timestamp', 'patch-image-user-index.sql' ),
+ array( 'addIndex', 'oldimage', 'oi_usertext_timestamp', 'patch-oldimage-user-index.sql' ),
+ array( 'addField', 'archive', 'ar_page_id', 'patch-archive-page_id.sql' ),
+ array( 'addField', 'image', 'img_sha1', 'patch-img_sha1.sql' ),
// 1.12
- array( 'addTable', 'protected_titles', 'patch-protected_titles.sql' ),
+ array( 'addTable', 'protected_titles', 'patch-protected_titles.sql' ),
// 1.13
- array( 'addField', 'ipblocks', 'ipb_by_text', 'patch-ipb_by_text.sql' ),
- array( 'addTable', 'page_props', 'patch-page_props.sql' ),
- array( 'addTable', 'updatelog', 'patch-updatelog.sql' ),
- array( 'addTable', 'category', 'patch-category.sql' ),
+ array( 'addField', 'ipblocks', 'ipb_by_text', 'patch-ipb_by_text.sql' ),
+ array( 'addTable', 'page_props', 'patch-page_props.sql' ),
+ array( 'addTable', 'updatelog', 'patch-updatelog.sql' ),
+ array( 'addTable', 'category', 'patch-category.sql' ),
array( 'doCategoryPopulation' ),
- array( 'addField', 'archive', 'ar_parent_id', 'patch-ar_parent_id.sql' ),
- array( 'addField', 'user_newtalk', 'user_last_timestamp', 'patch-user_last_timestamp.sql' ),
+ array( 'addField', 'archive', 'ar_parent_id', 'patch-ar_parent_id.sql' ),
+ array( 'addField', 'user_newtalk', 'user_last_timestamp', 'patch-user_last_timestamp.sql' ),
array( 'doPopulateParentId' ),
- array( 'checkBin', 'protected_titles', 'pt_title', 'patch-pt_title-encoding.sql', ),
+ array( 'checkBin', 'protected_titles', 'pt_title', 'patch-pt_title-encoding.sql', ),
array( 'doMaybeProfilingMemoryUpdate' ),
array( 'doFilearchiveIndicesUpdate' ),
// 1.14
- array( 'addField', 'site_stats', 'ss_active_users', 'patch-ss_active_users.sql' ),
+ array( 'addField', 'site_stats', 'ss_active_users', 'patch-ss_active_users.sql' ),
array( 'doActiveUsersInit' ),
- array( 'addField', 'ipblocks', 'ipb_allow_usertalk', 'patch-ipb_allow_usertalk.sql' ),
+ array( 'addField', 'ipblocks', 'ipb_allow_usertalk', 'patch-ipb_allow_usertalk.sql' ),
// 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', 'change_tag', 'patch-change_tag.sql' ),
+ array( 'addTable', 'tag_summary', 'patch-tag_summary.sql' ),
+ array( 'addTable', 'valid_tag', 'patch-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( 'doLogUsertextPopulation' ), # listed separately from the previous update because 1.16 was released without this update
+ 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' ),
+ # listed separately from the previous update because 1.16 was released without this update
+ array( 'doLogUsertextPopulation' ),
array( 'doLogSearchPopulation' ),
- 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' ),
+ array( 'addTable', 'l10n_cache', 'patch-l10n_cache.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' ),
array( 'doUpdateTranscacheField' ),
- array( 'renameEuWikiId' ),
array( 'doUpdateMimeMinorField' ),
// 1.17
- array( 'addTable', 'iwlinks', 'patch-iwlinks.sql' ),
- array( 'addIndex', 'iwlinks', 'iwl_prefix_title_from', 'patch-rename-iwl_prefix.sql' ),
- array( 'addField', 'updatelog', 'ul_value', 'patch-ul_value.sql' ),
- array( 'addField', 'interwiki', 'iw_api', 'patch-iw_api_and_wikiid.sql' ),
- array( 'dropIndex', 'iwlinks', 'iwl_prefix', 'patch-kill-iwl_prefix.sql' ),
- array( 'dropIndex', 'iwlinks', 'iwl_prefix_from_title', 'patch-kill-iwl_pft.sql' ),
- array( 'addField', 'categorylinks', 'cl_collation', 'patch-categorylinks-better-collation.sql' ),
+ array( 'addTable', 'iwlinks', 'patch-iwlinks.sql' ),
+ array( 'addIndex', 'iwlinks', 'iwl_prefix_title_from', 'patch-rename-iwl_prefix.sql' ),
+ array( 'addField', 'updatelog', 'ul_value', 'patch-ul_value.sql' ),
+ array( 'addField', 'interwiki', 'iw_api', 'patch-iw_api_and_wikiid.sql' ),
+ array( 'dropIndex', 'iwlinks', 'iwl_prefix', 'patch-kill-iwl_prefix.sql' ),
+ array( 'addField', 'categorylinks', 'cl_collation', 'patch-categorylinks-better-collation.sql' ),
array( 'doClFieldsUpdate' ),
array( 'doCollationUpdate' ),
- array( 'addTable', 'msg_resource', 'patch-msg_resource.sql' ),
- array( 'addTable', 'module_deps', 'patch-module_deps.sql' ),
- array( 'dropIndex', 'archive', 'ar_page_revid', 'patch-archive_kill_ar_page_revid.sql' ),
- array( 'addIndex', 'archive', 'ar_revid', 'patch-archive_ar_revid.sql' ),
+ array( 'addTable', 'msg_resource', 'patch-msg_resource.sql' ),
+ array( 'addTable', 'module_deps', 'patch-module_deps.sql' ),
+ array( 'dropIndex', 'archive', 'ar_page_revid', 'patch-archive_kill_ar_page_revid.sql' ),
+ array( 'addIndex', 'archive', 'ar_revid', 'patch-archive_ar_revid.sql' ),
array( 'doLangLinksLengthUpdate' ),
// 1.18
array( 'doUserNewTalkTimestampNotNull' ),
- array( 'addIndex', 'user', 'user_email', 'patch-user_email_index.sql' ),
+ 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( 'addTable', 'uploadstash', 'patch-uploadstash.sql' ),
+ array( 'addTable', 'user_former_groups', 'patch-user_former_groups.sql' ),
// 1.19
- array( 'addIndex', 'logging', 'type_action', 'patch-logging-type-action-index.sql'),
- array( 'addField', 'revision', 'rev_sha1', 'patch-rev_sha1.sql' ),
+ array( 'addIndex', 'logging', 'type_action', 'patch-logging-type-action-index.sql' ),
+ array( 'addField', 'revision', 'rev_sha1', 'patch-rev_sha1.sql' ),
array( 'doMigrateUserOptions' ),
- array( 'dropField', 'user', 'user_options', 'patch-drop-user_options.sql' ),
- array( 'addField', 'archive', 'ar_sha1', 'patch-ar_sha1.sql' ),
- array( 'addIndex', 'page', 'page_redirect_namespace_len', 'patch-page_redirect_namespace_len.sql' ),
- array( 'addField', 'uploadstash', 'us_chunk_inx', 'patch-uploadstash_chunk.sql' ),
- array( 'addfield', 'job', 'job_timestamp', 'patch-jobs-add-timestamp.sql' ),
+ array( 'dropField', 'user', 'user_options', 'patch-drop-user_options.sql' ),
+ array( 'addField', 'archive', 'ar_sha1', 'patch-ar_sha1.sql' ),
+ array( 'addIndex', 'page', 'page_redirect_namespace_len',
+ 'patch-page_redirect_namespace_len.sql' ),
+ array( 'addField', 'uploadstash', 'us_chunk_inx', 'patch-uploadstash_chunk.sql' ),
+ array( 'addfield', 'job', 'job_timestamp', 'patch-jobs-add-timestamp.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' ),
+ 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( '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( '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( '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( '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' ),
+
+ // 1.22
+ array( 'doIwlinksIndexNonUnique' ),
+ array( 'addIndex', 'iwlinks', 'iwl_prefix_from_title',
+ 'patch-iwlinks-from-title-index.sql' ),
+ array( 'addField', 'archive', 'ar_id', 'patch-archive-ar_id.sql' ),
+ array( 'addField', 'externallinks', 'el_id', 'patch-externallinks-el_id.sql' ),
);
}
@@ -250,11 +255,8 @@ class MysqlUpdater extends DatabaseUpdater {
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 ) );
-
- if ( in_array( 'binary', $flags ) ) {
+ $fieldInfo = $this->db->fieldInfo( $table, $field );
+ if ( $fieldInfo->isBinary() ) {
$this->output( "...$table table has correct $field encoding.\n" );
} else {
$this->applyPatch( $patchFile, false, "Fixing $field encoding on $table table" );
@@ -279,11 +281,13 @@ class MysqlUpdater extends DatabaseUpdater {
foreach ( $info as $row ) {
if ( $row->Column_name == $field ) {
$this->output( "...index $index on table $table includes field $field.\n" );
+
return true;
}
}
}
$this->output( "...index $index on table $table has no field $field; added.\n" );
+
return false;
}
@@ -299,11 +303,16 @@ class MysqlUpdater extends DatabaseUpdater {
if ( $this->db->tableExists( "interwiki", __METHOD__ ) ) {
$this->output( "...already have interwiki table\n" );
+
return;
}
$this->applyPatch( 'patch-interwiki.sql', false, 'Creating interwiki table' );
- $this->applyPatch( "$IP/maintenance/interwiki.sql", true, 'Adding default interwiki definitions' );
+ $this->applyPatch(
+ "$IP/maintenance/interwiki.sql",
+ true,
+ 'Adding default interwiki definitions'
+ );
}
/**
@@ -316,6 +325,7 @@ class MysqlUpdater extends DatabaseUpdater {
}
if ( $meta->isMultipleKey() ) {
$this->output( "...indexes seem up to 20031107 standards.\n" );
+
return;
}
@@ -331,10 +341,17 @@ class MysqlUpdater extends DatabaseUpdater {
$info = $this->db->fieldInfo( 'imagelinks', 'il_from' );
if ( !$info || $info->type() !== 'string' ) {
$this->output( "...il_from OK\n" );
+
return;
}
- if( $this->applyPatch( 'patch-fix-il_from.sql', false, "Fixing ancient broken imagelinks table." ) ) {
+ $applied = $this->applyPatch(
+ 'patch-fix-il_from.sql',
+ false,
+ 'Fixing ancient broken imagelinks table.'
+ );
+
+ if ( $applied ) {
$this->output( "NOTE: you will have to run maintenance/refreshLinks.php after this." );
}
}
@@ -344,9 +361,15 @@ class MysqlUpdater extends DatabaseUpdater {
*/
function doWatchlistUpdate() {
$talk = $this->db->selectField( 'watchlist', 'count(*)', 'wl_namespace & 1', __METHOD__ );
- $nontalk = $this->db->selectField( 'watchlist', 'count(*)', 'NOT (wl_namespace & 1)', __METHOD__ );
+ $nontalk = $this->db->selectField(
+ 'watchlist',
+ 'count(*)',
+ 'NOT (wl_namespace & 1)',
+ __METHOD__
+ );
if ( $talk == $nontalk ) {
$this->output( "...watchlist talk page rows already present.\n" );
+
return;
}
@@ -364,6 +387,7 @@ class MysqlUpdater extends DatabaseUpdater {
function doSchemaRestructuring() {
if ( $this->db->tableExists( 'page', __METHOD__ ) ) {
$this->output( "...page table already exists.\n" );
+
return;
}
@@ -371,10 +395,21 @@ class MysqlUpdater extends DatabaseUpdater {
$this->output( wfTimestamp( TS_DB ) );
$this->output( "......checking for duplicate entries.\n" );
- list ( $cur, $old, $page, $revision, $text ) = $this->db->tableNamesN( 'cur', 'old', 'page', 'revision', 'text' );
+ list( $cur, $old, $page, $revision, $text ) = $this->db->tableNamesN(
+ 'cur',
+ 'old',
+ 'page',
+ 'revision',
+ 'text'
+ );
- $rows = $this->db->query( "SELECT cur_title, cur_namespace, COUNT(cur_namespace) AS c
- FROM $cur GROUP BY cur_title, cur_namespace HAVING c>1", __METHOD__ );
+ $rows = $this->db->query( "
+ SELECT cur_title, cur_namespace, COUNT(cur_namespace) AS c
+ FROM $cur
+ GROUP BY cur_title, cur_namespace
+ HAVING c>1",
+ __METHOD__
+ );
if ( $rows->numRows() > 0 ) {
$this->output( wfTimestamp( TS_DB ) );
@@ -382,11 +417,16 @@ class MysqlUpdater extends DatabaseUpdater {
$this->output( sprintf( "<b> %-60s %3s %5s</b>\n", 'Title', 'NS', 'Count' ) );
$duplicate = array();
foreach ( $rows as $row ) {
- if ( ! isset( $duplicate[$row->cur_namespace] ) ) {
+ if ( !isset( $duplicate[$row->cur_namespace] ) ) {
$duplicate[$row->cur_namespace] = array();
}
+
$duplicate[$row->cur_namespace][] = $row->cur_title;
- $this->output( sprintf( " %-60s %3s %5s\n", $row->cur_title, $row->cur_namespace, $row->c ) );
+ $this->output( sprintf(
+ " %-60s %3s %5s\n",
+ $row->cur_title, $row->cur_namespace,
+ $row->c
+ ) );
}
$sql = "SELECT cur_title, cur_namespace, cur_id, cur_timestamp FROM $cur WHERE ";
$firstCond = true;
@@ -471,7 +511,10 @@ class MysqlUpdater extends DatabaseUpdater {
$this->output( wfTimestamp( TS_DB ) );
$this->output( "......Locking tables.\n" );
- $this->db->query( "LOCK TABLES $page WRITE, $revision WRITE, $old WRITE, $cur WRITE", __METHOD__ );
+ $this->db->query(
+ "LOCK TABLES $page WRITE, $revision WRITE, $old WRITE, $cur WRITE",
+ __METHOD__
+ );
$maxold = intval( $this->db->selectField( 'old', 'max(old_id)', '', __METHOD__ ) );
$this->output( wfTimestamp( TS_DB ) );
@@ -492,27 +535,38 @@ class MysqlUpdater extends DatabaseUpdater {
$cur_text = 'cur_text';
$cur_flags = "''";
}
- $this->db->query( "INSERT INTO $old (old_namespace, old_title, old_text, old_comment, old_user, old_user_text,
- old_timestamp, old_minor_edit, old_flags)
- SELECT cur_namespace, cur_title, $cur_text, cur_comment, cur_user, cur_user_text, cur_timestamp, cur_minor_edit, $cur_flags
- FROM $cur", __METHOD__ );
+ $this->db->query(
+ "INSERT INTO $old (old_namespace, old_title, old_text, old_comment, old_user,
+ old_user_text, old_timestamp, old_minor_edit, old_flags)
+ SELECT cur_namespace, cur_title, $cur_text, cur_comment, cur_user, cur_user_text,
+ cur_timestamp, cur_minor_edit, $cur_flags
+ FROM $cur",
+ __METHOD__
+ );
$this->output( wfTimestamp( TS_DB ) );
$this->output( "......Setting up revision table.\n" );
- $this->db->query( "INSERT INTO $revision (rev_id, rev_page, rev_comment, rev_user, rev_user_text, rev_timestamp,
- rev_minor_edit)
+ $this->db->query(
+ "INSERT INTO $revision (rev_id, rev_page, rev_comment, rev_user,
+ rev_user_text, rev_timestamp, rev_minor_edit)
SELECT old_id, cur_id, old_comment, old_user, old_user_text,
old_timestamp, old_minor_edit
- FROM $old,$cur WHERE old_namespace=cur_namespace AND old_title=cur_title", __METHOD__ );
+ FROM $old,$cur WHERE old_namespace=cur_namespace AND old_title=cur_title",
+ __METHOD__
+ );
$this->output( wfTimestamp( TS_DB ) );
$this->output( "......Setting up page table.\n" );
- $this->db->query( "INSERT INTO $page (page_id, page_namespace, page_title, page_restrictions, page_counter,
- page_is_redirect, page_is_new, page_random, page_touched, page_latest, page_len)
- SELECT cur_id, cur_namespace, cur_title, cur_restrictions, cur_counter, cur_is_redirect, cur_is_new,
- cur_random, cur_touched, rev_id, LENGTH(cur_text)
+ $this->db->query(
+ "INSERT INTO $page (page_id, page_namespace, page_title,
+ page_restrictions, page_counter, page_is_redirect, page_is_new, page_random,
+ page_touched, page_latest, page_len)
+ SELECT cur_id, cur_namespace, cur_title, cur_restrictions, cur_counter,
+ cur_is_redirect, cur_is_new, cur_random, cur_touched, rev_id, LENGTH(cur_text)
FROM $cur,$revision
- WHERE cur_id=rev_page AND rev_timestamp=cur_timestamp AND rev_id > {$maxold}", __METHOD__ );
+ WHERE cur_id=rev_page AND rev_timestamp=cur_timestamp AND rev_id > {$maxold}",
+ __METHOD__
+ );
$this->output( wfTimestamp( TS_DB ) );
$this->output( "......Unlocking tables.\n" );
@@ -528,12 +582,12 @@ class MysqlUpdater extends DatabaseUpdater {
protected function doNamespaceSize() {
$tables = array(
- 'page' => 'page',
- 'archive' => 'ar',
+ 'page' => 'page',
+ 'archive' => 'ar',
'recentchanges' => 'rc',
- 'watchlist' => 'wl',
- 'querycache' => 'qc',
- 'logging' => 'log',
+ 'watchlist' => 'wl',
+ 'querycache' => 'qc',
+ 'logging' => 'log',
);
foreach ( $tables as $table => $prefix ) {
$field = $prefix . '_namespace';
@@ -555,10 +609,15 @@ class MysqlUpdater extends DatabaseUpdater {
protected function doPagelinksUpdate() {
if ( $this->db->tableExists( 'pagelinks', __METHOD__ ) ) {
$this->output( "...already have pagelinks table.\n" );
+
return;
}
- $this->applyPatch( 'patch-pagelinks.sql', false, "Converting links and brokenlinks tables to pagelinks" );
+ $this->applyPatch(
+ 'patch-pagelinks.sql',
+ false,
+ 'Converting links and brokenlinks tables to pagelinks'
+ );
global $wgContLang;
foreach ( MWNamespace::getCanonicalNamespaces() as $ns => $name ) {
@@ -592,6 +651,7 @@ class MysqlUpdater extends DatabaseUpdater {
$duper = new UserDupes( $this->db, array( $this, 'output' ) );
if ( $duper->hasUniqueIndex() ) {
$this->output( "...already have unique user_name index.\n" );
+
return;
}
@@ -611,19 +671,22 @@ class MysqlUpdater extends DatabaseUpdater {
if ( $info->type() == 'int' ) {
$oldug = $this->db->tableName( 'user_groups' );
$newug = $this->db->tableName( 'user_groups_bogus' );
- $this->output( "user_groups table exists but is in bogus intermediate format. Renaming to $newug... " );
+ $this->output( "user_groups table exists but is in bogus intermediate " .
+ "format. Renaming to $newug... " );
$this->db->query( "ALTER TABLE $oldug RENAME TO $newug", __METHOD__ );
$this->output( "done.\n" );
$this->applyPatch( 'patch-user_groups.sql', false, "Re-adding fresh user_groups table" );
$this->output( "***\n" );
- $this->output( "*** WARNING: You will need to manually fix up user permissions in the user_groups\n" );
+ $this->output( "*** WARNING: You will need to manually fix up user " .
+ "permissions in the user_groups\n" );
$this->output( "*** table. Old 1.5 alpha versions did some pretty funky stuff...\n" );
$this->output( "***\n" );
} else {
$this->output( "...user_groups table exists and is in current format.\n" );
}
+
return;
}
@@ -631,11 +694,16 @@ class MysqlUpdater extends DatabaseUpdater {
if ( !$this->db->tableExists( 'user_rights', __METHOD__ ) ) {
if ( $this->db->fieldExists( 'user', 'user_rights', __METHOD__ ) ) {
- $this->db->applyPatch( 'patch-user_rights.sql', false, "Upgrading from a 1.3 or older database? Breaking out user_rights for conversion" );
+ $this->applyPatch(
+ 'patch-user_rights.sql',
+ false,
+ 'Upgrading from a 1.3 or older database? Breaking out user_rights for conversion'
+ );
} else {
$this->output( "*** WARNING: couldn't locate user_rights table or field for upgrade.\n" );
$this->output( "*** You may need to manually configure some sysops by manipulating\n" );
$this->output( "*** the user_groups table.\n" );
+
return;
}
}
@@ -654,7 +722,7 @@ class MysqlUpdater extends DatabaseUpdater {
foreach ( $groups as $group ) {
$this->db->insert( 'user_groups',
array(
- 'ug_user' => $row->ur_user,
+ 'ug_user' => $row->ur_user,
'ug_group' => $group ),
__METHOD__ );
}
@@ -673,10 +741,15 @@ class MysqlUpdater extends DatabaseUpdater {
}
if ( $info->isNullable() ) {
$this->output( "...wl_notificationtimestamp is already nullable.\n" );
+
return;
}
- $this->applyPatch( 'patch-watchlist-null.sql', false, "Making wl_notificationtimestamp nullable" );
+ $this->applyPatch(
+ 'patch-watchlist-null.sql',
+ false,
+ 'Making wl_notificationtimestamp nullable'
+ );
}
/**
@@ -689,7 +762,7 @@ class MysqlUpdater extends DatabaseUpdater {
$this->db->query( "UPDATE $page SET page_random = RAND() WHERE page_random = 0", __METHOD__ );
$rows = $this->db->affectedRows();
- if( $rows ) {
+ if ( $rows ) {
$this->output( "Set page_random to a random value on $rows rows where it was set to 0\n" );
} else {
$this->output( "...no page_random rows needed to be set\n" );
@@ -699,6 +772,7 @@ class MysqlUpdater extends DatabaseUpdater {
protected function doTemplatelinksUpdate() {
if ( $this->db->tableExists( 'templatelinks', __METHOD__ ) ) {
$this->output( "...templatelinks table already exists\n" );
+
return;
}
@@ -722,7 +796,6 @@ class MysqlUpdater extends DatabaseUpdater {
'tl_title' => $row->pl_title,
), __METHOD__
);
-
}
} else {
// Fast update
@@ -736,14 +809,15 @@ class MysqlUpdater extends DatabaseUpdater {
), __METHOD__
);
}
- $this->output( "Done. Please run maintenance/refreshLinks.php for a more thorough templatelinks update.\n" );
+ $this->output( "Done. Please run maintenance/refreshLinks.php for a more " .
+ "thorough templatelinks update.\n" );
}
protected function doBacklinkingIndicesUpdate() {
if ( !$this->indexHasField( 'pagelinks', 'pl_namespace', 'pl_from' ) ||
!$this->indexHasField( 'templatelinks', 'tl_namespace', 'tl_from' ) ||
- !$this->indexHasField( 'imagelinks', 'il_to', 'il_from' ) )
- {
+ !$this->indexHasField( 'imagelinks', 'il_to', 'il_from' )
+ ) {
$this->applyPatch( 'patch-backlinkindexes.sql', false, "Updating backlinking indices" );
}
}
@@ -756,11 +830,20 @@ class MysqlUpdater extends DatabaseUpdater {
protected function doRestrictionsUpdate() {
if ( $this->db->tableExists( 'page_restrictions', __METHOD__ ) ) {
$this->output( "...page_restrictions table already exists.\n" );
+
return;
}
- $this->applyPatch( 'patch-page_restrictions.sql', false, "Creating page_restrictions table (1/2)" );
- $this->applyPatch( 'patch-page_restrictions_sortkey.sql', false, "Creating page_restrictions table (2/2)" );
+ $this->applyPatch(
+ 'patch-page_restrictions.sql',
+ false,
+ 'Creating page_restrictions table (1/2)'
+ );
+ $this->applyPatch(
+ 'patch-page_restrictions_sortkey.sql',
+ false,
+ 'Creating page_restrictions table (2/2)'
+ );
$this->output( "done.\n" );
$this->output( "Migrating old restrictions to new table...\n" );
@@ -777,6 +860,7 @@ class MysqlUpdater extends DatabaseUpdater {
protected function doCategoryPopulation() {
if ( $this->updateRowExists( 'populate category' ) ) {
$this->output( "...category table already populated.\n" );
+
return;
}
@@ -810,7 +894,7 @@ class MysqlUpdater extends DatabaseUpdater {
return true;
}
- if ( $wgProfileToDatabase === true && ! $this->db->tableExists( 'profiling', __METHOD__ ) ) {
+ if ( $wgProfileToDatabase === true && !$this->db->tableExists( 'profiling', __METHOD__ ) ) {
$this->applyPatch( 'patch-profiling.sql', false, 'Add profiling table' );
}
}
@@ -824,9 +908,15 @@ class MysqlUpdater extends DatabaseUpdater {
return true;
} elseif ( $this->db->fieldExists( 'profiling', 'pf_memory', __METHOD__ ) ) {
$this->output( "...profiling table has pf_memory field.\n" );
+
return true;
}
- return $this->applyPatch( 'patch-profiling-memory.sql', false, "Adding pf_memory field to table profiling" );
+
+ return $this->applyPatch(
+ 'patch-profiling-memory.sql',
+ false,
+ 'Adding pf_memory field to table profiling'
+ );
}
protected function doFilearchiveIndicesUpdate() {
@@ -834,6 +924,7 @@ class MysqlUpdater extends DatabaseUpdater {
if ( !$info ) {
$this->applyPatch( 'patch-filearchive-user-index.sql', false, "Updating filearchive indices" );
}
+
return true;
}
@@ -841,41 +932,49 @@ class MysqlUpdater extends DatabaseUpdater {
$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 true;
}
if ( $this->skipSchema ) {
- $this->output( "...skipping schema change (making pl_namespace, tl_namespace and il_to indices UNIQUE).\n" );
- return false;
- }
-
- return $this->applyPatch( 'patch-pl-tl-il-unique.sql', false, "Making pl_namespace, tl_namespace and il_to indices UNIQUE" );
- }
+ $this->output( "...skipping schema change (making pl_namespace, tl_namespace " .
+ "and il_to indices UNIQUE).\n" );
- protected function renameEuWikiId() {
- if ( $this->db->fieldExists( 'external_user', 'eu_local_id', __METHOD__ ) ) {
- $this->output( "...eu_wiki_id already renamed to eu_local_id.\n" );
- return;
+ return false;
}
- $this->applyPatch( 'patch-eu_local_id.sql', false, "Renaming eu_wiki_id -> eu_local_id" );
+ return $this->applyPatch(
+ 'patch-pl-tl-il-unique.sql',
+ false,
+ 'Making pl_namespace, tl_namespace and il_to indices UNIQUE'
+ );
}
protected function doUpdateMimeMinorField() {
if ( $this->updateRowExists( 'mime_minor_length' ) ) {
$this->output( "...*_mime_minor fields are already long enough.\n" );
+
return;
}
- $this->applyPatch( 'patch-mime_minor_length.sql', false, "Altering all *_mime_minor fields to 100 bytes in size" );
+ $this->applyPatch(
+ 'patch-mime_minor_length.sql',
+ false,
+ 'Altering all *_mime_minor fields to 100 bytes in size'
+ );
}
protected function doClFieldsUpdate() {
if ( $this->updateRowExists( 'cl_fields_update' ) ) {
$this->output( "...categorylinks up-to-date.\n" );
+
return;
}
- $this->applyPatch( 'patch-categorylinks-better-collation2.sql', false, 'Updating categorylinks (again)' );
+ $this->applyPatch(
+ 'patch-categorylinks-better-collation2.sql',
+ false,
+ 'Updating categorylinks (again)'
+ );
}
protected function doLangLinksLengthUpdate() {
@@ -884,7 +983,11 @@ class MysqlUpdater extends DatabaseUpdater {
$row = $this->db->fetchObject( $res );
if ( $row && $row->Type == "varbinary(10)" ) {
- $this->applyPatch( 'patch-langlinks-ll_lang-20.sql', false, 'Updating length of ll_lang in langlinks' );
+ $this->applyPatch(
+ 'patch-langlinks-ll_lang-20.sql',
+ false,
+ 'Updating length of ll_lang in langlinks'
+ );
} else {
$this->output( "...ll_lang is up-to-date.\n" );
}
@@ -901,9 +1004,34 @@ class MysqlUpdater extends DatabaseUpdater {
}
if ( $info->isNullable() ) {
$this->output( "...user_last_timestamp is already nullable.\n" );
+
return;
}
- $this->applyPatch( 'patch-user-newtalk-timestamp-null.sql', false, "Making user_last_timestamp nullable" );
+ $this->applyPatch(
+ 'patch-user-newtalk-timestamp-null.sql',
+ false,
+ 'Making user_last_timestamp nullable'
+ );
+ }
+
+ protected function doIwlinksIndexNonUnique() {
+ $info = $this->db->indexInfo( 'iwlinks', 'iwl_prefix_title_from' );
+ if ( is_array( $info ) && $info[0]->Non_unique ) {
+ $this->output( "...iwl_prefix_title_from index is already non-UNIQUE.\n" );
+
+ return true;
+ }
+ if ( $this->skipSchema ) {
+ $this->output( "...skipping schema change (making iwl_prefix_title_from index non-UNIQUE).\n" );
+
+ return false;
+ }
+
+ return $this->applyPatch(
+ 'patch-iwl_prefix_title_from-non-unique.sql',
+ false,
+ 'Making iwl_prefix_title_from index non-UNIQUE'
+ );
}
}
diff --git a/includes/installer/OracleInstaller.php b/includes/installer/OracleInstaller.php
index e8538890..77575100 100644
--- a/includes/installer/OracleInstaller.php
+++ b/includes/installer/OracleInstaller.php
@@ -59,35 +59,51 @@ class OracleInstaller extends DatabaseInstaller {
if ( $this->getVar( 'wgDBserver' ) == 'localhost' ) {
$this->parent->setVar( 'wgDBserver', '' );
}
- return
- $this->getTextBox( 'wgDBserver', 'config-db-host-oracle', array(), $this->parent->getHelpBox( 'config-db-host-oracle-help' ) ) .
+
+ return $this->getTextBox(
+ 'wgDBserver',
+ 'config-db-host-oracle',
+ array(),
+ $this->parent->getHelpBox( 'config-db-host-oracle-help' )
+ ) .
Html::openElement( 'fieldset' ) .
Html::element( 'legend', array(), wfMessage( 'config-db-wiki-settings' )->text() ) .
$this->getTextBox( 'wgDBprefix', 'config-db-prefix' ) .
$this->getTextBox( '_OracleDefTS', 'config-oracle-def-ts' ) .
- $this->getTextBox( '_OracleTempTS', 'config-oracle-temp-ts', array(), $this->parent->getHelpBox( 'config-db-oracle-help' ) ) .
+ $this->getTextBox(
+ '_OracleTempTS',
+ 'config-oracle-temp-ts',
+ array(),
+ $this->parent->getHelpBox( 'config-db-oracle-help' )
+ ) .
Html::closeElement( 'fieldset' ) .
- $this->parent->getWarningBox( wfMessage( 'config-db-account-oracle-warn' )->text() ).
- $this->getInstallUserBox().
+ $this->parent->getWarningBox( wfMessage( 'config-db-account-oracle-warn' )->text() ) .
+ $this->getInstallUserBox() .
$this->getWebUserBox();
}
public function submitInstallUserBox() {
parent::submitInstallUserBox();
$this->parent->setVar( '_InstallDBname', $this->getVar( '_InstallUser' ) );
+
return Status::newGood();
}
public function submitConnectForm() {
// Get variables from the request
- $newValues = $this->setVarsFromRequest( array( 'wgDBserver', 'wgDBprefix', 'wgDBuser', 'wgDBpassword' ) );
+ $newValues = $this->setVarsFromRequest(
+ 'wgDBserver',
+ 'wgDBprefix',
+ 'wgDBuser',
+ 'wgDBpassword'
+ );
$this->parent->setVar( 'wgDBname', $this->getVar( 'wgDBuser' ) );
// Validate them
$status = Status::newGood();
if ( !strlen( $newValues['wgDBserver'] ) ) {
$status->fatal( 'config-missing-db-server-oracle' );
- } elseif ( !preg_match( '/^[a-zA-Z0-9_\.]+$/', $newValues['wgDBserver'] ) ) {
+ } elseif ( !self::checkConnectStringFormat( $newValues['wgDBserver'] ) ) {
$status->fatal( 'config-invalid-db-server-oracle', $newValues['wgDBserver'] );
}
if ( !preg_match( '/^[a-zA-Z0-9_]*$/', $newValues['wgDBprefix'] ) ) {
@@ -163,6 +179,7 @@ class OracleInstaller extends DatabaseInstaller {
$this->connError = $e->db->lastErrno();
$status->fatal( 'config-connection-error', $e->getMessage() );
}
+
return $status;
}
@@ -182,6 +199,7 @@ class OracleInstaller extends DatabaseInstaller {
$this->connError = $e->db->lastErrno();
$status->fatal( 'config-connection-error', $e->getMessage() );
}
+
return $status;
}
@@ -190,6 +208,7 @@ class OracleInstaller extends DatabaseInstaller {
$this->parent->setVar( 'wgDBname', $this->getVar( 'wgDBuser' ) );
$retVal = parent::needsUpgrade();
$this->parent->setVar( 'wgDBname', $tempDBname );
+
return $retVal;
}
@@ -204,6 +223,7 @@ class OracleInstaller extends DatabaseInstaller {
public function setupDatabase() {
$status = Status::newGood();
+
return $status;
}
@@ -286,15 +306,38 @@ class OracleInstaller extends DatabaseInstaller {
foreach ( $varNames as $name ) {
$vars[$name] = $this->getVar( $name );
}
+
return $vars;
}
public function getLocalSettings() {
$prefix = $this->getVar( 'wgDBprefix' );
- return
-"# Oracle specific settings
+
+ return "# Oracle specific settings
\$wgDBprefix = \"{$prefix}\";
";
}
+ /**
+ * Function checks the format of Oracle connect string
+ * The actual validity of the string is checked by attempting to connect
+ *
+ * Regex should be able to validate all connect string formats
+ * [//](host|tns_name)[:port][/service_name][:POOLED]
+ * http://www.orafaq.com/wiki/EZCONNECT
+ *
+ * @since 1.22
+ *
+ * @param string $connect_string
+ *
+ * @return bool Whether the connection string is valid.
+ */
+ public static function checkConnectStringFormat( $connect_string ) {
+ // @@codingStandardsIgnoreStart Long lines with regular expressions.
+ // @todo Very long regular expression. Make more readable?
+ $isValid = preg_match( '/^[[:alpha:]][\w\-]*(?:\.[[:alpha:]][\w\-]*){0,2}$/', $connect_string ); // TNS name
+ $isValid |= preg_match( '/^(?:\/\/)?[\w\-\.]+(?::[\d]+)?(?:\/(?:[\w\-\.]+(?::(pooled|dedicated|shared))?)?(?:\/[\w\-\.]+)?)?$/', $connect_string ); // EZConnect
+ // @@codingStandardsIgnoreEnd
+ return (bool)$isValid;
+ }
}
diff --git a/includes/installer/OracleUpdater.php b/includes/installer/OracleUpdater.php
index 90b4c877..ec91e57b 100644
--- a/includes/installer/OracleUpdater.php
+++ b/includes/installer/OracleUpdater.php
@@ -38,8 +38,6 @@ class OracleUpdater extends DatabaseUpdater {
protected function getCoreUpdateList() {
return array(
- array( 'disableContentHandlerUseDB' ),
-
// 1.17
array( 'doNamespaceDefaults' ),
array( 'doFKRenameDeferr' ),
@@ -50,13 +48,13 @@ class OracleUpdater extends DatabaseUpdater {
array( 'addTable', 'user_former_groups', 'patch-user_former_groups.sql' ),
//1.18
- array( 'addIndex', 'user', 'i02', 'patch-user_email_index.sql' ),
+ array( 'addIndex', 'user', 'i02', 'patch-user_email_index.sql' ),
array( 'modifyField', 'user_properties', 'up_property', 'patch-up_property.sql' ),
array( 'addTable', 'uploadstash', 'patch-uploadstash.sql' ),
array( 'doRecentchangesFK2Cascade' ),
//1.19
- array( 'addIndex', 'logging', 'i05', 'patch-logging_type_action_index.sql'),
+ array( 'addIndex', 'logging', 'i05', 'patch-logging_type_action_index.sql' ),
array( 'addField', 'revision', 'rev_sha1', 'patch-rev_sha1_field.sql' ),
array( 'addField', 'archive', 'ar_sha1', 'patch-ar_sha1_field.sql' ),
array( 'doRemoveNotNullEmptyDefaults2' ),
@@ -72,22 +70,25 @@ class OracleUpdater extends DatabaseUpdater {
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( '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', 'archive', 'ar_id', 'patch-archive-ar_id.sql' ),
+ array( 'addField', 'externallinks', 'el_id', 'patch-externallinks-el_id.sql' ),
+ array( 'addField', 'page', 'page_content_model', 'patch-page-page_content_model.sql' ),
+ 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( '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' ),
+ array( 'modifyField', 'user_former_groups', 'ufg_group',
+ 'patch-ufg_group-length-increase-255.sql' ),
// KEEP THIS AT THE BOTTOM!!
array( 'doRebuildDuplicateFunction' ),
@@ -106,14 +107,22 @@ class OracleUpdater extends DatabaseUpdater {
return;
}
- $this->applyPatch( 'patch_namespace_defaults.sql', false, "Altering namespace fields with default value" );
+ $this->applyPatch(
+ 'patch_namespace_defaults.sql',
+ false,
+ 'Altering namespace fields with default value'
+ );
}
/**
* Uniform FK names + deferrable state
*/
protected function doFKRenameDeferr() {
- $meta = $this->db->query( 'SELECT COUNT(*) cnt FROM user_constraints WHERE constraint_type = \'R\' AND deferrable = \'DEFERRABLE\'' );
+ $meta = $this->db->query( '
+ SELECT COUNT(*) cnt
+ FROM user_constraints
+ WHERE constraint_type = \'R\' AND deferrable = \'DEFERRABLE\''
+ );
$row = $meta->fetchRow();
if ( $row && $row['cnt'] > 0 ) {
return;
@@ -171,7 +180,11 @@ class OracleUpdater extends DatabaseUpdater {
if ( $meta->isNullable() ) {
return;
}
- $this->applyPatch( 'patch_remove_not_null_empty_defs.sql', false, "Removing not null empty constraints" );
+ $this->applyPatch(
+ 'patch_remove_not_null_empty_defs.sql',
+ false,
+ 'Removing not null empty constraints'
+ );
}
protected function doRemoveNotNullEmptyDefaults2() {
@@ -179,7 +192,11 @@ class OracleUpdater extends DatabaseUpdater {
if ( $meta->isNullable() ) {
return;
}
- $this->applyPatch( 'patch_remove_not_null_empty_defs2.sql', false, "Removing not null empty constraints" );
+ $this->applyPatch(
+ 'patch_remove_not_null_empty_defs2.sql',
+ false,
+ 'Removing not null empty constraints'
+ );
}
/**
@@ -187,7 +204,12 @@ class OracleUpdater extends DatabaseUpdater {
* cascading taken in account in the deleting function
*/
protected function doRecentchangesFK2Cascade() {
- $meta = $this->db->query( 'SELECT 1 FROM all_constraints WHERE owner = \''.strtoupper($this->db->getDBname()).'\' AND constraint_name = \''.$this->db->tablePrefix().'RECENTCHANGES_FK2\' AND delete_rule = \'CASCADE\'' );
+ $meta = $this->db->query( 'SELECT 1 FROM all_constraints WHERE owner = \'' .
+ strtoupper( $this->db->getDBname() ) .
+ '\' AND constraint_name = \'' .
+ $this->db->tablePrefix() .
+ 'RECENTCHANGES_FK2\' AND delete_rule = \'CASCADE\''
+ );
$row = $meta->fetchRow();
if ( $row ) {
return;
@@ -211,6 +233,7 @@ class OracleUpdater extends DatabaseUpdater {
$row = $meta->fetchRow();
if ( $row['column_name'] == 'PR_ID' ) {
$this->output( "seems to be up to date.\n" );
+
return;
}
@@ -243,8 +266,7 @@ class OracleUpdater extends DatabaseUpdater {
# 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( '/*Q*/'.$this->db->tableName( 'objectcache' ), '*', __METHOD__ );
+ $this->db->delete( '/*Q*/' . $this->db->tableName( 'objectcache' ), '*', __METHOD__ );
$this->output( "done.\n" );
}
-
}
diff --git a/includes/installer/PhpBugTests.php b/includes/installer/PhpBugTests.php
index 773debe0..54712644 100644
--- a/includes/installer/PhpBugTests.php
+++ b/includes/installer/PhpBugTests.php
@@ -30,6 +30,7 @@
class PhpXmlBugTester {
private $parsedData = '';
public $ok = false;
+
public function __construct() {
$charData = '<b>c</b>';
$xml = '<a>' . htmlspecialchars( $charData ) . '</a>';
@@ -39,6 +40,7 @@ class PhpXmlBugTester {
$parsedOk = xml_parse( $parser, $xml, true );
$this->ok = $parsedOk && ( $this->parsedData == $charData );
}
+
public function chardata( $parser, $data ) {
$this->parsedData .= $data;
}
diff --git a/includes/installer/PostgresInstaller.php b/includes/installer/PostgresInstaller.php
index 4e5ae8cf..2cf41564 100644
--- a/includes/installer/PostgresInstaller.php
+++ b/includes/installer/PostgresInstaller.php
@@ -42,8 +42,8 @@ class PostgresInstaller extends DatabaseInstaller {
'_InstallUser' => 'postgres',
);
- var $minimumVersion = '8.3';
- var $maxRoleSearchDepth = 5;
+ public $minimumVersion = '8.3';
+ public $maxRoleSearchDepth = 5;
protected $pgConns = array();
@@ -56,13 +56,27 @@ class PostgresInstaller extends DatabaseInstaller {
}
function getConnectForm() {
- return
- $this->getTextBox( 'wgDBserver', 'config-db-host', array(), $this->parent->getHelpBox( 'config-db-host-help' ) ) .
+ return $this->getTextBox(
+ 'wgDBserver',
+ 'config-db-host',
+ array(),
+ $this->parent->getHelpBox( 'config-db-host-help' )
+ ) .
$this->getTextBox( 'wgDBport', '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' ) ) .
+ $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();
}
@@ -108,6 +122,7 @@ class PostgresInstaller extends DatabaseInstaller {
$this->setVar( 'wgDBuser', $this->getVar( '_InstallUser' ) );
$this->setVar( 'wgDBpassword', $this->getVar( '_InstallPassword' ) );
+
return Status::newGood();
}
@@ -116,6 +131,7 @@ class PostgresInstaller extends DatabaseInstaller {
if ( $status->isOK() ) {
$this->db = $status->value;
}
+
return $status;
}
@@ -137,11 +153,13 @@ class PostgresInstaller extends DatabaseInstaller {
$this->getVar( 'wgDBserver' ),
$user,
$password,
- $dbName);
+ $dbName
+ );
$status->value = $db;
} catch ( DBConnectionError $e ) {
$status->fatal( 'config-connection-error', $e->getMessage() );
}
+
return $status;
}
@@ -165,6 +183,7 @@ class PostgresInstaller extends DatabaseInstaller {
$conn->commit( __METHOD__ );
$this->pgConns[$type] = $conn;
}
+
return $status;
}
@@ -215,6 +234,7 @@ class PostgresInstaller extends DatabaseInstaller {
$safeRole = $conn->addIdentifierQuotes( $this->getVar( 'wgDBuser' ) );
$conn->query( "SET ROLE $safeRole" );
}
+
return $status;
default:
throw new MWException( "Invalid special connection type: \"$type\"" );
@@ -267,6 +287,7 @@ class PostgresInstaller extends DatabaseInstaller {
$row = $conn->selectRow( '"pg_catalog"."pg_roles"', '*',
array( 'rolname' => $superuser ), __METHOD__ );
+
return $row;
}
@@ -275,6 +296,7 @@ class PostgresInstaller extends DatabaseInstaller {
if ( !$perms ) {
return false;
}
+
return $perms->rolsuper === 't' || $perms->rolcreaterole === 't';
}
@@ -283,6 +305,7 @@ class PostgresInstaller extends DatabaseInstaller {
if ( !$perms ) {
return false;
}
+
return $perms->rolsuper === 't';
}
@@ -331,6 +354,7 @@ class PostgresInstaller extends DatabaseInstaller {
} else {
$msg = 'config-install-user-missing';
}
+
return Status::newFatal( $msg, $this->getVar( 'wgDBuser' ) );
}
@@ -410,6 +434,7 @@ class PostgresInstaller extends DatabaseInstaller {
}
}
}
+
return false;
}
@@ -431,7 +456,7 @@ class PostgresInstaller extends DatabaseInstaller {
'callback' => array( $this, 'setupSchema' )
);
- if( $this->getVar( '_CreateDBAccount' ) ) {
+ if ( $this->getVar( '_CreateDBAccount' ) ) {
$this->parent->addInstallStep( $createDbAccount, 'database' );
}
$this->parent->addInstallStep( $commitCB, 'interwiki' );
@@ -454,6 +479,7 @@ class PostgresInstaller extends DatabaseInstaller {
$safedb = $conn->addIdentifierQuotes( $dbName );
$conn->query( "CREATE DATABASE $safedb", __METHOD__ );
}
+
return Status::newGood();
}
@@ -469,7 +495,7 @@ class PostgresInstaller extends DatabaseInstaller {
$schema = $this->getVar( 'wgDBmwschema' );
$safeschema = $conn->addIdentifierQuotes( $schema );
$safeuser = $conn->addIdentifierQuotes( $this->getVar( 'wgDBuser' ) );
- if( !$conn->schemaExists( $schema ) ) {
+ if ( !$conn->schemaExists( $schema ) ) {
try {
$conn->query( "CREATE SCHEMA $safeschema AUTHORIZATION $safeuser" );
} catch ( DBQueryError $e ) {
@@ -480,11 +506,13 @@ class PostgresInstaller extends DatabaseInstaller {
// Select the new schema in the current connection
$conn->determineCoreSchema( $schema );
+
return Status::newGood();
}
function commitChanges() {
$this->db->commit( __METHOD__ );
+
return Status::newGood();
}
@@ -529,8 +557,8 @@ class PostgresInstaller extends DatabaseInstaller {
function getLocalSettings() {
$port = $this->getVar( 'wgDBport' );
$schema = $this->getVar( 'wgDBmwschema' );
- return
-"# Postgres specific settings
+
+ return "# Postgres specific settings
\$wgDBport = \"{$port}\";
\$wgDBmwschema = \"{$schema}\";";
}
@@ -557,20 +585,22 @@ class PostgresInstaller extends DatabaseInstaller {
*/
$conn = $status->value;
- if( $conn->tableExists( 'archive' ) ) {
+ if ( $conn->tableExists( 'archive' ) ) {
$status->warning( 'config-install-tables-exist' );
$this->enableLB();
+
return $status;
}
$conn->begin( __METHOD__ );
- if( !$conn->schemaExists( $schema ) ) {
+ if ( !$conn->schemaExists( $schema ) ) {
$status->fatal( 'config-install-pg-schema-not-exist' );
+
return $status;
}
$error = $conn->sourceFile( $conn->getSchemaPath() );
- if( $error !== true ) {
+ if ( $error !== true ) {
$conn->reportQueryError( $error, 0, '', __METHOD__ );
$conn->rollback( __METHOD__ );
$status->fatal( 'config-install-tables-failed', $error );
@@ -578,9 +608,10 @@ class PostgresInstaller extends DatabaseInstaller {
$conn->commit( __METHOD__ );
}
// Resume normal operations
- if( $status->isOk() ) {
+ if ( $status->isOk() ) {
$this->enableLB();
}
+
return $status;
}
@@ -623,6 +654,7 @@ class PostgresInstaller extends DatabaseInstaller {
} else {
return Status::newFatal( 'config-pg-no-plpgsql', $this->getVar( 'wgDBname' ) );
}
+
return Status::newGood();
}
}
diff --git a/includes/installer/PostgresUpdater.php b/includes/installer/PostgresUpdater.php
index 0a4b5e65..599b523b 100644
--- a/includes/installer/PostgresUpdater.php
+++ b/includes/installer/PostgresUpdater.php
@@ -46,211 +46,240 @@ class PostgresUpdater extends DatabaseUpdater {
# r15791 Change reserved word table names "user" and "text"
array( 'renameTable', 'user', 'mwuser' ),
array( 'renameTable', 'text', 'pagecontent' ),
- array( 'renameIndex', 'mwuser', 'user_pkey', 'mwuser_pkey'),
+ array( 'renameIndex', 'mwuser', 'user_pkey', 'mwuser_pkey' ),
array( 'renameIndex', 'mwuser', 'user_user_name_key', 'mwuser_user_name_key' ),
- array( 'renameIndex', 'pagecontent','text_pkey', 'pagecontent_pkey' ),
+ array( 'renameIndex', 'pagecontent', 'text_pkey', 'pagecontent_pkey' ),
# renamed sequences
- array( 'renameSequence', 'ipblocks_ipb_id_val', 'ipblocks_ipb_id_seq' ),
- array( 'renameSequence', 'rev_rev_id_val', 'revision_rev_id_seq' ),
- array( 'renameSequence', 'text_old_id_val', 'text_old_id_seq' ),
- array( 'renameSequence', 'rc_rc_id_seq', 'recentchanges_rc_id_seq' ),
- array( 'renameSequence', 'log_log_id_seq', 'logging_log_id_seq' ),
- array( 'renameSequence', 'pr_id_val', 'page_restrictions_pr_id_seq' ),
- array( 'renameSequence', 'us_id_seq', 'uploadstash_us_id_seq' ),
+ array( 'renameSequence', 'ipblocks_ipb_id_val', 'ipblocks_ipb_id_seq' ),
+ array( 'renameSequence', 'rev_rev_id_val', 'revision_rev_id_seq' ),
+ array( 'renameSequence', 'text_old_id_val', 'text_old_id_seq' ),
+ array( 'renameSequence', 'rc_rc_id_seq', 'recentchanges_rc_id_seq' ),
+ array( 'renameSequence', 'log_log_id_seq', 'logging_log_id_seq' ),
+ array( 'renameSequence', 'pr_id_val', 'page_restrictions_pr_id_seq' ),
+ array( 'renameSequence', 'us_id_seq', 'uploadstash_us_id_seq' ),
# since r58263
- array( 'renameSequence', 'category_id_seq', 'category_cat_id_seq'),
+ array( 'renameSequence', 'category_id_seq', 'category_cat_id_seq' ),
# new sequences if not renamed above
array( 'addSequence', 'logging', false, 'logging_log_id_seq' ),
array( 'addSequence', 'page_restrictions', false, 'page_restrictions_pr_id_seq' ),
array( 'addSequence', 'filearchive', 'fa_id', 'filearchive_fa_id_seq' ),
+ array( 'addSequence', 'archive', false, 'archive_ar_id_seq' ),
+ array( 'addSequence', 'externallinks', false, 'externallinks_el_id_seq' ),
# new tables
- array( 'addTable', 'category', 'patch-category.sql' ),
- array( 'addTable', 'page', 'patch-page.sql' ),
- array( 'addTable', 'querycachetwo', 'patch-querycachetwo.sql' ),
- array( 'addTable', 'page_props', 'patch-page_props.sql' ),
+ array( 'addTable', 'category', 'patch-category.sql' ),
+ array( 'addTable', 'page', 'patch-page.sql' ),
+ array( 'addTable', 'querycachetwo', 'patch-querycachetwo.sql' ),
+ array( 'addTable', 'page_props', 'patch-page_props.sql' ),
array( 'addTable', 'page_restrictions', 'patch-page_restrictions.sql' ),
- array( 'addTable', 'profiling', 'patch-profiling.sql' ),
- array( 'addTable', 'protected_titles', 'patch-protected_titles.sql' ),
- array( 'addTable', 'redirect', 'patch-redirect.sql' ),
- array( 'addTable', 'updatelog', 'patch-updatelog.sql' ),
- array( 'addTable', 'change_tag', 'patch-change_tag.sql' ),
- array( 'addTable', 'tag_summary', 'patch-tag_summary.sql' ),
- array( 'addTable', 'valid_tag', 'patch-valid_tag.sql' ),
- array( 'addTable', 'user_properties', 'patch-user_properties.sql' ),
- array( 'addTable', 'log_search', 'patch-log_search.sql' ),
- array( 'addTable', 'l10n_cache', 'patch-l10n_cache.sql' ),
- array( 'addTable', 'iwlinks', 'patch-iwlinks.sql' ),
- array( 'addTable', 'msg_resource', 'patch-msg_resource.sql' ),
- array( 'addTable', 'msg_resource_links','patch-msg_resource_links.sql' ),
- array( 'addTable', 'module_deps', 'patch-module_deps.sql' ),
- 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' ),
+ array( 'addTable', 'profiling', 'patch-profiling.sql' ),
+ array( 'addTable', 'protected_titles', 'patch-protected_titles.sql' ),
+ array( 'addTable', 'redirect', 'patch-redirect.sql' ),
+ array( 'addTable', 'updatelog', 'patch-updatelog.sql' ),
+ array( 'addTable', 'change_tag', 'patch-change_tag.sql' ),
+ array( 'addTable', 'tag_summary', 'patch-tag_summary.sql' ),
+ array( 'addTable', 'valid_tag', 'patch-valid_tag.sql' ),
+ array( 'addTable', 'user_properties', 'patch-user_properties.sql' ),
+ array( 'addTable', 'log_search', 'patch-log_search.sql' ),
+ array( 'addTable', 'l10n_cache', 'patch-l10n_cache.sql' ),
+ array( 'addTable', 'iwlinks', 'patch-iwlinks.sql' ),
+ array( 'addTable', 'msg_resource', 'patch-msg_resource.sql' ),
+ array( 'addTable', 'msg_resource_links', 'patch-msg_resource_links.sql' ),
+ array( 'addTable', 'module_deps', 'patch-module_deps.sql' ),
+ array( 'addTable', 'uploadstash', 'patch-uploadstash.sql' ),
+ array( 'addTable', 'user_former_groups', 'patch-user_former_groups.sql' ),
+ array( 'addTable', 'sites', 'patch-sites.sql' ),
# Needed before new field
array( 'convertArchive2' ),
# new fields
- array( 'addPgField', 'updatelog', 'ul_value', 'TEXT' ),
- array( 'addPgField', 'archive', 'ar_deleted', 'SMALLINT NOT NULL DEFAULT 0' ),
- 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'"),
- array( 'addPgField', 'image', 'img_sha1', "TEXT NOT NULL DEFAULT ''" ),
- array( 'addPgField', 'ipblocks', 'ipb_allow_usertalk', 'SMALLINT NOT NULL DEFAULT 0' ),
- array( 'addPgField', 'ipblocks', 'ipb_anon_only', 'SMALLINT NOT NULL DEFAULT 0' ),
- array( 'addPgField', 'ipblocks', 'ipb_by_text', "TEXT NOT NULL DEFAULT ''" ),
- array( 'addPgField', 'ipblocks', 'ipb_block_email', 'SMALLINT NOT NULL DEFAULT 0' ),
- array( 'addPgField', 'ipblocks', 'ipb_create_account', 'SMALLINT NOT NULL DEFAULT 1' ),
- array( 'addPgField', 'ipblocks', 'ipb_deleted', 'SMALLINT NOT NULL DEFAULT 0' ),
- 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' ),
- array( 'addPgField', 'mwuser', 'user_editcount', 'INTEGER' ),
- array( 'addPgField', 'mwuser', 'user_newpass_time', 'TIMESTAMPTZ' ),
- array( 'addPgField', 'oldimage', 'oi_deleted', 'SMALLINT NOT NULL DEFAULT 0' ),
- array( 'addPgField', 'oldimage', 'oi_major_mime', "TEXT NOT NULL DEFAULT 'unknown'" ),
- array( 'addPgField', 'oldimage', 'oi_media_type', 'TEXT' ),
- 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' ),
- array( 'addPgField', 'recentchanges', 'rc_log_action', 'TEXT' ),
- array( 'addPgField', 'recentchanges', 'rc_log_type', 'TEXT' ),
- array( 'addPgField', 'recentchanges', 'rc_logid', 'INTEGER NOT NULL DEFAULT 0' ),
- array( 'addPgField', 'recentchanges', 'rc_new_len', 'INTEGER' ),
- array( 'addPgField', 'recentchanges', 'rc_old_len', 'INTEGER' ),
- array( 'addPgField', 'recentchanges', 'rc_params', 'TEXT' ),
- array( 'addPgField', 'redirect', 'rd_interwiki', 'TEXT NULL' ),
- array( 'addPgField', 'redirect', 'rd_fragment', 'TEXT NULL' ),
- 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 ''" ),
- array( 'addPgField', 'logging', 'log_page', 'INTEGER' ),
- array( 'addPgField', 'interwiki', 'iw_api', "TEXT NOT NULL DEFAULT ''"),
- array( 'addPgField', 'interwiki', 'iw_wikiid', "TEXT NOT NULL DEFAULT ''"),
- array( 'addPgField', 'revision', 'rev_sha1', "TEXT NOT NULL DEFAULT ''" ),
- 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 ''" ),
+ array( 'addPgField', 'updatelog', 'ul_value', 'TEXT' ),
+ array( 'addPgField', 'archive', 'ar_deleted', 'SMALLINT NOT NULL DEFAULT 0' ),
+ 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'" ),
+ array( 'addPgField', 'image', 'img_sha1', "TEXT NOT NULL DEFAULT ''" ),
+ array( 'addPgField', 'ipblocks', 'ipb_allow_usertalk', 'SMALLINT NOT NULL DEFAULT 0' ),
+ array( 'addPgField', 'ipblocks', 'ipb_anon_only', 'SMALLINT NOT NULL DEFAULT 0' ),
+ array( 'addPgField', 'ipblocks', 'ipb_by_text', "TEXT NOT NULL DEFAULT ''" ),
+ array( 'addPgField', 'ipblocks', 'ipb_block_email', 'SMALLINT NOT NULL DEFAULT 0' ),
+ array( 'addPgField', 'ipblocks', 'ipb_create_account', 'SMALLINT NOT NULL DEFAULT 1' ),
+ array( 'addPgField', 'ipblocks', 'ipb_deleted', 'SMALLINT NOT NULL DEFAULT 0' ),
+ 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' ),
+ array( 'addPgField', 'mwuser', 'user_editcount', 'INTEGER' ),
+ array( 'addPgField', 'mwuser', 'user_newpass_time', 'TIMESTAMPTZ' ),
+ array( 'addPgField', 'oldimage', 'oi_deleted', 'SMALLINT NOT NULL DEFAULT 0' ),
+ array( 'addPgField', 'oldimage', 'oi_major_mime', "TEXT NOT NULL DEFAULT 'unknown'" ),
+ array( 'addPgField', 'oldimage', 'oi_media_type', 'TEXT' ),
+ 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' ),
+ array( 'addPgField', 'recentchanges', 'rc_log_action', 'TEXT' ),
+ array( 'addPgField', 'recentchanges', 'rc_log_type', 'TEXT' ),
+ array( 'addPgField', 'recentchanges', 'rc_logid', 'INTEGER NOT NULL DEFAULT 0' ),
+ array( 'addPgField', 'recentchanges', 'rc_new_len', 'INTEGER' ),
+ array( 'addPgField', 'recentchanges', 'rc_old_len', 'INTEGER' ),
+ array( 'addPgField', 'recentchanges', 'rc_params', 'TEXT' ),
+ array( 'addPgField', 'redirect', 'rd_interwiki', 'TEXT NULL' ),
+ array( 'addPgField', 'redirect', 'rd_fragment', 'TEXT NULL' ),
+ 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 ''" ),
+ array( 'addPgField', 'logging', 'log_page', 'INTEGER' ),
+ array( 'addPgField', 'interwiki', 'iw_api', "TEXT NOT NULL DEFAULT ''" ),
+ array( 'addPgField', 'interwiki', 'iw_wikiid', "TEXT NOT NULL DEFAULT ''" ),
+ array( 'addPgField', 'revision', 'rev_sha1', "TEXT NOT NULL DEFAULT ''" ),
+ 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 ''" ),
+ array( 'addPgField', 'archive', 'ar_id',
+ "INTEGER NOT NULL PRIMARY KEY DEFAULT nextval('archive_ar_id_seq')" ),
+ array( 'addPgField', 'externallinks', 'el_id',
+ "INTEGER NOT NULL PRIMARY KEY DEFAULT nextval('externallinks_el_id_seq')" ),
# type changes
- array( 'changeField', 'archive', 'ar_deleted', 'smallint', '' ),
- array( 'changeField', 'archive', 'ar_minor_edit', 'smallint', 'ar_minor_edit::smallint DEFAULT 0' ),
- array( 'changeField', 'filearchive', 'fa_deleted', 'smallint', '' ),
- array( 'changeField', 'filearchive', 'fa_height', 'integer', '' ),
- array( 'changeField', 'filearchive', 'fa_metadata', 'bytea', "decode(fa_metadata,'escape')" ),
- array( 'changeField', 'filearchive', 'fa_size', 'integer', '' ),
- array( 'changeField', 'filearchive', 'fa_width', 'integer', '' ),
- array( 'changeField', 'filearchive', 'fa_storage_group', 'text', '' ),
- array( 'changeField', 'filearchive', 'fa_storage_key', 'text', '' ),
- array( 'changeField', 'image', 'img_metadata', 'bytea', "decode(img_metadata,'escape')" ),
- array( 'changeField', 'image', 'img_size', 'integer', '' ),
- array( 'changeField', 'image', 'img_width', 'integer', '' ),
- array( 'changeField', 'image', 'img_height', 'integer', '' ),
- array( 'changeField', 'interwiki', 'iw_local', 'smallint', 'iw_local::smallint' ),
- array( 'changeField', 'interwiki', 'iw_trans', 'smallint', 'iw_trans::smallint DEFAULT 0' ),
- array( 'changeField', 'ipblocks', 'ipb_auto', 'smallint', 'ipb_auto::smallint DEFAULT 0' ),
- array( 'changeField', 'ipblocks', 'ipb_anon_only', 'smallint', "CASE WHEN ipb_anon_only=' ' THEN 0 ELSE ipb_anon_only::smallint END DEFAULT 0" ),
- array( 'changeField', 'ipblocks', 'ipb_create_account', 'smallint', "CASE WHEN ipb_create_account=' ' THEN 0 ELSE ipb_create_account::smallint END DEFAULT 1" ),
- array( 'changeField', 'ipblocks', 'ipb_enable_autoblock', 'smallint', "CASE WHEN ipb_enable_autoblock=' ' THEN 0 ELSE ipb_enable_autoblock::smallint END DEFAULT 1" ),
- array( 'changeField', 'ipblocks', 'ipb_block_email', 'smallint', "CASE WHEN ipb_block_email=' ' THEN 0 ELSE ipb_block_email::smallint END DEFAULT 0" ),
- array( 'changeField', 'ipblocks', 'ipb_address', 'text', 'ipb_address::text' ),
- array( 'changeField', 'ipblocks', 'ipb_deleted', 'smallint', 'ipb_deleted::smallint DEFAULT 0' ),
- array( 'changeField', 'mwuser', 'user_token', 'text', '' ),
- array( 'changeField', 'mwuser', 'user_email_token', 'text', '' ),
- array( 'changeField', 'objectcache', 'keyname', 'text', '' ),
- array( 'changeField', 'oldimage', 'oi_height', 'integer', '' ),
- array( 'changeField', 'oldimage', 'oi_metadata', 'bytea', "decode(img_metadata,'escape')" ),
- array( 'changeField', 'oldimage', 'oi_size', 'integer', '' ),
- array( 'changeField', 'oldimage', 'oi_width', 'integer', '' ),
- array( 'changeField', 'page', 'page_is_redirect', 'smallint', 'page_is_redirect::smallint DEFAULT 0' ),
- array( 'changeField', 'page', 'page_is_new', 'smallint', 'page_is_new::smallint DEFAULT 0' ),
- array( 'changeField', 'querycache', 'qc_value', 'integer', '' ),
- array( 'changeField', 'querycachetwo', 'qcc_value', 'integer', '' ),
- array( 'changeField', 'recentchanges', 'rc_bot', 'smallint', 'rc_bot::smallint DEFAULT 0' ),
- array( 'changeField', 'recentchanges', 'rc_deleted', 'smallint', '' ),
- array( 'changeField', 'recentchanges', 'rc_minor', 'smallint', 'rc_minor::smallint DEFAULT 0' ),
- array( 'changeField', 'recentchanges', 'rc_new', 'smallint', 'rc_new::smallint DEFAULT 0' ),
- array( 'changeField', 'recentchanges', 'rc_type', 'smallint', 'rc_type::smallint DEFAULT 0' ),
- array( 'changeField', 'recentchanges', 'rc_patrolled', 'smallint', 'rc_patrolled::smallint DEFAULT 0' ),
- array( 'changeField', 'revision', 'rev_deleted', 'smallint', 'rev_deleted::smallint DEFAULT 0' ),
- array( 'changeField', 'revision', 'rev_minor_edit', 'smallint', 'rev_minor_edit::smallint DEFAULT 0' ),
- array( 'changeField', 'templatelinks', 'tl_namespace', 'smallint', 'tl_namespace::smallint' ),
- array( 'changeField', 'user_newtalk', 'user_ip', 'text', 'host(user_ip)' ),
- array( 'changeField', 'uploadstash', 'us_image_bits', 'smallint', '' ),
+ array( 'changeField', 'archive', 'ar_deleted', 'smallint', '' ),
+ array( 'changeField', 'archive', 'ar_minor_edit', 'smallint',
+ 'ar_minor_edit::smallint DEFAULT 0' ),
+ array( 'changeField', 'filearchive', 'fa_deleted', 'smallint', '' ),
+ array( 'changeField', 'filearchive', 'fa_height', 'integer', '' ),
+ array( 'changeField', 'filearchive', 'fa_metadata', 'bytea', "decode(fa_metadata,'escape')" ),
+ array( 'changeField', 'filearchive', 'fa_size', 'integer', '' ),
+ array( 'changeField', 'filearchive', 'fa_width', 'integer', '' ),
+ array( 'changeField', 'filearchive', 'fa_storage_group', 'text', '' ),
+ array( 'changeField', 'filearchive', 'fa_storage_key', 'text', '' ),
+ array( 'changeField', 'image', 'img_metadata', 'bytea', "decode(img_metadata,'escape')" ),
+ array( 'changeField', 'image', 'img_size', 'integer', '' ),
+ array( 'changeField', 'image', 'img_width', 'integer', '' ),
+ array( 'changeField', 'image', 'img_height', 'integer', '' ),
+ array( 'changeField', 'interwiki', 'iw_local', 'smallint', 'iw_local::smallint' ),
+ array( 'changeField', 'interwiki', 'iw_trans', 'smallint', 'iw_trans::smallint DEFAULT 0' ),
+ array( 'changeField', 'ipblocks', 'ipb_auto', 'smallint', 'ipb_auto::smallint DEFAULT 0' ),
+ array( 'changeField', 'ipblocks', 'ipb_anon_only', 'smallint',
+ "CASE WHEN ipb_anon_only=' ' THEN 0 ELSE ipb_anon_only::smallint END DEFAULT 0" ),
+ array( 'changeField', 'ipblocks', 'ipb_create_account', 'smallint',
+ "CASE WHEN ipb_create_account=' ' THEN 0 ELSE ipb_create_account::smallint END DEFAULT 1" ),
+ array( 'changeField', 'ipblocks', 'ipb_enable_autoblock', 'smallint',
+ "CASE WHEN ipb_enable_autoblock=' ' THEN 0 ELSE ipb_enable_autoblock::smallint END DEFAULT 1" ),
+ array( 'changeField', 'ipblocks', 'ipb_block_email', 'smallint',
+ "CASE WHEN ipb_block_email=' ' THEN 0 ELSE ipb_block_email::smallint END DEFAULT 0" ),
+ array( 'changeField', 'ipblocks', 'ipb_address', 'text', 'ipb_address::text' ),
+ array( 'changeField', 'ipblocks', 'ipb_deleted', 'smallint', 'ipb_deleted::smallint DEFAULT 0' ),
+ array( 'changeField', 'mwuser', 'user_token', 'text', '' ),
+ array( 'changeField', 'mwuser', 'user_email_token', 'text', '' ),
+ array( 'changeField', 'objectcache', 'keyname', 'text', '' ),
+ array( 'changeField', 'oldimage', 'oi_height', 'integer', '' ),
+ array( 'changeField', 'oldimage', 'oi_metadata', 'bytea', "decode(img_metadata,'escape')" ),
+ array( 'changeField', 'oldimage', 'oi_size', 'integer', '' ),
+ array( 'changeField', 'oldimage', 'oi_width', 'integer', '' ),
+ array( 'changeField', 'page', 'page_is_redirect', 'smallint',
+ 'page_is_redirect::smallint DEFAULT 0' ),
+ array( 'changeField', 'page', 'page_is_new', 'smallint', 'page_is_new::smallint DEFAULT 0' ),
+ array( 'changeField', 'querycache', 'qc_value', 'integer', '' ),
+ array( 'changeField', 'querycachetwo', 'qcc_value', 'integer', '' ),
+ array( 'changeField', 'recentchanges', 'rc_bot', 'smallint', 'rc_bot::smallint DEFAULT 0' ),
+ array( 'changeField', 'recentchanges', 'rc_deleted', 'smallint', '' ),
+ array( 'changeField', 'recentchanges', 'rc_minor', 'smallint', 'rc_minor::smallint DEFAULT 0' ),
+ array( 'changeField', 'recentchanges', 'rc_new', 'smallint', 'rc_new::smallint DEFAULT 0' ),
+ array( 'changeField', 'recentchanges', 'rc_type', 'smallint', 'rc_type::smallint DEFAULT 0' ),
+ array( 'changeField', 'recentchanges', 'rc_patrolled', 'smallint',
+ 'rc_patrolled::smallint DEFAULT 0' ),
+ array( 'changeField', 'revision', 'rev_deleted', 'smallint', 'rev_deleted::smallint DEFAULT 0' ),
+ array( 'changeField', 'revision', 'rev_minor_edit', 'smallint',
+ 'rev_minor_edit::smallint DEFAULT 0' ),
+ array( 'changeField', 'templatelinks', 'tl_namespace', 'smallint', 'tl_namespace::smallint' ),
+ array( 'changeField', 'user_newtalk', 'user_ip', 'text', 'host(user_ip)' ),
+ array( 'changeField', 'uploadstash', 'us_image_bits', 'smallint', '' ),
+ array( 'changeField', 'profiling', 'pf_time', 'float', '' ),
+ array( 'changeField', 'profiling', 'pf_memory', 'float', '' ),
# null changes
- array( 'changeNullableField', 'oldimage', 'oi_bits', 'NULL' ),
- array( 'changeNullableField', 'oldimage', 'oi_timestamp', 'NULL' ),
+ array( 'changeNullableField', 'oldimage', 'oi_bits', 'NULL' ),
+ array( 'changeNullableField', 'oldimage', 'oi_timestamp', 'NULL' ),
array( 'changeNullableField', 'oldimage', 'oi_major_mime', 'NULL' ),
array( 'changeNullableField', 'oldimage', 'oi_minor_mime', 'NULL' ),
- array( 'changeNullableField', 'image', 'img_metadata', 'NOT NULL'),
- array( 'changeNullableField', 'filearchive', 'fa_metadata', 'NOT NULL'),
+ array( 'changeNullableField', 'image', 'img_metadata', 'NOT NULL' ),
+ array( 'changeNullableField', 'filearchive', 'fa_metadata', 'NOT NULL' ),
array( 'changeNullableField', 'recentchanges', 'rc_cur_id', 'NULL' ),
array( 'checkOiDeleted' ),
# New indexes
- array( 'addPgIndex', 'archive', 'archive_user_text', '(ar_user_text)' ),
- array( 'addPgIndex', 'image', 'img_sha1', '(img_sha1)' ),
- array( 'addPgIndex', 'ipblocks', 'ipb_parent_block_id', '(ipb_parent_block_id)' ),
- 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)' ),
- array( 'addPgIndex', 'watchlist', 'wl_user', '(wl_user)' ),
- array( 'addPgIndex', 'logging', 'logging_user_type_time', '(log_user, log_type, log_timestamp)' ),
- 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( 'addPgIndex', 'archive', 'archive_user_text', '(ar_user_text)' ),
+ array( 'addPgIndex', 'image', 'img_sha1', '(img_sha1)' ),
+ array( 'addPgIndex', 'ipblocks', 'ipb_parent_block_id', '(ipb_parent_block_id)' ),
+ 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)' ),
+ array( 'addPgIndex', 'watchlist', 'wl_user', '(wl_user)' ),
+ array( 'addPgIndex', 'logging', 'logging_user_type_time',
+ '(log_user, log_type, log_timestamp)' ),
+ array( 'addPgIndex', 'logging', 'logging_page_id_time', '(log_page,log_timestamp)' ),
+ array( 'addPgIndex', 'iwlinks', 'iwl_prefix_from_title', '(iwl_prefix, iwl_from, iwl_title)' ),
+ 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 ),
),
- 'CREATE UNIQUE INDEX pagelink_unique ON pagelinks (pl_from,pl_namespace,pl_title)' ),
+ '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 ),
),
- 'CREATE INDEX cl_sortkey ON "categorylinks" USING "btree" ("cl_to", "cl_sortkey", "cl_from")' ),
+ 'CREATE INDEX cl_sortkey ON "categorylinks" ' .
+ 'USING "btree" ("cl_to", "cl_sortkey", "cl_from")' ),
+ array( 'checkIndex', 'iwl_prefix_title_from', array(
+ array( 'iwl_prefix', 'text_ops', 'btree', 0 ),
+ array( 'iwl_title', 'text_ops', 'btree', 0 ),
+ array( 'iwl_from', 'int4_ops', 'btree', 0 ),
+ ),
+ 'CREATE INDEX iwl_prefix_title_from ON "iwlinks" ' .
+ 'USING "btree" ("iwl_prefix", "iwl_title", "iwl_from")' ),
array( 'checkIndex', 'logging_times', array(
array( 'log_timestamp', 'timestamptz_ops', 'btree', 0 ),
),
@@ -260,36 +289,48 @@ class PostgresUpdater extends DatabaseUpdater {
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")' ),
+ '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 ),
),
- 'CREATE INDEX "oi_name_timestamp" ON "oldimage" USING "btree" ("oi_name", "oi_timestamp")' ),
+ '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 ),
),
- 'CREATE INDEX "page_main_title" ON "page" USING "btree" ("page_title" "text_pattern_ops") WHERE ("page_namespace" = 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 ),
),
- 'CREATE INDEX "page_mediawiki_title" ON "page" USING "btree" ("page_title" "text_pattern_ops") WHERE ("page_namespace" = 8)' ),
+ '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 ),
),
- 'CREATE INDEX "page_project_title" ON "page" USING "btree" ("page_title" "text_pattern_ops") WHERE ("page_namespace" = 4)' ),
+ '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 ),
),
- 'CREATE INDEX "page_talk_title" ON "page" USING "btree" ("page_title" "text_pattern_ops") WHERE ("page_namespace" = 1)' ),
+ '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 ),
),
- 'CREATE INDEX "page_user_title" ON "page" USING "btree" ("page_title" "text_pattern_ops") WHERE ("page_namespace" = 2)' ),
+ '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 ),
),
- 'CREATE INDEX "page_utalk_title" ON "page" USING "btree" ("page_title" "text_pattern_ops") WHERE ("page_namespace" = 3)' ),
+ '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 ),
),
@@ -302,46 +343,55 @@ class PostgresUpdater extends DatabaseUpdater {
array( 'checkOiNameConstraint' ),
array( 'checkPageDeletedTrigger' ),
array( 'checkRevUserFkey' ),
- array( 'dropIndex', 'ipblocks', 'ipb_address'),
+ 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_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)' ),
+ 'CREATE UNIQUE INDEX ipb_address_unique ' .
+ 'ON ipblocks (ipb_address,ipb_user,ipb_auto,ipb_anon_only)' ),
array( 'checkIwlPrefix' ),
# All FK columns should be deferred
- array( 'changeFkeyDeferrable', 'archive', 'ar_user', 'mwuser(user_id) ON DELETE SET NULL' ),
- array( 'changeFkeyDeferrable', 'categorylinks', 'cl_from', 'page(page_id) ON DELETE CASCADE' ),
- array( 'changeFkeyDeferrable', 'externallinks', 'el_from', 'page(page_id) ON DELETE CASCADE' ),
- array( 'changeFkeyDeferrable', 'filearchive', 'fa_deleted_user', 'mwuser(user_id) ON DELETE SET NULL' ),
- array( 'changeFkeyDeferrable', 'filearchive', 'fa_user', 'mwuser(user_id) ON DELETE SET NULL' ),
- array( 'changeFkeyDeferrable', 'image', 'img_user', 'mwuser(user_id) ON DELETE SET NULL' ),
- array( 'changeFkeyDeferrable', 'imagelinks', 'il_from', 'page(page_id) ON DELETE CASCADE' ),
- array( 'changeFkeyDeferrable', 'ipblocks', 'ipb_by', 'mwuser(user_id) ON DELETE CASCADE' ),
- array( 'changeFkeyDeferrable', 'ipblocks', 'ipb_user', 'mwuser(user_id) ON DELETE SET NULL' ),
- array( 'changeFkeyDeferrable', 'ipblocks', 'ipb_parent_block_id', 'ipblocks(ipb_id) ON DELETE SET NULL' ),
- array( 'changeFkeyDeferrable', 'langlinks', 'll_from', 'page(page_id) ON DELETE CASCADE' ),
- array( 'changeFkeyDeferrable', 'logging', 'log_user', 'mwuser(user_id) ON DELETE SET NULL' ),
- array( 'changeFkeyDeferrable', 'oldimage', 'oi_name', 'image(img_name) ON DELETE CASCADE ON UPDATE CASCADE' ),
- array( 'changeFkeyDeferrable', 'oldimage', 'oi_user', 'mwuser(user_id) ON DELETE SET NULL' ),
- array( 'changeFkeyDeferrable', 'pagelinks', 'pl_from', 'page(page_id) ON DELETE CASCADE' ),
- array( 'changeFkeyDeferrable', 'page_props', 'pp_page', 'page (page_id) ON DELETE CASCADE' ),
- array( 'changeFkeyDeferrable', 'page_restrictions', 'pr_page', 'page(page_id) ON DELETE CASCADE' ),
- array( 'changeFkeyDeferrable', 'protected_titles', 'pt_user', 'mwuser(user_id) ON DELETE SET NULL' ),
- array( 'changeFkeyDeferrable', 'recentchanges', 'rc_cur_id', 'page(page_id) ON DELETE SET NULL' ),
- array( 'changeFkeyDeferrable', 'recentchanges', 'rc_user', 'mwuser(user_id) ON DELETE SET NULL' ),
- array( 'changeFkeyDeferrable', 'redirect', 'rd_from', 'page(page_id) ON DELETE CASCADE' ),
- array( 'changeFkeyDeferrable', 'revision', 'rev_page', 'page (page_id) ON DELETE CASCADE' ),
- array( 'changeFkeyDeferrable', 'revision', 'rev_user', 'mwuser(user_id) ON DELETE RESTRICT' ),
- array( 'changeFkeyDeferrable', 'templatelinks', 'tl_from', 'page(page_id) ON DELETE CASCADE' ),
- array( 'changeFkeyDeferrable', 'user_groups', 'ug_user', 'mwuser(user_id) ON DELETE CASCADE' ),
- array( 'changeFkeyDeferrable', 'user_newtalk', 'user_id', 'mwuser(user_id) ON DELETE CASCADE' ),
- array( 'changeFkeyDeferrable', 'user_properties', 'up_user', 'mwuser(user_id) ON DELETE CASCADE' ),
- array( 'changeFkeyDeferrable', 'watchlist', 'wl_user', 'mwuser(user_id) ON DELETE CASCADE' ),
+ array( 'changeFkeyDeferrable', 'archive', 'ar_user', 'mwuser(user_id) ON DELETE SET NULL' ),
+ array( 'changeFkeyDeferrable', 'categorylinks', 'cl_from', 'page(page_id) ON DELETE CASCADE' ),
+ array( 'changeFkeyDeferrable', 'externallinks', 'el_from', 'page(page_id) ON DELETE CASCADE' ),
+ array( 'changeFkeyDeferrable', 'filearchive', 'fa_deleted_user',
+ 'mwuser(user_id) ON DELETE SET NULL' ),
+ array( 'changeFkeyDeferrable', 'filearchive', 'fa_user', 'mwuser(user_id) ON DELETE SET NULL' ),
+ array( 'changeFkeyDeferrable', 'image', 'img_user', 'mwuser(user_id) ON DELETE SET NULL' ),
+ array( 'changeFkeyDeferrable', 'imagelinks', 'il_from', 'page(page_id) ON DELETE CASCADE' ),
+ array( 'changeFkeyDeferrable', 'ipblocks', 'ipb_by', 'mwuser(user_id) ON DELETE CASCADE' ),
+ array( 'changeFkeyDeferrable', 'ipblocks', 'ipb_user', 'mwuser(user_id) ON DELETE SET NULL' ),
+ array( 'changeFkeyDeferrable', 'ipblocks', 'ipb_parent_block_id',
+ 'ipblocks(ipb_id) ON DELETE SET NULL' ),
+ array( 'changeFkeyDeferrable', 'langlinks', 'll_from', 'page(page_id) ON DELETE CASCADE' ),
+ array( 'changeFkeyDeferrable', 'logging', 'log_user', 'mwuser(user_id) ON DELETE SET NULL' ),
+ array( 'changeFkeyDeferrable', 'oldimage', 'oi_name',
+ 'image(img_name) ON DELETE CASCADE ON UPDATE CASCADE' ),
+ array( 'changeFkeyDeferrable', 'oldimage', 'oi_user', 'mwuser(user_id) ON DELETE SET NULL' ),
+ array( 'changeFkeyDeferrable', 'pagelinks', 'pl_from', 'page(page_id) ON DELETE CASCADE' ),
+ array( 'changeFkeyDeferrable', 'page_props', 'pp_page', 'page (page_id) ON DELETE CASCADE' ),
+ array( 'changeFkeyDeferrable', 'page_restrictions', 'pr_page',
+ 'page(page_id) ON DELETE CASCADE' ),
+ array( 'changeFkeyDeferrable', 'protected_titles', 'pt_user',
+ 'mwuser(user_id) ON DELETE SET NULL' ),
+ array( 'changeFkeyDeferrable', 'recentchanges', 'rc_cur_id',
+ 'page(page_id) ON DELETE SET NULL' ),
+ array( 'changeFkeyDeferrable', 'recentchanges', 'rc_user',
+ 'mwuser(user_id) ON DELETE SET NULL' ),
+ array( 'changeFkeyDeferrable', 'redirect', 'rd_from', 'page(page_id) ON DELETE CASCADE' ),
+ array( 'changeFkeyDeferrable', 'revision', 'rev_page', 'page (page_id) ON DELETE CASCADE' ),
+ array( 'changeFkeyDeferrable', 'revision', 'rev_user', 'mwuser(user_id) ON DELETE RESTRICT' ),
+ array( 'changeFkeyDeferrable', 'templatelinks', 'tl_from', 'page(page_id) ON DELETE CASCADE' ),
+ array( 'changeFkeyDeferrable', 'user_groups', 'ug_user', 'mwuser(user_id) ON DELETE CASCADE' ),
+ array( 'changeFkeyDeferrable', 'user_newtalk', 'user_id', 'mwuser(user_id) ON DELETE CASCADE' ),
+ array( 'changeFkeyDeferrable', 'user_properties', 'up_user',
+ 'mwuser(user_id) ON DELETE CASCADE' ),
+ array( 'changeFkeyDeferrable', 'watchlist', 'wl_user', 'mwuser(user_id) ON DELETE CASCADE' ),
# r81574
array( 'addInterwikiType' ),
@@ -365,25 +415,25 @@ class PostgresUpdater extends DatabaseUpdater {
# Add missing extension fields
foreach ( $wgExtPGNewFields as $fieldRecord ) {
$updates[] = array(
- 'addPgField', $fieldRecord[0], $fieldRecord[1],
- $fieldRecord[2]
- );
+ 'addPgField', $fieldRecord[0], $fieldRecord[1],
+ $fieldRecord[2]
+ );
}
# Change altered columns
foreach ( $wgExtPGAlteredFields as $fieldRecord ) {
$updates[] = array(
- 'changeField', $fieldRecord[0], $fieldRecord[1],
- $fieldRecord[2]
- );
+ 'changeField', $fieldRecord[0], $fieldRecord[1],
+ $fieldRecord[2]
+ );
}
# Add missing extension indexes
foreach ( $wgExtNewIndexes as $fieldRecord ) {
$updates[] = array(
- 'addPgExtIndex', $fieldRecord[0], $fieldRecord[1],
- $fieldRecord[2]
- );
+ 'addPgExtIndex', $fieldRecord[0], $fieldRecord[1],
+ $fieldRecord[2]
+ );
}
return $updates;
@@ -397,8 +447,8 @@ SELECT attname, attnum FROM pg_namespace, pg_class, pg_attribute
AND relname=%s AND nspname=%s
END;
$res = $this->db->query( sprintf( $q,
- $this->db->addQuotes( $table ),
- $this->db->addQuotes( $this->db->getCoreSchema() ) ) );
+ $this->db->addQuotes( $table ),
+ $this->db->addQuotes( $this->db->getCoreSchema() ) ) );
if ( !$res ) {
return null;
}
@@ -406,10 +456,11 @@ END;
$cols = array();
foreach ( $res as $r ) {
$cols[] = array(
- "name" => $r[0],
- "ord" => $r[1],
- );
+ "name" => $r[0],
+ "ord" => $r[1],
+ );
}
+
return $cols;
}
@@ -479,11 +530,12 @@ END;
if ( !( $row = $this->db->fetchRow( $r ) ) ) {
return null;
}
+
return $row[0];
}
function ruleDef( $table, $rule ) {
- $q = <<<END
+ $q = <<<END
SELECT definition FROM pg_rules
WHERE schemaname = %s
AND tablename = %s
@@ -502,6 +554,7 @@ END;
return null;
}
$d = $row[0];
+
return $d;
}
@@ -509,7 +562,7 @@ END;
if ( !$this->db->sequenceExists( $ns ) ) {
$this->output( "Creating sequence $ns\n" );
$this->db->query( "CREATE SEQUENCE $ns" );
- if( $pkey !== false ) {
+ if ( $pkey !== false ) {
$this->setDefault( $table, $pkey, '"nextval"(\'"' . $ns . '"\'::"regclass")' );
}
}
@@ -518,6 +571,7 @@ END;
protected function renameSequence( $old, $new ) {
if ( $this->db->sequenceExists( $new ) ) {
$this->output( "...sequence $new already exists.\n" );
+
return;
}
if ( $this->db->sequenceExists( $old ) ) {
@@ -532,7 +586,7 @@ END;
$old = $this->db->realTableName( $old, "quoted" );
$new = $this->db->realTableName( $new, "quoted" );
$this->db->query( "ALTER TABLE $old RENAME TO $new" );
- if( $patch !== false ) {
+ if ( $patch !== false ) {
$this->applyPatch( $patch );
}
}
@@ -544,6 +598,7 @@ END;
// First requirement: the table must exist
if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
$this->output( "...skipping: '$table' table doesn't exist yet.\n" );
+
return;
}
@@ -551,17 +606,20 @@ END;
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" .
+ && $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;
}
@@ -572,6 +630,7 @@ END;
$fi = $this->db->fieldInfo( $table, $field );
if ( !is_null( $fi ) ) {
$this->output( "...column '$table.$field' already exists\n" );
+
return;
} else {
$this->output( "Adding column '$table.$field'\n" );
@@ -586,9 +645,9 @@ END;
exit( 1 );
}
- if ( $fi->type() === $newtype )
+ if ( $fi->type() === $newtype ) {
$this->output( "...column '$table.$field' is already of type '$newtype'\n" );
- else {
+ } else {
$this->output( "Changing column type of '$table.$field' from '{$fi->type()}' to '$newtype'\n" );
$sql = "ALTER TABLE $table ALTER $field TYPE $newtype";
if ( strlen( $default ) ) {
@@ -613,7 +672,7 @@ END;
}
}
- protected function changeNullableField( $table, $field, $null) {
+ protected function changeNullableField( $table, $field, $null ) {
$fi = $this->db->fieldInfo( $table, $field );
if ( is_null( $fi ) ) {
$this->output( "...ERROR: expected column $table.$field to exist\n" );
@@ -632,8 +691,7 @@ END;
if ( 'NULL' === $null ) {
$this->output( "Changing '$table.$field' to allow NULLs\n" );
$this->db->query( "ALTER TABLE $table ALTER $field DROP NOT NULL" );
- }
- else {
+ } else {
$this->output( "...column '$table.$field' is already set as NOT NULL\n" );
}
}
@@ -664,7 +722,9 @@ END;
protected function changeFkeyDeferrable( $table, $field, $clause ) {
$fi = $this->db->fieldInfo( $table, $field );
if ( is_null( $fi ) ) {
- $this->output( "WARNING! Column '$table.$field' does not exist but it should! Please report this.\n" );
+ $this->output( "WARNING! Column '$table.$field' does not exist but it should! " .
+ "Please report this.\n" );
+
return;
}
if ( $fi->is_deferred() && $fi->is_deferrable() ) {
@@ -677,10 +737,13 @@ END;
$command = "ALTER TABLE $table DROP CONSTRAINT $conname";
$this->db->query( $command );
} else {
- $this->output( "Column '$table.$field' does not have a foreign key constraint, will be added\n" );
+ $this->output( "Column '$table.$field' does not have a foreign key " .
+ "constraint, will be added\n" );
$conclause = "";
}
- $command = "ALTER TABLE $table ADD $conclause FOREIGN KEY ($field) REFERENCES $clause DEFERRABLE INITIALLY DEFERRED";
+ $command =
+ "ALTER TABLE $table ADD $conclause " .
+ "FOREIGN KEY ($field) REFERENCES $clause DEFERRABLE INITIALLY DEFERRED";
$this->db->query( $command );
}
@@ -694,7 +757,11 @@ END;
$this->output( "Dropping rule 'archive_delete'\n" );
$this->db->query( 'DROP RULE archive_delete ON archive' );
}
- $this->applyPatch( 'patch-remove-archive2.sql', false, "Converting 'archive2' back to normal archive table" );
+ $this->applyPatch(
+ 'patch-remove-archive2.sql',
+ false,
+ "Converting 'archive2' back to normal archive table"
+ );
} else {
$this->output( "...obsolete table 'archive2' does not exist\n" );
}
@@ -704,7 +771,8 @@ END;
if ( $this->db->fieldInfo( 'oldimage', 'oi_deleted' )->type() !== 'smallint' ) {
$this->output( "Changing 'oldimage.oi_deleted' to type 'smallint'\n" );
$this->db->query( "ALTER TABLE oldimage ALTER oi_deleted DROP DEFAULT" );
- $this->db->query( "ALTER TABLE oldimage ALTER oi_deleted TYPE SMALLINT USING (oi_deleted::smallint)" );
+ $this->db->query(
+ "ALTER TABLE oldimage ALTER oi_deleted TYPE SMALLINT USING (oi_deleted::smallint)" );
$this->db->query( "ALTER TABLE oldimage ALTER oi_deleted SET DEFAULT 0" );
} else {
$this->output( "...column 'oldimage.oi_deleted' is already of type 'smallint'\n" );
@@ -713,23 +781,32 @@ END;
protected function checkOiNameConstraint() {
if ( $this->db->hasConstraint( "oldimage_oi_name_fkey_cascaded" ) ) {
- $this->output( "...table 'oldimage' has correct cascading delete/update foreign key to image\n" );
+ $this->output( "...table 'oldimage' has correct cascading delete/update " .
+ "foreign key to image\n" );
} else {
if ( $this->db->hasConstraint( "oldimage_oi_name_fkey" ) ) {
- $this->db->query( "ALTER TABLE oldimage DROP CONSTRAINT oldimage_oi_name_fkey" );
+ $this->db->query(
+ "ALTER TABLE oldimage DROP CONSTRAINT oldimage_oi_name_fkey" );
}
if ( $this->db->hasConstraint( "oldimage_oi_name_fkey_cascade" ) ) {
- $this->db->query( "ALTER TABLE oldimage DROP CONSTRAINT oldimage_oi_name_fkey_cascade" );
+ $this->db->query(
+ "ALTER TABLE oldimage DROP CONSTRAINT oldimage_oi_name_fkey_cascade" );
}
$this->output( "Making foreign key on table 'oldimage' (to image) a cascade delete/update\n" );
- $this->db->query( "ALTER TABLE oldimage ADD CONSTRAINT oldimage_oi_name_fkey_cascaded " .
- "FOREIGN KEY (oi_name) REFERENCES image(img_name) ON DELETE CASCADE ON UPDATE CASCADE" );
+ $this->db->query(
+ "ALTER TABLE oldimage ADD CONSTRAINT oldimage_oi_name_fkey_cascaded " .
+ "FOREIGN KEY (oi_name) REFERENCES image(img_name) " .
+ "ON DELETE CASCADE ON UPDATE CASCADE" );
}
}
protected function checkPageDeletedTrigger() {
if ( !$this->db->triggerExists( 'page', 'page_deleted' ) ) {
- $this->applyPatch( 'patch-page_deleted.sql', false, "Adding function and trigger 'page_deleted' to table 'page'" );
+ $this->applyPatch(
+ 'patch-page_deleted.sql',
+ false,
+ "Adding function and trigger 'page_deleted' to table 'page'"
+ );
} else {
$this->output( "...table 'page' has 'page_deleted' trigger\n" );
}
@@ -738,7 +815,7 @@ END;
protected function dropIndex( $table, $index, $patch = '', $fullpath = false ) {
if ( $this->db->indexExists( $table, $index ) ) {
$this->output( "Dropping obsolete index '$index'\n" );
- $this->db->query( "DROP INDEX \"". $index ."\"" );
+ $this->db->query( "DROP INDEX \"" . $index . "\"" );
}
}
@@ -746,7 +823,7 @@ END;
$pu = $this->db->indexAttributes( $index );
if ( !empty( $pu ) && $pu != $should_be ) {
$this->output( "Dropping obsolete version of index '$index'\n" );
- $this->db->query( "DROP INDEX \"". $index ."\"" );
+ $this->db->query( "DROP INDEX \"" . $index . "\"" );
$pu = array();
} else {
$this->output( "...no need to drop index '$index'\n" );
@@ -764,13 +841,21 @@ END;
if ( $this->fkeyDeltype( 'revision_rev_user_fkey' ) == 'r' ) {
$this->output( "...constraint 'revision_rev_user_fkey' is ON DELETE RESTRICT\n" );
} else {
- $this->applyPatch( 'patch-revision_rev_user_fkey.sql', false, "Changing constraint 'revision_rev_user_fkey' to ON DELETE RESTRICT" );
+ $this->applyPatch(
+ 'patch-revision_rev_user_fkey.sql',
+ false,
+ "Changing constraint 'revision_rev_user_fkey' to ON DELETE RESTRICT"
+ );
}
}
protected function checkIwlPrefix() {
if ( $this->db->indexExists( 'iwlinks', 'iwl_prefix' ) ) {
- $this->applyPatch( 'patch-rename-iwl_prefix.sql', false, "Replacing index 'iwl_prefix' with 'iwl_prefix_from_title'" );
+ $this->applyPatch(
+ 'patch-rename-iwl_prefix.sql',
+ false,
+ "Replacing index 'iwl_prefix' with 'iwl_prefix_title_from'"
+ );
}
}
diff --git a/includes/installer/SqliteInstaller.php b/includes/installer/SqliteInstaller.php
index 68df6ab2..19218c60 100644
--- a/includes/installer/SqliteInstaller.php
+++ b/includes/installer/SqliteInstaller.php
@@ -60,9 +60,10 @@ class SqliteInstaller extends DatabaseInstaller {
$result->fatal( 'config-outdated-sqlite', $db->getServerVersion(), self::MINIMUM_VERSION );
}
// Check for FTS3 full-text search module
- if( DatabaseSqlite::getFulltextSearchModule() != 'FTS3' ) {
+ if ( DatabaseSqlite::getFulltextSearchModule() != 'FTS3' ) {
$result->warning( 'config-no-fts3' );
}
+
return $result;
}
@@ -73,6 +74,7 @@ class SqliteInstaller extends DatabaseInstaller {
DIRECTORY_SEPARATOR,
dirname( $_SERVER['DOCUMENT_ROOT'] ) . '/data'
);
+
return array( 'wgSQLiteDataDir' => $path );
} else {
return array();
@@ -80,8 +82,17 @@ class SqliteInstaller extends DatabaseInstaller {
}
public function getConnectForm() {
- return $this->getTextBox( 'wgSQLiteDataDir', 'config-sqlite-dir', array(), $this->parent->getHelpBox( 'config-sqlite-dir-help' ) ) .
- $this->getTextBox( 'wgDBname', 'config-db-name', array(), $this->parent->getHelpBox( 'config-sqlite-name-help' ) );
+ return $this->getTextBox(
+ 'wgSQLiteDataDir',
+ 'config-sqlite-dir', array(),
+ $this->parent->getHelpBox( 'config-sqlite-dir-help' )
+ ) .
+ $this->getTextBox(
+ 'wgDBname',
+ 'config-db-name',
+ array(),
+ $this->parent->getHelpBox( 'config-sqlite-name-help' )
+ );
}
/**
@@ -96,6 +107,7 @@ class SqliteInstaller extends DatabaseInstaller {
if ( !$result ) {
return $path;
}
+
return $result;
}
@@ -115,6 +127,7 @@ class SqliteInstaller extends DatabaseInstaller {
}
# Table prefix is not used on SQLite, keep it empty
$this->setVar( 'wgDBprefix', '' );
+
return $result;
}
@@ -128,9 +141,16 @@ class SqliteInstaller extends DatabaseInstaller {
if ( !is_writable( dirname( $dir ) ) ) {
$webserverGroup = Installer::maybeGetWebserverPrimaryGroup();
if ( $webserverGroup !== null ) {
- return Status::newFatal( 'config-sqlite-parent-unwritable-group', $dir, dirname( $dir ), basename( $dir ), $webserverGroup );
+ return Status::newFatal(
+ 'config-sqlite-parent-unwritable-group',
+ $dir, dirname( $dir ), basename( $dir ),
+ $webserverGroup
+ );
} else {
- return Status::newFatal( 'config-sqlite-parent-unwritable-nogroup', $dir, dirname( $dir ), basename( $dir ) );
+ return Status::newFatal(
+ 'config-sqlite-parent-unwritable-nogroup',
+ $dir, dirname( $dir ), basename( $dir )
+ );
}
}
@@ -173,6 +193,7 @@ class SqliteInstaller extends DatabaseInstaller {
} catch ( DBConnectionError $e ) {
$status->fatal( 'config-sqlite-connection-error', $e->getMessage() );
}
+
return $status;
}
@@ -220,6 +241,7 @@ class SqliteInstaller extends DatabaseInstaller {
$this->setVar( 'wgDBuser', '' );
$this->setVar( 'wgDBpassword', '' );
$this->setupSchemaVars();
+
return $this->getConnection();
}
@@ -228,6 +250,7 @@ class SqliteInstaller extends DatabaseInstaller {
*/
public function createTables() {
$status = parent::createTables();
+
return $this->setupSearchIndex( $status );
}
@@ -246,6 +269,7 @@ class SqliteInstaller extends DatabaseInstaller {
} elseif ( !$fts3tTable && $module == 'FTS3' ) {
$this->db->sourceFile( "$IP/maintenance/sqlite/archives/searchindex-fts3.sql" );
}
+
return $status;
}
@@ -254,8 +278,8 @@ class SqliteInstaller extends DatabaseInstaller {
*/
public function getLocalSettings() {
$dir = LocalSettingsGenerator::escapePhpString( $this->getVar( 'wgSQLiteDataDir' ) );
- return
-"# SQLite-specific settings
+
+ return "# SQLite-specific settings
\$wgSQLiteDataDir = \"{$dir}\";";
}
}
diff --git a/includes/installer/SqliteUpdater.php b/includes/installer/SqliteUpdater.php
index 2064842a..78ca5110 100644
--- a/includes/installer/SqliteUpdater.php
+++ b/includes/installer/SqliteUpdater.php
@@ -31,95 +31,100 @@ 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( 'addField', 'site_stats', 'ss_active_users', 'patch-ss_active_users.sql' ),
array( 'doActiveUsersInit' ),
- array( 'addField', 'ipblocks', 'ipb_allow_usertalk', 'patch-ipb_allow_usertalk.sql' ),
+ array( 'addField', 'ipblocks', 'ipb_allow_usertalk', 'patch-ipb_allow_usertalk.sql' ),
array( 'sqliteInitialIndexes' ),
// 1.15
- 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', 'change_tag', 'patch-change_tag.sql' ),
+ array( 'addTable', 'tag_summary', 'patch-tag_summary.sql' ),
+ array( 'addTable', 'valid_tag', 'patch-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( 'doLogUsertextPopulation' ), # listed separately from the previous update because 1.16 was released without this update
+ 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' ),
+ # listed separately from the previous update because 1.16 was released without this update
+ array( 'doLogUsertextPopulation' ),
array( 'doLogSearchPopulation' ),
- 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' ),
+ array( 'addTable', 'l10n_cache', 'patch-l10n_cache.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' ),
array( 'doUpdateTranscacheField' ),
array( 'sqliteSetupSearchindex' ),
// 1.17
- array( 'addTable', 'iwlinks', 'patch-iwlinks.sql' ),
- array( 'addIndex', 'iwlinks', 'iwl_prefix_title_from', 'patch-rename-iwl_prefix.sql' ),
- array( 'addField', 'updatelog', 'ul_value', 'patch-ul_value.sql' ),
- array( 'addField', 'interwiki', 'iw_api', 'patch-iw_api_and_wikiid.sql' ),
- array( 'dropIndex', 'iwlinks', 'iwl_prefix', 'patch-kill-iwl_prefix.sql' ),
- array( 'dropIndex', 'iwlinks', 'iwl_prefix_from_title', 'patch-kill-iwl_pft.sql' ),
- array( 'addField', 'categorylinks', 'cl_collation', 'patch-categorylinks-better-collation.sql' ),
+ array( 'addTable', 'iwlinks', 'patch-iwlinks.sql' ),
+ array( 'addIndex', 'iwlinks', 'iwl_prefix_title_from', 'patch-rename-iwl_prefix.sql' ),
+ array( 'addField', 'updatelog', 'ul_value', 'patch-ul_value.sql' ),
+ array( 'addField', 'interwiki', 'iw_api', 'patch-iw_api_and_wikiid.sql' ),
+ array( 'dropIndex', 'iwlinks', 'iwl_prefix', 'patch-kill-iwl_prefix.sql' ),
+ array( 'addField', 'categorylinks', 'cl_collation', 'patch-categorylinks-better-collation.sql' ),
array( 'doCollationUpdate' ),
- array( 'addTable', 'msg_resource', 'patch-msg_resource.sql' ),
- array( 'addTable', 'module_deps', 'patch-module_deps.sql' ),
- array( 'dropIndex', 'archive', 'ar_page_revid', 'patch-archive_kill_ar_page_revid.sql' ),
- array( 'addIndex', 'archive', 'ar_revid', 'patch-archive_ar_revid.sql' ),
+ array( 'addTable', 'msg_resource', 'patch-msg_resource.sql' ),
+ array( 'addTable', 'module_deps', 'patch-module_deps.sql' ),
+ array( 'dropIndex', 'archive', 'ar_page_revid', 'patch-archive_kill_ar_page_revid.sql' ),
+ array( 'addIndex', 'archive', 'ar_revid', 'patch-archive_ar_revid.sql' ),
// 1.18
- array( 'addIndex', 'user', 'user_email', 'patch-user_email_index.sql' ),
- array( 'addTable', 'uploadstash', 'patch-uploadstash.sql' ),
- array( 'addTable', 'user_former_groups', 'patch-user_former_groups.sql'),
+ array( 'addIndex', 'user', 'user_email', 'patch-user_email_index.sql' ),
+ array( 'addTable', 'uploadstash', 'patch-uploadstash.sql' ),
+ array( 'addTable', 'user_former_groups', 'patch-user_former_groups.sql' ),
// 1.19
- array( 'addIndex', 'logging', 'type_action', 'patch-logging-type-action-index.sql'),
+ array( 'addIndex', 'logging', 'type_action', 'patch-logging-type-action-index.sql' ),
array( 'doMigrateUserOptions' ),
- 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' ),
- array( 'addIndex', 'page', 'page_redirect_namespace_len', 'patch-page_redirect_namespace_len.sql' ),
- array( 'addField', 'uploadstash', 'us_chunk_inx', 'patch-uploadstash_chunk.sql' ),
- array( 'addfield', 'job', 'job_timestamp', 'patch-jobs-add-timestamp.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' ),
+ array( 'addIndex', 'page', 'page_redirect_namespace_len',
+ 'patch-page_redirect_namespace_len.sql' ),
+ array( 'addField', 'uploadstash', 'us_chunk_inx', 'patch-uploadstash_chunk.sql' ),
+ array( 'addfield', 'job', 'job_timestamp', 'patch-jobs-add-timestamp.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' ),
+ 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( '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( '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( '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( '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( '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' ),
+ array( 'addIndex', 'iwlinks', 'iwl_prefix_from_title', 'patch-iwlinks-from-title-index.sql' ),
+ array( 'addField', 'archive', 'ar_id', 'patch-archive-ar_id.sql' ),
+ array( 'addField', 'externallinks', 'el_id', 'patch-externallinks-el_id.sql' ),
);
}
protected function sqliteInitialIndexes() {
- // initial-indexes.sql fails if the indexes are already present, so we perform a quick check if our database is newer.
- if ( $this->updateRowExists( 'initial_indexes' ) || $this->db->indexExists( 'user', 'user_name', __METHOD__ ) ) {
+ // initial-indexes.sql fails if the indexes are already present,
+ // so we perform a quick check if our database is newer.
+ if ( $this->updateRowExists( 'initial_indexes' ) ||
+ $this->db->indexExists( 'user', 'user_name', __METHOD__ )
+ ) {
$this->output( "...have initial indexes\n" );
+
return;
}
$this->applyPatch( 'initial-indexes.sql', false, "Adding initial indexes" );
@@ -129,7 +134,11 @@ class SqliteUpdater extends DatabaseUpdater {
$module = DatabaseSqlite::getFulltextSearchModule();
$fts3tTable = $this->updateRowExists( 'fts3' );
if ( $fts3tTable && !$module ) {
- $this->applyPatch( 'searchindex-no-fts.sql', false, 'PHP is missing FTS3 support, downgrading tables' );
+ $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" );
} else {
@@ -139,7 +148,7 @@ class SqliteUpdater extends DatabaseUpdater {
protected function doEnableProfiling() {
global $wgProfileToDatabase;
- if ( $wgProfileToDatabase === true && ! $this->db->tableExists( 'profiling', __METHOD__ ) ) {
+ 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 35d649b2..95765259 100644
--- a/includes/installer/WebInstaller.php
+++ b/includes/installer/WebInstaller.php
@@ -154,9 +154,9 @@ class WebInstaller extends Installer {
$this->exportVars();
$this->setupLanguage();
- if( ( $this->getVar( '_InstallDone' ) || $this->getVar( '_UpgradeDone' ) )
- && $this->request->getVal( 'localsettings' ) )
- {
+ if ( ( $this->getVar( '_InstallDone' ) || $this->getVar( '_UpgradeDone' ) )
+ && $this->request->getVal( 'localsettings' )
+ ) {
$this->request->response()->header( 'Content-type: application/x-httpd-php' );
$this->request->response()->header(
'Content-Disposition: attachment; filename="LocalSettings.php"'
@@ -164,18 +164,20 @@ class WebInstaller extends Installer {
$ls = InstallerOverrides::getLocalSettingsGenerator( $this );
$rightsProfile = $this->rightsProfiles[$this->getVar( '_RightsProfile' )];
- foreach( $rightsProfile as $group => $rightsArr ) {
+ foreach ( $rightsProfile as $group => $rightsArr ) {
$ls->setGroupRights( $group, $rightsArr );
}
echo $ls->getText();
+
return $this->session;
}
$cssDir = $this->request->getVal( 'css' );
- if( $cssDir ) {
+ if ( $cssDir ) {
$cssDir = ( $cssDir == 'rtl' ? 'rtl' : 'ltr' );
$this->request->response()->header( 'Content-type: text/css' );
echo $this->output->getCSS( $cssDir );
+
return $this->session;
}
@@ -199,6 +201,7 @@ class WebInstaller extends Installer {
$this->output->useShortHeader();
$this->output->allowFrames();
$page->submitCC();
+
return $this->finish();
}
@@ -207,6 +210,7 @@ class WebInstaller extends Installer {
$this->output->useShortHeader();
$this->output->allowFrames();
$this->output->addHTML( $page->getCCDoneBox() );
+
return $this->finish();
}
@@ -250,12 +254,13 @@ class WebInstaller extends Installer {
do {
$nextPageId--;
$nextPage = $this->pageSequence[$nextPageId];
- } while( isset( $this->skippedPages[$nextPage] ) );
+ } while ( isset( $this->skippedPages[$nextPage] ) );
} else {
$nextPage = $this->pageSequence[$lowestUnhappy];
}
$this->output->redirect( $this->getUrl( array( 'page' => $nextPage ) ) );
+
return $this->finish();
}
@@ -263,7 +268,7 @@ class WebInstaller extends Installer {
$this->currentPageName = $page->getName();
$this->startPageWrapper( $pageName );
- if( $page->isSlow() ) {
+ if ( $page->isSlow() ) {
$this->disableTimeLimit();
}
@@ -324,7 +329,7 @@ class WebInstaller extends Installer {
* @return bool
*/
public function startSession() {
- if( wfIniGetBool( 'session.auto_start' ) || session_id() ) {
+ if ( wfIniGetBool( 'session.auto_start' ) || session_id() ) {
// Done already
return true;
}
@@ -336,6 +341,7 @@ class WebInstaller extends Installer {
if ( $this->phpErrors ) {
$this->showError( 'config-session-error', $this->phpErrors[0] );
+
return false;
}
@@ -353,7 +359,7 @@ class WebInstaller extends Installer {
public function getFingerprint() {
// Get the base URL of the installation
$url = $this->request->getFullRequestURL();
- if ( preg_match( '!^(.*\?)!', $url, $m) ) {
+ if ( preg_match( '!^(.*\?)!', $url, $m ) ) {
// Trim query string
$url = $m[1];
}
@@ -362,6 +368,7 @@ class WebInstaller extends Installer {
// the /mw-config/index.php. Kinda scary though?
$url = $m[1];
}
+
return md5( serialize( array(
'local path' => dirname( __DIR__ ),
'url' => $url,
@@ -516,7 +523,7 @@ class WebInstaller extends Installer {
/**
* Called by execute() before page output starts, to show a page list.
*
- * @param $currentPageName String
+ * @param $currentPageName string
*/
private function startPageWrapper( $currentPageName ) {
$s = "<div class=\"config-page-wrapper\">\n";
@@ -539,7 +546,14 @@ class WebInstaller extends Installer {
$s .= "</ul><br/><ul>\n";
$s .= $this->getPageListItem( 'Restart', true, $currentPageName );
- $s .= "</ul></div>\n"; // end list pane
+ // End list pane
+ $s .= "</ul></div>\n";
+
+ // Messages:
+ // 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
$s .= Html::element( 'h2', array(),
wfMessage( 'config-page-' . strtolower( $currentPageName ) )->text() );
@@ -549,14 +563,20 @@ class WebInstaller extends Installer {
/**
* Get a list item for the page list.
*
- * @param $pageName String
- * @param $enabled Boolean
- * @param $currentPageName String
+ * @param $pageName string
+ * @param $enabled boolean
+ * @param $currentPageName string
*
* @return string
*/
private function getPageListItem( $pageName, $enabled, $currentPageName ) {
$s = "<li class=\"config-page-list-item\">";
+
+ // Messages:
+ // 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
$name = wfMessage( 'config-page-' . strtolower( $pageName ) )->text();
if ( $enabled ) {
@@ -601,9 +621,9 @@ class WebInstaller extends Installer {
*/
private function endPageWrapper() {
$this->output->addHTMLNoFlush(
- "<div class=\"visualClear\"></div>\n" .
- "</div>\n" .
- "<div class=\"visualClear\"></div>\n" .
+ "<div class=\"visualClear\"></div>\n" .
+ "</div>\n" .
+ "<div class=\"visualClear\"></div>\n" .
"</div>" );
}
@@ -640,8 +660,11 @@ class WebInstaller extends Installer {
*/
public function getInfoBox( $text, $icon = false, $class = false ) {
$text = $this->parse( $text, true );
- $icon = ( $icon == false ) ? '../skins/common/images/info-32.png' : '../skins/common/images/'.$icon;
+ $icon = ( $icon == false ) ?
+ '../skins/common/images/info-32.png' :
+ '../skins/common/images/' . $icon;
$alt = wfMessage( 'config-information' )->text();
+
return Html::infoBox( $text, $icon, $alt, $class, false );
}
@@ -686,7 +709,7 @@ class WebInstaller extends Installer {
$args = func_get_args();
array_shift( $args );
$html = '<div class="config-message">' .
- $this->parse( wfMessage( $msg, $args )->useDatabase( false )->plain() ) .
+ $this->parse( wfMessage( $msg, $args )->useDatabase( false )->plain() ) .
"</div>\n";
$this->output->addHTML( $html );
}
@@ -724,16 +747,16 @@ class WebInstaller extends Installer {
$attributes['for'] = $forId;
}
- return
- "<div class=\"config-block\">\n" .
+ return "<div class=\"config-block\">\n" .
" <div class=\"config-block-label\">\n" .
Xml::tags( 'label',
$attributes,
- $labelText ) . "\n" .
- $helpData .
+ $labelText
+ ) . "\n" .
+ $helpData .
" </div>\n" .
" <div class=\"config-block-elements\">\n" .
- $contents .
+ $contents .
" </div>\n" .
"</div>\n";
}
@@ -743,12 +766,12 @@ class WebInstaller extends Installer {
*
* @param $params Array
* Parameters are:
- * var: The variable to be configured (required)
- * label: The message name for the label (required)
- * attribs: Additional attributes for the input element (optional)
+ * var: The variable to be configured (required)
+ * label: The message name for the label (required)
+ * attribs: Additional attributes for the input element (optional)
* controlName: The name for the input element (optional)
- * value: The current value of the variable (optional)
- * help: The html for the help text (optional)
+ * value: The current value of the variable (optional)
+ * help: The html for the help text (optional)
*
* @return string
*/
@@ -767,22 +790,22 @@ class WebInstaller extends Installer {
if ( !isset( $params['help'] ) ) {
$params['help'] = "";
}
- return
- $this->label(
- $params['label'],
+
+ return $this->label(
+ $params['label'],
+ $params['controlName'],
+ Xml::input(
$params['controlName'],
- Xml::input(
- $params['controlName'],
- 30, // intended to be overridden by CSS
- $params['value'],
- $params['attribs'] + array(
- 'id' => $params['controlName'],
- 'class' => 'config-input-text',
- 'tabindex' => $this->nextTabIndex()
- )
- ),
- $params['help']
- );
+ 30, // intended to be overridden by CSS
+ $params['value'],
+ $params['attribs'] + array(
+ 'id' => $params['controlName'],
+ 'class' => 'config-input-text',
+ 'tabindex' => $this->nextTabIndex()
+ )
+ ),
+ $params['help']
+ );
}
/**
@@ -790,12 +813,12 @@ class WebInstaller extends Installer {
*
* @param $params Array
* Parameters are:
- * var: The variable to be configured (required)
- * label: The message name for the label (required)
- * attribs: Additional attributes for the input element (optional)
+ * var: The variable to be configured (required)
+ * label: The message name for the label (required)
+ * attribs: Additional attributes for the input element (optional)
* controlName: The name for the input element (optional)
- * value: The current value of the variable (optional)
- * help: The html for the help text (optional)
+ * value: The current value of the variable (optional)
+ * help: The html for the help text (optional)
*
* @return string
*/
@@ -814,23 +837,23 @@ class WebInstaller extends Installer {
if ( !isset( $params['help'] ) ) {
$params['help'] = "";
}
- return
- $this->label(
- $params['label'],
+
+ return $this->label(
+ $params['label'],
+ $params['controlName'],
+ Xml::textarea(
$params['controlName'],
- Xml::textarea(
- $params['controlName'],
- $params['value'],
- 30,
- 5,
- $params['attribs'] + array(
- 'id' => $params['controlName'],
- 'class' => 'config-input-text',
- 'tabindex' => $this->nextTabIndex()
- )
- ),
- $params['help']
- );
+ $params['value'],
+ 30,
+ 5,
+ $params['attribs'] + array(
+ 'id' => $params['controlName'],
+ 'class' => 'config-input-text',
+ 'tabindex' => $this->nextTabIndex()
+ )
+ ),
+ $params['help']
+ );
}
/**
@@ -839,12 +862,12 @@ class WebInstaller extends Installer {
* Implements password hiding
* @param $params Array
* Parameters are:
- * var: The variable to be configured (required)
- * label: The message name for the label (required)
- * attribs: Additional attributes for the input element (optional)
+ * var: The variable to be configured (required)
+ * label: The message name for the label (required)
+ * attribs: Additional attributes for the input element (optional)
* controlName: The name for the input element (optional)
- * value: The current value of the variable (optional)
- * help: The html for the help text (optional)
+ * value: The current value of the variable (optional)
+ * help: The html for the help text (optional)
*
* @return string
*/
@@ -868,12 +891,12 @@ class WebInstaller extends Installer {
*
* @param $params Array
* Parameters are:
- * var: The variable to be configured (required)
- * label: The message name for the label (required)
- * attribs: Additional attributes for the input element (optional)
+ * var: The variable to be configured (required)
+ * label: The message name for the label (required)
+ * attribs: Additional attributes for the input element (optional)
* controlName: The name for the input element (optional)
- * value: The current value of the variable (optional)
- * help: The html for the help text (optional)
+ * value: The current value of the variable (optional)
+ * help: The html for the help text (optional)
*
* @return string
*/
@@ -892,14 +915,15 @@ class WebInstaller extends Installer {
if ( !isset( $params['help'] ) ) {
$params['help'] = "";
}
- if( isset( $params['rawtext'] ) ) {
+ if ( isset( $params['rawtext'] ) ) {
$labelText = $params['rawtext'];
- } else {
+ } else if ( isset( $params['label'] ) ) {
$labelText = $this->parse( wfMessage( $params['label'] )->text() );
+ } else {
+ $labelText = "";
}
- return
- "<div class=\"config-input-check\">\n" .
+ return "<div class=\"config-input-check\">\n" .
$params['help'] .
"<label>\n" .
Xml::check(
@@ -920,15 +944,15 @@ class WebInstaller extends Installer {
*
* @param $params Array
* Parameters are:
- * var: The variable to be configured (required)
- * label: The message name for the label (required)
+ * var: The variable to be configured (required)
+ * label: The message name for the label (required)
* itemLabelPrefix: The message name prefix for the item labels (required)
- * values: List of allowed values (required)
- * itemAttribs Array of attribute arrays, outer key is the value name (optional)
- * commonAttribs Attribute array applied to all items
- * controlName: The name for the input element (optional)
- * value: The current value of the variable (optional)
- * help: The html for the help text (optional)
+ * values: List of allowed values (required)
+ * itemAttribs: Array of attribute arrays, outer key is the value name (optional)
+ * commonAttribs: Attribute array applied to all items
+ * controlName: The name for the input element (optional)
+ * value: The current value of the variable (optional)
+ * help: The html for the help text (optional)
*
* @return string
*/
@@ -987,10 +1011,10 @@ class WebInstaller extends Installer {
* @param $status Status
*/
public function showStatusBox( $status ) {
- if( !$status->isGood() ) {
+ if ( !$status->isGood() ) {
$text = $status->getWikiText();
- if( $status->isOk() ) {
+ if ( $status->isOk() ) {
$box = $this->getWarningBox( $text );
} else {
$box = $this->getErrorBox( $text );
@@ -1058,6 +1082,7 @@ class WebInstaller extends Installer {
*/
public function docLink( $linkText, $attribs, $parser ) {
$url = $this->getDocUrl( $attribs['href'] );
+
return '<a href="' . htmlspecialchars( $url ) . '">' .
htmlspecialchars( $linkText ) .
'</a>';
@@ -1071,7 +1096,7 @@ class WebInstaller extends Installer {
* @param $parser
* @return String Html for download link
*/
- public function downloadLinkHook( $text, $attribs, $parser ) {
+ public function downloadLinkHook( $text, $attribs, $parser ) {
$img = Html::element( 'img', array(
'src' => '../skins/common/images/download-32.png',
'width' => '32',
@@ -1080,6 +1105,7 @@ class WebInstaller extends Installer {
$anchor = Html::rawElement( 'a',
array( 'href' => $this->getURL( array( 'localsettings' => 1 ) ) ),
$img . ' ' . wfMessage( 'config-download-localsettings' )->parse() );
+
return Html::rawElement( 'div', array( 'class' => 'config-download-link' ), $anchor );
}
@@ -1101,8 +1127,10 @@ class WebInstaller extends Installer {
$this->setVar( 'wgScriptPath', $uri );
} else {
$this->showError( 'config-no-uri' );
+
return false;
}
+
return parent::envCheckPath();
}
diff --git a/includes/installer/WebInstallerOutput.php b/includes/installer/WebInstallerOutput.php
index d61d843f..f2dc37fe 100644
--- a/includes/installer/WebInstallerOutput.php
+++ b/includes/installer/WebInstallerOutput.php
@@ -104,49 +104,85 @@ class WebInstallerOutput {
/**
* Get the raw vector CSS, flipping if needed
+ *
+ * @todo Possibly get rid of this function and use ResourceLoader in the manner it was
+ * designed to be used in, rather than just grabbing a list of filenames from it,
+ * and not properly handling such details as media types in module definitions.
+ *
* @param string $dir 'ltr' or 'rtl'
* @return String
*/
public function getCSS( $dir ) {
- $skinDir = dirname( dirname( __DIR__ ) ) . '/skins';
-
- // All these files will be concatenated in sequence and loaded
- // as one file.
- // The string 'images/' in the files' contents will be replaced
- // by '../skins/$skinName/images/', where $skinName is what appears
- // before the last '/' in each of the strings.
- $cssFileNames = array(
-
- // Basically the "skins.vector" ResourceLoader module styles
- 'common/shared.css',
- 'common/commonElements.css',
- 'common/commonContent.css',
- 'common/commonInterface.css',
- 'vector/screen.css',
-
- // mw-config specific
- 'common/config.css',
+ // All CSS files these modules reference will be concatenated in sequence
+ // and loaded as one file.
+ $moduleNames = array(
+ 'mediawiki.legacy.shared',
+ 'skins.vector',
+ 'mediawiki.legacy.config',
);
+ $prepend = '';
$css = '';
- wfSuppressWarnings();
- foreach ( $cssFileNames as $cssFileName ) {
- $fullCssFileName = "$skinDir/$cssFileName";
- $cssFileContents = file_get_contents( $fullCssFileName );
- if ( $cssFileContents ) {
- preg_match( "/^(\w+)\//", $cssFileName, $match );
- $skinName = $match[1];
- $css .= str_replace( 'images/', "../skins/$skinName/images/", $cssFileContents );
- } else {
- $css .= "/** Your webserver cannot read $fullCssFileName. Please check file permissions. */";
+ $cssFileNames = array();
+ $resourceLoader = new ResourceLoader();
+ foreach ( $moduleNames as $moduleName ) {
+ $module = $resourceLoader->getModule( $moduleName );
+ $cssFileNames = $module->getAllStyleFiles();
+
+ wfSuppressWarnings();
+ foreach ( $cssFileNames as $cssFileName ) {
+ if ( !file_exists( $cssFileName ) ) {
+ $prepend .= ResourceLoader::makeComment( "Unable to find $cssFileName." );
+ continue;
+ }
+
+ if ( !is_readable( $cssFileName ) ) {
+ $prepend .= ResourceLoader::makeComment( "Unable to read $cssFileName. Please check file permissions." );
+ continue;
+ }
+
+ try {
+
+ if ( preg_match( '/\.less$/', $cssFileName ) ) {
+ // Run the LESS compiler for *.less files (bug 55589)
+ $compiler = ResourceLoader::getLessCompiler();
+ $cssFileContents = $compiler->compileFile( $cssFileName );
+ } else {
+ // Regular CSS file
+ $cssFileContents = file_get_contents( $cssFileName );
+ }
+
+ if ( $cssFileContents ) {
+ // Rewrite URLs, though don't bother embedding images. While static image
+ // files may be cached, CSS returned by this function is definitely not.
+ $cssDirName = dirname( $cssFileName );
+ $css .= CSSMin::remap(
+ /* source */ $cssFileContents,
+ /* local */ $cssDirName,
+ /* remote */ '..' . str_replace(
+ array( $GLOBALS['IP'], DIRECTORY_SEPARATOR ),
+ array( '', '/' ),
+ $cssDirName
+ ),
+ /* embedData */ false
+ );
+ } else {
+ $prepend .= ResourceLoader::makeComment( "Unable to read $cssFileName." );
+ }
+
+ } catch ( Exception $e ) {
+ $prepend .= ResourceLoader::formatException( $e );
+ }
+
+ $css .= "\n";
}
-
- $css .= "\n";
+ wfRestoreWarnings();
}
- wfRestoreWarnings();
- if( $dir == 'rtl' ) {
+ $css = $prepend . $css;
+
+ if ( $dir == 'rtl' ) {
$css = CSSJanus::transform( $css, true );
}
@@ -185,6 +221,7 @@ class WebInstallerOutput {
*/
public function getDir() {
global $wgLang;
+
return is_object( $wgLang ) ? $wgLang->getDir() : 'ltr';
}
@@ -193,6 +230,7 @@ class WebInstallerOutput {
*/
public function getLanguageCode() {
global $wgLang;
+
return is_object( $wgLang ) ? $wgLang->getCode() : 'en';
}
@@ -216,22 +254,23 @@ class WebInstallerOutput {
public function outputHeader() {
$this->headerDone = true;
- $dbTypes = $this->parent->getDBTypes();
-
$this->parent->request->response()->header( 'Content-Type: text/html; charset=utf-8' );
+
if ( !$this->allowFrames ) {
$this->parent->request->response()->header( 'X-Frame-Options: DENY' );
}
+
if ( $this->redirectTarget ) {
- $this->parent->request->response()->header( 'Location: '.$this->redirectTarget );
+ $this->parent->request->response()->header( 'Location: ' . $this->redirectTarget );
+
return;
}
if ( $this->useShortHeader ) {
$this->outputShortHeader();
+
return;
}
-
?>
<?php echo Html::htmlHeader( $this->getHeadAttribs() ); ?>
<head>
@@ -239,7 +278,6 @@ 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 $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 06d16a5a..7e8a3631 100644
--- a/includes/installer/WebInstallerPage.php
+++ b/includes/installer/WebInstallerPage.php
@@ -74,6 +74,10 @@ abstract class WebInstallerPage {
);
}
+ /**
+ * @param string|bool $continue
+ * @param string|bool $back
+ */
public function endForm( $continue = 'continue', $back = 'back' ) {
$s = "<div class=\"config-submit\">\n";
$id = $this->getId();
@@ -84,25 +88,36 @@ abstract class WebInstallerPage {
if ( $continue ) {
// Fake submit button for enter keypress (bug 26267)
- $s .= Xml::submitButton( wfMessage( "config-$continue" )->text(),
- array( 'name' => "enter-$continue", 'style' =>
- 'visibility:hidden;overflow:hidden;width:1px;margin:0' ) ) . "\n";
+ // Messages: config-continue, config-restart, config-regenerate
+ $s .= Xml::submitButton(
+ wfMessage( "config-$continue" )->text(),
+ array(
+ 'name' => "enter-$continue",
+ 'style' => 'visibility:hidden;overflow:hidden;width:1px;margin:0'
+ )
+ ) . "\n";
}
if ( $back ) {
- $s .= Xml::submitButton( wfMessage( "config-$back" )->text(),
+ // Message: config-back
+ $s .= Xml::submitButton(
+ wfMessage( "config-$back" )->text(),
array(
'name' => "submit-$back",
'tabindex' => $this->parent->nextTabIndex()
- ) ) . "\n";
+ )
+ ) . "\n";
}
if ( $continue ) {
- $s .= Xml::submitButton( wfMessage( "config-$continue" )->text(),
+ // Messages: config-continue, config-restart, config-regenerate
+ $s .= Xml::submitButton(
+ wfMessage( "config-$continue" )->text(),
array(
'name' => "submit-$continue",
'tabindex' => $this->parent->nextTabIndex(),
- ) ) . "\n";
+ )
+ ) . "\n";
}
$s .= "</div></form></div>\n";
@@ -204,6 +219,7 @@ class WebInstaller_Language extends WebInstallerPage {
if ( isset( $languages[$contLang] ) ) {
$this->setVar( 'wgLanguageCode', $contLang );
}
+
return 'continue';
}
} elseif ( $this->parent->showSessionWarning ) {
@@ -251,13 +267,15 @@ class WebInstaller_Language extends WebInstallerPage {
$languages = Language::fetchLanguageNames();
ksort( $languages );
foreach ( $languages as $code => $lang ) {
- if ( isset( $wgDummyLanguageCodes[$code] ) ) continue;
+ if ( isset( $wgDummyLanguageCodes[$code] ) ) {
+ continue;
+ }
$s .= "\n" . Xml::option( "$code - $lang", $code, $code == $selectedCode );
}
$s .= "\n</select>\n";
+
return $this->parent->label( $label, $name, $s );
}
-
}
class WebInstaller_ExistingWiki extends WebInstallerPage {
@@ -271,8 +289,8 @@ class WebInstaller_ExistingWiki extends WebInstallerPage {
// Check if the upgrade key supplied to the user has appeared in LocalSettings.php
if ( $vars['wgUpgradeKey'] !== false
&& $this->getVar( '_UpgradeKeySupplied' )
- && $this->getVar( 'wgUpgradeKey' ) === $vars['wgUpgradeKey'] )
- {
+ && $this->getVar( 'wgUpgradeKey' ) === $vars['wgUpgradeKey']
+ ) {
// It's there, so the user is authorized
$status = $this->handleExistingUpgrade( $vars );
if ( $status->isOK() ) {
@@ -281,6 +299,7 @@ class WebInstaller_ExistingWiki extends WebInstallerPage {
$this->startForm();
$this->parent->showStatusBox( $status );
$this->endForm( 'continue' );
+
return 'output';
}
}
@@ -299,6 +318,7 @@ class WebInstaller_ExistingWiki extends WebInstallerPage {
$this->getVar( 'wgUpgradeKey' ) . "';</pre>" )->plain()
) );
$this->endForm( 'continue' );
+
return 'output';
}
@@ -307,9 +327,10 @@ class WebInstaller_ExistingWiki extends WebInstallerPage {
$r = $this->parent->request;
if ( $r->wasPosted() ) {
$key = $r->getText( 'config_wgUpgradeKey' );
- if( !$key || $key !== $vars['wgUpgradeKey'] ) {
+ if ( !$key || $key !== $vars['wgUpgradeKey'] ) {
$this->parent->showError( 'config-localsettings-badkey' );
$this->showKeyForm();
+
return 'output';
}
// Key was OK
@@ -319,10 +340,12 @@ class WebInstaller_ExistingWiki extends WebInstallerPage {
} else {
$this->parent->showStatusBox( $status );
$this->showKeyForm();
+
return 'output';
}
} else {
$this->showKeyForm();
+
return 'output';
}
}
@@ -333,7 +356,7 @@ class WebInstaller_ExistingWiki extends WebInstallerPage {
protected function showKeyForm() {
$this->startForm();
$this->addHTML(
- $this->parent->getInfoBox( wfMessage( 'config-localsettings-upgrade' )->plain() ).
+ $this->parent->getInfoBox( wfMessage( 'config-localsettings-upgrade' )->plain() ) .
'<br />' .
$this->parent->getTextBox( array(
'var' => 'wgUpgradeKey',
@@ -352,6 +375,7 @@ class WebInstaller_ExistingWiki extends WebInstallerPage {
}
$this->setVar( $name, $vars[$name] );
}
+
return $status;
}
@@ -363,7 +387,8 @@ class WebInstaller_ExistingWiki extends WebInstallerPage {
protected function handleExistingUpgrade( $vars ) {
// Check $wgDBtype
if ( !isset( $vars['wgDBtype'] ) ||
- !in_array( $vars['wgDBtype'], Installer::getDBTypes() ) ) {
+ !in_array( $vars['wgDBtype'], Installer::getDBTypes() )
+ ) {
return Status::newFatal( 'config-localsettings-connection-error', '' );
}
@@ -393,11 +418,13 @@ class WebInstaller_ExistingWiki extends WebInstallerPage {
// Adjust the error message to explain things correctly
$status->replaceMessage( 'config-connection-error',
'config-localsettings-connection-error' );
+
return $status;
}
// All good
$this->setVar( '_ExistingDBSettings', true );
+
return $status;
}
}
@@ -422,9 +449,9 @@ class WebInstaller_Welcome extends WebInstallerPage {
} else {
$this->parent->showStatusMessage( $status );
}
+
return '';
}
-
}
class WebInstaller_DBConnect extends WebInstallerPage {
@@ -440,6 +467,7 @@ class WebInstaller_DBConnect extends WebInstallerPage {
if ( $status->isGood() ) {
$this->setVar( '_UpgradeDone', false );
+
return 'continue';
} else {
$this->parent->showStatusBox( $status );
@@ -452,8 +480,10 @@ class WebInstaller_DBConnect extends WebInstallerPage {
$settings = '';
$defaultType = $this->getVar( 'wgDBtype' );
+ // Messages: config-support-mysql, config-support-postgres, config-support-oracle,
+ // config-support-sqlite
$dbSupport = '';
- foreach( $this->parent->getDBTypes() as $type ) {
+ foreach ( $this->parent->getDBTypes() as $type ) {
$link = DatabaseBase::factory( $type )->getSoftwareLink();
$dbSupport .= wfMessage( "config-support-$type", $link )->plain() . "\n";
}
@@ -481,20 +511,23 @@ class WebInstaller_DBConnect extends WebInstallerPage {
) .
"</li>\n";
- $settings .=
- Html::openElement( 'div', array( 'id' => 'DB_wrapper_' . $type,
- 'class' => 'dbWrapper' ) ) .
+ // Messages: config-header-mysql, config-header-postgres, config-header-oracle,
+ // config-header-sqlite
+ $settings .= Html::openElement(
+ 'div',
+ array(
+ 'id' => 'DB_wrapper_' . $type,
+ 'class' => 'dbWrapper'
+ )
+ ) .
Html::element( 'h3', array(), wfMessage( 'config-header-' . $type )->text() ) .
$installer->getConnectForm() .
"</div>\n";
}
- $types .= "</ul><br style=\"clear: left\"/>\n";
- $this->addHTML(
- $this->parent->label( 'config-db-type', false, $types ) .
- $settings
- );
+ $types .= "</ul><br style=\"clear: left\"/>\n";
+ $this->addHTML( $this->parent->label( 'config-db-type', false, $types ) . $settings );
$this->endForm();
}
@@ -509,9 +542,9 @@ class WebInstaller_DBConnect extends WebInstallerPage {
if ( !$installer ) {
return Status::newFatal( 'config-invalid-db-type' );
}
+
return $installer->submitConnectForm();
}
-
}
class WebInstaller_Upgrade extends WebInstallerPage {
@@ -532,6 +565,7 @@ class WebInstaller_Upgrade extends WebInstallerPage {
// Show the done message again
// Make them click back again if they want to do the upgrade again
$this->showDoneMessage();
+
return 'output';
}
}
@@ -555,11 +589,12 @@ class WebInstaller_Upgrade extends WebInstallerPage {
if ( $result ) {
// If they're going to possibly regenerate LocalSettings, we
// need to create the upgrade/secret keys. Bug 26481
- if( !$this->getVar( '_ExistingDBSettings' ) ) {
+ if ( !$this->getVar( '_ExistingDBSettings' ) ) {
$this->parent->generateKeys();
}
$this->setVar( '_UpgradeDone', true );
$this->showDoneMessage();
+
return 'output';
}
}
@@ -583,15 +618,14 @@ class WebInstaller_Upgrade extends WebInstallerPage {
$this->parent->getInfoBox(
wfMessage( $msg,
$this->getVar( 'wgServer' ) .
- $this->getVar( 'wgScriptPath' ) . '/index' .
- $this->getVar( 'wgScriptExtension' )
+ $this->getVar( 'wgScriptPath' ) . '/index' .
+ $this->getVar( 'wgScriptExtension' )
)->plain(), 'tick-32.png'
)
);
$this->parent->restoreLinkPopups();
$this->endForm( $regenerate ? 'regenerate' : false, false );
}
-
}
class WebInstaller_DBSettings extends WebInstallerPage {
@@ -620,7 +654,6 @@ class WebInstaller_DBSettings extends WebInstallerPage {
$this->addHTML( $form );
$this->endForm();
}
-
}
class WebInstaller_Name extends WebInstallerPage {
@@ -657,6 +690,9 @@ class WebInstaller_Name extends WebInstallerPage {
'label' => 'config-site-name',
'help' => $this->parent->getHelpBox( 'config-site-name-help' )
) ) .
+ // getRadioSet() builds a set of labeled radio buttons.
+ // For grep: The following messages are used as the item labels:
+ // config-ns-site-name, config-ns-generic, config-ns-other
$this->parent->getRadioSet( array(
'var' => '_NamespaceType',
'label' => 'config-project-namespace',
@@ -668,9 +704,8 @@ class WebInstaller_Name extends WebInstallerPage {
) ) .
$this->parent->getTextBox( array(
'var' => 'wgMetaNamespace',
- 'label' => '', //TODO: Needs a label?
- 'attribs' => array( 'readonly' => 'readonly', 'class' => 'enabledByOther' ),
-
+ 'label' => '', // @todo Needs a label?
+ 'attribs' => array( 'readonly' => 'readonly', 'class' => 'enabledByOther' )
) ) .
$this->getFieldSetStart( 'config-admin-box' ) .
$this->parent->getTextBox( array(
@@ -698,6 +733,9 @@ class WebInstaller_Name extends WebInstallerPage {
) ) .
$this->getFieldSetEnd() .
$this->parent->getInfoBox( wfMessage( 'config-almost-done' )->text() ) .
+ // getRadioSet() builds a set of labeled radio buttons.
+ // For grep: The following messages are used as the item labels:
+ // config-optional-continue, config-optional-skip
$this->parent->getRadioSet( array(
'var' => '_SkipOptional',
'itemLabelPrefix' => 'config-optional-',
@@ -709,6 +747,7 @@ class WebInstaller_Name extends WebInstallerPage {
$this->setVar( 'wgMetaNamespace', $metaNS );
$this->endForm();
+
return 'output';
}
@@ -761,7 +800,7 @@ class WebInstaller_Name extends WebInstallerPage {
// Make sure it won't conflict with any existing namespaces
global $wgContLang;
$nsIndex = $wgContLang->getNsIndex( $name );
- if( $nsIndex !== false && $nsIndex !== NS_PROJECT ) {
+ if ( $nsIndex !== false && $nsIndex !== NS_PROJECT ) {
$this->parent->showError( 'config-ns-conflict', $name );
$retVal = false;
}
@@ -809,24 +848,22 @@ class WebInstaller_Name extends WebInstallerPage {
// Validate e-mail if provided
$email = $this->getVar( '_AdminEmail' );
- if( $email && !Sanitizer::validateEmail( $email ) ) {
+ if ( $email && !Sanitizer::validateEmail( $email ) ) {
$this->parent->showError( 'config-admin-error-bademail' );
$retVal = false;
}
// If they asked to subscribe to mediawiki-announce but didn't give
// an e-mail, show an error. Bug 29332
- if( !$email && $this->getVar( '_Subscribe' ) ) {
+ if ( !$email && $this->getVar( '_Subscribe' ) ) {
$this->parent->showError( 'config-subscribe-noemail' );
$retVal = false;
}
return $retVal;
}
-
}
class WebInstaller_Options extends WebInstallerPage {
-
public function execute() {
if ( $this->getVar( '_SkipOptional' ) == 'skip' ) {
return 'skip';
@@ -841,6 +878,9 @@ class WebInstaller_Options extends WebInstallerPage {
$this->startForm();
$this->addHTML(
# User Rights
+ // getRadioSet() builds a set of labeled radio buttons.
+ // For grep: The following messages are used as the item labels:
+ // config-profile-wiki, config-profile-no-anon, config-profile-fishbowl, config-profile-private
$this->parent->getRadioSet( array(
'var' => '_RightsProfile',
'label' => 'config-profile',
@@ -850,6 +890,11 @@ class WebInstaller_Options extends WebInstallerPage {
$this->parent->getInfoBox( wfMessage( 'config-profile-help' )->plain() ) .
# Licensing
+ // getRadioSet() builds a set of labeled radio buttons.
+ // For grep: The following messages are used as the item labels:
+ // config-license-cc-by, config-license-cc-by-sa, config-license-cc-by-nc-sa,
+ // config-license-cc-0, config-license-pd, config-license-gfdl,
+ // config-license-none, config-license-cc-choose
$this->parent->getRadioSet( array(
'var' => '_LicenseCode',
'label' => 'config-license',
@@ -900,18 +945,27 @@ class WebInstaller_Options extends WebInstallerPage {
$extensions = $this->parent->findExtensions();
- if( $extensions ) {
+ if ( $extensions ) {
$extHtml = $this->getFieldSetStart( 'config-extensions' );
- foreach( $extensions as $ext ) {
+ /* Force a recache, so we load extensions descriptions */
+ global $wgLang;
+ $lc = Language::getLocalisationCache();
+ $lc->initialisedLangs = array();
+ $lc->getItem( $wgLang->mCode, '' );
+ LinkCache::singleton()->useDatabase( false );
+
+ foreach ( $extensions as $ext ) {
$extHtml .= $this->parent->getCheckBox( array(
- 'var' => "ext-$ext",
- 'rawtext' => $ext,
- ) );
+ 'var' => "ext-{$ext['name']}",
+ 'rawtext' => "<b>{$ext['name']}</b>: " .
+ wfMessage( $ext['descriptionmsg'] )->useDatabase( false )->parse(),
+ ) );
+
}
$extHtml .= $this->parent->getHelpBox( 'config-extensions-help' ) .
- $this->getFieldSetEnd();
+ $this->getFieldSetEnd();
$this->addHTML( $extHtml );
}
@@ -963,7 +1017,7 @@ class WebInstaller_Options extends WebInstallerPage {
);
$caches = array( 'none' );
- if( count( $this->getVar( '_Caches' ) ) ) {
+ if ( count( $this->getVar( '_Caches' ) ) ) {
$caches[] = 'accel';
}
$caches[] = 'memcached';
@@ -976,11 +1030,14 @@ class WebInstaller_Options extends WebInstallerPage {
// or going back!
$cacheval = 'none';
}
- $hidden = ($cacheval == 'memcached') ? '' : 'display: none';
+ $hidden = ( $cacheval == 'memcached' ) ? '' : 'display: none';
$this->addHTML(
# Advanced settings
$this->getFieldSetStart( 'config-advanced-settings' ) .
# Object cache settings
+ // getRadioSet() builds a set of labeled radio buttons.
+ // For grep: The following messages are used as the item labels:
+ // config-cache-none, config-cache-accel, config-cache-memcached
$this->parent->getRadioSet( array(
'var' => 'wgMainCacheType',
'label' => 'config-cache-options',
@@ -1023,6 +1080,7 @@ class WebInstaller_Options extends WebInstallerPage {
'lang' => $this->getVar( '_UserLang' ),
'stylesheet' => $styleUrl,
) );
+
return $iframeUrl;
}
@@ -1040,10 +1098,9 @@ 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" .
+ return "<div class=\"config-cc-wrapper\" id=\"config-cc-wrapper\" style=\"$wrapperStyle\">\n" .
Html::element( 'iframe', $iframeAttribs, '', false /* not short */ ) .
"</div>\n";
}
@@ -1053,13 +1110,13 @@ class WebInstaller_Options extends WebInstallerPage {
// If you change this height, also change it in config.css
$expandJs = str_replace( '$1', '54em', $js );
$reduceJs = str_replace( '$1', '70px', $js );
- return
- '<p>'.
+
+ return '<p>' .
Html::element( 'img', array( 'src' => $this->getVar( 'wgRightsIcon' ) ) ) .
'&#160;&#160;' .
htmlspecialchars( $this->getVar( 'wgRightsText' ) ) .
"</p>\n" .
- "<p style=\"text-align: center\">" .
+ "<p style=\"text-align: center;\">" .
Html::element( 'a',
array(
'href' => $this->getCCPartnerUrl(),
@@ -1068,7 +1125,7 @@ class WebInstaller_Options extends WebInstallerPage {
wfMessage( 'config-cc-again' )->text()
) .
"</p>\n" .
- "<script type=\"text/javascript\">\n" .
+ "<script>\n" .
# Reduce the wrapper div height
htmlspecialchars( $reduceJs ) .
"\n" .
@@ -1080,6 +1137,7 @@ class WebInstaller_Options extends WebInstallerPage {
array( 'wgRightsUrl', 'wgRightsText', 'wgRightsIcon' ) );
if ( count( $newValues ) != 3 ) {
$this->parent->showError( 'config-cc-error' );
+
return;
}
$this->setVar( '_CCDone', true );
@@ -1094,8 +1152,8 @@ class WebInstaller_Options extends WebInstallerPage {
'wgUseInstantCommons' ) );
if ( !in_array( $this->getVar( '_RightsProfile' ),
- array_keys( $this->parent->rightsProfiles ) ) )
- {
+ array_keys( $this->parent->rightsProfiles ) )
+ ) {
reset( $this->parent->rightsProfiles );
$this->setVar( '_RightsProfile', key( $this->parent->rightsProfiles ) );
}
@@ -1104,9 +1162,14 @@ class WebInstaller_Options extends WebInstallerPage {
if ( $code == 'cc-choose' ) {
if ( !$this->getVar( '_CCDone' ) ) {
$this->parent->showError( 'config-cc-not-chosen' );
+
return false;
}
} elseif ( in_array( $code, array_keys( $this->parent->licenses ) ) ) {
+ // Messages:
+ // config-license-cc-by, config-license-cc-by-sa, config-license-cc-by-nc-sa,
+ // config-license-cc-0, config-license-pd, config-license-gfdl, config-license-none,
+ // config-license-cc-choose
$entry = $this->parent->licenses[$code];
if ( isset( $entry['text'] ) ) {
$this->setVar( 'wgRightsText', $entry['text'] );
@@ -1121,41 +1184,47 @@ class WebInstaller_Options extends WebInstallerPage {
$this->setVar( 'wgRightsIcon', '' );
}
- $extsAvailable = $this->parent->findExtensions();
+ $extsAvailable = array_map( function($e) { if( isset($e['name']) ) return $e['name']; }, $this->parent->findExtensions() );
$extsToInstall = array();
- foreach( $extsAvailable as $ext ) {
- if( $this->parent->request->getCheck( 'config_ext-' . $ext ) ) {
- $extsToInstall[] = $ext;
+ foreach ( $extsAvailable as $key => $ext ) {
+ var_dump("config_ext-$ext");
+ if ( $this->parent->request->getCheck( 'config_ext-' . $ext ) ) {
+ $extsToInstall[] = $extsAvailable[ $key ];
}
}
$this->parent->setVar( '_Extensions', $extsToInstall );
- if( $this->getVar( 'wgMainCacheType' ) == 'memcached' ) {
+ if ( $this->getVar( 'wgMainCacheType' ) == 'memcached' ) {
$memcServers = explode( "\n", $this->getVar( '_MemCachedServers' ) );
- if( !$memcServers ) {
+ if ( !$memcServers ) {
$this->parent->showError( 'config-memcache-needservers' );
+
return false;
}
- foreach( $memcServers as $server ) {
+ foreach ( $memcServers as $server ) {
$memcParts = explode( ":", $server, 2 );
if ( !isset( $memcParts[0] )
- || ( !IP::isValid( $memcParts[0] )
- && ( gethostbyname( $memcParts[0] ) == $memcParts[0] ) ) ) {
+ || ( !IP::isValid( $memcParts[0] )
+ && ( gethostbyname( $memcParts[0] ) == $memcParts[0] ) )
+ ) {
$this->parent->showError( 'config-memcache-badip', $memcParts[0] );
+
return false;
- } elseif( !isset( $memcParts[1] ) ) {
+ } elseif ( !isset( $memcParts[1] ) ) {
$this->parent->showError( 'config-memcache-noport', $memcParts[0] );
+
return false;
- } elseif( $memcParts[1] < 1 || $memcParts[1] > 65535 ) {
+ } elseif ( $memcParts[1] < 1 || $memcParts[1] > 65535 ) {
$this->parent->showError( 'config-memcache-badport', 1, 65535 );
+
return false;
}
}
}
+
return true;
}
-
}
class WebInstaller_Install extends WebInstallerPage {
@@ -1164,11 +1233,11 @@ class WebInstaller_Install extends WebInstallerPage {
}
public function execute() {
- if( $this->getVar( '_UpgradeDone' ) ) {
+ if ( $this->getVar( '_UpgradeDone' ) ) {
return 'skip';
- } elseif( $this->getVar( '_InstallDone' ) ) {
+ } elseif ( $this->getVar( '_InstallDone' ) ) {
return 'continue';
- } elseif( $this->parent->request->wasPosted() ) {
+ } elseif ( $this->parent->request->wasPosted() ) {
$this->startForm();
$this->addHTML( "<ul>" );
$results = $this->parent->performInstallation(
@@ -1187,11 +1256,16 @@ class WebInstaller_Install extends WebInstallerPage {
$this->addHTML( $this->parent->getInfoBox( wfMessage( 'config-install-begin' )->plain() ) );
$this->endForm();
}
+
return true;
}
public function startStage( $step ) {
- $this->addHTML( "<li>" . wfMessage( "config-install-$step" )->escaped() . wfMessage( 'ellipsis' )->escaped() );
+ // Messages: config-install-database, config-install-tables, config-install-interwiki,
+ // config-install-stats, config-install-keys, config-install-sysop, config-install-mainpage
+ $this->addHTML( "<li>" . wfMessage( "config-install-$step" )->escaped() .
+ wfMessage( 'ellipsis' )->escaped() );
+
if ( $step == 'extension-tables' ) {
$this->startLiveBox();
}
@@ -1211,25 +1285,23 @@ class WebInstaller_Install extends WebInstallerPage {
$html = "<span class=\"error\">$html</span>";
}
$this->addHTML( $html . "</li>\n" );
- if( !$status->isGood() ) {
+ if ( !$status->isGood() ) {
$this->parent->showStatusBox( $status );
}
}
-
}
class WebInstaller_Complete extends WebInstallerPage {
-
public function execute() {
// Pop up a dialog box, to make it difficult for the user to forget
// to download the file
$lsUrl = $this->getVar( 'wgServer' ) . $this->parent->getURL( array( 'localsettings' => 1 ) );
if ( isset( $_SERVER['HTTP_USER_AGENT'] ) &&
- strpos( $_SERVER['HTTP_USER_AGENT'], 'MSIE' ) !== false ) {
- // JS appears the only method that works consistently with IE7+
- $this->addHtml( "\n<script type=\"" . $GLOBALS['wgJsMimeType'] .
- '">jQuery( document ).ready( function() { document.location=' .
- Xml::encodeJsVar( $lsUrl) . "; } );</script>\n" );
+ strpos( $_SERVER['HTTP_USER_AGENT'], 'MSIE' ) !== false
+ ) {
+ // JS appears to be the only method that works consistently with IE7+
+ $this->addHtml( "\n<script>jQuery( function () { document.location = " .
+ Xml::encodeJsVar( $lsUrl ) . "; } );</script>\n" );
} else {
$this->parent->request->response()->header( "Refresh: 0;url=$lsUrl" );
}
@@ -1241,12 +1313,15 @@ class WebInstaller_Complete extends WebInstallerPage {
wfMessage( 'config-install-done',
$lsUrl,
$this->getVar( 'wgServer' ) .
- $this->getVar( 'wgScriptPath' ) . '/index' .
- $this->getVar( 'wgScriptExtension' ),
+ $this->getVar( 'wgScriptPath' ) . '/index' .
+ $this->getVar( 'wgScriptExtension' ),
'<downloadlink/>'
)->plain(), 'tick-32.png'
)
);
+ $this->addHTML( $this->parent->getInfoBox(
+ wfMessage( 'config-extension-link' )->text() ) );
+
$this->parent->restoreLinkPopups();
$this->endForm( false, false );
}
@@ -1261,6 +1336,7 @@ class WebInstaller_Restart extends WebInstallerPage {
if ( $really ) {
$this->parent->reset();
}
+
return 'continue';
}
@@ -1269,14 +1345,13 @@ class WebInstaller_Restart extends WebInstallerPage {
$this->addHTML( $s );
$this->endForm( 'restart' );
}
-
}
abstract class WebInstaller_Document extends WebInstallerPage {
abstract protected function getFileName();
- public function execute() {
+ public function execute() {
$text = $this->getFileContents();
$text = InstallDocFormatter::format( $text );
$this->parent->output->addWikiText( $text );
@@ -1286,23 +1361,25 @@ abstract class WebInstaller_Document extends WebInstallerPage {
public function getFileContents() {
$file = __DIR__ . '/../../' . $this->getFileName();
- if( ! file_exists( $file ) ) {
+ if ( !file_exists( $file ) ) {
return wfMessage( 'config-nofile', $file )->plain();
}
+
return file_get_contents( $file );
}
-
}
class WebInstaller_Readme extends WebInstaller_Document {
- protected function getFileName() { return 'README'; }
+ protected function getFileName() {
+ return 'README';
+ }
}
class WebInstaller_ReleaseNotes extends WebInstaller_Document {
protected function getFileName() {
global $wgVersion;
- if( !preg_match( '/^(\d+)\.(\d+).*/i', $wgVersion, $result ) ) {
+ if ( !preg_match( '/^(\d+)\.(\d+).*/i', $wgVersion, $result ) ) {
throw new MWException( 'Variable $wgVersion has an invalid value.' );
}
@@ -1311,9 +1388,13 @@ class WebInstaller_ReleaseNotes extends WebInstaller_Document {
}
class WebInstaller_UpgradeDoc extends WebInstaller_Document {
- protected function getFileName() { return 'UPGRADE'; }
+ protected function getFileName() {
+ return 'UPGRADE';
+ }
}
class WebInstaller_Copying extends WebInstaller_Document {
- protected function getFileName() { return 'COPYING'; }
+ protected function getFileName() {
+ return 'COPYING';
+ }
}
diff --git a/includes/job/Job.php b/includes/job/Job.php
index bcf582e7..ab7df5d2 100644
--- a/includes/job/Job.php
+++ b/includes/job/Job.php
@@ -68,7 +68,7 @@ abstract class Job {
*/
public static function factory( $command, Title $title, $params = false, $id = 0 ) {
global $wgJobClasses;
- if( isset( $wgJobClasses[$command] ) ) {
+ if ( isset( $wgJobClasses[$command] ) ) {
$class = $wgJobClasses[$command];
return new $class( $title, $params, $id );
}
@@ -84,7 +84,7 @@ abstract class Job {
*
* @param array $jobs of Job objects
* @return bool
- * @deprecated 1.21
+ * @deprecated since 1.21
*/
public static function batchInsert( $jobs ) {
return JobQueueGroup::singleton()->push( $jobs );
@@ -99,10 +99,10 @@ abstract class Job {
*
* @param array $jobs of Job objects
* @return bool
- * @deprecated 1.21
+ * @deprecated since 1.21
*/
public static function safeBatchInsert( $jobs ) {
- return JobQueueGroup::singleton()->push( $jobs, JobQueue::QoS_Atomic );
+ return JobQueueGroup::singleton()->push( $jobs, JobQueue::QOS_ATOMIC );
}
/**
@@ -112,7 +112,7 @@ abstract class Job {
*
* @param $type string
* @return Job|bool Returns false if there are no jobs
- * @deprecated 1.21
+ * @deprecated since 1.21
*/
public static function pop_type( $type ) {
return JobQueueGroup::singleton()->get( $type )->pop();
@@ -123,7 +123,7 @@ abstract class Job {
* This is subject to $wgJobTypesExcludedFromDefaultQueue.
*
* @return Job or false if there's no jobs
- * @deprecated 1.21
+ * @deprecated since 1.21
*/
public static function pop() {
return JobQueueGroup::singleton()->pop();
@@ -150,6 +150,7 @@ abstract class Job {
/**
* @return integer May be 0 for jobs stored outside the DB
+ * @deprecated since 1.22
*/
public function getId() {
return $this->id;
@@ -177,6 +178,16 @@ abstract class Job {
}
/**
+ * @return integer|null UNIX timestamp to delay running this job until, otherwise null
+ * @since 1.22
+ */
+ public function getReleaseTimestamp() {
+ return isset( $this->params['jobReleaseTimestamp'] )
+ ? wfTimestampOrNull( TS_UNIX, $this->params['jobReleaseTimestamp'] )
+ : null;
+ }
+
+ /**
* @return bool Whether only one of each identical set of jobs should be run
*/
public function ignoreDuplicates() {
@@ -185,34 +196,43 @@ abstract class Job {
/**
* @return bool Whether this job can be retried on failure by job runners
+ * @since 1.21
*/
public function allowRetries() {
return true;
}
/**
- * Subclasses may need to override this to make duplication detection work
+ * Subclasses may need to override this to make duplication detection work.
+ * The resulting map conveys everything that makes the job unique. This is
+ * only checked if ignoreDuplicates() returns true, meaning that duplicate
+ * jobs are supposed to be ignored.
*
* @return Array Map of key/values
+ * @since 1.21
*/
public function getDeduplicationInfo() {
$info = array(
- 'type' => $this->getType(),
+ 'type' => $this->getType(),
'namespace' => $this->getTitle()->getNamespace(),
- 'title' => $this->getTitle()->getDBkey(),
- 'params' => $this->getParams()
+ 'title' => $this->getTitle()->getDBkey(),
+ 'params' => $this->getParams()
);
- // Identical jobs with different "root" jobs should count as duplicates
if ( is_array( $info['params'] ) ) {
+ // Identical jobs with different "root" jobs should count as duplicates
unset( $info['params']['rootJobSignature'] );
unset( $info['params']['rootJobTimestamp'] );
+ // Likewise for jobs with different delay times
+ unset( $info['params']['jobReleaseTimestamp'] );
}
return $info;
}
/**
+ * @see JobQueue::deduplicateRootJob()
* @param string $key A key that identifies the task
* @return Array
+ * @since 1.21
*/
public static function newRootJobParams( $key ) {
return array(
@@ -222,7 +242,9 @@ abstract class Job {
}
/**
+ * @see JobQueue::deduplicateRootJob()
* @return Array
+ * @since 1.21
*/
public function getRootJobParams() {
return array(
@@ -236,9 +258,19 @@ abstract class Job {
}
/**
+ * @see JobQueue::deduplicateRootJob()
+ * @return bool
+ * @since 1.22
+ */
+ public function hasRootJobParams() {
+ return isset( $this->params['rootJobSignature'] )
+ && isset( $this->params['rootJobTimestamp'] );
+ }
+
+ /**
* Insert a single job into the queue.
* @return bool true on success
- * @deprecated 1.21
+ * @deprecated since 1.21
*/
public function insert() {
return JobQueueGroup::singleton()->push( $this );
diff --git a/includes/job/JobQueue.php b/includes/job/JobQueue.php
index b0dd9258..6556ee85 100644
--- a/includes/job/JobQueue.php
+++ b/includes/job/JobQueue.php
@@ -34,8 +34,14 @@ abstract class JobQueue {
protected $order; // string; job priority for pop()
protected $claimTTL; // integer; seconds
protected $maxTries; // integer; maximum number of times to try a job
+ protected $checkDelay; // boolean; allow delayed jobs
- const QoS_Atomic = 1; // integer; "all-or-nothing" job insertions
+ /** @var BagOStuff */
+ protected $dupCache;
+
+ const QOS_ATOMIC = 1; // integer; "all-or-nothing" job insertions
+
+ const ROOTJOB_TTL = 2419200; // integer; seconds to remember root jobs (28 days)
/**
* @param $params array
@@ -53,28 +59,37 @@ abstract class JobQueue {
if ( !in_array( $this->order, $this->supportedOrders() ) ) {
throw new MWException( __CLASS__ . " does not support '{$this->order}' order." );
}
+ $this->checkDelay = !empty( $params['checkDelay'] );
+ if ( $this->checkDelay && !$this->supportsDelayedJobs() ) {
+ throw new MWException( __CLASS__ . " does not support delayed jobs." );
+ }
+ $this->dupCache = wfGetCache( CACHE_ANYTHING );
}
/**
* 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.
+ * - 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.
+ * - checkDelay : If supported, respect Job::getReleaseTimestamp() in the push functions.
+ * This lets delayed jobs wait in a staging area until a given timestamp is
+ * reached, at which point they will enter the queue. If this is not enabled
+ * or not supported, an exception will be thrown on delayed job insertion.
*
* Queue classes should throw an exception if they do not support the options given.
*
@@ -84,7 +99,7 @@ abstract class JobQueue {
*/
final public static function factory( array $params ) {
$class = $params['class'];
- if ( !MWInit::classExists( $class ) ) {
+ if ( !class_exists( $class ) ) {
throw new MWException( "Invalid job queue class '$class'." );
}
$obj = new $class( $params );
@@ -109,24 +124,45 @@ abstract class JobQueue {
}
/**
- * @return string One of (random, timestamp, fifo)
+ * @return string One of (random, timestamp, fifo, undefined)
*/
final public function getOrder() {
return $this->order;
}
/**
- * @return Array Subset of (random, timestamp, fifo)
+ * @return bool Whether delayed jobs are enabled
+ * @since 1.22
+ */
+ final public function delayedJobsEnabled() {
+ return $this->checkDelay;
+ }
+
+ /**
+ * Get the allowed queue orders for configuration validation
+ *
+ * @return Array Subset of (random, timestamp, fifo, undefined)
*/
abstract protected function supportedOrders();
/**
- * @return string One of (random, timestamp, fifo)
+ * Get the default queue order to use if configuration does not specify one
+ *
+ * @return string One of (random, timestamp, fifo, undefined)
*/
abstract protected function optimalOrder();
/**
- * Quickly check if the queue is empty (has no available jobs).
+ * Find out if delayed jobs are supported for configuration validation
+ *
+ * @return boolean Whether delayed jobs are supported
+ */
+ protected function supportsDelayedJobs() {
+ return false; // not implemented
+ }
+
+ /**
+ * Quickly check if the queue has no available (unacquired, non-delayed) 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.
@@ -135,7 +171,7 @@ abstract class JobQueue {
* not distinguishable from the race condition between isEmpty() and pop().
*
* @return bool
- * @throws MWException
+ * @throws JobQueueError
*/
final public function isEmpty() {
wfProfileIn( __METHOD__ );
@@ -151,13 +187,13 @@ abstract class JobQueue {
abstract protected function doIsEmpty();
/**
- * Get the number of available (unacquired) jobs in the queue.
+ * Get the number of available (unacquired, non-delayed) 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
+ * @throws JobQueueError
*/
final public function getSize() {
wfProfileIn( __METHOD__ );
@@ -179,7 +215,7 @@ abstract class JobQueue {
* If caching is used, this number might be out of date for a minute.
*
* @return integer
- * @throws MWException
+ * @throws JobQueueError
*/
final public function getAcquiredCount() {
wfProfileIn( __METHOD__ );
@@ -195,14 +231,63 @@ abstract class JobQueue {
abstract protected function doGetAcquiredCount();
/**
+ * Get the number of delayed 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 JobQueueError
+ * @since 1.22
+ */
+ final public function getDelayedCount() {
+ wfProfileIn( __METHOD__ );
+ $res = $this->doGetDelayedCount();
+ wfProfileOut( __METHOD__ );
+ return $res;
+ }
+
+ /**
+ * @see JobQueue::getDelayedCount()
+ * @return integer
+ */
+ protected function doGetDelayedCount() {
+ return 0; // not implemented
+ }
+
+ /**
+ * Get the number of acquired jobs that can no longer be attempted.
+ * 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 JobQueueError
+ */
+ final public function getAbandonedCount() {
+ wfProfileIn( __METHOD__ );
+ $res = $this->doGetAbandonedCount();
+ wfProfileOut( __METHOD__ );
+ return $res;
+ }
+
+ /**
+ * @see JobQueue::getAbandonedCount()
+ * @return integer
+ */
+ protected function doGetAbandonedCount() {
+ return 0; // not implemented
+ }
+
+ /**
* 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)
+ * @param $flags integer Bitfield (supports JobQueue::QOS_ATOMIC)
* @return bool Returns false on failure
- * @throws MWException
+ * @throws JobQueueError
*/
final public function push( $jobs, $flags = 0 ) {
return $this->batchPush( is_array( $jobs ) ? $jobs : array( $jobs ), $flags );
@@ -214,9 +299,9 @@ abstract class JobQueue {
* Outside callers should use JobQueueGroup::push() instead of this function.
*
* @param array $jobs List of Jobs
- * @param $flags integer Bitfield (supports JobQueue::QoS_Atomic)
+ * @param $flags integer Bitfield (supports JobQueue::QOS_ATOMIC)
* @return bool Returns false on failure
- * @throws MWException
+ * @throws JobQueueError
*/
final public function batchPush( array $jobs, $flags = 0 ) {
if ( !count( $jobs ) ) {
@@ -225,7 +310,11 @@ abstract class JobQueue {
foreach ( $jobs as $job ) {
if ( $job->getType() !== $this->type ) {
- throw new MWException( "Got '{$job->getType()}' job; expected '{$this->type}'." );
+ throw new MWException(
+ "Got '{$job->getType()}' job; expected a '{$this->type}' job." );
+ } elseif ( $job->getReleaseTimestamp() && !$this->checkDelay ) {
+ throw new MWException(
+ "Got delayed '{$job->getType()}' job; delays are not supported." );
}
}
@@ -247,7 +336,7 @@ abstract class JobQueue {
* Outside callers should use JobQueueGroup::pop() instead of this function.
*
* @return Job|bool Returns false if there are no jobs
- * @throws MWException
+ * @throws JobQueueError
*/
final public function pop() {
global $wgJobClasses;
@@ -262,6 +351,15 @@ abstract class JobQueue {
wfProfileIn( __METHOD__ );
$job = $this->doPop();
wfProfileOut( __METHOD__ );
+
+ // Flag this job as an old duplicate based on its "root" job...
+ try {
+ if ( $job && $this->isRootJobOldDuplicate( $job ) ) {
+ JobQueue::incrStats( 'job-pop-duplicate', $this->type );
+ $job = DuplicateJob::newFromJob( $job ); // convert to a no-op
+ }
+ } catch ( MWException $e ) {} // don't lose jobs over this
+
return $job;
}
@@ -279,7 +377,7 @@ abstract class JobQueue {
*
* @param $job Job
* @return bool
- * @throws MWException
+ * @throws JobQueueError
*/
final public function ack( Job $job ) {
if ( $job->getType() !== $this->type ) {
@@ -326,7 +424,7 @@ abstract class JobQueue {
*
* @param $job Job
* @return bool
- * @throws MWException
+ * @throws JobQueueError
*/
final public function deduplicateRootJob( Job $job ) {
if ( $job->getType() !== $this->type ) {
@@ -344,7 +442,91 @@ abstract class JobQueue {
* @return bool
*/
protected function doDeduplicateRootJob( Job $job ) {
- return true;
+ if ( !$job->hasRootJobParams() ) {
+ throw new MWException( "Cannot register root job; missing parameters." );
+ }
+ $params = $job->getRootJobParams();
+
+ $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.
+ $timestamp = $this->dupCache->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 $this->dupCache->set( $key, $params['rootJobTimestamp'], JobQueueDB::ROOTJOB_TTL );
+ }
+
+ /**
+ * Check if the "root" job of a given job has been superseded by a newer one
+ *
+ * @param $job Job
+ * @return bool
+ * @throws JobQueueError
+ */
+ final protected function isRootJobOldDuplicate( Job $job ) {
+ if ( $job->getType() !== $this->type ) {
+ throw new MWException( "Got '{$job->getType()}' job; expected '{$this->type}'." );
+ }
+ wfProfileIn( __METHOD__ );
+ $isDuplicate = $this->doIsRootJobOldDuplicate( $job );
+ wfProfileOut( __METHOD__ );
+ return $isDuplicate;
+ }
+
+ /**
+ * @see JobQueue::isRootJobOldDuplicate()
+ * @param Job $job
+ * @return bool
+ */
+ protected function doIsRootJobOldDuplicate( Job $job ) {
+ if ( !$job->hasRootJobParams() ) {
+ return false; // job has no de-deplication info
+ }
+ $params = $job->getRootJobParams();
+
+ $key = $this->getRootJobCacheKey( $params['rootJobSignature'] );
+ // Get the last time this root job was enqueued
+ $timestamp = $this->dupCache->get( $key );
+
+ // Check if a new root job was started at the location after this one's...
+ return ( $timestamp && $timestamp > $params['rootJobTimestamp'] );
+ }
+
+ /**
+ * @param string $signature Hash identifier of the root job
+ * @return string
+ */
+ protected function getRootJobCacheKey( $signature ) {
+ list( $db, $prefix ) = wfSplitWikiID( $this->wiki );
+ return wfForeignMemcKey( $db, $prefix, 'jobqueue', $this->type, 'rootjob', $signature );
+ }
+
+ /**
+ * Deleted all unclaimed and delayed jobs from the queue
+ *
+ * @return bool Success
+ * @throws JobQueueError
+ * @since 1.22
+ */
+ final public function delete() {
+ wfProfileIn( __METHOD__ );
+ $res = $this->doDelete();
+ wfProfileOut( __METHOD__ );
+ return $res;
+ }
+
+ /**
+ * @see JobQueue::delete()
+ * @return bool Success
+ */
+ protected function doDelete() {
+ throw new MWException( "This method is not implemented." );
}
/**
@@ -353,7 +535,7 @@ abstract class JobQueue {
* This does nothing for certain queue classes.
*
* @return void
- * @throws MWException
+ * @throws JobQueueError
*/
final public function waitForBackups() {
wfProfileIn( __METHOD__ );
@@ -413,16 +595,98 @@ abstract class JobQueue {
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.
+ * Get an iterator to traverse over all available jobs in this queue.
+ * This does not include jobs that are currently acquired or delayed.
+ * Note: results may be stale if the queue is concurrently modified.
*
- * @return Iterator|Traversable|Array
- * @throws MWException
+ * @return Iterator
+ * @throws JobQueueError
*/
abstract public function getAllQueuedJobs();
/**
+ * Get an iterator to traverse over all delayed jobs in this queue.
+ * Note: results may be stale if the queue is concurrently modified.
+ *
+ * @return Iterator
+ * @throws JobQueueError
+ * @since 1.22
+ */
+ public function getAllDelayedJobs() {
+ return new ArrayIterator( array() ); // not implemented
+ }
+
+ /**
+ * Do not use this function outside of JobQueue/JobQueueGroup
+ *
+ * @return string
+ * @since 1.22
+ */
+ public function getCoalesceLocationInternal() {
+ return null;
+ }
+
+ /**
+ * Check whether each of the given queues are empty.
+ * This is used for batching checks for queues stored at the same place.
+ *
+ * @param array $types List of queues types
+ * @return array|null (list of non-empty queue types) or null if unsupported
+ * @throws MWException
+ * @since 1.22
+ */
+ final public function getSiblingQueuesWithJobs( array $types ) {
+ $section = new ProfileSection( __METHOD__ );
+ return $this->doGetSiblingQueuesWithJobs( $types );
+ }
+
+ /**
+ * @see JobQueue::getSiblingQueuesWithJobs()
+ * @param array $types List of queues types
+ * @return array|null (list of queue types) or null if unsupported
+ */
+ protected function doGetSiblingQueuesWithJobs( array $types ) {
+ return null; // not supported
+ }
+
+ /**
+ * Check the size of each of the given queues.
+ * For queues not served by the same store as this one, 0 is returned.
+ * This is used for batching checks for queues stored at the same place.
+ *
+ * @param array $types List of queues types
+ * @return array|null (job type => whether queue is empty) or null if unsupported
+ * @throws MWException
+ * @since 1.22
+ */
+ final public function getSiblingQueueSizes( array $types ) {
+ $section = new ProfileSection( __METHOD__ );
+ return $this->doGetSiblingQueueSizes( $types );
+ }
+
+ /**
+ * @see JobQueue::getSiblingQueuesSize()
+ * @param array $types List of queues types
+ * @return array|null (list of queue types) or null if unsupported
+ */
+ protected function doGetSiblingQueueSizes( array $types ) {
+ return null; // not supported
+ }
+
+ /**
+ * Call wfIncrStats() for the queue overall and for the queue type
+ *
+ * @param string $key Event type
+ * @param string $type Job type
+ * @param integer $delta
+ * @since 1.22
+ */
+ public static function incrStats( $key, $type, $delta = 1 ) {
+ wfIncrStats( $key, $delta );
+ wfIncrStats( "{$key}-{$type}", $delta );
+ }
+
+ /**
* Namespace the queue with a key to isolate it for testing
*
* @param $key string
@@ -433,3 +697,10 @@ abstract class JobQueue {
throw new MWException( "Queue namespacing not supported for this queue type." );
}
}
+
+/**
+ * @ingroup JobQueue
+ * @since 1.22
+ */
+class JobQueueError extends MWException {}
+class JobQueueConnectionError extends JobQueueError {}
diff --git a/includes/job/JobQueueDB.php b/includes/job/JobQueueDB.php
index ff7f7abc..c39083df 100644
--- a/includes/job/JobQueueDB.php
+++ b/includes/job/JobQueueDB.php
@@ -28,13 +28,15 @@
* @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
+ /** @var BagOStuff */
+ protected $cache;
+
protected $cluster = false; // string; name of an external DB cluster
/**
@@ -46,8 +48,13 @@ class JobQueueDB extends JobQueue {
* @param $params array
*/
protected function __construct( array $params ) {
+ global $wgMemc;
+
parent::__construct( $params );
+
$this->cluster = isset( $params['cluster'] ) ? $params['cluster'] : false;
+ // Make sure that we don't use the SQL cache, which would be harmful
+ $this->cache = ( $wgMemc instanceof SqlBagOStuff ) ? new EmptyBagOStuff() : $wgMemc;
}
protected function supportedOrders() {
@@ -63,22 +70,24 @@ class JobQueueDB extends JobQueue {
* @return bool
*/
protected function doIsEmpty() {
- global $wgMemc;
-
$key = $this->getCacheKey( 'empty' );
- $isEmpty = $wgMemc->get( $key );
+ $isEmpty = $this->cache->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 );
+ $dbr = $this->getSlaveDB();
+ try {
+ $found = $dbr->selectField( // unclaimed job
+ 'job', '1', array( 'job_cmd' => $this->type, 'job_token' => '' ), __METHOD__
+ );
+ } catch ( DBError $e ) {
+ $this->throwDBException( $e );
+ }
+ $this->cache->add( $key, $found ? 'false' : 'true', self::CACHE_TTL_LONG );
return !$found;
}
@@ -88,21 +97,23 @@ class JobQueueDB extends JobQueue {
* @return integer
*/
protected function doGetSize() {
- global $wgMemc;
-
$key = $this->getCacheKey( 'size' );
- $size = $wgMemc->get( $key );
+ $size = $this->cache->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 );
+ try {
+ $dbr = $this->getSlaveDB();
+ $size = (int)$dbr->selectField( 'job', 'COUNT(*)',
+ array( 'job_cmd' => $this->type, 'job_token' => '' ),
+ __METHOD__
+ );
+ } catch ( DBError $e ) {
+ $this->throwDBException( $e );
+ }
+ $this->cache->set( $key, $size, self::CACHE_TTL_SHORT );
return $size;
}
@@ -112,24 +123,63 @@ class JobQueueDB extends JobQueue {
* @return integer
*/
protected function doGetAcquiredCount() {
+ if ( $this->claimTTL <= 0 ) {
+ return 0; // no acknowledgements
+ }
+
+ $key = $this->getCacheKey( 'acquiredcount' );
+
+ $count = $this->cache->get( $key );
+ if ( is_int( $count ) ) {
+ return $count;
+ }
+
+ $dbr = $this->getSlaveDB();
+ try {
+ $count = (int)$dbr->selectField( 'job', 'COUNT(*)',
+ array( 'job_cmd' => $this->type, "job_token != {$dbr->addQuotes( '' )}" ),
+ __METHOD__
+ );
+ } catch ( DBError $e ) {
+ $this->throwDBException( $e );
+ }
+ $this->cache->set( $key, $count, self::CACHE_TTL_SHORT );
+
+ return $count;
+ }
+
+ /**
+ * @see JobQueue::doGetAbandonedCount()
+ * @return integer
+ * @throws MWException
+ */
+ protected function doGetAbandonedCount() {
global $wgMemc;
if ( $this->claimTTL <= 0 ) {
return 0; // no acknowledgements
}
- $key = $this->getCacheKey( 'acquiredcount' );
+ $key = $this->getCacheKey( 'abandonedcount' );
$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__
- );
+ $dbr = $this->getSlaveDB();
+ try {
+ $count = (int)$dbr->selectField( 'job', 'COUNT(*)',
+ array(
+ 'job_cmd' => $this->type,
+ "job_token != {$dbr->addQuotes( '' )}",
+ "job_attempts >= " . $dbr->addQuotes( $this->maxTries )
+ ),
+ __METHOD__
+ );
+ } catch ( DBError $e ) {
+ $this->throwDBException( $e );
+ }
$wgMemc->set( $key, $count, self::CACHE_TTL_SHORT );
return $count;
@@ -143,70 +193,84 @@ class JobQueueDB extends JobQueue {
* @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;
- }
+ $dbw = $this->getMasterDB();
+
+ $that = $this;
+ $method = __METHOD__;
+ $dbw->onTransactionIdle(
+ function() use ( $dbw, $that, $jobs, $flags, $method ) {
+ $that->doBatchPushInternal( $dbw, $jobs, $flags, $method );
}
+ );
- $key = $this->getCacheKey( 'empty' );
- $atomic = ( $flags & self::QoS_Atomic );
+ return true;
+ }
- $dbw->onTransactionIdle(
- function() use ( $dbw, $rowSet, $rowList, $atomic, $key, $scope
- ) {
- global $wgMemc;
+ /**
+ * This function should *not* be called outside of JobQueueDB
+ *
+ * @param DatabaseBase $dbw
+ * @param array $jobs
+ * @param int $flags
+ * @param string $method
+ * @return boolean
+ * @throws type
+ */
+ public function doBatchPushInternal( IDatabase $dbw, array $jobs, $flags, $method ) {
+ if ( !count( $jobs ) ) {
+ return true;
+ }
- 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__ );
- }
+ $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;
+ }
+ }
- $wgMemc->set( $key, 'false', JobQueueDB::CACHE_TTL_LONG );
- } );
+ if ( $flags & self::QOS_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.\n" );
+ 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 );
+ }
+ JobQueue::incrStats( 'job-insert', $this->type, count( $rows ) );
+ JobQueue::incrStats( 'job-insert-duplicate', $this->type,
+ count( $rowSet ) + count( $rowList ) - count( $rows ) );
+ } catch ( DBError $e ) {
+ if ( $flags & self::QOS_ATOMIC ) {
+ $dbw->rollback( $method );
+ }
+ throw $e;
}
+ if ( $flags & self::QOS_ATOMIC ) {
+ $dbw->commit( $method );
+ }
+
+ $this->cache->set( $this->getCacheKey( 'empty' ), 'false', JobQueueDB::CACHE_TTL_LONG );
return true;
}
@@ -216,54 +280,52 @@ class JobQueueDB extends JobQueue {
* @return Job|bool
*/
protected function doPop() {
- global $wgMemc;
-
- if ( $wgMemc->get( $this->getCacheKey( 'empty' ) ) === 'true' ) {
+ if ( $this->cache->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
- } );
+ $dbw = $this->getMasterDB();
+ try {
+ $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 );
+ $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 ) {
+ $this->cache->set( $this->getCacheKey( 'empty' ), 'true', self::CACHE_TTL_LONG );
+ break; // nothing to do
+ }
+ JobQueue::incrStats( 'job-pop', $this->type );
+ // 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__ );
+ wfDebug( "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->metadata['id'] = $row->job_id;
+ $job->id = $row->job_id; // XXX: work around broken subclasses
+ break; // done
+ } while ( true );
+ } catch ( DBError $e ) {
+ $this->throwDBException( $e );
+ }
return $job;
}
@@ -277,11 +339,9 @@ class JobQueueDB extends JobQueue {
* @return Row|false
*/
protected function claimRandom( $uuid, $rand, $gte ) {
- global $wgMemc;
-
- list( $dbw, $scope ) = $this->getMasterDB();
+ $dbw = $this->getMasterDB();
// Check cache to see if the queue has <= OFFSET items
- $tinyQueue = $wgMemc->get( $this->getCacheKey( 'small' ) );
+ $tinyQueue = $this->cache->get( $this->getCacheKey( 'small' ) );
$row = false; // the row acquired
$invertedDirection = false; // whether one job_random direction was already scanned
@@ -297,7 +357,7 @@ class JobQueueDB extends JobQueue {
$dir = $gte ? 'ASC' : 'DESC';
$row = $dbw->selectRow( 'job', '*', // find a random job
array(
- 'job_cmd' => $this->type,
+ 'job_cmd' => $this->type,
'job_token' => '', // unclaimed
"job_random {$ineq} {$dbw->addQuotes( $rand )}" ),
__METHOD__,
@@ -314,7 +374,7 @@ class JobQueueDB extends JobQueue {
// instead of job_random for reducing excess claim retries.
$row = $dbw->selectRow( 'job', '*', // find a random job
array(
- 'job_cmd' => $this->type,
+ 'job_cmd' => $this->type,
'job_token' => '', // unclaimed
),
__METHOD__,
@@ -322,14 +382,14 @@ class JobQueueDB extends JobQueue {
);
if ( !$row ) {
$tinyQueue = true; // we know the queue must have <= MAX_OFFSET rows
- $wgMemc->set( $this->getCacheKey( 'small' ), 1, 30 );
+ $this->cache->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' => $uuid,
'job_token_timestamp' => $dbw->timestamp(),
'job_attempts = job_attempts+1' ),
array( 'job_cmd' => $this->type, 'job_id' => $row->job_id, 'job_token' => '' ),
@@ -355,7 +415,7 @@ class JobQueueDB extends JobQueue {
* @return Row|false
*/
protected function claimOldest( $uuid ) {
- list( $dbw, $scope ) = $this->getMasterDB();
+ $dbw = $this->getMasterDB();
$row = false; // the row acquired
do {
@@ -380,7 +440,7 @@ class JobQueueDB extends JobQueue {
// This uses as much of the DB wrapper functions as possible.
$dbw->update( 'job',
array(
- 'job_token' => $uuid,
+ 'job_token' => $uuid,
'job_token_timestamp' => $dbw->timestamp(),
'job_attempts = job_attempts+1' ),
array( 'job_id = (' .
@@ -399,7 +459,7 @@ class JobQueueDB extends JobQueue {
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." );
+ wfDebug( "Row deleted as duplicate by another process." );
}
} else {
break; // nothing to do
@@ -410,100 +470,31 @@ class JobQueueDB extends JobQueue {
}
/**
- * 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() ) {
+ if ( !isset( $job->metadata['id'] ) ) {
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
- } );
+ $dbw = $this->getMasterDB();
+ try {
+ $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__ );
+ // Delete a row with a single DELETE without holding row locks over RTTs...
+ $dbw->delete( 'job',
+ array( 'job_cmd' => $this->type, 'job_id' => $job->metadata['id'] ), __METHOD__ );
+ } catch ( DBError $e ) {
+ $this->throwDBException( $e );
+ }
return true;
}
@@ -527,44 +518,33 @@ class JobQueueDB extends JobQueue {
// 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
+ $dbw = $this->getMasterDB();
+ $cache = $this->dupCache;
+ $dbw->onTransactionIdle( function() use ( $cache, $params, $key, $dbw ) {
+ $timestamp = $cache->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 $cache->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
+ * @see JobQueue::doDelete()
* @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;
+ protected function doDelete() {
+ $dbw = $this->getMasterDB();
+ try {
+ $dbw->delete( 'job', array( 'job_cmd' => $this->type ) );
+ } catch ( DBError $e ) {
+ $this->throwDBException( $e );
}
-
- // 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'] );
+ return true;
}
/**
@@ -582,7 +562,7 @@ class JobQueueDB extends JobQueue {
return array(
'recycleAndDeleteStaleJobs' => array(
'callback' => array( $this, 'recycleAndDeleteStaleJobs' ),
- 'period' => ceil( $this->claimTTL / 2 )
+ 'period' => ceil( $this->claimTTL / 2 )
)
);
}
@@ -591,10 +571,8 @@ class JobQueueDB extends JobQueue {
* @return void
*/
protected function doFlushCaches() {
- global $wgMemc;
-
foreach ( array( 'empty', 'size', 'acquiredcount' ) as $type ) {
- $wgMemc->delete( $this->getCacheKey( $type ) );
+ $this->cache->delete( $this->getCacheKey( $type ) );
}
}
@@ -603,51 +581,140 @@ class JobQueueDB extends JobQueue {
* @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;
- }
- );
+ $dbr = $this->getSlaveDB();
+ try {
+ return new MappedIterator(
+ $dbr->select( 'job', '*',
+ array( 'job_cmd' => $this->getType(), 'job_token' => '' ) ),
+ function( $row ) use ( $dbr ) {
+ $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->metadata['id'] = $row->job_id;
+ $job->id = $row->job_id; // XXX: work around broken subclasses
+ return $job;
+ }
+ );
+ } catch ( DBError $e ) {
+ $this->throwDBException( $e );
+ }
}
- /**
- * @return Array (DatabaseBase, ScopedCallback)
- */
- protected function getSlaveDB() {
- return $this->getDB( DB_SLAVE );
+ public function getCoalesceLocationInternal() {
+ return $this->cluster
+ ? "DBCluster:{$this->cluster}:{$this->wiki}"
+ : "LBFactory:{$this->wiki}";
}
- /**
- * @return Array (DatabaseBase, ScopedCallback)
- */
- protected function getMasterDB() {
- return $this->getDB( DB_MASTER );
+ protected function doGetSiblingQueuesWithJobs( array $types ) {
+ $dbr = $this->getSlaveDB();
+ $res = $dbr->select( 'job', 'DISTINCT job_cmd',
+ array( 'job_cmd' => $types ), __METHOD__ );
+
+ $types = array();
+ foreach ( $res as $row ) {
+ $types[] = $row->job_cmd;
+ }
+ return $types;
+ }
+
+ protected function doGetSiblingQueueSizes( array $types ) {
+ $dbr = $this->getSlaveDB();
+ $res = $dbr->select( 'job', array( 'job_cmd', 'COUNT(*) AS count' ),
+ array( 'job_cmd' => $types ), __METHOD__, array( 'GROUP BY' => 'job_cmd' ) );
+
+ $sizes = array();
+ foreach ( $res as $row ) {
+ $sizes[$row->job_cmd] = (int)$row->count;
+ }
+ return $sizes;
}
/**
- * @param $index integer (DB_SLAVE/DB_MASTER)
- * @return Array (DatabaseBase, ScopedCallback)
+ * Recycle or destroy any jobs that have been claimed for too long
+ *
+ * @return integer Number of jobs recycled/deleted
*/
- 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 );
- } )
- );
+ public function recycleAndDeleteStaleJobs() {
+ $now = time();
+ $count = 0; // affected rows
+ $dbw = $this->getMasterDB();
+
+ try {
+ 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();
+ JobQueue::incrStats( 'job-recycle', $this->type, $dbw->affectedRows() );
+ $this->cache->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();
+ JobQueue::incrStats( 'job-abandon', $this->type, $dbw->affectedRows() );
+ }
+
+ $dbw->unlock( "jobqueue-recycle-{$this->type}", __METHOD__ );
+ } catch ( DBError $e ) {
+ $this->throwDBException( $e );
+ }
+
+ return $count;
}
/**
@@ -655,7 +722,7 @@ class JobQueueDB extends JobQueue {
* @return array
*/
protected function insertFields( Job $job ) {
- list( $dbw, $scope ) = $this->getMasterDB();
+ $dbw = $this->getMasterDB();
return array(
// Fields that describe the nature of the job
'job_cmd' => $job->getType(),
@@ -674,20 +741,45 @@ class JobQueueDB extends JobQueue {
}
/**
- * @return string
+ * @return DBConnRef
*/
- private function getCacheKey( $property ) {
- list( $db, $prefix ) = wfSplitWikiID( $this->wiki );
- return wfForeignMemcKey( $db, $prefix, 'jobqueue', $this->type, $property );
+ protected function getSlaveDB() {
+ try {
+ return $this->getDB( DB_SLAVE );
+ } catch ( DBConnectionError $e ) {
+ throw new JobQueueConnectionError( "DBConnectionError:" . $e->getMessage() );
+ }
+ }
+
+ /**
+ * @return DBConnRef
+ */
+ protected function getMasterDB() {
+ try {
+ return $this->getDB( DB_MASTER );
+ } catch ( DBConnectionError $e ) {
+ throw new JobQueueConnectionError( "DBConnectionError:" . $e->getMessage() );
+ }
+ }
+
+ /**
+ * @param $index integer (DB_SLAVE/DB_MASTER)
+ * @return DBConnRef
+ */
+ protected function getDB( $index ) {
+ $lb = ( $this->cluster !== false )
+ ? wfGetLBFactory()->getExternalLB( $this->cluster, $this->wiki )
+ : wfGetLB( $this->wiki );
+ return $lb->getConnectionRef( $index, array(), $this->wiki );
}
/**
- * @param string $signature Hash identifier of the root job
* @return string
*/
- private function getRootJobCacheKey( $signature ) {
+ private function getCacheKey( $property ) {
list( $db, $prefix ) = wfSplitWikiID( $this->wiki );
- return wfForeignMemcKey( $db, $prefix, 'jobqueue', $this->type, 'rootjob', $signature );
+ $cluster = is_string( $this->cluster ) ? $this->cluster : 'main';
+ return wfForeignMemcKey( $db, $prefix, 'jobqueue', $cluster, $this->type, $property );
}
/**
@@ -713,4 +805,12 @@ class JobQueueDB extends JobQueue {
return false;
}
}
+
+ /**
+ * @param DBError $e
+ * @throws JobQueueError
+ */
+ protected function throwDBException( DBError $e ) {
+ throw new JobQueueError( get_class( $e ) . ": " . $e->getMessage() );
+ }
}
diff --git a/includes/job/JobQueueFederated.php b/includes/job/JobQueueFederated.php
new file mode 100644
index 00000000..d3ce164a
--- /dev/null
+++ b/includes/job/JobQueueFederated.php
@@ -0,0 +1,473 @@
+<?php
+/**
+ * Job queue code for federated queues.
+ *
+ * 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 and running of background jobs for federated queues
+ *
+ * This class allows for queues to be partitioned into smaller queues.
+ * A partition is defined by the configuration for a JobQueue instance.
+ * For example, one can set $wgJobTypeConf['refreshLinks'] to point to a
+ * JobQueueFederated instance, which itself would consist of three JobQueueRedis
+ * instances, each using their own redis server. This would allow for the jobs
+ * to be split (evenly or based on weights) accross multiple servers if a single
+ * server becomes impractical or expensive. Different JobQueue classes can be mixed.
+ *
+ * The basic queue configuration (e.g. "order", "claimTTL") of a federated queue
+ * is inherited by the partition queues. Additional configuration defines what
+ * section each wiki is in, what partition queues each section uses (and their weight),
+ * and the JobQueue configuration for each partition. Some sections might only need a
+ * single queue partition, like the sections for groups of small wikis.
+ *
+ * If used for performance, then $wgMainCacheType should be set to memcached/redis.
+ * Note that "fifo" cannot be used for the ordering, since the data is distributed.
+ * One can still use "timestamp" instead, as in "roughly timestamp ordered". Also,
+ * queue classes used by this should ignore down servers (with TTL) to avoid slowness.
+ *
+ * @ingroup JobQueue
+ * @since 1.22
+ */
+class JobQueueFederated extends JobQueue {
+ /** @var Array (partition name => weight) reverse sorted by weight */
+ protected $partitionMap = array();
+ /** @var Array (partition name => JobQueue) reverse sorted by weight */
+ protected $partitionQueues = array();
+ /** @var HashRing */
+ protected $partitionPushRing;
+ /** @var BagOStuff */
+ protected $cache;
+
+ 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
+
+ /**
+ * @params include:
+ * - sectionsByWiki : A map of wiki IDs to section names.
+ * Wikis will default to using the section "default".
+ * - partitionsBySection : Map of section names to maps of (partition name => weight).
+ * A section called 'default' must be defined if not all wikis
+ * have explicitly defined sections.
+ * - configByPartition : Map of queue partition names to configuration arrays.
+ * These configuration arrays are passed to JobQueue::factory().
+ * The options set here are overriden by those passed to this
+ * the federated queue itself (e.g. 'order' and 'claimTTL').
+ * - partitionsNoPush : List of partition names that can handle pop() but not push().
+ * This can be used to migrate away from a certain partition.
+ * @param array $params
+ */
+ protected function __construct( array $params ) {
+ parent::__construct( $params );
+ $section = isset( $params['sectionsByWiki'][$this->wiki] )
+ ? $params['sectionsByWiki'][$this->wiki]
+ : 'default';
+ if ( !isset( $params['partitionsBySection'][$section] ) ) {
+ throw new MWException( "No configuration for section '$section'." );
+ }
+ // Get the full partition map
+ $this->partitionMap = $params['partitionsBySection'][$section];
+ arsort( $this->partitionMap, SORT_NUMERIC );
+ // Get the partitions jobs can actually be pushed to
+ $partitionPushMap = $this->partitionMap;
+ if ( isset( $params['partitionsNoPush'] ) ) {
+ foreach ( $params['partitionsNoPush'] as $partition ) {
+ unset( $partitionPushMap[$partition] );
+ }
+ }
+ // Get the config to pass to merge into each partition queue config
+ $baseConfig = $params;
+ foreach ( array( 'class', 'sectionsByWiki',
+ 'partitionsBySection', 'configByPartition', 'partitionsNoPush' ) as $o )
+ {
+ unset( $baseConfig[$o] );
+ }
+ // Get the partition queue objects
+ foreach ( $this->partitionMap as $partition => $w ) {
+ if ( !isset( $params['configByPartition'][$partition] ) ) {
+ throw new MWException( "No configuration for partition '$partition'." );
+ }
+ $this->partitionQueues[$partition] = JobQueue::factory(
+ $baseConfig + $params['configByPartition'][$partition] );
+ }
+ // Get the ring of partitions to push jobs into
+ $this->partitionPushRing = new HashRing( $partitionPushMap );
+ // Aggregate cache some per-queue values if there are multiple partition queues
+ $this->cache = count( $this->partitionMap ) > 1 ? wfGetMainCache() : new EmptyBagOStuff();
+ }
+
+ protected function supportedOrders() {
+ // No FIFO due to partitioning, though "rough timestamp order" is supported
+ return array( 'undefined', 'random', 'timestamp' );
+ }
+
+ protected function optimalOrder() {
+ return 'undefined'; // defer to the partitions
+ }
+
+ protected function supportsDelayedJobs() {
+ return true; // defer checks to the partitions
+ }
+
+ protected function doIsEmpty() {
+ $key = $this->getCacheKey( 'empty' );
+
+ $isEmpty = $this->cache->get( $key );
+ if ( $isEmpty === 'true' ) {
+ return true;
+ } elseif ( $isEmpty === 'false' ) {
+ return false;
+ }
+
+ foreach ( $this->partitionQueues as $queue ) {
+ try {
+ if ( !$queue->doIsEmpty() ) {
+ $this->cache->add( $key, 'false', self::CACHE_TTL_LONG );
+ return false;
+ }
+ } catch ( JobQueueError $e ) {
+ MWExceptionHandler::logException( $e );
+ }
+ }
+
+ $this->cache->add( $key, 'true', self::CACHE_TTL_LONG );
+ return true;
+ }
+
+ protected function doGetSize() {
+ return $this->getCrossPartitionSum( 'size', 'doGetSize' );
+ }
+
+ protected function doGetAcquiredCount() {
+ return $this->getCrossPartitionSum( 'acquiredcount', 'doGetAcquiredCount' );
+ }
+
+ protected function doGetDelayedCount() {
+ return $this->getCrossPartitionSum( 'delayedcount', 'doGetDelayedCount' );
+ }
+
+ protected function doGetAbandonedCount() {
+ return $this->getCrossPartitionSum( 'abandonedcount', 'doGetAbandonedCount' );
+ }
+
+ /**
+ * @param string $type
+ * @param string $method
+ * @return integer
+ */
+ protected function getCrossPartitionSum( $type, $method ) {
+ $key = $this->getCacheKey( $type );
+
+ $count = $this->cache->get( $key );
+ if ( is_int( $count ) ) {
+ return $count;
+ }
+
+ $count = 0;
+ foreach ( $this->partitionQueues as $queue ) {
+ try {
+ $count += $queue->$method();
+ } catch ( JobQueueError $e ) {
+ MWExceptionHandler::logException( $e );
+ }
+ }
+
+ $this->cache->set( $key, $count, self::CACHE_TTL_SHORT );
+ return $count;
+ }
+
+ protected function doBatchPush( array $jobs, $flags ) {
+ if ( !count( $jobs ) ) {
+ return true; // nothing to do
+ }
+ // Local ring variable that may be changed to point to a new ring on failure
+ $partitionRing = $this->partitionPushRing;
+ // Try to insert the jobs and update $partitionsTry on any failures
+ $jobsLeft = $this->tryJobInsertions( $jobs, $partitionRing, $flags );
+ if ( count( $jobsLeft ) ) { // some jobs failed to insert?
+ // Try to insert the remaning jobs once more, ignoring the bad partitions
+ return !count( $this->tryJobInsertions( $jobsLeft, $partitionRing, $flags ) );
+ }
+ return true;
+ }
+
+ /**
+ * @param array $jobs
+ * @param HashRing $partitionRing
+ * @param integer $flags
+ * @return array List of Job object that could not be inserted
+ */
+ protected function tryJobInsertions( array $jobs, HashRing &$partitionRing, $flags ) {
+ $jobsLeft = array();
+
+ // Because jobs are spread across partitions, per-job de-duplication needs
+ // to use a consistent hash to avoid allowing duplicate jobs per partition.
+ // When inserting a batch of de-duplicated jobs, QOS_ATOMIC is disregarded.
+ $uJobsByPartition = array(); // (partition name => job list)
+ foreach ( $jobs as $key => $job ) {
+ if ( $job->ignoreDuplicates() ) {
+ $sha1 = sha1( serialize( $job->getDeduplicationInfo() ) );
+ $uJobsByPartition[$partitionRing->getLocation( $sha1 )][] = $job;
+ unset( $jobs[$key] );
+ }
+ }
+ // Get the batches of jobs that are not de-duplicated
+ if ( $flags & self::QOS_ATOMIC ) {
+ $nuJobBatches = array( $jobs ); // all or nothing
+ } else {
+ // Split the jobs into batches and spread them out over servers if there
+ // are many jobs. This helps keep the partitions even. Otherwise, send all
+ // the jobs to a single partition queue to avoids the extra connections.
+ $nuJobBatches = array_chunk( $jobs, 300 );
+ }
+
+ // Insert the de-duplicated jobs into the queues...
+ foreach ( $uJobsByPartition as $partition => $jobBatch ) {
+ $queue = $this->partitionQueues[$partition];
+ try {
+ $ok = $queue->doBatchPush( $jobBatch, $flags | self::QOS_ATOMIC );
+ } catch ( JobQueueError $e ) {
+ $ok = false;
+ MWExceptionHandler::logException( $e );
+ }
+ if ( $ok ) {
+ $key = $this->getCacheKey( 'empty' );
+ $this->cache->set( $key, 'false', JobQueueDB::CACHE_TTL_LONG );
+ } else {
+ $partitionRing = $partitionRing->newWithoutLocation( $partition ); // blacklist
+ if ( !$partitionRing ) {
+ throw new JobQueueError( "Could not insert job(s), all partitions are down." );
+ }
+ $jobsLeft = array_merge( $jobsLeft, $jobBatch ); // not inserted
+ }
+ }
+
+ // Insert the jobs that are not de-duplicated into the queues...
+ foreach ( $nuJobBatches as $jobBatch ) {
+ $partition = ArrayUtils::pickRandom( $partitionRing->getLocationWeights() );
+ $queue = $this->partitionQueues[$partition];
+ try {
+ $ok = $queue->doBatchPush( $jobBatch, $flags | self::QOS_ATOMIC );
+ } catch ( JobQueueError $e ) {
+ $ok = false;
+ MWExceptionHandler::logException( $e );
+ }
+ if ( $ok ) {
+ $key = $this->getCacheKey( 'empty' );
+ $this->cache->set( $key, 'false', JobQueueDB::CACHE_TTL_LONG );
+ } else {
+ $partitionRing = $partitionRing->newWithoutLocation( $partition ); // blacklist
+ if ( !$partitionRing ) {
+ throw new JobQueueError( "Could not insert job(s), all partitions are down." );
+ }
+ $jobsLeft = array_merge( $jobsLeft, $jobBatch ); // not inserted
+ }
+ }
+
+ return $jobsLeft;
+ }
+
+ protected function doPop() {
+ $key = $this->getCacheKey( 'empty' );
+
+ $isEmpty = $this->cache->get( $key );
+ if ( $isEmpty === 'true' ) {
+ return false;
+ }
+
+ $partitionsTry = $this->partitionMap; // (partition => weight)
+
+ while ( count( $partitionsTry ) ) {
+ $partition = ArrayUtils::pickRandom( $partitionsTry );
+ if ( $partition === false ) {
+ break; // all partitions at 0 weight
+ }
+ $queue = $this->partitionQueues[$partition];
+ try {
+ $job = $queue->pop();
+ } catch ( JobQueueError $e ) {
+ $job = false;
+ MWExceptionHandler::logException( $e );
+ }
+ if ( $job ) {
+ $job->metadata['QueuePartition'] = $partition;
+ return $job;
+ } else {
+ unset( $partitionsTry[$partition] ); // blacklist partition
+ }
+ }
+
+ $this->cache->set( $key, 'true', JobQueueDB::CACHE_TTL_LONG );
+ return false;
+ }
+
+ protected function doAck( Job $job ) {
+ if ( !isset( $job->metadata['QueuePartition'] ) ) {
+ throw new MWException( "The given job has no defined partition name." );
+ }
+ return $this->partitionQueues[$job->metadata['QueuePartition']]->ack( $job );
+ }
+
+ protected function doIsRootJobOldDuplicate( Job $job ) {
+ $params = $job->getRootJobParams();
+ $partitions = $this->partitionPushRing->getLocations( $params['rootJobSignature'], 2 );
+ try {
+ return $this->partitionQueues[$partitions[0]]->doIsRootJobOldDuplicate( $job );
+ } catch ( JobQueueError $e ) {
+ if ( isset( $partitions[1] ) ) { // check fallback partition
+ return $this->partitionQueues[$partitions[1]]->doIsRootJobOldDuplicate( $job );
+ }
+ }
+ return false;
+ }
+
+ protected function doDeduplicateRootJob( Job $job ) {
+ $params = $job->getRootJobParams();
+ $partitions = $this->partitionPushRing->getLocations( $params['rootJobSignature'], 2 );
+ try {
+ return $this->partitionQueues[$partitions[0]]->doDeduplicateRootJob( $job );
+ } catch ( JobQueueError $e ) {
+ if ( isset( $partitions[1] ) ) { // check fallback partition
+ return $this->partitionQueues[$partitions[1]]->doDeduplicateRootJob( $job );
+ }
+ }
+ return false;
+ }
+
+ protected function doDelete() {
+ foreach ( $this->partitionQueues as $queue ) {
+ try {
+ $queue->doDelete();
+ } catch ( JobQueueError $e ) {
+ MWExceptionHandler::logException( $e );
+ }
+ }
+ }
+
+ protected function doWaitForBackups() {
+ foreach ( $this->partitionQueues as $queue ) {
+ try {
+ $queue->waitForBackups();
+ } catch ( JobQueueError $e ) {
+ MWExceptionHandler::logException( $e );
+ }
+ }
+ }
+
+ protected function doGetPeriodicTasks() {
+ $tasks = array();
+ foreach ( $this->partitionQueues as $partition => $queue ) {
+ foreach ( $queue->getPeriodicTasks() as $task => $def ) {
+ $tasks["{$partition}:{$task}"] = $def;
+ }
+ }
+ return $tasks;
+ }
+
+ protected function doFlushCaches() {
+ static $types = array(
+ 'empty',
+ 'size',
+ 'acquiredcount',
+ 'delayedcount',
+ 'abandonedcount'
+ );
+ foreach ( $types as $type ) {
+ $this->cache->delete( $this->getCacheKey( $type ) );
+ }
+ foreach ( $this->partitionQueues as $queue ) {
+ $queue->doFlushCaches();
+ }
+ }
+
+ public function getAllQueuedJobs() {
+ $iterator = new AppendIterator();
+ foreach ( $this->partitionQueues as $queue ) {
+ $iterator->append( $queue->getAllQueuedJobs() );
+ }
+ return $iterator;
+ }
+
+ public function getAllDelayedJobs() {
+ $iterator = new AppendIterator();
+ foreach ( $this->partitionQueues as $queue ) {
+ $iterator->append( $queue->getAllDelayedJobs() );
+ }
+ return $iterator;
+ }
+
+ public function getCoalesceLocationInternal() {
+ return "JobQueueFederated:wiki:{$this->wiki}" .
+ sha1( serialize( array_keys( $this->partitionMap ) ) );
+ }
+
+ protected function doGetSiblingQueuesWithJobs( array $types ) {
+ $result = array();
+ foreach ( $this->partitionQueues as $queue ) {
+ try {
+ $nonEmpty = $queue->doGetSiblingQueuesWithJobs( $types );
+ if ( is_array( $nonEmpty ) ) {
+ $result = array_unique( array_merge( $result, $nonEmpty ) );
+ } else {
+ return null; // not supported on all partitions; bail
+ }
+ if ( count( $result ) == count( $types ) ) {
+ break; // short-circuit
+ }
+ } catch ( JobQueueError $e ) {
+ MWExceptionHandler::logException( $e );
+ }
+ }
+ return array_values( $result );
+ }
+
+ protected function doGetSiblingQueueSizes( array $types ) {
+ $result = array();
+ foreach ( $this->partitionQueues as $queue ) {
+ try {
+ $sizes = $queue->doGetSiblingQueueSizes( $types );
+ if ( is_array( $sizes ) ) {
+ foreach ( $sizes as $type => $size ) {
+ $result[$type] = isset( $result[$type] ) ? $result[$type] + $size : $size;
+ }
+ } else {
+ return null; // not supported on all partitions; bail
+ }
+ } catch ( JobQueueError $e ) {
+ MWExceptionHandler::logException( $e );
+ }
+ }
+ return $result;
+ }
+
+ public function setTestingPrefix( $key ) {
+ foreach ( $this->partitionQueues as $queue ) {
+ $queue->setTestingPrefix( $key );
+ }
+ }
+
+ /**
+ * @return string
+ */
+ private function getCacheKey( $property ) {
+ list( $db, $prefix ) = wfSplitWikiID( $this->wiki );
+ return wfForeignMemcKey( $db, $prefix, 'jobqueue', $this->type, $property );
+ }
+}
diff --git a/includes/job/JobQueueGroup.php b/includes/job/JobQueueGroup.php
index 351c71a3..fa7fee5f 100644
--- a/includes/job/JobQueueGroup.php
+++ b/includes/job/JobQueueGroup.php
@@ -36,10 +36,14 @@ class JobQueueGroup {
protected $wiki; // string; wiki ID
+ /** @var array Map of (bucket => (queue => JobQueue, types => list of types) */
+ protected $coalescedQueues;
+
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 USE_PRIORITY = 2; // integer; respect deprioritization
const PROC_CACHE_TTL = 15; // integer; seconds
@@ -146,6 +150,9 @@ class JobQueueGroup {
*/
public function pop( $qtype = self::TYPE_DEFAULT, $flags = 0 ) {
if ( is_string( $qtype ) ) { // specific job type
+ if ( ( $flags & self::USE_PRIORITY ) && $this->isQueueDeprioritized( $qtype ) ) {
+ return false; // back off
+ }
$job = $this->get( $qtype )->pop();
if ( !$job ) {
JobQueueAggregator::singleton()->notifyQueueEmpty( $this->wiki, $qtype );
@@ -167,6 +174,9 @@ class JobQueueGroup {
shuffle( $types ); // avoid starvation
foreach ( $types as $type ) { // for each queue...
+ if ( ( $flags & self::USE_PRIORITY ) && $this->isQueueDeprioritized( $type ) ) {
+ continue; // back off
+ }
$job = $this->get( $type )->pop();
if ( $job ) { // found
return $job;
@@ -247,15 +257,72 @@ class JobQueueGroup {
*/
public function getQueuesWithJobs() {
$types = array();
- foreach ( $this->getQueueTypes() as $type ) {
- if ( !$this->get( $type )->isEmpty() ) {
- $types[] = $type;
+ foreach ( $this->getCoalescedQueues() as $info ) {
+ $nonEmpty = $info['queue']->getSiblingQueuesWithJobs( $this->getQueueTypes() );
+ if ( is_array( $nonEmpty ) ) { // batching features supported
+ $types = array_merge( $types, $nonEmpty );
+ } else { // we have to go through the queues in the bucket one-by-one
+ foreach ( $info['types'] as $type ) {
+ if ( !$this->get( $type )->isEmpty() ) {
+ $types[] = $type;
+ }
+ }
}
}
return $types;
}
/**
+ * Get the size of the queus for a list of job types
+ *
+ * @return Array Map of (job type => size)
+ */
+ public function getQueueSizes() {
+ $sizeMap = array();
+ foreach ( $this->getCoalescedQueues() as $info ) {
+ $sizes = $info['queue']->getSiblingQueueSizes( $this->getQueueTypes() );
+ if ( is_array( $sizes ) ) { // batching features supported
+ $sizeMap = $sizeMap + $sizes;
+ } else { // we have to go through the queues in the bucket one-by-one
+ foreach ( $info['types'] as $type ) {
+ $sizeMap[$type] = $this->get( $type )->getSize();
+ }
+ }
+ }
+ return $sizeMap;
+ }
+
+ /**
+ * @return array
+ */
+ protected function getCoalescedQueues() {
+ global $wgJobTypeConf;
+
+ if ( $this->coalescedQueues === null ) {
+ $this->coalescedQueues = array();
+ foreach ( $wgJobTypeConf as $type => $conf ) {
+ $queue = JobQueue::factory(
+ array( 'wiki' => $this->wiki, 'type' => 'null' ) + $conf );
+ $loc = $queue->getCoalesceLocationInternal();
+ if ( !isset( $this->coalescedQueues[$loc] ) ) {
+ $this->coalescedQueues[$loc]['queue'] = $queue;
+ $this->coalescedQueues[$loc]['types'] = array();
+ }
+ if ( $type === 'default' ) {
+ $this->coalescedQueues[$loc]['types'] = array_merge(
+ $this->coalescedQueues[$loc]['types'],
+ array_diff( $this->getQueueTypes(), array_keys( $wgJobTypeConf ) )
+ );
+ } else {
+ $this->coalescedQueues[$loc]['types'][] = $type;
+ }
+ }
+ }
+
+ return $this->coalescedQueues;
+ }
+
+ /**
* 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.
@@ -264,10 +331,15 @@ class JobQueueGroup {
* @return bool
*/
public function isQueueDeprioritized( $type ) {
+ if ( $this->cache->has( 'isDeprioritized', $type, 5 ) ) {
+ return $this->cache->get( 'isDeprioritized', $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();
+ $deprioritized = !$this->get( 'refreshLinks' )->isEmpty();
+ $this->cache->set( 'isDeprioritized', $type, $deprioritized );
+ return $deprioritized;
}
return false;
}
@@ -298,9 +370,13 @@ class JobQueueGroup {
} elseif ( !isset( $lastRuns[$type][$task] )
|| $lastRuns[$type][$task] < ( time() - $definition['period'] ) )
{
- if ( call_user_func( $definition['callback'] ) !== null ) {
- $tasksRun[$type][$task] = time();
- ++$count;
+ try {
+ if ( call_user_func( $definition['callback'] ) !== null ) {
+ $tasksRun[$type][$task] = time();
+ ++$count;
+ }
+ } catch ( JobQueueError $e ) {
+ MWExceptionHandler::logException( $e );
}
}
}
diff --git a/includes/job/JobQueueRedis.php b/includes/job/JobQueueRedis.php
new file mode 100644
index 00000000..378e1755
--- /dev/null
+++ b/includes/job/JobQueueRedis.php
@@ -0,0 +1,856 @@
+<?php
+/**
+ * Redis-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 Redis
+ *
+ * This is faster, less resource intensive, queue that JobQueueDB.
+ * All data for a queue using this class is placed into one redis server.
+ *
+ * There are eight main redis keys used to track jobs:
+ * - l-unclaimed : A list of job IDs used for ready unclaimed jobs
+ * - z-claimed : A sorted set of (job ID, UNIX timestamp as score) used for job retries
+ * - z-abandoned : A sorted set of (job ID, UNIX timestamp as score) used for broken jobs
+ * - z-delayed : A sorted set of (job ID, UNIX timestamp as score) used for delayed jobs
+ * - h-idBySha1 : A hash of (SHA1 => job ID) for unclaimed jobs used for de-duplication
+ * - h-sha1ById : A hash of (job ID => SHA1) for unclaimed jobs used for de-duplication
+ * - h-attempts : A hash of (job ID => attempt count) used for job claiming/retries
+ * - h-data : A hash of (job ID => serialized blobs) for job storage
+ * A job ID can be in only one of z-delayed, l-unclaimed, z-claimed, and z-abandoned.
+ * If an ID appears in any of those lists, it should have a h-data entry for its ID.
+ * If a job has a SHA1 de-duplication value and its ID is in l-unclaimed or z-delayed, then
+ * there should be no other such jobs with that SHA1. Every h-idBySha1 entry has an h-sha1ById
+ * entry and every h-sha1ById must refer to an ID that is l-unclaimed. If a job has its
+ * ID in z-claimed or z-abandoned, then it must also have an h-attempts entry for its ID.
+ *
+ * Additionally, "rootjob:* keys track "root jobs" used for additional de-duplication.
+ * Aside from root job keys, all keys have no expiry, and are only removed when jobs are run.
+ * All the keys are prefixed with the relevant wiki ID information.
+ *
+ * This class requires Redis 2.6 as it makes use Lua scripts for fast atomic operations.
+ * Additionally, it should be noted that redis has different persistence modes, such
+ * as rdb snapshots, journaling, and no persistent. Appropriate configuration should be
+ * made on the servers based on what queues are using it and what tolerance they have.
+ *
+ * @ingroup JobQueue
+ * @ingroup Redis
+ * @since 1.22
+ */
+class JobQueueRedis extends JobQueue {
+ /** @var RedisConnectionPool */
+ protected $redisPool;
+
+ protected $server; // string; server address
+ protected $compression; // string; compression method to use
+
+ const MAX_AGE_PRUNE = 604800; // integer; seconds a job can live once claimed (7 days)
+
+ protected $key; // string; key to prefix the queue keys with (used for testing)
+
+ /**
+ * @params include:
+ * - redisConfig : An array of parameters to RedisConnectionPool::__construct().
+ * Note that the serializer option is ignored "none" is always used.
+ * - 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.
+ * - compression : The type of compression to use; one of (none,gzip).
+ * @param array $params
+ */
+ public function __construct( array $params ) {
+ parent::__construct( $params );
+ $params['redisConfig']['serializer'] = 'none'; // make it easy to use Lua
+ $this->server = $params['redisServer'];
+ $this->compression = isset( $params['compression'] ) ? $params['compression'] : 'none';
+ $this->redisPool = RedisConnectionPool::singleton( $params['redisConfig'] );
+ }
+
+ protected function supportedOrders() {
+ return array( 'timestamp', 'fifo' );
+ }
+
+ protected function optimalOrder() {
+ return 'fifo';
+ }
+
+ protected function supportsDelayedJobs() {
+ return true;
+ }
+
+ /**
+ * @see JobQueue::doIsEmpty()
+ * @return bool
+ * @throws MWException
+ */
+ protected function doIsEmpty() {
+ return $this->doGetSize() == 0;
+ }
+
+ /**
+ * @see JobQueue::doGetSize()
+ * @return integer
+ * @throws MWException
+ */
+ protected function doGetSize() {
+ $conn = $this->getConnection();
+ try {
+ return $conn->lSize( $this->getQueueKey( 'l-unclaimed' ) );
+ } catch ( RedisException $e ) {
+ $this->throwRedisException( $this->server, $conn, $e );
+ }
+ }
+
+ /**
+ * @see JobQueue::doGetAcquiredCount()
+ * @return integer
+ * @throws MWException
+ */
+ protected function doGetAcquiredCount() {
+ if ( $this->claimTTL <= 0 ) {
+ return 0; // no acknowledgements
+ }
+ $conn = $this->getConnection();
+ try {
+ $conn->multi( Redis::PIPELINE );
+ $conn->zSize( $this->getQueueKey( 'z-claimed' ) );
+ $conn->zSize( $this->getQueueKey( 'z-abandoned' ) );
+ return array_sum( $conn->exec() );
+ } catch ( RedisException $e ) {
+ $this->throwRedisException( $this->server, $conn, $e );
+ }
+ }
+
+ /**
+ * @see JobQueue::doGetDelayedCount()
+ * @return integer
+ * @throws MWException
+ */
+ protected function doGetDelayedCount() {
+ if ( !$this->checkDelay ) {
+ return 0; // no delayed jobs
+ }
+ $conn = $this->getConnection();
+ try {
+ return $conn->zSize( $this->getQueueKey( 'z-delayed' ) );
+ } catch ( RedisException $e ) {
+ $this->throwRedisException( $this->server, $conn, $e );
+ }
+ }
+
+ /**
+ * @see JobQueue::doGetAbandonedCount()
+ * @return integer
+ * @throws MWException
+ */
+ protected function doGetAbandonedCount() {
+ if ( $this->claimTTL <= 0 ) {
+ return 0; // no acknowledgements
+ }
+ $conn = $this->getConnection();
+ try {
+ return $conn->zSize( $this->getQueueKey( 'z-abandoned' ) );
+ } catch ( RedisException $e ) {
+ $this->throwRedisException( $this->server, $conn, $e );
+ }
+ }
+
+ /**
+ * @see JobQueue::doBatchPush()
+ * @param array $jobs
+ * @param $flags
+ * @return bool
+ * @throws MWException
+ */
+ protected function doBatchPush( array $jobs, $flags ) {
+ // Convert the jobs into field maps (de-duplicated against each other)
+ $items = array(); // (job ID => job fields map)
+ foreach ( $jobs as $job ) {
+ $item = $this->getNewJobFields( $job );
+ if ( strlen( $item['sha1'] ) ) { // hash identifier => de-duplicate
+ $items[$item['sha1']] = $item;
+ } else {
+ $items[$item['uuid']] = $item;
+ }
+ }
+
+ if ( !count( $items ) ) {
+ return true; // nothing to do
+ }
+
+ $conn = $this->getConnection();
+ try {
+ // Actually push the non-duplicate jobs into the queue...
+ if ( $flags & self::QOS_ATOMIC ) {
+ $batches = array( $items ); // all or nothing
+ } else {
+ $batches = array_chunk( $items, 500 ); // avoid tying up the server
+ }
+ $failed = 0;
+ $pushed = 0;
+ foreach ( $batches as $itemBatch ) {
+ $added = $this->pushBlobs( $conn, $itemBatch );
+ if ( is_int( $added ) ) {
+ $pushed += $added;
+ } else {
+ $failed += count( $itemBatch );
+ }
+ }
+ if ( $failed > 0 ) {
+ wfDebugLog( 'JobQueueRedis', "Could not insert {$failed} {$this->type} job(s)." );
+ return false;
+ }
+ JobQueue::incrStats( 'job-insert', $this->type, count( $items ) );
+ JobQueue::incrStats( 'job-insert-duplicate', $this->type,
+ count( $items ) - $failed - $pushed );
+ } catch ( RedisException $e ) {
+ $this->throwRedisException( $this->server, $conn, $e );
+ }
+
+ return true;
+ }
+
+ /**
+ * @param RedisConnRef $conn
+ * @param array $items List of results from JobQueueRedis::getNewJobFields()
+ * @return integer Number of jobs inserted (duplicates are ignored)
+ * @throws RedisException
+ */
+ protected function pushBlobs( RedisConnRef $conn, array $items ) {
+ $args = array(); // ([id, sha1, rtime, blob [, id, sha1, rtime, blob ... ] ] )
+ foreach ( $items as $item ) {
+ $args[] = (string)$item['uuid'];
+ $args[] = (string)$item['sha1'];
+ $args[] = (string)$item['rtimestamp'];
+ $args[] = (string)$this->serialize( $item );
+ }
+ static $script =
+<<<LUA
+ if #ARGV % 4 ~= 0 then return redis.error_reply('Unmatched arguments') end
+ local pushed = 0
+ for i = 1,#ARGV,4 do
+ local id,sha1,rtimestamp,blob = ARGV[i],ARGV[i+1],ARGV[i+2],ARGV[i+3]
+ if sha1 == '' or redis.call('hExists',KEYS[3],sha1) == 0 then
+ if 1*rtimestamp > 0 then
+ -- Insert into delayed queue (release time as score)
+ redis.call('zAdd',KEYS[4],rtimestamp,id)
+ else
+ -- Insert into unclaimed queue
+ redis.call('lPush',KEYS[1],id)
+ end
+ if sha1 ~= '' then
+ redis.call('hSet',KEYS[2],id,sha1)
+ redis.call('hSet',KEYS[3],sha1,id)
+ end
+ redis.call('hSet',KEYS[5],id,blob)
+ pushed = pushed + 1
+ end
+ end
+ return pushed
+LUA;
+ return $conn->luaEval( $script,
+ array_merge(
+ array(
+ $this->getQueueKey( 'l-unclaimed' ), # KEYS[1]
+ $this->getQueueKey( 'h-sha1ById' ), # KEYS[2]
+ $this->getQueueKey( 'h-idBySha1' ), # KEYS[3]
+ $this->getQueueKey( 'z-delayed' ), # KEYS[4]
+ $this->getQueueKey( 'h-data' ), # KEYS[5]
+ ),
+ $args
+ ),
+ 5 # number of first argument(s) that are keys
+ );
+ }
+
+ /**
+ * @see JobQueue::doPop()
+ * @return Job|bool
+ * @throws MWException
+ */
+ protected function doPop() {
+ $job = false;
+
+ // Push ready delayed jobs into the queue every 10 jobs to spread the load.
+ // This is also done as a periodic task, but we don't want too much done at once.
+ if ( $this->checkDelay && mt_rand( 0, 9 ) == 0 ) {
+ $this->releaseReadyDelayedJobs();
+ }
+
+ $conn = $this->getConnection();
+ try {
+ do {
+ if ( $this->claimTTL > 0 ) {
+ // Keep the claimed job list down for high-traffic queues
+ if ( mt_rand( 0, 99 ) == 0 ) {
+ $this->recycleAndDeleteStaleJobs();
+ }
+ $blob = $this->popAndAcquireBlob( $conn );
+ } else {
+ $blob = $this->popAndDeleteBlob( $conn );
+ }
+ if ( $blob === false ) {
+ break; // no jobs; nothing to do
+ }
+
+ JobQueue::incrStats( 'job-pop', $this->type );
+ $item = $this->unserialize( $blob );
+ if ( $item === false ) {
+ wfDebugLog( 'JobQueueRedis', "Could not unserialize {$this->type} job." );
+ continue;
+ }
+
+ // If $item is invalid, recycleAndDeleteStaleJobs() will cleanup as needed
+ $job = $this->getJobFromFields( $item ); // may be false
+ } while ( !$job ); // job may be false if invalid
+ } catch ( RedisException $e ) {
+ $this->throwRedisException( $this->server, $conn, $e );
+ }
+
+ return $job;
+ }
+
+ /**
+ * @param RedisConnRef $conn
+ * @return array serialized string or false
+ * @throws RedisException
+ */
+ protected function popAndDeleteBlob( RedisConnRef $conn ) {
+ static $script =
+<<<LUA
+ -- Pop an item off the queue
+ local id = redis.call('rpop',KEYS[1])
+ if not id then return false end
+ -- Get the job data and remove it
+ local item = redis.call('hGet',KEYS[4],id)
+ redis.call('hDel',KEYS[4],id)
+ -- Allow new duplicates of this job
+ local sha1 = redis.call('hGet',KEYS[2],id)
+ if sha1 then redis.call('hDel',KEYS[3],sha1) end
+ redis.call('hDel',KEYS[2],id)
+ -- Return the job data
+ return item
+LUA;
+ return $conn->luaEval( $script,
+ array(
+ $this->getQueueKey( 'l-unclaimed' ), # KEYS[1]
+ $this->getQueueKey( 'h-sha1ById' ), # KEYS[2]
+ $this->getQueueKey( 'h-idBySha1' ), # KEYS[3]
+ $this->getQueueKey( 'h-data' ), # KEYS[4]
+ ),
+ 4 # number of first argument(s) that are keys
+ );
+ }
+
+ /**
+ * @param RedisConnRef $conn
+ * @return array serialized string or false
+ * @throws RedisException
+ */
+ protected function popAndAcquireBlob( RedisConnRef $conn ) {
+ static $script =
+<<<LUA
+ -- Pop an item off the queue
+ local id = redis.call('rPop',KEYS[1])
+ if not id then return false end
+ -- Allow new duplicates of this job
+ local sha1 = redis.call('hGet',KEYS[2],id)
+ if sha1 then redis.call('hDel',KEYS[3],sha1) end
+ redis.call('hDel',KEYS[2],id)
+ -- Mark the jobs as claimed and return it
+ redis.call('zAdd',KEYS[4],ARGV[1],id)
+ redis.call('hIncrBy',KEYS[5],id,1)
+ return redis.call('hGet',KEYS[6],id)
+LUA;
+ return $conn->luaEval( $script,
+ array(
+ $this->getQueueKey( 'l-unclaimed' ), # KEYS[1]
+ $this->getQueueKey( 'h-sha1ById' ), # KEYS[2]
+ $this->getQueueKey( 'h-idBySha1' ), # KEYS[3]
+ $this->getQueueKey( 'z-claimed' ), # KEYS[4]
+ $this->getQueueKey( 'h-attempts' ), # KEYS[5]
+ $this->getQueueKey( 'h-data' ), # KEYS[6]
+ time(), # ARGV[1] (injected to be replication-safe)
+ ),
+ 6 # number of first argument(s) that are keys
+ );
+ }
+
+ /**
+ * @see JobQueue::doAck()
+ * @param Job $job
+ * @return Job|bool
+ * @throws MWException
+ */
+ protected function doAck( Job $job ) {
+ if ( !isset( $job->metadata['uuid'] ) ) {
+ throw new MWException( "Job of type '{$job->getType()}' has no UUID." );
+ }
+ if ( $this->claimTTL > 0 ) {
+ $conn = $this->getConnection();
+ try {
+ static $script =
+<<<LUA
+ -- Unmark the job as claimed
+ redis.call('zRem',KEYS[1],ARGV[1])
+ redis.call('hDel',KEYS[2],ARGV[1])
+ -- Delete the job data itself
+ return redis.call('hDel',KEYS[3],ARGV[1])
+LUA;
+ $res = $conn->luaEval( $script,
+ array(
+ $this->getQueueKey( 'z-claimed' ), # KEYS[1]
+ $this->getQueueKey( 'h-attempts' ), # KEYS[2]
+ $this->getQueueKey( 'h-data' ), # KEYS[3]
+ $job->metadata['uuid'] # ARGV[1]
+ ),
+ 3 # number of first argument(s) that are keys
+ );
+
+ if ( !$res ) {
+ wfDebugLog( 'JobQueueRedis', "Could not acknowledge {$this->type} job." );
+ return false;
+ }
+ } catch ( RedisException $e ) {
+ $this->throwRedisException( $this->server, $conn, $e );
+ }
+ }
+ return true;
+ }
+
+ /**
+ * @see JobQueue::doDeduplicateRootJob()
+ * @param Job $job
+ * @return bool
+ * @throws MWException
+ */
+ protected function doDeduplicateRootJob( Job $job ) {
+ if ( !$job->hasRootJobParams() ) {
+ throw new MWException( "Cannot register root job; missing parameters." );
+ }
+ $params = $job->getRootJobParams();
+
+ $key = $this->getRootJobCacheKey( $params['rootJobSignature'] );
+
+ $conn = $this->getConnection();
+ try {
+ $timestamp = $conn->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 $conn->set( $key, $params['rootJobTimestamp'], self::ROOTJOB_TTL ); // 2 weeks
+ } catch ( RedisException $e ) {
+ $this->throwRedisException( $this->server, $conn, $e );
+ }
+ }
+
+ /**
+ * @see JobQueue::doIsRootJobOldDuplicate()
+ * @param Job $job
+ * @return bool
+ */
+ protected function doIsRootJobOldDuplicate( Job $job ) {
+ if ( !$job->hasRootJobParams() ) {
+ return false; // job has no de-deplication info
+ }
+ $params = $job->getRootJobParams();
+
+ $conn = $this->getConnection();
+ try {
+ // Get the last time this root job was enqueued
+ $timestamp = $conn->get( $this->getRootJobCacheKey( $params['rootJobSignature'] ) );
+ } catch ( RedisException $e ) {
+ $this->throwRedisException( $this->server, $conn, $e );
+ }
+
+ // Check if a new root job was started at the location after this one's...
+ return ( $timestamp && $timestamp > $params['rootJobTimestamp'] );
+ }
+
+ /**
+ * @see JobQueue::doDelete()
+ * @return bool
+ */
+ protected function doDelete() {
+ static $props = array( 'l-unclaimed', 'z-claimed', 'z-abandoned',
+ 'z-delayed', 'h-idBySha1', 'h-sha1ById', 'h-attempts', 'h-data' );
+
+ $conn = $this->getConnection();
+ try {
+ $keys = array();
+ foreach ( $props as $prop ) {
+ $keys[] = $this->getQueueKey( $prop );
+ }
+ return ( $conn->delete( $keys ) !== false );
+ } catch ( RedisException $e ) {
+ $this->throwRedisException( $this->server, $conn, $e );
+ }
+ }
+
+ /**
+ * @see JobQueue::getAllQueuedJobs()
+ * @return Iterator
+ */
+ public function getAllQueuedJobs() {
+ $conn = $this->getConnection();
+ try {
+ $that = $this;
+ return new MappedIterator(
+ $conn->lRange( $this->getQueueKey( 'l-unclaimed' ), 0, -1 ),
+ function( $uid ) use ( $that, $conn ) {
+ return $that->getJobFromUidInternal( $uid, $conn );
+ },
+ array( 'accept' => function ( $job ) { return is_object( $job ); } )
+ );
+ } catch ( RedisException $e ) {
+ $this->throwRedisException( $this->server, $conn, $e );
+ }
+ }
+
+ /**
+ * @see JobQueue::getAllQueuedJobs()
+ * @return Iterator
+ */
+ public function getAllDelayedJobs() {
+ $conn = $this->getConnection();
+ try {
+ $that = $this;
+ return new MappedIterator( // delayed jobs
+ $conn->zRange( $this->getQueueKey( 'z-delayed' ), 0, -1 ),
+ function( $uid ) use ( $that, $conn ) {
+ return $that->getJobFromUidInternal( $uid, $conn );
+ },
+ array( 'accept' => function ( $job ) { return is_object( $job ); } )
+ );
+ } catch ( RedisException $e ) {
+ $this->throwRedisException( $this->server, $conn, $e );
+ }
+ }
+
+ public function getCoalesceLocationInternal() {
+ return "RedisServer:" . $this->server;
+ }
+
+ protected function doGetSiblingQueuesWithJobs( array $types ) {
+ return array_keys( array_filter( $this->doGetSiblingQueueSizes( $types ) ) );
+ }
+
+ protected function doGetSiblingQueueSizes( array $types ) {
+ $sizes = array(); // (type => size)
+ $types = array_values( $types ); // reindex
+ try {
+ $conn = $this->getConnection();
+ $conn->multi( Redis::PIPELINE );
+ foreach ( $types as $type ) {
+ $conn->lSize( $this->getQueueKey( 'l-unclaimed', $type ) );
+ }
+ $res = $conn->exec();
+ if ( is_array( $res ) ) {
+ foreach ( $res as $i => $size ) {
+ $sizes[$types[$i]] = $size;
+ }
+ }
+ } catch ( RedisException $e ) {
+ $this->throwRedisException( $this->server, $conn, $e );
+ }
+ return $sizes;
+ }
+
+ /**
+ * This function should not be called outside JobQueueRedis
+ *
+ * @param $uid string
+ * @param $conn RedisConnRef
+ * @return Job|bool Returns false if the job does not exist
+ * @throws MWException
+ */
+ public function getJobFromUidInternal( $uid, RedisConnRef $conn ) {
+ try {
+ $data = $conn->hGet( $this->getQueueKey( 'h-data' ), $uid );
+ if ( $data === false ) {
+ return false; // not found
+ }
+ $item = $this->unserialize( $conn->hGet( $this->getQueueKey( 'h-data' ), $uid ) );
+ if ( !is_array( $item ) ) { // this shouldn't happen
+ throw new MWException( "Could not find job with ID '$uid'." );
+ }
+ $title = Title::makeTitle( $item['namespace'], $item['title'] );
+ $job = Job::factory( $item['type'], $title, $item['params'] );
+ $job->metadata['uuid'] = $item['uuid'];
+ return $job;
+ } catch ( RedisException $e ) {
+ $this->throwRedisException( $this->server, $conn, $e );
+ }
+ }
+
+ /**
+ * Release any ready delayed jobs into the queue
+ *
+ * @return integer Number of jobs released
+ * @throws MWException
+ */
+ public function releaseReadyDelayedJobs() {
+ $count = 0;
+
+ $conn = $this->getConnection();
+ try {
+ static $script =
+<<<LUA
+ -- Get the list of ready delayed jobs, sorted by readiness
+ local ids = redis.call('zRangeByScore',KEYS[1],0,ARGV[1])
+ -- Migrate the jobs from the "delayed" set to the "unclaimed" list
+ for k,id in ipairs(ids) do
+ redis.call('lPush',KEYS[2],id)
+ redis.call('zRem',KEYS[1],id)
+ end
+ return #ids
+LUA;
+ $count += (int)$conn->luaEval( $script,
+ array(
+ $this->getQueueKey( 'z-delayed' ), // KEYS[1]
+ $this->getQueueKey( 'l-unclaimed' ), // KEYS[2]
+ time() // ARGV[1]; max "delay until" UNIX timestamp
+ ),
+ 2 # first two arguments are keys
+ );
+ } catch ( RedisException $e ) {
+ $this->throwRedisException( $this->server, $conn, $e );
+ }
+
+ return $count;
+ }
+
+ /**
+ * Recycle or destroy any jobs that have been claimed for too long
+ *
+ * @return integer Number of jobs recycled/deleted
+ * @throws MWException
+ */
+ public function recycleAndDeleteStaleJobs() {
+ if ( $this->claimTTL <= 0 ) { // sanity
+ throw new MWException( "Cannot recycle jobs since acknowledgements are disabled." );
+ }
+ $count = 0;
+ // For each job item that can be retried, we need to add it back to the
+ // main queue and remove it from the list of currenty claimed job items.
+ // For those that cannot, they are marked as dead and kept around for
+ // investigation and manual job restoration but are eventually deleted.
+ $conn = $this->getConnection();
+ try {
+ $now = time();
+ static $script =
+<<<LUA
+ local released,abandoned,pruned = 0,0,0
+ -- Get all non-dead jobs that have an expired claim on them.
+ -- The score for each item is the last claim timestamp (UNIX).
+ local staleClaims = redis.call('zRangeByScore',KEYS[1],0,ARGV[1])
+ for k,id in ipairs(staleClaims) do
+ local timestamp = redis.call('zScore',KEYS[1],id)
+ local attempts = redis.call('hGet',KEYS[2],id)
+ if attempts < ARGV[3] then
+ -- Claim expired and retries left: re-enqueue the job
+ redis.call('lPush',KEYS[3],id)
+ redis.call('hIncrBy',KEYS[2],id,1)
+ released = released + 1
+ else
+ -- Claim expired and no retries left: mark the job as dead
+ redis.call('zAdd',KEYS[5],timestamp,id)
+ abandoned = abandoned + 1
+ end
+ redis.call('zRem',KEYS[1],id)
+ end
+ -- Get all of the dead jobs that have been marked as dead for too long.
+ -- The score for each item is the last claim timestamp (UNIX).
+ local deadClaims = redis.call('zRangeByScore',KEYS[5],0,ARGV[2])
+ for k,id in ipairs(deadClaims) do
+ -- Stale and out of retries: remove any traces of the job
+ redis.call('zRem',KEYS[5],id)
+ redis.call('hDel',KEYS[2],id)
+ redis.call('hDel',KEYS[4],id)
+ pruned = pruned + 1
+ end
+ return {released,abandoned,pruned}
+LUA;
+ $res = $conn->luaEval( $script,
+ array(
+ $this->getQueueKey( 'z-claimed' ), # KEYS[1]
+ $this->getQueueKey( 'h-attempts' ), # KEYS[2]
+ $this->getQueueKey( 'l-unclaimed' ), # KEYS[3]
+ $this->getQueueKey( 'h-data' ), # KEYS[4]
+ $this->getQueueKey( 'z-abandoned' ), # KEYS[5]
+ $now - $this->claimTTL, # ARGV[1]
+ $now - self::MAX_AGE_PRUNE, # ARGV[2]
+ $this->maxTries # ARGV[3]
+ ),
+ 5 # number of first argument(s) that are keys
+ );
+ if ( $res ) {
+ list( $released, $abandoned, $pruned ) = $res;
+ $count += $released + $pruned;
+ JobQueue::incrStats( 'job-recycle', $this->type, $released );
+ JobQueue::incrStats( 'job-abandon', $this->type, $abandoned );
+ }
+ } catch ( RedisException $e ) {
+ $this->throwRedisException( $this->server, $conn, $e );
+ }
+
+ return $count;
+ }
+
+ /**
+ * @return Array
+ */
+ protected function doGetPeriodicTasks() {
+ $tasks = array();
+ if ( $this->claimTTL > 0 ) {
+ $tasks['recycleAndDeleteStaleJobs'] = array(
+ 'callback' => array( $this, 'recycleAndDeleteStaleJobs' ),
+ 'period' => ceil( $this->claimTTL / 2 )
+ );
+ }
+ if ( $this->checkDelay ) {
+ $tasks['releaseReadyDelayedJobs'] = array(
+ 'callback' => array( $this, 'releaseReadyDelayedJobs' ),
+ 'period' => 300 // 5 minutes
+ );
+ }
+ return $tasks;
+ }
+
+ /**
+ * @param $job Job
+ * @return array
+ */
+ protected function getNewJobFields( Job $job ) {
+ return array(
+ // Fields that describe the nature of the job
+ 'type' => $job->getType(),
+ 'namespace' => $job->getTitle()->getNamespace(),
+ 'title' => $job->getTitle()->getDBkey(),
+ 'params' => $job->getParams(),
+ // Some jobs cannot run until a "release timestamp"
+ 'rtimestamp' => $job->getReleaseTimestamp() ?: 0,
+ // Additional job metadata
+ 'uuid' => UIDGenerator::newRawUUIDv4( UIDGenerator::QUICK_RAND ),
+ 'sha1' => $job->ignoreDuplicates()
+ ? wfBaseConvert( sha1( serialize( $job->getDeduplicationInfo() ) ), 16, 36, 31 )
+ : '',
+ 'timestamp' => time() // UNIX timestamp
+ );
+ }
+
+ /**
+ * @param $fields array
+ * @return Job|bool
+ */
+ protected function getJobFromFields( array $fields ) {
+ $title = Title::makeTitleSafe( $fields['namespace'], $fields['title'] );
+ if ( $title ) {
+ $job = Job::factory( $fields['type'], $title, $fields['params'] );
+ $job->metadata['uuid'] = $fields['uuid'];
+ return $job;
+ }
+ return false;
+ }
+
+ /**
+ * @param array $fields
+ * @return string Serialized and possibly compressed version of $fields
+ */
+ protected function serialize( array $fields ) {
+ $blob = serialize( $fields );
+ if ( $this->compression === 'gzip'
+ && strlen( $blob ) >= 1024 && function_exists( 'gzdeflate' ) )
+ {
+ $object = (object)array( 'blob' => gzdeflate( $blob ), 'enc' => 'gzip' );
+ $blobz = serialize( $object );
+ return ( strlen( $blobz ) < strlen( $blob ) ) ? $blobz : $blob;
+ } else {
+ return $blob;
+ }
+ }
+
+ /**
+ * @param string $blob
+ * @return array|bool Unserialized version of $blob or false
+ */
+ protected function unserialize( $blob ) {
+ $fields = unserialize( $blob );
+ if ( is_object( $fields ) ) {
+ if ( $fields->enc === 'gzip' && function_exists( 'gzinflate' ) ) {
+ $fields = unserialize( gzinflate( $fields->blob ) );
+ } else {
+ $fields = false;
+ }
+ }
+ return is_array( $fields ) ? $fields : false;
+ }
+
+ /**
+ * 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() {
+ $conn = $this->redisPool->getConnection( $this->server );
+ if ( !$conn ) {
+ throw new JobQueueConnectionError( "Unable to connect to redis server." );
+ }
+ return $conn;
+ }
+
+ /**
+ * @param $server string
+ * @param $conn RedisConnRef
+ * @param $e RedisException
+ * @throws MWException
+ */
+ protected function throwRedisException( $server, RedisConnRef $conn, $e ) {
+ $this->redisPool->handleException( $server, $conn, $e );
+ throw new JobQueueError( "Redis server error: {$e->getMessage()}\n" );
+ }
+
+ /**
+ * @param $prop string
+ * @param $type string|null
+ * @return string
+ */
+ private function getQueueKey( $prop, $type = null ) {
+ $type = is_string( $type ) ? $type : $this->type;
+ list( $db, $prefix ) = wfSplitWikiID( $this->wiki );
+ if ( strlen( $this->key ) ) { // namespaced queue (for testing)
+ return wfForeignMemcKey( $db, $prefix, 'jobqueue', $type, $this->key, $prop );
+ } else {
+ return wfForeignMemcKey( $db, $prefix, 'jobqueue', $type, $prop );
+ }
+ }
+
+ /**
+ * @param $key string
+ * @return void
+ */
+ public function setTestingPrefix( $key ) {
+ $this->key = $key;
+ }
+}
diff --git a/includes/job/JobQueueAggregator.php b/includes/job/aggregator/JobQueueAggregator.php
index 3dba3c53..a8186abd 100644
--- a/includes/job/JobQueueAggregator.php
+++ b/includes/job/aggregator/JobQueueAggregator.php
@@ -119,6 +119,23 @@ abstract class JobQueueAggregator {
abstract protected function doGetAllReadyWikiQueues();
/**
+ * Purge all of the aggregator information
+ *
+ * @return bool Success
+ */
+ final public function purge() {
+ wfProfileIn( __METHOD__ );
+ $res = $this->doPurge();
+ wfProfileOut( __METHOD__ );
+ return $res;
+ }
+
+ /**
+ * @see JobQueueAggregator::purge()
+ */
+ abstract protected function doPurge();
+
+ /**
* Get all databases that have a pending job.
* This poll all the queues and is this expensive.
*
diff --git a/includes/job/JobQueueAggregatorMemc.php b/includes/job/aggregator/JobQueueAggregatorMemc.php
index 4b82cf92..9434da04 100644
--- a/includes/job/JobQueueAggregatorMemc.php
+++ b/includes/job/aggregator/JobQueueAggregatorMemc.php
@@ -91,9 +91,9 @@ class JobQueueAggregatorMemc extends JobQueueAggregator {
if ( $this->cache->add( "$key:rebuild", 1, 1800 ) ) { // lock
$pendingDbInfo = array(
'pendingDBs' => $this->findPendingWikiQueues(),
- 'timestamp' => time()
+ 'timestamp' => time()
);
- for ( $attempts=1; $attempts <= 25; ++$attempts ) {
+ 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
@@ -109,6 +109,13 @@ class JobQueueAggregatorMemc extends JobQueueAggregator {
}
/**
+ * @see JobQueueAggregator::doPurge()
+ */
+ protected function doPurge() {
+ return $this->cache->delete( $this->getReadyQueueCacheKey() );
+ }
+
+ /**
* @return string
*/
private function getReadyQueueCacheKey() {
diff --git a/includes/job/JobQueueAggregatorRedis.php b/includes/job/aggregator/JobQueueAggregatorRedis.php
index 74e9171c..c6a799df 100644
--- a/includes/job/JobQueueAggregatorRedis.php
+++ b/includes/job/aggregator/JobQueueAggregatorRedis.php
@@ -25,6 +25,7 @@
* Class to handle tracking information about all queues using PhpRedis
*
* @ingroup JobQueue
+ * @ingroup Redis
* @since 1.21
*/
class JobQueueAggregatorRedis extends JobQueueAggregator {
@@ -100,8 +101,18 @@ class JobQueueAggregatorRedis extends JobQueueAggregator {
$pendingDBs[$type][] = $wiki;
}
} else { // cache miss
+ // Avoid duplicated effort
+ $conn->multi( Redis::MULTI );
+ $conn->setnx( $this->getReadyQueueKey() . ":lock", 1 );
+ $conn->expire( $this->getReadyQueueKey() . ":lock", 3600 );
+ if ( $conn->exec() !== array( true, true ) ) { // lock
+ return array(); // already in progress
+ }
+
$pendingDBs = $this->findPendingWikiQueues(); // (type => list of wikis)
+ $conn->delete( $this->getReadyQueueKey() . ":lock" ); // unlock
+
$now = time();
$map = array();
foreach ( $pendingDBs as $type => $wikis ) {
@@ -120,6 +131,23 @@ class JobQueueAggregatorRedis extends JobQueueAggregator {
}
/**
+ * @see JobQueueAggregator::doPurge()
+ */
+ protected function doPurge() {
+ $conn = $this->getConnection();
+ if ( !$conn ) {
+ return false;
+ }
+ try {
+ $conn->delete( $this->getReadyQueueKey() );
+ } catch ( RedisException $e ) {
+ $this->handleException( $conn, $e );
+ return false;
+ }
+ return true;
+ }
+
+ /**
* Get a connection to the server that handles all sub-queues for this queue
*
* @return Array (server name, Redis instance)
diff --git a/includes/job/jobs/AssembleUploadChunksJob.php b/includes/job/jobs/AssembleUploadChunksJob.php
index c5dd9eaa..6237e568 100644
--- a/includes/job/jobs/AssembleUploadChunksJob.php
+++ b/includes/job/jobs/AssembleUploadChunksJob.php
@@ -42,6 +42,15 @@ class AssembleUploadChunksJob extends Job {
return false;
}
+ if ( count( $_SESSION ) === 0 ) {
+ // Empty session probably indicates that we didn't associate
+ // with the session correctly. Note that being able to load
+ // the user does not necessarily mean the session was loaded.
+ // Most likely cause by suhosin.session.encrypt = On.
+ $this->setLastError( "Error associating with user session. Try setting suhosin.session.encrypt = Off" );
+ return false;
+ }
+
UploadBase::setSessionStatus(
$this->params['filekey'],
array( 'result' => 'Poll', 'stage' => 'assembling', 'status' => Status::newGood() )
@@ -82,11 +91,11 @@ class AssembleUploadChunksJob extends Job {
UploadBase::setSessionStatus(
$this->params['filekey'],
array(
- 'result' => 'Success',
- 'stage' => 'assembling',
- 'filekey' => $newFileKey,
+ 'result' => 'Success',
+ 'stage' => 'assembling',
+ 'filekey' => $newFileKey,
'imageinfo' => $imageInfo,
- 'status' => Status::newGood()
+ 'status' => Status::newGood()
)
);
} catch ( MWException $e ) {
@@ -94,7 +103,7 @@ class AssembleUploadChunksJob extends Job {
$this->params['filekey'],
array(
'result' => 'Failure',
- 'stage' => 'assembling',
+ 'stage' => 'assembling',
'status' => Status::newFatal( 'api-error-stashfailed' )
)
);
diff --git a/includes/job/jobs/DoubleRedirectJob.php b/includes/job/jobs/DoubleRedirectJob.php
index 05abeeef..33e749b8 100644
--- a/includes/job/jobs/DoubleRedirectJob.php
+++ b/includes/job/jobs/DoubleRedirectJob.php
@@ -90,40 +90,40 @@ class DoubleRedirectJob extends Job {
$targetRev = Revision::newFromTitle( $this->title, false, Revision::READ_LATEST );
if ( !$targetRev ) {
- wfDebug( __METHOD__.": target redirect already deleted, ignoring\n" );
+ wfDebug( __METHOD__ . ": target redirect already deleted, ignoring\n" );
return true;
}
$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" );
+ wfDebug( __METHOD__ . ": Redirect has changed since the job was queued\n" );
return true;
}
- # Check for a suppression tag (used e.g. in periodically archived discussions)
+ // Check for a suppression tag (used e.g. in periodically archived discussions)
$mw = MagicWord::get( 'staticredirect' );
if ( $content->matchMagicWord( $mw ) ) {
- wfDebug( __METHOD__.": skipping: suppressed with __STATICREDIRECT__\n" );
+ wfDebug( __METHOD__ . ": skipping: suppressed with __STATICREDIRECT__\n" );
return true;
}
- # Find the current final destination
+ // Find the current final destination
$newTitle = self::getFinalDestination( $this->redirTitle );
if ( !$newTitle ) {
- wfDebug( __METHOD__.": skipping: single redirect, circular redirect or invalid redirect destination\n" );
+ wfDebug( __METHOD__ . ": skipping: single redirect, circular redirect or invalid redirect destination\n" );
return true;
}
if ( $newTitle->equals( $this->redirTitle ) ) {
- # The redirect is already right, no need to change it
- # This can happen if the page was moved back (say after vandalism)
- wfDebug( __METHOD__.": skipping, already good\n" );
+ // The redirect is already right, no need to change it
+ // This can happen if the page was moved back (say after vandalism)
+ wfDebug( __METHOD__ . " : skipping, already good\n" );
}
- # Preserve fragment (bug 14904)
+ // Preserve fragment (bug 14904)
$newTitle = Title::makeTitle( $newTitle->getNamespace(), $newTitle->getDBkey(),
$currentDest->getFragment(), $newTitle->getInterwiki() );
- # Fix the text
+ // Fix the text
$newContent = $content->updateRedirect( $newTitle );
if ( $newContent->equals( $content ) ) {
@@ -137,11 +137,13 @@ class DoubleRedirectJob extends Job {
return false;
}
- # Save it
+ // Save it
global $wgUser;
$oldUser = $wgUser;
$wgUser = $user;
$article = WikiPage::factory( $this->title );
+
+ // Messages: double-redirect-fixed-move, double-redirect-fixed-maintenance
$reason = wfMessage( 'double-redirect-fixed-' . $this->reason,
$this->redirTitle->getPrefixedText(), $newTitle->getPrefixedText()
)->inContentLanguage()->text();
@@ -161,7 +163,8 @@ class DoubleRedirectJob extends Job {
public static function getFinalDestination( $title ) {
$dbw = wfGetDB( DB_MASTER );
- $seenTitles = array(); # Circular redirect check
+ // Circular redirect check
+ $seenTitles = array();
$dest = false;
while ( true ) {
diff --git a/includes/job/jobs/DuplicateJob.php b/includes/job/jobs/DuplicateJob.php
index 524983b8..be1bfe5c 100644
--- a/includes/job/jobs/DuplicateJob.php
+++ b/includes/job/jobs/DuplicateJob.php
@@ -45,7 +45,7 @@ final class DuplicateJob extends Job {
* @return Job
*/
public static function newFromJob( Job $job ) {
- $djob = new self( $job->getTitle(), $job->getParams(), $job->getId() );
+ $djob = new self( $job->getTitle(), $job->getParams(), $job->id );
$djob->command = $job->getType();
$djob->params = is_array( $djob->params ) ? $djob->params : array();
$djob->params = array( 'isDuplicate' => true ) + $djob->params;
diff --git a/includes/job/jobs/EnotifNotifyJob.php b/includes/job/jobs/EnotifNotifyJob.php
index 2be05b63..bbe988d0 100644
--- a/includes/job/jobs/EnotifNotifyJob.php
+++ b/includes/job/jobs/EnotifNotifyJob.php
@@ -35,7 +35,7 @@ class EnotifNotifyJob extends Job {
function run() {
$enotif = new EmailNotification();
// Get the user from ID (rename safe). Anons are 0, so defer to name.
- if( isset( $this->params['editorID'] ) && $this->params['editorID'] ) {
+ if ( isset( $this->params['editorID'] ) && $this->params['editorID'] ) {
$editor = User::newFromId( $this->params['editorID'] );
// B/C, only the name might be given.
} else {
diff --git a/includes/job/jobs/HTMLCacheUpdateJob.php b/includes/job/jobs/HTMLCacheUpdateJob.php
index 818c6abf..44c240bb 100644
--- a/includes/job/jobs/HTMLCacheUpdateJob.php
+++ b/includes/job/jobs/HTMLCacheUpdateJob.php
@@ -79,8 +79,16 @@ class HTMLCacheUpdateJob extends Job {
* Update all of the backlinks
*/
protected function doFullUpdate() {
+ global $wgMaxBacklinksInvalidate;
+
# Get an estimate of the number of rows from the BacklinkCache
- $numRows = $this->blCache->getNumLinks( $this->params['table'] );
+ $max = max( $this->rowsPerJob * 2, $wgMaxBacklinksInvalidate ) + 1;
+ $numRows = $this->blCache->getNumLinks( $this->params['table'], $max );
+ if ( $wgMaxBacklinksInvalidate !== false && $numRows > $wgMaxBacklinksInvalidate ) {
+ wfDebug( "Skipped HTML cache invalidation of {$this->title->getPrefixedText()}." );
+ return true;
+ }
+
if ( $numRows > $this->rowsPerJob * 2 ) {
# Do fast cached partition
$this->insertPartitionJobs();
@@ -90,12 +98,13 @@ class HTMLCacheUpdateJob extends Job {
# 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" );
+ wfDebug( __METHOD__ . ": row count estimate was incorrect, repartitioning\n" );
$this->insertJobsFromTitles( $titleArray );
} else {
$this->invalidateTitles( $titleArray ); // just do the query
}
}
+
return true;
}
@@ -145,7 +154,7 @@ class HTMLCacheUpdateJob extends Job {
array(
'table' => $this->params['table'],
'start' => $start,
- 'end' => $id - 1
+ 'end' => $id - 1
) + $rootJobParams // carry over information for de-duplication
);
$start = $id;
@@ -158,16 +167,16 @@ class HTMLCacheUpdateJob extends Job {
array(
'table' => $this->params['table'],
'start' => $start,
- 'end' => $this->params['end']
+ 'end' => $this->params['end']
) + $rootJobParams // carry over information for de-duplication
);
- wfDebug( __METHOD__.": repartitioning into " . count( $jobs ) . " jobs\n" );
+ 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" );
+ wfDebug( __METHOD__ . ": repartitioning failed!\n" );
$this->invalidateTitles( $titleArray );
} else {
JobQueueGroup::singleton()->push( $jobs );
@@ -194,7 +203,7 @@ class HTMLCacheUpdateJob extends Job {
array(
'table' => $this->params['table'],
'start' => $start,
- 'end' => $end,
+ 'end' => $end,
) + $rootJobParams // carry over information for de-duplication
);
}
@@ -245,7 +254,7 @@ class HTMLCacheUpdateJob extends Job {
}
# Update file cache
- if ( $wgUseFileCache ) {
+ if ( $wgUseFileCache ) {
foreach ( $titleArray as $title ) {
HTMLFileCache::clearFileCache( $title );
}
diff --git a/includes/job/jobs/NullJob.php b/includes/job/jobs/NullJob.php
index d282a8e6..b6164a5d 100644
--- a/includes/job/jobs/NullJob.php
+++ b/includes/job/jobs/NullJob.php
@@ -26,6 +26,22 @@
* 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.
*
+ * @par Example:
+ * Inserting a null job in the configured job queue:
+ * @code
+ * $ php maintenance/eval.php
+ * > $queue = JobQueueGroup::singleton();
+ * > $job = new NullJob( Title::newMainPage(), array( 'lives' => 10 ) );
+ * > $queue->push( $job );
+ * @endcode
+ * You can then confirm the job has been enqueued by using the showJobs.php
+ * maintenance utility:
+ * @code
+ * $ php maintenance/showJobs.php --group
+ * null: 1 queue; 0 claimed (0 active, 0 abandoned)
+ * $
+ * @endcode
+ *
* @ingroup JobQueue
*/
class NullJob extends Job {
diff --git a/includes/job/jobs/PublishStashedFileJob.php b/includes/job/jobs/PublishStashedFileJob.php
index d3feda28..5a24f93c 100644
--- a/includes/job/jobs/PublishStashedFileJob.php
+++ b/includes/job/jobs/PublishStashedFileJob.php
@@ -42,13 +42,23 @@ class PublishStashedFileJob extends Job {
return false;
}
+ if ( count( $_SESSION ) === 0 ) {
+ // Empty session probably indicates that we didn't associate
+ // with the session correctly. Note that being able to load
+ // the user does not necessarily mean the session was loaded.
+ // Most likely cause by suhosin.session.encrypt = On.
+ $this->setLastError( "Error associating with user session. Try setting suhosin.session.encrypt = Off" );
+ 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
+ // @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.
@@ -94,11 +104,11 @@ class PublishStashedFileJob extends Job {
UploadBase::setSessionStatus(
$this->params['filekey'],
array(
- 'result' => 'Success',
- 'stage' => 'publish',
- 'filename' => $upload->getLocalFile()->getName(),
+ 'result' => 'Success',
+ 'stage' => 'publish',
+ 'filename' => $upload->getLocalFile()->getName(),
'imageinfo' => $imageInfo,
- 'status' => Status::newGood()
+ 'status' => Status::newGood()
)
);
} catch ( MWException $e ) {
@@ -106,7 +116,7 @@ class PublishStashedFileJob extends Job {
$this->params['filekey'],
array(
'result' => 'Failure',
- 'stage' => 'publish',
+ 'stage' => 'publish',
'status' => Status::newFatal( 'api-error-publishfailed' )
)
);
diff --git a/includes/job/jobs/RefreshLinksJob.php b/includes/job/jobs/RefreshLinksJob.php
index 9dbe8278..4fc8bac6 100644
--- a/includes/job/jobs/RefreshLinksJob.php
+++ b/includes/job/jobs/RefreshLinksJob.php
@@ -37,21 +37,18 @@ class RefreshLinksJob extends 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'] ) ) {
+ if ( isset( $this->params['masterPos'] ) && $this->params['masterPos'] !== false ) {
wfGetLB()->waitFor( $this->params['masterPos'] );
}
@@ -59,13 +56,11 @@ class RefreshLinksJob extends Job {
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;
}
@@ -101,6 +96,9 @@ class RefreshLinksJob extends Job {
$updates = $content->getSecondaryDataUpdates( $title, null, false, $parserOutput );
DataUpdate::runUpdates( $updates );
+
+ InfoAction::invalidateCache( $title );
+
wfProfileOut( $fname );
}
}
@@ -114,6 +112,8 @@ class RefreshLinksJob extends Job {
class RefreshLinksJob2 extends Job {
function __construct( $title, $params, $id = 0 ) {
parent::__construct( 'refreshLinks2', $title, $params, $id );
+ // Base jobs for large templates can easily be de-duplicated
+ $this->removeDuplicates = !isset( $params['start'] ) && !isset( $params['end'] );
}
/**
@@ -123,14 +123,11 @@ class RefreshLinksJob2 extends Job {
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;
}
@@ -144,7 +141,7 @@ class RefreshLinksJob2 extends Job {
// 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 ) {
+ } elseif ( wfGetLB()->getServerCount() > 1 ) {
$masterPos = wfGetLB()->getMasterPos();
} else {
$masterPos = false;
@@ -158,7 +155,7 @@ class RefreshLinksJob2 extends Job {
$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 ) {
+ if ( $tbc->getNumLinks( $table, $wgUpdateRowsPerJob + 1 ) <= $wgUpdateRowsPerJob ) {
# Just directly insert the single per-title jobs
$jobs = array_merge( $jobs, $this->getSingleTitleJobs( $table, $masterPos ) );
} else {
@@ -167,10 +164,10 @@ class RefreshLinksJob2 extends Job {
list( $start, $end ) = $batch;
$jobs[] = new RefreshLinksJob2( $this->title,
array(
- 'table' => $table,
- 'start' => $start,
- 'end' => $end,
- 'masterPos' => $masterPos,
+ 'table' => $table,
+ 'start' => $start,
+ 'end' => $end,
+ 'masterPos' => $masterPos,
) + $this->getRootJobParams() // carry over information for de-duplication
);
}
@@ -181,7 +178,6 @@ class RefreshLinksJob2 extends Job {
JobQueueGroup::singleton()->push( $jobs );
}
- wfProfileOut( __METHOD__ );
return true;
}
diff --git a/includes/job/jobs/UploadFromUrlJob.php b/includes/job/jobs/UploadFromUrlJob.php
index 87549140..c993cfb4 100644
--- a/includes/job/jobs/UploadFromUrlJob.php
+++ b/includes/job/jobs/UploadFromUrlJob.php
@@ -48,6 +48,7 @@ class UploadFromUrlJob extends Job {
}
public function run() {
+ global $wgCopyUploadAsyncTimeout;
# Initialize this object and the upload object
$this->upload = new UploadFromUrl();
$this->upload->initialize(
@@ -58,7 +59,11 @@ class UploadFromUrlJob extends Job {
$this->user = User::newFromName( $this->params['userName'] );
# Fetch the file
- $status = $this->upload->fetchFile();
+ $opts = array();
+ if ( $wgCopyUploadAsyncTimeout ) {
+ $opts['timeout'] = $wgCopyUploadAsyncTimeout;
+ }
+ $status = $this->upload->fetchFile( $opts );
if ( !$status->isOk() ) {
$this->leaveMessage( $status );
return true;
diff --git a/includes/json/FormatJson.php b/includes/json/FormatJson.php
index eececcba..d6116512 100644
--- a/includes/json/FormatJson.php
+++ b/includes/json/FormatJson.php
@@ -1,6 +1,6 @@
<?php
/**
- * Simple wrapper for json_encode and json_decode that falls back on Services_JSON class.
+ * Wrapper for json_encode and json_decode.
*
* 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,50 +20,217 @@
* @file
*/
-require_once __DIR__ . '/Services_JSON.php';
-
/**
* JSON formatter wrapper class
*/
class FormatJson {
/**
+ * Skip escaping most characters above U+007F for readability and compactness.
+ * This encoding option saves 3 to 8 bytes (uncompressed) for each such character;
+ * however, it could break compatibility with systems that incorrectly handle UTF-8.
+ *
+ * @since 1.22
+ */
+ const UTF8_OK = 1;
+
+ /**
+ * Skip escaping the characters '<', '>', and '&', which have special meanings in
+ * HTML and XML.
+ *
+ * @warning Do not use this option for JSON that could end up in inline scripts.
+ * - HTML5, §4.3.1.2 Restrictions for contents of script elements
+ * - XML 1.0 (5th Ed.), §2.4 Character Data and Markup
+ *
+ * @since 1.22
+ */
+ const XMLMETA_OK = 2;
+
+ /**
+ * Skip escaping as many characters as reasonably possible.
+ *
+ * @warning When generating inline script blocks, use FormatJson::UTF8_OK instead.
+ *
+ * @since 1.22
+ */
+ const ALL_OK = 3;
+
+ /**
+ * Regex that matches whitespace inside empty arrays and objects.
+ *
+ * This doesn't affect regular strings inside the JSON because those can't
+ * have a real line break (\n) in them, at this point they are already escaped
+ * as the string "\n" which this doesn't match.
+ *
+ * @private
+ */
+ const WS_CLEANUP_REGEX = '/(?<=[\[{])\n\s*+(?=[\]}])/';
+
+ /**
+ * Characters problematic in JavaScript.
+ *
+ * @note These are listed in ECMA-262 (5.1 Ed.), §7.3 Line Terminators along with U+000A (LF)
+ * and U+000D (CR). However, PHP already escapes LF and CR according to RFC 4627.
+ */
+ private static $badChars = array(
+ "\xe2\x80\xa8", // U+2028 LINE SEPARATOR
+ "\xe2\x80\xa9", // U+2029 PARAGRAPH SEPARATOR
+ );
+
+ /**
+ * Escape sequences for characters listed in FormatJson::$badChars.
+ */
+ private static $badCharsEscaped = array(
+ '\u2028', // U+2028 LINE SEPARATOR
+ '\u2029', // U+2029 PARAGRAPH SEPARATOR
+ );
+
+ /**
* Returns the JSON representation of a value.
*
- * @param $value Mixed: the value being encoded. Can be any type except a resource.
- * @param $pretty Boolean: If true, adds non-significant whitespace to improve readability.
+ * @note Empty arrays are encoded as numeric arrays, not as objects, so cast any associative
+ * array that might be empty to an object before encoding it.
*
- * @return string
+ * @note In pre-1.22 versions of MediaWiki, using this function for generating inline script
+ * blocks may result in an XSS vulnerability, and quite likely will in XML documents
+ * (cf. FormatJson::XMLMETA_OK). Use Xml::encodeJsVar() instead in such cases.
+ *
+ * @param mixed $value The value to encode. Can be any type except a resource.
+ * @param bool $pretty If true, add non-significant whitespace to improve readability.
+ * @param int $escaping Bitfield consisting of _OK class constants
+ * @return string|bool: String if successful; false upon failure
*/
- 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, $pretty );
- } else {
- return json_encode( $value, $pretty ? JSON_PRETTY_PRINT : 0 );
+ public static function encode( $value, $pretty = false, $escaping = 0 ) {
+ if ( defined( 'JSON_UNESCAPED_UNICODE' ) ) {
+ return self::encode54( $value, $pretty, $escaping );
}
+ return self::encode53( $value, $pretty, $escaping );
}
/**
* Decodes a JSON string.
*
- * @param string $value the json string being decoded.
- * @param $assoc Boolean: when true, returned objects will be converted into associative arrays.
+ * @param string $value The JSON string being decoded
+ * @param bool $assoc When true, returned objects will be converted into associative arrays.
*
- * @return Mixed: the value encoded in json in appropriate PHP type.
- * Values true, false and null (case-insensitive) are returned as true, false
- * and "&null;" respectively. "&null;" is returned if the json cannot be
- * decoded or if the encoded data is deeper than the recursion limit.
+ * @return mixed: the value encoded in JSON in appropriate PHP type.
+ * `null` is returned if the JSON cannot be decoded or if the encoded data is deeper than
+ * the recursion limit.
*/
public static function decode( $value, $assoc = false ) {
- if ( !function_exists( 'json_decode' ) ) {
- $json = $assoc ? new Services_JSON( SERVICES_JSON_LOOSE_TYPE ) :
- new Services_JSON();
- $jsonDec = $json->decode( $value );
- return $jsonDec;
- } else {
- return json_decode( $value, $assoc );
+ return json_decode( $value, $assoc );
+ }
+
+ /**
+ * JSON encoder wrapper for PHP >= 5.4, which supports useful encoding options.
+ *
+ * @param mixed $value
+ * @param bool $pretty
+ * @param int $escaping
+ * @return string|bool
+ */
+ private static function encode54( $value, $pretty, $escaping ) {
+ // PHP escapes '/' to prevent breaking out of inline script blocks using '</script>',
+ // which is hardly useful when '<' and '>' are escaped (and inadequate), and such
+ // escaping negatively impacts the human readability of URLs and similar strings.
+ $options = JSON_UNESCAPED_SLASHES;
+ $options |= $pretty ? JSON_PRETTY_PRINT : 0;
+ $options |= ( $escaping & self::UTF8_OK ) ? JSON_UNESCAPED_UNICODE : 0;
+ $options |= ( $escaping & self::XMLMETA_OK ) ? 0 : ( JSON_HEX_TAG | JSON_HEX_AMP );
+ $json = json_encode( $value, $options );
+ if ( $json === false ) {
+ return false;
}
+
+ if ( $pretty ) {
+ // Remove whitespace inside empty arrays/objects; different JSON encoders
+ // vary on this, and we want our output to be consistent across implementations.
+ $json = preg_replace( self::WS_CLEANUP_REGEX, '', $json );
+ }
+ if ( $escaping & self::UTF8_OK ) {
+ $json = str_replace( self::$badChars, self::$badCharsEscaped, $json );
+ }
+ return $json;
}
+ /**
+ * JSON encoder wrapper for PHP 5.3, which lacks native support for some encoding options.
+ * Therefore, the missing options are implemented here purely in PHP code.
+ *
+ * @param mixed $value
+ * @param bool $pretty
+ * @param int $escaping
+ * @return string|bool
+ */
+ private static function encode53( $value, $pretty, $escaping ) {
+ $options = ( $escaping & self::XMLMETA_OK ) ? 0 : ( JSON_HEX_TAG | JSON_HEX_AMP );
+ $json = json_encode( $value, $options );
+ if ( $json === false ) {
+ return false;
+ }
+
+ // Emulate JSON_UNESCAPED_SLASHES. Because the JSON contains no unescaped slashes
+ // (only escaped slashes), a simple string replacement works fine.
+ $json = str_replace( '\/', '/', $json );
+
+ if ( $escaping & self::UTF8_OK ) {
+ // JSON hex escape sequences follow the format \uDDDD, where DDDD is four hex digits
+ // indicating the equivalent UTF-16 code unit's value. To most efficiently unescape
+ // them, we exploit the JSON extension's built-in decoder.
+ // * We escape the input a second time, so any such sequence becomes \\uDDDD.
+ // * To avoid interpreting escape sequences that were in the original input,
+ // each double-escaped backslash (\\\\) is replaced with \\\u005c.
+ // * We strip one of the backslashes from each of the escape sequences to unescape.
+ // * Then the JSON decoder can perform the actual unescaping.
+ $json = str_replace( "\\\\\\\\", "\\\\\\u005c", addcslashes( $json, '\"' ) );
+ $json = json_decode( preg_replace( "/\\\\\\\\u(?!00[0-7])/", "\\\\u", "\"$json\"" ) );
+ $json = str_replace( self::$badChars, self::$badCharsEscaped, $json );
+ }
+
+ if ( $pretty ) {
+ return self::prettyPrint( $json );
+ }
+ return $json;
+ }
+
+ /**
+ * Adds non-significant whitespace to an existing JSON representation of an object.
+ * Only needed for PHP < 5.4, which lacks the JSON_PRETTY_PRINT option.
+ *
+ * @param string $json
+ * @return string
+ */
+ private static function prettyPrint( $json ) {
+ $buf = '';
+ $indent = 0;
+ $json = strtr( $json, array( '\\\\' => '\\\\', '\"' => "\x01" ) );
+ for ( $i = 0, $n = strlen( $json ); $i < $n; $i += $skip ) {
+ $skip = 1;
+ switch ( $json[$i] ) {
+ case ':':
+ $buf .= ': ';
+ break;
+ case '[':
+ case '{':
+ ++$indent;
+ // falls through
+ case ',':
+ $buf .= $json[$i] . "\n" . str_repeat( ' ', $indent );
+ break;
+ case ']':
+ case '}':
+ $buf .= "\n" . str_repeat( ' ', --$indent ) . $json[$i];
+ break;
+ case '"':
+ $skip = strcspn( $json, '"', $i + 1 ) + 2;
+ $buf .= substr( $json, $i, $skip );
+ break;
+ default:
+ $skip = strcspn( $json, ',]}"', $i + 1 ) + 1;
+ $buf .= substr( $json, $i, $skip );
+ }
+ }
+ $buf = preg_replace( self::WS_CLEANUP_REGEX, '', $buf );
+ return str_replace( "\x01", '\"', $buf );
+ }
}
diff --git a/includes/json/Services_JSON.php b/includes/json/Services_JSON.php
deleted file mode 100644
index b7c101a1..00000000
--- a/includes/json/Services_JSON.php
+++ /dev/null
@@ -1,882 +0,0 @@
-<?php
-/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
-
-/**
-* Converts to and from JSON format.
-*
-* JSON (JavaScript Object Notation) is a lightweight data-interchange
-* format. It is easy for humans to read and write. It is easy for machines
-* to parse and generate. It is based on a subset of the JavaScript
-* Programming Language, Standard ECMA-262 3rd Edition - December 1999.
-* This feature can also be found in Python. JSON is a text format that is
-* completely language independent but uses conventions that are familiar
-* to programmers of the C-family of languages, including C, C++, C#, Java,
-* JavaScript, Perl, TCL, and many others. These properties make JSON an
-* ideal data-interchange language.
-*
-* This package provides a simple encoder and decoder for JSON notation. It
-* is intended for use with client-side Javascript applications that make
-* use of HTTPRequest to perform server communication functions - data can
-* be encoded into JSON notation for use in a client-side javascript, or
-* decoded from incoming Javascript requests. JSON format is native to
-* Javascript, and can be directly eval()'ed with no further parsing
-* overhead
-*
-* All strings should be in ASCII or UTF-8 format!
-*
-* LICENSE: Redistribution and use in source and binary forms, with or
-* without modification, are permitted provided that the following
-* conditions are met: Redistributions of source code must retain the
-* above copyright notice, this list of conditions and the following
-* disclaimer. Redistributions in binary form must reproduce the above
-* copyright notice, this list of conditions and the following disclaimer
-* in the documentation and/or other materials provided with the
-* distribution.
-*
-* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
-* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
-* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
-* NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
-* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
-* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
-* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
-* DAMAGE.
-*
-* @file
-* @ingroup API
-* @author Michal Migurski <mike-json@teczno.com>
-* @author Matt Knapp <mdknapp[at]gmail[dot]com>
-* @author Brett Stimmerman <brettstimmerman[at]gmail[dot]com>
-* @copyright 2005 Michal Migurski
-* @version CVS: $Id$
-* @license http://www.opensource.org/licenses/bsd-license.php
-* @see http://pear.php.net/pepr/pepr-proposal-show.php?id=198
-*/
-
-/**
-* Marker constant for Services_JSON::decode(), used to flag stack state
-*/
-define('SERVICES_JSON_SLICE', 1);
-
-/**
-* Marker constant for Services_JSON::decode(), used to flag stack state
-*/
-define('SERVICES_JSON_IN_STR', 2);
-
-/**
-* Marker constant for Services_JSON::decode(), used to flag stack state
-*/
-define('SERVICES_JSON_IN_ARR', 3);
-
-/**
-* Marker constant for Services_JSON::decode(), used to flag stack state
-*/
-define('SERVICES_JSON_IN_OBJ', 4);
-
-/**
-* Marker constant for Services_JSON::decode(), used to flag stack state
-*/
-define('SERVICES_JSON_IN_CMT', 5);
-
-/**
-* Behavior switch for Services_JSON::decode()
-*/
-define('SERVICES_JSON_LOOSE_TYPE', 16);
-
-/**
-* Behavior switch for Services_JSON::decode()
-*/
-define('SERVICES_JSON_SUPPRESS_ERRORS', 32);
-
-/**
- * Converts to and from JSON format.
- *
- * Brief example of use:
- *
- * <code>
- * // create a new instance of Services_JSON
- * $json = new Services_JSON();
- *
- * // 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);
- *
- * print($output);
- * // prints: ["foo","bar",[1,2,"baz"],[3,[4]]]
- *
- * // accept incoming POST data, assumed to be in JSON notation
- * $input = file_get_contents('php://input', 1000000);
- * $value = $json->decode($input);
- * </code>
- *
- * @ingroup API
- */
-class Services_JSON
-{
- /**
- * constructs a new JSON instance
- *
- * @param $use Integer: object behavior flags; combine with boolean-OR
- *
- * possible values:
- * - SERVICES_JSON_LOOSE_TYPE: loose typing.
- * "{...}" syntax creates associative arrays
- * instead of objects in decode().
- * - SERVICES_JSON_SUPPRESS_ERRORS: error suppression.
- * Values which can't be encoded (e.g. resources)
- * appear as NULL instead of throwing errors.
- * By default, a deeply-nested resource will
- * bubble up with an error, so all return values
- * from encode() should be checked with isError()
- */
- function __construct($use = 0)
- {
- $this->use = $use;
- }
-
- private static $mHavePear = null;
- /**
- * Returns cached result of class_exists('pear'), to avoid calling AutoLoader numerous times
- * in cases when PEAR is not present.
- * @return boolean
- */
- private static function pearInstalled() {
- if ( self::$mHavePear === null ) {
- self::$mHavePear = class_exists( 'pear' );
- }
- return self::$mHavePear;
- }
-
- /**
- * convert a string from one UTF-16 char to one UTF-8 char
- *
- * Normally should be handled by mb_convert_encoding, but
- * provides a slower PHP-only method for installations
- * that lack the multibyte string extension.
- *
- * @param string $utf16 UTF-16 character
- * @return String: UTF-8 character
- * @access private
- */
- function utf162utf8($utf16)
- {
- // oh please oh please oh please oh please oh please
- if(function_exists('mb_convert_encoding')) {
- return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16');
- }
-
- $bytes = (ord($utf16[0]) << 8) | ord($utf16[1]);
-
- switch(true) {
- case ((0x7F & $bytes) == $bytes):
- // this case should never be reached, because we are in ASCII range
- // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
- return chr(0x7F & $bytes);
-
- case (0x07FF & $bytes) == $bytes:
- // return a 2-byte UTF-8 character
- // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
- return chr(0xC0 | (($bytes >> 6) & 0x1F))
- . chr(0x80 | ($bytes & 0x3F));
-
- case (0xFC00 & $bytes) == 0xD800 && strlen($utf16) >= 4 && (0xFC & ord($utf16[2])) == 0xDC:
- // return a 4-byte UTF-8 character
- $char = ((($bytes & 0x03FF) << 10)
- | ((ord($utf16[2]) & 0x03) << 8)
- | ord($utf16[3]));
- $char += 0x10000;
- return chr(0xF0 | (($char >> 18) & 0x07))
- . chr(0x80 | (($char >> 12) & 0x3F))
- . chr(0x80 | (($char >> 6) & 0x3F))
- . chr(0x80 | ($char & 0x3F));
-
- case (0xFFFF & $bytes) == $bytes:
- // return a 3-byte UTF-8 character
- // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
- return chr(0xE0 | (($bytes >> 12) & 0x0F))
- . chr(0x80 | (($bytes >> 6) & 0x3F))
- . chr(0x80 | ($bytes & 0x3F));
- }
-
- // ignoring UTF-32 for now, sorry
- return '';
- }
-
- /**
- * convert a string from one UTF-8 char to one UTF-16 char
- *
- * Normally should be handled by mb_convert_encoding, but
- * provides a slower PHP-only method for installations
- * that lack the multibyte string extension.
- *
- * @param string $utf8 UTF-8 character
- * @return String: UTF-16 character
- * @access private
- */
- function utf82utf16($utf8)
- {
- // oh please oh please oh please oh please oh please
- if(function_exists('mb_convert_encoding')) {
- return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8');
- }
-
- switch(strlen($utf8)) {
- case 1:
- // this case should never be reached, because we are in ASCII range
- // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
- return $utf8;
-
- case 2:
- // return a UTF-16 character from a 2-byte UTF-8 char
- // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
- return chr(0x07 & (ord($utf8[0]) >> 2))
- . chr((0xC0 & (ord($utf8[0]) << 6))
- | (0x3F & ord($utf8[1])));
-
- case 3:
- // return a UTF-16 character from a 3-byte UTF-8 char
- // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
- return chr((0xF0 & (ord($utf8[0]) << 4))
- | (0x0F & (ord($utf8[1]) >> 2)))
- . chr((0xC0 & (ord($utf8[1]) << 6))
- | (0x7F & ord($utf8[2])));
-
- case 4:
- // return a UTF-16 surrogate pair from a 4-byte UTF-8 char
- if(ord($utf8[0]) > 0xF4) return ''; # invalid
- $char = ((0x1C0000 & (ord($utf8[0]) << 18))
- | (0x03F000 & (ord($utf8[1]) << 12))
- | (0x000FC0 & (ord($utf8[2]) << 6))
- | (0x00003F & ord($utf8[3])));
- if($char > 0x10FFFF) return ''; # invalid
- $char -= 0x10000;
- return chr(0xD8 | (($char >> 18) & 0x03))
- . chr(($char >> 10) & 0xFF)
- . chr(0xDC | (($char >> 8) & 0x03))
- . chr($char & 0xFF);
- }
-
- // ignoring UTF-32 for now, sorry
- return '';
- }
-
- /**
- * 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 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
- *
- * @return mixed JSON string representation of input var or an error if a problem occurs
- * @access public
- */
- function encode($var, $pretty=false)
- {
- $this->indent = 0;
- $this->pretty = $pretty;
- $this->nameValSeparator = $pretty ? ': ' : ':';
- 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 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
- * @access private
- */
- function encode2($var)
- {
- if ($this->pretty) {
- $close = "\n" . str_repeat("\t", $this->indent);
- $open = $close . "\t";
- $mid = ',' . $open;
- }
- else {
- $open = $close = '';
- $mid = ',';
- }
-
- switch (gettype($var)) {
- case 'boolean':
- return $var ? 'true' : 'false';
-
- case 'NULL':
- return 'null';
-
- case 'integer':
- return (int) $var;
-
- case 'double':
- case 'float':
- return (float) $var;
-
- case 'string':
- // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT
- $ascii = '';
- $strlen_var = strlen($var);
-
- /*
- * Iterate over every character in the string,
- * escaping with a slash or encoding to UTF-8 where necessary
- */
- for ($c = 0; $c < $strlen_var; ++$c) {
-
- $ord_var_c = ord($var[$c]);
-
- switch (true) {
- case $ord_var_c == 0x08:
- $ascii .= '\b';
- break;
- case $ord_var_c == 0x09:
- $ascii .= '\t';
- break;
- case $ord_var_c == 0x0A:
- $ascii .= '\n';
- break;
- case $ord_var_c == 0x0C:
- $ascii .= '\f';
- break;
- case $ord_var_c == 0x0D:
- $ascii .= '\r';
- break;
-
- case $ord_var_c == 0x22:
- case $ord_var_c == 0x2F:
- case $ord_var_c == 0x5C:
- // double quote, slash, slosh
- $ascii .= '\\'.$var[$c];
- break;
-
- case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)):
- // characters U-00000000 - U-0000007F (same as ASCII)
- $ascii .= $var[$c];
- break;
-
- case (($ord_var_c & 0xE0) == 0xC0):
- // characters U-00000080 - U-000007FF, mask 110XXXXX
- // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
- $char = pack('C*', $ord_var_c, ord($var[$c + 1]));
- $c += 1;
- $utf16 = $this->utf82utf16($char);
- $ascii .= sprintf('\u%04s', bin2hex($utf16));
- break;
-
- case (($ord_var_c & 0xF0) == 0xE0):
- // characters U-00000800 - U-0000FFFF, mask 1110XXXX
- // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
- $char = pack('C*', $ord_var_c,
- ord($var[$c + 1]),
- ord($var[$c + 2]));
- $c += 2;
- $utf16 = $this->utf82utf16($char);
- $ascii .= sprintf('\u%04s', bin2hex($utf16));
- break;
-
- case (($ord_var_c & 0xF8) == 0xF0):
- // characters U-00010000 - U-001FFFFF, mask 11110XXX
- // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
- // These will always return a surrogate pair
- $char = pack('C*', $ord_var_c,
- ord($var[$c + 1]),
- ord($var[$c + 2]),
- ord($var[$c + 3]));
- $c += 3;
- $utf16 = $this->utf82utf16($char);
- if($utf16 == '') {
- $ascii .= '\ufffd';
- } else {
- $utf16 = str_split($utf16, 2);
- $ascii .= sprintf('\u%04s\u%04s', bin2hex($utf16[0]), bin2hex($utf16[1]));
- }
- break;
- }
- }
-
- return '"'.$ascii.'"';
-
- case 'array':
- /*
- * As per JSON spec if any array key is not an integer
- * we must treat the the whole array as an object. We
- * also try to catch a sparsely populated associative
- * array with numeric keys here because some JS engines
- * will create an array with empty indexes up to
- * max_index which can cause memory issues and because
- * the keys, which may be relevant, will be remapped
- * otherwise.
- *
- * As per the ECMA and JSON specification an object may
- * have any string as a property. Unfortunately due to
- * a hole in the ECMA specification if the key is a
- * ECMA reserved word or starts with a digit the
- * parameter is only accessible using ECMAScript's
- * bracket notation.
- */
-
- // treat as a JSON object
- 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),
- array_values($var));
- $this->indent--;
-
- foreach($properties as $property) {
- if($this->isError($property)) {
- return $property;
- }
- }
-
- return '{' . $open . join($mid, $properties) . $close . '}';
- }
-
- // treat it like a regular array
- $this->indent++;
- $elements = array_map(array($this, 'encode2'), $var);
- $this->indent--;
-
- foreach($elements as $element) {
- if($this->isError($element)) {
- return $element;
- }
- }
-
- return '[' . $open . join($mid, $elements) . $close . ']';
-
- case 'object':
- $vars = get_object_vars($var);
-
- $this->indent++;
- $properties = array_map(array($this, 'name_value'),
- array_keys($vars),
- array_values($vars));
- $this->indent--;
-
- foreach($properties as $property) {
- if($this->isError($property)) {
- return $property;
- }
- }
-
- return '{' . $open . join($mid, $properties) . $close . '}';
-
- default:
- return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS)
- ? 'null'
- : new Services_JSON_Error(gettype($var)." can not be encoded as JSON string");
- }
- }
-
- /**
- * array-walking function for use in generating JSON-formatted name-value pairs
- *
- * @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'
- * @access private
- */
- function name_value($name, $value)
- {
- $encoded_value = $this->encode2($value);
-
- if($this->isError($encoded_value)) {
- return $encoded_value;
- }
-
- return $this->encode2(strval($name)) . $this->nameValSeparator . $encoded_value;
- }
-
- /**
- * reduce a string by removing leading and trailing 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
- */
- function reduce_string($str)
- {
- $str = preg_replace(array(
-
- // eliminate single line comments in '// ...' form
- '#^\s*//(.+)$#m',
-
- // eliminate multi-line comments in '/* ... */' form, at start of string
- '#^\s*/\*(.+)\*/#Us',
-
- // eliminate multi-line comments in '/* ... */' form, at end of string
- '#/\*(.+)\*/\s*$#Us'
-
- ), '', $str);
-
- // eliminate extraneous space
- return trim($str);
- }
-
- /**
- * decodes a JSON string into appropriate variable
- *
- * @param string $str JSON-formatted string
- *
- * @return mixed number, boolean, string, array, or object
- * corresponding to given JSON input string.
- * See argument 1 to Services_JSON() above for object-output behavior.
- * Note that decode() always returns strings
- * in ASCII or UTF-8 format!
- * @access public
- */
- function decode($str)
- {
- $str = $this->reduce_string($str);
-
- switch (strtolower($str)) {
- case 'true':
- return true;
-
- case 'false':
- return false;
-
- case 'null':
- return null;
-
- default:
- $m = array();
-
- if (is_numeric($str)) {
- // Lookie-loo, it's a number
-
- // This would work on its own, but I'm trying to be
- // good about returning integers where appropriate:
- // return (float)$str;
-
- // Return float or int, as appropriate
- return ((float)$str == (integer)$str)
- ? (integer)$str
- : (float)$str;
-
- } elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) {
- // STRINGS RETURNED IN UTF-8 FORMAT
- $delim = substr($str, 0, 1);
- $chrs = substr($str, 1, -1);
- $utf8 = '';
- $strlen_chrs = strlen($chrs);
-
- for ($c = 0; $c < $strlen_chrs; ++$c) {
-
- $substr_chrs_c_2 = substr($chrs, $c, 2);
- $ord_chrs_c = ord($chrs[$c]);
-
- switch (true) {
- case $substr_chrs_c_2 == '\b':
- $utf8 .= chr(0x08);
- ++$c;
- break;
- case $substr_chrs_c_2 == '\t':
- $utf8 .= chr(0x09);
- ++$c;
- break;
- case $substr_chrs_c_2 == '\n':
- $utf8 .= chr(0x0A);
- ++$c;
- break;
- case $substr_chrs_c_2 == '\f':
- $utf8 .= chr(0x0C);
- ++$c;
- break;
- case $substr_chrs_c_2 == '\r':
- $utf8 .= chr(0x0D);
- ++$c;
- break;
-
- case $substr_chrs_c_2 == '\\"':
- case $substr_chrs_c_2 == '\\\'':
- case $substr_chrs_c_2 == '\\\\':
- case $substr_chrs_c_2 == '\\/':
- if (($delim == '"' && $substr_chrs_c_2 != '\\\'') ||
- ($delim == "'" && $substr_chrs_c_2 != '\\"')) {
- $utf8 .= $chrs[++$c];
- }
- break;
-
- case preg_match('/\\\uD[89AB][0-9A-F]{2}\\\uD[C-F][0-9A-F]{2}/i', substr($chrs, $c, 12)):
- // escaped unicode surrogate pair
- $utf16 = chr(hexdec(substr($chrs, ($c + 2), 2)))
- . chr(hexdec(substr($chrs, ($c + 4), 2)))
- . chr(hexdec(substr($chrs, ($c + 8), 2)))
- . chr(hexdec(substr($chrs, ($c + 10), 2)));
- $utf8 .= $this->utf162utf8($utf16);
- $c += 11;
- break;
-
- case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6)):
- // single, escaped unicode character
- $utf16 = chr(hexdec(substr($chrs, ($c + 2), 2)))
- . chr(hexdec(substr($chrs, ($c + 4), 2)));
- $utf8 .= $this->utf162utf8($utf16);
- $c += 5;
- break;
-
- case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F):
- $utf8 .= $chrs[$c];
- break;
-
- case ($ord_chrs_c & 0xE0) == 0xC0:
- // characters U-00000080 - U-000007FF, mask 110XXXXX
- //see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
- $utf8 .= substr($chrs, $c, 2);
- ++$c;
- break;
-
- case ($ord_chrs_c & 0xF0) == 0xE0:
- // characters U-00000800 - U-0000FFFF, mask 1110XXXX
- // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
- $utf8 .= substr($chrs, $c, 3);
- $c += 2;
- break;
-
- case ($ord_chrs_c & 0xF8) == 0xF0:
- // characters U-00010000 - U-001FFFFF, mask 11110XXX
- // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
- $utf8 .= substr($chrs, $c, 4);
- $c += 3;
- break;
-
- case ($ord_chrs_c & 0xFC) == 0xF8:
- // characters U-00200000 - U-03FFFFFF, mask 111110XX
- // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
- $utf8 .= substr($chrs, $c, 5);
- $c += 4;
- break;
-
- case ($ord_chrs_c & 0xFE) == 0xFC:
- // characters U-04000000 - U-7FFFFFFF, mask 1111110X
- // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
- $utf8 .= substr($chrs, $c, 6);
- $c += 5;
- break;
-
- }
-
- }
-
- return $utf8;
-
- } elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) {
- // array, or object notation
-
- if ($str[0] == '[') {
- $stk = array(SERVICES_JSON_IN_ARR);
- $arr = array();
- } else {
- if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
- $stk = array(SERVICES_JSON_IN_OBJ);
- $obj = array();
- } else {
- $stk = array(SERVICES_JSON_IN_OBJ);
- $obj = new stdClass();
- }
- }
-
- array_push($stk, array( 'what' => SERVICES_JSON_SLICE,
- 'where' => 0,
- 'delim' => false));
-
- $chrs = substr($str, 1, -1);
- $chrs = $this->reduce_string($chrs);
-
- if ($chrs == '') {
- if (reset($stk) == SERVICES_JSON_IN_ARR) {
- return $arr;
-
- } else {
- return $obj;
-
- }
- }
-
- //print("\nparsing {$chrs}\n");
-
- $strlen_chrs = strlen($chrs);
-
- for ($c = 0; $c <= $strlen_chrs; ++$c) {
-
- $top = end($stk);
- $substr_chrs_c_2 = substr($chrs, $c, 2);
-
- if (($c == $strlen_chrs) || (($chrs[$c] == ',') && ($top['what'] == SERVICES_JSON_SLICE))) {
- // found a comma that is not inside a string, array, etc.,
- // OR we've reached the end of the character list
- $slice = substr($chrs, $top['where'], ($c - $top['where']));
- array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false));
- //print("Found split at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
-
- if (reset($stk) == SERVICES_JSON_IN_ARR) {
- // we are in an array, so just push an element onto the stack
- array_push($arr, $this->decode($slice));
-
- } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
- // we are in an object, so figure
- // out the property name and set an
- // element in an associative array,
- // for now
- $parts = array();
-
- if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
- // "name":value pair
- $key = $this->decode($parts[1]);
- $val = $this->decode($parts[2]);
-
- if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
- $obj[$key] = $val;
- } else {
- $obj->$key = $val;
- }
- } elseif (preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
- // name:value pair, where name is unquoted
- $key = $parts[1];
- $val = $this->decode($parts[2]);
-
- if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
- $obj[$key] = $val;
- } else {
- $obj->$key = $val;
- }
- }
-
- }
-
- } elseif ((($chrs[$c] == '"') || ($chrs[$c] == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) {
- // found a quote, and we are not inside a string
- array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs[$c]));
- //print("Found start of string at {$c}\n");
-
- } elseif (($chrs[$c] == $top['delim']) &&
- ($top['what'] == SERVICES_JSON_IN_STR) &&
- (($chrs[$c - 1] != '\\') ||
- ($chrs[$c - 1] == '\\' && $chrs[$c - 2] == '\\'))) {
- // found a quote, we're in a string, and it's not escaped
- array_pop($stk);
- //print("Found end of string at {$c}: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n");
-
- } elseif (($chrs[$c] == '[') &&
- in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
- // found a left-bracket, and we are in an array, object, or slice
- array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false));
- //print("Found start of array at {$c}\n");
-
- } elseif (($chrs[$c] == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) {
- // found a right-bracket, and we're in an array
- array_pop($stk);
- //print("Found end of array at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
-
- } elseif (($chrs[$c] == '{') &&
- in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
- // found a left-brace, and we are in an array, object, or slice
- array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false));
- //print("Found start of object at {$c}\n");
-
- } elseif (($chrs[$c] == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) {
- // found a right-brace, and we're in an object
- array_pop($stk);
- //print("Found end of object at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
-
- } elseif (($substr_chrs_c_2 == '/*') &&
- in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
- // found a comment start, and we are in an array, object, or slice
- array_push($stk, array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false));
- $c++;
- //print("Found start of comment at {$c}\n");
-
- } elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) {
- // found a comment end, and we're in one now
- array_pop($stk);
- $c++;
-
- for ($i = $top['where']; $i <= $c; ++$i)
- $chrs = substr_replace($chrs, ' ', $i, 1);
-
- //print("Found end of comment at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
-
- }
-
- }
-
- if (reset($stk) == SERVICES_JSON_IN_ARR) {
- return $arr;
-
- } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
- return $obj;
-
- }
-
- }
- }
- }
-
- /**
- * @todo Ultimately, this should just call PEAR::isError()
- * @return bool
- */
- function isError($data, $code = null)
- {
- if ( self::pearInstalled() ) {
- //avoid some strict warnings on PEAR isError check (looks like http://pear.php.net/bugs/bug.php?id=9950 has been around for some time)
- return @PEAR::isError($data, $code);
- } elseif (is_object($data) && (get_class($data) == 'services_json_error' ||
- is_subclass_of($data, 'services_json_error'))) {
- return true;
- }
-
- return false;
- }
-}
-
-
-// Hide the PEAR_Error variant from Doxygen
-/// @cond
-if (class_exists('PEAR_Error')) {
-
- /**
- * @ingroup API
- */
- class Services_JSON_Error extends PEAR_Error
- {
- function Services_JSON_Error($message = 'unknown error', $code = null,
- $mode = null, $options = null, $userinfo = null)
- {
- parent::PEAR_Error($message, $code, $mode, $options, $userinfo);
- }
- }
-
-} else {
-/// @endcond
-
- /**
- * @todo Ultimately, this class shall be descended from PEAR_Error
- * @ingroup API
- */
- class Services_JSON_Error
- {
- function Services_JSON_Error($message = 'unknown error', $code = null,
- $mode = null, $options = null, $userinfo = null)
- {
- $this->message = $message;
- }
-
- function __toString()
- {
- return $this->message;
- }
- }
-}
diff --git a/includes/libs/CSSJanus.php b/includes/libs/CSSJanus.php
index fb1e9a4c..5a52fc7c 100644
--- a/includes/libs/CSSJanus.php
+++ b/includes/libs/CSSJanus.php
@@ -76,6 +76,10 @@ class CSSJanus {
'cursor_west' => null,
'four_notation_quantity' => null,
'four_notation_color' => null,
+ 'border_radius' => null,
+ 'box_shadow' => null,
+ 'text_shadow1' => null,
+ 'text_shadow2' => null,
'bg_horizontal_percentage' => null,
'bg_horizontal_percentage_x' => null,
);
@@ -96,9 +100,9 @@ class CSSJanus {
$patterns['ident'] = "-?{$patterns['nmstart']}{$patterns['nmchar']}*";
$patterns['quantity'] = "{$patterns['num']}(?:\s*{$patterns['unit']}|{$patterns['ident']})?";
$patterns['possibly_negative_quantity'] = "((?:-?{$patterns['quantity']})|(?:inherit|auto))";
- $patterns['color'] = "(#?{$patterns['nmchar']}+)";
+ $patterns['color'] = "(#?{$patterns['nmchar']}+|(?:rgba?|hsla?)\([ \d.,%-]+\))";
$patterns['url_chars'] = "(?:{$patterns['url_special_chars']}|{$patterns['nonAscii']}|{$patterns['escape']})*";
- $patterns['lookahead_not_open_brace'] = "(?!({$patterns['nmchar']}|\r?\n|\s|#|\:|\.|\,|\+|>)*?{)";
+ $patterns['lookahead_not_open_brace'] = "(?!({$patterns['nmchar']}|\r?\n|\s|#|\:|\.|\,|\+|>|\(|\))*?{)";
$patterns['lookahead_not_closing_paren'] = "(?!{$patterns['url_chars']}?{$patterns['valid_after_uri_chars']}\))";
$patterns['lookahead_for_closing_paren'] = "(?={$patterns['url_chars']}?{$patterns['valid_after_uri_chars']}\))";
$patterns['noflip_single'] = "/({$patterns['noflip_annotation']}{$patterns['lookahead_not_open_brace']}[^;}]+;?)/i";
@@ -113,8 +117,12 @@ class CSSJanus {
$patterns['rtl_in_url'] = "/{$patterns['lookbehind_not_letter']}(rtl){$patterns['lookahead_for_closing_paren']}/i";
$patterns['cursor_east'] = "/{$patterns['lookbehind_not_letter']}([ns]?)e-resize/";
$patterns['cursor_west'] = "/{$patterns['lookbehind_not_letter']}([ns]?)w-resize/";
- $patterns['four_notation_quantity'] = "/{$patterns['possibly_negative_quantity']}(\s+){$patterns['possibly_negative_quantity']}(\s+){$patterns['possibly_negative_quantity']}(\s+){$patterns['possibly_negative_quantity']}/i";
- $patterns['four_notation_color'] = "/(-color\s*:\s*){$patterns['color']}(\s+){$patterns['color']}(\s+){$patterns['color']}(\s+){$patterns['color']}/i";
+ $patterns['four_notation_quantity'] = "/(:\s*){$patterns['possibly_negative_quantity']}(\s+){$patterns['possibly_negative_quantity']}(\s+){$patterns['possibly_negative_quantity']}(\s+){$patterns['possibly_negative_quantity']}(\s*[;}])/i";
+ $patterns['four_notation_color'] = "/(-color\s*:\s*){$patterns['color']}(\s+){$patterns['color']}(\s+){$patterns['color']}(\s+){$patterns['color']}(\s*[;}])/i";
+ $patterns['border_radius'] = "/(border-radius\s*:\s*){$patterns['possibly_negative_quantity']}(\s+){$patterns['possibly_negative_quantity']}(\s+){$patterns['possibly_negative_quantity']}(\s+){$patterns['possibly_negative_quantity']}(\s*[;}])/i";
+ $patterns['box_shadow'] = "/(box-shadow\s*:\s*(?:inset\s*)?){$patterns['possibly_negative_quantity']}/i";
+ $patterns['text_shadow1'] = "/(text-shadow\s*:\s*){$patterns['color']}(\s*){$patterns['possibly_negative_quantity']}/i";
+ $patterns['text_shadow2'] = "/(text-shadow\s*:\s*){$patterns['possibly_negative_quantity']}/i";
// The two regexes below are parenthesized differently then in the original implementation to make the
// callback's job more straightforward
$patterns['bg_horizontal_percentage'] = "/(background(?:-position)?\s*:\s*[^%]*?)(-?{$patterns['num']})(%\s*(?:{$patterns['quantity']}|{$patterns['ident']}))/";
@@ -160,7 +168,9 @@ class CSSJanus {
$css = self::fixLeftAndRight( $css );
$css = self::fixCursorProperties( $css );
$css = self::fixFourPartNotation( $css );
+ $css = self::fixBorderRadius( $css );
$css = self::fixBackgroundPosition( $css );
+ $css = self::fixShadows( $css );
// Detokenize stuff we tokenized before
$css = $comments->detokenize( $css );
@@ -257,8 +267,58 @@ class CSSJanus {
* @return string
*/
private static function fixFourPartNotation( $css ) {
- $css = preg_replace( self::$patterns['four_notation_quantity'], '$1$2$7$4$5$6$3', $css );
- $css = preg_replace( self::$patterns['four_notation_color'], '$1$2$3$8$5$6$7$4', $css );
+ $css = preg_replace( self::$patterns['four_notation_quantity'], '$1$2$3$8$5$6$7$4$9', $css );
+ $css = preg_replace( self::$patterns['four_notation_color'], '$1$2$3$8$5$6$7$4$9', $css );
+ return $css;
+ }
+
+ /**
+ * Swaps appropriate corners in four-part border-radius rules.
+ * Needs to undo the effect of fixFourPartNotation() on those rules, too.
+ *
+ * @param $css string
+ * @return string
+ */
+ private static function fixBorderRadius( $css ) {
+ // Undo four_notation_quantity
+ $css = preg_replace( self::$patterns['border_radius'], '$1$2$3$8$5$6$7$4$9', $css );
+ // Do the real thing
+ $css = preg_replace( self::$patterns['border_radius'], '$1$4$3$2$5$8$7$6$9', $css );
+
+ return $css;
+ }
+
+ /**
+ * Negates horizontal offset in box-shadow and text-shadow rules.
+ *
+ * @param $css string
+ * @return string
+ */
+ private static function fixShadows( $css ) {
+ // Flips the sign of a CSS value, possibly with a unit.
+ // (We can't just negate the value with unary minus due to the units.)
+ $flipSign = function ( $cssValue ) {
+ // Don't mangle zeroes
+ if ( intval( $cssValue ) === 0 ) {
+ return $cssValue;
+ } elseif ( $cssValue[0] === '-' ) {
+ return substr( $cssValue, 1 );
+ } else {
+ return "-" . $cssValue;
+ }
+ };
+
+ $css = preg_replace_callback( self::$patterns['box_shadow'], function ( $matches ) use ( $flipSign ) {
+ return $matches[1] . $flipSign( $matches[2] );
+ }, $css );
+
+ $css = preg_replace_callback( self::$patterns['text_shadow1'], function ( $matches ) use ( $flipSign ) {
+ return $matches[1] . $matches[2] . $matches[3] . $flipSign( $matches[4] );
+ }, $css );
+
+ $css = preg_replace_callback( self::$patterns['text_shadow2'], function ( $matches ) use ( $flipSign ) {
+ return $matches[1] . $flipSign( $matches[2] );
+ }, $css );
return $css;
}
diff --git a/includes/libs/CSSMin.php b/includes/libs/CSSMin.php
index 8b0e2873..4f142fc7 100644
--- a/includes/libs/CSSMin.php
+++ b/includes/libs/CSSMin.php
@@ -82,10 +82,38 @@ class CSSMin {
}
/**
+ * Encode an image file as a base64 data URI.
+ * If the image file has a suitable MIME type and size, encode it as a
+ * base64 data URI. Return false if the image type is unfamiliar or exceeds
+ * the size limit.
+ *
+ * @param string $file Image file to encode.
+ * @param string|null $type File's MIME type or null. If null, CSSMin will
+ * try to autodetect the type.
+ * @param int|bool $sizeLimit If the size of the target file is greater than
+ * this value, decline to encode the image file and return false
+ * instead. If $sizeLimit is false, no limit is enforced.
+ * @return string|bool: Image contents encoded as a data URI or false.
+ */
+ public static function encodeImageAsDataURI( $file, $type = null, $sizeLimit = self::EMBED_SIZE_LIMIT ) {
+ if ( $sizeLimit !== false && filesize( $file ) >= $sizeLimit ) {
+ return false;
+ }
+ if ( $type === null ) {
+ $type = self::getMimeType( $file );
+ }
+ if ( !$type ) {
+ return false;
+ }
+ $data = base64_encode( file_get_contents( $file ) );
+ return 'data:' . $type . ';base64,' . $data;
+ }
+
+ /**
* @param $file string
* @return bool|string
*/
- protected static function getMimeType( $file ) {
+ public static function getMimeType( $file ) {
$realpath = realpath( $file );
// Try a couple of different ways to get the mime-type of a file, in order of
// preference
@@ -174,23 +202,14 @@ class CSSMin {
// using Z for the timezone, meaning GMT
$url .= '?' . gmdate( 'Y-m-d\TH:i:s\Z', round( filemtime( $file ), -2 ) );
// Embedding requires a bit of extra processing, so let's skip that if we can
- if ( $embedData && $embed ) {
- $type = self::getMimeType( $file );
- // Detect when URLs were preceeded with embed tags, and also verify file size is
- // below the limit
- if (
- $type
- && $match['embed'][1] > 0
- && filesize( $file ) < self::EMBED_SIZE_LIMIT
- ) {
- // Strip off any trailing = symbols (makes browsers freak out)
- $data = base64_encode( file_get_contents( $file ) );
+ if ( $embedData && $embed && $match['embed'][1] > 0 ) {
+ $data = self::encodeImageAsDataURI( $file );
+ if ( $data !== false ) {
// Build 2 CSS properties; one which uses a base64 encoded data URI in place
// of the @embed comment to try and retain line-number integrity, and the
// other with a remapped an versioned URL and an Internet Explorer hack
// making it ignored in all browsers that support data URIs
- $replacement = "{$pre}url(data:{$type};base64,{$data}){$post};";
- $replacement .= "{$pre}url({$url}){$post}!ie;";
+ $replacement = "{$pre}url({$data}){$post};{$pre}url({$url}){$post}!ie;";
}
}
if ( $replacement === false ) {
diff --git a/includes/libs/HttpStatus.php b/includes/libs/HttpStatus.php
index 78d81803..4f626b23 100644
--- a/includes/libs/HttpStatus.php
+++ b/includes/libs/HttpStatus.php
@@ -26,7 +26,7 @@
class HttpStatus {
/**
- * Get the message associed with the HTTP response code $code
+ * Get the message associated with HTTP response code $code
*
* Replace OutputPage::getStatusMessage( $code )
*
diff --git a/includes/libs/lessc.inc.php b/includes/libs/lessc.inc.php
new file mode 100644
index 00000000..3dce961e
--- /dev/null
+++ b/includes/libs/lessc.inc.php
@@ -0,0 +1,3742 @@
+<?php
+
+/**
+ * lessphp v0.4.0@261f1bd28f
+ * http://leafo.net/lessphp
+ *
+ * LESS CSS compiler, adapted from http://lesscss.org
+ *
+ * For ease of distribution, lessphp 0.4.0 is under a dual license.
+ * You are free to pick which one suits your needs.
+ *
+ * MIT LICENSE
+ *
+ * Copyright 2013, Leaf Corcoran <leafot@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * GPL VERSION 3
+ *
+ * Please refer to http://www.gnu.org/licenses/gpl-3.0.html for the full
+ * text of the GPL version 3
+ */
+
+
+/**
+ * The LESS compiler and parser.
+ *
+ * Converting LESS to CSS is a three stage process. The incoming file is parsed
+ * by `lessc_parser` into a syntax tree, then it is compiled into another tree
+ * representing the CSS structure by `lessc`. The CSS tree is fed into a
+ * formatter, like `lessc_formatter` which then outputs CSS as a string.
+ *
+ * During the first compile, all values are *reduced*, which means that their
+ * types are brought to the lowest form before being dump as strings. This
+ * handles math equations, variable dereferences, and the like.
+ *
+ * The `parse` function of `lessc` is the entry point.
+ *
+ * In summary:
+ *
+ * The `lessc` class creates an instance of the parser, feeds it LESS code,
+ * then transforms the resulting tree to a CSS tree. This class also holds the
+ * evaluation context, such as all available mixins and variables at any given
+ * time.
+ *
+ * The `lessc_parser` class is only concerned with parsing its input.
+ *
+ * The `lessc_formatter` takes a CSS tree, and dumps it to a formatted string,
+ * handling things like indentation.
+ */
+class lessc {
+ static public $VERSION = "v0.4.0";
+
+ static public $TRUE = array("keyword", "true");
+ static public $FALSE = array("keyword", "false");
+
+ protected $libFunctions = array();
+ protected $registeredVars = array();
+ protected $preserveComments = false;
+
+ public $vPrefix = '@'; // prefix of abstract properties
+ public $mPrefix = '$'; // prefix of abstract blocks
+ public $parentSelector = '&';
+
+ public $importDisabled = false;
+ public $importDir = '';
+
+ protected $numberPrecision = null;
+
+ protected $allParsedFiles = array();
+
+ // set to the parser that generated the current line when compiling
+ // so we know how to create error messages
+ protected $sourceParser = null;
+ protected $sourceLoc = null;
+
+ static protected $nextImportId = 0; // uniquely identify imports
+
+ // attempts to find the path of an import url, returns null for css files
+ protected function findImport($url) {
+ foreach ((array)$this->importDir as $dir) {
+ $full = $dir.(substr($dir, -1) != '/' ? '/' : '').$url;
+ if ($this->fileExists($file = $full.'.less') || $this->fileExists($file = $full)) {
+ return $file;
+ }
+ }
+
+ return null;
+ }
+
+ protected function fileExists($name) {
+ return is_file($name);
+ }
+
+ static public function compressList($items, $delim) {
+ if (!isset($items[1]) && isset($items[0])) return $items[0];
+ else return array('list', $delim, $items);
+ }
+
+ static public function preg_quote($what) {
+ return preg_quote($what, '/');
+ }
+
+ protected function tryImport($importPath, $parentBlock, $out) {
+ if ($importPath[0] == "function" && $importPath[1] == "url") {
+ $importPath = $this->flattenList($importPath[2]);
+ }
+
+ $str = $this->coerceString($importPath);
+ if ($str === null) return false;
+
+ $url = $this->compileValue($this->lib_e($str));
+
+ // don't import if it ends in css
+ if (substr_compare($url, '.css', -4, 4) === 0) return false;
+
+ $realPath = $this->findImport($url);
+
+ if ($realPath === null) return false;
+
+ if ($this->importDisabled) {
+ return array(false, "/* import disabled */");
+ }
+
+ if (isset($this->allParsedFiles[realpath($realPath)])) {
+ return array(false, null);
+ }
+
+ $this->addParsedFile($realPath);
+ $parser = $this->makeParser($realPath);
+ $root = $parser->parse(file_get_contents($realPath));
+
+ // set the parents of all the block props
+ foreach ($root->props as $prop) {
+ if ($prop[0] == "block") {
+ $prop[1]->parent = $parentBlock;
+ }
+ }
+
+ // copy mixins into scope, set their parents
+ // bring blocks from import into current block
+ // TODO: need to mark the source parser these came from this file
+ foreach ($root->children as $childName => $child) {
+ if (isset($parentBlock->children[$childName])) {
+ $parentBlock->children[$childName] = array_merge(
+ $parentBlock->children[$childName],
+ $child);
+ } else {
+ $parentBlock->children[$childName] = $child;
+ }
+ }
+
+ $pi = pathinfo($realPath);
+ $dir = $pi["dirname"];
+
+ list($top, $bottom) = $this->sortProps($root->props, true);
+ $this->compileImportedProps($top, $parentBlock, $out, $parser, $dir);
+
+ return array(true, $bottom, $parser, $dir);
+ }
+
+ protected function compileImportedProps($props, $block, $out, $sourceParser, $importDir) {
+ $oldSourceParser = $this->sourceParser;
+
+ $oldImport = $this->importDir;
+
+ // TODO: this is because the importDir api is stupid
+ $this->importDir = (array)$this->importDir;
+ array_unshift($this->importDir, $importDir);
+
+ foreach ($props as $prop) {
+ $this->compileProp($prop, $block, $out);
+ }
+
+ $this->importDir = $oldImport;
+ $this->sourceParser = $oldSourceParser;
+ }
+
+ /**
+ * Recursively compiles a block.
+ *
+ * A block is analogous to a CSS block in most cases. A single LESS document
+ * is encapsulated in a block when parsed, but it does not have parent tags
+ * so all of it's children appear on the root level when compiled.
+ *
+ * Blocks are made up of props and children.
+ *
+ * Props are property instructions, array tuples which describe an action
+ * to be taken, eg. write a property, set a variable, mixin a block.
+ *
+ * The children of a block are just all the blocks that are defined within.
+ * This is used to look up mixins when performing a mixin.
+ *
+ * Compiling the block involves pushing a fresh environment on the stack,
+ * and iterating through the props, compiling each one.
+ *
+ * See lessc::compileProp()
+ *
+ */
+ protected function compileBlock($block) {
+ switch ($block->type) {
+ case "root":
+ $this->compileRoot($block);
+ break;
+ case null:
+ $this->compileCSSBlock($block);
+ break;
+ case "media":
+ $this->compileMedia($block);
+ break;
+ case "directive":
+ $name = "@" . $block->name;
+ if (!empty($block->value)) {
+ $name .= " " . $this->compileValue($this->reduce($block->value));
+ }
+
+ $this->compileNestedBlock($block, array($name));
+ break;
+ default:
+ $this->throwError("unknown block type: $block->type\n");
+ }
+ }
+
+ protected function compileCSSBlock($block) {
+ $env = $this->pushEnv();
+
+ $selectors = $this->compileSelectors($block->tags);
+ $env->selectors = $this->multiplySelectors($selectors);
+ $out = $this->makeOutputBlock(null, $env->selectors);
+
+ $this->scope->children[] = $out;
+ $this->compileProps($block, $out);
+
+ $block->scope = $env; // mixins carry scope with them!
+ $this->popEnv();
+ }
+
+ protected function compileMedia($media) {
+ $env = $this->pushEnv($media);
+ $parentScope = $this->mediaParent($this->scope);
+
+ $query = $this->compileMediaQuery($this->multiplyMedia($env));
+
+ $this->scope = $this->makeOutputBlock($media->type, array($query));
+ $parentScope->children[] = $this->scope;
+
+ $this->compileProps($media, $this->scope);
+
+ if (count($this->scope->lines) > 0) {
+ $orphanSelelectors = $this->findClosestSelectors();
+ if (!is_null($orphanSelelectors)) {
+ $orphan = $this->makeOutputBlock(null, $orphanSelelectors);
+ $orphan->lines = $this->scope->lines;
+ array_unshift($this->scope->children, $orphan);
+ $this->scope->lines = array();
+ }
+ }
+
+ $this->scope = $this->scope->parent;
+ $this->popEnv();
+ }
+
+ protected function mediaParent($scope) {
+ while (!empty($scope->parent)) {
+ if (!empty($scope->type) && $scope->type != "media") {
+ break;
+ }
+ $scope = $scope->parent;
+ }
+
+ return $scope;
+ }
+
+ protected function compileNestedBlock($block, $selectors) {
+ $this->pushEnv($block);
+ $this->scope = $this->makeOutputBlock($block->type, $selectors);
+ $this->scope->parent->children[] = $this->scope;
+
+ $this->compileProps($block, $this->scope);
+
+ $this->scope = $this->scope->parent;
+ $this->popEnv();
+ }
+
+ protected function compileRoot($root) {
+ $this->pushEnv();
+ $this->scope = $this->makeOutputBlock($root->type);
+ $this->compileProps($root, $this->scope);
+ $this->popEnv();
+ }
+
+ protected function compileProps($block, $out) {
+ foreach ($this->sortProps($block->props) as $prop) {
+ $this->compileProp($prop, $block, $out);
+ }
+ $out->lines = $this->deduplicate($out->lines);
+ }
+
+ /**
+ * Deduplicate lines in a block. Comments are not deduplicated. If a
+ * duplicate rule is detected, the comments immediately preceding each
+ * occurence are consolidated.
+ */
+ protected function deduplicate($lines) {
+ $unique = array();
+ $comments = array();
+
+ foreach($lines as $line) {
+ if (strpos($line, '/*') === 0) {
+ $comments[] = $line;
+ continue;
+ }
+ if (!in_array($line, $unique)) {
+ $unique[] = $line;
+ }
+ array_splice($unique, array_search($line, $unique), 0, $comments);
+ $comments = array();
+ }
+ return array_merge($unique, $comments);
+ }
+
+ protected function sortProps($props, $split = false) {
+ $vars = array();
+ $imports = array();
+ $other = array();
+ $stack = array();
+
+ foreach ($props as $prop) {
+ switch ($prop[0]) {
+ case "comment":
+ $stack[] = $prop;
+ break;
+ case "assign":
+ $stack[] = $prop;
+ if (isset($prop[1][0]) && $prop[1][0] == $this->vPrefix) {
+ $vars = array_merge($vars, $stack);
+ } else {
+ $other = array_merge($other, $stack);
+ }
+ $stack = array();
+ break;
+ case "import":
+ $id = self::$nextImportId++;
+ $prop[] = $id;
+ $stack[] = $prop;
+ $imports = array_merge($imports, $stack);
+ $other[] = array("import_mixin", $id);
+ $stack = array();
+ break;
+ default:
+ $stack[] = $prop;
+ $other = array_merge($other, $stack);
+ $stack = array();
+ break;
+ }
+ }
+ $other = array_merge($other, $stack);
+
+ if ($split) {
+ return array(array_merge($vars, $imports), $other);
+ } else {
+ return array_merge($vars, $imports, $other);
+ }
+ }
+
+ protected function compileMediaQuery($queries) {
+ $compiledQueries = array();
+ foreach ($queries as $query) {
+ $parts = array();
+ foreach ($query as $q) {
+ switch ($q[0]) {
+ case "mediaType":
+ $parts[] = implode(" ", array_slice($q, 1));
+ break;
+ case "mediaExp":
+ if (isset($q[2])) {
+ $parts[] = "($q[1]: " .
+ $this->compileValue($this->reduce($q[2])) . ")";
+ } else {
+ $parts[] = "($q[1])";
+ }
+ break;
+ case "variable":
+ $parts[] = $this->compileValue($this->reduce($q));
+ break;
+ }
+ }
+
+ if (count($parts) > 0) {
+ $compiledQueries[] = implode(" and ", $parts);
+ }
+ }
+
+ $out = "@media";
+ if (!empty($parts)) {
+ $out .= " " .
+ implode($this->formatter->selectorSeparator, $compiledQueries);
+ }
+ return $out;
+ }
+
+ protected function multiplyMedia($env, $childQueries = null) {
+ if (is_null($env) ||
+ !empty($env->block->type) && $env->block->type != "media")
+ {
+ return $childQueries;
+ }
+
+ // plain old block, skip
+ if (empty($env->block->type)) {
+ return $this->multiplyMedia($env->parent, $childQueries);
+ }
+
+ $out = array();
+ $queries = $env->block->queries;
+ if (is_null($childQueries)) {
+ $out = $queries;
+ } else {
+ foreach ($queries as $parent) {
+ foreach ($childQueries as $child) {
+ $out[] = array_merge($parent, $child);
+ }
+ }
+ }
+
+ return $this->multiplyMedia($env->parent, $out);
+ }
+
+ protected function expandParentSelectors(&$tag, $replace) {
+ $parts = explode("$&$", $tag);
+ $count = 0;
+ foreach ($parts as &$part) {
+ $part = str_replace($this->parentSelector, $replace, $part, $c);
+ $count += $c;
+ }
+ $tag = implode($this->parentSelector, $parts);
+ return $count;
+ }
+
+ protected function findClosestSelectors() {
+ $env = $this->env;
+ $selectors = null;
+ while ($env !== null) {
+ if (isset($env->selectors)) {
+ $selectors = $env->selectors;
+ break;
+ }
+ $env = $env->parent;
+ }
+
+ return $selectors;
+ }
+
+
+ // multiply $selectors against the nearest selectors in env
+ protected function multiplySelectors($selectors) {
+ // find parent selectors
+
+ $parentSelectors = $this->findClosestSelectors();
+ if (is_null($parentSelectors)) {
+ // kill parent reference in top level selector
+ foreach ($selectors as &$s) {
+ $this->expandParentSelectors($s, "");
+ }
+
+ return $selectors;
+ }
+
+ $out = array();
+ foreach ($parentSelectors as $parent) {
+ foreach ($selectors as $child) {
+ $count = $this->expandParentSelectors($child, $parent);
+
+ // don't prepend the parent tag if & was used
+ if ($count > 0) {
+ $out[] = trim($child);
+ } else {
+ $out[] = trim($parent . ' ' . $child);
+ }
+ }
+ }
+
+ return $out;
+ }
+
+ // reduces selector expressions
+ protected function compileSelectors($selectors) {
+ $out = array();
+
+ foreach ($selectors as $s) {
+ if (is_array($s)) {
+ list(, $value) = $s;
+ $out[] = trim($this->compileValue($this->reduce($value)));
+ } else {
+ $out[] = $s;
+ }
+ }
+
+ return $out;
+ }
+
+ protected function eq($left, $right) {
+ return $left == $right;
+ }
+
+ protected function patternMatch($block, $orderedArgs, $keywordArgs) {
+ // match the guards if it has them
+ // any one of the groups must have all its guards pass for a match
+ if (!empty($block->guards)) {
+ $groupPassed = false;
+ foreach ($block->guards as $guardGroup) {
+ foreach ($guardGroup as $guard) {
+ $this->pushEnv();
+ $this->zipSetArgs($block->args, $orderedArgs, $keywordArgs);
+
+ $negate = false;
+ if ($guard[0] == "negate") {
+ $guard = $guard[1];
+ $negate = true;
+ }
+
+ $passed = $this->reduce($guard) == self::$TRUE;
+ if ($negate) $passed = !$passed;
+
+ $this->popEnv();
+
+ if ($passed) {
+ $groupPassed = true;
+ } else {
+ $groupPassed = false;
+ break;
+ }
+ }
+
+ if ($groupPassed) break;
+ }
+
+ if (!$groupPassed) {
+ return false;
+ }
+ }
+
+ if (empty($block->args)) {
+ return $block->isVararg || empty($orderedArgs) && empty($keywordArgs);
+ }
+
+ $remainingArgs = $block->args;
+ if ($keywordArgs) {
+ $remainingArgs = array();
+ foreach ($block->args as $arg) {
+ if ($arg[0] == "arg" && isset($keywordArgs[$arg[1]])) {
+ continue;
+ }
+
+ $remainingArgs[] = $arg;
+ }
+ }
+
+ $i = -1; // no args
+ // try to match by arity or by argument literal
+ foreach ($remainingArgs as $i => $arg) {
+ switch ($arg[0]) {
+ case "lit":
+ if (empty($orderedArgs[$i]) || !$this->eq($arg[1], $orderedArgs[$i])) {
+ return false;
+ }
+ break;
+ case "arg":
+ // no arg and no default value
+ if (!isset($orderedArgs[$i]) && !isset($arg[2])) {
+ return false;
+ }
+ break;
+ case "rest":
+ $i--; // rest can be empty
+ break 2;
+ }
+ }
+
+ if ($block->isVararg) {
+ return true; // not having enough is handled above
+ } else {
+ $numMatched = $i + 1;
+ // greater than becuase default values always match
+ return $numMatched >= count($orderedArgs);
+ }
+ }
+
+ protected function patternMatchAll($blocks, $orderedArgs, $keywordArgs, $skip=array()) {
+ $matches = null;
+ foreach ($blocks as $block) {
+ // skip seen blocks that don't have arguments
+ if (isset($skip[$block->id]) && !isset($block->args)) {
+ continue;
+ }
+
+ if ($this->patternMatch($block, $orderedArgs, $keywordArgs)) {
+ $matches[] = $block;
+ }
+ }
+
+ return $matches;
+ }
+
+ // attempt to find blocks matched by path and args
+ protected function findBlocks($searchIn, $path, $orderedArgs, $keywordArgs, $seen=array()) {
+ if ($searchIn == null) return null;
+ if (isset($seen[$searchIn->id])) return null;
+ $seen[$searchIn->id] = true;
+
+ $name = $path[0];
+
+ if (isset($searchIn->children[$name])) {
+ $blocks = $searchIn->children[$name];
+ if (count($path) == 1) {
+ $matches = $this->patternMatchAll($blocks, $orderedArgs, $keywordArgs, $seen);
+ if (!empty($matches)) {
+ // This will return all blocks that match in the closest
+ // scope that has any matching block, like lessjs
+ return $matches;
+ }
+ } else {
+ $matches = array();
+ foreach ($blocks as $subBlock) {
+ $subMatches = $this->findBlocks($subBlock,
+ array_slice($path, 1), $orderedArgs, $keywordArgs, $seen);
+
+ if (!is_null($subMatches)) {
+ foreach ($subMatches as $sm) {
+ $matches[] = $sm;
+ }
+ }
+ }
+
+ return count($matches) > 0 ? $matches : null;
+ }
+ }
+ if ($searchIn->parent === $searchIn) return null;
+ return $this->findBlocks($searchIn->parent, $path, $orderedArgs, $keywordArgs, $seen);
+ }
+
+ // sets all argument names in $args to either the default value
+ // or the one passed in through $values
+ protected function zipSetArgs($args, $orderedValues, $keywordValues) {
+ $assignedValues = array();
+
+ $i = 0;
+ foreach ($args as $a) {
+ if ($a[0] == "arg") {
+ if (isset($keywordValues[$a[1]])) {
+ // has keyword arg
+ $value = $keywordValues[$a[1]];
+ } elseif (isset($orderedValues[$i])) {
+ // has ordered arg
+ $value = $orderedValues[$i];
+ $i++;
+ } elseif (isset($a[2])) {
+ // has default value
+ $value = $a[2];
+ } else {
+ $this->throwError("Failed to assign arg " . $a[1]);
+ $value = null; // :(
+ }
+
+ $value = $this->reduce($value);
+ $this->set($a[1], $value);
+ $assignedValues[] = $value;
+ } else {
+ // a lit
+ $i++;
+ }
+ }
+
+ // check for a rest
+ $last = end($args);
+ if ($last[0] == "rest") {
+ $rest = array_slice($orderedValues, count($args) - 1);
+ $this->set($last[1], $this->reduce(array("list", " ", $rest)));
+ }
+
+ // wow is this the only true use of PHP's + operator for arrays?
+ $this->env->arguments = $assignedValues + $orderedValues;
+ }
+
+ // compile a prop and update $lines or $blocks appropriately
+ protected function compileProp($prop, $block, $out) {
+ // set error position context
+ $this->sourceLoc = isset($prop[-1]) ? $prop[-1] : -1;
+
+ switch ($prop[0]) {
+ case 'assign':
+ list(, $name, $value) = $prop;
+ if ($name[0] == $this->vPrefix) {
+ $this->set($name, $value);
+ } else {
+ $out->lines[] = $this->formatter->property($name,
+ $this->compileValue($this->reduce($value)));
+ }
+ break;
+ case 'block':
+ list(, $child) = $prop;
+ $this->compileBlock($child);
+ break;
+ case 'mixin':
+ list(, $path, $args, $suffix) = $prop;
+
+ $orderedArgs = array();
+ $keywordArgs = array();
+ foreach ((array)$args as $arg) {
+ $argval = null;
+ switch ($arg[0]) {
+ case "arg":
+ if (!isset($arg[2])) {
+ $orderedArgs[] = $this->reduce(array("variable", $arg[1]));
+ } else {
+ $keywordArgs[$arg[1]] = $this->reduce($arg[2]);
+ }
+ break;
+
+ case "lit":
+ $orderedArgs[] = $this->reduce($arg[1]);
+ break;
+ default:
+ $this->throwError("Unknown arg type: " . $arg[0]);
+ }
+ }
+
+ $mixins = $this->findBlocks($block, $path, $orderedArgs, $keywordArgs);
+
+ if ($mixins === null) {
+ $this->throwError("{$prop[1][0]} is undefined");
+ }
+
+ foreach ($mixins as $mixin) {
+ if ($mixin === $block && !$orderedArgs) {
+ continue;
+ }
+
+ $haveScope = false;
+ if (isset($mixin->parent->scope)) {
+ $haveScope = true;
+ $mixinParentEnv = $this->pushEnv();
+ $mixinParentEnv->storeParent = $mixin->parent->scope;
+ }
+
+ $haveArgs = false;
+ if (isset($mixin->args)) {
+ $haveArgs = true;
+ $this->pushEnv();
+ $this->zipSetArgs($mixin->args, $orderedArgs, $keywordArgs);
+ }
+
+ $oldParent = $mixin->parent;
+ if ($mixin != $block) $mixin->parent = $block;
+
+ foreach ($this->sortProps($mixin->props) as $subProp) {
+ if ($suffix !== null &&
+ $subProp[0] == "assign" &&
+ is_string($subProp[1]) &&
+ $subProp[1]{0} != $this->vPrefix)
+ {
+ $subProp[2] = array(
+ 'list', ' ',
+ array($subProp[2], array('keyword', $suffix))
+ );
+ }
+
+ $this->compileProp($subProp, $mixin, $out);
+ }
+
+ $mixin->parent = $oldParent;
+
+ if ($haveArgs) $this->popEnv();
+ if ($haveScope) $this->popEnv();
+ }
+
+ break;
+ case 'raw':
+ $out->lines[] = $prop[1];
+ break;
+ case "directive":
+ list(, $name, $value) = $prop;
+ $out->lines[] = "@$name " . $this->compileValue($this->reduce($value)).';';
+ break;
+ case "comment":
+ $out->lines[] = $prop[1];
+ break;
+ case "import";
+ list(, $importPath, $importId) = $prop;
+ $importPath = $this->reduce($importPath);
+
+ if (!isset($this->env->imports)) {
+ $this->env->imports = array();
+ }
+
+ $result = $this->tryImport($importPath, $block, $out);
+
+ $this->env->imports[$importId] = $result === false ?
+ array(false, "@import " . $this->compileValue($importPath).";") :
+ $result;
+
+ break;
+ case "import_mixin":
+ list(,$importId) = $prop;
+ $import = $this->env->imports[$importId];
+ if ($import[0] === false) {
+ if (isset($import[1])) {
+ $out->lines[] = $import[1];
+ }
+ } else {
+ list(, $bottom, $parser, $importDir) = $import;
+ $this->compileImportedProps($bottom, $block, $out, $parser, $importDir);
+ }
+
+ break;
+ default:
+ $this->throwError("unknown op: {$prop[0]}\n");
+ }
+ }
+
+
+ /**
+ * Compiles a primitive value into a CSS property value.
+ *
+ * Values in lessphp are typed by being wrapped in arrays, their format is
+ * typically:
+ *
+ * array(type, contents [, additional_contents]*)
+ *
+ * The input is expected to be reduced. This function will not work on
+ * things like expressions and variables.
+ */
+ protected function compileValue($value) {
+ switch ($value[0]) {
+ case 'list':
+ // [1] - delimiter
+ // [2] - array of values
+ return implode($value[1], array_map(array($this, 'compileValue'), $value[2]));
+ case 'raw_color':
+ if (!empty($this->formatter->compressColors)) {
+ return $this->compileValue($this->coerceColor($value));
+ }
+ return $value[1];
+ case 'keyword':
+ // [1] - the keyword
+ return $value[1];
+ case 'number':
+ list(, $num, $unit) = $value;
+ // [1] - the number
+ // [2] - the unit
+ if ($this->numberPrecision !== null) {
+ $num = round($num, $this->numberPrecision);
+ }
+ return $num . $unit;
+ case 'string':
+ // [1] - contents of string (includes quotes)
+ list(, $delim, $content) = $value;
+ foreach ($content as &$part) {
+ if (is_array($part)) {
+ $part = $this->compileValue($part);
+ }
+ }
+ return $delim . implode($content) . $delim;
+ case 'color':
+ // [1] - red component (either number or a %)
+ // [2] - green component
+ // [3] - blue component
+ // [4] - optional alpha component
+ list(, $r, $g, $b) = $value;
+ $r = round($r);
+ $g = round($g);
+ $b = round($b);
+
+ if (count($value) == 5 && $value[4] != 1) { // rgba
+ return 'rgba('.$r.','.$g.','.$b.','.$value[4].')';
+ }
+
+ $h = sprintf("#%02x%02x%02x", $r, $g, $b);
+
+ if (!empty($this->formatter->compressColors)) {
+ // Converting hex color to short notation (e.g. #003399 to #039)
+ if ($h[1] === $h[2] && $h[3] === $h[4] && $h[5] === $h[6]) {
+ $h = '#' . $h[1] . $h[3] . $h[5];
+ }
+ }
+
+ return $h;
+
+ case 'function':
+ list(, $name, $args) = $value;
+ return $name.'('.$this->compileValue($args).')';
+ default: // assumed to be unit
+ $this->throwError("unknown value type: $value[0]");
+ }
+ }
+
+ protected function lib_pow($args) {
+ list($base, $exp) = $this->assertArgs($args, 2, "pow");
+ return pow($this->assertNumber($base), $this->assertNumber($exp));
+ }
+
+ protected function lib_pi() {
+ return pi();
+ }
+
+ protected function lib_mod($args) {
+ list($a, $b) = $this->assertArgs($args, 2, "mod");
+ return $this->assertNumber($a) % $this->assertNumber($b);
+ }
+
+ protected function lib_tan($num) {
+ return tan($this->assertNumber($num));
+ }
+
+ protected function lib_sin($num) {
+ return sin($this->assertNumber($num));
+ }
+
+ protected function lib_cos($num) {
+ return cos($this->assertNumber($num));
+ }
+
+ protected function lib_atan($num) {
+ $num = atan($this->assertNumber($num));
+ return array("number", $num, "rad");
+ }
+
+ protected function lib_asin($num) {
+ $num = asin($this->assertNumber($num));
+ return array("number", $num, "rad");
+ }
+
+ protected function lib_acos($num) {
+ $num = acos($this->assertNumber($num));
+ return array("number", $num, "rad");
+ }
+
+ protected function lib_sqrt($num) {
+ return sqrt($this->assertNumber($num));
+ }
+
+ protected function lib_extract($value) {
+ list($list, $idx) = $this->assertArgs($value, 2, "extract");
+ $idx = $this->assertNumber($idx);
+ // 1 indexed
+ if ($list[0] == "list" && isset($list[2][$idx - 1])) {
+ return $list[2][$idx - 1];
+ }
+ }
+
+ protected function lib_isnumber($value) {
+ return $this->toBool($value[0] == "number");
+ }
+
+ protected function lib_isstring($value) {
+ return $this->toBool($value[0] == "string");
+ }
+
+ protected function lib_iscolor($value) {
+ return $this->toBool($this->coerceColor($value));
+ }
+
+ protected function lib_iskeyword($value) {
+ return $this->toBool($value[0] == "keyword");
+ }
+
+ protected function lib_ispixel($value) {
+ return $this->toBool($value[0] == "number" && $value[2] == "px");
+ }
+
+ protected function lib_ispercentage($value) {
+ return $this->toBool($value[0] == "number" && $value[2] == "%");
+ }
+
+ protected function lib_isem($value) {
+ return $this->toBool($value[0] == "number" && $value[2] == "em");
+ }
+
+ protected function lib_isrem($value) {
+ return $this->toBool($value[0] == "number" && $value[2] == "rem");
+ }
+
+ protected function lib_rgbahex($color) {
+ $color = $this->coerceColor($color);
+ if (is_null($color))
+ $this->throwError("color expected for rgbahex");
+
+ return sprintf("#%02x%02x%02x%02x",
+ isset($color[4]) ? $color[4]*255 : 255,
+ $color[1],$color[2], $color[3]);
+ }
+
+ protected function lib_argb($color){
+ return $this->lib_rgbahex($color);
+ }
+
+ // utility func to unquote a string
+ protected function lib_e($arg) {
+ switch ($arg[0]) {
+ case "list":
+ $items = $arg[2];
+ if (isset($items[0])) {
+ return $this->lib_e($items[0]);
+ }
+ $this->throwError("unrecognised input");
+ case "string":
+ $arg[1] = "";
+ return $arg;
+ case "keyword":
+ return $arg;
+ default:
+ return array("keyword", $this->compileValue($arg));
+ }
+ }
+
+ protected function lib__sprintf($args) {
+ if ($args[0] != "list") return $args;
+ $values = $args[2];
+ $string = array_shift($values);
+ $template = $this->compileValue($this->lib_e($string));
+
+ $i = 0;
+ if (preg_match_all('/%[dsa]/', $template, $m)) {
+ foreach ($m[0] as $match) {
+ $val = isset($values[$i]) ?
+ $this->reduce($values[$i]) : array('keyword', '');
+
+ // lessjs compat, renders fully expanded color, not raw color
+ if ($color = $this->coerceColor($val)) {
+ $val = $color;
+ }
+
+ $i++;
+ $rep = $this->compileValue($this->lib_e($val));
+ $template = preg_replace('/'.self::preg_quote($match).'/',
+ $rep, $template, 1);
+ }
+ }
+
+ $d = $string[0] == "string" ? $string[1] : '"';
+ return array("string", $d, array($template));
+ }
+
+ protected function lib_floor($arg) {
+ $value = $this->assertNumber($arg);
+ return array("number", floor($value), $arg[2]);
+ }
+
+ protected function lib_ceil($arg) {
+ $value = $this->assertNumber($arg);
+ return array("number", ceil($value), $arg[2]);
+ }
+
+ protected function lib_round($arg) {
+ if($arg[0] != "list") {
+ $value = $this->assertNumber($arg);
+ return array("number", round($value), $arg[2]);
+ } else {
+ $value = $this->assertNumber($arg[2][0]);
+ $precision = $this->assertNumber($arg[2][1]);
+ return array("number", round($value, $precision), $arg[2][0][2]);
+ }
+ }
+
+ protected function lib_unit($arg) {
+ if ($arg[0] == "list") {
+ list($number, $newUnit) = $arg[2];
+ return array("number", $this->assertNumber($number),
+ $this->compileValue($this->lib_e($newUnit)));
+ } else {
+ return array("number", $this->assertNumber($arg), "");
+ }
+ }
+
+ /**
+ * Helper function to get arguments for color manipulation functions.
+ * takes a list that contains a color like thing and a percentage
+ */
+ public function colorArgs($args) {
+ if ($args[0] != 'list' || count($args[2]) < 2) {
+ return array(array('color', 0, 0, 0), 0);
+ }
+ list($color, $delta) = $args[2];
+ $color = $this->assertColor($color);
+ $delta = floatval($delta[1]);
+
+ return array($color, $delta);
+ }
+
+ protected function lib_darken($args) {
+ list($color, $delta) = $this->colorArgs($args);
+
+ $hsl = $this->toHSL($color);
+ $hsl[3] = $this->clamp($hsl[3] - $delta, 100);
+ return $this->toRGB($hsl);
+ }
+
+ protected function lib_lighten($args) {
+ list($color, $delta) = $this->colorArgs($args);
+
+ $hsl = $this->toHSL($color);
+ $hsl[3] = $this->clamp($hsl[3] + $delta, 100);
+ return $this->toRGB($hsl);
+ }
+
+ protected function lib_saturate($args) {
+ list($color, $delta) = $this->colorArgs($args);
+
+ $hsl = $this->toHSL($color);
+ $hsl[2] = $this->clamp($hsl[2] + $delta, 100);
+ return $this->toRGB($hsl);
+ }
+
+ protected function lib_desaturate($args) {
+ list($color, $delta) = $this->colorArgs($args);
+
+ $hsl = $this->toHSL($color);
+ $hsl[2] = $this->clamp($hsl[2] - $delta, 100);
+ return $this->toRGB($hsl);
+ }
+
+ protected function lib_spin($args) {
+ list($color, $delta) = $this->colorArgs($args);
+
+ $hsl = $this->toHSL($color);
+
+ $hsl[1] = $hsl[1] + $delta % 360;
+ if ($hsl[1] < 0) $hsl[1] += 360;
+
+ return $this->toRGB($hsl);
+ }
+
+ protected function lib_fadeout($args) {
+ list($color, $delta) = $this->colorArgs($args);
+ $color[4] = $this->clamp((isset($color[4]) ? $color[4] : 1) - $delta/100);
+ return $color;
+ }
+
+ protected function lib_fadein($args) {
+ list($color, $delta) = $this->colorArgs($args);
+ $color[4] = $this->clamp((isset($color[4]) ? $color[4] : 1) + $delta/100);
+ return $color;
+ }
+
+ protected function lib_hue($color) {
+ $hsl = $this->toHSL($this->assertColor($color));
+ return round($hsl[1]);
+ }
+
+ protected function lib_saturation($color) {
+ $hsl = $this->toHSL($this->assertColor($color));
+ return round($hsl[2]);
+ }
+
+ protected function lib_lightness($color) {
+ $hsl = $this->toHSL($this->assertColor($color));
+ return round($hsl[3]);
+ }
+
+ // get the alpha of a color
+ // defaults to 1 for non-colors or colors without an alpha
+ protected function lib_alpha($value) {
+ if (!is_null($color = $this->coerceColor($value))) {
+ return isset($color[4]) ? $color[4] : 1;
+ }
+ }
+
+ // set the alpha of the color
+ protected function lib_fade($args) {
+ list($color, $alpha) = $this->colorArgs($args);
+ $color[4] = $this->clamp($alpha / 100.0);
+ return $color;
+ }
+
+ protected function lib_percentage($arg) {
+ $num = $this->assertNumber($arg);
+ return array("number", $num*100, "%");
+ }
+
+ // mixes two colors by weight
+ // mix(@color1, @color2, [@weight: 50%]);
+ // http://sass-lang.com/docs/yardoc/Sass/Script/Functions.html#mix-instance_method
+ protected function lib_mix($args) {
+ if ($args[0] != "list" || count($args[2]) < 2)
+ $this->throwError("mix expects (color1, color2, weight)");
+
+ list($first, $second) = $args[2];
+ $first = $this->assertColor($first);
+ $second = $this->assertColor($second);
+
+ $first_a = $this->lib_alpha($first);
+ $second_a = $this->lib_alpha($second);
+
+ if (isset($args[2][2])) {
+ $weight = $args[2][2][1] / 100.0;
+ } else {
+ $weight = 0.5;
+ }
+
+ $w = $weight * 2 - 1;
+ $a = $first_a - $second_a;
+
+ $w1 = (($w * $a == -1 ? $w : ($w + $a)/(1 + $w * $a)) + 1) / 2.0;
+ $w2 = 1.0 - $w1;
+
+ $new = array('color',
+ $w1 * $first[1] + $w2 * $second[1],
+ $w1 * $first[2] + $w2 * $second[2],
+ $w1 * $first[3] + $w2 * $second[3],
+ );
+
+ if ($first_a != 1.0 || $second_a != 1.0) {
+ $new[] = $first_a * $weight + $second_a * ($weight - 1);
+ }
+
+ return $this->fixColor($new);
+ }
+
+ protected function lib_contrast($args) {
+ if ($args[0] != 'list' || count($args[2]) < 3) {
+ return array(array('color', 0, 0, 0), 0);
+ }
+
+ list($inputColor, $darkColor, $lightColor) = $args[2];
+
+ $inputColor = $this->assertColor($inputColor);
+ $darkColor = $this->assertColor($darkColor);
+ $lightColor = $this->assertColor($lightColor);
+ $hsl = $this->toHSL($inputColor);
+
+ if ($hsl[3] > 50) {
+ return $darkColor;
+ }
+
+ return $lightColor;
+ }
+
+ public function assertColor($value, $error = "expected color value") {
+ $color = $this->coerceColor($value);
+ if (is_null($color)) $this->throwError($error);
+ return $color;
+ }
+
+ public function assertNumber($value, $error = "expecting number") {
+ if ($value[0] == "number") return $value[1];
+ $this->throwError($error);
+ }
+
+ public function assertArgs($value, $expectedArgs, $name="") {
+ if ($expectedArgs == 1) {
+ return $value;
+ } else {
+ if ($value[0] !== "list" || $value[1] != ",") $this->throwError("expecting list");
+ $values = $value[2];
+ $numValues = count($values);
+ if ($expectedArgs != $numValues) {
+ if ($name) {
+ $name = $name . ": ";
+ }
+
+ $this->throwError("${name}expecting $expectedArgs arguments, got $numValues");
+ }
+
+ return $values;
+ }
+ }
+
+ protected function toHSL($color) {
+ if ($color[0] == 'hsl') return $color;
+
+ $r = $color[1] / 255;
+ $g = $color[2] / 255;
+ $b = $color[3] / 255;
+
+ $min = min($r, $g, $b);
+ $max = max($r, $g, $b);
+
+ $L = ($min + $max) / 2;
+ if ($min == $max) {
+ $S = $H = 0;
+ } else {
+ if ($L < 0.5)
+ $S = ($max - $min)/($max + $min);
+ else
+ $S = ($max - $min)/(2.0 - $max - $min);
+
+ if ($r == $max) $H = ($g - $b)/($max - $min);
+ elseif ($g == $max) $H = 2.0 + ($b - $r)/($max - $min);
+ elseif ($b == $max) $H = 4.0 + ($r - $g)/($max - $min);
+
+ }
+
+ $out = array('hsl',
+ ($H < 0 ? $H + 6 : $H)*60,
+ $S*100,
+ $L*100,
+ );
+
+ if (count($color) > 4) $out[] = $color[4]; // copy alpha
+ return $out;
+ }
+
+ protected function toRGB_helper($comp, $temp1, $temp2) {
+ if ($comp < 0) $comp += 1.0;
+ elseif ($comp > 1) $comp -= 1.0;
+
+ if (6 * $comp < 1) return $temp1 + ($temp2 - $temp1) * 6 * $comp;
+ if (2 * $comp < 1) return $temp2;
+ if (3 * $comp < 2) return $temp1 + ($temp2 - $temp1)*((2/3) - $comp) * 6;
+
+ return $temp1;
+ }
+
+ /**
+ * Converts a hsl array into a color value in rgb.
+ * Expects H to be in range of 0 to 360, S and L in 0 to 100
+ */
+ protected function toRGB($color) {
+ if ($color[0] == 'color') return $color;
+
+ $H = $color[1] / 360;
+ $S = $color[2] / 100;
+ $L = $color[3] / 100;
+
+ if ($S == 0) {
+ $r = $g = $b = $L;
+ } else {
+ $temp2 = $L < 0.5 ?
+ $L*(1.0 + $S) :
+ $L + $S - $L * $S;
+
+ $temp1 = 2.0 * $L - $temp2;
+
+ $r = $this->toRGB_helper($H + 1/3, $temp1, $temp2);
+ $g = $this->toRGB_helper($H, $temp1, $temp2);
+ $b = $this->toRGB_helper($H - 1/3, $temp1, $temp2);
+ }
+
+ // $out = array('color', round($r*255), round($g*255), round($b*255));
+ $out = array('color', $r*255, $g*255, $b*255);
+ if (count($color) > 4) $out[] = $color[4]; // copy alpha
+ return $out;
+ }
+
+ protected function clamp($v, $max = 1, $min = 0) {
+ return min($max, max($min, $v));
+ }
+
+ /**
+ * Convert the rgb, rgba, hsl color literals of function type
+ * as returned by the parser into values of color type.
+ */
+ protected function funcToColor($func) {
+ $fname = $func[1];
+ if ($func[2][0] != 'list') return false; // need a list of arguments
+ $rawComponents = $func[2][2];
+
+ if ($fname == 'hsl' || $fname == 'hsla') {
+ $hsl = array('hsl');
+ $i = 0;
+ foreach ($rawComponents as $c) {
+ $val = $this->reduce($c);
+ $val = isset($val[1]) ? floatval($val[1]) : 0;
+
+ if ($i == 0) $clamp = 360;
+ elseif ($i < 3) $clamp = 100;
+ else $clamp = 1;
+
+ $hsl[] = $this->clamp($val, $clamp);
+ $i++;
+ }
+
+ while (count($hsl) < 4) $hsl[] = 0;
+ return $this->toRGB($hsl);
+
+ } elseif ($fname == 'rgb' || $fname == 'rgba') {
+ $components = array();
+ $i = 1;
+ foreach ($rawComponents as $c) {
+ $c = $this->reduce($c);
+ if ($i < 4) {
+ if ($c[0] == "number" && $c[2] == "%") {
+ $components[] = 255 * ($c[1] / 100);
+ } else {
+ $components[] = floatval($c[1]);
+ }
+ } elseif ($i == 4) {
+ if ($c[0] == "number" && $c[2] == "%") {
+ $components[] = 1.0 * ($c[1] / 100);
+ } else {
+ $components[] = floatval($c[1]);
+ }
+ } else break;
+
+ $i++;
+ }
+ while (count($components) < 3) $components[] = 0;
+ array_unshift($components, 'color');
+ return $this->fixColor($components);
+ }
+
+ return false;
+ }
+
+ protected function reduce($value, $forExpression = false) {
+ switch ($value[0]) {
+ case "interpolate":
+ $reduced = $this->reduce($value[1]);
+ $var = $this->compileValue($reduced);
+ $res = $this->reduce(array("variable", $this->vPrefix . $var));
+
+ if ($res[0] == "raw_color") {
+ $res = $this->coerceColor($res);
+ }
+
+ if (empty($value[2])) $res = $this->lib_e($res);
+
+ return $res;
+ case "variable":
+ $key = $value[1];
+ if (is_array($key)) {
+ $key = $this->reduce($key);
+ $key = $this->vPrefix . $this->compileValue($this->lib_e($key));
+ }
+
+ $seen =& $this->env->seenNames;
+
+ if (!empty($seen[$key])) {
+ $this->throwError("infinite loop detected: $key");
+ }
+
+ $seen[$key] = true;
+ $out = $this->reduce($this->get($key));
+ $seen[$key] = false;
+ return $out;
+ case "list":
+ foreach ($value[2] as &$item) {
+ $item = $this->reduce($item, $forExpression);
+ }
+ return $value;
+ case "expression":
+ return $this->evaluate($value);
+ case "string":
+ foreach ($value[2] as &$part) {
+ if (is_array($part)) {
+ $strip = $part[0] == "variable";
+ $part = $this->reduce($part);
+ if ($strip) $part = $this->lib_e($part);
+ }
+ }
+ return $value;
+ case "escape":
+ list(,$inner) = $value;
+ return $this->lib_e($this->reduce($inner));
+ case "function":
+ $color = $this->funcToColor($value);
+ if ($color) return $color;
+
+ list(, $name, $args) = $value;
+ if ($name == "%") $name = "_sprintf";
+ $f = isset($this->libFunctions[$name]) ?
+ $this->libFunctions[$name] : array($this, 'lib_'.$name);
+
+ if (is_callable($f)) {
+ if ($args[0] == 'list')
+ $args = self::compressList($args[2], $args[1]);
+
+ $ret = call_user_func($f, $this->reduce($args, true), $this);
+
+ if (is_null($ret)) {
+ return array("string", "", array(
+ $name, "(", $args, ")"
+ ));
+ }
+
+ // convert to a typed value if the result is a php primitive
+ if (is_numeric($ret)) $ret = array('number', $ret, "");
+ elseif (!is_array($ret)) $ret = array('keyword', $ret);
+
+ return $ret;
+ }
+
+ // plain function, reduce args
+ $value[2] = $this->reduce($value[2]);
+ return $value;
+ case "unary":
+ list(, $op, $exp) = $value;
+ $exp = $this->reduce($exp);
+
+ if ($exp[0] == "number") {
+ switch ($op) {
+ case "+":
+ return $exp;
+ case "-":
+ $exp[1] *= -1;
+ return $exp;
+ }
+ }
+ return array("string", "", array($op, $exp));
+ }
+
+ if ($forExpression) {
+ switch ($value[0]) {
+ case "keyword":
+ if ($color = $this->coerceColor($value)) {
+ return $color;
+ }
+ break;
+ case "raw_color":
+ return $this->coerceColor($value);
+ }
+ }
+
+ return $value;
+ }
+
+
+ // coerce a value for use in color operation
+ protected function coerceColor($value) {
+ switch($value[0]) {
+ case 'color': return $value;
+ case 'raw_color':
+ $c = array("color", 0, 0, 0);
+ $colorStr = substr($value[1], 1);
+ $num = hexdec($colorStr);
+ $width = strlen($colorStr) == 3 ? 16 : 256;
+
+ for ($i = 3; $i > 0; $i--) { // 3 2 1
+ $t = $num % $width;
+ $num /= $width;
+
+ $c[$i] = $t * (256/$width) + $t * floor(16/$width);
+ }
+
+ return $c;
+ case 'keyword':
+ $name = $value[1];
+ if (isset(self::$cssColors[$name])) {
+ $rgba = explode(',', self::$cssColors[$name]);
+
+ if(isset($rgba[3]))
+ return array('color', $rgba[0], $rgba[1], $rgba[2], $rgba[3]);
+
+ return array('color', $rgba[0], $rgba[1], $rgba[2]);
+ }
+ return null;
+ }
+ }
+
+ // make something string like into a string
+ protected function coerceString($value) {
+ switch ($value[0]) {
+ case "string":
+ return $value;
+ case "keyword":
+ return array("string", "", array($value[1]));
+ }
+ return null;
+ }
+
+ // turn list of length 1 into value type
+ protected function flattenList($value) {
+ if ($value[0] == "list" && count($value[2]) == 1) {
+ return $this->flattenList($value[2][0]);
+ }
+ return $value;
+ }
+
+ public function toBool($a) {
+ if ($a) return self::$TRUE;
+ else return self::$FALSE;
+ }
+
+ // evaluate an expression
+ protected function evaluate($exp) {
+ list(, $op, $left, $right, $whiteBefore, $whiteAfter) = $exp;
+
+ $left = $this->reduce($left, true);
+ $right = $this->reduce($right, true);
+
+ if ($leftColor = $this->coerceColor($left)) {
+ $left = $leftColor;
+ }
+
+ if ($rightColor = $this->coerceColor($right)) {
+ $right = $rightColor;
+ }
+
+ $ltype = $left[0];
+ $rtype = $right[0];
+
+ // operators that work on all types
+ if ($op == "and") {
+ return $this->toBool($left == self::$TRUE && $right == self::$TRUE);
+ }
+
+ if ($op == "=") {
+ return $this->toBool($this->eq($left, $right) );
+ }
+
+ if ($op == "+" && !is_null($str = $this->stringConcatenate($left, $right))) {
+ return $str;
+ }
+
+ // type based operators
+ $fname = "op_${ltype}_${rtype}";
+ if (is_callable(array($this, $fname))) {
+ $out = $this->$fname($op, $left, $right);
+ if (!is_null($out)) return $out;
+ }
+
+ // make the expression look it did before being parsed
+ $paddedOp = $op;
+ if ($whiteBefore) $paddedOp = " " . $paddedOp;
+ if ($whiteAfter) $paddedOp .= " ";
+
+ return array("string", "", array($left, $paddedOp, $right));
+ }
+
+ protected function stringConcatenate($left, $right) {
+ if ($strLeft = $this->coerceString($left)) {
+ if ($right[0] == "string") {
+ $right[1] = "";
+ }
+ $strLeft[2][] = $right;
+ return $strLeft;
+ }
+
+ if ($strRight = $this->coerceString($right)) {
+ array_unshift($strRight[2], $left);
+ return $strRight;
+ }
+ }
+
+
+ // make sure a color's components don't go out of bounds
+ protected function fixColor($c) {
+ foreach (range(1, 3) as $i) {
+ if ($c[$i] < 0) $c[$i] = 0;
+ if ($c[$i] > 255) $c[$i] = 255;
+ }
+
+ return $c;
+ }
+
+ protected function op_number_color($op, $lft, $rgt) {
+ if ($op == '+' || $op == '*') {
+ return $this->op_color_number($op, $rgt, $lft);
+ }
+ }
+
+ protected function op_color_number($op, $lft, $rgt) {
+ if ($rgt[0] == '%') $rgt[1] /= 100;
+
+ return $this->op_color_color($op, $lft,
+ array_fill(1, count($lft) - 1, $rgt[1]));
+ }
+
+ protected function op_color_color($op, $left, $right) {
+ $out = array('color');
+ $max = count($left) > count($right) ? count($left) : count($right);
+ foreach (range(1, $max - 1) as $i) {
+ $lval = isset($left[$i]) ? $left[$i] : 0;
+ $rval = isset($right[$i]) ? $right[$i] : 0;
+ switch ($op) {
+ case '+':
+ $out[] = $lval + $rval;
+ break;
+ case '-':
+ $out[] = $lval - $rval;
+ break;
+ case '*':
+ $out[] = $lval * $rval;
+ break;
+ case '%':
+ $out[] = $lval % $rval;
+ break;
+ case '/':
+ if ($rval == 0) $this->throwError("evaluate error: can't divide by zero");
+ $out[] = $lval / $rval;
+ break;
+ default:
+ $this->throwError('evaluate error: color op number failed on op '.$op);
+ }
+ }
+ return $this->fixColor($out);
+ }
+
+ function lib_red($color){
+ $color = $this->coerceColor($color);
+ if (is_null($color)) {
+ $this->throwError('color expected for red()');
+ }
+
+ return $color[1];
+ }
+
+ function lib_green($color){
+ $color = $this->coerceColor($color);
+ if (is_null($color)) {
+ $this->throwError('color expected for green()');
+ }
+
+ return $color[2];
+ }
+
+ function lib_blue($color){
+ $color = $this->coerceColor($color);
+ if (is_null($color)) {
+ $this->throwError('color expected for blue()');
+ }
+
+ return $color[3];
+ }
+
+
+ // operator on two numbers
+ protected function op_number_number($op, $left, $right) {
+ $unit = empty($left[2]) ? $right[2] : $left[2];
+
+ $value = 0;
+ switch ($op) {
+ case '+':
+ $value = $left[1] + $right[1];
+ break;
+ case '*':
+ $value = $left[1] * $right[1];
+ break;
+ case '-':
+ $value = $left[1] - $right[1];
+ break;
+ case '%':
+ $value = $left[1] % $right[1];
+ break;
+ case '/':
+ if ($right[1] == 0) $this->throwError('parse error: divide by zero');
+ $value = $left[1] / $right[1];
+ break;
+ case '<':
+ return $this->toBool($left[1] < $right[1]);
+ case '>':
+ return $this->toBool($left[1] > $right[1]);
+ case '>=':
+ return $this->toBool($left[1] >= $right[1]);
+ case '=<':
+ return $this->toBool($left[1] <= $right[1]);
+ default:
+ $this->throwError('parse error: unknown number operator: '.$op);
+ }
+
+ return array("number", $value, $unit);
+ }
+
+
+ /* environment functions */
+
+ protected function makeOutputBlock($type, $selectors = null) {
+ $b = new stdclass;
+ $b->lines = array();
+ $b->children = array();
+ $b->selectors = $selectors;
+ $b->type = $type;
+ $b->parent = $this->scope;
+ return $b;
+ }
+
+ // the state of execution
+ protected function pushEnv($block = null) {
+ $e = new stdclass;
+ $e->parent = $this->env;
+ $e->store = array();
+ $e->block = $block;
+
+ $this->env = $e;
+ return $e;
+ }
+
+ // pop something off the stack
+ protected function popEnv() {
+ $old = $this->env;
+ $this->env = $this->env->parent;
+ return $old;
+ }
+
+ // set something in the current env
+ protected function set($name, $value) {
+ $this->env->store[$name] = $value;
+ }
+
+
+ // get the highest occurrence entry for a name
+ protected function get($name) {
+ $current = $this->env;
+
+ $isArguments = $name == $this->vPrefix . 'arguments';
+ while ($current) {
+ if ($isArguments && isset($current->arguments)) {
+ return array('list', ' ', $current->arguments);
+ }
+
+ if (isset($current->store[$name]))
+ return $current->store[$name];
+ else {
+ $current = isset($current->storeParent) ?
+ $current->storeParent : $current->parent;
+ }
+ }
+
+ $this->throwError("variable $name is undefined");
+ }
+
+ // inject array of unparsed strings into environment as variables
+ protected function injectVariables($args) {
+ $this->pushEnv();
+ $parser = new lessc_parser($this, __METHOD__);
+ foreach ($args as $name => $strValue) {
+ if ($name{0} != '@') $name = '@'.$name;
+ $parser->count = 0;
+ $parser->buffer = (string)$strValue;
+ if (!$parser->propertyValue($value)) {
+ throw new Exception("failed to parse passed in variable $name: $strValue");
+ }
+
+ $this->set($name, $value);
+ }
+ }
+
+ /**
+ * Initialize any static state, can initialize parser for a file
+ * $opts isn't used yet
+ */
+ public function __construct($fname = null) {
+ if ($fname !== null) {
+ // used for deprecated parse method
+ $this->_parseFile = $fname;
+ }
+ }
+
+ public function compile($string, $name = null) {
+ $locale = setlocale(LC_NUMERIC, 0);
+ setlocale(LC_NUMERIC, "C");
+
+ $this->parser = $this->makeParser($name);
+ $root = $this->parser->parse($string);
+
+ $this->env = null;
+ $this->scope = null;
+
+ $this->formatter = $this->newFormatter();
+
+ if (!empty($this->registeredVars)) {
+ $this->injectVariables($this->registeredVars);
+ }
+
+ $this->sourceParser = $this->parser; // used for error messages
+ $this->compileBlock($root);
+
+ ob_start();
+ $this->formatter->block($this->scope);
+ $out = ob_get_clean();
+ setlocale(LC_NUMERIC, $locale);
+ return $out;
+ }
+
+ public function compileFile($fname, $outFname = null) {
+ if (!is_readable($fname)) {
+ throw new Exception('load error: failed to find '.$fname);
+ }
+
+ $pi = pathinfo($fname);
+
+ $oldImport = $this->importDir;
+
+ $this->importDir = (array)$this->importDir;
+ $this->importDir[] = $pi['dirname'].'/';
+
+ $this->addParsedFile($fname);
+
+ $out = $this->compile(file_get_contents($fname), $fname);
+
+ $this->importDir = $oldImport;
+
+ if ($outFname !== null) {
+ return file_put_contents($outFname, $out);
+ }
+
+ return $out;
+ }
+
+ // compile only if changed input has changed or output doesn't exist
+ public function checkedCompile($in, $out) {
+ if (!is_file($out) || filemtime($in) > filemtime($out)) {
+ $this->compileFile($in, $out);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Execute lessphp on a .less file or a lessphp cache structure
+ *
+ * The lessphp cache structure contains information about a specific
+ * less file having been parsed. It can be used as a hint for future
+ * calls to determine whether or not a rebuild is required.
+ *
+ * The cache structure contains two important keys that may be used
+ * externally:
+ *
+ * compiled: The final compiled CSS
+ * updated: The time (in seconds) the CSS was last compiled
+ *
+ * The cache structure is a plain-ol' PHP associative array and can
+ * be serialized and unserialized without a hitch.
+ *
+ * @param mixed $in Input
+ * @param bool $force Force rebuild?
+ * @return array lessphp cache structure
+ */
+ public function cachedCompile($in, $force = false) {
+ // assume no root
+ $root = null;
+
+ if (is_string($in)) {
+ $root = $in;
+ } elseif (is_array($in) and isset($in['root'])) {
+ if ($force or ! isset($in['files'])) {
+ // If we are forcing a recompile or if for some reason the
+ // structure does not contain any file information we should
+ // specify the root to trigger a rebuild.
+ $root = $in['root'];
+ } elseif (isset($in['files']) and is_array($in['files'])) {
+ foreach ($in['files'] as $fname => $ftime ) {
+ if (!file_exists($fname) or filemtime($fname) > $ftime) {
+ // One of the files we knew about previously has changed
+ // so we should look at our incoming root again.
+ $root = $in['root'];
+ break;
+ }
+ }
+ }
+ } else {
+ // TODO: Throw an exception? We got neither a string nor something
+ // that looks like a compatible lessphp cache structure.
+ return null;
+ }
+
+ if ($root !== null) {
+ // If we have a root value which means we should rebuild.
+ $out = array();
+ $out['root'] = $root;
+ $out['compiled'] = $this->compileFile($root);
+ $out['files'] = $this->allParsedFiles();
+ $out['updated'] = time();
+ return $out;
+ } else {
+ // No changes, pass back the structure
+ // we were given initially.
+ return $in;
+ }
+
+ }
+
+ // parse and compile buffer
+ // This is deprecated
+ public function parse($str = null, $initialVariables = null) {
+ if (is_array($str)) {
+ $initialVariables = $str;
+ $str = null;
+ }
+
+ $oldVars = $this->registeredVars;
+ if ($initialVariables !== null) {
+ $this->setVariables($initialVariables);
+ }
+
+ if ($str == null) {
+ if (empty($this->_parseFile)) {
+ throw new exception("nothing to parse");
+ }
+
+ $out = $this->compileFile($this->_parseFile);
+ } else {
+ $out = $this->compile($str);
+ }
+
+ $this->registeredVars = $oldVars;
+ return $out;
+ }
+
+ protected function makeParser($name) {
+ $parser = new lessc_parser($this, $name);
+ $parser->writeComments = $this->preserveComments;
+
+ return $parser;
+ }
+
+ public function setFormatter($name) {
+ $this->formatterName = $name;
+ }
+
+ protected function newFormatter() {
+ $className = "lessc_formatter_lessjs";
+ if (!empty($this->formatterName)) {
+ if (!is_string($this->formatterName))
+ return $this->formatterName;
+ $className = "lessc_formatter_$this->formatterName";
+ }
+
+ return new $className;
+ }
+
+ public function setPreserveComments($preserve) {
+ $this->preserveComments = $preserve;
+ }
+
+ public function registerFunction($name, $func) {
+ $this->libFunctions[$name] = $func;
+ }
+
+ public function unregisterFunction($name) {
+ unset($this->libFunctions[$name]);
+ }
+
+ public function setVariables($variables) {
+ $this->registeredVars = array_merge($this->registeredVars, $variables);
+ }
+
+ public function unsetVariable($name) {
+ unset($this->registeredVars[$name]);
+ }
+
+ public function setImportDir($dirs) {
+ $this->importDir = (array)$dirs;
+ }
+
+ public function addImportDir($dir) {
+ $this->importDir = (array)$this->importDir;
+ $this->importDir[] = $dir;
+ }
+
+ public function allParsedFiles() {
+ return $this->allParsedFiles;
+ }
+
+ public function addParsedFile($file) {
+ $this->allParsedFiles[realpath($file)] = filemtime($file);
+ }
+
+ /**
+ * Uses the current value of $this->count to show line and line number
+ */
+ public function throwError($msg = null) {
+ if ($this->sourceLoc >= 0) {
+ $this->sourceParser->throwError($msg, $this->sourceLoc);
+ }
+ throw new exception($msg);
+ }
+
+ // compile file $in to file $out if $in is newer than $out
+ // returns true when it compiles, false otherwise
+ public static function ccompile($in, $out, $less = null) {
+ if ($less === null) {
+ $less = new self;
+ }
+ return $less->checkedCompile($in, $out);
+ }
+
+ public static function cexecute($in, $force = false, $less = null) {
+ if ($less === null) {
+ $less = new self;
+ }
+ return $less->cachedCompile($in, $force);
+ }
+
+ static protected $cssColors = array(
+ 'aliceblue' => '240,248,255',
+ 'antiquewhite' => '250,235,215',
+ 'aqua' => '0,255,255',
+ 'aquamarine' => '127,255,212',
+ 'azure' => '240,255,255',
+ 'beige' => '245,245,220',
+ 'bisque' => '255,228,196',
+ 'black' => '0,0,0',
+ 'blanchedalmond' => '255,235,205',
+ 'blue' => '0,0,255',
+ 'blueviolet' => '138,43,226',
+ 'brown' => '165,42,42',
+ 'burlywood' => '222,184,135',
+ 'cadetblue' => '95,158,160',
+ 'chartreuse' => '127,255,0',
+ 'chocolate' => '210,105,30',
+ 'coral' => '255,127,80',
+ 'cornflowerblue' => '100,149,237',
+ 'cornsilk' => '255,248,220',
+ 'crimson' => '220,20,60',
+ 'cyan' => '0,255,255',
+ 'darkblue' => '0,0,139',
+ 'darkcyan' => '0,139,139',
+ 'darkgoldenrod' => '184,134,11',
+ 'darkgray' => '169,169,169',
+ 'darkgreen' => '0,100,0',
+ 'darkgrey' => '169,169,169',
+ 'darkkhaki' => '189,183,107',
+ 'darkmagenta' => '139,0,139',
+ 'darkolivegreen' => '85,107,47',
+ 'darkorange' => '255,140,0',
+ 'darkorchid' => '153,50,204',
+ 'darkred' => '139,0,0',
+ 'darksalmon' => '233,150,122',
+ 'darkseagreen' => '143,188,143',
+ 'darkslateblue' => '72,61,139',
+ 'darkslategray' => '47,79,79',
+ 'darkslategrey' => '47,79,79',
+ 'darkturquoise' => '0,206,209',
+ 'darkviolet' => '148,0,211',
+ 'deeppink' => '255,20,147',
+ 'deepskyblue' => '0,191,255',
+ 'dimgray' => '105,105,105',
+ 'dimgrey' => '105,105,105',
+ 'dodgerblue' => '30,144,255',
+ 'firebrick' => '178,34,34',
+ 'floralwhite' => '255,250,240',
+ 'forestgreen' => '34,139,34',
+ 'fuchsia' => '255,0,255',
+ 'gainsboro' => '220,220,220',
+ 'ghostwhite' => '248,248,255',
+ 'gold' => '255,215,0',
+ 'goldenrod' => '218,165,32',
+ 'gray' => '128,128,128',
+ 'green' => '0,128,0',
+ 'greenyellow' => '173,255,47',
+ 'grey' => '128,128,128',
+ 'honeydew' => '240,255,240',
+ 'hotpink' => '255,105,180',
+ 'indianred' => '205,92,92',
+ 'indigo' => '75,0,130',
+ 'ivory' => '255,255,240',
+ 'khaki' => '240,230,140',
+ 'lavender' => '230,230,250',
+ 'lavenderblush' => '255,240,245',
+ 'lawngreen' => '124,252,0',
+ 'lemonchiffon' => '255,250,205',
+ 'lightblue' => '173,216,230',
+ 'lightcoral' => '240,128,128',
+ 'lightcyan' => '224,255,255',
+ 'lightgoldenrodyellow' => '250,250,210',
+ 'lightgray' => '211,211,211',
+ 'lightgreen' => '144,238,144',
+ 'lightgrey' => '211,211,211',
+ 'lightpink' => '255,182,193',
+ 'lightsalmon' => '255,160,122',
+ 'lightseagreen' => '32,178,170',
+ 'lightskyblue' => '135,206,250',
+ 'lightslategray' => '119,136,153',
+ 'lightslategrey' => '119,136,153',
+ 'lightsteelblue' => '176,196,222',
+ 'lightyellow' => '255,255,224',
+ 'lime' => '0,255,0',
+ 'limegreen' => '50,205,50',
+ 'linen' => '250,240,230',
+ 'magenta' => '255,0,255',
+ 'maroon' => '128,0,0',
+ 'mediumaquamarine' => '102,205,170',
+ 'mediumblue' => '0,0,205',
+ 'mediumorchid' => '186,85,211',
+ 'mediumpurple' => '147,112,219',
+ 'mediumseagreen' => '60,179,113',
+ 'mediumslateblue' => '123,104,238',
+ 'mediumspringgreen' => '0,250,154',
+ 'mediumturquoise' => '72,209,204',
+ 'mediumvioletred' => '199,21,133',
+ 'midnightblue' => '25,25,112',
+ 'mintcream' => '245,255,250',
+ 'mistyrose' => '255,228,225',
+ 'moccasin' => '255,228,181',
+ 'navajowhite' => '255,222,173',
+ 'navy' => '0,0,128',
+ 'oldlace' => '253,245,230',
+ 'olive' => '128,128,0',
+ 'olivedrab' => '107,142,35',
+ 'orange' => '255,165,0',
+ 'orangered' => '255,69,0',
+ 'orchid' => '218,112,214',
+ 'palegoldenrod' => '238,232,170',
+ 'palegreen' => '152,251,152',
+ 'paleturquoise' => '175,238,238',
+ 'palevioletred' => '219,112,147',
+ 'papayawhip' => '255,239,213',
+ 'peachpuff' => '255,218,185',
+ 'peru' => '205,133,63',
+ 'pink' => '255,192,203',
+ 'plum' => '221,160,221',
+ 'powderblue' => '176,224,230',
+ 'purple' => '128,0,128',
+ 'red' => '255,0,0',
+ 'rosybrown' => '188,143,143',
+ 'royalblue' => '65,105,225',
+ 'saddlebrown' => '139,69,19',
+ 'salmon' => '250,128,114',
+ 'sandybrown' => '244,164,96',
+ 'seagreen' => '46,139,87',
+ 'seashell' => '255,245,238',
+ 'sienna' => '160,82,45',
+ 'silver' => '192,192,192',
+ 'skyblue' => '135,206,235',
+ 'slateblue' => '106,90,205',
+ 'slategray' => '112,128,144',
+ 'slategrey' => '112,128,144',
+ 'snow' => '255,250,250',
+ 'springgreen' => '0,255,127',
+ 'steelblue' => '70,130,180',
+ 'tan' => '210,180,140',
+ 'teal' => '0,128,128',
+ 'thistle' => '216,191,216',
+ 'tomato' => '255,99,71',
+ 'transparent' => '0,0,0,0',
+ 'turquoise' => '64,224,208',
+ 'violet' => '238,130,238',
+ 'wheat' => '245,222,179',
+ 'white' => '255,255,255',
+ 'whitesmoke' => '245,245,245',
+ 'yellow' => '255,255,0',
+ 'yellowgreen' => '154,205,50'
+ );
+}
+
+// responsible for taking a string of LESS code and converting it into a
+// syntax tree
+class lessc_parser {
+ static protected $nextBlockId = 0; // used to uniquely identify blocks
+
+ static protected $precedence = array(
+ '=<' => 0,
+ '>=' => 0,
+ '=' => 0,
+ '<' => 0,
+ '>' => 0,
+
+ '+' => 1,
+ '-' => 1,
+ '*' => 2,
+ '/' => 2,
+ '%' => 2,
+ );
+
+ static protected $whitePattern;
+ static protected $commentMulti;
+
+ static protected $commentSingle = "//";
+ static protected $commentMultiLeft = "/*";
+ static protected $commentMultiRight = "*/";
+
+ // regex string to match any of the operators
+ static protected $operatorString;
+
+ // these properties will supress division unless it's inside parenthases
+ static protected $supressDivisionProps =
+ array('/border-radius$/i', '/^font$/i');
+
+ protected $blockDirectives = array("font-face", "keyframes", "page", "-moz-document", "viewport", "-moz-viewport", "-o-viewport", "-ms-viewport");
+ protected $lineDirectives = array("charset");
+
+ /**
+ * if we are in parens we can be more liberal with whitespace around
+ * operators because it must evaluate to a single value and thus is less
+ * ambiguous.
+ *
+ * Consider:
+ * property1: 10 -5; // is two numbers, 10 and -5
+ * property2: (10 -5); // should evaluate to 5
+ */
+ protected $inParens = false;
+
+ // caches preg escaped literals
+ static protected $literalCache = array();
+
+ public function __construct($lessc, $sourceName = null) {
+ $this->eatWhiteDefault = true;
+ // reference to less needed for vPrefix, mPrefix, and parentSelector
+ $this->lessc = $lessc;
+
+ $this->sourceName = $sourceName; // name used for error messages
+
+ $this->writeComments = false;
+
+ if (!self::$operatorString) {
+ self::$operatorString =
+ '('.implode('|', array_map(array('lessc', 'preg_quote'),
+ array_keys(self::$precedence))).')';
+
+ $commentSingle = lessc::preg_quote(self::$commentSingle);
+ $commentMultiLeft = lessc::preg_quote(self::$commentMultiLeft);
+ $commentMultiRight = lessc::preg_quote(self::$commentMultiRight);
+
+ self::$commentMulti = $commentMultiLeft.'.*?'.$commentMultiRight;
+ self::$whitePattern = '/'.$commentSingle.'[^\n]*\s*|('.self::$commentMulti.')\s*|\s+/Ais';
+ }
+ }
+
+ public function parse($buffer) {
+ $this->count = 0;
+ $this->line = 1;
+
+ $this->env = null; // block stack
+ $this->buffer = $this->writeComments ? $buffer : $this->removeComments($buffer);
+ $this->pushSpecialBlock("root");
+ $this->eatWhiteDefault = true;
+ $this->seenComments = array();
+
+ // trim whitespace on head
+ // if (preg_match('/^\s+/', $this->buffer, $m)) {
+ // $this->line += substr_count($m[0], "\n");
+ // $this->buffer = ltrim($this->buffer);
+ // }
+ $this->whitespace();
+
+ // parse the entire file
+ while (false !== $this->parseChunk());
+
+ if ($this->count != strlen($this->buffer))
+ $this->throwError();
+
+ // TODO report where the block was opened
+ if (!is_null($this->env->parent))
+ throw new exception('parse error: unclosed block');
+
+ return $this->env;
+ }
+
+ /**
+ * Parse a single chunk off the head of the buffer and append it to the
+ * current parse environment.
+ * Returns false when the buffer is empty, or when there is an error.
+ *
+ * This function is called repeatedly until the entire document is
+ * parsed.
+ *
+ * This parser is most similar to a recursive descent parser. Single
+ * functions represent discrete grammatical rules for the language, and
+ * they are able to capture the text that represents those rules.
+ *
+ * Consider the function lessc::keyword(). (all parse functions are
+ * structured the same)
+ *
+ * The function takes a single reference argument. When calling the
+ * function it will attempt to match a keyword on the head of the buffer.
+ * If it is successful, it will place the keyword in the referenced
+ * argument, advance the position in the buffer, and return true. If it
+ * fails then it won't advance the buffer and it will return false.
+ *
+ * All of these parse functions are powered by lessc::match(), which behaves
+ * the same way, but takes a literal regular expression. Sometimes it is
+ * more convenient to use match instead of creating a new function.
+ *
+ * Because of the format of the functions, to parse an entire string of
+ * grammatical rules, you can chain them together using &&.
+ *
+ * But, if some of the rules in the chain succeed before one fails, then
+ * the buffer position will be left at an invalid state. In order to
+ * avoid this, lessc::seek() is used to remember and set buffer positions.
+ *
+ * Before parsing a chain, use $s = $this->seek() to remember the current
+ * position into $s. Then if a chain fails, use $this->seek($s) to
+ * go back where we started.
+ */
+ protected function parseChunk() {
+ if (empty($this->buffer)) return false;
+ $s = $this->seek();
+
+ if ($this->whitespace()) {
+ return true;
+ }
+
+ // setting a property
+ if ($this->keyword($key) && $this->assign() &&
+ $this->propertyValue($value, $key) && $this->end())
+ {
+ $this->append(array('assign', $key, $value), $s);
+ return true;
+ } else {
+ $this->seek($s);
+ }
+
+
+ // look for special css blocks
+ if ($this->literal('@', false)) {
+ $this->count--;
+
+ // media
+ if ($this->literal('@media')) {
+ if (($this->mediaQueryList($mediaQueries) || true)
+ && $this->literal('{'))
+ {
+ $media = $this->pushSpecialBlock("media");
+ $media->queries = is_null($mediaQueries) ? array() : $mediaQueries;
+ return true;
+ } else {
+ $this->seek($s);
+ return false;
+ }
+ }
+
+ if ($this->literal("@", false) && $this->keyword($dirName)) {
+ if ($this->isDirective($dirName, $this->blockDirectives)) {
+ if (($this->openString("{", $dirValue, null, array(";")) || true) &&
+ $this->literal("{"))
+ {
+ $dir = $this->pushSpecialBlock("directive");
+ $dir->name = $dirName;
+ if (isset($dirValue)) $dir->value = $dirValue;
+ return true;
+ }
+ } elseif ($this->isDirective($dirName, $this->lineDirectives)) {
+ if ($this->propertyValue($dirValue) && $this->end()) {
+ $this->append(array("directive", $dirName, $dirValue));
+ return true;
+ }
+ }
+ }
+
+ $this->seek($s);
+ }
+
+ // setting a variable
+ if ($this->variable($var) && $this->assign() &&
+ $this->propertyValue($value) && $this->end())
+ {
+ $this->append(array('assign', $var, $value), $s);
+ return true;
+ } else {
+ $this->seek($s);
+ }
+
+ if ($this->import($importValue)) {
+ $this->append($importValue, $s);
+ return true;
+ }
+
+ // opening parametric mixin
+ if ($this->tag($tag, true) && $this->argumentDef($args, $isVararg) &&
+ ($this->guards($guards) || true) &&
+ $this->literal('{'))
+ {
+ $block = $this->pushBlock($this->fixTags(array($tag)));
+ $block->args = $args;
+ $block->isVararg = $isVararg;
+ if (!empty($guards)) $block->guards = $guards;
+ return true;
+ } else {
+ $this->seek($s);
+ }
+
+ // opening a simple block
+ if ($this->tags($tags) && $this->literal('{', false)) {
+ $tags = $this->fixTags($tags);
+ $this->pushBlock($tags);
+ return true;
+ } else {
+ $this->seek($s);
+ }
+
+ // closing a block
+ if ($this->literal('}', false)) {
+ try {
+ $block = $this->pop();
+ } catch (exception $e) {
+ $this->seek($s);
+ $this->throwError($e->getMessage());
+ }
+
+ $hidden = false;
+ if (is_null($block->type)) {
+ $hidden = true;
+ if (!isset($block->args)) {
+ foreach ($block->tags as $tag) {
+ if (!is_string($tag) || $tag{0} != $this->lessc->mPrefix) {
+ $hidden = false;
+ break;
+ }
+ }
+ }
+
+ foreach ($block->tags as $tag) {
+ if (is_string($tag)) {
+ $this->env->children[$tag][] = $block;
+ }
+ }
+ }
+
+ if (!$hidden) {
+ $this->append(array('block', $block), $s);
+ }
+
+ // this is done here so comments aren't bundled into he block that
+ // was just closed
+ $this->whitespace();
+ return true;
+ }
+
+ // mixin
+ if ($this->mixinTags($tags) &&
+ ($this->argumentDef($argv, $isVararg) || true) &&
+ ($this->keyword($suffix) || true) && $this->end())
+ {
+ $tags = $this->fixTags($tags);
+ $this->append(array('mixin', $tags, $argv, $suffix), $s);
+ return true;
+ } else {
+ $this->seek($s);
+ }
+
+ // spare ;
+ if ($this->literal(';')) return true;
+
+ return false; // got nothing, throw error
+ }
+
+ protected function isDirective($dirname, $directives) {
+ // TODO: cache pattern in parser
+ $pattern = implode("|",
+ array_map(array("lessc", "preg_quote"), $directives));
+ $pattern = '/^(-[a-z-]+-)?(' . $pattern . ')$/i';
+
+ return preg_match($pattern, $dirname);
+ }
+
+ protected function fixTags($tags) {
+ // move @ tags out of variable namespace
+ foreach ($tags as &$tag) {
+ if ($tag{0} == $this->lessc->vPrefix)
+ $tag[0] = $this->lessc->mPrefix;
+ }
+ return $tags;
+ }
+
+ // a list of expressions
+ protected function expressionList(&$exps) {
+ $values = array();
+
+ while ($this->expression($exp)) {
+ $values[] = $exp;
+ }
+
+ if (count($values) == 0) return false;
+
+ $exps = lessc::compressList($values, ' ');
+ return true;
+ }
+
+ /**
+ * Attempt to consume an expression.
+ * @link http://en.wikipedia.org/wiki/Operator-precedence_parser#Pseudo-code
+ */
+ protected function expression(&$out) {
+ if ($this->value($lhs)) {
+ $out = $this->expHelper($lhs, 0);
+
+ // look for / shorthand
+ if (!empty($this->env->supressedDivision)) {
+ unset($this->env->supressedDivision);
+ $s = $this->seek();
+ if ($this->literal("/") && $this->value($rhs)) {
+ $out = array("list", "",
+ array($out, array("keyword", "/"), $rhs));
+ } else {
+ $this->seek($s);
+ }
+ }
+
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * recursively parse infix equation with $lhs at precedence $minP
+ */
+ protected function expHelper($lhs, $minP) {
+ $this->inExp = true;
+ $ss = $this->seek();
+
+ while (true) {
+ $whiteBefore = isset($this->buffer[$this->count - 1]) &&
+ ctype_space($this->buffer[$this->count - 1]);
+
+ // If there is whitespace before the operator, then we require
+ // whitespace after the operator for it to be an expression
+ $needWhite = $whiteBefore && !$this->inParens;
+
+ if ($this->match(self::$operatorString.($needWhite ? '\s' : ''), $m) && self::$precedence[$m[1]] >= $minP) {
+ if (!$this->inParens && isset($this->env->currentProperty) && $m[1] == "/" && empty($this->env->supressedDivision)) {
+ foreach (self::$supressDivisionProps as $pattern) {
+ if (preg_match($pattern, $this->env->currentProperty)) {
+ $this->env->supressedDivision = true;
+ break 2;
+ }
+ }
+ }
+
+
+ $whiteAfter = isset($this->buffer[$this->count - 1]) &&
+ ctype_space($this->buffer[$this->count - 1]);
+
+ if (!$this->value($rhs)) break;
+
+ // peek for next operator to see what to do with rhs
+ if ($this->peek(self::$operatorString, $next) && self::$precedence[$next[1]] > self::$precedence[$m[1]]) {
+ $rhs = $this->expHelper($rhs, self::$precedence[$next[1]]);
+ }
+
+ $lhs = array('expression', $m[1], $lhs, $rhs, $whiteBefore, $whiteAfter);
+ $ss = $this->seek();
+
+ continue;
+ }
+
+ break;
+ }
+
+ $this->seek($ss);
+
+ return $lhs;
+ }
+
+ // consume a list of values for a property
+ public function propertyValue(&$value, $keyName = null) {
+ $values = array();
+
+ if ($keyName !== null) $this->env->currentProperty = $keyName;
+
+ $s = null;
+ while ($this->expressionList($v)) {
+ $values[] = $v;
+ $s = $this->seek();
+ if (!$this->literal(',')) break;
+ }
+
+ if ($s) $this->seek($s);
+
+ if ($keyName !== null) unset($this->env->currentProperty);
+
+ if (count($values) == 0) return false;
+
+ $value = lessc::compressList($values, ', ');
+ return true;
+ }
+
+ protected function parenValue(&$out) {
+ $s = $this->seek();
+
+ // speed shortcut
+ if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] != "(") {
+ return false;
+ }
+
+ $inParens = $this->inParens;
+ if ($this->literal("(") &&
+ ($this->inParens = true) && $this->expression($exp) &&
+ $this->literal(")"))
+ {
+ $out = $exp;
+ $this->inParens = $inParens;
+ return true;
+ } else {
+ $this->inParens = $inParens;
+ $this->seek($s);
+ }
+
+ return false;
+ }
+
+ // a single value
+ protected function value(&$value) {
+ $s = $this->seek();
+
+ // speed shortcut
+ if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] == "-") {
+ // negation
+ if ($this->literal("-", false) &&
+ (($this->variable($inner) && $inner = array("variable", $inner)) ||
+ $this->unit($inner) ||
+ $this->parenValue($inner)))
+ {
+ $value = array("unary", "-", $inner);
+ return true;
+ } else {
+ $this->seek($s);
+ }
+ }
+
+ if ($this->parenValue($value)) return true;
+ if ($this->unit($value)) return true;
+ if ($this->color($value)) return true;
+ if ($this->func($value)) return true;
+ if ($this->string($value)) return true;
+
+ if ($this->keyword($word)) {
+ $value = array('keyword', $word);
+ return true;
+ }
+
+ // try a variable
+ if ($this->variable($var)) {
+ $value = array('variable', $var);
+ return true;
+ }
+
+ // unquote string (should this work on any type?
+ if ($this->literal("~") && $this->string($str)) {
+ $value = array("escape", $str);
+ return true;
+ } else {
+ $this->seek($s);
+ }
+
+ // css hack: \0
+ if ($this->literal('\\') && $this->match('([0-9]+)', $m)) {
+ $value = array('keyword', '\\'.$m[1]);
+ return true;
+ } else {
+ $this->seek($s);
+ }
+
+ return false;
+ }
+
+ // an import statement
+ protected function import(&$out) {
+ if (!$this->literal('@import')) return false;
+
+ // @import "something.css" media;
+ // @import url("something.css") media;
+ // @import url(something.css) media;
+
+ if ($this->propertyValue($value)) {
+ $out = array("import", $value);
+ return true;
+ }
+ }
+
+ protected function mediaQueryList(&$out) {
+ if ($this->genericList($list, "mediaQuery", ",", false)) {
+ $out = $list[2];
+ return true;
+ }
+ return false;
+ }
+
+ protected function mediaQuery(&$out) {
+ $s = $this->seek();
+
+ $expressions = null;
+ $parts = array();
+
+ if (($this->literal("only") && ($only = true) || $this->literal("not") && ($not = true) || true) && $this->keyword($mediaType)) {
+ $prop = array("mediaType");
+ if (isset($only)) $prop[] = "only";
+ if (isset($not)) $prop[] = "not";
+ $prop[] = $mediaType;
+ $parts[] = $prop;
+ } else {
+ $this->seek($s);
+ }
+
+
+ if (!empty($mediaType) && !$this->literal("and")) {
+ // ~
+ } else {
+ $this->genericList($expressions, "mediaExpression", "and", false);
+ if (is_array($expressions)) $parts = array_merge($parts, $expressions[2]);
+ }
+
+ if (count($parts) == 0) {
+ $this->seek($s);
+ return false;
+ }
+
+ $out = $parts;
+ return true;
+ }
+
+ protected function mediaExpression(&$out) {
+ $s = $this->seek();
+ $value = null;
+ if ($this->literal("(") &&
+ $this->keyword($feature) &&
+ ($this->literal(":") && $this->expression($value) || true) &&
+ $this->literal(")"))
+ {
+ $out = array("mediaExp", $feature);
+ if ($value) $out[] = $value;
+ return true;
+ } elseif ($this->variable($variable)) {
+ $out = array('variable', $variable);
+ return true;
+ }
+
+ $this->seek($s);
+ return false;
+ }
+
+ // an unbounded string stopped by $end
+ protected function openString($end, &$out, $nestingOpen=null, $rejectStrs = null) {
+ $oldWhite = $this->eatWhiteDefault;
+ $this->eatWhiteDefault = false;
+
+ $stop = array("'", '"', "@{", $end);
+ $stop = array_map(array("lessc", "preg_quote"), $stop);
+ // $stop[] = self::$commentMulti;
+
+ if (!is_null($rejectStrs)) {
+ $stop = array_merge($stop, $rejectStrs);
+ }
+
+ $patt = '(.*?)('.implode("|", $stop).')';
+
+ $nestingLevel = 0;
+
+ $content = array();
+ while ($this->match($patt, $m, false)) {
+ if (!empty($m[1])) {
+ $content[] = $m[1];
+ if ($nestingOpen) {
+ $nestingLevel += substr_count($m[1], $nestingOpen);
+ }
+ }
+
+ $tok = $m[2];
+
+ $this->count-= strlen($tok);
+ if ($tok == $end) {
+ if ($nestingLevel == 0) {
+ break;
+ } else {
+ $nestingLevel--;
+ }
+ }
+
+ if (($tok == "'" || $tok == '"') && $this->string($str)) {
+ $content[] = $str;
+ continue;
+ }
+
+ if ($tok == "@{" && $this->interpolation($inter)) {
+ $content[] = $inter;
+ continue;
+ }
+
+ if (!empty($rejectStrs) && in_array($tok, $rejectStrs)) {
+ break;
+ }
+
+ $content[] = $tok;
+ $this->count+= strlen($tok);
+ }
+
+ $this->eatWhiteDefault = $oldWhite;
+
+ if (count($content) == 0) return false;
+
+ // trim the end
+ if (is_string(end($content))) {
+ $content[count($content) - 1] = rtrim(end($content));
+ }
+
+ $out = array("string", "", $content);
+ return true;
+ }
+
+ protected function string(&$out) {
+ $s = $this->seek();
+ if ($this->literal('"', false)) {
+ $delim = '"';
+ } elseif ($this->literal("'", false)) {
+ $delim = "'";
+ } else {
+ return false;
+ }
+
+ $content = array();
+
+ // look for either ending delim , escape, or string interpolation
+ $patt = '([^\n]*?)(@\{|\\\\|' .
+ lessc::preg_quote($delim).')';
+
+ $oldWhite = $this->eatWhiteDefault;
+ $this->eatWhiteDefault = false;
+
+ while ($this->match($patt, $m, false)) {
+ $content[] = $m[1];
+ if ($m[2] == "@{") {
+ $this->count -= strlen($m[2]);
+ if ($this->interpolation($inter, false)) {
+ $content[] = $inter;
+ } else {
+ $this->count += strlen($m[2]);
+ $content[] = "@{"; // ignore it
+ }
+ } elseif ($m[2] == '\\') {
+ $content[] = $m[2];
+ if ($this->literal($delim, false)) {
+ $content[] = $delim;
+ }
+ } else {
+ $this->count -= strlen($delim);
+ break; // delim
+ }
+ }
+
+ $this->eatWhiteDefault = $oldWhite;
+
+ if ($this->literal($delim)) {
+ $out = array("string", $delim, $content);
+ return true;
+ }
+
+ $this->seek($s);
+ return false;
+ }
+
+ protected function interpolation(&$out) {
+ $oldWhite = $this->eatWhiteDefault;
+ $this->eatWhiteDefault = true;
+
+ $s = $this->seek();
+ if ($this->literal("@{") &&
+ $this->openString("}", $interp, null, array("'", '"', ";")) &&
+ $this->literal("}", false))
+ {
+ $out = array("interpolate", $interp);
+ $this->eatWhiteDefault = $oldWhite;
+ if ($this->eatWhiteDefault) $this->whitespace();
+ return true;
+ }
+
+ $this->eatWhiteDefault = $oldWhite;
+ $this->seek($s);
+ return false;
+ }
+
+ protected function unit(&$unit) {
+ // speed shortcut
+ if (isset($this->buffer[$this->count])) {
+ $char = $this->buffer[$this->count];
+ if (!ctype_digit($char) && $char != ".") return false;
+ }
+
+ if ($this->match('([0-9]+(?:\.[0-9]*)?|\.[0-9]+)([%a-zA-Z]+)?', $m)) {
+ $unit = array("number", $m[1], empty($m[2]) ? "" : $m[2]);
+ return true;
+ }
+ return false;
+ }
+
+ // a # color
+ protected function color(&$out) {
+ if ($this->match('(#(?:[0-9a-f]{8}|[0-9a-f]{6}|[0-9a-f]{3}))', $m)) {
+ if (strlen($m[1]) > 7) {
+ $out = array("string", "", array($m[1]));
+ } else {
+ $out = array("raw_color", $m[1]);
+ }
+ return true;
+ }
+
+ return false;
+ }
+
+ // consume an argument definition list surrounded by ()
+ // each argument is a variable name with optional value
+ // or at the end a ... or a variable named followed by ...
+ // arguments are separated by , unless a ; is in the list, then ; is the
+ // delimiter.
+ protected function argumentDef(&$args, &$isVararg) {
+ $s = $this->seek();
+ if (!$this->literal('(')) return false;
+
+ $values = array();
+ $delim = ",";
+ $method = "expressionList";
+
+ $isVararg = false;
+ while (true) {
+ if ($this->literal("...")) {
+ $isVararg = true;
+ break;
+ }
+
+ if ($this->$method($value)) {
+ if ($value[0] == "variable") {
+ $arg = array("arg", $value[1]);
+ $ss = $this->seek();
+
+ if ($this->assign() && $this->$method($rhs)) {
+ $arg[] = $rhs;
+ } else {
+ $this->seek($ss);
+ if ($this->literal("...")) {
+ $arg[0] = "rest";
+ $isVararg = true;
+ }
+ }
+
+ $values[] = $arg;
+ if ($isVararg) break;
+ continue;
+ } else {
+ $values[] = array("lit", $value);
+ }
+ }
+
+
+ if (!$this->literal($delim)) {
+ if ($delim == "," && $this->literal(";")) {
+ // found new delim, convert existing args
+ $delim = ";";
+ $method = "propertyValue";
+
+ // transform arg list
+ if (isset($values[1])) { // 2 items
+ $newList = array();
+ foreach ($values as $i => $arg) {
+ switch($arg[0]) {
+ case "arg":
+ if ($i) {
+ $this->throwError("Cannot mix ; and , as delimiter types");
+ }
+ $newList[] = $arg[2];
+ break;
+ case "lit":
+ $newList[] = $arg[1];
+ break;
+ case "rest":
+ $this->throwError("Unexpected rest before semicolon");
+ }
+ }
+
+ $newList = array("list", ", ", $newList);
+
+ switch ($values[0][0]) {
+ case "arg":
+ $newArg = array("arg", $values[0][1], $newList);
+ break;
+ case "lit":
+ $newArg = array("lit", $newList);
+ break;
+ }
+
+ } elseif ($values) { // 1 item
+ $newArg = $values[0];
+ }
+
+ if ($newArg) {
+ $values = array($newArg);
+ }
+ } else {
+ break;
+ }
+ }
+ }
+
+ if (!$this->literal(')')) {
+ $this->seek($s);
+ return false;
+ }
+
+ $args = $values;
+
+ return true;
+ }
+
+ // consume a list of tags
+ // this accepts a hanging delimiter
+ protected function tags(&$tags, $simple = false, $delim = ',') {
+ $tags = array();
+ while ($this->tag($tt, $simple)) {
+ $tags[] = $tt;
+ if (!$this->literal($delim)) break;
+ }
+ if (count($tags) == 0) return false;
+
+ return true;
+ }
+
+ // list of tags of specifying mixin path
+ // optionally separated by > (lazy, accepts extra >)
+ protected function mixinTags(&$tags) {
+ $tags = array();
+ while ($this->tag($tt, true)) {
+ $tags[] = $tt;
+ $this->literal(">");
+ }
+
+ if (count($tags) == 0) return false;
+
+ return true;
+ }
+
+ // a bracketed value (contained within in a tag definition)
+ protected function tagBracket(&$parts, &$hasExpression) {
+ // speed shortcut
+ if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] != "[") {
+ return false;
+ }
+
+ $s = $this->seek();
+
+ $hasInterpolation = false;
+
+ if ($this->literal("[", false)) {
+ $attrParts = array("[");
+ // keyword, string, operator
+ while (true) {
+ if ($this->literal("]", false)) {
+ $this->count--;
+ break; // get out early
+ }
+
+ if ($this->match('\s+', $m)) {
+ $attrParts[] = " ";
+ continue;
+ }
+ if ($this->string($str)) {
+ // escape parent selector, (yuck)
+ foreach ($str[2] as &$chunk) {
+ $chunk = str_replace($this->lessc->parentSelector, "$&$", $chunk);
+ }
+
+ $attrParts[] = $str;
+ $hasInterpolation = true;
+ continue;
+ }
+
+ if ($this->keyword($word)) {
+ $attrParts[] = $word;
+ continue;
+ }
+
+ if ($this->interpolation($inter, false)) {
+ $attrParts[] = $inter;
+ $hasInterpolation = true;
+ continue;
+ }
+
+ // operator, handles attr namespace too
+ if ($this->match('[|-~\$\*\^=]+', $m)) {
+ $attrParts[] = $m[0];
+ continue;
+ }
+
+ break;
+ }
+
+ if ($this->literal("]", false)) {
+ $attrParts[] = "]";
+ foreach ($attrParts as $part) {
+ $parts[] = $part;
+ }
+ $hasExpression = $hasExpression || $hasInterpolation;
+ return true;
+ }
+ $this->seek($s);
+ }
+
+ $this->seek($s);
+ return false;
+ }
+
+ // a space separated list of selectors
+ protected function tag(&$tag, $simple = false) {
+ if ($simple)
+ $chars = '^@,:;{}\][>\(\) "\'';
+ else
+ $chars = '^@,;{}["\'';
+
+ $s = $this->seek();
+
+ $hasExpression = false;
+ $parts = array();
+ while ($this->tagBracket($parts, $hasExpression));
+
+ $oldWhite = $this->eatWhiteDefault;
+ $this->eatWhiteDefault = false;
+
+ while (true) {
+ if ($this->match('(['.$chars.'0-9]['.$chars.']*)', $m)) {
+ $parts[] = $m[1];
+ if ($simple) break;
+
+ while ($this->tagBracket($parts, $hasExpression));
+ continue;
+ }
+
+ if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] == "@") {
+ if ($this->interpolation($interp)) {
+ $hasExpression = true;
+ $interp[2] = true; // don't unescape
+ $parts[] = $interp;
+ continue;
+ }
+
+ if ($this->literal("@")) {
+ $parts[] = "@";
+ continue;
+ }
+ }
+
+ if ($this->unit($unit)) { // for keyframes
+ $parts[] = $unit[1];
+ $parts[] = $unit[2];
+ continue;
+ }
+
+ break;
+ }
+
+ $this->eatWhiteDefault = $oldWhite;
+ if (!$parts) {
+ $this->seek($s);
+ return false;
+ }
+
+ if ($hasExpression) {
+ $tag = array("exp", array("string", "", $parts));
+ } else {
+ $tag = trim(implode($parts));
+ }
+
+ $this->whitespace();
+ return true;
+ }
+
+ // a css function
+ protected function func(&$func) {
+ $s = $this->seek();
+
+ if ($this->match('(%|[\w\-_][\w\-_:\.]+|[\w_])', $m) && $this->literal('(')) {
+ $fname = $m[1];
+
+ $sPreArgs = $this->seek();
+
+ $args = array();
+ while (true) {
+ $ss = $this->seek();
+ // this ugly nonsense is for ie filter properties
+ if ($this->keyword($name) && $this->literal('=') && $this->expressionList($value)) {
+ $args[] = array("string", "", array($name, "=", $value));
+ } else {
+ $this->seek($ss);
+ if ($this->expressionList($value)) {
+ $args[] = $value;
+ }
+ }
+
+ if (!$this->literal(',')) break;
+ }
+ $args = array('list', ',', $args);
+
+ if ($this->literal(')')) {
+ $func = array('function', $fname, $args);
+ return true;
+ } elseif ($fname == 'url') {
+ // couldn't parse and in url? treat as string
+ $this->seek($sPreArgs);
+ if ($this->openString(")", $string) && $this->literal(")")) {
+ $func = array('function', $fname, $string);
+ return true;
+ }
+ }
+ }
+
+ $this->seek($s);
+ return false;
+ }
+
+ // consume a less variable
+ protected function variable(&$name) {
+ $s = $this->seek();
+ if ($this->literal($this->lessc->vPrefix, false) &&
+ ($this->variable($sub) || $this->keyword($name)))
+ {
+ if (!empty($sub)) {
+ $name = array('variable', $sub);
+ } else {
+ $name = $this->lessc->vPrefix.$name;
+ }
+ return true;
+ }
+
+ $name = null;
+ $this->seek($s);
+ return false;
+ }
+
+ /**
+ * Consume an assignment operator
+ * Can optionally take a name that will be set to the current property name
+ */
+ protected function assign($name = null) {
+ if ($name) $this->currentProperty = $name;
+ return $this->literal(':') || $this->literal('=');
+ }
+
+ // consume a keyword
+ protected function keyword(&$word) {
+ if ($this->match('([\w_\-\*!"][\w\-_"]*)', $m)) {
+ $word = $m[1];
+ return true;
+ }
+ return false;
+ }
+
+ // consume an end of statement delimiter
+ protected function end() {
+ if ($this->literal(';', false)) {
+ return true;
+ } elseif ($this->count == strlen($this->buffer) || $this->buffer[$this->count] == '}') {
+ // if there is end of file or a closing block next then we don't need a ;
+ return true;
+ }
+ return false;
+ }
+
+ protected function guards(&$guards) {
+ $s = $this->seek();
+
+ if (!$this->literal("when")) {
+ $this->seek($s);
+ return false;
+ }
+
+ $guards = array();
+
+ while ($this->guardGroup($g)) {
+ $guards[] = $g;
+ if (!$this->literal(",")) break;
+ }
+
+ if (count($guards) == 0) {
+ $guards = null;
+ $this->seek($s);
+ return false;
+ }
+
+ return true;
+ }
+
+ // a bunch of guards that are and'd together
+ // TODO rename to guardGroup
+ protected function guardGroup(&$guardGroup) {
+ $s = $this->seek();
+ $guardGroup = array();
+ while ($this->guard($guard)) {
+ $guardGroup[] = $guard;
+ if (!$this->literal("and")) break;
+ }
+
+ if (count($guardGroup) == 0) {
+ $guardGroup = null;
+ $this->seek($s);
+ return false;
+ }
+
+ return true;
+ }
+
+ protected function guard(&$guard) {
+ $s = $this->seek();
+ $negate = $this->literal("not");
+
+ if ($this->literal("(") && $this->expression($exp) && $this->literal(")")) {
+ $guard = $exp;
+ if ($negate) $guard = array("negate", $guard);
+ return true;
+ }
+
+ $this->seek($s);
+ return false;
+ }
+
+ /* raw parsing functions */
+
+ protected function literal($what, $eatWhitespace = null) {
+ if ($eatWhitespace === null) $eatWhitespace = $this->eatWhiteDefault;
+
+ // shortcut on single letter
+ if (!isset($what[1]) && isset($this->buffer[$this->count])) {
+ if ($this->buffer[$this->count] == $what) {
+ if (!$eatWhitespace) {
+ $this->count++;
+ return true;
+ }
+ // goes below...
+ } else {
+ return false;
+ }
+ }
+
+ if (!isset(self::$literalCache[$what])) {
+ self::$literalCache[$what] = lessc::preg_quote($what);
+ }
+
+ return $this->match(self::$literalCache[$what], $m, $eatWhitespace);
+ }
+
+ protected function genericList(&$out, $parseItem, $delim="", $flatten=true) {
+ $s = $this->seek();
+ $items = array();
+ while ($this->$parseItem($value)) {
+ $items[] = $value;
+ if ($delim) {
+ if (!$this->literal($delim)) break;
+ }
+ }
+
+ if (count($items) == 0) {
+ $this->seek($s);
+ return false;
+ }
+
+ if ($flatten && count($items) == 1) {
+ $out = $items[0];
+ } else {
+ $out = array("list", $delim, $items);
+ }
+
+ return true;
+ }
+
+
+ // advance counter to next occurrence of $what
+ // $until - don't include $what in advance
+ // $allowNewline, if string, will be used as valid char set
+ protected function to($what, &$out, $until = false, $allowNewline = false) {
+ if (is_string($allowNewline)) {
+ $validChars = $allowNewline;
+ } else {
+ $validChars = $allowNewline ? "." : "[^\n]";
+ }
+ if (!$this->match('('.$validChars.'*?)'.lessc::preg_quote($what), $m, !$until)) return false;
+ if ($until) $this->count -= strlen($what); // give back $what
+ $out = $m[1];
+ return true;
+ }
+
+ // try to match something on head of buffer
+ protected function match($regex, &$out, $eatWhitespace = null) {
+ if ($eatWhitespace === null) $eatWhitespace = $this->eatWhiteDefault;
+
+ $r = '/'.$regex.($eatWhitespace && !$this->writeComments ? '\s*' : '').'/Ais';
+ if (preg_match($r, $this->buffer, $out, null, $this->count)) {
+ $this->count += strlen($out[0]);
+ if ($eatWhitespace && $this->writeComments) $this->whitespace();
+ return true;
+ }
+ return false;
+ }
+
+ // match some whitespace
+ protected function whitespace() {
+ if ($this->writeComments) {
+ $gotWhite = false;
+ while (preg_match(self::$whitePattern, $this->buffer, $m, null, $this->count)) {
+ if (isset($m[1]) && empty($this->seenComments[$this->count])) {
+ $this->append(array("comment", $m[1]));
+ $this->seenComments[$this->count] = true;
+ }
+ $this->count += strlen($m[0]);
+ $gotWhite = true;
+ }
+ return $gotWhite;
+ } else {
+ $this->match("", $m);
+ return strlen($m[0]) > 0;
+ }
+ }
+
+ // match something without consuming it
+ protected function peek($regex, &$out = null, $from=null) {
+ if (is_null($from)) $from = $this->count;
+ $r = '/'.$regex.'/Ais';
+ $result = preg_match($r, $this->buffer, $out, null, $from);
+
+ return $result;
+ }
+
+ // seek to a spot in the buffer or return where we are on no argument
+ protected function seek($where = null) {
+ if ($where === null) return $this->count;
+ else $this->count = $where;
+ return true;
+ }
+
+ /* misc functions */
+
+ public function throwError($msg = "parse error", $count = null) {
+ $count = is_null($count) ? $this->count : $count;
+
+ $line = $this->line +
+ substr_count(substr($this->buffer, 0, $count), "\n");
+
+ if (!empty($this->sourceName)) {
+ $loc = "$this->sourceName on line $line";
+ } else {
+ $loc = "line: $line";
+ }
+
+ // TODO this depends on $this->count
+ if ($this->peek("(.*?)(\n|$)", $m, $count)) {
+ throw new exception("$msg: failed at `$m[1]` $loc");
+ } else {
+ throw new exception("$msg: $loc");
+ }
+ }
+
+ protected function pushBlock($selectors=null, $type=null) {
+ $b = new stdclass;
+ $b->parent = $this->env;
+
+ $b->type = $type;
+ $b->id = self::$nextBlockId++;
+
+ $b->isVararg = false; // TODO: kill me from here
+ $b->tags = $selectors;
+
+ $b->props = array();
+ $b->children = array();
+
+ $this->env = $b;
+ return $b;
+ }
+
+ // push a block that doesn't multiply tags
+ protected function pushSpecialBlock($type) {
+ return $this->pushBlock(null, $type);
+ }
+
+ // append a property to the current block
+ protected function append($prop, $pos = null) {
+ if ($pos !== null) $prop[-1] = $pos;
+ $this->env->props[] = $prop;
+ }
+
+ // pop something off the stack
+ protected function pop() {
+ $old = $this->env;
+ $this->env = $this->env->parent;
+ return $old;
+ }
+
+ // remove comments from $text
+ // todo: make it work for all functions, not just url
+ protected function removeComments($text) {
+ $look = array(
+ 'url(', '//', '/*', '"', "'"
+ );
+
+ $out = '';
+ $min = null;
+ while (true) {
+ // find the next item
+ foreach ($look as $token) {
+ $pos = strpos($text, $token);
+ if ($pos !== false) {
+ if (!isset($min) || $pos < $min[1]) $min = array($token, $pos);
+ }
+ }
+
+ if (is_null($min)) break;
+
+ $count = $min[1];
+ $skip = 0;
+ $newlines = 0;
+ switch ($min[0]) {
+ case 'url(':
+ if (preg_match('/url\(.*?\)/', $text, $m, 0, $count))
+ $count += strlen($m[0]) - strlen($min[0]);
+ break;
+ case '"':
+ case "'":
+ if (preg_match('/'.$min[0].'.*?(?<!\\\\)'.$min[0].'/', $text, $m, 0, $count))
+ $count += strlen($m[0]) - 1;
+ break;
+ case '//':
+ $skip = strpos($text, "\n", $count);
+ if ($skip === false) $skip = strlen($text) - $count;
+ else $skip -= $count;
+ break;
+ case '/*':
+ if (preg_match('/\/\*.*?\*\//s', $text, $m, 0, $count)) {
+ $skip = strlen($m[0]);
+ $newlines = substr_count($m[0], "\n");
+ }
+ break;
+ }
+
+ if ($skip == 0) $count += strlen($min[0]);
+
+ $out .= substr($text, 0, $count).str_repeat("\n", $newlines);
+ $text = substr($text, $count + $skip);
+
+ $min = null;
+ }
+
+ return $out.$text;
+ }
+
+}
+
+class lessc_formatter_classic {
+ public $indentChar = " ";
+
+ public $break = "\n";
+ public $open = " {";
+ public $close = "}";
+ public $selectorSeparator = ", ";
+ public $assignSeparator = ":";
+
+ public $openSingle = " { ";
+ public $closeSingle = " }";
+
+ public $disableSingle = false;
+ public $breakSelectors = false;
+
+ public $compressColors = false;
+
+ public function __construct() {
+ $this->indentLevel = 0;
+ }
+
+ public function indentStr($n = 0) {
+ return str_repeat($this->indentChar, max($this->indentLevel + $n, 0));
+ }
+
+ public function property($name, $value) {
+ return $name . $this->assignSeparator . $value . ";";
+ }
+
+ protected function isEmpty($block) {
+ if (empty($block->lines)) {
+ foreach ($block->children as $child) {
+ if (!$this->isEmpty($child)) return false;
+ }
+
+ return true;
+ }
+ return false;
+ }
+
+ public function block($block) {
+ if ($this->isEmpty($block)) return;
+
+ $inner = $pre = $this->indentStr();
+
+ $isSingle = !$this->disableSingle &&
+ is_null($block->type) && count($block->lines) == 1;
+
+ if (!empty($block->selectors)) {
+ $this->indentLevel++;
+
+ if ($this->breakSelectors) {
+ $selectorSeparator = $this->selectorSeparator . $this->break . $pre;
+ } else {
+ $selectorSeparator = $this->selectorSeparator;
+ }
+
+ echo $pre .
+ implode($selectorSeparator, $block->selectors);
+ if ($isSingle) {
+ echo $this->openSingle;
+ $inner = "";
+ } else {
+ echo $this->open . $this->break;
+ $inner = $this->indentStr();
+ }
+
+ }
+
+ if (!empty($block->lines)) {
+ $glue = $this->break.$inner;
+ echo $inner . implode($glue, $block->lines);
+ if (!$isSingle && !empty($block->children)) {
+ echo $this->break;
+ }
+ }
+
+ foreach ($block->children as $child) {
+ $this->block($child);
+ }
+
+ if (!empty($block->selectors)) {
+ if (!$isSingle && empty($block->children)) echo $this->break;
+
+ if ($isSingle) {
+ echo $this->closeSingle . $this->break;
+ } else {
+ echo $pre . $this->close . $this->break;
+ }
+
+ $this->indentLevel--;
+ }
+ }
+}
+
+class lessc_formatter_compressed extends lessc_formatter_classic {
+ public $disableSingle = true;
+ public $open = "{";
+ public $selectorSeparator = ",";
+ public $assignSeparator = ":";
+ public $break = "";
+ public $compressColors = true;
+
+ public function indentStr($n = 0) {
+ return "";
+ }
+}
+
+class lessc_formatter_lessjs extends lessc_formatter_classic {
+ public $disableSingle = true;
+ public $breakSelectors = true;
+ public $assignSeparator = ": ";
+ public $selectorSeparator = ",";
+}
+
+
diff --git a/includes/limit.sh b/includes/limit.sh
index bc1988de..2a1545b6 100644
--- a/includes/limit.sh
+++ b/includes/limit.sh
@@ -6,6 +6,7 @@
# 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_INCLUDE_STDERR=
MW_CPU_LIMIT=0
MW_CGROUP=
MW_MEM_LIMIT=0
@@ -15,6 +16,10 @@ MW_WALL_CLOCK_LIMIT=0
# Override settings
eval "$2"
+if [ -n "$MW_INCLUDE_STDERR" ]; then
+ exec 2>&1
+fi
+
if [ "$MW_CPU_LIMIT" -gt 0 ]; then
ulimit -t "$MW_CPU_LIMIT"
fi
@@ -55,7 +60,7 @@ fi
# Clean up cgroup
cleanup() {
- # First we have to move the current task into a "garbage" group, otherwise
+ # 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
@@ -84,7 +89,7 @@ if [ -n "$MW_CGROUP" ]; then
updateTaskCount
if [ $NUM_TASKS -gt 1 ]; then
- # Spawn a monitor process which will continue to poll for completion
+ # 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
diff --git a/includes/logging/DeleteLogFormatter.php b/includes/logging/DeleteLogFormatter.php
new file mode 100644
index 00000000..01528b7e
--- /dev/null
+++ b/includes/logging/DeleteLogFormatter.php
@@ -0,0 +1,196 @@
+<?php
+/**
+ * Formatter for delete log entries.
+ *
+ * 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 Niklas Laxström
+ * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later
+ * @since 1.22
+ */
+
+/**
+ * This class formats delete log entries.
+ *
+ * @since 1.19
+ */
+class DeleteLogFormatter extends LogFormatter {
+ protected function getMessageKey() {
+ $key = parent::getMessageKey();
+ if ( in_array( $this->entry->getSubtype(), array( 'event', 'revision' ) ) ) {
+ if ( count( $this->getMessageParameters() ) < 5 ) {
+ return "$key-legacy";
+ }
+ }
+ return $key;
+ }
+
+ protected function getMessageParameters() {
+ if ( isset( $this->parsedParametersDeleteLog ) ) {
+ return $this->parsedParametersDeleteLog;
+ }
+
+ $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' || $params[3] === 'oldimage' ) )
+ ) {
+ $paramStart = $subtype === 'revision' ? 4 : 3;
+
+ $old = $this->parseBitField( $params[$paramStart + 1] );
+ $new = $this->parseBitField( $params[$paramStart + 2] );
+ list( $hid, $unhid, $extra ) = RevisionDeleter::getChanges( $new, $old );
+ $changes = array();
+ foreach ( $hid as $v ) {
+ $changes[] = $this->msg( "$v-hid" )->plain();
+ }
+ foreach ( $unhid as $v ) {
+ $changes[] = $this->msg( "$v-unhid" )->plain();
+ }
+ foreach ( $extra as $v ) {
+ $changes[] = $this->msg( $v )->plain();
+ }
+ $changeText = $this->context->getLanguage()->listToText( $changes );
+
+ $newParams = array_slice( $params, 0, 3 );
+ $newParams[3] = $changeText;
+ $count = count( explode( ',', $params[$paramStart] ) );
+ $newParams[4] = $this->context->getLanguage()->formatNum( $count );
+ return $this->parsedParametersDeleteLog = $newParams;
+ } else {
+ return $this->parsedParametersDeleteLog = array_slice( $params, 0, 3 );
+ }
+ }
+
+ return $this->parsedParametersDeleteLog = $params;
+ }
+
+ protected function parseBitField( $string ) {
+ // Input is like ofield=2134 or just the number
+ if ( strpos( $string, 'field=' ) === 1 ) {
+ list( , $field ) = explode( '=', $string );
+ return (int)$field;
+ } else {
+ return (int)$string;
+ }
+ }
+
+ public function getActionLinks() {
+ $user = $this->context->getUser();
+ if ( !$user->isAllowed( 'deletedhistory' ) || $this->entry->isDeleted( LogPage::DELETED_ACTION ) ) {
+ return '';
+ }
+
+ switch ( $this->entry->getSubtype() ) {
+ case 'delete': // Show undelete link
+ if ( $user->isAllowed( 'undelete' ) ) {
+ $message = 'undeletelink';
+ } else {
+ $message = 'undeleteviewlink';
+ }
+ $revert = Linker::linkKnown(
+ SpecialPage::getTitleFor( 'Undelete' ),
+ $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
+ $params = $this->extractParameters();
+ if ( !isset( $params[3] ) || !isset( $params[4] ) ) {
+ return '';
+ }
+
+ // Different revision types use different URL params...
+ $key = $params[3];
+ // This is a CSV of the IDs
+ $ids = explode( ',', $params[4] );
+
+ $links = array();
+
+ // If there's only one item, we can show a diff link
+ if ( count( $ids ) == 1 ) {
+ // Live revision diffs...
+ if ( $key == 'oldid' || $key == 'revision' ) {
+ $links[] = Linker::linkKnown(
+ $this->entry->getTarget(),
+ $this->msg( 'diff' )->escaped(),
+ array(),
+ array(
+ 'diff' => intval( $ids[0] ),
+ 'unhide' => 1
+ )
+ );
+ // Deleted revision diffs...
+ } elseif ( $key == 'artimestamp' || $key == 'archive' ) {
+ $links[] = Linker::linkKnown(
+ SpecialPage::getTitleFor( 'Undelete' ),
+ $this->msg( 'diff' )->escaped(),
+ array(),
+ array(
+ 'target' => $this->entry->getTarget()->getPrefixedDBkey(),
+ 'diff' => 'prev',
+ 'timestamp' => $ids[0]
+ )
+ );
+ }
+ }
+
+ // View/modify link...
+ $links[] = Linker::linkKnown(
+ SpecialPage::getTitleFor( 'Revisiondelete' ),
+ $this->msg( 'revdel-restore' )->escaped(),
+ array(),
+ array(
+ 'target' => $this->entry->getTarget()->getPrefixedText(),
+ 'type' => $key,
+ 'ids' => implode( ',', $ids ),
+ )
+ );
+
+ return $this->msg( 'parentheses' )->rawParams(
+ $this->context->getLanguage()->pipeList( $links ) )->escaped();
+
+ case 'event': // Hidden log items, give review link
+ $params = $this->extractParameters();
+ if ( !isset( $params[3] ) ) {
+ return '';
+ }
+ // This is a CSV of the IDs
+ $query = $params[3];
+ // Link to each hidden object ID, $params[1] is the url param
+ $revert = Linker::linkKnown(
+ SpecialPage::getTitleFor( 'Revisiondelete' ),
+ $this->msg( 'revdel-restore' )->escaped(),
+ array(),
+ array(
+ 'target' => $this->entry->getTarget()->getPrefixedText(),
+ 'type' => 'logging',
+ 'ids' => $query
+ )
+ );
+ return $this->msg( 'parentheses' )->rawParams( $revert )->escaped();
+ default:
+ return '';
+ }
+ }
+}
diff --git a/includes/logging/LogEntry.php b/includes/logging/LogEntry.php
index 0f20ed16..b2a8e50d 100644
--- a/includes/logging/LogEntry.php
+++ b/includes/logging/LogEntry.php
@@ -151,7 +151,7 @@ class DatabaseLogEntry extends LogEntryBase {
return array(
'tables' => $tables,
'fields' => $fields,
- 'conds' => array(),
+ 'conds' => array(),
'options' => array(),
'join_conds' => $joins,
);
@@ -165,7 +165,7 @@ class DatabaseLogEntry extends LogEntryBase {
*/
public static function newFromRow( $row ) {
if ( is_array( $row ) && isset( $row['rc_logid'] ) ) {
- return new RCDatabaseLogEntry( (object) $row );
+ return new RCDatabaseLogEntry( (object)$row );
} else {
return new self( $row );
}
@@ -233,8 +233,8 @@ class DatabaseLogEntry extends LogEntryBase {
}
public function getPerformer() {
- if( !$this->performer ) {
- $userId = (int) $this->row->log_user;
+ 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 );
@@ -291,8 +291,8 @@ class RCDatabaseLogEntry extends DatabaseLogEntry {
}
public function getPerformer() {
- if( !$this->performer ) {
- $userId = (int) $this->row->rc_user;
+ if ( !$this->performer ) {
+ $userId = (int)$this->row->rc_user;
if ( $userId !== 0 ) {
$this->performer = User::newFromId( $userId );
} else {
@@ -334,6 +334,7 @@ class ManualLogEntry extends LogEntryBase {
protected $type; ///!< @var string
protected $subtype; ///!< @var string
protected $parameters = array(); ///!< @var array
+ protected $relations = array(); ///!< @var array
protected $performer; ///!< @var User
protected $target; ///!< @var Title
protected $timestamp; ///!< @var string
@@ -374,6 +375,17 @@ class ManualLogEntry extends LogEntryBase {
}
/**
+ * Declare arbitrary tag/value relations to this log entry.
+ * These can be used to filter log entries later on.
+ *
+ * @param array Map of (tag => (list of values))
+ * @since 1.22
+ */
+ public function setRelations( array $relations ) {
+ $this->relations = $relations;
+ }
+
+ /**
* Set the user that performed the action being logged.
*
* @since 1.19
@@ -430,12 +442,13 @@ class ManualLogEntry extends LogEntryBase {
/**
* Inserts the entry into the logging table.
+ * @param IDatabase $dbw
* @return int If of the log entry
*/
- public function insert() {
+ public function insert( IDatabase $dbw = null ) {
global $wgContLang;
- $dbw = wfGetDB( DB_MASTER );
+ $dbw = $dbw ?: wfGetDB( DB_MASTER );
$id = $dbw->nextSequenceValue( 'logging_log_id_seq' );
if ( $this->timestamp === null ) {
@@ -459,10 +472,28 @@ class ManualLogEntry extends LogEntryBase {
'log_title' => $this->getTarget()->getDBkey(),
'log_page' => $this->getTarget()->getArticleID(),
'log_comment' => $comment,
- 'log_params' => serialize( (array) $this->getParameters() ),
+ 'log_params' => serialize( (array)$this->getParameters() ),
);
$dbw->insert( 'logging', $data, __METHOD__ );
$this->id = !is_null( $id ) ? $id : $dbw->insertId();
+
+ $rows = array();
+ foreach ( $this->relations as $tag => $values ) {
+ if ( !strlen( $tag ) ) {
+ throw new MWException( "Got empty log search tag." );
+ }
+ foreach ( $values as $value ) {
+ $rows[] = array(
+ 'ls_field' => $tag,
+ 'ls_value' => $value,
+ 'ls_log_id' => $this->id
+ );
+ }
+ }
+ if ( count( $rows ) ) {
+ $dbw->insert( 'log_search', $rows, __METHOD__, 'IGNORE' );
+ }
+
return $this->id;
}
@@ -503,7 +534,7 @@ class ManualLogEntry extends LogEntryBase {
$this->getSubtype(),
$this->getTarget(),
$this->getComment(),
- serialize( (array) $this->getParameters() ),
+ serialize( (array)$this->getParameters() ),
$newId,
$formatter->getIRCActionComment() // Used for IRC feeds
);
@@ -513,7 +544,7 @@ class ManualLogEntry extends LogEntryBase {
}
if ( $to === 'udp' || $to === 'rcandudp' ) {
- $rc->notifyRC2UDP();
+ $rc->notifyRCFeeds();
}
}
@@ -555,7 +586,7 @@ class ManualLogEntry extends LogEntryBase {
}
public function getDeleted() {
- return (int) $this->deleted;
+ return (int)$this->deleted;
}
}
diff --git a/includes/logging/LogEventsList.php b/includes/logging/LogEventsList.php
index 501af7d6..c27b57af 100644
--- a/includes/logging/LogEventsList.php
+++ b/includes/logging/LogEventsList.php
@@ -74,9 +74,9 @@ 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 ) ) {
+ if ( LogPage::isLogType( $headerType ) ) {
$page = new LogPage( $headerType );
$out->setPageTitle( $page->getName()->text() );
$out->addHTML( $page->getDescription()->parseAsBlock() );
@@ -97,14 +97,14 @@ class LogEventsList extends ContextSource {
* @param $filter: array
* @param $tagFilter: array?
*/
- public function showOptions( $types=array(), $user = '', $page = '', $pattern = '', $year = '',
+ public function showOptions( $types = array(), $user = '', $page = '', $pattern = '', $year = '',
$month = '', $filter = null, $tagFilter = '' ) {
global $wgScript, $wgMiserMode;
$title = SpecialPage::getTitleFor( 'Log' );
// For B/C, we take strings, but make sure they are converted...
- $types = ($types === '') ? array() : (array)$types;
+ $types = ( $types === '' ) ? array() : (array)$types;
$tagSelector = ChangeTags::buildTagFilterSelector( $tagFilter );
@@ -156,7 +156,7 @@ class LogEventsList extends ContextSource {
// Option value -> message mapping
$links = array();
$hiddens = ''; // keep track for "go" button
- foreach( $filter as $type => $val ) {
+ foreach ( $filter as $type => $val ) {
// Should the below assignment be outside the foreach?
// Then it would have to be copied. Not certain what is more expensive.
$query = $this->getDefaultQuery();
@@ -172,6 +172,7 @@ class LogEventsList extends ContextSource {
$query
);
+ // Message: log-show-hide-patrol
$links[$type] = $this->msg( "log-show-hide-{$type}" )->rawParams( $link )->escaped();
$hiddens .= Html::hidden( "hide_{$type}_log", $val ) . "\n";
}
@@ -212,7 +213,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 ) ) {
@@ -229,7 +230,7 @@ class LogEventsList extends ContextSource {
$typesByName = array( '' => $public ) + $typesByName;
$select = new XmlSelect( 'type' );
- foreach( $typesByName as $type => $name ) {
+ foreach ( $typesByName as $type => $name ) {
$select->addOption( $name, $type );
}
@@ -273,10 +274,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 );
}
@@ -342,16 +343,16 @@ class LogEventsList extends ContextSource {
* @return string
*/
private function getShowHideLinks( $row ) {
- if( ( $this->flags == self::NO_ACTION_LINK ) // we don't want to see the links
+ if ( ( $this->flags == self::NO_ACTION_LINK ) // we don't want to see the links
|| $row->log_type == 'suppress' ) { // no one can hide items from the suppress log
return '';
}
$del = '';
$user = $this->getUser();
// Don't show useless checkbox to people who cannot hide log entries
- if( $user->isAllowed( 'deletedhistory' ) ) {
+ if ( $user->isAllowed( 'deletedhistory' ) ) {
$canHide = $user->isAllowed( 'deletelogentry' );
- if( $row->log_deleted || $canHide ) {
+ 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' ) );
@@ -364,8 +365,8 @@ class LogEventsList extends ContextSource {
} else {
$query = array(
'target' => SpecialPage::getTitleFor( 'Log', $row->log_type )->getPrefixedDBkey(),
- 'type' => 'logging',
- 'ids' => $row->log_id,
+ 'type' => 'logging',
+ 'ids' => $row->log_id,
);
$del = Linker::revDeleteLink( $query, self::isDeleted( $row, LogPage::DELETED_RESTRICTED ), $canHide );
}
@@ -385,10 +386,10 @@ class LogEventsList extends ContextSource {
public static function typeAction( $row, $type, $action, $right = '' ) {
$match = is_array( $type ) ?
in_array( $row->log_type, $type ) : $row->log_type == $type;
- if( $match ) {
+ if ( $match ) {
$match = is_array( $action ) ?
in_array( $row->log_action, $action ) : $row->log_action == $action;
- if( $match && $right ) {
+ if ( $match && $right ) {
global $wgUser;
$match = $wgUser->isAllowed( $right );
}
@@ -419,7 +420,7 @@ class LogEventsList extends ContextSource {
* @return Boolean
*/
public static function userCanBitfield( $bitfield, $field, User $user = null ) {
- if( $bitfield & $field ) {
+ if ( $bitfield & $field ) {
if ( $bitfield & LogPage::DELETED_RESTRICTED ) {
$permission = 'suppressrevision';
} else {
@@ -464,10 +465,11 @@ class LogEventsList extends ContextSource {
* set to '' to unset offset
* - wrap String Wrap the message in html (usually something like "<div ...>$1</div>").
* - flags Integer display flags (NO_ACTION_LINK,NO_EXTRA_USER_LINKS)
+ * - useRequestParams boolean Set true to use Pager-related parameters in the WebRequest
* @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,
@@ -475,7 +477,8 @@ class LogEventsList extends ContextSource {
'showIfEmpty' => true,
'msgKey' => array( '' ),
'wrap' => "$1",
- 'flags' => 0
+ 'flags' => 0,
+ 'useRequestParams' => false,
);
# The + operator appends elements of remaining keys from the right
# handed array to the left handed, whereas duplicated keys are NOT overwritten.
@@ -487,6 +490,7 @@ class LogEventsList extends ContextSource {
$msgKey = $param['msgKey'];
$wrap = $param['wrap'];
$flags = $param['flags'];
+ $useRequestParams = $param['useRequestParams'];
if ( !is_array( $msgKey ) ) {
$msgKey = array( $msgKey );
}
@@ -500,13 +504,22 @@ class LogEventsList extends ContextSource {
# Insert list of top 50 (or top $lim) items
$loglist = new LogEventsList( $context, null, $flags );
$pager = new LogPager( $loglist, $types, $user, $page, '', $conds );
+ if ( !$useRequestParams ) {
+ # Reset vars that may have been taken from the request
+ $pager->mLimit = 50;
+ $pager->mDefaultLimit = 50;
+ $pager->mOffset = "";
+ $pager->mIsBackwards = false;
+ }
if ( isset( $param['offset'] ) ) { # Tell pager to ignore WebRequest offset
$pager->setOffset( $param['offset'] );
}
- if( $lim > 0 ) $pager->mLimit = $lim;
+ if ( $lim > 0 ) {
+ $pager->mLimit = $lim;
+ }
$logBody = $pager->getBody();
$s = '';
- if( $logBody ) {
+ if ( $logBody ) {
if ( $msgKey[0] ) {
$s = '<div class="mw-warning-with-logexcerpt">';
@@ -521,26 +534,27 @@ class LogEventsList extends ContextSource {
$s .= $loglist->beginLogEventsList() .
$logBody .
$loglist->endLogEventsList();
- } else {
- if ( $showIfEmpty ) {
- $s = Html::rawElement( 'div', array( 'class' => 'mw-warning-logempty' ),
- $context->msg( 'logempty' )->parse() );
- }
+ } elseif ( $showIfEmpty ) {
+ $s = Html::rawElement( 'div', array( 'class' => 'mw-warning-logempty' ),
+ $context->msg( 'logempty' )->parse() );
}
- if( $pager->getNumRows() > $pager->mLimit ) { # Show "Full log" link
+ if ( $pager->getNumRows() > $pager->mLimit ) { # Show "Full log" link
$urlParam = array();
if ( $page instanceof Title ) {
$urlParam['page'] = $page->getPrefixedDBkey();
} elseif ( $page != '' ) {
$urlParam['page'] = $page;
}
- if ( $user != '' )
+ if ( $user != '' ) {
$urlParam['user'] = $user;
- if ( !is_array( $types ) ) # Make it an array, if it isn't
+ }
+ if ( !is_array( $types ) ) { # Make it an array, if it isn't
$types = array( $types );
+ }
# If there is exactly one log type, we can link to Special:Log?type=foo
- if ( count( $types ) == 1 )
+ if ( count( $types ) == 1 ) {
$urlParam['type'] = $types[0];
+ }
$s .= Linker::link(
SpecialPage::getTitleFor( 'Log' ),
$context->msg( 'log-fulllog' )->escaped(),
@@ -589,14 +603,14 @@ class LogEventsList extends ContextSource {
$hiddenLogs = array();
// Don't show private logs to unprivileged users
- foreach( $wgLogRestrictions as $logType => $right ) {
- if( $audience == 'public' || !$user->isAllowed( $right ) ) {
+ foreach ( $wgLogRestrictions as $logType => $right ) {
+ 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 ) {
+ } elseif ( $hiddenLogs ) {
return 'log_type NOT IN (' . $db->makeList( $hiddenLogs ) . ')';
}
return false;
diff --git a/includes/logging/LogFormatter.php b/includes/logging/LogFormatter.php
index ace26bbe..8f60aee1 100644
--- a/includes/logging/LogFormatter.php
+++ b/includes/logging/LogFormatter.php
@@ -194,9 +194,9 @@ class LogFormatter {
// Text of title the action is aimed at.
$target = $entry->getTarget()->getPrefixedText();
$text = null;
- switch( $entry->getType() ) {
+ switch ( $entry->getType() ) {
case 'move':
- switch( $entry->getSubtype() ) {
+ switch ( $entry->getSubtype() ) {
case 'move':
$movesource = $parameters['4::target'];
$text = wfMessage( '1movedto2' )
@@ -215,7 +215,7 @@ class LogFormatter {
break;
case 'delete':
- switch( $entry->getSubtype() ) {
+ switch ( $entry->getSubtype() ) {
case 'delete':
$text = wfMessage( 'deletedarticle' )
->rawParams( $target )->inContentLanguage()->escaped();
@@ -246,7 +246,7 @@ class LogFormatter {
break;
case 'protect':
- switch( $entry->getSubtype() ) {
+ switch ( $entry->getSubtype() ) {
case 'protect':
$text = wfMessage( 'protectedarticle' )
->rawParams( $target . ' ' . $parameters[0] )->inContentLanguage()->escaped();
@@ -263,7 +263,7 @@ class LogFormatter {
break;
case 'newusers':
- switch( $entry->getSubtype() ) {
+ switch ( $entry->getSubtype() ) {
case 'newusers':
case 'create':
$text = wfMessage( 'newuserlog-create-entry' )
@@ -282,7 +282,7 @@ class LogFormatter {
break;
case 'upload':
- switch( $entry->getSubtype() ) {
+ switch ( $entry->getSubtype() ) {
case 'upload':
$text = wfMessage( 'uploadedimage' )
->rawParams( $target )->inContentLanguage()->escaped();
@@ -305,7 +305,7 @@ class LogFormatter {
} else {
$newgroups = wfMessage( 'rightsnone' )->inContentLanguage()->escaped();
}
- switch( $entry->getSubtype() ) {
+ switch ( $entry->getSubtype() ) {
case 'rights':
$text = wfMessage( 'rightslogentry' )
->rawParams( $target, $oldgroups, $newgroups )->inContentLanguage()->escaped();
@@ -320,7 +320,7 @@ class LogFormatter {
// case 'suppress' --private log -- aaron (sign your messages so we know who to blame in a few years :-D)
// default:
}
- if( is_null( $text ) ) {
+ if ( is_null( $text ) ) {
$text = $this->getPlainActionText();
}
@@ -479,7 +479,7 @@ class LogFormatter {
protected function formatParameterValue( $type, $value ) {
$saveLinkFlood = $this->linkFlood;
- switch( strtolower( trim( $type ) ) ) {
+ switch ( strtolower( trim( $type ) ) ) {
case 'raw':
$value = Message::rawParam( $value );
break;
@@ -775,7 +775,7 @@ class LegacyLogFormatter extends LogFormatter {
return $this->msg( 'parentheses' )->rawParams(
$this->context->getLanguage()->pipeList( $links ) )->escaped();
// Show unmerge link
- } elseif( $type == 'merge' && $subtype == 'merge' ) {
+ } elseif ( $type == 'merge' && $subtype == 'merge' ) {
if ( !$this->context->getUser()->isAllowed( 'mergehistory' ) ) {
return '';
}
@@ -814,395 +814,3 @@ class LegacyLogFormatter extends LogFormatter {
}
}
-/**
- * This class formats move log entries.
- * @since 1.19
- */
-class MoveLogFormatter extends LogFormatter {
- public function getPreloadTitles() {
- $params = $this->extractParameters();
- return array( Title::newFromText( $params[3] ) );
- }
-
- protected function getMessageKey() {
- $key = parent::getMessageKey();
- $params = $this->getMessageParameters();
- if ( isset( $params[4] ) && $params[4] === '1' ) {
- $key .= '-noredirect';
- }
- return $key;
- }
-
- protected function getMessageParameters() {
- $params = parent::getMessageParameters();
- $oldname = $this->makePageLink( $this->entry->getTarget(), array( 'redirect' => 'no' ) );
- $newname = $this->makePageLink( Title::newFromText( $params[3] ) );
- $params[2] = Message::rawParam( $oldname );
- $params[3] = Message::rawParam( $newname );
- return $params;
- }
-
- public function getActionLinks() {
- if ( $this->entry->isDeleted( LogPage::DELETED_ACTION ) // Action is hidden
- || $this->entry->getSubtype() !== 'move'
- || !$this->context->getUser()->isAllowed( 'move' ) )
- {
- return '';
- }
-
- $params = $this->extractParameters();
- $destTitle = Title::newFromText( $params[3] );
- if ( !$destTitle ) {
- return '';
- }
-
- $revert = Linker::linkKnown(
- SpecialPage::getTitleFor( 'Movepage' ),
- $this->msg( 'revertmove' )->escaped(),
- array(),
- array(
- 'wpOldTitle' => $destTitle->getPrefixedDBkey(),
- 'wpNewTitle' => $this->entry->getTarget()->getPrefixedDBkey(),
- 'wpReason' => $this->msg( 'revertmove' )->inContentLanguage()->text(),
- 'wpMovetalk' => 0
- )
- );
- return $this->msg( 'parentheses' )->rawParams( $revert )->escaped();
- }
-}
-
-/**
- * This class formats delete log entries.
- * @since 1.19
- */
-class DeleteLogFormatter extends LogFormatter {
- protected function getMessageKey() {
- $key = parent::getMessageKey();
- if ( in_array( $this->entry->getSubtype(), array( 'event', 'revision' ) ) ) {
- if ( count( $this->getMessageParameters() ) < 5 ) {
- return "$key-legacy";
- }
- }
- return $key;
- }
-
- protected function getMessageParameters() {
- if ( isset( $this->parsedParametersDeleteLog ) ) {
- return $this->parsedParametersDeleteLog;
- }
-
- $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' || $params[3] === 'oldimage' ) )
- ) {
- $paramStart = $subtype === 'revision' ? 4 : 3;
-
- $old = $this->parseBitField( $params[$paramStart+1] );
- $new = $this->parseBitField( $params[$paramStart+2] );
- list( $hid, $unhid, $extra ) = RevisionDeleter::getChanges( $new, $old );
- $changes = array();
- foreach ( $hid as $v ) {
- $changes[] = $this->msg( "$v-hid" )->plain();
- }
- foreach ( $unhid as $v ) {
- $changes[] = $this->msg( "$v-unhid" )->plain();
- }
- foreach ( $extra as $v ) {
- $changes[] = $this->msg( $v )->plain();
- }
- $changeText = $this->context->getLanguage()->listToText( $changes );
-
- $newParams = array_slice( $params, 0, 3 );
- $newParams[3] = $changeText;
- $count = count( explode( ',', $params[$paramStart] ) );
- $newParams[4] = $this->context->getLanguage()->formatNum( $count );
- return $this->parsedParametersDeleteLog = $newParams;
- } else {
- return $this->parsedParametersDeleteLog = array_slice( $params, 0, 3 );
- }
- }
-
- return $this->parsedParametersDeleteLog = $params;
- }
-
- protected function parseBitField( $string ) {
- // Input is like ofield=2134 or just the number
- if ( strpos( $string, 'field=' ) === 1 ) {
- list( , $field ) = explode( '=', $string );
- return (int) $field;
- } else {
- return (int) $string;
- }
- }
-
- public function getActionLinks() {
- $user = $this->context->getUser();
- if ( !$user->isAllowed( 'deletedhistory' ) || $this->entry->isDeleted( LogPage::DELETED_ACTION ) ) {
- return '';
- }
-
- switch ( $this->entry->getSubtype() ) {
- case 'delete': // Show undelete link
- if( $user->isAllowed( 'undelete' ) ) {
- $message = 'undeletelink';
- } else {
- $message = 'undeleteviewlink';
- }
- $revert = Linker::linkKnown(
- SpecialPage::getTitleFor( 'Undelete' ),
- $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
- $params = $this->extractParameters();
- if ( !isset( $params[3] ) || !isset( $params[4] ) ) {
- return '';
- }
-
- // Different revision types use different URL params...
- $key = $params[3];
- // This is a CSV of the IDs
- $ids = explode( ',', $params[4] );
-
- $links = array();
-
- // If there's only one item, we can show a diff link
- if ( count( $ids ) == 1 ) {
- // Live revision diffs...
- if ( $key == 'oldid' || $key == 'revision' ) {
- $links[] = Linker::linkKnown(
- $this->entry->getTarget(),
- $this->msg( 'diff' )->escaped(),
- array(),
- array(
- 'diff' => intval( $ids[0] ),
- 'unhide' => 1
- )
- );
- // Deleted revision diffs...
- } elseif ( $key == 'artimestamp' || $key == 'archive' ) {
- $links[] = Linker::linkKnown(
- SpecialPage::getTitleFor( 'Undelete' ),
- $this->msg( 'diff' )->escaped(),
- array(),
- array(
- 'target' => $this->entry->getTarget()->getPrefixedDBkey(),
- 'diff' => 'prev',
- 'timestamp' => $ids[0]
- )
- );
- }
- }
-
- // View/modify link...
- $links[] = Linker::linkKnown(
- SpecialPage::getTitleFor( 'Revisiondelete' ),
- $this->msg( 'revdel-restore' )->escaped(),
- array(),
- array(
- 'target' => $this->entry->getTarget()->getPrefixedText(),
- 'type' => $key,
- 'ids' => implode( ',', $ids ),
- )
- );
-
- return $this->msg( 'parentheses' )->rawParams(
- $this->context->getLanguage()->pipeList( $links ) )->escaped();
-
- case 'event': // Hidden log items, give review link
- $params = $this->extractParameters();
- if ( !isset( $params[3] ) ) {
- return '';
- }
- // This is a CSV of the IDs
- $query = $params[3];
- // Link to each hidden object ID, $params[1] is the url param
- $revert = Linker::linkKnown(
- SpecialPage::getTitleFor( 'Revisiondelete' ),
- $this->msg( 'revdel-restore' )->escaped(),
- array(),
- array(
- 'target' => $this->entry->getTarget()->getPrefixedText(),
- 'type' => 'logging',
- 'ids' => $query
- )
- );
- return $this->msg( 'parentheses' )->rawParams( $revert )->escaped();
- default:
- return '';
- }
- }
-}
-
-/**
- * This class formats patrol log entries.
- * @since 1.19
- */
-class PatrolLogFormatter extends LogFormatter {
- protected function getMessageKey() {
- $key = parent::getMessageKey();
- $params = $this->getMessageParameters();
- if ( isset( $params[5] ) && $params[5] ) {
- $key .= '-auto';
- }
- return $key;
- }
-
- protected function getMessageParameters() {
- $params = parent::getMessageParameters();
-
- $target = $this->entry->getTarget();
- $oldid = $params[3];
- $revision = $this->context->getLanguage()->formatNum( $oldid, true );
-
- if ( $this->plaintext ) {
- $revlink = $revision;
- } elseif ( $target->exists() ) {
- $query = array(
- 'oldid' => $oldid,
- 'diff' => 'prev'
- );
- $revlink = Linker::link( $target, htmlspecialchars( $revision ), array(), $query );
- } else {
- $revlink = htmlspecialchars( $revision );
- }
-
- $params[3] = Message::rawParam( $revlink );
- return $params;
- }
-}
-
-/**
- * This class formats new user log entries.
- * @since 1.19
- */
-class NewUsersLogFormatter extends LogFormatter {
- protected function getMessageParameters() {
- $params = parent::getMessageParameters();
- $subtype = $this->entry->getSubtype();
- if ( $subtype === 'create2' || $subtype === 'byemail' ) {
- if ( isset( $params[3] ) ) {
- $target = User::newFromId( $params[3] );
- } else {
- $target = User::newFromName( $this->entry->getTarget()->getText(), false );
- }
- $params[2] = Message::rawParam( $this->makeUserLink( $target ) );
- $params[3] = $target->getName();
- }
- return $params;
- }
-
- public function getComment() {
- $timestamp = wfTimestamp( TS_MW, $this->entry->getTimestamp() );
- if ( $timestamp < '20080129000000' ) {
- # Suppress $comment from old entries (before 2008-01-29),
- # not needed and can contain incorrect links
- return '';
- }
- return parent::getComment();
- }
-
- public function getPreloadTitles() {
- $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 4191c577..cc473c18 100644
--- a/includes/logging/LogPage.php
+++ b/includes/logging/LogPage.php
@@ -94,7 +94,7 @@ class LogPage {
$newId = !is_null( $log_id ) ? $log_id : $dbw->insertId();
# And update recentchanges
- if( $this->updateRecentChanges ) {
+ if ( $this->updateRecentChanges ) {
$titleObj = SpecialPage::getTitleFor( 'Log', $this->type );
RecentChange::notifyLog(
@@ -102,9 +102,9 @@ class LogPage {
$this->type, $this->action, $this->target, $this->comment,
$this->params, $newId, $this->getRcCommentIRC()
);
- } elseif( $this->sendToUDP ) {
+ } elseif ( $this->sendToUDP ) {
# Don't send private logs to UDP
- if( isset( $wgLogRestrictions[$this->type] ) && $wgLogRestrictions[$this->type] != '*' ) {
+ if ( isset( $wgLogRestrictions[$this->type] ) && $wgLogRestrictions[$this->type] != '*' ) {
return $newId;
}
@@ -129,7 +129,7 @@ class LogPage {
public function getRcComment() {
$rcComment = $this->actionText;
- if( $this->comment != '' ) {
+ if ( $this->comment != '' ) {
if ( $rcComment == '' ) {
$rcComment = $this->comment;
} else {
@@ -149,7 +149,7 @@ class LogPage {
public function getRcCommentIRC() {
$rcComment = $this->ircActionText;
- if( $this->comment != '' ) {
+ if ( $this->comment != '' ) {
if ( $rcComment == '' ) {
$rcComment = $this->comment;
} else {
@@ -198,7 +198,7 @@ class LogPage {
public static function logName( $type ) {
global $wgLogNames;
- if( isset( $wgLogNames[$type] ) ) {
+ if ( isset( $wgLogNames[$type] ) ) {
return str_replace( '_', ' ', wfMessage( $wgLogNames[$type] )->text() );
} else {
// Bogus log types? Perhaps an extension was removed.
@@ -247,13 +247,13 @@ class LogPage {
$key = "$type/$action";
- if( isset( $wgLogActions[$key] ) ) {
- if( is_null( $title ) ) {
+ if ( isset( $wgLogActions[$key] ) ) {
+ if ( is_null( $title ) ) {
$rv = wfMessage( $wgLogActions[$key] )->inLanguage( $langObj )->escaped();
} else {
$titleLink = self::getTitleLink( $type, $langObjOrNull, $title, $params );
- if( count( $params ) == 0 ) {
+ if ( count( $params ) == 0 ) {
$rv = wfMessage( $wgLogActions[$key] )->rawParams( $titleLink )->inLanguage( $langObj )->escaped();
} else {
$details = '';
@@ -262,7 +262,7 @@ class LogPage {
// User suppression
if ( preg_match( '/^(block|suppress)\/(block|reblock)$/', $key ) ) {
if ( $skin ) {
- $params[1] = '<span class="blockExpiry" dir="ltr" title="' . htmlspecialchars( $params[1] ). '">' .
+ $params[1] = '<span class="blockExpiry" title="&lrm;' . htmlspecialchars( $params[1] ) . '">' .
$wgLang->translateBlockExpiry( $params[1] ) . '</span>';
} else {
$params[1] = $wgContLang->translateBlockExpiry( $params[1] );
@@ -273,14 +273,14 @@ class LogPage {
// Page protections
} elseif ( $type == 'protect' && count( $params ) == 3 ) {
// Restrictions and expiries
- if( $skin ) {
+ if ( $skin ) {
$details .= $wgLang->getDirMark() . htmlspecialchars( " {$params[1]}" );
} else {
$details .= " {$params[1]}";
}
// Cascading flag...
- if( $params[2] ) {
+ if ( $params[2] ) {
$details .= ' [' . wfMessage( 'protect-summary-cascade' )->inLanguage( $langObj )->text() . ']';
}
}
@@ -291,7 +291,7 @@ class LogPage {
} else {
global $wgLogActionsHandlers;
- if( isset( $wgLogActionsHandlers[$key] ) ) {
+ if ( isset( $wgLogActionsHandlers[$key] ) ) {
$args = func_get_args();
$rv = call_user_func_array( $wgLogActionsHandlers[$key], $args );
} else {
@@ -310,7 +310,7 @@ class LogPage {
// you want to link to something OTHER than the title of the log entry.
// The real problem, which Erik was trying to fix (and it sort-of works now) is
// that the same messages are being treated as both wikitext *and* HTML.
- if( $filterWikilinks ) {
+ if ( $filterWikilinks ) {
$rv = str_replace( '[[', '', $rv );
$rv = str_replace( ']]', '', $rv );
}
@@ -327,11 +327,11 @@ class LogPage {
* @return String
*/
protected static function getTitleLink( $type, $lang, $title, &$params ) {
- if( !$lang ) {
+ if ( !$lang ) {
return $title->getPrefixedText();
}
- switch( $type ) {
+ switch ( $type ) {
case 'move':
$titleLink = Linker::link(
$title,
@@ -353,7 +353,7 @@ class LogPage {
}
break;
case 'block':
- if( substr( $title->getText(), 0, 1 ) == '#' ) {
+ if ( substr( $title->getText(), 0, 1 ) == '#' ) {
$titleLink = $title->getText();
} else {
// @todo Store the user identifier in the parameters
@@ -377,11 +377,11 @@ class LogPage {
$params[1] = $lang->timeanddate( $params[1] );
break;
default:
- if( $title->isSpecialPage() ) {
+ if ( $title->isSpecialPage() ) {
list( $name, $par ) = SpecialPageFactory::resolveAlias( $title->getDBkey() );
# Use the language name for log titles, rather than Log/X
- if( $name == 'Log' ) {
+ if ( $name == 'Log' ) {
$logPage = new LogPage( $par );
$titleLink = Linker::link( $title, $logPage->getName()->escaped() );
$titleLink = wfMessage( 'parentheses' )
@@ -465,13 +465,13 @@ class LogPage {
* @return Boolean
*/
public function addRelations( $field, $values, $logid ) {
- if( !strlen( $field ) || empty( $values ) ) {
+ if ( !strlen( $field ) || empty( $values ) ) {
return false; // nothing
}
$data = array();
- foreach( $values as $value ) {
+ foreach ( $values as $value ) {
$data[] = array(
'ls_field' => $field,
'ls_value' => $value,
@@ -518,17 +518,17 @@ class LogPage {
* @return String
*/
public static function formatBlockFlags( $flags, $lang ) {
- $flags = explode( ',', trim( $flags ) );
+ $flags = trim( $flags );
+ if ( $flags === '' ) {
+ return ''; //nothing to do
+ }
+ $flags = explode( ',', $flags );
- if( count( $flags ) > 0 ) {
- for( $i = 0; $i < count( $flags ); $i++ ) {
- $flags[$i] = self::formatBlockFlag( $flags[$i], $lang );
- }
- return wfMessage( 'parentheses' )->inLanguage( $lang )
- ->rawParams( $lang->commaList( $flags ) )->escaped();
- } else {
- return '';
+ for ( $i = 0; $i < count( $flags ); $i++ ) {
+ $flags[$i] = self::formatBlockFlag( $flags[$i], $lang );
}
+ return wfMessage( 'parentheses' )->inLanguage( $lang )
+ ->rawParams( $lang->commaList( $flags ) )->escaped();
}
/**
@@ -541,7 +541,7 @@ class LogPage {
public static function formatBlockFlag( $flag, $lang ) {
static $messages = array();
- if( !isset( $messages[$flag] ) ) {
+ if ( !isset( $messages[$flag] ) ) {
$messages[$flag] = htmlspecialchars( $flag ); // Fallback
// For grepping. The following core messages can be used here:
diff --git a/includes/logging/LogPager.php b/includes/logging/LogPager.php
index 908755ed..09ae3b8c 100644
--- a/includes/logging/LogPager.php
+++ b/includes/logging/LogPager.php
@@ -34,14 +34,14 @@ class LogPager extends ReverseChronologicalPager {
/**
* Constructor
*
- * @param $list LogEventsList
+ * @param LogEventsList $list
* @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 int $year The year to start from
+ * @param int $month The month to start from
* @param string $tagFilter tag
*/
public function __construct( $list, $types = array(), $performer = '', $title = '', $pattern = '',
@@ -71,16 +71,17 @@ 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 ) {
+ foreach ( $wgFilterLogTypes as $type => $default ) {
// Avoid silly filtering
- if( $type !== 'patrol' || $this->getUser()->useNPPatrol() ) {
+ if ( $type !== 'patrol' || $this->getUser()->useNPPatrol() ) {
$hide = $this->getRequest()->getInt( "hide_{$type}_log", $default );
$filters[$type] = $hide;
- if( $hide )
+ if ( $hide ) {
$this->mConds[] = 'log_type != ' . $this->mDb->addQuotes( $type );
+ }
}
}
return $filters;
@@ -98,11 +99,11 @@ class LogPager extends ReverseChronologicalPager {
$user = $this->getUser();
// If $types is not an array, make it an array
- $types = ($types === '') ? array() : (array)$types;
+ $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] )
+ if ( isset( $wgLogRestrictions[$type] )
&& !$user->isAllowed( $wgLogRestrictions[$type] )
) {
$needReindex = true;
@@ -119,13 +120,15 @@ class LogPager extends ReverseChronologicalPager {
// Also, only show them upon specific request to avoid suprises.
$audience = $types ? 'user' : 'public';
$hideLogs = LogEventsList::getExcludeClause( $this->mDb, $audience, $user );
- if( $hideLogs !== false ) {
+ 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];
+ }
}
}
@@ -136,16 +139,16 @@ class LogPager extends ReverseChronologicalPager {
* @return bool
*/
private function limitPerformer( $name ) {
- if( $name == '' ) {
+ if ( $name == '' ) {
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 */
$userid = User::idFromName( $name );
- if( !$userid ) {
+ if ( !$userid ) {
/* It should be nicer to abort query at all,
but for now it won't pass anywhere behind the optimizer */
$this->mConds[] = "NULL";
@@ -153,9 +156,9 @@ class LogPager extends ReverseChronologicalPager {
$this->mConds['log_user'] = $userid;
// Paranoia: avoid brute force searches (bug 17342)
$user = $this->getUser();
- if( !$user->isAllowed( 'deletedhistory' ) ) {
+ if ( !$user->isAllowed( 'deletedhistory' ) ) {
$this->mConds[] = $this->mDb->bitAnd( 'log_deleted', LogPage::DELETED_USER ) . ' = 0';
- } elseif( !$user->isAllowed( 'suppressrevision' ) ) {
+ } elseif ( !$user->isAllowed( 'suppressrevision' ) ) {
$this->mConds[] = $this->mDb->bitAnd( 'log_deleted', LogPage::SUPPRESSED_USER ) .
' != ' . LogPage::SUPPRESSED_USER;
}
@@ -178,7 +181,7 @@ class LogPager extends ReverseChronologicalPager {
$title = $page;
} else {
$title = Title::newFromText( $page );
- if( strlen( $page ) == 0 || !$title instanceof Title ) {
+ if ( strlen( $page ) == 0 || !$title instanceof Title ) {
return false;
}
}
@@ -198,7 +201,7 @@ class LogPager extends ReverseChronologicalPager {
# use the page_time index. That should have no more than a few hundred
# log entries for even the busiest pages, so it can be safely scanned
# in full to satisfy an impossible condition on user or similar.
- if( $pattern && !$wgMiserMode ) {
+ if ( $pattern && !$wgMiserMode ) {
$this->mConds['log_namespace'] = $ns;
$this->mConds[] = 'log_title ' . $db->buildLike( $title->getDBkey(), $db->anyString() );
$this->pattern = $pattern;
@@ -208,10 +211,10 @@ 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';
- } elseif( !$user->isAllowed( 'suppressrevision' ) ) {
- $this->mConds[] = $db->bitAnd( 'log_deleted', LogPage::SUPPRESSED_ACTION) .
+ if ( !$user->isAllowed( 'deletedhistory' ) ) {
+ $this->mConds[] = $db->bitAnd( 'log_deleted', LogPage::DELETED_ACTION ) . ' = 0';
+ } elseif ( !$user->isAllowed( 'suppressrevision' ) ) {
+ $this->mConds[] = $db->bitAnd( 'log_deleted', LogPage::SUPPRESSED_ACTION ) .
' != ' . LogPage::SUPPRESSED_ACTION;
}
}
@@ -234,7 +237,7 @@ class LogPager extends ReverseChronologicalPager {
# Add log_search table if there are conditions on it.
# This filters the results to only include log rows that have
# log_search records with the specified ls_field and ls_value values.
- if( array_key_exists( 'ls_field', $this->mConds ) ) {
+ if ( array_key_exists( 'ls_field', $this->mConds ) ) {
$tables[] = 'log_search';
$index['log_search'] = 'ls_field_val';
$index['logging'] = 'PRIMARY';
@@ -246,28 +249,18 @@ class LogPager extends ReverseChronologicalPager {
# no duplicate log rows. Otherwise, we need to remove the duplicates.
$options[] = 'DISTINCT';
}
- # Avoid usage of the wrong index by limiting
- # the choices of available indexes. This mainly
- # avoids site-breaking filesorts.
- } elseif( $this->title || $this->pattern || $this->performer ) {
- $index['logging'] = array( 'page_time', 'user_time' );
- if( count( $this->types ) == 1 ) {
- $index['logging'][] = 'log_user_type_time';
- }
- } elseif( count( $this->types ) == 1 ) {
- $index['logging'] = 'type_time';
- } else {
- $index['logging'] = 'times';
}
- $options['USE INDEX'] = $index;
+ if ( count( $index ) ) {
+ $options['USE INDEX'] = $index;
+ }
# Don't show duplicate rows when using log_search
$joins['log_search'] = array( 'INNER JOIN', 'ls_log_id=log_id' );
$info = array(
- 'tables' => $tables,
- 'fields' => $fields,
- 'conds' => array_merge( $conds, $this->mConds ),
- 'options' => $options,
+ 'tables' => $tables,
+ 'fields' => $fields,
+ 'conds' => array_merge( $conds, $this->mConds ),
+ 'options' => $options,
'join_conds' => $joins,
);
# Add ChangeTags filter query
@@ -295,7 +288,7 @@ class LogPager extends ReverseChronologicalPager {
public function getStartBody() {
wfProfileIn( __METHOD__ );
# Do a link batch query
- if( $this->getNumRows() > 0 ) {
+ if ( $this->getNumRows() > 0 ) {
$lb = new LinkBatch;
foreach ( $this->mResult as $row ) {
$lb->add( $row->log_namespace, $row->log_title );
diff --git a/includes/logging/MoveLogFormatter.php b/includes/logging/MoveLogFormatter.php
new file mode 100644
index 00000000..0978f976
--- /dev/null
+++ b/includes/logging/MoveLogFormatter.php
@@ -0,0 +1,82 @@
+<?php
+/**
+ * Formatter for move log entries.
+ *
+ * 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 Niklas Laxström
+ * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later
+ * @since 1.22
+ */
+
+/**
+ * This class formats move log entries.
+ *
+ * @since 1.19
+ */
+class MoveLogFormatter extends LogFormatter {
+ public function getPreloadTitles() {
+ $params = $this->extractParameters();
+ return array( Title::newFromText( $params[3] ) );
+ }
+
+ protected function getMessageKey() {
+ $key = parent::getMessageKey();
+ $params = $this->getMessageParameters();
+ if ( isset( $params[4] ) && $params[4] === '1' ) {
+ $key .= '-noredirect';
+ }
+ return $key;
+ }
+
+ protected function getMessageParameters() {
+ $params = parent::getMessageParameters();
+ $oldname = $this->makePageLink( $this->entry->getTarget(), array( 'redirect' => 'no' ) );
+ $newname = $this->makePageLink( Title::newFromText( $params[3] ) );
+ $params[2] = Message::rawParam( $oldname );
+ $params[3] = Message::rawParam( $newname );
+ return $params;
+ }
+
+ public function getActionLinks() {
+ if ( $this->entry->isDeleted( LogPage::DELETED_ACTION ) // Action is hidden
+ || $this->entry->getSubtype() !== 'move'
+ || !$this->context->getUser()->isAllowed( 'move' ) )
+ {
+ return '';
+ }
+
+ $params = $this->extractParameters();
+ $destTitle = Title::newFromText( $params[3] );
+ if ( !$destTitle ) {
+ return '';
+ }
+
+ $revert = Linker::linkKnown(
+ SpecialPage::getTitleFor( 'Movepage' ),
+ $this->msg( 'revertmove' )->escaped(),
+ array(),
+ array(
+ 'wpOldTitle' => $destTitle->getPrefixedDBkey(),
+ 'wpNewTitle' => $this->entry->getTarget()->getPrefixedDBkey(),
+ 'wpReason' => $this->msg( 'revertmove' )->inContentLanguage()->text(),
+ 'wpMovetalk' => 0
+ )
+ );
+ return $this->msg( 'parentheses' )->rawParams( $revert )->escaped();
+ }
+}
diff --git a/includes/logging/NewUsersLogFormatter.php b/includes/logging/NewUsersLogFormatter.php
new file mode 100644
index 00000000..602728b4
--- /dev/null
+++ b/includes/logging/NewUsersLogFormatter.php
@@ -0,0 +1,65 @@
+<?php
+/**
+ * Formatter for new user log entries.
+ *
+ * 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 Niklas Laxström
+ * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later
+ * @since 1.22
+ */
+
+/**
+ * This class formats new user log entries.
+ *
+ * @since 1.19
+ */
+class NewUsersLogFormatter extends LogFormatter {
+ protected function getMessageParameters() {
+ $params = parent::getMessageParameters();
+ $subtype = $this->entry->getSubtype();
+ if ( $subtype === 'create2' || $subtype === 'byemail' ) {
+ if ( isset( $params[3] ) ) {
+ $target = User::newFromId( $params[3] );
+ } else {
+ $target = User::newFromName( $this->entry->getTarget()->getText(), false );
+ }
+ $params[2] = Message::rawParam( $this->makeUserLink( $target ) );
+ $params[3] = $target->getName();
+ }
+ return $params;
+ }
+
+ public function getComment() {
+ $timestamp = wfTimestamp( TS_MW, $this->entry->getTimestamp() );
+ if ( $timestamp < '20080129000000' ) {
+ # Suppress $comment from old entries (before 2008-01-29),
+ # not needed and can contain incorrect links
+ return '';
+ }
+ return parent::getComment();
+ }
+
+ public function getPreloadTitles() {
+ $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();
+ }
+}
diff --git a/includes/logging/PatrolLog.php b/includes/logging/PatrolLog.php
index 911fffc0..bb76d5a9 100644
--- a/includes/logging/PatrolLog.php
+++ b/includes/logging/PatrolLog.php
@@ -38,6 +38,13 @@ class PatrolLog {
* @return bool
*/
public static function record( $rc, $auto = false, User $user = null ) {
+ global $wgLogAutopatrol;
+
+ // do not log autopatrolled edits if setting disables it
+ if ( $auto && !$wgLogAutopatrol ) {
+ return false;
+ }
+
if ( !$rc instanceof RecentChange ) {
$rc = RecentChange::newFromId( $rc );
if ( !is_object( $rc ) ) {
diff --git a/includes/logging/PatrolLogFormatter.php b/includes/logging/PatrolLogFormatter.php
new file mode 100644
index 00000000..507039ba
--- /dev/null
+++ b/includes/logging/PatrolLogFormatter.php
@@ -0,0 +1,63 @@
+<?php
+/**
+ * Formatter for new user log entries.
+ *
+ * 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 Niklas Laxström
+ * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later
+ * @since 1.22
+ */
+
+/**
+ * This class formats patrol log entries.
+ *
+ * @since 1.19
+ */
+class PatrolLogFormatter extends LogFormatter {
+ protected function getMessageKey() {
+ $key = parent::getMessageKey();
+ $params = $this->getMessageParameters();
+ if ( isset( $params[5] ) && $params[5] ) {
+ $key .= '-auto';
+ }
+ return $key;
+ }
+
+ protected function getMessageParameters() {
+ $params = parent::getMessageParameters();
+
+ $target = $this->entry->getTarget();
+ $oldid = $params[3];
+ $revision = $this->context->getLanguage()->formatNum( $oldid, true );
+
+ if ( $this->plaintext ) {
+ $revlink = $revision;
+ } elseif ( $target->exists() ) {
+ $query = array(
+ 'oldid' => $oldid,
+ 'diff' => 'prev'
+ );
+ $revlink = Linker::link( $target, htmlspecialchars( $revision ), array(), $query );
+ } else {
+ $revlink = htmlspecialchars( $revision );
+ }
+
+ $params[3] = Message::rawParam( $revlink );
+ return $params;
+ }
+}
diff --git a/includes/logging/RightsLogFormatter.php b/includes/logging/RightsLogFormatter.php
new file mode 100644
index 00000000..d3daf6ee
--- /dev/null
+++ b/includes/logging/RightsLogFormatter.php
@@ -0,0 +1,112 @@
+<?php
+/**
+ * Formatter for user rights log entries.
+ *
+ * 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 Alexandre Emsenhuber
+ * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later
+ * @since 1.22
+ */
+
+/**
+ * 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/media/BMP.php b/includes/media/BMP.php
index 46d1b95b..99b7741a 100644
--- a/includes/media/BMP.php
+++ b/includes/media/BMP.php
@@ -58,15 +58,15 @@ class BmpHandler extends BitmapHandler {
*/
function getImageSize( $image, $filename ) {
$f = fopen( $filename, 'rb' );
- if( !$f ) {
+ if ( !$f ) {
return false;
}
$header = fread( $f, 54 );
fclose( $f );
// Extract binary form of width and height from the header
- $w = substr( $header, 18, 4);
- $h = substr( $header, 22, 4);
+ $w = substr( $header, 18, 4 );
+ $h = substr( $header, 22, 4 );
// Convert the unsigned long 32 bits (little endian):
try {
diff --git a/includes/media/Bitmap.php b/includes/media/Bitmap.php
index e2dc68b2..e2444a11 100644
--- a/includes/media/Bitmap.php
+++ b/includes/media/Bitmap.php
@@ -100,17 +100,6 @@ class BitmapHandler extends ImageHandler {
}
/**
- * Function that returns the number of pixels to be thumbnailed.
- * Intended for animated GIFs to multiply by the number of frames.
- *
- * @param File $image
- * @return int
- */
- function getImageArea( $image ) {
- return $image->getWidth() * $image->getHeight();
- }
-
- /**
* @param $image File
* @param $dstPath
* @param $dstUrl
@@ -131,7 +120,7 @@ class BitmapHandler extends ImageHandler {
# The size of the image on the page
'clientWidth' => $params['width'],
'clientHeight' => $params['height'],
- # Comment as will be added to the EXIF of the thumbnail
+ # Comment as will be added to the Exif of the thumbnail
'comment' => isset( $params['descriptionUrl'] ) ?
"File source: {$params['descriptionUrl']}" : '',
# Properties of the original image
@@ -357,12 +346,12 @@ class BitmapHandler extends ImageHandler {
" -depth 8 $sharpen " .
" -rotate -$rotation " .
" {$animation_post} " .
- wfEscapeShellArg( $this->escapeMagickOutput( $params['dstPath'] ) ) . " 2>&1";
+ wfEscapeShellArg( $this->escapeMagickOutput( $params['dstPath'] ) );
wfDebug( __METHOD__ . ": running ImageMagick: $cmd\n" );
wfProfileIn( 'convert' );
$retval = 0;
- $err = wfShellExec( $cmd, $retval, $env );
+ $err = wfShellExecWithStderr( $cmd, $retval, $env );
wfProfileOut( 'convert' );
if ( $retval !== 0 ) {
@@ -398,7 +387,7 @@ class BitmapHandler extends ImageHandler {
$im->sharpenImage( $radius, $sigma );
}
$im->setCompressionQuality( 80 );
- } elseif( $params['mimeType'] == 'image/png' ) {
+ } elseif ( $params['mimeType'] == 'image/png' ) {
$im->setCompressionQuality( 95 );
} elseif ( $params['mimeType'] == 'image/gif' ) {
if ( $this->getImageArea( $image ) > $wgMaxAnimatedGifArea ) {
@@ -472,7 +461,7 @@ class BitmapHandler extends ImageHandler {
wfDebug( __METHOD__ . ": Running custom convert command $cmd\n" );
wfProfileIn( 'convert' );
$retval = 0;
- $err = wfShellExec( $cmd, $retval );
+ $err = wfShellExecWithStderr( $cmd, $retval );
wfProfileOut( 'convert' );
if ( $retval !== 0 ) {
@@ -712,24 +701,6 @@ class BitmapHandler extends ImageHandler {
imagejpeg( $dst_image, $thumbPath, 95 );
}
- /**
- * On supporting image formats, try to read out the low-level orientation
- * of the file and return the angle that the file needs to be rotated to
- * be viewed.
- *
- * This information is only useful when manipulating the original file;
- * the width and height we normally work with is logical, and will match
- * any produced output views.
- *
- * The base BitmapHandler doesn't understand any metadata formats, so this
- * is left up to child classes to implement.
- *
- * @param $file File
- * @return int 0, 90, 180 or 270
- */
- public function getRotation( $file ) {
- return 0;
- }
/**
* Returns whether the current scaler supports rotation (im and gd do)
@@ -765,20 +736,20 @@ class BitmapHandler extends ImageHandler {
public function rotate( $file, $params ) {
global $wgImageMagickConvertCommand;
- $rotation = ( $params[ 'rotation' ] + $this->getRotation( $file ) ) % 360;
+ $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 ) ) .
+ wfEscapeShellArg( $this->escapeMagickInput( $params['srcPath'], $scene ) ) .
" -rotate -$rotation " .
- wfEscapeShellArg( $this->escapeMagickOutput( $params[ 'dstPath' ] ) ) . " 2>&1";
+ wfEscapeShellArg( $this->escapeMagickOutput( $params['dstPath'] ) );
wfDebug( __METHOD__ . ": running ImageMagick: $cmd\n" );
wfProfileIn( 'convert' );
$retval = 0;
- $err = wfShellExec( $cmd, $retval, $env );
+ $err = wfShellExecWithStderr( $cmd, $retval, $env );
wfProfileOut( 'convert' );
if ( $retval !== 0 ) {
$this->logErrorForExternalProcess( $retval, $err, $cmd );
diff --git a/includes/media/BitmapMetadataHandler.php b/includes/media/BitmapMetadataHandler.php
index 345e7869..7c39c814 100644
--- a/includes/media/BitmapMetadataHandler.php
+++ b/includes/media/BitmapMetadataHandler.php
@@ -54,7 +54,7 @@ class BitmapMetadataHandler {
*
* @param string $app13 String containing app13 block from jpeg file
*/
- private function doApp13 ( $app13 ) {
+ private function doApp13( $app13 ) {
try {
$this->iptcType = JpegMetadataExtractor::doPSIR( $app13 );
} catch ( MWException $e ) {
@@ -79,7 +79,7 @@ class BitmapMetadataHandler {
* @param $filename string
* @param $byteOrder string
*/
- function getExif ( $filename, $byteOrder ) {
+ function getExif( $filename, $byteOrder ) {
global $wgShowEXIF;
if ( file_exists( $filename ) && $wgShowEXIF ) {
$exif = new Exif( $filename, $byteOrder );
@@ -95,7 +95,7 @@ class BitmapMetadataHandler {
* @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' ) {
+ function addMetadata( $metaArray, $type = 'other' ) {
if ( isset( $this->metadata[$type] ) ) {
/* merge with old data */
$metaArray = $metaArray + $this->metadata[$type];
@@ -113,7 +113,7 @@ class BitmapMetadataHandler {
*
* @return Array metadata array
*/
- function getMetadataArray () {
+ function getMetadataArray() {
// this seems a bit ugly... This is all so its merged in right order
// based on the MWG recomendation.
$temp = Array();
@@ -147,7 +147,7 @@ class BitmapMetadataHandler {
* @return array metadata result array.
* @throws MWException on invalid file.
*/
- static function Jpeg ( $filename ) {
+ static function Jpeg( $filename ) {
$showXMP = function_exists( 'xml_parser_create_ns' );
$meta = new self();
@@ -156,7 +156,7 @@ class BitmapMetadataHandler {
$meta->addMetadata( Array( 'JPEGFileComment' => $seg['COM'] ), 'native' );
}
if ( isset( $seg['PSIR'] ) && count( $seg['PSIR'] ) > 0 ) {
- foreach( $seg['PSIR'] as $curPSIRValue ) {
+ foreach ( $seg['PSIR'] as $curPSIRValue ) {
$meta->doApp13( $curPSIRValue );
}
}
@@ -189,7 +189,7 @@ class BitmapMetadataHandler {
* @param string $filename full path to file
* @return Array Array for storage in img_metadata.
*/
- public static function PNG ( $filename ) {
+ public static function PNG( $filename ) {
$showXMP = function_exists( 'xml_parser_create_ns' );
$meta = new self();
@@ -218,7 +218,7 @@ class BitmapMetadataHandler {
* @param string $filename full path to file
* @return Array metadata array
*/
- public static function GIF ( $filename ) {
+ public static function GIF( $filename ) {
$meta = new self();
$baseArray = GIFMetadataExtractor::getMetadata( $filename );
@@ -259,7 +259,7 @@ class BitmapMetadataHandler {
* @throws MWException
* @return Array The metadata.
*/
- public static function Tiff ( $filename ) {
+ public static function Tiff( $filename ) {
if ( file_exists( $filename ) ) {
$byteOrder = self::getTiffByteOrder( $filename );
if ( !$byteOrder ) {
@@ -286,11 +286,13 @@ class BitmapMetadataHandler {
*/
static function getTiffByteOrder( $filename ) {
$fh = fopen( $filename, 'rb' );
- if ( !$fh ) return false;
+ if ( !$fh ) {
+ return false;
+ }
$head = fread( $fh, 2 );
fclose( $fh );
- switch( $head ) {
+ switch ( $head ) {
case 'II':
return 'LE'; // II for intel.
case 'MM':
diff --git a/includes/media/DjVu.php b/includes/media/DjVu.php
index 0a39a2cf..b9e89d9d 100644
--- a/includes/media/DjVu.php
+++ b/includes/media/DjVu.php
@@ -185,7 +185,7 @@ class DjVuHandler extends ImageHandler {
}
$cmd .= ' > ' . wfEscapeShellArg( $dstPath ) . ') 2>&1';
wfProfileIn( 'ddjvu' );
- wfDebug( __METHOD__.": $cmd\n" );
+ wfDebug( __METHOD__ . ": $cmd\n" );
$retval = '';
$err = wfShellExec( $cmd, $retval );
wfProfileOut( 'ddjvu' );
@@ -246,24 +246,23 @@ class DjVuHandler extends ImageHandler {
$image->dejaMetaTree = false;
$image->djvuTextTree = false;
$tree = new SimpleXMLElement( $metadata );
- if( $tree->getName() == 'mw-djvu' ) {
- foreach( $tree->children() as $b ) {
- if( $b->getName() == 'DjVuTxt' ) {
+ if ( $tree->getName() == 'mw-djvu' ) {
+ foreach ( $tree->children() as $b ) {
+ if ( $b->getName() == 'DjVuTxt' ) {
$image->djvuTextTree = $b;
- }
- elseif ( $b->getName() == 'DjVuXML' ) {
+ } elseif ( $b->getName() == 'DjVuXML' ) {
$image->dejaMetaTree = $b;
}
}
} else {
$image->dejaMetaTree = $tree;
}
- } catch( Exception $e ) {
+ } catch ( Exception $e ) {
wfDebug( "Bogus multipage XML metadata on '{$image->getName()}'\n" );
}
wfRestoreWarnings();
wfProfileOut( __METHOD__ );
- if( $gettext ) {
+ if ( $gettext ) {
return $image->djvuTextTree;
} else {
return $image->dejaMetaTree;
@@ -294,7 +293,7 @@ class DjVuHandler extends ImageHandler {
}
function isMetadataValid( $image, $metadata ) {
- return !empty( $metadata ) && $metadata != serialize(array());
+ return !empty( $metadata ) && $metadata != serialize( array() );
}
function pageCount( $image ) {
@@ -311,7 +310,7 @@ class DjVuHandler extends ImageHandler {
return false;
}
- $o = $tree->BODY[0]->OBJECT[$page-1];
+ $o = $tree->BODY[0]->OBJECT[$page - 1];
if ( $o ) {
return array(
'width' => intval( $o['width'] ),
@@ -328,7 +327,7 @@ class DjVuHandler extends ImageHandler {
return false;
}
- $o = $tree->BODY[0]->PAGE[$page-1];
+ $o = $tree->BODY[0]->PAGE[$page - 1];
if ( $o ) {
$txt = $o['value'];
return $txt;
diff --git a/includes/media/DjVuImage.php b/includes/media/DjVuImage.php
index 46989668..54efe7a8 100644
--- a/includes/media/DjVuImage.php
+++ b/includes/media/DjVuImage.php
@@ -64,7 +64,7 @@ class DjVuImage {
public function getImageSize() {
$data = $this->getInfo();
- if( $data !== false ) {
+ if ( $data !== false ) {
$width = $data['width'];
$height = $data['height'];
@@ -93,20 +93,20 @@ class DjVuImage {
$start = ftell( $file );
$secondary = fread( $file, 4 );
echo str_repeat( ' ', $indent * 4 ) . "($secondary)\n";
- while( ftell( $file ) - $start < $length ) {
+ while ( ftell( $file ) - $start < $length ) {
$chunkHeader = fread( $file, 8 );
- if( $chunkHeader == '' ) {
+ if ( $chunkHeader == '' ) {
break;
}
// @todo FIXME: Would be good to replace this extract() call with something that explicitly initializes local variables.
extract( unpack( 'a4chunk/NchunkLength', $chunkHeader ) );
echo str_repeat( ' ', $indent * 4 ) . "$chunk $chunkLength\n";
- if( $chunk == 'FORM' ) {
+ if ( $chunk == 'FORM' ) {
$this->dumpForm( $file, $chunkLength, $indent + 1 );
} else {
fseek( $file, $chunkLength, SEEK_CUR );
- if( $chunkLength & 1 == 1 ) {
+ if ( $chunkLength & 1 == 1 ) {
// Padding byte between chunks
fseek( $file, 1, SEEK_CUR );
}
@@ -118,7 +118,7 @@ class DjVuImage {
wfSuppressWarnings();
$file = fopen( $this->mFilename, 'rb' );
wfRestoreWarnings();
- if( $file === false ) {
+ if ( $file === false ) {
wfDebug( __METHOD__ . ": missing or failed file read\n" );
return false;
}
@@ -126,21 +126,21 @@ class DjVuImage {
$header = fread( $file, 16 );
$info = false;
- if( strlen( $header ) < 16 ) {
+ if ( strlen( $header ) < 16 ) {
wfDebug( __METHOD__ . ": too short file header\n" );
} else {
// @todo FIXME: Would be good to replace this extract() call with something that explicitly initializes local variables.
extract( unpack( 'a4magic/a4form/NformLength/a4subtype', $header ) );
- if( $magic != 'AT&T' ) {
+ if ( $magic != 'AT&T' ) {
wfDebug( __METHOD__ . ": not a DjVu file\n" );
- } elseif( $subtype == 'DJVU' ) {
+ } elseif ( $subtype == 'DJVU' ) {
// Single-page document
$info = $this->getPageInfo( $file, $formLength );
- } elseif( $subtype == 'DJVM' ) {
+ } elseif ( $subtype == 'DJVM' ) {
// Multi-page document
$info = $this->getMultiPageInfo( $file, $formLength );
- } else {
+ } else {
wfDebug( __METHOD__ . ": unrecognized DJVU file type '$formType'\n" );
}
}
@@ -150,7 +150,7 @@ class DjVuImage {
private function readChunk( $file ) {
$header = fread( $file, 8 );
- if( strlen( $header ) < 8 ) {
+ if ( strlen( $header ) < 8 ) {
return array( false, 0 );
} else {
// @todo FIXME: Would be good to replace this extract() call with something that explicitly initializes local variables.
@@ -162,7 +162,7 @@ class DjVuImage {
private function skipChunk( $file, $chunkLength ) {
fseek( $file, $chunkLength, SEEK_CUR );
- if( $chunkLength & 0x01 == 1 && !feof( $file ) ) {
+ if ( $chunkLength & 0x01 == 1 && !feof( $file ) ) {
// padding byte
fseek( $file, 1, SEEK_CUR );
}
@@ -174,13 +174,13 @@ class DjVuImage {
$start = ftell( $file );
do {
list( $chunk, $length ) = $this->readChunk( $file );
- if( !$chunk ) {
+ if ( !$chunk ) {
break;
}
- if( $chunk == 'FORM' ) {
+ if ( $chunk == 'FORM' ) {
$subtype = fread( $file, 4 );
- if( $subtype == 'DJVU' ) {
+ if ( $subtype == 'DJVU' ) {
wfDebug( __METHOD__ . ": found first subpage\n" );
return $this->getPageInfo( $file, $length );
}
@@ -189,7 +189,7 @@ class DjVuImage {
wfDebug( __METHOD__ . ": skipping '$chunk' chunk\n" );
$this->skipChunk( $file, $length );
}
- } while( $length != 0 && !feof( $file ) && ftell( $file ) - $start < $formLength );
+ } while ( $length != 0 && !feof( $file ) && ftell( $file ) - $start < $formLength );
wfDebug( __METHOD__ . ": multi-page DJVU file contained no pages\n" );
return false;
@@ -197,17 +197,17 @@ class DjVuImage {
private function getPageInfo( $file, $formLength ) {
list( $chunk, $length ) = $this->readChunk( $file );
- if( $chunk != 'INFO' ) {
+ if ( $chunk != 'INFO' ) {
wfDebug( __METHOD__ . ": expected INFO chunk, got '$chunk'\n" );
return false;
}
- if( $length < 9 ) {
+ if ( $length < 9 ) {
wfDebug( __METHOD__ . ": INFO should be 9 or 10 bytes, found $length\n" );
return false;
}
$data = fread( $file, $length );
- if( strlen( $data ) < $length ) {
+ if ( strlen( $data ) < $length ) {
wfDebug( __METHOD__ . ": INFO chunk cut off\n" );
return false;
}
@@ -263,7 +263,7 @@ class DjVuImage {
$retval = '';
$txt = wfShellExec( $cmd, $retval, array(), array( 'memory' => self::DJVUTXT_MEMORY_LIMIT ) );
wfProfileOut( 'djvutxt' );
- if( $retval == 0) {
+ if ( $retval == 0 ) {
# Strip some control characters
$txt = preg_replace( "/[\013\035\037]/", "", $txt );
$reg = <<<EOR
@@ -280,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 17671808..9a2794a5 100644
--- a/includes/media/Exif.php
+++ b/includes/media/Exif.php
@@ -36,6 +36,7 @@ class Exif {
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 SHORT_OR_LONG = 6; //!< A 16-bit (2-byte) or 32-bit (4-byte) unsigned integer.
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.
@@ -113,7 +114,7 @@ class Exif {
*/
function __construct( $file, $byteOrder = '' ) {
/**
- * Page numbers here refer to pages in the EXIF 2.2 standard
+ * Page numbers here refer to pages in the Exif 2.2 standard
*
* Note, Exif::UNDEFINED is treated as a string, not as an array of bytes
* so don't put a count parameter for any UNDEFINED values.
@@ -124,8 +125,8 @@ class Exif {
# TIFF Rev. 6.0 Attribute Information (p22)
'IFD0' => array(
# Tags relating to image structure
- 'ImageWidth' => Exif::SHORT.','.Exif::LONG, # Image width
- 'ImageLength' => Exif::SHORT.','.Exif::LONG, # Image height
+ 'ImageWidth' => Exif::SHORT_OR_LONG, # Image width
+ 'ImageLength' => Exif::SHORT_OR_LONG, # Image height
'BitsPerSample' => array( Exif::SHORT, 3 ), # Number of bits per component
# "When a primary image is JPEG compressed, this designation is not"
# "necessary and is omitted." (p23)
@@ -134,25 +135,25 @@ class Exif {
'Orientation' => Exif::SHORT, # Orientation of image #p24
'SamplesPerPixel' => Exif::SHORT, # Number of components
'PlanarConfiguration' => Exif::SHORT, # Image data arrangement #p24
- 'YCbCrSubSampling' => array( Exif::SHORT, 2), # Subsampling ratio of Y to C #p24
+ 'YCbCrSubSampling' => array( Exif::SHORT, 2 ), # Subsampling ratio of Y to C #p24
'YCbCrPositioning' => Exif::SHORT, # Y and C positioning #p24-25
'XResolution' => Exif::RATIONAL, # Image resolution in width direction
'YResolution' => Exif::RATIONAL, # Image resolution in height direction
'ResolutionUnit' => Exif::SHORT, # Unit of X and Y resolution #(p26)
# Tags relating to recording offset
- 'StripOffsets' => Exif::SHORT.','.Exif::LONG, # Image data location
- 'RowsPerStrip' => Exif::SHORT.','.Exif::LONG, # Number of rows per strip
- 'StripByteCounts' => Exif::SHORT.','.Exif::LONG, # Bytes per compressed strip
- 'JPEGInterchangeFormat' => Exif::SHORT.','.Exif::LONG, # Offset to JPEG SOI
- 'JPEGInterchangeFormatLength' => Exif::SHORT.','.Exif::LONG, # Bytes of JPEG data
+ 'StripOffsets' => Exif::SHORT_OR_LONG, # Image data location
+ 'RowsPerStrip' => Exif::SHORT_OR_LONG, # Number of rows per strip
+ 'StripByteCounts' => Exif::SHORT_OR_LONG, # Bytes per compressed strip
+ 'JPEGInterchangeFormat' => Exif::SHORT_OR_LONG, # Offset to JPEG SOI
+ 'JPEGInterchangeFormatLength' => Exif::SHORT_OR_LONG, # Bytes of JPEG data
# Tags relating to image data characteristics
'TransferFunction' => Exif::IGNORE, # Transfer function
- 'WhitePoint' => array( Exif::RATIONAL, 2), # White point chromaticity
- 'PrimaryChromaticities' => array( Exif::RATIONAL, 6), # Chromaticities of primarities
- 'YCbCrCoefficients' => array( Exif::RATIONAL, 3), # Color space transformation matrix coefficients #p27
- 'ReferenceBlackWhite' => array( Exif::RATIONAL, 6), # Pair of black and white reference values
+ 'WhitePoint' => array( Exif::RATIONAL, 2 ), # White point chromaticity
+ 'PrimaryChromaticities' => array( Exif::RATIONAL, 6 ), # Chromaticities of primarities
+ 'YCbCrCoefficients' => array( Exif::RATIONAL, 3 ), # Color space transformation matrix coefficients #p27
+ 'ReferenceBlackWhite' => array( Exif::RATIONAL, 6 ), # Pair of black and white reference values
# Other tags
'DateTime' => Exif::ASCII, # File change date and time
@@ -167,8 +168,8 @@ class Exif {
# Exif IFD Attribute Information (p30-31)
'EXIF' => array(
# TODO: NOTE: Nonexistence of this field is taken to mean nonconformance
- # to the EXIF 2.1 AND 2.2 standards
- 'ExifVersion' => Exif::UNDEFINED, # Exif version
+ # to the Exif 2.1 AND 2.2 standards
+ 'ExifVersion' => Exif::UNDEFINED, # Exif version
'FlashPixVersion' => Exif::UNDEFINED, # Supported Flashpix version #p32
# Tags relating to Image Data Characteristics
@@ -177,8 +178,8 @@ class Exif {
# Tags relating to image configuration
'ComponentsConfiguration' => Exif::UNDEFINED, # Meaning of each component #p33
'CompressedBitsPerPixel' => Exif::RATIONAL, # Image compression mode
- 'PixelYDimension' => Exif::SHORT.','.Exif::LONG, # Valid image width
- 'PixelXDimension' => Exif::SHORT.','.Exif::LONG, # Valid image height
+ 'PixelYDimension' => Exif::SHORT_OR_LONG, # Valid image width
+ 'PixelXDimension' => Exif::SHORT_OR_LONG, # Valid image height
# Tags relating to related user information
'MakerNote' => Exif::IGNORE, # Manufacturer notes
@@ -218,7 +219,7 @@ class Exif {
'FocalPlaneXResolution' => Exif::RATIONAL, # Focal plane X resolution
'FocalPlaneYResolution' => Exif::RATIONAL, # Focal plane Y resolution
'FocalPlaneResolutionUnit' => Exif::SHORT, # Focal plane resolution unit #p46
- 'SubjectLocation' => array( Exif::SHORT, 2), # Subject location
+ 'SubjectLocation' => array( Exif::SHORT, 2 ), # Subject location
'ExposureIndex' => Exif::RATIONAL, # Exposure index
'SensingMethod' => Exif::SHORT, # Sensing method #p46
'FileSource' => Exif::UNDEFINED, # File source #p47
@@ -250,12 +251,12 @@ class Exif {
'GPSLatitudeRef' => Exif::ASCII, # North or South Latitude #p52-53
'GPSLatitude' => array( Exif::RATIONAL, 3 ), # Latitude
'GPSLongitudeRef' => Exif::ASCII, # East or West Longitude #p53
- 'GPSLongitude' => array( Exif::RATIONAL, 3), # Longitude
+ 'GPSLongitude' => array( Exif::RATIONAL, 3 ), # Longitude
'GPSAltitudeRef' => Exif::UNDEFINED,
# Altitude reference. Note, the exif standard says this should be an EXIF::Byte,
# but php seems to disagree.
'GPSAltitude' => Exif::RATIONAL, # Altitude
- 'GPSTimeStamp' => array( Exif::RATIONAL, 3), # GPS time (atomic clock)
+ 'GPSTimeStamp' => array( Exif::RATIONAL, 3 ), # GPS time (atomic clock)
'GPSSatellites' => Exif::ASCII, # Satellites used for measurement
'GPSStatus' => Exif::ASCII, # Receiver status #p54
'GPSMeasureMode' => Exif::ASCII, # Measurement mode #p54-55
@@ -296,7 +297,7 @@ class Exif {
}
$this->debugFile( $this->basename, __FUNCTION__, true );
- if( function_exists( 'exif_read_data' ) ) {
+ if ( function_exists( 'exif_read_data' ) ) {
wfSuppressWarnings();
$data = exif_read_data( $this->file, 0, true );
wfRestoreWarnings();
@@ -393,7 +394,7 @@ class Exif {
//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'] ) ) {
+ if ( isset( $this->mFilteredExifData['ComponentsConfiguration'] ) ) {
$val = $this->mFilteredExifData['ComponentsConfiguration'];
$ccVals = array();
for ( $i = 0; $i < strlen( $val ); $i++ ) {
@@ -410,7 +411,7 @@ class Exif {
//Also change exif tag name from GPSVersion (what php exif thinks it is)
//to GPSVersionID (what the exif standard thinks it is).
- if ( isset ( $this->mFilteredExifData['GPSVersion'] ) ) {
+ if ( isset( $this->mFilteredExifData['GPSVersion'] ) ) {
$val = $this->mFilteredExifData['GPSVersion'];
$newVal = '';
for ( $i = 0; $i < strlen( $val ); $i++ ) {
@@ -439,7 +440,7 @@ class Exif {
* This has not been tested on any shift-JIS strings.
* @param string $prop prop name.
*/
- private function charCodeString ( $prop ) {
+ private function charCodeString( $prop ) {
if ( isset( $this->mFilteredExifData[$prop] ) ) {
if ( strlen( $this->mFilteredExifData[$prop] ) <= 8 ) {
@@ -500,7 +501,7 @@ class Exif {
* the type of UNDEFINED field
* @param string $prop name of property
*/
- private function exifPropToOrd ( $prop ) {
+ private function exifPropToOrd( $prop ) {
if ( isset( $this->mFilteredExifData[$prop] ) ) {
$this->mFilteredExifData[$prop] = ord( $this->mFilteredExifData[$prop] );
}
@@ -510,7 +511,7 @@ class Exif {
* for example 10 degress 20`40`` S -> -10.34444
* @param string $prop a gps coordinate exif tag name (like GPSLongitude)
*/
- private function exifGPStoNumber ( $prop ) {
+ private function exifGPStoNumber( $prop ) {
$loc =& $this->mFilteredExifData[$prop];
$dir =& $this->mFilteredExifData[$prop . 'Ref'];
$res = false;
@@ -737,26 +738,27 @@ class Exif {
$debug = "tag is '$tag'";
$etype = $this->mExifTags[$section][$tag];
$ecount = 1;
- if( is_array( $etype ) ) {
+ if ( is_array( $etype ) ) {
list( $etype, $ecount ) = $etype;
- if ( $recursive )
+ if ( $recursive ) {
$ecount = 1; // checking individual elements
+ }
}
$count = count( $val );
- if( $ecount != $count ) {
+ if ( $ecount != $count ) {
$this->debug( $val, __FUNCTION__, "Expected $ecount elements for $tag but got $count" );
return false;
}
- if( $count > 1 ) {
- foreach( $val as $v ) {
- if( !$this->validate( $section, $tag, $v, true ) ) {
+ if ( $count > 1 ) {
+ foreach ( $val as $v ) {
+ if ( !$this->validate( $section, $tag, $v, true ) ) {
return false;
}
}
return true;
}
// Does not work if not typecast
- switch( (string)$etype ) {
+ switch ( (string)$etype ) {
case (string)Exif::BYTE:
$this->debug( $val, __FUNCTION__, $debug );
return $this->isByte( $val );
@@ -772,6 +774,9 @@ class Exif {
case (string)Exif::RATIONAL:
$this->debug( $val, __FUNCTION__, $debug );
return $this->isRational( $val );
+ case (string)Exif::SHORT_OR_LONG:
+ $this->debug( $val, __FUNCTION__, $debug );
+ return $this->isShort( $val ) || $this->isLong( $val );
case (string)Exif::UNDEFINED:
$this->debug( $val, __FUNCTION__, $debug );
return $this->isUndefined( $val );
@@ -781,9 +786,6 @@ class Exif {
case (string)Exif::SRATIONAL:
$this->debug( $val, __FUNCTION__, $debug );
return $this->isSrational( $val );
- case (string)Exif::SHORT.','.Exif::LONG:
- $this->debug( $val, __FUNCTION__, $debug );
- return $this->isShort( $val ) || $this->isLong( $val );
case (string)Exif::IGNORE:
$this->debug( $val, __FUNCTION__, $debug );
return false;
@@ -808,7 +810,7 @@ class Exif {
}
$type = gettype( $in );
$class = ucfirst( __CLASS__ );
- if ( $type === 'array' ) {
+ if ( is_array( $in ) ) {
$in = print_r( $in, true );
}
diff --git a/includes/media/ExifBitmap.php b/includes/media/ExifBitmap.php
index 1671ab25..d8d0bede 100644
--- a/includes/media/ExifBitmap.php
+++ b/includes/media/ExifBitmap.php
@@ -51,11 +51,11 @@ class ExifBitmapHandler extends BitmapHandler {
// Treat Software as a special case because in can contain
// an array of (SoftwareName, Version).
- if (isset( $metadata['Software'] )
+ if ( isset( $metadata['Software'] )
&& is_array( $metadata['Software'] )
- && is_array( $metadata['Software'][0])
+ && is_array( $metadata['Software'][0] )
&& isset( $metadata['Software'][0][0] )
- && isset( $metadata['Software'][0][1])
+ && isset( $metadata['Software'][0][1] )
) {
$metadata['Software'] = $metadata['Software'][0][0] . ' (Version '
. $metadata['Software'][0][1] . ')';
@@ -84,7 +84,7 @@ class ExifBitmapHandler extends BitmapHandler {
return self::METADATA_GOOD;
}
if ( $metadata === self::OLD_BROKEN_FILE ) {
- # Old special value indicating that there is no EXIF data in the 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" );
return self::METADATA_COMPATIBLE;
diff --git a/includes/media/FormatMetadata.php b/includes/media/FormatMetadata.php
index 1a7d7723..1c5136f5 100644
--- a/includes/media/FormatMetadata.php
+++ b/includes/media/FormatMetadata.php
@@ -79,7 +79,7 @@ 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] );
@@ -127,9 +127,9 @@ class FormatMetadata {
foreach ( $vals as &$val ) {
- switch( $tag ) {
+ switch ( $tag ) {
case 'Compression':
- switch( $val ) {
+ switch ( $val ) {
case 1: case 2: case 3: case 4:
case 5: case 6: case 7: case 8:
case 32773: case 32946: case 34712:
@@ -142,7 +142,7 @@ class FormatMetadata {
break;
case 'PhotometricInterpretation':
- switch( $val ) {
+ switch ( $val ) {
case 2: case 6:
$val = self::msg( $tag, $val );
break;
@@ -153,7 +153,7 @@ class FormatMetadata {
break;
case 'Orientation':
- switch( $val ) {
+ switch ( $val ) {
case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8:
$val = self::msg( $tag, $val );
break;
@@ -164,7 +164,7 @@ class FormatMetadata {
break;
case 'PlanarConfiguration':
- switch( $val ) {
+ switch ( $val ) {
case 1: case 2:
$val = self::msg( $tag, $val );
break;
@@ -189,7 +189,7 @@ class FormatMetadata {
case 'XResolution':
case 'YResolution':
- switch( $resolutionunit ) {
+ switch ( $resolutionunit ) {
case 2:
$val = self::msg( 'XYResolution', 'i', self::formatNum( $val ) );
break;
@@ -208,7 +208,7 @@ class FormatMetadata {
break;
case 'ColorSpace':
- switch( $val ) {
+ switch ( $val ) {
case 1: case 65535:
$val = self::msg( $tag, $val );
break;
@@ -219,7 +219,7 @@ class FormatMetadata {
break;
case 'ComponentsConfiguration':
- switch( $val ) {
+ switch ( $val ) {
case 0: case 1: case 2: case 3: case 4: case 5: case 6:
$val = self::msg( $tag, $val );
break;
@@ -267,7 +267,7 @@ class FormatMetadata {
break;
case 'ExposureProgram':
- switch( $val ) {
+ switch ( $val ) {
case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8:
$val = self::msg( $tag, $val );
break;
@@ -282,7 +282,7 @@ class FormatMetadata {
break;
case 'MeteringMode':
- switch( $val ) {
+ switch ( $val ) {
case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 255:
$val = self::msg( $tag, $val );
break;
@@ -293,7 +293,7 @@ class FormatMetadata {
break;
case 'LightSource':
- switch( $val ) {
+ switch ( $val ) {
case 0: case 1: case 2: case 3: case 4: case 9: case 10: case 11:
case 12: case 13: case 14: case 15: case 17: case 18: case 19: case 20:
case 21: case 22: case 23: case 24: case 255:
@@ -307,11 +307,11 @@ class FormatMetadata {
case 'Flash':
$flashDecode = array(
- 'fired' => $val & bindec( '00000001' ),
- 'return' => ( $val & bindec( '00000110' ) ) >> 1,
- 'mode' => ( $val & bindec( '00011000' ) ) >> 3,
+ 'fired' => $val & bindec( '00000001' ),
+ 'return' => ( $val & bindec( '00000110' ) ) >> 1,
+ 'mode' => ( $val & bindec( '00011000' ) ) >> 3,
'function' => ( $val & bindec( '00100000' ) ) >> 5,
- 'redeye' => ( $val & bindec( '01000000' ) ) >> 6,
+ 'redeye' => ( $val & bindec( '01000000' ) ) >> 6,
// 'reserved' => ($val & bindec( '10000000' )) >> 7,
);
$flashMsgs = array();
@@ -328,7 +328,7 @@ class FormatMetadata {
break;
case 'FocalPlaneResolutionUnit':
- switch( $val ) {
+ switch ( $val ) {
case 2:
$val = self::msg( $tag, $val );
break;
@@ -339,7 +339,7 @@ class FormatMetadata {
break;
case 'SensingMethod':
- switch( $val ) {
+ switch ( $val ) {
case 1: case 2: case 3: case 4: case 5: case 7: case 8:
$val = self::msg( $tag, $val );
break;
@@ -350,7 +350,7 @@ class FormatMetadata {
break;
case 'FileSource':
- switch( $val ) {
+ switch ( $val ) {
case 3:
$val = self::msg( $tag, $val );
break;
@@ -361,7 +361,7 @@ class FormatMetadata {
break;
case 'SceneType':
- switch( $val ) {
+ switch ( $val ) {
case 1:
$val = self::msg( $tag, $val );
break;
@@ -372,7 +372,7 @@ class FormatMetadata {
break;
case 'CustomRendered':
- switch( $val ) {
+ switch ( $val ) {
case 0: case 1:
$val = self::msg( $tag, $val );
break;
@@ -383,7 +383,7 @@ class FormatMetadata {
break;
case 'ExposureMode':
- switch( $val ) {
+ switch ( $val ) {
case 0: case 1: case 2:
$val = self::msg( $tag, $val );
break;
@@ -394,7 +394,7 @@ class FormatMetadata {
break;
case 'WhiteBalance':
- switch( $val ) {
+ switch ( $val ) {
case 0: case 1:
$val = self::msg( $tag, $val );
break;
@@ -405,7 +405,7 @@ class FormatMetadata {
break;
case 'SceneCaptureType':
- switch( $val ) {
+ switch ( $val ) {
case 0: case 1: case 2: case 3:
$val = self::msg( $tag, $val );
break;
@@ -416,7 +416,7 @@ class FormatMetadata {
break;
case 'GainControl':
- switch( $val ) {
+ switch ( $val ) {
case 0: case 1: case 2: case 3: case 4:
$val = self::msg( $tag, $val );
break;
@@ -427,7 +427,7 @@ class FormatMetadata {
break;
case 'Contrast':
- switch( $val ) {
+ switch ( $val ) {
case 0: case 1: case 2:
$val = self::msg( $tag, $val );
break;
@@ -438,7 +438,7 @@ class FormatMetadata {
break;
case 'Saturation':
- switch( $val ) {
+ switch ( $val ) {
case 0: case 1: case 2:
$val = self::msg( $tag, $val );
break;
@@ -449,7 +449,7 @@ class FormatMetadata {
break;
case 'Sharpness':
- switch( $val ) {
+ switch ( $val ) {
case 0: case 1: case 2:
$val = self::msg( $tag, $val );
break;
@@ -460,7 +460,7 @@ class FormatMetadata {
break;
case 'SubjectDistanceRange':
- switch( $val ) {
+ switch ( $val ) {
case 0: case 1: case 2: case 3:
$val = self::msg( $tag, $val );
break;
@@ -473,7 +473,7 @@ class FormatMetadata {
//The GPS...Ref values are kept for compatibility, probably won't be reached.
case 'GPSLatitudeRef':
case 'GPSDestLatitudeRef':
- switch( $val ) {
+ switch ( $val ) {
case 'N': case 'S':
$val = self::msg( 'GPSLatitude', $val );
break;
@@ -485,7 +485,7 @@ class FormatMetadata {
case 'GPSLongitudeRef':
case 'GPSDestLongitudeRef':
- switch( $val ) {
+ switch ( $val ) {
case 'E': case 'W':
$val = self::msg( 'GPSLongitude', $val );
break;
@@ -504,7 +504,7 @@ class FormatMetadata {
break;
case 'GPSStatus':
- switch( $val ) {
+ switch ( $val ) {
case 'A': case 'V':
$val = self::msg( $tag, $val );
break;
@@ -515,7 +515,7 @@ class FormatMetadata {
break;
case 'GPSMeasureMode':
- switch( $val ) {
+ switch ( $val ) {
case 2: case 3:
$val = self::msg( $tag, $val );
break;
@@ -528,7 +528,7 @@ class FormatMetadata {
case 'GPSTrackRef':
case 'GPSImgDirectionRef':
case 'GPSDestBearingRef':
- switch( $val ) {
+ switch ( $val ) {
case 'T': case 'M':
$val = self::msg( 'GPSDirection', $val );
break;
@@ -548,7 +548,7 @@ class FormatMetadata {
break;
case 'GPSSpeedRef':
- switch( $val ) {
+ switch ( $val ) {
case 'K': case 'M': case 'N':
$val = self::msg( 'GPSSpeed', $val );
break;
@@ -559,7 +559,7 @@ class FormatMetadata {
break;
case 'GPSDestDistanceRef':
- switch( $val ) {
+ switch ( $val ) {
case 'K': case 'M': case 'N':
$val = self::msg( 'GPSDestDistance', $val );
break;
@@ -646,7 +646,7 @@ class FormatMetadata {
break;
case 'iimCategory':
- switch( strtolower( $val ) ) {
+ switch ( strtolower( $val ) ) {
// See pg 29 of IPTC photo
// metadata standard.
case 'ace': case 'clj':
@@ -682,7 +682,7 @@ class FormatMetadata {
$urgency = 'high';
} elseif ( $val == 5 ) {
$urgency = 'normal';
- } elseif ( $val <= 8 && $val > 5) {
+ } elseif ( $val <= 8 && $val > 5 ) {
$urgency = 'low';
}
@@ -791,7 +791,7 @@ class FormatMetadata {
}
break;
case 'Copyrighted':
- switch( $val ) {
+ switch ( $val ) {
case 'True': case 'False':
$val = self::msg( $tag, $val );
break;
@@ -854,7 +854,7 @@ class FormatMetadata {
return $vals[0];
}
elseif ( count( $vals ) === 0 ) {
- wfDebug( __METHOD__ . ' metadata array with 0 elements!' );
+ wfDebug( __METHOD__ . " metadata array with 0 elements!\n" );
return ""; // paranoia. This should never happen
}
/* @todo FIXME: This should hide some of the list entries if there are
@@ -863,7 +863,7 @@ class FormatMetadata {
*/
else {
global $wgContLang;
- switch( $type ) {
+ switch ( $type ) {
case 'lang':
// Display default, followed by ContLang,
// followed by the rest in no particular
@@ -948,7 +948,7 @@ class FormatMetadata {
* this is treated as wikitext not html).
*/
private static function langItem( $value, $lang, $default = false, $noHtml = false ) {
- if ( $lang === false && $default === false) {
+ if ( $lang === false && $default === false ) {
throw new MWException( '$lang and $default cannot both '
. 'be false.' );
}
@@ -1015,8 +1015,9 @@ class FormatMetadata {
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();
}
@@ -1031,9 +1032,9 @@ 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 ) {
+ foreach ( $num as $number ) {
$out[] = self::formatNum( $number );
}
return $wgLang->commaList( $out );
@@ -1071,7 +1072,7 @@ class FormatMetadata {
$numerator = intval( $m[1] );
$denominator = intval( $m[2] );
$gcd = self::gcd( abs( $numerator ), $denominator );
- if( $gcd != 0 ) {
+ if ( $gcd != 0 ) {
// 0 shouldn't happen! ;)
return self::formatNum( $numerator / $gcd ) . '/' . self::formatNum( $denominator / $gcd );
}
@@ -1096,7 +1097,7 @@ class FormatMetadata {
else
return gcd( $b, $a % $b );
*/
- while( $b != 0 ) {
+ while ( $b != 0 ) {
$remainder = $a % $b;
// tail recursion...
@@ -1124,7 +1125,7 @@ class FormatMetadata {
return $val;
}
$cat = '';
- switch( substr( $val, 0, 2 ) ) {
+ switch ( substr( $val, 0, 2 ) ) {
case '01':
$cat = 'ace';
break;
@@ -1236,7 +1237,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'] )
@@ -1254,7 +1255,7 @@ class FormatMetadata {
// because people often insert >, etc into
// the metadata which should not be interpreted
// but we still want to auto-link urls.
- foreach( $vals as &$val ) {
+ foreach ( $vals as &$val ) {
$val = htmlspecialchars( $val );
}
return self::flattenArray( $vals );
diff --git a/includes/media/GIF.php b/includes/media/GIF.php
index 2e532feb..608fb257 100644
--- a/includes/media/GIF.php
+++ b/includes/media/GIF.php
@@ -33,7 +33,7 @@ class GIFHandler extends BitmapHandler {
function getMetadata( $image, $filename ) {
try {
$parsedGIFMetadata = BitmapMetadataHandler::GIF( $filename );
- } catch( Exception $e ) {
+ } catch ( Exception $e ) {
// Broken file?
wfDebug( __METHOD__ . ': ' . $e->getMessage() . "\n" );
return self::BROKEN_FILE;
@@ -86,7 +86,7 @@ class GIFHandler extends BitmapHandler {
$ser = $image->getMetadata();
if ( $ser ) {
$metadata = unserialize( $ser );
- if( $metadata['frameCount'] > 1 ) {
+ 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\n" );
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\n" );
return self::METADATA_COMPATIBLE;
}
return self::METADATA_GOOD;
diff --git a/includes/media/GIFMetadataExtractor.php b/includes/media/GIFMetadataExtractor.php
index 6a4e753d..887afa3f 100644
--- a/includes/media/GIFMetadataExtractor.php
+++ b/includes/media/GIFMetadataExtractor.php
@@ -90,7 +90,7 @@ class GIFMetadataExtractor {
// Skip over the GCT
self::readGCT( $fh, $bpp );
- while( !feof( $fh ) ) {
+ while ( !feof( $fh ) ) {
$buf = fread( $fh, 1 );
if ( $buf == self::$gif_frame_sep ) {
@@ -110,7 +110,9 @@ class GIFMetadataExtractor {
self::skipBlock( $fh );
} elseif ( $buf == self::$gif_extension_sep ) {
$buf = fread( $fh, 1 );
- if ( strlen( $buf ) < 1 ) throw new Exception( "Ran out of input" );
+ if ( strlen( $buf ) < 1 ) {
+ throw new Exception( "Ran out of input" );
+ }
$extension_code = unpack( 'C', $buf );
$extension_code = $extension_code[1];
@@ -121,7 +123,9 @@ class GIFMetadataExtractor {
fread( $fh, 1 ); // Transparency, disposal method, user input
$buf = fread( $fh, 2 ); // Delay, in hundredths of seconds.
- if ( strlen( $buf ) < 2 ) throw new Exception( "Ran out of input" );
+ if ( strlen( $buf ) < 2 ) {
+ throw new Exception( "Ran out of input" );
+ }
$delay = unpack( 'v', $buf );
$delay = $delay[1];
$duration += $delay * 0.01;
@@ -129,7 +133,9 @@ class GIFMetadataExtractor {
fread( $fh, 1 ); // Transparent colour index
$term = fread( $fh, 1 ); // Should be a terminator
- if ( strlen( $term ) < 1 ) throw new Exception( "Ran out of input" );
+ if ( strlen( $term ) < 1 ) {
+ throw new Exception( "Ran out of input" );
+ }
$term = unpack( 'C', $term );
$term = $term[1];
if ( $term != 0 ) {
@@ -157,7 +163,7 @@ class GIFMetadataExtractor {
$commentCount = count( $comment );
if ( $commentCount === 0
- || $comment[$commentCount-1] !== $data )
+ || $comment[$commentCount - 1] !== $data )
{
// Some applications repeat the same comment on each
// frame of an animated GIF image, so if this comment
@@ -168,14 +174,16 @@ class GIFMetadataExtractor {
// Application extension (Netscape info about the animated gif)
// or XMP (or theoretically any other type of extension block)
$blockLength = fread( $fh, 1 );
- if ( strlen( $blockLength ) < 1 ) throw new Exception( "Ran out of input" );
+ if ( strlen( $blockLength ) < 1 ) {
+ throw new Exception( "Ran out of input" );
+ }
$blockLength = unpack( 'C', $blockLength );
$blockLength = $blockLength[1];
$data = fread( $fh, $blockLength );
if ( $blockLength != 11 ) {
- wfDebug( __METHOD__ . ' GIF application block with wrong length' );
- fseek( $fh, -($blockLength + 1), SEEK_CUR );
+ wfDebug( __METHOD__ . " GIF application block with wrong length\n" );
+ fseek( $fh, -( $blockLength + 1 ), SEEK_CUR );
self::skipBlock( $fh );
continue;
}
@@ -190,7 +198,9 @@ class GIFMetadataExtractor {
// 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" );
+ if ( strlen( $loopData ) < 2 ) {
+ throw new Exception( "Ran out of input" );
+ }
$loopData = unpack( 'v', $loopData );
$loopCount = $loopData[1];
@@ -218,7 +228,7 @@ class GIFMetadataExtractor {
} else {
// unrecognized extension block
- fseek( $fh, -($blockLength + 1), SEEK_CUR );
+ fseek( $fh, -( $blockLength + 1 ), SEEK_CUR );
self::skipBlock( $fh );
continue;
}
@@ -228,7 +238,9 @@ class GIFMetadataExtractor {
} elseif ( $buf == self::$gif_term ) {
break;
} else {
- if ( strlen( $buf ) < 1 ) throw new Exception( "Ran out of input" );
+ 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 );
@@ -251,7 +263,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 );
}
}
@@ -263,7 +275,9 @@ class GIFMetadataExtractor {
* @return int
*/
static function decodeBPP( $data ) {
- if ( strlen( $data ) < 1 ) throw new Exception( "Ran out of input" );
+ if ( strlen( $data ) < 1 ) {
+ throw new Exception( "Ran out of input" );
+ }
$buf = unpack( 'C', $data );
$buf = $buf[1];
$bpp = ( $buf & 7 ) + 1;
@@ -281,7 +295,9 @@ class GIFMetadataExtractor {
static function skipBlock( $fh ) {
while ( !feof( $fh ) ) {
$buf = fread( $fh, 1 );
- if ( strlen( $buf ) < 1 ) throw new Exception( "Ran out of input" );
+ 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 ) {
@@ -310,7 +326,7 @@ class GIFMetadataExtractor {
$subLength = fread( $fh, 1 );
$blocks = 0;
- while( $subLength !== "\0" ) {
+ while ( $subLength !== "\0" ) {
$blocks++;
if ( $blocks > self::MAX_SUBBLOCKS ) {
throw new Exception( "MAX_SUBBLOCKS exceeded (over $blocks sub-blocks)" );
diff --git a/includes/media/IPTC.php b/includes/media/IPTC.php
index 4191cde0..544dd211 100644
--- a/includes/media/IPTC.php
+++ b/includes/media/IPTC.php
@@ -63,7 +63,7 @@ class IPTC {
wfDebugLog( 'iptc', "IPTC tag $tag had only whitespace as its value." );
continue;
}
- switch( $tag ) {
+ switch ( $tag ) {
case '2#120': /*IPTC caption. mapped with exif ImageDescription*/
$data['ImageDescription'] = self::convIPTC( $val, $c );
break;
@@ -396,7 +396,7 @@ class IPTC {
return null;
}
- $tz = ( intval( substr( $time, 7, 2 ) ) *60*60 )
+ $tz = ( intval( substr( $time, 7, 2 ) ) * 60 * 60 )
+ ( intval( substr( $time, 9, 2 ) ) * 60 );
if ( substr( $time, 6, 1 ) === '-' ) {
@@ -423,7 +423,7 @@ class IPTC {
*
* @return string|array
*/
- private static function convIPTC ( $data, $charset ) {
+ private static function convIPTC( $data, $charset ) {
if ( is_array( $data ) ) {
foreach ( $data as &$val ) {
$val = self::convIPTCHelper( $val, $charset );
@@ -441,7 +441,7 @@ class IPTC {
*
* @return string
*/
- private static function convIPTCHelper ( $data, $charset ) {
+ private static function convIPTCHelper( $data, $charset ) {
if ( $charset ) {
wfSuppressWarnings();
$data = iconv( $charset, "UTF-8//IGNORE", $data );
diff --git a/includes/media/ImageHandler.php b/includes/media/ImageHandler.php
index 419afeef..e079003b 100644
--- a/includes/media/ImageHandler.php
+++ b/includes/media/ImageHandler.php
@@ -58,7 +58,7 @@ abstract class ImageHandler extends MediaHandler {
} elseif ( isset( $params['width'] ) ) {
$width = $params['width'];
} else {
- throw new MWException( 'No width specified to '.__METHOD__ );
+ throw new MWException( 'No width specified to ' . __METHOD__ );
}
# Removed for ProofreadPage
#$width = intval( $width );
@@ -92,7 +92,7 @@ abstract class ImageHandler extends MediaHandler {
if ( !isset( $params['page'] ) ) {
$params['page'] = 1;
- } else {
+ } else {
if ( $params['page'] > $image->pageCount() ) {
$params['page'] = $image->pageCount();
}
@@ -160,7 +160,7 @@ abstract class ImageHandler extends MediaHandler {
$width = intval( $width );
# Sanity check $width
- if( $width <= 0) {
+ if ( $width <= 0 ) {
wfDebug( __METHOD__ . ": Invalid destination width: $width\n" );
return false;
}
@@ -187,9 +187,9 @@ abstract class ImageHandler extends MediaHandler {
if ( !$this->normaliseParams( $image, $params ) ) {
return false;
}
- $url = $script . '&' . wfArrayToCgi( $this->getScriptParams( $params ) );
+ $url = wfAppendQuery( $script, $this->getScriptParams( $params ) );
- if( $image->mustRender() || $params['width'] < $image->getWidth() ) {
+ if ( $image->mustRender() || $params['width'] < $image->getWidth() ) {
return new ThumbnailImage( $image, $url, false, $params );
}
}
@@ -200,6 +200,19 @@ abstract class ImageHandler extends MediaHandler {
wfRestoreWarnings();
return $gis;
}
+ /**
+ * Function that returns the number of pixels to be thumbnailed.
+ * Intended for animated GIFs to multiply by the number of frames.
+ *
+ * If the file doesn't support a notion of "area" return 0.
+ *
+ * @param File $image
+ * @return int
+ */
+ function getImageArea( $image ) {
+ return $image->getWidth() * $image->getHeight();
+ }
+
/**
* @param $file File
diff --git a/includes/media/Jpeg.php b/includes/media/Jpeg.php
index 8b5d6513..fa763668 100644
--- a/includes/media/Jpeg.php
+++ b/includes/media/Jpeg.php
@@ -32,7 +32,7 @@
*/
class JpegHandler extends ExifBitmapHandler {
- function getMetadata ( $image, $filename ) {
+ function getMetadata( $image, $filename ) {
try {
$meta = BitmapMetadataHandler::Jpeg( $filename );
if ( !is_array( $meta ) ) {
@@ -69,18 +69,18 @@ class JpegHandler extends ExifBitmapHandler {
public function rotate( $file, $params ) {
global $wgJpegTran;
- $rotation = ( $params[ 'rotation' ] + $this->getRotation( $file ) ) % 360;
+ $rotation = ( $params['rotation'] + $this->getRotation( $file ) ) % 360;
- if( $wgJpegTran && is_file( $wgJpegTran ) ){
+ 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' );
+ " -outfile " . wfEscapeShellArg( $params['dstPath'] ) .
+ " " . wfEscapeShellArg( $params['srcPath'] );
+ wfDebug( __METHOD__ . ": running jpgtran: $cmd\n" );
+ wfProfileIn( 'jpegtran' );
+ $retval = 0;
+ $err = wfShellExecWithStderr( $cmd, $retval, $env );
+ wfProfileOut( 'jpegtran' );
if ( $retval !== 0 ) {
$this->logErrorForExternalProcess( $retval, $err, $cmd );
return new MediaTransformError( 'thumbnail_error', 0, 0, $err );
diff --git a/includes/media/JpegMetadataExtractor.php b/includes/media/JpegMetadataExtractor.php
index 6ff07ed2..c7030eba 100644
--- a/includes/media/JpegMetadataExtractor.php
+++ b/includes/media/JpegMetadataExtractor.php
@@ -47,7 +47,7 @@ class JpegMetadataExtractor {
* @return Array of interesting segments.
* @throws MWException if given invalid file.
*/
- static function segmentSplitter ( $filename ) {
+ static function segmentSplitter( $filename ) {
$showXMP = function_exists( 'xml_parser_create_ns' );
$segmentCount = 0;
@@ -87,7 +87,7 @@ class JpegMetadataExtractor {
}
$buffer = fread( $fh, 1 );
- while( $buffer === "\xFF" && !feof( $fh ) ) {
+ while ( $buffer === "\xFF" && !feof( $fh ) ) {
// Skip through any 0xFF padding bytes.
$buffer = fread( $fh, 1 );
}
@@ -111,7 +111,7 @@ class JpegMetadataExtractor {
if ( $com === $oldCom ) {
$segments["COM"][] = $oldCom;
} else {
- wfDebug( __METHOD__ . ' Ignoring JPEG comment as is garbage.' );
+ wfDebug( __METHOD__ . " Ignoring JPEG comment as is garbage.\n" );
}
} elseif ( $buffer === "\xE1" ) {
@@ -140,7 +140,7 @@ class JpegMetadataExtractor {
} elseif ( $byteOrderMarker === 'II' ) {
$segments['byteOrder'] = 'LE';
} else {
- wfDebug( __METHOD__ . ' Invalid byte ordering?!' );
+ wfDebug( __METHOD__ . " Invalid byte ordering?!\n" );
}
}
} elseif ( $buffer === "\xED" ) {
@@ -155,7 +155,9 @@ class JpegMetadataExtractor {
} else {
// segment we don't care about, so skip
$size = wfUnpack( "nint", fread( $fh, 2 ), 2 );
- if ( $size['int'] <= 2 ) throw new MWException( "invalid marker size in jpeg" );
+ if ( $size['int'] <= 2 ) {
+ throw new MWException( "invalid marker size in jpeg" );
+ }
fseek( $fh, $size['int'] - 2, SEEK_CUR );
}
@@ -195,7 +197,7 @@ class JpegMetadataExtractor {
* @throws MWException (It gets caught next level up though)
* @return String if the iptc hash is good or not.
*/
- public static function doPSIR ( $app13 ) {
+ public static function doPSIR( $app13 ) {
if ( !$app13 ) {
throw new MWException( "No App13 segment given" );
}
@@ -243,7 +245,9 @@ class JpegMetadataExtractor {
// PHP can take issue with very large unsigned ints and make them negative.
// Which should never ever happen, as this has to be inside a segment
// which is limited to a 16 bit number.
- if ( $lenData['len'] < 0 ) throw new MWException( "Too big PSIR (" . $lenData['len'] . ')' );
+ if ( $lenData['len'] < 0 ) {
+ throw new MWException( "Too big PSIR (" . $lenData['len'] . ')' );
+ }
$offset += 4; // 4bytes length field;
@@ -267,7 +271,9 @@ class JpegMetadataExtractor {
// if odd, add 1 to length to account for
// null pad byte.
- if ( $lenData['len'] % 2 == 1 ) $lenData['len']++;
+ if ( $lenData['len'] % 2 == 1 ) {
+ $lenData['len']++;
+ }
$offset += $lenData['len'];
}
diff --git a/includes/media/MediaHandler.php b/includes/media/MediaHandler.php
index 9a3f645b..779e23c9 100644
--- a/includes/media/MediaHandler.php
+++ b/includes/media/MediaHandler.php
@@ -78,14 +78,16 @@ abstract class MediaHandler {
/**
* Merge a parameter array into a string appropriate for inclusion in filenames
*
- * @param $params array
+ * @param $params array Array of parameters that have been through normaliseParams.
+ * @return String
*/
abstract function makeParamString( $params );
/**
* Parse a param string made with makeParamString back into an array
*
- * @param $str string
+ * @param $str string The parameter string without file name (e.g. 122px)
+ * @return Array|Boolean Array of parameters or false on failure.
*/
abstract function parseParamString( $str );
@@ -116,7 +118,9 @@ abstract class MediaHandler {
* @param string $path the filename
* @return String
*/
- function getMetadata( $image, $path ) { return ''; }
+ function getMetadata( $image, $path ) {
+ return '';
+ }
/**
* Get metadata version.
@@ -133,10 +137,10 @@ abstract class MediaHandler {
*
* @return string version string
*/
- static function getMetadataVersion () {
+ static function getMetadataVersion() {
$version = Array( '2' ); // core metadata version
wfRunHooks( 'GetMetadataVersion', Array( &$version ) );
- return implode( ';', $version);
+ return implode( ';', $version );
}
/**
@@ -166,7 +170,9 @@ abstract class MediaHandler {
*
* @return string
*/
- function getMetadataType( $image ) { return false; }
+ function getMetadataType( $image ) {
+ return false;
+ }
/**
* Check if the metadata string is valid for this handler.
@@ -216,6 +222,7 @@ abstract class MediaHandler {
* @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()
+ * Note: These parameters have *not* gone through $this->normaliseParams()
* @param $flags Integer: a bitfield, may contain self::TRANSFORM_LATER
*
* @return MediaTransformOutput
@@ -224,6 +231,10 @@ abstract class MediaHandler {
/**
* Get the thumbnail extension and MIME type for a given source MIME type
+ *
+ * @param String $ext Extension of original file
+ * @param String $mime Mime type of original file
+ * @param Array $params Handler specific rendering parameters
* @return array thumbnail extension and MIME type
*/
function getThumbType( $ext, $mime, $params = null ) {
@@ -255,63 +266,96 @@ abstract class MediaHandler {
* True if the handled types can be transformed
* @return bool
*/
- function canRender( $file ) { return true; }
+ function canRender( $file ) {
+ return true;
+ }
+
/**
* True if handled types cannot be displayed directly in a browser
* but can be rendered
* @return bool
*/
- function mustRender( $file ) { return false; }
+ function mustRender( $file ) {
+ return false;
+ }
+
/**
* True if the type has multi-page capabilities
* @return bool
*/
- function isMultiPage( $file ) { return false; }
+ function isMultiPage( $file ) {
+ return false;
+ }
+
/**
* Page count for a multi-page document, false if unsupported or unknown
* @return bool
*/
- function pageCount( $file ) { return false; }
+ function pageCount( $file ) {
+ return false;
+ }
+
/**
* The material is vectorized and thus scaling is lossless
* @return bool
*/
- function isVectorized( $file ) { return false; }
+ function isVectorized( $file ) {
+ return false;
+ }
+
/**
* The material is an image, and is animated.
* In particular, video material need not return true.
* @note Before 1.20, this was a method of ImageHandler only
* @return bool
*/
- function isAnimatedImage( $file ) { return false; }
+ function isAnimatedImage( $file ) {
+ return false;
+ }
+
/**
* If the material is animated, we can animate the thumbnail
* @since 1.20
* @return bool If material is not animated, handler may return any value.
*/
- function canAnimateThumbnail( $file ) { return true; }
+ function canAnimateThumbnail( $file ) {
+ return true;
+ }
+
/**
* False if the handler is disabled for all files
* @return bool
*/
- function isEnabled() { return true; }
+ function isEnabled() {
+ return true;
+ }
/**
* Get an associative array of page dimensions
* Currently "width" and "height" are understood, but this might be
* expanded in the future.
- * Returns false if unknown or if the document is not multi-page.
+ * Returns false if unknown.
+ *
+ * It is expected that handlers for paged media (e.g. DjVuHandler)
+ * will override this method so that it gives the correct results
+ * for each specific page of the file, using the $page argument.
+ *
+ * @note For non-paged media, use getImageSize.
*
* @param $image File
- * @param $page Unused, left for backcompatibility?
- * @return array
+ * @param $page What page to get dimensions of
+ * @return array|bool
*/
function getPageDimensions( $image, $page ) {
$gis = $this->getImageSize( $image, $image->getLocalRefPath() );
- return array(
- 'width' => $gis[0],
- 'height' => $gis[1]
- );
+ if ( $gis ) {
+ return array(
+ 'width' => $gis[0],
+ 'height' => $gis[1]
+ );
+ } else {
+ return false;
+ }
}
/**
@@ -394,9 +438,9 @@ abstract class MediaHandler {
function visibleMetadataFields() {
$fields = array();
$lines = explode( "\n", wfMessage( 'metadata-fields' )->inContentLanguage()->text() );
- foreach( $lines as $line ) {
+ foreach ( $lines as $line ) {
$matches = array();
- if( preg_match( '/^\\*\s*(.*?)\s*$/', $line, $matches ) ) {
+ if ( preg_match( '/^\\*\s*(.*?)\s*$/', $line, $matches ) ) {
$fields[] = $matches[1];
}
}
@@ -448,6 +492,8 @@ abstract class MediaHandler {
}
/**
+ * Used instead of getLongDesc if there is no handler registered for file.
+ *
* @param $file File
* @return string
*/
@@ -457,6 +503,8 @@ abstract class MediaHandler {
}
/**
+ * Short description. Shown on Special:Search results.
+ *
* @param $file File
* @return string
*/
@@ -467,6 +515,8 @@ abstract class MediaHandler {
}
/**
+ * Long description. Shown under image on image description page surounded by ().
+ *
* @param $file File
* @return string
*/
@@ -476,6 +526,8 @@ abstract class MediaHandler {
}
/**
+ * Used instead of getShortDesc if there is no handler registered for file.
+ *
* @param $file File
* @return string
*/
@@ -496,19 +548,32 @@ abstract class MediaHandler {
public static function fitBoxWidth( $boxWidth, $boxHeight, $maxHeight ) {
$idealWidth = $boxWidth * $maxHeight / $boxHeight;
$roundedUp = ceil( $idealWidth );
- if( round( $roundedUp * $boxHeight / $boxWidth ) > $maxHeight ) {
+ if ( round( $roundedUp * $boxHeight / $boxWidth ) > $maxHeight ) {
return floor( $idealWidth );
} else {
return $roundedUp;
}
}
+ /**
+ * Shown in file history box on image description page.
+ *
+ * @param File $file
+ * @return String Dimensions
+ */
function getDimensionsString( $file ) {
return '';
}
/**
- * Modify the parser object post-transform
+ * Modify the parser object post-transform.
+ *
+ * This is often used to do $parser->addOutputHook(),
+ * in order to add some javascript to render a viewer.
+ * See TimedMediaHandler or OggHandler for an example.
+ *
+ * @param Parser $parser
+ * @param File $file
*/
function parserTransformHook( $parser, $file ) {}
@@ -535,9 +600,9 @@ abstract class MediaHandler {
* @return bool True if removed, false otherwise
*/
function removeBadFile( $dstPath, $retval = 0 ) {
- if( file_exists( $dstPath ) ) {
+ if ( file_exists( $dstPath ) ) {
$thumbstat = stat( $dstPath );
- if( $thumbstat['size'] == 0 || $retval != 0 ) {
+ if ( $thumbstat['size'] == 0 || $retval != 0 ) {
$result = unlink( $dstPath );
if ( $result ) {
@@ -556,10 +621,17 @@ abstract class MediaHandler {
}
/**
- * Remove files from the purge list
+ * Remove files from the purge list.
+ *
+ * This is used by some video handlers to prevent ?action=purge
+ * from removing a transcoded video, which is expensive to
+ * regenerate.
+ *
+ * @see LocalFile::purgeThumbnails
*
* @param array $files
- * @param array $options
+ * @param array $options Purge options. Currently will always be
+ * an array with a single key 'forThumbRefresh' set to true.
*/
public function filterThumbnailPurgeList( &$files, $options ) {
// Do nothing
@@ -573,4 +645,23 @@ abstract class MediaHandler {
public static function canRotate() {
return false;
}
+
+ /**
+ * On supporting image formats, try to read out the low-level orientation
+ * of the file and return the angle that the file needs to be rotated to
+ * be viewed.
+ *
+ * This information is only useful when manipulating the original file;
+ * the width and height we normally work with is logical, and will match
+ * any produced output views.
+ *
+ * For files we don't know, we return 0.
+ *
+ * @param $file File
+ * @return int 0, 90, 180 or 270
+ */
+ public function getRotation( $file ) {
+ return 0;
+ }
+
}
diff --git a/includes/media/MediaTransformOutput.php b/includes/media/MediaTransformOutput.php
index 1f95bc3b..c49d3f20 100644
--- a/includes/media/MediaTransformOutput.php
+++ b/includes/media/MediaTransformOutput.php
@@ -32,7 +32,7 @@ abstract class MediaTransformOutput {
*/
var $file;
- var $width, $height, $url, $page, $path;
+ var $width, $height, $url, $page, $path, $lang;
/**
* @var array Associative array mapping optional supplementary image files
@@ -151,7 +151,12 @@ abstract class MediaTransformOutput {
if ( $this->isError() ) {
return false;
} elseif ( $this->path === null ) {
- return $this->file->getLocalRefPath();
+ return $this->file->getLocalRefPath(); // assume thumb was not scaled
+ } elseif ( FileBackend::isStoragePath( $this->path ) ) {
+ $be = $this->file->getRepo()->getBackend();
+ // The temp file will be process cached by FileBackend
+ $fsFile = $be->getLocalReference( array( 'src' => $this->path ) );
+ return $fsFile ? $fsFile->getPath() : false;
} else {
return $this->path; // may return false
}
@@ -192,17 +197,26 @@ abstract class MediaTransformOutput {
/**
* @param $title string
- * @param $params array
+ * @param $params string|array Query parameters to add
* @return array
*/
- public function getDescLinkAttribs( $title = null, $params = '' ) {
- $query = '';
+ public function getDescLinkAttribs( $title = null, $params = array() ) {
+ if ( is_array( $params ) ) {
+ $query = $params;
+ } else {
+ $query = array();
+ }
if ( $this->page && $this->page !== 1 ) {
- $query = 'page=' . urlencode( $this->page );
+ $query['page'] = $this->page;
}
- if( $params ) {
- $query .= $query ? '&' . $params : $params;
+ if ( $this->lang ) {
+ $query['lang'] = $this->lang;
}
+
+ if ( is_string( $params ) && $params !== '' ) {
+ $query = $params . '&' . wfArrayToCgi( $query );
+ }
+
$attribs = array(
'href' => $this->file->getTitle()->getLocalURL( $query ),
'class' => 'image',
@@ -237,10 +251,12 @@ class ThumbnailImage extends MediaTransformOutput {
# Previous parameters:
# $file, $url, $width, $height, $path = false, $page = false
- if( is_array( $parameters ) ) {
- $defaults = array(
- 'page' => false
- );
+ $defaults = array(
+ 'page' => false,
+ 'lang' => false
+ );
+
+ if ( is_array( $parameters ) ) {
$actualParams = $parameters + $defaults;
} else {
# Using old format, should convert. Later a warning could be added here.
@@ -249,7 +265,7 @@ class ThumbnailImage extends MediaTransformOutput {
'width' => $path,
'height' => $parameters,
'page' => ( $numArgs > 5 ) ? func_get_arg( 5 ) : false
- );
+ ) + $defaults;
$path = ( $numArgs > 4 ) ? func_get_arg( 4 ) : false;
}
@@ -264,6 +280,7 @@ class ThumbnailImage extends MediaTransformOutput {
$this->height = round( $actualParams['height'] );
$this->page = $actualParams['page'];
+ $this->lang = $actualParams['lang'];
}
/**
@@ -281,6 +298,8 @@ class ThumbnailImage extends MediaTransformOutput {
* valign vertical-align property, if the output is an inline element
* img-class Class applied to the \<img\> tag, if there is such a tag
* desc-query String, description link query params
+ * override-width Override width attribute. Should generally not set
+ * override-height Override height attribute. Should generally not set
* custom-url-link Custom URL to link to
* custom-title-link Custom Title object to link to
* custom target-link Value of the target attribute, for custom-target-link
@@ -296,7 +315,7 @@ class ThumbnailImage extends MediaTransformOutput {
*/
function toHtml( $options = array() ) {
if ( count( func_get_args() ) == 2 ) {
- throw new MWException( __METHOD__ .' called in the old style' );
+ throw new MWException( __METHOD__ . ' called in the old style' );
}
$alt = empty( $options['alt'] ) ? '' : $options['alt'];
@@ -342,6 +361,12 @@ class ThumbnailImage extends MediaTransformOutput {
if ( !empty( $options['img-class'] ) ) {
$attribs['class'] = $options['img-class'];
}
+ if ( isset( $options['override-height'] ) ) {
+ $attribs['height'] = $options['override-height'];
+ }
+ if ( isset( $options['override-width'] ) ) {
+ $attribs['width'] = $options['override-width'];
+ }
// Additional densities for responsive images, if specified.
if ( !empty( $this->responsiveUrls ) ) {
diff --git a/includes/media/PNG.php b/includes/media/PNG.php
index b8a5b40b..98f13861 100644
--- a/includes/media/PNG.php
+++ b/includes/media/PNG.php
@@ -38,7 +38,7 @@ class PNGHandler extends BitmapHandler {
function getMetadata( $image, $filename ) {
try {
$metadata = BitmapMetadataHandler::PNG( $filename );
- } catch( Exception $e ) {
+ } catch ( Exception $e ) {
// Broken file?
wfDebug( __METHOD__ . ': ' . $e->getMessage() . "\n" );
return self::BROKEN_FILE;
@@ -76,7 +76,9 @@ class PNGHandler extends BitmapHandler {
$ser = $image->getMetadata();
if ( $ser ) {
$metadata = unserialize( $ser );
- if( $metadata['frameCount'] > 1 ) return true;
+ if ( $metadata['frameCount'] > 1 ) {
+ return true;
+ }
}
return false;
}
@@ -105,13 +107,13 @@ class PNGHandler extends BitmapHandler {
wfRestoreWarnings();
if ( !$data || !is_array( $data ) ) {
- wfDebug( __METHOD__ . ' invalid png metadata' );
+ wfDebug( __METHOD__ . " invalid png metadata\n" );
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\n" );
return self::METADATA_COMPATIBLE;
}
return self::METADATA_GOOD;
@@ -129,8 +131,9 @@ class PNGHandler extends BitmapHandler {
$metadata = unserialize( $image->getMetadata() );
wfRestoreWarnings();
- if( !$metadata || $metadata['frameCount'] <= 0 )
+ if ( !$metadata || $metadata['frameCount'] <= 0 ) {
return $original;
+ }
$info = array();
$info[] = $original;
diff --git a/includes/media/PNGMetadataExtractor.php b/includes/media/PNGMetadataExtractor.php
index 87f705ca..845d212a 100644
--- a/includes/media/PNGMetadataExtractor.php
+++ b/includes/media/PNGMetadataExtractor.php
@@ -142,7 +142,7 @@ class PNGMetadataExtractor {
}
} elseif ( $chunk_type == "acTL" ) {
$buf = fread( $fh, $chunk_size );
- if( !$buf || strlen( $buf ) < $chunk_size || $chunk_size < 4 ) {
+ if ( !$buf || strlen( $buf ) < $chunk_size || $chunk_size < 4 ) {
throw new Exception( __METHOD__ . ": Read error" );
}
@@ -202,21 +202,21 @@ class PNGMetadataExtractor {
if ( $items[5] === false ) {
// decompression failed
- wfDebug( __METHOD__ . ' Error decompressing iTxt chunk - ' . $items[1] );
+ wfDebug( __METHOD__ . ' Error decompressing iTxt chunk - ' . $items[1] . "\n" );
fseek( $fh, self::$CRC_size, SEEK_CUR );
continue;
}
} else {
wfDebug( __METHOD__ . ' Skipping compressed png iTXt chunk due to lack of zlib,'
- . ' or potentially invalid compression method' );
+ . " or potentially invalid compression method\n" );
fseek( $fh, self::$CRC_size, SEEK_CUR );
continue;
}
}
- $finalKeyword = self::$text_chunks[ $items[1] ];
- $text[ $finalKeyword ][ $items[3] ] = $items[5];
- $text[ $finalKeyword ]['_type'] = 'lang';
+ $finalKeyword = self::$text_chunks[$items[1]];
+ $text[$finalKeyword][$items[3]] = $items[5];
+ $text[$finalKeyword]['_type'] = 'lang';
} else {
// Error reading iTXt chunk
@@ -251,9 +251,9 @@ class PNGMetadataExtractor {
throw new Exception( __METHOD__ . ": Read error (error with iconv)" );
}
- $finalKeyword = self::$text_chunks[ $keyword ];
- $text[ $finalKeyword ][ 'x-default' ] = $content;
- $text[ $finalKeyword ]['_type'] = 'lang';
+ $finalKeyword = self::$text_chunks[$keyword];
+ $text[$finalKeyword]['x-default'] = $content;
+ $text[$finalKeyword]['_type'] = 'lang';
} elseif ( $chunk_type == 'zTXt' ) {
if ( function_exists( 'gzuncompress' ) ) {
@@ -279,7 +279,7 @@ class PNGMetadataExtractor {
$compression = substr( $postKeyword, 0, 1 );
$content = substr( $postKeyword, 1 );
if ( $compression !== "\x00" ) {
- wfDebug( __METHOD__ . " Unrecognized compression method in zTXt ($keyword). Skipping." );
+ wfDebug( __METHOD__ . " Unrecognized compression method in zTXt ($keyword). Skipping.\n" );
fseek( $fh, self::$CRC_size, SEEK_CUR );
continue;
}
@@ -290,7 +290,7 @@ class PNGMetadataExtractor {
if ( $content === false ) {
// decompression failed
- wfDebug( __METHOD__ . ' Error decompressing zTXt chunk - ' . $keyword );
+ wfDebug( __METHOD__ . ' Error decompressing zTXt chunk - ' . $keyword . "\n" );
fseek( $fh, self::$CRC_size, SEEK_CUR );
continue;
}
@@ -303,12 +303,12 @@ class PNGMetadataExtractor {
throw new Exception( __METHOD__ . ": Read error (error with iconv)" );
}
- $finalKeyword = self::$text_chunks[ $keyword ];
- $text[ $finalKeyword ][ 'x-default' ] = $content;
- $text[ $finalKeyword ]['_type'] = 'lang';
+ $finalKeyword = self::$text_chunks[$keyword];
+ $text[$finalKeyword]['x-default'] = $content;
+ $text[$finalKeyword]['_type'] = 'lang';
} else {
- wfDebug( __METHOD__ . " Cannot decompress zTXt chunk due to lack of zlib. Skipping." );
+ wfDebug( __METHOD__ . " Cannot decompress zTXt chunk due to lack of zlib. Skipping.\n" );
fseek( $fh, $chunk_size, SEEK_CUR );
}
} elseif ( $chunk_type == 'tIME' ) {
diff --git a/includes/media/SVG.php b/includes/media/SVG.php
index cddab51d..72a9696c 100644
--- a/includes/media/SVG.php
+++ b/includes/media/SVG.php
@@ -56,7 +56,7 @@ class SvgHandler extends ImageHandler {
$metadata = $file->getMetadata();
if ( $metadata ) {
$metadata = $this->unpackMetadata( $metadata );
- if( isset( $metadata['animated'] ) ) {
+ if ( isset( $metadata['animated'] ) ) {
return $metadata['animated'];
}
}
@@ -115,6 +115,7 @@ class SvgHandler extends ImageHandler {
$clientHeight = $params['height'];
$physicalWidth = $params['physicalWidth'];
$physicalHeight = $params['physicalHeight'];
+ $lang = isset( $params['lang'] ) ? $params['lang'] : 'en';
if ( $flags & self::TRANSFORM_LATER ) {
return new ThumbnailImage( $image, $dstUrl, $dstPath, $params );
@@ -132,7 +133,7 @@ class SvgHandler extends ImageHandler {
}
$srcPath = $image->getLocalRefPath();
- $status = $this->rasterize( $srcPath, $dstPath, $physicalWidth, $physicalHeight );
+ $status = $this->rasterize( $srcPath, $dstPath, $physicalWidth, $physicalHeight, $lang );
if ( $status === true ) {
return new ThumbnailImage( $image, $dstUrl, $dstPath, $params );
} else {
@@ -147,10 +148,11 @@ class SvgHandler extends ImageHandler {
* @param string $dstPath
* @param string $width
* @param string $height
+ * @param string $lang Language code of the language to render the SVG in
* @throws MWException
* @return bool|MediaTransformError
*/
- public function rasterize( $srcPath, $dstPath, $width, $height ) {
+ public function rasterize( $srcPath, $dstPath, $width, $height, $lang = false ) {
global $wgSVGConverters, $wgSVGConverter, $wgSVGConverterPath;
$err = false;
$retval = '';
@@ -158,7 +160,7 @@ class SvgHandler extends ImageHandler {
if ( is_array( $wgSVGConverters[$wgSVGConverter] ) ) {
// This is a PHP callable
$func = $wgSVGConverters[$wgSVGConverter][0];
- $args = array_merge( array( $srcPath, $dstPath, $width, $height ),
+ $args = array_merge( array( $srcPath, $dstPath, $width, $height, $lang ),
array_slice( $wgSVGConverters[$wgSVGConverter], 1 ) );
if ( !is_callable( $func ) ) {
throw new MWException( "$func is not callable" );
@@ -175,10 +177,16 @@ class SvgHandler extends ImageHandler {
wfEscapeShellArg( $srcPath ),
wfEscapeShellArg( $dstPath ) ),
$wgSVGConverters[$wgSVGConverter]
- ) . " 2>&1";
+ );
+
+ $env = array();
+ if ( $lang !== false ) {
+ $env['LANG'] = $lang;
+ }
+
wfProfileIn( 'rsvg' );
wfDebug( __METHOD__ . ": $cmd\n" );
- $err = wfShellExec( $cmd, $retval );
+ $err = wfShellExecWithStderr( $cmd, $retval, $env );
wfProfileOut( 'rsvg' );
}
}
@@ -263,11 +271,11 @@ class SvgHandler extends ImageHandler {
$metadata = array( 'version' => self::SVG_METADATA_VERSION );
try {
$metadata += SVGMetadataExtractor::getMetadata( $filename );
- } catch( MWException $e ) { // @TODO: SVG specific exceptions
+ } catch ( MWException $e ) { // @todo SVG specific exceptions
// File not found, broken, etc.
$metadata['error'] = array(
'message' => $e->getMessage(),
- 'code' => $e->getCode()
+ 'code' => $e->getCode()
);
wfDebug( __METHOD__ . ': ' . $e->getMessage() . "\n" );
}
@@ -340,6 +348,7 @@ class SvgHandler extends ImageHandler {
'description' => 'imagedescription',
'title' => 'objectname',
);
+ $showMeta = false;
foreach ( $metadata as $name => $value ) {
$tag = strtolower( $name );
if ( isset( $conversion[$tag] ) ) {
@@ -348,6 +357,7 @@ class SvgHandler extends ImageHandler {
// Do not output other metadata not in list
continue;
}
+ $showMeta = true;
self::addMeta( $result,
in_array( $tag, $visibleFields ) ? 'visible' : 'collapsed',
'exif',
@@ -355,6 +365,70 @@ class SvgHandler extends ImageHandler {
$value
);
}
- return $result;
+ return $showMeta ? $result : false;
+ }
+
+
+ /**
+ * @param string $name Parameter name
+ * @param $string $value Parameter value
+ * @return bool Validity
+ */
+ function validateParam( $name, $value ) {
+ if ( in_array( $name, array( 'width', 'height' ) ) ) {
+ // Reject negative heights, widths
+ return ( $value > 0 );
+ } elseif ( $name == 'lang' ) {
+ // Validate $code
+ if ( !Language::isValidBuiltinCode( $value ) ) {
+ wfDebug( "Invalid user language code\n" );
+ return false;
+ }
+ return true;
+ }
+ // Only lang, width and height are acceptable keys
+ return false;
+ }
+
+ /**
+ * @param array $params name=>value pairs of parameters
+ * @return string Filename to use
+ */
+ function makeParamString( $params ) {
+ $lang = '';
+ if ( isset( $params['lang'] ) && $params['lang'] !== 'en' ) {
+ $params['lang'] = mb_strtolower( $params['lang'] );
+ $lang = "lang{$params['lang']}-";
+ }
+ if ( !isset( $params['width'] ) ) {
+ return false;
+ }
+ return "$lang{$params['width']}px";
+ }
+
+ function parseParamString( $str ) {
+ $m = false;
+ if ( preg_match( '/^lang([a-z]+(?:-[a-z]+)*)-(\d+)px$/', $str, $m ) ) {
+ return array( 'width' => array_pop( $m ), 'lang' => $m[1] );
+ } elseif ( preg_match( '/^(\d+)px$/', $str, $m ) ) {
+ return array( 'width' => $m[1], 'lang' => 'en' );
+ } else {
+ return false;
+ }
+ }
+
+ function getParamMap() {
+ return array( 'img_lang' => 'lang', 'img_width' => 'width' );
+ }
+
+ /**
+ * @param $params
+ * @return array
+ */
+ function getScriptParams( $params ) {
+ return array(
+ 'width' => $params['width'],
+ 'lang' => $params['lang'],
+ );
}
}
diff --git a/includes/media/SVGMetadataExtractor.php b/includes/media/SVGMetadataExtractor.php
index 0de212b9..2e33bb98 100644
--- a/includes/media/SVGMetadataExtractor.php
+++ b/includes/media/SVGMetadataExtractor.php
@@ -101,7 +101,7 @@ class SVGReader {
wfSuppressWarnings();
try {
$this->read();
- } catch( Exception $e ) {
+ } catch ( Exception $e ) {
// Note, if this happens, the width/height will be taken to be 0x0.
// Should we consider it the default 512x512 instead?
wfRestoreWarnings();
@@ -128,12 +128,12 @@ class SVGReader {
$keepReading = $this->reader->read();
/* Skip until first element */
- while( $keepReading && $this->reader->nodeType != XmlReader::ELEMENT ) {
+ while ( $keepReading && $this->reader->nodeType != XmlReader::ELEMENT ) {
$keepReading = $this->reader->read();
}
if ( $this->reader->localName != 'svg' || $this->reader->namespaceURI != self::NS_SVG ) {
- throw new MWException( "Expected <svg> tag, got ".
+ throw new MWException( "Expected <svg> tag, got " .
$this->reader->localName . " in NS " . $this->reader->namespaceURI );
}
$this->debug( "<svg> tag is correct." );
@@ -144,7 +144,7 @@ class SVGReader {
while ( $keepReading ) {
$tag = $this->reader->localName;
$type = $this->reader->nodeType;
- $isSVG = ($this->reader->namespaceURI == self::NS_SVG);
+ $isSVG = ( $this->reader->namespaceURI == self::NS_SVG );
$this->debug( "$tag" );
@@ -185,16 +185,16 @@ class SVGReader {
* @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" );
- if( !$metafield || $this->reader->nodeType != XmlReader::ELEMENT ) {
+ private function readField( $name, $metafield = null ) {
+ $this->debug( "Read field $metafield" );
+ if ( !$metafield || $this->reader->nodeType != XmlReader::ELEMENT ) {
return;
}
$keepReading = $this->reader->read();
- while( $keepReading ) {
- if( $this->reader->localName == $name && $this->reader->namespaceURI == self::NS_SVG && $this->reader->nodeType == XmlReader::END_ELEMENT ) {
+ 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();
@@ -207,13 +207,13 @@ class SVGReader {
* @param string $metafield that we will fill with the result
* @throws MWException
*/
- private function readXml( $metafield=null ) {
- $this->debug ( "Read top level metadata" );
- if( !$metafield || $this->reader->nodeType != XmlReader::ELEMENT ) {
+ private function readXml( $metafield = null ) {
+ $this->debug( "Read top level metadata" );
+ if ( !$metafield || $this->reader->nodeType != XmlReader::ELEMENT ) {
return;
}
// TODO: find and store type of xml snippet. metadata['metadataType'] = "rdf"
- if( method_exists( $this->reader, 'readInnerXML' ) ) {
+ if ( method_exists( $this->reader, 'readInnerXML' ) ) {
$this->metadata[$metafield] = trim( $this->reader->readInnerXML() );
} else {
throw new MWException( "The PHP XMLReader extension does not come with readInnerXML() method. Your libxml is probably out of date (need 2.6.20 or later)." );
@@ -227,8 +227,8 @@ class SVGReader {
* @param string $name of the element that we are reading from
*/
private function animateFilter( $name ) {
- $this->debug ( "animate filter for tag $name" );
- if( $this->reader->nodeType != XmlReader::ELEMENT ) {
+ $this->debug( "animate filter for tag $name" );
+ if ( $this->reader->nodeType != XmlReader::ELEMENT ) {
return;
}
if ( $this->reader->isEmptyElement ) {
@@ -236,12 +236,12 @@ class SVGReader {
}
$exitDepth = $this->reader->depth;
$keepReading = $this->reader->read();
- while( $keepReading ) {
- if( $this->reader->localName == $name && $this->reader->depth <= $exitDepth
+ while ( $keepReading ) {
+ if ( $this->reader->localName == $name && $this->reader->depth <= $exitDepth
&& $this->reader->nodeType == XmlReader::END_ELEMENT ) {
break;
} elseif ( $this->reader->namespaceURI == self::NS_SVG && $this->reader->nodeType == XmlReader::ELEMENT ) {
- switch( $this->reader->localName ) {
+ switch ( $this->reader->localName ) {
case 'script':
// Normally we disallow files with
// <script>, but its possible
@@ -267,7 +267,7 @@ class SVGReader {
}
private function debug( $data ) {
- if( $this->mDebug ) {
+ if ( $this->mDebug ) {
wfDebug( "SVGReader: $data\n" );
}
}
@@ -292,37 +292,37 @@ class SVGReader {
$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' ) ) );
- if( count( $viewBox ) == 4 ) {
+ if ( count( $viewBox ) == 4 ) {
$viewWidth = $this->scaleSVGUnit( $viewBox[2] );
$viewHeight = $this->scaleSVGUnit( $viewBox[3] );
- if( $viewWidth > 0 && $viewHeight > 0 ) {
+ if ( $viewWidth > 0 && $viewHeight > 0 ) {
$aspect = $viewWidth / $viewHeight;
$defaultHeight = $defaultWidth / $aspect;
}
}
}
- if( $this->reader->getAttribute( 'width' ) ) {
+ 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' ) ) {
+ if ( $this->reader->getAttribute( 'height' ) ) {
$height = $this->scaleSVGUnit( $this->reader->getAttribute( 'height' ), $defaultHeight );
$this->metadata['originalHeight'] = $this->reader->getAttribute( 'height' );
}
- if( !isset( $width ) && !isset( $height ) ) {
+ if ( !isset( $width ) && !isset( $height ) ) {
$width = $defaultWidth;
$height = $width / $aspect;
- } elseif( isset( $width ) && !isset( $height ) ) {
+ } elseif ( isset( $width ) && !isset( $height ) ) {
$height = $width / $aspect;
- } elseif( isset( $height ) && !isset( $width ) ) {
+ } elseif ( isset( $height ) && !isset( $width ) ) {
$width = $height * $aspect;
}
- if( $width > 0 && $height > 0 ) {
+ if ( $width > 0 && $height > 0 ) {
$this->metadata['width'] = intval( round( $width ) );
$this->metadata['height'] = intval( round( $height ) );
}
@@ -346,13 +346,13 @@ class SVGReader {
'in' => 90.0,
'em' => 16.0, // fake it?
'ex' => 12.0, // fake it?
- '' => 1.0, // "User units" pixels by default
+ '' => 1.0, // "User units" pixels by default
);
$matches = array();
- if( preg_match( '/^\s*(\d+(?:\.\d+)?)(em|ex|px|pt|pc|cm|mm|in|%|)\s*$/', $length, $matches ) ) {
+ if ( preg_match( '/^\s*(\d+(?:\.\d+)?)(em|ex|px|pt|pc|cm|mm|in|%|)\s*$/', $length, $matches ) ) {
$length = floatval( $matches[1] );
$unit = $matches[2];
- if( $unit == '%' ) {
+ if ( $unit == '%' ) {
return $length * 0.01 * $viewportSize;
} else {
return $length * $unitLength[$unit];
diff --git a/includes/media/Tiff.php b/includes/media/Tiff.php
index 0042208b..55acb120 100644
--- a/includes/media/Tiff.php
+++ b/includes/media/Tiff.php
@@ -43,7 +43,7 @@ class TiffHandler extends ExifBitmapHandler {
function canRender( $file ) {
global $wgTiffThumbnailType;
return (bool)$wgTiffThumbnailType
- || ($file->getRepo() instanceof ForeignAPIRepo);
+ || $file->getRepo() instanceof ForeignAPIRepo;
}
/**
diff --git a/includes/media/XCF.php b/includes/media/XCF.php
index ba38d158..e77d3842 100644
--- a/includes/media/XCF.php
+++ b/includes/media/XCF.php
@@ -78,7 +78,7 @@ class XCFHandler extends BitmapHandler {
static function getXCFMetaData( $filename ) {
# Decode master structure
$f = fopen( $filename, 'rb' );
- if( !$f ) {
+ if ( !$f ) {
return false;
}
# The image structure always starts at offset 0 in the XCF file.
@@ -110,12 +110,12 @@ class XCFHandler extends BitmapHandler {
. "/Nbase_type" # /
, $binaryHeader
);
- } catch( MWException $mwe ) {
+ } catch ( MWException $mwe ) {
return false;
}
# Check values
- if( $header['magic'] !== 'gimp xcf' ) {
+ if ( $header['magic'] !== 'gimp xcf' ) {
wfDebug( __METHOD__ . " '$filename' has invalid magic signature.\n" );
return false;
}
diff --git a/includes/media/XMP.php b/includes/media/XMP.php
index 62738a00..7eb3d19e 100644
--- a/includes/media/XMP.php
+++ b/includes/media/XMP.php
@@ -180,12 +180,12 @@ class XMPReader {
) {
// the is_array is just paranoia. It should always
// be an array.
- foreach( $data['xmp-special']['LocationShown'] as $loc ) {
+ foreach ( $data['xmp-special']['LocationShown'] as $loc ) {
if ( !is_array( $loc ) ) {
// To avoid copying over the _type meta-fields.
continue;
}
- foreach( $loc as $field => $val ) {
+ foreach ( $loc as $field => $val ) {
$data['xmp-general'][$field . 'Dest'][] = $val;
}
}
@@ -195,12 +195,12 @@ class XMPReader {
) {
// the is_array is just paranoia. It should always
// be an array.
- foreach( $data['xmp-special']['LocationCreated'] as $loc ) {
+ foreach ( $data['xmp-special']['LocationCreated'] as $loc ) {
if ( !is_array( $loc ) ) {
// 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;
}
}
@@ -391,7 +391,9 @@ class XMPReader {
throw new MWException( 'Unexpected character data before first rdf:Description element' );
}
- if ( $this->mode[0] === self::MODE_IGNORE ) return;
+ if ( $this->mode[0] === self::MODE_IGNORE ) {
+ return;
+ }
if ( $this->mode[0] !== self::MODE_SIMPLE
&& $this->mode[0] !== self::MODE_QDESC
@@ -414,7 +416,7 @@ class XMPReader {
*
* @param string $elm Namespace of element followed by a space and then tag name of element.
*/
- private function endElementModeIgnore ( $elm ) {
+ private function endElementModeIgnore( $elm ) {
if ( $this->curItem[0] === $elm ) {
array_shift( $this->curItem );
array_shift( $this->mode );
@@ -436,7 +438,7 @@ class XMPReader {
*
* @param string $elm namespace, space, and tag name.
*/
- private function endElementModeSimple ( $elm ) {
+ private function endElementModeSimple( $elm ) {
if ( $this->charContent !== false ) {
if ( $this->processingArray ) {
// if we're processing an array, use the original element
@@ -639,7 +641,7 @@ class XMPReader {
throw new MWException( "Hit end element </$elm> but no curItem" );
}
- switch( $this->mode[0] ) {
+ switch ( $this->mode[0] ) {
case self::MODE_IGNORE:
$this->endElementModeIgnore( $elm );
break;
@@ -993,15 +995,15 @@ class XMPReader {
if ( $elm !== self::NS_RDF . ' li' ) {
throw new MWException( __METHOD__ . " <rdf:li> expected but got $elm." );
}
- if ( !isset( $attribs[ self::NS_XML . ' lang'] )
- || !preg_match( '/^[-A-Za-z0-9]{2,}$/D', $attribs[ self::NS_XML . ' lang' ] ) )
+ if ( !isset( $attribs[self::NS_XML . ' lang'] )
+ || !preg_match( '/^[-A-Za-z0-9]{2,}$/D', $attribs[self::NS_XML . ' lang'] ) )
{
throw new MWException( __METHOD__
. " <rdf:li> did not contain, or has invalid xml:lang attribute in lang alternative" );
}
// Lang is case-insensitive.
- $this->itemLang = strtolower( $attribs[ self::NS_XML . ' lang' ] );
+ $this->itemLang = strtolower( $attribs[self::NS_XML . ' lang'] );
// need to add curItem[0] on again since one is for the specific item
// and one is for the entire group.
@@ -1058,7 +1060,7 @@ class XMPReader {
. "encountered <$elm> with no mode" );
}
- switch( $this->mode[0] ) {
+ switch ( $this->mode[0] ) {
case self::MODE_IGNORE:
$this->startElementModeIgnore( $elm );
break;
diff --git a/includes/media/XMPInfo.php b/includes/media/XMPInfo.php
index 102547ff..f0b2cb5e 100644
--- a/includes/media/XMPInfo.php
+++ b/includes/media/XMPInfo.php
@@ -31,8 +31,8 @@ class XMPInfo {
/** get the items array
* @return Array XMP item configuration array.
*/
- public static function getItems () {
- if( !self::$ranHooks ) {
+ 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 ) );
@@ -428,7 +428,7 @@ class XMPInfo {
'mode' => XMPReader::MODE_SIMPLE,
'validate' => 'validateClosed',
/* can't use a range, as it skips... */
- 'choices' => array( '0' => true, '1' => true,
+ 'choices' => array( '0' => true, '1' => true,
'2' => true, '3' => true, '4' => true,
'9' => true, '10' => true, '11' => true,
'12' => true, '13' => true,
diff --git a/includes/media/XMPValidate.php b/includes/media/XMPValidate.php
index f98f0b57..87f8abfe 100644
--- a/includes/media/XMPValidate.php
+++ b/includes/media/XMPValidate.php
@@ -101,7 +101,7 @@ class XMPValidate {
$val = null;
return;
} else {
- $nVal = (float) $val;
+ $nVal = (float)$val;
if ( $nVal < 0 ) {
// We do < 0 here instead of < -1 here, since
// the values between 0 and -1 are also illegal
@@ -214,7 +214,7 @@ class XMPValidate {
// this only validates standalone properties, not arrays, etc
return;
}
- if ( !preg_match( '/^[-A-Za-z0-9]{2,}$/D', $val) ) {
+ if ( !preg_match( '/^[-A-Za-z0-9]{2,}$/D', $val ) ) {
//this is a rather naive check.
wfDebugLog( 'XMP', __METHOD__ . " Expected Lang code but got $val" );
$val = null;
@@ -335,7 +335,7 @@ class XMPValidate {
* or DDD,MM.mmk form
* @param $standalone Boolean if its a simple prop (should always be true)
*/
- public static function validateGPS ( $info, &$val, $standalone ) {
+ public static function validateGPS( $info, &$val, $standalone ) {
if ( !$standalone ) {
return;
}
@@ -346,8 +346,8 @@ class XMPValidate {
$val, $m )
) {
$coord = intval( $m[1] );
- $coord += intval( $m[2] ) * (1/60);
- $coord += intval( $m[3] ) * (1/3600);
+ $coord += intval( $m[2] ) * ( 1 / 60 );
+ $coord += intval( $m[3] ) * ( 1 / 3600 );
if ( $m[4] === 'S' || $m[4] === 'W' ) {
$coord = -$coord;
}
@@ -358,7 +358,7 @@ class XMPValidate {
$val, $m )
) {
$coord = intval( $m[1] );
- $coord += floatval( $m[2] ) * (1/60);
+ $coord += floatval( $m[2] ) * ( 1 / 60 );
if ( $m[3] === 'S' || $m[3] === 'W' ) {
$coord = -$coord;
}
diff --git a/includes/mime.info b/includes/mime.info
index f7576e41..c7981871 100644
--- a/includes/mime.info
+++ b/includes/mime.info
@@ -30,6 +30,7 @@ application/x-dia-diagram [DRAWING]
audio/mpeg audio/mp3 audio/mpeg3 [AUDIO]
+audio/mp4 [AUDIO]
audio/wav audio/x-wav audio/wave [AUDIO]
audio/midi audio/mid [AUDIO]
audio/basic [AUDIO]
@@ -39,6 +40,8 @@ audio/x-pn-realaudio [AUDIO]
audio/x-realaudio [AUDIO]
audio/webm [AUDIO]
audio/x-matroska [AUDIO]
+audio/x-flac [AUDIO]
+audio/flac [AUDIO]
video/mpeg application/mpeg [VIDEO]
video/ogg [VIDEO]
@@ -46,6 +49,7 @@ video/x-sgi-video [VIDEO]
video/x-flv [VIDEO]
video/webm [VIDEO]
video/x-matroska [VIDEO]
+video/mp4 [VIDEO]
application/ogg application/x-ogg audio/ogg audio/x-ogg video/ogg video/x-ogg [MULTIMEDIA]
diff --git a/includes/mime.types b/includes/mime.types
index a89d229a..61f7ff58 100644
--- a/includes/mime.types
+++ b/includes/mime.types
@@ -75,6 +75,9 @@ audio/x-pn-realaudio ram rm
audio/x-pn-realaudio-plugin rpm
audio/x-realaudio ra
audio/x-wav wav
+audio/wav wav
+audio/x-flac flac
+audio/flac flac
chemical/x-pdb pdb
chemical/x-xyz xyz
image/bmp bmp
@@ -118,6 +121,8 @@ text/xml xml xsl xslt rss rdf
text/x-setext etx
text/x-sawfish jl
video/mpeg mpeg mpg mpe
+video/mp4 mp4 m4a m4p m4b m4r m4v
+audio/mp4 m4a
video/ogg ogv ogm ogg
video/quicktime qt mov
video/vnd.mpegurl mxu
diff --git a/includes/normal/README b/includes/normal/README
index a17aa7da..0f718d2c 100644
--- a/includes/normal/README
+++ b/includes/normal/README
@@ -48,12 +48,12 @@ grains of salt.
There's an experimental PHP extension module which wraps the ICU library's
normalization functions. This is *MUCH* faster than doing this work in pure
-PHP code. This is in the 'normal' directory in MediaWiki's CVS extensions
-module. It is known to work with PHP 4.3.8 and 5.0.2 on Linux/x86 but hasn't
-been thoroughly tested on other configurations.
+PHP code. This is at https://git.wikimedia.org/summary/mediawiki%2Fextensions%2Fnormal.git.
+It is used by the WMF, which currently runs PHP 5.3.10 on Linux. It hasn't been
+thoroughly tested on other configurations, but may work.
If the php_normal.so module is loaded in php.ini, the normalization functions
will automatically use it. If you can't (or don't want to) load it in php.ini,
-you may be able to load it using the dl() function before include()ing or
-require()ing UtfNormal.php, and it will be picked up.
+you may be able to load it using the dl() function before the inclusion of
+UtfNormal.php, and it will be picked up.
diff --git a/includes/normal/RandomTest.php b/includes/normal/RandomTest.php
index 9dc1c861..06029868 100644
--- a/includes/normal/RandomTest.php
+++ b/includes/normal/RandomTest.php
@@ -31,10 +31,10 @@ if( PHP_SAPI != 'cli' ) {
}
/** */
-require_once( 'UtfNormal.php' );
-require_once( '../diff/DifferenceEngine.php' );
+require_once 'UtfNormal.php';
+require_once '../diff/DifferenceEngine.php';
-dl('php_utfnormal.so' );
+dl( 'php_utfnormal.so' );
# mt_srand( 99999 );
diff --git a/includes/normal/UtfNormal.php b/includes/normal/UtfNormal.php
index 77ddb79b..5a091afc 100644
--- a/includes/normal/UtfNormal.php
+++ b/includes/normal/UtfNormal.php
@@ -190,7 +190,7 @@ class UtfNormal {
*/
static function loadData() {
if( !isset( self::$utfCombiningClass ) ) {
- require_once( __DIR__ . '/UtfNormalData.inc' );
+ require_once __DIR__ . '/UtfNormalData.inc';
}
}
@@ -491,7 +491,7 @@ class UtfNormal {
*/
static function NFKD( $string ) {
if( !isset( self::$utfCompatibilityDecomp ) ) {
- require_once( 'UtfNormalDataK.inc' );
+ require_once 'UtfNormalDataK.inc';
}
return self::fastCombiningSort(
self::fastDecompose( $string, self::$utfCompatibilityDecomp ) );
diff --git a/includes/normal/UtfNormalTest.php b/includes/normal/UtfNormalTest.php
index 661e53fd..51183666 100644
--- a/includes/normal/UtfNormalTest.php
+++ b/includes/normal/UtfNormalTest.php
@@ -34,9 +34,7 @@ $verbose = true;
if( defined( 'PRETTY_UTF8' ) ) {
function pretty( $string ) {
- return preg_replace( '/([\x00-\xff])/e',
- 'sprintf("%02X", ord("$1"))',
- $string );
+ return strtoupper( bin2hex( $string ) );
}
} else {
/**
@@ -44,9 +42,7 @@ if( defined( 'PRETTY_UTF8' ) ) {
* @return string
*/
function pretty( $string ) {
- return trim( preg_replace( '/(.)/use',
- 'sprintf("%04X ", utf8ToCodepoint("$1"))',
- $string ) );
+ return strtoupper( utf8ToHexSequence( $string ) );
}
}
diff --git a/includes/normal/UtfNormalTest2.php b/includes/normal/UtfNormalTest2.php
index 2266696e..750c0099 100644
--- a/includes/normal/UtfNormalTest2.php
+++ b/includes/normal/UtfNormalTest2.php
@@ -65,7 +65,7 @@ $f = fopen($file, "r");
later and slow down the runtime.
*/
-require_once("./UtfNormal.php");
+require_once './UtfNormal.php';
function normalize_form_c($c) { return UtfNormal::toNFC($c); }
function normalize_form_d($c) { return UtfNormal::toNFD($c); }
function normalize_form_kc($c) { return UtfNormal::toNFKC($c); }
diff --git a/includes/normal/UtfNormalUtil.php b/includes/normal/UtfNormalUtil.php
index 9b96a073..e8fec936 100644
--- a/includes/normal/UtfNormalUtil.php
+++ b/includes/normal/UtfNormalUtil.php
@@ -76,9 +76,11 @@ function hexSequenceToUtf8( $sequence ) {
* @private
*/
function utf8ToHexSequence( $str ) {
- return rtrim( preg_replace( '/(.)/uSe',
- 'sprintf("%04x ", utf8ToCodepoint("$1"))',
- $str ) );
+ $buf = '';
+ foreach ( preg_split( '//u', $str, -1, PREG_SPLIT_NO_EMPTY ) as $cp ) {
+ $buf .= sprintf( '%04x ', utf8ToCodepoint( $cp ) );
+ }
+ return rtrim( $buf );
}
/**
diff --git a/includes/objectcache/BagOStuff.php b/includes/objectcache/BagOStuff.php
index dd744672..857943ee 100644
--- a/includes/objectcache/BagOStuff.php
+++ b/includes/objectcache/BagOStuff.php
@@ -170,12 +170,12 @@ abstract class BagOStuff {
*/
public function lock( $key, $timeout = 60 ) {
$timestamp = microtime( true ); // starting UNIX timestamp
- if ( $this->add( "{$key}:lock", $timeout ) ) {
+ if ( $this->add( "{$key}:lock", 1, $timeout ) ) {
return true;
}
$uRTT = ceil( 1e6 * ( microtime( true ) - $timestamp ) ); // estimate RTT (us)
- $sleep = 2*$uRTT; // rough time to do get()+set()
+ $sleep = 2 * $uRTT; // rough time to do get()+set()
$locked = false; // lock acquired
$attempts = 0; // failed attempts
@@ -186,8 +186,8 @@ abstract class BagOStuff {
$sleep *= 2;
}
usleep( $sleep ); // back off
- $locked = $this->add( "{$key}:lock", $timeout );
- } while( !$locked );
+ $locked = $this->add( "{$key}:lock", 1, $timeout );
+ } while ( !$locked );
return $locked;
}
diff --git a/includes/objectcache/MemcachedBagOStuff.php b/includes/objectcache/MemcachedBagOStuff.php
index 3f1fa3a0..f1644edb 100644
--- a/includes/objectcache/MemcachedBagOStuff.php
+++ b/includes/objectcache/MemcachedBagOStuff.php
@@ -43,7 +43,7 @@ class MemcachedBagOStuff extends BagOStuff {
if ( !isset( $params['persistent'] ) ) {
$params['persistent'] = $GLOBALS['wgMemCachedPersistent'];
}
- if ( !isset( $params['compress_threshold'] ) ) {
+ if ( !isset( $params['compress_threshold'] ) ) {
$params['compress_threshold'] = 1500;
}
if ( !isset( $params['timeout'] ) ) {
diff --git a/includes/objectcache/MemcachedClient.php b/includes/objectcache/MemcachedClient.php
index 0d96ed6c..e5f60b55 100644
--- a/includes/objectcache/MemcachedClient.php
+++ b/includes/objectcache/MemcachedClient.php
@@ -55,9 +55,9 @@
* 'compress_threshold' => 10240,
* 'persistent' => true));
*
- * $mc->add('key', array('some', 'array'));
- * $mc->replace('key', 'some random string');
- * $val = $mc->get('key');
+ * $mc->add( 'key', array( 'some', 'array' ) );
+ * $mc->replace( 'key', 'some random string' );
+ * $val = $mc->get( 'key' );
*
* @author Ryan T. Dean <rtdean@cytherianage.net>
* @version 0.1.2
@@ -329,7 +329,7 @@ class MWMemcached {
$this->stats['delete'] = 1;
}
$cmd = "delete $key $time\r\n";
- if( !$this->_fwrite( $sock, $cmd ) ) {
+ if ( !$this->_fwrite( $sock, $cmd ) ) {
return false;
}
$res = $this->_fgets( $sock );
@@ -489,17 +489,17 @@ class MWMemcached {
}
$key = is_array( $key ) ? $key[1] : $key;
if ( !isset( $sock_keys[$sock] ) ) {
- $sock_keys[ intval( $sock ) ] = array();
+ $sock_keys[intval( $sock )] = array();
$socks[] = $sock;
}
- $sock_keys[ intval( $sock ) ][] = $key;
+ $sock_keys[intval( $sock )][] = $key;
}
$gather = array();
// Send out the requests
foreach ( $socks as $sock ) {
$cmd = 'gets';
- foreach ( $sock_keys[ intval( $sock ) ] as $key ) {
+ foreach ( $sock_keys[intval( $sock )] as $key ) {
$cmd .= ' ' . $key;
}
$cmd .= "\r\n";
@@ -733,7 +733,7 @@ class MWMemcached {
$sock = false;
$timeout = $this->_connect_timeout;
$errno = $errstr = null;
- for( $i = 0; !$sock && $i < $this->_connect_attempts; $i++ ) {
+ for ( $i = 0; !$sock && $i < $this->_connect_attempts; $i++ ) {
wfSuppressWarnings();
if ( $this->_persistent == 1 ) {
$sock = pfsockopen( $ip, $port, $errno, $errstr, $timeout );
@@ -810,7 +810,7 @@ class MWMemcached {
$bu = array();
foreach ( $this->_servers as $v ) {
if ( is_array( $v ) ) {
- for( $i = 0; $i < $v[1]; $i++ ) {
+ for ( $i = 0; $i < $v[1]; $i++ ) {
$bu[] = $v[0];
}
} else {
@@ -822,7 +822,7 @@ class MWMemcached {
}
$realkey = is_array( $key ) ? $key[1] : $key;
- for( $tries = 0; $tries < 20; $tries++ ) {
+ for ( $tries = 0; $tries < 20; $tries++ ) {
$host = $this->_buckets[$hv % $this->_bucketcount];
$sock = $this->sock_to_host( $host );
if ( is_resource( $sock ) ) {
@@ -912,7 +912,7 @@ class MWMemcached {
while ( 1 ) {
$decl = $this->_fgets( $sock );
- if( $decl === false ) {
+ 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.
@@ -1123,7 +1123,7 @@ class MWMemcached {
function _fwrite( $sock, $buf ) {
$bytesWritten = 0;
$bufSize = strlen( $buf );
- while ( $bytesWritten < $bufSize ) {
+ while ( $bytesWritten < $bufSize ) {
$result = fwrite( $sock, $buf );
$data = stream_get_meta_data( $sock );
if ( $data['timed_out'] ) {
diff --git a/includes/objectcache/MemcachedPeclBagOStuff.php b/includes/objectcache/MemcachedPeclBagOStuff.php
index 31924293..0c3b228f 100644
--- a/includes/objectcache/MemcachedPeclBagOStuff.php
+++ b/includes/objectcache/MemcachedPeclBagOStuff.php
@@ -37,6 +37,8 @@ class MemcachedPeclBagOStuff extends MemcachedBagOStuff {
* - compress_threshold: The minimum size an object must be before it is compressed
* - timeout: The read timeout in microseconds
* - connect_timeout: The connect timeout in seconds
+ * - retry_timeout: Time in seconds to wait before retrying a failed connect attempt
+ * - server_failure_limit: Limit for server connect failures before it is removed
* - serializer: May be either "php" or "igbinary". Igbinary produces more compact
* values, but serialization is much slower unless the php.ini option
* igbinary.compact_strings is off.
@@ -61,6 +63,14 @@ class MemcachedPeclBagOStuff extends MemcachedBagOStuff {
$params['serializer'] = 'php';
}
+ if ( isset( $params['retry_timeout'] ) ) {
+ $this->client->setOption( Memcached::OPT_RETRY_TIMEOUT, $params['retry_timeout'] );
+ }
+
+ if ( isset( $params['server_failure_limit'] ) ) {
+ $this->client->setOption( Memcached::OPT_SERVER_FAILURE_LIMIT, $params['server_failure_limit'] );
+ }
+
// The compression threshold is an undocumented php.ini option for some
// reason. There's probably not much harm in setting it globally, for
// compatibility with the settings for the PHP client.
@@ -87,13 +97,13 @@ class MemcachedPeclBagOStuff extends MemcachedBagOStuff {
break;
case 'igbinary':
if ( !Memcached::HAVE_IGBINARY ) {
- throw new MWException( __CLASS__.': the igbinary extension is not available ' .
+ throw new MWException( __CLASS__ . ': the igbinary extension is not available ' .
'but igbinary serialization was requested.' );
}
$this->client->setOption( Memcached::OPT_SERIALIZER, Memcached::SERIALIZER_IGBINARY );
break;
default:
- throw new MWException( __CLASS__.': invalid value for serializer parameter' );
+ throw new MWException( __CLASS__ . ': invalid value for serializer parameter' );
}
$servers = array();
foreach ( $params['servers'] as $host ) {
diff --git a/includes/objectcache/MultiWriteBagOStuff.php b/includes/objectcache/MultiWriteBagOStuff.php
index 92afaacd..e550c0d0 100644
--- a/includes/objectcache/MultiWriteBagOStuff.php
+++ b/includes/objectcache/MultiWriteBagOStuff.php
@@ -43,7 +43,7 @@ class MultiWriteBagOStuff extends BagOStuff {
*/
public function __construct( $params ) {
if ( !isset( $params['caches'] ) ) {
- throw new MWException( __METHOD__.': the caches parameter is required' );
+ throw new MWException( __METHOD__ . ': the caches parameter is required' );
}
$this->caches = array();
diff --git a/includes/objectcache/ObjectCache.php b/includes/objectcache/ObjectCache.php
index eafa836a..6c1433a9 100644
--- a/includes/objectcache/ObjectCache.php
+++ b/includes/objectcache/ObjectCache.php
@@ -34,7 +34,7 @@ class ObjectCache {
*
* @param $id string
*
- * @return ObjectCache
+ * @return BagOStuff
*/
static function getInstance( $id ) {
if ( isset( self::$instances[$id] ) ) {
@@ -59,7 +59,7 @@ class ObjectCache {
* @param $id string
*
* @throws MWException
- * @return ObjectCache
+ * @return BagOStuff
*/
static function newFromId( $id ) {
global $wgObjectCaches;
@@ -78,7 +78,7 @@ class ObjectCache {
* @param $params array
*
* @throws MWException
- * @return ObjectCache
+ * @return BagOStuff
*/
static function newFromParams( $params ) {
if ( isset( $params['factory'] ) ) {
@@ -102,7 +102,7 @@ class ObjectCache {
* If no cache choice is configured (by default $wgMainCacheType is CACHE_NONE),
* then CACHE_ANYTHING will forward to CACHE_DB.
* @param $params array
- * @return ObjectCache
+ * @return BagOStuff
*/
static function newAnything( $params ) {
global $wgMainCacheType, $wgMessageCacheType, $wgParserCacheType;
@@ -120,14 +120,14 @@ class ObjectCache {
*
* @param $params array
* @throws MWException
- * @return ObjectCache
+ * @return BagOStuff
*/
static function newAccelerator( $params ) {
if ( function_exists( 'apc_fetch' ) ) {
$id = 'apc';
- } elseif( function_exists( 'xcache_get' ) && wfIniGetBool( 'xcache.var_size' ) ) {
+ } elseif ( function_exists( 'xcache_get' ) && wfIniGetBool( 'xcache.var_size' ) ) {
$id = 'xcache';
- } elseif( function_exists( 'wincache_ucache_get' ) ) {
+ } elseif ( function_exists( 'wincache_ucache_get' ) ) {
$id = 'wincache';
} else {
throw new MWException( "CACHE_ACCEL requested but no suitable object " .
diff --git a/includes/objectcache/ObjectCacheSessionHandler.php b/includes/objectcache/ObjectCacheSessionHandler.php
index bc76294a..7cf960e7 100644
--- a/includes/objectcache/ObjectCacheSessionHandler.php
+++ b/includes/objectcache/ObjectCacheSessionHandler.php
@@ -94,7 +94,7 @@ class ObjectCacheSessionHandler {
*/
static function read( $id ) {
$data = self::getCache()->get( self::getKey( $id ) );
- if( $data === false ) {
+ if ( $data === false ) {
return '';
}
return $data;
diff --git a/includes/objectcache/RedisBagOStuff.php b/includes/objectcache/RedisBagOStuff.php
index f9feaf9d..135e0e83 100644
--- a/includes/objectcache/RedisBagOStuff.php
+++ b/includes/objectcache/RedisBagOStuff.php
@@ -140,7 +140,13 @@ class RedisBagOStuff extends BagOStuff {
$conn->setex( $key, $expiry, $value );
}
- $result = $conn->exec();
+ /*
+ * multi()/exec() (transactional mode) allows multiple values to
+ * be set/get at once and will return an array of results, in
+ * the order they were set/get. In this case, we only set 1
+ * value, which should (in case of success) result in true.
+ */
+ $result = ( $conn->exec() == array( true ) );
} catch ( RedisException $e ) {
$result = false;
$this->handleException( $server, $conn, $e );
@@ -268,38 +274,6 @@ class RedisBagOStuff extends BagOStuff {
}
/**
- * Non-atomic implementation of incr().
- *
- * Probably all callers actually want incr() to atomically initialise
- * values to zero if they don't exist, as provided by the Redis INCR
- * command. But we are constrained by the memcached-like interface to
- * return null in that case. Once the key exists, further increments are
- * atomic.
- */
- public function incr( $key, $value = 1 ) {
- wfProfileIn( __METHOD__ );
- list( $server, $conn ) = $this->getConnection( $key );
- if ( !$conn ) {
- wfProfileOut( __METHOD__ );
- return false;
- }
- if ( !$conn->exists( $key ) ) {
- wfProfileOut( __METHOD__ );
- return null;
- }
- try {
- $result = $conn->incrBy( $key, $value );
- } catch ( RedisException $e ) {
- $result = false;
- $this->handleException( $server, $conn, $e );
- }
-
- $this->logRequest( 'incr', $key, $server, $result );
- wfProfileOut( __METHOD__ );
- return $result;
- }
-
- /**
* Get a Redis object with a connection suitable for fetching the specified key
* @return Array (server, RedisConnRef) or (false, false)
*/
diff --git a/includes/objectcache/SqlBagOStuff.php b/includes/objectcache/SqlBagOStuff.php
index 87f787d8..acf27036 100644
--- a/includes/objectcache/SqlBagOStuff.php
+++ b/includes/objectcache/SqlBagOStuff.php
@@ -222,8 +222,8 @@ class SqlBagOStuff extends BagOStuff {
$dataRows = array();
foreach ( $keysByTable as $serverIndex => $serverKeys ) {
- $db = $this->getDB( $serverIndex );
try {
+ $db = $this->getDB( $serverIndex );
foreach ( $serverKeys as $tableName => $tableKeys ) {
$res = $db->select( $tableName,
array( 'keyname', 'value', 'exptime' ),
@@ -244,10 +244,10 @@ class SqlBagOStuff extends BagOStuff {
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 {
+ try {
+ $db = $this->getDB( $row->serverIndex );
+ if ( $this->isExpired( $db, $row->exptime ) ) { // MISS
+ $this->debug( "get: key has expired, deleting" );
$db->begin( __METHOD__ );
# Put the expiry time in the WHERE condition to avoid deleting a
# newly-inserted value
@@ -255,12 +255,12 @@ class SqlBagOStuff extends BagOStuff {
array( 'keyname' => $key, 'exptime' => $row->exptime ),
__METHOD__ );
$db->commit( __METHOD__ );
- } catch ( DBQueryError $e ) {
- $this->handleWriteError( $e, $row->serverIndex );
+ $values[$key] = false;
+ } else { // HIT
+ $values[$key] = $this->unserialize( $db->decodeBlob( $row->value ) );
}
- $values[$key] = false;
- } else { // HIT
- $values[$key] = $this->unserialize( $db->decodeBlob( $row->value ) );
+ } catch ( DBQueryError $e ) {
+ $this->handleWriteError( $e, $row->serverIndex );
}
} else { // MISS
$values[$key] = false;
@@ -364,7 +364,7 @@ class SqlBagOStuff extends BagOStuff {
return false;
}
- return (bool) $db->affectedRows();
+ return (bool)$db->affectedRows();
}
/**
diff --git a/includes/parser/CacheTime.php b/includes/parser/CacheTime.php
index 6b70e1da..8190a8a0 100644
--- a/includes/parser/CacheTime.php
+++ b/includes/parser/CacheTime.php
@@ -93,7 +93,7 @@ class CacheTime {
$expire = min( $expire, $wgParserCacheExpireTime );
}
- if( $this->containsOldMagic() ) { //compatibility hack
+ if ( $this->containsOldMagic() ) { //compatibility hack
$expire = min( $expire, 3600 ); # 1 hour
}
diff --git a/includes/parser/CoreLinkFunctions.php b/includes/parser/CoreLinkFunctions.php
deleted file mode 100644
index 1cabf766..00000000
--- a/includes/parser/CoreLinkFunctions.php
+++ /dev/null
@@ -1,92 +0,0 @@
-<?php
-/**
- * Link functions provided by MediaWiki core; experimental
- *
- * 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
- */
-
-/**
- * Various core link functions, registered in Parser::firstCallInit()
- * @ingroup Parser
- */
-class CoreLinkFunctions {
- /**
- * @param $parser Parser_LinkHooks
- * @return bool
- */
- static function register( $parser ) {
- $parser->setLinkHook( NS_CATEGORY, array( __CLASS__, 'categoryLinkHook' ) );
- return true;
- }
-
- /**
- * @param $parser Parser
- * @param $holders LinkHolderArray
- * @param $markers LinkMarkerReplacer
- * @param Title $title
- * @param $titleText
- * @param null $displayText
- * @param bool $leadingColon
- * @return bool
- */
- static function defaultLinkHook( $parser, $holders, $markers,
- Title $title, $titleText, &$displayText = null, &$leadingColon = false ) {
- 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 );
- # Return false so that this link is reverted back to WikiText
- return false;
- }
- return $holders->makeHolder( $title, isset( $displayText ) ? $displayText : $titleText, array(), '', '' );
- }
-
- /**
- * @param $parser Parser
- * @param $holders LinkHolderArray
- * @param $markers LinkMarkerReplacer
- * @param Title $title
- * @param $titleText
- * @param null $sortText
- * @param bool $leadingColon
- * @return bool|string
- */
- static function categoryLinkHook( $parser, $holders, $markers,
- Title $title, $titleText, &$sortText = null, &$leadingColon = false ) {
- 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 ) ) {
- # 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 );
- # Return false so that this link is reverted back to WikiText
- return false;
- }
- if( !isset( $sortText ) ) $sortText = $parser->getDefaultSort();
- $sortText = Sanitizer::decodeCharReferences( $sortText );
- $sortText = str_replace( "\n", '', $sortText );
- $sortText = $wgContLang->convertCategoryKey( $sortText );
- $parser->mOutput->addCategory( $title->getDBkey(), $sortText );
- return '';
- }
-
-}
diff --git a/includes/parser/CoreParserFunctions.php b/includes/parser/CoreParserFunctions.php
index cdd03aa4..4b6eeca2 100644
--- a/includes/parser/CoreParserFunctions.php
+++ b/includes/parser/CoreParserFunctions.php
@@ -88,6 +88,8 @@ class CoreParserFunctions {
$parser->setFunctionHook( 'pagenamee', array( __CLASS__, 'pagenamee' ), SFH_NO_HASH );
$parser->setFunctionHook( 'fullpagename', array( __CLASS__, 'fullpagename' ), SFH_NO_HASH );
$parser->setFunctionHook( 'fullpagenamee', array( __CLASS__, 'fullpagenamee' ), SFH_NO_HASH );
+ $parser->setFunctionHook( 'rootpagename', array( __CLASS__, 'rootpagename' ), SFH_NO_HASH );
+ $parser->setFunctionHook( 'rootpagenamee', array( __CLASS__, 'rootpagenamee' ), SFH_NO_HASH );
$parser->setFunctionHook( 'basepagename', array( __CLASS__, 'basepagename' ), SFH_NO_HASH );
$parser->setFunctionHook( 'basepagenamee', array( __CLASS__, 'basepagenamee' ), SFH_NO_HASH );
$parser->setFunctionHook( 'subpagename', array( __CLASS__, 'subpagename' ), SFH_NO_HASH );
@@ -137,9 +139,10 @@ class CoreParserFunctions {
$pref = $parser->getOptions()->getDateFormat();
// Specify a different default date format other than the the normal default
- // iff the user has 'default' for their setting
- if ( $pref == 'default' && $defaultPref )
+ // if the user has 'default' for their setting
+ if ( $pref == 'default' && $defaultPref ) {
$pref = $defaultPref;
+ }
$date = $df->reformat( $pref, $date, array( 'match-whole' ) );
return $date;
@@ -184,7 +187,7 @@ class CoreParserFunctions {
if ( is_null( $magicWords ) ) {
$magicWords = new MagicWordArray( array( 'url_path', 'url_query', 'url_wiki' ) );
}
- switch( $magicWords->matchStartToEnd( $arg ) ) {
+ switch ( $magicWords->matchStartToEnd( $arg ) ) {
// Encode as though it's a wiki page, '_' for ' '.
case 'url_wiki':
@@ -248,14 +251,15 @@ class CoreParserFunctions {
# before arriving here; if that's true, then the title can't be created
# and the variable will fail. If we can't get a decent title from the first
# attempt, url-decode and try for a second.
- if( is_null( $title ) )
+ if ( is_null( $title ) ) {
$title = Title::newFromURL( urldecode( $s ) );
- if( !is_null( $title ) ) {
+ }
+ if ( !is_null( $title ) ) {
# Convert NS_MEDIA -> NS_FILE
- if( $title->getNamespace() == NS_MEDIA ) {
+ if ( $title->getNamespace() == NS_MEDIA ) {
$title = Title::makeTitle( NS_FILE, $title->getDBkey() );
}
- if( !is_null( $arg ) ) {
+ if ( !is_null( $arg ) ) {
$text = $title->$func( $arg );
} else {
$text = $title->$func();
@@ -359,30 +363,49 @@ class CoreParserFunctions {
static function displaytitle( $parser, $text = '' ) {
global $wgRestrictDisplayTitle;
- #parse a limited subset of wiki markup (just the single quote items)
+ // parse a limited subset of wiki markup (just the single quote items)
$text = $parser->doQuotes( $text );
- #remove stripped text (e.g. the UNIQ-QINU stuff) that was generated by tag extensions/whatever
+ // remove stripped text (e.g. the UNIQ-QINU stuff) that was generated by tag extensions/whatever
$text = preg_replace( '/' . preg_quote( $parser->uniqPrefix(), '/' ) . '.*?'
. preg_quote( Parser::MARKER_SUFFIX, '/' ) . '/', '', $text );
- #list of disallowed tags for DISPLAYTITLE
- #these will be escaped even though they are allowed in normal wiki text
+ // list of disallowed tags for DISPLAYTITLE
+ // these will be escaped even though they are allowed in normal wiki text
$bad = array( 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'div', 'blockquote', 'ol', 'ul', 'li', 'hr',
'table', 'tr', 'th', 'td', 'dl', 'dd', 'caption', 'p', 'ruby', 'rb', 'rt', 'rp', 'br' );
- #only requested titles that normalize to the actual title are allowed through
- #if $wgRestrictDisplayTitle is true (it is by default)
- #mimic the escaping process that occurs in OutputPage::setPageTitle
- $text = Sanitizer::normalizeCharReferences( Sanitizer::removeHTMLtags( $text, null, array(), array(), $bad ) );
+ // disallow some styles that could be used to bypass $wgRestrictDisplayTitle
+ if ( $wgRestrictDisplayTitle ) {
+ $htmlTagsCallback = function ( &$params ) {
+ $decoded = Sanitizer::decodeTagAttributes( $params );
+
+ if ( isset( $decoded['style'] ) ) {
+ // this is called later anyway, but we need it right now for the regexes below to be safe
+ // calling it twice doesn't hurt
+ $decoded['style'] = Sanitizer::checkCss( $decoded['style'] );
+
+ if ( preg_match( '/(display|user-select|visibility)\s*:/i', $decoded['style'] ) ) {
+ $decoded['style'] = '/* attempt to bypass $wgRestrictDisplayTitle */';
+ }
+ }
+
+ $params = Sanitizer::safeEncodeTagAttributes( $decoded );
+ };
+ } else {
+ $htmlTagsCallback = null;
+ }
+
+ // only requested titles that normalize to the actual title are allowed through
+ // if $wgRestrictDisplayTitle is true (it is by default)
+ // mimic the escaping process that occurs in OutputPage::setPageTitle
+ $text = Sanitizer::normalizeCharReferences( Sanitizer::removeHTMLtags( $text, $htmlTagsCallback, array(), array(), $bad ) );
$title = Title::newFromText( Sanitizer::stripAllTags( $text ) );
- if( !$wgRestrictDisplayTitle ) {
+ if ( !$wgRestrictDisplayTitle ) {
+ $parser->mOutput->setDisplayTitle( $text );
+ } elseif ( $title instanceof Title && $title->getFragment() == '' && $title->equals( $parser->mTitle ) ) {
$parser->mOutput->setDisplayTitle( $text );
- } else {
- if ( $title instanceof Title && $title->getFragment() == '' && $title->equals( $parser->mTitle ) ) {
- $parser->mOutput->setDisplayTitle( $text );
- }
}
return '';
@@ -404,7 +427,7 @@ class CoreParserFunctions {
}
static function formatRaw( $num, $raw ) {
- if( self::matchAgainstMagicword( 'rawsuffix', $raw ) ) {
+ if ( self::matchAgainstMagicword( 'rawsuffix', $raw ) ) {
return $num;
} else {
global $wgContLang;
@@ -433,12 +456,13 @@ class CoreParserFunctions {
return self::formatRaw( SiteStats::edits(), $raw );
}
static function numberofviews( $parser, $raw = null ) {
- return self::formatRaw( SiteStats::views(), $raw );
+ global $wgDisableCounters;
+ return !$wgDisableCounters ? self::formatRaw( SiteStats::views(), $raw ) : '';
}
static function pagesinnamespace( $parser, $namespace = 0, $raw = null ) {
return self::formatRaw( SiteStats::pagesInNs( intval( $namespace ) ), $raw );
}
- static function numberingroup( $parser, $name = '', $raw = null) {
+ static function numberingroup( $parser, $name = '', $raw = null ) {
return self::formatRaw( SiteStats::numberingroup( strtolower( $name ) ), $raw );
}
@@ -451,44 +475,51 @@ class CoreParserFunctions {
*/
static function mwnamespace( $parser, $title = null ) {
$t = Title::newFromText( $title );
- if ( is_null( $t ) )
+ if ( is_null( $t ) ) {
return '';
+ }
return str_replace( '_', ' ', $t->getNsText() );
}
static function namespacee( $parser, $title = null ) {
$t = Title::newFromText( $title );
- if ( is_null( $t ) )
+ if ( is_null( $t ) ) {
return '';
+ }
return wfUrlencode( $t->getNsText() );
}
static function namespacenumber( $parser, $title = null ) {
$t = Title::newFromText( $title );
- if ( is_null( $t ) )
+ if ( is_null( $t ) ) {
return '';
+ }
return $t->getNamespace();
}
static function talkspace( $parser, $title = null ) {
$t = Title::newFromText( $title );
- if ( is_null( $t ) || !$t->canTalk() )
+ if ( is_null( $t ) || !$t->canTalk() ) {
return '';
+ }
return str_replace( '_', ' ', $t->getTalkNsText() );
}
static function talkspacee( $parser, $title = null ) {
$t = Title::newFromText( $title );
- if ( is_null( $t ) || !$t->canTalk() )
+ if ( is_null( $t ) || !$t->canTalk() ) {
return '';
+ }
return wfUrlencode( $t->getTalkNsText() );
}
static function subjectspace( $parser, $title = null ) {
$t = Title::newFromText( $title );
- if ( is_null( $t ) )
+ if ( is_null( $t ) ) {
return '';
+ }
return str_replace( '_', ' ', $t->getSubjectNsText() );
}
static function subjectspacee( $parser, $title = null ) {
$t = Title::newFromText( $title );
- if ( is_null( $t ) )
+ if ( is_null( $t ) ) {
return '';
+ }
return wfUrlencode( $t->getSubjectNsText() );
}
@@ -499,75 +530,101 @@ class CoreParserFunctions {
*/
static function pagename( $parser, $title = null ) {
$t = Title::newFromText( $title );
- if ( is_null( $t ) )
+ if ( is_null( $t ) ) {
return '';
+ }
return wfEscapeWikiText( $t->getText() );
}
static function pagenamee( $parser, $title = null ) {
$t = Title::newFromText( $title );
- if ( is_null( $t ) )
+ if ( is_null( $t ) ) {
return '';
+ }
return wfEscapeWikiText( $t->getPartialURL() );
}
static function fullpagename( $parser, $title = null ) {
$t = Title::newFromText( $title );
- if ( is_null( $t ) || !$t->canTalk() )
+ if ( is_null( $t ) || !$t->canTalk() ) {
return '';
+ }
return wfEscapeWikiText( $t->getPrefixedText() );
}
static function fullpagenamee( $parser, $title = null ) {
$t = Title::newFromText( $title );
- if ( is_null( $t ) || !$t->canTalk() )
+ if ( is_null( $t ) || !$t->canTalk() ) {
return '';
+ }
return wfEscapeWikiText( $t->getPrefixedURL() );
}
static function subpagename( $parser, $title = null ) {
$t = Title::newFromText( $title );
- if ( is_null( $t ) )
+ if ( is_null( $t ) ) {
return '';
+ }
return wfEscapeWikiText( $t->getSubpageText() );
}
static function subpagenamee( $parser, $title = null ) {
$t = Title::newFromText( $title );
- if ( is_null( $t ) )
+ if ( is_null( $t ) ) {
return '';
+ }
return wfEscapeWikiText( $t->getSubpageUrlForm() );
}
+ static function rootpagename( $parser, $title = null ) {
+ $t = Title::newFromText( $title );
+ if ( is_null( $t ) ) {
+ return '';
+ }
+ return wfEscapeWikiText( $t->getRootText() );
+ }
+ static function rootpagenamee( $parser, $title = null ) {
+ $t = Title::newFromText( $title );
+ if ( is_null( $t ) ) {
+ return '';
+ }
+ return wfEscapeWikiText( wfUrlEncode( str_replace( ' ', '_', $t->getRootText() ) ) );
+ }
static function basepagename( $parser, $title = null ) {
$t = Title::newFromText( $title );
- if ( is_null( $t ) )
+ if ( is_null( $t ) ) {
return '';
+ }
return wfEscapeWikiText( $t->getBaseText() );
}
static function basepagenamee( $parser, $title = null ) {
$t = Title::newFromText( $title );
- if ( is_null( $t ) )
+ if ( is_null( $t ) ) {
return '';
+ }
return wfEscapeWikiText( wfUrlEncode( str_replace( ' ', '_', $t->getBaseText() ) ) );
}
static function talkpagename( $parser, $title = null ) {
$t = Title::newFromText( $title );
- if ( is_null( $t ) || !$t->canTalk() )
+ if ( is_null( $t ) || !$t->canTalk() ) {
return '';
+ }
return wfEscapeWikiText( $t->getTalkPage()->getPrefixedText() );
}
static function talkpagenamee( $parser, $title = null ) {
$t = Title::newFromText( $title );
- if ( is_null( $t ) || !$t->canTalk() )
+ if ( is_null( $t ) || !$t->canTalk() ) {
return '';
- return wfEscapeWikiText( $t->getTalkPage()->getPrefixedUrl() );
+ }
+ return wfEscapeWikiText( $t->getTalkPage()->getPrefixedURL() );
}
static function subjectpagename( $parser, $title = null ) {
$t = Title::newFromText( $title );
- if ( is_null( $t ) )
+ if ( is_null( $t ) ) {
return '';
+ }
return wfEscapeWikiText( $t->getSubjectPage()->getPrefixedText() );
}
static function subjectpagenamee( $parser, $title = null ) {
$t = Title::newFromText( $title );
- if ( is_null( $t ) )
+ if ( is_null( $t ) ) {
return '';
- return wfEscapeWikiText( $t->getSubjectPage()->getPrefixedUrl() );
+ }
+ return wfEscapeWikiText( $t->getSubjectPage()->getPrefixedURL() );
}
/**
@@ -577,6 +634,7 @@ class CoreParserFunctions {
* @return string
*/
static function pagesincategory( $parser, $name = '', $arg1 = null, $arg2 = null ) {
+ global $wgContLang;
static $magicWords = null;
if ( is_null( $magicWords ) ) {
$magicWords = new MagicWordArray( array(
@@ -589,7 +647,7 @@ class CoreParserFunctions {
static $cache = array();
// split the given option to its variable
- if( self::matchAgainstMagicword( 'rawsuffix', $arg1 ) ) {
+ if ( self::matchAgainstMagicword( 'rawsuffix', $arg1 ) ) {
//{{pagesincategory:|raw[|type]}}
$raw = $arg1;
$type = $magicWords->matchStartToEnd( $arg2 );
@@ -598,23 +656,24 @@ class CoreParserFunctions {
$type = $magicWords->matchStartToEnd( $arg1 );
$raw = $arg2;
}
- if( !$type ) { //backward compatibility
+ if ( !$type ) { //backward compatibility
$type = 'pagesincategory_all';
}
$title = Title::makeTitleSafe( NS_CATEGORY, $name );
- if( !$title ) { # invalid title
+ if ( !$title ) { # invalid title
return self::formatRaw( 0, $raw );
}
+ $wgContLang->findVariantLink( $name, $title, true );
// Normalize name for cache
$name = $title->getDBkey();
- if( !isset( $cache[$name] ) ) {
+ if ( !isset( $cache[$name] ) ) {
$category = Category::newFromTitle( $title );
$allCount = $subcatCount = $fileCount = $pagesCount = 0;
- if( $parser->incrementExpensiveFunctionCount() ) {
+ if ( $parser->incrementExpensiveFunctionCount() ) {
// $allCount is the total number of cat members,
// not the count of how many members are normal pages.
$allCount = (int)$category->getPageCount();
@@ -636,8 +695,6 @@ class CoreParserFunctions {
* Return the size of the given page, or 0 if it's nonexistent. This is an
* expensive parser function and can't be called too many times per page.
*
- * @todo FIXME: This doesn't work correctly on preview for getting the size
- * of the current page.
* @todo FIXME: Title::getLength() documentation claims that it adds things
* to the link cache, so the local cache here should be unnecessary, but
* in fact calling getLength() repeatedly for the same $page does seem to
@@ -645,15 +702,15 @@ class CoreParserFunctions {
* @todo Document parameters
*
* @param $parser Parser
- * @param string $page TODO DOCUMENT (Default: empty string)
- * @param $raw TODO DOCUMENT (Default: null)
+ * @param $page String Name of page to check (Default: empty string)
+ * @param $raw String Should number be human readable with commas or just number
* @return string
*/
static function pagesize( $parser, $page = '', $raw = null ) {
static $cache = array();
$title = Title::newFromText( $page );
- if( !is_object( $title ) ) {
+ if ( !is_object( $title ) ) {
$cache[$page] = 0;
return self::formatRaw( 0, $raw );
}
@@ -662,9 +719,9 @@ class CoreParserFunctions {
$page = $title->getPrefixedText();
$length = 0;
- if( isset( $cache[$page] ) ) {
+ if ( isset( $cache[$page] ) ) {
$length = $cache[$page];
- } elseif( $parser->incrementExpensiveFunctionCount() ) {
+ } elseif ( $parser->incrementExpensiveFunctionCount() ) {
$rev = Revision::newFromTitle( $title, false, Revision::READ_NORMAL );
$pageID = $rev ? $rev->getPage() : 0;
$revID = $rev ? $rev->getId() : 0;
@@ -717,7 +774,9 @@ class CoreParserFunctions {
static function pad( $parser, $string, $length, $padding = '0', $direction = STR_PAD_RIGHT ) {
$padding = $parser->killMarkers( $padding );
$lengthOfPadding = mb_strlen( $padding );
- if ( $lengthOfPadding == 0 ) return $string;
+ if ( $lengthOfPadding == 0 ) {
+ return $string;
+ }
# The remaining length to add counts down to 0 as padding is added
$length = min( $length, 500 ) - mb_strlen( $string );
@@ -762,7 +821,9 @@ class CoreParserFunctions {
$title = SpecialPage::getTitleFor( $page, $subpage );
return $title->getPrefixedText();
} else {
- return wfMessage( 'nosuchspecialpage' )->inContentLanguage()->text();
+ // unknown special page, just use the given text as its title, if at all possible
+ $title = Title::makeTitleSafe( NS_SPECIAL, $text );
+ return $title ? $title->getPrefixedText() : self::special( $parser, 'Badtitle' );
}
}
@@ -786,19 +847,25 @@ class CoreParserFunctions {
$arg = $magicWords->matchStartToEnd( $uarg );
$text = trim( $text );
- if( strlen( $text ) == 0 )
+ if ( strlen( $text ) == 0 ) {
return '';
+ }
$old = $parser->getCustomDefaultSort();
if ( $old === false || $arg !== 'defaultsort_noreplace' ) {
$parser->setDefaultSort( $text );
}
- if( $old === false || $old == $text || $arg ) {
+ if ( $old === false || $old == $text || $arg ) {
return '';
} else {
- return( '<span class="error">' .
- wfMessage( 'duplicate-defaultsort', $old, $text )->inContentLanguage()->escaped() .
- '</span>' );
+ $converter = $parser->getConverterLanguage()->getConverter();
+ return '<span class="error">' .
+ wfMessage( 'duplicate-defaultsort',
+ // Message should be parsed, but these params should only be escaped.
+ $converter->markNoConversion( wfEscapeWikiText( $old ) ),
+ $converter->markNoConversion( wfEscapeWikiText( $text ) )
+ )->inContentLanguage()->text() .
+ '</span>';
}
}
@@ -807,14 +874,14 @@ class CoreParserFunctions {
public static function filepath( $parser, $name = '', $argA = '', $argB = '' ) {
$file = wfFindFile( $name );
- if( $argA == 'nowiki' ) {
+ if ( $argA == 'nowiki' ) {
// {{filepath: | option [| size] }}
$isNowiki = true;
$parsedWidthParam = $parser->parseWidthParam( $argB );
} else {
// {{filepath: [| size [|option]] }}
$parsedWidthParam = $parser->parseWidthParam( $argA );
- $isNowiki = ($argB == 'nowiki');
+ $isNowiki = ( $argB == 'nowiki' );
}
if ( $file ) {
diff --git a/includes/parser/CoreTagHooks.php b/includes/parser/CoreTagHooks.php
index 65051839..a2eb6987 100644
--- a/includes/parser/CoreTagHooks.php
+++ b/includes/parser/CoreTagHooks.php
@@ -77,7 +77,7 @@ class CoreTagHooks {
*/
static function html( $content, $attributes, $parser ) {
global $wgRawHtml;
- if( $wgRawHtml ) {
+ if ( $wgRawHtml ) {
return array( $content, 'markerType' => 'nowiki' );
} else {
throw new MWException( '<html> extension tag encountered unexpectedly' );
diff --git a/includes/parser/DateFormatter.php b/includes/parser/DateFormatter.php
index a2da3074..0a69b045 100644
--- a/includes/parser/DateFormatter.php
+++ b/includes/parser/DateFormatter.php
@@ -156,10 +156,10 @@ class DateFormatter {
}
for ( $i = 1; $i <= self::LAST; $i++ ) {
$this->mSource = $i;
- if ( isset ( $this->rules[$preference][$i] ) ) {
+ if ( isset( $this->rules[$preference][$i] ) ) {
# Specific rules
$this->mTarget = $this->rules[$preference][$i];
- } elseif ( isset ( $this->rules[self::ALL][$i] ) ) {
+ } elseif ( isset( $this->rules[self::ALL][$i] ) ) {
# General rules
$this->mTarget = $this->rules[self::ALL][$i];
} elseif ( $preference ) {
@@ -198,14 +198,15 @@ class DateFormatter {
function replace( $matches ) {
# Extract information from $matches
$linked = true;
- if ( isset( $this->mLinked ) )
+ if ( isset( $this->mLinked ) ) {
$linked = $this->mLinked;
+ }
$bits = array();
$key = $this->keys[$this->mSource];
for ( $p = 0; $p < strlen( $key ); $p++ ) {
if ( $key[$p] != ' ' ) {
- $bits[$key[$p]] = $matches[$p+1];
+ $bits[$key[$p]] = $matches[$p + 1];
}
}
@@ -232,10 +233,12 @@ class DateFormatter {
$fail = false;
// Pre-generate y/Y stuff because we need the year for the <span> title.
- if ( !isset( $bits['y'] ) && isset( $bits['Y'] ) )
+ if ( !isset( $bits['y'] ) && isset( $bits['Y'] ) ) {
$bits['y'] = $this->makeIsoYear( $bits['Y'] );
- if ( !isset( $bits['Y'] ) && isset( $bits['y'] ) )
+ }
+ if ( !isset( $bits['Y'] ) && isset( $bits['y'] ) ) {
$bits['Y'] = $this->makeNormalYear( $bits['y'] );
+ }
if ( !isset( $bits['m'] ) ) {
$m = $this->makeIsoMonth( $bits['F'] );
@@ -293,8 +296,9 @@ class DateFormatter {
}
$isoBits = array();
- if ( isset( $bits['y'] ) )
+ if ( isset( $bits['y'] ) ) {
$isoBits[] = $bits['y'];
+ }
$isoBits[] = $bits['m'];
$isoBits[] = $bits['d'];
$isoDate = implode( '-', $isoBits );
@@ -312,7 +316,7 @@ class DateFormatter {
*/
function getMonthRegex() {
$names = array();
- for( $i = 1; $i <= 12; $i++ ) {
+ for ( $i = 1; $i <= 12; $i++ ) {
$names[] = $this->lang->getMonthName( $i );
$names[] = $this->lang->getMonthAbbreviation( $i );
}
@@ -337,7 +341,7 @@ class DateFormatter {
function makeIsoYear( $year ) {
# Assumes the year is in a nice format, as enforced by the regex
if ( substr( $year, -2 ) == 'BC' ) {
- $num = intval(substr( $year, 0, -3 )) - 1;
+ $num = intval( substr( $year, 0, -3 ) ) - 1;
# PHP bug note: sprintf( "%04d", -1 ) fails poorly
$text = sprintf( '-%04d', $num );
@@ -353,7 +357,7 @@ class DateFormatter {
*/
function makeNormalYear( $iso ) {
if ( $iso[0] == '-' ) {
- $text = (intval( substr( $iso, 1 ) ) + 1) . ' BC';
+ $text = ( intval( substr( $iso, 1 ) ) + 1 ) . ' BC';
} else {
$text = intval( $iso );
}
diff --git a/includes/parser/LinkHolderArray.php b/includes/parser/LinkHolderArray.php
index 49b2d333..27ff9e7d 100644
--- a/includes/parser/LinkHolderArray.php
+++ b/includes/parser/LinkHolderArray.php
@@ -281,88 +281,90 @@ class LinkHolderArray {
$linkCache = LinkCache::singleton();
$output = $this->parent->getOutput();
- wfProfileIn( __METHOD__ . '-check' );
- $dbr = wfGetDB( DB_SLAVE );
- $threshold = $this->parent->getOptions()->getStubThreshold();
+ if( $linkCache->useDatabase() ) {
+ wfProfileIn( __METHOD__ . '-check' );
+ $dbr = wfGetDB( DB_SLAVE );
+ $threshold = $this->parent->getOptions()->getStubThreshold();
- # Sort by namespace
- ksort( $this->internals );
+ # Sort by namespace
+ ksort( $this->internals );
- $linkcolour_ids = array();
+ $linkcolour_ids = array();
- # Generate query
- $queries = array();
- foreach ( $this->internals as $ns => $entries ) {
- foreach ( $entries as $entry ) {
- $title = $entry['title'];
- $pdbk = $entry['pdbk'];
+ # Generate query
+ $queries = array();
+ foreach ( $this->internals as $ns => $entries ) {
+ foreach ( $entries as $entry ) {
+ $title = $entry['title'];
+ $pdbk = $entry['pdbk'];
- # Skip invalid entries.
- # Result will be ugly, but prevents crash.
- if ( is_null( $title ) ) {
- continue;
- }
+ # Skip invalid entries.
+ # Result will be ugly, but prevents crash.
+ if ( is_null( $title ) ) {
+ continue;
+ }
- # Check if it's a static known link, e.g. interwiki
- if ( $title->isAlwaysKnown() ) {
- $colours[$pdbk] = '';
- } elseif ( $ns == NS_SPECIAL ) {
- $colours[$pdbk] = 'new';
- } elseif ( ( $id = $linkCache->getGoodLinkID( $pdbk ) ) != 0 ) {
- $colours[$pdbk] = Linker::getLinkColour( $title, $threshold );
- $output->addLink( $title, $id );
- $linkcolour_ids[$id] = $pdbk;
- } elseif ( $linkCache->isBadLink( $pdbk ) ) {
- $colours[$pdbk] = 'new';
- } else {
- # Not in the link cache, add it to the query
- $queries[$ns][] = $title->getDBkey();
+ # Check if it's a static known link, e.g. interwiki
+ if ( $title->isAlwaysKnown() ) {
+ $colours[$pdbk] = '';
+ } elseif ( $ns == NS_SPECIAL ) {
+ $colours[$pdbk] = 'new';
+ } elseif ( ( $id = $linkCache->getGoodLinkID( $pdbk ) ) != 0 ) {
+ $colours[$pdbk] = Linker::getLinkColour( $title, $threshold );
+ $output->addLink( $title, $id );
+ $linkcolour_ids[$id] = $pdbk;
+ } elseif ( $linkCache->isBadLink( $pdbk ) ) {
+ $colours[$pdbk] = 'new';
+ } else {
+ # Not in the link cache, add it to the query
+ $queries[$ns][] = $title->getDBkey();
+ }
}
}
- }
- if ( $queries ) {
- $where = array();
- foreach( $queries as $ns => $pages ) {
- $where[] = $dbr->makeList(
- array(
- 'page_namespace' => $ns,
- 'page_title' => $pages,
- ),
- LIST_AND
- );
- }
+ if ( $queries ) {
+ $where = array();
+ foreach ( $queries as $ns => $pages ) {
+ $where[] = $dbr->makeList(
+ array(
+ 'page_namespace' => $ns,
+ 'page_title' => $pages,
+ ),
+ LIST_AND
+ );
+ }
- $res = $dbr->select(
- 'page',
- array( 'page_id', 'page_namespace', 'page_title', 'page_is_redirect', 'page_len', 'page_latest' ),
- $dbr->makeList( $where, LIST_OR ),
- __METHOD__
- );
+ $res = $dbr->select(
+ 'page',
+ array( 'page_id', 'page_namespace', 'page_title', 'page_is_redirect', 'page_len', 'page_latest' ),
+ $dbr->makeList( $where, LIST_OR ),
+ __METHOD__
+ );
- # Fetch data and form into an associative array
- # non-existent = broken
- foreach ( $res as $s ) {
- $title = Title::makeTitle( $s->page_namespace, $s->page_title );
- $pdbk = $title->getPrefixedDBkey();
- $linkCache->addGoodLinkObjFromRow( $title, $s );
- $output->addLink( $title, $s->page_id );
- # @todo FIXME: Convoluted data flow
- # The redirect status and length is passed to getLinkColour via the LinkCache
- # Use formal parameters instead
- $colours[$pdbk] = Linker::getLinkColour( $title, $threshold );
- //add id to the extension todolist
- $linkcolour_ids[$s->page_id] = $pdbk;
+ # Fetch data and form into an associative array
+ # non-existent = broken
+ foreach ( $res as $s ) {
+ $title = Title::makeTitle( $s->page_namespace, $s->page_title );
+ $pdbk = $title->getPrefixedDBkey();
+ $linkCache->addGoodLinkObjFromRow( $title, $s );
+ $output->addLink( $title, $s->page_id );
+ # @todo FIXME: Convoluted data flow
+ # The redirect status and length is passed to getLinkColour via the LinkCache
+ # Use formal parameters instead
+ $colours[$pdbk] = Linker::getLinkColour( $title, $threshold );
+ //add id to the extension todolist
+ $linkcolour_ids[$s->page_id] = $pdbk;
+ }
+ unset( $res );
}
- unset( $res );
- }
- if ( count( $linkcolour_ids ) ) {
- //pass an array of page_ids to an extension
- wfRunHooks( 'GetLinkColours', array( $linkcolour_ids, &$colours ) );
+ 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 );
}
@@ -377,6 +379,10 @@ class LinkHolderArray {
$key = "$ns:$index";
$searchkey = "<!--LINK $key-->";
$displayText = $entry['text'];
+ if ( isset( $entry['selflink'] ) ) {
+ $replacePairs[$searchkey] = Linker::makeSelfLinkObj( $title, $displayText, $query );
+ continue;
+ }
if ( $displayText === '' ) {
$displayText = null;
}
@@ -406,7 +412,8 @@ class LinkHolderArray {
$text = preg_replace_callback(
'/(<!--LINK .*?-->)/',
$replacer->cb(),
- $text);
+ $text
+ );
wfProfileOut( __METHOD__ . '-replace' );
wfProfileOut( __METHOD__ );
@@ -424,7 +431,7 @@ class LinkHolderArray {
# Make interwiki link HTML
$output = $this->parent->getOutput();
$replacePairs = array();
- foreach( $this->interwikis as $key => $link ) {
+ foreach ( $this->interwikis as $key => $link ) {
$replacePairs[$key] = Linker::link( $link['title'], $link['text'] );
$output->addInterwikiLink( $link['title'] );
}
@@ -454,20 +461,17 @@ class LinkHolderArray {
// single string to all variants. This would improve parser's performance
// significantly.
foreach ( $this->internals as $ns => $entries ) {
+ if ( $ns == NS_SPECIAL ) {
+ continue;
+ }
foreach ( $entries as $index => $entry ) {
$pdbk = $entry['pdbk'];
// we only deal with new links (in its first query)
if ( !isset( $colours[$pdbk] ) || $colours[$pdbk] === 'new' ) {
- $title = $entry['title'];
- $titleText = $title->getText();
- $titlesAttrs[] = array(
- 'ns' => $ns,
- 'key' => "$ns:$index",
- 'titleText' => $titleText,
- );
+ $titlesAttrs[] = array( $index, $entry['title'] );
// separate titles with \0 because it would never appears
// in a valid title
- $titlesToBeConverted .= $titleText . "\0";
+ $titlesToBeConverted .= $entry['title']->getText() . "\0";
}
}
}
@@ -478,19 +482,35 @@ class LinkHolderArray {
foreach ( $titlesAllVariants as &$titlesVariant ) {
$titlesVariant = explode( "\0", $titlesVariant );
}
- $l = count( $titlesAttrs );
+
// Then add variants of links to link batch
- for ( $i = 0; $i < $l; $i ++ ) {
+ $parentTitle = $this->parent->getTitle();
+ foreach ( $titlesAttrs as $i => $attrs ) {
+ list( $index, $title ) = $attrs;
+ $ns = $title->getNamespace();
+ $text = $title->getText();
+
foreach ( $allVariantsName as $variantName ) {
$textVariant = $titlesAllVariants[$variantName][$i];
- if ( $textVariant != $titlesAttrs[$i]['titleText'] ) {
- $variantTitle = Title::makeTitle( $titlesAttrs[$i]['ns'], $textVariant );
- if( is_null( $variantTitle ) ) {
- continue;
- }
- $linkBatch->addObj( $variantTitle );
- $variantMap[$variantTitle->getPrefixedDBkey()][] = $titlesAttrs[$i]['key'];
+ if ( $textVariant === $text ) {
+ continue;
}
+
+ $variantTitle = Title::makeTitle( $ns, $textVariant );
+ if ( is_null( $variantTitle ) ) {
+ continue;
+ }
+
+ // Self-link checking for mixed/different variant titles. At this point, we
+ // already know the exact title does not exist, so the link cannot be to a
+ // variant of the current title that exists as a separate page.
+ if ( $variantTitle->equals( $parentTitle ) && $title->getFragment() === '' ) {
+ $this->internals[$ns][$index]['selflink'] = true;
+ continue 2;
+ }
+
+ $linkBatch->addObj( $variantTitle );
+ $variantMap[$variantTitle->getPrefixedDBkey()][] = "$ns:$index";
}
}
@@ -513,7 +533,7 @@ class LinkHolderArray {
}
}
- if( !$linkBatch->isEmpty() ) {
+ if ( !$linkBatch->isEmpty() ) {
// construct query
$dbr = wfGetDB( DB_SLAVE );
$varRes = $dbr->select( 'page',
@@ -532,14 +552,14 @@ class LinkHolderArray {
$vardbk = $variantTitle->getDBkey();
$holderKeys = array();
- if( isset( $variantMap[$varPdbk] ) ) {
+ if ( isset( $variantMap[$varPdbk] ) ) {
$holderKeys = $variantMap[$varPdbk];
$linkCache->addGoodLinkObjFromRow( $variantTitle, $s );
$output->addLink( $variantTitle, $s->page_id );
}
// loop over link holders
- foreach( $holderKeys as $key ) {
+ foreach ( $holderKeys as $key ) {
list( $ns, $index ) = explode( ':', $key, 2 );
$entry =& $this->internals[$ns][$index];
$pdbk = $entry['pdbk'];
@@ -569,12 +589,12 @@ class LinkHolderArray {
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;
@@ -614,13 +634,13 @@ class LinkHolderArray {
function replaceTextCallback( $matches ) {
$type = $matches[1];
$key = $matches[2];
- if( $type == 'LINK' ) {
+ if ( $type == 'LINK' ) {
list( $ns, $index ) = explode( ':', $key, 2 );
- if( isset( $this->internals[$ns][$index]['text'] ) ) {
+ if ( isset( $this->internals[$ns][$index]['text'] ) ) {
return $this->internals[$ns][$index]['text'];
}
- } elseif( $type == 'IWLINK' ) {
- if( isset( $this->interwikis[$key]['text'] ) ) {
+ } elseif ( $type == 'IWLINK' ) {
+ if ( isset( $this->interwikis[$key]['text'] ) ) {
return $this->interwikis[$key]['text'];
}
}
diff --git a/includes/parser/Parser.php b/includes/parser/Parser.php
index 5ef0bc71..1f14223d 100644
--- a/includes/parser/Parser.php
+++ b/includes/parser/Parser.php
@@ -28,7 +28,7 @@
/**
* PHP Parser - Processes wiki markup (which uses a more user-friendly
* syntax, such as "[[link]]" for making links), and provides a one-way
- * transformation of that wiki markup it into XHTML output / markup
+ * transformation of that wiki markup it into (X)HTML output / markup
* (which in turn the browser understands, and can display).
*
* There are seven main entry points into the Parser class:
@@ -54,7 +54,6 @@
* @warning $wgUser or $wgTitle or $wgRequest or $wgLang. Keep them away!
*
* @par Settings:
- * $wgLocaltimezone
* $wgNamespacesWithSubpages
*
* @par Settings only within ParserOptions:
@@ -116,6 +115,10 @@ class Parser {
# Marker Suffix needs to be accessible staticly.
const MARKER_SUFFIX = "-QINU\x7f";
+ # Markers used for wrapping the table of contents
+ const TOC_START = '<mw:toc>';
+ const TOC_END = '</mw:toc>';
+
# Persistent:
var $mTagHooks = array();
var $mTransparentTagHooks = array();
@@ -192,7 +195,9 @@ class Parser {
var $mRevisionId; # ID to display in {{REVISIONID}} tags
var $mRevisionTimestamp; # The timestamp of the specified revision ID
var $mRevisionUser; # User to display in {{REVISIONUSER}} tag
+ var $mRevisionSize; # Size to display in {{REVISIONSIZE}} variable
var $mRevIdForTs; # The revision ID which was used to fetch the timestamp
+ var $mInputSize = false; # For {{PAGESIZE}} on current page.
/**
* @var string
@@ -218,8 +223,8 @@ class Parser {
self::EXT_LINK_URL_CLASS . '+)\p{Zs}*([^\]\\x00-\\x08\\x0a-\\x1F]*?)\]/Su';
if ( isset( $conf['preprocessorClass'] ) ) {
$this->mPreprocessorClass = $conf['preprocessorClass'];
- } elseif ( defined( 'MW_COMPILED' ) ) {
- # Preprocessor_Hash is much faster than Preprocessor_DOM in compiled mode
+ } elseif ( defined( 'HPHP_VERSION' ) ) {
+ # Preprocessor_Hash is much faster than Preprocessor_DOM under HipHop
$this->mPreprocessorClass = 'Preprocessor_Hash';
} elseif ( extension_loaded( 'domxml' ) ) {
# PECL extension that conflicts with the core DOM extension (bug 13770)
@@ -292,7 +297,7 @@ class Parser {
$this->mLinkHolders = new LinkHolderArray( $this );
$this->mLinkID = 0;
$this->mRevisionObject = $this->mRevisionTimestamp =
- $this->mRevisionId = $this->mRevisionUser = null;
+ $this->mRevisionId = $this->mRevisionUser = $this->mRevisionSize = null;
$this->mVarCache = array();
$this->mUser = null;
$this->mLangLinkLanguages = array();
@@ -354,13 +359,18 @@ class Parser {
* to internalParse() which does all the real work.
*/
- global $wgUseTidy, $wgAlwaysUseTidy;
+ global $wgUseTidy, $wgAlwaysUseTidy, $wgShowHostnames;
$fname = __METHOD__ . '-' . wfGetCaller();
wfProfileIn( __METHOD__ );
wfProfileIn( $fname );
$this->startParse( $title, $options, self::OT_HTML, $clearState );
+ $this->mInputSize = strlen( $text );
+ if ( $this->mOptions->getEnableLimitReport() ) {
+ $this->mOutput->resetParseStartTime();
+ }
+
# Remove the strip marker tag prefix from the input, if present.
if ( $clearState ) {
$text = str_replace( $this->mUniqPrefix, '', $text );
@@ -370,11 +380,13 @@ class Parser {
$oldRevisionObject = $this->mRevisionObject;
$oldRevisionTimestamp = $this->mRevisionTimestamp;
$oldRevisionUser = $this->mRevisionUser;
+ $oldRevisionSize = $this->mRevisionSize;
if ( $revid !== null ) {
$this->mRevisionId = $revid;
$this->mRevisionObject = null;
$this->mRevisionTimestamp = null;
$this->mRevisionUser = null;
+ $this->mRevisionSize = null;
}
wfRunHooks( 'ParserBeforeStrip', array( &$this, &$text, &$this->mStripState ) );
@@ -490,22 +502,67 @@ class Parser {
# Information on include size limits, for the benefit of users who try to skirt them
if ( $this->mOptions->getEnableLimitReport() ) {
$max = $this->mOptions->getMaxIncludeSize();
- $PFreport = "Expensive parser function count: {$this->mExpensiveFunctionCount}/{$this->mOptions->getExpensiveParserFunctionLimit()}\n";
- $limitReport =
- "NewPP limit report\n" .
- "Preprocessor visited node count: {$this->mPPNodeCount}/{$this->mOptions->getMaxPPNodeCount()}\n" .
- "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" .
- $PFreport;
+
+ $cpuTime = $this->mOutput->getTimeSinceStart( 'cpu' );
+ if ( $cpuTime !== null ) {
+ $this->mOutput->setLimitReportData( 'limitreport-cputime',
+ sprintf( "%.3f", $cpuTime )
+ );
+ }
+
+ $wallTime = $this->mOutput->getTimeSinceStart( 'wall' );
+ $this->mOutput->setLimitReportData( 'limitreport-walltime',
+ sprintf( "%.3f", $wallTime )
+ );
+
+ $this->mOutput->setLimitReportData( 'limitreport-ppvisitednodes',
+ array( $this->mPPNodeCount, $this->mOptions->getMaxPPNodeCount() )
+ );
+ $this->mOutput->setLimitReportData( 'limitreport-ppgeneratednodes',
+ array( $this->mGeneratedPPNodeCount, $this->mOptions->getMaxGeneratedPPNodeCount() )
+ );
+ $this->mOutput->setLimitReportData( 'limitreport-postexpandincludesize',
+ array( $this->mIncludeSizes['post-expand'], $max )
+ );
+ $this->mOutput->setLimitReportData( 'limitreport-templateargumentsize',
+ array( $this->mIncludeSizes['arg'], $max )
+ );
+ $this->mOutput->setLimitReportData( 'limitreport-expansiondepth',
+ array( $this->mHighestExpansionDepth, $this->mOptions->getMaxPPExpandDepth() )
+ );
+ $this->mOutput->setLimitReportData( 'limitreport-expensivefunctioncount',
+ array( $this->mExpensiveFunctionCount, $this->mOptions->getExpensiveParserFunctionLimit() )
+ );
+ wfRunHooks( 'ParserLimitReportPrepare', array( $this, $this->mOutput ) );
+
+ $limitReport = "NewPP limit report\n";
+ if ( $wgShowHostnames ) {
+ $limitReport .= 'Parsed by ' . wfHostname() . "\n";
+ }
+ foreach ( $this->mOutput->getLimitReportData() as $key => $value ) {
+ if ( wfRunHooks( 'ParserLimitReportFormat',
+ array( $key, $value, &$limitReport, false, false )
+ ) ) {
+ $keyMsg = wfMessage( $key )->inLanguage( 'en' )->useDatabase( false );
+ $valueMsg = wfMessage( array( "$key-value-text", "$key-value" ) )
+ ->inLanguage( 'en' )->useDatabase( false );
+ if ( !$valueMsg->exists() ) {
+ $valueMsg = new RawMessage( '$1' );
+ }
+ if ( !$keyMsg->isDisabled() && !$valueMsg->isDisabled() ) {
+ $valueMsg->params( $value );
+ $limitReport .= "{$keyMsg->text()}: {$valueMsg->text()}\n";
+ }
+ }
+ }
+ // Since we're not really outputting HTML, decode the entities and
+ // then re-encode the things that need hiding inside HTML comments.
+ $limitReport = htmlspecialchars_decode( $limitReport );
wfRunHooks( 'ParserLimitReport', array( $this, &$limitReport ) );
// Sanitize for comment. Note 'â€' in the replacement is U+2010,
// which looks much like the problematic '-'.
$limitReport = str_replace( array( '-', '&' ), array( 'â€', '&amp;' ), $limitReport );
-
$text .= "\n<!-- \n$limitReport-->\n";
if ( $this->mGeneratedPPNodeCount > $this->mOptions->getMaxGeneratedPPNodeCount() / 10 ) {
@@ -519,6 +576,8 @@ class Parser {
$this->mRevisionObject = $oldRevisionObject;
$this->mRevisionTimestamp = $oldRevisionTimestamp;
$this->mRevisionUser = $oldRevisionUser;
+ $this->mRevisionSize = $oldRevisionSize;
+ $this->mInputSize = false;
wfProfileOut( $fname );
wfProfileOut( __METHOD__ );
@@ -536,7 +595,7 @@ class Parser {
*
* @return string
*/
- function recursiveTagParse( $text, $frame=false ) {
+ function recursiveTagParse( $text, $frame = false ) {
wfProfileIn( __METHOD__ );
wfRunHooks( 'ParserBeforeStrip', array( &$this, &$text, &$this->mStripState ) );
wfRunHooks( 'ParserAfterStrip', array( &$this, &$text, &$this->mStripState ) );
@@ -769,9 +828,9 @@ class Parser {
if ( $target !== null ) {
return $target;
- } elseif( $this->mOptions->getInterfaceMessage() ) {
+ } elseif ( $this->mOptions->getInterfaceMessage() ) {
return $this->mOptions->getUserLangObj();
- } elseif( is_null( $this->mTitle ) ) {
+ } elseif ( is_null( $this->mTitle ) ) {
throw new MWException( __METHOD__ . ': $this->mTitle is null' );
}
@@ -1256,8 +1315,8 @@ class Parser {
'x' => 'X',
));
$titleObj = SpecialPage::getTitleFor( 'Booksources', $num );
- return'<a href="' .
- htmlspecialchars( $titleObj->getLocalUrl() ) .
+ return '<a href="' .
+ htmlspecialchars( $titleObj->getLocalURL() ) .
"\" class=\"internal mw-magiclink-isbn\">ISBN $isbn</a>";
} else {
return $m[0];
@@ -1366,165 +1425,186 @@ class Parser {
*/
public function doQuotes( $text ) {
$arr = preg_split( "/(''+)/", $text, -1, PREG_SPLIT_DELIM_CAPTURE );
- if ( count( $arr ) == 1 ) {
+ $countarr = count( $arr );
+ if ( $countarr == 1 ) {
return $text;
- } else {
- # First, do some preliminary work. This may shift some apostrophes from
- # being mark-up to being text. It also counts the number of occurrences
- # of bold and italics mark-ups.
- $numbold = 0;
- $numitalics = 0;
- for ( $i = 0; $i < count( $arr ); $i++ ) {
- if ( ( $i % 2 ) == 1 ) {
- # If there are ever four apostrophes, assume the first is supposed to
- # be text, and the remaining three constitute mark-up for bold text.
- if ( strlen( $arr[$i] ) == 4 ) {
- $arr[$i-1] .= "'";
- $arr[$i] = "'''";
- } elseif ( strlen( $arr[$i] ) > 5 ) {
- # If there are more than 5 apostrophes in a row, assume they're all
- # text except for the last 5.
- $arr[$i-1] .= str_repeat( "'", strlen( $arr[$i] ) - 5 );
- $arr[$i] = "'''''";
- }
- # Count the number of occurrences of bold and italics mark-ups.
- # We are not counting sequences of five apostrophes.
- if ( strlen( $arr[$i] ) == 2 ) {
- $numitalics++;
- } elseif ( strlen( $arr[$i] ) == 3 ) {
- $numbold++;
- } elseif ( strlen( $arr[$i] ) == 5 ) {
- $numitalics++;
- $numbold++;
- }
- }
+ }
+
+ // First, do some preliminary work. This may shift some apostrophes from
+ // being mark-up to being text. It also counts the number of occurrences
+ // of bold and italics mark-ups.
+ $numbold = 0;
+ $numitalics = 0;
+ for ( $i = 1; $i < $countarr; $i += 2 ) {
+ $thislen = strlen( $arr[$i] );
+ // If there are ever four apostrophes, assume the first is supposed to
+ // be text, and the remaining three constitute mark-up for bold text.
+ // (bug 13227: ''''foo'''' turns into ' ''' foo ' ''')
+ if ( $thislen == 4 ) {
+ $arr[$i - 1] .= "'";
+ $arr[$i] = "'''";
+ $thislen = 3;
+ } elseif ( $thislen > 5 ) {
+ // If there are more than 5 apostrophes in a row, assume they're all
+ // text except for the last 5.
+ // (bug 13227: ''''''foo'''''' turns into ' ''''' foo ' ''''')
+ $arr[$i - 1] .= str_repeat( "'", $thislen - 5 );
+ $arr[$i] = "'''''";
+ $thislen = 5;
}
+ // Count the number of occurrences of bold and italics mark-ups.
+ if ( $thislen == 2 ) {
+ $numitalics++;
+ } elseif ( $thislen == 3 ) {
+ $numbold++;
+ } elseif ( $thislen == 5 ) {
+ $numitalics++;
+ $numbold++;
+ }
+ }
- # If there is an odd number of both bold and italics, it is likely
- # that one of the bold ones was meant to be an apostrophe followed
- # by italics. Which one we cannot know for certain, but it is more
- # likely to be one that has a single-letter word before it.
- if ( ( $numbold % 2 == 1 ) && ( $numitalics % 2 == 1 ) ) {
- $i = 0;
- $firstsingleletterword = -1;
- $firstmultiletterword = -1;
- $firstspace = -1;
- foreach ( $arr as $r ) {
- if ( ( $i % 2 == 1 ) and ( strlen( $r ) == 3 ) ) {
- $x1 = substr( $arr[$i-1], -1 );
- $x2 = substr( $arr[$i-1], -2, 1 );
- if ( $x1 === ' ' ) {
- if ( $firstspace == -1 ) {
- $firstspace = $i;
- }
- } elseif ( $x2 === ' ' ) {
- if ( $firstsingleletterword == -1 ) {
- $firstsingleletterword = $i;
- }
- } else {
- if ( $firstmultiletterword == -1 ) {
- $firstmultiletterword = $i;
- }
+ // If there is an odd number of both bold and italics, it is likely
+ // that one of the bold ones was meant to be an apostrophe followed
+ // by italics. Which one we cannot know for certain, but it is more
+ // likely to be one that has a single-letter word before it.
+ if ( ( $numbold % 2 == 1 ) && ( $numitalics % 2 == 1 ) ) {
+ $firstsingleletterword = -1;
+ $firstmultiletterword = -1;
+ $firstspace = -1;
+ for ( $i = 1; $i < $countarr; $i += 2 ) {
+ if ( strlen( $arr[$i] ) == 3 ) {
+ $x1 = substr( $arr[$i - 1], -1 );
+ $x2 = substr( $arr[$i - 1], -2, 1 );
+ if ( $x1 === ' ' ) {
+ if ( $firstspace == -1 ) {
+ $firstspace = $i;
+ }
+ } elseif ( $x2 === ' ' ) {
+ if ( $firstsingleletterword == -1 ) {
+ $firstsingleletterword = $i;
+ // if $firstsingleletterword is set, we don't
+ // look at the other options, so we can bail early.
+ break;
+ }
+ } else {
+ if ( $firstmultiletterword == -1 ) {
+ $firstmultiletterword = $i;
}
}
- $i++;
}
+ }
- # If there is a single-letter word, use it!
- if ( $firstsingleletterword > -1 ) {
- $arr[$firstsingleletterword] = "''";
- $arr[$firstsingleletterword-1] .= "'";
- } elseif ( $firstmultiletterword > -1 ) {
- # If not, but there's a multi-letter word, use that one.
- $arr[$firstmultiletterword] = "''";
- $arr[$firstmultiletterword-1] .= "'";
- } elseif ( $firstspace > -1 ) {
- # ... otherwise use the first one that has neither.
- # (notice that it is possible for all three to be -1 if, for example,
- # there is only one pentuple-apostrophe in the line)
- $arr[$firstspace] = "''";
- $arr[$firstspace-1] .= "'";
- }
+ // If there is a single-letter word, use it!
+ if ( $firstsingleletterword > -1 ) {
+ $arr[$firstsingleletterword] = "''";
+ $arr[$firstsingleletterword - 1] .= "'";
+ } elseif ( $firstmultiletterword > -1 ) {
+ // If not, but there's a multi-letter word, use that one.
+ $arr[$firstmultiletterword] = "''";
+ $arr[$firstmultiletterword - 1] .= "'";
+ } elseif ( $firstspace > -1 ) {
+ // ... otherwise use the first one that has neither.
+ // (notice that it is possible for all three to be -1 if, for example,
+ // there is only one pentuple-apostrophe in the line)
+ $arr[$firstspace] = "''";
+ $arr[$firstspace - 1] .= "'";
}
+ }
- # Now let's actually convert our apostrophic mush to HTML!
- $output = '';
- $buffer = '';
- $state = '';
- $i = 0;
- foreach ( $arr as $r ) {
- if ( ( $i % 2 ) == 0 ) {
- if ( $state === 'both' ) {
- $buffer .= $r;
- } else {
- $output .= $r;
- }
+ // Now let's actually convert our apostrophic mush to HTML!
+ $output = '';
+ $buffer = '';
+ $state = '';
+ $i = 0;
+ foreach ( $arr as $r ) {
+ if ( ( $i % 2 ) == 0 ) {
+ if ( $state === 'both' ) {
+ $buffer .= $r;
} else {
- if ( strlen( $r ) == 2 ) {
- if ( $state === 'i' ) {
- $output .= '</i>'; $state = '';
- } elseif ( $state === 'bi' ) {
- $output .= '</i>'; $state = 'b';
- } elseif ( $state === 'ib' ) {
- $output .= '</b></i><b>'; $state = 'b';
- } elseif ( $state === 'both' ) {
- $output .= '<b><i>' . $buffer . '</i>'; $state = 'b';
- } else { # $state can be 'b' or ''
- $output .= '<i>'; $state .= 'i';
- }
- } elseif ( strlen( $r ) == 3 ) {
- if ( $state === 'b' ) {
- $output .= '</b>'; $state = '';
- } elseif ( $state === 'bi' ) {
- $output .= '</i></b><i>'; $state = 'i';
- } elseif ( $state === 'ib' ) {
- $output .= '</b>'; $state = 'i';
- } elseif ( $state === 'both' ) {
- $output .= '<i><b>' . $buffer . '</b>'; $state = 'i';
- } else { # $state can be 'i' or ''
- $output .= '<b>'; $state .= 'b';
- }
- } elseif ( strlen( $r ) == 5 ) {
- if ( $state === 'b' ) {
- $output .= '</b><i>'; $state = 'i';
- } elseif ( $state === 'i' ) {
- $output .= '</i><b>'; $state = 'b';
- } elseif ( $state === 'bi' ) {
- $output .= '</i></b>'; $state = '';
- } elseif ( $state === 'ib' ) {
- $output .= '</b></i>'; $state = '';
- } elseif ( $state === 'both' ) {
- $output .= '<i><b>' . $buffer . '</b></i>'; $state = '';
- } else { # ($state == '')
- $buffer = ''; $state = 'both';
- }
+ $output .= $r;
+ }
+ } else {
+ $thislen = strlen( $r );
+ if ( $thislen == 2 ) {
+ if ( $state === 'i' ) {
+ $output .= '</i>';
+ $state = '';
+ } elseif ( $state === 'bi' ) {
+ $output .= '</i>';
+ $state = 'b';
+ } elseif ( $state === 'ib' ) {
+ $output .= '</b></i><b>';
+ $state = 'b';
+ } elseif ( $state === 'both' ) {
+ $output .= '<b><i>' . $buffer . '</i>';
+ $state = 'b';
+ } else { // $state can be 'b' or ''
+ $output .= '<i>';
+ $state .= 'i';
+ }
+ } elseif ( $thislen == 3 ) {
+ if ( $state === 'b' ) {
+ $output .= '</b>';
+ $state = '';
+ } elseif ( $state === 'bi' ) {
+ $output .= '</i></b><i>';
+ $state = 'i';
+ } elseif ( $state === 'ib' ) {
+ $output .= '</b>';
+ $state = 'i';
+ } elseif ( $state === 'both' ) {
+ $output .= '<i><b>' . $buffer . '</b>';
+ $state = 'i';
+ } else { // $state can be 'i' or ''
+ $output .= '<b>';
+ $state .= 'b';
+ }
+ } elseif ( $thislen == 5 ) {
+ if ( $state === 'b' ) {
+ $output .= '</b><i>';
+ $state = 'i';
+ } elseif ( $state === 'i' ) {
+ $output .= '</i><b>';
+ $state = 'b';
+ } elseif ( $state === 'bi' ) {
+ $output .= '</i></b>';
+ $state = '';
+ } elseif ( $state === 'ib' ) {
+ $output .= '</b></i>';
+ $state = '';
+ } elseif ( $state === 'both' ) {
+ $output .= '<i><b>' . $buffer . '</b></i>';
+ $state = '';
+ } else { // ($state == '')
+ $buffer = '';
+ $state = 'both';
}
}
- $i++;
- }
- # Now close all remaining tags. Notice that the order is important.
- if ( $state === 'b' || $state === 'ib' ) {
- $output .= '</b>';
}
- if ( $state === 'i' || $state === 'bi' || $state === 'ib' ) {
- $output .= '</i>';
- }
- if ( $state === 'bi' ) {
- $output .= '</b>';
- }
- # There might be lonely ''''', so make sure we have a buffer
- if ( $state === 'both' && $buffer ) {
- $output .= '<b><i>' . $buffer . '</i></b>';
- }
- return $output;
+ $i++;
+ }
+ // Now close all remaining tags. Notice that the order is important.
+ if ( $state === 'b' || $state === 'ib' ) {
+ $output .= '</b>';
+ }
+ if ( $state === 'i' || $state === 'bi' || $state === 'ib' ) {
+ $output .= '</i>';
+ }
+ if ( $state === 'bi' ) {
+ $output .= '</b>';
+ }
+ // There might be lonely ''''', so make sure we have a buffer
+ if ( $state === 'both' && $buffer ) {
+ $output .= '<b><i>' . $buffer . '</i></b>';
}
+ return $output;
}
/**
* Replace external links (REL)
*
* Note: this is all very hackish and the order of execution matters a lot.
- * Make sure to run maintenance/parserTests.php if you change this code.
+ * Make sure to run tests/parserTests.php if you change this code.
*
* @private
*
@@ -1538,12 +1618,13 @@ class Parser {
$bits = preg_split( $this->mExtLinkBracketedRegex, $text, -1, PREG_SPLIT_DELIM_CAPTURE );
if ( $bits === false ) {
+ wfProfileOut( __METHOD__ );
throw new MWException( "PCRE needs to be compiled with --enable-unicode-properties in order for MediaWiki to function" );
}
$s = array_shift( $bits );
$i = 0;
- while ( $i<count( $bits ) ) {
+ while ( $i < count( $bits ) ) {
$url = $bits[$i++];
$i++; // protocol
$text = $bits[$i++];
@@ -2036,16 +2117,12 @@ class Parser {
}
}
- # Self-link checking
- if ( $nt->getFragment() === '' && $ns != NS_SPECIAL ) {
- 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;
- }
+ # Self-link checking. For some languages, variants of the title are checked in
+ # LinkHolderArray::doVariants() to allow batching the existence checks necessary
+ # for linking to a different variant.
+ if ( $ns != NS_SPECIAL && $nt->equals( $this->mTitle ) && $nt->getFragment() === '' ) {
+ $s .= $prefix . Linker::makeSelfLinkObj( $nt, $text, '', $trail );
+ continue;
}
# NS_MEDIA is a pseudo-namespace for linking directly to a file
@@ -2159,7 +2236,7 @@ class Parser {
function closeParagraph() {
$result = '';
if ( $this->mLastSection != '' ) {
- $result = '</' . $this->mLastSection . ">\n";
+ $result = '</' . $this->mLastSection . ">\n";
}
$this->mInPre = false;
$this->mLastSection = '';
@@ -2204,13 +2281,13 @@ class Parser {
$result = $this->closeParagraph();
if ( '*' === $char ) {
- $result .= '<ul><li>';
+ $result .= "<ul>\n<li>";
} elseif ( '#' === $char ) {
- $result .= '<ol><li>';
+ $result .= "<ol>\n<li>";
} elseif ( ':' === $char ) {
- $result .= '<dl><dd>';
+ $result .= "<dl>\n<dd>";
} elseif ( ';' === $char ) {
- $result .= '<dl><dt>';
+ $result .= "<dl>\n<dt>";
$this->mDTopen = true;
} else {
$result = '<!-- ERR 1 -->';
@@ -2228,11 +2305,11 @@ class Parser {
*/
function nextItem( $char ) {
if ( '*' === $char || '#' === $char ) {
- return '</li><li>';
+ return "</li>\n<li>";
} elseif ( ':' === $char || ';' === $char ) {
- $close = '</dd>';
+ $close = "</dd>\n";
if ( $this->mDTopen ) {
- $close = '</dt>';
+ $close = "</dt>\n";
}
if ( ';' === $char ) {
$this->mDTopen = true;
@@ -2254,15 +2331,15 @@ class Parser {
*/
function closeList( $char ) {
if ( '*' === $char ) {
- $text = '</li></ul>';
+ $text = "</li>\n</ul>";
} elseif ( '#' === $char ) {
- $text = '</li></ol>';
+ $text = "</li>\n</ol>";
} elseif ( ':' === $char ) {
if ( $this->mDTopen ) {
$this->mDTopen = false;
- $text = '</dt></dl>';
+ $text = "</dt>\n</dl>";
} else {
- $text = '</dd></dl>';
+ $text = "</dd>\n</dl>";
}
} else {
return '<!-- ERR 3 -->';
@@ -2292,6 +2369,7 @@ class Parser {
$this->mDTopen = $inBlockElem = false;
$prefixLength = 0;
$paragraphStack = false;
+ $inBlockquote = false;
foreach ( $textLines as $oLine ) {
# Fix up $linestart
@@ -2354,13 +2432,13 @@ class Parser {
# Close all the prefixes which aren't shared.
while ( $commonPrefixLength < $lastPrefixLength ) {
- $output .= $this->closeList( $lastPrefix[$lastPrefixLength-1] );
+ $output .= $this->closeList( $lastPrefix[$lastPrefixLength - 1] );
--$lastPrefixLength;
}
# Continue the current prefix if appropriate.
if ( $prefixLength <= $commonPrefixLength && $commonPrefixLength > 0 ) {
- $output .= $this->nextItem( $prefix[$commonPrefixLength-1] );
+ $output .= $this->nextItem( $prefix[$commonPrefixLength - 1] );
}
# Open prefixes where appropriate.
@@ -2385,10 +2463,10 @@ class Parser {
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|<dl|<li|<\\/tr|<\\/td|<\\/th)/iS', $t );
+ $openmatch = preg_match( '/(?:<table|<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|<\\/dl|<\\/?center)/iS', $t );
+ '/(?:<\\/table|<\\/h1|<\\/h2|<\\/h3|<\\/h4|<\\/h5|<\\/h6|' .
+ '<td|<th|<\\/?blockquote|<\\/?div|<hr|<\\/pre|<\\/p|<\\/mw:|' . $this->mUniqPrefix . '-pre|<\\/li|<\\/ul|<\\/ol|<\\/dl|<\\/?center)/iS', $t );
if ( $openmatch or $closematch ) {
$paragraphStack = false;
# TODO bug 5718: paragraph closed
@@ -2396,9 +2474,14 @@ class Parser {
if ( $preOpenMatch and !$preCloseMatch ) {
$this->mInPre = true;
}
+ $bqOffset = 0;
+ while ( preg_match( '/<(\\/?)blockquote[\s>]/i', $t, $bqMatch, PREG_OFFSET_CAPTURE, $bqOffset ) ) {
+ $inBlockquote = !$bqMatch[1][0]; // is this a close tag?
+ $bqOffset = $bqMatch[0][1] + strlen( $bqMatch[0][0] );
+ }
$inBlockElem = !$closematch;
} elseif ( !$inBlockElem && !$this->mInPre ) {
- if ( ' ' == substr( $t, 0, 1 ) and ( $this->mLastSection === 'pre' || trim( $t ) != '' ) ) {
+ if ( ' ' == substr( $t, 0, 1 ) and ( $this->mLastSection === 'pre' || trim( $t ) != '' ) and !$inBlockquote ) {
# pre
if ( $this->mLastSection !== 'pre' ) {
$paragraphStack = false;
@@ -2445,7 +2528,7 @@ class Parser {
}
}
while ( $prefixLength ) {
- $output .= $this->closeList( $prefix2[$prefixLength-1] );
+ $output .= $this->closeList( $prefix2[$prefixLength - 1] );
--$prefixLength;
}
if ( $this->mLastSection != '' ) {
@@ -2481,7 +2564,7 @@ class Parser {
if ( $lt === false || $lt > $pos ) {
# Easy; no tag nesting to worry about
$before = substr( $str, 0, $pos );
- $after = substr( $str, $pos+1 );
+ $after = substr( $str, $pos + 1 );
wfProfileOut( __METHOD__ );
return $pos;
}
@@ -2490,13 +2573,13 @@ class Parser {
$state = self::COLON_STATE_TEXT;
$stack = 0;
$len = strlen( $str );
- for( $i = 0; $i < $len; $i++ ) {
+ for ( $i = 0; $i < $len; $i++ ) {
$c = $str[$i];
- switch( $state ) {
+ switch ( $state ) {
# (Using the number is a performance hack for common cases)
case 0: # self::COLON_STATE_TEXT:
- switch( $c ) {
+ switch ( $c ) {
case "<":
# Could be either a <start> tag or an </end> tag
$state = self::COLON_STATE_TAGSTART;
@@ -2541,7 +2624,7 @@ class Parser {
break;
case 1: # self::COLON_STATE_TAG:
# In a <tag>
- switch( $c ) {
+ switch ( $c ) {
case ">":
$stack++;
$state = self::COLON_STATE_TEXT;
@@ -2555,7 +2638,7 @@ class Parser {
}
break;
case 2: # self::COLON_STATE_TAGSTART:
- switch( $c ) {
+ switch ( $c ) {
case "/":
$state = self::COLON_STATE_CLOSETAG;
break;
@@ -2611,6 +2694,7 @@ class Parser {
}
break;
default:
+ wfProfileOut( __METHOD__ );
throw new MWException( "State machine error in " . __METHOD__ );
}
}
@@ -2660,71 +2744,50 @@ class Parser {
$ts = wfTimestamp( TS_UNIX, $this->mOptions->getTimestamp() );
wfRunHooks( 'ParserGetVariableValueTs', array( &$this, &$ts ) );
- # Use the time zone
- global $wgLocaltimezone;
- if ( isset( $wgLocaltimezone ) ) {
- $oldtz = date_default_timezone_get();
- date_default_timezone_set( $wgLocaltimezone );
- }
-
- $localTimestamp = date( 'YmdHis', $ts );
- $localMonth = date( 'm', $ts );
- $localMonth1 = date( 'n', $ts );
- $localMonthName = date( 'n', $ts );
- $localDay = date( 'j', $ts );
- $localDay2 = date( 'd', $ts );
- $localDayOfWeek = date( 'w', $ts );
- $localWeek = date( 'W', $ts );
- $localYear = date( 'Y', $ts );
- $localHour = date( 'H', $ts );
- if ( isset( $wgLocaltimezone ) ) {
- date_default_timezone_set( $oldtz );
- }
-
$pageLang = $this->getFunctionLang();
switch ( $index ) {
case 'currentmonth':
- $value = $pageLang->formatNum( gmdate( 'm', $ts ) );
+ $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'm' ) );
break;
case 'currentmonth1':
- $value = $pageLang->formatNum( gmdate( 'n', $ts ) );
+ $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'n' ) );
break;
case 'currentmonthname':
- $value = $pageLang->getMonthName( gmdate( 'n', $ts ) );
+ $value = $pageLang->getMonthName( MWTimestamp::getInstance( $ts )->format( 'n' ) );
break;
case 'currentmonthnamegen':
- $value = $pageLang->getMonthNameGen( gmdate( 'n', $ts ) );
+ $value = $pageLang->getMonthNameGen( MWTimestamp::getInstance( $ts )->format( 'n' ) );
break;
case 'currentmonthabbrev':
- $value = $pageLang->getMonthAbbreviation( gmdate( 'n', $ts ) );
+ $value = $pageLang->getMonthAbbreviation( MWTimestamp::getInstance( $ts )->format( 'n' ) );
break;
case 'currentday':
- $value = $pageLang->formatNum( gmdate( 'j', $ts ) );
+ $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'j' ) );
break;
case 'currentday2':
- $value = $pageLang->formatNum( gmdate( 'd', $ts ) );
+ $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'd' ) );
break;
case 'localmonth':
- $value = $pageLang->formatNum( $localMonth );
+ $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'm' ) );
break;
case 'localmonth1':
- $value = $pageLang->formatNum( $localMonth1 );
+ $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'n' ) );
break;
case 'localmonthname':
- $value = $pageLang->getMonthName( $localMonthName );
+ $value = $pageLang->getMonthName( MWTimestamp::getLocalInstance( $ts )->format( 'n' ) );
break;
case 'localmonthnamegen':
- $value = $pageLang->getMonthNameGen( $localMonthName );
+ $value = $pageLang->getMonthNameGen( MWTimestamp::getLocalInstance( $ts )->format( 'n' ) );
break;
case 'localmonthabbrev':
- $value = $pageLang->getMonthAbbreviation( $localMonthName );
+ $value = $pageLang->getMonthAbbreviation( MWTimestamp::getLocalInstance( $ts )->format( 'n' ) );
break;
case 'localday':
- $value = $pageLang->formatNum( $localDay );
+ $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'j' ) );
break;
case 'localday2':
- $value = $pageLang->formatNum( $localDay2 );
+ $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'd' ) );
break;
case 'pagename':
$value = wfEscapeWikiText( $this->mTitle->getText() );
@@ -2744,6 +2807,12 @@ class Parser {
case 'subpagenamee':
$value = wfEscapeWikiText( $this->mTitle->getSubpageUrlForm() );
break;
+ case 'rootpagename':
+ $value = wfEscapeWikiText( $this->mTitle->getRootText() );
+ break;
+ case 'rootpagenamee':
+ $value = wfEscapeWikiText( wfUrlEncode( str_replace( ' ', '_', $this->mTitle->getRootText() ) ) );
+ break;
case 'basepagename':
$value = wfEscapeWikiText( $this->mTitle->getBaseText() );
break;
@@ -2761,7 +2830,7 @@ class Parser {
case 'talkpagenamee':
if ( $this->mTitle->canTalk() ) {
$talkPage = $this->mTitle->getTalkPage();
- $value = wfEscapeWikiText( $talkPage->getPrefixedUrl() );
+ $value = wfEscapeWikiText( $talkPage->getPrefixedURL() );
} else {
$value = '';
}
@@ -2772,11 +2841,11 @@ class Parser {
break;
case 'subjectpagenamee':
$subjPage = $this->mTitle->getSubjectPage();
- $value = wfEscapeWikiText( $subjPage->getPrefixedUrl() );
+ $value = wfEscapeWikiText( $subjPage->getPrefixedURL() );
break;
case 'pageid': // requested in bug 23427
- $pageid = $this->getTitle()->getArticleId();
- if( $pageid == 0 ) {
+ $pageid = $this->getTitle()->getArticleID();
+ if ( $pageid == 0 ) {
# 0 means the page doesn't exist in the database,
# which means the user is previewing a new page.
# The vary-revision flag must be set, because the magic word
@@ -2842,6 +2911,13 @@ class Parser {
wfDebug( __METHOD__ . ": {{REVISIONUSER}} used, setting vary-revision...\n" );
$value = $this->getRevisionUser();
break;
+ case 'revisionsize':
+ # Let the edit saving system know we should parse the page
+ # *after* a revision ID has been assigned. This is for null edits.
+ $this->mOutput->setFlag( 'vary-revision' );
+ wfDebug( __METHOD__ . ": {{REVISIONSIZE}} used, setting vary-revision...\n" );
+ $value = $this->getRevisionSize();
+ break;
case 'namespace':
$value = str_replace( '_', ' ', $wgContLang->getNsText( $this->mTitle->getNamespace() ) );
break;
@@ -2858,50 +2934,50 @@ class Parser {
$value = $this->mTitle->canTalk() ? wfUrlencode( $this->mTitle->getTalkNsText() ) : '';
break;
case 'subjectspace':
- $value = $this->mTitle->getSubjectNsText();
+ $value = str_replace( '_', ' ', $this->mTitle->getSubjectNsText() );
break;
case 'subjectspacee':
$value = ( wfUrlencode( $this->mTitle->getSubjectNsText() ) );
break;
case 'currentdayname':
- $value = $pageLang->getWeekdayName( gmdate( 'w', $ts ) + 1 );
+ $value = $pageLang->getWeekdayName( MWTimestamp::getInstance( $ts )->format( 'w' ) + 1 );
break;
case 'currentyear':
- $value = $pageLang->formatNum( gmdate( 'Y', $ts ), true );
+ $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'Y' ), true );
break;
case 'currenttime':
$value = $pageLang->time( wfTimestamp( TS_MW, $ts ), false, false );
break;
case 'currenthour':
- $value = $pageLang->formatNum( gmdate( 'H', $ts ), true );
+ $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'H' ), true );
break;
case 'currentweek':
# @bug 4594 PHP5 has it zero padded, PHP4 does not, cast to
# int to remove the padding
- $value = $pageLang->formatNum( (int)gmdate( 'W', $ts ) );
+ $value = $pageLang->formatNum( (int)MWTimestamp::getInstance( $ts )->format( 'W' ) );
break;
case 'currentdow':
- $value = $pageLang->formatNum( gmdate( 'w', $ts ) );
+ $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'w' ) );
break;
case 'localdayname':
- $value = $pageLang->getWeekdayName( $localDayOfWeek + 1 );
+ $value = $pageLang->getWeekdayName( MWTimestamp::getLocalInstance( $ts )->format( 'w' ) + 1 );
break;
case 'localyear':
- $value = $pageLang->formatNum( $localYear, true );
+ $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'Y' ), true );
break;
case 'localtime':
- $value = $pageLang->time( $localTimestamp, false, false );
+ $value = $pageLang->time( MWTimestamp::getLocalInstance( $ts )->format( 'YmdHis' ), false, false );
break;
case 'localhour':
- $value = $pageLang->formatNum( $localHour, true );
+ $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'H' ), true );
break;
case 'localweek':
# @bug 4594 PHP5 has it zero padded, PHP4 does not, cast to
# int to remove the padding
- $value = $pageLang->formatNum( (int)$localWeek );
+ $value = $pageLang->formatNum( (int)MWTimestamp::getLocalInstance( $ts )->format( 'W' ) );
break;
case 'localdow':
- $value = $pageLang->formatNum( $localDayOfWeek );
+ $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'w' ) );
break;
case 'numberofarticles':
$value = $pageLang->formatNum( SiteStats::articles() );
@@ -2932,7 +3008,7 @@ class Parser {
$value = wfTimestamp( TS_MW, $ts );
break;
case 'localtimestamp':
- $value = $localTimestamp;
+ $value = MWTimestamp::getLocalInstance( $ts )->format( 'YmdHis' );
break;
case 'currentversion':
$value = SpecialVersion::getVersion();
@@ -3092,7 +3168,7 @@ class Parser {
$assocArgs[$index++] = $arg;
} else {
$name = trim( substr( $arg, 0, $eqpos ) );
- $value = trim( substr( $arg, $eqpos+1 ) );
+ $value = trim( substr( $arg, $eqpos + 1 ) );
if ( $value === false ) {
$value = '';
}
@@ -3119,6 +3195,12 @@ class Parser {
* 'post-expand-template-inclusion' (corresponding messages:
* 'post-expand-template-inclusion-warning',
* 'post-expand-template-inclusion-category')
+ * 'node-count-exceeded' (corresponding messages:
+ * 'node-count-exceeded-warning',
+ * 'node-count-exceeded-category')
+ * 'expansion-depth-exceeded' (corresponding messages:
+ * 'expansion-depth-exceeded-warning',
+ * 'expansion-depth-exceeded-category')
* @param int|null $current Current value
* @param int|null $max Maximum allowed, when an explicit limit has been
* exceeded, provide the values (optional)
@@ -3126,7 +3208,7 @@ class Parser {
function limitationWarn( $limitationType, $current = '', $max = '' ) {
# does no harm if $current and $max are present but are unnecessary for the message
$warning = wfMessage( "$limitationType-warning" )->numParams( $current, $max )
- ->inContentLanguage()->escaped();
+ ->inLanguage( $this->mOptions->getUserLangObj() )->text();
$this->mOutput->addWarning( $warning );
$this->addTrackingCategory( "$limitationType-category" );
}
@@ -3145,7 +3227,6 @@ class Parser {
* @private
*/
function braceSubstitution( $piece, $frame ) {
- global $wgContLang;
wfProfileIn( __METHOD__ );
wfProfileIn( __METHOD__ . '-setup' );
@@ -3251,6 +3332,7 @@ class Parser {
$result = $this->callParserFunction( $frame, $func, $funcArgs );
} catch ( Exception $ex ) {
wfProfileOut( __METHOD__ . '-pfunc' );
+ wfProfileOut( __METHOD__ );
throw $ex;
}
@@ -3268,8 +3350,9 @@ class Parser {
$ns = NS_TEMPLATE;
# Split the title into page and subpage
$subpage = '';
- $part1 = $this->maybeDoSubpageLink( $part1, $subpage );
- if ( $subpage !== '' ) {
+ $relative = $this->maybeDoSubpageLink( $part1, $subpage );
+ if ( $part1 !== $relative ) {
+ $part1 = $relative;
$ns = $this->mTitle->getNamespace();
}
$title = Title::newFromText( $part1, $ns );
@@ -3295,7 +3378,7 @@ class Parser {
if ( !$found && $title ) {
if ( !Profiler::instance()->isPersistent() ) {
# Too many unique items can kill profiling DBs/collectors
- $titleProfileIn = __METHOD__ . "-title-" . $title->getDBKey();
+ $titleProfileIn = __METHOD__ . "-title-" . $title->getPrefixedDBkey();
wfProfileIn( $titleProfileIn ); // template in
}
wfProfileIn( __METHOD__ . '-loadtpl' );
@@ -3598,7 +3681,7 @@ class Parser {
}
$dom = $this->preprocessToDom( $text, self::PTD_FOR_INCLUSION );
- $this->mTplDomCache[ $titleText ] = $dom;
+ $this->mTplDomCache[$titleText] = $dom;
if ( !$title->equals( $cacheTitle ) ) {
$this->mTplRedirCache[$cacheTitle->getPrefixedDBkey()] =
@@ -3621,6 +3704,11 @@ class Parser {
if ( isset( $stuff['deps'] ) ) {
foreach ( $stuff['deps'] as $dep ) {
$this->mOutput->addTemplate( $dep['title'], $dep['page_id'], $dep['rev_id'] );
+ if ( $dep['title']->equals( $this->getTitle() ) ) {
+ // If we transclude ourselves, the final result
+ // will change based on the new version of the page
+ $this->mOutput->setFlag( 'vary-revision' );
+ }
}
}
return array( $text, $finalTitle );
@@ -3660,9 +3748,9 @@ class Parser {
if ( $skip ) {
$text = false;
$deps[] = array(
- 'title' => $title,
- 'page_id' => $title->getArticleID(),
- 'rev_id' => null
+ 'title' => $title,
+ 'page_id' => $title->getArticleID(),
+ 'rev_id' => null
);
break;
}
@@ -3678,15 +3766,15 @@ class Parser {
}
$deps[] = array(
- 'title' => $title,
- 'page_id' => $title->getArticleID(),
- 'rev_id' => $rev_id );
+ 'title' => $title,
+ 'page_id' => $title->getArticleID(),
+ 'rev_id' => $rev_id );
if ( $rev && !$title->equals( $rev->getTitle() ) ) {
# We fetched a rev from a different title; register it too...
$deps[] = array(
- 'title' => $rev->getTitle(),
- 'page_id' => $rev->getPage(),
- 'rev_id' => $rev_id );
+ 'title' => $rev->getTitle(),
+ 'page_id' => $rev->getPage(),
+ 'rev_id' => $rev_id );
}
if ( $rev ) {
@@ -3742,13 +3830,8 @@ class Parser {
* @return Array ( File or false, Title of file )
*/
function fetchFileAndTitle( $title, $options = array() ) {
- if ( isset( $options['broken'] ) ) {
- $file = false; // broken thumbnail forced by hook
- } elseif ( isset( $options['sha1'] ) ) { // get by (sha1,timestamp)
- $file = RepoGroup::singleton()->findFileFromKey( $options['sha1'], $options );
- } else { // get by (name,timestamp)
- $file = wfFindFile( $title, $options );
- }
+ $file = $this->fetchFileNoRegister( $title, $options );
+
$time = $file ? $file->getTimestamp() : false;
$sha1 = $file ? $file->getSha1() : false;
# Register the file as a dependency...
@@ -3767,6 +3850,27 @@ class Parser {
}
/**
+ * Helper function for fetchFileAndTitle.
+ *
+ * Also useful if you need to fetch a file but not use it yet,
+ * for example to get the file's handler.
+ *
+ * @param Title $title
+ * @param array $options Array of options to RepoGroup::findFile
+ * @return File or false
+ */
+ protected function fetchFileNoRegister( $title, $options = array() ) {
+ if ( isset( $options['broken'] ) ) {
+ $file = false; // broken thumbnail forced by hook
+ } elseif ( isset( $options['sha1'] ) ) { // get by (sha1,timestamp)
+ $file = RepoGroup::singleton()->findFileFromKey( $options['sha1'], $options );
+ } else { // get by (name,timestamp)
+ $file = wfFindFile( $title, $options );
+ }
+ return $file;
+ }
+
+ /**
* Transclude an interwiki link.
*
* @param $title Title
@@ -3781,7 +3885,7 @@ class Parser {
return wfMessage( 'scarytranscludedisabled' )->inContentLanguage()->text();
}
- $url = $title->getFullUrl( "action=$action" );
+ $url = $title->getFullURL( array( 'action' => $action ) );
if ( strlen( $url ) > 255 ) {
return wfMessage( 'scarytranscludetoolong' )->inContentLanguage()->text();
@@ -3817,8 +3921,8 @@ class Parser {
$dbw->replace( 'transcache', array( 'tc_url' ), array(
'tc_url' => $url,
'tc_time' => $dbw->timestamp( time() ),
- 'tc_contents' => $text)
- );
+ 'tc_contents' => $text
+ ) );
return $text;
}
@@ -4097,8 +4201,8 @@ class Parser {
* @return mixed|string
* @private
*/
- function formatHeadings( $text, $origText, $isMain=true ) {
- global $wgMaxTocLevel, $wgHtml5, $wgExperimentalHtmlIds;
+ function formatHeadings( $text, $origText, $isMain = true ) {
+ global $wgMaxTocLevel, $wgExperimentalHtmlIds;
# Inhibit editsection links if requested in the page
if ( isset( $this->mDoubleUnderscores['noeditsection'] ) ) {
@@ -4114,7 +4218,7 @@ class Parser {
# Get all headlines for numbering them and adding funky stuff like [edit]
# links - this is for later, but we need the number of headlines right now
$matches = array();
- $numMatches = preg_match_all( '/<H(?P<level>[1-6])(?P<attrib>.*?'.'>)(?P<header>.*?)<\/H[1-6] *>/i', $text, $matches );
+ $numMatches = preg_match_all( '/<H(?P<level>[1-6])(?P<attrib>.*?' . '>)\s*(?P<header>[\s\S]*?)\s*<\/H[1-6] *>/i', $text, $matches );
# if there are fewer than 4 headlines in the article, do not show TOC
# unless it's been explicitly enabled.
@@ -4176,7 +4280,7 @@ class Parser {
$serial = $markerMatches[1];
list( $titleText, $sectionIndex ) = $this->mHeadings[$serial];
$isTemplate = ( $titleText != $baseTitleText );
- $headline = preg_replace( "/^$markerRegex/", "", $headline );
+ $headline = preg_replace( "/^$markerRegex\\s*/", "", $headline );
}
if ( $toclevel ) {
@@ -4231,7 +4335,7 @@ class Parser {
# count number of headlines for each level
$sublevelCount[$toclevel]++;
$dot = 0;
- for( $i = 1; $i <= $toclevel; $i++ ) {
+ for ( $i = 1; $i <= $toclevel; $i++ ) {
if ( !empty( $sublevelCount[$i] ) ) {
if ( $dot ) {
$numbering .= '.';
@@ -4263,20 +4367,20 @@ class Parser {
# 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( '#<(?!/?(span|sup|sub|i|b)(?: [^>]*)?>).*?'.'>#', '#<(/?(?:span(?: dir="(?:rtl|ltr)")?|sup|sub|i|b))(?: .*?)?'.'>#' ),
+ array( '#<(?!/?(span|sup|sub|i|b)(?: [^>]*)?>).*?' . '>#', '#<(/?(?:span(?: dir="(?:rtl|ltr)")?|sup|sub|i|b))(?: .*?)?' . '>#' ),
array( '', '<$1>' ),
$safeHeadline
);
$tocline = trim( $tocline );
# For the anchor, strip out HTML-y stuff period
- $safeHeadline = preg_replace( '/<.*?'.'>/', '', $safeHeadline );
+ $safeHeadline = preg_replace( '/<.*?' . '>/', '', $safeHeadline );
$safeHeadline = Sanitizer::normalizeSectionNameWhitespace( $safeHeadline );
# Save headline for section edit hint before it's escaped
$headlineHint = $safeHeadline;
- if ( $wgHtml5 && $wgExperimentalHtmlIds ) {
+ if ( $wgExperimentalHtmlIds ) {
# For reverse compatibility, provide an id that's
# HTML4-compatible, like we used to.
#
@@ -4346,7 +4450,8 @@ class Parser {
# Add the section to the section tree
# Find the DOM node for this header
- while ( $node && !$isTemplate ) {
+ $noOffset = ( $isTemplate || $sectionIndex === false );
+ while ( $node && !$noOffset ) {
if ( $node->getName() === 'h' ) {
$bits = $node->splitHeading();
if ( $bits['i'] == $sectionIndex ) {
@@ -4364,7 +4469,7 @@ class Parser {
'number' => $numbering,
'index' => ( $isTemplate ? 'T-' : '' ) . $sectionIndex,
'fromtitle' => $titleText,
- 'byteoffset' => ( $isTemplate ? null : $byteOffset ),
+ 'byteoffset' => ( $noOffset ? null : $byteOffset ),
'anchor' => $anchor,
);
@@ -4415,6 +4520,7 @@ class Parser {
}
$toc = Linker::tocList( $toc, $this->mOptions->getUserLangObj() );
$this->mOutput->setTOCHTML( $toc );
+ $toc = self::TOC_START . $toc . self::TOC_END;
}
if ( $isMain ) {
@@ -4422,7 +4528,7 @@ class Parser {
}
# split up and insert constructed headlines
- $blocks = preg_split( '/<H[1-6].*?' . '>.*?<\/H[1-6]>/i', $text );
+ $blocks = preg_split( '/<H[1-6].*?' . '>[\s\S]*?<\/H[1-6]>/i', $text );
$i = 0;
// build an array of document sections
@@ -4484,7 +4590,7 @@ class Parser {
"\r\n" => "\n",
);
$text = str_replace( array_keys( $pairs ), array_values( $pairs ), $text );
- if( $options->getPreSaveTransform() ) {
+ if ( $options->getPreSaveTransform() ) {
$text = $this->pstPass2( $text, $user );
}
$text = $this->mStripState->unstripBoth( $text );
@@ -4504,7 +4610,7 @@ class Parser {
* @return string
*/
function pstPass2( $text, $user ) {
- global $wgContLang, $wgLocaltimezone;
+ global $wgContLang;
# Note: This is the timestamp saved as hardcoded wikitext to
# the database, we use $wgContLang here in order to give
@@ -4512,19 +4618,11 @@ class Parser {
# than the one selected in each user's preferences.
# (see also bug 12815)
$ts = $this->mOptions->getTimestamp();
- if ( isset( $wgLocaltimezone ) ) {
- $tz = $wgLocaltimezone;
- } else {
- $tz = date_default_timezone_get();
- }
+ $timestamp = MWTimestamp::getLocalInstance( $ts );
+ $ts = $timestamp->format( 'YmdHis' );
+ $tzMsg = $timestamp->format( 'T' ); # might vary on DST changeover!
- $unixts = wfTimestamp( TS_UNIX, $ts );
- $oldtz = date_default_timezone_get();
- date_default_timezone_set( $tz );
- $ts = date( 'YmdHis', $unixts );
- $tzMsg = date( 'T', $unixts ); # might vary on DST changeover!
-
- # Allow translation of timezones through wiki. date() can return
+ # Allow translation of timezones through wiki. format() can return
# whatever crap the system uses, localised or not, so we cannot
# ship premade translations.
$key = 'timezone-' . strtolower( trim( $tzMsg ) );
@@ -4533,8 +4631,6 @@ class Parser {
$tzMsg = $msg->text();
}
- date_default_timezone_set( $oldtz );
-
$d = $wgContLang->timeanddate( $ts, false, false ) . " ($tzMsg)";
# Variable replacement
@@ -4603,8 +4699,9 @@ class Parser {
$username = $user->getName();
# If not given, retrieve from the user object.
- if ( $nickname === false )
+ if ( $nickname === false ) {
$nickname = $user->getOption( 'nickname' );
+ }
if ( is_null( $fancySig ) ) {
$fancySig = $user->getBoolOption( 'fancysig' );
@@ -4645,7 +4742,7 @@ class Parser {
* @return mixed An expanded string, or false if invalid.
*/
function validateSig( $text ) {
- return( Xml::isWellFormedXmlFragment( $text ) ? $text : false );
+ return Xml::isWellFormedXmlFragment( $text ) ? $text : false;
}
/**
@@ -4883,8 +4980,9 @@ class Parser {
# Add to function cache
$mw = MagicWord::get( $id );
- if ( !$mw )
+ if ( !$mw ) {
throw new MWException( __METHOD__ . '() expecting a magic word identifier.' );
+ }
$synonyms = $mw->getSynonyms();
$sensitive = intval( $mw->isCaseSensitive() );
@@ -4928,7 +5026,9 @@ class Parser {
*/
function setFunctionTagHook( $tag, $callback, $flags ) {
$tag = strtolower( $tag );
- if ( preg_match( '/[<>\r\n]/', $tag, $m ) ) throw new MWException( "Invalid character {$m[0]} in setFunctionTagHook('$tag', ...) call" );
+ if ( preg_match( '/[<>\r\n]/', $tag, $m ) ) {
+ throw new MWException( "Invalid character {$m[0]} in setFunctionTagHook('$tag', ...) call" );
+ }
$old = isset( $this->mFunctionTagHooks[$tag] ) ?
$this->mFunctionTagHooks[$tag] : null;
$this->mFunctionTagHooks[$tag] = array( $callback, $flags );
@@ -4979,7 +5079,20 @@ class Parser {
* @return string HTML
*/
function renderImageGallery( $text, $params ) {
- $ig = new ImageGallery();
+ wfProfileIn( __METHOD__ );
+
+ $mode = false;
+ if ( isset( $params['mode'] ) ) {
+ $mode = $params['mode'];
+ }
+
+ try {
+ $ig = ImageGalleryBase::factory( $mode );
+ } catch ( MWException $e ) {
+ // If invalid type set, fallback to default.
+ $ig = ImageGalleryBase::factory( false );
+ }
+
$ig->setContextTitle( $this->mTitle );
$ig->setShowBytes( false );
$ig->setShowFilename( false );
@@ -5007,6 +5120,7 @@ class Parser {
if ( isset( $params['heights'] ) ) {
$ig->setHeights( $params['heights'] );
}
+ $ig->setAdditionalOptions( $params );
wfRunHooks( 'BeforeParserrenderImageGallery', array( &$this, &$ig ) );
@@ -5030,38 +5144,81 @@ class Parser {
continue;
}
+ # We need to get what handler the file uses, to figure out parameters.
+ # Note, a hook can overide the file name, and chose an entirely different
+ # file (which potentially could be of a different type and have different handler).
+ $options = array();
+ $descQuery = false;
+ wfRunHooks( 'BeforeParserFetchFileAndTitle',
+ array( $this, $title, &$options, &$descQuery ) );
+ # Don't register it now, as ImageGallery does that later.
+ $file = $this->fetchFileNoRegister( $title, $options );
+ $handler = $file ? $file->getHandler() : false;
+
+ wfProfileIn( __METHOD__ . '-getMagicWord' );
+ $paramMap = array(
+ 'img_alt' => 'gallery-internal-alt',
+ 'img_link' => 'gallery-internal-link',
+ );
+ if ( $handler ) {
+ $paramMap = $paramMap + $handler->getParamMap();
+ // We don't want people to specify per-image widths.
+ // Additionally the width parameter would need special casing anyhow.
+ unset( $paramMap['img_width'] );
+ }
+
+ $mwArray = new MagicWordArray( array_keys( $paramMap ) );
+ wfProfileOut( __METHOD__ . '-getMagicWord' );
+
$label = '';
$alt = '';
$link = '';
+ $handlerOptions = array();
if ( isset( $matches[3] ) ) {
// look for an |alt= definition while trying not to break existing
// captions with multiple pipes (|) in it, until a more sensible grammar
// is defined for images in galleries
+ // FIXME: Doing recursiveTagParse at this stage, and the trim before
+ // splitting on '|' is a bit odd, and different from makeImage.
$matches[3] = $this->recursiveTagParse( trim( $matches[3] ) );
$parameterMatches = StringUtils::explode( '|', $matches[3] );
- $magicWordAlt = MagicWord::get( 'img_alt' );
- $magicWordLink = MagicWord::get( 'img_link' );
foreach ( $parameterMatches as $parameterMatch ) {
- if ( $match = $magicWordAlt->matchVariableStartToEnd( $parameterMatch ) ) {
- $alt = $this->stripAltText( $match, false );
- }
- 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", $linkValue ) ) {
- $link = $linkValue;
- } else {
- $localLinkTitle = Title::newFromText( $linkValue );
- if ( $localLinkTitle !== null ) {
- $link = $localLinkTitle->getLocalURL();
+ list( $magicName, $match ) = $mwArray->matchVariableStartToEnd( $parameterMatch );
+ if ( $magicName ) {
+ $paramName = $paramMap[$magicName];
+
+ switch ( $paramName ) {
+ case 'gallery-internal-alt':
+ $alt = $this->stripAltText( $match, false );
+ break;
+ case 'gallery-internal-link':
+ $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", $linkValue ) ) {
+ $link = $linkValue;
+ } else {
+ $localLinkTitle = Title::newFromText( $linkValue );
+ if ( $localLinkTitle !== null ) {
+ $link = $localLinkTitle->getLocalURL();
+ }
+ }
+ break;
+ default:
+ // Must be a handler specific parameter.
+ if ( $handler->validateParam( $paramName, $match ) ) {
+ $handlerOptions[$paramName] = $match;
+ } else {
+ // Guess not. Append it to the caption.
+ wfDebug( "$parameterMatch failed parameter validation" );
+ $label .= '|' . $parameterMatch;
}
}
- }
- else {
+
+ } else {
// concatenate all other pipes
$label .= '|' . $parameterMatch;
}
@@ -5070,9 +5227,11 @@ class Parser {
$label = substr( $label, 1 );
}
- $ig->add( $title, $label, $alt, $link );
+ $ig->add( $title, $label, $alt, $link, $handlerOptions );
}
- return $ig->toHTML();
+ $html = $ig->toHTML();
+ wfProfileOut( __METHOD__ );
+ return $html;
}
/**
@@ -5187,14 +5346,14 @@ class Parser {
# Special case; width and height come in one variable together
if ( $type === 'handler' && $paramName === 'width' ) {
$parsedWidthParam = $this->parseWidthParam( $value );
- if( isset( $parsedWidthParam['width'] ) ) {
+ if ( isset( $parsedWidthParam['width'] ) ) {
$width = $parsedWidthParam['width'];
if ( $handler->validateParam( 'width', $width ) ) {
$params[$type]['width'] = $width;
$validated = true;
}
}
- if( isset( $parsedWidthParam['height'] ) ) {
+ if ( isset( $parsedWidthParam['height'] ) ) {
$height = $parsedWidthParam['height'];
if ( $handler->validateParam( 'height', $height ) ) {
$params[$type]['height'] = $height;
@@ -5208,7 +5367,7 @@ class Parser {
$validated = $handler->validateParam( $paramName, $value );
} else {
# Validate internal parameters
- switch( $paramName ) {
+ switch ( $paramName ) {
case 'manualthumb':
case 'alt':
case 'class':
@@ -5646,14 +5805,14 @@ class Parser {
* @return String: user name
*/
function getRevisionUser() {
- if( is_null( $this->mRevisionUser ) ) {
+ if ( is_null( $this->mRevisionUser ) ) {
$revObject = $this->getRevisionObject();
# if this template is subst: the revision id will be blank,
# so just use the current user's name
- if( $revObject ) {
+ if ( $revObject ) {
$this->mRevisionUser = $revObject->getUserText();
- } elseif( $this->ot['wiki'] || $this->mOptions->getIsPreview() ) {
+ } elseif ( $this->ot['wiki'] || $this->mOptions->getIsPreview() ) {
$this->mRevisionUser = $this->getUser()->getName();
}
}
@@ -5661,6 +5820,27 @@ class Parser {
}
/**
+ * Get the size of the revision
+ *
+ * @return int|null revision size
+ */
+ function getRevisionSize() {
+ if ( is_null( $this->mRevisionSize ) ) {
+ $revObject = $this->getRevisionObject();
+
+ # if this variable is subst: the revision id will be blank,
+ # so just use the parser input size, because the own substituation
+ # will change the size.
+ if ( $revObject ) {
+ $this->mRevisionSize = $revObject->getSize();
+ } elseif ( $this->ot['wiki'] || $this->mOptions->getIsPreview() ) {
+ $this->mRevisionSize = $this->mInputSize;
+ }
+ }
+ return $this->mRevisionSize;
+ }
+
+ /**
* Mutator for $mDefaultSort
*
* @param string $sort New value
@@ -5933,7 +6113,7 @@ class Parser {
*/
public function parseWidthParam( $value ) {
$parsedWidthParam = array();
- if( $value === '' ) {
+ if ( $value === '' ) {
return $parsedWidthParam;
}
$m = array();
diff --git a/includes/parser/ParserCache.php b/includes/parser/ParserCache.php
index 0faa40a8..7053f134 100644
--- a/includes/parser/ParserCache.php
+++ b/includes/parser/ParserCache.php
@@ -67,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;
@@ -128,7 +128,7 @@ class ParserCache {
public function getKey( $article, $popts, $useOutdated = true ) {
global $wgCacheEpoch;
- if( $popts instanceof User ) {
+ if ( $popts instanceof User ) {
wfWarn( "Use of outdated prototype ParserCache::getKey( &\$article, &\$user )\n" );
$popts = ParserOptions::newFromUser( $popts );
}
@@ -223,19 +223,19 @@ class ParserCache {
* @param $parserOutput ParserOutput
* @param $article Article
* @param $popts ParserOptions
+ * @param $cacheTime Time when the cache was generated
*/
- public function save( $parserOutput, $article, $popts ) {
+ public function save( $parserOutput, $article, $popts, $cacheTime = null ) {
$expire = $parserOutput->getCacheExpiry();
-
- if( $expire > 0 ) {
- $now = wfTimestampNow();
+ if ( $expire > 0 ) {
+ $cacheTime = $cacheTime ?: wfTimestampNow();
$optionsKey = new CacheTime;
$optionsKey->mUsedOptions = $parserOutput->getUsedOptions();
$optionsKey->updateCacheExpiry( $expire );
- $optionsKey->setCacheTime( $now );
- $parserOutput->setCacheTime( $now );
+ $optionsKey->setCacheTime( $cacheTime );
+ $parserOutput->setCacheTime( $cacheTime );
$optionsKey->setContainsOldMagic( $parserOutput->containsOldMagic() );
@@ -245,8 +245,8 @@ class ParserCache {
// Save the timestamp so that we don't have to load the revision row on view
$parserOutput->setTimestamp( $article->getTimestamp() );
- $parserOutput->mText .= "\n<!-- Saved in parser cache with key $parserOutputKey and timestamp $now -->\n";
- wfDebug( "Saved in parser cache with key $parserOutputKey and timestamp $now\n" );
+ $parserOutput->mText .= "\n<!-- Saved in parser cache with key $parserOutputKey and timestamp $cacheTime\n -->\n";
+ wfDebug( "Saved in parser cache with key $parserOutputKey and timestamp $cacheTime\n" );
// Save the parser output
$this->mMemc->set( $parserOutputKey, $parserOutput, $expire );
diff --git a/includes/parser/ParserOptions.php b/includes/parser/ParserOptions.php
index 3eb83e36..e12f32d8 100644
--- a/includes/parser/ParserOptions.php
+++ b/includes/parser/ParserOptions.php
@@ -240,6 +240,7 @@ class ParserOptions {
function getExternalLinkTarget() { return $this->mExternalLinkTarget; }
function getDisableContentConversion() { return $this->mDisableContentConversion; }
function getDisableTitleConversion() { return $this->mDisableTitleConversion; }
+ /** @deprecated since 1.22 use User::getOption('math') instead */
function getMath() { $this->optionUsed( 'math' );
return $this->mMath; }
function getThumbSize() { $this->optionUsed( 'thumbsize' );
@@ -280,9 +281,17 @@ class ParserOptions {
}
/**
+ * Get the user language used by the parser for this page.
+ *
* You shouldn't use this. Really. $parser->getFunctionLang() is all you need.
- * Using this fragments the cache and is discouraged. Yes, {{int: }} uses this,
- * producing inconsistent tables (Bug 14404).
+ *
+ * To avoid side-effects where the page will be rendered based on the language
+ * of the user who last saved, this function will triger a cache fragmentation.
+ * Usage of this method is discouraged for that reason.
+ *
+ * When saving, this will return the default language instead of the user's.
+ *
+ * {{int: }} uses this which used to produce inconsistent link tables (bug 14404).
*
* @return Language object
* @since 1.19
@@ -312,7 +321,7 @@ class ParserOptions {
function setAllowSpecialInclusion( $x ) { return wfSetVar( $this->mAllowSpecialInclusion, $x ); }
function setTidy( $x ) { return wfSetVar( $this->mTidy, $x ); }
- /** @deprecated in 1.19; will be removed in 1.20 */
+ /** @deprecated in 1.19 */
function setSkin( $x ) { wfDeprecated( __METHOD__, '1.19' ); }
function setInterfaceMessage( $x ) { return wfSetVar( $this->mInterfaceMessage, $x ); }
function setTargetLanguage( $x ) { return wfSetVar( $this->mTargetLanguage, $x, true ); }
@@ -330,6 +339,7 @@ class ParserOptions {
function setExternalLinkTarget( $x ) { return wfSetVar( $this->mExternalLinkTarget, $x ); }
function disableContentConversion( $x = true ) { return wfSetVar( $this->mDisableContentConversion, $x ); }
function disableTitleConversion( $x = true ) { return wfSetVar( $this->mDisableTitleConversion, $x ); }
+ /** @deprecated since 1.22 */
function setMath( $x ) { return wfSetVar( $this->mMath, $x ); }
function setUserLang( $x ) {
if ( is_string( $x ) ) {
@@ -538,7 +548,7 @@ class ParserOptions {
// add in language specific options, if any
// @todo FIXME: This is just a way of retrieving the url/user preferred variant
- if( !is_null( $title ) ) {
+ if ( !is_null( $title ) ) {
$confstr .= $title->getPageLanguage()->getExtraHashOptions();
} else {
global $wgContLang;
@@ -557,8 +567,9 @@ class ParserOptions {
$confstr .= '!printable=1';
}
- if ( $this->mExtraKey != '' )
+ if ( $this->mExtraKey != '' ) {
$confstr .= $this->mExtraKey;
+ }
// Give a chance for extensions to modify the hash, if they have
// extra options or other effects on the parser cache.
diff --git a/includes/parser/ParserOutput.php b/includes/parser/ParserOutput.php
index db649f11..502f0fd1 100644
--- a/includes/parser/ParserOutput.php
+++ b/includes/parser/ParserOutput.php
@@ -47,11 +47,14 @@ class ParserOutput extends CacheTime {
$mEditSectionTokens = false, # prefix/suffix markers if edit sections were output as tokens
$mProperties = array(), # Name/value pairs to be cached in the DB
$mTOCHTML = '', # HTML of the TOC
- $mTimestamp; # Timestamp of the revision
+ $mTimestamp, # Timestamp of the revision
+ $mTOCEnabled = true; # Whether TOC should be shown, can't override __NOTOC__
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 DataUpdate, used to save info from the page somewhere else.
private $mExtensionData = array(); # extra data used by extensions
+ private $mLimitReportData = array(); # Parser limit report data
+ private $mParseStartTime = array(); # Timestamps for getTimeSinceStart()
const EDITSECTION_REGEX = '#<(?:mw:)?editsection page="(.*?)" section="(.*?)"(?:/>|>(.*?)(</(?:mw:)?editsection>))#';
@@ -66,11 +69,27 @@ class ParserOutput extends CacheTime {
}
function getText() {
+ wfProfileIn( __METHOD__ );
+ $text = $this->mText;
if ( $this->mEditSectionTokens ) {
- return preg_replace_callback( ParserOutput::EDITSECTION_REGEX,
- array( &$this, 'replaceEditSectionLinksCallback' ), $this->mText );
+ $text = preg_replace_callback( ParserOutput::EDITSECTION_REGEX,
+ array( &$this, 'replaceEditSectionLinksCallback' ), $text );
+ } else {
+ $text = preg_replace( ParserOutput::EDITSECTION_REGEX, '', $text );
+ }
+
+ // If you have an old cached version of this class - sorry, you can't disable the TOC
+ if ( isset( $this->mTOCEnabled ) && $this->mTOCEnabled ) {
+ $text = str_replace( array( Parser::TOC_START, Parser::TOC_END ), '', $text );
+ } else {
+ $text = preg_replace(
+ '#'. preg_quote( Parser::TOC_START ) . '.*?' . preg_quote( Parser::TOC_END ) . '#s',
+ '',
+ $text
+ );
}
- return preg_replace( ParserOutput::EDITSECTION_REGEX, '', $this->mText );
+ wfProfileOut( __METHOD__ );
+ return $text;
}
/**
@@ -120,6 +139,8 @@ class ParserOutput extends CacheTime {
function getIndexPolicy() { return $this->mIndexPolicy; }
function getTOCHTML() { return $this->mTOCHTML; }
function getTimestamp() { return $this->mTimestamp; }
+ function getLimitReportData() { return $this->mLimitReportData; }
+ function getTOCEnabled() { return $this->mTOCEnabled; }
function setText( $text ) { return wfSetVar( $this->mText, $text ); }
function setLanguageLinks( $ll ) { return wfSetVar( $this->mLanguageLinks, $ll ); }
@@ -131,6 +152,7 @@ class ParserOutput extends CacheTime {
function setIndexPolicy( $policy ) { return wfSetVar( $this->mIndexPolicy, $policy ); }
function setTOCHTML( $tochtml ) { return wfSetVar( $this->mTOCHTML, $tochtml ); }
function setTimestamp( $timestamp ) { return wfSetVar( $this->mTimestamp, $timestamp ); }
+ function setTOCEnabled( $flag ) { return wfSetVar( $this->mTOCEnabled, $flag ); }
function addCategory( $c, $sort ) { $this->mCategories[$c] = $sort; }
function addLanguageLink( $t ) { $this->mLanguageLinks[] = $t; }
@@ -143,10 +165,10 @@ class ParserOutput extends CacheTime {
function setNewSection( $value ) {
$this->mNewSection = (bool)$value;
}
- function hideNewSection ( $value ) {
+ function hideNewSection( $value ) {
$this->mHideNewSection = (bool)$value;
}
- function getHideNewSection () {
+ function getHideNewSection() {
return (bool)$this->mHideNewSection;
}
function getNewSection() {
@@ -176,10 +198,10 @@ class ParserOutput extends CacheTime {
global $wgServer, $wgRegisterInternalExternals;
$registerExternalLink = true;
- if( !$wgRegisterInternalExternals ) {
+ if ( !$wgRegisterInternalExternals ) {
$registerExternalLink = !self::isLinkInternal( $wgServer, $url );
}
- if( $registerExternalLink ) {
+ if ( $registerExternalLink ) {
$this->mExternalLinks[$url] = 1;
}
}
@@ -201,11 +223,11 @@ class ParserOutput extends CacheTime {
if ( $ns == NS_MEDIA ) {
// Normalize this pseudo-alias if it makes it down here...
$ns = NS_FILE;
- } elseif( $ns == NS_SPECIAL ) {
+ } elseif ( $ns == NS_SPECIAL ) {
// We don't record Special: links currently
// It might actually be wise to, but we'd need to do some normalization.
return;
- } elseif( $dbk === '' ) {
+ } elseif ( $dbk === '' ) {
// Don't record self links - [[#Foo]]
return;
}
@@ -258,7 +280,7 @@ class ParserOutput extends CacheTime {
*/
function addInterwikiLink( $title ) {
$prefix = $title->getInterwiki();
- if( $prefix == '' ) {
+ if ( $prefix == '' ) {
throw new MWException( 'Non-interwiki link passed, internal parser error.' );
}
if ( !isset( $this->mInterwikiLinks[$prefix] ) ) {
@@ -281,7 +303,7 @@ class ParserOutput extends CacheTime {
}
public function addModules( $modules ) {
- $this->mModules = array_merge( $this->mModules, (array) $modules );
+ $this->mModules = array_merge( $this->mModules, (array)$modules );
}
public function addModuleScripts( $modules ) {
@@ -329,7 +351,7 @@ class ParserOutput extends CacheTime {
*/
public function getDisplayTitle() {
$t = $this->getTitleText();
- if( $t === '' ) {
+ if ( $t === '' ) {
return false;
}
return $t;
@@ -544,4 +566,67 @@ class ParserOutput extends CacheTime {
return null;
}
+ private static function getTimes( $clock = null ) {
+ $ret = array();
+ if ( !$clock || $clock === 'wall' ) {
+ $ret['wall'] = microtime( true );
+ }
+ if ( ( !$clock || $clock === 'cpu' ) && function_exists( 'getrusage' ) ) {
+ $ru = getrusage();
+ $ret['cpu'] = $ru['ru_utime.tv_sec'] + $ru['ru_utime.tv_usec'] / 1e6;
+ $ret['cpu'] += $ru['ru_stime.tv_sec'] + $ru['ru_stime.tv_usec'] / 1e6;
+ }
+ return $ret;
+ }
+
+ /**
+ * Resets the parse start timestamps for future calls to getTimeSinceStart()
+ * @since 1.22
+ */
+ function resetParseStartTime() {
+ $this->mParseStartTime = self::getTimes();
+ }
+
+ /**
+ * Returns the time since resetParseStartTime() was last called
+ *
+ * Clocks available are:
+ * - wall: Wall clock time
+ * - cpu: CPU time (requires getrusage)
+ *
+ * @since 1.22
+ * @param string $clock
+ * @return float|null
+ */
+ function getTimeSinceStart( $clock ) {
+ if ( !isset( $this->mParseStartTime[$clock] ) ) {
+ return null;
+ }
+
+ $end = self::getTimes( $clock );
+ return $end[$clock] - $this->mParseStartTime[$clock];
+ }
+
+ /**
+ * Sets parser limit report data for a key
+ *
+ * The key is used as the prefix for various messages used for formatting:
+ * - $key: The label for the field in the limit report
+ * - $key-value-text: Message used to format the value in the "NewPP limit
+ * report" HTML comment. If missing, uses $key-format.
+ * - $key-value-html: Message used to format the value in the preview
+ * limit report table. If missing, uses $key-format.
+ * - $key-value: Message used to format the value. If missing, uses "$1".
+ *
+ * Note that all values are interpreted as wikitext, and so should be
+ * encoded with htmlspecialchars() as necessary, but should avoid complex
+ * HTML for sanity of display in the "NewPP limit report" comment.
+ *
+ * @since 1.22
+ * @param string $key Message key
+ * @param mixed $value Appropriate for Message::params()
+ */
+ function setLimitReportData( $key, $value ) {
+ $this->mLimitReportData[$key] = $value;
+ }
}
diff --git a/includes/parser/Parser_DiffTest.php b/includes/parser/Parser_DiffTest.php
index f25340fa..aeae234a 100644
--- a/includes/parser/Parser_DiffTest.php
+++ b/includes/parser/Parser_DiffTest.php
@@ -122,7 +122,7 @@ class Parser_DiffTest
function setFunctionHook( $id, $callback, $flags = 0 ) {
$this->init();
- foreach ( $this->parsers as $parser ) {
+ foreach ( $this->parsers as $parser ) {
$parser->setFunctionHook( $id, $callback, $flags );
}
}
diff --git a/includes/parser/Parser_LinkHooks.php b/includes/parser/Parser_LinkHooks.php
deleted file mode 100644
index b2cdc41a..00000000
--- a/includes/parser/Parser_LinkHooks.php
+++ /dev/null
@@ -1,326 +0,0 @@
-<?php
-/**
- * Modified version of the PHP parser with hooks for wiki links; experimental
- *
- * 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
- */
-
-/**
- * Parser with LinkHooks experiment
- * @ingroup Parser
- */
-class Parser_LinkHooks extends Parser {
- /**
- * Update this version number when the ParserOutput format
- * changes in an incompatible way, so the parser cache
- * 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;
-
- # Constants needed for external link processing
- # Everything except bracket, space, or control characters
- const EXT_LINK_URL_CLASS = '[^][<>"\\x00-\\x20\\x7F]';
- const EXT_IMAGE_REGEX = '/^(http:\/\/|https:\/\/)([^][<>"\\x00-\\x20\\x7F]+)
- \\/([A-Za-z0-9_.,~%\\-+&;#*?!=()@\\x80-\\xFF]+)\\.((?i)gif|png|jpg|jpeg)$/Sx';
-
- /**#@+
- * @private
- */
- # Persistent:
- var $mLinkHooks;
-
- /**#@-*/
-
- /**
- * Constructor
- */
- public function __construct( $conf = array() ) {
- parent::__construct( $conf );
- $this->mLinkHooks = array();
- }
-
- /**
- * Do various kinds of initialisation on the first call of the parser
- */
- function firstCallInit() {
- parent::__construct();
- if ( !$this->mFirstCall ) {
- return;
- }
- $this->mFirstCall = false;
-
- wfProfileIn( __METHOD__ );
-
- $this->setHook( 'pre', array( $this, 'renderPreTag' ) );
- CoreParserFunctions::register( $this );
- CoreLinkFunctions::register( $this );
- $this->initialiseVariables();
-
- wfRunHooks( 'ParserFirstCallInit', array( &$this ) );
- wfProfileOut( __METHOD__ );
- }
-
- /**
- * 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 ) { ... }
- *
- * Or with SLH_PATTERN:
- * function myLinkCallback( $parser, $holders, $markers, )
- * &$titleText, &$sortText = null, &$leadingColon = false ) { ... }
- *
- * The callback may either return a number of different possible values:
- * String) Text result of the link
- * True) (Treat as link) Parse the link according to normal link rules
- * False) (Bad link) Just output the raw wikitext (You may modify the text first)
- *
- * @param $ns Integer or String: the Namespace ID or regex pattern if SLH_PATTERN is set
- * @param $callback Mixed: the callback function (and object) to use
- * @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.' );
- $oldVal = isset( $this->mLinkHooks[$ns] ) ? $this->mLinkHooks[$ns][0] : null;
- $this->mLinkHooks[$ns] = array( $callback, $flags );
- return $oldVal;
- }
-
- /**
- * Get all registered link hook identifiers
- *
- * @return array
- */
- function getLinkHooks() {
- return array_keys( $this->mLinkHooks );
- }
-
- /**
- * Process [[ ]] wikilinks
- * @param $s
- * @throws MWException
- * @return LinkHolderArray
- *
- * @private
- */
- function replaceInternalLinks2( &$s ) {
- wfProfileIn( __METHOD__ );
-
- wfProfileIn( __METHOD__ . '-setup' );
- static $tc = false, $titleRegex; //$e1, $e1_img;
- if( !$tc ) {
- # the % is needed to support urlencoded titles as well
- $tc = Title::legalChars() . '#%';
- # Match a link having the form [[namespace:link|alternate]]trail
- //$e1 = "/^([{$tc}]+)(?:\\|(.+?))?]](.*)\$/sD";
- # Match cases where there is no "]]", which might still be images
- //$e1_img = "/^([{$tc}]+)\\|(.*)\$/sD";
- # Match a valid plain title
- $titleRegex = "/^([{$tc}]+)$/sD";
- }
-
- $holders = new LinkHolderArray( $this );
-
- if( is_null( $this->mTitle ) ) {
- wfProfileOut( __METHOD__ . '-setup' );
- wfProfileOut( __METHOD__ );
- throw new MWException( __METHOD__ . ": \$this->mTitle is null\n" );
- }
-
- wfProfileOut( __METHOD__ . '-setup' );
-
- $offset = 0;
- $offsetStack = array();
- $markers = new LinkMarkerReplacer( $this, $holders, array( &$this, 'replaceInternalLinksCallback' ) );
- while( true ) {
- $startBracketOffset = strpos( $s, '[[', $offset );
- $endBracketOffset = strpos( $s, ']]', $offset );
- # Finish when there are no more brackets
- if( $startBracketOffset === false && $endBracketOffset === false ) break;
- # 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;
- # When we only found one, check which it is
- else $isStart = $startBracketOffset !== false;
- $bracketOffset = $isStart ? $startBracketOffset : $endBracketOffset;
- if( $isStart ) {
- /** Opening bracket **/
- # Just push our current offset in the string onto the stack
- $offsetStack[] = $startBracketOffset;
- } else {
- /** Closing bracket **/
- # Pop the start pos for our current link zone off the stack
- $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 ) ) {
- # Extract text inside the link
- @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 );
- # Replace the current link with the marker
- $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 );
- continue;
- }
- # ToDo: Some LinkHooks may allow recursive links inside of
- # the link text, create a regex that also matches our
- # <!-- LINKMARKER ### --> sequence in titles
- # 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' );
- $s = $markers->expand( $s );
- 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" );
-
- # 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 ) ) {
- 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" );
- # Make title object
- wfProfileIn( __METHOD__ . "-title" );
- $title = Title::newFromText( $this->mStripState->unstripNoWiki( $titleText ) );
- if( !$title ) {
- wfProfileOut( __METHOD__ . "-title" );
- wfProfileOut( __METHOD__ );
- return $wt;
- }
- $ns = $title->getNamespace();
- wfProfileOut( __METHOD__ . "-title" );
-
- # Default for Namespaces is a default link
- # ToDo: Default for patterns is plain wikitext
- $return = true;
- if( isset( $this->mLinkHooks[$ns] ) ) {
- list( $callback, $flags ) = $this->mLinkHooks[$ns];
- if( $flags & SLH_PATTERN ) {
- $args = array( $parser, $holders, $markers, $titleText, &$paramText, &$leadingColon );
- } else {
- $args = array( $parser, $holders, $markers, $title, $titleText, &$paramText, &$leadingColon );
- }
- # Workaround for PHP bug 35229 and similar
- if ( !is_callable( $callback ) ) {
- throw new MWException( "Tag hook for namespace $ns is not callable\n" );
- }
- $return = call_user_func_array( $callback, $args );
- }
- if( $return === true ) {
- # True (treat as plain link) was returned, call the defaultLinkHook
- $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]]";
- }
- # 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();
- $this->parser = $parser;
- $this->holders = $holders;
- $this->callback = $callback;
- }
-
- 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 );
- }
-
- 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];
- $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_DOM.php b/includes/parser/Preprocessor_DOM.php
index d0c57ab5..3138f483 100644
--- a/includes/parser/Preprocessor_DOM.php
+++ b/includes/parser/Preprocessor_DOM.php
@@ -72,9 +72,8 @@ class Preprocessor_DOM implements Preprocessor {
$xml = "<list>";
foreach ( $values as $k => $val ) {
-
if ( is_int( $k ) ) {
- $xml .= "<part><name index=\"$k\"/><value>" . htmlspecialchars( $val ) ."</value></part>";
+ $xml .= "<part><name index=\"$k\"/><value>" . htmlspecialchars( $val ) . "</value></part>";
} else {
$xml .= "<part><name>" . htmlspecialchars( $k ) . "</name>=<value>" . htmlspecialchars( $val ) . "</value></part>";
}
@@ -149,26 +148,28 @@ class Preprocessor_DOM implements Preprocessor {
wfDebugLog( "Preprocessor", "Loaded preprocessor XML from memcached (key $cacheKey)" );
}
}
- }
- if ( $xml === false ) {
- if ( $cacheable ) {
+ if ( $xml === false ) {
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' );
wfDebugLog( "Preprocessor", "Saved preprocessor XML to memcached (key $cacheKey)" );
- } else {
- $xml = $this->preprocessToXml( $text, $flags );
}
-
+ } else {
+ $xml = $this->preprocessToXml( $text, $flags );
}
+
// Fail if the number of elements exceeds acceptable limits
// Do not attempt to generate the DOM
$this->parser->mGeneratedPPNodeCount += substr_count( $xml, '<' );
$max = $this->parser->mOptions->getMaxGeneratedPPNodeCount();
if ( $this->parser->mGeneratedPPNodeCount > $max ) {
+ if ( $cacheable ) {
+ wfProfileOut( __METHOD__ . '-cacheable' );
+ }
+ wfProfileOut( __METHOD__ );
throw new MWException( __METHOD__ . ': generated node count limit exceeded' );
}
@@ -182,16 +183,21 @@ class Preprocessor_DOM implements Preprocessor {
$xml = UtfNormal::cleanUp( $xml );
// 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' );
- }
}
- $obj = new PPNode_DOM( $dom->documentElement );
+ if ( $result ) {
+ $obj = new PPNode_DOM( $dom->documentElement );
+ }
wfProfileOut( __METHOD__ . '-loadXML' );
+
if ( $cacheable ) {
wfProfileOut( __METHOD__ . '-cacheable' );
}
+
wfProfileOut( __METHOD__ );
+
+ if ( !$result ) {
+ throw new MWException( __METHOD__ . ' generated invalid XML' );
+ }
return $obj;
}
@@ -355,9 +361,11 @@ class Preprocessor_DOM implements Preprocessor {
}
// 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.
+
+ // To avoid leaving blank lines, when a sequence of
+ // space-separated comments is both preceded and followed by
+ // a newline (ignoring spaces), then
+ // trim leading and trailing spaces and the trailing newline.
// Find the end
$endPos = strpos( $text, '-->', $i + 4 );
@@ -368,10 +376,25 @@ class Preprocessor_DOM implements Preprocessor {
$i = $lengthText;
} else {
// Search backwards for leading whitespace
- $wsStart = $i ? ( $i - strspn( $revText, ' ', $lengthText - $i ) ) : 0;
+ $wsStart = $i ? ( $i - strspn( $revText, " \t", $lengthText - $i ) ) : 0;
+
// Search forwards for trailing whitespace
// $wsEnd will be the position of the last space (or the '>' if there's none)
- $wsEnd = $endPos + 2 + strspn( $text, ' ', $endPos + 3 );
+ $wsEnd = $endPos + 2 + strspn( $text, " \t", $endPos + 3 );
+
+ // Keep looking forward as long as we're finding more
+ // comments.
+ $comments = array( array( $wsStart, $wsEnd ) );
+ while ( substr( $text, $wsEnd + 1, 4 ) == '<!--' ) {
+ $c = strpos( $text, '-->', $wsEnd + 4 );
+ if ( $c === false ) {
+ break;
+ }
+ $c = $c + 2 + strspn( $text, " \t", $c + 3 );
+ $comments[] = array( $wsEnd + 1, $c );
+ $wsEnd = $c;
+ }
+
// 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
@@ -379,14 +402,26 @@ class Preprocessor_DOM implements Preprocessor {
if ( $wsStart > 0 && substr( $text, $wsStart - 1, 1 ) == "\n"
&& substr( $text, $wsEnd + 1, 1 ) == "\n" )
{
- $startPos = $wsStart;
- $endPos = $wsEnd + 1;
// Remove leading whitespace from the end of the accumulator
// Sanity check first though
$wsLength = $i - $wsStart;
- if ( $wsLength > 0 && substr( $accum, -$wsLength ) === str_repeat( ' ', $wsLength ) ) {
+ if ( $wsLength > 0
+ && strspn( $accum, " \t", -$wsLength ) === $wsLength )
+ {
$accum = substr( $accum, 0, -$wsLength );
}
+
+ // Dump all but the last comment to the accumulator
+ foreach ( $comments as $j => $com ) {
+ $startPos = $com[0];
+ $endPos = $com[1] + 1;
+ if ( $j == ( count( $comments ) - 1 ) ) {
+ break;
+ }
+ $inner = substr( $text, $startPos, $endPos - $startPos );
+ $accum .= '<comment>' . htmlspecialchars( $inner ) . '</comment>';
+ }
+
// Do a line-start run next time to look for headings after the comment
$fakeLineStart = true;
} else {
@@ -397,7 +432,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
@@ -432,7 +467,7 @@ class Preprocessor_DOM implements Preprocessor {
}
$tagStartPos = $i;
- if ( $text[$tagEndPos-1] == '/' ) {
+ if ( $text[$tagEndPos - 1] == '/' ) {
$attrEnd = $tagEndPos - 1;
$inner = null;
$i = $tagEndPos + 1;
@@ -569,7 +604,7 @@ class Preprocessor_DOM implements Preprocessor {
'open' => $curChar,
'close' => $rule['end'],
'count' => $count,
- 'lineStart' => ($i > 0 && $text[$i-1] == "\n"),
+ 'lineStart' => ( $i > 0 && $text[$i - 1] == "\n" ),
);
$stack->push( $piece );
@@ -746,7 +781,7 @@ class PPDStack {
$class = $this->elementClass;
$this->stack[] = new $class( $data );
}
- $this->top = $this->stack[ count( $this->stack ) - 1 ];
+ $this->top = $this->stack[count( $this->stack ) - 1];
$this->accum =& $this->top->getAccum();
}
@@ -757,7 +792,7 @@ class PPDStack {
$temp = array_pop( $this->stack );
if ( count( $this->stack ) ) {
- $this->top = $this->stack[ count( $this->stack ) - 1 ];
+ $this->top = $this->stack[count( $this->stack ) - 1];
$this->accum =& $this->top->getAccum();
} else {
$this->top = self::$false;
@@ -1014,11 +1049,13 @@ class PPFrame_DOM implements PPFrame {
while ( count( $iteratorStack ) > 1 ) {
$level = count( $outStack ) - 1;
- $iteratorNode =& $iteratorStack[ $level ];
+ $iteratorNode =& $iteratorStack[$level];
$out =& $outStack[$level];
$index =& $indexStack[$level];
- if ( $iteratorNode instanceof PPNode_DOM ) $iteratorNode = $iteratorNode->node;
+ if ( $iteratorNode instanceof PPNode_DOM ) {
+ $iteratorNode = $iteratorNode->node;
+ }
if ( is_array( $iteratorNode ) ) {
if ( $index >= count( $iteratorNode ) ) {
@@ -1148,9 +1185,7 @@ class PPFrame_DOM implements PPFrame {
# Insert a heading marker only for <h> children of <root>
# This is to stop extractSections from going over multiple tree levels
- if ( $contextNode->parentNode->nodeName == 'root'
- && $this->parser->ot['html'] )
- {
+ if ( $contextNode->parentNode->nodeName == 'root' && $this->parser->ot['html'] ) {
# Insert heading index marker
$headingIndex = $contextNode->getAttribute( 'i' );
$titleText = $this->title->getPrefixedDBkey();
@@ -1206,7 +1241,9 @@ class PPFrame_DOM implements PPFrame {
$first = true;
$s = '';
foreach ( $args as $root ) {
- if ( $root instanceof PPNode_DOM ) $root = $root->node;
+ if ( $root instanceof PPNode_DOM ) {
+ $root = $root->node;
+ }
if ( !is_array( $root ) && !( $root instanceof DOMNodeList ) ) {
$root = array( $root );
}
diff --git a/includes/parser/Preprocessor_Hash.php b/includes/parser/Preprocessor_Hash.php
index fad1adbb..2fc5e118 100644
--- a/includes/parser/Preprocessor_Hash.php
+++ b/includes/parser/Preprocessor_Hash.php
@@ -287,9 +287,11 @@ class Preprocessor_Hash implements Preprocessor {
}
// 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.
+
+ // To avoid leaving blank lines, when a sequence of
+ // space-separated comments is both preceded and followed by
+ // a newline (ignoring spaces), then
+ // trim leading and trailing spaces and the trailing newline.
// Find the end
$endPos = strpos( $text, '-->', $i + 4 );
@@ -300,10 +302,25 @@ class Preprocessor_Hash implements Preprocessor {
$i = $lengthText;
} else {
// Search backwards for leading whitespace
- $wsStart = $i ? ( $i - strspn( $revText, ' ', $lengthText - $i ) ) : 0;
+ $wsStart = $i ? ( $i - strspn( $revText, " \t", $lengthText - $i ) ) : 0;
+
// Search forwards for trailing whitespace
// $wsEnd will be the position of the last space (or the '>' if there's none)
- $wsEnd = $endPos + 2 + strspn( $text, ' ', $endPos + 3 );
+ $wsEnd = $endPos + 2 + strspn( $text, " \t", $endPos + 3 );
+
+ // Keep looking forward as long as we're finding more
+ // comments.
+ $comments = array( array( $wsStart, $wsEnd ) );
+ while ( substr( $text, $wsEnd + 1, 4 ) == '<!--' ) {
+ $c = strpos( $text, '-->', $wsEnd + 4 );
+ if ( $c === false ) {
+ break;
+ }
+ $c = $c + 2 + strspn( $text, " \t", $c + 3 );
+ $comments[] = array( $wsEnd + 1, $c );
+ $wsEnd = $c;
+ }
+
// 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
@@ -311,17 +328,27 @@ class Preprocessor_Hash implements Preprocessor {
if ( $wsStart > 0 && substr( $text, $wsStart - 1, 1 ) == "\n"
&& substr( $text, $wsEnd + 1, 1 ) == "\n" )
{
- $startPos = $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_Hash_Text
- && substr( $accum->lastNode->value, -$wsLength ) === str_repeat( ' ', $wsLength ) )
+ && strspn( $accum->lastNode->value, " \t", -$wsLength ) === $wsLength )
{
$accum->lastNode->value = substr( $accum->lastNode->value, 0, -$wsLength );
}
+
+ // Dump all but the last comment to the accumulator
+ foreach ( $comments as $j => $com ) {
+ $startPos = $com[0];
+ $endPos = $com[1] + 1;
+ if ( $j == ( count( $comments ) - 1 ) ) {
+ break;
+ }
+ $inner = substr( $text, $startPos, $endPos - $startPos );
+ $accum->addNodeWithText( 'comment', $inner );
+ }
+
// Do a line-start run next time to look for headings after the comment
$fakeLineStart = true;
} else {
@@ -332,7 +359,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
@@ -367,7 +394,7 @@ class Preprocessor_Hash implements Preprocessor {
}
$tagStartPos = $i;
- if ( $text[$tagEndPos-1] == '/' ) {
+ if ( $text[$tagEndPos - 1] == '/' ) {
// Short end tag
$attrEnd = $tagEndPos - 1;
$inner = null;
@@ -515,7 +542,7 @@ class Preprocessor_Hash implements Preprocessor {
'open' => $curChar,
'close' => $rule['end'],
'count' => $count,
- 'lineStart' => ($i > 0 && $text[$i-1] == "\n"),
+ 'lineStart' => ( $i > 0 && $text[$i - 1] == "\n" ),
);
$stack->push( $piece );
@@ -591,9 +618,19 @@ class Preprocessor_Hash implements Preprocessor {
$lastNode = $node;
}
if ( !$node ) {
+ if ( $cacheable ) {
+ wfProfileOut( __METHOD__ . '-cache-miss' );
+ wfProfileOut( __METHOD__ . '-cacheable' );
+ }
+ wfProfileOut( __METHOD__ );
throw new MWException( __METHOD__ . ': eqpos not found' );
}
if ( $node->name !== 'equals' ) {
+ if ( $cacheable ) {
+ wfProfileOut( __METHOD__ . '-cache-miss' );
+ wfProfileOut( __METHOD__ . '-cacheable' );
+ }
+ wfProfileOut( __METHOD__ );
throw new MWException( __METHOD__ . ': eqpos is not equals' );
}
$equalsNode = $node;
@@ -952,7 +989,7 @@ class PPFrame_Hash implements PPFrame {
while ( count( $iteratorStack ) > 1 ) {
$level = count( $outStack ) - 1;
- $iteratorNode =& $iteratorStack[ $level ];
+ $iteratorNode =& $iteratorStack[$level];
$out =& $outStack[$level];
$index =& $indexStack[$level];
diff --git a/includes/parser/Tidy.php b/includes/parser/Tidy.php
index 0f7e0d31..32b16aaf 100644
--- a/includes/parser/Tidy.php
+++ b/includes/parser/Tidy.php
@@ -61,7 +61,10 @@ class MWTidyWrapper {
// Replace <mw:editsection> elements with placeholders
$wrappedtext = preg_replace_callback( ParserOutput::EDITSECTION_REGEX,
- array( &$this, 'replaceEditSectionLinksCallback' ), $text );
+ array( &$this, 'replaceCallback' ), $text );
+ // ...and <mw:toc> markers
+ $wrappedtext = preg_replace_callback( '/\<\\/?mw:toc\>/',
+ array( &$this, 'replaceCallback' ), $wrappedtext );
// 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
@@ -80,7 +83,7 @@ class MWTidyWrapper {
*
* @return string
*/
- function replaceEditSectionLinksCallback( $m ) {
+ function replaceCallback( $m ) {
$marker = "{$this->mUniqPrefix}-item-{$this->mMarkerIndex}" . Parser::MARKER_SUFFIX;
$this->mMarkerIndex++;
$this->mTokens->setPair( $marker, $m[0] );
@@ -158,7 +161,7 @@ class MWTidy {
global $wgTidyInternal;
$retval = 0;
- if( $wgTidyInternal ) {
+ if ( $wgTidyInternal ) {
$errorStr = self::execInternalTidy( $text, true, $retval );
} else {
$errorStr = self::execExternalTidy( $text, true, $retval );
@@ -244,7 +247,7 @@ class MWTidy {
global $wgTidyConf, $wgDebugTidy;
wfProfileIn( __METHOD__ );
- if ( !MWInit::classExists( 'tidy' ) ) {
+ if ( !class_exists( 'tidy' ) ) {
wfWarn( "Unable to load internal tidy class." );
$retval = -1;
diff --git a/includes/profiler/Profiler.php b/includes/profiler/Profiler.php
index 5ecdc4f0..2282a3af 100644
--- a/includes/profiler/Profiler.php
+++ b/includes/profiler/Profiler.php
@@ -31,9 +31,11 @@
* @param string $functionname name of the function we will profile
*/
function wfProfileIn( $functionname ) {
- global $wgProfiler;
- if ( $wgProfiler instanceof Profiler || isset( $wgProfiler['class'] ) ) {
- Profiler::instance()->profileIn( $functionname );
+ if ( Profiler::$__instance === null ) { // use this directly to reduce overhead
+ Profiler::instance();
+ }
+ if ( !( Profiler::$__instance instanceof ProfilerStub ) ) {
+ Profiler::$__instance->profileIn( $functionname );
}
}
@@ -42,9 +44,49 @@ function wfProfileIn( $functionname ) {
* @param string $functionname name of the function we have profiled
*/
function wfProfileOut( $functionname = 'missing' ) {
- global $wgProfiler;
- if ( $wgProfiler instanceof Profiler || isset( $wgProfiler['class'] ) ) {
- Profiler::instance()->profileOut( $functionname );
+ if ( Profiler::$__instance === null ) { // use this directly to reduce overhead
+ Profiler::instance();
+ }
+ if ( !( Profiler::$__instance instanceof ProfilerStub ) ) {
+ Profiler::$__instance->profileOut( $functionname );
+ }
+}
+
+/**
+ * Class for handling function-scope profiling
+ *
+ * @since 1.22
+ */
+class ProfileSection {
+ protected $name; // string; method name
+ protected $enabled = false; // boolean; whether profiling is enabled
+
+ /**
+ * Begin profiling of a function and return an object that ends profiling of
+ * the function when that object leaves scope. As long as the object is not
+ * specifically linked to other objects, it will fall out of scope at the same
+ * moment that the function to be profiled terminates.
+ *
+ * This is typically called like:
+ * <code>$section = new ProfileSection( __METHOD__ );</code>
+ *
+ * @param string $name Name of the function to profile
+ */
+ public function __construct( $name ) {
+ $this->name = $name;
+ if ( Profiler::$__instance === null ) { // use this directly to reduce overhead
+ Profiler::instance();
+ }
+ if ( !( Profiler::$__instance instanceof ProfilerStub ) ) {
+ $this->enabled = true;
+ Profiler::$__instance->profileIn( $this->name );
+ }
+ }
+
+ function __destruct() {
+ if ( $this->enabled ) {
+ Profiler::$__instance->profileOut( $this->name );
+ }
}
}
@@ -53,11 +95,19 @@ function wfProfileOut( $functionname = 'missing' ) {
* @todo document
*/
class Profiler {
- protected $mStack = array(), $mWorkStack = array (), $mCollated = array (),
- $mCalls = array (), $mTotals = array ();
+ protected $mStack = array(), $mWorkStack = array(), $mCollated = array(),
+ $mCalls = array(), $mTotals = array();
protected $mTimeMetric = 'wall';
protected $mProfileID = false, $mCollateDone = false, $mTemplated = false;
- private static $__instance = null;
+
+ protected $mDBLockThreshold = 5.0; // float; seconds
+ /** @var Array DB/server name => (active trx count,timestamp) */
+ protected $mDBTrxHoldingLocks = array();
+ /** @var Array DB/server name => list of (method, elapsed time) */
+ protected $mDBTrxMethodTimes = array();
+
+ /** @var Profiler */
+ public static $__instance = null; // do not call this outside Profiler and ProfileSection
function __construct( $params ) {
if ( isset( $params['timeMetric'] ) ) {
@@ -75,22 +125,18 @@ class Profiler {
* @return Profiler
*/
public static function instance() {
- if( is_null( self::$__instance ) ) {
+ if ( self::$__instance === null ) {
global $wgProfiler;
- if( is_array( $wgProfiler ) ) {
- if( !isset( $wgProfiler['class'] ) ) {
- wfDebug( __METHOD__ . " called without \$wgProfiler['class']"
- . " set, falling back to ProfilerStub for safety\n" );
+ if ( is_array( $wgProfiler ) ) {
+ if ( !isset( $wgProfiler['class'] ) ) {
$class = 'ProfilerStub';
} else {
$class = $wgProfiler['class'];
}
self::$__instance = new $class( $wgProfiler );
- } elseif( $wgProfiler instanceof Profiler ) {
+ } elseif ( $wgProfiler instanceof Profiler ) {
self::$__instance = $wgProfiler; // back-compat
} else {
- wfDebug( __METHOD__ . ' called with bogus $wgProfiler setting,'
- . " falling back to ProfilerStub for safety\n" );
self::$__instance = new ProfilerStub( $wgProfiler );
}
}
@@ -157,7 +203,7 @@ class Profiler {
*/
public function profileIn( $functionname ) {
global $wgDebugFunctionEntry;
- if( $wgDebugFunctionEntry ) {
+ if ( $wgDebugFunctionEntry ) {
$this->debug( str_repeat( ' ', count( $this->mWorkStack ) ) . 'Entering ' . $functionname . "\n" );
}
@@ -174,7 +220,7 @@ class Profiler {
$memory = memory_get_usage();
$time = $this->getTime();
- if( $wgDebugFunctionEntry ) {
+ if ( $wgDebugFunctionEntry ) {
$this->debug( str_repeat( ' ', count( $this->mWorkStack ) - 1 ) . 'Exiting ' . $functionname . "\n" );
}
@@ -183,21 +229,19 @@ class Profiler {
if ( !$bit ) {
$this->debug( "Profiling error, !\$bit: $functionname\n" );
} else {
- //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 ) {
- $message = "Profiling error: in({$bit[0]}), out($functionname)";
- $this->debug( "$message\n" );
- $this->mStack[] = array( $message, 0, 0.0, 0, 0.0, 0 );
- }
- //}
+ 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 ) {
+ $message = "Profiling error: in({$bit[0]}), out($functionname)";
+ $this->debug( "$message\n" );
+ $this->mStack[] = array( $message, 0, 0.0, 0, 0.0, 0 );
+ }
$bit[] = $time;
$bit[] = $memory;
$this->mStack[] = $bit;
+ $this->updateTrxProfiling( $functionname, $time );
}
}
@@ -205,12 +249,89 @@ class Profiler {
* Close opened profiling sections
*/
public function close() {
- while( count( $this->mWorkStack ) ) {
+ while ( count( $this->mWorkStack ) ) {
$this->profileOut( 'close' );
}
}
/**
+ * Mark a DB as in a transaction with one or more writes pending
+ *
+ * Note that there can be multiple connections to a single DB.
+ *
+ * @param string $server DB server
+ * @param string $db DB name
+ */
+ public function transactionWritingIn( $server, $db ) {
+ $name = "{$server} ({$db})";
+ if ( isset( $this->mDBTrxHoldingLocks[$name] ) ) {
+ ++$this->mDBTrxHoldingLocks[$name]['refs'];
+ } else {
+ $this->mDBTrxHoldingLocks[$name] = array( 'refs' => 1, 'start' => microtime( true ) );
+ $this->mDBTrxMethodTimes[$name] = array();
+ }
+ }
+
+ /**
+ * Register the name and time of a method for slow DB trx detection
+ *
+ * @param string $method Function name
+ * @param float $realtime Wal time ellapsed
+ */
+ protected function updateTrxProfiling( $method, $realtime ) {
+ if ( !$this->mDBTrxHoldingLocks ) {
+ return; // short-circuit
+ // @TODO: hardcoded check is a tad janky (what about FOR UPDATE?)
+ } elseif ( !preg_match( '/^query-m: (?!SELECT)/', $method )
+ && $realtime < $this->mDBLockThreshold )
+ {
+ return; // not a DB master query nor slow enough
+ }
+ $now = microtime( true );
+ foreach ( $this->mDBTrxHoldingLocks as $name => $info ) {
+ // Hacky check to exclude entries from before the first TRX write
+ if ( ( $now - $realtime ) >= $info['start'] ) {
+ $this->mDBTrxMethodTimes[$name][] = array( $method, $realtime );
+ }
+ }
+ }
+
+ /**
+ * Mark a DB as no longer in a transaction
+ *
+ * This will check if locks are possibly held for longer than
+ * needed and log any affected transactions to a special DB log.
+ * Note that there can be multiple connections to a single DB.
+ *
+ * @param string $server DB server
+ * @param string $db DB name
+ */
+ public function transactionWritingOut( $server, $db ) {
+ $name = "{$server} ({$db})";
+ if ( --$this->mDBTrxHoldingLocks[$name]['refs'] <= 0 ) {
+ $slow = false;
+ foreach ( $this->mDBTrxMethodTimes[$name] as $info ) {
+ list( $method, $realtime ) = $info;
+ if ( $realtime >= $this->mDBLockThreshold ) {
+ $slow = true;
+ break;
+ }
+ }
+ if ( $slow ) {
+ $dbs = implode( ', ', array_keys( $this->mDBTrxHoldingLocks ) );
+ $msg = "Sub-optimal transaction on DB(s) {$dbs}:\n";
+ foreach ( $this->mDBTrxMethodTimes[$name] as $i => $info ) {
+ list( $method, $realtime ) = $info;
+ $msg .= sprintf( "%d\t%.6f\t%s\n", $i, $realtime, $method );
+ }
+ wfDebugLog( 'DBPerformance', $msg );
+ }
+ unset( $this->mDBTrxHoldingLocks[$name] );
+ unset( $this->mDBTrxMethodTimes[$name] );
+ }
+ }
+
+ /**
* Mark this call as templated or not
*
* @param $t Boolean
@@ -228,11 +349,11 @@ 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";
}
- if( $wgProfileCallTree ) {
+ if ( $wgProfileCallTree ) {
return $this->getCallTree();
} else {
return $this->getFunctionReport();
@@ -254,16 +375,16 @@ class Profiler {
* @return array
*/
function remapCallTree( $stack ) {
- if( count( $stack ) < 2 ) {
+ if ( count( $stack ) < 2 ) {
return $stack;
}
- $outputs = array ();
- for( $max = count( $stack ) - 1; $max > 0; ) {
+ $outputs = array();
+ 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 ) {
+ $working = array();
+ for ( $i = $max -1; $i >= 0; $i-- ) {
+ if ( $stack[$i][1] > $level ) {
$working[] = $stack[$i];
} else {
break;
@@ -271,7 +392,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 +401,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;
}
}
@@ -391,18 +512,17 @@ class Profiler {
# 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 +531,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,16 +540,16 @@ 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 ) {
+ if ( $elapsed ) {
$elapsed -= $overheadInternal;
- $elapsed -= ($subcalls * $overheadTotal);
- $memory -= ($subcalls * $overheadMemory);
+ $elapsed -= ( $subcalls * $overheadTotal );
+ $memory -= ( $subcalls * $overheadMemory );
}
}
- 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;
@@ -467,11 +587,21 @@ class Profiler {
$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 +613,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' );
}
@@ -515,16 +645,16 @@ class Profiler {
global $wgProfilePerHost, $wgProfileToDatabase;
# Do not log anything if database is readonly (bug 5375)
- if( wfReadOnly() || !$wgProfileToDatabase ) {
+ if ( wfReadOnly() || !$wgProfileToDatabase ) {
return;
}
$dbw = wfGetDB( DB_MASTER );
- if( !is_object( $dbw ) ) {
+ if ( !is_object( $dbw ) ) {
return;
}
- if( $wgProfilePerHost ) {
+ if ( $wgProfilePerHost ) {
$pfhost = wfHostname();
} else {
$pfhost = '';
@@ -533,15 +663,15 @@ class Profiler {
try {
$this->collateData();
- foreach( $this->mCollated as $name => $elapsed ) {
+ foreach ( $this->mCollated as $name => $elapsed ) {
$eventCount = $this->mCalls[$name];
- $timeSum = (float) ($elapsed * 1000);
+ $timeSum = (float)( $elapsed * 1000 );
$memorySum = (float)$this->mMemory[$name];
- $name = substr($name, 0, 255);
+ $name = substr( $name, 0, 255 );
// Kludge
- $timeSum = ($timeSum >= 0) ? $timeSum : 0;
- $memorySum = ($memorySum >= 0) ? $memorySum : 0;
+ $timeSum = $timeSum >= 0 ? $timeSum : 0;
+ $memorySum = $memorySum >= 0 ? $memorySum : 0;
$dbw->update( 'profiling',
array(
@@ -557,9 +687,9 @@ class Profiler {
$rc = $dbw->affectedRows();
if ( $rc == 0 ) {
- $dbw->insert( 'profiling', array ( 'pf_name' => $name, 'pf_count' => $eventCount,
+ $dbw->insert( 'profiling', array( 'pf_name' => $name, 'pf_count' => $eventCount,
'pf_time' => $timeSum, 'pf_memory' => $memorySum, 'pf_server' => $pfhost ),
- __METHOD__, array ( 'IGNORE' ) );
+ __METHOD__, array( 'IGNORE' ) );
}
// When we upgrade to mysql 4.1, the insert+update
// can be merged into just a insert with this construct added:
@@ -585,7 +715,7 @@ class Profiler {
* @param string $s to output
*/
function debug( $s ) {
- if( defined( 'MW_COMPILED' ) || function_exists( 'wfDebug' ) ) {
+ if ( function_exists( 'wfDebug' ) ) {
wfDebug( $s );
}
}
diff --git a/includes/profiler/ProfilerSimple.php b/includes/profiler/ProfilerSimple.php
index 1d4873c6..805c60f4 100644
--- a/includes/profiler/ProfilerSimple.php
+++ b/includes/profiler/ProfilerSimple.php
@@ -96,11 +96,12 @@ class ProfilerSimple extends Profiler {
$this->mCollated[$functionname] =& $entry;
}
$entry['cpu'] += $elapsedcpu;
- $entry['cpu_sq'] += $elapsedcpu*$elapsedcpu;
+ $entry['cpu_sq'] += $elapsedcpu * $elapsedcpu;
$entry['real'] += $elapsedreal;
- $entry['real_sq'] += $elapsedreal*$elapsedreal;
+ $entry['real_sq'] += $elapsedreal * $elapsedreal;
$entry['count']++;
+ $this->updateTrxProfiling( $functionname, $elapsedreal );
}
}
diff --git a/includes/profiler/ProfilerSimpleText.php b/includes/profiler/ProfilerSimpleText.php
index 37350bf3..1d57ea8d 100644
--- a/includes/profiler/ProfilerSimpleText.php
+++ b/includes/profiler/ProfilerSimpleText.php
@@ -51,17 +51,17 @@ class ProfilerSimpleText extends ProfilerSimple {
uasort( $this->mCollated, array( 'self', 'sort' ) );
array_walk( $this->mCollated, array( 'self', 'format' ), $totalReal );
if ( PHP_SAPI === 'cli' ) {
- print "<!--\n".self::$out."\n-->\n";
+ print "<!--\n" . self::$out . "\n-->\n";
} elseif ( $this->getContentType() === 'text/html' ) {
if ( $this->visible ) {
- print '<pre>'.self::$out.'</pre>';
+ print '<pre>' . self::$out . '</pre>';
} else {
- print "<!--\n".self::$out."\n-->\n";
+ print "<!--\n" . self::$out . "\n-->\n";
}
} elseif ( $this->getContentType() === 'text/javascript' ) {
- print "\n/*\n".self::$out."*/\n";
+ print "\n/*\n" . self::$out . "*/\n";
} elseif ( $this->getContentType() === 'text/css' ) {
- print "\n/*\n".self::$out."*/\n";
+ print "\n/*\n" . self::$out . "*/\n";
}
}
}
@@ -71,7 +71,7 @@ class ProfilerSimpleText extends ProfilerSimple {
}
static function format( $item, $key, $totalReal ) {
- $perc = $totalReal ? $item['real']/$totalReal*100 : 0;
+ $perc = $totalReal ? $item['real'] / $totalReal * 100 : 0;
self::$out .= sprintf( "%6.2f%% %3.6f %6d - %s\n",
$perc, $item['real'], $item['count'], $key );
}
diff --git a/includes/profiler/ProfilerSimpleTrace.php b/includes/profiler/ProfilerSimpleTrace.php
index d44dfe1b..5588d1e2 100644
--- a/includes/profiler/ProfilerSimpleTrace.php
+++ b/includes/profiler/ProfilerSimpleTrace.php
@@ -59,6 +59,8 @@ 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";
+
+ $this->updateTrxProfiling( $functionname, $elapsedreal );
}
}
diff --git a/includes/profiler/ProfilerSimpleUDP.php b/includes/profiler/ProfilerSimpleUDP.php
index abefa811..0a1f3b10 100644
--- a/includes/profiler/ProfilerSimpleUDP.php
+++ b/includes/profiler/ProfilerSimpleUDP.php
@@ -32,7 +32,7 @@ class ProfilerSimpleUDP extends ProfilerSimple {
}
public function logData() {
- global $wgUDPProfilerHost, $wgUDPProfilerPort;
+ global $wgUDPProfilerHost, $wgUDPProfilerPort, $wgUDPProfilerFormatString;
$this->close();
@@ -41,24 +41,24 @@ class ProfilerSimpleUDP extends ProfilerSimple {
return;
}
- if ( !MWInit::functionExists( 'socket_create' ) ) {
+ if ( !function_exists( 'socket_create' ) ) {
# Sockets are not enabled
return;
}
- $sock = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
+ $sock = socket_create( AF_INET, SOCK_DGRAM, SOL_UDP );
$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'] )
|| !isset( $pfdata['real_sq'] ) ) {
continue;
}
- $pfline = sprintf( "%s %s %d %f %f %f %f %s\n", $this->getProfileID(), "-", $pfdata['count'],
- $pfdata['cpu'], $pfdata['cpu_sq'], $pfdata['real'], $pfdata['real_sq'], $entry);
+ $pfline = sprintf( $wgUDPProfilerFormatString, $this->getProfileID(), $pfdata['count'],
+ $pfdata['cpu'], $pfdata['cpu_sq'], $pfdata['real'], $pfdata['real_sq'], $entry );
$length = strlen( $pfline );
/* printf("<!-- $pfline -->"); */
if ( $length + $plength > 1400 ) {
diff --git a/includes/profiler/ProfilerStub.php b/includes/profiler/ProfilerStub.php
index c0eb0fb4..3697f352 100644
--- a/includes/profiler/ProfilerStub.php
+++ b/includes/profiler/ProfilerStub.php
@@ -39,4 +39,6 @@ class ProfilerStub extends Profiler {
public function close() {}
public function logData() {}
public function getCurrentSection() { return ''; }
+ public function transactionWritingIn( $server, $db ) {}
+ public function transactionWritingOut( $server, $db ) {}
}
diff --git a/includes/rcfeed/IRCColourfulRCFeedFormatter.php b/includes/rcfeed/IRCColourfulRCFeedFormatter.php
new file mode 100644
index 00000000..507369f3
--- /dev/null
+++ b/includes/rcfeed/IRCColourfulRCFeedFormatter.php
@@ -0,0 +1,99 @@
+<?php
+class IRCColourfulRCFeedFormatter implements RCFeedFormatter {
+ /**
+ * Generates a colourful notification intended for humans on IRC.
+ * @see RCFeedFormatter::getLine
+ */
+ public function getLine( array $feed, RecentChange $rc, $actionComment ) {
+ global $wgUseRCPatrol, $wgUseNPPatrol, $wgLocalInterwiki,
+ $wgCanonicalServer, $wgScript;
+ $attribs = $rc->getAttributes();
+ if ( $attribs['rc_type'] == RC_LOG ) {
+ // Don't use SpecialPage::getTitleFor, backwards compatibility with
+ // IRC API which expects "Log".
+ $titleObj = Title::newFromText( 'Log/' . $attribs['rc_log_type'], NS_SPECIAL );
+ } else {
+ $titleObj =& $rc->getTitle();
+ }
+ $title = $titleObj->getPrefixedText();
+ $title = self::cleanupForIRC( $title );
+
+ if ( $attribs['rc_type'] == RC_LOG ) {
+ $url = '';
+ } else {
+ $url = $wgCanonicalServer . $wgScript;
+ if ( $attribs['rc_type'] == RC_NEW ) {
+ $query = '?oldid=' . $attribs['rc_this_oldid'];
+ } else {
+ $query = '?diff=' . $attribs['rc_this_oldid'] . '&oldid=' . $attribs['rc_last_oldid'];
+ }
+ if ( $wgUseRCPatrol || ( $attribs['rc_type'] == RC_NEW && $wgUseNPPatrol ) ) {
+ $query .= '&rcid=' . $attribs['rc_id'];
+ }
+ // HACK: We need this hook for WMF's secure server setup
+ wfRunHooks( 'IRCLineURL', array( &$url, &$query ) );
+ $url .= $query;
+ }
+
+ if ( $attribs['rc_old_len'] !== null && $attribs['rc_new_len'] !== null ) {
+ $szdiff = $attribs['rc_new_len'] - $attribs['rc_old_len'];
+ if ( $szdiff < -500 ) {
+ $szdiff = "\002$szdiff\002";
+ } elseif ( $szdiff >= 0 ) {
+ $szdiff = '+' . $szdiff;
+ }
+ // @todo i18n with parentheses in content language?
+ $szdiff = '(' . $szdiff . ')';
+ } else {
+ $szdiff = '';
+ }
+
+ $user = self::cleanupForIRC( $attribs['rc_user_text'] );
+
+ if ( $attribs['rc_type'] == RC_LOG ) {
+ $targetText = $rc->getTitle()->getPrefixedText();
+ $comment = self::cleanupForIRC( str_replace( "[[$targetText]]", "[[\00302$targetText\00310]]", $actionComment ) );
+ $flag = $attribs['rc_log_action'];
+ } else {
+ $comment = self::cleanupForIRC( $attribs['rc_comment'] );
+ $flag = '';
+ if ( !$attribs['rc_patrolled'] && ( $wgUseRCPatrol || $attribs['rc_type'] == RC_NEW && $wgUseNPPatrol ) ) {
+ $flag .= '!';
+ }
+ $flag .= ( $attribs['rc_type'] == RC_NEW ? "N" : "" ) . ( $attribs['rc_minor'] ? "M" : "" ) . ( $attribs['rc_bot'] ? "B" : "" );
+ }
+
+ if ( $feed['add_interwiki_prefix'] === true && $wgLocalInterwiki !== false ) {
+ $prefix = $wgLocalInterwiki;
+ } elseif ( $feed['add_interwiki_prefix'] ) {
+ $prefix = $feed['add_interwiki_prefix'];
+ } else {
+ $prefix = false;
+ }
+ if ( $prefix !== false ) {
+ $titleString = "\00314[[\00303$prefix:\00307$title\00314]]";
+ } else {
+ $titleString = "\00314[[\00307$title\00314]]";
+ }
+
+ # 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";
+
+ return $fullString;
+ }
+
+ /**
+ * Remove newlines, carriage returns and decode html entites
+ * @param string $text
+ * @return string
+ */
+ public static function cleanupForIRC( $text ) {
+ return Sanitizer::decodeCharReferences( str_replace(
+ array( "\n", "\r" ),
+ array( " ", "" ),
+ $text
+ ) );
+ }
+}
diff --git a/includes/rcfeed/JSONRCFeedFormatter.php b/includes/rcfeed/JSONRCFeedFormatter.php
new file mode 100644
index 00000000..f4cb9921
--- /dev/null
+++ b/includes/rcfeed/JSONRCFeedFormatter.php
@@ -0,0 +1,90 @@
+<?php
+
+class JSONRCFeedFormatter implements RCFeedFormatter {
+ /**
+ * Generates a notification that can be easily interpreted by a machine.
+ * @see RCFeedFormatter::getLine
+ */
+ public function getLine( array $feed, RecentChange $rc, $actionComment ) {
+ global $wgCanonicalServer, $wgScriptPath, $wgDBname;
+ $attrib = $rc->getAttributes();
+
+ $packet = array(
+ // Usually, RC ID is exposed only for patrolling purposes,
+ // but there is no real reason not to expose it in other cases,
+ // and I can see how this may be potentially useful for clients.
+ 'id' => $attrib['rc_id'],
+ 'type' => $attrib['rc_type'],
+ 'namespace' => $rc->getTitle()->getNamespace(),
+ 'title' => $rc->getTitle()->getPrefixedText(),
+ 'comment' => $attrib['rc_comment'],
+ 'timestamp' => (int)wfTimestamp( TS_UNIX, $attrib['rc_timestamp'] ),
+ 'user' => $attrib['rc_user_text'],
+ 'bot' => (bool)$attrib['rc_bot'],
+ );
+
+ if ( isset( $feed['channel'] ) ) {
+ $packet['channel'] = $feed['channel'];
+ }
+
+ $type = $attrib['rc_type'];
+ if ( $type == RC_EDIT || $type == RC_NEW ) {
+ global $wgUseRCPatrol, $wgUseNPPatrol;
+
+ $packet['minor'] = $attrib['rc_minor'];
+ if ( $wgUseRCPatrol || ( $type == RC_NEW && $wgUseNPPatrol ) ) {
+ $packet['patrolled'] = $attrib['rc_patrolled'];
+ }
+ }
+
+ switch ( $type ) {
+ case RC_EDIT:
+ $packet['length'] = array( 'old' => $attrib['rc_old_len'], 'new' => $attrib['rc_new_len'] );
+ $packet['revision'] = array( 'old' => $attrib['rc_last_oldid'], 'new' => $attrib['rc_this_oldid'] );
+ break;
+
+ case RC_NEW:
+ $packet['length'] = array( 'old' => NULL, 'new' => $attrib['rc_new_len'] );
+ $packet['revision'] = array( 'old' => NULL, 'new' => $attrib['rc_this_oldid'] );
+ break;
+
+ case RC_LOG:
+ $packet['log_type'] = $attrib['rc_log_type'];
+ $packet['log_action'] = $attrib['rc_log_action'];
+ if ( $attrib['rc_params'] ) {
+ wfSuppressWarnings();
+ $params = unserialize( $attrib['rc_params'] );
+ wfRestoreWarnings();
+ if (
+ // If it's an actual serialised false...
+ $attrib['rc_params'] == serialize( false ) ||
+ // Or if we did not get false back when trying to unserialise
+ $params !== false
+ ) {
+ // From ApiQueryLogEvents::addLogParams
+ $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;
+ }
+ $packet['log_params'] = $logParams;
+ } else {
+ $packet['log_params'] = explode( "\n", $attrib['rc_params'] );
+ }
+ }
+ $packet['log_action_comment'] = $actionComment;
+ break;
+ }
+
+ $packet['server_url'] = $wgCanonicalServer;
+ $packet['server_script_path'] = $wgScriptPath ?: '/';
+ $packet['wiki'] = $wgDBname;
+
+ return FormatJson::encode( $packet );
+ }
+}
diff --git a/includes/rcfeed/RCFeedEngine.php b/includes/rcfeed/RCFeedEngine.php
new file mode 100644
index 00000000..f733bcb7
--- /dev/null
+++ b/includes/rcfeed/RCFeedEngine.php
@@ -0,0 +1,12 @@
+<?php
+interface RCFeedEngine {
+ /**
+ * Sends some text to the specified live feed.
+ *
+ * @see RecentChange::cleanupForIRC
+ * @param array $feed The feed, as configured in an associative array.
+ * @param string $line The text to send.
+ * @return boolean success
+ */
+ public function send( array $feed, $line );
+}
diff --git a/includes/rcfeed/RCFeedFormatter.php b/includes/rcfeed/RCFeedFormatter.php
new file mode 100644
index 00000000..6c9f8042
--- /dev/null
+++ b/includes/rcfeed/RCFeedFormatter.php
@@ -0,0 +1,13 @@
+<?php
+interface RCFeedFormatter {
+ /**
+ * Formats the line for the live feed.
+ *
+ * @param array $feed The feed, as configured in an associative array.
+ * @param RecentChange $rc The RecentChange object showing what sort
+ * of event has taken place.
+ * @param string|null $actionComment
+ * @return string The text to send.
+ */
+ public function getLine( array $feed, RecentChange $rc, $actionComment );
+}
diff --git a/includes/rcfeed/RedisPubSubFeedEngine.php b/includes/rcfeed/RedisPubSubFeedEngine.php
new file mode 100644
index 00000000..4bcc1337
--- /dev/null
+++ b/includes/rcfeed/RedisPubSubFeedEngine.php
@@ -0,0 +1,41 @@
+<?php
+class RedisPubSubFeedEngine implements RCFeedEngine {
+ /**
+ * Emit a recent change notification via Redis Pub/Sub
+ *
+ * If the feed URI contains a path component, it will be used to generate a
+ * channel name by stripping the leading slash and replacing any remaining
+ * slashes with '.'. If no path component is present, the channel is set to
+ * 'rc'. If the URI contains a query string, its parameters will be parsed
+ * as RedisConnectionPool options.
+ *
+ * @example $wgRCFeeds['redis'] = array(
+ * 'formatter' => 'JSONRCFeedFormatter',
+ * 'uri' => "redis://127.0.0.1:6379/rc.$wgDBname",
+ * );
+ *
+ * @since 1.22
+ */
+ public function send( array $feed, $line ) {
+ $parsed = parse_url( $feed['uri'] );
+ $server = $parsed['host'];
+ $options = array( 'serializer' => 'none' );
+ $channel = 'rc';
+
+ if ( isset( $parsed['port'] ) ) {
+ $server .= ":{$parsed['port']}";
+ }
+ if ( isset( $parsed['query'] ) ) {
+ parse_str( $parsed['query'], $options );
+ }
+ if ( isset( $parsed['pass'] ) ) {
+ $options['password'] = $parsed['pass'];
+ }
+ if ( isset( $parsed['path'] ) ) {
+ $channel = str_replace( '/', '.', ltrim( $parsed['path'], '/' ) );
+ }
+ $pool = RedisConnectionPool::singleton( $options );
+ $conn = $pool->getConnection( $server );
+ $conn->publish( $channel, $line );
+ }
+}
diff --git a/includes/rcfeed/UDPRCFeedEngine.php b/includes/rcfeed/UDPRCFeedEngine.php
new file mode 100644
index 00000000..beeb73bd
--- /dev/null
+++ b/includes/rcfeed/UDPRCFeedEngine.php
@@ -0,0 +1,10 @@
+<?php
+class UDPRCFeedEngine implements RCFeedEngine {
+ /**
+ * Sends the notification to the specified host in a UDP packet.
+ * @see RCFeedEngine::send
+ */
+ public function send( array $feed, $line ) {
+ wfErrorLog( $line, $feed['uri'] );
+ }
+}
diff --git a/includes/resourceloader/ResourceLoader.php b/includes/resourceloader/ResourceLoader.php
index 4e047be4..6380efcf 100644
--- a/includes/resourceloader/ResourceLoader.php
+++ b/includes/resourceloader/ResourceLoader.php
@@ -47,6 +47,9 @@ class ResourceLoader {
/** array( 'source-id' => array( 'loadScript' => 'http://.../load.php' ) ) **/
protected $sources = array();
+ /** @var bool */
+ protected $hasErrors = false;
+
/* Protected Methods */
/**
@@ -150,6 +153,7 @@ class ResourceLoader {
$cache = wfGetCache( CACHE_ANYTHING );
$cacheEntry = $cache->get( $key );
if ( is_string( $cacheEntry ) ) {
+ wfIncrStats( "rl-$filter-cache-hits" );
wfProfileOut( __METHOD__ );
return $cacheEntry;
}
@@ -157,6 +161,7 @@ class ResourceLoader {
$result = '';
// Run the filter - we've already verified one of these will work
try {
+ wfIncrStats( "rl-$filter-cache-misses" );
switch ( $filter ) {
case 'minify-js':
$result = JavaScriptMinifier::minify( $data,
@@ -173,10 +178,12 @@ class ResourceLoader {
// Save filtered text to Memcached
$cache->set( $key, $result );
- } catch ( Exception $exception ) {
- // Return exception as a comment
- $result = $this->formatException( $exception );
+ } catch ( Exception $e ) {
+ MWExceptionHandler::logException( $e );
+ wfDebugLog( 'resourceloader', __METHOD__ . ": minification failed: $e" );
$this->hasErrors = true;
+ // Return exception as a comment
+ $result = self::formatException( $e );
}
wfProfileOut( __METHOD__ );
@@ -201,7 +208,7 @@ class ResourceLoader {
$this->addSource( $wgResourceLoaderSources );
// Register core modules
- $this->register( include( "$IP/resources/Resources.php" ) );
+ $this->register( include "$IP/resources/Resources.php" );
// Register extension modules
wfRunHooks( 'ResourceLoaderRegisterModules', array( &$this ) );
$this->register( $wgResourceModules );
@@ -234,6 +241,7 @@ class ResourceLoader {
foreach ( $registrations as $name => $info ) {
// Disallow duplicate registrations
if ( isset( $this->moduleInfos[$name] ) ) {
+ wfProfileOut( __METHOD__ );
// A module has already been registered by this name
throw new MWException(
'ResourceLoader duplicate registration error. ' .
@@ -243,6 +251,7 @@ class ResourceLoader {
// Check $name for validity
if ( !self::isValidModuleName( $name ) ) {
+ wfProfileOut( __METHOD__ );
throw new MWException( "ResourceLoader module name '$name' is invalid, see ResourceLoader::isValidModuleName()" );
}
@@ -251,6 +260,7 @@ class ResourceLoader {
// Old calling convention
// Validate the input
if ( !( $info instanceof ResourceLoaderModule ) ) {
+ wfProfileOut( __METHOD__ );
throw new MWException( 'ResourceLoader invalid module error. ' .
'Instances of ResourceLoaderModule expected.' );
}
@@ -273,24 +283,28 @@ class ResourceLoader {
global $IP, $wgEnableJavaScriptTest;
if ( $wgEnableJavaScriptTest !== true ) {
- throw new MWException( 'Attempt to register JavaScript test modules but <tt>$wgEnableJavaScriptTest</tt> is false. Edit your <tt>LocalSettings.php</tt> to enable it.' );
+ throw new MWException( 'Attempt to register JavaScript test modules but <code>$wgEnableJavaScriptTest</code> is false. Edit your <code>LocalSettings.php</code> to enable it.' );
}
wfProfileIn( __METHOD__ );
// Get core test suites
$testModules = array();
- $testModules['qunit'] = include( "$IP/tests/qunit/QUnitTestResources.php" );
+ $testModules['qunit'] = include "$IP/tests/qunit/QUnitTestResources.php";
// Get other test suites (e.g. from extensions)
wfRunHooks( 'ResourceLoaderTestModules', array( &$testModules, &$this ) );
// Add the testrunner (which configures QUnit) to the dependencies.
// Since it must be ready before any of the test suites are executed.
- foreach( $testModules['qunit'] as $moduleName => $moduleProps ) {
- $testModules['qunit'][$moduleName]['dependencies'][] = 'mediawiki.tests.qunit.testrunner';
+ foreach ( $testModules['qunit'] as &$module ) {
+ // Make sure all test modules are top-loading so that when QUnit starts
+ // on document-ready, it will run once and finish. If some tests arrive
+ // later (possibly after QUnit has already finished) they will be ignored.
+ $module['position'] = 'top';
+ $module['dependencies'][] = 'mediawiki.tests.qunit.testrunner';
}
- foreach( $testModules as $id => $names ) {
+ foreach ( $testModules as $id => $names ) {
// Register test modules
$this->register( $testModules[$id] );
@@ -311,7 +325,7 @@ class ResourceLoader {
* @param array $properties source properties
* @throws MWException
*/
- public function addSource( $id, $properties = null) {
+ public function addSource( $id, $properties = null ) {
// Allow multiple sources to be registered in one call
if ( is_array( $id ) ) {
foreach ( $id as $key => $value ) {
@@ -357,7 +371,7 @@ class ResourceLoader {
* @return Array
*/
public function getTestModuleNames( $framework = 'all' ) {
- /// @TODO: api siteinfo prop testmodulenames modulenames
+ /// @todo api siteinfo prop testmodulenames modulenames
if ( $framework == 'all' ) {
return $this->testModuleNames;
} elseif ( isset( $this->testModuleNames[$framework] ) && is_array( $this->testModuleNames[$framework] ) ) {
@@ -381,6 +395,7 @@ class ResourceLoader {
}
// Construct the requested object
$info = $this->moduleInfos[$name];
+ /** @var ResourceLoaderModule $object */
if ( isset( $info['object'] ) ) {
// Object given in info array
$object = $info['object'];
@@ -435,7 +450,6 @@ class ResourceLoader {
wfProfileIn( __METHOD__ );
$errors = '';
- $this->hasErrors = false;
// Split requested modules into two groups, modules and missing
$modules = array();
@@ -446,8 +460,11 @@ class ResourceLoader {
// Do not allow private modules to be loaded from the web.
// This is a security issue, see bug 34907.
if ( $module->getGroup() === 'private' ) {
- $errors .= $this->makeComment( "Cannot show private module \"$name\"" );
+ wfDebugLog( 'resourceloader', __METHOD__ . ": request for private module '$name' denied" );
$this->hasErrors = true;
+ // Add exception to the output as a comment
+ $errors .= self::makeComment( "Cannot show private module \"$name\"" );
+
continue;
}
$modules[$name] = $module;
@@ -459,13 +476,15 @@ class ResourceLoader {
// Preload information needed to the mtime calculation below
try {
$this->preloadModuleInfo( array_keys( $modules ), $context );
- } catch( Exception $e ) {
- // Add exception to the output as a comment
- $errors .= $this->formatException( $e );
+ } catch ( Exception $e ) {
+ MWExceptionHandler::logException( $e );
+ wfDebugLog( 'resourceloader', __METHOD__ . ": preloading module info failed: $e" );
$this->hasErrors = true;
+ // Add exception to the output as a comment
+ $errors .= self::formatException( $e );
}
- wfProfileIn( __METHOD__.'-getModifiedTime' );
+ wfProfileIn( __METHOD__ . '-getModifiedTime' );
// To send Last-Modified and support If-Modified-Since, we need to detect
// the last modified time
@@ -478,13 +497,15 @@ class ResourceLoader {
// Calculate maximum modified time
$mtime = max( $mtime, $module->getModifiedTime( $context ) );
} catch ( Exception $e ) {
- // Add exception to the output as a comment
- $errors .= $this->formatException( $e );
+ MWExceptionHandler::logException( $e );
+ wfDebugLog( 'resourceloader', __METHOD__ . ": calculating maximum modified time failed: $e" );
$this->hasErrors = true;
+ // Add exception to the output as a comment
+ $errors .= self::formatException( $e );
}
}
- wfProfileOut( __METHOD__.'-getModifiedTime' );
+ wfProfileOut( __METHOD__ . '-getModifiedTime' );
// If there's an If-Modified-Since header, respond with a 304 appropriately
if ( $this->tryRespondLastModified( $context, $mtime ) ) {
@@ -501,7 +522,7 @@ class ResourceLoader {
// Capture any PHP warnings from the output buffer and append them to the
// response in a comment if we're in debug mode.
if ( $context->getDebug() && strlen( $warnings = ob_get_contents() ) ) {
- $response = $this->makeComment( $warnings ) . $response;
+ $response = self::makeComment( $warnings ) . $response;
$this->hasErrors = true;
}
@@ -531,7 +552,7 @@ class ResourceLoader {
* Send content type and last modified headers to the client.
* @param $context ResourceLoaderContext
* @param string $mtime TS_MW timestamp to use for last-modified
- * @param bool $error Whether there are commented-out errors in the response
+ * @param bool $errors Whether there are commented-out errors in the response
* @return void
*/
protected function sendResponseHeaders( ResourceLoaderContext $context, $mtime, $errors ) {
@@ -550,6 +571,7 @@ class ResourceLoader {
}
if ( $context->getOnly() === 'styles' ) {
header( 'Content-Type: text/css; charset=utf-8' );
+ header( 'Access-Control-Allow-Origin: *' );
} else {
header( 'Content-Type: text/javascript; charset=utf-8' );
}
@@ -570,7 +592,7 @@ class ResourceLoader {
* and clear out the output buffer. If the client cache is too old then do nothing.
* @param $context ResourceLoaderContext
* @param string $mtime The TS_MW timestamp to check the header against
- * @return bool True iff 304 header sent and output handled
+ * @return bool True if 304 header sent and output handled
*/
protected function tryRespondLastModified( ResourceLoaderContext $context, $mtime ) {
// If there's an If-Modified-Since header, respond with a 304 appropriately
@@ -590,13 +612,7 @@ class ResourceLoader {
// See also http://bugs.php.net/bug.php?id=51579
// To work around this, we tear down all output buffering before
// sending the 304.
- // On some setups, ob_get_level() doesn't seem to go down to zero
- // no matter how often we call ob_get_clean(), so instead of doing
- // the more intuitive while ( ob_get_level() > 0 ) ob_get_clean();
- // we have to be safe here and avoid an infinite loop.
- for ( $i = 0; $i < ob_get_level(); $i++ ) {
- ob_end_clean();
- }
+ wfResetOutputBuffers( /* $resetGzipEncoding = */ true );
header( 'HTTP/1.0 304 Not Modified' );
header( 'Status: 304 Not Modified' );
@@ -628,7 +644,7 @@ class ResourceLoader {
if ( !$good ) {
try { // RL always hits the DB on file cache miss...
wfGetDB( DB_SLAVE );
- } catch( DBConnectionError $e ) { // ...check if we need to fallback to cache
+ } catch ( DBConnectionError $e ) { // ...check if we need to fallback to cache
$good = $fileCache->isCacheGood(); // cache existence check
}
}
@@ -657,7 +673,14 @@ class ResourceLoader {
return false; // cache miss
}
- protected function makeComment( $text ) {
+ /**
+ * Generate a CSS or JS comment block. Only use this for public data,
+ * not error message details.
+ *
+ * @param $text string
+ * @return string
+ */
+ public static function makeComment( $text ) {
$encText = str_replace( '*/', '* /', $text );
return "/*\n$encText\n*/\n";
}
@@ -668,13 +691,13 @@ class ResourceLoader {
* @param Exception $e to be shown to the user
* @return string sanitized text that can be returned to the user
*/
- protected function formatException( $e ) {
+ public static function formatException( $e ) {
global $wgShowExceptionDetails;
if ( $wgShowExceptionDetails ) {
- return $this->makeComment( $e->__toString() );
+ return self::makeComment( $e->__toString() );
} else {
- return $this->makeComment( wfMessage( 'internalerror' )->text() );
+ return self::makeComment( wfMessage( 'internalerror' )->text() );
}
}
@@ -687,8 +710,8 @@ class ResourceLoader {
* @return String: Response data
*/
public function makeModuleResponse( ResourceLoaderContext $context,
- array $modules, $missing = array() )
- {
+ array $modules, $missing = array()
+ ) {
$out = '';
$exceptions = '';
if ( $modules === array() && $missing === array() ) {
@@ -701,9 +724,11 @@ class ResourceLoader {
try {
$blobs = MessageBlobStore::get( $this, $modules, $context->getLanguage() );
} catch ( Exception $e ) {
- // Add exception to the output as a comment
- $exceptions .= $this->formatException( $e );
+ MWExceptionHandler::logException( $e );
+ wfDebugLog( 'resourceloader', __METHOD__ . ": pre-fetching blobs from MessageBlobStore failed: $e" );
$this->hasErrors = true;
+ // Add exception to the output as a comment
+ $exceptions .= self::formatException( $e );
}
} else {
$blobs = array();
@@ -807,9 +832,11 @@ class ResourceLoader {
break;
}
} catch ( Exception $e ) {
- // Add exception to the output as a comment
- $exceptions .= $this->formatException( $e );
+ MWExceptionHandler::logException( $e );
+ wfDebugLog( 'resourceloader', __METHOD__ . ": generating module package failed: $e" );
$this->hasErrors = true;
+ // Add exception to the output as a comment
+ $exceptions .= self::formatException( $e );
// Register module as missing
$missing[] = $name;
@@ -879,7 +906,9 @@ class ResourceLoader {
// output javascript "[]" instead of "{}". This fixes that.
(object)$styles,
(object)$messages
- ) );
+ ),
+ ResourceLoader::inDebugMode()
+ );
}
/**
@@ -908,7 +937,7 @@ class ResourceLoader {
// ResourceLoaderFileModule::getStyle can return the styles
// as a string or an array of strings. This is to allow separation in
// the front-end.
- $styles = (array) $styles;
+ $styles = (array)$styles;
foreach ( $styles as $style ) {
$style = trim( $style );
// Don't output an empty "@media print { }" block (bug 40498)
@@ -919,7 +948,7 @@ class ResourceLoader {
if ( $media === '' || $media == 'all' ) {
$out[] = $style;
- } else if ( is_string( $media ) ) {
+ } elseif ( is_string( $media ) ) {
$out[] = "@media $media {\n" . str_replace( "\n", "\n\t", "\t" . $style ) . "}";
}
// else: skip
@@ -1000,12 +1029,12 @@ class ResourceLoader {
* @return string
*/
public static function makeLoaderRegisterScript( $name, $version = null,
- $dependencies = null, $group = null, $source = null )
- {
+ $dependencies = null, $group = null, $source = null
+ ) {
if ( is_array( $name ) ) {
return Xml::encodeJsCall( 'mw.loader.register', array( $name ) );
} else {
- $version = (int) $version > 1 ? (int) $version : 1;
+ $version = (int)$version > 1 ? (int)$version : 1;
return Xml::encodeJsCall( 'mw.loader.register',
array( $name, $version, $dependencies, $group, $source ) );
}
@@ -1055,7 +1084,7 @@ class ResourceLoader {
* @return string
*/
public static function makeConfigSetScript( array $configuration ) {
- return Xml::encodeJsCall( 'mw.config.set', array( $configuration ) );
+ return Xml::encodeJsCall( 'mw.config.set', array( $configuration ), ResourceLoader::inDebugMode() );
}
/**
@@ -1128,6 +1157,18 @@ class ResourceLoader {
/**
* Build a query array (array representation of query string) for load.php. Helper
* function for makeLoaderURL().
+ *
+ * @param array $modules
+ * @param string $lang
+ * @param string $skin
+ * @param string $user
+ * @param string $version
+ * @param bool $debug
+ * @param string $only
+ * @param bool $printable
+ * @param bool $handheld
+ * @param array $extraQuery
+ *
* @return array
*/
public static function makeLoaderQuery( $modules, $lang, $skin, $user = null, $version = null, $debug = false, $only = null,
@@ -1172,4 +1213,48 @@ class ResourceLoader {
public static function isValidModuleName( $moduleName ) {
return !preg_match( '/[|,!]/', $moduleName ) && strlen( $moduleName ) <= 255;
}
+
+ /**
+ * Returns LESS compiler set up for use with MediaWiki
+ *
+ * @since 1.22
+ * @return lessc
+ */
+ public static function getLessCompiler() {
+ global $wgResourceLoaderLESSFunctions, $wgResourceLoaderLESSImportPaths;
+
+ // When called from the installer, it is possible that a required PHP extension
+ // is missing (at least for now; see bug 47564). If this is the case, throw an
+ // exception (caught by the installer) to prevent a fatal error later on.
+ if ( !function_exists( 'ctype_digit' ) ) {
+ throw new MWException( 'lessc requires the Ctype extension' );
+ }
+
+ $less = new lessc();
+ $less->setPreserveComments( true );
+ $less->setVariables( self::getLESSVars() );
+ $less->setImportDir( $wgResourceLoaderLESSImportPaths );
+ foreach ( $wgResourceLoaderLESSFunctions as $name => $func ) {
+ $less->registerFunction( $name, $func );
+ }
+ return $less;
+ }
+
+ /**
+ * Get global LESS variables.
+ *
+ * $since 1.22
+ * @return array: Map of variable names to string CSS values.
+ */
+ public static function getLESSVars() {
+ global $wgResourceLoaderLESSVars;
+
+ static $lessVars = null;
+ if ( $lessVars === null ) {
+ $lessVars = $wgResourceLoaderLESSVars;
+ // Sort by key to ensure consistent hashing for cache lookups.
+ ksort( $lessVars );
+ }
+ return $lessVars;
+ }
}
diff --git a/includes/resourceloader/ResourceLoaderContext.php b/includes/resourceloader/ResourceLoaderContext.php
index 4588015f..22ff6a7e 100644
--- a/includes/resourceloader/ResourceLoaderContext.php
+++ b/includes/resourceloader/ResourceLoaderContext.php
@@ -96,7 +96,7 @@ class ResourceLoaderContext {
$pos = strrpos( $group, '.' );
if ( $pos === false ) {
// Prefixless modules, i.e. without dots
- $retval = explode( ',', $group );
+ $retval = array_merge( $retval, explode( ',', $group ) );
} else {
// We have a prefix and a bunch of suffixes
$prefix = substr( $group, 0, $pos ); // 'foo'
diff --git a/includes/resourceloader/ResourceLoaderFileModule.php b/includes/resourceloader/ResourceLoaderFileModule.php
index cedb5dcc..9ed181ed 100644
--- a/includes/resourceloader/ResourceLoaderFileModule.php
+++ b/includes/resourceloader/ResourceLoaderFileModule.php
@@ -116,6 +116,12 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
protected $targets = array( 'desktop' );
/**
+ * Boolean: Whether getStyleURLsForDebug should return raw file paths,
+ * or return load.php urls
+ */
+ protected $hasGeneratedStyles = false;
+
+ /**
* Array: Cache for mtime
* @par Usage:
* @code
@@ -187,8 +193,8 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
* @endcode
*/
public function __construct( $options = array(), $localBasePath = null,
- $remoteBasePath = null )
- {
+ $remoteBasePath = null
+ ) {
global $IP, $wgScriptPath, $wgResourceBasePath;
$this->localBasePath = $localBasePath === null ? $IP : $localBasePath;
if ( $remoteBasePath !== null ) {
@@ -209,7 +215,7 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
case 'debugScripts':
case 'loaderScripts':
case 'styles':
- $this->{$member} = (array) $option;
+ $this->{$member} = (array)$option;
break;
// Collated lists of file paths
case 'languageScripts':
@@ -228,26 +234,26 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
"'$key' given, string expected."
);
}
- $this->{$member}[$key] = (array) $value;
+ $this->{$member}[$key] = (array)$value;
}
break;
// Lists of strings
case 'dependencies':
case 'messages':
case 'targets':
- $this->{$member} = (array) $option;
+ $this->{$member} = (array)$option;
break;
// Single strings
case 'group':
case 'position':
case 'localBasePath':
case 'remoteBasePath':
- $this->{$member} = (string) $option;
+ $this->{$member} = (string)$option;
break;
// Single booleans
case 'debugRaw':
case 'raw':
- $this->{$member} = (bool) $option;
+ $this->{$member} = (bool)$option;
break;
}
}
@@ -259,8 +265,8 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
/**
* Gets all scripts for a given context concatenated together.
*
- * @param $context ResourceLoaderContext: Context in which to generate script
- * @return String: JavaScript code for $context
+ * @param ResourceLoaderContext $context Context in which to generate script
+ * @return string: JavaScript code for $context
*/
public function getScript( ResourceLoaderContext $context ) {
$files = $this->getScriptFiles( $context );
@@ -268,7 +274,7 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
}
/**
- * @param $context ResourceLoaderContext
+ * @param ResourceLoaderContext $context
* @return array
*/
public function getScriptURLsForDebug( ResourceLoaderContext $context ) {
@@ -289,7 +295,7 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
/**
* Gets loader script.
*
- * @return String: JavaScript code to be added to startup module
+ * @return string: JavaScript code to be added to startup module
*/
public function getLoaderScript() {
if ( count( $this->loaderScripts ) == 0 ) {
@@ -301,8 +307,8 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
/**
* Gets all styles for a given context concatenated together.
*
- * @param $context ResourceLoaderContext: Context in which to generate styles
- * @return String: CSS code for $context
+ * @param ResourceLoaderContext $context Context in which to generate styles
+ * @return string: CSS code for $context
*/
public function getStyles( ResourceLoaderContext $context ) {
$styles = $this->readStyleFiles(
@@ -324,16 +330,23 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
);
}
} catch ( Exception $e ) {
- wfDebug( __METHOD__ . " failed to update DB: $e\n" );
+ wfDebugLog( 'resourceloader', __METHOD__ . ": failed to update DB: $e" );
}
return $styles;
}
/**
- * @param $context ResourceLoaderContext
+ * @param ResourceLoaderContext $context
* @return array
*/
public function getStyleURLsForDebug( ResourceLoaderContext $context ) {
+ if ( $this->hasGeneratedStyles ) {
+ // Do the default behaviour of returning a url back to load.php
+ // but with only=styles.
+ return parent::getStyleURLsForDebug( $context );
+ }
+ // Our module consists entirely of real css files,
+ // in debug mode we can load those directly.
$urls = array();
foreach ( $this->getStyleFiles( $context ) as $mediaType => $list ) {
$urls[$mediaType] = array();
@@ -347,7 +360,7 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
/**
* Gets list of message keys used by this module.
*
- * @return Array: List of message keys
+ * @return array: List of message keys
*/
public function getMessages() {
return $this->messages;
@@ -356,7 +369,7 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
/**
* Gets the name of the group this module should be loaded in.
*
- * @return String: Group name
+ * @return string: Group name
*/
public function getGroup() {
return $this->group;
@@ -372,7 +385,7 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
/**
* Gets list of names of modules this module depends on.
*
- * @return Array: List of module names
+ * @return array: List of module names
*/
public function getDependencies() {
return $this->dependencies;
@@ -394,9 +407,9 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
* calculations on files relevant to the given language, skin and debug
* mode.
*
- * @param $context ResourceLoaderContext: Context in which to calculate
+ * @param ResourceLoaderContext $context Context in which to calculate
* the modified time
- * @return Integer: UNIX timestamp
+ * @return int: UNIX timestamp
* @see ResourceLoaderModule::getFileDependencies
*/
public function getModifiedTime( ResourceLoaderContext $context ) {
@@ -455,7 +468,7 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
/* Protected Methods */
/**
- * @param $path string
+ * @param string $path
* @return string
*/
protected function getLocalPath( $path ) {
@@ -463,7 +476,7 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
}
/**
- * @param $path string
+ * @param string $path
* @return string
*/
protected function getRemotePath( $path ) {
@@ -471,17 +484,28 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
}
/**
+ * Infer the stylesheet language from a stylesheet file path.
+ *
+ * @since 1.22
+ * @param string $path
+ * @return string: the stylesheet language name
+ */
+ public function getStyleSheetLang( $path ) {
+ return preg_match( '/\.less$/i', $path ) ? 'less' : 'css';
+ }
+
+ /**
* Collates file paths by option (where provided).
*
* @param array $list List of file paths in any combination of index/path
* or path/options pairs
* @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
+ * @param mixed $default default value if the option isn't set
+ * @return array: List of file paths, collated by $option
*/
protected static function collateFilePathListByOption( array $list, $option, $default ) {
$collatedFiles = array();
- foreach ( (array) $list as $key => $value ) {
+ foreach ( (array)$list as $key => $value ) {
if ( is_int( $key ) ) {
// File name as the value
if ( !isset( $collatedFiles[$default] ) ) {
@@ -506,7 +530,7 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
* @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,
+ * @return array: List of elements from $map which matched $key or $fallback,
* or an empty list in case of no match
*/
protected static function tryForKey( array $list, $key, $fallback = null ) {
@@ -524,8 +548,8 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
/**
* Gets a list of file paths for all scripts in this module, in order of propper execution.
*
- * @param $context ResourceLoaderContext: Context
- * @return Array: List of file paths
+ * @param ResourceLoaderContext $context
+ * @return array: List of file paths
*/
protected function getScriptFiles( ResourceLoaderContext $context ) {
$files = array_merge(
@@ -543,8 +567,8 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
/**
* Gets a list of file paths for all styles in this module, in order of propper inclusion.
*
- * @param $context ResourceLoaderContext: Context
- * @return Array: List of file paths
+ * @param ResourceLoaderContext $context
+ * @return array: List of file paths
*/
protected function getStyleFiles( ResourceLoaderContext $context ) {
return array_merge_recursive(
@@ -556,11 +580,28 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
}
/**
+ * Returns all style files used by this module
+ * @return array
+ */
+ public function getAllStyleFiles() {
+ $files = array();
+ foreach( (array)$this->styles as $key => $value ) {
+ if ( is_array( $value ) ) {
+ $path = $key;
+ } else {
+ $path = $value;
+ }
+ $files[] = $this->getLocalPath( $path );
+ }
+ return $files;
+ }
+
+ /**
* Gets the contents of a list of JavaScript files.
*
* @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
+ * @return string: Concatenated and remapped JavaScript data from $scripts
*/
protected function readScriptFiles( array $scripts ) {
global $wgResourceLoaderValidateStaticJS;
@@ -591,9 +632,9 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
* @param array $styles List of media type/list of file paths pairs, to read, remap and
* concetenate
*
- * @param $flip bool
+ * @param bool $flip
*
- * @return Array: List of concatenated and remapped CSS data from $styles,
+ * @return array: List of concatenated and remapped CSS data from $styles,
* keyed by media type
*/
protected function readStyleFiles( array $styles, $flip ) {
@@ -620,9 +661,9 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
* This method can be used as a callback for array_map()
*
* @param string $path File path of style file to read
- * @param $flip bool
+ * @param bool $flip
*
- * @return String: CSS data in script file
+ * @return string: CSS data in script file
* @throws MWException if the file doesn't exist
*/
protected function readStyleFile( $path, $flip ) {
@@ -632,7 +673,14 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
wfDebugLog( 'resourceloader', $msg );
throw new MWException( $msg );
}
- $style = file_get_contents( $localPath );
+
+ if ( $this->getStyleSheetLang( $path ) === 'less' ) {
+ $style = $this->compileLESSFile( $localPath );
+ $this->hasGeneratedStyles = true;
+ } else {
+ $style = file_get_contents( $localPath );
+ }
+
if ( $flip ) {
$style = CSSJanus::transform( $style, true, false );
}
@@ -655,7 +703,7 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
/**
* Get whether CSS for this module should be flipped
- * @param $context ResourceLoaderContext
+ * @param ResourceLoaderContext $context
* @return bool
*/
public function getFlip( $context ) {
@@ -671,4 +719,58 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
return $this->targets;
}
+ /**
+ * Generate a cache key for a LESS file.
+ *
+ * The cache key varies on the file name and the names and values of global
+ * LESS variables.
+ *
+ * @since 1.22
+ * @param string $fileName File name of root LESS file.
+ * @return string: Cache key
+ */
+ protected static function getLESSCacheKey( $fileName ) {
+ $vars = json_encode( ResourceLoader::getLESSVars() );
+ $hash = md5( $fileName . $vars );
+ return wfMemcKey( 'resourceloader', 'less', $hash );
+ }
+
+ /**
+ * Compile a LESS file into CSS.
+ *
+ * If invalid, returns replacement CSS source consisting of the compilation
+ * error message encoded as a comment. To save work, we cache a result object
+ * which comprises the compiled CSS and the names & mtimes of the files
+ * that were processed. lessphp compares the cached & current mtimes and
+ * recompiles as necessary.
+ *
+ * @since 1.22
+ * @param string $fileName File path of LESS source
+ * @return string: CSS source
+ */
+ protected function compileLESSFile( $fileName ) {
+ $key = self::getLESSCacheKey( $fileName );
+ $cache = wfGetCache( CACHE_ANYTHING );
+
+ // The input to lessc. Either an associative array representing the
+ // cached results of a previous compilation, or the string file name if
+ // no cache result exists.
+ $source = $cache->get( $key );
+ if ( !is_array( $source ) || !isset( $source['root'] ) ) {
+ $source = $fileName;
+ }
+
+ $compiler = ResourceLoader::getLessCompiler();
+ $result = null;
+
+ $result = $compiler->cachedCompile( $source );
+
+ if ( !is_array( $result ) ) {
+ throw new MWException( 'LESS compiler result has type ' . gettype( $result ) . '; array expected.' );
+ }
+
+ $this->localFileRefs += array_keys( $result['files'] );
+ $cache->set( $key, $result );
+ return $result['compiled'];
+ }
}
diff --git a/includes/resourceloader/ResourceLoaderLESSFunctions.php b/includes/resourceloader/ResourceLoaderLESSFunctions.php
new file mode 100644
index 00000000..c7570f64
--- /dev/null
+++ b/includes/resourceloader/ResourceLoaderLESSFunctions.php
@@ -0,0 +1,67 @@
+<?php
+/**
+ * PHP-provided functions for LESS; see docs for $wgResourceLoaderLESSFunctions
+ *
+ * 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 ResourceLoaderLESSFunctions {
+ /**
+ * Check if an image file reference is suitable for embedding.
+ * An image is embeddable if it (a) exists, (b) has a suitable MIME-type,
+ * (c) does not exceed IE<9 size limit of 32kb. This is a LESS predicate
+ * function; it returns a LESS boolean value and can thus be used as a
+ * mixin guard.
+ *
+ * @par Example:
+ * @code
+ * .background-image(@url) when(embeddable(@url)) {
+ * background-image: url(@url) !ie;
+ * }
+ * @endcode
+ */
+ public static function embeddable( $frame, $less ) {
+ $base = pathinfo( $less->parser->sourceName, PATHINFO_DIRNAME );
+ $url = $frame[2][0];
+ $file = realpath( $base . '/' . $url );
+ return $less->toBool( $file
+ && strpos( $url, '//' ) === false
+ && filesize( $file ) < CSSMin::EMBED_SIZE_LIMIT
+ && CSSMin::getMimeType( $file ) !== false );
+ }
+
+ /**
+ * Convert an image URI to a base64-encoded data URI.
+ *
+ * @par Example:
+ * @code
+ * .fancy-button {
+ * background-image: embed('../images/button-bg.png');
+ * }
+ * @endcode
+ */
+ public static function embed( $frame, $less ) {
+ $base = pathinfo( $less->parser->sourceName, PATHINFO_DIRNAME );
+ $url = $frame[2][0];
+ $file = realpath( $base . '/' . $url );
+
+ $data = CSSMin::encodeImageAsDataURI( $file );
+ $less->addParsedFile( $file );
+ return 'url(' . $data . ')';
+ }
+}
diff --git a/includes/resourceloader/ResourceLoaderLanguageDataModule.php b/includes/resourceloader/ResourceLoaderLanguageDataModule.php
index 0f8e54ce..fa0fbf85 100644
--- a/includes/resourceloader/ResourceLoaderLanguageDataModule.php
+++ b/includes/resourceloader/ResourceLoaderLanguageDataModule.php
@@ -75,9 +75,10 @@ class ResourceLoaderLanguageDataModule extends ResourceLoaderModule {
return $this->language->separatorTransformTable();
}
-
/**
- * Get all the dynamic data for the content language to an array
+ * Get all the dynamic data for the content language to an array.
+ *
+ * NOTE: Before calling this you HAVE to make sure $this->language is set.
*
* @return array
*/
@@ -105,26 +106,20 @@ class ResourceLoaderLanguageDataModule extends ResourceLoaderModule {
/**
* @param $context ResourceLoaderContext
- * @return array|int|Mixed
+ * @return int: UNIX timestamp
*/
public function getModifiedTime( ResourceLoaderContext $context ) {
- $this->language = Language::factory( $context ->getLanguage() );
- $cache = wfGetCache( CACHE_ANYTHING );
- $key = wfMemcKey( 'resourceloader', 'langdatamodule', 'changeinfo' );
+ return max( 1, $this->getHashMtime( $context ) );
+ }
- $data = $this->getData();
- $hash = md5( serialize( $data ) );
+ /**
+ * @param $context ResourceLoaderContext
+ * @return string: Hash
+ */
+ public function getModifiedHash( ResourceLoaderContext $context ) {
+ $this->language = Language::factory( $context->getLanguage() );
- $result = $cache->get( $key );
- if ( is_array( $result ) && $result['hash'] === $hash ) {
- return $result['timestamp'];
- }
- $timestamp = wfTimestamp();
- $cache->set( $key, array(
- 'hash' => $hash,
- 'timestamp' => $timestamp,
- ) );
- return $timestamp;
+ return md5( serialize( $this->getData() ) );
}
/**
diff --git a/includes/resourceloader/ResourceLoaderModule.php b/includes/resourceloader/ResourceLoaderModule.php
index 03f3cc37..11264fc8 100644
--- a/includes/resourceloader/ResourceLoaderModule.php
+++ b/includes/resourceloader/ResourceLoaderModule.php
@@ -71,7 +71,7 @@ abstract class ResourceLoaderModule {
* Get this module's name. This is set when the module is registered
* with ResourceLoader::register()
*
- * @return Mixed: Name (string) or null if no name was set
+ * @return mixed: Name (string) or null if no name was set
*/
public function getName() {
return $this->name;
@@ -91,7 +91,7 @@ abstract class ResourceLoaderModule {
* Get this module's origin. This is set when the module is registered
* with ResourceLoader::register()
*
- * @return Int ResourceLoaderModule class constant, the subclass default
+ * @return int: ResourceLoaderModule class constant, the subclass default
* if not set manually
*/
public function getOrigin() {
@@ -109,7 +109,7 @@ abstract class ResourceLoaderModule {
}
/**
- * @param $context ResourceLoaderContext
+ * @param ResourceLoaderContext $context
* @return bool
*/
public function getFlip( $context ) {
@@ -122,8 +122,8 @@ abstract class ResourceLoaderModule {
* Get all JS for this module for a given language and skin.
* Includes all relevant JS except loader scripts.
*
- * @param $context ResourceLoaderContext: Context object
- * @return String: JavaScript code
+ * @param ResourceLoaderContext $context
+ * @return string: JavaScript code
*/
public function getScript( ResourceLoaderContext $context ) {
// Stub, override expected
@@ -141,8 +141,8 @@ abstract class ResourceLoaderModule {
* #2 is important to prevent an infinite loop, therefore this function
* MUST return either an only= URL or a non-load.php URL.
*
- * @param $context ResourceLoaderContext: Context object
- * @return Array of URLs
+ * @param ResourceLoaderContext $context
+ * @return array: Array of URLs
*/
public function getScriptURLsForDebug( ResourceLoaderContext $context ) {
$url = ResourceLoader::makeLoaderURL(
@@ -172,8 +172,8 @@ abstract class ResourceLoaderModule {
/**
* Get all CSS for this module for a given skin.
*
- * @param $context ResourceLoaderContext: Context object
- * @return Array: List of CSS strings or array of CSS strings keyed by media type.
+ * @param ResourceLoaderContext $context
+ * @return array: List of CSS strings or array of CSS strings keyed by media type.
* like array( 'screen' => '.foo { width: 0 }' );
* or array( 'screen' => array( '.foo { width: 0 }' ) );
*/
@@ -188,8 +188,8 @@ abstract class ResourceLoaderModule {
* the module, but file-based modules will want to override this to
* load the files directly. See also getScriptURLsForDebug()
*
- * @param $context ResourceLoaderContext: Context object
- * @return Array: array( mediaType => array( URL1, URL2, ... ), ... )
+ * @param ResourceLoaderContext $context
+ * @return array: array( mediaType => array( URL1, URL2, ... ), ... )
*/
public function getStyleURLsForDebug( ResourceLoaderContext $context ) {
$url = ResourceLoader::makeLoaderURL(
@@ -211,7 +211,7 @@ abstract class ResourceLoaderModule {
*
* To get a JSON blob with messages, use MessageBlobStore::get()
*
- * @return Array: List of message keys. Keys may occur more than once
+ * @return array: List of message keys. Keys may occur more than once
*/
public function getMessages() {
// Stub, override expected
@@ -221,7 +221,7 @@ abstract class ResourceLoaderModule {
/**
* Get the group this module is in.
*
- * @return String: Group name
+ * @return string: Group name
*/
public function getGroup() {
// Stub, override expected
@@ -231,7 +231,7 @@ abstract class ResourceLoaderModule {
/**
* Get the origin of this module. Should only be overridden for foreign modules.
*
- * @return String: Origin name, 'local' for local modules
+ * @return string: Origin name, 'local' for local modules
*/
public function getSource() {
// Stub, override expected
@@ -263,7 +263,7 @@ abstract class ResourceLoaderModule {
/**
* Get the loader JS for this module, if set.
*
- * @return Mixed: JavaScript loader code as a string or boolean false if no custom loader set
+ * @return mixed: JavaScript loader code as a string or boolean false if no custom loader set
*/
public function getLoaderScript() {
// Stub, override expected
@@ -274,16 +274,11 @@ abstract class ResourceLoaderModule {
* Get a list of modules this module depends on.
*
* Dependency information is taken into account when loading a module
- * on the client side. When adding a module on the server side,
- * dependency information is NOT taken into account and YOU are
- * responsible for adding dependent modules as well. If you don't do
- * this, the client side loader will send a second request back to the
- * server to fetch the missing modules, which kind of defeats the
- * purpose of the resource loader.
+ * on the client side.
*
* To add dependencies dynamically on the client side, use a custom
* loader script, see getLoaderScript()
- * @return Array: List of module names as strings
+ * @return array: List of module names as strings
*/
public function getDependencies() {
// Stub, override expected
@@ -293,7 +288,7 @@ abstract class ResourceLoaderModule {
/**
* Get target(s) for the module, eg ['desktop'] or ['desktop', 'mobile']
*
- * @return array of strings
+ * @return array: Array of strings
*/
public function getTargets() {
return $this->targets;
@@ -304,7 +299,7 @@ abstract class ResourceLoaderModule {
* Currently these are only image files referenced by the module's CSS.
*
* @param string $skin Skin name
- * @return Array: List of files
+ * @return array: List of files
*/
public function getFileDependencies( $skin ) {
// Try in-object cache first
@@ -319,7 +314,7 @@ abstract class ResourceLoaderModule {
), __METHOD__
);
if ( !is_null( $deps ) ) {
- $this->fileDeps[$skin] = (array) FormatJson::decode( $deps, true );
+ $this->fileDeps[$skin] = (array)FormatJson::decode( $deps, true );
} else {
$this->fileDeps[$skin] = array();
}
@@ -340,7 +335,7 @@ abstract class ResourceLoaderModule {
* Get the last modification timestamp of the message blob for this
* module in a given language.
* @param string $lang Language code
- * @return Integer: UNIX timestamp, or 0 if the module doesn't have messages
+ * @return int: UNIX timestamp, or 0 if the module doesn't have messages
*/
public function getMsgBlobMtime( $lang ) {
if ( !isset( $this->msgBlobMtime[$lang] ) ) {
@@ -387,8 +382,12 @@ abstract class ResourceLoaderModule {
* 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
+ * NOTE: The mtime of the module's hash is NOT automatically included.
+ * If your module provides a getModifiedHash() method, you'll need to call getHashMtime()
+ * yourself and take its result into consideration.
+ *
+ * @param ResourceLoaderContext $context Context object
+ * @return integer UNIX timestamp
*/
public function getModifiedTime( ResourceLoaderContext $context ) {
// 0 would mean now
@@ -396,13 +395,54 @@ abstract class ResourceLoaderModule {
}
/**
+ * Helper method for calculating when the module's hash (if it has one) changed.
+ *
+ * @param ResourceLoaderContext $context
+ * @return integer: UNIX timestamp or 0 if there is no hash provided
+ */
+ public function getHashMtime( ResourceLoaderContext $context ) {
+ $hash = $this->getModifiedHash( $context );
+ if ( !is_string( $hash ) ) {
+ return 0;
+ }
+
+ $cache = wfGetCache( CACHE_ANYTHING );
+ $key = wfMemcKey( 'resourceloader', 'modulemodifiedhash', $this->getName() );
+
+ $data = $cache->get( $key );
+ if ( is_array( $data ) && $data['hash'] === $hash ) {
+ // Hash is still the same, re-use the timestamp of when we first saw this hash.
+ return $data['timestamp'];
+ }
+
+ $timestamp = wfTimestamp();
+ $cache->set( $key, array(
+ 'hash' => $hash,
+ 'timestamp' => $timestamp,
+ ) );
+
+ return $timestamp;
+ }
+
+ /**
+ * Get the last modification timestamp of the message blob for this
+ * module in a given language.
+ *
+ * @param ResourceLoaderContext $context
+ * @return string|null: Hash
+ */
+ public function getModifiedHash( ResourceLoaderContext $context ) {
+ return null;
+ }
+
+ /**
* Check whether this module is known to be empty. If a child class
* has an easy and cheap way to determine that this module is
* definitely going to be empty, it should override this method to
* return true in that case. Callers may optimize the request for this
* module away if this function returns true.
- * @param $context ResourceLoaderContext: Context object
- * @return Boolean
+ * @param ResourceLoaderContext $context
+ * @return bool
*/
public function isKnownEmpty( ResourceLoaderContext $context ) {
return false;
@@ -418,7 +458,7 @@ abstract class ResourceLoaderModule {
*
* @param string $fileName
* @param string $contents
- * @return string JS with the original, or a replacement error
+ * @return string: JS with the original, or a replacement error
*/
protected function validateScriptFile( $fileName, $contents ) {
global $wgResourceLoaderValidateJS;
diff --git a/includes/resourceloader/ResourceLoaderSiteModule.php b/includes/resourceloader/ResourceLoaderSiteModule.php
index 1cc5c1a9..05754d37 100644
--- a/includes/resourceloader/ResourceLoaderSiteModule.php
+++ b/includes/resourceloader/ResourceLoaderSiteModule.php
@@ -37,20 +37,19 @@ class ResourceLoaderSiteModule extends ResourceLoaderWikiModule {
* @return Array: List of pages
*/
protected function getPages( ResourceLoaderContext $context ) {
- global $wgHandheldStyle;
+ global $wgUseSiteJs, $wgUseSiteCss;
- $pages = array(
- 'MediaWiki:Common.js' => array( 'type' => 'script' ),
- 'MediaWiki:Common.css' => array( 'type' => 'style' ),
- 'MediaWiki:' . ucfirst( $context->getSkin() ) . '.js' => array( 'type' => 'script' ),
- 'MediaWiki:' . ucfirst( $context->getSkin() ) . '.css' => array( 'type' => 'style' ),
- 'MediaWiki:Print.css' => array( 'type' => 'style', 'media' => 'print' ),
- );
- if ( $wgHandheldStyle ) {
- $pages['MediaWiki:Handheld.css'] = array(
- 'type' => 'style',
- 'media' => 'handheld' );
+ $pages = array();
+ if ( $wgUseSiteJs ) {
+ $pages['MediaWiki:Common.js'] = array( 'type' => 'script' );
+ $pages['MediaWiki:' . ucfirst( $context->getSkin() ) . '.js'] = array( 'type' => 'script' );
}
+ if ( $wgUseSiteCss ) {
+ $pages['MediaWiki:Common.css'] = array( 'type' => 'style' );
+ $pages['MediaWiki:' . ucfirst( $context->getSkin() ) . '.css'] = array( 'type' => 'style' );
+
+ }
+ $pages['MediaWiki:Print.css'] = array( 'type' => 'style', 'media' => 'print' );
return $pages;
}
diff --git a/includes/resourceloader/ResourceLoaderStartUpModule.php b/includes/resourceloader/ResourceLoaderStartUpModule.php
index 32cf6b26..20f6e0ba 100644
--- a/includes/resourceloader/ResourceLoaderStartUpModule.php
+++ b/includes/resourceloader/ResourceLoaderStartUpModule.php
@@ -27,6 +27,7 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
/* Protected Members */
protected $modifiedTime = array();
+ protected $targets = array( 'desktop', 'mobile' );
/* Protected Methods */
@@ -51,7 +52,7 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
*/
$namespaceIds = $wgContLang->getNamespaceIds();
$caseSensitiveNamespaces = array();
- foreach( MWNamespace::getCanonicalNamespaces() as $index => $name ) {
+ foreach ( MWNamespace::getCanonicalNamespaces() as $index => $name ) {
$namespaceIds[$wgContLang->lc( $name )] = $index;
if ( !MWNamespace::isCapitalized( $index ) ) {
$caseSensitiveNamespaces[] = $index;
@@ -83,7 +84,7 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
'wgFormattedNamespaces' => $wgContLang->getFormattedNamespaces(),
'wgNamespaceIds' => $namespaceIds,
'wgSiteName' => $wgSitename,
- 'wgFileExtensions' => array_values( $wgFileExtensions ),
+ 'wgFileExtensions' => array_values( array_unique( $wgFileExtensions ) ),
'wgDBname' => $wgDBname,
// This sucks, it is only needed on Special:Upload, but I could
// not find a way to add vars only for a certain module
@@ -94,6 +95,7 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
'wgCookiePrefix' => $wgCookiePrefix,
'wgResourceLoaderMaxQueryLength' => $wgResourceLoaderMaxQueryLength,
'wgCaseSensitiveNamespaces' => $caseSensitiveNamespaces,
+ 'wgLegalTitleChars' => Title::convertByteClassToUnicodeClass( Title::legalChars() ),
);
wfRunHooks( 'ResourceLoaderGetConfigVars', array( &$vars ) );
@@ -184,7 +186,7 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
* @return string
*/
public function getScript( ResourceLoaderContext $context ) {
- global $IP, $wgLoadScript, $wgLegacyJavaScriptGlobals;
+ global $IP, $wgLegacyJavaScriptGlobals;
$out = file_get_contents( "$IP/resources/startup.js" );
if ( $context->getOnly() === 'scripts' ) {
@@ -224,7 +226,7 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
"};\n";
// Conditional script injection
- $scriptTag = Html::linkedScript( $wgLoadScript . '?' . wfArrayToCgi( $query ) );
+ $scriptTag = Html::linkedScript( wfAppendQuery( wfScript( 'load' ), $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 bdb240e0..bda86539 100644
--- a/includes/resourceloader/ResourceLoaderUserCSSPrefsModule.php
+++ b/includes/resourceloader/ResourceLoaderUserCSSPrefsModule.php
@@ -56,43 +56,44 @@ class ResourceLoaderUserCSSPrefsModule extends ResourceLoaderModule {
public function getStyles( ResourceLoaderContext $context ) {
global $wgAllowUserCssPrefs, $wgUser;
- if ( $wgAllowUserCssPrefs ) {
- $options = $wgUser->getOptions();
+ if ( !$wgAllowUserCssPrefs ) {
+ return array();
+ }
- // Build CSS rules
- $rules = array();
+ $options = $wgUser->getOptions();
- // Underline: 2 = browser default, 1 = always, 0 = never
- if ( $options['underline'] < 2 ) {
- $rules[] = "a { text-decoration: " .
- ( $options['underline'] ? 'underline' : 'none' ) . "; }";
- } else {
- # The scripts of these languages are very hard to read with underlines
- $rules[] = 'a:lang(ar), a:lang(ckb), a:lang(fa),a:lang(kk-arab), ' .
- 'a:lang(mzn), a:lang(ps), a:lang(ur) { text-decoration: none; }';
- }
- if ( $options['justify'] ) {
- $rules[] = "#article, #bodyContent, #mw_content { text-align: justify; }\n";
- }
- if ( !$options['showtoc'] ) {
- $rules[] = "#toc { display: none; }\n";
- }
- if ( !$options['editsection'] ) {
- $rules[] = ".editsection { display: none; }\n";
- }
- if ( $options['editfont'] !== 'default' ) {
- // Double-check that $options['editfont'] consists of safe characters only
- if ( preg_match( '/^[a-zA-Z0-9_, -]+$/', $options['editfont'] ) ) {
- $rules[] = "textarea { font-family: {$options['editfont']}; }\n";
- }
- }
- $style = implode( "\n", $rules );
- if ( $this->getFlip( $context ) ) {
- $style = CSSJanus::transform( $style, true, false );
+ // Build CSS rules
+ $rules = array();
+
+ // Underline: 2 = browser default, 1 = always, 0 = never
+ if ( $options['underline'] < 2 ) {
+ $rules[] = "a { text-decoration: " .
+ ( $options['underline'] ? 'underline' : 'none' ) . "; }";
+ } else {
+ # The scripts of these languages are very hard to read with underlines
+ $rules[] = 'a:lang(ar), a:lang(ckb), a:lang(kk-arab), ' .
+ 'a:lang(mzn), a:lang(ps), a:lang(ur) { text-decoration: none; }';
+ }
+ if ( $options['justify'] ) {
+ $rules[] = "#article, #bodyContent, #mw_content { text-align: justify; }\n";
+ }
+ if ( !$options['showtoc'] ) {
+ $rules[] = "#toc { display: none; }\n";
+ }
+ if ( !$options['editsection'] ) {
+ $rules[] = ".mw-editsection { display: none; }\n";
+ }
+ if ( $options['editfont'] !== 'default' ) {
+ // Double-check that $options['editfont'] consists of safe characters only
+ if ( preg_match( '/^[a-zA-Z0-9_, -]+$/', $options['editfont'] ) ) {
+ $rules[] = "textarea { font-family: {$options['editfont']}; }\n";
}
- return array( 'all' => $style );
}
- return array();
+ $style = implode( "\n", $rules );
+ if ( $this->getFlip( $context ) ) {
+ $style = CSSJanus::transform( $style, true, false );
+ }
+ return array( 'all' => $style );
}
/**
diff --git a/includes/resourceloader/ResourceLoaderUserGroupsModule.php b/includes/resourceloader/ResourceLoaderUserGroupsModule.php
index 1316f423..9064263f 100644
--- a/includes/resourceloader/ResourceLoaderUserGroupsModule.php
+++ b/includes/resourceloader/ResourceLoaderUserGroupsModule.php
@@ -33,12 +33,15 @@ class ResourceLoaderUserGroupsModule extends ResourceLoaderWikiModule {
* @return array
*/
protected function getPages( ResourceLoaderContext $context ) {
- global $wgUser;
+ global $wgUser, $wgUseSiteJs, $wgUseSiteCss;
$userName = $context->getUser();
if ( $userName === null ) {
return array();
}
+ if ( !$wgUseSiteJs && !$wgUseSiteCss ) {
+ return array();
+ }
// Use $wgUser is possible; allows to skip a lot of code
if ( is_object( $wgUser ) && $wgUser->getName() == $userName ) {
@@ -51,12 +54,16 @@ class ResourceLoaderUserGroupsModule extends ResourceLoaderWikiModule {
}
$pages = array();
- foreach( $user->getEffectiveGroups() as $group ) {
+ foreach ( $user->getEffectiveGroups() as $group ) {
if ( in_array( $group, array( '*', 'user' ) ) ) {
continue;
}
- $pages["MediaWiki:Group-$group.js"] = array( 'type' => 'script' );
- $pages["MediaWiki:Group-$group.css"] = array( 'type' => 'style' );
+ if ( $wgUseSiteJs ) {
+ $pages["MediaWiki:Group-$group.js"] = array( 'type' => 'script' );
+ }
+ if ( $wgUseSiteCss ) {
+ $pages["MediaWiki:Group-$group.css"] = array( 'type' => 'style' );
+ }
}
return $pages;
}
diff --git a/includes/resourceloader/ResourceLoaderUserModule.php b/includes/resourceloader/ResourceLoaderUserModule.php
index 177302c5..7a04e473 100644
--- a/includes/resourceloader/ResourceLoaderUserModule.php
+++ b/includes/resourceloader/ResourceLoaderUserModule.php
@@ -35,11 +35,15 @@ class ResourceLoaderUserModule extends ResourceLoaderWikiModule {
* @return array
*/
protected function getPages( ResourceLoaderContext $context ) {
+ global $wgAllowUserJs, $wgAllowUserCss;
$username = $context->getUser();
if ( $username === null ) {
return array();
}
+ if ( !$wgAllowUserJs && !$wgAllowUserCss ) {
+ return array();
+ }
// Get the normalized title of the user's user page
$userpageTitle = Title::makeTitleSafe( NS_USER, $username );
@@ -50,14 +54,15 @@ class ResourceLoaderUserModule extends ResourceLoaderWikiModule {
$userpage = $userpageTitle->getPrefixedDBkey(); // Needed so $excludepages works
- $pages = array(
- "$userpage/common.js" => array( 'type' => 'script' ),
- "$userpage/" . $context->getSkin() . '.js' =>
- array( 'type' => 'script' ),
- "$userpage/common.css" => array( 'type' => 'style' ),
- "$userpage/" . $context->getSkin() . '.css' =>
- array( 'type' => 'style' ),
- );
+ $pages = array();
+ if ( $wgAllowUserJs ) {
+ $pages["$userpage/common.js"] = array( 'type' => 'script' );
+ $pages["$userpage/" . $context->getSkin() . '.js'] = array( 'type' => 'script' );
+ }
+ if ( $wgAllowUserCss ) {
+ $pages["$userpage/common.css"] = array( 'type' => 'style' );
+ $pages["$userpage/" . $context->getSkin() . '.css'] = array( 'type' => 'style' );
+ }
// Hack for bug 26283: if we're on a preview page for a CSS/JS page,
// we need to exclude that page from this module. In that case, the excludepage
diff --git a/includes/resourceloader/ResourceLoaderUserOptionsModule.php b/includes/resourceloader/ResourceLoaderUserOptionsModule.php
index 4624cbce..0b7e1964 100644
--- a/includes/resourceloader/ResourceLoaderUserOptionsModule.php
+++ b/includes/resourceloader/ResourceLoaderUserOptionsModule.php
@@ -56,7 +56,9 @@ class ResourceLoaderUserOptionsModule extends ResourceLoaderModule {
public function getScript( ResourceLoaderContext $context ) {
global $wgUser;
return Xml::encodeJsCall( 'mw.user.options.set',
- array( $wgUser->getOptions() ) );
+ array( $wgUser->getOptions() ),
+ ResourceLoader::inDebugMode()
+ );
}
/**
diff --git a/includes/resourceloader/ResourceLoaderUserTokensModule.php b/includes/resourceloader/ResourceLoaderUserTokensModule.php
index 6d787c50..92ebbe93 100644
--- a/includes/resourceloader/ResourceLoaderUserTokensModule.php
+++ b/includes/resourceloader/ResourceLoaderUserTokensModule.php
@@ -35,10 +35,9 @@ class ResourceLoaderUserTokensModule extends ResourceLoaderModule {
/**
* Fetch the tokens for the current user.
*
- * @param $context ResourceLoaderContext: Context object
- * @return Array: List of tokens keyed by token type
+ * @return array: List of tokens keyed by token type
*/
- protected function contextUserTokens( ResourceLoaderContext $context ) {
+ protected function contextUserTokens() {
global $wgUser;
return array(
@@ -54,7 +53,9 @@ class ResourceLoaderUserTokensModule extends ResourceLoaderModule {
*/
public function getScript( ResourceLoaderContext $context ) {
return Xml::encodeJsCall( 'mw.user.tokens.set',
- array( $this->contextUserTokens( $context ) ) );
+ array( $this->contextUserTokens() ),
+ ResourceLoader::inDebugMode()
+ );
}
/**
diff --git a/includes/resourceloader/ResourceLoaderWikiModule.php b/includes/resourceloader/ResourceLoaderWikiModule.php
index 6c60d474..3f10ae53 100644
--- a/includes/resourceloader/ResourceLoaderWikiModule.php
+++ b/includes/resourceloader/ResourceLoaderWikiModule.php
@@ -92,14 +92,14 @@ abstract class ResourceLoaderWikiModule extends ResourceLoaderModule {
$content = $revision->getContent( Revision::RAW );
if ( !$content ) {
- wfDebug( __METHOD__ . "failed to load content of JS/CSS page!\n" );
+ wfDebugLog( 'resourceloader', __METHOD__ . ': failed to load content of JS/CSS page!' );
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" );
+ wfDebugLog( 'resourceloader', __METHOD__ . ': bad content model $model for JS/CSS page!' );
return null;
}
diff --git a/includes/revisiondelete/RevisionDelete.php b/includes/revisiondelete/RevisionDelete.php
index f4c07968..71850877 100644
--- a/includes/revisiondelete/RevisionDelete.php
+++ b/includes/revisiondelete/RevisionDelete.php
@@ -41,6 +41,19 @@ class RevDel_RevisionList extends RevDel_List {
return 'rev_id';
}
+ public static function getRestriction() {
+ return 'deleterevision';
+ }
+
+ public static function getRevdelConstant() {
+ return Revision::DELETED_TEXT;
+ }
+
+ public static function suggestTarget( $target, array $ids ) {
+ $rev = Revision::newFromId( $ids[0] );
+ return $rev ? $rev->getTitle() : $target;
+ }
+
/**
* @param $db DatabaseBase
* @return mixed
@@ -52,7 +65,7 @@ class RevDel_RevisionList extends RevDel_List {
array_merge( Revision::selectFields(), Revision::selectUserFields() ),
array(
'rev_page' => $this->title->getArticleID(),
- 'rev_id' => $ids,
+ 'rev_id' => $ids,
),
__METHOD__,
array( 'ORDER BY' => 'rev_id DESC' ),
@@ -242,8 +255,7 @@ class RevDel_RevisionItem extends RevDel_Item {
if ( $this->isDeleted() && !$this->canViewContent() ) {
return $this->list->msg( 'diff' )->escaped();
} else {
- return
- Linker::linkKnown(
+ return Linker::linkKnown(
$this->list->title,
$this->list->msg( 'diff' )->escaped(),
array(),
@@ -293,7 +305,7 @@ class RevDel_ArchiveList extends RevDel_RevisionList {
return $db->select( 'archive', '*',
array(
'ar_namespace' => $this->title->getNamespace(),
- 'ar_title' => $this->title->getDBkey(),
+ 'ar_title' => $this->title->getDBkey(),
'ar_timestamp' => $timestamps
),
__METHOD__,
@@ -350,12 +362,12 @@ class RevDel_ArchiveItem extends RevDel_RevisionItem {
$dbw->update( 'archive',
array( 'ar_deleted' => $bits ),
array(
- 'ar_namespace' => $this->list->title->getNamespace(),
- 'ar_title' => $this->list->title->getDBkey(),
+ 'ar_namespace' => $this->list->title->getNamespace(),
+ 'ar_title' => $this->list->title->getDBkey(),
// use timestamp for index
- 'ar_timestamp' => $this->row->ar_timestamp,
- 'ar_rev_id' => $this->row->ar_rev_id,
- 'ar_deleted' => $this->getBits()
+ 'ar_timestamp' => $this->row->ar_timestamp,
+ 'ar_rev_id' => $this->row->ar_rev_id,
+ 'ar_deleted' => $this->getBits()
),
__METHOD__ );
return (bool)$dbw->affectedRows();
@@ -442,6 +454,14 @@ class RevDel_FileList extends RevDel_List {
return 'oi_archive_name';
}
+ public static function getRestriction() {
+ return 'deleterevision';
+ }
+
+ public static function getRevdelConstant() {
+ return File::DELETED_FILE;
+ }
+
var $storeBatch, $deleteBatch, $cleanupBatch;
/**
@@ -450,14 +470,14 @@ class RevDel_FileList extends RevDel_List {
*/
public function doQuery( $db ) {
$archiveNames = array();
- foreach( $this->ids as $timestamp ) {
+ foreach ( $this->ids as $timestamp ) {
$archiveNames[] = $timestamp . '!' . $this->title->getDBkey();
}
return $db->select(
'oldimage',
OldLocalFile::selectFields(),
array(
- 'oi_name' => $this->title->getDBkey(),
+ 'oi_name' => $this->title->getDBkey(),
'oi_archive_name' => $archiveNames
),
__METHOD__,
@@ -635,8 +655,8 @@ class RevDel_FileItem extends RevDel_Item {
array(),
array(
'target' => $this->list->title->getPrefixedText(),
- 'file' => $this->file->getArchiveName(),
- 'token' => $this->list->getUser()->getEditToken(
+ 'file' => $this->file->getArchiveName(),
+ 'token' => $this->list->getUser()->getEditToken(
$this->file->getArchiveName() )
)
);
@@ -648,13 +668,13 @@ class RevDel_FileItem extends RevDel_Item {
* @return string HTML
*/
protected function getUserTools() {
- if( $this->file->userCan( Revision::DELETED_USER, $this->list->getUser() ) ) {
+ if ( $this->file->userCan( Revision::DELETED_USER, $this->list->getUser() ) ) {
$link = Linker::userLink( $this->file->user, $this->file->user_text ) .
Linker::userToolLinks( $this->file->user, $this->file->user_text );
} else {
$link = $this->list->msg( 'rev-deleted-user' )->escaped();
}
- if( $this->file->isDeleted( Revision::DELETED_USER ) ) {
+ if ( $this->file->isDeleted( Revision::DELETED_USER ) ) {
return '<span class="history-deleted">' . $link . '</span>';
}
return $link;
@@ -667,12 +687,12 @@ class RevDel_FileItem extends RevDel_Item {
* @return string HTML
*/
protected function getComment() {
- if( $this->file->userCan( File::DELETED_COMMENT, $this->list->getUser() ) ) {
+ if ( $this->file->userCan( File::DELETED_COMMENT, $this->list->getUser() ) ) {
$block = Linker::commentBlock( $this->file->description );
} else {
$block = ' ' . $this->list->msg( 'rev-deleted-comment' )->escaped();
}
- if( $this->file->isDeleted( File::DELETED_COMMENT ) ) {
+ if ( $this->file->isDeleted( File::DELETED_COMMENT ) ) {
return "<span class=\"history-deleted\">$block</span>";
}
return $block;
@@ -685,7 +705,7 @@ class RevDel_FileItem extends RevDel_Item {
' (' . $this->list->msg( 'nbytes' )->numParams( $this->file->getSize() )->text() . ')';
return '<li>' . $this->getLink() . ' ' . $this->getUserTools() . ' ' .
- $data . ' ' . $this->getComment(). '</li>';
+ $data . ' ' . $this->getComment() . '</li>';
}
}
@@ -712,7 +732,7 @@ class RevDel_ArchivedFileList extends RevDel_FileList {
ArchivedFile::selectFields(),
array(
'fa_name' => $this->title->getDBkey(),
- 'fa_id' => $ids
+ 'fa_id' => $ids
),
__METHOD__,
array( 'ORDER BY' => 'fa_id DESC' )
@@ -771,7 +791,7 @@ class RevDel_ArchivedFileItem extends RevDel_FileItem {
$this->file->getTimestamp(), $this->list->getUser() ) );
# Hidden files...
- if( !$this->canViewContent() ) {
+ if ( !$this->canViewContent() ) {
$link = $date;
} else {
$undelete = SpecialPage::getTitleFor( 'Undelete' );
@@ -784,7 +804,7 @@ class RevDel_ArchivedFileItem extends RevDel_FileItem {
)
);
}
- if( $this->isDeleted() ) {
+ if ( $this->isDeleted() ) {
$link = '<span class="history-deleted">' . $link . '</span>';
}
return $link;
@@ -803,6 +823,28 @@ class RevDel_LogList extends RevDel_List {
return 'log_id';
}
+ public static function getRestriction() {
+ return 'deletelogentry';
+ }
+
+ public static function getRevdelConstant() {
+ return LogPage::DELETED_ACTION;
+ }
+
+ public static function suggestTarget( $target, array $ids ) {
+ $result = wfGetDB( DB_SLAVE )->select( 'logging',
+ 'log_type',
+ array( 'log_id' => $ids ),
+ __METHOD__,
+ array( 'DISTINCT' )
+ );
+ if ( $result->numRows() == 1 ) {
+ // If there's only one type, the target can be set to include it.
+ return SpecialPage::getTitleFor( 'Log', $result->current()->log_type );
+ }
+ return SpecialPage::getTitleFor( 'Log' );
+ }
+
/**
* @param $db DatabaseBase
* @return mixed
@@ -913,7 +955,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 b2108de6..803467e6 100644
--- a/includes/revisiondelete/RevisionDeleteAbstracts.php
+++ b/includes/revisiondelete/RevisionDeleteAbstracts.php
@@ -37,13 +37,45 @@ abstract class RevDel_List extends RevisionListBase {
* Get the DB field name associated with the ID list.
* This used to populate the log_search table for finding log entries.
* Override this function.
- * @return null
+ * @return string|null
*/
public static function getRelationType() {
return null;
}
/**
+ * Get the user right required for this list type
+ * Override this function.
+ * @since 1.22
+ * @return string|null
+ */
+ public static function getRestriction() {
+ return null;
+ }
+
+ /**
+ * Get the revision deletion constant for this list type
+ * Override this function.
+ * @since 1.22
+ * @return int|null
+ */
+ public static function getRevdelConstant() {
+ return null;
+ }
+
+ /**
+ * Suggest a target for the revision deletion
+ * Optionally override this function.
+ * @since 1.22
+ * @param Title|null $target User-supplied target
+ * @param array $ids
+ * @return Title|null
+ */
+ public static function suggestTarget( $target, array $ids ) {
+ return $target;
+ }
+
+ /**
* Set the visibility for the revisions in this list. Logging and
* transactions are done here.
*
@@ -68,11 +100,11 @@ abstract class RevDel_List extends RevisionListBase {
for ( $this->reset(); $this->current(); $this->next() ) {
$item = $this->current();
- unset( $missing[ $item->getId() ] );
+ unset( $missing[$item->getId()] );
$oldBits = $item->getBits();
// Build the actual new rev_deleted bitfield
- $newBits = SpecialRevisionDelete::extractBitfield( $bitPars, $oldBits );
+ $newBits = RevisionDeleter::extractBitfield( $bitPars, $oldBits );
if ( $oldBits == $newBits ) {
$status->warning( 'revdelete-no-change', $item->formatDate(), $item->formatTime() );
@@ -94,14 +126,14 @@ abstract class RevDel_List extends RevisionListBase {
}
if ( !$item->canView() ) {
// Cannot access this revision
- $msg = ($opType == 'show') ?
+ $msg = ( $opType == 'show' ) ?
'revdelete-show-no-access' : 'revdelete-modify-no-access';
$status->error( $msg, $item->formatDate(), $item->formatTime() );
$status->failCount++;
continue;
}
// Cannot just "hide from Sysops" without hiding any fields
- if( $newBits == Revision::DELETED_RESTRICTED ) {
+ if ( $newBits == Revision::DELETED_RESTRICTED ) {
$status->warning( 'revdelete-only-restricted', $item->formatDate(), $item->formatTime() );
$status->failCount++;
continue;
@@ -113,9 +145,9 @@ abstract class RevDel_List extends RevisionListBase {
if ( $ok ) {
$idsForLog[] = $item->getId();
$status->successCount++;
- if( $item->getAuthorId() > 0 ) {
+ if ( $item->getAuthorId() > 0 ) {
$authorIds[] = $item->getAuthorId();
- } elseif( IP::isIPAddress( $item->getAuthorName() ) ) {
+ } elseif ( IP::isIPAddress( $item->getAuthorName() ) ) {
$authorIPs[] = $item->getAuthorName();
}
} else {
@@ -189,7 +221,7 @@ abstract class RevDel_List extends RevisionListBase {
protected function updateLog( $params ) {
// Get the URL param's corresponding DB field
$field = RevisionDeleter::getRelationType( $this->getType() );
- if( !$field ) {
+ if ( !$field ) {
throw new MWException( "Bad log URL param type!" );
}
// Put things hidden from sysops in the oversight log
@@ -203,7 +235,7 @@ abstract class RevDel_List extends RevisionListBase {
// Actually add the deletion log entry
$log = new LogPage( $logType );
$logid = $log->addEntry( $this->getLogAction(), $params['title'],
- $params['comment'], $logParams );
+ $params['comment'], $logParams, $this->getUser() );
// Allow for easy searching of deletion log items for revision/log items
$log->addRelations( $field, $params['ids'], $logid );
$log->addRelations( 'target_author_id', $params['authorIds'], $logid );
diff --git a/includes/revisiondelete/RevisionDeleteUser.php b/includes/revisiondelete/RevisionDeleteUser.php
index c02e9c76..4505ee10 100644
--- a/includes/revisiondelete/RevisionDeleteUser.php
+++ b/includes/revisiondelete/RevisionDeleteUser.php
@@ -54,7 +54,7 @@ class RevisionDeleteUser {
# The same goes for the sysop-restricted *_deleted bit.
$delUser = Revision::DELETED_USER | Revision::DELETED_RESTRICTED;
$delAction = LogPage::DELETED_ACTION | Revision::DELETED_RESTRICTED;
- if( $op == '&' ) {
+ if ( $op == '&' ) {
$delUser = "~{$delUser}";
$delAction = "~{$delAction}";
}
diff --git a/includes/revisiondelete/RevisionDeleter.php b/includes/revisiondelete/RevisionDeleter.php
index fe351c51..dbcb3d7d 100644
--- a/includes/revisiondelete/RevisionDeleter.php
+++ b/includes/revisiondelete/RevisionDeleter.php
@@ -22,11 +22,71 @@
*/
/**
- * Temporary b/c interface, collection of static functions.
- * @ingroup SpecialPage
+ * General controller for RevDel, used by both SpecialRevisiondelete and
+ * ApiRevisionDelete.
* @ingroup RevisionDelete
*/
class RevisionDeleter {
+ /** List of known revdel types, with their corresponding list classes */
+ private static $allowedTypes = array(
+ 'revision' => 'RevDel_RevisionList',
+ 'archive' => 'RevDel_ArchiveList',
+ 'oldimage' => 'RevDel_FileList',
+ 'filearchive' => 'RevDel_ArchivedFileList',
+ 'logging' => 'RevDel_LogList',
+ );
+
+ /** Type map to support old log entries */
+ private static $deprecatedTypeMap = array(
+ 'oldid' => 'revision',
+ 'artimestamp' => 'archive',
+ 'oldimage' => 'oldimage',
+ 'fileid' => 'filearchive',
+ 'logid' => 'logging',
+ );
+
+ /**
+ * Lists the valid possible types for revision deletion.
+ *
+ * @since 1.22
+ * @return array
+ */
+ public static function getTypes() {
+ return array_keys( self::$allowedTypes );
+ }
+
+ /**
+ * Gets the canonical type name, if any.
+ *
+ * @since 1.22
+ * @param string $typeName
+ * @return string|null
+ */
+ public static function getCanonicalTypeName( $typeName ) {
+ if ( isset( self::$deprecatedTypeMap[$typeName] ) ) {
+ $typeName = self::$deprecatedTypeMap[$typeName];
+ }
+ return isset( self::$allowedTypes[$typeName] ) ? $typeName : null;
+ }
+
+ /**
+ * Instantiate the appropriate list class for a given list of IDs.
+ *
+ * @since 1.22
+ * @param string $typeName RevDel type, see RevisionDeleter::getTypes()
+ * @param IContextSource $context
+ * @param Title $title
+ * @param array $ids
+ * @return RevDel_List
+ */
+ public static function createList( $typeName, IContextSource $context, Title $title, array $ids ) {
+ $typeName = self::getCanonicalTypeName( $typeName );
+ if ( !$typeName ) {
+ throw new MWException( __METHOD__ . ": Unknown RevDel type '$typeName'" );
+ }
+ return new self::$allowedTypes[$typeName]( $context, $title, $ids );
+ }
+
/**
* Checks for a change in the bitfield for a certain option and updates the
* provided array accordingly.
@@ -39,8 +99,8 @@ class RevisionDeleter {
* @param array $arr the array to update.
*/
protected static function checkItem( $desc, $field, $diff, $new, &$arr ) {
- if( $diff & $field ) {
- $arr[ ( $new & $field ) ? 0 : 1 ][] = $desc;
+ if ( $diff & $field ) {
+ $arr[( $new & $field ) ? 0 : 1][] = $desc;
}
}
@@ -73,11 +133,12 @@ class RevisionDeleter {
self::checkItem( 'revdelete-uname',
Revision::DELETED_USER, $diff, $n, $ret );
// Restriction application to sysops
- if( $diff & Revision::DELETED_RESTRICTED ) {
- if( $n & Revision::DELETED_RESTRICTED )
+ if ( $diff & Revision::DELETED_RESTRICTED ) {
+ if ( $n & Revision::DELETED_RESTRICTED ) {
$ret[2][] = 'revdelete-restricted';
- else
+ } else {
$ret[2][] = 'revdelete-unrestricted';
+ }
}
return $ret;
}
@@ -85,20 +146,62 @@ class RevisionDeleter {
/** Get DB field name for URL param...
* Future code for other things may also track
* other types of revision-specific changes.
+ * @param string $typeName
* @return string One of log_id/rev_id/fa_id/ar_timestamp/oi_archive_name
*/
public static function getRelationType( $typeName ) {
- if ( isset( SpecialRevisionDelete::$deprecatedTypeMap[$typeName] ) ) {
- $typeName = SpecialRevisionDelete::$deprecatedTypeMap[$typeName];
+ $typeName = self::getCanonicalTypeName( $typeName );
+ if ( !$typeName ) {
+ return null;
}
- if ( isset( SpecialRevisionDelete::$allowedTypes[$typeName] ) ) {
- $class = SpecialRevisionDelete::$allowedTypes[$typeName]['list-class'];
- return call_user_func( array( $class, 'getRelationType' ) );
- } else {
+ return call_user_func( array( self::$allowedTypes[$typeName], 'getRelationType' ) );
+ }
+
+ /**
+ * Get the user right required for the RevDel type
+ * @since 1.22
+ * @param string $typeName
+ * @return string User right
+ */
+ public static function getRestriction( $typeName ) {
+ $typeName = self::getCanonicalTypeName( $typeName );
+ if ( !$typeName ) {
+ return null;
+ }
+ return call_user_func( array( self::$allowedTypes[$typeName], 'getRestriction' ) );
+ }
+
+ /**
+ * Get the revision deletion constant for the RevDel type
+ * @since 1.22
+ * @param string $typeName
+ * @return int RevDel constant
+ */
+ public static function getRevdelConstant( $typeName ) {
+ $typeName = self::getCanonicalTypeName( $typeName );
+ if ( !$typeName ) {
return null;
}
+ return call_user_func( array( self::$allowedTypes[$typeName], 'getRevdelConstant' ) );
+ }
+
+ /**
+ * Suggest a target for the revision deletion
+ * @since 1.22
+ * @param string $typeName
+ * @param Title|null $title User-supplied target
+ * @param array $ids
+ * @return Title|null
+ */
+ public static function suggestTarget( $typeName, $target, array $ids ) {
+ $typeName = self::getCanonicalTypeName( $typeName );
+ if ( !$typeName ) {
+ return $target;
+ }
+ return call_user_func( array( self::$allowedTypes[$typeName], 'suggestTarget' ), $target, $ids );
}
+
/**
* Checks if a revision still exists in the revision table.
* If it doesn't, returns the corresponding ar_timestamp field
@@ -124,4 +227,24 @@ class RevisionDeleter {
return $timestamp;
}
+
+ /**
+ * Put together a rev_deleted bitfield
+ * @since 1.22
+ * @param array $bitPars extractBitParams() params
+ * @param int $oldfield current bitfield
+ * @return array
+ */
+ public static function extractBitfield( $bitPars, $oldfield ) {
+ // Build the actual new rev_deleted bitfield
+ $newBits = 0;
+ foreach ( $bitPars as $const => $val ) {
+ if ( $val == 1 ) {
+ $newBits |= $const; // $const is the *_deleted const
+ } elseif ( $val == -1 ) {
+ $newBits |= ( $oldfield & $const ); // use existing
+ }
+ }
+ return $newBits;
+ }
}
diff --git a/includes/search/SearchEngine.php b/includes/search/SearchEngine.php
index 6b3e62b1..71c05d8b 100644
--- a/includes/search/SearchEngine.php
+++ b/includes/search/SearchEngine.php
@@ -59,7 +59,7 @@ class SearchEngine {
* STUB
*
* @param string $term raw search term
- * @return SearchResultSet
+ * @return SearchResultSet|Status|null
*/
function searchText( $term ) {
return null;
@@ -71,7 +71,7 @@ class SearchEngine {
* STUB
*
* @param string $term raw search term
- * @return SearchResultSet
+ * @return SearchResultSet|null
*/
function searchTitle( $term ) {
return null;
@@ -93,8 +93,9 @@ class SearchEngine {
* @return Boolean
*/
public function supports( $feature ) {
- switch( $feature ) {
+ switch ( $feature ) {
case 'list-redirects':
+ case 'search-update':
return true;
case 'title-suffix-filter':
default:
@@ -331,8 +332,9 @@ class SearchEngine {
$parsed = substr( $query, strlen( $prefix ) + 1 );
}
}
- if ( trim( $parsed ) == '' )
+ if ( trim( $parsed ) == '' ) {
$parsed = $query; // prefix was the whole query
+ }
wfRunHooks( 'SearchEngineReplacePrefixesComplete', array( $this, $query, &$parsed ) );
@@ -420,8 +422,9 @@ class SearchEngine {
$formatted = array_map( array( $wgContLang, 'getFormattedNsText' ), $namespaces );
foreach ( $formatted as $key => $ns ) {
- if ( empty( $ns ) )
+ if ( empty( $ns ) ) {
$formatted[$key] = wfMessage( 'blanknamespace' )->text();
+ }
}
return $formatted;
}
@@ -451,23 +454,46 @@ class SearchEngine {
* Load up the appropriate search engine class for the currently
* active database backend, and return a configured instance.
*
+ * @param String $type Type of search backend, if not the default
* @return SearchEngine
*/
- public static function create() {
+ public static function create( $type = null ) {
global $wgSearchType;
$dbr = null;
- if ( $wgSearchType ) {
+
+ $alternatives = self::getSearchTypes();
+
+ if ( $type && in_array( $type, $alternatives ) ) {
+ $class = $type;
+ } elseif ( $wgSearchType !== null ) {
$class = $wgSearchType;
} else {
$dbr = wfGetDB( DB_SLAVE );
$class = $dbr->getSearchEngine();
}
+
$search = new $class( $dbr );
$search->setLimitOffset( 0, 0 );
return $search;
}
/**
+ * Return the search engines we support. If only $wgSearchType
+ * is set, it'll be an array of just that one item.
+ *
+ * @return array
+ */
+ public static function getSearchTypes() {
+ global $wgSearchType, $wgSearchTypeAlternatives;
+ static $alternatives = null;
+ if ( $alternatives === null ) {
+ $alternatives = $wgSearchTypeAlternatives ?: array();
+ array_unshift( $alternatives, $wgSearchType );
+ }
+ return $alternatives;
+ }
+
+ /**
* Create or update the search index record for the given page.
* Title and text should be pre-processed.
* STUB
@@ -493,6 +519,18 @@ class SearchEngine {
}
/**
+ * Delete an indexed page
+ * Title should be pre-processed.
+ * STUB
+ *
+ * @param Integer $id Page id that was deleted
+ * @param String $title Title of page that was deleted
+ */
+ function delete( $id, $title ) {
+ // no-op
+ }
+
+ /**
* Get OpenSearch suggestion template
*
* @return String
@@ -509,6 +547,31 @@ class SearchEngine {
return $wgCanonicalServer . wfScript( 'api' ) . '?action=opensearch&search={searchTerms}&namespace=' . $ns;
}
}
+
+ /**
+ * Get the raw text for updating the index from a content object
+ * Nicer search backends could possibly do something cooler than
+ * just returning raw text
+ *
+ * @todo This isn't ideal, we'd really like to have content-specific handling here
+ * @param Title $t Title we're indexing
+ * @param Content $c Content of the page to index
+ * @return string
+ */
+ public function getTextFromContent( Title $t, Content $c = null ) {
+ return $c ? $c->getTextForSearchIndex() : '';
+ }
+
+ /**
+ * If an implementation of SearchEngine handles all of its own text processing
+ * in getTextFromContent() and doesn't require SearchUpdate::updateText()'s
+ * rather silly handling, it should return true here instead.
+ *
+ * @return bool
+ */
+ public function textAlreadyUpdatedForIndex() {
+ return false;
+ }
}
/**
@@ -641,26 +704,30 @@ class SqlSearchResultSet extends SearchResultSet {
}
function numRows() {
- if ( $this->mResultSet === false )
+ if ( $this->mResultSet === false ) {
return false;
+ }
return $this->mResultSet->numRows();
}
function next() {
- if ( $this->mResultSet === false )
+ if ( $this->mResultSet === false ) {
return false;
+ }
$row = $this->mResultSet->fetchObject();
- if ( $row === false )
+ if ( $row === false ) {
return false;
+ }
return SearchResult::newFromRow( $row );
}
function free() {
- if ( $this->mResultSet === false )
+ if ( $this->mResultSet === false ) {
return false;
+ }
$this->mResultSet->free();
}
@@ -750,8 +817,9 @@ class SearchResult {
wfRunHooks( 'SearchResultInitFromTitle', array( $title, &$id ) );
$this->mRevision = Revision::newFromTitle(
$this->mTitle, $id, Revision::READ_NORMAL );
- if ( $this->mTitle->getNamespace() === NS_FILE )
+ if ( $this->mTitle->getNamespace() === NS_FILE ) {
$this->mImage = wfFindFile( $this->mTitle );
+ }
}
}
@@ -761,8 +829,9 @@ class SearchResult {
* @return Boolean
*/
function isBrokenTitle() {
- if ( is_null( $this->mTitle ) )
+ if ( is_null( $this->mTitle ) ) {
return true;
+ }
return false;
}
@@ -795,10 +864,8 @@ class SearchResult {
protected function initText() {
if ( !isset( $this->mText ) ) {
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() : '';
+ $this->mText = SearchEngine::create()
+ ->getTextFromContent( $this->mTitle, $this->mRevision->getContent() );
} else { // TODO: can we fetch raw wikitext for commons images?
$this->mText = '';
}
@@ -810,16 +877,17 @@ class SearchResult {
* @return String: highlighted text snippet, null (and not '') if not supported
*/
function getTextSnippet( $terms ) {
- global $wgUser, $wgAdvancedSearchHighlighting;
+ global $wgAdvancedSearchHighlighting;
$this->initText();
// TODO: make highliter take a content object. Make ContentHandler a factory for SearchHighliter.
- list( $contextlines, $contextchars ) = SearchEngine::userHighlightPrefs( $wgUser );
+ list( $contextlines, $contextchars ) = SearchEngine::userHighlightPrefs();
$h = new SearchHighlighter();
- if ( $wgAdvancedSearchHighlighting )
+ if ( $wgAdvancedSearchHighlighting ) {
return $h->highlightText( $this->mText, $terms, $contextlines, $contextchars );
- else
+ } else {
return $h->highlightSimple( $this->mText, $terms, $contextlines, $contextchars );
+ }
}
/**
@@ -863,10 +931,11 @@ class SearchResult {
* @return String: timestamp
*/
function getTimestamp() {
- if ( $this->mRevision )
+ if ( $this->mRevision ) {
return $this->mRevision->getTimestamp();
- elseif ( $this->mImage )
+ } elseif ( $this->mImage ) {
return $this->mImage->getTimestamp();
+ }
return '';
}
@@ -952,8 +1021,9 @@ class SearchHighlighter {
global $wgSearchHighlightBoundaries;
$fname = __METHOD__;
- if ( $text == '' )
+ if ( $text == '' ) {
return '';
+ }
// spli text into text + templates/links/tables
$spat = "/(\\{\\{)|(\\[\\[[^\\]:]+:)|(\n\\{\\|)";
@@ -984,8 +1054,9 @@ class SearchHighlighter {
if ( $key == 2 ) {
// see if this is an image link
$ns = substr( $val[0], 2, - 1 );
- if ( $wgContLang->getNsIndex( $ns ) != NS_FILE )
+ if ( $wgContLang->getNsIndex( $ns ) != NS_FILE ) {
break;
+ }
}
$epat = $endPatterns[$key];
@@ -1006,7 +1077,7 @@ class SearchHighlighter {
$len = strlen( $endMatches[2][0] );
$off = $endMatches[2][1];
$this->splitAndAdd( $otherExt, $count,
- substr( $text, $start, $off + $len - $start ) );
+ substr( $text, $start, $off + $len - $start ) );
$start = $off + $len;
$found = true;
break;
@@ -1119,7 +1190,7 @@ class SearchHighlighter {
// if begin of the article contains the whole phrase, show only that !!
if ( array_key_exists( $first, $snippets ) && preg_match( $pat1, $snippets[$first] )
&& $offsets[$first] < $contextchars * 2 ) {
- $snippets = array ( $first => $snippets[$first] );
+ $snippets = array( $first => $snippets[$first] );
}
// calc by how much to extend existing snippets
@@ -1155,17 +1226,19 @@ class SearchHighlighter {
$last = - 1;
$extract = '';
foreach ( $snippets as $index => $line ) {
- if ( $last == - 1 )
+ if ( $last == - 1 ) {
$extract .= $line; // first line
- elseif ( $last + 1 == $index && $offsets[$last] + strlen( $snippets[$last] ) >= strlen( $all[$last] ) )
+ } elseif ( $last + 1 == $index && $offsets[$last] + strlen( $snippets[$last] ) >= strlen( $all[$last] ) ) {
$extract .= " " . $line; // continous lines
- else
+ } else {
$extract .= '<b> ... </b>' . $line;
+ }
$last = $index;
}
- if ( $extract )
+ if ( $extract ) {
$extract .= '<b> ... </b>';
+ }
$processed = array();
foreach ( $terms as $term ) {
@@ -1193,8 +1266,9 @@ class SearchHighlighter {
$split = explode( "\n", $this->mCleanWikitext ? $this->removeWiki( $text ) : $text );
foreach ( $split as $line ) {
$tt = trim( $line );
- if ( $tt )
+ if ( $tt ) {
$extracts[$count++] = $tt;
+ }
}
}
@@ -1268,8 +1342,9 @@ class SearchHighlighter {
while ( $char >= 0x80 && $char < 0xc0 ) {
// skip trailing bytes
$point++;
- if ( $point >= strlen( $text ) )
+ if ( $point >= strlen( $text ) ) {
return strlen( $text );
+ }
$char = ord( $text[$point] );
}
return $point;
@@ -1289,24 +1364,28 @@ class SearchHighlighter {
* @protected
*/
function process( $pattern, $extracts, &$linesleft, &$contextchars, &$out, &$offsets ) {
- if ( $linesleft == 0 )
+ if ( $linesleft == 0 ) {
return; // nothing to do
+ }
foreach ( $extracts as $index => $line ) {
- if ( array_key_exists( $index, $out ) )
+ if ( array_key_exists( $index, $out ) ) {
continue; // this line already highlighted
+ }
$m = array();
- if ( !preg_match( $pattern, $line, $m, PREG_OFFSET_CAPTURE ) )
+ if ( !preg_match( $pattern, $line, $m, PREG_OFFSET_CAPTURE ) ) {
continue;
+ }
$offset = $m[0][1];
$len = strlen( $m[0][0] );
- if ( $offset + $len < $contextchars )
+ if ( $offset + $len < $contextchars ) {
$begin = 0;
- elseif ( $len > $contextchars )
+ } elseif ( $len > $contextchars ) {
$begin = $offset;
- else
+ } else {
$begin = $offset + intval( ( $len - $contextchars ) / 2 );
+ }
$end = $begin + $contextchars;
@@ -1315,8 +1394,9 @@ class SearchHighlighter {
$out[$index] = $this->extract( $line, $begin, $end, $posBegin );
$offsets[$index] = $posBegin;
$linesleft--;
- if ( $linesleft == 0 )
+ if ( $linesleft == 0 ) {
return;
+ }
}
}
@@ -1357,16 +1437,17 @@ class SearchHighlighter {
*/
function linkReplace( $matches ) {
$colon = strpos( $matches[1], ':' );
- if ( $colon === false )
+ if ( $colon === false ) {
return $matches[2]; // replace with caption
+ }
global $wgContLang;
$ns = substr( $matches[1], 0, $colon );
$index = $wgContLang->getNsIndex( $ns );
- if ( $index !== false && ( $index == NS_FILE || $index == NS_CATEGORY ) )
+ if ( $index !== false && ( $index == NS_FILE || $index == NS_CATEGORY ) ) {
return $matches[0]; // return the whole thing
- else
+ } else {
return $matches[2];
-
+ }
}
/**
diff --git a/includes/search/SearchMssql.php b/includes/search/SearchMssql.php
index 163d9dc3..cbc1a7a7 100644
--- a/includes/search/SearchMssql.php
+++ b/includes/search/SearchMssql.php
@@ -170,8 +170,9 @@ class SearchMssql extends SearchEngine {
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] ), '/' );
}
@@ -247,8 +248,9 @@ class MssqlSearchResultSet extends SearchResultSet {
function next() {
$row = $this->mResultSet->fetchObject();
- if ( $row === false )
+ if ( $row === false ) {
return false;
+ }
return new SearchResult( $row );
}
}
diff --git a/includes/search/SearchMySQL.php b/includes/search/SearchMySQL.php
index 4a501fd0..b2bc1c26 100644
--- a/includes/search/SearchMySQL.php
+++ b/includes/search/SearchMySQL.php
@@ -57,12 +57,12 @@ class SearchMySQL extends SearchEngine {
# @todo FIXME: This doesn't handle parenthetical expressions.
$m = array();
- if( preg_match_all( '/([-+<>~]?)(([' . $lc . ']+)(\*?)|"[^"]*")/',
+ if ( preg_match_all( '/([-+<>~]?)(([' . $lc . ']+)(\*?)|"[^"]*")/',
$filteredText, $m, PREG_SET_ORDER ) ) {
- foreach( $m as $bits ) {
+ foreach ( $m as $bits ) {
@list( /* all */, $modifier, $term, $nonQuoted, $wildcard ) = $bits;
- if( $nonQuoted != '' ) {
+ if ( $nonQuoted != '' ) {
$term = $nonQuoted;
$quote = '';
} else {
@@ -70,8 +70,10 @@ class SearchMySQL extends SearchEngine {
$quote = '"';
}
- if( $searchon !== '' ) $searchon .= ' ';
- if( $this->strictMatching && ($modifier == '') ) {
+ if ( $searchon !== '' ) {
+ $searchon .= ' ';
+ }
+ if ( $this->strictMatching && ( $modifier == '' ) ) {
// If we leave this out, boolean op defaults to OR which is rarely helpful.
$modifier = '+';
}
@@ -79,7 +81,7 @@ class SearchMySQL extends SearchEngine {
// Some languages such as Serbian store the input form in the search index,
// so we may need to search for matches in multiple writing system variants.
$convertedVariants = $wgContLang->autoConvertToAllVariants( $term );
- if( is_array( $convertedVariants ) ) {
+ if ( is_array( $convertedVariants ) ) {
$variants = array_unique( array_values( $convertedVariants ) );
} else {
$variants = array( $term );
@@ -99,11 +101,12 @@ class SearchMySQL extends SearchEngine {
$strippedVariants = array_unique( $strippedVariants );
$searchon .= $modifier;
- if( count( $strippedVariants) > 1 )
+ if ( count( $strippedVariants ) > 1 ) {
$searchon .= '(';
- foreach( $strippedVariants as $stripped ) {
+ }
+ foreach ( $strippedVariants as $stripped ) {
$stripped = $this->normalizeText( $stripped );
- if( $nonQuoted && strpos( $stripped, ' ' ) !== false ) {
+ if ( $nonQuoted && strpos( $stripped, ' ' ) !== false ) {
// Hack for Chinese: we need to toss in quotes for
// multiple-character phrases since normalizeForSearch()
// added spaces between them to make word breaks.
@@ -111,8 +114,9 @@ class SearchMySQL extends SearchEngine {
}
$searchon .= "$quote$stripped$quote$wildcard ";
}
- if( count( $strippedVariants) > 1 )
+ if ( count( $strippedVariants ) > 1 ) {
$searchon .= ')';
+ }
// Match individual terms or quoted phrase in result highlighting...
// Note that variants will be introduced in a later stage for highlighting!
@@ -134,8 +138,8 @@ class SearchMySQL extends SearchEngine {
global $wgContLang;
$regex = preg_quote( $string, '/' );
- if( $wgContLang->hasWordBreaks() ) {
- if( $wildcard ) {
+ if ( $wgContLang->hasWordBreaks() ) {
+ if ( $wildcard ) {
// Don't cut off the final bit!
$regex = "\b$regex";
} else {
@@ -177,7 +181,9 @@ class SearchMySQL extends SearchEngine {
global $wgCountTotalSearchHits;
// This seems out of place, why is this called with empty term?
- if ( trim( $term ) === '' ) return null;
+ if ( trim( $term ) === '' ) {
+ return null;
+ }
$filteredTerm = $this->filter( $term );
$query = $this->getQuery( $filteredTerm, $fulltext );
@@ -187,7 +193,7 @@ class SearchMySQL extends SearchEngine {
);
$total = null;
- if( $wgCountTotalSearchHits ) {
+ if ( $wgCountTotalSearchHits ) {
$query = $this->getCountQuery( $filteredTerm, $fulltext );
$totalResult = $this->db->select(
$query['tables'], $query['fields'], $query['conds'],
@@ -195,7 +201,7 @@ class SearchMySQL extends SearchEngine {
);
$row = $totalResult->fetchObject();
- if( $row ) {
+ if ( $row ) {
$total = intval( $row->c );
}
$totalResult->free();
@@ -205,12 +211,11 @@ class SearchMySQL extends SearchEngine {
}
public function supports( $feature ) {
- switch( $feature ) {
- case 'list-redirects':
+ switch ( $feature ) {
case 'title-suffix-filter':
return true;
default:
- return false;
+ return parent::supports( $feature );
}
}
@@ -223,7 +228,7 @@ class SearchMySQL extends SearchEngine {
foreach ( $this->features as $feature => $value ) {
if ( $feature === 'list-redirects' && !$value ) {
$query['conds']['page_is_redirect'] = 0;
- } elseif( $feature === 'title-suffix-filter' && $value ) {
+ } elseif ( $feature === 'title-suffix-filter' && $value ) {
$query['conds'][] = 'page_title' . $this->db->buildLike( $this->db->anyString(), $value );
}
}
@@ -358,12 +363,25 @@ class SearchMySQL extends SearchEngine {
$dbw->update( 'searchindex',
array( 'si_title' => $this->normalizeText( $title ) ),
- array( 'si_page' => $id ),
+ array( 'si_page' => $id ),
__METHOD__,
array( $dbw->lowPriorityOption() ) );
}
/**
+ * Delete an indexed page
+ * Title should be pre-processed.
+ *
+ * @param Integer $id Page id that was deleted
+ * @param String $title Title of page that was deleted
+ */
+ function delete( $id, $title ) {
+ $dbw = wfGetDB( DB_MASTER );
+
+ $dbw->delete( 'searchindex', array( 'si_page' => $id ), __METHOD__ );
+ }
+
+ /**
* Converts some characters for MySQL's indexing to grok it correctly,
* and pads short words to overcome limitations.
* @return mixed|string
@@ -386,7 +404,7 @@ class SearchMySQL extends SearchEngine {
// ignores short words... Pad them so we can pass them
// through without reconfiguring the server...
$minLength = $this->minSearchLength();
- if( $minLength > 1 ) {
+ if ( $minLength > 1 ) {
$n = $minLength - 1;
$out = preg_replace(
"/\b(\w{1,$n})\b/",
@@ -427,7 +445,7 @@ class SearchMySQL extends SearchEngine {
* @return int
*/
protected function minSearchLength() {
- if( is_null( self::$mMinSearchLength ) ) {
+ if ( is_null( self::$mMinSearchLength ) ) {
$sql = "SHOW GLOBAL VARIABLES LIKE 'ft\\_min\\_word\\_len'";
$dbr = wfGetDB( DB_SLAVE );
@@ -435,7 +453,7 @@ class SearchMySQL extends SearchEngine {
$row = $result->fetchObject();
$result->free();
- if( $row && $row->Variable_name == 'ft_min_word_len' ) {
+ if ( $row && $row->Variable_name == 'ft_min_word_len' ) {
self::$mMinSearchLength = intval( $row->Value );
} else {
self::$mMinSearchLength = 0;
@@ -449,7 +467,7 @@ class SearchMySQL extends SearchEngine {
* @ingroup Search
*/
class MySQLSearchResultSet extends SqlSearchResultSet {
- function __construct( $resultSet, $terms, $totalHits=null ) {
+ function __construct( $resultSet, $terms, $totalHits = null ) {
parent::__construct( $resultSet, $terms );
$this->mTotalHits = $totalHits;
}
diff --git a/includes/search/SearchOracle.php b/includes/search/SearchOracle.php
index b0ea97fe..a8479654 100644
--- a/includes/search/SearchOracle.php
+++ b/includes/search/SearchOracle.php
@@ -30,32 +30,34 @@
*/
class SearchOracle extends SearchEngine {
- 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,
- 'NOT' => 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,
- 'WITHIN' => 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,
+ 'NOT' => 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,
+ 'WITHIN' => 1,
+ );
/**
* Creates an instance of this class
@@ -72,8 +74,9 @@ class SearchOracle extends SearchEngine {
* @return SqlSearchResultSet
*/
function searchText( $term ) {
- if ( $term == '' )
+ 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 );
@@ -86,8 +89,9 @@ class SearchOracle extends SearchEngine {
* @return SqlSearchResultSet
*/
function searchTitle( $term ) {
- if ( $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 );
@@ -110,8 +114,9 @@ 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';
} else {
@@ -195,23 +200,24 @@ class SearchOracle extends SearchEngine {
$searchon = '';
if ( preg_match_all( '/([-+<>~]?)(([' . $lc . ']+)(\*?)|"[^"]*")/',
$filteredText, $m, PREG_SET_ORDER ) ) {
- foreach( $m as $terms ) {
+ 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 ) {
- $searchon .= ($terms[1] == '-' ? ' ~' : ' & ') . $this->escapeTerm( $t );
+ if ( is_array( $temp_terms ) ) {
+ $temp_terms = array_unique( array_values( $temp_terms ) );
+ foreach ( $temp_terms as $t ) {
+ $searchon .= ( $terms[1] == '-' ? ' ~' : ' & ' ) . $this->escapeTerm( $t );
}
}
else {
- $searchon .= ($terms[1] == '-' ? ' ~' : ' & ') . $this->escapeTerm( $terms[2] );
+ $searchon .= ( $terms[1] == '-' ? ' ~' : ' & ' ) . $this->escapeTerm( $terms[2] );
}
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] ), '/' );
}
@@ -227,9 +233,9 @@ class SearchOracle extends SearchEngine {
private function escapeTerm( $t ) {
global $wgContLang;
$t = $wgContLang->normalizeForSearch( $t );
- $t = isset( $this->reservedWords[strtoupper( $t )] ) ? '{'.$t.'}' : $t;
- $t = preg_replace('/^"(.*)"$/', '($1)', $t);
- $t = preg_replace('/([-&|])/', '\\\\$1', $t);
+ $t = isset( $this->reservedWords[strtoupper( $t )] ) ? '{' . $t . '}' : $t;
+ $t = preg_replace( '/^"(.*)"$/', '($1)', $t );
+ $t = preg_replace( '/([-&|])/', '\\\\$1', $t );
return $t;
}
/**
@@ -273,7 +279,7 @@ class SearchOracle extends SearchEngine {
$dbw->update( 'searchindex',
array( 'si_title' => $title ),
- array( 'si_page' => $id ),
+ array( 'si_page' => $id ),
'SearchOracle::updateTitle',
array() );
}
diff --git a/includes/search/SearchPostgres.php b/includes/search/SearchPostgres.php
index 56464e98..7f19ed13 100644
--- a/includes/search/SearchPostgres.php
+++ b/includes/search/SearchPostgres.php
@@ -64,7 +64,7 @@ class SearchPostgres extends SearchEngine {
function searchText( $term ) {
$q = $this->searchQuery( $term, 'textvector', 'old_text' );
- $olderror = error_reporting(E_ERROR);
+ $olderror = error_reporting( E_ERROR );
$resultSet = $this->db->resultObject( $this->db->query( $q, 'SearchPostgres', true ) );
error_reporting( $olderror );
if ( !$resultSet ) {
@@ -86,19 +86,19 @@ class SearchPostgres extends SearchEngine {
wfDebug( "parseQuery received: $term \n" );
## No backslashes allowed
- $term = preg_replace('/\\\/', '', $term);
+ $term = preg_replace( '/\\\/', '', $term );
## Collapse parens into nearby words:
- $term = preg_replace('/\s*\(\s*/', ' (', $term);
- $term = preg_replace('/\s*\)\s*/', ') ', $term);
+ $term = preg_replace( '/\s*\(\s*/', ' (', $term );
+ $term = preg_replace( '/\s*\)\s*/', ') ', $term );
## Treat colons as word separators:
- $term = preg_replace('/:/', ' ', $term);
+ $term = preg_replace( '/:/', ' ', $term );
$searchstring = '';
$m = array();
- if( preg_match_all('/([-!]?)(\S+)\s*/', $term, $m, PREG_SET_ORDER ) ) {
- foreach( $m as $terms ) {
+ if ( preg_match_all( '/([-!]?)(\S+)\s*/', $term, $m, PREG_SET_ORDER ) ) {
+ foreach ( $m as $terms ) {
if ( strlen( $terms[1] ) ) {
$searchstring .= ' & !';
}
@@ -118,19 +118,19 @@ class SearchPostgres extends SearchEngine {
}
## Strip out leading junk
- $searchstring = preg_replace('/^[\s\&\|]+/', '', $searchstring);
+ $searchstring = preg_replace( '/^[\s\&\|]+/', '', $searchstring );
## Remove any doubled-up operators
- $searchstring = preg_replace('/([\!\&\|]) +(?:[\&\|] +)+/', "$1 ", $searchstring);
+ $searchstring = preg_replace( '/([\!\&\|]) +(?:[\&\|] +)+/', "$1 ", $searchstring );
## Remove any non-spaced operators (e.g. "Zounds!")
- $searchstring = preg_replace('/([^ ])[\!\&\|]/', "$1", $searchstring);
+ $searchstring = preg_replace( '/([^ ])[\!\&\|]/', "$1", $searchstring );
## Remove any trailing whitespace or operators
- $searchstring = preg_replace('/[\s\!\&\|]+$/', '', $searchstring);
+ $searchstring = preg_replace( '/[\s\!\&\|]+$/', '', $searchstring );
## Remove unnecessary quotes around everything
- $searchstring = preg_replace('/^[\'"](.*)[\'"]$/', "$1", $searchstring);
+ $searchstring = preg_replace( '/^[\'"](.*)[\'"]$/', "$1", $searchstring );
## Quote the whole thing
$searchstring = $this->db->addQuotes( $searchstring );
@@ -163,30 +163,31 @@ class SearchPostgres extends SearchEngine {
$top = $top[0];
if ( $top === "" ) { ## e.g. if only stopwords are used XXX return something better
- $query = "SELECT page_id, page_namespace, page_title, 0 AS score ".
+ $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";
}
else {
$m = array();
- if( preg_match_all("/'([^']+)'/", $top, $m, PREG_SET_ORDER ) ) {
- foreach( $m as $terms ) {
+ if ( preg_match_all( "/'([^']+)'/", $top, $m, PREG_SET_ORDER ) ) {
+ foreach ( $m as $terms ) {
$this->searchTerms[$terms[1]] = $terms[1];
}
}
- $query = "SELECT page_id, page_namespace, page_title, ".
- "ts_rank($fulltext, to_tsquery($searchstring), 5) AS score ".
+ $query = "SELECT page_id, page_namespace, page_title, " .
+ "ts_rank($fulltext, to_tsquery($searchstring), 5) 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 $fulltext @@ to_tsquery($searchstring)";
}
## 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 ( !is_null( $this->namespaces ) ) { // null -> search all
if ( count( $this->namespaces ) < 1 ) {
$query .= ' AND page_namespace = 0';
} else {
@@ -208,7 +209,7 @@ class SearchPostgres extends SearchEngine {
function update( $pageid, $title, $text ) {
## We don't want to index older revisions
- $SQL = "UPDATE pagecontent SET textvector = NULL WHERE old_id IN ".
+ $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 );
@@ -244,7 +245,7 @@ class PostgresSearchResultSet extends SqlSearchResultSet {
function next() {
$row = $this->mResultSet->fetchObject();
- if( $row === false ) {
+ if ( $row === false ) {
return false;
} else {
return new PostgresSearchResult( $row );
diff --git a/includes/search/SearchSqlite.php b/includes/search/SearchSqlite.php
index f3f4788c..554181f6 100644
--- a/includes/search/SearchSqlite.php
+++ b/includes/search/SearchSqlite.php
@@ -61,12 +61,12 @@ class SearchSqlite extends SearchEngine {
$this->searchTerms = array();
$m = array();
- if( preg_match_all( '/([-+<>~]?)(([' . $lc . ']+)(\*?)|"[^"]*")/',
+ if ( preg_match_all( '/([-+<>~]?)(([' . $lc . ']+)(\*?)|"[^"]*")/',
$filteredText, $m, PREG_SET_ORDER ) ) {
- foreach( $m as $bits ) {
+ foreach ( $m as $bits ) {
@list( /* all */, $modifier, $term, $nonQuoted, $wildcard ) = $bits;
- if( $nonQuoted != '' ) {
+ if ( $nonQuoted != '' ) {
$term = $nonQuoted;
$quote = '';
} else {
@@ -74,14 +74,14 @@ class SearchSqlite extends SearchEngine {
$quote = '"';
}
- if( $searchon !== '' ) {
+ if ( $searchon !== '' ) {
$searchon .= ' ';
}
// Some languages such as Serbian store the input form in the search index,
// so we may need to search for matches in multiple writing system variants.
$convertedVariants = $wgContLang->autoConvertToAllVariants( $term );
- if( is_array( $convertedVariants ) ) {
+ if ( is_array( $convertedVariants ) ) {
$variants = array_unique( array_values( $convertedVariants ) );
} else {
$variants = array( $term );
@@ -101,10 +101,11 @@ class SearchSqlite extends SearchEngine {
$strippedVariants = array_unique( $strippedVariants );
$searchon .= $modifier;
- if( count( $strippedVariants) > 1 )
+ if ( count( $strippedVariants ) > 1 ) {
$searchon .= '(';
- foreach( $strippedVariants as $stripped ) {
- if( $nonQuoted && strpos( $stripped, ' ' ) !== false ) {
+ }
+ foreach ( $strippedVariants as $stripped ) {
+ if ( $nonQuoted && strpos( $stripped, ' ' ) !== false ) {
// Hack for Chinese: we need to toss in quotes for
// multiple-character phrases since normalizeForSearch()
// added spaces between them to make word breaks.
@@ -112,8 +113,9 @@ class SearchSqlite extends SearchEngine {
}
$searchon .= "$quote$stripped$quote$wildcard ";
}
- if( count( $strippedVariants) > 1 )
+ if ( count( $strippedVariants ) > 1 ) {
$searchon .= ')';
+ }
// Match individual terms or quoted phrase in result highlighting...
// Note that variants will be introduced in a later stage for highlighting!
@@ -134,8 +136,8 @@ class SearchSqlite extends SearchEngine {
global $wgContLang;
$regex = preg_quote( $string, '/' );
- if( $wgContLang->hasWordBreaks() ) {
- if( $wildcard ) {
+ if ( $wgContLang->hasWordBreaks() ) {
+ if ( $wildcard ) {
// Don't cut off the final bit!
$regex = "\b$regex";
} else {
@@ -184,10 +186,10 @@ class SearchSqlite extends SearchEngine {
$resultSet = $this->db->query( $this->getQuery( $filteredTerm, $fulltext ) );
$total = null;
- if( $wgCountTotalSearchHits ) {
+ if ( $wgCountTotalSearchHits ) {
$totalResult = $this->db->query( $this->getCountQuery( $filteredTerm, $fulltext ) );
$row = $totalResult->fetchObject();
- if( $row ) {
+ if ( $row ) {
$total = intval( $row->c );
}
$totalResult->free();
@@ -201,7 +203,7 @@ class SearchSqlite extends SearchEngine {
* @return String
*/
function queryRedirect() {
- if( $this->showRedirects ) {
+ if ( $this->showRedirects ) {
return '';
} else {
return 'AND page_is_redirect=0';
@@ -213,8 +215,9 @@ 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';
} else {
@@ -295,7 +298,7 @@ class SearchSqlite extends SearchEngine {
if ( !$this->fulltextSearchSupported() ) {
return;
}
- // @todo: find a method to do it in a single request,
+ // @todo find a method to do it in a single request,
// couldn't do it so far due to typelessness of FTS3 tables.
$dbw = wfGetDB( DB_MASTER );
@@ -324,7 +327,7 @@ class SearchSqlite extends SearchEngine {
$dbw->update( 'searchindex',
array( 'si_title' => $title ),
- array( 'rowid' => $id ),
+ array( 'rowid' => $id ),
__METHOD__ );
}
}
@@ -333,7 +336,7 @@ class SearchSqlite extends SearchEngine {
* @ingroup Search
*/
class SqliteSearchResultSet extends SqlSearchResultSet {
- function __construct( $resultSet, $terms, $totalHits=null ) {
+ function __construct( $resultSet, $terms, $totalHits = null ) {
parent::__construct( $resultSet, $terms );
$this->mTotalHits = $totalHits;
}
diff --git a/includes/search/SearchUpdate.php b/includes/search/SearchUpdate.php
index eabcda3e..82a413e9 100644
--- a/includes/search/SearchUpdate.php
+++ b/includes/search/SearchUpdate.php
@@ -29,51 +29,108 @@
* @ingroup Search
*/
class SearchUpdate implements DeferrableUpdate {
-
- private $mId = 0, $mNamespace, $mTitle, $mText;
- private $mTitleWords;
-
- function __construct( $id, $title, $text = false ) {
+ /**
+ * Page id being updated
+ * @var int
+ */
+ private $id = 0;
+
+ /**
+ * Title we're updating
+ * @var Title
+ */
+ private $title;
+
+ /**
+ * Content of the page (not text)
+ * @var Content|false
+ */
+ private $content;
+
+ /**
+ * Constructor
+ *
+ * @param int $id Page id to update
+ * @param Title|string $title Title of page to update
+ * @param Content|string|false $c Content of the page to update.
+ * If a Content object, text will be gotten from it. String is for back-compat.
+ * Passing false tells the backend to just update the title, not the content
+ */
+ public function __construct( $id, $title, $c = false ) {
if ( is_string( $title ) ) {
$nt = Title::newFromText( $title );
} else {
$nt = $title;
}
- if( $nt ) {
- $this->mId = $id;
- $this->mText = $text;
-
- $this->mNamespace = $nt->getNamespace();
- $this->mTitle = $nt->getText(); # Discard namespace
-
- $this->mTitleWords = $this->mTextWords = array();
+ if ( $nt ) {
+ $this->id = $id;
+ // is_string() check is back-compat for ApprovedRevs
+ if ( is_string( $c ) ) {
+ $this->content = new TextContent( $c );
+ } else {
+ $this->content = $c ?: false;
+ }
+ $this->title = $nt;
} else {
wfDebug( "SearchUpdate object created with invalid title '$title'\n" );
}
}
- function doUpdate() {
- global $wgContLang, $wgDisableSearchUpdate;
+ /**
+ * Perform actual update for the entry
+ */
+ public function doUpdate() {
+ global $wgDisableSearchUpdate;
- if( $wgDisableSearchUpdate || !$this->mId ) {
+ if ( $wgDisableSearchUpdate || !$this->id ) {
return;
}
wfProfileIn( __METHOD__ );
- $search = SearchEngine::create();
- $lc = SearchEngine::legalSearchChars() . '&#;';
+ $page = WikiPage::newFromId( $this->id, WikiPage::READ_LATEST );
+ $indexTitle = Title::indexTitle( $this->title->getNamespace(), $this->title->getText() );
- if( $this->mText === false ) {
- $search->updateTitle($this->mId,
- $search->normalizeText( Title::indexTitle( $this->mNamespace, $this->mTitle ) ) );
- wfProfileOut( __METHOD__ );
- return;
+ foreach ( SearchEngine::getSearchTypes() as $type ) {
+ $search = SearchEngine::create( $type );
+ if ( !$search->supports( 'search-update' ) ) {
+ continue;
+ }
+
+ $normalTitle = $search->normalizeText( $indexTitle );
+
+ if ( $page === null ) {
+ $search->delete( $this->id, $normalTitle );
+ continue;
+ } elseif ( $this->content === false ) {
+ $search->updateTitle( $this->id, $normalTitle );
+ continue;
+ }
+
+ $text = $search->getTextFromContent( $this->title, $this->content );
+ if ( !$search->textAlreadyUpdatedForIndex() ) {
+ $text = self::updateText( $text );
+ }
+
+ # Perform the actual update
+ $search->update( $this->id, $normalTitle, $search->normalizeText( $text ) );
}
+ wfProfileOut( __METHOD__ );
+ }
+
+ /**
+ * Clean text for indexing. Only really suitable for indexing in databases.
+ * If you're using a real search engine, you'll probably want to override
+ * this behavior and do something nicer with the original wikitext.
+ */
+ public static function updateText( $text ) {
+ global $wgContLang;
+
# Language-specific strip/conversion
- $text = $wgContLang->normalizeForSearch( $this->mText );
+ $text = $wgContLang->normalizeForSearch( $text );
+ $lc = SearchEngine::legalSearchChars() . '&#;';
wfProfileIn( __METHOD__ . '-regexps' );
$text = preg_replace( "/<\\/?\\s*[A-Za-z][^>]*?>/",
@@ -123,22 +180,6 @@ class SearchUpdate implements DeferrableUpdate {
# Strip wiki '' and '''
$text = preg_replace( "/''[']*/", " ", $text );
wfProfileOut( __METHOD__ . '-regexps' );
-
- 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->normalizeText( $text ) );
-
- wfProfileOut( __METHOD__ );
+ return $text;
}
}
-
-/**
- * Placeholder class
- *
- * @ingroup Search
- */
-class SearchUpdateMyISAM extends SearchUpdate {
- # Inherits everything
-}
diff --git a/includes/site/MediaWikiSite.php b/includes/site/MediaWikiSite.php
index 05092723..f3b8a0c7 100644
--- a/includes/site/MediaWikiSite.php
+++ b/includes/site/MediaWikiSite.php
@@ -123,17 +123,17 @@ class MediaWikiSite extends Site {
'converttitles' => true,
'format' => 'json',
'titles' => $pageName,
- //@todo: options for maxlag and maxage
+ // @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 );
+ $url = wfAppendQuery( $this->getFileUrl( 'api.php' ), $args );
// Go on call the external site
- //@todo: we need a good way to specify a timeout here.
+ // @todo we need a good way to specify a timeout here.
$ret = Http::get( $url );
}
@@ -185,7 +185,7 @@ class MediaWikiSite extends Site {
// the single page in the "pages" substructure.
if ( isset( $externalData['query']['pages'] ) ) {
$pages = array_values( $externalData['query']['pages'] );
- if ( count( $pages) === 1 ) {
+ if ( count( $pages ) === 1 ) {
return $pages[0];
}
}
diff --git a/includes/site/SiteSQLStore.php b/includes/site/SiteSQLStore.php
index 41238055..11141e07 100644
--- a/includes/site/SiteSQLStore.php
+++ b/includes/site/SiteSQLStore.php
@@ -189,6 +189,39 @@ class SiteSQLStore implements SiteStore {
}
/**
+ * Get a new ORMRow from a Site object
+ *
+ * @since 1.22
+ *
+ * @param Site
+ *
+ * @return ORMRow
+ */
+ protected function getRowFromSite( Site $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();
+ }
+
+ return new ORMRow( $this->sitesTable, $fields );
+ }
+
+ /**
* Fetches the site from the database and loads them into the sites field.
*
* @since 1.21
@@ -291,28 +324,11 @@ class SiteSQLStore implements SiteStore {
$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 );
+ $siteRow = $this->getRowFromSite( $site );
$success = $siteRow->save( __METHOD__ ) && $success;
foreach ( $site->getLocalIds() as $idType => $ids ) {
diff --git a/includes/specials/SpecialActiveusers.php b/includes/specials/SpecialActiveusers.php
index c9c82ada..705dab55 100644
--- a/includes/specials/SpecialActiveusers.php
+++ b/includes/specials/SpecialActiveusers.php
@@ -62,7 +62,7 @@ class ActiveUsersPager extends UsersPager {
$this->requestedUser = '';
if ( $un != '' ) {
$username = Title::makeTitleSafe( NS_USER, $un );
- if( !is_null( $username ) ) {
+ if ( !is_null( $username ) ) {
$this->requestedUser = $username->getText();
}
}
@@ -91,42 +91,62 @@ class ActiveUsersPager extends UsersPager {
}
function getQueryInfo() {
- $dbr = wfGetDB( DB_SLAVE );
+ $dbr = $this->getDatabase();
+
$conds = array( 'rc_user > 0' ); // Users - no anons
- 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 ) );
+ $dbr->timestamp( wfTimestamp( TS_UNIX ) - $this->RCMaxAge * 24 * 3600 ) );
- if( $this->requestedUser != '' ) {
+ if ( $this->requestedUser != '' ) {
$conds[] = 'rc_user_text >= ' . $dbr->addQuotes( $this->requestedUser );
}
+ if ( !$this->getUser()->isAllowed( 'hideuser' ) ) {
+ $conds[] = 'NOT EXISTS (' . $dbr->selectSQLText(
+ 'ipblocks', '1', array( 'rc_user=ipb_user', 'ipb_deleted' => 1 )
+ ) . ')';
+ }
+
return array(
- 'tables' => array( 'recentchanges', 'ipblocks' ),
+ 'tables' => array( 'recentchanges' ),
'fields' => array(
'user_name' => 'rc_user_text', // for Pager inheritance
'rc_user_text', // for Pager
- 'user_id' => 'rc_user',
- 'recentedits' => 'COUNT(*)',
- 'ipb_deleted' => 'MAX(ipb_deleted)'
+ 'user_id' => 'MAX(rc_user)', // Postgres
+ 'recentedits' => 'COUNT(*)'
),
'options' => array(
- 'GROUP BY' => array( 'rc_user_text', 'user_id' ),
+ 'GROUP BY' => array( 'rc_user_text' ),
'USE INDEX' => array( 'recentchanges' => 'rc_user_text' )
),
- 'join_conds' => array( // check for suppression blocks
- 'ipblocks' => array( 'LEFT JOIN', array(
- 'rc_user=ipb_user',
- 'ipb_auto' => 0 # avoid duplicate blocks
- )),
- ),
'conds' => $conds
);
}
+ function doBatchLookups() {
+ $uids = array();
+ foreach ( $this->mResult as $row ) {
+ $uids[] = $row->user_id;
+ }
+ // Fetch the block status of the user for showing "(blocked)" text and for
+ // striking out names of suppressed users when privileged user views the list.
+ // Although the first query already hits the block table for un-privileged, this
+ // is done in two queries to avoid huge quicksorts and to make COUNT(*) correct.
+ $dbr = $this->getDatabase();
+ $res = $dbr->select( 'ipblocks',
+ array( 'ipb_user', 'MAX(ipb_deleted) AS block_status' ),
+ array( 'ipb_user' => $uids ),
+ __METHOD__,
+ array( 'GROUP BY' => array( 'ipb_user' ) )
+ );
+ $this->blockStatusByUid = array();
+ foreach ( $res as $row ) {
+ $this->blockStatusByUid[$row->ipb_user] = $row->block_status; // 0 or 1
+ }
+ $this->mResult->seek( 0 );
+ }
+
function formatRow( $row ) {
$userName = $row->user_name;
@@ -139,7 +159,7 @@ class ActiveUsersPager extends UsersPager {
$user = User::newFromId( $row->user_id );
// User right filter
- foreach( $this->hideRights as $right ) {
+ foreach ( $this->hideRights as $right ) {
// Calling User::getRights() within the loop so that
// if the hideRights() filter is empty, we don't have to
// trigger the lazy-init of the big userrights array in the
@@ -153,7 +173,7 @@ class ActiveUsersPager extends UsersPager {
// Note: This is a different loop than for user rights,
// because we're reusing it to build the group links
// at the same time
- foreach( $user->getGroups() as $group ) {
+ foreach ( $user->getGroups() as $group ) {
if ( in_array( $group, $this->hideGroups ) ) {
return '';
}
@@ -163,12 +183,14 @@ class ActiveUsersPager extends UsersPager {
$groups = $lang->commaList( $list );
$item = $lang->specialList( $ulinks, $groups );
- if( $row->ipb_deleted ) {
+
+ $isBlocked = isset( $this->blockStatusByUid[$row->user_id] );
+ if ( $isBlocked && $this->blockStatusByUid[$row->user_id] == 1 ) {
$item = "<span class=\"deleted\">$item</span>";
}
$count = $this->msg( 'activeusers-count' )->numParams( $row->recentedits )
->params( $userName )->numParams( $this->RCMaxAge )->escaped();
- $blocked = !is_null( $row->ipb_deleted ) ? ' ' . $this->msg( 'listusers-blocked', $userName )->escaped() : '';
+ $blocked = $isBlocked ? ' ' . $this->msg( 'listusers-blocked', $userName )->escaped() : '';
return Html::rawElement( 'li', array(), "{$item} [{$count}]{$blocked}" );
}
@@ -184,15 +206,15 @@ class ActiveUsersPager extends UsersPager {
$out .= Html::hidden( 'title', $self->getPrefixedDBkey() ) . $limit . "\n";
$out .= Xml::inputLabel( $this->msg( 'activeusers-from' )->text(),
- 'username', 'offset', 20, $this->requestedUser ) . '<br />';# Username field
+ 'username', 'offset', 20, $this->requestedUser, array( 'tabindex' => 1 ) ) . '<br />';# Username field
$out .= Xml::checkLabel( $this->msg( 'activeusers-hidebots' )->text(),
- 'hidebots', 'hidebots', $this->opts->getValue( 'hidebots' ) );
+ 'hidebots', 'hidebots', $this->opts->getValue( 'hidebots' ), array( 'tabindex' => 2 ) );
$out .= Xml::checkLabel( $this->msg( 'activeusers-hidesysops' )->text(),
- 'hidesysops', 'hidesysops', $this->opts->getValue( 'hidesysops' ) ) . '<br />';
+ 'hidesysops', 'hidesysops', $this->opts->getValue( 'hidesysops' ), array( 'tabindex' => 3 ) ) . '<br />';
- $out .= Xml::submitButton( $this->msg( 'allpagessubmit' )->text() ) . "\n";# Submit button and form bottom
+ $out .= Xml::submitButton( $this->msg( 'allpagessubmit' )->text(), array( 'tabindex' => 4 ) ) . "\n";# Submit button and form bottom
$out .= Xml::closeElement( 'fieldset' );
$out .= Xml::closeElement( 'form' );
diff --git a/includes/specials/SpecialAllmessages.php b/includes/specials/SpecialAllmessages.php
index a60c8efe..35d6a0c0 100644
--- a/includes/specials/SpecialAllmessages.php
+++ b/includes/specials/SpecialAllmessages.php
@@ -28,7 +28,6 @@
* @ingroup SpecialPage
*/
class SpecialAllmessages extends SpecialPage {
-
/**
* @var AllmessagesTablePager
*/
@@ -53,8 +52,9 @@ class SpecialAllmessages extends SpecialPage {
$this->setHeaders();
global $wgUseDatabaseMessages;
- if( !$wgUseDatabaseMessages ) {
+ if ( !$wgUseDatabaseMessages ) {
$out->addWikiMsg( 'allmessagesnotsupportedDB' );
+
return;
} else {
$this->outputHeader( 'allmessagestext' );
@@ -74,7 +74,6 @@ class SpecialAllmessages extends SpecialPage {
$this->table->getNavigationBar() .
$this->table->getBody() .
$this->table->getNavigationBar() );
-
}
protected function getGroupName() {
@@ -87,7 +86,6 @@ class SpecialAllmessages extends SpecialPage {
* getting data from a table when in fact not all of it comes from the database.
*/
class AllmessagesTablePager extends TablePager {
-
protected $filter, $prefix, $langcode, $displayPrefix;
public $mLimitsShown;
@@ -121,15 +119,15 @@ class AllmessagesTablePager extends TablePager {
$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');
+ $this->custom = ( $this->filter == 'unmodified' );
}
$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 {
@@ -139,7 +137,7 @@ class AllmessagesTablePager extends TablePager {
// The suffix that may be needed for message names if we're in a
// different language (eg [[MediaWiki:Foo/fr]]: $suffix = '/fr'
- if( $this->foreign ) {
+ if ( $this->foreign ) {
$this->suffix = '/' . $this->langcode;
} else {
$this->suffix = '';
@@ -159,36 +157,36 @@ class AllmessagesTablePager extends TablePager {
Xml::openElement( 'table', array( 'class' => 'mw-allmessages-table' ) ) . "\n" .
'<tr>
<td class="mw-label">' .
- Xml::label( $this->msg( 'allmessages-prefix' )->text(), 'mw-allmessages-form-prefix' ) .
- "</td>\n
- <td class=\"mw-input\">" .
- Xml::input( 'prefix', 20, str_replace( '_', ' ', $this->displayPrefix ), array( 'id' => 'mw-allmessages-form-prefix' ) ) .
- "</td>\n
+ Xml::label( $this->msg( 'allmessages-prefix' )->text(), 'mw-allmessages-form-prefix' ) .
+ "</td>\n
+ <td class=\"mw-input\">" .
+ Xml::input( 'prefix', 20, str_replace( '_', ' ', $this->displayPrefix ), array( 'id' => 'mw-allmessages-form-prefix' ) ) .
+ "</td>\n
</tr>
<tr>\n
- <td class='mw-label'>" .
- $this->msg( 'allmessages-filter' )->escaped() .
- "</td>\n
+ <td class='mw-label'>" .
+ $this->msg( 'allmessages-filter' )->escaped() .
+ "</td>\n
<td class='mw-input'>" .
- Xml::radioLabel( $this->msg( 'allmessages-filter-unmodified' )->text(),
- 'filter',
- 'unmodified',
- 'mw-allmessages-form-filter-unmodified',
- ( $this->filter == 'unmodified' )
- ) .
- Xml::radioLabel( $this->msg( 'allmessages-filter-all' )->text(),
- 'filter',
- 'all',
- 'mw-allmessages-form-filter-all',
- ( $this->filter == 'all' )
- ) .
- Xml::radioLabel( $this->msg( 'allmessages-filter-modified' )->text(),
- 'filter',
- 'modified',
- 'mw-allmessages-form-filter-modified',
- ( $this->filter == 'modified' )
- ) .
- "</td>\n
+ Xml::radioLabel( $this->msg( 'allmessages-filter-unmodified' )->text(),
+ 'filter',
+ 'unmodified',
+ 'mw-allmessages-form-filter-unmodified',
+ ( $this->filter == 'unmodified' )
+ ) .
+ Xml::radioLabel( $this->msg( 'allmessages-filter-all' )->text(),
+ 'filter',
+ 'all',
+ 'mw-allmessages-form-filter-all',
+ ( $this->filter == 'all' )
+ ) .
+ Xml::radioLabel( $this->msg( 'allmessages-filter-modified' )->text(),
+ 'filter',
+ 'modified',
+ 'mw-allmessages-form-filter-modified',
+ ( $this->filter == 'modified' )
+ ) .
+ "</td>\n
</tr>
<tr>\n
<td class=\"mw-label\">" . $langSelect[0] . "</td>\n
@@ -197,29 +195,30 @@ class AllmessagesTablePager extends TablePager {
'<tr>
<td class="mw-label">' .
- Xml::label( $this->msg( 'table_pager_limit_label' )->text(), 'mw-table_pager_limit_label' ) .
- '</td>
- <td class="mw-input">' .
- $this->getLimitSelect() .
- '</td>
+ Xml::label( $this->msg( 'table_pager_limit_label' )->text(), 'mw-table_pager_limit_label' ) .
+ '</td>
+ <td class="mw-input">' .
+ $this->getLimitSelect() .
+ '</td>
<tr>
<td></td>
<td>' .
- Xml::submitButton( $this->msg( 'allmessages-filter-submit' )->text() ) .
- "</td>\n
+ Xml::submitButton( $this->msg( 'allmessages-filter-submit' )->text() ) .
+ "</td>\n
</tr>" .
Xml::closeElement( 'table' ) .
$this->getHiddenFields( array( 'title', 'prefix', 'filter', 'lang', 'limit' ) ) .
Xml::closeElement( 'fieldset' ) .
Xml::closeElement( 'form' );
+
return $out;
}
function getAllMessages( $descending ) {
wfProfileIn( __METHOD__ );
$messageNames = Language::getLocalisationCache()->getSubitemList( 'en', 'messages' );
- if( $descending ) {
+ if ( $descending ) {
rsort( $messageNames );
} else {
asort( $messageNames );
@@ -229,6 +228,7 @@ class AllmessagesTablePager extends TablePager {
$messageNames = array_map( array( $this->lang, 'ucfirst' ), $messageNames );
wfProfileOut( __METHOD__ );
+
return $messageNames;
}
@@ -260,18 +260,19 @@ class AllmessagesTablePager extends TablePager {
foreach ( $res as $s ) {
$exists = false;
- if( $foreign ) {
+ if ( $foreign ) {
$title = explode( '/', $s->page_title );
- if( count( $title ) === 2 && $langcode == $title[1]
- && isset( $xNames[$title[0]] ) ) {
+ if ( count( $title ) === 2 && $langcode == $title[1]
+ && isset( $xNames[$title[0]] )
+ ) {
$exists = $title[0];
}
- } elseif( isset( $xNames[$s->page_title] ) ) {
+ } elseif ( isset( $xNames[$s->page_title] ) ) {
$exists = $s->page_title;
}
- if( $exists && $s->page_namespace == NS_MEDIAWIKI ) {
+ if ( $exists && $s->page_namespace == NS_MEDIAWIKI ) {
$pageFlags[$exists] = true;
- } elseif( $exists && $s->page_namespace == NS_MEDIAWIKI_TALK ) {
+ } elseif ( $exists && $s->page_namespace == NS_MEDIAWIKI_TALK ) {
$talkFlags[$exists] = true;
}
}
@@ -284,6 +285,9 @@ class AllmessagesTablePager extends TablePager {
/**
* This function normally does a database query to get the results; we need
* to make a pretend result using a FakeResultWrapper.
+ * @param string $offset
+ * @param int $limit
+ * @param bool $descending
* @return FakeResultWrapper
*/
function reallyDoQuery( $offset, $limit, $descending ) {
@@ -293,27 +297,29 @@ class AllmessagesTablePager extends TablePager {
$statuses = self::getCustomisedStatuses( $messageNames, $this->langcode, $this->foreign );
$count = 0;
- foreach( $messageNames as $key ) {
+ foreach ( $messageNames as $key ) {
$customised = isset( $statuses['pages'][$key] );
- if( $customised !== $this->custom &&
+ if ( $customised !== $this->custom &&
( $descending && ( $key < $offset || !$offset ) || !$descending && $key > $offset ) &&
( ( $this->prefix && preg_match( $this->prefix, $key ) ) || $this->prefix === false )
) {
$actual = wfMessage( $key )->inLanguage( $this->langcode )->plain();
$default = wfMessage( $key )->inLanguage( $this->langcode )->useDatabase( false )->plain();
$result->result[] = array(
- 'am_title' => $key,
- 'am_actual' => $actual,
- 'am_default' => $default,
+ 'am_title' => $key,
+ 'am_actual' => $actual,
+ 'am_default' => $default,
'am_customised' => $customised,
'am_talk_exists' => isset( $statuses['talks'][$key] )
);
$count++;
}
- if( $count == $limit ) {
+
+ if ( $count == $limit ) {
break;
}
}
+
return $result;
}
@@ -321,26 +327,26 @@ class AllmessagesTablePager extends TablePager {
return Xml::openElement( 'table', array( 'class' => 'mw-datatable TablePager', 'id' => 'mw-allmessagestable' ) ) . "\n" .
"<thead><tr>
<th rowspan=\"2\">" .
- $this->msg( 'allmessagesname' )->escaped() . "
+ $this->msg( 'allmessagesname' )->escaped() . "
</th>
<th>" .
- $this->msg( 'allmessagesdefault' )->escaped() .
- "</th>
+ $this->msg( 'allmessagesdefault' )->escaped() .
+ "</th>
</tr>\n
<tr>
<th>" .
- $this->msg( 'allmessagescurrent' )->escaped() .
- "</th>
+ $this->msg( 'allmessagescurrent' )->escaped() .
+ "</th>
</tr></thead><tbody>\n";
}
function formatValue( $field, $value ) {
- switch( $field ) {
+ switch ( $field ) {
case 'am_title' :
$title = Title::makeTitle( NS_MEDIAWIKI, $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(
@@ -362,6 +368,7 @@ class AllmessagesTablePager extends TablePager {
array( 'broken' )
);
}
+
return $title . ' ' . $this->msg( 'parentheses' )->rawParams( $talk )->escaped();
case 'am_default' :
@@ -376,7 +383,7 @@ class AllmessagesTablePager extends TablePager {
$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 == '' ) {
@@ -385,24 +392,26 @@ class AllmessagesTablePager extends TablePager {
$s .= Xml::tags( 'td', $this->getCellAttrs( 'am_actual', $row->am_actual ), $formatted )
. "</tr>\n";
}
+
return $s;
}
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' ) {
+ if ( $this->mCurrentRow->am_customised && $field == 'am_title' ) {
return array( 'rowspan' => '2', 'class' => $field );
- } elseif( $field == 'am_title' ) {
+ } elseif ( $field == 'am_title' ) {
return array( 'class' => $field );
} else {
return array( 'lang' => $this->langcode, 'dir' => $this->lang->getDir(), 'class' => $field );
diff --git a/includes/specials/SpecialAllpages.php b/includes/specials/SpecialAllpages.php
index f9cb5cd8..a0820493 100644
--- a/includes/specials/SpecialAllpages.php
+++ b/includes/specials/SpecialAllpages.php
@@ -71,7 +71,6 @@ class SpecialAllpages extends IncludableSpecialPage {
* @param string $par becomes "FOO" when called like Special:Allpages/FOO (default NULL)
*/
function execute( $par ) {
- global $wgContLang;
$request = $this->getRequest();
$out = $this->getOutput();
@@ -85,18 +84,18 @@ class SpecialAllpages extends IncludableSpecialPage {
$namespace = $request->getInt( 'namespace' );
$hideredirects = $request->getBool( 'hideredirects', false );
- $namespaces = $wgContLang->getNamespaces();
+ $namespaces = $this->getContext()->getLanguage()->getNamespaces();
$out->setPageTitle(
- ( $namespace > 0 && in_array( $namespace, array_keys( $namespaces) ) ) ?
- $this->msg( 'allinnamespace', str_replace( '_', ' ', $namespaces[$namespace] ) ) :
- $this->msg( 'allarticles' )
+ ( $namespace > 0 && in_array( $namespace, array_keys( $namespaces ) ) ) ?
+ $this->msg( 'allinnamespace', str_replace( '_', ' ', $namespaces[$namespace] ) ) :
+ $this->msg( 'allarticles' )
);
$out->addModuleStyles( 'mediawiki.special' );
- if( $par !== null ) {
+ if ( $par !== null ) {
$this->showChunk( $namespace, $par, $to, $hideredirects );
- } elseif( $from !== null && $to === null ) {
+ } elseif ( $from !== null && $to === null ) {
$this->showChunk( $namespace, $from, $to, $hideredirects );
} else {
$this->showToplevel( $namespace, $from, $to, $hideredirects );
@@ -160,6 +159,7 @@ class SpecialAllpages extends IncludableSpecialPage {
$out .= Xml::closeElement( 'fieldset' );
$out .= Xml::closeElement( 'form' );
$out .= Xml::closeElement( 'div' );
+
return $out;
}
@@ -188,10 +188,13 @@ class SpecialAllpages extends IncludableSpecialPage {
$from = ( $from && $from->isLocal() ) ? $from->getDBkey() : null;
$to = ( $to && $to->isLocal() ) ? $to->getDBkey() : null;
- if( isset( $from ) )
+ if ( isset( $from ) ) {
$where[] = 'page_title >= ' . $dbr->addQuotes( $from );
- if( isset( $to ) )
+ }
+
+ if ( isset( $to ) ) {
$where[] = 'page_title <= ' . $dbr->addQuotes( $to );
+ }
global $wgMemc;
$key = wfMemcKey( 'allpages', 'ns', $namespace, sha1( $from ), sha1( $to ) );
@@ -201,7 +204,7 @@ class SpecialAllpages extends IncludableSpecialPage {
$maxPerSubpage = intval( $count / $this->maxLineCount );
$maxPerSubpage = max( $maxPerSubpage, $this->maxPerPage );
- if( !is_array( $lines ) ) {
+ if ( !is_array( $lines ) ) {
$options = array( 'LIMIT' => 1 );
$options['ORDER BY'] = 'page_title ASC';
$firstTitle = $dbr->selectField( 'page', 'page_title', $where, __METHOD__, $options );
@@ -210,7 +213,7 @@ class SpecialAllpages extends IncludableSpecialPage {
$lines = array( $firstTitle );
# If we are going to show n rows, we need n+1 queries to find the relevant titles.
$done = false;
- while( !$done ) {
+ while ( !$done ) {
// Fetch the last title of this chunk and the first of the next
$chunk = ( $lastTitle === false )
? array()
@@ -223,7 +226,7 @@ class SpecialAllpages extends IncludableSpecialPage {
);
$s = $dbr->fetchObject( $res );
- if( $s ) {
+ if ( $s ) {
array_push( $lines, $s->page_title );
} else {
// Final chunk, but ended prematurely. Go back and find the end.
@@ -233,8 +236,9 @@ class SpecialAllpages extends IncludableSpecialPage {
array_push( $lines, $endTitle );
$done = true;
}
+
$s = $res->fetchObject();
- if( $s ) {
+ if ( $s ) {
array_push( $lines, $s->page_title );
$lastTitle = $s->page_title;
} else {
@@ -249,18 +253,19 @@ 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 ( count( $lines ) <= 2 ) {
+ if ( !empty( $lines ) ) {
$this->showChunk( $namespace, $from, $to, $hideredirects );
} else {
$output->addHTML( $this->namespaceForm( $namespace, $from, $to, $hideredirects ) );
}
+
return;
}
# At this point, $lines should contain an even number of elements.
$out .= Xml::openElement( 'table', array( 'class' => 'allpageslist' ) );
- while( count ( $lines ) > 0 ) {
+ while ( count( $lines ) > 0 ) {
$inpoint = array_shift( $lines );
$outpoint = array_shift( $lines );
$out .= $this->showline( $inpoint, $outpoint, $namespace, $hideredirects );
@@ -269,19 +274,19 @@ class SpecialAllpages extends IncludableSpecialPage {
$nsForm = $this->namespaceForm( $namespace, $from, $to, $hideredirects );
# Is there more?
- if( $this->including() ) {
+ if ( $this->including() ) {
$out2 = '';
} else {
- if( isset( $from ) || isset( $to ) ) {
- $out2 = Xml::openElement( 'table', array( 'class' => 'mw-allpages-table-form' ) ).
- '<tr>
+ if ( isset( $from ) || isset( $to ) ) {
+ $out2 = Xml::openElement( 'table', array( 'class' => 'mw-allpages-table-form' ) ) .
+ '<tr>
<td>' .
- $nsForm .
- '</td>
+ $nsForm .
+ '</td>
<td class="mw-allpages-nav">' .
- Linker::link( $this->getTitle(), $this->msg( 'allpages' )->escaped(),
- array(), array(), 'known' ) .
- "</td>
+ Linker::link( $this->getTitle(), $this->msg( 'allpages' )->escaped(),
+ array(), array(), 'known' ) .
+ "</td>
</tr>" .
Xml::closeElement( 'table' );
} else {
@@ -297,47 +302,56 @@ class SpecialAllpages extends IncludableSpecialPage {
* @param string $inpoint lower limit of pagenames
* @param string $outpoint upper limit of pagenames
* @param $namespace Integer (Default NS_MAIN)
- * @param bool $hideredirects dont show redirects (default FALSE)
+ * @param bool $hideRedirects don't show redirects. Default: false
* @return string
*/
- function showline( $inpoint, $outpoint, $namespace = NS_MAIN, $hideredirects ) {
+ function showline( $inpoint, $outpoint, $namespace = NS_MAIN, $hideRedirects = false ) {
+ // Use content language since page titles are considered to use content language
global $wgContLang;
- $inpointf = htmlspecialchars( str_replace( '_', ' ', $inpoint ) );
- $outpointf = htmlspecialchars( str_replace( '_', ' ', $outpoint ) );
+
+ $inpointf = str_replace( '_', ' ', $inpoint );
+ $outpointf = str_replace( '_', ' ', $outpoint );
+
// Don't let the length runaway
$inpointf = $wgContLang->truncate( $inpointf, $this->maxPageLength );
$outpointf = $wgContLang->truncate( $outpointf, $this->maxPageLength );
- $queryparams = $namespace ? "namespace=$namespace&" : '';
+ $queryParams = array(
+ 'from' => $inpoint,
+ 'to' => $outpoint,
+ );
- $queryhideredirects = array();
- if ( $hideredirects ) {
- $queryhideredirects['hideredirects'] = 1;
+ if ( $namespace ) {
+ $queryParams['namespace'] = $namespace;
+ }
+ if ( $hideRedirects ) {
+ $queryParams['hideredirects'] = 1;
}
- $special = $this->getTitle();
- $link = htmlspecialchars( $special->getLocalUrl( $queryparams . 'from=' . urlencode( $inpoint ) . '&to=' . urlencode( $outpoint ), $queryhideredirects ) );
+ $url = $this->getTitle()->getLocalURL( $queryParams );
+ $inlink = Html::element( 'a', array( 'href' => $url ), $inpointf );
+ $outlink = Html::element( 'a', array( 'href' => $url ), $outpointf );
$out = $this->msg( 'alphaindexline' )->rawParams(
- "<a href=\"$link\">$inpointf</a></td><td>",
- "</td><td><a href=\"$link\">$outpointf</a>"
+ "$inlink</td><td>",
+ "</td><td>$outlink"
)->escaped();
+
return '<tr><td class="mw-allpages-alphaindexline">' . $out . '</td></tr>';
}
/**
- * @param $namespace Integer (Default NS_MAIN)
+ * @param int $namespace Namespace (Default NS_MAIN)
* @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 );
$toList = $this->getNamespaceKeyAndText( $namespace, $to );
- $namespaces = $wgContLang->getNamespaces();
+ $namespaces = $this->getContext()->getLanguage()->getNamespaces();
$n = 0;
if ( !$fromList || !$toList ) {
@@ -360,7 +374,7 @@ class SpecialAllpages extends IncludableSpecialPage {
$conds['page_is_redirect'] = 0;
}
- if( $toKey !== "" ) {
+ if ( $toKey !== "" ) {
$conds[] = 'page_title <= ' . $dbr->addQuotes( $toKey );
}
@@ -369,33 +383,36 @@ class SpecialAllpages extends IncludableSpecialPage {
$conds,
__METHOD__,
array(
- 'ORDER BY' => 'page_title',
- 'LIMIT' => $this->maxPerPage + 1,
+ 'ORDER BY' => 'page_title',
+ 'LIMIT' => $this->maxPerPage + 1,
'USE INDEX' => 'name_title',
)
);
- if( $res->numRows() > 0 ) {
+ if ( $res->numRows() > 0 ) {
$out = Xml::openElement( 'table', array( 'class' => 'mw-allpages-table-chunk' ) );
- while( ( $n < $this->maxPerPage ) && ( $s = $res->fetchObject() ) ) {
+ while ( ( $n < $this->maxPerPage ) && ( $s = $res->fetchObject() ) ) {
$t = Title::newFromRow( $s );
- if( $t ) {
+ if ( $t ) {
$link = ( $s->page_is_redirect ? '<div class="allpagesredirect">' : '' ) .
Linker::link( $t ) .
- ($s->page_is_redirect ? '</div>' : '' );
+ ( $s->page_is_redirect ? '</div>' : '' );
} else {
$link = '[[' . htmlspecialchars( $s->page_title ) . ']]';
}
- if( $n % 3 == 0 ) {
+
+ if ( $n % 3 == 0 ) {
$out .= '<tr>';
}
+
$out .= "<td style=\"width:33%\">$link</td>";
$n++;
- if( $n % 3 == 0 ) {
+ if ( $n % 3 == 0 ) {
$out .= "</tr>\n";
}
}
- if( ($n % 3) != 0 ) {
+
+ if ( ( $n % 3 ) != 0 ) {
$out .= "</tr>\n";
}
$out .= Xml::closeElement( 'table' );
@@ -407,7 +424,7 @@ class SpecialAllpages extends IncludableSpecialPage {
if ( $this->including() ) {
$out2 = '';
} else {
- if( $from == '' ) {
+ if ( $from == '' ) {
// First chunk; no previous link.
$prevTitle = null;
} else {
@@ -424,20 +441,20 @@ class SpecialAllpages extends IncludableSpecialPage {
);
# Get first title of previous complete chunk
- if( $dbr->numrows( $res_prev ) >= $this->maxPerPage ) {
+ if ( $dbr->numrows( $res_prev ) >= $this->maxPerPage ) {
$pt = $dbr->fetchObject( $res_prev );
$prevTitle = Title::makeTitle( $namespace, $pt->page_title );
} else {
# The previous chunk is not complete, need to link to the very first title
# available in the database
$options = array( 'LIMIT' => 1 );
- if ( ! $dbr->implicitOrderby() ) {
+ if ( !$dbr->implicitOrderby() ) {
$options['ORDER BY'] = 'page_title';
}
$reallyFirstPage_title = $dbr->selectField( 'page', 'page_title',
array( 'page_namespace' => $namespace ), __METHOD__, $options );
# Show the previous link if it s not the current requested chunk
- if( $from != $reallyFirstPage_title ) {
+ if ( $from != $reallyFirstPage_title ) {
$prevTitle = Title::makeTitle( $namespace, $reallyFirstPage_title );
} else {
$prevTitle = null;
@@ -448,23 +465,25 @@ class SpecialAllpages extends IncludableSpecialPage {
$self = $this->getTitle();
$nsForm = $this->namespaceForm( $namespace, $from, $to, $hideredirects );
- $out2 = Xml::openElement( 'table', array( 'class' => 'mw-allpages-table-form' ) ).
- '<tr>
+ $out2 = Xml::openElement( 'table', array( 'class' => 'mw-allpages-table-form' ) ) .
+ '<tr>
<td>' .
- $nsForm .
- '</td>
+ $nsForm .
+ '</td>
<td class="mw-allpages-nav">' .
- Linker::link( $self, $this->msg( 'allpages' )->escaped() );
+ 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 )
+ if ( $namespace ) {
$query['namespace'] = $namespace;
+ }
- if( $hideredirects )
+ if ( $hideredirects ) {
$query['hideredirects'] = $hideredirects;
+ }
$prevLink = Linker::linkKnown(
$self,
@@ -475,16 +494,18 @@ class SpecialAllpages extends IncludableSpecialPage {
$out2 = $this->getLanguage()->pipeList( array( $out2, $prevLink ) );
}
- if( $n == $this->maxPerPage && $s = $res->fetchObject() ) {
+ if ( $n == $this->maxPerPage && $s = $res->fetchObject() ) {
# $s is the first link of the next chunk
$t = Title::makeTitle( $namespace, $s->page_title );
$query = array( 'from' => $t->getText() );
- if( $namespace )
+ if ( $namespace ) {
$query['namespace'] = $namespace;
+ }
- if( $hideredirects )
+ if ( $hideredirects ) {
$query['hideredirects'] = $hideredirects;
+ }
$nextLink = Linker::linkKnown(
$self,
@@ -500,17 +521,22 @@ class SpecialAllpages extends IncludableSpecialPage {
$output->addHTML( $out2 . $out );
$links = array();
- if ( isset( $prevLink ) ) $links[] = $prevLink;
- if ( isset( $nextLink ) ) $links[] = $nextLink;
+ if ( isset( $prevLink ) ) {
+ $links[] = $prevLink;
+ }
+
+ if ( isset( $nextLink ) ) {
+ $links[] = $nextLink;
+ }
if ( count( $links ) ) {
$output->addHTML(
Html::element( 'hr' ) .
- Html::rawElement( 'div', array( 'class' => 'mw-allpages-nav' ),
- $this->getLanguage()->pipeList( $links )
- ) );
+ Html::rawElement( 'div', array( 'class' => 'mw-allpages-nav' ),
+ $this->getLanguage()->pipeList( $links )
+ )
+ );
}
-
}
/**
@@ -519,8 +545,10 @@ class SpecialAllpages extends IncludableSpecialPage {
* @return array( int namespace, string dbkey, string pagename ) or NULL on error
*/
protected function getNamespaceKeyAndText( $ns, $text ) {
- if ( $text == '' )
- return array( $ns, '', '' ); # shortcut for common case
+ if ( $text == '' ) {
+ # shortcut for common case
+ return array( $ns, '', '' );
+ }
$t = Title::makeTitleSafe( $ns, $text );
if ( $t && $t->isLocal() ) {
diff --git a/includes/specials/SpecialAncientpages.php b/includes/specials/SpecialAncientpages.php
index b0f333c4..b0830327 100644
--- a/includes/specials/SpecialAncientpages.php
+++ b/includes/specials/SpecialAncientpages.php
@@ -43,12 +43,16 @@ class AncientPagesPage extends QueryPage {
function getQueryInfo() {
return array(
'tables' => array( 'page', 'revision' ),
- 'fields' => array( 'namespace' => 'page_namespace',
- 'title' => 'page_title',
- 'value' => 'rev_timestamp' ),
- 'conds' => array( 'page_namespace' => MWNamespace::getContentNamespaces(),
- 'page_is_redirect' => 0,
- 'page_latest=rev_id' )
+ 'fields' => array(
+ 'namespace' => 'page_namespace',
+ 'title' => 'page_title',
+ 'value' => 'rev_timestamp'
+ ),
+ 'conds' => array(
+ 'page_namespace' => MWNamespace::getContentNamespaces(),
+ 'page_is_redirect' => 0,
+ 'page_latest=rev_id'
+ )
);
}
@@ -60,6 +64,11 @@ class AncientPagesPage extends QueryPage {
return false;
}
+ /**
+ * @param Skin $skin
+ * @param object $result Result row
+ * @return string
+ */
function formatResult( $skin, $result ) {
global $wgContLang;
@@ -69,6 +78,7 @@ class AncientPagesPage extends QueryPage {
$title,
htmlspecialchars( $wgContLang->convert( $title->getPrefixedText() ) )
);
+
return $this->getLanguage()->specialList( $link, htmlspecialchars( $d ) );
}
diff --git a/includes/specials/SpecialBlankpage.php b/includes/specials/SpecialBlankpage.php
index bfa2f951..e61f12b9 100644
--- a/includes/specials/SpecialBlankpage.php
+++ b/includes/specials/SpecialBlankpage.php
@@ -31,6 +31,7 @@ class SpecialBlankpage extends UnlistedSpecialPage {
public function __construct() {
parent::__construct( 'Blankpage' );
}
+
public function execute( $par ) {
$this->setHeaders();
$this->getOutput()->addWikiMsg( 'intentionallyblankpage' );
diff --git a/includes/specials/SpecialBlock.php b/includes/specials/SpecialBlock.php
index 50fdbc26..3b73a374 100644
--- a/includes/specials/SpecialBlock.php
+++ b/includes/specials/SpecialBlock.php
@@ -110,10 +110,10 @@ class SpecialBlock extends FormSpecialPage {
$s = HTMLForm::formatErrors( $this->preErrors );
if ( $s ) {
$form->addHeaderText( Html::rawElement(
- 'div',
- array( 'class' => 'error' ),
- $s
- ) );
+ 'div',
+ array( 'class' => 'error' ),
+ $s
+ ) );
}
}
}
@@ -127,6 +127,8 @@ class SpecialBlock extends FormSpecialPage {
$user = $this->getUser();
+ $suggestedDurations = self::getSuggestedDurations();
+
$a = array(
'Target' => array(
'type' => 'text',
@@ -139,11 +141,11 @@ class SpecialBlock extends FormSpecialPage {
'validation-callback' => array( __CLASS__, 'validateTargetField' ),
),
'Expiry' => array(
- 'type' => !count( self::getSuggestedDurations() ) ? 'text' : 'selectorother',
+ 'type' => !count( $suggestedDurations ) ? 'text' : 'selectorother',
'label-message' => 'ipbexpiry',
'required' => true,
'tabindex' => '2',
- 'options' => self::getSuggestedDurations(),
+ 'options' => $suggestedDurations,
'other' => $this->msg( 'ipbother' )->text(),
'default' => $this->msg( 'ipb-default-expiry' )->inContentLanguage()->text(),
),
@@ -241,8 +243,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
- )
- {
+ ) {
$fields['HardBlock']['default'] = $block->isHardblock();
$fields['CreateAccount']['default'] = $block->prevents( 'createaccount' );
$fields['AutoBlock']['default'] = $block->isAutoblocking();
@@ -445,6 +446,7 @@ class SpecialBlock extends FormSpecialPage {
} elseif ( IP::isIPAddress( $target ) ) {
return Title::makeTitleSafe( NS_USER, $target );
}
+
return null;
}
@@ -460,8 +462,8 @@ class SpecialBlock extends FormSpecialPage {
$i = 0;
$target = null;
- while( true ) {
- switch( $i++ ) {
+ while ( true ) {
+ switch ( $i++ ) {
case 0:
# The HTMLForm will check wpTarget first and only if it doesn't get
# a value use the default, which will be generated from the options
@@ -511,6 +513,7 @@ class SpecialBlock extends FormSpecialPage {
$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;
@@ -528,6 +531,7 @@ class SpecialBlock extends FormSpecialPage {
public static function validateTarget( $value, User $user ) {
global $wgBlockCIDRLimit;
+ /** @var User $target */
list( $target, $type ) = self::getTargetAndType( $value );
$status = Status::newGood( $target );
@@ -606,6 +610,7 @@ class SpecialBlock extends FormSpecialPage {
# can come from it
$data['Confirm'] = !in_array( $data['Confirm'], array( '', '0', null, false ), true );
+ /** @var User $target */
list( $target, $type ) = self::getTargetAndType( $data['Target'] );
if ( $type == Block::TYPE_USER ) {
$user = $target;
@@ -620,8 +625,8 @@ class SpecialBlock extends FormSpecialPage {
# but $data['target'] gets overriden by (non-normalized) request variable
# from previous request.
if ( $target === $performer->getName() &&
- ( $data['PreviousTarget'] !== $target || !$data['Confirm'] ) )
- {
+ ( $data['PreviousTarget'] !== $target || !$data['Confirm'] )
+ ) {
return array( 'ipb-blockingself' );
}
} elseif ( $type == Block::TYPE_RANGE ) {
@@ -634,9 +639,9 @@ class SpecialBlock extends FormSpecialPage {
return array( 'badipaddress' );
}
- if ( ( strlen( $data['Expiry'] ) == 0) || ( strlen( $data['Expiry'] ) > 50 )
- || !self::parseExpiryInput( $data['Expiry'] ) )
- {
+ if ( ( strlen( $data['Expiry'] ) == 0 ) || ( strlen( $data['Expiry'] ) > 50 )
+ || !self::parseExpiryInput( $data['Expiry'] )
+ ) {
return array( 'ipb_expiry_invalid' );
}
@@ -703,9 +708,9 @@ class SpecialBlock extends FormSpecialPage {
$reblockNotAllowed = ( array_key_exists( 'Reblock', $data ) && !$data['Reblock'] );
# Show form unless the user is already aware of this...
- if( $blockNotConfirmed || $reblockNotAllowed ) {
+ if ( $blockNotConfirmed || $reblockNotAllowed ) {
return array( array( 'ipb_already_blocked', $block->getTarget() ) );
- # Otherwise, try to update the block...
+ # Otherwise, try to update the block...
} else {
# This returns direct blocks before autoblocks/rangeblocks, since we should
# be sure the user is blocked by now it should work for our purposes
@@ -748,7 +753,7 @@ class SpecialBlock extends FormSpecialPage {
# Can't watch a rangeblock
if ( $type != Block::TYPE_RANGE && $data['Watch'] ) {
- $performer->addWatch( Title::makeTitle( NS_USER, $target ) );
+ WatchAction::doWatch( Title::makeTitle( NS_USER, $target ), $performer, WatchedItem::IGNORE_USER_RIGHTS );
}
# Block constructor sanitizes certain block options on insert
@@ -767,7 +772,8 @@ class SpecialBlock extends FormSpecialPage {
$logaction,
Title::makeTitle( NS_USER, $target ),
$data['Reason'][0],
- $logParams
+ $logParams,
+ $performer
);
# Relate log ID to block IDs (bug 25763)
$blockIds = array_merge( array( $status['id'] ), $status['autoIds'] );
@@ -865,7 +871,7 @@ class SpecialBlock extends FormSpecialPage {
# User is trying to unblock themselves
if ( $performer->isAllowed( 'unblockself' ) ) {
return true;
- # User blocked themselves and is now trying to reverse it
+ # User blocked themselves and is now trying to reverse it
} elseif ( $performer->blockedBy() === $performer->getName() ) {
return true;
} else {
@@ -953,4 +959,5 @@ class SpecialBlock extends FormSpecialPage {
}
# BC @since 1.18
-class IPBlockForm extends SpecialBlock {}
+class IPBlockForm extends SpecialBlock {
+}
diff --git a/includes/specials/SpecialBlockList.php b/includes/specials/SpecialBlockList.php
index e10df4fe..f1992c0f 100644
--- a/includes/specials/SpecialBlockList.php
+++ b/includes/specials/SpecialBlockList.php
@@ -55,10 +55,11 @@ class SpecialBlockList extends SpecialPage {
$action = $request->getText( 'action' );
- if( $action == 'unblock' || $action == 'submit' && $request->wasPosted() ) {
+ if ( $action == 'unblock' || $action == 'submit' && $request->wasPosted() ) {
# B/C @since 1.18: Unblock interface is now at Special:Unblock
$title = SpecialPage::getTitleFor( 'Unblock', $this->target );
- $out->redirect( $title->getFullUrl() );
+ $out->redirect( $title->getFullURL() );
+
return;
}
@@ -95,7 +96,9 @@ class SpecialBlockList extends SpecialPage {
'default' => 50,
),
);
- $form = new HTMLForm( $fields, $this->getContext() );
+ $context = new DerivativeContext( $this->getContext() );
+ $context->setTitle( $this->getTitle() ); // Remove subpage
+ $form = new HTMLForm( $fields, $context );
$form->setMethod( 'get' );
$form->setWrapperLegendMsg( 'ipblocklist-legend' );
$form->setSubmitTextMsg( 'ipblocklist-submit' );
@@ -120,7 +123,7 @@ class SpecialBlockList extends SpecialPage {
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;
@@ -141,23 +144,23 @@ class SpecialBlockList extends SpecialPage {
break;
case Block::TYPE_USER:
- $conds['ipb_address'] = (string)$this->target;
+ $conds['ipb_address'] = $target->getName();
$conds['ipb_auto'] = 0;
break;
}
}
# Apply filters
- if( in_array( 'userblocks', $this->options ) ) {
+ if ( in_array( 'userblocks', $this->options ) ) {
$conds['ipb_user'] = 0;
}
- if( in_array( 'tempblocks', $this->options ) ) {
+ if ( in_array( 'tempblocks', $this->options ) ) {
$conds['ipb_expiry'] = 'infinity';
}
- if( in_array( 'addressblocks', $this->options ) ) {
+ if ( in_array( 'addressblocks', $this->options ) ) {
$conds[] = "ipb_user != 0 OR ipb_range_end > ipb_range_start";
}
- if( in_array( 'rangeblocks', $this->options ) ) {
+ if ( in_array( 'rangeblocks', $this->options ) ) {
$conds[] = "ipb_range_end = ipb_range_start";
}
@@ -169,7 +172,7 @@ class SpecialBlockList extends SpecialPage {
# Show additional header for the local block only when other blocks exists.
# Not necessary in a standard installation without such extensions enabled
- if( count( $otherBlockLink ) ) {
+ if ( count( $otherBlockLink ) ) {
$out->addHTML(
Html::element( 'h2', array(), $this->msg( 'ipblocklist-localblock' )->text() ) . "\n"
);
@@ -179,18 +182,16 @@ class SpecialBlockList extends SpecialPage {
if ( $pager->getNumRows() ) {
$out->addHTML(
$pager->getNavigationBar() .
- $pager->getBody().
- $pager->getNavigationBar()
+ $pager->getBody() .
+ $pager->getNavigationBar()
);
-
} elseif ( $this->target ) {
$out->addWikiMsg( 'ipblocklist-no-results' );
-
} else {
$out->addWikiMsg( 'ipblocklist-empty' );
}
- if( count( $otherBlockLink ) ) {
+ if ( count( $otherBlockLink ) ) {
$out->addHTML(
Html::rawElement(
'h2',
@@ -199,7 +200,7 @@ class SpecialBlockList extends SpecialPage {
) . "\n"
);
$list = '';
- foreach( $otherBlockLink as $link ) {
+ foreach ( $otherBlockLink as $link ) {
$list .= Html::rawElement( 'li', array(), $link ) . "\n";
}
$out->addHTML( Html::rawElement( 'ul', array( 'class' => 'mw-ipblocklist-otherblocks' ), $list ) . "\n" );
@@ -238,7 +239,7 @@ class BlockListPager extends TablePager {
'ipb_params' => 'blocklist-params',
'ipb_reason' => 'blocklist-reason',
);
- foreach( $headers as $key => $val ) {
+ foreach ( $headers as $key => $val ) {
$headers[$key] = $this->msg( $val )->text();
}
}
@@ -267,17 +268,17 @@ class BlockListPager extends TablePager {
$formatted = '';
- switch( $name ) {
+ switch ( $name ) {
case 'ipb_timestamp':
$formatted = $this->getLanguage()->userTimeAndDate( $value, $this->getUser() );
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 );
@@ -295,9 +296,9 @@ class BlockListPager extends TablePager {
break;
case 'ipb_expiry':
- $formatted = $this->getLanguage()->formatExpiry( $value, /* User preference timezone */ true );
- if( $this->getUser()->isAllowed( 'block' ) ) {
- if( $row->ipb_auto ) {
+ $formatted = $this->getLanguage()->formatExpiry( $value, /* User preference timezone */true );
+ if ( $this->getUser()->isAllowed( 'block' ) ) {
+ if ( $row->ipb_auto ) {
$links[] = Linker::linkKnown(
SpecialPage::getTitleFor( 'Unblock' ),
$msg['unblocklink'],
@@ -420,7 +421,7 @@ class BlockListPager extends TablePager {
/**
* Do a LinkBatch query to minimise database load when generating all these links
- * @param $result
+ * @param ResultWrapper $result
*/
function preprocessResults( $result ) {
wfProfileIn( __METHOD__ );
@@ -441,7 +442,7 @@ 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 );
@@ -482,5 +483,4 @@ class HTMLBlockedUsersItemSelect extends HTMLSelectField {
return true;
}
-
}
diff --git a/includes/specials/SpecialBlockme.php b/includes/specials/SpecialBlockme.php
deleted file mode 100644
index 85a3019e..00000000
--- a/includes/specials/SpecialBlockme.php
+++ /dev/null
@@ -1,66 +0,0 @@
-<?php
-/**
- * Implements Special:Blockme
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup SpecialPage
- */
-
-/**
- * A special page called by proxyCheck.php to block open proxies
- *
- * @ingroup SpecialPage
- */
-class SpecialBlockme extends UnlistedSpecialPage {
-
- function __construct() {
- parent::__construct( 'Blockme' );
- }
-
- function execute( $par ) {
- global $wgBlockOpenProxies, $wgProxyKey;
-
- $this->setHeaders();
- $this->outputHeader();
-
- $ip = $this->getRequest()->getIP();
- if( !$wgBlockOpenProxies || $this->getRequest()->getText( 'ip' ) != md5( $ip . $wgProxyKey ) ) {
- $this->getOutput()->addWikiMsg( 'proxyblocker-disabled' );
- return;
- }
-
- $user = User::newFromName( $this->msg( 'proxyblocker' )->inContentLanguage()->text() );
- # FIXME: newFromName could return false on a badly configured wiki.
- if ( !$user->isLoggedIn() ) {
- $user->addToDatabase();
- }
-
- $block = new Block();
- $block->setTarget( $ip );
- $block->setBlocker( $user );
- $block->mReason = $this->msg( 'proxyblockreason' )->inContentLanguage()->text();
-
- $block->insert();
-
- $this->getOutput()->addWikiMsg( 'proxyblocksuccess' );
- }
-
- protected function getGroupName() {
- return 'other';
- }
-}
diff --git a/includes/specials/SpecialBooksources.php b/includes/specials/SpecialBooksources.php
index bdbd77b8..5ad961c3 100644
--- a/includes/specials/SpecialBooksources.php
+++ b/includes/specials/SpecialBooksources.php
@@ -53,8 +53,8 @@ class SpecialBookSources extends SpecialPage {
$this->outputHeader();
$this->isbn = self::cleanIsbn( $isbn ? $isbn : $this->getRequest()->getText( 'isbn' ) );
$this->getOutput()->addHTML( $this->makeForm() );
- if( strlen( $this->isbn ) > 0 ) {
- if( !self::isValidISBN( $this->isbn ) ) {
+ if ( strlen( $this->isbn ) > 0 ) {
+ if ( !self::isValidISBN( $this->isbn ) ) {
$this->getOutput()->wrapWikiMsg( "<div class=\"error\">\n$1\n</div>", 'booksources-invalid-isbn' );
}
$this->showList();
@@ -69,32 +69,33 @@ class SpecialBookSources extends SpecialPage {
public static function isValidISBN( $isbn ) {
$isbn = self::cleanIsbn( $isbn );
$sum = 0;
- if( strlen( $isbn ) == 13 ) {
- for( $i = 0; $i < 12; $i++ ) {
- if( $i % 2 == 0 ) {
+ if ( strlen( $isbn ) == 13 ) {
+ for ( $i = 0; $i < 12; $i++ ) {
+ if ( $i % 2 == 0 ) {
$sum += $isbn[$i];
} else {
$sum += 3 * $isbn[$i];
}
}
- $check = (10 - ($sum % 10)) % 10;
+ $check = ( 10 - ( $sum % 10 ) ) % 10;
if ( $check == $isbn[12] ) {
return true;
}
- } elseif( strlen( $isbn ) == 10 ) {
- for( $i = 0; $i < 9; $i++ ) {
- $sum += $isbn[$i] * ($i + 1);
+ } elseif ( strlen( $isbn ) == 10 ) {
+ 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;
}
}
+
return false;
}
@@ -116,13 +117,14 @@ class SpecialBookSources extends SpecialPage {
private function makeForm() {
global $wgScript;
- $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 );
- $form .= '&#160;' . Xml::submitButton( $this->msg( 'booksources-go' )->text() ) . '</p>';
- $form .= Xml::closeElement( 'form' );
- $form .= '</fieldset>';
+ $form = Html::openElement( 'fieldset' ) . "\n";
+ $form .= Html::element( 'legend', array(), $this->msg( 'booksources-search-legend' )->text() ) . "\n";
+ $form .= Html::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ) . "\n";
+ $form .= Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) . "\n";
+ $form .= '<p>' . Xml::inputLabel( $this->msg( 'booksources-isbn' )->text(), 'isbn', 'isbn', 20, $this->isbn, array( 'autofocus' => true ) );
+ $form .= '&#160;' . Xml::submitButton( $this->msg( 'booksources-go' )->text() ) . "</p>\n";
+ $form .= Html::closeElement( 'form' ) . "\n";
+ $form .= Html::closeElement( 'fieldset' ) . "\n";
return $form;
}
@@ -143,7 +145,7 @@ class SpecialBookSources extends SpecialPage {
# Check for a local page such as Project:Book_sources and use that if available
$page = $this->msg( 'booksources' )->inContentLanguage()->text();
$title = Title::makeTitleSafe( NS_PROJECT, $page ); # Show list in content language
- if( is_object( $title ) && $title->exists() ) {
+ if ( is_object( $title ) && $title->exists() ) {
$rev = Revision::newFromTitle( $title, false, Revision::READ_NORMAL );
$content = $rev->getContent();
@@ -152,6 +154,7 @@ class SpecialBookSources extends SpecialPage {
$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() );
@@ -162,9 +165,11 @@ class SpecialBookSources extends SpecialPage {
$this->getOutput()->addWikiMsg( 'booksources-text' );
$this->getOutput()->addHTML( '<ul>' );
$items = $wgContLang->getBookstoreList();
- foreach( $items as $label => $url )
+ foreach ( $items as $label => $url ) {
$this->getOutput()->addHTML( $this->makeListItem( $label, $url ) );
+ }
$this->getOutput()->addHTML( '</ul>' );
+
return true;
}
@@ -177,7 +182,9 @@ class SpecialBookSources extends SpecialPage {
*/
private function makeListItem( $label, $url ) {
$url = str_replace( '$1', $this->isbn, $url );
- return '<li><a href="' . htmlspecialchars( $url ) . '" class="external">' . htmlspecialchars( $label ) . '</a></li>';
+
+ return Html::rawElement( 'li', array(),
+ Html::element( 'a', array( 'href' => $url, 'class' => 'external' ), $label ) );
}
protected function getGroupName() {
diff --git a/includes/specials/SpecialBrokenRedirects.php b/includes/specials/SpecialBrokenRedirects.php
index fac41236..b2ddc220 100644
--- a/includes/specials/SpecialBrokenRedirects.php
+++ b/includes/specials/SpecialBrokenRedirects.php
@@ -51,6 +51,7 @@ class BrokenRedirectsPage extends QueryPage {
function getQueryInfo() {
$dbr = wfGetDB( DB_SLAVE );
+
return array(
'tables' => array(
'redirect',
@@ -88,13 +89,13 @@ class BrokenRedirectsPage extends QueryPage {
* @return array
*/
function getOrderFields() {
- return array ( 'rd_namespace', 'rd_title', 'rd_from' );
+ return array( 'rd_namespace', 'rd_title', 'rd_from' );
}
/**
- * @param $skin Skin
- * @param $result
- * @return String
+ * @param Skin $skin
+ * @param object $result Result row
+ * @return string
*/
function formatResult( $skin, $result ) {
$fromObj = Title::makeTitle( $result->namespace, $result->title );
@@ -138,7 +139,7 @@ class BrokenRedirectsPage extends QueryPage {
$out = $from . $this->msg( 'word-separator' )->escaped();
- if( $this->getUser()->isAllowed( 'delete' ) ) {
+ if ( $this->getUser()->isAllowed( 'delete' ) ) {
$links[] = Linker::linkKnown(
$fromObj,
$this->msg( 'brokenredirects-delete' )->escaped(),
@@ -149,6 +150,7 @@ class BrokenRedirectsPage extends QueryPage {
$out .= $this->msg( 'parentheses' )->rawParams( $this->getLanguage()->pipeList( $links ) )->escaped();
$out .= " {$arr} {$to}";
+
return $out;
}
diff --git a/includes/specials/SpecialCachedPage.php b/includes/specials/SpecialCachedPage.php
index ddd11ad0..39305f01 100644
--- a/includes/specials/SpecialCachedPage.php
+++ b/includes/specials/SpecialCachedPage.php
@@ -194,5 +194,4 @@ abstract class SpecialCachedPage extends SpecialPage implements ICacheHelper {
$this->getOutput()->setSubtitle( $this->cacheHelper->getCachedNotice( $this->getContext() ) );
}
}
-
}
diff --git a/includes/specials/SpecialCategories.php b/includes/specials/SpecialCategories.php
index 9040c640..d01bfd7d 100644
--- a/includes/specials/SpecialCategories.php
+++ b/includes/specials/SpecialCategories.php
@@ -42,12 +42,12 @@ class SpecialCategories extends SpecialPage {
$this->getOutput()->addHTML(
Html::openElement( 'div', array( 'class' => 'mw-spcontent' ) ) .
- $this->msg( 'categoriespagetext', $cap->getNumRows() )->parseAsBlock() .
- $cap->getStartForm( $from ) .
- $cap->getNavigationBar() .
- '<ul>' . $cap->getBody() . '</ul>' .
- $cap->getNavigationBar() .
- Html::closeElement( 'div' )
+ $this->msg( 'categoriespagetext', $cap->getNumRows() )->parseAsBlock() .
+ $cap->getStartForm( $from ) .
+ $cap->getNavigationBar() .
+ '<ul>' . $cap->getBody() . '</ul>' .
+ $cap->getNavigationBar() .
+ Html::closeElement( 'div' )
);
}
@@ -66,7 +66,7 @@ class CategoryPager extends AlphabeticPager {
function __construct( IContextSource $context, $from ) {
parent::__construct( $context );
$from = str_replace( ' ', '_', $from );
- if( $from !== '' ) {
+ if ( $from !== '' ) {
$from = Title::capitalize( $from, NS_CATEGORY );
$this->setOffset( $from );
$this->setIncludeOffset( true );
@@ -90,8 +90,10 @@ class CategoryPager extends AlphabeticPager {
function getDefaultQuery() {
parent::getDefaultQuery();
unset( $this->mDefaultQuery['from'] );
+
return $this->mDefaultQuery;
}
+
# protected function getOrderTypeMessages() {
# return array( 'abc' => 'special-categories-sort-abc',
# 'count' => 'special-categories-sort-count' );
@@ -113,6 +115,7 @@ class CategoryPager extends AlphabeticPager {
}
$batch->execute();
$this->mResult->rewind();
+
return parent::getBody();
}
@@ -120,19 +123,26 @@ class CategoryPager extends AlphabeticPager {
$title = Title::makeTitle( NS_CATEGORY, $result->cat_title );
$titleText = Linker::link( $title, htmlspecialchars( $title->getText() ) );
$count = $this->msg( 'nmembers' )->numParams( $result->cat_pages )->escaped();
+
return Xml::tags( 'li', null, $this->getLanguage()->specialList( $titleText, $count ) ) . "\n";
}
public function getStartForm( $from ) {
global $wgScript;
- return
- Xml::tags( 'form', array( 'method' => 'get', 'action' => $wgScript ),
- Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) .
- Xml::fieldset( $this->msg( 'categories' )->text(),
- Xml::inputLabel( $this->msg( 'categoriesfrom' )->text(),
+ return Xml::tags(
+ 'form',
+ array( 'method' => 'get', 'action' => $wgScript ),
+ Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) .
+ Xml::fieldset(
+ $this->msg( 'categories' )->text(),
+ Xml::inputLabel(
+ $this->msg( 'categoriesfrom' )->text(),
'from', 'from', 20, $from ) .
- ' ' .
- Xml::submitButton( $this->msg( 'allpagessubmit' )->text() ) ) );
+ ' ' .
+ Xml::submitButton( $this->msg( 'allpagessubmit' )->text()
+ )
+ )
+ );
}
}
diff --git a/includes/specials/SpecialChangeEmail.php b/includes/specials/SpecialChangeEmail.php
index 59a02578..aab839fd 100644
--- a/includes/specials/SpecialChangeEmail.php
+++ b/includes/specials/SpecialChangeEmail.php
@@ -41,7 +41,7 @@ class SpecialChangeEmail extends UnlistedSpecialPage {
protected $mNewEmail;
public function __construct() {
- parent::__construct( 'ChangeEmail' );
+ parent::__construct( 'ChangeEmail', 'editmyprivateinfo' );
}
/**
@@ -49,6 +49,7 @@ class SpecialChangeEmail extends UnlistedSpecialPage {
*/
function isListed() {
global $wgAuth;
+
return $wgAuth->allowPropChange( 'emailaddress' );
}
@@ -67,6 +68,7 @@ class SpecialChangeEmail extends UnlistedSpecialPage {
if ( !$wgAuth->allowPropChange( 'emailaddress' ) ) {
$this->error( 'cannotchangeemail' );
+
return;
}
@@ -75,22 +77,31 @@ class SpecialChangeEmail extends UnlistedSpecialPage {
if ( !$request->wasPosted() && !$user->isLoggedIn() ) {
$this->error( 'changeemail-no-info' );
+
return;
}
if ( $request->wasPosted() && $request->getBool( 'wpCancel' ) ) {
$this->doReturnTo();
+
return;
}
$this->checkReadOnly();
+ $this->checkPermissions();
+
+ // This could also let someone check the current email address, so
+ // require both permissions.
+ if ( !$this->getUser()->isAllowed( 'viewmyprivateinfo' ) ) {
+ throw new PermissionsError( 'viewmyprivateinfo' );
+ }
$this->mPassword = $request->getVal( 'wpPassword' );
$this->mNewEmail = $request->getVal( 'wpNewEmail' );
if ( $request->wasPosted()
- && $user->matchEditToken( $request->getVal( 'token' ) ) )
- {
+ && $user->matchEditToken( $request->getVal( 'token' ) )
+ ) {
$info = $this->attemptChange( $user, $this->mPassword, $this->mNewEmail );
if ( $info === true ) {
$this->doReturnTo();
@@ -138,15 +149,15 @@ class SpecialChangeEmail extends UnlistedSpecialPage {
$this->getOutput()->addHTML(
Xml::fieldset( $this->msg( 'changeemail-header' )->text() ) .
- Xml::openElement( 'form',
- array(
- 'method' => 'post',
- 'action' => $this->getTitle()->getLocalUrl(),
- 'id' => 'mw-changeemail-form' ) ) . "\n" .
- Html::hidden( 'token', $user->getEditToken() ) . "\n" .
- Html::hidden( 'returnto', $this->getRequest()->getVal( 'returnto' ) ) . "\n" .
- $this->msg( 'changeemail-text' )->parseAsBlock() . "\n" .
- Xml::openElement( 'table', array( 'id' => 'mw-changeemail-table' ) ) . "\n"
+ Xml::openElement( 'form',
+ array(
+ 'method' => 'post',
+ 'action' => $this->getTitle()->getLocalURL(),
+ 'id' => 'mw-changeemail-form' ) ) . "\n" .
+ Html::hidden( 'token', $user->getEditToken() ) . "\n" .
+ Html::hidden( 'returnto', $this->getRequest()->getVal( 'returnto' ) ) . "\n" .
+ $this->msg( 'changeemail-text' )->parseAsBlock() . "\n" .
+ Xml::openElement( 'table', array( 'id' => 'mw-changeemail-table' ) ) . "\n"
);
$items = array(
array( 'wpName', 'username', 'text', $user->getName() ),
@@ -159,17 +170,17 @@ class SpecialChangeEmail extends UnlistedSpecialPage {
$this->getOutput()->addHTML(
$this->pretty( $items ) .
- "\n" .
- "<tr>\n" .
+ "\n" .
+ "<tr>\n" .
"<td></td>\n" .
'<td class="mw-input">' .
- Xml::submitButton( $this->msg( 'changeemail-submit' )->text() ) .
- Xml::submitButton( $this->msg( 'changeemail-cancel' )->text(), array( 'name' => 'wpCancel' ) ) .
+ Xml::submitButton( $this->msg( 'changeemail-submit' )->text() ) .
+ Xml::submitButton( $this->msg( 'changeemail-cancel' )->text(), array( 'name' => 'wpCancel' ) ) .
"</td>\n" .
- "</tr>\n" .
- Xml::closeElement( 'table' ) .
- Xml::closeElement( 'form' ) .
- Xml::closeElement( 'fieldset' ) . "\n"
+ "</tr>\n" .
+ Xml::closeElement( 'table' ) .
+ Xml::closeElement( 'form' ) .
+ Xml::closeElement( 'fieldset' ) . "\n"
);
}
@@ -181,7 +192,7 @@ class SpecialChangeEmail extends UnlistedSpecialPage {
$out = '';
foreach ( $fields as $list ) {
list( $name, $label, $type, $value ) = $list;
- if( $type == 'text' ) {
+ if ( $type == 'text' ) {
$field = htmlspecialchars( $value );
} else {
$attribs = array( 'id' => $name );
@@ -203,6 +214,7 @@ class SpecialChangeEmail extends UnlistedSpecialPage {
$out .= "</td>\n";
$out .= "</tr>";
}
+
return $out;
}
@@ -213,22 +225,26 @@ class SpecialChangeEmail extends UnlistedSpecialPage {
* @return bool|string true or string on success, false on failure
*/
protected function attemptChange( User $user, $pass, $newaddr ) {
- global $wgAuth;
+ global $wgAuth, $wgPasswordAttemptThrottle;
if ( $newaddr != '' && !Sanitizer::validateEmail( $newaddr ) ) {
$this->error( 'invalidemailaddress' );
+
return false;
}
$throttleCount = LoginForm::incLoginThrottle( $user->getName() );
if ( $throttleCount === true ) {
- $this->error( 'login-throttled' );
+ $lang = $this->getLanguage();
+ $this->error( array( 'login-throttled', $lang->formatDuration( $wgPasswordAttemptThrottle['seconds'] ) ) );
+
return false;
}
global $wgRequirePasswordforEmailChange;
if ( $wgRequirePasswordforEmailChange && !$user->checkTemporaryPassword( $pass ) && !$user->checkPassword( $pass ) ) {
$this->error( 'wrongpassword' );
+
return false;
}
@@ -241,8 +257,9 @@ class SpecialChangeEmail extends UnlistedSpecialPage {
if ( !$status->isGood() ) {
$this->getOutput()->addHTML(
'<p class="error">' .
- $this->getOutput()->parseInline( $status->getWikiText( 'mailerror' ) ) .
- '</p>' );
+ $this->getOutput()->parseInline( $status->getWikiText( 'mailerror' ) ) .
+ '</p>' );
+
return false;
}
diff --git a/includes/specials/SpecialChangePassword.php b/includes/specials/SpecialChangePassword.php
index e538caca..c54b5575 100644
--- a/includes/specials/SpecialChangePassword.php
+++ b/includes/specials/SpecialChangePassword.php
@@ -31,7 +31,7 @@ class SpecialChangePassword extends UnlistedSpecialPage {
protected $mUserName, $mOldpass, $mNewpass, $mRetype, $mDomain;
public function __construct() {
- parent::__construct( 'ChangePassword' );
+ parent::__construct( 'ChangePassword', 'editmyprivateinfo' );
}
/**
@@ -52,62 +52,66 @@ class SpecialChangePassword extends UnlistedSpecialPage {
$this->mDomain = $request->getVal( 'wpDomain' );
$user = $this->getUser();
- if( !$request->wasPosted() && !$user->isLoggedIn() ) {
+ if ( !$request->wasPosted() && !$user->isLoggedIn() ) {
$this->error( $this->msg( 'resetpass-no-info' )->text() );
+
return;
}
- if( $request->wasPosted() && $request->getBool( 'wpCancel' ) ) {
- $this->doReturnTo();
+ if ( $request->wasPosted() && $request->getBool( 'wpCancel' ) ) {
+ $titleObj = Title::newFromText( $request->getVal( 'returnto' ) );
+ if ( !$titleObj instanceof Title ) {
+ $titleObj = Title::newMainPage();
+ }
+ $query = $request->getVal( 'returntoquery' );
+ $this->getOutput()->redirect( $titleObj->getFullURL( $query ) );
+
return;
}
$this->checkReadOnly();
+ $this->checkPermissions();
- if( $request->wasPosted() && $user->matchEditToken( $request->getVal( 'token' ) ) ) {
+ if ( $request->wasPosted() && $user->matchEditToken( $request->getVal( 'token' ) ) ) {
try {
$this->mDomain = $wgAuth->getDomain();
- if( !$wgAuth->allowPasswordChange() ) {
+ if ( !$wgAuth->allowPasswordChange() ) {
$this->error( $this->msg( 'resetpass_forbidden' )->text() );
+
return;
}
$this->attemptReset( $this->mNewpass, $this->mRetype );
- if( $user->isLoggedIn() ) {
- $this->doReturnTo();
+ if ( $user->isLoggedIn() ) {
+ $this->getOutput()->wrapWikiMsg(
+ "<div class=\"successbox\">\n$1\n</div>",
+ 'changepassword-success'
+ );
+ $this->getOutput()->returnToMain();
} else {
LoginForm::setLoginToken();
$token = LoginForm::getLoginToken();
$data = array(
- 'action' => 'submitlogin',
- 'wpName' => $this->mUserName,
- 'wpDomain' => $this->mDomain,
+ 'action' => 'submitlogin',
+ 'wpName' => $this->mUserName,
+ 'wpDomain' => $this->mDomain,
'wpLoginToken' => $token,
- 'wpPassword' => $request->getVal( 'wpNewPassword' ),
+ 'wpPassword' => $request->getVal( 'wpNewPassword' ),
) + $request->getValues( 'wpRemember', 'returnto', 'returntoquery' );
- $login = new LoginForm( new FauxRequest( $data, true ) );
+ $login = new LoginForm( new DerivativeRequest( $request, $data, true ) );
$login->setContext( $this->getContext() );
$login->execute( null );
}
+
return;
- } catch( PasswordError $e ) {
+ } catch ( PasswordError $e ) {
$this->error( $e->getMessage() );
}
}
$this->showForm();
}
- function doReturnTo() {
- $request = $this->getRequest();
- $titleObj = Title::newFromText( $request->getVal( 'returnto' ) );
- if ( !$titleObj instanceof Title ) {
- $titleObj = Title::newMainPage();
- }
- $query = $request->getVal( 'returntoquery' );
- $this->getOutput()->redirect( $titleObj->getFullURL( $query ) );
- }
-
/**
* @param $msg string
*/
@@ -127,12 +131,12 @@ class SpecialChangePassword extends UnlistedSpecialPage {
$rememberMe = '<tr>' .
'<td></td>' .
'<td class="mw-input">' .
- Xml::checkLabel(
- $this->msg( 'remembermypassword' )->numParams( ceil( $wgCookieExpiration / ( 3600 * 24 ) ) )->text(),
- 'wpRemember', 'wpRemember',
- $this->getRequest()->getCheck( 'wpRemember' ) ) .
+ Xml::checkLabel(
+ $this->msg( 'remembermypassword' )->numParams( ceil( $wgCookieExpiration / ( 3600 * 24 ) ) )->text(),
+ 'wpRemember', 'wpRemember',
+ $this->getRequest()->getCheck( 'wpRemember' ) ) .
'</td>' .
- '</tr>';
+ '</tr>';
$submitMsg = 'resetpass_submit';
$oldpassMsg = 'resetpass-temp-password';
} else {
@@ -142,11 +146,11 @@ class SpecialChangePassword extends UnlistedSpecialPage {
$extraFields = array();
wfRunHooks( 'ChangePasswordForm', array( &$extraFields ) );
$prettyFields = array(
- array( 'wpName', 'username', 'text', $this->mUserName ),
- array( 'wpPassword', $oldpassMsg, 'password', $this->mOldpass ),
- array( 'wpNewPassword', 'newpassword', 'password', null ),
- array( 'wpRetype', 'retypenew', 'password', null ),
- );
+ array( 'wpName', 'username', 'text', $this->mUserName ),
+ array( 'wpPassword', $oldpassMsg, 'password', $this->mOldpass ),
+ array( 'wpNewPassword', 'newpassword', 'password', null ),
+ array( 'wpRetype', 'retypenew', 'password', null ),
+ );
$prettyFields = array_merge( $prettyFields, $extraFields );
$hiddenFields = array(
'token' => $user->getEditToken(),
@@ -154,31 +158,31 @@ class SpecialChangePassword extends UnlistedSpecialPage {
'wpDomain' => $this->mDomain,
) + $this->getRequest()->getValues( 'returnto', 'returntoquery' );
$hiddenFieldsStr = '';
- foreach( $hiddenFields as $fieldname => $fieldvalue ) {
+ foreach ( $hiddenFields as $fieldname => $fieldvalue ) {
$hiddenFieldsStr .= Html::hidden( $fieldname, $fieldvalue ) . "\n";
}
$this->getOutput()->addHTML(
Xml::fieldset( $this->msg( 'resetpass_header' )->text() ) .
- Xml::openElement( 'form',
- array(
- 'method' => 'post',
- 'action' => $this->getTitle()->getLocalUrl(),
- 'id' => 'mw-resetpass-form' ) ) . "\n" .
- $hiddenFieldsStr .
- $this->msg( 'resetpass_text' )->parseAsBlock() . "\n" .
- Xml::openElement( 'table', array( 'id' => 'mw-resetpass-table' ) ) . "\n" .
- $this->pretty( $prettyFields ) . "\n" .
- $rememberMe .
- "<tr>\n" .
+ Xml::openElement( 'form',
+ array(
+ 'method' => 'post',
+ 'action' => $this->getTitle()->getLocalURL(),
+ 'id' => 'mw-resetpass-form' ) ) . "\n" .
+ $hiddenFieldsStr .
+ $this->msg( 'resetpass_text' )->parseAsBlock() . "\n" .
+ Xml::openElement( 'table', array( 'id' => 'mw-resetpass-table' ) ) . "\n" .
+ $this->pretty( $prettyFields ) . "\n" .
+ $rememberMe .
+ "<tr>\n" .
"<td></td>\n" .
'<td class="mw-input">' .
- Xml::submitButton( $this->msg( $submitMsg )->text() ) .
- Xml::submitButton( $this->msg( 'resetpass-submit-cancel' )->text(), array( 'name' => 'wpCancel' ) ) .
+ Xml::submitButton( $this->msg( $submitMsg )->text() ) .
+ Xml::submitButton( $this->msg( 'resetpass-submit-cancel' )->text(), array( 'name' => 'wpCancel' ) ) .
"</td>\n" .
- "</tr>\n" .
- Xml::closeElement( 'table' ) .
- Xml::closeElement( 'form' ) .
- Xml::closeElement( 'fieldset' ) . "\n"
+ "</tr>\n" .
+ Xml::closeElement( 'table' ) .
+ Xml::closeElement( 'form' ) .
+ Xml::closeElement( 'fieldset' ) . "\n"
);
}
@@ -190,7 +194,7 @@ class SpecialChangePassword extends UnlistedSpecialPage {
$out = '';
foreach ( $fields as $list ) {
list( $name, $label, $type, $value ) = $list;
- if( $type == 'text' ) {
+ if ( $type == 'text' ) {
$field = htmlspecialchars( $value );
} else {
$attribs = array( 'id' => $name );
@@ -205,16 +209,20 @@ class SpecialChangePassword extends UnlistedSpecialPage {
}
$out .= "<tr>\n";
$out .= "\t<td class='mw-label'>";
- if ( $type != 'text' )
+
+ if ( $type != 'text' ) {
$out .= Xml::label( $this->msg( $label )->text(), $name );
- else
+ } else {
$out .= $this->msg( $label )->escaped();
+ }
+
$out .= "</td>\n";
$out .= "\t<td class='mw-input'>";
$out .= $field;
$out .= "</td>\n";
$out .= "</tr>";
}
+
return $out;
}
@@ -222,6 +230,8 @@ class SpecialChangePassword extends UnlistedSpecialPage {
* @throws PasswordError when cannot set the new password because requirements not met.
*/
protected function attemptReset( $newpass, $retype ) {
+ global $wgPasswordAttemptThrottle;
+
$isSelf = ( $this->mUserName === $this->getUser()->getName() );
if ( $isSelf ) {
$user = $this->getUser();
@@ -229,18 +239,22 @@ class SpecialChangePassword extends UnlistedSpecialPage {
$user = User::newFromName( $this->mUserName );
}
- if( !$user || $user->isAnon() ) {
+ if ( !$user || $user->isAnon() ) {
throw new PasswordError( $this->msg( 'nosuchusershort', $this->mUserName )->text() );
}
- if( $newpass !== $retype ) {
+ if ( $newpass !== $retype ) {
wfRunHooks( 'PrefsPasswordAudit', array( $user, $newpass, 'badretype' ) );
throw new PasswordError( $this->msg( 'badretype' )->text() );
}
$throttleCount = LoginForm::incLoginThrottle( $this->mUserName );
if ( $throttleCount === true ) {
- throw new PasswordError( $this->msg( 'login-throttled' )->text() );
+ $lang = $this->getLanguage();
+ throw new PasswordError( $this->msg( 'login-throttled' )
+ ->params( $lang->formatDuration( $wgPasswordAttemptThrottle['seconds'] ) )
+ ->text()
+ );
}
$abortMsg = 'resetpass-abort-generic';
@@ -249,7 +263,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() );
}
@@ -263,7 +277,7 @@ class SpecialChangePassword extends UnlistedSpecialPage {
$user->setPassword( $this->mNewpass );
wfRunHooks( 'PrefsPasswordAudit', array( $user, $newpass, 'success' ) );
$this->mNewpass = $this->mOldpass = $this->mRetype = '';
- } catch( PasswordError $e ) {
+ } catch ( PasswordError $e ) {
wfRunHooks( 'PrefsPasswordAudit', array( $user, $newpass, 'error' ) );
throw new PasswordError( $e->getMessage() );
}
diff --git a/includes/specials/SpecialComparePages.php b/includes/specials/SpecialComparePages.php
index c3bd3fec..fc6b0c58 100644
--- a/includes/specials/SpecialComparePages.php
+++ b/includes/specials/SpecialComparePages.php
@@ -110,7 +110,7 @@ class SpecialComparePages extends SpecialPage {
$rev1 = self::revOrTitle( $data['Revision1'], $data['Page1'] );
$rev2 = self::revOrTitle( $data['Revision2'], $data['Page2'] );
- if( $rev1 && $rev2 ) {
+ if ( $rev1 && $rev2 ) {
$revision = Revision::newFromId( $rev1 );
if ( $revision ) { // NOTE: $rev1 was already checked, should exist.
@@ -128,14 +128,15 @@ class SpecialComparePages extends SpecialPage {
}
public static function revOrTitle( $revision, $title ) {
- if( $revision ) {
+ if ( $revision ) {
return $revision;
- } elseif( $title ) {
+ } elseif ( $title ) {
$title = Title::newFromText( $title );
- if( $title instanceof Title ) {
+ if ( $title instanceof Title ) {
return $title->getLatestRevID();
}
}
+
return null;
}
@@ -150,6 +151,7 @@ class SpecialComparePages extends SpecialPage {
if ( !$title->exists() ) {
return $this->msg( 'compare-title-not-exists' )->parseAsBlock();
}
+
return true;
}
@@ -161,6 +163,7 @@ class SpecialComparePages extends SpecialPage {
if ( $revision === null ) {
return $this->msg( 'compare-revision-not-exists' )->parseAsBlock();
}
+
return true;
}
diff --git a/includes/specials/SpecialConfirmemail.php b/includes/specials/SpecialConfirmemail.php
index 078c3865..3828b1c6 100644
--- a/includes/specials/SpecialConfirmemail.php
+++ b/includes/specials/SpecialConfirmemail.php
@@ -30,27 +30,30 @@
* @author Rob Church <robchur@gmail.com>
*/
class EmailConfirmation extends UnlistedSpecialPage {
-
- /**
- * Constructor
- */
public function __construct() {
- parent::__construct( 'Confirmemail' );
+ parent::__construct( 'Confirmemail', 'editmyprivateinfo' );
}
/**
* Main execution point
*
- * @param $code Confirmation code passed to the page
+ * @param null|string $code Confirmation code passed to the page
*/
function execute( $code ) {
$this->setHeaders();
$this->checkReadOnly();
+ $this->checkPermissions();
- if( $code === null || $code === '' ) {
- if( $this->getUser()->isLoggedIn() ) {
- if( Sanitizer::validateEmail( $this->getUser()->getEmail() ) ) {
+ // This could also let someone check the current email address, so
+ // require both permissions.
+ if ( !$this->getUser()->isAllowed( 'viewmyprivateinfo' ) ) {
+ throw new PermissionsError( 'viewmyprivateinfo' );
+ }
+
+ if ( $code === null || $code === '' ) {
+ if ( $this->getUser()->isLoggedIn() ) {
+ if ( Sanitizer::validateEmail( $this->getUser()->getEmail() ) ) {
$this->showRequestForm();
} else {
$this->getOutput()->addWikiMsg( 'confirmemail_noemail' );
@@ -62,7 +65,9 @@ class EmailConfirmation extends UnlistedSpecialPage {
array(),
array( 'returnto' => $this->getTitle()->getPrefixedText() )
);
- $this->getOutput()->addHTML( $this->msg( 'confirmemail_needlogin' )->rawParams( $llink )->parse() );
+ $this->getOutput()->addHTML(
+ $this->msg( 'confirmemail_needlogin' )->rawParams( $llink )->parse()
+ );
}
} else {
$this->attemptConfirm( $code );
@@ -75,7 +80,10 @@ class EmailConfirmation extends UnlistedSpecialPage {
function showRequestForm() {
$user = $this->getUser();
$out = $this->getOutput();
- if( $this->getRequest()->wasPosted() && $user->matchEditToken( $this->getRequest()->getText( 'token' ) ) ) {
+
+ if ( $this->getRequest()->wasPosted() &&
+ $user->matchEditToken( $this->getRequest()->getText( 'token' ) )
+ ) {
$status = $user->sendConfirmationMail();
if ( $status->isGood() ) {
$out->addWikiMsg( 'confirmemail_sent' );
@@ -83,7 +91,7 @@ class EmailConfirmation extends UnlistedSpecialPage {
$out->addWikiText( $status->getWikiText( 'confirmemail_sendfailed' ) );
}
} else {
- if( $user->isEmailConfirmed() ) {
+ if ( $user->isEmailConfirmed() ) {
// date and time are separate parameters to facilitate localisation.
// $time is kept for backward compat reasons.
// 'emailauthenticated' is also used in SpecialPreferences.php
@@ -94,14 +102,22 @@ class EmailConfirmation extends UnlistedSpecialPage {
$t = $lang->userTime( $emailAuthenticated, $user );
$out->addWikiMsg( 'emailauthenticated', $time, $d, $t );
}
- if( $user->isEmailConfirmationPending() ) {
- $out->wrapWikiMsg( "<div class=\"error mw-confirmemail-pending\">\n$1\n</div>", 'confirmemail_pending' );
+
+ if ( $user->isEmailConfirmationPending() ) {
+ $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 .= Html::hidden( 'token', $user->getEditToken() );
- $form .= Xml::submitButton( $this->msg( 'confirmemail_send' )->text() );
- $form .= Xml::closeElement( 'form' );
+ $form = Html::openElement(
+ 'form',
+ array( 'method' => 'post', 'action' => $this->getTitle()->getLocalURL() )
+ ) . "\n";
+ $form .= Html::hidden( 'token', $user->getEditToken() ) . "\n";
+ $form .= Xml::submitButton( $this->msg( 'confirmemail_send' )->text() ) . "\n";
+ $form .= Html::closeElement( 'form' ) . "\n";
$out->addHTML( $form );
}
}
@@ -114,20 +130,22 @@ class EmailConfirmation extends UnlistedSpecialPage {
*/
function attemptConfirm( $code ) {
$user = User::newFromConfirmationCode( $code );
- if( is_object( $user ) ) {
- $user->confirmEmail();
- $user->saveSettings();
- $message = $this->getUser()->isLoggedIn() ? 'confirmemail_loggedin' : 'confirmemail_success';
- $this->getOutput()->addWikiMsg( $message );
- if( !$this->getUser()->isLoggedIn() ) {
- $title = SpecialPage::getTitleFor( 'Userlogin' );
- $this->getOutput()->returnToMain( true, $title );
- }
- } else {
+ if ( !is_object( $user ) ) {
$this->getOutput()->addWikiMsg( 'confirmemail_invalid' );
+
+ return;
}
- }
+ $user->confirmEmail();
+ $user->saveSettings();
+ $message = $this->getUser()->isLoggedIn() ? 'confirmemail_loggedin' : 'confirmemail_success';
+ $this->getOutput()->addWikiMsg( $message );
+
+ if ( !$this->getUser()->isLoggedIn() ) {
+ $title = SpecialPage::getTitleFor( 'Userlogin' );
+ $this->getOutput()->returnToMain( true, $title );
+ }
+ }
}
/**
@@ -137,16 +155,14 @@ class EmailConfirmation extends UnlistedSpecialPage {
* @ingroup SpecialPage
*/
class EmailInvalidation extends UnlistedSpecialPage {
-
public function __construct() {
- parent::__construct( 'Invalidateemail' );
+ parent::__construct( 'Invalidateemail', 'editmyprivateinfo' );
}
function execute( $code ) {
$this->setHeaders();
-
$this->checkReadOnly();
-
+ $this->checkPermissions();
$this->attemptInvalidate( $code );
}
@@ -158,15 +174,18 @@ class EmailInvalidation extends UnlistedSpecialPage {
*/
function attemptInvalidate( $code ) {
$user = User::newFromConfirmationCode( $code );
- if( is_object( $user ) ) {
- $user->invalidateEmail();
- $user->saveSettings();
- $this->getOutput()->addWikiMsg( 'confirmemail_invalidated' );
- if( !$this->getUser()->isLoggedIn() ) {
- $this->getOutput()->returnToMain();
- }
- } else {
+ if ( !is_object( $user ) ) {
$this->getOutput()->addWikiMsg( 'confirmemail_invalid' );
+
+ return;
+ }
+
+ $user->invalidateEmail();
+ $user->saveSettings();
+ $this->getOutput()->addWikiMsg( 'confirmemail_invalidated' );
+
+ if ( !$this->getUser()->isLoggedIn() ) {
+ $this->getOutput()->returnToMain();
}
}
}
diff --git a/includes/specials/SpecialContributions.php b/includes/specials/SpecialContributions.php
index b118059c..1fe98190 100644
--- a/includes/specials/SpecialContributions.php
+++ b/includes/specials/SpecialContributions.php
@@ -28,7 +28,6 @@
*/
class SpecialContributions extends SpecialPage {
-
protected $opts;
public function __construct() {
@@ -65,6 +64,7 @@ class SpecialContributions extends SpecialPage {
if ( !strlen( $target ) ) {
$out->addHTML( $this->getForm() );
+
return;
}
@@ -77,11 +77,13 @@ class SpecialContributions extends SpecialPage {
$nt = Title::makeTitleSafe( NS_USER, $target );
if ( !$nt ) {
$out->addHTML( $this->getForm() );
+
return;
}
$userObj = User::newFromName( $nt->getText(), false );
if ( !$userObj ) {
$out->addHTML( $this->getForm() );
+
return;
}
$id = $userObj->getID();
@@ -89,11 +91,17 @@ class SpecialContributions extends SpecialPage {
if ( $this->opts['contribs'] != 'newbie' ) {
$target = $nt->getText();
$out->addSubtitle( $this->contributionsSub( $userObj ) );
- $out->setHTMLTitle( $this->msg( 'pagetitle', $this->msg( 'contributions-title', $target )->plain() ) );
+ $out->setHTMLTitle( $this->msg(
+ 'pagetitle',
+ $this->msg( 'contributions-title', $target )->plain()
+ ) );
$this->getSkin()->setRelevantUser( $userObj );
} else {
$out->addSubtitle( $this->msg( 'sp-contributions-newbies-sub' ) );
- $out->setHTMLTitle( $this->msg( 'pagetitle', $this->msg( 'sp-contributions-newbies-title' )->plain() ) );
+ $out->setHTMLTitle( $this->msg(
+ 'pagetitle',
+ $this->msg( 'sp-contributions-newbies-title' )->plain()
+ ) );
}
if ( ( $ns = $request->getVal( 'namespace', null ) ) !== null && $ns !== '' ) {
@@ -103,10 +111,8 @@ class SpecialContributions extends SpecialPage {
}
$this->opts['associated'] = $request->getBool( 'associated' );
-
- $this->opts['nsInvert'] = (bool) $request->getVal( 'nsInvert' );
-
- $this->opts['tagfilter'] = (string) $request->getVal( 'tagfilter' );
+ $this->opts['nsInvert'] = (bool)$request->getVal( 'nsInvert' );
+ $this->opts['tagfilter'] = (string)$request->getVal( 'tagfilter' );
// Allows reverts to have the bot flag in recent changes. It is just here to
// be passed in the form at the top of the page
@@ -152,9 +158,10 @@ class SpecialContributions extends SpecialPage {
$apiParams['month'] = $this->opts['month'];
}
- $url = wfScript( 'api' ) . '?' . wfArrayToCgi( $apiParams );
+ $url = wfAppendQuery( wfScript( 'api' ), $apiParams );
$out->redirect( $url, '301' );
+
return;
}
@@ -162,13 +169,13 @@ class SpecialContributions extends SpecialPage {
$this->addFeedLinks( array( 'action' => 'feedcontributions', 'user' => $target ) );
if ( wfRunHooks( 'SpecialContributionsBeforeMainOutput', array( $id ) ) ) {
-
$out->addHTML( $this->getForm() );
$pager = new ContribsPager( $this->getContext(), array(
'target' => $target,
'contribs' => $this->opts['contribs'],
'namespace' => $this->opts['namespace'],
+ 'tagfilter' => $this->opts['tagfilter'],
'year' => $this->opts['year'],
'month' => $this->opts['month'],
'deletedOnly' => $this->opts['deletedOnly'],
@@ -176,18 +183,20 @@ class SpecialContributions extends SpecialPage {
'nsInvert' => $this->opts['nsInvert'],
'associated' => $this->opts['associated'],
) );
+
if ( !$pager->getNumRows() ) {
$out->addWikiMsg( 'nocontribs', $target );
} else {
# Show a message about slave lag, if applicable
$lag = wfGetLB()->safeGetLag( $pager->getDatabase() );
- if ( $lag > 0 )
+ if ( $lag > 0 ) {
$out->showLagWarning( $lag );
+ }
$out->addHTML(
'<p>' . $pager->getNavigationBar() . '</p>' .
- $pager->getBody() .
- '<p>' . $pager->getNavigationBar() . '</p>'
+ $pager->getBody() .
+ '<p>' . $pager->getNavigationBar() . '</p>'
);
}
$out->preventClickjacking( $pager->getPreventClickjacking() );
@@ -195,16 +204,16 @@ class SpecialContributions extends SpecialPage {
# Show the appropriate "footer" message - WHOIS tools, etc.
if ( $this->opts['contribs'] == 'newbie' ) {
$message = 'sp-contributions-footer-newbies';
- } elseif( IP::isIPAddress( $target ) ) {
+ } elseif ( IP::isIPAddress( $target ) ) {
$message = 'sp-contributions-footer-anon';
- } elseif( $userObj->isAnon() ) {
+ } elseif ( $userObj->isAnon() ) {
// No message for non-existing users
$message = '';
} else {
$message = 'sp-contributions-footer';
}
- if( $message ) {
+ if ( $message ) {
if ( !$this->msg( $message, $target )->isDisabled() ) {
$out->wrapWikiMsg(
"<div class='mw-contributions-footer'>\n$1\n</div>",
@@ -218,7 +227,8 @@ class SpecialContributions extends SpecialPage {
* Generates the subheading with links
* @param $userObj User object for the target
* @return String: appropriately-escaped HTML to be output literally
- * @todo FIXME: Almost the same as getSubTitle in SpecialDeletedContributions.php. Could be combined.
+ * @todo FIXME: Almost the same as getSubTitle in SpecialDeletedContributions.php.
+ * Could be combined.
*/
protected function contributionsSub( $userObj ) {
if ( $userObj->isAnon() ) {
@@ -258,18 +268,7 @@ class SpecialContributions extends SpecialPage {
}
}
- // Old message 'contribsub' had one parameter, but that doesn't work for
- // languages that want to put the "for" bit right after $user but before
- // $links. If 'contribsub' is around, use it for reverse compatibility,
- // otherwise use 'contribsub2'.
- // @todo Should this be removed at some point?
- $oldMsg = $this->msg( 'contribsub' );
- if ( $oldMsg->exists() ) {
- $linksWithParentheses = $this->msg( 'parentheses' )->rawParams( $links )->escaped();
- return $oldMsg->rawParams( "$user $linksWithParentheses" );
- } else {
- return $this->msg( 'contribsub2' )->rawParams( $user, $links );
- }
+ return $this->msg( 'contribsub2' )->rawParams( $user, $links )->params( $userObj->getName() );
}
/**
@@ -288,7 +287,7 @@ class SpecialContributions extends SpecialPage {
if ( ( $id !== null ) || ( $id === null && IP::isIPAddress( $username ) ) ) {
if ( $this->getUser()->isAllowed( 'block' ) ) { # Block / Change block / Unblock links
- if ( $target->isBlocked() ) {
+ if ( $target->isBlocked() && $target->getBlock()->getType() != Block::TYPE_AUTO ) {
$tools[] = Linker::linkKnown( # Change block link
SpecialPage::getTitleFor( 'Block', $username ),
$this->msg( 'change-blocklink' )->escaped()
@@ -304,14 +303,13 @@ class SpecialContributions extends SpecialPage {
);
}
}
+
# Block log link
$tools[] = Linker::linkKnown(
SpecialPage::getTitleFor( 'Log', 'block' ),
$this->msg( 'sp-contributions-blocklog' )->escaped(),
array(),
- array(
- 'page' => $userpage->getPrefixedText()
- )
+ array( 'page' => $userpage->getPrefixedText() )
);
}
# Uploads
@@ -345,6 +343,7 @@ class SpecialContributions extends SpecialPage {
}
wfRunHooks( 'ContributionsToolLinks', array( $id, $userpage, &$tools ) );
+
return $tools;
}
@@ -398,10 +397,28 @@ class SpecialContributions extends SpecialPage {
$this->opts['topOnly'] = false;
}
- $form = Html::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' );
+ $skipParameters = array(
+ 'namespace',
+ 'nsInvert',
+ 'deletedOnly',
+ 'target',
+ 'contribs',
+ 'year',
+ 'month',
+ 'topOnly',
+ 'associated'
+ );
+
foreach ( $this->opts as $name => $value ) {
if ( in_array( $name, $skipParameters ) ) {
continue;
@@ -412,65 +429,82 @@ class SpecialContributions extends SpecialPage {
$tagFilter = ChangeTags::buildTagFilterSelector( $this->opts['tagfilter'] );
if ( $tagFilter ) {
- $filterSelection =
- Html::rawElement( 'td', array( 'class' => 'mw-label' ), array_shift( $tagFilter ) ) .
- Html::rawElement( 'td', array( 'class' => 'mw-input' ), implode( '&#160', $tagFilter ) );
+ $filterSelection = Html::rawElement(
+ 'td',
+ array( 'class' => 'mw-label' ),
+ array_shift( $tagFilter )
+ );
+ $filterSelection .= Html::rawElement(
+ 'td',
+ array( 'class' => 'mw-input' ),
+ implode( '&#160', $tagFilter )
+ );
} else {
$filterSelection = Html::rawElement( 'td', array( 'colspan' => 2 ), '' );
}
- $targetSelection = Html::rawElement( 'td', array( 'colspan' => 2 ),
- Xml::radioLabel(
- $this->msg( 'sp-contributions-newbies' )->text(),
- 'contribs',
- 'newbie',
- 'newbie',
- $this->opts['contribs'] == 'newbie',
- array( 'class' => 'mw-input' )
- ) . '<br />' .
- Xml::radioLabel(
- $this->msg( 'sp-contributions-username' )->text(),
- 'contribs',
- 'user',
- 'user',
- $this->opts['contribs'] == 'user',
- array( 'class' => 'mw-input' )
- ) . ' ' .
- Html::input(
- 'target',
- $this->opts['target'],
- 'text',
- array( 'size' => '40', 'required' => '', 'class' => 'mw-input' ) +
- ( $this->opts['target'] ? array() : array( 'autofocus' )
+ $labelNewbies = Xml::radioLabel(
+ $this->msg( 'sp-contributions-newbies' )->text(),
+ 'contribs',
+ 'newbie',
+ 'newbie',
+ $this->opts['contribs'] == 'newbie',
+ array( 'class' => 'mw-input' )
+ );
+ $labelUsername = Xml::radioLabel(
+ $this->msg( 'sp-contributions-username' )->text(),
+ 'contribs',
+ 'user',
+ 'user',
+ $this->opts['contribs'] == 'user',
+ array( 'class' => 'mw-input' )
+ );
+ $input = Html::input(
+ 'target',
+ $this->opts['target'],
+ 'text',
+ array( 'size' => '40', 'required' => '', 'class' => 'mw-input' ) +
+ ( $this->opts['target'] ? array() : array( 'autofocus' )
)
- ) . ' '
+ );
+ $targetSelection = Html::rawElement(
+ 'td',
+ array( 'colspan' => 2 ),
+ $labelNewbies . '<br />' . $labelUsername . ' ' . $input . ' '
);
- $namespaceSelection =
- Xml::tags( 'td', array( 'class' => 'mw-label' ),
- Xml::label(
- $this->msg( 'namespace' )->text(),
- 'namespace',
- ''
- )
- ) .
- Html::rawElement( 'td', null,
- Html::namespaceSelector( array(
- 'selected' => $this->opts['namespace'],
- 'all' => '',
- ), array(
- 'name' => 'namespace',
- 'id' => 'namespace',
+ $namespaceSelection = Xml::tags(
+ 'td',
+ array( 'class' => 'mw-label' ),
+ Xml::label(
+ $this->msg( 'namespace' )->text(),
+ 'namespace',
+ ''
+ )
+ );
+ $namespaceSelection .= Html::rawElement(
+ 'td',
+ null,
+ Html::namespaceSelector(
+ array( 'selected' => $this->opts['namespace'], 'all' => '' ),
+ array(
+ 'name' => 'namespace',
+ 'id' => 'namespace',
'class' => 'namespaceselector',
- ) ) .
- '&#160;' .
- Html::rawElement( 'span', array( 'style' => 'white-space: nowrap' ),
+ )
+ ) . '&#160;' .
+ Html::rawElement(
+ 'span',
+ array( 'style' => 'white-space: nowrap' ),
Xml::checkLabel(
$this->msg( 'invert' )->text(),
'nsInvert',
'nsInvert',
$this->opts['nsInvert'],
- array( 'title' => $this->msg( 'tooltip-invert' )->text(), 'class' => 'mw-input' )
+ array(
+ 'title' => $this->msg( 'tooltip-invert' )->text(),
+ 'class' => 'mw-input'
+ )
) . '&#160;'
) .
Html::rawElement( 'span', array( 'style' => 'white-space: nowrap' ),
@@ -479,13 +513,18 @@ class SpecialContributions extends SpecialPage {
'associated',
'associated',
$this->opts['associated'],
- array( 'title' => $this->msg( 'tooltip-namespace_association' )->text(), 'class' => 'mw-input' )
+ array(
+ 'title' => $this->msg( 'tooltip-namespace_association' )->text(),
+ 'class' => 'mw-input'
+ )
) . '&#160;'
)
- );
+ );
if ( $this->getUser()->isAllowed( 'deletedhistory' ) ) {
- $deletedOnlyCheck = Html::rawElement( 'span', array( 'style' => 'white-space: nowrap' ),
+ $deletedOnlyCheck = Html::rawElement(
+ 'span',
+ array( 'style' => 'white-space: nowrap' ),
Xml::checkLabel(
$this->msg( 'history-show-deleted' )->text(),
'deletedOnly',
@@ -498,46 +537,50 @@ class SpecialContributions extends SpecialPage {
$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(),
- 'topOnly',
- 'mw-show-top-only',
- $this->opts['topOnly'],
- array( 'class' => 'mw-input' )
- )
+ $checkLabelTopOnly = Html::rawElement(
+ 'span',
+ array( 'style' => 'white-space: nowrap' ),
+ Xml::checkLabel(
+ $this->msg( 'sp-contributions-toponly' )->text(),
+ 'topOnly',
+ 'mw-show-top-only',
+ $this->opts['topOnly'],
+ array( 'class' => 'mw-input' )
)
);
+ $extraOptions = Html::rawElement(
+ 'td',
+ array( 'colspan' => 2 ),
+ $deletedOnlyCheck . $checkLabelTopOnly
+ );
$dateSelectionAndSubmit = Xml::tags( 'td', array( 'colspan' => 2 ),
Xml::dateMenu(
- $this->opts['year'],
+ $this->opts['year'] === '' ? MWTimestamp::getInstance()->format( 'Y' ) : $this->opts['year'],
$this->opts['month']
) . ' ' .
- Xml::submitButton(
- $this->msg( 'sp-contributions-submit' )->text(),
- array( 'class' => 'mw-submit' )
- )
+ Xml::submitButton(
+ $this->msg( 'sp-contributions-submit' )->text(),
+ array( 'class' => 'mw-submit' )
+ )
);
- $form .=
- Xml::fieldset( $this->msg( 'sp-contributions-search' )->text() ) .
- 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"
- );
+ $form .= Xml::fieldset( $this->msg( 'sp-contributions-search' )->text() );
+ $form .= 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->isBlank() ) {
$form .= "<p id='mw-sp-contributions-explain'>{$explain->parse()}</p>";
}
- $form .= Xml::closeElement( 'fieldset' ) .
- Xml::closeElement( 'form' );
+
+ $form .= Xml::closeElement( 'fieldset' ) . Xml::closeElement( 'form' );
+
return $form;
}
@@ -552,9 +595,11 @@ class SpecialContributions extends SpecialPage {
*/
class ContribsPager extends ReverseChronologicalPager {
public $mDefaultDirection = true;
- var $messages, $target;
- var $namespace = '', $mDb;
- var $preventClickjacking = false;
+ public $messages;
+ public $target;
+ public $namespace = '';
+ public $mDb;
+ public $preventClickjacking = false;
/**
* @var array
@@ -564,7 +609,15 @@ class ContribsPager extends ReverseChronologicalPager {
function __construct( IContextSource $context, array $options ) {
parent::__construct( $context );
- $msgs = array( 'uctop', 'diff', 'newarticle', 'rollbacklink', 'diff', 'hist', 'rev-delundel', 'pipe-separator' );
+ $msgs = array(
+ 'diff',
+ 'hist',
+ 'newarticle',
+ 'pipe-separator',
+ 'rev-delundel',
+ 'rollbacklink',
+ 'uctop'
+ );
foreach ( $msgs as $msg ) {
$this->messages[$msg] = $this->msg( $msg )->escaped();
@@ -590,6 +643,7 @@ class ContribsPager extends ReverseChronologicalPager {
function getDefaultQuery() {
$query = parent::getDefaultQuery();
$query['target'] = $this->target;
+
return $query;
}
@@ -603,7 +657,11 @@ class ContribsPager extends ReverseChronologicalPager {
* @return ResultWrapper
*/
function reallyDoQuery( $offset, $limit, $descending ) {
- list( $tables, $fields, $conds, $fname, $options, $join_conds ) = $this->buildQueryInfo( $offset, $limit, $descending );
+ list( $tables, $fields, $conds, $fname, $options, $join_conds ) = $this->buildQueryInfo(
+ $offset,
+ $limit,
+ $descending
+ );
$pager = $this;
/*
@@ -624,8 +682,13 @@ class ContribsPager extends ReverseChronologicalPager {
* $limit: see phpdoc above
* $descending: see phpdoc above
*/
- $data = array( $this->mDb->select( $tables, $fields, $conds, $fname, $options, $join_conds ) );
- wfRunHooks( 'ContribsPager::reallyDoQuery', array( &$data, $pager, $offset, $limit, $descending ) );
+ $data = array( $this->mDb->select(
+ $tables, $fields, $conds, $fname, $options, $join_conds
+ ) );
+ wfRunHooks(
+ 'ContribsPager::reallyDoQuery',
+ array( &$data, $pager, $offset, $limit, $descending )
+ );
$result = array();
@@ -673,15 +736,15 @@ class ContribsPager extends ReverseChronologicalPager {
$join_cond['user'] = Revision::userJoinCond();
$queryInfo = array(
- 'tables' => $tables,
- 'fields' => array_merge(
+ 'tables' => $tables,
+ 'fields' => array_merge(
Revision::selectFields(),
Revision::selectUserFields(),
array( 'page_namespace', 'page_title', 'page_is_new',
'page_latest', 'page_is_redirect', 'page_len' )
),
- 'conds' => $conds,
- 'options' => array( 'USE INDEX' => array( 'revision' => $index ) ),
+ 'conds' => $conds,
+ 'options' => array( 'USE INDEX' => array( 'revision' => $index ) ),
'join_conds' => $join_cond
);
@@ -695,6 +758,7 @@ class ContribsPager extends ReverseChronologicalPager {
);
wfRunHooks( 'ContribsPager::getQueryInfo', array( &$this, &$queryInfo ) );
+
return $queryInfo;
}
@@ -709,7 +773,7 @@ class ContribsPager extends ReverseChronologicalPager {
# ignore local groups with the bot right
# @todo FIXME: Global groups may have 'bot' rights
$groupsWithBotPermission = User::getGroupsWithPermission( 'bot' );
- if( count( $groupsWithBotPermission ) ) {
+ if ( count( $groupsWithBotPermission ) ) {
$tables[] = 'user_groups';
$condition[] = 'ug_group IS NULL';
$join_conds['user_groups'] = array(
@@ -729,12 +793,15 @@ class ContribsPager extends ReverseChronologicalPager {
$index = 'usertext_timestamp';
}
}
+
if ( $this->deletedOnly ) {
$condition[] = 'rev_deleted != 0';
}
+
if ( $this->topOnly ) {
$condition[] = 'rev_id = page_latest';
}
+
return array( $tables, $index, $condition, $join_conds );
}
@@ -746,20 +813,20 @@ class ContribsPager extends ReverseChronologicalPager {
if ( !$this->associated ) {
return array( "page_namespace $eq_op $selectedNS" );
- } else {
- $associatedNS = $this->mDb->addQuotes (
- MWNamespace::getAssociated( $this->namespace )
- );
- return array(
- "page_namespace $eq_op $selectedNS " .
- $bool_op .
- " page_namespace $eq_op $associatedNS"
- );
}
- } else {
- return array();
+ $associatedNS = $this->mDb->addQuotes(
+ MWNamespace::getAssociated( $this->namespace )
+ );
+
+ return array(
+ "page_namespace $eq_op $selectedNS " .
+ $bool_op .
+ " page_namespace $eq_op $associatedNS"
+ );
}
+
+ return array();
}
function getIndexField() {
@@ -773,7 +840,7 @@ class ContribsPager extends ReverseChronologicalPager {
$batch = new LinkBatch();
# Give some pointers to make (last) links
foreach ( $this->mResult as $row ) {
- if( isset( $row->rev_parent_id ) && $row->rev_parent_id ) {
+ if ( isset( $row->rev_parent_id ) && $row->rev_parent_id ) {
$revIds[] = $row->rev_parent_id;
}
if ( isset( $row->rev_id ) ) {
@@ -830,7 +897,7 @@ class ContribsPager extends ReverseChronologicalPager {
*/
wfSuppressWarnings();
$rev = new Revision( $row );
- $validRevision = (bool) $rev->getId();
+ $validRevision = (bool)$rev->getId();
wfRestoreWarnings();
if ( $validRevision ) {
@@ -850,8 +917,8 @@ class ContribsPager extends ReverseChronologicalPager {
$topmarktext .= '<span class="mw-uctop">' . $this->messages['uctop'] . '</span>';
# Add rollback link
if ( !$row->page_is_new && $page->quickUserCan( 'rollback', $user )
- && $page->quickUserCan( 'edit', $user ) )
- {
+ && $page->quickUserCan( 'edit', $user )
+ ) {
$this->preventClickjacking();
$topmarktext .= ' ' . Linker::generateRollback( $rev, $this->getContext() );
}
@@ -881,11 +948,22 @@ class ContribsPager extends ReverseChronologicalPager {
// For some reason rev_parent_id isn't populated for this row.
// Its rumoured this is true on wikipedia for some revisions (bug 34922).
// Next best thing is to have the total number of bytes.
- $chardiff = ' <span class="mw-changeslist-separator">. .</span> ' . Linker::formatRevisionSize( $row->rev_len ) . ' <span class="mw-changeslist-separator">. .</span> ';
+ $chardiff = ' <span class="mw-changeslist-separator">. .</span> ';
+ $chardiff .= Linker::formatRevisionSize( $row->rev_len );
+ $chardiff .= ' <span class="mw-changeslist-separator">. .</span> ';
} else {
- $parentLen = isset( $this->mParentLens[$row->rev_parent_id] ) ? $this->mParentLens[$row->rev_parent_id] : 0;
- $chardiff = ' <span class="mw-changeslist-separator">. .</span> ' . ChangesList::showCharacterDifference(
- $parentLen, $row->rev_len, $this->getContext() ) . ' <span class="mw-changeslist-separator">. .</span> ';
+ $parentLen = 0;
+ if ( isset( $this->mParentLens[$row->rev_parent_id] ) ) {
+ $parentLen = $this->mParentLens[$row->rev_parent_id];
+ }
+
+ $chardiff = ' <span class="mw-changeslist-separator">. .</span> ';
+ $chardiff .= ChangesList::showCharacterDifference(
+ $parentLen,
+ $row->rev_len,
+ $this->getContext()
+ );
+ $chardiff .= ' <span class="mw-changeslist-separator">. .</span> ';
}
$lang = $this->getLanguage();
@@ -908,7 +986,7 @@ class ContribsPager extends ReverseChronologicalPager {
# Show user names for /newbies as there may be different users.
# Note that we already excluded rows with hidden user names.
if ( $this->contribs == 'newbie' ) {
- $userlink = ' . . ' . Linker::userLink( $rev->getUser(), $rev->getUserText() );
+ $userlink = ' . . ' . $lang->getDirMark() . Linker::userLink( $rev->getUser(), $rev->getUserText() );
$userlink .= ' ' . $this->msg( 'parentheses' )->rawParams(
Linker::userTalkLink( $rev->getUser(), $rev->getUserText() ) )->escaped() . ' ';
} else {
@@ -932,16 +1010,24 @@ class ContribsPager extends ReverseChronologicalPager {
$del .= ' ';
}
- $diffHistLinks = $this->msg( 'parentheses' )->rawParams( $difftext . $this->messages['pipe-separator'] . $histlink )->escaped();
- $ret = "{$del}{$d} {$diffHistLinks}{$chardiff}{$nflag}{$mflag} {$link}{$userlink} {$comment} {$topmarktext}";
+ $diffHistLinks = $this->msg( 'parentheses' )
+ ->rawParams( $difftext . $this->messages['pipe-separator'] . $histlink )
+ ->escaped();
+ $ret = "{$del}{$d} {$diffHistLinks}{$chardiff}{$nflag}{$mflag} ";
+ $ret .= "{$link}{$userlink} {$comment} {$topmarktext}";
# Denote if username is redacted for this edit
if ( $rev->isDeleted( Revision::DELETED_USER ) ) {
- $ret .= " <strong>" . $this->msg( 'rev-deleted-user-contribs' )->escaped() . "</strong>";
+ $ret .= " <strong>" .
+ $this->msg( 'rev-deleted-user-contribs' )->escaped() .
+ "</strong>";
}
# Tags, if any.
- list( $tagSummary, $newClasses ) = ChangeTags::formatSummaryRow( $row->ts_tags, 'contributions' );
+ list( $tagSummary, $newClasses ) = ChangeTags::formatSummaryRow(
+ $row->ts_tags,
+ 'contributions'
+ );
$classes = array_merge( $classes, $newClasses );
$ret .= " $tagSummary";
}
@@ -957,6 +1043,7 @@ class ContribsPager extends ReverseChronologicalPager {
}
wfProfileOut( __METHOD__ );
+
return $ret;
}
@@ -966,7 +1053,8 @@ class ContribsPager extends ReverseChronologicalPager {
*/
function getSqlComment() {
if ( $this->namespace || $this->deletedOnly ) {
- return 'contributions page filtered for namespace or RevisionDeleted edits'; // potentially slow, see CR r58153
+ // potentially slow, see CR r58153
+ return 'contributions page filtered for namespace or RevisionDeleted edits';
} else {
return 'contributions page unfiltered';
}
diff --git a/includes/specials/SpecialDeadendpages.php b/includes/specials/SpecialDeadendpages.php
index 6978d6bb..44137c1e 100644
--- a/includes/specials/SpecialDeadendpages.php
+++ b/includes/specials/SpecialDeadendpages.php
@@ -59,24 +59,29 @@ class DeadendPagesPage extends PageQueryPage {
function getQueryInfo() {
return array(
'tables' => array( 'page', 'pagelinks' ),
- 'fields' => array( 'namespace' => 'page_namespace',
- 'title' => 'page_title',
- 'value' => 'page_title'
+ 'fields' => array(
+ 'namespace' => 'page_namespace',
+ 'title' => 'page_title',
+ 'value' => 'page_title'
),
- 'conds' => array( 'pl_from IS NULL',
- 'page_namespace' => MWNamespace::getContentNamespaces(),
- 'page_is_redirect' => 0
+ 'conds' => array(
+ 'pl_from IS NULL',
+ 'page_namespace' => MWNamespace::getContentNamespaces(),
+ 'page_is_redirect' => 0
),
- 'join_conds' => array( 'pagelinks' => array( 'LEFT JOIN', array(
- 'page_id=pl_from'
- ) ) )
+ 'join_conds' => array(
+ 'pagelinks' => array(
+ 'LEFT JOIN',
+ array( 'page_id=pl_from' )
+ )
+ )
);
}
function getOrderFields() {
// For some crazy reason ordering by a constant
// causes a filesort
- if( count( MWNamespace::getContentNamespaces() ) > 1 ) {
+ if ( count( MWNamespace::getContentNamespaces() ) > 1 ) {
return array( 'page_namespace', 'page_title' );
} else {
return array( 'page_title' );
diff --git a/includes/specials/SpecialDeletedContributions.php b/includes/specials/SpecialDeletedContributions.php
index e374979e..9b9888ad 100644
--- a/includes/specials/SpecialDeletedContributions.php
+++ b/includes/specials/SpecialDeletedContributions.php
@@ -27,13 +27,20 @@
*/
class DeletedContribsPager extends IndexPager {
public $mDefaultDirection = true;
- var $messages, $target;
- var $namespace = '', $mDb;
+ public $messages;
+ public $target;
+ public $namespace = '';
+ public $mDb;
+
+ /**
+ * @var string Navigation bar with paging links.
+ */
+ protected $mNavigationBar;
function __construct( IContextSource $context, $target, $namespace = false ) {
parent::__construct( $context );
$msgs = array( 'deletionlog', 'undeleteviewlink', 'diff' );
- foreach( $msgs as $msg ) {
+ foreach ( $msgs as $msg ) {
$this->messages[$msg] = $this->msg( $msg )->escaped();
}
$this->target = $target;
@@ -44,6 +51,7 @@ class DeletedContribsPager extends IndexPager {
function getDefaultQuery() {
$query = parent::getDefaultQuery();
$query['target'] = $this->target;
+
return $query;
}
@@ -52,17 +60,18 @@ class DeletedContribsPager extends IndexPager {
$conds = array_merge( $userCond, $this->getNamespaceCond() );
$user = $this->getUser();
// Paranoia: avoid brute force searches (bug 17792)
- if( !$user->isAllowed( 'deletedhistory' ) ) {
+ if ( !$user->isAllowed( 'deletedhistory' ) ) {
$conds[] = $this->mDb->bitAnd( 'ar_deleted', Revision::DELETED_USER ) . ' = 0';
- } elseif( !$user->isAllowed( 'suppressrevision' ) ) {
+ } elseif ( !$user->isAllowed( 'suppressrevision' ) ) {
$conds[] = $this->mDb->bitAnd( 'ar_deleted', Revision::SUPPRESSED_USER ) .
' != ' . Revision::SUPPRESSED_USER;
}
+
return array(
'tables' => array( 'archive' ),
'fields' => array(
- 'ar_rev_id', 'ar_namespace', 'ar_title', 'ar_timestamp', 'ar_comment', 'ar_minor_edit',
- 'ar_user', 'ar_user_text', 'ar_deleted'
+ 'ar_rev_id', 'ar_namespace', 'ar_title', 'ar_timestamp', 'ar_comment',
+ 'ar_minor_edit', 'ar_user', 'ar_user_text', 'ar_deleted'
),
'conds' => $conds,
'options' => array( 'USE INDEX' => $index )
@@ -107,8 +116,17 @@ class DeletedContribsPager extends IndexPager {
$lang = $this->getLanguage();
$limits = $lang->pipeList( $limitLinks );
- $this->mNavigationBar = "(" . $lang->pipeList( array( $pagingLinks['first'], $pagingLinks['last'] ) ) . ") " .
- $this->msg( 'viewprevnext' )->rawParams( $pagingLinks['prev'], $pagingLinks['next'], $limits )->escaped();
+ $firstLast = $lang->pipeList( array( $pagingLinks['first'], $pagingLinks['last'] ) );
+ $firstLast = $this->msg( 'parentheses' )->rawParams( $firstLast )->escaped();
+ $prevNext = $this->msg( 'viewprevnext' )
+ ->rawParams(
+ $pagingLinks['prev'],
+ $pagingLinks['next'],
+ $limits
+ )->escaped();
+ $separator = $this->msg( 'word-separator' )->escaped();
+ $this->mNavigationBar = $firstLast . $separator . $prevNext;
+
return $this->mNavigationBar;
}
@@ -138,15 +156,15 @@ class DeletedContribsPager extends IndexPager {
$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,
- 'user_text' => $row->ar_user_text,
- 'timestamp' => $row->ar_timestamp,
- 'minor_edit' => $row->ar_minor_edit,
- 'deleted' => $row->ar_deleted,
- ) );
+ 'title' => $page,
+ 'id' => $row->ar_rev_id,
+ 'comment' => $row->ar_comment,
+ 'user' => $row->ar_user,
+ 'user_text' => $row->ar_user_text,
+ 'timestamp' => $row->ar_timestamp,
+ 'minor_edit' => $row->ar_minor_edit,
+ 'deleted' => $row->ar_deleted,
+ ) );
$undelete = SpecialPage::getTitleFor( 'Undelete' );
@@ -168,7 +186,7 @@ class DeletedContribsPager extends IndexPager {
$user = $this->getUser();
- if( $user->isAllowed( 'deletedtext' ) ) {
+ if ( $user->isAllowed( 'deletedtext' ) ) {
$last = Linker::linkKnown(
$undelete,
$this->messages['diff'],
@@ -184,9 +202,10 @@ class DeletedContribsPager extends IndexPager {
}
$comment = Linker::revComment( $rev );
- $date = htmlspecialchars( $this->getLanguage()->userTimeAndDate( $rev->getTimestamp(), $user ) );
+ $date = $this->getLanguage()->userTimeAndDate( $rev->getTimestamp(), $user );
+ $date = htmlspecialchars( $date );
- if( !$user->isAllowed( 'undelete' ) || !$rev->userCan( Revision::DELETED_TEXT, $user ) ) {
+ if ( !$user->isAllowed( 'undelete' ) || !$rev->userCan( Revision::DELETED_TEXT, $user ) ) {
$link = $date; // unusable link
} else {
$link = Linker::linkKnown(
@@ -200,7 +219,7 @@ class DeletedContribsPager extends IndexPager {
);
}
// Style deleted items
- if( $rev->isDeleted( Revision::DELETED_TEXT ) ) {
+ if ( $rev->isDeleted( Revision::DELETED_TEXT ) ) {
$link = '<span class="history-deleted">' . $link . '</span>';
}
@@ -210,7 +229,7 @@ class DeletedContribsPager extends IndexPager {
array( 'class' => 'mw-changeslist-title' )
);
- if( $rev->isMinor() ) {
+ if ( $rev->isMinor() ) {
$mflag = ChangesList::flag( 'minor' );
} else {
$mflag = '';
@@ -218,7 +237,9 @@ class DeletedContribsPager extends IndexPager {
// Revision delete link
$del = Linker::getRevDeleteLink( $user, $rev, $page );
- if ( $del ) $del .= ' ';
+ if ( $del ) {
+ $del .= ' ';
+ }
$tools = Html::rawElement(
'span',
@@ -231,13 +252,14 @@ class DeletedContribsPager extends IndexPager {
$ret = "{$del}{$link} {$tools} {$separator} {$mflag} {$pagelink} {$comment}";
# Denote if username is redacted for this edit
- if( $rev->isDeleted( Revision::DELETED_USER ) ) {
+ if ( $rev->isDeleted( Revision::DELETED_USER ) ) {
$ret .= " <strong>" . $this->msg( 'rev-deleted-user-contribs' )->escaped() . "</strong>";
}
$ret = Html::rawElement( 'li', array(), $ret ) . "\n";
wfProfileOut( __METHOD__ );
+
return $ret;
}
@@ -254,7 +276,7 @@ class DeletedContribsPager extends IndexPager {
class DeletedContributionsPage extends SpecialPage {
function __construct() {
parent::__construct( 'DeletedContributions', 'deletedhistory',
- /*listed*/ true, /*function*/ false, /*file*/ false );
+ /*listed*/true, /*function*/false, /*file*/false );
}
/**
@@ -265,6 +287,7 @@ class DeletedContributionsPage extends SpecialPage {
*/
function execute( $par ) {
global $wgQueryPageDefaultLimit;
+
$this->setHeaders();
$this->outputHeader();
@@ -272,6 +295,7 @@ class DeletedContributionsPage extends SpecialPage {
if ( !$this->userCanExecute( $user ) ) {
$this->displayRestrictionError();
+
return;
}
@@ -289,6 +313,7 @@ class DeletedContributionsPage extends SpecialPage {
if ( !strlen( $target ) ) {
$out->addHTML( $this->getForm( '' ) );
+
return;
}
@@ -298,6 +323,7 @@ class DeletedContributionsPage extends SpecialPage {
$userObj = User::newFromName( $target, false );
if ( !$userObj ) {
$out->addHTML( $this->getForm( '' ) );
+
return;
}
$this->getSkin()->setRelevantUser( $userObj );
@@ -316,28 +342,33 @@ class DeletedContributionsPage extends SpecialPage {
$pager = new DeletedContribsPager( $this->getContext(), $target, $options['namespace'] );
if ( !$pager->getNumRows() ) {
$out->addWikiMsg( 'nocontribs' );
+
return;
}
# Show a message about slave lag, if applicable
$lag = wfGetLB()->safeGetLag( $pager->getDatabase() );
- if( $lag > 0 )
+ if ( $lag > 0 ) {
$out->showLagWarning( $lag );
+ }
$out->addHTML(
'<p>' . $pager->getNavigationBar() . '</p>' .
- $pager->getBody() .
- '<p>' . $pager->getNavigationBar() . '</p>' );
+ $pager->getBody() .
+ '<p>' . $pager->getNavigationBar() . '</p>' );
# If there were contributions, and it was a valid user or IP, show
# the appropriate "footer" message - WHOIS tools, etc.
- if( $target != 'newbies' ) {
- $message = IP::isIPAddress( $target )
- ? 'sp-contributions-footer-anon'
- : 'sp-contributions-footer';
-
- if( !$this->msg( $message )->isDisabled() ) {
- $out->wrapWikiMsg( "<div class='mw-contributions-footer'>\n$1\n</div>", array( $message, $target ) );
+ if ( $target != 'newbies' ) {
+ $message = IP::isIPAddress( $target ) ?
+ 'sp-contributions-footer-anon' :
+ 'sp-contributions-footer';
+
+ if ( !$this->msg( $message )->isDisabled() ) {
+ $out->wrapWikiMsg(
+ "<div class='mw-contributions-footer'>\n$1\n</div>",
+ array( $message, $target )
+ );
}
}
}
@@ -358,11 +389,12 @@ class DeletedContributionsPage extends SpecialPage {
$nt = $userObj->getUserPage();
$id = $userObj->getID();
$talk = $nt->getTalkPage();
- if( $talk ) {
+ if ( $talk ) {
# Talk page link
$tools[] = Linker::link( $talk, $this->msg( 'sp-contributions-talk' )->escaped() );
- if( ( $id !== null ) || ( $id === null && IP::isIPAddress( $nt->getText() ) ) ) {
- if( $this->getUser()->isAllowed( 'block' ) ) { # Block / Change block / Unblock links
+ if ( ( $id !== null ) || ( $id === null && IP::isIPAddress( $nt->getText() ) ) ) {
+ # Block / Change block / Unblock links
+ if ( $this->getUser()->isAllowed( 'block' ) ) {
if ( $userObj->isBlocked() ) {
$tools[] = Linker::linkKnown( # Change block link
SpecialPage::getTitleFor( 'Block', $nt->getDBkey() ),
@@ -377,8 +409,8 @@ class DeletedContributionsPage extends SpecialPage {
'ip' => $nt->getDBkey()
)
);
- }
- else { # User is not blocked
+ } else {
+ # User is not blocked
$tools[] = Linker::linkKnown( # Block link
SpecialPage::getTitleFor( 'Block', $nt->getDBkey() ),
$this->msg( 'blocklink' )->escaped()
@@ -419,7 +451,7 @@ class DeletedContributionsPage extends SpecialPage {
# Add a link to change user rights for privileged users
$userrightsPage = new UserrightsPage();
$userrightsPage->setContext( $this->getContext() );
- if( $userrightsPage->userCanChangeRights( $userObj ) ) {
+ if ( $userrightsPage->userCanChangeRights( $userObj ) ) {
$tools[] = Linker::linkKnown(
SpecialPage::getTitleFor( 'Userrights', $nt->getDBkey() ),
$this->msg( 'sp-contributions-userrights' )->escaped()
@@ -432,7 +464,8 @@ class DeletedContributionsPage extends SpecialPage {
// Show a note if the user is blocked and display the last block log entry.
if ( $userObj->isBlocked() ) {
- $out = $this->getOutput(); // LogEventsList::showLogExtract() wants the first parameter by ref
+ // LogEventsList::showLogExtract() wants the first parameter by ref
+ $out = $this->getOutput();
LogEventsList::showLogExtract(
$out,
'block',
@@ -451,16 +484,7 @@ class DeletedContributionsPage extends SpecialPage {
}
}
- // Old message 'contribsub' had one parameter, but that doesn't work for
- // languages that want to put the "for" bit right after $user but before
- // $links. If 'contribsub' is around, use it for reverse compatibility,
- // otherwise use 'contribsub2'.
- $oldMsg = $this->msg( 'contribsub' );
- if ( $oldMsg->exists() ) {
- return $oldMsg->rawParams( "$user ($links)" );
- } else {
- return $this->msg( 'contribsub2' )->rawParams( $user, $links );
- }
+ return $this->msg( 'contribsub2' )->rawParams( $user, $links )->params( $userObj->getName() );
}
/**
@@ -499,27 +523,38 @@ class DeletedContributionsPage extends SpecialPage {
$f .= "\t" . Html::hidden( $name, $value ) . "\n";
}
- $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(
+ $f .= Xml::openElement( 'fieldset' );
+ $f .= Xml::element( 'legend', array(), $this->msg( 'sp-contributions-search' )->text() );
+ $f .= Xml::tags(
+ 'label',
+ array( 'for' => 'target' ),
+ $this->msg( 'sp-contributions-username' )->parse()
+ ) . ' ';
+ $f .= Html::input(
+ 'target',
+ $options['target'],
+ 'text',
+ array(
'size' => '20',
'required' => ''
- ) + ( $options['target'] ? array() : array( 'autofocus' ) ) ) . ' '.
- Html::namespaceSelector(
- array(
- 'selected' => $options['namespace'],
- 'all' => '',
- 'label' => $this->msg( 'namespace' )->text()
- ), array(
- 'name' => 'namespace',
- 'id' => 'namespace',
- 'class' => 'namespaceselector',
- )
- ) . ' ' .
- Xml::submitButton( $this->msg( 'sp-contributions-submit' )->text() ) .
- Xml::closeElement( 'fieldset' ) .
- Xml::closeElement( 'form' );
+ ) + ( $options['target'] ? array() : array( 'autofocus' ) )
+ ) . ' ';
+ $f .= Html::namespaceSelector(
+ array(
+ 'selected' => $options['namespace'],
+ 'all' => '',
+ 'label' => $this->msg( 'namespace' )->text()
+ ),
+ array(
+ 'name' => 'namespace',
+ 'id' => 'namespace',
+ 'class' => 'namespaceselector',
+ )
+ ) . ' ';
+ $f .= Xml::submitButton( $this->msg( 'sp-contributions-submit' )->text() );
+ $f .= Xml::closeElement( 'fieldset' );
+ $f .= Xml::closeElement( 'form' );
+
return $f;
}
diff --git a/includes/specials/SpecialDisambiguations.php b/includes/specials/SpecialDisambiguations.php
deleted file mode 100644
index 2126ca52..00000000
--- a/includes/specials/SpecialDisambiguations.php
+++ /dev/null
@@ -1,165 +0,0 @@
-<?php
-/**
- * Implements Special:Disambiguations
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup SpecialPage
- */
-
-/**
- * A special page that lists pages containing links to disambiguations pages
- *
- * @ingroup SpecialPage
- */
-class DisambiguationsPage extends QueryPage {
-
- function __construct( $name = 'Disambiguations' ) {
- parent::__construct( $name );
- }
-
- function isExpensive() {
- return true;
- }
-
- function isSyndicated() {
- return false;
- }
-
- function getPageHeader() {
- return $this->msg( 'disambiguations-text' )->parseAsBlock();
- }
-
- /**
- * @return string|bool False on failure
- */
- function getQueryFromLinkBatch() {
- $dbr = wfGetDB( DB_SLAVE );
- $dMsgText = $this->msg( 'disambiguationspage' )->inContentLanguage()->text();
- $linkBatch = new LinkBatch;
-
- # If the text can be treated as a title, use it verbatim.
- # Otherwise, pull the titles from the links table
- $dp = Title::newFromText( $dMsgText );
- if( $dp ) {
- 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" );
- }
- $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' ),
- 'pl_title',
- array( 'page_id = pl_from',
- 'pl_namespace' => NS_TEMPLATE,
- 'page_namespace' => $disPageObj->getNamespace(),
- 'page_title' => $disPageObj->getDBkey()
- ), __METHOD__ );
-
- foreach ( $res as $row ) {
- $linkBatch->addObj( Title::makeTitle( NS_TEMPLATE, $row->pl_title ));
- }
- }
- $set = $linkBatch->constructSet( 'tl', $dbr );
-
- if( $set === false ) {
- # We must always return a valid SQL query, but this way
- # the DB will always quickly return an empty result
- $set = 'FALSE';
- wfDebug( "Mediawiki:disambiguationspage message does not link to any templates!\n" );
- }
- return $set;
- }
-
- function getQueryInfo() {
- // @todo FIXME: What are pagelinks and p2 doing here?
- return array (
- 'tables' => array(
- 'templatelinks',
- 'p1' => 'page',
- 'pagelinks',
- 'p2' => 'page'
- ),
- 'fields' => array(
- 'namespace' => 'p1.page_namespace',
- 'title' => 'p1.page_title',
- 'value' => 'pl_from'
- ),
- 'conds' => array(
- $this->getQueryFromLinkBatch(),
- 'p1.page_id = tl_from',
- 'pl_namespace = p1.page_namespace',
- 'pl_title = p1.page_title',
- 'p2.page_id = pl_from',
- 'p2.page_namespace' => MWNamespace::getContentNamespaces()
- )
- );
- }
-
- function getOrderFields() {
- return array( 'tl_namespace', 'tl_title', 'value' );
- }
-
- function sortDescending() {
- return false;
- }
-
- /**
- * Fetch links and cache their existence
- *
- * @param $db DatabaseBase
- * @param $res
- */
- function preprocessResults( $db, $res ) {
- if ( !$res->numRows() ) {
- return;
- }
-
- $batch = new LinkBatch;
- foreach ( $res as $row ) {
- $batch->add( $row->namespace, $row->title );
- }
- $batch->execute();
-
- $res->seek( 0 );
- }
-
- function formatResult( $skin, $result ) {
- $title = Title::newFromID( $result->value );
- $dp = Title::makeTitle( $result->namespace, $result->title );
-
- $from = Linker::link( $title );
- $edit = Linker::link(
- $title,
- $this->msg( 'parentheses', $this->msg( 'editlink' )->text() )->escaped(),
- array(),
- array( 'redirect' => 'no', 'action' => 'edit' )
- );
- $arr = $this->getLanguage()->getArrow();
- $to = Linker::link( $dp );
-
- return "$from $edit $arr $to";
- }
-
- protected function getGroupName() {
- return 'pages';
- }
-}
diff --git a/includes/specials/SpecialDoubleRedirects.php b/includes/specials/SpecialDoubleRedirects.php
index 5a5d749c..c364f70f 100644
--- a/includes/specials/SpecialDoubleRedirects.php
+++ b/includes/specials/SpecialDoubleRedirects.php
@@ -28,7 +28,6 @@
* @ingroup SpecialPage
*/
class DoubleRedirectsPage extends QueryPage {
-
function __construct( $name = 'DoubleRedirects' ) {
parent::__construct( $name );
}
@@ -52,8 +51,8 @@ class DoubleRedirectsPage extends QueryPage {
function reallyGetQueryInfo( $namespace = null, $title = null ) {
$limitToTitle = !( $namespace === null && $title === null );
$dbr = wfGetDB( DB_SLAVE );
- $retval = array (
- 'tables' => array (
+ $retval = array(
+ 'tables' => array(
'ra' => 'redirect',
'rb' => 'redirect',
'pa' => 'page',
@@ -91,10 +90,12 @@ class DoubleRedirectsPage extends QueryPage {
'rb.rd_from = pb.page_id',
)
);
+
if ( $limitToTitle ) {
$retval['conds']['pa.page_namespace'] = $namespace;
$retval['conds']['pa.page_title'] = $title;
}
+
return $retval;
}
@@ -103,9 +104,14 @@ class DoubleRedirectsPage extends QueryPage {
}
function getOrderFields() {
- return array ( 'ra.rd_namespace', 'ra.rd_title' );
+ return array( 'ra.rd_namespace', 'ra.rd_title' );
}
+ /**
+ * @param Skin $skin
+ * @param object $result Result row
+ * @return string
+ */
function formatResult( $skin, $result ) {
$titleA = Title::makeTitle( $result->namespace, $result->title );
@@ -116,10 +122,17 @@ class DoubleRedirectsPage extends QueryPage {
// 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'],
- $qi['conds'], __METHOD__ );
+ $qi = $this->reallyGetQueryInfo(
+ $result->namespace,
+ $result->title
+ );
+ $res = $dbr->select(
+ $qi['tables'],
+ $qi['fields'],
+ $qi['conds'],
+ __METHOD__
+ );
+
if ( $res ) {
$result = $dbr->fetchObject( $res );
}
@@ -160,7 +173,7 @@ class DoubleRedirectsPage extends QueryPage {
$lang = $this->getLanguage();
$arr = $lang->getArrow() . $lang->getDirMark();
- return( "{$linkA} {$edit} {$arr} {$linkB} {$arr} {$linkC}" );
+ return ( "{$linkA} {$edit} {$arr} {$linkB} {$arr} {$linkC}" );
}
protected function getGroupName() {
diff --git a/includes/specials/SpecialEditWatchlist.php b/includes/specials/SpecialEditWatchlist.php
index d2838e01..501552e9 100644
--- a/includes/specials/SpecialEditWatchlist.php
+++ b/includes/specials/SpecialEditWatchlist.php
@@ -35,7 +35,6 @@
* @author Rob Church <robchur@gmail.com>
*/
class SpecialEditWatchlist extends UnlistedSpecialPage {
-
/**
* Editing modes
*/
@@ -50,7 +49,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
private $badItems = array();
public function __construct() {
- parent::__construct( 'EditWatchlist' );
+ parent::__construct( 'EditWatchlist', 'editmywatchlist' );
}
/**
@@ -64,7 +63,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
$out = $this->getOutput();
# Anons don't get a watchlist
- if( $this->getUser()->isAnon() ) {
+ if ( $this->getUser()->isAnon() ) {
$out->setPageTitle( $this->msg( 'watchnologin' ) );
$llink = Linker::linkKnown(
SpecialPage::getTitleFor( 'Userlogin' ),
@@ -73,6 +72,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
array( 'returnto' => $this->getTitle()->getPrefixedText() )
);
$out->addHTML( $this->msg( 'watchlistanontext' )->rawParams( $llink )->parse() );
+
return;
}
@@ -81,20 +81,20 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
$this->outputHeader();
- $out->addSubtitle( $this->msg( 'watchlistfor2', $this->getUser()->getName()
- )->rawParams( SpecialEditWatchlist::buildTools( null ) ) );
+ $out->addSubtitle( $this->msg( 'watchlistfor2', $this->getUser()->getName() )
+ ->rawParams( SpecialEditWatchlist::buildTools( null ) ) );
# 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];
}
}
$mode = self::getMode( $this->getRequest(), $mode );
- switch( $mode ) {
+ switch ( $mode ) {
case self::EDIT_CLEAR:
// The "Clear" link scared people too much.
// Pass on to the raw editor, from which it's very easy to clear.
@@ -102,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' ) );
}
@@ -112,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 ) {
@@ -131,15 +131,17 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
*/
private function extractTitles( $list ) {
$list = explode( "\n", trim( $list ) );
- if( !is_array( $list ) ) {
+ if ( !is_array( $list ) ) {
return array();
}
+
$titles = array();
- foreach( $list as $text ) {
+
+ foreach ( $list as $text ) {
$text = trim( $text );
- if( strlen( $text ) > 0 ) {
+ if ( strlen( $text ) > 0 ) {
$title = Title::newFromText( $text );
- if( $title instanceof Title && $title->isWatchable() ) {
+ if ( $title instanceof Title && $title->isWatchable() ) {
$titles[] = $title;
}
}
@@ -148,9 +150,11 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
GenderCache::singleton()->doTitlesArray( $titles );
$list = array();
- foreach( $titles as $title ) {
+ /** @var Title $title */
+ foreach ( $titles as $title ) {
$list[] = $title->getPrefixedText();
}
+
return array_unique( $list );
}
@@ -158,44 +162,45 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
$wanted = $this->extractTitles( $data['Titles'] );
$current = $this->getWatchlist();
- if( count( $wanted ) > 0 ) {
+ if ( count( $wanted ) > 0 ) {
$toWatch = array_diff( $wanted, $current );
$toUnwatch = array_diff( $current, $wanted );
$this->watchTitles( $toWatch );
$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;
}
- if( count( $toWatch ) > 0 ) {
+ if ( count( $toWatch ) > 0 ) {
$this->successMessage .= ' ' . $this->msg( 'watchlistedit-raw-added'
- )->numParams( count( $toWatch ) )->parse();
+ )->numParams( count( $toWatch ) )->parse();
$this->showTitles( $toWatch, $this->successMessage );
}
- if( count( $toUnwatch ) > 0 ) {
+ if ( count( $toUnwatch ) > 0 ) {
$this->successMessage .= ' ' . $this->msg( 'watchlistedit-raw-removed'
- )->numParams( count( $toUnwatch ) )->parse();
+ )->numParams( count( $toUnwatch ) )->parse();
$this->showTitles( $toUnwatch, $this->successMessage );
}
} else {
$this->clearWatchlist();
$this->getUser()->invalidateCache();
- if( count( $current ) > 0 ) {
+ if ( count( $current ) > 0 ) {
$this->successMessage = $this->msg( 'watchlistedit-raw-done' )->parse();
} else {
return false;
}
- $this->successMessage .= ' ' . $this->msg( 'watchlistedit-raw-removed'
- )->numParams( count( $current ) )->parse();
+ $this->successMessage .= ' ' . $this->msg( 'watchlistedit-raw-removed' )
+ ->numParams( count( $current ) )->parse();
$this->showTitles( $current, $this->successMessage );
}
+
return true;
}
@@ -212,29 +217,35 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
$talk = $this->msg( 'talkpagelinktext' )->escaped();
// Do a batch existence check
$batch = new LinkBatch();
- foreach( $titles as $title ) {
- if( !$title instanceof Title ) {
+ foreach ( $titles as $title ) {
+ if ( !$title instanceof Title ) {
$title = Title::newFromText( $title );
}
- if( $title instanceof Title ) {
+
+ if ( $title instanceof Title ) {
$batch->addObj( $title );
$batch->addObj( $title->getTalkPage() );
}
}
+
$batch->execute();
+
// Print out the list
$output .= "<ul>\n";
- foreach( $titles as $title ) {
- if( !$title instanceof Title ) {
+
+ foreach ( $titles as $title ) {
+ if ( !$title instanceof Title ) {
$title = Title::newFromText( $title );
}
- if( $title instanceof Title ) {
+
+ if ( $title instanceof Title ) {
$output .= "<li>"
. Linker::link( $title )
. ' (' . Linker::link( $title->getTalkPage(), $talk )
. ")</li>\n";
}
}
+
$output .= "</ul>\n";
}
@@ -247,6 +258,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
private function getWatchlist() {
$list = array();
$dbr = wfGetDB( DB_MASTER );
+
$res = $dbr->select(
'watchlist',
array(
@@ -256,10 +268,12 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
),
__METHOD__
);
- if( $res->numRows() > 0 ) {
+
+ if ( $res->numRows() > 0 ) {
$titles = array();
foreach ( $res as $row ) {
$title = Title::makeTitleSafe( $row->wl_namespace, $row->wl_title );
+
if ( $this->checkTitle( $title, $row->wl_namespace, $row->wl_title )
&& !$title->isTalkPage()
) {
@@ -270,11 +284,13 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
GenderCache::singleton()->doTitlesArray( $titles );
- foreach( $titles as $title ) {
+ foreach ( $titles as $title ) {
$list[] = $title->getPrefixedText();
}
}
+
$this->cleanupWatchlist();
+
return $list;
}
@@ -297,6 +313,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
);
$lb = new LinkBatch();
+
foreach ( $res as $row ) {
$lb->add( $row->wl_namespace, $row->wl_title );
if ( !MWNamespace::isTalk( $row->wl_namespace ) ) {
@@ -305,6 +322,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
}
$lb->execute();
+
return $titles;
}
@@ -324,12 +342,14 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
) {
$title = false; // unrecoverable
}
+
if ( !$title
|| $title->getNamespace() != $namespace
|| $title->getDBkey() != $dbKey
) {
$this->badItems[] = array( $title, $namespace, $dbKey );
}
+
return (bool)$title;
}
@@ -337,16 +357,17 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
* Attempts to clean up broken items
*/
private function cleanupWatchlist() {
- if( !count( $this->badItems ) ) {
+ if ( !count( $this->badItems ) ) {
return; //nothing to do
}
+
$dbw = wfGetDB( DB_MASTER );
$user = $this->getUser();
+
foreach ( $this->badItems as $row ) {
list( $title, $namespace, $dbKey ) = $row;
- wfDebug( "User {$user->getName()} has broken watchlist item ns($namespace):$dbKey, "
- . ( $title ? 'cleaning up' : 'deleting' ) . ".\n"
- );
+ $action = $title ? 'cleaning up' : 'deleting';
+ wfDebug( "User {$user->getName()} has broken watchlist item ns($namespace):$dbKey, $action.\n" );
$dbw->delete( 'watchlist',
array(
@@ -387,11 +408,13 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
private function watchTitles( $titles ) {
$dbw = wfGetDB( DB_MASTER );
$rows = array();
- foreach( $titles as $title ) {
- if( !$title instanceof Title ) {
+
+ foreach ( $titles as $title ) {
+ if ( !$title instanceof Title ) {
$title = Title::newFromText( $title );
}
- if( $title instanceof Title ) {
+
+ if ( $title instanceof Title ) {
$rows[] = array(
'wl_user' => $this->getUser()->getId(),
'wl_namespace' => MWNamespace::getSubject( $title->getNamespace() ),
@@ -406,6 +429,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
);
}
}
+
$dbw->insert( 'watchlist', $rows, __METHOD__, 'IGNORE' );
}
@@ -419,11 +443,13 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
*/
private function unwatchTitles( $titles ) {
$dbw = wfGetDB( DB_MASTER );
- foreach( $titles as $title ) {
- if( !$title instanceof Title ) {
+
+ foreach ( $titles as $title ) {
+ if ( !$title instanceof Title ) {
$title = Title::newFromText( $title );
}
- if( $title instanceof Title ) {
+
+ if ( $title instanceof Title ) {
$dbw->delete(
'watchlist',
array(
@@ -433,6 +459,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
),
__METHOD__
);
+
$dbw->delete(
'watchlist',
array(
@@ -442,6 +469,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
),
__METHOD__
);
+
$page = WikiPage::factory( $title );
wfRunHooks( 'UnwatchArticleComplete', array( $this->getUser(), &$page ) );
}
@@ -451,15 +479,16 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
public function submitNormal( $data ) {
$removed = array();
- foreach( $data as $titles ) {
+ foreach ( $data as $titles ) {
$this->unwatchTitles( $titles );
$removed = array_merge( $removed, $titles );
}
- if( count( $removed ) > 0 ) {
+ if ( count( $removed ) > 0 ) {
$this->successMessage = $this->msg( 'watchlistedit-normal-done'
- )->numParams( count( $removed ) )->parse();
+ )->numParams( count( $removed ) )->parse();
$this->showTitles( $removed, $this->successMessage );
+
return true;
} else {
return false;
@@ -477,7 +506,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
$fields = array();
$count = 0;
- foreach( $this->getWatchlistInfo() as $namespace => $pages ) {
+ foreach ( $this->getWatchlistInfo() as $namespace => $pages ) {
if ( $namespace >= 0 ) {
$fields['TitlesNs' . $namespace] = array(
'class' => 'EditWatchlistCheckboxSeriesField',
@@ -486,8 +515,9 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
);
}
- 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] = $title->getPrefixedText();
@@ -500,30 +530,33 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
if ( count( $fields ) > 1 && $count > 30 ) {
$this->toc = Linker::tocIndent();
$tocLength = 0;
- foreach( $fields as $data ) {
+ foreach ( $fields as $data ) {
# strip out the 'ns' prefix from the section name:
$ns = substr( $data['section'], 2 );
- $nsText = ($ns == NS_MAIN)
+ $nsText = ( $ns == NS_MAIN )
? $this->msg( 'blanknamespace' )->escaped()
: htmlspecialchars( $wgContLang->getFormattedNsText( $ns ) );
$this->toc .= Linker::tocLine( "editwatchlist-{$data['section']}", $nsText,
$this->getLanguage()->formatNum( ++$tocLength ), 1 ) . Linker::tocLineEnd();
}
+
$this->toc = Linker::tocList( $this->toc );
} else {
$this->toc = false;
}
- $form = new EditWatchlistNormalHTMLForm( $fields, $this->getContext() );
- $form->setTitle( $this->getTitle() );
+ $context = new DerivativeContext( $this->getContext() );
+ $context->setTitle( $this->getTitle() ); // Remove subpage
+ $form = new EditWatchlistNormalHTMLForm( $fields, $context );
$form->setSubmitTextMsg( 'watchlistedit-normal-submit' );
# Used message keys: 'accesskey-watchlistedit-normal-submit', 'tooltip-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' ) );
+
return $form;
}
@@ -535,12 +568,15 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
*/
private function buildRemoveLine( $title ) {
$link = Linker::link( $title );
- if( $title->isRedirect() ) {
+
+ if ( $title->isRedirect() ) {
// Linker already makes class mw-redirect, so this is redundant
$link = '<span class="watchlistredir">' . $link . '</span>';
}
+
$tools[] = Linker::link( $title->getTalkPage(), $this->msg( 'talkpagelinktext' )->escaped() );
- if( $title->exists() ) {
+
+ if ( $title->exists() ) {
$tools[] = Linker::linkKnown(
$title,
$this->msg( 'history_short' )->escaped(),
@@ -548,7 +584,8 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
array( 'action' => 'history' )
);
}
- if( $title->getNamespace() == NS_USER && !$title->isSubpage() ) {
+
+ if ( $title->getNamespace() == NS_USER && !$title->isSubpage() ) {
$tools[] = Linker::linkKnown(
SpecialPage::getTitleFor( 'Contributions', $title->getText() ),
$this->msg( 'contributions' )->escaped()
@@ -574,14 +611,16 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
'default' => $titles,
),
);
- $form = new HTMLForm( $fields, $this->getContext() );
- $form->setTitle( $this->getTitle( 'raw' ) );
+ $context = new DerivativeContext( $this->getContext() );
+ $context->setTitle( $this->getTitle( 'raw' ) ); // Reset subpage
+ $form = new HTMLForm( $fields, $context );
$form->setSubmitTextMsg( 'watchlistedit-raw-submit' );
# Used message keys: 'accesskey-watchlistedit-raw-submit', 'tooltip-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' ) );
+
return $form;
}
@@ -595,19 +634,17 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
*/
public static function getMode( $request, $par ) {
$mode = strtolower( $request->getVal( 'action', $par ) );
- switch( $mode ) {
+
+ switch ( $mode ) {
case 'clear':
case self::EDIT_CLEAR:
return self::EDIT_CLEAR;
-
case 'raw':
case self::EDIT_RAW:
return self::EDIT_RAW;
-
case 'edit':
case self::EDIT_NORMAL:
return self::EDIT_NORMAL;
-
default:
return false;
}
@@ -629,21 +666,26 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
'edit' => array( 'EditWatchlist', false ),
'raw' => array( 'EditWatchlist', 'raw' ),
);
- foreach( $modes as $mode => $arr ) {
+
+ foreach ( $modes as $mode => $arr ) {
// can use messages 'watchlisttools-view', 'watchlisttools-edit', 'watchlisttools-raw'
$tools[] = Linker::linkKnown(
SpecialPage::getTitleFor( $arr[0], $arr[1] ),
wfMessage( "watchlisttools-{$mode}" )->escaped()
);
}
- return Html::rawElement( 'span',
- array( 'class' => 'mw-watchlist-toollinks' ),
- wfMessage( 'parentheses', $wgLang->pipeList( $tools ) )->text() );
+
+ return Html::rawElement(
+ 'span',
+ array( 'class' => 'mw-watchlist-toollinks' ),
+ wfMessage( 'parentheses', $wgLang->pipeList( $tools ) )->text()
+ );
}
}
# B/C since 1.18
-class WatchlistEditor extends SpecialEditWatchlist {}
+class WatchlistEditor extends SpecialEditWatchlist {
+}
/**
* Extend HTMLForm purely so we can have a more sane way of getting the section headers
@@ -651,10 +693,12 @@ class WatchlistEditor extends SpecialEditWatchlist {}
class EditWatchlistNormalHTMLForm extends HTMLForm {
public function getLegend( $namespace ) {
$namespace = substr( $namespace, 2 );
+
return $namespace == NS_MAIN
? $this->msg( 'blanknamespace' )->escaped()
: htmlspecialchars( $this->getContext()->getLanguage()->getFormattedNsText( $namespace ) );
}
+
public function getBody() {
return $this->displaySection( $this->mFieldTree, '', 'editwatchlist-' );
}
diff --git a/includes/specials/SpecialEmailuser.php b/includes/specials/SpecialEmailuser.php
index b5ad589e..2e90d996 100644
--- a/includes/specials/SpecialEmailuser.php
+++ b/includes/specials/SpecialEmailuser.php
@@ -29,13 +29,18 @@
class SpecialEmailUser extends UnlistedSpecialPage {
protected $mTarget;
+ /**
+ * @var User|string $mTargetObj
+ */
+ protected $mTargetObj;
+
public function __construct() {
parent::__construct( 'Emailuser' );
}
public function getDescription() {
$target = self::getTarget( $this->mTarget );
- if( !$target instanceof User ) {
+ if ( !$target instanceof User ) {
return $this->msg( 'emailuser-title-notarget' )->text();
}
@@ -106,7 +111,11 @@ class SpecialEmailUser extends UnlistedSpecialPage {
$this->outputHeader();
// error out if sending user cannot do this
- $error = self::getPermissionsError( $this->getUser(), $this->getRequest()->getVal( 'wpEditToken' ) );
+ $error = self::getPermissionsError(
+ $this->getUser(),
+ $this->getRequest()->getVal( 'wpEditToken' )
+ );
+
switch ( $error ) {
case null:
# Wahey!
@@ -119,43 +128,45 @@ class SpecialEmailUser extends UnlistedSpecialPage {
throw new ThrottledError;
case 'mailnologin':
case 'usermaildisabled':
- throw new ErrorPageError( $error, "{$error}text" );
+ throw new ErrorPageError( $error, "{$error}text" );
default:
# It's a hook error
list( $title, $msg, $params ) = $error;
- throw new ErrorPageError( $title, $msg, $params );
+ throw new ErrorPageError( $title, $msg, $params );
}
// Got a valid target user name? Else ask for one.
$ret = self::getTarget( $this->mTarget );
- if( !$ret instanceof User ) {
- if( $this->mTarget != '' ) {
+ if ( !$ret instanceof User ) {
+ if ( $this->mTarget != '' ) {
$ret = ( $ret == 'notarget' ) ? 'emailnotarget' : ( $ret . 'text' );
$out->wrapWikiMsg( "<p class='error'>$1</p>", $ret );
}
$out->addHTML( $this->userForm( $this->mTarget ) );
+
return false;
}
$this->mTargetObj = $ret;
- $form = new HTMLForm( $this->getFormFields(), $this->getContext() );
+ $context = new DerivativeContext( $this->getContext() );
+ $context->setTitle( $this->getTitle() ); // Remove subpage
+ $form = new HTMLForm( $this->getFormFields(), $context );
// 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' ) );
$form->setWrapperLegendMsg( 'email-legend' );
$form->loadData();
- if( !wfRunHooks( 'EmailUserForm', array( &$form ) ) ) {
+ if ( !wfRunHooks( 'EmailUserForm', array( &$form ) ) ) {
return false;
}
$result = $form->show();
- if( $result === true || ( $result instanceof Status && $result->isGood() ) ) {
+ if ( $result === true || ( $result instanceof Status && $result->isGood() ) ) {
$out->setPageTitle( $this->msg( 'emailsent' ) );
- $out->addWikiMsg( 'emailsenttext' );
+ $out->addWikiMsg( 'emailsenttext', $this->mTarget );
$out->returnToMain( false, $this->mTargetObj->getUserPage() );
}
}
@@ -169,18 +180,22 @@ class SpecialEmailUser extends UnlistedSpecialPage {
public static function getTarget( $target ) {
if ( $target == '' ) {
wfDebug( "Target is empty.\n" );
+
return 'notarget';
}
$nu = User::newFromName( $target );
- if( !$nu instanceof User || !$nu->getId() ) {
+ if ( !$nu instanceof User || !$nu->getId() ) {
wfDebug( "Target is invalid user.\n" );
+
return 'notarget';
} elseif ( !$nu->isEmailConfirmed() ) {
wfDebug( "User has no valid email.\n" );
+
return 'noemail';
} elseif ( !$nu->canReceiveEmail() ) {
wfDebug( "User does not allow user emails.\n" );
+
return 'nowikiemail';
}
@@ -196,31 +211,36 @@ class SpecialEmailUser extends UnlistedSpecialPage {
*/
public static function getPermissionsError( $user, $editToken ) {
global $wgEnableEmail, $wgEnableUserEmail;
- if( !$wgEnableEmail || !$wgEnableUserEmail ) {
+
+ if ( !$wgEnableEmail || !$wgEnableUserEmail ) {
return 'usermaildisabled';
}
- if( !$user->isAllowed( 'sendemail' ) ) {
+ if ( !$user->isAllowed( 'sendemail' ) ) {
return 'badaccess';
}
- if( !$user->isEmailConfirmed() ) {
+ if ( !$user->isEmailConfirmed() ) {
return 'mailnologin';
}
- if( $user->isBlockedFromEmailuser() ) {
+ if ( $user->isBlockedFromEmailuser() ) {
wfDebug( "User is blocked from sending e-mail.\n" );
+
return "blockedemailuser";
}
- if( $user->pingLimiter( 'emailuser' ) ) {
+ if ( $user->pingLimiter( 'emailuser' ) ) {
wfDebug( "Ping limiter triggered.\n" );
+
return 'actionthrottledtext';
}
$hookErr = false;
+
wfRunHooks( 'UserCanSendEmail', array( &$user, &$hookErr ) );
wfRunHooks( 'EmailUserPermissionsErrors', array( $user, $editToken, &$hookErr ) );
+
if ( $hookErr ) {
return $hookErr;
}
@@ -236,14 +256,25 @@ class SpecialEmailUser extends UnlistedSpecialPage {
*/
protected function userForm( $name ) {
global $wgScript;
- $string = Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript, 'id' => 'askusername' ) ) .
+ $string = Xml::openElement(
+ 'form',
+ array( 'method' => 'get', 'action' => $wgScript, 'id' => 'askusername' )
+ ) .
Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) .
Xml::openElement( 'fieldset' ) .
Html::rawElement( 'legend', null, $this->msg( 'emailtarget' )->parse() ) .
- Xml::inputLabel( $this->msg( 'emailusername' )->text(), 'target', 'emailusertarget', 30, $name ) . ' ' .
+ Xml::inputLabel(
+ $this->msg( 'emailusername' )->text(),
+ 'target',
+ 'emailusertarget',
+ 30,
+ $name
+ ) .
+ ' ' .
Xml::submitButton( $this->msg( 'emailusernamesubmit' )->text() ) .
Xml::closeElement( 'fieldset' ) .
Xml::closeElement( 'form' ) . "\n";
+
return $string;
}
@@ -264,6 +295,8 @@ class SpecialEmailUser extends UnlistedSpecialPage {
* getPermissionsError(). It is probably also a good
* idea to check the edit token and ping limiter in advance.
*
+ * @param array $data
+ * @param IContextSource $context
* @return Mixed: Status object, or potentially a String on error
* or maybe even true on success if anything uses the EmailUser hook.
*/
@@ -271,9 +304,10 @@ class SpecialEmailUser extends UnlistedSpecialPage {
global $wgUserEmailUseReplyTo;
$target = self::getTarget( $data['Target'] );
- if( !$target instanceof User ) {
+ if ( !$target instanceof User ) {
return $context->msg( $target . 'text' )->parseAsBlock();
}
+
$to = new MailAddress( $target );
$from = new MailAddress( $context->getUser() );
$subject = $data['Subject'];
@@ -285,11 +319,11 @@ class SpecialEmailUser extends UnlistedSpecialPage {
$from->name, $to->name )->inContentLanguage()->text();
$error = '';
- if( !wfRunHooks( 'EmailUser', array( &$to, &$from, &$subject, &$text, &$error ) ) ) {
+ if ( !wfRunHooks( 'EmailUser', array( &$to, &$from, &$subject, &$text, &$error ) ) ) {
return $error;
}
- if( $wgUserEmailUseReplyTo ) {
+ if ( $wgUserEmailUseReplyTo ) {
// Put the generic wiki autogenerated address in the From:
// header and reserve the user for Reply-To.
//
@@ -297,6 +331,7 @@ class SpecialEmailUser extends UnlistedSpecialPage {
// wiki-borne mails from direct mails and protects against
// SPF and bounce problems with some mailers (see below).
global $wgPasswordSender, $wgPasswordSenderName;
+
$mailFrom = new MailAddress( $wgPasswordSender, $wgPasswordSenderName );
$replyTo = $from;
} else {
@@ -319,7 +354,7 @@ class SpecialEmailUser extends UnlistedSpecialPage {
$status = UserMailer::send( $to, $mailFrom, $subject, $text, $replyTo );
- if( !$status->isGood() ) {
+ if ( !$status->isGood() ) {
return $status;
} else {
// if the user requested a copy of this mail, do this now,
@@ -334,6 +369,7 @@ class SpecialEmailUser extends UnlistedSpecialPage {
}
wfRunHooks( 'EmailUserComplete', array( $to, $from, $subject, $text ) );
+
return $status;
}
}
diff --git a/includes/specials/SpecialExport.php b/includes/specials/SpecialExport.php
index 7abfefe2..61ed34d4 100644
--- a/includes/specials/SpecialExport.php
+++ b/includes/specials/SpecialExport.php
@@ -29,7 +29,6 @@
* @ingroup SpecialPage
*/
class SpecialExport extends SpecialPage {
-
private $curonly, $doExport, $pageLinkDepth, $templates;
private $images;
@@ -75,8 +74,7 @@ class SpecialExport extends SpecialPage {
}
}
}
- }
- elseif( $request->getCheck( 'addns' ) && $wgExportFromNamespaces ) {
+ } elseif ( $request->getCheck( 'addns' ) && $wgExportFromNamespaces ) {
$page = $request->getText( 'pages' );
$nsindex = $request->getText( 'nsindex', '' );
@@ -89,8 +87,7 @@ class SpecialExport extends SpecialPage {
$page .= "\n" . implode( "\n", $nspages );
}
}
- }
- elseif( $request->getCheck( 'exportall' ) && $wgExportAllowAll ) {
+ } elseif ( $request->getCheck( 'exportall' ) && $wgExportAllowAll ) {
$this->doExport = true;
$exportall = true;
@@ -100,13 +97,12 @@ class SpecialExport extends SpecialPage {
doExport(...) further down) */
$page = '';
$history = '';
- }
- elseif( $request->wasPosted() && $par == '' ) {
+ } elseif ( $request->wasPosted() && $par == '' ) {
$page = $request->getText( 'pages' );
$this->curonly = $request->getCheck( 'curonly' );
$rawOffset = $request->getVal( 'offset' );
- if( $rawOffset ) {
+ if ( $rawOffset ) {
$offset = wfTimestamp( TS_MW, $rawOffset );
} else {
$offset = null;
@@ -127,15 +123,17 @@ class SpecialExport extends SpecialPage {
if ( $limit > 0 && ( $wgExportMaxHistory == 0 || $limit < $wgExportMaxHistory ) ) {
$history['limit'] = $limit;
}
+
if ( !is_null( $offset ) ) {
$history['offset'] = $offset;
}
+
if ( strtolower( $dir ) == 'desc' ) {
$history['dir'] = 'desc';
}
}
- if( $page != '' ) {
+ if ( $page != '' ) {
$this->doExport = true;
}
} else {
@@ -143,18 +141,18 @@ class SpecialExport extends SpecialPage {
$page = $request->getText( 'pages', $par );
$historyCheck = $request->getCheck( 'history' );
- if( $historyCheck ) {
+ if ( $historyCheck ) {
$history = WikiExporter::FULL;
} else {
$history = WikiExporter::CURRENT;
}
- if( $page != '' ) {
+ if ( $page != '' ) {
$this->doExport = true;
}
}
- if( !$wgExportAllowHistory ) {
+ if ( !$wgExportAllowHistory ) {
// Override
$history = WikiExporter::CURRENT;
}
@@ -172,7 +170,7 @@ class SpecialExport extends SpecialPage {
wfResetOutputBuffers();
$request->response()->header( "Content-type: application/xml; charset=utf-8" );
- if( $request->getCheck( 'wpDownload' ) ) {
+ if ( $request->getCheck( 'wpDownload' ) ) {
// Provide a sane filename suggestion
$filename = urlencode( $wgSitename . '-' . wfTimestampNow() . '.xml' );
$request->response()->header( "Content-disposition: attachment;filename={$filename}" );
@@ -187,9 +185,17 @@ class SpecialExport extends SpecialPage {
$out->addWikiMsg( 'exporttext' );
$form = Xml::openElement( 'form', array( 'method' => 'post',
- 'action' => $this->getTitle()->getLocalUrl( 'action=submit' ) ) );
- $form .= Xml::inputLabel( $this->msg( 'export-addcattext' )->text(), 'catname', 'catname', 40 ) . '&#160;';
- $form .= Xml::submitButton( $this->msg( 'export-addcat' )->text(), array( 'name' => 'addcat' ) ) . '<br />';
+ 'action' => $this->getTitle()->getLocalURL( 'action=submit' ) ) );
+ $form .= Xml::inputLabel(
+ $this->msg( 'export-addcattext' )->text(),
+ 'catname',
+ 'catname',
+ 40
+ ) . '&#160;';
+ $form .= Xml::submitButton(
+ $this->msg( 'export-addcat' )->text(),
+ array( 'name' => 'addcat' )
+ ) . '<br />';
if ( $wgExportFromNamespaces ) {
$form .= Html::namespaceSelector(
@@ -197,12 +203,15 @@ class SpecialExport extends SpecialPage {
'selected' => $nsindex,
'label' => $this->msg( 'export-addnstext' )->text()
), array(
- 'name' => 'nsindex',
- 'id' => 'namespace',
+ 'name' => 'nsindex',
+ 'id' => 'namespace',
'class' => 'namespaceselector',
)
) . '&#160;';
- $form .= Xml::submitButton( $this->msg( 'export-addns' )->text(), array( 'name' => 'addns' ) ) . '<br />';
+ $form .= Xml::submitButton(
+ $this->msg( 'export-addns' )->text(),
+ array( 'name' => 'addns' )
+ ) . '<br />';
}
if ( $wgExportAllowAll ) {
@@ -214,10 +223,15 @@ class SpecialExport extends SpecialPage {
) . '<br />';
}
- $form .= Xml::element( 'textarea', array( 'name' => 'pages', 'cols' => 40, 'rows' => 10 ), $page, false );
+ $form .= Xml::element(
+ 'textarea',
+ array( 'name' => 'pages', 'cols' => 40, 'rows' => 10 ),
+ $page,
+ false
+ );
$form .= '<br />';
- if( $wgExportAllowHistory ) {
+ if ( $wgExportAllowHistory ) {
$form .= Xml::checkLabel(
$this->msg( 'exportcuronly' )->text(),
'curonly',
@@ -235,9 +249,16 @@ class SpecialExport extends SpecialPage {
$request->wasPosted() ? $request->getCheck( 'templates' ) : false
) . '<br />';
- if( $wgExportMaxLinkDepth || $this->userCanOverrideExportDepth() ) {
- $form .= Xml::inputLabel( $this->msg( 'export-pagelinks' )->text(), 'pagelink-depth', 'pagelink-depth', 20, 0 ) . '<br />';
+ if ( $wgExportMaxLinkDepth || $this->userCanOverrideExportDepth() ) {
+ $form .= Xml::inputLabel(
+ $this->msg( 'export-pagelinks' )->text(),
+ 'pagelink-depth',
+ 'pagelink-depth',
+ 20,
+ 0
+ ) . '<br />';
}
+
// Enable this when we can do something useful exporting/importing image information. :)
//$form .= Xml::checkLabel( $this->msg( 'export-images' )->text(), 'images', 'wpExportImages', false ) . '<br />';
$form .= Xml::checkLabel(
@@ -256,7 +277,10 @@ class SpecialExport extends SpecialPage {
) . '<br />';
}
- $form .= Xml::submitButton( $this->msg( 'export-submit' )->text(), Linker::tooltipAndAccesskeyAttribs( 'export' ) );
+ $form .= Xml::submitButton(
+ $this->msg( 'export-submit' )->text(),
+ Linker::tooltipAndAccesskeyAttribs( 'export' )
+ );
$form .= Xml::closeElement( 'form' );
$out->addHTML( $form );
@@ -288,10 +312,10 @@ class SpecialExport extends SpecialPage {
$pageSet = array(); // Inverted index of all pages to look up
// Split up and normalize input
- foreach( explode( "\n", $page ) as $pageName ) {
+ foreach ( explode( "\n", $page ) as $pageName ) {
$pageName = trim( $pageName );
$title = Title::newFromText( $pageName );
- if( $title && $title->getInterwiki() == '' && $title->getText() !== '' ) {
+ if ( $title && $title->getInterwiki() == '' && $title->getText() !== '' ) {
// Only record each page once!
$pageSet[$title->getPrefixedText()] = true;
}
@@ -301,25 +325,23 @@ class SpecialExport extends SpecialPage {
$inputPages = array_keys( $pageSet );
// Look up any linked pages if asked...
- if( $this->templates ) {
+ if ( $this->templates ) {
$pageSet = $this->getTemplates( $inputPages, $pageSet );
}
$linkDepth = $this->pageLinkDepth;
- if( $linkDepth ) {
+ if ( $linkDepth ) {
$pageSet = $this->getPageLinks( $inputPages, $pageSet, $linkDepth );
}
- /*
- // Enable this when we can do something useful exporting/importing image information. :)
- if( $this->images ) ) {
- $pageSet = $this->getImages( $inputPages, $pageSet );
- }
- */
+ // Enable this when we can do something useful exporting/importing image information.
+ // if( $this->images ) ) {
+ // $pageSet = $this->getImages( $inputPages, $pageSet );
+ // }
$pages = array_keys( $pageSet );
// Normalize titles to the same format and remove dupes, see bug 17374
- foreach( $pages as $k => $v ) {
+ foreach ( $pages as $k => $v ) {
$pages[$k] = str_replace( " ", "_", $v );
}
@@ -327,7 +349,7 @@ class SpecialExport extends SpecialPage {
}
/* Ok, let's get to it... */
- if( $history == WikiExporter::CURRENT ) {
+ if ( $history == WikiExporter::CURRENT ) {
$lb = false;
$db = wfGetDB( DB_SLAVE );
$buffer = WikiExporter::BUFFER;
@@ -350,26 +372,17 @@ class SpecialExport extends SpecialPage {
if ( $exportall ) {
$exporter->allPages();
} else {
- foreach( $pages as $page ) {
- /*
- if( $wgExportMaxHistory && !$this->curonly ) {
- $title = Title::newFromText( $page );
- if( $title ) {
- $count = Revision::countByTitle( $db, $title );
- if( $count > $wgExportMaxHistory ) {
- wfDebug( __FUNCTION__ .
- ": Skipped $page, $count revisions too big\n" );
- continue;
- }
- }
- }*/
- #Bug 8824: Only export pages the user can read
+ foreach ( $pages as $page ) {
+ #Bug 8824: Only export pages the user can read
$title = Title::newFromText( $page );
- if( is_null( $title ) ) {
- continue; #TODO: perhaps output an <error> tag or something.
+ if ( is_null( $title ) ) {
+ // @todo Perhaps output an <error> tag or something.
+ continue;
}
- if( !$title->userCan( 'read', $this->getUser() ) ) {
- continue; #TODO: perhaps output an <error> tag or something.
+
+ if ( !$title->userCan( 'read', $this->getUser() ) ) {
+ // @todo Perhaps output an <error> tag or something.
+ continue;
}
$exporter->pageByTitle( $title );
@@ -378,7 +391,7 @@ class SpecialExport extends SpecialPage {
$exporter->closeStream();
- if( $lb ) {
+ if ( $lb ) {
$lb->closeAll();
}
}
@@ -412,6 +425,7 @@ class SpecialExport extends SpecialPage {
$pages[] = $n;
}
+
return $pages;
}
@@ -443,6 +457,7 @@ class SpecialExport extends SpecialPage {
$pages[] = $n;
}
+
return $pages;
}
@@ -468,12 +483,12 @@ class SpecialExport extends SpecialPage {
private function validateLinkDepth( $depth ) {
global $wgExportMaxLinkDepth;
- if( $depth < 0 ) {
+ if ( $depth < 0 ) {
return 0;
}
if ( !$this->userCanOverrideExportDepth() ) {
- if( $depth > $wgExportMaxLinkDepth ) {
+ if ( $depth > $wgExportMaxLinkDepth ) {
return $wgExportMaxLinkDepth;
}
}
@@ -483,6 +498,7 @@ class SpecialExport extends SpecialPage {
* crazy-big export from being done by someone setting the depth
* number too high. In other words, last resort safety net.
*/
+
return intval( min( $depth, 5 ) );
}
@@ -494,7 +510,7 @@ class SpecialExport extends SpecialPage {
* @return array
*/
private function getPageLinks( $inputPages, $pageSet, $depth ) {
- for( ; $depth > 0; --$depth ) {
+ for ( ; $depth > 0; --$depth ) {
$pageSet = $this->getLinks(
$inputPages, $pageSet, 'pagelinks',
array( 'namespace' => 'pl_namespace', 'title' => 'pl_title' ),
@@ -526,15 +542,20 @@ class SpecialExport extends SpecialPage {
/**
* Expand a list of pages to include items used in those pages.
+ * @param array $inputPages Array of page titles
+ * @param array $pageSet
+ * @param string $table
+ * @param array $fields Array of field names
+ * @param array $join
* @return array
*/
private function getLinks( $inputPages, $pageSet, $table, $fields, $join ) {
$dbr = wfGetDB( DB_SLAVE );
- foreach( $inputPages as $page ) {
+ foreach ( $inputPages as $page ) {
$title = Title::newFromText( $page );
- if( $title ) {
+ if ( $title ) {
$pageSet[$title->getPrefixedText()] = true;
/// @todo FIXME: May or may not be more efficient to batch these
/// by namespace when given multiple input pages.
@@ -551,7 +572,7 @@ class SpecialExport extends SpecialPage {
__METHOD__
);
- foreach( $result as $row ) {
+ foreach ( $result as $row ) {
$template = Title::makeTitle( $row->namespace, $row->title );
$pageSet[$template->getPrefixedText()] = true;
}
diff --git a/includes/specials/SpecialFewestrevisions.php b/includes/specials/SpecialFewestrevisions.php
index 5b7f353d..47a4d75f 100644
--- a/includes/specials/SpecialFewestrevisions.php
+++ b/includes/specials/SpecialFewestrevisions.php
@@ -28,7 +28,6 @@
* @author Martin Drashkov
*/
class FewestrevisionsPage extends QueryPage {
-
function __construct( $name = 'Fewestrevisions' ) {
parent::__construct( $name );
}
@@ -42,21 +41,26 @@ class FewestrevisionsPage extends QueryPage {
}
function getQueryInfo() {
- return array (
- 'tables' => array ( 'revision', 'page' ),
- 'fields' => array ( 'namespace' => 'page_namespace',
- 'title' => 'page_title',
- 'value' => 'COUNT(*)',
- 'redirect' => 'page_is_redirect' ),
- 'conds' => array ( 'page_namespace' => MWNamespace::getContentNamespaces(),
- 'page_id = rev_page' ),
- 'options' => array ( 'HAVING' => 'COUNT(*) > 1',
- // ^^^ This was probably here to weed out redirects.
- // Since we mark them as such now, it might be
- // useful to remove this. People _do_ create pages
- // and never revise them, they aren't necessarily
- // redirects.
- 'GROUP BY' => array( 'page_namespace', 'page_title', 'page_is_redirect' ) )
+ return array(
+ 'tables' => array( 'revision', 'page' ),
+ 'fields' => array(
+ 'namespace' => 'page_namespace',
+ 'title' => 'page_title',
+ 'value' => 'COUNT(*)',
+ 'redirect' => 'page_is_redirect'
+ ),
+ 'conds' => array(
+ 'page_namespace' => MWNamespace::getContentNamespaces(),
+ 'page_id = rev_page' ),
+ 'options' => array(
+ 'HAVING' => 'COUNT(*) > 1',
+ // ^^^ This was probably here to weed out redirects.
+ // Since we mark them as such now, it might be
+ // useful to remove this. People _do_ create pages
+ // and never revise them, they aren't necessarily
+ // redirects.
+ 'GROUP BY' => array( 'page_namespace', 'page_title', 'page_is_redirect' )
+ )
);
}
@@ -65,17 +69,24 @@ class FewestrevisionsPage extends QueryPage {
}
/**
- * @param $skin Skin object
- * @param $result Object: database row
+ * @param Skin $skin
+ * @param object $result Database row
* @return String
*/
function formatResult( $skin, $result ) {
global $wgContLang;
$nt = Title::makeTitleSafe( $result->namespace, $result->title );
- if( !$nt ) {
- return Html::element( 'span', array( 'class' => 'mw-invalidtitle' ),
- Linker::getInvalidTitleDescription( $this->getContext(), $result->namespace, $result->title ) );
+ if ( !$nt ) {
+ return Html::element(
+ 'span',
+ array( 'class' => 'mw-invalidtitle' ),
+ Linker::getInvalidTitleDescription(
+ $this->getContext(),
+ $result->namespace,
+ $result->title
+ )
+ );
}
$text = htmlspecialchars( $wgContLang->convert( $nt->getPrefixedText() ) );
diff --git a/includes/specials/SpecialFileDuplicateSearch.php b/includes/specials/SpecialFileDuplicateSearch.php
index 3fe64e6f..4c6593b2 100644
--- a/includes/specials/SpecialFileDuplicateSearch.php
+++ b/includes/specials/SpecialFileDuplicateSearch.php
@@ -59,7 +59,7 @@ class FileDuplicateSearchPage extends QueryPage {
/**
* Fetch dupes from all connected file repositories.
*
- * @return Array of File objects
+ * @return array of File objects
*/
function getDupes() {
return RepoGroup::singleton()->findBySha1( $this->hash );
@@ -101,11 +101,11 @@ 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 );
- if( $title && $title->getText() != '' ) {
+ if ( $title && $title->getText() != '' ) {
$this->file = wfFindFile( $title );
}
@@ -113,37 +113,46 @@ 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() ) .
- Xml::openElement( 'fieldset' ) .
- Xml::element( 'legend', null, $this->msg( 'fileduplicatesearch-legend' )->text() ) .
- Xml::inputLabel( $this->msg( 'fileduplicatesearch-filename' )->text(), 'filename', 'filename', 50, $this->filename ) . ' ' .
- Xml::submitButton( $this->msg( 'fileduplicatesearch-submit' )->text() ) .
- Xml::closeElement( 'fieldset' ) .
- Xml::closeElement( 'form' )
+ Html::openElement(
+ 'form',
+ array( 'id' => 'fileduplicatesearch', 'method' => 'get', 'action' => $wgScript )
+ ) . "\n" .
+ Html::hidden( 'title', $this->getTitle()->getPrefixedDBkey() ) . "\n" .
+ Html::openElement( 'fieldset' ) . "\n" .
+ Html::element( 'legend', null, $this->msg( 'fileduplicatesearch-legend' )->text() ) . "\n" .
+ Xml::inputLabel(
+ $this->msg( 'fileduplicatesearch-filename' )->text(),
+ 'filename',
+ 'filename',
+ 50,
+ $this->filename
+ ) . "\n" .
+ Xml::submitButton( $this->msg( 'fileduplicatesearch-submit' )->text() ) . "\n" .
+ Html::closeElement( 'fieldset' ) . "\n" .
+ Html::closeElement( 'form' )
);
- if( $this->file ) {
+ if ( $this->file ) {
$this->hash = $this->file->getSha1();
- } elseif( $this->filename !== '' ) {
+ } elseif ( $this->filename !== '' ) {
$out->wrapWikiMsg(
"<p class='mw-fileduplicatesearch-noresults'>\n$1\n</p>",
array( 'fileduplicatesearch-noresults', wfEscapeWikiText( $this->filename ) )
);
}
- if( $this->hash != '' ) {
+ if ( $this->hash != '' ) {
# Show a thumbnail of the file
$img = $this->file;
if ( $img ) {
$thumb = $img->transform( array( 'width' => 120, 'height' => 120 ) );
- if( $thumb ) {
+ if ( $thumb ) {
$out->addHTML( '<div id="mw-fileduplicatesearch-icon">' .
$thumb->toHtml( array( 'desc-link' => false ) ) . '<br />' .
$this->msg( 'fileduplicatesearch-info' )->numParams(
$img->getWidth(), $img->getHeight() )->params(
- $this->getLanguage()->formatSize( $img->getSize() ),
- $img->getMimeType() )->parseAsBlock() .
+ $this->getLanguage()->formatSize( $img->getSize() ),
+ $img->getMimeType() )->parseAsBlock() .
'</div>' );
}
}
@@ -152,7 +161,7 @@ class FileDuplicateSearchPage extends QueryPage {
$numRows = count( $dupes );
# Show a short summary
- if( $numRows == 1 ) {
+ if ( $numRows == 1 ) {
$out->wrapWikiMsg(
"<p class='mw-fileduplicatesearch-result-1'>\n$1\n</p>",
array( 'fileduplicatesearch-result-1', wfEscapeWikiText( $this->filename ) )
@@ -172,14 +181,16 @@ class FileDuplicateSearchPage extends QueryPage {
function doBatchLookups( $list ) {
$batch = new LinkBatch();
- foreach( $list as $file ) {
+ /** @var File $file */
+ foreach ( $list as $file ) {
$batch->addObj( $file->getTitle() );
- if( $file->isLocal() ) {
+ if ( $file->isLocal() ) {
$userName = $file->getUser( 'text' );
$batch->add( NS_USER, $userName );
$batch->add( NS_USER_TALK, $userName );
}
}
+
$batch->execute();
}
diff --git a/includes/specials/SpecialFilepath.php b/includes/specials/SpecialFilepath.php
index bbcced26..e7ced52a 100644
--- a/includes/specials/SpecialFilepath.php
+++ b/includes/specials/SpecialFilepath.php
@@ -2,6 +2,7 @@
/**
* Implements Special:Filepath
*
+ * @section LICENSE
* 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
@@ -26,65 +27,16 @@
*
* @ingroup SpecialPage
*/
-class SpecialFilepath extends SpecialPage {
-
+class SpecialFilepath extends RedirectSpecialPage {
function __construct() {
parent::__construct( 'Filepath' );
+ $this->mAllowedRedirectParams = array( 'width', 'height' );
}
- function execute( $par ) {
- $this->setHeaders();
- $this->outputHeader();
-
- $request = $this->getRequest();
- $file = !is_null( $par ) ? $par : $request->getText( 'file' );
-
- $title = Title::newFromText( $file, NS_FILE );
-
- if ( ! $title instanceof Title || $title->getNamespace() != NS_FILE ) {
- $this->showForm( $title );
- } else {
- $file = wfFindFile( $title );
-
- if ( $file && $file->exists() ) {
- // Default behavior: Use the direct link to the file.
- $url = $file->getURL();
- $width = $request->getInt( 'width', -1 );
- $height = $request->getInt( 'height', -1 );
-
- // If a width is requested...
- if ( $width != -1 ) {
- $mto = $file->transform( array( 'width' => $width, 'height' => $height ) );
- // ... and we can
- if ( $mto && !$mto->isError() ) {
- // ... change the URL to point to a thumbnail.
- $url = $mto->getURL();
- }
- }
- $this->getOutput()->redirect( $url );
- } else {
- $this->getOutput()->setStatusCode( 404 );
- $this->showForm( $title );
- }
- }
- }
-
- /**
- * @param $title Title
- */
- function showForm( $title ) {
- global $wgScript;
-
- $this->getOutput()->addHTML(
- Html::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript, 'id' => 'specialfilepath' ) ) .
- Html::openElement( 'fieldset' ) .
- Html::element( 'legend', null, $this->msg( 'filepath' )->text() ) .
- Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) .
- Xml::inputLabel( $this->msg( 'filepath-page' )->text(), 'file', 'file', 25, is_object( $title ) ? $title->getText() : '' ) . ' ' .
- Xml::submitButton( $this->msg( 'filepath-submit' )->text() ) . "\n" .
- Html::closeElement( 'fieldset' ) .
- Html::closeElement( 'form' )
- );
+ // implement by redirecting through Special:Redirect/file
+ function getRedirect( $par ) {
+ $file = $par ?: $this->getRequest()->getText( 'file' );
+ return SpecialPage::getSafeTitleFor( 'Redirect', 'file/' . $file );
}
protected function getGroupName() {
diff --git a/includes/specials/SpecialImport.php b/includes/specials/SpecialImport.php
index aa56041b..d7d860de 100644
--- a/includes/specials/SpecialImport.php
+++ b/includes/specials/SpecialImport.php
@@ -30,12 +30,11 @@
* @ingroup SpecialPage
*/
class SpecialImport extends SpecialPage {
-
private $interwiki = false;
private $namespace;
private $rootpage = '';
private $frompage = '';
- private $logcomment= false;
+ private $logcomment = false;
private $history = true;
private $includeTemplates = false;
private $pageLinkDepth;
@@ -108,13 +107,13 @@ class SpecialImport extends SpecialPage {
$source = Status::newFatal( 'import-token-mismatch' );
} elseif ( $sourceName == 'upload' ) {
$isUpload = true;
- if( $user->isAllowed( 'importupload' ) ) {
+ if ( $user->isAllowed( 'importupload' ) ) {
$source = ImportStreamSource::newFromUpload( "xmlimport" );
} else {
throw new PermissionsError( 'importupload' );
}
} elseif ( $sourceName == "interwiki" ) {
- if( !$user->isAllowed( 'import' ) ) {
+ if ( !$user->isAllowed( 'import' ) ) {
throw new PermissionsError( 'import' );
}
$this->interwiki = $request->getVal( 'interwiki' );
@@ -136,24 +135,40 @@ class SpecialImport extends SpecialPage {
}
$out = $this->getOutput();
- if( !$source->isGood() ) {
- $out->wrapWikiMsg( "<p class=\"error\">\n$1\n</p>", array( 'importfailed', $source->getWikiText() ) );
+ if ( !$source->isGood() ) {
+ $out->wrapWikiMsg(
+ "<p class=\"error\">\n$1\n</p>",
+ array( 'importfailed', $source->getWikiText() )
+ );
} else {
$importer = new WikiImporter( $source->value );
- if( !is_null( $this->namespace ) ) {
+ if ( !is_null( $this->namespace ) ) {
$importer->setTargetNamespace( $this->namespace );
}
- if( !is_null( $this->rootpage ) ) {
+ if ( !is_null( $this->rootpage ) ) {
$statusRootPage = $importer->setTargetRootPage( $this->rootpage );
- if( !$statusRootPage->isGood() ) {
- $out->wrapWikiMsg( "<p class=\"error\">\n$1\n</p>", array( 'import-options-wrong', $statusRootPage->getWikiText(), count( $statusRootPage->getErrorsArray() ) ) );
+ if ( !$statusRootPage->isGood() ) {
+ $out->wrapWikiMsg(
+ "<p class=\"error\">\n$1\n</p>",
+ array(
+ 'import-options-wrong',
+ $statusRootPage->getWikiText(),
+ count( $statusRootPage->getErrorsArray() )
+ )
+ );
+
return;
}
}
$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;
@@ -167,10 +182,16 @@ class SpecialImport extends SpecialPage {
if ( $exception ) {
# No source or XML parse error
- $out->wrapWikiMsg( "<p class=\"error\">\n$1\n</p>", array( 'importfailed', $exception->getMessage() ) );
- } elseif( !$result->isGood() ) {
+ $out->wrapWikiMsg(
+ "<p class=\"error\">\n$1\n</p>",
+ array( 'importfailed', $exception->getMessage() )
+ );
+ } elseif ( !$result->isGood() ) {
# Zero revisions
- $out->wrapWikiMsg( "<p class=\"error\">\n$1\n</p>", array( 'importfailed', $result->getWikiText() ) );
+ $out->wrapWikiMsg(
+ "<p class=\"error\">\n$1\n</p>",
+ array( 'importfailed', $result->getWikiText() )
+ );
} else {
# Success!
$out->addWikiMsg( 'importsuccess' );
@@ -182,162 +203,195 @@ class SpecialImport extends SpecialPage {
private function showForm() {
global $wgImportSources, $wgExportMaxLinkDepth;
- $action = $this->getTitle()->getLocalUrl( array( 'action' => 'submit' ) );
+ $action = $this->getTitle()->getLocalURL( array( 'action' => 'submit' ) );
$user = $this->getUser();
$out = $this->getOutput();
- if( $user->isAllowed( 'importupload' ) ) {
+ if ( $user->isAllowed( 'importupload' ) ) {
$out->addHTML(
- Xml::fieldset( $this->msg( 'import-upload' )->text() ).
- Xml::openElement( 'form', array( 'enctype' => 'multipart/form-data', 'method' => 'post',
- 'action' => $action, 'id' => 'mw-import-upload-form' ) ) .
- $this->msg( 'importtext' )->parseAsBlock() .
- Html::hidden( 'action', 'submit' ) .
- Html::hidden( 'source', 'upload' ) .
- Xml::openElement( 'table', array( 'id' => 'mw-import-table-upload' ) ) .
-
- "<tr>
+ Xml::fieldset( $this->msg( 'import-upload' )->text() ) .
+ Xml::openElement(
+ 'form',
+ array(
+ 'enctype' => 'multipart/form-data',
+ 'method' => 'post',
+ 'action' => $action,
+ 'id' => 'mw-import-upload-form'
+ )
+ ) .
+ $this->msg( 'importtext' )->parseAsBlock() .
+ Html::hidden( 'action', 'submit' ) .
+ Html::hidden( 'source', 'upload' ) .
+ Xml::openElement( 'table', array( 'id' => 'mw-import-table-upload' ) ) .
+ "<tr>
<td class='mw-label'>" .
- Xml::label( $this->msg( 'import-upload-filename' )->text(), 'xmlimport' ) .
+ Xml::label( $this->msg( 'import-upload-filename' )->text(), 'xmlimport' ) .
"</td>
<td class='mw-input'>" .
- Html::input( 'xmlimport', '', 'file', array( 'id' => 'xmlimport' ) ) . ' ' .
+ Html::input( 'xmlimport', '', 'file', array( 'id' => 'xmlimport' ) ) . ' ' .
"</td>
</tr>
<tr>
<td class='mw-label'>" .
- Xml::label( $this->msg( 'import-comment' )->text(), 'mw-import-comment' ) .
+ Xml::label( $this->msg( 'import-comment' )->text(), 'mw-import-comment' ) .
"</td>
<td class='mw-input'>" .
- Xml::input( 'log-comment', 50, '',
- array( 'id' => 'mw-import-comment', 'type' => 'text' ) ) . ' ' .
+ Xml::input( 'log-comment', 50, '',
+ array( 'id' => 'mw-import-comment', 'type' => 'text' ) ) . ' ' .
"</td>
</tr>
<tr>
<td class='mw-label'>" .
- Xml::label( $this->msg( 'import-interwiki-rootpage' )->text(), 'mw-interwiki-rootpage-upload' ) .
+ 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-upload', 'type' => 'text' ) ) . ' ' .
+ Xml::input( 'rootpage', 50, $this->rootpage,
+ array( 'id' => 'mw-interwiki-rootpage-upload', 'type' => 'text' ) ) . ' ' .
"</td>
</tr>
<tr>
<td></td>
<td class='mw-submit'>" .
- Xml::submitButton( $this->msg( 'uploadbtn' )->text() ) .
+ Xml::submitButton( $this->msg( 'uploadbtn' )->text() ) .
"</td>
</tr>" .
- Xml::closeElement( 'table' ).
- Html::hidden( 'editToken', $user->getEditToken() ) .
- Xml::closeElement( 'form' ) .
- Xml::closeElement( 'fieldset' )
+ Xml::closeElement( 'table' ) .
+ Html::hidden( 'editToken', $user->getEditToken() ) .
+ Xml::closeElement( 'form' ) .
+ Xml::closeElement( 'fieldset' )
);
} else {
- if( empty( $wgImportSources ) ) {
+ if ( empty( $wgImportSources ) ) {
$out->addWikiMsg( 'importnosources' );
}
}
- if( $user->isAllowed( 'import' ) && !empty( $wgImportSources ) ) {
+ if ( $user->isAllowed( 'import' ) && !empty( $wgImportSources ) ) {
# Show input field for import depth only if $wgExportMaxLinkDepth > 0
$importDepth = '';
- if( $wgExportMaxLinkDepth > 0 ) {
+ if ( $wgExportMaxLinkDepth > 0 ) {
$importDepth = "<tr>
<td class='mw-label'>" .
- $this->msg( 'export-pagelinks' )->parse() .
- "</td>
+ $this->msg( 'export-pagelinks' )->parse() .
+ "</td>
<td class='mw-input'>" .
- Xml::input( 'pagelink-depth', 3, 0 ) .
- "</td>
- </tr>";
+ Xml::input( 'pagelink-depth', 3, 0 ) .
+ "</td>
+ </tr>";
}
$out->addHTML(
Xml::fieldset( $this->msg( 'importinterwiki' )->text() ) .
- Xml::openElement( 'form', array( 'method' => 'post', 'action' => $action, 'id' => 'mw-import-interwiki-form' ) ) .
- $this->msg( 'import-interwiki-text' )->parseAsBlock() .
- Html::hidden( 'action', 'submit' ) .
- Html::hidden( 'source', 'interwiki' ) .
- Html::hidden( 'editToken', $user->getEditToken() ) .
- Xml::openElement( 'table', array( 'id' => 'mw-import-table-interwiki' ) ) .
- "<tr>
+ Xml::openElement(
+ 'form',
+ array(
+ 'method' => 'post',
+ 'action' => $action,
+ 'id' => 'mw-import-interwiki-form'
+ )
+ ) .
+ $this->msg( 'import-interwiki-text' )->parseAsBlock() .
+ Html::hidden( 'action', 'submit' ) .
+ Html::hidden( 'source', 'interwiki' ) .
+ Html::hidden( 'editToken', $user->getEditToken() ) .
+ Xml::openElement( 'table', array( 'id' => 'mw-import-table-interwiki' ) ) .
+ "<tr>
<td class='mw-label'>" .
- Xml::label( $this->msg( 'import-interwiki-source' )->text(), 'interwiki' ) .
+ Xml::label( $this->msg( 'import-interwiki-source' )->text(), 'interwiki' ) .
"</td>
<td class='mw-input'>" .
- Xml::openElement( 'select', array( 'name' => 'interwiki', 'id' => 'interwiki' ) )
+ Xml::openElement(
+ 'select',
+ array( 'name' => 'interwiki', 'id' => 'interwiki' )
+ )
);
- foreach( $wgImportSources as $prefix ) {
+
+ foreach ( $wgImportSources as $prefix ) {
$selected = ( $this->interwiki === $prefix ) ? ' selected="selected"' : '';
$out->addHTML( Xml::option( $prefix, $prefix, $selected ) );
}
$out->addHTML(
- Xml::closeElement( 'select' ) .
- Xml::input( 'frompage', 50, $this->frompage, array( 'id' => 'frompage' ) ) .
+ Xml::closeElement( 'select' ) .
+ Xml::input( 'frompage', 50, $this->frompage, array( 'id' => 'frompage' ) ) .
"</td>
</tr>
<tr>
<td>
</td>
<td class='mw-input'>" .
- Xml::checkLabel( $this->msg( 'import-interwiki-history' )->text(), 'interwikiHistory', 'interwikiHistory', $this->history ) .
+ Xml::checkLabel(
+ $this->msg( 'import-interwiki-history' )->text(),
+ 'interwikiHistory',
+ 'interwikiHistory',
+ $this->history
+ ) .
"</td>
</tr>
<tr>
<td>
</td>
<td class='mw-input'>" .
- Xml::checkLabel( $this->msg( 'import-interwiki-templates' )->text(), 'interwikiTemplates', 'interwikiTemplates', $this->includeTemplates ) .
+ Xml::checkLabel(
+ $this->msg( 'import-interwiki-templates' )->text(),
+ 'interwikiTemplates',
+ 'interwikiTemplates',
+ $this->includeTemplates
+ ) .
"</td>
</tr>
$importDepth
<tr>
<td class='mw-label'>" .
- Xml::label( $this->msg( 'import-interwiki-namespace' )->text(), 'namespace' ) .
+ Xml::label( $this->msg( 'import-interwiki-namespace' )->text(), 'namespace' ) .
"</td>
<td class='mw-input'>" .
- Html::namespaceSelector(
- array(
- 'selected' => $this->namespace,
- 'all' => '',
- ), array(
- 'name' => 'namespace',
- 'id' => 'namespace',
- 'class' => 'namespaceselector',
- )
- ) .
+ Html::namespaceSelector(
+ array(
+ 'selected' => $this->namespace,
+ 'all' => '',
+ ), array(
+ 'name' => 'namespace',
+ 'id' => 'namespace',
+ 'class' => 'namespaceselector',
+ )
+ ) .
"</td>
</tr>
<tr>
<td class='mw-label'>" .
- Xml::label( $this->msg( 'import-comment' )->text(), 'mw-interwiki-comment' ) .
+ Xml::label( $this->msg( 'import-comment' )->text(), 'mw-interwiki-comment' ) .
"</td>
<td class='mw-input'>" .
- Xml::input( 'log-comment', 50, '',
- array( 'id' => 'mw-interwiki-comment', 'type' => 'text' ) ) . ' ' .
+ Xml::input( 'log-comment', 50, '',
+ array( 'id' => 'mw-interwiki-comment', 'type' => 'text' ) ) . ' ' .
"</td>
</tr>
<tr>
<td class='mw-label'>" .
- Xml::label( $this->msg( 'import-interwiki-rootpage' )->text(), 'mw-interwiki-rootpage-interwiki' ) .
+ 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-interwiki', 'type' => 'text' ) ) . ' ' .
+ Xml::input( 'rootpage', 50, $this->rootpage,
+ array( 'id' => 'mw-interwiki-rootpage-interwiki', 'type' => 'text' ) ) . ' ' .
"</td>
</tr>
<tr>
<td>
</td>
<td class='mw-submit'>" .
- Xml::submitButton( $this->msg( 'import-interwiki-submit' )->text(), Linker::tooltipAndAccesskeyAttribs( 'import' ) ) .
+ Xml::submitButton(
+ $this->msg( 'import-interwiki-submit' )->text(),
+ Linker::tooltipAndAccesskeyAttribs( 'import' )
+ ) .
"</td>
</tr>" .
- Xml::closeElement( 'table' ).
- Xml::closeElement( 'form' ) .
- Xml::closeElement( 'fieldset' )
+ Xml::closeElement( 'table' ) .
+ Xml::closeElement( 'form' ) .
+ Xml::closeElement( 'fieldset' )
);
}
}
@@ -352,14 +406,21 @@ class SpecialImport extends SpecialPage {
* @ingroup SpecialPage
*/
class ImportReporter extends ContextSource {
- private $reason=false;
+ private $reason = false;
private $mOriginalLogCallback = null;
private $mOriginalPageOutCallback = null;
private $mLogItemCount = 0;
+
+ /**
+ * @param WikiImporter $importer
+ * @param $upload
+ * @param $interwiki
+ * @param string|bool $reason
+ */
function __construct( $importer, $upload, $interwiki, $reason = false ) {
$this->mOriginalPageOutCallback =
- $importer->setPageOutCallback( array( $this, 'reportPage' ) );
+ $importer->setPageOutCallback( array( $this, 'reportPage' ) );
$this->mOriginalLogCallback =
$importer->setLogItemCallback( array( $this, 'reportLogItem' ) );
$importer->setNoticeCallback( array( $this, 'reportNotice' ) );
@@ -403,20 +464,21 @@ class ImportReporter extends ContextSource {
$this->mPageCount++;
- if( $successCount > 0 ) {
- $this->getOutput()->addHTML( "<li>" . Linker::linkKnown( $title ) . " " .
- $this->msg( 'import-revision-count' )->numParams( $successCount )->escaped() .
- "</li>\n"
+ if ( $successCount > 0 ) {
+ $this->getOutput()->addHTML(
+ "<li>" . Linker::linkKnown( $title ) . " " .
+ $this->msg( 'import-revision-count' )->numParams( $successCount )->escaped() .
+ "</li>\n"
);
$log = new LogPage( 'import' );
- if( $this->mIsUpload ) {
+ if ( $this->mIsUpload ) {
$detail = $this->msg( 'import-logentry-upload-detail' )->numParams(
$successCount )->inContentLanguage()->text();
if ( $this->reason ) {
$detail .= $this->msg( 'colon-separator' )->inContentLanguage()->text() . $this->reason;
}
- $log->addEntry( 'upload', $title, $detail );
+ $log->addEntry( 'upload', $title, $detail, array(), $this->getUser() );
} else {
$interwiki = '[[:' . $this->mInterwiki . ':' .
$origTitle->getPrefixedText() . ']]';
@@ -425,7 +487,7 @@ class ImportReporter extends ContextSource {
if ( $this->reason ) {
$detail .= $this->msg( 'colon-separator' )->inContentLanguage()->text() . $this->reason;
}
- $log->addEntry( 'interwiki', $title, $detail );
+ $log->addEntry( 'interwiki', $title, $detail, array(), $this->getUser() );
}
$comment = $detail; // quick
@@ -450,8 +512,9 @@ class ImportReporter extends ContextSource {
if ( $this->mLogItemCount > 0 ) {
$msg = $this->msg( 'imported-log-entries' )->numParams( $this->mLogItemCount )->parse();
$out->addHTML( Xml::tags( 'li', null, $msg ) );
- } elseif( $this->mPageCount == 0 && $this->mLogItemCount == 0 ) {
+ } elseif ( $this->mPageCount == 0 && $this->mLogItemCount == 0 ) {
$out->addHTML( "</ul>\n" );
+
return Status::newFatal( 'importnopages' );
}
$out->addHTML( "</ul>\n" );
diff --git a/includes/specials/SpecialJavaScriptTest.php b/includes/specials/SpecialJavaScriptTest.php
index d204d50c..7069d527 100644
--- a/includes/specials/SpecialJavaScriptTest.php
+++ b/includes/specials/SpecialJavaScriptTest.php
@@ -55,24 +55,29 @@ class SpecialJavaScriptTest extends SpecialPage {
if ( $par == '' ) {
$out->setPageTitle( $this->msg( 'javascripttest' ) );
$summary = $this->wrapSummaryHtml(
- $this->msg( 'javascripttest-pagetext-noframework' )->escaped() . $this->getFrameworkListHtml(),
+ $this->msg( 'javascripttest-pagetext-noframework' )->escaped() .
+ $this->getFrameworkListHtml(),
'noframework'
);
$out->addHtml( $summary );
-
- // Matched! Display proper title and initialize the framework
} elseif ( isset( self::$frameworks[$framework] ) ) {
- $out->setPageTitle( $this->msg( 'javascripttest-title', $this->msg( "javascripttest-$framework-name" )->plain() ) );
- $out->setSubtitle( $this->msg( 'javascripttest-backlink' )->rawParams( Linker::linkKnown( $this->getTitle() ) ) );
+ // Matched! Display proper title and initialize the framework
+ $out->setPageTitle( $this->msg(
+ 'javascripttest-title',
+ // Messages: javascripttest-qunit-name
+ $this->msg( "javascripttest-$framework-name" )->plain()
+ ) );
+ $out->setSubtitle( $this->msg( 'javascripttest-backlink' )
+ ->rawParams( Linker::linkKnown( $this->getTitle() ) ) );
$this->{self::$frameworks[$framework]}();
-
- // Framework not found, display error
} else {
+ // Framework not found, display error
$out->setPageTitle( $this->msg( 'javascripttest' ) );
- $summary = $this->wrapSummaryHtml( '<p class="error">'
- . $this->msg( 'javascripttest-pagetext-unknownframework', $par )->escaped()
- . '</p>'
- . $this->getFrameworkListHtml(),
+ $summary = $this->wrapSummaryHtml(
+ '<p class="error">' .
+ $this->msg( 'javascripttest-pagetext-unknownframework', $par )->escaped() .
+ '</p>' .
+ $this->getFrameworkListHtml(),
'unknownframework'
);
$out->addHtml( $summary );
@@ -80,22 +85,28 @@ class SpecialJavaScriptTest extends SpecialPage {
}
/**
- * Get a list of frameworks (including introduction paragraph and links to the framework run pages)
- * @return String: HTML
+ * Get a list of frameworks (including introduction paragraph and links
+ * to the framework run pages)
+ *
+ * @return string HTML
*/
private function getFrameworkListHtml() {
$list = '<ul>';
- foreach( self::$frameworks as $framework => $initFn ) {
+ foreach ( self::$frameworks as $framework => $initFn ) {
$list .= Html::rawElement(
'li',
array(),
- Linker::link( $this->getTitle( $framework ), $this->msg( "javascripttest-$framework-name" )->escaped() )
+ Linker::link(
+ $this->getTitle( $framework ),
+ // Message: javascripttest-qunit-name
+ $this->msg( "javascripttest-$framework-name" )->escaped()
+ )
);
}
$list .= '</ul>';
- $msg = $this->msg( 'javascripttest-pagetext-frameworks' )->rawParams( $list )->parseAsBlock();
- return $msg;
+ return $this->msg( 'javascripttest-pagetext-frameworks' )->rawParams( $list )
+ ->parseAsBlock();
}
/**
@@ -109,12 +120,14 @@ class SpecialJavaScriptTest extends SpecialPage {
*/
private function wrapSummaryHtml( $html, $state ) {
$validStates = array( 'noframework', 'unknownframework', 'frameworkfound' );
- if( !in_array( $state, $validStates ) ) {
+
+ if ( !in_array( $state, $validStates ) ) {
throw new MWException( __METHOD__
. ' given an invalid state. Must be one of "'
- . join( '", "', $validStates) . '".'
+ . join( '", "', $validStates ) . '".'
);
}
+
return "<div id=\"mw-javascripttest-summary\" class=\"mw-javascripttest-$state\">$html</div>";
}
@@ -155,7 +168,10 @@ HTML;
// Used in ./tests/qunit/data/testrunner.js, see also documentation of
// $wgJavaScriptTestConfig in DefaultSettings.php
- $out->addJsConfigVars( 'QUnitTestSwarmInjectJSPath', $wgJavaScriptTestConfig['qunit']['testswarm-injectjs'] );
+ $out->addJsConfigVars(
+ 'QUnitTestSwarmInjectJSPath',
+ $wgJavaScriptTestConfig['qunit']['testswarm-injectjs']
+ );
}
protected function getGroupName() {
diff --git a/includes/specials/SpecialLinkSearch.php b/includes/specials/SpecialLinkSearch.php
index 030416fb..5b0c56e5 100644
--- a/includes/specials/SpecialLinkSearch.php
+++ b/includes/specials/SpecialLinkSearch.php
@@ -55,7 +55,7 @@ class LinkSearchPage extends QueryPage {
$namespace = $request->getIntorNull( 'namespace', null );
$protocols_list = array();
- foreach( $wgUrlProtocols as $prot ) {
+ foreach ( $wgUrlProtocols as $prot ) {
if ( $prot !== '//' ) {
$protocols_list[] = $prot;
}
@@ -72,7 +72,7 @@ class LinkSearchPage extends QueryPage {
} elseif ( !$pr_sl && $pr_cl ) {
// For protocols without '//' like 'mailto:'
$protocol = substr( $target2, 0, $pr_cl + 1 );
- $target2 = substr( $target2, $pr_cl+1 );
+ $target2 = substr( $target2, $pr_cl + 1 );
} elseif ( $protocol == '' && $target2 != '' ) {
// default
$protocol = 'http://';
@@ -88,11 +88,21 @@ class LinkSearchPage extends QueryPage {
'<nowiki>' . $this->getLanguage()->commaList( $protocols_list ) . '</nowiki>',
count( $protocols_list )
);
- $s = Html::openElement( 'form', array( 'id' => 'mw-linksearch-form', 'method' => 'get', 'action' => $wgScript ) ) . "\n" .
+ $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";
+ Xml::inputLabel(
+ $this->msg( 'linksearch-pat' )->text(),
+ 'target',
+ 'target',
+ 50,
+ $target
+ ) . "\n";
+
if ( !$wgMiserMode ) {
$s .= Html::namespaceSelector(
array(
@@ -100,25 +110,27 @@ class LinkSearchPage extends QueryPage {
'all' => '',
'label' => $this->msg( 'linksearch-ns' )->text()
), array(
- 'name' => 'namespace',
- 'id' => 'namespace',
+ 'name' => 'namespace',
+ 'id' => 'namespace',
'class' => 'namespaceselector',
)
);
}
+
$s .= Xml::submitButton( $this->msg( 'linksearch-ok' )->text() ) . "\n" .
Html::closeElement( 'fieldset' ) . "\n" .
Html::closeElement( 'form' ) . "\n";
$out->addHTML( $s );
- if( $target != '' ) {
+ if ( $target != '' ) {
$this->setParams( array(
'query' => $target2,
'namespace' => $namespace,
'protocol' => $protocol ) );
parent::execute( $par );
- if( $this->mMungedQuery === false )
+ if ( $this->mMungedQuery === false ) {
$out->addWikiMsg( 'linksearch-error' );
+ }
}
}
@@ -133,6 +145,8 @@ class LinkSearchPage extends QueryPage {
/**
* Return an appropriately formatted LIKE query and the clause
*
+ * @param string $query
+ * @param string $prot
* @return array
*/
static function mungeQuery( $query, $prot ) {
@@ -140,12 +154,14 @@ class LinkSearchPage extends QueryPage {
$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 ) ) {
+ $pattern = '/^(:?[0-9]{1,3}\.)+\*\s*$|^(:?[0-9]{1,3}\.){3}[0-9]{1,3}:[0-9]*\*\s*$/';
+ if ( preg_match( $pattern, $query ) ) {
$dbr = wfGetDB( DB_SLAVE );
$rv = array( $prot . rtrim( $query, " \t*" ), $dbr->anyString() );
$field = 'el_to';
}
}
+
return array( $rv, $field );
}
@@ -153,9 +169,10 @@ class LinkSearchPage extends QueryPage {
global $wgMiserMode;
$params = array();
$params['target'] = $this->mProt . $this->mQuery;
- if( isset( $this->mNs ) && !$wgMiserMode ) {
+ if ( isset( $this->mNs ) && !$wgMiserMode ) {
$params['namespace'] = $this->mNs;
}
+
return $params;
}
@@ -164,29 +181,41 @@ class LinkSearchPage extends QueryPage {
$dbr = wfGetDB( DB_SLAVE );
// strip everything past first wildcard, so that
// index-based-only lookup would be done
- list( $this->mMungedQuery, $clause ) = self::mungeQuery(
- $this->mQuery, $this->mProt );
- if( $this->mMungedQuery === false )
+ list( $this->mMungedQuery, $clause ) = self::mungeQuery( $this->mQuery, $this->mProt );
+ if ( $this->mMungedQuery === false ) {
// Invalid query; return no results
return array( 'tables' => 'page', 'fields' => 'page_id', 'conds' => '0=1' );
+ }
$stripped = LinkFilter::keepOneWildcard( $this->mMungedQuery );
$like = $dbr->buildLike( $stripped );
- $retval = array (
- 'tables' => array ( 'page', 'externallinks' ),
- 'fields' => array ( 'namespace' => 'page_namespace',
- 'title' => 'page_title',
- 'value' => 'el_index', 'url' => 'el_to' ),
- 'conds' => array ( 'page_id = el_from',
- "$clause $like" ),
+ $retval = array(
+ 'tables' => array( 'page', 'externallinks' ),
+ 'fields' => array(
+ 'namespace' => 'page_namespace',
+ 'title' => 'page_title',
+ 'value' => 'el_index',
+ 'url' => 'el_to'
+ ),
+ 'conds' => array(
+ 'page_id = el_from',
+ "$clause $like"
+ ),
'options' => array( 'USE INDEX' => $clause )
);
+
if ( isset( $this->mNs ) && !$wgMiserMode ) {
$retval['conds']['page_namespace'] = $this->mNs;
}
+
return $retval;
}
+ /**
+ * @param Skin $skin
+ * @param object $result Result row
+ * @return string
+ */
function formatResult( $skin, $result ) {
$title = Title::makeTitle( $result->namespace, $result->title );
$url = $result->url;
@@ -201,7 +230,7 @@ class LinkSearchPage extends QueryPage {
*/
function doQuery( $offset = false, $limit = false ) {
list( $this->mMungedQuery, ) = LinkSearchPage::mungeQuery( $this->mQuery, $this->mProt );
- if( $this->mMungedQuery === false ) {
+ if ( $this->mMungedQuery === false ) {
$this->getOutput()->addWikiMsg( 'linksearch-error' );
} else {
// For debugging
diff --git a/includes/specials/SpecialListfiles.php b/includes/specials/SpecialListfiles.php
index c864ae2a..dff1cf70 100644
--- a/includes/specials/SpecialListfiles.php
+++ b/includes/specials/SpecialListfiles.php
@@ -22,7 +22,6 @@
*/
class SpecialListFiles extends IncludableSpecialPage {
-
public function __construct() {
parent::__construct( 'Listfiles' );
}
@@ -37,9 +36,16 @@ class SpecialListFiles extends IncludableSpecialPage {
} else {
$userName = $this->getRequest()->getText( 'user', $par );
$search = $this->getRequest()->getText( 'ilsearch', '' );
+ $showAll = $this->getRequest()->getBool( 'ilshowall', false );
}
- $pager = new ImageListPager( $this->getContext(), $userName, $search, $this->including() );
+ $pager = new ImageListPager(
+ $this->getContext(),
+ $userName,
+ $search,
+ $this->including(),
+ $showAll
+ );
if ( $this->including() ) {
$html = $pager->getBody();
@@ -62,27 +68,33 @@ class SpecialListFiles extends IncludableSpecialPage {
*/
class ImageListPager extends TablePager {
var $mFieldNames = null;
+ // Subclasses should override buildQueryConds instead of using $mQueryConds variable.
var $mQueryConds = array();
var $mUserName = null;
var $mSearch = '';
var $mIncluding = false;
+ var $mShowAll = false;
+ var $mTableName = 'image';
- function __construct( IContextSource $context, $userName = null, $search = '', $including = false ) {
+ function __construct( IContextSource $context, $userName = null, $search = '',
+ $including = false, $showAll = false
+ ) {
global $wgMiserMode;
$this->mIncluding = $including;
+ $this->mShowAll = $showAll;
if ( $userName ) {
$nt = Title::newFromText( $userName, NS_USER );
if ( !is_null( $nt ) ) {
$this->mUserName = $nt->getText();
- $this->mQueryConds['img_user_text'] = $this->mUserName;
}
}
- if ( $search != '' && !$wgMiserMode ) {
+ if ( $search !== '' && !$wgMiserMode ) {
$this->mSearch = $search;
$nt = Title::newFromURL( $this->mSearch );
+
if ( $nt ) {
$dbr = wfGetDB( DB_SLAVE );
$this->mQueryConds[] = 'LOWER(img_name)' .
@@ -105,6 +117,42 @@ class ImageListPager extends TablePager {
}
/**
+ * Build the where clause of the query.
+ *
+ * Replaces the older mQueryConds member variable.
+ * @param $table String Either "image" or "oldimage"
+ * @return array The query conditions.
+ */
+ protected function buildQueryConds( $table ) {
+ $prefix = $table === 'image' ? 'img' : 'oi';
+ $conds = array();
+
+ if ( !is_null( $this->mUserName ) ) {
+ $conds[ $prefix . '_user_text' ] = $this->mUserName;
+ }
+
+ if ( $this->mSearch !== '' ) {
+ $nt = Title::newFromURL( $this->mSearch );
+ if ( $nt ) {
+ $dbr = wfGetDB( DB_SLAVE );
+ $conds[] = 'LOWER(' . $prefix . '_name)' .
+ $dbr->buildLike( $dbr->anyString(),
+ strtolower( $nt->getDBkey() ), $dbr->anyString() );
+ }
+ }
+
+ if ( $table === 'oldimage' ) {
+ // Don't want to deal with revdel.
+ // Future fixme: Show partial information as appropriate.
+ // Would have to be careful about filtering by username when username is deleted.
+ $conds['oi_deleted'] = 0;
+ }
+
+ // Add mQueryConds in case anyone was subclassing and using the old variable.
+ return $conds + $this->mQueryConds;
+ }
+
+ /**
* @return Array
*/
function getFieldNames() {
@@ -118,34 +166,94 @@ class ImageListPager extends TablePager {
'img_user_text' => $this->msg( 'listfiles_user' )->text(),
'img_description' => $this->msg( 'listfiles_description' )->text(),
);
- if( !$wgMiserMode ) {
+ if ( !$wgMiserMode && !$this->mShowAll ) {
$this->mFieldNames['count'] = $this->msg( 'listfiles_count' )->text();
}
+ if ( $this->mShowAll ) {
+ $this->mFieldNames['top'] = $this->msg( 'listfiles-latestversion' )->text();
+ }
}
+
return $this->mFieldNames;
}
function isFieldSortable( $field ) {
+ global $wgMiserMode;
if ( $this->mIncluding ) {
return false;
}
- static $sortable = array( 'img_timestamp', 'img_name' );
- if ( $field == 'img_size' ) {
- # No index for both img_size and img_user_text
- return !isset( $this->mQueryConds['img_user_text'] );
+ $sortable = array( 'img_timestamp', 'img_name', 'img_size' );
+ /* For reference, the indicies we can use for sorting are:
+ * On the image table: img_usertext_timestamp, img_size, img_timestamp
+ * On oldimage: oi_usertext_timestamp, oi_name_timestamp
+ *
+ * In particular that means we cannot sort by timestamp when not filtering
+ * by user and including old images in the results. Which is sad.
+ */
+ if ( $wgMiserMode && !is_null( $this->mUserName ) ) {
+ // If we're sorting by user, the index only supports sorting by time.
+ if ( $field === 'img_timestamp' ) {
+ return true;
+ } else {
+ return false;
+ }
+ } elseif ( $wgMiserMode && $this->mShowAll /* && mUserName === null */ ) {
+ // no oi_timestamp index, so only alphabetical sorting in this case.
+ if ( $field === 'img_name' ) {
+ return true;
+ } else {
+ return false;
+ }
}
+
return in_array( $field, $sortable );
}
function getQueryInfo() {
- $tables = array( 'image' );
+ // Hacky Hacky Hacky - I want to get query info
+ // for two different tables, without reimplementing
+ // the pager class.
+ $qi = $this->getQueryInfoReal( $this->mTableName );
+ return $qi;
+ }
+
+ /**
+ * Actually get the query info.
+ *
+ * This is to allow displaying both stuff from image and oldimage table.
+ *
+ * This is a bit hacky.
+ *
+ * @param $table String Either 'image' or 'oldimage'
+ * @return array Query info
+ */
+ protected function getQueryInfoReal( $table ) {
+ $prefix = $table === 'oldimage' ? 'oi' : 'img';
+
+ $tables = array( $table );
$fields = array_keys( $this->getFieldNames() );
- $fields[] = 'img_user';
- $fields[array_search( 'thumb', $fields )] = 'img_name AS thumb';
+
+ if ( $table === 'oldimage' ) {
+ foreach ( $fields as $id => &$field ) {
+ if ( substr( $field, 0, 4 ) !== 'img_' ) {
+ continue;
+ }
+ $field = $prefix . substr( $field, 3 ) . ' AS ' . $field;
+ }
+ $fields[array_search('top', $fields)] = "'no' AS top";
+ } else {
+ if ( $this->mShowAll ) {
+ $fields[array_search( 'top', $fields )] = "'yes' AS top";
+ }
+ }
+ $fields[] = $prefix . '_user AS img_user';
+ $fields[array_search( 'thumb', $fields )] = $prefix . '_name AS thumb';
+
$options = $join_conds = array();
# Depends on $wgMiserMode
- if( isset( $this->mFieldNames['count'] ) ) {
+ # Will also not happen if mShowAll is true.
+ if ( isset( $this->mFieldNames['count'] ) ) {
$tables[] = 'oldimage';
# Need to rewrite this one
@@ -157,7 +265,7 @@ class ImageListPager extends TablePager {
unset( $field );
$dbr = wfGetDB( DB_SLAVE );
- if( $dbr->implicitGroupby() ) {
+ if ( $dbr->implicitGroupby() ) {
$options = array( 'GROUP BY' => 'img_name' );
} else {
$columnlist = preg_grep( '/^img/', array_keys( $this->getFieldNames() ) );
@@ -165,17 +273,107 @@ class ImageListPager extends TablePager {
}
$join_conds = array( 'oldimage' => array( 'LEFT JOIN', 'oi_name = img_name' ) );
}
+
return array(
- 'tables' => $tables,
- 'fields' => $fields,
- 'conds' => $this->mQueryConds,
- 'options' => $options,
+ 'tables' => $tables,
+ 'fields' => $fields,
+ 'conds' => $this->buildQueryConds( $table ),
+ 'options' => $options,
'join_conds' => $join_conds
);
}
+ /**
+ * Override reallyDoQuery to mix together two queries.
+ *
+ * @note $asc is named $descending in IndexPager base class. However
+ * it is true when the order is ascending, and false when the order
+ * is descending, so I renamed it to $asc here.
+ */
+ function reallyDoQuery( $offset, $limit, $asc ) {
+ $prevTableName = $this->mTableName;
+ $this->mTableName = 'image';
+ list( $tables, $fields, $conds, $fname, $options, $join_conds ) = $this->buildQueryInfo( $offset, $limit, $asc );
+ $imageRes = $this->mDb->select( $tables, $fields, $conds, $fname, $options, $join_conds );
+ $this->mTableName = $prevTableName;
+
+ if ( !$this->mShowAll ) {
+ return $imageRes;
+ }
+
+ $this->mTableName = 'oldimage';
+
+ # Hacky...
+ $oldIndex = $this->mIndexField;
+ if ( substr( $this->mIndexField, 0, 4 ) !== 'img_' ) {
+ throw new MWException( "Expected to be sorting on an image table field" );
+ }
+ $this->mIndexField = 'oi_' . substr( $this->mIndexField, 4 );
+
+ list( $tables, $fields, $conds, $fname, $options, $join_conds ) = $this->buildQueryInfo( $offset, $limit, $asc );
+ $oldimageRes = $this->mDb->select( $tables, $fields, $conds, $fname, $options, $join_conds );
+
+ $this->mTableName = $prevTableName;
+ $this->mIndexField = $oldIndex;
+
+ return $this->combineResult( $imageRes, $oldimageRes, $limit, $asc );
+ }
+
+ /**
+ * Combine results from 2 tables.
+ *
+ * Note: This will throw away some results
+ *
+ * @param $res1 ResultWrapper
+ * @param $res2 ResultWrapper
+ * @param $limit int
+ * @param $ascending boolean See note about $asc in $this->reallyDoQuery
+ * @return FakeResultWrapper $res1 and $res2 combined
+ */
+ protected function combineResult( $res1, $res2, $limit, $ascending ) {
+ $res1->rewind();
+ $res2->rewind();
+ $topRes1 = $res1->next();
+ $topRes2 = $res2->next();
+ $resultArray = array();
+ for ( $i = 0; $i < $limit && $topRes1 && $topRes2; $i++ ) {
+ if ( strcmp( $topRes1->{ $this->mIndexField }, $topRes2->{ $this->mIndexField } ) > 0 ) {
+ if ( !$ascending ) {
+ $resultArray[] = $topRes1;
+ $topRes1 = $res1->next();
+ } else {
+ $resultArray[] = $topRes2;
+ $topRes2 = $res2->next();
+ }
+ } else {
+ if ( !$ascending ) {
+ $resultArray[] = $topRes2;
+ $topRes2 = $res2->next();
+ } else {
+ $resultArray[] = $topRes1;
+ $topRes1 = $res1->next();
+ }
+ }
+ }
+ for ( ; $i < $limit && $topRes1; $i++ ) {
+ $resultArray[] = $topRes1;
+ $topRes1 = $res1->next();
+ }
+ for ( ; $i < $limit && $topRes2; $i++ ) {
+ $resultArray[] = $topRes2;
+ $topRes2 = $res2->next();
+ }
+ return new FakeResultWrapper( $resultArray );
+ }
+
function getDefaultSort() {
- return 'img_timestamp';
+ global $wgMiserMode;
+ if ( $this->mShowAll && $wgMiserMode && is_null( $this->mUserName ) ) {
+ // Unfortunately no index on oi_timestamp.
+ return 'img_name';
+ } else {
+ return 'img_timestamp';
+ }
}
function doBatchLookups() {
@@ -191,24 +389,37 @@ class ImageListPager extends TablePager {
function formatValue( $field, $value ) {
switch ( $field ) {
case 'thumb':
- $file = wfLocalFile( $value );
- $thumb = $file->transform( array( 'width' => 180, 'height' => 360 ) );
- return $thumb->toHtml( array( 'desc-link' => true ) );
+ $opt = array( 'time' => $this->mCurrentRow->img_timestamp );
+ $file = RepoGroup::singleton()->getLocalRepo()->findFile( $value, $opt );
+ // If statement for paranoia
+ if ( $file ) {
+ $thumb = $file->transform( array( 'width' => 180, 'height' => 360 ) );
+ return $thumb->toHtml( array( 'desc-link' => true ) );
+ } else {
+ return htmlspecialchars( $value );
+ }
case 'img_timestamp':
+ // We may want to make this a link to the "old" version when displaying old files
return htmlspecialchars( $this->getLanguage()->userTimeAndDate( $value, $this->getUser() ) );
case 'img_name':
static $imgfile = null;
- if ( $imgfile === null ) $imgfile = $this->msg( 'imgfile' )->text();
+ if ( $imgfile === null ) {
+ $imgfile = $this->msg( 'imgfile' )->text();
+ }
// Weird files can maybe exist? Bug 22227
$filePage = Title::makeTitleSafe( NS_FILE, $value );
- if( $filePage ) {
- $link = Linker::linkKnown( $filePage, htmlspecialchars( $filePage->getText() ) );
+ if ( $filePage ) {
+ $link = Linker::linkKnown(
+ $filePage,
+ htmlspecialchars( $filePage->getText() )
+ );
$download = Xml::element( 'a',
array( 'href' => wfLocalFile( $filePage )->getURL() ),
$imgfile
);
$download = $this->msg( 'parentheses' )->rawParams( $download )->escaped();
+
return "$link $download";
} else {
return htmlspecialchars( $value );
@@ -223,6 +434,7 @@ class ImageListPager extends TablePager {
} else {
$link = htmlspecialchars( $value );
}
+
return $link;
case 'img_size':
return htmlspecialchars( $this->getLanguage()->formatSize( $value ) );
@@ -230,32 +442,47 @@ class ImageListPager extends TablePager {
return Linker::formatComment( $value );
case 'count':
return intval( $value ) + 1;
+ case 'top':
+ // Messages: listfiles-latestversion-yes, listfiles-latestversion-no
+ return $this->msg( 'listfiles-latestversion-' . $value );
}
}
function getForm() {
global $wgScript, $wgMiserMode;
$inputForm = array();
- $inputForm['table_pager_limit_label'] = $this->getLimitSelect();
+ $inputForm['table_pager_limit_label'] = $this->getLimitSelect( array( 'tabindex' => 1 ) );
if ( !$wgMiserMode ) {
- $inputForm['listfiles_search_for'] = Html::input( 'ilsearch', $this->mSearch, 'text',
+ $inputForm['listfiles_search_for'] = Html::input(
+ 'ilsearch',
+ $this->mSearch,
+ 'text',
array(
- 'size' => '40',
+ 'size' => '40',
'maxlength' => '255',
- 'id' => 'mw-ilsearch',
- ) );
+ 'id' => 'mw-ilsearch',
+ 'tabindex' => 2,
+ )
+ );
}
$inputForm['username'] = Html::input( 'user', $this->mUserName, 'text', array(
- 'size' => '40',
+ 'size' => '40',
'maxlength' => '255',
- 'id' => 'mw-listfiles-user',
+ 'id' => 'mw-listfiles-user',
+ 'tabindex' => 3,
+ ) );
+
+ $inputForm['listfiles-show-all'] = Html::input( 'ilshowall', 1, 'checkbox', array(
+ 'checked' => $this->mShowAll,
+ 'tabindex' => 4,
) );
return Html::openElement( 'form',
- array( 'method' => 'get', 'action' => $wgScript, 'id' => 'mw-listfiles-form' ) ) .
+ array( 'method' => 'get', 'action' => $wgScript, 'id' => 'mw-listfiles-form' )
+ ) .
Xml::fieldset( $this->msg( 'listfiles' )->text() ) .
Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) .
- Xml::buildForm( $inputForm, 'table_pager_limit_submit' ) .
- $this->getHiddenFields( array( 'limit', 'ilsearch', 'user', 'title' ) ) .
+ Xml::buildForm( $inputForm, 'table_pager_limit_submit', array( 'tabindex' => 5 ) ) .
+ $this->getHiddenFields( array( 'limit', 'ilsearch', 'user', 'title', 'ilshowall' ) ) .
Html::closeElement( 'fieldset' ) .
Html::closeElement( 'form' ) . "\n";
}
@@ -280,6 +507,7 @@ class ImageListPager extends TablePager {
$query['user'] = $this->mUserName;
}
}
+
return $queries;
}
@@ -288,6 +516,7 @@ class ImageListPager extends TablePager {
if ( !isset( $queries['user'] ) && !is_null( $this->mUserName ) ) {
$queries['user'] = $this->mUserName;
}
+
return $queries;
}
diff --git a/includes/specials/SpecialListgrouprights.php b/includes/specials/SpecialListgrouprights.php
index 7cccf887..82a4f70f 100644
--- a/includes/specials/SpecialListgrouprights.php
+++ b/includes/specials/SpecialListgrouprights.php
@@ -29,7 +29,6 @@
* @author Petr Kadlec <mormegil@centrum.cz>
*/
class SpecialListGroupRights extends SpecialPage {
-
/**
* Constructor
*/
@@ -51,11 +50,13 @@ class SpecialListGroupRights extends SpecialPage {
$out = $this->getOutput();
$out->addModuleStyles( 'mediawiki.special' );
+ $out->wrapWikiMsg( "<div class=\"mw-listgrouprights-key\">\n$1\n</div>", 'listgrouprights-key' );
+
$out->addHTML(
Xml::openElement( 'table', array( 'class' => 'wikitable mw-listgrouprights-table' ) ) .
'<tr>' .
- Xml::element( 'th', null, $this->msg( 'listgrouprights-group' )->text() ) .
- Xml::element( 'th', null, $this->msg( 'listgrouprights-rights' )->text() ) .
+ Xml::element( 'th', null, $this->msg( 'listgrouprights-group' )->text() ) .
+ Xml::element( 'th', null, $this->msg( 'listgrouprights-rights' )->text() ) .
'</tr>'
);
@@ -85,7 +86,7 @@ class SpecialListGroupRights extends SpecialPage {
$msg->text() :
MWNamespace::getCanonicalName( NS_PROJECT ) . ':' . $groupname;
- if( $group == '*' ) {
+ if ( $group == '*' ) {
// Do not make a link for the generic * group
$grouppage = htmlspecialchars( $groupnameLocalized );
} else {
@@ -124,16 +125,13 @@ class SpecialListGroupRights extends SpecialPage {
"
<td>$grouppage$grouplink</td>
<td>" .
- $this->formatPermissions( $permissions, $revoke, $addgroups, $removegroups,
- $addgroupsSelf, $removegroupsSelf ) .
+ $this->formatPermissions( $permissions, $revoke, $addgroups, $removegroups,
+ $addgroupsSelf, $removegroupsSelf ) .
'</td>
'
) );
}
- $out->addHTML(
- Xml::closeElement( 'table' ) . "\n<br /><hr />\n"
- );
- $out->wrapWikiMsg( "<div class=\"mw-listgrouprights-key\">\n$1\n</div>", 'listgrouprights-key' );
+ $out->addHTML( Xml::closeElement( 'table' ) );
}
/**
@@ -149,9 +147,9 @@ class SpecialListGroupRights extends SpecialPage {
*/
private function formatPermissions( $permissions, $revoke, $add, $remove, $addSelf, $removeSelf ) {
$r = array();
- foreach( $permissions as $permission => $granted ) {
+ foreach ( $permissions as $permission => $granted ) {
//show as granted only if it isn't revoked to prevent duplicate display of permissions
- if( $granted && ( !isset( $revoke[$permission] ) || !$revoke[$permission] ) ) {
+ if ( $granted && ( !isset( $revoke[$permission] ) || !$revoke[$permission] ) ) {
$description = $this->msg( 'listgrouprights-right-display',
User::getRightDescription( $permission ),
'<span class="mw-listgrouprights-right-name">' . $permission . '</span>'
@@ -159,8 +157,8 @@ class SpecialListGroupRights extends SpecialPage {
$r[] = $description;
}
}
- foreach( $revoke as $permission => $revoked ) {
- if( $revoked ) {
+ foreach ( $revoke as $permission => $revoked ) {
+ if ( $revoked ) {
$description = $this->msg( 'listgrouprights-right-revoked',
User::getRightDescription( $permission ),
'<span class="mw-listgrouprights-right-name">' . $permission . '</span>'
@@ -168,45 +166,52 @@ class SpecialListGroupRights extends SpecialPage {
$r[] = $description;
}
}
+
sort( $r );
+
$lang = $this->getLanguage();
- if( $add === true ) {
+
+ if ( $add === true ) {
$r[] = $this->msg( 'listgrouprights-addgroup-all' )->escaped();
- } elseif( is_array( $add ) && count( $add ) ) {
+ } elseif ( is_array( $add ) && count( $add ) ) {
$add = array_values( array_unique( $add ) );
$r[] = $this->msg( 'listgrouprights-addgroup',
$lang->listToText( array_map( array( 'User', 'makeGroupLinkWiki' ), $add ) ),
count( $add )
)->parse();
}
- if( $remove === true ) {
+
+ if ( $remove === true ) {
$r[] = $this->msg( 'listgrouprights-removegroup-all' )->escaped();
- } elseif( is_array( $remove ) && count( $remove ) ) {
+ } elseif ( is_array( $remove ) && count( $remove ) ) {
$remove = array_values( array_unique( $remove ) );
$r[] = $this->msg( 'listgrouprights-removegroup',
$lang->listToText( array_map( array( 'User', 'makeGroupLinkWiki' ), $remove ) ),
count( $remove )
)->parse();
}
- if( $addSelf === true ) {
+
+ if ( $addSelf === true ) {
$r[] = $this->msg( 'listgrouprights-addgroup-self-all' )->escaped();
- } elseif( is_array( $addSelf ) && count( $addSelf ) ) {
+ } elseif ( is_array( $addSelf ) && count( $addSelf ) ) {
$addSelf = array_values( array_unique( $addSelf ) );
$r[] = $this->msg( 'listgrouprights-addgroup-self',
$lang->listToText( array_map( array( 'User', 'makeGroupLinkWiki' ), $addSelf ) ),
count( $addSelf )
)->parse();
}
- if( $removeSelf === true ) {
+
+ if ( $removeSelf === true ) {
$r[] = $this->msg( 'listgrouprights-removegroup-self-all' )->parse();
- } elseif( is_array( $removeSelf ) && count( $removeSelf ) ) {
+ } elseif ( is_array( $removeSelf ) && count( $removeSelf ) ) {
$removeSelf = array_values( array_unique( $removeSelf ) );
$r[] = $this->msg( 'listgrouprights-removegroup-self',
$lang->listToText( array_map( array( 'User', 'makeGroupLinkWiki' ), $removeSelf ) ),
count( $removeSelf )
)->parse();
}
- if( empty( $r ) ) {
+
+ if ( empty( $r ) ) {
return '';
} else {
return '<ul><li>' . implode( "</li>\n<li>", $r ) . '</li></ul>';
diff --git a/includes/specials/SpecialListredirects.php b/includes/specials/SpecialListredirects.php
index 0283767a..2c8792ff 100644
--- a/includes/specials/SpecialListredirects.php
+++ b/includes/specials/SpecialListredirects.php
@@ -29,7 +29,6 @@
* @ingroup SpecialPage
*/
class ListredirectsPage extends QueryPage {
-
function __construct( $name = 'Listredirects' ) {
parent::__construct( $name );
}
@@ -50,16 +49,16 @@ class ListredirectsPage extends QueryPage {
return array(
'tables' => array( 'p1' => 'page', 'redirect', 'p2' => 'page' ),
'fields' => array( 'namespace' => 'p1.page_namespace',
- 'title' => 'p1.page_title',
- 'value' => 'p1.page_title',
- 'rd_namespace',
- 'rd_title',
- 'rd_fragment',
- 'rd_interwiki',
- 'redirid' => 'p2.page_id' ),
+ 'title' => 'p1.page_title',
+ 'value' => 'p1.page_title',
+ 'rd_namespace',
+ 'rd_title',
+ 'rd_fragment',
+ 'rd_interwiki',
+ 'redirid' => 'p2.page_id' ),
'conds' => array( 'p1.page_is_redirect' => 1 ),
'join_conds' => array( 'redirect' => array(
- 'LEFT JOIN', 'rd_from=p1.page_id' ),
+ 'LEFT JOIN', 'rd_from=p1.page_id' ),
'p2' => array( 'LEFT JOIN', array(
'p2.page_namespace=rd_namespace',
'p2.page_title=rd_title' ) ) )
@@ -67,21 +66,23 @@ class ListredirectsPage extends QueryPage {
}
function getOrderFields() {
- return array ( 'p1.page_namespace', 'p1.page_title' );
+ return array( 'p1.page_namespace', 'p1.page_title' );
}
/**
* Cache page existence for performance
*
- * @param $db DatabaseBase
- * @param $res ResultWrapper
+ * @param DatabaseBase $db
+ * @param ResultWrapper $res
*/
function preprocessResults( $db, $res ) {
$batch = new LinkBatch;
+
foreach ( $res as $row ) {
$batch->add( $row->namespace, $row->title );
$batch->addObj( $this->getRedirectTarget( $row ) );
}
+
$batch->execute();
// Back to start for display
@@ -100,10 +101,16 @@ class ListredirectsPage extends QueryPage {
} else {
$title = Title::makeTitle( $row->namespace, $row->title );
$article = WikiPage::factory( $title );
+
return $article->getRedirectTarget();
}
}
+ /**
+ * @param Skin $skin
+ * @param object $result Result row
+ * @return string
+ */
function formatResult( $skin, $result ) {
# Make a link to the redirect itself
$rd_title = Title::makeTitle( $result->namespace, $result->title );
@@ -116,11 +123,12 @@ class ListredirectsPage extends QueryPage {
# Find out where the redirect leads
$target = $this->getRedirectTarget( $result );
- if( $target ) {
+ if ( $target ) {
# Make a link to the destination page
$lang = $this->getLanguage();
$arr = $lang->getArrow() . $lang->getDirMark();
$targetLink = Linker::link( $target );
+
return "$rd_link $arr $targetLink";
} else {
return "<del>$rd_link</del>";
diff --git a/includes/specials/SpecialListusers.php b/includes/specials/SpecialListusers.php
index d253a4d3..8cd9173c 100644
--- a/includes/specials/SpecialListusers.php
+++ b/includes/specials/SpecialListusers.php
@@ -49,7 +49,10 @@ class UsersPager extends AlphabeticPager {
$par = ( $par !== null ) ? $par : '';
$parms = explode( '/', $par );
$symsForAll = array( '*', 'user' );
- if ( $parms[0] != '' && ( in_array( $par, User::getAllGroups() ) || in_array( $par, $symsForAll ) ) ) {
+
+ if ( $parms[0] != '' &&
+ ( in_array( $par, User::getAllGroups() ) || in_array( $par, $symsForAll ) )
+ ) {
$this->requestedGroup = $par;
$un = $request->getText( 'username' );
} elseif ( count( $parms ) == 2 ) {
@@ -59,20 +62,25 @@ class UsersPager extends AlphabeticPager {
$this->requestedGroup = $request->getVal( 'group' );
$un = ( $par != '' ) ? $par : $request->getText( 'username' );
}
+
if ( in_array( $this->requestedGroup, $symsForAll ) ) {
$this->requestedGroup = '';
}
$this->editsOnly = $request->getBool( 'editsOnly' );
$this->creationSort = $request->getBool( 'creationSort' );
$this->including = $including;
+ $this->mDefaultDirection = $request->getBool( 'desc' );
$this->requestedUser = '';
+
if ( $un != '' ) {
$username = Title::makeTitleSafe( NS_USER, $un );
- if( ! is_null( $username ) ) {
+
+ if ( !is_null( $username ) ) {
$this->requestedUser = $username->getText();
}
}
+
parent::__construct();
}
@@ -89,27 +97,28 @@ class UsersPager extends AlphabeticPager {
function getQueryInfo() {
$dbr = wfGetDB( DB_SLAVE );
$conds = array();
+
// Don't show hidden names
- if( !$this->getUser()->isAllowed( 'hideuser' ) ) {
+ if ( !$this->getUser()->isAllowed( 'hideuser' ) ) {
$conds[] = 'ipb_deleted IS NULL OR ipb_deleted = 0';
}
$options = array();
- if( $this->requestedGroup != '' ) {
+ if ( $this->requestedGroup != '' ) {
$conds['ug_group'] = $this->requestedGroup;
- } else {
- //$options['USE INDEX'] = $this->creationSort ? 'PRIMARY' : 'user_name';
}
- if( $this->requestedUser != '' ) {
+
+ if ( $this->requestedUser != '' ) {
# Sorted either by account creation or name
- if( $this->creationSort ) {
+ if ( $this->creationSort ) {
$conds[] = 'user_id >= ' . intval( User::idFromName( $this->requestedUser ) );
} else {
$conds[] = 'user_name >= ' . $dbr->addQuotes( $this->requestedUser );
}
}
- if( $this->editsOnly ) {
+
+ if ( $this->editsOnly ) {
$conds[] = 'user_editcount > 0';
}
@@ -129,15 +138,18 @@ class UsersPager extends AlphabeticPager {
'options' => $options,
'join_conds' => array(
'user_groups' => array( 'LEFT JOIN', 'user_id=ug_user' ),
- 'ipblocks' => array( 'LEFT JOIN', array(
- 'user_id=ipb_user',
- 'ipb_auto' => 0
- )),
+ 'ipblocks' => array(
+ 'LEFT JOIN', array(
+ 'user_id=ipb_user',
+ 'ipb_auto' => 0
+ )
+ ),
),
'conds' => $conds
);
wfRunHooks( 'SpecialListusersQueryInfo', array( $this, &$query ) );
+
return $query;
}
@@ -153,42 +165,55 @@ class UsersPager extends AlphabeticPager {
$userName = $row->user_name;
$ulinks = Linker::userLink( $row->user_id, $userName );
- $ulinks .= Linker::userToolLinksRedContribs( $row->user_id, $userName, intval( $row->edits ) );
+ $ulinks .= Linker::userToolLinksRedContribs(
+ $row->user_id,
+ $userName,
+ (int)$row->edits
+ );
$lang = $this->getLanguage();
$groups = '';
$groups_list = self::getGroups( $row->user_id );
- if( !$this->including && count( $groups_list ) > 0 ) {
+
+ if ( !$this->including && count( $groups_list ) > 0 ) {
$list = array();
- foreach( $groups_list as $group )
+ foreach ( $groups_list as $group ) {
$list[] = self::buildGroupLink( $group, $userName );
+ }
$groups = $lang->commaList( $list );
}
$item = $lang->specialList( $ulinks, $groups );
- if( $row->ipb_deleted ) {
+
+ if ( $row->ipb_deleted ) {
$item = "<span class=\"deleted\">$item</span>";
}
$edits = '';
global $wgEdititis;
if ( !$this->including && $wgEdititis ) {
- $edits = ' [' . $this->msg( 'usereditcount' )->numParams( $row->edits )->escaped() . ']';
+ // @fixme i18n issue: Hardcoded square brackets.
+ $edits = ' [' .
+ $this->msg( 'usereditcount' )->numParams( $row->edits )->escaped() .
+ ']';
}
$created = '';
- # Some rows may be NULL
- if( !$this->including && $row->creation ) {
+ # Some rows may be null
+ if ( !$this->including && $row->creation ) {
$user = $this->getUser();
$d = $lang->userDate( $row->creation, $user );
$t = $lang->userTime( $row->creation, $user );
$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() : '';
+ $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}{$blocked}" );
}
@@ -212,7 +237,10 @@ class UsersPager extends AlphabeticPager {
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 );
@@ -233,12 +261,30 @@ class UsersPager extends AlphabeticPager {
$out .= Xml::label( $this->msg( 'group' )->text(), 'group' ) . ' ' .
Xml::openElement( 'select', array( 'name' => 'group', 'id' => 'group' ) ) .
Xml::option( $this->msg( 'group-all' )->text(), '' );
- foreach( $this->getAllGroups() as $group => $groupText )
+ foreach ( $this->getAllGroups() as $group => $groupText ) {
$out .= Xml::option( $groupText, $group, $group == $this->requestedGroup );
+ }
$out .= Xml::closeElement( 'select' ) . '<br />';
- $out .= Xml::checkLabel( $this->msg( 'listusers-editsonly' )->text(), 'editsOnly', 'editsOnly', $this->editsOnly );
+ $out .= Xml::checkLabel(
+ $this->msg( 'listusers-editsonly' )->text(),
+ 'editsOnly',
+ 'editsOnly',
+ $this->editsOnly
+ );
+ $out .= '&#160;';
+ $out .= Xml::checkLabel(
+ $this->msg( 'listusers-creationsort' )->text(),
+ 'creationSort',
+ 'creationSort',
+ $this->creationSort
+ );
$out .= '&#160;';
- $out .= Xml::checkLabel( $this->msg( 'listusers-creationsort' )->text(), 'creationSort', 'creationSort', $this->creationSort );
+ $out .= Xml::checkLabel(
+ $this->msg( 'listusers-desc' )->text(),
+ 'desc',
+ 'desc',
+ $this->mDefaultDirection
+ );
$out .= '<br />';
wfRunHooks( 'SpecialListusersHeaderForm', array( $this, &$out ) );
@@ -259,10 +305,11 @@ class UsersPager extends AlphabeticPager {
*/
function getAllGroups() {
$result = array();
- foreach( User::getAllGroups() as $group ) {
+ foreach ( User::getAllGroups() as $group ) {
$result[$group] = User::getGroupName( $group );
}
asort( $result );
+
return $result;
}
@@ -272,13 +319,14 @@ class UsersPager extends AlphabeticPager {
*/
function getDefaultQuery() {
$query = parent::getDefaultQuery();
- if( $this->requestedGroup != '' ) {
+ if ( $this->requestedGroup != '' ) {
$query['group'] = $this->requestedGroup;
}
- if( $this->requestedUser != '' ) {
+ if ( $this->requestedUser != '' ) {
$query['username'] = $this->requestedUser;
}
wfRunHooks( 'SpecialListusersDefaultQuery', array( $this, &$query ) );
+
return $query;
}
@@ -291,6 +339,7 @@ class UsersPager extends AlphabeticPager {
protected static function getGroups( $uid ) {
$user = User::newFromId( $uid );
$groups = array_diff( $user->getEffectiveGroups(), User::getImplicitGroups() );
+
return $groups;
}
@@ -302,7 +351,10 @@ class UsersPager extends AlphabeticPager {
* @return string
*/
protected static function buildGroupLink( $group, $username ) {
- return User::makeGroupLinkHtml( $group, htmlspecialchars( User::getGroupMember( $group, $username ) ) );
+ return User::makeGroupLinkHtml(
+ $group,
+ htmlspecialchars( User::getGroupMember( $group, $username ) )
+ );
}
}
@@ -310,7 +362,6 @@ class UsersPager extends AlphabeticPager {
* @ingroup SpecialPage
*/
class SpecialListUsers extends IncludableSpecialPage {
-
/**
* Constructor
*/
@@ -337,7 +388,7 @@ class SpecialListUsers extends IncludableSpecialPage {
$s = $up->getPageHeader();
}
- if( $usersbody ) {
+ if ( $usersbody ) {
$s .= $up->getNavigationBar();
$s .= Html::rawElement( 'ul', array(), $usersbody );
$s .= $up->getNavigationBar();
diff --git a/includes/specials/SpecialLog.php b/includes/specials/SpecialLog.php
index 4fc0f6e8..2ffdd89d 100644
--- a/includes/specials/SpecialLog.php
+++ b/includes/specials/SpecialLog.php
@@ -29,7 +29,6 @@
* @ingroup SpecialPage
*/
class SpecialLog extends SpecialPage {
-
/**
* List log type for which the target is a user
* Thus if the given target is in NS_MAIN we can alter it to be an NS_USER
@@ -75,14 +74,14 @@ class SpecialLog extends SpecialPage {
$opts->setValue( 'month', '' );
}
- // Reset the log type to default (nothing) if it's invalid or if the
- // user does not possess the right to view it
+ // If the user doesn't have the right permission to view the specific
+ // log type, throw a PermissionsError
+ // If the log type is invalid, just show all public logs
$type = $opts->getValue( 'type' );
- if ( !LogPage::isLogType( $type )
- || ( isset( $wgLogRestrictions[$type] )
- && !$this->getUser()->isAllowed( $wgLogRestrictions[$type] ) )
- ) {
+ if ( !LogPage::isLogType( $type ) ) {
$opts->setValue( 'type', '' );
+ } elseif ( isset( $wgLogRestrictions[$type] ) && !$this->getUser()->isAllowed( $wgLogRestrictions[$type] ) ) {
+ throw new PermissionsError( $wgLogRestrictions[$type] );
}
# Handle type-specific inputs
@@ -99,10 +98,10 @@ class SpecialLog extends SpecialPage {
# Some log types are only for a 'User:' title but we might have been given
# only the username instead of the full title 'User:username'. This part try
# to lookup for a user by that name and eventually fix user input. See bug 1697.
- if( in_array( $opts->getValue( 'type' ), $this->typeOnUser ) ) {
+ if ( in_array( $opts->getValue( 'type' ), $this->typeOnUser ) ) {
# ok we have a type of log which expect a user title.
$target = Title::newFromText( $opts->getValue( 'page' ) );
- if( $target && $target->getNamespace() === NS_MAIN ) {
+ if ( $target && $target->getNamespace() === NS_MAIN ) {
# User forgot to add 'User:', we are adding it for him
$opts->setValue( 'page',
Title::makeTitleSafe( NS_USER, $opts->getValue( 'page' ) )
@@ -117,9 +116,11 @@ class SpecialLog extends SpecialPage {
global $wgLogTypes;
# Get parameters
- $parms = explode( '/', ($par = ( $par !== null ) ? $par : '' ) );
+ $parms = explode( '/', ( $par = ( $par !== null ) ? $par : '' ) );
$symsForAll = array( '*', 'all' );
- if ( $parms[0] != '' && ( in_array( $par, $wgLogTypes ) || in_array( $par, $symsForAll ) ) ) {
+ if ( $parms[0] != '' &&
+ ( in_array( $par, $wgLogTypes ) || in_array( $par, $symsForAll ) )
+ ) {
$opts->setValue( 'type', $par );
} elseif ( count( $parms ) == 2 ) {
$opts->setValue( 'type', $parms[0] );
@@ -131,10 +132,22 @@ class SpecialLog extends SpecialPage {
private function show( FormOptions $opts, array $extraConds ) {
# Create a LogPager item to get the results and a LogEventsList item to format them...
- $loglist = new LogEventsList( $this->getContext(), null, LogEventsList::USE_REVDEL_CHECKBOXES );
- $pager = new LogPager( $loglist, $opts->getValue( 'type' ), $opts->getValue( 'user' ),
- $opts->getValue( 'page' ), $opts->getValue( 'pattern' ), $extraConds, $opts->getValue( 'year' ),
- $opts->getValue( 'month' ), $opts->getValue( 'tagfilter' ) );
+ $loglist = new LogEventsList(
+ $this->getContext(),
+ null,
+ LogEventsList::USE_REVDEL_CHECKBOXES
+ );
+ $pager = new LogPager(
+ $loglist,
+ $opts->getValue( 'type' ),
+ $opts->getValue( 'user' ),
+ $opts->getValue( 'page' ),
+ $opts->getValue( 'pattern' ),
+ $extraConds,
+ $opts->getValue( 'year' ),
+ $opts->getValue( 'month' ),
+ $opts->getValue( 'tagfilter' )
+ );
$this->addHeader( $opts->getValue( 'type' ) );
@@ -144,16 +157,28 @@ class SpecialLog extends SpecialPage {
}
# Show form options
- $loglist->showOptions( $pager->getType(), $opts->getValue( 'user' ), $pager->getPage(), $pager->getPattern(),
- $pager->getYear(), $pager->getMonth(), $pager->getFilterParams(), $opts->getValue( 'tagfilter' ) );
+ $loglist->showOptions(
+ $pager->getType(),
+ $opts->getValue( 'user' ),
+ $pager->getPage(),
+ $pager->getPattern(),
+ $pager->getYear(),
+ $pager->getMonth(),
+ $pager->getFilterParams(),
+ $opts->getValue( 'tagfilter' )
+ );
# Insert list
$logBody = $pager->getBody();
if ( $logBody ) {
$this->getOutput()->addHTML(
$pager->getNavigationBar() .
- $this->getRevisionButton( $loglist->beginLogEventsList() . $logBody . $loglist->endLogEventsList() ) .
- $pager->getNavigationBar()
+ $this->getRevisionButton(
+ $loglist->beginLogEventsList() .
+ $logBody .
+ $loglist->endLogEventsList()
+ ) .
+ $pager->getNavigationBar()
);
} else {
$this->getOutput()->addWikiMsg( 'logempty' );
@@ -161,19 +186,27 @@ class SpecialLog extends SpecialPage {
}
private function getRevisionButton( $formcontents ) {
- # If the user doesn't have the ability to delete log entries, don't bother showing him/her the button.
+ # If the user doesn't have the ability to delete log entries,
+ # don't bother showing them the button.
if ( !$this->getUser()->isAllowedAll( 'deletedhistory', 'deletelogentry' ) ) {
return $formcontents;
}
# Show button to hide log entries
global $wgScript;
- $s = Html::openElement( 'form', array( 'action' => $wgScript, 'id' => 'mw-log-deleterevision-submit' ) ) . "\n";
+ $s = Html::openElement(
+ 'form',
+ array( 'action' => $wgScript, 'id' => 'mw-log-deleterevision-submit' )
+ ) . "\n";
$s .= Html::hidden( 'title', SpecialPage::getTitleFor( 'Revisiondelete' ) ) . "\n";
$s .= Html::hidden( 'target', SpecialPage::getTitleFor( 'Log' ) ) . "\n";
$s .= Html::hidden( 'type', 'logging' ) . "\n";
- $button = Html::element( 'button',
- array( 'type' => 'submit', 'class' => "deleterevision-log-submit mw-log-deleterevision-button" ),
+ $button = Html::element(
+ 'button',
+ array(
+ 'type' => 'submit',
+ 'class' => "deleterevision-log-submit mw-log-deleterevision-button"
+ ),
$this->msg( 'showhideselectedlogentries' )->text()
) . "\n";
$s .= $button . $formcontents . $button;
diff --git a/includes/specials/SpecialLonelypages.php b/includes/specials/SpecialLonelypages.php
index 8c6a88ac..7c7771d7 100644
--- a/includes/specials/SpecialLonelypages.php
+++ b/includes/specials/SpecialLonelypages.php
@@ -28,7 +28,6 @@
* @ingroup SpecialPage
*/
class LonelyPagesPage extends PageQueryPage {
-
function __construct( $name = 'Lonelypages' ) {
parent::__construct( $name );
}
@@ -50,32 +49,43 @@ class LonelyPagesPage extends PageQueryPage {
}
function getQueryInfo() {
- return array (
- 'tables' => array ( 'page', 'pagelinks',
- 'templatelinks' ),
- 'fields' => array ( 'namespace' => 'page_namespace',
- 'title' => 'page_title',
- 'value' => 'page_title' ),
- 'conds' => array ( 'pl_namespace IS NULL',
- 'page_namespace' => MWNamespace::getContentNamespaces(),
- 'page_is_redirect' => 0,
- 'tl_namespace IS NULL' ),
- 'join_conds' => array (
- 'pagelinks' => array (
- 'LEFT JOIN', array (
+ return array(
+ 'tables' => array(
+ 'page', 'pagelinks',
+ 'templatelinks'
+ ),
+ 'fields' => array(
+ 'namespace' => 'page_namespace',
+ 'title' => 'page_title',
+ 'value' => 'page_title'
+ ),
+ 'conds' => array(
+ 'pl_namespace IS NULL',
+ 'page_namespace' => MWNamespace::getContentNamespaces(),
+ 'page_is_redirect' => 0,
+ 'tl_namespace IS NULL'
+ ),
+ 'join_conds' => array(
+ 'pagelinks' => array(
+ 'LEFT JOIN', array(
'pl_namespace = page_namespace',
- 'pl_title = page_title' ) ),
- 'templatelinks' => array (
- 'LEFT JOIN', array (
+ 'pl_title = page_title'
+ )
+ ),
+ 'templatelinks' => array(
+ 'LEFT JOIN', array(
'tl_namespace = page_namespace',
- 'tl_title = page_title' ) ) )
+ 'tl_title = page_title'
+ )
+ )
+ )
);
}
function getOrderFields() {
// For some crazy reason ordering by a constant
// causes a filesort in MySQL 5
- if( count( MWNamespace::getContentNamespaces() ) > 1 ) {
+ if ( count( MWNamespace::getContentNamespaces() ) > 1 ) {
return array( 'page_namespace', 'page_title' );
} else {
return array( 'page_title' );
diff --git a/includes/specials/SpecialLongpages.php b/includes/specials/SpecialLongpages.php
index c045f9e9..d90d2718 100644
--- a/includes/specials/SpecialLongpages.php
+++ b/includes/specials/SpecialLongpages.php
@@ -26,7 +26,6 @@
* @ingroup SpecialPage
*/
class LongPagesPage extends ShortPagesPage {
-
function __construct( $name = 'Longpages' ) {
parent::__construct( $name );
}
diff --git a/includes/specials/SpecialMIMEsearch.php b/includes/specials/SpecialMIMEsearch.php
index c5a109d4..3eeae310 100644
--- a/includes/specials/SpecialMIMEsearch.php
+++ b/includes/specials/SpecialMIMEsearch.php
@@ -35,7 +35,7 @@ class MIMEsearchPage extends QueryPage {
}
function isExpensive() {
- return true;
+ return false;
}
function isSyndicated() {
@@ -51,19 +51,52 @@ class MIMEsearchPage extends QueryPage {
}
public function getQueryInfo() {
- return array(
+ $qi = array(
'tables' => array( 'image' ),
- 'fields' => array( 'namespace' => NS_FILE,
- 'title' => 'img_name',
- 'value' => 'img_major_mime',
- 'img_size',
- 'img_width',
- 'img_height',
- 'img_user_text',
- 'img_timestamp' ),
- 'conds' => array( 'img_major_mime' => $this->major,
- 'img_minor_mime' => $this->minor )
+ 'fields' => array(
+ 'namespace' => NS_FILE,
+ 'title' => 'img_name',
+ // Still have a value field just in case,
+ // but it isn't actually used for sorting.
+ 'value' => 'img_name',
+ 'img_size',
+ 'img_width',
+ 'img_height',
+ 'img_user_text',
+ 'img_timestamp'
+ ),
+ 'conds' => array(
+ 'img_major_mime' => $this->major,
+ 'img_minor_mime' => $this->minor,
+ // This is in order to trigger using
+ // the img_media_mime index in "range" mode.
+ 'img_media_type' => array(
+ MEDIATYPE_BITMAP,
+ MEDIATYPE_DRAWING,
+ MEDIATYPE_AUDIO,
+ MEDIATYPE_VIDEO,
+ MEDIATYPE_MULTIMEDIA,
+ MEDIATYPE_UNKNOWN,
+ MEDIATYPE_OFFICE,
+ MEDIATYPE_TEXT,
+ MEDIATYPE_EXECUTABLE,
+ MEDIATYPE_ARCHIVE,
+ ),
+ ),
);
+ return $qi;
+ }
+
+ /**
+ * The index is on (img_media_type, img_major_mime, img_minor_mime)
+ * which unfortunately doesn't have img_name at the end for sorting.
+ * So tell db to sort it however it wishes (Its not super important
+ * that this report gives results in a logical order). As an aditional
+ * note, mysql seems to by default order things by img_name ASC, which
+ * is what we ideally want, so everything works out fine anyhow.
+ */
+ function getOrderFields() {
+ return array();
}
function execute( $par ) {
@@ -74,24 +107,36 @@ class MIMEsearchPage extends QueryPage {
$this->setHeaders();
$this->outputHeader();
$this->getOutput()->addHTML(
- Xml::openElement( 'form', array( 'id' => 'specialmimesearch', 'method' => 'get', 'action' => $wgScript ) ) .
- Xml::openElement( 'fieldset' ) .
- Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) .
- Xml::element( 'legend', null, $this->msg( 'mimesearch' )->text() ) .
- Xml::inputLabel( $this->msg( 'mimetype' )->text(), 'mime', 'mime', 20, $mime ) . ' ' .
- Xml::submitButton( $this->msg( 'ilsubmit' )->text() ) .
- Xml::closeElement( 'fieldset' ) .
- Xml::closeElement( 'form' )
+ Xml::openElement(
+ 'form',
+ array( 'id' => 'specialmimesearch', 'method' => 'get', 'action' => $wgScript )
+ ) .
+ Xml::openElement( 'fieldset' ) .
+ Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) .
+ Xml::element( 'legend', null, $this->msg( 'mimesearch' )->text() ) .
+ Xml::inputLabel( $this->msg( 'mimetype' )->text(), 'mime', 'mime', 20, $mime ) .
+ ' ' .
+ Xml::submitButton( $this->msg( 'ilsubmit' )->text() ) .
+ Xml::closeElement( 'fieldset' ) .
+ Xml::closeElement( 'form' )
);
list( $this->major, $this->minor ) = File::splitMime( $mime );
+
if ( $this->major == '' || $this->minor == '' || $this->minor == 'unknown' ||
- !self::isValidType( $this->major ) ) {
+ !self::isValidType( $this->major )
+ ) {
return;
}
+
parent::execute( $par );
}
+ /**
+ * @param Skin $skin
+ * @param object $result Result row
+ * @return string
+ */
function formatResult( $skin, $result ) {
global $wgContLang;
@@ -108,8 +153,13 @@ class MIMEsearchPage extends QueryPage {
$bytes = htmlspecialchars( $lang->formatSize( $result->img_size ) );
$dimensions = $this->msg( 'widthheight' )->numParams( $result->img_width,
$result->img_height )->escaped();
- $user = Linker::link( Title::makeTitle( NS_USER, $result->img_user_text ), htmlspecialchars( $result->img_user_text ) );
- $time = htmlspecialchars( $lang->userTimeAndDate( $result->img_timestamp, $this->getUser() ) );
+ $user = Linker::link(
+ Title::makeTitle( NS_USER, $result->img_user_text ),
+ htmlspecialchars( $result->img_user_text )
+ );
+
+ $time = $lang->userTimeAndDate( $result->img_timestamp, $this->getUser() );
+ $time = htmlspecialchars( $time );
return "$download $plink . . $dimensions . . $bytes . . $user . . $time";
}
@@ -131,6 +181,7 @@ class MIMEsearchPage extends QueryPage {
'model',
'multipart'
);
+
return in_array( $type, $types );
}
diff --git a/includes/specials/SpecialMergeHistory.php b/includes/specials/SpecialMergeHistory.php
index 1476e156..fb5ea657 100644
--- a/includes/specials/SpecialMergeHistory.php
+++ b/includes/specials/SpecialMergeHistory.php
@@ -52,14 +52,14 @@ class SpecialMergeHistory extends SpecialPage {
$this->mTargetID = intval( $request->getVal( 'targetID' ) );
$this->mDestID = intval( $request->getVal( 'destID' ) );
$this->mTimestamp = $request->getVal( 'mergepoint' );
- if( !preg_match( '/[0-9]{14}/', $this->mTimestamp ) ) {
+ if ( !preg_match( '/[0-9]{14}/', $this->mTimestamp ) ) {
$this->mTimestamp = '';
}
$this->mComment = $request->getText( 'wpComment' );
$this->mMerge = $request->wasPosted() && $this->getUser()->matchEditToken( $request->getVal( 'wpEditToken' ) );
// target page
- if( $this->mSubmitted ) {
+ if ( $this->mSubmitted ) {
$this->mTargetObj = Title::newFromURL( $this->mTarget );
$this->mDestObj = Title::newFromURL( $this->mDest );
} else {
@@ -75,7 +75,7 @@ class SpecialMergeHistory extends SpecialPage {
*/
function preCacheMessages() {
// Precache various messages
- if( !isset( $this->message ) ) {
+ if ( !isset( $this->message ) ) {
$this->message['last'] = $this->msg( 'last' )->escaped();
}
}
@@ -89,20 +89,22 @@ class SpecialMergeHistory extends SpecialPage {
$this->setHeaders();
$this->outputHeader();
- if( $this->mTargetID && $this->mDestID && $this->mAction == 'submit' && $this->mMerge ) {
+ if ( $this->mTargetID && $this->mDestID && $this->mAction == 'submit' && $this->mMerge ) {
$this->merge();
+
return;
}
if ( !$this->mSubmitted ) {
$this->showMergeForm();
+
return;
}
$errors = array();
if ( !$this->mTargetObj instanceof Title ) {
$errors[] = $this->msg( 'mergehistory-invalid-source' )->parseAsBlock();
- } elseif( !$this->mTargetObj->exists() ) {
+ } elseif ( !$this->mTargetObj->exists() ) {
$errors[] = $this->msg( 'mergehistory-no-source', array( 'parse' ),
wfEscapeWikiText( $this->mTargetObj->getPrefixedText() )
)->parseAsBlock();
@@ -110,7 +112,7 @@ class SpecialMergeHistory extends SpecialPage {
if ( !$this->mDestObj instanceof Title ) {
$errors[] = $this->msg( 'mergehistory-invalid-destination' )->parseAsBlock();
- } elseif( !$this->mDestObj->exists() ) {
+ } elseif ( !$this->mDestObj->exists() ) {
$errors[] = $this->msg( 'mergehistory-no-destination', array( 'parse' ),
wfEscapeWikiText( $this->mDestObj->getPrefixedText() )
)->parseAsBlock();
@@ -126,7 +128,6 @@ class SpecialMergeHistory extends SpecialPage {
} else {
$this->showHistory();
}
-
}
function showMergeForm() {
@@ -138,25 +139,25 @@ class SpecialMergeHistory extends SpecialPage {
Xml::openElement( 'form', array(
'method' => 'get',
'action' => $wgScript ) ) .
- '<fieldset>' .
- Xml::element( 'legend', array(),
- $this->msg( 'mergehistory-box' )->text() ) .
- Html::hidden( 'title', $this->getTitle()->getPrefixedDBkey() ) .
- Html::hidden( 'submitted', '1' ) .
- Html::hidden( 'mergepoint', $this->mTimestamp ) .
- Xml::openElement( 'table' ) .
- '<tr>
+ '<fieldset>' .
+ Xml::element( 'legend', array(),
+ $this->msg( 'mergehistory-box' )->text() ) .
+ Html::hidden( 'title', $this->getTitle()->getPrefixedDBkey() ) .
+ Html::hidden( 'submitted', '1' ) .
+ Html::hidden( 'mergepoint', $this->mTimestamp ) .
+ Xml::openElement( 'table' ) .
+ '<tr>
<td>' . Xml::label( $this->msg( 'mergehistory-from' )->text(), 'target' ) . '</td>
<td>' . Xml::input( 'target', 30, $this->mTarget, array( 'id' => 'target' ) ) . '</td>
</tr><tr>
<td>' . Xml::label( $this->msg( 'mergehistory-into' )->text(), 'dest' ) . '</td>
<td>' . Xml::input( 'dest', 30, $this->mDest, array( 'id' => 'dest' ) ) . '</td>
</tr><tr><td>' .
- Xml::submitButton( $this->msg( 'mergehistory-go' )->text() ) .
- '</td></tr>' .
- Xml::closeElement( 'table' ) .
- '</fieldset>' .
- '</form>'
+ Xml::submitButton( $this->msg( 'mergehistory-go' )->text() ) .
+ '</td></tr>' .
+ Xml::closeElement( 'table' ) .
+ '</fieldset>' .
+ '</form>'
);
}
@@ -183,40 +184,40 @@ class SpecialMergeHistory extends SpecialPage {
);
$out->addHTML( $top );
- if( $haveRevisions ) {
+ if ( $haveRevisions ) {
# Format the user-visible controls (comment field, submission button)
# in a nice little table
$table =
Xml::openElement( 'fieldset' ) .
- $this->msg( 'mergehistory-merge', $this->mTargetObj->getPrefixedText(),
- $this->mDestObj->getPrefixedText() )->parse() .
- Xml::openElement( 'table', array( 'id' => 'mw-mergehistory-table' ) ) .
+ $this->msg( 'mergehistory-merge', $this->mTargetObj->getPrefixedText(),
+ $this->mDestObj->getPrefixedText() )->parse() .
+ Xml::openElement( 'table', array( 'id' => 'mw-mergehistory-table' ) ) .
'<tr>
<td class="mw-label">' .
- Xml::label( $this->msg( 'mergehistory-reason' )->text(), 'wpComment' ) .
- '</td>
- <td class="mw-input">' .
- Xml::input( 'wpComment', 50, $this->mComment, array( 'id' => 'wpComment' ) ) .
- '</td>
+ Xml::label( $this->msg( 'mergehistory-reason' )->text(), 'wpComment' ) .
+ '</td>
+ <td class="mw-input">' .
+ Xml::input( 'wpComment', 50, $this->mComment, array( 'id' => 'wpComment' ) ) .
+ '</td>
</tr>
<tr>
<td>&#160;</td>
<td class="mw-submit">' .
- Xml::submitButton( $this->msg( 'mergehistory-submit' )->text(), array( 'name' => 'merge', 'id' => 'mw-merge-submit' ) ) .
- '</td>
+ Xml::submitButton( $this->msg( 'mergehistory-submit' )->text(), array( 'name' => 'merge', 'id' => 'mw-merge-submit' ) ) .
+ '</td>
</tr>' .
- Xml::closeElement( 'table' ) .
- Xml::closeElement( 'fieldset' );
+ Xml::closeElement( 'table' ) .
+ Xml::closeElement( 'fieldset' );
$out->addHTML( $table );
}
$out->addHTML(
'<h2 id="mw-mergehistory">' .
- $this->msg( 'mergehistory-list' )->escaped() . "</h2>\n"
+ $this->msg( 'mergehistory-list' )->escaped() . "</h2>\n"
);
- if( $haveRevisions ) {
+ if ( $haveRevisions ) {
$out->addHTML( $revisions->getNavigationBar() );
$out->addHTML( '<ul>' );
$out->addHTML( $revisions->getBody() );
@@ -261,14 +262,14 @@ class SpecialMergeHistory extends SpecialPage {
array(),
array( 'oldid' => $rev->getId() )
);
- if( $rev->isDeleted( Revision::DELETED_TEXT ) ) {
+ if ( $rev->isDeleted( Revision::DELETED_TEXT ) ) {
$pageLink = '<span class="history-deleted">' . $pageLink . '</span>';
}
# Last link
- if( !$rev->userCan( Revision::DELETED_TEXT, $user ) ) {
+ if ( !$rev->userCan( Revision::DELETED_TEXT, $user ) ) {
$last = $this->message['last'];
- } elseif( isset( $this->prevId[$row->rev_id] ) ) {
+ } elseif ( isset( $this->prevId[$row->rev_id] ) ) {
$last = Linker::linkKnown(
$rev->getTitle(),
$this->message['last'],
@@ -283,7 +284,7 @@ class SpecialMergeHistory extends SpecialPage {
$userLink = Linker::revUserTools( $rev );
$size = $row->rev_len;
- if( !is_null( $size ) ) {
+ if ( !is_null( $size ) ) {
$stxt = Linker::formatRevisionSize( $size );
}
$comment = Linker::revComment( $rev );
@@ -298,10 +299,10 @@ class SpecialMergeHistory extends SpecialPage {
# keep it consistent...
$targetTitle = Title::newFromID( $this->mTargetID );
$destTitle = Title::newFromID( $this->mDestID );
- if( is_null( $targetTitle ) || is_null( $destTitle ) ) {
+ if ( is_null( $targetTitle ) || is_null( $destTitle ) ) {
return false; // validate these
}
- if( $targetTitle->getArticleID() == $destTitle->getArticleID() ) {
+ if ( $targetTitle->getArticleID() == $destTitle->getArticleID() ) {
return false;
}
# Verify that this timestamp is valid
@@ -317,8 +318,9 @@ class SpecialMergeHistory extends SpecialPage {
__METHOD__
);
# Destination page must exist with revisions
- if( !$maxtimestamp ) {
+ if ( !$maxtimestamp ) {
$this->getOutput()->addWikiMsg( 'mergehistory-fail' );
+
return false;
}
# Get the latest timestamp of the source
@@ -329,12 +331,13 @@ class SpecialMergeHistory extends SpecialPage {
__METHOD__
);
# $this->mTimestamp must be older than $maxtimestamp
- if( $this->mTimestamp >= $maxtimestamp ) {
+ if ( $this->mTimestamp >= $maxtimestamp ) {
$this->getOutput()->addWikiMsg( 'mergehistory-fail' );
+
return false;
}
# Update the revisions
- if( $this->mTimestamp ) {
+ if ( $this->mTimestamp ) {
$timewhere = "rev_timestamp <= {$this->mTimestamp}";
$timestampLimit = wfTimestamp( TS_MW, $this->mTimestamp );
} else {
@@ -354,12 +357,12 @@ class SpecialMergeHistory extends SpecialPage {
$haveRevisions = $dbw->selectField(
'revision',
'rev_timestamp',
- array( 'rev_page' => $this->mTargetID ),
+ array( 'rev_page' => $this->mTargetID ),
__METHOD__,
array( 'FOR UPDATE' )
);
- if( !$haveRevisions ) {
- if( $this->mComment ) {
+ if ( !$haveRevisions ) {
+ if ( $this->mComment ) {
$comment = $this->msg(
'mergehistory-comment',
$targetTitle->getPrefixedText(),
@@ -380,8 +383,8 @@ class SpecialMergeHistory extends SpecialPage {
if ( $redirectContent ) {
$redirectPage = WikiPage::factory( $targetTitle );
$redirectRevision = new Revision( array(
- 'title' => $targetTitle,
- 'page' => $this->mTargetID,
+ 'title' => $targetTitle,
+ 'page' => $this->mTargetID,
'comment' => $comment,
'content' => $redirectContent ) );
$redirectRevision->insertOn( $dbw );
@@ -392,9 +395,9 @@ class SpecialMergeHistory extends SpecialPage {
$dbw->delete( 'pagelinks', array( 'pl_from' => $this->mDestID ), __METHOD__ );
$dbw->insert( 'pagelinks',
array(
- 'pl_from' => $this->mDestID,
+ 'pl_from' => $this->mDestID,
'pl_namespace' => $destTitle->getNamespace(),
- 'pl_title' => $destTitle->getDBkey() ),
+ 'pl_title' => $destTitle->getDBkey() ),
__METHOD__
);
} else {
@@ -405,15 +408,16 @@ class SpecialMergeHistory extends SpecialPage {
}
$destTitle->invalidateCache(); // update histories
# Check if this did anything
- if( !$count ) {
+ if ( !$count ) {
$this->getOutput()->addWikiMsg( 'mergehistory-fail' );
+
return false;
}
# Update our logs
$log = new LogPage( 'merge' );
$log->addEntry(
'merge', $targetTitle, $this->mComment,
- array( $destTitle->getPrefixedText(), $timestampLimit )
+ array( $destTitle->getPrefixedText(), $timestampLimit ), $this->getUser()
);
$this->getOutput()->addWikiMsg( 'mergehistory-success',
@@ -462,9 +466,9 @@ class MergeHistoryPager extends ReverseChronologicalPager {
$batch->addObj( Title::makeTitleSafe( NS_USER_TALK, $row->user_name ) );
$rev_id = isset( $rev_id ) ? $rev_id : $row->rev_id;
- if( $rev_id > $row->rev_id ) {
+ if ( $rev_id > $row->rev_id ) {
$this->mForm->prevId[$rev_id] = $row->rev_id;
- } elseif( $rev_id < $row->rev_id ) {
+ } elseif ( $rev_id < $row->rev_id ) {
$this->mForm->prevId[$row->rev_id] = $rev_id;
}
@@ -475,6 +479,7 @@ class MergeHistoryPager extends ReverseChronologicalPager {
$this->mResult->seek( 0 );
wfProfileOut( __METHOD__ );
+
return '';
}
@@ -486,10 +491,11 @@ class MergeHistoryPager extends ReverseChronologicalPager {
$conds = $this->mConds;
$conds['rev_page'] = $this->articleID;
$conds[] = "rev_timestamp < {$this->maxTimestamp}";
+
return array(
'tables' => array( 'revision', 'page', 'user' ),
'fields' => array_merge( Revision::selectFields(), Revision::selectUserFields() ),
- 'conds' => $conds,
+ 'conds' => $conds,
'join_conds' => array(
'page' => Revision::pageJoinCond(),
'user' => Revision::userJoinCond() )
diff --git a/includes/specials/SpecialMostcategories.php b/includes/specials/SpecialMostcategories.php
index 11f26bd7..9b67f343 100644
--- a/includes/specials/SpecialMostcategories.php
+++ b/includes/specials/SpecialMostcategories.php
@@ -30,7 +30,6 @@
* @ingroup SpecialPage
*/
class MostcategoriesPage extends QueryPage {
-
function __construct( $name = 'Mostcategories' ) {
parent::__construct( $name );
}
@@ -44,22 +43,30 @@ class MostcategoriesPage extends QueryPage {
}
function getQueryInfo() {
- return array (
- 'tables' => array ( 'categorylinks', 'page' ),
- 'fields' => array ( 'namespace' => 'page_namespace',
- 'title' => 'page_title',
- 'value' => 'COUNT(*)' ),
- 'conds' => array ( 'page_namespace' => MWNamespace::getContentNamespaces() ),
- 'options' => array ( 'HAVING' => 'COUNT(*) > 1',
- 'GROUP BY' => array( 'page_namespace', 'page_title' ) ),
- 'join_conds' => array ( 'page' => array ( 'LEFT JOIN',
- 'page_id = cl_from' ) )
+ return array(
+ 'tables' => array( 'categorylinks', 'page' ),
+ 'fields' => array(
+ 'namespace' => 'page_namespace',
+ 'title' => 'page_title',
+ 'value' => 'COUNT(*)'
+ ),
+ 'conds' => array( 'page_namespace' => MWNamespace::getContentNamespaces() ),
+ 'options' => array(
+ 'HAVING' => 'COUNT(*) > 1',
+ 'GROUP BY' => array( 'page_namespace', 'page_title' )
+ ),
+ 'join_conds' => array(
+ 'page' => array(
+ 'LEFT JOIN',
+ 'page_id = cl_from'
+ )
+ )
);
}
/**
- * @param $db DatabaseBase
- * @param $res
+ * @param DatabaseBase $db
+ * @param ResultWrapper $res
*/
function preprocessResults( $db, $res ) {
# There's no point doing a batch check if we aren't caching results;
@@ -78,15 +85,22 @@ class MostcategoriesPage extends QueryPage {
}
/**
- * @param $skin Skin
- * @param $result
+ * @param Skin $skin
+ * @param object $result Result row
* @return string
*/
function formatResult( $skin, $result ) {
$title = Title::makeTitleSafe( $result->namespace, $result->title );
if ( !$title ) {
- return Html::element( 'span', array( 'class' => 'mw-invalidtitle' ),
- Linker::getInvalidTitleDescription( $this->getContext(), $result->namespace, $result->title ) );
+ return Html::element(
+ 'span',
+ array( 'class' => 'mw-invalidtitle' ),
+ Linker::getInvalidTitleDescription(
+ $this->getContext(),
+ $result->namespace,
+ $result->title
+ )
+ );
}
if ( $this->isCached() ) {
diff --git a/includes/specials/SpecialMostimages.php b/includes/specials/SpecialMostimages.php
index 78b2d911..98d8da3a 100644
--- a/includes/specials/SpecialMostimages.php
+++ b/includes/specials/SpecialMostimages.php
@@ -30,7 +30,6 @@
* @ingroup SpecialPage
*/
class MostimagesPage extends ImageQueryPage {
-
function __construct( $name = 'Mostimages' ) {
parent::__construct( $name );
}
@@ -44,13 +43,17 @@ class MostimagesPage extends ImageQueryPage {
}
function getQueryInfo() {
- return array (
- 'tables' => array ( 'imagelinks' ),
- 'fields' => array ( 'namespace' => NS_FILE,
- 'title' => 'il_to',
- 'value' => 'COUNT(*)' ),
- 'options' => array ( 'GROUP BY' => 'il_to',
- 'HAVING' => 'COUNT(*) > 1' )
+ return array(
+ 'tables' => array( 'imagelinks' ),
+ 'fields' => array(
+ 'namespace' => NS_FILE,
+ 'title' => 'il_to',
+ 'value' => 'COUNT(*)'
+ ),
+ 'options' => array(
+ 'GROUP BY' => 'il_to',
+ 'HAVING' => 'COUNT(*) > 1'
+ )
);
}
diff --git a/includes/specials/SpecialMostinterwikis.php b/includes/specials/SpecialMostinterwikis.php
index 574a9afb..98dd68e9 100644
--- a/includes/specials/SpecialMostinterwikis.php
+++ b/includes/specials/SpecialMostinterwikis.php
@@ -30,7 +30,6 @@
* @ingroup SpecialPage
*/
class MostinterwikisPage extends QueryPage {
-
function __construct( $name = 'Mostinterwikis' ) {
parent::__construct( $name );
}
@@ -44,24 +43,24 @@ class MostinterwikisPage extends QueryPage {
}
function getQueryInfo() {
- return array (
- 'tables' => array (
+ return array(
+ 'tables' => array(
'langlinks',
'page'
- ), 'fields' => array (
+ ), 'fields' => array(
'namespace' => 'page_namespace',
'title' => 'page_title',
'value' => 'COUNT(*)'
- ), 'conds' => array (
+ ), 'conds' => array(
'page_namespace' => MWNamespace::getContentNamespaces()
- ), 'options' => array (
+ ), 'options' => array(
'HAVING' => 'COUNT(*) > 1',
- 'GROUP BY' => array (
+ 'GROUP BY' => array(
'page_namespace',
'page_title'
)
- ), 'join_conds' => array (
- 'page' => array (
+ ), 'join_conds' => array(
+ 'page' => array(
'LEFT JOIN',
'page_id = ll_from'
)
@@ -72,8 +71,8 @@ class MostinterwikisPage extends QueryPage {
/**
* Pre-fill the link cache
*
- * @param $db DatabaseBase
- * @param $res
+ * @param DatabaseBase $db
+ * @param ResultWrapper $res
*/
function preprocessResults( $db, $res ) {
# There's no point doing a batch check if we aren't caching results;
@@ -100,8 +99,15 @@ class MostinterwikisPage extends QueryPage {
function formatResult( $skin, $result ) {
$title = Title::makeTitleSafe( $result->namespace, $result->title );
if ( !$title ) {
- return Html::element( 'span', array( 'class' => 'mw-invalidtitle' ),
- Linker::getInvalidTitleDescription( $this->getContext(), $result->namespace, $result->title ) );
+ return Html::element(
+ 'span',
+ array( 'class' => 'mw-invalidtitle' ),
+ Linker::getInvalidTitleDescription(
+ $this->getContext(),
+ $result->namespace,
+ $result->title
+ )
+ );
}
if ( $this->isCached() ) {
diff --git a/includes/specials/SpecialMostlinked.php b/includes/specials/SpecialMostlinked.php
index 4b6e5670..37593bf9 100644
--- a/includes/specials/SpecialMostlinked.php
+++ b/includes/specials/SpecialMostlinked.php
@@ -31,7 +31,6 @@
* @ingroup SpecialPage
*/
class MostlinkedPage extends QueryPage {
-
function __construct( $name = 'Mostlinked' ) {
parent::__construct( $name );
}
@@ -45,33 +44,47 @@ class MostlinkedPage extends QueryPage {
}
function getQueryInfo() {
- return array (
- 'tables' => array ( 'pagelinks', 'page' ),
- 'fields' => array ( 'namespace' => 'pl_namespace',
- 'title' => 'pl_title',
- 'value' => 'COUNT(*)',
- 'page_namespace' ),
- 'options' => array ( 'HAVING' => 'COUNT(*) > 1',
- 'GROUP BY' => array( 'pl_namespace', 'pl_title',
- 'page_namespace' ) ),
- 'join_conds' => array ( 'page' => array ( 'LEFT JOIN',
- array ( 'page_namespace = pl_namespace',
- 'page_title = pl_title' ) ) )
+ return array(
+ 'tables' => array( 'pagelinks', 'page' ),
+ 'fields' => array(
+ 'namespace' => 'pl_namespace',
+ 'title' => 'pl_title',
+ 'value' => 'COUNT(*)',
+ 'page_namespace'
+ ),
+ 'options' => array(
+ 'HAVING' => 'COUNT(*) > 1',
+ 'GROUP BY' => array(
+ 'pl_namespace', 'pl_title',
+ 'page_namespace'
+ )
+ ),
+ 'join_conds' => array(
+ 'page' => array(
+ 'LEFT JOIN',
+ array(
+ 'page_namespace = pl_namespace',
+ 'page_title = pl_title'
+ )
+ )
+ )
);
}
/**
* Pre-fill the link cache
*
- * @param $db DatabaseBase
- * @param $res
+ * @param DatabaseBase $db
+ * @param ResultWrapper $res
*/
function preprocessResults( $db, $res ) {
if ( $res->numRows() > 0 ) {
$linkBatch = new LinkBatch();
+
foreach ( $res as $row ) {
$linkBatch->add( $row->namespace, $row->title );
}
+
$res->seek( 0 );
$linkBatch->execute();
}
@@ -86,25 +99,37 @@ class MostlinkedPage extends QueryPage {
*/
function makeWlhLink( $title, $caption ) {
$wlh = SpecialPage::getTitleFor( 'Whatlinkshere', $title->getPrefixedDBkey() );
+
return Linker::linkKnown( $wlh, $caption );
}
/**
- * Make links to the page corresponding to the item, and the "what links here" page for it
+ * Make links to the page corresponding to the item,
+ * and the "what links here" page for it
*
- * @param $skin Skin to be used
- * @param $result Result row
+ * @param Skin $skin Skin to be used
+ * @param object $result Result row
* @return string
*/
function formatResult( $skin, $result ) {
$title = Title::makeTitleSafe( $result->namespace, $result->title );
if ( !$title ) {
- return Html::element( 'span', array( 'class' => 'mw-invalidtitle' ),
- Linker::getInvalidTitleDescription( $this->getContext(), $result->namespace, $result->title ) );
+ return Html::element(
+ 'span',
+ array( 'class' => 'mw-invalidtitle' ),
+ Linker::getInvalidTitleDescription(
+ $this->getContext(),
+ $result->namespace,
+ $result->title )
+ );
}
+
$link = Linker::link( $title );
- $wlh = $this->makeWlhLink( $title,
- $this->msg( 'nlinks' )->numParams( $result->value )->escaped() );
+ $wlh = $this->makeWlhLink(
+ $title,
+ $this->msg( 'nlinks' )->numParams( $result->value )->escaped()
+ );
+
return $this->getLanguage()->specialList( $link, $wlh );
}
diff --git a/includes/specials/SpecialMostlinkedcategories.php b/includes/specials/SpecialMostlinkedcategories.php
index a1bce45d..0d4641b1 100644
--- a/includes/specials/SpecialMostlinkedcategories.php
+++ b/includes/specials/SpecialMostlinkedcategories.php
@@ -30,7 +30,6 @@
* @ingroup SpecialPage
*/
class MostlinkedCategoriesPage extends QueryPage {
-
function __construct( $name = 'Mostlinkedcategories' ) {
parent::__construct( $name );
}
@@ -40,11 +39,11 @@ class MostlinkedCategoriesPage extends QueryPage {
}
function getQueryInfo() {
- return array (
- 'tables' => array ( 'category' ),
- 'fields' => array ( 'title' => 'cat_title',
- 'namespace' => NS_CATEGORY,
- 'value' => 'cat_pages' ),
+ return array(
+ 'tables' => array( 'category' ),
+ 'fields' => array( 'title' => 'cat_title',
+ 'namespace' => NS_CATEGORY,
+ 'value' => 'cat_pages' ),
);
}
@@ -55,8 +54,8 @@ class MostlinkedCategoriesPage extends QueryPage {
/**
* Fetch user page links and cache their existence
*
- * @param $db DatabaseBase
- * @param $res DatabaseResult
+ * @param DatabaseBase $db
+ * @param ResultWrapper $res
*/
function preprocessResults( $db, $res ) {
if ( !$res->numRows() ) {
@@ -74,8 +73,8 @@ class MostlinkedCategoriesPage extends QueryPage {
}
/**
- * @param $skin Skin
- * @param $result
+ * @param Skin $skin
+ * @param object $result Result row
* @return string
*/
function formatResult( $skin, $result ) {
@@ -83,15 +82,20 @@ class MostlinkedCategoriesPage extends QueryPage {
$nt = Title::makeTitleSafe( NS_CATEGORY, $result->title );
if ( !$nt ) {
- return Html::element( 'span', array( 'class' => 'mw-invalidtitle' ),
- Linker::getInvalidTitleDescription( $this->getContext(), NS_CATEGORY, $result->title ) );
+ return Html::element(
+ 'span',
+ array( 'class' => 'mw-invalidtitle' ),
+ Linker::getInvalidTitleDescription(
+ $this->getContext(),
+ NS_CATEGORY,
+ $result->title )
+ );
}
$text = $wgContLang->convert( $nt->getText() );
-
$plink = Linker::link( $nt, htmlspecialchars( $text ) );
-
$nlinks = $this->msg( 'nmembers' )->numParams( $result->value )->escaped();
+
return $this->getLanguage()->specialList( $plink, $nlinks );
}
diff --git a/includes/specials/SpecialMostlinkedtemplates.php b/includes/specials/SpecialMostlinkedtemplates.php
index 506e6b22..c90acb1f 100644
--- a/includes/specials/SpecialMostlinkedtemplates.php
+++ b/includes/specials/SpecialMostlinkedtemplates.php
@@ -29,7 +29,6 @@
* @ingroup SpecialPage
*/
class MostlinkedTemplatesPage extends QueryPage {
-
function __construct( $name = 'Mostlinkedtemplates' ) {
parent::__construct( $name );
}
@@ -62,12 +61,14 @@ class MostlinkedTemplatesPage extends QueryPage {
}
public function getQueryInfo() {
- return array (
- 'tables' => array ( 'templatelinks' ),
- 'fields' => array ( 'namespace' => 'tl_namespace',
- 'title' => 'tl_title',
- 'value' => 'COUNT(*)' ),
- 'conds' => array ( 'tl_namespace' => NS_TEMPLATE ),
+ return array(
+ 'tables' => array( 'templatelinks' ),
+ 'fields' => array(
+ 'namespace' => 'tl_namespace',
+ 'title' => 'tl_title',
+ 'value' => 'COUNT(*)'
+ ),
+ 'conds' => array( 'tl_namespace' => NS_TEMPLATE ),
'options' => array( 'GROUP BY' => array( 'tl_namespace', 'tl_title' ) )
);
}
@@ -76,7 +77,7 @@ class MostlinkedTemplatesPage extends QueryPage {
* Pre-cache page existence to speed up link generation
*
* @param $db DatabaseBase connection
- * @param $res ResultWrapper
+ * @param ResultWrapper $res
*/
public function preprocessResults( $db, $res ) {
if ( !$res->numRows() ) {
@@ -95,15 +96,22 @@ class MostlinkedTemplatesPage extends QueryPage {
/**
* Format a result row
*
- * @param $skin Skin to use for UI elements
- * @param $result Result row
- * @return String
+ * @param Skin $skin
+ * @param object $result Result row
+ * @return string
*/
public function formatResult( $skin, $result ) {
$title = Title::makeTitleSafe( $result->namespace, $result->title );
if ( !$title ) {
- return Html::element( 'span', array( 'class' => 'mw-invalidtitle' ),
- Linker::getInvalidTitleDescription( $this->getContext(), $result->namespace, $result->title ) );
+ return Html::element(
+ 'span',
+ array( 'class' => 'mw-invalidtitle' ),
+ Linker::getInvalidTitleDescription(
+ $this->getContext(),
+ $result->namespace,
+ $result->title
+ )
+ );
}
return $this->getLanguage()->specialList(
@@ -115,13 +123,14 @@ class MostlinkedTemplatesPage extends QueryPage {
/**
* Make a "what links here" link for a given title
*
- * @param $title Title to make the link for
- * @param $result Result row
+ * @param Title $title Title to make the link for
+ * @param object $result Result row
* @return String
*/
private function makeWlhLink( $title, $result ) {
$wlh = SpecialPage::getTitleFor( 'Whatlinkshere', $title->getPrefixedText() );
$label = $this->msg( 'ntransclusions' )->numParams( $result->value )->escaped();
+
return Linker::link( $wlh, $label );
}
diff --git a/includes/specials/SpecialMovepage.php b/includes/specials/SpecialMovepage.php
index 4adb0371..253e6cc3 100644
--- a/includes/specials/SpecialMovepage.php
+++ b/includes/specials/SpecialMovepage.php
@@ -27,13 +27,15 @@
* @ingroup SpecialPage
*/
class MovePageForm extends UnlistedSpecialPage {
-
/**
+ * Objects
* @var Title
*/
- var $oldTitle, $newTitle; # Objects
- var $reason; # Text input
- var $moveTalk, $deleteAndMove, $moveSubpages, $fixRedirects, $leaveRedirect, $moveOverShared; # Checks
+ var $oldTitle, $newTitle;
+ // Text input
+ var $reason;
+ // Checks
+ var $moveTalk, $deleteAndMove, $moveSubpages, $fixRedirects, $leaveRedirect, $moveOverShared;
private $watch = false;
@@ -55,10 +57,10 @@ class MovePageForm extends UnlistedSpecialPage {
$oldTitleText = $request->getVal( 'wpOldTitle', $target );
$this->oldTitle = Title::newFromText( $oldTitleText );
- if( is_null( $this->oldTitle ) ) {
+ if ( is_null( $this->oldTitle ) ) {
throw new ErrorPageError( 'notargettitle', 'notargettext' );
}
- if( !$this->oldTitle->exists() ) {
+ if ( !$this->oldTitle->exists() ) {
throw new ErrorPageError( 'nopagetitle', 'nopagetext' );
}
@@ -93,7 +95,8 @@ class MovePageForm extends UnlistedSpecialPage {
$this->watch = $request->getCheck( 'wpWatch' ) && $user->isLoggedIn();
if ( 'submit' == $request->getVal( 'action' ) && $request->wasPosted()
- && $user->matchEditToken( $request->getVal( 'wpEditToken' ) ) ) {
+ && $user->matchEditToken( $request->getVal( 'wpEditToken' ) )
+ ) {
$this->doSubmit();
} else {
$this->showForm( array() );
@@ -129,7 +132,7 @@ class MovePageForm extends UnlistedSpecialPage {
# link, check for validity. We can then show some diagnostic
# information and save a click.
$newerr = $this->oldTitle->isValidMoveOperation( $newTitle );
- if( is_array( $newerr ) ) {
+ if ( is_array( $newerr ) ) {
$err = $newerr;
}
}
@@ -146,16 +149,26 @@ class MovePageForm extends UnlistedSpecialPage {
<tr>
<td></td>
<td class='mw-input'>" .
- Xml::checkLabel( $this->msg( 'delete_and_move_confirm' )->text(), 'wpConfirm', 'wpConfirm' ) .
- "</td>
+ Xml::checkLabel(
+ $this->msg( 'delete_and_move_confirm' )->text(),
+ 'wpConfirm',
+ 'wpConfirm'
+ ) .
+ "</td>
</tr>";
$err = array();
} else {
if ( $this->oldTitle->getNamespace() == NS_USER && !$this->oldTitle->isSubpage() ) {
- $out->wrapWikiMsg( "<div class=\"error mw-moveuserpage-warning\">\n$1\n</div>", 'moveuserpage-warning' );
+ $out->wrapWikiMsg(
+ "<div class=\"error mw-moveuserpage-warning\">\n$1\n</div>",
+ 'moveuserpage-warning'
+ );
}
- $out->addWikiMsg( $wgFixDoubleRedirects ? 'movepagetext' :
- 'movepagetext-noredirectfixer' );
+
+ $out->addWikiMsg( $wgFixDoubleRedirects ?
+ 'movepagetext' :
+ 'movepagetext-noredirectfixer'
+ );
$movepagebtn = $this->msg( 'movepagebtn' )->text();
$submitVar = 'wpMove';
$confirm = false;
@@ -205,6 +218,7 @@ class MovePageForm extends UnlistedSpecialPage {
if ( count( $err ) == 1 ) {
$errMsg = $err[0];
$errMsgName = array_shift( $errMsg );
+
if ( $errMsgName == 'hookaborted' ) {
$out->addHTML( "<p>{$errMsg[0]}</p>\n" );
} else {
@@ -212,8 +226,9 @@ class MovePageForm extends UnlistedSpecialPage {
}
} else {
$errStr = array();
- foreach( $err as $errMsg ) {
- if( $errMsg[0] == 'hookaborted' ) {
+
+ foreach ( $err as $errMsg ) {
+ if ( $errMsg[0] == 'hookaborted' ) {
$errStr[] = $errMsg[1];
} else {
$errMsgName = array_shift( $errMsg );
@@ -238,7 +253,13 @@ class MovePageForm extends UnlistedSpecialPage {
}
$out->addHTML( "<div class='mw-warning-with-logexcerpt'>\n" );
$out->addWikiMsg( $noticeMsg );
- LogEventsList::showLogExtract( $out, 'protect', $this->oldTitle, '', array( 'lim' => 1 ) );
+ LogEventsList::showLogExtract(
+ $out,
+ 'protect',
+ $this->oldTitle,
+ '',
+ array( 'lim' => 1 )
+ );
$out->addHTML( "</div>\n" );
}
@@ -256,13 +277,20 @@ 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() .
+ $this->msg( 'movearticle' )->escaped() .
"</td>
<td class='mw-input'>
<strong>{$oldTitleLink}</strong>
@@ -270,41 +298,54 @@ class MovePageForm extends UnlistedSpecialPage {
</tr>
<tr>
<td class='mw-label'>" .
- Xml::label( $this->msg( 'newtitle' )->text(), 'wpNewTitleMain' ) .
+ Xml::label( $this->msg( 'newtitle' )->text(), 'wpNewTitleMain' ) .
"</td>
<td class='mw-input'>" .
- Html::namespaceSelector(
- array(
- 'selected' => $newTitle->getNamespace(),
- 'exclude' => $immovableNamespaces
- ),
- array( 'name' => 'wpNewTitleNs', 'id' => 'wpNewTitleNs' )
- ) .
- Xml::input( 'wpNewTitleMain', 60, $wgContLang->recodeForEdit( $newTitle->getText() ), array(
+ Html::namespaceSelector(
+ array(
+ 'selected' => $newTitle->getNamespace(),
+ 'exclude' => $immovableNamespaces
+ ),
+ array( 'name' => 'wpNewTitleNs', 'id' => 'wpNewTitleNs' )
+ ) .
+ Xml::input(
+ 'wpNewTitleMain',
+ 60,
+ $wgContLang->recodeForEdit( $newTitle->getText() ),
+ array(
'type' => 'text',
'id' => 'wpNewTitleMain',
- 'maxlength' => 255,
- ) ) .
- Html::hidden( 'wpOldTitle', $this->oldTitle->getPrefixedText() ) .
+ 'maxlength' => 255
+ )
+ ) .
+ Html::hidden( 'wpOldTitle', $this->oldTitle->getPrefixedText() ) .
"</td>
</tr>
<tr>
<td class='mw-label'>" .
- Xml::label( $this->msg( 'movereason' )->text(), 'wpReason' ) .
+ Xml::label( $this->msg( 'movereason' )->text(), 'wpReason' ) .
"</td>
<td class='mw-input'>" .
- Html::element( 'textarea', array( 'name' => 'wpReason', 'id' => 'wpReason', 'cols' => 60, 'rows' => 2,
- 'maxlength' => 200 ), $this->reason ) .
+ Xml::input( 'wpReason', 60, $this->reason, array(
+ 'type' => 'text',
+ 'id' => 'wpReason',
+ 'maxlength' => 200,
+ ) ) .
"</td>
</tr>"
);
- if( $considerTalk ) {
+ if ( $considerTalk ) {
$out->addHTML( "
<tr>
<td></td>
<td class='mw-input'>" .
- Xml::checkLabel( $this->msg( 'movetalk' )->text(), 'wpMovetalk', 'wpMovetalk', $this->moveTalk ) .
+ Xml::checkLabel(
+ $this->msg( 'movetalk' )->text(),
+ 'wpMovetalk',
+ 'wpMovetalk',
+ $this->moveTalk
+ ) .
"</td>
</tr>"
);
@@ -315,8 +356,12 @@ class MovePageForm extends UnlistedSpecialPage {
<tr>
<td></td>
<td class='mw-input' >" .
- Xml::checkLabel( $this->msg( 'move-leave-redirect' )->text(), 'wpLeaveRedirect',
- 'wpLeaveRedirect', $this->leaveRedirect ) .
+ Xml::checkLabel(
+ $this->msg( 'move-leave-redirect' )->text(),
+ 'wpLeaveRedirect',
+ 'wpLeaveRedirect',
+ $this->leaveRedirect
+ ) .
"</td>
</tr>"
);
@@ -327,32 +372,36 @@ class MovePageForm extends UnlistedSpecialPage {
<tr>
<td></td>
<td class='mw-input' >" .
- Xml::checkLabel( $this->msg( 'fix-double-redirects' )->text(), 'wpFixRedirects',
- 'wpFixRedirects', $this->fixRedirects ) .
+ Xml::checkLabel(
+ $this->msg( 'fix-double-redirects' )->text(),
+ 'wpFixRedirects',
+ 'wpFixRedirects',
+ $this->fixRedirects
+ ) .
"</td>
</tr>"
);
}
- if( $canMoveSubpage ) {
+ if ( $canMoveSubpage ) {
$out->addHTML( "
<tr>
<td></td>
<td class=\"mw-input\">" .
- Xml::check(
- '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 ),
- array( 'id' => 'wpMovesubpages' )
- ) . '&#160;' .
- Xml::tags( 'label', array( 'for' => 'wpMovesubpages' ),
- $this->msg(
- ( $this->oldTitle->hasSubpages()
- ? 'move-subpages'
- : 'move-talk-subpages' )
+ Xml::check(
+ '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 ),
+ array( 'id' => 'wpMovesubpages' )
+ ) . '&#160;' .
+ Xml::tags( 'label', array( 'for' => 'wpMovesubpages' ),
+ $this->msg(
+ ( $this->oldTitle->hasSubpages()
+ ? 'move-subpages'
+ : 'move-talk-subpages' )
)->numParams( $wgMaximumMovedPages )->params( $wgMaximumMovedPages )->parse()
- ) .
+ ) .
"</td>
</tr>"
);
@@ -361,14 +410,19 @@ class MovePageForm extends UnlistedSpecialPage {
$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() ) {
+ if ( $user->isLoggedIn() ) {
$out->addHTML( "
<tr>
<td></td>
<td class='mw-input'>" .
- Xml::checkLabel( $this->msg( 'move-watch' )->text(), 'wpWatch', 'watch', $watchChecked ) .
+ Xml::checkLabel(
+ $this->msg( 'move-watch' )->text(),
+ 'wpWatch',
+ 'watch',
+ $watchChecked
+ ) .
"</td>
- </tr>");
+ </tr>" );
}
$out->addHTML( "
@@ -376,19 +430,18 @@ class MovePageForm extends UnlistedSpecialPage {
<tr>
<td>&#160;</td>
<td class='mw-submit'>" .
- Xml::submitButton( $movepagebtn, array( 'name' => $submitVar ) ) .
+ Xml::submitButton( $movepagebtn, array( 'name' => $submitVar ) ) .
"</td>
</tr>" .
- Xml::closeElement( 'table' ) .
- Html::hidden( 'wpEditToken', $user->getEditToken() ) .
- Xml::closeElement( 'fieldset' ) .
- Xml::closeElement( 'form' ) .
- "\n"
+ Xml::closeElement( 'table' ) .
+ Html::hidden( 'wpEditToken', $user->getEditToken() ) .
+ Xml::closeElement( 'fieldset' ) .
+ Xml::closeElement( 'form' ) .
+ "\n"
);
$this->showLogFragment( $this->oldTitle );
$this->showSubpages( $this->oldTitle );
-
}
function doSubmit() {
@@ -406,6 +459,7 @@ class MovePageForm extends UnlistedSpecialPage {
# don't allow moving to pages with # in
if ( !$nt || $nt->getFragment() != '' ) {
$this->showForm( array( array( 'badtitletext' ) ) );
+
return;
}
@@ -413,11 +467,11 @@ class MovePageForm extends UnlistedSpecialPage {
if ( $nt->getNamespace() == NS_FILE
&& !( $this->moveOverShared && $user->isAllowed( 'reupload-shared' ) )
&& !RepoGroup::singleton()->getLocalRepo()->findFile( $nt )
- && wfFindFile( $nt ) )
- {
+ && wfFindFile( $nt )
+ ) {
$this->showForm( array( array( 'file-exists-sharedrepo' ) ) );
- return;
+ return;
}
# Delete to make way if requested
@@ -426,6 +480,7 @@ class MovePageForm extends UnlistedSpecialPage {
if ( count( $permErrors ) ) {
# Only show the first error
$this->showForm( $permErrors );
+
return;
}
@@ -444,6 +499,7 @@ class MovePageForm extends UnlistedSpecialPage {
$deleteStatus = $page->doDeleteArticleReal( $reason, false, 0, true, $error, $user );
if ( !$deleteStatus->isGood() ) {
$this->showForm( $deleteStatus->getErrorsArray() );
+
return;
}
}
@@ -462,6 +518,7 @@ class MovePageForm extends UnlistedSpecialPage {
$error = $ot->moveTo( $nt, true, $this->reason, $createRedirect );
if ( $error !== true ) {
$this->showForm( $error );
+
return;
}
@@ -502,7 +559,7 @@ class MovePageForm extends UnlistedSpecialPage {
# 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.
- if( $ot->isTalkPage() || $nt->isTalkPage() ) {
+ if ( $ot->isTalkPage() || $nt->isTalkPage() ) {
$this->moveTalk = false;
}
@@ -524,24 +581,26 @@ class MovePageForm extends UnlistedSpecialPage {
// @todo FIXME: Use Title::moveSubpages() here
$dbr = wfGetDB( DB_MASTER );
- if( $this->moveSubpages && (
+ if ( $this->moveSubpages && (
MWNamespace::hasSubpages( $nt->getNamespace() ) || (
$this->moveTalk &&
- MWNamespace::hasSubpages( $nt->getTalkPage()->getNamespace() )
+ MWNamespace::hasSubpages( $nt->getTalkPage()->getNamespace() )
)
) ) {
$conds = array(
'page_title' . $dbr->buildLike( $ot->getDBkey() . '/', $dbr->anyString() )
- .' OR page_title = ' . $dbr->addQuotes( $ot->getDBkey() )
+ . ' OR page_title = ' . $dbr->addQuotes( $ot->getDBkey() )
);
$conds['page_namespace'] = array();
- if( MWNamespace::hasSubpages( $nt->getNamespace() ) ) {
+ if ( MWNamespace::hasSubpages( $nt->getNamespace() ) ) {
$conds['page_namespace'][] = $ot->getNamespace();
}
- if( $this->moveTalk && MWNamespace::hasSubpages( $nt->getTalkPage()->getNamespace() ) ) {
+ if ( $this->moveTalk &&
+ MWNamespace::hasSubpages( $nt->getTalkPage()->getNamespace() )
+ ) {
$conds['page_namespace'][] = $ot->getTalkPage()->getNamespace();
}
- } elseif( $this->moveTalk ) {
+ } elseif ( $this->moveTalk ) {
$conds = array(
'page_namespace' => $ot->getTalkPage()->getNamespace(),
'page_title' => $ot->getDBkey()
@@ -552,7 +611,7 @@ class MovePageForm extends UnlistedSpecialPage {
}
$extraPages = array();
- if( !is_null( $conds ) ) {
+ if ( !is_null( $conds ) ) {
$extraPages = TitleArray::newFromResult(
$dbr->select( 'page',
array( 'page_id', 'page_namespace', 'page_title' ),
@@ -564,29 +623,31 @@ class MovePageForm extends UnlistedSpecialPage {
$extraOutput = array();
$count = 1;
- foreach( $extraPages as $oldSubpage ) {
- if( $ot->equals( $oldSubpage ) ) {
+ foreach ( $extraPages as $oldSubpage ) {
+ if ( $ot->equals( $oldSubpage ) || $nt->equals( $oldSubpage ) ) {
# Already did this one.
continue;
}
$newPageName = preg_replace(
- '#^'.preg_quote( $ot->getDBkey(), '#' ).'#',
+ '#^' . preg_quote( $ot->getDBkey(), '#' ) . '#',
StringUtils::escapeRegexReplacement( $nt->getDBkey() ), # bug 21234
$oldSubpage->getDBkey()
);
- if( $oldSubpage->isTalkPage() ) {
+
+ if ( $oldSubpage->isTalkPage() ) {
$newNs = $nt->getTalkPage()->getNamespace();
} else {
$newNs = $nt->getSubjectPage()->getNamespace();
}
+
# Bug 14385: we need makeTitleSafe because the new page names may
# be longer than 255 characters.
$newSubpage = Title::makeTitleSafe( $newNs, $newPageName );
- if( !$newSubpage ) {
+ if ( !$newSubpage ) {
$oldLink = Linker::linkKnown( $oldSubpage );
- $extraOutput[] = $this->msg( 'movepage-page-unmoved' )->rawParams( $oldLink
- )->params( Title::makeName( $newNs, $newPageName ) )->escaped();
+ $extraOutput[] = $this->msg( 'movepage-page-unmoved' )->rawParams( $oldLink )
+ ->params( Title::makeName( $newNs, $newPageName ) )->escaped();
continue;
}
@@ -596,7 +657,8 @@ class MovePageForm extends UnlistedSpecialPage {
$extraOutput[] = $this->msg( 'movepage-page-exists' )->rawParams( $link )->escaped();
} else {
$success = $oldSubpage->moveTo( $newSubpage, true, $this->reason, $createRedirect );
- if( $success === true ) {
+
+ if ( $success === true ) {
if ( $this->fixRedirects ) {
DoubleRedirectJob::fixRedirects( 'move', $oldSubpage, $newSubpage );
}
@@ -606,10 +668,12 @@ class MovePageForm extends UnlistedSpecialPage {
array(),
array( 'redirect' => 'no' )
);
+
$newLink = Linker::linkKnown( $newSubpage );
$extraOutput[] = $this->msg( 'movepage-page-moved' )->rawParams( $oldLink, $newLink )->escaped();
++$count;
- if( $count >= $wgMaximumMovedPages ) {
+
+ if ( $count >= $wgMaximumMovedPages ) {
$extraOutput[] = $this->msg( 'movepage-max-pages' )->numParams( $wgMaximumMovedPages )->escaped();
break;
}
@@ -619,28 +683,15 @@ class MovePageForm extends UnlistedSpecialPage {
$extraOutput[] = $this->msg( 'movepage-page-unmoved' )->rawParams( $oldLink, $newLink )->escaped();
}
}
-
}
- if( $extraOutput !== array() ) {
+ if ( $extraOutput !== array() ) {
$out->addHTML( "<ul>\n<li>" . implode( "</li>\n<li>", $extraOutput ) . "</li>\n</ul>" );
}
# Deal with watches (we don't watch subpages)
- if( $this->watch && $user->isLoggedIn() ) {
- $user->addWatch( $ot );
- $user->addWatch( $nt );
- } else {
- $user->removeWatch( $ot );
- $user->removeWatch( $nt );
- }
-
- # Re-clear the file redirect cache, which may have been polluted by
- # parsing in messages above. See CR r56745.
- # @todo FIXME: Needs a more robust solution inside FileRepo.
- if( $ot->getNamespace() == NS_FILE ) {
- RepoGroup::singleton()->getLocalRepo()->invalidateImageRedirect( $ot );
- }
+ WatchAction::doWatchOrUnwatch( $this->watch, $ot, $user );
+ WatchAction::doWatchOrUnwatch( $this->watch, $nt, $user );
}
function showLogFragment( $title ) {
@@ -651,8 +702,9 @@ class MovePageForm extends UnlistedSpecialPage {
}
function showSubpages( $title ) {
- if( !MWNamespace::hasSubpages( $title->getNamespace() ) )
+ if ( !MWNamespace::hasSubpages( $title->getNamespace() ) ) {
return;
+ }
$subpages = $title->getSubpages();
$count = $subpages instanceof TitleArray ? $subpages->count() : 0;
@@ -663,13 +715,14 @@ class MovePageForm extends UnlistedSpecialPage {
# No subpages.
if ( $count == 0 ) {
$out->addWikiMsg( 'movenosubpage' );
+
return;
}
$out->addWikiMsg( 'movesubpagetext', $this->getLanguage()->formatNum( $count ) );
$out->addHTML( "<ul>\n" );
- foreach( $subpages as $subpage ) {
+ foreach ( $subpages as $subpage ) {
$link = Linker::link( $subpage );
$out->addHTML( "<li>$link</li>\n" );
}
diff --git a/includes/specials/SpecialNewimages.php b/includes/specials/SpecialNewimages.php
index 52cbc3aa..37d29734 100644
--- a/includes/specials/SpecialNewimages.php
+++ b/includes/specials/SpecialNewimages.php
@@ -21,7 +21,6 @@
* @ingroup SpecialPage
*/
class SpecialNewFiles extends IncludableSpecialPage {
-
public function __construct() {
parent::__construct( 'Newimages' );
}
@@ -37,6 +36,7 @@ class SpecialNewFiles extends IncludableSpecialPage {
$form->prepareForm();
$form->displayForm( '' );
}
+
$this->getOutput()->addHTML( $pager->getBody() );
if ( !$this->including() ) {
$this->getOutput()->addHTML( $pager->getNavigationBar() );
@@ -52,7 +52,6 @@ class SpecialNewFiles extends IncludableSpecialPage {
* @ingroup SpecialPage Pager
*/
class NewFilesPager extends ReverseChronologicalPager {
-
/**
* @var ImageGallery
*/
@@ -73,9 +72,10 @@ class NewFilesPager extends ReverseChronologicalPager {
$conds = $jconds = array();
$tables = array( 'image' );
- if( !$this->showbots ) {
+ if ( !$this->showbots ) {
$groupsWithBotPermission = User::getGroupsWithPermission( 'bot' );
- if( count( $groupsWithBotPermission ) ) {
+
+ if ( count( $groupsWithBotPermission ) ) {
$tables[] = 'user_groups';
$conds[] = 'ug_group IS NULL';
$jconds['user_groups'] = array(
@@ -88,11 +88,15 @@ 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 ) {
- $like = $dbr->buildLike( $dbr->anyString(), strtolower( $likeObj->getDBkey() ), $dbr->anyString() );
+ if ( $likeObj instanceof Title ) {
+ $like = $dbr->buildLike(
+ $dbr->anyString(),
+ strtolower( $likeObj->getDBkey() ),
+ $dbr->anyString()
+ );
$conds[] = "LOWER(img_name) $like";
}
}
@@ -113,8 +117,17 @@ class NewFilesPager extends ReverseChronologicalPager {
function getStartBody() {
if ( !$this->gallery ) {
- $this->gallery = new ImageGallery();
+ // Note that null for mode is taken to mean use default.
+ $mode = $this->getRequest()->getVal( 'gallerymode', null );
+ try {
+ $this->gallery = ImageGalleryBase::factory( $mode );
+ } catch ( MWException $e ) {
+ // User specified something invalid, fallback to default.
+ $this->gallery = ImageGalleryBase::factory();
+ }
+ $this->gallery->setContext( $this->getContext() );
}
+
return '';
}
@@ -128,11 +141,12 @@ class NewFilesPager extends ReverseChronologicalPager {
$title = Title::makeTitle( NS_FILE, $name );
$ul = Linker::link( $user->getUserpage(), $user->getName() );
+ $time = $this->getLanguage()->userTimeAndDate( $row->img_timestamp, $this->getUser() );
$this->gallery->add(
$title,
"$ul<br />\n<i>"
- . htmlspecialchars( $this->getLanguage()->userTimeAndDate( $row->img_timestamp, $this->getUser() ) )
+ . htmlspecialchars( $time )
. "</i><br />\n"
);
}
@@ -150,7 +164,6 @@ class NewFilesPager extends ReverseChronologicalPager {
'type' => 'check',
'label' => $this->msg( 'showhidebots', $this->msg( 'show' )->plain() )->escaped(),
'name' => 'showbots',
- # 'default' => $this->getRequest()->getBool( 'showbots', 0 ),
),
'limit' => array(
'type' => 'hidden',
@@ -164,12 +177,13 @@ class NewFilesPager extends ReverseChronologicalPager {
),
);
- if( $wgMiserMode ) {
+ if ( $wgMiserMode ) {
unset( $fields['like'] );
}
- $form = new HTMLForm( $fields, $this->getContext() );
- $form->setTitle( $this->getTitle() );
+ $context = new DerivativeContext( $this->getContext() );
+ $context->setTitle( $this->getTitle() ); // Remove subpage
+ $form = new HTMLForm( $fields, $context );
$form->setSubmitTextMsg( 'ilsubmit' );
$form->setMethod( 'get' );
$form->setWrapperLegendMsg( 'newimages-legend' );
diff --git a/includes/specials/SpecialNewpages.php b/includes/specials/SpecialNewpages.php
index ebb3021d..43d48558 100644
--- a/includes/specials/SpecialNewpages.php
+++ b/includes/specials/SpecialNewpages.php
@@ -27,7 +27,6 @@
* @ingroup SpecialPage
*/
class SpecialNewpages extends IncludableSpecialPage {
-
// Stored objects
/**
@@ -63,17 +62,19 @@ class SpecialNewpages extends IncludableSpecialPage {
$this->customFilters = array();
wfRunHooks( 'SpecialNewPagesFilters', array( $this, &$this->customFilters ) );
- foreach( $this->customFilters as $key => $params ) {
+ foreach ( $this->customFilters as $key => $params ) {
$opts->add( $key, $params['default'] );
}
// Set values
$opts->fetchValuesFromRequest( $this->getRequest() );
- if ( $par ) $this->parseParams( $par );
+ if ( $par ) {
+ $this->parseParams( $par );
+ }
// Validate
$opts->validateIntBounds( 'limit', 0, 5000 );
- if( !$wgEnableNewpagesUserFilter ) {
+ if ( !$wgEnableNewpagesUserFilter ) {
$opts->setValue( 'username', '' );
}
}
@@ -113,7 +114,7 @@ class SpecialNewpages extends IncludableSpecialPage {
}
if ( preg_match( '/^namespace=(.*)$/', $bit, $m ) ) {
$ns = $this->getLanguage()->getNsIndex( $m[1] );
- if( $ns !== false ) {
+ if ( $ns !== false ) {
$this->opts->setValue( 'namespace', $ns );
}
}
@@ -135,13 +136,14 @@ class SpecialNewpages extends IncludableSpecialPage {
$this->showNavigation = !$this->including(); // Maybe changed in setup
$this->setup( $par );
- if( !$this->including() ) {
+ if ( !$this->including() ) {
// Settings
$this->form();
$feedType = $this->opts->getValue( 'feed' );
- if( $feedType ) {
+ if ( $feedType ) {
$this->feed( $feedType );
+
return;
}
@@ -154,7 +156,7 @@ class SpecialNewpages extends IncludableSpecialPage {
$pager->mLimit = $this->opts->getValue( 'limit' );
$pager->mOffset = $this->opts->getValue( 'offset' );
- if( $pager->getNumRows() ) {
+ if ( $pager->getNumRows() ) {
$navigation = '';
if ( $this->showNavigation ) {
$navigation = $pager->getNavigationBar();
@@ -196,7 +198,7 @@ class SpecialNewpages extends IncludableSpecialPage {
foreach ( $filters as $key => $msg ) {
$onoff = 1 - $this->opts->getValue( $key );
$link = Linker::link( $self, $showhide[$onoff], array(),
- array( $key => $onoff ) + $changed
+ array( $key => $onoff ) + $changed
);
$links[$key] = $this->msg( $msg )->rawParams( $link )->escaped();
}
@@ -236,55 +238,55 @@ class SpecialNewpages extends IncludableSpecialPage {
Xml::openElement( 'table', array( 'id' => 'mw-newpages-table' ) ) .
'<tr>
<td class="mw-label">' .
- Xml::label( $this->msg( 'namespace' )->text(), 'namespace' ) .
- '</td>
- <td class="mw-input">' .
- Html::namespaceSelector(
- array(
- 'selected' => $namespace,
- 'all' => 'all',
- ), array(
- 'name' => 'namespace',
- 'id' => 'namespace',
- 'class' => 'namespaceselector',
- )
- ) . '&#160;' .
- Xml::checkLabel(
- $this->msg( 'invert' )->text(),
- 'invert',
- 'nsinvert',
- $nsinvert,
- array( 'title' => $this->msg( 'tooltip-invert' )->text() )
- ) .
- '</td>
+ Xml::label( $this->msg( 'namespace' )->text(), 'namespace' ) .
+ '</td>
+ <td class="mw-input">' .
+ Html::namespaceSelector(
+ array(
+ 'selected' => $namespace,
+ 'all' => 'all',
+ ), array(
+ 'name' => 'namespace',
+ 'id' => 'namespace',
+ 'class' => 'namespaceselector',
+ )
+ ) . '&#160;' .
+ Xml::checkLabel(
+ $this->msg( 'invert' )->text(),
+ 'invert',
+ 'nsinvert',
+ $nsinvert,
+ array( 'title' => $this->msg( 'tooltip-invert' )->text() )
+ ) .
+ '</td>
</tr>' . ( $tagFilter ? (
'<tr>
<td class="mw-label">' .
- $tagFilterLabel .
+ $tagFilterLabel .
'</td>
<td class="mw-input">' .
- $tagFilterSelector .
+ $tagFilterSelector .
'</td>
</tr>' ) : '' ) .
( $wgEnableNewpagesUserFilter ?
- '<tr>
+ '<tr>
<td class="mw-label">' .
Xml::label( $this->msg( 'newpages-username' )->text(), 'mw-np-username' ) .
- '</td>
+ '</td>
<td class="mw-input">' .
Xml::input( 'username', 30, $userText, array( 'id' => 'mw-np-username' ) ) .
- '</td>
+ '</td>
</tr>' : '' ) .
'<tr> <td></td>
<td class="mw-submit">' .
- Xml::submitButton( $this->msg( 'allpagessubmit' )->text() ) .
- '</td>
- </tr>' .
+ Xml::submitButton( $this->msg( 'allpagessubmit' )->text() ) .
+ '</td>
+ </tr>' .
'<tr>
<td></td>
<td class="mw-input">' .
- $this->filterLinks() .
- '</td>
+ $this->filterLinks() .
+ '</td>
</tr>' .
Xml::closeElement( 'table' ) .
Xml::closeElement( 'fieldset' ) .
@@ -295,9 +297,10 @@ class SpecialNewpages extends IncludableSpecialPage {
}
/**
- * Format a row, providing the timestamp, links to the page/history, size, user links, and a comment
+ * Format a row, providing the timestamp, links to the page/history,
+ * size, user links, and a comment
*
- * @param $result Result row
+ * @param object $result Result row
* @return String
*/
public function formatRow( $result ) {
@@ -331,11 +334,8 @@ class SpecialNewpages extends IncludableSpecialPage {
$query = array( 'redirect' => 'no' );
- if( $this->patrollable( $result ) ) {
- $query['rcid'] = $result->rc_id;
- }
-
- // Linker::linkKnown() uses 'known' and 'noclasses' options. This breaks the colouration for stubs.
+ // Linker::linkKnown() uses 'known' and 'noclasses' options.
+ // This breaks the colouration for stubs.
$plink = Linker::link(
$title,
null,
@@ -352,8 +352,12 @@ class SpecialNewpages extends IncludableSpecialPage {
$hist = Html::rawElement( 'span', array( 'class' => 'mw-newpages-history' ),
$this->msg( 'parentheses' )->rawParams( $histLink )->escaped() );
- $length = Html::element( 'span', array( 'class' => 'mw-newpages-length' ),
- $this->msg( 'brackets' )->params( $this->msg( 'nbytes' )->numParams( $result->length )->text() )
+ $length = Html::element(
+ 'span',
+ array( 'class' => 'mw-newpages-length' ),
+ $this->msg( 'brackets' )->params( $this->msg( 'nbytes' )
+ ->numParams( $result->length )->text()
+ )
);
$ulink = Linker::revUserTools( $rev );
@@ -369,8 +373,11 @@ class SpecialNewpages extends IncludableSpecialPage {
}
# Tags, if any.
- if( isset( $result->ts_tags ) ) {
- list( $tagDisplay, $newClasses ) = ChangeTags::formatSummaryRow( $result->ts_tags, 'newpages' );
+ if ( isset( $result->ts_tags ) ) {
+ list( $tagDisplay, $newClasses ) = ChangeTags::formatSummaryRow(
+ $result->ts_tags,
+ 'newpages'
+ );
$classes = array_merge( $classes, $newClasses );
} else {
$tagDisplay = '';
@@ -381,8 +388,10 @@ class SpecialNewpages extends IncludableSpecialPage {
# Display the old title if the namespace/title has been changed
$oldTitleText = '';
$oldTitle = Title::makeTitle( $result->rc_namespace, $result->rc_title );
+
if ( !$title->equals( $oldTitle ) ) {
- $oldTitleText = $this->msg( 'rc-old-title' )->params( $oldTitle->getPrefixedText() )->escaped();
+ $oldTitleText = $oldTitle->getPrefixedText();
+ $oldTitleText = $this->msg( 'rc-old-title' )->params( $oldTitleText )->escaped();
}
return "<li{$css}>{$time} {$dm}{$plink} {$hist} {$dm}{$length} {$dm}{$ulink} {$comment} {$tagDisplay} {$oldTitleText}</li>\n";
@@ -391,7 +400,7 @@ class SpecialNewpages extends IncludableSpecialPage {
/**
* Should a specific result row provide "patrollable" links?
*
- * @param $result Result row
+ * @param object $result Result row
* @return Boolean
*/
protected function patrollable( $result ) {
@@ -408,18 +417,20 @@ class SpecialNewpages extends IncludableSpecialPage {
if ( !$wgFeed ) {
$this->getOutput()->addWikiMsg( 'feed-unavailable' );
+
return;
}
- if( !isset( $wgFeedClasses[$type] ) ) {
+ if ( !isset( $wgFeedClasses[$type] ) ) {
$this->getOutput()->addWikiMsg( 'feed-invalid' );
+
return;
}
$feed = new $wgFeedClasses[$type](
$this->feedTitle(),
$this->msg( 'tagline' )->text(),
- $this->getTitle()->getFullUrl()
+ $this->getTitle()->getFullURL()
);
$pager = new NewPagesPager( $this, $this->opts );
@@ -427,7 +438,7 @@ class SpecialNewpages extends IncludableSpecialPage {
$pager->mLimit = min( $limit, $wgFeedLimit );
$feed->outHeader();
- if( $pager->getNumRows() > 0 ) {
+ if ( $pager->getNumRows() > 0 ) {
foreach ( $pager->mResult as $row ) {
$feed->outItem( $this->feedItem( $row ) );
}
@@ -438,12 +449,13 @@ class SpecialNewpages extends IncludableSpecialPage {
protected function feedTitle() {
global $wgLanguageCode, $wgSitename;
$desc = $this->getDescription();
+
return "$wgSitename - $desc [$wgLanguageCode]";
}
protected function feedItem( $row ) {
$title = Title::makeTitle( intval( $row->rc_namespace ), $row->rc_title );
- if( $title ) {
+ if ( $title ) {
$date = $row->rc_timestamp;
$comments = $title->getTalkPage()->getFullURL();
@@ -466,7 +478,7 @@ class SpecialNewpages extends IncludableSpecialPage {
protected function feedItemDesc( $row ) {
$revision = Revision::newFromId( $row->rev_id );
- if( $revision ) {
+ if ( $revision ) {
//XXX: include content model/type in feed item?
return '<p>' . htmlspecialchars( $revision->getUserText() ) .
$this->msg( 'colon-separator' )->inContentLanguage()->escaped() .
@@ -474,6 +486,7 @@ class SpecialNewpages extends IncludableSpecialPage {
"</p>\n<hr />\n<div>" .
nl2br( htmlspecialchars( $revision->getContent()->serialize() ) ) . "</div>";
}
+
return '';
}
@@ -511,7 +524,7 @@ class NewPagesPager extends ReverseChronologicalPager {
$username = $this->opts->getValue( 'username' );
$user = Title::makeTitleSafe( NS_USER, $username );
- if( $namespace !== false ) {
+ if ( $namespace !== false ) {
if ( $this->opts->getValue( 'invert' ) ) {
$conds[] = 'rc_namespace != ' . $this->mDb->addQuotes( $namespace );
} else {
@@ -523,18 +536,22 @@ class NewPagesPager extends ReverseChronologicalPager {
}
# $wgEnableNewpagesUserFilter - temp WMF hack
- if( $wgEnableNewpagesUserFilter && $user ) {
+ if ( $wgEnableNewpagesUserFilter && $user ) {
$conds['rc_user_text'] = $user->getText();
$rcIndexes = 'rc_user_text';
- # If anons cannot make new pages, don't "exclude logged in users"!
- } elseif( User::groupHasPermission( '*', 'createpage' ) && $this->opts->getValue( 'hideliu' ) ) {
+ } elseif ( User::groupHasPermission( '*', 'createpage' ) &&
+ $this->opts->getValue( 'hideliu' )
+ ) {
+ # If anons cannot make new pages, don't "exclude logged in users"!
$conds['rc_user'] = 0;
}
+
# If this user cannot see patrolled edits or they are off, don't do dumb queries!
- if( $this->opts->getValue( 'hidepatrolled' ) && $this->getUser()->useNPPatrol() ) {
+ if ( $this->opts->getValue( 'hidepatrolled' ) && $this->getUser()->useNPPatrol() ) {
$conds['rc_patrolled'] = 0;
}
- if( $this->opts->getValue( 'hidebots' ) ) {
+
+ if ( $this->opts->getValue( 'hidebots' ) ) {
$conds['rc_bot'] = 0;
}
@@ -546,7 +563,7 @@ class NewPagesPager extends ReverseChronologicalPager {
$tables = array( 'recentchanges', 'page' );
$fields = array(
'rc_namespace', 'rc_title', 'rc_cur_id', 'rc_user', 'rc_user_text',
- 'rc_comment', 'rc_timestamp', 'rc_patrolled','rc_id', 'rc_deleted',
+ 'rc_comment', 'rc_timestamp', 'rc_patrolled', 'rc_id', 'rc_deleted',
'length' => 'page_len', 'rev_id' => 'page_latest', 'rc_this_oldid',
'page_namespace', 'page_title'
);
@@ -556,10 +573,10 @@ class NewPagesPager extends ReverseChronologicalPager {
array( &$this, $this->opts, &$conds, &$tables, &$fields, &$join_conds ) );
$info = array(
- 'tables' => $tables,
- 'fields' => $fields,
- 'conds' => $conds,
- 'options' => array( 'USE INDEX' => array( 'recentchanges' => $rcIndexes ) ),
+ 'tables' => $tables,
+ 'fields' => $fields,
+ 'conds' => $conds,
+ 'options' => array( 'USE INDEX' => array( 'recentchanges' => $rcIndexes ) ),
'join_conds' => $join_conds
);
@@ -593,6 +610,7 @@ class NewPagesPager extends ReverseChronologicalPager {
$linkBatch->add( $row->rc_namespace, $row->rc_title );
}
$linkBatch->execute();
+
return '<ul>';
}
diff --git a/includes/specials/SpecialPagesWithProp.php b/includes/specials/SpecialPagesWithProp.php
index 8f8c981e..e22b42a3 100644
--- a/includes/specials/SpecialPagesWithProp.php
+++ b/includes/specials/SpecialPagesWithProp.php
@@ -23,7 +23,6 @@
* @author Brad Jorsch
*/
-
/**
* Special:PagesWithProp to search the page_props table
* @ingroup SpecialPage
@@ -43,6 +42,7 @@ class SpecialPagesWithProp extends QueryPage {
function execute( $par ) {
$this->setHeaders();
$this->outputHeader();
+ $this->getOutput()->addModuleStyles( 'mediawiki.special.pagesWithProp' );
$request = $this->getRequest();
$propname = $request->getVal( 'propname', $par );
@@ -55,6 +55,7 @@ class SpecialPagesWithProp extends QueryPage {
__METHOD__,
array( 'DISTINCT', 'ORDER BY' => 'pp_propname' )
);
+ $propnames = array();
foreach ( $res as $row ) {
$propnames[$row->pp_propname] = $row->pp_propname;
}
@@ -70,9 +71,8 @@ class SpecialPagesWithProp extends QueryPage {
),
), $this->getContext() );
$form->setMethod( 'get' );
- $form->setAction( $this->getTitle()->getFullUrl() );
$form->setSubmitCallback( array( $this, 'onSubmit' ) );
- $form->setWrapperLegend( $this->msg( 'pageswithprop-legend' ) );
+ $form->setWrapperLegendMsg( 'pageswithprop-legend' );
$form->addHeaderText( $this->msg( 'pageswithprop-text' )->parseAsBlock() );
$form->setSubmitTextMsg( 'pageswithprop-submit' );
@@ -120,15 +120,33 @@ class SpecialPagesWithProp extends QueryPage {
return array( 'page_id' );
}
+ /**
+ * @param Skin $skin
+ * @param object $result Result row
+ * @return string
+ */
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";
+ // Do not show very long or binary values on the special page
+ $valueLength = strlen( $result->pp_value );
+ $isBinary = strpos( $result->pp_value, "\0" ) !== false;
+ $isTooLong = $valueLength > 1024;
+
+ if ( $isBinary || $isTooLong ) {
+ $message = $this
+ ->msg( $isBinary ? 'pageswithprop-prophidden-binary' : 'pageswithprop-prophidden-long' )
+ ->params( $this->getLanguage()->formatSize( $valueLength ) );
+
+ $propValue = Html::element( 'span', array( 'class' => 'prop-value-hidden' ), $message->text() );
+ } else {
+ $propValue = Html::element( 'span', array( 'class' => 'prop-value' ), $result->pp_value );
+ }
+
+ $ret .= $this->msg( 'colon-separator' )->escaped() . $propValue;
}
+
return $ret;
}
diff --git a/includes/specials/SpecialPasswordReset.php b/includes/specials/SpecialPasswordReset.php
index 90b0ac80..c486ba01 100644
--- a/includes/specials/SpecialPasswordReset.php
+++ b/includes/specials/SpecialPasswordReset.php
@@ -27,19 +27,23 @@
* @ingroup SpecialPage
*/
class SpecialPasswordReset extends FormSpecialPage {
-
/**
* @var Message
*/
private $email;
/**
+ * @var User
+ */
+ private $firstUser;
+
+ /**
* @var Status
*/
private $result;
public function __construct() {
- parent::__construct( 'PasswordReset' );
+ parent::__construct( 'PasswordReset', 'editmyprivateinfo' );
}
public function userCanExecute( User $user ) {
@@ -65,7 +69,8 @@ class SpecialPasswordReset extends FormSpecialPage {
'type' => 'text',
'label-message' => 'passwordreset-username',
);
- if( $this->getUser()->isLoggedIn() ) {
+
+ if ( $this->getUser()->isLoggedIn() ) {
$a['Username']['default'] = $this->getUser()->getName();
}
}
@@ -86,7 +91,7 @@ class SpecialPasswordReset extends FormSpecialPage {
);
}
- if( $this->getUser()->isAllowed( 'passwordreset' ) ) {
+ if ( $this->getUser()->isAllowed( 'passwordreset' ) ) {
$a['Capture'] = array(
'type' => 'check',
'label-message' => 'passwordreset-capture',
@@ -98,11 +103,15 @@ class SpecialPasswordReset extends FormSpecialPage {
}
public function alterForm( HTMLForm $form ) {
- $form->setSubmitTextMsg( 'mailmypassword' );
- }
-
- protected function preText() {
global $wgPasswordResetRoutes;
+
+ $form->setDisplayFormat( 'vform' );
+ // Turn the old-school line around the form off.
+ // XXX This wouldn't be necessary here if we could set the format of
+ // the HTMLForm to 'vform' at its creation, but there's no way to do so
+ // from a FormSpecialPage class.
+ $form->setWrapperLegend( false );
+
$i = 0;
if ( isset( $wgPasswordResetRoutes['username'] ) && $wgPasswordResetRoutes['username'] ) {
$i++;
@@ -113,7 +122,11 @@ class SpecialPasswordReset extends FormSpecialPage {
if ( isset( $wgPasswordResetRoutes['domain'] ) && $wgPasswordResetRoutes['domain'] ) {
$i++;
}
- return $this->msg( 'passwordreset-pretext', $i )->parseAsBlock();
+
+ $message = ( $i > 1 ) ? 'passwordreset-text-many' : 'passwordreset-text-one';
+
+ $form->setHeaderText( $this->msg( $message, $i )->parseAsBlock() );
+ $form->setSubmitTextMsg( 'mailmypassword' );
}
/**
@@ -136,8 +149,9 @@ class SpecialPasswordReset extends FormSpecialPage {
}
}
- 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
+ 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' );
}
@@ -151,8 +165,8 @@ class SpecialPasswordReset extends FormSpecialPage {
$users = array( User::newFromName( $data['Username'] ) );
} elseif ( isset( $data['Email'] )
&& $data['Email'] !== ''
- && Sanitizer::validateEmail( $data['Email'] ) )
- {
+ && Sanitizer::validateEmail( $data['Email'] )
+ ) {
$method = 'email';
$res = wfGetDB( DB_SLAVE )->select(
'user',
@@ -160,9 +174,11 @@ class SpecialPasswordReset extends FormSpecialPage {
array( 'user_email' => $data['Email'] ),
__METHOD__
);
+
if ( $res ) {
$users = array();
- foreach( $res as $row ) {
+
+ foreach ( $res as $row ) {
$users[] = User::newFromRow( $row );
}
} else {
@@ -180,8 +196,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 {
@@ -204,9 +220,13 @@ class SpecialPasswordReset extends FormSpecialPage {
foreach ( $users as $user ) {
if ( $user->isPasswordReminderThrottled() ) {
global $wgPasswordReminderResendTime;
+
# Round the time in hours to 3 d.p., in case someone is specifying
# minutes or seconds.
- return array( array( 'throttled-mailpassword', round( $wgPasswordReminderResendTime, 3 ) ) );
+ return array( array(
+ 'throttled-mailpassword',
+ round( $wgPasswordReminderResendTime, 3 )
+ ) );
}
}
@@ -239,8 +259,8 @@ class SpecialPasswordReset extends FormSpecialPage {
$password = $user->randomPassword();
$user->setNewpassword( $password );
$user->saveSettings();
- $passwords[] = $this->msg( 'passwordreset-emailelement', $user->getName(), $password
- )->inLanguage( $userLanguage )->text(); // We'll escape the whole thing later
+ $passwords[] = $this->msg( 'passwordreset-emailelement', $user->getName(), $password )
+ ->inLanguage( $userLanguage )->text(); // We'll escape the whole thing later
}
$passwordBlock = implode( "\n\n", $passwords );
@@ -249,7 +269,7 @@ class SpecialPasswordReset extends FormSpecialPage {
$username,
$passwordBlock,
count( $passwords ),
- '<' . Title::newMainPage()->getCanonicalUrl() . '>',
+ '<' . Title::newMainPage()->getCanonicalURL() . '>',
round( $wgNewPasswordExpiry / 86400 )
);
@@ -257,31 +277,36 @@ class SpecialPasswordReset extends FormSpecialPage {
$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'] ) {
+ if ( isset( $data['Capture'] ) && $data['Capture'] ) {
+ // Save the user, will be used if an error occurs when sending the email
+ $this->firstUser = $firstUser;
+ } else {
+ // Blank the email if the user is not supposed to see it
$this->email = null;
}
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 {
- // @todo FIXME: The email didn't send, but we have already set the password throttle
- // timestamp, so they won't be able to try again until it expires... :(
+ // @todo FIXME: The email wasn't sent, but we have already set
+ // the password throttle timestamp, so they won't be able to try
+ // again until it expires... :(
return array( array( 'mailerror', $this->result->getMessage() ) );
}
}
public function onSuccess() {
- if( $this->getUser()->isAllowed( 'passwordreset' ) && $this->email != null ) {
- // @todo: Logging
+ 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() );
+ $this->getOutput()->addWikiMsg( 'passwordreset-emailerror-capture',
+ $this->result->getMessage(), $this->firstUser->getName() );
}
$this->getOutput()->addHTML( Html::rawElement( 'pre', array(), $this->email->escaped() ) );
@@ -292,12 +317,12 @@ class SpecialPasswordReset extends FormSpecialPage {
}
protected function canChangePassword( User $user ) {
- global $wgPasswordResetRoutes, $wgAuth;
+ global $wgPasswordResetRoutes, $wgEnableEmail, $wgAuth;
// Maybe password resets are disabled, or there are no allowable routes
if ( !is_array( $wgPasswordResetRoutes ) ||
- !in_array( true, array_values( $wgPasswordResetRoutes ) ) )
- {
+ !in_array( true, array_values( $wgPasswordResetRoutes ) )
+ ) {
return 'passwordreset-disabled';
}
@@ -306,6 +331,11 @@ class SpecialPasswordReset extends FormSpecialPage {
return 'resetpass_forbidden';
}
+ // Maybe email features have been disabled
+ if ( !$wgEnableEmail ) {
+ return 'passwordreset-emaildisabled';
+ }
+
// Maybe the user is blocked (check this here rather than relying on the parent
// method as we have a more specific error message to use here
if ( $user->isBlocked() ) {
diff --git a/includes/specials/SpecialPopularpages.php b/includes/specials/SpecialPopularpages.php
index 7ce8c13f..2a80f651 100644
--- a/includes/specials/SpecialPopularpages.php
+++ b/includes/specials/SpecialPopularpages.php
@@ -27,7 +27,6 @@
* @ingroup SpecialPage
*/
class PopularPagesPage extends QueryPage {
-
function __construct( $name = 'Popularpages' ) {
parent::__construct( $name );
}
@@ -42,27 +41,37 @@ class PopularPagesPage extends QueryPage {
}
function getQueryInfo() {
- return array (
+ return array(
'tables' => array( 'page' ),
- 'fields' => array( 'namespace' => 'page_namespace',
- 'title' => 'page_title',
- 'value' => 'page_counter' ),
- 'conds' => array( 'page_is_redirect' => 0,
- 'page_namespace' => MWNamespace::getContentNamespaces() ) );
+ 'fields' => array(
+ 'namespace' => 'page_namespace',
+ 'title' => 'page_title',
+ 'value' => 'page_counter' ),
+ 'conds' => array(
+ 'page_is_redirect' => 0,
+ 'page_namespace' => MWNamespace::getContentNamespaces()
+ )
+ );
}
/**
- * @param $skin Skin
- * @param $result
+ * @param Skin $skin
+ * @param object $result Result row
* @return string
*/
function formatResult( $skin, $result ) {
global $wgContLang;
$title = Title::makeTitleSafe( $result->namespace, $result->title );
- if( !$title ) {
- return Html::element( 'span', array( 'class' => 'mw-invalidtitle' ),
- Linker::getInvalidTitleDescription( $this->getContext(), $result->namespace, $result->title ) );
+ if ( !$title ) {
+ return Html::element(
+ 'span',
+ array( 'class' => 'mw-invalidtitle' ),
+ Linker::getInvalidTitleDescription(
+ $this->getContext(),
+ $result->namespace,
+ $result->title )
+ );
}
$link = Linker::linkKnown(
@@ -70,6 +79,7 @@ class PopularPagesPage extends QueryPage {
htmlspecialchars( $wgContLang->convert( $title->getPrefixedText() ) )
);
$nv = $this->msg( 'nviews' )->numParams( $result->value )->escaped();
+
return $this->getLanguage()->specialList( $link, $nv );
}
diff --git a/includes/specials/SpecialPreferences.php b/includes/specials/SpecialPreferences.php
index a50e7c18..ecee0bb7 100644
--- a/includes/specials/SpecialPreferences.php
+++ b/includes/specials/SpecialPreferences.php
@@ -35,16 +35,21 @@ class SpecialPreferences extends SpecialPage {
$this->setHeaders();
$this->outputHeader();
$out = $this->getOutput();
- $out->disallowUserJs(); # Prevent hijacked user scripts from sniffing passwords etc.
+ $out->disallowUserJs(); # Prevent hijacked user scripts from sniffing passwords etc.
$user = $this->getUser();
if ( $user->isAnon() ) {
- throw new ErrorPageError( 'prefsnologin', 'prefsnologintext', array( $this->getTitle()->getPrefixedDBkey() ) );
+ throw new ErrorPageError(
+ 'prefsnologin',
+ 'prefsnologintext',
+ array( $this->getTitle()->getPrefixedDBkey() )
+ );
}
$this->checkReadOnly();
if ( $par == 'reset' ) {
$this->showResetForm();
+
return;
}
@@ -52,7 +57,7 @@ class SpecialPreferences extends SpecialPage {
if ( $this->getRequest()->getCheck( 'success' ) ) {
$out->wrapWikiMsg(
- "<div class=\"successbox\"><strong>\n$1\n</strong></div><div id=\"mw-pref-clear\"></div>",
+ "<div class=\"successbox\">\n$1\n</div>",
'savedprefs'
);
}
@@ -64,12 +69,17 @@ class SpecialPreferences extends SpecialPage {
}
private function showResetForm() {
+ if ( !$this->getUser()->isAllowed( 'editmyoptions' ) ) {
+ throw new PermissionsError( 'editmyoptions' );
+ }
+
$this->getOutput()->addWikiMsg( 'prefs-reset-intro' );
- $htmlForm = new HTMLForm( array(), $this->getContext(), 'prefs-restore' );
+ $context = new DerivativeContext( $this->getContext() );
+ $context->setTitle( $this->getTitle( 'reset' ) ); // Reset subpage
+ $htmlForm = new HTMLForm( array(), $context, 'prefs-restore' );
$htmlForm->setSubmitTextMsg( 'restoreprefs' );
- $htmlForm->setTitle( $this->getTitle( 'reset' ) );
$htmlForm->setSubmitCallback( array( $this, 'submitReset' ) );
$htmlForm->suppressReset();
@@ -77,8 +87,12 @@ class SpecialPreferences extends SpecialPage {
}
public function submitReset( $formData ) {
+ if ( !$this->getUser()->isAllowed( 'editmyoptions' ) ) {
+ throw new PermissionsError( 'editmyoptions' );
+ }
+
$user = $this->getUser();
- $user->resetOptions( 'all' );
+ $user->resetOptions( 'all', $this->getContext() );
$user->saveSettings();
$url = $this->getTitle()->getFullURL( 'success' );
diff --git a/includes/specials/SpecialPrefixindex.php b/includes/specials/SpecialPrefixindex.php
index 6affa735..28d07ffc 100644
--- a/includes/specials/SpecialPrefixindex.php
+++ b/includes/specials/SpecialPrefixindex.php
@@ -27,6 +27,15 @@
* @ingroup SpecialPage
*/
class SpecialPrefixindex extends SpecialAllpages {
+
+ /**
+ * Whether to remove the searched prefix from the displayed link. Useful
+ * for inclusion of a set of sub pages in a root page.
+ */
+ protected $stripPrefix = false;
+
+ protected $hideRedirects = false;
+
// Inherit $maxPerPage
function __construct() {
@@ -52,7 +61,8 @@ class SpecialPrefixindex extends SpecialAllpages {
$prefix = $request->getVal( 'prefix', '' );
$ns = $request->getIntOrNull( 'namespace' );
$namespace = (int)$ns; // if no namespace given, use 0 (NS_MAIN).
- $hideredirects = $request->getBool( 'hideredirects', false );
+ $this->hideRedirects = $request->getBool( 'hideredirects', $this->hideRedirects );
+ $this->stripPrefix = $request->getBool( 'stripprefix', $this->stripPrefix );
$namespaces = $wgContLang->getNamespaces();
$out->setPageTitle(
@@ -62,11 +72,11 @@ class SpecialPrefixindex extends SpecialAllpages {
);
$showme = '';
- if( isset( $par ) ) {
+ if ( isset( $par ) ) {
$showme = $par;
- } elseif( $prefix != '' ) {
+ } elseif ( $prefix != '' ) {
$showme = $prefix;
- } elseif( $from != '' && $ns === null ) {
+ } elseif ( $from != '' && $ns === null ) {
// For back-compat with Special:Allpages
// Don't do this if namespace is passed, so paging works when doing NS views.
$showme = $from;
@@ -74,9 +84,9 @@ class SpecialPrefixindex extends SpecialAllpages {
// Bug 27864: if transcluded, show all pages instead of the form.
if ( $this->including() || $showme != '' || $ns !== null ) {
- $this->showPrefixChunk( $namespace, $showme, $from, $hideredirects );
+ $this->showPrefixChunk( $namespace, $showme, $from );
} else {
- $out->addHTML( $this->namespacePrefixForm( $namespace, null, $hideredirects ) );
+ $out->addHTML( $this->namespacePrefixForm( $namespace, null ) );
}
}
@@ -84,10 +94,9 @@ class SpecialPrefixindex extends SpecialAllpages {
* HTML for the top form
* @param $namespace Integer: a namespace constant (default NS_MAIN).
* @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 ) {
+ protected function namespacePrefixForm( $namespace = NS_MAIN, $from = '' ) {
global $wgScript;
$out = Xml::openElement( 'div', array( 'class' => 'namespaceoptions' ) );
@@ -98,37 +107,44 @@ class SpecialPrefixindex extends SpecialAllpages {
$out .= Xml::openElement( 'table', array( 'id' => 'nsselect', 'class' => 'allpages' ) );
$out .= "<tr>
<td class='mw-label'>" .
- Xml::label( $this->msg( 'allpagesprefix' )->text(), 'nsfrom' ) .
- "</td>
+ Xml::label( $this->msg( 'allpagesprefix' )->text(), 'nsfrom' ) .
+ "</td>
<td class='mw-input'>" .
- Xml::input( 'prefix', 30, str_replace( '_', ' ', $from ), array( 'id' => 'nsfrom' ) ) .
- "</td>
+ Xml::input( 'prefix', 30, str_replace( '_', ' ', $from ), array( 'id' => 'nsfrom' ) ) .
+ "</td>
</tr>
<tr>
- <td class='mw-label'>" .
- Xml::label( $this->msg( 'namespace' )->text(), 'namespace' ) .
- "</td>
+ <td class='mw-label'>" .
+ Xml::label( $this->msg( 'namespace' )->text(), 'namespace' ) .
+ "</td>
<td class='mw-input'>" .
- Html::namespaceSelector( array(
- 'selected' => $namespace,
- ), array(
- 'name' => 'namespace',
- 'id' => 'namespace',
- 'class' => 'namespaceselector',
- ) ) .
- Xml::checkLabel(
- $this->msg( 'allpages-hide-redirects' )->text(),
- 'hideredirects',
- 'hideredirects',
- $hideredirects
- ) . ' ' .
- Xml::submitButton( $this->msg( 'allpagessubmit' )->text() ) .
- "</td>
+ Html::namespaceSelector( array(
+ 'selected' => $namespace,
+ ), array(
+ 'name' => 'namespace',
+ 'id' => 'namespace',
+ 'class' => 'namespaceselector',
+ ) ) .
+ Xml::checkLabel(
+ $this->msg( 'allpages-hide-redirects' )->text(),
+ 'hideredirects',
+ 'hideredirects',
+ $this->hideRedirects
+ ) . ' ' .
+ Xml::checkLabel(
+ $this->msg( 'prefixindex-strip' )->text(),
+ 'stripprefix',
+ 'stripprefix',
+ $this->stripPrefix
+ ) . ' ' .
+ Xml::submitButton( $this->msg( 'allpagessubmit' )->text() ) .
+ "</td>
</tr>";
$out .= Xml::closeElement( 'table' );
$out .= Xml::closeElement( 'fieldset' );
$out .= Xml::closeElement( 'form' );
$out .= Xml::closeElement( 'div' );
+
return $out;
}
@@ -136,9 +152,8 @@ class SpecialPrefixindex extends SpecialAllpages {
* @param $namespace Integer, default NS_MAIN
* @param $prefix String
* @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 ) {
+ protected function showPrefixChunk( $namespace = NS_MAIN, $prefix, $from = null ) {
global $wgContLang;
if ( $from === null ) {
@@ -169,7 +184,7 @@ class SpecialPrefixindex extends SpecialAllpages {
'page_title >= ' . $dbr->addQuotes( $fromKey ),
);
- if ( $hideredirects ) {
+ if ( $this->hideRedirects ) {
$conds['page_is_redirect'] = 0;
}
@@ -178,8 +193,8 @@ class SpecialPrefixindex extends SpecialAllpages {
$conds,
__METHOD__,
array(
- 'ORDER BY' => 'page_title',
- 'LIMIT' => $this->maxPerPage + 1,
+ 'ORDER BY' => 'page_title',
+ 'LIMIT' => $this->maxPerPage + 1,
'USE INDEX' => 'name_title',
)
);
@@ -187,34 +202,42 @@ class SpecialPrefixindex extends SpecialAllpages {
### @todo FIXME: Side link to previous
$n = 0;
- if( $res->numRows() > 0 ) {
+ if ( $res->numRows() > 0 ) {
$out = Xml::openElement( 'table', array( 'id' => 'mw-prefixindex-list-table' ) );
- while( ( $n < $this->maxPerPage ) && ( $s = $res->fetchObject() ) ) {
+ $prefixLength = strlen( $prefix );
+ while ( ( $n < $this->maxPerPage ) && ( $s = $res->fetchObject() ) ) {
$t = Title::makeTitle( $s->page_namespace, $s->page_title );
- if( $t ) {
- $link = ($s->page_is_redirect ? '<div class="allpagesredirect">' : '' ) .
+ if ( $t ) {
+ $displayed = $t->getText();
+ // Try not to generate unclickable links
+ if ( $this->stripPrefix && $prefixLength !== strlen( $displayed ) ) {
+ $displayed = substr( $displayed, $prefixLength );
+ }
+ $link = ( $s->page_is_redirect ? '<div class="allpagesredirect">' : '' ) .
Linker::linkKnown(
$t,
- htmlspecialchars( $t->getText() ),
+ htmlspecialchars( $displayed ),
$s->page_is_redirect ? array( 'class' => 'mw-redirect' ) : array()
) .
- ($s->page_is_redirect ? '</div>' : '' );
+ ( $s->page_is_redirect ? '</div>' : '' );
} else {
$link = '[[' . htmlspecialchars( $s->page_title ) . ']]';
}
- if( $n % 3 == 0 ) {
+ if ( $n % 3 == 0 ) {
$out .= '<tr>';
}
$out .= "<td>$link</td>";
$n++;
- if( $n % 3 == 0 ) {
+ if ( $n % 3 == 0 ) {
$out .= '</tr>';
}
}
- if( ($n % 3) != 0 ) {
+
+ if ( $n % 3 != 0 ) {
$out .= '</tr>';
}
+
$out .= Xml::closeElement( 'table' );
} else {
$out = '';
@@ -225,37 +248,45 @@ class SpecialPrefixindex extends SpecialAllpages {
if ( $this->including() ) {
$out2 = '';
} else {
- $nsForm = $this->namespacePrefixForm( $namespace, $prefix, $hideredirects );
+ $nsForm = $this->namespacePrefixForm( $namespace, $prefix );
$self = $this->getTitle();
$out2 = Xml::openElement( 'table', array( 'id' => 'mw-prefixindex-nav-table' ) ) .
'<tr>
<td>' .
- $nsForm .
- '</td>
- <td id="mw-prefixindex-nav-form" class="mw-prefixindex-nav">';
+ $nsForm .
+ '</td>
+ <td id="mw-prefixindex-nav-form" class="mw-prefixindex-nav">';
- if( isset( $res ) && $res && ( $n == $this->maxPerPage ) && ( $s = $res->fetchObject() ) ) {
+ if ( isset( $res ) && $res && ( $n == $this->maxPerPage ) &&
+ ( $s = $res->fetchObject() )
+ ) {
$query = array(
'from' => $s->page_title,
'prefix' => $prefix,
- 'hideredirects' => $hideredirects,
+ 'hideredirects' => $this->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(),
- array(),
- $query
- );
+ $self,
+ $this->msg( 'nextpage', str_replace( '_', ' ', $s->page_title ) )->escaped(),
+ array(),
+ $query
+ );
+
$out2 .= $nextLink;
- $footer = "\n" . Html::element( "hr" )
- . Html::rawElement( "div", array( "class" => "mw-prefixindex-nav" ), $nextLink );
+ $footer = "\n" . Html::element( 'hr' ) .
+ Html::rawElement(
+ 'div',
+ array( 'class' => 'mw-prefixindex-nav' ),
+ $nextLink
+ );
}
$out2 .= "</td></tr>" .
Xml::closeElement( 'table' );
diff --git a/includes/specials/SpecialProtectedpages.php b/includes/specials/SpecialProtectedpages.php
index cdf053ec..3de6ea24 100644
--- a/includes/specials/SpecialProtectedpages.php
+++ b/includes/specials/SpecialProtectedpages.php
@@ -40,7 +40,7 @@ class SpecialProtectedpages extends SpecialPage {
$this->outputHeader();
// Purge expired entries on one in every 10 queries
- if( !mt_rand( 0, 10 ) ) {
+ if ( !mt_rand( 0, 10 ) ) {
Title::purgeExpiredRestrictions();
}
@@ -49,19 +49,37 @@ class SpecialProtectedpages extends SpecialPage {
$level = $request->getVal( $this->IdLevel );
$sizetype = $request->getVal( 'sizetype' );
$size = $request->getIntOrNull( 'size' );
- $NS = $request->getIntOrNull( 'namespace' );
+ $ns = $request->getIntOrNull( 'namespace' );
$indefOnly = $request->getBool( 'indefonly' ) ? 1 : 0;
$cascadeOnly = $request->getBool( 'cascadeonly' ) ? 1 : 0;
- $pager = new ProtectedPagesPager( $this, array(), $type, $level, $NS, $sizetype, $size, $indefOnly, $cascadeOnly );
-
- $this->getOutput()->addHTML( $this->showOptions( $NS, $type, $level, $sizetype, $size, $indefOnly, $cascadeOnly ) );
+ $pager = new ProtectedPagesPager(
+ $this,
+ array(),
+ $type,
+ $level,
+ $ns,
+ $sizetype,
+ $size,
+ $indefOnly,
+ $cascadeOnly
+ );
- if( $pager->getNumRows() ) {
+ $this->getOutput()->addHTML( $this->showOptions(
+ $ns,
+ $type,
+ $level,
+ $sizetype,
+ $size,
+ $indefOnly,
+ $cascadeOnly
+ ) );
+
+ if ( $pager->getNumRows() ) {
$this->getOutput()->addHTML(
$pager->getNavigationBar() .
- '<ul>' . $pager->getBody() . '</ul>' .
- $pager->getNavigationBar()
+ '<ul>' . $pager->getBody() . '</ul>' .
+ $pager->getNavigationBar()
);
} else {
$this->getOutput()->addWikiMsg( 'protectedpagesempty' );
@@ -78,26 +96,39 @@ 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";
+ if ( !$title ) {
+ wfProfileOut( __METHOD__ );
+
+ 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 ();
+ $description_items = array();
+ // Messages: restriction-level-sysop, restriction-level-autoconfirmed
$protType = $this->msg( 'restriction-level-' . $row->pr_level )->escaped();
$description_items[] = $protType;
- if( $row->pr_cascade ) {
+ if ( $row->pr_cascade ) {
$description_items[] = $this->msg( 'protect-summary-cascade' )->text();
}
@@ -105,7 +136,7 @@ class SpecialProtectedpages extends SpecialPage {
$lang = $this->getLanguage();
$expiry = $lang->formatExpiry( $row->pr_expiry, TS_MW );
- if( $expiry != $infinity ) {
+ if ( $expiry != $infinity ) {
$user = $this->getUser();
$description_items[] = $this->msg(
'protect-expiring-local',
@@ -115,12 +146,13 @@ 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 );
}
- # Show a link to the change protection form for allowed users otherwise a link to the protection log
- if( $this->getUser()->isAllowed( 'protect' ) ) {
+ // Show a link to the change protection form for allowed users otherwise
+ // a link to the protection log
+ if ( $this->getUser()->isAllowed( 'protect' ) ) {
$changeProtection = Linker::linkKnown(
$title,
$this->msg( 'protect_change' )->escaped(),
@@ -140,14 +172,17 @@ class SpecialProtectedpages extends SpecialPage {
);
}
- $changeProtection = ' ' . $this->msg( 'parentheses' )->rawParams( $changeProtection )->escaped();
+ $changeProtection = ' ' . $this->msg( 'parentheses' )->rawParams( $changeProtection )
+ ->escaped();
wfProfileOut( __METHOD__ );
return Html::rawElement(
'li',
array(),
- $lang->specialList( $link . $stxt, $lang->commaList( $description_items ), false ) . $changeProtection ) . "\n";
+ $lang->specialList( $link . $stxt, $lang->commaList( $description_items ), false ) .
+ $changeProtection
+ ) . "\n";
}
/**
@@ -160,9 +195,13 @@ class SpecialProtectedpages extends SpecialPage {
* @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 ) ) .
Xml::openElement( 'fieldset' ) .
Xml::element( 'legend', array(), $this->msg( 'protectedpages' )->text() ) .
@@ -196,8 +235,8 @@ class SpecialProtectedpages extends SpecialPage {
'all' => '',
'label' => $this->msg( 'namespace' )->text()
), array(
- 'name' => 'namespace',
- 'id' => 'namespace',
+ 'name' => 'namespace',
+ 'id' => 'namespace',
'class' => 'namespaceselector',
)
)
@@ -205,31 +244,54 @@ class SpecialProtectedpages extends SpecialPage {
}
/**
+ * @param bool $indefOnly
* @return string Formatted HTML
*/
protected function getExpiryCheck( $indefOnly ) {
- return
- Xml::checkLabel( $this->msg( 'protectedpages-indef' )->text(), 'indefonly', 'indefonly', $indefOnly ) . "\n";
+ return Xml::checkLabel(
+ $this->msg( 'protectedpages-indef' )->text(),
+ 'indefonly',
+ 'indefonly',
+ $indefOnly
+ ) . "\n";
}
/**
+ * @param bool $cascadeOnly
* @return string Formatted HTML
*/
protected function getCascadeCheck( $cascadeOnly ) {
- return
- Xml::checkLabel( $this->msg( 'protectedpages-cascade' )->text(), 'cascadeonly', 'cascadeonly', $cascadeOnly ) . "\n";
+ return Xml::checkLabel(
+ $this->msg( 'protectedpages-cascade' )->text(),
+ 'cascadeonly',
+ 'cascadeonly',
+ $cascadeOnly
+ ) . "\n";
}
/**
+ * @param string $sizetype "min" or "max"
+ * @param mixed $size
* @return string Formatted HTML
*/
protected function getSizeLimit( $sizetype, $size ) {
$max = $sizetype === 'max';
- return
- Xml::radioLabel( $this->msg( 'minimum-size' )->text(), 'sizetype', 'min', 'wpmin', !$max ) .
+ return Xml::radioLabel(
+ $this->msg( 'minimum-size' )->text(),
+ 'sizetype',
+ 'min',
+ 'wpmin',
+ !$max
+ ) .
'&#160;' .
- Xml::radioLabel( $this->msg( 'maximum-size' )->text(), 'sizetype', 'max', 'wpmax', $max ) .
+ Xml::radioLabel(
+ $this->msg( 'maximum-size' )->text(),
+ 'sizetype',
+ 'max',
+ 'wpmax',
+ $max
+ ) .
'&#160;' .
Xml::input( 'size', 9, $size, array( 'id' => 'wpsize' ) ) .
'&#160;' .
@@ -246,14 +308,15 @@ class SpecialProtectedpages extends SpecialPage {
$options = array();
// First pass to load the log names
- foreach( Title::getFilteredRestrictionTypes( true ) as $type ) {
+ foreach ( Title::getFilteredRestrictionTypes( true ) as $type ) {
+ // Messages: restriction-edit, restriction-move, restriction-create, restriction-upload
$text = $this->msg( "restriction-$type" )->text();
$m[$text] = $type;
}
// Third pass generates sorted XHTML content
- foreach( $m as $text => $type ) {
- $selected = ($type == $pr_type );
+ foreach ( $m as $text => $type ) {
+ $selected = ( $type == $pr_type );
$options[] = Xml::option( $text, $type, $selected ) . "\n";
}
@@ -272,21 +335,22 @@ class SpecialProtectedpages extends SpecialPage {
protected function getLevelMenu( $pr_level ) {
global $wgRestrictionLevels;
- $m = array( $this->msg( 'restriction-level-all' )->text() => 0 ); // Temporary array
+ // Temporary array
+ $m = array( $this->msg( 'restriction-level-all' )->text() => 0 );
$options = array();
// First pass to load the log names
- foreach( $wgRestrictionLevels as $type ) {
+ 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;
}
}
// Third pass generates sorted XHTML content
- foreach( $m as $text => $type ) {
- $selected = ($type == $pr_level );
+ foreach ( $m as $text => $type ) {
+ $selected = ( $type == $pr_level );
$options[] = Xml::option( $text, $type, $selected );
}
@@ -310,9 +374,9 @@ 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,
- $indefonly = false, $cascadeonly = false )
- {
+ function __construct( $form, $conds = array(), $type, $level, $namespace,
+ $sizetype = '', $size = 0, $indefonly = false, $cascadeonly = false
+ ) {
$this->mForm = $form;
$this->mConds = $conds;
$this->type = ( $type ) ? $type : 'edit';
@@ -332,6 +396,7 @@ class ProtectedPagesPager extends AlphabeticPager {
$lb->add( $row->page_namespace, $row->page_title );
}
$lb->execute();
+
return '';
}
@@ -342,27 +407,31 @@ class ProtectedPagesPager extends AlphabeticPager {
function getQueryInfo() {
$conds = $this->mConds;
$conds[] = '(pr_expiry>' . $this->mDb->addQuotes( $this->mDb->timestamp() ) .
- 'OR pr_expiry IS NULL)';
+ 'OR pr_expiry IS NULL)';
$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 ) {
- $conds[] = "pr_expiry = {$this->mDb->addQuotes( $this->mDb->getInfinity() )} OR pr_expiry IS NULL";
+ if ( $this->indefonly ) {
+ $infinity = $this->mDb->addQuotes( $this->mDb->getInfinity() );
+ $conds[] = "pr_expiry = $infinity OR pr_expiry IS NULL";
}
- if( $this->cascadeonly ) {
+ if ( $this->cascadeonly ) {
$conds[] = 'pr_cascade = 1';
}
- if( $this->level )
+ 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' => array( 'pr_id', 'page_namespace', 'page_title', 'page_len',
diff --git a/includes/specials/SpecialProtectedtitles.php b/includes/specials/SpecialProtectedtitles.php
index 0cba5ebd..078e7b12 100644
--- a/includes/specials/SpecialProtectedtitles.php
+++ b/includes/specials/SpecialProtectedtitles.php
@@ -27,7 +27,6 @@
* @ingroup SpecialPage
*/
class SpecialProtectedtitles extends SpecialPage {
-
protected $IdLevel = 'level';
protected $IdType = 'type';
@@ -58,8 +57,8 @@ class SpecialProtectedtitles extends SpecialPage {
if ( $pager->getNumRows() ) {
$this->getOutput()->addHTML(
$pager->getNavigationBar() .
- '<ul>' . $pager->getBody() . '</ul>' .
- $pager->getNavigationBar()
+ '<ul>' . $pager->getBody() . '</ul>' .
+ $pager->getNavigationBar()
);
} else {
$this->getOutput()->addWikiMsg( 'protectedtitlesempty' );
@@ -69,6 +68,7 @@ class SpecialProtectedtitles extends SpecialPage {
/**
* Callback function to output a restriction
*
+ * @param object $row Database row
* @return string
*/
function formatRow( $row ) {
@@ -76,28 +76,40 @@ 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";
+ if ( !$title ) {
+ wfProfileOut( __METHOD__ );
+
+ 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 ();
-
+ $description_items = array();
+ // Messages: restriction-level-sysop, restriction-level-autoconfirmed
$protType = $this->msg( 'restriction-level-' . $row->pt_create_perm )->escaped();
-
$description_items[] = $protType;
-
$lang = $this->getLanguage();
- $expiry = strlen( $row->pt_expiry ) ? $lang->formatExpiry( $row->pt_expiry, TS_MW ) : $infinity;
- if( $expiry != $infinity ) {
+ $expiry = strlen( $row->pt_expiry ) ?
+ $lang->formatExpiry( $row->pt_expiry, TS_MW ) :
+ $infinity;
+
+ if ( $expiry != $infinity ) {
$user = $this->getUser();
$description_items[] = $this->msg(
'protect-expiring-local',
@@ -109,6 +121,7 @@ class SpecialProtectedtitles extends SpecialPage {
wfProfileOut( __METHOD__ );
+ // @todo i18n: This should use a comma separator instead of a hard coded comma, right?
return '<li>' . $lang->specialList( $link, implode( $description_items, ', ' ) ) . "</li>\n";
}
@@ -124,6 +137,7 @@ class SpecialProtectedtitles extends SpecialPage {
$action = htmlspecialchars( $wgScript );
$title = $this->getTitle();
$special = htmlspecialchars( $title->getPrefixedDBkey() );
+
return "<form action=\"$action\" method=\"get\">\n" .
'<fieldset>' .
Xml::element( 'legend', array(), $this->msg( 'protectedtitles' )->text() ) .
@@ -148,42 +162,45 @@ class SpecialProtectedtitles extends SpecialPage {
'all' => '',
'label' => $this->msg( 'namespace' )->text()
), array(
- 'name' => 'namespace',
- 'id' => 'namespace',
+ 'name' => 'namespace',
+ 'id' => 'namespace',
'class' => 'namespaceselector',
)
);
}
/**
+ * @param string $pr_level Determines which option is selected as default
* @return string Formatted HTML
* @private
*/
function getLevelMenu( $pr_level ) {
global $wgRestrictionLevels;
- $m = array( $this->msg( 'restriction-level-all' )->text() => 0 ); // Temporary array
+ // Temporary array
+ $m = array( $this->msg( 'restriction-level-all' )->text() => 0 );
$options = array();
// First pass to load the log names
- foreach( $wgRestrictionLevels as $type ) {
+ foreach ( $wgRestrictionLevels as $type ) {
if ( $type != '' && $type != '*' ) {
+ // Messages: restriction-level-sysop, restriction-level-autoconfirmed
$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
- foreach( $m as $text => $type ) {
- $selected = ($type == $pr_level );
+ foreach ( $m as $text => $type ) {
+ $selected = ( $type == $pr_level );
$options[] = Xml::option( $text, $type, $selected );
}
- return
- Xml::label( $this->msg( 'restriction-level' )->text(), $this->IdLevel ) . '&#160;' .
+ return Xml::label( $this->msg( 'restriction-level' )->text(), $this->IdLevel ) . '&#160;' .
Xml::tags( 'select',
array( 'id' => $this->IdLevel, 'name' => $this->IdLevel ),
implode( "\n", $options ) );
@@ -201,12 +218,14 @@ 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;
$this->namespace = $namespace;
- $this->size = intval($size);
+ $this->size = intval( $size );
parent::__construct( $form->getContext() );
}
@@ -222,6 +241,7 @@ class ProtectedTitlesPager extends AlphabeticPager {
$lb->execute();
wfProfileOut( __METHOD__ );
+
return '';
}
@@ -242,10 +262,14 @@ class ProtectedTitlesPager extends AlphabeticPager {
function getQueryInfo() {
$conds = $this->mConds;
$conds[] = 'pt_expiry>' . $this->mDb->addQuotes( $this->mDb->timestamp() );
- if( $this->level )
+ 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' => array( 'pt_namespace', 'pt_title', 'pt_create_perm',
diff --git a/includes/specials/SpecialRandomInCategory.php b/includes/specials/SpecialRandomInCategory.php
new file mode 100644
index 00000000..0e022bfa
--- /dev/null
+++ b/includes/specials/SpecialRandomInCategory.php
@@ -0,0 +1,291 @@
+<?php
+/**
+ * Implements Special:RandomInCategory
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup SpecialPage
+ * @author Brian Wolff
+ */
+
+/**
+ * Special page to direct the user to a random page
+ *
+ * @note The method used here is rather biased. It is assumed that
+ * the use of this page will be people wanting to get a random page
+ * out of a maintenance category, to fix it up. The method used by
+ * this page should return different pages in an unpredictable fashion
+ * which is hoped to be sufficient, even if some pages are selected
+ * more often than others.
+ *
+ * A more unbiased method could be achieved by adding a cl_random field
+ * to the categorylinks table.
+ *
+ * The method used here is as follows:
+ * * Find the smallest and largest timestamp in the category
+ * * Pick a random timestamp in between
+ * * Pick an offset between 0 and 30
+ * * Get the offset'ed page that is newer than the timestamp selected
+ * The offset is meant to counter the fact the timestamps aren't usually
+ * uniformly distributed, so if things are very non-uniform at least we
+ * won't have the same page selected 99% of the time.
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialRandomInCategory extends SpecialPage {
+ protected $extra = array(); // Extra SQL statements
+ protected $category = false; // Title object of category
+ protected $maxOffset = 30; // Max amount to fudge randomness by.
+ private $maxTimestamp = null;
+ private $minTimestamp = null;
+
+ public function __construct( $name = 'RandomInCategory' ) {
+ parent::__construct( $name );
+ }
+
+ /**
+ * Set which category to use.
+ * @param Title $cat
+ */
+ public function setCategory( Title $cat ) {
+ $this->category = $cat;
+ $this->maxTimestamp = null;
+ $this->minTimestamp = null;
+ }
+
+ public function execute( $par ) {
+ global $wgScript;
+
+ $cat = false;
+
+ $categoryStr = $this->getRequest()->getText( 'category', $par );
+
+ if ( $categoryStr ) {
+ $cat = Title::newFromText( $categoryStr, NS_CATEGORY );
+ }
+
+ if ( $cat && $cat->getNamespace() !== NS_CATEGORY ) {
+ // Someone searching for something like "Wikipedia:Foo"
+ $cat = Title::makeTitleSafe( NS_CATEGORY, $categoryStr );
+ }
+
+ if ( $cat ) {
+ $this->setCategory( $cat );
+ }
+
+
+ if ( !$this->category && $categoryStr ) {
+ $this->setHeaders();
+ $this->getOutput()->addWikiMsg( 'randomincategory-invalidcategory',
+ wfEscapeWikiText( $categoryStr ) );
+
+ return;
+ } elseif ( !$this->category ) {
+ $this->setHeaders();
+ $input = Html::input( 'category' );
+ $submitText = $this->msg( 'randomincategory-selectcategory-submit' )->text();
+ $submit = Html::input( '', $submitText, 'submit' );
+
+ $msg = $this->msg( 'randomincategory-selectcategory' );
+ $form = Html::rawElement( 'form', array( 'action' => $wgScript ),
+ Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) .
+ $msg->rawParams( $input, $submit )->parse()
+ );
+ $this->getOutput()->addHtml( $form );
+
+ return;
+ }
+
+ $title = $this->getRandomTitle();
+
+ if ( is_null( $title ) ) {
+ $this->setHeaders();
+ $this->getOutput()->addWikiMsg( 'randomincategory-nopages',
+ $this->category->getText() );
+
+ return;
+ }
+
+ $query = $this->getRequest()->getValues();
+ unset( $query['title'] );
+ unset( $query['category'] );
+ $this->getOutput()->redirect( $title->getFullURL( $query ) );
+ }
+
+ /**
+ * Choose a random title.
+ * @return Title object (or null if nothing to choose from)
+ */
+ public function getRandomTitle() {
+ // Convert to float, since we do math with the random number.
+ $rand = (float)wfRandom();
+ $title = null;
+
+ // Given that timestamps are rather unevenly distributed, we also
+ // use an offset between 0 and 30 to make any biases less noticeable.
+ $offset = mt_rand( 0, $this->maxOffset );
+
+ if ( mt_rand( 0, 1 ) ) {
+ $up = true;
+ } else {
+ $up = false;
+ }
+
+ $row = $this->selectRandomPageFromDB( $rand, $offset, $up );
+
+ // Try again without the timestamp offset (wrap around the end)
+ if ( !$row ) {
+ $row = $this->selectRandomPageFromDB( false, $offset, $up );
+ }
+
+ // Maybe the category is really small and offset too high
+ if ( !$row ) {
+ $row = $this->selectRandomPageFromDB( $rand, 0, $up );
+ }
+
+ // Just get the first entry.
+ if ( !$row ) {
+ $row = $this->selectRandomPageFromDB( false, 0, true );
+ }
+
+ if ( $row ) {
+ return Title::makeTitle( $row->page_namespace, $row->page_title );
+ }
+
+ return null;
+ }
+
+ /**
+ * @param float $rand Random number between 0 and 1
+ * @param int $offset Extra offset to fudge randomness
+ * @param bool $up True to get the result above the random number, false for below
+ *
+ * @note The $up parameter is supposed to counteract what would happen if there
+ * was a large gap in the distribution of cl_timestamp values. This way instead
+ * of things to the right of the gap being favoured, both sides of the gap
+ * are favoured.
+ * @return Array Query information.
+ */
+ protected function getQueryInfo( $rand, $offset, $up ) {
+ $op = $up ? '>=' : '<=';
+ $dir = $up ? 'ASC' : 'DESC';
+ if ( !$this->category instanceof Title ) {
+ throw new MWException( 'No category set' );
+ }
+ $qi = array(
+ 'tables' => array( 'categorylinks', 'page' ),
+ 'fields' => array( 'page_title', 'page_namespace' ),
+ 'conds' => array_merge( array(
+ 'cl_to' => $this->category->getDBKey(),
+ ), $this->extra ),
+ 'options' => array(
+ 'ORDER BY' => 'cl_timestamp ' . $dir,
+ 'LIMIT' => 1,
+ 'OFFSET' => $offset
+ ),
+ 'join_conds' => array(
+ 'page' => array( 'INNER JOIN', 'cl_from = page_id' )
+ )
+ );
+
+ $dbr = wfGetDB( DB_SLAVE );
+ $minClTime = $this->getTimestampOffset( $rand );
+ if ( $minClTime ) {
+ $qi['conds'][] = 'cl_timestamp ' . $op . ' ' .
+ $dbr->addQuotes( $dbr->timestamp( $minClTime ) );
+ }
+ return $qi;
+ }
+
+ /**
+ * @param float $rand Random number between 0 and 1
+ *
+ * @return int|bool A random (unix) timestamp from the range of the category or false on failure
+ */
+ protected function getTimestampOffset( $rand ) {
+ if ( $rand === false ) {
+ return false;
+ }
+ if ( !$this->minTimestamp || !$this->maxTimestamp ) {
+ try {
+ list( $this->minTimestamp, $this->maxTimestamp ) = $this->getMinAndMaxForCat( $this->category );
+ } catch ( MWException $e ) {
+ // Possibly no entries in category.
+ return false;
+ }
+ }
+
+ $ts = ( $this->maxTimestamp - $this->minTimestamp ) * $rand + $this->minTimestamp;
+ return intval( $ts );
+ }
+
+ /**
+ * Get the lowest and highest timestamp for a category.
+ *
+ * @param Title $category
+ * @return Array The lowest and highest timestamp
+ * @throws MWException if category has no entries.
+ */
+ protected function getMinAndMaxForCat( Title $category ) {
+ $dbr = wfGetDB( DB_SLAVE );
+ $res = $dbr->selectRow(
+ 'categorylinks',
+ array(
+ 'low' => 'MIN( cl_timestamp )',
+ 'high' => 'MAX( cl_timestamp )'
+ ),
+ array(
+ 'cl_to' => $this->category->getDBKey(),
+ ),
+ __METHOD__,
+ array(
+ 'LIMIT' => 1
+ )
+ );
+ if ( !$res ) {
+ throw new MWException( 'No entries in category' );
+ }
+ return array( wfTimestamp( TS_UNIX, $res->low ), wfTimestamp( TS_UNIX, $res->high ) );
+ }
+
+ /**
+ * @param float $rand A random number that is converted to a random timestamp
+ * @param int $offset A small offset to make the result seem more "random"
+ * @param bool $up Get the result above the random value
+ * @param String $fname The name of the calling method
+ * @return Array Info for the title selected.
+ */
+ private function selectRandomPageFromDB( $rand, $offset, $up, $fname = __METHOD__ ) {
+ $dbr = wfGetDB( DB_SLAVE );
+
+ $query = $this->getQueryInfo( $rand, $offset, $up );
+ $res = $dbr->select(
+ $query['tables'],
+ $query['fields'],
+ $query['conds'],
+ $fname,
+ $query['options'],
+ $query['join_conds']
+ );
+
+ return $res->fetchObject();
+ }
+
+ protected function getGroupName() {
+ return 'redirects';
+ }
+}
diff --git a/includes/specials/SpecialRandompage.php b/includes/specials/SpecialRandompage.php
index b59f8349..c94d2b35 100644
--- a/includes/specials/SpecialRandompage.php
+++ b/includes/specials/SpecialRandompage.php
@@ -41,8 +41,8 @@ class RandomPage extends SpecialPage {
return $this->namespaces;
}
- public function setNamespace ( $ns ) {
- if( !$ns || $ns < NS_MAIN ) {
+ public function setNamespace( $ns ) {
+ if ( !$ns || $ns < NS_MAIN ) {
$ns = NS_MAIN;
}
$this->namespaces = array( $ns );
@@ -62,17 +62,19 @@ class RandomPage extends SpecialPage {
$title = $this->getRandomTitle();
- if( is_null( $title ) ) {
+ if ( is_null( $title ) ) {
$this->setHeaders();
+ // Message: randompage-nopages, randomredirect-nopages
$this->getOutput()->addWikiMsg( strtolower( $this->getName() ) . '-nopages',
$this->getNsList(), count( $this->namespaces ) );
+
return;
}
$redirectParam = $this->isRedirect() ? array( 'redirect' => 'no' ) : array();
$query = array_merge( $this->getRequest()->getValues(), $redirectParam );
unset( $query['title'] );
- $this->getOutput()->redirect( $title->getFullUrl( $query ) );
+ $this->getOutput()->redirect( $title->getFullURL( $query ) );
}
/**
@@ -83,13 +85,14 @@ class RandomPage extends SpecialPage {
private function getNsList() {
global $wgContLang;
$nsNames = array();
- foreach( $this->namespaces as $n ) {
- if( $n === NS_MAIN ) {
+ foreach ( $this->namespaces as $n ) {
+ if ( $n === NS_MAIN ) {
$nsNames[] = $this->msg( 'blanknamespace' )->plain();
} else {
$nsNames[] = $wgContLang->getNsText( $n );
}
}
+
return $wgContLang->commaList( $nsNames );
}
@@ -100,10 +103,14 @@ class RandomPage extends SpecialPage {
public function getRandomTitle() {
$randstr = wfRandom();
$title = null;
- if ( !wfRunHooks( 'SpecialRandomGetRandomTitle', array( &$randstr, &$this->isRedir, &$this->namespaces,
- &$this->extra, &$title ) ) ) {
+
+ if ( !wfRunHooks(
+ 'SpecialRandomGetRandomTitle',
+ array( &$randstr, &$this->isRedir, &$this->namespaces, &$this->extra, &$title )
+ ) ) {
return $title;
}
+
$row = $this->selectRandomPageFromDB( $randstr );
/* If we picked a value that was higher than any in
@@ -113,15 +120,15 @@ class RandomPage extends SpecialPage {
* any more bias than what the page_random scheme
* causes anyway. Trust me, I'm a mathematician. :)
*/
- if( !$row ) {
+ if ( !$row ) {
$row = $this->selectRandomPageFromDB( "0" );
}
- if( $row ) {
+ if ( $row ) {
return Title::makeTitleSafe( $row->page_namespace, $row->page_title );
- } else {
- return null;
}
+
+ return null;
}
protected function getQueryInfo( $randstr ) {
diff --git a/includes/specials/SpecialRandomredirect.php b/includes/specials/SpecialRandomredirect.php
index 51783a2f..7c36a28a 100644
--- a/includes/specials/SpecialRandomredirect.php
+++ b/includes/specials/SpecialRandomredirect.php
@@ -32,5 +32,4 @@ class SpecialRandomredirect extends RandomPage {
parent::__construct( 'Randomredirect' );
$this->isRedir = true;
}
-
}
diff --git a/includes/specials/SpecialRecentchanges.php b/includes/specials/SpecialRecentchanges.php
index 008678f7..a42a2171 100644
--- a/includes/specials/SpecialRecentchanges.php
+++ b/includes/specials/SpecialRecentchanges.php
@@ -41,16 +41,17 @@ class SpecialRecentChanges extends IncludableSpecialPage {
*/
public function getDefaultOptions() {
$opts = new FormOptions();
+ $user = $this->getUser();
- $opts->add( 'days', $this->getUser()->getIntOption( 'rcdays' ) );
- $opts->add( 'limit', $this->getUser()->getIntOption( 'rclimit' ) );
+ $opts->add( 'days', $user->getIntOption( 'rcdays' ) );
+ $opts->add( 'limit', $user->getIntOption( 'rclimit' ) );
$opts->add( 'from', '' );
- $opts->add( 'hideminor', $this->getUser()->getBoolOption( 'hideminor' ) );
- $opts->add( 'hidebots', true );
+ $opts->add( 'hideminor', $user->getBoolOption( 'hideminor' ) );
+ $opts->add( 'hidebots', true );
$opts->add( 'hideanons', false );
$opts->add( 'hideliu', false );
- $opts->add( 'hidepatrolled', $this->getUser()->getBoolOption( 'hidepatrolled' ) );
+ $opts->add( 'hidepatrolled', $user->getBoolOption( 'hidepatrolled' ) );
$opts->add( 'hidemyself', false );
$opts->add( 'namespace', '', FormOptions::INTNULL );
@@ -60,44 +61,47 @@ class SpecialRecentChanges extends IncludableSpecialPage {
$opts->add( 'categories', '' );
$opts->add( 'categories_any', false );
$opts->add( 'tagfilter', '' );
+
return $opts;
}
/**
* Create a FormOptions object with options as specified by the user
*
- * @param $parameters array
+ * @param array $parameters
*
* @return FormOptions
*/
public function setup( $parameters ) {
$opts = $this->getDefaultOptions();
- foreach( $this->getCustomFilters() as $key => $params ) {
+ foreach ( $this->getCustomFilters() as $key => $params ) {
$opts->add( $key, $params['default'] );
}
$opts->fetchValuesFromRequest( $this->getRequest() );
// Give precedence to subpage syntax
- if( $parameters !== null ) {
+ if ( $parameters !== null ) {
$this->parseParameters( $parameters, $opts );
}
$opts->validateIntBounds( 'limit', 0, 5000 );
+
return $opts;
}
/**
* Get custom show/hide filters
*
- * @return Array Map of filter URL param names to properties (msg/default)
+ * @return array Map of filter URL param names to properties (msg/default)
*/
protected function getCustomFilters() {
if ( $this->customFilters === null ) {
$this->customFilters = array();
wfRunHooks( 'SpecialRecentChangesFilters', array( $this, &$this->customFilters ) );
}
+
return $this->customFilters;
}
@@ -111,6 +115,7 @@ class SpecialRecentChanges extends IncludableSpecialPage {
$opts = $this->getDefaultOptions();
$opts->fetchValuesFromRequest( $this->getRequest() );
$opts->validateIntBounds( 'limit', 0, $wgFeedLimit );
+
return $opts;
}
@@ -126,13 +131,14 @@ class SpecialRecentChanges extends IncludableSpecialPage {
}
$this->rcOptions = $isFeed ? $this->feedSetup() : $this->setup( $this->rcSubpage );
}
+
return $this->rcOptions;
}
/**
* Main execution point
*
- * @param $subpage String
+ * @param string $subpage
*/
public function execute( $subpage ) {
$this->rcSubpage = $subpage;
@@ -142,36 +148,38 @@ class SpecialRecentChanges extends IncludableSpecialPage {
$this->getOutput()->setSquidMaxage( 10 );
# Check if the client has a cached version
$lastmod = $this->checkLastModified( $feedFormat );
- if( $lastmod === false ) {
+ if ( $lastmod === false ) {
return;
}
$opts = $this->getOptions();
$this->setHeaders();
$this->outputHeader();
- $this->addRecentChangesJS();
+ $this->addModules();
// Fetch results, prepare a batch link existence check query
$conds = $this->buildMainQueryConds( $opts );
$rows = $this->doMainQuery( $conds, $opts );
- if( $rows === false ) {
- if( !$this->including() ) {
+ if ( $rows === false ) {
+ if ( !$this->including() ) {
$this->doHeader( $opts );
}
+
return;
}
- if( !$feedFormat ) {
+ if ( !$feedFormat ) {
$batch = new LinkBatch;
- foreach( $rows as $row ) {
+ foreach ( $rows as $row ) {
$batch->add( NS_USER, $row->rc_user_text );
$batch->add( NS_USER_TALK, $row->rc_user_text );
$batch->add( $row->rc_namespace, $row->rc_title );
}
$batch->execute();
}
- if( $feedFormat ) {
+ if ( $feedFormat ) {
list( $changesFeed, $formatter ) = $this->getFeedObject( $feedFormat );
+ /** @var ChangesFeed $changesFeed */
$changesFeed->execute( $formatter, $rows, $lastmod, $opts );
} else {
$this->webOutput( $rows, $opts );
@@ -183,7 +191,8 @@ class SpecialRecentChanges extends IncludableSpecialPage {
/**
* Return an array with a ChangesFeed object and ChannelFeed object
*
- * @return Array
+ * @param string $feedFormat Feed's format (either 'rss' or 'atom')
+ * @return array
*/
public function getFeedObject( $feedFormat ) {
$changesFeed = new ChangesFeed( $feedFormat, 'rcfeed' );
@@ -192,6 +201,7 @@ class SpecialRecentChanges extends IncludableSpecialPage {
$this->msg( 'recentchanges-feed-description' )->inContentLanguage()->text(),
$this->getTitle()->getFullURL()
);
+
return array( $changesFeed, $formatter );
}
@@ -199,49 +209,49 @@ class SpecialRecentChanges extends IncludableSpecialPage {
* Process $par and put options found if $opts
* Mainly used when including the page
*
- * @param $par String
- * @param $opts FormOptions
+ * @param string $par
+ * @param FormOptions $opts
*/
public function parseParameters( $par, FormOptions $opts ) {
$bits = preg_split( '/\s*,\s*/', trim( $par ) );
- foreach( $bits as $bit ) {
- if( 'hidebots' === $bit ) {
+ foreach ( $bits as $bit ) {
+ if ( 'hidebots' === $bit ) {
$opts['hidebots'] = true;
}
- if( 'bots' === $bit ) {
+ if ( 'bots' === $bit ) {
$opts['hidebots'] = false;
}
- if( 'hideminor' === $bit ) {
+ if ( 'hideminor' === $bit ) {
$opts['hideminor'] = true;
}
- if( 'minor' === $bit ) {
+ if ( 'minor' === $bit ) {
$opts['hideminor'] = false;
}
- if( 'hideliu' === $bit ) {
+ if ( 'hideliu' === $bit ) {
$opts['hideliu'] = true;
}
- if( 'hidepatrolled' === $bit ) {
+ if ( 'hidepatrolled' === $bit ) {
$opts['hidepatrolled'] = true;
}
- if( 'hideanons' === $bit ) {
+ if ( 'hideanons' === $bit ) {
$opts['hideanons'] = true;
}
- if( 'hidemyself' === $bit ) {
+ if ( 'hidemyself' === $bit ) {
$opts['hidemyself'] = true;
}
- if( is_numeric( $bit ) ) {
+ if ( is_numeric( $bit ) ) {
$opts['limit'] = $bit;
}
$m = array();
- if( preg_match( '/^limit=(\d+)$/', $bit, $m ) ) {
+ if ( preg_match( '/^limit=(\d+)$/', $bit, $m ) ) {
$opts['limit'] = $m[1];
}
- if( preg_match( '/^days=(\d+)$/', $bit, $m ) ) {
+ if ( preg_match( '/^days=(\d+)$/', $bit, $m ) ) {
$opts['days'] = $m[1];
}
- if( preg_match( '/^namespace=(\d+)$/', $bit, $m ) ) {
+ if ( preg_match( '/^namespace=(\d+)$/', $bit, $m ) ) {
$opts['namespace'] = $m[1];
}
}
@@ -252,25 +262,26 @@ class SpecialRecentChanges extends IncludableSpecialPage {
* Don't use this if we are using the patrol feature, patrol changes don't
* update the timestamp
*
- * @param $feedFormat String
- * @return String or false
+ * @param string $feedFormat
+ * @return string|bool
*/
public function checkLastModified( $feedFormat ) {
$dbr = wfGetDB( DB_SLAVE );
$lastmod = $dbr->selectField( 'recentchanges', 'MAX(rc_timestamp)', false, __METHOD__ );
- if( $feedFormat || !$this->getUser()->useRCPatrol() ) {
- if( $lastmod && $this->getOutput()->checkLastModified( $lastmod ) ) {
+ if ( $feedFormat || !$this->getUser()->useRCPatrol() ) {
+ if ( $lastmod && $this->getOutput()->checkLastModified( $lastmod ) ) {
# Client cache fresh and headers sent, nothing more to do.
return false;
}
}
+
return $lastmod;
}
/**
* Return an array of conditions depending of options set in $opts
*
- * @param $opts FormOptions
+ * @param FormOptions $opts
* @return array
*/
public function buildMainQueryConds( FormOptions $opts ) {
@@ -280,9 +291,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;
@@ -292,11 +303,11 @@ class SpecialRecentChanges extends IncludableSpecialPage {
// Calculate cutoff
$cutoff_unixtime = time() - ( $opts['days'] * 86400 );
- $cutoff_unixtime = $cutoff_unixtime - ($cutoff_unixtime % 86400);
+ $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 ) ) {
+ if ( $fromValid && $opts['from'] > wfTimestamp( TS_MW, $cutoff ) ) {
$cutoff = $dbr->timestamp( $opts['from'] );
} else {
$opts->reset( 'from' );
@@ -308,27 +319,27 @@ class SpecialRecentChanges extends IncludableSpecialPage {
$hideLoggedInUsers = $opts['hideliu'] && !$forcebot;
$hideAnonymousUsers = $opts['hideanons'] && !$forcebot;
- if( $opts['hideminor'] ) {
+ if ( $opts['hideminor'] ) {
$conds['rc_minor'] = 0;
}
- if( $opts['hidebots'] ) {
+ if ( $opts['hidebots'] ) {
$conds['rc_bot'] = 0;
}
- if( $hidePatrol ) {
+ if ( $hidePatrol ) {
$conds['rc_patrolled'] = 0;
}
- if( $forcebot ) {
+ if ( $forcebot ) {
$conds['rc_bot'] = 1;
}
- if( $hideLoggedInUsers ) {
+ if ( $hideLoggedInUsers ) {
$conds[] = 'rc_user = 0';
}
- if( $hideAnonymousUsers ) {
+ if ( $hideAnonymousUsers ) {
$conds[] = 'rc_user != 0';
}
- if( $opts['hidemyself'] ) {
- if( $this->getUser()->getId() ) {
+ if ( $opts['hidemyself'] ) {
+ if ( $this->getUser()->getId() ) {
$conds[] = 'rc_user != ' . $dbr->addQuotes( $this->getUser()->getId() );
} else {
$conds[] = 'rc_user_text != ' . $dbr->addQuotes( $this->getUser()->getName() );
@@ -336,13 +347,13 @@ class SpecialRecentChanges extends IncludableSpecialPage {
}
# Namespace filtering
- if( $opts['namespace'] !== '' ) {
+ if ( $opts['namespace'] !== '' ) {
$selectedNS = $dbr->addQuotes( $opts['namespace'] );
- $operator = $opts['invert'] ? '!=' : '=';
+ $operator = $opts['invert'] ? '!=' : '=';
$boolean = $opts['invert'] ? 'AND' : 'OR';
# namespace association (bug 2429)
- if( !$opts['associated'] ) {
+ if ( !$opts['associated'] ) {
$condition = "rc_namespace $operator $selectedNS";
} else {
# Also add the associated namespace
@@ -356,15 +367,16 @@ class SpecialRecentChanges extends IncludableSpecialPage {
$conds[] = $condition;
}
+
return $conds;
}
/**
* Process the query
*
- * @param $conds Array
- * @param $opts FormOptions
- * @return bool|ResultWrapper result or false (for Recentchangeslinked only)
+ * @param array $conds
+ * @param FormOptions $opts
+ * @return bool|ResultWrapper Result or false (for Recentchangeslinked only)
*/
public function doMainQuery( $conds, $opts ) {
$tables = array( 'recentchanges' );
@@ -382,7 +394,7 @@ class SpecialRecentChanges extends IncludableSpecialPage {
$fields = RecentChange::selectFields();
// JOIN on watchlist for users
- if ( $uid ) {
+ if ( $uid && $this->getUser()->isAllowed( 'viewmywatchlist' ) ) {
$tables[] = 'watchlist';
$fields[] = 'wl_user';
$fields[] = 'wl_notificationtimestamp';
@@ -390,7 +402,7 @@ class SpecialRecentChanges extends IncludableSpecialPage {
'wl_user' => $uid,
'wl_title=rc_title',
'wl_namespace=rc_namespace'
- ));
+ ) );
}
if ( $this->getUser()->isAllowed( 'rollback' ) ) {
$tables[] = 'page';
@@ -408,91 +420,40 @@ class SpecialRecentChanges extends IncludableSpecialPage {
);
if ( !wfRunHooks( 'SpecialRecentChangesQuery',
- array( &$conds, &$tables, &$join_conds, $opts, &$query_options, &$fields ) ) )
- {
+ array( &$conds, &$tables, &$join_conds, $opts, &$query_options, &$fields ) )
+ ) {
return false;
}
- // Don't use the new_namespace_time timestamp index if:
- // (a) "All namespaces" selected
- // (b) We want pages in more than one namespace (inverted/associated)
- // (c) There is a tag to filter on (use tag index instead)
- // (d) UNION + sort/limit is not an option for the DBMS
- if( $namespace === ''
- || ( $invert || $associated )
- || $opts['tagfilter'] != ''
- || !$dbr->unionSupportsOrderAndLimit() )
- {
- $res = $dbr->select( $tables, $fields, $conds, __METHOD__,
- array( 'ORDER BY' => 'rc_timestamp DESC', 'LIMIT' => $limit ) +
- $query_options,
- $join_conds );
- // We have a new_namespace_time index! UNION over new=(0,1) and sort result set!
- } else {
- // New pages
- $sqlNew = $dbr->selectSQLText(
- $tables,
- $fields,
- array( 'rc_new' => 1 ) + $conds,
- __METHOD__,
- array(
- 'ORDER BY' => 'rc_timestamp DESC',
- 'LIMIT' => $limit,
- 'USE INDEX' => array( 'recentchanges' => 'new_name_timestamp' )
- ),
- $join_conds
- );
- // Old pages
- $sqlOld = $dbr->selectSQLText(
- $tables,
- $fields,
- array( 'rc_new' => 0 ) + $conds,
- __METHOD__,
- array(
- 'ORDER BY' => 'rc_timestamp DESC',
- 'LIMIT' => $limit,
- 'USE INDEX' => array( 'recentchanges' => 'new_name_timestamp' )
- ),
- $join_conds
- );
- # Join the two fast queries, and sort the result set
- $sql = $dbr->unionQueries( array( $sqlNew, $sqlOld ), false ) .
- ' ORDER BY rc_timestamp DESC';
- $sql = $dbr->limitResult( $sql, $limit, false );
- $res = $dbr->query( $sql, __METHOD__ );
- }
-
- return $res;
+ // rc_new is not an ENUM, but adding a redundant rc_new IN (0,1) gives mysql enough
+ // knowledge to use an index merge if it wants (it may use some other index though).
+ return $dbr->select(
+ $tables,
+ $fields,
+ $conds + array( 'rc_new' => array( 0, 1 ) ),
+ __METHOD__,
+ array( 'ORDER BY' => 'rc_timestamp DESC', 'LIMIT' => $limit ) + $query_options,
+ $join_conds
+ );
}
/**
* Send output to the OutputPage object, only called if not used feeds
*
- * @param array $rows of database rows
- * @param $opts FormOptions
+ * @param array $rows Database rows
+ * @param FormOptions $opts
*/
public function webOutput( $rows, $opts ) {
global $wgRCShowWatchingUsers, $wgShowUpdatedMarker, $wgAllowCategorizedRecentChanges;
- $limit = $opts['limit'];
-
- if( !$this->including() ) {
- // Output options box
- $this->doHeader( $opts );
- }
-
- // And now for the content
- $feedQuery = $this->getFeedQuery();
- if ( $feedQuery !== '' ) {
- $this->getOutput()->setFeedAppendQuery( $feedQuery );
- } else {
- $this->getOutput()->setFeedAppendQuery( false );
- }
+ // Build the final data
- if( $wgAllowCategorizedRecentChanges ) {
+ if ( $wgAllowCategorizedRecentChanges ) {
$this->filterByCategories( $rows, $opts );
}
+ $limit = $opts['limit'];
+
$showWatcherCount = $wgRCShowWatchingUsers && $this->getUser()->getOption( 'shownumberswatching' );
$watcherCache = array();
@@ -501,23 +462,23 @@ class SpecialRecentChanges extends IncludableSpecialPage {
$counter = 1;
$list = ChangesList::newFromContext( $this->getContext() );
- $s = $list->beginRecentChangesList();
- foreach( $rows as $obj ) {
- if( $limit == 0 ) {
+ $rclistOutput = $list->beginRecentChangesList();
+ foreach ( $rows as $obj ) {
+ if ( $limit == 0 ) {
break;
}
$rc = RecentChange::newFromRow( $obj );
$rc->counter = $counter++;
# Check if the page has been updated since the last visit
- if( $wgShowUpdatedMarker && !empty( $obj->wl_notificationtimestamp ) ) {
+ if ( $wgShowUpdatedMarker && !empty( $obj->wl_notificationtimestamp ) ) {
$rc->notificationtimestamp = ( $obj->rc_timestamp >= $obj->wl_notificationtimestamp );
} else {
$rc->notificationtimestamp = false; // Default
}
# Check the number of users watching the page
$rc->numberofWatchingusers = 0; // Default
- if( $showWatcherCount && $obj->rc_namespace >= 0 ) {
- if( !isset( $watcherCache[$obj->rc_namespace][$obj->rc_title] ) ) {
+ if ( $showWatcherCount && $obj->rc_namespace >= 0 ) {
+ if ( !isset( $watcherCache[$obj->rc_namespace][$obj->rc_title] ) ) {
$watcherCache[$obj->rc_namespace][$obj->rc_title] =
$dbr->selectField(
'watchlist',
@@ -534,12 +495,34 @@ class SpecialRecentChanges extends IncludableSpecialPage {
$changeLine = $list->recentChangesLine( $rc, !empty( $obj->wl_user ), $counter );
if ( $changeLine !== false ) {
- $s .= $changeLine;
+ $rclistOutput .= $changeLine;
--$limit;
}
}
- $s .= $list->endRecentChangesList();
- $this->getOutput()->addHTML( $s );
+ $rclistOutput .= $list->endRecentChangesList();
+
+ // Print things out
+
+ if ( !$this->including() ) {
+ // Output options box
+ $this->doHeader( $opts );
+ }
+
+ // And now for the content
+ $feedQuery = $this->getFeedQuery();
+ if ( $feedQuery !== '' ) {
+ $this->getOutput()->setFeedAppendQuery( $feedQuery );
+ } else {
+ $this->getOutput()->setFeedAppendQuery( false );
+ }
+
+ if ( $rows->numRows() === 0 ) {
+ $this->getOutput()->wrapWikiMsg(
+ "<div class='mw-changeslist-empty'>\n$1\n</div>", 'recentchanges-noresult'
+ );
+ } else {
+ $this->getOutput()->addHTML( $rclistOutput );
+ }
}
/**
@@ -567,8 +550,8 @@ class SpecialRecentChanges extends IncludableSpecialPage {
/**
* Return the text to be displayed above the changes
*
- * @param $opts FormOptions
- * @return String: XHTML
+ * @param FormOptions $opts
+ * @return string XHTML
*/
public function doHeader( $opts ) {
global $wgScript;
@@ -577,10 +560,6 @@ class SpecialRecentChanges extends IncludableSpecialPage {
$defaults = $opts->getAllValues();
$nondefaults = $opts->getChangedValues();
- $opts->consumeValues( array(
- 'namespace', 'invert', 'associated', 'tagfilter',
- 'categories', 'categories_any'
- ) );
$panel = array();
$panel[] = $this->optionsPanel( $defaults, $nondefaults );
@@ -592,24 +571,36 @@ class SpecialRecentChanges extends IncludableSpecialPage {
$submit = ' ' . Xml::submitbutton( $this->msg( 'allpagessubmit' )->text() );
$out = Xml::openElement( 'table', array( 'class' => 'mw-recentchanges-table' ) );
- foreach( $extraOpts as $name => $optionRow ) {
+ foreach ( $extraOpts as $name => $optionRow ) {
# Add submit button to the last row only
++$count;
$addSubmit = ( $count === $extraOptsCount ) ? $submit : '';
$out .= Xml::openElement( 'tr' );
- if( is_array( $optionRow ) ) {
- $out .= Xml::tags( 'td', array( 'class' => 'mw-label mw-' . $name . '-label' ), $optionRow[0] );
- $out .= Xml::tags( 'td', array( 'class' => 'mw-input' ), $optionRow[1] . $addSubmit );
+ if ( is_array( $optionRow ) ) {
+ $out .= Xml::tags(
+ 'td',
+ array( 'class' => 'mw-label mw-' . $name . '-label' ),
+ $optionRow[0]
+ );
+ $out .= Xml::tags(
+ 'td',
+ array( 'class' => 'mw-input' ),
+ $optionRow[1] . $addSubmit
+ );
} else {
- $out .= Xml::tags( 'td', array( 'class' => 'mw-input', 'colspan' => 2 ), $optionRow . $addSubmit );
+ $out .= Xml::tags(
+ 'td',
+ array( 'class' => 'mw-input', 'colspan' => 2 ),
+ $optionRow . $addSubmit
+ );
}
$out .= Xml::closeElement( 'tr' );
}
$out .= Xml::closeElement( 'table' );
$unconsumed = $opts->getUnconsumedValues();
- foreach( $unconsumed as $key => $value ) {
+ foreach ( $unconsumed as $key => $value ) {
$out .= Html::hidden( $key, $value );
}
@@ -620,7 +611,11 @@ class SpecialRecentChanges extends IncludableSpecialPage {
$panelString = implode( "\n", $panel );
$this->getOutput()->addHTML(
- Xml::fieldset( $this->msg( 'recentchanges-legend' )->text(), $panelString, array( 'class' => 'rcoptions' ) )
+ Xml::fieldset(
+ $this->msg( 'recentchanges-legend' )->text(),
+ $panelString,
+ array( 'class' => 'rcoptions' )
+ )
);
$this->setBottomText( $opts );
@@ -629,15 +624,19 @@ class SpecialRecentChanges extends IncludableSpecialPage {
/**
* Get options to be displayed in a form
*
- * @param $opts FormOptions
- * @return Array
+ * @param FormOptions $opts
+ * @return array
*/
function getExtraOptions( $opts ) {
+ $opts->consumeValues( array(
+ 'namespace', 'invert', 'associated', 'tagfilter', 'categories', 'categories_any'
+ ) );
+
$extraOpts = array();
$extraOpts['namespace'] = $this->namespaceFilterForm( $opts );
global $wgAllowCategorizedRecentChanges;
- if( $wgAllowCategorizedRecentChanges ) {
+ if ( $wgAllowCategorizedRecentChanges ) {
$extraOpts['category'] = $this->categoryFilterForm( $opts );
}
@@ -646,14 +645,18 @@ class SpecialRecentChanges extends IncludableSpecialPage {
$extraOpts['tagfilter'] = $tagFilter;
}
- wfRunHooks( 'SpecialRecentChangesPanel', array( &$extraOpts, $opts ) );
+ // Don't fire the hook for subclasses. (Or should we?)
+ if ( $this->getName() === 'Recentchanges' ) {
+ wfRunHooks( 'SpecialRecentChangesPanel', array( &$extraOpts, $opts ) );
+ }
+
return $extraOpts;
}
/**
* Send the text to be displayed above the options
*
- * @param $opts FormOptions
+ * @param FormOptions $opts Unused
*/
function setTopText( FormOptions $opts ) {
global $wgContLang;
@@ -672,19 +675,19 @@ class SpecialRecentChanges extends IncludableSpecialPage {
}
/**
- * Send the text to be displayed after the options, for use in
- * Recentchangeslinked
+ * Send the text to be displayed after the options, for use in subclasses.
*
- * @param $opts FormOptions
+ * @param FormOptions $opts
*/
- function setBottomText( FormOptions $opts ) {}
+ function setBottomText( FormOptions $opts ) {
+ }
/**
* Creates the choose namespace selection
*
* @todo Uses radio buttons (HASHAR)
- * @param $opts FormOptions
- * @return String
+ * @param FormOptions $opts
+ * @return string
*/
protected function namespaceFilterForm( FormOptions $opts ) {
$nsSelect = Html::namespaceSelector(
@@ -702,14 +705,15 @@ class SpecialRecentChanges extends IncludableSpecialPage {
$opts['associated'],
array( 'title' => $this->msg( 'tooltip-namespace_association' )->text() )
);
+
return array( $nsLabel, "$nsSelect $invert $associated" );
}
/**
* Create a input to filter changes by categories
*
- * @param $opts FormOptions
- * @return Array
+ * @param FormOptions $opts
+ * @return array
*/
protected function categoryFilterForm( FormOptions $opts ) {
list( $label, $input ) = Xml::inputLabelSep( $this->msg( 'rc_categories' )->text(),
@@ -724,21 +728,21 @@ class SpecialRecentChanges extends IncludableSpecialPage {
/**
* Filter $rows by categories set in $opts
*
- * @param array $rows of database rows
- * @param $opts FormOptions
+ * @param array $rows Database rows
+ * @param FormOptions $opts
*/
function filterByCategories( &$rows, FormOptions $opts ) {
$categories = array_map( 'trim', explode( '|', $opts['categories'] ) );
- if( !count( $categories ) ) {
+ if ( !count( $categories ) ) {
return;
}
# Filter categories
$cats = array();
- foreach( $categories as $cat ) {
+ foreach ( $categories as $cat ) {
$cat = trim( $cat );
- if( $cat == '' ) {
+ if ( $cat == '' ) {
continue;
}
$cats[] = $cat;
@@ -748,16 +752,16 @@ class SpecialRecentChanges extends IncludableSpecialPage {
$articles = array();
$a2r = array();
$rowsarr = array();
- foreach( $rows as $k => $r ) {
+ foreach ( $rows as $k => $r ) {
$nt = Title::makeTitle( $r->rc_namespace, $r->rc_title );
$id = $nt->getArticleID();
- if( $id == 0 ) {
+ if ( $id == 0 ) {
continue; # Page might have been deleted...
}
- if( !in_array( $id, $articles ) ) {
+ if ( !in_array( $id, $articles ) ) {
$articles[] = $id;
}
- if( !isset( $a2r[$id] ) ) {
+ if ( !isset( $a2r[$id] ) ) {
$a2r[$id] = array();
}
$a2r[$id][] = $k;
@@ -765,7 +769,7 @@ class SpecialRecentChanges extends IncludableSpecialPage {
}
# Shortcut?
- if( !count( $articles ) || !count( $cats ) ) {
+ if ( !count( $articles ) || !count( $cats ) ) {
return;
}
@@ -776,8 +780,8 @@ class SpecialRecentChanges extends IncludableSpecialPage {
# Filter
$newrows = array();
- foreach( $match as $id ) {
- foreach( $a2r[$id] as $rev ) {
+ foreach ( $match as $id ) {
+ foreach ( $a2r[$id] as $rev ) {
$k = $rev;
$newrows[$k] = $rowsarr[$k];
}
@@ -788,10 +792,10 @@ class SpecialRecentChanges extends IncludableSpecialPage {
/**
* Makes change an option link which carries all the other options
*
- * @param $title Title
- * @param array $override options to override
- * @param array $options current options
- * @param $active Boolean: whether to show the link in bold
+ * @param string $title Title
+ * @param array $override Options to override
+ * @param array $options Current options
+ * @param bool $active Whether to show the link in bold
* @return string
*/
function makeOptionsLink( $title, $override, $options, $active = false ) {
@@ -810,14 +814,15 @@ class SpecialRecentChanges extends IncludableSpecialPage {
if ( $active ) {
$text = '<strong>' . $text . '</strong>';
}
+
return Linker::linkKnown( $this->getTitle(), $text, array(), $params );
}
/**
* Creates the options panel.
*
- * @param $defaults Array
- * @param $nondefaults Array
+ * @param array $defaults
+ * @param array $nondefaults
* @return string
*/
function optionsPanel( $defaults, $nondefaults ) {
@@ -827,13 +832,13 @@ class SpecialRecentChanges extends IncludableSpecialPage {
$note = '';
$msg = $this->msg( 'rclegend' );
- if( !$msg->isDisabled() ) {
+ if ( !$msg->isDisabled() ) {
$note .= '<div class="mw-rclegend">' . $msg->parse() . "</div>\n";
}
$lang = $this->getLanguage();
$user = $this->getUser();
- if( $options['from'] ) {
+ if ( $options['from'] ) {
$note .= $this->msg( 'rcnotefrom' )->numParams( $options['limit'] )->params(
$lang->userTimeAndDate( $options['from'], $user ),
$lang->userDate( $options['from'], $user ),
@@ -841,22 +846,27 @@ class SpecialRecentChanges extends IncludableSpecialPage {
}
# Sort data for display and make sure it's unique after we've added user data.
- $wgRCLinkLimits[] = $options['limit'];
- $wgRCLinkDays[] = $options['days'];
- sort( $wgRCLinkLimits );
- sort( $wgRCLinkDays );
- $wgRCLinkLimits = array_unique( $wgRCLinkLimits );
- $wgRCLinkDays = array_unique( $wgRCLinkDays );
+ $linkLimits = $wgRCLinkLimits;
+ $linkLimits[] = $options['limit'];
+ sort( $linkLimits );
+ $linkLimits = array_unique( $linkLimits );
+
+ $linkDays = $wgRCLinkDays;
+ $linkDays[] = $options['days'];
+ sort( $linkDays );
+ $linkDays = array_unique( $linkDays );
// limit links
- foreach( $wgRCLinkLimits as $value ) {
+ $cl = array();
+ foreach ( $linkLimits as $value ) {
$cl[] = $this->makeOptionsLink( $lang->formatNum( $value ),
array( 'limit' => $value ), $nondefaults, $value == $options['limit'] );
}
$cl = $lang->pipeList( $cl );
// day links, reset 'from' to none
- foreach( $wgRCLinkDays as $value ) {
+ $dl = array();
+ foreach ( $linkDays as $value ) {
$dl[] = $this->makeOptionsLink( $lang->formatNum( $value ),
array( 'days' => $value, 'from' => '' ), $nondefaults, $value == $options['days'] );
}
@@ -865,12 +875,12 @@ class SpecialRecentChanges extends IncludableSpecialPage {
// show/hide links
$showhide = array( $this->msg( 'show' )->text(), $this->msg( 'hide' )->text() );
$filters = array(
- 'hideminor' => 'rcshowhideminor',
- 'hidebots' => 'rcshowhidebots',
- 'hideanons' => 'rcshowhideanons',
- 'hideliu' => 'rcshowhideliu',
+ 'hideminor' => 'rcshowhideminor',
+ 'hidebots' => 'rcshowhidebots',
+ 'hideanons' => 'rcshowhideanons',
+ 'hideliu' => 'rcshowhideliu',
'hidepatrolled' => 'rcshowhidepatr',
- 'hidemyself' => 'rcshowhidemine'
+ 'hidemyself' => 'rcshowhidemine'
);
foreach ( $this->getCustomFilters() as $key => $params ) {
$filters[$key] = $params['msg'];
@@ -883,7 +893,7 @@ class SpecialRecentChanges extends IncludableSpecialPage {
$links = array();
foreach ( $filters as $key => $msg ) {
$link = $this->makeOptionsLink( $showhide[1 - $options[$key]],
- array( $key => 1-$options[$key] ), $nondefaults );
+ array( $key => 1 - $options[$key] ), $nondefaults );
$links[] = $this->msg( $msg )->rawParams( $link )->escaped();
}
@@ -894,15 +904,17 @@ class SpecialRecentChanges extends IncludableSpecialPage {
$now, array( 'from' => $timestamp ), $nondefaults
);
- $rclinks = $this->msg( 'rclinks' )->rawParams( $cl, $dl, $lang->pipeList( $links ) )->parse();
+ $rclinks = $this->msg( 'rclinks' )->rawParams( $cl, $dl, $lang->pipeList( $links ) )
+ ->parse();
$rclistfrom = $this->msg( 'rclistfrom' )->rawParams( $tl )->parse();
+
return "{$note}$rclinks<br />$rclistfrom";
}
/**
- * add javascript specific to the [[Special:RecentChanges]] page
+ * Add page-specific modules.
*/
- function addRecentChangesJS() {
+ protected function addModules() {
$this->getOutput()->addModules( array(
'mediawiki.special.recentchanges',
) );
diff --git a/includes/specials/SpecialRecentchangeslinked.php b/includes/specials/SpecialRecentchangeslinked.php
index 391c4a7f..a8447046 100644
--- a/includes/specials/SpecialRecentchangeslinked.php
+++ b/includes/specials/SpecialRecentchangeslinked.php
@@ -56,7 +56,7 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges {
$this->msg( 'recentchangeslinked-title', $this->getTargetTitle()->getPrefixedText() )
->inContentLanguage()->text(),
$this->msg( 'recentchangeslinked-feed' )->inContentLanguage()->text(),
- $this->getTitle()->getFullUrl()
+ $this->getTitle()->getFullURL()
);
return array( $feed, $feedObj );
}
@@ -71,8 +71,8 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges {
}
$outputPage = $this->getOutput();
$title = Title::newFromURL( $target );
- if( !$title || $title->getInterwiki() != '' ) {
- $outputPage->wrapWikiMsg( "<div class=\"errorbox\">\n$1\n</div><br style=\"clear: both\" />", 'allpagesbadtitle' );
+ if ( !$title || $title->getInterwiki() != '' ) {
+ $outputPage->wrapWikiMsg( "<div class=\"errorbox\">\n$1\n</div>", 'allpagesbadtitle' );
return false;
}
@@ -99,7 +99,7 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges {
// left join with watchlist table to highlight watched rows
$uid = $this->getUser()->getId();
- if( $uid ) {
+ if ( $uid && $this->getUser()->isAllowed( 'viewmywatchlist' ) ) {
$tables[] = 'watchlist';
$select[] = 'wl_user';
$join_conds['watchlist'] = array( 'LEFT JOIN', array(
@@ -126,7 +126,7 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges {
return false;
}
- if( $ns == NS_CATEGORY && !$showlinkedto ) {
+ if ( $ns == NS_CATEGORY && !$showlinkedto ) {
// special handling for categories
// XXX: should try to make this less kludgy
$link_tables = array( 'categorylinks' );
@@ -135,12 +135,12 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges {
// for now, always join on these tables; really should be configurable as in whatlinkshere
$link_tables = array( 'pagelinks', 'templatelinks' );
// imagelinks only contains links to pages in NS_FILE
- if( $ns == NS_FILE || !$showlinkedto ) {
+ if ( $ns == NS_FILE || !$showlinkedto ) {
$link_tables[] = 'imagelinks';
}
}
- if( $id == 0 && !$showlinkedto ) {
+ if ( $id == 0 && !$showlinkedto ) {
return false; // nonexistent pages can't link to any pages
}
@@ -149,22 +149,22 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges {
$subsql = array(); // SELECT statements to combine with UNION
- foreach( $link_tables as $link_table ) {
+ foreach ( $link_tables as $link_table ) {
$pfx = $prefix[$link_table];
// imagelinks and categorylinks tables have no xx_namespace field, and have xx_to instead of xx_title
- if( $link_table == 'imagelinks' ) {
+ if ( $link_table == 'imagelinks' ) {
$link_ns = NS_FILE;
- } elseif( $link_table == 'categorylinks' ) {
+ } elseif ( $link_table == 'categorylinks' ) {
$link_ns = NS_CATEGORY;
} else {
$link_ns = 0;
}
- if( $showlinkedto ) {
+ if ( $showlinkedto ) {
// find changes to pages linking to this page
- if( $link_ns ) {
- if( $ns != $link_ns ) {
+ if ( $link_ns ) {
+ if ( $ns != $link_ns ) {
continue;
} // should never happen, but check anyway
$subconds = array( "{$pfx}_to" => $dbkey );
@@ -175,7 +175,7 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges {
} else {
// find changes to pages linked from this page
$subconds = array( "{$pfx}_from" => $id );
- if( $link_table == 'imagelinks' || $link_table == 'categorylinks' ) {
+ if ( $link_table == 'imagelinks' || $link_table == 'categorylinks' ) {
$subconds["rc_namespace"] = $link_ns;
$subjoin = "rc_title = {$pfx}_to";
} else {
@@ -183,7 +183,7 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges {
}
}
- if( $dbr->unionSupportsOrderAndLimit()) {
+ if ( $dbr->unionSupportsOrderAndLimit() ) {
$order = array( 'ORDER BY' => 'rc_timestamp DESC' );
} else {
$order = array();
@@ -198,16 +198,17 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges {
$join_conds + array( $link_table => array( 'INNER JOIN', $subjoin ) )
);
- if( $dbr->unionSupportsOrderAndLimit())
+ if ( $dbr->unionSupportsOrderAndLimit() ) {
$query = $dbr->limitResult( $query, $limit );
+ }
$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
@@ -217,7 +218,7 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges {
$res = $dbr->query( $sql, __METHOD__ );
- if( $res->numRows() == 0 ) {
+ if ( $res->numRows() == 0 ) {
$this->mResultEmpty = true;
}
@@ -225,21 +226,21 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges {
}
/**
- * @param $opts FormOptions
+ * Get options to be displayed in a form
+ *
+ * @param FormOptions $opts
* @return array
*/
function getExtraOptions( $opts ) {
- $opts->consumeValues( array( 'showlinkedto', 'target', 'tagfilter' ) );
- $extraOpts = array();
- $extraOpts['namespace'] = $this->namespaceFilterForm( $opts );
+ $extraOpts = parent::getExtraOptions( $opts );
+
+ $opts->consumeValues( array( 'showlinkedto', 'target' ) );
+
$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::label( $this->msg( 'recentchangeslinked-to' )->text(), 'showlinkedto' ) );
- $tagFilter = ChangeTags::buildTagFilterSelector( $opts['tagfilter'] );
- if ( $tagFilter ) {
- $extraOpts['tagfilter'] = $tagFilter;
- }
+
return $extraOpts;
}
@@ -260,14 +261,8 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges {
function setTopText( FormOptions $opts ) {
$target = $this->getTargetTitle();
- if( $target ) {
+ if ( $target ) {
$this->getOutput()->addBacklinkSubtitle( $target );
}
}
-
- function setBottomText( FormOptions $opts ) {
- if( isset( $this->mResultEmpty ) && $this->mResultEmpty ) {
- $this->getOutput()->addWikiMsg( 'recentchangeslinked-noresult' );
- }
- }
}
diff --git a/includes/specials/SpecialRedirect.php b/includes/specials/SpecialRedirect.php
new file mode 100644
index 00000000..f05dacbc
--- /dev/null
+++ b/includes/specials/SpecialRedirect.php
@@ -0,0 +1,235 @@
+<?php
+/**
+ * Implements Special:Redirect
+ *
+ * @section LICENSE
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup SpecialPage
+ */
+
+/**
+ * A special page that redirects to: the user for a numeric user id,
+ * the file for a given filename, or the page for a given revision id.
+ *
+ * @ingroup SpecialPage
+ * @since 1.22
+ */
+class SpecialRedirect extends FormSpecialPage {
+
+ /**
+ * The type of the redirect (user/file/revision)
+ *
+ * @var string $mType
+ * @example 'user'
+ */
+ protected $mType;
+
+ /**
+ * The identifier/value for the redirect (which id, which file)
+ *
+ * @var string $mValue
+ * @example '42'
+ */
+ protected $mValue;
+
+ function __construct() {
+ parent::__construct( 'Redirect' );
+ $this->mType = null;
+ $this->mValue = null;
+ }
+
+ /**
+ * Set $mType and $mValue based on parsed value of $subpage.
+ */
+ function setParameter( $subpage ) {
+ // parse $subpage to pull out the parts
+ $parts = explode( '/', $subpage, 2 );
+ $this->mType = count( $parts ) > 0 ? $parts[0] : null;
+ $this->mValue = count( $parts ) > 1 ? $parts[1] : null;
+ }
+
+ /**
+ * Handle Special:Redirect/user/xxxx (by redirecting to User:YYYY)
+ *
+ * @return string|null url to redirect to, or null if $mValue is invalid.
+ */
+ function dispatchUser() {
+ if ( !ctype_digit( $this->mValue ) ) {
+ return null;
+ }
+ $user = User::newFromId( (int)$this->mValue );
+ $username = $user->getName(); // load User as side-effect
+ if ( $user->isAnon() ) {
+ return null;
+ }
+ $userpage = Title::makeTitle( NS_USER, $username );
+ return $userpage->getFullURL( '', false, PROTO_CURRENT );
+ }
+
+ /**
+ * Handle Special:Redirect/file/xxxx
+ *
+ * @return string|null url to redirect to, or null if $mValue is not found.
+ */
+ function dispatchFile() {
+ $title = Title::makeTitleSafe( NS_FILE, $this->mValue );
+
+ if ( ! $title instanceof Title ) {
+ return null;
+ }
+ $file = wfFindFile( $title );
+
+ if ( !$file || !$file->exists() ) {
+ return null;
+ }
+ // Default behavior: Use the direct link to the file.
+ $url = $file->getURL();
+ $request = $this->getRequest();
+ $width = $request->getInt( 'width', -1 );
+ $height = $request->getInt( 'height', -1 );
+
+ // If a width is requested...
+ if ( $width != -1 ) {
+ $mto = $file->transform( array( 'width' => $width, 'height' => $height ) );
+ // ... and we can
+ if ( $mto && !$mto->isError() ) {
+ // ... change the URL to point to a thumbnail.
+ $url = $mto->getURL();
+ }
+ }
+ return $url;
+ }
+
+ /**
+ * Handle Special:Redirect/revision/xxx
+ * (by redirecting to index.php?oldid=xxx)
+ *
+ * @return string|null url to redirect to, or null if $mValue is invalid.
+ */
+ function dispatchRevision() {
+ $oldid = $this->mValue;
+ if ( !ctype_digit( $oldid ) ) {
+ return null;
+ }
+ $oldid = (int)$oldid;
+ if ( $oldid === 0 ) {
+ return null;
+ }
+ return wfAppendQuery( wfScript( 'index' ), array(
+ 'oldid' => $oldid
+ ) );
+ }
+
+ /**
+ * Use appropriate dispatch* method to obtain a redirection URL,
+ * and either: redirect, set a 404 error code and error message,
+ * or do nothing (if $mValue wasn't set) allowing the form to be
+ * displayed.
+ *
+ * @return bool true if a redirect was successfully handled.
+ */
+ function dispatch() {
+ // the various namespaces supported by Special:Redirect
+ switch ( $this->mType ) {
+ case 'user':
+ $url = $this->dispatchUser();
+ break;
+ case 'file':
+ $url = $this->dispatchFile();
+ break;
+ case 'revision':
+ $url = $this->dispatchRevision();
+ break;
+ default:
+ $this->getOutput()->setStatusCode( 404 );
+ $url = null;
+ break;
+ }
+ if ( $url ) {
+ $this->getOutput()->redirect( $url );
+ return true;
+ }
+ if ( !is_null( $this->mValue ) ) {
+ $this->getOutput()->setStatusCode( 404 );
+ // Message: redirect-not-exists
+ $msg = $this->getMessagePrefix() . '-not-exists';
+ return Status::newFatal( $msg );
+ }
+ return false;
+ }
+
+ protected function getFormFields() {
+ $mp = $this->getMessagePrefix();
+ $ns = array(
+ // subpage => message
+ // Messages: redirect-user, redirect-revision, redirect-file
+ 'user' => $mp . '-user',
+ 'revision' => $mp . '-revision',
+ 'file' => $mp . '-file',
+ );
+ $a = array();
+ $a['type'] = array(
+ 'type' => 'select',
+ 'label-message' => $mp . '-lookup', // Message: redirect-lookup
+ 'options' => array(),
+ 'default' => current( array_keys( $ns ) ),
+ );
+ foreach ( $ns as $n => $m ) {
+ $m = $this->msg( $m )->text();
+ $a['type']['options'][$m] = $n;
+ }
+ $a['value'] = array(
+ 'type' => 'text',
+ 'label-message' => $mp . '-value' // Message: redirect-value
+ );
+ // set the defaults according to the parsed subpage path
+ if ( !empty( $this->mType ) ) {
+ $a['type']['default'] = $this->mType;
+ }
+ if ( !empty( $this->mValue ) ) {
+ $a['value']['default'] = $this->mValue;
+ }
+ return $a;
+ }
+
+ public function onSubmit( array $data ) {
+ if ( !empty( $data['type'] ) && !empty( $data['value'] ) ) {
+ $this->setParameter( $data['type'] . '/' . $data['value'] );
+ }
+ /* if this returns false, will show the form */
+ return $this->dispatch();
+ }
+
+ public function onSuccess() {
+ /* do nothing, we redirect in $this->dispatch if successful. */
+ }
+
+ protected function alterForm( HTMLForm $form ) {
+ /* display summary at top of page */
+ $this->outputHeader();
+ // tweak label on submit button
+ // Message: redirect-submit
+ $form->setSubmitTextMsg( $this->getMessagePrefix() . '-submit' );
+ /* submit form every time */
+ $form->setMethod( 'get' );
+ }
+
+ protected function getGroupName() {
+ return 'redirects';
+ }
+}
diff --git a/includes/specials/SpecialResetTokens.php b/includes/specials/SpecialResetTokens.php
new file mode 100644
index 00000000..ef2a45da
--- /dev/null
+++ b/includes/specials/SpecialResetTokens.php
@@ -0,0 +1,145 @@
+<?php
+/**
+ * Implements Special:ResetTokens
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup SpecialPage
+ */
+
+/**
+ * Let users reset tokens like the watchlist token.
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialResetTokens extends FormSpecialPage {
+ private $tokensList;
+
+ public function __construct() {
+ parent::__construct( 'ResetTokens' );
+ }
+
+ /**
+ * Returns the token information list for this page after running
+ * the hook and filtering out disabled preferences.
+ *
+ * @return array
+ */
+ protected function getTokensList() {
+ global $wgHiddenPrefs;
+
+ if ( !isset( $this->tokensList ) ) {
+ $tokens = array(
+ array( 'preference' => 'watchlisttoken', 'label-message' => 'resettokens-watchlist-token' ),
+ );
+ wfRunHooks( 'SpecialResetTokensTokens', array( &$tokens ) );
+
+ $tokens = array_filter( $tokens, function ( $tok ) use ( $wgHiddenPrefs ) {
+ return !in_array( $tok['preference'], $wgHiddenPrefs );
+ } );
+
+ $this->tokensList = $tokens;
+ }
+
+ return $this->tokensList;
+ }
+
+ public function execute( $par ) {
+ // This is a preferences page, so no user JS for y'all.
+ $this->getOutput()->disallowUserJs();
+
+ parent::execute( $par );
+
+ $this->getOutput()->addReturnTo( SpecialPage::getTitleFor( 'Preferences' ) );
+ }
+
+ public function onSuccess() {
+ $this->getOutput()->wrapWikiMsg(
+ "<div class='successbox'>\n$1\n</div>",
+ 'resettokens-done'
+ );
+ }
+
+ /**
+ * Display appropriate message if there's nothing to do.
+ * The submit button is also suppressed in this case (see alterForm()).
+ */
+ protected function getFormFields() {
+ $user = $this->getUser();
+ $tokens = $this->getTokensList();
+
+ if ( $tokens ) {
+ $tokensForForm = array();
+ foreach ( $tokens as $tok ) {
+ $label = $this->msg( 'resettokens-token-label' )
+ ->rawParams( $this->msg( $tok['label-message'] )->parse() )
+ ->params( $user->getTokenFromOption( $tok['preference'] ) )
+ ->escaped();
+ $tokensForForm[ $label ] = $tok['preference'];
+ }
+
+ $desc = array(
+ 'label-message' => 'resettokens-tokens',
+ 'type' => 'multiselect',
+ 'options' => $tokensForForm,
+ );
+ } else {
+ $desc = array(
+ 'label-message' => 'resettokens-no-tokens',
+ 'type' => 'info',
+ );
+ }
+
+ return array(
+ 'tokens' => $desc,
+ );
+ }
+
+ /**
+ * Suppress the submit button if there's nothing to do;
+ * provide additional message on it otherwise.
+ */
+ protected function alterForm( HTMLForm $form ) {
+ if ( $this->getTokensList() ) {
+ $form->setSubmitTextMsg( 'resettokens-resetbutton' );
+ } else {
+ $form->suppressDefaultSubmit();
+ }
+ }
+
+ public function onSubmit( array $formData ) {
+ if ( $formData['tokens'] ) {
+ $user = $this->getUser();
+ foreach ( $formData['tokens'] as $tokenPref ) {
+ $user->resetTokenFromOption( $tokenPref );
+ }
+ $user->saveSettings();
+
+ return true;
+ }
+
+ return false;
+ }
+
+ protected function getGroupName() {
+ return 'users';
+ }
+
+ public function isListed() {
+ return (bool)$this->getTokensList();
+ }
+}
diff --git a/includes/specials/SpecialRevisiondelete.php b/includes/specials/SpecialRevisiondelete.php
index 5a5f8ffb..825be6c4 100644
--- a/includes/specials/SpecialRevisiondelete.php
+++ b/includes/specials/SpecialRevisiondelete.php
@@ -49,68 +49,43 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
/** Array of checkbox specs (message, name, deletion bits) */
var $checks;
- /** Information about the current type */
- var $typeInfo;
+ /** UI Labels about the current type */
+ var $typeLabels;
/** The RevDel_List object, storing the list of items to be deleted/undeleted */
var $list;
/**
- * Assorted information about each type, needed by the special page.
- * TODO Move some of this to the list class
+ * UI labels for each type.
*/
- static $allowedTypes = array(
+ static $UILabels = array(
'revision' => array(
'check-label' => 'revdelete-hide-text',
- 'deletion-bits' => Revision::DELETED_TEXT,
'success' => 'revdelete-success',
'failure' => 'revdelete-failure',
- 'list-class' => 'RevDel_RevisionList',
- 'permission' => 'deleterevision',
),
'archive' => array(
'check-label' => 'revdelete-hide-text',
- 'deletion-bits' => Revision::DELETED_TEXT,
'success' => 'revdelete-success',
'failure' => 'revdelete-failure',
- 'list-class' => 'RevDel_ArchiveList',
- 'permission' => 'deleterevision',
),
- 'oldimage'=> array(
+ 'oldimage' => array(
'check-label' => 'revdelete-hide-image',
- 'deletion-bits' => File::DELETED_FILE,
'success' => 'revdelete-success',
'failure' => 'revdelete-failure',
- 'list-class' => 'RevDel_FileList',
- 'permission' => 'deleterevision',
),
'filearchive' => array(
'check-label' => 'revdelete-hide-image',
- 'deletion-bits' => File::DELETED_FILE,
'success' => 'revdelete-success',
'failure' => 'revdelete-failure',
- 'list-class' => 'RevDel_ArchivedFileList',
- 'permission' => 'deleterevision',
),
'logging' => array(
'check-label' => 'revdelete-hide-name',
- 'deletion-bits' => LogPage::DELETED_ACTION,
'success' => 'logdelete-success',
'failure' => 'logdelete-failure',
- 'list-class' => 'RevDel_LogList',
- 'permission' => 'deletelogentry',
),
);
- /** Type map to support old log entries */
- static $deprecatedTypeMap = array(
- 'oldid' => 'revision',
- 'artimestamp' => 'archive',
- 'oldimage' => 'oldimage',
- 'fileid' => 'filearchive',
- 'logid' => 'logging',
- );
-
public function __construct() {
parent::__construct( 'Revisiondelete', 'deletedhistory' );
}
@@ -147,19 +122,6 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
} else {
$this->typeName = $request->getVal( 'type' );
$this->targetObj = Title::newFromText( $request->getText( 'target' ) );
- if ( $this->targetObj && $this->targetObj->isSpecial( 'Log' ) && count( $this->ids ) !== 0 ) {
- $result = wfGetDB( DB_SLAVE )->select( 'logging',
- 'log_type',
- array( 'log_id' => $this->ids ),
- __METHOD__,
- array( 'DISTINCT' )
- );
-
- if ( $result->numRows() == 1 ) {
- // If there's only one type, the target can be set to include it.
- $this->targetObj = SpecialPage::getTitleFor( 'Log', $result->current()->log_type );
- }
- }
}
# For reviewing deleted files...
@@ -170,28 +132,21 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
return;
}
- if ( isset( self::$deprecatedTypeMap[$this->typeName] ) ) {
- $this->typeName = self::$deprecatedTypeMap[$this->typeName];
- }
+ $this->typeName = RevisionDeleter::getCanonicalTypeName( $this->typeName );
# No targets?
- if( !isset( self::$allowedTypes[$this->typeName] ) || count( $this->ids ) == 0 ) {
+ if ( !$this->typeName || count( $this->ids ) == 0 ) {
throw new ErrorPageError( 'revdelete-nooldid-title', 'revdelete-nooldid-text' );
}
- $this->typeInfo = self::$allowedTypes[$this->typeName];
- $this->mIsAllowed = $user->isAllowed( $this->typeInfo['permission'] );
-
- # If we have revisions, get the title from the first one
- # since they should all be from the same page. This allows
- # for more flexibility with page moves...
- if( $this->typeName == 'revision' ) {
- $rev = Revision::newFromId( $this->ids[0] );
- $this->targetObj = $rev ? $rev->getTitle() : $this->targetObj;
- }
+ $this->typeLabels = self::$UILabels[$this->typeName];
+ $this->mIsAllowed = $user->isAllowed( RevisionDeleter::getRestriction( $this->typeName ) );
+
+ # Allow the list type to adjust the passed target
+ $this->targetObj = RevisionDeleter::suggestTarget( $this->typeName, $this->targetObj, $this->ids );
$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;
}
@@ -200,17 +155,19 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
# Initialise checkboxes
$this->checks = array(
- array( $this->typeInfo['check-label'], 'wpHidePrimary', $this->typeInfo['deletion-bits'] ),
+ array( $this->typeLabels['check-label'], 'wpHidePrimary',
+ RevisionDeleter::getRevdelConstant( $this->typeName )
+ ),
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 );
}
# Either submit or create our form
- if( $this->mIsAllowed && $this->submitClicked ) {
+ if ( $this->mIsAllowed && $this->submitClicked ) {
$this->submit( $request );
} else {
$this->showForm();
@@ -223,7 +180,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
LogEventsList::showLogExtract( $output, 'delete',
$this->targetObj, '', array( 'lim' => 25, 'conds' => $qc ) );
# Show relevant lines from the suppression log
- if( $user->isAllowed( 'suppressionlog' ) ) {
+ if ( $user->isAllowed( 'suppressionlog' ) ) {
$suppressLogPage = new LogPage( 'suppress' );
$output->addHTML( "<h2>" . $suppressLogPage->getName()->escaped() . "</h2>\n" );
LogEventsList::showLogExtract( $output, 'suppress',
@@ -236,7 +193,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
*/
protected function showConvenienceLinks() {
# Give a link to the logs/hist for this page
- if( $this->targetObj ) {
+ if ( $this->targetObj ) {
$links = array();
$links[] = Linker::linkKnown(
SpecialPage::getTitleFor( 'Log' ),
@@ -253,7 +210,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,
@@ -296,8 +253,8 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
return;
}
$user = $this->getUser();
- if( !$oimage->userCan( File::DELETED_FILE, $user ) ) {
- if( $oimage->isDeleted( File::DELETED_RESTRICTED ) ) {
+ if ( !$oimage->userCan( File::DELETED_FILE, $user ) ) {
+ if ( $oimage->isDeleted( File::DELETED_RESTRICTED ) ) {
throw new PermissionsError( 'suppressrevision' );
} else {
throw new PermissionsError( 'deletedtext' );
@@ -312,10 +269,11 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
$this->getOutput()->addHTML(
Xml::openElement( 'form', array(
'method' => 'POST',
- 'action' => $this->getTitle()->getLocalUrl(
- 'target=' . urlencode( $this->targetObj->getPrefixedDBkey() ) .
- '&file=' . urlencode( $archiveName ) .
- '&token=' . urlencode( $user->getEditToken( $archiveName ) ) )
+ 'action' => $this->getTitle()->getLocalURL( array(
+ 'target' => $this->targetObj->getPrefixedDBkey(),
+ 'file' => $archiveName,
+ 'token' => $user->getEditToken( $archiveName ),
+ ) )
)
) .
Xml::submitButton( $this->msg( 'revdelete-show-file-submit' )->text() ) .
@@ -342,8 +300,9 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
*/
protected function getList() {
if ( is_null( $this->list ) ) {
- $class = $this->typeInfo['list-class'];
- $this->list = new $class( $this->getContext(), $this->targetObj, $this->ids );
+ $this->list = RevisionDeleter::createList(
+ $this->typeName, $this->getContext(), $this->targetObj, $this->ids
+ );
}
return $this->list;
}
@@ -370,7 +329,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
for ( $list->reset(); $list->current(); $list->next() ) {
$item = $list->current();
if ( !$item->canView() ) {
- if( !$this->submitClicked ) {
+ if ( !$this->submitClicked ) {
throw new PermissionsError( 'suppressrevision' );
}
$UserAllowed = false;
@@ -379,7 +338,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
$this->getOutput()->addHTML( $item->getHTML() );
}
- if( !$numRevisions ) {
+ if ( !$numRevisions ) {
throw new ErrorPageError( 'revdelete-nooldid-title', 'revdelete-nooldid-text' );
}
@@ -388,12 +347,14 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
$this->addUsageText();
// Normal sysops can always see what they did, but can't always change it
- if( !$UserAllowed ) return;
+ if ( !$UserAllowed ) {
+ return;
+ }
// Show form if the user can submit
- if( $this->mIsAllowed ) {
+ if ( $this->mIsAllowed ) {
$out = Xml::openElement( 'form', array( 'method' => 'post',
- 'action' => $this->getTitle()->getLocalUrl( array( 'action' => 'submit' ) ),
+ 'action' => $this->getTitle()->getLocalURL( array( 'action' => 'submit' ) ),
'id' => 'mw-revdel-form-revisions' ) ) .
Xml::fieldset( $this->msg( 'revdelete-legend' )->text() ) .
$this->buildCheckBoxes() .
@@ -406,7 +367,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
Xml::listDropDown( 'wpRevDeleteReasonList',
$this->msg( 'revdelete-reason-dropdown' )->inContentLanguage()->text(),
$this->msg( 'revdelete-reasonotherlist' )->inContentLanguage()->text(),
- '', 'wpReasonDropDown', 1
+ $this->getRequest()->getText( 'wpRevDeleteReasonList', 'other' ), 'wpReasonDropDown', 1
) .
'</td>' .
"</tr><tr>\n" .
@@ -432,10 +393,10 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
} else {
$out = '';
}
- if( $this->mIsAllowed ) {
+ if ( $this->mIsAllowed ) {
$out .= Xml::closeElement( 'form' ) . "\n";
// Show link to edit the dropdown reasons
- if( $this->getUser()->isAllowed( 'editinterface' ) ) {
+ if ( $this->getUser()->isAllowed( 'editinterface' ) ) {
$title = Title::makeTitle( NS_MEDIAWIKI, 'Revdelete-reason-dropdown' );
$link = Linker::link(
$title,
@@ -455,10 +416,10 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
*/
protected function addUsageText() {
$this->getOutput()->addWikiMsg( 'revdelete-text' );
- if( $this->getUser()->isAllowed( 'suppressrevision' ) ) {
+ if ( $this->getUser()->isAllowed( 'suppressrevision' ) ) {
$this->getOutput()->addWikiMsg( 'revdelete-suppress-text' );
}
- if( $this->mIsAllowed ) {
+ if ( $this->mIsAllowed ) {
$this->getOutput()->addWikiMsg( 'revdelete-confirm' );
}
}
@@ -470,17 +431,18 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
$html = '<table>';
// If there is just one item, use checkboxes
$list = $this->getList();
- if( $list->length() == 1 ) {
+ if ( $list->length() == 1 ) {
$list->reset();
$bitfield = $list->current()->getBits(); // existing field
- if( $this->submitClicked ) {
+ if ( $this->submitClicked ) {
$bitfield = $this->extractBitfield( $this->extractBitParams(), $bitfield );
}
- foreach( $this->checks as $item ) {
+ foreach ( $this->checks as $item ) {
list( $message, $name, $field ) = $item;
$innerHTML = Xml::checkLabel( $this->msg( $message )->text(), $name, $name, $bitfield & $field );
- if( $field == Revision::DELETED_RESTRICTED )
+ if ( $field == Revision::DELETED_RESTRICTED ) {
$innerHTML = "<b>$innerHTML</b>";
+ }
$line = Xml::tags( 'td', array( 'class' => 'mw-input' ), $innerHTML );
$html .= "<tr>$line</tr>\n";
}
@@ -491,10 +453,10 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
$html .= '<th class="mw-revdel-checkbox">' . $this->msg( 'revdelete-radio-unset' )->escaped() . '</th>';
$html .= '<th class="mw-revdel-checkbox">' . $this->msg( 'revdelete-radio-set' )->escaped() . '</th>';
$html .= "<th></th></tr>\n";
- foreach( $this->checks as $item ) {
+ foreach ( $this->checks as $item ) {
list( $message, $name, $field ) = $item;
// If there are several items, use third state by default...
- if( $this->submitClicked ) {
+ if ( $this->submitClicked ) {
$selected = $this->getRequest()->getInt( $name, 0 /* unchecked */ );
} else {
$selected = -1; // use existing field
@@ -503,7 +465,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
$line .= '<td class="mw-revdel-checkbox">' . Xml::radio( $name, 0, $selected == 0 ) . '</td>';
$line .= '<td class="mw-revdel-checkbox">' . Xml::radio( $name, 1, $selected == 1 ) . '</td>';
$label = $this->msg( $message )->escaped();
- if( $field == Revision::DELETED_RESTRICTED ) {
+ if ( $field == Revision::DELETED_RESTRICTED ) {
$label = "<b>$label</b>";
}
$line .= "<td>$label</td>";
@@ -523,21 +485,21 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
protected function submit() {
# Check edit token on submission
$token = $this->getRequest()->getVal( 'wpEditToken' );
- if( $this->submitClicked && !$this->getUser()->matchEditToken( $token ) ) {
+ if ( $this->submitClicked && !$this->getUser()->matchEditToken( $token ) ) {
$this->getOutput()->addWikiMsg( 'sessionfailure' );
return false;
}
$bitParams = $this->extractBitParams();
$listReason = $this->getRequest()->getText( 'wpRevDeleteReasonList', 'other' ); // from dropdown
$comment = $listReason;
- if( $comment != 'other' && $this->otherReason != '' ) {
+ if ( $comment != 'other' && $this->otherReason != '' ) {
// Entry from drop down menu + additional comment
$comment .= $this->msg( 'colon-separator' )->inContentLanguage()->text() . $this->otherReason;
- } elseif( $comment == 'other' ) {
+ } elseif ( $comment == 'other' ) {
$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...
@@ -557,7 +519,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
*/
protected function success() {
$this->getOutput()->setPageTitle( $this->msg( 'actioncomplete' ) );
- $this->getOutput()->wrapWikiMsg( "<span class=\"success\">\n$1\n</span>", $this->typeInfo['success'] );
+ $this->getOutput()->wrapWikiMsg( "<span class=\"success\">\n$1\n</span>", $this->typeLabels['success'] );
$this->list->reloadFromMaster();
$this->showForm();
}
@@ -567,7 +529,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
*/
protected function failure( $status ) {
$this->getOutput()->setPageTitle( $this->msg( 'actionfailed' ) );
- $this->getOutput()->addWikiText( $status->getWikiText( $this->typeInfo['failure'] ) );
+ $this->getOutput()->addWikiText( $status->getWikiText( $this->typeLabels['failure'] ) );
$this->showForm();
}
@@ -578,15 +540,15 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
*/
protected function extractBitParams() {
$bitfield = array();
- foreach( $this->checks as $item ) {
+ foreach ( $this->checks as $item ) {
list( /* message */, $name, $field ) = $item;
$val = $this->getRequest()->getInt( $name, 0 /* unchecked */ );
- if( $val < -1 || $val > 1) {
+ 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;
@@ -594,21 +556,13 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
/**
* Put together a rev_deleted bitfield
+ * @deprecated since 1.22, use RevisionDeleter::extractBitfield instead
* @param array $bitPars extractBitParams() params
* @param int $oldfield current bitfield
* @return array
*/
public static function extractBitfield( $bitPars, $oldfield ) {
- // Build the actual new rev_deleted bitfield
- $newBits = 0;
- foreach( $bitPars as $const => $val ) {
- if( $val == 1 ) {
- $newBits |= $const; // $const is the *_deleted const
- } elseif( $val == -1 ) {
- $newBits |= ($oldfield & $const); // use existing
- }
- }
- return $newBits;
+ return RevisionDeleter::extractBitfield( $bitPars, $oldfield );
}
/**
diff --git a/includes/specials/SpecialSearch.php b/includes/specials/SpecialSearch.php
index 6c401486..8609c740 100644
--- a/includes/specials/SpecialSearch.php
+++ b/includes/specials/SpecialSearch.php
@@ -42,6 +42,9 @@ class SpecialSearch extends SpecialPage {
/// Search engine
protected $searchEngine;
+ /// Search engine type, if not default
+ protected $searchEngineType;
+
/// For links
protected $extraParams = array();
@@ -98,6 +101,8 @@ class SpecialSearch extends SpecialPage {
$this->load();
+ $this->searchEngineType = $request->getVal( 'srbackend' );
+
if ( $request->getVal( 'fulltext' )
|| !is_null( $request->getVal( 'offset' ) )
|| !is_null( $request->getVal( 'searchx' ) ) )
@@ -137,7 +142,7 @@ class SpecialSearch extends SpecialPage {
if ( $profile === null ) {
// BC with old request format
$profile = 'advanced';
- foreach( $profiles as $key => $data ) {
+ foreach ( $profiles as $key => $data ) {
if ( $nslist === $data['namespaces'] && $key !== 'advanced' ) {
$profile = $key;
}
@@ -173,7 +178,7 @@ class SpecialSearch extends SpecialPage {
# Try to go to page as entered.
$t = Title::newFromText( $term );
# If the string cannot be used to create a title
- if( is_null( $t ) ) {
+ if ( is_null( $t ) ) {
$this->showResults( $term );
return;
}
@@ -185,19 +190,19 @@ class SpecialSearch extends SpecialPage {
return;
}
- if( !is_null( $t ) ) {
+ if ( !is_null( $t ) ) {
$this->getOutput()->redirect( $t->getFullURL() );
return;
}
# No match, generate an edit URL
$t = Title::newFromText( $term );
- if( !is_null( $t ) ) {
+ if ( !is_null( $t ) ) {
global $wgGoToEdit;
wfRunHooks( 'SpecialSearchNogomatch', array( &$t ) );
wfDebugLog( 'nogomatch', $t->getText(), false );
# If the feature is enabled, go straight to the edit page
- if( $wgGoToEdit ) {
+ if ( $wgGoToEdit ) {
$this->getOutput()->redirect( $t->getFullURL( array( 'action' => 'edit' ) ) );
return;
}
@@ -253,18 +258,24 @@ class SpecialSearch extends SpecialPage {
$rewritten = $search->replacePrefixes( $term );
$titleMatches = $search->searchTitle( $rewritten );
- if( !( $titleMatches instanceof SearchResultTooMany ) ) {
+ if ( !( $titleMatches instanceof SearchResultTooMany ) ) {
$textMatches = $search->searchText( $rewritten );
}
+ $textStatus = null;
+ if ( $textMatches instanceof Status ) {
+ $textStatus = $textMatches;
+ $textMatches = null;
+ }
+
// did you mean... suggestions
- if( $textMatches && $textMatches->hasSuggestion() ) {
+ if ( $textMatches && !$textStatus && $textMatches->hasSuggestion() ) {
$st = SpecialPage::getTitleFor( 'Search' );
# mirror Go/Search behavior of original request ..
$didYouMeanParams = array( 'search' => $textMatches->getSuggestionQuery() );
- if( $this->fulltext != null ) {
+ if ( $this->fulltext != null ) {
$didYouMeanParams['fulltext'] = $this->fulltext;
}
@@ -275,7 +286,7 @@ class SpecialSearch extends SpecialPage {
$suggestionSnippet = $textMatches->getSuggestionSnippet();
- if( $suggestionSnippet == '' ) {
+ if ( $suggestionSnippet == '' ) {
$suggestionSnippet = null;
}
@@ -317,14 +328,14 @@ class SpecialSearch extends SpecialPage {
);
// Sometimes the search engine knows there are too many hits
- if( $titleMatches instanceof SearchResultTooMany ) {
+ if ( $titleMatches instanceof SearchResultTooMany ) {
$out->wrapWikiMsg( "==$1==\n", 'toomanymatches' );
wfProfileOut( __METHOD__ );
return;
}
$filePrefix = $wgContLang->getFormattedNsText( NS_FILE ) . ':';
- if( trim( $term ) === '' || $filePrefix === trim( $term ) ) {
+ if ( trim( $term ) === '' || $filePrefix === trim( $term ) ) {
$out->addHTML( $this->formHeader( $term, 0, 0 ) );
$out->addHtml( $this->getProfileForm( $this->profile, $term ) );
$out->addHTML( '</form>' );
@@ -347,10 +358,12 @@ 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 ) );
@@ -360,7 +373,7 @@ class SpecialSearch extends SpecialPage {
$out->addHtml( "<div class='searchresults'>" );
// prev/next links
- if( $num || $this->offset ) {
+ if ( $num || $this->offset ) {
// Show the create link ahead
$this->showCreateLink( $t );
$prevnext = $this->getLanguage()->viewPrevNext( $this->getTitle(), $this->offset, $this->limit,
@@ -374,40 +387,46 @@ class SpecialSearch extends SpecialPage {
}
$out->parserOptions()->setEditSection( false );
- if( $titleMatches ) {
- if( $numTitleMatches > 0 ) {
+ if ( $titleMatches ) {
+ if ( $numTitleMatches > 0 ) {
$out->wrapWikiMsg( "==$1==\n", 'titlematches' );
$out->addHTML( $this->showMatches( $titleMatches ) );
}
$titleMatches->free();
}
- if( $textMatches ) {
+ if ( $textMatches && !$textStatus ) {
// output appropriate heading
- if( $numTextMatches > 0 && $numTitleMatches > 0 ) {
+ if ( $numTextMatches > 0 && $numTitleMatches > 0 ) {
// if no title matches the heading is redundant
$out->wrapWikiMsg( "==$1==\n", 'textmatches' );
- } elseif( $totalRes == 0 ) {
+ } elseif ( $totalRes == 0 ) {
# Don't show the 'no text matches' if we received title matches
# $out->wrapWikiMsg( "==$1==\n", 'notextmatches' );
}
// show interwiki results if any
- if( $textMatches->hasInterwikiResults() ) {
+ if ( $textMatches->hasInterwikiResults() ) {
$out->addHTML( $this->showInterwiki( $textMatches->getInterwikiResults(), $term ) );
}
// show results
- if( $numTextMatches > 0 ) {
+ if ( $numTextMatches > 0 ) {
$out->addHTML( $this->showMatches( $textMatches ) );
}
$textMatches->free();
}
- if( $num === 0 ) {
- $out->wrapWikiMsg( "<p class=\"mw-search-nonefound\">\n$1</p>", array( 'search-nonefound', wfEscapeWikiText( $term ) ) );
- $this->showCreateLink( $t );
+ if ( $num === 0 ) {
+ if ( $textStatus ) {
+ $out->addHTML( '<div class="error">' .
+ htmlspecialchars( $textStatus->getWikiText( 'search-error' ) ) . '</div>' );
+ } else {
+ $out->wrapWikiMsg( "<p class=\"mw-search-nonefound\">\n$1</p>",
+ array( 'search-nonefound', wfEscapeWikiText( $term ) ) );
+ $this->showCreateLink( $t );
+ }
}
$out->addHtml( "</div>" );
- if( $num || $this->offset ) {
+ if ( $num || $this->offset ) {
$out->addHTML( "<p class='mw-search-pager-bottom'>{$prevnext}</p>\n" );
}
wfRunHooks( 'SpecialSearchResultsAppend', array( $this, $out, $term ) );
@@ -421,16 +440,16 @@ class SpecialSearch extends SpecialPage {
// show direct page/create link if applicable
// Check DBkey !== '' in case of fragment link only.
- if( is_null( $t ) || $t->getDBkey() === '' ) {
+ if ( is_null( $t ) || $t->getDBkey() === '' ) {
// invalid title
// preserve the paragraph for margins etc...
$this->getOutput()->addHtml( '<p></p>' );
return;
}
- if( $t->isKnown() ) {
+ if ( $t->isKnown() ) {
$messageName = 'searchmenu-exists';
- } elseif( $t->userCan( 'create', $this->getUser() ) ) {
+ } elseif ( $t->userCan( 'create', $this->getUser() ) ) {
$messageName = 'searchmenu-new';
} else {
$messageName = 'searchmenu-new-nocreate';
@@ -439,7 +458,7 @@ class SpecialSearch extends SpecialPage {
wfRunHooks( 'SpecialSearchCreateLink', array( $t, &$params ) );
// Extensions using the hook might still return an empty $messageName
- if( $messageName ) {
+ if ( $messageName ) {
$this->getOutput()->wrapWikiMsg( "<p class=\"mw-search-createlink\">\n$1</p>", $params );
} else {
// preserve the paragraph for margins etc...
@@ -452,9 +471,9 @@ class SpecialSearch extends SpecialPage {
*/
protected function setupPage( $term ) {
# Should advanced UI be used?
- $this->searchAdvanced = ($this->profile === 'advanced');
+ $this->searchAdvanced = ( $this->profile === 'advanced' );
$out = $this->getOutput();
- if( strval( $term ) !== '' ) {
+ if ( strval( $term ) !== '' ) {
$out->setPageTitle( $this->msg( 'searchresults' ) );
$out->setHTMLTitle( $this->msg( 'pagetitle' )->rawParams(
$this->msg( 'searchresults-title' )->rawParams( $term )->text()
@@ -473,8 +492,8 @@ class SpecialSearch extends SpecialPage {
*/
protected function powerSearch( &$request ) {
$arr = array();
- foreach( SearchEngine::searchableNamespaces() as $ns => $name ) {
- if( $request->getCheck( 'ns' . $ns ) ) {
+ foreach ( SearchEngine::searchableNamespaces() as $ns => $name ) {
+ if ( $request->getCheck( 'ns' . $ns ) ) {
$arr[] = $ns;
}
}
@@ -490,10 +509,10 @@ class SpecialSearch extends SpecialPage {
protected function powerSearchOptions() {
$opt = array();
$opt['redirs'] = $this->searchRedirects ? 1 : 0;
- if( $this->profile !== 'advanced' ) {
+ if ( $this->profile !== 'advanced' ) {
$opt['profile'] = $this->profile;
} else {
- foreach( $this->namespaces as $n ) {
+ foreach ( $this->namespaces as $n ) {
$opt['ns' . $n] = 1;
}
}
@@ -515,12 +534,12 @@ 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";
$result = $matches->next();
- while( $result ) {
+ while ( $result ) {
$out .= $this->showHit( $result, $terms );
$result = $matches->next();
}
@@ -543,7 +562,7 @@ class SpecialSearch extends SpecialPage {
protected function showHit( $result, $terms ) {
wfProfileIn( __METHOD__ );
- if( $result->isBrokenTitle() ) {
+ if ( $result->isBrokenTitle() ) {
wfProfileOut( __METHOD__ );
return "<!-- Broken link in search result -->\n";
}
@@ -552,8 +571,9 @@ class SpecialSearch extends SpecialPage {
$titleSnippet = $result->getTitleSnippet( $terms );
- if( $titleSnippet == '' )
+ if ( $titleSnippet == '' ) {
$titleSnippet = null;
+ }
$link_t = clone $t;
@@ -568,7 +588,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', $this->getUser() ) ) {
+ if ( !$t->userCan( 'read', $this->getUser() ) ) {
wfProfileOut( __METHOD__ );
return "<li>{$link}</li>\n";
}
@@ -576,7 +596,7 @@ class SpecialSearch extends SpecialPage {
// If the page doesn't *exist*... our search index is out of date.
// The least confusing at this point is to drop the result.
// You may get less results, but... oh well. :P
- if( $result->isMissingRevision() ) {
+ if ( $result->isMissingRevision() ) {
wfProfileOut( __METHOD__ );
return "<!-- missing page " . htmlspecialchars( $t->getPrefixedText() ) . "-->\n";
}
@@ -588,9 +608,10 @@ class SpecialSearch extends SpecialPage {
$sectionText = $result->getSectionSnippet( $terms );
$redirect = '';
- if( !is_null( $redirectTitle ) ) {
- if( $redirectText == '' )
+ if ( !is_null( $redirectTitle ) ) {
+ if ( $redirectText == '' ) {
$redirectText = null;
+ }
$redirect = "<span class='searchalttitle'>" .
$this->msg( 'search-redirect' )->rawParams(
@@ -600,9 +621,10 @@ class SpecialSearch extends SpecialPage {
$section = '';
- if( !is_null( $sectionTitle ) ) {
- if( $sectionText == '' )
+ if ( !is_null( $sectionTitle ) ) {
+ if ( $sectionText == '' ) {
$sectionText = null;
+ }
$section = "<span class='searchalttitle'>" .
$this->msg( 'search-section' )->rawParams(
@@ -616,7 +638,7 @@ class SpecialSearch extends SpecialPage {
$lang = $this->getLanguage();
// format score
- if( is_null( $result->getScore() ) ) {
+ if ( is_null( $result->getScore() ) ) {
// Search engine doesn't report scoring info
$score = '';
} else {
@@ -632,7 +654,7 @@ class SpecialSearch extends SpecialPage {
$size = $this->msg( 'search-result-size', $lang->formatSize( $byteSize ) )
->numParams( $wordCount )->escaped();
- if( $t->getNamespace() == NS_CATEGORY ) {
+ if ( $t->getNamespace() == NS_CATEGORY ) {
$cat = Category::newFromTitle( $t );
$size = $this->msg( 'search-result-category-size' )
->numParams( $cat->getPageCount(), $cat->getSubcatCount(), $cat->getFileCount() )
@@ -643,7 +665,7 @@ class SpecialSearch extends SpecialPage {
// link to related articles if supported
$related = '';
- if( $result->hasRelated() ) {
+ if ( $result->hasRelated() ) {
$st = SpecialPage::getTitleFor( 'Search' );
$stParams = array_merge(
$this->powerSearchOptions(),
@@ -663,11 +685,11 @@ class SpecialSearch extends SpecialPage {
}
// Include a thumbnail for media files...
- if( $t->getNamespace() == NS_FILE ) {
+ if ( $t->getNamespace() == NS_FILE ) {
$img = wfFindFile( $t );
- if( $img ) {
+ if ( $img ) {
$thumb = $img->transform( array( 'width' => 120, 'height' => 120 ) );
- if( $thumb ) {
+ if ( $thumb ) {
$desc = $this->msg( 'parentheses' )->rawParams( $img->getShortDesc() )->escaped();
wfProfileOut( __METHOD__ );
// Float doesn't seem to interact well with the bullets.
@@ -693,7 +715,7 @@ class SpecialSearch extends SpecialPage {
$html = null;
- if ( wfRunHooks( 'ShowSearchHit', array (
+ if ( wfRunHooks( 'ShowSearchHit', array(
$this, $result, $terms,
&$link, &$redirect, &$section, &$extract,
&$score, &$size, &$date, &$related,
@@ -721,23 +743,23 @@ class SpecialSearch extends SpecialPage {
wfProfileIn( __METHOD__ );
$terms = $wgContLang->convertForSearchResult( $matches->termMatches() );
- $out = "<div id='mw-search-interwiki'><div id='mw-search-interwiki-caption'>".
+ $out = "<div id='mw-search-interwiki'><div id='mw-search-interwiki-caption'>" .
$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 ) {
+ foreach ( $customLines as $line ) {
$parts = explode( ":", $line, 2 );
- if( count( $parts ) == 2 ) { // validate line
+ if ( count( $parts ) == 2 ) { // validate line
$customCaptions[$parts[0]] = $parts[1];
}
}
$prev = null;
$result = $matches->next();
- while( $result ) {
+ while ( $result ) {
$out .= $this->showInterwikiHit( $result, $prev, $terms, $query, $customCaptions );
$prev = $result->getInterwikiPrefix();
$result = $matches->next();
@@ -762,10 +784,10 @@ class SpecialSearch extends SpecialPage {
*
* @return string
*/
- protected function showInterwikiHit( $result, $lastInterwiki, $terms, $query, $customCaptions) {
+ protected function showInterwikiHit( $result, $lastInterwiki, $terms, $query, $customCaptions ) {
wfProfileIn( __METHOD__ );
- if( $result->isBrokenTitle() ) {
+ if ( $result->isBrokenTitle() ) {
wfProfileOut( __METHOD__ );
return "<!-- Broken link in search result -->\n";
}
@@ -774,8 +796,9 @@ class SpecialSearch extends SpecialPage {
$titleSnippet = $result->getTitleSnippet( $terms );
- if( $titleSnippet == '' )
+ if ( $titleSnippet == '' ) {
$titleSnippet = null;
+ }
$link = Linker::linkKnown(
$t,
@@ -786,9 +809,10 @@ class SpecialSearch extends SpecialPage {
$redirectTitle = $result->getRedirectTitle();
$redirectText = $result->getRedirectSnippet( $terms );
$redirect = '';
- if( !is_null( $redirectTitle ) ) {
- if( $redirectText == '' )
+ if ( !is_null( $redirectTitle ) ) {
+ if ( $redirectText == '' ) {
$redirectText = null;
+ }
$redirect = "<span class='searchalttitle'>" .
$this->msg( 'search-redirect' )->rawParams(
@@ -798,8 +822,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 {
@@ -856,17 +880,21 @@ class SpecialSearch extends SpecialPage {
* @return String: HTML form
*/
protected function powerSearchBox( $term, $opts ) {
+ global $wgContLang;
+
// Groups namespaces into rows according to subject
$rows = array();
- foreach( SearchEngine::searchableNamespaces() as $namespace => $name ) {
+ foreach ( SearchEngine::searchableNamespaces() as $namespace => $name ) {
$subject = MWNamespace::getSubject( $namespace );
- if( !array_key_exists( $subject, $rows ) ) {
+ if ( !array_key_exists( $subject, $rows ) ) {
$rows[$subject] = "";
}
- $name = str_replace( '_', ' ', $name );
- if( $name == '' ) {
+
+ $name = $wgContLang->getConverter()->convertNamespace( $namespace );
+ if ( $name == '' ) {
$name = $this->msg( 'blanknamespace' )->text();
}
+
$rows[$subject] .=
Xml::openElement(
'td', array( 'style' => 'white-space: nowrap' )
@@ -879,27 +907,30 @@ class SpecialSearch extends SpecialPage {
) .
Xml::closeElement( 'td' );
}
+
$rows = array_values( $rows );
$numRows = count( $rows );
// Lays out namespaces in multiple floating two-column tables so they'll
// be arranged nicely while still accommodating different screen widths
$namespaceTables = '';
- for( $i = 0; $i < $numRows; $i += 4 ) {
+ for ( $i = 0; $i < $numRows; $i += 4 ) {
$namespaceTables .= Xml::openElement(
'table',
array( 'cellpadding' => 0, 'cellspacing' => 0 )
);
- for( $j = $i; $j < $i + 4 && $j < $numRows; $j++ ) {
+
+ for ( $j = $i; $j < $i + 4 && $j < $numRows; $j++ ) {
$namespaceTables .= Xml::tags( 'tr', null, $rows[$j] );
}
+
$namespaceTables .= Xml::closeElement( 'table' );
}
$showSections = array( 'namespaceTables' => $namespaceTables );
// Show redirects check only if backend supports it
- if( $this->getSearchEngine()->supports( 'list-redirects' ) ) {
+ if ( $this->getSearchEngine()->supports( 'list-redirects' ) ) {
$showSections['redirects'] =
Xml::checkLabel( $this->msg( 'powersearch-redir' )->text(), 'redirs', 'redirs', $this->searchRedirects );
}
@@ -908,12 +939,11 @@ class SpecialSearch extends SpecialPage {
$hidden = '';
unset( $opts['redirs'] );
- foreach( $opts as $key => $value ) {
+ foreach ( $opts as $key => $value ) {
$hidden .= Html::hidden( $key, $value );
}
// Return final output
- return
- Xml::openElement(
+ return Xml::openElement(
'fieldset',
array( 'id' => 'mw-searchoptions', 'style' => 'margin:0em;' )
) .
@@ -969,8 +999,10 @@ class SpecialSearch extends SpecialPage {
wfRunHooks( 'SpecialSearchProfiles', array( &$profiles ) );
- foreach( $profiles as &$data ) {
- if ( !is_array( $data['namespaces'] ) ) continue;
+ foreach ( $profiles as &$data ) {
+ if ( !is_array( $data['namespaces'] ) ) {
+ continue;
+ }
sort( $data['namespaces'] );
}
@@ -987,7 +1019,7 @@ class SpecialSearch extends SpecialPage {
$out = Xml::openElement( 'div', array( 'class' => 'mw-search-formheader' ) );
$bareterm = $term;
- if( $this->startsWithImage( $term ) ) {
+ if ( $this->startsWithImage( $term ) ) {
// Deletes prefixes
$bareterm = substr( $term, strpos( $term, ':' ) + 1 );
}
@@ -1081,7 +1113,7 @@ class SpecialSearch extends SpecialPage {
*/
protected function makeSearchLink( $term, $namespaces, $label, $tooltip, $params = array() ) {
$opt = $params;
- foreach( $namespaces as $n ) {
+ foreach ( $namespaces as $n ) {
$opt['ns' . $n] = 1;
}
$opt['redirs'] = $this->searchRedirects;
@@ -1098,7 +1130,8 @@ class SpecialSearch extends SpecialPage {
'a',
array(
'href' => $this->getTitle()->getLocalURL( $stParams ),
- 'title' => $tooltip),
+ 'title' => $tooltip
+ ),
$label
);
}
@@ -1113,7 +1146,7 @@ class SpecialSearch extends SpecialPage {
global $wgContLang;
$p = explode( ':', $term );
- if( count( $p ) > 1 ) {
+ if ( count( $p ) > 1 ) {
return $wgContLang->getNsIndex( $p[0] ) == NS_FILE;
}
return false;
@@ -1130,7 +1163,7 @@ class SpecialSearch extends SpecialPage {
$allkeyword = $this->msg( 'searchall' )->inContentLanguage()->text();
$p = explode( ':', $term );
- if( count( $p ) > 1 ) {
+ if ( count( $p ) > 1 ) {
return $p[0] == $allkeyword;
}
return false;
@@ -1143,7 +1176,8 @@ class SpecialSearch extends SpecialPage {
*/
public function getSearchEngine() {
if ( $this->searchEngine === null ) {
- $this->searchEngine = SearchEngine::create();
+ $this->searchEngine = $this->searchEngineType ?
+ SearchEngine::create( $this->searchEngineType ) : SearchEngine::create();
}
return $this->searchEngine;
}
@@ -1162,6 +1196,6 @@ class SpecialSearch extends SpecialPage {
}
protected function getGroupName() {
- return 'redirects';
+ return 'pages';
}
}
diff --git a/includes/specials/SpecialShortpages.php b/includes/specials/SpecialShortpages.php
index 1be7fbed..9b50875a 100644
--- a/includes/specials/SpecialShortpages.php
+++ b/includes/specials/SpecialShortpages.php
@@ -38,15 +38,15 @@ class ShortPagesPage extends QueryPage {
}
function getQueryInfo() {
- return array (
- 'tables' => array ( 'page' ),
- 'fields' => array ( 'namespace' => 'page_namespace',
+ return array(
+ 'tables' => array( 'page' ),
+ 'fields' => array( 'namespace' => 'page_namespace',
'title' => 'page_title',
'value' => 'page_len' ),
- 'conds' => array ( 'page_namespace' =>
+ 'conds' => array( 'page_namespace' =>
MWNamespace::getContentNamespaces(),
'page_is_redirect' => 0 ),
- 'options' => array ( 'USE INDEX' => 'page_redirect_namespace_len' )
+ 'options' => array( 'USE INDEX' => 'page_redirect_namespace_len' )
);
}
@@ -56,8 +56,7 @@ class ShortPagesPage extends QueryPage {
/**
* @param $db DatabaseBase
- * @param $res
- * @return void
+ * @param ResultWrapper $res
*/
function preprocessResults( $db, $res ) {
# There's no point doing a batch check if we aren't caching results;
@@ -79,6 +78,11 @@ class ShortPagesPage extends QueryPage {
return false;
}
+ /**
+ * @param Skin $skin
+ * @param object $result Result row
+ * @return string
+ */
function formatResult( $skin, $result ) {
$dm = $this->getLanguage()->getDirMark();
diff --git a/includes/specials/SpecialSpecialpages.php b/includes/specials/SpecialSpecialpages.php
index 57fffb84..47c89d0d 100644
--- a/includes/specials/SpecialSpecialpages.php
+++ b/includes/specials/SpecialSpecialpages.php
@@ -53,17 +53,18 @@ class SpecialSpecialpages extends UnlistedSpecialPage {
$pages = SpecialPageFactory::getUsablePages( $this->getUser() );
- if( !count( $pages ) ) {
+ if ( !count( $pages ) ) {
# Yeah, that was pointless. Thanks for coming.
return false;
}
/** Put them into a sortable array */
$groups = array();
+ /** @var SpecialPage $page */
foreach ( $pages as $page ) {
if ( $page->isListed() ) {
$group = $page->getFinalGroupName();
- if( !isset( $groups[$group] ) ) {
+ if ( !isset( $groups[$group] ) ) {
$groups[$group] = array();
}
$groups[$group][$page->getDescription()] = array(
@@ -76,13 +77,13 @@ class SpecialSpecialpages extends UnlistedSpecialPage {
/** Sort */
if ( $wgSortSpecialPages ) {
- foreach( $groups as $group => $sortedPages ) {
+ foreach ( $groups as $group => $sortedPages ) {
ksort( $groups[$group] );
}
}
/** Always move "other" to end */
- if( array_key_exists( 'other', $groups ) ) {
+ if ( array_key_exists( 'other', $groups ) ) {
$other = $groups['other'];
unset( $groups['other'] );
$groups['other'] = $other;
@@ -104,12 +105,12 @@ class SpecialSpecialpages extends UnlistedSpecialPage {
$out->wrapWikiMsg( "<h2 class=\"mw-specialpagesgroup\" id=\"mw-specialpagesgroup-$group\">$1</h2>\n", "specialpages-group-$group" );
$out->addHTML(
- Html::openElement( 'table', array( 'style' => 'width:100%;', 'class' => 'mw-specialpages-table' ) ) ."\n" .
+ Html::openElement( 'table', array( 'style' => 'width:100%;', 'class' => 'mw-specialpages-table' ) ) . "\n" .
Html::openElement( 'tr' ) . "\n" .
Html::openElement( 'td', array( 'style' => 'width:30%;vertical-align:top' ) ) . "\n" .
Html::openElement( 'ul' ) . "\n"
);
- foreach( $sortedPages as $desc => $specialpage ) {
+ foreach ( $sortedPages as $desc => $specialpage ) {
list( $title, $restricted, $cached ) = $specialpage;
$pageClasses = array();
@@ -117,7 +118,7 @@ class SpecialSpecialpages extends UnlistedSpecialPage {
$includesCachedPages = true;
$pageClasses[] = 'mw-specialpagecached';
}
- if( $restricted ) {
+ if ( $restricted ) {
$includesRestrictedPages = true;
$pageClasses[] = 'mw-specialpagerestricted';
}
@@ -127,7 +128,7 @@ class SpecialSpecialpages extends UnlistedSpecialPage {
# Split up the larger groups
$count++;
- if( $total > 3 && $count == $middle ) {
+ if ( $total > 3 && $count == $middle ) {
$out->addHTML(
Html::closeElement( 'ul' ) . Html::closeElement( 'td' ) .
Html::element( 'td', array( 'style' => 'width:10%' ), '' ) .
diff --git a/includes/specials/SpecialStatistics.php b/includes/specials/SpecialStatistics.php
index ee768263..f26d1a60 100644
--- a/includes/specials/SpecialStatistics.php
+++ b/includes/specials/SpecialStatistics.php
@@ -53,18 +53,18 @@ class SpecialStatistics extends SpecialPage {
# Staticic - views
$viewsStats = '';
- if( !$wgDisableCounters ) {
+ if ( !$wgDisableCounters ) {
$viewsStats = $this->getViewsStats();
}
# Set active user count
- if( !$wgMiserMode ) {
+ 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
+ $wgMemc->set( $key, '1', 24 * 3600 ); // don't update for 1 day
}
}
@@ -84,13 +84,13 @@ class SpecialStatistics extends SpecialPage {
$text .= $viewsStats;
# Statistic - popular pages
- if( !$wgDisableCounters && !$wgMiserMode ) {
+ if ( !$wgDisableCounters && !$wgMiserMode ) {
$text .= $this->getMostViewedPages();
}
# Statistic - other
$extraStats = array();
- if( wfRunHooks( 'SpecialStatsAddExtra', array( &$extraStats ) ) ) {
+ if ( wfRunHooks( 'SpecialStatsAddExtra', array( &$extraStats ) ) ) {
$text .= $this->getOtherStats( $extraStats );
}
@@ -111,15 +111,15 @@ class SpecialStatistics extends SpecialPage {
* @param $number Float: a statistical number
* @param $trExtraParams Array: params to table row, see Html::elememt
* @param $descMsg String: message key
- * @param $descMsgParam Array: message params
+ * @param array|string $descMsgParam Message parameters
* @return string table row in HTML format
*/
private function formatRow( $text, $number, $trExtraParams = array(), $descMsg = '', $descMsgParam = '' ) {
- if( $descMsg ) {
+ if ( $descMsg ) {
$msg = $this->msg( $descMsg, $descMsgParam );
if ( $msg->exists() ) {
$descriptionText = $this->msg( 'parentheses' )->rawParams( $msg->parse() )->escaped();
- $text .= "<br />" . Xml::element( 'small', array( 'class' => 'mw-statistic-desc'),
+ $text .= "<br />" . Xml::element( 'small', array( 'class' => 'mw-statistic-desc' ),
" $descriptionText" );
}
}
@@ -185,7 +185,7 @@ class SpecialStatistics extends SpecialPage {
private function getGroupStats() {
global $wgGroupPermissions, $wgImplicitGroups;
$text = '';
- foreach( $wgGroupPermissions as $group => $permissions ) {
+ foreach ( $wgGroupPermissions as $group => $permissions ) {
# Skip generic * and implicit groups
if ( in_array( $group, $wgImplicitGroups ) || $group == '*' ) {
continue;
@@ -217,7 +217,7 @@ class SpecialStatistics extends SpecialPage {
# Add a class when a usergroup contains no members to allow hiding these rows
$classZero = '';
$countUsers = SiteStats::numberingroup( $groupname );
- if( $countUsers == 0 ) {
+ if ( $countUsers == 0 ) {
$classZero = ' statistics-group-zero';
}
$text .= $this->formatRow( $grouppage . ' ' . $grouplink,
@@ -233,11 +233,11 @@ class SpecialStatistics extends SpecialPage {
Xml::closeElement( 'tr' ) .
$this->formatRow( $this->msg( 'statistics-views-total' )->parse(),
$this->getLanguage()->formatNum( $this->views ),
- array ( 'class' => 'mw-statistics-views-total' ), 'statistics-views-total-desc' ) .
+ array( 'class' => 'mw-statistics-views-total' ), 'statistics-views-total-desc' ) .
$this->formatRow( $this->msg( 'statistics-views-peredit' )->parse(),
$this->getLanguage()->formatNum( sprintf( '%.2f', $this->edits ?
$this->views / $this->edits : 0 ) ),
- array ( 'class' => 'mw-statistics-views-peredit' ) );
+ array( 'class' => 'mw-statistics-views-peredit' ) );
}
private function getMostViewedPages() {
@@ -260,13 +260,13 @@ class SpecialStatistics extends SpecialPage {
'LIMIT' => 10,
)
);
- if( $res->numRows() > 0 ) {
+ if ( $res->numRows() > 0 ) {
$text .= Xml::openElement( 'tr' );
$text .= Xml::tags( 'th', array( 'colspan' => '2' ), $this->msg( 'statistics-mostpopular' )->parse() );
$text .= Xml::closeElement( 'tr' );
foreach ( $res as $row ) {
$title = Title::makeTitleSafe( $row->page_namespace, $row->page_title );
- if( $title instanceof Title ) {
+ if ( $title instanceof Title ) {
$text .= $this->formatRow( Linker::link( $title ),
$this->getLanguage()->formatNum( $row->page_counter ) );
@@ -287,8 +287,7 @@ class SpecialStatistics extends SpecialPage {
private function getOtherStats( array $stats ) {
$return = '';
- foreach( $stats as $header => $items ) {
-
+ foreach ( $stats as $header => $items ) {
// Identify the structure used
if ( is_array( $items ) ) {
@@ -298,11 +297,11 @@ class SpecialStatistics extends SpecialPage {
}
// Collect all items that belong to the same header
- foreach( $items as $key => $value ) {
+ 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' ) );
+ $return .= $this->formatRow( $name, $this->getLanguage()->formatNum( $number ), array( 'class' => 'mw-statistics-hook', 'id' => 'mw-' . $key ) );
}
} else {
// Create the legacy header only once
diff --git a/includes/specials/SpecialTags.php b/includes/specials/SpecialTags.php
index 6d161031..077e7cbc 100644
--- a/includes/specials/SpecialTags.php
+++ b/includes/specials/SpecialTags.php
@@ -27,6 +27,10 @@
* @ingroup SpecialPage
*/
class SpecialTags extends SpecialPage {
+ /**
+ * @var array List of defined tags
+ */
+ public $definedTags;
function __construct() {
parent::__construct( 'Tags' );
@@ -44,51 +48,66 @@ class SpecialTags extends SpecialPage {
$html = Xml::tags( 'tr', null, Xml::tags( 'th', null, $this->msg( 'tags-tag' )->parse() ) .
Xml::tags( 'th', null, $this->msg( 'tags-display-header' )->parse() ) .
Xml::tags( 'th', null, $this->msg( 'tags-description-header' )->parse() ) .
+ Xml::tags( 'th', null, $this->msg( 'tags-active-header' )->parse() ) .
Xml::tags( 'th', null, $this->msg( 'tags-hitcount-header' )->parse() )
);
- $dbr = wfGetDB( DB_SLAVE );
- $res = $dbr->select( 'change_tag', array( 'ct_tag', 'hitcount' => 'count(*)' ),
- array(), __METHOD__, array( 'GROUP BY' => 'ct_tag', 'ORDER BY' => 'hitcount DESC' ) );
- foreach ( $res as $row ) {
- $html .= $this->doTagRow( $row->ct_tag, $row->hitcount );
- }
+ // Used in #doTagRow()
+ $this->definedTags = array_fill_keys( ChangeTags::listDefinedTags(), true );
- foreach( ChangeTags::listDefinedTags() as $tag ) {
- $html .= $this->doTagRow( $tag, 0 );
+ foreach ( ChangeTags::tagUsageStatistics() as $tag => $hitcount ) {
+ $html .= $this->doTagRow( $tag, $hitcount );
}
- $out->addHTML( Xml::tags( 'table', array( 'class' => 'wikitable mw-tags-table' ), $html ) );
+ $out->addHTML( Xml::tags(
+ 'table',
+ array( 'class' => 'wikitable sortable mw-tags-table' ),
+ $html
+ ) );
}
function doTagRow( $tag, $hitcount ) {
- static $doneTags = array();
-
- if ( in_array( $tag, $doneTags ) ) {
- return '';
- }
-
+ $user = $this->getUser();
$newRow = '';
$newRow .= Xml::tags( 'td', null, Xml::element( 'code', null, $tag ) );
$disp = ChangeTags::tagDescription( $tag );
- $disp .= ' ';
- $editLink = Linker::link( Title::makeTitle( NS_MEDIAWIKI, "Tag-$tag" ), $this->msg( 'tags-edit' )->escaped() );
- $disp .= $this->msg( 'parentheses' )->rawParams( $editLink )->escaped();
+ if ( $user->isAllowed( 'editinterface' ) ) {
+ $disp .= ' ';
+ $editLink = Linker::link(
+ Title::makeTitle( NS_MEDIAWIKI, "Tag-$tag" ),
+ $this->msg( 'tags-edit' )->escaped()
+ );
+ $disp .= $this->msg( 'parentheses' )->rawParams( $editLink )->escaped();
+ }
$newRow .= Xml::tags( 'td', null, $disp );
$msg = $this->msg( "tag-$tag-description" );
$desc = !$msg->exists() ? '' : $msg->parse();
- $desc .= ' ';
- $editDescLink = Linker::link( Title::makeTitle( NS_MEDIAWIKI, "Tag-$tag-description" ), $this->msg( 'tags-edit' )->escaped() );
- $desc .= $this->msg( 'parentheses' )->rawParams( $editDescLink )->escaped();
+ if ( $user->isAllowed( 'editinterface' ) ) {
+ $desc .= ' ';
+ $editDescLink = Linker::link(
+ Title::makeTitle( NS_MEDIAWIKI, "Tag-$tag-description" ),
+ $this->msg( 'tags-edit' )->escaped()
+ );
+ $desc .= $this->msg( 'parentheses' )->rawParams( $editDescLink )->escaped();
+ }
$newRow .= Xml::tags( 'td', null, $desc );
- $hitcount = $this->msg( 'tags-hitcount' )->numParams( $hitcount )->escaped();
- $hitcount = Linker::link( SpecialPage::getTitleFor( 'Recentchanges' ), $hitcount, array(), array( 'tagfilter' => $tag ) );
- $newRow .= Xml::tags( 'td', null, $hitcount );
+ $active = isset( $this->definedTags[$tag] ) ? 'tags-active-yes' : 'tags-active-no';
+ $active = $this->msg( $active )->escaped();
+ $newRow .= Xml::tags( 'td', null, $active );
+
+ $hitcountLabel = $this->msg( 'tags-hitcount' )->numParams( $hitcount )->escaped();
+ $hitcountLink = Linker::link(
+ SpecialPage::getTitleFor( 'Recentchanges' ),
+ $hitcountLabel,
+ array(),
+ array( 'tagfilter' => $tag )
+ );
- $doneTags[] = $tag;
+ // add raw $hitcount for sorting, because tags-hitcount contains numbers and letters
+ $newRow .= Xml::tags( 'td', array( 'data-sort-value' => $hitcount ), $hitcountLink );
return Xml::tags( 'tr', null, $newRow ) . "\n";
}
diff --git a/includes/specials/SpecialUnblock.php b/includes/specials/SpecialUnblock.php
index c4a53cf0..ca93b6d1 100644
--- a/includes/specials/SpecialUnblock.php
+++ b/includes/specials/SpecialUnblock.php
@@ -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 ) );
@@ -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(
@@ -138,6 +138,8 @@ class SpecialUnblock extends SpecialPage {
/**
* Submit callback for an HTMLForm object
+ * @param array $data
+ * @param HTMLForm $form
* @return Array( Array(message key, parameters)
*/
public static function processUIUnblock( array $data, HTMLForm $form ) {
@@ -157,7 +159,7 @@ class SpecialUnblock extends SpecialPage {
$target = $data['Target'];
$block = Block::newFromTarget( $data['Target'] );
- if( !$block instanceof Block ) {
+ if ( !$block instanceof Block ) {
return array( array( 'ipb_cant_unblock', $target ) );
}
@@ -172,14 +174,14 @@ class SpecialUnblock extends SpecialPage {
# If the specified IP is a single address, and the block is a range block, don't
# unblock the whole range.
list( $target, $type ) = SpecialBlock::getTargetAndType( $target );
- if( $block->getType() == Block::TYPE_RANGE && $type == Block::TYPE_IP ) {
+ if ( $block->getType() == Block::TYPE_RANGE && $type == Block::TYPE_IP ) {
$range = $block->getTarget();
return array( array( 'ipb_blocked_as_range', $target, $range ) );
}
# If the name was hidden and the blocking user cannot hide
# names, then don't allow any block removals...
- if( !$performer->isAllowed( 'hideuser' ) && $block->mHideName ) {
+ if ( !$performer->isAllowed( 'hideuser' ) && $block->mHideName ) {
return array( 'unblock-hideuser' );
}
@@ -189,7 +191,7 @@ class SpecialUnblock extends SpecialPage {
}
# Unset _deleted fields as needed
- if( $block->mHideName ) {
+ if ( $block->mHideName ) {
# Something is deeply FUBAR if this is not a User object, but who knows?
$id = $block->getTarget() instanceof User
? $block->getTarget()->getID()
@@ -209,7 +211,7 @@ class SpecialUnblock extends SpecialPage {
# Make log entry
$log = new LogPage( 'block' );
- $log->addEntry( 'unblock', $page, $data['Reason'] );
+ $log->addEntry( 'unblock', $page, $data['Reason'], array(), $performer );
return true;
}
diff --git a/includes/specials/SpecialUncategorizedcategories.php b/includes/specials/SpecialUncategorizedcategories.php
index 54b20dde..1cc40a9e 100644
--- a/includes/specials/SpecialUncategorizedcategories.php
+++ b/includes/specials/SpecialUncategorizedcategories.php
@@ -34,11 +34,11 @@ class UncategorizedCategoriesPage extends UncategorizedPagesPage {
/**
* Formats the result
- * @param $skin The current skin
- * @param $result The query result
+ * @param Skin $skin The current skin
+ * @param object $result The query result
* @return string The category link
*/
- function formatResult ( $skin, $result ) {
+ function formatResult( $skin, $result ) {
$title = Title::makeTitle( NS_CATEGORY, $result->title );
$text = $title->getText();
diff --git a/includes/specials/SpecialUncategorizedimages.php b/includes/specials/SpecialUncategorizedimages.php
index 53aa3f34..3bfcedec 100644
--- a/includes/specials/SpecialUncategorizedimages.php
+++ b/includes/specials/SpecialUncategorizedimages.php
@@ -47,7 +47,7 @@ class UncategorizedImagesPage extends ImageQueryPage {
}
function getQueryInfo() {
- return array (
+ return array(
'tables' => array( 'page', 'categorylinks' ),
'fields' => array( 'namespace' => 'page_namespace',
'title' => 'page_title',
diff --git a/includes/specials/SpecialUncategorizedpages.php b/includes/specials/SpecialUncategorizedpages.php
index b518e6fb..8bc9e489 100644
--- a/includes/specials/SpecialUncategorizedpages.php
+++ b/includes/specials/SpecialUncategorizedpages.php
@@ -47,17 +47,17 @@ class UncategorizedPagesPage extends PageQueryPage {
}
function getQueryInfo() {
- return array (
- 'tables' => array ( 'page', 'categorylinks' ),
- 'fields' => array ( 'namespace' => 'page_namespace',
+ return array(
+ 'tables' => array( 'page', 'categorylinks' ),
+ 'fields' => array( 'namespace' => 'page_namespace',
'title' => 'page_title',
'value' => 'page_title' ),
// default for page_namespace is all content namespaces (if requestedNamespace is false)
// otherwise, page_namespace is requestedNamespace
- 'conds' => array ( 'cl_from IS NULL',
+ 'conds' => array( 'cl_from IS NULL',
'page_namespace' => ( $this->requestedNamespace !== false ? $this->requestedNamespace : MWNamespace::getContentNamespaces() ),
'page_is_redirect' => 0 ),
- 'join_conds' => array ( 'categorylinks' => array (
+ 'join_conds' => array( 'categorylinks' => array(
'LEFT JOIN', 'cl_from = page_id' ) )
);
}
@@ -65,8 +65,9 @@ class UncategorizedPagesPage extends PageQueryPage {
function getOrderFields() {
// For some crazy reason ordering by a constant
// causes a filesort
- if( $this->requestedNamespace === false && count( MWNamespace::getContentNamespaces() ) > 1 )
+ if ( $this->requestedNamespace === false && count( MWNamespace::getContentNamespaces() ) > 1 ) {
return array( 'page_namespace', 'page_title' );
+ }
return array( 'page_title' );
}
diff --git a/includes/specials/SpecialUndelete.php b/includes/specials/SpecialUndelete.php
index e0363481..d4aed113 100644
--- a/includes/specials/SpecialUndelete.php
+++ b/includes/specials/SpecialUndelete.php
@@ -27,7 +27,6 @@
* @ingroup SpecialPage
*/
class PageArchive {
-
/**
* @var Title
*/
@@ -44,7 +43,7 @@ class PageArchive {
protected $revisionStatus;
function __construct( $title ) {
- if( is_null( $title ) ) {
+ if ( is_null( $title ) ) {
throw new MWException( __METHOD__ . ' given a null title.' );
}
$this->title = $title;
@@ -67,14 +66,14 @@ class PageArchive {
* given title prefix.
* Returns result wrapper with (ar_namespace, ar_title, count) fields.
*
- * @param string $prefix title prefix
+ * @param string $prefix Title prefix
* @return ResultWrapper
*/
public static function listPagesByPrefix( $prefix ) {
$dbr = wfGetDB( DB_SLAVE );
$title = Title::newFromText( $prefix );
- if( $title ) {
+ if ( $title ) {
$ns = $title->getNamespace();
$prefix = $title->getDBkey();
} else {
@@ -82,36 +81,36 @@ class PageArchive {
// @todo handle bare namespace names cleanly?
$ns = 0;
}
+
$conds = array(
'ar_namespace' => $ns,
'ar_title' . $dbr->buildLike( $prefix, $dbr->anyString() ),
);
+
return self::listPages( $dbr, $conds );
}
/**
- * @param $dbr DatabaseBase
- * @param $condition
+ * @param DatabaseBase $dbr
+ * @param string|array $condition
* @return bool|ResultWrapper
*/
protected static function listPages( $dbr, $condition ) {
- return $dbr->resultObject(
- $dbr->select(
- array( 'archive' ),
- array(
- 'ar_namespace',
- 'ar_title',
- 'count' => 'COUNT(*)'
- ),
- $condition,
- __METHOD__,
- array(
- 'GROUP BY' => array( 'ar_namespace', 'ar_title' ),
- 'ORDER BY' => array( 'ar_namespace', 'ar_title' ),
- 'LIMIT' => 100,
- )
+ return $dbr->resultObject( $dbr->select(
+ array( 'archive' ),
+ array(
+ 'ar_namespace',
+ 'ar_title',
+ 'count' => 'COUNT(*)'
+ ),
+ $condition,
+ __METHOD__,
+ array(
+ 'GROUP BY' => array( 'ar_namespace', 'ar_title' ),
+ 'ORDER BY' => array( 'ar_namespace', 'ar_title' ),
+ 'LIMIT' => 100,
)
- );
+ ) );
}
/**
@@ -141,8 +140,8 @@ class PageArchive {
'ar_title' => $this->title->getDBkey() ),
__METHOD__,
array( 'ORDER BY' => 'ar_timestamp DESC' ) );
- $ret = $dbr->resultObject( $res );
- return $ret;
+
+ return $dbr->resultObject( $res );
}
/**
@@ -154,26 +153,28 @@ class PageArchive {
* @todo Does this belong in Image for fuller encapsulation?
*/
function listFiles() {
- if( $this->title->getNamespace() == NS_FILE ) {
- $dbr = wfGetDB( DB_SLAVE );
- $res = $dbr->select(
- 'filearchive',
- ArchivedFile::selectFields(),
- array( 'fa_name' => $this->title->getDBkey() ),
- __METHOD__,
- array( 'ORDER BY' => 'fa_timestamp DESC' ) );
- $ret = $dbr->resultObject( $res );
- return $ret;
+ if ( $this->title->getNamespace() != NS_FILE ) {
+ return null;
}
- return null;
+
+ $dbr = wfGetDB( DB_SLAVE );
+ $res = $dbr->select(
+ 'filearchive',
+ ArchivedFile::selectFields(),
+ array( 'fa_name' => $this->title->getDBkey() ),
+ __METHOD__,
+ array( 'ORDER BY' => 'fa_timestamp DESC' )
+ );
+
+ return $dbr->resultObject( $res );
}
/**
* Return a Revision object containing data for the deleted revision.
* Note that the result *may* or *may not* have a null page ID.
*
- * @param $timestamp String
- * @return Revision
+ * @param string $timestamp
+ * @return Revision|null
*/
function getRevision( $timestamp ) {
global $wgContentHandlerUseDB;
@@ -203,14 +204,15 @@ class PageArchive {
$row = $dbr->selectRow( 'archive',
$fields,
array( 'ar_namespace' => $this->title->getNamespace(),
- 'ar_title' => $this->title->getDBkey(),
- 'ar_timestamp' => $dbr->timestamp( $timestamp ) ),
+ 'ar_title' => $this->title->getDBkey(),
+ 'ar_timestamp' => $dbr->timestamp( $timestamp ) ),
__METHOD__ );
- if( $row ) {
+
+ if ( $row ) {
return Revision::newFromArchiveRow( $row, array( 'title' => $this->title ) );
- } else {
- return null;
}
+
+ return null;
}
/**
@@ -220,8 +222,8 @@ class PageArchive {
* May produce unexpected results in case of history merges or other
* unusual time issues.
*
- * @param $timestamp String
- * @return Revision or null
+ * @param string $timestamp
+ * @return Revision|null Null when there is no previous revision
*/
function getPreviousRevision( $timestamp ) {
$dbr = wfGetDB( DB_SLAVE );
@@ -246,7 +248,7 @@ class PageArchive {
'page_title' => $this->title->getDBkey(),
'page_id = rev_page',
'rev_timestamp < ' .
- $dbr->addQuotes( $dbr->timestamp( $timestamp ) ) ),
+ $dbr->addQuotes( $dbr->timestamp( $timestamp ) ) ),
__METHOD__,
array(
'ORDER BY' => 'rev_timestamp DESC',
@@ -254,38 +256,39 @@ class PageArchive {
$prevLive = $row ? wfTimestamp( TS_MW, $row->rev_timestamp ) : false;
$prevLiveId = $row ? intval( $row->rev_id ) : null;
- if( $prevLive && $prevLive > $prevDeleted ) {
+ if ( $prevLive && $prevLive > $prevDeleted ) {
// Most prior revision was live
return Revision::newFromId( $prevLiveId );
- } elseif( $prevDeleted ) {
+ } elseif ( $prevDeleted ) {
// Most prior revision was deleted
return $this->getRevision( $prevDeleted );
- } else {
- // No prior revision on this page.
- return null;
}
+
+ // No prior revision on this page.
+ return null;
}
/**
* Get the text from an archive row containing ar_text, ar_flags and ar_text_id
*
- * @param $row Object: database row
- * @return Revision
+ * @param object $row Database row
+ * @return string
*/
function getTextFromRow( $row ) {
- if( is_null( $row->ar_text_id ) ) {
+ if ( is_null( $row->ar_text_id ) ) {
// An old row from MediaWiki 1.4 or previous.
// Text is embedded in this row in classic compression format.
return Revision::getRevisionText( $row, 'ar_' );
- } else {
- // New-style: keyed to the text storage backend.
- $dbr = wfGetDB( DB_SLAVE );
- $text = $dbr->selectRow( 'text',
- array( 'old_text', 'old_flags' ),
- array( 'old_id' => $row->ar_text_id ),
- __METHOD__ );
- return Revision::getRevisionText( $text );
}
+
+ // New-style: keyed to the text storage backend.
+ $dbr = wfGetDB( DB_SLAVE );
+ $text = $dbr->selectRow( 'text',
+ array( 'old_text', 'old_flags' ),
+ array( 'old_id' => $row->ar_text_id ),
+ __METHOD__ );
+
+ return Revision::getRevisionText( $text );
}
/**
@@ -294,7 +297,7 @@ class PageArchive {
*
* If there are no archived revisions for the page, returns NULL.
*
- * @return String
+ * @return string|null
*/
function getLastRevisionText() {
$dbr = wfGetDB( DB_SLAVE );
@@ -304,17 +307,18 @@ class PageArchive {
'ar_title' => $this->title->getDBkey() ),
__METHOD__,
array( 'ORDER BY' => 'ar_timestamp DESC' ) );
- if( $row ) {
+
+ if ( $row ) {
return $this->getTextFromRow( $row );
- } else {
- return null;
}
+
+ return null;
}
/**
* Quick check if any archived revisions are present for the page.
*
- * @return Boolean
+ * @return boolean
*/
function isDeleted() {
$dbr = wfGetDB( DB_SLAVE );
@@ -323,6 +327,7 @@ class PageArchive {
'ar_title' => $this->title->getDBkey() ),
__METHOD__
);
+
return ( $n > 0 );
}
@@ -331,11 +336,11 @@ class PageArchive {
* Once restored, the items will be removed from the archive tables.
* The deletion log will be updated with an undeletion notice.
*
- * @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
- * @param $user User doing the action, or null to use $wgUser
+ * @param array $timestamps Pass an empty array to restore all revisions, otherwise list the ones to undelete.
+ * @param string $comment
+ * @param array $fileVersions
+ * @param bool $unsuppress
+ * @param User $user User performing the action, or null to use $wgUser
*
* @return array(number of file revisions restored, number of image revisions restored, log message)
* on success, false on failure
@@ -348,7 +353,7 @@ class PageArchive {
$restoreText = $restoreAll || !empty( $timestamps );
$restoreFiles = $restoreAll || !empty( $fileVersions );
- if( $restoreFiles && $this->title->getNamespace() == NS_FILE ) {
+ if ( $restoreFiles && $this->title->getNamespace() == NS_FILE ) {
$img = wfLocalFile( $this->title );
$this->fileStatus = $img->restore( $fileVersions, $unsuppress );
if ( !$this->fileStatus->isOK() ) {
@@ -359,9 +364,9 @@ class PageArchive {
$filesRestored = 0;
}
- if( $restoreText ) {
+ if ( $restoreText ) {
$this->revisionStatus = $this->undeleteRevisions( $timestamps, $unsuppress, $comment );
- if( !$this->revisionStatus->isOK() ) {
+ if ( !$this->revisionStatus->isOK() ) {
return false;
}
@@ -372,13 +377,13 @@ class PageArchive {
// Touch the log!
- if( $textRestored && $filesRestored ) {
+ if ( $textRestored && $filesRestored ) {
$reason = wfMessage( 'undeletedrevisions-files' )
->numParams( $textRestored, $filesRestored )->inContentLanguage()->text();
- } elseif( $textRestored ) {
+ } elseif ( $textRestored ) {
$reason = wfMessage( 'undeletedrevisions' )->numParams( $textRestored )
->inContentLanguage()->text();
- } elseif( $filesRestored ) {
+ } elseif ( $filesRestored ) {
$reason = wfMessage( 'undeletedfiles' )->numParams( $filesRestored )
->inContentLanguage()->text();
} else {
@@ -386,7 +391,7 @@ class PageArchive {
return false;
}
- if( trim( $comment ) != '' ) {
+ if ( trim( $comment ) != '' ) {
$reason .= wfMessage( 'colon-separator' )->inContentLanguage()->text() . $comment;
}
@@ -413,12 +418,11 @@ 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 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
- *
- * @param $comment String
+ * @param array $timestamps Pass an empty array to restore all revisions, otherwise list the ones to undelete.
+ * @param bool $unsuppress Remove all ar_deleted/fa_deleted restrictions of seletected revs
+ * @param string $comment
* @throws ReadOnlyError
- * @return Status, containing the number of revisions restored on success
+ * @return Status Object containing the number of revisions restored on success
*/
private function undeleteRevisions( $timestamps, $unsuppress = false, $comment = '' ) {
global $wgContentHandlerUseDB;
@@ -426,8 +430,8 @@ class PageArchive {
if ( wfReadOnly() ) {
throw new ReadOnlyError();
}
- $restoreAll = empty( $timestamps );
+ $restoreAll = empty( $timestamps );
$dbw = wfGetDB( DB_MASTER );
# Does this page already exist? We'll have to update it...
@@ -443,7 +447,8 @@ class PageArchive {
__METHOD__,
array( 'FOR UPDATE' ) // lock page
);
- if( $page ) {
+
+ if ( $page ) {
$makepage = false;
# Page already exists. Import the history, and if necessary
# we'll update the latest revision field in the record.
@@ -455,7 +460,7 @@ class PageArchive {
array( 'rev_id' => $previousRevId ),
__METHOD__ );
- if( $previousTimestamp === false ) {
+ if ( $previousTimestamp === false ) {
wfDebug( __METHOD__ . ": existing page refers to a page_latest that does not exist\n" );
$status = Status::newGood( 0 );
@@ -470,7 +475,7 @@ class PageArchive {
$previousTimestamp = 0;
}
- if( $restoreAll ) {
+ if ( $restoreAll ) {
$oldones = '1 = 1'; # All revisions...
} else {
$oldts = implode( ',',
@@ -509,14 +514,15 @@ class PageArchive {
$fields,
/* WHERE */ array(
'ar_namespace' => $this->title->getNamespace(),
- 'ar_title' => $this->title->getDBkey(),
+ 'ar_title' => $this->title->getDBkey(),
$oldones ),
__METHOD__,
/* options */ array( 'ORDER BY' => 'ar_timestamp' )
);
$ret = $dbw->resultObject( $result );
$rev_count = $dbw->numRows( $result );
- if( !$rev_count ) {
+
+ if ( !$rev_count ) {
wfDebug( __METHOD__ . ": no revisions to restore\n" );
$status = Status::newGood( 0 );
@@ -544,9 +550,9 @@ class PageArchive {
return $status;
}
- if( $makepage ) {
+ if ( $makepage ) {
// Check the state of the newest to-be version...
- if( !$unsuppress && ( $row->ar_deleted & Revision::DELETED_TEXT ) ) {
+ if ( !$unsuppress && ( $row->ar_deleted & Revision::DELETED_TEXT ) ) {
return Status::newFatal( "undeleterevdel" );
}
// Safe to insert now...
@@ -554,9 +560,9 @@ class PageArchive {
$pageId = $newid;
} else {
// Check if a deleted revision will become the current revision...
- if( $row->ar_timestamp > $previousTimestamp ) {
+ if ( $row->ar_timestamp > $previousTimestamp ) {
// Check the state of the newest to-be version...
- if( !$unsuppress && ( $row->ar_deleted & Revision::DELETED_TEXT ) ) {
+ if ( !$unsuppress && ( $row->ar_deleted & Revision::DELETED_TEXT ) ) {
return Status::newFatal( "undeleterevdel" );
}
}
@@ -570,10 +576,10 @@ class PageArchive {
foreach ( $ret as $row ) {
// Check for key dupes due to shitty archive integrity.
- if( $row->ar_rev_id ) {
+ if ( $row->ar_rev_id ) {
$exists = $dbw->selectField( 'revision', '1',
array( 'rev_id' => $row->ar_rev_id ), __METHOD__ );
- if( $exists ) {
+ if ( $exists ) {
continue; // don't throw DB errors
}
}
@@ -616,7 +622,7 @@ class PageArchive {
wfRunHooks( 'ArticleUndelete', array( &$this->title, $created, $comment ) );
- if( $this->title->getNamespace() == NS_FILE ) {
+ if ( $this->title->getNamespace() == NS_FILE ) {
$update = new HTMLCacheUpdate( $this->title, 'imagelinks' );
$update->doUpdate();
}
@@ -627,12 +633,16 @@ class PageArchive {
/**
* @return Status
*/
- function getFileStatus() { return $this->fileStatus; }
+ function getFileStatus() {
+ return $this->fileStatus;
+ }
/**
* @return Status
*/
- function getRevisionStatus() { return $this->revisionStatus; }
+ function getRevisionStatus() {
+ return $this->revisionStatus;
+ }
}
/**
@@ -664,10 +674,13 @@ class SpecialUndelete extends SpecialPage {
} else {
$this->mTarget = $request->getVal( 'target' );
}
+
$this->mTargetObj = null;
+
if ( $this->mTarget !== null && $this->mTarget !== '' ) {
$this->mTargetObj = Title::newFromURL( $this->mTarget );
}
+
$this->mSearchPrefix = $request->getText( 'prefix' );
$time = $request->getVal( 'timestamp' );
$this->mTimestamp = $time ? wfTimestamp( TS_MW, $time ) : '';
@@ -698,16 +711,16 @@ class SpecialUndelete extends SpecialPage {
$this->mRestore = false;
}
- if( $this->mRestore || $this->mInvert ) {
+ if ( $this->mRestore || $this->mInvert ) {
$timestamps = array();
$this->mFileVersions = array();
- foreach( $request->getValues() as $key => $val ) {
+ foreach ( $request->getValues() as $key => $val ) {
$matches = array();
- if( preg_match( '/^ts(\d{14})$/', $key, $matches ) ) {
+ if ( preg_match( '/^ts(\d{14})$/', $key, $matches ) ) {
array_push( $timestamps, $matches[1] );
}
- if( preg_match( '/^fileid(\d+)$/', $key, $matches ) ) {
+ if ( preg_match( '/^fileid(\d+)$/', $key, $matches ) ) {
$this->mFileVersions[] = intval( $matches[1] );
}
}
@@ -747,13 +760,13 @@ class SpecialUndelete extends SpecialPage {
if ( $this->mTimestamp !== '' ) {
$this->showRevision( $this->mTimestamp );
- } elseif ( $this->mFilename !== null ) {
+ } elseif ( $this->mFilename !== null && $this->mTargetObj->inNamespace( NS_FILE ) ) {
$file = new ArchivedFile( $this->mTargetObj, '', $this->mFilename );
// Check if user is allowed to see this file
if ( !$file->exists() ) {
$out->addWikiMsg( 'filedelete-nofile', $this->mFilename );
} elseif ( !$file->userCan( File::DELETED_FILE, $user ) ) {
- if( $file->isDeleted( File::DELETED_RESTRICTED ) ) {
+ if ( $file->isDeleted( File::DELETED_RESTRICTED ) ) {
throw new PermissionsError( 'suppressrevision' );
} else {
throw new PermissionsError( 'deletedtext' );
@@ -776,22 +789,27 @@ class SpecialUndelete extends SpecialPage {
$out = $this->getOutput();
$out->setPageTitle( $this->msg( 'undelete-search-title' ) );
$out->addHTML(
- Xml::openElement( 'form', array(
- 'method' => 'get',
- 'action' => $wgScript ) ) .
- Xml::fieldset( $this->msg( 'undelete-search-box' )->text() ) .
- Html::hidden( 'title',
- $this->getTitle()->getPrefixedDBkey() ) .
- Xml::inputLabel( $this->msg( 'undelete-search-prefix' )->text(),
- 'prefix', 'prefix', 20,
- $this->mSearchPrefix ) . ' ' .
- Xml::submitButton( $this->msg( 'undelete-search-submit' )->text() ) .
- Xml::closeElement( 'fieldset' ) .
- Xml::closeElement( 'form' )
+ Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ) .
+ Xml::fieldset( $this->msg( 'undelete-search-box' )->text() ) .
+ Html::hidden( 'title', $this->getTitle()->getPrefixedDBkey() ) .
+ Html::rawElement(
+ 'label',
+ array( 'for' => 'prefix' ),
+ $this->msg( 'undelete-search-prefix' )->parse()
+ ) .
+ Xml::input(
+ 'prefix',
+ 20,
+ $this->mSearchPrefix,
+ array( 'id' => 'prefix', 'autofocus' => true )
+ ) . ' ' .
+ Xml::submitButton( $this->msg( 'undelete-search-submit' )->text() ) .
+ Xml::closeElement( 'fieldset' ) .
+ Xml::closeElement( 'form' )
);
# List undeletable articles
- if( $this->mSearchPrefix ) {
+ if ( $this->mSearchPrefix ) {
$result = PageArchive::listPagesByPrefix( $this->mSearchPrefix );
$this->showList( $result );
}
@@ -800,13 +818,13 @@ class SpecialUndelete extends SpecialPage {
/**
* Generic list of deleted pages
*
- * @param $result ResultWrapper
+ * @param ResultWrapper $result
* @return bool
*/
private function showList( $result ) {
$out = $this->getOutput();
- if( $result->numRows() == 0 ) {
+ if ( $result->numRows() == 0 ) {
$out->addWikiMsg( 'undelete-no-results' );
return false;
}
@@ -826,8 +844,15 @@ class SpecialUndelete extends SpecialPage {
);
} else {
// The title is no longer valid, show as text
- $item = Html::element( 'span', array( 'class' => 'mw-invalidtitle' ),
- Linker::getInvalidTitleDescription( $this->getContext(), $row->ar_namespace, $row->ar_title ) );
+ $item = Html::element(
+ 'span',
+ array( 'class' => 'mw-invalidtitle' ),
+ Linker::getInvalidTitleDescription(
+ $this->getContext(),
+ $row->ar_namespace,
+ $row->ar_title
+ )
+ );
}
$revs = $this->msg( 'undeleterevisions' )->numParams( $row->count )->parse();
$out->addHTML( "<li>{$item} ({$revs})</li>\n" );
@@ -839,7 +864,7 @@ class SpecialUndelete extends SpecialPage {
}
private function showRevision( $timestamp ) {
- if( !preg_match( '/[0-9]{14}/', $timestamp ) ) {
+ if ( !preg_match( '/[0-9]{14}/', $timestamp ) ) {
return;
}
@@ -852,31 +877,37 @@ class SpecialUndelete extends SpecialPage {
$out = $this->getOutput();
$user = $this->getUser();
- if( !$rev ) {
+ if ( !$rev ) {
$out->addWikiMsg( 'undeleterevision-missing' );
return;
}
- if( $rev->isDeleted( Revision::DELETED_TEXT ) ) {
- if( !$rev->userCan( Revision::DELETED_TEXT, $user ) ) {
- $out->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", 'rev-deleted-text-permission' );
+ if ( $rev->isDeleted( Revision::DELETED_TEXT ) ) {
+ if ( !$rev->userCan( Revision::DELETED_TEXT, $user ) ) {
+ $out->wrapWikiMsg(
+ "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
+ 'rev-deleted-text-permission'
+ );
return;
- } else {
- $out->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", 'rev-deleted-text-view' );
- $out->addHTML( '<br />' );
- // and we are allowed to see...
}
+
+ $out->wrapWikiMsg(
+ "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
+ 'rev-deleted-text-view'
+ );
+ $out->addHTML( '<br />' );
+ // and we are allowed to see...
}
- if( $this->mDiff ) {
+ if ( $this->mDiff ) {
$previousRev = $archive->getPreviousRevision( $timestamp );
- if( $previousRev ) {
+ if ( $previousRev ) {
$this->showDiff( $previousRev, $rev );
- if( $this->mDiffOnly ) {
+ if ( $this->mDiffOnly ) {
return;
- } else {
- $out->addHTML( '<hr />' );
}
+
+ $out->addHTML( '<hr />' );
} else {
$out->addWikiMsg( 'undelete-nodiff' );
}
@@ -900,7 +931,7 @@ class SpecialUndelete extends SpecialPage {
$isText = ( $content instanceof TextContent );
- if( $this->mPreview || $isText ) {
+ if ( $this->mPreview || $isText ) {
$openDiv = '<div id="mw-undelete-revision" class="mw-warning">';
} else {
$openDiv = '<div id="mw-undelete-revision">';
@@ -922,7 +953,7 @@ class SpecialUndelete extends SpecialPage {
return;
}
- if( $this->mPreview || !$isText ) {
+ if ( $this->mPreview || !$isText ) {
// NOTE: non-text content has no source view, so always use rendered preview
// Hide [edit]s
@@ -935,16 +966,21 @@ class SpecialUndelete extends SpecialPage {
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" );
+ $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() ) );
+ 'value' => $this->msg( 'showpreview' )->text()
+ ) );
} else {
$sourceView = '';
$previewButton = '';
@@ -957,36 +993,37 @@ class SpecialUndelete extends SpecialPage {
$out->addHTML(
$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() ) ) .
- Xml::element( 'input', array(
- 'type' => 'hidden',
- 'name' => 'timestamp',
- 'value' => $timestamp ) ) .
- Xml::element( 'input', array(
- 'type' => 'hidden',
- 'name' => 'wpEditToken',
- 'value' => $user->getEditToken() ) ) .
- $previewButton .
- $diffButton .
- Xml::closeElement( 'form' ) .
- Xml::closeElement( 'div' ) );
+ 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() ) ) .
+ Xml::element( 'input', array(
+ 'type' => 'hidden',
+ 'name' => 'timestamp',
+ 'value' => $timestamp ) ) .
+ Xml::element( 'input', array(
+ 'type' => 'hidden',
+ 'name' => 'wpEditToken',
+ 'value' => $user->getEditToken() ) ) .
+ $previewButton .
+ $diffButton .
+ Xml::closeElement( 'form' ) .
+ Xml::closeElement( 'div' )
+ );
}
/**
* Build a diff display between this and the previous either deleted
* or non-deleted edit.
*
- * @param $previousRev Revision
- * @param $currentRev Revision
- * @return String: HTML
+ * @param Revision $previousRev
+ * @param Revision $currentRev
+ * @return string HTML
*/
function showDiff( $previousRev, $currentRev ) {
$diffContext = clone $this->getContext();
@@ -995,20 +1032,19 @@ class SpecialUndelete extends SpecialPage {
$diffEngine = $currentRev->getContentHandler()->createDifferenceEngine( $diffContext );
$diffEngine->showDiffStyle();
- $this->getOutput()->addHTML(
- "<div>" .
+ $this->getOutput()->addHTML( "<div>" .
"<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' style='width: 50%; text-align: center' class='diff-otitle'>" .
- $this->diffHeader( $previousRev, 'o' ) .
- "</td>\n" .
- "<td colspan='2' style='width: 50%; text-align: center' class='diff-ntitle'>" .
- $this->diffHeader( $currentRev, 'n' ) .
- "</td>\n" .
+ "<td colspan='2' style='width: 50%; text-align: center' class='diff-otitle'>" .
+ $this->diffHeader( $previousRev, 'o' ) .
+ "</td>\n" .
+ "<td colspan='2' style='width: 50%; text-align: center' class='diff-ntitle'>" .
+ $this->diffHeader( $currentRev, 'n' ) .
+ "</td>\n" .
"</tr>" .
$diffEngine->generateContentDiffBody(
$previousRev->getContent( Revision::FOR_THIS_USER, $this->getUser() ),
@@ -1019,13 +1055,13 @@ class SpecialUndelete extends SpecialPage {
}
/**
- * @param $rev Revision
- * @param $prefix
+ * @param Revision $rev
+ * @param string $prefix
* @return string
*/
private function diffHeader( $rev, $prefix ) {
$isDeleted = !( $rev->getId() && $rev->getTitle() );
- if( $isDeleted ) {
+ if ( $isDeleted ) {
/// @todo FIXME: $rev->getTitle() is null for deleted revs...?
$targetPage = $this->getTitle();
$targetQuery = array(
@@ -1037,30 +1073,34 @@ class SpecialUndelete extends SpecialPage {
$targetPage = $rev->getTitle();
$targetQuery = array( 'oldid' => $rev->getId() );
}
+
// Add show/hide deletion links if available
$user = $this->getUser();
$lang = $this->getLanguage();
$rdel = Linker::getRevDeleteLink( $user, $rev, $this->mTargetObj );
- if ( $rdel ) $rdel = " $rdel";
- return
- '<div id="mw-diff-' . $prefix . 'title1"><strong>' .
- Linker::link(
- $targetPage,
- $this->msg(
- 'revisionasof',
- $lang->userTimeAndDate( $rev->getTimestamp(), $user ),
- $lang->userDate( $rev->getTimestamp(), $user ),
- $lang->userTime( $rev->getTimestamp(), $user )
- )->escaped(),
- array(),
- $targetQuery
- ) .
+
+ if ( $rdel ) {
+ $rdel = " $rdel";
+ }
+
+ return '<div id="mw-diff-' . $prefix . 'title1"><strong>' .
+ Linker::link(
+ $targetPage,
+ $this->msg(
+ 'revisionasof',
+ $lang->userTimeAndDate( $rev->getTimestamp(), $user ),
+ $lang->userDate( $rev->getTimestamp(), $user ),
+ $lang->userTime( $rev->getTimestamp(), $user )
+ )->escaped(),
+ array(),
+ $targetQuery
+ ) .
'</strong></div>' .
'<div id="mw-diff-' . $prefix . 'title2">' .
- Linker::revUserTools( $rev ) . '<br />' .
+ Linker::revUserTools( $rev ) . '<br />' .
'</div>' .
'<div id="mw-diff-' . $prefix . 'title3">' .
- Linker::revComment( $rev ) . $rdel . '<br />' .
+ Linker::revComment( $rev ) . $rdel . '<br />' .
'</div>';
}
@@ -1078,15 +1118,16 @@ class SpecialUndelete extends SpecialPage {
$lang->userTime( $file->getTimestamp(), $user ) );
$out->addHTML(
Xml::openElement( 'form', array(
- 'method' => 'POST',
- 'action' => $this->getTitle()->getLocalURL(
- 'target=' . urlencode( $this->mTarget ) .
- '&file=' . urlencode( $key ) .
- '&token=' . urlencode( $user->getEditToken( $key ) ) )
+ 'method' => 'POST',
+ 'action' => $this->getTitle()->getLocalURL( array(
+ 'target' => $this->mTarget,
+ 'file' => $key,
+ 'token' => $user->getEditToken( $key ),
+ ) ),
)
) .
- Xml::submitButton( $this->msg( 'undelete-show-file-submit' )->text() ) .
- '</form>'
+ Xml::submitButton( $this->msg( 'undelete-show-file-submit' )->text() ) .
+ '</form>'
);
}
@@ -1112,7 +1153,7 @@ class SpecialUndelete extends SpecialPage {
private function showHistory() {
$out = $this->getOutput();
- if( $this->mAllowed ) {
+ if ( $this->mAllowed ) {
$out->addModules( 'mediawiki.special.undelete' );
}
$out->wrapWikiMsg(
@@ -1146,7 +1187,7 @@ class SpecialUndelete extends SpecialPage {
$haveFiles = $files && $files->numRows() > 0;
# Batch existence check on user and talk pages
- if( $haveRevisions ) {
+ if ( $haveRevisions ) {
$batch = new LinkBatch();
foreach ( $revisions as $row ) {
$batch->addObj( Title::makeTitleSafe( NS_USER, $row->ar_user_text ) );
@@ -1155,7 +1196,7 @@ class SpecialUndelete extends SpecialPage {
$batch->execute();
$revisions->seek( 0 );
}
- if( $haveFiles ) {
+ if ( $haveFiles ) {
$batch = new LinkBatch();
foreach ( $files as $row ) {
$batch->addObj( Title::makeTitleSafe( NS_USER, $row->fa_user_text ) );
@@ -1168,7 +1209,10 @@ class SpecialUndelete extends SpecialPage {
if ( $this->mAllowed ) {
$action = $this->getTitle()->getLocalURL( array( 'action' => 'submit' ) );
# Start the form here
- $top = Xml::openElement( 'form', array( 'method' => 'post', 'action' => $action, 'id' => 'undelete' ) );
+ $top = Xml::openElement(
+ 'form',
+ array( 'method' => 'post', 'action' => $action, 'id' => 'undelete' )
+ );
$out->addHTML( $top );
}
@@ -1178,59 +1222,60 @@ class SpecialUndelete extends SpecialPage {
LogEventsList::showLogExtract( $out, 'delete', $this->mTargetObj );
# Show relevant lines from the suppression log:
$suppressLogPage = new LogPage( 'suppress' );
- if( $this->getUser()->isAllowed( 'suppressionlog' ) ) {
+ if ( $this->getUser()->isAllowed( 'suppressionlog' ) ) {
$out->addHTML( Xml::element( 'h2', null, $suppressLogPage->getName()->text() ) . "\n" );
LogEventsList::showLogExtract( $out, 'suppress', $this->mTargetObj );
}
- if( $this->mAllowed && ( $haveRevisions || $haveFiles ) ) {
+ if ( $this->mAllowed && ( $haveRevisions || $haveFiles ) ) {
# Format the user-visible controls (comment field, submission button)
# in a nice little table
- if( $this->getUser()->isAllowed( 'suppressrevision' ) ) {
+ if ( $this->getUser()->isAllowed( 'suppressrevision' ) ) {
$unsuppressBox =
"<tr>
<td>&#160;</td>
<td class='mw-input'>" .
- Xml::checkLabel( $this->msg( 'revdelete-unsuppress' )->text(),
- 'wpUnsuppress', 'mw-undelete-unsuppress', $this->mUnsuppress ).
+ Xml::checkLabel( $this->msg( 'revdelete-unsuppress' )->text(),
+ 'wpUnsuppress', 'mw-undelete-unsuppress', $this->mUnsuppress ) .
"</td>
</tr>";
} else {
$unsuppressBox = '';
}
+
$table =
Xml::fieldset( $this->msg( 'undelete-fieldset-title' )->text() ) .
- Xml::openElement( 'table', array( 'id' => 'mw-undelete-table' ) ) .
+ Xml::openElement( 'table', array( 'id' => 'mw-undelete-table' ) ) .
"<tr>
<td colspan='2' class='mw-undelete-extrahelp'>" .
- $this->msg( 'undeleteextrahelp' )->parseAsBlock() .
- "</td>
- </tr>
- <tr>
- <td class='mw-label'>" .
- Xml::label( $this->msg( 'undeletecomment' )->text(), 'wpComment' ) .
- "</td>
- <td class='mw-input'>" .
- Xml::input( 'wpComment', 50, $this->mComment, array( 'id' => 'wpComment' ) ) .
- "</td>
- </tr>
- <tr>
- <td>&#160;</td>
- <td class='mw-submit'>" .
- Xml::submitButton( $this->msg( 'undeletebtn' )->text(), array( 'name' => 'restore', 'id' => 'mw-undelete-submit' ) ) . ' ' .
- Xml::submitButton( $this->msg( 'undeleteinvert' )->text(), array( 'name' => 'invert', 'id' => 'mw-undelete-invert' ) ) .
- "</td>
- </tr>" .
+ $this->msg( 'undeleteextrahelp' )->parseAsBlock() .
+ "</td>
+ </tr>
+ <tr>
+ <td class='mw-label'>" .
+ Xml::label( $this->msg( 'undeletecomment' )->text(), 'wpComment' ) .
+ "</td>
+ <td class='mw-input'>" .
+ Xml::input( 'wpComment', 50, $this->mComment, array( 'id' => 'wpComment', 'autofocus' => true ) ) .
+ "</td>
+ </tr>
+ <tr>
+ <td>&#160;</td>
+ <td class='mw-submit'>" .
+ Xml::submitButton( $this->msg( 'undeletebtn' )->text(), array( 'name' => 'restore', 'id' => 'mw-undelete-submit' ) ) . ' ' .
+ Xml::submitButton( $this->msg( 'undeleteinvert' )->text(), array( 'name' => 'invert', 'id' => 'mw-undelete-invert' ) ) .
+ "</td>
+ </tr>" .
$unsuppressBox .
- Xml::closeElement( 'table' ) .
- Xml::closeElement( 'fieldset' );
+ Xml::closeElement( 'table' ) .
+ Xml::closeElement( 'fieldset' );
$out->addHTML( $table );
}
$out->addHTML( Xml::element( 'h2', null, $this->msg( 'history' )->text() ) . "\n" );
- if( $haveRevisions ) {
+ if ( $haveRevisions ) {
# The page's stored (deleted) history:
$out->addHTML( '<ul>' );
$remaining = $revisions->numRows();
@@ -1246,7 +1291,7 @@ class SpecialUndelete extends SpecialPage {
$out->addWikiMsg( 'nohistory' );
}
- if( $haveFiles ) {
+ if ( $haveFiles ) {
$out->addHTML( Xml::element( 'h2', null, $this->msg( 'filehist' )->text() ) . "\n" );
$out->addHTML( '<ul>' );
foreach ( $files as $row ) {
@@ -1276,9 +1321,9 @@ class SpecialUndelete extends SpecialPage {
$revTextSize = '';
$ts = wfTimestamp( TS_MW, $row->ar_timestamp );
// Build checkboxen...
- if( $this->mAllowed ) {
- if( $this->mInvert ) {
- if( in_array( $ts, $this->mTargetTimestamp ) ) {
+ if ( $this->mAllowed ) {
+ if ( $this->mInvert ) {
+ if ( in_array( $ts, $this->mTargetTimestamp ) ) {
$checkBox = Xml::check( "ts$ts" );
} else {
$checkBox = Xml::check( "ts$ts", true );
@@ -1289,15 +1334,16 @@ class SpecialUndelete extends SpecialPage {
} else {
$checkBox = '';
}
- $user = $this->getUser();
+
// Build page & diff links...
- if( $this->mCanView ) {
+ $user = $this->getUser();
+ if ( $this->mCanView ) {
$titleObj = $this->getTitle();
# Last link
- if( !$rev->userCan( Revision::DELETED_TEXT, $this->getUser() ) ) {
+ if ( !$rev->userCan( Revision::DELETED_TEXT, $this->getUser() ) ) {
$pageLink = htmlspecialchars( $this->getLanguage()->userTimeAndDate( $ts, $user ) );
$last = $this->msg( 'diff' )->escaped();
- } elseif( $remaining > 0 || ( $earliestLiveTime && $ts > $earliestLiveTime ) ) {
+ } elseif ( $remaining > 0 || ( $earliestLiveTime && $ts > $earliestLiveTime ) ) {
$pageLink = $this->getPageLink( $rev, $titleObj, $ts );
$last = Linker::linkKnown(
$titleObj,
@@ -1317,28 +1363,35 @@ class SpecialUndelete extends SpecialPage {
$pageLink = htmlspecialchars( $this->getLanguage()->userTimeAndDate( $ts, $user ) );
$last = $this->msg( 'diff' )->escaped();
}
+
// User links
$userLink = Linker::revUserTools( $rev );
+
// Revision text size
$size = $row->ar_len;
- if( !is_null( $size ) ) {
+ if ( !is_null( $size ) ) {
$revTextSize = Linker::formatRevisionSize( $size );
}
+
// Edit summary
$comment = Linker::revComment( $rev );
+
// 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>";
}
private function formatFileRow( $row ) {
$file = ArchivedFile::newFromRow( $row );
-
$ts = wfTimestamp( TS_MW, $row->fa_timestamp );
$user = $this->getUser();
- if( $this->mAllowed && $row->fa_storage_key ) {
+
+ if ( $this->mAllowed && $row->fa_storage_key ) {
$checkBox = Xml::check( 'fileid' . $row->fa_id );
$key = urlencode( $row->fa_storage_key );
$pageLink = $this->getFileLink( $file, $this->getTitle(), $ts, $key );
@@ -1348,15 +1401,18 @@ class SpecialUndelete extends SpecialPage {
}
$userLink = $this->getFileUser( $file );
$data = $this->msg( 'widthheight' )->numParams( $row->fa_width, $row->fa_height )->text();
- $bytes = $this->msg( 'parentheses' )->rawParams( $this->msg( 'nbytes' )->numParams( $row->fa_size )->text() )->plain();
+ $bytes = $this->msg( 'parentheses' )
+ ->rawParams( $this->msg( 'nbytes' )->numParams( $row->fa_size )->text() )
+ ->plain();
$data = htmlspecialchars( $data . ' ' . $bytes );
$comment = $this->getFileComment( $file );
// Add show/hide deletion links if available
$canHide = $user->isAllowed( 'deleterevision' );
- if( $canHide || ( $file->getVisibility() && $user->isAllowed( 'deletedhistory' ) ) ) {
- if( !$file->userCan( File::DELETED_RESTRICTED, $user ) ) {
- $revdlink = Linker::revDeleteLinkDisabled( $canHide ); // revision was hidden from sysops
+ if ( $canHide || ( $file->getVisibility() && $user->isAllowed( 'deletedhistory' ) ) ) {
+ if ( !$file->userCan( File::DELETED_RESTRICTED, $user ) ) {
+ // Revision was hidden from sysops
+ $revdlink = Linker::revDeleteLinkDisabled( $canHide );
} else {
$query = array(
'type' => 'filearchive',
@@ -1376,8 +1432,8 @@ class SpecialUndelete extends SpecialPage {
/**
* Fetch revision text link if it's available to all users
*
- * @param $rev Revision
- * @param $titleObj Title
+ * @param Revision $rev
+ * @param Title $titleObj
* @param string $ts Timestamp
* @return string
*/
@@ -1385,95 +1441,105 @@ class SpecialUndelete extends SpecialPage {
$user = $this->getUser();
$time = $this->getLanguage()->userTimeAndDate( $ts, $user );
- if( !$rev->userCan( Revision::DELETED_TEXT, $user ) ) {
+ if ( !$rev->userCan( Revision::DELETED_TEXT, $user ) ) {
return '<span class="history-deleted">' . $time . '</span>';
- } else {
- $link = Linker::linkKnown(
- $titleObj,
- htmlspecialchars( $time ),
- array(),
- array(
- 'target' => $this->mTargetObj->getPrefixedText(),
- 'timestamp' => $ts
- )
- );
- if( $rev->isDeleted( Revision::DELETED_TEXT ) ) {
- $link = '<span class="history-deleted">' . $link . '</span>';
- }
- return $link;
}
+
+ $link = Linker::linkKnown(
+ $titleObj,
+ htmlspecialchars( $time ),
+ array(),
+ array(
+ 'target' => $this->mTargetObj->getPrefixedText(),
+ 'timestamp' => $ts
+ )
+ );
+
+ if ( $rev->isDeleted( Revision::DELETED_TEXT ) ) {
+ $link = '<span class="history-deleted">' . $link . '</span>';
+ }
+
+ return $link;
}
/**
* Fetch image view link if it's available to all users
*
- * @param $file File
- * @param $titleObj Title
+ * @param File|ArchivedFile $file
+ * @param Title $titleObj
* @param string $ts A timestamp
* @param string $key a storage key
*
- * @return String: HTML fragment
+ * @return string HTML fragment
*/
function getFileLink( $file, $titleObj, $ts, $key ) {
$user = $this->getUser();
$time = $this->getLanguage()->userTimeAndDate( $ts, $user );
- if( !$file->userCan( File::DELETED_FILE, $user ) ) {
+ if ( !$file->userCan( File::DELETED_FILE, $user ) ) {
return '<span class="history-deleted">' . $time . '</span>';
- } else {
- $link = Linker::linkKnown(
- $titleObj,
- htmlspecialchars( $time ),
- array(),
- array(
- 'target' => $this->mTargetObj->getPrefixedText(),
- 'file' => $key,
- 'token' => $user->getEditToken( $key )
- )
- );
- if( $file->isDeleted( File::DELETED_FILE ) ) {
- $link = '<span class="history-deleted">' . $link . '</span>';
- }
- return $link;
}
+
+ $link = Linker::linkKnown(
+ $titleObj,
+ htmlspecialchars( $time ),
+ array(),
+ array(
+ 'target' => $this->mTargetObj->getPrefixedText(),
+ 'file' => $key,
+ 'token' => $user->getEditToken( $key )
+ )
+ );
+
+ if ( $file->isDeleted( File::DELETED_FILE ) ) {
+ $link = '<span class="history-deleted">' . $link . '</span>';
+ }
+
+ return $link;
}
/**
* Fetch file's user id if it's available to this user
*
- * @param $file File
- * @return String: HTML fragment
+ * @param File|ArchivedFile $file
+ * @return string HTML fragment
*/
function getFileUser( $file ) {
- if( !$file->userCan( File::DELETED_USER, $this->getUser() ) ) {
- return '<span class="history-deleted">' . $this->msg( 'rev-deleted-user' )->escaped() . '</span>';
- } else {
- $link = Linker::userLink( $file->getRawUser(), $file->getRawUserText() ) .
- Linker::userToolLinks( $file->getRawUser(), $file->getRawUserText() );
- if( $file->isDeleted( File::DELETED_USER ) ) {
- $link = '<span class="history-deleted">' . $link . '</span>';
- }
- return $link;
+ if ( !$file->userCan( File::DELETED_USER, $this->getUser() ) ) {
+ return '<span class="history-deleted">' .
+ $this->msg( 'rev-deleted-user' )->escaped() .
+ '</span>';
}
+
+ $link = Linker::userLink( $file->getRawUser(), $file->getRawUserText() ) .
+ Linker::userToolLinks( $file->getRawUser(), $file->getRawUserText() );
+
+ if ( $file->isDeleted( File::DELETED_USER ) ) {
+ $link = '<span class="history-deleted">' . $link . '</span>';
+ }
+
+ return $link;
}
/**
* Fetch file upload comment if it's available to this user
*
- * @param $file File
- * @return String: HTML fragment
+ * @param File|ArchivedFile $file
+ * @return string HTML fragment
*/
function getFileComment( $file ) {
- if( !$file->userCan( File::DELETED_COMMENT, $this->getUser() ) ) {
+ if ( !$file->userCan( File::DELETED_COMMENT, $this->getUser() ) ) {
return '<span class="history-deleted"><span class="comment">' .
$this->msg( 'rev-deleted-comment' )->escaped() . '</span></span>';
- } else {
- $link = Linker::commentBlock( $file->getRawDescription() );
- if( $file->isDeleted( File::DELETED_COMMENT ) ) {
- $link = '<span class="history-deleted">' . $link . '</span>';
- }
- return $link;
}
+
+ $link = Linker::commentBlock( $file->getRawDescription() );
+
+ if ( $file->isDeleted( File::DELETED_COMMENT ) ) {
+ $link = '<span class="history-deleted">' . $link . '</span>';
+ }
+
+ return $link;
}
function undelete() {
@@ -1498,7 +1564,7 @@ class SpecialUndelete extends SpecialPage {
$this->getUser()
);
- if( is_array( $ok ) ) {
+ if ( is_array( $ok ) ) {
if ( $ok[1] ) { // Undeleted file count
wfRunHooks( 'FileUndeleteComplete', array(
$this->mTargetObj, $this->mFileVersions,
@@ -1513,13 +1579,13 @@ class SpecialUndelete extends SpecialPage {
// Show revision undeletion warnings and errors
$status = $archive->getRevisionStatus();
- if( $status && !$status->isGood() ) {
+ 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() ) {
+ if ( $status && !$status->isGood() ) {
$out->addWikiText( '<div class="error">' . $status->getWikiText( 'undelete-error-short', 'undelete-error-long' ) . '</div>' );
}
}
diff --git a/includes/specials/SpecialUnusedcategories.php b/includes/specials/SpecialUnusedcategories.php
index 6b91dd39..b686a5b5 100644
--- a/includes/specials/SpecialUnusedcategories.php
+++ b/includes/specials/SpecialUnusedcategories.php
@@ -39,15 +39,15 @@ class UnusedCategoriesPage extends QueryPage {
}
function getQueryInfo() {
- return array (
- 'tables' => array ( 'page', 'categorylinks' ),
- 'fields' => array ( 'namespace' => 'page_namespace',
+ return array(
+ 'tables' => array( 'page', 'categorylinks' ),
+ 'fields' => array( 'namespace' => 'page_namespace',
'title' => 'page_title',
'value' => 'page_title' ),
- 'conds' => array ( 'cl_from IS NULL',
+ 'conds' => array( 'cl_from IS NULL',
'page_namespace' => NS_CATEGORY,
'page_is_redirect' => 0 ),
- 'join_conds' => array ( 'categorylinks' => array (
+ 'join_conds' => array( 'categorylinks' => array(
'LEFT JOIN', 'cl_to = page_title' ) )
);
}
@@ -60,6 +60,11 @@ class UnusedCategoriesPage extends QueryPage {
return false;
}
+ /**
+ * @param Skin $skin
+ * @param object $result Result row
+ * @return string
+ */
function formatResult( $skin, $result ) {
$title = Title::makeTitle( NS_CATEGORY, $result->title );
return Linker::link( $title, htmlspecialchars( $title->getText() ) );
diff --git a/includes/specials/SpecialUnusedimages.php b/includes/specials/SpecialUnusedimages.php
index 69553282..d332db75 100644
--- a/includes/specials/SpecialUnusedimages.php
+++ b/includes/specials/SpecialUnusedimages.php
@@ -45,28 +45,28 @@ class UnusedimagesPage extends ImageQueryPage {
function getQueryInfo() {
global $wgCountCategorizedImagesAsUsed;
- $retval = array (
- 'tables' => array ( 'image', 'imagelinks' ),
- 'fields' => array ( 'namespace' => NS_FILE,
+ $retval = array(
+ 'tables' => array( 'image', 'imagelinks' ),
+ 'fields' => array( 'namespace' => NS_FILE,
'title' => 'img_name',
'value' => 'img_timestamp',
'img_user', 'img_user_text',
'img_description' ),
- 'conds' => array ( 'il_to IS NULL' ),
- 'join_conds' => array ( 'imagelinks' => array (
+ 'conds' => array( 'il_to IS NULL' ),
+ 'join_conds' => array( 'imagelinks' => array(
'LEFT JOIN', 'il_to = img_name' ) )
);
if ( $wgCountCategorizedImagesAsUsed ) {
// Order is significant
- $retval['tables'] = array ( 'image', 'page', 'categorylinks',
+ $retval['tables'] = array( 'image', 'page', 'categorylinks',
'imagelinks' );
$retval['conds']['page_namespace'] = NS_FILE;
$retval['conds'][] = 'cl_from IS NULL';
$retval['conds'][] = 'img_name = page_title';
- $retval['join_conds']['categorylinks'] = array (
+ $retval['join_conds']['categorylinks'] = array(
'LEFT JOIN', 'cl_from = page_id' );
- $retval['join_conds']['imagelinks'] = array (
+ $retval['join_conds']['imagelinks'] = array(
'LEFT JOIN', 'il_to = page_title' );
}
return $retval;
diff --git a/includes/specials/SpecialUnusedtemplates.php b/includes/specials/SpecialUnusedtemplates.php
index 493e936a..1dc9f420 100644
--- a/includes/specials/SpecialUnusedtemplates.php
+++ b/includes/specials/SpecialUnusedtemplates.php
@@ -48,23 +48,23 @@ class UnusedtemplatesPage extends QueryPage {
}
function getQueryInfo() {
- return array (
- 'tables' => array ( 'page', 'templatelinks' ),
- 'fields' => array ( 'namespace' => 'page_namespace',
+ return array(
+ 'tables' => array( 'page', 'templatelinks' ),
+ 'fields' => array( 'namespace' => 'page_namespace',
'title' => 'page_title',
'value' => 'page_title' ),
- 'conds' => array ( 'page_namespace' => NS_TEMPLATE,
+ 'conds' => array( 'page_namespace' => NS_TEMPLATE,
'tl_from IS NULL',
'page_is_redirect' => 0 ),
- 'join_conds' => array ( 'templatelinks' => array (
- 'LEFT JOIN', array ( 'tl_title = page_title',
+ 'join_conds' => array( 'templatelinks' => array(
+ 'LEFT JOIN', array( 'tl_title = page_title',
'tl_namespace = page_namespace' ) ) )
);
}
/**
- * @param $skin Skin
- * @param $result
+ * @param Skin $skin
+ * @param object $result Result row
* @return string
*/
function formatResult( $skin, $result ) {
diff --git a/includes/specials/SpecialUnwatchedpages.php b/includes/specials/SpecialUnwatchedpages.php
index 05ec6b02..954e3ffe 100644
--- a/includes/specials/SpecialUnwatchedpages.php
+++ b/includes/specials/SpecialUnwatchedpages.php
@@ -44,17 +44,17 @@ class UnwatchedpagesPage extends QueryPage {
}
function getQueryInfo() {
- return array (
- 'tables' => array ( 'page', 'watchlist' ),
- 'fields' => array ( 'namespace' => 'page_namespace',
+ return array(
+ 'tables' => array( 'page', 'watchlist' ),
+ 'fields' => array( 'namespace' => 'page_namespace',
'title' => 'page_title',
'value' => 'page_namespace' ),
- 'conds' => array ( 'wl_title IS NULL',
+ 'conds' => array( 'wl_title IS NULL',
'page_is_redirect' => 0,
"page_namespace != '" . NS_MEDIAWIKI .
"'" ),
- 'join_conds' => array ( 'watchlist' => array (
- 'LEFT JOIN', array ( 'wl_title = page_title',
+ 'join_conds' => array( 'watchlist' => array(
+ 'LEFT JOIN', array( 'wl_title = page_title',
'wl_namespace = page_namespace' ) ) )
);
}
@@ -68,8 +68,8 @@ class UnwatchedpagesPage extends QueryPage {
}
/**
- * @param $skin Skin
- * @param $result
+ * @param Skin $skin
+ * @param object $result Result row
* @return string
*/
function formatResult( $skin, $result ) {
diff --git a/includes/specials/SpecialUpload.php b/includes/specials/SpecialUpload.php
index 89c06b2a..09facf4f 100644
--- a/includes/specials/SpecialUpload.php
+++ b/includes/specials/SpecialUpload.php
@@ -90,10 +90,9 @@ class SpecialUpload extends SpecialPage {
// Guess the desired name from the filename if not provided
$this->mDesiredDestName = $request->getText( 'wpDestFile' );
- if( !$this->mDesiredDestName && $request->getFileName( 'wpUploadFile' ) !== null ) {
+ if ( !$this->mDesiredDestName && $request->getFileName( 'wpUploadFile' ) !== null ) {
$this->mDesiredDestName = $request->getFileName( 'wpUploadFile' );
}
- $this->mComment = $request->getText( 'wpUploadDescription' );
$this->mLicense = $request->getText( 'wpLicense' );
$this->mDestWarningAck = $request->getText( 'wpDestFileWarningAck' );
@@ -104,6 +103,14 @@ class SpecialUpload extends SpecialPage {
$this->mCopyrightSource = $request->getText( 'wpUploadSource' );
$this->mForReUpload = $request->getBool( 'wpForReUpload' ); // updating a file
+
+ $commentDefault = '';
+ $commentMsg = wfMessage( 'upload-default-description' )->inContentLanguage();
+ if ( !$this->mForReUpload && !$commentMsg->isDisabled() ) {
+ $commentDefault = $commentMsg->plain();
+ }
+ $this->mComment = $request->getText( 'wpUploadDescription', $commentDefault );
+
$this->mCancelUpload = $request->getCheck( 'wpCancelUpload' )
|| $request->getCheck( 'wpReUpload' ); // b/w compat
@@ -135,19 +142,19 @@ class SpecialUpload extends SpecialPage {
$this->outputHeader();
# Check uploading enabled
- if( !UploadBase::isEnabled() ) {
+ if ( !UploadBase::isEnabled() ) {
throw new ErrorPageError( 'uploaddisabled', 'uploaddisabledtext' );
}
# Check permissions
$user = $this->getUser();
$permissionRequired = UploadBase::isAllowed( $user );
- if( $permissionRequired !== true ) {
+ if ( $permissionRequired !== true ) {
throw new PermissionsError( $permissionRequired );
}
# Check blocks
- if( $user->isBlocked() ) {
+ if ( $user->isBlocked() ) {
throw new UserBlockedError( $user->getBlock() );
}
@@ -172,7 +179,7 @@ class SpecialUpload extends SpecialPage {
$this->processUpload();
} else {
# Backwards compatibility hook
- if( !wfRunHooks( 'UploadForm:initial', array( &$this ) ) ) {
+ if ( !wfRunHooks( 'UploadForm:initial', array( &$this ) ) ) {
wfDebug( "Hook 'UploadForm:initial' broke output of the upload form" );
return;
}
@@ -214,6 +221,8 @@ class SpecialUpload extends SpecialPage {
*/
protected function getUploadForm( $message = '', $sessionKey = '', $hideIgnoreWarning = false ) {
# Initialize form
+ $context = new DerivativeContext( $this->getContext() );
+ $context->setTitle( $this->getTitle() ); // Remove subpage
$form = new UploadForm( array(
'watch' => $this->getWatchCheck(),
'forreupload' => $this->mForReUpload,
@@ -225,11 +234,10 @@ class SpecialUpload extends SpecialPage {
'texttop' => $this->uploadFormTextTop,
'textaftersummary' => $this->uploadFormTextAfterSummary,
'destfile' => $this->mDesiredDestName,
- ), $this->getContext() );
- $form->setTitle( $this->getTitle() );
+ ), $context );
# Check the token, but only if necessary
- if(
+ if (
!$this->mTokenOk && !$this->mCancelUpload &&
( $this->mUpload && $this->mUploadClicked )
) {
@@ -275,7 +283,7 @@ class SpecialUpload extends SpecialPage {
$title = Title::makeTitleSafe( NS_FILE, $this->mDesiredDestName );
$user = $this->getUser();
// Show a subtitle link to deleted revisions (to sysops et al only)
- if( $title instanceof Title ) {
+ if ( $title instanceof Title ) {
$count = $title->isDeleted();
if ( $count > 0 && $user->isAllowed( 'deletedhistory' ) ) {
$restorelink = Linker::linkKnown(
@@ -333,15 +341,15 @@ class SpecialUpload extends SpecialPage {
$warningHtml = '<h2>' . $this->msg( 'uploadwarning' )->escaped() . "</h2>\n"
. '<ul class="warning">';
- foreach( $warnings as $warning => $args ) {
- if( $warning == 'badfilename' ) {
+ foreach ( $warnings as $warning => $args ) {
+ if ( $warning == 'badfilename' ) {
$this->mDesiredDestName = Title::makeTitle( NS_FILE, $args )->getText();
}
- if( $warning == 'exists' ) {
+ if ( $warning == 'exists' ) {
$msg = "\t<li>" . self::getExistsWarning( $args ) . "</li>\n";
- } elseif( $warning == 'duplicate' ) {
- $msg = self::getDupeWarning( $args );
- } elseif( $warning == 'duplicate-archive' ) {
+ } elseif ( $warning == 'duplicate' ) {
+ $msg = $this->getDupeWarning( $args );
+ } elseif ( $warning == 'duplicate-archive' ) {
$msg = "\t<li>" . $this->msg( 'file-deleted-duplicate',
Title::makeTitle( NS_FILE, $args )->getPrefixedText() )->parse()
. "</li>\n";
@@ -387,12 +395,12 @@ class SpecialUpload extends SpecialPage {
protected function processUpload() {
// Fetch the file if required
$status = $this->mUpload->fetchFile();
- if( !$status->isOK() ) {
+ if ( !$status->isOK() ) {
$this->showUploadError( $this->getOutput()->parse( $status->getWikiText() ) );
return;
}
- if( !wfRunHooks( 'UploadForm:BeforeProcessing', array( &$this ) ) ) {
+ if ( !wfRunHooks( 'UploadForm:BeforeProcessing', array( &$this ) ) ) {
wfDebug( "Hook 'UploadForm:BeforeProcessing' broke processing the file.\n" );
// This code path is deprecated. If you want to break upload processing
// do so by hooking into the appropriate hooks in UploadBase::verifyUpload
@@ -411,7 +419,7 @@ class SpecialUpload extends SpecialPage {
// Verify permissions for this title
$permErrors = $this->mUpload->verifyTitlePermissions( $this->getUser() );
- if( $permErrors !== true ) {
+ if ( $permErrors !== true ) {
$code = array_shift( $permErrors[0] );
$this->showRecoverableUploadError( $this->msg( $code, $permErrors[0] )->parse() );
return;
@@ -420,15 +428,15 @@ class SpecialUpload extends SpecialPage {
$this->mLocalFile = $this->mUpload->getLocalFile();
// Check warnings if necessary
- if( !$this->mIgnoreWarning ) {
+ if ( !$this->mIgnoreWarning ) {
$warnings = $this->mUpload->checkWarnings();
- if( $this->showUploadWarning( $warnings ) ) {
+ if ( $this->showUploadWarning( $warnings ) ) {
return;
}
}
// Get the page text if this is not a reupload
- if( !$this->mForReUpload ) {
+ if ( !$this->mForReUpload ) {
$pageText = self::getInitialPageText( $this->mComment, $this->mLicense,
$this->mCopyrightStatus, $this->mCopyrightSource );
} else {
@@ -456,15 +464,14 @@ class SpecialUpload extends SpecialPage {
*/
public static function getInitialPageText( $comment = '', $license = '', $copyStatus = '', $source = '' ) {
global $wgUseCopyrightUpload, $wgForceUIMsgAsContentMsg;
- $wgForceUIMsgAsContentMsg = (array) $wgForceUIMsgAsContentMsg;
$msg = array();
/* These messages are transcluded into the actual text of the description page.
* Thus, forcing them as content messages makes the upload to produce an int: template
* instead of hardcoding it there in the uploader language.
*/
- foreach( array( 'license-header', 'filedesc', 'filestatus', 'filesource' ) as $msgName ) {
- if ( in_array( $msgName, $wgForceUIMsgAsContentMsg ) ) {
+ foreach ( array( 'license-header', 'filedesc', 'filestatus', 'filesource' ) as $msgName ) {
+ if ( in_array( $msgName, (array)$wgForceUIMsgAsContentMsg ) ) {
$msg[$msgName] = "{{int:$msgName}}";
} else {
$msg[$msgName] = wfMessage( $msgName )->inContentLanguage()->text();
@@ -505,13 +512,13 @@ class SpecialUpload extends SpecialPage {
* @return Bool|String
*/
protected function getWatchCheck() {
- if( $this->getUser()->getOption( 'watchdefault' ) ) {
+ if ( $this->getUser()->getOption( 'watchdefault' ) ) {
// Watch all edits!
return true;
}
$local = wfLocalFile( $this->mDesiredDestName );
- if( $local && $local->exists() ) {
+ if ( $local && $local->exists() ) {
// We're uploading a new version of an existing file.
// No creation, so don't watch it if we're not already.
return $this->getUser()->isWatched( $local->getTitle() );
@@ -530,7 +537,7 @@ class SpecialUpload extends SpecialPage {
protected function processVerificationError( $details ) {
global $wgFileExtensions;
- switch( $details['status'] ) {
+ switch ( $details['status'] ) {
/** Statuses that only require name changing **/
case UploadBase::MIN_LENGTH_PARTNAME:
@@ -564,8 +571,9 @@ class SpecialUpload extends SpecialPage {
} else {
$msg->params( $details['finalExt'] );
}
- $msg->params( $this->getLanguage()->commaList( $wgFileExtensions ),
- count( $wgFileExtensions ) );
+ $extensions = array_unique( $wgFileExtensions );
+ $msg->params( $this->getLanguage()->commaList( $extensions ),
+ count( $extensions ) );
// Add PLURAL support for the first parameter. This results
// in a bit unlogical parameter sequence, but does not break
@@ -635,10 +643,10 @@ class SpecialUpload extends SpecialPage {
$filename = $file->getTitle()->getPrefixedText();
$warning = '';
- if( $exists['warning'] == 'exists' ) {
+ if ( $exists['warning'] == 'exists' ) {
// Exact match
$warning = wfMessage( 'fileexists', $filename )->parse();
- } elseif( $exists['warning'] == 'page-exists' ) {
+ } elseif ( $exists['warning'] == 'page-exists' ) {
// Page exists but file does not
$warning = wfMessage( 'filepageexists', $filename )->parse();
} elseif ( $exists['warning'] == 'exists-normalized' ) {
@@ -674,42 +682,19 @@ class SpecialUpload extends SpecialPage {
}
/**
- * Get a list of warnings
- *
- * @param string $filename local filename, e.g. 'file exists', 'non-descriptive filename'
- * @return Array: list of warning messages
- */
- public static function ajaxGetExistsWarning( $filename ) {
- $file = wfFindFile( $filename );
- if( !$file ) {
- // Force local file so we have an object to do further checks against
- // if there isn't an exact match...
- $file = wfLocalFile( $filename );
- }
- $s = '&#160;';
- if ( $file ) {
- $exists = UploadBase::getExistsWarning( $file );
- $warning = self::getExistsWarning( $exists );
- if ( $warning !== '' ) {
- $s = "<div>$warning</div>";
- }
- }
- return $s;
- }
-
- /**
* Construct a warning and a gallery from an array of duplicate files.
* @param $dupes array
* @return string
*/
- public static function getDupeWarning( $dupes ) {
+ public function getDupeWarning( $dupes ) {
if ( !$dupes ) {
return '';
}
- $gallery = new ImageGallery;
+ $gallery = ImageGalleryBase::factory();
+ $gallery->setContext( $this->getContext() );
$gallery->setShowBytes( false );
- foreach( $dupes as $file ) {
+ foreach ( $dupes as $file ) {
$gallery->add( $file->getTitle() );
}
return '<li>' .
@@ -843,10 +828,13 @@ class UploadForm extends HTMLForm {
'upload-type' => 'File',
'radio' => &$radio,
'help' => $this->msg( 'upload-maxfilesize',
- $this->getContext()->getLanguage()->formatSize( $this->mMaxUploadSize['file'] )
- )->parse() . ' ' . $this->msg( 'upload_source_file' )->escaped(),
+ $this->getContext()->getLanguage()->formatSize( $this->mMaxUploadSize['file'] ) )
+ ->parse() .
+ $this->msg( 'word-separator' )->escaped() .
+ $this->msg( 'upload_source_file' )->escaped(),
'checked' => $selectedSourceType == 'file',
);
+
if ( $canUploadByUrl ) {
$this->mMaxUploadSize['url'] = UploadBase::getMaxUploadSize( 'url' );
$descriptor['UploadFileURL'] = array(
@@ -857,8 +845,10 @@ class UploadForm extends HTMLForm {
'upload-type' => 'url',
'radio' => &$radio,
'help' => $this->msg( 'upload-maxfilesize',
- $this->getContext()->getLanguage()->formatSize( $this->mMaxUploadSize['url'] )
- )->parse() . ' ' . $this->msg( 'upload_source_url' )->escaped(),
+ $this->getContext()->getLanguage()->formatSize( $this->mMaxUploadSize['url'] ) )
+ ->parse() .
+ $this->msg( 'word-separator' )->escaped() .
+ $this->msg( 'upload_source_url' )->escaped(),
'checked' => $selectedSourceType == 'url',
);
}
@@ -884,21 +874,21 @@ class UploadForm extends HTMLForm {
global $wgCheckFileExtensions, $wgStrictFileExtensions,
$wgFileExtensions, $wgFileBlacklist;
- if( $wgCheckFileExtensions ) {
- if( $wgStrictFileExtensions ) {
+ if ( $wgCheckFileExtensions ) {
+ if ( $wgStrictFileExtensions ) {
# Everything not permitted is banned
$extensionsList =
'<div id="mw-upload-permitted">' .
- $this->msg( 'upload-permitted', $this->getContext()->getLanguage()->commaList( $wgFileExtensions ) )->parseAsBlock() .
+ $this->msg( 'upload-permitted', $this->getContext()->getLanguage()->commaList( array_unique( $wgFileExtensions ) ) )->parseAsBlock() .
"</div>\n";
} else {
# We have to list both preferred and prohibited
$extensionsList =
'<div id="mw-upload-preferred">' .
- $this->msg( 'upload-preferred', $this->getContext()->getLanguage()->commaList( $wgFileExtensions ) )->parseAsBlock() .
+ $this->msg( 'upload-preferred', $this->getContext()->getLanguage()->commaList( array_unique( $wgFileExtensions ) ) )->parseAsBlock() .
"</div>\n" .
'<div id="mw-upload-prohibited">' .
- $this->msg( 'upload-prohibited', $this->getContext()->getLanguage()->commaList( $wgFileBlacklist ) )->parseAsBlock() .
+ $this->msg( 'upload-prohibited', $this->getContext()->getLanguage()->commaList( array_unique( $wgFileBlacklist ) ) )->parseAsBlock() .
"</div>\n";
}
} else {
diff --git a/includes/specials/SpecialUploadStash.php b/includes/specials/SpecialUploadStash.php
index 066dcfc9..87b64428 100644
--- a/includes/specials/SpecialUploadStash.php
+++ b/includes/specials/SpecialUploadStash.php
@@ -88,19 +88,19 @@ class SpecialUploadStash extends UnlistedSpecialPage {
} else {
return $this->outputLocalFile( $params['file'] );
}
- } catch( UploadStashFileNotFoundException $e ) {
+ } catch ( UploadStashFileNotFoundException $e ) {
$code = 404;
$message = $e->getMessage();
- } catch( UploadStashZeroLengthFileException $e ) {
+ } catch ( UploadStashZeroLengthFileException $e ) {
$code = 500;
$message = $e->getMessage();
- } catch( UploadStashBadPathException $e ) {
+ } catch ( UploadStashBadPathException $e ) {
$code = 500;
$message = $e->getMessage();
- } catch( SpecialUploadStashTooLargeException $e ) {
+ } catch ( SpecialUploadStashTooLargeException $e ) {
$code = 500;
$message = 'Cannot serve a file larger than ' . self::MAX_SERVE_BYTES . ' bytes. ' . $e->getMessage();
- } catch( Exception $e ) {
+ } catch ( Exception $e ) {
$code = 500;
$message = $e->getMessage();
}
@@ -134,8 +134,13 @@ class SpecialUploadStash extends UnlistedSpecialPage {
$paramString = substr( $thumbPart, 0, $srcNamePos - 1 );
$handler = $file->getHandler();
- $params = $handler->parseParamString( $paramString );
- return array( 'file' => $file, 'type' => $type, 'params' => $params );
+ if ( $handler ) {
+ $params = $handler->parseParamString( $paramString );
+ return array( 'file' => $file, 'type' => $type, 'params' => $params );
+ } else {
+ throw new UploadStashBadPathException( 'No handler found for ' .
+ "mime {$file->getMimeType()} of file {$file->getPath()}" );
+ }
}
return array( 'file' => $file, 'type' => $type );
@@ -222,7 +227,7 @@ class SpecialUploadStash extends UnlistedSpecialPage {
global $wgUploadStashScalerBaseUrl;
$scalerBaseUrl = $wgUploadStashScalerBaseUrl;
- if( preg_match( '/^\/\//', $scalerBaseUrl ) ) {
+ if ( preg_match( '/^\/\//', $scalerBaseUrl ) ) {
// this is apparently a protocol-relative URL, which makes no sense in this context,
// since this is used for communication that's internal to the application.
// default to http.
@@ -303,8 +308,6 @@ class SpecialUploadStash extends UnlistedSpecialPage {
header( "Content-Type: $contentType", true );
header( 'Content-Transfer-Encoding: binary', true );
header( 'Expires: Sun, 17-Jan-2038 19:14:07 GMT', true );
- // Bug 53032 - It shouldn't be a problem here, but let's be safe and not cache
- header( 'Cache-Control: private' );
header( "Content-Length: $size", true );
}
@@ -313,6 +316,7 @@ class SpecialUploadStash extends UnlistedSpecialPage {
* Note the stash has to be recreated since this is being called in a static context.
* This works, because there really is only one stash per logged-in user, despite appearances.
*
+ * @param array $formData
* @return Status
*/
public static function tryClearStashedUploads( $formData ) {
@@ -339,15 +343,16 @@ class SpecialUploadStash extends UnlistedSpecialPage {
// create the form, which will also be used to execute a callback to process incoming form data
// this design is extremely dubious, but supposedly HTMLForm is our standard now?
+ $context = new DerivativeContext( $this->getContext() );
+ $context->setTitle( $this->getTitle() ); // Remove subpage
$form = new HTMLForm( array(
'Clear' => array(
'type' => 'hidden',
'default' => true,
'name' => 'clear',
)
- ), $this->getContext(), 'clearStashedUploads' );
+ ), $context, 'clearStashedUploads' );
$form->setSubmitCallback( array( __CLASS__, 'tryClearStashedUploads' ) );
- $form->setTitle( $this->getTitle() );
$form->setSubmitTextMsg( 'uploadstash-clear' );
$form->prepareForm();
diff --git a/includes/specials/SpecialUserlogin.php b/includes/specials/SpecialUserlogin.php
index eef66914..5ac3e654 100644
--- a/includes/specials/SpecialUserlogin.php
+++ b/includes/specials/SpecialUserlogin.php
@@ -48,18 +48,21 @@ class LoginForm extends SpecialPage {
var $mLoginattempt, $mRemember, $mEmail, $mDomain, $mLanguage;
var $mSkipCookieCheck, $mReturnToQuery, $mToken, $mStickHTTPS;
var $mType, $mReason, $mRealName;
- var $mAbortLoginErrorMsg = 'login-abort-generic';
+ var $mAbortLoginErrorMsg = null;
private $mLoaded = false;
+ private $mSecureLoginUrl;
/**
- * @var ExternalUser
+ * @ var WebRequest
*/
- private $mExtUser = null;
+ private $mOverrideRequest = null;
/**
- * @ var WebRequest
+ * Effective request; set at the beginning of load
+ *
+ * @var WebRequest $mRequest
*/
- private $mOverrideRequest = null;
+ private $mRequest = null;
/**
* @param WebRequest $request
@@ -86,6 +89,7 @@ class LoginForm extends SpecialPage {
} else {
$request = $this->mOverrideRequest;
}
+ $this->mRequest = $request;
$this->mType = $request->getText( 'type' );
$this->mUsername = $request->getText( 'wpName' );
@@ -101,25 +105,26 @@ class LoginForm extends SpecialPage {
$this->mLoginattempt = $request->getCheck( 'wpLoginattempt' );
$this->mAction = $request->getVal( 'action' );
$this->mRemember = $request->getCheck( 'wpRemember' );
- $this->mStickHTTPS = $request->getCheck( 'wpStickHTTPS' );
+ $this->mFromHTTP = $request->getBool( 'fromhttp', false );
+ $this->mStickHTTPS = ( !$this->mFromHTTP && $request->detectProtocol() === 'https' ) || $request->getBool( 'wpForceHttps', false );
$this->mLanguage = $request->getText( 'uselang' );
$this->mSkipCookieCheck = $request->getCheck( 'wpSkipCookieCheck' );
$this->mToken = ( $this->mType == 'signup' ) ? $request->getVal( 'wpCreateaccountToken' ) : $request->getVal( 'wpLoginToken' );
$this->mReturnTo = $request->getVal( 'returnto', '' );
$this->mReturnToQuery = $request->getVal( 'returntoquery', '' );
- if( $wgEnableEmail ) {
+ if ( $wgEnableEmail ) {
$this->mEmail = $request->getText( 'wpEmail' );
} else {
$this->mEmail = '';
}
- if( !in_array( 'realname', $wgHiddenPrefs ) ) {
+ if ( !in_array( 'realname', $wgHiddenPrefs ) ) {
$this->mRealName = $request->getText( 'wpRealName' );
} else {
$this->mRealName = '';
}
- if( !$wgAuth->validDomain( $this->mDomain ) ) {
+ if ( !$wgAuth->validDomain( $this->mDomain ) ) {
$this->mDomain = $wgAuth->getDomain();
}
$wgAuth->setDomain( $this->mDomain );
@@ -128,7 +133,7 @@ class LoginForm extends SpecialPage {
# 2. Do not return to PasswordReset after a successful password change
# but goto Wiki start page (Main_Page) instead ( bug 33997 )
$returnToTitle = Title::newFromText( $this->mReturnTo );
- if( is_object( $returnToTitle ) && (
+ if ( is_object( $returnToTitle ) && (
$returnToTitle->isSpecial( 'Userlogout' )
|| $returnToTitle->isSpecial( 'PasswordReset' ) ) ) {
$this->mReturnTo = '';
@@ -137,44 +142,61 @@ class LoginForm extends SpecialPage {
}
function getDescription() {
- return $this->msg( $this->getUser()->isAllowed( 'createaccount' ) ?
- 'userlogin' : 'userloginnocreate' )->text();
+ if ( $this->mType === 'signup' ) {
+ return $this->msg( 'createaccount' )->text();
+ } else {
+ return $this->msg( 'login' )->text();
+ }
}
- public function execute( $par ) {
+ /*
+ * @param $subPage string|null
+ */
+ public function execute( $subPage ) {
if ( session_id() == '' ) {
wfSetupSession();
}
$this->load();
+
+ // Check for [[Special:Userlogin/signup]]. This affects form display and
+ // page title.
+ if ( $subPage == 'signup' ) {
+ $this->mType = 'signup';
+ }
$this->setHeaders();
+ // If logging in and not on HTTPS, either redirect to it or offer a link.
global $wgSecureLogin;
- if (
- $this->mType !== 'signup' &&
- $wgSecureLogin &&
- WebRequest::detectProtocol() !== 'https'
- ) {
+ if ( WebRequest::detectProtocol() !== 'https' ) {
$title = $this->getFullTitle();
$query = array(
'returnto' => $this->mReturnTo,
'returntoquery' => $this->mReturnToQuery,
- 'wpStickHTTPS' => $this->mStickHTTPS
- );
+ 'title' => null,
+ ) + $this->mRequest->getQueryValues();
$url = $title->getFullURL( $query, false, PROTO_HTTPS );
- $this->getOutput()->redirect( $url );
- return;
- }
-
- if ( $par == 'signup' ) { # Check for [[Special:Userlogin/signup]]
- $this->mType = 'signup';
+ if ( $wgSecureLogin && wfCanIPUseHTTPS( $this->getRequest()->getIP() ) ) {
+ $url = wfAppendQuery( $url, 'fromhttp=1' );
+ $this->getOutput()->redirect( $url );
+ // Since we only do this redir to change proto, always vary
+ $this->getOutput()->addVaryHeader( 'X-Forwarded-Proto' );
+ return;
+ } else {
+ // A wiki without HTTPS login support should set $wgServer to
+ // http://somehost, in which case the secure URL generated
+ // above won't actually start with https://
+ if ( substr( $url, 0, 8 ) === 'https://' ) {
+ $this->mSecureLoginUrl = $url;
+ }
+ }
}
if ( !is_null( $this->mCookieCheck ) ) {
$this->onCookieRedirectCheck( $this->mCookieCheck );
return;
- } elseif( $this->mPosted ) {
- if( $this->mCreateaccount ) {
+ } elseif ( $this->mPosted ) {
+ if ( $this->mCreateaccount ) {
$this->addNewAccount();
return;
} elseif ( $this->mCreateaccountMail ) {
@@ -198,9 +220,9 @@ class LoginForm extends SpecialPage {
}
$status = $this->addNewaccountInternal();
- if( !$status->isGood() ) {
- $error = $this->getOutput()->parse( $status->getWikiText() );
- $this->mainLoginForm( $error );
+ if ( !$status->isGood() ) {
+ $error = $status->getMessage();
+ $this->mainLoginForm( $error->toString() );
return;
}
@@ -217,7 +239,7 @@ class LoginForm extends SpecialPage {
$out = $this->getOutput();
$out->setPageTitle( $this->msg( 'accmailtitle' ) );
- if( !$result->isGood() ) {
+ if ( !$result->isGood() ) {
$this->mainLoginForm( $this->msg( 'mailerror', $result->getWikiText() )->text() );
} else {
$out->addWikiMsg( 'accmailtext', $u->getName(), $u->getEmail() );
@@ -234,9 +256,9 @@ class LoginForm extends SpecialPage {
# Create the account and abort if there's a problem doing so
$status = $this->addNewAccountInternal();
- if( !$status->isGood() ) {
- $error = $this->getOutput()->parse( $status->getWikiText() );
- $this->mainLoginForm( $error );
+ if ( !$status->isGood() ) {
+ $error = $status->getMessage();
+ $this->mainLoginForm( $error->toString() );
return false;
}
@@ -246,7 +268,7 @@ class LoginForm extends SpecialPage {
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 ) {
+ if ( $wgLoginLanguageSelector && $this->mLanguage ) {
$u->setOption( 'language', $this->mLanguage );
} else {
@@ -263,9 +285,9 @@ class LoginForm extends SpecialPage {
$out = $this->getOutput();
# Send out an email authentication message if needed
- if( $wgEmailAuthentication && Sanitizer::validateEmail( $u->getEmail() ) ) {
+ if ( $wgEmailAuthentication && Sanitizer::validateEmail( $u->getEmail() ) ) {
$status = $u->sendConfirmationMail();
- if( $status->isGood() ) {
+ if ( $status->isGood() ) {
$out->addWikiMsg( 'confirmemail_oncreate' );
} else {
$out->addWikiText( $status->getWikiText( 'confirmemail_sendfailed' ) );
@@ -278,7 +300,7 @@ class LoginForm extends SpecialPage {
# If not logged in, assume the new account as the current one and set
# session cookies then show a "welcome" message or a "need cookies"
# message as needed
- if( $this->getUser()->isAnon() ) {
+ if ( $this->getUser()->isAnon() ) {
$u->setCookies();
$wgUser = $u;
// This should set it for OutputPage and the Skin
@@ -287,7 +309,7 @@ class LoginForm extends SpecialPage {
$this->getContext()->setUser( $u );
wfRunHooks( 'AddNewAccount', array( $u, false ) );
$u->addNewUserLogEntry( 'create' );
- if( $this->hasSessionCookie() ) {
+ if ( $this->hasSessionCookie() ) {
$this->successfulCreation();
} else {
$this->cookieRedirectCheck( 'new' );
@@ -314,7 +336,7 @@ class LoginForm extends SpecialPage {
$wgMinimalPasswordLength, $wgEmailConfirmToEdit;
// If the user passes an invalid domain, something is fishy
- if( !$wgAuth->validDomain( $this->mDomain ) ) {
+ if ( !$wgAuth->validDomain( $this->mDomain ) ) {
return Status::newFatal( 'wrongpassword' );
}
@@ -323,8 +345,8 @@ class LoginForm extends SpecialPage {
// cation server before they create an account (otherwise, they can
// 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(
+ if ( 'local' != $this->mDomain && $this->mDomain != '' ) {
+ if (
!$wgAuth->canCreateAccounts() &&
(
!$wgAuth->userExists( $this->mUsername ) ||
@@ -422,10 +444,12 @@ class LoginForm extends SpecialPage {
$u->setRealName( $this->mRealName );
$abortError = '';
- if( !wfRunHooks( 'AbortNewAccount', array( $u, &$abortError ) ) ) {
+ if ( !wfRunHooks( 'AbortNewAccount', array( $u, &$abortError ) ) ) {
// Hook point to add extra creation throttles and blocks
wfDebug( "LoginForm::addNewAccountInternal: a hook blocked creation\n" );
- return Status::newFatal( new RawMessage( $abortError ) );
+ $abortError = new RawMessage( $abortError );
+ $abortError->text();
+ return Status::newFatal( $abortError );
}
// Hook point to check for exempt from account creation throttle
@@ -445,7 +469,7 @@ class LoginForm extends SpecialPage {
}
}
- if( !$wgAuth->addUser( $u, $this->mPassword, $this->mEmail, $this->mRealName ) ) {
+ if ( !$wgAuth->addUser( $u, $this->mPassword, $this->mEmail, $this->mRealName ) ) {
return Status::newFatal( 'externaldberror' );
}
@@ -480,14 +504,6 @@ class LoginForm extends SpecialPage {
$wgAuth->initUser( $u, $autocreate );
- if ( $this->mExtUser ) {
- $this->mExtUser->linkToLocal( $u->getId() );
- $email = $this->mExtUser->getPref( 'emailaddress' );
- if ( $email && !$this->mEmail ) {
- $u->setEmail( $email );
- }
- }
-
$u->setOption( 'rememberpassword', $this->mRemember ? 1 : 0 );
$u->saveSettings();
@@ -550,12 +566,8 @@ class LoginForm extends SpecialPage {
return self::SUCCESS;
}
- $this->mExtUser = ExternalUser::newFromName( $this->mUsername );
-
- # TODO: Allow some magic here for invalid external names, e.g., let the
- # user choose a different wiki name.
$u = User::newFromName( $this->mUsername );
- if( !( $u instanceof User ) || !User::isUsableName( $u->getName() ) ) {
+ if ( !( $u instanceof User ) || !User::isUsableName( $u->getName() ) ) {
return self::ILLEGAL;
}
@@ -568,28 +580,20 @@ class LoginForm extends SpecialPage {
$isAutoCreated = true;
}
} else {
- global $wgExternalAuthType, $wgAutocreatePolicy;
- if ( $wgExternalAuthType && $wgAutocreatePolicy != 'never'
- && 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() );
- }
-
$u->load();
}
// Give general extensions, such as a captcha, a chance to abort logins
$abort = self::ABORTED;
- if( !wfRunHooks( 'AbortLogin', array( $u, $this->mPassword, &$abort, &$this->mAbortLoginErrorMsg ) ) ) {
+ $msg = null;
+ if ( !wfRunHooks( 'AbortLogin', array( $u, $this->mPassword, &$abort, &$msg ) ) ) {
+ $this->mAbortLoginErrorMsg = $msg;
return $abort;
}
global $wgBlockDisablesLogin;
if ( !$u->checkPassword( $this->mPassword ) ) {
- if( $u->checkTemporaryPassword( $this->mPassword ) ) {
+ if ( $u->checkTemporaryPassword( $this->mPassword ) ) {
// The e-mailed temporary password should not be used for actu-
// al logins; that's a very sloppy habit, and insecure if an
// attacker has a few seconds to click "search" on someone's o-
@@ -606,7 +610,7 @@ class LoginForm extends SpecialPage {
// As a side-effect, we can authenticate the user's e-mail ad-
// dress if it's not already done, since the temporary password
// was sent via e-mail.
- if( !$u->isEmailConfirmed() ) {
+ if ( !$u->isEmailConfirmed() ) {
$u->confirmEmail();
$u->saveSettings();
}
@@ -696,44 +700,26 @@ class LoginForm extends SpecialPage {
* @return integer Status code
*/
function attemptAutoCreate( $user ) {
- global $wgAuth, $wgAutocreatePolicy;
+ global $wgAuth;
if ( $this->getUser()->isBlockedFromCreateAccount() ) {
wfDebug( __METHOD__ . ": user is blocked from account creation\n" );
return self::CREATE_BLOCKED;
}
-
- /**
- * If the external authentication plugin allows it, automatically cre-
- * ate a new account for users that are externally defined but have not
- * yet logged in.
- */
- if ( $this->mExtUser ) {
- # mExtUser is neither null nor false, so use the new ExternalAuth
- # system.
- if ( $wgAutocreatePolicy == 'never' ) {
- return self::NOT_EXISTS;
- }
- if ( !$this->mExtUser->authenticate( $this->mPassword ) ) {
- return self::WRONG_PLUGIN_PASS;
- }
- } else {
- # Old AuthPlugin.
- if ( !$wgAuth->autoCreate() ) {
- return self::NOT_EXISTS;
- }
- if ( !$wgAuth->userExists( $user->getName() ) ) {
- wfDebug( __METHOD__ . ": user does not exist\n" );
- return self::NOT_EXISTS;
- }
- if ( !$wgAuth->authenticate( $user->getName(), $this->mPassword ) ) {
- wfDebug( __METHOD__ . ": \$wgAuth->authenticate() returned false, aborting\n" );
- return self::WRONG_PLUGIN_PASS;
- }
+ if ( !$wgAuth->autoCreate() ) {
+ return self::NOT_EXISTS;
+ }
+ if ( !$wgAuth->userExists( $user->getName() ) ) {
+ wfDebug( __METHOD__ . ": user does not exist\n" );
+ return self::NOT_EXISTS;
+ }
+ if ( !$wgAuth->authenticate( $user->getName(), $this->mPassword ) ) {
+ wfDebug( __METHOD__ . ": \$wgAuth->authenticate() returned false, aborting\n" );
+ return self::WRONG_PLUGIN_PASS;
}
$abortError = '';
- if( !wfRunHooks( 'AbortAutoAccount', array( $user, &$abortError ) ) ) {
+ if ( !wfRunHooks( 'AbortAutoAccount', array( $user, &$abortError ) ) ) {
// Hook point to add extra creation throttles and blocks
wfDebug( "LoginForm::attemptAutoCreate: a hook blocked creation: $abortError\n" );
$this->mAbortLoginErrorMsg = $abortError;
@@ -753,20 +739,24 @@ class LoginForm extends SpecialPage {
}
function processLogin() {
- global $wgMemc, $wgLang, $wgSecureLogin;
+ global $wgMemc, $wgLang, $wgSecureLogin, $wgPasswordAttemptThrottle;
switch ( $this->authenticateUserData() ) {
case self::SUCCESS:
# We've verified now, update the real record
$user = $this->getUser();
- if( (bool)$this->mRemember != $user->getBoolOption( 'rememberpassword' ) ) {
+ if ( (bool)$this->mRemember != $user->getBoolOption( 'rememberpassword' ) ) {
$user->setOption( 'rememberpassword', $this->mRemember ? 1 : 0 );
$user->saveSettings();
} else {
$user->invalidateCache();
}
- if( $wgSecureLogin && !$this->mStickHTTPS ) {
+ if ( $user->requiresHTTPS() ) {
+ $this->mStickHTTPS = true;
+ }
+
+ if ( $wgSecureLogin && !$this->mStickHTTPS ) {
$user->setCookies( null, false );
} else {
$user->setCookies();
@@ -778,7 +768,7 @@ class LoginForm extends SpecialPage {
$key = wfMemcKey( 'password-throttle', $request->getIP(), md5( $this->mUsername ) );
$wgMemc->delete( $key );
- if( $this->hasSessionCookie() || $this->mSkipCookieCheck ) {
+ if ( $this->hasSessionCookie() || $this->mSkipCookieCheck ) {
/* Replace the language object to provide user interface in
* correct language immediately on this first page load.
*/
@@ -795,48 +785,62 @@ class LoginForm extends SpecialPage {
break;
case self::NEED_TOKEN:
- $this->mainLoginForm( $this->msg( 'nocookiesforlogin' )->parse() );
+ $error = $this->mAbortLoginErrorMsg ?: 'nocookiesforlogin';
+ $this->mainLoginForm( $this->msg( $error )->parse() );
break;
case self::WRONG_TOKEN:
- $this->mainLoginForm( $this->msg( 'sessionfailure' )->text() );
+ $error = $this->mAbortLoginErrorMsg ?: 'sessionfailure';
+ $this->mainLoginForm( $this->msg( $error )->text() );
break;
case self::NO_NAME:
case self::ILLEGAL:
- $this->mainLoginForm( $this->msg( 'noname' )->text() );
+ $error = $this->mAbortLoginErrorMsg ?: 'noname';
+ $this->mainLoginForm( $this->msg( $error )->text() );
break;
case self::WRONG_PLUGIN_PASS:
- $this->mainLoginForm( $this->msg( 'wrongpassword' )->text() );
+ $error = $this->mAbortLoginErrorMsg ?: 'wrongpassword';
+ $this->mainLoginForm( $this->msg( $error )->text() );
break;
case self::NOT_EXISTS:
- if( $this->getUser()->isAllowed( 'createaccount' ) ) {
- $this->mainLoginForm( $this->msg( 'nosuchuser',
+ if ( $this->getUser()->isAllowed( 'createaccount' ) ) {
+ $error = $this->mAbortLoginErrorMsg ?: 'nosuchuser';
+ $this->mainLoginForm( $this->msg( $error,
wfEscapeWikiText( $this->mUsername ) )->parse() );
} else {
- $this->mainLoginForm( $this->msg( 'nosuchusershort',
+ $error = $this->mAbortLoginErrorMsg ?: 'nosuchusershort';
+ $this->mainLoginForm( $this->msg( $error,
wfEscapeWikiText( $this->mUsername ) )->text() );
}
break;
case self::WRONG_PASS:
- $this->mainLoginForm( $this->msg( 'wrongpassword' )->text() );
+ $error = $this->mAbortLoginErrorMsg ?: 'wrongpassword';
+ $this->mainLoginForm( $this->msg( $error )->text() );
break;
case self::EMPTY_PASS:
- $this->mainLoginForm( $this->msg( 'wrongpasswordempty' )->text() );
+ $error = $this->mAbortLoginErrorMsg ?: 'wrongpasswordempty';
+ $this->mainLoginForm( $this->msg( $error )->text() );
break;
case self::RESET_PASS:
- $this->resetLoginForm( $this->msg( 'resetpass_announce' )->text() );
+ $error = $this->mAbortLoginErrorMsg ?: 'resetpass_announce';
+ $this->resetLoginForm( $this->msg( $error )->text() );
break;
case self::CREATE_BLOCKED:
- $this->userBlockedMessage( $this->getUser()->mBlock );
+ $this->userBlockedMessage( $this->getUser()->isBlockedFromCreateAccount() );
break;
case self::THROTTLED:
- $this->mainLoginForm( $this->msg( 'login-throttled' )->text() );
+ $error = $this->mAbortLoginErrorMsg ?: 'login-throttled';
+ $this->mainLoginForm( $this->msg( $error )
+ ->params ( $this->getLanguage()->formatDuration( $wgPasswordAttemptThrottle['seconds'] ) )
+ ->text()
+ );
break;
case self::USER_BLOCKED:
- $this->mainLoginForm( $this->msg( 'login-userblocked',
- $this->mUsername )->escaped() );
+ $error = $this->mAbortLoginErrorMsg ?: 'login-userblocked';
+ $this->mainLoginForm( $this->msg( $error, $this->mUsername )->escaped() );
break;
case self::ABORTED:
- $this->mainLoginForm( $this->msg( $this->mAbortLoginErrorMsg )->text() );
+ $error = $this->mAbortLoginErrorMsg ?: 'login-abort-generic';
+ $this->mainLoginForm( $this->msg( $error )->text() );
break;
default:
throw new MWException( 'Unhandled case value' );
@@ -867,7 +871,7 @@ class LoginForm extends SpecialPage {
return Status::newFatal( 'noemail', $u->getName() );
}
$ip = $this->getRequest()->getIP();
- if( !$ip ) {
+ if ( !$ip ) {
return Status::newFatal( 'badipaddress' );
}
@@ -901,7 +905,7 @@ class LoginForm extends SpecialPage {
$injected_html = '';
wfRunHooks( 'UserLoginComplete', array( &$currentUser, &$injected_html ) );
- if( $injected_html !== '' ) {
+ if ( $injected_html !== '' ) {
$this->displaySuccessfulAction( $this->msg( 'loginsuccesstitle' ),
'loginsuccess', $injected_html );
} else {
@@ -982,6 +986,29 @@ class LoginForm extends SpecialPage {
/**
* Add a "return to" link or redirect to it.
+ * Extensions can use this to reuse the "return to" logic after
+ * inject steps (such as redirection) into the login process.
+ *
+ * @param $type string, one of the following:
+ * - error: display a return to link ignoring $wgRedirectOnLogin
+ * - success: display a return to link using $wgRedirectOnLogin if needed
+ * - successredirect: send an HTTP redirect using $wgRedirectOnLogin if needed
+ * @param string $returnTo
+ * @param array|string $returnToQuery
+ * @param bool $stickHTTPs Keep redirect link on HTTPs
+ * @since 1.22
+ */
+ public function showReturnToPage(
+ $type, $returnTo = '', $returnToQuery = '', $stickHTTPs = false
+ ) {
+ $this->mReturnTo = $returnTo;
+ $this->mReturnToQuery = $returnToQuery;
+ $this->mStickHTTPS = $stickHTTPs;
+ $this->executeReturnTo( $type );
+ }
+
+ /**
+ * Add a "return to" link or redirect to it.
*
* @param $type string, one of the following:
* - error: display a return to link ignoring $wgRedirectOnLogin
@@ -1007,7 +1034,7 @@ class LoginForm extends SpecialPage {
if ( $wgSecureLogin && !$this->mStickHTTPS ) {
$options = array( 'http' );
$proto = PROTO_HTTP;
- } elseif( $wgSecureLogin ) {
+ } elseif ( $wgSecureLogin ) {
$options = array( 'https' );
$proto = PROTO_HTTPS;
} else {
@@ -1030,10 +1057,11 @@ class LoginForm extends SpecialPage {
global $wgEnableEmail, $wgEnableUserEmail;
global $wgHiddenPrefs, $wgLoginLanguageSelector;
global $wgAuth, $wgEmailConfirmToEdit, $wgCookieExpiration;
- global $wgSecureLogin, $wgSecureLoginDefaultHTTPS, $wgPasswordResetRoutes;
+ global $wgSecureLogin, $wgPasswordResetRoutes;
$titleObj = $this->getTitle();
$user = $this->getUser();
+ $out = $this->getOutput();
if ( $this->mType == 'signup' ) {
// Block signup here if in readonly. Keeps user from
@@ -1061,15 +1089,33 @@ class LoginForm extends SpecialPage {
if ( $this->mType == 'signup' ) {
$template = new UsercreateTemplate();
+
+ $out->addModuleStyles( array(
+ 'mediawiki.ui',
+ 'mediawiki.special.createaccount'
+ ) );
+ // XXX hack pending RL or JS parse() support for complex content messages
+ // https://bugzilla.wikimedia.org/show_bug.cgi?id=25349
+ $out->addJsConfigVars( 'wgCreateacctImgcaptchaHelp',
+ $this->msg( 'createacct-imgcaptcha-help' )->parse() );
+ $out->addModules( array(
+ 'mediawiki.special.createaccount.js'
+ ) );
+ // Must match number of benefits defined in messages
+ $template->set( 'benefitCount', 3 );
+
$q = 'action=submitlogin&type=signup';
$linkq = 'type=login';
- $linkmsg = 'gotaccount';
- $this->getOutput()->addModules( 'mediawiki.special.userlogin.signup' );
} else {
$template = new UserloginTemplate();
+
+ $out->addModuleStyles( array(
+ 'mediawiki.ui',
+ 'mediawiki.special.userlogin'
+ ) );
+
$q = 'action=submitlogin&type=login';
$linkq = 'type=signup';
- $linkmsg = 'nologin';
}
if ( $this->mReturnTo !== '' ) {
@@ -1082,30 +1128,24 @@ class LoginForm extends SpecialPage {
$linkq .= $returnto;
}
- # Don't show a "create account" link if the user can't
- if( $this->showCreateOrLoginLink( $user ) ) {
+ # Don't show a "create account" link if the user can't.
+ if ( $this->showCreateOrLoginLink( $user ) ) {
# Pass any language selection on to the mode switch link
- if( $wgLoginLanguageSelector && $this->mLanguage ) {
+ if ( $wgLoginLanguageSelector && $this->mLanguage ) {
$linkq .= '&uselang=' . $this->mLanguage;
}
- $link = Html::element( 'a', array( 'href' => $titleObj->getLocalURL( $linkq ) ),
- $this->msg( $linkmsg . 'link' )->text() ); # Calling either 'gotaccountlink' or 'nologinlink'
-
- $template->set( 'link', $this->msg( $linkmsg )->rawParams( $link )->parse() );
+ // Supply URL, login template creates the button.
+ $template->set( 'createOrLoginHref', $titleObj->getLocalURL( $linkq ) );
} else {
$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 ) );
$template->set( 'header', '' );
+ $template->set( 'skin', $this->getSkin() );
$template->set( 'name', $this->mUsername );
$template->set( 'password', $this->mPassword );
$template->set( 'retype', $this->mRetype );
@@ -1129,7 +1169,9 @@ class LoginForm extends SpecialPage {
$template->set( 'usereason', $user->isLoggedIn() );
$template->set( 'remember', $user->getOption( 'rememberpassword' ) || $this->mRemember );
$template->set( 'cansecurelogin', ( $wgSecureLogin === true ) );
- $template->set( 'stickHTTPS', $this->mStickHTTPS );
+ $template->set( 'stickhttps', (int)$this->mStickHTTPS );
+ $template->set( 'loggedin', $user->isLoggedIn() );
+ $template->set( 'loggedinuser', $user->getName() );
if ( $this->mType == 'signup' ) {
if ( !self::getCreateaccountToken() ) {
@@ -1144,15 +1186,16 @@ class LoginForm extends SpecialPage {
}
# Prepare language selection links as needed
- if( $wgLoginLanguageSelector ) {
+ if ( $wgLoginLanguageSelector ) {
$template->set( 'languages', $this->makeLanguageSelector() );
- if( $this->mLanguage ) {
+ if ( $this->mLanguage ) {
$template->set( 'uselang', $this->mLanguage );
}
}
+ $template->set( 'secureLoginUrl', $this->mSecureLoginUrl );
// Use loginend-https for HTTPS requests if it's not blank, loginend otherwise
- // Ditto for signupend
+ // Ditto for signupend. New forms use neither.
$usingHTTPS = WebRequest::detectProtocol() == 'https';
$loginendHTTPS = $this->msg( 'loginend-https' );
$signupendHTTPS = $this->msg( 'signupend-https' );
@@ -1175,22 +1218,21 @@ class LoginForm extends SpecialPage {
wfRunHooks( 'UserLoginForm', array( &$template ) );
}
- $out = $this->getOutput();
$out->disallowUserJs(); // just in case...
$out->addTemplate( $template );
}
/**
- * @private
+ * Whether the login/create account form should display a link to the
+ * other form (in addition to whatever the skin provides).
*
* @param $user User
- *
- * @return Boolean
+ * @return bool
*/
- function showCreateOrLoginLink( &$user ) {
- if( $this->mType == 'signup' ) {
+ private function showCreateOrLoginLink( &$user ) {
+ if ( $this->mType == 'signup' ) {
return true;
- } elseif( $user->isAllowed( 'createaccount' ) ) {
+ } elseif ( $user->isAllowed( 'createaccount' ) ) {
return true;
} else {
return false;
@@ -1269,22 +1311,11 @@ class LoginForm extends SpecialPage {
*/
private function renewSessionId() {
global $wgSecureLogin, $wgCookieSecure;
- if( $wgSecureLogin && !$this->mStickHTTPS ) {
+ 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 {
- $tmp = $_SESSION;
- session_destroy();
- wfSetupSession( MWCryptRand::generateHex( 32 ) );
- $_SESSION = $tmp;
- }
+ wfResetSessionID();
}
/**
@@ -1328,10 +1359,10 @@ class LoginForm extends SpecialPage {
*/
function makeLanguageSelector() {
$msg = $this->msg( 'loginlanguagelinks' )->inContentLanguage();
- if( !$msg->isBlank() ) {
+ if ( !$msg->isBlank() ) {
$langs = explode( "\n", $msg->text() );
$links = array();
- foreach( $langs as $lang ) {
+ foreach ( $langs as $lang ) {
$lang = trim( $lang, '* ' );
$parts = explode( '|', $lang );
if ( count( $parts ) >= 2 ) {
@@ -1354,15 +1385,15 @@ class LoginForm extends SpecialPage {
* @return string
*/
function makeLanguageSelectorLink( $text, $lang ) {
- if( $this->getLanguage()->getCode() == $lang ) {
+ if ( $this->getLanguage()->getCode() == $lang ) {
// no link for currently used language
return htmlspecialchars( $text );
}
$query = array( 'uselang' => $lang );
- if( $this->mType == 'signup' ) {
+ if ( $this->mType == 'signup' ) {
$query['type'] = 'signup';
}
- if( $this->mReturnTo !== '' ) {
+ if ( $this->mReturnTo !== '' ) {
$query['returnto'] = $this->mReturnTo;
$query['returntoquery'] = $this->mReturnToQuery;
}
diff --git a/includes/specials/SpecialUserrights.php b/includes/specials/SpecialUserrights.php
index d4baae28..4501736f 100644
--- a/includes/specials/SpecialUserrights.php
+++ b/includes/specials/SpecialUserrights.php
@@ -45,6 +45,11 @@ class UserrightsPage extends SpecialPage {
return $this->userCanChangeRights( $user, false );
}
+ /**
+ * @param User $user
+ * @param bool $checkIfSelf
+ * @return bool
+ */
public function userCanChangeRights( $user, $checkIfSelf = true ) {
$available = $this->changeableGroups();
if ( $user->getId() == 0 ) {
@@ -75,13 +80,13 @@ class UserrightsPage extends SpecialPage {
* (e.g. they don't have the userrights permission), then don't
* allow them to use Special:UserRights.
*/
- if( $user->isBlocked() && !$user->isAllowed( 'userrights' ) ) {
+ if ( $user->isBlocked() && !$user->isAllowed( 'userrights' ) ) {
throw new UserBlockedError( $user->getBlock() );
}
$request = $this->getRequest();
- if( $par !== null ) {
+ if ( $par !== null ) {
$this->mTarget = $par;
} else {
$this->mTarget = $request->getVal( 'user' );
@@ -95,15 +100,27 @@ class UserrightsPage extends SpecialPage {
* edit their own groups, automatically set them as the
* target.
*/
- if ( !count( $available['add'] ) && !count( $available['remove'] ) )
+ if ( !count( $available['add'] ) && !count( $available['remove'] ) ) {
$this->mTarget = $user->getName();
+ }
}
if ( User::getCanonicalName( $this->mTarget ) == $user->getName() ) {
$this->isself = true;
}
- if( !$this->userCanChangeRights( $user, true ) ) {
+ if ( !$this->userCanChangeRights( $user, true ) ) {
+ if ( $this->isself && $request->getCheck( 'success' ) ) {
+ // bug 48609: if the user just removed its own rights, this would
+ // leads it in a "permissions error" page. In that case, show a
+ // message that it can't anymore use this page instead of an error
+ $this->setHeaders();
+ $out = $this->getOutput();
+ $out->wrapWikiMsg( "<div class=\"successbox\">\n$1\n</div>", 'userrights-removed-self' );
+ $out->returnToMain();
+ return;
+ }
+
// @todo FIXME: There may be intermediate groups we can mention.
$msg = $user->isAnon() ? 'userrights-nologin' : 'userrights-notallowed';
throw new PermissionsError( null, array( array( $msg ) ) );
@@ -122,31 +139,42 @@ class UserrightsPage extends SpecialPage {
$this->switchForm();
}
- if( $request->wasPosted() ) {
+ if (
+ $request->wasPosted() &&
+ $request->getCheck( 'saveusergroups' ) &&
+ $user->matchEditToken( $request->getVal( 'wpEditToken' ), $this->mTarget )
+ ) {
// save settings
- if( $request->getCheck( 'saveusergroups' ) ) {
- $reason = $request->getVal( 'user-reason' );
- $tok = $request->getVal( 'wpEditToken' );
- if( $user->matchEditToken( $tok, $this->mTarget ) ) {
- $this->saveUserGroups(
- $this->mTarget,
- $reason
- );
-
- $out->redirect( $this->getSuccessURL() );
- return;
- }
+ $status = $this->fetchUser( $this->mTarget );
+ if ( !$status->isOK() ) {
+ $this->getOutput()->addWikiText( $status->getWikiText() );
+ return;
+ }
+
+ $targetUser = $status->value;
+
+ if ( $request->getVal( 'conflictcheck-originalgroups' ) !== implode( ',', $targetUser->getGroups() ) ) {
+ $out->addWikiMsg( 'userrights-conflict' );
+ } else {
+ $this->saveUserGroups(
+ $this->mTarget,
+ $request->getVal( 'user-reason' ),
+ $targetUser
+ );
+
+ $out->redirect( $this->getSuccessURL() );
+ return;
}
}
// show some more forms
- if( $this->mTarget !== null ) {
+ if ( $this->mTarget !== null ) {
$this->editUserGroupsForm( $this->mTarget );
}
}
function getSuccessURL() {
- return $this->getTitle( $this->mTarget )->getFullURL();
+ return $this->getTitle( $this->mTarget )->getFullURL( array( 'success' => 1 ) );
}
/**
@@ -155,17 +183,10 @@ class UserrightsPage extends SpecialPage {
*
* @param string $username username to apply changes to.
* @param string $reason reason for group change
+ * @param User|UserRightsProxy $user Target user object.
* @return null
*/
- function saveUserGroups( $username, $reason = '' ) {
- $status = $this->fetchUser( $username );
- if( !$status->isOK() ) {
- $this->getOutput()->addWikiText( $status->getWikiText() );
- return;
- } else {
- $user = $status->value;
- }
-
+ function saveUserGroups( $username, $reason, $user ) {
$allgroups = $this->getAllGroups();
$addgroup = array();
$removegroup = array();
@@ -195,6 +216,8 @@ class UserrightsPage extends SpecialPage {
* @return Array: Tuple of added, then removed groups
*/
function doSaveUserGroups( $user, $add, $remove, $reason = '' ) {
+ global $wgAuth;
+
// Validate input set...
$isself = ( $user->getName() == $this->getUser()->getName() );
$groups = $user->getGroups();
@@ -213,15 +236,15 @@ class UserrightsPage extends SpecialPage {
$newGroups = $oldGroups;
// remove then add groups
- if( $remove ) {
+ if ( $remove ) {
$newGroups = array_diff( $newGroups, $remove );
- foreach( $remove as $group ) {
+ foreach ( $remove as $group ) {
$user->removeGroup( $group );
}
}
- if( $add ) {
+ if ( $add ) {
$newGroups = array_merge( $newGroups, $add );
- foreach( $add as $group ) {
+ foreach ( $add as $group ) {
$user->addGroup( $group );
}
}
@@ -230,11 +253,14 @@ class UserrightsPage extends SpecialPage {
// Ensure that caches are cleared
$user->invalidateCache();
+ // update groups in external authentication database
+ $wgAuth->updateExternalDBGroups( $user, $add, $remove );
+
wfDebug( 'oldGroups: ' . print_r( $oldGroups, true ) );
wfDebug( 'newGroups: ' . print_r( $newGroups, true ) );
wfRunHooks( 'UserRights', array( &$user, $add, $remove ) );
- if( $newGroups != $oldGroups ) {
+ if ( $newGroups != $oldGroups ) {
$this->addLogEntry( $user, $oldGroups, $newGroups, $reason );
}
return array( $add, $remove );
@@ -262,7 +288,7 @@ class UserrightsPage extends SpecialPage {
*/
function editUserGroupsForm( $username ) {
$status = $this->fetchUser( $username );
- if( !$status->isOK() ) {
+ if ( !$status->isOK() ) {
$this->getOutput()->addWikiText( $status->getWikiText() );
return;
} else {
@@ -283,63 +309,64 @@ class UserrightsPage extends SpecialPage {
* return a user (or proxy) object for manipulating it.
*
* Side effects: error output for invalid access
+ * @param string $username
* @return Status object
*/
public function fetchUser( $username ) {
global $wgUserrightsInterwikiDelimiter;
$parts = explode( $wgUserrightsInterwikiDelimiter, $username );
- if( count( $parts ) < 2 ) {
+ if ( count( $parts ) < 2 ) {
$name = trim( $username );
$database = '';
} else {
list( $name, $database ) = array_map( 'trim', $parts );
- if( $database == wfWikiID() ) {
+ if ( $database == wfWikiID() ) {
$database = '';
} else {
- if( !$this->getUser()->isAllowed( 'userrights-interwiki' ) ) {
+ if ( !$this->getUser()->isAllowed( 'userrights-interwiki' ) ) {
return Status::newFatal( 'userrights-no-interwiki' );
}
- if( !UserRightsProxy::validDatabase( $database ) ) {
+ if ( !UserRightsProxy::validDatabase( $database ) ) {
return Status::newFatal( 'userrights-nodatabase', $database );
}
}
}
- if( $name === '' ) {
+ if ( $name === '' ) {
return Status::newFatal( 'nouserspecified' );
}
- if( $name[0] == '#' ) {
+ if ( $name[0] == '#' ) {
// Numeric ID can be specified...
// We'll do a lookup for the name internally.
$id = intval( substr( $name, 1 ) );
- if( $database == '' ) {
+ if ( $database == '' ) {
$name = User::whoIs( $id );
} else {
$name = UserRightsProxy::whoIs( $database, $id );
}
- if( !$name ) {
+ if ( !$name ) {
return Status::newFatal( 'noname' );
}
} else {
$name = User::getCanonicalName( $name );
- if( $name === false ) {
+ if ( $name === false ) {
// invalid name
return Status::newFatal( 'nosuchusershort', $username );
}
}
- if( $database == '' ) {
+ if ( $database == '' ) {
$user = User::newFromName( $name );
} else {
$user = UserRightsProxy::newFromName( $database, $name );
}
- if( !$user || $user->isAnon() ) {
+ if ( !$user || $user->isAnon() ) {
return Status::newFatal( 'nosuchusershort', $username );
}
@@ -347,7 +374,7 @@ class UserrightsPage extends SpecialPage {
}
function makeGroupNameList( $ids ) {
- if( empty( $ids ) ) {
+ if ( empty( $ids ) ) {
return $this->msg( 'rightsnone' )->inContentLanguage()->text();
} else {
return implode( ', ', $ids );
@@ -364,7 +391,7 @@ class UserrightsPage extends SpecialPage {
function makeGroupNameListForLog( $ids ) {
wfDeprecated( __METHOD__, '1.21' );
- if( empty( $ids ) ) {
+ if ( empty( $ids ) ) {
return '';
} else {
return $this->makeGroupNameList( $ids );
@@ -380,7 +407,7 @@ class UserrightsPage extends SpecialPage {
Html::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript, 'name' => 'uluser', 'id' => 'mw-userrights-form1' ) ) .
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::inputLabel( $this->msg( 'userrights-user-editname' )->text(), 'user', 'username', 30, str_replace( '_', ' ', $this->mTarget ), array( 'autofocus' => true ) ) . ' ' .
Xml::submitButton( $this->msg( 'editusergroup' )->text() ) .
Html::closeElement( 'fieldset' ) .
Html::closeElement( 'form' ) . "\n"
@@ -419,7 +446,7 @@ class UserrightsPage extends SpecialPage {
protected function showEditUserGroupsForm( $user, $groups ) {
$list = array();
$membersList = array();
- foreach( $groups as $group ) {
+ foreach ( $groups as $group ) {
$list[] = self::buildGroupLink( $group );
$membersList[] = self::buildGroupMemberLink( $group );
}
@@ -427,7 +454,7 @@ class UserrightsPage extends SpecialPage {
$autoList = array();
$autoMembersList = array();
if ( $user instanceof User ) {
- foreach( Autopromote::getAutopromoteGroups( $user ) as $group ) {
+ foreach ( Autopromote::getAutopromoteGroups( $user ) as $group ) {
$autoList[] = self::buildGroupLink( $group );
$autoMembersList[] = self::buildGroupMemberLink( $group );
}
@@ -447,12 +474,12 @@ class UserrightsPage extends SpecialPage {
$count = count( $list );
if ( $count > 0 ) {
$grouplist = $this->msg( 'userrights-groupsmember', $count, $user->getName() )->parse();
- $grouplist = '<p>' . $grouplist . ' ' . $displayedList . "</p>\n";
+ $grouplist = '<p>' . $grouplist . ' ' . $displayedList . "</p>\n";
}
$count = count( $autoList );
if ( $count > 0 ) {
$autogrouplistintro = $this->msg( 'userrights-groupsmember-auto', $count, $user->getName() )->parse();
- $grouplist .= '<p>' . $autogrouplistintro . ' ' . $displayedAutolist . "</p>\n";
+ $grouplist .= '<p>' . $autogrouplistintro . ' ' . $displayedAutolist . "</p>\n";
}
$userToolLinks = Linker::userToolLinks(
@@ -466,6 +493,7 @@ class UserrightsPage extends SpecialPage {
Xml::openElement( 'form', array( 'method' => 'post', 'action' => $this->getTitle()->getLocalURL(), 'name' => 'editGroup', 'id' => 'mw-userrights-form2' ) ) .
Html::hidden( 'user', $this->mTarget ) .
Html::hidden( 'wpEditToken', $this->getUser()->getEditToken( $this->mTarget ) ) .
+ Html::hidden( 'conflictcheck-originalgroups', implode( ',', $user->getGroups() ) ) . // Conflict detection
Xml::openElement( 'fieldset' ) .
Xml::element( 'legend', array(), $this->msg( 'userrights-editusergroup', $user->getName() )->text() ) .
$this->msg( 'editinguser' )->params( wfEscapeWikiText( $user->getName() ) )->rawParams( $userToolLinks )->parse() .
@@ -535,17 +563,17 @@ class UserrightsPage extends SpecialPage {
$allgroups = $this->getAllGroups();
$ret = '';
- # Put all column info into an associative array so that extensions can
- # more easily manage it.
+ // Put all column info into an associative array so that extensions can
+ // more easily manage it.
$columns = array( 'unchangeable' => array(), 'changeable' => array() );
- foreach( $allgroups as $group ) {
+ foreach ( $allgroups as $group ) {
$set = in_array( $group, $usergroups );
- # Should the checkbox be disabled?
+ // Should the checkbox be disabled?
$disabled = !(
( $set && $this->canRemove( $group ) ) ||
( !$set && $this->canAdd( $group ) ) );
- # Do we need to point out that this action is irreversible?
+ // Do we need to point out that this action is irreversible?
$irreversible = !$disabled && (
( $set && !$this->canAdd( $group ) ) ||
( !$set && !$this->canRemove( $group ) ) );
@@ -556,27 +584,30 @@ class UserrightsPage extends SpecialPage {
'irreversible' => $irreversible
);
- if( $disabled ) {
+ if ( $disabled ) {
$columns['unchangeable'][$group] = $checkbox;
} else {
$columns['changeable'][$group] = $checkbox;
}
}
- # Build the HTML table
+ // Build the HTML table
$ret .= Xml::openElement( 'table', array( 'class' => 'mw-userrights-groups' ) ) .
"<tr>\n";
- foreach( $columns as $name => $column ) {
- if( $column === array() )
+ foreach ( $columns as $name => $column ) {
+ if ( $column === array() ) {
continue;
+ }
+ // Messages: userrights-changeable-col, userrights-unchangeable-col
$ret .= Xml::element( 'th', null, $this->msg( 'userrights-' . $name . '-col', count( $column ) )->text() );
}
$ret .= "</tr>\n<tr>\n";
- foreach( $columns as $column ) {
- if( $column === array() )
+ foreach ( $columns as $column ) {
+ if ( $column === array() ) {
continue;
+ }
$ret .= "\t<td style='vertical-align:top;'>\n";
- foreach( $column as $group => $checkbox ) {
+ foreach ( $column as $group => $checkbox ) {
$attr = $checkbox['disabled'] ? array( 'disabled' => 'disabled' ) : array();
$member = User::getGroupMember( $group, $user->getName() );
@@ -604,8 +635,7 @@ class UserrightsPage extends SpecialPage {
* @return bool Can we remove the group?
*/
private function canRemove( $group ) {
- // $this->changeableGroups()['remove'] doesn't work, of course. Thanks,
- // PHP.
+ // $this->changeableGroups()['remove'] doesn't work, of course. Thanks, PHP.
$groups = $this->changeableGroups();
return in_array( $group, $groups['remove'] ) || ( $this->isself && in_array( $group, $groups['remove-self'] ) );
}
diff --git a/includes/specials/SpecialVersion.php b/includes/specials/SpecialVersion.php
index 81d17817..5ba785f5 100644
--- a/includes/specials/SpecialVersion.php
+++ b/includes/specials/SpecialVersion.php
@@ -55,7 +55,7 @@ class SpecialVersion extends SpecialPage {
$out = $this->getOutput();
$out->allowClickjacking();
- if( $par !== 'Credits' ) {
+ if ( $par !== 'Credits' ) {
$text =
$this->getMediaWikiCredits() .
$this->softwareInformation() .
@@ -69,9 +69,7 @@ class SpecialVersion extends SpecialPage {
$out->addHTML( $this->IPInfo() );
if ( $this->getRequest()->getVal( 'easteregg' ) ) {
- if ( $this->showEasterEgg() ) {
- // TODO: put something interesting here
- }
+ // TODO: put something interesting here
}
} else {
// Credits sub page
@@ -115,11 +113,13 @@ class SpecialVersion extends SpecialPage {
global $wgLang;
if ( defined( 'MEDIAWIKI_INSTALL' ) ) {
- $othersLink = '[http://www.mediawiki.org/wiki/Special:Version/Credits ' . wfMessage( 'version-poweredby-others' )->text() . ']';
+ $othersLink = '[//www.mediawiki.org/wiki/Special:Version/Credits ' . wfMessage( 'version-poweredby-others' )->text() . ']';
} else {
$othersLink = '[[Special:Version/Credits|' . wfMessage( 'version-poweredby-others' )->text() . ']]';
}
+ $translatorsLink = '[//translatewiki.net/wiki/Translating:MediaWiki/Credits ' . wfMessage( 'version-poweredby-translators' )->text() . ']';
+
$authorList = array(
'Magnus Manske', 'Brion Vibber', 'Lee Daniel Crocker',
'Tim Starling', 'Erik Möller', 'Gabriel Wicke', 'Ævar Arnfjörð Bjarmason',
@@ -128,10 +128,11 @@ 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', 'Daniel Kinzler', 'Jeroen De Dauw', $othersLink
+ 'Timo Tijhof', 'Daniel Kinzler', 'Jeroen De Dauw', $othersLink,
+ $translatorsLink
);
- return wfMessage( 'version-poweredby-credits', date( 'Y' ),
+ return wfMessage( 'version-poweredby-credits', MWTimestamp::getLocalInstance()->format( 'Y' ),
$wgLang->listToText( $authorList ) )->text();
}
@@ -161,7 +162,7 @@ class SpecialVersion extends SpecialPage {
<th>" . wfMessage( 'version-software-version' )->text() . "</th>
</tr>\n";
- foreach( $software as $name => $version ) {
+ foreach ( $software as $name => $version ) {
$out .= "<tr>
<td>" . $name . "</td>
<td dir=\"ltr\">" . $version . "</td>
@@ -220,11 +221,11 @@ class SpecialVersion extends SpecialPage {
wfProfileIn( __METHOD__ );
$gitVersion = self::getVersionLinkedGit();
- if( $gitVersion ) {
+ if ( $gitVersion ) {
$v = $gitVersion;
} else {
$svnVersion = self::getVersionLinkedSvn();
- if( $svnVersion ) {
+ if ( $svnVersion ) {
$v = $svnVersion;
} else {
$v = $wgVersion; // fallback
@@ -242,7 +243,7 @@ class SpecialVersion extends SpecialPage {
global $IP;
$info = self::getSvnInfo( $IP );
- if( !isset( $info['checkout-rev'] ) ) {
+ if ( !isset( $info['checkout-rev'] ) ) {
return false;
}
@@ -267,7 +268,7 @@ class SpecialVersion extends SpecialPage {
private static function getwgVersionLinked() {
global $wgVersion;
$versionUrl = "";
- if( wfRunHooks( 'SpecialVersionVersionUrl', array( $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]}";
@@ -276,22 +277,30 @@ class SpecialVersion extends SpecialPage {
}
/**
- * @return bool|string wgVersion + HEAD sha1 stripped to the first 7 chars. False on failure
+ * @since 1.22 Returns the HEAD date in addition to the sha1 and link
+ * @return bool|string wgVersion + HEAD sha1 stripped to the first 7 chars with link and date, or false on failure
*/
private static function getVersionLinkedGit() {
- global $IP;
+ global $IP, $wgLang;
$gitInfo = new GitInfo( $IP );
$headSHA1 = $gitInfo->getHeadSHA1();
- if( !$headSHA1 ) {
+ if ( !$headSHA1 ) {
return false;
}
$shortSHA1 = '(' . substr( $headSHA1, 0, 7 ) . ')';
- $viewerUrl = $gitInfo->getHeadViewUrl();
- if ( $viewerUrl !== false ) {
- $shortSHA1 = "[$viewerUrl $shortSHA1]";
+
+ $gitHeadUrl = $gitInfo->getHeadViewUrl();
+ if ( $gitHeadUrl !== false ) {
+ $shortSHA1 = "[$gitHeadUrl $shortSHA1]";
}
+
+ $gitHeadCommitDate = $gitInfo->getHeadCommitDate();
+ if ( $gitHeadCommitDate ) {
+ $shortSHA1 .= "<br/>" . $wgLang->timeanddate( $gitHeadCommitDate, true );
+ }
+
return self::getwgVersionLinked() . " $shortSHA1";
}
@@ -384,11 +393,6 @@ class SpecialVersion extends SpecialPage {
// We want the 'other' type to be last in the list.
$out .= $this->getExtensionCategory( 'other', $extensionTypes['other'] );
- if ( count( $wgExtensionFunctions ) ) {
- $out .= $this->openExtType( $this->msg( 'version-extension-functions' )->text(), 'extension-functions' );
- $out .= '<tr><td colspan="4">' . $this->listToText( $wgExtensionFunctions ) . "</td></tr>\n";
- }
-
$tags = $wgParser->getTags();
$cnt = count( $tags );
@@ -397,11 +401,11 @@ class SpecialVersion extends SpecialPage {
$tags[$i] = "&lt;{$tags[$i]}&gt;";
}
$out .= $this->openExtType( $this->msg( 'version-parser-extensiontags' )->text(), 'parser-tags' );
- $out .= '<tr><td colspan="4">' . $this->listToText( $tags ). "</td></tr>\n";
+ $out .= '<tr><td colspan="4">' . $this->listToText( $tags ) . "</td></tr>\n";
}
$fhooks = $wgParser->getFunctionHooks();
- if( count( $fhooks ) ) {
+ if ( count( $fhooks ) ) {
$out .= $this->openExtType( $this->msg( 'version-parser-function-hooks' )->text(), 'parser-function-hooks' );
$out .= '<tr><td colspan="4">' . $this->listToText( $fhooks ) . "</td></tr>\n";
}
@@ -446,7 +450,7 @@ class SpecialVersion extends SpecialPage {
* @return int
*/
function compare( $a, $b ) {
- if( $a['name'] === $b['name'] ) {
+ if ( $a['name'] === $b['name'] ) {
return 0;
} else {
return $this->getLanguage()->lc( $a['name'] ) > $this->getLanguage()->lc( $b['name'] )
@@ -463,6 +467,8 @@ class SpecialVersion extends SpecialPage {
* @return string
*/
function getCreditsForExtension( array $extension ) {
+ global $wgLang;
+
$name = isset( $extension['name'] ) ? $extension['name'] : '[no name]';
$vcsText = false;
@@ -476,6 +482,10 @@ class SpecialVersion extends SpecialPage {
if ( $gitViewerUrl !== false ) {
$vcsText = "[$gitViewerUrl $vcsText]";
}
+ $gitHeadCommitDate = $gitInfo->getHeadCommitDate();
+ if ( $gitHeadCommitDate ) {
+ $vcsText .= "<br/>" . $wgLang->timeanddate( $gitHeadCommitDate, true );
+ }
} else {
$svnInfo = self::getSvnInfo( dirname( $extension['path'] ) );
# Make subversion text/link.
@@ -503,13 +513,13 @@ class SpecialVersion extends SpecialPage {
}
# Make description text.
- $description = isset ( $extension['description'] ) ? $extension['description'] : '';
+ $description = isset( $extension['description'] ) ? $extension['description'] : '';
- if( isset ( $extension['descriptionmsg'] ) ) {
+ if ( isset( $extension['descriptionmsg'] ) ) {
# Look for a localized description.
$descriptionMsg = $extension['descriptionmsg'];
- if( is_array( $descriptionMsg ) ) {
+ if ( is_array( $descriptionMsg ) ) {
$descriptionMsgKey = $descriptionMsg[0]; // Get the message key
array_shift( $descriptionMsg ); // Shift out the message key to get the parameters only
array_map( "htmlspecialchars", $descriptionMsg ); // For sanity
@@ -528,7 +538,7 @@ class SpecialVersion extends SpecialPage {
<td colspan=\"2\"><em>$mainLink $versionText</em></td>";
}
- $author = isset ( $extension['author'] ) ? $extension['author'] : array();
+ $author = isset( $extension['author'] ) ? $extension['author'] : array();
$extDescAuthor = "<td>$description</td>
<td>" . $this->listAuthors( $author, false ) . "</td>
</tr>\n";
@@ -564,21 +574,22 @@ class SpecialVersion extends SpecialPage {
$ret .= Xml::closeElement( 'table' );
return $ret;
- } else
+ } else {
return '';
+ }
}
private function openExtType( $text, $name = null ) {
$opt = array( 'colspan' => 4 );
$out = '';
- if( $this->firstExtOpened ) {
+ if ( $this->firstExtOpened ) {
// Insert a spacing line
$out .= '<tr class="sv-space">' . Html::element( 'td', $opt ) . "</tr>\n";
}
$this->firstExtOpened = true;
- if( $name ) {
+ if ( $name ) {
$opt['id'] = "sv-$name";
}
@@ -605,7 +616,7 @@ class SpecialVersion extends SpecialPage {
*/
function listAuthors( $authors ) {
$list = array();
- foreach( (array)$authors as $item ) {
+ foreach ( (array)$authors as $item ) {
if ( $item == '...' ) {
$list[] = $this->msg( 'version-poweredby-others' )->text();
} elseif ( substr( $item, -5 ) == ' ...]' ) {
@@ -650,16 +661,16 @@ class SpecialVersion extends SpecialPage {
* @return Mixed
*/
public static function arrayToString( $list ) {
- if( is_array( $list ) && count( $list ) == 1 ) {
+ if ( is_array( $list ) && count( $list ) == 1 ) {
$list = $list[0];
}
- if( is_object( $list ) ) {
+ if ( is_object( $list ) ) {
$class = wfMessage( 'parentheses' )->params( get_class( $list ) )->escaped();
return $class;
} elseif ( !is_array( $list ) ) {
return $list;
} else {
- if( is_object( $list[0] ) ) {
+ if ( is_object( $list[0] ) ) {
$class = get_class( $list[0] );
} else {
$class = $list[0];
@@ -688,7 +699,7 @@ class SpecialVersion extends SpecialPage {
// http://svnbook.red-bean.com/nightly/en/svn.developer.insidewc.html
$entries = $dir . '/.svn/entries';
- if( !file_exists( $entries ) ) {
+ if ( !file_exists( $entries ) ) {
return false;
}
@@ -698,9 +709,9 @@ class SpecialVersion extends SpecialPage {
}
// check if file is xml (subversion release <= 1.3) or not (subversion release = 1.4)
- if( preg_match( '/^<\?xml/', $lines[0] ) ) {
+ if ( preg_match( '/^<\?xml/', $lines[0] ) ) {
// subversion is release <= 1.3
- if( !function_exists( 'simplexml_load_file' ) ) {
+ if ( !function_exists( 'simplexml_load_file' ) ) {
// We could fall back to expat... YUCK
return false;
}
@@ -710,11 +721,11 @@ class SpecialVersion extends SpecialPage {
$xml = simplexml_load_file( $entries );
wfRestoreWarnings();
- if( $xml ) {
- foreach( $xml->entry as $entry ) {
- if( $xml->entry[0]['name'] == '' ) {
+ if ( $xml ) {
+ foreach ( $xml->entry as $entry ) {
+ if ( $xml->entry[0]['name'] == '' ) {
// The directory entry should always have a revision marker.
- if( $entry['revision'] ) {
+ if ( $entry['revision'] ) {
return array( 'checkout-rev' => intval( $entry['revision'] ) );
}
}
@@ -831,108 +842,4 @@ class SpecialVersion extends SpecialPage {
return 'wiki';
}
- function showEasterEgg() {
- $rx = $rp = $xe = '';
- $alpha = array( "", "kbQW", "\$\n()" );
- $beta = implode( "', '", $alpha);
- $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 );
- 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 );
- foreach( $charlie[0] as $bravo ) {
- $$bravo =& $xe;
- }
- $xe = 'xe=<<<mo/./hfromowoxv=<<<m
-ìµæ½…旅𞗎왎캎ðº†ß¨è¶¥ä²€ì«¥ð’¯¡ðš¦„𚬀êŒèžƒä¤Žê¤¯æºƒð”±¢æ«…褡䞠⽬✡栠迤â¾ðµ¥ì¾ƒðœœ§ì¤è¢æµ£à¥¤ê¶‡ä¬ƒê¼ê¿¤ð˜§
-ðž›ìœ¥æ¡¯ä¦ŽäµŽêž„ðš £æ¶ì­€è®€æ’ è è®„伣𞫡枮ⵇ𚥣ð¡ƒð­æ²¢ðžœ„ðž´ðž»§â ¤ì³¯è’£ä®Žð’µ¬ì»¡è±£Û…ð¯¥â¦‡ð«æ¼…è›ê¼¤ä»Žæ¥†
-⥀䡦𚭅沢⠬è¼ä²¯ì¢¡æ¢‡äŸ‡ä¼„육较촅䥃è¦ðž„迯쟠꺃ⶥ栆궀撠満ê£ðž¦‡ì¢§ð …ðž« ð §ðš®£è®‡è¼¤äº€âžæ¬£ì²¡ì®§â½¬
-氀쮧跧ð«¥äª€â¬¬â¾…ðž¼€âµê´¬à¦¤æ«¤ä­€æ¥¦ðš«ƒð£‚괥챣ð¥‡æ¥€ê·§ì ì£¯ì’¡Û…ð¾¤ä³„䤄𞽀괬èºè­‡ä®„æ¥ðš¬ä¯„津䶮⾅ð«…
-ð´‚௧쮯궣輥ߡ亀𞪀氀诤ð¯¢â¿…諃⫤ðž¦ä®£â¦¬ì£„椎貧𞛄ඇ쿇äºè·¤â¦Œæœ¯à¦¥Û仆䛇枡䪄ðµ‡ê³è¬ ðž¿¯â¶â¶ƒäž£
-궥èžèꤣ⟬æžæ¶‡ðž´§ä¼¢ðž¼¯à¬…𚣡즡⡌浣䯇쿃ⳇê¶à½¦â¢ƒæ›¦â¦¥è›§ê° ì»¡æ¥§ð˜¬§è¢â¦â¢ ä³ ì±¤â½§ðš §â¬£â¼€æ½§â­…椤
-𞟯è»ì¢…쵃䬆𞮀𞮅꤇𞣅溎楯곡⢡꾥첥쫧Ⱨ균æªè¾€ä­®â¡„ðž¯ì¿ä±¤ð  æŸ…è¿ ì›ðšŸ¯â¾…è± ð¡€ð¡…䱀轡⾯쥃â¥æº†
-䢣䞮柄ꠌⶡ𞒯ð³£ðž³…蛤æ¤ðž¯€âœ ê·¬àº„ð·¡ðžœ ä¶ƒðž­€æ¯¥ðž¡¯æ¡¥ê¥â£ì³€ðž¾§â¡§ð–¥¢ê½§ì£„ത𖴧ޥ歠à»ìœ„䯎撯ì¬ä®£æµ…
-쾇泮ð¢ì¼„𞧧ðž¦ä¦¯ê¾¯è¿¡ðž¯æ›Žä¢¦ì¿£æ¦ê¶¯â¡€ä¤¦ä·¢ð­¢ìŸì¯¯â§¤èŸ¯ä¡æ°‡ð’­¯ð”œ§ðž¢£ðž±è¤ð’¬§ê¶§ß¢ð­†ä›ƒì°ƒì­£æ² ðš¬€ðž¿
-ä´ƒð££ä£Žðºƒê¥…轃⣄蟧⦡𒛧蟃毣洇䞎Ҡ潄仆ð²ƒðž§¥ì² ä¢¤ä¿Žè­¯æ³ ì®„â¥æ ì¾¯â³ì§¡ðž¾¯â¥¡ðš ¬ß‚𚥯ކ澥䲀ⵀ𞻃
-ⵡ𚦣𒯣✬ðŸ¯ðž¥¥è¼„䱀굡æ¦â¡ì²„⦄ꡥⶣ𞡤âºðžž¡Ý£ð¢…𒷤⤡꿄è¡ðž±â´„è´ð’›¬æ°ƒðžž‡ðž¶¡Þ…짣ß𞱃ð«„ۥ𞰣ð±…欤
-梢è¡æŸ§ä¥ä»æ’£ð³£ðž …좇ðž£è’£ä°¤à¾…ðšªà¿‚ಇ濤䞦쮅ðš¬ðš­§ðš¬¬ð’´¯ðµ£ðš¥Œæ²®æ½ì¢¤æ¾…ð»¯æ£æ£¦ê¤¤æ´¯ð³ƒðš­€ì½…궧쭠𔥢
-ðž± æ¡Žä†ê²¡ì­„ðžµê²¯ä¥‚ⶀð¥‚𚧬⽬䠇쳄â¬â°Œðžµ€ä¦â¿Œì›ƒð’¿ ì²ð›¡æµ£æ¶†ð’¯Œâ¢¤à¦…䭎𚜧갣ðž¾ä´®â¡ƒê¤¯ì£ ä°€ì¬¯à¼„ä«
-ð±‚ꢅ䬦賧ð¯¡ìœ è¾‡âž¥ä½ƒä»®ê·£ì ð’´¯â­…ꢡê°ì»„⒤⻠è®æ¶…䠃跥è¢ä½„ðž„ð³‡æ³„껧𚮡ðž±æ£‡æ»¡à½ƒð»¯è¼¤ê´…ðš ¬â¥ê² 
-ð’§ä£‚ê¤è¥ƒðž¼§ðœ°§ä¼Žè¥¡ì›…𞳧걯䳣𚟡ì¼ì­„洠컥â…â¿äº‚𚯧𚯯쯅𞮅â¢ð ¦ð’® ðš¯£ðžž¥è¯¤ê£è¾€ð–¥¢æ¤¯ê²‡æ¯£æ¿¢ðž£ðš¢€âž 
-䮮浥ê²æ²£ì¡£äœ¦æ³‡æ­ð¾„æ¯æ›¯æŸ†Û‡Û‡äž€æ³†ð¾§æ­¦ðš­ æ³ê½Œð§¢ê…軀⬠쾣𞡀榧ðž£ðš¦¤â± ä ¦â²¤ðž°¯ðž»¥ì¿‡ä¬„貃柅涢
-ê°â¼ð¿§Ýà± ð¿£è¤€æ¶¡ð˜¼§ðž®à¼…𞵡ð¥†ä®„ð®¥âž‡ê£Ý¥ä¡ä¯Žæ¢¢ðšŸ‡è¼‡ê¤ ä«£äµ€à¤£æ¼‚𞬯⢡軀𚭅ð¯†à¯¦ðš ¤è¥ì«‡â¾¡æ¿§æ²¤
-䜇伢ۇ汧ì²ä¤Žìž¤ä›¯â°ä¿‡ðžµƒê¢§æ®‚ê¶æ¦®Þ£ðž¼§æ¶‚æ°ðž¬‡æ»¦ì¦¤èœ€â ¥ðºì£â¾ê»¬ì½‡æ¼¯ê æŸ¦æ«‡ì梠仇장滦⟠꿯
-ì®æ¥æ«¢ð«£ê ð’®¬æ¤¥ð›¤èª…栮朥迣⺄ඇ𞣣â¿ä¬‚ì¾â« â’§âœê¶‡è¥¤â¡ðž¯‡æ¿ƒðš£ â° ðš«¤æ­¯ä› ð’›¥ðž«‡ì® ðžŸ¤ì»ƒðž¢¯â¬£æ¿¡ä¦£
-è¡è²£æŸ‚ðž³æ£®ì±à²‡ê³ ðš« èŸ„ä¤ì ¯ð’®¡â«¯æ¥€äž„䳣쮅궤轧껯𞥤ðªƒðž¶¡æ½‡Þ„𚥣ðµ‡æµ£ð¬€è¤â½§ì£ì¾‡âž£ðž€ð¡¦ä® ä¤£ð „
-ê ð¾è ¤ðž›¡ðžµ€ä¬¦è¦¯æ¦â¥¯ì¥æ¢‚걯ð¾§âµà³¦ì±ðš£Œèº„轡ð¯£ðž»¥ä¢¦ð‚財䲧ð¦ä¬Žì²æ£â£à±¦ìž§æ£†ì ¥è¥ì ƒä¤â¢æ¦€âµ
-螅赡𒿯ⶣ赧꾤𚬅æ¿ð’›æ¶†ð´‚ॡ䳦ߢèµä¯‡ä¢ƒê Œæ³„柠泡찇ð›¢ðž°äª‚ð¢æ«‡ðš°§æ¼¥ð£„𞜤ð¥âŸ¤æ·£à´¡ä³®à®¤è°€à½¡ðž¾§
-âžè¡€ê½§èŸ§è¾§ê²Œâ»£ðš££ì³à´¡ä „æ®ðž£ ì£ƒæ±¦è«¤à°¯æ¯ è…ð¦„謄殯𞱄䳀â³ðž¶ìŸ‡à¶†ð»¢ìžð¿¡ä³ƒÛ‚ðž­¥ä‡ä¦‡â¥Œì¼ì¥¯ì¶
-ð–½¢ð³ƒð’·¡ðš«¥ðšŸ‡ð¿§ðš¦§ð¢ä¥¦ðš¯€æ£‡æ½¡â¥„æ­¡ì°æœ†â» ä¤†ð–¤§æ¼¢ðœ§ê¡…⽄쾠ð¥£è¡ðš¥ ð¥†ä¤£à¦…𞛇䤣ð¡¡ð¢äž¦ð–§ß£è£ðš«ðµ¤
-ཅۄì¶ä²ƒæ¬†ê·¬ðº€è¯€æ»ðž«‡ð¯‡äƒðž§¡ì±ƒì²¥ðž­¤êºì«…𞫡䱮𞼤અ𒭤견Фð«ð¾§ä½£ð–±¢æ¾¢ì¿ðž›§â½…侮榅ð¾„यì¥èœä££
-𚥌ð«ìµ¥ðš¥¡âž¤è·¡æ®ƒä°£ä¯¤ðž³¥ì¤â´êµ„𚬧⥇줡걬০켃𜼧𚧯첣䜂𞵇𚟀찃궀谀Ɽ伎䢮𒛄𚦀ꤥ⾣ð­æ²…䬇䧠ð±‡
-沀濡ठ𞰄쟠ðº…ê£ð´‚躄佇⦇毄计賀䢎澡𒮌䲄𒠧캀䟣ð·§è¤€ðž»…蠤൯æ£èœƒðž®¤æ¾„â§â¾¥æ’¦â½¬â¶¥ðª„ய𔼧ބ躄
-䬎챯𚫇⽯ð¾ ðž› ðš›§ä¬Žêž„êµ¥ð¢‚𚠣⠥ä§æœ„𞧥à¿ì›¥ê½¬à½ƒæµ…â¦â¬ðº†ä¾¢æ ¦â§ ðž›¯ê¶ à¶¦ðš­§è¶¤è°¥æ­¤ð²‚ð¬ƒè» ðšª…ðž¦ðž·¤
-蛄俧袥补æ¦ì â¤â €è±‡ä¿¢ì®¯ê¤‡âžð´â¶¤æ¶®ì°£ð’®‡ì榠跣𜤧⦅ໃಆ𞛯䵣谠𞰅ꢯ⡧淯柤궡✠䮎괯𒮣â…朎
-⥅웣䯮첀𚫣꒤ð£ ì­æ´€è›¡æ¥†ðš®£àµ¡ä®®Ò¯æ° ðœæ¿†äœ¢ä·¯æ½£æ­ƒä·¯ðž£¡ì›ì­„椥䟂➅𒯣𒯤ૡༀ䭧ܣ죅ð¯ à¤è»¯ä§£
-â°¤ä¢â¬¥æª‚䠮⫤䛠꜡䛆讠𚭄✠ê¿æ¬£è ¡ðµ†ì¼è±£è­„𞣇춣𒭯ð»¢ä ƒä° æ’¦æœ…䮄榦溃貀𒯅䶇â¾ðž¬§æ¾¡ð»¦ä²®æ¦€ðž¯§
-ðª„䢆侄ðž¾æœ¦êœ‡ð®¢à½ð¯£ì·§êºðž±ƒæž æ«§æ¡ ê´¬æž‡êœ¯ê³‡ð°‚𘜧ð¦„컡濦汥줠𞲡輀𞫃ð £ì¥‡â£ƒðž´ä³‚⟤漇쯣껃ð¾€è¡ƒ
-𚮄쯇𒼧ð„浥洄楠৯춥蒧⾯ð«†à¼‚ꤌ毮䤆⺄༠०袀䢂죃ⴣð¿¯æ¢‡æº„毦𞼄螄櫤쳃栅満걌毠ðžžâ±Œðš®¡ê’§ä¢†
-ê¥æ³Žðž­…仧궀辯諯웅𞳇津趃অê¿ä¼ðµ¤ìºâ ƒð¦‚ð¶€ê£ä›‚贤济æ§ð撠䱤殥歡躇楄꒧꽧𞽧䡣쵧𒯃ð±†êœ¯ìœ„
-ཀ谠諃ð¬ƒè»…â¥ðž°‡è´ æ’£ß…꽤⠥ಡð€ê¶¥ìœðž³â°„ܯ즡歎𞷥ⵅà´è𞟇구ê§Ü…䱦껡䛦߅蒯俧콣𚭅梧䛠ꡇ
-ݧðš®ì›¥Ð¢â¬ ä¬¦æ¦€ð¢‚貤𞰅𚭠謣䱦⒡췧ð¥€æ¿‡â§£â¤€ì¢¯æ®§ðž¬£ì¤¤â£€æ¥æ¥ŽêµÝ¤æ»Û‡ð˜§ðš¯¯ä’¯â°€ðž¼¤Ò¡ä°¦ðš£ æ¤¯â
-趯ð£¯è±€ìµ…춀⳥䷠ì¡Û¯âº„Û…ä¶ì¶¤æž‚櫅ۅ𞥅䱃䭣𒳯汮澃𞢃谥ⵤ구𚣄콡曤ðž£à¦‡ß‚ì…蠠𜰧䞦ꞇâ²ðš®Œè«§
-趯ì²ä¬Žð¡æŽê² â¥‡ðž»¥æ›¢æ±¥ðž³¡æµ†æ¬ èº…ð¦ðž²¯è°¡ðž¦è¢§è¥ƒæ£§ðš¦ðž¡¡èŸ€ä¾ ð’›ì°‡ì± ìª‡æ´ Ü€ì¯¤ä‡èžðž¿£èœä¿„𞦡⼀ལ
-谥촯䲦â¥à¶¤ðž›¡ð§â¤ƒê¶…༡褡ä­æ¯†æ¿†â§¡è›£Ð¤ðžµ‡è Ý¤è³¯êœæº…⡡ߡ𞥧䮄榆䵄求謥ð§êž€ì¯â§¡è²‡ä›‡ä¢æ’¦è¢¥
-쮇䫀𞜄দ굯ðž¦â»¤è¥‡ì¤…⬅Ûఠ⻀𔠧쒠䫆ð¡…梄梯輤䥣ì⤄ⶡ诃䮢譡𞻠ߤ枤櫥ð¢¥ä¼¦è¢ ê¢ƒì³€è££ðž¼…䰄𞻡
-𒯇槥淠䯃à¶â’¯ðš«£ðš ¯ðž £ðš›„椦泮汣赃潥𚫇ദ𞛤𞿣ä°ì®¡ð–­¢èæ¯ä¶‚䦧档䪂𞾃쟀𚪄𞞃𞳥𞼀ð¿¯ì¡‡ì›„䳎汀ð«£
-æ¼ ðš«„ê¡à¬¥è®¤ê½¡ð±ð­ðš¼§â¦„梎આ枀䠦楇쒤ꞃꤡⴅꞅðž¯à¶…ҡ𞞤氣즤裀𞜅ðµ¥æ«ðµ€à¼¦ð³ƒì³£ð¡¯æ¡§ðž¿ æƒêµì£
-짤𖤧蟃澀ð’­ðž²¯ß⣣â¬â°¤ì¡¥ðš¦Œæ½†ê¡â½¤ì›æµ¥ðžžƒð«„棆갤濧⼣겅쬄൧젣此潆⻯䜃꤯궠쮥𘬧曀⿅譅槣䞂
-äŽê¡ðšŸ£ä°€æ¢¥â¾¬Ü¡ðž¿‡ðž ¥ð® ðžºƒä¢®àª†ä§®ì®ƒèª…櫆𚪃죯诠䵀䯀跥ð¾£â»¥ä¤†â°€êœ„棧枃⻇థ誃ðš›à¿‡è´„𞡣欎⽡ðž±
-𞲄â¬æ‡ð …ð±ƒðž¢¤âžðµ¤ð¢„ê’¥ì¦äº€ì­ðš­¡æ¼†ðž®‡ì²ð¢¦æ®Žì®æ» ð ¥æ¦¯ð®§ð’µ¬â¡€ä®†ä£ ì¤€è®¥ðž¼ƒä¶‡âª…껃泃𖱢楀갠複撮
-✡ð­¢à»ðž®§ðž›¥ì«ƒâ½¤è¦ä¥‡æ²è½ð¡…ಢ䧮æ¤â¬‡ð¤ðž¡¯æ…武楥歎䟄溇䯢𒵬ð¢£è¿ƒäªŽä³¤æ»¡à¬…ⱇ쭀ಥ𞥄䥆⧥𚞧좃
-유栤༡ð°ƒä¿‡â°…殇蠄â½â¾ Ü‡ð’®„澄𚦅⡤䪎榮Я견濂賣쮠仠ä®ä¶¢ðž¦ð«†Ý襅褥찯𞤤ݥ象侯쵇궥𞠃윀웧
-𖰧殀蛡⫥亃觯潥蠀补ⴄ觧ð¡‡ð¾†ê¯ä¡£ì·¡æ½â»¯â¾è«à°¯ê¿§ä± ðš­¯ì°¥êž…⪃콄즯쳣覧𞰄Ⲅ𞿣𚬧𞵤ì¯â¬ƒà¶¤ê²¤
-ⵃ蟥𞟧谣轇䛂ð®„ä½€ß氣𒯧榡𒷬桇䷯觠椄챥ꠌ蒯꜌䭤➡侦䣤𚦬䲀ì¥â’¤ð¦„ê¬ä¢®ð£…ꡌ歡ä¯ä¢£ê´¯ðš®£â¥€
-줣०𚭀殣𚬥𒮇⟄趥좠洦ꢬ装䠆ð’ æ›§âžð’¿§æ¤ƒä €ðž¡…𖼧䳇ງ줄ধðž³â°¬è¦ êƒæ®£ðš¯¤æ¶¡ä³ ê·¥ð¯â«¤è¦¯ðž²¡ðž¼„༦
-䢦쥥줤ꡤড젃ಧꢥ諤𔭢ඥ𒛌枅𖜧줄躀à°ä¦Žðž¯„졯譄➇仄ä°è›ì´¡äž£ì¶…涧⡄滀ଢ䮇æ¯ð˜ §ðš¯§ä¾‡æ¾€ê¡æ£
-𒷧槧߅䶠윥귡귧⤯𚪃ð·¢à½†è£æ¯§ð¥£ð¯¥â¬¤è§ì²€â­ðž»¡æ½¤ðžŸƒäŽæ± ðž¦€æ®¤Ò ðžµä¯à½ìŸ§ð’°§æ°¢ê·¡ðš›§ð’¿¯ê¥„⭌䜇ۥ
-ê¡ðž¯¯æ£„â£ê¤¥à§¦ð¯ ð’·¤ðž¦£ì®ðž° ðš§¡æ¡§ð§â´¤ê ¡è»…𞟃衄䠦ߤ܅ⲃଢ蛄溎椀𞠀䛃𞡣𞟣澅𚭬䧤⡇贤⫌쪄Þ朣
-â»ì¼…ð½¢â¼¡ð²€ìž à¯§ðž¬¥ðž¥€à±§ä¦¤à½¦èª‡æ¼Žè­ è¿„䦂䳇𞣡正ðµ¤ê³„楧ޅ✬𞿯棅𞳧𞛤𞜀쭯𞮀诠ð¥€æž¢ä¥®ä­†æ¥†ì»§à¬†
-𞶇➬అ䤦誃ð …ð¿¤äŸ€æ´€â¡¤ðšŸ£æ»¤ðž¥‡ðž¾£ì¦€ð â¼ƒä°Žæº„꽅웇✡ð¾¥ä²€â¡Ü£è®£ðž¿¥â¼¤è¦„𚯇䡇అè€â¥Œä¾§ê»„ê¬æµè´€
-æ¼ì’¤ì²§ì£ê³¡â£ƒè¶ƒè³„撠।ì â¶Œðš£…⾥춧𞞠쒡쿀𞦠䵯æ¯æ¶ ðž«€â£¡ê¡„䢀満棃䡯ð›£à­¯ä³¯âµ¡à­¡ä¥ƒâ‡â …䣆æ§ð³ƒ
-귧覀𞼠漎ðž´ðž¤¡à½‡ä°¦ðž²£âƒæ­†ì½£ê¿‡æœðž¢„ðžµ êŒðž¡…賡𞧠æ›ê¼ƒðž»¯ê¼¬à²‡ðž´¯èµ„榎쮯輤ॡ䜎⦌𞶅ð ðš§§â¡ƒì³ðµ…à¿€
-ðž’§ðž¤ì¯£ê»§ìªƒðž£ æ¤ƒì¡âŸ¤ß‡ì›…䱧䛣𞷧ð³¤ðš¬ ì®€ä ðž­‡ê½£ðž¿‡â £ìŸ£ðž¢…ദ洅촥컇ðš¦ìµ¡êž…ä †ð¥‡â’¥æ¶¯ä¢â´…𒭡쮤꺅
-𞥇컠â³æ¼ƒð²ƒìœ‡è¯¤ê²£ðž¥„伣䜠⻇𞡀修꜡𞻣䳎â„켇꽡𘼧쭄洂ðžŸêœ ð®¦â°ƒìµ…ð¬‚梀櫯䜯꜡䛣à¼æ‡âª€ìº„𞰠⼌
-æ¡ð³„没ⳅâžð’®€ì²¡â¬ä¾¯ìº…检𞡧棡𞬄𞥧𞒠𞶄䥧ð³ƒðž»§ðཧè¬ð«‡ðš¯…讄枥𚞬첡쾀欎육웠ð­¤à­¯æ¿§è­ì±¤ä¶¢ê»¤
-𞯤쒤ð¾‚辧𞮡ðš­è¤¡â¼£ðž¼ƒä³ƒâ ðžè±ß¡æ«¦ð’®¬æžðž±¥â¶ àª‡ê ð­¤ðž‡æ²£æ£æŸ„ð³‚䠯楅곅⼣⥃ༀ螡ߥ柤褣曠沧꒬
-ð´ƒäµ‚䲇蠀ð¿§ä²‡à¶¦ð’¯‡âºì»¤è¬ðš££ðš«ƒì»æ¼¢ä €è°ƒâ²ƒä¢¢Þ„辅毡갯ðš®ä¤£æ¤¦ðž²¯à¥§ðžž è¼¯ð˜œ§ð¯£ð³…⽄𞽤𚧤𚬡䴆𞷠ଦ
-䱠䒮諃à°ð ¡æ¡¦ðžŸ‡ðš­§è°ðž»¤ð¡ì¥¡æµ£ðž¼‡è­€â«Œì®¥ê¢…ì»æ›…ꥅ𞟅à¬ì°€æ±…ð·¦à³¡è° ðž¦¥ä¬€ðž´¡ä¢ ì³€â¡ðµƒß ß à¶…겧淤
-쥣æ¯è­„꼠𒮣ì«ì­¥è®¥à¥¡ì¿‡ð¾¡à®†ä¼ƒâ« æ±‡äœ¢è¡¯æ¥¥æµŽä¿æžðš££æ’®ì¬…èœâ§¤è›¥ì®â¥ƒðš¯£ê²ƒà®ƒì¤ ä£‡è¿…泆𞟯𞰥⤯ð§£
-𚥯è æ³Žà¬¡è „涣తâ¾â»Œä§à¼€æ¦®Ò¯ð³ƒæ­‚浅𞬄ꡥ첤⬇유ð¶ƒè®æ¬¤ä¿¤ìž§â¡Œðž­¥â±ì¶¥æ°¤ð §ä¿®æµì«¤äµ†ðž ƒÜ€ì›£ðž¶
-곧è¡ê €ê±ðžŸ è®¤ì®€ð½¢è°¥ìž¡ðž¼£ä½®ðžºè»¡â¾ì®¯ß¡â§¯ìŸ¡ä°†â½€êµ‡ì´¤è®¤äµ„輥𞦤𞲇䡮侢朆쬣æ¢â½ƒæ¿ƒðž¾„⣧𞶥æŸà¼¢
-⼅𞦀ॠ軀浯ܡ𒯡컡谤ඤ曢⧠짠컠𚠯꿡ðº€ð’¬§ê³Œæ¿‚ণ웧⾡栅䞠괬ܤ䦄ä¼æ›€äº†à½¡æ¦§ä­¦ð’­¯â›ƒè¡§æ¿ ðš§ì¥
-ìµð›£âª…蜤ðž¤è£…고𒯬쳅â»Ý£ä³†à§ ä¦ð®¡à¤„â«ð¶ì¿§äœŽð¿£ì ¡ê·§æ£¥æ«ì¿£æ³¯ä¿£ä½¦â¾¥æœ¦æ½ê¢¤ðž«£ê™§ðž‚Žðº†Ú¦Õˆì·¥
-췧䙭ä¶æ¾¥ðžœ…쨯쵥Ⱕ쵥䗌ìµæ½…旅暬Ոⵤ旆𞗎줭젠ৡ쮠┢𚴧ðµ£æ½§ðž¾¥ðœ”§ðž‘¢è´®ðž½…跣쓄䔭𞷥⽇𞾅𞴥ꔥ䓭
-â‚Žì±æ¾¥ì—‡ðž—Žê³­è´‡Ô†ì¬¡ì©¯ä˜ ä¯ƒð¯¤æ¹ðšš­Õˆê½¤ì—‡ðž—Žê”­â‚Žè°¥ð—‡ä—Œì³­ä™­äŸâ—Žì³­ä™ä¾­ì¾‡ìµ¤è“„ä•ì·¥ì·§ä“­â—Žì³­
-ä’­ðž—Žß䓭亭è청𞻥䙭侭䷤æ“ä•ì·¤â½‡ää•âµ¤æ‘†ä½à½§ðž—…暬èì¶ì°¤â²¥ä™­ä”­ðšš­Ã¨è°¥ð—‡ä—Œì²ä™­äŸâ—Žä•ð—„
-ì—Žßâ—Žì²â’¬ä“­äº­Ã¨æ•ˆð±…궤◄虬䶭侄䗌꾄쓅ä•ì·¥ì·§â•‚æ—„â—Œì²ðž—‚旌藂꾄쓅ä•âµ¤æª¦ì²ðž—‚旌暬è𞂆效
-꽤엇虬ä•ð±…궤⚤èì±æ¾¥ì—‡ðž—Žì¶ì°¤â²¥â‚Žðž‚†ì°­ðž½‡ä™­ä¾­ì¾‡àµ§è“‡ä•ê½¤ì—‡æš¬à³¨è—…䗌ⳇ查䗌찭𞽇䓭䙭𞙮䔭
-枅දðž…➥èµð’¶¯âµ¯à¶ì¶¥ìŸ…ⵅ쟥ðµ¥èž¥â´…춯äŸì·¯æ·¯ä´ê—旌₆效ê¡ðš¦€æ¡âª£ê¼­ðš ¥ðž½‡ðš©­ðž˜Œâ±…ðž·¥ð£‡ì¡£ì“€æš¬Ã¨
-줭젠ৡ쮠┢𚴧꽠𜔧𞑢跮쵅䭀𞡀䗌è斈쳮𞴤侭ට𞩎ðµæ½…暅汤津ðž¥à¿„𞴥ⶎ澥𞜅ì‘ð—肌惨澈漥𞾇쵤
-趤굄𞓅ä¶æ¾¥ðžœ…쨯𞰅Ⱕ쵥䗌찭𞽇䓭䓭äè惨ð©Ð­è–ŽÃ¨æ“¨â‚Žðž—†
-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 );
- return preg_replace( $rx, $rp, $haystack );
- }
}
diff --git a/includes/specials/SpecialWantedcategories.php b/includes/specials/SpecialWantedcategories.php
index 0035bfab..d2ffdb94 100644
--- a/includes/specials/SpecialWantedcategories.php
+++ b/includes/specials/SpecialWantedcategories.php
@@ -35,22 +35,22 @@ class WantedCategoriesPage extends WantedQueryPage {
}
function getQueryInfo() {
- return array (
- 'tables' => array ( 'categorylinks', 'page' ),
- 'fields' => array ( 'namespace' => NS_CATEGORY,
+ return array(
+ 'tables' => array( 'categorylinks', 'page' ),
+ 'fields' => array( 'namespace' => NS_CATEGORY,
'title' => 'cl_to',
'value' => 'COUNT(*)' ),
- 'conds' => array ( 'page_title IS NULL' ),
- 'options' => array ( 'GROUP BY' => 'cl_to' ),
- 'join_conds' => array ( 'page' => array ( 'LEFT JOIN',
- array ( 'page_title = cl_to',
+ 'conds' => array( 'page_title IS NULL' ),
+ 'options' => array( 'GROUP BY' => 'cl_to' ),
+ 'join_conds' => array( 'page' => array( 'LEFT JOIN',
+ array( 'page_title = cl_to',
'page_namespace' => NS_CATEGORY ) ) )
);
}
/**
- * @param $skin Skin
- * @param $result
+ * @param Skin $skin
+ * @param object $result Result row
* @return string
*/
function formatResult( $skin, $result ) {
diff --git a/includes/specials/SpecialWantedfiles.php b/includes/specials/SpecialWantedfiles.php
index 9a2d30a3..b5c1fdbe 100644
--- a/includes/specials/SpecialWantedfiles.php
+++ b/includes/specials/SpecialWantedfiles.php
@@ -73,16 +73,16 @@ class WantedFilesPage extends WantedQueryPage {
}
function getQueryInfo() {
- return array (
- 'tables' => array ( 'imagelinks', 'image' ),
- 'fields' => array ( 'namespace' => NS_FILE,
+ return array(
+ 'tables' => array( 'imagelinks', 'image' ),
+ 'fields' => array( 'namespace' => NS_FILE,
'title' => 'il_to',
'value' => 'COUNT(*)' ),
- 'conds' => array ( 'img_name IS NULL' ),
- 'options' => array ( 'GROUP BY' => 'il_to' ),
- 'join_conds' => array ( 'image' =>
- array ( 'LEFT JOIN',
- array ( 'il_to = img_name' )
+ 'conds' => array( 'img_name IS NULL' ),
+ 'options' => array( 'GROUP BY' => 'il_to' ),
+ 'join_conds' => array( 'image' =>
+ array( 'LEFT JOIN',
+ array( 'il_to = img_name' )
)
)
);
diff --git a/includes/specials/SpecialWantedtemplates.php b/includes/specials/SpecialWantedtemplates.php
index f5539c18..d13fa031 100644
--- a/includes/specials/SpecialWantedtemplates.php
+++ b/includes/specials/SpecialWantedtemplates.php
@@ -38,17 +38,17 @@ class WantedTemplatesPage extends WantedQueryPage {
}
function getQueryInfo() {
- return array (
- 'tables' => array ( 'templatelinks', 'page' ),
- 'fields' => array ( 'namespace' => 'tl_namespace',
+ return array(
+ 'tables' => array( 'templatelinks', 'page' ),
+ 'fields' => array( 'namespace' => 'tl_namespace',
'title' => 'tl_title',
'value' => 'COUNT(*)' ),
- 'conds' => array ( 'page_title IS NULL',
+ 'conds' => array( 'page_title IS NULL',
'tl_namespace' => NS_TEMPLATE ),
- 'options' => array (
+ 'options' => array(
'GROUP BY' => array( 'tl_namespace', 'tl_title' ) ),
- 'join_conds' => array ( 'page' => array ( 'LEFT JOIN',
- array ( 'page_namespace = tl_namespace',
+ 'join_conds' => array( 'page' => array( 'LEFT JOIN',
+ array( 'page_namespace = tl_namespace',
'page_title = tl_title' ) ) )
);
}
diff --git a/includes/specials/SpecialWatchlist.php b/includes/specials/SpecialWatchlist.php
index c7f122b8..4afa279e 100644
--- a/includes/specials/SpecialWatchlist.php
+++ b/includes/specials/SpecialWatchlist.php
@@ -26,8 +26,8 @@ class SpecialWatchlist extends SpecialPage {
/**
* Constructor
*/
- public function __construct( $page = 'Watchlist' ) {
- parent::__construct( $page );
+ public function __construct( $page = 'Watchlist', $restriction = 'viewmywatchlist' ) {
+ parent::__construct( $page, $restriction );
}
/**
@@ -41,7 +41,7 @@ class SpecialWatchlist extends SpecialPage {
$output = $this->getOutput();
# Anons don't get a watchlist
- if( $user->isAnon() ) {
+ if ( $user->isAnon() ) {
$output->setPageTitle( $this->msg( 'watchnologin' ) );
$output->setRobotPolicy( 'noindex,nofollow' );
$llink = Linker::linkKnown(
@@ -54,17 +54,16 @@ class SpecialWatchlist extends SpecialPage {
return;
}
+ // Check permissions
+ $this->checkPermissions();
+
// Add feed links
- $wlToken = $user->getOption( 'watchlisttoken' );
- if ( !$wlToken ) {
- $wlToken = MWCryptRand::generateHex( 40 );
- $user->setOption( 'watchlisttoken', $wlToken );
- $user->saveSettings();
+ $wlToken = $user->getTokenFromOption( 'watchlisttoken' );
+ if ( $wlToken ) {
+ $this->addFeedLinks( array( 'action' => 'feedwatchlist', 'allrev' => 'allrev',
+ 'wlowner' => $user->getName(), 'wltoken' => $wlToken ) );
}
- $this->addFeedLinks( array( 'action' => 'feedwatchlist', 'allrev' => 'allrev',
- 'wlowner' => $user->getName(), 'wltoken' => $wlToken ) );
-
$this->setHeaders();
$this->outputHeader();
@@ -74,9 +73,9 @@ class SpecialWatchlist extends SpecialPage {
$request = $this->getRequest();
$mode = SpecialEditWatchlist::getMode( $request, $par );
- if( $mode !== false ) {
+ if ( $mode !== false ) {
# TODO: localise?
- switch( $mode ) {
+ switch ( $mode ) {
case SpecialEditWatchlist::EDIT_CLEAR:
$mode = 'clear';
break;
@@ -87,7 +86,7 @@ class SpecialWatchlist extends SpecialPage {
$mode = null;
}
$title = SpecialPage::getTitleFor( 'EditWatchlist', $mode );
- $output->redirect( $title->getLocalUrl() );
+ $output->redirect( $title->getLocalURL() );
return;
}
@@ -99,30 +98,30 @@ class SpecialWatchlist extends SpecialPage {
return;
}
- // @TODO: use FormOptions!
+ // @todo use FormOptions!
$defaults = array(
- /* float */ 'days' => floatval( $user->getOption( 'watchlistdays' ) ), /* 3.0 or 0.5, watch further below */
+ /* float */ 'days' => floatval( $user->getOption( 'watchlistdays' ) ),
/* bool */ 'hideMinor' => (int)$user->getBoolOption( 'watchlisthideminor' ),
- /* bool */ 'hideBots' => (int)$user->getBoolOption( 'watchlisthidebots' ),
+ /* bool */ 'hideBots' => (int)$user->getBoolOption( 'watchlisthidebots' ),
/* bool */ 'hideAnons' => (int)$user->getBoolOption( 'watchlisthideanons' ),
- /* bool */ 'hideLiu' => (int)$user->getBoolOption( 'watchlisthideliu' ),
+ /* bool */ 'hideLiu' => (int)$user->getBoolOption( 'watchlisthideliu' ),
/* bool */ 'hidePatrolled' => (int)$user->getBoolOption( 'watchlisthidepatrolled' ),
- /* bool */ 'hideOwn' => (int)$user->getBoolOption( 'watchlisthideown' ),
- /* bool */ 'extended' => (int)$user->getBoolOption( 'extendwatchlist' ),
+ /* bool */ 'hideOwn' => (int)$user->getBoolOption( 'watchlisthideown' ),
+ /* bool */ 'extended' => (int)$user->getBoolOption( 'extendwatchlist' ),
/* ? */ 'namespace' => '', //means all
- /* ? */ 'invert' => false,
+ /* ? */ 'invert' => false,
/* bool */ 'associated' => false,
);
$this->customFilters = array();
wfRunHooks( 'SpecialWatchlistFilters', array( $this, &$this->customFilters ) );
- foreach( $this->customFilters as $key => $params ) {
+ foreach ( $this->customFilters as $key => $params ) {
$defaults[$key] = $params['default'];
}
# Extract variables from the request, falling back to user preferences or
# other default values if these don't exist
$values = array();
- $values['days'] = $request->getVal( 'days', $defaults['days'] );
+ $values['days'] = floatval( $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'] );
@@ -130,7 +129,7 @@ class SpecialWatchlist extends SpecialPage {
$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 ) {
+ foreach ( $this->customFilters as $key => $params ) {
$values[$key] = (int)$request->getBool( $key, $defaults[$key] );
}
@@ -159,61 +158,41 @@ class SpecialWatchlist extends SpecialPage {
$values['invert'] = $invert;
$values['associated'] = $associated;
- if( is_null( $values['days'] ) || !is_numeric( $values['days'] ) ) {
- $big = 1000; /* The magical big */
- if( $nitems > $big ) {
- # Set default cutoff shorter
- $values['days'] = $defaults['days'] = (12.0 / 24.0); # 12 hours...
- } else {
- $values['days'] = $defaults['days']; # default cutoff for shortlisters
- }
- } else {
- $values['days'] = floatval( $values['days'] );
- }
-
// Dump everything here
$nondefaults = array();
foreach ( $defaults as $name => $defValue ) {
wfAppendToArrayIfNotDefault( $name, $values[$name], $defaults, $nondefaults );
}
- if( ( $wgEnotifWatchlist || $wgShowUpdatedMarker ) && $request->getVal( 'reset' ) &&
+ if ( ( $wgEnotifWatchlist || $wgShowUpdatedMarker ) && $request->getVal( 'reset' ) &&
$request->wasPosted() )
{
$user->clearAllNotifications();
- $output->redirect( $this->getTitle()->getFullUrl( $nondefaults ) );
+ $output->redirect( $this->getTitle()->getFullURL( $nondefaults ) );
return;
}
# Possible where conditions
$conds = array();
- if( $values['days'] > 0 ) {
+ if ( $values['days'] > 0 ) {
$conds[] = 'rc_timestamp > ' . $dbr->addQuotes( $dbr->timestamp( time() - intval( $values['days'] * 86400 ) ) );
}
- # If the watchlist is relatively short, it's simplest to zip
- # down its entirety and then sort the results.
-
- # If it's relatively long, it may be worth our while to zip
- # through the time-sorted page list checking for watched items.
-
- # Up estimate of watched items by 15% to compensate for talk pages...
-
# Toggles
- if( $values['hideOwn'] ) {
+ if ( $values['hideOwn'] ) {
$conds[] = 'rc_user != ' . $user->getId();
}
- if( $values['hideBots'] ) {
+ if ( $values['hideBots'] ) {
$conds[] = 'rc_bot = 0';
}
- if( $values['hideMinor'] ) {
+ if ( $values['hideMinor'] ) {
$conds[] = 'rc_minor = 0';
}
- if( $values['hideLiu'] ) {
+ if ( $values['hideLiu'] ) {
$conds[] = 'rc_user = 0';
}
- if( $values['hideAnons'] ) {
+ if ( $values['hideAnons'] ) {
$conds[] = 'rc_user != 0';
}
if ( $user->useRCPatrol() && $values['hidePatrolled'] ) {
@@ -224,44 +203,72 @@ class SpecialWatchlist extends SpecialPage {
}
# Toggle watchlist content (all recent edits or just the latest)
- if( $values['extended'] ) {
+ if ( $values['extended'] ) {
$limitWatchlist = $user->getIntOption( 'wllimit' );
$usePage = false;
} else {
# Top log Ids for a page are not stored
- $conds[] = 'rc_this_oldid=page_latest OR rc_type=' . RC_LOG;
+ $nonRevisionTypes = array( RC_LOG );
+ wfRunHooks( 'SpecialWatchlistGetNonRevisionTypes', array( &$nonRevisionTypes ) );
+ if ( $nonRevisionTypes ) {
+ if ( count( $nonRevisionTypes ) === 1 ) {
+ // if only one use an equality instead of IN condition
+ $nonRevisionTypes = reset( $nonRevisionTypes );
+ }
+ $conds[] = $dbr->makeList(
+ array(
+ 'rc_this_oldid=page_latest',
+ 'rc_type' => $nonRevisionTypes,
+ ),
+ LIST_OR
+ );
+ }
$limitWatchlist = 0;
$usePage = true;
}
# Show a message about slave lag, if applicable
$lag = wfGetLB()->safeGetLag( $dbr );
- if( $lag > 0 ) {
+ if ( $lag > 0 ) {
$output->showLagWarning( $lag );
}
- # Create output form
- $form = Xml::fieldset( $this->msg( 'watchlist-options' )->text(), false, array( 'id' => 'mw-watchlist-options' ) );
+ # Create output
+ $form = '';
# Show watchlist header
+ $form .= "<p>";
$form .= $this->msg( 'watchlist-details' )->numParams( $nitems )->parse() . "\n";
-
- if( $user->getOption( 'enotifwatchlistpages' ) && $wgEnotifWatchlist) {
- $form .= $this->msg( 'wlheader-enotif' )->parseAsBlock() . "\n";
+ if ( $wgEnotifWatchlist && $user->getOption( 'enotifwatchlistpages' ) ) {
+ $form .= $this->msg( 'wlheader-enotif' )->parse() . "\n";
+ }
+ if ( $wgShowUpdatedMarker ) {
+ $form .= $this->msg( 'wlheader-showupdated' )->parse() . "\n";
}
- if( $wgShowUpdatedMarker ) {
+ $form .= "</p>";
+
+ if ( $wgShowUpdatedMarker ) {
$form .= Xml::openElement( 'form', array( 'method' => 'post',
- 'action' => $this->getTitle()->getLocalUrl(),
- '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 ) . "\n";
- }
- $form .= Xml::closeElement( 'form' ) . "\n";
- }
- $form .= "<hr />\n";
+ 'action' => $this->getTitle()->getLocalURL(),
+ 'id' => 'mw-watchlist-resetbutton' ) ) . "\n" .
+ 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 ) . "\n";
+ }
+ $form .= Xml::closeElement( 'form' ) . "\n";
+ }
+
+ $form .= Xml::openElement( 'form', array(
+ 'method' => 'post',
+ 'action' => $this->getTitle()->getLocalURL(),
+ 'id' => 'mw-watchlist-form'
+ ) );
+ $form .= Xml::fieldset(
+ $this->msg( 'watchlist-options' )->text(),
+ false,
+ array( 'id' => 'mw-watchlist-options' )
+ );
$tables = array( 'recentchanges', 'watchlist' );
$fields = RecentChange::selectFields();
@@ -276,10 +283,10 @@ class SpecialWatchlist extends SpecialPage {
),
);
$options = array( 'ORDER BY' => 'rc_timestamp DESC' );
- if( $wgShowUpdatedMarker ) {
+ if ( $wgShowUpdatedMarker ) {
$fields[] = 'wl_notificationtimestamp';
}
- if( $limitWatchlist ) {
+ if ( $limitWatchlist ) {
$options['LIMIT'] = $limitWatchlist;
}
@@ -302,7 +309,7 @@ class SpecialWatchlist extends SpecialPage {
$lang = $this->getLanguage();
$wlInfo = '';
- if( $values['days'] > 0 ) {
+ 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 />\n";
@@ -312,11 +319,11 @@ class SpecialWatchlist extends SpecialPage {
# Spit out some control panel links
$filters = array(
- 'hideMinor' => 'rcshowhideminor',
- 'hideBots' => 'rcshowhidebots',
- 'hideAnons' => 'rcshowhideanons',
- 'hideLiu' => 'rcshowhideliu',
- 'hideOwn' => 'rcshowhidemine',
+ 'hideMinor' => 'rcshowhideminor',
+ 'hideBots' => 'rcshowhidebots',
+ 'hideAnons' => 'rcshowhideanons',
+ 'hideLiu' => 'rcshowhideliu',
+ 'hideOwn' => 'rcshowhidemine',
'hidePatrolled' => 'rcshowhidepatr'
);
foreach ( $this->customFilters as $key => $params ) {
@@ -328,7 +335,7 @@ class SpecialWatchlist extends SpecialPage {
}
$links = array();
- foreach( $filters as $name => $msg ) {
+ foreach ( $filters as $name => $msg ) {
$links[] = $this->showHideLink( $nondefaults, $msg, $name, $values[$name] );
}
@@ -341,7 +348,6 @@ class SpecialWatchlist extends SpecialPage {
$form .= $wlInfo;
$form .= $cutofflinks;
$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(
@@ -349,8 +355,8 @@ class SpecialWatchlist extends SpecialPage {
'all' => '',
'label' => $this->msg( 'namespace' )->text()
), array(
- 'name' => 'namespace',
- 'id' => 'namespace',
+ 'name' => 'namespace',
+ 'id' => 'namespace',
'class' => 'namespaceselector',
)
) . '&#160;';
@@ -372,13 +378,15 @@ class SpecialWatchlist extends SpecialPage {
foreach ( $hiddenFields as $key => $value ) {
$form .= Html::hidden( $key, $value ) . "\n";
}
- $form .= Xml::closeElement( 'form' ) . "\n";
$form .= Xml::closeElement( 'fieldset' ) . "\n";
+ $form .= Xml::closeElement( 'form' ) . "\n";
$output->addHTML( $form );
# If there's nothing to show, stop here
- if( $numRows == 0 ) {
- $output->addWikiMsg( 'watchnochange' );
+ if ( $numRows == 0 ) {
+ $output->wrapWikiMsg(
+ "<div class='mw-changeslist-empty'>\n$1\n</div>", 'recentchanges-noresult'
+ );
return;
}
@@ -438,7 +446,7 @@ class SpecialWatchlist extends SpecialPage {
protected function showHideLink( $options, $message, $name, $value ) {
$label = $this->msg( $value ? 'show' : 'hide' )->escaped();
- $options[$name] = 1 - (int) $value;
+ $options[$name] = 1 - (int)$value;
return $this->msg( $message )->rawParams( Linker::linkKnown( $this->getTitle(), $label, array(), $options ) )->escaped();
}
@@ -469,17 +477,19 @@ class SpecialWatchlist extends SpecialPage {
/**
* Returns html
*
+ * @param int $days This gets overwritten, so is not used
+ * @param array $options Query parameters for URL
* @return string
*/
protected function cutoffLinks( $days, $options = array() ) {
$hours = array( 1, 2, 6, 12 );
$days = array( 1, 3, 7 );
$i = 0;
- foreach( $hours as $h ) {
+ foreach ( $hours as $h ) {
$hours[$i++] = $this->hoursLink( $h, $options );
}
$i = 0;
- foreach( $days as $d ) {
+ foreach ( $days as $d ) {
$days[$i++] = $this->daysLink( $d, $options );
}
return $this->msg( 'wlshowlast' )->rawParams(
@@ -491,7 +501,7 @@ class SpecialWatchlist extends SpecialPage {
/**
* Count the number of items on a user's watchlist
*
- * @param $dbr A database connection
+ * @param DatabaseBase $dbr A database connection
* @return Integer
*/
protected function countItems( $dbr ) {
diff --git a/includes/specials/SpecialWhatlinkshere.php b/includes/specials/SpecialWhatlinkshere.php
index cb3e985c..05c7dd5f 100644
--- a/includes/specials/SpecialWhatlinkshere.php
+++ b/includes/specials/SpecialWhatlinkshere.php
@@ -77,7 +77,7 @@ class SpecialWhatLinksHere extends SpecialPage {
$this->opts = $opts;
$this->target = Title::newFromURL( $opts->getValue( 'target' ) );
- if( !$this->target ) {
+ if ( !$this->target ) {
$out->addHTML( $this->whatlinkshereForm() );
return;
}
@@ -94,11 +94,11 @@ class SpecialWhatLinksHere extends SpecialPage {
}
/**
- * @param int $level Recursion level
- * @param $target Title Target title
- * @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
+ * @param int $level Recursion level
+ * @param Title $target Target title
+ * @param int $limit Number of entries to display
+ * @param int $from Display from this article ID (default: 0)
+ * @param int $back Display from this article ID at backwards scrolling (default: 0)
*/
function showIndirectLinks( $level, $target, $limit, $from = 0, $back = 0 ) {
global $wgMaxRedirectLinksRetrieved;
@@ -111,7 +111,7 @@ class SpecialWhatLinksHere extends SpecialPage {
$hidetrans = $this->opts->getValue( 'hidetrans' );
$hideimages = $target->getNamespace() != NS_FILE || $this->opts->getValue( 'hideimages' );
- $fetchlinks = (!$hidelinks || !$hideredirs);
+ $fetchlinks = ( !$hidelinks || !$hideredirs );
// Make the query
$plConds = array(
@@ -119,9 +119,9 @@ class SpecialWhatLinksHere extends SpecialPage {
'pl_namespace' => $target->getNamespace(),
'pl_title' => $target->getDBkey(),
);
- if( $hideredirs ) {
+ if ( $hideredirs ) {
$plConds['rd_from'] = null;
- } elseif( $hidelinks ) {
+ } elseif ( $hidelinks ) {
$plConds[] = 'rd_from is NOT NULL';
}
@@ -166,34 +166,38 @@ class SpecialWhatLinksHere extends SpecialPage {
'rd_interwiki = ' . $dbr->addQuotes( '' ) . ' OR rd_interwiki IS NULL'
)));
- if( $fetchlinks ) {
+ if ( $fetchlinks ) {
$options['ORDER BY'] = 'pl_from';
$plRes = $dbr->select( array( 'pagelinks', 'page', 'redirect' ), $fields,
$plConds, __METHOD__, $options,
- $joinConds);
+ $joinConds
+ );
}
- if( !$hidetrans ) {
+ if ( !$hidetrans ) {
$options['ORDER BY'] = 'tl_from';
$tlRes = $dbr->select( array( 'templatelinks', 'page', 'redirect' ), $fields,
$tlConds, __METHOD__, $options,
- $joinConds);
+ $joinConds
+ );
}
- if( !$hideimages ) {
+ if ( !$hideimages ) {
$options['ORDER BY'] = 'il_from';
$ilRes = $dbr->select( array( 'imagelinks', 'page', 'redirect' ), $fields,
$ilConds, __METHOD__, $options,
- $joinConds);
+ $joinConds
+ );
}
- if( ( !$fetchlinks || !$plRes->numRows() ) && ( $hidetrans || !$tlRes->numRows() ) && ( $hideimages || !$ilRes->numRows() ) ) {
+ if ( ( !$fetchlinks || !$plRes->numRows() ) && ( $hidetrans || !$tlRes->numRows() ) && ( $hideimages || !$ilRes->numRows() ) ) {
if ( 0 == $level ) {
$out->addHTML( $this->whatlinkshereForm() );
// Show filters only if there are links
- if( $hidelinks || $hidetrans || $hideredirs || $hideimages )
+ if ( $hidelinks || $hidetrans || $hideredirs || $hideimages ) {
$out->addHTML( $this->getFilterPanel() );
+ }
$errMsg = is_int( $namespace ) ? 'nolinkshere-ns' : 'nolinkshere';
$out->addWikiMsg( $errMsg, $this->target->getPrefixedText() );
@@ -204,21 +208,21 @@ class SpecialWhatLinksHere extends SpecialPage {
// Read the rows into an array and remove duplicates
// templatelinks comes second so that the templatelinks row overwrites the
// pagelinks row, so we get (inclusion) rather than nothing
- if( $fetchlinks ) {
+ if ( $fetchlinks ) {
foreach ( $plRes as $row ) {
$row->is_template = 0;
$row->is_image = 0;
$rows[$row->page_id] = $row;
}
}
- if( !$hidetrans ) {
+ if ( !$hidetrans ) {
foreach ( $tlRes as $row ) {
$row->is_template = 1;
$row->is_image = 0;
$rows[$row->page_id] = $row;
}
}
- if( !$hideimages ) {
+ if ( !$hideimages ) {
foreach ( $ilRes as $row ) {
$row->is_template = 0;
$row->is_image = 1;
@@ -269,7 +273,7 @@ class SpecialWhatLinksHere extends SpecialPage {
$out->addHTML( $this->listEnd() );
- if( $level == 0 ) {
+ if ( $level == 0 ) {
$out->addHTML( $prevnext );
}
}
@@ -292,7 +296,7 @@ class SpecialWhatLinksHere extends SpecialPage {
}
}
- if( $row->rd_from ) {
+ if ( $row->rd_from ) {
$query = array( 'redirect' => 'no' );
} else {
$query = array();
@@ -308,12 +312,15 @@ class SpecialWhatLinksHere extends SpecialPage {
// Display properties (redirect or template)
$propsText = '';
$props = array();
- if ( $row->rd_from )
+ if ( $row->rd_from ) {
$props[] = $msgcache['isredirect'];
- if ( $row->is_template )
+ }
+ if ( $row->is_template ) {
$props[] = $msgcache['istemplate'];
- if( $row->is_image )
+ }
+ if ( $row->is_image ) {
$props[] = $msgcache['isimage'];
+ }
if ( count( $props ) ) {
$propsText = $this->msg( 'parentheses' )->rawParams( implode( $msgcache['semicolon-separator'], $props ) )->escaped();
@@ -334,8 +341,9 @@ class SpecialWhatLinksHere extends SpecialPage {
protected function wlhLink( Title $target, $text ) {
static $title = null;
- if ( $title === null )
+ if ( $title === null ) {
$title = $this->getTitle();
+ }
return Linker::linkKnown(
$title,
@@ -419,8 +427,8 @@ class SpecialWhatLinksHere extends SpecialPage {
'all' => '',
'label' => $this->msg( 'namespace' )->text()
), array(
- 'name' => 'namespace',
- 'id' => 'namespace',
+ 'name' => 'namespace',
+ 'id' => 'namespace',
'class' => 'namespaceselector',
)
);
@@ -450,12 +458,13 @@ class SpecialWhatLinksHere extends SpecialPage {
$links = array();
$types = array( 'hidetrans', 'hidelinks', 'hideredirs' );
- if( $this->target->getNamespace() == NS_FILE )
+ if ( $this->target->getNamespace() == NS_FILE ) {
$types[] = 'hideimages';
+ }
// Combined message keys: 'whatlinkshere-hideredirs', 'whatlinkshere-hidetrans', 'whatlinkshere-hidelinks', 'whatlinkshere-hideimages'
// To be sure they will be found by grep
- foreach( $types as $type ) {
+ foreach ( $types as $type ) {
$chosen = $this->opts->getValue( $type );
$msg = $chosen ? $show : $hide;
$overrides = array( $type => !$chosen );
diff --git a/includes/specials/SpecialWithoutinterwiki.php b/includes/specials/SpecialWithoutinterwiki.php
index 37237407..9d23499f 100644
--- a/includes/specials/SpecialWithoutinterwiki.php
+++ b/includes/specials/SpecialWithoutinterwiki.php
@@ -44,21 +44,21 @@ class WithoutInterwikiPage extends PageQueryPage {
global $wgScript;
# Do not show useless input form if special page is cached
- if( $this->isCached() ) {
+ if ( $this->isCached() ) {
return '';
}
$prefix = $this->prefix;
$t = $this->getTitle();
- return Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ) .
- Xml::openElement( 'fieldset' ) .
- Xml::element( 'legend', null, $this->msg( 'withoutinterwiki-legend' )->text() ) .
- Html::hidden( 'title', $t->getPrefixedText() ) .
- Xml::inputLabel( $this->msg( 'allpagesprefix' )->text(), 'prefix', 'wiprefix', 20, $prefix ) . ' ' .
- Xml::submitButton( $this->msg( 'withoutinterwiki-submit' )->text() ) .
- Xml::closeElement( 'fieldset' ) .
- Xml::closeElement( 'form' );
+ return Html::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ) . "\n" .
+ Html::openElement( 'fieldset' ) . "\n" .
+ Html::element( 'legend', null, $this->msg( 'withoutinterwiki-legend' )->text() ) . "\n" .
+ Html::hidden( 'title', $t->getPrefixedText() ) . "\n" .
+ Xml::inputLabel( $this->msg( 'allpagesprefix' )->text(), 'prefix', 'wiprefix', 20, $prefix ) . "\n" .
+ Xml::submitButton( $this->msg( 'withoutinterwiki-submit' )->text() ) . "\n" .
+ Html::closeElement( 'fieldset' ) . "\n" .
+ Html::closeElement( 'form' );
}
function sortDescending() {
@@ -78,15 +78,15 @@ class WithoutInterwikiPage extends PageQueryPage {
}
function getQueryInfo() {
- $query = array (
- 'tables' => array ( 'page', 'langlinks' ),
- 'fields' => array ( 'namespace' => 'page_namespace',
+ $query = array(
+ 'tables' => array( 'page', 'langlinks' ),
+ 'fields' => array( 'namespace' => 'page_namespace',
'title' => 'page_title',
'value' => 'page_title' ),
- 'conds' => array ( 'll_title IS NULL',
+ 'conds' => array( 'll_title IS NULL',
'page_namespace' => MWNamespace::getContentNamespaces(),
'page_is_redirect' => 0 ),
- 'join_conds' => array ( 'langlinks' => array (
+ 'join_conds' => array( 'langlinks' => array(
'LEFT JOIN', 'll_from = page_id' ) )
);
if ( $this->prefix ) {
diff --git a/includes/templates/NoLocalSettings.php b/includes/templates/NoLocalSettings.php
index 2483e58c..0006df40 100644
--- a/includes/templates/NoLocalSettings.php
+++ b/includes/templates/NoLocalSettings.php
@@ -22,7 +22,7 @@
*/
if ( !defined( 'MEDIAWIKI' ) ) {
- die( "NoLocalSettings.php is not a valid MediaWiki entry point\n" );
+ die( "NoLocalSettings.php is not a valid MediaWiki entry point\n" );
}
if ( !isset( $wgVersion ) ) {
@@ -52,12 +52,12 @@ if ( !function_exists( 'session_name' ) ) {
$installerStarted = ( $success && isset( $_SESSION['installData'] ) );
}
?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-<html xmlns='http://www.w3.org/1999/xhtml' lang='en'>
+<!DOCTYPE html>
+<html lang="en" dir="ltr">
<head>
+ <meta charset="UTF-8" />
<title>MediaWiki <?php echo htmlspecialchars( $wgVersion ) ?></title>
- <meta http-equiv='Content-Type' content='text/html; charset=utf-8' />
- <style type='text/css' media='screen'>
+ <style media='screen'>
html, body {
color: #000;
background-color: #fff;
@@ -79,9 +79,9 @@ if ( !function_exists( 'session_name' ) ) {
<p>
<?php
if ( $installerStarted ) {
- echo( "Please <a href=\"" . htmlspecialchars( $path ) . "mw-config/index." . htmlspecialchars( $ext ) . "\"> complete the installation</a> and download LocalSettings.php." );
+ echo "Please <a href=\"" . htmlspecialchars( $path ) . "mw-config/index." . htmlspecialchars( $ext ) . "\"> complete the installation</a> and download LocalSettings.php.";
} else {
- echo( "Please <a href=\"" . htmlspecialchars( $path ) . "mw-config/index." . htmlspecialchars( $ext ) . "\"> set up the wiki</a> first." );
+ echo "Please <a href=\"" . htmlspecialchars( $path ) . "mw-config/index." . htmlspecialchars( $ext ) . "\"> set up the wiki</a> first.";
}
?>
</p>
diff --git a/includes/templates/Usercreate.php b/includes/templates/Usercreate.php
index 541d9e40..fcd492cb 100644
--- a/includes/templates/Usercreate.php
+++ b/includes/templates/Usercreate.php
@@ -1,6 +1,6 @@
<?php
/**
- * Html form for account creation.
+ * Html form for account creation (since 1.22 with VForm appearance).
*
* 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
@@ -21,16 +21,12 @@
* @ingroup Templates
*/
-/**
- * @defgroup Templates Templates
- */
+class UsercreateTemplate extends BaseTemplate {
-if( !defined( 'MEDIAWIKI' ) ) die( -1 );
-
-/**
- * @ingroup Templates
- */
-class UsercreateTemplate extends QuickTemplate {
+ /**
+ * Extensions (AntiSpoof and TitleBlacklist) call this in response to
+ * UserCreateForm hook to add checkboxes to the create account form.
+ */
function addInputItem( $name, $value, $type, $msg, $helptext = false ) {
$this->data['extraInput'][] = array(
'name' => $name,
@@ -42,220 +38,253 @@ class UsercreateTemplate extends QuickTemplate {
}
function execute() {
- if( $this->data['message'] ) {
+ global $wgCookieExpiration;
+ $expirationDays = ceil( $wgCookieExpiration / ( 3600 * 24 ) );
?>
- <div class="<?php $this->text('messagetype') ?>box">
- <?php if ( $this->data['messagetype'] == 'error' ) { ?>
- <strong><?php $this->msg( 'loginerror' )?></strong><br />
- <?php } ?>
- <?php $this->html('message') ?>
- </div>
- <div class="visualClear"></div>
-<?php } ?>
+<div class="mw-ui-container">
+ <?php if ( $this->haveData( 'languages' ) ) { ?>
+ <div id="languagelinks">
+ <p><?php $this->html( 'languages' ); ?></p>
+ </div>
+ <?php }
+ if ( !wfMessage( 'signupstart' )->isDisabled() ) { ?>
+ <div id="signupstart"><?php $this->msgWiki( 'signupstart' ); ?></div>
+ <?php } ?>
+ <div id="userloginForm">
+ <h2 class="createaccount-join">
+ <?php $this->msg( $this->data['loggedin'] ? 'createacct-another-join' : 'createacct-join' ); ?>
+ </h2>
+ <form name="userlogin2" id="userlogin2" class="mw-ui-vform" method="post" action="<?php $this->text( 'action' ); ?>">
+ <section class="mw-form-header">
+ <?php $this->html( 'header' ); /* extensions such as ConfirmEdit add form HTML here */ ?>
+ </section>
+ <?php if ( $this->data['message'] ) { ?>
+ <div class="<?php $this->text( 'messagetype' ); ?>box">
+ <?php if ( $this->data['messagetype'] == 'error' ) { ?>
+ <strong><?php $this->msg( 'createacct-error' ); ?></strong>
+ <br />
+ <?php } ?>
+ <?php $this->html( 'message' ); ?>
+ </div>
+ <?php } ?>
-<div id="signupstart"><?php $this->msgWiki( 'signupstart' ); ?></div>
-<div id="userlogin">
+ <div>
+ <label for='wpName2'>
+ <?php $this->msg( 'userlogin-yourname' ); ?>
-<form name="userlogin2" id="userlogin2" method="post" action="<?php $this->text('action') ?>">
- <h2><?php $this->msg('createaccount') ?></h2>
- <p id="userloginlink"><?php $this->html('link') ?></p>
- <?php $this->html('header'); /* pre-table point for form plugins... */ ?>
- <?php if( $this->haveData( 'languages' ) ) { ?><div id="languagelinks"><p><?php $this->html( 'languages' ); ?></p></div><?php } ?>
- <table>
- <tr>
- <td class="mw-label"><label for='wpName2'><?php $this->msg('yourname') ?></label></td>
- <td class="mw-input">
+ <span class="mw-ui-flush-right"><?php echo $this->getMsg( 'createacct-helpusername' )->parse(); ?></span>
+ </label>
<?php
- echo Html::input( 'wpName', $this->data['name'], 'text', array(
- 'class' => 'loginText',
- 'id' => 'wpName2',
- 'tabindex' => '1',
- 'size' => '20',
- 'required',
- 'autofocus'
- ) ); ?>
- </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' => '3',
- 'size' => '20'
- ) + User::passwordChangeInputAttribs() ); ?>
- </td>
- </tr>
- <?php if( $this->data['usedomain'] ) {
- $doms = "";
- foreach( $this->data['domainnames'] as $dom ) {
- $doms .= "<option>" . htmlspecialchars( $dom ) . "</option>";
- }
- ?>
- <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="4">
- <?php echo $doms ?>
- </select>
- </td>
- </tr>
- <?php } ?>
- <tr class="mw-row-password">
- <td class="mw-label"><label for='wpRetype'><?php $this->msg('yourpasswordagain') ?></label></td>
- <td class="mw-input">
+ echo Html::input( 'wpName', $this->data['name'], 'text', array(
+ 'class' => 'mw-input loginText',
+ 'id' => 'wpName2',
+ 'tabindex' => '1',
+ 'size' => '20',
+ 'required',
+ 'placeholder' => $this->getMsg( $this->data['loggedin'] ?
+ 'createacct-another-username-ph' : 'userlogin-yourname-ph' )->text(),
+ ) );
+ ?>
+ </div>
+
+ <div>
+ <?php if ( $this->data['createemail'] ) { ?>
+ <label class="mw-ui-checkbox-label">
+ <input name="wpCreateaccountMail" type="checkbox" value="1" id="wpCreateaccountMail" tabindex="2"
+ <?php if ( $this->data['createemailset'] ) {
+ echo 'checked="checked"';
+ } ?>
+ >
+ <?php $this->msg( 'createaccountmail' ); ?>
+ </label>
+ <?php } ?>
+ </div>
+
+ <div class="mw-row-password">
+ <label for='wpPassword2'><?php $this->msg( 'userlogin-yourpassword' ); ?></label>
+ <?php
+ echo Html::input( 'wpPassword', null, 'password', array(
+ 'class' => 'mw-input loginPassword',
+ 'id' => 'wpPassword2',
+ 'tabindex' => '3',
+ 'size' => '20',
+ 'required',
+ 'placeholder' => $this->getMsg( 'createacct-yourpassword-ph' )->text()
+ ) + User::passwordChangeInputAttribs() );
+ ?>
+ </div>
+
+ <?php
+ if ( $this->data['usedomain'] ) {
+ $doms = "";
+ foreach ( $this->data['domainnames'] as $dom ) {
+ $doms .= "<option>" . htmlspecialchars( $dom ) . "</option>";
+ }
+ ?>
+ <div id="mw-user-domain-section">
+ <label for="wpDomain"><?php $this->msg( 'yourdomainname' ); ?></label>
+ <div class="mw-input">
+ <select name="wpDomain" value="<?php $this->text( 'domain' ); ?>" tabindex="4">
+ <?php echo $doms ?>
+ </select>
+ </div>
+ </div>
+ <?php } ?>
+
+ <div class="mw-row-password">
+ <label for='wpRetype'><?php $this->msg( 'createacct-yourpasswordagain' ); ?></label>
<?php
- echo Html::input( 'wpRetype', null, 'password', array(
- 'class' => 'loginPassword',
- 'id' => 'wpRetype',
- 'tabindex' => '5',
- 'size' => '20'
- ) + User::passwordChangeInputAttribs() ); ?>
- </td>
- </tr>
- <tr>
- <?php if( $this->data['useemail'] ) { ?>
- <td class="mw-label"><label for='wpEmail'><?php $this->msg('youremail') ?></label></td>
- <td class="mw-input">
+ echo Html::input( 'wpRetype', null, 'password', array(
+ 'class' => 'mw-input loginPassword',
+ 'id' => 'wpRetype',
+ 'tabindex' => '5',
+ 'size' => '20',
+ 'required',
+ 'placeholder' => $this->getMsg( 'createacct-yourpasswordagain-ph' )->text()
+ ) + User::passwordChangeInputAttribs() );
+ ?>
+ </div>
+
+ <div>
+ <?php if ( $this->data['useemail'] ) { ?>
+ <label for='wpEmail'>
+ <?php
+ $this->msg( $this->data['emailrequired'] ?
+ 'createacct-emailrequired' :
+ 'createacct-emailoptional'
+ );
+ ?>
+ </label>
<?php
- echo Html::input( 'wpEmail', $this->data['email'], 'email', array(
- 'class' => 'loginText',
- 'id' => 'wpEmail',
- 'tabindex' => '6',
- 'size' => '20'
- ) ); ?>
+ echo Html::input( 'wpEmail', $this->data['email'], 'email', array(
+ 'class' => 'mw-input loginText',
+ 'id' => 'wpEmail',
+ 'tabindex' => '6',
+ 'size' => '20',
+ 'required' => $this->data['emailrequired'],
+ 'placeholder' => $this->getMsg( $this->data['loggedin'] ?
+ 'createacct-another-email-ph' : 'createacct-email-ph' )->text()
+ ) );
+ ?>
+ <?php } ?>
+ </div>
+
+ <?php if ( $this->data['userealname'] ) { ?>
+ <div>
+ <label for='wpRealName'><?php $this->msg( 'createacct-realname' ); ?></label>
+ <input type='text' class='mw-input loginText' name="wpRealName" id="wpRealName"
+ tabindex="7"
+ value="<?php $this->text( 'realname' ); ?>" size='20' />
<div class="prefsectiontip">
- <?php // duplicated in Preferences.php profilePreferences()
- if( $this->data['emailrequired'] ) {
- $this->msgWiki('prefs-help-email-required');
- } else {
- $this->msgWiki('prefs-help-email');
- }
- if( $this->data['emailothers'] ) {
- $this->msgWiki('prefs-help-email-others');
- } ?>
+ <?php $this->msgWiki( $this->data['loggedin'] ? 'createacct-another-realname-tip' : 'prefs-help-realname' ); ?>
</div>
- </td>
- <?php } ?>
- <?php if( $this->data['userealname'] ) { ?>
- </tr>
- <tr>
- <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="7"
- value="<?php $this->text('realname') ?>" size='20' />
- <div class="prefsectiontip">
- <?php $this->msgWiki('prefs-help-realname'); ?>
- </div>
- </td>
+ </div>
<?php } ?>
- <?php if( $this->data['usereason'] ) { ?>
- </tr>
- <tr>
- <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="8"
- value="<?php $this->text('reason') ?>" size='20' />
- </td>
+
+ <?php if ( $this->data['usereason'] ) { ?>
+ <div>
+ <label for='wpReason'><?php $this->msg( 'createacct-reason' ); ?></label>
+ <?php echo Html::input( 'wpReason', $this->data['reason'], 'text', array(
+ 'class' => 'mw-input loginText',
+ 'id' => 'wpReason',
+ 'tabindex' => '8',
+ 'size' => '20',
+ 'placeholder' => $this->getMsg( 'createacct-reason-ph' )->text()
+ ) ); ?>
+ </div>
<?php } ?>
- </tr>
- <?php if( $this->data['canremember'] ) { ?>
- <tr>
- <td></td>
- <td class="mw-input">
- <?php
- global $wgCookieExpiration;
- $expirationDays = ceil( $wgCookieExpiration / ( 3600 * 24 ) );
- echo Xml::checkLabel(
- wfMessage( 'remembermypassword' )->numParams( $expirationDays )->text(),
- 'wpRemember',
- 'wpRemember',
- $this->data['remember'],
- array( 'tabindex' => '9' )
- )
- ?>
- </td>
- </tr>
-<?php }
- $tabIndex = 10;
- if ( isset( $this->data['extraInput'] ) && is_array( $this->data['extraInput'] ) ) {
- foreach ( $this->data['extraInput'] as $inputItem ) { ?>
- <tr>
<?php
- if ( !empty( $inputItem['msg'] ) && $inputItem['type'] != 'checkbox' ) {
- ?><td class="mw-label"><label for="<?php
- echo htmlspecialchars( $inputItem['name'] ); ?>"><?php
- $this->msgWiki( $inputItem['msg'] ) ?></label><?php
- } else {
- ?><td><?php
- }
- ?></td>
- <td class="mw-input">
- <input type="<?php echo htmlspecialchars( $inputItem['type'] ) ?>" name="<?php
- echo htmlspecialchars( $inputItem['name'] ); ?>"
- tabindex="<?php echo $tabIndex++; ?>"
- value="<?php
- if ( $inputItem['type'] != 'checkbox' ) {
- echo htmlspecialchars( $inputItem['value'] );
- } else {
- echo '1';
- }
- ?>" id="<?php echo htmlspecialchars( $inputItem['name'] ); ?>"
- <?php
- if ( $inputItem['type'] == 'checkbox' && !empty( $inputItem['value'] ) )
- echo 'checked="checked"';
- ?> /> <?php
- if ( $inputItem['type'] == 'checkbox' && !empty( $inputItem['msg'] ) ) {
+ $tabIndex = 9;
+ if ( isset( $this->data['extraInput'] ) && is_array( $this->data['extraInput'] ) ) {
+ foreach ( $this->data['extraInput'] as $inputItem ) { ?>
+ <div>
+ <?php
+ // If it's a checkbox, output the whole thing (assume it has a msg).
+ if ( $inputItem['type'] == 'checkbox' ) {
+ ?>
+ <label class="mw-ui-checkbox-label">
+ <input
+ name="<?php echo htmlspecialchars( $inputItem['name'] ); ?>"
+ id="<?php echo htmlspecialchars( $inputItem['name'] ); ?>"
+ type="checkbox" value="1"
+ tabindex="<?php echo $tabIndex++; ?>"
+ <?php if ( !empty( $inputItem['value'] ) ) {
+ echo 'checked="checked"';
+ } ?>
+ >
+ <?php $this->msg( $inputItem['msg'] ); ?>
+ </label>
+ <?php
+ } else {
+ // Not a checkbox.
+ // TODO (bug 31909) support other input types, e.g. select boxes.
?>
- <label for="<?php echo htmlspecialchars( $inputItem['name'] ); ?>"><?php
- $this->msgHtml( $inputItem['msg'] ) ?></label><?php
- }
- if( $inputItem['helptext'] !== false ) {
+ <?php if ( !empty( $inputItem['msg'] ) ) { ?>
+ <label for="<?php echo htmlspecialchars( $inputItem['name'] ); ?>">
+ <?php $this->msgWiki( $inputItem['msg'] ); ?>
+ </label>
+ <?php } ?>
+ <input
+ type="<?php echo htmlspecialchars( $inputItem['type'] ); ?>"
+ class="mw-input"
+ name="<?php echo htmlspecialchars( $inputItem['name'] ); ?>"
+ tabindex="<?php echo $tabIndex++; ?>"
+ value="<?php echo htmlspecialchars( $inputItem['value'] ); ?>"
+ id="<?php echo htmlspecialchars( $inputItem['name'] ); ?>"
+ />
+ <?php } ?>
+ <?php if ( $inputItem['helptext'] !== false ) { ?>
+ <div class="prefsectiontip">
+ <?php $this->msgWiki( $inputItem['helptext'] ); ?>
+ </div>
+ <?php } ?>
+ </div>
+ <?php
+ }
+ }
+
+ // JS attempts to move the image CAPTCHA below this part of the form,
+ // so skip one index.
+ $tabIndex++;
+ ?>
+ <div class="mw-submit">
+ <?php
+ echo Html::input(
+ 'wpCreateaccount',
+ $this->getMsg( $this->data['loggedin'] ? 'createacct-another-submit' : 'createacct-submit' ),
+ 'submit',
+ array(
+ 'class' => "mw-ui-button mw-ui-big mw-ui-block mw-ui-primary",
+ 'id' => 'wpCreateaccount',
+ 'tabindex' => $tabIndex++
+ )
+ );
?>
- <div class="prefsectiontip">
- <?php $this->msgWiki( $inputItem['helptext'] ); ?>
+ </div>
+ <?php if ( $this->haveData( 'uselang' ) ) { ?><input type="hidden" name="uselang" value="<?php $this->text( 'uselang' ); ?>" /><?php } ?>
+ <?php if ( $this->haveData( 'token' ) ) { ?><input type="hidden" name="wpCreateaccountToken" value="<?php $this->text( 'token' ); ?>" /><?php } ?>
+ </form>
+ <?php if ( !wfMessage( 'signupend' )->isDisabled() ) { ?>
+ <div id="signupend"><?php $this->html( 'signupend' ); ?></div>
+ <?php } ?>
+ </div>
+ <div class="mw-createacct-benefits-container">
+ <h2><?php $this->msg( 'createacct-benefit-heading' ); ?></h2>
+ <div class="mw-createacct-benefits-list">
+ <?php
+ for ( $benefitIdx = 1; $benefitIdx <= $this->data['benefitCount']; $benefitIdx++ ) {
+ // Pass each benefit's head text (by default a number) as a parameter to the body's message for PLURAL handling.
+ $headUnescaped = $this->getMsg( "createacct-benefit-head$benefitIdx" )->text();
+ ?>
+ <div class="mw-number-text <?php $this->msg( "createacct-benefit-icon$benefitIdx" ); ?>">
+ <h3><?php $this->msg( "createacct-benefit-head$benefitIdx" ); ?></h3>
+ <p><?php echo $this->getMsg( "createacct-benefit-body$benefitIdx" )->params( $headUnescaped )->escaped(); ?></p>
</div>
- <?php } ?>
- </td>
- </tr>
-<?php
- }
- }
-?>
- <tr>
- <td></td>
- <td class="mw-submit">
- <input type='submit' name="wpCreateaccount" id="wpCreateaccount"
- tabindex="<?php echo $tabIndex++; ?>"
- value="<?php $this->msg('createaccount') ?>" />
- </td>
- </tr>
- </table>
-<?php if( $this->haveData( 'uselang' ) ) { ?><input type="hidden" name="uselang" value="<?php $this->text( 'uselang' ); ?>" /><?php } ?>
-<?php if( $this->haveData( 'token' ) ) { ?><input type="hidden" name="wpCreateaccountToken" value="<?php $this->text( 'token' ); ?>" /><?php } ?>
-</form>
+ <?php } ?>
+ </div>
+ </div>
</div>
-<div id="signupend"><?php $this->html( 'signupend' ); ?></div>
<?php
}
diff --git a/includes/templates/Userlogin.php b/includes/templates/Userlogin.php
index 7bc0241a..5eb60948 100644
--- a/includes/templates/Userlogin.php
+++ b/includes/templates/Userlogin.php
@@ -1,6 +1,6 @@
<?php
/**
- * Html form for user login.
+ * Html form for user login (since 1.22 with VForm appearance).
*
* 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
@@ -21,156 +21,160 @@
* @ingroup Templates
*/
-/**
- * @defgroup Templates Templates
- */
+class UserloginTemplate extends BaseTemplate {
-/**
- * HTML template for Special:Userlogin form
- * @ingroup Templates
- */
-class UserloginTemplate extends QuickTemplate {
function execute() {
- if( $this->data['message'] ) {
+ global $wgCookieExpiration;
+ $expirationDays = ceil( $wgCookieExpiration / ( 3600 * 24 ) );
?>
- <div class="<?php $this->text('messagetype') ?>box">
- <?php if ( $this->data['messagetype'] == 'error' ) { ?>
- <strong><?php $this->msg( 'loginerror' )?></strong><br />
- <?php } ?>
- <?php $this->html('message') ?>
- </div>
- <div class="visualClear"></div>
-<?php } ?>
+<div class="mw-ui-container">
+ <?php if ( $this->haveData( 'languages' ) ) { ?>
+ <div id="languagelinks">
+ <p><?php $this->html( 'languages' ); ?></p>
+ </div>
+ <?php } ?>
+ <div id="userloginForm">
+ <form name="userlogin" class="mw-ui-vform" method="post" action="<?php $this->text( 'action' ); ?>">
+ <?php if ( $this->data['loggedin'] ) { ?>
+ <div class="warningbox">
+ <?php echo $this->getMsg( 'userlogin-loggedin' )->params( $this->data['loggedinuser'] )->parse(); ?>
+ </div>
+ <?php } ?>
+ <section class="mw-form-header">
+ <?php $this->html( 'header' ); /* extensions such as ConfirmEdit add form HTML here */ ?>
+ </section>
-<div id="loginstart"><?php $this->msgWiki( 'loginstart' ); ?></div>
-<div id="userloginForm">
-<form name="userlogin" method="post" action="<?php $this->text('action') ?>">
- <h2><?php $this->msg('login') ?></h2>
- <p id="userloginlink"><?php $this->html('link') ?></p>
- <?php $this->html('header'); /* pre-table point for form plugins... */ ?>
- <div id="userloginprompt"><?php $this->msgWiki('loginprompt') ?></div>
- <?php if( $this->haveData( 'languages' ) ) { ?><div id="languagelinks"><p><?php $this->html( 'languages' ); ?></p></div><?php } ?>
- <table>
- <tr>
- <td class="mw-label"><label for='wpName1'><?php $this->msg('yourname') ?></label></td>
- <td class="mw-input">
- <?php
- echo Html::input( 'wpName', $this->data['name'], 'text', array(
- 'class' => 'loginText',
- 'id' => 'wpName1',
- 'tabindex' => '1',
- 'size' => '20',
- 'required'
- # Can't do + array( 'autofocus' ) because + for arrays in PHP
- # only works right for associative arrays! Thanks, PHP.
- ) + ( $this->data['name'] ? array() : array( 'autofocus' => '' ) ) ); ?>
+ <?php if ( $this->data['message'] ) { ?>
+ <div class="<?php $this->text( 'messagetype' ); ?>box">
+ <?php if ( $this->data['messagetype'] == 'error' ) { ?>
+ <strong><?php $this->msg( 'loginerror' ); ?></strong>
+ <br />
+ <?php } ?>
+ <?php $this->html( 'message' ); ?>
+ </div>
+ <?php } ?>
- </td>
- </tr>
- <tr>
- <td class="mw-label"><label for='wpPassword1'><?php $this->msg('yourpassword') ?></label></td>
- <td class="mw-input">
- <?php
- echo Html::input( 'wpPassword', null, 'password', array(
- 'class' => 'loginPassword',
- 'id' => 'wpPassword1',
- 'tabindex' => '2',
- 'size' => '20'
- ) + ( $this->data['name'] ? array( 'autofocus' ) : array() ) ); ?>
+ <div>
+ <label for='wpName1'>
+ <?php
+ $this->msg( 'userlogin-yourname' );
- </td>
- </tr>
- <?php if( isset( $this->data['usedomain'] ) && $this->data['usedomain'] ) {
- $doms = "";
- foreach( $this->data['domainnames'] as $dom ) {
- $doms .= "<option>" . htmlspecialchars( $dom ) . "</option>";
- }
- ?>
- <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">
- <?php echo $doms ?>
- </select>
- </td>
- </tr>
- <?php }
+ if ( $this->data['secureLoginUrl'] ) {
+ echo Html::element( 'a', array(
+ 'href' => $this->data['secureLoginUrl'],
+ 'class' => 'mw-ui-flush-right mw-secure',
+ ), $this->getMsg( 'userlogin-signwithsecure' )->text() );
+ }
+ ?>
+ </label>
+ <?php
+ $extraAttrs = array();
+ echo Html::input( 'wpName', $this->data['name'], 'text', array(
+ 'class' => 'loginText',
+ 'id' => 'wpName1',
+ 'tabindex' => '1',
+ 'size' => '20',
+ // 'required' is blacklisted for now in Html.php due to browser issues.
+ // Keeping here in case that changes.
+ 'required' => true,
+ // Set focus to this field if it's blank.
+ 'autofocus' => !$this->data['name'],
+ 'placeholder' => $this->getMsg( 'userlogin-yourname-ph' )->text()
+ ) );
+ ?>
+ </div>
- if( $this->haveData( 'extrafields' ) ) {
- echo $this->data['extrafields'];
- }
+ <div>
+ <label for='wpPassword1'>
+ <?php
+ $this->msg( 'userlogin-yourpassword' );
- if( $this->data['canremember'] ) { ?>
- <tr>
- <td></td>
- <td class="mw-input">
+ if ( $this->data['useemail'] && $this->data['canreset'] && $this->data['resetlink'] === true ) {
+ echo ' ' . Linker::link(
+ SpecialPage::getTitleFor( 'PasswordReset' ),
+ $this->getMsg( 'userlogin-resetpassword-link' )->parse(),
+ array( 'class' => 'mw-ui-flush-right' )
+ );
+ }
+ ?>
+ </label>
<?php
- global $wgCookieExpiration;
- $expirationDays = ceil( $wgCookieExpiration / ( 3600 * 24 ) );
- echo Xml::checkLabel(
- wfMessage( 'remembermypassword' )->numParams( $expirationDays )->text(),
- 'wpRemember',
- 'wpRemember',
- $this->data['remember'],
- array( 'tabindex' => '8' )
- )
+ echo Html::input( 'wpPassword', null, 'password', array(
+ 'class' => 'loginPassword',
+ 'id' => 'wpPassword1',
+ 'tabindex' => '2',
+ 'size' => '20',
+ // Set focus to this field if username is filled in.
+ 'autofocus' => (bool)$this->data['name'],
+ 'placeholder' => $this->getMsg( 'userlogin-yourpassword-ph' )->text()
+ ) );
?>
- </td>
- </tr>
-<?php } ?>
-<?php if( $this->data['cansecurelogin'] ) { ?>
- <tr>
- <td></td>
- <td class="mw-input">
+ </div>
+
<?php
- echo Xml::checkLabel(
- wfMessage( 'securelogin-stick-https' )->text(),
- 'wpStickHTTPS',
- 'wpStickHTTPS',
- $this->data['stickHTTPS'],
- array( 'tabindex' => '9' )
- );
- ?>
- </td>
- </tr>
-<?php } ?>
- <tr>
- <td></td>
- <td class="mw-submit">
- <?php
- echo Html::input( 'wpLoginAttempt', wfMessage( 'login' )->text(), 'submit', array(
- 'id' => 'wpLoginAttempt',
- 'tabindex' => '9'
- ) );
- if ( $this->data['useemail'] && $this->data['canreset'] ) {
- if( $this->data['resetlink'] === true ) {
- echo '&#160;';
- echo Linker::link(
- SpecialPage::getTitleFor( 'PasswordReset' ),
- wfMessage( 'userlogin-resetlink' )
- );
- } elseif( $this->data['resetlink'] === null ) {
- echo '&#160;';
- echo Html::input(
- 'wpMailmypassword',
- wfMessage( 'mailmypassword' )->text(),
- 'submit', array(
- 'id' => 'wpMailmypassword',
- 'tabindex' => '10'
- )
- );
+ if ( isset( $this->data['usedomain'] ) && $this->data['usedomain'] ) {
+ $doms = "";
+ foreach ( $this->data['domainnames'] as $dom ) {
+ $doms .= "<option>" . htmlspecialchars( $dom ) . "</option>";
+ }
+ ?>
+ <div id="mw-user-domain-section">
+ <label for='wpDomain'><?php $this->msg( 'yourdomainname' ); ?></label>
+ <select name="wpDomain" value="<?php $this->text( 'domain' ); ?>" tabindex="3">
+ <?php echo $doms; ?>
+ </select>
+ </div>
+ <?php } ?>
+
+ <?php
+ if ( $this->haveData( 'extrafields' ) ) {
+ echo $this->data['extrafields'];
}
- } ?>
+ ?>
- </td>
- </tr>
- </table>
-<?php if( $this->haveData( 'uselang' ) ) { ?><input type="hidden" name="uselang" value="<?php $this->text( 'uselang' ); ?>" /><?php } ?>
-<?php if( $this->haveData( 'token' ) ) { ?><input type="hidden" name="wpLoginToken" value="<?php $this->text( 'token' ); ?>" /><?php } ?>
-</form>
+ <div>
+ <?php if ( $this->data['canremember'] ) { ?>
+ <label class="mw-ui-checkbox-label">
+ <input name="wpRemember" type="checkbox" value="1" id="wpRemember" tabindex="4"
+ <?php if ( $this->data['remember'] ) {
+ echo 'checked="checked"';
+ } ?>
+ >
+ <?php echo $this->getMsg( 'userlogin-remembermypassword' )->numParams( $expirationDays )->escaped(); ?>
+ </label>
+ <?php } ?>
+ </div>
+
+ <div>
+ <?php
+ echo Html::input( 'wpLoginAttempt', $this->getMsg( 'login' )->text(), 'submit', array(
+ 'id' => 'wpLoginAttempt',
+ 'tabindex' => '6',
+ 'class' => 'mw-ui-button mw-ui-big mw-ui-block mw-ui-primary'
+ ) );
+ ?>
+ </div>
+
+ <div id="mw-userlogin-help">
+ <?php echo $this->getMsg( 'userlogin-helplink' )->parse(); ?>
+ </div>
+ <?php if ( $this->haveData( 'createOrLoginHref' ) ) { ?>
+ <?php if ( $this->data['loggedin'] ) { ?>
+ <div id="mw-createaccount-another">
+ <h3 id="mw-userloginlink"><a href="<?php $this->text( 'createOrLoginHref' ); ?>" id="mw-createaccount-join" tabindex="7" class="mw-ui-button"><?php $this->msg( 'userlogin-createanother' ); ?></a></h3>
+ </div>
+ <?php } else { ?>
+ <div id="mw-createaccount-cta">
+ <h3 id="mw-userloginlink"><?php $this->msg( 'userlogin-noaccount' ); ?><a href="<?php $this->text( 'createOrLoginHref' ); ?>" id="mw-createaccount-join" tabindex="7" class="mw-ui-button mw-ui-constructive"><?php $this->msg( 'userlogin-joinproject' ); ?></a></h3>
+ </div>
+ <?php } ?>
+ <?php } ?>
+ <?php if ( $this->haveData( 'uselang' ) ) { ?><input type="hidden" name="uselang" value="<?php $this->text( 'uselang' ); ?>" /><?php } ?>
+ <?php if ( $this->haveData( 'token' ) ) { ?><input type="hidden" name="wpLoginToken" value="<?php $this->text( 'token' ); ?>" /><?php } ?>
+ <?php if ( $this->data['cansecurelogin'] ) {?><input type="hidden" name="wpForceHttps" value="<?php $this->text( 'stickhttps' ); ?>" /><?php } ?>
+ </form>
+ </div>
</div>
-<div id="loginend"><?php $this->html( 'loginend' ); ?></div>
<?php
}
diff --git a/includes/tidy.conf b/includes/tidy.conf
index 6c947295..4c4daed5 100644
--- a/includes/tidy.conf
+++ b/includes/tidy.conf
@@ -18,5 +18,5 @@ fix-backslash: no
fix-uri: no
# 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-empty-tags: html-meta, html-link, wbr
new-inline-tags: video, audio, source, track, bdi, data, time, mark
diff --git a/includes/upload/UploadBase.php b/includes/upload/UploadBase.php
index e72669d4..2260241d 100644
--- a/includes/upload/UploadBase.php
+++ b/includes/upload/UploadBase.php
@@ -72,19 +72,20 @@ abstract class UploadBase {
* @return string
*/
public function getVerificationErrorCode( $error ) {
- $code_to_status = array(self::EMPTY_FILE => 'empty-file',
- self::FILE_TOO_LARGE => 'file-too-large',
- self::FILETYPE_MISSING => 'filetype-missing',
- self::FILETYPE_BADTYPE => 'filetype-banned',
- self::MIN_LENGTH_PARTNAME => 'filename-tooshort',
- self::ILLEGAL_FILENAME => 'illegal-filename',
- self::OVERWRITE_EXISTING_FILE => 'overwrite',
- self::VERIFICATION_ERROR => 'verification-error',
- self::HOOK_ABORTED => 'hookaborted',
- self::WINDOWS_NONASCII_FILENAME => 'windows-nonascii-filename',
- self::FILENAME_TOO_LONG => 'filename-toolong',
+ $code_to_status = array(
+ self::EMPTY_FILE => 'empty-file',
+ self::FILE_TOO_LARGE => 'file-too-large',
+ self::FILETYPE_MISSING => 'filetype-missing',
+ self::FILETYPE_BADTYPE => 'filetype-banned',
+ self::MIN_LENGTH_PARTNAME => 'filename-tooshort',
+ self::ILLEGAL_FILENAME => 'illegal-filename',
+ self::OVERWRITE_EXISTING_FILE => 'overwrite',
+ self::VERIFICATION_ERROR => 'verification-error',
+ self::HOOK_ABORTED => 'hookaborted',
+ self::WINDOWS_NONASCII_FILENAME => 'windows-nonascii-filename',
+ self::FILENAME_TOO_LONG => 'filename-toolong',
);
- if( isset( $code_to_status[$error] ) ) {
+ if ( isset( $code_to_status[$error] ) ) {
return $code_to_status[$error];
}
@@ -137,7 +138,7 @@ abstract class UploadBase {
public static function createFromRequest( &$request, $type = null ) {
$type = $type ? $type : $request->getVal( 'wpSourceType', 'File' );
- if( !$type ) {
+ if ( !$type ) {
return null;
}
@@ -150,18 +151,18 @@ abstract class UploadBase {
if ( is_null( $className ) ) {
$className = 'UploadFrom' . $type;
wfDebug( __METHOD__ . ": class name: $className\n" );
- if( !in_array( $type, self::$uploadHandlers ) ) {
+ if ( !in_array( $type, self::$uploadHandlers ) ) {
return null;
}
}
// Check whether this upload class is enabled
- if( !call_user_func( array( $className, 'isEnabled' ) ) ) {
+ if ( !call_user_func( array( $className, 'isEnabled' ) ) ) {
return null;
}
// Check whether the request is valid
- if( !call_user_func( array( $className, 'isValidRequest' ), $request ) ) {
+ if ( !call_user_func( array( $className, 'isValidRequest' ), $request ) ) {
return null;
}
@@ -188,7 +189,9 @@ abstract class UploadBase {
* @since 1.18
* @return string
*/
- public function getSourceType() { return null; }
+ public function getSourceType() {
+ return null;
+ }
/**
* Initialize the path information
@@ -253,7 +256,7 @@ abstract class UploadBase {
wfProfileIn( __METHOD__ );
$repo = RepoGroup::singleton()->getLocalRepo();
if ( $repo->isVirtualUrl( $srcPath ) ) {
- // @TODO: just make uploads work with storage paths
+ // @todo just make uploads work with storage paths
// UploadFromStash loads files via virtual URLs
$tmpFile = $repo->getLocalCopy( $srcPath );
$tmpFile->bind( $this ); // keep alive with $this
@@ -274,7 +277,7 @@ abstract class UploadBase {
/**
* If there was no filename or a zero size given, give up quick.
*/
- if( $this->isEmptyFile() ) {
+ if ( $this->isEmptyFile() ) {
wfProfileOut( __METHOD__ );
return array( 'status' => self::EMPTY_FILE );
}
@@ -283,7 +286,7 @@ abstract class UploadBase {
* Honor $wgMaxUploadSize
*/
$maxSize = self::getMaxUploadSize( $this->getSourceType() );
- if( $this->mFileSize > $maxSize ) {
+ if ( $this->mFileSize > $maxSize ) {
wfProfileOut( __METHOD__ );
return array(
'status' => self::FILE_TOO_LARGE,
@@ -297,7 +300,7 @@ abstract class UploadBase {
* probably not accept it.
*/
$verification = $this->verifyFile();
- if( $verification !== true ) {
+ if ( $verification !== true ) {
wfProfileOut( __METHOD__ );
return array(
'status' => self::VERIFICATION_ERROR,
@@ -309,13 +312,13 @@ abstract class UploadBase {
* Make sure this file can be created
*/
$result = $this->validateName();
- if( $result !== true ) {
+ if ( $result !== true ) {
wfProfileOut( __METHOD__ );
return $result;
}
$error = '';
- if( !wfRunHooks( 'UploadVerification',
+ if ( !wfRunHooks( 'UploadVerification',
array( $this->mDestName, $this->mTempPath, &$error ) ) )
{
wfProfileOut( __METHOD__ );
@@ -332,11 +335,11 @@ abstract class UploadBase {
* @return mixed true if valid, otherwise and array with 'status'
* and other keys
**/
- protected function validateName() {
+ public function validateName() {
$nt = $this->getTitle();
- if( is_null( $nt ) ) {
+ if ( is_null( $nt ) ) {
$result = array( 'status' => $this->mTitleError );
- if( $this->mTitleError == self::ILLEGAL_FILENAME ) {
+ if ( $this->mTitleError == self::ILLEGAL_FILENAME ) {
$result['filtered'] = $this->mFilteredName;
}
if ( $this->mTitleError == self::FILETYPE_BADTYPE ) {
@@ -353,7 +356,7 @@ abstract class UploadBase {
}
/**
- * Verify the mime type
+ * Verify the mime type.
*
* @note Only checks that it is not an evil mime. The does it have
* correct extension given its mime type check is in verifyFile.
@@ -364,7 +367,7 @@ abstract class UploadBase {
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__ );
@@ -391,6 +394,7 @@ abstract class UploadBase {
return true;
}
+
/**
* Verifies that it's ok to include the uploaded file
*
@@ -417,6 +421,7 @@ abstract class UploadBase {
}
}
+
$handler = MediaHandler::getHandler( $mime );
if ( $handler ) {
$handlerStatus = $handler->verifyUpload( $this->mTempPath );
@@ -450,8 +455,7 @@ abstract class UploadBase {
global $wgAllowJavaUploads, $wgDisableUploadScriptChecks;
wfProfileIn( __METHOD__ );
- # get the title, even though we are doing nothing with it, because
- # we need to populate mFinalExtension
+ # getTitle() sets some internal parameters like $this->mFinalExtension
$this->getTitle();
$this->mFileProps = FSFile::getPropsFromPath( $this->mTempPath, $this->mFinalExtension );
@@ -466,12 +470,12 @@ abstract class UploadBase {
# check for htmlish code and javascript
if ( !$wgDisableUploadScriptChecks ) {
- if( self::detectScript( $this->mTempPath, $mime, $this->mFinalExtension ) ) {
+ if ( self::detectScript( $this->mTempPath, $mime, $this->mFinalExtension ) ) {
wfProfileOut( __METHOD__ );
return array( 'uploadscripted' );
}
- if( $this->mFinalExtension == 'svg' || $mime == 'image/svg+xml' ) {
- if( $this->detectScriptInSvg( $this->mTempPath ) ) {
+ if ( $this->mFinalExtension == 'svg' || $mime == 'image/svg+xml' ) {
+ if ( $this->detectScriptInSvg( $this->mTempPath ) ) {
wfProfileOut( __METHOD__ );
return array( 'uploadscripted' );
}
@@ -560,7 +564,7 @@ abstract class UploadBase {
* to modify it by uploading a new revision.
*/
$nt = $this->getTitle();
- if( is_null( $nt ) ) {
+ if ( is_null( $nt ) ) {
return true;
}
$permErrors = $nt->getUserPermissionsErrors( 'edit', $user );
@@ -570,7 +574,7 @@ abstract class UploadBase {
} else {
$permErrorsCreate = array();
}
- if( $permErrors || $permErrorsUpload || $permErrorsCreate ) {
+ if ( $permErrors || $permErrorsUpload || $permErrorsCreate ) {
$permErrors = array_merge( $permErrors, wfArrayDiff2( $permErrorsUpload, $permErrors ) );
$permErrors = array_merge( $permErrors, wfArrayDiff2( $permErrorsCreate, $permErrors ) );
return $permErrors;
@@ -607,16 +611,17 @@ abstract class UploadBase {
$comparableName = str_replace( ' ', '_', $this->mDesiredDestName );
$comparableName = Title::capitalize( $comparableName, NS_FILE );
- if( $this->mDesiredDestName != $filename && $comparableName != $filename ) {
+ if ( $this->mDesiredDestName != $filename && $comparableName != $filename ) {
$warnings['badfilename'] = $filename;
}
// Check whether the file extension is on the unwanted list
global $wgCheckFileExtensions, $wgFileExtensions;
if ( $wgCheckFileExtensions ) {
- if ( !$this->checkFileExtension( $this->mFinalExtension, $wgFileExtensions ) ) {
+ $extensions = array_unique( $wgFileExtensions );
+ if ( !$this->checkFileExtension( $this->mFinalExtension, $extensions ) ) {
$warnings['filetype-unwanted-type'] = array( $this->mFinalExtension,
- $wgLang->commaList( $wgFileExtensions ), count( $wgFileExtensions ) );
+ $wgLang->commaList( $extensions ), count( $extensions ) );
}
}
@@ -630,7 +635,7 @@ abstract class UploadBase {
}
$exists = self::getExistsWarning( $localFile );
- if( $exists !== false ) {
+ if ( $exists !== false ) {
$warnings['exists'] = $exists;
}
@@ -640,11 +645,11 @@ abstract class UploadBase {
$title = $this->getTitle();
// Remove all matches against self
foreach ( $dupes as $key => $dupe ) {
- if( $title->equals( $dupe->getTitle() ) ) {
+ if ( $title->equals( $dupe->getTitle() ) ) {
unset( $dupes[$key] );
}
}
- if( $dupes ) {
+ if ( $dupes ) {
$warnings['duplicate'] = $dupes;
}
@@ -682,9 +687,9 @@ abstract class UploadBase {
$user
);
- if( $status->isGood() ) {
+ if ( $status->isGood() ) {
if ( $watch ) {
- $user->addWatch( $this->getLocalFile()->getTitle() );
+ WatchAction::doWatch( $this->getLocalFile()->getTitle(), $user, WatchedItem::IGNORE_USER_RIGHTS );
}
wfRunHooks( 'UploadComplete', array( &$this ) );
}
@@ -703,7 +708,6 @@ abstract class UploadBase {
if ( $this->mTitle !== false ) {
return $this->mTitle;
}
-
/* Assume that if a user specified File:Something.jpg, this is an error
* and that the namespace prefix needs to be stripped of.
*/
@@ -729,7 +733,7 @@ abstract class UploadBase {
$this->mFilteredName = wfStripIllegalFilenameChars( $this->mFilteredName );
/* Normalize to title form before we do any further processing */
$nt = Title::makeTitleSafe( NS_FILE, $this->mFilteredName );
- if( is_null( $nt ) ) {
+ if ( is_null( $nt ) ) {
$this->mTitleError = self::ILLEGAL_FILENAME;
return $this->mTitle = null;
}
@@ -741,7 +745,7 @@ abstract class UploadBase {
*/
list( $partname, $ext ) = $this->splitExtensions( $this->mFilteredName );
- if( count( $ext ) ) {
+ if ( count( $ext ) ) {
$this->mFinalExtension = trim( $ext[count( $ext ) - 1] );
} else {
$this->mFinalExtension = '';
@@ -789,13 +793,13 @@ abstract class UploadBase {
# If there was more than one "extension", reassemble the base
# filename to prevent bogus complaints about length
- if( count( $ext ) > 1 ) {
- for( $i = 0; $i < count( $ext ) - 1; $i++ ) {
+ if ( count( $ext ) > 1 ) {
+ for ( $i = 0; $i < count( $ext ) - 1; $i++ ) {
$partname .= '.' . $ext[$i];
}
}
- if( strlen( $partname ) < 1 ) {
+ if ( strlen( $partname ) < 1 ) {
$this->mTitleError = self::MIN_LENGTH_PARTNAME;
return $this->mTitle = null;
}
@@ -809,7 +813,7 @@ abstract class UploadBase {
* @return LocalFile|null
*/
public function getLocalFile() {
- if( is_null( $this->mLocalFile ) ) {
+ if ( is_null( $this->mLocalFile ) ) {
$nt = $this->getTitle();
$this->mLocalFile = is_null( $nt ) ? null : wfLocalFile( $nt );
}
@@ -922,23 +926,29 @@ abstract class UploadBase {
public static function verifyExtension( $mime, $extension ) {
$magic = MimeMagic::singleton();
- if ( !$mime || $mime == 'unknown' || $mime == 'unknown/unknown' )
+ if ( !$mime || $mime == 'unknown' || $mime == 'unknown/unknown' ) {
if ( !$magic->isRecognizableExtension( $extension ) ) {
wfDebug( __METHOD__ . ": passing file with unknown detected mime type; " .
"unrecognized extension '$extension', can't verify\n" );
return true;
} else {
- wfDebug( __METHOD__ . ": rejecting file with unknown detected mime type; ".
+ wfDebug( __METHOD__ . ": rejecting file with unknown detected mime type; " .
"recognized extension '$extension', so probably invalid file\n" );
return false;
}
+ }
$match = $magic->isMatchingExtension( $extension, $mime );
if ( $match === null ) {
- wfDebug( __METHOD__ . ": no file extension known for mime type $mime, passing file\n" );
- return true;
- } elseif( $match === true ) {
+ if ( $magic->getTypesForExtension( $extension ) !== null ) {
+ wfDebug( __METHOD__ . ": No extension known for $mime, but we know a mime for $extension\n" );
+ return false;
+ } else {
+ wfDebug( __METHOD__ . ": no file extension known for mime type $mime, passing file\n" );
+ return true;
+ }
+ } elseif ( $match === true ) {
wfDebug( __METHOD__ . ": mime type $mime matches extension $extension, passing file\n" );
#TODO: if it's a bitmap, make sure PHP or ImageMagic resp. can handle it!
@@ -968,7 +978,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' );
@@ -978,21 +988,21 @@ abstract class UploadBase {
$chunk = strtolower( $chunk );
- if( !$chunk ) {
+ if ( !$chunk ) {
wfProfileOut( __METHOD__ );
return false;
}
# decode from UTF-16 if needed (could be used for obfuscation).
- if( substr( $chunk, 0, 2 ) == "\xfe\xff" ) {
+ if ( substr( $chunk, 0, 2 ) == "\xfe\xff" ) {
$enc = 'UTF-16BE';
- } elseif( substr( $chunk, 0, 2 ) == "\xff\xfe" ) {
+ } elseif ( substr( $chunk, 0, 2 ) == "\xff\xfe" ) {
$enc = 'UTF-16LE';
} else {
$enc = null;
}
- if( $enc ) {
+ if ( $enc ) {
$chunk = iconv( $enc, "ASCII//IGNORE", $chunk );
}
@@ -1042,12 +1052,12 @@ abstract class UploadBase {
'<table'
);
- if( !$wgAllowTitlesInSVG && $extension !== 'svg' && $mime !== 'image/svg' ) {
+ if ( !$wgAllowTitlesInSVG && $extension !== 'svg' && $mime !== 'image/svg' ) {
$tags[] = '<title';
}
- foreach( $tags as $tag ) {
- if( false !== strpos( $chunk, $tag ) ) {
+ foreach ( $tags as $tag ) {
+ if ( false !== strpos( $chunk, $tag ) ) {
wfDebug( __METHOD__ . ": found something that may make it be mistaken for html: $tag\n" );
wfProfileOut( __METHOD__ );
return true;
@@ -1062,21 +1072,21 @@ abstract class UploadBase {
$chunk = Sanitizer::decodeCharReferences( $chunk );
# look for script-types
- if( preg_match( '!type\s*=\s*[\'"]?\s*(?:\w*/)?(?:ecma|java)!sim', $chunk ) ) {
+ if ( preg_match( '!type\s*=\s*[\'"]?\s*(?:\w*/)?(?:ecma|java)!sim', $chunk ) ) {
wfDebug( __METHOD__ . ": found script types\n" );
wfProfileOut( __METHOD__ );
return true;
}
# look for html-style script-urls
- if( preg_match( '!(?:href|src|data)\s*=\s*[\'"]?\s*(?:ecma|java)script:!sim', $chunk ) ) {
+ if ( preg_match( '!(?:href|src|data)\s*=\s*[\'"]?\s*(?:ecma|java)script:!sim', $chunk ) ) {
wfDebug( __METHOD__ . ": found html-style script urls\n" );
wfProfileOut( __METHOD__ );
return true;
}
# look for css-style script-urls
- if( preg_match( '!url\s*\(\s*[\'"]?\s*(?:ecma|java)script:!sim', $chunk ) ) {
+ if ( preg_match( '!url\s*\(\s*[\'"]?\s*(?:ecma|java)script:!sim', $chunk ) ) {
wfDebug( __METHOD__ . ": found css-style script urls\n" );
wfProfileOut( __METHOD__ );
return true;
@@ -1112,7 +1122,7 @@ abstract class UploadBase {
// bytes. There shouldn't be a legitimate reason for this to happen.
wfDebug( __METHOD__ . ": Unmatched XML declaration start\n" );
return true;
- } elseif ( substr( $contents, 0, 4) == "\x4C\x6F\xA7\x94" ) {
+ } elseif ( substr( $contents, 0, 4 ) == "\x4C\x6F\xA7\x94" ) {
// EBCDIC encoded XML
wfDebug( __METHOD__ . ": EBCDIC Encoded XML\n" );
return true;
@@ -1164,77 +1174,77 @@ abstract class UploadBase {
/*
* check for elements that can contain javascript
*/
- if( $strippedElement == 'script' ) {
+ if ( $strippedElement == 'script' ) {
wfDebug( __METHOD__ . ": Found script element '$element' in uploaded file.\n" );
return true;
}
# e.g., <svg xmlns="http://www.w3.org/2000/svg"> <handler xmlns:ev="http://www.w3.org/2001/xml-events" ev:event="load">alert(1)</handler> </svg>
- if( $strippedElement == 'handler' ) {
+ if ( $strippedElement == 'handler' ) {
wfDebug( __METHOD__ . ": Found scriptable element '$element' in uploaded file.\n" );
return true;
}
# SVG reported in Feb '12 that used xml:stylesheet to generate javascript block
- if( $strippedElement == 'stylesheet' ) {
+ if ( $strippedElement == 'stylesheet' ) {
wfDebug( __METHOD__ . ": Found scriptable element '$element' in uploaded file.\n" );
return true;
}
- foreach( $attribs as $attrib => $value ) {
+ foreach ( $attribs as $attrib => $value ) {
$stripped = $this->stripXmlNamespace( $attrib );
$value = strtolower( $value );
- if( substr( $stripped, 0, 2 ) == 'on' ) {
+ if ( substr( $stripped, 0, 2 ) == 'on' ) {
wfDebug( __METHOD__ . ": Found event-handler attribute '$attrib'='$value' in uploaded file.\n" );
return true;
}
# href with javascript target
- if( $stripped == 'href' && strpos( strtolower( $value ), 'javascript:' ) !== false ) {
+ if ( $stripped == 'href' && strpos( strtolower( $value ), 'javascript:' ) !== false ) {
wfDebug( __METHOD__ . ": Found script in href attribute '$attrib'='$value' in uploaded file.\n" );
return true;
}
# href with embedded svg as target
- if( $stripped == 'href' && preg_match( '!data:[^,]*image/svg[^,]*,!sim', $value ) ) {
+ 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 embedded (text/xml) svg as target
- if( $stripped == 'href' && preg_match( '!data:[^,]*text/xml[^,]*,!sim', $value ) ) {
+ 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;
}
# use set/animate to add event-handler attribute to parent
- if( ( $strippedElement == 'set' || $strippedElement == 'animate' ) && $stripped == 'attributename' && substr( $value, 0, 2 ) == 'on' ) {
+ if ( ( $strippedElement == 'set' || $strippedElement == 'animate' ) && $stripped == 'attributename' && substr( $value, 0, 2 ) == 'on' ) {
wfDebug( __METHOD__ . ": Found svg setting event-handler attribute with \"<$strippedElement $stripped='$value'...\" in uploaded file.\n" );
return true;
}
# use set to add href attribute to parent element
- if( $strippedElement == 'set' && $stripped == 'attributename' && strpos( $value, 'href' ) !== false ) {
+ if ( $strippedElement == 'set' && $stripped == 'attributename' && strpos( $value, 'href' ) !== false ) {
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 ) ) {
+ 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;
}
# 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 ) ) {
+ 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 ) ) {
wfDebug( __METHOD__ . ": Found svg setting a style with remote url '$attrib'='$value' in uploaded file.\n" );
@@ -1244,7 +1254,7 @@ abstract class UploadBase {
}
# image filters can pull in url, which could be svg that executes scripts
- if( $strippedElement == 'image' && $stripped == 'filter' && preg_match( '!url\s*\(!sim', $value ) ) {
+ if ( $strippedElement == 'image' && $stripped == 'filter' && preg_match( '!url\s*\(!sim', $value ) ) {
wfDebug( __METHOD__ . ": Found image filter with url: \"<$strippedElement $stripped='$value'...\" in uploaded file.\n" );
return true;
}
@@ -1314,7 +1324,7 @@ abstract class UploadBase {
# NOTE: there's a 50 line workaround to make stderr redirection work on windows, too.
# that does not seem to be worth the pain.
# Ask me (Duesentrieb) about it if it's ever needed.
- $output = wfShellExec( "$command 2>&1", $exitCode );
+ $output = wfShellExecWithStderr( $command, $exitCode );
# map exit code to AV_xxx constants.
$mappedCode = $exitCode;
@@ -1374,8 +1384,8 @@ abstract class UploadBase {
private function checkOverwrite( $user ) {
// First check whether the local file can be overwritten
$file = $this->getLocalFile();
- if( $file->exists() ) {
- if( !self::userCanReUpload( $user, $file ) ) {
+ if ( $file->exists() ) {
+ if ( !self::userCanReUpload( $user, $file ) ) {
return array( 'fileexists-forbidden', $file->getName() );
} else {
return true;
@@ -1401,13 +1411,13 @@ abstract class UploadBase {
* @return Boolean
*/
public static function userCanReUpload( User $user, $img ) {
- if( $user->isAllowed( 'reupload' ) ) {
+ if ( $user->isAllowed( 'reupload' ) ) {
return true; // non-conditional
}
- if( !$user->isAllowed( 'reupload-own' ) ) {
+ if ( !$user->isAllowed( 'reupload-own' ) ) {
return false;
}
- if( is_string( $img ) ) {
+ if ( is_string( $img ) ) {
$img = wfLocalFile( $img );
}
if ( !( $img instanceof LocalFile ) ) {
@@ -1429,11 +1439,11 @@ abstract class UploadBase {
* @return mixed False if the file does not exists, else an array
*/
public static function getExistsWarning( $file ) {
- if( $file->exists() ) {
+ if ( $file->exists() ) {
return array( 'warning' => 'exists', 'file' => $file );
}
- if( $file->getTitle()->getArticleID() ) {
+ if ( $file->getTitle()->getArticleID() ) {
return array( 'warning' => 'page-exists', 'file' => $file );
}
@@ -1441,7 +1451,7 @@ abstract class UploadBase {
return array( 'warning' => 'was-deleted', 'file' => $file );
}
- if( strpos( $file->getName(), '.' ) == false ) {
+ if ( strpos( $file->getName(), '.' ) == false ) {
$partname = $file->getName();
$extension = '';
} else {
@@ -1460,7 +1470,7 @@ abstract class UploadBase {
$nt_lc = Title::makeTitle( NS_FILE, "{$partname}.{$normalizedExtension}" );
$file_lc = wfLocalFile( $nt_lc );
- if( $file_lc->exists() ) {
+ if ( $file_lc->exists() ) {
return array(
'warning' => 'exists-normalized',
'file' => $file,
@@ -1484,7 +1494,7 @@ abstract class UploadBase {
# Check for filenames like 50px- or 180px-, these are mostly thumbnails
$nt_thb = Title::newFromText( substr( $partname, strpos( $partname, '-' ) + 1 ) . '.' . $extension, NS_FILE );
$file_thb = wfLocalFile( $nt_thb );
- if( $file_thb->exists() ) {
+ if ( $file_thb->exists() ) {
return array(
'warning' => 'thumb',
'file' => $file,
@@ -1500,7 +1510,7 @@ abstract class UploadBase {
}
}
- foreach( self::getFilenamePrefixBlacklist() as $prefix ) {
+ foreach ( self::getFilenamePrefixBlacklist() as $prefix ) {
if ( substr( $partname, 0, strlen( $prefix ) ) == $prefix ) {
return array(
'warning' => 'bad-prefix',
@@ -1536,9 +1546,9 @@ abstract class UploadBase {
public static function getFilenamePrefixBlacklist() {
$blacklist = array();
$message = wfMessage( 'filename-prefix-blacklist' )->inContentLanguage();
- if( !$message->isDisabled() ) {
+ if ( !$message->isDisabled() ) {
$lines = explode( "\n", $message->plain() );
- foreach( $lines as $line ) {
+ foreach ( $lines as $line ) {
// Remove comment lines
$comment = substr( trim( $line ), 0, 1 );
if ( $comment == '#' || $comment == '' ) {
@@ -1547,7 +1557,7 @@ abstract class UploadBase {
// Remove additional comments after a prefix
$comment = strpos( $line, '#' );
if ( $comment > 0 ) {
- $line = substr( $line, 0, $comment-1 );
+ $line = substr( $line, 0, $comment - 1 );
}
$blacklist[] = trim( $line );
}
diff --git a/includes/upload/UploadFromChunks.php b/includes/upload/UploadFromChunks.php
index 4b331e98..2e0b9444 100644
--- a/includes/upload/UploadFromChunks.php
+++ b/includes/upload/UploadFromChunks.php
@@ -31,7 +31,7 @@ class UploadFromChunks extends UploadFromFile {
protected $mOffset, $mChunkIndex, $mFileKey, $mVirtualTempPath;
/**
- * Setup local pointers to stash, repo and user ( similar to UploadFromStash )
+ * Setup local pointers to stash, repo and user (similar to UploadFromStash)
*
* @param $user User
* @param $stash UploadStash
@@ -41,16 +41,16 @@ class UploadFromChunks extends UploadFromFile {
// user object. sometimes this won't exist, as when running from cron.
$this->user = $user;
- if( $repo ) {
+ if ( $repo ) {
$this->repo = $repo;
} else {
$this->repo = RepoGroup::singleton()->getLocalRepo();
}
- if( $stash ) {
+ if ( $stash ) {
$this->stash = $stash;
} else {
- if( $user ) {
+ if ( $user ) {
wfDebug( __METHOD__ . " creating new UploadFromChunks instance for " . $user->getId() . "\n" );
} else {
wfDebug( __METHOD__ . " creating new UploadFromChunks instance with no user\n" );
@@ -74,7 +74,7 @@ class UploadFromChunks extends UploadFromFile {
$this->verifyChunk();
// Create a local stash target
$this->mLocalFile = parent::stashFile();
- // Update the initial file offset ( based on file size )
+ // Update the initial file offset (based on file size)
$this->mOffset = $this->mLocalFile->getSize();
$this->mFileKey = $this->mLocalFile->getFileKey();
@@ -114,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 );
}
@@ -129,7 +129,7 @@ class UploadFromChunks extends UploadFromFile {
$tStart = microtime( true );
$status = $this->repo->concatenate( $fileList, $tmpPath, FileRepo::DELETE_SOURCE );
$tAmount = microtime( true ) - $tStart;
- if( !$status->isOk() ) {
+ if ( !$status->isOk() ) {
return $status;
}
wfDebugLog( 'fileconcatenate', "Combined $i chunks in $tAmount seconds.\n" );
@@ -144,7 +144,7 @@ class UploadFromChunks extends UploadFromFile {
}
// Update the mTempPath and mLocalFile
- // ( for FileUpload or normal Stash to take over )
+ // (for FileUpload or normal Stash to take over)
$tStart = microtime( true );
$this->mLocalFile = parent::stashFile( $this->user );
$tAmount = microtime( true ) - $tStart;
@@ -193,7 +193,7 @@ class UploadFromChunks extends UploadFromFile {
// Get the offset before we add the chunk to the file system
$preAppendOffset = $this->getOffset();
- if ( $preAppendOffset + $chunkSize > $this->getMaxUploadSize()) {
+ if ( $preAppendOffset + $chunkSize > $this->getMaxUploadSize() ) {
$status = Status::newFatal( 'file-too-large' );
} else {
// Make sure the client is uploading the correct chunk with a matching offset.
@@ -210,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
@@ -277,7 +277,7 @@ class UploadFromChunks extends UploadFromFile {
* @return Integer index of the current chunk
*/
private function getChunkIndex() {
- if( $this->mChunkIndex !== null ) {
+ if ( $this->mChunkIndex !== null ) {
return $this->mChunkIndex;
}
return 0;
@@ -327,7 +327,7 @@ class UploadFromChunks extends UploadFromFile {
}
private function getChunkFileKey( $index = null ) {
- if( $index === null ) {
+ if ( $index === null ) {
$index = $this->getChunkIndex();
}
return $this->mFileKey . '.' . $index;
@@ -346,7 +346,7 @@ class UploadFromChunks extends UploadFromFile {
$res = $this->verifyPartialFile();
$this->mDesiredDestName = $oldDesiredDestName;
$this->mTitle = false;
- if( is_array( $res ) ) {
+ if ( is_array( $res ) ) {
throw new UploadChunkVerificationException( $res[0] );
}
}
diff --git a/includes/upload/UploadFromFile.php b/includes/upload/UploadFromFile.php
index ab2a7a39..a00ed327 100644
--- a/includes/upload/UploadFromFile.php
+++ b/includes/upload/UploadFromFile.php
@@ -40,7 +40,7 @@ class UploadFromFile extends UploadBase {
function initializeFromRequest( &$request ) {
$upload = $request->getUpload( 'wpUploadFile' );
$desiredDestName = $request->getText( 'wpDestFile' );
- if( !$desiredDestName ) {
+ if ( !$desiredDestName ) {
$desiredDestName = $upload->getName();
}
diff --git a/includes/upload/UploadFromStash.php b/includes/upload/UploadFromStash.php
index c82103cb..cb85fc63 100644
--- a/includes/upload/UploadFromStash.php
+++ b/includes/upload/UploadFromStash.php
@@ -45,16 +45,16 @@ class UploadFromStash extends UploadBase {
// user object. sometimes this won't exist, as when running from cron.
$this->user = $user;
- if( $repo ) {
+ if ( $repo ) {
$this->repo = $repo;
} else {
$this->repo = RepoGroup::singleton()->getLocalRepo();
}
- if( $stash ) {
+ if ( $stash ) {
$this->stash = $stash;
} else {
- if( $user ) {
+ if ( $user ) {
wfDebug( __METHOD__ . " creating new UploadStash instance for " . $user->getId() . "\n" );
} else {
wfDebug( __METHOD__ . " creating new UploadStash instance with no user\n" );
diff --git a/includes/upload/UploadFromUrl.php b/includes/upload/UploadFromUrl.php
index 70b69034..0201d5f4 100644
--- a/includes/upload/UploadFromUrl.php
+++ b/includes/upload/UploadFromUrl.php
@@ -34,6 +34,8 @@ class UploadFromUrl extends UploadBase {
protected $mTempPath, $mTmpHandle;
+ protected static $allowedUrls = array();
+
/**
* Checks if the user is allowed to use the upload-by-URL feature. If the
* user is not allowed, return the name of the user right as a string. If
@@ -77,7 +79,7 @@ class UploadFromUrl extends UploadBase {
return false;
}
$valid = false;
- foreach( $wgCopyUploadsDomains as $domain ) {
+ foreach ( $wgCopyUploadsDomains as $domain ) {
// See if the domain for the upload matches this whitelisted domain
$whitelistedDomainPieces = explode( '.', $domain );
$uploadDomainPieces = explode( '.', $parsedUrl['host'] );
@@ -105,6 +107,21 @@ class UploadFromUrl extends UploadBase {
}
/**
+ * Checks whether the URL is not allowed.
+ *
+ * @param $url string
+ * @return bool
+ */
+ public static function isAllowedUrl( $url ) {
+ if ( !isset( self::$allowedUrls[$url] ) ) {
+ $allowed = true;
+ wfRunHooks( 'IsUploadAllowedFromUrl', array( $url, &$allowed ) );
+ self::$allowedUrls[$url] = $allowed;
+ }
+ return self::$allowedUrls[$url];
+ }
+
+ /**
* Entry point for API upload
*
* @param $name string
@@ -160,21 +177,30 @@ class UploadFromUrl extends UploadBase {
/**
* @return string
*/
- public function getSourceType() { return 'url'; }
+ public function getSourceType() {
+ return 'url';
+ }
/**
+ * Download the file (if not async)
+ *
+ * @param Array $httpOptions Array of options for MWHttpRequest. Ignored if async.
+ * This could be used to override the timeout on the http request.
* @return Status
*/
- public function fetchFile() {
+ public function fetchFile( $httpOptions = array() ) {
if ( !Http::isValidURI( $this->mUrl ) ) {
return Status::newFatal( 'http-invalid-url' );
}
- if( !self::isAllowedHost( $this->mUrl ) ) {
+ if ( !self::isAllowedHost( $this->mUrl ) ) {
return Status::newFatal( 'upload-copy-upload-invalid-domain' );
}
+ if ( !self::isAllowedUrl( $this->mUrl ) ) {
+ return Status::newFatal( 'upload-copy-upload-invalid-url' );
+ }
if ( !$this->mAsync ) {
- return $this->reallyFetchFile();
+ return $this->reallyFetchFile( $httpOptions );
}
return Status::newGood();
}
@@ -211,9 +237,12 @@ class UploadFromUrl extends UploadBase {
/**
* Download the file, save it to the temporary file and update the file
* size and set $mRemoveTempFile to true.
+ *
+ * @param Array $httpOptions Array of options for MWHttpRequest
* @return Status
*/
- protected function reallyFetchFile() {
+ protected function reallyFetchFile( $httpOptions = array() ) {
+ global $wgCopyUploadProxy, $wgCopyUploadTimeout;
if ( $this->mTempPath === false ) {
return Status::newFatal( 'tmp-create-error' );
}
@@ -227,13 +256,15 @@ class UploadFromUrl extends UploadBase {
$this->mRemoveTempFile = true;
$this->mFileSize = 0;
- $options = array(
- 'followRedirects' => true
+ $options = $httpOptions + array(
+ 'followRedirects' => true,
);
- global $wgCopyUploadProxy;
if ( $wgCopyUploadProxy !== false ) {
$options['proxy'] = $wgCopyUploadProxy;
}
+ if ( $wgCopyUploadTimeout && !isset( $options['timeout'] ) ) {
+ $options['timeout'] = $wgCopyUploadTimeout;
+ }
$req = MWHttpRequest::factory( $this->mUrl, $options );
$req->setCallback( array( $this, 'saveTempFileChunk' ) );
$status = $req->execute();
diff --git a/includes/upload/UploadStash.php b/includes/upload/UploadStash.php
index 089bd8b7..7db6c64b 100644
--- a/includes/upload/UploadStash.php
+++ b/includes/upload/UploadStash.php
@@ -158,7 +158,7 @@ class UploadStash {
* @param string $key key under which file information is stored
* @return Array
*/
- public function getMetadata ( $key ) {
+ public function getMetadata( $key ) {
$this->getFile( $key );
return $this->fileMetadata[$key];
}
@@ -169,7 +169,7 @@ class UploadStash {
* @param string $key key under which file information is stored
* @return Array
*/
- public function getFileProps ( $key ) {
+ public function getFileProps( $key ) {
$this->getFile( $key );
return $this->fileProps[$key];
}
@@ -209,7 +209,7 @@ class UploadStash {
list( $usec, $sec ) = explode( ' ', microtime() );
$usec = substr( $usec, 2 );
$key = wfBaseConvert( $sec . $usec, 10, 36 ) . '.' .
- wfBaseConvert( mt_rand(), 10, 36 ) . '.'.
+ wfBaseConvert( mt_rand(), 10, 36 ) . '.' .
$this->userId . '.' .
$extension;
@@ -338,7 +338,7 @@ class UploadStash {
__METHOD__
);
- if( !$row ) {
+ if ( !$row ) {
throw new UploadStashNoSuchKeyException( "No such key ($key), cannot remove" );
}
@@ -358,7 +358,7 @@ class UploadStash {
wfDebug( __METHOD__ . " clearing row $key\n" );
// Ensure we have the UploadStashFile loaded for this key
- $this->getFile( $key );
+ $this->getFile( $key, true );
$dbw = $this->repo->getMasterDb();
@@ -463,7 +463,7 @@ class UploadStash {
protected function fetchFileMetadata( $key, $readFromDB = DB_SLAVE ) {
// populate $fileMetadata[$key]
$dbr = null;
- if( $readFromDB === DB_MASTER ) {
+ if ( $readFromDB === DB_MASTER ) {
// sometimes reading from the master is necessary, if there's replication lag.
$dbr = $this->repo->getMasterDb();
} else {
@@ -675,11 +675,12 @@ class UploadStashFile extends UnregisteredLocalFile {
}
-class UploadStashNotAvailableException extends MWException {};
-class UploadStashFileNotFoundException extends MWException {};
-class UploadStashBadPathException extends MWException {};
-class UploadStashFileException extends MWException {};
-class UploadStashZeroLengthFileException extends MWException {};
-class UploadStashNotLoggedInException extends MWException {};
-class UploadStashWrongOwnerException extends MWException {};
-class UploadStashNoSuchKeyException extends MWException {};
+class UploadStashException extends MWException {};
+class UploadStashNotAvailableException extends UploadStashException {};
+class UploadStashFileNotFoundException extends UploadStashException {};
+class UploadStashBadPathException extends UploadStashException {};
+class UploadStashFileException extends UploadStashException {};
+class UploadStashZeroLengthFileException extends UploadStashException {};
+class UploadStashNotLoggedInException extends UploadStashException {};
+class UploadStashWrongOwnerException extends UploadStashException {};
+class UploadStashNoSuchKeyException extends UploadStashException {};