Merge "[FileBackend] Tweaked TempFSFile::bind() to handle __get()."
authorDemon <chadh@wikimedia.org>
Tue, 9 Oct 2012 16:08:55 +0000 (16:08 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Tue, 9 Oct 2012 16:08:55 +0000 (16:08 +0000)
578 files changed:
RELEASE-NOTES-1.20
RELEASE-NOTES-1.21
bin/ulimit4.sh [changed mode: 0755->0644]
docs/contenthandler.txt [new file with mode: 0644]
docs/export-0.8.xsd [new file with mode: 0644]
docs/hooks.txt
docs/memcached.txt
includes/Action.php
includes/Article.php
includes/AutoLoader.php
includes/Autopromote.php
includes/BacklinkCache.php
includes/Block.php
includes/Category.php
includes/CategoryViewer.php
includes/Cdb_PHP.php
includes/ChangeTags.php
includes/ConfEditor.php
includes/Cookie.php
includes/DataUpdate.php
includes/DefaultSettings.php
includes/Defines.php
includes/EditPage.php
includes/Export.php
includes/ExternalStore.php
includes/ExternalStoreDB.php
includes/FeedUtils.php
includes/FileDeleteForm.php
includes/FormOptions.php
includes/GlobalFunctions.php
includes/HTMLForm.php
includes/Hooks.php
includes/Html.php
includes/HttpFunctions.php
includes/ImagePage.php
includes/Import.php
includes/LinkFilter.php
includes/Linker.php
includes/LinksUpdate.php
includes/LocalisationCache.php
includes/Message.php
includes/MessageBlobStore.php
includes/Namespace.php
includes/OutputPage.php
includes/PHPVersionError.php
includes/ProtectionForm.php
includes/QueryPage.php
includes/Revision.php
includes/Sanitizer.php
includes/Skin.php
includes/SkinTemplate.php
includes/SpecialPage.php
includes/SqlDataUpdate.php
includes/StreamFile.php
includes/StubObject.php
includes/Timestamp.php
includes/Title.php
includes/User.php
includes/UserMailer.php
includes/WebRequest.php
includes/Wiki.php
includes/WikiFilePage.php
includes/WikiPage.php
includes/Xml.php
includes/ZipDirectoryReader.php
includes/actions/CreditsAction.php
includes/actions/HistoryAction.php
includes/actions/InfoAction.php
includes/actions/RawAction.php
includes/actions/RevertAction.php
includes/actions/RollbackAction.php
includes/api/ApiComparePages.php
includes/api/ApiDelete.php
includes/api/ApiEditPage.php
includes/api/ApiFeedContributions.php
includes/api/ApiFileRevert.php
includes/api/ApiFormatNone.php [new file with mode: 0644]
includes/api/ApiMain.php
includes/api/ApiParse.php
includes/api/ApiPurge.php
includes/api/ApiQueryRevisions.php
includes/api/ApiUndelete.php
includes/cache/HTMLFileCache.php
includes/cache/LinkCache.php
includes/cache/MessageCache.php
includes/conf/Conf.php
includes/content/AbstractContent.php [new file with mode: 0644]
includes/content/Content.php [new file with mode: 0644]
includes/content/ContentHandler.php [new file with mode: 0644]
includes/content/CssContent.php [new file with mode: 0644]
includes/content/JavaScriptContent.php [new file with mode: 0644]
includes/content/MessageContent.php [new file with mode: 0644]
includes/content/TextContent.php [new file with mode: 0644]
includes/content/WikitextContent.php [new file with mode: 0644]
includes/context/DerivativeContext.php
includes/context/RequestContext.php
includes/db/Database.php
includes/db/DatabaseIbm_db2.php
includes/db/DatabaseMssql.php
includes/db/DatabaseMysql.php
includes/db/DatabaseOracle.php
includes/db/DatabasePostgres.php
includes/db/DatabaseSqlite.php
includes/db/LBFactory_Multi.php
includes/db/LoadBalancer.php
includes/diff/DifferenceEngine.php
includes/filebackend/FileBackend.php
includes/filebackend/FileBackendMultiWrite.php
includes/filebackend/FileBackendStore.php
includes/filebackend/SwiftFileBackend.php
includes/filebackend/lockmanager/MemcLockManager.php
includes/filerepo/FileRepo.php
includes/filerepo/file/ArchivedFile.php
includes/filerepo/file/LocalFile.php
includes/installer/Ibm_db2Updater.php
includes/installer/Installer.php
includes/installer/MysqlUpdater.php
includes/installer/OracleUpdater.php
includes/installer/PostgresInstaller.php
includes/installer/SqliteUpdater.php
includes/job/DoubleRedirectJob.php
includes/job/RefreshLinksJob.php
includes/logging/LogFormatter.php
includes/media/Bitmap.php
includes/media/BitmapMetadataHandler.php
includes/media/Exif.php
includes/media/GIFMetadataExtractor.php
includes/media/JpegMetadataExtractor.php
includes/media/MediaTransformOutput.php
includes/media/SVG.php
includes/media/SVGMetadataExtractor.php
includes/media/Tiff.php
includes/media/XMP.php
includes/objectcache/EhcacheBagOStuff.php
includes/objectcache/RedisBagOStuff.php
includes/parser/CoreTagHooks.php
includes/parser/Parser.php
includes/parser/ParserCache.php
includes/parser/ParserOutput.php
includes/parser/Parser_LinkHooks.php
includes/parser/Preprocessor_DOM.php
includes/parser/Preprocessor_Hash.php
includes/parser/Preprocessor_HipHop.hphp
includes/resourceloader/ResourceLoader.php
includes/resourceloader/ResourceLoaderFileModule.php
includes/resourceloader/ResourceLoaderWikiModule.php
includes/revisiondelete/RevisionDeleteAbstracts.php
includes/search/SearchEngine.php
includes/specials/SpecialBooksources.php
includes/specials/SpecialComparePages.php
includes/specials/SpecialJavaScriptTest.php
includes/specials/SpecialMergeHistory.php
includes/specials/SpecialNewpages.php
includes/specials/SpecialPasswordReset.php
includes/specials/SpecialRevisiondelete.php
includes/specials/SpecialUnblock.php
includes/specials/SpecialUndelete.php
includes/specials/SpecialUpload.php
includes/specials/SpecialUploadStash.php
includes/specials/SpecialUserlogin.php
includes/specials/SpecialUserlogout.php
includes/specials/SpecialUserrights.php
includes/upload/UploadFromChunks.php
includes/upload/UploadStash.php
languages/Language.php
languages/LanguageConverter.php
languages/classes/LanguageFi.php
languages/classes/LanguageGan.php
languages/classes/LanguageIu.php
languages/classes/LanguageKk.php
languages/classes/LanguageKu.php
languages/classes/LanguageQqx.php
languages/classes/LanguageShi.php
languages/classes/LanguageSr.php
languages/classes/LanguageUz.php
languages/classes/LanguageZh.php
languages/messages/MessagesAeb.php
languages/messages/MessagesAf.php
languages/messages/MessagesAln.php
languages/messages/MessagesAn.php
languages/messages/MessagesAng.php
languages/messages/MessagesAr.php
languages/messages/MessagesArc.php
languages/messages/MessagesAry.php
languages/messages/MessagesArz.php
languages/messages/MessagesAs.php
languages/messages/MessagesAst.php
languages/messages/MessagesAz.php
languages/messages/MessagesBa.php
languages/messages/MessagesBar.php
languages/messages/MessagesBcl.php
languages/messages/MessagesBe.php
languages/messages/MessagesBe_tarask.php
languages/messages/MessagesBg.php
languages/messages/MessagesBjn.php
languages/messages/MessagesBn.php
languages/messages/MessagesBpy.php
languages/messages/MessagesBr.php
languages/messages/MessagesBs.php
languages/messages/MessagesCa.php
languages/messages/MessagesCeb.php
languages/messages/MessagesCkb.php
languages/messages/MessagesCps.php
languages/messages/MessagesCrh_cyrl.php
languages/messages/MessagesCrh_latn.php
languages/messages/MessagesCs.php
languages/messages/MessagesCsb.php
languages/messages/MessagesCy.php
languages/messages/MessagesDa.php
languages/messages/MessagesDe.php
languages/messages/MessagesDiq.php
languages/messages/MessagesDsb.php
languages/messages/MessagesDtp.php
languages/messages/MessagesEl.php
languages/messages/MessagesEn.php
languages/messages/MessagesEo.php
languages/messages/MessagesEs.php
languages/messages/MessagesEt.php
languages/messages/MessagesEu.php
languages/messages/MessagesExt.php
languages/messages/MessagesFa.php
languages/messages/MessagesFi.php
languages/messages/MessagesFo.php
languages/messages/MessagesFr.php
languages/messages/MessagesFrp.php
languages/messages/MessagesFrr.php
languages/messages/MessagesFur.php
languages/messages/MessagesFy.php
languages/messages/MessagesGa.php
languages/messages/MessagesGag.php
languages/messages/MessagesGan_hans.php
languages/messages/MessagesGan_hant.php
languages/messages/MessagesGd.php
languages/messages/MessagesGl.php
languages/messages/MessagesGrc.php
languages/messages/MessagesGsw.php
languages/messages/MessagesGu.php
languages/messages/MessagesHe.php
languages/messages/MessagesHi.php
languages/messages/MessagesHif_latn.php
languages/messages/MessagesHil.php
languages/messages/MessagesHr.php
languages/messages/MessagesHsb.php
languages/messages/MessagesHt.php
languages/messages/MessagesHu.php
languages/messages/MessagesHy.php
languages/messages/MessagesIa.php
languages/messages/MessagesId.php
languages/messages/MessagesIe.php
languages/messages/MessagesIg.php
languages/messages/MessagesIlo.php
languages/messages/MessagesIo.php
languages/messages/MessagesIs.php
languages/messages/MessagesIt.php
languages/messages/MessagesJa.php
languages/messages/MessagesJam.php
languages/messages/MessagesJv.php
languages/messages/MessagesKa.php
languages/messages/MessagesKaa.php
languages/messages/MessagesKab.php
languages/messages/MessagesKbd_cyrl.php
languages/messages/MessagesKhw.php
languages/messages/MessagesKiu.php
languages/messages/MessagesKk_cyrl.php
languages/messages/MessagesKm.php
languages/messages/MessagesKn.php
languages/messages/MessagesKo.php
languages/messages/MessagesKrc.php
languages/messages/MessagesKsh.php
languages/messages/MessagesKu_latn.php
languages/messages/MessagesLa.php
languages/messages/MessagesLb.php
languages/messages/MessagesLg.php
languages/messages/MessagesLi.php
languages/messages/MessagesLmo.php
languages/messages/MessagesLt.php
languages/messages/MessagesLus.php
languages/messages/MessagesLv.php
languages/messages/MessagesLzh.php
languages/messages/MessagesMai.php
languages/messages/MessagesMap_bms.php
languages/messages/MessagesMdf.php
languages/messages/MessagesMg.php
languages/messages/MessagesMin.php
languages/messages/MessagesMk.php
languages/messages/MessagesMl.php
languages/messages/MessagesMn.php
languages/messages/MessagesMr.php
languages/messages/MessagesMs.php
languages/messages/MessagesMt.php
languages/messages/MessagesMy.php
languages/messages/MessagesNb.php
languages/messages/MessagesNds.php
languages/messages/MessagesNds_nl.php
languages/messages/MessagesNe.php
languages/messages/MessagesNl.php
languages/messages/MessagesNn.php
languages/messages/MessagesOc.php
languages/messages/MessagesOr.php
languages/messages/MessagesOs.php
languages/messages/MessagesPl.php
languages/messages/MessagesPms.php
languages/messages/MessagesPnb.php
languages/messages/MessagesPrg.php
languages/messages/MessagesPs.php
languages/messages/MessagesPt.php
languages/messages/MessagesPt_br.php
languages/messages/MessagesQqq.php
languages/messages/MessagesQu.php
languages/messages/MessagesRm.php
languages/messages/MessagesRo.php
languages/messages/MessagesRoa_tara.php
languages/messages/MessagesRu.php
languages/messages/MessagesRue.php
languages/messages/MessagesSa.php
languages/messages/MessagesSah.php
languages/messages/MessagesSc.php
languages/messages/MessagesScn.php
languages/messages/MessagesSgs.php
languages/messages/MessagesSh.php
languages/messages/MessagesSi.php
languages/messages/MessagesSk.php
languages/messages/MessagesSl.php
languages/messages/MessagesSli.php
languages/messages/MessagesSo.php
languages/messages/MessagesSq.php
languages/messages/MessagesSr_ec.php
languages/messages/MessagesSr_el.php
languages/messages/MessagesStq.php
languages/messages/MessagesSu.php
languages/messages/MessagesSv.php
languages/messages/MessagesSw.php
languages/messages/MessagesSzl.php
languages/messages/MessagesTa.php
languages/messages/MessagesTe.php
languages/messages/MessagesTg_cyrl.php
languages/messages/MessagesTg_latn.php
languages/messages/MessagesTh.php
languages/messages/MessagesTk.php
languages/messages/MessagesTl.php
languages/messages/MessagesTly.php
languages/messages/MessagesTr.php
languages/messages/MessagesTs.php
languages/messages/MessagesTt_cyrl.php
languages/messages/MessagesTt_latn.php
languages/messages/MessagesUg_arab.php
languages/messages/MessagesUk.php
languages/messages/MessagesUr.php
languages/messages/MessagesUz.php
languages/messages/MessagesVec.php
languages/messages/MessagesVep.php
languages/messages/MessagesVi.php
languages/messages/MessagesVo.php
languages/messages/MessagesVro.php
languages/messages/MessagesWa.php
languages/messages/MessagesWar.php
languages/messages/MessagesWo.php
languages/messages/MessagesWuu.php
languages/messages/MessagesXal.php
languages/messages/MessagesYi.php
languages/messages/MessagesYo.php
languages/messages/MessagesYue.php
languages/messages/MessagesZh_hans.php
languages/messages/MessagesZh_hant.php
languages/utils/CLDRPluralRuleEvaluator.php
maintenance/Maintenance.php
maintenance/archives/patch-archive-ar_content_format.sql [new file with mode: 0644]
maintenance/archives/patch-archive-ar_content_model.sql [new file with mode: 0644]
maintenance/archives/patch-page-page_content_model.sql [new file with mode: 0644]
maintenance/archives/patch-revision-rev_content_format.sql [new file with mode: 0644]
maintenance/archives/patch-revision-rev_content_model.sql [new file with mode: 0644]
maintenance/backupTextPass.inc
maintenance/checkBadRedirects.php
maintenance/cleanupSpam.php
maintenance/compareParsers.php
maintenance/cssjanus/cssjanus.py [changed mode: 0755->0644]
maintenance/cssjanus/csslex.py [changed mode: 0755->0644]
maintenance/dev/install.sh [changed mode: 0755->0644]
maintenance/dev/installmw.sh [changed mode: 0755->0644]
maintenance/dev/installphp.sh [changed mode: 0755->0644]
maintenance/dev/start.sh [changed mode: 0755->0644]
maintenance/dumpIterator.php
maintenance/edit.php
maintenance/getText.php
maintenance/hiphop/make [changed mode: 0755->0644]
maintenance/hiphop/run-server [changed mode: 0755->0644]
maintenance/importSiteScripts.php
maintenance/importTextFile.php
maintenance/install.php
maintenance/language/checkLanguage.inc
maintenance/language/messages.inc
maintenance/locking/LockServerDaemon.php
maintenance/populateRevisionLength.php
maintenance/populateRevisionSha1.php
maintenance/preprocessDump.php
maintenance/protect.php
maintenance/refreshLinks.php
maintenance/renderDump.php
maintenance/sqlite.inc
maintenance/storage/drop_content_model_info.sql [new file with mode: 0644]
maintenance/storage/make-blobs [changed mode: 0755->0644]
maintenance/storage/testCompression.php
maintenance/tables.sql
resources/jquery/jquery.makeCollapsible.js
resources/jquery/jquery.tablesorter.js
tests/jasmine/.htaccess [deleted file]
tests/jasmine/SpecRunner.html [deleted file]
tests/jasmine/lib/jasmine-1.0.1/MIT.LICENSE [deleted file]
tests/jasmine/lib/jasmine-1.0.1/jasmine-html.js [deleted file]
tests/jasmine/lib/jasmine-1.0.1/jasmine.css [deleted file]
tests/jasmine/lib/jasmine-1.0.1/jasmine.js [deleted file]
tests/jasmine/spec/mediawiki.jqueryMsg.spec.data.js [deleted file]
tests/jasmine/spec/mediawiki.jqueryMsg.spec.js [deleted file]
tests/jasmine/spec_makers/makeJqueryMsgSpec.php [deleted file]
tests/parser/parserTest.inc
tests/phpunit/MediaWikiLangTestCase.php
tests/phpunit/MediaWikiTestCase.php
tests/phpunit/includes/ArticleTablesTest.php
tests/phpunit/includes/ArticleTest.php
tests/phpunit/includes/BlockTest.php
tests/phpunit/includes/CdbTest.php
tests/phpunit/includes/ContentHandlerTest.php [new file with mode: 0644]
tests/phpunit/includes/CssContentTest.php [new file with mode: 0644]
tests/phpunit/includes/DiffHistoryBlobTest.php
tests/phpunit/includes/EditPageTest.php
tests/phpunit/includes/ExternalStoreTest.php
tests/phpunit/includes/ExtraParserTest.php
tests/phpunit/includes/FauxResponseTest.php
tests/phpunit/includes/GlobalFunctions/GlobalTest.php
tests/phpunit/includes/GlobalFunctions/wfAssembleUrlTest.php
tests/phpunit/includes/GlobalFunctions/wfExpandUrlTest.php
tests/phpunit/includes/GlobalFunctions/wfRemoveDotSegmentsTest.php
tests/phpunit/includes/GlobalFunctions/wfUrlencodeTest.php
tests/phpunit/includes/HooksTest.php
tests/phpunit/includes/HtmlTest.php
tests/phpunit/includes/HttpTest.php
tests/phpunit/includes/IPTest.php
tests/phpunit/includes/JavascriptContentTest.php [new file with mode: 0644]
tests/phpunit/includes/JsonTest.php
tests/phpunit/includes/LanguageConverterTest.php
tests/phpunit/includes/LinksUpdateTest.php
tests/phpunit/includes/LocalFileTest.php
tests/phpunit/includes/MWFunctionTest.php
tests/phpunit/includes/MWNamespaceTest.php
tests/phpunit/includes/MessageTest.php
tests/phpunit/includes/ParserOptionsTest.php
tests/phpunit/includes/PathRouterTest.php
tests/phpunit/includes/PreferencesTest.php
tests/phpunit/includes/RecentChangeTest.php
tests/phpunit/includes/ResourceLoaderTest.php
tests/phpunit/includes/RevisionStorageTest.php
tests/phpunit/includes/RevisionStorageTest_ContentHandlerUseDB.php [new file with mode: 0644]
tests/phpunit/includes/RevisionTest.php
tests/phpunit/includes/SampleTest.php
tests/phpunit/includes/SanitizerTest.php
tests/phpunit/includes/SeleniumConfigurationTest.php
tests/phpunit/includes/SiteConfigurationTest.php
tests/phpunit/includes/TemplateCategoriesTest.php
tests/phpunit/includes/TimeAdjustTest.php
tests/phpunit/includes/TimestampTest.php
tests/phpunit/includes/TitleMethodsTest.php
tests/phpunit/includes/TitlePermissionTest.php
tests/phpunit/includes/TitleTest.php
tests/phpunit/includes/UserTest.php
tests/phpunit/includes/WebRequestTest.php
tests/phpunit/includes/WikiPageTest.php
tests/phpunit/includes/WikiPageTest_ContentHandlerUseDB.php [new file with mode: 0644]
tests/phpunit/includes/WikitextContentHandlerTest.php [new file with mode: 0644]
tests/phpunit/includes/WikitextContentTest.php [new file with mode: 0644]
tests/phpunit/includes/XmlSelectTest.php
tests/phpunit/includes/XmlTest.php
tests/phpunit/includes/ZipDirectoryReaderTest.php
tests/phpunit/includes/api/ApiBlockTest.php
tests/phpunit/includes/api/ApiEditPageTest.php
tests/phpunit/includes/api/ApiOptionsTest.php
tests/phpunit/includes/api/ApiPurgeTest.php
tests/phpunit/includes/api/ApiQueryTest.php
tests/phpunit/includes/api/ApiTestCase.php
tests/phpunit/includes/api/ApiTestCaseUpload.php
tests/phpunit/includes/api/ApiWatchTest.php
tests/phpunit/includes/cache/GenderCacheTest.php
tests/phpunit/includes/cache/ProcessCacheLRUTest.php
tests/phpunit/includes/db/DatabaseSQLTest.php
tests/phpunit/includes/db/DatabaseSqliteTest.php
tests/phpunit/includes/db/DatabaseTest.php
tests/phpunit/includes/db/TestORMRowTest.php
tests/phpunit/includes/debug/MWDebugTest.php
tests/phpunit/includes/filerepo/FileBackendTest.php
tests/phpunit/includes/filerepo/StoreBatchTest.php
tests/phpunit/includes/libs/CSSMinTest.php
tests/phpunit/includes/media/BitmapMetadataHandlerTest.php
tests/phpunit/includes/media/BitmapScalingTest.php
tests/phpunit/includes/media/ExifBitmapTest.php
tests/phpunit/includes/media/ExifRotationTest.php
tests/phpunit/includes/media/ExifTest.php
tests/phpunit/includes/media/FormatMetadataTest.php
tests/phpunit/includes/media/GIFMetadataExtractorTest.php
tests/phpunit/includes/media/GIFTest.php
tests/phpunit/includes/media/JpegMetadataExtractorTest.php
tests/phpunit/includes/media/JpegTest.php
tests/phpunit/includes/media/PNGMetadataExtractorTest.php
tests/phpunit/includes/media/PNGTest.php
tests/phpunit/includes/media/SVGMetadataExtractorTest.php
tests/phpunit/includes/media/TiffTest.php
tests/phpunit/includes/media/XMPTest.php
tests/phpunit/includes/media/XMPValidateTest.php
tests/phpunit/includes/mobile/DeviceDetectionTest.php
tests/phpunit/includes/parser/MagicVariableTest.php
tests/phpunit/includes/parser/NewParserTest.php
tests/phpunit/includes/parser/ParserMethodsTest.php
tests/phpunit/includes/parser/ParserPreloadTest.php
tests/phpunit/includes/parser/PreprocessorTest.php
tests/phpunit/includes/search/SearchEngineTest.php
tests/phpunit/includes/search/SearchUpdateTest.php
tests/phpunit/includes/specials/SpecialRecentchangesTest.php
tests/phpunit/includes/specials/SpecialSearchTest.php
tests/phpunit/includes/upload/UploadFromUrlTest.php
tests/phpunit/includes/upload/UploadStashTest.php
tests/phpunit/includes/upload/UploadTest.php
tests/phpunit/languages/LanguageAmTest.php
tests/phpunit/languages/LanguageArTest.php
tests/phpunit/languages/LanguageBeTest.php
tests/phpunit/languages/LanguageBe_taraskTest.php
tests/phpunit/languages/LanguageBhTest.php
tests/phpunit/languages/LanguageBsTest.php
tests/phpunit/languages/LanguageCsTest.php
tests/phpunit/languages/LanguageCuTest.php
tests/phpunit/languages/LanguageCyTest.php
tests/phpunit/languages/LanguageDsbTest.php
tests/phpunit/languages/LanguageFrTest.php
tests/phpunit/languages/LanguageGaTest.php
tests/phpunit/languages/LanguageGdTest.php
tests/phpunit/languages/LanguageGvTest.php
tests/phpunit/languages/LanguageHeTest.php
tests/phpunit/languages/LanguageHiTest.php
tests/phpunit/languages/LanguageHrTest.php
tests/phpunit/languages/LanguageHsbTest.php
tests/phpunit/languages/LanguageHuTest.php
tests/phpunit/languages/LanguageHyTest.php
tests/phpunit/languages/LanguageKshTest.php
tests/phpunit/languages/LanguageLnTest.php
tests/phpunit/languages/LanguageLtTest.php
tests/phpunit/languages/LanguageLvTest.php
tests/phpunit/languages/LanguageMgTest.php
tests/phpunit/languages/LanguageMkTest.php
tests/phpunit/languages/LanguageMlTest.php
tests/phpunit/languages/LanguageMoTest.php
tests/phpunit/languages/LanguageMtTest.php
tests/phpunit/languages/LanguageNlTest.php
tests/phpunit/languages/LanguageNsoTest.php
tests/phpunit/languages/LanguagePlTest.php
tests/phpunit/languages/LanguageRoTest.php
tests/phpunit/languages/LanguageRuTest.php
tests/phpunit/languages/LanguageSeTest.php
tests/phpunit/languages/LanguageSgsTest.php
tests/phpunit/languages/LanguageShTest.php
tests/phpunit/languages/LanguageSkTest.php
tests/phpunit/languages/LanguageSlTest.php
tests/phpunit/languages/LanguageSmaTest.php
tests/phpunit/languages/LanguageSrTest.php
tests/phpunit/languages/LanguageTest.php
tests/phpunit/languages/LanguageTiTest.php
tests/phpunit/languages/LanguageTlTest.php
tests/phpunit/languages/LanguageTrTest.php
tests/phpunit/languages/LanguageUkTest.php
tests/phpunit/languages/LanguageUzTest.php
tests/phpunit/languages/LanguageWaTest.php
tests/phpunit/maintenance/DumpTestCase.php
tests/phpunit/maintenance/backupPrefetchTest.php
tests/phpunit/maintenance/backupTextPassTest.php
tests/phpunit/maintenance/backup_PageTest.php
tests/phpunit/maintenance/fetchTextTest.php
tests/phpunit/skins/SideBarTest.php
tests/phpunit/suites/UploadFromUrlTestSuite.php
tests/qunit/QUnitTestResources.php
tests/qunit/data/generateJqueryMsgData.php [new file with mode: 0644]
tests/qunit/data/mediawiki.jqueryMsg.data.js [new file with mode: 0644]
tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js

index 69fdb30..c168f77 100644 (file)
@@ -260,6 +260,8 @@ upgrade PHP if you have not done so prior to upgrading MediaWiki.
 * (bug 31676) ResourceLoader should work around IE stylesheet limit.
 * (bug 40498) ResourceLoader should not output an empty "@media print { }" block.
 * (bug 40500) ResourceLoader should not ignore media-type for urls in debug mode.
+* (bug 40660) ResourceLoaderWikiModule should not convert "&nbsp;" to a space
+  for pages from the MediaWiki-namespace.
 
 === API changes in 1.20 ===
 * (bug 34316) Add ability to retrieve maximum upload size from MediaWiki API.
index 66c3859..a1fa4ca 100644 (file)
@@ -1,7 +1,7 @@
 = MediaWiki release notes =
 
-Security reminder: MediaWiki does not require PHP's register_globals
-setting since version 1.2.0. If you have it on, turn it '''off''' if you can.
+Security reminder: MediaWiki does not require PHP's register_globals. If you
+have it on, turn it '''off''' if you can.
 
 == MediaWiki 1.21 ==
 
@@ -12,19 +12,27 @@ production.
 
 === Configuration changes in 1.21 ===
 * (bug 29374) $wgVectorUseSimpleSearch is now enabled by default.
-* Deprecated $wgAllowRealName is removed. Use $wgHiddenPrefs[] = 'realname' instead
+* Deprecated $wgAllowRealName is removed. Use $wgHiddenPrefs[] = 'realname'
+  instead.
 
 === New features in 1.21 ===
+* (bug 34876) jquery.makeCollapsible has been improved in performance.
+* Added ContentHandler facility to allow extensions to support other content than wikitext.
+  See docs/contenthandler.txt for details.
 
 === Bug fixes in 1.21 ===
 * (bug 40353) SpecialDoubleRedirect should support interwiki redirects.
 * (bug 40352) fixDoubleRedirects.php should support interwiki redirects.
 * (bug 9237) SpecialBrokenRedirect should not list interwiki redirects.
-* (bug 34960) Drop unused fields rc_moved_to_ns and rc_moved_to_title from recentchanges table.
+* (bug 34960) Drop unused fields rc_moved_to_ns and rc_moved_to_title from
+  recentchanges table.
 * (bug 32951) Do not register internal externals with absolute protocol,
   when server has relative protocol.
 
 === API changes in 1.21 ===
+* prop=revisions can now report the contentmodel and contentformat, see docs/contenthandler.txt
+* action=edit and action=parse now support contentmodel and contentformat parameters to control the interpretation of
+  page content; See docs/contenthandler.txt for details.
 * (bug 35693) ApiQueryImageInfo now suppresses errors when unserializing metadata.
 
 === Languages updated in 1.21 ===
@@ -32,12 +40,11 @@ production.
 MediaWiki supports over 350 languages. Many localisations are updated
 regularly. Below only new and removed languages are listed, as well as
 changes to languages because of Bugzilla reports.
-
 === Other changes in 1.21 ===
 
 == Compatibility ==
 
-MediaWiki 1.21 requires PHP 5.3.2. PHP 4 is no longer supported.
+MediaWiki 1.21 requires PHP 5.3.2 or later.
 
 MySQL is the recommended DBMS. PostgreSQL or SQLite can also be used, but
 support for them is somewhat less mature. There is experimental support for IBM
@@ -53,7 +60,9 @@ The supported versions are:
 == Upgrading ==
 
 1.21 has several database changes since 1.20, and will not work without schema
-updates.
+updates. Note that due to changes to some very large tables like the revision
+table, the schema update may take quite long (minutes on a medium sized site,
+many hours on a large site).
 
 If upgrading from before 1.11, and you are using a wiki as a commons
 repository, make sure that it is updated as well. Otherwise, errors may arise
old mode 100755 (executable)
new mode 100644 (file)
diff --git a/docs/contenthandler.txt b/docs/contenthandler.txt
new file mode 100644 (file)
index 0000000..3561432
--- /dev/null
@@ -0,0 +1,184 @@
+The ContentHandler facility adds support for arbitrary content types on wiki pages, instead of relying on wikitext
+for everything. It was introduced in MediaWiki 1.21.
+
+Each kind of content ("content model") supported by MediaWiki is identified by unique name. The content model determines
+how a page's content is rendered, compared, stored, edited, and so on.
+
+Built-in content types are:
+
+* wikitext - wikitext, as usual
+* javascript - user provided javascript code
+* css - user provided css code
+* text - plain text
+
+In PHP, use the corresponding CONTENT_MODEL_XXX constant.
+
+A page's content model is available using the Title::getContentModel() method. A page's default model is determined by
+ContentHandler::getDefaultModelFor($title) as follows:
+
+* The global setting $wgNamespaceContentModels specifies a content model for the given namespace.
+* The hook ContentHandlerDefaultModelFor may be used to override the page's default model.
+* Pages in NS_MEDIAWIKI and NS_USER default to the CSS or JavaScript model if they end in .js or .css, respectively.
+  Pages in NS_MEDIAWIKI default to the wikitext model otherwise.
+* The hook TitleIsCssOrJsPage may be used to force a page to use the CSS or JavaScript model.
+  This is a compatibility feature. The ContentHandlerDefaultModelFor hook should be used instead if possible.
+* The hook TitleIsWikitextPage may be used to force a page to use the wikitext model.
+  This is a compatibility feature. The ContentHandlerDefaultModelFor hook should be used instead if possible.
+* Otherwise, the wikitext model is used.
+
+Note that is currently no mechanism to convert a page from one content model to another, and there is no guarantee that
+revisions of a page will all have the same content model. Use Revision::getContentModel() to find it.
+
+
+== Architecture ==
+
+Two class hierarchies are used to provide the functionality associated with the different content models:
+
+* Content interface (and AbstractContent base class) define functionality that acts on the concrete content of a page, and
+* ContentHandler base class provides functionality specific to a content model, but not acting on concrete content.
+
+The most important function of ContentHandler is to act as a factory for the appropriate implementation of Content. These
+Content objects are to be used by MediaWiki everywhere, instead of passing page content around as text. All manipulation
+and analysis of page content must be done via the appropriate methods of the Content object.
+
+For each content model, a subclass of ContentHandler has to be registered with $wgContentHandlers. The ContentHandler
+object for a given content model can be obtained using ContentHandler::getForModelID( $id ). Also Title, WikiPage and
+Revision now have getContentHandler() methods for convenience.
+
+ContentHandler objects are singletons that provide functionality specific to the content type, but not directly acting
+on the content of some page. ContentHandler::makeEmptyContent() and ContentHandler::unserializeContent() can be used to
+create a Content object of the appropriate type. However, it is recommended to instead use WikiPage::getContent() resp.
+Revision::getContent() to get a page's content as a Content object. These two methods should be the ONLY way in which
+page content is accessed.
+
+Another important function of ContentHandler objects is to define custom action handlers for a content model, see
+ContentHandler::getActionOverrides(). This is similar to what WikiPage::getActionOverrides() was already doing.
+
+
+== Serialization ==
+
+With the ContentHandler facility, page content no longer has to be text based. Objects implementing the Content interface
+are used to represent and handle the content internally. For storage and data exchange, each content model supports
+at least one serialization format via ContentHandler::serializeContent( $content ). The list of supported formats for
+a given content model can be accessed using ContentHandler::getSupportedFormats().
+
+Content serialization formats are identified using MIME type like strings. The following formats are built in:
+
+* text/x-wiki - wikitext
+* text/javascript - for js pages
+* text/css - for css pages
+* text/plain - for future use, e.g. with plain text messages.
+* text/html - for future use, e.g. with plain html messages.
+* application/vnd.php.serialized - for future use with the api and for extensions
+* application/json - for future use with the api, and for use by extensions
+* application/xml - for future use with the api, and for use by extensions
+
+In PHP, use the corresponding CONTENT_FORMAT_XXX constant.
+
+Note that when using the API to access page content, especially action=edit, action=parse and action=query&prop=revisions,
+the model and format of the content should always be handled explicitly. Without that information, interpretation of
+the provided content is not reliable. The same applies to XML dumps generated via maintenance/dumpBackup.php or
+Special:Export.
+
+Also note that the API will provide encapsulated, serialized content - so if the API was called with format=json, and
+contentformat is also json (or rather, application/json), the page content is represented as a string containing an
+escaped json structure. Extensions that use JSON to serialize some types of page content may provide specialized API
+modules that allow access to that content in a more natural form.
+
+
+== Compatibility ==
+
+The ContentHandler facility is introduced in a way that should allow all existing code to keep functioning at least
+for pages that contain wikitext or other text based content. However, a number of functions and hooks have been
+deprecated in favor of new versions that are aware of the page's content model, and will now generate warnings when
+used.
+
+Most importantly, the following functions have been deprecated:
+
+* Revisions::getText() and Revisions::getRawText() is deprecated in favor Revisions::getContent()
+* WikiPage::getText() is deprecated in favor WikiPage::getContent()
+
+Also, the old Article::getContent() (which returns text) is superceded by Article::getContentObject(). However, both
+methods should be avoided since they do not provide clean access to the page's actual content. For instance, they may
+return a system message for non-existing pages. Use WikiPage::getContent() instead.
+
+Code that relies on a textual representation of the page content should eventually be rewritten. However,
+ContentHandler::getContentText() provides a stop-gap that can be used to get text for a page. Its behavior is controlled
+by $wgContentHandlerTextFallback; per default it will return the text for text based content, and null for any other
+content.
+
+For rendering page content, Content::getParserOutput() should be used instead of accessing the parser directly.
+ContentHandler::makeParserOptions() can be used to construct appropriate options.
+
+
+Besides some functions, some hooks have also been replaced by new versions (see hooks.txt for details).
+These hooks will now trigger a warning when used:
+
+* ArticleAfterFetchContent was replaced by ArticleAfterFetchContentObject
+* ArticleInsertComplete was replaced by ArticleContentInsertComplete
+* ArticleSave was replaced by ArticleContentSave
+* ArticleSaveComplete was replaced by ArticleContentSaveComplete
+* ArticleViewCustom was replaced by ArticleContentViewCustom (also consider a custom implementation of the view action)
+* EditFilterMerged was replaced by EditFilterMergedContent
+* EditPageGetDiffText was replaced by EditPageGetDiffContent
+* EditPageGetPreviewText was replaced by EditPageGetPreviewContent
+* ShowRawCssJs was deprecated in favor of custom rendering implemented in the respective ContentHandler object.
+
+
+== Database Storage ==
+
+Page content is stored in the database using the same mechanism as before. Non-text content is serialized first. The
+appropriate serialization and deserialization is handled by the Revision class.
+
+Each revision's content model and serialization format is stored in the revision table (resp. in the archive table, if
+the revision was deleted). The page's (current) content model (that is, the conent model of the latest revision) is also
+stored in the page table.
+
+Note however that the content model and format is only stored if it differs from the page's default, as determined by
+ContentHandler::getDefaultModelFor( $title ). The default values are represented as NULL in the database, to preserve
+space.
+
+Storage of content model and format can be disabled altogether by setting $wgContentHandlerUseDB = false. In that case,
+the page's default model (and the model's default format) will be used everywhere. Attempts to store a revision of a page
+using a model or format different from the default will result in an error.
+
+
+== Globals ==
+
+There are some new globals that can be used to control the behavior of the ContentHandler facility:
+
+* $wgContentHandlers associates content model IDs with the names of the appropriate ContentHandler subclasses.
+
+* $wgNamespaceContentModels maps namespace IDs to a content model that should be the default for that namespace.
+
+* $wgContentHandlerUseDB determines whether each revision's content model should be stored in the database.
+  Defaults is true.
+
+* $wgContentHandlerTextFallback determines how the compatibility method ContentHandler::getContentText() will behave for
+  non-text content:
+    'ignore'     causes null to be returned for non-text content (default).
+    'serialize'  causes the serialized form of any non-text content to be returned (scary).
+    'fail'       causes an exception to be thrown for non-text content (strict).
+
+
+== Caveats ==
+
+There are some changes in behavior that might be surprising to users:
+
+* Javascript and CSS pages are no longer parsed as wikitext (though pre-save transform is still applied). Most
+importantly, this means that links, including categorization links, contained in the code will not work.
+
+* With $wgContentHandlerUseDB = false, pages can not be moved in a way that would change the
+default model. E.g. [[MediaWiki:foo.js]] can not be moved to [[MediaWiki:foo bar]], but can still be moved to
+[[User:John/foo.js]]. Also, in this mode, changing the default content model for a page (e.g. by changing
+$wgNamespaceContentModels) may cause it to become inaccessible.
+
+* action=edit will fail for pages with non-text content, unless the respective ContentHandler implementation has
+provided a specialized handler for the edit action. This is true for the API as well.
+
+* action=raw will fail for all non-text content. This seems better than serving content in other formats to an
+unsuspecting recipient. This will also cause client-side diffs to fail.
+
+* File pages provide their own action overrides that do not combine gracefully with any custom handlers defined by a
+ContentHandler. If for example a File page used a content model with a custom revert action, this would be overridden by
+WikiFilePage's handler for the revert action.
diff --git a/docs/export-0.8.xsd b/docs/export-0.8.xsd
new file mode 100644 (file)
index 0000000..a18c608
--- /dev/null
@@ -0,0 +1,289 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+       This is an XML Schema description of the format
+       output by MediaWiki's Special:Export system.
+
+       Version 0.2 adds optional basic file upload info support,
+       which is used by our OAI export/import submodule.
+
+       Version 0.3 adds some site configuration information such
+       as a list of defined namespaces.
+
+       Version 0.4 adds per-revision delete flags, log exports,
+       discussion threading data, a per-page redirect flag, and
+       per-namespace capitalization.
+
+       Version 0.5 adds byte count per revision.
+
+       Version 0.6 adds a separate namespace tag, and resolves the
+       redirect target and adds a separate sha1 tag for each revision.
+
+       Version 0.7 adds a unique identity constraint for both page and
+       revision identifiers. See also bug 4220.
+       Fix type for <ns> from "positiveInteger" to "nonNegativeInteger" to allow 0
+       Moves <logitem> to its right location.
+       Add parentid to revision.
+       Fix type for <id> within <contributor> to "nonNegativeInteger"
+
+       Version 0.8 adds support for a <model> and a <format> tag for
+       each revision. See contenthandler.txt.
+
+       The canonical URL to the schema document is:
+       http://www.mediawiki.org/xml/export-0.8.xsd
+
+       Use the namespace:
+       http://www.mediawiki.org/xml/export-0.8/
+-->
+<schema xmlns="http://www.w3.org/2001/XMLSchema"
+               xmlns:mw="http://www.mediawiki.org/xml/export-0.8/"
+               targetNamespace="http://www.mediawiki.org/xml/export-0.8/"
+               elementFormDefault="qualified">
+
+       <annotation>
+               <documentation xml:lang="en">
+                       MediaWiki's page export format
+               </documentation>
+       </annotation>
+
+       <!-- Need this to reference xml:lang -->
+       <import namespace="http://www.w3.org/XML/1998/namespace"
+                       schemaLocation="http://www.w3.org/2001/xml.xsd" />
+
+       <!-- Our root element -->
+       <element name="mediawiki" type="mw:MediaWikiType">
+               <!-- Page ID contraint, see bug 4220 -->
+               <unique name="PageIDConstraint">
+                       <selector xpath="mw:page" />
+                       <field xpath="mw:id" />
+               </unique>
+               <!-- Revision ID contraint, see bug 4220 -->
+               <unique name="RevIDConstraint">
+                       <selector xpath="mw:page/mw:revision" />
+                       <field xpath="mw:id" />
+               </unique>
+       </element>
+
+       <complexType name="MediaWikiType">
+               <sequence>
+                       <element name="siteinfo" type="mw:SiteInfoType"
+                                        minOccurs="0" maxOccurs="1" />
+                       <element name="page" type="mw:PageType"
+                                        minOccurs="0" maxOccurs="unbounded" />
+                       <element name="logitem" type="mw:LogItemType"
+                                        minOccurs="0" maxOccurs="unbounded" />
+               </sequence>
+               <attribute name="version" type="string" use="required" />
+               <attribute ref="xml:lang" use="required" />
+       </complexType>
+
+       <complexType name="SiteInfoType">
+               <sequence>
+                       <element name="sitename" type="string" minOccurs="0" />
+                       <element name="base" type="anyURI" minOccurs="0" />
+                       <element name="generator" type="string" minOccurs="0" />
+                       <element name="case" type="mw:CaseType" minOccurs="0" />
+                       <element name="namespaces" type="mw:NamespacesType" minOccurs="0" />
+               </sequence>
+       </complexType>
+
+       <simpleType name="CaseType">
+               <restriction base="NMTOKEN">
+                       <!-- Cannot have two titles differing only by case of first letter. -->
+                       <!-- Default behavior through 1.5, $wgCapitalLinks = true -->
+                       <enumeration value="first-letter" />
+
+                       <!-- Complete title is case-sensitive -->
+                       <!-- Behavior when $wgCapitalLinks = false -->
+                       <enumeration value="case-sensitive" />
+
+                       <!-- Cannot have non-case senstitive titles eg [[FOO]] == [[Foo]] -->
+                       <!-- Not yet implemented as of MediaWiki 1.18 -->
+                       <enumeration value="case-insensitive" />
+               </restriction>
+       </simpleType>
+
+       <simpleType name="DeletedFlagType">
+               <restriction base="NMTOKEN">
+                       <enumeration value="deleted" />
+               </restriction>
+       </simpleType>
+
+       <complexType name="NamespacesType">
+               <sequence>
+                       <element name="namespace" type="mw:NamespaceType"
+                                        minOccurs="0" maxOccurs="unbounded" />
+               </sequence>
+       </complexType>
+
+       <complexType name="NamespaceType">
+               <simpleContent>
+                       <extension base="string">
+                               <attribute name="key" type="integer" />
+                               <attribute name="case" type="mw:CaseType" />
+                       </extension>
+               </simpleContent>
+       </complexType>
+
+       <complexType name="RedirectType">
+               <simpleContent>
+                       <extension base="string">
+                               <attribute name="title" type="string" />
+                       </extension>
+               </simpleContent>
+       </complexType>
+
+       <simpleType name="ContentModelType">
+               <restriction base="string">
+                       <pattern value="[a-zA-Z][-+./a-zA-Z0-9]*"/>
+               </restriction>
+       </simpleType>
+
+       <simpleType name="ContentFormatType">
+               <restriction base="string">
+                       <pattern value='[a-zA-Z][-+.a-zA-Z0-9]*\/[a-zA-Z][-+.a-zA-Z0-9]*'/>
+               </restriction>
+       </simpleType>
+
+       <complexType name="PageType">
+               <sequence>
+                       <!-- Title in text form. (Using spaces, not underscores; with namespace ) -->
+                       <element name="title" type="string" />
+
+                       <!-- Namespace in canonical form -->
+                       <element name="ns" type="nonNegativeInteger" />
+
+                       <!-- optional page ID number -->
+                       <element name="id" type="positiveInteger" />
+
+                       <!-- flag if the current revision is a redirect -->
+                       <element name="redirect" type="mw:RedirectType" minOccurs="0" maxOccurs="1" />
+
+                       <!-- comma-separated list of string tokens, if present -->
+                       <element name="restrictions" type="string" minOccurs="0" />
+
+                       <!-- Zero or more sets of revision or upload data -->
+                       <choice minOccurs="0" maxOccurs="unbounded">
+                               <element name="revision" type="mw:RevisionType" />
+                               <element name="upload" type="mw:UploadType" />
+                       </choice>
+
+                       <!-- Zero or One sets of discussion threading data -->
+                       <element name="discussionthreadinginfo" minOccurs="0" maxOccurs="1" type="mw:DiscussionThreadingInfo" />
+               </sequence>
+       </complexType>
+
+       <complexType name="RevisionType">
+               <sequence>
+                       <element name="id" type="positiveInteger" />
+                       <element name="parentid" type="positiveInteger" minOccurs="0" />
+                       <element name="timestamp" type="dateTime" />
+                       <element name="contributor" type="mw:ContributorType" />
+                       <element name="minor" minOccurs="0" maxOccurs="1" />
+                       <element name="comment" type="mw:CommentType" minOccurs="0" maxOccurs="1" />
+                       <element name="text" type="mw:TextType" />
+                       <element name="sha1" type="string" />
+                       <element name="model" type="mw:ContentModelType" />
+                       <element name="format" type="mw:ContentFormatType" />
+               </sequence>
+       </complexType>
+
+       <complexType name="LogItemType">
+               <sequence>
+                       <element name="id" type="positiveInteger" />
+                       <element name="timestamp" type="dateTime" />
+                       <element name="contributor" type="mw:ContributorType" />
+                       <element name="comment" type="mw:CommentType" minOccurs="0" />
+                       <element name="type" type="string" />
+                       <element name="action" type="string" />
+                       <element name="text" type="mw:LogTextType" minOccurs="0" maxOccurs="1" />
+                       <element name="logtitle" type="string" minOccurs="0" maxOccurs="1" />
+                       <element name="params" type="mw:LogParamsType" minOccurs="0" maxOccurs="1" />
+               </sequence>
+       </complexType>
+
+       <complexType name="CommentType">
+               <simpleContent>
+                       <extension base="string">
+                               <!-- This allows deleted=deleted on non-empty elements, but XSD is not omnipotent -->
+                               <attribute name="deleted" use="optional" type="mw:DeletedFlagType" />
+                       </extension>
+               </simpleContent>
+       </complexType>
+
+       <complexType name="TextType">
+               <simpleContent>
+                       <extension base="string">
+                               <attribute ref="xml:space" use="optional" default="preserve" />
+                               <!-- This allows deleted=deleted on non-empty elements, but XSD is not omnipotent -->
+                               <attribute name="deleted" use="optional" type="mw:DeletedFlagType" />
+                               <!-- This isn't a good idea; we should be using "ID" instead of "NMTOKEN" -->
+                               <!-- However, "NMTOKEN" is strictest definition that is both compatible with existing -->
+                               <!-- usage ([0-9]+) and with the "ID" type. -->
+                               <attribute name="id" type="NMTOKEN" />
+                               <attribute name="bytes" use="optional" type="nonNegativeInteger" />
+                       </extension>
+               </simpleContent>
+       </complexType>
+
+       <complexType name="LogTextType">
+               <simpleContent>
+                       <extension base="string">
+                               <!-- This allows deleted=deleted on non-empty elements, but XSD is not omnipotent -->
+                               <attribute name="deleted" use="optional" type="mw:DeletedFlagType" />
+                       </extension>
+               </simpleContent>
+       </complexType>
+
+       <complexType name="LogParamsType">
+               <simpleContent>
+                       <extension base="string">
+                               <attribute ref="xml:space" use="optional" default="preserve" />
+                       </extension>
+               </simpleContent>
+       </complexType>
+
+       <complexType name="ContributorType">
+               <sequence>
+                       <element name="username" type="string" minOccurs="0" />
+                       <element name="id" type="nonNegativeInteger" minOccurs="0" />
+
+                       <element name="ip" type="string" minOccurs="0" />
+               </sequence>
+               <!-- This allows deleted=deleted on non-empty elements, but XSD is not omnipotent -->
+               <attribute name="deleted" use="optional" type="mw:DeletedFlagType" />
+       </complexType>
+
+       <complexType name="UploadType">
+               <sequence>
+                       <!-- Revision-style data... -->
+                       <element name="timestamp" type="dateTime" />
+                       <element name="contributor" type="mw:ContributorType" />
+                       <element name="comment" type="string" minOccurs="0" />
+
+                       <!-- Filename. (Using underscores, not spaces. No 'File:' namespace marker.) -->
+                       <element name="filename" type="string" />
+
+                       <!-- URI at which this resource can be obtained -->
+                       <element name="src" type="anyURI" />
+
+                       <element name="size" type="positiveInteger" />
+
+                       <!-- TODO: add other metadata fields -->
+               </sequence>
+       </complexType>
+
+       <!-- Discussion threading data for LiquidThreads -->
+       <complexType name="DiscussionThreadingInfo">
+               <sequence>
+                       <element name="ThreadSubject" type="string" />
+                       <element name="ThreadParent" type="positiveInteger" />
+                       <element name="ThreadAncestor" type="positiveInteger" />
+                       <element name="ThreadPage" type="string" />
+                       <element name="ThreadID" type="positiveInteger" />
+                       <element name="ThreadAuthor" type="string" />
+                       <element name="ThreadEditStatus" type="string" />
+                       <element name="ThreadType" type="string" />
+               </sequence>
+       </complexType>
+
+</schema>
index 96757cd..a4b4e57 100644 (file)
@@ -283,6 +283,11 @@ $article: Article object
 $user: the User object that was created. (Parameter added in 1.7)
 $byEmail: true when account was created "by email" (added in 1.12)
 
+'AfterFinalPageOutput': At the end of OutputPage::output() but before
+final ob_end_flush() which will send the buffered output to the client.
+This allows for last-minute modification of the output within the buffer
+by using ob_get_clean().
+
 'AfterImportPage': When a page import is completed
 $title: Title under which the revisions were imported
 $origTitle: Title provided by the XML file
@@ -429,9 +434,14 @@ token types.
 used to retrieve this type of tokens.
 
 'ArticleAfterFetchContent': after fetching content of an article from
+the database. DEPRECATED, use ArticleAfterFetchContentObject instead.
+$article: the article (object) being loaded from the database
+&$content: the content (string) of the article
+
+'ArticleAfterFetchContentObject': after fetching content of an article from
 the database
 $article: the article (object) being loaded from the database
-$content: the content (string) of the article
+&$content: the content of the article, as a Content object
 
 'ArticleConfirmDelete': before writing the confirmation form for article
        deletion
@@ -458,6 +468,8 @@ $article: the WikiPage that was deleted
 $user: the user that deleted the article
 $reason: the reason the article was deleted
 $id: id of the article that was deleted
+$content: the Content of the deleted page
+$logEntry: the ManualLogEntry used to record the deletion
 
 'ArticleEditUpdateNewTalk': before updating user_newtalk when a user talk page
 was changed
@@ -479,7 +491,7 @@ Wiki::articleFromTitle()
 $title: title (object) used to create the article object
 $article: article (object) that will be returned
 
-'ArticleInsertComplete': After a new article is created
+'ArticleInsertComplete': After a new article is created. DEPRECATED, use ArticleContentInsertComplete
 $article: WikiPage created
 $user: User creating the article
 $text: New content
@@ -487,7 +499,18 @@ $summary: Edit summary/comment
 $isMinor: Whether or not the edit was marked as minor
 $isWatch: (No longer used)
 $section: (No longer used)
-$flags: Flags passed to WikiPage::doEdit()
+$flags: Flags passed to WikiPage::doEditContent()
+$revision: New Revision of the article
+
+'ArticleContentInsertComplete': After a new article is created
+$article: WikiPage created
+$user: User creating the article
+$content: New content as a Content object
+$summary: Edit summary/comment
+$isMinor: Whether or not the edit was marked as minor
+$isWatch: (No longer used)
+$section: (No longer used)
+$flags: Flags passed to WikiPage::doEditContent()
 $revision: New Revision of the article
 
 'ArticleMergeComplete': after merging to article using Special:Mergehistory
@@ -538,7 +561,7 @@ $user: the user who did the rollback
 $revision: the revision the page was reverted back to
 $current: the reverted revision
 
-'ArticleSave': before an article is saved
+'ArticleSave': before an article is saved. DEPRECATED, use ArticleContentSave instead
 $article: the WikiPage (object) being saved
 $user: the user (object) saving the article
 $text: the new article text
@@ -547,7 +570,16 @@ $isminor: minor flag
 $iswatch: watch flag
 $section: section #
 
-'ArticleSaveComplete': After an article has been updated
+'ArticleContentSave': before an article is saved.
+$article: the WikiPage (object) being saved
+$user: the user (object) saving the article
+$content: the new article content, as a Content object
+$summary: the article summary (comment)
+$isminor: minor flag
+$iswatch: watch flag
+$section: section #
+
+'ArticleSaveComplete': After an article has been updated. DEPRECATED, use ArticleContentSaveComplete instead.
 $article: WikiPage modified
 $user: User performing the modification
 $text: New content
@@ -555,9 +587,22 @@ $summary: Edit summary/comment
 $isMinor: Whether or not the edit was marked as minor
 $isWatch: (No longer used)
 $section: (No longer used)
-$flags: Flags passed to WikiPage::doEdit()
+$flags: Flags passed to WikiPage::doEditContent()
+$revision: New Revision of the article
+$status: Status object about to be returned by doEditContent()
+$baseRevId: the rev ID (or false) this edit was based on
+
+'ArticleContentSaveComplete': After an article has been updated
+$article: WikiPage modified
+$user: User performing the modification
+$content: New content, as a Content object
+$summary: Edit summary/comment
+$isMinor: Whether or not the edit was marked as minor
+$isWatch: (No longer used)
+$section: (No longer used)
+$flags: Flags passed to WikiPage::doEditContent()
 $revision: New Revision of the article
-$status: Status object about to be returned by doEdit()
+$status: Status object about to be returned by doEditContent()
 $baseRevId: the rev ID (or false) this edit was based on
 
 'ArticleUndelete': When one or more revisions of an article are restored
@@ -586,11 +631,19 @@ object to both indicate that the output is done and what parser output was used.
 follwed an redirect
 $article: target article (object)
 
-'ArticleViewCustom': allows to output the text of the article in a different format than wikitext
+'ArticleViewCustom': allows to output the text of the article in a different format than wikitext.
+DEPRECATED, use ArticleContentViewCustom instead.
+Note that it is preferrable to implement proper handing for a custom data type using the ContentHandler facility.
 $text: text of the page
 $title: title of the page
 $output: reference to $wgOut
 
+'ArticleContentViewCustom': allows to output the text of the article in a different format than wikitext.
+Note that it is preferrable to implement proper handing for a custom data type using the ContentHandler facility.
+$content: content of the page, as a Content object
+$title: title of the page
+$output: reference to $wgOut
+
 'AuthPluginAutoCreate': Called when creating a local account for an user logged
 in from an external authentication method
 $user: User object created locally
@@ -722,6 +775,16 @@ the collation given in $collationName.
 'ConfirmEmailComplete': Called after a user's email has been confirmed successfully
 $user: user (object) whose email is being confirmed
 
+'ContentHandlerDefaultModelFor': Called when the default content model is determiend
+for a given title. May be used to assign a different model for that title.
+$title: the Title in question
+&$model: the model name. Use with CONTENT_MODEL_XXX constants.
+
+'ContentHandlerForModelID': Called when a ContentHandler is requested for a given
+cointent model name, but no entry for that model exists in $wgContentHandlers.
+$modeName: the requested content model name
+&$handler: set this to a ContentHandler object, if desired.
+
 'ContribsPager::getQueryInfo': Before the contributions query is about to run
 &$pager: Pager object for contributions
 &$queryInfo: The query for the contribs Pager
@@ -795,12 +858,19 @@ $section: Section being edited
 &$error: Error message to return
 $summary: Edit summary for page
 
-'EditFilterMerged': Post-section-merge edit filter
+'EditFilterMerged': Post-section-merge edit filter.
+DEPRECATED, use EditFilterMergedContent instead.
 $editor: EditPage instance (object)
 $text: content of the edit box
 &$error: error message to return
 $summary: Edit summary for page
 
+'EditFilterMergedContent': Post-section-merge edit filter
+$editor: EditPage instance (object)
+$content: content of the edit box, as a Content object
+&$error: error message to return
+$summary: Edit summary for page
+
 'EditFormPreloadText': Allows population of the edit form when creating
 new pages
 &$text: Text to preload with
@@ -811,7 +881,7 @@ pages
 $editPage: EditPage    object
 
 'EditPage::attemptSave': called before an article is
-saved, that is before WikiPage::doEdit() is called
+saved, that is before WikiPage::doEditContent() is called
 $editpage_Obj: the current EditPage object
 
 'EditPage::importFormData': allow extensions to read additional data
@@ -863,14 +933,28 @@ $title: title of page being edited
 &$msg: localization message name, overridable. Default is either 'copyrightwarning' or 'copyrightwarning2'
 
 'EditPageGetDiffText': Allow modifying the wikitext that will be used in
-"Show changes"
+"Show changes". DEPRECATED. Use EditPageGetDiffContent instead.
+Note that it is preferrable to implement diff handling for different data types using the ContentHandler facility.
+$editPage: EditPage object
+&$newtext: wikitext that will be used as "your version"
+
+'EditPageGetDiffContent': Allow modifying the wikitext that will be used in
+"Show changes".
+Note that it is preferrable to implement diff handling for different data types using the ContentHandler facility.
 $editPage: EditPage object
 &$newtext: wikitext that will be used as "your version"
 
-'EditPageGetPreviewText': Allow modifying the wikitext that will be previewed
+'EditPageGetPreviewText': Allow modifying the wikitext that will be previewed.
+DEPRECATED. Use EditPageGetPreviewContent instead.
+Note that it is preferrable to implement previews for different data types using the COntentHandler facility.
 $editPage: EditPage object
 &$toparse: wikitext that will be parsed
 
+'EditPageGetPreviewContent': Allow modifying the wikitext that will be previewed.
+Note that it is preferrable to implement previews for different data types using the COntentHandler facility.
+$editPage: EditPage object
+&$content: Content object to be previewed (may be replaced by hook function)
+
 'EditPageNoSuchSection': When a section edit request is given for an non-existent section
 &$editpage: The current EditPage object
 &$res: the HTML of the error text
@@ -1730,7 +1814,8 @@ $title : Current Title object being displayed in search results.
 'ShowMissingArticle': Called when generating the output for a non-existent page
 $article: The article object corresponding to the page
 
-'ShowRawCssJs': Customise the output of raw CSS and JavaScript in page views
+'ShowRawCssJs': Customise the output of raw CSS and JavaScript in page views.
+DEPRECATED, use the ContentHandler facility to handle CSS and JavaScript!
 $text: Text being shown
 $title: Title of the custom script/stylesheet page
 $output: Current OutputPage object
@@ -2349,6 +2434,14 @@ One, and only one hook should set this, and return false.
 &$opts: Options to use for the query
 &$join: Join conditions
 
+'WikiPageDeletionUpdates': manipulate the list of DataUpdates to be applied when
+       a page is deleted. Called in WikiPage::getDeletionUpdates().
+       Note that updates specific to a content model should be provided by the
+       respective Content's getDeletionUpdates() method.
+$page: the WikiPage
+$content: the Content to generate updates for
+&$updates: the array of DataUpdate objects. Hook function may want to add to it.
+
 'wfShellWikiCmd': Called when generating a shell-escaped command line
        string to run a MediaWiki cli script.
 &$script: MediaWiki cli script path
index 3872edc..971a611 100644 (file)
@@ -198,7 +198,7 @@ Revision text:
        expriry: $wgRevisionCacheExpiry
 
 Sessions:
-       controlled by: $wgSessionsInMemcached
+       controlled by: $wgSessionsInObjectCache
        key: $wgBDname:session:$id
        ex: wikidb:session:38d7c5b8d3bfc51egf40c69bc40f8be3
        stores: $SESSION, useful when using a multi-sever wiki
index 5192225..19552bc 100644 (file)
@@ -272,7 +272,7 @@ abstract class Action {
         * must throw subclasses of ErrorPageError
         *
         * @param $user User: the user to check, or null to use the context user
-        * @throws ErrorPageError
+        * @throws UserBlockedError|ReadOnlyError|PermissionsError
         * @return bool True on success
         */
        protected function checkCanExecute( User $user ) {
@@ -546,6 +546,7 @@ abstract class FormlessAction extends Action {
         * forms, they probably won't have any data, but some (eg rollback) may do
         * @param $data Array values that would normally be in the GET request
         * @param $captureErrors Bool whether to catch exceptions and just return false
+        * @throws ErrorPageError
         * @return Bool whether execution was successful
         */
        public function execute( array $data = null, $captureErrors = true ) {
index db4444e..7367812 100644 (file)
@@ -57,10 +57,17 @@ class Article extends Page {
        public $mParserOptions;
 
        /**
-        * Content of the revision we are working on
+        * Text of the revision we are working on
         * @var string $mContent
         */
-       var $mContent;                    // !<
+       var $mContent;                    // !< #BC cruft
+
+       /**
+        * Content of the revision we are working on
+        * @var Content
+        * @since 1.21
+        */
+       var $mContentObject;              // !<
 
        /**
         * Is the content ($mContent) already loaded?
@@ -231,9 +238,32 @@ class Article extends Page {
         * This function has side effects! Do not use this function if you
         * only want the real revision text if any.
         *
+        * @deprecated in 1.21; use WikiPage::getContent() instead
+        *
         * @return string Return the text of this revision
         */
        public function getContent() {
+               wfDeprecated( __METHOD__, '1.21' );
+               $content = $this->getContentObject();
+               return ContentHandler::getContentText( $content );
+       }
+
+       /**
+        * Returns a Content object representing the pages effective display content,
+        * not necessarily the revision's content!
+        *
+        * Note that getContent/loadContent do not follow redirects anymore.
+        * If you need to fetch redirectable content easily, try
+        * the shortcut in WikiPage::getRedirectTarget()
+        *
+        * This function has side effects! Do not use this function if you
+        * only want the real revision text if any.
+        *
+        * @return Content Return the content of this revision
+        *
+        * @since 1.21
+        */
+       protected function getContentObject() {
                wfProfileIn( __METHOD__ );
 
                if ( $this->mPage->getID() === 0 ) {
@@ -244,18 +274,20 @@ class Article extends Page {
                                if ( $text === false ) {
                                        $text = '';
                                }
+
+                               $content = ContentHandler::makeContent( $text, $this->getTitle() );
                        } else {
                                $message = $this->getContext()->getUser()->isLoggedIn() ? 'noarticletext' : 'noarticletextanon';
-                               $text = wfMessage( $message )->text();
+                               $content = new MessageContent( $message, null, 'parsemag' );
                        }
                        wfProfileOut( __METHOD__ );
 
-                       return $text;
+                       return $content;
                } else {
-                       $this->fetchContent();
+                       $this->fetchContentObject();
                        wfProfileOut( __METHOD__ );
 
-                       return $this->mContent;
+                       return $this->mContentObject;
                }
        }
 
@@ -336,22 +368,61 @@ class Article extends Page {
         * Get text of an article from database
         * Does *NOT* follow redirects.
         *
+        * @protected
+        * @note this is really internal functionality that should really NOT be used by other functions. For accessing
+        *       article content, use the WikiPage class, especially WikiBase::getContent(). However, a lot of legacy code
+        *       uses this method to retrieve page text from the database, so the function has to remain public for now.
+        *
         * @return mixed string containing article contents, or false if null
+        * @deprecated in 1.21, use WikiPage::getContent() instead
         */
-       function fetchContent() {
-               if ( $this->mContentLoaded ) {
+       function fetchContent() { #BC cruft!
+               wfDeprecated( __METHOD__, '1.21' );
+
+               if ( $this->mContentLoaded && $this->mContent ) {
                        return $this->mContent;
                }
 
                wfProfileIn( __METHOD__ );
 
+               $content = $this->fetchContentObject();
+
+               $this->mContent = ContentHandler::getContentText( $content ); #@todo: get rid of mContent everywhere!
+               ContentHandler::runLegacyHooks( 'ArticleAfterFetchContent', array( &$this, &$this->mContent ) );
+
+               wfProfileOut( __METHOD__ );
+
+               return $this->mContent;
+       }
+
+
+       /**
+        * Get text content object
+        * Does *NOT* follow redirects.
+        * TODO: when is this null?
+        *
+        * @note code that wants to retrieve page content from the database should use WikiPage::getContent().
+        *
+        * @return Content|null
+        *
+        * @since 1.21
+        */
+       protected function fetchContentObject() {
+               if ( $this->mContentLoaded ) {
+                       return $this->mContentObject;
+               }
+
+               wfProfileIn( __METHOD__ );
+
                $this->mContentLoaded = true;
+               $this->mContent = null;
 
                $oldid = $this->getOldID();
 
                # Pre-fill content with error message so that if something
                # fails we'll have something telling us what we intended.
-               $this->mContent = wfMessage( 'missing-revision', $oldid )->plain();
+               //XXX: this isn't page content but a UI message. horrible.
+               $this->mContentObject = new MessageContent( 'missing-revision', array( $oldid ), array() ) ;
 
                if ( $oldid ) {
                        # $this->mRevision might already be fetched by getOldIDFromRequest()
@@ -371,6 +442,7 @@ class Article extends Page {
                        }
 
                        $this->mRevision = $this->mPage->getRevision();
+
                        if ( !$this->mRevision ) {
                                wfDebug( __METHOD__ . " failed to retrieve current page, rev_id " . $this->mPage->getLatest() . "\n" );
                                wfProfileOut( __METHOD__ );
@@ -380,14 +452,14 @@ class Article extends Page {
 
                // @todo FIXME: Horrible, horrible! This content-loading interface just plain sucks.
                // We should instead work with the Revision object when we need it...
-               $this->mContent = $this->mRevision->getText( Revision::FOR_THIS_USER, $this->getContext()->getUser() ); // Loads if user is allowed
+               $this->mContentObject = $this->mRevision->getContent( Revision::FOR_THIS_USER, $this->getContext()->getUser() ); // Loads if user is allowed
                $this->mRevIdFetched = $this->mRevision->getId();
 
-               wfRunHooks( 'ArticleAfterFetchContent', array( &$this, &$this->mContent ) );
+               wfRunHooks( 'ArticleAfterFetchContentObject', array( &$this, &$this->mContentObject ) );
 
                wfProfileOut( __METHOD__ );
 
-               return $this->mContent;
+               return $this->mContentObject;
        }
 
        /**
@@ -420,7 +492,7 @@ class Article extends Page {
         * @return Revision|null
         */
        public function getRevisionFetched() {
-               $this->fetchContent();
+               $this->fetchContentObject();
 
                return $this->mRevision;
        }
@@ -580,7 +652,7 @@ class Article extends Page {
                                        break;
                                case 3:
                                        # This will set $this->mRevision if needed
-                                       $this->fetchContent();
+                                       $this->fetchContentObject();
 
                                        # Are we looking at an old revision
                                        if ( $oldid && $this->mRevision ) {
@@ -604,18 +676,25 @@ class Article extends Page {
                                                wfDebug( __METHOD__ . ": showing CSS/JS source\n" );
                                                $this->showCssOrJsPage();
                                                $outputDone = true;
-                                       } elseif( !wfRunHooks( 'ArticleViewCustom', array( $this->mContent, $this->getTitle(), $outputPage ) ) ) {
+                                       } elseif( !wfRunHooks( 'ArticleContentViewCustom',
+                                                       array( $this->fetchContentObject(), $this->getTitle(), $outputPage ) ) ) {
+
+                                               # Allow extensions do their own custom view for certain pages
+                                               $outputDone = true;
+                                       } elseif( !ContentHandler::runLegacyHooks( 'ArticleViewCustom',
+                                                       array( $this->fetchContentObject(), $this->getTitle(), $outputPage ) ) ) {
+
                                                # Allow extensions do their own custom view for certain pages
                                                $outputDone = true;
                                        } else {
-                                               $text = $this->getContent();
-                                               $rt = Title::newFromRedirectArray( $text );
+                                               $content = $this->getContentObject();
+                                               $rt = $content->getRedirectChain();
                                                if ( $rt ) {
                                                        wfDebug( __METHOD__ . ": showing redirect=no page\n" );
                                                        # Viewing a redirect page (e.g. with parameter redirect=no)
                                                        $outputPage->addHTML( $this->viewRedirect( $rt ) );
                                                        # Parse just to get categories, displaytitle, etc.
-                                                       $this->mParserOutput = $wgParser->parse( $text, $this->getTitle(), $parserOptions );
+                                                       $this->mParserOutput = $content->getParserOutput( $this->getTitle(), $oldid, $parserOptions, false );
                                                        $outputPage->addParserOutputNoText( $this->mParserOutput );
                                                        $outputDone = true;
                                                }
@@ -625,8 +704,9 @@ class Article extends Page {
                                        # Run the parse, protected by a pool counter
                                        wfDebug( __METHOD__ . ": doing uncached parse\n" );
 
+                                       // @todo: shouldn't we be passing $this->getPage() to PoolWorkArticleView instead of plain $this?
                                        $poolArticleView = new PoolWorkArticleView( $this, $parserOptions,
-                                               $this->getRevIdFetched(), $useParserCache, $this->getContent() );
+                                               $this->getRevIdFetched(), $useParserCache, $this->getContentObject(), $this->getContext() );
 
                                        if ( !$poolArticleView->execute() ) {
                                                $error = $poolArticleView->getError();
@@ -708,6 +788,8 @@ class Article extends Page {
        /**
         * Show a diff page according to current request variables. For use within
         * Article::view() only, other callers should use the DifferenceEngine class.
+        *
+        * @todo: make protected
         */
        public function showDiffPage() {
                $request = $this->getContext()->getRequest();
@@ -719,7 +801,17 @@ class Article extends Page {
                $unhide = $request->getInt( 'unhide' ) == 1;
                $oldid = $this->getOldID();
 
-               $de = new DifferenceEngine( $this->getContext(), $oldid, $diff, $rcid, $purge, $unhide );
+               $rev = $this->getRevisionFetched();
+
+               if ( !$rev ) {
+                       $this->getContext()->getOutput()->setPageTitle( wfMessage( 'errorpagetitle' ) );
+                       $this->getContext()->getOutput()->addWikiMsg( 'difference-missing-revision', $oldid, 1 );
+                       return;
+               }
+
+               $contentHandler = $rev->getContentHandler();
+               $de = $contentHandler->createDifferenceEngine( $this->getContext(), $oldid, $diff, $rcid, $purge, $unhide );
+
                // DifferenceEngine directly fetched the revision:
                $this->mRevIdFetched = $de->mNewid;
                $de->showDiffPage( $diffOnly );
@@ -736,23 +828,24 @@ class Article extends Page {
         *
         * This is hooked by SyntaxHighlight_GeSHi to do syntax highlighting of these
         * page views.
+        *
+        * @param bool $showCacheHint whether to show a message telling the user to clear the browser cache (default: true).
         */
-       protected function showCssOrJsPage() {
-               $dir = $this->getContext()->getLanguage()->getDir();
-               $lang = $this->getContext()->getLanguage()->getCode();
-
+       protected function showCssOrJsPage( $showCacheHint = true ) {
                $outputPage = $this->getContext()->getOutput();
-               $outputPage->wrapWikiMsg( "<div id='mw-clearyourcache' lang='$lang' dir='$dir' class='mw-content-$dir'>\n$1\n</div>",
-                       'clearyourcache' );
+
+               if ( $showCacheHint ) {
+                       $dir = $this->getContext()->getLanguage()->getDir();
+                       $lang = $this->getContext()->getLanguage()->getCode();
+
+                       $outputPage->wrapWikiMsg( "<div id='mw-clearyourcache' lang='$lang' dir='$dir' class='mw-content-$dir'>\n$1\n</div>",
+                               'clearyourcache' );
+               }
 
                // Give hooks a chance to customise the output
-               if ( wfRunHooks( 'ShowRawCssJs', array( $this->mContent, $this->getTitle(), $outputPage ) ) ) {
-                       // Wrap the whole lot in a <pre> and don't parse
-                       $m = array();
-                       preg_match( '!\.(css|js)$!u', $this->getTitle()->getText(), $m );
-                       $outputPage->addHTML( "<pre class=\"mw-code mw-{$m[1]}\" dir=\"ltr\">\n" );
-                       $outputPage->addHTML( htmlspecialchars( $this->mContent ) );
-                       $outputPage->addHTML( "\n</pre>\n" );
+               if ( ContentHandler::runLegacyHooks( 'ShowRawCssJs', array( $this->fetchContentObject(), $this->getTitle(), $outputPage ) ) ) {
+                       $po = $this->mContentObject->getParserOutput( $this->getTitle() );
+                       $outputPage->addHTML( $po->getText() );
                }
        }
 
@@ -1377,7 +1470,13 @@ class Article extends Page {
                // Generate deletion reason
                $hasHistory = false;
                if ( !$reason ) {
-                       $reason = $this->generateReason( $hasHistory );
+                       try {
+                               $reason = $this->generateReason( $hasHistory );
+                       } catch ( MWException $e ) {
+                               # if a page is horribly broken, we still want to be able to delete it. so be lenient about errors here.
+                               wfDebug("Error while building auto delete summary: $e");
+                               $reason = '';
+                       }
                }
 
                // If the page has a history, insert a warning
@@ -1617,6 +1716,8 @@ class Article extends Page {
         * @return ParserOutput or false if the given revsion ID is not found
         */
        public function getParserOutput( $oldid = null, User $user = null ) {
+               //XXX: bypasses mParserOptions and thus setParserOptions()
+
                if ( $user === null ) {
                        $parserOptions = $this->getParserOptions();
                } else {
@@ -1626,6 +1727,21 @@ class Article extends Page {
                return $this->mPage->getParserOutput( $parserOptions, $oldid );
        }
 
+       /**
+        * Override the ParserOptions used to render the primary article wikitext.
+        *
+        * @param ParserOptions $options
+        * @throws MWException if the parser options where already initialized.
+        */
+       public function setParserOptions( ParserOptions $options ) {
+               if ( $this->mParserOptions ) {
+                       throw new MWException( "can't change parser options after they have already been set" );
+               }
+
+               // clone, so if $options is modified later, it doesn't confuse the parser cache.
+               $this->mParserOptions = clone $options;
+       }
+
        /**
         * Get parser options suitable for rendering the primary article wikitext
         * @return ParserOptions
@@ -1845,7 +1961,13 @@ class Article extends Page {
         * @return bool
         */
        public function updateRestrictions( $limit = array(), $reason = '', &$cascade = 0, $expiry = array() ) {
-               return $this->mPage->updateRestrictions( $limit, $reason, $cascade, $expiry );
+               return $this->mPage->doUpdateRestrictions(
+                       $limit,
+                       $expiry,
+                       $cascade,
+                       $reason,
+                       $this->getContext()->getUser()
+               );
        }
 
        /**
@@ -1892,7 +2014,9 @@ class Article extends Page {
         * @return mixed
         */
        public function generateReason( &$hasHistory ) {
-               return $this->mPage->getAutoDeleteReason( $hasHistory );
+               $title = $this->mPage->getTitle();
+               $handler = ContentHandler::getForTitle( $title );
+               return $handler->getAutoDeleteReason( $title, $hasHistory );
        }
 
        // ****** B/C functions for static methods ( __callStatic is PHP>=5.3 ) ****** //
@@ -1930,6 +2054,7 @@ class Article extends Page {
         * @param $newtext
         * @param $flags
         * @return string
+        * @deprecated since 1.21, use ContentHandler::getAutosummary() instead
         */
        public static function getAutosummary( $oldtext, $newtext, $flags ) {
                return WikiPage::getAutosummary( $oldtext, $newtext, $flags );
index 706e1c8..7deeec0 100644 (file)
@@ -246,6 +246,7 @@ $wgAutoloadLocalClasses = array(
        'StubUserLang' => 'includes/StubObject.php',
        'TablePager' => 'includes/Pager.php',
        'MWTimestamp' => 'includes/Timestamp.php',
+       'TimestampException' => 'includes/Timestamp.php',
        'Title' => 'includes/Title.php',
        'TitleArray' => 'includes/TitleArray.php',
        'TitleArrayFromResult' => 'includes/TitleArray.php',
@@ -253,6 +254,7 @@ $wgAutoloadLocalClasses = array(
        'UnlistedSpecialPage' => 'includes/SpecialPage.php',
        'UploadSourceAdapter' => 'includes/Import.php',
        'UppercaseCollation' => 'includes/Collation.php',
+       'Uri' => 'includes/Uri.php',
        'User' => 'includes/User.php',
        'UserArray' => 'includes/UserArray.php',
        'UserArrayFromResult' => 'includes/UserArray.php',
@@ -288,6 +290,20 @@ $wgAutoloadLocalClasses = array(
        'ZipDirectoryReader' => 'includes/ZipDirectoryReader.php',
        'ZipDirectoryReaderError' => 'includes/ZipDirectoryReader.php',
 
+       # content handler
+       'AbstractContent' => 'includes/content/AbstractContent.php',
+       'ContentHandler' => 'includes/content/ContentHandler.php',
+       'Content' => 'includes/content/Content.php',
+       'CssContentHandler' => 'includes/content/ContentHandler.php',
+       'CssContent' => 'includes/content/CssContent.php',
+       'JavaScriptContentHandler' => 'includes/content/ContentHandler.php',
+       'JavaScriptContent' => 'includes/content/JavaScriptContent.php',
+       'MessageContent' => 'includes/content/MessageContent.php',
+       'TextContentHandler' => 'includes/content/ContentHandler.php',
+       'TextContent' => 'includes/content/TextContent.php',
+       'WikitextContentHandler' => 'includes/ContentHandler.php',
+       'WikitextContent' => 'includes/content/WikitextContent.php',
+
        # includes/actions
        'CachedAction' => 'includes/actions/CachedAction.php',
        'CreditsAction' => 'includes/actions/CreditsAction.php',
@@ -330,6 +346,7 @@ $wgAutoloadLocalClasses = array(
        'ApiFormatDump' => 'includes/api/ApiFormatDump.php',
        'ApiFormatFeedWrapper' => 'includes/api/ApiFormatBase.php',
        'ApiFormatJson' => 'includes/api/ApiFormatJson.php',
+       'ApiFormatNone' => 'includes/api/ApiFormatNone.php',
        'ApiFormatPhp' => 'includes/api/ApiFormatPhp.php',
        'ApiFormatRaw' => 'includes/api/ApiFormatRaw.php',
        'ApiFormatTxt' => 'includes/api/ApiFormatTxt.php',
@@ -447,7 +464,6 @@ $wgAutoloadLocalClasses = array(
        'Blob' => 'includes/db/DatabaseUtility.php',
        'ChronologyProtector' => 'includes/db/LBFactory.php',
        'CloneDatabase' => 'includes/db/CloneDatabase.php',
-       'Database' => 'includes/db/DatabaseMysql.php',
        'DatabaseBase' => 'includes/db/Database.php',
        'DatabaseIbm_db2' => 'includes/db/DatabaseIbm_db2.php',
        'DatabaseMssql' => 'includes/db/DatabaseMssql.php',
@@ -1007,7 +1023,12 @@ $wgAutoloadLocalClasses = array(
        'FakeConverter' => 'languages/Language.php',
        'Language' => 'languages/Language.php',
        'LanguageConverter' => 'languages/LanguageConverter.php',
+       'CLDRPluralRuleConverter' => 'languages/utils/CLDRPluralRuleEvaluator.php',
+       'CLDRPluralRuleConverter_Expression' => 'languages/utils/CLDRPluralRuleEvaluator.php',
+       'CLDRPluralRuleConverter_Fragment' => 'languages/utils/CLDRPluralRuleEvaluator.php',
+       'CLDRPluralRuleConverter_Operator' => 'languages/utils/CLDRPluralRuleEvaluator.php',
        'CLDRPluralRuleEvaluator' => 'languages/utils/CLDRPluralRuleEvaluator.php',
+       'CLDRPluralRuleEvaluator_Range' => 'languages/utils/CLDRPluralRuleEvaluator.php',
        'CLDRPluralRuleError' => 'languages/utils/CLDRPluralRuleEvaluator.php',
 
        # maintenance
@@ -1057,6 +1078,14 @@ $wgAutoloadLocalClasses = array(
        'TestFileIterator' => 'tests/testHelpers.inc',
        'TestRecorder' => 'tests/testHelpers.inc',
 
+       # tests/phpunit
+       'DummyContentHandlerForTesting' => 'tests/phpunit/includes/ContentHandlerTest.php',
+       'DummyContentForTesting' => 'tests/phpunit/includes/ContentHandlerTest.php',
+       'JavascriptContentTest' => 'tests/phpunit/includes/JavascriptContentTest.php',
+       'RevisionStorageTest' => 'tests/phpunit/includes/RevisionStorageTest.php',
+       'WikiPageTest' => 'tests/phpunit/includes/WikiPageTest.php',
+       'WikitextContentTest' => 'tests/phpunit/includes/WikitextContentTest.php',
+
        # tests/phpunit/includes
        'GenericArrayObjectTest' => 'tests/phpunit/includes/libs/GenericArrayObjectTest.php',
 
index 9c77855..d7ed2f9 100644 (file)
@@ -157,6 +157,7 @@ class Autopromote {
         *
         * @param $cond Array: A condition, which must not contain other conditions
         * @param $user User The user to check the condition against
+        * @throws MWException
         * @return bool Whether the condition is true for the user
         */
        private static function checkCondition( $cond, User $user ) {
index d2055dd..ba8691b 100644 (file)
@@ -217,6 +217,7 @@ class BacklinkCache {
        /**
         * Get the field name prefix for a given table
         * @param $table String
+        * @throws MWException
         * @return null|string
         */
        protected function getPrefix( $table ) {
@@ -245,6 +246,7 @@ class BacklinkCache {
         * Get the SQL condition array for selecting backlinks, with a join
         * on the page table.
         * @param $table String
+        * @throws MWException
         * @return array|null
         */
        protected function getConditions( $table ) {
@@ -381,6 +383,7 @@ class BacklinkCache {
         * Partition a DB result with backlinks in it into batches
         * @param $res ResultWrapper database result
         * @param $batchSize integer
+        * @throws MWException
         * @return array @see
         */
        protected function partitionResult( $res, $batchSize ) {
index 732699d..86b4d13 100644 (file)
@@ -231,6 +231,7 @@ class Block {
         *     3) An autoblock on the given IP
         * @param $vagueTarget User|String also search for blocks affecting this target.  Doesn't
         *     make any sense to use TYPE_AUTO / TYPE_ID here. Leave blank to skip IP lookups.
+        * @throws MWException
         * @return Bool whether a relevant block was found
         */
        protected function newLoad( $vagueTarget = null ) {
@@ -426,6 +427,7 @@ class Block {
        /**
         * Delete the row from the IP blocks table.
         *
+        * @throws MWException
         * @return Boolean
         */
        public function delete() {
@@ -780,6 +782,7 @@ class Block {
 
        /**
         * Get the IP address at the start of the range in Hex form
+        * @throws MWException
         * @return String IP in Hex form
         */
        public function getRangeStart() {
@@ -797,6 +800,7 @@ class Block {
 
        /**
         * Get the IP address at the start of the range in Hex form
+        * @throws MWException
         * @return String IP in Hex form
         */
        public function getRangeEnd() {
index b7b12e8..ffd7bb8 100644 (file)
@@ -44,6 +44,7 @@ class Category {
 
        /**
         * Set up all member variables using a database query.
+        * @throws MWException
         * @return bool True on success, false on failure.
         */
        protected function initialize() {
index 3bb2bc9..3d66b74 100644 (file)
@@ -613,6 +613,7 @@ class CategoryViewer extends ContextSource {
         *
         * @param Title $title: The title (usually $this->title)
         * @param String $section: Which section
+        * @throws MWException
         * @return Title
         */
        private function addFragmentToTitle( $title, $section ) {
index 02be65f..c97cf13 100644 (file)
@@ -126,6 +126,7 @@ class CdbReader_PHP extends CdbReader {
 
        /**
         * @param $fileName string
+        * @throws MWException
         */
        function __construct( $fileName ) {
                $this->fileName = $fileName;
@@ -198,7 +199,8 @@ class CdbReader_PHP extends CdbReader {
        /**
         * Unpack an unsigned integer and throw an exception if it needs more than 31 bits
         * @param $s
-        * @return
+        * @throws MWException
+        * @return mixed
         */
        protected function unpack31( $s ) {
                $data = unpack( 'V', $s );
index a97cb03..18f425a 100644 (file)
@@ -81,6 +81,7 @@ class ChangeTags {
         * @param $log_id int: log_id of the change to add the tags to
         * @param $params String: params to put in the ct_params field of tabel 'change_tag'
         *
+        * @throws MWException
         * @return bool: false if no changes are made, otherwise true
         *
         * @exception MWException when $rc_id, $rev_id and $log_id are all null
@@ -164,10 +165,9 @@ class ChangeTags {
         * @param $conds String|Array: conditions used in query, see DatabaseBase::select
         * @param $join_conds Array: join conditions, see DatabaseBase::select
         * @param $options Array: options, see Database::select
-        * @param $filter_tag String: tag to select on
-        *
-        * @exception MWException when unable to determine appropriate JOIN condition for tagging
+        * @param bool|string $filter_tag Tag to select on
         *
+        * @throws MWException When unable to determine appropriate JOIN condition for tagging
         */
        static function modifyDisplayQuery( &$tables, &$fields,  &$conds,
                                                                                &$join_conds, &$options, $filter_tag = false ) {
index b68fc76..d304e65 100644 (file)
@@ -159,6 +159,7 @@ class ConfEditor {
         * insert
         *    Insert a new element at the start of the array.
         *
+        * @throws MWException
         * @return string
         */
        public function edit( $ops ) {
@@ -392,6 +393,8 @@ class ConfEditor {
         * Finds the source byte region which you would want to delete, if $pathName
         * was to be deleted. Includes the leading spaces and tabs, the trailing line
         * break, and any comments in between.
+        * @param $pathName
+        * @throws MWException
         * @return array
         */
        function findDeletionRegion( $pathName ) {
@@ -450,6 +453,8 @@ class ConfEditor {
         * or semicolon.
         *
         * The end position is the past-the-end (end + 1) value as per convention.
+        * @param $pathName
+        * @throws MWException
         * @return array
         */
        function findValueRegion( $pathName ) {
index 7984d63..1b86f5d 100644 (file)
@@ -40,14 +40,15 @@ class Cookie {
 
        /**
         * Sets a cookie.  Used before a request to set up any individual
-        * cookies.      Used internally after a request to parse the
+        * cookies. Used internally after a request to parse the
         * Set-Cookie headers.
         *
         * @param $value String: the value of the cookie
         * @param $attr Array: possible key/values:
-        *              expires  A date string
-        *              path     The path this cookie is used on
-        *              domain   Domain this cookie is used on
+        *        expires A date string
+        *        path    The path this cookie is used on
+        *        domain  Domain this cookie is used on
+        * @throws MWException
         */
        public function set( $value, $attr ) {
                $this->value = $value;
index 377b64c..088bb7e 100644 (file)
@@ -76,6 +76,7 @@ abstract class DataUpdate implements DeferrableUpdate {
         *
         * @static
         * @param $updates array a list of DataUpdate instances
+        * @throws Exception|null
         */
        public static function runUpdates( $updates ) {
                if ( empty( $updates ) ) return; # nothing to do
index 28f9aea..2e1e82f 100644 (file)
@@ -738,6 +738,18 @@ $wgMediaHandlers = array(
        'image/x-djvu'   => 'DjVuHandler', // compat
 );
 
+/**
+ * Plugins for page content model handling.
+ * Each entry in the array maps a model id to a class name.
+ *
+ * @since 1.21
+ */
+$wgContentHandlers = array(
+       CONTENT_MODEL_WIKITEXT => 'WikitextContentHandler', // the usual case
+       CONTENT_MODEL_JAVASCRIPT => 'JavaScriptContentHandler', // dumb version, no syntax highlighting
+       CONTENT_MODEL_CSS => 'CssContentHandler', // dumb version, no syntax highlighting
+);
+
 /**
  * Resizing can be done using PHP's internal image libraries or using
  * ImageMagick or another third-party converter, e.g. GraphicMagick.
@@ -1377,10 +1389,14 @@ $wgAllDBsAreLocalhost = false;
  * $wgSharedTables may be customized with a list of tables to share in the shared
  * datbase. However it is advised to limit what tables you do share as many of
  * MediaWiki's tables may have side effects if you try to share them.
- * EXPERIMENTAL
  *
  * $wgSharedPrefix is the table prefix for the shared database. It defaults to
  * $wgDBprefix.
+ *
+ * @deprecated In new code, use the $wiki parameter to wfGetLB() to access 
+ *   remote databases. Using wfGetLB() allows the shared database to reside on 
+ *   separate servers to the wiki's own database, with suitable configuration 
+ *   of $wgLBFactoryConf.
  */
 $wgSharedDB = null;
 
@@ -1758,7 +1774,7 @@ $wgDBAhandler = 'db3';
 /**
  * Deprecated alias for $wgSessionsInObjectCache.
  *
- * @deprecated Use $wgSessionsInObjectCache
+ * @deprecated since 1.20; Use $wgSessionsInObjectCache
  */
 $wgSessionsInMemcached = false;
 
@@ -2073,13 +2089,13 @@ $wgHTCPMulticastRouting = array();
  * setting is ignored. If $wgHTCPMulticastRouting is not set and this setting
  * is, it is used to populate $wgHTCPMulticastRouting.
  *
- * @deprecated in favor of $wgHTCPMulticastRouting
+ * @deprecated since 1.20 in favor of $wgHTCPMulticastRouting
  */
 $wgHTCPMulticastAddress = false;
 
 /**
  * HTCP multicast port.
- * @deprecated in favor of $wgHTCPMulticastRouting
+ * @deprecated since 1.20 in favor of $wgHTCPMulticastRouting
  * @see $wgHTCPMulticastAddress
  */
 $wgHTCPPort = 4827;
@@ -4281,7 +4297,9 @@ $wgSecretKey = false;
  */
 $wgProxyList = array();
 
-/** deprecated */
+/**
+ * @deprecated since 1.14
+ */
 $wgProxyKey = false;
 
 /** @} */ # end of proxy scanner settings
@@ -6223,6 +6241,41 @@ $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).
+ *
+ * @since 1.21
+ */
+$wgNamespaceContentModels = array();
+
+/**
+ * How to react if a plain text version of a non-text Content object is requested using ContentHandler::getContentText():
+ *
+ * * 'ignore': return null
+ * * 'fail': throw an MWException
+ * * 'serialize': serialize to default format
+ *
+ * @since 1.21
+ */
+$wgContentHandlerTextFallback = 'ignore';
+
+/**
+ * Set to false to disable use of the database fields introduced by the ContentHandler facility.
+ * This way, the ContentHandler facility can be used without any additional information in the database.
+ * A page's content model is then derived solely from the page's title. This however means that changing
+ * a page's default model (e.g. using $wgNamespaceContentModels) will break the page and/or make the content
+ * inaccessible. This also means that pages can not be moved to a title that would default to a different
+ * content model.
+ *
+ * Overall, with $wgContentHandlerUseDB = false, no database updates are needed, but content handling
+ * is less robust and less flexible.
+ *
+ * @since 1.21
+ */
+$wgContentHandlerUseDB = true;
+
 /**
  * Whether the user must enter their password to change their e-mail address
  *
index be9f981..1bcb058 100644 (file)
@@ -39,7 +39,7 @@ define( 'MW_SPECIALPAGE_VERSION', 2 );
 define( 'DBO_DEBUG', 1 );
 define( 'DBO_NOBUFFER', 2 );
 define( 'DBO_IGNORE', 4 );
-define( 'DBO_TRX', 8 );
+define( 'DBO_TRX', 8 ); // automatically start transaction on first query
 define( 'DBO_DEFAULT', 16 );
 define( 'DBO_PERSISTENT', 32 );
 define( 'DBO_SYSDBA', 64 ); //for oracle maintenance
@@ -261,7 +261,7 @@ define( 'APCOND_BLOCKED', 8 );
 define( 'APCOND_ISBOT', 9 );
 /**@}*/
 
-/**
+/** @{
  * Protocol constants for wfExpandUrl()
  */
 define( 'PROTO_HTTP', 'http://' );
@@ -270,3 +270,35 @@ define( 'PROTO_RELATIVE', '//' );
 define( 'PROTO_CURRENT', null );
 define( 'PROTO_CANONICAL', 1 );
 define( 'PROTO_INTERNAL', 2 );
+/**@}*/
+
+/**@{
+ * Content model ids, used by Content and ContentHandler.
+ * These IDs will be exposed in the API and XML dumps.
+ *
+ * Extensions that define their own content model IDs should take
+ * care to avoid conflicts. Using the extension name as a prefix is recommended,
+ * for example 'myextension-somecontent'.
+ */
+define( 'CONTENT_MODEL_WIKITEXT', 'wikitext' );
+define( 'CONTENT_MODEL_JAVASCRIPT', 'javascript' );
+define( 'CONTENT_MODEL_CSS', 'css' );
+define( 'CONTENT_MODEL_TEXT', 'text' );
+/**@}*/
+
+/**@{
+ * Content formats, used by Content and ContentHandler.
+ * These should be MIME types, and will be exposed in the API and XML dumps.
+ *
+ * Extensions are free to use the below formats, or define their own.
+ * It is recommended to stick with the conventions for MIME types.
+ */
+define( 'CONTENT_FORMAT_WIKITEXT', 'text/x-wiki' ); // wikitext
+define( 'CONTENT_FORMAT_JAVASCRIPT', 'text/javascript' ); // for js pages
+define( 'CONTENT_FORMAT_CSS', 'text/css' );  // for css pages
+define( 'CONTENT_FORMAT_TEXT', 'text/plain' ); // for future use, e.g. with some plain-html messages.
+define( 'CONTENT_FORMAT_HTML', 'text/html' ); // for future use, e.g. with some plain-html messages.
+define( 'CONTENT_FORMAT_SERIALIZED', 'application/vnd.php.serialized' ); // for future use with the api and for extensions
+define( 'CONTENT_FORMAT_JSON', 'application/json' ); // for future use with the api, and for use by extensions
+define( 'CONTENT_FORMAT_XML', 'application/xml' ); // for future use with the api, and for use by extensions
+/**@}*/
index cc85a7d..35328f8 100644 (file)
@@ -155,6 +155,11 @@ class EditPage {
         */
        const AS_IMAGE_REDIRECT_LOGGED     = 234;
 
+       /**
+        * Status: can't parse content
+        */
+       const AS_PARSE_ERROR                = 240;
+
        /**
         * HTML id and name for the beginning of the edit form.
         */
@@ -214,6 +219,7 @@ class EditPage {
        var $textbox1 = '', $textbox2 = '', $summary = '', $nosummary = false;
        var $edittime = '', $section = '', $sectiontitle = '', $starttime = '';
        var $oldid = 0, $editintro = '', $scrolltop = null, $bot = true;
+       var $contentModel = null, $contentFormat = null;
 
        # Placeholders for text injection by hooks (must be HTML)
        # extensions should take care to _append_ to the present value
@@ -225,7 +231,7 @@ class EditPage {
        public $editFormTextBottom = '';
        public $editFormTextAfterContent = '';
        public $previewTextAfterContent = '';
-       public $mPreloadText = '';
+       public $mPreloadContent = null;
 
        /* $didSave should be set to true whenever an article was succesfully altered. */
        public $didSave = false;
@@ -233,12 +239,24 @@ class EditPage {
 
        public $suppressIntro = false;
 
+       /**
+        * Set to true to allow editing of non-text content types.
+        *
+        * @var bool
+        */
+       public $allowNonTextContent = false;
+
        /**
         * @param $article Article
         */
        public function __construct( Article $article ) {
                $this->mArticle = $article;
                $this->mTitle = $article->getTitle();
+
+               $this->contentModel = $this->mTitle->getContentModel();
+
+               $handler = ContentHandler::getForModelID( $this->contentModel );
+               $this->contentFormat = $handler->getDefaultFormat();
        }
 
        /**
@@ -267,7 +285,7 @@ class EditPage {
 
        /**
         * Get the context title object.
-        * If not set, $wgTitle will be returned. This behavior might changed in
+        * If not set, $wgTitle will be returned. This behavior might change in
         * the future to return $this->mTitle instead.
         *
         * @return Title object
@@ -392,14 +410,10 @@ class EditPage {
                                wfProfileOut( __METHOD__ );
                                return;
                        }
-
-                       if ( !$this->mTitle->getArticleID() ) {
+                       if ( !$this->mTitle->getArticleID() )
                                wfRunHooks( 'EditFormPreloadText', array( &$this->textbox1, &$this->mTitle ) );
-                       }
-                       else {
+                       else
                                wfRunHooks( 'EditFormInitialText', array( $this ) );
-                       }
-
                }
 
                $this->showEditForm();
@@ -442,6 +456,7 @@ class EditPage {
         * @since 1.19
         * @param $permErrors Array of permissions errors, as returned by
         *                    Title::getUserPermissionsErrors().
+        * @throws PermissionsError
         */
        protected function displayPermissionsError( array $permErrors ) {
                global $wgRequest, $wgOut;
@@ -454,10 +469,10 @@ class EditPage {
                        return;
                }
 
-               $content = $this->getContent();
+               $content = $this->getContentObject();
 
                # Use the normal message if there's nothing to display
-               if ( $this->firsttime && $content === '' ) {
+               if ( $this->firsttime && $content->isEmpty() ) {
                        $action = $this->mTitle->exists() ? 'edit' :
                                ( $this->mTitle->isTalkPage() ? 'createtalk' : 'createpage' );
                        throw new PermissionsError( $action, $permErrors );
@@ -471,13 +486,14 @@ class EditPage {
                # If the user made changes, preserve them when showing the markup
                # (This happens when a user is blocked during edit, for instance)
                if ( !$this->firsttime ) {
-                       $content = $this->textbox1;
+                       $text = $this->textbox1;
                        $wgOut->addWikiMsg( 'viewyourtext' );
                } else {
+                       $text = $this->toEditText( $content );
                        $wgOut->addWikiMsg( 'viewsourcetext' );
                }
 
-               $this->showTextbox( $content, 'wpTextbox1', array( 'readonly' ) );
+               $this->showTextbox( $text, 'wpTextbox1', array( 'readonly' ) );
 
                $wgOut->addHTML( Html::rawElement( 'div', array( 'class' => 'templatesUsed' ),
                        Linker::formatTemplates( $this->getTemplates() ) ) );
@@ -590,10 +606,8 @@ class EditPage {
                                // modified by subclasses
                                wfProfileIn( get_class( $this ) . "::importContentFormData" );
                                $textbox1 = $this->importContentFormData( $request );
-                               if ( isset( $textbox1 ) ) {
+                               if ( isset( $textbox1 ) )
                                        $this->textbox1 = $textbox1;
-                               }
-
                                wfProfileOut( get_class( $this ) . "::importContentFormData" );
                        }
 
@@ -717,10 +731,17 @@ class EditPage {
                        }
                }
 
+               $this->oldid = $request->getInt( 'oldid' );
+
                $this->bot = $request->getBool( 'bot', true );
                $this->nosummary = $request->getBool( 'nosummary' );
 
-               $this->oldid = $request->getInt( 'oldid' );
+               $content_handler = ContentHandler::getForTitle( $this->mTitle );
+               $this->contentModel = $request->getText( 'model', $content_handler->getModelID() ); #may be overridden by revision
+               $this->contentFormat = $request->getText( 'format', $content_handler->getDefaultFormat() ); #may be overridden by revision
+
+               #TODO: check if the desired model is allowed in this namespace, and if a transition from the page's current model to the new model is allowed
+               #TODO: check if the desired content model supports the given content format!
 
                $this->live = $request->getCheck( 'live' );
                $this->editintro = $request->getText( 'editintro',
@@ -753,7 +774,10 @@ class EditPage {
        function initialiseForm() {
                global $wgUser;
                $this->edittime = $this->mArticle->getTimestamp();
-               $this->textbox1 = $this->getContent( false );
+
+               $content = $this->getContentObject( false ); #TODO: track content object?!
+               $this->textbox1 = $this->toEditText( $content );
+
                // activate checkboxes if user wants them to be always active
                # Sort out the "watch" checkbox
                if ( $wgUser->getOption( 'watchdefault' ) ) {
@@ -782,33 +806,62 @@ class EditPage {
         * @param $def_text string
         * @return mixed string on success, $def_text for invalid sections
         * @private
+        * @deprecated since 1.21
         */
-       function getContent( $def_text = '' ) {
-               global $wgOut, $wgRequest, $wgParser;
+       function getContent( $def_text = false ) {
+               wfDeprecated( __METHOD__, '1.21' );
+
+               if ( $def_text !== null && $def_text !== false && $def_text !== '' ) {
+                       $def_content = $this->toEditContent( $def_text );
+               } else {
+                       $def_content = false;
+               }
+
+               $content = $this->getContentObject( $def_content );
+
+               // Note: EditPage should only be used with text based content anyway.
+               return $this->toEditText( $content );
+       }
+
+       /**
+        * @param Content|false $def_content The default value to return
+        *
+        * @return mixed Content on success, $def_content for invalid sections
+        *
+        * @since 1.21
+        */
+       protected function getContentObject( $def_content = null ) {
+               global $wgOut, $wgRequest;
 
                wfProfileIn( __METHOD__ );
 
-               $text = false;
+               $content = false;
 
                // For message page not locally set, use the i18n message.
                // For other non-existent articles, use preload text if any.
                if ( !$this->mTitle->exists() || $this->section == 'new' ) {
                        if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI && $this->section != 'new' ) {
                                # If this is a system message, get the default text.
-                               $text = $this->mTitle->getDefaultMessageText();
+                               $msg = $this->mTitle->getDefaultMessageText();
+
+                               $content = $this->toEditContent( $msg );
                        }
-                       if ( $text === false ) {
+                       if ( $content === false ) {
                                # If requested, preload some text.
                                $preload = $wgRequest->getVal( 'preload',
                                        // Custom preload text for new sections
                                        $this->section === 'new' ? 'MediaWiki:addsection-preload' : '' );
-                               $text = $this->getPreloadedText( $preload );
+
+                               $content = $this->getPreloadedContent( $preload );
                        }
                // For existing pages, get text based on "undo" or section parameters.
                } else {
                        if ( $this->section != '' ) {
                                // Get section edit text (returns $def_text for invalid sections)
-                               $text = $wgParser->getSection( $this->getOriginalContent(), $this->section, $def_text );
+                               $orig = $this->getOriginalContent();
+                               $content = $orig ? $orig->getSection( $this->section ) : null;
+
+                               if ( !$content ) $content = $def_content;
                        } else {
                                $undoafter = $wgRequest->getInt( 'undoafter' );
                                $undo = $wgRequest->getInt( 'undo' );
@@ -824,15 +877,16 @@ class EditPage {
 
                                        # Sanity check, make sure it's the right page,
                                        # the revisions exist and they were not deleted.
-                                       # Otherwise, $text will be left as-is.
+                                       # Otherwise, $content will be left as-is.
                                        if ( !is_null( $undorev ) && !is_null( $oldrev ) &&
                                                $undorev->getPage() == $oldrev->getPage() &&
                                                $undorev->getPage() == $this->mTitle->getArticleID() &&
                                                !$undorev->isDeleted( Revision::DELETED_TEXT ) &&
                                                !$oldrev->isDeleted( Revision::DELETED_TEXT ) ) {
 
-                                               $text = $this->mArticle->getUndoText( $undorev, $oldrev );
-                                               if ( $text === false ) {
+                                               $content = $this->mArticle->getUndoContent( $undorev, $oldrev );
+
+                                               if ( $content === false ) {
                                                        # Warn the user that something went wrong
                                                        $undoMsg = 'failure';
                                                } else {
@@ -865,14 +919,14 @@ class EditPage {
                                                wfMessage( 'undo-' . $undoMsg )->plain() . '</div>', true, /* interface */true );
                                }
 
-                               if ( $text === false ) {
-                                       $text = $this->getOriginalContent();
+                               if ( $content === false ) {
+                                       $content = $this->getOriginalContent();
                                }
                        }
                }
 
                wfProfileOut( __METHOD__ );
-               return $text;
+               return $content;
        }
 
        /**
@@ -891,39 +945,69 @@ class EditPage {
         */
        private function getOriginalContent() {
                if ( $this->section == 'new' ) {
-                       return $this->getCurrentText();
+                       return $this->getCurrentContent();
                }
                $revision = $this->mArticle->getRevisionFetched();
                if ( $revision === null ) {
-                       return '';
+                       if ( !$this->contentModel ) $this->contentModel = $this->getTitle()->getContentModel();
+                       $handler = ContentHandler::getForModelID( $this->contentModel );
+
+                       return $handler->makeEmptyContent();
                }
-               return $this->mArticle->getContent();
+               $content = $revision->getContent();
+               return $content;
        }
 
        /**
-        * Get the actual text of the page. This is basically similar to
-        * WikiPage::getRawText() except that when the page doesn't exist an empty
-        * string is returned instead of false.
+        * Get the current content of the page. This is basically similar to
+        * WikiPage::getContent( Revision::RAW ) except that when the page doesn't exist an empty
+        * content object is returned instead of null.
         *
-        * @since 1.19
-        * @return string
+        * @since 1.21
+        * @return Content
         */
-       private function getCurrentText() {
-               $text = $this->mArticle->getRawText();
-               if ( $text === false ) {
-                       return '';
+       protected function getCurrentContent() {
+               $rev = $this->mArticle->getRevision();
+               $content = $rev ? $rev->getContent( Revision::RAW ) : null;
+
+               if ( $content  === false || $content === null ) {
+                       if ( !$this->contentModel ) $this->contentModel = $this->getTitle()->getContentModel();
+                       $handler = ContentHandler::getForModelID( $this->contentModel );
+
+                       return $handler->makeEmptyContent();
                } else {
-                       return $text;
+                       # nasty side-effect, but needed for consistency
+                       $this->contentModel = $rev->getContentModel();
+                       $this->contentFormat = $rev->getContentFormat();
+
+                       return $content;
                }
        }
 
+
        /**
         * Use this method before edit() to preload some text into the edit box
         *
         * @param $text string
+        * @deprecated since 1.21
         */
        public function setPreloadedText( $text ) {
-               $this->mPreloadText = $text;
+               wfDeprecated( __METHOD__, "1.21" );
+
+               $content = $this->toEditContent( $text );
+
+               $this->setPreloadedContent( $content );
+       }
+
+       /**
+        * Use this method before edit() to preload some content into the edit box
+        *
+        * @param $content Content
+        *
+        * @since 1.21
+        */
+       public function setPreloadedContent( Content $content ) {
+               $this->mPreloadedContent = $content;
        }
 
        /**
@@ -931,37 +1015,63 @@ class EditPage {
         * an earlier setPreloadText() or by loading the given page.
         *
         * @param $preload String: representing the title to preload from.
+        *
         * @return String
+        *
+        * @deprecated since 1.21, use getPreloadedContent() instead
         */
        protected function getPreloadedText( $preload ) {
-               global $wgUser, $wgParser;
+               wfDeprecated( __METHOD__, "1.21" );
+
+               $content = $this->getPreloadedContent( $preload );
+               $text = $this->toEditText( $content );
+
+               return $text;
+       }
+
+       /**
+        * Get the contents to be preloaded into the box, either set by
+        * an earlier setPreloadText() or by loading the given page.
+        *
+        * @param $preload String: representing the title to preload from.
+        *
+        * @return Content
+        *
+        * @since 1.21
+        */
+       protected function getPreloadedContent( $preload ) {
+               global $wgUser;
 
-               if ( !empty( $this->mPreloadText ) ) {
-                       return $this->mPreloadText;
+               if ( !empty( $this->mPreloadContent ) ) {
+                       return $this->mPreloadContent;
                }
 
+               $handler = ContentHandler::getForTitle( $this->getTitle() );
+
                if ( $preload === '' ) {
-                       return '';
+                       return $handler->makeEmptyContent();
                }
 
                $title = Title::newFromText( $preload );
                # Check for existence to avoid getting MediaWiki:Noarticletext
-               if ( $title === null || !$title->exists() || !$title->userCan( 'read', $wgUser ) ) {
-                       return '';
+               if ( $title === null || !$title->exists() || !$title->userCan( 'read' ) ) {
+                       return $handler->makeEmptyContent();
                }
 
                $page = WikiPage::factory( $title );
                if ( $page->isRedirect() ) {
                        $title = $page->getRedirectTarget();
                        # Same as before
-                       if ( $title === null || !$title->exists() || !$title->userCan( 'read', $wgUser ) ) {
-                               return '';
+                       if ( $title === null || !$title->exists() || !$title->userCan( 'read' ) ) {
+                               return $handler->makeEmptyContent();
                        }
                        $page = WikiPage::factory( $title );
                }
 
                $parserOptions = ParserOptions::newFromUser( $wgUser );
-               return $wgParser->getPreloadText( $page->getRawText(), $title, $parserOptions );
+               $content = $page->getContent( Revision::RAW );
+
+               return $content->preloadTransform( $title, $parserOptions );
        }
 
        /**
@@ -981,6 +1091,7 @@ class EditPage {
 
        /**
         * Attempt submission
+        * @throws UserBlockedError|ReadOnlyError|ThrottledError|PermissionsError
         * @return bool false if output is done, true if the rest of the form should be displayed
         */
        function attemptSave() {
@@ -1009,6 +1120,10 @@ class EditPage {
                        case self::AS_HOOK_ERROR:
                                return false;
 
+                       case self::AS_PARSE_ERROR:
+                               $wgOut->addWikiText( '<div class="error">' . $status->getWikiText() . '</div>');
+                               return true;
+
                        case self::AS_SUCCESS_NEW_ARTICLE:
                                $query = $resultDetails['redirect'] ? 'redirect=no' : '';
                                $anchor = isset ( $resultDetails['sectionanchor'] ) ? $resultDetails['sectionanchor'] : '';
@@ -1101,9 +1216,19 @@ class EditPage {
                        return $status;
                }
 
+               try {
+                       # Construct Content object
+                       $textbox_content = $this->toEditContent( $this->textbox1 );
+               } catch (MWContentSerializationException $ex) {
+                       $status->fatal( 'content-failed-to-parse', $this->contentModel, $this->contentFormat, $ex->getMessage() );
+                       $status->value = self::AS_PARSE_ERROR;
+                       wfProfileOut( __METHOD__ );
+                       return $status;
+               }
+
                # Check image redirect
                if ( $this->mTitle->getNamespace() == NS_FILE &&
-                       Title::newFromRedirect( $this->textbox1 ) instanceof Title &&
+                       $textbox_content->isRedirect() &&
                        !$wgUser->isAllowed( 'upload' ) ) {
                                $code = $wgUser->isAnon() ? self::AS_IMAGE_REDIRECT_ANON : self::AS_IMAGE_REDIRECT_LOGGED;
                                $status->setResult( false, $code );
@@ -1215,7 +1340,7 @@ class EditPage {
 
                if ( $new ) {
                        // Late check for create permission, just in case *PARANOIA*
-                       if ( !$this->mTitle->userCan( 'create', $wgUser ) ) {
+                       if ( !$this->mTitle->userCan( 'create' ) ) {
                                $status->fatal( 'nocreatetext' );
                                $status->value = self::AS_NO_CREATE_PERMISSION;
                                wfDebug( __METHOD__ . ": no create permission\n" );
@@ -1245,13 +1370,13 @@ class EditPage {
                                return $status;
                        }
 
-                       $text = $this->textbox1;
+                       $content = $textbox_content;
+
                        $result['sectionanchor'] = '';
                        if ( $this->section == 'new' ) {
                                if ( $this->sectiontitle !== '' ) {
                                        // Insert the section title above the content.
-                                       $text = wfMessage( 'newsectionheaderdefaultlevel' )->rawParams( $this->sectiontitle )
-                                               ->inContentLanguage()->text() . "\n\n" . $text;
+                                       $content = $content->addSectionHeader( $this->sectiontitle );
 
                                        // Jump to the new section
                                        $result['sectionanchor'] = $wgParser->guessLegacySectionNameFromWikiText( $this->sectiontitle );
@@ -1261,30 +1386,32 @@ class EditPage {
                                        // passed.
                                        if ( $this->summary === '' ) {
                                                $cleanSectionTitle = $wgParser->stripSectionName( $this->sectiontitle );
-                                               $this->summary = wfMessage( 'newsectionsummary' )
-                                                       ->rawParams( $cleanSectionTitle )->inContentLanguage()->text();
+                                               $this->summary = wfMessage( 'newsectionsummary', $cleanSectionTitle )
+                                                       ->inContentLanguage()->text() ;
                                        }
                                } elseif ( $this->summary !== '' ) {
                                        // Insert the section title above the content.
-                                       $text = wfMessage( 'newsectionheaderdefaultlevel' )->rawParams( $this->summary )
-                                               ->inContentLanguage()->text() . "\n\n" . $text;
+                                       $content = $content->addSectionHeader( $this->summary );
 
                                        // Jump to the new section
                                        $result['sectionanchor'] = $wgParser->guessLegacySectionNameFromWikiText( $this->summary );
 
                                        // Create a link to the new section from the edit summary.
                                        $cleanSummary = $wgParser->stripSectionName( $this->summary );
-                                       $this->summary = wfMessage( 'newsectionsummary' )
-                                               ->rawParams( $cleanSummary )->inContentLanguage()->text();
+                                       $this->summary = wfMessage( 'newsectionsummary', $cleanSummary )
+                                               ->inContentLanguage()->text();
                                }
                        }
 
                        $status->value = self::AS_SUCCESS_NEW_ARTICLE;
 
-               } else {
+               } else { # not $new
 
                        # Article exists. Check for edit conflict.
+
+                       $this->mArticle->clear(); # Force reload of dates, etc.
                        $timestamp = $this->mArticle->getTimestamp();
+
                        wfDebug( "timestamp: {$timestamp}, edittime: {$this->edittime}\n" );
 
                        if ( $timestamp != $this->edittime ) {
@@ -1301,7 +1428,8 @@ class EditPage {
                                                $this->isConflict = false;
                                                wfDebug( __METHOD__ . ": conflict suppressed; new section\n" );
                                        }
-                               } elseif ( $this->section == '' && Revision::userWasLastToEdit( DB_MASTER,  $this->mTitle->getArticleID(), $wgUser->getId(), $this->edittime ) ) {
+                               } elseif ( $this->section == '' && Revision::userWasLastToEdit( DB_MASTER,  $this->mTitle->getArticleID(),
+                                                       $wgUser->getId(), $this->edittime ) ) {
                                        # Suppress edit conflict with self, except for section edits where merging is required.
                                        wfDebug( __METHOD__ . ": Suppressing edit conflict, same user.\n" );
                                        $this->isConflict = false;
@@ -1316,26 +1444,32 @@ class EditPage {
                                $sectionTitle = $this->summary;
                        }
 
+                       $content = null;
+
                        if ( $this->isConflict ) {
-                               wfDebug( __METHOD__ . ": conflict! getting section '$this->section' for time '$this->edittime' (article time '{$timestamp}')\n" );
-                               $text = $this->mArticle->replaceSection( $this->section, $this->textbox1, $sectionTitle, $this->edittime );
+                               wfDebug( __METHOD__ . ": conflict! getting section '{$this->section}' for time '{$this->edittime}'"
+                                               . " (article time '{$timestamp}')\n" );
+
+                               $content = $this->mArticle->replaceSectionContent( $this->section, $textbox_content, $sectionTitle, $this->edittime );
                        } else {
-                               wfDebug( __METHOD__ . ": getting section '$this->section'\n" );
-                               $text = $this->mArticle->replaceSection( $this->section, $this->textbox1, $sectionTitle );
+                               wfDebug( __METHOD__ . ": getting section '{$this->section}'\n" );
+                               $content = $this->mArticle->replaceSectionContent( $this->section, $textbox_content, $sectionTitle );
                        }
-                       if ( is_null( $text ) ) {
+
+                       if ( is_null( $content ) ) {
                                wfDebug( __METHOD__ . ": activating conflict; section replace failed.\n" );
                                $this->isConflict = true;
-                               $text = $this->textbox1; // do not try to merge here!
+                               $content = $textbox_content; // do not try to merge here!
                        } elseif ( $this->isConflict ) {
                                # Attempt merge
-                               if ( $this->mergeChangesInto( $text ) ) {
+                               if ( $this->mergeChangesIntoContent( $textbox_content ) ) {
                                        // Successful merge! Maybe we should tell the user the good news?
                                        $this->isConflict = false;
+                                       $content = $textbox_content;
                                        wfDebug( __METHOD__ . ": Suppressing edit conflict, successful merge.\n" );
                                } else {
                                        $this->section = '';
-                                       $this->textbox1 = $text;
+                                       #$this->textbox1 = $text; #redundant, nothing to do here?
                                        wfDebug( __METHOD__ . ": Keeping edit conflict, failed merge.\n" );
                                }
                        }
@@ -1347,7 +1481,10 @@ class EditPage {
                        }
 
                        // Run post-section-merge edit filter
-                       if ( !wfRunHooks( 'EditFilterMerged', array( $this, $text, &$this->hookError, $this->summary ) ) ) {
+                       $hook_args = array( $this, $content, &$this->hookError, $this->summary );
+
+                       if ( !ContentHandler::runLegacyHooks( 'EditFilterMerged', $hook_args )
+                               || !wfRunHooks( 'EditFilterMergedContent', $hook_args ) ) {
                                # Error messages etc. could be handled within the hook...
                                $status->fatal( 'hookaborted' );
                                $status->value = self::AS_HOOK_ERROR;
@@ -1363,8 +1500,8 @@ class EditPage {
 
                        # Handle the user preference to force summaries here, but not for null edits
                        if ( $this->section != 'new' && !$this->allowBlankSummary
-                               && $this->getOriginalContent() != $text
-                               && !Title::newFromRedirect( $text ) ) # check if it's not a redirect
+                               && !$content->equals( $this->getOriginalContent() )
+                               && !$content->isRedirect() ) # check if it's not a redirect
                        {
                                if ( md5( $this->summary ) == $this->autoSumm ) {
                                        $this->missingSummary = true;
@@ -1405,16 +1542,16 @@ class EditPage {
                                        // passed.
                                        if ( $this->summary === '' ) {
                                                $cleanSectionTitle = $wgParser->stripSectionName( $this->sectiontitle );
-                                               $this->summary = wfMessage( 'newsectionsummary' )
-                                                       ->rawParams( $cleanSectionTitle )->inContentLanguage()->text();
+                                               $this->summary = wfMessage( 'newsectionsummary', $cleanSectionTitle )
+                                                       ->inContentLanguage()->text();
                                        }
                                } elseif ( $this->summary !== '' ) {
                                        $sectionanchor = $wgParser->guessLegacySectionNameFromWikiText( $this->summary );
                                        # This is a new section, so create a link to the new section
                                        # in the revision summary.
                                        $cleanSummary = $wgParser->stripSectionName( $this->summary );
-                                       $this->summary = wfMessage( 'newsectionsummary' )
-                                               ->rawParams( $cleanSummary )->inContentLanguage()->text();
+                                       $this->summary = wfMessage( 'newsectionsummary', $cleanSummary )
+                                               ->inContentLanguage()->text();
                                }
                        } elseif ( $this->section != '' ) {
                                # Try to get a section anchor from the section source, redirect to edited section if header found
@@ -1434,14 +1571,14 @@ class EditPage {
                        // merged the section into full text. Clear the section field
                        // so that later submission of conflict forms won't try to
                        // replace that into a duplicated mess.
-                       $this->textbox1 = $text;
+                       $this->textbox1 = $this->toEditText( $content );
                        $this->section = '';
 
                        $status->value = self::AS_SUCCESS_UPDATE;
                }
 
                // Check for length errors again now that the section is merged in
-               $this->kblength = (int)( strlen( $text ) / 1024 );
+                       $this->kblength = (int)( strlen( $this->toEditText( $content ) ) / 1024 );
                if ( $this->kblength > $wgMaxArticleSize ) {
                        $this->tooBig = true;
                        $status->setResult( false, self::AS_MAX_ARTICLE_SIZE_EXCEEDED );
@@ -1454,11 +1591,12 @@ class EditPage {
                        ( ( $this->minoredit && !$this->isNew ) ? EDIT_MINOR : 0 ) |
                        ( $bot ? EDIT_FORCE_BOT : 0 );
 
-               $doEditStatus = $this->mArticle->doEdit( $text, $this->summary, $flags );
+                       $doEditStatus = $this->mArticle->doEditContent( $content, $this->summary, $flags,
+                                                                                                                       false, null, $this->contentFormat );
 
                if ( $doEditStatus->isOK() ) {
-                       $result['redirect'] = Title::newFromRedirect( $text ) !== null;
-                       $this->updateWatchlist();
+                               $result['redirect'] = $content->isRedirect();
+                       $this->commitWatch();
                        wfProfileOut( __METHOD__ );
                        return $status;
                } else {
@@ -1479,27 +1617,19 @@ class EditPage {
        }
 
        /**
-        * Register the change of watch status
+        * Commit the change of watch status
         */
-       protected function updateWatchlist() {
+       protected function commitWatch() {
                global $wgUser;
-
                if ( $wgUser->isLoggedIn() && $this->watchthis != $wgUser->isWatched( $this->mTitle ) ) {
-                       $fname = __METHOD__;
-                       $title = $this->mTitle;
-                       $watch = $this->watchthis;
-
-                       // Do this in its own transaction to reduce contention...
                        $dbw = wfGetDB( DB_MASTER );
-                       $dbw->onTransactionIdle( function() use ( $dbw, $title, $watch, $wgUser, $fname ) {
-                               $dbw->begin( $fname );
-                               if ( $watch ) {
-                                       WatchAction::doWatch( $title, $wgUser );
-                               } else {
-                                       WatchAction::doUnwatch( $title, $wgUser );
-                               }
-                               $dbw->commit( $fname );
-                       } );
+                       $dbw->begin( __METHOD__ );
+                       if ( $this->watchthis ) {
+                               WatchAction::doWatch( $this->mTitle, $wgUser );
+                       } else {
+                               WatchAction::doUnwatch( $this->mTitle, $wgUser );
+                       }
+                       $dbw->commit( __METHOD__ );
                }
        }
 
@@ -1510,8 +1640,33 @@ class EditPage {
         * @param $editText string
         *
         * @return bool
+        * @deprecated since 1.21, use mergeChangesIntoContent() instead
+        */
+       function mergeChangesInto( &$editText ){
+               wfDebug( __METHOD__, "1.21" );
+
+               $editContent = $this->toEditContent( $editText );
+
+               $ok = $this->mergeChangesIntoContent( $editContent );
+
+               if ( $ok ) {
+                       $editText = $this->toEditText( $editContent );
+                       return true;
+               } else {
+                       return false;
+               }
+       }
+
+       /**
+        * @private
+        * @todo document
+        *
+        * @parma $editText string
+        *
+        * @return bool
+        * @since since 1.WD
         */
-       function mergeChangesInto( &$editText ) {
+       private function mergeChangesIntoContent( &$editContent ){
                wfProfileIn( __METHOD__ );
 
                $db = wfGetDB( DB_MASTER );
@@ -1522,7 +1677,7 @@ class EditPage {
                        wfProfileOut( __METHOD__ );
                        return false;
                }
-               $baseText = $baseRevision->getText();
+               $baseContent = $baseRevision->getContent();
 
                // The current state, we want to merge updates into it
                $currentRevision = Revision::loadFromTitle( $db, $this->mTitle );
@@ -1530,11 +1685,14 @@ class EditPage {
                        wfProfileOut( __METHOD__ );
                        return false;
                }
-               $currentText = $currentRevision->getText();
+               $currentContent = $currentRevision->getContent();
+
+               $handler = ContentHandler::getForModelID( $baseContent->getModel() );
+
+               $result = $handler->merge3( $baseContent, $editContent, $currentContent );
 
-               $result = '';
-               if ( wfMerge( $baseText, $editText, $currentText, $result ) ) {
-                       $editText = $result;
+               if ( $result ) {
+                       $editContent = $result;
                        wfProfileOut( __METHOD__ );
                        return true;
                } else {
@@ -1605,7 +1763,7 @@ class EditPage {
                $wgOut->addModules( 'mediawiki.action.edit' );
 
                if ( $wgUser->getOption( 'uselivepreview', false ) ) {
-                       $wgOut->addModules( 'mediawiki.action.edit.preview' );
+                       $wgOut->addModules( 'mediawiki.legacy.preview' );
                }
                // Bug #19334: textarea jumps when editing articles in IE8
                $wgOut->addStyle( 'common/IE80Fixes.css', 'screen', 'IE 8' );
@@ -1733,6 +1891,54 @@ class EditPage {
                }
        }
 
+       /**
+        * Gets an editable textual representation of the given Content object.
+        * The textual representation can be turned by into a Content object by the
+        * toEditContent() method.
+        *
+        * If the given Content object is not of a type that can be edited using the text base EditPage,
+        * an exception will be raised. Set $this->allowNonTextContent to true to allow editing of non-textual
+        * content.
+        *
+        * @param Content $content
+        * @return String the editable text form of the content.
+        *
+        * @throws MWException if $content is not an instance of TextContent and $this->allowNonTextContent is not true.
+        */
+       protected function toEditText( Content $content ) {
+               if ( !$this->allowNonTextContent && !( $content instanceof TextContent ) ) {
+                       throw new MWException( "This content model can not be edited as text: "
+                                                               . ContentHandler::getLocalizedName( $content->getModel() ) );
+               }
+
+               return $content->serialize( $this->contentFormat );
+       }
+
+       /**
+        * Turns the given text into a Content object by unserializing it.
+        *
+        * If the resulting Content object is not of a type that can be edited using the text base EditPage,
+        * an exception will be raised. Set $this->allowNonTextContent to true to allow editing of non-textual
+        * content.
+        *
+        * @param String $text Text to unserialize
+        * @return Content the content object created from $text
+        *
+        * @throws MWException if unserializing the text results in a Content object that is not an instance of TextContent
+        *          and $this->allowNonTextContent is not true.
+        */
+       protected function toEditContent( $text ) {
+               $content = ContentHandler::makeContent( $text, $this->getTitle(),
+                       $this->contentModel, $this->contentFormat );
+
+               if ( !$this->allowNonTextContent && !( $content instanceof TextContent ) ) {
+                       throw new MWException( "This content model can not be edited as text: "
+                               . ContentHandler::getLocalizedName( $content->getModel() ) );
+               }
+
+               return $content;
+       }
+
        /**
         * Send the edit form and related headers to $wgOut
         * @param $formCallback Callback that takes an OutputPage parameter; will be called
@@ -1781,6 +1987,8 @@ class EditPage {
                        }
                }
 
+               //@todo: add EditForm plugin interface and use it here!
+               //       search for textarea1 and textares2, and allow EditForm to override all uses.
                $wgOut->addHTML( Html::openElement( 'form', array( 'id' => self::EDITFORM_ID, 'name' => self::EDITFORM_ID,
                        'method' => 'post', 'action' => $this->getActionURL( $this->getContextTitle() ),
                        'enctype' => 'multipart/form-data' ) ) );
@@ -1845,6 +2053,9 @@ class EditPage {
 
                $wgOut->addHTML( Html::hidden( 'oldid', $this->oldid ) );
 
+               $wgOut->addHTML( Html::hidden( 'format', $this->contentFormat ) );
+               $wgOut->addHTML( Html::hidden( 'model', $this->contentModel ) );
+
                if ( $this->section == 'new' ) {
                        $this->showSummaryInput( true, $this->summary );
                        $wgOut->addHTML( $this->getSummaryPreview( true, $this->summary ) );
@@ -1862,7 +2073,9 @@ class EditPage {
                        // resolved between page source edits and custom ui edits using the
                        // custom edit ui.
                        $this->textbox2 = $this->textbox1;
-                       $this->textbox1 = $this->getCurrentText();
+
+                       $content = $this->getCurrentContent();
+                       $this->textbox1 = $this->toEditText( $content );
 
                        $this->showTextbox1();
                } else {
@@ -1888,7 +2101,13 @@ class EditPage {
                        Linker::formatHiddenCategories( $this->mArticle->getHiddenCategories() ) ) );
 
                if ( $this->isConflict ) {
-                       $this->showConflict();
+                       try {
+                               $this->showConflict();
+                       } catch ( MWContentSerializationException $ex ) {
+                               // this can't really happen, but be nice if it does.
+                               $msg = wfMessage( 'content-failed-to-parse', $this->contentModel, $this->contentFormat, $ex->getMessage() );
+                               $wgOut->addWikiText( '<div class="error">' . $msg->text() . '</div>');
+                       }
                }
 
                $wgOut->addHTML( $this->editFormTextBottom . "\n</form>\n" );
@@ -1962,7 +2181,7 @@ class EditPage {
 
                        if ( $this->section != '' && $this->section != 'new' ) {
                                if ( !$this->summary && !$this->preview && !$this->diff ) {
-                                       $sectionTitle = self::extractSectionTitle( $this->textbox1 );
+                                       $sectionTitle = self::extractSectionTitle( $this->textbox1 ); //FIXME: use Content object
                                        if ( $sectionTitle !== false ) {
                                                $this->summary = "/* $sectionTitle */ ";
                                        }
@@ -1994,7 +2213,7 @@ class EditPage {
                                if ( $revision ) {
                                        // Let sysop know that this will make private content public if saved
 
-                                       if ( !$revision->userCan( Revision::DELETED_TEXT, $wgUser ) ) {
+                                       if ( !$revision->userCan( Revision::DELETED_TEXT ) ) {
                                                $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", 'rev-deleted-text-permission' );
                                        } elseif ( $revision->isDeleted( Revision::DELETED_TEXT ) ) {
                                                $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", 'rev-deleted-text-view' );
@@ -2028,13 +2247,10 @@ class EditPage {
                                        $wgOut->wrapWikiMsg( "<div class='error' id='mw-userinvalidcssjstitle'>\n$1\n</div>", array( 'userinvalidcssjstitle', $this->mTitle->getSkinFromCssJsSubpage() ) );
                                }
                                if ( $this->formtype !== 'preview' ) {
-                                       if ( $this->isCssSubpage ) {
+                                       if ( $this->isCssSubpage )
                                                $wgOut->wrapWikiMsg( "<div id='mw-usercssyoucanpreview'>\n$1\n</div>", array( 'usercssyoucanpreview' ) );
-                                       }
-
-                                       if ( $this->isJsSubpage ) {
+                                       if ( $this->isJsSubpage )
                                                $wgOut->wrapWikiMsg( "<div id='mw-userjsyoucanpreview'>\n$1\n</div>", array( 'userjsyoucanpreview' ) );
-                                       }
                                }
                        }
                }
@@ -2165,16 +2381,14 @@ class EditPage {
         * @return String
         */
        protected function getSummaryPreview( $isSubjectPreview, $summary = "" ) {
-               if ( !$summary || ( !$this->preview && !$this->diff ) ) {
+               if ( !$summary || ( !$this->preview && !$this->diff ) )
                        return "";
-               }
 
                global $wgParser;
 
-               if ( $isSubjectPreview ) {
+               if ( $isSubjectPreview )
                        $summary = wfMessage( 'newsectionsummary', $wgParser->stripSectionName( $summary ) )
                                ->inContentLanguage()->text();
-               }
 
                $message = $isSubjectPreview ? 'subject-preview' : 'summary-preview';
 
@@ -2193,9 +2407,8 @@ class EditPage {
 
 HTML
                );
-               if ( !$this->checkUnicodeCompliantBrowser() ) {
+               if ( !$this->checkUnicodeCompliantBrowser() )
                        $wgOut->addHTML( Html::hidden( 'safemode', '1' ) );
-               }
        }
 
        protected function showFormAfterText() {
@@ -2275,10 +2488,10 @@ HTML
                $this->showTextbox( $this->textbox2, 'wpTextbox2', array( 'tabindex' => 6, 'readonly' ) );
        }
 
-       protected function showTextbox( $content, $name, $customAttribs = array() ) {
+       protected function showTextbox( $text, $name, $customAttribs = array() ) {
                global $wgOut, $wgUser;
 
-               $wikitext = $this->safeUnicodeOutput( $content );
+               $wikitext = $this->safeUnicodeOutput( $text );
                if ( strval( $wikitext ) !== '' ) {
                        // Ensure there's a newline at the end, otherwise adding lines
                        // is awkward.
@@ -2305,15 +2518,13 @@ HTML
        protected function displayPreviewArea( $previewOutput, $isOnTop = false ) {
                global $wgOut;
                $classes = array();
-               if ( $isOnTop ) {
+               if ( $isOnTop )
                        $classes[] = 'ontop';
-               }
 
                $attribs = array( 'id' => 'wikiPreview', 'class' => implode( ' ', $classes ) );
 
-               if ( $this->formtype != 'preview' ) {
+               if ( $this->formtype != 'preview' )
                        $attribs['style'] = 'display: none;';
-               }
 
                $wgOut->addHTML( Xml::openElement( 'div', $attribs ) );
 
@@ -2324,7 +2535,12 @@ HTML
                $wgOut->addHTML( '</div>' );
 
                if ( $this->formtype == 'diff' ) {
-                       $this->showDiff();
+                       try {
+                               $this->showDiff();
+                       } catch ( MWContentSerializationException $ex ) {
+                               $msg = wfMessage( 'content-failed-to-parse', $this->contentModel, $this->contentFormat, $ex->getMessage() );
+                               $wgOut->addWikiText( '<div class="error">' . $msg->text() . '</div>');
+                       }
                }
        }
 
@@ -2364,24 +2580,33 @@ HTML
                        $oldtext = $this->mTitle->getDefaultMessageText();
                        if( $oldtext !== false ) {
                                $oldtitlemsg = 'defaultmessagetext';
+                               $oldContent = $this->toEditContent( $oldtext );
+                       } else {
+                               $oldContent = null;
                        }
                } else {
-                       $oldtext = $this->mArticle->getRawText();
+                       $oldContent = $this->getOriginalContent();
                }
-               $newtext = $this->mArticle->replaceSection(
-                       $this->section, $this->textbox1, $this->summary, $this->edittime );
 
-               wfRunHooks( 'EditPageGetDiffText', array( $this, &$newtext ) );
+               $textboxContent = $this->toEditContent( $this->textbox1 );
+
+               $newContent = $this->mArticle->replaceSectionContent(
+                                                       $this->section, $textboxContent,
+                                                       $this->summary, $this->edittime );
+
+               ContentHandler::runLegacyHooks( 'EditPageGetDiffText', array( $this, &$newContent ) );
+               wfRunHooks( 'EditPageGetDiffContent', array( $this, &$newContent ) );
 
                $popts = ParserOptions::newFromUserAndLang( $wgUser, $wgContLang );
-               $newtext = $wgParser->preSaveTransform( $newtext, $this->mTitle, $wgUser, $popts );
+               $newContent = $newContent->preSaveTransform( $this->mTitle, $wgUser, $popts );
 
-               if ( $oldtext !== false  || $newtext != '' ) {
+               if ( ( $oldContent && !$oldContent->isEmpty() ) || ( $newContent && !$newContent->isEmpty() ) ) {
                        $oldtitle = wfMessage( $oldtitlemsg )->parse();
                        $newtitle = wfMessage( 'yourtext' )->parse();
 
-                       $de = new DifferenceEngine( $this->mArticle->getContext() );
-                       $de->setText( $oldtext, $newtext );
+                       $de = $oldContent->getContentHandler()->createDifferenceEngine( $this->mArticle->getContext() );
+                       $de->setContent( $oldContent, $newContent );
+
                        $difftext = $de->getDiff( $oldtitle, $newtitle );
                        $de->showDiffStyle();
                } else {
@@ -2498,8 +2723,12 @@ HTML
                if ( wfRunHooks( 'EditPageBeforeConflictDiff', array( &$this, &$wgOut ) ) ) {
                        $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourdiff" );
 
-                       $de = new DifferenceEngine( $this->mArticle->getContext() );
-                       $de->setText( $this->textbox2, $this->textbox1 );
+                       $content1 = $this->toEditContent( $this->textbox1 );
+                       $content2 = $this->toEditContent( $this->textbox2 );
+
+                       $handler = ContentHandler::getForModelID( $this->contentModel );
+                       $de = $handler->createDifferenceEngine( $this->mArticle->getContext() );
+                       $de->setContent( $content2, $content1 );
                        $de->showDiff(
                                wfMessage( 'yourtext' )->parse(),
                                wfMessage( 'storedversion' )->text()
@@ -2590,19 +2819,17 @@ HTML
                );
                // Quick paranoid permission checks...
                if ( is_object( $data ) ) {
-                       if ( $data->log_deleted & LogPage::DELETED_USER ) {
+                       if ( $data->log_deleted & LogPage::DELETED_USER )
                                $data->user_name = wfMessage( 'rev-deleted-user' )->escaped();
-                       }
-
-                       if ( $data->log_deleted & LogPage::DELETED_COMMENT ) {
+                       if ( $data->log_deleted & LogPage::DELETED_COMMENT )
                                $data->log_comment = wfMessage( 'rev-deleted-comment' )->escaped();
-                       }
                }
                return $data;
        }
 
        /**
         * Get the rendered text for previewing.
+        * @throws MWException
         * @return string
         */
        function getPreviewText() {
@@ -2625,82 +2852,94 @@ HTML
                        return $parsedNote;
                }
 
-               if ( $this->mTriedSave && !$this->mTokenOk ) {
-                       if ( $this->mTokenOkExceptSuffix ) {
-                               $note = wfMessage( 'token_suffix_mismatch' )->plain();
-                       } else {
-                               $note = wfMessage( 'session_fail_preview' )->plain();
-                       }
-               } elseif ( $this->incompleteForm ) {
-                       $note = wfMessage( 'edit_form_incomplete' )->plain();
-               } else {
-                       $note = wfMessage( 'previewnote' )->plain() .
-                               ' [[#' . self::EDITFORM_ID . '|' . $wgLang->getArrow() . ' ' . wfMessage( 'continue-editing' )->text() . ']]';
-               }
+               $note = '';
 
-               $parserOptions = $this->mArticle->makeParserOptions( $this->mArticle->getContext() );
+               try {
+                       $content = $this->toEditContent( $this->textbox1 );
 
-               $parserOptions->setEditSection( false );
-               $parserOptions->setIsPreview( true );
-               $parserOptions->setIsSectionPreview( !is_null( $this->section ) && $this->section !== '' );
+                       if ( $this->mTriedSave && !$this->mTokenOk ) {
+                               if ( $this->mTokenOkExceptSuffix ) {
+                                       $note = wfMessage( 'token_suffix_mismatch' )->plain() ;
 
-               # don't parse non-wikitext pages, show message about preview
-               if ( $this->mTitle->isCssJsSubpage() || !$this->mTitle->isWikitextPage() ) {
-                       if ( $this->mTitle->isCssJsSubpage() ) {
-                               $level = 'user';
-                       } elseif ( $this->mTitle->isCssOrJsPage() ) {
-                               $level = 'site';
-                       } else {
-                               $level = false;
-                       }
-
-                       # Used messages to make sure grep find them:
-                       # Messages: usercsspreview, userjspreview, sitecsspreview, sitejspreview
-                       $class = 'mw-code';
-                       if ( $level ) {
-                               if ( preg_match( "/\\.css$/", $this->mTitle->getText() ) ) {
-                                       $previewtext = "<div id='mw-{$level}csspreview'>\n" . wfMessage( "{$level}csspreview" )->text() . "\n</div>";
-                                       $class .= " mw-css";
-                               } elseif ( preg_match( "/\\.js$/", $this->mTitle->getText() ) ) {
-                                       $previewtext = "<div id='mw-{$level}jspreview'>\n" . wfMessage( "{$level}jspreview" )->text() . "\n</div>";
-                                       $class .= " mw-js";
                                } else {
-                                       throw new MWException( 'A CSS/JS (sub)page but which is not css nor js!' );
+                                       $note = wfMessage( 'session_fail_preview' )->plain() ;
                                }
-                               $parserOutput = $wgParser->parse( $previewtext, $this->mTitle, $parserOptions );
-                               $previewHTML = $parserOutput->getText();
+                       } elseif ( $this->incompleteForm ) {
+                               $note = wfMessage( 'edit_form_incomplete' )->plain() ;
                        } else {
-                               $previewHTML = '';
+                               $note = wfMessage( 'previewnote' )->plain() .
+                                       ' [[#' . self::EDITFORM_ID . '|' . $wgLang->getArrow() . ' ' . wfMessage( 'continue-editing' )->text() . ']]';
                        }
 
-                       $previewHTML .= "<pre class=\"$class\" dir=\"ltr\">\n" . htmlspecialchars( $this->textbox1 ) . "\n</pre>\n";
-               } else {
-                       $toparse = $this->textbox1;
+                       $parserOptions = $this->mArticle->makeParserOptions( $this->mArticle->getContext() );
+                       $parserOptions->setEditSection( false );
+                       $parserOptions->setIsPreview( true );
+                       $parserOptions->setIsSectionPreview( !is_null($this->section) && $this->section !== '' );
 
-                       # If we're adding a comment, we need to show the
-                       # summary as the headline
-                       if ( $this->section == "new" && $this->summary != "" ) {
-                               $toparse = wfMessage( 'newsectionheaderdefaultlevel', $this->summary )->inContentLanguage()->text() . "\n\n" . $toparse;
-                       }
+                       # don't parse non-wikitext pages, show message about preview
+                       if ( $this->mTitle->isCssJsSubpage() || $this->mTitle->isCssOrJsPage() ) {
+                               if( $this->mTitle->isCssJsSubpage() ) {
+                                       $level = 'user';
+                               } elseif( $this->mTitle->isCssOrJsPage() ) {
+                                       $level = 'site';
+                               } else {
+                                       $level = false;
+                               }
 
-                       wfRunHooks( 'EditPageGetPreviewText', array( $this, &$toparse ) );
+                               if ( $content->getModel() == CONTENT_MODEL_CSS ) {
+                                       $format = 'css';
+                               } elseif ( $content->getModel() == CONTENT_MODEL_JAVASCRIPT ) {
+                                       $format = 'js';
+                               } else {
+                                       $format = false;
+                               }
 
-                       $toparse = $wgParser->preSaveTransform( $toparse, $this->mTitle, $wgUser, $parserOptions );
-                       $parserOutput = $wgParser->parse( $toparse, $this->mTitle, $parserOptions );
+                               # Used messages to make sure grep find them:
+                               # Messages: usercsspreview, userjspreview, sitecsspreview, sitejspreview
+                               if( $level && $format ) {
+                                       $note = "<div id='mw-{$level}{$format}preview'>" . wfMessage( "{$level}{$format}preview" )->text()  . "</div>";
+                               } else {
+                                       $note = wfMessage( 'previewnote' )->text() ;
+                               }
+                       } else {
+                               $note = wfMessage( 'previewnote' )->text() ;
+                       }
 
-                       $rt = Title::newFromRedirectArray( $this->textbox1 );
+                       $rt = $content->getRedirectChain();
                        if ( $rt ) {
                                $previewHTML = $this->mArticle->viewRedirect( $rt, false );
                        } else {
-                               $previewHTML = $parserOutput->getText();
-                       }
 
-                       $this->mParserOutput = $parserOutput;
-                       $wgOut->addParserOutputNoText( $parserOutput );
+                               # If we're adding a comment, we need to show the
+                               # summary as the headline
+                               if ( $this->section === "new" && $this->summary !== "" ) {
+                                       $content = $content->addSectionHeader( $this->summary );
+                               }
+
+                               $hook_args = array( $this, &$content );
+                               ContentHandler::runLegacyHooks( 'EditPageGetPreviewText', $hook_args );
+                               wfRunHooks( 'EditPageGetPreviewContent', $hook_args );
+
+                               $parserOptions->enableLimitReport();
 
-                       if ( count( $parserOutput->getWarnings() ) ) {
-                               $note .= "\n\n" . implode( "\n\n", $parserOutput->getWarnings() );
+                               # For CSS/JS pages, we should have called the ShowRawCssJs hook here.
+                               # But it's now deprecated, so never mind
+
+                               $content = $content->preSaveTransform( $this->mTitle, $wgUser, $parserOptions );
+                               $parserOutput = $content->getParserOutput( $this->getArticle()->getTitle(), null, $parserOptions );
+
+                               $previewHTML = $parserOutput->getText();
+                               $this->mParserOutput = $parserOutput;
+                               $wgOut->addParserOutputNoText( $parserOutput );
+
+                               if ( count( $parserOutput->getWarnings() ) ) {
+                                       $note .= "\n\n" . implode( "\n\n", $parserOutput->getWarnings() );
+                               }
                        }
+               } catch ( MWContentSerializationException $ex ) {
+                       $m = wfMessage('content-failed-to-parse', $this->contentModel, $this->contentFormat, $ex->getMessage() );
+                       $note .= "\n\n" . $m->parse();
+                       $previewHTML = '';
                }
 
                if ( $this->isConflict ) {
index c2409de..e2b01b5 100644 (file)
@@ -63,7 +63,7 @@ class WikiExporter {
         * @return string
         */
        public static function schemaVersion() {
-               return "0.7";
+               return "0.8";
        }
 
        /**
@@ -498,7 +498,7 @@ class XmlDumpWriter {
                        'xmlns'              => "http://www.mediawiki.org/xml/export-$ver/",
                        'xmlns:xsi'          => "http://www.w3.org/2001/XMLSchema-instance",
                        'xsi:schemaLocation' => "http://www.mediawiki.org/xml/export-$ver/ " .
-                                               "http://www.mediawiki.org/xml/export-$ver.xsd",
+                                               "http://www.mediawiki.org/xml/export-$ver.xsd", #TODO: how do we get a new version up there?
                        'version'            => $ver,
                        'xml:lang'           => $wgLanguageCode ),
                        null ) .
@@ -657,12 +657,6 @@ class XmlDumpWriter {
                        $out .= "      " . Xml::elementClean( 'comment', array(), strval( $row->rev_comment ) ) . "\n";
                }
 
-               if ( $row->rev_sha1 && !( $row->rev_deleted & Revision::DELETED_TEXT ) ) {
-                       $out .= "      " . Xml::element('sha1', null, strval( $row->rev_sha1 ) ) . "\n";
-               } else {
-                       $out .= "      <sha1/>\n";
-               }
-
                $text = '';
                if ( $row->rev_deleted & Revision::DELETED_TEXT ) {
                        $out .= "      " . Xml::element( 'text', array( 'deleted' => 'deleted' ) ) . "\n";
@@ -679,6 +673,34 @@ class XmlDumpWriter {
                                "" ) . "\n";
                }
 
+               if ( $row->rev_sha1 && !( $row->rev_deleted & Revision::DELETED_TEXT ) ) {
+                       $out .= "      " . Xml::element('sha1', null, strval( $row->rev_sha1 ) ) . "\n";
+               } else {
+                       $out .= "      <sha1/>\n";
+               }
+
+               if ( isset( $row->rev_content_model ) && !is_null( $row->rev_content_model )  ) {
+                       $content_model = strval( $row->rev_content_model );
+               } else {
+                       // probably using $wgContentHandlerUseDB = false;
+                       // @todo: test!
+                       $title = Title::makeTitle( $row->page_namespace, $row->page_title );
+                       $content_model = ContentHandler::getDefaultModelFor( $title );
+               }
+
+               $out .= "      " . Xml::element('model', null, strval( $content_model ) ) . "\n";
+
+               if ( isset( $row->rev_content_format ) && !is_null( $row->rev_content_format ) ) {
+                       $content_format = strval( $row->rev_content_format );
+               } else {
+                       // probably using $wgContentHandlerUseDB = false;
+                       // @todo: test!
+                       $content_handler = ContentHandler::getForModelID( $content_model );
+                       $content_format = $content_handler->getDefaultFormat();
+               }
+
+               $out .= "      " . Xml::element('format', null, strval( $content_format ) ) . "\n";
+
                wfRunHooks( 'XmlDumpWriterWriteRevision', array( &$this, &$out, $row, $text ) );
 
                $out .= "    </revision>\n";
@@ -1325,6 +1347,7 @@ class DumpNamespaceFilter extends DumpFilter {
        /**
         * @param $sink DumpOutput
         * @param $param
+        * @throws MWException
         */
        function __construct( &$sink, $param ) {
                parent::__construct( $sink );
index 26e456c..1b7c29d 100644 (file)
@@ -130,8 +130,8 @@ class ExternalStore {
         *
         * @param $data String
         * @param $storageParams Array: associative array of parameters for the ExternalStore object.
-        * @throws DBConnectionError|DBQueryError|MWException
-        * @return string The URL of the stored data item, or false on error
+        * @throws MWException|DBConnectionError|DBQueryError
+        * @return string|bool The URL of the stored data item, or false on error
         */
        public static function insertToDefault( $data, $storageParams = array() ) {
                global $wgDefaultExternalStore;
index 4f35394..37b1b93 100644 (file)
@@ -100,8 +100,8 @@ class ExternalStoreDB {
         */
        function fetchFromURL( $url ) {
                $path = explode( '/', $url );
-               $cluster  = $path[2];
-               $id       = $path[3];
+               $cluster = $path[2];
+               $id = $path[3];
                if ( isset( $path[4] ) ) {
                        $itemID = $path[4];
                } else {
index 11b2675..b0a0e2b 100644 (file)
@@ -138,13 +138,23 @@ class FeedUtils {
                        $diffText = '';
                        // Don't bother generating the diff if we won't be able to show it
                        if ( $wgFeedDiffCutoff > 0 ) {
-                               $de = new DifferenceEngine( $title, $oldid, $newid );
-                               $diffText = $de->getDiff(
-                                       wfMessage( 'previousrevision' )->text(), // hack
-                                       wfMessage( 'revisionasof',
-                                       $wgLang->timeanddate( $timestamp ),
-                                       $wgLang->date( $timestamp ),
-                                       $wgLang->time( $timestamp ) )->text() );
+                               $rev = Revision::newFromId( $oldid );
+
+                               if ( !$rev ) {
+                                       $diffText = false;
+                               } else {
+                                       $context = clone RequestContext::getMain();
+                                       $context->setTitle( $title );
+
+                                       $contentHandler = $rev->getContentHandler();
+                                       $de = $contentHandler->createDifferenceEngine( $context, $oldid, $newid );
+                                       $diffText = $de->getDiff(
+                                               wfMessage( 'previousrevision' )->text(), // hack
+                                               wfMessage( 'revisionasof',
+                                                       $wgLang->timeanddate( $timestamp ),
+                                                       $wgLang->date( $timestamp ),
+                                                       $wgLang->time( $timestamp ) )->text() );
+                               }
                        }
 
                        if ( $wgFeedDiffCutoff <= 0 || ( strlen( $diffText ) > $wgFeedDiffCutoff ) ) {
@@ -162,16 +172,36 @@ class FeedUtils {
                } else {
                        $rev = Revision::newFromId( $newid );
                        if( $wgFeedDiffCutoff <= 0 || is_null( $rev ) ) {
-                               $newtext = '';
+                               $newContent = ContentHandler::getForTitle( $title )->makeEmptyContent();
+                       } else {
+                               $newContent = $rev->getContent();
+                       }
+
+                       if ( $newContent instanceof TextContent ) {
+                               // only textual content has a "source view".
+                               $text = $newContent->getNativeData();
+
+                               if ( $wgFeedDiffCutoff <= 0 || strlen( $text ) > $wgFeedDiffCutoff ) {
+                                       $html = null;
+                               } else {
+                                       $html = nl2br( htmlspecialchars( $text ) );
+                               }
                        } else {
-                               $newtext = $rev->getText();
+                               //XXX: we could get an HTML representation of the content via getParserOutput, but that may
+                               //     contain JS magic and generally may not be suitable for inclusion in a feed.
+                               //     Perhaps Content should have a getDescriptiveHtml method and/or a getSourceText method.
+                               //Compare also ApiFeedContributions::feedItemDesc
+                               $html = null;
                        }
-                       if ( $wgFeedDiffCutoff <= 0 || strlen( $newtext ) > $wgFeedDiffCutoff ) {
+
+                       if ( $html === null ) {
+
                                // Omit large new page diffs, bug 29110
+                               // Also use diff link for non-textual content
                                $diffText = self::getDiffLink( $title, $newid );
                        } else {
                                $diffText = '<p><b>' . wfMessage( 'newpage' )->text() . '</b></p>' .
-                                       '<div>' . nl2br( htmlspecialchars( $newtext ) ) . '</div>';
+                                       '<div>' . $html . '</div>';
                        }
                }
                $completeText .= $diffText;
index 7e616b2..e2fcd9c 100644 (file)
@@ -144,6 +144,7 @@ class FileDeleteForm {
         * @param $reason String: reason of the deletion
         * @param $suppress Boolean: whether to mark all deleted versions as restricted
         * @param $user User object performing the request
+        * @throws MWException
         * @return bool|Status
         */
        public static function doDelete( &$title, &$file, &$oldimage, $reason, $suppress, User $user = null ) {
index 33bbd86..f978639 100644 (file)
@@ -83,6 +83,7 @@ class FormOptions implements ArrayAccess {
         * which will be assumed as INT if the data is an integer.
         *
         * @param $data Mixed: value to guess type for
+        * @throws MWException
         * @exception MWException Unsupported datatype
         * @return int Type constant
         */
@@ -105,6 +106,7 @@ class FormOptions implements ArrayAccess {
         *
         * @param $name String: option name
         * @param $strict Boolean: throw an exception when the option does not exist (default false)
+        * @throws MWException
         * @return Boolean: true if option exist, false otherwise
         */
        public function validateName( $name, $strict = false ) {
@@ -205,11 +207,12 @@ class FormOptions implements ArrayAccess {
 
        /**
         * Validate and set an option integer value
-        * The value will be altered to fit in the range. 
+        * The value will be altered to fit in the range.
         *
         * @param $name String: option name
         * @param $min Int: minimum value
         * @param $max Int: maximum value
+        * @throws MWException
         * @exception MWException Option is not of type int
         * @return null
         */
index 17e3f4d..3de25e7 100644 (file)
@@ -169,6 +169,7 @@ function wfArrayLookup( $a, $b ) {
  * @param $value Mixed
  * @param $default Mixed
  * @param $changed Array to alter
+ * @throws MWException
  */
 function wfAppendToArrayIfNotDefault( $key, $value, $default, &$changed ) {
        if ( is_null( $changed ) ) {
@@ -1110,6 +1111,7 @@ function wfWarn( $msg, $callerOffset = 1, $level = E_USER_NOTICE ) {
  *
  * @param $text String
  * @param $file String filename
+ * @throws MWException
  */
 function wfErrorLog( $text, $file ) {
        if ( substr( $file, 0, 4 ) == 'udp:' ) {
@@ -1719,6 +1721,7 @@ function wfEmptyMsg( $key ) {
  * but now throws an exception instead, with similar results.
  *
  * @param $msg String: message shown when dying.
+ * @throws MWException
  */
 function wfDebugDieBacktrace( $msg = '' ) {
        throw new MWException( $msg );
@@ -2512,6 +2515,7 @@ function wfTempDir() {
  * @param $dir String: full path to directory to create
  * @param $mode Integer: chmod value to use, default is $wgDirectoryMode
  * @param $caller String: optional caller param for debugging.
+ * @throws MWException
  * @return bool
  */
 function wfMkdirParents( $dir, $mode = null, $caller = null ) {
@@ -3022,6 +3026,7 @@ function wfDiff( $before, $after, $params = '-u' ) {
  *
  * @param $req_ver Mixed: the version to check, can be a string, an integer, or
  *                 a float
+ * @throws MWException
  */
 function wfUsePHP( $req_ver ) {
        $php_ver = PHP_VERSION;
@@ -3043,6 +3048,7 @@ function wfUsePHP( $req_ver ) {
  *
  * @param $req_ver Mixed: the version to check, can be a string, an integer, or
  *                 a float
+ * @throws MWException
  */
 function wfUseMW( $req_ver ) {
        global $wgVersion;
@@ -3551,16 +3557,6 @@ function wfBoolToStr( $value ) {
        return $value ? 'true' : 'false';
 }
 
-/**
- * Load an extension messages file
- *
- * @deprecated since 1.16, warnings in 1.18, remove in 1.20
- * @codeCoverageIgnore
- */
-function wfLoadExtensionMessages() {
-       wfDeprecated( __FUNCTION__, '1.16' );
-}
-
 /**
  * Get a platform-independent path to the null file, e.g. /dev/null
  *
index 5c00b9f..ef24b62 100644 (file)
@@ -248,6 +248,7 @@ class HTMLForm extends ContextSource {
         * Set format in which to display the form
         * @param $format String the name of the format to use, must be one of
         *        $this->availableDisplayFormats
+        * @throws MWException
         * @since 1.20
         * @return HTMLForm $this for chaining calls (since 1.20)
         */
@@ -279,6 +280,7 @@ class HTMLForm extends ContextSource {
         * Initialise a new Object for the field
         * @param $fieldname string
         * @param $descriptor string input Descriptor, as described above
+        * @throws MWException
         * @return HTMLFormField subclass
         */
        static function loadInputFromParameters( $fieldname, $descriptor ) {
@@ -312,6 +314,7 @@ class HTMLForm extends ContextSource {
         * @attention When doing method chaining, that should be the very last
         * method call before displayForm().
         *
+        * @throws MWException
         * @return HTMLForm $this for chaining calls (since 1.20)
         */
        function prepareForm() {
@@ -375,9 +378,10 @@ class HTMLForm extends ContextSource {
        /**
         * Validate all the fields, and call the submision callback
         * function if everything is kosher.
+        * @throws MWException
         * @return Mixed Bool true == Successful submission, Bool false
-        *       == No submission attempted, anything else == Error to
-        *       display.
+        *     == No submission attempted, anything else == Error to
+        *     display.
         */
        function trySubmit() {
                # Check for validation
@@ -1177,6 +1181,7 @@ abstract class HTMLFormField {
        /**
         * Initialise the object
         * @param $params array Associative Array. See HTMLForm doc for syntax.
+        * @throws MWException
         */
        function __construct( $params ) {
                $this->mParams = $params;
@@ -1321,7 +1326,6 @@ abstract class HTMLFormField {
        public function getRaw( $value ) {
                list( $errors, $errorClass ) = $this->getErrorsAndErrorClass( $value );
                $inputHtml = $this->getInputHTML( $value );
-               $fieldType = get_class( $this );
                $helptext = $this->getHelpTextHtmlRaw( $this->getHelpText() );
                $cellAttributes = array();
                $label = $this->getLabelHtml( $cellAttributes );
index 00dccdc..c9c0679 100644 (file)
@@ -39,12 +39,31 @@ class Hooks {
 
        protected static $handlers = array();
 
+       /**
+        * Clears hooks registered via Hooks::register(). Does not touch $wgHooks.
+        * This is intended for use while testing and will fail if MW_PHPUNIT_TEST is not defined.
+        *
+        * @since 1.21
+        *
+        * @param $name String: the name of the hook to clear.
+        *
+        * @throws MWException if not in testing mode.
+        */
+       public static function clear( $name ) {
+               if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
+                       throw new MWException( 'can not reset hooks in operation.' );
+               }
+
+               unset( self::$handlers[$name] );
+       }
+
+
        /**
         * Attach an event handler to a given hook
         *
         * @since 1.18
         *
-        * @param $name Mixed: name of hook
+        * @param $name String: name of hook
         * @param $callback Mixed: callback function to attach
         */
        public static function register( $name, $callback ) {
@@ -57,77 +76,81 @@ class Hooks {
 
        /**
         * Returns true if a hook has a function registered to it.
+        * The function may have been registered either via Hooks::register or in $wgHooks.
         *
         * @since 1.18
         *
-        * @param $name Mixed: name of hook
-        * @return Boolean: true if a hook has a function registered to it
+        * @param $name String: name of hook
+        * @return Boolean: true if the hook has a function registered to it
         */
        public static function isRegistered( $name ) {
-               if( !isset( self::$handlers[$name] ) ) {
-                       self::$handlers[$name] = array();
-               }
+               global $wgHooks;
 
-               return ( count( self::$handlers[$name] ) != 0 );
+               return !empty( $wgHooks[$name] ) || !empty( self::$handlers[$name] );
        }
 
        /**
         * Returns an array of all the event functions attached to a hook
-        *
+        * This combines functions registered via Hooks::register and with $wgHooks.
         * @since 1.18
         *
-        * @param $name Mixed: name of the hook
+        * @throws MWException
+        * @throws FatalError
+        * @param $name String: name of the hook
+        *
         * @return array
         */
        public static function getHandlers( $name ) {
-               if( !isset( self::$handlers[$name] ) ) {
+               global $wgHooks;
+
+               // Return quickly in the most common case
+               if ( empty( self::$handlers[$name] ) && empty( $wgHooks[$name] ) ) {
                        return array();
                }
 
-               return self::$handlers[$name];
+               if ( !is_array( self::$handlers ) ) {
+                       throw new MWException( "Local hooks array is not an array!\n" );
+               }
+
+               if ( !is_array( $wgHooks ) ) {
+                       throw new MWException( "Global hooks array is not an array!\n" );
+               }
+
+               if ( empty( Hooks::$handlers[$name] ) ) {
+                       $hooks = $wgHooks[$name];
+               } elseif ( empty( $wgHooks[$name] ) ) {
+                       $hooks = Hooks::$handlers[$name];
+               } else {
+                       // so they are both not empty...
+                       $hooks = array_merge( Hooks::$handlers[$name], $wgHooks[$name] );
+               }
+
+               if ( !is_array( $hooks ) ) {
+                       throw new MWException( "Hooks array for event '$name' is not an array!\n" );
+               }
+
+               return $hooks;
        }
 
        /**
         * Call hook functions defined in Hooks::register
         *
-        * Because programmers assign to $wgHooks, we need to be very
-        * careful about its contents. So, there's a lot more error-checking
-        * in here than would normally be necessary.
-        *
-        * @since 1.18
-        *
         * @param $event String: event name
-        * @param $args Array: parameters passed to hook functions
-        * @throws MWException
-        * @throws FatalError
+        * @param $args  Array: parameters passed to hook functions
+        *
         * @return Boolean True if no handler aborted the hook
         */
        public static function run( $event, $args = array() ) {
                global $wgHooks;
 
                // Return quickly in the most common case
-               if ( !isset( self::$handlers[$event] ) && !isset( $wgHooks[$event] ) ) {
+               if ( empty( self::$handlers[$event] ) && empty( $wgHooks[$event] ) ) {
                        return true;
                }
 
-               if ( !is_array( self::$handlers ) ) {
-                       throw new MWException( "Local hooks array is not an array!\n" );
-               }
-
-               if ( !is_array( $wgHooks ) ) {
-                       throw new MWException( "Global hooks array is not an array!\n" );
-               }
-
-               $new_handlers = (array) self::$handlers;
-               $old_handlers = (array) $wgHooks;
-
-               $hook_array = array_merge( $new_handlers, $old_handlers );
-
-               if ( !is_array( $hook_array[$event] ) ) {
-                       throw new MWException( "Hooks array for event '$event' is not an array!\n" );
-               }
+               $hooks = self::getHandlers( $event );
 
-               foreach ( $hook_array[$event] as $index => $hook ) {
+               foreach ( $hooks as $hook ) {
                        $object = null;
                        $method = null;
                        $func = null;
@@ -145,7 +168,7 @@ class Hooks {
                                if ( count( $hook ) < 1 ) {
                                        throw new MWException( 'Empty array in hooks for ' . $event . "\n" );
                                } elseif ( is_object( $hook[0] ) ) {
-                                       $object = $hook_array[$event][$index][0];
+                                       $object = $hook[0];
                                        if ( $object instanceof Closure ) {
                                                $closure = true;
                                                if ( count( $hook ) > 1 ) {
@@ -175,7 +198,7 @@ class Hooks {
                        } elseif ( is_string( $hook ) ) { # functions look like strings, too
                                $func = $hook;
                        } elseif ( is_object( $hook ) ) {
-                               $object = $hook_array[$event][$index];
+                               $object = $hook;
                                if ( $object instanceof Closure ) {
                                        $closure = true;
                                } else {
index 9fcdc52..f4a3b55 100644 (file)
@@ -810,6 +810,14 @@ class Html {
                        );
                }
 
+               if ( !array_key_exists( 'id', $selectAttribs ) ) {
+                       $selectAttribs['id'] = 'namespace';
+               }
+
+               if ( !array_key_exists( 'name', $selectAttribs ) ) {
+                       $selectAttribs['name'] = 'namespace';
+               }
+
                $ret = '';
                if ( isset( $params['label'] ) ) {
                        $ret .= Html::element(
index 8453e62..32f77dc 100644 (file)
@@ -265,6 +265,7 @@ class MWHttpRequest {
         * Generate a new request object
         * @param $url String: url to use
         * @param $options Array: (optional) extra params to pass (see Http::request())
+        * @throws MWException
         * @return CurlHttpRequest|PhpHttpRequest
         * @see MWHttpRequest::__construct
         */
@@ -393,6 +394,7 @@ class MWHttpRequest {
         * will be aborted.
         *
         * @param $callback Callback
+        * @throws MWException
         */
        public function setCallback( $callback ) {
                if ( !is_callable( $callback ) ) {
index 6f1b1a1..316e1c9 100644 (file)
@@ -157,7 +157,9 @@ class ImagePage extends Article {
                        $out->addHTML( Xml::openElement( 'div', array( 'id' => 'mw-imagepage-content',
                                'lang' => $pageLang->getHtmlCode(), 'dir' => $pageLang->getDir(),
                                'class' => 'mw-content-'.$pageLang->getDir() ) ) );
+
                        parent::view();
+
                        $out->addHTML( Xml::closeElement( 'div' ) );
                } else {
                        # Just need to set the right headers
@@ -272,18 +274,18 @@ class ImagePage extends Article {
        }
 
        /**
-        * Overloading Article's getContent method.
+        * Overloading Article's getContentObject method.
         *
         * Omit noarticletext if sharedupload; text will be fetched from the
         * shared upload server if possible.
         * @return string
         */
-       public function getContent() {
+       public function getContentObject() {
                $this->loadFile();
                if ( $this->mPage->getFile() && !$this->mPage->getFile()->isLocal() && 0 == $this->getID() ) {
-                       return '';
+                       return null;
                }
-               return parent::getContent();
+               return parent::getContentObject();
        }
 
        protected function openShowImage() {
index 11f3795..c9b0997 100644 (file)
@@ -429,6 +429,7 @@ class WikiImporter {
 
        /**
         * Primary entry point
+        * @throws MWException
         * @return bool
         */
        public function doImport() {
@@ -611,7 +612,7 @@ class WikiImporter {
                $this->debug( "Enter revision handler" );
                $revisionInfo = array();
 
-               $normalFields = array( 'id', 'timestamp', 'comment', 'minor', 'text' );
+               $normalFields = array( 'id', 'timestamp', 'comment', 'minor', 'model', 'format', 'text' );
 
                $skip = false;
 
@@ -656,6 +657,12 @@ class WikiImporter {
                if ( isset( $revisionInfo['text'] ) ) {
                        $revision->setText( $revisionInfo['text'] );
                }
+               if ( isset( $revisionInfo['model'] ) ) {
+                       $revision->setModel( $revisionInfo['model'] );
+               }
+               if ( isset( $revisionInfo['text'] ) ) {
+                       $revision->setFormat( $revisionInfo['format'] );
+               }
                $revision->setTitle( $pageInfo['_title'] );
 
                if ( isset( $revisionInfo['timestamp'] ) ) {
@@ -1008,7 +1015,10 @@ class WikiRevision {
        var $timestamp = "20010115000000";
        var $user = 0;
        var $user_text = "";
+       var $model = null;
+       var $format = null;
        var $text = "";
+       var $content = null;
        var $comment = "";
        var $minor = false;
        var $type = "";
@@ -1064,6 +1074,20 @@ class WikiRevision {
                $this->user_text = $ip;
        }
 
+       /**
+        * @param $model
+        */
+       function setModel( $model ) {
+               $this->model = $model;
+       }
+
+       /**
+        * @param $format
+        */
+       function setFormat( $format ) {
+               $this->format = $format;
+       }
+
        /**
         * @param $text
         */
@@ -1187,11 +1211,54 @@ class WikiRevision {
 
        /**
         * @return string
+        *
+        * @deprecated Since 1.21, use getContent() instead.
         */
        function getText() {
+               wfDeprecated( "Use getContent() instead." );
+
                return $this->text;
        }
 
+       /**
+        * @return Content
+        */
+       function getContent() {
+               if ( is_null( $this->content ) ) {
+                       $this->content =
+                               ContentHandler::makeContent(
+                                       $this->text,
+                                       $this->getTitle(),
+                                       $this->getModel(),
+                                       $this->getFormat()
+                               );
+               }
+
+               return $this->content;
+       }
+
+       /**
+        * @return String
+        */
+       function getModel() {
+               if ( is_null( $this->model ) ) {
+                       $this->model = $this->getTitle()->getContentModel();
+               }
+
+               return $this->model;
+       }
+
+       /**
+        * @return String
+        */
+       function getFormat() {
+               if ( is_null( $this->model ) ) {
+                       $this->format = ContentHandler::getForTitle( $this->getTitle() )->getDefaultFormat();
+               }
+
+               return $this->format;
+       }
+
        /**
         * @return string
         */
@@ -1331,7 +1398,9 @@ class WikiRevision {
                # Insert the row
                $revision = new Revision( array(
                        'page'       => $pageId,
-                       'text'       => $this->getText(),
+                       'content_model'  => $this->getModel(),
+                       'content_format' => $this->getFormat(),
+                       'text'       => $this->getContent()->serialize( $this->getFormat() ), //XXX: just set 'content' => $this->getContent()?
                        'comment'    => $this->getComment(),
                        'user'       => $userId,
                        'user_text'  => $userText,
index 214f495..b19f6c4 100644 (file)
 class LinkFilter {
 
        /**
-        * Check whether $text contains a link to $filterEntry
+        * Check whether $content contains a link to $filterEntry
         *
-        * @param $text String: text to check
+        * @param $content Content: content to check
         * @param $filterEntry String: domainparts, see makeRegex() for more details
         * @return Integer: 0 if no match or 1 if there's at least one match
         */
-       static function matchEntry( $text, $filterEntry ) {
+       static function matchEntry( Content $content, $filterEntry ) {
+               if ( !( $content instanceof TextContent ) ) {
+                       //TODO: handle other types of content too.
+                       //      Maybe create ContentHandler::matchFilter( LinkFilter ).
+                       //      Think about a common base class for LinkFilter and MagicWord.
+                       return 0;
+               }
+
+               $text = $content->getNativeData();
+
                $regex = LinkFilter::makeRegex( $filterEntry );
                return preg_match( $regex, $text );
        }
index 97b03be..c17e2d1 100644 (file)
@@ -448,6 +448,7 @@ class Linker {
         * @param $context IContextSource context to use to get the messages
         * @param $namespace int Namespace number
         * @param $title string Text of the title, without the namespace part
+        * @return string
         */
        public static function getInvalidTitleDescription( IContextSource $context, $namespace, $title ) {
                global $wgContLang;
index 87db4d6..fd1fefb 100644 (file)
@@ -49,6 +49,7 @@ class LinksUpdate extends SqlDataUpdate {
         * @param $title Title of the page we're updating
         * @param $parserOutput ParserOutput: output from a full parse of this page
         * @param $recursive Boolean: queue jobs for recursive updates?
+        * @throws MWException
         */
        function __construct( $title, $parserOutput, $recursive = true ) {
                parent::__construct( false ); // no implicit transaction
@@ -71,6 +72,7 @@ class LinksUpdate extends SqlDataUpdate {
                }
 
                $this->mParserOutput = $parserOutput;
+
                $this->mLinks = $parserOutput->getLinks();
                $this->mImages = $parserOutput->getImages();
                $this->mTemplates = $parserOutput->getTemplates();
@@ -828,6 +830,10 @@ class LinksDeletionUpdate extends SqlDataUpdate {
                parent::__construct( false ); // no implicit transaction
 
                $this->mPage = $page;
+
+               if ( !$page->exists() ) {
+                       throw new MWException( "Page ID not known, perhaps the page doesn't exist?" );
+               }
        }
 
        /**
@@ -879,4 +885,16 @@ class LinksDeletionUpdate extends SqlDataUpdate {
                                __METHOD__ );
                }
        }
+
+       /**
+        * Update all the appropriate counts in the category table.
+        * @param $added array associative array of category name => sort key
+        * @param $deleted array associative array of category name => sort key
+        */
+       function updateCategoryCounts( $added, $deleted ) {
+               $a = WikiPage::factory( $this->mTitle );
+               $a->updateCategoryCounts(
+                       array_keys( $added ), array_keys( $deleted )
+               );
+       }
 }
index d8e5d3a..e88c240 100644 (file)
@@ -168,6 +168,7 @@ class LocalisationCache {
         * for $wgLocalisationCacheConf.
         *
         * @param $conf Array
+        * @throws MWException
         */
        function __construct( $conf ) {
                global $wgCacheDirectory;
@@ -404,6 +405,7 @@ class LocalisationCache {
        /**
         * Initialise a language in this object. Rebuild the cache if necessary.
         * @param $code
+        * @throws MWException
         */
        protected function initLanguage( $code ) {
                if ( isset( $this->initialisedLangs[$code] ) ) {
@@ -474,6 +476,7 @@ class LocalisationCache {
         * Read a PHP file containing localisation data.
         * @param $_fileName
         * @param $_fileType
+        * @throws MWException
         * @return array
         */
        protected function readPHPFile( $_fileName, $_fileType ) {
@@ -659,6 +662,7 @@ class LocalisationCache {
         * Load localisation data for a given language for both core and extensions
         * and save it to the persistent cache store and the process cache
         * @param $code
+        * @throws MWException
         */
        public function recache( $code ) {
                global $wgExtensionMessagesFiles;
index 9d09f00..824f177 100644 (file)
@@ -202,6 +202,11 @@ class Message {
         */
        protected $title = null;
 
+       /**
+        * Content object representing the message
+        */
+       protected $content = null;
+
        /**
         * @var string
         */
@@ -332,6 +337,7 @@ class Message {
         * turned off.
         * @since 1.17
         * @param $lang Mixed: language code or Language object.
+        * @throws MWException
         * @return Message: $this
         */
        public function inLanguage( $lang ) {
@@ -404,6 +410,18 @@ class Message {
                return $this;
        }
 
+       /**
+        * Returns the message as a Content object.
+        * @return Content
+        */
+       public function content() {
+               if ( !$this->content ) {
+                       $this->content = new MessageContent( $this->key );
+               }
+
+               return $this->content;
+       }
+
        /**
         * Returns the message parsed from wikitext to HTML.
         * @since 1.17
@@ -420,6 +438,15 @@ class Message {
                        return '&lt;' . $key . '&gt;';
                }
 
+               # Replace $* with a list of parameters for &uselang=qqx.
+               if ( strpos( $string, '$*' ) !== false ) {
+                       $paramlist = '';
+                       if ( $this->parameters !== array() ) {
+                               $paramlist = ': $' . implode( ', $', range( 1, count( $this->parameters ) ) );
+                       }
+                       $string = str_replace( '$*', $paramlist, $string );
+               }
+
                # Replace parameters before text parsing
                $string = $this->replaceParameters( $string, 'before' );
 
@@ -618,6 +645,7 @@ class Message {
        /**
         * Wrapper for what ever method we use to get message contents
         * @since 1.17
+        * @throws MWException
         * @return string
         */
        protected function fetchMessage() {
index 34014e1..3a698e5 100644 (file)
@@ -311,6 +311,7 @@ class MessageBlobStore {
         * @param $resourceLoader ResourceLoader object
         * @param $modules Array of module names
         * @param $lang String: language code
+        * @throws MWException
         * @return array Array mapping module names to blobs
         */
        private static function getFromDB( ResourceLoader $resourceLoader, $modules, $lang ) {
index 2e2b8d6..e8d5632 100644 (file)
@@ -48,6 +48,7 @@ class MWNamespace {
         * @param $index
         * @param $method
         *
+        * @throws MWException
         * @return bool
         */
        private static function isMethodValidFor( $index, $method ) {
@@ -209,12 +210,14 @@ class MWNamespace {
         * Returns array of all defined namespaces with their canonical
         * (English) names.
         *
+        * @param bool $rebuild rebuild namespace list (default = false). Used for testing.
+        *
         * @return array
         * @since 1.17
         */
-       public static function getCanonicalNamespaces() {
+       public static function getCanonicalNamespaces( $rebuild = false ) {
                static $namespaces = null;
-               if ( $namespaces === null ) {
+               if ( $namespaces === null || $rebuild ) {
                        global $wgExtraNamespaces, $wgCanonicalNamespaceNames;
                        $namespaces = array( NS_MAIN => '' ) + $wgCanonicalNamespaceNames;
                        if ( is_array( $wgExtraNamespaces ) ) {
index 02f22d9..dd9c9e3 100644 (file)
@@ -1570,9 +1570,10 @@ class OutputPage extends ContextSource {
         * @param $interface Boolean: use interface language ($wgLang instead of
         *                   $wgContLang) while parsing language sensitive magic
         *                   words like GRAMMAR and PLURAL. This also disables
-        *                                       LanguageConverter.
+        *                   LanguageConverter.
         * @param $language  Language object: target language object, will override
         *                   $interface
+        * @throws MWException
         * @return String: HTML
         */
        public function parse( $text, $linestart = true, $interface = false, $language = null ) {
@@ -1993,7 +1994,9 @@ class OutputPage extends ContextSource {
                wfRunHooks( 'AfterFinalPageOutput', array( $this ) );
 
                $this->sendCacheControl();
+
                ob_end_flush();
+
                wfProfileOut( __METHOD__ );
        }
 
@@ -2056,7 +2059,7 @@ class OutputPage extends ContextSource {
                $this->prepareErrorPage( $title );
 
                if ( $msg instanceof Message ){
-                       $this->addHTML( $msg->parse() );
+                       $this->addHTML( $msg->parseAsBlock() );
                } else {
                        $this->addWikiMsgArray( $msg, $params );
                }
@@ -2071,8 +2074,6 @@ class OutputPage extends ContextSource {
         * @param $action String: action that was denied or null if unknown
         */
        public function showPermissionsErrorPage( $errors, $action = null ) {
-               global $wgGroupPermissions;
-
                // For some action (read, edit, create and upload), display a "login to do this action"
                // error if all of the following conditions are met:
                // 1. the user is not logged in
@@ -2081,8 +2082,8 @@ class OutputPage extends ContextSource {
                if ( in_array( $action, array( 'read', 'edit', 'createpage', 'createtalk', 'upload' ) )
                        && $this->getUser()->isAnon() && count( $errors ) == 1 && isset( $errors[0][0] )
                        && ( $errors[0][0] == 'badaccess-groups' || $errors[0][0] == 'badaccess-group0' )
-                       && ( ( isset( $wgGroupPermissions['user'][$action] ) && $wgGroupPermissions['user'][$action] )
-                       || ( isset( $wgGroupPermissions['autoconfirmed'][$action] ) && $wgGroupPermissions['autoconfirmed'][$action] ) )
+                       && ( User::groupHasPermission( 'user', $action )
+                       || User::groupHasPermission( 'autoconfirmed', $action ) )
                ) {
                        $displayReturnto = null;
 
@@ -2155,6 +2156,7 @@ class OutputPage extends ContextSource {
         * Display an error page noting that a given permission bit is required.
         * @deprecated since 1.18, just throw the exception directly
         * @param $permission String: key required
+        * @throws PermissionsError
         */
        public function permissionRequired( $permission ) {
                throw new PermissionsError( $permission );
@@ -2225,6 +2227,7 @@ class OutputPage extends ContextSource {
         * @param $protected Boolean: is this a permissions error?
         * @param $reasons   Array: list of reasons for this error, as returned by Title::getUserPermissionsErrors().
         * @param $action    String: action that was denied or null if unknown
+        * @throws ReadOnlyError
         */
        public function readOnlyPage( $source = null, $protected = false, $reasons = array(), $action = null ) {
                $this->setRobotPolicy( 'noindex,nofollow' );
@@ -2912,7 +2915,7 @@ $templates
         * @return array
         */
        public function getJSVars() {
-               global $wgUseAjax, $wgContLang;
+               global $wgContLang;
 
                $latestRevID = 0;
                $pageID = 0;
@@ -3565,7 +3568,6 @@ $templates
                $msgSpecs = array_values( $msgSpecs );
                $s = $wrap;
                foreach ( $msgSpecs as $n => $spec ) {
-                       $options = array();
                        if ( is_array( $spec ) ) {
                                $args = $spec;
                                $name = array_shift( $args );
index dad71f8..2aed2af 100644 (file)
@@ -38,7 +38,7 @@
  * version are hardcoded here
  */
 function wfPHPVersionError( $type ){
-       $mwVersion = '1.20';
+       $mwVersion = '1.21';
        $phpVersion = PHP_VERSION;
        $message = "MediaWiki $mwVersion requires at least PHP version 5.3.2, you are using PHP $phpVersion.";
        if( $type == 'index.php' ) {
index ce0e36b..beb20ea 100644 (file)
@@ -286,12 +286,10 @@ class ProtectionForm {
 
                # They shouldn't be able to do this anyway, but just to make sure, ensure that cascading restrictions aren't being applied
                #  to a semi-protected page.
-               global $wgGroupPermissions;
-
                $edit_restriction = isset( $this->mRestrictions['edit'] ) ? $this->mRestrictions['edit'] : '';
                $this->mCascade = $wgRequest->getBool( 'mwProtect-cascade' );
                if ($this->mCascade && ($edit_restriction != 'protect') &&
-                       !(isset($wgGroupPermissions[$edit_restriction]['protect']) && $wgGroupPermissions[$edit_restriction]['protect'] ) )
+                       !User::groupHasPermission( $edit_restriction, 'protect' ) )
                        $this->mCascade = false;
 
                $status = $this->mArticle->doUpdateRestrictions( $this->mRestrictions, $expiry, $this->mCascade, $reasonstr, $wgUser );
@@ -600,11 +598,11 @@ class ProtectionForm {
        }
 
        function buildCleanupScript() {
-               global $wgRestrictionLevels, $wgGroupPermissions, $wgOut;
+               global $wgRestrictionLevels, $wgOut;
 
                $cascadeableLevels = array();
                foreach( $wgRestrictionLevels as $key ) {
-                       if ( ( isset( $wgGroupPermissions[$key]['protect'] ) && $wgGroupPermissions[$key]['protect'] )
+                       if ( User::groupHasPermission( $key, 'protect' )
                                || $key == 'protect'
                        ) {
                                $cascadeableLevels[] = $key;
index ac559dc..1f21584 100644 (file)
@@ -151,6 +151,7 @@ abstract class QueryPage extends SpecialPage {
        /**
         * For back-compat, subclasses may return a raw SQL query here, as a string.
         * This is stronly deprecated; getQueryInfo() should be overridden instead.
+        * @throws MWException
         * @return string
         */
        function getSQL() {
index 20cc8f5..40e7734 100644 (file)
@@ -40,6 +40,10 @@ class Revision implements IDBAccessObject {
        protected $mTextRow;
        protected $mTitle;
        protected $mCurrent;
+       protected $mContentModel;
+       protected $mContentFormat;
+       protected $mContent;
+       protected $mContentHandler;
 
        // Revision deletion constants
        const DELETED_TEXT = 1;
@@ -135,9 +139,12 @@ class Revision implements IDBAccessObject {
         * @param $row
         * @param $overrides array
         *
+        * @throws MWException
         * @return Revision
         */
        public static function newFromArchiveRow( $row, $overrides = array() ) {
+               global $wgContentHandlerUseDB;
+
                $attribs = $overrides + array(
                        'page'       => isset( $row->ar_page_id ) ? $row->ar_page_id : null,
                        'id'         => isset( $row->ar_rev_id ) ? $row->ar_rev_id : null,
@@ -150,7 +157,15 @@ class Revision implements IDBAccessObject {
                        'deleted'    => $row->ar_deleted,
                        'len'        => $row->ar_len,
                        'sha1'       => isset( $row->ar_sha1 ) ? $row->ar_sha1 : null,
+                       'content_model'   => isset( $row->ar_content_model ) ? $row->ar_content_model : null,
+                       'content_format'  => isset( $row->ar_content_format ) ? $row->ar_content_format : null,
                );
+
+               if ( !$wgContentHandlerUseDB ) {
+                       unset( $attribs['content_model'] );
+                       unset( $attribs['content_format'] );
+               }
+
                if ( isset( $row->ar_text ) && !$row->ar_text_id ) {
                        // Pre-1.5 ar_text row
                        $attribs['text'] = self::getRevisionText( $row, 'ar_' );
@@ -358,7 +373,9 @@ class Revision implements IDBAccessObject {
         * @return array
         */
        public static function selectFields() {
-               return array(
+               global $wgContentHandlerUseDB;
+
+               $fields = array(
                        'rev_id',
                        'rev_page',
                        'rev_text_id',
@@ -370,8 +387,15 @@ class Revision implements IDBAccessObject {
                        'rev_deleted',
                        'rev_len',
                        'rev_parent_id',
-                       'rev_sha1'
+                       'rev_sha1',
                );
+
+               if ( $wgContentHandlerUseDB ) {
+                       $fields[] = 'rev_content_format';
+                       $fields[] = 'rev_content_model';
+               }
+
+               return $fields;
        }
 
        /**
@@ -436,6 +460,7 @@ class Revision implements IDBAccessObject {
         * Constructor
         *
         * @param $row Mixed: either a database row or an array
+        * @throws MWException
         * @access private
         */
        function __construct( $row ) {
@@ -475,6 +500,18 @@ class Revision implements IDBAccessObject {
                                $this->mTitle = null;
                        }
 
+                       if( !isset( $row->rev_content_model ) || is_null( $row->rev_content_model ) ) {
+                               $this->mContentModel = null; # determine on demand if needed
+                       } else {
+                               $this->mContentModel = strval( $row->rev_content_model );
+                       }
+
+                       if( !isset( $row->rev_content_format ) || is_null( $row->rev_content_format ) ) {
+                               $this->mContentFormat = null; # determine on demand if needed
+                       } else {
+                               $this->mContentFormat = strval( $row->rev_content_format );
+                       }
+
                        // Lazy extraction...
                        $this->mText      = null;
                        if( isset( $row->old_text ) ) {
@@ -496,6 +533,21 @@ class Revision implements IDBAccessObject {
                        // Build a new revision to be saved...
                        global $wgUser; // ugh
 
+
+                       # if we have a content object, use it to set the model and type
+                       if ( !empty( $row['content'] ) ) {
+                               //@todo: when is that set? test with external store setup! check out insertOn() [dk]
+                               if ( !empty( $row['text_id'] ) ) {
+                                       throw new MWException( "Text already stored in external store (id {$row['text_id']}), "
+                                                                                       . "can't serialize content object" );
+                               }
+
+                               $row['content_model'] = $row['content']->getModel();
+                               # note: mContentFormat is initializes later accordingly
+                               # note: content is serialized later in this method!
+                               # also set text to null?
+                       }
+
                        $this->mId        = isset( $row['id']         ) ? intval( $row['id']         ) : null;
                        $this->mPage      = isset( $row['page']       ) ? intval( $row['page']       ) : null;
                        $this->mTextId    = isset( $row['text_id']    ) ? intval( $row['text_id']    ) : null;
@@ -508,21 +560,63 @@ class Revision implements IDBAccessObject {
                        $this->mParentId  = isset( $row['parent_id']  ) ? intval( $row['parent_id']  ) : null;
                        $this->mSha1      = isset( $row['sha1']  )      ? strval( $row['sha1']  )      : null;
 
+                       $this->mContentModel   = isset( $row['content_model']  )  ? strval( $row['content_model'] )  : null;
+                       $this->mContentFormat  = isset( $row['content_format']  ) ? strval( $row['content_format'] ) : null;
+
                        // Enforce spacing trimming on supplied text
                        $this->mComment   = isset( $row['comment']    ) ?  trim( strval( $row['comment'] ) ) : null;
                        $this->mText      = isset( $row['text']       ) ? rtrim( strval( $row['text']    ) ) : null;
                        $this->mTextRow   = null;
 
-                       $this->mTitle     = null; # Load on demand if needed
+                       $this->mTitle     = isset( $row['title']      ) ? $row['title'] : null;
+
+                       // if we have a Content object, override mText and mContentModel
+                       if ( !empty( $row['content'] ) ) {
+                               if ( !( $row['content'] instanceof Content ) ) {
+                                       throw new MWException( '`content` field must contain a Content object.' );
+                               }
+
+                               $handler = $this->getContentHandler();
+                               $this->mContent = $row['content'];
+
+                               $this->mContentModel = $this->mContent->getModel();
+                               $this->mContentHandler = null;
+
+                               $this->mText = $handler->serializeContent( $row['content'], $this->getContentFormat() );
+                       } elseif ( !is_null( $this->mText ) ) {
+                               $handler = $this->getContentHandler();
+                               $this->mContent = $handler->unserializeContent( $this->mText );
+                       }
+
+                       // if we have a Title object, override mPage. Useful for testing and convenience.
+                       if ( isset( $row['title'] ) ) {
+                               $this->mTitle     = $row['title'];
+                               $this->mPage      = $this->mTitle->getArticleID();
+                       } else {
+                               $this->mTitle     = null; // Load on demand if needed
+                       }
+
+                       // @todo: XXX: really? we are about to create a revision. it will usually then be the current one.
                        $this->mCurrent   = false;
-                       # If we still have no length, see it we have the text to figure it out
+
+                       // If we still have no length, see it we have the text to figure it out
                        if ( !$this->mSize ) {
-                               $this->mSize = is_null( $this->mText ) ? null : strlen( $this->mText );
+                               if ( !is_null( $this->mContent ) ) {
+                                       $this->mSize = $this->mContent->getSize();
+                               } else {
+                                       #NOTE: this should never happen if we have either text or content object!
+                                       $this->mSize = null;
+                               }
                        }
-                       # Same for sha1
+
+                       // Same for sha1
                        if ( $this->mSha1 === null ) {
                                $this->mSha1 = is_null( $this->mText ) ? null : self::base36Sha1( $this->mText );
                        }
+
+                       // force lazy init
+                       $this->getContentModel();
+                       $this->getContentFormat();
                } else {
                        throw new MWException( 'Revision constructor passed invalid row format.' );
                }
@@ -595,7 +689,7 @@ class Revision implements IDBAccessObject {
                if( isset( $this->mTitle ) ) {
                        return $this->mTitle;
                }
-               if( !is_null( $this->mId ) ) { //rev_id is defined as NOT NULL
+               if( !is_null( $this->mId ) ) { //rev_id is defined as NOT NULL, but this revision may not yet have been inserted.
                        $dbr = wfGetDB( DB_SLAVE );
                        $row = $dbr->selectRow(
                                array( 'page', 'revision' ),
@@ -607,6 +701,11 @@ class Revision implements IDBAccessObject {
                                $this->mTitle = Title::newFromRow( $row );
                        }
                }
+
+               if ( !$this->mTitle && !is_null( $this->mPage ) && $this->mPage > 0 ) {
+                       $this->mTitle = Title::newFromID( $this->mPage );
+               }
+
                return $this->mTitle;
        }
 
@@ -789,15 +888,39 @@ class Revision implements IDBAccessObject {
         *      Revision::RAW              get the text regardless of permissions
         * @param $user User object to check for, only if FOR_THIS_USER is passed
         *              to the $audience parameter
+        *
+        * @deprecated in 1.21, use getContent() instead
+        * @todo: replace usage in core
         * @return String
         */
        public function getText( $audience = self::FOR_PUBLIC, User $user = null ) {
+               wfDeprecated( __METHOD__, '1.21' );
+
+               $content = $this->getContent( $audience, $user );
+               return ContentHandler::getContentText( $content ); # returns the raw content text, if applicable
+       }
+
+       /**
+        * Fetch revision content if it's available to the specified audience.
+        * If the specified audience does not have the ability to view this
+        * revision, null will be returned.
+        *
+        * @param $audience Integer: one of:
+        *      Revision::FOR_PUBLIC       to be displayed to all users
+        *      Revision::FOR_THIS_USER    to be displayed to $wgUser
+        *      Revision::RAW              get the text regardless of permissions
+        * @param $user User object to check for, only if FOR_THIS_USER is passed
+        *              to the $audience parameter
+        * @since 1.21
+        * @return Content
+        */
+       public function getContent( $audience = self::FOR_PUBLIC, User $user = null ) {
                if( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_TEXT ) ) {
-                       return '';
+                       return null;
                } elseif( $audience == self::FOR_THIS_USER && !$this->userCan( self::DELETED_TEXT, $user ) ) {
-                       return '';
+                       return null;
                } else {
-                       return $this->getRawText();
+                       return $this->getContentInternal();
                }
        }
 
@@ -816,15 +939,110 @@ class Revision implements IDBAccessObject {
         * Fetch revision text without regard for view restrictions
         *
         * @return String
+        *
+        * @deprecated since 1.21. Instead, use Revision::getContent( Revision::RAW )
+        *                         or Revision::getSerializedData() as appropriate.
         */
        public function getRawText() {
-               if( is_null( $this->mText ) ) {
-                       // Revision text is immutable. Load on demand:
-                       $this->mText = $this->loadText();
-               }
+               wfDeprecated( __METHOD__, "1.21" );
+
+               return $this->getText( self::RAW );
+       }
+
+       /**
+        * Fetch original serialized data without regard for view restrictions
+        *
+        * @since 1.21
+        * @return String
+        */
+       public function getSerializedData() {
                return $this->mText;
        }
 
+       /**
+        * Gets the content object for the revision
+        *
+        * @since 1.21
+        * @return Content
+        */
+       protected function getContentInternal() {
+               if( is_null( $this->mContent ) ) {
+                       // Revision is immutable. Load on demand:
+
+                       $handler = $this->getContentHandler();
+                       $format = $this->getContentFormat();
+                       $title = $this->getTitle();
+
+                       if( is_null( $this->mText ) ) {
+                               // Load text on demand:
+                               $this->mText = $this->loadText();
+                       }
+
+                       $this->mContent = is_null( $this->mText ) ? null : $handler->unserializeContent( $this->mText, $format );
+               }
+
+               return $this->mContent->copy(); // NOTE: copy() will return $this for immutable content objects
+       }
+
+       /**
+        * Returns the content model for this revision.
+        *
+        * If no content model was stored in the database, $this->getTitle()->getContentModel() is
+        * used to determine the content model to use. If no title is know, CONTENT_MODEL_WIKITEXT
+        * is used as a last resort.
+        *
+        * @return String the content model id associated with this revision, see the CONTENT_MODEL_XXX constants.
+        **/
+       public function getContentModel() {
+               if ( !$this->mContentModel ) {
+                       $title = $this->getTitle();
+                       $this->mContentModel = ( $title ? $title->getContentModel() : CONTENT_MODEL_WIKITEXT );
+
+                       assert( !empty( $this->mContentModel ) );
+               }
+
+               return $this->mContentModel;
+       }
+
+       /**
+        * Returns the content format for this revision.
+        *
+        * If no content format was stored in the database, the default format for this
+        * revision's content model is returned.
+        *
+        * @return String the content format id associated with this revision, see the CONTENT_FORMAT_XXX constants.
+        **/
+       public function getContentFormat() {
+               if ( !$this->mContentFormat ) {
+                       $handler = $this->getContentHandler();
+                       $this->mContentFormat = $handler->getDefaultFormat();
+
+                       assert( !empty( $this->mContentFormat ) );
+               }
+
+               return $this->mContentFormat;
+       }
+
+       /**
+        * Returns the content handler appropriate for this revision's content model.
+        *
+        * @return ContentHandler
+        */
+       public function getContentHandler() {
+               if ( !$this->mContentHandler ) {
+                       $model = $this->getContentModel();
+                       $this->mContentHandler = ContentHandler::getForModelID( $model );
+
+                       $format = $this->getContentFormat();
+
+                       if ( !$this->mContentHandler->isSupportedFormat( $format ) ) {
+                               throw new MWException( "Oops, the content format $format is not supported for this content model, $model" );
+                       }
+               }
+
+               return $this->mContentHandler;
+       }
+
        /**
         * @return String
         */
@@ -1004,13 +1222,16 @@ class Revision implements IDBAccessObject {
         * number on success and dies horribly on failure.
         *
         * @param $dbw DatabaseBase: (master connection)
+        * @throws MWException
         * @return Integer
         */
        public function insertOn( $dbw ) {
-               global $wgDefaultExternalStore;
+               global $wgDefaultExternalStore, $wgContentHandlerUseDB;
 
                wfProfileIn( __METHOD__ );
 
+               $this->checkContentModel();
+
                $data = $this->mText;
                $flags = self::compressRevisionText( $data );
 
@@ -1046,27 +1267,47 @@ class Revision implements IDBAccessObject {
                $rev_id = isset( $this->mId )
                        ? $this->mId
                        : $dbw->nextSequenceValue( 'revision_rev_id_seq' );
-               $dbw->insert( 'revision',
-                       array(
-                               'rev_id'         => $rev_id,
-                               'rev_page'       => $this->mPage,
-                               'rev_text_id'    => $this->mTextId,
-                               'rev_comment'    => $this->mComment,
-                               'rev_minor_edit' => $this->mMinorEdit ? 1 : 0,
-                               'rev_user'       => $this->mUser,
-                               'rev_user_text'  => $this->mUserText,
-                               'rev_timestamp'  => $dbw->timestamp( $this->mTimestamp ),
-                               'rev_deleted'    => $this->mDeleted,
-                               'rev_len'        => $this->mSize,
-                               'rev_parent_id'  => is_null( $this->mParentId )
-                                       ? $this->getPreviousRevisionId( $dbw )
-                                       : $this->mParentId,
-                               'rev_sha1'       => is_null( $this->mSha1 )
-                                       ? self::base36Sha1( $this->mText )
-                                       : $this->mSha1
-                       ), __METHOD__
+               $row = array(
+                       'rev_id'         => $rev_id,
+                       'rev_page'       => $this->mPage,
+                       'rev_text_id'    => $this->mTextId,
+                       'rev_comment'    => $this->mComment,
+                       'rev_minor_edit' => $this->mMinorEdit ? 1 : 0,
+                       'rev_user'       => $this->mUser,
+                       'rev_user_text'  => $this->mUserText,
+                       'rev_timestamp'  => $dbw->timestamp( $this->mTimestamp ),
+                       'rev_deleted'    => $this->mDeleted,
+                       'rev_len'        => $this->mSize,
+                       'rev_parent_id'  => is_null( $this->mParentId )
+                               ? $this->getPreviousRevisionId( $dbw )
+                               : $this->mParentId,
+                       'rev_sha1'       => is_null( $this->mSha1 )
+                               ? Revision::base36Sha1( $this->mText )
+                               : $this->mSha1,
                );
 
+               if ( $wgContentHandlerUseDB ) {
+                       //NOTE: Store null for the default model and format, to save space.
+                       //XXX: Makes the DB sensitive to changed defaults. Make this behaviour optional? Only in miser mode?
+
+                       $model = $this->getContentModel();
+                       $format = $this->getContentFormat();
+
+                       $title = $this->getTitle();
+
+                       if ( $title === null ) {
+                               throw new MWException( "Insufficient information to determine the title of the revision's page!" );
+                       }
+
+                       $defaultModel = ContentHandler::getDefaultModelFor( $title );
+                       $defaultFormat = ContentHandler::getForModelID( $defaultModel )->getDefaultFormat();
+
+                       $row[ 'rev_content_model' ] = ( $model === $defaultModel ) ? null : $model;
+                       $row[ 'rev_content_format' ] = ( $format === $defaultFormat ) ? null : $format;
+               }
+
+               $dbw->insert( 'revision', $row, __METHOD__ );
+
                $this->mId = !is_null( $rev_id ) ? $rev_id : $dbw->insertId();
 
                wfRunHooks( 'RevisionInsertComplete', array( &$this, $data, $flags ) );
@@ -1075,6 +1316,52 @@ class Revision implements IDBAccessObject {
                return $this->mId;
        }
 
+       protected function checkContentModel() {
+               global $wgContentHandlerUseDB;
+
+               $title = $this->getTitle(); //note: may return null for revisions that have not yet been inserted.
+
+               $model = $this->getContentModel();
+               $format = $this->getContentFormat();
+               $handler = $this->getContentHandler();
+
+               if ( !$handler->isSupportedFormat( $format ) ) {
+                       $t = $title->getPrefixedDBkey();
+
+                       throw new MWException( "Can't use format $format with content model $model on $t" );
+               }
+
+               if ( !$wgContentHandlerUseDB && $title ) {
+                       // if $wgContentHandlerUseDB is not set, all revisions must use the default content model and format.
+
+                       $defaultModel = ContentHandler::getDefaultModelFor( $title );
+                       $defaultHandler = ContentHandler::getForModelID( $defaultModel );
+                       $defaultFormat = $defaultHandler->getDefaultFormat();
+
+                       if ( $this->getContentModel() != $defaultModel ) {
+                               $t = $title->getPrefixedDBkey();
+
+                               throw new MWException( "Can't save non-default content model with \$wgContentHandlerUseDB disabled: "
+                                                                               . "model is $model , default for $t is $defaultModel" );
+                       }
+
+                       if ( $this->getContentFormat() != $defaultFormat ) {
+                               $t = $title->getPrefixedDBkey();
+
+                               throw new MWException( "Can't use non-default content format with \$wgContentHandlerUseDB disabled: "
+                                                                               . "format is $format, default for $t is $defaultFormat" );
+                       }
+               }
+
+               $content = $this->getContent( Revision::RAW );
+
+               if ( !$content->isValid() ) {
+                       $t = $title->getPrefixedDBkey();
+
+                       throw new MWException( "Content of $t is not valid! Content model is $model" );
+               }
+       }
+
        /**
         * Get the base 36 SHA-1 value for a string of text
         * @param $text String
@@ -1159,12 +1446,21 @@ class Revision implements IDBAccessObject {
         * @return Revision|null on error
         */
        public static function newNullRevision( $dbw, $pageId, $summary, $minor ) {
+               global $wgContentHandlerUseDB;
+
                wfProfileIn( __METHOD__ );
 
+               $fields = array( 'page_latest', 'page_namespace', 'page_title',
+                                               'rev_text_id', 'rev_len', 'rev_sha1' );
+
+               if ( $wgContentHandlerUseDB ) {
+                       $fields[] = 'rev_content_model';
+                       $fields[] = 'rev_content_format';
+               }
+
                $current = $dbw->selectRow(
                        array( 'page', 'revision' ),
-                       array( 'page_latest', 'page_namespace', 'page_title',
-                               'rev_text_id', 'rev_len', 'rev_sha1' ),
+                       $fields,
                        array(
                                'page_id' => $pageId,
                                'page_latest=rev_id',
@@ -1172,7 +1468,7 @@ class Revision implements IDBAccessObject {
                        __METHOD__ );
 
                if( $current ) {
-                       $revision = new Revision( array(
+                       $row = array(
                                'page'       => $pageId,
                                'comment'    => $summary,
                                'minor_edit' => $minor,
@@ -1180,7 +1476,14 @@ class Revision implements IDBAccessObject {
                                'parent_id'  => $current->page_latest,
                                'len'        => $current->rev_len,
                                'sha1'       => $current->rev_sha1
-                               ) );
+                       );
+
+                       if ( $wgContentHandlerUseDB ) {
+                               $row[ 'content_model' ] = $current->rev_content_model;
+                               $row[ 'content_format' ] = $current->rev_content_format;
+                       }
+
+                       $revision = new Revision( $row );
                        $revision->setTitle( Title::makeTitle( $current->page_namespace, $current->page_title ) );
                } else {
                        $revision = null;
@@ -1328,4 +1631,4 @@ class Revision implements IDBAccessObject {
                }
                return true;
        }
-}
\ No newline at end of file
+}
index 6358540..8919f10 100644 (file)
@@ -1183,6 +1183,7 @@ class Sanitizer {
         * attribs regex matches.
         *
         * @param $set Array
+        * @throws MWException
         * @return String
         */
        private static function getTagAttributeCallback( $set ) {
index 9bee8a2..24d48bc 100644 (file)
@@ -1536,6 +1536,7 @@ abstract class Skin extends ContextSource {
         *
         * @param $fname String Name of called method
         * @param $args Array Arguments to the method
+        * @throws MWException
         * @return mixed
         */
        function __call( $fname, $args ) {
index c654747..6233228 100644 (file)
@@ -503,6 +503,7 @@ class SkinTemplate extends Skin {
         * By default it is capitalized.
         *
         * @param $name string Language name, e.g. "English" or "español"
+        * @return string
         * @private
         */
        function formatLanguageName( $name ) {
index 2e5e02b..a72c1af 100644 (file)
@@ -105,19 +105,6 @@ class SpecialPage {
                return SpecialPageFactory::resolveAlias( $alias );
        }
 
-       /**
-        * Add a page to the list of valid special pages. This used to be the preferred
-        * method for adding special pages in extensions. It's now suggested that you add
-        * an associative record to $wgSpecialPages. This avoids autoloading SpecialPage.
-        *
-        * @param $page SpecialPage
-        * @deprecated since 1.7, warnings in 1.17, might be removed in 1.20
-        */
-       static function addPage( &$page ) {
-               wfDeprecated( __METHOD__, '1.7' );
-               SpecialPageFactory::getList()->{$page->mName} = $page;
-       }
-
        /**
         * Add a page to a certain display group for Special:SpecialPages
         *
@@ -267,6 +254,7 @@ class SpecialPage {
         *
         * @param $name String
         * @param $subpage String|Bool subpage string, or false to not use a subpage
+        * @throws MWException
         * @return Title object
         */
        public static function getTitleFor( $name, $subpage = false ) {
@@ -363,6 +351,7 @@ class SpecialPage {
         *
         * @param $fName String Name of called method
         * @param $a Array Arguments to the method
+        * @throws MWException
         * @deprecated since 1.17, call parent::__construct()
         */
        public function __call( $fName, $a ) {
@@ -532,9 +521,8 @@ class SpecialPage {
         *   pages?
         */
        public function isRestricted() {
-               global $wgGroupPermissions;
                // DWIM: If all anons can do something, then it is not restricted
-               return $this->mRestriction != '' && empty( $wgGroupPermissions['*'][$this->mRestriction] );
+               return $this->mRestriction != '' && User::groupHasPermission( '*', $this->mRestriction );
        }
 
        /**
@@ -945,8 +933,8 @@ abstract class FormSpecialPage extends SpecialPage {
         * Called from execute() to check if the given user can perform this action.
         * Failures here must throw subclasses of ErrorPageError.
         * @param $user User
+        * @throws UserBlockedError
         * @return Bool true
-        * @throws ErrorPageError
         */
        protected function checkExecutePermissions( User $user ) {
                $this->checkPermissions();
index 256e8f7..13bb361 100644 (file)
@@ -95,7 +95,7 @@ abstract class SqlDataUpdate extends DataUpdate {
         * Abort the database transaction started via beginTransaction (if any).
         */
        public function abortTransaction() {
-               if ( $this->mHasTransaction ) {
+               if ( $this->mHasTransaction ) { //XXX: actually... maybe always?
                        $this->mDb->rollback( get_class( $this ) . '::abortTransaction' );
                        $this->mHasTransaction = false;
                }
index 95c69a2..b0e6c12 100644 (file)
@@ -35,6 +35,7 @@ class StreamFile {
         * @param $fname string Full name and path of the file to stream
         * @param $headers array Any additional headers to send
         * @param $sendErrors bool Send error messages if errors occur (like 404)
+        * @throws MWException
         * @return bool Success
         */
        public static function stream( $fname, $headers = array(), $sendErrors = true ) {
index 615bcb5..cc0adb7 100644 (file)
@@ -108,6 +108,7 @@ class StubObject {
         * @param $name String: name of the method called in this object.
         * @param $level Integer: level to go in the stact trace to get the function
         *               who called this function.
+        * @throws MWException
         */
        function _unstub( $name = '_unstub', $level = 2 ) {
                static $recursionLevel = 0;
index a5844b6..56ce46c 100644 (file)
@@ -196,7 +196,7 @@ class MWTimestamp {
         *
         * @since 1.20
         *
-        * @return string Formatted timestamp
+        * @return Message Formatted timestamp
         */
        public function getHumanTimestamp() {
                $then = $this->getTimestamp( TS_UNIX );
index 3573198..3212f54 100644 (file)
@@ -65,6 +65,7 @@ class Title {
        var $mFragment;                   // /< Title fragment (i.e. the bit after the #)
        var $mArticleID = -1;             // /< Article ID, fetched from the link cache on demand
        var $mLatestID = false;           // /< ID of most recent revision
+       var $mContentModel = false;       // /< ID of the page's content model, i.e. one of the CONTENT_MODEL_XXX constants
        private $mEstimateRevisions;      // /< Estimated number of revisions; null of not loaded
        var $mRestrictions = array();     // /< Array of groups allowed to edit this article
        var $mOldRestrictions = false;
@@ -199,6 +200,27 @@ class Title {
                }
        }
 
+       /**
+        * Returns a list of fields that are to be selected for initializing Title objects or LinkCache entries.
+        * Uses $wgContentHandlerUseDB to determine whether to include page_content_model.
+        *
+        * @return array
+        */
+       protected static function getSelectFields() {
+               global $wgContentHandlerUseDB;
+
+               $fields = array(
+                       'page_namespace', 'page_title', 'page_id',
+                       'page_len', 'page_is_redirect', 'page_latest',
+               );
+
+               if ( $wgContentHandlerUseDB ) {
+                       $fields[] = 'page_content_model';
+               }
+
+               return $fields;
+       }
+
        /**
         * Create a new Title from an article ID
         *
@@ -210,10 +232,7 @@ class Title {
                $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
                $row = $db->selectRow(
                        'page',
-                       array(
-                               'page_namespace', 'page_title', 'page_id',
-                               'page_len', 'page_is_redirect', 'page_latest',
-                       ),
+                       self::getSelectFields(),
                        array( 'page_id' => $id ),
                        __METHOD__
                );
@@ -239,10 +258,7 @@ class Title {
 
                $res = $dbr->select(
                        'page',
-                       array(
-                               'page_namespace', 'page_title', 'page_id',
-                               'page_len', 'page_is_redirect', 'page_latest',
-                       ),
+                       self::getSelectFields(),
                        array( 'page_id' => $ids ),
                        __METHOD__
                );
@@ -282,11 +298,16 @@ class Title {
                                $this->mRedirect = (bool)$row->page_is_redirect;
                        if ( isset( $row->page_latest ) )
                                $this->mLatestID = (int)$row->page_latest;
+                       if ( isset( $row->page_content_model ) )
+                               $this->mContentModel = strval( $row->page_content_model );
+                       else
+                               $this->mContentModel = false; # initialized lazily in getContentModel()
                } else { // page not found
                        $this->mArticleID = 0;
                        $this->mLength = 0;
                        $this->mRedirect = false;
                        $this->mLatestID = 0;
+                       $this->mContentModel = false; # initialized lazily in getContentModel()
                }
        }
 
@@ -312,6 +333,7 @@ class Title {
                $t->mArticleID = ( $ns >= 0 ) ? -1 : 0;
                $t->mUrlform = wfUrlencode( $t->mDbkeyform );
                $t->mTextform = str_replace( '_', ' ', $title );
+               $t->mContentModel = false; # initialized lazily in getContentModel()
                return $t;
        }
 
@@ -362,9 +384,11 @@ class Title {
         *
         * @param $text String: Text with possible redirect
         * @return Title: The corresponding Title
+        * @deprecated since 1.21, use Content::getRedirectTarget instead.
         */
        public static function newFromRedirect( $text ) {
-               return self::newFromRedirectInternal( $text );
+               $content = ContentHandler::makeContent( $text, null, CONTENT_MODEL_WIKITEXT );
+               return $content->getRedirectTarget();
        }
 
        /**
@@ -375,10 +399,11 @@ class Title {
         *
         * @param $text String Text with possible redirect
         * @return Title
+        * @deprecated since 1.21, use Content::getUltimateRedirectTarget instead.
         */
        public static function newFromRedirectRecurse( $text ) {
-               $titles = self::newFromRedirectArray( $text );
-               return $titles ? array_pop( $titles ) : null;
+               $content = ContentHandler::makeContent( $text, null, CONTENT_MODEL_WIKITEXT );
+               return $content->getUltimateRedirectTarget();
        }
 
        /**
@@ -389,71 +414,11 @@ class Title {
         *
         * @param $text String Text with possible redirect
         * @return Array of Titles, with the destination last
+        * @deprecated since 1.21, use Content::getRedirectChain instead.
         */
        public static function newFromRedirectArray( $text ) {
-               global $wgMaxRedirects;
-               $title = self::newFromRedirectInternal( $text );
-               if ( is_null( $title ) ) {
-                       return null;
-               }
-               // recursive check to follow double redirects
-               $recurse = $wgMaxRedirects;
-               $titles = array( $title );
-               while ( --$recurse > 0 ) {
-                       if ( $title->isRedirect() ) {
-                               $page = WikiPage::factory( $title );
-                               $newtitle = $page->getRedirectTarget();
-                       } else {
-                               break;
-                       }
-                       // Redirects to some special pages are not permitted
-                       if ( $newtitle instanceOf Title && $newtitle->isValidRedirectTarget() ) {
-                               // the new title passes the checks, so make that our current title so that further recursion can be checked
-                               $title = $newtitle;
-                               $titles[] = $newtitle;
-                       } else {
-                               break;
-                       }
-               }
-               return $titles;
-       }
-
-       /**
-        * Really extract the redirect destination
-        * Do not call this function directly, use one of the newFromRedirect* functions above
-        *
-        * @param $text String Text with possible redirect
-        * @return Title
-        */
-       protected static function newFromRedirectInternal( $text ) {
-               global $wgMaxRedirects;
-               if ( $wgMaxRedirects < 1 ) {
-                       //redirects are disabled, so quit early
-                       return null;
-               }
-               $redir = MagicWord::get( 'redirect' );
-               $text = trim( $text );
-               if ( $redir->matchStartAndRemove( $text ) ) {
-                       // Extract the first link and see if it's usable
-                       // Ensure that it really does come directly after #REDIRECT
-                       // Some older redirects included a colon, so don't freak about that!
-                       $m = array();
-                       if ( preg_match( '!^\s*:?\s*\[{2}(.*?)(?:\|.*?)?\]{2}!', $text, $m ) ) {
-                               // Strip preceding colon used to "escape" categories, etc.
-                               // and URL-decode links
-                               if ( strpos( $m[1], '%' ) !== false ) {
-                                       // Match behavior of inline link parsing here;
-                                       $m[1] = rawurldecode( ltrim( $m[1], ':' ) );
-                               }
-                               $title = Title::newFromText( $m[1] );
-                               // If the title is a redirect to bad special pages or is invalid, return null
-                               if ( !$title instanceof Title || !$title->isValidRedirectTarget() ) {
-                                       return null;
-                               }
-                               return $title;
-                       }
-               }
-               return null;
+               $content = ContentHandler::makeContent( $text, null, CONTENT_MODEL_WIKITEXT );
+               return $content->getRedirectChain();
        }
 
        /**
@@ -701,6 +666,38 @@ class Title {
                return $this->mNamespace;
        }
 
+       /**
+        * Get the page's content model id, see the CONTENT_MODEL_XXX constants.
+        *
+        * @return String: Content model id
+        */
+       public function getContentModel() {
+               if ( !$this->mContentModel ) {
+                       $linkCache = LinkCache::singleton();
+                       $this->mContentModel = $linkCache->getGoodLinkFieldObj( $this, 'model' );
+               }
+
+               if ( !$this->mContentModel ) {
+                       $this->mContentModel = ContentHandler::getDefaultModelFor( $this );
+               }
+
+               if( !$this->mContentModel ) {
+                       throw new MWException( "failed to determin content model!" );
+               }
+
+               return $this->mContentModel;
+       }
+
+       /**
+        * Convenience method for checking a title's content model name
+        *
+        * @param String $id The content model ID (use the CONTENT_MODEL_XXX constants).
+        * @return Boolean true if $this->getContentModel() == $id
+        */
+       public function hasContentModel( $id ) {
+               return $this->getContentModel() == $id;
+       }
+
        /**
         * Get the namespace text
         *
@@ -934,6 +931,8 @@ class Title {
         * @return Bool
         */
        public function isConversionTable() {
+               //@todo: ConversionTable should become a separate content model.
+
                return $this->getNamespace() == NS_MEDIAWIKI &&
                        strpos( $this->getText(), 'Conversiontable/' ) === 0;
        }
@@ -944,22 +943,31 @@ class Title {
         * @return Bool
         */
        public function isWikitextPage() {
-               $retval = !$this->isCssOrJsPage() && !$this->isCssJsSubpage();
-               wfRunHooks( 'TitleIsWikitextPage', array( $this, &$retval ) );
-               return $retval;
+               return $this->hasContentModel( CONTENT_MODEL_WIKITEXT );
        }
 
        /**
-        * Could this page contain custom CSS or JavaScript, based
-        * on the title?
+        * Could this page contain custom CSS or JavaScript for the global UI.
+        * This is generally true for pages in the MediaWiki namespace having CONTENT_MODEL_CSS
+        * or CONTENT_MODEL_JAVASCRIPT.
+        *
+        * This method does *not* return true for per-user JS/CSS. Use isCssJsSubpage() for that!
+        *
+        * Note that this method should not return true for pages that contain and show "inactive" CSS or JS.
         *
         * @return Bool
         */
        public function isCssOrJsPage() {
-               $retval = $this->mNamespace == NS_MEDIAWIKI
-                       && preg_match( '!\.(?:css|js)$!u', $this->mTextform ) > 0;
-               wfRunHooks( 'TitleIsCssOrJsPage', array( $this, &$retval ) );
-               return $retval;
+               $isCssOrJsPage = NS_MEDIAWIKI == $this->mNamespace
+                       && ( $this->hasContentModel( CONTENT_MODEL_CSS )
+                               || $this->hasContentModel( CONTENT_MODEL_JAVASCRIPT ) );
+
+               #NOTE: this hook is also called in ContentHandler::getDefaultModel. It's called here again to make sure
+               #      hook funktions can force this method to return true even outside the mediawiki namespace.
+
+               wfRunHooks( 'TitleIsCssOrJsPage', array( $this, &$isCssOrJsPage ) );
+
+               return $isCssOrJsPage;
        }
 
        /**
@@ -967,7 +975,9 @@ class Title {
         * @return Bool
         */
        public function isCssJsSubpage() {
-               return ( NS_USER == $this->mNamespace and preg_match( "/\\/.*\\.(?:css|js)$/", $this->mTextform ) );
+               return ( NS_USER == $this->mNamespace && $this->isSubpage()
+                               && ( $this->hasContentModel( CONTENT_MODEL_CSS )
+                                       || $this->hasContentModel( CONTENT_MODEL_JAVASCRIPT ) ) );
        }
 
        /**
@@ -990,7 +1000,8 @@ class Title {
         * @return Bool
         */
        public function isCssSubpage() {
-               return ( NS_USER == $this->mNamespace && preg_match( "/\\/.*\\.css$/", $this->mTextform ) );
+               return ( NS_USER == $this->mNamespace && $this->isSubpage()
+                       && $this->hasContentModel( CONTENT_MODEL_CSS ) );
        }
 
        /**
@@ -999,7 +1010,8 @@ class Title {
         * @return Bool
         */
        public function isJsSubpage() {
-               return ( NS_USER == $this->mNamespace && preg_match( "/\\/.*\\.js$/", $this->mTextform ) );
+               return ( NS_USER == $this->mNamespace && $this->isSubpage()
+                       && $this->hasContentModel( CONTENT_MODEL_JAVASCRIPT ) );
        }
 
        /**
@@ -1723,15 +1735,8 @@ class Title {
 
                        if ( !$user->isAllowed( 'move' ) ) {
                                // User can't move anything
-                               global $wgGroupPermissions;
-                               $userCanMove = false;
-                               if ( isset( $wgGroupPermissions['user']['move'] ) ) {
-                                       $userCanMove = $wgGroupPermissions['user']['move'];
-                               }
-                               $autoconfirmedCanMove = false;
-                               if ( isset( $wgGroupPermissions['autoconfirmed']['move'] ) ) {
-                                       $autoconfirmedCanMove = $wgGroupPermissions['autoconfirmed']['move'];
-                               }
+                               $userCanMove = User::groupHasPermission( 'user', 'move' );
+                               $autoconfirmedCanMove = User::groupHasPermission( 'autoconfirmed', 'move' );
                                if ( $user->isAnon() && ( $userCanMove || $autoconfirmedCanMove ) ) {
                                        // custom message if logged-in users without any special rights can move
                                        $errors[] = array( 'movenologintext' );
@@ -2072,13 +2077,13 @@ class Title {
         * @return Array list of errors
         */
        private function checkReadPermissions( $action, $user, $errors, $doExpensiveQueries, $short ) {
-               global $wgWhitelistRead, $wgGroupPermissions, $wgRevokePermissions;
+               global $wgWhitelistRead, $wgRevokePermissions;
                static $useShortcut = null;
 
                # Initialize the $useShortcut boolean, to determine if we can skip quite a bit of code below
                if ( is_null( $useShortcut ) ) {
                        $useShortcut = true;
-                       if ( empty( $wgGroupPermissions['*']['read'] ) ) {
+                       if ( !User::groupHasPermission( '*', 'read' ) ) {
                                # Not a public wiki, so no shortcut
                                $useShortcut = false;
                        } elseif ( !empty( $wgRevokePermissions ) ) {
@@ -2908,8 +2913,16 @@ class Title {
                if ( !$this->getArticleID( $flags ) ) {
                        return $this->mRedirect = false;
                }
+
                $linkCache = LinkCache::singleton();
-               $this->mRedirect = (bool)$linkCache->getGoodLinkFieldObj( $this, 'redirect' );
+               $cached = $linkCache->getGoodLinkFieldObj( $this, 'redirect' );
+               if ( $cached === null ) { # check the assumption that the cache actually knows about this title
+                       # XXX: this does apparently happen, see https://bugzilla.wikimedia.org/show_bug.cgi?id=37209
+                       #      as a stop gap, perhaps log this, but don't throw an exception?
+                       throw new MWException( "LinkCache doesn't currently know about this title: " . $this->getPrefixedDBkey() );
+               }
+
+               $this->mRedirect = (bool)$cached;
 
                return $this->mRedirect;
        }
@@ -2930,7 +2943,14 @@ class Title {
                        return $this->mLength = 0;
                }
                $linkCache = LinkCache::singleton();
-               $this->mLength = intval( $linkCache->getGoodLinkFieldObj( $this, 'length' ) );
+               $cached = $linkCache->getGoodLinkFieldObj( $this, 'length' );
+               if ( $cached === null ) { # check the assumption that the cache actually knows about this title
+                       # XXX: this does apparently happen, see https://bugzilla.wikimedia.org/show_bug.cgi?id=37209
+                       #      as a stop gap, perhaps log this, but don't throw an exception?
+                       throw new MWException( "LinkCache doesn't currently know about this title: " . $this->getPrefixedDBkey() );
+               }
+
+               $this->mLength = intval( $cached );
 
                return $this->mLength;
        }
@@ -2950,7 +2970,14 @@ class Title {
                        return $this->mLatestID = 0;
                }
                $linkCache = LinkCache::singleton();
-               $this->mLatestID = intval( $linkCache->getGoodLinkFieldObj( $this, 'revision' ) );
+               $cached = $linkCache->getGoodLinkFieldObj( $this, 'revision' );
+               if ( $cached === null ) { # check the assumption that the cache actually knows about this title
+                       # XXX: this does apparently happen, see https://bugzilla.wikimedia.org/show_bug.cgi?id=37209
+                       #      as a stop gap, perhaps log this, but don't throw an exception?
+                       throw new MWException( "LinkCache doesn't currently know about this title: " . $this->getPrefixedDBkey() );
+               }
+
+               $this->mLatestID = intval( $cached );
 
                return $this->mLatestID;
        }
@@ -2979,6 +3006,7 @@ class Title {
                $this->mRedirect = null;
                $this->mLength = -1;
                $this->mLatestID = false;
+               $this->mContentModel = false;
                $this->mEstimateRevisions = null;
        }
 
@@ -3217,7 +3245,7 @@ class Title {
 
                $res = $db->select(
                        array( 'page', $table ),
-                       array( 'page_namespace', 'page_title', 'page_id', 'page_len', 'page_is_redirect', 'page_latest' ),
+                       self::getSelectFields(),
                        array(
                                "{$prefix}_from=page_id",
                                "{$prefix}_namespace" => $this->getNamespace(),
@@ -3267,6 +3295,8 @@ class Title {
         * @return Array of Title objects linking here
         */
        public function getLinksFrom( $options = array(), $table = 'pagelinks', $prefix = 'pl' ) {
+               global $wgContentHandlerUseDB;
+
                $id = $this->getArticleID();
 
                # If the page doesn't exist; there can't be any link from this page
@@ -3283,9 +3313,12 @@ class Title {
                $namespaceFiled = "{$prefix}_namespace";
                $titleField = "{$prefix}_title";
 
+               $fields = array( $namespaceFiled, $titleField, 'page_id', 'page_len', 'page_is_redirect', 'page_latest' );
+               if ( $wgContentHandlerUseDB ) $fields[] = 'page_content_model';
+
                $res = $db->select(
                        array( $table, 'page' ),
-                       array( $namespaceFiled, $titleField, 'page_id', 'page_len', 'page_is_redirect', 'page_latest' ),
+                       $fields,
                        array( "{$prefix}_from" => $id ),
                        __METHOD__,
                        $options,
@@ -3417,7 +3450,7 @@ class Title {
         * @return Mixed True on success, getUserPermissionsErrors()-like array on failure
         */
        public function isValidMoveOperation( &$nt, $auth = true, $reason = '' ) {
-               global $wgUser;
+               global $wgUser, $wgContentHandlerUseDB;
 
                $errors = array();
                if ( !$nt ) {
@@ -3450,6 +3483,15 @@ class Title {
                        $errors[] = array( 'badarticleerror' );
                }
 
+               // Content model checks
+               if ( !$wgContentHandlerUseDB &&
+                               $this->getContentModel() !== $nt->getContentModel() ) {
+                       // can't move a page if that would change the page's content model
+                       $errors[] = array( 'bad-target-model',
+                                                       ContentHandler::getLocalizedName( $this->getContentModel() ),
+                                                       ContentHandler::getLocalizedName( $nt->getContentModel() ) );
+               }
+
                // Image-specific checks
                if ( $this->getNamespace() == NS_FILE ) {
                        $errors = array_merge( $errors, $this->validateFileMoveOperation( $nt ) );
@@ -3676,7 +3718,14 @@ class Title {
                        $logType = 'move';
                }
 
-               $redirectSuppressed = !$createRedirect;
+               if ( $createRedirect ) {
+                       $contentHandler = ContentHandler::getForTitle( $this );
+                       $redirectContent = $contentHandler->makeRedirectContent( $nt );
+
+                       // NOTE: If this page's content model does not support redirects, $redirectContent will be null.
+               } else {
+                       $redirectContent = null;
+               }
 
                $logEntry = new ManualLogEntry( 'move', $logType );
                $logEntry->setPerformer( $wgUser );
@@ -3684,7 +3733,7 @@ class Title {
                $logEntry->setComment( $reason );
                $logEntry->setParameters( array(
                        '4::target' => $nt->getPrefixedText(),
-                       '5::noredir' => $redirectSuppressed ? '1': '0',
+                       '5::noredir' => $redirectContent ? '0': '1',
                ) );
 
                $formatter = LogFormatter::newFromEntry( $logEntry );
@@ -3719,7 +3768,8 @@ class Title {
                if ( !is_object( $nullRevision ) ) {
                        throw new MWException( 'No valid null revision produced in ' . __METHOD__ );
                }
-               $nullRevId = $nullRevision->insertOn( $dbw );
+
+               $nullRevision->insertOn( $dbw );
 
                # Change the name of the target page:
                $dbw->update( 'page',
@@ -3746,18 +3796,16 @@ class Title {
                }
 
                # Recreate the redirect, this time in the other direction.
-               if ( $redirectSuppressed ) {
+               if ( !$redirectContent ) {
                        WikiPage::onArticleDelete( $this );
                } else {
-                       $mwRedir = MagicWord::get( 'redirect' );
-                       $redirectText = $mwRedir->getSynonym( 0 ) . ' [[' . $nt->getPrefixedText() . "]]\n";
                        $redirectArticle = WikiPage::factory( $this );
                        $newid = $redirectArticle->insertOn( $dbw );
                        if ( $newid ) { // sanity
                                $redirectRevision = new Revision( array(
                                        'page'    => $newid,
                                        'comment' => $comment,
-                                       'text'    => $redirectText ) );
+                                       'content'    => $redirectContent ) );
                                $redirectRevision->insertOn( $dbw );
                                $redirectArticle->updateRevisionOn( $dbw, $redirectRevision, 0 );
 
@@ -3853,10 +3901,16 @@ class Title {
         * @return Bool
         */
        public function isSingleRevRedirect() {
+               global $wgContentHandlerUseDB;
+
                $dbw = wfGetDB( DB_MASTER );
+
                # Is it a redirect?
+               $fields = array( 'page_is_redirect', 'page_latest', 'page_id' );
+               if ( $wgContentHandlerUseDB ) $fields[] = 'page_content_model';
+
                $row = $dbw->selectRow( 'page',
-                       array( 'page_is_redirect', 'page_latest', 'page_id' ),
+                       $fields,
                        $this->pageCond(),
                        __METHOD__,
                        array( 'FOR UPDATE' )
@@ -3865,6 +3919,7 @@ class Title {
                $this->mArticleID = $row ? intval( $row->page_id ) : 0;
                $this->mRedirect = $row ? (bool)$row->page_is_redirect : false;
                $this->mLatestID = $row ? intval( $row->page_latest ) : false;
+               $this->mContentModel = $row && isset( $row->page_content_model ) ? strval( $row->page_content_model ) : false;
                if ( !$this->mRedirect ) {
                        return false;
                }
@@ -3909,24 +3964,25 @@ class Title {
                if( !is_object( $rev ) ){
                        return false;
                }
-               $text = $rev->getText();
+               $content = $rev->getContent();
                # Does the redirect point to the source?
                # Or is it a broken self-redirect, usually caused by namespace collisions?
-               $m = array();
-               if ( preg_match( "/\\[\\[\\s*([^\\]\\|]*)]]/", $text, $m ) ) {
-                       $redirTitle = Title::newFromText( $m[1] );
-                       if ( !is_object( $redirTitle ) ||
-                               ( $redirTitle->getPrefixedDBkey() != $this->getPrefixedDBkey() &&
-                               $redirTitle->getPrefixedDBkey() != $nt->getPrefixedDBkey() ) ) {
+               $redirTitle = $content->getRedirectTarget();
+
+               if ( $redirTitle ) {
+                       if ( $redirTitle->getPrefixedDBkey() != $this->getPrefixedDBkey() &&
+                               $redirTitle->getPrefixedDBkey() != $nt->getPrefixedDBkey() ) {
                                wfDebug( __METHOD__ . ": redirect points to other page\n" );
                                return false;
+                       } else {
+                               return true;
                        }
                } else {
-                       # Fail safe
-                       wfDebug( __METHOD__ . ": failsafe\n" );
+                       # Fail safe (not a redirect after all. strange.)
+                       wfDebug( __METHOD__ . ": failsafe: database sais " . $nt->getPrefixedDBkey() .
+                                               " is a redirect, but it doesn't contain a valid redirect.\n" );
                        return false;
                }
-               return true;
        }
 
        /**
@@ -4625,17 +4681,13 @@ class Title {
                if ( $this->isSpecialPage() ) {
                        // special pages are in the user language
                        return $wgLang;
-               } elseif ( $this->isCssOrJsPage() || $this->isCssJsSubpage() ) {
-                       // css/js should always be LTR and is, in fact, English
-                       return wfGetLangObj( 'en' );
-               } elseif ( $this->getNamespace() == NS_MEDIAWIKI ) {
-                       // Parse mediawiki messages with correct target language
-                       list( /* $unused */, $lang ) = MessageCache::singleton()->figureMessage( $this->getText() );
-                       return wfGetLangObj( $lang );
                }
-               global $wgContLang;
-               // If nothing special, it should be in the wiki content language
-               $pageLang = $wgContLang;
+
+               //TODO: use the LinkCache to cache this! Note that this may depend on user settings, so the cache should be only per-request.
+               //NOTE: ContentHandler::getPageLanguage() may need to load the content to determine the page language!
+               $contentHandler = ContentHandler::getForTitle( $this );
+               $pageLang = $contentHandler->getPageLanguage( $this );
+
                // Hook at the end because we don't want to override the above stuff
                wfRunHooks( 'PageContentLanguage', array( $this, &$pageLang, $wgLang ) );
                return wfGetLangObj( $pageLang );
@@ -4650,19 +4702,23 @@ class Title {
         * @return Language
         */
        public function getPageViewLanguage() {
-               $pageLang = $this->getPageLanguage();
-               // If this is nothing special (so the content is converted when viewed)
-               if ( !$this->isSpecialPage()
-                       && !$this->isCssOrJsPage() && !$this->isCssJsSubpage()
-                       && $this->getNamespace() !== NS_MEDIAWIKI
-               ) {
+               global $wgLang;
+
+               if ( $this->isSpecialPage() ) {
                        // If the user chooses a variant, the content is actually
                        // in a language whose code is the variant code.
-                       $variant = $pageLang->getPreferredVariant();
-                       if ( $pageLang->getCode() !== $variant ) {
-                               $pageLang = Language::factory( $variant );
+                       $variant = $wgLang->getPreferredVariant();
+                       if ( $wgLang->getCode() !== $variant ) {
+                               return Language::factory( $variant );
                        }
+
+                       return $wgLang;
                }
+
+               //NOTE: can't be cached persistently, depends on user settings
+               //NOTE: ContentHandler::getPageViewLanguage() may need to load the content to determine the page language!
+               $contentHandler = ContentHandler::getForTitle( $this );
+               $pageLang = $contentHandler->getPageViewLanguage( $this );
                return $pageLang;
        }
 }
index 4ab90ed..93a6835 100644 (file)
@@ -765,6 +765,7 @@ class User {
         *                - 'usable'     Valid for batch processes and login
         *                - 'creatable'  Valid for batch processes, login and account creation
         *
+        * @throws MWException
         * @return bool|string
         */
        public static function getCanonicalName( $name, $validate = 'valid' ) {
@@ -3640,14 +3641,27 @@ class User {
        public static function getGroupsWithPermission( $role ) {
                global $wgGroupPermissions;
                $allowedGroups = array();
-               foreach ( $wgGroupPermissions as $group => $rights ) {
-                       if ( isset( $rights[$role] ) && $rights[$role] ) {
+               foreach ( array_keys( $wgGroupPermissions ) as $group ) {
+                       if ( self::groupHasPermission( $group, $role ) ) {
                                $allowedGroups[] = $group;
                        }
                }
                return $allowedGroups;
        }
 
+       /**
+        * Check, if the given group has the given permission
+        *
+        * @param $group String Group to check
+        * @param $role String Role to check
+        * @return bool
+        */
+       public static function groupHasPermission( $group, $role ) {
+               global $wgGroupPermissions, $wgRevokePermissions;
+               return isset( $wgGroupPermissions[$group][$role] ) && $wgGroupPermissions[$group][$role]
+                       && !( isset( $wgRevokePermissions[$group][$role] ) && $wgRevokePermissions[$group][$role] );
+       }
+
        /**
         * Get the localized descriptive name for a group, if it exists
         *
index 5d5ed85..9830f69 100644 (file)
@@ -152,6 +152,7 @@ class UserMailer {
         * @param $body String: email's text.
         * @param $replyto MailAddress: optional reply-to email (default: null).
         * @param $contentType String: optional custom Content-Type (default: text/plain; charset=UTF-8)
+        * @throws MWException
         * @return Status object
         */
        public static function send( $to, $from, $subject, $body, $replyto = null, $contentType = 'text/plain; charset=UTF-8' ) {
index 2cc6338..80fb81a 100644 (file)
@@ -620,6 +620,7 @@ class WebRequest {
         * Return the path and query string portion of the request URI.
         * This will be suitable for use as a relative link in HTML output.
         *
+        * @throws MWException
         * @return String
         */
        public function getRequestURL() {
@@ -907,6 +908,7 @@ class WebRequest {
         * false if an error message has been shown and the request should be aborted.
         *
         * @param $extWhitelist array
+        * @throws HttpError
         * @return bool
         */
        public function checkUrlExtension( $extWhitelist = array() ) {
@@ -1056,9 +1058,10 @@ HTML;
        /**
         * Work out the IP address based on various globals
         * For trusted proxies, use the XFF client IP (first of the chain)
-        * 
+        *
         * @since 1.19
         *
+        * @throws MWException
         * @return string
         */
        public function getIP() {
@@ -1238,6 +1241,7 @@ class FauxRequest extends WebRequest {
         *   fake GET/POST values
         * @param $wasPosted Bool: whether to treat the data as POST
         * @param $session Mixed: session array or null
+        * @throws MWException
         */
        public function __construct( $data = array(), $wasPosted = false, $session = null ) {
                if( is_array( $data ) ) {
index e1d84d4..e6ccbe5 100644 (file)
@@ -169,6 +169,7 @@ class MediaWiki {
         * - special pages
         * - normal pages
         *
+        * @throws MWException|PermissionsError|BadTitleError|HttpError
         * @return void
         */
        private function performRequest() {
index 9fb1522..0114cce 100644 (file)
@@ -41,7 +41,9 @@ class WikiFilePage extends WikiPage {
        }
 
        public function getActionOverrides() {
-               return array( 'revert' => 'RevertFileAction' );
+               $overrides = parent::getActionOverrides();
+               $overrides[ 'revert' ] = 'RevertFileAction';
+               return $overrides;
        }
 
        /**
@@ -103,13 +105,12 @@ class WikiFilePage extends WikiPage {
        }
 
        /**
-        * @param bool $text
         * @return bool
         */
-       public function isRedirect( $text = false ) {
+       public function isRedirect( ) {
                $this->loadFile();
                if ( $this->mFile->isLocal() ) {
-                       return parent::isRedirect( $text );
+                       return parent::isRedirect();
                }
 
                return (bool)$this->mFile->getRedirected();
index 5a3a9e6..770c37a 100644 (file)
@@ -187,7 +187,21 @@ class WikiPage extends Page implements IDBAccessObject {
         * @return Array
         */
        public function getActionOverrides() {
-               return array();
+               $content_handler = $this->getContentHandler();
+               return $content_handler->getActionOverrides();
+       }
+
+       /**
+        * Returns the ContentHandler instance to be used to deal with the content of this WikiPage.
+        *
+        * Shorthand for ContentHandler::getForModelID( $this->getContentModel() );
+        *
+        * @return ContentHandler
+        *
+        * @since 1.21
+        */
+       public function getContentHandler() {
+               return ContentHandler::getForModelID( $this->getContentModel() );
        }
 
        /**
@@ -231,7 +245,9 @@ class WikiPage extends Page implements IDBAccessObject {
         * @return array
         */
        public static function selectFields() {
-               return array(
+               global $wgContentHandlerUseDB;
+
+               $fields = array(
                        'page_id',
                        'page_namespace',
                        'page_title',
@@ -244,6 +260,12 @@ class WikiPage extends Page implements IDBAccessObject {
                        'page_latest',
                        'page_len',
                );
+
+               if ( $wgContentHandlerUseDB ) {
+                       $fields[] = 'page_content_model';
+               }
+
+               return $fields;
        }
 
        /**
@@ -418,21 +440,42 @@ class WikiPage extends Page implements IDBAccessObject {
        }
 
        /**
-        * Tests if the article text represents a redirect
+        * Tests if the article content represents a redirect
         *
-        * @param $text mixed string containing article contents, or boolean
         * @return bool
         */
-       public function isRedirect( $text = false ) {
-               if ( $text === false ) {
-                       if ( !$this->mDataLoaded ) {
-                               $this->loadPageData();
-                       }
+       public function isRedirect( ) {
+               $content = $this->getContent();
+               if ( !$content ) return false;
 
-                       return (bool)$this->mIsRedirect;
-               } else {
-                       return Title::newFromRedirect( $text ) !== null;
+               return $content->isRedirect();
+       }
+
+       /**
+        * Returns the page's content model id (see the CONTENT_MODEL_XXX constants).
+        *
+        * Will use the revisions actual content model if the page exists,
+        * and the page's default if the page doesn't exist yet.
+        *
+        * @return String
+        *
+        * @since 1.21
+        */
+       public function getContentModel() {
+               if ( $this->exists() ) {
+                       # look at the revision's actual content model
+                       $rev = $this->getRevision();
+
+                       if ( $rev !== null ) {
+                               return $rev->getContentModel();
+                       } else {
+                               $title = $this->mTitle->getPrefixedDBkey();
+                               wfWarn( "Page $title exists but has no (visible) revisions!" );
+                       }
                }
+
+               # use the default model for this page
+               return $this->mTitle->getContentModel();
        }
 
        /**
@@ -554,6 +597,27 @@ class WikiPage extends Page implements IDBAccessObject {
                return null;
        }
 
+       /**
+        * Get the content of the current revision. No side-effects...
+        *
+        * @param $audience Integer: one of:
+        *      Revision::FOR_PUBLIC       to be displayed to all users
+        *      Revision::FOR_THIS_USER    to be displayed to $wgUser
+        *      Revision::RAW              get the text regardless of permissions
+        * @param $user User object to check for, only if FOR_THIS_USER is passed
+        *              to the $audience parameter
+        * @return Content|null The content of the current revision
+        *
+        * @since 1.21
+        */
+       public function getContent( $audience = Revision::FOR_PUBLIC, User $user = null ) {
+               $this->loadLastEdit();
+               if ( $this->mLastRevision ) {
+                       return $this->mLastRevision->getContent( $audience );
+               }
+               return null;
+       }
+
        /**
         * Get the text of the current revision. No side-effects...
         *
@@ -563,9 +627,11 @@ class WikiPage extends Page implements IDBAccessObject {
         *      Revision::RAW              get the text regardless of permissions
         * @param $user User object to check for, only if FOR_THIS_USER is passed
         *              to the $audience parameter
-        * @return String|bool The text of the current revision. False on failure
+        * @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 ) {
+       public function getText( $audience = Revision::FOR_PUBLIC, User $user = null ) { #@todo: deprecated, replace usage!
+               wfDeprecated( __METHOD__, '1.21' );
                $this->loadLastEdit();
                if ( $this->mLastRevision ) {
                        return $this->mLastRevision->getText( $audience, $user );
@@ -577,13 +643,12 @@ class WikiPage extends Page implements IDBAccessObject {
         * Get the text of the current revision. No side-effects...
         *
         * @return String|bool The text of the current revision. False on failure
+        * @deprecated as of 1.21, getContent() should be used instead.
         */
        public function getRawText() {
-               $this->loadLastEdit();
-               if ( $this->mLastRevision ) {
-                       return $this->mLastRevision->getRawText();
-               }
-               return false;
+               wfDeprecated( __METHOD__, '1.21' );
+
+               return $this->getText( Revision::RAW );
        }
 
        /**
@@ -733,32 +798,34 @@ class WikiPage extends Page implements IDBAccessObject {
                        return false;
                }
 
-               $text = $editInfo ? $editInfo->pst : false;
+               if ( $editInfo ) {
+                       $content = $editInfo->pstContent;
+               } else {
+                       $content = $this->getContent();
+               }
 
-               if ( $this->isRedirect( $text ) ) {
+               if ( !$content || $content->isRedirect( ) ) {
                        return false;
                }
 
-               switch ( $wgArticleCountMethod ) {
-               case 'any':
-                       return true;
-               case 'comma':
-                       if ( $text === false ) {
-                               $text = $this->getRawText();
-                       }
-                       return strpos( $text,  ',' ) !== false;
-               case 'link':
+               $hasLinks = null;
+
+               if ( $wgArticleCountMethod === 'link' ) {
+                       # nasty special case to avoid re-parsing to detect links
+
                        if ( $editInfo ) {
                                // ParserOutput::getLinks() is a 2D array of page links, so
                                // to be really correct we would need to recurse in the array
                                // but the main array should only have items in it if there are
                                // links.
-                               return (bool)count( $editInfo->output->getLinks() );
+                               $hasLinks = (bool)count( $editInfo->output->getLinks() );
                        } else {
-                               return (bool)wfGetDB( DB_SLAVE )->selectField( 'pagelinks', 1,
+                               $hasLinks = (bool)wfGetDB( DB_SLAVE )->selectField( 'pagelinks', 1,
                                        array( 'pl_from' => $this->getId() ), __METHOD__ );
                        }
                }
+
+               return $content->isCountable( $hasLinks );
        }
 
        /**
@@ -804,7 +871,8 @@ class WikiPage extends Page implements IDBAccessObject {
         */
        public function insertRedirect() {
                // recurse through to only get the final target
-               $retval = Title::newFromRedirectRecurse( $this->getRawText() );
+               $content = $this->getContent();
+               $retval = $content ? $content->getUltimateRedirectTarget() : null;
                if ( !$retval ) {
                        return null;
                }
@@ -1000,7 +1068,7 @@ class WikiPage extends Page implements IDBAccessObject {
                        && $parserOptions->getStubThreshold() == 0
                        && $this->mTitle->exists()
                        && ( $oldid === null || $oldid === 0 || $oldid === $this->getLatest() )
-                       && $this->mTitle->isWikitextPage();
+                       && $this->getContentHandler()->isParserCacheSupported();
        }
 
        /**
@@ -1011,6 +1079,7 @@ class WikiPage extends Page implements IDBAccessObject {
         * @param $parserOptions ParserOptions to use for the parse operation
         * @param $oldid Revision ID to get the text from, passing null or 0 will
         *               get the current revision (default value)
+        *
         * @return ParserOutput or false if the revision was not found
         */
        public function getParserOutput( ParserOptions $parserOptions, $oldid = null ) {
@@ -1088,8 +1157,16 @@ class WikiPage extends Page implements IDBAccessObject {
                }
 
                if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
+                       //@todo: move this logic to MessageCache
+
                        if ( $this->mTitle->exists() ) {
-                               $text = $this->getRawText();
+                               // NOTE: use transclusion text for messages.
+                               //       This is consistent with  MessageCache::getMsgFromNamespace()
+
+                               $content = $this->getContent();
+                               $text = $content === null ? null : $content->getWikitextForTransclusion();
+
+                               if ( $text === null ) $text = false;
                        } else {
                                $text = false;
                        }
@@ -1154,11 +1231,13 @@ class WikiPage extends Page implements IDBAccessObject {
         * @private
         */
        public function updateRevisionOn( $dbw, $revision, $lastRevision = null, $lastRevIsRedirect = null ) {
+               global $wgContentHandlerUseDB;
+
                wfProfileIn( __METHOD__ );
 
-               $text = $revision->getText();
-               $len = strlen( $text );
-               $rt = Title::newFromRedirectRecurse( $text );
+               $content = $revision->getContent();
+               $len = $content->getSize();
+               $rt = $content->getUltimateRedirectTarget();
 
                $conditions = array( 'page_id' => $this->getId() );
 
@@ -1168,14 +1247,20 @@ class WikiPage extends Page implements IDBAccessObject {
                }
 
                $now = wfTimestampNow();
+               $row = array( /* SET */
+                       'page_latest'      => $revision->getId(),
+                       'page_touched'     => $dbw->timestamp( $now ),
+                       'page_is_new'      => ( $lastRevision === 0 ) ? 1 : 0,
+                       'page_is_redirect' => $rt !== null ? 1 : 0,
+                       'page_len'         => $len,
+               );
+
+               if ( $wgContentHandlerUseDB ) {
+                       $row[ 'page_content_model' ] = $revision->getContentModel();
+               }
+
                $dbw->update( 'page',
-                       array( /* SET */
-                               'page_latest'      => $revision->getId(),
-                               'page_touched'     => $dbw->timestamp( $now ),
-                               'page_is_new'      => ( $lastRevision === 0 ) ? 1 : 0,
-                               'page_is_redirect' => $rt !== null ? 1 : 0,
-                               'page_len'         => $len,
-                       ),
+                       $row,
                        $conditions,
                        __METHOD__ );
 
@@ -1187,7 +1272,8 @@ class WikiPage extends Page implements IDBAccessObject {
                        $this->mLatest = $revision->getId();
                        $this->mIsRedirect = (bool)$rt;
                        # Update the LinkCache.
-                       LinkCache::singleton()->addGoodLinkObj( $this->getId(), $this->mTitle, $len, $this->mIsRedirect, $this->mLatest );
+                       LinkCache::singleton()->addGoodLinkObj( $this->getId(), $this->mTitle, $len, $this->mIsRedirect,
+                                                                                                       $this->mLatest, $revision->getContentModel() );
                }
 
                wfProfileOut( __METHOD__ );
@@ -1270,6 +1356,21 @@ class WikiPage extends Page implements IDBAccessObject {
                return $ret;
        }
 
+       /**
+        * Get the content that needs to be saved in order to undo all revisions
+        * between $undo and $undoafter. Revisions must belong to the same page,
+        * must exist and must not be deleted
+        * @param $undo Revision
+        * @param $undoafter Revision Must be an earlier revision than $undo
+        * @return mixed string on success, false on failure
+        * @since 1.21
+        * Before we had the Content object, this was done in getUndoText
+        */
+       public function getUndoContent( Revision $undo, Revision $undoafter = null ) {
+               $handler = $undo->getContentHandler();
+               return $handler->getUndoContent( $this->getRevision(), $undo, $undoafter );
+       }
+
        /**
         * Get the text that needs to be saved in order to undo all revisions
         * between $undo and $undoafter. Revisions must belong to the same page,
@@ -1277,27 +1378,29 @@ class WikiPage extends Page implements IDBAccessObject {
         * @param $undo Revision
         * @param $undoafter Revision Must be an earlier revision than $undo
         * @return mixed string on success, false on failure
+        * @deprecated since 1.21: use ContentHandler::getUndoContent() instead.
         */
        public function getUndoText( Revision $undo, Revision $undoafter = null ) {
-               $cur_text = $this->getRawText();
-               if ( $cur_text === false ) {
-                       return false; // no page
-               }
-               $undo_text = $undo->getText();
-               $undoafter_text = $undoafter->getText();
+               wfDeprecated( __METHOD__, '1.21' );
 
-               if ( $cur_text == $undo_text ) {
-                       # No use doing a merge if it's just a straight revert.
-                       return $undoafter_text;
-               }
+               $this->loadLastEdit();
 
-               $undone_text = '';
+               if ( $this->mLastRevision ) {
+                       if ( is_null( $undoafter ) ) {
+                               $undoafter = $undo->getPrevious();
+                       }
 
-               if ( !wfMerge( $undo_text, $undoafter_text, $cur_text, $undone_text ) ) {
-                       return false;
+                       $handler = $this->getContentHandler();
+                       $undone = $handler->getUndoContent( $this->mLastRevision, $undo, $undoafter );
+
+                       if ( !$undone ) {
+                               return false;
+                       } else {
+                               return ContentHandler::getContentText( $undone );
+                       }
                }
 
-               return $undone_text;
+               return false;
        }
 
        /**
@@ -1305,18 +1408,67 @@ class WikiPage extends Page implements IDBAccessObject {
         * @param $text String: new text of the section
         * @param $sectionTitle String: new section's subject, only if $section is 'new'
         * @param $edittime String: revision timestamp or null to use the current revision
-        * @return string Complete article text, or null if error
+        * @return String new complete article text, or null if error
+        *
+        * @deprecated since 1.21, use replaceSectionContent() instead
         */
        public function replaceSection( $section, $text, $sectionTitle = '', $edittime = null ) {
+               wfDeprecated( __METHOD__, '1.21' );
+
+               if ( strval( $section ) == '' ) { //NOTE: keep condition in sync with condition in replaceSectionContent!
+                       // Whole-page edit; let the whole text through
+                       return $text;
+               }
+
+               if ( !$this->supportsSections() ) {
+                       throw new MWException( "sections not supported for content model " . $this->getContentHandler()->getModelID() );
+               }
+
+               # could even make section title, but that's not required.
+               $sectionContent = ContentHandler::makeContent( $text, $this->getTitle() );
+
+               $newContent = $this->replaceSectionContent( $section, $sectionContent, $sectionTitle, $edittime );
+
+               return ContentHandler::getContentText( $newContent );
+       }
+
+       /**
+        * Returns true iff this page's content model supports sections.
+        *
+        * @return boolean whether sections are supported.
+        *
+        * @todo: the skin should check this and not offer section functionality if sections are not supported.
+        * @todo: the EditPage should check this and not offer section functionality if sections are not supported.
+        */
+       public function supportsSections() {
+               return $this->getContentHandler()->supportsSections();
+       }
+
+       /**
+        * @param $section null|bool|int or a section number (0, 1, 2, T1, T2...)
+        * @param $content Content: new content of the section
+        * @param $sectionTitle String: new section's subject, only if $section is 'new'
+        * @param $edittime String: revision timestamp or null to use the current revision
+        *
+        * @return Content new complete article content, or null if error
+        *
+        * @since 1.21
+        */
+       public function replaceSectionContent( $section, Content $sectionContent, $sectionTitle = '', $edittime = null ) {
                wfProfileIn( __METHOD__ );
 
                if ( strval( $section ) == '' ) {
                        // Whole-page edit; let the whole text through
+                       $newContent = $sectionContent;
                } else {
+                       if ( !$this->supportsSections() ) {
+                               throw new MWException( "sections not supported for content model " . $this->getContentHandler()->getModelID() );
+                       }
+
                        // Bug 30711: always use current version when adding a new section
                        if ( is_null( $edittime ) || $section == 'new' ) {
-                               $oldtext = $this->getRawText();
-                               if ( $oldtext === false ) {
+                               $oldContent = $this->getContent();
+                               if ( ! $oldContent ) {
                                        wfDebug( __METHOD__ . ": no page text\n" );
                                        wfProfileOut( __METHOD__ );
                                        return null;
@@ -1332,28 +1484,14 @@ class WikiPage extends Page implements IDBAccessObject {
                                        return null;
                                }
 
-                               $oldtext = $rev->getText();
+                               $oldContent = $rev->getContent();
                        }
 
-                       if ( $section == 'new' ) {
-                               # Inserting a new section
-                               $subject = $sectionTitle ? wfMessage( 'newsectionheaderdefaultlevel' )
-                                       ->rawParams( $sectionTitle )->inContentLanguage()->text() . "\n\n" : '';
-                               if ( wfRunHooks( 'PlaceNewSection', array( $this, $oldtext, $subject, &$text ) ) ) {
-                                       $text = strlen( trim( $oldtext ) ) > 0
-                                               ? "{$oldtext}\n\n{$subject}{$text}"
-                                               : "{$subject}{$text}";
-                               }
-                       } else {
-                               # Replacing an existing section; roll out the big guns
-                               global $wgParser;
-
-                               $text = $wgParser->replaceSection( $oldtext, $section, $text );
-                       }
+                       $newContent = $oldContent->replaceSection( $section, $sectionContent, $sectionTitle );
                }
 
                wfProfileOut( __METHOD__ );
-               return $text;
+               return $newContent;
        }
 
        /**
@@ -1419,8 +1557,66 @@ class WikiPage extends Page implements IDBAccessObject {
         *     revision:                The revision object for the inserted revision, or null
         *
         *  Compatibility note: this function previously returned a boolean value indicating success/failure
+        *
+        * @deprecated since 1.21: use doEditContent() instead.
         */
        public function doEdit( $text, $summary, $flags = 0, $baseRevId = false, $user = null ) {
+               wfDeprecated( __METHOD__, '1.21' );
+
+               $content = ContentHandler::makeContent( $text, $this->getTitle() );
+
+               return $this->doEditContent( $content, $summary, $flags, $baseRevId, $user );
+       }
+
+       /**
+        * Change an existing article or create a new article. Updates RC and all necessary caches,
+        * optionally via the deferred update array.
+        *
+        * @param $content Content: new content
+        * @param $summary String: edit summary
+        * @param $flags Integer bitfield:
+        *      EDIT_NEW
+        *          Article is known or assumed to be non-existent, create a new one
+        *      EDIT_UPDATE
+        *          Article is known or assumed to be pre-existing, update it
+        *      EDIT_MINOR
+        *          Mark this edit minor, if the user is allowed to do so
+        *      EDIT_SUPPRESS_RC
+        *          Do not log the change in recentchanges
+        *      EDIT_FORCE_BOT
+        *          Mark the edit a "bot" edit regardless of user rights
+        *      EDIT_DEFER_UPDATES
+        *          Defer some of the updates until the end of index.php
+        *      EDIT_AUTOSUMMARY
+        *          Fill in blank summaries with generated text where possible
+        *
+        * If neither EDIT_NEW nor EDIT_UPDATE is specified, the status of the article will be detected.
+        * If EDIT_UPDATE is specified and the article doesn't exist, the function will return an
+        * edit-gone-missing error. If EDIT_NEW is specified and the article does exist, an
+        * edit-already-exists error will be returned. These two conditions are also possible with
+        * auto-detection due to MediaWiki's performance-optimised locking strategy.
+        *
+        * @param $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
+        *
+        * @return Status object. Possible errors:
+        *     edit-hook-aborted:       The ArticleSave hook aborted the edit but didn't set the fatal flag of $status
+        *     edit-gone-missing:       In update mode, but the article didn't exist
+        *     edit-conflict:           In update mode, the article changed unexpectedly
+        *     edit-no-change:          Warning that the text was the same as before
+        *     edit-already-exists:     In creation mode, but the article already exists
+        *
+        *  Extensions may define additional errors.
+        *
+        *  $return->value will contain an associative array with members as follows:
+        *     new:                     Boolean indicating if the function attempted to create a new article
+        *     revision:                The revision object for the inserted revision, or null
+        *
+        * @since 1.21
+        */
+       public function doEditContent( Content $content, $summary, $flags = 0, $baseRevId = false,
+                                                                  User $user = null, $serialisation_format = null ) {
                global $wgUser, $wgUseAutomaticEditSummaries, $wgUseRCPatrol, $wgUseNPPatrol;
 
                # Low-level sanity check
@@ -1430,6 +1626,13 @@ class WikiPage extends Page implements IDBAccessObject {
 
                wfProfileIn( __METHOD__ );
 
+               if ( !$content->getContentHandler()->canBeUsedOn( $this->getTitle() ) ) {
+                       wfProfileOut( __METHOD__ );
+                       return Status::newFatal( 'content-not-allowed-here',
+                               ContentHandler::getLocalizedName( $content->getModel() ),
+                               $this->getTitle()->getPrefixedText() );
+               }
+
                $user = is_null( $user ) ? $wgUser : $user;
                $status = Status::newGood( array() );
 
@@ -1440,10 +1643,14 @@ class WikiPage extends Page implements IDBAccessObject {
 
                $flags = $this->checkFlags( $flags );
 
-               if ( !wfRunHooks( 'ArticleSave', array( &$this, &$user, &$text, &$summary,
-                       $flags & EDIT_MINOR, null, null, &$flags, &$status ) ) )
-               {
-                       wfDebug( __METHOD__ . ": ArticleSave hook aborted save!\n" );
+               # handle hook
+               $hook_args = array( &$this, &$user, &$content, &$summary,
+                                                       $flags & EDIT_MINOR, null, null, &$flags, &$status );
+
+               if ( !wfRunHooks( 'ArticleContentSave', $hook_args )
+                       || !ContentHandler::runLegacyHooks( 'ArticleSave', $hook_args ) ) {
+
+                       wfDebug( __METHOD__ . ": ArticleSave or ArticleSaveContent hook aborted save!\n" );
 
                        if ( $status->isOK() ) {
                                $status->fatal( 'edit-hook-aborted' );
@@ -1457,20 +1664,25 @@ class WikiPage extends Page implements IDBAccessObject {
                $isminor = ( $flags & EDIT_MINOR ) && $user->isAllowed( 'minoredit' );
                $bot = $flags & EDIT_FORCE_BOT;
 
-               $oldtext = $this->getRawText(); // current revision
-               $oldsize = strlen( $oldtext );
+               $old_content = $this->getContent( Revision::RAW ); // current revision's content
+
+               $oldsize = $old_content ? $old_content->getSize() : 0;
                $oldid = $this->getLatest();
                $oldIsRedirect = $this->isRedirect();
                $oldcountable = $this->isCountable();
 
+               $handler = $content->getContentHandler();
+
                # Provide autosummaries if one is not provided and autosummaries are enabled.
                if ( $wgUseAutomaticEditSummaries && $flags & EDIT_AUTOSUMMARY && $summary == '' ) {
-                       $summary = self::getAutosummary( $oldtext, $text, $flags );
+                       if ( !$old_content ) $old_content = null;
+                       $summary = $handler->getAutosummary( $old_content, $content, $flags );
                }
 
-               $editInfo = $this->prepareTextForEdit( $text, null, $user );
-               $text = $editInfo->pst;
-               $newsize = strlen( $text );
+               $editInfo = $this->prepareContentForEdit( $content, null, $user, $serialisation_format );
+               $serialized = $editInfo->pst;
+               $content = $editInfo->pstContent;
+               $newsize =  $content->getSize();
 
                $dbw = wfGetDB( DB_MASTER );
                $now = wfTimestampNow();
@@ -1487,7 +1699,7 @@ class WikiPage extends Page implements IDBAccessObject {
 
                                wfProfileOut( __METHOD__ );
                                return $status;
-                       } elseif ( $oldtext === false ) {
+                       } elseif ( !$old_content ) {
                                # Sanity check for bug 37225
                                wfProfileOut( __METHOD__ );
                                throw new MWException( "Could not find text for current revision {$oldid}." );
@@ -1497,20 +1709,35 @@ class WikiPage extends Page implements IDBAccessObject {
                                'page'       => $this->getId(),
                                'comment'    => $summary,
                                'minor_edit' => $isminor,
-                               'text'       => $text,
+                               'text'       => $serialized,
+                               'len'        => $newsize,
                                'parent_id'  => $oldid,
                                'user'       => $user->getId(),
                                'user_text'  => $user->getName(),
-                               'timestamp'  => $now
-                       ) );
-                       # Bug 37225: use accessor to get the text as Revision may trim it.
-                       # After trimming, the text may be a duplicate of the current text.
-                       $text = $revision->getText(); // sanity; EditPage should trim already
+                               'timestamp'  => $now,
+                               'content_model' => $content->getModel(),
+                               'content_format' => $serialisation_format,
+                       ) ); #XXX: pass content object?!
 
-                       $changed = ( strcmp( $text, $oldtext ) != 0 );
+                       $changed = !$content->equals( $old_content );
 
                        if ( $changed ) {
+                               if ( !$content->isValid() ) {
+                                       throw new MWException( "New content failed validity check!" );
+                               }
+
                                $dbw->begin( __METHOD__ );
+
+                               $prepStatus = $content->prepareSave( $this, $flags, $baseRevId, $user );
+                               $status->merge( $prepStatus );
+
+                               if ( !$status->isOK() ) {
+                                       $dbw->rollback();
+
+                                       wfProfileOut( __METHOD__ );
+                                       return $status;
+                               }
+
                                $revisionId = $revision->insertOn( $dbw );
 
                                # Update page
@@ -1558,8 +1785,14 @@ class WikiPage extends Page implements IDBAccessObject {
                        }
 
                        # Update links tables, site stats, etc.
-                       $this->doEditUpdates( $revision, $user, array( 'changed' => $changed,
-                               'oldcountable' => $oldcountable ) );
+                       $this->doEditUpdates(
+                               $revision,
+                               $user,
+                               array(
+                                       'changed' => $changed,
+                                       'oldcountable' => $oldcountable
+                               )
+                       );
 
                        if ( !$changed ) {
                                $status->warning( 'edit-no-change' );
@@ -1574,6 +1807,18 @@ class WikiPage extends Page implements IDBAccessObject {
 
                        $dbw->begin( __METHOD__ );
 
+                       $prepStatus = $content->prepareSave( $this, $flags, $baseRevId, $user );
+                       $status->merge( $prepStatus );
+
+                       if ( !$status->isOK() ) {
+                               $dbw->rollback();
+
+                               wfProfileOut( __METHOD__ );
+                               return $status;
+                       }
+
+                       $status->merge( $prepStatus );
+
                        # Add the page record; stake our claim on this title!
                        # This will return false if the article already exists
                        $newid = $this->insertOn( $dbw );
@@ -1591,15 +1836,18 @@ class WikiPage extends Page implements IDBAccessObject {
                                'page'       => $newid,
                                'comment'    => $summary,
                                'minor_edit' => $isminor,
-                               'text'       => $text,
+                               'text'       => $serialized,
+                               'len'        => $newsize,
                                'user'       => $user->getId(),
                                'user_text'  => $user->getName(),
-                               'timestamp'  => $now
+                               'timestamp'  => $now,
+                               'content_model' => $content->getModel(),
+                               'content_format' => $serialisation_format,
                        ) );
                        $revisionId = $revision->insertOn( $dbw );
 
                        # Bug 37225: use accessor to get the text as Revision may trim it
-                       $text = $revision->getText(); // sanity; EditPage should trim already
+                       $content = $revision->getContent(); // sanity; get normalized version
 
                        # Update the page record with revision data
                        $this->updateRevisionOn( $dbw, $revision, 0 );
@@ -1613,7 +1861,7 @@ class WikiPage extends Page implements IDBAccessObject {
                                        $this->mTitle->getUserPermissionsErrors( 'autopatrol', $user ) );
                                # Add RC row to the DB
                                $rc = RecentChange::notifyNew( $now, $this->mTitle, $isminor, $user, $summary, $bot,
-                                       '', strlen( $text ), $revisionId, $patrolled );
+                                       '', $content->getSize(), $revisionId, $patrolled );
 
                                # Log auto-patrolled edits
                                if ( $patrolled ) {
@@ -1626,8 +1874,11 @@ class WikiPage extends Page implements IDBAccessObject {
                        # Update links, etc.
                        $this->doEditUpdates( $revision, $user, array( 'created' => true ) );
 
-                       wfRunHooks( 'ArticleInsertComplete', array( &$this, &$user, $text, $summary,
-                               $flags & EDIT_MINOR, null, null, &$flags, $revision ) );
+                       $hook_args = array( &$this, &$user, $content, $summary,
+                                                               $flags & EDIT_MINOR, null, null, &$flags, $revision );
+
+                       ContentHandler::runLegacyHooks( 'ArticleInsertComplete', $hook_args );
+                       wfRunHooks( 'ArticleContentInsertComplete', $hook_args );
                }
 
                # Do updates right now unless deferral was requested
@@ -1638,8 +1889,11 @@ class WikiPage extends Page implements IDBAccessObject {
                // Return the new revision (or null) to the caller
                $status->value['revision'] = $revision;
 
-               wfRunHooks( 'ArticleSaveComplete', array( &$this, &$user, $text, $summary,
-                       $flags & EDIT_MINOR, null, null, &$flags, $revision, &$status, $baseRevId ) );
+               $hook_args = array( &$this, &$user, $content, $summary,
+                                                       $flags & EDIT_MINOR, null, null, &$flags, $revision, &$status, $baseRevId );
+
+               ContentHandler::runLegacyHooks( 'ArticleSaveComplete', $hook_args );
+               wfRunHooks( 'ArticleContentSaveComplete', $hook_args );
 
                # Promote user to any groups they meet the criteria for
                $user->addAutopromoteOnceGroups( 'onEdit' );
@@ -1651,6 +1905,8 @@ class WikiPage extends Page implements IDBAccessObject {
        /**
         * Get parser options suitable for rendering the primary article wikitext
         *
+        * @see ContentHandler::makeParserOptions
+        *
         * @param IContextSource|User|string $context One of the following:
         *        - IContextSource: Use the User and the Language of the provided
         *          context
@@ -1661,38 +1917,52 @@ class WikiPage extends Page implements IDBAccessObject {
         * @return ParserOptions
         */
        public function makeParserOptions( $context ) {
-               global $wgContLang;
-
-               if ( $context instanceof IContextSource ) {
-                       $options = ParserOptions::newFromContext( $context );
-               } elseif ( $context instanceof User ) { // settings per user (even anons)
-                       $options = ParserOptions::newFromUser( $context );
-               } else { // canonical settings
-                       $options = ParserOptions::newFromUserAndLang( new User, $wgContLang );
-               }
+               $options = $this->getContentHandler()->makeParserOptions( $context );
 
                if ( $this->getTitle()->isConversionTable() ) {
+                       //@todo: ConversionTable should become a separate content model, so we don't need special cases like this one.
                        $options->disableContentConversion();
                }
 
-               $options->enableLimitReport(); // show inclusion/loop reports
-               $options->setTidy( true ); // fix bad HTML
-
                return $options;
        }
 
        /**
         * Prepare text which is about to be saved.
         * Returns a stdclass with source, pst and output members
-        * @return bool|object
+        *
+        * @deprecated in 1.21: use prepareContentForEdit instead.
         */
        public function prepareTextForEdit( $text, $revid = null, User $user = null ) {
+               wfDeprecated( __METHOD__, '1.21' );
+               $content = ContentHandler::makeContent( $text, $this->getTitle() );
+               return $this->prepareContentForEdit( $content, $revid , $user );
+       }
+
+       /**
+        * Prepare content which is about to be saved.
+        * Returns a stdclass with source, pst and output members
+        *
+        * @param \Content $content
+        * @param null $revid
+        * @param null|\User $user
+        * @param null $serialization_format
+        *
+        * @return bool|object
+        *
+        * @since 1.21
+        */
+       public function prepareContentForEdit( Content $content, $revid = null, User $user = null, $serialization_format = null ) {
                global $wgParser, $wgContLang, $wgUser;
                $user = is_null( $user ) ? $wgUser : $user;
-               // @TODO fixme: check $user->getId() here???
+               //XXX: check $user->getId() here???
+
                if ( $this->mPreparedEdit
-                       && $this->mPreparedEdit->newText == $text
+                       && $this->mPreparedEdit->newContent
+                       && $this->mPreparedEdit->newContent->equals( $content )
                        && $this->mPreparedEdit->revid == $revid
+                       && $this->mPreparedEdit->format == $serialization_format
+                       #XXX: also check $user here?
                ) {
                        // Already prepared
                        return $this->mPreparedEdit;
@@ -1703,11 +1973,21 @@ class WikiPage extends Page implements IDBAccessObject {
 
                $edit = (object)array();
                $edit->revid = $revid;
-               $edit->newText = $text;
-               $edit->pst = $wgParser->preSaveTransform( $text, $this->mTitle, $user, $popts );
+
+               $edit->pstContent = $content->preSaveTransform( $this->mTitle, $user, $popts );
+               $edit->pst = $edit->pstContent->serialize( $serialization_format ); #XXX: do we need this??
+               $edit->format = $serialization_format;
+
                $edit->popts = $this->makeParserOptions( 'canonical' );
-               $edit->output = $wgParser->parse( $edit->pst, $this->mTitle, $edit->popts, true, true, $revid );
-               $edit->oldText = $this->getRawText();
+
+               $edit->output = $edit->pstContent->getParserOutput( $this->mTitle, $revid, $edit->popts );
+
+               $edit->newContent = $content;
+               $edit->oldContent = $this->getContent( Revision::RAW );
+
+               #NOTE: B/C for hooks! don't use these fields!
+               $edit->newText = ContentHandler::getContentText( $edit->newContent );
+               $edit->oldText = $edit->oldContent ? ContentHandler::getContentText( $edit->oldContent ) : '';
 
                $this->mPreparedEdit = $edit;
 
@@ -1720,7 +2000,6 @@ class WikiPage extends Page implements IDBAccessObject {
         * Purges pages that include this page if the text was changed here.
         * Every 100th edit, prune the recent changes table.
         *
-        * @private
         * @param $revision Revision object
         * @param $user User object that did the revision
         * @param $options Array of options, following indexes are used:
@@ -1737,13 +2016,13 @@ class WikiPage extends Page implements IDBAccessObject {
                wfProfileIn( __METHOD__ );
 
                $options += array( 'changed' => true, 'created' => false, 'oldcountable' => null );
-               $text = $revision->getText();
+               $content = $revision->getContent();
 
                # Parse the text
                # Be careful not to double-PST: $text is usually already PST-ed once
                if ( !$this->mPreparedEdit || $this->mPreparedEdit->output->getFlag( 'vary-revision' ) ) {
                        wfDebug( __METHOD__ . ": No prepared edit or vary-revision is set...\n" );
-                       $editInfo = $this->prepareTextForEdit( $text, $revision->getId(), $user );
+                       $editInfo = $this->prepareContentForEdit( $content, $revision->getId(), $user );
                } else {
                        wfDebug( __METHOD__ . ": No vary-revision, using prepared edit...\n" );
                        $editInfo = $this->mPreparedEdit;
@@ -1756,7 +2035,7 @@ class WikiPage extends Page implements IDBAccessObject {
                }
 
                # Update the links tables and other secondary data
-               $updates = $editInfo->output->getSecondaryDataUpdates( $this->mTitle );
+               $updates = $content->getSecondaryDataUpdates( $this->getTitle(), null, true, $editInfo->output );
                DataUpdate::runUpdates( $updates );
 
                wfRunHooks( 'ArticleEditUpdates', array( &$this, &$editInfo, $options['changed'] ) );
@@ -1801,7 +2080,8 @@ class WikiPage extends Page implements IDBAccessObject {
                }
 
                DeferredUpdates::addUpdate( new SiteStatsUpdate( 0, 1, $good, $total ) );
-               DeferredUpdates::addUpdate( new SearchUpdate( $id, $title, $text ) );
+               DeferredUpdates::addUpdate( new SearchUpdate( $id, $title, $content->getTextForSearchIndex() ) );
+               #@TODO: let the search engine decide what to do with the content object
 
                # If this is another user's talk page, update newtalk.
                # Don't do this if $options['changed'] = false (null-edits) nor if
@@ -1827,7 +2107,11 @@ class WikiPage extends Page implements IDBAccessObject {
                }
 
                if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
-                       MessageCache::singleton()->replace( $shortTitle, $text );
+                       #XXX: could skip pseudo-messages like js/css here, based on content model.
+                       $msgtext = $content->getWikitextForTransclusion();
+                       if ( $msgtext === false || $msgtext === null ) $msgtext = '';
+
+                       MessageCache::singleton()->replace( $shortTitle, $msgtext );
                }
 
                if( $options['created'] ) {
@@ -1848,17 +2132,40 @@ class WikiPage extends Page implements IDBAccessObject {
         * @param $user User The relevant user
         * @param $comment String: comment submitted
         * @param $minor Boolean: whereas it's a minor modification
+        *
+        * @deprecated since 1.21, use doEditContent() instead.
         */
        public function doQuickEdit( $text, User $user, $comment = '', $minor = 0 ) {
+               wfDeprecated( __METHOD__, "1.21" );
+
+               $content = ContentHandler::makeContent( $text, $this->getTitle() );
+               return $this->doQuickEditContent( $content, $user, $comment , $minor );
+       }
+
+       /**
+        * Edit an article without doing all that other stuff
+        * The article must already exist; link tables etc
+        * are not updated, caches are not flushed.
+        *
+        * @param $content Content: content submitted
+        * @param $user User The relevant user
+        * @param $comment String: comment submitted
+        * @param $serialisation_format String: format for storing the content in the database
+        * @param $minor Boolean: whereas it's a minor modification
+        */
+       public function doQuickEditContent( Content $content, User $user, $comment = '', $minor = 0, $serialisation_format = null ) {
                wfProfileIn( __METHOD__ );
 
+               $serialized = $content->serialize( $serialisation_format );
+
                $dbw = wfGetDB( DB_MASTER );
                $revision = new Revision( array(
                        'page'       => $this->getId(),
-                       'text'       => $text,
+                       'text'       => $serialized,
+                       'length'     => $content->getSize(),
                        'comment'    => $comment,
                        'minor_edit' => $minor ? 1 : 0,
-               ) );
+               ) ); #XXX: set the content object?
                $revision->insertOn( $dbw );
                $this->updateRevisionOn( $dbw, $revision );
 
@@ -2152,7 +2459,7 @@ class WikiPage extends Page implements IDBAccessObject {
        public function doDeleteArticleReal(
                $reason, $suppress = false, $id = 0, $commit = true, &$error = '', User $user = null
        ) {
-               global $wgUser;
+               global $wgUser, $wgContentHandlerUseDB;
 
                wfDebug( __METHOD__ . "\n" );
 
@@ -2193,6 +2500,9 @@ class WikiPage extends Page implements IDBAccessObject {
                        $bitfield = 'rev_deleted';
                }
 
+               // we need to remember the old content so we can use it to generate all deletion updates.
+               $content = $this->getContent( Revision::RAW );
+
                $dbw = wfGetDB( DB_MASTER );
                $dbw->begin( __METHOD__ );
                // For now, shunt the revision data into the archive table.
@@ -2205,25 +2515,34 @@ class WikiPage extends Page implements IDBAccessObject {
                //
                // In the future, we may keep revisions and mark them with
                // the rev_deleted field, which is reserved for this purpose.
+
+               $row = array(
+                       'ar_namespace'  => 'page_namespace',
+                       'ar_title'      => 'page_title',
+                       'ar_comment'    => 'rev_comment',
+                       'ar_user'       => 'rev_user',
+                       'ar_user_text'  => 'rev_user_text',
+                       'ar_timestamp'  => 'rev_timestamp',
+                       'ar_minor_edit' => 'rev_minor_edit',
+                       'ar_rev_id'     => 'rev_id',
+                       'ar_parent_id'  => 'rev_parent_id',
+                       'ar_text_id'    => 'rev_text_id',
+                       'ar_text'       => '\'\'', // Be explicit to appease
+                       'ar_flags'      => '\'\'', // MySQL's "strict mode"...
+                       'ar_len'        => 'rev_len',
+                       'ar_page_id'    => 'page_id',
+                       'ar_deleted'    => $bitfield,
+                       'ar_sha1'       => 'rev_sha1',
+               );
+
+               if ( $wgContentHandlerUseDB ) {
+                       $row[ 'ar_content_model' ] = 'rev_content_model';
+                       $row[ 'ar_content_format' ] = 'rev_content_format';
+               }
+
                $dbw->insertSelect( 'archive', array( 'page', 'revision' ),
+                       $row,
                        array(
-                               'ar_namespace'  => 'page_namespace',
-                               'ar_title'      => 'page_title',
-                               'ar_comment'    => 'rev_comment',
-                               'ar_user'       => 'rev_user',
-                               'ar_user_text'  => 'rev_user_text',
-                               'ar_timestamp'  => 'rev_timestamp',
-                               'ar_minor_edit' => 'rev_minor_edit',
-                               'ar_rev_id'     => 'rev_id',
-                               'ar_parent_id'  => 'rev_parent_id',
-                               'ar_text_id'    => 'rev_text_id',
-                               'ar_text'       => '\'\'', // Be explicit to appease
-                               'ar_flags'      => '\'\'', // MySQL's "strict mode"...
-                               'ar_len'        => 'rev_len',
-                               'ar_page_id'    => 'page_id',
-                               'ar_deleted'    => $bitfield,
-                               'ar_sha1'       => 'rev_sha1'
-                       ), array(
                                'page_id' => $id,
                                'page_id = rev_page'
                        ), __METHOD__
@@ -2239,7 +2558,7 @@ class WikiPage extends Page implements IDBAccessObject {
                        return $status;
                }
 
-               $this->doDeleteUpdates( $id );
+               $this->doDeleteUpdates( $id, $content );
 
                # Log the deletion, if the page was suppressed, log it at Oversight instead
                $logtype = $suppress ? 'suppress' : 'delete';
@@ -2255,7 +2574,7 @@ class WikiPage extends Page implements IDBAccessObject {
                        $dbw->commit( __METHOD__ );
                }
 
-               wfRunHooks( 'ArticleDeleteComplete', array( &$this, &$user, $reason, $id ) );
+               wfRunHooks( 'ArticleDeleteComplete', array( &$this, &$user, $reason, $id, $content, $logEntry ) );
                $status->value = $logid;
                return $status;
        }
@@ -2264,13 +2583,15 @@ class WikiPage extends Page implements IDBAccessObject {
         * Do some database updates after deletion
         *
         * @param $id Int: page_id value of the page being deleted (B/C, currently unused)
+        * @param $content Content: optional page content to be used when determining the required updates.
+        *        This may be needed because $this->getContent() may already return null when the page proper was deleted.
         */
-       public function doDeleteUpdates( $id ) {
+       public function doDeleteUpdates( $id, Content $content = null ) {
                # update site status
                DeferredUpdates::addUpdate( new SiteStatsUpdate( 0, 1, - (int)$this->isCountable(), -1 ) );
 
                # remove secondary indexes, etc
-               $updates = $this->getDeletionUpdates( );
+               $updates = $this->getDeletionUpdates( $content );
                DataUpdate::runUpdates( $updates );
 
                # Clear caches
@@ -2283,16 +2604,6 @@ class WikiPage extends Page implements IDBAccessObject {
                $this->mTitle->resetArticleID( 0 );
        }
 
-       public function getDeletionUpdates() {
-               $updates = array(
-                       new LinksDeletionUpdate( $this ),
-               );
-
-               //@todo: make a hook to add update objects
-               //NOTE: deletion updates will be determined by the ContentHandler in the future
-               return $updates;
-       }
-
        /**
         * Roll back the most recent consecutive set of edits to a page
         * from the same user; fails if there are no eligible edits to
@@ -2465,7 +2776,12 @@ class WikiPage extends Page implements IDBAccessObject {
                }
 
                # Actually store the edit
-               $status = $this->doEdit( $target->getText(), $summary, $flags, $target->getId(), $guser );
+               $status = $this->doEditContent( $target->getContent(), $summary, $flags, $target->getId(), $guser );
+
+               if ( !$status->isOK() ) {
+                       return $status->getErrorsArray();
+               }
+
                if ( !empty( $status->value['revision'] ) ) {
                        $revId = $status->value['revision']->getId();
                } else {
@@ -2611,60 +2927,23 @@ class WikiPage extends Page implements IDBAccessObject {
 
        /**
        * Return an applicable autosummary if one exists for the given edit.
-       * @param $oldtext String: the previous text of the page.
-       * @param $newtext String: The submitted text of the page.
+       * @param $oldtext String|null: the previous text of the page.
+       * @param $newtext String|null: The submitted text of the page.
        * @param $flags Int bitmask: a bitmask of flags submitted for the edit.
        * @return string An appropriate autosummary, or an empty string.
+       *
+       * @deprecated since 1.21, use ContentHandler::getAutosummary() instead
        */
        public static function getAutosummary( $oldtext, $newtext, $flags ) {
-               global $wgContLang;
-
-               # Decide what kind of autosummary is needed.
-
-               # Redirect autosummaries
-               $ot = Title::newFromRedirect( $oldtext );
-               $rt = Title::newFromRedirect( $newtext );
-
-               if ( is_object( $rt ) && ( !is_object( $ot ) || !$rt->equals( $ot ) || $ot->getFragment() != $rt->getFragment() ) ) {
-                       $truncatedtext = $wgContLang->truncate(
-                               str_replace( "\n", ' ', $newtext ),
-                               max( 0, 255
-                                       - strlen( wfMessage( 'autoredircomment' )->inContentLanguage()->text() )
-                                       - strlen( $rt->getFullText() )
-                               ) );
-                       return wfMessage( 'autoredircomment', $rt->getFullText() )
-                               ->rawParams( $truncatedtext )->inContentLanguage()->text();
-               }
+               # NOTE: stub for backwards-compatibility. assumes the given text is wikitext. will break horribly if it isn't.
 
-               # New page autosummaries
-               if ( $flags & EDIT_NEW && strlen( $newtext ) ) {
-                       # If they're making a new article, give its text, truncated, in the summary.
+               wfDeprecated( __METHOD__, '1.21' );
 
-                       $truncatedtext = $wgContLang->truncate(
-                               str_replace( "\n", ' ', $newtext ),
-                               max( 0, 200 - strlen( wfMessage( 'autosumm-new' )->inContentLanguage()->text() ) ) );
+               $handler = ContentHandler::getForModelID( CONTENT_MODEL_WIKITEXT );
+               $oldContent = is_null( $oldtext ) ? null : $handler->unserializeContent( $oldtext );
+               $newContent = is_null( $newtext ) ? null : $handler->unserializeContent( $newtext );
 
-                       return wfMessage( 'autosumm-new' )->rawParams( $truncatedtext )
-                               ->inContentLanguage()->text();
-               }
-
-               # Blanking autosummaries
-               if ( $oldtext != '' && $newtext == '' ) {
-                       return wfMessage( 'autosumm-blank' )->inContentLanguage()->text();
-               } elseif ( strlen( $oldtext ) > 10 * strlen( $newtext ) && strlen( $newtext ) < 500 ) {
-                       # Removing more than 90% of the article
-
-                       $truncatedtext = $wgContLang->truncate(
-                               $newtext,
-                               max( 0, 200 - strlen( wfMessage( 'autosumm-replace' )->inContentLanguage()->text() ) ) );
-
-                       return wfMessage( 'autosumm-replace' )->rawParams( $truncatedtext )
-                               ->inContentLanguage()->text();
-               }
-
-               # If we reach this point, there's no applicable autosummary for our case, so our
-               # autosummary is empty.
-               return '';
+               return $handler->getAutosummary( $oldContent, $newContent, $flags );
        }
 
        /**
@@ -2675,95 +2954,7 @@ class WikiPage extends Page implements IDBAccessObject {
         *    if no revision occurred
         */
        public function getAutoDeleteReason( &$hasHistory ) {
-               global $wgContLang;
-
-               // Get the last revision
-               $rev = $this->getRevision();
-
-               if ( is_null( $rev ) ) {
-                       return false;
-               }
-
-               // Get the article's contents
-               $contents = $rev->getText();
-               $blank = false;
-
-               // If the page is blank, use the text from the previous revision,
-               // which can only be blank if there's a move/import/protect dummy revision involved
-               if ( $contents == '' ) {
-                       $prev = $rev->getPrevious();
-
-                       if ( $prev )    {
-                               $contents = $prev->getText();
-                               $blank = true;
-                       }
-               }
-
-               $dbw = wfGetDB( DB_MASTER );
-
-               // Find out if there was only one contributor
-               // Only scan the last 20 revisions
-               $res = $dbw->select( 'revision', 'rev_user_text',
-                       array( 'rev_page' => $this->getID(), $dbw->bitAnd( 'rev_deleted', Revision::DELETED_USER ) . ' = 0' ),
-                       __METHOD__,
-                       array( 'LIMIT' => 20 )
-               );
-
-               if ( $res === false ) {
-                       // This page has no revisions, which is very weird
-                       return false;
-               }
-
-               $hasHistory = ( $res->numRows() > 1 );
-               $row = $dbw->fetchObject( $res );
-
-               if ( $row ) { // $row is false if the only contributor is hidden
-                       $onlyAuthor = $row->rev_user_text;
-                       // Try to find a second contributor
-                       foreach ( $res as $row ) {
-                               if ( $row->rev_user_text != $onlyAuthor ) { // Bug 22999
-                                       $onlyAuthor = false;
-                                       break;
-                               }
-                       }
-               } else {
-                       $onlyAuthor = false;
-               }
-
-               // Generate the summary with a '$1' placeholder
-               if ( $blank ) {
-                       // The current revision is blank and the one before is also
-                       // blank. It's just not our lucky day
-                       $reason = wfMessage( 'exbeforeblank', '$1' )->inContentLanguage()->text();
-               } else {
-                       if ( $onlyAuthor ) {
-                               $reason = wfMessage(
-                                       'excontentauthor',
-                                       '$1',
-                                       $onlyAuthor
-                               )->inContentLanguage()->text();
-                       } else {
-                               $reason = wfMessage( 'excontent', '$1' )->inContentLanguage()->text();
-                       }
-               }
-
-               if ( $reason == '-' ) {
-                       // Allow these UI messages to be blanked out cleanly
-                       return '';
-               }
-
-               // Replace newlines with spaces to prevent uglyness
-               $contents = preg_replace( "/[\n\r]/", ' ', $contents );
-               // Calculate the maximum amount of chars to get
-               // Max content length = max comment length - length of the comment (excl. $1)
-               $maxLength = 255 - ( strlen( $reason ) - 2 );
-               $contents = $wgContLang->truncate( $contents, $maxLength );
-               // Remove possible unfinished links
-               $contents = preg_replace( '/\[\[([^\]]*)\]?$/', '$1', $contents );
-               // Now replace the '$1' placeholder
-               $reason = str_replace( '$1', $contents, $reason );
-
-               return $reason;
+               return $this->getContentHandler()->getAutoDeleteReason( $this->getTitle(), $hasHistory );
        }
 
        /**
@@ -3005,6 +3196,31 @@ class WikiPage extends Page implements IDBAccessObject {
                global $wgUser;
                return $this->isParserCacheUsed( ParserOptions::newFromUser( $wgUser ), $oldid );
        }
+
+       /**
+        * Returns a list of updates to be performed when this page is deleted. The updates should remove any information
+        * about this page from secondary data stores such as links tables.
+        *
+        * @param Content|null $content optional Content object for determining the necessary updates
+        * @return Array an array of DataUpdates objects
+        */
+       public function getDeletionUpdates( Content $content = null ) {
+               if ( !$content ) {
+                       // load content object, which may be used to determine the necessary updates
+                       // XXX: the content may not be needed to determine the updates, then this would be overhead.
+                       $content = $this->getContent( Revision::RAW );
+               }
+
+               if ( !$content ) {
+                       $updates = array();
+               } else {
+                       $updates = $content->getDeletionUpdates( $this );
+               }
+
+               wfRunHooks( 'WikiPageDeletionUpdates', array( $this, $content, &$updates ) );
+               return $updates;
+       }
+
 }
 
 class PoolWorkArticleView extends PoolCounterWork {
@@ -3030,9 +3246,9 @@ class PoolWorkArticleView extends PoolCounterWork {
        private $parserOptions;
 
        /**
-        * @var string|null
+        * @var Content|null
         */
-       private $text;
+       private $content = null;
 
        /**
         * @var ParserOutput|bool
@@ -3056,14 +3272,20 @@ class PoolWorkArticleView extends PoolCounterWork {
         * @param $revid Integer: ID of the revision being parsed
         * @param $useParserCache Boolean: whether to use the parser cache
         * @param $parserOptions parserOptions to use for the parse operation
-        * @param $text String: text to parse or null to load it
+        * @param $content Content|String: content to parse or null to load it; may also be given as a wikitext string, for BC
         */
-       function __construct( Page $page, ParserOptions $parserOptions, $revid, $useParserCache, $text = null ) {
+       function __construct( Page $page, ParserOptions $parserOptions, $revid, $useParserCache, $content = null ) {
+               if ( is_string($content) ) { #BC: old style call
+                       $modelId = $page->getRevision()->getContentModel();
+                       $format = $page->getRevision()->getContentFormat();
+                       $content = ContentHandler::makeContent( $content, $page->getTitle(), $modelId, $format );
+               }
+
                $this->page = $page;
                $this->revid = $revid;
                $this->cacheable = $useParserCache;
                $this->parserOptions = $parserOptions;
-               $this->text = $text;
+               $this->content = $content;
                $this->cacheKey = ParserCache::singleton()->getKey( $page, $parserOptions );
                parent::__construct( 'ArticleView', $this->cacheKey . ':revid:' . $revid );
        }
@@ -3099,25 +3321,30 @@ class PoolWorkArticleView extends PoolCounterWork {
         * @return bool
         */
        function doWork() {
-               global $wgParser, $wgUseFileCache;
+               global $wgUseFileCache;
+
+               // @todo: several of the methods called on $this->page are not declared in Page, but present
+               //        in WikiPage and delegated by Article.
 
                $isCurrent = $this->revid === $this->page->getLatest();
 
-               if ( $this->text !== null ) {
-                       $text = $this->text;
+               if ( $this->content !== null ) {
+                       $content = $this->content;
                } elseif ( $isCurrent ) {
-                       $text = $this->page->getRawText();
+                       #XXX: why use RAW audience here, and PUBLIC (default) below?
+                       $content = $this->page->getContent( Revision::RAW );
                } else {
                        $rev = Revision::newFromTitle( $this->page->getTitle(), $this->revid );
                        if ( $rev === null ) {
                                return false;
                        }
-                       $text = $rev->getText();
+
+                       #XXX: why use PUBLIC audience here (default), and RAW above?
+                       $content = $rev->getContent();
                }
 
                $time = - microtime( true );
-               $this->parserOutput = $wgParser->parse( $text, $this->page->getTitle(),
-                       $this->parserOptions, true, true, $this->revid );
+               $this->parserOutput = $content->getParserOutput( $this->page->getTitle(), $this->revid, $this->parserOptions );
                $time += microtime( true );
 
                # Timing hack
@@ -3186,3 +3413,4 @@ class PoolWorkArticleView extends PoolCounterWork {
                return false;
        }
 }
+
index 4a55d5e..35019ff 100644 (file)
@@ -59,6 +59,7 @@ class Xml {
         * The values are passed to Sanitizer::encodeAttribute.
         * Return null if no attributes given.
         * @param $attribs Array of attributes for an XML element
+        * @throws MWException
         * @return null|string
         */
        public static function expandAttributes( $attribs ) {
index 0e84583..ccdf2bb 100644 (file)
@@ -596,6 +596,7 @@ class ZipDirectoryReader {
         *
         * @param $offset int The offset into the string at which to start unpacking.
         *
+        * @throws MWException
         * @return array Unpacked associative array. Note that large integers in the input
         *    may be represented as floating point numbers in the return value, so
         *    the use of weak comparison is advised.
@@ -622,7 +623,6 @@ class ZipDirectoryReader {
                        } else {
                                // Unsigned little-endian integer
                                $length = intval( $type );
-                               $bytes = substr( $string, $pos, $length );
 
                                // Calculate the value. Use an algorithm which automatically
                                // upgrades the value to floating point if necessary.
index defd93e..f715229 100644 (file)
@@ -29,12 +29,204 @@ class CreditsAction extends FormlessAction {
                return 'credits';
        }
 
+       protected function getDescription() {
+               return $this->msg( 'creditspage' )->escaped();
+       }
+
        /**
         * This is largely cadged from PageHistory::history
         *
         * @return String HTML
         */
        public function onView() {
-               $this->getOutput()->redirect( $this->getTitle()->getLocalURL( "action=info" ) );
+               wfProfileIn( __METHOD__ );
+
+               if ( $this->page->getID() == 0 ) {
+                       $s = $this->msg( 'nocredits' )->parse();
+               } else {
+                       $s = $this->getCredits( -1 );
+               }
+
+               wfProfileOut( __METHOD__ );
+
+               return Html::rawElement( 'div', array( 'id' => 'mw-credits' ), $s );
+       }
+
+       /**
+        * Get a list of contributors
+        *
+        * @param $cnt Int: maximum list of contributors to show
+        * @param $showIfMax Bool: whether to contributors if there more than $cnt
+        * @return String: html
+        */
+       public function getCredits( $cnt, $showIfMax = true ) {
+               wfProfileIn( __METHOD__ );
+               $s = '';
+
+               if ( $cnt != 0 ) {
+                       $s = $this->getAuthor( $this->page );
+                       if ( $cnt > 1 || $cnt < 0 ) {
+                               $s .= ' ' . $this->getContributors( $cnt - 1, $showIfMax );
+                       }
+               }
+
+               wfProfileOut( __METHOD__ );
+               return $s;
+       }
+
+       /**
+        * Get the last author with the last modification time
+        * @param $article Article object
+        * @return String HTML
+        */
+       protected function getAuthor( Page $article ) {
+               $user = User::newFromName( $article->getUserText(), false );
+
+               $timestamp = $article->getTimestamp();
+               if ( $timestamp ) {
+                       $lang = $this->getLanguage();
+                       $d = $lang->date( $article->getTimestamp(), true );
+                       $t = $lang->time( $article->getTimestamp(), true );
+               } else {
+                       $d = '';
+                       $t = '';
+               }
+               return $this->msg( 'lastmodifiedatby', $d, $t )->rawParams(
+                       $this->userLink( $user ) )->params( $user->getName() )->escaped();
+       }
+
+       /**
+        * Get a list of contributors of $article
+        * @param $cnt Int: maximum list of contributors to show
+        * @param $showIfMax Bool: whether to contributors if there more than $cnt
+        * @return String: html
+        */
+       protected function getContributors( $cnt, $showIfMax ) {
+               global $wgHiddenPrefs;
+
+               $contributors = $this->page->getContributors();
+
+               $others_link = false;
+
+               # Hmm... too many to fit!
+               if ( $cnt > 0 && $contributors->count() > $cnt ) {
+                       $others_link = $this->othersLink();
+                       if ( !$showIfMax )
+                               return $this->msg( 'othercontribs' )->rawParams(
+                                       $others_link )->params( $contributors->count() )->escaped();
+               }
+
+               $real_names = array();
+               $user_names = array();
+               $anon_ips = array();
+
+               # Sift for real versus user names
+               foreach ( $contributors as $user ) {
+                       $cnt--; 
+                       if ( $user->isLoggedIn() ) {
+                               $link = $this->link( $user );
+                               if ( !in_array( 'realname', $wgHiddenPrefs ) && $user->getRealName() ) {
+                                       $real_names[] = $link;
+                               } else {
+                                       $user_names[] = $link;
+                               }
+                       } else {
+                               $anon_ips[] = $this->link( $user );
+                       }
+
+                       if ( $cnt == 0 ) {
+                               break;
+                       }
+               }
+
+               $lang = $this->getLanguage();
+
+               if ( count( $real_names ) ) {
+                       $real = $lang->listToText( $real_names );
+               } else {
+                       $real = false;
+               }
+
+               # "ThisSite user(s) A, B and C"
+               if ( count( $user_names ) ) {
+                       $user = $this->msg( 'siteusers' )->rawParams( $lang->listToText( $user_names ) )->params(
+                               count( $user_names ) )->escaped();
+               } else {
+                       $user = false;
+               }
+
+               if ( count( $anon_ips ) ) {
+                       $anon = $this->msg( 'anonusers' )->rawParams( $lang->listToText( $anon_ips ) )->params(
+                               count( $anon_ips ) )->escaped();
+               } else {
+                       $anon = false;
+               }
+
+               # This is the big list, all mooshed together. We sift for blank strings
+               $fulllist = array();
+               foreach ( array( $real, $user, $anon, $others_link ) as $s ) {
+                       if ( $s !== false ) {
+                               array_push( $fulllist, $s );
+                       }
+               }
+
+               $count = count( $fulllist );
+               # "Based on work by ..."
+               return $count
+                       ? $this->msg( 'othercontribs' )->rawParams(
+                               $lang->listToText( $fulllist ) )->params( $count )->escaped()
+                       : '';
+       }
+
+       /**
+        * Get a link to $user's user page
+        * @param $user User object
+        * @return String: html
+        */
+       protected function link( User $user ) {
+               global $wgHiddenPrefs;
+               if ( !in_array( 'realname', $wgHiddenPrefs ) && !$user->isAnon() ) {
+                       $real = $user->getRealName();
+               } else {
+                       $real = false;
+               }
+
+               $page = $user->isAnon()
+                       ? SpecialPage::getTitleFor( 'Contributions', $user->getName() )
+                       : $user->getUserPage();
+
+               return Linker::link( $page, htmlspecialchars( $real ? $real : $user->getName() ) );
+       }
+
+       /**
+        * Get a link to $user's user page
+        * @param $user User object
+        * @return String: html
+        */
+       protected function userLink( User $user ) {
+               $link = $this->link( $user );
+               if ( $user->isAnon() ) {
+                       return $this->msg( 'anonuser' )->rawParams( $link )->parse();
+               } else {
+                       global $wgHiddenPrefs;
+                       if ( !in_array( 'realname', $wgHiddenPrefs ) && $user->getRealName() ) {
+                               return $link;
+                       } else {
+                               return $this->msg( 'siteuser' )->rawParams( $link )->params( $user->getName() )->escaped();
+                       }
+               }
+       }
+
+       /**
+        * Get a link to action=credits of $article page
+        * @return String: HTML link
+        */
+       protected function othersLink() {
+               return Linker::linkKnown(
+                       $this->getTitle(),
+                       $this->msg( 'others' )->escaped(),
+                       array(),
+                       array( 'action' => 'credits' )
+               );
        }
 }
index dcd6fe5..a2d49e6 100644 (file)
@@ -572,7 +572,7 @@ class HistoryPager extends ReverseChronologicalPager {
                } elseif ( $rev->getVisibility() && $user->isAllowed( 'deletedhistory' ) ) {
                        // If revision was hidden from sysops, disable the link
                        if ( !$rev->userCan( Revision::DELETED_RESTRICTED, $user ) ) {
-                               $cdel = Linker::revDeleteLinkDisabled( false );
+                               $del = Linker::revDeleteLinkDisabled( false );
                        // Otherwise, show the link...
                        } else {
                                $query = array( 'type' => 'revision',
index cb04ec5..a4b0cb7 100644 (file)
@@ -56,6 +56,99 @@ class InfoAction extends FormlessAction {
         * @return string Page information that will be added to the output
         */
        public function onView() {
+               $content = '';
+
+               // Page header
+               if ( !$this->msg( 'pageinfo-header' )->isDisabled() ) {
+                       $content .= $this->msg( 'pageinfo-header' )->parse();
+               }
+
+               // Hide "This page is a member of # hidden categories" explanation
+               $content .= Html::element( 'style', array(),
+                       '.mw-hiddenCategoriesExplanation { display: none; }' );
+
+               // Hide "Templates used on this page" explanation
+               $content .= Html::element( 'style', array(),
+                       '.mw-templatesUsedExplanation { display: none; }' );
+
+               // Get page information
+               $pageInfo = $this->pageInfo();
+
+               // Allow extensions to add additional information
+               wfRunHooks( 'InfoAction', array( &$pageInfo ) );
+
+               // Render page information
+               foreach ( $pageInfo as $header => $infoTable ) {
+                       $content .= $this->makeHeader( $this->msg( "pageinfo-${header}" )->escaped() );
+                       $table = '';
+                       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 );
+                       }
+                       $content = $this->addTable( $content, $table );
+               }
+
+               // Page footer
+               if ( !$this->msg( 'pageinfo-footer' )->isDisabled() ) {
+                       $content .= $this->msg( 'pageinfo-footer' )->parse();
+               }
+
+               // Page credits
+               /*if ( $this->page->exists() ) {
+                       $content .= Html::rawElement( 'div', array( 'id' => 'mw-credits' ), $this->getContributors() );
+               }*/
+
+               return $content;
+       }
+
+       /**
+        * Creates a header that can be added to the output.
+        *
+        * @param $header The header text.
+        * @return string The HTML.
+        */
+       protected function makeHeader( $header ) {
+               global $wgParser;
+               $spanAttribs = array( 'class' => 'mw-headline', 'id' => $wgParser->guessSectionNameFromWikiText( $header ) );
+               return Html::rawElement( 'h2', array(), Html::element( 'span', $spanAttribs, $header ) );
+       }
+
+       /**
+        * Adds a row to a table that will be added to the content.
+        *
+        * @param $table string The table that will be added to the content
+        * @param $name string The name of the row
+        * @param $value string The value of the row
+        * @return string The table with the row added
+        */
+       protected function addRow( $table, $name, $value ) {
+               return $table . Html::rawElement( 'tr', array(),
+                       Html::rawElement( 'td', array( 'style' => 'vertical-align: top;' ), $name ) .
+                       Html::rawElement( 'td', array(), $value )
+               );
+       }
+
+       /**
+        * Adds a table to the content that will be added to the output.
+        *
+        * @param $content string The content that will be added to the output
+        * @param $table string The table
+        * @return string The content with the table added
+        */
+       protected function addTable( $content, $table ) {
+               return $content . Html::rawElement( 'table', array( 'class' => 'wikitable mw-page-info' ),
+                       $table );
+       }
+
+       /**
+        * Returns page information in an easily-manipulated format. Array keys are used so extensions
+        * may add additional information in arbitrary positions. Array values are arrays with one
+        * element to be rendered as a header, arrays with two elements to be rendered as a table row.
+        *
+        * @return array
+        */
+       protected function pageInfo() {
                global $wgContLang, $wgDisableCounters, $wgRCMaxAge;
 
                $user = $this->getUser();
@@ -65,7 +158,7 @@ class InfoAction extends FormlessAction {
 
                // Get page information that would be too "expensive" to retrieve by normal means
                $userCanViewUnwatchedPages = $user->isAllowed( 'unwatchedpages' );
-               $pageInfo = self::pageCountInfo( $title, $userCanViewUnwatchedPages, $wgDisableCounters );
+               $pageCounts = self::pageCounts( $title, $userCanViewUnwatchedPages, $wgDisableCounters );
 
                // Get page properties
                $dbr = wfGetDB( DB_SLAVE );
@@ -81,21 +174,9 @@ class InfoAction extends FormlessAction {
                        $pageProperties[$row->pp_propname] = $row->pp_value;
                }
 
-               $content = '';
-               $table = '';
-
-               // Header
-               if ( !$this->msg( 'pageinfo-header' )->isDisabled() ) {
-                       $content .= $this->msg( 'pageinfo-header' )->parse();
-               }
-
-               // Credits
-               if ( $title->exists() ) {
-                       $content .= Html::rawElement( 'div', array( 'id' => 'mw-credits' ), $this->getContributors() );
-               }
-
                // Basic information
-               $content .= $this->makeHeader( $this->msg( 'pageinfo-header-basic' )->plain() );
+               $pageInfo = array();
+               $pageInfo['header-basic'] = array();
 
                // Display title
                $displayTitle = $title->getPrefixedText();
@@ -103,8 +184,9 @@ class InfoAction extends FormlessAction {
                        $displayTitle = $pageProperties['displaytitle'];
                }
 
-               $table = $this->addRow( $table,
-                       $this->msg( 'pageinfo-display-title' )->escaped(), $displayTitle );
+               $pageInfo['header-basic'][] = array(
+                       $this->msg( 'pageinfo-display-title' ), $displayTitle
+               );
 
                // Default sort key
                $sortKey = $title->getCategorySortKey();
@@ -112,16 +194,15 @@ class InfoAction extends FormlessAction {
                        $sortKey = $pageProperties['defaultsort'];
                }
 
-               $table = $this->addRow( $table,
-                       $this->msg( 'pageinfo-default-sort' )->escaped(), $sortKey );
+               $pageInfo['header-basic'][] = array( $this->msg( 'pageinfo-default-sort' ), $sortKey );
 
                // Page length (in bytes)
-               $table = $this->addRow( $table,
-                       $this->msg( 'pageinfo-length' )->escaped(), $lang->formatNum( $title->getLength() ) );
+               $pageInfo['header-basic'][] = array(
+                       $this->msg( 'pageinfo-length' ), $lang->formatNum( $title->getLength() )
+               );
 
-               // Page ID (number not localised, as it's a database ID.)
-               $table = $this->addRow( $table,
-                       $this->msg( 'pageinfo-article-id' )->escaped(), $id );
+               // Page ID (number not localised, as it's a database ID)
+               $pageInfo['header-basic'][] = array( $this->msg( 'pageinfo-article-id' ), $id );
 
                // Search engine status
                $pOutput = new ParserOutput();
@@ -131,27 +212,27 @@ class InfoAction extends FormlessAction {
 
                // Use robot policy logic
                $policy = $this->page->getRobotPolicy( 'view', $pOutput );
-               $table = $this->addRow( $table,
-                       $this->msg( 'pageinfo-robot-policy' )->escaped(),
-                       $this->msg( "pageinfo-robot-${policy['index']}" )->escaped()
+               $pageInfo['header-basic'][] = array(
+                       $this->msg( 'pageinfo-robot-policy' ), $this->msg( "pageinfo-robot-${policy['index']}" )
                );
 
                if ( !$wgDisableCounters ) {
                        // Number of views
-                       $table = $this->addRow( $table,
-                               $this->msg( 'pageinfo-views' )->escaped(), $lang->formatNum( $pageInfo['views'] )
+                       $pageInfo['header-basic'][] = array(
+                               $this->msg( 'pageinfo-views' ), $lang->formatNum( $pageCounts['views'] )
                        );
                }
 
                if ( $userCanViewUnwatchedPages ) {
                        // Number of page watchers
-                       $table = $this->addRow( $table,
-                               $this->msg( 'pageinfo-watchers' )->escaped(), $lang->formatNum( $pageInfo['watchers'] ) );
+                       $pageInfo['header-basic'][] = array(
+                               $this->msg( 'pageinfo-watchers' ), $lang->formatNum( $pageCounts['watchers'] )
+                       );
                }
 
                // Redirects to this page
                $whatLinksHere = SpecialPage::getTitleFor( 'Whatlinkshere', $title->getPrefixedText() );
-               $table = $this->addRow( $table,
+               $pageInfo['header-basic'][] = array(
                        Linker::link(
                                $whatLinksHere,
                                $this->msg( 'pageinfo-redirects-name' )->escaped(),
@@ -159,26 +240,24 @@ class InfoAction extends FormlessAction {
                                array( 'hidelinks' => 1, 'hidetrans' => 1 )
                        ),
                        $this->msg( 'pageinfo-redirects-value' )
-                               ->numParams( count( $title->getRedirectsHere() ) )->escaped()
+                               ->numParams( count( $title->getRedirectsHere() ) )
                );
 
                // Subpages of this page, if subpages are enabled for the current NS
                if ( MWNamespace::hasSubpages( $title->getNamespace() ) ) {
                        $prefixIndex = SpecialPage::getTitleFor( 'Prefixindex', $title->getPrefixedText() . '/' );
-                       $table = $this->addRow( $table,
+                       $pageInfo['header-basic'][] = array(
                                Linker::link( $prefixIndex, $this->msg( 'pageinfo-subpages-name' )->escaped() ),
                                $this->msg( 'pageinfo-subpages-value' )
                                        ->numParams(
-                                               $pageInfo['subpages']['total'],
-                                               $pageInfo['subpages']['redirects'],
-                                               $pageInfo['subpages']['nonredirects'] )->escaped()
+                                               $pageCounts['subpages']['total'],
+                                               $pageCounts['subpages']['redirects'],
+                                               $pageCounts['subpages']['nonredirects'] )
                        );
                }
 
                // Page protection
-               $content = $this->addTable( $content, $table );
-               $content .= $this->makeHeader( $this->msg( 'pageinfo-header-restrictions' )->plain() );
-               $table = '';
+               $pageInfo['header-restrictions'] = array();
 
                // Page protection
                foreach ( $title->getRestrictionTypes() as $restrictionType ) {
@@ -198,28 +277,25 @@ class InfoAction extends FormlessAction {
                                }
                        }
 
-                       $table = $this->addRow( $table,
-                               $this->msg( "restriction-$restrictionType" )->plain(),
-                               $message
+                       $pageInfo['header-restrictions'][] = array(
+                               $this->msg( "restriction-$restrictionType" ), $message
                        );
                }
 
                // Edit history
-               $content = $this->addTable( $content, $table );
-               $content .= $this->makeHeader( $this->msg( 'pageinfo-header-edits' )->plain() );
-               $table = '';
+               $pageInfo['header-edits'] = array();
 
                $firstRev = $this->page->getOldestRevision();
 
                // Page creator
-               $table = $this->addRow( $table,
-               $this->msg( 'pageinfo-firstuser' )->escaped(),
-                       Linker::userLink( $firstRev->getUser( Revision::FOR_THIS_USER, $user ), $firstRev->getUserText( Revision::FOR_THIS_USER, $user ) )
+               $pageInfo['header-edits'][] = array(
+                       $this->msg( 'pageinfo-firstuser' ),
+                       Linker::revUserTools( $firstRev )
                );
 
                // Date of page creation
-               $table = $this->addRow( $table,
-                       $this->msg( 'pageinfo-firsttime' )->escaped(),
+               $pageInfo['header-edits'][] = array(
+                       $this->msg( 'pageinfo-firsttime' ),
                        Linker::linkKnown(
                                $title,
                                $lang->userTimeAndDate( $firstRev->getTimestamp(), $user ),
@@ -229,14 +305,14 @@ class InfoAction extends FormlessAction {
                );
 
                // Latest editor
-               $table = $this->addRow( $table,
-               $this->msg( 'pageinfo-lastuser' )->escaped(),
-                       Linker::userLink( $this->page->getUser( Revision::FOR_THIS_USER, $user ), $this->page->getUserText( Revision::FOR_THIS_USER, $user ) )
+               $pageInfo['header-edits'][] = array(
+                       $this->msg( 'pageinfo-lastuser' ),
+                       Linker::revUserTools( $this->page->getRevision() )
                );
 
                // Date of latest edit
-               $table = $this->addRow( $table,
-                       $this->msg( 'pageinfo-lasttime' )->escaped(),
+               $pageInfo['header-edits'][] = array(
+                       $this->msg( 'pageinfo-lasttime' ),
                        Linker::linkKnown(
                                $title,
                                $lang->userTimeAndDate( $this->page->getTimestamp(), $user ),
@@ -246,28 +322,26 @@ class InfoAction extends FormlessAction {
                );
 
                // Total number of edits
-               $table = $this->addRow( $table,
-                       $this->msg( 'pageinfo-edits' )->escaped(), $lang->formatNum( $pageInfo['edits'] )
+               $pageInfo['header-edits'][] = array(
+                       $this->msg( 'pageinfo-edits' ), $lang->formatNum( $pageCounts['edits'] )
                );
 
                // Total number of distinct authors
-               $table = $this->addRow( $table,
-                       $this->msg( 'pageinfo-authors' )->escaped(), $lang->formatNum( $pageInfo['authors'] )
+               $pageInfo['header-edits'][] = array(
+                       $this->msg( 'pageinfo-authors' ), $lang->formatNum( $pageCounts['authors'] )
                );
 
                // Recent number of edits (within past 30 days)
-               $table = $this->addRow( $table,
-                       $this->msg( 'pageinfo-recent-edits', $lang->formatDuration( $wgRCMaxAge ) )->escaped(),
-                       $lang->formatNum( $pageInfo['recent_edits'] )
+               $pageInfo['header-edits'][] = array(
+                       $this->msg( 'pageinfo-recent-edits', $lang->formatDuration( $wgRCMaxAge ) ),
+                       $lang->formatNum( $pageCounts['recent_edits'] )
                );
 
                // Recent number of distinct authors
-               $table = $this->addRow( $table,
-                       $this->msg( 'pageinfo-recent-authors' )->escaped(), $lang->formatNum( $pageInfo['recent_authors'] )
+               $pageInfo['header-edits'][] = array(
+                       $this->msg( 'pageinfo-recent-authors' ), $lang->formatNum( $pageCounts['recent_authors'] )
                );
 
-               $content = $this->addTable( $content, $table );
-
                // Array of MagicWord objects
                $magicWords = MagicWord::getDoubleUnderscoreArray();
 
@@ -292,75 +366,47 @@ class InfoAction extends FormlessAction {
                        || count( $hiddenCategories ) > 0
                        || count( $transcludedTemplates ) > 0 ) {
                        // Page properties
-                       $content .= $this->makeHeader( $this->msg( 'pageinfo-header-properties' )->plain() );
-                       $table = '';
+                       $pageInfo['header-properties'] = array();
 
                        // Magic words
                        if ( count( $listItems ) > 0 ) {
-                               $table = $this->addRow( $table,
-                                       $this->msg( 'pageinfo-magic-words' )->numParams( count( $listItems ) )->escaped(),
+                               $pageInfo['header-properties'][] = array(
+                                       $this->msg( 'pageinfo-magic-words' )->numParams( count( $listItems ) ),
                                        $localizedList
                                );
                        }
 
-                       // Hide "This page is a member of # hidden categories explanation
-                       $content .= Html::element( 'style', array(),
-                               '.mw-hiddenCategoriesExplanation { display: none; }' );
-
                        // Hidden categories
                        if ( count( $hiddenCategories ) > 0 ) {
-                               $table = $this->addRow( $table,
+                               $pageInfo['header-properties'][] = array(
                                        $this->msg( 'pageinfo-hidden-categories' )
-                                               ->numParams( count( $hiddenCategories ) )->escaped(),
+                                               ->numParams( count( $hiddenCategories ) ),
                                        Linker::formatHiddenCategories( $hiddenCategories )
                                );
                        }
 
-                       // Hide "Templates used on this page:" explanation
-                       $content .= Html::element( 'style', array(),
-                               '.mw-templatesUsedExplanation { display: none; }' );
-
                        // Transcluded templates
                        if ( count( $transcludedTemplates ) > 0 ) {
-                               $table = $this->addRow( $table,
+                               $pageInfo['header-properties'][] = array(
                                        $this->msg( 'pageinfo-templates' )
-                                               ->numParams( count( $transcludedTemplates ) )->escaped(),
+                                               ->numParams( count( $transcludedTemplates ) ),
                                        Linker::formatTemplates( $transcludedTemplates )
                                );
                        }
-
-                       $content = $this->addTable( $content, $table );
                }
 
-               // Footer
-               if ( !$this->msg( 'pageinfo-footer' )->isDisabled() ) {
-                       $content .= $this->msg( 'pageinfo-footer' )->parse();
-               }
-
-               return $content;
-       }
-
-       /**
-        * Creates a header that can be added to the output.
-        *
-        * @param $header The header text.
-        * @return string The HTML.
-        */
-       public static function makeHeader( $header ) {
-               global $wgParser;
-               $spanAttribs = array( 'class' => 'mw-headline', 'id' => $wgParser->guessSectionNameFromWikiText( $header ) );
-               return Html::rawElement( 'h2', array(), Html::element( 'span', $spanAttribs, $header ) );
+               return $pageInfo;
        }
 
        /**
-        * Returns page information that would be too "expensive" to retrieve by normal means.
+        * Returns page counts that would be too "expensive" to retrieve by normal means.
         *
         * @param $title Title object
         * @param $canViewUnwatched bool
         * @param $disableCounter bool
         * @return array
         */
-       public static function pageCountInfo( $title, $canViewUnwatched, $disableCounter ) {
+       protected static function pageCounts( $title, $canViewUnwatched, $disableCounter ) {
                global $wgRCMaxAge;
 
                wfProfileIn( __METHOD__ );
@@ -470,42 +516,6 @@ class InfoAction extends FormlessAction {
                return $result;
        }
 
-       /**
-        * Adds a row to a table that will be added to the content.
-        *
-        * @param $table string The table that will be added to the content
-        * @param $name string The name of the row
-        * @param $value string The value of the row
-        * @return string The table with the row added
-        */
-       protected function addRow( $table, $name, $value ) {
-               return $table . Html::rawElement( 'tr', array(),
-                       Html::rawElement( 'td', array( 'style' => 'vertical-align: top;' ), $name ) .
-                       Html::rawElement( 'td', array(), $value )
-               );
-       }
-
-       /**
-        * Adds a table to the content that will be added to the output.
-        *
-        * @param $content string The content that will be added to the output
-        * @param $table string The table
-        * @return string The content with the table added
-        */
-       protected function addTable( $content, $table ) {
-               return $content . Html::rawElement( 'table', array( 'class' => 'wikitable mw-page-info' ),
-                       $table );
-       }
-
-       /**
-        * Returns the description that goes below the <h1> tag.
-        *
-        * @return string
-        */
-       protected function getDescription() {
-               return '';
-       }
-
        /**
         * Returns the name that goes in the <h1> page title.
         *
@@ -576,4 +586,13 @@ class InfoAction extends FormlessAction {
                                $lang->listToText( $fulllist ) )->params( $count )->escaped()
                        : '';
        }
+
+       /**
+        * Returns the description that goes below the <h1> tag.
+        *
+        * @return string
+        */
+       protected function getDescription() {
+               return '';
+       }
 }
index 174ca3f..7e7784c 100644 (file)
@@ -46,7 +46,7 @@ class RawAction extends FormlessAction {
        }
 
        function onView() {
-               global $wgGroupPermissions, $wgSquidMaxage, $wgForcedRawSMaxage, $wgJsMimeType;
+               global $wgSquidMaxage, $wgForcedRawSMaxage, $wgJsMimeType;
 
                $this->getOutput()->disable();
                $request = $this->getRequest();
@@ -91,7 +91,7 @@ class RawAction extends FormlessAction {
                $response->header( 'Content-type: ' . $contentType . '; charset=UTF-8' );
                # Output may contain user-specific data;
                # vary generated content for open sessions on private wikis
-               $privateCache = !$wgGroupPermissions['*']['read'] && ( $smaxage == 0 || session_id() != '' );
+               $privateCache = !User::groupHasPermission( '*', 'read' ) && ( $smaxage == 0 || session_id() != '' );
                # allow the client to cache this for 24 hours
                $mode = $privateCache ? 'private' : 'public';
                $response->header( 'Cache-Control: ' . $mode . ', s-maxage=' . $smaxage . ', max-age=' . $maxage );
@@ -148,11 +148,20 @@ class RawAction extends FormlessAction {
                                $request->response()->header( "Last-modified: $lastmod" );
 
                                // Public-only due to cache headers
-                               $text = $rev->getText();
+                               $content = $rev->getContent();
+
+                               if ( !$content instanceof TextContent ) {
+                                       wfHttpError( 406, "Not Acceptable", "The requested page uses the content model `"
+                                                                                                               . $content->getModel() . "` which is not supported via this interface." );
+                                       die();
+                               }
+
                                $section = $request->getIntOrNull( 'section' );
                                if ( $section !== null ) {
-                                       $text = $wgParser->getSection( $text, $section );
+                                       $content = $content->getSection( $section );
                                }
+
+                               $text = $content->getNativeData();
                        }
                }
 
index 7743438..1fc7e90 100644 (file)
@@ -115,7 +115,7 @@ class RevertFileAction extends FormAction {
                $source = $this->page->getFile()->getArchiveVirtualUrl( $this->getRequest()->getText( 'oldimage' ) );
                $comment = $data['comment'];
                // TODO: Preserve file properties from database instead of reloading from file
-               return $this->page->getFile()->upload( $source, $comment, $comment );
+               return $this->page->getFile()->upload( $source, $comment, $comment, 0, false, false, $this->getUser() );
        }
 
        public function onSuccess() {
index 0d9a902..81bad9d 100644 (file)
@@ -71,45 +71,32 @@ class RollbackAction extends FormlessAction {
                        return;
                }
 
-               # Display permissions errors before read-only message -- there's no
-               # point in misleading the user into thinking the inability to rollback
-               # is only temporary.
-               if ( !empty( $result ) && $result !== array( array( 'readonlytext' ) ) ) {
-                       # array_diff is completely broken for arrays of arrays, sigh.
-                       # Remove any 'readonlytext' error manually.
-                       $out = array();
-                       foreach ( $result as $error ) {
-                               if ( $error != array( 'readonlytext' ) ) {
-                                       $out [] = $error;
-                               }
-                       }
-                       throw new PermissionsError( 'rollback', $out );
-               }
+               #NOTE: Permission errors already handled by Action::checkExecute.
 
                if ( $result == array( array( 'readonlytext' ) ) ) {
                        throw new ReadOnlyError;
                }
 
+               #XXX: Would be nice if ErrorPageError could take multiple errors, and/or a status object.
+               #     Right now, we only show the first error
+               foreach ( $result as $error ) {
+                       throw new ErrorPageError( 'rollbackfailed', $error[0], array_slice( $error, 1 ) );
+               }
+
                $current = $details['current'];
                $target = $details['target'];
                $newId = $details['newid'];
                $this->getOutput()->setPageTitle( $this->msg( 'actioncomplete' ) );
                $this->getOutput()->setRobotPolicy( 'noindex,nofollow' );
 
-               if ( $current->getUserText() === '' ) {
-                       $old = $this->msg( 'rev-deleted-user' )->escaped();
-               } else {
-                       $old = Linker::userLink( $current->getUser(), $current->getUserText() )
-                               . Linker::userToolLinks( $current->getUser(), $current->getUserText() );
-               }
-
-               $new = Linker::userLink( $target->getUser(), $target->getUserText() )
-                       . Linker::userToolLinks( $target->getUser(), $target->getUserText() );
+               $old = Linker::revUserTools( $current );
+               $new = Linker::revUserTools( $target );
                $this->getOutput()->addHTML( $this->msg( 'rollback-success' )->rawParams( $old, $new )->parseAsBlock() );
                $this->getOutput()->returnToMain( false, $this->getTitle() );
 
                if ( !$request->getBool( 'hidediff', false ) && !$this->getUser()->getBoolOption( 'norollbackdiff', false ) ) {
-                       $de = new DifferenceEngine( $this->getContext(), $current->getId(), $newId, false, true );
+                       $contentHandler = $current->getContentHandler();
+                       $de = $contentHandler->createDifferenceEngine( $this->getContext(), $current->getId(), $newId, false, true );
                        $de->showDiff( '', '' );
                }
        }
index ed72b29..6741259 100644 (file)
@@ -35,7 +35,15 @@ class ApiComparePages extends ApiBase {
                $rev1 = $this->revisionOrTitleOrId( $params['fromrev'], $params['fromtitle'], $params['fromid'] );
                $rev2 = $this->revisionOrTitleOrId( $params['torev'], $params['totitle'], $params['toid'] );
 
-               $de = new DifferenceEngine( $this->getContext(),
+               $revision = Revision::newFromId( $rev1 );
+
+               if ( !$revision ) {
+                       $this->dieUsage( 'The diff cannot be retrieved, ' .
+                               'one revision does not exist or you do not have permission to view it.', 'baddiff' );
+               }
+
+               $contentHandler = $revision->getContentHandler();
+               $de = $contentHandler->createDifferenceEngine( $this->getContext(),
                        $rev1,
                        $rev2,
                        null, // rcid
index db4e931..283250c 100644 (file)
@@ -119,7 +119,7 @@ class ApiDelete extends ApiBase {
                        // Need to pass a throwaway variable because generateReason expects
                        // a reference
                        $hasHistory = false;
-                       $reason = $page->getAutoDeleteReason( $hasHistory );
+                       $reason = $page->getAutoDeleteReason( $hasHistory ); 
                        if ( $reason === false ) {
                                return array( array( 'cannotdelete', $title->getPrefixedText() ) );
                        }
@@ -164,7 +164,7 @@ class ApiDelete extends ApiBase {
                if ( is_null( $reason ) ) { // Log and RC don't like null reasons
                        $reason = '';
                }
-               return FileDeleteForm::doDelete( $title, $file, $oldimage, $reason, $suppress );
+               return FileDeleteForm::doDelete( $title, $file, $oldimage, $reason, $suppress, $user );
        }
 
        public function mustBePosted() {
index 2b9e849..87302e6 100644 (file)
@@ -54,17 +54,37 @@ class ApiEditPage extends ApiBase {
                        $this->dieUsageMsg( array( 'invalidtitle', $params['title'] ) );
                }
 
+               if ( !isset( $params['contentmodel'] ) || $params['contentmodel'] == '' ) {
+                       $contentHandler = $pageObj->getContentHandler();
+               } else {
+                       $contentHandler = ContentHandler::getForModelID( $params['contentmodel'] );
+               }
+
+               // @todo ask handler whether direct editing is supported at all! make allowFlatEdit() method or some such
+
+               if ( !isset( $params['contentformat'] ) || $params['contentformat'] == '' ) {
+                       $params['contentformat'] = $contentHandler->getDefaultFormat();
+               }
+
+               $contentFormat = $params['contentformat'];
+
+               if ( !$contentHandler->isSupportedFormat( $contentFormat ) ) {
+                       $name = $titleObj->getPrefixedDBkey();
+                       $model = $contentHandler->getModelID();
+
+                       $this->dieUsage( "The requested format $contentFormat is not supported for content model ".
+                                                       " $model used by $name", 'badformat' );
+               }
+
                $apiResult = $this->getResult();
 
                if ( $params['redirect'] ) {
                        if ( $titleObj->isRedirect() ) {
                                $oldTitle = $titleObj;
 
-                               $titles = Title::newFromRedirectArray(
-                                       Revision::newFromTitle(
-                                               $oldTitle, false, Revision::READ_LATEST
-                                       )->getText( Revision::FOR_THIS_USER, $user )
-                               );
+                               $titles = Revision::newFromTitle( $oldTitle, false, Revision::READ_LATEST )
+                                                       ->getContent( Revision::FOR_THIS_USER, $user )
+                                                       ->getRedirectChain();
                                // array_shift( $titles );
 
                                $redirValues = array();
@@ -103,31 +123,58 @@ class ApiEditPage extends ApiBase {
                        $this->dieUsageMsg( $errors[0] );
                }
 
-               $articleObj = Article::newFromTitle( $titleObj, $this->getContext() );
-
                $toMD5 = $params['text'];
                if ( !is_null( $params['appendtext'] ) || !is_null( $params['prependtext'] ) )
                {
-                       // For non-existent pages, Article::getContent()
-                       // returns an interface message rather than ''
-                       // We do want getContent()'s behavior for non-existent
-                       // MediaWiki: pages, though
-                       if ( $articleObj->getID() == 0 && $titleObj->getNamespace() != NS_MEDIAWIKI ) {
-                               $content = '';
-                       } else {
-                               $content = $articleObj->getContent();
+                       $content = $pageObj->getContent();
+
+                       // @todo: Add support for appending/prepending to the Content interface
+
+                       if ( !( $content instanceof TextContent ) ) {
+                               $mode = $contentHandler->getModelID();
+                               $this->dieUsage( "Can't append to pages using content model $mode", 'appendnotsupported' );
+                       }
+
+                       if ( !$content ) {
+                               # If this is a MediaWiki:x message, then load the messages
+                               # and return the message value for x.
+                               if ( $titleObj->getNamespace() == NS_MEDIAWIKI ) {
+                                       $text = $titleObj->getDefaultMessageText();
+                                       if ( $text === false ) {
+                                               $text = '';
+                                       }
+
+                                       try {
+                                               $content = ContentHandler::makeContent( $text, $this->getTitle() );
+                                       } catch ( MWContentSerializationException $ex ) {
+                                               $this->dieUsage( $ex->getMessage(), 'parseerror' );
+                                               return;
+                                       }
+                               }
                        }
 
                        if ( !is_null( $params['section'] ) ) {
+                               if ( !$contentHandler->supportsSections() ) {
+                                       $modelName = $contentHandler->getModelID();
+                                       $this->dieUsage( "Sections are not supported for this content model: $modelName.", 'sectionsnotsupported' );
+                               }
+
                                // Process the content for section edits
-                               global $wgParser;
                                $section = intval( $params['section'] );
-                               $content = $wgParser->getSection( $content, $section, false );
-                               if ( $content === false ) {
+                               $content = $content->getSection( $section );
+
+                               if ( !$content ) {
                                        $this->dieUsage( "There is no section {$section}.", 'nosuchsection' );
                                }
                        }
-                       $params['text'] = $params['prependtext'] . $content . $params['appendtext'];
+
+                       if ( !$content ) {
+                               $text = '';
+                       } else {
+                               $text = $content->serialize( $contentFormat );
+                       }
+
+                       $params['text'] = $params['prependtext'] . $text . $params['appendtext'];
                        $toMD5 = $params['prependtext'] . $params['appendtext'];
                }
 
@@ -151,18 +198,21 @@ class ApiEditPage extends ApiBase {
                                $this->dieUsageMsg( array( 'nosuchrevid', $params['undoafter'] ) );
                        }
 
-                       if ( $undoRev->getPage() != $articleObj->getID() ) {
+                       if ( $undoRev->getPage() != $pageObj->getID() ) {
                                $this->dieUsageMsg( array( 'revwrongpage', $undoRev->getID(), $titleObj->getPrefixedText() ) );
                        }
-                       if ( $undoafterRev->getPage() != $articleObj->getID() ) {
+                       if ( $undoafterRev->getPage() != $pageObj->getID() ) {
                                $this->dieUsageMsg( array( 'revwrongpage', $undoafterRev->getID(), $titleObj->getPrefixedText() ) );
                        }
 
-                       $newtext = $articleObj->getUndoText( $undoRev, $undoafterRev );
-                       if ( $newtext === false ) {
+                       $newContent = $contentHandler->getUndoContent( $pageObj->getRevision(), $undoRev, $undoafterRev );
+
+                       if ( !$newContent ) {
                                $this->dieUsageMsg( 'undo-failure' );
                        }
-                       $params['text'] = $newtext;
+
+                       $params['text'] = $newContent->serialize( $params['contentformat'] );
+
                        // If no summary was given and we only undid one rev,
                        // use an autosummary
                        if ( is_null( $params['summary'] ) && $titleObj->getNextRevisionID( $undoafterRev->getID() ) == $params['undo'] ) {
@@ -179,6 +229,8 @@ class ApiEditPage extends ApiBase {
                // That interface kind of sucks, but it's workable
                $requestArray = array(
                        'wpTextbox1' => $params['text'],
+                       'format' => $contentFormat,
+                       'model' => $contentHandler->getModelID(),
                        'wpEditToken' => $params['token'],
                        'wpIgnoreBlankSummary' => ''
                );
@@ -196,7 +248,7 @@ class ApiEditPage extends ApiBase {
                if ( !is_null( $params['basetimestamp'] ) && $params['basetimestamp'] != '' ) {
                        $requestArray['wpEdittime'] = wfTimestamp( TS_MW, $params['basetimestamp'] );
                } else {
-                       $requestArray['wpEdittime'] = $articleObj->getTimestamp();
+                       $requestArray['wpEdittime'] = $pageObj->getTimestamp();
                }
 
                if ( !is_null( $params['starttimestamp'] ) && $params['starttimestamp'] != '' ) {
@@ -244,7 +296,12 @@ class ApiEditPage extends ApiBase {
                // TODO: Make them not or check if they still do
                $wgTitle = $titleObj;
 
-               $ep = new EditPage( $articleObj );
+               $articleObject = new Article( $titleObj );
+               $ep = new EditPage( $articleObject );
+
+               // allow editing of non-textual content.
+               $ep->allowNonTextContent = true;
+
                $ep->setContextTitle( $titleObj );
                $ep->importFormData( $req );
 
@@ -262,7 +319,7 @@ class ApiEditPage extends ApiBase {
                }
 
                // Do the actual save
-               $oldRevId = $articleObj->getRevIdFetched();
+               $oldRevId = $articleObject->getRevIdFetched();
                $result = null;
                // Fake $wgRequest for some hooks inside EditPage
                // @todo FIXME: This interface SUCKS
@@ -278,6 +335,9 @@ class ApiEditPage extends ApiBase {
                        case EditPage::AS_HOOK_ERROR_EXPECTED:
                                $this->dieUsageMsg( 'hookaborted' );
 
+                       case EditPage::AS_PARSE_ERROR:
+                               $this->dieUsage( $status->getMessage(), 'parseerror' );
+
                        case EditPage::AS_IMAGE_REDIRECT_ANON:
                                $this->dieUsageMsg( 'noimageredirect-anon' );
 
@@ -329,14 +389,15 @@ class ApiEditPage extends ApiBase {
                                $r['result'] = 'Success';
                                $r['pageid'] = intval( $titleObj->getArticleID() );
                                $r['title'] = $titleObj->getPrefixedText();
-                               $newRevId = $articleObj->getLatest();
+                               $r['contentmodel'] = $titleObj->getContentModel();
+                               $newRevId = $articleObject->getLatest();
                                if ( $newRevId == $oldRevId ) {
                                        $r['nochange'] = '';
                                } else {
                                        $r['oldrevid'] = intval( $oldRevId );
                                        $r['newrevid'] = intval( $newRevId );
                                        $r['newtimestamp'] = wfTimestamp( TS_ISO_8601,
-                                               $articleObj->getTimestamp() );
+                                               $pageObj->getTimestamp() );
                                }
                                break;
 
@@ -380,6 +441,7 @@ class ApiEditPage extends ApiBase {
                                array( 'undo-failure' ),
                                array( 'hashcheckfailed' ),
                                array( 'hookaborted' ),
+                               array( 'code' => 'parseerror', 'info' => 'Failed to parse the given text.' ),
                                array( 'noimageredirect-anon' ),
                                array( 'noimageredirect-logged' ),
                                array( 'spamdetected', 'spam' ),
@@ -397,6 +459,13 @@ class ApiEditPage extends ApiBase {
                                array( 'unknownerror', 'retval' ),
                                array( 'code' => 'nosuchsection', 'info' => 'There is no section section.' ),
                                array( 'code' => 'invalidsection', 'info' => 'The section parameter must be set to an integer or \'new\'' ),
+                               array( 'code' => 'sectionsnotsupported', 'info' => 'Sections are not supported for this type of page.' ),
+                               array( 'code' => 'editnotsupported', 'info' => 'Editing of this type of page is not supported using '
+                                                                                                                               . 'the text based edit API.' ),
+                               array( 'code' => 'appendnotsupported', 'info' => 'This type of page can not be edited by appending '
+                                                                                                                               . 'or prepending text.' ),
+                               array( 'code' => 'badformat', 'info' => 'The requested serialization format can not be applied to '
+                                                                                                               . 'the page\'s content model' ),
                                array( 'customcssprotected' ),
                                array( 'customjsprotected' ),
                        )
@@ -460,6 +529,12 @@ class ApiEditPage extends ApiBase {
                                ApiBase::PARAM_TYPE => 'boolean',
                                ApiBase::PARAM_DFLT => false,
                        ),
+                       'contentformat' => array(
+                               ApiBase::PARAM_TYPE => ContentHandler::getAllContentFormats(),
+                       ),
+                       'contentmodel' => array(
+                               ApiBase::PARAM_TYPE => ContentHandler::getContentModels(),
+                       )
                );
        }
 
@@ -498,6 +573,8 @@ class ApiEditPage extends ApiBase {
                        'undo' => "Undo this revision. Overrides {$p}text, {$p}prependtext and {$p}appendtext",
                        'undoafter' => 'Undo all revisions from undo to this one. If not set, just undo one revision',
                        'redirect' => 'Automatically resolve redirects',
+                       'contentformat' => 'Content serialization format used for the input text',
+                       'contentmodel' => 'Content model of the new content',
                );
        }
 
index 1cf760a..fb6a06f 100644 (file)
@@ -130,10 +130,22 @@ class ApiFeedContributions extends ApiBase {
        protected function feedItemDesc( $revision ) {
                if( $revision ) {
                        $msg = wfMessage( 'colon-separator' )->inContentLanguage()->text();
+                       $content = $revision->getContent();
+
+                       if ( $content instanceof TextContent ) {
+                               // only textual content has a "source view".
+                               $html = nl2br( htmlspecialchars( $content->getNativeData() ) );
+                       } else {
+                               //XXX: we could get an HTML representation of the content via getParserOutput, but that may
+                               //     contain JS magic and generally may not be suitable for inclusion in a feed.
+                               //     Perhaps Content should have a getDescriptiveHtml method and/or a getSourceText method.
+                               //Compare also FeedUtils::formatDiffRow.
+                               $html = '';
+                       }
+
                        return '<p>' . htmlspecialchars( $revision->getUserText() ) . $msg .
                                htmlspecialchars( FeedItem::stripComment( $revision->getComment() ) ) .
-                               "</p>\n<hr />\n<div>" .
-                               nl2br( htmlspecialchars( $revision->getText() ) ) . "</div>";
+                               "</p>\n<hr />\n<div>" . $html . "</div>";
                }
                return '';
        }
index 83d078d..092b003 100644 (file)
@@ -50,7 +50,7 @@ class ApiFileRevert extends ApiBase {
                $this->checkPermissions( $this->getUser() );
 
                $sourceUrl = $this->file->getArchiveVirtualUrl( $this->archiveName );
-               $status = $this->file->upload( $sourceUrl, $this->params['comment'], $this->params['comment'] );
+               $status = $this->file->upload( $sourceUrl, $this->params['comment'], $this->params['comment'], 0, false, false, $this->getUser() );
 
                if ( $status->isGood() ) {
                        $result = array( 'result' => 'Success' );
diff --git a/includes/api/ApiFormatNone.php b/includes/api/ApiFormatNone.php
new file mode 100644 (file)
index 0000000..31c90e1
--- /dev/null
@@ -0,0 +1,51 @@
+<?php
+/**
+ *
+ *
+ * Created on Oct 22, 2006
+ *
+ * Copyright © 2006 Yuri Astrakhan <Firstname><Lastname>@gmail.com
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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
+ */
+
+/**
+ * API Serialized PHP output formatter
+ * @ingroup API
+ */
+class ApiFormatNone extends ApiFormatBase {
+
+       public function __construct( $main, $format ) {
+               parent::__construct( $main, $format );
+       }
+
+       public function getMimeType() {
+               return 'text/plain';
+       }
+
+       public function execute() {
+       }
+
+       public function getDescription() {
+               return 'Output nothing' . parent::getDescription();
+       }
+
+       public function getVersion() {
+               return __CLASS__ . ': $Id$';
+       }
+}
index 87a287b..7d7eb14 100644 (file)
@@ -105,6 +105,7 @@ class ApiMain extends ApiBase {
                'dbgfm' => 'ApiFormatDbg',
                'dump' => 'ApiFormatDump',
                'dumpfm' => 'ApiFormatDump',
+               'none' => 'ApiFormatNone',
        );
 
        /**
index 2fcdc38..312e439 100644 (file)
  * @ingroup API
  */
 class ApiParse extends ApiBase {
-       private $section, $text, $pstText = null;
+
+       /** @var String $section */
+       private $section = null;
+
+       /** @var Content $content */
+       private $content = null;
+
+       /** @var Content $pstContent */
+       private $pstContent = null;
 
        public function __construct( $main, $action ) {
                parent::__construct( $main, $action );
@@ -44,6 +52,9 @@ class ApiParse extends ApiBase {
                $pageid = $params['pageid'];
                $oldid = $params['oldid'];
 
+               $model = $params['contentmodel'];
+               $format = $params['contentformat'];
+
                if ( !is_null( $page ) && ( !is_null( $text ) || $title != 'API' ) ) {
                        $this->dieUsage( 'The page parameter cannot be used together with the text and title parameters', 'params' );
                }
@@ -93,17 +104,17 @@ class ApiParse extends ApiBase {
                                // If for some reason the "oldid" is actually the current revision, it may be cached
                                if ( $rev->isCurrent() )  {
                                        // May get from/save to parser cache
-                                       $p_result = $this->getParsedSectionOrText( $pageObj, $popts, $pageid,
-                                                isset( $prop['wikitext'] ) ) ;
+                                       $p_result = $this->getParsedContent( $pageObj, $popts,
+                                               $pageid, isset( $prop['wikitext'] ) ) ;
                                } else { // This is an old revision, so get the text differently
-                                       $this->text = $rev->getText( Revision::FOR_THIS_USER, $this->getUser() );
+                                       $this->content = $rev->getContent( Revision::FOR_THIS_USER, $this->getUser() );
 
                                        if ( $this->section !== false ) {
-                                               $this->text = $this->getSectionText( $this->text, 'r' . $rev->getId() );
+                                               $this->content = $this->getSectionContent( $this->content, 'r' . $rev->getId() );
                                        }
 
                                        // Should we save old revision parses to the parser cache?
-                                       $p_result = $wgParser->parse( $this->text, $titleObj, $popts );
+                                       $p_result = $this->content->getParserOutput( $titleObj, $popts );
                                }
                        } else { // Not $oldid, but $pageid or $page
                                if ( $params['redirects'] ) {
@@ -142,19 +153,15 @@ class ApiParse extends ApiBase {
                                        $oldid = $pageObj->getLatest();
                                }
 
+
                                $popts = $pageObj->makeParserOptions( $this->getContext() );
                                $popts->enableLimitReport( !$params['disablepp'] );
 
                                // Potentially cached
-                               $p_result = $this->getParsedSectionOrText( $pageObj, $popts, $pageid,
-                                        isset( $prop['wikitext'] ) ) ;
+                               $p_result = $this->getParsedContent( $pageObj, $popts, $pageid, 
+                                       isset( $prop['wikitext'] ) ) ;
                        }
                } else { // Not $oldid, $pageid, $page. Hence based on $text
-
-                       if ( is_null( $text ) ) {
-                               $this->dieUsage( 'The text parameter should be passed with the title parameter. Should you be using the "page" parameter instead?', 'params' );
-                       }
-                       $this->text = $text;
                        $titleObj = Title::newFromText( $title );
                        if ( !$titleObj ) {
                                $this->dieUsageMsg( array( 'invalidtitle', $title ) );
@@ -165,27 +172,42 @@ class ApiParse extends ApiBase {
                        $popts = $pageObj->makeParserOptions( $this->getContext() );
                        $popts->enableLimitReport( !$params['disablepp'] );
 
+                       if ( is_null( $text ) ) {
+                               $this->dieUsage( 'The text parameter should be passed with the title parameter. Should you be using the "page" parameter instead?', 'params' );
+                       }
+
+                       try {
+                               $this->content = ContentHandler::makeContent( $text, $titleObj, $model, $format );
+                       } catch ( MWContentSerializationException $ex ) {
+                               $this->dieUsage( $ex->getMessage(), 'parseerror' );
+                       }
+
                        if ( $this->section !== false ) {
-                               $this->text = $this->getSectionText( $this->text, $titleObj->getText() );
+                               $this->content = $this->getSectionContent( $this->content, $titleObj->getText() );
                        }
 
                        if ( $params['pst'] || $params['onlypst'] ) {
-                               $this->pstText = $wgParser->preSaveTransform( $this->text, $titleObj, $this->getUser(), $popts );
+                               $this->pstContent = $this->content->preSaveTransform( $titleObj, $this->getUser(), $popts );
                        }
                        if ( $params['onlypst'] ) {
                                // Build a result and bail out
                                $result_array = array();
                                $result_array['text'] = array();
-                               $result->setContent( $result_array['text'], $this->pstText );
+                               $result->setContent( $result_array['text'], $this->pstContent->serialize( $format ) );
                                if ( isset( $prop['wikitext'] ) ) {
                                        $result_array['wikitext'] = array();
-                                       $result->setContent( $result_array['wikitext'], $this->text );
+                                       $result->setContent( $result_array['wikitext'], $this->content->serialize( $format ) );
                                }
                                $result->addValue( null, $this->getModuleName(), $result_array );
                                return;
                        }
+
                        // Not cached (save or load)
-                       $p_result = $wgParser->parse( $params['pst'] ? $this->pstText : $this->text, $titleObj, $popts );
+                       if ( $params['pst'] ) {
+                               $p_result = $this->pstContent->getParserOutput( $titleObj, $popts );
+                       } else {
+                               $p_result = $this->content->getParserOutput( $titleObj, $popts );
+                       }
                }
 
                $result_array = array();
@@ -275,10 +297,10 @@ class ApiParse extends ApiBase {
 
                if ( isset( $prop['wikitext'] ) ) {
                        $result_array['wikitext'] = array();
-                       $result->setContent( $result_array['wikitext'], $this->text );
-                       if ( !is_null( $this->pstText ) ) {
+                       $result->setContent( $result_array['wikitext'], $this->content->serialize( $format ) );
+                       if ( !is_null( $this->pstContent ) ) {
                                $result_array['psttext'] = array();
-                               $result->setContent( $result_array['psttext'], $this->pstText );
+                               $result->setContent( $result_array['psttext'], $this->pstContent->serialize( $format ) );
                        }
                }
                if ( isset( $prop['properties'] ) ) {
@@ -286,8 +308,12 @@ class ApiParse extends ApiBase {
                }
 
                if ( $params['generatexml'] ) {
+                       if ( $this->content->getModel() != CONTENT_MODEL_WIKITEXT ) {
+                               $this->dieUsage( "generatexml is only supported for wikitext content", "notwikitext" );
+                       }
+
                        $wgParser->startExternalParse( $titleObj, $popts, OT_PREPROCESS );
-                       $dom = $wgParser->preprocessToDom( $this->text );
+                       $dom = $wgParser->preprocessToDom( $this->content->getNativeData() );
                        if ( is_callable( array( $dom, 'saveXML' ) ) ) {
                                $xml = $dom->saveXML();
                        } else {
@@ -325,15 +351,16 @@ class ApiParse extends ApiBase {
         * @param $getWikitext Bool
         * @return ParserOutput
         */
-       private function getParsedSectionOrText( $page, $popts, $pageId = null, $getWikitext = false ) {
-               global $wgParser;
+       private function getParsedContent( WikiPage $page, $popts, $pageId = null, $getWikitext = false ) {
+               $this->content = $page->getContent( Revision::RAW ); //XXX: really raw?
 
                if ( $this->section !== false ) {
-                       $this->text = $this->getSectionText( $page->getRawText(), !is_null( $pageId )
-                                       ? 'page id ' . $pageId : $page->getTitle()->getPrefixedText() );
+                       $this->content = $this->getSectionContent(
+                               $this->content,
+                               !is_null( $pageId ) ? 'page id ' . $pageId : $page->getTitle()->getText() );
 
                        // Not cached (save or load)
-                       return $wgParser->parse( $this->text, $page->getTitle(), $popts );
+                       return $this->content->getParserOutput( $page->getTitle(), $popts );
                } else {
                        // Try the parser cache first
                        // getParserOutput will save to Parser cache if able
@@ -342,20 +369,23 @@ class ApiParse extends ApiBase {
                                $this->dieUsage( "There is no revision ID {$page->getLatest()}", 'missingrev' );
                        }
                        if ( $getWikitext ) {
-                               $this->text = $page->getRawText();
+                               $this->content = $page->getContent( Revision::RAW );
                        }
                        return $pout;
                }
        }
 
-       private function getSectionText( $text, $what ) {
-               global $wgParser;
+       private function getSectionContent( Content $content, $what ) {
                // Not cached (save or load)
-               $text = $wgParser->getSection( $text, $this->section, false );
-               if ( $text === false ) {
+               $section = $content->getSection( $this->section );
+               if ( $section === false ) {
                        $this->dieUsage( "There is no section {$this->section} in " . $what, 'nosuchsection' );
                }
-               return $text;
+               if ( $section === null ) {
+                       $this->dieUsage( "Sections are not supported by " . $what, 'nosuchsection' );
+                       $section = false;
+               }
+               return $section;
        }
 
        private function formatLangLinks( $links ) {
@@ -548,6 +578,12 @@ class ApiParse extends ApiBase {
                        'section' => null,
                        'disablepp' => false,
                        'generatexml' => false,
+                       'contentformat' => array(
+                               ApiBase::PARAM_TYPE => ContentHandler::getAllContentFormats(),
+                       ),
+                       'contentmodel' => array(
+                               ApiBase::PARAM_TYPE => ContentHandler::getContentModels(),
+                       )
                );
        }
 
@@ -593,6 +629,8 @@ class ApiParse extends ApiBase {
                        'section' => 'Only retrieve the content of this section number',
                        'disablepp' => 'Disable the PP Report from the parser output',
                        'generatexml' => 'Generate XML parse tree',
+                       'contentformat' => 'Content serialization format used for the input text',
+                       'contentmodel' => 'Content model of the new content',
                );
        }
 
@@ -613,6 +651,8 @@ class ApiParse extends ApiBase {
                        array( 'code' => 'nosuchsection', 'info' => 'There is no section sectionnumber in page' ),
                        array( 'nosuchpageid' ),
                        array( 'invalidtitle', 'title' ),
+                       array( 'code' => 'parseerror', 'info' => 'Failed to parse the given text.' ),
+                       array( 'code' => 'notwikitext', 'info' => 'The requested operation is only supported on wikitext content.' ),
                ) );
        }
 
index 9fedaf1..dbfa89c 100644 (file)
@@ -86,14 +86,16 @@ class ApiPurge extends ApiBase {
 
                        if( $forceLinkUpdate ) {
                                if ( !$user->pingLimiter() ) {
-                                       global $wgParser, $wgEnableParserCache;
+                                       global $wgEnableParserCache;
 
                                        $popts = $page->makeParserOptions( 'canonical' );
-                                       $p_result = $wgParser->parse( $page->getRawText(), $title, $popts,
-                                               true, true, $page->getLatest() );
+
+                                       # Parse content; note that HTML generation is only needed if we want to cache the result.
+                                       $content = $page->getContent( Revision::RAW );
+                                       $p_result = $content->getParserOutput( $title, $page->getLatest(), $popts, $wgEnableParserCache );
 
                                        # Update the links tables
-                                       $updates = $p_result->getSecondaryDataUpdates( $title );
+                                       $updates = $content->getSecondaryDataUpdates( $title, null, true, $p_result );
                                        DataUpdate::runUpdates( $updates );
 
                                        $r['linkupdate'] = '';
index 41dfc33..5aff1e9 100644 (file)
 class ApiQueryRevisions extends ApiQueryBase {
 
        private $diffto, $difftotext, $expandTemplates, $generateXML, $section,
-               $token, $parseContent;
+               $token, $parseContent, $contentFormat;
 
        public function __construct( $query, $moduleName ) {
                parent::__construct( $query, $moduleName, 'rv' );
        }
 
-       private $fld_ids = false, $fld_flags = false, $fld_timestamp = false, $fld_size = false,
+       private $fld_ids = false, $fld_flags = false, $fld_timestamp = false, $fld_size = false, $fld_sha1 = false,
                        $fld_comment = false, $fld_parsedcomment = false, $fld_user = false, $fld_userid = false,
-                       $fld_content = false, $fld_tags = false;
+                       $fld_content = false, $fld_tags = false, $fld_contentmodel = false;
 
        private $tokenFunctions;
 
@@ -155,10 +155,15 @@ class ApiQueryRevisions extends ApiQueryBase {
                $this->fld_parsedcomment = isset ( $prop['parsedcomment'] );
                $this->fld_size = isset ( $prop['size'] );
                $this->fld_sha1 = isset ( $prop['sha1'] );
+               $this->fld_contentmodel = isset ( $prop['contentmodel'] );
                $this->fld_userid = isset( $prop['userid'] );
                $this->fld_user = isset ( $prop['user'] );
                $this->token = $params['token'];
 
+               if ( !empty( $params['contentformat'] ) ) {
+                       $this->contentFormat = $params['contentformat'];
+               }
+
                // Possible indexes used
                $index = array();
 
@@ -442,6 +447,10 @@ class ApiQueryRevisions extends ApiQueryBase {
                        }
                }
 
+               if ( $this->fld_contentmodel ) {
+                       $vals['contentmodel'] = $revision->getContentModel();
+               }
+
                if ( $this->fld_comment || $this->fld_parsedcomment ) {
                        if ( $revision->isDeleted( Revision::DELETED_COMMENT ) ) {
                                $vals['commenthidden'] = '';
@@ -480,39 +489,79 @@ class ApiQueryRevisions extends ApiQueryBase {
                        }
                }
 
-               $text = null;
+               $content = null;
                global $wgParser;
                if ( $this->fld_content || !is_null( $this->difftotext ) ) {
-                       $text = $revision->getText();
+                       $content = $revision->getContent();
                        // Expand templates after getting section content because
                        // template-added sections don't count and Parser::preprocess()
                        // will have less input
                        if ( $this->section !== false ) {
-                               $text = $wgParser->getSection( $text, $this->section, false );
-                               if ( $text === false ) {
+                               $content = $content->getSection( $this->section, false );
+                               if ( !$content ) {
                                        $this->dieUsage( "There is no section {$this->section} in r" . $revision->getId(), 'nosuchsection' );
                                }
                        }
                }
                if ( $this->fld_content && !$revision->isDeleted( Revision::DELETED_TEXT ) ) {
+                       $text = null;
+
                        if ( $this->generateXML ) {
-                               $wgParser->startExternalParse( $title, ParserOptions::newFromContext( $this->getContext() ), OT_PREPROCESS );
-                               $dom = $wgParser->preprocessToDom( $text );
-                               if ( is_callable( array( $dom, 'saveXML' ) ) ) {
-                                       $xml = $dom->saveXML();
+                               if ( $content->getModel() === CONTENT_MODEL_WIKITEXT ) {
+                                       $t = $content->getNativeData(); # note: don't set $text
+
+                                       $wgParser->startExternalParse( $title, ParserOptions::newFromContext( $this->getContext() ), OT_PREPROCESS );
+                                       $dom = $wgParser->preprocessToDom( $t );
+                                       if ( is_callable( array( $dom, 'saveXML' ) ) ) {
+                                               $xml = $dom->saveXML();
+                                       } else {
+                                               $xml = $dom->__toString();
+                                       }
+                                       $vals['parsetree'] = $xml;
                                } else {
-                                       $xml = $dom->__toString();
+                                       $this->setWarning( "Conversion to XML is supported for wikitext only, " .
+                                                                               $title->getPrefixedDBkey() .
+                                                                               " uses content model " . $content->getModel() . ")" );
                                }
-                               $vals['parsetree'] = $xml;
-
                        }
+
                        if ( $this->expandTemplates && !$this->parseContent ) {
-                               $text = $wgParser->preprocess( $text, $title, ParserOptions::newFromContext( $this->getContext() ) );
+                               #XXX: implement template expansion for all content types in ContentHandler?
+                               if ( $content->getModel() === CONTENT_MODEL_WIKITEXT ) {
+                                       $text = $content->getNativeData();
+
+                                       $text = $wgParser->preprocess( $text, $title, ParserOptions::newFromContext( $this->getContext() ) );
+                               } else {
+                                       $this->setWarning( "Template expansion is supported for wikitext only, " .
+                                               $title->getPrefixedDBkey() .
+                                               " uses content model " . $content->getModel() . ")" );
+
+                                       $text = false;
+                               }
                        }
                        if ( $this->parseContent ) {
-                               $text = $wgParser->parse( $text, $title, ParserOptions::newFromContext( $this->getContext() ) )->getText();
+                               $po = $content->getParserOutput( $title, ParserOptions::newFromContext( $this->getContext() ) );
+                               $text = $po->getText();
+                       }
+
+                       if ( $text === null ) {
+                               $format = $this->contentFormat ? $this->contentFormat : $content->getDefaultFormat();
+
+                               if ( !$content->isSupportedFormat( $format ) ) {
+                                       $model = $content->getModel();
+                                       $name = $title->getPrefixedDBkey();
+
+                                       $this->dieUsage( "The requested format {$this->contentFormat} is not supported ".
+                                                                       "for content model $model used by $name", 'badformat' );
+                               }
+
+                               $text = $content->serialize( $format );
+                               $vals['contentformat'] = $format;
+                       }
+
+                       if ( $text !== false ) {
+                               ApiResult::setContent( $vals, $text );
                        }
-                       ApiResult::setContent( $vals, $text );
                } elseif ( $this->fld_content ) {
                        $vals['texthidden'] = '';
                }
@@ -524,11 +573,26 @@ class ApiQueryRevisions extends ApiQueryBase {
                                $vals['diff'] = array();
                                $context = new DerivativeContext( $this->getContext() );
                                $context->setTitle( $title );
+                               $handler = $revision->getContentHandler();
+
                                if ( !is_null( $this->difftotext ) ) {
-                                       $engine = new DifferenceEngine( $context );
-                                       $engine->setText( $text, $this->difftotext );
+                                       $model = $title->getContentModel();
+
+                                       if ( $this->contentFormat
+                                               && !ContentHandler::getForModelID( $model )->isSupportedFormat( $this->contentFormat ) ) {
+
+                                               $name = $title->getPrefixedDBkey();
+
+                                               $this->dieUsage( "The requested format {$this->contentFormat} is not supported for ".
+                                                                                       "content model $model used by $name", 'badformat' );
+                                       }
+
+                                       $difftocontent = ContentHandler::makeContent( $this->difftotext, $title, $model, $this->contentFormat );
+
+                                       $engine = $handler->createDifferenceEngine( $context );
+                                       $engine->setContent( $content, $difftocontent );
                                } else {
-                                       $engine = new DifferenceEngine( $context, $revision->getID(), $this->diffto );
+                                       $engine = $handler->createDifferenceEngine( $context, $revision->getID(), $this->diffto );
                                        $vals['diff']['from'] = $engine->getOldid();
                                        $vals['diff']['to'] = $engine->getNewid();
                                }
@@ -568,6 +632,7 @@ class ApiQueryRevisions extends ApiQueryBase {
                                        'userid',
                                        'size',
                                        'sha1',
+                                       'contentmodel',
                                        'comment',
                                        'parsedcomment',
                                        'content',
@@ -617,6 +682,10 @@ class ApiQueryRevisions extends ApiQueryBase {
                        'continue' => null,
                        'diffto' => null,
                        'difftotext' => null,
+                       'contentformat' => array(
+                               ApiBase::PARAM_TYPE => ContentHandler::getAllContentFormats(),
+                               ApiBase::PARAM_DFLT => null
+                       ),
                );
        }
 
@@ -632,6 +701,7 @@ class ApiQueryRevisions extends ApiQueryBase {
                                ' userid         - User id of revision creator',
                                ' size           - Length (bytes) of the revision',
                                ' sha1           - SHA-1 (base 16) of the revision',
+                               ' contentmodel   - Content model id',
                                ' comment        - Comment by the user for revision',
                                ' parsedcomment  - Parsed comment by the user for the revision',
                                ' content        - Text of the revision',
@@ -656,6 +726,7 @@ class ApiQueryRevisions extends ApiQueryBase {
                        'difftotext' => array( 'Text to diff each revision to. Only diffs a limited number of revisions.',
                                "Overrides {$p}diffto. If {$p}section is set, only that section will be diffed against this text" ),
                        'tag' => 'Only list revisions tagged with this tag',
+                       'contentformat' => 'Serialization format used for difftotext and expected for output of content',
                );
        }
 
@@ -733,13 +804,18 @@ class ApiQueryRevisions extends ApiQueryBase {
        public function getPossibleErrors() {
                return array_merge( parent::getPossibleErrors(), array(
                        array( 'nosuchrevid', 'diffto' ),
-                       array( 'code' => 'revids', 'info' => 'The revids= parameter may not be used with the list options (limit, startid, endid, dirNewer, start, end).' ),
-                       array( 'code' => 'multpages', 'info' => 'titles, pageids or a generator was used to supply multiple pages, but the limit, startid, endid, dirNewer, user, excludeuser, start and end parameters may only be used on a single page.' ),
+                       array( 'code' => 'revids', 'info' => 'The revids= parameter may not be used with the list options '
+                                       . '(limit, startid, endid, dirNewer, start, end).' ),
+                       array( 'code' => 'multpages', 'info' => 'titles, pageids or a generator was used to supply multiple pages, '
+                                       . ' but the limit, startid, endid, dirNewer, user, excludeuser, '
+                                       . 'start and end parameters may only be used on a single page.' ),
                        array( 'code' => 'diffto', 'info' => 'rvdiffto must be set to a non-negative number, "prev", "next" or "cur"' ),
                        array( 'code' => 'badparams', 'info' => 'start and startid cannot be used together' ),
                        array( 'code' => 'badparams', 'info' => 'end and endid cannot be used together' ),
                        array( 'code' => 'badparams', 'info' => 'user and excludeuser cannot be used together' ),
                        array( 'code' => 'nosuchsection', 'info' => 'There is no section section in rID' ),
+                       array( 'code' => 'badformat', 'info' => 'The requested serialization format can not be applied '
+                                                                                                       . ' to the page\'s content model' ),
                ) );
        }
 
index c996251..2ee8641 100644 (file)
@@ -61,7 +61,13 @@ class ApiUndelete extends ApiBase {
                }
 
                $pa = new PageArchive( $titleObj );
-               $retval = $pa->undelete( ( isset( $params['timestamps'] ) ? $params['timestamps'] : array() ), $params['reason'] );
+               $retval = $pa->undelete(
+                       ( isset( $params['timestamps'] ) ? $params['timestamps'] : array() ),
+                       $params['reason'],
+                       array(),
+                       false,
+                       $this->getUser()
+               );
                if ( !is_array( $retval ) ) {
                        $this->dieUsageMsg( 'cannotundelete' );
                }
index 6bfeed3..fca071a 100644 (file)
@@ -33,6 +33,7 @@ class HTMLFileCache extends FileCacheBase {
         * Construct an ObjectFileCache from a Title and an action
         * @param $title Title|string Title object or prefixed DB key string
         * @param $action string
+        * @throws MWException
         * @return HTMLFileCache
         */
        public static function newFromTitle( $title, $action ) {
index f759c02..623f545 100644 (file)
@@ -74,7 +74,7 @@ class LinkCache {
         * Get a field of a title object from cache.
         * If this link is not good, it will return NULL.
         * @param $title Title
-        * @param $field String: ('length','redirect','revision')
+        * @param $field String: ('length','redirect','revision','model')
         * @return mixed
         */
        public function getGoodLinkFieldObj( $title, $field ) {
@@ -102,14 +102,16 @@ class LinkCache {
         * @param $len Integer: text's length
         * @param $redir Integer: whether the page is a redirect
         * @param $revision Integer: latest revision's ID
+        * @param $model Integer: latest revision's content model ID
         */
-       public function addGoodLinkObj( $id, $title, $len = -1, $redir = null, $revision = false ) {
+       public function addGoodLinkObj( $id, $title, $len = -1, $redir = null, $revision = false, $model = false ) {
                $dbkey = $title->getPrefixedDbKey();
                $this->mGoodLinks[$dbkey] = intval( $id );
                $this->mGoodLinkFields[$dbkey] = array(
                        'length' => intval( $len ),
                        'redirect' => intval( $redir ),
-                       'revision' => intval( $revision ) );
+                       'revision' => intval( $revision ),
+                       'model' => intval( $model ) );
        }
 
        /**
@@ -117,7 +119,7 @@ class LinkCache {
         * @since 1.19
         * @param $title Title
         * @param $row object which has the fields page_id, page_is_redirect,
-        *  page_latest
+        *  page_latest and page_content_model
         */
        public function addGoodLinkObjFromRow( $title, $row ) {
                $dbkey = $title->getPrefixedDbKey();
@@ -126,6 +128,7 @@ class LinkCache {
                        'length' => intval( $row->page_len ),
                        'redirect' => intval( $row->page_is_redirect ),
                        'revision' => intval( $row->page_latest ),
+                       'model' => !empty( $row->page_content_model ) ? strval( $row->page_content_model ) : null,
                );
        }
 
@@ -178,7 +181,8 @@ class LinkCache {
         * @return Integer
         */
        public function addLinkObj( $nt ) {
-               global $wgAntiLockFlags;
+               global $wgAntiLockFlags, $wgContentHandlerUseDB;
+
                wfProfileIn( __METHOD__ );
 
                $key = $nt->getPrefixedDBkey();
@@ -210,8 +214,10 @@ class LinkCache {
                        $options = array();
                }
 
-               $s = $db->selectRow( 'page',
-                       array( 'page_id', 'page_len', 'page_is_redirect', 'page_latest' ),
+               $f = array( 'page_id', 'page_len', 'page_is_redirect', 'page_latest' );
+               if ( $wgContentHandlerUseDB ) $f[] = 'page_content_model';
+
+               $s = $db->selectRow( 'page', $f,
                        array( 'page_namespace' => $nt->getNamespace(), 'page_title' => $nt->getDBkey() ),
                        __METHOD__, $options );
                # Set fields...
index b854a2e..e061101 100644 (file)
@@ -596,7 +596,7 @@ class MessageCache {
         * @param $key String: the message cache key
         * @param $useDB Boolean: get the message from the DB, false to use only
         *               the localisation
-        * @param $langcode String: code of the language to get the message for, if
+        * @param bool|string $langcode Code of the language to get the message for, if
         *                  it is a valid code create a language for that language,
         *                  if it is a string but not a valid code then make a basic
         *                  language object, if it is a false boolean then use the
@@ -607,6 +607,7 @@ class MessageCache {
         * @param $isFullKey Boolean: specifies whether $key is a two part key
         *                   "msg/lang".
         *
+        * @throws MWException
         * @return string|bool
         */
        function get( $key, $useDB = true, $langcode = true, $isFullKey = false ) {
@@ -770,16 +771,32 @@ class MessageCache {
                        Title::makeTitle( NS_MEDIAWIKI, $title ), false, Revision::READ_LATEST
                );
                if ( $revision ) {
-                       $message = $revision->getText();
-                       if ($message === false) {
+                       $content = $revision->getContent();
+                       if ( !$content ) {
                                // A possibly temporary loading failure.
                                wfDebugLog( 'MessageCache', __METHOD__ . ": failed to load message page text for {$title} ($code)" );
+                               $message = null; // no negative caching
                        } else {
-                               $this->mCache[$code][$title] = ' ' . $message;
-                               $this->mMemc->set( $titleKey, ' ' . $message, $this->mExpiry );
+                               // XXX: Is this the right way to turn a Content object into a message?
+                               // NOTE: $content is typically either WikitextContent, JavaScriptContent or CssContent.
+                               //       MessageContent is *not* used for storing messages, it's only used for wrapping them when needed.
+                               $message = $content->getWikitextForTransclusion();
+
+                               if ( $message === false || $message === null ) {
+                                       wfDebugLog( 'MessageCache', __METHOD__ . ": message content doesn't provide wikitext "
+                                                               . "(content model: " . $content->getContentHandler() . ")" );
+
+                                       $message = false; // negative caching
+                               } else {
+                                       $this->mCache[$code][$title] = ' ' . $message;
+                                       $this->mMemc->set( $titleKey, ' ' . $message, $this->mExpiry );
+                               }
                        }
                } else {
-                       $message = false;
+                       $message = false; // negative caching
+               }
+
+               if ( $message === false ) { // negative caching
                        $this->mCache[$code][$title] = '!NONEXISTENT';
                        $this->mMemc->set( $titleKey, '!NONEXISTENT', $this->mExpiry );
                }
index 93204ea..22c621f 100644 (file)
@@ -97,6 +97,7 @@ abstract class Conf {
         * Initialize a new child class based on a configuration array
         * @param $conf Array of configuration settings, see $wgConfiguration
         *   for details
+        * @throws MWException
         * @return Conf
         */
        private static function newFromSettings( $conf ) {
@@ -109,7 +110,8 @@ abstract class Conf {
 
        /**
         * Get the singleton if we don't want a specific wiki
-        * @param $wiki String An id for a remote wiki
+        * @param bool|string $wiki An id for a remote wiki
+        * @throws MWException
         * @return Conf child
         */
        public static function load( $wiki = false ) {
diff --git a/includes/content/AbstractContent.php b/includes/content/AbstractContent.php
new file mode 100644 (file)
index 0000000..860b4c3
--- /dev/null
@@ -0,0 +1,326 @@
+<?php
+/**
+ * A content object represents page content, e.g. the text to show on a page.
+ * Content objects have no knowledge about how they relate to Wiki pages.
+ *
+ * @since 1.21
+ */
+abstract class AbstractContent implements Content {
+
+       /**
+        * Name of the content model this Content object represents.
+        * Use with CONTENT_MODEL_XXX constants
+        *
+        * @var string $model_id
+        */
+       protected $model_id;
+
+       /**
+        * @param String $model_id
+        */
+       public function __construct( $model_id = null ) {
+               $this->model_id = $model_id;
+       }
+
+       /**
+        * @see Content::getModel()
+        */
+       public function getModel() {
+               return $this->model_id;
+       }
+
+       /**
+        * Throws an MWException if $model_id is not the id of the content model
+        * supported by this Content object.
+        *
+        * @param $model_id int the model to check
+        *
+        * @throws MWException
+        */
+       protected function checkModelID( $model_id ) {
+               if ( $model_id !== $this->model_id ) {
+                       throw new MWException( "Bad content model: " .
+                               "expected {$this->model_id}  " .
+                               "but got $model_id." );
+               }
+       }
+
+       /**
+        * @see Content::getContentHandler()
+        */
+       public function getContentHandler() {
+               return ContentHandler::getForContent( $this );
+       }
+
+       /**
+        * @see Content::getDefaultFormat()
+        */
+       public function getDefaultFormat() {
+               return $this->getContentHandler()->getDefaultFormat();
+       }
+
+       /**
+        * @see Content::getSupportedFormats()
+        */
+       public function getSupportedFormats() {
+               return $this->getContentHandler()->getSupportedFormats();
+       }
+
+       /**
+        * @see Content::isSupportedFormat()
+        */
+       public function isSupportedFormat( $format ) {
+               if ( !$format ) {
+                       return true; // this means "use the default"
+               }
+
+               return $this->getContentHandler()->isSupportedFormat( $format );
+       }
+
+       /**
+        * Throws an MWException if $this->isSupportedFormat( $format ) doesn't
+        * return true.
+        *
+        * @param $format
+        * @throws MWException
+        */
+       protected function checkFormat( $format ) {
+               if ( !$this->isSupportedFormat( $format ) ) {
+                       throw new MWException( "Format $format is not supported for content model " .
+                               $this->getModel() );
+               }
+       }
+
+       /**
+        * @see Content::serialize
+        */
+       public function serialize( $format = null ) {
+               return $this->getContentHandler()->serializeContent( $this, $format );
+       }
+
+       /**
+        * @see Content::isEmpty()
+        */
+       public function isEmpty() {
+               return $this->getSize() === 0;
+       }
+
+       /**
+        * @see Content::isValid()
+        */
+       public function isValid() {
+               return true;
+       }
+
+       /**
+        * @see Content::equals()
+        */
+       public function equals( Content $that = null ) {
+               if ( is_null( $that ) ) {
+                       return false;
+               }
+
+               if ( $that === $this ) {
+                       return true;
+               }
+
+               if ( $that->getModel() !== $this->getModel() ) {
+                       return false;
+               }
+
+               return $this->getNativeData() === $that->getNativeData();
+       }
+
+
+       /**
+        * Returns a list of DataUpdate objects for recording information about this
+        * Content in some secondary data store.
+        *
+        * This default implementation calls
+        * $this->getParserOutput( $content, $title, null, null, false ),
+        * and then calls getSecondaryDataUpdates( $title, $recursive ) on the
+        * resulting ParserOutput object.
+        *
+        * Subclasses may override this to determine the secondary data updates more
+        * efficiently, preferrably without the need to generate a parser output object.
+        *
+        * @see Content::getSecondaryDataUpdates()
+        *
+        * @param $title Title The context for determining the necessary updates
+        * @param $old Content|null An optional Content object representing the
+        *    previous content, i.e. the content being replaced by this Content
+        *    object.
+        * @param $recursive boolean Whether to include recursive updates (default:
+        *    false).
+        * @param $parserOutput ParserOutput|null Optional ParserOutput object.
+        *    Provide if you have one handy, to avoid re-parsing of the content.
+        *
+        * @return Array. A list of DataUpdate objects for putting information
+        *    about this content object somewhere.
+        *
+        * @since 1.21
+        */
+       public function getSecondaryDataUpdates( Title $title,
+               Content $old = null,
+               $recursive = true, ParserOutput $parserOutput = null
+       ) {
+               if ( !$parserOutput ) {
+                       $parserOutput = $this->getParserOutput( $title, null, null, false );
+               }
+
+               return $parserOutput->getSecondaryDataUpdates( $title, $recursive );
+       }
+
+
+       /**
+        * @see Content::getRedirectChain()
+        */
+       public function getRedirectChain() {
+               global $wgMaxRedirects;
+               $title = $this->getRedirectTarget();
+               if ( is_null( $title ) ) {
+                       return null;
+               }
+               // recursive check to follow double redirects
+               $recurse = $wgMaxRedirects;
+               $titles = array( $title );
+               while ( --$recurse > 0 ) {
+                       if ( $title->isRedirect() ) {
+                               $page = WikiPage::factory( $title );
+                               $newtitle = $page->getRedirectTarget();
+                       } else {
+                               break;
+                       }
+                       // Redirects to some special pages are not permitted
+                       if ( $newtitle instanceOf Title && $newtitle->isValidRedirectTarget() ) {
+                               // The new title passes the checks, so make that our current
+                               // title so that further recursion can be checked
+                               $title = $newtitle;
+                               $titles[] = $newtitle;
+                       } else {
+                               break;
+                       }
+               }
+               return $titles;
+       }
+
+       /**
+        * @see Content::getRedirectTarget()
+        */
+       public function getRedirectTarget() {
+               return null;
+       }
+
+       /**
+        * @see Content::getUltimateRedirectTarget()
+        * @note: migrated here from Title::newFromRedirectRecurse
+        */
+       public function getUltimateRedirectTarget() {
+               $titles = $this->getRedirectChain();
+               return $titles ? array_pop( $titles ) : null;
+       }
+
+       /**
+        * @see Content::isRedirect()
+        *
+        * @since 1.21
+        *
+        * @return bool
+        */
+       public function isRedirect() {
+               return $this->getRedirectTarget() !== null;
+       }
+
+       /**
+        * @see Content::updateRedirect()
+        *
+        * This default implementation always returns $this.
+        *
+        * @since 1.21
+        *
+        * @return Content $this
+        */
+       public function updateRedirect( Title $target ) {
+               return $this;
+       }
+
+       /**
+        * @see Content::getSection()
+        */
+       public function getSection( $sectionId ) {
+               return null;
+       }
+
+       /**
+        * @see Content::replaceSection()
+        */
+       public function replaceSection( $section, Content $with, $sectionTitle = ''  ) {
+               return null;
+       }
+
+       /**
+        * @see Content::preSaveTransform()
+        */
+       public function preSaveTransform( Title $title, User $user, ParserOptions $popts ) {
+               return $this;
+       }
+
+       /**
+        * @see Content::addSectionHeader()
+        */
+       public function addSectionHeader( $header ) {
+               return $this;
+       }
+
+       /**
+        * @see Content::preloadTransform()
+        */
+       public function preloadTransform( Title $title, ParserOptions $popts ) {
+               return $this;
+       }
+
+       /**
+        * @see  Content::prepareSave()
+        */
+       public function prepareSave( WikiPage $page, $flags, $baseRevId, User $user ) {
+               if ( $this->isValid() ) {
+                       return Status::newGood();
+               } else {
+                       return Status::newFatal( "invalid-content-data" );
+               }
+       }
+
+       /**
+        * @see  Content::getDeletionUpdates()
+        *
+        * @since 1.21
+        *
+        * @param $page \WikiPage the deleted page
+        * @param $parserOutput null|\ParserOutput optional parser output object
+        *    for efficient access to meta-information about the content object.
+        *    Provide if you have one handy.
+        *
+        * @return array A list of DataUpdate instances that will clean up the
+        *    database after deletion.
+        */
+       public function getDeletionUpdates( WikiPage $page,
+               ParserOutput $parserOutput = null )
+       {
+               return array(
+                       new LinksDeletionUpdate( $page ),
+               );
+       }
+
+       /**
+        * @see  Content::matchMagicWord()
+        *
+        * This default implementation always returns false. Subclasses may override this to supply matching logic.
+        *
+        * @param MagicWord $word
+        *
+        * @return bool
+        */
+       public function matchMagicWord( MagicWord $word ) {
+               return false;
+       }
+}
\ No newline at end of file
diff --git a/includes/content/Content.php b/includes/content/Content.php
new file mode 100644 (file)
index 0000000..6cb9d89
--- /dev/null
@@ -0,0 +1,466 @@
+<?php
+/**
+ * A content object represents page content, e.g. the text to show on a page.
+ * Content objects have no knowledge about how they relate to wiki pages.
+ *
+ * @since 1.21
+ */
+interface Content {
+
+       /**
+        * @since 1.21
+        *
+        * @return string A string representing the content in a way useful for
+        *   building a full text search index. If no useful representation exists,
+        *   this method returns an empty string.
+        *
+        * @todo: test that this actually works
+        * @todo: make sure this also works with LuceneSearch / WikiSearch
+        */
+       public function getTextForSearchIndex( );
+
+       /**
+        * @since 1.21
+        *
+        * @return string The wikitext to include when another page includes this
+        * content, or false if the content is not includable in a wikitext page.
+        *
+        * @TODO: allow native handling, bypassing wikitext representation, like
+        *    for includable special pages.
+        * @TODO: allow transclusion into other content models than Wikitext!
+        * @TODO: used in WikiPage and MessageCache to get message text. Not so
+        *    nice. What should we use instead?!
+        */
+       public function getWikitextForTransclusion( );
+
+       /**
+        * Returns a textual representation of the content suitable for use in edit
+        * summaries and log messages.
+        *
+        * @since 1.21
+        *
+        * @param $maxlength int Maximum length of the summary text
+        * @return   The summary text
+        */
+       public function getTextForSummary( $maxlength = 250 );
+
+       /**
+        * Returns native representation of the data. Interpretation depends on
+        * the data model used, as given by getDataModel().
+        *
+        * @since 1.21
+        *
+        * @return mixed The native representation of the content. Could be a
+        *    string, a nested array structure, an object, a binary blob...
+        *    anything, really.
+        *
+        * @NOTE: Caller must be aware of content model!
+        */
+       public function getNativeData( );
+
+       /**
+        * Returns the content's nominal size in bogo-bytes.
+        *
+        * @return int
+        */
+       public function getSize( );
+
+       /**
+        * Returns the ID of the content model used by this Content object.
+        * Corresponds to the CONTENT_MODEL_XXX constants.
+        *
+        * @since 1.21
+        *
+        * @return String The model id
+        */
+       public function getModel();
+
+       /**
+        * Convenience method that returns the ContentHandler singleton for handling
+        * the content model that this Content object uses.
+        *
+        * Shorthand for ContentHandler::getForContent( $this )
+        *
+        * @since 1.21
+        *
+        * @return ContentHandler
+        */
+       public function getContentHandler();
+
+       /**
+        * Convenience method that returns the default serialization format for the
+        * content model that this Content object uses.
+        *
+        * Shorthand for $this->getContentHandler()->getDefaultFormat()
+        *
+        * @since 1.21
+        *
+        * @return String
+        */
+       public function getDefaultFormat();
+
+       /**
+        * Convenience method that returns the list of serialization formats
+        * supported for the content model that this Content object uses.
+        *
+        * Shorthand for $this->getContentHandler()->getSupportedFormats()
+        *
+        * @since 1.21
+        *
+        * @return Array of supported serialization formats
+        */
+       public function getSupportedFormats();
+
+       /**
+        * Returns true if $format is a supported serialization format for this
+        * Content object, false if it isn't.
+        *
+        * Note that this should always return true if $format is null, because null
+        * stands for the default serialization.
+        *
+        * Shorthand for $this->getContentHandler()->isSupportedFormat( $format )
+        *
+        * @since 1.21
+        *
+        * @param $format string The format to check
+        * @return bool Whether the format is supported
+        */
+       public function isSupportedFormat( $format );
+
+       /**
+        * Convenience method for serializing this Content object.
+        *
+        * Shorthand for $this->getContentHandler()->serializeContent( $this, $format )
+        *
+        * @since 1.21
+        *
+        * @param $format null|string The desired serialization format (or null for
+        *    the default format).
+        * @return string Serialized form of this Content object
+        */
+       public function serialize( $format = null );
+
+       /**
+        * Returns true if this Content object represents empty content.
+        *
+        * @since 1.21
+        *
+        * @return bool Whether this Content object is empty
+        */
+       public function isEmpty();
+
+       /**
+        * Returns whether the content is valid. This is intended for local validity
+        * checks, not considering global consistency.
+        *
+        * Content needs to be valid before it can be saved.
+        *
+        * This default implementation always returns true.
+        *
+        * @since 1.21
+        *
+        * @return boolean
+        */
+       public function isValid();
+
+       /**
+        * Returns true if this Content objects is conceptually equivalent to the
+        * given Content object.
+        *
+        * Contract:
+        *
+        * - Will return false if $that is null.
+        * - Will return true if $that === $this.
+        * - Will return false if $that->getModel() != $this->getModel().
+        * - Will return false if $that->getNativeData() is not equal to $this->getNativeData(),
+        *   where the meaning of "equal" depends on the actual data model.
+        *
+        * Implementations should be careful to make equals() transitive and reflexive:
+        *
+        * - $a->equals( $b ) <=> $b->equals( $a )
+        * - $a->equals( $b ) &&  $b->equals( $c ) ==> $a->equals( $c )
+        *
+        * @since 1.21
+        *
+        * @param $that Content The Content object to compare to
+        * @return bool True if this Content object is equal to $that, false otherwise.
+        */
+       public function equals( Content $that = null );
+
+       /**
+        * Return a copy of this Content object. The following must be true for the
+        * object returned:
+        *
+        * if $copy = $original->copy()
+        *
+        * - get_class($original) === get_class($copy)
+        * - $original->getModel() === $copy->getModel()
+        * - $original->equals( $copy )
+        *
+        * If and only if the Content object is immutable, the copy() method can and
+        * should return $this. That is, $copy === $original may be true, but only
+        * for immutable content objects.
+        *
+        * @since 1.21
+        *
+        * @return Content. A copy of this object
+        */
+       public function copy( );
+
+       /**
+        * Returns true if this content is countable as a "real" wiki page, provided
+        * that it's also in a countable location (e.g. a current revision in the
+        * main namespace).
+        *
+        * @since 1.21
+        *
+        * @param $hasLinks Bool: If it is known whether this content contains
+        *    links, provide this information here, to avoid redundant parsing to
+        *    find out.
+        * @return boolean
+        */
+       public function isCountable( $hasLinks = null );
+
+
+       /**
+        * Parse the Content object and generate a ParserOutput from the result.
+        * $result->getText() can be used to obtain the generated HTML. If no HTML
+        * is needed, $generateHtml can be set to false; in that case,
+        * $result->getText() may return null.
+        *
+        * @param $title Title The page title to use as a context for rendering
+        * @param $revId null|int The revision being rendered (optional)
+        * @param $options null|ParserOptions Any parser options
+        * @param $generateHtml Boolean Whether to generate HTML (default: true). If false,
+        *        the result of calling getText() on the ParserOutput object returned by
+        *        this method is undefined.
+        *
+        * @since 1.21
+        *
+        * @return ParserOutput
+        */
+       public function getParserOutput( Title $title,
+               $revId = null,
+               ParserOptions $options = null, $generateHtml = true );
+       # TODO: make RenderOutput and RenderOptions base classes
+
+       /**
+        * Returns a list of DataUpdate objects for recording information about this
+        * Content in some secondary data store. If the optional second argument,
+        * $old, is given, the updates may model only the changes that need to be
+        * made to replace information about the old content with information about
+        * the new content.
+        *
+        * This default implementation calls
+        * $this->getParserOutput( $content, $title, null, null, false ),
+        * and then calls getSecondaryDataUpdates( $title, $recursive ) on the
+        * resulting ParserOutput object.
+        *
+        * Subclasses may implement this to determine the necessary updates more
+        * efficiently, or make use of information about the old content.
+        *
+        * @param $title Title The context for determining the necessary updates
+        * @param $old Content|null An optional Content object representing the
+        *    previous content, i.e. the content being replaced by this Content
+        *    object.
+        * @param $recursive boolean Whether to include recursive updates (default:
+        *    false).
+        * @param $parserOutput ParserOutput|null Optional ParserOutput object.
+        *    Provide if you have one handy, to avoid re-parsing of the content.
+        *
+        * @return Array. A list of DataUpdate objects for putting information
+        *    about this content object somewhere.
+        *
+        * @since 1.21
+        */
+       public function getSecondaryDataUpdates( Title $title,
+               Content $old = null,
+               $recursive = true, ParserOutput $parserOutput = null
+       );
+
+       /**
+        * Construct the redirect destination from this content and return an
+        * array of Titles, or null if this content doesn't represent a redirect.
+        * The last element in the array is the final destination after all redirects
+        * have been resolved (up to $wgMaxRedirects times).
+        *
+        * @since 1.21
+        *
+        * @return Array of Titles, with the destination last
+        */
+       public function getRedirectChain();
+
+       /**
+        * Construct the redirect destination from this content and return a Title,
+        * or null if this content doesn't represent a redirect.
+        * This will only return the immediate redirect target, useful for
+        * the redirect table and other checks that don't need full recursion.
+        *
+        * @since 1.21
+        *
+        * @return Title: The corresponding Title
+        */
+       public function getRedirectTarget();
+
+       /**
+        * Construct the redirect destination from this content and return the
+        * Title, or null if this content doesn't represent a redirect.
+        *
+        * This will recurse down $wgMaxRedirects times or until a non-redirect
+        * target is hit in order to provide (hopefully) the Title of the final
+        * destination instead of another redirect.
+        *
+        * There is usually no need to override the default behaviour, subclasses that
+        * want to implement redirects should override getRedirectTarget().
+        *
+        * @since 1.21
+        *
+        * @return Title
+        */
+       public function getUltimateRedirectTarget();
+
+       /**
+        * Returns whether this Content represents a redirect.
+        * Shorthand for getRedirectTarget() !== null.
+        *
+        * @since 1.21
+        *
+        * @return bool
+        */
+       public function isRedirect();
+
+       /**
+        * If this Content object is a redirect, this method updates the redirect target.
+        * Otherwise, it does nothing.
+        *
+        * @since 1.21
+        *
+        * @param Title $target the new redirect target
+        *
+        * @return Content a new Content object with the updated redirect (or $this if this Content object isn't a redirect)
+        */
+       public function updateRedirect( Title $target );
+
+       /**
+        * Returns the section with the given ID.
+        *
+        * @since 1.21
+        *
+        * @param $sectionId string The section's ID, given as a numeric string.
+        *    The ID "0" retrieves the section before the first heading, "1" the
+        *    text between the first heading (included) and the second heading
+        *    (excluded), etc.
+        * @return Content|Boolean|null The section, or false if no such section
+        *    exist, or null if sections are not supported.
+        */
+       public function getSection( $sectionId );
+
+       /**
+        * Replaces a section of the content and returns a Content object with the
+        * section replaced.
+        *
+        * @since 1.21
+        *
+        * @param $section Empty/null/false or a section number (0, 1, 2, T1, T2...), or "new"
+        * @param $with Content: new content of the section
+        * @param $sectionTitle String: new section's subject, only if $section is 'new'
+        * @return string Complete article text, or null if error
+        */
+       public function replaceSection( $section, Content $with, $sectionTitle = ''  );
+
+       /**
+        * Returns a Content object with pre-save transformations applied (or this
+        * object if no transformations apply).
+        *
+        * @since 1.21
+        *
+        * @param $title Title
+        * @param $user User
+        * @param $popts null|ParserOptions
+        * @return Content
+        */
+       public function preSaveTransform( Title $title, User $user, ParserOptions $popts );
+
+       /**
+        * Returns a new WikitextContent object with the given section heading
+        * prepended, if supported. The default implementation just returns this
+        * Content object unmodified, ignoring the section header.
+        *
+        * @since 1.21
+        *
+        * @param $header string
+        * @return Content
+        */
+       public function addSectionHeader( $header );
+
+       /**
+        * Returns a Content object with preload transformations applied (or this
+        * object if no transformations apply).
+        *
+        * @since 1.21
+        *
+        * @param $title Title
+        * @param $popts null|ParserOptions
+        * @return Content
+        */
+       public function preloadTransform( Title $title, ParserOptions $popts );
+
+       /**
+        * Prepare Content for saving. Called before Content is saved by WikiPage::doEditContent() and in
+        * similar places.
+        *
+        * This may be used to check the content's consistency with global state. This function should
+        * NOT write any information to the database.
+        *
+        * Note that this method will usually be called inside the same transaction bracket that will be used
+        * to save the new revision.
+        *
+        * Note that this method is called before any update to the page table is performed. This means that
+        * $page may not yet know a page ID.
+        *
+        * @param WikiPage $page The page to be saved.
+        * @param int      $flags bitfield for use with EDIT_XXX constants, see WikiPage::doEditContent()
+        * @param int      $baseRevId the ID of the current revision
+        * @param User     $user
+        *
+        * @return Status A status object indicating whether the content was successfully prepared for saving.
+        *                If the returned status indicates an error, a rollback will be performed and the
+        *                transaction aborted.
+        *
+        * @see see WikiPage::doEditContent()
+        */
+       public function prepareSave( WikiPage $page, $flags, $baseRevId, User $user );
+
+       /**
+        * Returns a list of updates to perform when this content is deleted.
+        * The necessary updates may be taken from the Content object, or depend on
+        * the current state of the database.
+        *
+        * @since 1.21
+        *
+        * @param $page \WikiPage the deleted page
+        * @param $parserOutput null|\ParserOutput optional parser output object
+        *    for efficient access to meta-information about the content object.
+        *    Provide if you have one handy.
+        *
+        * @return array A list of DataUpdate instances that will clean up the
+        *    database after deletion.
+        */
+       public function getDeletionUpdates( WikiPage $page,
+               ParserOutput $parserOutput = null );
+
+       /**
+        * Returns true if this Content object matches the given magic word.
+        *
+        * @param MagicWord $word the magic word to match
+        *
+        * @return bool whether this Content object matches the given magic word.
+        */
+       public function matchMagicWord( MagicWord $word );
+
+       # TODO: ImagePage and CategoryPage interfere with per-content action handlers
+       # TODO: nice&sane integration of GeSHi syntax highlighting
+       #   [11:59] <vvv> Hooks are ugly; make CodeHighlighter interface and a
+       #   config to set the class which handles syntax highlighting
+       #   [12:00] <vvv> And default it to a DummyHighlighter
+}
\ No newline at end of file
diff --git a/includes/content/ContentHandler.php b/includes/content/ContentHandler.php
new file mode 100644 (file)
index 0000000..00761cf
--- /dev/null
@@ -0,0 +1,1259 @@
+<?php
+
+/**
+ * Exception representing a failure to serialize or unserialize a content object.
+ */
+class MWContentSerializationException extends MWException {
+
+}
+
+/**
+ * A content handler knows how do deal with a specific type of content on a wiki
+ * page. Content is stored in the database in a serialized form (using a
+ * serialization format a.k.a. MIME type) and is unserialized into its native
+ * PHP representation (the content model), which is wrapped in an instance of
+ * the appropriate subclass of Content.
+ *
+ * ContentHandler instances are stateless singletons that serve, among other
+ * things, as a factory for Content objects. Generally, there is one subclass
+ * of ContentHandler and one subclass of Content for every type of content model.
+ *
+ * Some content types have a flat model, that is, their native representation
+ * is the same as their serialized form. Examples would be JavaScript and CSS
+ * code. As of now, this also applies to wikitext (MediaWiki's default content
+ * type), but wikitext content may be represented by a DOM or AST structure in
+ * the future.
+ *
+ * @since 1.21
+ */
+abstract class ContentHandler {
+
+       /**
+        * Convenience function for getting flat text from a Content object. This
+        * should only be used in the context of backwards compatibility with code
+        * that is not yet able to handle Content objects!
+        *
+        * If $content is null, this method returns the empty string.
+        *
+        * If $content is an instance of TextContent, this method returns the flat
+        * text as returned by $content->getNativeData().
+        *
+        * If $content is not a TextContent object, the behavior of this method
+        * depends on the global $wgContentHandlerTextFallback:
+        * - If $wgContentHandlerTextFallback is 'fail' and $content is not a
+        *   TextContent object, an MWException is thrown.
+        * - If $wgContentHandlerTextFallback is 'serialize' and $content is not a
+        *   TextContent object, $content->serialize() is called to get a string
+        *   form of the content.
+        * - If $wgContentHandlerTextFallback is 'ignore' and $content is not a
+        *   TextContent object, this method returns null.
+        * - otherwise, the behaviour is undefined.
+        *
+        * @since 1.21
+        * @deprecated since 1.21. Always try to use the content object.
+        *
+        * @static
+        * @param $content Content|null
+        * @return null|string the textual form of $content, if available
+        * @throws MWException if $content is not an instance of TextContent and
+        *   $wgContentHandlerTextFallback was set to 'fail'.
+        */
+       public static function getContentText( Content $content = null ) {
+               global $wgContentHandlerTextFallback;
+
+               if ( is_null( $content ) ) {
+                       return '';
+               }
+
+               if ( $content instanceof TextContent ) {
+                       return $content->getNativeData();
+               }
+
+               if ( $wgContentHandlerTextFallback == 'fail' ) {
+                       throw new MWException(
+                               "Attempt to get text from Content with model " .
+                               $content->getModel()
+                       );
+               }
+
+               if ( $wgContentHandlerTextFallback == 'serialize' ) {
+                       return $content->serialize();
+               }
+
+               return null;
+       }
+
+       /**
+        * Convenience function for creating a Content object from a given textual
+        * representation.
+        *
+        * $text will be deserialized into a Content object of the model specified
+        * by $modelId (or, if that is not given, $title->getContentModel()) using
+        * the given format.
+        *
+        * @since 1.21
+        *
+        * @static
+        *
+        * @param $text string the textual representation, will be
+        *    unserialized to create the Content object
+        * @param $title null|Title the title of the page this text belongs to.
+        *    Required if $modelId is not provided.
+        * @param $modelId null|string the model to deserialize to. If not provided,
+        *    $title->getContentModel() is used.
+        * @param $format null|string the format to use for deserialization. If not
+        *    given, the model's default format is used.
+        *
+        * @return Content a Content object representing $text
+        *
+        * @throw MWException if $model or $format is not supported or if $text can
+        *    not be unserialized using $format.
+        */
+       public static function makeContent( $text, Title $title = null,
+               $modelId = null, $format = null )
+       {
+               if ( is_null( $modelId ) ) {
+                       if ( is_null( $title ) ) {
+                               throw new MWException( "Must provide a Title object or a content model ID." );
+                       }
+
+                       $modelId = $title->getContentModel();
+               }
+
+               $handler = ContentHandler::getForModelID( $modelId );
+               return $handler->unserializeContent( $text, $format );
+       }
+
+       /**
+        * Returns the name of the default content model to be used for the page
+        * with the given title.
+        *
+        * Note: There should rarely be need to call this method directly.
+        * To determine the actual content model for a given page, use
+        * Title::getContentModel().
+        *
+        * Which model is to be used by default for the page is determined based
+        * on several factors:
+        * - The global setting $wgNamespaceContentModels specifies a content model
+        *   per namespace.
+        * - The hook ContentHandlerDefaultModelFor may be used to override the page's default
+        *   model.
+        * - Pages in NS_MEDIAWIKI and NS_USER default to the CSS or JavaScript
+        *   model if they end in .js or .css, respectively.
+        * - Pages in NS_MEDIAWIKI default to the wikitext model otherwise.
+        * - The hook TitleIsCssOrJsPage may be used to force a page to use the CSS
+        *   or JavaScript model. This is a compatibility feature. The ContentHandlerDefaultModelFor
+        *   hook should be used instead if possible.
+        * - The hook TitleIsWikitextPage may be used to force a page to use the
+        *   wikitext model. This is a compatibility feature. The ContentHandlerDefaultModelFor
+        *   hook should be used instead if possible.
+        *
+        * If none of the above applies, the wikitext model is used.
+        *
+        * Note: this is used by, and may thus not use, Title::getContentModel()
+        *
+        * @since 1.21
+        *
+        * @static
+        * @param $title Title
+        * @return null|string default model name for the page given by $title
+        */
+       public static function getDefaultModelFor( Title $title ) {
+               global $wgNamespaceContentModels;
+
+               // NOTE: this method must not rely on $title->getContentModel() directly or indirectly,
+               //       because it is used to initialize the mContentModel member.
+
+               $ns = $title->getNamespace();
+
+               $ext = false;
+               $m = null;
+               $model = null;
+
+               if ( !empty( $wgNamespaceContentModels[ $ns ] ) ) {
+                       $model = $wgNamespaceContentModels[ $ns ];
+               }
+
+               // Hook can determine default model
+               if ( !wfRunHooks( 'ContentHandlerDefaultModelFor', array( $title, &$model ) ) ) {
+                       if ( !is_null( $model ) ) {
+                               return $model;
+                       }
+               }
+
+               // Could this page contain custom CSS or JavaScript, based on the title?
+               $isCssOrJsPage = NS_MEDIAWIKI == $ns && preg_match( '!\.(css|js)$!u', $title->getText(), $m );
+               if ( $isCssOrJsPage ) {
+                       $ext = $m[1];
+               }
+
+               // Hook can force JS/CSS
+               wfRunHooks( 'TitleIsCssOrJsPage', array( $title, &$isCssOrJsPage ) );
+
+               // Is this a .css subpage of a user page?
+               $isJsCssSubpage = NS_USER == $ns
+                       && !$isCssOrJsPage
+                       && preg_match( "/\\/.*\\.(js|css)$/", $title->getText(), $m );
+               if ( $isJsCssSubpage ) {
+                       $ext = $m[1];
+               }
+
+               // Is this wikitext, according to $wgNamespaceContentModels or the DefaultModelFor hook?
+               $isWikitext = is_null( $model ) || $model == CONTENT_MODEL_WIKITEXT;
+               $isWikitext = $isWikitext && !$isCssOrJsPage && !$isJsCssSubpage;
+
+               // Hook can override $isWikitext
+               wfRunHooks( 'TitleIsWikitextPage', array( $title, &$isWikitext ) );
+
+               if ( !$isWikitext ) {
+                       switch ( $ext ) {
+                               case 'js':
+                                       return CONTENT_MODEL_JAVASCRIPT;
+                               case 'css':
+                                       return CONTENT_MODEL_CSS;
+                               default:
+                                       return is_null( $model ) ? CONTENT_MODEL_TEXT : $model;
+                       }
+               }
+
+               // We established that it must be wikitext
+
+               return CONTENT_MODEL_WIKITEXT;
+       }
+
+       /**
+        * Returns the appropriate ContentHandler singleton for the given title.
+        *
+        * @since 1.21
+        *
+        * @static
+        * @param $title Title
+        * @return ContentHandler
+        */
+       public static function getForTitle( Title $title ) {
+               $modelId = $title->getContentModel();
+               return ContentHandler::getForModelID( $modelId );
+       }
+
+       /**
+        * Returns the appropriate ContentHandler singleton for the given Content
+        * object.
+        *
+        * @since 1.21
+        *
+        * @static
+        * @param $content Content
+        * @return ContentHandler
+        */
+       public static function getForContent( Content $content ) {
+               $modelId = $content->getModel();
+               return ContentHandler::getForModelID( $modelId );
+       }
+
+       /**
+        * @var Array A Cache of ContentHandler instances by model id
+        */
+       static $handlers;
+
+       /**
+        * Returns the ContentHandler singleton for the given model ID. Use the
+        * CONTENT_MODEL_XXX constants to identify the desired content model.
+        *
+        * ContentHandler singletons are taken from the global $wgContentHandlers
+        * array. Keys in that array are model names, the values are either
+        * ContentHandler singleton objects, or strings specifying the appropriate
+        * subclass of ContentHandler.
+        *
+        * If a class name is encountered when looking up the singleton for a given
+        * model name, the class is instantiated and the class name is replaced by
+        * the resulting singleton in $wgContentHandlers.
+        *
+        * If no ContentHandler is defined for the desired $modelId, the
+        * ContentHandler may be provided by the ContentHandlerForModelID hook.
+        * If no ContentHandler can be determined, an MWException is raised.
+        *
+        * @since 1.21
+        *
+        * @static
+        * @param $modelId String The ID of the content model for which to get a
+        *    handler. Use CONTENT_MODEL_XXX constants.
+        * @return ContentHandler The ContentHandler singleton for handling the
+        *    model given by $modelId
+        * @throws MWException if no handler is known for $modelId.
+        */
+       public static function getForModelID( $modelId ) {
+               global $wgContentHandlers;
+
+               if ( isset( ContentHandler::$handlers[$modelId] ) ) {
+                       return ContentHandler::$handlers[$modelId];
+               }
+
+               if ( empty( $wgContentHandlers[$modelId] ) ) {
+                       $handler = null;
+
+                       wfRunHooks( 'ContentHandlerForModelID', array( $modelId, &$handler ) );
+
+                       if ( $handler === null ) {
+                               throw new MWException( "No handler for model #$modelId registered in \$wgContentHandlers" );
+                       }
+
+                       if ( !( $handler instanceof ContentHandler ) ) {
+                               throw new MWException( "ContentHandlerForModelID must supply a ContentHandler instance" );
+                       }
+               } else {
+                       $class = $wgContentHandlers[$modelId];
+                       $handler = new $class( $modelId );
+
+                       if ( !( $handler instanceof ContentHandler ) ) {
+                               throw new MWException( "$class from \$wgContentHandlers is not compatible with ContentHandler" );
+                       }
+               }
+
+               ContentHandler::$handlers[$modelId] = $handler;
+               return ContentHandler::$handlers[$modelId];
+       }
+
+       /**
+        * Returns the localized name for a given content model.
+        *
+        * Model names are localized using system messages. Message keys
+        * have the form content-model-$name, where $name is getContentModelName( $id ).
+        *
+        * @static
+        * @param $name String The content model ID, as given by a CONTENT_MODEL_XXX
+        *    constant or returned by Revision::getContentModel().
+        *
+        * @return string The content format's localized name.
+        * @throws MWException if the model id isn't known.
+        */
+       public static function getLocalizedName( $name ) {
+               $key = "content-model-$name";
+
+               $msg = wfMessage( $key );
+
+               return $msg->exists() ? $msg->plain() : $name;
+       }
+
+       public static function getContentModels() {
+               global $wgContentHandlers;
+
+               return array_keys( $wgContentHandlers );
+       }
+
+       public static function getAllContentFormats() {
+               global $wgContentHandlers;
+
+               $formats = array();
+
+               foreach ( $wgContentHandlers as $model => $class ) {
+                       $handler = ContentHandler::getForModelID( $model );
+                       $formats = array_merge( $formats, $handler->getSupportedFormats() );
+               }
+
+               $formats = array_unique( $formats );
+               return $formats;
+       }
+
+       // ------------------------------------------------------------------------
+
+       protected $mModelID;
+       protected $mSupportedFormats;
+
+       /**
+        * Constructor, initializing the ContentHandler instance with its model ID
+        * and a list of supported formats. Values for the parameters are typically
+        * provided as literals by subclass's constructors.
+        *
+        * @param $modelId String (use CONTENT_MODEL_XXX constants).
+        * @param $formats array List for supported serialization formats
+        *    (typically as MIME types)
+        */
+       public function __construct( $modelId, $formats ) {
+               $this->mModelID = $modelId;
+               $this->mSupportedFormats = $formats;
+
+               $this->mModelName = preg_replace( '/(Content)?Handler$/', '', get_class( $this ) );
+               $this->mModelName = preg_replace( '/[_\\\\]/', '', $this->mModelName );
+               $this->mModelName = strtolower( $this->mModelName );
+       }
+
+       /**
+        * Serializes a Content object of the type supported by this ContentHandler.
+        *
+        * @since 1.21
+        *
+        * @abstract
+        * @param $content Content The Content object to serialize
+        * @param $format null|String The desired serialization format
+        * @return string Serialized form of the content
+        */
+       public abstract function serializeContent( Content $content, $format = null );
+
+       /**
+        * Unserializes a Content object of the type supported by this ContentHandler.
+        *
+        * @since 1.21
+        *
+        * @abstract
+        * @param $blob string serialized form of the content
+        * @param $format null|String the format used for serialization
+        * @return Content the Content object created by deserializing $blob
+        */
+       public abstract function unserializeContent( $blob, $format = null );
+
+       /**
+        * Creates an empty Content object of the type supported by this
+        * ContentHandler.
+        *
+        * @since 1.21
+        *
+        * @return Content
+        */
+       public abstract function makeEmptyContent();
+
+       /**
+        * Creates a new Content object that acts as a redirect to the given page,
+        * or null of redirects are not supported by this content model.
+        *
+        * This default implementation always returns null. Subclasses supporting redirects
+        * must override this method.
+        *
+        * @since 1.21
+        *
+        * @param Title $destination the page to redirect to.
+        *
+        * @return Content
+        */
+       public function makeRedirectContent( Title $destination ) {
+               return null;
+       }
+
+       /**
+        * Returns the model id that identifies the content model this
+        * ContentHandler can handle. Use with the CONTENT_MODEL_XXX constants.
+        *
+        * @since 1.21
+        *
+        * @return String The model ID
+        */
+       public function getModelID() {
+               return $this->mModelID;
+       }
+
+       /**
+        * Throws an MWException if $model_id is not the ID of the content model
+        * supported by this ContentHandler.
+        *
+        * @since 1.21
+        *
+        * @param String $model_id The model to check
+        *
+        * @throws MWException
+        */
+       protected function checkModelID( $model_id ) {
+               if ( $model_id !== $this->mModelID ) {
+                       throw new MWException( "Bad content model: " .
+                               "expected {$this->mModelID} " .
+                               "but got $model_id." );
+               }
+       }
+
+       /**
+        * Returns a list of serialization formats supported by the
+        * serializeContent() and unserializeContent() methods of this
+        * ContentHandler.
+        *
+        * @since 1.21
+        *
+        * @return array of serialization formats as MIME type like strings
+        */
+       public function getSupportedFormats() {
+               return $this->mSupportedFormats;
+       }
+
+       /**
+        * The format used for serialization/deserialization by default by this
+        * ContentHandler.
+        *
+        * This default implementation will return the first element of the array
+        * of formats that was passed to the constructor.
+        *
+        * @since 1.21
+        *
+        * @return string the name of the default serialization format as a MIME type
+        */
+       public function getDefaultFormat() {
+               return $this->mSupportedFormats[0];
+       }
+
+       /**
+        * Returns true if $format is a serialization format supported by this
+        * ContentHandler, and false otherwise.
+        *
+        * Note that if $format is null, this method always returns true, because
+        * null means "use the default format".
+        *
+        * @since 1.21
+        *
+        * @param $format string the serialization format to check
+        * @return bool
+        */
+       public function isSupportedFormat( $format ) {
+
+               if ( !$format ) {
+                       return true; // this means "use the default"
+               }
+
+               return in_array( $format, $this->mSupportedFormats );
+       }
+
+       /**
+        * Throws an MWException if isSupportedFormat( $format ) is not true.
+        * Convenient for checking whether a format provided as a parameter is
+        * actually supported.
+        *
+        * @param $format string the serialization format to check
+        *
+        * @throws MWException
+        */
+       protected function checkFormat( $format ) {
+               if ( !$this->isSupportedFormat( $format ) ) {
+                       throw new MWException(
+                               "Format $format is not supported for content model "
+                               . $this->getModelID()
+                       );
+               }
+       }
+
+       /**
+        * Returns overrides for action handlers.
+        * Classes listed here will be used instead of the default one when
+        * (and only when) $wgActions[$action] === true. This allows subclasses
+        * to override the default action handlers.
+        *
+        * @since 1.21
+        *
+        * @return Array
+        */
+       public function getActionOverrides() {
+               return array();
+       }
+
+       /**
+        * Factory for creating an appropriate DifferenceEngine for this content model.
+        *
+        * @since 1.21
+        *
+        * @param $context IContextSource context to use, anything else will be
+        *    ignored
+        * @param $old Integer Old ID we want to show and diff with.
+        * @param $new int|string String either 'prev' or 'next'.
+        * @param $rcid Integer ??? FIXME (default 0)
+        * @param $refreshCache boolean If set, refreshes the diff cache
+        * @param $unhide boolean If set, allow viewing deleted revs
+        *
+        * @return DifferenceEngine
+        */
+       public function createDifferenceEngine( IContextSource $context,
+               $old = 0, $new = 0,
+               $rcid = 0, # FIXME: use everywhere!
+               $refreshCache = false, $unhide = false
+       ) {
+               $this->checkModelID( $context->getTitle()->getContentModel() );
+
+               $diffEngineClass = $this->getDiffEngineClass();
+
+               return new $diffEngineClass( $context, $old, $new, $rcid, $refreshCache, $unhide );
+       }
+
+       /**
+        * Get the language in which the content of the given page is written.
+        *
+        * This default implementation just returns $wgContLang (except for pages in the MediaWiki namespace)
+        *
+        * Note that the pages language is not cacheable, since it may in some cases depend on user settings.
+        *
+        * Also note that the page language may or may not depend on the actual content of the page,
+        * that is, this method may load the content in order to determine the language.
+        *
+        * @since 1.21
+        *
+        * @param Title        $title the page to determine the language for.
+        * @param Content|null $content the page's content, if you have it handy, to avoid reloading it.
+        *
+        * @return Language the page's language
+        */
+       public function getPageLanguage( Title $title, Content $content = null ) {
+               global $wgContLang;
+
+               if ( $title->getNamespace() == NS_MEDIAWIKI ) {
+                       // Parse mediawiki messages with correct target language
+                       list( /* $unused */, $lang ) = MessageCache::singleton()->figureMessage( $title->getText() );
+                       return wfGetLangObj( $lang );
+               }
+
+               return $wgContLang;
+       }
+
+       /**
+        * Get the language in which the content of this page is written when
+        * viewed by user. Defaults to $this->getPageLanguage(), but if the user
+        * specified a preferred variant, the variant will be used.
+        *
+        * This default implementation just returns $this->getPageLanguage( $title, $content ) unless
+        * the user specified a preferred variant.
+        *
+        * Note that the pages view language is not cacheable, since it depends on user settings.
+        *
+        * Also note that the page language may or may not depend on the actual content of the page,
+        * that is, this method may load the content in order to determine the language.
+        *
+        * @since 1.21
+        *
+        * @param Title        $title the page to determine the language for.
+        * @param Content|null $content the page's content, if you have it handy, to avoid reloading it.
+        *
+        * @return Language the page's language for viewing
+        */
+       public function getPageViewLanguage( Title $title, Content $content = null ) {
+               $pageLang = $this->getPageLanguage( $title, $content );
+
+               if ( $title->getNamespace() !== NS_MEDIAWIKI ) {
+                       // If the user chooses a variant, the content is actually
+                       // in a language whose code is the variant code.
+                       $variant = $pageLang->getPreferredVariant();
+                       if ( $pageLang->getCode() !== $variant ) {
+                               $pageLang = Language::factory( $variant );
+                       }
+               }
+
+               return $pageLang;
+       }
+
+       /**
+        * Determines whether the content type handled by this ContentHandler
+        * can be used on the given page.
+        *
+        * This default implementation always returns true.
+        * Subclasses may override this to restrict the use of this content model to specific locations,
+        * typically based on the namespace or some other aspect of the title, such as a special suffix
+        * (e.g. ".svg" for SVG content).
+        *
+        * @param Title $title the page's title.
+        *
+        * @return bool true if content of this kind can be used on the given page, false otherwise.
+        */
+       public function canBeUsedOn( Title $title ) {
+               return true;
+       }
+
+       /**
+        * Returns the name of the diff engine to use.
+        *
+        * @since 1.21
+        *
+        * @return string
+        */
+       protected function getDiffEngineClass() {
+               return 'DifferenceEngine';
+       }
+
+       /**
+        * Attempts to merge differences between three versions.
+        * Returns a new Content object for a clean merge and false for failure or
+        * a conflict.
+        *
+        * This default implementation always returns false.
+        *
+        * @since 1.21
+        *
+        * @param $oldContent Content|string  String
+        * @param $myContent Content|string   String
+        * @param $yourContent Content|string String
+        *
+        * @return Content|Bool
+        */
+       public function merge3( Content $oldContent, Content $myContent, Content $yourContent ) {
+               return false;
+       }
+
+       /**
+        * Return an applicable auto-summary if one exists for the given edit.
+        *
+        * @since 1.21
+        *
+        * @param $oldContent Content|null: the previous text of the page.
+        * @param $newContent Content|null: The submitted text of the page.
+        * @param $flags int Bit mask: a bit mask of flags submitted for the edit.
+        *
+        * @return string An appropriate auto-summary, or an empty string.
+        */
+       public function getAutosummary( Content $oldContent = null, Content $newContent = null, $flags ) {
+               global $wgContLang;
+
+               // Decide what kind of auto-summary is needed.
+
+               // Redirect auto-summaries
+
+               /**
+                * @var $ot Title
+                * @var $rt Title
+                */
+
+               $ot = !is_null( $oldContent ) ? $oldContent->getRedirectTarget() : null;
+               $rt = !is_null( $newContent ) ? $newContent->getRedirectTarget() : null;
+
+               if ( is_object( $rt ) ) {
+                       if ( !is_object( $ot )
+                               || !$rt->equals( $ot )
+                               || $ot->getFragment() != $rt->getFragment() )
+                       {
+                               $truncatedtext = $newContent->getTextForSummary(
+                                       250
+                                               - strlen( wfMessage( 'autoredircomment' )->inContentLanguage()->text() )
+                                               - strlen( $rt->getFullText() ) );
+
+                               return wfMessage( 'autoredircomment', $rt->getFullText() )
+                                               ->rawParams( $truncatedtext )->inContentLanguage()->text();
+                       }
+               }
+
+               // New page auto-summaries
+               if ( $flags & EDIT_NEW && $newContent->getSize() > 0 ) {
+                       // If they're making a new article, give its text, truncated, in
+                       // the summary.
+
+                       $truncatedtext = $newContent->getTextForSummary(
+                               200 - strlen( wfMessage( 'autosumm-new' )->inContentLanguage()->text() ) );
+
+                       return wfMessage( 'autosumm-new' )->rawParams( $truncatedtext )
+                                       ->inContentLanguage()->text();
+               }
+
+               // Blanking auto-summaries
+               if ( !empty( $oldContent ) && $oldContent->getSize() > 0 && $newContent->getSize() == 0 ) {
+                       return wfMessage( 'autosumm-blank' )->inContentLanguage()->text();
+               } elseif ( !empty( $oldContent )
+                       && $oldContent->getSize() > 10 * $newContent->getSize()
+                       && $newContent->getSize() < 500 )
+               {
+                       // Removing more than 90% of the article
+
+                       $truncatedtext = $newContent->getTextForSummary(
+                               200 - strlen( wfMessage( 'autosumm-replace' )->inContentLanguage()->text() ) );
+
+                       return wfMessage( 'autosumm-replace' )->rawParams( $truncatedtext )
+                                       ->inContentLanguage()->text();
+               }
+
+               // If we reach this point, there's no applicable auto-summary for our
+               // case, so our auto-summary is empty.
+               return '';
+       }
+
+       /**
+        * Auto-generates a deletion reason
+        *
+        * @since 1.21
+        *
+        * @param $title Title: the page's title
+        * @param &$hasHistory Boolean: whether the page has a history
+        * @return mixed String containing deletion reason or empty string, or
+        *    boolean false if no revision occurred
+        *
+        * @XXX &$hasHistory is extremely ugly, it's here because
+        * WikiPage::getAutoDeleteReason() and Article::getReason()
+        * have it / want it.
+        */
+       public function getAutoDeleteReason( Title $title, &$hasHistory ) {
+               $dbw = wfGetDB( DB_MASTER );
+
+               // Get the last revision
+               $rev = Revision::newFromTitle( $title );
+
+               if ( is_null( $rev ) ) {
+                       return false;
+               }
+
+               // Get the article's contents
+               $content = $rev->getContent();
+               $blank = false;
+
+               $this->checkModelID( $content->getModel() );
+
+               // If the page is blank, use the text from the previous revision,
+               // which can only be blank if there's a move/import/protect dummy
+               // revision involved
+               if ( $content->getSize() == 0 ) {
+                       $prev = $rev->getPrevious();
+
+                       if ( $prev )    {
+                               $content = $prev->getContent();
+                               $blank = true;
+                       }
+               }
+
+               // Find out if there was only one contributor
+               // Only scan the last 20 revisions
+               $res = $dbw->select( 'revision', 'rev_user_text',
+                       array(
+                               'rev_page' => $title->getArticleID(),
+                               $dbw->bitAnd( 'rev_deleted', Revision::DELETED_USER ) . ' = 0'
+                       ),
+                       __METHOD__,
+                       array( 'LIMIT' => 20 )
+               );
+
+               if ( $res === false ) {
+                       // This page has no revisions, which is very weird
+                       return false;
+               }
+
+               $hasHistory = ( $res->numRows() > 1 );
+               $row = $dbw->fetchObject( $res );
+
+               if ( $row ) { // $row is false if the only contributor is hidden
+                       $onlyAuthor = $row->rev_user_text;
+                       // Try to find a second contributor
+                       foreach ( $res as $row ) {
+                               if ( $row->rev_user_text != $onlyAuthor ) { // Bug 22999
+                                       $onlyAuthor = false;
+                                       break;
+                               }
+                       }
+               } else {
+                       $onlyAuthor = false;
+               }
+
+               // Generate the summary with a '$1' placeholder
+               if ( $blank ) {
+                       // The current revision is blank and the one before is also
+                       // blank. It's just not our lucky day
+                       $reason = wfMessage( 'exbeforeblank', '$1' )->inContentLanguage()->text();
+               } else {
+                       if ( $onlyAuthor ) {
+                               $reason = wfMessage(
+                                       'excontentauthor',
+                                       '$1',
+                                       $onlyAuthor
+                               )->inContentLanguage()->text();
+                       } else {
+                               $reason = wfMessage( 'excontent', '$1' )->inContentLanguage()->text();
+                       }
+               }
+
+               if ( $reason == '-' ) {
+                       // Allow these UI messages to be blanked out cleanly
+                       return '';
+               }
+
+               // Max content length = max comment length - length of the comment (excl. $1)
+               $text = $content->getTextForSummary( 255 - ( strlen( $reason ) - 2 ) );
+
+               // Now replace the '$1' placeholder
+               $reason = str_replace( '$1', $text, $reason );
+
+               return $reason;
+       }
+
+       /**
+        * Get the Content object that needs to be saved in order to undo all revisions
+        * between $undo and $undoafter. Revisions must belong to the same page,
+        * must exist and must not be deleted.
+        *
+        * @since 1.21
+        *
+        * @param $current Revision The current text
+        * @param $undo Revision The revision to undo
+        * @param $undoafter Revision Must be an earlier revision than $undo
+        *
+        * @return mixed String on success, false on failure
+        */
+       public function getUndoContent( Revision $current, Revision $undo, Revision $undoafter ) {
+               $cur_content = $current->getContent();
+
+               if ( empty( $cur_content ) ) {
+                       return false; // no page
+               }
+
+               $undo_content = $undo->getContent();
+               $undoafter_content = $undoafter->getContent();
+
+               $this->checkModelID( $cur_content->getModel() );
+               $this->checkModelID( $undo_content->getModel() );
+               $this->checkModelID( $undoafter_content->getModel() );
+
+               if ( $cur_content->equals( $undo_content ) ) {
+                       // No use doing a merge if it's just a straight revert.
+                       return $undoafter_content;
+               }
+
+               $undone_content = $this->merge3( $undo_content, $undoafter_content, $cur_content );
+
+               return $undone_content;
+       }
+
+       /**
+        * Get parser options suitable for rendering the primary article wikitext
+        *
+        * @param IContextSource|User|string $context One of the following:
+        *        - IContextSource: Use the User and the Language of the provided
+        *                                            context
+        *        - User: Use the provided User object and $wgLang for the language,
+        *                                            so use an IContextSource object if possible.
+        *        - 'canonical': Canonical options (anonymous user with default
+        *                                            preferences and content language).
+        *
+        * @param IContextSource|User|string $context
+        *
+        * @throws MWException
+        * @return ParserOptions
+        */
+       public function makeParserOptions( $context ) {
+               global $wgContLang;
+
+               if ( $context instanceof IContextSource ) {
+                       $options = ParserOptions::newFromContext( $context );
+               } elseif ( $context instanceof User ) { // settings per user (even anons)
+                       $options = ParserOptions::newFromUser( $context );
+               } elseif ( $context === 'canonical' ) { // canonical settings
+                       $options = ParserOptions::newFromUserAndLang( new User, $wgContLang );
+               } else {
+                       throw new MWException( "Bad context for parser options: $context" );
+               }
+
+               $options->enableLimitReport(); // show inclusion/loop reports
+               $options->setTidy( true ); // fix bad HTML
+
+               return $options;
+       }
+
+       /**
+        * Returns true for content models that support caching using the
+        * ParserCache mechanism. See WikiPage::isParserCacheUsed().
+        *
+        * @since 1.21
+        *
+        * @return bool
+        */
+       public function isParserCacheSupported() {
+               return false;
+       }
+
+       /**
+        * Returns true if this content model supports sections.
+        *
+        * This default implementation returns false.
+        *
+        * @return boolean whether sections are supported.
+        */
+       public function supportsSections() {
+               return false;
+       }
+
+       /**
+        * Call a legacy hook that uses text instead of Content objects.
+        * Will log a warning when a matching hook function is registered.
+        * If the textual representation of the content is changed by the
+        * hook function, a new Content object is constructed from the new
+        * text.
+        *
+        * @param $event String: event name
+        * @param $args Array: parameters passed to hook functions
+        * @param $warn bool: whether to log a warning (default: true). Should generally be true,
+        *                    may be set to false for testing.
+        *
+        * @return Boolean True if no handler aborted the hook
+        */
+       public static function runLegacyHooks( $event, $args = array(), $warn = true ) {
+               global $wgHooks; //@todo: once I39bd5de2 is merged, direct access to $wgHooks is no longer needed.
+
+               if ( !Hooks::isRegistered( $event ) && empty( $wgHooks[$event] ) ) {
+                       return true; // nothing to do here
+               }
+
+               if ( $warn ) {
+                       // Log information about which handlers are registered for the legacy hook,
+                       // so we can find and fix them.
+
+                       $handlers = Hooks::getHandlers( $event );
+                       $handlerInfo = array();
+
+                       wfSuppressWarnings();
+
+                       foreach ( $handlers as $handler ) {
+                               $info = '';
+
+                               if ( is_array( $handler ) ) {
+                                       if ( is_object( $handler[0] ) ) {
+                                               $info = get_class( $handler[0] );
+                                       } else {
+                                               $info = $handler[0];
+                                       }
+
+                                       if ( isset( $handler[1] ) ) {
+                                               $info .= '::' . $handler[1];
+                                       }
+                               } else if ( is_object( $handler ) ) {
+                                       $info = get_class( $handler[0] );
+                                       $info .= '::on' . $event;
+                               } else {
+                                       $info = $handler;
+                               }
+
+                               $handlerInfo[] = $info;
+                       }
+
+                       wfRestoreWarnings();
+
+                       wfWarn( "Using obsolete hook $event via ContentHandler::runLegacyHooks()! Handlers: " . implode(', ', $handlerInfo), 2 );
+               }
+
+               // convert Content objects to text
+               $contentObjects = array();
+               $contentTexts = array();
+
+               foreach ( $args as $k => $v ) {
+                       if ( $v instanceof Content ) {
+                               /* @var Content $v */
+
+                               $contentObjects[$k] = $v;
+
+                               $v = $v->serialize();
+                               $contentTexts[ $k ] = $v;
+                               $args[ $k ] = $v;
+                       }
+               }
+
+               // call the hook functions
+               $ok = wfRunHooks( $event, $args );
+
+               // see if the hook changed the text
+               foreach ( $contentTexts as $k => $orig ) {
+                       /* @var Content $content */
+
+                       $modified = $args[ $k ];
+                       $content = $contentObjects[$k];
+
+                       if ( $modified !== $orig ) {
+                               // text was changed, create updated Content object
+                               $content = $content->getContentHandler()->unserializeContent( $modified );
+                       }
+
+                       $args[ $k ] = $content;
+               }
+
+               return $ok;
+       }
+}
+
+/**
+ * @since 1.21
+ */
+abstract class TextContentHandler extends ContentHandler {
+
+       public function __construct( $modelId, $formats ) {
+               parent::__construct( $modelId, $formats );
+       }
+
+       /**
+        * Returns the content's text as-is.
+        *
+        * @param $content Content
+        * @param $format string|null
+        * @return mixed
+        */
+       public function serializeContent( Content $content, $format = null ) {
+               $this->checkFormat( $format );
+               return $content->getNativeData();
+       }
+
+       /**
+        * Attempts to merge differences between three versions. Returns a new
+        * Content object for a clean merge and false for failure or a conflict.
+        *
+        * All three Content objects passed as parameters must have the same
+        * content model.
+        *
+        * This text-based implementation uses wfMerge().
+        *
+        * @param $oldContent \Content|string  String
+        * @param $myContent \Content|string   String
+        * @param $yourContent \Content|string String
+        *
+        * @return Content|Bool
+        */
+       public function merge3( Content $oldContent, Content $myContent, Content $yourContent ) {
+               $this->checkModelID( $oldContent->getModel() );
+               $this->checkModelID( $myContent->getModel() );
+               $this->checkModelID( $yourContent->getModel() );
+
+               $format = $this->getDefaultFormat();
+
+               $old = $this->serializeContent( $oldContent, $format );
+               $mine = $this->serializeContent( $myContent, $format );
+               $yours = $this->serializeContent( $yourContent, $format );
+
+               $ok = wfMerge( $old, $mine, $yours, $result );
+
+               if ( !$ok ) {
+                       return false;
+               }
+
+               if ( !$result ) {
+                       return $this->makeEmptyContent();
+               }
+
+               $mergedContent = $this->unserializeContent( $result, $format );
+               return $mergedContent;
+       }
+
+}
+
+/**
+ * @since 1.21
+ */
+class WikitextContentHandler extends TextContentHandler {
+
+       public function __construct( $modelId = CONTENT_MODEL_WIKITEXT ) {
+               parent::__construct( $modelId, array( CONTENT_FORMAT_WIKITEXT ) );
+       }
+
+       public function unserializeContent( $text, $format = null ) {
+               $this->checkFormat( $format );
+
+               return new WikitextContent( $text );
+       }
+
+       /**
+        * @see ContentHandler::makeEmptyContent
+        *
+        * @return Content
+        */
+       public function makeEmptyContent() {
+               return new WikitextContent( '' );
+       }
+
+
+       /**
+        * Returns a WikitextContent object representing a redirect to the given destination page.
+        *
+        * @see ContentHandler::makeRedirectContent
+        *
+        * @param Title $destination the page to redirect to.
+        *
+        * @return Content
+        */
+       public function makeRedirectContent( Title $destination ) {
+               $mwRedir = MagicWord::get( 'redirect' );
+               $redirectText = $mwRedir->getSynonym( 0 ) . ' [[' . $destination->getPrefixedText() . "]]\n";
+
+               return new WikitextContent( $redirectText );
+       }
+
+       /**
+        * Returns true because wikitext supports sections.
+        *
+        * @return boolean whether sections are supported.
+        */
+       public function supportsSections() {
+               return true;
+       }
+
+       /**
+        * Returns true, because wikitext supports caching using the
+        * ParserCache mechanism.
+        *
+        * @since 1.21
+        * @return bool
+        */
+       public function isParserCacheSupported() {
+               return true;
+       }
+}
+
+# XXX: make ScriptContentHandler base class, do highlighting stuff there?
+
+/**
+ * @since 1.21
+ */
+class JavaScriptContentHandler extends TextContentHandler {
+
+       public function __construct( $modelId = CONTENT_MODEL_JAVASCRIPT ) {
+               parent::__construct( $modelId, array( CONTENT_FORMAT_JAVASCRIPT ) );
+       }
+
+       public function unserializeContent( $text, $format = null ) {
+               $this->checkFormat( $format );
+
+               return new JavaScriptContent( $text );
+       }
+
+       public function makeEmptyContent() {
+               return new JavaScriptContent( '' );
+       }
+
+       /**
+        * Returns the english language, because JS is english, and should be handled as such.
+        *
+        * @return Language wfGetLangObj( 'en' )
+        *
+        * @see ContentHandler::getPageLanguage()
+        */
+       public function getPageLanguage( Title $title, Content $content = null ) {
+               return wfGetLangObj( 'en' );
+       }
+
+       /**
+        * Returns the english language, because CSS is english, and should be handled as such.
+        *
+        * @return Language wfGetLangObj( 'en' )
+        *
+        * @see ContentHandler::getPageViewLanguage()
+        */
+       public function getPageViewLanguage( Title $title, Content $content = null ) {
+               return wfGetLangObj( 'en' );
+       }
+}
+
+/**
+ * @since 1.21
+ */
+class CssContentHandler extends TextContentHandler {
+
+       public function __construct( $modelId = CONTENT_MODEL_CSS ) {
+               parent::__construct( $modelId, array( CONTENT_FORMAT_CSS ) );
+       }
+
+       public function unserializeContent( $text, $format = null ) {
+               $this->checkFormat( $format );
+
+               return new CssContent( $text );
+       }
+
+       public function makeEmptyContent() {
+               return new CssContent( '' );
+       }
+
+       /**
+        * Returns the english language, because CSS is english, and should be handled as such.
+        *
+        * @return Language wfGetLangObj( 'en' )
+        *
+        * @see ContentHandler::getPageLanguage()
+        */
+       public function getPageLanguage( Title $title, Content $content = null ) {
+               return wfGetLangObj( 'en' );
+       }
+
+       /**
+        * Returns the english language, because CSS is english, and should be handled as such.
+        *
+        * @return Language wfGetLangObj( 'en' )
+        *
+        * @see ContentHandler::getPageViewLanguage()
+        */
+       public function getPageViewLanguage( Title $title, Content $content = null ) {
+               return wfGetLangObj( 'en' );
+       }
+}
diff --git a/includes/content/CssContent.php b/includes/content/CssContent.php
new file mode 100644 (file)
index 0000000..c881380
--- /dev/null
@@ -0,0 +1,38 @@
+<?php
+/**
+ * @since 1.21
+ */
+class CssContent extends TextContent {
+       public function __construct( $text ) {
+               parent::__construct( $text, CONTENT_MODEL_CSS );
+       }
+
+       /**
+        * Returns a Content object with pre-save transformations applied using
+        * Parser::preSaveTransform().
+        *
+        * @param $title Title
+        * @param $user User
+        * @param $popts ParserOptions
+        * @return Content
+        */
+       public function preSaveTransform( Title $title, User $user, ParserOptions $popts ) {
+               global $wgParser;
+               // @todo: make pre-save transformation optional for script pages
+
+               $text = $this->getNativeData();
+               $pst = $wgParser->preSaveTransform( $text, $title, $user, $popts );
+
+               return new CssContent( $pst );
+       }
+
+
+       protected function getHtml( ) {
+               $html = "";
+               $html .= "<pre class=\"mw-code mw-css\" dir=\"ltr\">\n";
+               $html .= $this->getHighlightHtml( );
+               $html .= "\n</pre>\n";
+
+               return $html;
+       }
+}
diff --git a/includes/content/JavaScriptContent.php b/includes/content/JavaScriptContent.php
new file mode 100644 (file)
index 0000000..ef0a4ac
--- /dev/null
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * @since 1.21
+ */
+class JavaScriptContent extends TextContent {
+       public function __construct( $text ) {
+               parent::__construct( $text, CONTENT_MODEL_JAVASCRIPT );
+       }
+
+       /**
+        * Returns a Content object with pre-save transformations applied using
+        * Parser::preSaveTransform().
+        *
+        * @param Title $title
+        * @param User $user
+        * @param ParserOptions $popts
+        * @return Content
+        */
+       public function preSaveTransform( Title $title, User $user, ParserOptions $popts ) {
+               global $wgParser;
+               // @todo: make pre-save transformation optional for script pages
+               // See bug #32858
+
+               $text = $this->getNativeData();
+               $pst = $wgParser->preSaveTransform( $text, $title, $user, $popts );
+
+               return new JavaScriptContent( $pst );
+       }
+
+
+       protected function getHtml( ) {
+               $html = "";
+               $html .= "<pre class=\"mw-code mw-js\" dir=\"ltr\">\n";
+               $html .= $this->getHighlightHtml( );
+               $html .= "\n</pre>\n";
+
+               return $html;
+       }
+}
\ No newline at end of file
diff --git a/includes/content/MessageContent.php b/includes/content/MessageContent.php
new file mode 100644 (file)
index 0000000..0e231d8
--- /dev/null
@@ -0,0 +1,132 @@
+<?php
+
+/**
+ * Wrapper allowing us to handle a system message as a Content object. Note that this is generally *not* used
+ * to represent content from the MediaWiki namespace, and that there is no MessageContentHandler. MessageContent
+ * is just intended as glue for wrapping a message programatically.
+ *
+ * @since 1.21
+ */
+class MessageContent extends AbstractContent {
+
+       /**
+        * @var Message
+        */
+       protected $mMessage;
+
+       /**
+        * @param Message|String $msg    A Message object, or a message key
+        * @param array|null     $params An optional array of message parameters
+        */
+       public function __construct( $msg, $params = null ) {
+               # XXX: messages may be wikitext, html or plain text! and maybe even something else entirely.
+               parent::__construct( CONTENT_MODEL_WIKITEXT );
+
+               if ( is_string( $msg ) ) {
+                       $this->mMessage = wfMessage( $msg );
+               } else {
+                       $this->mMessage = clone $msg;
+               }
+
+               if ( $params ) {
+                       $this->mMessage = $this->mMessage->params( $params );
+               }
+       }
+
+       /**
+        * Returns the message as rendered HTML
+        *
+        * @return string The message text, parsed into html
+        */
+       public function getHtml() {
+               return $this->mMessage->parse();
+       }
+
+       /**
+        * Returns the message as rendered HTML
+        *
+        * @return string The message text, parsed into html
+        */
+       public function getWikitext() {
+               return $this->mMessage->text();
+       }
+
+       /**
+        * Returns the message object, with any parameters already substituted.
+        *
+        * @return Message The message object.
+        */
+       public function getNativeData() {
+               //NOTE: Message objects are mutable. Cloning here makes MessageContent immutable.
+               return clone $this->mMessage;
+       }
+
+       /**
+        * @see Content::getTextForSearchIndex
+        */
+       public function getTextForSearchIndex() {
+               return $this->mMessage->plain();
+       }
+
+       /**
+        * @see Content::getWikitextForTransclusion
+        */
+       public function getWikitextForTransclusion() {
+               return $this->getWikitext();
+       }
+
+       /**
+        * @see Content::getTextForSummary
+        */
+       public function getTextForSummary( $maxlength = 250 ) {
+               return substr( $this->mMessage->plain(), 0, $maxlength );
+       }
+
+       /**
+        * @see Content::getSize
+        *
+        * @return int
+        */
+       public function getSize() {
+               return strlen( $this->mMessage->plain() );
+       }
+
+       /**
+        * @see Content::copy
+        *
+        * @return Content. A copy of this object
+        */
+       public function copy() {
+               // MessageContent is immutable (because getNativeData() returns a clone of the Message object)
+               return $this;
+       }
+
+       /**
+        * @see Content::isCountable
+        *
+        * @return bool false
+        */
+       public function isCountable( $hasLinks = null ) {
+               return false;
+       }
+
+       /**
+        * @see Content::getParserOutput
+        *
+        * @return ParserOutput
+        */
+       public function getParserOutput(
+               Title $title, $revId = null,
+               ParserOptions $options = null, $generateHtml = true
+       ) {
+
+               if ( $generateHtml ) {
+                       $html = $this->getHtml();
+               } else {
+                       $html = '';
+               }
+
+               $po = new ParserOutput( $html );
+               return $po;
+       }
+}
\ No newline at end of file
diff --git a/includes/content/TextContent.php b/includes/content/TextContent.php
new file mode 100644 (file)
index 0000000..68e6c39
--- /dev/null
@@ -0,0 +1,180 @@
+<?php
+
+/**
+ * Content object implementation for representing flat text.
+ *
+ * TextContent instances are immutable
+ *
+ * @since 1.21
+ */
+abstract class TextContent extends AbstractContent {
+
+       public function __construct( $text, $model_id = null ) {
+               parent::__construct( $model_id );
+
+               $this->mText = $text;
+       }
+
+       public function copy() {
+               return $this; # NOTE: this is ok since TextContent are immutable.
+       }
+
+       public function getTextForSummary( $maxlength = 250 ) {
+               global $wgContLang;
+
+               $text = $this->getNativeData();
+
+               $truncatedtext = $wgContLang->truncate(
+                       preg_replace( "/[\n\r]/", ' ', $text ),
+                       max( 0, $maxlength ) );
+
+               return $truncatedtext;
+       }
+
+       /**
+        * returns the text's size in bytes.
+        *
+        * @return int The size
+        */
+       public function getSize( ) {
+               $text = $this->getNativeData( );
+               return strlen( $text );
+       }
+
+       /**
+        * Returns true if this content is not a redirect, and $wgArticleCountMethod
+        * is "any".
+        *
+        * @param $hasLinks Bool: if it is known whether this content contains links,
+        * provide this information here, to avoid redundant parsing to find out.
+        *
+        * @return bool True if the content is countable
+        */
+       public function isCountable( $hasLinks = null ) {
+               global $wgArticleCountMethod;
+
+               if ( $this->isRedirect( ) ) {
+                       return false;
+               }
+
+               if ( $wgArticleCountMethod === 'any' ) {
+                       return true;
+               }
+
+               return false;
+       }
+
+       /**
+        * Returns the text represented by this Content object, as a string.
+        *
+        * @param   the raw text
+        */
+       public function getNativeData( ) {
+               $text = $this->mText;
+               return $text;
+       }
+
+       /**
+        * Returns the text represented by this Content object, as a string.
+        *
+        * @param   the raw text
+        */
+       public function getTextForSearchIndex( ) {
+               return $this->getNativeData();
+       }
+
+       /**
+        * Returns the text represented by this Content object, as a string.
+        *
+        * @param   the raw text
+        */
+       public function getWikitextForTransclusion( ) {
+               return $this->getNativeData();
+       }
+
+       /**
+        * Diff this content object with another content object..
+        *
+        * @since 1.21diff
+        *
+        * @param $that Content the other content object to compare this content object to
+        * @param $lang Language the language object to use for text segmentation.
+        *    If not given, $wgContentLang is used.
+        *
+        * @return DiffResult a diff representing the changes that would have to be
+        *    made to this content object to make it equal to $that.
+        */
+       public function diff( Content $that, Language $lang = null ) {
+               global $wgContLang;
+
+               $this->checkModelID( $that->getModel() );
+
+               # @todo: could implement this in DifferenceEngine and just delegate here?
+
+               if ( !$lang ) $lang = $wgContLang;
+
+               $otext = $this->getNativeData();
+               $ntext = $this->getNativeData();
+
+               # Note: Use native PHP diff, external engines don't give us abstract output
+               $ota = explode( "\n", $wgContLang->segmentForDiff( $otext ) );
+               $nta = explode( "\n", $wgContLang->segmentForDiff( $ntext ) );
+
+               $diff = new Diff( $ota, $nta );
+               return $diff;
+       }
+
+
+       /**
+        * Returns a generic ParserOutput object, wrapping the HTML returned by
+        * getHtml().
+        *
+        * @param $title Title Context title for parsing
+        * @param $revId int|null Revision ID (for {{REVISIONID}})
+        * @param $options ParserOptions|null Parser options
+        * @param $generateHtml bool Whether or not to generate HTML
+        *
+        * @return ParserOutput representing the HTML form of the text
+        */
+       public function getParserOutput( Title $title,
+               $revId = null,
+               ParserOptions $options = null, $generateHtml = true
+       ) {
+               # Generic implementation, relying on $this->getHtml()
+
+               if ( $generateHtml ) {
+                       $html = $this->getHtml();
+               } else {
+                       $html = '';
+               }
+
+               $po = new ParserOutput( $html );
+               return $po;
+       }
+
+       /**
+        * Generates an HTML version of the content, for display. Used by
+        * getParserOutput() to construct a ParserOutput object.
+        *
+        * This default implementation just calls getHighlightHtml(). Content
+        * models that have another mapping to HTML (as is the case for markup
+        * languages like wikitext) should override this method to generate the
+        * appropriate HTML.
+        *
+        * @return string An HTML representation of the content
+        */
+       protected function getHtml() {
+               return $this->getHighlightHtml();
+       }
+
+       /**
+        * Generates a syntax-highlighted version of the content, as HTML.
+        * Used by the default implementation of getHtml().
+        *
+        * @return string an HTML representation of the content's markup
+        */
+       protected function getHighlightHtml( ) {
+               # TODO: make Highlighter interface, use highlighter here, if available
+               return htmlspecialchars( $this->getNativeData() );
+       }
+}
\ No newline at end of file
diff --git a/includes/content/WikitextContent.php b/includes/content/WikitextContent.php
new file mode 100644 (file)
index 0000000..b660fc0
--- /dev/null
@@ -0,0 +1,289 @@
+<?php
+/**
+ * @since 1.21
+ */
+class WikitextContent extends TextContent {
+
+       public function __construct( $text ) {
+               parent::__construct( $text, CONTENT_MODEL_WIKITEXT );
+       }
+
+       /**
+        * @see Content::getSection()
+        */
+       public function getSection( $section ) {
+               global $wgParser;
+
+               $text = $this->getNativeData();
+               $sect = $wgParser->getSection( $text, $section, false );
+
+               return new WikitextContent( $sect );
+       }
+
+       /**
+        * @see Content::replaceSection()
+        */
+       public function replaceSection( $section, Content $with, $sectionTitle = '' ) {
+               wfProfileIn( __METHOD__ );
+
+               $myModelId = $this->getModel();
+               $sectionModelId = $with->getModel();
+
+               if ( $sectionModelId != $myModelId  ) {
+                       throw new MWException( "Incompatible content model for section: " .
+                               "document uses $myModelId but " .
+                               "section uses $sectionModelId." );
+               }
+
+               $oldtext = $this->getNativeData();
+               $text = $with->getNativeData();
+
+               if ( $section === '' ) {
+                       return $with; # XXX: copy first?
+               } if ( $section == 'new' ) {
+                       # Inserting a new section
+                       $subject = $sectionTitle ? wfMessage( 'newsectionheaderdefaultlevel' )
+                               ->rawParams( $sectionTitle )->inContentLanguage()->text() . "\n\n" : '';
+                       if ( wfRunHooks( 'PlaceNewSection', array( $this, $oldtext, $subject, &$text ) ) ) {
+                               $text = strlen( trim( $oldtext ) ) > 0
+                                       ? "{$oldtext}\n\n{$subject}{$text}"
+                                       : "{$subject}{$text}";
+                       }
+               } else {
+                       # Replacing an existing section; roll out the big guns
+                       global $wgParser;
+
+                       $text = $wgParser->replaceSection( $oldtext, $section, $text );
+               }
+
+               $newContent = new WikitextContent( $text );
+
+               wfProfileOut( __METHOD__ );
+               return $newContent;
+       }
+
+       /**
+        * Returns a new WikitextContent object with the given section heading
+        * prepended.
+        *
+        * @param $header string
+        * @return Content
+        */
+       public function addSectionHeader( $header ) {
+               $text = wfMessage( 'newsectionheaderdefaultlevel' )
+                       ->rawParams( $header )->inContentLanguage()->text();
+               $text .= "\n\n";
+               $text .= $this->getNativeData();
+
+               return new WikitextContent( $text );
+       }
+
+       /**
+        * Returns a Content object with pre-save transformations applied using
+        * Parser::preSaveTransform().
+        *
+        * @param $title Title
+        * @param $user User
+        * @param $popts ParserOptions
+        * @return Content
+        */
+       public function preSaveTransform( Title $title, User $user, ParserOptions $popts ) {
+               global $wgParser;
+
+               $text = $this->getNativeData();
+               $pst = $wgParser->preSaveTransform( $text, $title, $user, $popts );
+
+               return new WikitextContent( $pst );
+       }
+
+       /**
+        * Returns a Content object with preload transformations applied (or this
+        * object if no transformations apply).
+        *
+        * @param $title Title
+        * @param $popts ParserOptions
+        * @return Content
+        */
+       public function preloadTransform( Title $title, ParserOptions $popts ) {
+               global $wgParser;
+
+               $text = $this->getNativeData();
+               $plt = $wgParser->getPreloadText( $text, $title, $popts );
+
+               return new WikitextContent( $plt );
+       }
+
+       /**
+        * Implement redirect extraction for wikitext.
+        *
+        * @return null|Title
+        *
+        * @note: migrated here from Title::newFromRedirectInternal()
+        *
+        * @see Content::getRedirectTarget
+        * @see AbstractContent::getRedirectTarget
+        */
+       public function getRedirectTarget() {
+               global $wgMaxRedirects;
+               if ( $wgMaxRedirects < 1 ) {
+                       // redirects are disabled, so quit early
+                       return null;
+               }
+               $redir = MagicWord::get( 'redirect' );
+               $text = trim( $this->getNativeData() );
+               if ( $redir->matchStartAndRemove( $text ) ) {
+                       // Extract the first link and see if it's usable
+                       // Ensure that it really does come directly after #REDIRECT
+                       // Some older redirects included a colon, so don't freak about that!
+                       $m = array();
+                       if ( preg_match( '!^\s*:?\s*\[{2}(.*?)(?:\|.*?)?\]{2}!', $text, $m ) ) {
+                               // Strip preceding colon used to "escape" categories, etc.
+                               // and URL-decode links
+                               if ( strpos( $m[1], '%' ) !== false ) {
+                                       // Match behavior of inline link parsing here;
+                                       $m[1] = rawurldecode( ltrim( $m[1], ':' ) );
+                               }
+                               $title = Title::newFromText( $m[1] );
+                               // If the title is a redirect to bad special pages or is invalid, return null
+                               if ( !$title instanceof Title || !$title->isValidRedirectTarget() ) {
+                                       return null;
+                               }
+                               return $title;
+                       }
+               }
+               return null;
+       }
+
+       /**
+        * @see   Content::updateRedirect()
+        *
+        * This implementation replaces the first link on the page with the given new target
+        * if this Content object is a redirect. Otherwise, this method returns $this.
+        *
+        * @since 1.21
+        *
+        * @param Title $target
+        *
+        * @return Content a new Content object with the updated redirect (or $this if this Content object isn't a redirect)
+        */
+       public function updateRedirect( Title $target ) {
+               if ( !$this->isRedirect() ) {
+                       return $this;
+               }
+
+               # Fix the text
+               # Remember that redirect pages can have categories, templates, etc.,
+               # so the regex has to be fairly general
+               $newText = preg_replace( '/ \[ \[  [^\]]*  \] \] /x',
+                       '[[' . $target->getFullText() . ']]',
+                       $this->getNativeData(), 1 );
+
+               return new WikitextContent( $newText );
+       }
+
+       /**
+        * Returns true if this content is not a redirect, and this content's text
+        * is countable according to the criteria defined by $wgArticleCountMethod.
+        *
+        * @param $hasLinks Bool  if it is known whether this content contains
+        *    links, provide this information here, to avoid redundant parsing to
+        *    find out.
+        * @param $title null|\Title
+        *
+        * @internal param \IContextSource $context context for parsing if necessary
+        *
+        * @return bool True if the content is countable
+        */
+       public function isCountable( $hasLinks = null, Title $title = null ) {
+               global $wgArticleCountMethod;
+
+               if ( $this->isRedirect( ) ) {
+                       return false;
+               }
+
+               $text = $this->getNativeData();
+
+               switch ( $wgArticleCountMethod ) {
+                       case 'any':
+                               return true;
+                       case 'comma':
+                               return strpos( $text,  ',' ) !== false;
+                       case 'link':
+                               if ( $hasLinks === null ) { # not known, find out
+                                       if ( !$title ) {
+                                               $context = RequestContext::getMain();
+                                               $title = $context->getTitle();
+                                       }
+
+                                       $po = $this->getParserOutput( $title, null, null, false );
+                                       $links = $po->getLinks();
+                                       $hasLinks = !empty( $links );
+                               }
+
+                               return $hasLinks;
+               }
+
+               return false;
+       }
+
+       public function getTextForSummary( $maxlength = 250 ) {
+               $truncatedtext = parent::getTextForSummary( $maxlength );
+
+               # clean up unfinished links
+               # XXX: make this optional? wasn't there in autosummary, but required for
+               # deletion summary.
+               $truncatedtext = preg_replace( '/\[\[([^\]]*)\]?$/', '$1', $truncatedtext );
+
+               return $truncatedtext;
+       }
+
+       /**
+        * Returns a ParserOutput object resulting from parsing the content's text
+        * using $wgParser.
+        *
+        * @since    1.21
+        *
+        * @param $content Content the content to render
+        * @param $title \Title
+        * @param $revId null
+        * @param $options null|ParserOptions
+        * @param $generateHtml bool
+        *
+        * @internal param \IContextSource|null $context
+        * @return ParserOutput representing the HTML form of the text
+        */
+       public function getParserOutput( Title $title,
+               $revId = null,
+               ParserOptions $options = null, $generateHtml = true
+       ) {
+               global $wgParser;
+
+               if ( !$options ) {
+                       //NOTE: use canonical options per default to produce cacheable output
+                       $options = $this->getContentHandler()->makeParserOptions( 'canonical' );
+               }
+
+               $po = $wgParser->parse( $this->getNativeData(), $title, $options, true, true, $revId );
+               return $po;
+       }
+
+       protected function getHtml() {
+               throw new MWException(
+                       "getHtml() not implemented for wikitext. "
+                               . "Use getParserOutput()->getText()."
+               );
+       }
+
+       /**
+        * @see  Content::matchMagicWord()
+        *
+        * This implementation calls $word->match() on the this TextContent object's text.
+        *
+        * @param MagicWord $word
+        *
+        * @return bool whether this Content object matches the given magic word.
+        */
+       public function matchMagicWord( MagicWord $word ) {
+               return $word->match( $this->getNativeData() );
+       }
+}
\ No newline at end of file
index 5adf362..a4e3272 100644 (file)
@@ -222,6 +222,7 @@ class DerivativeContext extends ContextSource {
         * Set the Language object
         *
         * @param $l Mixed Language instance or language code
+        * @throws MWException
         * @since 1.19
         */
        public function setLanguage( $l ) {
index 1ffbc08..a528f22 100644 (file)
@@ -148,6 +148,7 @@ class RequestContext implements IContextSource {
         * canUseWikiPage() to check whether this method can be called safely.
         *
         * @since 1.19
+        * @throws MWException
         * @return WikiPage
         */
        public function getWikiPage() {
@@ -237,6 +238,7 @@ class RequestContext implements IContextSource {
         * Set the Language object
         *
         * @param $l Mixed Language instance or language code
+        * @throws MWException
         * @since 1.19
         */
        public function setLanguage( $l ) {
index 5271208..d703134 100644 (file)
@@ -266,6 +266,14 @@ abstract class DatabaseBase implements DatabaseType {
         */
        private $mTrxDoneWrites = false;
 
+       /**
+        * Record if the current transaction was started implicitly due to DBO_TRX being set.
+        *
+        * @var Bool
+        * @see DatabaseBase::mTrxLevel
+        */
+       private $mTrxAutomatic = false;
+
 # ------------------------------------------------------------------------------
 # Accessors
 # ------------------------------------------------------------------------------
@@ -745,8 +753,14 @@ abstract class DatabaseBase implements DatabaseType {
                $this->mOpened = false;
                if ( $this->mConn ) {
                        if ( $this->trxLevel() ) {
-                               $this->commit( __METHOD__ );
+                               if ( !$this->mTrxAutomatic ) {
+                                       wfWarn( "Transaction still in progress (from {$this->mTrxFname}), " .
+                                               " performing implicit commit before closing connection!" );
+                               }
+
+                               $this->commit( __METHOD__, true );
                        }
+
                        $ret = $this->closeConnection();
                        $this->mConn = false;
                        return $ret;
@@ -764,6 +778,7 @@ abstract class DatabaseBase implements DatabaseType {
 
        /**
         * @param $error String: fallback error message, used if none is given by DB
+        * @throws DBConnectionError
         */
        function reportConnectionError( $error = 'Unknown error' ) {
                $myError = $this->lastError();
@@ -813,9 +828,9 @@ abstract class DatabaseBase implements DatabaseType {
         *     comment (you can use __METHOD__ or add some extra info)
         * @param  $tempIgnore Boolean:   Whether to avoid throwing an exception on errors...
         *     maybe best to catch the exception instead?
+        * @throws MWException
         * @return boolean|ResultWrapper. true for a successful write query, ResultWrapper object
         *     for a successful read query, or false on failure if $tempIgnore set
-        * @throws DBQueryError Thrown when the database returns an error of any kind
         */
        public function query( $sql, $fname = '', $tempIgnore = false ) {
                $isMaster = !is_null( $this->getLBInfo( 'master' ) );
@@ -869,6 +884,7 @@ abstract class DatabaseBase implements DatabaseType {
                                        wfDebug("Implicit transaction start.\n");
                                }
                                $this->begin( __METHOD__ . " ($fname)" );
+                               $this->mTrxAutomatic = true;
                        }
                }
 
@@ -903,6 +919,7 @@ abstract class DatabaseBase implements DatabaseType {
                if ( false === $ret && $this->wasErrorReissuable() ) {
                        # Transaction is gone, like it or not
                        $this->mTrxLevel = 0;
+                       $this->trxIdleCallbacks = array(); // cancel
                        wfDebug( "Connection lost, reconnecting...\n" );
 
                        if ( $this->ping() ) {
@@ -942,6 +959,7 @@ abstract class DatabaseBase implements DatabaseType {
         * @param $sql String
         * @param $fname String
         * @param $tempIgnore Boolean
+        * @throws DBQueryError
         */
        public function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) {
                # Ignore errors during error handling to avoid infinite recursion
@@ -1028,6 +1046,7 @@ abstract class DatabaseBase implements DatabaseType {
         * while we're doing this.
         *
         * @param $matches Array
+        * @throws DBUnexpectedError
         * @return String
         */
        protected function fillPreparedArg( $matches ) {
@@ -1743,7 +1762,7 @@ abstract class DatabaseBase implements DatabaseType {
        /**
         * Makes an encoded list of strings from an array
         * @param $a Array containing the data
-        * @param $mode int Constant
+        * @param int $mode Constant
         *      - LIST_COMMA:          comma separated, no field names
         *      - LIST_AND:            ANDed WHERE clause (without the WHERE). See
         *        the documentation for $conds in DatabaseBase::select().
@@ -1751,6 +1770,7 @@ abstract class DatabaseBase implements DatabaseType {
         *      - LIST_SET:            comma separated with field names, like a SET clause
         *      - LIST_NAMES:          comma separated field names
         *
+        * @throws MWException|DBUnexpectedError
         * @return string
         */
        public function makeList( $a, $mode = LIST_COMMA ) {
@@ -2452,6 +2472,7 @@ abstract class DatabaseBase implements DatabaseType {
         *                    ANDed together in the WHERE clause
         * @param $fname      String: Calling function name (use __METHOD__) for
         *                    logs/profiling
+        * @throws DBUnexpectedError
         */
        public function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds,
                $fname = 'DatabaseBase::deleteJoin' )
@@ -2517,6 +2538,7 @@ abstract class DatabaseBase implements DatabaseType {
         *               the format. Use $conds == "*" to delete all rows
         * @param $fname String name of the calling function
         *
+        * @throws DBUnexpectedError
         * @return bool
         */
        public function delete( $table, $conds, $fname = 'DatabaseBase::delete' ) {
@@ -2615,6 +2637,7 @@ abstract class DatabaseBase implements DatabaseType {
         * @param $limit Integer the SQL limit
         * @param $offset Integer|bool the SQL offset (default false)
         *
+        * @throws DBUnexpectedError
         * @return string
         */
        public function limitResult( $sql, $limit, $offset = false ) {
@@ -2904,8 +2927,8 @@ abstract class DatabaseBase implements DatabaseType {
         * Note that when the DBO_TRX flag is set (which is usually the case for web requests, but not for maintenance scripts),
         * any previous database query will have started a transaction automatically.
         *
-        * Nesting of transactions is not supported. Attempts to nest transactions will cause warnings if DBO_TRX is not set
-        * or the extsting transaction contained write operations.
+        * Nesting of transactions is not supported. Attempts to nest transactions will cause a warning, unless the current
+        * transaction was started automatically because of the DBO_TRX flag.
         *
         * @param $fname string
         */
@@ -2913,21 +2936,20 @@ abstract class DatabaseBase implements DatabaseType {
                global $wgDebugDBTransactions;
 
                if ( $this->mTrxLevel ) { // implicit commit
-                       if ( $this->mTrxDoneWrites || ( $this->mFlags & DBO_TRX ) === 0 ) {
-                               // In theory, we should always warn about nesting BEGIN statements.
-                               // However, it is sometimes hard to avoid so we only warn if:
-                               //
-                               // a) the transaction has done writes. This gives warnings about bad transactions
-                               // that could cause partial writes but not about read queries seeing more
-                               // than one DB snapshot (when in REPEATABLE-READ) due to nested BEGINs.
-                               //
-                               // b) the DBO_TRX flag is not set. Explicit transactions should always be properly
-                               //    started and comitted.
-                               /*wfWarn( "$fname: Transaction already in progress (from {$this->mTrxFname}), " .
-                                       " performing implicit commit!" );*/
-                       } elseif ( $wgDebugDBTransactions ) {
-                               wfDebug( "$fname: Transaction already in progress (from {$this->mTrxFname}), " .
-                                       " performing implicit commit!\n" );
+                       if ( !$this->mTrxAutomatic ) {
+                               // We want to warn about inadvertently nested begin/commit pairs, but not about auto-committing
+                               // implicit transactions that were started by query() because DBO_TRX was set.
+
+                               wfWarn( "$fname: Transaction already in progress (from {$this->mTrxFname}), " .
+                                       " performing implicit commit!" );
+                       } else {
+                               // if the transaction was automatic and has done write operations,
+                               // log it if $wgDebugDBTransactions is enabled.
+
+                               if ( $this->mTrxDoneWrites && $wgDebugDBTransactions ) {
+                                       wfDebug( "$fname: Automatic transaction with writes in progress (from {$this->mTrxFname}), " .
+                                               " performing implicit commit!\n" );
+                               }
                        }
 
                        $this->doCommit( $fname );
@@ -2937,6 +2959,7 @@ abstract class DatabaseBase implements DatabaseType {
                $this->doBegin( $fname );
                $this->mTrxFname = $fname;
                $this->mTrxDoneWrites = false;
+               $this->mTrxAutomatic = false;
        }
 
        /**
@@ -2957,11 +2980,18 @@ abstract class DatabaseBase implements DatabaseType {
         * Nesting of transactions is not supported.
         *
         * @param $fname string
-        */
-       final public function commit( $fname = 'DatabaseBase::commit' ) {
-               if ( !$this->mTrxLevel ) {
-                       wfWarn( "$fname: No transaction to commit, something got out of sync!" );
+        * @param $suppressWarnings bool Suppress any warnings about open transactions (default_ false).
+        *        Only set this if you are absolutely sure that it is safe to ignore these warnings in your context.
+        */
+       final public function commit( $fname = 'DatabaseBase::commit', $suppressWarnings = false ) {
+               if ( !$suppressWarnings ) {
+                       if ( !$this->mTrxLevel ) {
+                               wfWarn( "$fname: No transaction to commit, something got out of sync!" );
+                       } elseif( $this->mTrxAutomatic ) {
+                               wfWarn( "$fname: Explicit commit of implicit transaction. Something may be out of sync!" );
+                       }
                }
+
                $this->doCommit( $fname );
                $this->runOnTransactionIdleCallbacks();
        }
@@ -3020,6 +3050,7 @@ abstract class DatabaseBase implements DatabaseType {
         * @param $newName String: name of table to be created
         * @param $temporary Boolean: whether the new table should be temporary
         * @param $fname String: calling function name
+        * @throws MWException
         * @return Boolean: true if operation was successful
         */
        public function duplicateTableStructure( $oldName, $newName, $temporary = false,
@@ -3034,6 +3065,7 @@ abstract class DatabaseBase implements DatabaseType {
         *
         * @param $prefix string Only show tables with this prefix, e.g. mw_
         * @param $fname String: calling function name
+        * @throws MWException
         */
        function listTables( $prefix = null, $fname = 'DatabaseBase::listTables' ) {
                throw new MWException( 'DatabaseBase::listTables is not implemented in descendant class' );
@@ -3177,10 +3209,11 @@ abstract class DatabaseBase implements DatabaseType {
         * on object's error ignore settings).
         *
         * @param $filename String: File name to open
-        * @param $lineCallback Callback: Optional function called before reading each line
-        * @param $resultCallback Callback: Optional function called for each MySQL result
-        * @param $fname String: Calling function name or false if name should be
+        * @param bool|callable $lineCallback Optional function called before reading each line
+        * @param bool|callable $resultCallback Optional function called for each MySQL result
+        * @param bool|string $fname Calling function name or false if name should be
         *      generated dynamically using $filename
+        * @throws MWException
         * @return bool|string
         */
        public function sourceFile(
index f1f6dfc..62c90d1 100644 (file)
@@ -496,6 +496,7 @@ class DatabaseIbm_db2 extends DatabaseBase {
         * @param $user String
         * @param $password String
         * @param $dbName String: database name
+        * @throws DBConnectionError
         * @return DatabaseBase a fresh connection
         */
        public function open( $server, $user, $password, $dbName ) {
@@ -622,6 +623,7 @@ class DatabaseIbm_db2 extends DatabaseBase {
        /**
         * The DBMS-dependent part of query()
         * @param  $sql String: SQL query.
+        * @throws DBUnexpectedError
         * @return object Result object for fetch functions or false on failure
         */
        protected function doQuery( $sql ) {
@@ -854,6 +856,9 @@ class DatabaseIbm_db2 extends DatabaseBase {
         *   LIST_SET           - comma separated with field names, like a SET clause
         *   LIST_NAMES         - comma separated field names
         *   LIST_SET_PREPARED  - like LIST_SET, except with ? tokens as values
+        * @param array $a
+        * @param int $mode
+        * @throws DBUnexpectedError
         * @return string
         */
        function makeList( $a, $mode = LIST_COMMA ) {
@@ -891,7 +896,8 @@ class DatabaseIbm_db2 extends DatabaseBase {
         *
         * @param $sql string SQL query we will append the limit too
         * @param $limit integer the SQL limit
-        * @param $offset integer the SQL offset (default false)
+        * @param bool|int $offset SQL offset (default false)
+        * @throws DBUnexpectedError
         * @return string
         */
        public function limitResult( $sql, $limit, $offset=false ) {
@@ -1173,6 +1179,10 @@ class DatabaseIbm_db2 extends DatabaseBase {
         * DELETE query wrapper
         *
         * Use $conds == "*" to delete all rows
+        * @param array $table
+        * @param array|string $conds
+        * @param string $fname
+        * @throws DBUnexpectedError
         * @return bool|\ResultWrapper
         */
        public function delete( $table, $conds, $fname = 'DatabaseIbm_db2::delete' ) {
@@ -1247,6 +1257,7 @@ class DatabaseIbm_db2 extends DatabaseBase {
        /**
         * Frees memory associated with a statement resource
         * @param $res Object: statement resource to free
+        * @throws DBUnexpectedError
         * @return Boolean success or failure
         */
        public function freeResult( $res ) {
index 914ab40..ff67f47 100644 (file)
@@ -61,6 +61,11 @@ class DatabaseMssql extends DatabaseBase {
 
        /**
         * Usually aborts on failure
+        * @param String $server
+        * @param String $user
+        * @param String $password
+        * @param String $dbName
+        * @throws DBConnectionError
         * @return bool|DatabaseBase|null
         */
        function open( $server, $user, $password, $dbName ) {
@@ -380,6 +385,11 @@ class DatabaseMssql extends DatabaseBase {
         *
         * Usually aborts on failure
         * If errors are explicitly ignored, returns success
+        * @param String $table
+        * @param Array $arrToInsert
+        * @param string $fname
+        * @param array $options
+        * @throws DBQueryError
         * @return bool
         */
        function insert( $table, $arrToInsert, $fname = 'DatabaseMssql::insert', $options = array() ) {
@@ -510,6 +520,14 @@ class DatabaseMssql extends DatabaseBase {
         * Source items may be literals rather than field names, but strings should be quoted with Database::addQuotes()
         * $conds may be "*" to copy the whole table
         * srcTable may be an array of tables.
+        * @param string $destTable
+        * @param array|string $srcTable
+        * @param array $varMap
+        * @param array $conds
+        * @param string $fname
+        * @param array $insertOptions
+        * @param array $selectOptions
+        * @throws DBQueryError
         * @return null|\ResultWrapper
         */
        function insertSelect( $destTable, $srcTable, $varMap, $conds, $fname = 'DatabaseMssql::insertSelect',
@@ -720,6 +738,8 @@ class DatabaseMssql extends DatabaseBase {
         * Escapes a identifier for use inm SQL.
         * Throws an exception if it is invalid.
         * Reference: http://msdn.microsoft.com/en-us/library/aa224033%28v=SQL.80%29.aspx
+        * @param $identifier
+        * @throws MWException
         * @return string
         */
        private function escapeIdentifier( $identifier ) {
index 7f389da..b509302 100644 (file)
@@ -805,7 +805,8 @@ class DatabaseMysql extends DatabaseBase {
         * @param $delVar string
         * @param $joinVar string
         * @param $conds array|string
-        * @param $fname bool
+        * @param bool|string $fname bool
+        * @throws DBUnexpectedError
         * @return bool|ResultWrapper
         */
        function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = 'DatabaseBase::deleteJoin' ) {
@@ -951,13 +952,6 @@ class DatabaseMysql extends DatabaseBase {
 
 }
 
-/**
- * Legacy support: Database == DatabaseMysql
- *
- * @deprecated in 1.16
- */
-class Database extends DatabaseMysql {}
-
 /**
  * Utility class.
  * @ingroup Database
index 7d8884f..aa4da0f 100644 (file)
@@ -241,6 +241,11 @@ class DatabaseOracle extends DatabaseBase {
 
        /**
         * Usually aborts on failure
+        * @param string $server
+        * @param string $user
+        * @param string $password
+        * @param string $dbName
+        * @throws DBConnectionError
         * @return DatabaseBase|null
         */
        function open( $server, $user, $password, $dbName ) {
index 457bf38..419488e 100644 (file)
@@ -325,6 +325,11 @@ class DatabasePostgres extends DatabaseBase {
 
        /**
         * Usually aborts on failure
+        * @param string $server
+        * @param string $user
+        * @param string $password
+        * @param string $dbName
+        * @throws DBConnectionError
         * @return DatabaseBase|null
         */
        function open( $server, $user, $password, $dbName ) {
index f1e553d..1125d4f 100644 (file)
@@ -79,11 +79,12 @@ class DatabaseSqlite extends DatabaseBase {
        /** Open an SQLite database and return a resource handle to it
         *  NOTE: only $dbName is used, the other parameters are irrelevant for SQLite databases
         *
-        * @param $server
-        * @param $user
-        * @param $pass
-        * @param $dbName
+        * @param string $server
+        * @param string $user
+        * @param string $pass
+        * @param string $dbName
         *
+        * @throws DBConnectionError
         * @return PDO
         */
        function open( $server, $user, $pass, $dbName ) {
@@ -103,6 +104,7 @@ class DatabaseSqlite extends DatabaseBase {
         *
         * @param $fileName string
         *
+        * @throws DBConnectionError
         * @return PDO|bool SQL connection or false if failed
         */
        function openFile( $fileName ) {
index 6008813..9b468a7 100644 (file)
@@ -70,6 +70,7 @@ class LBFactory_Multi extends LBFactory {
 
        /**
         * @param $conf array
+        * @throws MWException
         */
        function __construct( $conf ) {
                $this->chronProt = new ChronologyProtector;
@@ -153,8 +154,9 @@ class LBFactory_Multi extends LBFactory {
        }
 
        /**
-        * @param $cluster
-        * @param $wiki
+        * @param String $cluster
+        * @param bool $wiki
+        * @throws MWException
         * @return LoadBalancer
         */
        function newExternalLB( $cluster, $wiki = false ) {
index 195d4ec..cac7046 100644 (file)
@@ -41,6 +41,7 @@ class LoadBalancer {
         *    servers           Required. Array of server info structures.
         *    masterWaitTimeout Replication lag wait timeout
         *    loadMonitor       Name of a class used to fetch server lag and load.
+        * @throws MWException
         */
        function __construct( $params ) {
                if ( !isset( $params['servers'] ) ) {
@@ -197,6 +198,7 @@ class LoadBalancer {
         * Side effect: opens connections to databases
         * @param $group bool
         * @param $wiki bool
+        * @throws MWException
         * @return bool|int|string
         */
        function getReaderIndex( $group = false, $wiki = false ) {
@@ -452,8 +454,9 @@ class LoadBalancer {
         *
         * @param $i Integer: server index
         * @param $groups Array: query groups
-        * @param $wiki String: wiki ID
+        * @param bool|string $wiki Wiki ID
         *
+        * @throws MWException
         * @return DatabaseBase
         */
        public function &getConnection( $i, $groups = array(), $wiki = false ) {
@@ -520,6 +523,7 @@ class LoadBalancer {
         * the same number of times as getConnection() to work.
         *
         * @param DatabaseBase $conn
+        * @throws MWException
         */
        public function reuseConnection( $conn ) {
                $serverIndex = $conn->getLBInfo('serverIndex');
@@ -692,6 +696,7 @@ class LoadBalancer {
         *
         * @param $server
         * @param $dbNameOverride bool
+        * @throws MWException
         * @return DatabaseBase
         */
        function reallyOpenConnection( $server, $dbNameOverride = false ) {
@@ -902,7 +907,9 @@ class LoadBalancer {
                foreach ( $this->mConns as $conns2 ) {
                        foreach ( $conns2 as $conns3 ) {
                                foreach ( $conns3 as $conn ) {
-                                       $conn->commit( __METHOD__ );
+                                       if ( $conn->trxLevel() ) {
+                                               $conn->commit( __METHOD__, true );
+                                       }
                                }
                        }
                }
@@ -920,7 +927,7 @@ class LoadBalancer {
                        }
                        foreach ( $conns2[$masterIndex] as $conn ) {
                                if ( $conn->trxLevel() && $conn->doneWrites() ) {
-                                       $conn->commit( __METHOD__ );
+                                       $conn->commit( __METHOD__, true );
                                }
                        }
                }
index 31fdc6d..18dcfe9 100644 (file)
@@ -38,7 +38,7 @@ class DifferenceEngine extends ContextSource {
         * @private
         */
        var $mOldid, $mNewid;
-       var $mOldtext, $mNewtext;
+       var $mOldContent, $mNewContent;
        protected $mDiffLang;
 
        /**
@@ -224,6 +224,10 @@ class DifferenceEngine extends ContextSource {
                # we'll use the application/x-external-editor interface to call
                # an external diff tool like kompare, kdiff3, etc.
                if ( ExternalEdit::useExternalEngine( $this->getContext(), 'diff' ) ) {
+                       //TODO: come up with a good solution for non-text content here.
+                       //      at least, the content format needs to be passed to the client somehow.
+                       //      Currently, action=raw will just fail for non-text content.
+
                        $urls = array(
                                'File' => array( 'Extension' => 'wiki', 'URL' =>
                                        # This should be mOldPage, but it may not be set, see below.
@@ -510,19 +514,21 @@ class DifferenceEngine extends ContextSource {
                        $out->setRevisionTimestamp( $this->mNewRev->getTimestamp() );
                        $out->setArticleFlag( true );
 
+                       // NOTE: only needed for B/C: custom rendering of JS/CSS via hook
                        if ( $this->mNewPage->isCssJsSubpage() || $this->mNewPage->isCssOrJsPage() ) {
                                // Stolen from Article::view --AG 2007-10-11
                                // Give hooks a chance to customise the output
                                // @TODO: standardize this crap into one function
-                               if ( wfRunHooks( 'ShowRawCssJs', array( $this->mNewtext, $this->mNewPage, $out ) ) ) {
-                                       // Wrap the whole lot in a <pre> and don't parse
-                                       $m = array();
-                                       preg_match( '!\.(css|js)$!u', $this->mNewPage->getText(), $m );
-                                       $out->addHTML( "<pre class=\"mw-code mw-{$m[1]}\" dir=\"ltr\">\n" );
-                                       $out->addHTML( htmlspecialchars( $this->mNewtext ) );
-                                       $out->addHTML( "\n</pre>\n" );
+                               if ( ContentHandler::runLegacyHooks( 'ShowRawCssJs', array( $this->mNewContent, $this->mNewPage, $out ) ) ) {
+                                       // NOTE: deprecated hook, B/C only
+                                       // use the content object's own rendering
+                                       $po = $this->mNewRev->getContent()->getParserOutput( $this->mNewRev->getTitle(), $this->mNewRev->getId() );
+                                       $out->addHTML( $po->getText() );
                                }
-                       } elseif ( !wfRunHooks( 'ArticleViewCustom', array( $this->mNewtext, $this->mNewPage, $out ) ) ) {
+                       } elseif( !wfRunHooks( 'ArticleContentViewCustom', array( $this->mNewContent, $this->mNewPage, $out ) ) ) {
+                               // Handled by extension
+                       } elseif( !ContentHandler::runLegacyHooks( 'ArticleViewCustom', array( $this->mNewContent, $this->mNewPage, $out ) ) ) {
+                               // NOTE: deprecated hook, B/C only
                                // Handled by extension
                        } else {
                                // Normal page
@@ -536,13 +542,7 @@ class DifferenceEngine extends ContextSource {
                                        $wikiPage = WikiPage::factory( $this->mNewPage );
                                }
 
-                               $parserOptions = $wikiPage->makeParserOptions( $this->getContext() );
-
-                               if ( !$this->mNewRev->isCurrent() ) {
-                                       $parserOptions->setEditSection( false );
-                               }
-
-                               $parserOutput = $wikiPage->getParserOutput( $parserOptions, $this->mNewid );
+                               $parserOutput = $this->getParserOutput( $wikiPage, $this->mNewRev );
 
                                # WikiPage::getParserOutput() should not return false, but just in case
                                if( $parserOutput ) {
@@ -556,6 +556,17 @@ class DifferenceEngine extends ContextSource {
                wfProfileOut( __METHOD__ );
        }
 
+       protected function getParserOutput( WikiPage $page, Revision $rev ) {
+               $parserOptions = $page->makeParserOptions( $this->getContext() );
+
+               if ( !$rev->isCurrent() || !$rev->getTitle()->quickUserCan( "edit" ) ) {
+                       $parserOptions->setEditSection( false );
+               }
+
+               $parserOutput = $page->getParserOutput( $parserOptions, $rev->getId() );
+               return $parserOutput;
+       }
+
        /**
         * Get the diff text, send it to the OutputPage object
         * Returns false if the diff could not be generated, otherwise returns true
@@ -652,7 +663,7 @@ class DifferenceEngine extends ContextSource {
                        return false;
                }
 
-               $difftext = $this->generateDiffBody( $this->mOldtext, $this->mNewtext );
+               $difftext = $this->generateContentDiffBody( $this->mOldContent, $this->mNewContent );
 
                // Save to cache for 7 days
                if ( !wfRunHooks( 'AbortDiffCache', array( &$this ) ) ) {
@@ -689,14 +700,56 @@ class DifferenceEngine extends ContextSource {
                }
        }
 
+       /**
+        * Generate a diff, no caching.
+        *
+        * Subclasses may override this to provide a
+        *
+        * @param $old Content: old content
+        * @param $new Content: new content
+        *
+        * @since 1.21
+        */
+       function generateContentDiffBody( Content $old, Content $new ) {
+               if ( !( $old instanceof TextContent ) ) {
+                       throw new MWException( "Diff not implemented for " . get_class( $old ) . "; "
+                                       . "override generateContentDiffBody to fix this." );
+               }
+
+               if ( !( $new instanceof TextContent ) ) {
+                       throw new MWException( "Diff not implemented for " . get_class( $new ) . "; "
+                               . "override generateContentDiffBody to fix this." );
+               }
+
+               $otext = $old->serialize();
+               $ntext = $new->serialize();
+
+               return $this->generateTextDiffBody( $otext, $ntext );
+       }
+
        /**
         * Generate a diff, no caching
         *
         * @param $otext String: old text, must be already segmented
         * @param $ntext String: new text, must be already segmented
-        * @return bool|string
+        * @deprecated since 1.21, use generateContentDiffBody() instead!
         */
        function generateDiffBody( $otext, $ntext ) {
+               wfDeprecated( __METHOD__, "1.21" );
+
+               return $this->generateTextDiffBody( $otext, $ntext );
+       }
+
+       /**
+        * Generate a diff, no caching
+        *
+        * @todo move this to TextDifferenceEngine, make DifferenceEngine abstract. At some point.
+        *
+        * @param $otext String: old text, must be already segmented
+        * @param $ntext String: new text, must be already segmented
+        * @return bool|string
+        */
+       function generateTextDiffBody( $otext, $ntext ) {
                global $wgExternalDiffEngine, $wgContLang;
 
                wfProfileIn( __METHOD__ );
@@ -859,7 +912,7 @@ class DifferenceEngine extends ContextSource {
         *        the visibility of the revision and a link to edit the page.
         * @return String HTML fragment
         */
-       private function getRevisionHeader( Revision $rev, $complete = '' ) {
+       protected function getRevisionHeader( Revision $rev, $complete = '' ) {
                $lang = $this->getLanguage();
                $user = $this->getUser();
                $revtimestamp = $rev->getTimestamp();
@@ -951,10 +1004,25 @@ class DifferenceEngine extends ContextSource {
 
        /**
         * Use specified text instead of loading from the database
+        * @deprecated since 1.21, use setContent() instead.
         */
        function setText( $oldText, $newText ) {
-               $this->mOldtext = $oldText;
-               $this->mNewtext = $newText;
+               wfDeprecated( __METHOD__, "1.21" );
+
+               $oldContent = ContentHandler::makeContent( $oldText, $this->getTitle() );
+               $newContent = ContentHandler::makeContent( $newText, $this->getTitle() );
+
+               $this->setContent( $oldContent, $newContent );
+       }
+
+       /**
+        * Use specified text instead of loading from the database
+        * @since 1.21
+        */
+       function setContent( Content $oldContent, Content $newContent ) {
+               $this->mOldContent = $oldContent;
+               $this->mNewContent = $newContent;
+
                $this->mTextLoaded = 2;
                $this->mRevisionsLoaded = true;
        }
@@ -1082,14 +1150,14 @@ class DifferenceEngine extends ContextSource {
                        return false;
                }
                if ( $this->mOldRev ) {
-                       $this->mOldtext = $this->mOldRev->getText( Revision::FOR_THIS_USER, $this->getUser() );
-                       if ( $this->mOldtext === false ) {
+                       $this->mOldContent = $this->mOldRev->getContent( Revision::FOR_THIS_USER, $this->getUser() );
+                       if ( $this->mOldContent === false ) {
                                return false;
                        }
                }
                if ( $this->mNewRev ) {
-                       $this->mNewtext = $this->mNewRev->getText( Revision::FOR_THIS_USER, $this->getUser() );
-                       if ( $this->mNewtext === false ) {
+                       $this->mNewContent = $this->mNewRev->getContent( Revision::FOR_THIS_USER, $this->getUser() );
+                       if ( $this->mNewContent === false ) {
                                return false;
                        }
                }
@@ -1110,7 +1178,7 @@ class DifferenceEngine extends ContextSource {
                if ( !$this->loadRevisionData() ) {
                        return false;
                }
-               $this->mNewtext = $this->mNewRev->getText( Revision::FOR_THIS_USER, $this->getUser() );
+               $this->mNewContent = $this->mNewRev->getContent( Revision::FOR_THIS_USER, $this->getUser() );
                return true;
        }
 }
index c10d105..1136fc2 100644 (file)
@@ -1177,6 +1177,7 @@ abstract class FileBackend {
         *
         * @param $type string One of (attachment, inline)
         * @param $filename string Suggested file name (should not contain slashes)
+        * @throws MWException
         * @return string
         * @since 1.20
         */
index 7df09d1..90292ee 100644 (file)
@@ -179,10 +179,11 @@ class FileBackendMultiWrite extends FileBackend {
                // Actually attempt the operation batch on the master backend...
                $masterStatus = $mbe->doOperations( $realOps, $opts );
                $status->merge( $masterStatus );
-               // Propagate the operations to the clone backends if there were no fatal errors.
-               // If $ops only had one operation, this might avoid backend inconsistencies.
-               // This also avoids inconsistency for expected errors (like "file already exists").
-               if ( !count( $masterStatus->getErrorsArray() ) ) {
+               // Propagate the operations to the clone backends if there were no unexpected errors
+               // and if there were either no expected errors or if the 'force' option was used.
+               // However, if nothing succeeded at all, then don't replicate any of the operations.
+               // If $ops only had one operation, this might avoid backend sync inconsistencies.
+               if ( $masterStatus->isOK() && $masterStatus->successCount > 0 ) {
                        foreach ( $this->backends as $index => $backend ) {
                                if ( $index !== $this->masterIndex ) { // not done already
                                        $realOps = $this->substOpBatchPaths( $ops, $backend );
@@ -535,6 +536,7 @@ class FileBackendMultiWrite extends FileBackend {
        /**
         * @see FileBackend::fileExists()
         * @param $params array
+        * @return bool|null
         */
        public function fileExists( array $params ) {
                $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
index a29816f..440359d 100644 (file)
@@ -1176,6 +1176,8 @@ abstract class FileBackendStore extends FileBackend {
 
        /**
         * @see FileBackendStore::executeOpHandlesInternal()
+        * @param array $fileOpHandles
+        * @throws MWException
         * @return Array List of corresponding Status objects
         */
        protected function doExecuteOpHandlesInternal( array $fileOpHandles ) {
index 1807734..25c14f5 100644 (file)
@@ -1344,13 +1344,6 @@ class SwiftFileBackend extends FileBackendStore {
                return wfMemcKey( 'backend', $this->getName(), 'usercreds', $username );
        }
 
-       /**
-        * @see FileBackendStore::doClearCache()
-        */
-       protected function doClearCache( array $paths = null ) {
-               $this->connContainerCache->clear(); // clear container object cache
-       }
-
        /**
         * Get a Swift container object, possibly from process cache.
         * Use $reCache if the file count or byte count is needed.
index 57c0463..26a5e2d 100644 (file)
@@ -64,6 +64,7 @@ class MemcLockManager extends QuorumLockManager {
         *   - wikiId       : Wiki ID string that all resources are relative to. [optional]
         *
         * @param Array $config
+        * @throws MWException
         */
        public function __construct( array $config ) {
                parent::__construct( $config );
index 5f24fed..e8aa5a6 100644 (file)
@@ -1318,6 +1318,7 @@ class FileRepo {
         * e.g. s/z/a/ for sza251lrxrc1jad41h5mgilp8nysje52.jpg
         *
         * @param $key string
+        * @throws MWException
         * @return string
         */
        public function getDeletedHashPath( $key ) {
index c5a0bd1..c9751be 100644 (file)
@@ -108,6 +108,7 @@ class ArchivedFile {
 
        /**
         * Loads a file object from the filearchive table
+        * @throws MWException
         * @return bool|null True on success or null
         */
        public function load() {
index bbabe84..05958d6 100644 (file)
@@ -1186,8 +1186,9 @@ class LocalFile extends File {
                } else {
                        # New file; create the description page.
                        # There's already a log entry, so don't make a second RC entry
-                       # Squid and file cache for the description page are purged by doEdit.
-                       $status = $wikiPage->doEdit( $pageText, $comment, EDIT_NEW | EDIT_SUPPRESS_RC, false, $user );
+                       # Squid and file cache for the description page are purged by doEditContent.
+                       $content = ContentHandler::makeContent( $pageText, $descTitle );
+                       $status = $wikiPage->doEditContent( $content, $comment, EDIT_NEW | EDIT_SUPPRESS_RC, false, $user );
 
                        if ( isset( $status->value['revision'] ) ) { // XXX; doEdit() uses a transaction
                                $dbw->begin();
@@ -1475,9 +1476,9 @@ class LocalFile extends File {
                global $wgParser;
                $revision = Revision::newFromTitle( $this->title, false, Revision::READ_NORMAL );
                if ( !$revision ) return false;
-               $text = $revision->getText();
-               if ( !$text ) return false;
-               $pout = $wgParser->parse( $text, $this->title, new ParserOptions() );
+               $content = $revision->getContent();
+               if ( !$content ) return false;
+               $pout = $content->getParserOutput( $this->title, null, new ParserOptions() );
                return $pout->getText();
        }
 
index f7d5a1e..3a42953 100644 (file)
@@ -89,6 +89,11 @@ class Ibm_db2Updater extends DatabaseUpdater {
                        array( 'addTable', 'config',                            'patch-config.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' ),
                );
        }
 }
index ac5dbd7..c673f6f 100644 (file)
@@ -1594,13 +1594,16 @@ abstract class Installer {
                $status = Status::newGood();
                try {
                        $page = WikiPage::factory( Title::newMainPage() );
-                       $page->doEdit( wfMessage( 'mainpagetext' )->inContentLanguage()->text() . "\n\n" .
-                                       wfMessage( 'mainpagedocfooter' )->inContentLanguage()->text(),
+                       $content = new WikitextContent (
+                               wfMessage( 'mainpagetext' )->inContentLanguage()->text() . "\n\n" .
+                               wfMessage( 'mainpagedocfooter' )->inContentLanguage()->text()
+                       );
+
+                       $page->doEditContent( $content,
                                        '',
                                        EDIT_NEW,
                                        false,
-                                       User::newFromName( 'MediaWiki default' )
-                       );
+                                       User::newFromName( 'MediaWiki default' ) );
                } catch (MWException $e) {
                        //using raw, because $wgShowExceptionDetails can not be set yet
                        $status->fatal( 'config-install-mainpage-failed', $e->getMessage() );
index 98e1386..00009ca 100644 (file)
@@ -206,6 +206,7 @@ class MysqlUpdater extends DatabaseUpdater {
                        array( 'modifyField', 'user_groups', 'ug_group', 'patch-ug_group-length-increase.sql' ),
                        array( 'addField',      'uploadstash',  'us_chunk_inx',         'patch-uploadstash_chunk.sql' ),
                        array( 'addfield', 'job',           'job_timestamp',    'patch-jobs-add-timestamp.sql' ),
+
                        array( 'modifyField', 'user_former_groups', 'ufg_group', 'patch-ufg_group-length-increase.sql' ),
 
                        // 1.20
@@ -216,6 +217,11 @@ class MysqlUpdater 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( 'dropField', 'site_stats',   'ss_admins',        'patch-drop-ss_admins.sql' ),
                        array( 'dropField', 'recentchanges', 'rc_moved_to_title',            'patch-rc_moved.sql' ),
                );
index d81cf06..2f20135 100644 (file)
@@ -71,7 +71,12 @@ class OracleUpdater extends DatabaseUpdater {
                        array( 'addIndex', 'ipblocks', 'i05', 'patch-ipblocks_i05_index.sql' ),
                        array( 'addIndex', 'revision', 'i05', 'patch-revision_i05_index.sql' ),
 
-                       // 1.21
+                       //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' ),
 
                        // KEEP THIS AT THE BOTTOM!!
                        array( 'doRebuildDuplicateFunction' ),
index 3ac2b3a..882ec53 100644 (file)
@@ -190,6 +190,7 @@ class PostgresInstaller extends DatabaseInstaller {
         *                     other similar objects in the new DB.
         *    - create-tables: A connection with a role suitable for creating tables.
         *
+        * @throws MWException
         * @return Status object. On success, a connection object will be in the
         *   value member.
         */
index 95a61c1..e26aa78 100644 (file)
@@ -85,6 +85,7 @@ class SqliteUpdater extends DatabaseUpdater {
                        array( 'modifyField', 'user_groups', 'ug_group', 'patch-ug_group-length-increase.sql' ),
                        array( 'addField',      'uploadstash',  'us_chunk_inx',         'patch-uploadstash_chunk.sql' ),
                        array( 'addfield', 'job',           'job_timestamp',    'patch-jobs-add-timestamp.sql' ),
+
                        array( 'modifyField', 'user_former_groups', 'ufg_group', 'patch-ug_group-length-increase.sql' ),
 
                        // 1.20
@@ -95,7 +96,13 @@ class SqliteUpdater extends DatabaseUpdater {
                        array( 'dropField', 'category',     'cat_hidden',       'patch-cat_hidden.sql' ),
 
                        // 1.21
-                       array( 'dropField', 'site_stats',   'ss_admins',        'patch-drop-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', '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' ),
                );
        }
index f9c4b0f..b1b96b6 100644 (file)
@@ -93,8 +93,8 @@ class DoubleRedirectJob extends Job {
                        wfDebug( __METHOD__.": target redirect already deleted, ignoring\n" );
                        return true;
                }
-               $text = $targetRev->getText();
-               $currentDest = Title::newFromRedirect( $text );
+               $content = $targetRev->getContent();
+               $currentDest = $content->getRedirectTarget();
                if ( !$currentDest || !$currentDest->equals( $this->redirTitle ) ) {
                        wfDebug( __METHOD__.": Redirect has changed since the job was queued\n" );
                        return true;
@@ -102,7 +102,7 @@ class DoubleRedirectJob extends Job {
 
                # Check for a suppression tag (used e.g. in periodically archived discussions)
                $mw = MagicWord::get( 'staticredirect' );
-               if ( $mw->match( $text ) ) {
+               if ( $content->matchMagicWord( $mw ) ) {
                        wfDebug( __METHOD__.": skipping: suppressed with __STATICREDIRECT__\n" );
                        return true;
                }
@@ -124,14 +124,10 @@ class DoubleRedirectJob extends Job {
                        $currentDest->getFragment(), $newTitle->getInterwiki() );
 
                # Fix the text
-               # Remember that redirect pages can have categories, templates, etc.,
-               # so the regex has to be fairly general
-               $newText = preg_replace( '/ \[ \[  [^\]]*  \] \] /x',
-                       '[[' . $newTitle->getFullText() . ']]',
-                       $text, 1 );
-
-               if ( $newText === $text ) {
-                       $this->setLastError( 'Text unchanged???' );
+               $newContent = $content->updateRedirect( $newTitle );
+
+               if ( $newContent->equals( $content ) ) {
+                       $this->setLastError( 'Content unchanged???' );
                        return false;
                }
 
@@ -143,7 +139,7 @@ class DoubleRedirectJob extends Job {
                $reason = wfMessage( 'double-redirect-fixed-' . $this->reason,
                        $this->redirTitle->getPrefixedText(), $newTitle->getPrefixedText()
                )->inContentLanguage()->text();
-               $article->doEdit( $newText, $reason, EDIT_UPDATE | EDIT_SUPPRESS_RC, false, $this->getUser() );
+               $article->doEditContent( $newContent, $reason, EDIT_UPDATE | EDIT_SUPPRESS_RC, false, $this->getUser() );
                $wgUser = $oldUser;
 
                return true;
index b23951c..c4370f4 100644 (file)
@@ -70,16 +70,16 @@ class RefreshLinksJob extends Job {
        }
 
        public static function runForTitleInternal( Title $title, Revision $revision, $fname ) {
-               global $wgParser, $wgContLang;
+               global $wgContLang;
 
                wfProfileIn( $fname . '-parse' );
                $options = ParserOptions::newFromUserAndLang( new User, $wgContLang );
-               $parserOutput = $wgParser->parse(
-                       $revision->getText(), $title, $options, true, true, $revision->getId() );
+               $content = $revision->getContent();
+               $parserOutput = $content->getParserOutput( $title, $revision->getId(), $options, false );
                wfProfileOut( $fname . '-parse' );
 
                wfProfileIn( $fname . '-update' );
-               $updates = $parserOutput->getSecondaryDataUpdates( $title, false );
+               $updates = $content->getSecondaryDataUpdates( $title, null, false, $parserOutput  );
                DataUpdate::runUpdates( $updates );
                wfProfileOut( $fname . '-update' );
        }
index 7586bb6..7d94a30 100644 (file)
@@ -429,6 +429,7 @@ class LogFormatter {
         * value in consideration.
         * @param $title Title the page
         * @param $parameters array query parameters
+        * @throws MWException
         * @return String
         */
        protected function makePageLink( Title $title = null, $parameters = array() ) {
index 99ac854..ca9b636 100644 (file)
@@ -623,7 +623,8 @@ class BitmapHandler extends ImageHandler {
         * to filter down to users.
         *
         * @param $path string The file path
-        * @param $scene string The scene specification, or false if there is none
+        * @param bool|string $scene The scene specification, or false if there is none
+        * @throws MWException
         * @return string
         */
        function escapeMagickInput( $path, $scene = false ) {
@@ -654,7 +655,8 @@ class BitmapHandler extends ImageHandler {
         * helper function for escapeMagickInput() and escapeMagickOutput().
         *
         * @param $path string The file path
-        * @param $scene string The scene specification, or false if there is none
+        * @param bool|string $scene The scene specification, or false if there is none
+        * @throws MWException
         * @return string
         */
        protected function escapeMagickPath( $path, $scene = false ) {
index 0a19554..d2edc9f 100644 (file)
@@ -257,6 +257,7 @@ class BitmapMetadataHandler {
         *
         * The various exceptions this throws are caught later.
         * @param $filename String
+        * @throws MWException
         * @return Array The metadata.
         */
        static public function Tiff ( $filename ) {
index 784a601..104ba55 100644 (file)
@@ -104,6 +104,7 @@ class Exif {
         *
         * @param $file String: filename.
         * @param $byteOrder String Type of byte ordering either 'BE' (Big Endian) or 'LE' (Little Endian). Default ''.
+        * @throws MWException
         * @todo FIXME: The following are broke:
         * SubjectArea. Need to test the more obscure tags.
         *
index 5fc5c1a..f6ca775 100644 (file)
@@ -260,6 +260,7 @@ class GIFMetadataExtractor {
 
        /**
         * @param $data
+        * @throws Exception
         * @return int
         */
        static function decodeBPP( $data ) {
@@ -276,7 +277,7 @@ class GIFMetadataExtractor {
 
        /**
         * @param $fh
-        * @return
+        * @throws Exception
         */
        static function skipBlock( $fh ) {
                while ( !feof( $fh ) ) {
@@ -290,6 +291,7 @@ class GIFMetadataExtractor {
                        fread( $fh, $block_len );
                }
        }
+
        /**
         * Read a block. In the GIF format, a block is made up of
         * several sub-blocks. Each sub block starts with one byte
@@ -301,6 +303,7 @@ class GIFMetadataExtractor {
         *  sub-blocks in the returned value. Normally this is false,
         *  except XMP is weird and does a hack where you need to keep
         *  these length bytes.
+        * @throws Exception
         * @return string The data.
         */
        static function readBlock( $fh, $includeLengths = false ) {
index 8d7e43b..ecf216a 100644 (file)
@@ -165,10 +165,11 @@ class JpegMetadataExtractor {
        }
 
        /**
-       * Helper function for jpegSegmentSplitter
-       * @param &$fh FileHandle for jpeg file
-       * @return string data content of segment.
-       */
+        * Helper function for jpegSegmentSplitter
+        * @param &$fh FileHandle for jpeg file
+        * @throws MWException
+        * @return string data content of segment.
+        */
        private static function jpegExtractMarker( &$fh ) {
                $size = wfUnpack( "nint", fread( $fh, 2 ), 2 );
                if ( $size['int'] <= 2 ) {
index 773824c..c5e4566 100644 (file)
@@ -281,6 +281,7 @@ class ThumbnailImage extends MediaTransformOutput {
         * For images, desc-link and file-link are implemented as a click-through. For
         * sounds and videos, they may be displayed in other ways.
         *
+        * @throws MWException
         * @return string
         */
        function toHtml( $options = array() ) {
index 55fa554..75d474c 100644 (file)
@@ -135,14 +135,15 @@ class SvgHandler extends ImageHandler {
        }
 
        /**
-       * Transform an SVG file to PNG
-       * This function can be called outside of thumbnail contexts
-       * @param string $srcPath
-       * @param string $dstPath
-       * @param string $width
-       * @param string $height
-       * @return bool|MediaTransformError
-       */
+        * Transform an SVG file to PNG
+        * This function can be called outside of thumbnail contexts
+        * @param string $srcPath
+        * @param string $dstPath
+        * @param string $width
+        * @param string $height
+        * @throws MWException
+        * @return bool|MediaTransformError
+        */
        public function rasterize( $srcPath, $dstPath, $width, $height ) {
                global $wgSVGConverters, $wgSVGConverter, $wgSVGConverterPath;
                $err = false;
index 851fe42..871d419 100644 (file)
@@ -52,6 +52,7 @@ class SVGReader {
         *
         * Creates an SVGReader drawing from the source provided
         * @param $source String: URI from which to read
+        * @throws MWException|Exception
         */
        function __construct( $source ) {
                global $wgSVGMetadataCutoff;
@@ -113,6 +114,7 @@ class SVGReader {
 
        /**
         * Read the SVG
+        * @throws MWException
         * @return bool
         */
        public function read() {
@@ -196,6 +198,7 @@ class SVGReader {
         * Read an XML snippet from an element
         *
         * @param String $metafield that we will fill with the result
+        * @throws MWException
         */
        private function readXml( $metafield=null ) {
                $this->debug ( "Read top level metadata" );
index d95c907..55dff77 100644 (file)
@@ -70,8 +70,9 @@ class TiffHandler extends ExifBitmapHandler {
        }
 
        /**
-        * @param $image
-        * @param $filename
+        * @param File $image
+        * @param string $filename
+        * @throws MWException
         * @return string
         */
        function getMetadata( $image, $filename ) {
index 36660b3..c5743d7 100644 (file)
@@ -232,17 +232,18 @@ class XMPReader {
        }
 
        /**
-       * Main function to call to parse XMP. Use getResults to
-       * get results.
-       *
-       * Also catches any errors during processing, writes them to
-       * debug log, blanks result array and returns false.
-       *
-       * @param $content String: XMP data
-       * @param $allOfIt Boolean: If this is all the data (true) or if its split up (false). Default true
-       * @param $reset Boolean: does xml parser need to be reset. Default false
-       * @return Boolean success.
-       */
+        * Main function to call to parse XMP. Use getResults to
+        * get results.
+        *
+        * Also catches any errors during processing, writes them to
+        * debug log, blanks result array and returns false.
+        *
+        * @param $content String: XMP data
+        * @param $allOfIt Boolean: If this is all the data (true) or if its split up (false). Default true
+        * @param $reset Boolean: does xml parser need to be reset. Default false
+        * @throws MWException
+        * @return Boolean success.
+        */
        public function parse( $content, $allOfIt = true, $reset = false ) {
                if ( $reset ) {
                        $this->resetXMLParser();
@@ -463,22 +464,23 @@ class XMPReader {
        }
 
        /**
-       * Hit a closing element in MODE_STRUCT, MODE_SEQ, MODE_BAG
-       * generally means we've finished processing a nested structure.
-       * resets some internal variables to indicate that.
-       *
-       * Note this means we hit the closing element not the "</rdf:Seq>".
-       *
-       * @par For example, when processing:
-       * @code{,xml}
-       * <exif:ISOSpeedRatings> <rdf:Seq> <rdf:li>64</rdf:li>
-       *   </rdf:Seq> </exif:ISOSpeedRatings>
-       * @endcode
-       *
-       * This method is called when we hit the "</exif:ISOSpeedRatings>" tag.
-       *
-       * @param $elm String namespace . space . tag name.
-       */
+        * Hit a closing element in MODE_STRUCT, MODE_SEQ, MODE_BAG
+        * generally means we've finished processing a nested structure.
+        * resets some internal variables to indicate that.
+        *
+        * Note this means we hit the closing element not the "</rdf:Seq>".
+        *
+        * @par For example, when processing:
+        * @code{,xml}
+        * <exif:ISOSpeedRatings> <rdf:Seq> <rdf:li>64</rdf:li>
+        *   </rdf:Seq> </exif:ISOSpeedRatings>
+        * @endcode
+        *
+        * This method is called when we hit the "</exif:ISOSpeedRatings>" tag.
+        *
+        * @param $elm String namespace . space . tag name.
+        * @throws MWException
+        */
        private function endElementNested( $elm ) {
 
                /* cur item must be the same as $elm, unless if in MODE_STRUCT
@@ -528,23 +530,24 @@ class XMPReader {
        }
 
        /**
-       * Hit a closing element in MODE_LI (either rdf:Seq, or rdf:Bag )
-       * Add information about what type of element this is.
-       *
-       * Note we still have to hit the outer "</property>"
-       *
-       * @par For example, when processing:
-       * @code{,xml}
-       * <exif:ISOSpeedRatings> <rdf:Seq> <rdf:li>64</rdf:li>
-       *   </rdf:Seq> </exif:ISOSpeedRatings>
-       * @endcode
-       *
-       * This method is called when we hit the "</rdf:Seq>".
-       * (For comparison, we call endElementModeSimple when we
-       * hit the "</rdf:li>")
-       *
-       * @param $elm String namespace . ' ' . element name
-       */
+        * Hit a closing element in MODE_LI (either rdf:Seq, or rdf:Bag )
+        * Add information about what type of element this is.
+        *
+        * Note we still have to hit the outer "</property>"
+        *
+        * @par For example, when processing:
+        * @code{,xml}
+        * <exif:ISOSpeedRatings> <rdf:Seq> <rdf:li>64</rdf:li>
+        *   </rdf:Seq> </exif:ISOSpeedRatings>
+        * @endcode
+        *
+        * This method is called when we hit the "</rdf:Seq>".
+        * (For comparison, we call endElementModeSimple when we
+        * hit the "</rdf:li>")
+        *
+        * @param $elm String namespace . ' ' . element name
+        * @throws MWException
+        */
        private function endElementModeLi( $elm ) {
 
                list( $ns, $tag ) = explode( ' ', $this->curItem[0], 2 );
@@ -599,17 +602,18 @@ class XMPReader {
        }
 
        /**
-       * Handler for hitting a closing element.
-       *
-       * generally just calls a helper function depending on what
-       * mode we're in.
-       *
-       * Ignores the outer wrapping elements that are optional in
-       * xmp and have no meaning.
-       *
-       * @param $parser XMLParser
-       * @param $elm String namespace . ' ' . element name
-       */
+        * Handler for hitting a closing element.
+        *
+        * generally just calls a helper function depending on what
+        * mode we're in.
+        *
+        * Ignores the outer wrapping elements that are optional in
+        * xmp and have no meaning.
+        *
+        * @param $parser XMLParser
+        * @param $elm String namespace . ' ' . element name
+        * @throws MWException
+        */
        function endElement( $parser, $elm ) {
                if ( $elm === ( self::NS_RDF . ' RDF' )
                        || $elm === 'adobe:ns:meta/ xmpmeta'
@@ -759,22 +763,23 @@ class XMPReader {
        }
 
        /**
-       * Handle an opening element when in MODE_SIMPLE
-       *
-       * This should not happen often. This is for if a simple element
-       * already opened has a child element. Could happen for a
-       * qualified element.
-       *
-       * For example:
-       * <exif:DigitalZoomRatio><rdf:Description><rdf:value>0/10</rdf:value>
-       *   <foo:someQualifier>Bar</foo:someQualifier> </rdf:Description>
-       *   </exif:DigitalZoomRatio>
-       *
-       * This method is called when processing the <rdf:Description> element
-       *
-       * @param $elm String namespace and tag names separated by space.
-       * @param $attribs Array Attributes of the element.
-       */
+        * Handle an opening element when in MODE_SIMPLE
+        *
+        * This should not happen often. This is for if a simple element
+        * already opened has a child element. Could happen for a
+        * qualified element.
+        *
+        * For example:
+        * <exif:DigitalZoomRatio><rdf:Description><rdf:value>0/10</rdf:value>
+        *   <foo:someQualifier>Bar</foo:someQualifier> </rdf:Description>
+        *   </exif:DigitalZoomRatio>
+        *
+        * This method is called when processing the <rdf:Description> element
+        *
+        * @param $elm String namespace and tag names separated by space.
+        * @param $attribs Array Attributes of the element.
+        * @throws MWException
+        */
        private function startElementModeSimple( $elm, $attribs ) {
                if ( $elm === self::NS_RDF . ' Description' ) {
                        // If this value has qualifiers
@@ -824,16 +829,17 @@ class XMPReader {
        }
 
        /**
-       * Starting an element when in MODE_INITIAL
-       * This usually happens when we hit an element inside
-       * the outer rdf:Description
-       *
-       * This is generally where most properties start.
-       *
-       * @param $ns String Namespace
-       * @param $tag String tag name (without namespace prefix)
-       * @param $attribs Array array of attributes
-       */
+        * Starting an element when in MODE_INITIAL
+        * This usually happens when we hit an element inside
+        * the outer rdf:Description
+        *
+        * This is generally where most properties start.
+        *
+        * @param $ns String Namespace
+        * @param $tag String tag name (without namespace prefix)
+        * @param $attribs Array array of attributes
+        * @throws MWException
+        */
        private function startElementModeInitial( $ns, $tag, $attribs ) {
                if ( $ns !== self::NS_RDF ) {
 
@@ -877,23 +883,24 @@ class XMPReader {
        }
 
        /**
-       * Hit an opening element when in a Struct (MODE_STRUCT)
-       * This is generally for fields of a compound property.
-       *
-       * Example of a struct (abbreviated; flash has more properties):
-       *
-       * <exif:Flash> <rdf:Description> <exif:Fired>True</exif:Fired>
-       *  <exif:Mode>1</exif:Mode></rdf:Description></exif:Flash>
-       *
-       * or:
-       *
-       * <exif:Flash rdf:parseType='Resource'> <exif:Fired>True</exif:Fired>
-       *  <exif:Mode>1</exif:Mode></exif:Flash>
-       *
-       * @param $ns String namespace
-       * @param $tag String tag name (no ns)
-       * @param $attribs Array array of attribs w/ values.
-       */
+        * Hit an opening element when in a Struct (MODE_STRUCT)
+        * This is generally for fields of a compound property.
+        *
+        * Example of a struct (abbreviated; flash has more properties):
+        *
+        * <exif:Flash> <rdf:Description> <exif:Fired>True</exif:Fired>
+        *  <exif:Mode>1</exif:Mode></rdf:Description></exif:Flash>
+        *
+        * or:
+        *
+        * <exif:Flash rdf:parseType='Resource'> <exif:Fired>True</exif:Fired>
+        *  <exif:Mode>1</exif:Mode></exif:Flash>
+        *
+        * @param $ns String namespace
+        * @param $tag String tag name (no ns)
+        * @param $attribs Array array of attribs w/ values.
+        * @throws MWException
+        */
        private function startElementModeStruct( $ns, $tag, $attribs ) {
                if ( $ns !== self::NS_RDF ) {
 
@@ -1015,14 +1022,15 @@ class XMPReader {
        }
 
        /**
-       * Hits an opening element.
-       * Generally just calls a helper based on what MODE we're in.
-       * Also does some initial set up for the wrapper element
-       *
-       * @param $parser XMLParser
-       * @param $elm String namespace "<space>" element
-       * @param $attribs Array attribute name => value
-       */
+        * Hits an opening element.
+        * Generally just calls a helper based on what MODE we're in.
+        * Also does some initial set up for the wrapper element
+        *
+        * @param $parser XMLParser
+        * @param $elm String namespace "<space>" element
+        * @param $attribs Array attribute name => value
+        * @throws MWException
+        */
        function startElement( $parser, $elm, $attribs ) {
 
                if ( $elm === self::NS_RDF . ' RDF'
@@ -1100,19 +1108,20 @@ class XMPReader {
        }
 
        /**
-       * Process attributes.
-       * Simple values can be stored as either a tag or attribute
-       *
-       * Often the initial "<rdf:Description>" tag just has all the simple
-       * properties as attributes.
-       *
-       * @par Example:
-       * @code
-       * <rdf:Description rdf:about="" xmlns:exif="http://ns.adobe.com/exif/1.0/" exif:DigitalZoomRatio="0/10">
-       * @endcode
-       *
-       * @param $attribs Array attribute=>value array.
-       */
+        * Process attributes.
+        * Simple values can be stored as either a tag or attribute
+        *
+        * Often the initial "<rdf:Description>" tag just has all the simple
+        * properties as attributes.
+        *
+        * @par Example:
+        * @code
+        * <rdf:Description rdf:about="" xmlns:exif="http://ns.adobe.com/exif/1.0/" exif:DigitalZoomRatio="0/10">
+        * @endcode
+        *
+        * @param $attribs Array attribute=>value array.
+        * @throws MWException
+        */
        private function doAttribs( $attribs ) {
 
                // first check for rdf:parseType attribute, as that can change
index f86cf15..156c39e 100644 (file)
@@ -35,6 +35,7 @@ class EhcacheBagOStuff extends BagOStuff {
 
        /**
         * @param $params array
+        * @throws MWException
         */
        function __construct( $params ) {
                if ( !defined( 'CURLOPT_TIMEOUT_MS' ) ) {
index d5de044..40784f5 100644 (file)
@@ -321,6 +321,8 @@ class RedisBagOStuff extends BagOStuff {
         * Get a connection to the server with the specified name. Connections
         * are cached, and failures are persistent to avoid multiple timeouts.
         *
+        * @param $server
+        * @throws MWException
         * @return Redis object, or false on failure
         */
        protected function getConnectionToServer( $server ) {
index 296be66..6505183 100644 (file)
@@ -72,6 +72,7 @@ class CoreTagHooks {
         * @param $content string
         * @param $attributes array
         * @param $parser Parser
+        * @throws MWException
         * @return array
         */
        static function html( $content, $attributes, $parser ) {
index 9c15985..8671665 100644 (file)
@@ -753,6 +753,7 @@ class Parser {
         *
         * @since 1.19
         *
+        * @throws MWException
         * @return Language|null
         */
        public function getTargetLanguage() {
@@ -1531,6 +1532,7 @@ class Parser {
         *
         * @param $text string
         *
+        * @throws MWException
         * @return string
         */
        function replaceExternalLinks( $text ) {
@@ -1545,6 +1547,7 @@ class Parser {
                $i = 0;
                while ( $i<count( $bits ) ) {
                        $url = $bits[$i++];
+                       // @todo FIXME: Unused variable.
                        $protocol = $bits[$i++];
                        $text = $bits[$i++];
                        $trail = $bits[$i++];
@@ -1734,6 +1737,8 @@ class Parser {
 
        /**
         * Process [[ ]] wikilinks (RIL)
+        * @param $s
+        * @throws MWException
         * @return LinkHolderArray
         *
         * @private
@@ -1961,11 +1966,14 @@ class Parser {
                                # Interwikis
                                wfProfileIn( __METHOD__."-interwiki" );
                                if ( $iw && $this->mOptions->getInterwikiMagic() && $nottalk && Language::fetchLanguageName( $iw, null, 'mw' ) ) {
+                                       // XXX: the above check prevents links to sites with identifiers that are not language codes
+
                                        # Bug 24502: filter duplicates
                                        if ( !isset( $this->mLangLinkLanguages[$iw] ) ) {
                                                $this->mLangLinkLanguages[$iw] = true;
                                                $this->mOutput->addLanguageLink( $nt->getFullText() );
                                        }
+
                                        $s = rtrim( $s . $prefix );
                                        $s .= trim( $trail, "\n" ) == '' ? '': $prefix . $trail;
                                        wfProfileOut( __METHOD__."-interwiki" );
@@ -2448,6 +2456,7 @@ class Parser {
         * @param $str String the string to split
         * @param &$before String set to everything before the ':'
         * @param &$after String set to everything after the ':'
+        * @throws MWException
         * @return String the position of the ':', or false if none found
         */
        function findColonNoLinks( $str, &$before, &$after ) {
@@ -2612,8 +2621,9 @@ class Parser {
         * @private
         *
         * @param $index integer
-        * @param $frame PPFrame
+        * @param bool|\PPFrame $frame
         *
+        * @throws MWException
         * @return string
         */
        function getVariableValue( $index, $frame = false ) {
@@ -3122,6 +3132,7 @@ class Parser {
         *  $piece['parts']: the parameter array
         *  $piece['lineStart']: whether the brace was at the start of a line
         * @param $frame PPFrame The current frame, contains template arguments
+        * @throws MWException
         * @return String: the text of the template
         * @private
         */
@@ -3605,7 +3616,13 @@ class Parser {
                        }
 
                        if ( $rev ) {
-                               $text = $rev->getText();
+                               $content = $rev->getContent();
+                               $text = $content->getWikitextForTransclusion();
+
+                               if ( $text === false || $text === null ) {
+                                       $text = false;
+                                       break;
+                               }
                        } elseif ( $title->getNamespace() == NS_MEDIAWIKI ) {
                                global $wgContLang;
                                $message = wfMessage( $wgContLang->lcfirst( $title->getText() ) )->inContentLanguage();
@@ -3613,16 +3630,17 @@ class Parser {
                                        $text = false;
                                        break;
                                }
+                               $content = $message->content();
                                $text = $message->plain();
                        } else {
                                break;
                        }
-                       if ( $text === false ) {
+                       if ( !$content ) {
                                break;
                        }
                        # Redirect?
                        $finalTitle = $title;
-                       $title = Title::newFromRedirect( $text );
+                       $title = $content->getRedirectTarget();
                }
                return array(
                        'text' => $text,
@@ -3792,6 +3810,7 @@ class Parser {
         *     noClose    Original text did not have a close tag
         * @param $frame PPFrame
         *
+        * @throws MWException
         * @return string
         */
        function extensionSubstitution( $params, $frame ) {
@@ -4689,6 +4708,7 @@ class Parser {
         *
         * @param $tag Mixed: the tag to use, e.g. 'hook' for "<hook>"
         * @param $callback Mixed: the callback function (and object) to use for the tag
+        * @throws MWException
         * @return Mixed|null The old value of the mTagHooks array associated with the hook
         */
        public function setHook( $tag, $callback ) {
@@ -4719,6 +4739,7 @@ class Parser {
         *
         * @param $tag Mixed: the tag to use, e.g. 'hook' for "<hook>"
         * @param $callback Mixed: the callback function (and object) to use for the tag
+        * @throws MWException
         * @return Mixed|null The old value of the mTagHooks array associated with the hook
         */
        function setTransparentTagHook( $tag, $callback ) {
@@ -4781,6 +4802,7 @@ class Parser {
         *     Please read the documentation in includes/parser/Preprocessor.php for more information
         *     about the methods available in PPFrame and PPNode.
         *
+        * @throws MWException
         * @return string|callback The old callback function for this name, if any
         */
        public function setFunctionHook( $id, $callback, $flags = 0 ) {
@@ -4828,6 +4850,10 @@ class Parser {
         * Create a tag function, e.g. "<test>some stuff</test>".
         * Unlike tag hooks, tag functions are parsed at preprocessor level.
         * Unlike parser functions, their content is not preprocessed.
+        * @param $tag
+        * @param $callback
+        * @param $flags
+        * @throws MWException
         * @return null
         */
        function setFunctionTagHook( $tag, $callback, $flags ) {
@@ -5791,6 +5817,7 @@ class Parser {
         * check whether it is still valid, by calling isValidHalfParsedText().
         *
         * @param $data array Serialized data
+        * @throws MWException
         * @return String
         */
        function unserializeHalfParsedText( $data ) {
index 6a4ef0c..e5beba8 100644 (file)
@@ -48,6 +48,7 @@ class ParserCache {
         * May be a memcached client or a BagOStuff derivative.
         *
         * @param $memCached Object
+        * @throws MWException
         */
        protected function __construct( $memCached ) {
                if ( !$memCached ) {
index be629d3..b6bcf63 100644 (file)
@@ -50,7 +50,7 @@ class ParserOutput extends CacheTime {
                $mTimestamp;                  # Timestamp of the revision
                private $mIndexPolicy = '';       # 'index' or 'noindex'?  Any other value will result in no change.
                private $mAccessedOptions = array(); # List of ParserOptions (stored in the keys)
-               private $mSecondaryDataUpdates = array(); # List of instances of SecondaryDataObject(), used to cause some information extracted from the page in a custom place.
+               private $mSecondaryDataUpdates = array(); # List of DataUpdate, used to save info from the page somewhere else.
 
        const EDITSECTION_REGEX = '#<(?:mw:)?editsection page="(.*?)" section="(.*?)"(?:/>|>(.*?)(</(?:mw:)?editsection>))#';
 
@@ -75,6 +75,8 @@ class ParserOutput extends CacheTime {
        /**
         * callback used by getText to replace editsection tokens
         * @private
+        * @param $m
+        * @throws MWException
         * @return mixed
         */
        function replaceEditSectionLinksCallback( $m ) {
@@ -399,9 +401,13 @@ class ParserOutput extends CacheTime {
         * extracted from the page's content, including a LinksUpdate object for all links stored in
         * this ParserOutput object.
         *
+        * @note: Avoid using this method directly, use ContentHandler::getSecondaryDataUpdates() instead! The content
+        *        handler may provide additional update objects.
+        *
         * @since 1.20
         *
-        * @param $title Title of the page we're updating. If not given, a title object will be created based on $this->getTitleText()
+        * @param $title Title The title of the page we're updating. If not given, a title object will be created
+        *                      based on $this->getTitleText()
         * @param $recursive Boolean: queue jobs for recursive updates?
         *
         * @return Array. An array of instances of DataUpdate
index 6bcc324..9c750b8 100644 (file)
@@ -84,11 +84,11 @@ class Parser_LinkHooks extends Parser {
         * Create a link hook, e.g. [[Namepsace:...|display}}
         * The callback function should have the form:
         *    function myLinkCallback( $parser, $holders, $markers,
-        *      Title $title, $titleText, &$sortText = null, &$leadingColon = false ) { ... }
+        *      Title $title, $titleText, &$sortText = null, &$leadingColon = false ) { ... }
         *
         * Or with SLH_PATTERN:
         *    function myLinkCallback( $parser, $holders, $markers, )
-        *      &$titleText, &$sortText = null, &$leadingColon = false ) { ... }
+        *      &$titleText, &$sortText = null, &$leadingColon = false ) { ... }
         *
         * The callback may either return a number of different possible values:
         * String) Text result of the link
@@ -100,6 +100,7 @@ class Parser_LinkHooks extends Parser {
         * @param $flags Integer: a combination of the following flags:
         *     SLH_PATTERN   Use a regex link pattern rather than a namespace
         *
+        * @throws MWException
         * @return callback|null The old callback function for this name, if any
         */
        public function setLinkHook( $ns, $callback, $flags = 0 ) {
@@ -120,9 +121,11 @@ class Parser_LinkHooks extends Parser {
        function getLinkHooks() {
                return array_keys( $this->mLinkHooks );
        }
-       
+
        /**
         * Process [[ ]] wikilinks
+        * @param $s
+        * @throws MWException
         * @return LinkHolderArray
         *
         * @private
index 34de0ba..15ea5e4 100644 (file)
@@ -126,6 +126,7 @@ class Preprocessor_DOM implements Preprocessor {
         * cache may be implemented at a later date which takes further advantage of these strict
         * dependency requirements.
         *
+        * @throws MWException
         * @return PPNode_DOM
         */
        function preprocessToObj( $text, $flags = 0 ) {
@@ -1673,6 +1674,7 @@ class PPNode_DOM implements PPNode {
         *  - index         String index
         *  - value         PPNode value
         *
+        * @throws MWException
         * @return array
         */
        function splitArg() {
@@ -1694,6 +1696,7 @@ class PPNode_DOM implements PPNode {
         * Split an "<ext>" node into an associative array containing name, attr, inner and close
         * All values in the resulting array are PPNodes. Inner and close are optional.
         *
+        * @throws MWException
         * @return array
         */
        function splitExt() {
@@ -1719,6 +1722,7 @@ class PPNode_DOM implements PPNode {
 
        /**
         * Split a "<h>" node
+        * @throws MWException
         * @return array
         */
        function splitHeading() {
index 4f04c86..a4e408e 100644 (file)
@@ -105,6 +105,7 @@ class Preprocessor_Hash implements Preprocessor {
         * cache may be implemented at a later date which takes further advantage of these strict
         * dependency requirements.
         *
+        * @throws MWException
         * @return PPNode_Hash_Tree
         */
        function preprocessToObj( $text, $flags = 0 ) {
@@ -884,9 +885,11 @@ class PPFrame_Hash implements PPFrame {
         * Create a new child frame
         * $args is optionally a multi-root PPNode or array containing the template arguments
         *
-        * @param $args PPNode_Hash_Array|array
+        * @param array|bool|\PPNode_Hash_Array $args PPNode_Hash_Array|array
         * @param $title Title|bool
         *
+        * @param int $indexOffset
+        * @throws MWException
         * @return PPTemplateFrame_Hash
         */
        function newChild( $args = false, $title = false, $indexOffset = 0 ) {
@@ -1609,6 +1612,7 @@ class PPNode_Hash_Tree implements PPNode {
         *  - index         String index
         *  - value         PPNode value
         *
+        * @throws MWException
         * @return array
         */
        function splitArg() {
@@ -1642,6 +1646,7 @@ class PPNode_Hash_Tree implements PPNode {
         * Split an "<ext>" node into an associative array containing name, attr, inner and close
         * All values in the resulting array are PPNodes. Inner and close are optional.
         *
+        * @throws MWException
         * @return array
         */
        function splitExt() {
@@ -1669,6 +1674,7 @@ class PPNode_Hash_Tree implements PPNode {
        /**
         * Split an "<h>" node
         *
+        * @throws MWException
         * @return array
         */
        function splitHeading() {
@@ -1695,6 +1701,7 @@ class PPNode_Hash_Tree implements PPNode {
        /**
         * Split a "<template>" or "<tplarg>" node
         *
+        * @throws MWException
         * @return array
         */
        function splitTemplate() {
index 8b71a1b..8059e35 100644 (file)
@@ -1884,6 +1884,7 @@ class PPNode_HipHop_Tree implements PPNode {
        /**
         * Split a <template> or <tplarg> node
         *
+        * @throws MWException
         * @return array
         */
        function splitTemplate() {
index 3b48a26..f98266e 100644 (file)
@@ -309,6 +309,7 @@ class ResourceLoader {
         *
         * @param $id Mixed: source ID (string), or array( id1 => props1, id2 => props2, ... )
         * @param $properties Array: source properties
+        * @throws MWException
         */
        public function addSource( $id, $properties = null) {
                // Allow multiple sources to be registered in one call
@@ -832,6 +833,7 @@ class ResourceLoader {
         *     associative array mapping message key to value, or a JSON-encoded message blob containing
         *     the same data, wrapped in an XmlJsCode object.
         *
+        * @throws MWException
         * @return string
         */
        public static function makeLoaderImplementScript( $name, $scripts, $styles, $messages ) {
index d0c56ae..7231c0f 100644 (file)
@@ -143,44 +143,45 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
         *     to $wgScriptPath
         *
         * Below is a description for the $options array:
+        * @throws MWException
         * @par Construction options:
         * @code
-        *      array(
-        *              // Base path to prepend to all local paths in $options. Defaults to $IP
-        *              'localBasePath' => [base path],
-        *              // Base path to prepend to all remote paths in $options. Defaults to $wgScriptPath
-        *              'remoteBasePath' => [base path],
-        *              // Equivalent of remoteBasePath, but relative to $wgExtensionAssetsPath
-        *              'remoteExtPath' => [base path],
-        *              // Scripts to always include
-        *              'scripts' => [file path string or array of file path strings],
-        *              // Scripts to include in specific language contexts
-        *              'languageScripts' => array(
-        *                      [language code] => [file path string or array of file path strings],
-        *              ),
-        *              // Scripts to include in specific skin contexts
-        *              'skinScripts' => array(
-        *                      [skin name] => [file path string or array of file path strings],
-        *              ),
-        *              // Scripts to include in debug contexts
-        *              'debugScripts' => [file path string or array of file path strings],
-        *              // Scripts to include in the startup module
-        *              'loaderScripts' => [file path string or array of file path strings],
-        *              // Modules which must be loaded before this module
-        *              'dependencies' => [modile name string or array of module name strings],
-        *              // Styles to always load
-        *              'styles' => [file path string or array of file path strings],
-        *              // Styles to include in specific skin contexts
-        *              'skinStyles' => array(
-        *                      [skin name] => [file path string or array of file path strings],
-        *              ),
-        *              // Messages to always load
-        *              'messages' => [array of message key strings],
-        *              // Group which this module should be loaded together with
-        *              'group' => [group name string],
-        *              // Position on the page to load this module at
-        *              'position' => ['bottom' (default) or 'top']
-        *      )
+        *     array(
+        *         // Base path to prepend to all local paths in $options. Defaults to $IP
+        *         'localBasePath' => [base path],
+        *         // Base path to prepend to all remote paths in $options. Defaults to $wgScriptPath
+        *         'remoteBasePath' => [base path],
+        *         // Equivalent of remoteBasePath, but relative to $wgExtensionAssetsPath
+        *         'remoteExtPath' => [base path],
+        *         // Scripts to always include
+        *         'scripts' => [file path string or array of file path strings],
+        *         // Scripts to include in specific language contexts
+        *         'languageScripts' => array(
+        *             [language code] => [file path string or array of file path strings],
+        *         ),
+        *         // Scripts to include in specific skin contexts
+        *         'skinScripts' => array(
+        *             [skin name] => [file path string or array of file path strings],
+        *         ),
+        *         // Scripts to include in debug contexts
+        *         'debugScripts' => [file path string or array of file path strings],
+        *         // Scripts to include in the startup module
+        *         'loaderScripts' => [file path string or array of file path strings],
+        *         // Modules which must be loaded before this module
+        *         'dependencies' => [modile name string or array of module name strings],
+        *         // Styles to always load
+        *         'styles' => [file path string or array of file path strings],
+        *         // Styles to include in specific skin contexts
+        *         'skinStyles' => array(
+        *             [skin name] => [file path string or array of file path strings],
+        *         ),
+        *         // Messages to always load
+        *         'messages' => [array of message key strings],
+        *         // Group which this module should be loaded together with
+        *         'group' => [group name string],
+        *         // Position on the page to load this module at
+        *         'position' => ['bottom' (default) or 'top']
+        *     )
         * @endcode
         */
        public function __construct( $options = array(), $localBasePath = null,
@@ -550,6 +551,7 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
         * Gets the contents of a list of JavaScript files.
         *
         * @param $scripts Array: List of file paths to scripts to read, remap and concetenate
+        * @throws MWException
         * @return String: Concatenated and remapped JavaScript data from $scripts
         */
        protected function readScriptFiles( array $scripts ) {
index 0f09fc3..28c3426 100644 (file)
@@ -68,12 +68,6 @@ abstract class ResourceLoaderWikiModule extends ResourceLoaderModule {
         * @return null|string
         */
        protected function getContent( $title ) {
-               if ( $title->getNamespace() === NS_MEDIAWIKI ) {
-                       // The first "true" is to use the database, the second is to use the content langue
-                       // and the last one is to specify the message key already contains the language in it ("/de", etc.)
-                       $text = MessageCache::singleton()->get( $title->getDBkey(), true, true, true );
-                       return $text === false ? '' : $text;
-               }
                if ( !$title->isCssJsSubpage() && !$title->isCssOrJsPage() ) {
                        return null;
                }
@@ -81,7 +75,16 @@ abstract class ResourceLoaderWikiModule extends ResourceLoaderModule {
                if ( !$revision ) {
                        return null;
                }
-               return $revision->getRawText();
+
+               $content = $revision->getContent( Revision::RAW );
+               $model = $content->getModel();
+
+               if ( $model !== CONTENT_MODEL_CSS && $model !== CONTENT_MODEL_JAVASCRIPT ) {
+                       wfDebug( __METHOD__ . "bad content model #$model for JS/CSS page!\n" );
+                       return null;
+               }
+
+               return $content->getNativeData(); //NOTE: this is safe, we know it's JS or CSS
        }
 
        /* Methods */
index 4f58099..8b8a3a3 100644 (file)
@@ -184,6 +184,7 @@ abstract class RevDel_List extends RevisionListBase {
         *     comment:         The log comment
         *     authorsIds:      The array of the user IDs of the offenders
         *     authorsIPs:      The array of the IP/anon user offenders
+        * @throws MWException
         */
        protected function updateLog( $params ) {
                // Get the URL param's corresponding DB field
index 27a321a..e1bb32f 100644 (file)
@@ -791,11 +791,14 @@ class SearchResult {
         */
        protected function initText() {
                if ( !isset( $this->mText ) ) {
-                       if ( $this->mRevision != null )
-                               $this->mText = $this->mRevision->getText();
-                       else // TODO: can we fetch raw wikitext for commons images?
+                       if ( $this->mRevision != null ) {
+                               //TODO: if we could plug in some code that knows about special content models *and* about
+                               //      special features of the search engine, the search could benefit.
+                               $content = $this->mRevision->getContent();
+                               $this->mText = $content->getTextForSearchIndex();
+                       } else { // TODO: can we fetch raw wikitext for commons images?
                                $this->mText = '';
-
+                       }
                }
        }
 
@@ -806,6 +809,8 @@ class SearchResult {
        function getTextSnippet( $terms ) {
                global $wgUser, $wgAdvancedSearchHighlighting;
                $this->initText();
+
+               // TODO: make highliter take a content object. Make ContentHandler a factory for SearchHighliter.
                list( $contextlines, $contextchars ) = SearchEngine::userHighlightPrefs( $wgUser );
                $h = new SearchHighlighter();
                if ( $wgAdvancedSearchHighlighting )
index bf7de3f..fb65326 100644 (file)
@@ -144,8 +144,17 @@ class SpecialBookSources extends SpecialPage {
                $title = Title::makeTitleSafe( NS_PROJECT, $page ); # Show list in content language
                if( is_object( $title ) && $title->exists() ) {
                        $rev = Revision::newFromTitle( $title, false, Revision::READ_NORMAL );
-                       $this->getOutput()->addWikiText( str_replace( 'MAGICNUMBER', $this->isbn, $rev->getText() ) );
-                       return true;
+                       $content = $rev->getContent();
+
+                       if ( $content instanceof TextContent ) {
+                               //XXX: in the future, this could be stored as structured data, defining a list of book sources
+
+                               $text = $content->getNativeData();
+                               $this->getOutput()->addWikiText( str_replace( 'MAGICNUMBER', $this->isbn, $text ) );
+                               return true;
+                       } else {
+                               throw new MWException( "Unexpected content type for book sources: " . $content->getModel() );
+                       }
                }
 
                # Fall back to the defaults given in the language file
index 9e3c52b..2b7036c 100644 (file)
@@ -111,14 +111,19 @@ class SpecialComparePages extends SpecialPage {
                $rev2 = self::revOrTitle( $data['Revision2'], $data['Page2'] );
 
                if( $rev1 && $rev2 ) {
-                       $de = new DifferenceEngine( $form->getContext(),
-                               $rev1,
-                               $rev2,
-                               null, // rcid
-                               ( $data['Action'] == 'purge' ),
-                               ( $data['Unhide'] == '1' )
-                       );
-                       $de->showDiffPage( true );
+                       $revision = Revision::newFromId( $rev1 );
+
+                       if ( $revision ) { // NOTE: $rev1 was already checked, should exist.
+                               $contentHandler = $revision->getContentHandler();
+                               $de = $contentHandler->createDifferenceEngine( $form->getContext(),
+                                       $rev1,
+                                       $rev2,
+                                       null, // rcid
+                                       ( $data['Action'] == 'purge' ),
+                                       ( $data['Unhide'] == '1' )
+                               );
+                               $de->showDiffPage( true );
+                       }
                }
        }
 
index b32b0ca..84e3cb7 100644 (file)
@@ -104,6 +104,7 @@ class SpecialJavaScriptTest extends SpecialPage {
         * be thrown.
         * @param $html String: The raw HTML.
         * @param $state String: State, one of 'noframework', 'unknownframework' or 'frameworkfound'
+        * @throws MWException
         * @return string
         */
        private function wrapSummaryHtml( $html, $state ) {
index 1f05749..bc9a3d9 100644 (file)
@@ -373,26 +373,32 @@ class SpecialMergeHistory extends SpecialPage {
                                        $destTitle->getPrefixedText()
                                )->inContentLanguage()->text();
                        }
-                       $mwRedir = MagicWord::get( 'redirect' );
-                       $redirectText = $mwRedir->getSynonym( 0 ) . ' [[' . $destTitle->getPrefixedText() . "]]\n";
-                       $redirectPage = WikiPage::factory( $targetTitle );
-                       $redirectRevision = new Revision( array(
-                               'page'    => $this->mTargetID,
-                               'comment' => $comment,
-                               'text'    => $redirectText ) );
-                       $redirectRevision->insertOn( $dbw );
-                       $redirectPage->updateRevisionOn( $dbw, $redirectRevision );
-
-                       # Now, we record the link from the redirect to the new title.
-                       # It should have no other outgoing links...
-                       $dbw->delete( 'pagelinks', array( 'pl_from' => $this->mDestID ), __METHOD__ );
-                       $dbw->insert( 'pagelinks',
-                               array(
-                                       'pl_from'      => $this->mDestID,
-                                       'pl_namespace' => $destTitle->getNamespace(),
-                                       'pl_title'     => $destTitle->getDBkey() ),
-                               __METHOD__
-                       );
+
+                       $contentHandler = ContentHandler::getForTitle( $targetTitle );
+                       $redirectContent = $contentHandler->makeRedirectContent( $destTitle );
+
+                       if ( $redirectContent ) {
+                               $redirectPage = WikiPage::factory( $targetTitle );
+                               $redirectRevision = new Revision( array(
+                                       'page'    => $this->mTargetID,
+                                       'comment' => $comment,
+                                       'content' => $redirectContent ) );
+                               $redirectRevision->insertOn( $dbw );
+                               $redirectPage->updateRevisionOn( $dbw, $redirectRevision );
+
+                               # Now, we record the link from the redirect to the new title.
+                               # It should have no other outgoing links...
+                               $dbw->delete( 'pagelinks', array( 'pl_from' => $this->mDestID ), __METHOD__ );
+                               $dbw->insert( 'pagelinks',
+                                       array(
+                                               'pl_from'      => $this->mDestID,
+                                               'pl_namespace' => $destTitle->getNamespace(),
+                                               'pl_title'     => $destTitle->getDBkey() ),
+                                       __METHOD__
+                               );
+                       } else {
+                               // would be nice to show a warning if we couldn't create a redirect
+                       }
                } else {
                        $targetTitle->invalidateCache(); // update histories
                }
index 8e15d55..bd7b41f 100644 (file)
@@ -164,8 +164,6 @@ class SpecialNewpages extends IncludableSpecialPage {
        }
 
        protected function filterLinks() {
-               global $wgGroupPermissions;
-
                // show/hide links
                $showhide = array( $this->msg( 'show' )->escaped(), $this->msg( 'hide' )->escaped() );
 
@@ -181,8 +179,7 @@ class SpecialNewpages extends IncludableSpecialPage {
                }
 
                // Disable some if needed
-               # @todo FIXME: Throws E_NOTICEs if not set; and doesn't obey hooks etc.
-               if ( $wgGroupPermissions['*']['createpage'] !== true ) {
+               if ( !User::groupHasPermission( '*', 'createpage' ) ) {
                        unset( $filters['hideliu'] );
                }
                if ( !$this->getUser()->useNPPatrol() ) {
@@ -459,11 +456,12 @@ class SpecialNewpages extends IncludableSpecialPage {
        protected function feedItemDesc( $row ) {
                $revision = Revision::newFromId( $row->rev_id );
                if( $revision ) {
+                       //XXX: include content model/type in feed item?
                        return '<p>' . htmlspecialchars( $revision->getUserText() ) .
                                $this->msg( 'colon-separator' )->inContentLanguage()->escaped() .
                                htmlspecialchars( FeedItem::stripComment( $revision->getComment() ) ) .
                                "</p>\n<hr />\n<div>" .
-                               nl2br( htmlspecialchars( $revision->getText() ) ) . "</div>";
+                               nl2br( htmlspecialchars( $revision->getContent()->serialize() ) ) . "</div>";
                }
                return '';
        }
@@ -488,7 +486,7 @@ class NewPagesPager extends ReverseChronologicalPager {
        }
 
        function getQueryInfo() {
-               global $wgEnableNewpagesUserFilter, $wgGroupPermissions;
+               global $wgEnableNewpagesUserFilter;
                $conds = array();
                $conds['rc_new'] = 1;
 
@@ -510,7 +508,7 @@ class NewPagesPager extends ReverseChronologicalPager {
                        $conds['rc_user_text'] = $user->getText();
                        $rcIndexes = 'rc_user_text';
                # If anons cannot make new pages, don't "exclude logged in users"!
-               } elseif( $wgGroupPermissions['*']['createpage'] && $this->opts->getValue( 'hideliu' ) ) {
+               } elseif( User::groupHasPermission( '*', 'createpage' ) && $this->opts->getValue( 'hideliu' ) ) {
                        $conds['rc_user'] = 0;
                }
                # If this user cannot see patrolled edits or they are off, don't do dumb queries!
index c09da4e..97f0037 100644 (file)
@@ -121,6 +121,8 @@ class SpecialPasswordReset extends FormSpecialPage {
         * userCanExecute(), and if the data array contains 'Username', etc, then Username
         * resets are allowed.
         * @param $data array
+        * @throws MWException
+        * @throws ThrottledError|PermissionsError
         * @return Bool|Array
         */
        public function onSubmit( array $data ) {
index 089f967..7856e54 100644 (file)
@@ -504,6 +504,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
 
        /**
         * UI entry point for form submission.
+        * @throws PermissionsError
         * @return bool
         */
        protected function submit() {
index fb2005b..853a805 100644 (file)
@@ -149,6 +149,7 @@ class SpecialUnblock extends SpecialPage {
         *
         * @param $data Array
         * @param $context IContextSource
+        * @throws ErrorPageError
         * @return Array( Array(message key, parameters) ) on failure, True on success
         */
        public static function processUnblock( array $data, IContextSource $context ){
index 6ebaed5..b735b18 100644 (file)
@@ -32,7 +32,16 @@ class PageArchive {
         * @var Title
         */
        protected $title;
-       var $fileStatus;
+
+       /**
+        * @var Status
+        */
+       protected $fileStatus;
+
+       /**
+        * @var Status
+        */
+       protected $revisionStatus;
 
        function __construct( $title ) {
                if( is_null( $title ) ) {
@@ -112,12 +121,22 @@ class PageArchive {
         * @return ResultWrapper
         */
        function listRevisions() {
+               global $wgContentHandlerNoDB;
+
                $dbr = wfGetDB( DB_SLAVE );
+
+               $fields = array(
+                       'ar_minor_edit', 'ar_timestamp', 'ar_user', 'ar_user_text',
+                       'ar_comment', 'ar_len', 'ar_deleted', 'ar_rev_id', 'ar_sha1',
+               );
+
+               if ( !$wgContentHandlerNoDB ) {
+                       $fields[] = 'ar_content_format';
+                       $fields[] = 'ar_content_model';
+               }
+
                $res = $dbr->select( 'archive',
-                       array(
-                               'ar_minor_edit', 'ar_timestamp', 'ar_user', 'ar_user_text',
-                               'ar_comment', 'ar_len', 'ar_deleted', 'ar_rev_id', 'ar_sha1'
-                       ),
+                       $fields,
                        array( 'ar_namespace' => $this->title->getNamespace(),
                                   'ar_title' => $this->title->getDBkey() ),
                        __METHOD__,
@@ -174,28 +193,38 @@ class PageArchive {
         * @return Revision
         */
        function getRevision( $timestamp ) {
+               global $wgContentHandlerNoDB;
+
                $dbr = wfGetDB( DB_SLAVE );
+
+               $fields = array(
+                       'ar_rev_id',
+                       'ar_text',
+                       'ar_comment',
+                       'ar_user',
+                       'ar_user_text',
+                       'ar_timestamp',
+                       'ar_minor_edit',
+                       'ar_flags',
+                       'ar_text_id',
+                       'ar_deleted',
+                       'ar_len',
+                       'ar_sha1',
+               );
+
+               if ( !$wgContentHandlerNoDB ) {
+                       $fields[] = 'ar_content_format';
+                       $fields[] = 'ar_content_model';
+               }
+
                $row = $dbr->selectRow( 'archive',
-                       array(
-                               'ar_rev_id',
-                               'ar_text',
-                               'ar_comment',
-                               'ar_user',
-                               'ar_user_text',
-                               'ar_timestamp',
-                               'ar_minor_edit',
-                               'ar_flags',
-                               'ar_text_id',
-                               'ar_deleted',
-                               'ar_len',
-                               'ar_sha1',
-                       ),
+                       $fields,
                        array( 'ar_namespace' => $this->title->getNamespace(),
                                        'ar_title' => $this->title->getDBkey(),
                                        'ar_timestamp' => $dbr->timestamp( $timestamp ) ),
                        __METHOD__ );
                if( $row ) {
-                       return Revision::newFromArchiveRow( $row, array( 'page' => $this->title->getArticleID() ) );
+                       return Revision::newFromArchiveRow( $row, array( 'title' => $this->title ) );
                } else {
                        return null;
                }
@@ -329,8 +358,6 @@ class PageArchive {
         * on success, false on failure
         */
        function undelete( $timestamps, $comment = '', $fileVersions = array(), $unsuppress = false, User $user = null ) {
-               global $wgUser;
-
                // If both the set of text revisions and file revisions are empty,
                // restore everything. Otherwise, just restore the requested items.
                $restoreAll = empty( $timestamps ) && empty( $fileVersions );
@@ -341,7 +368,7 @@ class PageArchive {
                if( $restoreFiles && $this->title->getNamespace() == NS_FILE ) {
                        $img = wfLocalFile( $this->title );
                        $this->fileStatus = $img->restore( $fileVersions, $unsuppress );
-                       if ( !$this->fileStatus->isOk() ) {
+                       if ( !$this->fileStatus->isOK() ) {
                                return false;
                        }
                        $filesRestored = $this->fileStatus->successCount;
@@ -350,10 +377,12 @@ class PageArchive {
                }
 
                if( $restoreText ) {
-                       $textRestored = $this->undeleteRevisions( $timestamps, $unsuppress, $comment );
-                       if( $textRestored === false ) { // It must be one of UNDELETE_*
+                       $this->revisionStatus = $this->undeleteRevisions( $timestamps, $unsuppress, $comment );
+                       if( !$this->revisionStatus->isOK() ) {
                                return false;
                        }
+
+                       $textRestored = $this->revisionStatus->getValue();
                } else {
                        $textRestored = 0;
                }
@@ -379,6 +408,7 @@ class PageArchive {
                }
 
                if ( $user === null ) {
+                       global $wgUser;
                        $user = $wgUser;
                }
 
@@ -401,11 +431,13 @@ class PageArchive {
         * @param $comment String
         * @param $unsuppress Boolean: remove all ar_deleted/fa_deleted restrictions of seletected revs
         *
-        * @return Mixed: number of revisions restored or false on failure
+        * @return Status, containing the number of revisions restored on success
         */
        private function undeleteRevisions( $timestamps, $unsuppress = false, $comment = '' ) {
+               global $wgContentHandlerNoDB;
+
                if ( wfReadOnly() ) {
-                       return false;
+                       throw new ReadOnlyError();
                }
                $restoreAll = empty( $timestamps );
 
@@ -435,9 +467,14 @@ class PageArchive {
                        $previousTimestamp = $dbw->selectField( 'revision', 'rev_timestamp',
                                array( 'rev_id' => $previousRevId ),
                                __METHOD__ );
+
                        if( $previousTimestamp === false ) {
                                wfDebug( __METHOD__.": existing page refers to a page_latest that does not exist\n" );
-                               return 0;
+
+                               $status = Status::newGood( 0 );
+                               $status->warning( 'undeleterevision-missing' );
+
+                               return $status;
                        }
                } else {
                        # Have to create a new article...
@@ -457,24 +494,31 @@ class PageArchive {
                        $oldones = "ar_timestamp IN ( {$oldts} )";
                }
 
+               $fields = array(
+                       'ar_rev_id',
+                       'ar_text',
+                       'ar_comment',
+                       'ar_user',
+                       'ar_user_text',
+                       'ar_timestamp',
+                       'ar_minor_edit',
+                       'ar_flags',
+                       'ar_text_id',
+                       'ar_deleted',
+                       'ar_page_id',
+                       'ar_len',
+                       'ar_sha1');
+
+               if ( !$wgContentHandlerNoDB ) {
+                       $fields[] = 'ar_content_format';
+                       $fields[] = 'ar_content_model';
+               }
+
                /**
                 * Select each archived revision...
                 */
                $result = $dbw->select( 'archive',
-                       /* fields */ array(
-                               'ar_rev_id',
-                               'ar_text',
-                               'ar_comment',
-                               'ar_user',
-                               'ar_user_text',
-                               'ar_timestamp',
-                               'ar_minor_edit',
-                               'ar_flags',
-                               'ar_text_id',
-                               'ar_deleted',
-                               'ar_page_id',
-                               'ar_len',
-                               'ar_sha1' ),
+                       $fields,
                        /* WHERE */ array(
                                'ar_namespace' => $this->title->getNamespace(),
                                'ar_title'     => $this->title->getDBkey(),
@@ -486,17 +530,38 @@ class PageArchive {
                $rev_count = $dbw->numRows( $result );
                if( !$rev_count ) {
                        wfDebug( __METHOD__ . ": no revisions to restore\n" );
-                       return false; // ???
+
+                       $status = Status::newGood( 0 );
+                       $status->warning( "undelete-no-results" );
+                       return $status;
                }
 
                $ret->seek( $rev_count - 1 ); // move to last
                $row = $ret->fetchObject(); // get newest archived rev
                $ret->seek( 0 ); // move back
 
+               // grab the content to check consistency with global state before restoring the page.
+               $revision = Revision::newFromArchiveRow( $row,
+                       array(
+                               'title' => $article->getTitle(), // used to derive default content model
+                       ) );
+
+               $m = $revision->getContentModel();
+
+               $user = User::newFromName( $revision->getRawUserText(), false );
+               $content = $revision->getContent( Revision::RAW );
+
+               //NOTE: article ID may not be known yet. prepareSave() should not modify the database.
+               $status = $content->prepareSave( $article, 0, -1, $user );
+
+               if ( !$status->isOK() ) {
+                       return $status;
+               }
+
                if( $makepage ) {
                        // Check the state of the newest to-be version...
                        if( !$unsuppress && ( $row->ar_deleted & Revision::DELETED_TEXT ) ) {
-                               return false; // we can't leave the current revision like this!
+                               return Status::newFatal( "undeleterevdel" );
                        }
                        // Safe to insert now...
                        $newid  = $article->insertOn( $dbw );
@@ -506,7 +571,7 @@ class PageArchive {
                        if( $row->ar_timestamp > $previousTimestamp ) {
                                // Check the state of the newest to-be version...
                                if( !$unsuppress && ( $row->ar_deleted & Revision::DELETED_TEXT ) ) {
-                                       return false; // we can't leave the current revision like this!
+                                       return Status::newFatal( "undeleterevdel" );
                                }
                        }
                }
@@ -527,7 +592,7 @@ class PageArchive {
                        // unless we are specifically removing all restrictions...
                        $revision = Revision::newFromArchiveRow( $row,
                                array(
-                                       'page' => $pageId,
+                                       'title' => $this->title,
                                        'deleted' => $unsuppress ? 0 : $row->ar_deleted
                                ) );
 
@@ -546,7 +611,7 @@ class PageArchive {
 
                // Was anything restored at all?
                if ( $restored == 0 ) {
-                       return 0;
+                       return Status::newGood( 0 );
                }
 
                $created = (bool)$newid;
@@ -566,13 +631,18 @@ class PageArchive {
                        $update->doUpdate();
                }
 
-               return $restored;
+               return Status::newGood( $restored );
        }
 
        /**
         * @return Status
         */
        function getFileStatus() { return $this->fileStatus; }
+
+       /**
+        * @return Status
+        */
+       function getRevisionStatus() { return $this->revisionStatus; }
 }
 
 /**
@@ -780,11 +850,13 @@ class SpecialUndelete extends SpecialPage {
 
        private function showRevision( $timestamp ) {
                if( !preg_match( '/[0-9]{14}/', $timestamp ) ) {
-                       return 0;
+                       return;
                }
 
                $archive = new PageArchive( $this->mTargetObj );
-               wfRunHooks( 'UndeleteForm::showRevision', array( &$archive, $this->mTargetObj ) );
+               if ( !wfRunHooks( 'UndeleteForm::showRevision', array( &$archive, $this->mTargetObj ) ) ) {
+                       return;
+               }
                $rev = $archive->getRevision( $timestamp );
 
                $out = $this->getOutput();
@@ -834,7 +906,11 @@ class SpecialUndelete extends SpecialPage {
                $t = $lang->userTime( $timestamp, $user );
                $userLink = Linker::revUserTools( $rev );
 
-               if( $this->mPreview ) {
+               $content = $rev->getContent( Revision::FOR_THIS_USER, $user );
+
+               $isText = ( $content instanceof TextContent );
+
+               if( $this->mPreview || $isText ) {
                        $openDiv = '<div id="mw-undelete-revision" class="mw-warning">';
                } else {
                        $openDiv = '<div id="mw-undelete-revision">';
@@ -851,23 +927,48 @@ class SpecialUndelete extends SpecialPage {
 
                $out->addHTML( $this->msg( 'undelete-revision' )->rawParams( $link )->params(
                        $time )->rawParams( $userLink )->params( $d, $t )->parse() . '</div>' );
-               wfRunHooks( 'UndeleteShowRevision', array( $this->mTargetObj, $rev ) );
 
-               if( $this->mPreview ) {
+               if ( !wfRunHooks( 'UndeleteShowRevision', array( $this->mTargetObj, $rev ) ) ) {
+                       return;
+               }
+
+               if( $this->mPreview || !$isText ) {
+                       // NOTE: non-text content has no source view, so always use rendered preview
+
                        // Hide [edit]s
                        $popts = $out->parserOptions();
                        $popts->setEditSection( false );
-                       $out->parserOptions( $popts );
-                       $out->addWikiTextTitleTidy( $rev->getText( Revision::FOR_THIS_USER, $user ), $this->mTargetObj, true );
+
+                       $pout = $content->getParserOutput( $this->mTargetObj, $rev->getId(), $popts, true );
+                       $out->addParserOutput( $pout );
+               }
+
+               if ( $isText ) {
+                       // source view for textual content
+                       $sourceView = Xml::element( 'textarea', array(
+                               'readonly' => 'readonly',
+                               'cols' => intval( $user->getOption( 'cols' ) ),
+                               'rows' => intval( $user->getOption( 'rows' ) ) ),
+                               $content->getNativeData() . "\n" );
+
+                       $previewButton = Xml::element( 'input', array(
+                               'type' => 'submit',
+                               'name' => 'preview',
+                               'value' => $this->msg( 'showpreview' )->text() ) );
+               } else {
+                       $sourceView = '';
+                       $previewButton = '';
                }
 
+               $diffButton = Xml::element( 'input', array(
+                       'name' => 'diff',
+                       'type' => 'submit',
+                       'value' => $this->msg( 'showdiff' )->text() ) );
+
                $out->addHTML(
-                       Xml::element( 'textarea', array(
-                                       'readonly' => 'readonly',
-                                       'cols' => intval( $user->getOption( 'cols' ) ),
-                                       'rows' => intval( $user->getOption( 'rows' ) ) ),
-                               $rev->getText( Revision::FOR_THIS_USER, $user ) . "\n" ) .
-                       Xml::openElement( 'div' ) .
+                       $sourceView .
+                       Xml::openElement( 'div', array(
+                               'style' => 'clear: both' ) ) .
                        Xml::openElement( 'form', array(
                                'method' => 'post',
                                'action' => $this->getTitle()->getLocalURL( array( 'action' => 'submit' ) ) ) ) .
@@ -883,14 +984,8 @@ class SpecialUndelete extends SpecialPage {
                                'type' => 'hidden',
                                'name' => 'wpEditToken',
                                'value' => $user->getEditToken() ) ) .
-                       Xml::element( 'input', array(
-                               'type' => 'submit',
-                               'name' => 'preview',
-                               'value' => $this->msg( 'showpreview' )->text() ) ) .
-                       Xml::element( 'input', array(
-                               'name' => 'diff',
-                               'type' => 'submit',
-                               'value' => $this->msg( 'showdiff' )->text() ) ) .
+                       $previewButton .
+                       $diffButton .
                        Xml::closeElement( 'form' ) .
                        Xml::closeElement( 'div' ) );
        }
@@ -904,7 +999,11 @@ class SpecialUndelete extends SpecialPage {
         * @return String: HTML
         */
        function showDiff( $previousRev, $currentRev ) {
-               $diffEngine = new DifferenceEngine( $this->getContext() );
+               $diffContext = clone $this->getContext();
+               $diffContext->setTitle( $currentRev->getTitle() );
+               $diffContext->setWikiPage( WikiPage::factory( $currentRev->getTitle() ) );
+
+               $diffEngine = $currentRev->getContentHandler()->createDifferenceEngine( $diffContext );
                $diffEngine->showDiffStyle();
                $this->getOutput()->addHTML(
                        "<div>" .
@@ -921,9 +1020,9 @@ class SpecialUndelete extends SpecialPage {
                                $this->diffHeader( $currentRev, 'n' ) .
                                "</td>\n" .
                        "</tr>" .
-                       $diffEngine->generateDiffBody(
-                               $previousRev->getText( Revision::FOR_THIS_USER, $this->getUser() ),
-                               $currentRev->getText( Revision::FOR_THIS_USER, $this->getUser() ) ) .
+                       $diffEngine->generateContentDiffBody(
+                               $previousRev->getContent( Revision::FOR_THIS_USER, $this->getUser() ),
+                               $currentRev->getContent( Revision::FOR_THIS_USER, $this->getUser() ) ) .
                        "</table>" .
                        "</div>\n"
                );
@@ -1180,7 +1279,10 @@ class SpecialUndelete extends SpecialPage {
 
        private function formatRevisionRow( $row, $earliestLiveTime, $remaining ) {
                $rev = Revision::newFromArchiveRow( $row,
-                       array( 'page' => $this->mTargetObj->getArticleID() ) );
+                       array(
+                               'title' => $this->mTargetObj
+                       ) );
+
                $revTextSize = '';
                $ts = wfTimestamp( TS_MW, $row->ar_timestamp );
                // Build checkboxen...
@@ -1417,11 +1519,15 @@ class SpecialUndelete extends SpecialPage {
                        $out->addHTML( $this->msg( 'undeletedpage' )->rawParams( $link )->parse() );
                } else {
                        $out->setPageTitle( $this->msg( 'undelete-error' ) );
-                       $out->addWikiMsg( 'cannotundelete' );
-                       $out->addWikiMsg( 'undeleterevdel' );
                }
 
-               // Show file deletion warnings and errors
+               // Show revision undeletion warnings and errors
+               $status = $archive->getRevisionStatus();
+               if( $status && !$status->isGood() ) {
+                       $out->addWikiText( '<div class="error">' . $status->getWikiText( 'cannotundelete', 'cannotundelete' ) . '</div>' );
+               }
+
+               // Show file undeletion warnings and errors
                $status = $archive->getFileStatus();
                if( $status && !$status->isGood() ) {
                        $out->addWikiText( '<div class="error">' . $status->getWikiText( 'undelete-error-short', 'undelete-error-long' ) . '</div>' );
index 43ea345..a15fdd2 100644 (file)
@@ -525,6 +525,7 @@ class SpecialUpload extends SpecialPage {
         * Provides output to the user for a result of UploadBase::verifyUpload
         *
         * @param $details Array: result of UploadBase::verifyUpload
+        * @throws MWException
         */
        protected function processVerificationError( $details ) {
                global $wgFileExtensions;
index 1a00d73..6353b1c 100644 (file)
@@ -74,6 +74,7 @@ class SpecialUploadStash extends UnlistedSpecialPage {
         * n.b. Most sanity checking done in UploadStashLocalFile, so this is straightforward.
         *
         * @param $key String: the key of a particular requested file
+        * @throws HttpError
         * @return bool
         */
        public function showUpload( $key ) {
@@ -113,6 +114,7 @@ class SpecialUploadStash extends UnlistedSpecialPage {
         * application the transform parameters
         *
         * @param string $key
+        * @throws UploadStashBadPathException
         * @return array
         */
        private function parseKey( $key ) {
@@ -164,10 +166,11 @@ class SpecialUploadStash extends UnlistedSpecialPage {
 
        /**
         * Scale a file (probably with a locally installed imagemagick, or similar) and output it to STDOUT.
-        * @param $file: File object
-        * @param $params: scaling parameters ( e.g. array( width => '50' ) );
-        * @param $flags: scaling flags ( see File:: constants )
+        * @param $file File
+        * @param $params array Scaling parameters ( e.g. array( width => '50' ) );
+        * @param $flags int Scaling flags ( see File:: constants )
         * @throws MWException
+        * @throws UploadStashFileNotFoundException
         * @return boolean success
         */
        private function outputLocallyScaledThumb( $file, $params, $flags ) {
@@ -258,6 +261,7 @@ class SpecialUploadStash extends UnlistedSpecialPage {
         * Side effect: writes HTTP response to STDOUT.
         *
         * @param $file File object with a local path (e.g. UnregisteredLocalFile, LocalFile. Oddly these don't share an ancestor!)
+        * @throws SpecialUploadStashTooLargeException
         * @return bool
         */
        private function outputLocalFile( File $file ) {
@@ -275,6 +279,7 @@ class SpecialUploadStash extends UnlistedSpecialPage {
         * Side effect: writes HTTP response to STDOUT.
         * @param $content String content
         * @param $contentType String mime type
+        * @throws SpecialUploadStashTooLargeException
         * @return bool
         */
        private function outputContents( $content, $contentType ) {
index b7d01c8..995d879 100644 (file)
@@ -286,6 +286,7 @@ class LoginForm extends SpecialPage {
 
        /**
         * @private
+        * @throws PermissionsError|ReadOnlyError
         * @return bool|User
         */
        function addNewAccountInternal() {
index ab2bf0a..4be36c6 100644 (file)
@@ -49,8 +49,11 @@ class SpecialUserlogout extends UnlistedSpecialPage {
                $oldName = $user->getName();
                $user->logout();
 
+               $loginURL = SpecialPage::getTitleFor( 'Userlogin' )->getFullURL(
+                       $this->getRequest()->getValues( 'returnto', 'returntoquery' ) );
+
                $out = $this->getOutput();
-               $out->addWikiMsg( 'logouttext' );
+               $out->addWikiMsg( 'logouttext', $loginURL );
 
                // Hook.
                $injected_html = '';
index 9f5a48a..56ae302 100644 (file)
@@ -62,6 +62,7 @@ class UserrightsPage extends SpecialPage {
         * Depending on the submit button used, call a form or a save function.
         *
         * @param $par Mixed: string if any subpage provided, else null
+        * @throws UserBlockedError|PermissionsError
         */
        public function execute( $par ) {
                // If the visitor doesn't have permissions to assign or remove
index 0542bba..b0e5fb6 100644 (file)
@@ -76,7 +76,7 @@ class UploadFromChunks extends UploadFromFile {
                $this->mFileKey = $this->mLocalFile->getFileKey();
 
                // Output a copy of this first to chunk 0 location:
-               $status = $this->outputChunk( $this->mLocalFile->getPath() );
+               $this->outputChunk( $this->mLocalFile->getPath() );
 
                // Update db table to reflect initial "chunk" state
                $this->updateChunkStatus();
index bbd9c44..560acde 100644 (file)
@@ -319,8 +319,8 @@ class UploadStash {
        /**
         * Remove a particular file from the stash.  Also removes it from the repo.
         *
-        * @throws UploadStashNotLoggedInException
-        * @throws UploadStashWrongOwnerException
+        * @param $key
+        * @throws UploadStashNoSuchKeyException|UploadStashNotLoggedInException|UploadStashWrongOwnerException
         * @return boolean: success
         */
        public function removeFile( $key ) {
@@ -416,6 +416,8 @@ class UploadStash {
         * with an extension.
         * XXX this is somewhat redundant with the checks that ApiUpload.php does with incoming
         * uploads versus the desired filename. Maybe we can get that passed to us...
+        * @param $path
+        * @throws UploadStashFileException
         * @return string
         */
        public static function getExtensionForPath( $path ) {
index e648ee2..d3841c9 100644 (file)
@@ -419,6 +419,16 @@ class Language {
         */
        public function setNamespaces( array $namespaces ) {
                $this->namespaceNames = $namespaces;
+               $this->mNamespaceIds = null;
+       }
+
+       /**
+        * Resets all of the namespace caches. Mainly used for testing
+        */
+       public function resetNamespaces( ) {
+               $this->namespaceNames = null;
+               $this->mNamespaceIds = null;
+               $this->namespaceAliases = null;
        }
 
        /**
index 56b885f..8a69799 100644 (file)
@@ -925,7 +925,11 @@ class LanguageConverter {
                        if ( $title && $title->exists() ) {
                                $revision = Revision::newFromTitle( $title );
                                if ( $revision ) {
-                                       $txt = $revision->getRawText();
+                                       if ( $revision->getContentModel() == CONTENT_MODEL_WIKITEXT ) {
+                                               $txt = $revision->getContent( Revision::RAW )->getNativeData();
+                                       }
+
+                                       //@todo: in the future, use a specialized content model, perhaps based on json!
                                }
                        }
                }
@@ -1035,9 +1039,9 @@ class LanguageConverter {
         * MediaWiki:Conversiontable* is updated.
         * @private
         *
-        * @param $article Article object
+        * @param $page WikiPage object
         * @param $user Object: User object for the current user
-        * @param $text String: article text (?)
+        * @param $content Content: new page content
         * @param $summary String: edit summary of the edit
         * @param $isMinor Boolean: was the edit marked as minor?
         * @param $isWatch Boolean: did the user watch this page or not?
@@ -1046,9 +1050,9 @@ class LanguageConverter {
         * @param $revision Object: new Revision object or null
         * @return Boolean: true
         */
-       function OnArticleSaveComplete( $article, $user, $text, $summary, $isMinor,
+       function OnArticleContentSaveComplete( $page, $user, $content, $summary, $isMinor,
                        $isWatch, $section, $flags, $revision ) {
-               $titleobj = $article->getTitle();
+               $titleobj = $page->getTitle();
                if ( $titleobj->getNamespace() == NS_MEDIAWIKI ) {
                        $title = $titleobj->getDBkey();
                        $t = explode( '/', $title, 3 );
index 1865cc5..6a2820d 100644 (file)
@@ -102,7 +102,7 @@ class LanguageFi extends Language {
                        'monday' => 'maanantai',
                        'tuesday' => 'tiistai',
                        'wednesday' => 'keskiviikko',
-                       'thursay' => 'torstai',
+                       'thursday' => 'torstai',
                        'friday' => 'perjantai',
                        'saturday' => 'lauantai',
                        'sunday' => 'sunnuntai',
index 14fe928..0c8bd22 100644 (file)
@@ -116,7 +116,7 @@ class LanguageGan extends LanguageZh {
                                                                array(),
                                                                $ml );
 
-               $wgHooks['ArticleSaveComplete'][] = $this->mConverter;
+               $wgHooks['ArticleContentSaveComplete'][] = $this->mConverter;
        }
 
        /**
index 7402b08..3ff336b 100644 (file)
@@ -233,6 +233,6 @@ class LanguageIu extends Language {
 
                $flags = array();
                $this->mConverter = new IuConverter( $this, 'iu', $variants, $variantfallbacks, $flags );
-               $wgHooks['ArticleSaveComplete'][] = $this->mConverter;
+               $wgHooks['ArticleContentSaveComplete'][] = $this->mConverter;
        }
 }
index d3d487f..a7e5866 100644 (file)
@@ -440,7 +440,7 @@ class LanguageKk extends LanguageKk_cyrl {
 
                $this->mConverter = new KkConverter( $this, 'kk', $variants, $variantfallbacks );
 
-               $wgHooks['ArticleSaveComplete'][] = $this->mConverter;
+               $wgHooks['ArticleContentSaveComplete'][] = $this->mConverter;
        }
 
        /**
index d60f083..9150663 100644 (file)
@@ -273,6 +273,6 @@ class LanguageKu extends LanguageKu_ku {
                );
 
                $this->mConverter = new KuConverter( $this, 'ku', $variants, $variantfallbacks );
-               $wgHooks['ArticleSaveComplete'][] = $this->mConverter;
+               $wgHooks['ArticleContentSaveComplete'][] = $this->mConverter;
        }
 }
index 8e2115c..9d92834 100644 (file)
@@ -36,6 +36,6 @@ class LanguageQqx extends Language {
         * @return string
         */
        function getMessage( $key ) {
-               return "($key)";
+               return "($key$*)";
        }
 }
index 36f3407..335d551 100644 (file)
@@ -212,6 +212,6 @@ class LanguageShi extends Language {
 
                $flags = array();
                $this->mConverter = new ShiConverter( $this, 'shi', $variants, $variantfallbacks, $flags );
-               $wgHooks['ArticleSaveComplete'][] = $this->mConverter;
+               $wgHooks['ArticleContentSaveComplete'][] = $this->mConverter;
        }
 }
index 5f1110d..b043778 100644 (file)
@@ -246,7 +246,7 @@ class LanguageSr extends LanguageSr_ec {
                        'W' => 'W', 'реч'   => 'W', 'reč'   => 'W', 'ријеч' => 'W', 'riječ' => 'W'
                );
                $this->mConverter = new SrConverter( $this, 'sr', $variants, $variantfallbacks, $flags );
-               $wgHooks['ArticleSaveComplete'][] = $this->mConverter;
+               $wgHooks['ArticleContentSaveComplete'][] = $this->mConverter;
        }
 
        /**
index d6ddf10..2db64a7 100644 (file)
@@ -132,6 +132,6 @@ class LanguageUz extends Language {
                );
 
                $this->mConverter = new UzConverter( $this, 'uz', $variants, $variantfallbacks );
-               $wgHooks['ArticleSaveComplete'][] = $this->mConverter;
+               $wgHooks['ArticleContentSaveComplete'][] = $this->mConverter;
        }
 }
index 63f90fd..11cf0cf 100644 (file)
@@ -146,7 +146,7 @@ class LanguageZh extends LanguageZh_hans {
                                                                array(),
                                                                $ml );
 
-               $wgHooks['ArticleSaveComplete'][] = $this->mConverter;
+               $wgHooks['ArticleContentSaveComplete'][] = $this->mConverter;
        }
 
        /**
index 2e2ed5e..3aa6b1f 100644 (file)
@@ -417,7 +417,7 @@ $2',
 # Login and logout pages
 'logouttext' => "'''أنت الآن غير مسجل الدخول.'''
 
-تستطيع المتابعة باستعمال {{SITENAME}} كمجهول، أو [[Special:UserLogin|الدخول مرة أخرى]] بنفس الاسم أو باسم آخر.
+تستطيع المتابعة باستعمال {{SITENAME}} كمجهول، أو <span class='plainlinks'>[$1 الدخول مرة أخرى]</span> بنفس الاسم أو باسم آخر.
 من الممكن أن ترى بعض الصفحات كما لو أنك مسجل الدخول، وذلك حتى تقوم بإفراغ الصفحات المختزنة في المتصفح لديك.",
 'welcomecreation' => '== مرحبا، $1! ==
 تم إنشاء حسابك.
index 9df5047..517745c 100644 (file)
@@ -616,7 +616,7 @@ Die rede hiervoor is "\'\'$3\'\'".',
 # Login and logout pages
 'logouttext' => "'''U is nou uitgeteken'''
 
-U kan aanhou om {{SITENAME}} anoniem te gebruik; of u kan weer [[Special:UserLogin|inteken]] as dieselfde of 'n ander gebruiker.
+U kan aanhou om {{SITENAME}} anoniem te gebruik; of u kan weer <span class='plainlinks'>[$1 inteken]</span> as dieselfde of 'n ander gebruiker.
 Dit is moontlik dat sommige bladsye nog sal aandui dat u aangeteken is totdat u u webblaaier se kas skoonmaak.",
 'welcomecreation' => '== Welkom, $1! ==
 U rekening is geskep;
index 963c295..a94eac8 100644 (file)
@@ -451,7 +451,7 @@ Arsyeja e dhânë âsht "\'\'$2\'\'".',
 # Login and logout pages
 'logouttext' => "'''Jeni çlajmërue.'''
 
-Mundeni me vazhdue me shfrytëzue {{SITENAME}} në mënyrë anonime, apo mundeni [[Special:UserLogin|me u kyçë]] si përdoruesi i njêjtë apo si nji tjetër.
+Mundeni me vazhdue me shfrytëzue {{SITENAME}} në mënyrë anonime, apo mundeni <span class='plainlinks'>[$1 me u kyçë]</span> si përdoruesi i njêjtë apo si nji tjetër.
 Disa faqe mujnë me u paraqitë prap si t'kishit qenë t'kyçun, derisa ta pastroni memorizimin e shfletuesit.",
 'welcomecreation' => '== Mirësevini, $1! ==
 
index 33d88df..491b186 100644 (file)
@@ -522,7 +522,7 @@ A razón data ye ''$2''.",
 # Login and logout pages
 'logouttext' => "'''Ha rematato a sesión.'''
 
-Puede continar navegando por {{SITENAME}} anonimament, u puede [[Special:UserLogin|encetar]] una nueva sesión con o mesmo nombre d'usuario u bell atro diferent. Pare cuenta que, entre que se limpia a caché d'o navegador, puet estar que bellas pachinas s'amuestren como si encara continase en a sesión anterior.",
+Puede continar navegando por {{SITENAME}} anonimament, u puede <span class='plainlinks'>[$1 encetar]</span> una nueva sesión con o mesmo nombre d'usuario u bell atro diferent. Pare cuenta que, entre que se limpia a caché d'o navegador, puet estar que bellas pachinas s'amuestren como si encara continase en a sesión anterior.",
 'welcomecreation' => "== ¡Bienveniu(da), $1! ==
 S'ha creyato a suya cuenta.
 No xublide de presonalizar [[Special:Preferences|as suyas preferencias en {{SITENAME}}]].",
index 7a42ebd..3b55cfb 100644 (file)
@@ -359,7 +359,7 @@ Cȳþþuhord edƿende ƿōh "<tt>$3: $4</tt>"',
 # Login and logout pages
 'logouttext' => "'''Þū eart nū ūtmeldod.'''
 
-Þū canst ætfeolan tō brūcenne {{SITENAME}} ungecūðe, oþþe þū canst [[Special:UserLogin|inmeldian eft]] tō ylcan oþþe ōðrum brūcende.
+Þū canst ætfeolan tō brūcenne {{SITENAME}} ungecūðe, oþþe þū canst <span class='plainlinks'>[$1 inmeldian eft]</span> tō ylcan oþþe ōðrum brūcende.
 Cnāw þæt sume sīdan cunnon gelǣstende ēowod wesan swā þū wǣre gīet inmeldod, oþ þæt þū clǣnsie þīnes sēcendtōles gemynd.",
 'welcomecreation' => '== Ƿilcumen, $1! ==
 
index 9694fdf..a378245 100644 (file)
@@ -881,7 +881,7 @@ $2',
 # Login and logout pages
 'logouttext' => "'''أنت الآن غير مسجل الدخول.'''
 
-تستطيع المتابعة باستعمال {{SITENAME}} كمجهول، أو [[Special:UserLogin|الدخول مرة أخرى]] بنفس الاسم أو باسم آخر.
+تستطيع المتابعة باستعمال {{SITENAME}} كمجهول، أو <span class='plainlinks'>[$1 الدخول مرة أخرى]</span> بنفس الاسم أو باسم آخر.
 من الممكن أن ترى بعض الصفحات كما لو أنك مسجل الدخول، وذلك حتى تقوم بإفراغ الصفحات المختزنة في المتصفح لديك.",
 'welcomecreation' => '== مرحبا، $1! ==
 تم إنشاء حسابك.
@@ -3340,7 +3340,6 @@ $1',
 'pageinfo-authors' => 'عدد المؤلفين المختلفين',
 'pageinfo-recent-edits' => 'عدد التعديلات الأخيرة (خلال  $1 يوم/أيام)',
 'pageinfo-recent-authors' => 'عدد المؤلفين المختلفين الأخيرين',
-'pageinfo-restriction' => 'حماية الصفحة ($1)',
 'pageinfo-magic-words' => 'السحرية {{PLURAL:$1|الكلمة|الكلمات}} ($1)',
 'pageinfo-hidden-categories' => 'مخفية {{PLURAL:$1|فئة|فئات}} ($1)',
 'pageinfo-templates' => 'متضمنة {{PLURAL:$1|قالب|قوالب}} ($1)',
index 6c4493a..6314006 100644 (file)
@@ -116,6 +116,7 @@ $messages = array(
 'tog-watchdefault' => 'ܐܘܣܦ ܦܐܬܬ̈ܐ ܘܠܦܦ̈ܐ ܕܫܚܠܦ ܐܢܐ ܠܪ̈ܗܝܬܝ',
 'tog-watchmoves' => 'ܐܘܣܦ ܦܐܬܬ̈ܐ ܘܠܦܦ̈ܐ ܕܫܢܐ ܐܢܐ ܠܪ̈ܗܝܬܝ',
 'tog-watchdeletion' => 'ܐܘܣܦ ܦܐܬܬ̈ܐ ܘܠܦܦ̈ܐ ܕܫܐܦ ܐܢܐ ܠܪ̈ܗܝܬܝ',
+'tog-oldsig' => 'ܪܡܝ ܐܝܕܐ ܗܫܝܐ:',
 'tog-watchlisthideown' => 'ܛܫܝ ܫܘܚܠܦ̈ܝ ܡܢ ܪ̈ܗܝܬܐ',
 'tog-watchlisthidebots' => 'ܛܫܝ ܫܘܚܠܦ̈ܐ ܕܒܘܬ ܡܢ ܪ̈ܗܝܬܐ',
 'tog-watchlisthideminor' => 'ܛܫܝ ܫܘܚܠܦ̈ܐ ܙܥܘܪ̈ܐ ܡܢ ܪ̈ܗܝܬܐ',
@@ -387,7 +388,7 @@ $1',
 # Login and logout pages
 'logouttext' => "'''ܗܫܐ ܦܠܛܠܟ ܡܢ ܚܘܫܒܢܟ.'''
 
-ܡܨܐ ܐܢܬ ܐܦܠܚܬ {{SITENAME}} ܐܝܟ ܡܦܠܚܢܐ ܠܐ ܝܕܝܥܐ ܐܘ ܡܨܐ ܐܢܬ ܕ[[Special:UserLogin|ܬܥܘܠ]] ܒܚܘܫܒܢܐ ܥܝܢܗ ܐܘ ܐܝܟ ܡܦܠܚܢܐ ܐܚܪܢܐ.
+ܡܨܐ ܐܢܬ ܐܦܠܚܬ {{SITENAME}} ܐܝܟ ܡܦܠܚܢܐ ܠܐ ܝܕܝܥܐ ܐܘ ܡܨܐ ܐܢܬ ܕ<span class='plainlinks'>[$1 ܬܥܘܠ]</span> ܒܚܘܫܒܢܐ ܥܝܢܗ ܐܘ ܐܝܟ ܡܦܠܚܢܐ ܐܚܪܢܐ.
 
 ܚܕ ܟܡܐ ܡܢ ܦܐܬܬ̈ܐ ܡܬܚܙܝܢ ܐܝܟ ܕܗܘ ܐܢܬ ܥܠܝܠܐ ܥܕܡܐ ܕܐܣܦܩܬ ܠܦܐܬܬ̈ܐ ܠܒܝܟܬ̈ܐ ܕܡܦܐܬܢܐ ܕܝܠܟ",
 'welcomecreation' => '== ܒܫܝܢܐ, $1! ==
@@ -426,7 +427,7 @@ $1',
 'passwordremindertitle' => 'ܡܠܬܐ ܕܥܠܠܐ ܙܒܢܢܝܬܐ ܚܕܬܐ ܠ{{SITENAME}}',
 'noemail' => 'ܠܝܬ ܒܝܠܕܪܐ ܐܠܩܛܪܘܢܝܐ ܠܡܦܠܚܢܐ "$1".',
 'mailerror' => 'ܦܘܕܐ ܒܫܘܕܪܐ ܕܒܝܠܕܪܐ: $1',
-'emailconfirmlink' => 'Ü\9aܬܬ ܒܝܠܕܪܐ ܐܠܩܛܪܘܢܝܐ ܕܝܠܟ',
+'emailconfirmlink' => 'ܫܪܪ ܒܝܠܕܪܐ ܐܠܩܛܪܘܢܝܐ ܕܝܠܟ',
 'accountcreated' => 'ܚܘܫܒܢܐ ܒܪܐ',
 'accountcreatedtext' => 'ܐܬܒܪܝ ܚܘܫܒܢܐ ܕܡܦܠܚܢܐ ܠ $1.',
 'createaccount-title' => 'ܒܪܝܐ ܕܚܘܫܒܢܐ ܒ {{SITENAME}}',
@@ -638,6 +639,7 @@ $1',
 'searchrelated' => 'ܐܚܝܢܝ̈ܐ',
 'searchall' => 'ܟܠ',
 'showingresults' => "ܚܘܘܝܐ ܠܬܚܬ {{PLURAL:$1|'''1''' ܦܠܛܐ|'''$1''' ܦܠܛ̈ܐ}} ܫܪܐ ܡܢ ܡܢܝܢܐ '''$2'''.",
+'showingresultsnum' => "ܚܘܘܝܐ ܠܬܚܬ {{PLURAL:$3|'''ܚܕ ܦܠܛܐ'''|'''$3''' ܦܠܛ̈ܐ}} ܫܪܐ ܡܢ ܡܢܝܢܐ '''$2'''.",
 'showingresultsheader' => "{{PLURAL:$5|ܦܠܛܐ '''$1''' ܡܢ '''$3'''|ܦܠܛ̈ܐ '''$1 - $2''' ܡܢ '''$3'''}} ܠ'''$4'''",
 'search-nonefound' => 'ܠܝܬ ܦܠܛ̈ܐ ܐܘܝܢ̈ܐ ܠܗܢܐ ܒܨܝܐ.',
 'powersearch' => 'ܒܨܝܐ ܡܬܩܕܡܢܐ',
@@ -698,7 +700,7 @@ $1',
 'prefs-namespaces' => 'ܚܩܠܬ̈ܐ',
 'defaultns' => 'ܐܘ ܒܨܝ ܒܚܩܠܬ̈ܐ ܗܢܝܢ',
 'prefs-files' => 'ܠܦܦ̈ܐ',
-'prefs-emailconfirm-label' => 'Ü\9aÜ\98ܬܬܐ ܕܒܝܠܕܪܐ ܐܠܩܛܪܘܢܝܐ:',
+'prefs-emailconfirm-label' => 'Ü«Ü\98ܪܪܐ ܕܒܝܠܕܪܐ ܐܠܩܛܪܘܢܝܐ:',
 'youremail' => 'ܒܝܠܕܪܐ ܐܠܩܛܪܘܢܝܐ:',
 'username' => 'ܫܡܐ ܕܡܦܠܚܢܐ:',
 'uid' => 'ܗܝܝܘܬܐ ܕܡܦܠܚܢܐ:',
@@ -713,6 +715,8 @@ $1',
 'gender-unknown' => 'ܠܐ ܦܣܝܩܐ',
 'gender-male' => 'ܕܟܪܐ',
 'gender-female' => 'ܢܩܒܐ',
+'prefs-help-gender' => 'ܨܒܝܢܝܐ: ܐܬܦܠܚ ܠܡܬܡܠܠ ܒܓܢܣܐ ܬܪܝܨܐ ܒܝܕ ܬܚܪܙܬܐ.
+ܝܕܥܬܐ ܗܕܐ ܬܗܘܐ ܓܠܝܬܐ ܠܥܠܡܐ.',
 'email' => 'ܒܝܠܕܪܐ ܐܠܩܛܪܘܢܝܐ',
 'prefs-info' => 'ܝܕ̈ܥܬܐ ܪ̈ܫܝܬܐ',
 'prefs-i18n' => 'ܬܘܪܓܡܐ',
@@ -741,6 +745,7 @@ $1',
 # Groups
 'group' => 'ܟܢܘܫܬܐ:',
 'group-user' => 'ܡܦܠܚܢ̈ܐ',
+'group-autoconfirmed' => 'ܡܦܠܚܢ̈ܐ ܡܫܪܪ̈ܐ ܝܬܐܝܬ',
 'group-bot' => 'ܒܘܬ̈ܐ',
 'group-sysop' => 'ܡܕܒܪ̈ܢܐ',
 'group-bureaucrat' => 'ܒܝܪܘܩܪ̈ܛܐ',
@@ -748,14 +753,14 @@ $1',
 'group-all' => '(ܟܠ)',
 
 'group-user-member' => '{{GENDER:$1|ܡܦܠܚܢܐ|ܡܦܠܚܢܬܐ}}',
-'group-autoconfirmed-member' => '{{GENDER:$1|ܡܦܠÜ\9aÜ¢Ü\90 Ü\9aܬÜ\9dܬÜ\90 Ü\9dܬÜ\90Ü\9dܬ|ܡܦܠÜ\9aܢܬÜ\90 Ü\9aܬÜ\9dܬܐ ܝܬܐܝܬ}}',
+'group-autoconfirmed-member' => '{{GENDER:$1|ܡܦܠÜ\9aÜ¢Ü\90 Ü¡Ü«ÜªÜªÜ\90 Ü\9dܬÜ\90Ü\9dܬ|ܡܦܠÜ\9aܢܬÜ\90 Ü¡Ü«ÜªÜªܬܐ ܝܬܐܝܬ}}',
 'group-bot-member' => '{{GENDER:$1|ܒܘܬ (Bot)}}',
 'group-sysop-member' => '{{GENDER:$1|ܡܕܒܪܢܐ|ܡܕܒܪܢܬܐ}}',
 'group-bureaucrat-member' => '{{GENDER:$1|ܒܝܪܘܩܪܛܐ}}',
 'group-suppress-member' => '{{GENDER:$1|ܚܝܘܪܐ|ܚܝܘܪܬܐ}}',
 
 'grouppage-user' => '{{ns:project}}:ܡܦܠܚܢ̈ܐ',
-'grouppage-autoconfirmed' => '{{ns:project}}:ܡܦܠÜ\9aÜ¢Ì\88Ü\90 Ü\9aܬÜ\9dܬ̈ܐ ܝܬܐܝܬ',
+'grouppage-autoconfirmed' => '{{ns:project}}:ܡܦܠÜ\9aÜ¢Ì\88Ü\90 Ü¡Ü«ÜªÜª̈ܐ ܝܬܐܝܬ',
 'grouppage-bot' => '{{ns:project}}:ܒܘܬ̈ܐ',
 'grouppage-sysop' => '{{ns:project}}:ܡܕܒܪ̈ܢܐ',
 'grouppage-bureaucrat' => '{{ns:project}}:ܒܝܪܘܩܪ̈ܛܐ',
@@ -1179,7 +1184,7 @@ $1',
 
 # Delete
 'deletepage' => 'ܫܘܦ ܦܐܬܐ',
-'confirm' => 'Ü\9aܬܬ',
+'confirm' => 'ܫܪܪ',
 'excontent' => "ܚܒܝܫܬ̈ܐ ܗܘ̈ܝ: '$1'",
 'excontentauthor' => "ܚܒܝܫܬ̈ܐ ܗܘ̈ܝ: '$1' (ܘܫܘܬܦܢܐ ܝܚܝܕܝܐ ܗܘܐ '[[Special:Contributions/$2|$2]]')",
 'exblank' => 'ܦܐܬܐ ܣܦܝܩܬܐ ܗܘܐ',
@@ -1208,7 +1213,7 @@ $1',
 'unprotectedarticle' => 'ܫܩܘܠ ܢܛܝܪܘܬܐ ܡܢ "[[$1]]"',
 'movedarticleprotection' => 'ܫܢܐ ܛܘܝܒ̈ܐ ܕܢܛܪܐ ܡܢ "[[$2]]" ܠ "[[$1]]"',
 'prot_1movedto2' => '[[$1]] ܐܬܫܢܝܬ ܠ [[$2]]',
-'protect-legend' => 'Ü\9aܬܬ ܢܘܛܪܐ',
+'protect-legend' => 'ܫܪܪ ܢܘܛܪܐ',
 'protectcomment' => 'ܥܠܬܐ:',
 'protect-default' => 'ܦܣܣܐ ܠܟܠ ܡܦܠܚܢ̈ܐ',
 'protect-fallback' => 'ܒܥܝ "$1" ܦܣܣܐ',
@@ -1560,10 +1565,10 @@ $1',
 'limitall' => 'ܟܠ',
 
 # E-mail address confirmation
-'confirmemail' => 'Ü\9aܬܬ ܒܝܠܕܪܐ ܐܠܩܛܪܘܢܝܐ',
-'confirmemail_subject' => 'Ü\9aÜ\98ܬܬܐ ܕܒܝܠܕܪܐ ܐܠܩܛܪܘܢܝܐ ܡܢ {{SITENAME}}',
-'confirmemail_invalidated' => 'Ü\9aÜ\98ܬܬÜ\90 Ü\95Ü\92Ü\9dÜ Ü\95ܪÜ\90 Ü\90Ü Ü©Ü\9bܪÜ\98Ü¢Ü\9dÜ\90 ܒܛܠ',
-'invalidateemail' => 'Ü\92Ü\9bÜ\98Ü  Ü\9aÜ\98ܬܬܐ ܕܒܝܠܕܪܐ ܐܠܩܛܪܘܢܝܐ',
+'confirmemail' => 'ܫܪܪ ܒܝܠܕܪܐ ܐܠܩܛܪܘܢܝܐ',
+'confirmemail_subject' => 'Ü«Ü\98ܪܪܐ ܕܒܝܠܕܪܐ ܐܠܩܛܪܘܢܝܐ ܡܢ {{SITENAME}}',
+'confirmemail_invalidated' => 'Ü«Ü\98ܪܪÜ\90 Ü\95Ü\92Ü\9dÜ Ü\95ܪÜ\90 Ü\90Ü Ü©Ü\9bܪÜ\98Ü¢Ü\9dÜ\90 Ü\90ܬܒܛܠ',
+'invalidateemail' => 'Ü\92Ü\9bÜ\98Ü  Ü«Ü\98ܪܪܐ ܕܒܝܠܕܪܐ ܐܠܩܛܪܘܢܝܐ',
 
 # Delete conflict
 'recreate' => 'ܒܪܝ ܙܒܢܬܐ ܐܚܪܬܐ',
index 67b3830..d10ed89 100644 (file)
@@ -418,7 +418,7 @@ ossabab lli ĝtah hwwa "\'\'$2\'\'".',
 # Login and logout pages
 'logouttext' => "''' nta daba kharj.'''
 
-ila bghiti tqdr tstamr tstaml {{SITENAME}}  kamjhol , olla ila bghiti [[Special:UserLogin|tdkhl aawtani]] bnafs smiya ola bsmiya khra.
+ila bghiti tqdr tstamr tstaml {{SITENAME}}  kamjhol , olla ila bghiti <span class='plainlinks'>[$1 tdkhl aawtani]</span> bnafs smiya ola bsmiya khra.
 
 tqdr tchof baad sfahi bhal ila msjl hta tfrgh lcache dyalk",
 'welcomecreation' => '== mrhba bik, $1! ==
index 81c4dd1..b438695 100644 (file)
@@ -682,7 +682,7 @@ $2',
 # Login and logout pages
 'logouttext' => "'''أنت دلوقتى مش مسجل دخولك.'''
 
-تقدر تكمل استعمال {{SITENAME}} على انك مجهول، أو [[Special:UserLogin|الدخول مرة تانيه]] بنفس الاسم أو باسم تاني.
+تقدر تكمل استعمال {{SITENAME}} على انك مجهول، أو <span class='plainlinks'>[$1 الدخول مرة تانيه]</span> بنفس الاسم أو باسم تاني.
 ممكن تشوف بعض الصفحات  كأنك متسجل ، و دا علشان استعمال الصفحات المتخبية فى المتصفح بتاعك.",
 'welcomecreation' => '== اهلاً و سهلا يا $1! ==
 اتفتحلك حساب.
index cd1e503..7245f38 100644 (file)
@@ -446,6 +446,8 @@ $1',
 'youhavenewmessages' => 'আপোনাৰ কাৰণে $1 আছে। ($2)',
 'newmessageslink' => 'নতুন সংবাদ',
 'newmessagesdifflink' => 'শেহতীয়া সাল-সলনি',
+'newmessageslinkplural' => '{{PLURAL:$1|এটা নতুন বাৰ্তা|নতুন বাৰ্তা}}',
+'newmessagesdifflinkplural' => 'অন্তিম {{PLURAL:$1|সংশোধন}}',
 'youhavenewmessagesmulti' => '$1ত আপোনাৰ কাৰণে নতুন সংবাদ আছে',
 'editsection' => 'সম্পাদনা কৰক',
 'editold' => 'সম্পাদনা',
@@ -582,7 +584,7 @@ $2',
 # Login and logout pages
 'logouttext' => "'''আপুনি প্ৰস্থান কৰিলে ।'''
 
-আপুনি বেনামী ভাবেও {{SITENAME}} ব্যৱহাৰ কৰিব পাৰে, অথবা আকৌ সেই একে বা বেলেগ নামেৰে [[Special:UserLogin|প্ৰৱেশ]] কৰিব পাৰে।
+আপুনি বেনামী ভাবেও {{SITENAME}} ব্যৱহাৰ কৰিব পাৰে, অথবা আকৌ সেই একে বা বেলেগ নামেৰে <span class='plainlinks'>[$1 প্ৰৱেশ]</span> কৰিব পাৰে।
 মন কৰিব যে যেতিয়ালৈকে আপোনাৰ ব্ৰাউজাৰৰ অস্থায়ী-স্মৃতি (cache memory) খালী নকৰে, তেতিয়ালৈকে কিছুমান পৃষ্ঠাত আপুনি প্ৰৱেশ কৰা বুলি দেখুৱাই থাকিব পাৰে।",
 'welcomecreation' => '== আদৰিছোঁ, $1! ==
 আপোনাৰ সদস্যভুক্তি হৈ গ’ল ।
@@ -1321,7 +1323,7 @@ $1",
 'yourlanguage' => 'ভাষা:',
 'yourvariant' => 'বিষয়-বস্তুৰ ভাষা বিকল্প',
 'prefs-help-variant' => 'এই ৱিকিৰ সমল পৃষ্ঠাসমূহ প্ৰদৰ্শন কৰিবলে আপোনাৰ পছন্দৰ অপৰ অথবা বৰ্ণবিন্যাস।',
-'yournick' => 'নতà§\81ন à¦¸à§\8dবাà¦\95à§\8dষà§\8dযৰ:',
+'yournick' => 'নতুন স্বাক্ষৰ:',
 'prefs-help-signature' => 'কথা-বতৰা পৃষ্ঠাত মন্তব্যসমূহৰ তলত "<nowiki>~~~~</nowiki>" লিখিলে ই স্বয়ংক্ৰিয়ভাৱে আপোনাৰ নাম আৰু সময় সংযুক্ত কৰিব ।',
 'badsig' => 'অনুপযোগী স্বাক্ষ্যৰ, HTML টেগ পৰীক্ষা কৰি লওক।',
 'badsiglength' => 'আপোনাৰ স্বাক্ষৰ অত্যাধিক দীঘলীয়া ।
@@ -1996,6 +1998,7 @@ https://www.mediawiki.org/wiki/Manual:Image_Authorization চাওক।",
 'mostlinkedtemplates' => 'সৰ্বোচ্চ সংযোজিত সাঁচসমূহ',
 'mostcategories' => 'সৰ্বোচ্চ শ্ৰেণীসমৃদ্ধ প্ৰবন্ধসমূহ',
 'mostimages' => 'সৰ্বোচ্চ সংযোজিত ফাইলসমূহ',
+'mostinterwikis' => 'সৰ্বোচ্চ আন্তঃৱিকিসমৃদ্ধ পৃষ্ঠাসমূহ',
 'mostrevisions' => 'অধিকবাৰ সম্পাদনা কৰা পৃষ্ঠাসমূহ',
 'prefixindex' => 'উপসৰ্গসহ সকলো পৃষ্ঠা',
 'prefixindex-namespace' => 'উপসৰ্গ ($1 namespace) -ৰ সৈতে সকলো পৃষ্ঠা',
@@ -2141,6 +2144,8 @@ https://www.mediawiki.org/wiki/Manual:Image_Authorization চাওক।",
 'mailnologin' => 'পাওঁতাৰ ঠিকনা নাই',
 'mailnologintext' => 'আন সদস্যক ই-মেইল পঠিয়াবলৈ আপুনি [[Special:UserLogin|লগ্‌ ইন]] কৰিব লাগিব আৰু আপোনাৰ [[Special:Preferences|পছন্দসমূহত]] এটা বৈধ ই-মেইল ঠিকনা থাকিব লাগিব ।',
 'emailuser' => 'এই সদস্যজনলৈ ই-মেইল পঠিয়াওক',
+'emailuser-title-target' => '{{GENDER:$1|সদস্যজনক}} ইমেইল পঠিয়াওক',
+'emailuser-title-notarget' => 'ব্যৱহাৰকাৰী ই-পত্ৰ প্ৰেৰণ কৰক',
 'emailpage' => 'ই-পত্ৰ ব্যৱহাৰকাৰী',
 'emailpagetext' => 'তলৰ প্ৰপত্ৰখন ব্যৱহাৰ কৰি আপুনি এই সদস্যজনলৈ ই-মেইল পঠাব পাৰে ।
 আপুনি [[Special:Preferences|আপোনাৰ সদস্য পছন্দসমূহ]]ত প্ৰৱেশ কৰা ই-মেইল ঠিকনাটো প্ৰেৰকৰ ঠিকনা হিছাপে দেখা যাব, যাতে মেইলৰ প্ৰাপকে আপোনাক উত্তৰ দিব পাৰে ।',
@@ -2287,6 +2292,7 @@ $UNWATCHURL
 'rollback' => 'সম্পাদনা পূৰ্ববৎ কৰক',
 'rollback_short' => 'পূৰ্ববৎ কৰক',
 'rollbacklink' => 'পূৰ্ববৎ কৰক',
+'rollbacklinkcount' => '$1 {{PLURAL:$1|সম্পাদনা|সম্পাদনাসমূহ}} পূৰ্বৱত কৰক',
 'rollbackfailed' => 'পূৰ্ববৎ ব্যৰ্থ',
 'cantrollback' => 'পূৰ্বৰ অৱস্থালৈ ঘূৰাই নিব নোৱাৰি;
 শেষৰ সম্পাদকজন এই পৃষ্ঠাৰ একমাত্ৰ লেখক ।',
index d28e395..17b5379 100644 (file)
@@ -468,7 +468,7 @@ L'alministrador que lu bloquió dio esti motivu: «$3».",
 # Login and logout pages
 'logouttext' => "'''Agora tas desconeutáu.'''
 
-Pues siguir usando {{SITENAME}} de forma anónima, o pues [[Special:UserLogin|volver entrar]] como'l mesmu o como otru usuariu.
+Pues siguir usando {{SITENAME}} de forma anónima, o pues <span class='plainlinks'>[$1 volver entrar]</span> como'l mesmu o como otru usuariu.
 Ten en cuenta que dalgunes páxines puen siguir apaeciendo como si tovía tuvieres coneutáu, hasta que llimpies la caché del restolador.",
 'welcomecreation' => "== ¡Bienllegáu, $1! ==
 Creóse la to cuenta.
@@ -2915,7 +2915,6 @@ Probablemente tea causao por un enllaz a un sitiu esternu de la llista prieta.',
 'pageinfo-authors' => "Númberu total d'autores distintos",
 'pageinfo-recent-edits' => "Númberu d'ediciones recientes (nos caberos $1)",
 'pageinfo-recent-authors' => "Númberu d'autores distintos recientes",
-'pageinfo-restriction' => 'Proteición de la páxina ({{lcfirst:$1}})',
 'pageinfo-magic-words' => '{{PLURAL:$1|Pallabra máxica|Pallabres máxiques}} ($1)',
 'pageinfo-hidden-categories' => '{{PLURAL:$1|Categoría anubrida|Categoríes anubríes}} ($1)',
 'pageinfo-templates' => '{{PLURAL:$1|Plantía incluída|Plantíes incluíes}} ($1)',
index a27b38b..aee486f 100644 (file)
@@ -473,7 +473,7 @@ Göstərilən səbəb: "\'\'$2\'\'".',
 # Login and logout pages
 'logouttext' => "'''Sistemdən çıxdınız.'''
 
-Siz {{SITENAME}} saytını anonim olaraq istifadə etməyə davam edə bilər və ya eyni, yaxud başqa istifadəçi adı ilə [[Special:UserLogin|yenidən daxil ola]] bilərsiniz. Veb-brauzerin keş yaddaşını təmizləyənədək bəzi səhifələr hələ də sistemdə imişsiniz kimi görünə bilər.",
+Siz {{SITENAME}} saytını anonim olaraq istifadə etməyə davam edə bilər və ya eyni, yaxud başqa istifadəçi adı ilə <span class='plainlinks'>[$1 yenidən daxil ola]</span> bilərsiniz. Veb-brauzerin keş yaddaşını təmizləyənədək bəzi səhifələr hələ də sistemdə imişsiniz kimi görünə bilər.",
 'welcomecreation' => '== $1, xoş gəlmişsiniz! ==
 Hesabınız yaradıldı.
 [[Special:Preferences|{{SITENAME}} nizamlamalarınızı]] dəyişdirməyi unutmayın.',
index 7789270..043e6ed 100644 (file)
@@ -473,7 +473,7 @@ $2',
 # Login and logout pages
 'logouttext' => "'''Һеҙ иҫәп яҙыуығыҙҙан сыҡтығыҙ.'''
 
-Һеҙ {{SITENAME}} проектында аноним рәүештә дауам итә йәки [[Special:UserLogin|яңынан таныла]] алаһығыҙ (үҙ йәки башҡа исем менән).
+Һеҙ {{SITENAME}} проектында аноним рәүештә дауам итә йәки <span class='plainlinks'>[$1 яңынан таныла]</span> алаһығыҙ (үҙ йәки башҡа исем менән).
 Ҡайһы бер биттәр һеҙ системала танылған һымаҡ күренергә мөмкин, уны бөтөрөү өсөн браузер кэшын таҙартығыҙ.",
 'welcomecreation' => '== Рәхим итегеҙ, $1! ==
 Иҫәп яҙыуығыҙ яһалды.
index 2a6808f..7aff876 100644 (file)
@@ -413,7 +413,7 @@ $2",
 # Login and logout pages
 'logouttext' => "'''Iatzerd bist obgmödt.'''
 
-Du kåst {{SITENAME}} iatzerd anónym weiderdoah, óder di danaid unterm söwing óder am åndern Benutzernåm [[Special:UserLogin|åmöden]].
+Du kåst {{SITENAME}} iatzerd anónym weiderdoah, óder di danaid unterm söwing óder am åndern Benutzernåm <span class='plainlinks'>[$1 åmöden]</span>.
 Beochtt ower, daas oanige Seiten noh åzoang kennern, daas du ågmödt bist, sólång du néd deih Browsercache glaard host.",
 'welcomecreation' => '== Servas, $1! ==
 
index f6d2692..22d7628 100644 (file)
@@ -506,7 +506,7 @@ An administrador na iyo an nagkandado kaini nagpahayag kaining kapaliwanagan: "$
 # Login and logout pages
 'logouttext' => "'''Ika po sa ngunyan nakaluwas na.'''
 
-Ika makakadagos pa sa paggamit kan {{SITENAME}} na dai nagpapabisto, o ika [[Special:UserLogin|Maglaog giraray]] bilang pareho o bilang ibang paragamit.
+Ika makakadagos pa sa paggamit kan {{SITENAME}} na dai nagpapabisto, o ika <span class='plainlinks'>[$1 Maglaog giraray]</span> bilang pareho o bilang ibang paragamit.
 Giromdoma na an ibang mga pahina mapuwedeng padagos na magpapahiling siring baga na kun ika garo yaon man sana sa laog, sagkod na saimong malinigan mo an sarayan sa kilyawan.",
 'welcomecreation' => '== Maogmang Pag-abot, $1! ==
 An saimong panindog (account) naimukna na tabi.
index 8702c76..6e6e678 100644 (file)
@@ -481,7 +481,7 @@ $2',
 # Login and logout pages
 'logouttext' => "'''Вы выйшлі з сістэмы.'''
 
-Можна працягваць працу на {{SITENAME}} ананімна, або можна [[Special:UserLogin|ўвайсці ў сістэму ізноў]], пад тым самым або пад іншым удзельніцкім імем. Заўважце, што некаторыя старонкі могуць паказвацца так, быццам вы яшчэ не выйшлі; у такім разе трэба ачысціць кэш вашага браўзера.",
+Можна працягваць працу на {{SITENAME}} ананімна, або можна <span class='plainlinks'>[$1 ўвайсці ў сістэму ізноў]</span>, пад тым самым або пад іншым удзельніцкім імем. Заўважце, што некаторыя старонкі могуць паказвацца так, быццам вы яшчэ не выйшлі; у такім разе трэба ачысціць кэш вашага браўзера.",
 'welcomecreation' => '== Вітаем, $1! == Ваш  рахунак быў створаны. Не забудзьцеся дапасаваць свае [[Special:Preferences|{{SITENAME}} настáўленні]].',
 'yourname' => 'Імя ўдзельніка',
 'yourpassword' => 'Пароль',
index b7ecf35..51dd768 100644 (file)
@@ -662,7 +662,7 @@ $2',
 # Login and logout pages
 'logouttext' => "'''Вы выйшлі з сыстэмы.'''
 
-Вы можаце працягваць працу ў {{GRAMMAR:месны|{{SITENAME}}}} ананімна, альбо можаце [[Special:UserLogin|ўвайсьці ў сыстэму]] як той жа альбо іншы ўдзельнік.
+Вы можаце працягваць працу ў {{GRAMMAR:месны|{{SITENAME}}}} ананімна, альбо можаце <span class='plainlinks'>[$1 ўвайсьці ў сыстэму]</span> як той жа альбо іншы ўдзельнік.
 Некаторыя старонкі могуць паказвацца, быццам Вы ўсё яшчэ ў сыстэме. Каб гэтага пазьбегнуць, трэба ачысьціць кэш браўзэра.",
 'welcomecreation' => '== Вітаем, $1! ==
 Ваш рахунак быў створаны.
@@ -3033,10 +3033,10 @@ $1',
 'pageinfo-authors' => 'Колькасьць аўтараў',
 'pageinfo-recent-edits' => 'Колькасьць апошніх рэдагаваньняў (за $1)',
 'pageinfo-recent-authors' => 'Колькасьць апошніх аўтараў',
-'pageinfo-restriction' => 'Стан аховы ({{lcfirst:$1}})',
 'pageinfo-magic-words' => '{{PLURAL:$1|Магічнае слова|Магічныя словы}} ($1)',
 'pageinfo-hidden-categories' => '{{PLURAL:$1|Схаваная катэгорыя|Схаваныя катэгорыі}} ($1)',
 'pageinfo-templates' => '{{PLURAL:$1|Шаблён|Шаблёны}} ($1)',
+'pageinfo-toolboxlink' => 'Зьвесткі пра старонку',
 
 # Skin names
 'skinname-standard' => 'Клясычнае',
@@ -3613,6 +3613,7 @@ $5
 # Scary transclusion
 'scarytranscludedisabled' => '[Улучэньне інтэрвікі было адключанае]',
 'scarytranscludefailed' => '[Памылка атрыманьня шаблёну $1]',
+'scarytranscludefailed-httpstatus' => '[Памылка атрыманьня шаблёну $1: HTTP $2]',
 'scarytranscludetoolong' => '[Занадта даўгі URL-адрас]',
 
 # Delete conflict
index b783329..a61b28c 100644 (file)
@@ -635,7 +635,7 @@ $2',
 # Login and logout pages
 'logouttext' => "'''Излязохте от системата.'''
 
-Можете да продължите да използвате {{SITENAME}} анонимно или да [[Special:UserLogin|влезете отново]] като друг потребител.
+Можете да продължите да използвате {{SITENAME}} анонимно или да <span class='plainlinks'>[$1 влезете отново]</span> като друг потребител.
 Обърнете внимание, че някои страници все още ще се показват така, сякаш сте влезли, докато не изтриете кеш-паметта на браузъра.",
 'welcomecreation' => '== Добре дошли, $1! ==
 
index b2bdbba..6c1a2bc 100644 (file)
@@ -540,7 +540,7 @@ Administrator nang takunci nintu manawarakan panjalasan: "$3".',
 # Login and logout pages
 'logouttext' => "'''Pian parhatan ni sudah kaluar log.'''
 
-Pian kawa manyambung hagan mangguna'akan {{SITENAME}} kada bangaran, atawa Pian kawa [[Special:UserLogin|babuat log pulang]] sawagai pamakai nang sama atawa sawagai pamakai balain.
+Pian kawa manyambung hagan mangguna'akan {{SITENAME}} kada bangaran, atawa Pian kawa <span class='plainlinks'>[$1 babuat log pulang]</span> sawagai pamakai nang sama atawa sawagai pamakai balain.
 Catatan bahwasa babarapa tungkaran pinanya masih ha tarus manampaiakan Pian masih babuat log, sampai Pian mahabisakan timbuluk panjalajah web Pian.",
 'welcomecreation' => '==Salamat datang, $1!==
 Akun Pian sudah diulah.
index 154529d..c44c9ea 100644 (file)
@@ -475,7 +475,7 @@ $2',
 # Login and logout pages
 'logouttext' => "'''আপনি এইমাত্র আপনার একাউন্ট থেকে প্রস্থান করেছেন।'''
 
-এ পরিস্থিতিতে আপনি বেনামে {{SITENAME}} ব্যবহার করতে পারেন, কিংবা একই বা পৃথক নামে [[Special:UserLogin|আবার প্রবেশ করতে]] পারেন।
+এ পরিস্থিতিতে আপনি বেনামে {{SITENAME}} ব্যবহার করতে পারেন, কিংবা একই বা পৃথক নামে <span class='plainlinks'>[$1 আবার প্রবেশ করতে]</span> পারেন।
 লক্ষ্য করুন যে, এর কোন কোন পাতা এখনও এমনভাবে দেখাতে পারে যাতে মনে হবে আপনি আগের অবস্থাতেই আছেন। এক্ষেত্রে আপনাকে আপনার ব্রাওজারের ক্যাশ পরিষ্কার (clear browser cache) করে নিতে হবে।",
 'welcomecreation' => '== স্বাগতম $1! ==
 আপনার অ্যাকাউন্ট তৈরী হয়েছে।
@@ -2793,7 +2793,6 @@ $1',
 'pageinfo-authors' => 'সর্বমোট সতন্ত্র সম্পাদকের সংখ্যা',
 'pageinfo-recent-edits' => 'সর্বশেষ সম্পাদনা করা হয়েছে (শেষ $1 দিনে)',
 'pageinfo-recent-authors' => 'সাম্প্রতিক সতন্ত্র সম্পাদকের সংখ্যা',
-'pageinfo-restriction' => 'পাতার সুরক্ষা ({{lcfirst:$1}})',
 'pageinfo-magic-words' => 'ম্যাজিক {{PLURAL:$1|শব্দ|শব্দসমূহ}} ($1)',
 'pageinfo-hidden-categories' => 'লুকানো {{PLURAL:$1|বিষয়শ্রেণী|বিষয়শ্রেণীসমূহ}} ($1)',
 'pageinfo-templates' => 'সংযুক্ত {{PLURAL:$1|টেমপ্লেট|টেমপ্লেটসমূহ}} ($1)',
index 215fc9e..1cfff22 100644 (file)
@@ -434,7 +434,7 @@ $2',
 # Login and logout pages
 'logouttext' => "'''তি খানি আগে তর একাউন্টহাত্ত নিকুরিসত।'''
 
-এ পরিস্থিতিত তি বেনাঙল {{SITENAME}} ব্যবহার করানি পারর, নাইলে আরাক নাঙল [[Special:UserLogin|বারো হমানি]] পারর।
+এ পরিস্থিতিত তি বেনাঙল {{SITENAME}} ব্যবহার করানি পারর, নাইলে আরাক নাঙল <span class='plainlinks'>[$1 বারো হমানি]</span> পারর।
 খিয়াল থ, কোন কোন পাতা তি আগর অংতাত আসত বুলিয়া দেখা দিতে পারে। অসারে ইলে তি ব্রাওজারর ক্যাশ সেঙকরে বেলা (clear browser cache)।",
 'welcomecreation' => '==সম্ভাষা, $1! ==
 তর একাউন্টহান হঙিল। তর [[Special:Preferences|{{SITENAME}} পছনহান]] সিলানি না পাহুরিস।',
index 2c60dc9..bffea87 100644 (file)
@@ -583,7 +583,7 @@ Setu amañ perak ''$2''.",
 # Login and logout pages
 'logouttext' => "'''Digevreet oc'h bremañ.'''
 
-Gallout a rit kenderc'hel da implijout {{SITENAME}} en un doare dizanv, pe [[Special:UserLogin|kevreañ en-dro]] gant an hevelep anv pe un anv all mar fell deoc'h.
+Gallout a rit kenderc'hel da implijout {{SITENAME}} en un doare dizanv, pe <span class='plainlinks'>[$1 kevreañ en-dro]</span> gant an hevelep anv pe un anv all mar fell deoc'h.
 Notit mat e c'hallo pajennoù zo kenderc'hel da vezañ diskwelet evel pa vefec'h kevreet c'hoazh, betek ma vo riñset krubuilh ho merdeer ganeoc'h.",
 'welcomecreation' => '== Degemer mat, $1! ==
 
@@ -2969,7 +2969,6 @@ Sur a-walc'h abalamour d'ul liamm enni a gas d'ul lec'hienn ziavaez berzet.",
 'pageinfo-authors' => 'Niver a aozerien disheñvel',
 'pageinfo-recent-edits' => 'Niver a gemmoù nevez (er $1 diwezhañ)',
 'pageinfo-recent-authors' => "Niver a aozerien nevez a-ziforc'h",
-'pageinfo-restriction' => 'Gwareziñ ar bajenn ({{lcfirst:$1}})',
 'pageinfo-magic-words' => '{{PLURAL:$1|Ger hud |Gerioù hud}} ($1)',
 'pageinfo-hidden-categories' => '{{PLURAL:$1|Rumm kuzh|Rummoù kuzh}} ($1)',
 'pageinfo-templates' => "{{PLURAL:$1|Patrom endalc'het|Patromoù endalc'het}} ($1)",
index ce20c93..3db6342 100644 (file)
@@ -676,7 +676,7 @@ Iz razloga "\'\'$2\'\'".',
 # Login and logout pages
 'logouttext' => "'''Sad ste odjavljeni.'''
 
-Možete nastaviti da koristite {{SITENAME}} anonimno, ili se ponovo [[Special:UserLogin|prijaviti]] kao isti ili kao drugi korisnik.
+Možete nastaviti da koristite {{SITENAME}} anonimno, ili se ponovo <span class='plainlinks'>[$1 prijaviti]</span> kao isti ili kao drugi korisnik.
 Obratite pažnju da neke stranice mogu nastaviti da se prikazuju kao da ste još uvijek prijavljeni, dok ne očistite keš svog preglednika.",
 'welcomecreation' => '== Dobro došli, $1 ==
 Vaš nalog je napravljen.
index ca92ed1..16b256e 100644 (file)
@@ -599,7 +599,7 @@ L\'administrador que l\'ha bloquejat ha donat aquesta explicació: "$3".',
 # Login and logout pages
 'logouttext' => "'''Heu finalitzat la vostra sessió.'''
 
-Podeu continuar utilitzant {{SITENAME}} de forma anònima, o podeu [[Special:UserLogin|iniciar una sessió una altra vegada]] amb el mateix o un altre usuari.
+Podeu continuar utilitzant {{SITENAME}} de forma anònima, o podeu <span class='plainlinks'>[$1 iniciar una sessió una altra vegada]</span> amb el mateix o un altre usuari.
 Tingueu en compte que algunes pàgines poden continuar mostrant-se com si encara estiguéssiu en una sessió, fins que buideu la memòria cau del vostre navegador.",
 'welcomecreation' => "== Us donem la benvinguda, $1! ==
 
index 1761147..62aaf29 100644 (file)
@@ -443,7 +443,7 @@ Ang rason nga gihatag mao ang "\'\'$2\'\'".',
 # Login and logout pages
 'logouttext' => "'''Nakabiya ka na.'''
 
-Mahimo kang magpadayon sa paggamit sa {{SITENAME}} bisan wala ka magpaila o puyde usab nga [[Special:UserLogin|mag-log in ka'g usab]] o isip laing gumagamit. Palihog hinumdomi nga may ubang mga panid nga magpakita sama nga ikaw naka-log in pa; kini tungod kay wala pa nimo malimpiyohi ang cache sa imong brawser.",
+Mahimo kang magpadayon sa paggamit sa {{SITENAME}} bisan wala ka magpaila o puyde usab nga <span class='plainlinks'>[$1 mag-log in ka'g usab]</span> o isip laing gumagamit. Palihog hinumdomi nga may ubang mga panid nga magpakita sama nga ikaw naka-log in pa; kini tungod kay wala pa nimo malimpiyohi ang cache sa imong brawser.",
 'welcomecreation' => '== Maayong pag-abot, $1! ==
 Nahimo na ang imong akawnt.
 Ayaw kalimot sa pag-usab sa imong [[Special:Preferences|{{SITENAME}} mga preperensiya]].',
index 70a58c7..ee80d56 100644 (file)
@@ -546,7 +546,7 @@ $2',
 # Login and logout pages
 'logouttext' => "'''ئێستا تۆ لە ھەژمارەکەت ھاتوویتە دەرەوە.'''
 
-دەتوانی بە شێوەی بێناو درێژە بدەی بە بەرکارھێنانی {{SITENAME}}، یان دەتوانی [[Special:UserLogin|دیسانەوە بچیتەوە ژوورەوە]] ھەر بەو ناوە یان بە ناوی بەکارھێنەرییەکی جیاوازەوە.
+دەتوانی بە شێوەی بێناو درێژە بدەی بە بەرکارھێنانی {{SITENAME}}، یان دەتوانی <span class='plainlinks'>[$1 دیسانەوە بچیتەوە ژوورەوە]</span> ھەر بەو ناوە یان بە ناوی بەکارھێنەرییەکی جیاوازەوە.
 ئاگادار بە کە ھەتا کاتێک کە کەشی وێبگەڕەکەت دەسڕیتەوە، سەرەڕای چوونەدەرەوەی تۆ ھەندێک لە پەڕەکان ھەر بە شێوەیەک نیشان دەدرێن کە گوایە تۆ ھێشتا لە ژوورەوەیت.",
 'welcomecreation' => '== بەخێربێی، $1! ==
 ھەژمارەکەت دروست کرا.
index bb267b0..8f063a5 100644 (file)
@@ -408,7 +408,7 @@ Mini ang ginhatag nga kabangdanan "\'\'$2\'\'".',
 # Login and logout pages
 'logouttext' => "'''Nakagwa ka na.'''
 
-Pwede mo mapadayon usar ang {{SITENAME}}, ukon pwede ka [[Special:UserLogin|lmagsulod liwat]] bilang pareho ukon la-in nga manug-usar.
+Pwede mo mapadayon usar ang {{SITENAME}}, ukon pwede ka <span class='plainlinks'>[$1 lmagsulod liwat]</span> bilang pareho ukon la-in nga manug-usar.
 Tandaan nga ang iban nga pahina magapadayon nga ipakita nga nakasulod ka man gyapon kuno abi, asta panason mo ang cache sang imo browser.",
 'welcomecreation' => '==Malipayon nga pag-abot, $1! ==
 Nahimo na ang imo account.
index eda3c60..0c3f017 100644 (file)
@@ -491,7 +491,7 @@ $2',
 # Login and logout pages
 'logouttext' => "'''Отурымны къапаттынъыз.'''
 
-Шимди {{SITENAME}} сайтыны аноним оларакъ къулланып оласынъыз, я да янъыдан [[Special:UserLogin|отурым ачып]] оласынъыз (истер айны къулланыджы адынен, истер башкъа бир къулланыджы адынен). Web браузеринъиз кэшини темизлегендже базы саифелер санки аля даа отурымынъыз ачыкъ экен киби корюнип олур.",
+Шимди {{SITENAME}} сайтыны аноним оларакъ къулланып оласынъыз, я да янъыдан <span class='plainlinks'>[$1 отурым ачып]</span> оласынъыз (истер айны къулланыджы адынен, истер башкъа бир къулланыджы адынен). Web браузеринъиз кэшини темизлегендже базы саифелер санки аля даа отурымынъыз ачыкъ экен киби корюнип олур.",
 'welcomecreation' => '== Хош кельдинъиз, $1! ==
 Эсабынъыз ачылды.
 Бу сайтнынъ [[Special:Preferences|сазламаларыны]] шахсынъызгъа коре денъиштирмеге унутманъыз.',
index 32c14cf..95bf434 100644 (file)
@@ -487,7 +487,7 @@ Sebep: ''$2''.",
 # Login and logout pages
 'logouttext' => "'''Oturımnı qapattıñız.'''
 
-Şimdi {{SITENAME}} saytını anonim olaraq qullanıp olasıñız, ya da yañıdan [[Special:UserLogin|oturım açıp]] olasıñız (ister aynı qullanıcı adınen, ister başqa bir qullanıcı adınen). Web brauzeriñiz keşini temizlegence bazı saifeler sanki alâ daa oturımıñız açıq eken kibi körünip olur.",
+Şimdi {{SITENAME}} saytını anonim olaraq qullanıp olasıñız, ya da yañıdan <span class='plainlinks'>[$1 oturım açıp]</span> olasıñız (ister aynı qullanıcı adınen, ister başqa bir qullanıcı adınen). Web brauzeriñiz keşini temizlegence bazı saifeler sanki alâ daa oturımıñız açıq eken kibi körünip olur.",
 'welcomecreation' => '== Hoş keldiñiz, $1! ==
 Esabıñız açıldı.
 Bu saytnıñ [[Special:Preferences|sazlamalarını]] şahsıñızğa köre deñiştirmege unutmañız.',
index 5e03078..ce8cf6e 100644 (file)
@@ -764,7 +764,7 @@ Správce serveru, který úložiště zamkl, poskytl toto zdůvodnění: „''$3
 # Login and logout pages
 'logouttext' => "'''Nyní jste odhlášeni.'''
 
-Můžete pokračovat v anonymním prohlížení a editaci {{grammar:2sg|{{SITENAME}}}}, nebo se můžete [[Special:UserLogin|znovu přihlásit]] jako stejný či jiný uživatel.
+Můžete pokračovat v anonymním prohlížení a editaci {{grammar:2sg|{{SITENAME}}}}, nebo se můžete <span class='plainlinks'>[$1 znovu přihlásit]</span> jako stejný či jiný uživatel.
 Uvědomte si, že některé stránky se mohou i nadále zobrazovat, jako byste byli dosud přihlášeni, pokud nevymažete cache prohlížeče.",
 'welcomecreation' => '== Vítejte, $1! ==
 Váš účet byl úspěšně vytvořen.
@@ -3133,10 +3133,10 @@ Uložte jej na svůj disk a nahrajte ho sem.',
 'pageinfo-authors' => 'Celkový počet různých autorů',
 'pageinfo-recent-edits' => 'Počet nedávných ($1) editací',
 'pageinfo-recent-authors' => 'Nedávný počet různých autorů',
-'pageinfo-restriction' => 'Zámek stránky ({{lcfirst:$1}})',
 'pageinfo-magic-words' => '{{PLURAL:$1|Kouzelné slovo|Kouzelná slova}} ($1)',
 'pageinfo-hidden-categories' => '{{PLURAL:$1|Skrytá|Skryté}} kategorie ($1)',
 'pageinfo-templates' => '{{PLURAL:$1|Použitá šablona|Použité šablony}} ($1)',
+'pageinfo-toolboxlink' => 'Informace o stránce',
 
 # Skin names
 'skinname-standard' => 'Klasický',
@@ -3714,6 +3714,7 @@ Platnost tohoto potvrzovacího kódu vyprší $4.',
 # Scary transclusion
 'scarytranscludedisabled' => '[Vkládání šablon mezi wiki je vypnuto]',
 'scarytranscludefailed' => '[Nepodařilo se načíst šablonu pro $1]',
+'scarytranscludefailed-httpstatus' => '[Nepodařilo se načíst šablonu pro $1: HTTP $2]',
 'scarytranscludetoolong' => '[Příliš dlouhé URL]',
 
 # Delete conflict
index 5681adc..fc7eb7e 100644 (file)
@@ -354,7 +354,7 @@ Przemëszlë dolmaczënié na [//translatewiki.net/wiki/Main_Page?setlang=csb tr
 
 # Login and logout pages
 'logouttext' => "'''Jes wëlogòwóny.'''
-Mòżesz robic dali na {{SITENAME}} jakno anonimòwi brëkòwnik abò sã [[Special:UserLogin|wlogòwac]] znowa jakno równy, a bò jinszi brëkòwnik.
+Mòżesz robic dali na {{SITENAME}} jakno anonimòwi brëkòwnik abò sã <span class='plainlinks'>[$1 wlogòwac]</span> znowa jakno równy, a bò jinszi brëkòwnik.
 Bôczë, że do czasu wëczëszczenia pòdrãczny pamiãcë przezérnika, niejedné starnë bãdą wëzdrzëc jakbë të bëł wlogòwóny.",
 'welcomecreation' => ' == Witôj, $1! ==
 Twòjé kònto òstało prawie ùsôdzoné.
index 7ab05cf..21b5c61 100644 (file)
@@ -522,7 +522,7 @@ Y rheswm a roddwyd gan y gweinyddwr a roddodd y ffeil dan glo yw "\'\'$3\'\'".',
 # Login and logout pages
 'logouttext' => "'''Rydych wedi allgofnodi.'''
 
-Gallwch ddefnyddio {{SITENAME}} yn anhysbys, neu fe allwch [[Special:UserLogin|fewngofnodi eto]] wrth yr un un enw neu wrth enw arall.
+Gallwch ddefnyddio {{SITENAME}} yn anhysbys, neu fe allwch <span class='plainlinks'>[$1 fewngofnodi eto]</span> wrth yr un un enw neu wrth enw arall.
 Sylwer y bydd rhai tudalennau yn parhau i ymddangos fel ag yr oeddent pan oeddech wedi mewngofnodi hyd nes i chi glirio celc eich porwr.",
 'welcomecreation' => "==Croeso, $1!==
 Mae eich cyfrif wedi'i greu.
index 057831c..0022d3b 100644 (file)
@@ -345,7 +345,7 @@ $messages = array(
 'vector-action-protect' => 'Beskyt',
 'vector-action-undelete' => 'Gendan',
 'vector-action-unprotect' => 'Ændr beskyttelse',
-'vector-simplesearch-preference' => 'Aktivér forbedrede søgeforslag (kun Vector-udseendet)',
+'vector-simplesearch-preference' => 'Aktivér forenklet søgefelt (kun Vector-udseendet)',
 'vector-view-create' => 'Opret',
 'vector-view-edit' => 'Redigér',
 'vector-view-history' => 'Se historik',
@@ -593,7 +593,7 @@ Administratoren, som skrivebeskyttede den, gav følgende begrundelse: "$3".',
 # Login and logout pages
 'logouttext' => "'''Du er nu logget af.'''
 
-Du kan fortsætte med at bruge {{SITENAME}} anonymt, eller du kan [[Special:UserLogin|logge på igen]] som den samme eller en anden bruger.
+Du kan fortsætte med at bruge {{SITENAME}} anonymt, eller du kan <span class='plainlinks'>[$1 logge på igen]</span> som den samme eller en anden bruger.
 Bemærk, at nogle sider stadigvæk kan vises som om du var logget på, indtil du tømmer din browsers cache.",
 'welcomecreation' => '== Velkommen, $1! ==
 
@@ -2927,10 +2927,10 @@ Dette skyldes sandsynligvis en henvisning til et sortlistet eksternt websted.',
 'pageinfo-authors' => 'Det samlede antal forskellige forfattere',
 'pageinfo-recent-edits' => 'Antallet af nylige redigeringer (i løbet af de seneste $1)',
 'pageinfo-recent-authors' => 'Antallet af bidragydere, der har redigeret siden for nyligt',
-'pageinfo-restriction' => 'Sidebeskyttelse ({{lcfirst:$1}})',
 'pageinfo-magic-words' => '{{PLURAL:$1|Magisk|Magiske}} ord ($1)',
 'pageinfo-hidden-categories' => '{{PLURAL:$1|Skjult kategori|Skjulte kategorier}} ($1)',
 'pageinfo-templates' => '{{PLURAL:$1|Transkluderet skabelon|Transkluderede skabeloner}} ($1)',
+'pageinfo-toolboxlink' => 'Oplysninger om siden',
 
 # Skin names
 'skinname-standard' => 'Klassik',
@@ -3503,6 +3503,7 @@ Denne bekræftelseskode vil udløbe den $4.',
 # Scary transclusion
 'scarytranscludedisabled' => '[Interwiki-tilkobling er deaktiveret]',
 'scarytranscludefailed' => '[Hentning af skabelon for $1 mislykkedes]',
+'scarytranscludefailed-httpstatus' => '[Hentning af skabelon for $1 mislykkedes: HTTP $2]',
 'scarytranscludetoolong' => "[URL'en er for lang]",
 
 # Delete conflict
@@ -3775,6 +3776,7 @@ Ellers kan du bruge den enkle formular nedenfor. Din kommentar vil blive tilføj
 'feedback-bugnew' => 'Jeg har kontrolleret. Rapporter en ny fejl.',
 
 # Search suggestions
+'searchsuggest-search' => 'Søg',
 'searchsuggest-containing' => 'indeholder...',
 
 # API errors
index 5d17cea..869b998 100644 (file)
@@ -537,7 +537,7 @@ $messages = array(
 'newwindow' => '(wird in einem neuen Fenster geöffnet)',
 'cancel' => 'Abbrechen',
 'moredotdotdot' => 'Mehr …',
-'mypage' => 'Meine Seite',
+'mypage' => 'Eigene Seite',
 'mytalk' => 'Eigene Diskussion',
 'anontalk' => 'Diskussionsseite dieser IP',
 'navigation' => 'Navigation',
@@ -650,8 +650,8 @@ $1',
 'mainpage' => 'Hauptseite',
 'mainpage-description' => 'Hauptseite',
 'policy-url' => 'Project:Richtlinien',
-'portal' => 'Gemeinschafts-Portal',
-'portal-url' => 'Project:Gemeinschafts-Portal',
+'portal' => 'Gemeinschaftsportal',
+'portal-url' => 'Project:Gemeinschaftsportal',
 'privacy' => 'Datenschutz',
 'privacypage' => 'Project:Datenschutz',
 
@@ -812,7 +812,7 @@ Der Administrator, der den Schreibzugriff sperrte, gab folgenden Grund an: „$3
 # Login and logout pages
 'logouttext' => "'''Du bist nun abgemeldet.'''
 
-Du kannst {{SITENAME}} jetzt anonym weiternutzen oder dich erneut unter dem selben oder einem anderen Benutzernamen [[Special:UserLogin|anmelden]].
+Du kannst {{SITENAME}} jetzt anonym weiternutzen oder dich erneut unter dem selben oder einem anderen Benutzernamen <span class='plainlinks'>[$1 anmelden]</span>.
 Beachte, dass einige Seiten noch anzeigen können, dass du angemeldet bist, solange du nicht deinen Browsercache geleert hast.",
 'welcomecreation' => '== Willkommen, $1! ==
 
@@ -1400,7 +1400,7 @@ Einzelheiten sind im [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}}
 'searchhelp-url' => 'Help:Hilfe',
 'searchmenu-prefix' => '[[Special:PrefixIndex/$1|Zeige alle Seiten, die mit dem Suchbegriff anfangen]]',
 'searchprofile-articles' => 'Inhaltsseiten',
-'searchprofile-project' => 'Hilfe und Projektseiten',
+'searchprofile-project' => 'Hilfe- und Projektseiten',
 'searchprofile-images' => 'Multimedia',
 'searchprofile-everything' => 'Alles',
 'searchprofile-advanced' => 'Erweitert',
@@ -2602,7 +2602,8 @@ Der aktuelle Text der gelöschten Seite ist nur Administratoren zugänglich.',
 'undeletedrevisions' => '{{PLURAL:$1|1 Version wurde|$1 Versionen wurden}} wiederhergestellt',
 'undeletedrevisions-files' => '{{PLURAL:$1|1 Version|$1 Versionen}} und {{PLURAL:$2|1 Datei|$2 Dateien}} wurden wiederhergestellt',
 'undeletedfiles' => '{{PLURAL:$1|1 Datei wurde|$1 Dateien wurden}} wiederhergestellt',
-'cannotundelete' => 'Wiederherstellung fehlgeschlagen; jemand anderes hat die Seite bereits wiederhergestellt.',
+'cannotundelete' => 'Wiederherstellung fehlgeschlagen:
+$1',
 'undeletedpage' => "'''„$1“''' wurde wiederhergestellt.
 
 Im [[Special:Log/delete|Lösch-Logbuch]] findest du eine Übersicht der gelöschten und wiederhergestellten Seiten.",
@@ -3041,7 +3042,7 @@ Diese auf dem lokalen Rechner speichern und danach hier hochladen.',
 'tooltip-pt-anontalk' => 'Diskussion über Änderungen von dieser IP-Adresse',
 'tooltip-pt-preferences' => 'Eigene Einstellungen',
 'tooltip-pt-watchlist' => 'Liste der beobachteten Seiten',
-'tooltip-pt-mycontris' => 'Liste deiner Beiträge',
+'tooltip-pt-mycontris' => 'Liste eigener Beiträge',
 'tooltip-pt-login' => 'Sich anzumelden wird zwar gerne gesehen, ist aber keine Pflicht.',
 'tooltip-pt-anonlogin' => 'Sich anzumelden wird zwar gerne gesehen, ist aber keine Pflicht.',
 'tooltip-pt-logout' => 'Abmelden',
@@ -3066,7 +3067,7 @@ Diese auf dem lokalen Rechner speichern und danach hier hochladen.',
 'tooltip-n-portal' => 'Über das Projekt, was du tun kannst, wo was zu finden ist',
 'tooltip-n-currentevents' => 'Hintergrundinformationen zu aktuellen Ereignissen',
 'tooltip-n-recentchanges' => 'Liste der letzten Änderungen in {{SITENAME}}',
-'tooltip-n-randompage' => 'Zufällige Seite',
+'tooltip-n-randompage' => 'Zufällige Seite aufrufen',
 'tooltip-n-help' => 'Hilfeseite anzeigen',
 'tooltip-t-whatlinkshere' => 'Liste aller Seiten, die hierher verlinken',
 'tooltip-t-recentchangeslinked' => 'Letzte Änderungen an Seiten, die von hier verlinkt sind',
@@ -3190,10 +3191,10 @@ Das liegt wahrscheinlich an einem Link auf eine externe Seite.',
 'pageinfo-authors' => 'Gesamtzahl unterschiedlicher Autoren',
 'pageinfo-recent-edits' => 'Anzahl der kürzlich erfolgten Bearbeitungen (innerhalb von $1)',
 'pageinfo-recent-authors' => 'Anzahl der unterschiedlichen Autoren',
-'pageinfo-restriction' => 'Seitenschutz ({{lcfirst:$1}})',
 'pageinfo-magic-words' => '{{PLURAL:$1|Magisches Wort|Magische Wörter}} ($1)',
 'pageinfo-hidden-categories' => 'Versteckte {{PLURAL:$1|Kategorie|Kategorien}} ($1)',
 'pageinfo-templates' => 'Eingebundene {{PLURAL:$1|Vorlage|Vorlagen}} ($1)',
+'pageinfo-toolboxlink' => 'Informationen zur Seite',
 
 # Skin names
 'skinname-standard' => 'Klassik',
@@ -3775,6 +3776,7 @@ Dieser Bestätigungscode ist gültig bis $4.',
 # Scary transclusion
 'scarytranscludedisabled' => '[Interwiki-Einbindung ist deaktiviert]',
 'scarytranscludefailed' => '[Vorlageneinbindung für $1 ist gescheitert]',
+'scarytranscludefailed-httpstatus' => '[Vorlagenabruf fehlgeschlagen für $1: HTTP  $2]',
 'scarytranscludetoolong' => '[URL ist zu lang]',
 
 # Delete conflict
index ca4cb5f..d945252 100644 (file)
@@ -14,6 +14,7 @@
  * @author George Animal
  * @author Kaganer
  * @author Mirzali
+ * @author Olvörg
  * @author Reedy
  * @author Sahim
  * @author Xoser
@@ -722,7 +723,7 @@ Xızmetkarê  kılitkerdışi wa bewni ro enay wa çımra ravyarno: "$3".',
 # Login and logout pages
 'logouttext' => "'''Şıma hesab qefelna.'''
 
-Nıka kamiyê xo eşkera mekere u siteyê {{SITENAME}} ra eşkeni devam bıkeri, ya zi [[Special:UserLogin|newe ra hesabê xo akere]] (wazeni pey nameyê xo, wazeni pey yewna name).
+Nıka kamiyê xo eşkera mekere u siteyê {{SITENAME}} ra eşkeni devam bıkeri, ya zi <span class='plainlinks'>[$1 newe ra hesabê xo akere]</span> (wazeni pey nameyê xo, wazeni pey yewna name).
 Wexta ke verhafızayê cıgerayoxê şıma pak beno no benate de taye peli de hesabe şıma akerde aseno.",
 'welcomecreation' => '== Şıma xeyr amey, $1! ==
 
@@ -901,15 +902,15 @@ Parola vêrdiye: $2',
 'hr_tip' => 'Çıxiza dimdayi (hend akar mefiye)',
 
 # Edit pages
-'summary' => 'Xulasa:',
+'summary' => 'Xelese:',
 'subject' => 'Mewzu/serrêze:',
-'minoredit' => 'Eno yew vurnayışo qıckeko',
-'watchthis' => 'Ena perer temase ke',
+'minoredit' => 'Vurnayışo qıcek',
+'watchthis' => 'Ena perer teqib ke',
 'savearticle' => 'Pele qeyd ke',
 'preview' => 'Verqayt',
 'showpreview' => 'Verqayti bımocne',
 'showlivepreview' => 'Verqayto cıwın',
-'showdiff' => 'Vurnayışan bımocne',
+'showdiff' => 'Vurnayışa bıvin',
 'anoneditwarning' => 'Teme!: Şıma bı hesabê xo nıkewtê cı. Hurêndiya namey şıma dı IP-adresa şıma qeyd bena u asena.',
 'anonpreviewwarning' => "''Ti hama nicikewte. Qeyd kerdiş zerre tarixê pele de adresê IP yê tu keyd keno.''",
 'missingsummary' => "'''DİQET:''' Şıma kılmnuşte nıkerd.
@@ -1368,7 +1369,7 @@ Pe verbendi ''all:'', vaceyê xo bıvurni ki contenti hemi cıgeyro (pelanê mı
 'prefsnologintext' => 'Şıma gani be <span class="plainlinks">[{{fullurl:{{#Special:UserLogin}}|returnto=$1}} cikewte]</span> ke tercihanê karberi xo eyar bıkerê.',
 'changepassword' => 'Parola bıvurne',
 'prefs-skin' => 'Çerme',
-'skin-preview' => 'Verqayt',
+'skin-preview' => 'Verasayış',
 'datedefault' => 'Tercih çıniyo',
 'prefs-beta' => 'Xacetê beta',
 'prefs-datetime' => 'Tarix u wext',
@@ -2103,7 +2104,7 @@ gıreyê her satıri de gıreyi; raş motışê yewın u dıyıni esto.
 'lonelypagestext' => 'Ena pelî link nibiyê ya zi pelanê binî {{SITENAME}} de transclude biy.',
 'uncategorizedpages' => 'Pelayanê ke kategorî nibiye',
 'uncategorizedcategories' => 'Kategoriyê ke bê kategorîyê',
-'uncategorizedimages' => 'Dosyayê ke bê kategorîyê',
+'uncategorizedimages' => 'Dosyayê ke bê kategoriyê',
 'uncategorizedtemplates' => 'Şablonê ke bê kategoriyê',
 'unusedcategories' => 'Kategoriyê ke nê xebtênê',
 'unusedimages' => 'Dosyeyê ke nê xebtênê',
@@ -2872,7 +2873,7 @@ ma vaci: qey pelê "[[{{MediaWiki:Mainpage}}]]i " [[{{#Special:Export}}/{{MediaW
 'allmessages' => 'Mesacê sistemi',
 'allmessagesname' => 'Name',
 'allmessagesdefault' => 'Hesıbyaye metnê mesaci',
-'allmessagescurrent' => 'nuşte yo ke Karyayo',
+'allmessagescurrent' => 'Metno ke karyayo',
 'allmessagestext' => 'na liste, listeya mesajê cayê nameyê wikimedya yo.
 eke şıma qayili paşt bıdi mahalli kerdışê wikimedyayi, kerem kerê pelê [//www.mediawiki.org/wiki/Localisation mahalli kerdışê wikimedyayi] u [//translatewiki.net translatewiki.net] ziyaret bıkerê.',
 'allmessagesnotsupportedDB' => "'''\$wgUseDatabaseMessages''' qefelnaye yo u ey ra '''{{ns:special}}:Allmessages''' karkerdışi re akerde niyo.",
@@ -3593,6 +3594,7 @@ mw.loader.using( 'jquery.cookie', function() {
 'pageinfo-views' => 'Amarina mocnayışan',
 'pageinfo-watchers' => 'Amariya pela serykeran',
 'pageinfo-redirects-name' => 'Hetenayışê na pela',
+'pageinfo-redirects-value' => '$1',
 'pageinfo-subpages-name' => 'Bınpelê na pela',
 'pageinfo-subpages-value' => '$1 ($2 {{PLURAL:$2|hetenayış|hetenayışi}}; $3 {{PLURAL:$3|raykerdışt|raykerdışi}})',
 'pageinfo-firstuser' => 'Pela vıraşter',
@@ -3603,10 +3605,10 @@ mw.loader.using( 'jquery.cookie', function() {
 'pageinfo-authors' => 'Amarina nuştekaran pêro',
 'pageinfo-recent-edits' => 'Amariya vurnayışan ($1 ra nata)',
 'pageinfo-recent-authors' => 'Amarina nuştekaran pêro',
-'pageinfo-restriction' => 'Xısusiyetê pela ({{lcfirst:$1}})',
 'pageinfo-magic-words' => '{{PLURAL:$1|Çekuya|Çekuyê}} ($1) sihırini',
 'pageinfo-hidden-categories' => '{{PLURAL:$1|Kategoriye|Kategoriyan}} ($1) bınımne',
 'pageinfo-templates' => '{{PLURAL:$1|Şablon|Şabloni}} ($1) açarneyayê',
+'pageinfo-toolboxlink' => 'Malumatê perer',
 
 # Skin names
 'skinname-standard' => 'Klasik',
@@ -4293,6 +4295,7 @@ kodê tesdiqi heta ıney tarixi $4 meqbul o.',
 # Scary transclusion
 'scarytranscludedisabled' => '[Transcludê înterwîkîyî nihebityeno]',
 'scarytranscludefailed' => '[Qe $1 fetch kerdişî nihebitiyeno]',
+'scarytranscludefailed-httpstatus' => '[Qande $1 şablon nêşa bıgêriyo: HTTP $2]',
 'scarytranscludetoolong' => '[Ena URL zaf dergo]',
 
 # Delete conflict
index 846bf98..bf2393c 100644 (file)
@@ -562,7 +562,7 @@ Administrator, kenž jo jen zastajił, jo toś tu pśicynu pódał: "$3".',
 # Login and logout pages
 'logouttext' => "'''Sy se něnto wótzjawił.'''
 
-Móžoš {{SITENAME}} anomymnje dalej wužywaś abo móžoš [[Special:UserLogin|se znowego pśizjawiś]] ako samski abo hynakšy wužywaŕ.
+Móžoš {{SITENAME}} anomymnje dalej wužywaś abo móžoš <span class='plainlinks'>[$1 se znowego pśizjawiś]</span> ako samski abo hynakšy wužywaŕ.
 Źiwaj na to, až někotare boki se dalej tak zwobraznjuju ako by hyšći pśizjawjeny był, až njewuproznijoš cache swójego wobglědowaka.",
 'welcomecreation' => '== Witaj, $1! ==
 
@@ -2875,10 +2875,10 @@ W zespominanju dajo se pśicyna pódaś.',
 'pageinfo-authors' => 'Cełkowna licba wšakich awtorow',
 'pageinfo-recent-edits' => 'Licba nejnowšych změnow (za zachadnych $1)',
 'pageinfo-recent-authors' => 'Nejnowša licba rozdźělnych awtorow',
-'pageinfo-restriction' => 'Šćit boka ({{lcfirst:$1}})',
 'pageinfo-magic-words' => '{{PLURAL:$1|Magiske słowo|Magiskej słowje|Magiske słowa|Magiske słowa}} ($1)',
 'pageinfo-hidden-categories' => '{{PLURAL:$1|Schowana kategorija|Schowanej kategoriji|Schowane kategorije|Schowane kategorije}} ($1)',
 'pageinfo-templates' => '{{PLURAL:$1|Zapśěgnjona pśedłoga|Zapśěgnjonej pśedłoze|Zapśěgnjone pśedłogi|Zapśěgnjone pśedłogi}} ($1)',
+'pageinfo-toolboxlink' => 'Informacije wó boku',
 
 # Skin names
 'skinname-standard' => 'Klasiski',
@@ -3447,6 +3447,7 @@ Toś ten wobkšuśeński kod płaśi až do $4.',
 # Scary transclusion
 'scarytranscludedisabled' => '[Pśidawanje interwiki jo deaktiwěrowane]',
 'scarytranscludefailed' => '[Zapśěgnjenje pśedłogi za $1 njejo se raźiło]',
+'scarytranscludefailed-httpstatus' => '[Wótwołanje pśedłogi za $1 jo se njeraźiło: HTTP $2]',
 'scarytranscludetoolong' => '[URL jo pśedłujki]',
 
 # Delete conflict
index 7f9b705..c184f3d 100644 (file)
@@ -425,7 +425,7 @@ Mongungulud di minongunsi pinopointalang do kointalangan diti: "$3".',
 # Login and logout pages
 'logouttext' => "'''Baino nakalabus log ko noh.'''
 
-Milo ko do monilombus mongoguno {{SITENAME}} poinlisok, toi [[Special:UserLogin|sumuang log koh kawagu]] miagal ngaran di tiinu toi mongoguno ngaran suai.
+Milo ko do monilombus mongoguno {{SITENAME}} poinlisok, toi <span class='plainlinks'>[$1 sumuang log koh kawagu]</span> miagal ngaran di tiinu toi mongoguno ngaran suai.
 Birio do kipipiro bolikon popokito do maso poinsuang log koh poh gisom no do opugas nu dangkob do pogigihumnu.",
 'welcomecreation' => '== Kopiwosian, $1! ==
 Nowonsoi no takaunnu.
index 305bb4e..e5858a9 100644 (file)
@@ -751,7 +751,7 @@ $2',
 # Login and logout pages
 'logouttext' => "'''Έχετε αποσυνδεθεί.'''
 
-Μπορείτε να παραμείνετε στο {{SITENAME}} ανώνυμα, ή μπορείτε [[Special:UserLogin|να συνδεθείτε ξανά]] με το ίδιο ή με διαφορετικό (εάν έχετε) όνομα χρήστη.
+Μπορείτε να παραμείνετε στο {{SITENAME}} ανώνυμα, ή μπορείτε <span class='plainlinks'>[$1 να συνδεθείτε ξανά]</span> με το ίδιο ή με διαφορετικό (εάν έχετε) όνομα χρήστη.
 Έχετε υπόψη σας πως αρκετές σελίδες θα συνεχίσουν να εμφανίζονται κανονικά, σαν να μην έχετε αποσυνδεθεί, μέχρι να καθαρίσετε τη λανθάνουσα μνήμη του φυλλομετρητή σας.",
 'welcomecreation' => '== Καλώς ήλθατε, $1! ==
 Ο λογαριασμός σας έχει δημιουργηθεί.
index 8971421..70c86bc 100644 (file)
@@ -893,6 +893,7 @@ $1',
 'portal-url'           => 'Project:Community portal',
 'privacy'              => 'Privacy policy',
 'privacypage'          => 'Project:Privacy policy',
+'content-failed-to-parse' => "Failed to parse $2 content for $1 model: $3",
 
 'badaccess'        => 'Permission error',
 'badaccess-group0' => 'You are not allowed to execute the action you have requested.',
@@ -1065,7 +1066,7 @@ The administrator who locked it offered this explanation: "$3".',
 # Login and logout pages
 'logouttext'                 => "'''You are now logged out.'''
 
-You can continue to use {{SITENAME}} anonymously, or you can [[Special:UserLogin|log in again]] as the same or as a different user.
+You can continue to use {{SITENAME}} anonymously, or you can <span class='plainlinks'>[$1 log in again]</span> as the same or as a different user.
 Note that some pages may continue to be displayed as if you were still logged in, until you clear your browser cache.",
 'welcomecreation'            => '== Welcome, $1! ==
 Your account has been created.
@@ -1427,7 +1428,7 @@ If you save it, any changes made since this revision will be lost.",
 'yourdiff'                         => 'Differences',
 'copyrightwarning'                 => "Please note that all contributions to {{SITENAME}} are considered to be released under the $2 (see $1 for details).
 If you do not want your writing to be edited mercilessly and redistributed at will, then do not submit it here.<br />
-You are also promising us that you wrote this yourself, or copied it from a public domain or similar free resource.
+You are also promising us that you wrote this yourself, or copied editpageit from a public domain or similar free resource.
 '''Do not submit copyrighted work without permission!'''",
 'copyrightwarning2'                => "Please note that all contributions to {{SITENAME}} may be edited, altered, or removed by other contributors.
 If you do not want your writing to be edited mercilessly, then do not submit it here.<br />
@@ -1484,6 +1485,8 @@ It already exists.',
 'addsection-preload'               => '', # do not translate or duplicate this message to other languages
 'addsection-editintro'             => '', # do not translate or duplicate this message to other languages
 'defaultmessagetext'               => 'Default message text',
+'invalid-content-data'             => 'Invalid content data',
+'content-not-allowed-here'         => '"$1" content is not allowed on page [[$2]]',
 
 # Parser/template warnings
 'expensive-parserfunction-warning'        => "'''Warning:''' This page contains too many expensive parser function calls.
@@ -3065,8 +3068,8 @@ You may have a bad link, or the revision may have been restored or removed from
 'undeletedrevisions'           => '{{PLURAL:$1|1 revision|$1 revisions}} restored',
 'undeletedrevisions-files'     => '{{PLURAL:$1|1 revision|$1 revisions}} and {{PLURAL:$2|1 file|$2 files}} restored',
 'undeletedfiles'               => '{{PLURAL:$1|1 file|$1 files}} restored',
-'cannotundelete'               => 'Undelete failed;
-someone else may have undeleted the page first.',
+'cannotundelete'               => 'Undelete failed:
+$1',
 'undeletedpage'                => "'''$1 has been restored'''
 
 Consult the [[Special:Log/delete|deletion log]] for a record of recent deletions and restorations.",
@@ -3388,6 +3391,7 @@ cannot move a page over itself.',
 'immobile-target-namespace-iw' => 'Interwiki link is not a valid target for page move.',
 'immobile-source-page'         => 'This page is not movable.',
 'immobile-target-page'         => 'Cannot move to that destination title.',
+'bad-target-model'             => 'The desired destination uses a different content model. Can not convert from $1 to $2.',
 'imagenocrossnamespace'        => 'Cannot move file to non-file namespace',
 'nonfile-cannot-move-to-file'  => 'Cannot move non-file to file namespace',
 'imagetypemismatch'            => 'The new file extension does not match its type',
@@ -4943,4 +4947,10 @@ Otherwise, you can use the easy form below. Your comment will be added to the pa
 'duration-centuries' => '$1 {{PLURAL:$1|century|centuries}}',
 'duration-millennia' => '$1 {{PLURAL:$1|millennium|millennia}}',
 
+# Content model IDs for the ContentHandler facility; used by ContentHandler::getContentModel()
+'content-model-wikitext' => 'wikitext',
+'content-model-javascript' => 'JavaScript',
+'content-model-css' => 'CSS',
+'content-model-text' => 'plain text',
+
 );
index cf393eb..8045cf3 100644 (file)
@@ -722,7 +722,7 @@ La administranto kiu ŝlosis ĝin proponis tiun klarigon: "$3".',
 # Login and logout pages
 'logouttext' => "'''Vi nun estas elsalutita.'''
 
-Vi rajtas daŭre vikiumi sennome, aŭ vi povas [[Special:UserLogin|reensaluti]] kiel la sama aŭ kiel alia uzanto.
+Vi rajtas daŭre vikiumi sennome, aŭ vi povas <span class='plainlinks'>[$1 reensaluti]</span> kiel la sama aŭ kiel alia uzanto.
 Notu ke iuj paĝoj daŭre ŝajnos kvazaŭ vi ankoraŭ estas ensalutita, ĝis vi refreŝigu vian retumilan kaŝmemoron.",
 'welcomecreation' => '== Bonvenon, $1! ==
 Via konto estas kreita.
index fe5287c..29fe179 100644 (file)
@@ -765,7 +765,7 @@ El administrador que lo ha bloqueado ofrece esta explicación: "$3".',
 # Login and logout pages
 'logouttext' => "'''Ha terminado su sesión.'''
 
-Puedes continuar usando {{SITENAME}} de forma anónima, o puedes [[Special:UserLogin|iniciar sesión otra vez]] con el mismo u otro usuario.
+Puedes continuar usando {{SITENAME}} de forma anónima, o puedes <span class='plainlinks'>[$1 iniciar sesión otra vez]</span> con el mismo u otro usuario.
 Ten en cuenta que las páginas que tengas abiertas en otras ventanas o pestañas pueden verse como si siguieras identificado hasta que las refresques.",
 'welcomecreation' => '== ¡Bienvenido(a), $1! ==
 
@@ -1297,12 +1297,12 @@ No tiene acceso a él.',
 'revdelete-otherreason' => 'Otra/adicional razón:',
 'revdelete-reasonotherlist' => 'Otra razón',
 'revdelete-edit-reasonlist' => 'Editar razones de borrado',
-'revdelete-offender' => 'Autor de revisión:',
+'revdelete-offender' => 'Autor de la revisión:',
 
 # Suppression log
 'suppressionlog' => 'Registro de supresiones',
 'suppressionlogtext' => 'A continuación hay una lista con los borrados y bloqueos cuyo contenido se encuentra oculto para los administradores.
-Ver la [[Special:BlockList|lista de bloqueos]] que incluye las prohibiciones y bloqueos actualmente operativos.',
+Véase la [[Special:BlockList|lista de bloqueos]] que incluye las prohibiciones y bloqueos actualmente operativos.',
 
 # History merging
 'mergehistory' => 'Fusionar historiales de páginas',
@@ -3176,10 +3176,10 @@ Esto podría estar causado por un enlace a un sitio externo incluido en la lista
 'pageinfo-authors' => 'Número total de autores distintos',
 'pageinfo-recent-edits' => 'Número de ediciones recientes (en los últimos $1)',
 'pageinfo-recent-authors' => 'Número de autores distintos recientes',
-'pageinfo-restriction' => 'Protección de la página ({{lcfirst:$1}})',
 'pageinfo-magic-words' => '{{PLURAL:$1|Palabra mágica|Palabras mágicas}} ($1)',
 'pageinfo-hidden-categories' => '{{PLURAL:$1|Categoría oculta|Categorías ocultas}} ($1)',
 'pageinfo-templates' => '{{PLURAL:$1|plantilla incluida|plantillas incluidas}} ($1)',
+'pageinfo-toolboxlink' => 'Información de la página',
 
 # Skin names
 'skinname-standard' => 'Estándar',
@@ -3759,6 +3759,7 @@ Este código de confirmación caducará el $4.',
 # Scary transclusion
 'scarytranscludedisabled' => '[Transclusión interwiki está deshabilitada]',
 'scarytranscludefailed' => '[Obtención de plantilla falló para $1]',
+'scarytranscludefailed-httpstatus' => '[Error de recuperación de plantilla para $1: HTTP $2]',
 'scarytranscludetoolong' => '[El URL es demasiado largo]',
 
 # Delete conflict
@@ -4040,7 +4041,7 @@ En otro caso, puedes usar el siguiente formulario. Tu comentario será añadido
 
 # Search suggestions
 'searchsuggest-search' => 'Buscar',
-'searchsuggest-containing' => 'conteniendo...',
+'searchsuggest-containing' => 'que contiene...',
 
 # API errors
 'api-error-badaccess-groups' => 'No puedes cargar archivos en este wiki.',
index c94de43..9be5c96 100644 (file)
@@ -447,7 +447,7 @@ $messages = array(
 'vector-action-protect' => 'Kaitse',
 'vector-action-undelete' => 'Taasta',
 'vector-action-unprotect' => 'Muuda kaitset',
-'vector-simplesearch-preference' => 'Luba täiustatud otsinguvihjed (ainult Vektori-kujunduses)',
+'vector-simplesearch-preference' => 'Kasuta lihtsustatud otsiriba (ainult Vektori-kujunduses)',
 'vector-view-create' => 'Loo',
 'vector-view-edit' => 'Redigeeri',
 'vector-view-history' => 'Näita ajalugu',
@@ -697,7 +697,7 @@ Administraator lukustas selle järgmisel põhjusel: "$3".',
 # Login and logout pages
 'logouttext' => "'''Oled nüüd välja loginud.'''
 
-Võid jätkata {{GRAMMAR:genitive|{{SITENAME}}}} kasutamist anonüümselt, aga ka sama või mõne teise kasutajana uuesti [[Special:UserLogin|sisse logida]].
+Võid jätkata {{GRAMMAR:genitive|{{SITENAME}}}} kasutamist anonüümselt, aga ka sama või mõne teise kasutajana uuesti <span class='plainlinks'>[$1 sisse logida]</span>.
 Pane tähele, et seni kuni sa pole oma võrgulehitseja puhvrit tühjendanud, võidakse mõni lehekülg endiselt nii kuvada nagu oleksid ikka sisse logitud.",
 'welcomecreation' => '== Tere tulemast, $1! ==
 
@@ -3049,7 +3049,6 @@ See on ilmselt põhjustatud linkimisest mustas nimekirjas olevasse välisvõrguk
 'pageinfo-authors' => 'Erinevate autorite koguarv',
 'pageinfo-recent-edits' => 'Viimaste redigeerimiste arv (viimase $1 jooksul)',
 'pageinfo-recent-authors' => 'Erinevate viimaste toimetajate arv',
-'pageinfo-restriction' => 'Lehekülje kaitse ({{lcfirst:$1}})',
 'pageinfo-magic-words' => '{{PLURAL:$1|Võlusõna|Võlusõnad}} ($1)',
 'pageinfo-hidden-categories' => 'Peidetud {{PLURAL:$1|kategooria|kategooriad}} ($1)',
 'pageinfo-templates' => 'Kasutatud {{PLURAL:$1|mall|mallid}} ($1)',
@@ -3415,8 +3414,8 @@ Kui faili on rakendustarkvaraga töödeldud, võib osa andmeid olla muudetud võ
 
 'exif-filesource-3' => 'Digitaalne fotokaamera',
 
-'exif-customrendered-0' => 'Normaalne protsess',
-'exif-customrendered-1' => 'Kohandatud protsess',
+'exif-customrendered-0' => 'Tavatöötlus',
+'exif-customrendered-1' => 'Kohandatud töötlus',
 
 'exif-exposuremode-0' => 'Automaatne säritus',
 'exif-exposuremode-1' => 'Manuaalne säritus',
@@ -3437,16 +3436,16 @@ Kui faili on rakendustarkvaraga töödeldud, võib osa andmeid olla muudetud võ
 'exif-gaincontrol-4' => 'Vähene',
 
 'exif-contrast-0' => 'Normaalne',
-'exif-contrast-1' => 'Pehme',
-'exif-contrast-2' => 'Kõva',
+'exif-contrast-1' => 'Nõrk',
+'exif-contrast-2' => 'Tugev',
 
 'exif-saturation-0' => 'Normaalne',
 'exif-saturation-1' => 'Madal värviküllastus',
 'exif-saturation-2' => 'Kõrge värviküllastus',
 
 'exif-sharpness-0' => 'Normaalne',
-'exif-sharpness-1' => 'Pehme',
-'exif-sharpness-2' => 'Kõva',
+'exif-sharpness-1' => 'Nõrk',
+'exif-sharpness-2' => 'Tugev',
 
 'exif-subjectdistancerange-0' => 'Teadmata',
 'exif-subjectdistancerange-1' => 'Makro',
index 33614bd..69c3cc6 100644 (file)
@@ -514,7 +514,7 @@ Emandako arrazoia ''$2'' izan zen.",
 # Login and logout pages
 'logouttext' => "'''Saioa itxi egin duzu.'''
 
-Erabiltzaile anonimo bezala jarrai dezakezu {{SITENAME}} erabiltzen, edo [[Special:UserLogin|saioa has dezakezu berriz]] erabiltzaile berdinarekin edo ezberdin batekin.
+Erabiltzaile anonimo bezala jarrai dezakezu {{SITENAME}} erabiltzen, edo <span class='plainlinks'>[$1 saioa has dezakezu berriz]</span> erabiltzaile berdinarekin edo ezberdin batekin.
 Kontuan izan orrialde batzuk saioa hasita bazenu bezala ikus ditzakezula nabigatzailearen katxea garbitu arte.",
 'welcomecreation' => '== Ongi etorri, $1! ==
 
index 8475158..05c119c 100644 (file)
@@ -377,7 +377,7 @@ La razón es la siguienti: ''$2''.",
 
 # Login and logout pages
 'logouttext' => "'''Cuenta afechá corretamenti.'''<br />
-Pueis acontinal gastandu {{SITENAME}} de holma anónima, u [[Special:UserLogin|entral ena tu cuenta]] con el mesmu ussuáriu, u con otru.
+Pueis acontinal gastandu {{SITENAME}} de holma anónima, u <span class='plainlinks'>[$1 entral ena tu cuenta]</span> con el mesmu ussuáriu, u con otru.
 Dati cuenta que hata que nu esborris el caché del tu escrucaol pué paecel que la tu cuenta acontina abierta n'angunas páginas.",
 'welcomecreation' => "== Bienviniu, $1! ==
 
index 4ab6ff7..64bd2d4 100644 (file)
@@ -848,7 +848,7 @@ $2',
 # Login and logout pages
 'logouttext' => "'''هم‌اکنون از سامانه خارج شدید.'''
 
-شما می‌توانید به استفادهٔ گمنام از {{SITENAME}} ادامه دهید، یا با همین حساب کاربری یا حسابی دیگر [[Special:UserLogin|به سامانه وارد شوید]].
+شما می‌توانید به استفادهٔ گمنام از {{SITENAME}} ادامه دهید، یا با همین حساب کاربری یا حسابی دیگر <span class='plainlinks'>[$1 به سامانه وارد شوید]</span>.
 توجه کنید که تا زمانی که میانگیر مرورگرتان را پاک نکنید، بعضی صفحه‌ها ممکن است به گونه‌ای نمایش یابند که گویی هنوز از سامانه خارج نشده‌اید.",
 'welcomecreation' => '==$1، خوش آمدید!==
 حساب شما ایجاد شد.
@@ -1155,7 +1155,7 @@ $2
 
 '''اگر مطمئن هستید که این پیش‌نمایش یک ویرایش مجاز است، آن را تکرار کنید.'''
 اگر تکرار پیش‌نمایش نتیجه نداد، از سامانه [[Special:UserLogout|خارج شوید]] و دوباره وارد شوید.",
-'token_suffix_mismatch' => "'''ویرایش شما ذخیره نشد، زیرا مرورگر شما نویسه‌های نقطه‌گذاری را از هم پاشیده‌است.'''
+'token_suffix_mismatch' => "'''Ù\88Û\8cراÛ\8cØ´ Ø´Ù\85ا Ø°Ø®Û\8cرÙ\87 Ù\86شدØ\8c Ø²Û\8cرا Ù\85رÙ\88رگر Ø´Ù\85ا Ù\86Ù\88Û\8cسÙ\87â\80\8cÙ\87اÛ\8c Ù\86Ù\82Ø·Ù\87â\80\8cگذارÛ\8c Ø±Ø§ Ø¯Ø± Ú©Ø¯ Ø§Ù\85Ù\86Û\8cتÛ\8c Ù\88Û\8cراÛ\8cØ´ Ø§Ø² Ù\87Ù\85 Ù¾Ø§Ø´Û\8cدÙ\87â\80\8cاست.'''
 ویرایش شما مردود شد تا از خراب شدن متن صفحه جلوگیری شود.
 گاهی این اشکال زمانی پیش می‌آید که شما از یک پروکسی تحت وب استفاده کنید.",
 'edit_form_incomplete' => "'''بعضی قسمت‌های فرم ویرایش به سرور نرسیدند؛ اطمینان حاصل کنید که ویرایش‌های شما کامل است و دوباره تلاش کنید.'''",
@@ -3265,7 +3265,6 @@ $1',
 'pageinfo-authors' => 'تعداد کلی نویسندگان یکتا',
 'pageinfo-recent-edits' => 'شماره ویرایش‌های اخیر (در $1 گذشته)',
 'pageinfo-recent-authors' => 'تعداد نویسندگان یکتای اخیر',
-'pageinfo-restriction' => 'محافظت صفحه ( {{lcfirst:$1}} )',
 'pageinfo-magic-words' => '{{PLURAL:$1|حرف|حروف}} جادویی ($1)',
 'pageinfo-hidden-categories' => '{{PLURAL:$1| ردهٔ|ردهٔ}} پنهان ( $1 )',
 'pageinfo-templates' => '{{PLURAL:$1|الگو|الگو}} استفاده‌شده ($1)',
index 0f43d4b..1910d80 100644 (file)
@@ -714,7 +714,7 @@ Lukituksen asettanut ylläpitäjä on antanut seuraavan syyn toimenpiteelle: $3.
 # Login and logout pages
 'logouttext' => "'''Olet nyt kirjautunut ulos.'''
 
-Voit jatkaa {{GRAMMAR:genitive|{{SITENAME}}}} käyttöä nimettömänä, tai [[Special:UserLogin|kirjautua uudelleen sisään]].
+Voit jatkaa {{GRAMMAR:genitive|{{SITENAME}}}} käyttöä nimettömänä, tai <span class='plainlinks'>[$1 kirjautua uudelleen sisään]</span>.
 Huomaa, että jotkut sivut saattavat näkyä edelleen kuin olisit kirjautunut sisään, kunnes tyhjennät selaimen välimuistin.",
 'welcomecreation' => '== Tervetuloa $1! ==
 Käyttäjätunnuksesi on luotu.
@@ -781,7 +781,7 @@ Tästä johtuen tästä IP-osoitteesta ei voi tällä hetkellä luoda uusia tunn
 'noemailprefs' => 'Sähköpostiosoitetta ei ole määritelty.',
 'emailconfirmlink' => 'Varmenna sähköpostiosoite',
 'invalidemailaddress' => 'Sähköpostiosoitetta ei voida hyväksyä, koska se ei ole oikeassa muodossa. Ole hyvä ja anna oikea sähköpostiosoite tai jätä kenttä tyhjäksi.',
-'cannotchangeemail' => 'Tunnuksien sähköpostiosoitteita ei voi muuttaa tässä wikissä.',
+'cannotchangeemail' => 'Tunnusten sähköpostiosoitteita ei voi muuttaa tässä wikissä.',
 'emaildisabled' => 'Tältä sivustolta ei voi lähettää sähköpostia.',
 'accountcreated' => 'Käyttäjätunnus luotiin',
 'accountcreatedtext' => 'Käyttäjän $1 käyttäjätunnus luotiin.',
@@ -3022,13 +3022,13 @@ Tallenna tiedot koneellesi ja tuo ne tällä sivulla.',
 'pageinfo-watchers' => 'Sivun tarkkailijoiden lukumäärä',
 'pageinfo-redirects-name' => 'Sivulle johtavat ohjaukset',
 'pageinfo-subpages-name' => 'Sivun alasivut',
-'pageinfo-firstuser' => 'Sivun tekijä',
+'pageinfo-firstuser' => 'Sivun luonut',
 'pageinfo-lastuser' => 'Viimeisin muokkaaja',
 'pageinfo-edits' => 'Muokkausten kokonaismäärä',
 'pageinfo-authors' => 'Sivun eri muokkaajien kokonaismäärä',
-'pageinfo-restriction' => 'Sivun suojaus ({{lcfirst:$1}})',
 'pageinfo-hidden-categories' => '{{PLURAL:$1|Piilotettu luokka|Piilotetut luokat}} ($1)',
 'pageinfo-templates' => '{{PLURAL:$1|Sisällytetty malline|Sisällytetyt mallineet}} ($1)',
+'pageinfo-toolboxlink' => 'Sivun tiedot',
 
 # Skin names
 'skinname-standard' => 'Perus',
index a7567bd..ba766cc 100644 (file)
@@ -514,7 +514,7 @@ Givin orsøk er "\'\'$2\'\'".',
 
 # Login and logout pages
 'logouttext' => "'''Tú hevur nú ritað út.'''
-Tú kanst halda fram at brúka {{SITENAME}} sum dulnevndur, ella kanst tú [[Special:UserLogin|logga á aftur]] sum sami ella sum annar brúkari. 
+Tú kanst halda fram at brúka {{SITENAME}} sum dulnevndur, ella kanst tú <span class='plainlinks'>[\$1 logga á aftur]</span> sum sami ella sum annar brúkari. 
 Legg til merkis, at summar síður framvegis vera vístar, sum um tú enn vart loggaður á, til tú hevur reinsa tín brovsara fyri \"cache\".",
 'welcomecreation' => '== Vælkomin, $1! ==
 
index 3dd6dd2..0fa9a38 100644 (file)
@@ -780,7 +780,7 @@ L’administrateur qui l’a verrouillé a fourni ce motif: « $3 ».',
 # Login and logout pages
 'logouttext' => "'''Vous êtes à présent déconnecté(e).'''
 
-Vous pouvez continuer à utiliser {{SITENAME}} de façon anonyme, [[Special:UserLogin|vous reconnecter]] sous le même nom ou un autre.
+Vous pouvez continuer à utiliser {{SITENAME}} de façon anonyme, <span class='plainlinks'>[$1 vous reconnecter]</span> sous le même nom ou un autre.
 Notez que certaines pages peuvent être encore affichées comme si vous étiez toujours connecté(e), jusqu’à ce que vous effaciez le cache de votre navigateur.",
 'welcomecreation' => '== Bienvenue, $1 ! ==
 
@@ -2249,7 +2249,7 @@ Vous pouvez personnaliser l’affichage en sélectionnant le type de journal, le
 'allpagesprev' => 'Précédent',
 'allpagesnext' => 'Suivant',
 'allpagessubmit' => 'Lister',
-'allpagesprefix' => 'Afficher les pages commençant par le préfixe :',
+'allpagesprefix' => 'Afficher les pages commençant par :',
 'allpagesbadtitle' => 'Le titre de page indiqué est incorrect : il contient un préfixe inter-langue ou inter-wiki réservé, ou contient un ou plusieurs caractères inutilisables dans les titres.',
 'allpages-bad-ns' => '{{SITENAME}} n’a pas d’espace de noms « $1 ».',
 'allpages-hide-redirects' => 'Masquer les redirections',
@@ -3181,10 +3181,10 @@ Permet de rétablir la version précédente et d’ajouter un motif dans la boî
 'pageinfo-authors' => "Nombre total d'auteurs distincts",
 'pageinfo-recent-edits' => 'Nombre de modifications récentes (dans les derniers $1)',
 'pageinfo-recent-authors' => "Nombre d'auteurs distincts récents",
-'pageinfo-restriction' => 'Protection de la page ({{lcfirst:$1}})',
 'pageinfo-magic-words' => '{{PLURAL:$1|Mot magique|Mots magiques}} ($1)',
 'pageinfo-hidden-categories' => '{{PLURAL:$1|Catégorie cachée|Catégories cachées}} ($1)',
 'pageinfo-templates' => '{{PLURAL:$1|Modèle inclu|Modèles inclus}} ($1)',
+'pageinfo-toolboxlink' => 'Information sur la page',
 
 # Skin names
 'skinname-standard' => 'Standard',
@@ -3773,6 +3773,7 @@ $5',
 # Scary transclusion
 'scarytranscludedisabled' => '[La transclusion interwiki est désactivée]',
 'scarytranscludefailed' => '[La récupération de modèle a échoué pour $1]',
+'scarytranscludefailed-httpstatus' => '[Échec de la récupération du modèle pour  $1 : HTTP  $2 ]',
 'scarytranscludetoolong' => '[L’URL est trop longue]',
 
 # Delete conflict
index 8e882be..fab20f1 100644 (file)
@@ -467,7 +467,7 @@ $messages = array(
 'vector-action-protect' => 'Protègiér',
 'vector-action-undelete' => 'Refâre',
 'vector-action-unprotect' => 'Changiér la protèccion',
-'vector-simplesearch-preference' => 'Activar les idês de rechèrche bônâyes (solament por « Vèctor »)',
+'vector-simplesearch-preference' => 'Activar la bârra de rechèrche simplifiâye (solament por l’habelyâjo « Vèctor »)',
 'vector-view-create' => 'Fâre',
 'vector-view-edit' => 'Changiér',
 'vector-view-history' => 'Fâre vêre l’historico',
@@ -689,10 +689,11 @@ Volyéd tornar èprovar dens un tôrn.',
 'protectedpagetext' => 'Ceta pâge est étâye protègiêye por empachiér son changement.',
 'viewsourcetext' => 'Vos pouede vêre et pués copiyér lo tèxto sôrsa de ceta pâge :',
 'viewyourtext' => "Vos pouede vêre et pués copiyér lo tèxto sôrsa de '''voutros changements''' a ceta pâge :",
-'protectedinterface' => 'Ceta pâge balye de tèxto d’entèrface por la programeria et est vêr protègiêye por èvitar los abus.',
+'protectedinterface' => 'Cela pâge-que balye de tèxto d’entèrface por la programeria sur ceti vouiqui, et est vêr protègiêye por èvitar los abus.
+Por apondre ou ben changiér des traduccions sur tôs los vouiquis, volyéd empleyér [//translatewiki.net/ translatewiki.net], lo projèt de localisacion de MediaWiki.',
 'editinginterface' => "'''Atencion :''' vos éte aprés changiér na pâge empleyêye por fâre lo tèxto d’entèrface de la programeria.
-Los changements sè cognetront sur totes ou ben doux-três pâges visibles per los ôtros utilisators.
-Por les traduccions, nos vos envitens a empleyér [//translatewiki.net/wiki/Main_Page?setlang=frp translatewiki.net], lo projèt de localisacion de MediaWiki.",
+Los changements sè cognetront sur l’aparence de l’entèrface utilisator por los ôtros utilisators de ceti vouiqui.
+Por apondre ou ben changiér des traduccions sur tôs los vouiquis, volyéd empleyér [//translatewiki.net/ translatewiki.net], lo projèt de localisacion de MediaWiki.",
 'sqlhidden' => '(Demanda SQL cachiêye)',
 'cascadeprotected' => 'Cela pâge-que est protègiêye perce qu’el est encllua dedens {{PLURAL:$1|ceta pâge, qu’est étâye protègiêye|cetes pâges, que sont étâyes protègiêyes}} avouéc lo chouèx « protèccion en cascâda » activâ :
 $2',
@@ -712,7 +713,7 @@ La rêson balyêye est « ''$2'' ».",
 # Login and logout pages
 'logouttext' => "'''Ora vos éte dèbranchiê{{GENDER:||ye|(ye)}}.'''
 
-Vos pouede continuar a empleyér {{SITENAME}} de façon anonima ou ben [[Special:UserLogin|vos tornar branchiér]] desot lo mémo nom ou un ôtro.
+Vos pouede continuar a empleyér {{SITENAME}} de façon anonima ou ben <span class='plainlinks'>[$1 vos tornar branchiér]</span> desot lo mémo nom ou un ôtro.
 Notâd qu’y at des pâges que pôvont étre oncor fêtes vêre coment se vos érâd adés branchiê{{GENDER:||ye|(ye)}}, tant que vos èfaciéd lo cacho de voutron navigator.",
 'welcomecreation' => '== Benvegnua, $1 ! ==
 Voutron compto est étâ fêt.
@@ -1443,7 +1444,7 @@ Vê-que una valor fêta per hasârd que vos pouede utilisar : $1',
 'timezoneregion-indian' => 'Ocèan endien',
 'timezoneregion-pacific' => 'Ocèan pacefico',
 'allowemail' => 'Ôtorisar l’èxpèdicion de mèssâjos que vegnont d’ôtros usanciérs',
-'prefs-searchoptions' => 'Chouèx de rechèrche',
+'prefs-searchoptions' => 'Rechèrche',
 'prefs-namespaces' => 'Èspâços de noms',
 'defaultns' => 'Ôtrament rechèrchiér dens cetos èspâços de noms :',
 'default' => 'per dèfôt',
index 9b454ed..6d97dd8 100644 (file)
@@ -422,7 +422,7 @@ Di grünj faan di administraator as: „$3“.',
 # Login and logout pages
 'logouttext' => "'''Dü bast nü oufmälded.'''
 
-Dü koost {{SITENAME}} nü anonüüm widerbrüke, unti de wider uner diseelew unti en oudern brükernoome [[Special:UserLogin|önjmälde]].
+Dü koost {{SITENAME}} nü anonüüm widerbrüke, unti de wider uner diseelew unti en oudern brükernoome <span class='plainlinks'>[$1 önjmälde]</span>.
 Påås aw, dåt hu side nuch wise koone, dåt dü önjmälded bast, sülung dü ai dan browsercache lääsimååged heest.",
 'welcomecreation' => '== Wäljkiimen, $1! ==
 
index b6709d6..5841167 100644 (file)
@@ -410,7 +410,7 @@ Al podarès vê dentri caratars che no podin jessi doprâts tai titui.',
 # Login and logout pages
 'logouttext' => "'''Tu sâs cumò lât fûr.'''
 
-Tu puedis continuâ a doprâ {{SITENAME}} come anonim, o tu puedis [[Special:UserLogin|jentrâ di gnûf]] cul stes o cuntun altri non utent.
+Tu puedis continuâ a doprâ {{SITENAME}} come anonim, o tu puedis <span class='plainlinks'>[$1 jentrâ di gnûf]</span> cul stes o cuntun altri non utent.
 Considere che cualchi pagjine e pues mostrâti ancjemò come jentrât tal sît fin cuant che no tu netis la memorie cache dal sgarfadôr.",
 'welcomecreation' => '== Mandi e benvignût $1! ==
 La tô identitât e je stade creade. 
index 911b473..71b79b4 100644 (file)
@@ -504,7 +504,7 @@ De oanfierde reden is ''$2''.",
 # Login and logout pages
 'logouttext' => "'''Jo binne no ôfmeld.'''
 
-Jo kinne de {{SITENAME}} fierders anonym brûke, of jo op 'e [[Special:UserLogin|nij oanmelde]] ûnder deselde of in oare namme.
+Jo kinne de {{SITENAME}} fierders anonym brûke, of jo op 'e <span class='plainlinks'>[$1 nij oanmelde]</span> ûnder deselde of in oare namme.
 Mûglik wurdt noch in tal siden werjûn as wiene Jo oanmeld, oant Jo de cache fan Jo browser leegje.",
 'welcomecreation' => '<h2>Wolkom, $1!</h2><p>Jo ynstellings binne oanmakke.
 Ferjit net se oan jo foarkar oan te passen.',
index 043555c..cee9a1e 100644 (file)
@@ -476,7 +476,7 @@ An fáth ná ''$2''.",
 # Login and logout pages
 'logouttext' => "'''Tá tú logáilte amach anois.'''
 
-Is féidir leat an {{SITENAME}} a úsáid fós gan ainm, nó is féidir leat [[Special:UserLogin|logáil isteach arís]] mar an úsáideoir céanna, nó mar úsáideoir eile.
+Is féidir leat an {{SITENAME}} a úsáid fós gan ainm, nó is féidir leat <span class='plainlinks'>[$1 logáil isteach arís]</span> mar an úsáideoir céanna, nó mar úsáideoir eile.
 Tabhair faoi deara go taispeáinfear roinnt leathanaigh mar atá tú logáilte isteach fós, go dtí go ghlanfá amach do taisce líonleitheora.",
 'welcomecreation' => '== Tá fáilte romhat, $1! ==
 
index bab74e7..5502087 100644 (file)
@@ -375,8 +375,8 @@ Yalvarêrız benneyiniz URL - i hem raport ediniz bunu bir [[Special:ListUsers/s
 'viewsourcetext' => 'Var nicä görmää hem kopiya etmää bu yapraa gelinirini:',
 
 # Login and logout pages
-'logouttext' => 'Sessiyayı kapattınız.
-Şindi var nicä devam etmää kullanmaa {{SITENAME}} saytını kimlik göstermedän yaki [[Special:UserLogin|enidän sessiya açmaa]] (ister hep o kullanıcı adıylan, ister başka bir kullanıcı adıylan). O zamana kadar ani web brauzerinizin keşi temizlenecek bir takım sayfalar var nicä görünsün sansın sessiya hep açık.',
+'logouttext' => "Sessiyayı kapattınız.
+Şindi var nicä devam etmää kullanmaa {{SITENAME}} saytını kimlik göstermedän yaki <span class='plainlinks'>[$1 enidän sessiya açmaa]</span> (ister hep o kullanıcı adıylan, ister başka bir kullanıcı adıylan). O zamana kadar ani web brauzerinizin keşi temizlenecek bir takım sayfalar var nicä görünsün sansın sessiya hep açık.",
 'welcomecreation' => '== Hoş geldiniz $1! ==
 
 Esapınız açıldı. Unutmayın [[Special:Preferences|{{SITENAME}} preferences]] seçimnerin diiştirmää.',
index 5d90f99..3c792c9 100644 (file)
@@ -401,7 +401,7 @@ $2',
 # Login and logout pages
 'logouttext' => "'''汝退出正哩。'''
 
-接到汝得匿名使用{{SITENAME}},或[[Special:UserLogin|登入过]]。除非汝删吥浏览器缓存,只把子页面可能会接到话汝系登入状态。",
+接到汝得匿名使用{{SITENAME}},或<span class='plainlinks'>[$1 登入过]</span>。除非汝删吥浏览器缓存,只把子页面可能会接到话汝系登入状态。",
 'welcomecreation' => '== 欢迎, $1! ==
 
 建正哩汝𠮶帐户,莫𫍧记设置 [[Special:Preferences|{{SITENAME}}𠮶个人参数]]。',
index 35812d4..be42d3e 100644 (file)
@@ -423,7 +423,7 @@ $2',
 # Login and logout pages
 'logouttext' => "'''汝退出正哩。'''
 
-接到汝得匿名使用{{SITENAME}},或[[Special:UserLogin|登入過]]。除非汝刪吥瀏覽器緩存,隻把子頁面可能會接到話汝係登入狀態。",
+接到汝得匿名使用{{SITENAME}},或<span class='plainlinks'>[$1 登入過]</span>。除非汝刪吥瀏覽器緩存,隻把子頁面可能會接到話汝係登入狀態。",
 'welcomecreation' => '== 歡迎, $1! ==
 
 建正哩汝嗰帳戶,莫誺記設置 [[Special:Preferences|{{SITENAME}}嗰個人參數]]。',
index 0fe10b5..4be696f 100644 (file)
@@ -431,7 +431,7 @@ Seo am mìneachadh: "\'\'$2\'\'".',
 
 # Login and logout pages
 'logouttext' => "'''Chaidh do logadh a-mach.'''
-'S urrainn dhut leantainn air adhart a' cleachdadh {{SITENAME}} a chleachdadh gun urra no 's urrainn dhut [[Special:UserLogin|logadh a-steach a-rithist]] mar an dearbh-chleachdaiche no mar chleachdaiche eile.
+'S urrainn dhut leantainn air adhart a' cleachdadh {{SITENAME}} a chleachdadh gun urra no 's urrainn dhut <span class='plainlinks'>[$1 logadh a-steach a-rithist]</span> mar an dearbh-chleachdaiche no mar chleachdaiche eile.
 Thoir an aire gum bi coltas air cuide dhe na duilleagan mar gum biodh tu air logadh a-steach gus am falamhaich thu tasgadan a' bhrabhsair agad.",
 'welcomecreation' => '== Fàilte ort, $1! ==
 Chaidh an cunntas agad a chruthachadh.
index 49b3e86..d994775 100644 (file)
@@ -8,6 +8,7 @@
  * @file
  *
  * @author Alma
+ * @author Dferg
  * @author Elisardojm
  * @author Gallaecio
  * @author Gustronico
@@ -632,7 +633,7 @@ O administrador que bloqueou o repositorio achegou este motivo: "$3".',
 # Login and logout pages
 'logouttext' => "'''Agora está fóra do sistema.'''
 
-Pode continuar usando {{SITENAME}} de xeito anónimo, ou pode [[Special:UserLogin|acceder de novo]] co mesmo nome de usuario ou con outro.
+Pode continuar usando {{SITENAME}} de xeito anónimo, ou pode <span class='plainlinks'>[$1 acceder de novo]</span> co mesmo nome de usuario ou con outro.
 Teña en conta que mentres non se limpa a memoria caché do seu navegador algunhas páxinas poden continuar aparecendo como se aínda estivese dentro do sistema.",
 'welcomecreation' => '== Reciba a nosa benvida, $1! ==
 A súa conta foi creada correctamente.
@@ -2645,7 +2646,7 @@ O motivo do bloqueo de $1 é: "$2"',
 'blocklogpage' => 'Rexistro de bloqueos',
 'blocklog-showlog' => 'Este usuario xa foi bloqueado con anterioridade. Velaquí está o rexistro de bloqueos por se quere consultalo:',
 'blocklog-showsuppresslog' => 'Este usuario xa foi bloqueado e agochado con anterioridade. Velaquí está o rexistro de supresións por se quere consultalo:',
-'blocklogentry' => 'bloqueou a "[[$1]]" cun tempo de duración de $2 $3',
+'blocklogentry' => 'bloqueou a [[$1]] $3 cun tempo de duración de $2',
 'reblock-logentry' => 'cambiou as configuracións do bloqueo de "[[$1]]" cunha caducidade de $2 $3',
 'blocklogtext' => 'Este é o rexistro das accións de bloqueo e desbloqueo de usuarios.
 Non se listan os enderezos IP bloqueados automaticamente.
@@ -3072,10 +3073,10 @@ Isto, probabelmente, se debe a unha ligazón cara a un sitio externo que está n
 'pageinfo-authors' => 'Número total de autores distintos',
 'pageinfo-recent-edits' => 'Número de edicións recentes (durante os últimos $1)',
 'pageinfo-recent-authors' => 'Número de autores distintos recentes',
-'pageinfo-restriction' => 'Protección da páxina ({{lcfirst:$1}})',
 'pageinfo-magic-words' => '{{PLURAL:$1|Palabra máxica|Palabras máxicas}} ($1)',
 'pageinfo-hidden-categories' => '{{PLURAL:$1|Categoría agochada|Categorías agochadas}} ($1)',
 'pageinfo-templates' => '{{PLURAL:$1|Modelo incluído|Modelos incluídos}} ($1)',
+'pageinfo-toolboxlink' => 'Información da páxina',
 
 # Skin names
 'skinname-standard' => 'Clásica',
@@ -3661,6 +3662,7 @@ O código de confirmación caduca o $6 ás $7.',
 # Scary transclusion
 'scarytranscludedisabled' => '[A transclusión interwiki está desactivada]',
 'scarytranscludefailed' => '[Fallou a busca do modelo "$1"]',
+'scarytranscludefailed-httpstatus' => '[Fallou a busca do modelo "$1": HTTP $2]',
 'scarytranscludetoolong' => '[O enderezo URL é demasiado longo]',
 
 # Delete conflict
index 9aec492..758f19e 100644 (file)
@@ -413,7 +413,7 @@ $2',
 # Login and logout pages
 'logouttext' => "'''Νῦν γὰρ ἀποσυνδεδεμένος εἰ.'''
 
-Ἔξεστί σοι χρῆσθαι τῷ {{SITENAME}} ἀνωνύμως, ἢ ἔξεστί σοι [[Special:UserLogin|συνδεῖσθαι πάλιν]] ὡς ὁ αὐτὸς ἢ ὡς ἄλλος χρώμενος.
+Ἔξεστί σοι χρῆσθαι τῷ {{SITENAME}} ἀνωνύμως, ἢ ἔξεστί σοι <span class='plainlinks'>[$1 συνδεῖσθαι πάλιν]</span> ὡς ὁ αὐτὸς ἢ ὡς ἄλλος χρώμενος.
 Δέλτοι τινὲς δέ, ἐνδεχομένως, δειχθήσονται ὡς ἂν ἀκμὴν συνδεδεμένος ᾖς, μέχρι ὅτε καθαίρῃς τὴν λανθάνουσαν μνήμην τοῦ προγράμματος πλοηγήσεώς σου.",
 'welcomecreation' => '== Ὡς εὖ παρέστης, $1! ==
 
index f405e2a..2dac933 100644 (file)
@@ -291,7 +291,7 @@ $messages = array(
 'vector-action-protect' => 'Schitze',
 'vector-action-undelete' => 'Widerhärstelle',
 'vector-action-unprotect' => 'Syteschutz ändere',
-'vector-simplesearch-preference' => 'Erwytereti Suechvorschleg aktiviere (nume Vector)',
+'vector-simplesearch-preference' => 'Vereifachti Suechvorschleg aktiviere (nume Vector)',
 'vector-view-create' => 'Aalege',
 'vector-view-edit' => 'Bearbeite',
 'vector-view-history' => 'Versionsgschicht',
@@ -536,7 +536,7 @@ Dr Administrator, wu dr Schrybzuegriff gsperrt het, het dää Grund aagee: „$3
 # Login and logout pages
 'logouttext' => "'''Du bisch jetz abgmäldet.'''
 
-Du chasch {{SITENAME}} wyter anonym bruche, oder Du chasch di [[Special:UserLogin|wider aamälde]] mit em glyche oder eme andere Benutzername.
+Du chasch {{SITENAME}} wyter anonym bruche, oder Du chasch di <span class='plainlinks'>[$1 wider aamälde]</span> mit em glyche oder eme andere Benutzername.
 
 Ochat: s cha syy, ass bstimmti Syte eso aazeigt wäre, wie wänn Du allno aagmäldet wärsch, bis Du dr Zwischespycher vu Dyym Browser glescht hesch.",
 'welcomecreation' => '==Willcho, $1!==
@@ -2846,10 +2846,10 @@ Die uf em lokale Rächner spychere un derno do uffelade.',
 'pageinfo-authors' => 'Aazahl vu unterschidlige Autore',
 'pageinfo-recent-edits' => 'Aazahl vu dr letschte Bearbeitige (innerhalb vu $1)',
 'pageinfo-recent-authors' => 'Aazahl vu unterschidlige Autore',
-'pageinfo-restriction' => 'Syteschutz ({{lcfirst:$1}})',
 'pageinfo-magic-words' => '{{PLURAL:$1|Magischs Wort|Magischi Werter}} ($1)',
 'pageinfo-hidden-categories' => 'Versteckti {{PLURAL:$1|Kategori|Kategorie}} ($1)',
 'pageinfo-templates' => 'Yybundeni {{PLURAL:$1|Vorlag|Vorlage}} ($1)',
+'pageinfo-toolboxlink' => 'Informatione zue dr Syte',
 
 # Patrolling
 'markaspatrolleddiff' => 'Als patrulyrt markyre',
@@ -3419,6 +3419,7 @@ Dää Bstetigungscode isch giltig bis am $4.',
 # Scary transclusion
 'scarytranscludedisabled' => '[Interwiki-Yybindig isch deaktiviert]',
 'scarytranscludefailed' => '[Vorlage-Yybindig fir $1 isch gescheitert]',
+'scarytranscludefailed-httpstatus' => '[Vorlagenabruef fählgschlaa fir $1: HTTP  $2]',
 'scarytranscludetoolong' => '[URL isch z lang]',
 
 # Delete conflict
index 6f4e077..e416c8c 100644 (file)
@@ -229,7 +229,7 @@ $messages = array(
 'july' => 'જુલાઇ',
 'august' => 'ઓગસ્ટ',
 'september' => 'સપ્ટેમ્બર',
-'october' => 'ઓકટોબર',
+'october' => 'àª\93àª\95à«\8dàª\9fà«\8bબર',
 'november' => 'નવેમ્બર',
 'december' => 'ડિસેમ્બર',
 'january-gen' => 'જાન્યુઆરી',
@@ -241,7 +241,7 @@ $messages = array(
 'july-gen' => 'જુલાઇ',
 'august-gen' => 'ઓગસ્ટ',
 'september-gen' => 'સપ્ટેમ્બર',
-'october-gen' => 'ઓકટોબર',
+'october-gen' => 'àª\93àª\95à«\8dàª\9fà«\8bબર',
 'november-gen' => 'નવેમ્બર',
 'december-gen' => 'ડિસેમ્બર',
 'jan' => 'જાન્યુ',
@@ -557,7 +557,7 @@ $2',
 # Login and logout pages
 'logouttext' => "'''તમે (લોગ આઉટ કરીને) બહાર નિકળી ચુક્યા છો.'''
 
-તમે અનામી તરીકે {{SITENAME}} વાપરવાનું ચાલુ રાખી શકો છો, કે પછી તેના તે જ કે અલગ સભ્ય તરીકે [[Special:UserLogin|ફરી પ્રવેશ]] કરી શકો છો.
+તમે અનામી તરીકે {{SITENAME}} વાપરવાનું ચાલુ રાખી શકો છો, કે પછી તેના તે જ કે અલગ સભ્ય તરીકે <span class='plainlinks'>[$1 ફરી પ્રવેશ]</span> કરી શકો છો.
 ધ્યાન રાખો કે જ્યાં સુધી તમે તમારા બ્રાઉઝરનો  કૅશ સાફ નહીં કરો ત્યાં સુધી કેટલાક પાનાં તમે પ્રવેશી ચુક્યા છો તેમ બતાવશે.",
 'welcomecreation' => '== તમારૂં સ્વાગત છે $1! ==
 તમારૂં ખાતું બની ગયું છે.
index 499ad5a..92cd9a5 100644 (file)
@@ -764,7 +764,7 @@ $2',
 # Login and logout pages
 'logouttext' => "'''יצאתם זה עתה מהחשבון.'''
 
-באפשרותכם להמשיך ולעשות שימוש ב{{grammar:תחילית|{{SITENAME}}}} באופן אנונימי, או [[Special:UserLogin|לשוב ולהיכנס לאתר]] עם שם משתמש זהה או אחר.
+באפשרותכם להמשיך ולעשות שימוש ב{{grammar:תחילית|{{SITENAME}}}} באופן אנונימי, או <span class='plainlinks'>[$1 לשוב ולהיכנס לאתר]</span> עם שם משתמש זהה או אחר.
 שימו לב כי ייתכן שדפים אחדים ימשיכו להיות מוצגים כאילו אתם עדיין מחוברים לחשבון עד שתנקו את המטמון של הדפדפן שלכם.",
 'welcomecreation' => '== ברוך בואך, $1! ==
 חשבונך נוצר.
@@ -3189,10 +3189,10 @@ $1',
 'pageinfo-authors' => 'המספר הכולל של כותבים שונים',
 'pageinfo-recent-edits' => 'מספר העריכות לאחרונה ($1)',
 'pageinfo-recent-authors' => 'מספר הכותבים הייחודיים לאחרונה',
-'pageinfo-restriction' => 'הגנה על הדף ($1)',
 'pageinfo-magic-words' => '{{PLURAL:$1|מילת קסם|מילות קסם}} ($1)',
 'pageinfo-hidden-categories' => '{{PLURAL:$1|קטגוריה מוסתרת|קטגוריות מוסתרות}} ($1)',
 'pageinfo-templates' => '{{PLURAL:$1|תבנית מוכללת|תבניות מוכללות}} ($1)',
+'pageinfo-toolboxlink' => 'מידע על הדף',
 
 # Skin names
 'skinname-standard' => 'קלאסי',
@@ -3768,7 +3768,8 @@ $5
 
 # Scary transclusion
 'scarytranscludedisabled' => '[הכללת דפים בין אתרים מבוטלת]',
-'scarytranscludefailed' => '[הכללת התבנית נכשלה בגלל $1]',
+'scarytranscludefailed' => '[הכללת התבנית נכשלה עבור $1]',
+'scarytranscludefailed-httpstatus' => '[הכללת התבנית נכשלה עבור $1&rlm;: HTTP $2]',
 'scarytranscludetoolong' => '[כתובת ה־URL ארוכה מדי]',
 
 # Delete conflict
index 6a86595..c06a669 100644 (file)
@@ -581,7 +581,7 @@ $2',
 # Login and logout pages
 'logouttext' => "'''अब आपका सत्रांत हो चुका है।'''
 
-आप बेनामी हो के {{SITENAME}} का प्रयोग जारी रख सकते हैं, या उसी या किसी और सदस्य के तौर पर [[Special:UserLogin|फिर से सत्रारंभ]] कर सकते हैं।
+आप बेनामी हो के {{SITENAME}} का प्रयोग जारी रख सकते हैं, या उसी या किसी और सदस्य के तौर पर <span class='plainlinks'>[$1 फिर से सत्रारंभ]</span> कर सकते हैं।
 ध्यान दें कि जब तक आप अपनी ब्राउज़र कैशे खाली नहीं करते हैं, कुछ पृष्ठ अब भी ऐसे दिख सकते हैं जैसे कि आपका सत्र अभी भी चल रहा हो।",
 'welcomecreation' => '== आपका स्वागत है, $1 ! ==
 आपका खाता बनाया जा चुका है। अपनी [[Special:Preferences|{{SITENAME}} वरीयताएँ]] परिवर्तित करना न भूलिएगा।',
index 8b8e1fb..5e89a2f 100644 (file)
@@ -436,7 +436,7 @@ Jon administrator iske lock karis hae, koi kaaran nai diis hae: "$3"',
 # Login and logout pages
 'logouttext' => "'''Aap abhi logged out hai.'''
 
-Aap bina naam ke {{SITENAME}} ke kaam me lae sakta hai, nai to aap wahi sadasya ke naam se nai to duusra sadasya ke naam se [[Special:UserLogin|log in kare sakta hai]].
+Aap bina naam ke {{SITENAME}} ke kaam me lae sakta hai, nai to aap wahi sadasya ke naam se nai to duusra sadasya ke naam se <span class='plainlinks'>[$1 log in kare sakta hai]</span>.
 Yaad rakhna ki kuch panna wahi rakam se dekhai jaise ki aap log in bhaya hai, jab tak ki browser ke cache safaa nai hoe jaae.",
 'welcomecreation' => '== Swagat, $1! ==
 Aap ke account banae dewa gais hai.
index c4310df..983d30b 100644 (file)
@@ -427,7 +427,7 @@ Ang administrador nga nag-kandado sini naghatag sang paathag nga: "$3".',
 # Login and logout pages
 'logouttext' => "'''Naka-guha ka na.'''
 
-Makapadayon ka sa gihapon sa paggamit sang {{SITENAME}} nga indi makilal-an, ukon mahimo ka man [[Special:UserLogin|magsulod liwat]] bilang amo sa gihapon ukon lain nga nga manug-gamit.
+Makapadayon ka sa gihapon sa paggamit sang {{SITENAME}} nga indi makilal-an, ukon mahimo ka man <span class='plainlinks'>[$1 magsulod liwat]</span> bilang amo sa gihapon ukon lain nga nga manug-gamit.
 Tandaan nga may mga panid nga mahimo ma-display sa gihapon nga daw nakasulod ka sa gihapon, hasta mapanas mo na ang tinago sang imo brawser.",
 'welcomecreation' => '== Pagtamyaw, $1! ==
 Ang imo account nahimo na.
index 68649e2..e30bec1 100644 (file)
@@ -711,7 +711,7 @@ $2',
 # Login and logout pages
 'logouttext' => "'''Odjavili ste se.'''
 
-Možete nastaviti s korištenjem {{SITENAME}} neprijavljeni, ili se možete ponovo [[Special:UserLogin|prijaviti]] pod istim ili drugim imenom.
+Možete nastaviti s korištenjem {{SITENAME}} neprijavljeni, ili se možete ponovo <span class='plainlinks'>[$1 prijaviti]</span> pod istim ili drugim imenom.
 Neke se stranice mogu prikazivati kao da ste još uvijek prijavljeni, sve dok ne očistite međuspremnik svog preglednika.",
 'welcomecreation' => '== Dobrodošli, $1! ==
 Vaš je suradnički račun otvoren.
index f983e4c..a775eef 100644 (file)
@@ -557,7 +557,7 @@ Administrator, kiž je jón zawrěł, je tule přičinu podał: "$3".',
 # Login and logout pages
 'logouttext' => "'''{{GENDER:|Sy|Sy}} nětko {{GENDER:|wotzjewjeny|wotzjewjena}}.'''
 
-Móžeš {{GRAMMAR:akuzatiw|{{SITENAME}}}} nětko anonymnje dale wužiwać abo so ze samsnym abo druhim wužiwarskim mjenom [[Special:UserLogin|zaso přizjewić]].
+Móžeš {{GRAMMAR:akuzatiw|{{SITENAME}}}} nětko anonymnje dale wužiwać abo so ze samsnym abo druhim wužiwarskim mjenom <span class='plainlinks'>[$1 zaso přizjewić]</span>.
 Wobkedźbuj, zo so někotre strony dale jewja, kaž by hišće přizjewjeny był, doniž pufrowak swojeho wobhladowaka njewuprózdnješ.",
 'welcomecreation' => '== Witaj, $1! ==
 
@@ -2870,10 +2870,10 @@ W poslednim padźe móžeš tež wotkaz wužiwać, na př. „[[{{#Special:Expor
 'pageinfo-authors' => 'Cyłkowna ličba rozdźělnych awtorow',
 'pageinfo-recent-edits' => 'Ličba najnowšich změnow (za zańdźenych $1)',
 'pageinfo-recent-authors' => 'Najnowša ličba rozdźělnych awtorow',
-'pageinfo-restriction' => 'Škit strony ({{lcfirst:$1}})',
 'pageinfo-magic-words' => '{{PLURAL:$1|Magiske słowo|Magiskej słowje|Magiske słowa|Magiske słowa}} ($1)',
 'pageinfo-hidden-categories' => '{{PLURAL:$1|Schowana kategorija|Schowanej kategoriji|Schowane kategorije|Schowane kategorije}} ($1)',
 'pageinfo-templates' => '{{PLURAL:$1|Zapřijata předłoha|Zapřijatej předłoze|Zapřijate předłohi|Zapřijate předłohi}} ($1)',
+'pageinfo-toolboxlink' => 'Informacije wo stronje',
 
 # Skin names
 'skinname-standard' => 'Klasiski',
@@ -3442,6 +3442,7 @@ Tutón wobkrućenski kod spadnje $4.',
 # Scary transclusion
 'scarytranscludedisabled' => '[Zapřijeće mjezyrěčnych wotkazow je znjemóžnjene]',
 'scarytranscludefailed' => '[Zapřijimanje předłohi za $1 je so njeporadźiło]',
+'scarytranscludefailed-httpstatus' => '[Wotwołanje předłohi za $1 je so njeporadźiło: HTTP $2]',
 'scarytranscludetoolong' => '[URL je předołhi]',
 
 # Delete conflict
index 6be61f7..65845fe 100644 (file)
@@ -525,7 +525,7 @@ Rezon li bay yo se « ''$2'' ».",
 # Login and logout pages
 'logouttext' => "'''Ou dekonekte kounye a.'''
 
-Ou mèt kontinye itilize {{SITENAME}} san ou pa idantifye, oubyen ou ka [[Special:UserLogin|rekonekte]] w ankò ak menm non an oubyen yon lòt.
+Ou mèt kontinye itilize {{SITENAME}} san ou pa idantifye, oubyen ou ka <span class='plainlinks'>[$1 rekonekte]</span> w ankò ak menm non an oubyen yon lòt.
 Note ke kèk paj gendwa afiche tankou ou te toujou konekte tank ou pa efase kach nan navigatè ou.",
 'welcomecreation' => '== Byenvini, $1 ! ==
 
index cdab924..4efc521 100644 (file)
@@ -713,7 +713,7 @@ A lezárást végrehajtó rendszergazda az alábbi indoklást adta meg: "$3".',
 # Login and logout pages
 'logouttext' => "'''Sikeresen kijelentkeztél.'''
 
-Folytathatod névtelenül  a(z) {{SITENAME}} használatát, vagy [[Special:UserLogin|ismét bejelentkezhetsz]] ugyanezzel, vagy egy másik névvel.
+Folytathatod névtelenül  a(z) {{SITENAME}} használatát, vagy <span class='plainlinks'>[$1 ismét bejelentkezhetsz]</span> ugyanezzel, vagy egy másik névvel.
 Lehetséges, hogy néhány oldalon továbbra is azt látod, be vagy jelentkezve, mindaddig, amíg nem üríted a böngésződ gyorsítótárát.",
 'welcomecreation' => '== Köszöntünk, $1! ==
 A felhasználói fiókodat létrehoztuk.
index 15e2e72..0a44971 100644 (file)
@@ -662,7 +662,7 @@ $2',
 # Login and logout pages
 'logouttext' => "'''Դուք դուրս եկաք համակարգից։'''
 
-Դուք կարող եք շարունակել օգտագործել {{SITENAME}} կայքը անանուն, կամ [[Special:UserLogin|կրկին մուտք գործել համակարգ]] նույն կամ մեկ այլ մասնակցի անվամբ։ Ի նկատի ունեցեք, որ որոշ էջեր կարող են ցուցադրվել այնպես՝ ինչպես եթե դեռ համակարգում լինեիք մինչև որ չջնջեք ձեր զննարկիչի հիշապահեստը։",
+Դուք կարող եք շարունակել օգտագործել {{SITENAME}} կայքը անանուն, կամ <span class='plainlinks'>[$1 կրկին մուտք գործել համակարգ]</span> նույն կամ մեկ այլ մասնակցի անվամբ։ Ի նկատի ունեցեք, որ որոշ էջեր կարող են ցուցադրվել այնպես՝ ինչպես եթե դեռ համակարգում լինեիք մինչև որ չջնջեք ձեր զննարկիչի հիշապահեստը։",
 'welcomecreation' => '== Բարի՛ գալուստ, $1 ==
 Ձեր հաշիվը ստեղծված է։
 Չմոռանաք անձնավորել ձեր [[Special:Preferences|նախընտրությունները]]։',
index c10e591..903e2f0 100644 (file)
@@ -552,7 +552,7 @@ Le administrator qui lo blocava offereva iste explication: "$3".',
 # Login and logout pages
 'logouttext' => "'''Tu ha claudite le session.'''
 
-Tu pote continuar a usar {{SITENAME}} anonymemente, o tu pote [[Special:UserLogin|aperir un nove session]] con le mesme nomine de usator o con un altere.
+Tu pote continuar a usar {{SITENAME}} anonymemente, o tu pote <span class='plainlinks'>[$1 aperir un nove session]</span> con le mesme nomine de usator o con un altere.
 Nota que alcun paginas pote continuar a apparer como si tu esserea ancora authenticate. Pro remediar isto, tu pote vacuar le cache de tu navigator.",
 'welcomecreation' => '== Benvenite, $1! ==
 Tu conto ha essite create.
@@ -3020,7 +3020,6 @@ Le causa es probabilemente un ligamine verso un sito externe que es presente in
 'pageinfo-authors' => 'Numero total de autores distincte',
 'pageinfo-recent-edits' => 'Numero de modificationes recente (intra le ultime $1)',
 'pageinfo-recent-authors' => 'Numero de autores distincte recente',
-'pageinfo-restriction' => 'Protection del pagina ({{lcfirst:$1}})',
 'pageinfo-magic-words' => '{{PLURAL:$1|Parola|Parolas}} magic ($1)',
 'pageinfo-hidden-categories' => '{{PLURAL:$1|Categoria|Categorias}} celate ($1)',
 'pageinfo-templates' => '{{PLURAL:$1|Patrono|Patronos}} transcludite ($1)',
index afb5b7b..9ee48f4 100644 (file)
@@ -735,7 +735,7 @@ Administrator yang terkunci menawarkan penjelasan ini: " $3 ".',
 # Login and logout pages
 'logouttext' => "'''Anda telah keluar log dari sistem.'''
 
-Anda dapat terus menggunakan {{SITENAME}} secara anonim, atau Anda dapat [[Special:UserLogin|masuk log lagi]] sebagai pengguna yang sama atau pengguna yang lain.
+Anda dapat terus menggunakan {{SITENAME}} secara anonim, atau Anda dapat <span class='plainlinks'>[$1 masuk log lagi]</span> sebagai pengguna yang sama atau pengguna yang lain.
 Perhatikan bahwa beberapa halaman mungkin masih terus menunjukkan bahwa Anda masih masuk log sampai Anda membersihkan singgahan penjelajah web Anda",
 'welcomecreation' => '== Selamat datang, $1! ==
 
index ec2cba0..91bc84a 100644 (file)
@@ -422,7 +422,7 @@ Li motive dat es "\'\'$2\'\'".',
 
 # Login and logout pages
 'logouttext' => "'''Vu ha terminat vor session.'''
-Vu posse continuar usar {{SITENAME}} anonimimen, o vu posse [[Special:UserLogin|aperter un session denov]] quam li sam usator o quam un diferent usator.
+Vu posse continuar usar {{SITENAME}} anonimimen, o vu posse <span class='plainlinks'>[$1 aperter un session denov]</span> quam li sam usator o quam un diferent usator.
 Nota que alcun págines posse continuar esser monstrat quam si vu esset registrat, til que vu vacua li cache de tui navigator.",
 'welcomecreation' => '== Benevenit, $1! == 
 Tui conto hat esset creat. 
index c7eaa58..b1a8e09 100644 (file)
@@ -439,7 +439,7 @@ Maka ikuwaria na asụsụ nke ozor, biko chetu I ji [//translatewiki.net/wiki/M
 # Login and logout pages
 'logouttext' => "'''I fwuóla ubwá.'''
 
-I nwèríkí jíwá {{SITENAME}} na nke ẹnwéghi áhà, mànà Í nwèríkí [[Special:UserLogin|bátá òzọr]] na áhà Í shị fwüo ma áhà ozọr.
+I nwèríkí jíwá {{SITENAME}} na nke ẹnwéghi áhà, mànà Í nwèríkí <span class='plainlinks'>[$1 bátá òzọr]</span> na áhà Í shị fwüo ma áhà ozọr.
 Màkwá na o dị ihü gi zi kà Í nor kwa ímé, o gi kwüshí mgbe Í sáfùrù cache ihe ishi a gá intanet gi.",
 'welcomecreation' => '== Nnöö, $1! ==
 Okíkè buwa gi a guchala.
index f1d0bc9..7551df8 100644 (file)
@@ -446,7 +446,7 @@ Ti administrador a nagserra ket nagited iti daytoy a panagilawlawag "\'\'$3\'\'"
 # Login and logout pages
 'logouttext' => "'''Nakaruarkan.'''
 
-Mabalinmo nga ituloy ti agusar iti {{SITENAME}} a di am-ammo, wenno [[Special:UserLogin|sumrek ka manen]] iti sigud wenno sabali nga agar-aramat.
+Mabalinmo nga ituloy ti agusar iti {{SITENAME}} a di am-ammo, wenno <span class='plainlinks'>[\$1 sumrek ka manen]</span> iti sigud wenno sabali nga agar-aramat.
 Laglagipem a sumagmamano a pampanid ti mabalin a nakaparang latta a kasla nakaserrekka pay laeng, aginggana no dalusam ti \"cache\" ti panagbasabasam.",
 'welcomecreation' => '== Kablaaw, $1! ==
 Naaramiden ti pakabilangam.
@@ -2873,7 +2873,6 @@ Daytoy ket mabalin a gapuanan babaen ti panilpo a naiparit ti akin ruar a pagsaa
 'pageinfo-authors' => 'Dagup a bilang dagiti naisangsangayn a mannurat',
 'pageinfo-recent-edits' => 'Itay nabiit a bilang dagiti inurnos (ti uneg ti napalabas ti $1)',
 'pageinfo-recent-authors' => 'Itay nabiit a bilang dagiti naisangsangayan a mannurat',
-'pageinfo-restriction' => 'Panagsalaknib ti panid ({{lcfirst:$1}})',
 'pageinfo-magic-words' => 'Salamangka  {{PLURAL:$1|a balikas|a balbalikas}} ($1)',
 'pageinfo-hidden-categories' => 'Nailemmeng {{PLURAL:$1|a kategoria|a katkategoria}} ($1)',
 'pageinfo-templates' => 'Nailak-am  {{PLURAL:$1|a plantilia|a planplantilia}} ($1)',
index 71218a7..ca0f058 100644 (file)
@@ -409,7 +409,7 @@ nekorekta interlinguale od interwikale ligilo.',
 # Login and logout pages
 'logouttext' => "'''Vu nun esas nun ek {{SITENAME}}.'''
 
-Vu povas durar uzante {{SITENAME}} anonimale, o vu povas [[Special:UserLogin|enirar itere]] kom la sama o diferenta uzanto.
+Vu povas durar uzante {{SITENAME}} anonimale, o vu povas <span class='plainlinks'>[$1 enirar itere]</span> kom la sama o diferenta uzanto.
 Atencez ke kelka pagini posible duras montresar semblante ke vu ne ekirus, til vu vakuigas la tempala-magazino di vua navigilo.",
 'welcomecreation' => '== Bonveno, $1! ==
 Vua konto kreesis.
index 36bfc40..2022be9 100644 (file)
@@ -637,7 +637,7 @@ Möppudýrið sem læsti skránni gaf þessa ástæðu: "\'\'$3\'\'".',
 # Login and logout pages
 'logouttext' => "'''Þú hefur verið skráð(ur) út.'''
 
-Þú getur haldið áfram að nota {{SITENAME}} óþekkt(ur), eða þú getur [[Special:UserLogin|skráð þig inn aftur]] sem sami eða annar notandi.
+Þú getur haldið áfram að nota {{SITENAME}} óþekkt(ur), eða þú getur <span class='plainlinks'>[$1 skráð þig inn aftur]</span> sem sami eða annar notandi.
 Athugaðu að sumar síður kunna að birtast líkt og þú sért ennþá skráð(ur) inn, þangað til að þú hreinsar skyndiminnið í vafranum þínum.",
 'welcomecreation' => '== Velkomin(n), $1! ==
 Aðgangurinn þinn hefur verið búinn til.
@@ -2114,7 +2114,7 @@ Studdar samskiptareglur: <code>$1</code> (ekki bæta neinum af þessum í leitin
 'emailpagetext' => 'Hafi notandi tilgreint netfang í stillingunum sínum er hægt að senda póst til hans hér.
 Póstfangið sem þú tilgreindir í [[Special:Preferences|stillingunum þínum]] birtist í "Frá:" hluta tölvupóstsins, svo að viðtakandi þess geti svarað beint til þín.',
 'usermailererror' => 'Póst hlutur skilaði villu:',
-'defemailsubject' => '{{SITENAME}} netfang notanda "$1"',
+'defemailsubject' => '{{SITENAME}} skilaboð frá notandanum "$1"',
 'usermaildisabled' => 'Netfang notenda er óvirkt',
 'usermaildisabledtext' => 'Þú getur ekki sent tölvupóst til annara notenda á þessum wiki',
 'noemailtitle' => 'Ekkert póstfang',
@@ -2188,7 +2188,7 @@ Frekari breytingar á henni eða spallsíðu hennar munu verða sýndar þar, og
   $1',
 'enotif_lastdiff' => 'Einnig getur þú heimsótt eftirfarandi tengil til að skoða þessa breytingu:
   $1',
-'enotif_anon_editor' => 'ónefndur notandi $1',
+'enotif_anon_editor' => 'ónefndum notanda $1',
 'enotif_body' => 'Kæri $WATCHINGUSERNAME,
 
 Það lítur út fyrir að þú hafir ný skilaboð á {{SITENAME}} síðunni $PAGETITLE.
@@ -2715,7 +2715,7 @@ Ef síðari möguleikinn á við getur þú einnig notað tengil, til dæmis
 'allmessagesdefault' => 'Sjálfgefinn skilaboða texti',
 'allmessagescurrent' => 'Núverandi texti',
 'allmessagestext' => 'Þetta er listi yfir kerfismeldingar í Melding-nafnrýminu.
-Gjörðu svo vel og heimsæktu [//www.mediawiki.org/wiki/Localisation MediaWiki-staðfæringuna] og [//translatewiki.net translatewiki.net] ef þú vilt taka þátt í almennri MediaWiki-staðfæringu.',
+Vinsamlegast heimsæktu [//www.mediawiki.org/wiki/Localisation MediaWiki-staðfæringuna] og [//translatewiki.net translatewiki.net] ef þú vilt taka þátt í almennri MediaWiki-staðfæringu.',
 'allmessagesnotsupportedDB' => "Það er ekki hægt að nota '''{{ns:special}}:Allmessages''' því '''\$wgUseDatabaseMessages''' hefur verið gerð óvirk.",
 'allmessages-filter-legend' => 'Sía',
 'allmessages-filter' => 'Sía með breytingarstöðu:',
index d6c12f6..817262b 100644 (file)
@@ -674,7 +674,7 @@ L\'amministratore che lo ha bloccato ha fornito questa motivazione: "$3".',
 # Login and logout pages
 'logouttext' => "'''Logout effettuato.'''
 
-Si può continuare ad usare {{SITENAME}} come utente anonimo oppure [[Special:UserLogin|eseguire un nuovo accesso]], con lo stesso nome utente o un nome diverso.
+Si può continuare ad usare {{SITENAME}} come utente anonimo oppure <span class='plainlinks'>[$1 eseguire un nuovo accesso]</span>, con lo stesso nome utente o un nome diverso.
 Nota che alcune pagine potrebbero continuare ad apparire come se il logout non fosse avvenuto finché non viene pulita la cache del proprio browser.",
 'welcomecreation' => "== Benvenuto, $1! ==
 
@@ -3016,10 +3016,10 @@ Tutte le operazioni di importazione trans-wiki sono registrate nel [[Special:Log
 'pageinfo-authors' => 'Numero totale di autori diversi',
 'pageinfo-recent-edits' => 'Numero di modifiche recenti (negli ultimi $1)',
 'pageinfo-recent-authors' => 'Numero di autori diversi recenti',
-'pageinfo-restriction' => 'Protezione della pagina ({{lcfirst:$1}})',
 'pageinfo-magic-words' => '{{PLURAL:$1|Parola magica|Parole magiche}} ($1)',
 'pageinfo-hidden-categories' => '{{PLURAL:$1|Categoria nascosta|Categorie nascoste}} ($1)',
 'pageinfo-templates' => 'Template {{PLURAL:$1|incluso|inclusi}}  ($1)',
+'pageinfo-toolboxlink' => 'Informazioni sulla pagina',
 
 # Patrolling
 'markaspatrolleddiff' => 'Segna la modifica come verificata',
@@ -3598,6 +3598,7 @@ Questo codice di conferma scadrà automaticamente alle $4.',
 # Scary transclusion
 'scarytranscludedisabled' => "[L'inclusione di pagine tra siti wiki non è attiva]",
 'scarytranscludefailed' => '[Errore: Impossibile ottenere il template $1]',
+'scarytranscludefailed-httpstatus' => '[Errore: impossibile ottenere il template $1: HTTP $2]',
 'scarytranscludetoolong' => '[Errore: URL troppo lunga]',
 
 # Delete conflict
index dcde476..37cf5f9 100644 (file)
@@ -494,7 +494,7 @@ $messages = array(
 
 'about' => '解説',
 'article' => '本文',
-'newwindow' => '(新しいウィンドウが開きます)',
+'newwindow' => '(新しいウィンドウで開きます)',
 'cancel' => '中止',
 'moredotdotdot' => '続き...',
 'mypage' => '自分のページ',
@@ -532,7 +532,7 @@ $messages = array(
 'variants' => '変種',
 
 'errorpagetitle' => 'エラー',
-'returnto' => '$1に戻る。',
+'returnto' => '$1 に戻る。',
 'tagline' => '提供:{{SITENAME}}',
 'help' => 'ヘルプ',
 'search' => '検索',
@@ -777,7 +777,7 @@ $2',
 # Login and logout pages
 'logouttext' => "'''ログアウトしました。'''
 
-このまま匿名で{{SITENAME}}の使用を続行できます。同じまたは別の利用者として[[Special:UserLogin|もう一度ログイン]]することもできます。
+このまま匿名で{{SITENAME}}の使用を続行できます。同じまたは別の利用者として<span class='plainlinks'>[$1 もう一度ログイン]</span>することもできます。
 なお、ページによっては、ブラウザーのキャッシュをクリアするまで、ログインしているかのように表示され続ける場合があるためご注意ください。",
 'welcomecreation' => '== ようこそ、$1 さん! ==
 アカウントが作成されました。
@@ -1050,11 +1050,11 @@ $1または他の[[{{MediaWiki:Grouppage-sysop}}|管理者]]にこのブロッ
 IP アドレスは複数の利用者で共有されている場合があります。
 もし、あなたが匿名利用者であり、自分に関係のないコメントが寄せられている考えられる場合は、[[Special:UserLogin/signup|アカウントを作成する]]か[[Special:UserLogin|ログインして]]他の匿名利用者と間違えられないようにしてください。''",
 'noarticletext' => '現在このページには内容がありません。
-ä»\96ã\81®ã\83\9aã\83¼ã\82¸å\86\85ã\81§[[Special:Search/{{PAGENAME}}|ã\81\93ã\81®ã\83\9aã\83¼ã\82¸å\90\8dã\82\92æ¤\9cç´¢]]ã\81\99ã\82\8bã\81\8bã\80\81
-<span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} é\96¢é\80£ã\81\99ã\82\8bè¨\98é\8c²ã\82\92æ¤\9cç´¢]ã\81\99ã\82\8bã\81\8bã\80\81
-[{{fullurl:{{FULLPAGENAME}}|action=edit}} このページを編集]</span>することができます。',
+他のページ内で[[Special:Search/{{PAGENAME}}|このページ名を検索]]、
+<span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} 関連する記録を検索]、
+または[{{fullurl:{{FULLPAGENAME}}|action=edit}} このページを編集]</span>できます。',
 'noarticletext-nopermission' => '現在このページには内容がありません。
-ä»\96ã\81®ã\83\9aã\83¼ã\82¸å\86\85ã\81§[[Special:Search/{{PAGENAME}}|ã\81\93ã\81®ã\83\9aã\83¼ã\82¸å\90\8dã\82\92æ¤\9cç´¢]]ã\81\99ã\82\8bã\81\8bã\80\81<span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} é\96¢é\80£è¨\98é\8c²ã\82\92æ¤\9cç´¢]</span>ã\81\99ã\82\8bã\81\93ã\81¨ã\81\8cできますが、あなたにはこのページを作成する権限はありません。',
+ä»\96ã\81®ã\83\9aã\83¼ã\82¸å\86\85ã\81§[[Special:Search/{{PAGENAME}}|ã\81\93ã\81®ã\83\9aã\83¼ã\82¸å\90\8dã\82\92æ¤\9cç´¢]]ã\80\81ã\81¾ã\81\9fã\81¯<span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} é\96¢é\80£ã\81\99ã\82\8bè¨\98é\8c²ã\82\92æ¤\9cç´¢]</span>できますが、あなたにはこのページを作成する権限はありません。',
 'missing-revision' => '「{{PAGENAME}}」というページの版番号 $1 の版は存在しません。
 
 通常、削除されたページの版への古い差分表示や固定リンクをたどった際に、このようなことが起きます。 
@@ -2659,7 +2659,7 @@ $2による直前の版へ変更されました。',
 'undeleteviewlink' => '閲覧',
 'undeletereset' => 'リセット',
 'undeleteinvert' => '選択を反転',
-'undeletecomment' => '理由',
+'undeletecomment' => '理由:',
 'undeletedrevisions' => '{{PLURAL:$1|$1版}}を復元しました',
 'undeletedrevisions-files' => '{{PLURAL:$1|$1版}}と{{PLURAL:$2|$2ファイル}}を復元しました',
 'undeletedfiles' => '{{PLURAL:$1|$1ファイル}}を復元しました',
@@ -2689,10 +2689,10 @@ $1',
 
 # Namespace form on various pages
 'namespace' => '名前空間:',
-'invert' => '選択したものを除く',
-'tooltip-invert' => '選択した名前空間(チェックされている場合は、関連付けられた名前空間も)のページの変更を非表示にするには、このボックスにチェックを入れる',
-'namespace_association' => '関連付けられた名前空間',
-'tooltip-namespace_association' => '選択した名前空間に関連付けられたトークページまたは対象の名前空間も含めるには、このボックスにチェックを入れる',
+'invert' => '名前空間の選択を反転',
+'tooltip-invert' => '選択した名前空間 (チェックを入れている場合は、関連付けられた名前空間も含む) のページの変更を非表示にするには、このボックスにチェックを入れる',
+'namespace_association' => '関連付けられた名前空間も含める',
+'tooltip-namespace_association' => '選択した名前空間に関連付けられたトークページ (逆にトークページの名前空間を選択した場合も同様) の名前空間も含めるには、このボックスにチェックを入れる',
 'blanknamespace' => '(標準)',
 
 # Contributions
@@ -2815,8 +2815,8 @@ $1',
 'expiringblock' => '$1$2に解除',
 'anononlyblock' => '匿名利用者のみ',
 'noautoblockblock' => '自動ブロック無効',
-'createaccountblock' => 'アカウント作成のブロック',
-'emailblock' => 'メール送信のブロック',
+'createaccountblock' => 'アカウント作成の禁止',
+'emailblock' => 'メール送信の禁止',
 'blocklist-nousertalk' => '自分のトークページの編集禁止',
 'ipblocklist-empty' => 'ブロック一覧は空です。',
 'ipblocklist-no-results' => '指定されたIPアドレスまたは利用者名はブロックされていません。',
@@ -3268,10 +3268,10 @@ MediaWiki 全般のローカライズ(地域化)に貢献したい場合は
 'pageinfo-authors' => '総投稿者数',
 'pageinfo-recent-edits' => '最近の編集回数 (過去 $1)',
 'pageinfo-recent-authors' => '最近の投稿者数',
-'pageinfo-restriction' => 'ページ保護 ({{lcfirst:$1}})',
 'pageinfo-magic-words' => 'マジック {{PLURAL:$1|ワード}} ($1)',
 'pageinfo-hidden-categories' => '隠し{{PLURAL:$1|カテゴリ}} ($1)',
 'pageinfo-templates' => '参照読み込みされた{{PLURAL:$1|テンプレート}} ($1)',
+'pageinfo-toolboxlink' => 'ページ情報',
 
 # Skin names
 'skinname-standard' => 'クラシック',
@@ -3917,6 +3917,7 @@ $5
 # Scary transclusion
 'scarytranscludedisabled' => '[ウィキ間の参照読み込みは無効になっています]',
 'scarytranscludefailed' => '[$1に対してテンプレートの取得に失敗しました]',
+'scarytranscludefailed-httpstatus' => '[$1に対してテンプレートの取得に失敗しました: HTTP $2]',
 'scarytranscludetoolong' => '[URLが長すぎます]',
 
 # Delete conflict
@@ -4254,7 +4255,7 @@ MediaWikiは、有用であることを期待して配布されていますが
 
 # Feedback
 'feedback-bugornote' => '技術的な問題の詳細を説明する準備ができている場合は、[$1 バグ報告]をお願いします。
-準備ができていない場合は、下の簡易フォームを使用してください。あなたのコメントと利用者名が、ページ"[$3 $2]"に追加されます。',
+準備ができていない場合は、下の簡易フォームを使用してください。あなたのコメントと利用者名が、ページ「[$3 $2]」に追加されます。',
 'feedback-subject' => '件名:',
 'feedback-message' => 'メッセージ:',
 'feedback-cancel' => 'キャンセル',
index 21202a4..5494817 100644 (file)
@@ -403,7 +403,7 @@ Di riizn dehn gi a "\'\'$2\'\'".',
 # Login and logout pages
 'logouttext' => "'''Yu nou lag out.'''
 
-Yu kiahn kantiniu yuuz {{SITENAME}} ananimosli, ar yu kiahn [[Special:UserLogin|lag iin agen]] az di siem ar az difrant yuuza.
+Yu kiahn kantiniu yuuz {{SITENAME}} ananimosli, ar yu kiahn <span class='plainlinks'>[$1 lag iin agen]</span> az di siem ar az difrant yuuza.
 Nuot se som piej maita kantiniu fi displie laik se yu stil log iin, antel yu klier yu brouza kiash.",
 'welcomecreation' => '== Welkom, $1! ==
 Yu akount don kriet.
index 1dfc1ce..8777427 100644 (file)
@@ -448,7 +448,7 @@ Pangurus sing ngopèni kuwi ngawedharaké: "$3".',
 # Login and logout pages
 'logouttext' => "'''Sampéyan wis metu log'''
 
-Sampéyan bisa nganggo {{SITENAME}} sacara anonim, utawa bisa [[Special:UserLogin|mlebu log manèh]] kanthi jeneng panganggo sing padha utawa beda.
+Sampéyan bisa nganggo {{SITENAME}} sacara anonim, utawa bisa <span class='plainlinks'>[$1 mlebu log manèh]</span> kanthi jeneng panganggo sing padha utawa beda.
 
 Cathet yèn sapérangan kaca mungkin isih nampilaké tulisan yèn Sampéyan isih nèng njero log, kuwi bisa ilang yèn Sampéyan ngresiki ''cache'' pramban Sampéyan.",
 'welcomecreation' => '== Sugeng rawuh, $1! ==
index 1b5c76e..fcf0715 100644 (file)
@@ -584,7 +584,7 @@ $2',
 'logouttext' => "'''თქვენ ამჟამად სისტემიდან გასული ხართ.'''
 
 შეგიძლიათ გამოიყენოთ {{SITENAME}} ანონიმურად, ან შეგიძლიათ
-[[Special:UserLogin|შეხვიდეთ ისევ]] როგორც იგივე ან სხვა მომხმარებელი.
+<span class='plainlinks'>[$1 შეხვიდეთ ისევ]</span> როგორც იგივე ან სხვა მომხმარებელი.
 შენიშნეთ, რომ ზოგიერთ გვერდზე შესაძლოა ისევ უჩვენებდეს რომ შესული ხართ სანამ თქვენი ბრაუზერის მეხსიერებას არ გაწმენდთ.",
 'welcomecreation' => '== მოგესალმებით, $1! ==
 თქვენი ანგარიში შექმნილია.
@@ -2968,7 +2968,6 @@ $1',
 'pageinfo-authors' => 'განსხვავებულ ავტორთა ჯამური რაოდენობა',
 'pageinfo-recent-edits' => 'ბოლო ცვლილებები (უკანასკნელი $1 განმავლობაში)',
 'pageinfo-recent-authors' => 'უნიკალური ავტორების უკანასკნელი რაოდენობა',
-'pageinfo-restriction' => 'გვერდის დაცვა ({{lcfirst:$1}})',
 'pageinfo-magic-words' => 'ჯადოსნური {{PLURAL:$1|სიტყვა|სიტყვა}} ($1)',
 'pageinfo-hidden-categories' => 'დამალული {{PLURAL:$1|კატეგორია|კატეგორია}} ($1)',
 'pageinfo-templates' => 'ინტეგრირებულია {{PLURAL:$1|თარგი|თარგი}} ($1)',
index 24ff914..6e8a6d4 100644 (file)
@@ -511,7 +511,7 @@ Keltirilgen sebep: ''$2''.",
 'logouttext' => "'''Siz endi sayttan shıqtın'ız.'''
 
 Siz {{SITENAME}} saytınan anonim halda paydalanıwın'ız mu'mkin.
-Yamasa siz ja'ne ha'zirgi yaki basqa paydalanıwshı atı menen [[Special:UserLogin|qaytadan sistemag'a kiriwin'izge]] boladı.
+Yamasa siz ja'ne ha'zirgi yaki basqa paydalanıwshı atı menen <span class='plainlinks'>[$1 qaytadan sistemag'a kiriwin'izge]</span> boladı.
 Sonı este saqlan', ayrım betler sizin' brauzerin'izdin' keshi tazalanbag'anlıg'ı sebebli sistemada kirgenin'izdey ko'riniste dawam ettire beriwi mu'mkin.",
 'welcomecreation' => "== Xosh keldin'iz, $1! ==
 
index 0c10a6f..155134d 100644 (file)
@@ -9,6 +9,7 @@
  *
  * @author Agurzil
  * @author Agzennay
+ * @author Amazigh84
  * @author Azwaw
  * @author Mmistmurt
  * @author MoubarikBelkasim
@@ -198,7 +199,7 @@ $messages = array(
 'vector-action-protect' => 'Mmesten',
 'vector-action-undelete' => 'Uɣaled',
 'vector-action-unprotect' => 'Beddel amesten',
-'vector-simplesearch-preference' => 'Sermed isumar n unadi i silɣen (i "Vector" kan)',
+'vector-simplesearch-preference' => 'Sermed tafeggast taḥerfit n unadi (i "Vector" kan)',
 'vector-view-create' => 'Snulfu',
 'vector-view-edit' => 'Ẓẓiẓreg',
 'vector-view-history' => 'Ẓeṛ amazray',
@@ -304,6 +305,10 @@ $1',
 'youhavenewmessages' => 'Ɣur-k $1 ($2).',
 'newmessageslink' => 'Izen amaynut',
 'newmessagesdifflink' => 'Abeddel aneggaru',
+'youhavenewmessagesfromusers' => 'Tesɛiḍ $1 n {{PLURAL:$3|useqdac nniḍen|$3 iseqdacen nniḍen}} ( $2 ).',
+'youhavenewmessagesmanyusers' => 'Tesɛiḍ $1 n aṭas n iseqdacen ($2).',
+'newmessageslinkplural' => '{{PLURAL:$1|izen amaynut|inzan imaynuten}}',
+'newmessagesdifflinkplural' => '{{PLURAL:$1|abeddel aneggaru|ibeddilen ineggura}}',
 'youhavenewmessagesmulti' => 'Tesɛiḍ iznan imaynuten deg $1',
 'editsection' => 'beddel',
 'editold' => 'beddel',
@@ -393,6 +398,8 @@ Ilaq ad εeggenem yiwen [[Special:ListUsers/sysop|anedbal]] war ad ttum asefkem
 'cannotdelete' => 'Ulamek ad yemḥu asebter naɣ afaylu « $1 ».
 Ahat amdan wayeḍ yemḥa-t.',
 'cannotdelete-title' => 'Ulamek an kkes  asebter « $1 »',
+'delete-hook-aborted' => 'Tukkesa tesemmet s usiɣzef.
+Ulac asefru ɣef wagi.',
 'badtitle' => 'Azwel ur yelhi',
 'badtitletext' => 'Asebter i testeqsiḍ fell-as mačči ṣaḥiḥ, d ilem, neɣ yella ugul deg wezday seg wikipedia s tutlayt tayeḍ neɣ deg wezday n wiki nniḍen. Ahat tesɛa asekkil ur yezmir ara ad yettuseqdac deg wezwel.',
 'perfcached' => 'Talɣut deg ukessar seg lkac u waqila mačči d tasiwelt taneggarut. A maximum of {{PLURAL:$1|one result is|$1 results are}} available in the cache.',
@@ -420,6 +427,13 @@ $2',
 'ns-specialprotected' => 'Ur t-zemred ara ad beddeleḍ isebtar usligen',
 'titleprotected' => "Azwel agi yegdel deg usnulfu ɣef [[User:$1|$1]].
 Taɣẓint id yenna : ''$2''",
+'filereadonlyerror' => 'Ulamek an beddel afaylu « $1 » acku akaram n ifuyla « $2 » yella deg taɣuri kan.
+
+Anedbal i tid sekkweṛen yefkad taɣẓint agi : « $3 ».',
+'invalidtitle-knownnamespace' => 'Azwel ur i ɣbel ara s tallunt n isemawen « $2 » dɣa d-uglam « $3 »',
+'invalidtitle-unknownnamespace' => 'Azwel ur i ɣbel ara s uṭṭun n tallunt n isemawen $1 dɣa d-uglam « $2 » warisem',
+'exception-nologin' => 'Ur tekcimeḍ ara',
+'exception-nologin-text' => 'I usebter agi naɣ i tigawt agi, ilaq ad qqeneḍ ɣef wiki agi.',
 
 # Virus scanner
 'virus-badscanner' => "Yir tawila : anafraḍ n infafaden warisem : ''$1''",
@@ -429,7 +443,7 @@ Taɣẓint id yenna : ''$2''",
 # Login and logout pages
 'logouttext' => "'''Tura tesensereḍ.'''
 
-Tzemreḍ ad tesseqdceḍ {{SITENAME}} d udrig, [[Special:UserLogin|ad tkecmeḍ daɣen]] s yisem n wemseqdac inek (neɣ nniḍen).
+Tzemreḍ ad tesseqdceḍ {{SITENAME}} d udrig, <span class='plainlinks'>[$1 ad tkecmeḍ daɣen]</span> s yisem n wemseqdac inek (neɣ nniḍen).
 Kra n isebtar zemren ad sskanen belli mazal-ik s yisem n wemseqdac inek armi temḥuḍ lkac.",
 'welcomecreation' => '== Anṣuf yisek (yisem), $1 ! ==
 
@@ -441,6 +455,7 @@ Ur tettuḍ ara ad tbeddleḍ [[Special:Preferences|isemyifiyen inek (inem) ɣef
 'remembermypassword' => 'Cfu ɣef wawal n tbaḍnit inu di uselkim-agi (i afellay n $1 {{PLURAL:$1|ass|ussan}})',
 'securelogin-stick-https' => 'Qqim uqqin s HTTPS sakin tuqqna',
 'yourdomainname' => 'Taɣult inek',
+'password-change-forbidden' => 'Ur zemreḍ ara ad beddeleḍ awalen n uɛaddi ɣef uwiki agi.',
 'externaldberror' => 'Yella ugul aberrani n database neɣ ur tettalaseḍ ara ad tbeddleḍ isem an wemseqdac aberrani inek.',
 'login' => 'Kcem',
 'nav-login-createaccount' => 'Kcem / Xleq isem n wemseqdac',
@@ -505,6 +520,7 @@ iwakken ad tbeyyneḍ belli tansa n email inek.',
 'emailconfirmlink' => 'Sentem tansa e-mail inek',
 'invalidemailaddress' => 'Tansa e-mail-agi ur telhi, ur tesɛi ara taseddast n lɛali. Ssekcem tansa e-mail s taseddast n lɛali neɣ ur tefkiḍ acemma.',
 'cannotchangeemail' => 'Ur t-zemreḍ ara ad beddeleḍ tansa e-mail deg uwiki agi.',
+'emaildisabled' => 'Asmel agi ur yezmer ara ad i cegaɛ e-mail.',
 'accountcreated' => 'Isem n wemseqdac yettwaxleq',
 'accountcreatedtext' => 'Isem n wemseqdac i $1 yettwaxleq.',
 'createaccount-title' => 'Asnulfu n umiḍan i {{SITENAME}}',
@@ -548,10 +564,31 @@ Ahat ilaq ad beddeleḍ awal ik/im n uɛaddi naɣ ad ssutereḍ awal n uɛaddi a
 'passwordreset-username' => 'Isem n useqdac',
 'passwordreset-domain' => 'Talɣut :',
 'passwordreset-capture' => 'Ẓeṛ tirawt ?',
+'passwordreset-capture-help' => 'Lukan ad tekkiḍ ɣef texxamt agi, tirawt (deg-es awal n uɛaddi akudan) att beqqeḍ dɣa ad tetwetceggaɛ i useqdac.',
 'passwordreset-email' => 'Tansa e-mail :',
+'passwordreset-emailtitle' => 'Tilɣa n umiḍan ɣef {{SITENAME}}',
+'passwordreset-emailtext-ip' => 'Yiwen (Ahat kečč/kem, seg tansa IP $1) yessutered asiwel n tilɣa n umiḍan inek/inem i {{SITENAME}} ($4). {{PLURAL:$3|Amiḍan n useqdac agi yedrew|imiḍanen n iseqdacen agi drewen}} s tansa e-mail agi :
+
+$2
+
+{{PLURAL:$3|Awal n uɛaddi agi ad i aff tasewti-s|Awalen n uɛaddi agi ad affen taseweti nsen}} deg {{PLURAL:$5|yiwen ass|$5 ussan}}. Ilaq tura ad qqeneḍ dɣa ad freneḍ awal n uɛaddi amaynut. Lukan mačči d kečč/kem i xedmen asuter agi, naɣ tecfiḍ tura i awal n uɛaddi inek/inem, tzemreḍ ad eǧǧeḍ izen agi.',
+'passwordreset-emailtext-user' => 'Aseqdac $1 ɣef {{SITENAME}} yessutered asiwel n tilɣa n umiḍan inek/inem i {{SITENAME}} ($4). {{PLURAL:$3|Amiḍan n useqdac agi yedrew|imiḍanen n iseqdacen agi drewen}} s tansa e-mail agi :
+
+$2
+
+{{PLURAL:$3|Awal n uɛaddi agi ad i aff tasewti-s|Awalen n uɛaddi agi ad affen taseweti nsen}} deg {{PLURAL:$5|yiwen ass|$5 ussan}}. Ilaq tura ad qqeneḍ dɣa ad freneḍ awal n uɛaddi amaynut. Lukan mačči d kečč/kem i xedmen asuter agi, naɣ tecfiḍ tura i awal n uɛaddi inek/inem, tzemreḍ ad eǧǧeḍ izen agi.',
+'passwordreset-emailelement' => 'Isem n useqdac : $1
+Awal n uɛddi akudan : $2',
+'passwordreset-emailsent' => 'Tirawt n usmekti tetwazen.',
+'passwordreset-emailsent-capture' => 'Tirawt n usmekti tetwazen, ẓeṛ-itt ddaw agi.',
+'passwordreset-emailerror-capture' => 'Tirawt n usmekti t-arewed, ẓeṛ-itt ddaw agi, lamaɛna azen yefkad anezri (tirawt ur tru ara) : $1',
 
 # Special:ChangeEmail
+'changeemail' => 'Beddel tansa n e-mail',
+'changeemail-header' => 'Beddel tansa n e-mail n umiḍan',
+'changeemail-text' => 'Ččur tiferkit agi iwakken ad beddeleḍ tansa e-mail inek/inem. Ilaq ad sekcemeḍ awal ik/im n uɛaddi iwakken ad sergegeḍ abeddel agi.',
 'changeemail-no-info' => 'Ilaq ad qqeneḍ iwakken ad ẓṛeḍ asebter agi.',
+'changeemail-oldemail' => 'Tansa e-mail n tura :',
 'changeemail-newemail' => 'Tansa e-mail tamaynut :',
 'changeemail-none' => '(ulac)',
 'changeemail-submit' => 'Beddel tansa e-mail',
@@ -588,6 +625,7 @@ Ahat ilaq ad beddeleḍ awal ik/im n uɛaddi naɣ ad ssutereḍ awal n uɛaddi a
 'showlivepreview' => 'Pre-timeẓriwt taǧiḥbuṭ',
 'showdiff' => 'Ssken ibeddlen',
 'anoneditwarning' => "'''Aɣtal:''' Ur tkecmiḍ ara. Tansa IP inek ad tettusmekti deg umezruy n usebter-agi.",
+'anonpreviewwarning' => "''Ur tesuluḍ ara. Aḥraz ad yekles tansa IP inek/inem deg umezruy n ibeddilen n usebter.''",
 'missingsummary' => "'''Ur tettuḍ ara:''' Ur tefkiḍ ara azwel i ubeddel inek. Lukan twekkiḍ ''Smekti'' tikelt nniḍen, abeddel inek ad yettusmekti mebla azwel.",
 'missingcommenttext' => 'Ssekcem awennit deg ukessar.',
 'missingcommentheader' => "'''Ur tettuḍ ara:''' Ur tefkiḍ ara azwel-azellum i ubeddel inek. Lukan twekkiḍ ''Smekti'' tikelt nniḍen, abeddel inek ad yettusmekti mebla azwel-azellum.",
@@ -621,6 +659,7 @@ Tzemreḍ ad tmeslayeḍ s $1 neɣ [[{{MediaWiki:Grouppage-sysop}}|anedbal]] nni
 Lukan ur tefkiḍ ara email saḥih deg [[Special:Preferences|isemyifiyen n wemseqdac]], ur tezmireḍ ara ad tazneḍ email.
 Tansa n IP inek n tura d $3, ID n uɛekkil d #$5.
 Smekti-ten u fka-ten i unedbal-nni.",
+'blockednoreason' => 'Ulac taɣẓint',
 'whitelistedittext' => 'Yessefk ad $1 iwakken ad tbeddleḍ isebtar.',
 'confirmedittext' => 'Yessefk ad tsentmeḍ tansa e-mail inek uqbel abeddel. Xtar tansa e-mail di [[Special:Preferences|isemyifiyen n wemseqdac]].',
 'nosuchsectiontitle' => 'Ulamek an af tigezmi',
@@ -629,21 +668,31 @@ Smekti-ten u fka-ten i unedbal-nni.",
 'loginreqlink' => 'Kcem',
 'loginreqpagetext' => 'Yessefk $1 iwakken ad teẓriḍ isebtar wiyaḍ.',
 'accmailtitle' => 'Awal n tbaḍnit yettwazen.',
-'accmailtext' => 'Awal n tbaḍnit n "$1" yettwazen ar $2.',
+'accmailtext' => "Awal n uɛaddi id yuran s ugacur i [[User talk:$1|$1]] yetwecgaɛ i $2.
+Awal n uɛaddi i umiḍan agi amaynut yezmer ad yetbeddel ɣef usebter n ''[[Special:ChangePassword|ubeddel n awal uɛddi]]'' sakin tuqqna.",
 'newarticle' => '(Amaynut)',
 'newarticletext' => 'Tḍefreḍ azday ɣer usebter mazal ur yettwaxleq ara.
 Akken ad txelqeḍ asebter-nni, aru deg tenkult i tella deg ukessar
 (ẓer [[{{MediaWiki:Helppage}}|asebter n tallat]] akken ad tessneḍ kter).
 Ma tɣelṭeḍ, wekki kan ɣef tqeffalt "Back/Précédent" n browser/explorateur inek.',
-'anontalkpagetext' => "----''Wagi d asebter n umyennan n wemseqdac adrig. Ihi, yessef ad as nefk ID, nesseqdac tansa IP ines akken a t-neɛqel. Tansa IP nni ahat tettuseqdac sɣur aṭṭas n yimdanen. Lukan ula d kečč aqla-k amseqdac adrig u ur tebɣiḍ ara ad tettwabcreḍ izen am wigini, ihi [[Special:UserLogin|xleq isem n wemseqdac neɣ kcem]].''",
+'anontalkpagetext' => "---- ''Wagi d asebter n umyennan n useqdac adrig, mazal ur d-yesnufa ara amiḍan. I taɣẓint agi, ilaq an seqdec tansa IP ines iwakken at-id n sulu. Yiwet tansa IP tezmer at tettuseqdac sɣur aṭṭas n iseqdacen. Lukan ula d kečč aqla-k amseqdac adrig dɣa ur tebɣiḍ ara ad tettwabcreḍ izen am wigini, ihi [[Special:UserLogin/signup|snulfud amiḍan]] naɣ [[Special:UserLogin|qqened]] iwakken sya d asawen ur t-illint ara uguren n usulu.''",
 'noarticletext' => 'Ulac aḍris deg usebter-agi, tzemreḍ ad [[Special:Search/{{PAGENAME}}|tnadiḍ ɣef wezwel n usebter-agi]] deg isebtar wiyaḍ neɣ [{{fullurl:{{FULLPAGENAME}}|action=edit}} tettbeddileḍ asebter-agi].',
 'noarticletext-nopermission' => 'Imira ulac aḍris deg usebter agi.
 Tzemreḍ [[Special:Search/{{PAGENAME}}|ad nadiḍ ɣef azwel agi]] deg isebtaren nniḍen,
 naɣ <span class="plainlinks">[{{fullurl:{{#Special:Log}}|asebter={{FULLPAGENAMEE}}}} ad nadiḍ deg iɣmisen iqqenen]</span>.',
+'missing-revision' => 'Tacaggart #$1 n usebter s isem « {{PAGENAME}} » ulac-itt.
+
+Acku azday n umezruy, ɣef wayen tsennedeḍ, d-aqbur. Asebter yemḥa.
+Tzemreḍ ad affeḍ tilɣa deg [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} uɣmis n isebtar yemḥan].',
+'userpage-userdoesnotexist' => 'Amiḍan n useqdac « <nowiki>$1</nowiki> » ur yekles ara. Ilaq ad selkeneḍ ma tebɣiḍ ad snulfuḍ asebter agi.',
+'userpage-userdoesnotexist-view' => 'Amiḍan n useqdac « $1 » ur yekles ara.',
+'blocked-notice-logextract' => 'Aseqdac agi yekyef.
+Asekcem aneggaru n useklas n ikyafen yella ddaw agi :',
 'clearyourcache' => "'''Tamawt:''' Beɛd asmekti, ahat yessefk ad temḥuḍ lkac n browser/explorateur inek akken teẓriḍ ibeddlen. '''Mozilla / Firefox / Safari:''' qqim twekkiḍ ''Shift'' u wekki ɣef ''Reload/Recharger'', neɣ wekki ɣef ''Ctrl-Shift-R'' (''Cmd-Shift-R'' deg Apple Mac); '''IE:''' qqim twekkiḍ ɣef ''Ctrl'' u wekki ɣef ''Refresh/Actualiser'', neɣ wekki ɣef ''Ctrl-F5''; '''Konqueror:''': wekki kan ɣef taqeffalt ''Reload'', neɣ wekki ɣef ''F5''; '''Opera''' yessefk ad tesseqdceḍ ''Tools→Preferences/Outils→Préférences'' akken ad temḥud akk lkac.",
-'usercssyoucanpreview' => "'''Tixidest:''' Sseqdec taqeffalt 'Ssken pre-timeẓriwt' iwakken ad tɛerḍeḍ CSS amynut inek uqbel ad tesmektiḍ.",
-'userjsyoucanpreview' => "'''Tixidest:''' Sseqdec taqeffalt 'Ssken pre-timeẓriwt' iwakken ad tɛerḍeḍ JS amynut inek uqbel ad tesmektiḍ.",
-'usercsspreview' => "'''Smekti belli aql-ak twaliḍ CSS inek kan, mazal ur yettusmekti ara!'''",
+'usercssyoucanpreview' => "'''taxbalut :''' Sseqdec taqeffalt « {{int:showpreview}} » iwakken ad tɛerḍeḍ asebter CSS inek/inem amaynut  uqbel ad aklasteḍ.",
+'userjsyoucanpreview' => "'''taxbalut :''' Sseqdec taqeffalt « {{int:showpreview}} » iwakken ad tɛerḍeḍ asebter JavaScript inek/inem amaynut  uqbel ad aklasteḍ.",
+'usercsspreview' => "'''Cfu-d, wagi d-azaraskan n usebter ik/im n CSS.'''
+'''Mmazal ur yettusmekti ara!'''",
 'userjspreview' => "'''Smekti belli aql-ak tɛerḍeḍ JavaScript inek kan, mazal ur yettusmekti ara!'''",
 'sitecsspreview' => "'''Smekti belli aql-ak tɛerḍeḍ asebter CSS agi inek kan.'''
 '''Mazal ur yettusmekti ara!'''",
@@ -656,17 +705,24 @@ naɣ <span class="plainlinks">[{{fullurl:{{#Special:Log}}|asebter={{FULLPAGENAME
 
 '''Cfut, ttagi d azar-timeẓriwt kan.'''
 Ibeddlen mazal ur ttusmektin ara!",
+'continue-editing' => 'Kemmel abeddel',
 'previewconflict' => 'Pre-timeẓriwt-agi tesskan aḍris i yellan deg usawen lemmer tebɣiḍ a tt-tesmektiḍ.',
 'session_fail_preview' => "'''Suref-aɣ! ur nezmir ara a nesmekti abeddil inek axaṭer yella ugur.
 G leɛnayek ɛreḍ tikelt nniḍen. Lukan mazal yella ugur, ffeɣ umbeɛd kcem.'''",
-'session_fail_preview_html' => "'''Suref-aɣ! ur nezmir ara a nesmekti abeddel inek axaṭer yella ugur.'''
+'session_fail_preview_html' => "'''Ur nezmer ara an aklas ibeddilen inek/inem acku yella asṛuḥu n tilɣa deg taɣimit inek/inem.'''
 
-''Awaṭer wiki-yagi teǧǧa HTML, teffer pre-timeẓriwt akken teǧǧanez antag n JavaScript.''
+''Acku {{SITENAME}} i sermed azar n HTML, azaraskan yeseggelmes iwakken ur t-illint ara tinṭagin s Javascript.''
 
-'''Lukan abeddel agi d aḥeqqani, g leɛnayek ɛreḍ tikelt nniḍen.. Lukan mazal yella ugur, ffeɣ umbeɛd kcem.'''",
+''' Lukan abeddel agi d-aḥeqqani, ɛered tikkelt nniḍen.'''
+Lukan yella ugur, [[Special:UserLogout|Senser]] dɣa qqen.",
+'token_suffix_mismatch' => "'''Abeddel inek/inem ur yeɣbel ara acku iminig inek/inem ur yesettengel ara s umellil isekkilen n uqqa deg asulay n ubeddel.'''
+Tiririt agi telaq i usḍiqqef n usgufsu n uḍris deg usebter.
+Ugur agi, yetilli tikwal mi seqdeceḍ aqeddac Proxy warisem yellan ɣef Web.",
+'edit_form_incomplete' => "'''Kra n iḥricen n tiferkit n ubeddel ur gweḍen ara ar uqeddac, ilaq ad selkeneḍ ma ibeddilen ur erẓen ara dɣa ɛreḍ tikkelt nniḍen.'''",
 'editing' => 'Abeddel n $1',
+'creating' => 'Asnulfu n $1',
 'editingsection' => 'Abeddel n $1 (amur)',
-'editingcomment' => 'Abeddel n $1 (awennit)',
+'editingcomment' => 'Abeddel n $1 (tigezmi tamaynut)',
 'editconflict' => 'Amennuɣ deg ubeddel: $1',
 'explainconflict' => "Amdan nniḍen ibeddel asebter-agi asmi telliḍ tettbeddileḍ.
 Aḍris deg usawen yesɛa asebter am yella tura.
@@ -685,23 +741,31 @@ Aqlak teggaleḍ belli tureḍ wagi d kečč, neɣ teddmiḍ-t seg taɣult azaye
 'copyrightwarning2' => "Ssen belli akk tikkin deg {{SITENAME}} zemren ad ttubeddlen neɣ ttumḥan sɣur imdanen wiyaḍ. Lukan ur tebɣiḍ ara aru inek yettubeddel neɣ yettwazen u yettwaru deg imkanen nniḍen, ihi ur t-tazneḍ ara dagi.<br />
 Aqlak teggaleḍ belli tureḍ wagi d kečč, neɣ teddmiḍ-t seg taɣult azayez neɣ iɣbula tilelliyin (ẓer $1 akken ad tessneḍ kter).
 '''UR TEFKIḌ ARA AXDAM S COPYRIGHT MEBLA TURAGT!'''",
-'longpageerror' => "'''AGUL: Aḍris i tefkiḍ yesɛa $1 kB/ko, tiddi-yagi kter n $2 kB/ko, ur yezmir ara ad yesmekti.'''",
-'readonlywarning' => "'''AƔTAL: Database d tamsekker akken ad teddwaxdem,
-ihi ur tezmireḍ ara ad tesmektiḍ ibeddlen inek tura. Smekti aḍris inek
-deg afaylu nniḍen akken tesseqdceḍ-it umbeɛd.'''",
-'protectedpagewarning' => "'''AƔTAL:  Asebter-agi yettwaḥrez, ala inedbalen zemren a t-beddlen'''",
-'semiprotectedpagewarning' => "'''Tamawt:''' Asebter-agi yettwaḥrez, ala imseqdacen i yesɛan isem n wemseqdac zemren a t-beddlen.",
-'cascadeprotectedwarning' => "'''Aɣtal:''' Asebter-agi iɛekkel iwakken ad zemren ala inedbalen a t-beddlen, axaṭer yettwassekcem deg isebtar i yettwaḥerzen agi (acercur):",
+'longpageerror' => "'''Anezri : Aḍris i sekcemeḍ yeɛbeṛ {{PLURAL:$1|yiwen kilobyte|$1 kilobytes}}, tiddi-yagi kter n talast yellan af {{PLURAL:$2|yiwen kilobyte|$1 kilobytes}}.'''
+Ur yezmer ara ad yetwaḥrez.",
+'readonlywarning' => "'''ƔUR-WET : taffa n isefka t-sekkweṛ i timhelin n ibeddi. Ur tzemreḍ ara ad ḥrezeḍ  ibeddilen tura.'''
+Tzemreḍ ad nɣeleḍ aḍris ik/im deg ufaylu iwakken ad tesqedceḍ sakin.
+
+Anedbal i sekkweṛen taffa n isefka agi, yefka-d taɣẓint agi : $1",
+'protectedpagewarning' => "'''ƔUR-WET : Asebter-agi yettwaḥrez, inedbalen kan i zemren a t-beddlen.'''
+Asekcem aneggaru n uɣmis yella ddaw-agi :",
+'semiprotectedpagewarning' => "'''Tamawt :''' Asebter-agi yettwaḥrez, iseqdacen yesɛan amiḍan kan i zemren a t-beddlen.
+Asekcem aneggaru n uɣmis yella ddaw-agi :",
+'cascadeprotectedwarning' => "'''ƔUR-WET :''' Asebter-agi yettwaḥrez, inedbalen kan i zemren a t-beddlen. Yettwaḥrez acku yettwassekcem  deg {{PLURAL:$1|asebter i ḥerzen agi yesɛan|isebtar i ḥerzen agi yesɛan}} « amesten s uceṛcuṛ » i sermeden :",
+'titleprotectedwarning' => "'''ƔUR-WET : Asebter agi yemesten, dɣa ilaq ad sɛuḍ [[Special:ListGroupRights|izerfan usligen]] iwakken at id snulfuḍ.''' Asekcem aneggaru n uɣmis yebeqqeḍ ddaw agi :",
 'templatesused' => '{{PLURAL:$1|Talɣa i seqdacen|Tilɣatin i seqdacen}} deg usebter agi :',
-'templatesusedpreview' => 'Talɣiwin ttuseqdacen deg pre-timeẓriwt-agi:',
+'templatesusedpreview' => '{{PLURAL:$1|Talɣa i seqdacen|Tilɣatin i seqdacen}} deg azaraskan agi :',
 'templatesusedsection' => '{{PLURAL:$1|Talɣa i seqdacen|Tilɣatin i seqdacen}} deg tigezmi agi :',
 'template-protected' => '(yettwaḥrez)',
 'template-semiprotected' => '(nnefṣ-yettwaḥrez)',
 'hiddencategories' => 'Asebter agi yella deg {{PLURAL:$1|Taggayt i ffren|Tiggayin i ffren}} agi :',
 'edittools' => '<!-- Aḍris yettbanen-d seddaw talɣa n ubeddil d uzen. -->',
 'nocreatetitle' => 'Axleq n isebtar meḥdud',
-'nocreatetext' => 'Adeg n internet agi iḥedded axleq n isebtar imaynuten.
-Tzemreḍ a d-uɣaleḍ u tbeddleḍ asebter i yellan, neɣ ad [[Special:UserLogin|tkecmeḍ neɣ ad txelqeḍ isem n wemseqdac]].',
+'nocreatetext' => '{{SITENAME}} yekref iẓubaẓ n usnulfu n isebtar imaynuten.
+Tzemreḍ ad uɣaleḍ ar deffir dɣa ad beddeleḍ asebter yellan yakan, naɣ [[Special:UserLogin|ad qqeneḍ naɣ ad snulfuḍ amiḍan]].',
+'nocreate-loggedin' => 'Ur tesɛiḍ ara turagt i usnulfu n isebtar imaynuten.',
+'sectioneditnotsupported-title' => 'Abeddel n tigezmi agi ur yezmer ara',
+'sectioneditnotsupported-text' => 'Abeddel n tigezmi ur yezmer ara deg usebtar agi n ubeddel.',
 'permissionserrors' => 'Anezri n turagt',
 'permissionserrorstext' => 'Ur tesɛiḍ ara turagt iwakken ad xedmeḍ wayagi i {{PLURAL:$1|taɣẓint|tiɣẓinin}} agi :',
 'permissionserrorstext-withaction' => 'Ur sɛiḍ ara ttesriḥ af $2, i {{PLURAL:$1|taɣẓint|tiɣẓinin}} agi :',
@@ -709,20 +773,47 @@ Tzemreḍ a d-uɣaleḍ u tbeddleḍ asebter i yellan, neɣ ad [[Special:UserLog
 
 Ilaq ad snulfum asebter agi haca ma i xater. Aɣmis n isebtaren i twekkesen yella ddaw-agi :",
 'moveddeleted-notice' => 'Asebter agi yetwekkes. Aɣmis n isebtaren i twekkesen yella ddaw agi.',
+'log-fulllog' => 'Ẓeṛ aɣmis ummid',
+'edit-hook-aborted' => 'Abrir n ubeddel s usiɣzef.
+Tamentilt warisem',
+'edit-gone-missing' => 'Ur yezmer ara ad yemucceḍ asebter agi.
+Ahat yetwemḥa.',
+'edit-conflict' => 'Amgirred n ubeddel.',
+'edit-no-change' => 'Abeddel inek/inem ur yetwexdam ara acku ur di ban ara abeddel deg uḍris.',
+'edit-already-exists' => 'Asebter amaynut ur d yesnufu ara.
+Yella yakan.',
+'defaultmessagetext' => 'Izen s lexṣas',
 
 # Parser/template warnings
+'expensive-parserfunction-warning' => "'''Ɣur-wet :''' Asebter agi yesɛa aṭas n tiɣriwin ar tiseɣnin ɣlayen n umsisleḍ taseddast.
+Ilaq ad i sɛu ddaw n  $2 {{PLURAL:$2|tiɣri|tiɣriwin }}, wannag tura {{PLURAL:$1|tella $1 tiɣri|llant $1 tiɣriwin}}.",
+'expensive-parserfunction-category' => 'Isebtar yesɛan aṭas tiɣriwin ɣlayen n tiseeɣnin n umsisleḍ taseddast',
 'post-expand-template-inclusion-warning' => 'Ɣur-wet : Asebter agi yesɛa aṭas tilɣatin. Kra n tilɣatin ur zemrent ara ad seqdacent.',
 'post-expand-template-inclusion-category' => 'Isebtaren i sɛan aṭas tilɣatin',
 'post-expand-template-argument-warning' => "'''Ɣur-wet''' : Asebter agi yesɛa tuccḍa deg aɣewwar n yiwet talɣa.",
 'post-expand-template-argument-category' => 'Isebtaren i sɛan iɣewwaren n talɣa ur skazelen ara',
+'parser-template-loop-warning' => 'N-ufad talɣa s tineddict : [[$1]]',
+'parser-template-recursion-depth-warning' => 'Talast n lqay n tiɣriwin n tilɣatin tefel ($1)',
+'language-converter-depth-warning' => 'Talast n lqay n uselkat n tutlayt tefel ($1)',
+'node-count-exceeded-category' => 'Isebtar anda amḍa n tikerwas yefel',
+'node-count-exceeded-warning' => 'Asebter yefelen amḍan n tikerwas',
+'expansion-depth-exceeded-category' => 'Isebtar anda lqay n uderrec yefel',
+'expansion-depth-exceeded-warning' => 'Isebtar yefelen lqay n uderrec',
+'parser-unstrip-loop-warning' => 'Tifin n tineddict ur nezmer ara an sentuter',
+'parser-unstrip-recursion-limit' => 'Talast n usniles ur nezmer ara an sentuter tefel ($1)',
+'converter-manual-rule-error' => 'Tifin n unezri deg alugen awfus n uselket n tutlayt',
 
 # "Undo" feature
 'undo-success' => 'Tzemreḍ ad tessefsuḍ abeddil. Ssenqed asidmer akken ad tessneḍ ayen tebɣiḍ ad txdmeḍ d ṣṣeḥ, umbeɛd smekti ibeddlen u tkemmleḍ ad tessefsuḍ abeddil.',
 'undo-failure' => 'Ur yezmir ara ad issefu abeddel axaṭer yella amennuɣ abusari deg ubeddel.',
+'undo-norev' => 'Abeddel ur yezmer ara ad yetwekkes acku ulac-itt naɣ tetwekkes yakan',
 'undo-summary' => 'Ssefsu tasiwelt $1 sɣur [[Special:Contributions/$2|$2]] ([[User talk:$2|Meslay]])',
 
 # Account creation failure
 'cantcreateaccounttitle' => 'Ur yezmir ara ad yexleq isem n wemseqdac',
+'cantcreateaccount-text' => "Asnulfu n umiḍan seg tansa IP (<b>$1</b>) tekyef sɣur [[User:$3|$3]].
+
+Taɣẓint n $3 : ''$2''",
 
 # History pages
 'viewpagelogs' => 'Ẓer aɣmis n usebter-agi',
@@ -761,6 +852,7 @@ Ahat yettumḥa neɣ yettbeddel isem-is.
 'rev-deleted-comment' => '(agzul n taẓrigt yettwakes)',
 'rev-deleted-user' => '(isem n wemseqdac yettwakes)',
 'rev-deleted-event' => '(asekcem yettwakkes)',
+'rev-deleted-user-contribs' => '[isem n useqdac naɣ tansa IP yetwemḥa - abeddel yeffer deg tiwsitin]',
 'rev-deleted-text-permission' => "Lqem n usebter agi '''tetwesfeḍ'''.
 Tilɣa llant deg [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} uɣmis n usfeḍ].",
 'rev-deleted-text-unhide' => "Lqem n usebter agi '''tetwesfeḍ'''.
@@ -791,11 +883,21 @@ Tzemreḍ att ẓṛeḍ ; tilɣa llant deg [{{fullurl:{{#Special:Log}}/delete|p
 'revisiondelete' => 'Mḥu/kkes amḥay tisiwal',
 'revdelete-nooldid-title' => 'Lqem asaḍas ur i ɣbel ara',
 'revdelete-nooldid-text' => 'Ur textareḍ ara lqem nnican akken ad txedmeḍ tawuri fell-as.',
+'revdelete-nologtype-title' => 'Ulac tawsit n uɣmis',
+'revdelete-nologtype-text' => 'Ur d ssefruḍ ara tawsit n uɣmis ɣef anwa tigawt agi ad tetwexdam.',
+'revdelete-nologid-title' => 'Asekcem n uɣmis ur i ɣbel ara',
+'revdelete-nologid-text' => 'Ur d ssefruḍ ara asekcem n uɣmis ɣef anwa tigawt agi ilaq ad tetwexdam, naɣ tadyant agi ur tella ara.',
+'revdelete-no-file' => 'Afaylu id ssefruḍ ur yella ara.',
+'revdelete-show-file-confirm' => 'Tebɣriḍ ad mḥuḍ tacaggart n ufaylu « <nowiki>$1</nowiki> » n $2 af $3 ?',
 'revdelete-show-file-submit' => 'Ih',
 'revdelete-selected' => "'''{{PLURAL:$2|Tasiwelt tettwafren|Tisiwal ttwafernen}} n [[:$1]]'''",
 'logdelete-selected' => "'''{{PLURAL:$1|Tamirt n uɣmis tettwafren|Isallen n uɣmis ttwafernen}}:'''",
 'revdelete-text' => 'Ileqman d tidyanin yettumḥan ad qqimen deg umezruy n usebter dɣa deg iɣmisen, maca agbur nsen ur i sɛu ara tuffart i uzayez."
 Inedbalen wiyaḍ deg {{SITENAME}} zemren ad ẓṛen imuren i yettwafren u zemren a ten-mḥan, ḥaca ma llan icekkilen.',
+'revdelete-confirm' => 'Sergeg ma tebɣiḍ ad xedmeḍ tigawt agi, fehmeḍ inalkamen, dɣa temtawiḍ s [[{{MediaWiki:Policy-url}}|ilugan]].',
+'revdelete-suppress-text' => "Ilaq tukksa at illi kan deg tijṛa agi :
+* tilɣa n yiwen ur ezgan ara
+*: ''tansa, uṭṭun n tilifun, uṭṭun n taɣellist tamettit, …''",
 'revdelete-legend' => 'Sbebd akref n tamuɣli',
 'revdelete-hide-text' => 'Ffer aḍris n tsiwelt',
 'revdelete-hide-image' => 'Ffer ayen yellan deg ufaylu',
@@ -803,6 +905,7 @@ Inedbalen wiyaḍ deg {{SITENAME}} zemren ad ẓṛen imuren i yettwafren u zemr
 'revdelete-hide-comment' => 'Ffer abeddel n uwennit',
 'revdelete-hide-user' => 'Ffer Isem n wemseqdac/IP n umeskar',
 'revdelete-hide-restricted' => 'Mḥu isefka agi i inedbalen d yimdanen wiyaḍ',
+'revdelete-radio-same' => '(ur beddel ara)',
 'revdelete-radio-set' => 'Ih',
 'revdelete-radio-unset' => 'Ala',
 'revdelete-suppress' => 'Kkes talɣut seg inedbalen d yimdanen wiyaḍ',
@@ -810,20 +913,63 @@ Inedbalen wiyaḍ deg {{SITENAME}} zemren ad ẓṛen imuren i yettwafren u zemr
 'revdelete-log' => 'Ayɣer',
 'revdelete-submit' => 'Snes {{PLURAL:$1|i tacaggart i tettwafren|i ticggarin i tettwafren}}',
 'revdelete-success' => "''Asekkud n ileqman yemucce war uguren.'''",
+'revdelete-failure' => "'''Iẓṛi n lqem ur yemucceḍ ara :'''
+$1",
 'logdelete-success' => "'''Asekkud n tamirt yettuxdem.'''",
+'logdelete-failure' => "'''Iẓṛi n uɣmis ur yezmer ara ad yesbadu :'''
+$1",
 'revdel-restore' => 'beddel timezrit',
 'revdel-restore-deleted' => 'allas iqḍeεen',
 'revdel-restore-visible' => 'allas i nezmer an ẓeṛ',
 'pagehist' => 'Amezruy n usebter',
+'deletedhist' => 'Amezruy yemḥa',
+'revdelete-hide-current' => 'Yella anezri imi nemḥa aferdis yezemzen ass n $1 af $2 : d lqem aneggaru.
+Ur yezmer ara ad yemḥu.',
+'revdelete-show-no-access' => 'Yella anezri imi n beqqeḍ aferdis yezemzen ass n $1 af $2 : yecreḍ am "ukrif".
+Ur tesɛiḍ ara izerfan n wadduf.',
+'revdelete-modify-no-access' => 'Yella anezri imi nebeddel aferdis yezemzen ass n $1 af $2 : yecreḍ am "ukrif".
+Ur tesɛiḍ ara izerfan n wadduf.',
+'revdelete-modify-missing' => 'Yella anezri imi nebeddel aferdis yesɛan ID $1 : Ulac-it deg taffa n isefka !',
+'revdelete-no-change' => "'''Ɣur-wet :''' Aferdis yezemzen ass n $1 af $2 yesɛa yakan iɣewwaren n iẓṛi i tebɣiḍ.",
+'revdelete-concurrent-change' => 'Yella anezri imi nebeddel aferdis yezemzen ass n $1 af $2 : aẓayeris yetwebeddel sɣur amḍan nniḍen mi tbeddeleḍ
+Ẓeṛ iɣmisen.',
+'revdelete-only-restricted' => 'Yella anezri imi nemḥa asekcem yezemzen ass n $1 af $2 : ur tzemreḍ ara ad mḥuḍ iferdisen agi i inedbalen war ad fruḍ tixtiṛiyin nniḍen n umḥu.',
+'revdelete-reason-dropdown' => 'Tiɣẓinin timiranin n umḥu :
+** Akukel n izerfan umeskar (copyright) ;
+** Iwenniten naɣ tilɣa n yiwen ur yezgan ara ;
+** Tilɣa i zemren ad rgemen.',
+'revdelete-otherreason' => 'Taɣẓint nniḍen / taɣzint tamarnant :',
+'revdelete-reasonotherlist' => 'Taɣẓint nniḍen',
+'revdelete-edit-reasonlist' => 'Beddel tiɣẓinin n umḥu i-d-yettuɣalen',
+'revdelete-offender' => 'Ameskar n tacaggart :',
+
+# Suppression log
+'suppressionlog' => 'Aɣmis n isfaḍen',
 
 # History merging
+'mergehistory' => 'Zdi amezruy n isebtar',
+'mergehistory-box' => 'Zdi lqem n sin isebtar',
+'mergehistory-from' => 'Azar n usebter :',
+'mergehistory-into' => 'Aserken n usebter :',
+'mergehistory-list' => 'Amezruy n ibeddilen i nezmer an zdi',
+'mergehistory-go' => 'Ẓeṛ ibeddilen i nezmer an zdi',
+'mergehistory-submit' => 'Azday n ileqman',
+'mergehistory-empty' => 'Ulac lqem i nezmer an zdi.',
+'mergehistory-no-source' => 'Azar n usebter $1 ulac-it.',
+'mergehistory-no-destination' => 'Aserken n usebter $1 ulac-it',
+'mergehistory-invalid-source' => 'Azar n usebter ilaq ad i sɛu azwel i ɣbelen.',
+'mergehistory-invalid-destination' => 'Aserken n usebter ilaq ad i sɛu azwel i ɣbelen.',
 'mergehistory-reason' => 'Ayɣer',
 
 # Merge log
+'mergelog' => 'Aɣmis n izdayen',
 'revertmerge' => 'Fru',
 
 # Diffs
 'history-title' => 'Tiẓṛi tiss sint umezruy n "$1"',
+'difference-title' => '$1 : Tameẓla gar ileqman',
+'difference-title-multipage' => 'Timeẓliwin gar isebtar « $1 » d « $2 »',
+'difference-multipage' => '(Tameẓla gar isebtar)',
 'lineno' => 'Ajerriḍ $1:',
 'compareselectedversions' => 'Ẓer imgerraden ger tisiwal i textareḍ',
 'editundo' => 'ssefsu',
@@ -833,7 +979,7 @@ Inedbalen wiyaḍ deg {{SITENAME}} zemren ad ẓṛen imuren i yettwafren u zemr
 'searchresults' => 'Igmad n unadi',
 'searchresults-title' => 'Igmad n unadi i "$1"',
 'searchresulttext' => 'Akken ad tessneḍ amek ara tnadiḍ deg {{SITENAME}}, ẓer [[{{MediaWiki:Helppage}}|{{int:help}}]].',
-'searchsubtitle' => "Tnadiḍ ɣef '''[[:$1]]'''",
+'searchsubtitle' => "Tnudaḍ « '''[[:$1]]''' » ([[Special:Prefixindex/$1|akkw isebtar i zwiren s « $1 »]]{{int:pipe-separator}}[[Special:WhatLinksHere/$1|Akkw isebtar yesɛan azday ɣer « $1 »]])",
 'searchsubtitleinvalid' => "Tnadiḍ ɣef '''$1'''",
 'titlematches' => 'Ayen yecban azwel n umegrad',
 'notitlematches' => 'Ulac ayen yecban azwel n umegrad',
@@ -845,9 +991,11 @@ Inedbalen wiyaḍ deg {{SITENAME}} zemren ad ẓṛen imuren i yettwafren u zemr
 'nextn-title' => '$1 {{PLURAL:$1|agmud n sakin|igmad n sakin}}',
 'shown-title' => 'Beqqeḍ $1 {{PLURAL:$1|agmud|igmad}} s usebter',
 'viewprevnext' => 'Ẓer ($1 {{int:pipe-separator}} $2) ($3).',
+'searchmenu-legend' => 'Tixtiṛiyin n unadi',
 'searchmenu-exists' => "'''Yella asebter s isem \"[[:\$1]]\" deg wiki agi.'''",
 'searchmenu-new' => "'''Snulfud asebter « [[:$1|$1]] » deg wiki agi !'''",
 'searchhelp-url' => 'Help:Agbur',
+'searchmenu-prefix' => '[[Special:PrefixIndex/$1|Nadi isebtar i zwaren s adat agi]]',
 'searchprofile-articles' => 'Isebtaren n ugbur',
 'searchprofile-project' => 'Isebtaren n tallat dɣa n usenfa',
 'searchprofile-images' => 'Agetmedia',
@@ -863,59 +1011,89 @@ Inedbalen wiyaḍ deg {{SITENAME}} zemren ad ẓṛen imuren i yettwafren u zemr
 'search-redirect' => '(asemmimeḍ $1)',
 'search-section' => '(tigezmi $1)',
 'search-suggest' => 'D awal $1 i tnadiḍ ?',
+'search-interwiki-caption' => 'Isenfaren atmaten',
+'search-interwiki-default' => 'Igemmaḍ ɣef $1 :',
+'search-interwiki-more' => '(ugar)',
 'search-relatedarticle' => 'Amassaɣ',
+'mwsuggest-disable' => 'Ssens isumar n AJAX',
+'searcheverything-enable' => 'Nadi deg akkw tallunin n isemawen',
 'searchrelated' => 'ineqqes',
 'searchall' => 'akk',
 'showingresults' => "Tamuli n {{PLURAL:$1|'''Yiwen''' wegmud|'''$1''' n yigmad}} seg  #'''$2'''.",
 'showingresultsnum' => "Tamuli n {{PLURAL:$3|'''Yiwen''' wegmud|'''$3''' n yigmad}} seg  #'''$2'''.",
 'showingresultsheader' => "{{PLURAL:$5|Agmud '''$1'''|Igmad '''$1–$2'''}} n '''$3''' i '''$4'''",
-'nonefound' => "'''Tamawt''': S umata, asmi ur tufiḍ acemma
-d ilmen awalen am \"ala\" and \"seg\",
-awalen-agi mačči deg tasmult, neɣ tefkiḍ kter n yiwen n wawal (ala isebtar
-i yesɛan akk awalen i banen-d).",
+'nonefound' => "'''Tamawt''' : S lexṣas, ala kra n tallunin n isemawen t-seqdacen i unadi.
+Ɛreḍ s udat ''all:'' i unadi deg akkw ugbur (ula d-isebtar n umeslay, talɣiwin, ...) naɣ seqdec tallunt n isemawen i tebɣiḍ am adat.",
 'search-nonefound' => 'Ulac igmad i usuter agi.',
-'powersearch' => 'Nadi',
+'powersearch' => 'Anadi amahlan',
+'powersearch-legend' => 'Anadi amahlan',
+'powersearch-ns' => 'Nadi deg tallunin n isemawen',
+'powersearch-redir' => 'Beqqeḍ alsinamuden',
+'powersearch-field' => 'Nadi',
+'powersearch-togglelabel' => 'Ɛellem :',
+'powersearch-toggleall' => 'Akkw',
+'powersearch-togglenone' => 'Ulac',
+'search-external' => 'Anadi yeffɣen',
 'searchdisabled' => 'Anadi deg {{SITENAME}} yettwakkes. Tzemreḍ ad tnadiḍ s Google. Meɛna ur tettuḍ ara, tasmult n google taqdimt.',
 
 # Quickbar
 'qbsettings' => 'Tanuga taǧiḥbuṭ',
-'qbsettings-none' => 'Ulaḥedd',
+'qbsettings-none' => 'Ulac',
 'qbsettings-fixedleft' => 'Aẓelmaḍ',
 'qbsettings-fixedright' => 'Ayeffus',
 'qbsettings-floatingleft' => 'Tufeg aẓelmaḍ',
 'qbsettings-floatingright' => 'Tufeg ayeffus',
+'qbsettings-directionality' => 'Usbiḍ, ɣef wayen n unamud n tira n tutlayt ik/im',
 
 # Preferences page
 'preferences' => 'Isemyifiyen',
 'mypreferences' => 'Isemyifiyen inu',
+'prefs-edits' => 'Amḍan n ibeddlilen :',
 'prefsnologin' => 'Ur tekcimeḍ ara',
-'prefsnologintext' => 'Yessefk ad [[Special:UserLogin|tkecmeḍ]] iwakken textareḍ isemyifiyen inek.',
+'prefsnologintext' => 'Ilaq ad iliḍ <span class="plainlinks">[{{fullurl:{{#Special:UserLogin}}|returnto=$1}} qqeneḍ]</span> iwakken ad beddeleḍ iɣewwaren ik/im n useqdac.',
 'changepassword' => 'Beddel awal n tbaḍnit',
 'prefs-skin' => 'Aglim',
 'skin-preview' => 'Pre-timeẓriwt',
 'datedefault' => 'Ur sɛiɣ ara asemyifi',
+'prefs-beta' => 'Tiseɣnin bêta',
 'prefs-datetime' => 'Azemz d ukud',
+'prefs-labs' => 'Tiseɣnin « labs »',
+'prefs-user-pages' => 'Isebtar n useqdac',
 'prefs-personal' => 'Profile n wemseqdac',
 'prefs-rc' => 'Ibeddlen imaynuten',
 'prefs-watchlist' => 'Umuɣ n uɛessi',
-'prefs-watchlist-days' => 'Geddac n wussan yessefk ad banen deg wumuɣ n uɛessi:',
+'prefs-watchlist-days' => 'Amḍan n ussan i ubeqqeḍ deg umuɣ n uɛassi :',
+'prefs-watchlist-days-max' => 'Afellay $1 {{PLURAL:$1|ass|ussan}}',
 'prefs-watchlist-edits' => 'Geddac n yibeddlen yessefk ad banen deg wumuɣ n uɛessi ameqqran:',
+'prefs-watchlist-edits-max' => 'Amḍan afellay : 1000',
+'prefs-watchlist-token' => 'Tiddest  umuɣ n uɛassi :',
 'prefs-misc' => 'Isemyifiyen wiyaḍ',
 'prefs-resetpass' => 'Beddel awal n uɛaddi',
+'prefs-changeemail' => 'Beddel tansa n e-mail',
+'prefs-setemail' => 'Sbadu yiwet tansa e-mail',
+'prefs-email' => 'Tixtiṛiyin n tira',
+'prefs-rendering' => 'Tummant',
 'saveprefs' => 'Smekti',
-'resetprefs' => 'Reset/réinitialiser isemyifiyen',
+'resetprefs' => 'Asfeḍ n ibeddilen ur ḥrezen ara',
+'restoreprefs' => 'Err akkw azalen s lexṣas',
 'prefs-editing' => 'Abedddil',
+'prefs-edit-boxsize' => 'Lqedd n usfaylu n ubeddel.',
 'rows' => 'Ijerriḍen:',
 'columns' => 'Tigejda:',
 'searchresultshead' => 'Anadi',
 'resultsperpage' => 'Geddac n tiririyin i mkul asebter:',
-'recentchangescount' => 'Geddac n izwal deg ibeddilen imaynuten:',
+'stub-threshold-disabled' => 'Yensa',
+'recentchangesdays-max' => 'Afellay $1 {{PLURAL:$1|ass|ussan}}',
+'recentchangescount' => 'Amḍan n ibeddilen i ubeqqeḍ s lexṣas :',
 'savedprefs' => 'Isemyifiyen inek yettusmektan.',
-'timezonelegend' => 'Iẓḍi n ukud',
-'localtime' => 'Akud inek',
-'timezoneoffset' => 'Amgirred n ukud',
-'servertime' => 'Akud n server',
+'timezonelegend' => 'Iẓḍi n ukud :',
+'localtime' => 'Asrag adigan :',
+'timezoneuseserverdefault' => 'Seqdec azal s lexṣas n wiki ($1)',
+'timezoneuseoffset' => 'Nniḍen (ssefru asekḥer)',
+'timezoneoffset' => 'Asekḥer n usrag¹ :',
+'servertime' => 'Asrag n uqeddac :',
 'guesstimezone' => 'Sseqdec azal n browser/explorateur',
+'timezoneregion-africa' => 'Tafriqt',
 'timezoneregion-america' => 'Tamrikt',
 'timezoneregion-antarctica' => 'Antarktik',
 'timezoneregion-arctic' => 'Arktik',
@@ -923,41 +1101,100 @@ i yesɛan akk awalen i banen-d).",
 'timezoneregion-atlantic' => "Agaraw At'lasi",
 'timezoneregion-australia' => 'Usṭralya',
 'timezoneregion-europe' => 'Turuft',
+'timezoneregion-indian' => 'Agaraw Ahendi',
+'timezoneregion-pacific' => 'Agaraw Amelwi',
 'allowemail' => 'Eǧǧ imseqdacen wiyaḍ a k-aznen email',
-'defaultns' => 'Nadi deg yismawen n taɣult s umeslugen:',
+'prefs-searchoptions' => 'Nadi',
+'prefs-namespaces' => 'Talluntin n isemawen',
+'defaultns' => 'Nadi s lexṣas deg tallunin agi n isemawen :',
 'default' => 'ameslugen',
 'prefs-files' => 'Ifayluwen',
+'prefs-custom-css' => 'CSS asagen',
+'prefs-custom-js' => 'JavaScript asagen',
+'prefs-common-css-js' => 'JavaScript  d CSS azduklan i akkw lebsa :',
 'youremail' => 'E-mail *:',
 'username' => 'Isem n wemseqdac:',
 'uid' => 'Amseqdac ID:',
 'yourrealname' => 'Isem n ṣṣeḥ *:',
 'yourlanguage' => 'Tutlayt:',
-'yourvariant' => 'Ameskil:',
-'yournick' => 'Isem wis sin (mačči d amenṣib):',
+'yourvariant' => 'Lqem nniḍen n tutlayt n ugbur :',
+'yournick' => 'Azmul amaynut :',
 'badsig' => 'Azmul mačči d ṣaḥiḥ; Ssenqed tags n HTML.',
+'yourgender' => 'Tawsit :',
+'gender-unknown' => 'Ulac tumlin',
+'gender-male' => 'Amalay',
+'gender-female' => 'Untay',
+'email' => 'E-mail',
 'prefs-help-realname' => '* Isem n ṣṣeḥ (am tebɣiḍ): ma textareḍ a t-tefkeḍ, ad yettuseqdac iwakken ad snen medden anwa yura tikkin inek.',
 'prefs-help-email' => '* E-mail (am tebɣiḍ): Teǧǧi imseqdacen wiyaḍ a k-aznen email mebla ma ẓren tansa email inek.',
 'prefs-help-email-others' => 'Zemreḍ ad eǧǧeḍ wiyeḍ nniḍen ak(akem) cceqɛen izen deg usebter-ik (im) n umyannan war ad effekeḍ tamagit-ik (im).',
+'prefs-help-email-required' => 'Tansa e-mail tesḍulli.',
+'prefs-info' => 'Tilɣa n udasil',
+'prefs-i18n' => 'Asagraɣlan',
+'prefs-signature' => 'Azmul',
+'prefs-dateformat' => 'Amasal n izemzan',
+'prefs-timeoffset' => 'Asekḥer n usrag',
+'prefs-advancedediting' => 'Tixtiṛiyin timahlanin',
+'prefs-advancedrc' => 'Tixtiṛiyin timahlanin',
+'prefs-advancedrendering' => 'Tixtiṛiyin timahlanin',
+'prefs-advancedsearchoptions' => 'Tixtiṛiyin timahlanin',
+'prefs-advancedwatchlist' => 'Tixtiṛiyin timahlanin',
+'prefs-displayrc' => 'Tixtiṛiyin n ubeqqeḍ',
+'prefs-displaysearchoptions' => 'Tixtiṛiyin n ubeqqeḍ',
+'prefs-displaywatchlist' => 'Tixtiṛiyin n ubeqqeḍ',
+'prefs-diffs' => 'Timeẓliwin',
 
 # User rights
 'userrights' => 'Laɛej iserfan n wemseqdac',
 'userrights-lookup-user' => 'Laɛej iderman n yimseqdacen',
 'userrights-user-editname' => 'Ssekcem isem n wemseqdac:',
 'editusergroup' => 'Beddel iderman n yimseqdacen',
-'editinguser' => "Abeddel n wemseqdac '''[[User:$1|$1]]''' ([[User talk:$1|{{int:talkpagelinktext}}]] | [[Special:Contributions/$1|{{int:contribslink}}]])",
+'editinguser' => "Abeddel n izerfan n {{GENDER:$1|useqdac|taseqdact}} '''[[User:$1|$1]]''' $2",
 'userrights-editusergroup' => 'Beddel iderman n wemseqdac',
 'saveusergroups' => 'Smekti iderman n yimseqdacen',
 'userrights-groupsmember' => 'Amaslad deg:',
 'userrights-reason' => 'Ayɣer',
+'userrights-changeable-col' => 'Igrawen i tzemreḍ ad beddeleḍ',
+'userrights-unchangeable-col' => 'Igrawen ur tzemreḍ ara ad beddeleḍ',
 
 # Groups
 'group' => 'Adrum:',
+'group-user' => 'Iseqdacen',
+'group-autoconfirmed' => 'Iseqdacen i rgegen',
+'group-bot' => 'Iṛubuten',
 'group-sysop' => 'Inedbalen',
+'group-bureaucrat' => 'Imsifellura',
+'group-suppress' => 'Inemdayen',
 'group-all' => '(akk)',
 
-'group-sysop-member' => 'Anedbal',
+'group-user-member' => '{{GENDER:$1|aseqdac|taseqdact}}',
+'group-autoconfirmed-member' => '{{GENDER:$1|manrgeg aseqdac|manrgeg taseqdact}}',
+'group-bot-member' => '{{GENDER:$1|aṛubut}}',
+'group-sysop-member' => '{{GENDER:$1|anedbal|tanedbalt}}',
+'group-bureaucrat-member' => '{{GENDER:$1|amsfellaru}}',
+'group-suppress-member' => '{{GENDER:$1|anemday|tanemdayt}}',
 
+'grouppage-user' => '{{ns:project}}:Iseqdacen',
+'grouppage-autoconfirmed' => '{{ns:project}}:Iseqdacen i rgegen',
+'grouppage-bot' => '{{ns:project}}:Iṛubuten',
 'grouppage-sysop' => '{{ns:project}}:Inedbalen',
+'grouppage-bureaucrat' => '{{ns:project}}:Imsfelluran',
+'grouppage-suppress' => '{{ns:project}}:Inemdayen',
+
+# Rights
+'right-read' => 'Ɣeṛ isebtar',
+'right-edit' => 'Beddel isebtar',
+'right-createpage' => 'Snulfud isebtar (mačči d-isebtar n umeslay)',
+'right-createtalk' => 'Snulfud isebtar n umeslay',
+'right-createaccount' => 'Snulfud imiḍanen n iseqdacen',
+'right-minoredit' => 'Ffer ibeddilen yellan d-imectuḥen',
+'right-move' => 'Beddel isem n isebtar',
+'right-move-subpages' => 'Beddel isem n isebtar d adu-isebtar nsen',
+'right-move-rootuserpages' => 'Beddel isem n usebtar amenzawi n useqdac',
+'right-movefile' => 'Beddel isem n ifuyla',
+'right-upload' => 'Azen ifuyla',
+'right-reupload' => 'Sefxes afaylu yellan',
+'right-reupload-own' => 'Sefxes afaylu id n-azen.',
 
 # User rights log
 'rightslog' => 'Aɣmis n yizerfan n wemseqdac',
@@ -966,7 +1203,34 @@ i yesɛan akk awalen i banen-d).",
 'rightsnone' => '(ulaḥedd)',
 
 # Associated actions - in the sentence "You do not have permission to X"
+'action-read' => 'ɣaṛ asebter agi',
 'action-edit' => 'beddel asebter agi',
+'action-createpage' => 'Snulfud isebtar',
+'action-createtalk' => 'snulfud isebtar n umeslay',
+'action-createaccount' => 'snulfud amiḍan agi n useqdac',
+'action-minoredit' => 'cṛeḍ abeddel agi am umectuḥ',
+'action-move' => 'beddel isem n usebter agi',
+'action-move-subpages' => 'beddel isem n usebter agi d adu-isebtar is',
+'action-move-rootuserpages' => 'beddel isem n usebtar amenzawi n useqdac',
+'action-movefile' => 'beddel isem n ufaylu agi',
+'action-upload' => 'Azen afaylu agi',
+'action-reupload' => 'Sefxes afaylu yellan',
+'action-upload_by_url' => 'Azen afaylu agi seg tansa URL',
+'action-writeapi' => 'seqdec API n tira',
+'action-delete' => 'mḥu asebter-agi',
+'action-deleterevision' => 'mḥu lqem agi',
+'action-deletedhistory' => 'ẓeṛ amezruy yemḥan n usebter agi',
+'action-browsearchive' => 'nadi ɣef isebtar yettumḥan',
+'action-undelete' => 'erred asebter agi',
+'action-suppressionlog' => 'ẓeṛ aɣmis agi uslig',
+'action-block' => 'Kyef deg tira aseqdac agi',
+'action-protect' => 'beddel iswiren n umesten i usebter agi',
+'action-import' => 'Kter asebter agi seg wiki nniḍen',
+'action-importupload' => 'Kter asebter agi seg ufaylu n wezdam (upload)',
+'action-unwatchedpages' => 'Sken-d tabdart n isebtaren ur yettwalan ara.',
+'action-mergehistory' => 'Sdukel amezruy n usebtar agi',
+'action-userrights' => 'Ẓreg izerfan n imseqdacen yark',
+'action-userrights-interwiki' => 'Ẓreg izerfan n umseqdac deg wikis wiyaḍ',
 
 # Recent changes
 'nchanges' => '$1 {{PLURAL:$1|Abeddel|Ibeddlen}}',
@@ -995,7 +1259,7 @@ i yesɛan akk awalen i banen-d).",
 'minoreditletter' => 'm',
 'newpageletter' => 'N',
 'boteditletter' => 'b',
-'number_of_watching_users_pageview' => '[$1 aɛessas/iɛessasen]',
+'number_of_watching_users_pageview' => '[$1 {{PLURAL:$1|aɛessas|iɛessasen}}]',
 'rc_categories' => 'Ḥedded i taggayin (ferreq s "|")',
 'rc_categories_any' => 'Ulayɣer',
 'rc-enhanced-expand' => 'Ẓeṛ tilɣa (yeḥwaǧ JavaScript)',
@@ -1014,68 +1278,97 @@ i yesɛan akk awalen i banen-d).",
 # Upload
 'upload' => 'Azen afaylu',
 'uploadbtn' => 'Azen afaylu',
-'reuploaddesc' => 'Uɣal-d ar talɣa n tuznin.',
+'reuploaddesc' => 'Semmewet dɣa uɣaled ar tiferkit n tuznin.',
 'uploadnologin' => 'Ur tekcimeḍ ara',
 'uploadnologintext' => 'Yessefk [[Special:UserLogin|ad tkecmeḍ]]
 iwakken ad tazneḍ afaylu.',
 'upload_directory_read_only' => 'Weserver/serveur Web ur yezmir ara ad yaru deg ($1).',
 'uploaderror' => 'Agul deg usekcam',
-'uploadtext' => "Sseqdec talɣa deg ukessar akken ad tazeneḍ tugnawin, akken ad teẓred neɣ ad tnadiḍ tugnawin yettwaznen, ruḥ ɣer [[Special:FileList|umuɣ n usekcam n tugnawin]], Amezruy n usekcam d umḥay hatent daɣen deg [[Special:Log/upload|amezruy n usekcam]].
-
-Akken ad tessekcmeḍ tugna deg usebter, seqdec azay am wagi
-'''<nowiki>[[</nowiki>{{ns:file}}<nowiki>:Afaylu.jpg]]</nowiki>''',
-'''<nowiki>[[</nowiki>{{ns:file}}<nowiki>:Afaylu.png|aḍris]]</nowiki>''' neɣ
-'''<nowiki>[[</nowiki>{{ns:media}}<nowiki>:Afaylu.ogg]]</nowiki>''' akken ad iruḥ wezday qbala ar ufaylu.",
+'uploadtext' => "Sseqdec tiferkit agi iwakken ad ktereḍ ifuyla ɣef uqeddac.
+Iwakken ad ẓṛeḍ naɣ ad nadiḍ tugniwin i ktren uqbel, ẓeṛ [[Special:FileList|umuɣ n tugniwin]]. Taktert tella daɣen deg [[Special:Log/upload|aɣmis n taktert n ifuyla]], dɣa inuzal deg [[Special:Log/delete|aɣmis n inuzal]].
+
+Akken ad tessekcmeḍ afaylu deg usebter, seqdec azay am wagi
+* '''<code><nowiki>[[</nowiki>{{ns:file}}<nowiki>:afaylu.jpg]]</nowiki></code>''', iwakken ad beqqeḍeḍ afaylu deg tabadut tačurant (lukan d-tugna) ;
+* '''<code><nowiki>[[</nowiki>{{ns:file}}<nowiki>:afaylu.png|200px|thumb|left|aḍris n uglam]]</nowiki></code>''' i useqdac n uqmamaḍ n 200px s tehri deg tanaka af uzelmeḍ s « aḍris n uglam » am aglam ;
+* '''<code><nowiki>[[</nowiki>{{ns:media}}<nowiki>:afaylu.ogg]]</nowiki></code>''' iwakken ad qqeneḍ ɣer ufaylu war ubeqqeḍ.",
+'upload-permitted' => 'Amasal n ifuyla i siregen : $1.',
+'upload-preferred' => 'Amasal n ifuyla i smenyifen : $1.',
+'upload-prohibited' => 'Amasal n ifuyla igdelen : $1.',
 'uploadlog' => 'amezruy n usekcam',
 'uploadlogpage' => 'Amezruy n usekcam',
-'uploadlogpagetext' => 'Deg ukessar, yella wumuɣ n usekcam n ufayluwen imaynuten.',
+'uploadlogpagetext' => 'Hat-an umuɣ n ifuyla ineggura i kteren ɣef uqeddac.
+Ẓeṛ [[Special:NewFiles|tihawt n tugniwin timaynutin]].',
 'filename' => 'Isem n ufaylu',
 'filedesc' => 'Agzul',
 'fileuploadsummary' => 'Agzul:',
-'filestatus' => 'Aẓayer n copyright:',
-'filesource' => 'Seg way yekka',
+'filereuploadsummary' => 'Ibeddilen n ufaylu :',
+'filestatus' => 'Aẓayer n uzref n umeskar :',
+'filesource' => 'Aɣbalu :',
 'uploadedfiles' => 'Ifuyla yettwaznen',
 'ignorewarning' => 'Ttu aɣtal u smekti afaylu',
 'ignorewarnings' => 'Ttu iɣtalen',
+'minlength1' => 'Isem ufaylu ilaq ad yesεu ma drus yiwen usekkil',
 'illegalfilename' => 'Isem n ufaylu "$1" yesɛa isekkilen ur tettalaseḍ ara a ten-tesseqdceḍ deg yizwal n isebtar. G leɛnayek beddel isem n ufaylu u azen-it tikkelt nniḍen.',
+'filename-toolong' => 'Isem ufaylu ilaq ad yesεu m-ay aṭas 240 iṭamḍanen (bytes).',
 'badfilename' => 'Isem ufaylu yettubeddel ar "$1".',
 'filetype-badmime' => 'Ur tettalaseḍ ara ad tazneḍ ufayluwen n anaw n MIME "$1".',
 'filetype-missing' => 'Afaylu ur yesɛi ara taseggiwit (am ".jpg").',
+'empty-file' => 'Afaylu id cegɛeḍ d-ilem.',
+'file-too-large' => 'Afaylu id cegɛed d-ameqqṛan aṭas.',
+'filename-tooshort' => 'Isem n ufaylu d-awezzlan aṭas.',
+'filetype-banned' => 'Tawsit agi n ufaylu d-tazanbagt.',
+'verification-error' => 'Afaylu agi ur i ɛedda ara aselken n ifuyla.',
+'hookaborted' => 'Abeddel i ɛerdeḍ ad xedmeḍ yetweḥbes s tamdeyt n usiɣzef.',
+'illegal-filename' => 'Isem n ufaylu agi ur yeɣbel ara.',
+'overwrite' => 'Asefxes n ufaylu yellan ur yeɣbel ara.',
+'unknown-error' => 'Yefkad anezri warisem.',
+'tmp-create-error' => 'Ulamek ad nesnulfu afaylu agi akudan.',
+'tmp-write-error' => 'Anezri deg tira n ufaylu agi akudan.',
 'large-file' => 'Ilaq tiddi n ufayluwen ur tettili kter n $1; tiddi n ufaylu-agi $2.',
 'largefileserver' => 'Afaylu meqqer aṭṭas, server ur t-yeqbil ara.',
 'emptyfile' => 'Afaylu i tazneḍ d ilem. Waqila tɣelṭeḍ deg isem-is. G leɛnayek ssenqed-it.',
+'windows-nonascii-filename' => 'Wiki agi ur yebra ara isemawen n ifuyla s isekkilen usligen.',
 'fileexists' => 'Afaylu s yisem-agi yewǧed yagi, ssenqed <strong>[[:$1]]</strong> ma telliḍ mačči meḍmun akken a t-tbeddleḍ.
 [[$1|thumb]]',
-'fileexists-extension' => 'Afaylu s yisem-agi yewǧed: [[$2|thumb]]
-* Isem n ufaylu i tazneḍ: <strong>[[:$1]]</strong>
-* Isem n ufaylu i yewǧed: <strong>[[:$2]]</strong>
-Amgirred i yella kan deg isekkilen imecṭuḥen/imeqqranen deg taseggiwit (am ".jpg"/".jPg"). G leɛnayek ssenqed-it.',
+'fileexists-extension' => 'Afaylu s yisem yecban wagi yella : [[$2|thumb]]
+* Isem n ufaylu i tezneḍ: <strong>[[:$1]]</strong>
+* Isem n ufaylu i yellan: <strong>[[:$2]]</strong>
+Ilaq ad xtiṛeḍ isem nniḍen.',
 'fileexists-thumbnail-yes' => "Iban-d belli tugna-nni d tugna tamecṭuht n tugna nniḍen ''(thumbnail)''. [[$1|thumb]]
 G leɛnayek ssenqed tugna-agi <strong>[[:$1]]</strong>.
 Ma llant kif-kif ur tt-taznepd ara.",
-'file-thumbnail-no' => "Isem n tugna yebda s <strong>$1</strong>. Waqila tugna-nni d tugna tamecṭuht n tugna nniḍen ''(thumbnail)''.
-Ma tesɛiḍ tugna-nni s resolution tameqqrant, azen-it, ma ulac beddel isem-is.",
-'fileexists-forbidden' => 'Tugna s yisem kif-kif tewǧed yagi; g leɛnayek uɣal u beddel isem-is. [[File:$1|thumb|center|$1]]',
-'fileexists-shared-forbidden' => 'Tugna s yisem kif-kif tewǧed yagi; g leɛnayek uɣal u beddel isem-is. [[File:$1|thumb|center|$1]]',
+'file-thumbnail-no' => "Isem n ufaylu yezwer s <strong>$1</strong>.
+Ahat d lqem tamectuḥt ''(aqmamaḍ)''.
+Lukan tesɛiḍ afaylu s tabadut taɛlayant, azen-it, m-ulac beddel isem-is.",
+'fileexists-forbidden' => 'Afaylu s isem agi yella yakan, dɣa ur yezmer ara ad yetsefxes.
+Ma tebɣiḍ ad azeneḍ afaylu inek/inem, ilaq ad uɣaleḍ ar deffir dɣa ad as efkeḍ isem amaynut.
+[[File:$1|thumb|center|$1]]',
+'fileexists-shared-forbidden' => 'Afaylu s isem agi yella yakan deg uzadur n ifuyla azduklan.
+Ma tebɣiḍ ad azeneḍ afaylu inek/inem, ilaq ad uɣaleḍ ar deffir dɣa ad as efkeḍ isem amaynut.
+[[File:$1|thumb|center|$1]]',
+'file-exists-duplicate' => 'Afaylu agi d-asleg n {{PLURAL:$1|ufaylu agi|ifuyla agi}} :',
 'uploadwarning' => 'Aɣtal deg wazan n ufayluwen',
 'savefile' => 'Smekti afaylu',
 'uploadedimage' => '"[[$1]]" yettwazen',
 'uploaddisabled' => 'Suref-aɣ, azen n ufayluwen yettwakkes',
-'uploaddisabledtext' => 'Azen n ufayluwen yettwakkes deg wiki-yagi',
+'uploaddisabledtext' => 'Azen n ifuyla yettwakkes deg wiki agi.',
 'uploadscripted' => 'Afaylu-yagi yesɛa angal n HTML/script i yexdem agula deg browser/explorateur.',
 'uploadvirus' => 'Afaylu-nni yesɛa anfafad asenselkim (virus)! Ẓer kter: $1',
-'sourcefilename' => 'And yella afyalu',
-'destfilename' => 'Anda iruḥ afaylu',
-'watchthisupload' => 'Ɛass asebter-agi',
+'sourcefilename' => 'Isem n ufaylu aɣbalu :',
+'sourceurl' => 'URL aγbalu',
+'destfilename' => 'Isem n ufaylu deg aserken',
+'watchthisupload' => 'Ɛass asebter agi',
 'filewasdeleted' => 'Afaylu s yisem-agi yettwazen umbeɛd yettumḥa. Ssenqed $1 qbel ad tazniḍ tikelt nniḍen.',
 'upload-success-subj' => 'Azen yekfa',
 
 'upload-proto-error' => 'Agul deg protokol',
 'upload-proto-error-text' => 'Assekcam yenṭerr URL i yebdan s <code>http://</code> neɣ <code>ftp://</code>.',
 'upload-file-error' => 'Agul zdaxel',
-'upload-file-error-text' => 'Agul n daxel yeḍran asmi yeɛreḍ ad yexleq afaylu temporaire deg server.  G leɛnayek, meslay akk d unedbal n system.',
+'upload-file-error-text' => 'Anezri agensan yeḍran asmi yeɛreḍ ad yesnulfu afaylu akudan ɣef uqeddac. Ilaq ad meslayeḍ s [[Special:ListUsers/sysop|unedbal]].',
 'upload-misc-error' => 'Agul mačči mechur asmi yettwazen ufaylu',
-'upload-misc-error-text' => 'Agul mačči mechur teḍra asmi yettwazen afaylu.  G leɛnayek sseqed belli URL d ṣaḥiḥ u ɛreḍ tikelt nniḍen.  Ma yella daɣen wagul, mmeslay akk d unedbal n system.',
+'upload-misc-error-text' => 'Anezri warisem yegweḍeḍ asmi yettwazen afaylu.
+Ilaq ad selkeneḍ ma URL nni teɣbel, dɣa ɛreḍ tikkelt nniḍen.
+Ma yella daɣen anezri, ilaq ad meslaye ḍ s  [[Special:ListUsers/sysop|unedbal]].',
 
 # Some likely curl errors. More could be added from <http://curl.haxx.se/libcurl/c/libcurl-errors.html>
 'upload-curl-error6' => 'Ur yezmir ara ad yessglu URL',
@@ -1114,7 +1407,7 @@ Ma tesɛiḍ tugna-nni s resolution tameqqrant, azen-it, ma ulac beddel isem-is.
 'imagelinks' => 'Izdayen',
 'linkstoimage' => '{{PLURAL:$1|Asebter agi teseqdac|$1 isebtaren agi teseqdacen}} afaylu agi :',
 'nolinkstoimage' => 'Ulaḥedd seg isebtar sɛan azday ar afaylu-agi.',
-'sharedupload' => 'Afaylu-yagi yettuseqdac sɣur wiki tiyaḍ.',
+'sharedupload' => 'Afaylu agi yettuseqdac seg : $1. Yezmer ad yettuseqdac deg isenfaṛen nniḍen',
 'sharedupload-desc-here' => 'Afaylu agi yusad seg : $1. Ahat yeseqdec deg isenfaṛen nniḍen.
 Aglam-is ɣef [$2 asebter n aglam] ye beqqeḍ ddaw-agi.',
 'uploadnewversion-linktext' => 'tazneḍ tasiwelt tamaynut n ufaylu-yagi',
@@ -1133,12 +1426,13 @@ Aglam-is ɣef [$2 asebter n aglam] ye beqqeḍ ddaw-agi.',
 
 # Unused templates
 'unusedtemplates' => 'Talɣiwin mebla aseqdac',
-'unusedtemplatestext' => 'Asebter-agi yesɛa umuɣ n akk isebtar n isem n taɣult s yisem "talɣa" iwumi ulac-iten deg ḥedd asebter. Ur tettuḍ ara ad tessenqdeḍ isebtar n talɣa wiyaḍ qbel ad temḥuḍ.',
+'unusedtemplatestext' => 'Asebter-agi yesɛa umuɣ n akkw isebtar n tallunt isemawen « {{ns:template}} » ur llan ara deg asebter nniḍen.
+Ur tettuḍ ara ad selkeneḍ ma ur llan ara izdayen nniḍen ɣer tilɣatin uqbel ad temḥuḍ.',
 'unusedtemplateswlh' => 'izdayen wiyaḍ',
 
 # Random page
 'randompage' => 'Asebter menwala',
-'randompage-nopages' => 'Ulac isebtar deg isem n taɣult agi.',
+'randompage-nopages' => 'Ulac isebtar deg {{PLURAL:$2|tallunt n isemawen|tallunin n isemawen}} : $1.',
 
 # Random redirect
 'randomredirect' => 'Asemmimeḍ menwala',
@@ -1148,9 +1442,11 @@ Aglam-is ɣef [$2 asebter n aglam] ye beqqeḍ ddaw-agi.',
 'statistics-header-users' => 'Tisnaddanin n wemseqdac',
 'statistics-mostpopular' => 'isebtar mmeẓren aṭṭas',
 
-'disambiguations' => 'isebtar n usefham',
+'disambiguations' => 'Isebtar yesɛan izdayen ɣer isebtar n tiynisemt',
 'disambiguationspage' => 'Template:Asefham',
-'disambiguations-text' => "Isebtar-agi sɛan azday ɣer '''usebter n usefham'''. Yessefk ad sɛun azday ɣer wezwel ṣaḥiḥ mačči ɣer usebter n usefham.",
+'disambiguations-text' => "Isebtar agi azday ɣer '''asebter n tiynisemt'''.
+Ilaq ad sɛun azday ɣer amagrad amellay.<br />
+Asebter yella d asebter n tiynisemt lukan yetseqdac talɣa i qqenen ar [[MediaWiki:Disambiguationspage]]",
 
 'doubleredirects' => 'Asemmimeḍ yeḍra snat tikwal',
 'doubleredirectstext' => 'Mkull ajerriḍ yesɛa azday ɣer asmimeḍ amezwaru akk d wis sin, ajerriḍ amezwaru n uḍris n usebter wis sin daɣen, iwumi yefkan asmimeḍ ṣaḥiḥ i yessefk ad sɛan isebtar azday ɣur-s.',
@@ -1160,8 +1456,10 @@ Aglam-is ɣef [$2 asebter n aglam] ye beqqeḍ ddaw-agi.',
 'brokenredirects-edit' => 'beddel',
 'brokenredirects-delete' => 'mḥu',
 
-'withoutinterwiki' => 'isebtar mebla izdayen ar isebtar n wikipedia s tutlayin tiyaḍ',
-'withoutinterwiki-summary' => 'isebtar-agi ur sɛan ara izdayen ar isebtar n wikipedia s tutlayin tiyaḍ:',
+'withoutinterwiki' => 'Isebtar war izdayen ager-tutlayin',
+'withoutinterwiki-summary' => 'Isebtar agi ur sɛan ara izdayen ɣer tutlayin nniḍen :',
+'withoutinterwiki-legend' => 'Adat',
+'withoutinterwiki-submit' => 'Ssken',
 
 # Miscellaneous special pages
 'nbytes' => '$1 {{PLURAL:$1|byte/octet|bytes/octets}}',
@@ -1172,10 +1470,10 @@ Aglam-is ɣef [$2 asebter n aglam] ye beqqeḍ ddaw-agi.',
 'nviews' => '$1 {{PLURAL:$1|timeẓriwt|tuẓrin}}',
 'specialpage-empty' => 'Asebter-agi d ilem.',
 'lonelypages' => 'isebtar igujilen',
-'lonelypagestext' => 'isebtar-agi ur myezdin ara seg isebtar wiyaḍ deg wiki-yagi.',
+'lonelypagestext' => 'Isebtar agi ur sweṛen, ur llan deg isebtar nniḍen n {{SITENAME}}.',
 'uncategorizedpages' => 'isebtar mebla taggayt',
 'uncategorizedcategories' => 'Taggayin mebla taggayt',
-'uncategorizedimages' => 'Tugna mebla taggayt',
+'uncategorizedimages' => 'Ifuyla war taggayin',
 'uncategorizedtemplates' => 'Talɣiwin mebla taggayt',
 'unusedcategories' => 'Taggayin ur nettwaseqdac ara',
 'unusedimages' => 'Ifayluwin ur nettwaseqdac ara',
@@ -1185,13 +1483,13 @@ Aglam-is ɣef [$2 asebter n aglam] ye beqqeḍ ddaw-agi.',
 'mostlinked' => 'Isebtar myezdin aṭas',
 'mostlinkedcategories' => 'Taggayin myezdint aṭas',
 'mostcategories' => 'Isebtar i yesɛan aṭṭas taggayin',
-'mostimages' => 'Tugniwin myezdin aṭas',
+'mostimages' => 'Ifuyla i seqdacen aṭas',
 'mostrevisions' => 'Isebtar i yettubedlen aṭas',
 'prefixindex' => 'Akk isebtaren s yisekkilen imezwura',
 'shortpages' => 'isebtar imecṭuḥen',
 'longpages' => 'Isebtar imeqqranen',
 'deadendpages' => 'isebtar mebla izdayen',
-'deadendpagestext' => 'isebtar-agi ur sɛan ara azday ɣer isebtar wiyaḍ deg wiki-yagi.',
+'deadendpagestext' => 'Isebtar agi ur sɛan ara izdayen ɣer isebtar nniḍen n {{SITENAME}}.',
 'protectedpages' => 'isebtar yettwaḥerzen',
 'protectedpagestext' => 'isebtar-agi yettwaḥerzen seg ubeddel neɣ asemmimeḍ',
 'protectedpagesempty' => 'isebtar-agi ttwaḥerzen s imsektayen -agi.',
@@ -1216,10 +1514,10 @@ Aglam-is ɣef [$2 asebter n aglam] ye beqqeḍ ddaw-agi.',
 'booksources-text' => 'Deg ukessar, yella wumuɣ n yizdayen iberraniyen izzenzen idlisen (imaynuten akk d weqdimen), yernu ahat sɛan kter talɣut ɣef idlisen i tettnadiḍ fell-asen:',
 
 # Special:Log
-'specialloguserlabel' => 'Amseqdac:',
-'speciallogtitlelabel' => 'Azwel:',
+'specialloguserlabel' => 'Ameskar :',
+'speciallogtitlelabel' => 'Asaḍas (azwel naɣ aseqdac) :',
 'log' => 'Aɣmis',
-'all-logs-page' => 'Akk iɣmisen',
+'all-logs-page' => 'Akk iɣmisen izayezen',
 'alllogstext' => 'Ssken akk iɣmisen n {{SITENAME}}.
 Tzemreḍ ad textareḍ cwiṭ seg-sen ma tebɣiḍ.',
 'logempty' => 'Ur yufi ara deg uɣmis.',
@@ -1246,8 +1544,15 @@ Tzemreḍ ad textareḍ cwiṭ seg-sen ma tebɣiḍ.',
 'categoriespagetext' => 'Llant taggayin-agi deg wiki-yagi.
 [[Special:UnusedCategories|Unused categories]] are not shown here.
 Also see [[Special:WantedCategories|wanted categories]].',
+'categoriesfrom' => 'Ssken taggayin seg :',
+'special-categories-sort-count' => 'Afran s amḍan n iferdisen',
+'special-categories-sort-abc' => 'Afran s ugemmay',
 
 # Special:LinkSearch
+'linksearch' => 'Anadi n izdayen yeffɣen',
+'linksearch-pat' => 'Anadi n tanfalit :',
+'linksearch-ns' => 'Talluntin n isemawen :',
+'linksearch-ok' => 'Nadi',
 'linksearch-line' => '$1 yeqqen seg $2',
 
 # Special:ListUsers
@@ -1259,31 +1564,56 @@ Also see [[Special:WantedCategories|wanted categories]].',
 'newuserlogpage' => 'Aɣmis n isnulfan n  imiḍanen n imseqdacen',
 
 # Special:ListGroupRights
+'listgrouprights-group' => 'Agraw',
+'listgrouprights-rights' => 'Izerfan',
+'listgrouprights-helppage' => 'Help:Izerfan n igrawen',
 'listgrouprights-members' => '(umuɣ n imseqdacen)',
+'listgrouprights-addgroup' => 'Rnu iεeggalen i {{PLURAL:$2|ugraw|igrawen}} : $1',
+'listgrouprights-removegroup' => 'Ekkes iεeggalen i {{PLURAL:$2|ugraw|igrawen}} : $1',
+'listgrouprights-addgroup-all' => 'Rnu iεeggalen i akkw igrawen',
+'listgrouprights-removegroup-all' => 'Ekkes iεeggalen i akkw igrawen',
+'listgrouprights-addgroup-self' => 'Yezmer ad yernu {{PLURAL:$2|agraw|igrawen}} ar umiḍan-is : $1',
+'listgrouprights-removegroup-self' => 'Yezmer ad yekkes {{PLURAL:$2|agraw|igrawen}} ar umiḍan-is : $1',
+'listgrouprights-addgroup-self-all' => 'Yezmer ad yernu akkw igrawen ar umiḍan-is',
+'listgrouprights-removegroup-self-all' => 'Yezmer ad yekkes akkw igrawen ar umiḍan-is',
 
 # E-mail user
 'mailnologin' => 'Ur yufi ḥedd (tansa)',
 'mailnologintext' => 'Yessefk ad [[Special:UserLogin|tkecmeḍ]] u tesɛiḍ tansa e-mail ṭaṣhiḥt deg [[Special:Preferences|isemyifiyen]] inek
 iwakken ad tazneḍ email i imseqdacen wiyaḍ.',
 'emailuser' => 'Azen e-mail i wemseqdac-agi',
-'emailpage' => 'Azen e-mail i wemseqdac',
-'emailpagetext' => 'Lukan amseqdac-agi yefka-d tansa n email ṣaḥiḥ
-deg imsifiyen ines, talɣa deg ukessar a t-tazen izen.
-Tansa n email i tefkiḍ deg imisifyen inek ad tban-d
-deg « Expéditeur» n izen inek iwakken amseqdac-nni yezmer a k-yerr.',
+'emailuser-title-target' => 'Ceggaɛ tirawt i {{GENDER:$1|aseqdac agi|taseqdact agi}}',
+'emailuser-title-notarget' => 'Ceggaɛ tirawt i useqdac',
+'emailpage' => 'Ceggaɛ tirawt i useqdac',
+'emailpagetext' => 'Tzemreḍ ad seqdeceḍ tiferkit ddaw agi iwakken ad ceggɛeḍ tirawt i useqdac agi.
+Tansa e-mail id ekfeḍ deg [[Special:Preferences|iɣewwaren inek/inem]] ad tban deg urti "Amceggaɛ" n izen ; akka, anermas ad yezmer ak/akem yefk tiririt.',
 'usermailererror' => 'Yella ugul deg uzwel n email:',
-'defemailsubject' => 'e-mail n {{SITENAME}}',
+'defemailsubject' => '{{SITENAME}} tirawt n useqdac « $1 »',
+'usermaildisabled' => 'Aceggaɛ n tira gar iseqdacen yensa',
+'usermaildisabledtext' => 'Ur tzermeḍ ara ad ceggeɛeḍ tira i iseqdacen nniḍen ɣef wiki agi',
 'noemailtitle' => 'E-mail ulac-it',
-'noemailtext' => 'Amseqdac-agi ur yefki ara e-mail ṣaḥiḥ, neɣ ur yebɣi ara e-mailiyen seg medden.',
-'emailfrom' => 'Seg',
-'emailto' => 'i',
-'emailsubject' => 'Asentel',
-'emailmessage' => 'Izen',
+'noemailtext' => 'Aseqdac-agi ur d-yefka ara tansa e-mail iɣbelen.',
+'nowikiemailtitle' => 'Ulac turagt i e-mail',
+'nowikiemailtext' => 'Aseqdac agi ur yebɣa ara ad yeṭṭef tirawt sɣur iseqdacen nniḍen.',
+'emailnotarget' => 'Isem useqdac n unermas ur yella ara naɣ ur yeɣbel ara.',
+'emailtarget' => 'Sekcem isem useqdac n unermas',
+'emailusername' => 'Isem n useqdac',
+'emailusernamesubmit' => 'Sumer',
+'email-legend' => 'Ceggaɛ tirawt i yiwen useqdac nniḍen n {{SITENAME}}',
+'emailfrom' => 'Seg :',
+'emailto' => 'I :',
+'emailsubject' => 'Asentel :',
+'emailmessage' => 'Izen :',
 'emailsend' => 'Azen',
 'emailccme' => 'Azen-iyi-d e-mail n ulsaru n izen inu.',
 'emailccsubject' => 'Alsaru n izen inek i $1: $2',
 'emailsent' => 'E-mail yettwazen',
 'emailsenttext' => 'Izen n e-mail inek yettwazen.',
+'emailuserfooter' => 'Tirawt agi tetweceggaɛ sɣur « $1 » i « $2 » s tasɣent "Ceggaɛ tirawt i useqdac" n {{SITENAME}}.',
+
+# User Messenger
+'usermessage-summary' => 'Yeǧǧa-d izen anagraw',
+'usermessage-editor' => 'Ameskar n unagraw',
 
 # Watchlist
 'watchlist' => 'Umuɣ n uɛessi inu',
@@ -1293,12 +1623,14 @@ deg « Expéditeur» n izen inek iwakken amseqdac-nni yezmer a k-yerr.',
 'watchlistanontext' => 'G leɛnaya-k $1 iwakken ad twalaḍ neɣ tbeddleḍ iferdas deg wumuɣ n uɛessi inek.',
 'watchnologin' => 'Ur tekcimeḍ ara',
 'watchnologintext' => 'Yessefk ad [[Special:UserLogin|tkecmeḍ]] iwakken ad tbeddleḍ umuɣ n uɛessi inek.',
+'addwatch' => 'Rnu i umuɣ n uɛassi',
 'addedwatchtext' => "Asebter \"[[:\$1]]\" yettwarnu deg [[Special:Watchlist|wumuɣ n uɛessi]] inek.
 Ma llan ibeddlen deg usebter-nni neɣ deg usbtar umyennan ines, ad banen dagi,
 Deg [[Special:RecentChanges|wumuɣ n yibeddlen imaynuten]] ad banen s '''yisekkilen ibberbuzen''' (akken ad teẓriḍ).
 
 Ma tebɣiḍ ad tekkseḍ asebter seg wumuɣ n uɛessi inek, wekki ɣef \"Fakk aɛessi\".",
-'removedwatchtext' => 'Asebter "[[:$1]]" yettwakkes seg wumuɣ n uɛessi inek.',
+'removewatch' => 'Ekkes seg umuɣ n uɛassi',
+'removedwatchtext' => '!!Asebter "[[:$1]]" yettwakkes seg [[Special:Watchlist|umuɣ n uɛessi]] inek.',
 'watch' => 'Ɛass',
 'watchthispage' => 'Ɛass asebter-agi',
 'unwatch' => 'Fakk aɛassi',
@@ -1331,25 +1663,32 @@ Ma tebɣiḍ ad tekkseḍ asebter seg wumuɣ n uɛessi inek, wekki ɣef \"Fakk a
 'enotif_lastdiff' => 'Ẓer $1 akken ad tmuqleḍ abeddel.',
 'enotif_body' => 'Ay $WATCHINGUSERNAME,
 
-Asebter n {{SITENAME}} $PAGETITLE $CHANGEDORCREATED deg wass $PAGEEDITDATE sɣur $PAGEEDITOR, ẓer $PAGETITLE_URL i tasiwelt n tura.
+Asebter « $PAGETITLE » n {{SITENAME}} $CHANGEDORCREATED ass n $PAGEEDITDATE sɣur « $PAGEEDITOR », ẓeṛ $PAGETITLE_URL iwakken ad ẓṛeḍ lqem n tura.
 
 $NEWPAGE
 
 Abeddel n wegzul: $PAGESUMMARY $PAGEMINOREDIT
 
-Meslay akk d ambeddel:
-email: $PAGEEDITOR_EMAIL
+Meslay s umbeddel:
+e-mail: $PAGEEDITOR_EMAIL
 wiki: $PAGEEDITOR_WIKI
 
-Ur yelli ara email n talɣut asmi llan ibeddlen deg usebter ala lukan teẓreḍ asebter-nni. Tzemreḍ ad terreḍ i zero email n talɣut i akk isebraen i tettɛasseḍ.
+Ur yelli ara email n talɣut asmi llan ibeddlen deg usebter ala lukan teẓreḍ asebter-nni.
+Tzemreḍ ad awennezeḍ akkw isenǧaqen n talɣut i akkw isebtar yellan deg umuɣ inek/inem n uɛassi.
 
-             email n talɣut n {{SITENAME}}
+             Anagraw inek/inem n talɣut n {{SITENAME}}
 
 --
-Akken ad tbeddleḍ n wumuɣ n uɛessi inek settings, ruḥ ɣer
+Iwakken ad beddeleḍ iɣewwaren n talɣut deg tirawt, ẓeṛ
+{{canonicalurl:{{#special:Preferences}}}}
+
+Iwakken ad beddeleḍ iɣewwaren n umuɣ inek/inem n uɛassi, ẓeṛ
 {{canonicalurl:{{#special:EditWatchlist}}}}
 
-Tadhelt:
+Iwakken ad mḥuḍ asebter deg umuɣ inek/inem n uɛassi, ẓeṛ
+$UNWATCHURL
+
+Tuɣalin d tadhelt :
 {{canonicalurl:{{MediaWiki:Helppage}}}}',
 
 # Delete
@@ -1410,7 +1749,7 @@ G leɛnayek wekki ɣef taqeffalt "Back/Précédent" n browser/explorateur inek,
 'undelete-no-results' => 'Ur yufi ara ulaḥedd n wawalen i tnadiḍ ɣef isebtar deg iɣbaren.',
 
 # Namespace form on various pages
-'namespace' => 'Isem n taɣult:',
+'namespace' => 'Talluntin n isemawen :',
 'invert' => 'Snegdam ayen textareḍ',
 'blanknamespace' => '(Amenzawi)',
 
@@ -1656,6 +1995,23 @@ Anda tebɣiḍ tesmimeḍ "[[:$1]]" yella yagi. tebɣiḍ ad temḥuḍ iwakken
 'spam_reverting' => 'Asuɣal i tasiwel taneggarut i ur tesɛi ara izdayen ɣer $1',
 'spam_blanking' => 'Akk tisiwal sɛan izdayen ɣer $1, ad yemḥu',
 
+# Info page
+'pageinfo-title' => 'Tilɣa i « $1 »',
+'pageinfo-header-basic' => 'Tilɣa n udasil',
+'pageinfo-header-edits' => 'Amezruy n ibeddilen',
+'pageinfo-header-restrictions' => 'Amesten n usebter',
+'pageinfo-header-properties' => 'Ayla n usebter',
+'pageinfo-display-title' => 'Azwel yebeqqeḍen',
+'pageinfo-default-sort' => 'Tasarut n ufran s lexṣas',
+'pageinfo-length' => 'Tiddi n usebter (s itamḍanen)',
+'pageinfo-article-id' => 'Uṭṭun n usebter',
+'pageinfo-robot-policy' => 'Aẓayer n umsadday n unadi',
+'pageinfo-robot-index' => 'Ṭwamatar',
+'pageinfo-robot-noindex' => 'Arṭwamatar',
+'pageinfo-views' => 'Amḍan n timuɣliwin',
+'pageinfo-watchers' => 'Amḍan n imttekkiyen yesɛan asebter agi deg umuɣ nsen n uɛassi',
+'pageinfo-subpages-name' => 'Adu-isebtar n usebter agi',
+
 # Patrolling
 'markaspatrolleddiff' => 'Rcem "yettwassenqden"',
 'markaspatrolledtext' => 'Rcem amagrad-agi "yettwassenqden"',
@@ -1722,11 +2078,81 @@ Izdayen nniḍen ɣef yiwen ajerriḍ llan d tisuraf, am isebtar ɣef anta tugna
 
 # EXIF tags
 'exif-imagewidth' => 'Tehri',
+'exif-worldregiondest' => 'Timnaḍin n umaḍal yebeqqeḍen',
+'exif-countrydest' => 'Timura yebeqqeḍen',
+'exif-countrycodedest' => 'Tangalt n tamurt yebeqqeḍen',
+'exif-provinceorstatedest' => 'Tamnaṭ naɣ Tamurt yebeqqeḍen',
+'exif-citydest' => 'Tamdint yebeqqeḍen',
+'exif-sublocationdest' => 'Aḥric n temdint yebeqqeḍen',
+'exif-objectname' => 'Azwel amectuḥ',
+'exif-specialinstructions' => 'Tinaḍi tusligin',
+'exif-headline' => 'Azwel',
+'exif-credit' => 'Asmad / imefki',
+'exif-source' => 'Aɣbalu',
+'exif-editstatus' => 'Aẓayer amaẓrag n tugna',
+'exif-urgency' => 'Lḥir',
+'exif-fixtureidentifier' => 'Isem n uferdis aslagan',
+'exif-locationdest' => 'Amḍiq yebeqqeḍen',
+'exif-locationdestcode' => 'Tangalt n umḍiq yebeqqeḍen',
+'exif-contact' => 'Tilɣa n unermis',
+'exif-writer' => 'Ameskar',
+'exif-languagecode' => 'Tutlayt',
+'exif-iimversion' => 'Lqem n IIM',
+'exif-iimcategory' => 'Taggayt',
+'exif-iimsupplementalcategory' => 'Taggayin timarnanin',
+'exif-datetimeexpires' => 'Ur tseqdac ara sakin',
+'exif-datetimereleased' => 'Tuffɣa ass n',
+'exif-originaltransmissionref' => 'Tangalt n usideg n tuzzna tamezwarut',
+'exif-identifier' => 'Asulay',
 
 'exif-meteringmode-255' => 'Nniḍen',
 
 # Pseudotags used for GPSSpeedRef
 'exif-gpsspeed-k' => 'Kilometr deg ssaɛa',
+'exif-gpsspeed-m' => 'Miles deg usrag',
+'exif-gpsspeed-n' => 'Tikerrist',
+
+# Pseudotags used for GPSDestDistanceRef
+'exif-gpsdestdistance-k' => 'Ikilumetren',
+'exif-gpsdestdistance-m' => 'igimen',
+'exif-gpsdestdistance-n' => 'Miles iwlalen',
+
+'exif-gpsdop-good' => 'Tamellayt ($1)',
+'exif-gpsdop-moderate' => 'Tallalt ($1)',
+
+'exif-objectcycle-a' => 'Tanzayt kan',
+'exif-objectcycle-p' => 'Tameddit kan',
+'exif-objectcycle-b' => 'Tanzayt d tameddit',
+
+# Pseudotags used for GPSTrackRef, GPSImgDirectionRef and GPSDestBearingRef
+'exif-gpsdirection-t' => 'Anamud n tidett',
+'exif-gpsdirection-m' => 'Anamud adkiran',
+
+'exif-ycbcrpositioning-1' => 'Agwans',
+'exif-ycbcrpositioning-2' => 'Azdi-sideg',
+
+'exif-dc-contributor' => 'Imttekkiyen',
+'exif-dc-coverage' => 'Azrag allunan naɣ akudan n umedia',
+'exif-dc-date' => 'Azmez',
+'exif-dc-publisher' => 'Amaẓrag',
+'exif-dc-relation' => 'Imediaten iqqenen',
+'exif-dc-rights' => 'Izerfan',
+'exif-dc-source' => 'Aɣbalu umedia',
+'exif-dc-type' => 'Tawsit n umedia',
+
+'exif-rating-rejected' => 'Yerrad',
+
+'exif-isospeedratings-overflow' => 'Ameqqṛan ugar 65535',
+
+'exif-iimcategory-ace' => 'Tiẓuṛiyin, idles d amzel',
+'exif-iimcategory-clj' => 'Anɣa d uṣaḍuf',
+'exif-iimcategory-dis' => 'Tiwaɣin d timedriyin',
+'exif-iimcategory-fin' => 'Tadamsa d tidyanin',
+'exif-iimcategory-edu' => 'Asileɣ',
+'exif-iimcategory-evn' => 'Tawennaṭ',
+'exif-iimcategory-hth' => 'Tadawsa',
+'exif-iimcategory-hum' => 'Aramsu alsi',
+'exif-iimcategory-lab' => 'Amahil',
 
 # External editor support
 'edit-externally' => 'Beddel afaylu-yagi s usnas aberrani.',
@@ -1880,4 +2306,15 @@ G leɛnaya-k sentem belli ṣaḥḥ tebɣiḍ ad tɛiwedeḍ axlaq n usebter-ag
 # Special:Tags
 'tag-filter' => 'Astay n [[Special:Tags|ticraḍ]] :',
 
+# Durations
+'duration-seconds' => '$1 {{PLURAL:$1|tasint|tisinin}}',
+'duration-minutes' => '$1 {{PLURAL:$1|tamrect|timercin}}',
+'duration-hours' => '$1 {{PLURAL:$1|asrag|isragen}}',
+'duration-days' => '$1 {{PLURAL:$1|ass|ussan}}',
+'duration-weeks' => '$1 {{PLURAL:$1|imalas|imulas}}',
+'duration-years' => '$1 {{PLURAL:$1|aseggwas|iseggwasen}}',
+'duration-decades' => '$1 {{PLURAL:$1|amrawass|amrawussan}}',
+'duration-centuries' => '$1 {{PLURAL:$1|timiḍi|timiḍa}}',
+'duration-millennia' => '$1 {{PLURAL:$1|agimseggwas|agimseggwasen}}',
+
 );
index 0d3daa5..6aeebf5 100644 (file)
@@ -408,7 +408,7 @@ $2',
 # Login and logout pages
 'logouttext' => "'''Джыпсту уикӀыжауэ щыт.'''
 
-Уихьэжьыфыну {{grammar:genitive|{{SITENAME}}}} зыкъумгъэцӀыху иэ [[Special:UserLogin|зыкъегъэцӀыхун аргуэру]] уи цӀэмкӀэ иэ нэмыщӀымкӀэ.
+Уихьэжьыфыну {{grammar:genitive|{{SITENAME}}}} зыкъумгъэцӀыху иэ <span class='plainlinks'>[$1 зыкъегъэцӀыхун аргуэру]</span> уи цӀэмкӀэ иэ нэмыщӀымкӀэ.
 НапэкӀуэцӀ гуэрэхэр япэми хуэду къикӀыфынухэ, системэм уимыкӀыжьа хуэду. Апхуэду щымытын щхьэкӀэ браузэр кэшыр къэгъэщӀырыщӀын хуэй.",
 'welcomecreation' => '== Къеблагъэ, $1! ==
 Уи аккаунтыр хьэзырщ.
index 3ae50d3..04088c4 100644 (file)
@@ -386,7 +386,7 @@ MySQL جوابِ خطاء پرائے "$3: $4"',
 
 # Login and logout pages
 'logouttext' => "'''ھنیسے تو خارج بیتی آسوس'''<br />
-تو خفی الاسم {{SITENAME}}  استعمال جاری لاکھیکو بوس، یا دوبارہ ھیہ نامو یا مختلف نامان سورا داخل دی بیکو بوس۔  ھیہ یاد آوری کورے کہ ای کما صفحات ھش [[Special:UserLogin|دوباری لاگن بوس]] غیچھی گونی کہ تو ھنیسے خارج نو بیتی آسوس، کلہ پت کہ تو تان تفصحہ (براؤزرو) ابطن (cache) صاف نوکوروس۔\",",
+تو خفی الاسم {{SITENAME}}  استعمال جاری لاکھیکو بوس، یا دوبارہ ھیہ نامو یا مختلف نامان سورا داخل دی بیکو بوس۔  ھیہ یاد آوری کورے کہ ای کما صفحات ھش <span class='plainlinks'>[\$1 دوباری لاگن بوس]</span> غیچھی گونی کہ تو ھنیسے خارج نو بیتی آسوس، کلہ پت کہ تو تان تفصحہ (براؤزرو) ابطن (cache) صاف نوکوروس۔\",",
 'welcomecreation' => '== رحمت عزیز چترالی تتے خوشان گیے ریران، $1 ! ==
 
 تہ  کھاتہ ساوزینو بیتی شیر تو تان [[Special:Preferences|{{SITENAME}} ترجیحات]]ن مرتب کوریکو مو روخڅے.',
index 8828392..08bf1de 100644 (file)
@@ -447,7 +447,7 @@ Sebebê ho ''$2'' dero.",
 # Login and logout pages
 'logouttext' => "'''Sıma nıka cı ra veciyê.'''
 
-Sıma şikinê dızdêni {{SITENAME}} de dewam kerê, ya jê eyni karberi ya ki jê jüyê de bini [[Special:UserLogin|oncia cıkuyê]].
+Sıma şikinê dızdêni {{SITENAME}} de dewam kerê, ya jê eyni karberi ya ki jê jüyê de bini <span class='plainlinks'>[$1 oncia cıkuyê]</span>.
 Beno ke taê peli sıma hona cıkote asnenê, hata ke sıma ''browser cache''ê ho kerd pak.",
 'welcomecreation' => '== Xêr amê, $1! ==
 Hesabê sıma vıraciya.
index e5af115..39fd628 100644 (file)
@@ -756,7 +756,7 @@ $2',
 # Login and logout pages
 'logouttext' => "'''Жүйеден шықтыңыз.'''
 
-Жүйеге кірместен де {{SITENAME}} жобасын пайдалана аласыз, немесе баяғы не өзге қатысушы ретінде жүйеге [[Special:UserLogin|қайта кіруіңізге]] болады.
+Жүйеге кірместен де {{SITENAME}} жобасын пайдалана аласыз, немесе баяғы не өзге қатысушы ретінде жүйеге <span class='plainlinks'>[$1 қайта кіруіңізге]</span> болады.
 Аңғартпа: Кейбір беттер шолғышыңыздың кэшін тазартқанша әлі де жүйеге кіріп отырғаныңыздай көрінуі мүмкін.",
 'welcomecreation' => '== Қош келдіңіз, $1! ==
 Жаңа тіркелгіңіз жасалды.
index f6d1c60..7f53780 100644 (file)
@@ -419,7 +419,7 @@ $messages = array(
 'vector-action-protect' => 'ការពារ',
 'vector-action-undelete' => 'ឈប់លុបចោល',
 'vector-action-unprotect' => 'ប្ដូរការការពារ',
-'vector-simplesearch-preference' => 'á\9e\94á\9f\92á\9e\9aá\9e¾á\9e¢á\9e\93á\9e»á\9e\9fá\9e¶á\9e\9fá\9e\93á\9f\8dá\9e\96á\9e¶á\9e\80á\9f\92á\9e\99á\9e\85á\9e\84á\9f\8bá\9e\9fá\9f\92á\9e\9cá\9f\82á\9e\84á\9e\9aá\9e\80 (សំរាប់តែសំបកវ៉ិចទ័រប៉ុណ្ណោះ)',
+'vector-simplesearch-preference' => 'á\9e\94á\9f\92á\9e\9aá\9e¾á\9e\9aá\9e\94á\9e¶á\9e\9aá\9e\9fá\9f\92á\9e\9cá\9f\82á\9e\84á\9e\9aá\9e\80á\9e\9fá\9e¶á\9e\98á\9e\89á\9f\92á\9e\89 (សំរាប់តែសំបកវ៉ិចទ័រប៉ុណ្ណោះ)',
 'vector-view-create' => 'បង្កើត​',
 'vector-view-edit' => 'កែប្រែ​',
 'vector-view-history' => 'មើល​ប្រវត្តិ​',
@@ -633,7 +633,8 @@ $1',
 'protectedpagetext' => 'ទំព័រនេះបានត្រូវចាក់សោមិនឱ្យកែប្រែ​។',
 'viewsourcetext' => 'អ្នកអាចមើលនិងចម្លងកូដរបស់ទំព័រនេះ៖',
 'viewyourtext' => "អ្នកអាចមើលនិងចម្លងកូដរបស់'''ការកែប្រែរបស់អ្នក'''ទៅកាន់ទំព័រនេះ៖",
-'protectedinterface' => 'ទំព័រនេះ ផ្ដល់នូវ អត្ថបទអន្តរមុខ សម្រាប់ផ្នែកទន់, និង បានត្រូវចាក់សោ ដើម្បីចៀសវាង ការបំពាន ។',
+'protectedinterface' => 'ទំព័រនេះផ្ដល់នូវអត្ថបទអន្តរមុខសម្រាប់សូហ្វវែរនៅក្នុងវិគីនេះ និងត្រូវបានចាក់សោដើម្បីចៀសវាងការបំពាន។
+ដើម្បីបន្ថែមឬផ្លាស់ប្ដូរការបកប្រែសំរាប់វិគីទាំងអស់ សូមប្រើប្រាស់ [//translatewiki.net/ translatewiki.net] ដែលជាគំរោងបកប្រែរបស់MediaWiki។',
 'editinginterface' => "'''ប្រយ័ត្ន៖''' អ្នកកំពុងតែកែប្រែទំព័រដែលបានប្រើប្រាស់​ដើម្បីផ្ដល់នូវអន្តរមុខសម្រាប់ផ្នែកទន់​។ បំលាស់ប្ដូរចំពោះទំព័រនេះ​នឹងប៉ះពាល់ដល់ទំព័រអន្តរមុខនៃអ្នកប្រើប្រាស់​ជាច្រើន ដែលប្រើប្រាស់វិបសាយនេះ។ សម្រាប់ការបកប្រែ សូមពិចារណាប្រើប្រាស់ [//translatewiki.net/wiki/Main_Page?setlang=km translatewiki.net] (បេតាវិគី) គម្រោង​អន្តរជាតូបនីយកម្ម​នៃមេឌាវិគី ។",
 'sqlhidden' => '(ការអង្កេត SQL ត្រូវបិទបាំង)',
 'cascadeprotected' => 'ទំព័រនេះត្រូវបានការពារពីការការប្រែដោយសារវាមាន{{PLURAL:$1|ទំព័រ, ដែលមាន}} ដែលត្រូវបានការពារជាមួយជំរើស"ជាបណ្ដាក់"៖
@@ -657,7 +658,7 @@ $2',
 # Login and logout pages
 'logouttext' => "'''ឥឡូវនេះលោកអ្នកបានកត់ឈ្មោះចេញពីគណនីរបស់លោកអ្នកហើយ។'''
 
-អ្នកអាចបន្តប្រើប្រាស់{{SITENAME}}ក្នុងភាពអនាមិក ឬ [[Special:UserLogin|កត់ឈ្មោះចូលម្ដងទៀត]]ក្នុងនាមជាអ្នកប្រើប្រាស់ដដែលឬផ្សេងទៀត។
+អ្នកអាចបន្តប្រើប្រាស់{{SITENAME}}ក្នុងភាពអនាមិក ឬ <span class='plainlinks'>[$1 កត់ឈ្មោះចូលម្ដងទៀត]</span>ក្នុងនាមជាអ្នកប្រើប្រាស់ដដែលឬផ្សេងទៀត។
 
 សូមកត់សំគាល់ថាទំព័រមួយចំនួនប្រហែលជានៅតែបង្ហាញដូចពេលលោកអ្នកកត់ឈ្មោះចូលក្នុងគណនីរបស់លោកអ្នកដដែល។ ប្រសិនបើមានករណីនេះកើតឡើង សូមសំអាត សតិភ្ជាប់នៃកម្មវិធីរុករករបស់លោកអ្នក។",
 'welcomecreation' => '== សូមស្វាគមន៍ $1! ==
@@ -952,7 +953,7 @@ $2
 អ្នកអាច [[Special:Search/{{PAGENAME}}|ស្វែងរក​ចំណងជើង​នៃទំព័រនេះ]]ក្នុងទំព័រដទៃទៀត​​ ឬ [{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} ស្វែង​រក​កំណត់​ហេតុ​ដែល​ពាក់ព័ន្ធ] ឬ [{{fullurl:{{FULLPAGENAME}}|action=edit}} កែប្រែ​ទំព័រនេះ]។',
 'noarticletext-nopermission' => 'បច្ចុប្បន្ន គ្មានអត្ថបទណាមួយក្នុងទំព័រនេះទេ។
 
-អ្នកអាច [[Special:Search/{{PAGENAME}}|ស្វែងរក​ចំណងជើង​នៃទំព័រនេះ]] ក្នុងទំព័រ​ផ្សេងៗ ឬ<span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} ស្វែង​រក​កំណត់​ហេតុ​ដែល​ពាក់ព័ន្ធ]</span>។',
+អ្នកអាច [[Special:Search/{{PAGENAME}}|ស្វែងរក​ចំណងជើង​នៃទំព័រនេះ]] ក្នុងទំព័រ​ផ្សេងៗ ឬ<span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} ស្វែង​រក​កំណត់​ហេតុ​ដែល​ពាក់ព័ន្ធ]</span>។ ប៉ុន្តែអ្នកគ្មានសិទ្ធិក្នុងការបង្កើតទំព័រនេះទេ។',
 'userpage-userdoesnotexist' => 'គណនីអ្នកប្រើឈ្មោះ"<nowiki>$1</nowiki>" មិនទាន់បានចុះបញ្ជី។
 
 ចូរគិតម្ដងទៀតថាអ្នកចង់ បង្កើត / កែប្រែ ទំព័រនេះឬទេ។',
@@ -2949,7 +2950,6 @@ $1',
 'pageinfo-authors' => 'ចំនួនអ្នកនិពន្ធសរុប',
 'pageinfo-recent-edits' => 'ចំនួនការកែប្រែថ្មីៗ (ក្នុងរយៈពេល $1 កន្លងទៅនេះ)',
 'pageinfo-recent-authors' => 'ចំនួនអ្នកនិពន្ធថ្មីៗនេះ',
-'pageinfo-restriction' => 'ការការពារទំព័រ ({{$1}})',
 'pageinfo-magic-words' => '{{PLURAL:$1|ពាក្យ|ពាក្យ}} វេទមន្ត ($1)',
 'pageinfo-hidden-categories' => '{{PLURAL:$1|ចំណាត់ថ្នាក់ក្រុម|ចំណាត់ថ្នាក់ក្រុម}}ដែលបានលាក់ ($1)',
 
index 5a81333..de43d34 100644 (file)
@@ -437,7 +437,7 @@ $2',
 # Login and logout pages
 'logouttext' => "'''ನೀವು ಈಗ ಲಾಗ್ ಔಟ್ ಆಗಿರುವಿರಿ.'''
 
-ನೀವು {{SITENAME}} ಅನ್ನು ಅನಾಮಧೇಯವಾಗಿ ಉಪಯೋಗಿಸಬಹುದು, ಅಥವ ಮತ್ತೆ ಇದೇ ಹೆಸರಿನಲ್ಲಿ ಅಥವ ಬೇರೆ ಹೆಸರಿನಲ್ಲಿ [[Special:UserLogin|ಲಾಗ್ ಇನ್]] ಆಗಬಹುದು.
+ನೀವು {{SITENAME}} ಅನ್ನು ಅನಾಮಧೇಯವಾಗಿ ಉಪಯೋಗಿಸಬಹುದು, ಅಥವ ಮತ್ತೆ ಇದೇ ಹೆಸರಿನಲ್ಲಿ ಅಥವ ಬೇರೆ ಹೆಸರಿನಲ್ಲಿ <span class='plainlinks'>[$1 ಲಾಗ್ ಇನ್]</span> ಆಗಬಹುದು.
 ಗಮನಿಸಿ: ನಿಮ್ಮ ಬ್ರೌಸರ್‍ನ cache ಅನ್ನು ಅಳಿಸುವವರೆಗೂ ಕೆಲವು ಪುಟಗಳು ನೀವಿನ್ನೂ ಲಾಗ್ ಇನ್ ಆಗಿರುವಂತೆ ಪ್ರದರ್ಶಿತವಾಗಬಹುದು.",
 'welcomecreation' => '== ಸುಸ್ವಾಗತ, $1! ==
 ನಿಮ್ಮ ಅಕೌಂಟನ್ನು ಸೃಷ್ಟಿಸಲಾಗಿದೆ.
index 11f10ae..33d7793 100644 (file)
@@ -755,7 +755,7 @@ $2',
 # Login and logout pages
 'logouttext' => "'''{{SITENAME}}에서 로그아웃했습니다.'''
 
-이대로 이름 없이 {{SITENAME}}을(를) 이용하거나, 방금 사용했던 계정이나 다른 계정으로 다시 [[Special:UserLogin|로그인]]해서 이용할 수 있습니다.
+이대로 이름 없이 {{SITENAME}}을(를) 이용하거나, 방금 사용했던 계정이나 다른 계정으로 다시 <span class='plainlinks'>[$1 로그인]</span>해서 이용할 수 있습니다.
 웹 브라우저의 캐시를 지우지 않으면 몇몇 문서에서 로그인이 되어 있는 것처럼 보일 수 있다는 점을 유의해 주세요.",
 'welcomecreation' => '== $1 님, 환영합니다! ==
 계정이 만들어졌습니다.
@@ -947,7 +947,7 @@ $2
 'hr_tip' => '가로 줄 (되도록 사용하지 말아 주세요)',
 
 # Edit pages
-'summary' => '편집 요약:',
+'summary' => '요약:',
 'subject' => '주제/제목:',
 'minoredit' => '사소한 편집',
 'watchthis' => '이 문서 주시하기',
@@ -964,7 +964,7 @@ $2
 'missingcommenttext' => '아래에 내용을 채워 넣어 주세요.',
 'missingcommentheader' => "'''알림:''' 글의 제목을 입력하지 않았습니다.
 다시 \"{{int:savearticle}}\" 버튼을 클릭하면 글이 제목 없이 저장됩니다.",
-'summary-preview' => '편집 요약 미리 보기:',
+'summary-preview' => '요약 미리 보기:',
 'subject-preview' => '주제/제목 미리 보기:',
 'blockedtitle' => '차단됨',
 'blockedtext' => "'''당신의 계정 혹은 IP 주소가 차단되었습니다.'''
@@ -3224,10 +3224,10 @@ $1 사용자가 차단된 이유는 다음과 같습니다: "$2"',
 'pageinfo-authors' => '총 서로 다른 편집자 수',
 'pageinfo-recent-edits' => '최근 편집 수 (지난 $1일 이내)',
 'pageinfo-recent-authors' => '최근 기여자 수',
-'pageinfo-restriction' => '문서 보호 ({{lcfirst:$1}})',
 'pageinfo-magic-words' => '매직 {{PLURAL:$1|워드}} ($1개)',
 'pageinfo-hidden-categories' => '숨은 {{PLURAL:$1|분류}} ($1개)',
 'pageinfo-templates' => '포함한 {{PLURAL:$1|틀}} ($1개)',
+'pageinfo-toolboxlink' => '문서 정보',
 
 # Skin names
 'skinname-standard' => '클래식',
@@ -3828,6 +3828,7 @@ $5
 # Scary transclusion
 'scarytranscludedisabled' => '[인터위키가 비활성되어 있습니다]',
 'scarytranscludefailed' => '[$1 틀을 불러오는 데에 실패했습니다]',
+'scarytranscludefailed-httpstatus' => '[$1 틀을 가져오는 데 실패했습니다: HTTP $2]',
 'scarytranscludetoolong' => '[URL이 너무 깁니다]',
 
 # Delete conflict
index d4b01b4..acfa367 100644 (file)
@@ -428,7 +428,7 @@ $2',
 # Login and logout pages
 'logouttext' => "'''Аккаунтугъуздан чыкъдыгъыз.'''
 
-Сиз {{SITENAME}} сайтда аноним халда къалыргъа боллкъсуз. неда [[Special:UserLogin|джангыдан кирирге]].
+Сиз {{SITENAME}} сайтда аноним халда къалыргъа боллкъсуз. неда <span class='plainlinks'>[$1 джангыдан кирирге]</span>.
 Талай бетле сиз тергеу джазыу (аккаунт) бла киргенча кёрюнюрге боллукъдула, аны кетерир ючюн кэшни джангыртыгъыз.",
 'welcomecreation' => '== Хош келигиз, $1!  ==
 Сизни тергеу джазыуугъуз (аккаунтугъуз) къуралды.
index 77ece5d..51f588d 100644 (file)
@@ -635,7 +635,7 @@ Dä Wiki_Köbes dovun hät beim Deeschmaache als Jrond aanjejovve: „$3“',
 # Login and logout pages
 'logouttext' => "'''Jetz bes de usjelogg'''
 
-Do künnts heh em Wiki wigger maache, als ene namelose Metmaacher. Do kanns De ävver och [[Special:UserLogin|widder enlogge]], als däselve oder och ene andere Metmaacher.
+Do künnts heh em Wiki wigger maache, als ene namelose Metmaacher. Do kanns De ävver och <span class='plainlinks'>[\$1 widder enlogge]</span>, als däselve oder och ene andere Metmaacher.
 Künnt sin, dat De de ein oder ander Sigg immer wigger aanjezeich kriss, wie wann de noch enjelogg wörs. Dun Dingem Brauser singe <i lang=\"en\">Cache</i> fottschmieße oder leddich maache, öm us dä Nummer erus ze kumme!",
 'welcomecreation' => '== Dach, $1! ==
 Dinge Zojang för heh es do.
@@ -1540,11 +1540,11 @@ Ene zohfällesch ußjewörfelte Schlößel, dää De nämme künnß, wöhr: <cod
 'group-suppress' => 'Kontrollettis',
 'group-all' => '(jeede)',
 
-'group-user-member' => '{{GENDER:$1|Metmaacher|Metmaacherin}}',
+'group-user-member' => '{{GENDER:$1|Metmaacher|Metmaacherėn}}',
 'group-autoconfirmed-member' => 'automattesch beshtääteshte {{GENDER:$1|Metmaacher|Metmaacherėn}}',
 'group-bot-member' => '{{GENDER:$1|Bot}}',
 'group-sysop-member' => '{{GENDER:$1|Wiki-Köbes}}',
-'group-bureaucrat-member' => '{{GENDER:$1|Bürrokrad|Bürrokraatėn}}',
+'group-bureaucrat-member' => '{{GENDER:$1|Bürrokraad|Bürrokraadefrou}}',
 'group-suppress-member' => '{{GENDER:$1|Kontrolletti}}',
 
 'grouppage-user' => '{{ns:project}}:Metmaacher',
@@ -3228,10 +3228,10 @@ Esu kam_mer noch en Aanmerkung en „{{int:summary}}“ maache.',
 'pageinfo-authors' => 'De Aanzahl ongerscheidleje Schriever',
 'pageinfo-recent-edits' => 'De Aanzahl Änderonge en dä läzde Zik, ennerhallf vun $1',
 'pageinfo-recent-authors' => 'De Aanzahl ongerscheidleje Schriever en dä läzde Zik',
-'pageinfo-restriction' => 'Siggeschoz ({{lcfirst:$1}})',
 'pageinfo-magic-words' => '{{PLURAL:$1|Ei Zauberwoot|$1 Zauberwööter|Kein Zauberwööter}}',
 'pageinfo-hidden-categories' => '{{PLURAL:$1|Ein verstoche Saachjropp|$1 verstoche Saachjroppe|Kein verstoche Saachjropp}}',
 'pageinfo-templates' => '{{PLURAL:$1|Ein Schablohn|$1 Schablohne|Kein Schablohn}} opjerohfe',
+'pageinfo-toolboxlink' => 'Övver heh di Sigg',
 
 # Skin names
 'skinname-standard' => 'Klassesch',
@@ -3841,7 +3841,8 @@ Domet deiß De tirek sare, dat De di Adress nit bestätije wells.',
 
 # Scary transclusion
 'scarytranscludedisabled' => '[Et Enbinge per Interwiki es avjeschalt]',
-'scarytranscludefailed' => '[De Schablon „$1“ enzebinge hät nit jeflupp]',
+'scarytranscludefailed' => '[De Schablon „$1“ enzebenge hät nit jeflupp]',
+'scarytranscludefailed-httpstatus' => '[De Schablon „$1“ enzebenge hät nit jeflupp. Dä HTTP-Fähler es: $2]',
 'scarytranscludetoolong' => '[Schad, de URL es ze lang]',
 
 # Delete conflict
index d88006e..11dee6e 100644 (file)
@@ -451,7 +451,7 @@ $2',
 # Login and logout pages
 'logouttext' => "'''Tu niha derketî.'''
 
-Tu dikarî {{SITENAME}} niha weke bikarhênerekî nediyarkirî bikarbînî, yan jî tu dikarî dîsa bi vî navê xwe yan navekî din wek bikarhêner [[Special:UserLogin|dîsa têkevî]].
+Tu dikarî {{SITENAME}} niha weke bikarhênerekî nediyarkirî bikarbînî, yan jî tu dikarî dîsa bi vî navê xwe yan navekî din wek bikarhêner <span class='plainlinks'>[$1 dîsa têkevî]</span>.
 Bila di bîra te de be ku gengaz e hin rûpel mîna ku tu hîn bi navê xwe qeyd kiriyî werin nîşandan, heta ku tu nîşanên çavlêgerandina (browser) xwe jênebî.",
 'welcomecreation' => '== Tu bi xêr hatî, $1! ==
 
@@ -1599,8 +1599,9 @@ Ji bo jêbirinan û çêkirinên nû, ji kerema xwe li [[{{ns:special}}:Log/dele
 'whatlinkshere-prev' => '{{PLURAL:$1|yê|$1 yên}} berê',
 'whatlinkshere-next' => '{{PLURAL:$1|yê|$1 yên}} din',
 'whatlinkshere-links' => '← girêdan',
-'whatlinkshere-hideredirs' => '$1 beralîkirin',
-'whatlinkshere-hidelinks' => '$1 lînkan',
+'whatlinkshere-hideredirs' => 'Beralîkirinan $1',
+'whatlinkshere-hidetrans' => 'Naverokan $1',
+'whatlinkshere-hidelinks' => 'Lînkan $1',
 'whatlinkshere-hideimages' => '$1 lînkên wêneyan',
 'whatlinkshere-filters' => 'Parzûn',
 
index 2852e1d..96e79b7 100644 (file)
@@ -494,7 +494,7 @@ Ratio data est "\'\'$2\'\'".',
 # Login and logout pages
 'logouttext' => "'''Conventum tuum conclusum est.'''
 
-Ignote continues {{grammar:ablative|{{SITENAME}}}} uti, aut conventum novum vel sub eodem vel novo nomine [[Special:UserLogin|aperias]].
+Ignote continues {{grammar:ablative|{{SITENAME}}}} uti, aut conventum novum vel sub eodem vel novo nomine <span class='plainlinks'>[$1 aperias]</span>.
 Nota bene paginas fortasse videantur quasi tuum conventum esset apertum, priusquam navigatrum purgaveris.",
 'welcomecreation' => '== Salve, $1! ==
 Ratio tua iam creata est.
index 69d11ff..b22f804 100644 (file)
@@ -586,7 +586,7 @@ $2',
 # Login and logout pages
 'logouttext' => "'''Dir sidd elo ausgeloggt.'''
 
-Dir kënnt {{SITENAME}} elo anonym benotzen, oder Iech [[Special:UserLogin|erëm aloggen]].
+Dir kënnt {{SITENAME}} elo anonym benotzen, oder Iech <span class='plainlinks'>[$1 erëm aloggen]</span>.
 
 Opgepasst: Op verschiddene Säite kann et nach esou aus gesinn, wéi wann Dir nach ageloggt wiert, bis Dir Ärem Browser säin Tëschespäicher (cache) eidel maacht.",
 'welcomecreation' => '== Wëllkomm, $1! ==
@@ -835,6 +835,10 @@ Dir kënnt op anere Säiten no [[Special:Search/{{PAGENAME}}|dësem Säitentitel
 <span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} an den entspriechende Logbicher nokucken] oder [{{fullurl:{{FULLPAGENAME}}|action=edit}} esou eng Säit uleeën]</span>.',
 'noarticletext-nopermission' => 'Elo ass keen Text op dëser Säit.
 Dir kënnt op anere Säiten [[Special:Search/{{PAGENAME}}|no dësem Sàitentitel sichen]], oder <span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} an de Logbicher sichen]</span>, mä Dir hutt net déi néideg Rechter fir dës Säit unzeleeën.',
+'missing-revision' => 'D\'Versioun #$1 vun der Säit mam Numm "{{PAGENAME}}" gëtt et net.
+
+Dat geschitt normalerweis wann Dir op e vereelste Link vun enger Versioun vun enger Säit klickt déi geläscht ginn ass.
+Detailer fannt Dir am [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} Logbuch vum Läschen].',
 'userpage-userdoesnotexist' => 'De Benotzerkont "<nowiki>$1</nowiki>" ass net registréiert.
 Iwwerpréift w.e.g. op Dir dës Säit uleeën/ännere wëllt.',
 'userpage-userdoesnotexist-view' => 'De Benotzerkont "$1" ass net registréiert.',
@@ -1758,7 +1762,7 @@ Kuckt w.e.g. no op kee Feeler an der URL ass an op de Site och online ass.',
 # Special:ListFiles
 'listfiles-summary' => 'Op dëser Spezialsäit stinn all déi eropgeluede Fichieren.
 
-Wann se pro Benotzer gefiltert sinn, ginn nëmmen déi Fichiere gewise wou dee Benotzer déi lescht Versioun vum Fichier eropgelueden huet.',
+Wa se pro Benotzer gefiltert sinn, ginn nëmmen déi Fichiere gewise wou dee Benotzer déi lescht Versioun vum Fichier eropgelueden huet.',
 'listfiles_search_for' => 'Sicht nom Fichier:',
 'imgfile' => 'Fichier',
 'listfiles' => 'Lëscht vun de Fichieren',
@@ -1873,7 +1877,7 @@ Dir musst ëmmer de Medien- a Subtyp aginn: z. Bsp. <code>image/jpeg</code>.",
 'statistics' => 'Statistik',
 'statistics-header-pages' => 'Säitestatistiken',
 'statistics-header-edits' => 'Statistik vun den Ännerungen',
-'statistics-header-views' => "Sttistiken iwwert d'Visiten",
+'statistics-header-views' => "Statistiken iwwert d'Visiten",
 'statistics-header-users' => 'Benotzerstatistik',
 'statistics-header-hooks' => 'Aner Statistiken',
 'statistics-articles' => 'Säite mat Inhalt',
@@ -2768,6 +2772,7 @@ Späichert en op Ärem Computer of a luet en hei nees erop.',
 'import-error-special' => 'D\'Säit "$1" gouf net importéiert well se zu engem speziellen Nummraum gehéiert an deem et keng Säite gëtt.',
 'import-error-invalid' => 'D\'Säit "$1" gouf net importéiert well hiren Numm net valabel ass.',
 'import-options-wrong' => 'Falsch {{PLURAL:$2|Optioun|Optiounen}}: <nowiki>$1</nowiki>',
+'import-rootpage-invalid' => 'Déi Basis-Säit déi Dir uginn hutt ass kee valabelen Titel.',
 
 # Import log
 'importlogpage' => 'Lëscht vun den Säitenimporten',
@@ -2915,10 +2920,10 @@ Dëst warscheinlech duerch en externe Link den op der schwaarzer Lëscht (blackl
 'pageinfo-authors' => 'Gesamtzuel vun de verschiddenen Auteuren',
 'pageinfo-recent-edits' => 'Zuel vun de rezenten Ännerungen (an de leschten $1)',
 'pageinfo-recent-authors' => 'Zuel vun de verschiddenen Auteuren',
-'pageinfo-restriction' => 'Protectioun vun der Säit ({{lcfirst:$1}})',
 'pageinfo-magic-words' => '{{PLURAL:$1|Magescht Wuert|Magesch Wierder}} ($1)',
 'pageinfo-hidden-categories' => 'Verstoppte {{PLURAL:$1|Kategorie|Kategorien}} ($1)',
 'pageinfo-templates' => 'Agebonne {{PLURAL:$1|Schabloun|Schabloune}} ($1)',
+'pageinfo-toolboxlink' => "Informatiounen iwwert d'Säit",
 
 # Skin names
 'skinname-standard' => 'Klassesch',
@@ -3479,6 +3484,7 @@ Dëse Confirmatiouns-Code leeft den $4 of.',
 # Scary transclusion
 'scarytranscludedisabled' => '[Interwiki-Abannung ass ausgeschalt]',
 'scarytranscludefailed' => "[D'Siche no der Schabloun fir $1 huet net funktionéiert]",
+'scarytranscludefailed-httpstatus' => "[D'Oprufe vun der Schabloun $1: HTTP $2 huet net fonctionnéiert]",
 'scarytranscludetoolong' => "[D'URL ass ze laang]",
 
 # Delete conflict
index 33b8ea1..1a928c7 100644 (file)
@@ -400,7 +400,7 @@ Ensonga gy\'awadde eri nti "\'\'$2\'\'".',
 # Login and logout pages
 'logouttext' => "'''Kati ovuddemu.'''
 
-Osobola okusigala nga okozesa {{SITENAME}} nga at'eyanjudde, ate osobola [[Special:UserLogin|n'okuddamu okuyingira]] nga bw'obadde oba nga okozesezza ery'obwa memba eddala.
+Osobola okusigala nga okozesa {{SITENAME}} nga at'eyanjudde, ate osobola <span class='plainlinks'>[$1 n'okuddamu okuyingira]</span> nga bw'obadde oba nga okozesezza ery'obwa memba eddala.
 Wekkaanye, empapula ezimu ziyinza okukweyolekera nga bwe zibadde nga oyingidde - okutuusa lw'okunkumula eggwanika ezzibizi erya kalambula-neti yo.",
 'welcomecreation' => "== $1 tukwanirizza! == <br />
 Akawunti yo ekoledwa.<br />
index 84fed12..7360204 100644 (file)
@@ -558,7 +558,7 @@ d\'n Opgegaeve raej vanne sloetendje admin waar "\'\'$3\'\'".',
 # Login and logout pages
 'logouttext' => "'''De bis noe aafgemeld.'''
 
-De kèns {{SITENAME}} noe anoniem (mit vermeljing van IP-adres) gebroeke, of [[Special:UserLogin|opnuuj aanmelde]] ónger dezelfde of 'ne angere naam.
+De kèns {{SITENAME}} noe anoniem (mit vermeljing van IP-adres) gebroeke, of <span class='plainlinks'>[$1 opnuuj aanmelde]</span> ónger dezelfde of 'ne angere naam.
 Mäögelik waert nog 'n deil pagina's getuind esofs te nog aangemeld bis pès te de cache van diene browser laeg maaks.",
 'welcomecreation' => '== Wèlkóm, $1! ==
 Diene gebroeker is noe vaerdig.
index 7406469..adaa3c1 100644 (file)
@@ -420,7 +420,7 @@ Per piasè, fa raport a 'n'[[Special:ListUsers/sysop|aministradur]], cun la nota
 # Login and logout pages
 'logouttext' => "'''Adess a sii descuness.'''
 
-A pudé andà inanz a druvà la {{SITENAME}} in manera anònima, o a pudé [[Special:UserLogin|cunètev anmò]] cun l'istess suranomm o cun un suranomm diferent.
+A pudé andà inanz a druvà la {{SITENAME}} in manera anònima, o a pudé <span class='plainlinks'>[$1 cunètev anmò]</span> cun l'istess suranomm o cun un suranomm diferent.
 Tegné cünt che certi paginn pödass che i seguiten a vedess tant 'me se a füdìssuv anmò cuness, fin quand che hii nò vudaa 'l ''cache'' del voster browser.",
 'welcomecreation' => "== Benvegnüü, $1! ==
 'L to cünt l'è staa pruntaa. Desmenteghet mía de mudifegà i to [[Special:Preferences|preferenz de {{SITENAME}}]].",
index b5eda82..724e7e9 100644 (file)
@@ -588,7 +588,7 @@ Ją užrakinęs administratorius pateikė šį paaiškinimą: "$3".',
 # Login and logout pages
 'logouttext' => "'''Dabar jūs esate atsijungęs.'''
 
-Galite toliau naudoti {{SITENAME}} anonimiškai arba [[Special:UserLogin|prisijunkite]] iš naujo tuo pačiu ar kitu naudotoju.
+Galite toliau naudoti {{SITENAME}} anonimiškai arba <span class='plainlinks'>[$1 prisijunkite]</span> iš naujo tuo pačiu ar kitu naudotoju.
 Pastaba: kai kuriuose puslapiuose ir toliau gali rodyti, kad esate prisijungęs iki tol, kol išvalysite savo naršyklės podėlį.",
 'welcomecreation' => '== Sveiki, $1! ==
 
index d2af51a..df75921 100644 (file)
@@ -394,7 +394,7 @@ $2',
 
 # Login and logout pages
 'logouttext' => "'''I chhuak fel ta.'''
-Inziaklût kher lovin {{SITENAME}} hi i hmang chhunzawm thei ang, a nih loh vëk pawhin hmangtu hming pangngai emaw, a hming dang emawin [[Special:UserLogin|lût leh]] thei ang.
+Inziaklût kher lovin {{SITENAME}} hi i hmang chhunzawm thei ang, a nih loh vëk pawhin hmangtu hming pangngai emaw, a hming dang emawin <span class='plainlinks'>[$1 lût leh]</span> thei ang.
 I fangtu cache i thenfai hma chu phêk ţhenkhat intar lang a awm reng mai thei, i la  chhuak lo emaw tih mai tùrin.",
 'welcomecreation' => '==Kan lo lawm a che, $1!==
 I siangchan siam a ni ta.
index 8ac36d0..72e6a04 100644 (file)
@@ -429,7 +429,7 @@ Norādītais iemesls bija ''$2''.",
 # Login and logout pages
 'logouttext' => "'''Tu esi izgājis no {{grammar:ģenitīvs|{{SITENAME}}}}.'''
 
-Vari turpināt to izmantot anonīmi, vari [[Special:UserLogin|atgriezties]] kā cits lietotājs vai varbūt tas pats.
+Vari turpināt to izmantot anonīmi, vari <span class='plainlinks'>[$1 atgriezties]</span> kā cits lietotājs vai varbūt tas pats.
 Ņem vērā, ka arī pēc iziešanas, dažas lapas var tikt parādītas tā, it kā tu vēl būtu iekšā, līdz tiks iztīrīta pārlūka kešatmiņa.",
 'welcomecreation' => '== Laipni lūdzam, $1! ==
 
index 907e269..b519d82 100644 (file)
@@ -521,7 +521,7 @@ $2',
 # Login and logout pages
 'logouttext' => "'''子去簿矣'''
 
-子可匿名還覽{{SITENAME}},或[[Special:UserLogin|復登]]同簿、異簿。
+子可匿名還覽{{SITENAME}},或<span class='plainlinks'>[$1 復登]</span>同簿、異簿。
 未清謄本,覽器文舊,且慎之。",
 'welcomecreation' => '== $1大駕光臨! ==
 子簿增矣,敬更[[Special:Preferences|簿註]]。',
index 7c760c9..2cdf041 100644 (file)
@@ -422,7 +422,7 @@ $2',
 # Login and logout pages
 'logouttext' => "'''अहाँ निष्क्रमण कऽ गेल छी।'''
 
-अहाँ {{अन्तर्जाल}} प्रयोग अनाम भऽ कऽ सकै छी, वा अहाँ [[Special:UserLogin|log in again]] वएह आकि कोनो आन प्रयोक्ताक रूपमे सेहू प्रयोक कऽ सकै छी।
+अहाँ {{अन्तर्जाल}} प्रयोग अनाम भऽ कऽ सकै छी, वा अहाँ <span class='plainlinks'>[$1 log in again]</span> वएह आकि कोनो आन प्रयोक्ताक रूपमे सेहू प्रयोक कऽ सकै छी।
 ई मोन राखू जे किछु पन्ना एना देखा पड़ि सकैए जेना अहाँ अखनो सम्प्रवेशित होइ, जावत अहाँ अपन गवेषकक उपस्मृति मेटा नै दै छी।",
 'welcomecreation' => '== स्वागत अछि, $1! ==
 अहाँक खाता खुजि गेल अछि।
index 87e55c3..7b6732d 100644 (file)
@@ -409,7 +409,7 @@ Alesane yakuwe "\'\'$2\'\'".',
 # Login and logout pages
 'logouttext' => "'''Rika uwis metu log sekang sistem.'''
 
-Rika teyeng terus nggunakna {{SITENAME}} kanthi anonim, utawa Rika teyeng [[Special:UserLogin|mlebu log maning]] nganggo jeneng panganggo sing padha utawa sejene.
+Rika teyeng terus nggunakna {{SITENAME}} kanthi anonim, utawa Rika teyeng <span class='plainlinks'>[$1 mlebu log maning]</span> nganggo jeneng panganggo sing padha utawa sejene.
 Digatekna ya, nek ana kaca sing esih terus nidokna nek rika esih mlebu log nnganti Rika mbusak singgahan nang panjelajah web-e Rika.",
 'welcomecreation' => '== Sugeng rawuh, $1! ==
 
index 1d939ad..526f075 100644 (file)
@@ -498,7 +498,7 @@ $2',
 # Login and logout pages
 'logouttext' => "'''Тон лисеть.'''
 
-Тондейть ули кода ащемс {{SITENAME}}са апак содак эли [[Special:UserLogin|сувак тага весть]] кода сяка эли иля тиись.
+Тондейть ули кода ащемс {{SITENAME}}са апак содак эли <span class='plainlinks'>[$1 сувак тага весть]</span> кода сяка эли иля тиись.
 Кой-кона лопатне илядсть стамкс кодамкс синь ульсть тонь лисемада инголе мъзярс тонь интернет полатксце изь аруяфтов эсь ванфневи файлхнень эзда.",
 'welcomecreation' => '== Сувак, $1! ==
 
index 26b0f41..ec6defb 100644 (file)
@@ -611,7 +611,7 @@ Ny antony napetraka dia : « ''$2'' ».",
 # Login and logout pages
 'logouttext' => "'''Tafavoaka ianao ankehitriny.'''
 
-Mbola afaka mampiasa ny {{SITENAME}} ianao na dia ef anivoaka aza, na afaka [[Special:UserLogin|miverina mihiditra]] ianao ambanin'ny anaranao na anaram-pikambana hafa.
+Mbola afaka mampiasa ny {{SITENAME}} ianao na dia ef anivoaka aza, na afaka <span class='plainlinks'>[$1 miverina mihiditra]</span> ianao ambanin'ny anaranao na anaram-pikambana hafa.
 Fantaro fa ny endriky ny pejy sasany dia mety mitovy amin'ny endrika nahitanao azy tamin' ianao mbola niditra tato, ho toy izany ny endri-pejy raha tsy nofafanao ny cache.",
 'welcomecreation' => '== Tonga soa, $1! ==
 
index f248963..b5f7718 100644 (file)
@@ -400,7 +400,7 @@ Alasan nan diberikan adolah ''$2''.",
 # Login and logout pages
 'logouttext' => "'''Sanak alah kalua log dari sistem.'''
 
-Sanak dapek taruih manggunoan {{SITENAME}} sacaro anonim, atau Sanak dapek [[Special:UserLogin|masuak log liak]] sabagai pangguno nan samo atau pangguno nan lain.
+Sanak dapek taruih manggunoan {{SITENAME}} sacaro anonim, atau Sanak dapek <span class='plainlinks'>[$1 masuak log liak]</span> sabagai pangguno nan samo atau pangguno nan lain.
 Parhatian bahawa bara laman mungkin masih taruih manunjukkan bahawa Sanak masih masuak log sampai Sanak mambarasihan singgahan panjelajah web Sanak.",
 'welcomecreation' => '== Salamaik datang, $1! ==
 
index eb1d435..0324a11 100644 (file)
@@ -761,7 +761,7 @@ $2',
 # Login and logout pages
 'logouttext' => "'''Сега сте одјавени.'''
 
-Можете да продолжите со користење на {{SITENAME}} анонимно или можете [[Special:UserLogin|повторно да се најавите]] под исто или различно корисничко име.
+Можете да продолжите со користење на {{SITENAME}} анонимно или можете <span class='plainlinks'>[$1 повторно да се најавите]</span> под исто или различно корисничко име.
 Да напоменеме дека некои страници може да продолжат да се прикажуваат како да сте најавени, се додека не го исчистите кешот на вашиот прелистувач.",
 'welcomecreation' => '== Добредојдовте, $1! ==
 Вашата корисничка сметка е создадена.
@@ -3219,10 +3219,10 @@ $1',
 'pageinfo-authors' => 'Број на засебни автори',
 'pageinfo-recent-edits' => 'Број на скорешни уредувања (во последните $1)',
 'pageinfo-recent-authors' => 'Број на скорешни засебни автори',
-'pageinfo-restriction' => 'Заштита на страницата ({{lcfirst:$1}})',
 'pageinfo-magic-words' => '{{PLURAL:$1|Волшебен збор|Волшебни зборови}} ($1)',
 'pageinfo-hidden-categories' => '{{PLURAL:$1|Скриена категорија|Скриени категории}} ($1)',
 'pageinfo-templates' => '{{PLURAL:$1|Превметнат шаблон|Превметнати шаблони}} ($1)',
+'pageinfo-toolboxlink' => 'Информации за страницата',
 
 # Skin names
 'skinname-standard' => 'Класично',
@@ -3876,6 +3876,7 @@ $5
 # Scary transclusion
 'scarytranscludedisabled' => '[Превметнувањето помеѓу викијата е оневозможено]',
 'scarytranscludefailed' => '[Преземањето на шаблонот за $1 не успеа]',
+'scarytranscludefailed-httpstatus' => '[Преземањето на шаблонот не успеа за $1: HTTP $2]',
 'scarytranscludetoolong' => '[Премногу долго URL]',
 
 # Delete conflict
@@ -4173,8 +4174,8 @@ $5
 'dberr-cachederror' => 'Следнава содржина е кеширана копија на бараната страница, која може да е застарена.',
 
 # HTML forms
-'htmlform-invalid-input' => 'Ð\98ма Ð¿Ñ\80облеми Ñ\81о Ð´ÐµÐ» Ð¾Ð´ Ð²Ð°Ñ\88иоÑ\82 Ð²Ð½Ðµс',
-'htmlform-select-badoption' => 'Ð\92Ñ\80едноÑ\81Ñ\82а ÐºÐ¾Ñ\98а Ñ\98а Ð½Ð°Ð²ÐµÐ´Ð¾Ð²Ñ\82е Ð½Ðµ Ðµ Ð²Ð°Ð¶ÐµÑ\87ка.',
+'htmlform-invalid-input' => 'Ð\98ма Ð¿Ñ\80облеми Ñ\81о Ð´ÐµÐ» Ð¾Ð´ Ð²Ð°Ñ\88иоÑ\82 Ð²Ð½Ð¾с',
+'htmlform-select-badoption' => 'УкажанаÑ\82а Ð²Ñ\80едноÑ\81Ñ\82 Ðµ Ð½ÐµÐ²Ð°Ð¶ÐµÑ\87ка ÐºÐ°ÐºÐ¾ Ð¼Ð¾Ð¶Ð½Ð¾Ñ\81Ñ\82.',
 'htmlform-int-invalid' => 'Вредноста која ја наведовте не е цел број.',
 'htmlform-float-invalid' => 'Вредноста која ја наведовте не е број.',
 'htmlform-int-toolow' => 'Вредноста која ја наведовте е под минимумот од $1',
index 9adb476..0266cbc 100644 (file)
@@ -736,7 +736,7 @@ $2',
 'logouttext' => "'''താങ്കൾ ഇപ്പോൾ {{SITENAME}} സംരംഭത്തിൽനിന്നും ലോഗൗട്ട് ചെയ്തിരിക്കുന്നു'''
 
 അജ്ഞാതമായിരുന്നു കൊണ്ട് {{SITENAME}} സം‌രംഭം താങ്കൾക്കു തുടർന്നും ഉപയോഗിക്കാവുന്നതാണ്‌.
-അല്ലെങ്കിൽ  [[Special:UserLogin|ലോഗിൻ സൗകര്യം ഉപയോഗിച്ച്]] വീണ്ടും ലോഗിൻ ചെയ്യാവുന്നതും ആണ്‌.
+അല്ലെങ്കിൽ  <span class='plainlinks'>[$1 ലോഗിൻ സൗകര്യം ഉപയോഗിച്ച്]</span> വീണ്ടും ലോഗിൻ ചെയ്യാവുന്നതും ആണ്‌.
 താങ്കൾ വെബ് ബ്രൌസറിന്റെ ക്യാഷെ ശൂന്യമാക്കിയിട്ടില്ലെങ്കിൽ ചില താളുകളിൽ താങ്കൾ ലോഗിൻ ചെയ്തിരിക്കുന്നതായി കാണിക്കാൻ സാധ്യതയുണ്ട്.",
 'welcomecreation' => '== സ്വാഗതം, $1! ==
 താങ്കളുടെ അംഗത്വം സൃഷ്ടിക്കപ്പെട്ടിരിക്കുന്നു.
@@ -3101,10 +3101,10 @@ $1',
 'pageinfo-authors' => 'ആകെ വ്യത്യസ്തരചയിതാക്കളുടെ എണ്ണം',
 'pageinfo-recent-edits' => 'സമീപകാലത്തെ തിരുത്തുകൾ (കഴിഞ്ഞ $1 കാലയളവിനുള്ളിൽ)',
 'pageinfo-recent-authors' => 'സമീപകാലത്തെ വ്യത്യസ്തരചയിതാക്കളുടെ എണ്ണം',
-'pageinfo-restriction' => 'താൾ സംരക്ഷണം ($1)',
 'pageinfo-magic-words' => 'മാന്ത്രിക{{PLURAL:$1|വാക്ക്|വാക്കുകൾ}} ($1)',
 'pageinfo-hidden-categories' => 'മറഞ്ഞിരിക്കുന്ന {{PLURAL:$1|വർഗ്ഗം|വർഗ്ഗങ്ങൾ}} ($1)',
 'pageinfo-templates' => 'ഉൾപ്പെടുത്തിയിട്ടുള്ള {{PLURAL:$1|ഫലകം|ഫലകങ്ങൾ}} ($1)',
+'pageinfo-toolboxlink' => 'താളിന്റെ വിവരങ്ങൾ',
 
 # Skin names
 'skinname-standard' => 'സാർവത്രികം',
@@ -3673,6 +3673,7 @@ $5
 # Scary transclusion
 'scarytranscludedisabled' => '[അന്തർവിക്കി ഉൾപ്പെടുത്തൽ സജ്ജമല്ല]',
 'scarytranscludefailed' => '[$1-നു ഫലകം കണ്ടുപിടിക്കാൻ പറ്റിയില്ല]',
+'scarytranscludefailed-httpstatus' => '[$1-നു ഫലകം എടുക്കാൻ കഴിഞ്ഞില്ല: എച്ച്.റ്റി.റ്റി.പി. $2]',
 'scarytranscludetoolong' => '[വളരെ നീളക്കൂടുതലുള്ള യൂ.ആർ.എൽ.]',
 
 # Delete conflict
index 5252d65..284fca6 100644 (file)
@@ -437,7 +437,7 @@ $2',
 # Login and logout pages
 'logouttext' => "'''Та одоо гарлаа.'''
 
-Та ямар нэг хэрэглэгчийн бүртгэлгүйгээр {{SITENAME}}-г ашиглах боломжтой, эсвэл саяынхаа болон өөр хэрэглэгчийн бүртгэлээ ашиглан [[Special:UserLogin|дахин нэвтэрч]] болно.
+Та ямар нэг хэрэглэгчийн бүртгэлгүйгээр {{SITENAME}}-г ашиглах боломжтой, эсвэл саяынхаа болон өөр хэрэглэгчийн бүртгэлээ ашиглан <span class='plainlinks'>[$1 дахин нэвтэрч]</span> болно.
 Броузерийнхаа хийсвэр санах ойг цэвэрлэх хүртэл зарим нэг хуудсууд нь таны холбогдсон байдлаар харагдаж болзошгүйг анхааруулъя.",
 'welcomecreation' => '= $1, тавтай морилно уу! ==
 Та амжилттай бүртгэгдлээ.
index d899219..73c0a09 100644 (file)
@@ -708,7 +708,7 @@ $2',
 # Login and logout pages
 'logouttext' => "'''तुम्ही आता अदाखल झाला(logout)आहात.'''
 
-तुम्ही अनामिकपणे {{SITENAME}}चा उपयोग करत राहू शकता, किंवा त्याच अथवा वेगळ्या सदस्य नावाने [[Special:UserLogin| पुन्हा दाखल होऊ शकता]].
+तुम्ही अनामिकपणे {{SITENAME}}चा उपयोग करत राहू शकता, किंवा त्याच अथवा वेगळ्या सदस्य नावाने <span class='plainlinks'>[$1  पुन्हा दाखल होऊ शकता]</span>.
 आपण स्वत:च्या न्याहाळकाची सय (cache) रिकामी करत नाही तो पर्यंत काही पाने आपण अजून दाखल आहात, असे नुसतेच दाखवत राहू शकतील.",
 'welcomecreation' => '== सुस्वागतम, $1! ==
 
index 61cf737..f1bed2f 100644 (file)
@@ -603,7 +603,7 @@ Pentadbir yang menguncinya memberikan penjelasan yang berikut: "$3".',
 # Login and logout pages
 'logouttext' => "'''Anda telah log keluar.'''
 
-Anda boleh terus menggunakan {{SITENAME}} sebagai pengguna tanpa nama, atau anda boleh [[Special:UserLogin|log masuk sekali lagi]] sebagai pengguna lain. Anda boleh membersihkan cache pelayar web anda sekiranya terdapat laman yang memaparkan seolah-olah anda masih log masuk.",
+Anda boleh terus menggunakan {{SITENAME}} sebagai pengguna tanpa nama, atau anda boleh <span class='plainlinks'>[$1 log masuk sekali lagi]</span> sebagai pengguna lain. Anda boleh membersihkan cache pelayar web anda sekiranya terdapat laman yang memaparkan seolah-olah anda masih log masuk.",
 'welcomecreation' => '== Selamat datang, $1! ==
 
 Akaun anda telah dibuka. Jangan lupa untuk mengubah [[Special:Preferences|keutamaan {{SITENAME}}]] anda.',
@@ -2976,10 +2976,10 @@ Simpan dalam komputer anda dan muat naiknya di sini.',
 'pageinfo-authors' => 'Jumlah pengarang yang berlainan',
 'pageinfo-recent-edits' => 'Bilangan suntingan terkini (dalam $1 yang lalu)',
 'pageinfo-recent-authors' => 'Bilangan pengarang berbeza yang terkini',
-'pageinfo-restriction' => 'Perlindungan halaman ({{lcfirst:$1}})',
 'pageinfo-magic-words' => 'Kata sakti ($1)',
 'pageinfo-hidden-categories' => 'Kategori tersembunyi ($1)',
 'pageinfo-templates' => 'Templat tertransklusi ($1)',
+'pageinfo-toolboxlink' => 'Maklumat halaman',
 
 # Skin names
 'skinname-standard' => 'Klasik',
@@ -3559,6 +3559,7 @@ Kod pengesahan ini akan luput pada $4.',
 # Scary transclusion
 'scarytranscludedisabled' => '[Penyertaan pautan interwiki dilumpuhkan]',
 'scarytranscludefailed' => '[Gagal mendapatkan templat $1]',
+'scarytranscludefailed-httpstatus' => '[Ambilan templat gagal untuk $1: HTTP $2]',
 'scarytranscludetoolong' => '[URL terlalu panjang]',
 
 # Delete conflict
index 4e30069..9f4aeb4 100644 (file)
@@ -664,7 +664,7 @@ Ir-raġuni li ġiet mogħtija kienet ''$2''.",
 # Login and logout pages
 'logouttext' => "'''Bħalissa tinsab barra mill-kont tiegħek.'''
 
-Tista' tkompli tuża' {{SITENAME}} bħala utent anonimu, jew tista' terġa [[Special:UserLogin|tidħol]] bħala l-istess utent jew wieħed differenti.
+Tista' tkompli tuża' {{SITENAME}} bħala utent anonimu, jew tista' terġa <span class='plainlinks'>[$1 tidħol]</span> bħala l-istess utent jew wieħed differenti.
 Kun af li ċerti paġni jistgħu jkomplu jidhru bħallikieku l-illogjar 'l barra mill-kont qatt ma seħħ, sakemm ma tħassarx il-cache tal-browser.",
 'welcomecreation' => "== Merħba, $1! ==
 Il-kont tiegħek ġie maħluq.<br />
@@ -1195,7 +1195,7 @@ Nota li l-użu tal-links tan-navigazzjoni jagħmel reset tal-kolonna.",
 'mergehistory-reason' => 'Raġuni:',
 
 # Merge log
-'mergelog' => "Reġistru ta' twaħħid",
+'mergelog' => 'Twaħħid',
 'pagemerge-logentry' => "waħħad [[$1]] ma' [[$2]] (reviżjonijiet sa $3)",
 'revertmerge' => 'Infired',
 'mergelogpagetext' => "Hawn taħt hawn lista ta' l-aktar tgħaqqid riċenti ta' paġna waħda ta' storja f'oħra.",
@@ -2654,9 +2654,9 @@ Jekk jogħġbok, waħħad iż-żewġ paġni manwalment.'''",
 'move-talk-subpages' => "Mexxi is-sottopaġni kollha tal-paġna ta' diskussjoni (sa $1)",
 'movepage-page-exists' => 'Il-paġna $1 diġà teżisti u ma tistax tiġi miktuba fuqha awtomatikament.',
 'movepage-page-moved' => 'Il-Paġna $1 ġiet imċaqilqa għal $2.',
-'movepage-page-unmoved' => 'Il-Paġna $1 ma setgħatx tiġi mċaqilqa għal $2.',
+'movepage-page-unmoved' => 'Il-paġna $1 ma setgħetx titmexxa lejn $2.',
 'movepage-max-pages' => "Ġie mċaqlaq in-numru massimu ta' {{PLURAL:$1|paġna u ma jistax jiġi mċaqlaq aktar awtomatikament|$1 paġni u ma jistgħux jiġu mċaqilqa aktar awtomatikament.}}",
-'movelogpage' => "Reġistru tat-tmexxija ta' paġni",
+'movelogpage' => "Tmexxija ta' paġni",
 'movelogpagetext' => "Hawn taħt jinsab lista ta' paġni mċaqilqa.",
 'movesubpage' => '{{PLURAL:$1|Sottopaġna|Sottopaġna}}',
 'movesubpagetext' => 'Din il-paġna għandha $1 {{PLURAL:$1|sottopaġna murija|sottopaġni murija}} hawn taħt:',
index 2b64998..56298d6 100644 (file)
@@ -376,9 +376,9 @@ $1',
 'virus-unknownscanner' => 'အမည်မသိအန်တီဗိုင်းရပ်စ် -',
 
 # Login and logout pages
-'logouttext' => 'သင်သည် လော့ဂ်အောက် လုပ်လိုက်ပြီဖြစ်သည်။
-သင့်အနေနှင့် ဤ {{SITENAME}} ဝက်ဘ်ဆိုက်ဒ်ကို အမည်မသိ အသုံးပြုသူ အနေနှင့် ဆက်လက် အသုံးပြုနိုင်သည်။ သို့မဟုတ် ယခင် အသုံးပြုသူ အမည် သို့ အသုံးပြုသူ အခြားအမည်တစ်ခုဖြင့် [[Special:UserLogin|နောက်တစ်ကြိမ် လော့ဂ်အင်ပြန်ဝင်]] နိုင်သည်။
-သင်၏ ဘရောက်ဆာမှ cache ကို ရှင်းလင်းသည့် အချိန် အထိ အချို့သော စာမျက်နှာ များသည် သင် လော့ဂ်အင် ဝင်ထားစဉ်က အတိုင်းပင် ဆက်လက် ပြသနေမည်ဖြစ်သည်။',
+'logouttext' => "သင်သည် လော့ဂ်အောက် လုပ်လိုက်ပြီဖြစ်သည်။
+သင့်အနေနှင့် ဤ {{SITENAME}} ဝက်ဘ်ဆိုက်ဒ်ကို အမည်မသိ အသုံးပြုသူ အနေနှင့် ဆက်လက် အသုံးပြုနိုင်သည်။ သို့မဟုတ် ယခင် အသုံးပြုသူ အမည် သို့ အသုံးပြုသူ အခြားအမည်တစ်ခုဖြင့် <span class='plainlinks'>[$1 နောက်တစ်ကြိမ် လော့ဂ်အင်ပြန်ဝင်]</span> နိုင်သည်။
+သင်၏ ဘရောက်ဆာမှ cache ကို ရှင်းလင်းသည့် အချိန် အထိ အချို့သော စာမျက်နှာ များသည် သင် လော့ဂ်အင် ဝင်ထားစဉ်က အတိုင်းပင် ဆက်လက် ပြသနေမည်ဖြစ်သည်။",
 'welcomecreation' => '== မင်္ဂလာပါ $1! ==
 သင့်အကောင့်ကို ဖန်တီးပြီးပါပြီ။
 [[Special:Preferences|{{SITENAME}} စိတ်​ကြိုက်​ရွေးချယ်စရာတို့]]ကို ပြောင်းရန် မမေ့ပါနှင့်။',
index 3236f9e..7ed7c6f 100644 (file)
@@ -471,7 +471,7 @@ $messages = array(
 'vector-action-protect' => 'Beskytt',
 'vector-action-undelete' => 'Gjenopprett',
 'vector-action-unprotect' => 'Endre beskyttelse',
-'vector-simplesearch-preference' => 'Aktiver forbedrede søkeforslag (kun for drakten Vector)',
+'vector-simplesearch-preference' => 'Aktiver forenklet søkefelt (kun for drakten Vector)',
 'vector-view-create' => 'Opprett',
 'vector-view-edit' => 'Rediger',
 'vector-view-history' => 'Vis historikk',
@@ -721,7 +721,7 @@ Administrators nærmere begrunnelse: «$3».',
 # Login and logout pages
 'logouttext' => "'''Du er nå logget ut.'''
 
-Du kan fortsette å bruke {{SITENAME}} anonymt, eller [[Special:UserLogin|logge inn igjen]] som samme eller en annen bruker.
+Du kan fortsette å bruke {{SITENAME}} anonymt, eller <span class='plainlinks'>[$1 logge inn igjen]</span> som samme eller en annen bruker.
 Merk at noen sider kan vise at du fortsatt er logget inn fram til du tømmer mellomlageret i nettleseren.",
 'welcomecreation' => '==Velkommen, $1!==
 Brukerkontoen din har blitt opprettet.
@@ -2464,7 +2464,7 @@ Dette er de nåværende innstillingene for siden '''$1''':",
 Du kan endre sidens beskyttelsesnivå, men det vil ikke påvirke dypbeskyttelsen.',
 'protect-default' => 'Tillat alle brukere',
 'protect-fallback' => 'Må ha «$1»-tillatelse',
-'protect-level-autoconfirmed' => 'Blokker nye og uregistrerte brukere',
+'protect-level-autoconfirmed' => 'Blokker uregistrerte og nye brukere',
 'protect-level-sysop' => 'Kun administratorer',
 'protect-summary-cascade' => 'dypbeskyttelse',
 'protect-expiring' => 'utløper $1 (UTC)',
@@ -3103,10 +3103,10 @@ Dette er sannsynligvis forårsaket av en lenke til et svartelistet eksternt nett
 'pageinfo-authors' => 'Totalt antall forskjellige forfattere',
 'pageinfo-recent-edits' => 'Antall nylige redigeringer (innen siste $1)',
 'pageinfo-recent-authors' => 'Antall nylige forfattere',
-'pageinfo-restriction' => 'Sidebeskyttelse ({{lcfirst:$1}})',
 'pageinfo-magic-words' => '{{PLURAL:$1|Magisk|Magiske}} ord ($1)',
 'pageinfo-hidden-categories' => '{{PLURAL:$1|Skjult kategori|Skjulte kategorier}} ($1)',
 'pageinfo-templates' => 'Transkludert {{PLURAL:$1|mal|maler}} ($1)',
+'pageinfo-toolboxlink' => 'Sideinformasjon',
 
 # Skin names
 'skinname-standard' => 'Standard',
@@ -3681,6 +3681,7 @@ Denne bekreftelseskoden går ut på dato $4.',
 # Scary transclusion
 'scarytranscludedisabled' => '[Interwiki-transkludering er slått av]',
 'scarytranscludefailed' => '[Malen kunne ikke hentes for $1]',
+'scarytranscludefailed-httpstatus' => '[Henting av mal for $1 feilet: HTTP $2]',
 'scarytranscludetoolong' => '[URL-en er for lang]',
 
 # Delete conflict
index df4e839..a23b88a 100644 (file)
@@ -606,7 +606,7 @@ As Grund is angeven: ''$2''.",
 # Login and logout pages
 'logouttext' => "'''Du büst nu afmellt.'''
 
-Du kannst {{SITENAME}} nu anonym wiederbruken oder di ünner dissen oder en annern Brukernaam wedder [[Special:UserLogin|anmellen]].
+Du kannst {{SITENAME}} nu anonym wiederbruken oder di ünner dissen oder en annern Brukernaam wedder <span class='plainlinks'>[$1 anmellen]</span>.
 Denk dor an, dat welk Sieden ünner Ümstänn noch jümmer so wiest warrn köönt, as wenn du anmellt weerst. Dat ännert sik, wenn du den Cache vun dien Browser leddig maakst.",
 'welcomecreation' => '== Willkamen, $1! ==
 Dien Brukerkonto is nu inricht.
index aa97fb8..a416c5c 100644 (file)
@@ -714,7 +714,7 @@ De beheerder gaf hierveur de volgende reden: "$3".',
 # Login and logout pages
 'logouttext' => "'''Je bin noen aofemeld.'''
 
-Je kunnen {{SITENAME}} noen anoniem gebruken of je eigen [[Special:UserLogin|opniej anmelden]] onder disse of n aandere gebrukersnaam.
+Je kunnen {{SITENAME}} noen anoniem gebruken of je eigen <span class='plainlinks'>[$1 opniej anmelden]</span> onder disse of n aandere gebrukersnaam.
 t Kan ween dat der wat ziejen bin die weeregeven wörden asof je an-emeld bin totda'j t tussengeheugen van joew webkieker leegmaken.",
 'welcomecreation' => '== Welkom, $1! ==
 Joew gebrukersnaam is an-emaakt.
@@ -3062,7 +3062,6 @@ Meestentieds kömp dit deur n uutgaonde verwiezing die op de zwarte lieste steet
 'pageinfo-authors' => 'Totaal antal verschillende auteurs',
 'pageinfo-recent-edits' => 'Antal nieje bewarkingen (in de veurbieje $1).',
 'pageinfo-recent-authors' => 'Leste antal van verschillende auteurs',
-'pageinfo-restriction' => 'Ziedbeveiliging ({{lcfirst:$1}})',
 'pageinfo-magic-words' => '{{PLURAL:$1|Magies woord|Magiese woorden}} ($1)',
 'pageinfo-hidden-categories' => 'Verbörgen {{PLURAL:$1|kategorie|kategorieën}} ($1)',
 'pageinfo-templates' => '{{PLURAL:$1|Gebruukten mal|Gebruukten mallen}} ($1)',
index 201d6ab..f93fabb 100644 (file)
@@ -457,7 +457,7 @@ $2',
 
 # Login and logout pages
 'logouttext' => "'''तपाईं अहिले बाहिर निस्कनु भएको छ।'''
-तपाईंले नाम/खाताविनै पनि {{SITENAME}}मा प्रयोग गर्न सक्नुहुन्छ, अथवा अघिकै वा अर्कै कुनै नामको खाताबाट [[Special:UserLogin|फेरि प्रवेश गर्न]] पनि सक्नुहुन्छ।
+तपाईंले नाम/खाताविनै पनि {{SITENAME}}मा प्रयोग गर्न सक्नुहुन्छ, अथवा अघिकै वा अर्कै कुनै नामको खाताबाट <span class='plainlinks'>[$1 फेरि प्रवेश गर्न]</span> पनि सक्नुहुन्छ।
 याद राख्नुहोस् तपाईंले ब्राउजरको स्मरण भण्डार खालि नगर्दासम्म कुनै पृष्ठहरूमा तपाईं अझै प्रवेश गरिराखेको देखाउन सक्छ।",
 'welcomecreation' => '== स्वागतम् , $1! ==
 तपाँईको खाता खोलिएको छ। [[Special:Preferences|{{SITENAME}} preferences]]मा आफ्ना अभिरुचिहरू परिवर्तन गर्न नबिर्सिनुहोला।',
index 7fb6c2e..40952c6 100644 (file)
@@ -763,7 +763,7 @@ De opgegeven reden is "\'\'$3\'\'".',
 # Login and logout pages
 'logouttext' => "'''U bent nu afgemeld.'''
 
-U kunt {{SITENAME}} nu anoniem gebruiken of weer [[Special:UserLogin|aanmelden]] als dezelfde of een andere gebruiker.
+U kunt {{SITENAME}} nu anoniem gebruiken of weer <span class='plainlinks'>[$1 aanmelden]</span> als dezelfde of een andere gebruiker.
 Mogelijk worden nog een aantal pagina's weergegeven alsof u aangemeld bent totdat u de cache van uw browser leegt.",
 'welcomecreation' => '== Welkom, $1! ==
 Uw gebruiker is geregistreerd.
@@ -3245,10 +3245,10 @@ Meestal wordt dit door een externe verwijzing op een zwarte lijst veroorzaakt.',
 'pageinfo-authors' => 'Totaal aantal verschillende auteurs',
 'pageinfo-recent-edits' => 'Recent aantal bewerkingen (binnen de afgelopen $1).',
 'pageinfo-recent-authors' => 'Recent aantal verschillende auteurs',
-'pageinfo-restriction' => 'Paginabeveiliging ($1)',
 'pageinfo-magic-words' => '{{PLURAL:$1|Magisch woord|Magische woorden}} ($1)',
 'pageinfo-hidden-categories' => 'Verborgen {{PLURAL:$1|categorie|categorieën}} ($1)',
 'pageinfo-templates' => '{{PLURAL:$1|Gebruikt sjabloon|Gebruikte sjablonen}} ($1)',
+'pageinfo-toolboxlink' => 'Paginagegevens',
 
 # Skin names
 'skinname-standard' => 'Klassiek',
@@ -3831,6 +3831,7 @@ De bevestigingscode vervalt op $4.',
 # Scary transclusion
 'scarytranscludedisabled' => '[Interwiki-invoeging van sjablonen is uitgeschakeld]',
 'scarytranscludefailed' => '[Het sjabloon $1 kon niet opgehaald worden]',
+'scarytranscludefailed-httpstatus' => '[Het sjabloon $1 kon niet opgehaald worden: HTTP $2]',
 'scarytranscludetoolong' => '[De URL is te lang]',
 
 # Delete conflict
index 92d5f51..9768664 100644 (file)
@@ -471,7 +471,7 @@ $messages = array(
 'vector-action-protect' => 'Vern',
 'vector-action-undelete' => 'Gjenopprett',
 'vector-action-unprotect' => 'Endra vern',
-'vector-simplesearch-preference' => 'Slå på betra søkjeframlegg (einast i Vector-drakta)',
+'vector-simplesearch-preference' => 'Slå på forenkla søkjefelt (berre for Vector-drakta)',
 'vector-view-create' => 'Opprett',
 'vector-view-edit' => 'Endre',
 'vector-view-history' => 'Sjå historikken',
@@ -710,7 +710,7 @@ Administratoren som låste filsamlinga oppgav den fylgjande årsaka: «$3».',
 # Login and logout pages
 'logouttext' => "'''Du er no utlogga.'''
 
-Du kan no halde fram å bruke {{SITENAME}} anonymt, eller du kan [[Special:UserLogin|logge inn att]]  med same kontoen eller ein annan brukar kan logge inn.
+Du kan no halde fram å bruke {{SITENAME}} anonymt, eller du kan <span class='plainlinks'>[$1 logge inn att]</span>  med same kontoen eller ein annan brukar kan logge inn.
 Ver merksam på at nokre sider framleis kan visast fram som om du er innlogga fram til du slettar mellomlageret til nettlesaren din.",
 'welcomecreation' => '== Hjarteleg velkommen til {{SITENAME}}, $1! ==
 Brukarkontoen din er oppretta.
@@ -1606,10 +1606,10 @@ Dette kan ikkje tilbakestillast.',
 'recentchanges-legend' => 'Alternativ for siste endringar',
 'recentchanges-summary' => 'På denne sida ser du dei sist endra sidene i {{SITENAME}}.',
 'recentchanges-feed-description' => 'Fylg med på dei siste endringane på denne wikien med dette abonnementet.',
-'recentchanges-label-newpage' => 'Denne endringa oppretta ei ny side',
+'recentchanges-label-newpage' => 'Endringa oppretta ei ny side',
 'recentchanges-label-minor' => 'Dette er ei mindre endring',
 'recentchanges-label-bot' => 'Denne endringa vart gjort av ein bot',
-'recentchanges-label-unpatrolled' => 'Denne endringa er ikkje patruljert enno',
+'recentchanges-label-unpatrolled' => 'Endringa er ikkje patruljert enno',
 'rcnote' => "Nedanfor er {{PLURAL:$1|den siste endringa gjord|dei siste '''$1''' endringane gjorde}} {{PLURAL:$2|den siste dagen|dei siste '''$2''' dagane}}, for $4, kl. $5.",
 'rcnotefrom' => "Nedanfor vert opp til '''$1''' endringar sidan  ''' $2''' viste.",
 'rclistfrom' => 'Vis nye endringar sidan $1',
@@ -2973,7 +2973,6 @@ Vitja [//www.mediawiki.org/wiki/Localisation MediaWiki Localisation] og [//trans
 'pageinfo-authors' => 'Totalt tal på ulike forfattarar',
 'pageinfo-recent-edits' => 'Tal på nylege endringar (innan dei siste $1)',
 'pageinfo-recent-authors' => 'Tal på nylege forfattarar',
-'pageinfo-restriction' => 'Sidevern ({{lcfirst:$1}})',
 'pageinfo-magic-words' => '{{PLURAL:$1|Trylleord}} ($1)',
 'pageinfo-hidden-categories' => '{{PLURAL:$1|Løynd kategori|Løynde kategoriar}} ($1)',
 'pageinfo-templates' => '{{PLURAL:$1|Inkludert mal|Inkluderte malar}} ($1)',
@@ -3221,6 +3220,7 @@ Andre er gøymde som standard.
 'exif-source' => 'Kjelde',
 'exif-urgency' => 'Prioritet',
 'exif-objectcycle' => 'Tid på dagen mediet er meint for',
+'exif-contact' => 'Kontaktinformasjon',
 'exif-writer' => 'Forfattar',
 'exif-languagecode' => 'Språk',
 'exif-iimversion' => 'IIM-versjon',
@@ -3643,7 +3643,7 @@ Skriv inn filnamnet utan «{{ns:file}}:»-prefikset.',
 'specialpages' => 'Spesialsider',
 'specialpages-note' => '----
 * Vanlege spesialsider.
-* <strong class="mw-specialpagerestricted">Spesialsider med avgrensa tilgang.</strong>',
+* <span class="mw-specialpagerestricted">Spesialsider med avgrensa tilgang.</span>',
 'specialpages-group-maintenance' => 'Vedlikehaldsrapportar',
 'specialpages-group-other' => 'Andre spesialsider',
 'specialpages-group-login' => 'Logga inn / oppretta brukarkonto',
@@ -3767,6 +3767,7 @@ Om ikkje kan du nytta det enkle skjemaet under. Merknaden din vert lagd til på
 
 # Search suggestions
 'searchsuggest-search' => 'Søk',
+'searchsuggest-containing' => 'som inneheld …',
 
 # API errors
 'api-error-badaccess-groups' => 'Du har ikkje løyve til å lasta opp filer til wikien.',
index 20087b3..f27be7b 100644 (file)
@@ -679,7 +679,7 @@ Lo motiu avançat es « ''$2'' ».",
 # Login and logout pages
 'logouttext' => "'''Ara, sètz desconnect{{GENDER:||at|ada}}..'''
 
-Podètz contunhar d'utilizar {{SITENAME}} anonimament, o vos podètz [[Special:UserLogin|tornar connectar]] jol meteis nom o amb un autre nom.
+Podètz contunhar d'utilizar {{SITENAME}} anonimament, o vos podètz <span class='plainlinks'>[$1 tornar connectar]</span> jol meteis nom o amb un autre nom.
 Notatz que d'unas paginas pòdon èsser encara afichadas coma s'eratz encara connect{{GENDER:||at|ada}}, fins al moment qu'escafaretz l'amagatal de vòstre navigador.",
 'welcomecreation' => "== Benvenguda, $1 ! ==
 Vòstre compte d'utilizaire es estat creat.
index 7650fa9..6f4779d 100644 (file)
@@ -322,13 +322,13 @@ $messages = array(
 'thursday' => 'ଗୁରୁବାର',
 'friday' => 'ଶୁକ୍ରବାର',
 'saturday' => 'ଶନିବାର',
-'sun' => 'ରବିବାର',
-'mon' => 'ସୋମବାର',
-'tue' => 'ମଙ୍ଗଳବାର',
-'wed' => 'ବୁଧବାର',
-'thu' => 'ଗୁରୁବାର',
-'fri' => 'ଶୁକ୍ରବାର',
-'sat' => 'ଶନିବାର',
+'sun' => 'ରବି',
+'mon' => 'ସୋମ',
+'tue' => 'ମଙ୍ଗଳ',
+'wed' => 'ବୁଧ',
+'thu' => 'ଗୁରୁ',
+'fri' => 'ଶୁକ୍ର',
+'sat' => 'ଶନି',
 'january' => 'ଜାନୁଆରୀ',
 'february' => 'ଫେବ୍ରୁଆରୀ',
 'march' => 'ମାର୍ଚ୍ଚ',
@@ -664,7 +664,7 @@ $2',
 # Login and logout pages
 'logouttext' => "'''ଆପଣ ଲଗାଆଉଟ କରିଦେଲେ'''
 
-ଆପଣ ଅଜଣା ଭାବରେ {{SITENAME}}କୁ ଯାଇପାରିବେ, କିମ୍ବା [[Special:UserLogin|ଆଉଥରେ]] ଆଗର ଇଉଜର ନାଆଁରେ/ଅଲଗା ନାଆଁରେ ଲଗଇନ କରିପାରିବେ ।
+ଆପଣ ଅଜଣା ଭାବରେ {{SITENAME}}କୁ ଯାଇପାରିବେ, କିମ୍ବା <span class='plainlinks'>[$1 ଆଉଥରେ]</span> ଆଗର ଇଉଜର ନାଆଁରେ/ଅଲଗା ନାଆଁରେ ଲଗଇନ କରିପାରିବେ ।
 ଜାଣିରଖନ୍ତୁ, କିଛି ପୃଷ୍ଠା ଲଗାଆଉଟ କଲାପରେ ବି ଆଗପରି ଦେଖାଯାଇପାରେ, ଆପଣ ବ୍ରାଉଜର କାସକୁ ହଟାଇଲା ଯାଏଁ ଏହା ଏମିତି ରହିବ ।",
 'welcomecreation' => '== $1!, ଆପଣଙ୍କ ଖାତାଟି ତିଆରି ହୋଇଗଲା==
 ତେବେ, ନିଜର [[Special:Preferences|{{SITENAME}} ପସନ୍ଦସବୁକୁ]] ବଦଳାଇବାକୁ ଭୁଲିବେ ନାହିଁ ।',
index 3012bb0..b81f83a 100644 (file)
@@ -247,7 +247,7 @@ $messages = array(
 'vector-action-protect' => 'Сæхгæнын',
 'vector-action-undelete' => 'Рацаразын',
 'vector-action-unprotect' => 'Ивын хъахъхъæд',
-'vector-simplesearch-preference' => 'Баиу кæнын уæрæхгонд агурыны æххуыстæ (Вектор цармæн æрмæст)',
+'vector-simplesearch-preference' => 'Баиу кæнын æнцонгонд агурыны формæ (Вектор цармæн æрмæст)',
 'vector-view-create' => 'Скæнын',
 'vector-view-edit' => 'Ивын',
 'vector-view-history' => 'Истори',
@@ -502,7 +502,7 @@ $2',
 # Login and logout pages
 'logouttext' => "'''Ныр дæ æддæмæ хызт.'''
 
-Дæ бон у дарддæр архайай {{grammar:genitive|{{SITENAME}}}} æнæномæй, æви та [[Special:UserLogin|фæстæмæ бахизын]] раздæры номæй кæнæ та æндæр номæй.
+Дæ бон у дарддæр архайай {{grammar:genitive|{{SITENAME}}}} æнæномæй, æви та <span class='plainlinks'>[$1 фæстæмæ бахизын]</span> раздæры номæй кæнæ та æндæр номæй.
 Дæ сæры дар æмæ иуæй иу фæрстæ гæнæн ис æвдыст цæуой афтæ, цымæ нырмæ дæр нæ рахызтæ. Уый тыххæй дæ браузеры кеш сафтид кæн.",
 'welcomecreation' => '== Ӕгас цу, $1! ==
 Дæ аккаунт арæзт æрцыдис.
index 58e222a..ac63621 100644 (file)
@@ -451,7 +451,7 @@ $messages = array(
 'vector-action-protect' => 'Zabezpiecz',
 'vector-action-undelete' => 'Odtwórz',
 'vector-action-unprotect' => 'Zmień zabezpieczenie',
-'vector-simplesearch-preference' => 'Włącz zaawansowane podpowiedzi wyszukiwania (tylko dla skórki Wektor)',
+'vector-simplesearch-preference' => 'Włącz uproszczony pasek wyszukiwania (tylko dla skórki Wektor)',
 'vector-view-create' => 'Utwórz',
 'vector-view-edit' => 'Edytuj',
 'vector-view-history' => 'Wyświetl historię',
@@ -701,7 +701,7 @@ Administrator blokujący go podał następujący powód "\'\'$3\'\'".',
 # Login and logout pages
 'logouttext' => "'''Nie jesteś już zalogowany.'''
 
-Możesz kontynuować pracę w {{GRAMMAR:MS.lp|{{SITENAME}}}} jako niezarejestrowany użytkownik albo [[Special:UserLogin|zalogować się ponownie]] jako ten sam lub inny użytkownik.
+Możesz kontynuować pracę w {{GRAMMAR:MS.lp|{{SITENAME}}}} jako niezarejestrowany użytkownik albo <span class='plainlinks'>[$1 zalogować się ponownie]</span> jako ten sam lub inny użytkownik.
 Zauważ, że do momentu wyczyszczenia pamięci podręcznej przeglądarki niektóre strony mogą wyglądać tak, jakbyś wciąż był zalogowany.",
 'welcomecreation' => '== Witaj, $1! ==
 Twoje konto zostało utworzone.
@@ -3120,7 +3120,6 @@ Najprawdopodobniej zostało to spowodowane przez link do zewnętrznej strony int
 'pageinfo-authors' => 'Całkowita liczba autorów',
 'pageinfo-recent-edits' => 'Liczba ostatnich edycji (w przeciągu $1)',
 'pageinfo-recent-authors' => 'Liczba ostatnich autorów',
-'pageinfo-restriction' => 'Zabezpieczenie strony – {{lcfirst:$1}}',
 'pageinfo-magic-words' => 'Magiczne {{PLURAL:$1|słowo|słowa|słowa}} ($1)',
 'pageinfo-hidden-categories' => '{{PLURAL:$1|Ukryta kategoria|Ukryte kategorie|Ukryte kategorie}} ($1)',
 'pageinfo-templates' => 'Wykorzystywan{{PLURAL:$1|y szablon|e szablony}} ($1)',
@@ -4038,6 +4037,7 @@ W przeciwnym wypadku można użyć prostego formularza poniżej. Komentarz zosta
 
 # Search suggestions
 'searchsuggest-search' => 'Szukaj',
+'searchsuggest-containing' => 'zawierające...',
 
 # API errors
 'api-error-badaccess-groups' => 'Nie masz uprawnień aby przesyłać pliki do tej wiki.',
index d48ccc8..4bd5cc3 100644 (file)
@@ -199,7 +199,7 @@ $messages = array(
 'vector-action-protect' => 'Protegg',
 'vector-action-undelete' => 'Arcùpera',
 'vector-action-unprotect' => 'Cangé la protession',
-'vector-simplesearch-preference' => "Abilité ij sugeriment d'arserca ameliorà (mach për la pel Vector)",
+'vector-simplesearch-preference' => "Abilité la bara d'arserca semplificà (mach për la pel Vector)",
 'vector-view-create' => 'Crea',
 'vector-view-edit' => 'Modìfica',
 'vector-view-history' => 'Varda stòria',
@@ -445,10 +445,10 @@ L'aministrator ch'a l'ha blocalo a l'ha lassà sta spiegassion: «$3».",
 'virus-unknownscanner' => 'antivìrus nen conossù:',
 
 # Login and logout pages
-'logouttext' => "'''A l'é sortù da 'nt ël sistema.'''
+'logouttext' => "'''A l'é surtì da 'nt ël sistema.'''
 
-A peul tiré anans a dovré {{SITENAME}} coma Utent anonim, ò pura a peul [[Special:UserLogin|rintré torna ant ël sistema]] con l'istess stranòm che a dovrava prima, ò con un diferent.
-Ch'a nòta che chèich pàgine a peulo continué a esse visualisà com s'a fussa ancó ant ël sistema, fin ch'a scancela pa la cache ëd sò navigador.",
+A peul tiré anans a dovré {{SITENAME}} coma Utent anònim, ò pura a peul <span class='plainlinks'>[$1 rintré torna ant ël sistema]</span> con l'istess stranòm che a dovrava prima, ò con un diferent.
+Ch'a nòta che chèiche pàgine a peulo continué a esse visualisà com s'a fussa ancor ant ël sistema, fin ch'a scancela nen la memòria local ëd sò navigador.",
 'welcomecreation' => '==Bin ëvnù, $1!==
 Sò cont a l\'é stàit creà.
 Che as dësmentia pa ëd cambié ij [[Special:Preferences|"sò gust" an {{SITENAME}}]].',
@@ -2838,10 +2838,10 @@ Sòn a l'é motobin belfé che a sia rivà përchè a-i era n'anliura a un sit e
 'pageinfo-authors' => "Nùmer d'autor diferent",
 'pageinfo-recent-edits' => "Nùmer ëd modìfiche recente (ant j'ùltim $1)",
 'pageinfo-recent-authors' => "Nùmer d'autor diferent recent",
-'pageinfo-restriction' => 'Protession ëd la pàgina ({{lcfirst:$1}})',
 'pageinfo-magic-words' => '{{PLURAL:$1|Paròla màgica|Paròle màgiche}} ($1)',
 'pageinfo-hidden-categories' => '{{PLURAL:$1|Categorìa|Categorìe}} stërmà ($1)',
 'pageinfo-templates' => '{{PLURAL:$1|stamp contnù|stamp contnù}} ($1)',
+'pageinfo-toolboxlink' => 'Anformassion an sla pagina',
 
 # Patrolling
 'markaspatrolleddiff' => 'Marché coma verificà',
@@ -3038,7 +3038,7 @@ J'àutri a saran stërmà coma stàndard.
 'exif-gpsstatus' => 'Condission dël ricevitor',
 'exif-gpsmeasuremode' => "Sistema d'amzura",
 'exif-gpsdop' => "Precision dl'amzura",
-'exif-gpsspeedref' => "Unità d'amzura për la velocità",
+'exif-gpsspeedref' => "Unità d'amzura për l'andi",
 'exif-gpsspeed' => 'Velocità dël ricevitor GPS',
 'exif-gpstrackref' => 'Arferiment për la diression dël moviment',
 'exif-gpstrack' => 'Diression dël moviment',
@@ -3050,7 +3050,7 @@ J'àutri a saran stërmà coma stàndard.
 'exif-gpsdestlongituderef' => 'Arferiment për la longitùdin dla destinassion',
 'exif-gpsdestlongitude' => 'Longitùdin dla destinassion',
 'exif-gpsdestbearingref' => "Arferiment për l'orientament a destinassion",
-'exif-gpsdestbearing' => 'Orientament anvers a la destinassion',
+'exif-gpsdestbearing' => 'Orientament vers la destinassion',
 'exif-gpsdestdistanceref' => "Arferiment për la lontanansa da 'nt la destinassion",
 'exif-gpsdestdistance' => "Lontanansa da 'nt la destinassion",
 'exif-gpsprocessingmethod' => 'Nòm dël sistema ëd process an GPS',
@@ -3061,7 +3061,7 @@ J'àutri a saran stërmà coma stàndard.
 'exif-keywords' => 'Paròle ciav',
 'exif-worldregioncreated' => "Region dël mond anté che la fòto a l'é stàita pijà",
 'exif-countrycreated' => "Pais anté che la fòto a l'é stàita fàita",
-'exif-countrycodecreated' => "Còdes dëlpais anté che la fòto a l'é stàita pijà",
+'exif-countrycodecreated' => "Còdes dël pais anté che la fòto a l'é stàita pijà",
 'exif-provinceorstatecreated' => "Provinsa o stat anté che la fòto a l'é stàita pijà",
 'exif-citycreated' => "Sità anté che la fòto a l'é stàita pijà",
 'exif-sublocationcreated' => "Borgh ëd la sità anté che la fòto a l'é stàita pijà",
@@ -3078,7 +3078,7 @@ J'àutri a saran stërmà coma stàndard.
 'exif-source' => 'Sorgiss',
 'exif-editstatus' => 'Stat ëd modìfica dla figura',
 'exif-urgency' => 'Pressa',
-'exif-fixtureidentifier' => 'Nòm utiss',
+'exif-fixtureidentifier' => 'Nòm element arcorent',
 'exif-locationdest' => 'Locassion fotografà',
 'exif-locationdestcode' => 'Còdes ëd la locassion fotografà',
 'exif-objectcycle' => "Ora dël di ëd destinassion d'ës mojen",
@@ -3094,7 +3094,7 @@ J'àutri a saran stërmà coma stàndard.
 'exif-identifier' => 'Identificator',
 'exif-lens' => 'Lent dovrà',
 'exif-serialnumber' => 'Nùmer serial ëd la màchina fotogràfica',
-'exif-cameraownername' => 'Padron ëd la màchina fotogràfica',
+'exif-cameraownername' => 'Propietari ëd la màchina fotogràfica',
 'exif-label' => 'Tichëtta',
 'exif-datetimemetadata' => "Quand ij metadat a son stàit modificà l'ùltima vira",
 'exif-nickname' => 'Nòm anformal ëd la figura',
@@ -3102,9 +3102,9 @@ J'àutri a saran stërmà coma stàndard.
 'exif-rightscertificate' => 'Sertificà ëd gestion dij drit',
 'exif-copyrighted' => "Stat dël drit d'autor",
 'exif-copyrightowner' => "Titolar dël drit d'autor",
-'exif-usageterms' => "Termo d'usagi",
-'exif-webstatement' => "Diciarassion an linia dël drit d'autor",
-'exif-originaldocumentid' => 'ID unìvoch dël document original',
+'exif-usageterms' => "Condission d'utilisassion",
+'exif-webstatement' => "Diciarassion ëd drit d'autor an linia",
+'exif-originaldocumentid' => 'Identificativ ùnich dël papé original',
 'exif-licenseurl' => "Anliura ëd la licensa dij drit d'autor",
 'exif-morepermissionsurl' => 'Anformassion an sle license alternativa',
 'exif-attributionurl' => "Quand as deuvra torna cost travaj, për piasì ch'a-j buta l'anliura a",
@@ -3423,6 +3423,7 @@ Cost còdes ëd conferma a scad ai \$4.",
 # Scary transclusion
 'scarytranscludedisabled' => "[L'inclusion ëd pàgine antra wiki diferente a l'é nen abilità]",
 'scarytranscludefailed' => "[Darmagi, ma lë stamp $1 a l'é pa podusse carié]",
+'scarytranscludefailed-httpstatus' => '[Letura dlë stamp falìa për $1: HTTP $2]',
 'scarytranscludetoolong' => "[L'URL a l'é tròp longa]",
 
 # Delete conflict
@@ -3715,6 +3716,7 @@ Dësnò, a peule dovré ël formlari semplificà sì-sota. Sò coment a sarà gi
 
 # Search suggestions
 'searchsuggest-search' => 'Arserca',
+'searchsuggest-containing' => 'contenent ...',
 
 # API errors
 'api-error-badaccess-groups' => "Chiel a peul pa carié d'archivi su sta wiki.",
index 906d882..22e90e8 100644 (file)
@@ -411,8 +411,8 @@ $messages = array(
 
 # Login and logout pages
 'logouttext' => "'''تسی لاگ آؤٹ ہوگۓ او.'''
-تسی   {{SITENAME}} نوں گمنامی چ ورت سکدے او یا تسی [[Special:UserLogin|لاگ ان دوبارہ]] ہوجاؤ اوسے ناں توں یا وکھرے ورتن والے توں۔ اے گل چیتے رکھنا جے کج صفیاں تے تسی لاگ ان دسے جاؤگے جدوں تک تسی اپنے براؤزر دے کاشے نوں صاف ناں کرلو۔
-You can continue to use {{SITENAME}} anonymously, or you can [[Special:UserLogin|log in again]] as the same or as a different user.
+تسی   {{SITENAME}} نوں گمنامی چ ورت سکدے او یا تسی <span class='plainlinks'>[$1 لاگ ان دوبارہ]</span> ہوجاؤ اوسے ناں توں یا وکھرے ورتن والے توں۔ اے گل چیتے رکھنا جے کج صفیاں تے تسی لاگ ان دسے جاؤگے جدوں تک تسی اپنے براؤزر دے کاشے نوں صاف ناں کرلو۔
+You can continue to use {{SITENAME}} anonymously, or you can <span class='plainlinks'>[$1 log in again]</span> as the same or as a different user.
 Note that some pages may continue to be displayed as if you were still logged in, until you clear your browser cache.",
 'welcomecreation' => '== جی آیاں نوں, $1! ==
 تواڈا کھاتا بن گیا اے۔
index 7f223b8..7571d87 100644 (file)
@@ -376,7 +376,7 @@ Drēudisnas pagrintinsna: "$2".',
 # Login and logout pages
 'logouttext' => "'''Tū assei teinū izgūbun.'''
 
-Tū mazzi ēmpirsin sadīntun tērpautun {{SITENAME}} kāigi niengūbuns tērpautajs, anga [[Special:UserLogin|enēitwei etkūmps]] kāigi šis sūbs anga kits tērpautajs.
+Tū mazzi ēmpirsin sadīntun tērpautun {{SITENAME}} kāigi niengūbuns tērpautajs, anga <span class='plainlinks'>[$1 enēitwei etkūmps]</span> kāigi šis sūbs anga kits tērpautajs.
 Endirēis, kāi ainuntai pāusai mazzi būtwei waidīntan ikāigi tū būlai ainatīngi engūbun, ērgi tū wīrst skistinnuns lasātlas rānkas minīsnan.",
 'welcomecreation' => '== Kaīls, $1! ==
 Twājs rekkens pastāi teīktan.
index ad0aec0..d7cf926 100644 (file)
@@ -531,7 +531,7 @@ $1',
 # Login and logout pages
 'logouttext' => "'''تاسې اوس د غونډال نه ووتلی.'''
 
-تاسې کولای شی چې د کارن-نوم نه پرته په ورکنومي توګه {{SITENAME}} وکاروی، او يا هم په همدې او يا کوم بل کارن-نوم، يو ځل [[Special:UserLogin|بيا غونډال ته ورننوځۍ]].
+تاسې کولای شی چې د کارن-نوم نه پرته په ورکنومي توګه {{SITENAME}} وکاروی، او يا هم په همدې او يا کوم بل کارن-نوم، يو ځل <span class='plainlinks'>[$1 بيا غونډال ته ورننوځۍ]</span>.
 دا په پام کې وساتۍ چې تر څو تاسې د خپل کتنمل حافظه نه وي سپينه کړې، نو ځينې مخونو کې به لا تر اوسه پورې په غونډال کې ننوتي ښکارۍ.",
 'welcomecreation' => '==$1 ښه راغلۍ! ==
 
index 728c87c..cc58d37 100644 (file)
@@ -717,7 +717,7 @@ O administrador que efetuou o bloqueio deu a seguinte explicação: "$3".',
 # Login and logout pages
 'logouttext' => "'''Já não está autenticado.'''
 
-Pode continuar a utilizar a {{SITENAME}} anonimamente, ou pode [[Special:UserLogin|autenticar-se novamente]] com o mesmo nome de utilizador ou com um nome de utilizador diferente.
+Pode continuar a utilizar a {{SITENAME}} anonimamente, ou pode <span class='plainlinks'>[$1 autenticar-se novamente]</span> com o mesmo nome de utilizador ou com um nome de utilizador diferente.
 Tenha em atenção que algumas páginas poderão continuar a ser apresentadas como se ainda estivesse autenticado até limpar a cache do seu browser.",
 'welcomecreation' => '== Bem-vindo, $1! ==
 A sua conta foi criada.
@@ -3146,7 +3146,6 @@ Este bloqueio foi provavelmente causado por um link para um site externo que con
 'pageinfo-authors' => 'Número total de autores distintos',
 'pageinfo-recent-edits' => 'Número de edições recentes (nos últimos $1)',
 'pageinfo-recent-authors' => 'Número recente de autores distintos',
-'pageinfo-restriction' => 'Proteção da página ({{lcfirst:$1}})',
 'pageinfo-magic-words' => '{{PLURAL:$1|Palavra mágica|Palavras mágicas}} ($1)',
 'pageinfo-hidden-categories' => '{{PLURAL:$1|Categoria oculta|Categorias ocultas}} ($1)',
 
index 6ba4425..5db359b 100644 (file)
@@ -721,7 +721,7 @@ O administrador que bloqueou ofereceu a seguinte explicação: "$3".',
 # Login and logout pages
 'logouttext' => "'''Agora você encontra-se desautenticado.'''
 
-É possível continuar usando {{SITENAME}} anonimamente ou [[Special:UserLogin|autenticar-se novamente]] com o mesmo nome de usuário ou com um nome diferente.
+É possível continuar usando {{SITENAME}} anonimamente ou <span class='plainlinks'>[$1 autenticar-se novamente]</span> com o mesmo nome de usuário ou com um nome diferente.
 Note que algumas páginas podem continuar sendo exibidas como se você ainda estivesse autenticado até que você limpe a ''cache'' do seu navegador.",
 'welcomecreation' => '== Bem-vindo(a), $1! ==
 A sua conta foi criada.
@@ -3107,7 +3107,6 @@ Tal bloqueio foi provavelmente causado por uma ligação para um ''website'' ext
 'pageinfo-authors' => 'Número total de autores distintos',
 'pageinfo-recent-edits' => 'Número de edições recentes (nos últimos $1)',
 'pageinfo-recent-authors' => 'Número recente de autores distintos',
-'pageinfo-restriction' => 'Proteção da página ({{lcfirst:$1}})',
 
 # Skin names
 'skinname-standard' => 'Clássico',
index a895f0b..30e1ec1 100644 (file)
@@ -726,7 +726,8 @@ See also {{msg-mw|protectedinterface}}.',
 'exception-nologin-text' => 'Generic reason displayed on error page when a user is not logged in. Message used by the UserNotLoggedIn exception.',
 
 # Login and logout pages
-'logouttext' => 'Log out message',
+'logouttext' => 'Log out message
+* $1 is an URL to [[Special:Userlogin]] containing returnto and returntoquery parameters',
 'welcomecreation' => 'The welcome message users see after registering a user account. $1 is the username of the new user.',
 'yourname' => "In user preferences
 
@@ -1059,6 +1060,11 @@ Please report at [[Support]] if you are unable to properly translate this messag
 'moveddeleted-notice' => 'Shown on top of a deleted page in normal view modus ([http://translatewiki.net/wiki/Test example]).',
 'edit-conflict' => "An 'Edit conflict' happens when more than one edit is being made to a page at the same time. This would usually be caused by separate individuals working on the same page. However, if the system is slow, several edits from one individual could back up and attempt to apply simultaneously - causing the conflict.",
 'defaultmessagetext' => 'Caption above the default message text shown on the left-hand side of a diff displayed after clicking “Show changes” when creating a new page in the MediaWiki: namespace',
+'content-failed-to-parse' => "Error message indicating that the page\'s content can not be saved because it is syntactically invalid. This may occurr for content types using serialization or a strict markup syntax.",
+'invalid-content-data'             => 'Error message indicating that the page\'s content can not be saved because it is invalid. This may occurr for content types with internal consistency constraints.',
+'content-not-allowed-here'         => 'Error message indicating that the desired content model is not supported in given localtion.
+* $1 is the human readable name of the content model
+* $1 is the title of the page in question.',
 
 # Parser/template warnings
 'expensive-parserfunction-warning' => 'On some (expensive) [[MetaWikipedia:Help:ParserFunctions|parser functions]] (e.g. <code><nowiki>{{#ifexist:}}</nowiki></code>) there is a limit of how many times it may be used. This is an error message shown when the limit is exceeded.
@@ -1860,7 +1866,7 @@ This action allows editing of all of the "user rights", not just the rights of t
 'recentchanges-legend' => 'Legend of the fieldset of [[Special:RecentChanges]]',
 'recentchanges-summary' => 'Summary of [[Special:RecentChanges]].',
 'recentchanges-label-newpage' => 'Tooltip for {{msg-mw|newpageletter}}',
-'recentchanges-label-minor' => 'Tooltip for {{msg-mw|newpageletter}}',
+'recentchanges-label-minor' => 'Tooltip for {{msg-mw|minoreditletter}}',
 'recentchanges-label-bot' => 'Tooltip for {{msg-mw|boteditletter}}',
 'recentchanges-label-unpatrolled' => 'Tooltip for {{msg-mw|unpatrolledletter}}',
 'rcnote' => 'Used on [[Special:RecentChanges]].
@@ -2888,6 +2894,8 @@ Options for the duration of the page protection. Example: See e.g. [[MediaWiki:P
 {{Identical|Reset}}',
 'undeleteinvert' => '{{Identical|Invert selection}}',
 'undeletecomment' => '{{Identical|Reason}}',
+'cannotundelete' => 'Message shown when undeletion failed for some reason.
+* <code>$1</code> is the combined wikitext of messages for all errors that caused the failure.',
 'undelete-search-title' => 'Page title when showing the search form in Special:Undelete',
 'undelete-search-submit' => '{{Identical|Search}}',
 'undelete-error' => 'Page title when a page could not be undeleted',
@@ -2909,11 +2917,12 @@ Options for the duration of the page protection. Example: See e.g. [[MediaWiki:P
 
 # Namespace form on various pages
 'namespace' => 'This message is located at [[Special:Contributions]].',
-'invert' => 'Displayed in [[Special:RecentChanges|RecentChanges]], [[Special:RecentChangesLinked|RecentChangesLinked]] and [[Special:Watchlist|Watchlist]]
+'invert' => 'Displayed in [[Special:RecentChanges|RecentChanges]], [[Special:RecentChangesLinked|RecentChangesLinked]] and [[Special:Watchlist|Watchlist]].
 
-{{Identical|Invert selection}}
+This message means "Invert selection of namespace".
 
-This message has a tooltip {{msg-mw|tooltip-invert}}',
+This message has a tooltip {{msg-mw|tooltip-invert}}
+{{Identical|Invert selection}}',
 'tooltip-invert' => 'Used in [[Special:Recentchanges]] as a tooltip for the invert checkbox. See also the message {{msg-mw|invert}}',
 'namespace_association' => 'Used in [[Special:Recentchanges]] with a checkbox which selects the associated namespace to be added to the selected namespace, so that both are searched (or excluded depending on another checkbox selection). The association is between a namespace and its talk namespace.
 
@@ -3191,6 +3200,10 @@ Parameters:
 'immobile-target-namespace-iw' => "This message appears when attempting to move a page, if a person has typed an interwiki link as a namespace prefix in the input box labelled 'To new title'.  The special page 'Movepage' cannot be used to move a page to another wiki.
 
 'Destination' can be used instead of 'target' in this message.",
+'bad-target-model'             => "This message is shown when attempting to move a page, but the move would change the page's content model.
+This may be the case when \$wgContentHandlerUseDB is set to false, because then a page's content model is derived from the page's title.
+* $1: The localized name of the original page's content model.
+* $2: The localized name of the content model used by the destination title.",
 'fix-double-redirects' => 'This is a checkbox in [[Special:MovePage]] which allows to move all redirects from the old title to the new title.',
 'protectedpagemovewarning' => 'Related message: [[MediaWiki:protectedpagewarning/{{#titleparts:{{PAGENAME}}|1|2}}]]
 {{Related|Semiprotectedpagewarning}}',
@@ -3504,7 +3517,7 @@ See also {{msg-mw|Anonuser}} and {{msg-mw|Siteusers}}.',
 * $1 is the number of hidden categories on the page.',
 'pageinfo-templates' => 'The list of templates transcluded within the page. Parameters:
 * $1 is the number of templates transcluded within the page.',
-'pageinfo-toolboxlink' => 'Information link for the page (like \'What links here\', but to action=info for the current page instead)',
+'pageinfo-toolboxlink' => "Information link for the page (like 'What links here', but to action=info for the current page instead)",
 
 # Skin names
 'skinname-standard' => '{{optional}}
@@ -4350,7 +4363,7 @@ See also [[MediaWiki:Confirmemail_body_changed]].
 # Scary transclusion
 'scarytranscludedisabled' => 'Shown when scary transclusion is disabled.',
 'scarytranscludefailed' => 'Shown when the HTTP request for the template failed.',
-'scarytranscludefailed-httpstatus' => 'Identical to scarytranscludefailed, but shows the HTTP error which was received.',
+'scarytranscludefailed-httpstatus' => 'Identical to {{msg-mw|scarytranscludefailed}}, but shows the HTTP error which was received.',
 'scarytranscludetoolong' => 'The URL was too long.',
 
 'unit-pixel' => '{{optional}}',
@@ -4900,4 +4913,10 @@ $4 is the gender of the target user.',
 'api-error-uploaddisabled' => 'API error message that can be used for client side localisation of API errors.',
 'api-error-verification-error' => 'The word "extension" refers to the part behind the last dot in a file name, that by convention gives a hint about the kind of data format which a files contents are in.',
 
+# Content model IDs for the ContentHandler facility; used by ContentHandler::getContentModel()
+'content-model-wikitext' => 'Name for the wikitext content model, used when decribing what type of content a page contains.',
+'content-model-javascript' => 'Name for the JavaScript content model, used when decribing what type of content a page contains.',
+'content-model-css' => 'Name for the CSS content model, used when decribing what type of content a page contains.',
+'content-model-text' => 'Name for the plain text content model, used when decribing what type of content a page contains.',
+
 );
index 21cb4ec..8e7e833 100644 (file)
@@ -660,7 +660,7 @@ Amachaq kamachiqqa kayrayku amachani nispa nirqanmi: "$3".',
 # Login and logout pages
 'logouttext' => "'''Llamk'apuy tiyayniykiqa puchukasqañam.'''
 
-Sutinnaq kaspaykipas {{SITENAME}}pi wamp'uytam atinki. Mana hinataq munaspaykiqa, [[Special:UserLogin|musuqmanta yaykuy]] ñawpaq icha huk sutiwan. Huk p'anqakunaqa kaqllam rikch'akunqa, ''cache'' nisqa pakasqa hallch'ata mana ch'usaqchaptiykiqa.",
+Sutinnaq kaspaykipas {{SITENAME}}pi wamp'uytam atinki. Mana hinataq munaspaykiqa, <span class='plainlinks'>[$1 musuqmanta yaykuy]</span> ñawpaq icha huk sutiwan. Huk p'anqakunaqa kaqllam rikch'akunqa, ''cache'' nisqa pakasqa hallch'ata mana ch'usaqchaptiykiqa.",
 'welcomecreation' => '== Allinmi hamusqayki $1! ==
 Rakiqunaykiqa kicharisqañam.
 Ama qunqaychu [[Special:Preferences|{{SITENAME}} allinkachinaykikunata]] kikinchayta.',
index 8fedd55..e295cd6 100644 (file)
@@ -437,7 +437,7 @@ Il motiv inditgà è "\'\'$2\'\'".',
 # Login and logout pages
 'logouttext' => "'''Sortì cun success.'''
 
-Ti pos cuntinuar cun utilisar {{SITENAME}} anonimamain, u che ti pos [[Special:UserLogin|t'annunziar]] sco medem u in'auter utilisader. Resguarda che entginas paginas pon anc vesair or tuttina sco sche ti eras annunzià enfin che ti has stizzà il cache da tes navigatur.",
+Ti pos cuntinuar cun utilisar {{SITENAME}} anonimamain, u che ti pos <span class='plainlinks'>[$1 t'annunziar]</span> sco medem u in'auter utilisader. Resguarda che entginas paginas pon anc vesair or tuttina sco sche ti eras annunzià enfin che ti has stizzà il cache da tes navigatur.",
 'welcomecreation' => '==Bainvegni, $1! ==
 Tes conto è vegni creà.
 Betg emblida da midar tias [[Special:Preferences|preferenzas da {{SITENAME}}]].',
index 1172cdc..88fbdca 100644 (file)
@@ -712,7 +712,7 @@ Administratorul care a efectuat blocarea a furnizat explicația: „$3”.',
 # Login and logout pages
 'logouttext' => "'''Acum sunteți deconectat.'''
 
-Sesiunea dumneavoastră la {{SITENAME}} a fost închisă. Puteți continua să folosiți {{SITENAME}} ca utilizator anonim, sau puteți să vă [[Special:UserLogin|reautentificați]] ca același sau ca alt utilizator.
+Sesiunea dumneavoastră la {{SITENAME}} a fost închisă. Puteți continua să folosiți {{SITENAME}} ca utilizator anonim, sau puteți să vă <span class='plainlinks'>[$1 reautentificați]</span> ca același sau ca alt utilizator.
 Țineți minte că anumite pagini pot fi în continuare afișate ca și când ați fi autentificat până când curățați memoria cache a navigatorului.",
 'welcomecreation' => '==Bun venit, $1!==
 
@@ -3108,10 +3108,10 @@ Permite adăugarea unui motiv în descrierea modificărilor',
 'pageinfo-authors' => 'Număr total de autori distincți',
 'pageinfo-recent-edits' => 'Număr de modificări recente (în ultima perioadă de $1)',
 'pageinfo-recent-authors' => 'Număr de autori distincți recenți',
-'pageinfo-restriction' => 'Protecție pagină ({{lcfirst:$1}})',
 'pageinfo-magic-words' => '{{PLURAL:$1|Cuvânt magic|Cuvinte magice}} ($1)',
 'pageinfo-hidden-categories' => '{{PLURAL:$1|Categorie ascunsă|Categorii ascunse}} ($1)',
 'pageinfo-templates' => '{{PLURAL:$1|Format inclus|Formate incluse}} ($1)',
+'pageinfo-toolboxlink' => 'Informații despre pagină',
 
 # Skin names
 'skinname-standard' => 'Clasic',
@@ -3691,6 +3691,7 @@ Acest cod de confirmare va expira la $4.',
 # Scary transclusion
 'scarytranscludedisabled' => '[Transcluderea interwiki este dezactivată]',
 'scarytranscludefailed' => '[Șiretlicul formatului a dat greș pentru $1]',
+'scarytranscludefailed-httpstatus' => '[Șiretlicul formatului a dat greș pentru $1: HTTP $2]',
 'scarytranscludetoolong' => '[URL-ul este prea lung]',
 
 # Delete conflict
index 2da6908..b26a477 100644 (file)
@@ -446,7 +446,7 @@ L\'amministratore ca l\'ha bloccate dèje sta spiegazione: "$3".',
 # Login and logout pages
 'logouttext' => "'''Tu tè scolleghete.'''
 
-Tu puè condinuà a ausà {{SITENAME}} in mode anonime, o tu puè [[Special:UserLogin|collegarte 'n'otra vote]] cumme 'u stesse utende o cumme 'n'otre utende.
+Tu puè condinuà a ausà {{SITENAME}} in mode anonime, o tu puè <span class='plainlinks'>[$1 collegarte 'n'otra vote]</span> cumme 'u stesse utende o cumme 'n'otre utende.
 Note Bbuene ca certe pàggene ponne condinuà a essere viste cumme ce tu ste angore colleghete, fine a quanne a cache d'u browser no se sdeveche.",
 'welcomecreation' => "== Bovegne, $1! ==
 'U cunde tue ha state ccrejete.
@@ -2929,7 +2929,6 @@ Stu fatte ha state causate da 'nu collegamende a 'nu site esterne ca appartene a
 'pageinfo-authors' => 'Numere Totale de autore diverse',
 'pageinfo-recent-edits' => "Numere de le urteme cangiaminde ('mbonde a $1)",
 'pageinfo-recent-authors' => 'Numere de le urteme autore diverse',
-'pageinfo-restriction' => "Protezione d'a pàgene ({{lcfirst:$1}})",
 'pageinfo-magic-words' => '{{PLURAL:$1|Parole|Parole}} maggiche ($1)',
 'pageinfo-hidden-categories' => '{{PLURAL:$1|Categorije|Categorije}} scunnute ($1)',
 'pageinfo-templates' => 'Esclude {{PLURAL:$1|template|template}} ($1)',
index 8019280..0e4a24a 100644 (file)
@@ -28,6 +28,7 @@
  * @author Dim Grits
  * @author Don Alessandro
  * @author Eleferen
+ * @author Erdemaslancan
  * @author EugeneZelenko
  * @author Eugrus
  * @author Express2000
@@ -803,7 +804,7 @@ $2',
 # Login and logout pages
 'logouttext' => "'''Вы завершили сеанс работы.'''
 
-Вы можете продолжить участие в {{grammar:genitive|{{SITENAME}}}} анонимно или [[Special:UserLogin|представиться заново]] под тем же или другим именем.
+Вы можете продолжить участие в {{grammar:genitive|{{SITENAME}}}} анонимно или <span class='plainlinks'>[$1 представиться заново]</span> под тем же или другим именем.
 Некоторые страницы могут продолжать отображаться в том виде, как будто вы всё ещё представлены системе. Для борьбы с этим явлением обновите кеш браузера.",
 'welcomecreation' => '== Добро пожаловать, $1! ==
 Ваша учётная запись создана.
@@ -972,7 +973,7 @@ $2
 'changeemail-oldemail' => 'Текущий адрес электронной почты:',
 'changeemail-newemail' => 'Новый адрес электронной почты:',
 'changeemail-none' => '(нет)',
-'changeemail-submit' => 'Ð\98зменениÑ\82Ñ\8c Ð°Ð´Ñ\80еÑ\81',
+'changeemail-submit' => 'Изменить адрес',
 'changeemail-cancel' => 'Отмена',
 
 # Edit page toolbar
@@ -3214,6 +3215,7 @@ The wiki server can't provide data in a format your client can read.",
 'pageinfo-views' => 'Количество просмотров',
 'pageinfo-watchers' => 'Число наблюдающих',
 'pageinfo-redirects-name' => 'Перенаправления на эту страницу',
+'pageinfo-redirects-value' => '$1',
 'pageinfo-subpages-name' => 'Подстраницы данной страницы',
 'pageinfo-subpages-value' => '$1($2 {{PLURAL:$2|перенаправление|перенаправления|перенаправлений}}; $3 {{PLURAL:$3|обычная|обычные|обычных}})',
 'pageinfo-firstuser' => 'Создатель страницы',
@@ -3224,7 +3226,6 @@ The wiki server can't provide data in a format your client can read.",
 'pageinfo-authors' => 'Общее число различных авторов',
 'pageinfo-recent-edits' => 'Правок за последнее время (в течение $1)',
 'pageinfo-recent-authors' => 'Уникальных авторов за последнее время',
-'pageinfo-restriction' => 'Защита страницы ({{lcfirst:$1}})',
 'pageinfo-magic-words' => '{{PLURAL:$1|Магическое слово|Магические слова}} ($1)',
 'pageinfo-hidden-categories' => '{{PLURAL:$1|Скрытая категория|Скрытых категорий}} ($1)',
 'pageinfo-templates' => '{{PLURAL:$1|Шаблон|Шаблонов}} ($1)',
index e75f66c..2f6168c 100644 (file)
@@ -522,7 +522,7 @@ $2',
 # Login and logout pages
 'logouttext' => "'''Нынї сьте одголошеный(а).'''
 
-Можете продовжовати в анонімнім перезераню і едітації {{grammar:2sg|{{SITENAME}}}}, або ся можете [[Special:UserLogin|зясь приголосити]] як тот самый або як іншый хоснователь. Даякы сторінкы ся можуть зображовати як кібы сьте были дотеперь приголошены, покы не змажете кеш переглядача.",
+Можете продовжовати в анонімнім перезераню і едітації {{grammar:2sg|{{SITENAME}}}}, або ся можете <span class='plainlinks'>[$1 зясь приголосити]</span> як тот самый або як іншый хоснователь. Даякы сторінкы ся можуть зображовати як кібы сьте были дотеперь приголошены, покы не змажете кеш переглядача.",
 'welcomecreation' => '== Вітаєме вас, $1! ==
 Ваше конто было вытворене.
 Не забудьте змінити свої [[Special:Preferences|наставлїня сайту]].',
index fe3cd0f..e6a1910 100644 (file)
@@ -679,7 +679,7 @@ $2',
 # Login and logout pages
 'logouttext' => "'''भवान् अधुना बहिरागतः ।'''
 
-भवान् {{SITENAME}} इत्येतत् अनामतया प्रयोक्तुं शक्नोति, अथवा भवान् तेनैव प्रयोक्तृनाम्ना, भिन्नप्रयोक्तृनाम्ना वा  [[Special:UserLogin|पुनः प्रवेष्टुं शक्नोति]]
+भवान् {{SITENAME}} इत्येतत् अनामतया प्रयोक्तुं शक्नोति, अथवा भवान् तेनैव प्रयोक्तृनाम्ना, भिन्नप्रयोक्तृनाम्ना वा  <span class='plainlinks'>[$1 पुनः प्रवेष्टुं शक्नोति]</span>
 इदानीमपि कानिचन पृष्ठानि पूर्ववदेव दृश्येरन् । अस्य वारणाय विचरकस्य स्मृतिसञ्चयः रिक्तीक्रियताम् ।",
 'welcomecreation' => '==स्वागतम्‌, $1!==
 भवता सदस्यता प्राप्ता अस्ति।
@@ -2988,7 +2988,6 @@ $2 इति प्रकारस्य अवरोधं कर्तुं 
 'pageinfo-authors' => 'प्रत्येककर्तॄणां समग्रा सङ्ख्या ।',
 'pageinfo-recent-edits' => 'सद्योजातसम्पादनानां सङ्ख्या (गतेषु $1 दिनेषु)',
 'pageinfo-recent-authors' => 'प्रत्येककर्तॄणां सद्यःकालीना सङ्ख्या ।',
-'pageinfo-restriction' => 'पृष्ठसंरक्षणम्  ({{lcfirst:$1}})',
 'pageinfo-magic-words' => 'मान्त्रिक{{PLURAL:$1|शब्दः|शब्दाः}} ($1)',
 'pageinfo-hidden-categories' => 'गोपित{{PLURAL:$1|वर्गः|वर्गाः}} ($1)',
 'pageinfo-templates' => 'समायोजित{{PLURAL:$1|फलकम्|फलकानि}} ($1)',
index 7998429..be7b5d0 100644 (file)
@@ -444,7 +444,7 @@ $2',
 'logouttext' => "'''Эн тиһиликтэн таҕыстыҥ.'''
 
 {{SITENAME}} ситим-сиргэ билигин урукку ааккынан буолбакка IP-аадырыһынан эрэ көстөҕүн.
-Салгыы ааккын ааттаабакка үлэлиэххин сөп, эбэтэр саҥаттан урукку ааккынан дуу, атын аатынан дуу [[Special:UserLogin|киириэххин]] сөп.
+Салгыы ааккын ааттаабакка үлэлиэххин сөп, эбэтэр саҥаттан урукку ааккынан дуу, атын аатынан дуу <span class='plainlinks'>[$1 киириэххин]</span> сөп.
 Сорох сирэйдэр өссө даҕаны эйигин урукку ааккынан көрдөрүөхтэрин сөп, ону суох гыныаххын баҕардаххына интэриниэт көрдөрөөччүҥ кээһин ыраастаа.",
 'welcomecreation' => '== Нөрүөн нөргүй, $1! ==
 Эн манна бэлиэтэнниҥ.
@@ -705,8 +705,8 @@ IP-аадырыһа эрэ көстөр.
 <span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} сурунаалларга көрдүөххүн сөп],
 эбэтэр [{{fullurl:{{FULLPAGENAME}}|action=edit}} маннык ааттаах саҥа ыстатыйаны суруйуоххун] сөп</span>.',
 'noarticletext-nopermission' => 'Билигин бу сирэй кураанах.
-Бу [[Special:Search/{{PAGENAME}}|тылы атын сирэйдэргэ көрдөөн көрүөххүн]] сөп,
-эбэтэр <span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} сурунаалларга манна сыһыаннаах суруктары булуоххун сөп].</span>',
+Бу [[Special:Search/{{PAGENAME}}|ааты атын сирэйдэргэ көрдөөн көрүөххүн]] сөп,
+эбэтэр <span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} сурунаалларга манна сыһыаннаах суруктары булуоххун сөп].</span> Бу сирэйи айар кыаҕыҥ суох.',
 'userpage-userdoesnotexist' => '"<nowiki>$1</nowiki>" аат бэлиэтэммэтэх. Бу сирэйи оҥорор/уларытар баҕалааххын дуо?',
 'userpage-userdoesnotexist-view' => '"$1" кыттааччы аата бэлиэтэниллибэтэх.',
 'blocked-notice-logextract' => 'Бу кыттааччы билигин бобуллубут.
index 518e78e..5e034bc 100644 (file)
@@ -392,7 +392,7 @@ Podet èsser stadu burradu dae calicunu àteru.',
 # Login and logout pages
 'logouttext' => "'''As acabadu sa sessione.'''
 
-Immoe podes sighire a impreare {{SITENAME}} in forma anònima, o ti podes [[Special:UserLogin|identificare torra]] comente su de prima o comente usuàriu diferente.
+Immoe podes sighire a impreare {{SITENAME}} in forma anònima, o ti podes <span class='plainlinks'>[$1 identificare torra]</span> comente su de prima o comente usuàriu diferente.
 Tene contu ca is pàginas ki sunt giai abertas in àteras bentanas podent sighire a pàrrer comente cando fias identificadu, fintzas a cando non ddas renfriscas.",
 'welcomecreation' => "== Benènnidu, $1! ==
 S'account tuo est istadu creadu.
index c2a4c2f..64ad9bb 100644 (file)
@@ -520,7 +520,7 @@ La mutivazzioni è chista: ''$2''.",
 # Login and logout pages
 'logouttext' => "'''Nisciuta. Ora siti fora.'''
 
-Poi cuntinuari a usari {{SITENAME}} di manera anònima, o poi [[Special:UserLogin|tràsiri n'àutra vota]] cu lu stissu o cu n'àutru nomu d'utenti.
+Poi cuntinuari a usari {{SITENAME}} di manera anònima, o poi <span class='plainlinks'>[$1 tràsiri n'àutra vota]</span> cu lu stissu o cu n'àutru nomu d'utenti.
 Accura chi quarchi pàggina pò cuntinuari a èssiri ammustrata comu si nun avissi nisciutu nzinu a quannu tu nun scancelli tutta la mimoria dû tò browser.",
 'welcomecreation' => "== Bonvinutu, $1! ==
 
index 339ce9c..7982896 100644 (file)
@@ -393,7 +393,7 @@ Ožklausėms: $2',
 # Login and logout pages
 'logouttext' => "'''Daba Tamsta esat atsėjongės.'''
 
-Galat ė tuoliau nauduotė {{SITENAME}} anuonimėškā aba [[Special:UserLogin|prisėjonkat]] ėš naujė šėtuo patiu a kėto nauduotuojė vardu.
+Galat ė tuoliau nauduotė {{SITENAME}} anuonimėškā aba <span class='plainlinks'>[$1 prisėjonkat]</span> ėš naujė šėtuo patiu a kėto nauduotuojė vardu.
 Pastebiejims: katruos nekatruos poslapiuos ė tuoliau gal ruodītė būktā būtomiet prisėjongės lėgė tuol, kumet ėšvalīsėt sava naršīklės dietovė (''cache'').",
 'welcomecreation' => '== Svēkė, $1! ==
 
index ffef3b9..a18343b 100644 (file)
@@ -616,7 +616,7 @@ Administrator koji ju je zaključao ponudio je sledeće objašnjenje: „$3“.'
 # Login and logout pages
 'logouttext' => "'''Sad ste odjavljeni.'''
 
-Možete nastaviti da koristite {{SITENAME}} anonimno, ili se ponovo [[Special:UserLogin|prijaviti]] kao isti ili kao drugi korisnik.
+Možete nastaviti da koristite {{SITENAME}} anonimno, ili se ponovo <span class='plainlinks'>[$1 prijaviti]</span> kao isti ili kao drugi korisnik.
 Obratite pažnju da neke stranice mogu nastaviti da se prikazuju kao da ste još uvijek prijavljeni, dok ne očistite keš svog preglednika.",
 'welcomecreation' => '== Dobro došli, $1! ==
 Vaš korisnički račun je napravljen.
@@ -3027,7 +3027,6 @@ Ovo je vjerovatno izazvano vezom ka vanjskoj nepoželjnoj stranici.',
 'pageinfo-authors' => 'Ukupni broj specifičnih autora',
 'pageinfo-recent-edits' => 'Broj nedavnih izmjena (u posljednjih $1)',
 'pageinfo-recent-authors' => 'Broj nedavnih specifičnih autora',
-'pageinfo-restriction' => 'Zaštita stranice ({{lcfirst:$1}})',
 'pageinfo-magic-words' => '{{PLURAL:$1|Magična riječ|Magične riječi}} ($1)',
 'pageinfo-hidden-categories' => '{{PLURAL:$1|Sakrivena kategorija|Sakrivene kategorije}} ($1)',
 'pageinfo-templates' => '{{PLURAL:$1|Uključeni šablon|Uključeni šabloni}} ($1)',
index 3d58cf5..c7ed509 100644 (file)
@@ -602,7 +602,7 @@ $2',
 # Login and logout pages
 'logouttext' => "'''ඔබ දැන් ගිණුමෙන් නික්මී ඇත.'''
 
-ඔබට නිර්නාමිකව {{SITENAME}} කටයුතු කරගෙන යාහැක, නැතහොත් පෙර පරිශීලක ලෙස හෝ වෙනත් පරිශීලකයෙකු ලෙස [[Special:UserLogin|නැවත ගිණුමක‍ට පිවිසිය හැක]].
+ඔබට නිර්නාමිකව {{SITENAME}} කටයුතු කරගෙන යාහැක, නැතහොත් පෙර පරිශීලක ලෙස හෝ වෙනත් පරිශීලකයෙකු ලෙස <span class='plainlinks'>[$1 නැවත ගිණුමක‍ට පිවිසිය හැක]</span>.
 ඔබගේ බ්‍රවුසරයෙහි පූර්වාපේක්‍ෂී සංචිතය (කෑෂය) පිරිසිදුකරන තෙක්, සමහරක් පිටු විසින් ඔබ තවදුරටත් පිවිසී ඇති බවක් දිගටම පෙන්නුම් කිරීමට ඉඩ ඇත.",
 'welcomecreation' => '== ආයුබෝවන්, $1! ==
 
index ee40452..5fcadbf 100644 (file)
@@ -686,7 +686,7 @@ Správca, ktorý ho zamkol ponúkol toto vysvetlenie: „$3“.',
 # Login and logout pages
 'logouttext' => "'''Práve ste sa odhlásili.'''
 
-Odteraz môžete používať {{GRAMMAR:akuzatív|{{SITENAME}}}} ako anonymný používateľ alebo sa môžete opäť [[Special:UserLogin|prihlásiť]] pod rovnakým alebo odlišným používateľským menom.
+Odteraz môžete používať {{GRAMMAR:akuzatív|{{SITENAME}}}} ako anonymný používateľ alebo sa môžete opäť <span class='plainlinks'>[$1 prihlásiť]</span> pod rovnakým alebo odlišným používateľským menom.
 Uvedomte si, že niektoré stránky sa môžu naďalej zobrazovať ako keby ste boli prihlásený, až kým nevymažete vyrovnávaciu pamäť vášho prehliadača.",
 'welcomecreation' => '== Vitaj, $1! ==
 
index 3e174bd..ea0db4d 100644 (file)
@@ -470,7 +470,7 @@ $1',
 'restorelink' => '$1 {{PLURAL:$1|izbrisano redakcijo|izbrisani redakciji|izbrisane redakcije|izbrisanih redakcij}}',
 'feedlinks' => 'Podajanje:',
 'feed-invalid' => 'Neveljavna vrsta naročniškega vira.',
-'feed-unavailable' => 'Živi zaznamki niso na voljo',
+'feed-unavailable' => 'Živi zaznamki niso na razpolago.',
 'site-rss-feed' => 'RSS-vir strani »$1«',
 'site-atom-feed' => 'Atom-vir strani »$1«',
 'page-rss-feed' => 'RSS-vir strani »$1«',
@@ -551,8 +551,8 @@ Vrnila ni nobene razlage.',
 'badtitle' => 'Nepravilen naslov',
 'badtitletext' => 'Navedeni naslov strani je neveljaven, prazen, napačno povezan k drugim jezikom oziroma wikiprojektom.
 Morda vsebuje enega ali več nepodprtih znakov.',
-'perfcached' => 'Navedeni podatki so shranjeni v predpomnilniku in morda niso popolnoma posodobljeni. V predpomnilniku {{PLURAL:$1|je|sta|so|je}} na voljo največ $1 {{PLURAL:$1|rezultat|rezultata|rezultate|rezultatov}}.',
-'perfcachedts' => 'Prikazani podatki so shranjeni v predpomnilniku in so bili nazadnje osveženi $1. V predpomnilniku {{PLURAL:$4|je|sta|so|je}} na voljo največ $4 {{PLURAL:$4|rezultat|rezultata|rezultate|rezultatov}}.',
+'perfcached' => 'Navedeni podatki so shranjeni v predpomnilniku in morda niso popolnoma posodobljeni. V predpomnilniku {{PLURAL:$1|je|sta|so|je}} na razpolago največ $1 {{PLURAL:$1|rezultat|rezultata|rezultate|rezultatov}}.',
+'perfcachedts' => 'Prikazani podatki so shranjeni v predpomnilniku in so bili zadnjič osveženi $1. V predpomnilniku {{PLURAL:$4|je|sta|so|je}} na razpolago največ $4 {{PLURAL:$4|rezultat|rezultata|rezultate|rezultatov}}.',
 'querypage-no-updates' => 'Posodobitve za to stran so trenutno onemogočene. Tukajšnji podatki se v kratkem ne bodo osvežili.',
 'wrong_wfQuery_params' => 'Nepravilni parametri za wfQuery()<br />
 Funkcija: $1<br />
@@ -596,7 +596,7 @@ Administrator, ki ga je zaklenil, je podal naslednje pojasnilo: »$3«.',
 # Login and logout pages
 'logouttext' => "'''Odjavili ste se.'''
 
-{{GRAMMAR:tožilnik|{{SITENAME}}}} lahko zdaj uporabljate neprijavljeni ali pa se [[Special:UserLogin|ponovno prijavite]] kot enak ali drug uporabnik.
+{{GRAMMAR:tožilnik|{{SITENAME}}}} lahko zdaj uporabljate neprijavljeni ali pa se <span class='plainlinks'>[$1 ponovno prijavite]</span> kot enak ali drug uporabnik.
 Morda bodo nekatere strani še naprej prikazane, kot da ste prijavljeni, dokler ne boste izpraznili predpomnilnika brskalnika.",
 'welcomecreation' => '== Dobrodošli, $1! ==
 Ustvarili ste račun.
@@ -962,8 +962,8 @@ Lahko se vrnete nazaj in urejate že obstoječe strani, ali pa se [[Special:User
 
 Premislite preden nadaljujete s pisanjem, morda bo stran zaradi istih razlogov ponovno odstranjena.
 Spodaj je prikazan dnevnik brisanja in prestavljanja:",
-'moveddeleted-notice' => 'Ta stran je bila izbrisana.
-Dnevnik brisanja in prestavljanja strani je na voljo spodaj.',
+'moveddeleted-notice' => 'Stran je bila izbrisana.
+Spodaj sta za sklicevanje na razpolago dnevnik brisanja in dnevnik prestavljanja strani.',
 'log-fulllog' => 'Ogled celotnih dnevniških zapiskov',
 'edit-hook-aborted' => 'Urejanje je bilo brez obrazložitve prekinjeno zaradi neznane napake.',
 'edit-gone-missing' => 'Strani ni mogoče posodobiti.
@@ -1716,7 +1716,7 @@ MGP # Pentax
 PICT # mešano
  #</pre> <!-- pustite to vrstico takšno, kot je -->',
 'upload-success-subj' => 'Datoteka je bila uspešno naložena',
-'upload-success-msg' => 'Vaša datoteka iz [$2] je bila uspešno naložena. Na voljo je tukaj: [[:{{ns:file}}:$1]]',
+'upload-success-msg' => 'Datoteka s strani [$2] se je uspešno naložila. Na razpolago je tukaj: [[:{{ns:file}}:$1]]',
 'upload-failure-subj' => 'Težava pri nalaganju',
 'upload-failure-msg' => 'Prišlo je do težave z vašo naloženo datoteko iz [$2]:
 
@@ -1736,7 +1736,7 @@ Prosimo, preverite veljavnost in dostopnost naslova URL ter poskusite ponovno.
 'upload-too-many-redirects' => 'URL vsebuje preveč preusmeritev',
 'upload-unknown-size' => 'neznana velikost',
 'upload-http-error' => 'Prišlo je do napake HTTP: $1',
-'upload-copy-upload-invalid-domain' => 'Nalaganje kopij s te domene ni na voljo.',
+'upload-copy-upload-invalid-domain' => 'Nalaganje kopij s te domene ni možno.',
 
 # File backend
 'backend-fail-stream' => 'Ne morem pretakati datoteke $1.',
@@ -1877,8 +1877,8 @@ Ko so rezultati filtrirani po uporabniku, so prikazane samo datoteke, pri kateri
 'imagelinks' => 'Uporaba datoteke',
 'linkstoimage' => 'Datoteka je del {{PLURAL:$1|naslednje $1 strani|naslednjih $1 strani}} {{GRAMMAR:rodilnik|{{SITENAME}}}}:',
 'linkstoimage-more' => 'Na to datoteko se {{PLURAL:$1|povezuje več kot $1 stran|povezujeta več kot $1 strani|povezujejo več kot $1 strani|povezuje več kot $1 strani}}.
-Naslednji seznam obsega samo {{PLURAL:$1|prvo stran, ki se povezuje|prvi $1 strani, ki se povezujeta|prve $1 strani, ki se povezujejo|prvih $1 strani, ki se povezujejo}} na to datoteko.
-Na razpolago je tudi [[Special:WhatLinksHere/$2|celotni seznam]].',
+Naslednji seznam obsega samo {{PLURAL:$1|prvo stran, ki se povezuje|prvi $1 strani, ki se povezujeta|prve $1 strani, ki se povezujejo|prvih $1 strani, ki se povezujejo}} na datoteko.
+Na razpolago je tudi [[Special:WhatLinksHere/$2|popoln seznam]].',
 'nolinkstoimage' => 'Z datoteko se ne povezuje nobena stran.',
 'morelinkstoimage' => 'Preglejte [[Special:WhatLinksHere/$1|več povezav]] na to datoteko.',
 'linkstoimage-redirect' => '$1 (preusmeritev datoteke) $2',
@@ -2162,8 +2162,8 @@ Podprti protokoli: <code>$1</code> (teh ne dodajte v svoje iskanje).',
 
 # Special:ListGroupRights
 'listgrouprights' => 'Pravice uporabniških skupin',
-'listgrouprights-summary' => 'Spodaj se nahaja seznam uporabniških skupin na tem wikiju in njim dodeljene pravice dostopa.
-Na voljo so morda [[{{MediaWiki:Listgrouprights-helppage}}|dodatne informacije]] o posameznih skupinah.',
+'listgrouprights-summary' => 'Tu je na razpolago seznam uporabniških skupin na tem wikiju z navedbo dodeljenih pravic dostopa.
+Morda so na razpolago tudi [[{{MediaWiki:Listgrouprights-helppage}}|dodatne informacije]] o posameznih skupinah.',
 'listgrouprights-key' => '* <span class="listgrouprights-granted">Dodeljena pravica</span>
 * <span class="listgrouprights-revoked">Odvzeta pravica</span>',
 'listgrouprights-group' => 'Skupina',
@@ -2613,7 +2613,7 @@ Oglejte si [[Special:BlockList|seznam blokad]] za pregled blokad.',
 Razlog za blokado uporabnika $1 je: »$2«',
 'blocklogpage' => 'Dnevnik blokiranja',
 'blocklog-showlog' => 'Ta uporabnik je že bil blokiran.
-Dnevnik blokiranja je na voljo spodaj:',
+Za sklicevanje so tule navedeni vnosi v dnevniku blokiranja:',
 'blocklog-showsuppresslog' => 'Ta uporabnik je že bil blokiran in skrit.
 Dnevnik skrivanja je na voljo spodaj:',
 'blocklogentry' => '[[$1]] blokiran s časom poteka blokade $2 $3',
@@ -2762,10 +2762,10 @@ strani ni mogoče prestaviti samo vaše.',
 'imageinvalidfilename' => 'Ciljno ime datoteke je neveljavno',
 'fix-double-redirects' => 'Posodobi vse preusmeritve, ki kažejo na prvotni naslov',
 'move-leave-redirect' => 'Na prejšnji strani ustvari preusmeritev',
-'protectedpagemovewarning' => "'''Opozorilo:''' Stran je bila zaklenjena, tako da jo lahko prestavljajo samo uporabniki z administratorskimi dovoljenji.
-Najnovejši vnos v dnevniku je na voljo spodaj:",
-'semiprotectedpagemovewarning' => "'''Opomba:''' Stran je bila zaklenjena, tako da jo lahko prestavljajo samo registrirani uporabniki.
-Najnovejši vnos v dnevniku je na voljo spodaj:",
+'protectedpagemovewarning' => "'''Opozorilo:''' Stran je bila zaklenjena in jo lahko prestavljajo samo uporabniki z administratorskimi pravicami.
+Za sklicevanje je naveden zadnji vnos v dnevnik:",
+'semiprotectedpagemovewarning' => "'''Opomba:''' Stran je bila zaklenjena in jo lahko prestavljajo samo registrirani uporabniki.
+Za sklicevanje je naveden zadnji vnos v dnevniku:",
 'move-over-sharedrepo' => '== Datoteka obstaja ==
 [[:$1]] obstaja v deljeni shrambi. Premik datoteke na ta naslov bo prepisalo deljeno datoteko.',
 'file-exists-sharedrepo' => 'Izbrano ime datoteke je že v uporabi v deljeni shrambi.
@@ -3015,10 +3015,10 @@ Omogoča vnos pojasnila v povzetku urejanja.',
 'pageinfo-authors' => 'Skupno število različnih avtorjev',
 'pageinfo-recent-edits' => 'Nedavno število urejanj (zadnjih $1)',
 'pageinfo-recent-authors' => 'Nedavno število različnih avtorjev',
-'pageinfo-restriction' => 'Zaščita strani ({{lcfirst:$1}})',
 'pageinfo-magic-words' => '{{PLURAL:$1|Čarobna beseda|Čarobni besedi|Čarobne besede}} ($1)',
 'pageinfo-hidden-categories' => '{{PLURAL:$1|Skrita kategorija|Skriti kategoriji|Skrite kategorije}} ($1)',
 'pageinfo-templates' => '{{PLURAL:$1|Vključena predloga|Vključeni predlogi|Vključene predloge}} ($1)',
+'pageinfo-toolboxlink' => 'Podatki o strani',
 
 # Patrolling
 'markaspatrolleddiff' => 'Označite kot nadzorovano',
@@ -3061,7 +3061,7 @@ Z njenim zagonom lahko ogrozite vaš sistem.",
 'file-info' => 'Velikost datoteke: $1, MIME-vrsta: <code>$2</code>',
 'file-info-size' => '$1 × $2 točk, velikost datoteke: $3, vrsta MIME: $4',
 'file-info-size-pages' => '$1 × $2 točk, velikost datoteke: $3, vrsta MIME: $4, $5 {{PLURAL:$5|stran|strani}}',
-'file-nohires' => 'Slika višje ločljivosti ni na voljo.',
+'file-nohires' => 'Višja ločljivost slike ni na razpolago.',
 'svg-long-desc' => 'datoteka SVG, v izvirniku $1 × $2 slikovnih točk, velikost datoteke: $3',
 'svg-long-desc-animated' => 'animirana datoteka SVG, v izvirniku $1 × $2 slikovnih točk, velikost datoteke: $3',
 'show-big-image' => 'Slika v višji ločljivosti',
@@ -3599,6 +3599,7 @@ Potrditvena koda poteče $4.',
 # Scary transclusion
 'scarytranscludedisabled' => '[Prevključevanje med wikiji je onemogočeno]',
 'scarytranscludefailed' => '[Pridobivanje predloge za $1 ni uspelo]',
+'scarytranscludefailed-httpstatus' => '[Pridobivanje predloge za $1 ni uspelo: HTTP $2]',
 'scarytranscludetoolong' => '[Spletni naslov je predolg]',
 
 # Delete conflict
index 7be6f32..2ebb2d0 100644 (file)
@@ -363,7 +363,7 @@ De Sperre wurde durch [[User:$1|$1]] miet der Begrindung ''„$2“'' eigerichte
 # Login and logout pages
 'logouttext' => "'''Du best nun obgemeldet.'''
 
-Du koast {{SITENAME}} jitz anonym wetter nutzen, oder diech erneut under damm selba oder a'm andern Nutzernoama [[Special:UserLogin|oamelda]].
+Du koast {{SITENAME}} jitz anonym wetter nutzen, oder diech erneut under damm selba oder a'm andern Nutzernoama <span class='plainlinks'>[$1 oamelda]</span>.
 Beachte, doas einige Seyta noo oazeiga kinna, doas du oagemeldet best, sulange du ne denn Browsercache gelaart host.",
 'welcomecreation' => '== Willkumma, $1! ==
 
index 0e6a486..b590c8e 100644 (file)
@@ -366,7 +366,7 @@ Sababta neh waxaa waaye "\'\'$2\'\'".',
 # Login and logout pages
 'logouttext' => "'''Hada waad ka baxday.'''
 
-Waad sii isticmaali kartaa {{SITENAME}} adoona lagu aqoon, ama [[Special:UserLogin|gudaha gal]] adiga oo isticmaalaya magacaagii hore ama mid ka duwan. OGEYSIIS waxaa lagayabaa bogyaasha qaarkood in ay yiraahdaan wali gudaha ayaad ku jirtaa, ilaa inta aad ka nadiifineesid browsahaaga Internetka.",
+Waad sii isticmaali kartaa {{SITENAME}} adoona lagu aqoon, ama <span class='plainlinks'>[$1 gudaha gal]</span> adiga oo isticmaalaya magacaagii hore ama mid ka duwan. OGEYSIIS waxaa lagayabaa bogyaasha qaarkood in ay yiraahdaan wali gudaha ayaad ku jirtaa, ilaa inta aad ka nadiifineesid browsahaaga Internetka.",
 'welcomecreation' => "== Soo dhawoow, $1! ==
 Akoon kaada  waa la sameeyay.
 Ha' hilmaamin in aad wax ka bedesho [[Special:Preferences|{{SITENAME}} dooqyadaada]].",
index 1e57a4c..56d5b82 100644 (file)
@@ -626,7 +626,7 @@ Administratori i cili e mbylli atë e dha këtë shpjegim: "$3".',
 # Login and logout pages
 'logouttext' => "'''Ju keni dalë jashtë.''' 
 
- Ju mund të vazhdoni të përdorni {{SITENAME}} në mënyrë anonime, ose mund të [[Special:UserLogin|identifikoheni përsëri]] si përdoruesi i mëparshëm ose si një përdorues tjetër. 
+ Ju mund të vazhdoni të përdorni {{SITENAME}} në mënyrë anonime, ose mund të <span class='plainlinks'>[$1 identifikoheni përsëri]</span> si përdoruesi i mëparshëm ose si një përdorues tjetër. 
  Kini parasysh që disa faqe mund të shfaqen sikur të ishit i identifikuar derisa të fshini ''cache''-in e shfletuesit tuaj.",
 'welcomecreation' => '== Mirësevini, $1! == 
  Llogaria juaj është krijuar. 
index 35e75a1..873c7dd 100644 (file)
@@ -813,7 +813,7 @@ $2',
 # Login and logout pages
 'logouttext' => "'''Одјављени сте.'''
 
-Можете да наставите с коришћењем овог викија као гост, или се [[Special:UserLogin|поново пријавите]] као други корисник.
+Можете да наставите с коришћењем овог викија као гост, или се <span class='plainlinks'>[$1 поново пријавите]</span> као други корисник.
 Имајте на уму да неке странице могу наставити да се приказују као да сте још пријављени, све док не очистите привремену меморију свог прегледача.",
 'welcomecreation' => '== Добро дошли, $1! ==
 
@@ -3287,7 +3287,6 @@ $1',
 'pageinfo-authors' => 'Број засебних аутора',
 'pageinfo-recent-edits' => 'Број скорашњих измена (у последњих $1)',
 'pageinfo-recent-authors' => 'Број скорашњих засебних аутора',
-'pageinfo-restriction' => 'Заштита странице ({{lcfirst:$1}})',
 'pageinfo-magic-words' => '{{PLURAL:$1|Магична реч|Магичне речи}} ($1)',
 'pageinfo-hidden-categories' => '{{PLURAL:$1|Сакривена категорија|Сакривене категорије}} ($1)',
 'pageinfo-templates' => '{{PLURAL:$1|Укључени шаблон|Укључени шаблони}} ($1)',
index d75d842..d11560a 100644 (file)
@@ -722,7 +722,7 @@ Administrator koji ju je zaključao ponudio je sledeće objašnjenje: „$3“.'
 # Login and logout pages
 'logouttext' => "'''Odjavljeni ste.'''
 
-Možete da nastavite s korišćenjem ovog vikija kao gost, ili se [[Special:UserLogin|ponovo prijavite]] kao drugi korisnik.
+Možete da nastavite s korišćenjem ovog vikija kao gost, ili se <span class='plainlinks'>[$1 ponovo prijavite]</span> kao drugi korisnik.
 Imajte na umu da neke stranice mogu nastaviti da se prikazuju kao da ste još prijavljeni, sve dok ne očistite privremenu memoriju svog pregledača.",
 'welcomecreation' => '== Dobro došli, $1! ==
 
@@ -3196,7 +3196,6 @@ Ovo je verovatno izazvano vezom do spoljašnjeg sajta koji se nalazi na crnoj li
 'pageinfo-authors' => 'Broj zasebnih autora',
 'pageinfo-recent-edits' => 'Broj skorašnjih izmena (u poslednjih $1)',
 'pageinfo-recent-authors' => 'Broj skorašnjih zasebnih autora',
-'pageinfo-restriction' => 'Zaštita stranice ({{lcfirst:$1}})',
 'pageinfo-magic-words' => '{{PLURAL:$1|Magična reč|Magične reči}} ($1)',
 'pageinfo-hidden-categories' => '{{PLURAL:$1|Sakrivena kategorija|Sakrivene kategorije}} ($1)',
 'pageinfo-templates' => '{{PLURAL:$1|Uključeni šablon|Uključeni šabloni}} ($1)',
index 199abb7..ad7a991 100644 (file)
@@ -443,7 +443,7 @@ Die Administrator, die dän Skrieuwtougriep speerde, roate foulgjenden Gruund an
 # Login and logout pages
 'logouttext' => "'''Du bäst nu oumälded.'''
 
-Du koast {{SITENAME}} nu anonym fääre benutsje, of die fonnäien unner dänsälge of n uur Benutsernoome wier [[Special:UserLogin|anmäldje]].
+Du koast {{SITENAME}} nu anonym fääre benutsje, of die fonnäien unner dänsälge of n uur Benutsernoome wier <span class='plainlinks'>[$1 anmäldje]</span>.
 Beoachtje, dät eenige Sieden noch anwiese konnen, dät du oumälded bäst, soloange du nit din Browsercache loosmoaked hääst.",
 'welcomecreation' => '== Wäilkuumen, $1 ==
 
index eae5327..f847941 100644 (file)
@@ -518,7 +518,7 @@ Pikeun alihbasa, mangga sumping ka [//translatewiki.net/wiki/Main_Page?setlang=e
 # Login and logout pages
 'logouttext' => "'''Anjeun ayeuna geus kaluar log.'''
 
-Anjeun bisa tetep migunakeun {{SITENAME}} bari anonim, atawa bisa [[Special:UserLogin|asup log deui]] salaku pamaké nu sarua atawa nu séjén deui.
+Anjeun bisa tetep migunakeun {{SITENAME}} bari anonim, atawa bisa <span class='plainlinks'>[$1 asup log deui]</span> salaku pamaké nu sarua atawa nu séjén deui.
 Mangkahadé, sababaraha kaca bakal tetep némbongkeun saolah-olah anjeun asup log kénéh nepi ka anjeun ngosongkeun ''cache'' panyungsi anjeun.",
 'welcomecreation' => '==Wilujeng sumping, $1!==
 Rekening anjeun geus dijieun.
index 7b9c494..dbdc979 100644 (file)
@@ -726,7 +726,7 @@ Den administratören som låste den gav denna anledning: "\'\'$3\'\'".',
 # Login and logout pages
 'logouttext' => "'''Du är nu utloggad.'''
 
-Du kan fortsätta att använda {{SITENAME}} anonymt, eller så kan du [[Special:UserLogin|logga in igen]] som samma eller som en annan användare.
+Du kan fortsätta att använda {{SITENAME}} anonymt, eller så kan du <span class='plainlinks'>[$1 logga in igen]</span> som samma eller som en annan användare.
 Observera att det, tills du tömmer din webbläsares cache, på vissa sidor kan se ut som att du fortfarande är inloggad.",
 'welcomecreation' => '== Välkommen, $1! ==
 Ditt konto har skapats.
@@ -3114,7 +3114,7 @@ Detta orsakades troligen av en länk till en svartlistad webbplats.',
 'pageinfo-default-sort' => 'Standardsorteringsnyckel',
 'pageinfo-length' => 'Sidlängd (i byte)',
 'pageinfo-article-id' => 'Sid-ID',
-'pageinfo-robot-policy' => 'Sökmotorns status',
+'pageinfo-robot-policy' => 'Sökmotordirektiv',
 'pageinfo-robot-index' => 'Indexerbar',
 'pageinfo-robot-noindex' => 'Inte indexerbar',
 'pageinfo-views' => 'Antal visningar',
@@ -3130,10 +3130,10 @@ Detta orsakades troligen av en länk till en svartlistad webbplats.',
 'pageinfo-authors' => 'Totalt antal olika författare',
 'pageinfo-recent-edits' => 'Antal nyliga redigeringar (inom de senaste $1)',
 'pageinfo-recent-authors' => 'Antal nyliga olika författare',
-'pageinfo-restriction' => 'Sidskydd ({{lcfirst:$1}})',
 'pageinfo-magic-words' => '{{PLURAL:$1|Magiskt|Magiska}} ord ($1)',
 'pageinfo-hidden-categories' => '{{PLURAL:$1|Dold kategori|Dolda kategorier}} ($1)',
 'pageinfo-templates' => '{{PLURAL:$1|Inkluderad mall|Inkluderade mallar}} ($1)',
+'pageinfo-toolboxlink' => 'Sidinformation',
 
 # Skin names
 'skinname-standard' => 'Standard',
index b64687e..df33d64 100644 (file)
@@ -527,7 +527,7 @@ Sababu zilizotolewa ni "\'\'$2\'\'".',
 # Login and logout pages
 'logouttext' => "'''Umetoka kwenye akaunti yako.'''
 
-Unaweza kuendelea kutumia {{SITENAME}} bila kutaja jina lako, au unaweza [[Special:UserLogin|kuingia tena]] kwenye akaunti yako. Kumbuka kwamba kurasa nyingine zitaendelea kuonekana kana kwamba bado hujatoka kwenye akaunti yako, hadi utakaposafisha kache ya kivinjari.",
+Unaweza kuendelea kutumia {{SITENAME}} bila kutaja jina lako, au unaweza <span class='plainlinks'>[$1 kuingia tena]</span> kwenye akaunti yako. Kumbuka kwamba kurasa nyingine zitaendelea kuonekana kana kwamba bado hujatoka kwenye akaunti yako, hadi utakaposafisha kache ya kivinjari.",
 'welcomecreation' => '== Karibu, $1! ==
 Ushafunguliwa akaunti yako tayari.
 Usisahau kubadilisha mapendekezo yako ya [[Special:Preferences|{{SITENAME}}]].',
index f7b0f98..0fa1792 100644 (file)
@@ -451,7 +451,7 @@ Administrator kery zawarł wćepał kůmyntorz: "$3".',
 # Login and logout pages
 'logouttext' => "'''Terozki ježeś wylůgowany'''.
 
-Možeš dali sam sprowjać zajty we {{SITENAME}} kej ńyzalůgowany užytkowńik, abo [[Special:UserLogin|zalůgować śe nazod]] kej tyn som abo inkšy užytkowńik.
+Možeš dali sam sprowjać zajty we {{SITENAME}} kej ńyzalůgowany užytkowńik, abo <span class='plainlinks'>[$1 zalůgować śe nazod]</span> kej tyn som abo inkšy užytkowńik.
 Dej pozůr, co na ńykerych zajtach přeglůndarka može dali pokozywać co ježeś zalůgowany, a bydźe tak aže uodśwjyžyš jeij cache.",
 'welcomecreation' => '== Witej, $1! ==
 Uotwarli my sam lo Ćebje kůnto.
index 2b0c9d8..3c1efe7 100644 (file)
@@ -479,7 +479,7 @@ MySQL returned error "$3: $4".',
 # Login and logout pages
 'logouttext' => "'''நீங்கள் இப்பொழுது விடுபதிகையில் உள்ளீர்கள்.'''
 
-நீங்கள் தொடர்ந்து {{SITENAME}} தளத்தை அனானியாகப் பயன்படுத்தலாம், அல்லது அதே பயனராகவோ வேறு பயனராகவோ [[Special:UserLogin|மீண்டும் புகுபதிகை]] செய்யலாம். உங்கள் உலாவியின் இடைமாற்று நீக்கப்படும் வரை சில பக்கங்கள் தொடர்ந்தும் புகுபதிகையில் உள்ளது போன்றே காட்சி தரும் என்பதைக் கவனிக்கவும்.",
+நீங்கள் தொடர்ந்து {{SITENAME}} தளத்தை அனானியாகப் பயன்படுத்தலாம், அல்லது அதே பயனராகவோ வேறு பயனராகவோ <span class='plainlinks'>[$1 மீண்டும் புகுபதிகை]</span> செய்யலாம். உங்கள் உலாவியின் இடைமாற்று நீக்கப்படும் வரை சில பக்கங்கள் தொடர்ந்தும் புகுபதிகையில் உள்ளது போன்றே காட்சி தரும் என்பதைக் கவனிக்கவும்.",
 'welcomecreation' => '==நல்வரவு, $1!==
 உங்களுக்கான பயனர் கணக்கு உருவாக்கப்பட்டுள்ளது. உங்களுக்கேற்றவாறு [[Special:Preferences|{{SITENAME}} விருப்பத்தேர்வுகளை]] மாற்றிக் கொள்ள மறவாதீர்கள்.',
 'yourname' => 'பயனர் பெயர்:',
index 42b45db..6cf6da3 100644 (file)
@@ -542,7 +542,7 @@ $2',
 # Login and logout pages
 'logouttext' => "'''ఇప్పుడు మీరు నిష్క్రమించారు.'''
 
-మీరు {{SITENAME}}ని అజ్ఞాతంగా వాడుతూండొచ్చు, లేదా ఇదే వాడుకరిగా కానీ లేదా వేరే వాడుకరిగా కానీ [[Special:UserLogin|మళ్ళీ ప్రవేశించవచ్చు]].
+మీరు {{SITENAME}}ని అజ్ఞాతంగా వాడుతూండొచ్చు, లేదా ఇదే వాడుకరిగా కానీ లేదా వేరే వాడుకరిగా కానీ <span class='plainlinks'>[$1 మళ్ళీ ప్రవేశించవచ్చు]</span>.
 అయితే, మీ విహారిణిలోని కోశాన్ని శుభ్రపరిచే వరకు కొన్ని పేజీలు మీరింకా ప్రవేశించి ఉన్నట్లుగానే చూపించవచ్చని గమనించండి.",
 'welcomecreation' => '== స్వాగతం, $1! ==
 
index 0d18a83..827dd6d 100644 (file)
@@ -444,7 +444,7 @@ $1',
 # Login and logout pages
 'logouttext' => "'''Акнун аз систем хориҷ шудаед.'''
 
-Шумо метавонед гумном аз {{SITENAME}} истифодабариро идома диҳед, ё метавонед бо ҳамин номи корбариатон ва ё номи корбарии дигаре [[Special:UserLogin|боз вуруд кунед]].
+Шумо метавонед гумном аз {{SITENAME}} истифодабариро идома диҳед, ё метавонед бо ҳамин номи корбариатон ва ё номи корбарии дигаре <span class='plainlinks'>[$1 боз вуруд кунед]</span>.
 Тавваҷӯҳ кунед, ки баъзе аз саҳифаҳо қаблан чи тавре намоиш шуда будан ҳамин тавр намоиш дода мешаванд, то даме ки шумо ҳофизаи мурургаратонро пок кунед.",
 'welcomecreation' => '== Хуш омадед, $1! ==
 
index c2228ba..5cf9a02 100644 (file)
@@ -379,7 +379,7 @@ Daleli zikrşuda az in qaror ast ''$2''.",
 # Login and logout pages
 'logouttext' => "'''Aknun az sistem xoriç şudaed.'''
 
-Şumo metavoned gumnom az {{SITENAME}} istifodabariro idoma dihed, jo metavoned bo hamin nomi korbariaton va jo nomi korbariji digare [[Special:UserLogin|boz vurud kuned]].
+Şumo metavoned gumnom az {{SITENAME}} istifodabariro idoma dihed, jo metavoned bo hamin nomi korbariaton va jo nomi korbariji digare <span class='plainlinks'>[$1 boz vurud kuned]</span>.
 Tavvaçūh kuned, ki ba'ze az sahifaho qablan ci tavre namoiş şuda budan hamin tavr namoiş doda meşavand, to dame ki şumo hofizai mururgaratonro pok kuned.",
 'welcomecreation' => '== Xuş omaded, $1! ==
 
index 5025925..a5e7809 100644 (file)
@@ -574,7 +574,7 @@ $1',
 # Login and logout pages
 'logouttext' => "'''ขณะนี้คุณได้ล็อกเอาต์ออกจากระบบ'''
 
-คุณสามารถใช้งาน {{SITENAME}} ได้ต่อในฐานะผู้ใช้นิรนาม หรือคุณสามารถ[[Special:UserLogin|ล็อกอินกลับเข้าไป]]ด้วยชื่อผู้ใช้เดิมหรือชื่อผู้ใช้อื่นๆ
+คุณสามารถใช้งาน {{SITENAME}} ได้ต่อในฐานะผู้ใช้นิรนาม หรือคุณสามารถ<span class='plainlinks'>[$1 ล็อกอินกลับเข้าไป]</span>ด้วยชื่อผู้ใช้เดิมหรือชื่อผู้ใช้อื่นๆ
 อย่างไรก็ตามอาจจะมีบางหน้าที่ยังแสดงข้อความว่าคุณกำลังล็อกอินอยู่ จนกว่าคุณจะล้างแคชออกจากเว็บเบราว์เซอร์",
 'welcomecreation' => '== ยินดีต้อนรับ $1! ==
 
index 64b77eb..8a31a0b 100644 (file)
@@ -422,7 +422,7 @@ Görkezilen sebäp: ''$2''.",
 # Login and logout pages
 'logouttext' => "'''Sessiýany ýapdyňyz.'''
 
-Indi anonim ýagdaýda {{SITENAME}} saýtyny ulanyp bilersiňiz, ýa-da şol bir ýa-da başga bir at bilen [[Special:UserLogin|sessiýany ýaňadan]] açyp bilersiňiz.
+Indi anonim ýagdaýda {{SITENAME}} saýtyny ulanyp bilersiňiz, ýa-da şol bir ýa-da başga bir at bilen <span class='plainlinks'>[$1 sessiýany ýaňadan]</span> açyp bilersiňiz.
 Web brauzeriňiziň keşini arassalaýançaňyz käbir sahypalar sessiýaňyzyň açyk wagtkysy ýaly görünip biler.",
 'welcomecreation' => '== Hoş geldiňiz, $1! ==
 
index b370efe..0d61625 100644 (file)
@@ -567,7 +567,7 @@ Ang tagapangasiwang nagkandado nito ay nag-alok ng ganitong paliwanag: "$3".',
 # Login and logout pages
 'logouttext' => "'''Nakaalis ka na sa pagkakalagda.'''
 
-Maaari kang tumuloy sa paggamit ng {{SITENAME}} nang hindi nakikilala (anonimo), o maaaring kang [[Special:UserLogin|lumagda/tumala muli]] bilang kapareho o ibang tagagamit.
+Maaari kang tumuloy sa paggamit ng {{SITENAME}} nang hindi nakikilala (anonimo), o maaaring kang <span class='plainlinks'>[$1 lumagda/tumala muli]</span> bilang kapareho o ibang tagagamit.
 Tandaan na may ilang pahinang maaaring magpatuloy na nagpapakitang parang nakalagda ka pa rin, hanggang sa linisin mo ang iyong baunang pambasa-basa (''browser cache'').",
 'welcomecreation' => '== Maligayang pagdating, $1! ==
 Nilikha na ang iyong kuwenta.
@@ -3011,7 +3011,6 @@ Maaaring dahil ito sa isang kawing sa isang nakatalang hinarang dahil di-kinaisn
 'pageinfo-authors' => 'Kabuuang bilang ng magkakabukod na mga may-akda',
 'pageinfo-recent-edits' => 'Kamakailang bilang ng mga pamamatnugot (sa loob ng huling $1)',
 'pageinfo-recent-authors' => 'Kamakailang bilang ng magkakabukod na mga may-akda',
-'pageinfo-restriction' => 'Pruteksiyon ng pahina ({{lcfirst:$1}})',
 'pageinfo-magic-words' => '{{PLURAL:$1|Salita|Mga salita}}ng mahiwaga ($1)',
 'pageinfo-hidden-categories' => 'Nakatagong {{PLURAL:$1|kategorya|mga kategorya}} ($1)',
 'pageinfo-templates' => '{{PLURAL:$1|Suleras|Mga suleras}} ($1) na nasa transklusyon (kasama sa maraming mga lugar)',
index e68c60b..48da335 100644 (file)
@@ -946,6 +946,7 @@ $messages = array(
 
 # Info page
 'pageinfo-header-edits' => 'Дәгиш кардә быә чијон тарых',
+'pageinfo-redirects-value' => '$1',
 
 # Browsing diffs
 'previousdiff' => '← Навынәни дәгиши',
index da6c103..ebcf4ae 100644 (file)
@@ -723,7 +723,7 @@ Verilen sebep: ''$2''.",
 # Login and logout pages
 'logouttext' => "'''Oturumu kapattınız.'''
 
-Şimdi anonim olarak {{SITENAME}} sitesini kullanmaya devam edebilirsiniz ya da aynı kullanıcı adıyla ya da ister başka bir kullanıcı adıyla [[Special:UserLogin|yeniden oturum açabilirsiniz]].
+Şimdi anonim olarak {{SITENAME}} sitesini kullanmaya devam edebilirsiniz ya da aynı kullanıcı adıyla ya da ister başka bir kullanıcı adıyla <span class='plainlinks'>[$1 yeniden oturum açabilirsiniz]</span>.
 Tarayıcınızın önbelleğini temizleyene kadar bazı sayfalar sanki hâlâ oturumunuz açıkmış gibi görünebilir.",
 'welcomecreation' => '== Hoş geldin, $1! ==
 
@@ -2975,6 +2975,7 @@ Geçici dosya kayıp.',
 'pageinfo-header-edits' => 'Değişiklikler',
 'pageinfo-views' => 'Görüntülenme sayısı',
 'pageinfo-watchers' => 'İzleyen sayısı',
+'pageinfo-redirects-value' => '$1',
 'pageinfo-edits' => 'Değişiklik sayısı',
 
 # Skin names
index 726ecba..45cecda 100644 (file)
@@ -372,7 +372,7 @@ Hikwlaho ka xivangelo xa "\'\'$2\'\'".',
 # Login and logout pages
 'logouttext' => "'''Uhumile eka wiki leyi.'''
 
-Ungaya emahlweni utirhisa {{SITENAME}} handle ko tipaluxa, kumbe unga [[Special:UserLogin|pfula unghena nakambe]] tani hi mutirhisa un'wana kumbe kumbe hivuxokoxoko bya wena.
+Ungaya emahlweni utirhisa {{SITENAME}} handle ko tipaluxa, kumbe unga <span class='plainlinks'>[$1 pfula unghena nakambe]</span> tani hi mutirhisa un'wana kumbe kumbe hivuxokoxoko bya wena.
 Tsundzuka leswaku matluka man'wana mangaha komba onge upfule unghena eka wiki, loko ungasi sula tluka rakhompuyuta leri tsundzukaka matluka lawa uma vhakeleke.",
 'welcomecreation' => '== Hoyohoyo, eka Wena $1 ! ==
 Akhawunti yawena yitumbuluxiwile.
index 1f9fc5c..ea2fc37 100644 (file)
@@ -606,7 +606,7 @@ $2',
 # Login and logout pages
 'logouttext' => "'''Сез хисап язмагыздан чыктыгыз.'''
 
-Сез {{SITENAME}} проектында аноним рәвештә кала яисә шул ук яки башка исем белән яңадан [[Special:UserLogin|керә]] аласыз.
+Сез {{SITENAME}} проектында аноним рәвештә кала яисә шул ук яки башка исем белән яңадан <span class='plainlinks'>[$1 керә]</span> аласыз.
 Кайбер битләр Сез кергән кебек күрсәтелергә мөмкин. Моны бетерү өчен браузер кэшын чистартыгыз.",
 'welcomecreation' => '== Рәхим итегез, $1! ==
 Сез теркәлдегез.
index d3262c4..57755f7 100644 (file)
@@ -477,7 +477,7 @@ Ul kürsätkän säbäp: ''$2''.",
 # Login and logout pages
 'logouttext' => "'''Sez xisap yazmağızdan çıqtığız.'''
 
-Sez {{SITENAME}} proyektında anonim räweştä qala yäisä şul uq yäki başqa isem belän yañadan [[Special:UserLogin|kerä]] alasız.
+Sez {{SITENAME}} proyektında anonim räweştä qala yäisä şul uq yäki başqa isem belän yañadan <span class='plainlinks'>[$1 kerä]</span> alasız.
 Qayber bitlär Sez kergän kebek kürsätelergä mömkin. Monı beterü öçen brauzer keşın çistartığız.",
 'welcomecreation' => '== Räxim itegez, $1! ==
 Sez terkäldegez.
index 32db00b..44df07a 100644 (file)
@@ -468,7 +468,7 @@ $2',
 # Login and logout pages
 'logouttext' => "'''ھازىر تىزىمدىن چىقتىڭىز.'''
 
-سىز نامسىز ھالەتتە {{SITENAME}} نى ئىشلىتەلەيسىز ياكى ئوخشاش ۋە ياكى ئوخشاش بولمىغان ئىشلەتكۈچى سالاھىيىتىدە [[Special:UserLogin|تىزىمغا كىر]]ەلەيسىز.
+سىز نامسىز ھالەتتە {{SITENAME}} نى ئىشلىتەلەيسىز ياكى ئوخشاش ۋە ياكى ئوخشاش بولمىغان ئىشلەتكۈچى سالاھىيىتىدە <span class='plainlinks'>[$1 تىزىمغا كىر]</span>ەلەيسىز.
 دىققەت، بەزى بەتلەر توركۆرگۈنىڭ غەملىكى تازىلانمىغۇچە يەنىلا سىزنى تىزىمغا كىرگەن ھالەتتە كۆرسىتىشى مۇمكىن.",
 'welcomecreation' => '==  $1! خۇش كەپسىز ==
 
index d42b17e..e02c793 100644 (file)
@@ -763,7 +763,7 @@ $1',
 # Login and logout pages
 'logouttext' => "'''Тепер ви працюєте в тому ж режимі, який був до вашого входу до системи.'''
 
-Ви можете продовжувати використовувати {{grammar:accusative|{{SITENAME}}}} анонімно або знову [[Special:UserLogin|ввійти до системи]] як той самий або інший користувач. Деякі сторінки можуть відображатися, ніби ви ще представлені системі під іменем, щоб уникнути цього, оновіть кеш браузера.",
+Ви можете продовжувати використовувати {{grammar:accusative|{{SITENAME}}}} анонімно або знову <span class='plainlinks'>[$1 ввійти до системи]</span> як той самий або інший користувач. Деякі сторінки можуть відображатися, ніби ви ще представлені системі під іменем, щоб уникнути цього, оновіть кеш браузера.",
 'welcomecreation' => '== Вітаємо вас, $1! ==
 Ваш обліковий запис створено.
 Не забудьте змінити свої [[Special:Preferences|налаштування для сайту]].',
index c71d004..0dbd2cd 100644 (file)
@@ -418,7 +418,7 @@ Warning: Page may not contain recent updates.',
 # Login and logout pages
 'logouttext' => "'''اب آپ خارج ہوچکے ہیں'''
 
-آپ گمنام طور پر {{SITENAME}}  کا استعمال جاری رکھ سکتے ہیں، یا دوبارہ اسی نام یا مختلف نام سے [[Special:UserLogin|دوبارہ داخلِ نوشتہ]] بھی ہو سکتے ہیں۔  یہ یاد آوری کرلیجیۓ کہ کچھ صفحات ایسے نظر آتے رہیں گے کہ جیسے ابھی آپ خارج نہیں ہوئے ، جب تک آپ اپنے متصفح کا ابطن صاف نہ کردیں۔",
+آپ گمنام طور پر {{SITENAME}}  کا استعمال جاری رکھ سکتے ہیں، یا دوبارہ اسی نام یا مختلف نام سے <span class='plainlinks'>[$1 دوبارہ داخلِ نوشتہ]</span> بھی ہو سکتے ہیں۔  یہ یاد آوری کرلیجیۓ کہ کچھ صفحات ایسے نظر آتے رہیں گے کہ جیسے ابھی آپ خارج نہیں ہوئے ، جب تک آپ اپنے متصفح کا ابطن صاف نہ کردیں۔",
 'welcomecreation' => '== خوش آمدید، $1 ! ==
 آپ کا کھاتہ بنا دیا گیا ہے۔ اپنی [[Special:Preferences|{{SITENAME}} ترجیحات]] مرتب کرنا مت بھولئے گا.',
 'yourname' => 'اسمِ رکنیت',
index 434fb7d..c54ceea 100644 (file)
@@ -61,6 +61,7 @@ $messages = array(
 'tog-newpageshidepatrolled' => "Yangi sahifalar ro'yxatida patrullangan sahifalarni yashirish",
 'tog-numberheadings' => 'Sarlavhalarni avtomatik tarzda raqamlash',
 'tog-showtoolbar' => "Tahrirlash vaqtida yuqorigi unsurlar darchasini ko'rsatish (JavaScript)",
+'tog-editsection' => "[tahrir] havolasini har bir seksiyada ko'rsatish",
 'tog-showtoc' => "Mundarijani ko'rsatish (3 ta sarlavhadan ko'p bo'lgan sahifalar uchun)",
 'tog-rememberpassword' => 'Hisob ma’lumotlarini ushbu kompyuterda eslab qolish (eng ko‘pi bilan $1 {{PLURAL:$1|kunga|kunga}})',
 'tog-watchcreations' => 'Men yaratgan sahifalarni va yuklagan fayllarni kuzatuv roʻyxatimga qoʻsh',
@@ -352,6 +353,9 @@ $messages = array(
 'actionthrottled' => "Tezlik bo'yicha cheklov",
 'protectedpagetext' => 'Bu sahifa tahrirlashdan saqlanish maqsadida qulflangan.',
 'viewsourcetext' => "Siz bu sahifaning manbasini ko'rishingiz va uni nusxasini olishingiz mumkin:",
+'editinginterface' => "'''Diqqat:''' Siz dasturiy ta'minot interfeysi matni mavjud bo'lgan sahifani tahrirlamoqdasiz.
+Uning o'zgartirilishi ushbu vikidagi boshqa foydalanuvchilar uchun ham interfeysning tashqi ko'rinishiga ta'sir qiladi.
+Ushbu xabar tarjimasini qo'shish yoki o'zgartirish uchun, iltimos, MediaWikining [//translatewiki.net/ translatewiki.net] lokalizatsiya saytidan foydalaning.",
 'namespaceprotected' => "Sizda '''$1''' nomfazosi sahifalarini tahrirlash huquqi yoʻq",
 'customcssprotected' => 'Sizda uchbu CSS sahifani tahrirlash huquqi yoʻq, chunki bu yerda boshqa foydalanuvchining shaxsiy moslamalari saqlanadi.',
 'customjsprotected' => 'Sizda uchbu JavaScript sahifani tahrirlash huquqi yoʻq, chunki bu yerda boshqa foydalanuvchining shaxsiy moslamalari saqlanadi.',
@@ -409,7 +413,7 @@ Xatosiz yozishga urinib koʻring.',
 'mailmypassword' => "Elektron pochta orqali yangi maxfiy so'zni jo'natish",
 'passwordremindertitle' => "{{SITENAME}} uchun vaqtinchalik yangi maxfiy so'z",
 'emailauthenticated' => 'Sizning e-mail manzilingiz $2, $3 da tasdiqlangan.',
-'emailconfirmlink' => 'Sizning elektron pochtangizni tasdiqlash',
+'emailconfirmlink' => 'Sizning elektron pochta manzilingizni tasdiqlash',
 'emaildisabled' => 'Bu sayt elektron pochta xatlarini yubora olmaydi.',
 'accountcreated' => 'Hisob yozuvi yaratildi',
 'login-abort-generic' => 'Tizimga kirishga mufavvaqiyatsiz urinish',
@@ -498,8 +502,11 @@ Sizning hozirgi IP manzilingiz - $3, chetlashtirish raqamingiz - #$5. Arizaga bu
 Sahifani yaratish uchun quyida matn kiritishingiz mumkin (qo'shimcha axborot uchun [[{{MediaWiki:Helppage}}|yordam sahifasini]] ko'ring).
 Agar bu sahifaga xatolik sabab kelgan bo'lsangiz brauzeringizning '''orqaga''' tugmasini bosing.",
 'noarticletext' => 'Bu sahifada hozircha hech qanday matn yoʻq. Siz bu sarlavhani boshqa sahifalardan [[Special:Search/{{PAGENAME}}|qidirishingiz]], <span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} tegishli loglarga qarashingiz] yoki bu sahifani [{{fullurl:{{FULLPAGENAME}}|action=edit}} tahrirlashingiz]</span> mumkin.',
-'clearyourcache' => "'''Etibor bering:''' O'zgartirishlaringiz ko'rish uchun, yangi moslamalaringizning saqlashdan keyin, brauser keshini tozalash kerak:<br />
-'''Mozilla / Firefox:''' ''Ctrl+Shift+R'', '''IE:''' ''Ctrl+F5'', '''Safari:''' ''Cmd+Shift+R'', '''Konqueror:''' ''F5'', '''Opera:''' ''Tools → Preferences'' orqali keshni tozalang.",
+'clearyourcache' => "'''Eslatma.''' Saqlaganingizdan so'ng o'zgarishlarni ko'rish uchun siz o'z brauzeringiz keshini tozalashingizga to'gri kelishi mumkin.
+* '''Firefox / Safari:''' ''Shift'' tugmasini bosgan holda, ''Yangilash'' unsurlar darchasini bosing, yoki ''Ctrl-F5'' yoki ''Ctrl-R'' (Macda ''⌘-R'') ni bosing
+* '''Google Chrome:''' ''Ctrl-Shift-R'' (Macda ''⌘-Shift-R'') ni bosing
+* '''Internet Explorer:''' ''Ctrl''ni bosgan holda, ''Yangilash''ni bosing, yoki ''Ctrl-F5''ni bosing
+* '''Opera:''' ''Asboblar → Moslamalar'' menyusidan keshni tozalashni tanlang",
 'updated' => '(Yangilandi)',
 'note' => "'''Izoh:'''",
 'previewnote' => "'''Bu shunchaki ko‘rib chiqish. O‘zgartirishlar hali saqlangani yo‘q!'''",
@@ -642,10 +649,10 @@ Bu yerda: (joriy) = hozirgi koʻrinish bilan farq,
 'searchhelp-url' => 'Help:Mundarija',
 'searchmenu-prefix' => "[[Special:PrefixIndex/$1|Ushbu prefiks mavjud bo'lgan sahifalarni ko'rsatish]]",
 'searchprofile-articles' => 'Asosiy sahifalar',
-'searchprofile-project' => 'Yordam va Loyiha sahifalari',
+'searchprofile-project' => 'Yordam va loyiha sahifalari',
 'searchprofile-images' => 'Multimediya',
 'searchprofile-everything' => 'Har yerda',
-'searchprofile-advanced' => 'Kengaytirilgan',
+'searchprofile-advanced' => "Qo'shimcha",
 'searchprofile-articles-tooltip' => '$1da qidirish',
 'searchprofile-project-tooltip' => '$1da qidirish',
 'searchprofile-images-tooltip' => 'Fayllarni qidir',
@@ -672,7 +679,7 @@ Bu yerda: (joriy) = hozirgi koʻrinish bilan farq,
 'powersearch' => 'Qidiruv',
 'powersearch-legend' => 'Kengaytirilgan qidiruv',
 'powersearch-ns' => 'Bu nom-fazolarda izla:',
-'powersearch-redir' => 'Yoʻnaltirishlarni koʻrsat',
+'powersearch-redir' => 'Qayta yoʻnaltirishlarni koʻrsatish',
 'powersearch-field' => 'Qidiruv',
 'powersearch-togglelabel' => 'Belgilash:',
 'powersearch-toggleall' => 'Hammasini',
@@ -925,7 +932,7 @@ Agar siz uni ko'rsatsangiz, undan sahifa tahriri kim tomonidan kiritilganligini
 'imagelinks' => 'Fayllarga ishoratlar',
 'linkstoimage' => 'Bu faylga quyidagi {{PLURAL:$1|sahifa|$1 sahifalar}} bogʻlangan:',
 'nolinkstoimage' => 'Bu faylga bogʻlangan sahifalar yoʻq.',
-'sharedupload' => 'This file is from $1 and may be used by other projects.',
+'sharedupload' => "Ushbu fayl $1dan va boshqa loyihalarda ham qo'llanilishi mumkin.",
 'sharedupload-desc-here' => 'Ushbu fayl $1dan boʻlib, boshqa loyihalarda ham ishlatilishi mumkin.
 Uning [$2 fayl tavsifi sahifasidan] olingan tavsifi quyida keltirilgan.',
 'uploadnewversion-linktext' => 'Bu faylning yangi versiyasini yuklash',
@@ -960,7 +967,7 @@ Uning [$2 fayl tavsifi sahifasidan] olingan tavsifi quyida keltirilgan.',
 'lonelypages' => 'Yetim sahifalar',
 'uncategorizedpages' => 'Turkumlashtirilmagan sahifalar',
 'uncategorizedcategories' => 'Turkumlashtirilmagan turkumlar',
-'uncategorizedimages' => 'Kategoriyasiz tasvirlar',
+'uncategorizedimages' => 'Turkumlashtirilmagan fayllar',
 'uncategorizedtemplates' => 'Turkumlashtirilmagan andozalar',
 'unusedcategories' => 'Ishlatilinmagan turkumlar',
 'unusedimages' => 'Ishlatilinmagan fayllar',
@@ -1031,6 +1038,7 @@ Also see [[Special:WantedCategories|wanted categories]].',
 
 # E-mail user
 'emailuser' => 'Bu foydalanuvchiga e-maktub',
+'noemailtitle' => 'Elektron pochta manzili mavjud emas',
 'noemailtext' => "Bu foydalanuvchi e-mail manzil ko'rsatgani yo'q.",
 'emailtarget' => 'Oluvchi ishtirokchining ismini kiriting',
 'emailusername' => 'Ishtirokchi nomi:',
@@ -1363,6 +1371,7 @@ Yaqinda sodir etilgan yoʻqotishlar uchun $2ni koʻring.',
 'pageinfo-article-id' => 'Sahifa identifikatori',
 'pageinfo-watchers' => 'Sahifa kuzatuvchilari soni',
 'pageinfo-edits' => 'Jami tahrirlar soni',
+'pageinfo-toolboxlink' => 'Sahifa haqida maʼlumot',
 
 # Skin names
 'skinname-standard' => 'Klassik',
@@ -1382,7 +1391,8 @@ Yaqinda sodir etilgan yoʻqotishlar uchun $2ni koʻring.',
 'nextdiff' => 'Keyingi tahrir →',
 
 # Media information
-'imagemaxsize' => 'Tasvir taʼrifi sahifasidagi tasvirning oʻlchami:',
+'imagemaxsize' => "Tasvir oʻlchamining chegarasi:<br />
+''(fayl taʼrifi sahifasi uchun)''",
 'thumbsize' => 'Tasvirning kichiklashtirilgan versiyasining oʻlchami:',
 'file-info-size' => '$1 × $2 piksel, fayl hajmi: $3, MIME tipi: $4',
 'file-nohires' => 'Bundan kattaroq tasvir yoʻq.',
index 33cceb8..1848d6f 100644 (file)
@@ -548,7 +548,7 @@ L\'aministradore che ło ga blocà ga fornìo sta spiegasion: "$3".',
 # Login and logout pages
 'logouttext' => "'''Te sì 'ndà fora da la to utensa.'''
 
-Te poli 'ndar vanti doparando {{SITENAME}} come utente anonimo o se nò [[Special:UserLogin|entrar da novo]], col stesso nome utente o uno difarente.
+Te poli 'ndar vanti doparando {{SITENAME}} come utente anonimo o se nò <span class='plainlinks'>[$1 entrar da novo]</span>, col stesso nome utente o uno difarente.
 Ocio che serte pagine podarìa èssar che ti 'e vedi come se te fussi 'ncora drento col to nome de prima, fin che no te neti la ''cache'' del to browser.",
 'welcomecreation' => '== Benvegnù, $1! ==
 
index 19cae5e..9a25185 100644 (file)
@@ -471,7 +471,7 @@ Sü om "\'\'$2\'\'".',
 # Login and logout pages
 'logouttext' => "'''Tö olet lähtnuded sistemaspäi.'''
 
-Sab jatkta rad {{SITENAME}}-saital anonimižikš, vai [[Special:UserLogin|kirjutagatoiš udes]] sil-žo vai toižel kävutajan nimel.
+Sab jatkta rad {{SITENAME}}-saital anonimižikš, vai <span class='plainlinks'>[$1 kirjutagatoiš udes]</span> sil-žo vai toižel kävutajan nimel.
 Otkat sil'mnägubale, miše erasid lehtpolid ozutaškatas mugažo, kut i edel teiden lähtendad sistemaspäi. Miše vajehtada niiden nägu, puhtastagat teiden kaclimen keš.",
 'welcomecreation' => '== Tulgat tervhen, $1! ==
 Teiden registracii om loptud.
index 8bc3afd..ae36f9c 100644 (file)
@@ -454,7 +454,7 @@ $messages = array(
 'vector-action-protect' => 'Khóa',
 'vector-action-undelete' => 'Phục hồi',
 'vector-action-unprotect' => 'Đổi mức khóa',
-'vector-simplesearch-preference' => 'Gợi ý tìm kiếm nâng cao (cần bề ngoài Vectơ)',
+'vector-simplesearch-preference' => 'Hộp tìm kiếm đơn giản (cần bề ngoài Vectơ)',
 'vector-view-create' => 'Tạo',
 'vector-view-edit' => 'Sửa',
 'vector-view-history' => 'Xem lịch sử',
@@ -701,7 +701,7 @@ Bảo quản viên khóa nó đưa lý do là: “$3”.',
 # Login and logout pages
 'logouttext' => "'''Bạn đã đăng xuất.'''
 
-Bạn có thể tiếp tục dùng {{SITENAME}} một cách vô danh, hoặc bạn có thể [[Special:UserLogin|đăng nhập lại]] dưới cùng tên người dùng này hoặc một tên người dùng khác. Xin lưu ý rằng một vài trang có thể vẫn hiển thị như khi bạn còn đăng nhập, cho đến khi bạn xóa vùng nhớ đệm (''cache'') của trình duyệt.",
+Bạn có thể tiếp tục dùng {{SITENAME}} một cách vô danh, hoặc bạn có thể <span class='plainlinks'>[$1 đăng nhập lại]</span> dưới cùng tên người dùng này hoặc một tên người dùng khác. Xin lưu ý rằng một vài trang có thể vẫn hiển thị như khi bạn còn đăng nhập, cho đến khi bạn xóa vùng nhớ đệm (''cache'') của trình duyệt.",
 'welcomecreation' => '== Chào mừng, $1! ==
 Tài khoản của bạn đã mở.
 Đừng quên thay đổi [[Special:Preferences|tùy chọn cá nhân của bạn tại {{SITENAME}}]].',
@@ -3100,10 +3100,10 @@ Lưu nó vào máy tính của bạn rồi tải nó lên đây.',
 'pageinfo-authors' => 'Tổng số tác giả riêng',
 'pageinfo-recent-edits' => 'Số lần sửa đổi gần đây (trong $1 qua)',
 'pageinfo-recent-authors' => 'Số người dùng sửa đổi gần đây',
-'pageinfo-restriction' => 'Mức khóa trang ({{lcfirst:$1}})',
 'pageinfo-magic-words' => 'Từ thần chú ($1)',
 'pageinfo-hidden-categories' => 'Thể loại ẩn ($1)',
 'pageinfo-templates' => 'Bản mẫu được nhúng ($1)',
+'pageinfo-toolboxlink' => 'Thông tin trang',
 
 # Skin names
 'skinname-standard' => 'Cổ điển',
@@ -3737,7 +3737,8 @@ Mã xác nhận này sẽ hết hạn vào $4.',
 
 # Scary transclusion
 'scarytranscludedisabled' => '[Nhúng giữa các wiki bị tắt]',
-'scarytranscludefailed' => '[Truy xuất bản mẫu cho $1 thất bại]',
+'scarytranscludefailed' => '[Truy xuất bản mẫu $1 bị thất bại]',
+'scarytranscludefailed-httpstatus' => '[Truy xuất bản mẫu $1 bị thất bại: HTTP $2]',
 'scarytranscludetoolong' => '[Địa chỉ URL quá dài]',
 
 # Delete conflict
index 21d5509..1cb1e80 100644 (file)
@@ -474,7 +474,7 @@ Kod binon: ''$2''.",
 # Login and logout pages
 'logouttext' => "'''Esenunädol oli.'''
 
-Kanol laigebön {{SITENAME}} nennemiko, u kanol [[Special:UserLogin|nunädön oli dönu]] me gebananem ot u gebenanem votik.
+Kanol laigebön {{SITENAME}} nennemiko, u kanol <span class='plainlinks'>[$1 nunädön oli dönu]</span> me gebananem ot u gebenanem votik.
 Küpälolös, das pads anik ba nog pojenons äsva no esenunädol oli, jüs uklinükol memi no laidüpik bevüresodanaföma olik.",
 'welcomecreation' => '== Benokömö, o $1! ==
 Kal olik pejafon.
index 28a23e0..8eee3d9 100644 (file)
@@ -423,7 +423,7 @@ $2',
 # Login and logout pages
 'logouttext' => "'''Olõt nime alt vällä lännüq.'''
 
-Võit {{SITENAME}}t ilma nimeldä edesi toimõndaq vai [[Special:UserLogin|vahtsõst sama vai tõõsõ nimega sisse minnäq]].
+Võit {{SITENAME}}t ilma nimeldä edesi toimõndaq vai <span class='plainlinks'>[$1 vahtsõst sama vai tõõsõ nimega sisse minnäq]</span>.
 Tähelepandmisõs: niikavva, ku sa olõ-i tühäs tennüq uma võrgokaeja vaihõmällo, võivaq mõnõq leheküleq iks viil näüdädäq, nigu sa olõsi nimega seen.",
 'welcomecreation' => '<h2>Tereq, $1!</h2><p>Su konto om valmis. Võit taa hindä perrä sisse säädäq.',
 'yourname' => 'Pruukjanimi',
index f17a18e..86cd086 100644 (file)
@@ -423,7 +423,7 @@ $2",
 # Login and logout pages
 'logouttext' => "'''Vos vs avoz dislodjî.'''
 
-Vos ploz continouwer a naivyî so {{SITENAME}} anonimmint, oudonbén [[Special:UserLogin|vos relodjî]], dizo l' minme uzeu ou dizo èn uzeu diferin.
+Vos ploz continouwer a naivyî so {{SITENAME}} anonimmint, oudonbén <span class='plainlinks'>[$1 vos relodjî]</span>, dizo l' minme uzeu ou dizo èn uzeu diferin.
 Notez ki des pådjes k' i gn a si pôrént continowuer a vey come si vos estîz elodjî, disk' a tant ki vos vudrîz l' muchete di vosse betchteu waibe.",
 'welcomecreation' => '== Bénvnowe, $1! ==
 
index 9675b93..1cfc057 100644 (file)
@@ -328,6 +328,10 @@ $1',
 'youhavenewmessages' => 'Mayda ka $1 ($2).',
 'newmessageslink' => 'bag-o nga mga mensahe',
 'newmessagesdifflink' => 'kataposan nga pagbag-o',
+'youhavenewmessagesfromusers' => 'May-ada ka $1 tikang ha {{PLURAL:$3|iba nga gumaramit|$3 mga gumaramit}} ($2).',
+'youhavenewmessagesmanyusers' => 'May-ada ka $1 tikang ha damo nga mga gumaramit ($2).',
+'newmessageslinkplural' => '{{PLURAL:$1|uska bag-o nga mensahe|bag-o nga mga mensahe}}',
+'newmessagesdifflinkplural' => '$1 {{PLURAL:$1|nga pagbag-o|nga mga pagbag-o}}',
 'youhavenewmessagesmulti' => 'Mayda ka mga bag-o nga mensahe ha $1',
 'editsection' => 'igliwat',
 'editsection-brackets' => '[$1]',
@@ -413,6 +417,7 @@ Alayon la igsumat ini ha [[Special:ListUsers/sysop|administrator]], igsurat la a
 'badarticleerror' => 'Ini nga pagbuhat diri mahihimo dinhi nga pakli',
 'cannotdelete' => 'An pakli o an fayl nga "$1" diri napapara.
 Bangin na ini ginpara hin iba.',
+'cannotdelete-title' => 'diri nakakapara han pakli "$1"',
 'badtitle' => 'Maraot nga titulo',
 'badtitletext' => 'An ginhangyo nga pakli diri puyde, waray sulod, o sayop nga nasumpay nga inter-pinunongan o inter-wiki nga titulo.
 Bangin mayda usa o damo nga mga agi nga diri puyde magamit ha mga titulo.',
@@ -424,11 +429,19 @@ An data dini diri mahihimo nga bag-o.',
 Funsyon: $1<br />
 Kweri: $2',
 'viewsource' => 'Kitaa an ginkuhaan',
+'viewsource-title' => 'Kitaa an tinikangan para han $1',
+'actionthrottledtext' => 'Komo uska pangontra ha spam, ikaw in ginlilimitaran paghimo hini nga pagbuhat hin sobra kadamo ha sulod hin gutiay nga oras, ngan ikaw in naglapos hini nga katubtuban.
+Alayon pagutro kahuman hin pipira ka mga minuto.',
+'protectedpagetext' => 'Ini nga pakli in pinasaliporan para mapugngan an mga pagliwat.',
 'viewsourcetext' => 'Puydi ka kinmita ngan kinmopya han gintikangan han pakli:',
+'viewyourtext' => "Puydi nim makit-an ngan makopya an tinikangan han '''imo mga pagliwat''' ha dinhi nga pakli:",
+'sqlhidden' => '(nakatago an SQL query)',
 'namespaceprotected' => "Diri ka gintutugutan pagliwat han mga pakli ha ngaran-lat'ang nga '''$1'''.",
 'ns-specialprotected' => 'Diri maliliwat an mga ispisyal nga pakli.',
 'titleprotected' => 'Ini nga titulo pinasalipod ha paghimo ni [[User:$1|$1]].
 An katadungan nga ginhatag amo in "\'\'$2\'\'".',
+'exception-nologin' => 'Diri nakalog-in',
+'exception-nologin-text' => 'Ini nga pakli o pagbuhat in nagkikinahanglan nga ikaw in mag-log-in ha dinhi nga wiki.',
 
 # Virus scanner
 'virus-unknownscanner' => 'diri-nasasabtan nga antivirus:',
@@ -441,7 +454,9 @@ Ayaw paghingalimot hin pagbalyo han imo [[Special:Preferences|{{SITENAME}} mga g
 'yourpassword' => 'Tigaman-pagsulod:',
 'yourpasswordagain' => 'Utroha pagbutang an tigaman-han-pagsakob:',
 'remembermypassword' => "Hinumdumi an akon pan-sakob dinhi nga panngaykay ''(browser)'' (para ha pinakamaiha $1 {{PLURAL:$1|ka adlaw|ka mga adlaw}})",
+'securelogin-stick-https' => 'Nagpapabilin nga masumpay ha HTTPS kahuman makalog-in',
 'yourdomainname' => 'Imo dominyo:',
+'password-change-forbidden' => 'Diri ka makakabalyo hin pulong-pagsulod ha dinhi nga wiki.',
 'login' => 'Sakob',
 'nav-login-createaccount' => 'Magpalista nga masakob / paghimo hin bag-o nga akawnt',
 'loginprompt' => "Kinahanglan mo hin mga kuki (''cookie'') para makapag log-in ha {{SITENAME}}.",
@@ -481,9 +496,12 @@ Alayon pagutro pagbutang.',
 'mailerror' => 'Sayop han pagpadangat hin surat: $1',
 'emailauthenticated' => 'Ginpamatuod an imo e-mail adres han $2 ha $3.',
 'emailconfirmlink' => 'Igkompirma an imo e-mail address',
+'emaildisabled' => 'Ini nga sityo in diri nakakapadangat hin mga e-mail.',
 'accountcreated' => 'Nahimo an akawnt',
 'accountcreatedtext' => 'An akwant han gumaramit para kan $1 in ginhimo.',
 'createaccount-title' => 'Paghimo hin akawnt para han {{SITENAME}}',
+'usernamehasherror' => 'Agnay-hin-gumaramit in diri puydi magkamay-ada hin mga hash karakter',
+'login-abort-generic' => 'An imo paglog-in in diri malinamposon - Naundang',
 'loginlanguagelabel' => 'Pinulongan: $1',
 
 # Change password dialog
@@ -507,6 +525,8 @@ Temporaryo nga tigaman han pagsakob: $2',
 'passwordreset-emailsent' => 'Ginpadara hin usa ka pahinumdom nga e-mail.',
 
 # Special:ChangeEmail
+'changeemail-none' => '(waray)',
+'changeemail-submit' => 'Igbalyo an e-mail',
 'changeemail-cancel' => 'Pasagdi',
 
 # Edit page toolbar
@@ -751,16 +771,21 @@ Leyenda: '''({{int:cur}})''' = kaibhan ha giuurhii nga pag-bag-o, '''({{int:last
 'timezoneregion-europe' => 'Europa',
 'timezoneregion-indian' => 'Kalawdan Indyana',
 'timezoneregion-pacific' => 'Kalawdan Pasipiko',
-'prefs-searchoptions' => 'Mga pagpipilian han pamiling',
+'prefs-searchoptions' => 'Pamilnga',
 'prefs-namespaces' => "Ngaran-lat'ang",
+'prefs-files' => 'Mga paypay',
 'youremail' => 'E-mail:',
 'username' => 'Agnay hiton gumaramit:',
 'uid' => 'ID han gumaramit:',
+'prefs-memberingroups' => 'Api han {{PLURAL:$1| nga hugpo|nga mga hugpo}}:',
+'prefs-registration' => 'Oras han pagrehistro:',
 'yourrealname' => 'Tinuod nga ngaran:',
 'yourlanguage' => 'Yinaknan:',
 'yournick' => 'Bag-o nga pirma:',
 'badsiglength' => 'Hilaba hin duro it im pirma.
 Dapat diri malabaw ha $1 {{PLURAL:$1|agi|mga agi}} nga kahilaba.',
+'yourgender' => 'Henero:',
+'gender-unknown' => 'Waray ginpasabot',
 'gender-male' => 'Lalaki',
 'gender-female' => 'Babaye',
 'email' => 'E-mail',
@@ -769,16 +794,36 @@ Dapat diri malabaw ha $1 {{PLURAL:$1|agi|mga agi}} nga kahilaba.',
 An imo e-mail address in diri makikit-an kun an iba nga mga gumaramit in makontak ha imo.',
 'prefs-help-email-required' => 'Kinahanglanon it e-mail address.',
 'prefs-info' => 'Panguna nga pananabotan',
+'prefs-i18n' => 'Internasyonalisasyon',
 'prefs-signature' => 'Pirma',
+'prefs-advancedediting' => 'Abansado nga mga pagpipilian',
+'prefs-advancedrc' => 'Abansado nga mga pagpipilian',
+'prefs-advancedrendering' => 'Abansado nga mga pagpipilian',
+'prefs-advancedsearchoptions' => 'Abansado nga mga pagpipilian',
+'prefs-advancedwatchlist' => 'Abansado nga mga pagpipilian',
 'prefs-diffs' => 'Mga kaibhan',
 
+# User preference: e-mail validation using jQuery
+'email-address-validity-valid' => 'E-mail address in baga puydi',
+
 # User rights
+'userrights' => 'Pagdudumara hin mga katungod han gumaramit',
+'userrights-lookup-user' => 'Pagdumaraa han mga hugpo han gumaramit',
+'userrights-user-editname' => 'Igbutang an agnay han gumaramit:',
+'editusergroup' => 'Igliwat han mga hugpo han gumaramit',
+'editinguser' => "Igliliwat an mga katungod han gumaramit han gumaramit '''[[Gumaramit:$1|$1]]''' $2",
 'userrights-groupsmember' => 'Api han:',
 'userrights-reason' => 'Katadungan:',
+'userrights-no-interwiki' => '
+Diri ka gintutugotan pagliwat han mga katungod han gumaramit ha iba nga mga wiki.',
+'userrights-nodatabase' => 'Waray kaaagii an Database $1 o diri ini aada ha lokal.',
+'userrights-changeable-col' => 'Mga hugpo nga puydi mo labtan',
+'userrights-unchangeable-col' => 'Mga hugpo nga diri mo puydi labtan',
 
 # Groups
 'group' => 'Hugpo:',
 'group-user' => 'Mga gumaramit',
+'group-autoconfirmed' => 'Mga gumaramit nga lugaring nakokonpirma',
 'group-bot' => 'Mga bot',
 'group-sysop' => 'Mga magdudumara',
 'group-bureaucrat' => 'Mga burokrata',
@@ -788,9 +833,12 @@ An imo e-mail address in diri makikit-an kun an iba nga mga gumaramit in makonta
 'group-user-member' => '{{HENERO:$1|gumaramit}}',
 'group-bot-member' => 'bot',
 'group-sysop-member' => 'magdudumara',
+'group-bureaucrat-member' => '{{GENDER:$1|burokrata}}',
 
 'grouppage-user' => '{{ns:project}}:Mga gumaramit',
+'grouppage-bot' => '{{ns:project}}:Mga bot',
 'grouppage-sysop' => '{{ns:project}}:Mga magdudumara',
+'grouppage-bureaucrat' => '{{ns:project}}:Mga burokrata',
 'grouppage-suppress' => '{{ns:project}}:Nanginginano',
 
 # Rights
@@ -798,11 +846,24 @@ An imo e-mail address in diri makikit-an kun an iba nga mga gumaramit in makonta
 'right-edit' => 'Igliwat an mga pakli',
 'right-createpage' => 'Paghimo hin mga pakli (nga diri an mga hiruhimangraw nga mga pakli)',
 'right-createtalk' => 'Paghimo hin hiruhimangraw nga mga pakli',
+'right-createaccount' => 'Paghimo hin bag-o nga mga akawnt hin gumaramit',
 'right-minoredit' => 'Igmarka an mga ginliwat komo gutiay la',
 'right-move' => 'Igbalhin an mga pakli',
+'right-move-subpages' => 'Igbalhin an pakli lakip an ira mga bahinpakli',
+'right-move-rootuserpages' => 'Igbalhin an gamot nga mga pakli han gumaramit',
 'right-movefile' => 'Balhina an mga paypay',
+'right-upload' => 'Igkarga paigbaw an mga paypay',
+'right-reupload' => 'Sapawa an mga aada nga mga paypay',
+'right-upload_by_url' => 'Igkarga paigbaw an mga paypay tikang ha uska URL',
+'right-bot' => 'Igtrato komo uska naglulugaring nga proseso',
 'right-delete' => 'Igpara an mga pakli',
+'right-bigdelete' => 'Igpara an mga pakli nga may-ada dagko nga mga kaagi',
+'right-browsearchive' => 'Pamiling hin mga ginpara nga mga pakli',
 'right-undelete' => 'Igpawara an pagpara han pakli',
+'right-suppressionlog' => 'Kitaa an mga pribado nga mga talaan',
+'right-block' => 'Pugnga an iba nga mga gumaramit ha pagliwat',
+'right-blockemail' => 'Pugnga an uska gumaramit tikang ha pagpadangat hin e-mail',
+'right-hideuser' => 'Pugnga an uska agnay-hin-gumaramit, tago-a ito tikang ha publiko',
 
 # User rights log
 'rightsnone' => '(waray)',
@@ -959,7 +1020,7 @@ An paglaladawan han iya [$2 fayl han paglaladawan nga pakli] didto in ginpapakit
 'statistics-header-hooks' => 'Lain nga mga estadistika',
 'statistics-articles' => 'Unod nga mga pakli',
 'statistics-pages' => 'Mga pakli',
-'statistics-pages-desc' => 'Ngatanan nga mga pakli ha sulod hini nga wiki, lakip an hiruhimangraw nga mga pakli, mga redirect, ngan iba pa',
+'statistics-pages-desc' => 'Ngatanan nga mga pakli ha sulod hini nga wiki, lakip an<br />hiruhimangraw nga mga pakli, mga redirect, ngan iba pa',
 'statistics-files' => 'Mga paypay nga iginkarga pasaka',
 'statistics-edits' => 'Mga pagliwat hit pakli tikang gintukod hini nga {{SITENAME}}',
 'statistics-edits-average' => 'Average nga mga pagliwat kada pakli',
@@ -1205,6 +1266,8 @@ Kitaa an $2 para hin talaan han mga gibag-ohi nga mga ginpamara.',
 'blockip' => 'Pugngi an gumaramit',
 'blockip-title' => 'Pugngi an gumaramit',
 'blockip-legend' => 'Pugngi an gumaramit',
+'ipbreason' => 'Katadungan:',
+'ipbreasonotherlist' => 'Lain nga katadungan',
 'ipbreason-dropdown' => '*Agsob nga mga rason hit pagpugong
 ** Pagsusuksok hin sayop nga pananabutan
 ** Pagtatangtang hin sulod tikang ha mga pakli
@@ -1219,6 +1282,9 @@ Kitaa an $2 para hin talaan han mga gibag-ohi nga mga ginpamara.',
 'ipbotherreason' => 'Lain/dugang nga katadungan:',
 'blockipsuccesssub' => 'Malinamposon an pagpugong',
 'ipblocklist' => 'Mga ginpugngan nga gumaramit',
+'blocklist-target' => 'Gin-iigo',
+'blocklist-expiry' => 'Napan-os',
+'blocklist-by' => 'Ginpupugngan an admin',
 'ipblocklist-submit' => 'Bilnga',
 'blocklink' => 'igpugong',
 'unblocklink' => 'igtanggal an pagpugong',
@@ -1234,6 +1300,7 @@ Kitaa an $2 para hin talaan han mga gibag-ohi nga mga ginpamara.',
 
 # Move page
 'move-page' => 'Mabalhin an $1',
+'move-page-legend' => 'Balhina an pakli:',
 'movearticle' => 'Balhina an pakli:',
 'moveuserpage-warning' => "'''Pahimatngon:''' Tibalhin ka hin pakli hin gumaramit. Alayon pagtigaman nga an pakli là an mababalhin ngan an gumaramit in ''diri'' mababalyoan hin ngaran.",
 'newtitle' => 'Para ha bag-o nga titulo:',
@@ -1266,6 +1333,7 @@ Kitaa an $2 para hin talaan han mga gibag-ohi nga mga ginpamara.',
 'thumbnail_image-type' => 'An klase han hulagway in diri suportado',
 
 # Special:Import
+'import-upload-filename' => 'Ngaran han paypay:',
 'import-comment' => 'Komento:',
 
 # Tooltip help for the actions
@@ -1332,6 +1400,22 @@ Makikit-an nimo an ginkuhaaan',
 # Attribution
 'othercontribs' => 'Ginbasihan ha binuhat ni $1.',
 
+# Info page
+'pageinfo-header-edits' => 'Kaagi han pagliwat',
+'pageinfo-header-restrictions' => 'Panalipod han pakli',
+'pageinfo-robot-index' => 'Matutudlok',
+'pageinfo-robot-noindex' => 'Diri matutudlok',
+'pageinfo-views' => 'Ihap han mga naglantaw',
+'pageinfo-watchers' => 'Ihap han nangingita hin pakli',
+'pageinfo-redirects-name' => 'Nairedirekta ha dinhi nga pakli',
+'pageinfo-subpages-name' => 'Mga bahinpakli hin nga pakli',
+'pageinfo-subpages-value' => '$1 ($2 {{PLURAL:$2|redirekta|mga redirekta}}; $3 {{PLURAL:$3|diri redirekta|mga diri redirekta}})',
+'pageinfo-firstuser' => 'Naghimo han pakli',
+'pageinfo-firsttime' => 'Adlaw han pagkahimo han pakli',
+'pageinfo-lastuser' => 'Giurhii nga nagliwat',
+'pageinfo-lasttime' => 'Petsa han kataposan nga pagliwat',
+'pageinfo-edits' => 'Ngatanan nga ihap han mga pakli',
+
 # Browsing diffs
 'previousdiff' => '← Durudaan nga pagliwat',
 'nextdiff' => 'Burubag-o nga pagliwat',
@@ -1342,6 +1426,7 @@ Makikit-an nimo an ginkuhaaan',
 'file-nohires' => 'Waray mas hiruhitaas nga resolusyon.',
 'svg-long-desc' => 'SVG nga fayl, ginbabanabanahan nga $1 × $2 nga mga pixel, kadako han fayl: $3',
 'show-big-image' => 'Bug-os nga resolusyon',
+'show-big-image-size' => '$1 × $2 nga mga pixel',
 
 # Special:NewFiles
 'newimages-legend' => 'Panara',
@@ -1416,6 +1501,14 @@ An iba in daan nakatago.
 'exif-gpsaltitude-above-sealevel' => '$1 {{PLURAL:$1|metro|mga metro}} bawbaw han katupngan ha dagat',
 'exif-gpsaltitude-below-sealevel' => '$1 {{PLURAL:$1|metro|mga metro}} ubos han katupngan ha dagat',
 
+# Pseudotags used for GPSSpeedRef
+'exif-gpsspeed-k' => 'Mga kilometro kada oras',
+'exif-gpsspeed-m' => 'Mga milya kada oras',
+
+# Pseudotags used for GPSDestDistanceRef
+'exif-gpsdestdistance-k' => 'Mga kilometro',
+'exif-gpsdestdistance-m' => 'Mga milya',
+
 'exif-objectcycle-a' => 'Aga la',
 'exif-objectcycle-p' => 'Gab-i la',
 
@@ -1509,6 +1602,7 @@ An iba in daan nakatago.
 'version-software-version' => 'Bersyon',
 
 # Special:FilePath
+'filepath' => 'Aragian han paypay',
 'filepath-page' => 'Paypay:',
 'filepath-submit' => 'Kadto-a',
 
@@ -1557,9 +1651,48 @@ An iba in daan nakatago.
 'htmlform-selectorother-other' => 'iba',
 
 # New logging system
+'logentry-newusers-newusers' => '$1 in naghimo hin gumaramit nga akawnt',
+'logentry-newusers-create' => '$1 in naghimo hin gumaramit nga akawnt',
+'logentry-newusers-create2' => '$1 in naghimo hin gumaramit nga akawnt $3',
+'logentry-newusers-autocreate' => 'An akawnt nga $1 in lugaring nga nahimo',
 'newuserlog-byemail' => 'Ginpadangat an tigaman-pagsulod pinaagi han e-mail',
 
 # Feedback
 'feedback-close' => 'Human na.',
 
+# Search suggestions
+'searchsuggest-search' => 'Pamilnga',
+'searchsuggest-containing' => 'nagsusulod. . .',
+
+# API errors
+'api-error-badaccess-groups' => 'Diri ka gintutugotan pagkarga paigbaw ha dinhi nga wiki.',
+'api-error-badtoken' => 'Sayop ha sulod: Maraot nga token.',
+'api-error-copyuploaddisabled' => 'Pagkarga paigbaw pinaagi han URL in diri mahihimo ha dinhi nga serbidor.',
+'api-error-filename-tooshort' => 'An ngaran han paypay in halipot hin duro.',
+'api-error-filetype-banned' => 'Diri gintutugotan ini nga klase nga paypay.',
+'api-error-filetype-missing' => 'Ini nga ngaran han paypay in nawawad-an hin ekstensyon.',
+'api-error-http' => 'Sayop ha sulod: Diri nakakasumpay ha serbidor.',
+'api-error-illegal-filename' => 'Diri gintutugotan an ngaran-han-paypay.',
+'api-error-overwrite' => 'Pagsasapaw in aada nga paypay in diri gintutugotan.',
+'api-error-stashfailed' => 'Sayop ha sulod:  An serbidor in waray makatipig han temporaryo nga paypay.',
+'api-error-timeout' => 'An serbidor in diri nabaton ha sulod han ginaasahan nga oras.',
+'api-error-unclassified' => 'Nahitabo an waray kasabti nga sayop.',
+'api-error-unknown-code' => 'Waray kasabti nga sayop: "$1".',
+'api-error-unknown-error' => 'Sayop ha sulod: May-ada nagkasayop han pagkakarga paigbaw han imo paypay.',
+'api-error-unknown-warning' => 'Waray kasabti nga pahimatngon: "$1".',
+'api-error-unknownerror' => 'Waray kasabti nga sayop: "$1".',
+'api-error-uploaddisabled' => 'Diri ginpapakarga paigbaw ha dinhi nga wiki.',
+'api-error-verification-error' => 'Ini nga paypay in bangin naraot, o may-ada iba nga ekstensyon.',
+
+# Durations
+'duration-seconds' => '$1 {{PLURAL:$1|segundo|mga segundo}}',
+'duration-minutes' => '$1 {{PLURAL:$1|minuto|mga minuto}}',
+'duration-hours' => '$1 {{PLURAL:$1|oras|mga oras}}',
+'duration-days' => '$1 {{PLURAL:$1|adlaw|mga adlaw}}',
+'duration-weeks' => '$1 {{PLURAL:$1|semana|mga semana}}',
+'duration-years' => '$1 {{PLURAL:$1|tuig|mga tuig}}',
+'duration-decades' => '$1 {{PLURAL:$1|dekada|mga dekada}}',
+'duration-centuries' => '$1 {{PLURAL:$1|gatostuig|mga gatostuig}}',
+'duration-millennia' => '$1 {{PLURAL:$1|yukottuig|mga yukottuig}}',
+
 );
index 15bcb03..da7837b 100644 (file)
@@ -450,7 +450,7 @@ Ngirte li mu joxe mooy ne « ''$2'' ».",
 # Login and logout pages
 'logouttext' => "Fi mu nekk nii génn nga.'''
 
-Man ngaa wéy di jëfandikoo {{SITENAME}} ci anam buñ la dul xamme walla nga  [[Special:UserLogin|duggewaat]] ak wenn tur wi walla ak weneen.",
+Man ngaa wéy di jëfandikoo {{SITENAME}} ci anam buñ la dul xamme walla nga  <span class='plainlinks'>[$1 duggewaat]</span> ak wenn tur wi walla ak weneen.",
 'welcomecreation' => '== Dalal-jàmm, $1 ! ==
 Sosees na sa sàq.
 Bul fatte soppi say [[Special:Preferences|{{SITENAME}} tànneef]].',
index bf1a49b..0b31e31 100644 (file)
@@ -382,7 +382,7 @@ $2',
 # Login and logout pages
 'logouttext' => "侬已经登出哉。'''
 
-侬可以继续匿名使用{{SITENAME}} ,也可以再次以相同或者两样个用户名[[Special:UserLogin|登录]]
+侬可以继续匿名使用{{SITENAME}} ,也可以再次以相同或者两样个用户名<span class='plainlinks'>[$1 登录]</span>
 注意,有眼页面作兴还是会搭侬登出前头一样显示,一脚到侬清除浏览器缓存。",
 'welcomecreation' => '== 欢迎侬, $1! ==
 
index a75c474..c5ef036 100644 (file)
@@ -397,7 +397,7 @@ $1',
 # Login and logout pages
 'logouttext' => "'''Та һарад бәәнәт.'''
 
-Та {{SITENAME}} гидг ормиг нертә уга олзлҗ чаднат, аль та [[Special:UserLogin|дәкәд орҗ]] цацу аль талдан нертә чаднат.
+Та {{SITENAME}} гидг ормиг нертә уга олзлҗ чаднат, аль та <span class='plainlinks'>[$1 дәкәд орҗ]</span> цацу аль талдан нертә чаднат.
 Зәрм халхс цааранднь та ода чигн орсн мет үзүлҗ чаддг тускар темдглтн (та хәләчин санлиг цеврлтл).",
 'welcomecreation' => '== Ирхитн эрҗәнәвидн, $1! ==
 Таднар шин бичгдлһн бүтв.
index 4cecc32..cc7e5a6 100644 (file)
@@ -595,7 +595,7 @@ $2',
 # Login and logout pages
 'logouttext' => "'''איר האָט זיך ארויסלאָגירט.'''
 
-איר קענט ממשיך זיין ניצן {{SITENAME}} אַנאנים, אדער איר קענט  [[Special:UserLogin|צוריק אריינלאגירן]] מיט דעם זעלבן אדער אן אנדער באַניצער נאָמען. באמערקט אז געוויסע בלעטער קענען זיך ווייטער ארויסשטעלן אזוי ווי ווען איר זענט אריינלאגירט, ביז איר וועט אויסליידיגן דעם בלעטערער זאפאס.",
+איר קענט ממשיך זיין ניצן {{SITENAME}} אַנאנים, אדער איר קענט  <span class='plainlinks'>[$1 צוריק אריינלאגירן]</span> מיט דעם זעלבן אדער אן אנדער באַניצער נאָמען. באמערקט אז געוויסע בלעטער קענען זיך ווייטער ארויסשטעלן אזוי ווי ווען איר זענט אריינלאגירט, ביז איר וועט אויסליידיגן דעם בלעטערער זאפאס.",
 'welcomecreation' => '== ברוך הבא, $1! ==
 אייער קאנטע איז באשאפן געווארן. נישט פארגעסן צו ענדערן אייערע [[Special:Preferences|{{SITENAME}} פרעפֿערענצן]].',
 'yourname' => 'באַניצער נאָמען:',
@@ -2865,7 +2865,6 @@ $1',
 'pageinfo-authors' => 'סה"כ צאָל באַזונדערע שרײַבער',
 'pageinfo-recent-edits' => 'לעצטיקע צאל רעדאקטירונגען (במשך די לעצטע $1)',
 'pageinfo-recent-authors' => 'לעצטיקע צאָל באַזונדערע שרײַבער',
-'pageinfo-restriction' => 'בלאט שוץ ($1)',
 'pageinfo-magic-words' => '{{PLURAL:$1|מאגיש ווארט|מאגישע ווערטער}} ($1)',
 'pageinfo-hidden-categories' => 'באהאלטענע {{PLURAL:$1|קאטעגאריע|קאטעגאריעס}} ($1)',
 'pageinfo-templates' => ' {{PLURAL:$1|אריבערגעשלאסענער מוסטער|אריבערגשלאסענע מוסטערן}} ($1)',
index 4769e8b..d20de05 100644 (file)
@@ -448,7 +448,7 @@ Láti ṣ'àfikún tàbí ṣ'àyípadà àwọn ìyédèpadà fún gbogbo àw
 # Login and logout pages
 'logouttext' => "'''Ẹ ti bọ́sọ́de.'''
 
-Ẹ le tẹ̀síwájú sí ní lo {{SITENAME}} láìmorúkọ yín, tàbí kí ẹ [[Special:UserLogin|padà wọlé]] bí ẹnikanan tàbí ẹlòmíràn.
+Ẹ le tẹ̀síwájú sí ní lo {{SITENAME}} láìmorúkọ yín, tàbí kí ẹ <span class='plainlinks'>[$1 padà wọlé]</span> bí ẹnikanan tàbí ẹlòmíràn.
 Àkíyèsí wípé àwọn ojúewé kan le hàn b'ígbà tójẹ́pé ẹ sì wọlé títí tí ẹ ó fi jọ̀wọ́ cache browser yín.",
 'welcomecreation' => "== Ẹ kú àbọ̀, $1! ==
 
index 08a7337..9f99887 100644 (file)
@@ -613,7 +613,7 @@ $1',
 # Login and logout pages
 'logouttext' => "'''你而家已經登出咗。'''
 
-你重可以用匿名身份用{{SITENAME}},又或者[[Special:UserLogin|重新登入]]
+你重可以用匿名身份用{{SITENAME}},又或者<span class='plainlinks'>[$1 重新登入]</span>
 但係留意某啲頁面可能會繼續話你未登入,除非等你清除瀏覽器嘅快取儲存。",
 'welcomecreation' => '== 歡迎, $1! ==
 
index b36656b..8226da9 100644 (file)
@@ -681,7 +681,7 @@ $2',
 # Login and logout pages
 'logouttext' => "'''您现在已经退出。'''
 
-您可以继续以匿名方式使用{{SITENAME}},或再次以相同或不同用户身份[[Special:UserLogin|登录]]。请注意一些页面可能仍然显示您为登录状态,直到您清空您的浏览器缓存为止。",
+您可以继续以匿名方式使用{{SITENAME}},或再次以相同或不同用户身份<span class='plainlinks'>[$1 登录]</span>。请注意一些页面可能仍然显示您为登录状态,直到您清空您的浏览器缓存为止。",
 'welcomecreation' => '== 欢迎,$1! ==
 你的账户已创建。请别忘记更改你的[[Special:Preferences|{{SITENAME}}系统设置]]。',
 'yourname' => '用户名:',
@@ -2963,7 +2963,6 @@ $1被封禁的理由是:“$2”',
 'pageinfo-authors' => '不同编者总计',
 'pageinfo-recent-edits' => '最近的编辑数 ($1天内)',
 'pageinfo-recent-authors' => '最近的不同编者数',
-'pageinfo-restriction' => '页面保护 ({{lcfirst:$1}})',
 'pageinfo-magic-words' => '魔术字 ($1)',
 'pageinfo-hidden-categories' => '隐藏分类 ($1)',
 'pageinfo-templates' => '使用的模板 ($1)',
index 69f2ac2..1be7036 100644 (file)
@@ -615,10 +615,10 @@ $2',
 'virus-unknownscanner' => '未知的防病毒:',
 
 # Login and logout pages
-'logouttext' => '您已經登出。
+'logouttext' => "您已經登出。
 
-您可以以匿名方式繼續使用{{SITENAME}},或以相同或不同用戶身份[[Special:UserLogin|登入]]
-請注意,如果你再次登入,此頁或會繼續顯示,直到您清除瀏覽器緩存。',
+您可以以匿名方式繼續使用{{SITENAME}},或以相同或不同用戶身份<span class='plainlinks'>[$1 登入]</span>
+請注意,如果你再次登入,此頁或會繼續顯示,直到您清除瀏覽器緩存。",
 'welcomecreation' => '== 歡迎,$1! ==
 您的賬號已經建立。
 不要忘記設置[[Special:Preferences|{{SITENAME}}的個人參數]]。',
@@ -2967,10 +2967,10 @@ $1被封禁的理由是“$2”',
 'pageinfo-authors' => '作者總數',
 'pageinfo-recent-edits' => '最近編輯次數 (過去 $1 內)',
 'pageinfo-recent-authors' => '最近作者數目',
-'pageinfo-restriction' => '保護頁面 ( {{lcfirst:$1}} )',
 'pageinfo-magic-words' => '魔術{{PLURAL:$1|字|字}} ( $1 )',
 'pageinfo-hidden-categories' => '隱藏{{PLURAL:$1|分類|分類}} ( $1 )',
 'pageinfo-templates' => '被引用的{{PLURAL:$1|模版|模版}} ( $1 )',
+'pageinfo-toolboxlink' => '頁面資訊',
 
 # Skin names
 'skinname-standard' => '標準',
@@ -3566,6 +3566,7 @@ $5
 # Scary transclusion
 'scarytranscludedisabled' => '[跨wiki轉換代碼不可用]',
 'scarytranscludefailed' => '[模板$1讀取失敗]',
+'scarytranscludefailed-httpstatus' => '[模板$1讀取失敗:HTTP$2]',
 'scarytranscludetoolong' => '[URL 地址太長]',
 
 # Delete conflict
index 1056ece..be2b8c3 100644 (file)
@@ -81,6 +81,7 @@ class CLDRPluralRuleEvaluator {
         * @param $token string The token string
         * @param $left The left operand. If it is an object, its state may be destroyed.
         * @param $right The right operand
+        * @throws CLDRPluralRuleError
         * @return mixed
         */
        private static function doOperation( $token, $left, $right ) {
index c00d7a6..ea649a6 100644 (file)
@@ -1136,7 +1136,8 @@ abstract class Maintenance {
                        $title = $titleObj->getPrefixedDBkey();
                        $this->output( "$title..." );
                        # Update searchindex
-                       $u = new SearchUpdate( $pageId, $titleObj->getText(), $rev->getText() );
+                       # TODO: pass the Content object to SearchUpdate, let the search engine decide how to deal with it.
+                       $u = new SearchUpdate( $pageId, $titleObj->getText(), $rev->getContent()->getTextForSearchIndex() );
                        $u->doUpdate();
                        $this->output( "\n" );
                }
diff --git a/maintenance/archives/patch-archive-ar_content_format.sql b/maintenance/archives/patch-archive-ar_content_format.sql
new file mode 100644 (file)
index 0000000..81f9fca
--- /dev/null
@@ -0,0 +1,2 @@
+ALTER TABLE /*$wgDBprefix*/archive
+  ADD ar_content_format varbinary(64) DEFAULT NULL;
diff --git a/maintenance/archives/patch-archive-ar_content_model.sql b/maintenance/archives/patch-archive-ar_content_model.sql
new file mode 100644 (file)
index 0000000..1a8b630
--- /dev/null
@@ -0,0 +1,2 @@
+ALTER TABLE /*$wgDBprefix*/archive
+  ADD ar_content_model varbinary(32) DEFAULT NULL;
diff --git a/maintenance/archives/patch-page-page_content_model.sql b/maintenance/archives/patch-page-page_content_model.sql
new file mode 100644 (file)
index 0000000..30434d9
--- /dev/null
@@ -0,0 +1,2 @@
+ALTER TABLE /*$wgDBprefix*/page
+  ADD page_content_model varbinary(32) DEFAULT NULL;
diff --git a/maintenance/archives/patch-revision-rev_content_format.sql b/maintenance/archives/patch-revision-rev_content_format.sql
new file mode 100644 (file)
index 0000000..22aeb8a
--- /dev/null
@@ -0,0 +1,2 @@
+ALTER TABLE /*$wgDBprefix*/revision
+  ADD rev_content_format varbinary(64) DEFAULT NULL;
diff --git a/maintenance/archives/patch-revision-rev_content_model.sql b/maintenance/archives/patch-revision-rev_content_model.sql
new file mode 100644 (file)
index 0000000..1ba0572
--- /dev/null
@@ -0,0 +1,2 @@
+ALTER TABLE /*$wgDBprefix*/revision
+  ADD rev_content_model varbinary(32) DEFAULT NULL;
index f1f0954..81e61b7 100644 (file)
@@ -169,7 +169,7 @@ class TextPassDumper extends BackupDumper {
                $this->xmlwriterobj = new XmlDumpWriter();
 
                $input = fopen( $this->input, "rt" );
-               $result = $this->readDump( $input );
+               $this->readDump( $input );
 
                if ( $this->spawnProc ) {
                        $this->closeSpawn();
index 670b93d..4ba7e66 100644 (file)
@@ -50,7 +50,7 @@ class CheckBadRedirects extends Maintenance {
                        $title = Title::makeTitle( $row->page_namespace, $row->page_title );
                        $rev = Revision::newFromId( $row->page_latest );
                        if ( $rev ) {
-                               $target = Title::newFromRedirect( $rev->getText() );
+                               $target = $rev->getContent()->getRedirectTarget();
                                if ( !$target ) {
                                        $this->output( $title->getPrefixedText() . "\n" );
                                }
index e20bcd8..9838569 100644 (file)
@@ -103,7 +103,8 @@ class CleanupSpam extends Maintenance {
                $rev = Revision::newFromTitle( $title );
                $currentRevId = $rev->getId();
 
-               while ( $rev && ( $rev->isDeleted( Revision::DELETED_TEXT ) || LinkFilter::matchEntry( $rev->getText() , $domain ) ) ) {
+               while ( $rev && ( $rev->isDeleted( Revision::DELETED_TEXT )
+                                               || LinkFilter::matchEntry( $rev->getContent( Revision::RAW ), $domain ) ) ) {
                        $rev = $rev->getPrevious();
                }
 
@@ -117,8 +118,10 @@ class CleanupSpam extends Maintenance {
                        $page = WikiPage::factory( $title );
                        if ( $rev ) {
                                // Revert to this revision
+                               $content = $rev->getContent( Revision::RAW );
+
                                $this->output( "reverting\n" );
-                               $page->doEdit( $rev->getText(), wfMessage( 'spam_reverting', $domain )->inContentLanguage()->text(),
+                               $page->doEditContent( $content, wfMessage( 'spam_reverting', $domain )->inContentLanguage()->text(),
                                        EDIT_UPDATE, $rev->getId() );
                        } elseif ( $this->hasOption( 'delete' ) ) {
                                // Didn't find a non-spammy revision, blank the page
@@ -126,8 +129,11 @@ class CleanupSpam extends Maintenance {
                                $page->doDeleteArticle( wfMessage( 'spam_deleting', $domain )->inContentLanguage()->text() );
                        } else {
                                // Didn't find a non-spammy revision, blank the page
+                               $handler = ContentHandler::getForTitle( $title );
+                               $content = $handler->makeEmptyContent();
+
                                $this->output( "blanking\n" );
-                               $page->doEdit( '', wfMessage( 'spam_blanking', $domain )->inContentLanguage()->text() );
+                               $page->doEditContent( $content, wfMessage( 'spam_blanking', $domain )->inContentLanguage()->text() );
                        }
                        $dbw->commit( __METHOD__ );
                }
index a333717..1f3ac1c 100644 (file)
@@ -114,15 +114,24 @@ class CompareParsers extends DumpIterator {
                $parser1 = new $parser1Name();
                $parser2 = new $parser2Name();
 
-               $output1 = $parser1->parse( $rev->getText(), $title, $this->options );
-               $output2 = $parser2->parse( $rev->getText(), $title, $this->options );
+               $content = $rev->getContent();
+
+               if ( $content->getModel() !== CONTENT_MODEL_WIKITEXT ) {
+                       $this->error( "Page {$title->getPrefixedText()} does not contain wikitext but {$content->getModel()}\n" );
+                       return;
+               }
+
+               $text = strval( $content->getNativeData() );
+
+               $output1 = $parser1->parse( $text, $title, $this->options );
+               $output2 = $parser2->parse( $text, $title, $this->options );
 
                if ( $output1->getText() != $output2->getText() ) {
                        $this->failed++;
                        $this->error( "Parsing for {$title->getPrefixedText()} differs\n" );
 
                        if ( $this->saveFailed ) {
-                               file_put_contents( $this->saveFailed . '/' . rawurlencode( $title->getPrefixedText() ) . ".txt", $rev->getText());
+                               file_put_contents( $this->saveFailed . '/' . rawurlencode( $title->getPrefixedText() ) . ".txt", $text );
                        }
                        if ( $this->showDiff ) {
                                $this->output( wfDiff( $this->stripParameters( $output1->getText() ), $this->stripParameters( $output2->getText() ), '' ) );
old mode 100755 (executable)
new mode 100644 (file)
old mode 100755 (executable)
new mode 100644 (file)
old mode 100755 (executable)
new mode 100644 (file)
old mode 100755 (executable)
new mode 100644 (file)
old mode 100755 (executable)
new mode 100644 (file)
old mode 100755 (executable)
new mode 100644 (file)
index 3657f96..870d632 100644 (file)
@@ -168,7 +168,7 @@ class SearchDump extends DumpIterator {
         * @param $rev Revision
         */
        public function processRevision( $rev ) {
-               if ( preg_match( $this->getOption( 'regex' ), $rev->getText() ) ) {
+               if ( preg_match( $this->getOption( 'regex' ), $rev->getContent()->getTextForSearchIndex() ) ) {
                        $this->output( $rev->getTitle() . " matches at edit from " . $rev->getTimestamp() . "\n" );
                }
        }
index 59df5e8..ad4c12f 100644 (file)
@@ -68,10 +68,11 @@ class EditCLI extends Maintenance {
 
                # Read the text
                $text = $this->getStdin( Maintenance::STDIN_ALL );
+               $content = ContentHandler::makeContent( $text, $wgTitle );
 
                # Do the edit
                $this->output( "Saving... " );
-               $status = $page->doEdit( $text, $summary,
+               $status = $page->doEditContent( $content, $summary,
                        ( $minor ? EDIT_MINOR : 0 ) |
                        ( $bot ? EDIT_FORCE_BOT : 0 ) |
                        ( $autoSummary ? EDIT_AUTOSUMMARY : 0 ) |
index 3e2f854..f6adfe2 100644 (file)
@@ -52,12 +52,12 @@ class GetTextMaint extends Maintenance {
                        $titleText = $title->getPrefixedText();
                        $this->error( "Page $titleText does not exist.\n", true );
                }
-               $text = $rev->getText( $this->hasOption( 'show-private' ) ? Revision::RAW : Revision::FOR_PUBLIC );
-               if ( $text === false ) {
+               $content = $rev->getContent( $this->hasOption( 'show-private' ) ? Revision::RAW : Revision::FOR_PUBLIC );
+               if ( $content === false ) {
                        $titleText = $title->getPrefixedText();
                        $this->error( "Couldn't extract the text from $titleText.\n", true );
                }
-               $this->output( $text );
+               $this->output( $content->serialize() );
        }
 }
 
old mode 100755 (executable)
new mode 100644 (file)
old mode 100755 (executable)
new mode 100644 (file)
index e369cb1..fabc6dc 100644 (file)
@@ -62,7 +62,8 @@ class ImportSiteScripts extends Maintenance {
                        $text = Http::get( $url );
 
                        $wikiPage = WikiPage::factory( $title );
-                       $wikiPage->doEdit( $text, "Importing from $url", 0, false, $user );
+                       $content = ContentHandler::makeContent( $text, $wikiPage->getTitle() );
+                       $wikiPage->doEditContent( $content, "Importing from $url", 0, false, $user );
                }
 
        }
index adb5063..c04989c 100644 (file)
@@ -56,7 +56,8 @@ if ( count( $args ) < 1 || isset( $options['help'] ) ) {
 
                                        echo( "\nPerforming edit..." );
                                        $page = WikiPage::factory( $title );
-                                       $page->doEdit( $text, $comment, $flags, false, $user );
+                                       $content = ContentHandler::makeContent( $text, $title );
+                                       $page->doEditContent( $content, $comment, $flags, false, $user );
                                        echo( "done.\n" );
 
                                } else {
index 762bb94..39e613f 100644 (file)
@@ -45,7 +45,8 @@ class CommandLineInstaller extends Maintenance {
                $this->addArg( 'name', 'The name of the wiki', true);
 
                $this->addArg( 'admin', 'The username of the wiki administrator (WikiSysop)', true );
-               $this->addOption( 'pass', 'The password for the wiki administrator.', true, true );
+               $this->addOption( 'pass', 'The password for the wiki administrator.', false, true );
+               $this->addOption( 'passfile', 'An alternative way to provide pass option, as the contents of this file', false, true );
                /* $this->addOption( 'email', 'The email for the wiki administrator', false, true ); */
                $this->addOption( 'scriptpath', 'The relative path of the wiki in the web server (/wiki)', false, true );
 
@@ -77,6 +78,9 @@ class CommandLineInstaller extends Maintenance {
 
                $dbpassfile = $this->getOption( 'dbpassfile', false );
                if ( $dbpassfile !== false ) {
+                       if ( $this->getOption( 'dbpass', false ) !== false ) {
+                               $this->error( 'WARNING: You provide the options "dbpass" and "dbpassfile". The content of "dbpassfile" overwrites "dbpass".' );
+                       }
                        wfSuppressWarnings();
                        $dbpass = file_get_contents( $dbpassfile );
                        wfRestoreWarnings();
@@ -86,6 +90,22 @@ class CommandLineInstaller extends Maintenance {
                        $this->mOptions['dbpass'] = trim( $dbpass, "\r\n" );
                }
 
+               $passfile = $this->getOption( 'passfile', false );
+               if ( $passfile !== false ) {
+                       if ( $this->getOption( 'pass', false ) !== false ) {
+                               $this->error( 'WARNING: You provide the options "pass" and "passfile". The content of "passfile" overwrites "pass".' );
+                       }
+                       wfSuppressWarnings();
+                       $pass = file_get_contents( $passfile );
+                       wfRestoreWarnings();
+                       if ( $pass === false ) {
+                               $this->error( "Couldn't open $passfile", true );
+                       }
+                       $this->mOptions['pass'] = str_replace( array( "\n", "\r" ), "", $pass );
+               } elseif ( $this->getOption( 'pass', false ) === false ) {
+                       $this->error( 'You need to provide the option "pass" or "passfile"', true );
+               }
+
                $installer =
                        InstallerOverrides::getCliInstaller( $siteName, $adminName, $this->mOptions );
 
index 2a0932a..1860f4a 100644 (file)
@@ -305,6 +305,7 @@ ENDS;
        /**
         * Check a language.
         * @param $code string The language code.
+        * @throws MWException
         * @return array The results.
         */
        protected function checkLanguage( $code ) {
@@ -641,6 +642,7 @@ ENDS;
        /**
         * Check a language and show the results.
         * @param $code string The language code.
+        * @throws MWException
         */
        protected function checkLanguage( $code ) {
                foreach( $this->extensions as $extension ) {
index 4d2542f..eaf170b 100644 (file)
@@ -689,6 +689,15 @@ $wgMessageStructure = array(
                'addsection-preload',
                'addsection-editintro',
                'defaultmessagetext',
+               'content-failed-to-parse',
+               'invalid-content-data',
+               'content-not-allowed-here',
+       ),
+       'contentmodels' => array(
+               'content-model-wikitext',
+               'content-model-text',
+               'content-model-javascript',
+               'content-model-css',
        ),
        'parserwarnings' => array(
                'expensive-parserfunction-warning',
@@ -3829,6 +3838,7 @@ XHTML id names.",
        'toolbar'             => 'Edit page toolbar',
        'edit'                => 'Edit pages',
        'parserwarnings'      => 'Parser/template warnings',
+       'contentmodels'       => 'Content models',
        'undo'                => '"Undo" feature',
        'cantcreateaccount'   => 'Account creation failure',
        'history'             => 'History pages',
index f45525c..c591665 100644 (file)
@@ -68,6 +68,8 @@ class LockServerDaemon {
 
        /**
         * @params $config Array
+        * @param array $config
+        * @throws Exception
         * @return LockServerDaemon
         */
        public static function init( array $config ) {
@@ -113,6 +115,7 @@ class LockServerDaemon {
        }
 
        /**
+        * @throws Exception
         * @return void
         */
        protected function setupServerSocket() {
index 6c835f4..07c395f 100644 (file)
@@ -74,16 +74,16 @@ class PopulateRevisionLength extends LoggedUpdateMaintenance {
                        # Go through and update rev_len from these rows.
                        foreach ( $res as $row ) {
                                $rev = new Revision( $row );
-                               $text = $rev->getRawText();
-                               if ( !is_string( $text ) ) {
+                               $content = $rev->getContent();
+                               if ( !$content ) {
                                        # This should not happen, but sometimes does (bug 20757)
-                                       $this->output( "Text of revision {$row->rev_id} unavailable!\n" );
+                                       $this->output( "Content of revision {$row->rev_id} unavailable!\n" );
                                        $missing++;
                                }
                                else {
                                        # Update the row...
                                        $db->update( 'revision',
-                                                        array( 'rev_len' => strlen( $text ) ),
+                                                        array( 'rev_len' => $content->getSize() ),
                                                         array( 'rev_id' => $row->rev_id ),
                                                         __METHOD__ );
                                        $count++;
index 2e14d31..382b7be 100644 (file)
@@ -143,14 +143,14 @@ class PopulateRevisionSha1 extends LoggedUpdateMaintenance {
                        $rev = ( $table === 'archive' )
                                ? Revision::newFromArchiveRow( $row )
                                : new Revision( $row );
-                       $text = $rev->getRawText();
+                       $text = $rev->getSerializedData();
                } catch ( MWException $e ) {
-                       $this->output( "Text of revision with {$idCol}={$row->$idCol} unavailable!\n" );
+                       $this->output( "Data of revision with {$idCol}={$row->$idCol} unavailable!\n" );
                        return false; // bug 22624?
                }
                if ( !is_string( $text ) ) {
                        # This should not happen, but sometimes does (bug 20757)
-                       $this->output( "Text of revision with {$idCol}={$row->$idCol} unavailable!\n" );
+                       $this->output( "Data of revision with {$idCol}={$row->$idCol} unavailable!\n" );
                        return false;
                } else {
                        $db->update( $table,
@@ -174,10 +174,10 @@ class PopulateRevisionSha1 extends LoggedUpdateMaintenance {
                        $this->output( "Text of revision with timestamp {$row->ar_timestamp} unavailable!\n" );
                        return false; // bug 22624?
                }
-               $text = $rev->getRawText();
+               $text = $rev->getSerializedData();
                if ( !is_string( $text ) ) {
                        # This should not happen, but sometimes does (bug 20757)
-                       $this->output( "Text of revision with timestamp {$row->ar_timestamp} unavailable!\n" );
+                       $this->output( "Data of revision with timestamp {$row->ar_timestamp} unavailable!\n" );
                        return false;
                } else {
                        # Archive table as no PK, but (NS,title,time) should be near unique.
index 5952fd9..87fc997 100644 (file)
@@ -78,8 +78,14 @@ class PreprocessDump extends DumpIterator {
         * @param $rev Revision
         */
        public function processRevision( $rev ) {
+               $content = $rev->getContent( Revision::RAW );
+
+               if ( $content->getModel() !== CONTENT_MODEL_WIKITEXT ) {
+                       return;
+               }
+
                try {
-                       $this->mPreprocessor->preprocessToObj( $rev->getText(), 0 );
+                       $this->mPreprocessor->preprocessToObj( strval( $content->getNativeData() ), 0 );
                }
                catch(Exception $e) {
                        $this->error("Caught exception " . $e->getMessage() . " in " . $rev->getTitle()->getPrefixedText() );
index 2f6aa1a..ff13bd6 100644 (file)
@@ -58,6 +58,7 @@ class Protect extends Maintenance {
                        $this->error( "Invalid username", true );
                }
 
+               // @todo FIXME: This is reset 7 lines down.
                $restrictions = array( 'edit' => $protection, 'move' => $protection );
 
                $t = Title::newFromText( $this->getArg() );
index 3d9270b..535808d 100644 (file)
@@ -216,18 +216,16 @@ class RefreshLinks extends Maintenance {
                        return;
                }
 
-               $text = $page->getRawText();
-               if ( $text === false ) {
+               $content = $page->getContent( REVISION::RAW );
+               if ( null === false ) {
                        return;
                }
 
                $dbw = wfGetDB( DB_MASTER );
                $dbw->begin( __METHOD__ );
 
-               $options = ParserOptions::newFromUserAndLang( new User, $wgContLang );
-               $parserOutput = $wgParser->parse( $text, $page->getTitle(), $options, true, true, $page->getLatest() );
-               $update = new LinksUpdate( $page->getTitle(), $parserOutput, false );
-               $update->doUpdate();
+               $updates = $content->getSecondaryDataUpdates( $page->getTitle() );
+               DataUpdate::runUpdates( $updates );
 
                $dbw->commit( __METHOD__ );
        }
index 24bedfa..ad9a380 100644 (file)
@@ -100,10 +100,10 @@ class DumpRenderer extends Maintenance {
                $this->output( sprintf( "%s\n", $filename, $display ) );
 
                $user = new User();
-               $parser = new $wgParserConf['class']();
                $options = ParserOptions::newFromUser( $user );
 
-               $output = $parser->parse( $rev->getText(), $title, $options );
+               $content = $rev->getContent();
+               $output = $content->getParserOutput( $title, null, $options );
 
                file_put_contents( $filename,
                        "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" " .
index a8a1fce..16568ac 100644 (file)
@@ -43,6 +43,8 @@ class Sqlite {
         * Checks given files for correctness of SQL syntax. MySQL DDL will be converted to
         * SQLite-compatible during processing.
         * Will throw exceptions on SQL errors
+        * @param $files
+        * @throws MWException
         * @return mixed true if no error or error string in case of errors
         */
        public static function checkSqlSyntax( $files ) {
diff --git a/maintenance/storage/drop_content_model_info.sql b/maintenance/storage/drop_content_model_info.sql
new file mode 100644 (file)
index 0000000..7bd9aba
--- /dev/null
@@ -0,0 +1,7 @@
+ALTER TABLE /*$wgDBprefix*/archive  DROP COLUMN ar_content_model;
+ALTER TABLE /*$wgDBprefix*/archive  DROP COLUMN ar_content_format;
+
+ALTER TABLE /*$wgDBprefix*/revision  DROP COLUMN rev_content_model;
+ALTER TABLE /*$wgDBprefix*/revision  DROP COLUMN rev_content_format;
+
+ALTER TABLE /*$wgDBprefix*/page  DROP COLUMN page_content_model;
old mode 100755 (executable)
new mode 100644 (file)
index 58b0847..9487bbf 100644 (file)
@@ -66,7 +66,7 @@ $uncompressedSize = 0;
 $t = -microtime( true );
 foreach ( $res as $row ) {
        $revision = new Revision( $row );
-       $text = $revision->getText();
+       $text = $revision->getSerializedData();
        $uncompressedSize += strlen( $text );
        $hashes[$row->rev_id] = md5( $text );
        $keys[$row->rev_id] = $blob->addItem( $text );
index 4f52289..4a707fd 100644 (file)
@@ -260,7 +260,10 @@ CREATE TABLE /*_*/page (
   page_latest int unsigned NOT NULL,
 
   -- Uncompressed length in bytes of the page's current source text.
-  page_len int unsigned NOT NULL
+  page_len int unsigned NOT NULL,
+
+  -- content model, see CONTENT_MODEL_XXX constants
+  page_content_model  int unsigned  default NULL
 ) /*$wgDBTableOptions*/;
 
 CREATE UNIQUE INDEX /*i*/name_title ON /*_*/page (page_namespace,page_title);
@@ -316,7 +319,13 @@ CREATE TABLE /*_*/revision (
   rev_parent_id int unsigned default NULL,
 
   -- SHA-1 text content hash in base-36
-  rev_sha1 varbinary(32) NOT NULL default ''
+  rev_sha1 varbinary(32) NOT NULL default '',
+
+  -- content model, see CONTENT_MODEL_XXX constants
+  rev_content_model  int unsigned  default NULL,
+
+  -- content format, see CONTENT_FORMAT_XXX constants
+  rev_content_format int unsigned  default NULL
 
 ) /*$wgDBTableOptions*/ MAX_ROWS=10000000 AVG_ROW_LENGTH=1024;
 -- In case tables are created as MyISAM, use row hints for MySQL <5.0 to avoid 4GB limit
@@ -427,7 +436,14 @@ CREATE TABLE /*_*/archive (
   ar_parent_id int unsigned default NULL,
 
   -- SHA-1 text content hash in base-36
-  ar_sha1 varbinary(32) NOT NULL default ''
+  ar_sha1 varbinary(32) NOT NULL default '',
+
+  -- content model, see CONTENT_MODEL_XXX constants
+  ar_content_model  int unsigned default NULL,
+
+  -- content format, see CONTENT_FORMAT_XXX constants
+  ar_content_format int unsigned default NULL
+
 ) /*$wgDBTableOptions*/;
 
 CREATE INDEX /*i*/name_title_timestamp ON /*_*/archive (ar_namespace,ar_title,ar_timestamp);
index 0a4d364..ad15607 100644 (file)
@@ -2,13 +2,13 @@
  * jQuery makeCollapsible
  *
  * This will enable collapsible-functionality on all passed elements.
- * Will prevent binding twice to the same element.
- * Initial state is expanded by default, this can be overriden by adding class
- * "mw-collapsed" to the "mw-collapsible" element.
- * Elements made collapsible have class "mw-made-collapsible".
- * Except for tables and lists, the inner content is wrapped in "mw-collapsible-content".
+ * Will prevent binding twice to the same element.
+ * Initial state is expanded by default, this can be overriden by adding class
+ *   "mw-collapsed" to the "mw-collapsible" element.
+ * - Elements made collapsible have jQuery data "mw-made-collapsible" set to true.
+ * - The inner content is wrapped in a "div.mw-collapsible-content" (except for tables and lists).
  *
- * @author Krinkle <krinklemail@gmail.com>
+ * @author Krinkle, 2011-2012
  *
  * Dual license:
  * @license CC-BY 3.0 <http://creativecommons.org/licenses/by/3.0>
@@ -21,17 +21,33 @@ $.fn.makeCollapsible = function () {
        return this.each(function () {
 
                // Define reused variables and functions
-               var $toggle,
-                       lpx = 'jquery.makeCollapsible> ',
-                       $that = $(this).addClass( 'mw-collapsible' ), // case: $( '#myAJAXelement' ).makeCollapsible()
-                       that = this,
-                       collapsetext = $(this).attr( 'data-collapsetext' ),
-                       expandtext = $(this).attr( 'data-expandtext' ),
-                       toggleElement = function ( $collapsible, action, $defaultToggle, instantHide ) {
+               var lpx = 'jquery.makeCollapsible> ',
+                       collapsible = this,
+                       // Ensure class "mw-collapsible" is present in case .makeCollapsible()
+                       // is called on element(s) that don't have it yet.
+                       $collapsible = $(collapsible).addClass( 'mw-collapsible' ),
+                       collapsetext = $collapsible.attr( 'data-collapsetext' ),
+                       expandtext = $collapsible.attr( 'data-expandtext' ),
+                       $toggle,
+                       $toggleLink,
+                       $firstItem,
+                       collapsibleId,
+                       $customTogglers,
+                       firstval,
+                       /**
+                        * @param {jQuery} $collapsible
+                        * @param {string} action The action this function will take ('expand' or 'collapse').
+                        * @param {jQuery|null} [optional] $defaultToggle
+                        * @param {Object|undefined} options
+                        */
+                       toggleElement = function ( $collapsible, action, $defaultToggle, options ) {
                                var $collapsibleContent, $containers;
+                               options = options || {};
 
                                // Validate parameters
-                               if ( !$collapsible.jquery ) { // $collapsible must be an instance of jQuery
+
+                               // $collapsible must be an instance of jQuery
+                               if ( !$collapsible.jquery ) {
                                        return;
                                }
                                if ( action !== 'expand' && action !== 'collapse' ) {
@@ -41,7 +57,7 @@ $.fn.makeCollapsible = function () {
                                if ( $defaultToggle === undefined ) {
                                        $defaultToggle = null;
                                }
-                               if ( $defaultToggle !== null && !($defaultToggle instanceof $) ) {
+                               if ( $defaultToggle !== null && !$defaultToggle.jquery ) {
                                        // is optional (may be undefined), but if defined it must be an instance of jQuery.
                                        // If it's not, abort right away.
                                        // After this $defaultToggle is either null or a valid jQuery instance.
@@ -55,12 +71,12 @@ $.fn.makeCollapsible = function () {
                                                // Hide all table rows of this table
                                                // Slide doens't work with tables, but fade does as of jQuery 1.1.3
                                                // http://stackoverflow.com/questions/467336#920480
-                                               $containers = $collapsible.find( '>tbody>tr' );
+                                               $containers = $collapsible.find( '> tbody > tr' );
                                                if ( $defaultToggle ) {
                                                        // Exclude tablerow containing togglelink
                                                        $containers.not( $defaultToggle.closest( 'tr' ) ).stop(true, true).fadeOut();
                                                } else {
-                                                       if ( instantHide ) {
+                                                       if ( options.instantHide ) {
                                                                $containers.hide();
                                                        } else {
                                                                $containers.stop( true, true ).fadeOut();
@@ -73,19 +89,20 @@ $.fn.makeCollapsible = function () {
                                                        // Exclude list-item containing togglelink
                                                        $containers.not( $defaultToggle.parent() ).stop( true, true ).slideUp();
                                                } else {
-                                                       if ( instantHide ) {
+                                                       if ( options.instantHide ) {
                                                                $containers.hide();
                                                        } else {
                                                                $containers.stop( true, true ).slideUp();
                                                        }
                                                }
 
-                                       } else { // <div>, <p> etc.
+                                       } else {
+                                               // <div>, <p> etc.
                                                $collapsibleContent = $collapsible.find( '> .mw-collapsible-content' );
 
                                                // If a collapsible-content is defined, collapse it
                                                if ( $collapsibleContent.length ) {
-                                                       if ( instantHide ) {
+                                                       if ( options.instantHide ) {
                                                                $collapsibleContent.hide();
                                                        } else {
                                                                $collapsibleContent.slideUp();
@@ -111,7 +128,7 @@ $.fn.makeCollapsible = function () {
                                                        // Exclude tablerow containing togglelink
                                                        $containers.not( $defaultToggle.parent().parent() ).stop(true, true).fadeIn();
                                                } else {
-                                                       $containers.stop(true, true).fadeIn();
+                                                       $containers.stop( true, true ).fadeIn();
                                                }
 
                                        } else if ( $collapsible.is( 'ul' ) || $collapsible.is( 'ol' ) ) {
@@ -123,7 +140,8 @@ $.fn.makeCollapsible = function () {
                                                        $containers.stop( true, true ).slideDown();
                                                }
 
-                                       } else { // <div>, <p> etc.
+                                       } else {
+                                               // <div>, <p> etc.
                                                $collapsibleContent = $collapsible.find( '> .mw-collapsible-content' );
 
                                                // If a collapsible-content is defined, collapse it
@@ -142,10 +160,15 @@ $.fn.makeCollapsible = function () {
                                        }
                                }
                        },
-                       // Toggles collapsible and togglelink class and updates text label
-                       toggleLinkDefault = function ( that, e ) {
-                               var $that = $(that),
-                                       $collapsible = $that.closest( '.mw-collapsible.mw-made-collapsible' ).toggleClass( 'mw-collapsed' );
+                       /**
+                        * Toggles collapsible and togglelink class and updates text label.
+                        *
+                        * @param {jQuery} $that
+                        * @param {jQuery.Event} e
+                        * @param {Object|undefined} options
+                        */
+                       toggleLinkDefault = function ( $that, e, options ) {
+                               var $collapsible = $that.closest( '.mw-collapsible' ).toggleClass( 'mw-collapsed' );
                                e.preventDefault();
                                e.stopPropagation();
 
@@ -159,7 +182,7 @@ $.fn.makeCollapsible = function () {
                                                $that.text( expandtext );
                                        }
                                        // Collapse element
-                                       toggleElement( $collapsible, 'collapse', $that );
+                                       toggleElement( $collapsible, 'collapse', $that, options );
 
                                // It's collapsed right now
                                } else {
@@ -171,14 +194,20 @@ $.fn.makeCollapsible = function () {
                                                $that.text( collapsetext );
                                        }
                                        // Expand element
-                                       toggleElement( $collapsible, 'expand', $that );
+                                       toggleElement( $collapsible, 'expand', $that, options );
                                }
                                return;
                        },
-                       // Toggles collapsible and togglelink class
-                       toggleLinkPremade = function ( $that, e ) {
-                               var $collapsible = $that.eq(0).closest( '.mw-collapsible.mw-made-collapsible' ).toggleClass( 'mw-collapsed' );
-                               if ( $(e.target).is( 'a' ) ) {
+                       /**
+                        * Toggles collapsible and togglelink class.
+                        *
+                        * @param {jQuery} $that
+                        * @param {jQuery.Event} e
+                        * @param {Object|undefined} options
+                        */
+                       toggleLinkPremade = function ( $that, e, options ) {
+                               var $collapsible = $that.eq( 0 ).closest( '.mw-collapsible' ).toggleClass( 'mw-collapsed' );
+                               if ( $.nodeName( e.target, 'a' ) ) {
                                        return true;
                                }
                                e.preventDefault();
@@ -189,31 +218,45 @@ $.fn.makeCollapsible = function () {
                                        // Change toggle to collapsed
                                        $that.removeClass( 'mw-collapsible-toggle-expanded' ).addClass( 'mw-collapsible-toggle-collapsed' );
                                        // Collapse element
-                                       toggleElement( $collapsible, 'collapse', $that );
+                                       toggleElement( $collapsible, 'collapse', $that, options );
 
                                // It's collapsed right now
                                } else {
                                        // Change toggle to expanded
                                        $that.removeClass( 'mw-collapsible-toggle-collapsed' ).addClass( 'mw-collapsible-toggle-expanded' );
                                        // Expand element
-                                       toggleElement( $collapsible, 'expand', $that );
+                                       toggleElement( $collapsible, 'expand', $that, options );
                                }
                                return;
                        },
-                       // Toggles customcollapsible
-                       toggleLinkCustom = function ( $that, e, $collapsible ) {
+                       /**
+                        * Toggles customcollapsible.
+                        *
+                        * @param {jQuery} $that
+                        * @param {jQuery.Event} e
+                        * @param {Object|undefined} options
+                        * @param {jQuery} $collapsible
+                        */
+                       toggleLinkCustom = function ( $that, e, options, $collapsible ) {
                                // For the initial state call of customtogglers there is no event passed
-                               if (e) {
+                               if ( e ) {
                                        e.preventDefault();
                                        e.stopPropagation();
                                }
                                // Get current state and toggle to the opposite
                                var action = $collapsible.hasClass( 'mw-collapsed' ) ? 'expand' : 'collapse';
                                $collapsible.toggleClass( 'mw-collapsed' );
-                               toggleElement( $collapsible, action, $that );
+                               toggleElement( $collapsible, action, $that, options );
 
                        };
 
+               // Return if it has been enabled already.
+               if ( $collapsible.data( 'mw-made-collapsible' ) ) {
+                       return;
+               } else {
+                       $collapsible.data( 'mw-made-collapsible', true );
+               }
+
                // Use custom text or default ?
                if ( !collapsetext ) {
                        collapsetext = mw.msg( 'collapsible-collapse' );
@@ -223,46 +266,41 @@ $.fn.makeCollapsible = function () {
                }
 
                // Create toggle link with a space around the brackets (&nbsp;[text]&nbsp;)
-               var $toggleLink =
+               $toggleLink =
                        $( '<a href="#"></a>' )
                                .text( collapsetext )
                                .wrap( '<span class="mw-collapsible-toggle"></span>' )
-                               .parent()
-                               .prepend( '&nbsp;[' )
-                               .append( ']&nbsp;' )
-                               .on( 'click.mw-collapse', function ( e ) {
-                                       toggleLinkDefault( this, e );
-                               } );
-
-               // Return if it has been enabled already.
-               if ( $that.hasClass( 'mw-made-collapsible' ) ) {
-                       return;
-               } else {
-                       $that.addClass( 'mw-made-collapsible' );
-               }
+                                       .parent()
+                                       .prepend( '&nbsp;[' )
+                                       .append( ']&nbsp;' )
+                                       .on( 'click.mw-collapse', function ( e, options ) {
+                                               toggleLinkDefault( $(this), e, options );
+                                       } );
 
                // Check if this element has a custom position for the toggle link
                // (ie. outside the container or deeper inside the tree)
                // Then: Locate the custom toggle link(s) and bind them
-               if ( ( $that.attr( 'id' ) || '' ).indexOf( 'mw-customcollapsible-' ) === 0 ) {
+               if ( ( $collapsible.attr( 'id' ) || '' ).indexOf( 'mw-customcollapsible-' ) === 0 ) {
 
-                       var thatId = $that.attr( 'id' ),
-                               $customTogglers = $( '.' + thatId.replace( 'mw-customcollapsible', 'mw-customtoggle' ) );
-                       mw.log( lpx + 'Found custom collapsible: #' + thatId );
+                       collapsibleId = $collapsible.attr( 'id' );
+                       $customTogglers = $( '.' + collapsibleId.replace( 'mw-customcollapsible', 'mw-customtoggle' ) );
+                       mw.log( lpx + 'Found custom collapsible: #' + collapsibleId );
 
                        // Double check that there is actually a customtoggle link
                        if ( $customTogglers.length ) {
-                               $customTogglers.on( 'click.mw-collapse', function ( e ) {
-                                       toggleLinkCustom( $(this), e, $that );
+                               $customTogglers.on( 'click.mw-collapse', function ( e, options ) {
+                                       toggleLinkCustom( $(this), e, options, $collapsible );
                                } );
                        } else {
-                               mw.log( lpx + '#' + thatId + ': Missing toggler!' );
+                               mw.log( lpx + '#' + collapsibleId + ': Missing toggler!' );
                        }
 
                        // Initial state
-                       if ( $that.hasClass( 'mw-collapsed' ) ) {
-                               $that.removeClass( 'mw-collapsed' );
-                               toggleLinkCustom( $customTogglers, null, $that );
+                       if ( $collapsible.hasClass( 'mw-collapsed' ) ) {
+                               // Remove here so that the toggler goes in the right direction,
+                               // It re-adds the class.
+                               $collapsible.removeClass( 'mw-collapsed' );
+                               toggleLinkCustom( $customTogglers, null, { instantHide: true }, $collapsible );
                        }
 
                // If this is not a custom case, do the default:
@@ -270,23 +308,23 @@ $.fn.makeCollapsible = function () {
                } else {
 
                        // Elements are treated differently
-                       if ( $that.is( 'table' ) ) {
+                       if ( $collapsible.is( 'table' ) ) {
                                // The toggle-link will be in one the the cells (td or th) of the first row
-                               var $firstRowCells = $that.find( 'tr:first th, tr:first td' );
-                               $toggle = $firstRowCells.find( '> .mw-collapsible-toggle' );
+                               $firstItem = $collapsible.find( 'tr:first th, tr:first td' );
+                               $toggle = $firstItem.find( '> .mw-collapsible-toggle' );
 
                                // If theres no toggle link, add it to the last cell
                                if ( !$toggle.length ) {
-                                       $firstRowCells.eq(-1).prepend( $toggleLink );
+                                       $firstItem.eq(-1).prepend( $toggleLink );
                                } else {
-                                       $toggleLink = $toggle.off( 'click.mw-collapse' ).on( 'click.mw-collapse', function ( e ) {
-                                               toggleLinkPremade( $toggle, e );
+                                       $toggleLink = $toggle.off( 'click.mw-collapse' ).on( 'click.mw-collapse', function ( e, options ) {
+                                               toggleLinkPremade( $toggle, e, options );
                                        } );
                                }
 
-                       } else if ( $that.is( 'ul' ) || $that.is( 'ol' ) ) {
+                       } else if ( $collapsible.is( 'ul' ) || $collapsible.is( 'ol' ) ) {
                                // The toggle-link will be in the first list-item
-                               var $firstItem = $that.find( 'li:first' );
+                               $firstItem = $collapsible.find( 'li:first' );
                                $toggle = $firstItem.find( '> .mw-collapsible-toggle' );
 
                                // If theres no toggle link, add it
@@ -294,46 +332,47 @@ $.fn.makeCollapsible = function () {
                                        // Make sure the numeral order doesn't get messed up, force the first (soon to be second) item
                                        // to be "1". Except if the value-attribute is already used.
                                        // If no value was set WebKit returns "", Mozilla returns '-1', others return null or undefined.
-                                       var firstval = $firstItem.attr( 'value' );
+                                       firstval = $firstItem.attr( 'value' );
                                        if ( firstval === undefined || !firstval || firstval === '-1' || firstval === -1 ) {
                                                $firstItem.attr( 'value', '1' );
                                        }
-                                       $that.prepend( $toggleLink.wrap( '<li class="mw-collapsible-toggle-li"></li>' ).parent() );
+                                       $collapsible.prepend( $toggleLink.wrap( '<li class="mw-collapsible-toggle-li"></li>' ).parent() );
                                } else {
-                                       $toggleLink = $toggle.off( 'click.mw-collapse' ).on( 'click.mw-collapse', function ( e ) {
-                                               toggleLinkPremade( $toggle, e );
+                                       $toggleLink = $toggle.off( 'click.mw-collapse' ).on( 'click.mw-collapse', function ( e, options ) {
+                                               toggleLinkPremade( $toggle, e, options );
                                        } );
                                }
 
                        } else { // <div>, <p> etc.
 
                                // The toggle-link will be the first child of the element
-                               $toggle = $that.find( '> .mw-collapsible-toggle' );
+                               $toggle = $collapsible.find( '> .mw-collapsible-toggle' );
 
                                // If a direct child .content-wrapper does not exists, create it
-                               if ( !$that.find( '> .mw-collapsible-content' ).length ) {
-                                       $that.wrapInner( '<div class="mw-collapsible-content"></div>' );
+                               if ( !$collapsible.find( '> .mw-collapsible-content' ).length ) {
+                                       $collapsible.wrapInner( '<div class="mw-collapsible-content"></div>' );
                                }
 
                                // If theres no toggle link, add it
                                if ( !$toggle.length ) {
-                                       $that.prepend( $toggleLink );
+                                       $collapsible.prepend( $toggleLink );
                                } else {
-                                       $toggleLink = $toggle.off( 'click.mw-collapse' ).on( 'click.mw-collapse', function ( e ) {
-                                               toggleLinkPremade( $toggle, e );
+                                       $toggleLink = $toggle.off( 'click.mw-collapse' ).on( 'click.mw-collapse', function ( e, options ) {
+                                               toggleLinkPremade( $toggle, e, options );
                                        } );
                                }
                        }
                }
 
-               // Initial state (only for those that are not custom)
-               if ( $that.hasClass( 'mw-collapsed' ) && ( $that.attr( 'id' ) || '').indexOf( 'mw-customcollapsible-' ) !== 0 ) {
-                       $that.removeClass( 'mw-collapsed' );
+               // Initial state (only for those that are not custom,
+               // because the initial state of those has been taken care of already).
+               if ( $collapsible.hasClass( 'mw-collapsed' ) && ( $collapsible.attr( 'id' ) || '').indexOf( 'mw-customcollapsible-' ) !== 0 ) {
+                       $collapsible.removeClass( 'mw-collapsed' );
                        // The collapsible element could have multiple togglers
                        // To toggle the initial state only click one of them (ie. the first one, eq(0) )
                        // Else it would go like: hide,show,hide,show for each toggle link.
-                       toggleElement( $that, 'collapse', $toggleLink.eq(0), /* instantHide = */ true );
-                       $toggleLink.eq(0).click();
+                       // This is just like it would be in reality (only one toggle is clicked at a time).
+                       $toggleLink.eq( 0 ).trigger( 'click', [ { instantHide: true } ] );
                }
        } );
 };
index 3ef71d5..2940d6f 100644 (file)
 
                        for ( i = 0; i < len; i++ ) {
                                parser = false;
-                               sortType = $headers.eq( i ).data( 'sort-type' );
+                               sortType = $headers.eq( i ).data( 'sortType' );
                                if ( sortType !== undefined ) {
                                        parser = getParserById( sortType );
                                }
diff --git a/tests/jasmine/.htaccess b/tests/jasmine/.htaccess
deleted file mode 100644 (file)
index 605d2f4..0000000
+++ /dev/null
@@ -1 +0,0 @@
-Allow from all
diff --git a/tests/jasmine/SpecRunner.html b/tests/jasmine/SpecRunner.html
deleted file mode 100644 (file)
index 63d0fdf..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-<!DOCTYPE html>
-<html lang="en" dir="ltr">
-       <head>
-               <title>Jasmine Test Runner</title>
-               <meta charset="UTF-8" />
-               <link rel="stylesheet" type="text/css" href="lib/jasmine-1.0.1/jasmine.css">
-               <script src="lib/jasmine-1.0.1/jasmine.js"></script>
-               <script src="lib/jasmine-1.0.1/jasmine-html.js"></script>
-
-               <!-- include source files here... -->
-               <script src="../../load.php?debug=true&amp;lang=en&amp;modules=startup&amp;only=scripts&amp;skin=vector&amp;*"></script>
-               <script>
-               mw.loader.load( ['mediawiki.jqueryMsg'] );
-               </script>
-
-               <!-- insert test data files here -->
-               <script src="spec/mediawiki.jqueryMsg.spec.data.js"></script>
-
-               <!-- include spec files here... -->
-               <script src="spec/mediawiki.jqueryMsg.spec.js"></script>
-       </head>
-<body>
-       <script>
-               jasmine.getEnv().addReporter( new jasmine.TrivialReporter() );
-               jasmine.getEnv().execute();
-       </script>
-</body>
-</html>
diff --git a/tests/jasmine/lib/jasmine-1.0.1/MIT.LICENSE b/tests/jasmine/lib/jasmine-1.0.1/MIT.LICENSE
deleted file mode 100644 (file)
index 1eb9b49..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-Copyright (c) 2008-2010 Pivotal Labs
-
-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.
diff --git a/tests/jasmine/lib/jasmine-1.0.1/jasmine-html.js b/tests/jasmine/lib/jasmine-1.0.1/jasmine-html.js
deleted file mode 100644 (file)
index 81402b9..0000000
+++ /dev/null
@@ -1,188 +0,0 @@
-jasmine.TrivialReporter = function(doc) {
-  this.document = doc || document;
-  this.suiteDivs = {};
-  this.logRunningSpecs = false;
-};
-
-jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) {
-  var el = document.createElement(type);
-
-  for (var i = 2; i < arguments.length; i++) {
-    var child = arguments[i];
-
-    if (typeof child === 'string') {
-      el.appendChild(document.createTextNode(child));
-    } else {
-      if (child) { el.appendChild(child); }
-    }
-  }
-
-  for (var attr in attrs) {
-    if (attr == "className") {
-      el[attr] = attrs[attr];
-    } else {
-      el.setAttribute(attr, attrs[attr]);
-    }
-  }
-
-  return el;
-};
-
-jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) {
-  var showPassed, showSkipped;
-
-  this.outerDiv = this.createDom('div', { className: 'jasmine_reporter' },
-      this.createDom('div', { className: 'banner' },
-        this.createDom('div', { className: 'logo' },
-            this.createDom('a', { href: 'http://pivotal.github.com/jasmine/', target: "_blank" }, "Jasmine"),
-            this.createDom('span', { className: 'version' }, runner.env.versionString())),
-        this.createDom('div', { className: 'options' },
-            "Show ",
-            showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }),
-            this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "),
-            showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }),
-            this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped")
-            )
-          ),
-
-      this.runnerDiv = this.createDom('div', { className: 'runner running' },
-          this.createDom('a', { className: 'run_spec', href: '?' }, "run all"),
-          this.runnerMessageSpan = this.createDom('span', {}, "Running..."),
-          this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, ""))
-      );
-
-  this.document.body.appendChild(this.outerDiv);
-
-  var suites = runner.suites();
-  for (var i = 0; i < suites.length; i++) {
-    var suite = suites[i];
-    var suiteDiv = this.createDom('div', { className: 'suite' },
-        this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"),
-        this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description));
-    this.suiteDivs[suite.id] = suiteDiv;
-    var parentDiv = this.outerDiv;
-    if (suite.parentSuite) {
-      parentDiv = this.suiteDivs[suite.parentSuite.id];
-    }
-    parentDiv.appendChild(suiteDiv);
-  }
-
-  this.startedAt = new Date();
-
-  var self = this;
-  showPassed.onclick = function(evt) {
-    if (showPassed.checked) {
-      self.outerDiv.className += ' show-passed';
-    } else {
-      self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, '');
-    }
-  };
-
-  showSkipped.onclick = function(evt) {
-    if (showSkipped.checked) {
-      self.outerDiv.className += ' show-skipped';
-    } else {
-      self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, '');
-    }
-  };
-};
-
-jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) {
-  var results = runner.results();
-  var className = (results.failedCount > 0) ? "runner failed" : "runner passed";
-  this.runnerDiv.setAttribute("class", className);
-  //do it twice for IE
-  this.runnerDiv.setAttribute("className", className);
-  var specs = runner.specs();
-  var specCount = 0;
-  for (var i = 0; i < specs.length; i++) {
-    if (this.specFilter(specs[i])) {
-      specCount++;
-    }
-  }
-  var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s");
-  message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s";
-  this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild);
-
-  this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString()));
-};
-
-jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) {
-  var results = suite.results();
-  var status = results.passed() ? 'passed' : 'failed';
-  if (results.totalCount == 0) { // todo: change this to check results.skipped
-    status = 'skipped';
-  }
-  this.suiteDivs[suite.id].className += " " + status;
-};
-
-jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) {
-  if (this.logRunningSpecs) {
-    this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...');
-  }
-};
-
-jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) {
-  var results = spec.results();
-  var status = results.passed() ? 'passed' : 'failed';
-  if (results.skipped) {
-    status = 'skipped';
-  }
-  var specDiv = this.createDom('div', { className: 'spec '  + status },
-      this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"),
-      this.createDom('a', {
-        className: 'description',
-        href: '?spec=' + encodeURIComponent(spec.getFullName()),
-        title: spec.getFullName()
-      }, spec.description));
-
-
-  var resultItems = results.getItems();
-  var messagesDiv = this.createDom('div', { className: 'messages' });
-  for (var i = 0; i < resultItems.length; i++) {
-    var result = resultItems[i];
-
-    if (result.type == 'log') {
-      messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString()));
-    } else if (result.type == 'expect' && result.passed && !result.passed()) {
-      messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message));
-
-      if (result.trace.stack) {
-        messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack));
-      }
-    }
-  }
-
-  if (messagesDiv.childNodes.length > 0) {
-    specDiv.appendChild(messagesDiv);
-  }
-
-  this.suiteDivs[spec.suite.id].appendChild(specDiv);
-};
-
-jasmine.TrivialReporter.prototype.log = function() {
-  var console = jasmine.getGlobal().console;
-  if (console && console.log) {
-    if (console.log.apply) {
-      console.log.apply(console, arguments);
-    } else {
-      console.log(arguments); // ie fix: console.log.apply doesn't exist on ie
-    }
-  }
-};
-
-jasmine.TrivialReporter.prototype.getLocation = function() {
-  return this.document.location;
-};
-
-jasmine.TrivialReporter.prototype.specFilter = function(spec) {
-  var paramMap = {};
-  var params = this.getLocation().search.substring(1).split('&');
-  for (var i = 0; i < params.length; i++) {
-    var p = params[i].split('=');
-    paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]);
-  }
-
-  if (!paramMap["spec"]) return true;
-  return spec.getFullName().indexOf(paramMap["spec"]) == 0;
-};
diff --git a/tests/jasmine/lib/jasmine-1.0.1/jasmine.css b/tests/jasmine/lib/jasmine-1.0.1/jasmine.css
deleted file mode 100644 (file)
index 6583fe7..0000000
+++ /dev/null
@@ -1,166 +0,0 @@
-body {
-  font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif;
-}
-
-
-.jasmine_reporter a:visited, .jasmine_reporter a {
-  color: #303; 
-}
-
-.jasmine_reporter a:hover, .jasmine_reporter a:active {
-  color: blue; 
-}
-
-.run_spec {
-  float:right;
-  padding-right: 5px;
-  font-size: .8em;
-  text-decoration: none;
-}
-
-.jasmine_reporter {
-  margin: 0 5px;
-}
-
-.banner {
-  color: #303;
-  background-color: #fef;
-  padding: 5px;
-}
-
-.logo {
-  float: left;
-  font-size: 1.1em;
-  padding-left: 5px;
-}
-
-.logo .version {
-  font-size: .6em;
-  padding-left: 1em;
-}
-
-.runner.running {
-  background-color: yellow;
-}
-
-
-.options {
-  text-align: right;
-  font-size: .8em;
-}
-
-
-
-
-.suite {
-  border: 1px outset gray;
-  margin: 5px 0;
-  padding-left: 1em;
-}
-
-.suite .suite {
-  margin: 5px; 
-}
-
-.suite.passed {
-  background-color: #dfd;
-}
-
-.suite.failed {
-  background-color: #fdd;
-}
-
-.spec {
-  margin: 5px;
-  padding-left: 1em;
-  clear: both;
-}
-
-.spec.failed, .spec.passed, .spec.skipped {
-  padding-bottom: 5px;
-  border: 1px solid gray;
-}
-
-.spec.failed {
-  background-color: #fbb;
-  border-color: red;
-}
-
-.spec.passed {
-  background-color: #bfb;
-  border-color: green;
-}
-
-.spec.skipped {
-  background-color: #bbb;
-}
-
-.messages {
-  border-left: 1px dashed gray;
-  padding-left: 1em;
-  padding-right: 1em;
-}
-
-.passed {
-  background-color: #cfc;
-  display: none;
-}
-
-.failed {
-  background-color: #fbb;
-}
-
-.skipped {
-  color: #777;
-  background-color: #eee;
-  display: none;
-}
-
-
-/*.resultMessage {*/
-  /*white-space: pre;*/
-/*}*/
-
-.resultMessage span.result {
-  display: block;
-  line-height: 2em;
-  color: black;
-}
-
-.resultMessage .mismatch {
-  color: black;
-}
-
-.stackTrace {
-  white-space: pre;
-  font-size: .8em;
-  margin-left: 10px;
-  max-height: 5em;
-  overflow: auto;
-  border: 1px inset red;
-  padding: 1em;
-  background: #eef;
-}
-
-.finished-at {
-  padding-left: 1em;
-  font-size: .6em;
-}
-
-.show-passed .passed,
-.show-skipped .skipped {
-  display: block;
-}
-
-
-#jasmine_content {
-  position:fixed;
-  right: 100%;
-}
-
-.runner {
-  border: 1px solid gray;
-  display: block;
-  margin: 5px 0;
-  padding: 2px 0 2px 10px;
-}
diff --git a/tests/jasmine/lib/jasmine-1.0.1/jasmine.js b/tests/jasmine/lib/jasmine-1.0.1/jasmine.js
deleted file mode 100644 (file)
index 964f99e..0000000
+++ /dev/null
@@ -1,2421 +0,0 @@
-/**
- * Top level namespace for Jasmine, a lightweight JavaScript BDD/spec/testing framework.
- *
- * @namespace
- */
-var jasmine = {};
-
-/**
- * @private
- */
-jasmine.unimplementedMethod_ = function() {
-  throw new Error("unimplemented method");
-};
-
-/**
- * Use <code>jasmine.undefined</code> instead of <code>undefined</code>, since <code>undefined</code> is just
- * a plain old variable and may be redefined by somebody else.
- *
- * @private
- */
-jasmine.undefined = jasmine.___undefined___;
-
-/**
- * Default interval in milliseconds for event loop yields (e.g. to allow network activity or to refresh the screen with the HTML-based runner). Small values here may result in slow test running. Zero means no updates until all tests have completed.
- *
- */
-jasmine.DEFAULT_UPDATE_INTERVAL = 250;
-
-/**
- * Default timeout interval in milliseconds for waitsFor() blocks.
- */
-jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000;
-
-jasmine.getGlobal = function() {
-  function getGlobal() {
-    return this;
-  }
-
-  return getGlobal();
-};
-
-/**
- * Allows for bound functions to be compared.  Internal use only.
- *
- * @ignore
- * @private
- * @param base {Object} bound 'this' for the function
- * @param name {Function} function to find
- */
-jasmine.bindOriginal_ = function(base, name) {
-  var original = base[name];
-  if (original.apply) {
-    return function() {
-      return original.apply(base, arguments);
-    };
-  } else {
-    // IE support
-    return jasmine.getGlobal()[name];
-  }
-};
-
-jasmine.setTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'setTimeout');
-jasmine.clearTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearTimeout');
-jasmine.setInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'setInterval');
-jasmine.clearInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearInterval');
-
-jasmine.MessageResult = function(values) {
-  this.type = 'log';
-  this.values = values;
-  this.trace = new Error(); // todo: test better
-};
-
-jasmine.MessageResult.prototype.toString = function() {
-  var text = "";
-  for(var i = 0; i < this.values.length; i++) {
-    if (i > 0) text += " ";
-    if (jasmine.isString_(this.values[i])) {
-      text += this.values[i];
-    } else {
-      text += jasmine.pp(this.values[i]);
-    }
-  }
-  return text;
-};
-
-jasmine.ExpectationResult = function(params) {
-  this.type = 'expect';
-  this.matcherName = params.matcherName;
-  this.passed_ = params.passed;
-  this.expected = params.expected;
-  this.actual = params.actual;
-
-  this.message = this.passed_ ? 'Passed.' : params.message;
-  this.trace = this.passed_ ? '' : new Error(this.message);
-};
-
-jasmine.ExpectationResult.prototype.toString = function () {
-  return this.message;
-};
-
-jasmine.ExpectationResult.prototype.passed = function () {
-  return this.passed_;
-};
-
-/**
- * Getter for the Jasmine environment. Ensures one gets created
- */
-jasmine.getEnv = function() {
-  return jasmine.currentEnv_ = jasmine.currentEnv_ || new jasmine.Env();
-};
-
-/**
- * @ignore
- * @private
- * @param value
- * @returns {Boolean}
- */
-jasmine.isArray_ = function(value) {
-  return jasmine.isA_("Array", value);  
-};
-
-/**
- * @ignore
- * @private
- * @param value
- * @returns {Boolean}
- */
-jasmine.isString_ = function(value) {
-  return jasmine.isA_("String", value);
-};
-
-/**
- * @ignore
- * @private
- * @param value
- * @returns {Boolean}
- */
-jasmine.isNumber_ = function(value) {
-  return jasmine.isA_("Number", value);
-};
-
-/**
- * @ignore
- * @private
- * @param {String} typeName
- * @param value
- * @returns {Boolean}
- */
-jasmine.isA_ = function(typeName, value) {
-  return Object.prototype.toString.apply(value) === '[object ' + typeName + ']';
-};
-
-/**
- * Pretty printer for expecations.  Takes any object and turns it into a human-readable string.
- *
- * @param value {Object} an object to be outputted
- * @returns {String}
- */
-jasmine.pp = function(value) {
-  var stringPrettyPrinter = new jasmine.StringPrettyPrinter();
-  stringPrettyPrinter.format(value);
-  return stringPrettyPrinter.string;
-};
-
-/**
- * Returns true if the object is a DOM Node.
- *
- * @param {Object} obj object to check
- * @returns {Boolean}
- */
-jasmine.isDomNode = function(obj) {
-  return obj['nodeType'] > 0;
-};
-
-/**
- * Returns a matchable 'generic' object of the class type.  For use in expecations of type when values don't matter.
- *
- * @example
- * // don't care about which function is passed in, as long as it's a function
- * expect(mySpy).toHaveBeenCalledWith(jasmine.any(Function));
- *
- * @param {Class} clazz
- * @returns matchable object of the type clazz
- */
-jasmine.any = function(clazz) {
-  return new jasmine.Matchers.Any(clazz);
-};
-
-/**
- * Jasmine Spies are test doubles that can act as stubs, spies, fakes or when used in an expecation, mocks.
- *
- * Spies should be created in test setup, before expectations.  They can then be checked, using the standard Jasmine
- * expectation syntax. Spies can be checked if they were called or not and what the calling params were.
- *
- * A Spy has the following fields: wasCalled, callCount, mostRecentCall, and argsForCall (see docs).
- *
- * Spies are torn down at the end of every spec.
- *
- * Note: Do <b>not</b> call new jasmine.Spy() directly - a spy must be created using spyOn, jasmine.createSpy or jasmine.createSpyObj.
- *
- * @example
- * // a stub
- * var myStub = jasmine.createSpy('myStub');  // can be used anywhere
- *
- * // spy example
- * var foo = {
- *   not: function(bool) { return !bool; }
- * }
- *
- * // actual foo.not will not be called, execution stops
- * spyOn(foo, 'not');
-
- // foo.not spied upon, execution will continue to implementation
- * spyOn(foo, 'not').andCallThrough();
- *
- * // fake example
- * var foo = {
- *   not: function(bool) { return !bool; }
- * }
- *
- * // foo.not(val) will return val
- * spyOn(foo, 'not').andCallFake(function(value) {return value;});
- *
- * // mock example
- * foo.not(7 == 7);
- * expect(foo.not).toHaveBeenCalled();
- * expect(foo.not).toHaveBeenCalledWith(true);
- *
- * @constructor
- * @see spyOn, jasmine.createSpy, jasmine.createSpyObj
- * @param {String} name
- */
-jasmine.Spy = function(name) {
-  /**
-   * The name of the spy, if provided.
-   */
-  this.identity = name || 'unknown';
-  /**
-   *  Is this Object a spy?
-   */
-  this.isSpy = true;
-  /**
-   * The actual function this spy stubs.
-   */
-  this.plan = function() {
-  };
-  /**
-   * Tracking of the most recent call to the spy.
-   * @example
-   * var mySpy = jasmine.createSpy('foo');
-   * mySpy(1, 2);
-   * mySpy.mostRecentCall.args = [1, 2];
-   */
-  this.mostRecentCall = {};
-
-  /**
-   * Holds arguments for each call to the spy, indexed by call count
-   * @example
-   * var mySpy = jasmine.createSpy('foo');
-   * mySpy(1, 2);
-   * mySpy(7, 8);
-   * mySpy.mostRecentCall.args = [7, 8];
-   * mySpy.argsForCall[0] = [1, 2];
-   * mySpy.argsForCall[1] = [7, 8];
-   */
-  this.argsForCall = [];
-  this.calls = [];
-};
-
-/**
- * Tells a spy to call through to the actual implemenatation.
- *
- * @example
- * var foo = {
- *   bar: function() { // do some stuff }
- * }
- *
- * // defining a spy on an existing property: foo.bar
- * spyOn(foo, 'bar').andCallThrough();
- */
-jasmine.Spy.prototype.andCallThrough = function() {
-  this.plan = this.originalValue;
-  return this;
-};
-
-/**
- * For setting the return value of a spy.
- *
- * @example
- * // defining a spy from scratch: foo() returns 'baz'
- * var foo = jasmine.createSpy('spy on foo').andReturn('baz');
- *
- * // defining a spy on an existing property: foo.bar() returns 'baz'
- * spyOn(foo, 'bar').andReturn('baz');
- *
- * @param {Object} value
- */
-jasmine.Spy.prototype.andReturn = function(value) {
-  this.plan = function() {
-    return value;
-  };
-  return this;
-};
-
-/**
- * For throwing an exception when a spy is called.
- *
- * @example
- * // defining a spy from scratch: foo() throws an exception w/ message 'ouch'
- * var foo = jasmine.createSpy('spy on foo').andThrow('baz');
- *
- * // defining a spy on an existing property: foo.bar() throws an exception w/ message 'ouch'
- * spyOn(foo, 'bar').andThrow('baz');
- *
- * @param {String} exceptionMsg
- */
-jasmine.Spy.prototype.andThrow = function(exceptionMsg) {
-  this.plan = function() {
-    throw exceptionMsg;
-  };
-  return this;
-};
-
-/**
- * Calls an alternate implementation when a spy is called.
- *
- * @example
- * var baz = function() {
- *   // do some stuff, return something
- * }
- * // defining a spy from scratch: foo() calls the function baz
- * var foo = jasmine.createSpy('spy on foo').andCall(baz);
- *
- * // defining a spy on an existing property: foo.bar() calls an anonymnous function
- * spyOn(foo, 'bar').andCall(function() { return 'baz';} );
- *
- * @param {Function} fakeFunc
- */
-jasmine.Spy.prototype.andCallFake = function(fakeFunc) {
-  this.plan = fakeFunc;
-  return this;
-};
-
-/**
- * Resets all of a spy's the tracking variables so that it can be used again.
- *
- * @example
- * spyOn(foo, 'bar');
- *
- * foo.bar();
- *
- * expect(foo.bar.callCount).toEqual(1);
- *
- * foo.bar.reset();
- *
- * expect(foo.bar.callCount).toEqual(0);
- */
-jasmine.Spy.prototype.reset = function() {
-  this.wasCalled = false;
-  this.callCount = 0;
-  this.argsForCall = [];
-  this.calls = [];
-  this.mostRecentCall = {};
-};
-
-jasmine.createSpy = function(name) {
-
-  var spyObj = function() {
-    spyObj.wasCalled = true;
-    spyObj.callCount++;
-    var args = jasmine.util.argsToArray(arguments);
-    spyObj.mostRecentCall.object = this;
-    spyObj.mostRecentCall.args = args;
-    spyObj.argsForCall.push(args);
-    spyObj.calls.push({object: this, args: args});
-    return spyObj.plan.apply(this, arguments);
-  };
-
-  var spy = new jasmine.Spy(name);
-
-  for (var prop in spy) {
-    spyObj[prop] = spy[prop];
-  }
-
-  spyObj.reset();
-
-  return spyObj;
-};
-
-/**
- * Determines whether an object is a spy.
- *
- * @param {jasmine.Spy|Object} putativeSpy
- * @returns {Boolean}
- */
-jasmine.isSpy = function(putativeSpy) {
-  return putativeSpy && putativeSpy.isSpy;
-};
-
-/**
- * Creates a more complicated spy: an Object that has every property a function that is a spy.  Used for stubbing something
- * large in one call.
- *
- * @param {String} baseName name of spy class
- * @param {Array} methodNames array of names of methods to make spies
- */
-jasmine.createSpyObj = function(baseName, methodNames) {
-  if (!jasmine.isArray_(methodNames) || methodNames.length == 0) {
-    throw new Error('createSpyObj requires a non-empty array of method names to create spies for');
-  }
-  var obj = {};
-  for (var i = 0; i < methodNames.length; i++) {
-    obj[methodNames[i]] = jasmine.createSpy(baseName + '.' + methodNames[i]);
-  }
-  return obj;
-};
-
-/**
- * All parameters are pretty-printed and concatenated together, then written to the current spec's output.
- *
- * Be careful not to leave calls to <code>jasmine.log</code> in production code.
- */
-jasmine.log = function() {
-  var spec = jasmine.getEnv().currentSpec;
-  spec.log.apply(spec, arguments);
-};
-
-/**
- * Function that installs a spy on an existing object's method name.  Used within a Spec to create a spy.
- *
- * @example
- * // spy example
- * var foo = {
- *   not: function(bool) { return !bool; }
- * }
- * spyOn(foo, 'not'); // actual foo.not will not be called, execution stops
- *
- * @see jasmine.createSpy
- * @param obj
- * @param methodName
- * @returns a Jasmine spy that can be chained with all spy methods
- */
-var spyOn = function(obj, methodName) {
-  return jasmine.getEnv().currentSpec.spyOn(obj, methodName);
-};
-
-/**
- * Creates a Jasmine spec that will be added to the current suite.
- *
- * // TODO: pending tests
- *
- * @example
- * it('should be true', function() {
- *   expect(true).toEqual(true);
- * });
- *
- * @param {String} desc description of this specification
- * @param {Function} func defines the preconditions and expectations of the spec
- */
-var it = function(desc, func) {
-  return jasmine.getEnv().it(desc, func);
-};
-
-/**
- * Creates a <em>disabled</em> Jasmine spec.
- *
- * A convenience method that allows existing specs to be disabled temporarily during development.
- *
- * @param {String} desc description of this specification
- * @param {Function} func defines the preconditions and expectations of the spec
- */
-var xit = function(desc, func) {
-  return jasmine.getEnv().xit(desc, func);
-};
-
-/**
- * Starts a chain for a Jasmine expectation.
- *
- * It is passed an Object that is the actual value and should chain to one of the many
- * jasmine.Matchers functions.
- *
- * @param {Object} actual Actual value to test against and expected value
- */
-var expect = function(actual) {
-  return jasmine.getEnv().currentSpec.expect(actual);
-};
-
-/**
- * Defines part of a jasmine spec.  Used in cominbination with waits or waitsFor in asynchrnous specs.
- *
- * @param {Function} func Function that defines part of a jasmine spec.
- */
-var runs = function(func) {
-  jasmine.getEnv().currentSpec.runs(func);
-};
-
-/**
- * Waits a fixed time period before moving to the next block.
- *
- * @deprecated Use waitsFor() instead
- * @param {Number} timeout milliseconds to wait
- */
-var waits = function(timeout) {
-  jasmine.getEnv().currentSpec.waits(timeout);
-};
-
-/**
- * Waits for the latchFunction to return true before proceeding to the next block.
- *
- * @param {Function} latchFunction
- * @param {String} optional_timeoutMessage
- * @param {Number} optional_timeout
- */
-var waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) {
-  jasmine.getEnv().currentSpec.waitsFor.apply(jasmine.getEnv().currentSpec, arguments);
-};
-
-/**
- * A function that is called before each spec in a suite.
- *
- * Used for spec setup, including validating assumptions.
- *
- * @param {Function} beforeEachFunction
- */
-var beforeEach = function(beforeEachFunction) {
-  jasmine.getEnv().beforeEach(beforeEachFunction);
-};
-
-/**
- * A function that is called after each spec in a suite.
- *
- * Used for restoring any state that is hijacked during spec execution.
- *
- * @param {Function} afterEachFunction
- */
-var afterEach = function(afterEachFunction) {
-  jasmine.getEnv().afterEach(afterEachFunction);
-};
-
-/**
- * Defines a suite of specifications.
- *
- * Stores the description and all defined specs in the Jasmine environment as one suite of specs. Variables declared
- * are accessible by calls to beforeEach, it, and afterEach. Describe blocks can be nested, allowing for specialization
- * of setup in some tests.
- *
- * @example
- * // TODO: a simple suite
- *
- * // TODO: a simple suite with a nested describe block
- *
- * @param {String} description A string, usually the class under test.
- * @param {Function} specDefinitions function that defines several specs.
- */
-var describe = function(description, specDefinitions) {
-  return jasmine.getEnv().describe(description, specDefinitions);
-};
-
-/**
- * Disables a suite of specifications.  Used to disable some suites in a file, or files, temporarily during development.
- *
- * @param {String} description A string, usually the class under test.
- * @param {Function} specDefinitions function that defines several specs.
- */
-var xdescribe = function(description, specDefinitions) {
-  return jasmine.getEnv().xdescribe(description, specDefinitions);
-};
-
-
-// Provide the XMLHttpRequest class for IE 5.x-6.x:
-jasmine.XmlHttpRequest = (typeof XMLHttpRequest == "undefined") ? function() {
-  try {
-    return new ActiveXObject("Msxml2.XMLHTTP.6.0");
-  } catch(e) {
-  }
-  try {
-    return new ActiveXObject("Msxml2.XMLHTTP.3.0");
-  } catch(e) {
-  }
-  try {
-    return new ActiveXObject("Msxml2.XMLHTTP");
-  } catch(e) {
-  }
-  try {
-    return new ActiveXObject("Microsoft.XMLHTTP");
-  } catch(e) {
-  }
-  throw new Error("This browser does not support XMLHttpRequest.");
-} : XMLHttpRequest;
-/**
- * @namespace
- */
-jasmine.util = {};
-
-/**
- * Declare that a child class inherit it's prototype from the parent class.
- *
- * @private
- * @param {Function} childClass
- * @param {Function} parentClass
- */
-jasmine.util.inherit = function(childClass, parentClass) {
-  /**
-   * @private
-   */
-  var subclass = function() {
-  };
-  subclass.prototype = parentClass.prototype;
-  childClass.prototype = new subclass;
-};
-
-jasmine.util.formatException = function(e) {
-  var lineNumber;
-  if (e.line) {
-    lineNumber = e.line;
-  }
-  else if (e.lineNumber) {
-    lineNumber = e.lineNumber;
-  }
-
-  var file;
-
-  if (e.sourceURL) {
-    file = e.sourceURL;
-  }
-  else if (e.fileName) {
-    file = e.fileName;
-  }
-
-  var message = (e.name && e.message) ? (e.name + ': ' + e.message) : e.toString();
-
-  if (file && lineNumber) {
-    message += ' in ' + file + ' (line ' + lineNumber + ')';
-  }
-
-  return message;
-};
-
-jasmine.util.htmlEscape = function(str) {
-  if (!str) return str;
-  return str.replace(/&/g, '&amp;')
-    .replace(/</g, '&lt;')
-    .replace(/>/g, '&gt;');
-};
-
-jasmine.util.argsToArray = function(args) {
-  var arrayOfArgs = [];
-  for (var i = 0; i < args.length; i++) arrayOfArgs.push(args[i]);
-  return arrayOfArgs;
-};
-
-jasmine.util.extend = function(destination, source) {
-  for (var property in source) destination[property] = source[property];
-  return destination;
-};
-
-/**
- * Environment for Jasmine
- *
- * @constructor
- */
-jasmine.Env = function() {
-  this.currentSpec = null;
-  this.currentSuite = null;
-  this.currentRunner_ = new jasmine.Runner(this);
-
-  this.reporter = new jasmine.MultiReporter();
-
-  this.updateInterval = jasmine.DEFAULT_UPDATE_INTERVAL;
-  this.defaultTimeoutInterval = jasmine.DEFAULT_TIMEOUT_INTERVAL;
-  this.lastUpdate = 0;
-  this.specFilter = function() {
-    return true;
-  };
-
-  this.nextSpecId_ = 0;
-  this.nextSuiteId_ = 0;
-  this.equalityTesters_ = [];
-
-  // wrap matchers
-  this.matchersClass = function() {
-    jasmine.Matchers.apply(this, arguments);
-  };
-  jasmine.util.inherit(this.matchersClass, jasmine.Matchers);
-
-  jasmine.Matchers.wrapInto_(jasmine.Matchers.prototype, this.matchersClass);
-};
-
-
-jasmine.Env.prototype.setTimeout = jasmine.setTimeout;
-jasmine.Env.prototype.clearTimeout = jasmine.clearTimeout;
-jasmine.Env.prototype.setInterval = jasmine.setInterval;
-jasmine.Env.prototype.clearInterval = jasmine.clearInterval;
-
-/**
- * @returns an object containing jasmine version build info, if set.
- */
-jasmine.Env.prototype.version = function () {
-  if (jasmine.version_) {
-    return jasmine.version_;
-  } else {
-    throw new Error('Version not set');
-  }
-};
-
-/**
- * @returns string containing jasmine version build info, if set.
- */
-jasmine.Env.prototype.versionString = function() {
-  if (jasmine.version_) {
-    var version = this.version();
-    return version.major + "." + version.minor + "." + version.build + " revision " + version.revision;
-  } else {
-    return "version unknown";
-  }
-};
-
-/**
- * @returns a sequential integer starting at 0
- */
-jasmine.Env.prototype.nextSpecId = function () {
-  return this.nextSpecId_++;
-};
-
-/**
- * @returns a sequential integer starting at 0
- */
-jasmine.Env.prototype.nextSuiteId = function () {
-  return this.nextSuiteId_++;
-};
-
-/**
- * Register a reporter to receive status updates from Jasmine.
- * @param {jasmine.Reporter} reporter An object which will receive status updates.
- */
-jasmine.Env.prototype.addReporter = function(reporter) {
-  this.reporter.addReporter(reporter);
-};
-
-jasmine.Env.prototype.execute = function() {
-  this.currentRunner_.execute();
-};
-
-jasmine.Env.prototype.describe = function(description, specDefinitions) {
-  var suite = new jasmine.Suite(this, description, specDefinitions, this.currentSuite);
-
-  var parentSuite = this.currentSuite;
-  if (parentSuite) {
-    parentSuite.add(suite);
-  } else {
-    this.currentRunner_.add(suite);
-  }
-
-  this.currentSuite = suite;
-
-  var declarationError = null;
-  try {
-    specDefinitions.call(suite);
-  } catch(e) {
-    declarationError = e;
-  }
-
-  this.currentSuite = parentSuite;
-
-  if (declarationError) {
-    this.it("encountered a declaration exception", function() {
-      throw declarationError;
-    });
-  }
-
-  return suite;
-};
-
-jasmine.Env.prototype.beforeEach = function(beforeEachFunction) {
-  if (this.currentSuite) {
-    this.currentSuite.beforeEach(beforeEachFunction);
-  } else {
-    this.currentRunner_.beforeEach(beforeEachFunction);
-  }
-};
-
-jasmine.Env.prototype.currentRunner = function () {
-  return this.currentRunner_;
-};
-
-jasmine.Env.prototype.afterEach = function(afterEachFunction) {
-  if (this.currentSuite) {
-    this.currentSuite.afterEach(afterEachFunction);
-  } else {
-    this.currentRunner_.afterEach(afterEachFunction);
-  }
-
-};
-
-jasmine.Env.prototype.xdescribe = function(desc, specDefinitions) {
-  return {
-    execute: function() {
-    }
-  };
-};
-
-jasmine.Env.prototype.it = function(description, func) {
-  var spec = new jasmine.Spec(this, this.currentSuite, description);
-  this.currentSuite.add(spec);
-  this.currentSpec = spec;
-
-  if (func) {
-    spec.runs(func);
-  }
-
-  return spec;
-};
-
-jasmine.Env.prototype.xit = function(desc, func) {
-  return {
-    id: this.nextSpecId(),
-    runs: function() {
-    }
-  };
-};
-
-jasmine.Env.prototype.compareObjects_ = function(a, b, mismatchKeys, mismatchValues) {
-  if (a.__Jasmine_been_here_before__ === b && b.__Jasmine_been_here_before__ === a) {
-    return true;
-  }
-
-  a.__Jasmine_been_here_before__ = b;
-  b.__Jasmine_been_here_before__ = a;
-
-  var hasKey = function(obj, keyName) {
-    return obj != null && obj[keyName] !== jasmine.undefined;
-  };
-
-  for (var property in b) {
-    if (!hasKey(a, property) && hasKey(b, property)) {
-      mismatchKeys.push("expected has key '" + property + "', but missing from actual.");
-    }
-  }
-  for (property in a) {
-    if (!hasKey(b, property) && hasKey(a, property)) {
-      mismatchKeys.push("expected missing key '" + property + "', but present in actual.");
-    }
-  }
-  for (property in b) {
-    if (property == '__Jasmine_been_here_before__') continue;
-    if (!this.equals_(a[property], b[property], mismatchKeys, mismatchValues)) {
-      mismatchValues.push("'" + property + "' was '" + (b[property] ? jasmine.util.htmlEscape(b[property].toString()) : b[property]) + "' in expected, but was '" + (a[property] ? jasmine.util.htmlEscape(a[property].toString()) : a[property]) + "' in actual.");
-    }
-  }
-
-  if (jasmine.isArray_(a) && jasmine.isArray_(b) && a.length != b.length) {
-    mismatchValues.push("arrays were not the same length");
-  }
-
-  delete a.__Jasmine_been_here_before__;
-  delete b.__Jasmine_been_here_before__;
-  return (mismatchKeys.length == 0 && mismatchValues.length == 0);
-};
-
-jasmine.Env.prototype.equals_ = function(a, b, mismatchKeys, mismatchValues) {
-  mismatchKeys = mismatchKeys || [];
-  mismatchValues = mismatchValues || [];
-
-  for (var i = 0; i < this.equalityTesters_.length; i++) {
-    var equalityTester = this.equalityTesters_[i];
-    var result = equalityTester(a, b, this, mismatchKeys, mismatchValues);
-    if (result !== jasmine.undefined) return result;
-  }
-
-  if (a === b) return true;
-
-  if (a === jasmine.undefined || a === null || b === jasmine.undefined || b === null) {
-    return (a == jasmine.undefined && b == jasmine.undefined);
-  }
-
-  if (jasmine.isDomNode(a) && jasmine.isDomNode(b)) {
-    return a === b;
-  }
-
-  if (a instanceof Date && b instanceof Date) {
-    return a.getTime() == b.getTime();
-  }
-
-  if (a instanceof jasmine.Matchers.Any) {
-    return a.matches(b);
-  }
-
-  if (b instanceof jasmine.Matchers.Any) {
-    return b.matches(a);
-  }
-
-  if (jasmine.isString_(a) && jasmine.isString_(b)) {
-    return (a == b);
-  }
-
-  if (jasmine.isNumber_(a) && jasmine.isNumber_(b)) {
-    return (a == b);
-  }
-
-  if (typeof a === "object" && typeof b === "object") {
-    return this.compareObjects_(a, b, mismatchKeys, mismatchValues);
-  }
-
-  //Straight check
-  return (a === b);
-};
-
-jasmine.Env.prototype.contains_ = function(haystack, needle) {
-  if (jasmine.isArray_(haystack)) {
-    for (var i = 0; i < haystack.length; i++) {
-      if (this.equals_(haystack[i], needle)) return true;
-    }
-    return false;
-  }
-  return haystack.indexOf(needle) >= 0;
-};
-
-jasmine.Env.prototype.addEqualityTester = function(equalityTester) {
-  this.equalityTesters_.push(equalityTester);
-};
-/** No-op base class for Jasmine reporters.
- *
- * @constructor
- */
-jasmine.Reporter = function() {
-};
-
-//noinspection JSUnusedLocalSymbols
-jasmine.Reporter.prototype.reportRunnerStarting = function(runner) {
-};
-
-//noinspection JSUnusedLocalSymbols
-jasmine.Reporter.prototype.reportRunnerResults = function(runner) {
-};
-
-//noinspection JSUnusedLocalSymbols
-jasmine.Reporter.prototype.reportSuiteResults = function(suite) {
-};
-
-//noinspection JSUnusedLocalSymbols
-jasmine.Reporter.prototype.reportSpecStarting = function(spec) {
-};
-
-//noinspection JSUnusedLocalSymbols
-jasmine.Reporter.prototype.reportSpecResults = function(spec) {
-};
-
-//noinspection JSUnusedLocalSymbols
-jasmine.Reporter.prototype.log = function(str) {
-};
-
-/**
- * Blocks are functions with executable code that make up a spec.
- *
- * @constructor
- * @param {jasmine.Env} env
- * @param {Function} func
- * @param {jasmine.Spec} spec
- */
-jasmine.Block = function(env, func, spec) {
-  this.env = env;
-  this.func = func;
-  this.spec = spec;
-};
-
-jasmine.Block.prototype.execute = function(onComplete) {  
-  try {
-    this.func.apply(this.spec);
-  } catch (e) {
-    this.spec.fail(e);
-  }
-  onComplete();
-};
-/** JavaScript API reporter.
- *
- * @constructor
- */
-jasmine.JsApiReporter = function() {
-  this.started = false;
-  this.finished = false;
-  this.suites_ = [];
-  this.results_ = {};
-};
-
-jasmine.JsApiReporter.prototype.reportRunnerStarting = function(runner) {
-  this.started = true;
-  var suites = runner.topLevelSuites();
-  for (var i = 0; i < suites.length; i++) {
-    var suite = suites[i];
-    this.suites_.push(this.summarize_(suite));
-  }
-};
-
-jasmine.JsApiReporter.prototype.suites = function() {
-  return this.suites_;
-};
-
-jasmine.JsApiReporter.prototype.summarize_ = function(suiteOrSpec) {
-  var isSuite = suiteOrSpec instanceof jasmine.Suite;
-  var summary = {
-    id: suiteOrSpec.id,
-    name: suiteOrSpec.description,
-    type: isSuite ? 'suite' : 'spec',
-    children: []
-  };
-  
-  if (isSuite) {
-    var children = suiteOrSpec.children();
-    for (var i = 0; i < children.length; i++) {
-      summary.children.push(this.summarize_(children[i]));
-    }
-  }
-  return summary;
-};
-
-jasmine.JsApiReporter.prototype.results = function() {
-  return this.results_;
-};
-
-jasmine.JsApiReporter.prototype.resultsForSpec = function(specId) {
-  return this.results_[specId];
-};
-
-//noinspection JSUnusedLocalSymbols
-jasmine.JsApiReporter.prototype.reportRunnerResults = function(runner) {
-  this.finished = true;
-};
-
-//noinspection JSUnusedLocalSymbols
-jasmine.JsApiReporter.prototype.reportSuiteResults = function(suite) {
-};
-
-//noinspection JSUnusedLocalSymbols
-jasmine.JsApiReporter.prototype.reportSpecResults = function(spec) {
-  this.results_[spec.id] = {
-    messages: spec.results().getItems(),
-    result: spec.results().failedCount > 0 ? "failed" : "passed"
-  };
-};
-
-//noinspection JSUnusedLocalSymbols
-jasmine.JsApiReporter.prototype.log = function(str) {
-};
-
-jasmine.JsApiReporter.prototype.resultsForSpecs = function(specIds){
-  var results = {};
-  for (var i = 0; i < specIds.length; i++) {
-    var specId = specIds[i];
-    results[specId] = this.summarizeResult_(this.results_[specId]);
-  }
-  return results;
-};
-
-jasmine.JsApiReporter.prototype.summarizeResult_ = function(result){
-  var summaryMessages = [];
-  var messagesLength = result.messages.length;
-  for (var messageIndex = 0; messageIndex < messagesLength; messageIndex++) {
-    var resultMessage = result.messages[messageIndex];
-    summaryMessages.push({
-      text: resultMessage.type == 'log' ? resultMessage.toString() : jasmine.undefined,
-      passed: resultMessage.passed ? resultMessage.passed() : true,
-      type: resultMessage.type,
-      message: resultMessage.message,
-      trace: {
-        stack: resultMessage.passed && !resultMessage.passed() ? resultMessage.trace.stack : jasmine.undefined
-      }
-    });
-  }
-
-  return {
-    result : result.result,
-    messages : summaryMessages
-  };
-};
-
-/**
- * @constructor
- * @param {jasmine.Env} env
- * @param actual
- * @param {jasmine.Spec} spec
- */
-jasmine.Matchers = function(env, actual, spec, opt_isNot) {
-  this.env = env;
-  this.actual = actual;
-  this.spec = spec;
-  this.isNot = opt_isNot || false;
-  this.reportWasCalled_ = false;
-};
-
-// todo: @deprecated as of Jasmine 0.11, remove soon [xw]
-jasmine.Matchers.pp = function(str) {
-  throw new Error("jasmine.Matchers.pp() is no longer supported, please use jasmine.pp() instead!");
-};
-
-// todo: @deprecated Deprecated as of Jasmine 0.10. Rewrite your custom matchers to return true or false. [xw]
-jasmine.Matchers.prototype.report = function(result, failing_message, details) {
-  throw new Error("As of jasmine 0.11, custom matchers must be implemented differently -- please see jasmine docs");
-};
-
-jasmine.Matchers.wrapInto_ = function(prototype, matchersClass) {
-  for (var methodName in prototype) {
-    if (methodName == 'report') continue;
-    var orig = prototype[methodName];
-    matchersClass.prototype[methodName] = jasmine.Matchers.matcherFn_(methodName, orig);
-  }
-};
-
-jasmine.Matchers.matcherFn_ = function(matcherName, matcherFunction) {
-  return function() {
-    var matcherArgs = jasmine.util.argsToArray(arguments);
-    var result = matcherFunction.apply(this, arguments);
-
-    if (this.isNot) {
-      result = !result;
-    }
-
-    if (this.reportWasCalled_) return result;
-
-    var message;
-    if (!result) {
-      if (this.message) {
-        message = this.message.apply(this, arguments);
-        if (jasmine.isArray_(message)) {
-          message = message[this.isNot ? 1 : 0];
-        }
-      } else {
-        var englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); });
-        message = "Expected " + jasmine.pp(this.actual) + (this.isNot ? " not " : " ") + englishyPredicate;
-        if (matcherArgs.length > 0) {
-          for (var i = 0; i < matcherArgs.length; i++) {
-            if (i > 0) message += ",";
-            message += " " + jasmine.pp(matcherArgs[i]);
-          }
-        }
-        message += ".";
-      }
-    }
-    var expectationResult = new jasmine.ExpectationResult({
-      matcherName: matcherName,
-      passed: result,
-      expected: matcherArgs.length > 1 ? matcherArgs : matcherArgs[0],
-      actual: this.actual,
-      message: message
-    });
-    this.spec.addMatcherResult(expectationResult);
-    return jasmine.undefined;
-  };
-};
-
-
-
-
-/**
- * toBe: compares the actual to the expected using ===
- * @param expected
- */
-jasmine.Matchers.prototype.toBe = function(expected) {
-  return this.actual === expected;
-};
-
-/**
- * toNotBe: compares the actual to the expected using !==
- * @param expected
- * @deprecated as of 1.0. Use not.toBe() instead.
- */
-jasmine.Matchers.prototype.toNotBe = function(expected) {
-  return this.actual !== expected;
-};
-
-/**
- * toEqual: compares the actual to the expected using common sense equality. Handles Objects, Arrays, etc.
- *
- * @param expected
- */
-jasmine.Matchers.prototype.toEqual = function(expected) {
-  return this.env.equals_(this.actual, expected);
-};
-
-/**
- * toNotEqual: compares the actual to the expected using the ! of jasmine.Matchers.toEqual
- * @param expected
- * @deprecated as of 1.0. Use not.toNotEqual() instead.
- */
-jasmine.Matchers.prototype.toNotEqual = function(expected) {
-  return !this.env.equals_(this.actual, expected);
-};
-
-/**
- * Matcher that compares the actual to the expected using a regular expression.  Constructs a RegExp, so takes
- * a pattern or a String.
- *
- * @param expected
- */
-jasmine.Matchers.prototype.toMatch = function(expected) {
-  return new RegExp(expected).test(this.actual);
-};
-
-/**
- * Matcher that compares the actual to the expected using the boolean inverse of jasmine.Matchers.toMatch
- * @param expected
- * @deprecated as of 1.0. Use not.toMatch() instead.
- */
-jasmine.Matchers.prototype.toNotMatch = function(expected) {
-  return !(new RegExp(expected).test(this.actual));
-};
-
-/**
- * Matcher that compares the actual to jasmine.undefined.
- */
-jasmine.Matchers.prototype.toBeDefined = function() {
-  return (this.actual !== jasmine.undefined);
-};
-
-/**
- * Matcher that compares the actual to jasmine.undefined.
- */
-jasmine.Matchers.prototype.toBeUndefined = function() {
-  return (this.actual === jasmine.undefined);
-};
-
-/**
- * Matcher that compares the actual to null.
- */
-jasmine.Matchers.prototype.toBeNull = function() {
-  return (this.actual === null);
-};
-
-/**
- * Matcher that boolean not-nots the actual.
- */
-jasmine.Matchers.prototype.toBeTruthy = function() {
-  return !!this.actual;
-};
-
-
-/**
- * Matcher that boolean nots the actual.
- */
-jasmine.Matchers.prototype.toBeFalsy = function() {
-  return !this.actual;
-};
-
-
-/**
- * Matcher that checks to see if the actual, a Jasmine spy, was called.
- */
-jasmine.Matchers.prototype.toHaveBeenCalled = function() {
-  if (arguments.length > 0) {
-    throw new Error('toHaveBeenCalled does not take arguments, use toHaveBeenCalledWith');
-  }
-
-  if (!jasmine.isSpy(this.actual)) {
-    throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
-  }
-
-  this.message = function() {
-    return [
-      "Expected spy " + this.actual.identity + " to have been called.",
-      "Expected spy " + this.actual.identity + " not to have been called."
-    ];
-  };
-
-  return this.actual.wasCalled;
-};
-
-/** @deprecated Use expect(xxx).toHaveBeenCalled() instead */
-jasmine.Matchers.prototype.wasCalled = jasmine.Matchers.prototype.toHaveBeenCalled;
-
-/**
- * Matcher that checks to see if the actual, a Jasmine spy, was not called.
- *
- * @deprecated Use expect(xxx).not.toHaveBeenCalled() instead
- */
-jasmine.Matchers.prototype.wasNotCalled = function() {
-  if (arguments.length > 0) {
-    throw new Error('wasNotCalled does not take arguments');
-  }
-
-  if (!jasmine.isSpy(this.actual)) {
-    throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
-  }
-
-  this.message = function() {
-    return [
-      "Expected spy " + this.actual.identity + " to not have been called.",
-      "Expected spy " + this.actual.identity + " to have been called."
-    ];
-  };
-
-  return !this.actual.wasCalled;
-};
-
-/**
- * Matcher that checks to see if the actual, a Jasmine spy, was called with a set of parameters.
- *
- * @example
- *
- */
-jasmine.Matchers.prototype.toHaveBeenCalledWith = function() {
-  var expectedArgs = jasmine.util.argsToArray(arguments);
-  if (!jasmine.isSpy(this.actual)) {
-    throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
-  }
-  this.message = function() {
-    if (this.actual.callCount == 0) {
-      // todo: what should the failure message for .not.toHaveBeenCalledWith() be? is this right? test better. [xw]
-      return [
-        "Expected spy to have been called with " + jasmine.pp(expectedArgs) + " but it was never called.",
-        "Expected spy not to have been called with " + jasmine.pp(expectedArgs) + " but it was."
-      ];
-    } else {
-      return [
-        "Expected spy to have been called with " + jasmine.pp(expectedArgs) + " but was called with " + jasmine.pp(this.actual.argsForCall),
-        "Expected spy not to have been called with " + jasmine.pp(expectedArgs) + " but was called with " + jasmine.pp(this.actual.argsForCall)
-      ];
-    }
-  };
-
-  return this.env.contains_(this.actual.argsForCall, expectedArgs);
-};
-
-/** @deprecated Use expect(xxx).toHaveBeenCalledWith() instead */
-jasmine.Matchers.prototype.wasCalledWith = jasmine.Matchers.prototype.toHaveBeenCalledWith;
-
-/** @deprecated Use expect(xxx).not.toHaveBeenCalledWith() instead */
-jasmine.Matchers.prototype.wasNotCalledWith = function() {
-  var expectedArgs = jasmine.util.argsToArray(arguments);
-  if (!jasmine.isSpy(this.actual)) {
-    throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
-  }
-
-  this.message = function() {
-    return [
-      "Expected spy not to have been called with " + jasmine.pp(expectedArgs) + " but it was",
-      "Expected spy to have been called with " + jasmine.pp(expectedArgs) + " but it was"
-    ]
-  };
-
-  return !this.env.contains_(this.actual.argsForCall, expectedArgs);
-};
-
-/**
- * Matcher that checks that the expected item is an element in the actual Array.
- *
- * @param {Object} expected
- */
-jasmine.Matchers.prototype.toContain = function(expected) {
-  return this.env.contains_(this.actual, expected);
-};
-
-/**
- * Matcher that checks that the expected item is NOT an element in the actual Array.
- *
- * @param {Object} expected
- * @deprecated as of 1.0. Use not.toNotContain() instead.
- */
-jasmine.Matchers.prototype.toNotContain = function(expected) {
-  return !this.env.contains_(this.actual, expected);
-};
-
-jasmine.Matchers.prototype.toBeLessThan = function(expected) {
-  return this.actual < expected;
-};
-
-jasmine.Matchers.prototype.toBeGreaterThan = function(expected) {
-  return this.actual > expected;
-};
-
-/**
- * Matcher that checks that the expected exception was thrown by the actual.
- *
- * @param {String} expected
- */
-jasmine.Matchers.prototype.toThrow = function(expected) {
-  var result = false;
-  var exception;
-  if (typeof this.actual != 'function') {
-    throw new Error('Actual is not a function');
-  }
-  try {
-    this.actual();
-  } catch (e) {
-    exception = e;
-  }
-  if (exception) {
-    result = (expected === jasmine.undefined || this.env.equals_(exception.message || exception, expected.message || expected));
-  }
-
-  var not = this.isNot ? "not " : "";
-
-  this.message = function() {
-    if (exception && (expected === jasmine.undefined || !this.env.equals_(exception.message || exception, expected.message || expected))) {
-      return ["Expected function " + not + "to throw", expected ? expected.message || expected : " an exception", ", but it threw", exception.message || exception].join(' ');
-    } else {
-      return "Expected function to throw an exception.";
-    }
-  };
-
-  return result;
-};
-
-jasmine.Matchers.Any = function(expectedClass) {
-  this.expectedClass = expectedClass;
-};
-
-jasmine.Matchers.Any.prototype.matches = function(other) {
-  if (this.expectedClass == String) {
-    return typeof other == 'string' || other instanceof String;
-  }
-
-  if (this.expectedClass == Number) {
-    return typeof other == 'number' || other instanceof Number;
-  }
-
-  if (this.expectedClass == Function) {
-    return typeof other == 'function' || other instanceof Function;
-  }
-
-  if (this.expectedClass == Object) {
-    return typeof other == 'object';
-  }
-
-  return other instanceof this.expectedClass;
-};
-
-jasmine.Matchers.Any.prototype.toString = function() {
-  return '<jasmine.any(' + this.expectedClass + ')>';
-};
-
-/**
- * @constructor
- */
-jasmine.MultiReporter = function() {
-  this.subReporters_ = [];
-};
-jasmine.util.inherit(jasmine.MultiReporter, jasmine.Reporter);
-
-jasmine.MultiReporter.prototype.addReporter = function(reporter) {
-  this.subReporters_.push(reporter);
-};
-
-(function() {
-  var functionNames = [
-    "reportRunnerStarting",
-    "reportRunnerResults",
-    "reportSuiteResults",
-    "reportSpecStarting",
-    "reportSpecResults",
-    "log"
-  ];
-  for (var i = 0; i < functionNames.length; i++) {
-    var functionName = functionNames[i];
-    jasmine.MultiReporter.prototype[functionName] = (function(functionName) {
-      return function() {
-        for (var j = 0; j < this.subReporters_.length; j++) {
-          var subReporter = this.subReporters_[j];
-          if (subReporter[functionName]) {
-            subReporter[functionName].apply(subReporter, arguments);
-          }
-        }
-      };
-    })(functionName);
-  }
-})();
-/**
- * Holds results for a set of Jasmine spec. Allows for the results array to hold another jasmine.NestedResults
- *
- * @constructor
- */
-jasmine.NestedResults = function() {
-  /**
-   * The total count of results
-   */
-  this.totalCount = 0;
-  /**
-   * Number of passed results
-   */
-  this.passedCount = 0;
-  /**
-   * Number of failed results
-   */
-  this.failedCount = 0;
-  /**
-   * Was this suite/spec skipped?
-   */
-  this.skipped = false;
-  /**
-   * @ignore
-   */
-  this.items_ = [];
-};
-
-/**
- * Roll up the result counts.
- *
- * @param result
- */
-jasmine.NestedResults.prototype.rollupCounts = function(result) {
-  this.totalCount += result.totalCount;
-  this.passedCount += result.passedCount;
-  this.failedCount += result.failedCount;
-};
-
-/**
- * Adds a log message.
- * @param values Array of message parts which will be concatenated later.
- */
-jasmine.NestedResults.prototype.log = function(values) {
-  this.items_.push(new jasmine.MessageResult(values));
-};
-
-/**
- * Getter for the results: message & results.
- */
-jasmine.NestedResults.prototype.getItems = function() {
-  return this.items_;
-};
-
-/**
- * Adds a result, tracking counts (total, passed, & failed)
- * @param {jasmine.ExpectationResult|jasmine.NestedResults} result
- */
-jasmine.NestedResults.prototype.addResult = function(result) {
-  if (result.type != 'log') {
-    if (result.items_) {
-      this.rollupCounts(result);
-    } else {
-      this.totalCount++;
-      if (result.passed()) {
-        this.passedCount++;
-      } else {
-        this.failedCount++;
-      }
-    }
-  }
-  this.items_.push(result);
-};
-
-/**
- * @returns {Boolean} True if <b>everything</b> below passed
- */
-jasmine.NestedResults.prototype.passed = function() {
-  return this.passedCount === this.totalCount;
-};
-/**
- * Base class for pretty printing for expectation results.
- */
-jasmine.PrettyPrinter = function() {
-  this.ppNestLevel_ = 0;
-};
-
-/**
- * Formats a value in a nice, human-readable string.
- *
- * @param value
- */
-jasmine.PrettyPrinter.prototype.format = function(value) {
-  if (this.ppNestLevel_ > 40) {
-    throw new Error('jasmine.PrettyPrinter: format() nested too deeply!');
-  }
-
-  this.ppNestLevel_++;
-  try {
-    if (value === jasmine.undefined) {
-      this.emitScalar('undefined');
-    } else if (value === null) {
-      this.emitScalar('null');
-    } else if (value === jasmine.getGlobal()) {
-      this.emitScalar('<global>');
-    } else if (value instanceof jasmine.Matchers.Any) {
-      this.emitScalar(value.toString());
-    } else if (typeof value === 'string') {
-      this.emitString(value);
-    } else if (jasmine.isSpy(value)) {
-      this.emitScalar("spy on " + value.identity);
-    } else if (value instanceof RegExp) {
-      this.emitScalar(value.toString());
-    } else if (typeof value === 'function') {
-      this.emitScalar('Function');
-    } else if (typeof value.nodeType === 'number') {
-      this.emitScalar('HTMLNode');
-    } else if (value instanceof Date) {
-      this.emitScalar('Date(' + value + ')');
-    } else if (value.__Jasmine_been_here_before__) {
-      this.emitScalar('<circular reference: ' + (jasmine.isArray_(value) ? 'Array' : 'Object') + '>');
-    } else if (jasmine.isArray_(value) || typeof value == 'object') {
-      value.__Jasmine_been_here_before__ = true;
-      if (jasmine.isArray_(value)) {
-        this.emitArray(value);
-      } else {
-        this.emitObject(value);
-      }
-      delete value.__Jasmine_been_here_before__;
-    } else {
-      this.emitScalar(value.toString());
-    }
-  } finally {
-    this.ppNestLevel_--;
-  }
-};
-
-jasmine.PrettyPrinter.prototype.iterateObject = function(obj, fn) {
-  for (var property in obj) {
-    if (property == '__Jasmine_been_here_before__') continue;
-    fn(property, obj.__lookupGetter__ ? (obj.__lookupGetter__(property) != null) : false);
-  }
-};
-
-jasmine.PrettyPrinter.prototype.emitArray = jasmine.unimplementedMethod_;
-jasmine.PrettyPrinter.prototype.emitObject = jasmine.unimplementedMethod_;
-jasmine.PrettyPrinter.prototype.emitScalar = jasmine.unimplementedMethod_;
-jasmine.PrettyPrinter.prototype.emitString = jasmine.unimplementedMethod_;
-
-jasmine.StringPrettyPrinter = function() {
-  jasmine.PrettyPrinter.call(this);
-
-  this.string = '';
-};
-jasmine.util.inherit(jasmine.StringPrettyPrinter, jasmine.PrettyPrinter);
-
-jasmine.StringPrettyPrinter.prototype.emitScalar = function(value) {
-  this.append(value);
-};
-
-jasmine.StringPrettyPrinter.prototype.emitString = function(value) {
-  this.append("'" + value + "'");
-};
-
-jasmine.StringPrettyPrinter.prototype.emitArray = function(array) {
-  this.append('[ ');
-  for (var i = 0; i < array.length; i++) {
-    if (i > 0) {
-      this.append(', ');
-    }
-    this.format(array[i]);
-  }
-  this.append(' ]');
-};
-
-jasmine.StringPrettyPrinter.prototype.emitObject = function(obj) {
-  var self = this;
-  this.append('{ ');
-  var first = true;
-
-  this.iterateObject(obj, function(property, isGetter) {
-    if (first) {
-      first = false;
-    } else {
-      self.append(', ');
-    }
-
-    self.append(property);
-    self.append(' : ');
-    if (isGetter) {
-      self.append('<getter>');
-    } else {
-      self.format(obj[property]);
-    }
-  });
-
-  this.append(' }');
-};
-
-jasmine.StringPrettyPrinter.prototype.append = function(value) {
-  this.string += value;
-};
-jasmine.Queue = function(env) {
-  this.env = env;
-  this.blocks = [];
-  this.running = false;
-  this.index = 0;
-  this.offset = 0;
-  this.abort = false;
-};
-
-jasmine.Queue.prototype.addBefore = function(block) {
-  this.blocks.unshift(block);
-};
-
-jasmine.Queue.prototype.add = function(block) {
-  this.blocks.push(block);
-};
-
-jasmine.Queue.prototype.insertNext = function(block) {
-  this.blocks.splice((this.index + this.offset + 1), 0, block);
-  this.offset++;
-};
-
-jasmine.Queue.prototype.start = function(onComplete) {
-  this.running = true;
-  this.onComplete = onComplete;
-  this.next_();
-};
-
-jasmine.Queue.prototype.isRunning = function() {
-  return this.running;
-};
-
-jasmine.Queue.LOOP_DONT_RECURSE = true;
-
-jasmine.Queue.prototype.next_ = function() {
-  var self = this;
-  var goAgain = true;
-
-  while (goAgain) {
-    goAgain = false;
-    
-    if (self.index < self.blocks.length && !this.abort) {
-      var calledSynchronously = true;
-      var completedSynchronously = false;
-
-      var onComplete = function () {
-        if (jasmine.Queue.LOOP_DONT_RECURSE && calledSynchronously) {
-          completedSynchronously = true;
-          return;
-        }
-
-        if (self.blocks[self.index].abort) {
-          self.abort = true;
-        }
-
-        self.offset = 0;
-        self.index++;
-
-        var now = new Date().getTime();
-        if (self.env.updateInterval && now - self.env.lastUpdate > self.env.updateInterval) {
-          self.env.lastUpdate = now;
-          self.env.setTimeout(function() {
-            self.next_();
-          }, 0);
-        } else {
-          if (jasmine.Queue.LOOP_DONT_RECURSE && completedSynchronously) {
-            goAgain = true;
-          } else {
-            self.next_();
-          }
-        }
-      };
-      self.blocks[self.index].execute(onComplete);
-
-      calledSynchronously = false;
-      if (completedSynchronously) {
-        onComplete();
-      }
-      
-    } else {
-      self.running = false;
-      if (self.onComplete) {
-        self.onComplete();
-      }
-    }
-  }
-};
-
-jasmine.Queue.prototype.results = function() {
-  var results = new jasmine.NestedResults();
-  for (var i = 0; i < this.blocks.length; i++) {
-    if (this.blocks[i].results) {
-      results.addResult(this.blocks[i].results());
-    }
-  }
-  return results;
-};
-
-
-/**
- * Runner
- *
- * @constructor
- * @param {jasmine.Env} env
- */
-jasmine.Runner = function(env) {
-  var self = this;
-  self.env = env;
-  self.queue = new jasmine.Queue(env);
-  self.before_ = [];
-  self.after_ = [];
-  self.suites_ = [];
-};
-
-jasmine.Runner.prototype.execute = function() {
-  var self = this;
-  if (self.env.reporter.reportRunnerStarting) {
-    self.env.reporter.reportRunnerStarting(this);
-  }
-  self.queue.start(function () {
-    self.finishCallback();
-  });
-};
-
-jasmine.Runner.prototype.beforeEach = function(beforeEachFunction) {
-  beforeEachFunction.typeName = 'beforeEach';
-  this.before_.splice(0,0,beforeEachFunction);
-};
-
-jasmine.Runner.prototype.afterEach = function(afterEachFunction) {
-  afterEachFunction.typeName = 'afterEach';
-  this.after_.splice(0,0,afterEachFunction);
-};
-
-
-jasmine.Runner.prototype.finishCallback = function() {
-  this.env.reporter.reportRunnerResults(this);
-};
-
-jasmine.Runner.prototype.addSuite = function(suite) {
-  this.suites_.push(suite);
-};
-
-jasmine.Runner.prototype.add = function(block) {
-  if (block instanceof jasmine.Suite) {
-    this.addSuite(block);
-  }
-  this.queue.add(block);
-};
-
-jasmine.Runner.prototype.specs = function () {
-  var suites = this.suites();
-  var specs = [];
-  for (var i = 0; i < suites.length; i++) {
-    specs = specs.concat(suites[i].specs());
-  }
-  return specs;
-};
-
-jasmine.Runner.prototype.suites = function() {
-  return this.suites_;
-};
-
-jasmine.Runner.prototype.topLevelSuites = function() {
-  var topLevelSuites = [];
-  for (var i = 0; i < this.suites_.length; i++) {
-    if (!this.suites_[i].parentSuite) {
-      topLevelSuites.push(this.suites_[i]);
-    }
-  }
-  return topLevelSuites;
-};
-
-jasmine.Runner.prototype.results = function() {
-  return this.queue.results();
-};
-/**
- * Internal representation of a Jasmine specification, or test.
- *
- * @constructor
- * @param {jasmine.Env} env
- * @param {jasmine.Suite} suite
- * @param {String} description
- */
-jasmine.Spec = function(env, suite, description) {
-  if (!env) {
-    throw new Error('jasmine.Env() required');
-  }
-  if (!suite) {
-    throw new Error('jasmine.Suite() required');
-  }
-  var spec = this;
-  spec.id = env.nextSpecId ? env.nextSpecId() : null;
-  spec.env = env;
-  spec.suite = suite;
-  spec.description = description;
-  spec.queue = new jasmine.Queue(env);
-
-  spec.afterCallbacks = [];
-  spec.spies_ = [];
-
-  spec.results_ = new jasmine.NestedResults();
-  spec.results_.description = description;
-  spec.matchersClass = null;
-};
-
-jasmine.Spec.prototype.getFullName = function() {
-  return this.suite.getFullName() + ' ' + this.description + '.';
-};
-
-
-jasmine.Spec.prototype.results = function() {
-  return this.results_;
-};
-
-/**
- * All parameters are pretty-printed and concatenated together, then written to the spec's output.
- *
- * Be careful not to leave calls to <code>jasmine.log</code> in production code.
- */
-jasmine.Spec.prototype.log = function() {
-  return this.results_.log(arguments);
-};
-
-jasmine.Spec.prototype.runs = function (func) {
-  var block = new jasmine.Block(this.env, func, this);
-  this.addToQueue(block);
-  return this;
-};
-
-jasmine.Spec.prototype.addToQueue = function (block) {
-  if (this.queue.isRunning()) {
-    this.queue.insertNext(block);
-  } else {
-    this.queue.add(block);
-  }
-};
-
-/**
- * @param {jasmine.ExpectationResult} result
- */
-jasmine.Spec.prototype.addMatcherResult = function(result) {
-  this.results_.addResult(result);
-};
-
-jasmine.Spec.prototype.expect = function(actual) {
-  var positive = new (this.getMatchersClass_())(this.env, actual, this);
-  positive.not = new (this.getMatchersClass_())(this.env, actual, this, true);
-  return positive;
-};
-
-/**
- * Waits a fixed time period before moving to the next block.
- *
- * @deprecated Use waitsFor() instead
- * @param {Number} timeout milliseconds to wait
- */
-jasmine.Spec.prototype.waits = function(timeout) {
-  var waitsFunc = new jasmine.WaitsBlock(this.env, timeout, this);
-  this.addToQueue(waitsFunc);
-  return this;
-};
-
-/**
- * Waits for the latchFunction to return true before proceeding to the next block.
- *
- * @param {Function} latchFunction
- * @param {String} optional_timeoutMessage
- * @param {Number} optional_timeout
- */
-jasmine.Spec.prototype.waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) {
-  var latchFunction_ = null;
-  var optional_timeoutMessage_ = null;
-  var optional_timeout_ = null;
-
-  for (var i = 0; i < arguments.length; i++) {
-    var arg = arguments[i];
-    switch (typeof arg) {
-      case 'function':
-        latchFunction_ = arg;
-        break;
-      case 'string':
-        optional_timeoutMessage_ = arg;
-        break;
-      case 'number':
-        optional_timeout_ = arg;
-        break;
-    }
-  }
-
-  var waitsForFunc = new jasmine.WaitsForBlock(this.env, optional_timeout_, latchFunction_, optional_timeoutMessage_, this);
-  this.addToQueue(waitsForFunc);
-  return this;
-};
-
-jasmine.Spec.prototype.fail = function (e) {
-  var expectationResult = new jasmine.ExpectationResult({
-    passed: false,
-    message: e ? jasmine.util.formatException(e) : 'Exception'
-  });
-  this.results_.addResult(expectationResult);
-};
-
-jasmine.Spec.prototype.getMatchersClass_ = function() {
-  return this.matchersClass || this.env.matchersClass;
-};
-
-jasmine.Spec.prototype.addMatchers = function(matchersPrototype) {
-  var parent = this.getMatchersClass_();
-  var newMatchersClass = function() {
-    parent.apply(this, arguments);
-  };
-  jasmine.util.inherit(newMatchersClass, parent);
-  jasmine.Matchers.wrapInto_(matchersPrototype, newMatchersClass);
-  this.matchersClass = newMatchersClass;
-};
-
-jasmine.Spec.prototype.finishCallback = function() {
-  this.env.reporter.reportSpecResults(this);
-};
-
-jasmine.Spec.prototype.finish = function(onComplete) {
-  this.removeAllSpies();
-  this.finishCallback();
-  if (onComplete) {
-    onComplete();
-  }
-};
-
-jasmine.Spec.prototype.after = function(doAfter) {
-  if (this.queue.isRunning()) {
-    this.queue.add(new jasmine.Block(this.env, doAfter, this));
-  } else {
-    this.afterCallbacks.unshift(doAfter);
-  }
-};
-
-jasmine.Spec.prototype.execute = function(onComplete) {
-  var spec = this;
-  if (!spec.env.specFilter(spec)) {
-    spec.results_.skipped = true;
-    spec.finish(onComplete);
-    return;
-  }
-
-  this.env.reporter.reportSpecStarting(this);
-
-  spec.env.currentSpec = spec;
-
-  spec.addBeforesAndAftersToQueue();
-
-  spec.queue.start(function () {
-    spec.finish(onComplete);
-  });
-};
-
-jasmine.Spec.prototype.addBeforesAndAftersToQueue = function() {
-  var runner = this.env.currentRunner();
-  var i;
-
-  for (var suite = this.suite; suite; suite = suite.parentSuite) {
-    for (i = 0; i < suite.before_.length; i++) {
-      this.queue.addBefore(new jasmine.Block(this.env, suite.before_[i], this));
-    }
-  }
-  for (i = 0; i < runner.before_.length; i++) {
-    this.queue.addBefore(new jasmine.Block(this.env, runner.before_[i], this));
-  }
-  for (i = 0; i < this.afterCallbacks.length; i++) {
-    this.queue.add(new jasmine.Block(this.env, this.afterCallbacks[i], this));
-  }
-  for (suite = this.suite; suite; suite = suite.parentSuite) {
-    for (i = 0; i < suite.after_.length; i++) {
-      this.queue.add(new jasmine.Block(this.env, suite.after_[i], this));
-    }
-  }
-  for (i = 0; i < runner.after_.length; i++) {
-    this.queue.add(new jasmine.Block(this.env, runner.after_[i], this));
-  }
-};
-
-jasmine.Spec.prototype.explodes = function() {
-  throw 'explodes function should not have been called';
-};
-
-jasmine.Spec.prototype.spyOn = function(obj, methodName, ignoreMethodDoesntExist) {
-  if (obj == jasmine.undefined) {
-    throw "spyOn could not find an object to spy upon for " + methodName + "()";
-  }
-
-  if (!ignoreMethodDoesntExist && obj[methodName] === jasmine.undefined) {
-    throw methodName + '() method does not exist';
-  }
-
-  if (!ignoreMethodDoesntExist && obj[methodName] && obj[methodName].isSpy) {
-    throw new Error(methodName + ' has already been spied upon');
-  }
-
-  var spyObj = jasmine.createSpy(methodName);
-
-  this.spies_.push(spyObj);
-  spyObj.baseObj = obj;
-  spyObj.methodName = methodName;
-  spyObj.originalValue = obj[methodName];
-
-  obj[methodName] = spyObj;
-
-  return spyObj;
-};
-
-jasmine.Spec.prototype.removeAllSpies = function() {
-  for (var i = 0; i < this.spies_.length; i++) {
-    var spy = this.spies_[i];
-    spy.baseObj[spy.methodName] = spy.originalValue;
-  }
-  this.spies_ = [];
-};
-
-/**
- * Internal representation of a Jasmine suite.
- *
- * @constructor
- * @param {jasmine.Env} env
- * @param {String} description
- * @param {Function} specDefinitions
- * @param {jasmine.Suite} parentSuite
- */
-jasmine.Suite = function(env, description, specDefinitions, parentSuite) {
-  var self = this;
-  self.id = env.nextSuiteId ? env.nextSuiteId() : null;
-  self.description = description;
-  self.queue = new jasmine.Queue(env);
-  self.parentSuite = parentSuite;
-  self.env = env;
-  self.before_ = [];
-  self.after_ = [];
-  self.children_ = [];
-  self.suites_ = [];
-  self.specs_ = [];
-};
-
-jasmine.Suite.prototype.getFullName = function() {
-  var fullName = this.description;
-  for (var parentSuite = this.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) {
-    fullName = parentSuite.description + ' ' + fullName;
-  }
-  return fullName;
-};
-
-jasmine.Suite.prototype.finish = function(onComplete) {
-  this.env.reporter.reportSuiteResults(this);
-  this.finished = true;
-  if (typeof(onComplete) == 'function') {
-    onComplete();
-  }
-};
-
-jasmine.Suite.prototype.beforeEach = function(beforeEachFunction) {
-  beforeEachFunction.typeName = 'beforeEach';
-  this.before_.unshift(beforeEachFunction);
-};
-
-jasmine.Suite.prototype.afterEach = function(afterEachFunction) {
-  afterEachFunction.typeName = 'afterEach';
-  this.after_.unshift(afterEachFunction);
-};
-
-jasmine.Suite.prototype.results = function() {
-  return this.queue.results();
-};
-
-jasmine.Suite.prototype.add = function(suiteOrSpec) {
-  this.children_.push(suiteOrSpec);
-  if (suiteOrSpec instanceof jasmine.Suite) {
-    this.suites_.push(suiteOrSpec);
-    this.env.currentRunner().addSuite(suiteOrSpec);
-  } else {
-    this.specs_.push(suiteOrSpec);
-  }
-  this.queue.add(suiteOrSpec);
-};
-
-jasmine.Suite.prototype.specs = function() {
-  return this.specs_;
-};
-
-jasmine.Suite.prototype.suites = function() {
-  return this.suites_;
-};
-
-jasmine.Suite.prototype.children = function() {
-  return this.children_;
-};
-
-jasmine.Suite.prototype.execute = function(onComplete) {
-  var self = this;
-  this.queue.start(function () {
-    self.finish(onComplete);
-  });
-};
-jasmine.WaitsBlock = function(env, timeout, spec) {
-  this.timeout = timeout;
-  jasmine.Block.call(this, env, null, spec);
-};
-
-jasmine.util.inherit(jasmine.WaitsBlock, jasmine.Block);
-
-jasmine.WaitsBlock.prototype.execute = function (onComplete) {
-  this.env.reporter.log('>> Jasmine waiting for ' + this.timeout + ' ms...');
-  this.env.setTimeout(function () {
-    onComplete();
-  }, this.timeout);
-};
-/**
- * A block which waits for some condition to become true, with timeout.
- *
- * @constructor
- * @extends jasmine.Block
- * @param {jasmine.Env} env The Jasmine environment.
- * @param {Number} timeout The maximum time in milliseconds to wait for the condition to become true.
- * @param {Function} latchFunction A function which returns true when the desired condition has been met.
- * @param {String} message The message to display if the desired condition hasn't been met within the given time period.
- * @param {jasmine.Spec} spec The Jasmine spec.
- */
-jasmine.WaitsForBlock = function(env, timeout, latchFunction, message, spec) {
-  this.timeout = timeout || env.defaultTimeoutInterval;
-  this.latchFunction = latchFunction;
-  this.message = message;
-  this.totalTimeSpentWaitingForLatch = 0;
-  jasmine.Block.call(this, env, null, spec);
-};
-jasmine.util.inherit(jasmine.WaitsForBlock, jasmine.Block);
-
-jasmine.WaitsForBlock.TIMEOUT_INCREMENT = 10;
-
-jasmine.WaitsForBlock.prototype.execute = function(onComplete) {
-  this.env.reporter.log('>> Jasmine waiting for ' + (this.message || 'something to happen'));
-  var latchFunctionResult;
-  try {
-    latchFunctionResult = this.latchFunction.apply(this.spec);
-  } catch (e) {
-    this.spec.fail(e);
-    onComplete();
-    return;
-  }
-
-  if (latchFunctionResult) {
-    onComplete();
-  } else if (this.totalTimeSpentWaitingForLatch >= this.timeout) {
-    var message = 'timed out after ' + this.timeout + ' msec waiting for ' + (this.message || 'something to happen');
-    this.spec.fail({
-      name: 'timeout',
-      message: message
-    });
-
-    this.abort = true;
-    onComplete();
-  } else {
-    this.totalTimeSpentWaitingForLatch += jasmine.WaitsForBlock.TIMEOUT_INCREMENT;
-    var self = this;
-    this.env.setTimeout(function() {
-      self.execute(onComplete);
-    }, jasmine.WaitsForBlock.TIMEOUT_INCREMENT);
-  }
-};
-// Mock setTimeout, clearTimeout
-// Contributed by Pivotal Computer Systems, www.pivotalsf.com
-
-jasmine.FakeTimer = function() {
-  this.reset();
-
-  var self = this;
-  self.setTimeout = function(funcToCall, millis) {
-    self.timeoutsMade++;
-    self.scheduleFunction(self.timeoutsMade, funcToCall, millis, false);
-    return self.timeoutsMade;
-  };
-
-  self.setInterval = function(funcToCall, millis) {
-    self.timeoutsMade++;
-    self.scheduleFunction(self.timeoutsMade, funcToCall, millis, true);
-    return self.timeoutsMade;
-  };
-
-  self.clearTimeout = function(timeoutKey) {
-    self.scheduledFunctions[timeoutKey] = jasmine.undefined;
-  };
-
-  self.clearInterval = function(timeoutKey) {
-    self.scheduledFunctions[timeoutKey] = jasmine.undefined;
-  };
-
-};
-
-jasmine.FakeTimer.prototype.reset = function() {
-  this.timeoutsMade = 0;
-  this.scheduledFunctions = {};
-  this.nowMillis = 0;
-};
-
-jasmine.FakeTimer.prototype.tick = function(millis) {
-  var oldMillis = this.nowMillis;
-  var newMillis = oldMillis + millis;
-  this.runFunctionsWithinRange(oldMillis, newMillis);
-  this.nowMillis = newMillis;
-};
-
-jasmine.FakeTimer.prototype.runFunctionsWithinRange = function(oldMillis, nowMillis) {
-  var scheduledFunc;
-  var funcsToRun = [];
-  for (var timeoutKey in this.scheduledFunctions) {
-    scheduledFunc = this.scheduledFunctions[timeoutKey];
-    if (scheduledFunc != jasmine.undefined &&
-        scheduledFunc.runAtMillis >= oldMillis &&
-        scheduledFunc.runAtMillis <= nowMillis) {
-      funcsToRun.push(scheduledFunc);
-      this.scheduledFunctions[timeoutKey] = jasmine.undefined;
-    }
-  }
-
-  if (funcsToRun.length > 0) {
-    funcsToRun.sort(function(a, b) {
-      return a.runAtMillis - b.runAtMillis;
-    });
-    for (var i = 0; i < funcsToRun.length; ++i) {
-      try {
-        var funcToRun = funcsToRun[i];
-        this.nowMillis = funcToRun.runAtMillis;
-        funcToRun.funcToCall();
-        if (funcToRun.recurring) {
-          this.scheduleFunction(funcToRun.timeoutKey,
-              funcToRun.funcToCall,
-              funcToRun.millis,
-              true);
-        }
-      } catch(e) {
-      }
-    }
-    this.runFunctionsWithinRange(oldMillis, nowMillis);
-  }
-};
-
-jasmine.FakeTimer.prototype.scheduleFunction = function(timeoutKey, funcToCall, millis, recurring) {
-  this.scheduledFunctions[timeoutKey] = {
-    runAtMillis: this.nowMillis + millis,
-    funcToCall: funcToCall,
-    recurring: recurring,
-    timeoutKey: timeoutKey,
-    millis: millis
-  };
-};
-
-/**
- * @namespace
- */
-jasmine.Clock = {
-  defaultFakeTimer: new jasmine.FakeTimer(),
-
-  reset: function() {
-    jasmine.Clock.assertInstalled();
-    jasmine.Clock.defaultFakeTimer.reset();
-  },
-
-  tick: function(millis) {
-    jasmine.Clock.assertInstalled();
-    jasmine.Clock.defaultFakeTimer.tick(millis);
-  },
-
-  runFunctionsWithinRange: function(oldMillis, nowMillis) {
-    jasmine.Clock.defaultFakeTimer.runFunctionsWithinRange(oldMillis, nowMillis);
-  },
-
-  scheduleFunction: function(timeoutKey, funcToCall, millis, recurring) {
-    jasmine.Clock.defaultFakeTimer.scheduleFunction(timeoutKey, funcToCall, millis, recurring);
-  },
-
-  useMock: function() {
-    if (!jasmine.Clock.isInstalled()) {
-      var spec = jasmine.getEnv().currentSpec;
-      spec.after(jasmine.Clock.uninstallMock);
-
-      jasmine.Clock.installMock();
-    }
-  },
-
-  installMock: function() {
-    jasmine.Clock.installed = jasmine.Clock.defaultFakeTimer;
-  },
-
-  uninstallMock: function() {
-    jasmine.Clock.assertInstalled();
-    jasmine.Clock.installed = jasmine.Clock.real;
-  },
-
-  real: {
-    setTimeout: jasmine.getGlobal().setTimeout,
-    clearTimeout: jasmine.getGlobal().clearTimeout,
-    setInterval: jasmine.getGlobal().setInterval,
-    clearInterval: jasmine.getGlobal().clearInterval
-  },
-
-  assertInstalled: function() {
-    if (!jasmine.Clock.isInstalled()) {
-      throw new Error("Mock clock is not installed, use jasmine.Clock.useMock()");
-    }
-  },
-
-  isInstalled: function() {
-    return jasmine.Clock.installed == jasmine.Clock.defaultFakeTimer;
-  },
-
-  installed: null
-};
-jasmine.Clock.installed = jasmine.Clock.real;
-
-//else for IE support
-jasmine.getGlobal().setTimeout = function(funcToCall, millis) {
-  if (jasmine.Clock.installed.setTimeout.apply) {
-    return jasmine.Clock.installed.setTimeout.apply(this, arguments);
-  } else {
-    return jasmine.Clock.installed.setTimeout(funcToCall, millis);
-  }
-};
-
-jasmine.getGlobal().setInterval = function(funcToCall, millis) {
-  if (jasmine.Clock.installed.setInterval.apply) {
-    return jasmine.Clock.installed.setInterval.apply(this, arguments);
-  } else {
-    return jasmine.Clock.installed.setInterval(funcToCall, millis);
-  }
-};
-
-jasmine.getGlobal().clearTimeout = function(timeoutKey) {
-  if (jasmine.Clock.installed.clearTimeout.apply) {
-    return jasmine.Clock.installed.clearTimeout.apply(this, arguments);
-  } else {
-    return jasmine.Clock.installed.clearTimeout(timeoutKey);
-  }
-};
-
-jasmine.getGlobal().clearInterval = function(timeoutKey) {
-  if (jasmine.Clock.installed.clearTimeout.apply) {
-    return jasmine.Clock.installed.clearInterval.apply(this, arguments);
-  } else {
-    return jasmine.Clock.installed.clearInterval(timeoutKey);
-  }
-};
-
-
-jasmine.version_= {
-  "major": 1,
-  "minor": 0,
-  "build": 1,
-  "revision": 1286311016
-};
diff --git a/tests/jasmine/spec/mediawiki.jqueryMsg.spec.data.js b/tests/jasmine/spec/mediawiki.jqueryMsg.spec.data.js
deleted file mode 100644 (file)
index a867f72..0000000
+++ /dev/null
@@ -1,488 +0,0 @@
-// This file stores the results from the PHP parser for certain messages and arguments,
-// so we can test the equivalent Javascript libraries.
-// Last generated with makeLanguageSpec.php at 2011-01-28T02:04:09+00:00
-
-mediaWiki.messages.set( {
-       "en_undelete_short": "Undelete {{PLURAL:$1|one edit|$1 edits}}",
-       "en_category-subcat-count": "{{PLURAL:$2|This category has only the following subcategory.|This category has the following {{PLURAL:$1|subcategory|$1 subcategories}}, out of $2 total.}}",
-       "fr_undelete_short": "Restaurer $1 modification{{PLURAL:$1||s}}",
-       "fr_category-subcat-count": "Cette cat\u00e9gorie comprend {{PLURAL:$2|la sous-cat\u00e9gorie|$2 sous-cat\u00e9gories, dont {{PLURAL:$1|celle|les $1}}}} ci-dessous.",
-       "ar_undelete_short": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 {{PLURAL:$1|\u062a\u0639\u062f\u064a\u0644 \u0648\u0627\u062d\u062f|\u062a\u0639\u062f\u064a\u0644\u064a\u0646|$1 \u062a\u0639\u062f\u064a\u0644\u0627\u062a|$1 \u062a\u0639\u062f\u064a\u0644|$1 \u062a\u0639\u062f\u064a\u0644\u0627}}",
-       "ar_category-subcat-count": "{{PLURAL:$2|\u0644\u0627 \u062a\u0635\u0627\u0646\u064a\u0641 \u0641\u0631\u0639\u064a\u0629 \u0641\u064a \u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641|\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0641\u064a\u0647 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0627\u0644\u0641\u0631\u0639\u064a \u0627\u0644\u062a\u0627\u0644\u064a \u0641\u0642\u0637.|\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0641\u064a\u0647 {{PLURAL:$1||\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0627\u0644\u0641\u0631\u0639\u064a|\u0647\u0630\u064a\u0646 \u0627\u0644\u062a\u0635\u0646\u064a\u0641\u064a\u0646 \u0627\u0644\u0641\u0631\u0639\u064a\u064a\u0646|\u0647\u0630\u0647 \u0627\u0644$1 \u062a\u0635\u0627\u0646\u064a\u0641 \u0627\u0644\u0641\u0631\u0639\u064a\u0629|\u0647\u0630\u0647 \u0627\u0644$1 \u062a\u0635\u0646\u064a\u0641\u0627 \u0641\u0631\u0639\u064a\u0627|\u0647\u0630\u0647 \u0627\u0644$1 \u062a\u0635\u0646\u064a\u0641 \u0641\u0631\u0639\u064a}}\u060c \u0645\u0646 \u0625\u062c\u0645\u0627\u0644\u064a $2.}}",
-       "jp_undelete_short": "Undelete {{PLURAL:$1|one edit|$1 edits}}",
-       "jp_category-subcat-count": "{{PLURAL:$2|This category has only the following subcategory.|This category has the following {{PLURAL:$1|subcategory|$1 subcategories}}, out of $2 total.}}",
-       "zh_undelete_short": "\u6062\u590d\u88ab\u5220\u9664\u7684$1\u9879\u4fee\u8ba2",
-       "zh_category-subcat-count": "{{PLURAL:$2|\u672c\u5206\u7c7b\u53ea\u6709\u4e0b\u5217\u4e00\u4e2a\u5b50\u5206\u7c7b\u3002|\u672c\u5206\u7c7b\u5305\u542b\u4e0b\u5217$1\u4e2a\u5b50\u5206\u7c7b\uff0c\u5171\u6709$2\u4e2a\u5b50\u5206\u7c7b\u3002}}"
-} );
-var jasmineMsgSpec = [
-       {
-               "name": "en undelete_short 0",
-               "key": "en_undelete_short",
-               "args": [
-                       0
-               ],
-               "result": "Undelete 0 edits",
-               "lang": "en"
-       },
-       {
-               "name": "en undelete_short 1",
-               "key": "en_undelete_short",
-               "args": [
-                       1
-               ],
-               "result": "Undelete one edit",
-               "lang": "en"
-       },
-       {
-               "name": "en undelete_short 2",
-               "key": "en_undelete_short",
-               "args": [
-                       2
-               ],
-               "result": "Undelete 2 edits",
-               "lang": "en"
-       },
-       {
-               "name": "en undelete_short 5",
-               "key": "en_undelete_short",
-               "args": [
-                       5
-               ],
-               "result": "Undelete 5 edits",
-               "lang": "en"
-       },
-       {
-               "name": "en undelete_short 21",
-               "key": "en_undelete_short",
-               "args": [
-                       21
-               ],
-               "result": "Undelete 21 edits",
-               "lang": "en"
-       },
-       {
-               "name": "en undelete_short 101",
-               "key": "en_undelete_short",
-               "args": [
-                       101
-               ],
-               "result": "Undelete 101 edits",
-               "lang": "en"
-       },
-       {
-               "name": "en category-subcat-count 0,10",
-               "key": "en_category-subcat-count",
-               "args": [
-                       0,
-                       10
-               ],
-               "result": "This category has the following 0 subcategories, out of 10 total.",
-               "lang": "en"
-       },
-       {
-               "name": "en category-subcat-count 1,1",
-               "key": "en_category-subcat-count",
-               "args": [
-                       1,
-                       1
-               ],
-               "result": "This category has only the following subcategory.",
-               "lang": "en"
-       },
-       {
-               "name": "en category-subcat-count 1,2",
-               "key": "en_category-subcat-count",
-               "args": [
-                       1,
-                       2
-               ],
-               "result": "This category has the following subcategory, out of 2 total.",
-               "lang": "en"
-       },
-       {
-               "name": "en category-subcat-count 3,30",
-               "key": "en_category-subcat-count",
-               "args": [
-                       3,
-                       30
-               ],
-               "result": "This category has the following 3 subcategories, out of 30 total.",
-               "lang": "en"
-       },
-       {
-               "name": "fr undelete_short 0",
-               "key": "fr_undelete_short",
-               "args": [
-                       0
-               ],
-               "result": "Restaurer 0 modification",
-               "lang": "fr"
-       },
-       {
-               "name": "fr undelete_short 1",
-               "key": "fr_undelete_short",
-               "args": [
-                       1
-               ],
-               "result": "Restaurer 1 modification",
-               "lang": "fr"
-       },
-       {
-               "name": "fr undelete_short 2",
-               "key": "fr_undelete_short",
-               "args": [
-                       2
-               ],
-               "result": "Restaurer 2 modifications",
-               "lang": "fr"
-       },
-       {
-               "name": "fr undelete_short 5",
-               "key": "fr_undelete_short",
-               "args": [
-                       5
-               ],
-               "result": "Restaurer 5 modifications",
-               "lang": "fr"
-       },
-       {
-               "name": "fr undelete_short 21",
-               "key": "fr_undelete_short",
-               "args": [
-                       21
-               ],
-               "result": "Restaurer 21 modifications",
-               "lang": "fr"
-       },
-       {
-               "name": "fr undelete_short 101",
-               "key": "fr_undelete_short",
-               "args": [
-                       101
-               ],
-               "result": "Restaurer 101 modifications",
-               "lang": "fr"
-       },
-       {
-               "name": "fr category-subcat-count 0,10",
-               "key": "fr_category-subcat-count",
-               "args": [
-                       0,
-                       10
-               ],
-               "result": "Cette cat\u00e9gorie comprend 10 sous-cat\u00e9gories, dont celle ci-dessous.",
-               "lang": "fr"
-       },
-       {
-               "name": "fr category-subcat-count 1,1",
-               "key": "fr_category-subcat-count",
-               "args": [
-                       1,
-                       1
-               ],
-               "result": "Cette cat\u00e9gorie comprend la sous-cat\u00e9gorie ci-dessous.",
-               "lang": "fr"
-       },
-       {
-               "name": "fr category-subcat-count 1,2",
-               "key": "fr_category-subcat-count",
-               "args": [
-                       1,
-                       2
-               ],
-               "result": "Cette cat\u00e9gorie comprend 2 sous-cat\u00e9gories, dont celle ci-dessous.",
-               "lang": "fr"
-       },
-       {
-               "name": "fr category-subcat-count 3,30",
-               "key": "fr_category-subcat-count",
-               "args": [
-                       3,
-                       30
-               ],
-               "result": "Cette cat\u00e9gorie comprend 30 sous-cat\u00e9gories, dont les 3 ci-dessous.",
-               "lang": "fr"
-       },
-       {
-               "name": "ar undelete_short 0",
-               "key": "ar_undelete_short",
-               "args": [
-                       0
-               ],
-               "result": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 \u062a\u0639\u062f\u064a\u0644 \u0648\u0627\u062d\u062f",
-               "lang": "ar"
-       },
-       {
-               "name": "ar undelete_short 1",
-               "key": "ar_undelete_short",
-               "args": [
-                       1
-               ],
-               "result": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 \u062a\u0639\u062f\u064a\u0644\u064a\u0646",
-               "lang": "ar"
-       },
-       {
-               "name": "ar undelete_short 2",
-               "key": "ar_undelete_short",
-               "args": [
-                       2
-               ],
-               "result": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 2 \u062a\u0639\u062f\u064a\u0644\u0627\u062a",
-               "lang": "ar"
-       },
-       {
-               "name": "ar undelete_short 5",
-               "key": "ar_undelete_short",
-               "args": [
-                       5
-               ],
-               "result": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 5 \u062a\u0639\u062f\u064a\u0644",
-               "lang": "ar"
-       },
-       {
-               "name": "ar undelete_short 21",
-               "key": "ar_undelete_short",
-               "args": [
-                       21
-               ],
-               "result": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 21 \u062a\u0639\u062f\u064a\u0644\u0627",
-               "lang": "ar"
-       },
-       {
-               "name": "ar undelete_short 101",
-               "key": "ar_undelete_short",
-               "args": [
-                       101
-               ],
-               "result": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 101 \u062a\u0639\u062f\u064a\u0644\u0627",
-               "lang": "ar"
-       },
-       {
-               "name": "ar category-subcat-count 0,10",
-               "key": "ar_category-subcat-count",
-               "args": [
-                       0,
-                       10
-               ],
-               "result": "\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0641\u064a\u0647 \u060c \u0645\u0646 \u0625\u062c\u0645\u0627\u0644\u064a 10.",
-               "lang": "ar"
-       },
-       {
-               "name": "ar category-subcat-count 1,1",
-               "key": "ar_category-subcat-count",
-               "args": [
-                       1,
-                       1
-               ],
-               "result": "\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0641\u064a\u0647 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0627\u0644\u0641\u0631\u0639\u064a \u0627\u0644\u062a\u0627\u0644\u064a \u0641\u0642\u0637.",
-               "lang": "ar"
-       },
-       {
-               "name": "ar category-subcat-count 1,2",
-               "key": "ar_category-subcat-count",
-               "args": [
-                       1,
-                       2
-               ],
-               "result": "\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0641\u064a\u0647 \u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0627\u0644\u0641\u0631\u0639\u064a\u060c \u0645\u0646 \u0625\u062c\u0645\u0627\u0644\u064a 2.",
-               "lang": "ar"
-       },
-       {
-               "name": "ar category-subcat-count 3,30",
-               "key": "ar_category-subcat-count",
-               "args": [
-                       3,
-                       30
-               ],
-               "result": "\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0641\u064a\u0647 \u0647\u0630\u0647 \u0627\u06443 \u062a\u0635\u0627\u0646\u064a\u0641 \u0627\u0644\u0641\u0631\u0639\u064a\u0629\u060c \u0645\u0646 \u0625\u062c\u0645\u0627\u0644\u064a 30.",
-               "lang": "ar"
-       },
-       {
-               "name": "jp undelete_short 0",
-               "key": "jp_undelete_short",
-               "args": [
-                       0
-               ],
-               "result": "Undelete 0 edits",
-               "lang": "jp"
-       },
-       {
-               "name": "jp undelete_short 1",
-               "key": "jp_undelete_short",
-               "args": [
-                       1
-               ],
-               "result": "Undelete one edit",
-               "lang": "jp"
-       },
-       {
-               "name": "jp undelete_short 2",
-               "key": "jp_undelete_short",
-               "args": [
-                       2
-               ],
-               "result": "Undelete 2 edits",
-               "lang": "jp"
-       },
-       {
-               "name": "jp undelete_short 5",
-               "key": "jp_undelete_short",
-               "args": [
-                       5
-               ],
-               "result": "Undelete 5 edits",
-               "lang": "jp"
-       },
-       {
-               "name": "jp undelete_short 21",
-               "key": "jp_undelete_short",
-               "args": [
-                       21
-               ],
-               "result": "Undelete 21 edits",
-               "lang": "jp"
-       },
-       {
-               "name": "jp undelete_short 101",
-               "key": "jp_undelete_short",
-               "args": [
-                       101
-               ],
-               "result": "Undelete 101 edits",
-               "lang": "jp"
-       },
-       {
-               "name": "jp category-subcat-count 0,10",
-               "key": "jp_category-subcat-count",
-               "args": [
-                       0,
-                       10
-               ],
-               "result": "This category has the following 0 subcategories, out of 10 total.",
-               "lang": "jp"
-       },
-       {
-               "name": "jp category-subcat-count 1,1",
-               "key": "jp_category-subcat-count",
-               "args": [
-                       1,
-                       1
-               ],
-               "result": "This category has only the following subcategory.",
-               "lang": "jp"
-       },
-       {
-               "name": "jp category-subcat-count 1,2",
-               "key": "jp_category-subcat-count",
-               "args": [
-                       1,
-                       2
-               ],
-               "result": "This category has the following subcategory, out of 2 total.",
-               "lang": "jp"
-       },
-       {
-               "name": "jp category-subcat-count 3,30",
-               "key": "jp_category-subcat-count",
-               "args": [
-                       3,
-                       30
-               ],
-               "result": "This category has the following 3 subcategories, out of 30 total.",
-               "lang": "jp"
-       },
-       {
-               "name": "zh undelete_short 0",
-               "key": "zh_undelete_short",
-               "args": [
-                       0
-               ],
-               "result": "\u6062\u590d\u88ab\u5220\u9664\u76840\u9879\u4fee\u8ba2",
-               "lang": "zh"
-       },
-       {
-               "name": "zh undelete_short 1",
-               "key": "zh_undelete_short",
-               "args": [
-                       1
-               ],
-               "result": "\u6062\u590d\u88ab\u5220\u9664\u76841\u9879\u4fee\u8ba2",
-               "lang": "zh"
-       },
-       {
-               "name": "zh undelete_short 2",
-               "key": "zh_undelete_short",
-               "args": [
-                       2
-               ],
-               "result": "\u6062\u590d\u88ab\u5220\u9664\u76842\u9879\u4fee\u8ba2",
-               "lang": "zh"
-       },
-       {
-               "name": "zh undelete_short 5",
-               "key": "zh_undelete_short",
-               "args": [
-                       5
-               ],
-               "result": "\u6062\u590d\u88ab\u5220\u9664\u76845\u9879\u4fee\u8ba2",
-               "lang": "zh"
-       },
-       {
-               "name": "zh undelete_short 21",
-               "key": "zh_undelete_short",
-               "args": [
-                       21
-               ],
-               "result": "\u6062\u590d\u88ab\u5220\u9664\u768421\u9879\u4fee\u8ba2",
-               "lang": "zh"
-       },
-       {
-               "name": "zh undelete_short 101",
-               "key": "zh_undelete_short",
-               "args": [
-                       101
-               ],
-               "result": "\u6062\u590d\u88ab\u5220\u9664\u7684101\u9879\u4fee\u8ba2",
-               "lang": "zh"
-       },
-       {
-               "name": "zh category-subcat-count 0,10",
-               "key": "zh_category-subcat-count",
-               "args": [
-                       0,
-                       10
-               ],
-               "result": "\u672c\u5206\u7c7b\u5305\u542b\u4e0b\u52170\u4e2a\u5b50\u5206\u7c7b\uff0c\u5171\u670910\u4e2a\u5b50\u5206\u7c7b\u3002",
-               "lang": "zh"
-       },
-       {
-               "name": "zh category-subcat-count 1,1",
-               "key": "zh_category-subcat-count",
-               "args": [
-                       1,
-                       1
-               ],
-               "result": "\u672c\u5206\u7c7b\u53ea\u6709\u4e0b\u5217\u4e00\u4e2a\u5b50\u5206\u7c7b\u3002",
-               "lang": "zh"
-       },
-       {
-               "name": "zh category-subcat-count 1,2",
-               "key": "zh_category-subcat-count",
-               "args": [
-                       1,
-                       2
-               ],
-               "result": "\u672c\u5206\u7c7b\u5305\u542b\u4e0b\u52171\u4e2a\u5b50\u5206\u7c7b\uff0c\u5171\u67092\u4e2a\u5b50\u5206\u7c7b\u3002",
-               "lang": "zh"
-       },
-       {
-               "name": "zh category-subcat-count 3,30",
-               "key": "zh_category-subcat-count",
-               "args": [
-                       3,
-                       30
-               ],
-               "result": "\u672c\u5206\u7c7b\u5305\u542b\u4e0b\u52173\u4e2a\u5b50\u5206\u7c7b\uff0c\u5171\u670930\u4e2a\u5b50\u5206\u7c7b\u3002",
-               "lang": "zh"
-       }
-];
diff --git a/tests/jasmine/spec/mediawiki.jqueryMsg.spec.js b/tests/jasmine/spec/mediawiki.jqueryMsg.spec.js
deleted file mode 100644 (file)
index 46dcaa8..0000000
+++ /dev/null
@@ -1,389 +0,0 @@
-/* spec for language & message behaviour in MediaWiki */
-
-mw.messages.set( {
-       "en_empty": "",
-       "en_simple": "Simple message",
-       "en_replace": "Simple $1 replacement",
-       "en_replace2": "Simple $1 $2 replacements",
-       "en_link": "Simple [http://example.com link to example].",
-       "en_link_replace": "Complex [$1 $2] behaviour.",
-       "en_simple_magic": "Simple {{ALOHOMORA}} message",
-       "en_undelete_short": "Undelete {{PLURAL:$1|one edit|$1 edits}}",
-       "en_undelete_empty_param": "Undelete{{PLURAL:$1|| multiple edits}}",
-       "en_category-subcat-count": "{{PLURAL:$2|This category has only the following subcategory.|This category has the following {{PLURAL:$1|subcategory|$1 subcategories}}, out of $2 total.}}",
-       "en_escape0": "Escape \\to fantasy island",
-       "en_escape1": "I had \\$2.50 in my pocket",
-       "en_escape2": "I had {{PLURAL:$1|the absolute \\|$1\\| which came out to \\$3.00 in my C:\\\\drive| some stuff}}",
-       "en_fail": "This should fail to {{parse",
-       "en_fail_magic": "There is no such magic word as {{SIETNAME}}",
-       "en_evil": "This has <script type='text/javascript'>window.en_evil = true;</script> tags"
-} );
-
-/**
- * Tests
- */
-( function( mw, $, undefined ) {
-
-       describe( "mediaWiki.jqueryMsg", function() {
-               
-               describe( "basic message functionality", function() {
-
-                       it( "should return identity for empty string", function() {
-                               var parser = new mw.jqueryMsg.parser();
-                               expect( parser.parse( 'en_empty' ).html() ).toEqual( '' );
-                       } );
-
-
-                       it( "should return identity for simple string", function() {
-                               var parser = new mw.jqueryMsg.parser();
-                               expect( parser.parse( 'en_simple' ).html() ).toEqual( 'Simple message' );
-                       } );
-
-               } );
-
-               describe( "escaping", function() {
-
-                       it ( "should handle simple escaping", function() {
-                               var parser = new mw.jqueryMsg.parser();
-                               expect( parser.parse( 'en_escape0' ).html() ).toEqual( 'Escape to fantasy island' );
-                       } );
-
-                       it ( "should escape dollar signs found in ordinary text when backslashed", function() {
-                               var parser = new mw.jqueryMsg.parser();
-                               expect( parser.parse( 'en_escape1' ).html() ).toEqual( 'I had $2.50 in my pocket' );
-                       } );
-
-                       it ( "should handle a complicated escaping case, including escaped pipe chars in template args", function() {
-                               var parser = new mw.jqueryMsg.parser();
-                               expect( parser.parse( 'en_escape2', [ 1 ] ).html() ).toEqual( 'I had the absolute |1| which came out to $3.00 in my C:\\drive' );
-                       } );
-
-               } );
-
-               describe( "replacing", function() {
-
-                       it ( "should handle simple replacing", function() {
-                               var parser = new mw.jqueryMsg.parser();
-                               expect( parser.parse( 'en_replace', [ 'foo' ] ).html() ).toEqual( 'Simple foo replacement' );
-                       } );
-
-                       it ( "should return $n if replacement not there", function() {
-                               var parser = new mw.jqueryMsg.parser();
-                               expect( parser.parse( 'en_replace', [] ).html() ).toEqual( 'Simple $1 replacement' );
-                               expect( parser.parse( 'en_replace2', [ 'bar' ] ).html() ).toEqual( 'Simple bar $2 replacements' );
-                       } );
-
-               } );
-
-               describe( "linking", function() {
-
-                       it ( "should handle a simple link", function() {
-                               var parser = new mw.jqueryMsg.parser();
-                               var parsed = parser.parse( 'en_link' );
-                               var contents = parsed.contents();
-                               expect( contents.length ).toEqual( 3 );
-                               expect( contents[0].nodeName ).toEqual( '#text' );
-                               expect( contents[0].nodeValue ).toEqual( 'Simple ' );
-                               expect( contents[1].nodeName ).toEqual( 'A' );
-                               expect( contents[1].getAttribute( 'href' ) ).toEqual( 'http://example.com' );
-                               expect( contents[1].childNodes[0].nodeValue ).toEqual( 'link to example' );
-                               expect( contents[2].nodeName ).toEqual( '#text' );
-                               expect( contents[2].nodeValue ).toEqual( '.' );
-                       } );
-
-                       it ( "should replace a URL into a link", function() {
-                               var parser = new mw.jqueryMsg.parser();
-                               var parsed = parser.parse( 'en_link_replace', [ 'http://example.com/foo', 'linking' ] );
-                               var contents = parsed.contents();
-                               expect( contents.length ).toEqual( 3 );
-                               expect( contents[0].nodeName ).toEqual( '#text' );
-                               expect( contents[0].nodeValue ).toEqual( 'Complex ' );
-                               expect( contents[1].nodeName ).toEqual( 'A' );
-                               expect( contents[1].getAttribute( 'href' ) ).toEqual( 'http://example.com/foo' );
-                               expect( contents[1].childNodes[0].nodeValue ).toEqual( 'linking' );
-                               expect( contents[2].nodeName ).toEqual( '#text' );
-                               expect( contents[2].nodeValue ).toEqual( ' behaviour.' );
-                       } );
-
-                       it ( "should bind a click handler into a link", function() {
-                               var parser = new mw.jqueryMsg.parser();
-                               var clicked = false;
-                               var click = function() { clicked = true; };
-                               var parsed = parser.parse( 'en_link_replace', [ click, 'linking' ] );
-                               var contents = parsed.contents();
-                               expect( contents.length ).toEqual( 3 );
-                               expect( contents[0].nodeName ).toEqual( '#text' );
-                               expect( contents[0].nodeValue ).toEqual( 'Complex ' );
-                               expect( contents[1].nodeName ).toEqual( 'A' );
-                               expect( contents[1].getAttribute( 'href' ) ).toEqual( '#' );
-                               expect( contents[1].childNodes[0].nodeValue ).toEqual( 'linking' );
-                               expect( contents[2].nodeName ).toEqual( '#text' );
-                               expect( contents[2].nodeValue ).toEqual( ' behaviour.' );
-                               // determining bindings is hard in IE
-                               var anchor = parsed.find( 'a' );
-                               if ( ( $.browser.mozilla || $.browser.webkit ) && anchor.click ) {
-                                       expect( clicked ).toEqual( false );
-                                       anchor.click(); 
-                                       expect( clicked ).toEqual( true );
-                               }
-                       } );
-
-                       it ( "should wrap a jquery arg around link contents -- even another element", function() {
-                               var parser = new mw.jqueryMsg.parser();
-                               var clicked = false;
-                               var click = function() { clicked = true; };
-                               var button = $( '<button>' ).click( click );
-                               var parsed = parser.parse( 'en_link_replace', [ button, 'buttoning' ] );
-                               var contents = parsed.contents();
-                               expect( contents.length ).toEqual( 3 );
-                               expect( contents[0].nodeName ).toEqual( '#text' );
-                               expect( contents[0].nodeValue ).toEqual( 'Complex ' );
-                               expect( contents[1].nodeName ).toEqual( 'BUTTON' );
-                               expect( contents[1].childNodes[0].nodeValue ).toEqual( 'buttoning' );
-                               expect( contents[2].nodeName ).toEqual( '#text' );
-                               expect( contents[2].nodeValue ).toEqual( ' behaviour.' );
-                               // determining bindings is hard in IE
-                               if ( ( $.browser.mozilla || $.browser.webkit ) && button.click ) {
-                                       expect( clicked ).toEqual( false );
-                                       parsed.find( 'button' ).click();
-                                       expect( clicked ).toEqual( true );
-                               }
-                       } );
-
-
-               } );
-
-
-               describe( "magic keywords", function() {
-                       it( "should substitute magic keywords", function() {
-                               var options = {
-                                       magic: { 
-                                               'alohomora' : 'open'
-                                       }
-                               };
-                               var parser = new mw.jqueryMsg.parser( options );
-                               expect( parser.parse( 'en_simple_magic' ).html() ).toEqual( 'Simple open message' );
-                       } );
-               } );
-               
-               describe( "error conditions", function() {
-                       it( "should return non-existent key in square brackets", function() {
-                               var parser = new mw.jqueryMsg.parser();
-                               expect( parser.parse( 'en_does_not_exist' ).html() ).toEqual( '[en_does_not_exist]' );
-                       } );
-
-
-                       it( "should fail to parse", function() {
-                               var parser = new mw.jqueryMsg.parser();
-                               expect( function() { parser.parse( 'en_fail' ); } ).toThrow( 
-                                       'Parse error at position 20 in input: This should fail to {{parse'
-                               );
-                       } );
-               } );
-
-               describe( "empty parameters", function() {
-                       it( "should deal with empty parameters", function() {
-                               var parser = new mw.jqueryMsg.parser();
-                               var ast = parser.getAst( 'en_undelete_empty_param' );
-                               expect( parser.parse( 'en_undelete_empty_param', [ 1 ] ).html() ).toEqual( 'Undelete' );
-                               expect( parser.parse( 'en_undelete_empty_param', [ 3 ] ).html() ).toEqual( 'Undelete multiple edits' );
-
-                       } );
-               } );
-
-               describe( "easy message interface functions", function() {
-                       it( "should allow a global that returns strings", function() {
-                               var gM = mw.jqueryMsg.getMessageFunction();
-                               // passing this through jQuery and back to string, because browsers may have subtle differences, like the case of tag names.
-                               // a surrounding <SPAN> is needed for html() to work right
-                               var expectedHtml = $( '<span>Complex <a href="http://example.com/foo">linking</a> behaviour.</span>' ).html();
-                               var result = gM( 'en_link_replace', 'http://example.com/foo', 'linking' );
-                               expect( typeof result ).toEqual( 'string' );
-                               expect( result ).toEqual( expectedHtml );
-                       } );
-
-                       it( "should allow a jQuery plugin that appends to nodes", function() {
-                               $.fn.msg = mw.jqueryMsg.getPlugin();
-                               var $div = $( '<div>' ).append( $( '<p>' ).addClass( 'foo' ) );
-                               var clicked = false;
-                               var $button = $( '<button>' ).click( function() { clicked = true; } );
-                               $div.find( '.foo' ).msg( 'en_link_replace', $button, 'buttoning' );
-                               // passing this through jQuery and back to string, because browsers may have subtle differences, like the case of tag names.
-                               // a surrounding <SPAN> is needed for html() to work right
-                               var expectedHtml = $( '<span>Complex <button>buttoning</button> behaviour.</span>' ).html();
-                               var createdHtml = $div.find( '.foo' ).html();
-                               // it is hard to test for clicks with IE; also it inserts or removes spaces around nodes when creating HTML tags, depending on their type.
-                               // so need to check the strings stripped of spaces.
-                               if ( ( $.browser.mozilla || $.browser.webkit ) && $button.click ) {
-                                       expect( createdHtml ).toEqual( expectedHtml );
-                                       $div.find( 'button ').click();
-                                       expect( clicked ).toEqual( true );
-                               } else if ( $.browser.ie ) {
-                                       expect( createdHtml.replace( /\s/, '' ) ).toEqual( expectedHtml.replace( /\s/, '' ) );
-                               }
-                               delete $.fn.msg;
-                       } );
-
-                       it( "jQuery plugin should escape incoming string arguments", function() {
-                               $.fn.msg = mw.jqueryMsg.getPlugin();
-                               var $div = $( '<div>' ).addClass( 'foo' );
-                               $div.msg( 'en_replace', '<p>x</p>' ); // looks like HTML, but as a string, should be escaped.
-                               // passing this through jQuery and back to string, because browsers may have subtle differences, like the case of tag names.
-                               var expectedHtml = $( '<div class="foo">Simple &lt;p&gt;x&lt;/p&gt; replacement</div>' ).html();
-                               var createdHtml = $div.html();
-                               expect( expectedHtml ).toEqual( createdHtml );
-                               delete $.fn.msg;
-                       } );
-
-
-                       it( "jQuery plugin should never execute scripts", function() {
-                               window.en_evil = false;
-                               $.fn.msg = mw.jqueryMsg.getPlugin();
-                               var $div = $( '<div>' );
-                               $div.msg( 'en_evil' );
-                               expect( window.en_evil ).toEqual( false );
-                               delete $.fn.msg;
-                       } );
-
-
-                       // n.b. this passes because jQuery already seems to strip scripts away; however, it still executes them if they are appended to any element.
-                       it( "jQuery plugin should never emit scripts", function() {
-                               $.fn.msg = mw.jqueryMsg.getPlugin();
-                               var $div = $( '<div>' );
-                               $div.msg( 'en_evil' );
-                               // passing this through jQuery and back to string, because browsers may have subtle differences, like the case of tag names.
-                               var expectedHtml = $( '<div>This has  tags</div>' ).html();
-                               var createdHtml = $div.html();
-                               expect( expectedHtml ).toEqual( createdHtml );
-                               console.log( 'expected: ' + expectedHtml );
-                               console.log( 'created: ' + createdHtml );
-                               delete $.fn.msg;
-                       } );
-
-
-
-               } );
-
-               // The parser functions can throw errors, but let's not actually blow up for the user -- instead dump the error into the interface so we have
-               // a chance at fixing this
-               describe( "easy message interface functions with graceful failures", function() {
-                       it( "should allow a global that returns strings, with graceful failure", function() {
-                               var gM = mw.jqueryMsg.getMessageFunction();
-                               // passing this through jQuery and back to string, because browsers may have subtle differences, like the case of tag names.
-                               // a surrounding <SPAN> is needed for html() to work right
-                               var expectedHtml = $( '<span>en_fail: Parse error at position 20 in input: This should fail to {{parse</span>' ).html();
-                               var result = gM( 'en_fail' );
-                               expect( typeof result ).toEqual( 'string' );
-                               expect( result ).toEqual( expectedHtml );
-                       } );
-
-                       it( "should allow a global that returns strings, with graceful failure on missing magic words", function() {
-                               var gM = mw.jqueryMsg.getMessageFunction();
-                               // passing this through jQuery and back to string, because browsers may have subtle differences, like the case of tag names.
-                               // a surrounding <SPAN> is needed for html() to work right
-                               var expectedHtml = $( '<span>en_fail_magic: unknown operation "sietname"</span>' ).html();
-                               var result = gM( 'en_fail_magic' );
-                               expect( typeof result ).toEqual( 'string' );
-                               expect( result ).toEqual( expectedHtml );
-                       } );
-
-
-                       it( "should allow a jQuery plugin, with graceful failure", function() {
-                               $.fn.msg = mw.jqueryMsg.getPlugin();
-                               var $div = $( '<div>' ).append( $( '<p>' ).addClass( 'foo' ) );
-                               $div.find( '.foo' ).msg( 'en_fail' );
-                               // passing this through jQuery and back to string, because browsers may have subtle differences, like the case of tag names.
-                               // a surrounding <SPAN> is needed for html() to work right
-                               var expectedHtml = $( '<span>en_fail: Parse error at position 20 in input: This should fail to {{parse</span>' ).html();
-                               var createdHtml = $div.find( '.foo' ).html();
-                               expect( createdHtml ).toEqual( expectedHtml );
-                               delete $.fn.msg;
-                       } );
-
-               } );
-
-
-
-
-               describe( "test plurals and other language-specific functions", function() {
-                       /* copying some language definitions in here -- it's hard to make this test fast and reliable 
-                          otherwise, and we don't want to have to know the mediawiki URL from this kind of test either.
-                          We also can't preload the langs for the test since they clobber the same namespace.
-                          In principle Roan said it was okay to change how languages worked so that didn't happen... maybe 
-                          someday. We'd have to the same kind of importing of the default rules for most rules, or maybe 
-                          come up with some kind of subclassing scheme for languages */
-                       var languageClasses = {
-                               ar: {
-                                       /**
-                                        * Arabic (العربية) language functions
-                                        */
-
-                                       convertPlural: function( count, forms ) {
-                                               forms = mw.language.preConvertPlural( forms, 6 );
-                                               if ( count === 0 ) {
-                                                       return forms[0];
-                                               }
-                                               if ( count == 1 ) {
-                                                       return forms[1];
-                                               }
-                                               if ( count == 2 ) {
-                                                       return forms[2];
-                                               }
-                                               if ( count % 100 >= 3 && count % 100 <= 10 ) {
-                                                       return forms[3];
-                                               }
-                                               if ( count % 100 >= 11 && count % 100 <= 99 ) {
-                                                       return forms[4];
-                                               }
-                                               return forms[5];
-                                       },
-
-                                       digitTransformTable: {
-                                           '0': '٠', // &#x0660;
-                                           '1': '١', // &#x0661;
-                                           '2': '٢', // &#x0662;
-                                           '3': '٣', // &#x0663;
-                                           '4': '٤', // &#x0664;
-                                           '5': '٥', // &#x0665;
-                                           '6': '٦', // &#x0666;
-                                           '7': '٧', // &#x0667;
-                                           '8': '٨', // &#x0668;
-                                           '9': '٩', // &#x0669;
-                                           '.': '٫', // &#x066b; wrong table ?
-                                           ',': '٬' // &#x066c;
-                                       }
-
-                               },
-                               en: { },
-                               fr: {
-                                       convertPlural: function( count, forms ) {
-                                               forms = mw.language.preConvertPlural( forms, 2 );
-                                               return ( count <= 1 ) ? forms[0] : forms[1];
-                                       }
-                               },
-                               jp: { },
-                               zh: { }
-                       };
-
-                       /* simulate how the language classes override, or don't, the standard functions in mw.language */
-                       $.each( languageClasses, function( langCode, rules ) { 
-                               $.each( [ 'convertPlural', 'convertNumber' ], function( i, propertyName ) { 
-                                       if ( typeof rules[ propertyName ] === 'undefined' ) {
-                                               rules[ propertyName ] = mw.language[ propertyName ];
-                                       }
-                               } );
-                       } );
-
-                       $.each( jasmineMsgSpec, function( i, test ) { 
-                               it( "should parse " + test.name, function() { 
-                                       // using language override so we don't have to muck with global namespace
-                                       var parser = new mw.jqueryMsg.parser( { language: languageClasses[ test.lang ] } );
-                                       var parsedHtml = parser.parse( test.key, test.args ).html();
-                                       expect( parsedHtml ).toEqual( test.result );
-                               } );
-                       } );
-
-               } );
-
-       } );
-} )( window.mediaWiki, jQuery );
diff --git a/tests/jasmine/spec_makers/makeJqueryMsgSpec.php b/tests/jasmine/spec_makers/makeJqueryMsgSpec.php
deleted file mode 100644 (file)
index 840da96..0000000
+++ /dev/null
@@ -1,110 +0,0 @@
-<?php
-
-/**
- * This PHP script defines the spec that the Javascript message parser should conform to.
- *
- * It does this by looking up the results of various string kinds of string parsing, with various languages,
- * in the current installation of MediaWiki. It then outputs a static specification, mapping expected inputs to outputs,
- * which can be used with the JasmineBDD framework. This specification can then be used by simply including it into
- * the SpecRunner.html file.
- *
- * This is similar to Michael Dale (mdale@mediawiki.org)'s parser tests, except that it doesn't look up the
- * API results while doing the test, so the Jasmine run is much faster(at the cost of being out of date in rare
- * circumstances. But mostly the parsing that we are doing in Javascript doesn't change much.)
- *
- */
-
-$maintenanceDir = dirname( dirname( dirname( __DIR__ ) ) ) . '/maintenance';
-
-require( "$maintenanceDir/Maintenance.php" );
-
-class MakeLanguageSpec extends Maintenance {
-
-       static $keyToTestArgs = array(
-               'undelete_short' => array( 
-                       array( 0 ), 
-                       array( 1 ), 
-                       array( 2 ), 
-                       array( 5 ), 
-                       array( 21 ), 
-                       array( 101 ) 
-               ),
-               'category-subcat-count' => array(  
-                       array( 0, 10 ), 
-                       array( 1, 1 ), 
-                       array( 1, 2 ), 
-                       array( 3, 30 ) 
-               )
-       );
-
-       public function __construct() {
-                parent::__construct();
-                $this->mDescription = "Create a JasmineBDD-compatible specification for message parsing";
-                // add any other options here
-        }
-
-       public function execute() {
-               list( $messages, $tests ) = $this->getMessagesAndTests();
-               $this->writeJavascriptFile( $messages, $tests, "spec/mediawiki.language.parser.spec.data.js" );
-       }
-
-       private function getMessagesAndTests() {
-               $messages = array();
-               $tests = array();
-               foreach ( array( 'en', 'fr', 'ar', 'jp', 'zh' ) as $languageCode ) {
-                       foreach ( self::$keyToTestArgs as $key => $testArgs ) {
-                               foreach ($testArgs as $args) {
-                                       // get the raw template, without any transformations
-                                       $template = wfMessage( $key )->inLanguage( $languageCode )->plain();
-
-                                       $result = wfMessage( $key, $args )->inLanguage( $languageCode )->text();
-
-                                       // record the template, args, language, and expected result
-                                       // fake multiple languages by flattening them together
-                                       $langKey = $languageCode . '_' . $key;
-                                       $messages[ $langKey ] = $template;
-                                       $tests[] = array(
-                                               'name' => $languageCode . " " . $key . " " . join( ",", $args ),
-                                               'key' => $langKey,
-                                               'args' => $args,
-                                               'result' => $result,
-                                               'lang' => $languageCode
-                                       );
-                               }
-                       }
-               }
-               return array( $messages, $tests );
-       }
-
-       private function writeJavascriptFile( $messages, $tests, $dataSpecFile ) {
-               global $argv;
-               $arguments = count($argv) ? $argv : $_SERVER[ 'argv' ];
-
-               $json = new Services_JSON;
-               $json->pretty = true;
-               $javascriptPrologue = "// This file stores the results from the PHP parser for certain messages and arguments,\n"
-                                     . "// so we can test the equivalent Javascript libraries.\n"
-                                     . '// Last generated with ' . join(' ', $arguments) . ' at ' . gmdate('c') . "\n\n";
-               $javascriptMessages = "mediaWiki.messages.set( " . $json->encode( $messages, true ) . " );\n";
-               $javascriptTests = 'var jasmineMsgSpec = ' . $json->encode( $tests, true ) . ";\n";
-
-               $fp = fopen( $dataSpecFile, 'w' );
-               if ( !$fp ) {
-                       die( "couldn't open $dataSpecFile for writing" );
-               }
-               $success = fwrite( $fp, $javascriptPrologue . $javascriptMessages . $javascriptTests );
-               if ( !$success ) { 
-                       die( "couldn't write to $dataSpecFile" );
-               }
-               $success = fclose( $fp );
-               if ( !$success ) {
-                       die( "couldn't close $dataSpecFile" );
-               }
-       }
-}
-
-$maintClass = "MakeLanguageSpec";
-require_once( "$maintenanceDir/doMaintenance.php" );
-
-
-
index 10ca5a5..fb0c848 100644 (file)
@@ -1206,7 +1206,7 @@ class ParserTest {
                        }
                }
 
-               $page->doEdit( $text, '', EDIT_NEW );
+               $page->doEditContent( ContentHandler::makeContent( $text, $title ), '', EDIT_NEW );
 
                $wgCapitalLinks = $oldCapitalLinks;
        }
index 6dd8ea3..3034601 100644 (file)
@@ -4,39 +4,26 @@
  * Base class that store and restore the Language objects
  */
 abstract class MediaWikiLangTestCase extends MediaWikiTestCase {
-       private static $oldLang;
-       private static $oldContLang;
-
-       public function setUp() {
-               global $wgLanguageCode, $wgLang, $wgContLang;
 
+       protected function setUp() {
+               global $wgLanguageCode, $wgContLang;
                parent::setUp();
 
-               self::$oldLang = $wgLang;
-               self::$oldContLang = $wgContLang;
-
-               if( $wgLanguageCode != $wgContLang->getCode() ) {
+               if ( $wgLanguageCode != $wgContLang->getCode() ) {
                        throw new MWException("Error in MediaWikiLangTestCase::setUp(): " .
                                "\$wgLanguageCode ('$wgLanguageCode') is different from " .
                                "\$wgContLang->getCode() (" . $wgContLang->getCode() . ")" );
                }
 
-               $wgLanguageCode = 'en'; # For mainpage to be 'Main Page'
+               $langCode = 'en'; # For mainpage to be 'Main Page'
+               $langObj = Language::factory( $langCode );
 
-               $wgContLang = $wgLang = Language::factory( $wgLanguageCode );
-               MessageCache::singleton()->disable();
-
-       }
+               $this->setMwGlobals( array(
+                       'wgLanguageCode' => $langCode,
+                       'wgLang' => $langObj,
+                       'wgContLang' => $langObj,
+               ) );
 
-       public function tearDown() {
-               global $wgContLang, $wgLang, $wgLanguageCode;
-               $wgLang = self::$oldLang;
-
-               $wgContLang = self::$oldContLang;
-               $wgLanguageCode = $wgContLang->getCode();
-               self::$oldContLang = self::$oldLang = null;
-
-               parent::tearDown();
+               MessageCache::singleton()->disable();
        }
-
 }
index 54a3f7c..ff13702 100644 (file)
@@ -29,6 +29,13 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
         */
        private $tmpfiles = array();
 
+       /**
+        * Holds original values of MediaWiki configuration settings
+        * to be restored in tearDown().
+        * See also setMwGlobal().
+        * @var array
+        */
+       private $mwGlobals = array();
 
        /**
         * Table name prefixes. Oracle likes it shorter.
@@ -119,6 +126,41 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
                return $fname;
        }
 
+       /**
+        * setUp and tearDown should (where significant)
+        * happen in reverse order.
+        */
+       protected function setUp() {
+               parent::setUp();
+
+               /*
+               //@todo: global variables to restore for *every* test
+               array(
+                       'wgLang',
+                       'wgContLang',
+                       'wgLanguageCode',
+                       'wgUser',
+                       'wgTitle',
+               );
+               */
+
+               // Cleaning up temporary files
+               foreach ( $this->tmpfiles as $fname ) {
+                       if ( is_file( $fname ) || ( is_link( $fname ) ) ) {
+                               unlink( $fname );
+                       } elseif ( is_dir( $fname ) ) {
+                               wfRecursiveRemoveDir( $fname );
+                       }
+               }
+
+               // Clean up open transactions
+               if ( $this->needsDB() && $this->db ) {
+                       while( $this->db->trxLevel() > 0 ) {
+                               $this->db->rollback();
+                       }
+               }
+       }
+
        protected function tearDown() {
                // Cleaning up temporary files
                foreach ( $this->tmpfiles as $fname ) {
@@ -129,16 +171,95 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
                        }
                }
 
-               // clean up open transactions
-               if( $this->needsDB() && $this->db ) {
+               // Clean up open transactions
+               if ( $this->needsDB() && $this->db ) {
                        while( $this->db->trxLevel() > 0 ) {
                                $this->db->rollback();
                        }
                }
 
+               // Restore mw globals
+               foreach ( $this->mwGlobals as $key => $value ) {
+                       $GLOBALS[$key] = $value;
+               }
+               $this->mwGlobals = array();
+
                parent::tearDown();
        }
 
+       /**
+        * Individual test functions may override globals (either directly or through this
+        * setMwGlobals() function), however one must call this method at least once for
+        * each key within the setUp().
+        * That way the key is added to the array of globals that will be reset afterwards
+        * in the tearDown(). And, equally important, that way all other tests are executed
+        * with the same settings (instead of using the unreliable local settings for most
+        * tests and fix it only for some tests).
+        *
+        * @example
+        * <code>
+        *     protected function setUp() {
+        *         $this->setMwGlobals( 'wgRestrictStuff', true );
+        *     }
+        *
+        *     function testFoo() {}
+        *
+        *     function testBar() {}
+        *         $this->assertTrue( self::getX()->doStuff() );
+        *
+        *         $this->setMwGlobals( 'wgRestrictStuff', false );
+        *         $this->assertTrue( self::getX()->doStuff() );
+        *     }
+        *
+        *     function testQuux() {}
+        * </code>
+        *
+        * @param array|string $pairs Key to the global variable, or an array
+        *  of key/value pairs.
+        * @param mixed $value Value to set the global to (ignored
+        *  if an array is given as first argument).
+        */
+       protected function setMwGlobals( $pairs, $value = null ) {
+               if ( !is_array( $pairs ) ) {
+                       $key = $pairs;
+                       $this->mwGlobals[$key] = $GLOBALS[$key];
+                       $GLOBALS[$key] = $value;
+               } else {
+                       foreach ( $pairs as $key => $value ) {
+                               $this->mwGlobals[$key] = $GLOBALS[$key];
+                               $GLOBALS[$key] = $value;
+                       }
+               }
+       }
+
+       /**
+        * Merges the given values into a MW global array variable.
+        * Useful for setting some entries in a configuration array, instead of
+        * setting the entire array.
+        *
+        * @param String $name The name of the global, as in wgFooBar
+        * @param Array $values The array containing the entries to set in that global
+        *
+        * @throws MWException if the designated global is not an array.
+        */
+       protected function mergeMwGlobalArrayValue( $name, $values ) {
+               if ( !isset( $GLOBALS[$name] ) ) {
+                       $merged = $values;
+               } else {
+                       if ( !is_array( $GLOBALS[$name] ) ) {
+                               throw new MWException( "MW global $name is not an array." );
+                       }
+
+                       //NOTE: do not use array_merge, it screws up for numeric keys.
+                       $merged = $GLOBALS[$name];
+                       foreach ( $values as $k => $v ) {
+                               $merged[$k] = $v;
+                       }
+               }
+
+               $this->setMwGlobals( $name, $merged );
+       }
+
        function dbPrefix() {
                return $this->db->getType() == 'oracle' ? self::ORA_DB_PREFIX : self::DB_PREFIX;
        }
@@ -212,11 +333,12 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
                //Make 1 page with 1 revision
                $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
                if ( !$page->getId() == 0 ) {
-                       $page->doEdit( 'UTContent',
-                                                       'UTPageSummary',
-                                                       EDIT_NEW,
-                                                       false,
-                                                       User::newFromName( 'UTSysop' ) );
+                       $page->doEditContent(
+                               new WikitextContent( 'UTContent' ),
+                               'UTPageSummary',
+                               EDIT_NEW,
+                               false,
+                               User::newFromName( 'UTSysop' ) );
                }
        }
 
index 17cee6e..967ffa1 100644 (file)
@@ -16,14 +16,14 @@ class ArticleTablesTest extends MediaWikiLangTestCase {
                $wgContLang = Language::factory( 'es' );
 
                $wgLang = Language::factory( 'fr' );
-               $status = $page->doEdit( '{{:{{int:history}}}}', 'Test code for bug 14404', 0, false, $user );
+               $status = $page->doEditContent( new WikitextContent( '{{:{{int:history}}}}' ), 'Test code for bug 14404', 0, false, $user );
                $templates1 = $title->getTemplateLinksFrom();
 
                $wgLang = Language::factory( 'de' );
                $page->mPreparedEdit = false; // In order to force the rerendering of the same wikitext
 
                // We need an edit, a purge is not enough to regenerate the tables
-               $status = $page->doEdit( '{{:{{int:history}}}}', 'Test code for bug 14404', EDIT_UPDATE, false, $user );
+               $status = $page->doEditContent( new WikitextContent( '{{:{{int:history}}}}' ), 'Test code for bug 14404', EDIT_UPDATE, false, $user );
                $templates2 = $title->getTemplateLinksFrom();
 
                $this->assertEquals( $templates1, $templates2 );
index 846d2b8..46aaa4b 100644 (file)
@@ -2,19 +2,25 @@
 
 class ArticleTest extends MediaWikiTestCase {
 
-       private $title; // holds a Title object
-       private $article; // holds an article
+       /**
+        * @var Title
+        */
+       private $title;
+       /**
+        * @var Article
+        */
+       private $article;
 
        /** creates a title object and its article object */
-       function setUp() {
-               $this->title   = Title::makeTitle( NS_MAIN, 'SomePage' );
+       protected function setUp() {
+               $this->title = Title::makeTitle( NS_MAIN, 'SomePage' );
                $this->article = new Article( $this->title );
 
        }
 
        /** cleanup title object and its article object */
-       function tearDown() {
-               $this->title   = null;
+       protected function tearDown() {
+               $this->title = null;
                $this->article = null;
 
        }
@@ -54,6 +60,9 @@ class ArticleTest extends MediaWikiTestCase {
         * Checks for the existence of the backwards compatibility static functions (forwarders to WikiPage class)
         */
        function testStaticFunctions() {
+               $this->hideDeprecated( 'Article::getAutosummary' );
+               $this->hideDeprecated( 'WikiPage::getAutosummary' );
+
                $this->assertEquals( WikiPage::selectFields(), Article::selectFields(),
                        "Article static functions" );
                $this->assertEquals( true, is_callable( "Article::onArticleCreate" ),
index 0c95b8d..5ac05ba 100644 (file)
@@ -11,17 +11,15 @@ class BlockTest extends MediaWikiLangTestCase {
        /* variable used to save up the blockID we insert in this test suite */
        private $blockId;
 
-       function setUp() {
-               global $wgContLang;
+       protected function setUp() {
                parent::setUp();
-               $wgContLang = Language::factory( 'en' );
+               $this->setMwGlobals( 'wgContLang', Language::factory( 'en' ) );
        }
 
        function addDBData() {
-               //$this->dumpBlocks();
 
                $user = User::newFromName( 'UTBlockee' );
-               if( $user->getID() == 0 ) {
+               if ( $user->getID() == 0 ) {
                        $user->addToDatabase();
                        $user->setPassword( 'UTBlockeePassword' );
 
@@ -45,7 +43,7 @@ class BlockTest extends MediaWikiLangTestCase {
                // its value might change depending on the order the tests are run.
                // ApiBlockTest insert its own blocks!
                $newBlockId = $this->block->getId();
-               if ($newBlockId) {
+               if ( $newBlockId ) {
                        $this->blockId = $newBlockId;
                } else {
                        throw new MWException( "Failed to insert block for BlockTest; old leftover block remaining?" );
@@ -88,7 +86,7 @@ class BlockTest extends MediaWikiLangTestCase {
         *
         * This stopped working with r84475 and friends: regression being fixed for bug 29116.
         *
-        * @dataProvider dataBug29116
+        * @dataProvider provideBug29116Data
         */
        function testBug29116LoadWithEmptyIp( $vagueTarget ) {
                $this->hideDeprecated( 'Block::load' );
@@ -108,14 +106,14 @@ class BlockTest extends MediaWikiLangTestCase {
         * because the new function didn't accept empty strings like Block::load()
         * had. Regression bug 29116.
         *
-        * @dataProvider dataBug29116
+        * @dataProvider provideBug29116Data
         */
        function testBug29116NewFromTargetWithEmptyIp( $vagueTarget ) {
                $block = Block::newFromTarget('UTBlockee', $vagueTarget);
                $this->assertTrue( $this->block->equals( $block ), "newFromTarget() returns the same block as the one that was made when given empty vagueTarget param " . var_export( $vagueTarget, true ) );
        }
 
-       function dataBug29116() {
+       public static function provideBug29116Data() {
                return array(
                        array( null ),
                        array( '' ),
index b5418dd..97fffda 100644 (file)
@@ -6,7 +6,7 @@
 
 class CdbTest extends MediaWikiTestCase {
 
-       public function setUp() {
+       protected function setUp() {
                if ( !CdbReader::haveExtension() ) {
                        $this->markTestSkipped( 'Native CDB support is not available' );
                }
diff --git a/tests/phpunit/includes/ContentHandlerTest.php b/tests/phpunit/includes/ContentHandlerTest.php
new file mode 100644 (file)
index 0000000..797a3ee
--- /dev/null
@@ -0,0 +1,410 @@
+<?php
+
+/**
+ * @group ContentHandler
+ *
+ * @note: Declare that we are using the database, because otherwise we'll fail in the "databaseless" test run.
+ * This is because the LinkHolderArray used by the parser needs database access.
+ *
+ * @group Database
+ */
+class ContentHandlerTest extends MediaWikiTestCase {
+
+       public function setup() {
+               parent::setup();
+
+               global $wgExtraNamespaces, $wgNamespaceContentModels, $wgContentHandlers, $wgContLang;
+
+               $wgExtraNamespaces[ 12312 ] = 'Dummy';
+               $wgExtraNamespaces[ 12313 ] = 'Dummy_talk';
+
+               $wgNamespaceContentModels[ 12312 ] = "testing";
+               $wgContentHandlers[ "testing" ] = 'DummyContentHandlerForTesting';
+
+               MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache
+               $wgContLang->resetNamespaces(); # reset namespace cache
+       }
+
+       public function teardown() {
+               global $wgExtraNamespaces, $wgNamespaceContentModels, $wgContentHandlers, $wgContLang;
+
+               unset( $wgExtraNamespaces[ 12312 ] );
+               unset( $wgExtraNamespaces[ 12313 ] );
+
+               unset( $wgNamespaceContentModels[ 12312 ] );
+               unset( $wgContentHandlers[ "testing" ] );
+
+               MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache
+               $wgContLang->resetNamespaces(); # reset namespace cache
+
+               parent::teardown();
+       }
+
+       public function dataGetDefaultModelFor() {
+               //NOTE: assume that the Help namespace default to wikitext content
+               return array(
+                       array( 'Help:Foo', CONTENT_MODEL_WIKITEXT ),
+                       array( 'Help:Foo.js', CONTENT_MODEL_WIKITEXT ),
+                       array( 'Help:Foo/bar.js', CONTENT_MODEL_WIKITEXT ),
+                       array( 'User:Foo', CONTENT_MODEL_WIKITEXT ),
+                       array( 'User:Foo.js', CONTENT_MODEL_WIKITEXT ),
+                       array( 'User:Foo/bar.js', CONTENT_MODEL_JAVASCRIPT ),
+                       array( 'User:Foo/bar.css', CONTENT_MODEL_CSS ),
+                       array( 'User talk:Foo/bar.css', CONTENT_MODEL_WIKITEXT ),
+                       array( 'User:Foo/bar.js.xxx', CONTENT_MODEL_WIKITEXT ),
+                       array( 'User:Foo/bar.xxx', CONTENT_MODEL_WIKITEXT ),
+                       array( 'MediaWiki:Foo.js', CONTENT_MODEL_JAVASCRIPT ),
+                       array( 'MediaWiki:Foo.css', CONTENT_MODEL_CSS ),
+                       array( 'MediaWiki:Foo.JS', CONTENT_MODEL_WIKITEXT ),
+                       array( 'MediaWiki:Foo.CSS', CONTENT_MODEL_WIKITEXT ),
+                       array( 'MediaWiki:Foo.css.xxx', CONTENT_MODEL_WIKITEXT ),
+               );
+       }
+
+       /**
+        * @dataProvider dataGetDefaultModelFor
+        */
+       public function testGetDefaultModelFor( $title, $expectedModelId ) {
+               $title = Title::newFromText( $title );
+               $this->assertEquals( $expectedModelId, ContentHandler::getDefaultModelFor( $title ) );
+       }
+       /**
+        * @dataProvider dataGetDefaultModelFor
+        */
+       public function testGetForTitle( $title, $expectedContentModel ) {
+               $title = Title::newFromText( $title );
+               $handler = ContentHandler::getForTitle( $title );
+               $this->assertEquals( $expectedContentModel, $handler->getModelID() );
+       }
+
+       public function dataGetLocalizedName() {
+               return array(
+                       array( null, null ),
+                       array( "xyzzy", null ),
+
+                       array( CONTENT_MODEL_JAVASCRIPT, '/javascript/i' ), //XXX: depends on content language
+               );
+       }
+
+       /**
+        * @dataProvider dataGetLocalizedName
+        */
+       public function testGetLocalizedName( $id, $expected ) {
+               $name = ContentHandler::getLocalizedName( $id );
+
+               if ( $expected ) {
+                       $this->assertNotNull( $name, "no name found for content model $id" );
+                       $this->assertTrue( preg_match( $expected, $name ) > 0 ,
+                                                               "content model name for #$id did not match pattern $expected" );
+               } else {
+                       $this->assertEquals( $id, $name, "localization of unknown model $id should have "
+                                                                                       . "fallen back to use the model id directly." );
+               }
+       }
+
+       public function dataGetPageLanguage() {
+               global $wgLanguageCode;
+
+               return array(
+                       array( "Main", $wgLanguageCode ),
+                       array( "Dummy:Foo", $wgLanguageCode ),
+                       array( "MediaWiki:common.js", 'en' ),
+                       array( "User:Foo/common.js", 'en' ),
+                       array( "MediaWiki:common.css", 'en' ),
+                       array( "User:Foo/common.css", 'en' ),
+                       array( "User:Foo", $wgLanguageCode ),
+
+                       array( CONTENT_MODEL_JAVASCRIPT, 'javascript' ),
+               );
+       }
+
+       /**
+        * @dataProvider dataGetPageLanguage
+        */
+       public function testGetPageLanguage( $title, $expected ) {
+               if ( is_string( $title ) ) {
+                       $title = Title::newFromText( $title );
+               }
+
+               $expected = wfGetLangObj( $expected );
+
+               $handler = ContentHandler::getForTitle( $title );
+               $lang = $handler->getPageLanguage( $title );
+
+               $this->assertEquals( $expected->getCode(), $lang->getCode() );
+       }
+
+       public function testGetContentText_Null( ) {
+               global $wgContentHandlerTextFallback;
+
+               $content = null;
+
+               $wgContentHandlerTextFallback = 'fail';
+               $text = ContentHandler::getContentText( $content );
+               $this->assertEquals( '', $text );
+
+               $wgContentHandlerTextFallback = 'serialize';
+               $text = ContentHandler::getContentText( $content );
+               $this->assertEquals( '', $text );
+
+               $wgContentHandlerTextFallback = 'ignore';
+               $text = ContentHandler::getContentText( $content );
+               $this->assertEquals( '', $text );
+       }
+
+       public function testGetContentText_TextContent( ) {
+               global $wgContentHandlerTextFallback;
+
+               $content = new WikitextContent( "hello world" );
+
+               $wgContentHandlerTextFallback = 'fail';
+               $text = ContentHandler::getContentText( $content );
+               $this->assertEquals( $content->getNativeData(), $text );
+
+               $wgContentHandlerTextFallback = 'serialize';
+               $text = ContentHandler::getContentText( $content );
+               $this->assertEquals( $content->serialize(), $text );
+
+               $wgContentHandlerTextFallback = 'ignore';
+               $text = ContentHandler::getContentText( $content );
+               $this->assertEquals( $content->getNativeData(), $text );
+       }
+
+       public function testGetContentText_NonTextContent( ) {
+               global $wgContentHandlerTextFallback;
+
+               $content = new DummyContentForTesting( "hello world" );
+
+               $wgContentHandlerTextFallback = 'fail';
+
+               try {
+                       $text = ContentHandler::getContentText( $content );
+
+                       $this->fail( "ContentHandler::getContentText should have thrown an exception for non-text Content object" );
+               } catch (MWException $ex) {
+                       // as expected
+               }
+
+               $wgContentHandlerTextFallback = 'serialize';
+               $text = ContentHandler::getContentText( $content );
+               $this->assertEquals( $content->serialize(), $text );
+
+               $wgContentHandlerTextFallback = 'ignore';
+               $text = ContentHandler::getContentText( $content );
+               $this->assertNull( $text );
+       }
+
+       #public static function makeContent( $text, Title $title, $modelId = null, $format = null )
+
+       public function dataMakeContent() {
+               //NOTE: assume the Help namespace defaults to wikitext content
+               return array(
+                       array( 'hallo', 'Help:Test', null, null, CONTENT_MODEL_WIKITEXT, 'hallo', false ),
+                       array( 'hallo', 'MediaWiki:Test.js', null, null, CONTENT_MODEL_JAVASCRIPT, 'hallo', false ),
+                       array( serialize('hallo'), 'Dummy:Test', null, null, "testing", 'hallo', false ),
+
+                       array( 'hallo', 'Help:Test', null, CONTENT_FORMAT_WIKITEXT, CONTENT_MODEL_WIKITEXT, 'hallo', false ),
+                       array( 'hallo', 'MediaWiki:Test.js', null, CONTENT_FORMAT_JAVASCRIPT, CONTENT_MODEL_JAVASCRIPT, 'hallo', false ),
+                       array( serialize('hallo'), 'Dummy:Test', null, "testing", "testing", 'hallo', false ),
+
+                       array( 'hallo', 'Help:Test', CONTENT_MODEL_CSS, null, CONTENT_MODEL_CSS, 'hallo', false ),
+                       array( 'hallo', 'MediaWiki:Test.js', CONTENT_MODEL_CSS, null, CONTENT_MODEL_CSS, 'hallo', false ),
+                       array( serialize('hallo'), 'Dummy:Test', CONTENT_MODEL_CSS, null, CONTENT_MODEL_CSS, serialize('hallo'), false ),
+
+                       array( 'hallo', 'Help:Test', CONTENT_MODEL_WIKITEXT, "testing", null, null, true ),
+                       array( 'hallo', 'MediaWiki:Test.js', CONTENT_MODEL_CSS, "testing", null, null, true ),
+                       array( 'hallo', 'Dummy:Test', CONTENT_MODEL_JAVASCRIPT, "testing", null, null, true ),
+               );
+       }
+
+       /**
+        * @dataProvider dataMakeContent
+        */
+       public function testMakeContent( $data, $title, $modelId, $format, $expectedModelId, $expectedNativeData, $shouldFail ) {
+               global $wgExtraNamespaces, $wgNamespaceContentModels, $wgContentHandlers;
+
+               $title = Title::newFromText( $title );
+
+               try {
+                       $content = ContentHandler::makeContent( $data, $title, $modelId, $format );
+
+                       if ( $shouldFail ) $this->fail( "ContentHandler::makeContent should have failed!" );
+
+                       $this->assertEquals( $expectedModelId, $content->getModel(), 'bad model id' );
+                       $this->assertEquals( $expectedNativeData, $content->getNativeData(), 'bads native data' );
+               } catch ( MWException $ex ) {
+                       if ( !$shouldFail ) $this->fail( "ContentHandler::makeContent failed unexpectedly: " . $ex->getMessage() );
+                       else $this->assertTrue( true ); // dummy, so we don't get the "test did not perform any assertions" message.
+               }
+
+       }
+
+       public function testSupportsSections() {
+               $this->markTestIncomplete( "not yet implemented" );
+       }
+
+       public function testRunLegacyHooks() {
+               Hooks::register( 'testRunLegacyHooks', __CLASS__ . '::dummyHookHandler' );
+
+               $content = new WikitextContent( 'test text' );
+               $ok = ContentHandler::runLegacyHooks( 'testRunLegacyHooks', array( 'foo', &$content, 'bar' ), false );
+
+               $this->assertTrue( $ok, "runLegacyHooks should have returned true" );
+               $this->assertEquals( "TEST TEXT", $content->getNativeData() );
+       }
+
+       public static function dummyHookHandler( $foo, &$text, $bar ) {
+               if ( $text === null || $text === false ) {
+                       return false;
+               }
+
+               $text = strtoupper( $text );
+
+               return true;
+       }
+}
+
+class DummyContentHandlerForTesting extends ContentHandler {
+
+       public function __construct( $dataModel ) {
+               parent::__construct( $dataModel, array( "testing" ) );
+       }
+
+       /**
+        * Serializes Content object of the type supported by this ContentHandler.
+        *
+        * @param Content $content the Content object to serialize
+        * @param null $format the desired serialization format
+        * @return String serialized form of the content
+        */
+       public function serializeContent( Content $content, $format = null )
+       {
+          return $content->serialize();
+       }
+
+       /**
+        * Unserializes a Content object of the type supported by this ContentHandler.
+        *
+        * @param $blob String serialized form of the content
+        * @param null $format the format used for serialization
+        * @return Content the Content object created by deserializing $blob
+        */
+       public function unserializeContent( $blob, $format = null )
+       {
+               $d = unserialize( $blob );
+               return new DummyContentForTesting( $d );
+       }
+
+       /**
+        * Creates an empty Content object of the type supported by this ContentHandler.
+        *
+        */
+       public function makeEmptyContent()
+       {
+               return new DummyContentForTesting( '' );
+       }
+}
+
+class DummyContentForTesting extends AbstractContent {
+
+       public function __construct( $data ) {
+               parent::__construct( "testing" );
+
+               $this->data = $data;
+       }
+
+       public function serialize( $format = null ) {
+               return serialize( $this->data );
+       }
+
+       /**
+        * @return String a string representing the content in a way useful for building a full text search index.
+        *         If no useful representation exists, this method returns an empty string.
+        */
+       public function getTextForSearchIndex() {
+               return '';
+       }
+
+       /**
+        * @return String the wikitext to include when another page includes this  content, or false if the content is not
+        *         includable in a wikitext page.
+        */
+       public function getWikitextForTransclusion() {
+               return false;
+       }
+
+       /**
+        * Returns a textual representation of the content suitable for use in edit summaries and log messages.
+        *
+        * @param int $maxlength maximum length of the summary text
+        * @return String the summary text
+        */
+       public function getTextForSummary( $maxlength = 250 ) {
+               return '';
+       }
+
+       /**
+        * Returns native represenation of the data. Interpretation depends on the data model used,
+        * as given by getDataModel().
+        *
+        * @return mixed the native representation of the content. Could be a string, a nested array
+        *         structure, an object, a binary blob... anything, really.
+        */
+       public function getNativeData()
+       {
+               return $this->data;
+       }
+
+       /**
+        * returns the content's nominal size in bogo-bytes.
+        *
+        * @return int
+        */
+       public function getSize() {
+               return strlen( $this->data );
+       }
+
+       /**
+        * Return a copy of this Content object. The following must be true for the object returned
+        * if $copy = $original->copy()
+        *
+        * * get_class($original) === get_class($copy)
+        * * $original->getModel() === $copy->getModel()
+        * * $original->equals( $copy )
+        *
+        * If and only if the Content object is imutable, the copy() method can and should
+        * return $this. That is,  $copy === $original may be true, but only for imutable content
+        * objects.
+        *
+        * @return Content. A copy of this object
+        */
+       public function copy() {
+               return $this;
+       }
+
+       /**
+        * Returns true if this content is countable as a "real" wiki page, provided
+        * that it's also in a countable location (e.g. a current revision in the main namespace).
+        *
+        * @param $hasLinks Bool: if it is known whether this content contains links, provide this information here,
+        *                        to avoid redundant parsing to find out.
+        * @return boolean
+        */
+       public function isCountable( $hasLinks = null ) {
+               return false;
+       }
+
+       /**
+        * @param Title $title
+        * @param null $revId
+        * @param null|ParserOptions $options
+        * @param Boolean $generateHtml whether to generate Html (default: true). If false,
+        *        the result of calling getText() on the ParserOutput object returned by
+        *        this method is undefined.
+        *
+        * @return ParserOutput
+        */
+       public function getParserOutput( Title $title, $revId = null, ParserOptions $options = NULL, $generateHtml = true ) {
+               return new ParserOutput( $this->getNativeData() );
+       }
+}
diff --git a/tests/phpunit/includes/CssContentTest.php b/tests/phpunit/includes/CssContentTest.php
new file mode 100644 (file)
index 0000000..ebbece2
--- /dev/null
@@ -0,0 +1,48 @@
+<?php
+
+/**
+ * @group ContentHandler
+ *
+ * @group Database
+ *        ^--- needed, because we do need the database to test link updates
+ */
+class CssContentTest extends JavascriptContentTest {
+
+       public function newContent( $text ) {
+               return new CssContent( $text );
+       }
+
+
+       public function dataGetParserOutput() {
+               return array(
+                       array("MediaWiki:Test.css", null, "hello <world>\n",
+                               "<pre class=\"mw-code mw-css\" dir=\"ltr\">\nhello &lt;world&gt;\n\n</pre>\n"),
+                       // @todo: more...?
+               );
+       }
+
+
+       # =================================================================================================================
+
+       public function testGetModel() {
+               $content = $this->newContent( "hello world." );
+
+               $this->assertEquals( CONTENT_MODEL_CSS, $content->getModel() );
+       }
+
+       public function testGetContentHandler() {
+               $content = $this->newContent( "hello world." );
+
+               $this->assertEquals( CONTENT_MODEL_CSS, $content->getContentHandler()->getModelID() );
+       }
+
+       public function dataEquals( ) {
+               return array(
+                       array( new CssContent( "hallo" ), null, false ),
+                       array( new CssContent( "hallo" ), new CssContent( "hallo" ), true ),
+                       array( new CssContent( "hallo" ), new WikitextContent( "hallo" ), false ),
+                       array( new CssContent( "hallo" ), new CssContent( "HALLO" ), false ),
+               );
+       }
+
+}
index cdb6ed2..d46e683 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 
 class DiffHistoryBlobTest extends MediaWikiTestCase {
-       function setUp() {
+       protected function setUp() {
                if ( !extension_loaded( 'xdiff' ) ) {
                        $this->markTestSkipped( 'The xdiff extension is not available' );
                        return;
@@ -28,7 +28,7 @@ class DiffHistoryBlobTest extends MediaWikiTestCase {
                        "Hash of " . addcslashes( $input, "\0..\37!@\@\177..\377" ) );
        }
 
-       function provideXdiffAdler32() {
+       public static function provideXdiffAdler32() {
                return array(
                        array( '', 'Empty string' ),
                        array( "\0", 'Null' ),
index 8ecfd7e..eee5f37 100644 (file)
@@ -6,14 +6,14 @@
 class EditPageTest extends MediaWikiTestCase {
 
        /**
-        * @dataProvider dataExtractSectionTitle
+        * @dataProvider provideExtractSectionTitle
         */
        function testExtractSectionTitle( $section, $title ) {
                $extracted = EditPage::extractSectionTitle( $section );
                $this->assertEquals( $title, $extracted );
        }
 
-       function dataExtractSectionTitle() {
+       public static function provideExtractSectionTitle() {
                return array(
                        array(
                                "== Test ==\n\nJust a test section.",
index 92ec734..fe6c60d 100644 (file)
@@ -4,29 +4,78 @@
  */
 
 class ExternalStoreTest extends MediaWikiTestCase {
-       private $saved_wgExternalStores;
 
-       function setUp() {
-               global $wgExternalStores;
-               $this->saved_wgExternalStores = $wgExternalStores ;
-       }
+       function testExternalFetchFromURL() {
+               $this->setMwGlobals( 'wgExternalStores', false );
 
-       function tearDown() {
-               global $wgExternalStores;
-               $wgExternalStores = $this->saved_wgExternalStores ;
-       }
+               $this->assertFalse(
+                       ExternalStore::fetchFromURL( 'FOO://cluster1/200' ),
+                       'Deny if wgExternalStores is not set to a non-empty array'
+               );
 
-       function testExternalStoreDoesNotFetchIncorrectURL() {
-               global $wgExternalStores;
-               $wgExternalStores = true;
+               $this->setMwGlobals( 'wgExternalStores', array( 'FOO' ) );
 
+               $this->assertEquals(
+                       ExternalStore::fetchFromURL( 'FOO://cluster1/200' ),
+                       'Hello',
+                       'Allow FOO://cluster1/200'
+               );
+               $this->assertEquals(
+                       ExternalStore::fetchFromURL( 'FOO://cluster1/300/0' ),
+                       'Hello',
+                       'Allow FOO://cluster1/300/0'
+               );
                # Assertions for r68900
                $this->assertFalse(
-                       ExternalStore::fetchFromURL( 'http://' ) );
+                       ExternalStore::fetchFromURL( 'ftp.example.org' ),
+                       'Deny domain ftp.example.org'
+               );
                $this->assertFalse(
-                       ExternalStore::fetchFromURL( 'ftp.wikimedia.org' ) );
+                       ExternalStore::fetchFromURL( '/example.txt' ),
+                       'Deny path /example.txt'
+               );
                $this->assertFalse(
-                       ExternalStore::fetchFromURL( '/super.txt' ) );
+                       ExternalStore::fetchFromURL( 'http://' ),
+                       'Deny protocol http://'
+               );
        }
 }
 
+class ExternalStoreFOO {
+
+       protected $data = array(
+               'cluster1' => array(
+                       '200' => 'Hello',
+                       '300' => array(
+                               'Hello', 'World',
+                       ),
+               ),
+       );
+
+       /**
+        * Fetch data from given URL
+        * @param $url String: an url of the form FOO://cluster/id or FOO://cluster/id/itemid.
+        * @return mixed
+        */
+       function fetchFromURL( $url ) {
+               // Based on ExternalStoreDB
+               $path = explode( '/', $url );
+               $cluster = $path[2];
+               $id = $path[3];
+               if ( isset( $path[4] ) ) {
+                       $itemID = $path[4];
+               } else {
+                       $itemID = false;
+               }
+
+               if ( !isset( $this->data[$cluster][$id] ) ) {
+                       return null;
+               }
+
+               if ( $itemID !== false && is_array( $this->data[$cluster][$id] ) && isset( $this->data[$cluster][$id][$itemID] ) ) {
+                       return $this->data[$cluster][$id][$itemID];
+               }
+
+               return $this->data[$cluster][$id];
+       }
+}
\ No newline at end of file
index 903a6d2..14bc0bb 100644 (file)
@@ -5,18 +5,18 @@
  */
 class ExtraParserTest extends MediaWikiTestCase {
 
-       function setUp() {
-               global $wgMemc;
-               global $wgContLang;
-               global $wgShowDBErrorBacktrace;
-               global $wgLanguageCode;
-               global $wgAlwaysUseTidy;
+       protected function setUp() {
+               parent::setUp();
 
-               $wgShowDBErrorBacktrace = true;
-               $wgLanguageCode = 'en';
-               $wgContLang = new Language( 'en' );
-               $wgMemc = new EmptyBagOStuff;
-               $wgAlwaysUseTidy = false;
+               $this->setMwGlobals( array(
+                       'wgShowDBErrorBacktrace' => true,
+                       'wgLanguageCode' => 'en',
+                       'wgContLang' => Language::factory( 'en' ),
+                       'wgLang' => Language::factory( 'en' ),
+                       'wgMemc' => new EmptyBagOStuff,
+                       'wgAlwaysUseTidy' => false,
+                       'wgCleanSignatures' => true,
+               ) );
                
                $this->options = new ParserOptions;
                $this->options->setTemplateCallback( array( __CLASS__, 'statelessFetchTemplate' ) );
@@ -27,11 +27,8 @@ class ExtraParserTest extends MediaWikiTestCase {
 
        // Bug 8689 - Long numeric lines kill the parser
        function testBug8689() {
-               global $wgLang;
                global $wgUser;
                $longLine = '1.' . str_repeat( '1234567890', 100000 ) . "\n";
-
-               if ( $wgLang === null ) $wgLang = new Language;
                
                $t = Title::newFromText( 'Unit test' );
                $options = ParserOptions::newFromUser( $wgUser );
@@ -65,14 +62,8 @@ class ExtraParserTest extends MediaWikiTestCase {
         * cleanSig() makes all templates substs and removes tildes
         */
        function testCleanSig() {
-               global $wgCleanSignatures;
-               $oldCleanSignature = $wgCleanSignatures;
-               $wgCleanSignatures = true;
-
                $title = Title::newFromText( __FUNCTION__ );
                $outputText = $this->parser->cleanSig( "{{Foo}} ~~~~" );
-
-               $wgCleanSignatures = $oldCleanSignature;
                
                $this->assertEquals( "{{SUBST:Foo}} ", $outputText );
        }
@@ -82,13 +73,10 @@ class ExtraParserTest extends MediaWikiTestCase {
         */
        function testCleanSigDisabled() {
                global $wgCleanSignatures;
-               $oldCleanSignature = $wgCleanSignatures;
                $wgCleanSignatures = false;
 
                $title = Title::newFromText( __FUNCTION__ );
                $outputText = $this->parser->cleanSig( "{{Foo}} ~~~~" );
-
-               $wgCleanSignatures = $oldCleanSignature;
                
                $this->assertEquals( "{{Foo}} ~~~~", $outputText );
        }
@@ -101,7 +89,7 @@ class ExtraParserTest extends MediaWikiTestCase {
                $this->assertEquals( Parser::cleanSigInSig( $in), $out );
        }
        
-       function provideStringsForCleanSigInSig() {
+       public static function provideStringsForCleanSigInSig() {
                return array(
                        array( "{{Foo}} ~~~~", "{{Foo}} " ),
                        array( "~~~", "" ),
index c042004..35c7f8f 100644 (file)
@@ -25,7 +25,7 @@
 class FauxResponseTest extends MediaWikiTestCase {
        var $response;
 
-       function setUp() {
+       protected function setUp() {
                $this->response = new FauxResponse;
        }
 
index 9097d30..4d53d06 100644 (file)
@@ -1,22 +1,32 @@
 <?php
 
 class GlobalTest extends MediaWikiTestCase {
-       function setUp() {
-               global $wgReadOnlyFile, $wgUrlProtocols;
-               $this->originals['wgReadOnlyFile'] = $wgReadOnlyFile;
-               $this->originals['wgUrlProtocols'] = $wgUrlProtocols;
-               $wgReadOnlyFile = tempnam( wfTempDir(), "mwtest_readonly" );
-               $wgUrlProtocols[] = 'file://';
-               unlink( $wgReadOnlyFile );
+       protected function setUp() {
+               parent::setUp();
+
+               $readOnlyFile = tempnam( wfTempDir(), "mwtest_readonly" );
+               unlink( $readOnlyFile );
+
+               $this->setMwGlobals( array(
+                       'wgReadOnlyFile' => $readOnlyFile,
+                       'wgUrlProtocols' => array(
+                               'http://',
+                               'https://',
+                               'mailto:',
+                               '//',
+                               'file://', # Non-default
+                       ),
+               ) );
        }
 
-       function tearDown() {
-               global $wgReadOnlyFile, $wgUrlProtocols;
+       protected function tearDown() {
+               global $wgReadOnlyFile;
+
                if ( file_exists( $wgReadOnlyFile ) ) {
                        unlink( $wgReadOnlyFile );
                }
-               $wgReadOnlyFile = $this->originals['wgReadOnlyFile'];
-               $wgUrlProtocols = $this->originals['wgUrlProtocols'];
+
+               parent::tearDown();
        }
 
        /** @dataProvider provideForWfArrayDiff2 */
@@ -27,7 +37,7 @@ class GlobalTest extends MediaWikiTestCase {
        }
 
        // @todo Provide more tests
-       public function provideForWfArrayDiff2() {
+       public static function provideForWfArrayDiff2() {
                // $a $b $expected
                return array(
                        array(
@@ -100,7 +110,7 @@ class GlobalTest extends MediaWikiTestCase {
                $this->assertTrue( $end > $start, "Time is running backwards!" );
        }
 
-       function dataArrayToCGI() {
+       public static function provideArrayToCGI() {
                return array(
                        array( array(), '' ), // empty
                        array( array( 'foo' => 'bar' ), 'foo=bar' ), // string test
@@ -119,7 +129,7 @@ class GlobalTest extends MediaWikiTestCase {
        }
 
        /**
-        * @dataProvider dataArrayToCGI
+        * @dataProvider provideArrayToCGI
         */
        function testArrayToCGI( $array, $result ) {
                $this->assertEquals( $result, wfArrayToCGI( $array ) );
@@ -134,7 +144,7 @@ class GlobalTest extends MediaWikiTestCase {
                                array( 'foo' => 'bar', 'baz' => 'overridden value' ) ) );
        }
 
-       function dataCgiToArray() {
+       public static function provideCgiToArray() {
                return array(
                        array( '', array() ), // empty
                        array( 'foo=bar', array( 'foo' => 'bar' ) ), // string
@@ -150,13 +160,13 @@ class GlobalTest extends MediaWikiTestCase {
        }
 
        /**
-        * @dataProvider dataCgiToArray
+        * @dataProvider provideCgiToArray
         */
        function testCgiToArray( $cgi, $result ) {
                $this->assertEquals( $result, wfCgiToArray( $cgi ) );
        }
 
-       function dataCgiRoundTrip() {
+       public static function provideCgiRoundTrip() {
                return array(
                        array( '' ),
                        array( 'foo=bar' ),
@@ -170,7 +180,7 @@ class GlobalTest extends MediaWikiTestCase {
        }
 
        /**
-        * @dataProvider dataCgiRoundTrip
+        * @dataProvider provideCgiRoundTrip
         */
        function testCgiRoundTrip( $cgi ) {
                $this->assertEquals( $cgi, wfArrayToCGI( wfCgiToArray( $cgi ) ) );
@@ -437,7 +447,7 @@ class GlobalTest extends MediaWikiTestCase {
        }
 
        /** array( shorthand, expected integer ) */
-       public function provideShorthand() {
+       public static function provideShorthand() {
                return array(
                        # Null, empty ... 
                        array(     '', -1),
index be6c99e..bed435a 100644 (file)
@@ -19,7 +19,7 @@ class wfAssembleUrl extends MediaWikiTestCase {
         *
         * @return array
         */
-       public function provideURLParts() {
+       public static function provideURLParts() {
                $schemes = array(
                        '' => array(),
                        '//' => array(
index 8867cd5..cb6e6c4 100644 (file)
@@ -32,7 +32,7 @@ class wfExpandUrl extends MediaWikiTestCase {
         *
         * @return array
         */
-       public function provideExpandableUrls() {
+       public static function provideExpandableUrls() {
                $modes = array( 'http', 'https' );
                $servers = array(
                        'http' => 'http://example.com',
index 1cf0e0f..af784bd 100644 (file)
@@ -18,7 +18,7 @@ class wfRemoveDotSegments extends MediaWikiTestCase {
         *
         * @return array
         */
-       public function providePaths() {
+       public static function providePaths() {
                return array(
                        array( '/a/b/c/./../../g', '/a/g' ),
                        array( 'mid/content=5/../6', 'mid/6' ),
index cd1a8db..673702e 100644 (file)
@@ -85,7 +85,7 @@ class wfUrlencodeTest extends MediaWikiTestCase {
         * If you want to add other HTTP server name, you will have to add a new
         * testing method much like the testEncodingUrlWith() method above. 
         */
-       public function provideURLS() {
+       public static function provideURLS() {
                return array(
                ### RFC 1738 chars      
                        // + is not safe
index 2f9d9f8..e455f0f 100644 (file)
@@ -75,27 +75,62 @@ class HooksTest extends MediaWikiTestCase {
 
                $this->assertEquals( 'bah', $foo, 'Standard static method' );
                $foo = 'Foo';
+
+               Hooks::clear( 'MediaWikiHooksTest001' );
+       }
+
+       public function testNewStyleHookInteraction() {
+               global $wgHooks;
+
+               $a = new NothingClass();
+               $b = new NothingClass();
+
+               // make sure to start with a clean slate
+               Hooks::clear( 'MediaWikiHooksTest001' );
+               unset( $wgHooks['MediaWikiHooksTest001'] );
+
+               $wgHooks['MediaWikiHooksTest001'][] = $a;
+               $this->assertTrue( Hooks::isRegistered( 'MediaWikiHooksTest001' ), 'Hook registered via $wgHooks should be noticed by Hooks::isRegistered' );
+
+               Hooks::register( 'MediaWikiHooksTest001', $b );
+               $this->assertEquals( 2, count( Hooks::getHandlers( 'MediaWikiHooksTest001' ) ), 'Hooks::getHandlers() should return hooks registered via wgHooks as well as Hooks::register' );
+
+               $foo = 'quux';
+               $bar = 'qaax';
+
+               Hooks::run( 'MediaWikiHooksTest001', array( &$foo, &$bar ) );
+               $this->assertEquals( 1, $a->calls, 'Hooks::run() should run hooks registered via wgHooks as well as Hooks::register' );
+               $this->assertEquals( 1, $b->calls, 'Hooks::run() should run hooks registered via wgHooks as well as Hooks::register' );
+
+               // clean up
+               Hooks::clear( 'MediaWikiHooksTest001' );
+               unset( $wgHooks['MediaWikiHooksTest001'] );
        }
 }
 
 class NothingClass {
+       public $calls = 0;
+
        static public function someStatic( &$foo, &$bar ) {
                $foo = 'bah';
                return true;
        }
 
        public function someNonStatic( &$foo, &$bar ) {
+               $this->calls++;
                $foo = 'fOO';
                $bar = 'bAR';
                return true;
        }
 
        public function onMediaWikiHooksTest001( &$foo, &$bar ) {
+               $this->calls++;
                $foo = 'foo';
                return true;
        }
 
        public function someNonStaticWithData( $foo, &$bar ) {
+               $this->calls++;
                $bar = $foo;
                return true;
        }
index 6e936aa..95a6cb0 100644 (file)
@@ -2,29 +2,17 @@
 /** tests for includes/Html.php */
 
 class HtmlTest extends MediaWikiTestCase {
-       private static $oldLang;
-       private static $oldContLang;
-       private static $oldLanguageCode;
-       private static $oldNamespaces;
-       private static $oldHTML5;
 
-       public function setUp() {
-               global $wgLang, $wgContLang, $wgLanguageCode, $wgHtml5;
+       protected function setUp() {
+               parent::setUp();
 
-               // Save globals
-               self::$oldLang = $wgLang;
-               self::$oldContLang = $wgContLang;
-               self::$oldNamespaces = $wgContLang->getNamespaces();
-               self::$oldLanguageCode = $wgLanguageCode;
-               self::$oldHTML5 = $wgHtml5;
-
-               $wgLanguageCode = 'en';
-               $wgContLang = $wgLang = Language::factory( $wgLanguageCode );
+               $langCode = 'en';
+               $langObj = Language::factory( $langCode );
 
                // Hardcode namespaces during test runs,
                // so that html output based on existing namespaces
                // can be properly evaluated.
-               $wgContLang->setNamespaces( array(
+               $langObj->setNamespaces( array(
                        -2 => 'Media',
                        -1 => 'Special',
                        0  => '',
@@ -44,77 +32,102 @@ class HtmlTest extends MediaWikiTestCase {
                        100  => 'Custom',
                        101  => 'Custom_talk',
                ) );
+
+               $this->setMwGlobals( array(
+                       'wgLanguageCode' => $langCode,
+                       'wgContLang' => $langObj,
+                       'wgLang' => $langObj,
+                       'wgHtml5' => true,
+                       'wgWellFormedXml' => false,
+               ) );
        }
 
-       public function tearDown() {
-               global $wgLang, $wgContLang, $wgLanguageCode, $wgHtml5;
+       public function testElementBasics() {
+               global $wgWellFormedXml;
 
-               // Restore globals
-               $wgContLang->setNamespaces( self::$oldNamespaces );
-               $wgLang = self::$oldLang;
-               $wgContLang = self::$oldContLang;
-               $wgLanguageCode = self::$oldLanguageCode;
-               $wgHtml5 = self::$oldHTML5;
-       }
+               $this->assertEquals(
+                       '<img>',
+                       Html::element( 'img', null, '' ),
+                       'No close tag for short-tag elements'
+               );
 
-       /**
-        * Wrapper to easily set $wgHtml5 = true.
-        * Original value will be restored after test completion.
-        * @todo Move to MediaWikiTestCase
-        */
-       public function enableHTML5() {
-               global $wgHtml5;
-               $wgHtml5 = true;
-       }
-       /**
-        * Wrapper to easily set $wgHtml5 = false
-        * Original value will be restored after test completion.
-        * @todo Move to MediaWikiTestCase
-        */
-       public function disableHTML5() {
-               global $wgHtml5;
-               $wgHtml5 = false;
+               $this->assertEquals(
+                       '<element></element>',
+                       Html::element( 'element', null, null ),
+                       'Close tag for empty element (null, null)'
+               );
+
+               $this->assertEquals(
+                       '<element></element>',
+                       Html::element( 'element', array(), '' ),
+                       'Close tag for empty element (array, string)'
+               );
+
+               $wgWellFormedXml = true;
+
+               $this->assertEquals(
+                       '<img />',
+                       Html::element( 'img', null, '' ),
+                       'Self-closing tag for short-tag elements (wgWellFormedXml = true)'
+               );
        }
 
        public function testExpandAttributesSkipsNullAndFalse() {
                
                ### EMPTY ########
-               $this->AssertEmpty(
+               $this->assertEmpty(
                        Html::expandAttributes( array( 'foo' => null ) ),
                        'skip keys with null value'
                );
-               $this->AssertEmpty(
+               $this->assertEmpty(
                        Html::expandAttributes( array( 'foo' => false ) ),
                        'skip keys with false value'
                );
-               $this->AssertNotEmpty(
+               $this->assertNotEmpty(
                        Html::expandAttributes( array( 'foo' => '' ) ),
                        'keep keys with an empty string'
                );
        }
 
        public function testExpandAttributesForBooleans() {
-               global $wgHtml5;
-               $this->AssertEquals(
+               global $wgHtml5, $wgWellFormedXml;
+
+               $this->assertEquals(
                        '',
                        Html::expandAttributes( array( 'selected' => false ) ),
                        'Boolean attributes do not generates output when value is false'
                );
-               $this->AssertEquals(
+               $this->assertEquals(
                        '',
                        Html::expandAttributes( array( 'selected' => null ) ),
                        'Boolean attributes do not generates output when value is null'
                );
 
-               $this->AssertEquals(
-                       $wgHtml5 ? ' selected=""' : ' selected="selected"',
+               $this->assertEquals(
+                       ' selected',
                        Html::expandAttributes( array( 'selected' => true ) ),
-                       'Boolean attributes skip value output'
+                       'Boolean attributes have no value when value is true'
                );
-               $this->AssertEquals(
-                       $wgHtml5 ? ' selected=""' : ' selected="selected"',
+               $this->assertEquals(
+                       ' selected',
                        Html::expandAttributes( array( 'selected' ) ),
-                       'Boolean attributes (ex: selected) do not need a value'
+                       'Boolean attributes have no value when value is true (passed as numerical array)'
+               );
+
+               $wgWellFormedXml = true;
+
+               $this->assertEquals(
+                       ' selected=""',
+                       Html::expandAttributes( array( 'selected' => true ) ),
+                       'Boolean attributes have empty string value when value is true (wgWellFormedXml)'
+               );
+
+               $wgHtml5 = false;
+
+               $this->assertEquals(
+                       ' selected="selected"',
+                       Html::expandAttributes( array( 'selected' => true ) ),
+                       'Boolean attributes have their key as value when value is true (wgWellFormedXml, wgHTML5 = false)'
                );
        }
 
@@ -123,26 +136,51 @@ class HtmlTest extends MediaWikiTestCase {
         * Please note it output a string prefixed with a space!
         */
        public function testExpandAttributesVariousExpansions() {
+               global $wgWellFormedXml;
+
                ### NOT EMPTY ####
-               $this->AssertEquals(
+               $this->assertEquals(
+                       ' empty_string=""',
+                       Html::expandAttributes( array( 'empty_string' => '' ) ),
+                       'Empty string is always quoted'
+               );
+               $this->assertEquals(
+                       ' key=value',
+                       Html::expandAttributes( array( 'key' => 'value' ) ),
+                       'Simple string value needs no quotes'
+               );
+               $this->assertEquals(
+                       ' one=1',
+                       Html::expandAttributes( array( 'one' => 1 ) ),
+                       'Number 1 value needs no quotes'
+               );
+               $this->assertEquals(
+                       ' zero=0',
+                       Html::expandAttributes( array( 'zero' => 0 ) ),
+                       'Number 0 value needs no quotes'
+               );
+
+               $wgWellFormedXml = true;
+
+               $this->assertEquals(
                        ' empty_string=""',
                        Html::expandAttributes( array( 'empty_string' => '' ) ),
-                       'Value with an empty string'
+                       'Attribtue values are always quoted (wgWellFormedXml): Empty string'
                );
-               $this->AssertEquals(
+               $this->assertEquals(
                        ' key="value"',
                        Html::expandAttributes( array( 'key' => 'value' ) ),
-                       'Value is a string'
+                       'Attribtue values are always quoted (wgWellFormedXml): Simple string'
                );
-               $this->AssertEquals(
+               $this->assertEquals(
                        ' one="1"',
                        Html::expandAttributes( array( 'one' => 1 ) ),
-                       'Value is a numeric one'
+                       'Attribtue values are always quoted (wgWellFormedXml): Number 1'
                );
-               $this->AssertEquals(
+               $this->assertEquals(
                        ' zero="0"',
                        Html::expandAttributes( array( 'zero' => 0 ) ),
-                       'Value is a numeric zero'
+                       'Attribtue values are always quoted (wgWellFormedXml): Number 0'
                );
        }
 
@@ -153,29 +191,29 @@ class HtmlTest extends MediaWikiTestCase {
         */
        public function testExpandAttributesListValueAttributes() {
                ### STRING VALUES
-               $this->AssertEquals(
+               $this->assertEquals(
                        ' class="redundant spaces here"',
                        Html::expandAttributes( array( 'class' => ' redundant  spaces  here  ' ) ),
                        'Normalization should strip redundant spaces'
                );
-               $this->AssertEquals(
+               $this->assertEquals(
                        ' class="foo bar"',
                        Html::expandAttributes( array( 'class' => 'foo bar foo bar bar' ) ),
                        'Normalization should remove duplicates in string-lists'
                );
                ### "EMPTY" ARRAY VALUES
-               $this->AssertEquals(
+               $this->assertEquals(
                        ' class=""',
                        Html::expandAttributes( array( 'class' => array() ) ),
                        'Value with an empty array'
                );
-               $this->AssertEquals(
+               $this->assertEquals(
                        ' class=""',
                        Html::expandAttributes( array( 'class' => array( null, '', ' ', '  ' ) ) ),
                        'Array with null, empty string and spaces'
                );
                ### NON-EMPTY ARRAY VALUES
-               $this->AssertEquals(
+               $this->assertEquals(
                        ' class="foo bar"',
                        Html::expandAttributes( array( 'class' => array(
                                'foo',
@@ -186,7 +224,7 @@ class HtmlTest extends MediaWikiTestCase {
                        ) ) ),
                        'Normalization should remove duplicates in the array'
                );
-               $this->AssertEquals(
+               $this->assertEquals(
                        ' class="foo bar"',
                        Html::expandAttributes( array( 'class' => array(
                                'foo bar',
@@ -239,48 +277,48 @@ class HtmlTest extends MediaWikiTestCase {
 
        function testNamespaceSelector() {
                $this->assertEquals(
-                       '<select>' . "\n" .
-'<option value="0">(Main)</option>' . "\n" .
-'<option value="1">Talk</option>' . "\n" .
-'<option value="2">User</option>' . "\n" .
-'<option value="3">User talk</option>' . "\n" .
-'<option value="4">MyWiki</option>' . "\n" .
-'<option value="5">MyWiki Talk</option>' . "\n" .
-'<option value="6">File</option>' . "\n" .
-'<option value="7">File talk</option>' . "\n" .
-'<option value="8">MediaWiki</option>' . "\n" .
-'<option value="9">MediaWiki talk</option>' . "\n" .
-'<option value="10">Template</option>' . "\n" .
-'<option value="11">Template talk</option>' . "\n" .
-'<option value="14">Category</option>' . "\n" .
-'<option value="15">Category talk</option>' . "\n" .
-'<option value="100">Custom</option>' . "\n" .
-'<option value="101">Custom talk</option>' . "\n" .
+                       '<select id=namespace name=namespace>' . "\n" .
+'<option value=0>(Main)</option>' . "\n" .
+'<option value=1>Talk</option>' . "\n" .
+'<option value=2>User</option>' . "\n" .
+'<option value=3>User talk</option>' . "\n" .
+'<option value=4>MyWiki</option>' . "\n" .
+'<option value=5>MyWiki Talk</option>' . "\n" .
+'<option value=6>File</option>' . "\n" .
+'<option value=7>File talk</option>' . "\n" .
+'<option value=8>MediaWiki</option>' . "\n" .
+'<option value=9>MediaWiki talk</option>' . "\n" .
+'<option value=10>Template</option>' . "\n" .
+'<option value=11>Template talk</option>' . "\n" .
+'<option value=14>Category</option>' . "\n" .
+'<option value=15>Category talk</option>' . "\n" .
+'<option value=100>Custom</option>' . "\n" .
+'<option value=101>Custom talk</option>' . "\n" .
 '</select>',
                        Html::namespaceSelector(),
                        'Basic namespace selector without custom options'
                );
 
                $this->assertEquals(
-                       '<label for="mw-test-namespace">Select a namespace:</label>&#160;' .
-'<select id="mw-test-namespace" name="wpNamespace">' . "\n" .
-'<option value="all">all</option>' . "\n" .
-'<option value="0">(Main)</option>' . "\n" .
-'<option value="1">Talk</option>' . "\n" .
-'<option value="2" selected="">User</option>' . "\n" .
-'<option value="3">User talk</option>' . "\n" .
-'<option value="4">MyWiki</option>' . "\n" .
-'<option value="5">MyWiki Talk</option>' . "\n" .
-'<option value="6">File</option>' . "\n" .
-'<option value="7">File talk</option>' . "\n" .
-'<option value="8">MediaWiki</option>' . "\n" .
-'<option value="9">MediaWiki talk</option>' . "\n" .
-'<option value="10">Template</option>' . "\n" .
-'<option value="11">Template talk</option>' . "\n" .
-'<option value="14">Category</option>' . "\n" .
-'<option value="15">Category talk</option>' . "\n" .
-'<option value="100">Custom</option>' . "\n" .
-'<option value="101">Custom talk</option>' . "\n" .
+                       '<label for=mw-test-namespace>Select a namespace:</label>&#160;' .
+'<select id=mw-test-namespace name=wpNamespace>' . "\n" .
+'<option value=all>all</option>' . "\n" .
+'<option value=0>(Main)</option>' . "\n" .
+'<option value=1>Talk</option>' . "\n" .
+'<option value=2 selected>User</option>' . "\n" .
+'<option value=3>User talk</option>' . "\n" .
+'<option value=4>MyWiki</option>' . "\n" .
+'<option value=5>MyWiki Talk</option>' . "\n" .
+'<option value=6>File</option>' . "\n" .
+'<option value=7>File talk</option>' . "\n" .
+'<option value=8>MediaWiki</option>' . "\n" .
+'<option value=9>MediaWiki talk</option>' . "\n" .
+'<option value=10>Template</option>' . "\n" .
+'<option value=11>Template talk</option>' . "\n" .
+'<option value=14>Category</option>' . "\n" .
+'<option value=15>Category talk</option>' . "\n" .
+'<option value=100>Custom</option>' . "\n" .
+'<option value=101>Custom talk</option>' . "\n" .
 '</select>',
                        Html::namespaceSelector(
                                array( 'selected' => '2', 'all' => 'all', 'label' => 'Select a namespace:' ),
@@ -290,24 +328,24 @@ class HtmlTest extends MediaWikiTestCase {
                );
 
                $this->assertEquals(
-                       '<label>Select a namespace:</label>&#160;' .
-'<select>' . "\n" .
-'<option value="0">(Main)</option>' . "\n" .
-'<option value="1">Talk</option>' . "\n" .
-'<option value="2">User</option>' . "\n" .
-'<option value="3">User talk</option>' . "\n" .
-'<option value="4">MyWiki</option>' . "\n" .
-'<option value="5">MyWiki Talk</option>' . "\n" .
-'<option value="6">File</option>' . "\n" .
-'<option value="7">File talk</option>' . "\n" .
-'<option value="8">MediaWiki</option>' . "\n" .
-'<option value="9">MediaWiki talk</option>' . "\n" .
-'<option value="10">Template</option>' . "\n" .
-'<option value="11">Template talk</option>' . "\n" .
-'<option value="14">Category</option>' . "\n" .
-'<option value="15">Category talk</option>' . "\n" .
-'<option value="100">Custom</option>' . "\n" .
-'<option value="101">Custom talk</option>' . "\n" .
+                       '<label for=namespace>Select a namespace:</label>&#160;' .
+'<select id=namespace name=namespace>' . "\n" .
+'<option value=0>(Main)</option>' . "\n" .
+'<option value=1>Talk</option>' . "\n" .
+'<option value=2>User</option>' . "\n" .
+'<option value=3>User talk</option>' . "\n" .
+'<option value=4>MyWiki</option>' . "\n" .
+'<option value=5>MyWiki Talk</option>' . "\n" .
+'<option value=6>File</option>' . "\n" .
+'<option value=7>File talk</option>' . "\n" .
+'<option value=8>MediaWiki</option>' . "\n" .
+'<option value=9>MediaWiki talk</option>' . "\n" .
+'<option value=10>Template</option>' . "\n" .
+'<option value=11>Template talk</option>' . "\n" .
+'<option value=14>Category</option>' . "\n" .
+'<option value=15>Category talk</option>' . "\n" .
+'<option value=100>Custom</option>' . "\n" .
+'<option value=101>Custom talk</option>' . "\n" .
 '</select>',
                        Html::namespaceSelector(
                                array( 'label' => 'Select a namespace:' )
@@ -318,18 +356,18 @@ class HtmlTest extends MediaWikiTestCase {
 
        function testCanFilterOutNamespaces() {
                $this->assertEquals(
-'<select>' . "\n" .
-'<option value="2">User</option>' . "\n" .
-'<option value="4">MyWiki</option>' . "\n" .
-'<option value="5">MyWiki Talk</option>' . "\n" .
-'<option value="6">File</option>' . "\n" .
-'<option value="7">File talk</option>' . "\n" .
-'<option value="8">MediaWiki</option>' . "\n" .
-'<option value="9">MediaWiki talk</option>' . "\n" .
-'<option value="10">Template</option>' . "\n" .
-'<option value="11">Template talk</option>' . "\n" .
-'<option value="14">Category</option>' . "\n" .
-'<option value="15">Category talk</option>' . "\n" .
+'<select id=namespace name=namespace>' . "\n" .
+'<option value=2>User</option>' . "\n" .
+'<option value=4>MyWiki</option>' . "\n" .
+'<option value=5>MyWiki Talk</option>' . "\n" .
+'<option value=6>File</option>' . "\n" .
+'<option value=7>File talk</option>' . "\n" .
+'<option value=8>MediaWiki</option>' . "\n" .
+'<option value=9>MediaWiki talk</option>' . "\n" .
+'<option value=10>Template</option>' . "\n" .
+'<option value=11>Template talk</option>' . "\n" .
+'<option value=14>Category</option>' . "\n" .
+'<option value=15>Category talk</option>' . "\n" .
 '</select>',
                        Html::namespaceSelector(
                                array( 'exclude' => array( 0, 1, 3, 100, 101 ) )
@@ -340,23 +378,23 @@ class HtmlTest extends MediaWikiTestCase {
 
        function testCanDisableANamespaces() {
                $this->assertEquals(
-'<select>' . "\n" .
-'<option disabled="" value="0">(Main)</option>' . "\n" .
-'<option disabled="" value="1">Talk</option>' . "\n" .
-'<option disabled="" value="2">User</option>' . "\n" .
-'<option disabled="" value="3">User talk</option>' . "\n" .
-'<option disabled="" value="4">MyWiki</option>' . "\n" .
-'<option value="5">MyWiki Talk</option>' . "\n" .
-'<option value="6">File</option>' . "\n" .
-'<option value="7">File talk</option>' . "\n" .
-'<option value="8">MediaWiki</option>' . "\n" .
-'<option value="9">MediaWiki talk</option>' . "\n" .
-'<option value="10">Template</option>' . "\n" .
-'<option value="11">Template talk</option>' . "\n" .
-'<option value="14">Category</option>' . "\n" .
-'<option value="15">Category talk</option>' . "\n" .
-'<option value="100">Custom</option>' . "\n" .
-'<option value="101">Custom talk</option>' . "\n" .
+'<select id=namespace name=namespace>' . "\n" .
+'<option disabled value=0>(Main)</option>' . "\n" .
+'<option disabled value=1>Talk</option>' . "\n" .
+'<option disabled value=2>User</option>' . "\n" .
+'<option disabled value=3>User talk</option>' . "\n" .
+'<option disabled value=4>MyWiki</option>' . "\n" .
+'<option value=5>MyWiki Talk</option>' . "\n" .
+'<option value=6>File</option>' . "\n" .
+'<option value=7>File talk</option>' . "\n" .
+'<option value=8>MediaWiki</option>' . "\n" .
+'<option value=9>MediaWiki talk</option>' . "\n" .
+'<option value=10>Template</option>' . "\n" .
+'<option value=11>Template talk</option>' . "\n" .
+'<option value=14>Category</option>' . "\n" .
+'<option value=15>Category talk</option>' . "\n" .
+'<option value=100>Custom</option>' . "\n" .
+'<option value=101>Custom talk</option>' . "\n" .
 '</select>',
                        Html::namespaceSelector( array(
                                'disable' => array( 0, 1, 2, 3, 4 )
@@ -366,12 +404,11 @@ class HtmlTest extends MediaWikiTestCase {
        }
 
        /**
-        * @dataProvider providesHtml5InputTypes
+        * @dataProvider provideHtml5InputTypes
         */
        function testHtmlElementAcceptsNewHtml5TypesInHtml5Mode( $HTML5InputType ) {
-               $this->enableHTML5();
                $this->assertEquals(
-                       '<input type="' . $HTML5InputType . '" />',
+                       '<input type=' . $HTML5InputType . '>',
                        Html::element( 'input', array( 'type' => $HTML5InputType ) ),
                        'In HTML5, HTML::element() should accept type="' . $HTML5InputType . '"'
                );
@@ -381,7 +418,7 @@ class HtmlTest extends MediaWikiTestCase {
         * List of input element types values introduced by HTML5
         * Full list at http://www.w3.org/TR/html-markup/input.html
         */
-       function providesHtml5InputTypes() {
+       function provideHtml5InputTypes() {
                $types = array(
                        'datetime',
                        'datetime-local',
@@ -409,19 +446,18 @@ class HtmlTest extends MediaWikiTestCase {
         * @cover Html::dropDefaults
         * @dataProvider provideElementsWithAttributesHavingDefaultValues
         */
-       function testDropDefaults( $expected, $element, $message = '' ) {
-               $this->enableHTML5();
-               $this->assertEquals( $expected, $element, $message );
+       function testDropDefaults( $expected, $element, $attribs, $message = '' ) {
+               $this->assertEquals( $expected, Html::element( $element, $attribs ), $message );
        }
 
-       function provideElementsWithAttributesHavingDefaultValues() {
+       public static function provideElementsWithAttributesHavingDefaultValues() {
                # Use cases in a concise format:
                # <expected>, <element name>, <array of attributes> [, <message>]
                # Will be mapped to Html::element()
                $cases = array();
 
                ### Generic cases, match $attribDefault static array
-               $cases[] = array( '<area />',
+               $cases[] = array( '<area>',
                        'area', array( 'shape' => 'rect' )
                );
 
@@ -449,7 +485,7 @@ class HtmlTest extends MediaWikiTestCase {
                        'canvas', array( 'width' => 300 )
                );
 
-               $cases[] = array( '<command />',
+               $cases[] = array( '<command>',
                        'command', array( 'type' => 'command' )
                );
 
@@ -463,18 +499,18 @@ class HtmlTest extends MediaWikiTestCase {
                        'form', array( 'enctype' => 'application/x-www-form-urlencoded' )
                );
 
-               $cases[] = array( '<input />',
+               $cases[] = array( '<input>',
                        'input', array( 'formaction' => 'GET' )
                );
-               $cases[] = array( '<input />',
+               $cases[] = array( '<input>',
                        'input', array( 'type' => 'text' )
                );
 
-               $cases[] = array( '<keygen />',
+               $cases[] = array( '<keygen>',
                        'keygen', array( 'keytype' => 'rsa' )
                );
 
-               $cases[] = array( '<link />',
+               $cases[] = array( '<link>',
                        'link', array( 'media' => 'all' )
                );
 
@@ -499,37 +535,37 @@ class HtmlTest extends MediaWikiTestCase {
 
                ### SPECIFIC CASES
 
-               # <link type="text/css" />
-               $cases[] = array( '<link />',
+               # <link type="text/css">
+               $cases[] = array( '<link>',
                        'link', array( 'type' => 'text/css' )
                );
 
-               # <input /> specific handling
-               $cases[] = array( '<input type="checkbox" />',
+               # <input> specific handling
+               $cases[] = array( '<input type=checkbox>',
                        'input', array( 'type' => 'checkbox', 'value' => 'on' ),
                        'Default value "on" is stripped of checkboxes',
                );
-               $cases[] = array( '<input type="radio" />',
+               $cases[] = array( '<input type=radio>',
                        'input', array( 'type' => 'radio', 'value' => 'on' ),
                        'Default value "on" is stripped of radio buttons',
                );
-               $cases[] = array( '<input type="submit" value="Submit" />',
+               $cases[] = array( '<input type=submit value=Submit>',
                        'input', array( 'type' => 'submit', 'value' => 'Submit' ),
                        'Default value "Submit" is kept on submit buttons (for possible l10n issues)',
                );
-               $cases[] = array( '<input type="color" />',
+               $cases[] = array( '<input type=color>',
                        'input', array( 'type' => 'color', 'value' => '' ),
                );
-               $cases[] = array( '<input type="range" />',
+               $cases[] = array( '<input type=range>',
                        'input', array( 'type' => 'range', 'value' => '' ),
                );
 
-               # <select /> specifc handling
-               $cases[] = array( '<select multiple=""></select>',
+               # <select> specifc handling
+               $cases[] = array( '<select multiple></select>',
                        'select', array( 'size' => '4', 'multiple' => true ),
                );
                # .. with numeric value
-               $cases[] = array( '<select multiple=""></select>',
+               $cases[] = array( '<select multiple></select>',
                        'select', array( 'size' => 4, 'multiple' => true ),
                );
                $cases[] = array( '<select></select>',
@@ -553,13 +589,13 @@ class HtmlTest extends MediaWikiTestCase {
                        "dropDefaults accepts values given as an array"
                );
 
-
                # Craft the Html elements
                $ret = array();
                foreach( $cases as $case ) {
                        $ret[] = array(
                                $case[0],
-                               Html::element( $case[1], $case[2] )
+                               $case[1], $case[2],
+                               isset( $case[3] ) ? $case[3] : ''
                        );
                }
                return $ret;
index 263383f..155bd31 100644 (file)
@@ -17,7 +17,7 @@ class HttpTest extends MediaWikiTestCase {
                $this->assertEquals( $expected, $ok, $msg );
        }
 
-       function cookieDomains() {
+       public static function cookieDomains() {
                return array(
                        array( false, "org"),
                        array( false, ".org"),
@@ -62,7 +62,7 @@ class HttpTest extends MediaWikiTestCase {
        /**
         * Feeds URI to test a long regular expression in Http::isValidURI
         */
-       function provideURI() {
+       public static function provideURI() {
                /** Format: 'boolean expectation', 'URI to test', 'Optional message' */
                return array(
                        array( false, '¿non sens before!! http://a', 'Allow anything before URI' ),
@@ -131,6 +131,10 @@ class HttpTest extends MediaWikiTestCase {
         * handles header reporting on redirect pages, and will need to be
         * rewritten when bug 29232 is taken care of (high-level handling of
         * HTTP redirects).
+        * @group Broken
+        *  MWHttpRequestTester's constructor is private, needs to use
+        *  MWHttpRequestTester::factory instead. However the objects coming
+        *  from that won't have MWHttpRequestTester::setRespHeaders...
         */
        function testRelativeRedirections() {
                $h = new MWHttpRequestTester( 'http://oldsite/file.ext' );
index f50b2fe..c4a6c55 100644 (file)
@@ -405,7 +405,7 @@ class IPTest extends MediaWikiTestCase {
        }
 
        /** Provider for testIPIsInRange() */
-       function provideIPsAndRanges() {
+       public static function provideIPsAndRanges() {
                        # Format: (expected boolean, address, range, optional message)
                return array(
                        # IPv4
@@ -443,7 +443,7 @@ class IPTest extends MediaWikiTestCase {
        /**
         * Provider for IP::splitHostAndPort()
         */
-       function provideSplitHostAndPort() {
+       public static function provideSplitHostAndPort() {
                return array(
                        array( false, '[', 'Unclosed square bracket' ),
                        array( false, '[::', 'Unclosed square bracket 2' ),
@@ -474,7 +474,7 @@ class IPTest extends MediaWikiTestCase {
        /**
         * Provider for IP::combineHostAndPort()
         */
-       function provideCombineHostAndPort() {
+       public static function provideCombineHostAndPort() {
                return array(
                        array( '[::1]', array( '::1', 2, 2 ), 'IPv6 default port' ),
                        array( '[::1]:2', array( '::1', 2, 3 ), 'IPv6 non-default port' ),
@@ -494,7 +494,7 @@ class IPTest extends MediaWikiTestCase {
        /**
         * Provider for IP::testSanitizeRange()
         */
-       function provideIPCIDRs() {
+       public static function provideIPCIDRs() {
                return array(
                        array( '35.56.31.252/16', '35.56.0.0/16', 'IPv4 range' ),
                        array( '135.16.21.252/24', '135.16.21.0/24', 'IPv4 range' ),
@@ -518,7 +518,7 @@ class IPTest extends MediaWikiTestCase {
        /**
         * Provider for IP::testPrettifyIP()
         */
-       function provideIPsToPrettify() {
+       public static function provideIPsToPrettify() {
                return array(
                        array( '0:0:0:0:0:0:0:0', '::' ),
                        array( '0:0:0::0:0:0', '::' ),
diff --git a/tests/phpunit/includes/JavascriptContentTest.php b/tests/phpunit/includes/JavascriptContentTest.php
new file mode 100644 (file)
index 0000000..a3b75d1
--- /dev/null
@@ -0,0 +1,259 @@
+<?php
+
+/**
+ * @group ContentHandler
+ *
+ * @group Database
+ *        ^--- needed, because we do need the database to test link updates
+ */
+class JavascriptContentTest extends WikitextContentTest {
+
+       public function newContent( $text ) {
+               return new JavascriptContent( $text );
+       }
+
+
+       public function dataGetParserOutput() {
+               return array(
+                       array("MediaWiki:Test.js", null, "hello <world>\n",
+                                       "<pre class=\"mw-code mw-js\" dir=\"ltr\">\nhello &lt;world&gt;\n\n</pre>\n"),
+                       // @todo: more...?
+               );
+       }
+
+       public function dataGetSection() {
+               return array(
+                       array( WikitextContentTest::$sections,
+                              "0",
+                              null
+                       ),
+                       array( WikitextContentTest::$sections,
+                              "2",
+                              null
+                       ),
+                       array( WikitextContentTest::$sections,
+                              "8",
+                              null
+                       ),
+               );
+       }
+
+       public function dataReplaceSection() {
+               return array(
+                       array( WikitextContentTest::$sections,
+                              "0",
+                              "No more",
+                              null,
+                              null
+                       ),
+                       array( WikitextContentTest::$sections,
+                              "",
+                              "No more",
+                              null,
+                              null
+                       ),
+                       array( WikitextContentTest::$sections,
+                              "2",
+                              "== TEST ==\nmore fun",
+                              null,
+                              null
+                       ),
+                       array( WikitextContentTest::$sections,
+                              "8",
+                              "No more",
+                              null,
+                              null
+                       ),
+                       array( WikitextContentTest::$sections,
+                              "new",
+                              "No more",
+                              "New",
+                              null
+                       ),
+               );
+       }
+
+       public function testAddSectionHeader( ) {
+               $content = $this->newContent( 'hello world' );
+               $c = $content->addSectionHeader( 'test' );
+
+               $this->assertTrue( $content->equals( $c ) );
+       }
+
+       // XXX: currently, preSaveTransform is applied to scripts. this may change or become optional.
+       /*
+       public function dataPreSaveTransform() {
+               return array(
+                       array( 'hello this is ~~~',
+                              "hello this is ~~~",
+                       ),
+                       array( 'hello \'\'this\'\' is <nowiki>~~~</nowiki>',
+                              'hello \'\'this\'\' is <nowiki>~~~</nowiki>',
+                       ),
+               );
+       }
+       */
+
+       public function dataPreloadTransform() {
+               return array(
+                       array( 'hello this is ~~~',
+                              "hello this is ~~~",
+                       ),
+                       array( 'hello \'\'this\'\' is <noinclude>foo</noinclude><includeonly>bar</includeonly>',
+                              'hello \'\'this\'\' is <noinclude>foo</noinclude><includeonly>bar</includeonly>',
+                       ),
+               );
+       }
+
+       public function dataGetRedirectTarget() {
+               return array(
+                       array( '#REDIRECT [[Test]]',
+                              null,
+                       ),
+                       array( '#REDIRECT Test',
+                              null,
+                       ),
+                       array( '* #REDIRECT [[Test]]',
+                              null,
+                       ),
+               );
+       }
+
+       /**
+        * @todo: test needs database!
+        */
+       /*
+       public function getRedirectChain() {
+               $text = $this->getNativeData();
+               return Title::newFromRedirectArray( $text );
+       }
+       */
+
+       /**
+        * @todo: test needs database!
+        */
+       /*
+       public function getUltimateRedirectTarget() {
+               $text = $this->getNativeData();
+               return Title::newFromRedirectRecurse( $text );
+       }
+       */
+
+
+       public function dataIsCountable() {
+               return array(
+                       array( '',
+                              null,
+                              'any',
+                              true
+                       ),
+                       array( 'Foo',
+                              null,
+                              'any',
+                              true
+                       ),
+                       array( 'Foo',
+                              null,
+                              'comma',
+                              false
+                       ),
+                       array( 'Foo, bar',
+                              null,
+                              'comma',
+                              false
+                       ),
+                       array( 'Foo',
+                              null,
+                              'link',
+                              false
+                       ),
+                       array( 'Foo [[bar]]',
+                              null,
+                              'link',
+                              false
+                       ),
+                       array( 'Foo',
+                              true,
+                              'link',
+                              false
+                       ),
+                       array( 'Foo [[bar]]',
+                              false,
+                              'link',
+                              false
+                       ),
+                       array( '#REDIRECT [[bar]]',
+                              true,
+                              'any',
+                              true
+                       ),
+                       array( '#REDIRECT [[bar]]',
+                              true,
+                              'comma',
+                              false
+                       ),
+                       array( '#REDIRECT [[bar]]',
+                              true,
+                              'link',
+                              false
+                       ),
+               );
+       }
+
+       public function dataGetTextForSummary() {
+               return array(
+                       array( "hello\nworld.",
+                              16,
+                              'hello world.',
+                       ),
+                       array( 'hello world.',
+                              8,
+                              'hello...',
+                       ),
+                       array( '[[hello world]].',
+                              8,
+                              '[[hel...',
+                       ),
+               );
+       }
+
+       public function testMatchMagicWord( ) {
+               $mw = MagicWord::get( "staticredirect" );
+
+               $content = $this->newContent( "#REDIRECT [[FOO]]\n__STATICREDIRECT__" );
+               $this->assertFalse( $content->matchMagicWord( $mw ), "should not have matched magic word, since it's not wikitext" );
+       }
+
+       public function testUpdateRedirect( ) {
+               $target = Title::newFromText( "testUpdateRedirect_target" );
+
+               $content = $this->newContent( "#REDIRECT [[Someplace]]" );
+               $newContent = $content->updateRedirect( $target );
+
+               $this->assertTrue( $content->equals( $newContent ), "content should be unchanged since it's not wikitext" );
+       }
+
+       # =================================================================================================================
+
+       public function testGetModel() {
+               $content = $this->newContent( "hello world." );
+
+               $this->assertEquals( CONTENT_MODEL_JAVASCRIPT, $content->getModel() );
+       }
+
+       public function testGetContentHandler() {
+               $content = $this->newContent( "hello world." );
+
+               $this->assertEquals( CONTENT_MODEL_JAVASCRIPT, $content->getContentHandler()->getModelID() );
+       }
+
+       public function dataEquals( ) {
+               return array(
+                       array( new JavascriptContent( "hallo" ), null, false ),
+                       array( new JavascriptContent( "hallo" ), new JavascriptContent( "hallo" ), true ),
+                       array( new JavascriptContent( "hallo" ), new CssContent( "hallo" ), false ),
+                       array( new JavascriptContent( "hallo" ), new JavascriptContent( "HALLO" ), false ),
+               );
+       }
+
+}
index 75dd18d..9b508f7 100644 (file)
@@ -3,31 +3,25 @@
 class JsonTest extends MediaWikiTestCase {
        
        function testPhpBug46944Test() {
-               
                $this->assertNotEquals( 
                        '\ud840\udc00',                 
                        strtolower( FormatJson::encode( "\xf0\xa0\x80\x80" ) ),
                        'Test encoding an broken json_encode character (U+20000)'
                );
-               
-               
+
        }
        
        function testDecodeVarTypes() {
-               
                $this->assertInternalType( 
                        'object',                       
                        FormatJson::decode( '{"Name": "Cheeso", "Rank": 7}' ),
                        'Default to object'
                );
-               
+
                $this->assertInternalType( 
                        'array',                        
                        FormatJson::decode( '{"Name": "Cheeso", "Rank": 7}', true ),
                        'Optional array'
                );
-               
        }
-       
 }
-
index baf28b0..9fc6f4d 100644 (file)
@@ -4,24 +4,28 @@ class LanguageConverterTest extends MediaWikiLangTestCase {
        protected $lang = null;
        protected $lc = null;
 
-       function setUp() {
+       protected function setUp() {
                parent::setUp();
-               global $wgMemc, $wgRequest, $wgUser, $wgContLang;
 
-               $wgUser = new User;
-               $wgRequest = new FauxRequest( array() );
-               $wgMemc = new EmptyBagOStuff;
-               $wgContLang = Language::factory( 'tg' );
+               $this->setMwGlobals( array(
+                       'wgContLang' => Language::factory( 'tg' ),
+                       'wgDefaultLanguageVariant' => false,
+                       'wgMemc' => new EmptyBagOStuff,
+                       'wgRequest' => new FauxRequest( array() ),
+                       'wgUser' => new User,
+               ) );
+
                $this->lang = new LanguageToTest();
-               $this->lc = new TestConverter( $this->lang, 'tg',
-                                                                          array( 'tg', 'tg-latn' ) );
+               $this->lc = new TestConverter(
+                       $this->lang, 'tg',
+                       array( 'tg', 'tg-latn' )
+               );
        }
 
-       function tearDown() {
-               global $wgMemc;
-               unset( $wgMemc );
+       protected function tearDown() {
                unset( $this->lc );
                unset( $this->lang );
+
                parent::tearDown();
        }
 
@@ -71,7 +75,7 @@ class LanguageConverterTest extends MediaWikiLangTestCase {
        }
 
        function testGetPreferredVariantHeaderUserVsUrl() {
-               global $wgRequest, $wgUser, $wgContLang;
+               global $wgContLang, $wgRequest, $wgUser;
 
                $wgContLang = Language::factory( 'tg-latn' );
                $wgRequest->setVal( 'variant', 'tg' );
index 8e37b13..bc71ab7 100644 (file)
@@ -25,7 +25,7 @@ class LinksUpdateTest extends MediaWikiTestCase {
                );
        }
 
-       function setUp() {
+       protected function setUp() {
                $dbw = wfGetDB( DB_MASTER );
                $dbw->replace(
                        'interwiki',
@@ -151,7 +151,10 @@ class LinksUpdateTest extends MediaWikiTestCase {
        protected function assertLinksUpdate( Title $title, ParserOutput $parserOutput, $table, $fields, $condition, array $expectedRows ) {
                $update = new LinksUpdate( $title, $parserOutput );
 
+               //NOTE: make sure LinksUpdate does not generate warnings when called inside a transaction.
+               $update->beginTransaction();
                $update->doUpdate();
+               $update->commitTransaction();
 
                $this->assertSelect( $table, $fields, $condition, $expectedRows );
        }
index 5b26b89..c5a9dc1 100644 (file)
@@ -6,10 +6,11 @@
  */
 
 class LocalFileTest extends MediaWikiTestCase {
-       function setUp() {
-               global $wgCapitalLinks;
 
-               $wgCapitalLinks = true;
+       protected function setUp() {
+               parent::setUp();
+
+               $this->setMwGlobals( 'wgCapitalLinks', true );
 
                $info = array(
                        'name'            => 'test',
index ed5e760..f7be59f 100644 (file)
@@ -3,7 +3,6 @@
 class MWFunctionTest extends MediaWikiTestCase {
        
        function testCallUserFuncWorkarounds() {
-               
                $this->assertEquals( 
                        call_user_func( array( 'MWFunctionTest', 'someMethod' ) ),
                        MWFunction::call( 'MWFunctionTest::someMethod' )
@@ -12,9 +11,7 @@ class MWFunctionTest extends MediaWikiTestCase {
                        call_user_func( array( 'MWFunctionTest', 'someMethod' ), 'foo', 'bar', 'baz' ),
                        MWFunction::call( 'MWFunctionTest::someMethod', 'foo', 'bar', 'baz' )
                );
-               
-               
-               
+
                $this->assertEquals( 
                        call_user_func_array( array( 'MWFunctionTest', 'someMethod' ), array() ),
                        MWFunction::callArray( 'MWFunctionTest::someMethod', array() )
@@ -23,38 +20,33 @@ class MWFunctionTest extends MediaWikiTestCase {
                        call_user_func_array( array( 'MWFunctionTest', 'someMethod' ), array( 'foo', 'bar', 'baz' ) ),
                        MWFunction::callArray( 'MWFunctionTest::someMethod', array( 'foo', 'bar', 'baz' ) )
                );
-               
        }
        
        function testNewObjFunction() {
-               
                $arg1 = 'Foo';
                $arg2 = 'Bar';
                $arg3 = array( 'Baz' );
                $arg4 = new ExampleObject;
-               
+
                $args = array( $arg1, $arg2, $arg3, $arg4 );
-               
+
                $newObject = new MWBlankClass( $arg1, $arg2, $arg3, $arg4 );
-               
                $this->assertEquals( 
                        MWFunction::newObj( 'MWBlankClass', $args )->args, 
                        $newObject->args
                );
-               
+
                $this->assertEquals( 
                        MWFunction::newObj( 'MWBlankClass', $args, true )->args, 
                        $newObject->args,
                        'Works even with PHP version < 5.1.3'
                );
-               
        }
        
        /**
         * @expectedException MWException
         */
        function testCallingParentFails() {
-               
                MWFunction::call( 'parent::foo' );
        }
        
@@ -62,7 +54,6 @@ class MWFunctionTest extends MediaWikiTestCase {
         * @expectedException MWException
         */
        function testCallingSelfFails() {
-               
                MWFunction::call( 'self::foo' );
        }
        
@@ -73,13 +64,12 @@ class MWFunctionTest extends MediaWikiTestCase {
 }
 
 class MWBlankClass {
-       
+
        public $args = array();
-       
+
        function __construct( $arg1, $arg2, $arg3, $arg4 ) {
                $this->args = array( $arg1, $arg2, $arg3, $arg4 );
        }
-       
 }
 
 class ExampleObject {
index 3b05d67..5de5cc9 100644 (file)
  *
  */
 class MWNamespaceTest extends MediaWikiTestCase {
-       /**
-        * Sets up the fixture, for example, opens a network connection.
-        * This method is called before a test is executed.
-        */
        protected function setUp() {
-       }
+               parent::setUp();
 
-       /**
-        * Tears down the fixture, for example, closes a network connection.
-        * This method is called after a test is executed.
-        */
-       protected function tearDown() {
+               $this->setMwGlobals( array(
+                       'wgContentNamespaces' => array( NS_MAIN ),
+                       'wgNamespacesWithSubpages' => array(
+                               NS_TALK           => true,
+                               NS_USER           => true,
+                               NS_USER_TALK      => true,
+                       ),
+                       'wgCapitalLinks' => true,
+                       'wgCapitalLinkOverrides' => array(),
+                       'wgNonincludableNamespaces' => array(),
+               ) );
        }
 
-
 #### START OF TESTS #########################################################
 
        /**
@@ -268,77 +269,36 @@ class MWNamespaceTest extends MediaWikiTestCase {
        public function testIsContent() {
                // NS_MAIN is a content namespace per DefaultSettings.php
                // and per function definition.
-               $this->assertIsContent( NS_MAIN );
 
-               global $wgContentNamespaces;
-
-               $saved = $wgContentNamespaces;
-
-               $wgContentNamespaces[] = NS_MAIN;
                $this->assertIsContent( NS_MAIN );
 
                // Other namespaces which are not expected to be content
-               if ( isset( $wgContentNamespaces[NS_MEDIA] ) ) {
-                       unset( $wgContentNamespaces[NS_MEDIA] );
-               }
-               $this->assertIsNotContent( NS_MEDIA );
 
-               if ( isset( $wgContentNamespaces[NS_SPECIAL] ) ) {
-                       unset( $wgContentNamespaces[NS_SPECIAL] );
-               }
+               $this->assertIsNotContent( NS_MEDIA );
                $this->assertIsNotContent( NS_SPECIAL );
-
-               if ( isset( $wgContentNamespaces[NS_TALK] ) ) {
-                       unset( $wgContentNamespaces[NS_TALK] );
-               }
                $this->assertIsNotContent( NS_TALK );
-
-               if ( isset( $wgContentNamespaces[NS_USER] ) ) {
-                       unset( $wgContentNamespaces[NS_USER] );
-               }
                $this->assertIsNotContent( NS_USER );
-
-               if ( isset( $wgContentNamespaces[NS_CATEGORY] ) ) {
-                       unset( $wgContentNamespaces[NS_CATEGORY] );
-               }
                $this->assertIsNotContent( NS_CATEGORY );
-
-               if ( isset( $wgContentNamespaces[100] ) ) {
-                       unset( $wgContentNamespaces[100] );
-               }
                $this->assertIsNotContent( 100 );
-
-               $wgContentNamespaces = $saved;
        }
 
        /**
         * Similar to testIsContent() but alters the $wgContentNamespaces
         * global variable.
         */
-       public function testIsContentWithAdditionsInWgContentNamespaces() {
-               // NS_MAIN is a content namespace per DefaultSettings.php
-               // and per function definition.
-               $this->assertIsContent( NS_MAIN );
+       public function testIsContentAdvanced() {
+               global $wgContentNamespaces;
 
-               // Tests that user defined namespace #252 is not content:
+               // Test that user defined namespace #252 is not content
                $this->assertIsNotContent( 252 );
 
-               # @todo FIXME: Is global saving really required for PHPUnit?
                // Bless namespace # 252 as a content namespace
-               global $wgContentNamespaces;
-               $savedGlobal = $wgContentNamespaces;
                $wgContentNamespaces[] = 252;
+
                $this->assertIsContent( 252 );
 
                // Makes sure NS_MAIN was not impacted
                $this->assertIsContent( NS_MAIN );
-
-               // Restore global
-               $wgContentNamespaces = $savedGlobal;
-
-               // Verify namespaces after global restauration
-               $this->assertIsContent( NS_MAIN  );
-               $this->assertIsNotContent( 252 );
        }
 
        public function testIsWatchable() {
@@ -356,31 +316,21 @@ class MWNamespaceTest extends MediaWikiTestCase {
        }
 
        public function testHasSubpages() {
+               global $wgNamespacesWithSubpages;
+
                // Special namespaces:
                $this->assertHasNotSubpages( NS_MEDIA   );
                $this->assertHasNotSubpages( NS_SPECIAL );
 
-               // namespaces without subpages
-               # save up global
-               global $wgNamespacesWithSubpages;
-               $saved = null;
-               if( array_key_exists( NS_MAIN, $wgNamespacesWithSubpages ) ) {
-                       $saved = $wgNamespacesWithSubpages[NS_MAIN];
-                       unset( $wgNamespacesWithSubpages[NS_MAIN] );
-               }
-
+               // Namespaces without subpages
                $this->assertHasNotSubpages( NS_MAIN );
 
                $wgNamespacesWithSubpages[NS_MAIN] = true;
                $this->assertHasSubpages( NS_MAIN );
+
                $wgNamespacesWithSubpages[NS_MAIN] = false;
                $this->assertHasNotSubpages( NS_MAIN );
 
-               # restore global
-               if( $saved !== null ) {
-                       $wgNamespacesWithSubpages[NS_MAIN] = $saved;
-               }
-
                // Some namespaces with subpages
                $this->assertHasSubpages( NS_TALK      );
                $this->assertHasSubpages( NS_USER      );
@@ -390,22 +340,25 @@ class MWNamespaceTest extends MediaWikiTestCase {
        /**
         */
        public function testGetContentNamespaces() {
+               global $wgContentNamespaces;
+
                $this->assertEquals(
                        array( NS_MAIN ),
                        MWNamespace::getcontentNamespaces(),
                        '$wgContentNamespaces is an array with only NS_MAIN by default'
                );
 
-               global $wgContentNamespaces;
 
-               $saved = $wgContentNamespaces;
                # test !is_array( $wgcontentNamespaces )
                $wgContentNamespaces = '';
                $this->assertEquals( NS_MAIN, MWNamespace::getcontentNamespaces() );
+
                $wgContentNamespaces = false;
                $this->assertEquals( NS_MAIN, MWNamespace::getcontentNamespaces() );
+
                $wgContentNamespaces = null;
                $this->assertEquals( NS_MAIN, MWNamespace::getcontentNamespaces() );
+
                $wgContentNamespaces = 5;
                $this->assertEquals( NS_MAIN, MWNamespace::getcontentNamespaces() );
 
@@ -433,8 +386,6 @@ class MWNamespaceTest extends MediaWikiTestCase {
                        array( NS_MAIN, NS_USER, NS_CATEGORY ),
                        MWNamespace::getcontentNamespaces()
                );
-
-               $wgContentNamespaces = $saved;
        }
 
        /**
@@ -504,24 +455,20 @@ class MWNamespaceTest extends MediaWikiTestCase {
         */
        public function testIsCapitalizedWithWgCapitalLinks() {
                global $wgCapitalLinks;
-               // Save the global to easily reset to MediaWiki default settings
-               $savedGlobal = $wgCapitalLinks;
 
-               $wgCapitalLinks = true;
                $this->assertIsCapitalized( NS_PROJECT      );
                $this->assertIsCapitalized( NS_PROJECT_TALK );
 
                $wgCapitalLinks = false;
+
                // hardcoded namespaces (see above function) are still capitalized:
                $this->assertIsCapitalized( NS_SPECIAL   );
                $this->assertIsCapitalized( NS_USER      );
                $this->assertIsCapitalized( NS_MEDIAWIKI );
+
                // setting is correctly applied
                $this->assertIsNotCapitalized( NS_PROJECT      );
                $this->assertIsNotCapitalized( NS_PROJECT_TALK );
-
-               // reset global state:
-               $wgCapitalLinks = $savedGlobal;
        }
 
        /**
@@ -532,12 +479,11 @@ class MWNamespaceTest extends MediaWikiTestCase {
         */
        public function testIsCapitalizedWithWgCapitalLinkOverrides() {
                global $wgCapitalLinkOverrides;
-               // Save the global to easily reset to MediaWiki default settings
-               $savedGlobal = $wgCapitalLinkOverrides;
 
                // Test default settings
                $this->assertIsCapitalized( NS_PROJECT      );
                $this->assertIsCapitalized( NS_PROJECT_TALK );
+
                // hardcoded namespaces (see above function) are capitalized:
                $this->assertIsCapitalized( NS_SPECIAL   );
                $this->assertIsCapitalized( NS_USER      );
@@ -547,20 +493,19 @@ class MWNamespaceTest extends MediaWikiTestCase {
                $wgCapitalLinkOverrides[NS_SPECIAL]   = false;
                $wgCapitalLinkOverrides[NS_USER]      = false;
                $wgCapitalLinkOverrides[NS_MEDIAWIKI] = false;
+
                $this->assertIsCapitalized( NS_SPECIAL   );
                $this->assertIsCapitalized( NS_USER      );
                $this->assertIsCapitalized( NS_MEDIAWIKI );
 
-               $wgCapitalLinkOverrides = $savedGlobal;
                $wgCapitalLinkOverrides[NS_PROJECT] = false;
                $this->assertIsNotCapitalized( NS_PROJECT );
+
                $wgCapitalLinkOverrides[NS_PROJECT] = true ;
                $this->assertIsCapitalized( NS_PROJECT );
-               unset(  $wgCapitalLinkOverrides[NS_PROJECT] );
-               $this->assertIsCapitalized( NS_PROJECT );
 
-               // reset global state:
-               $wgCapitalLinkOverrides = $savedGlobal;
+               unset( $wgCapitalLinkOverrides[NS_PROJECT] );
+               $this->assertIsCapitalized( NS_PROJECT );
        }
 
        public function testHasGenderDistinction() {
@@ -578,10 +523,10 @@ class MWNamespaceTest extends MediaWikiTestCase {
 
        public function testIsNonincludable() {
                global $wgNonincludableNamespaces;
+
                $wgNonincludableNamespaces = array( NS_USER );
 
                $this->assertTrue( MWNamespace::isNonincludable( NS_USER ) );
-
                $this->assertFalse( MWNamespace::isNonincludable( NS_TEMPLATE ) );
        }
 
index 20181fd..22450dc 100644 (file)
@@ -1,6 +1,14 @@
 <?php
 
 class MessageTest extends MediaWikiLangTestCase {
+       protected function setUp() {
+               parent::setUp();
+
+               $this->setMwGlobals( array(
+                       'wgLang' => Language::factory( 'en' ),
+                       'wgForceUIMsgAsContentMsg' => array(),
+               ) );
+       }
 
        function testExists() {
                $this->assertTrue( wfMessage( 'mainpage' )->exists() );
@@ -43,16 +51,11 @@ class MessageTest extends MediaWikiLangTestCase {
 
        function testInContentLanguage() {
                global $wgLang, $wgForceUIMsgAsContentMsg;
-               $oldLang = $wgLang;
                $wgLang = Language::factory( 'fr' );
 
                $this->assertEquals( 'Main Page', wfMessage( 'mainpage' )->inContentLanguage()->plain(), 'ForceUIMsg disabled' );
                $wgForceUIMsgAsContentMsg['testInContentLanguage'] = 'mainpage';
                $this->assertEquals( 'Accueil', wfMessage( 'mainpage' )->inContentLanguage()->plain(), 'ForceUIMsg enabled' );
-
-               /* Restore globals */
-               $wgLang = $oldLang;
-               unset( $wgForceUIMsgAsContentMsg['testInContentLanguage'] );
        }
 
        /**
index 59c955f..5b2adaf 100644 (file)
@@ -5,15 +5,19 @@ class ParserOptionsTest extends MediaWikiTestCase {
        private $popts;
        private $pcache;
 
-       function setUp() {
-               global $wgContLang, $wgUser, $wgLanguageCode;
-               $wgContLang = Language::factory( $wgLanguageCode );
-               $this->popts = ParserOptions::newFromUserAndLang( $wgUser, $wgContLang );
-               $this->pcache = ParserCache::singleton();
-       }
+       protected function setUp() {
+               global $wgLanguageCode, $wgUser;
+               parent::setUp();
+
+               $langObj = Language::factory( $wgLanguageCode );
+
+               $this->setMwGlobals( array(
+                       'wgContLang' => $langObj,
+                       'wgUseDynamicDates' => true,
+               ) );
 
-       function tearDown() {
-               parent::tearDown();
+               $this->popts = ParserOptions::newFromUserAndLang( $wgUser, $langObj );
+               $this->pcache = ParserCache::singleton();
        }
 
        /**
@@ -21,14 +25,12 @@ class ParserOptionsTest extends MediaWikiTestCase {
         * @group Database
         */
        function testGetParserCacheKeyWithDynamicDates() {
-               global $wgUseDynamicDates;
-               $wgUseDynamicDates = true;
-
                $title = Title::newFromText( "Some test article" );
                $page = WikiPage::factory( $title );
 
                $pcacheKeyBefore = $this->pcache->getKey( $page, $this->popts );
                $this->assertNotNull( $this->popts->getDateFormat() );
+
                $pcacheKeyAfter = $this->pcache->getKey( $page, $this->popts );
                $this->assertEquals( $pcacheKeyBefore, $pcacheKeyAfter );
        }
index f627458..4487210 100644 (file)
@@ -5,7 +5,7 @@
 
 class PathRouterTest extends MediaWikiTestCase {
 
-       public function setUp() {
+       protected function setUp() {
                $router = new PathRouter;
                $router->add("/wiki/$1");
                $this->basicRouter = $router;
@@ -182,7 +182,7 @@ class PathRouterTest extends MediaWikiTestCase {
                $this->assertEquals( $matches, array( 'title' => "Title_With Space" ) );
        }
 
-       public function dataRegexpChars() {
+       public static function provideRegexpChars() {
                return array(
                        array( "$" ),
                        array( "$1" ),
@@ -193,7 +193,7 @@ class PathRouterTest extends MediaWikiTestCase {
 
        /**
         * Make sure the router doesn't break on special characters like $ used in regexp replacements
-        * @dataProvider dataRegexpChars
+        * @dataProvider provideRegexpChars
         */
        public function testRegexpChars( $char ) {
                $matches = $this->basicRouter->parse( "/wiki/$char" );
index 0e12317..1a8a4cc 100644 (file)
@@ -7,7 +7,6 @@ class PreferencesTest extends MediaWikiTestCase {
 
        function __construct() {
                parent::__construct();
-               global $wgEnableEmail;
 
                $this->prefUsers['noemail'] = new User;
 
@@ -23,9 +22,12 @@ class PreferencesTest extends MediaWikiTestCase {
 
                $this->context = new RequestContext;
                $this->context->setTitle( Title::newFromText('PreferencesTest') );
+       }
+
+       protected function setUp() {
+               parent::setUp();
 
-               //some tests depends on email setting
-               $wgEnableEmail = true;
+               $this->setMwGlobals( 'wgEnableEmail', true );
        }
 
        /**
index fbf271c..a1f808c 100644 (file)
@@ -220,7 +220,8 @@ class RecentChangeTest extends MediaWikiTestCase {
         * @todo: Emulate these edits somehow and extract
         * raw edit summary from RecentChange object
         * --
-
+        */
+/*
        function testIrcMsgForBlankingAES() {
                // $this->context->msg( 'autosumm-blank', .. );
        }
@@ -237,8 +238,7 @@ class RecentChangeTest extends MediaWikiTestCase {
                // $this->context->msg( 'undo-summary', .. );
        }
 
-        * --
-        */
+*/
 
        /**
         * @param $expected String Expected IRC text without colors codes
index ab70483..893d260 100644 (file)
@@ -15,7 +15,7 @@ class ResourceLoaderTest extends MediaWikiTestCase {
        }
 
        /* Provider Methods */
-       public function provideValidModules() {
+       public static function provideValidModules() {
                return array(
                        array( 'TEST.validModule1', new ResourceLoaderTestModule() ),
                );
@@ -61,7 +61,7 @@ class ResourceLoaderTest extends MediaWikiTestCase {
                $this->assertEquals( $modules, ResourceLoaderContext::expandModuleNames( $packed ), $desc );
        }
 
-       public function providePackedModules() {
+       public static function providePackedModules() {
                return array(
                        array(
                                'Example from makePackedModulesString doc comment',
index 8a7face..e06de7c 100644 (file)
@@ -3,6 +3,7 @@
 /**
  * Test class for Revision storage.
  *
+ * @group ContentHandler
  * @group Database
  * ^--- important, causes temporary tables to be used instead of the real database
  *
@@ -11,6 +12,9 @@
  */
 class RevisionStorageTest extends MediaWikiTestCase {
 
+       /**
+        * @var WikiPage $the_page
+        */
        var $the_page;
 
        function  __construct( $name = null, array $data = array(), $dataName = '' ) {
@@ -35,11 +39,34 @@ class RevisionStorageTest extends MediaWikiTestCase {
        }
 
        public function setUp() {
+               global $wgExtraNamespaces, $wgNamespaceContentModels, $wgContentHandlers, $wgContLang;
+
+               $wgExtraNamespaces[ 12312 ] = 'Dummy';
+               $wgExtraNamespaces[ 12313 ] = 'Dummy_talk';
+
+               $wgNamespaceContentModels[ 12312 ] = 'DUMMY';
+               $wgContentHandlers[ 'DUMMY' ] = 'DummyContentHandlerForTesting';
+
+               MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache
+               $wgContLang->resetNamespaces(); # reset namespace cache
                if ( !$this->the_page ) {
-                       $this->the_page = $this->createPage( 'RevisionStorageTest_the_page', "just a dummy page" );
+                       $this->the_page = $this->createPage( 'RevisionStorageTest_the_page', "just a dummy page", CONTENT_MODEL_WIKITEXT );
                }
        }
 
+       public function tearDown() {
+               global $wgExtraNamespaces, $wgNamespaceContentModels, $wgContentHandlers, $wgContLang;
+
+               unset( $wgExtraNamespaces[ 12312 ] );
+               unset( $wgExtraNamespaces[ 12313 ] );
+
+               unset( $wgNamespaceContentModels[ 12312 ] );
+               unset( $wgContentHandlers[ 'DUMMY' ] );
+
+               MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache
+               $wgContLang->resetNamespaces(); # reset namespace cache
+       }
+
        protected function makeRevision( $props = null ) {
                if ( $props === null ) $props = array();
 
@@ -63,7 +90,8 @@ class RevisionStorageTest extends MediaWikiTestCase {
                        $page->doDeleteArticle( "done" );
                }
 
-               $page->doEdit( $text, "testing", EDIT_NEW );
+               $content = ContentHandler::makeContent( $text, $page->getTitle(), $model );
+               $page->doEditContent( $content, "testing", EDIT_NEW );
 
                return $page;
        }
@@ -75,6 +103,8 @@ class RevisionStorageTest extends MediaWikiTestCase {
                $this->assertEquals( $orig->getPage(), $rev->getPage() );
                $this->assertEquals( $orig->getTimestamp(), $rev->getTimestamp() );
                $this->assertEquals( $orig->getUser(), $rev->getUser() );
+               $this->assertEquals( $orig->getContentModel(), $rev->getContentModel() );
+               $this->assertEquals( $orig->getContentFormat(), $rev->getContentFormat() );
                $this->assertEquals( $orig->getSha1(), $rev->getSha1() );
        }
 
@@ -122,7 +152,7 @@ class RevisionStorageTest extends MediaWikiTestCase {
         */
        public function testNewFromArchiveRow()
        {
-               $page = $this->createPage( 'RevisionStorageTest_testNewFromArchiveRow', 'Lorem Ipsum' );
+               $page = $this->createPage( 'RevisionStorageTest_testNewFromArchiveRow', 'Lorem Ipsum', CONTENT_MODEL_WIKITEXT );
                $orig = $page->getRevision();
                $page->doDeleteArticle( 'test Revision::newFromArchiveRow' );
 
@@ -155,10 +185,10 @@ class RevisionStorageTest extends MediaWikiTestCase {
         */
        public function testFetchRevision()
        {
-               $page = $this->createPage( 'RevisionStorageTest_testFetchRevision', 'one' );
+               $page = $this->createPage( 'RevisionStorageTest_testFetchRevision', 'one', CONTENT_MODEL_WIKITEXT );
                $id1 = $page->getRevision()->getId();
 
-               $page->doEdit( 'two', 'second rev' );
+               $page->doEditContent( new WikitextContent( 'two' ), 'second rev' );
                $id2 = $page->getRevision()->getId();
 
                $res = Revision::fetchRevision( $page->getTitle() );
@@ -179,12 +209,23 @@ class RevisionStorageTest extends MediaWikiTestCase {
         */
        public function testSelectFields()
        {
+               global $wgContentHandlerUseDB;
+
                $fields = Revision::selectFields();
 
                $this->assertTrue( in_array( 'rev_id', $fields ), 'missing rev_id in list of fields');
                $this->assertTrue( in_array( 'rev_page', $fields ), 'missing rev_page in list of fields');
                $this->assertTrue( in_array( 'rev_timestamp', $fields ), 'missing rev_timestamp in list of fields');
                $this->assertTrue( in_array( 'rev_user', $fields ), 'missing rev_user in list of fields');
+
+               if ( $wgContentHandlerUseDB ) {
+                       $this->assertTrue( in_array( 'rev_content_model', $fields ),
+                                                               'missing rev_content_model in list of fields');
+                       $this->assertTrue( in_array( 'rev_content_format', $fields ),
+                                                               'missing rev_content_format in list of fields');
+               } else {
+                       $this->markTestSkipped( '$wgContentHandlerUseDB is disabled' );
+               }
        }
 
        /**
@@ -205,12 +246,25 @@ class RevisionStorageTest extends MediaWikiTestCase {
         */
        public function testGetText()
        {
+               $this->hideDeprecated( 'Revision::getText' );
+
                $orig = $this->makeRevision( array( 'text' => 'hello hello.' ) );
                $rev = Revision::newFromId( $orig->getId() );
 
                $this->assertEquals( 'hello hello.', $rev->getText() );
        }
 
+       /**
+        * @covers Revision::getContent
+        */
+       public function testGetContent()
+       {
+               $orig = $this->makeRevision( array( 'text' => 'hello hello.' ) );
+               $rev = Revision::newFromId( $orig->getId() );
+
+               $this->assertEquals( 'hello hello.', $rev->getContent()->getNativeData() );
+       }
+
        /**
         * @covers Revision::revText
         */
@@ -228,17 +282,57 @@ class RevisionStorageTest extends MediaWikiTestCase {
         */
        public function testGetRawText()
        {
+               $this->hideDeprecated( 'Revision::getRawText' );
+
                $orig = $this->makeRevision( array( 'text' => 'hello hello raw.' ) );
                $rev = Revision::newFromId( $orig->getId() );
 
                $this->assertEquals( 'hello hello raw.', $rev->getRawText() );
        }
+
+       /**
+        * @covers Revision::getContentModel
+        */
+       public function testGetContentModel()
+       {
+               global $wgContentHandlerUseDB;
+
+               if ( !$wgContentHandlerUseDB ) {
+                       $this->markTestSkipped( '$wgContentHandlerUseDB is disabled' );
+               }
+
+               $orig = $this->makeRevision( array( 'text' => 'hello hello.',
+                                                                                       'content_model' => CONTENT_MODEL_JAVASCRIPT ) );
+               $rev = Revision::newFromId( $orig->getId() );
+
+               $this->assertEquals( CONTENT_MODEL_JAVASCRIPT, $rev->getContentModel() );
+       }
+
+       /**
+        * @covers Revision::getContentFormat
+        */
+       public function testGetContentFormat()
+       {
+               global $wgContentHandlerUseDB;
+
+               if ( !$wgContentHandlerUseDB ) {
+                       $this->markTestSkipped( '$wgContentHandlerUseDB is disabled' );
+               }
+
+               $orig = $this->makeRevision( array( 'text' => 'hello hello.',
+                                                                                       'content_model' => CONTENT_MODEL_JAVASCRIPT,
+                                                                                       'content_format' => CONTENT_FORMAT_JAVASCRIPT ) );
+               $rev = Revision::newFromId( $orig->getId() );
+
+               $this->assertEquals( CONTENT_FORMAT_JAVASCRIPT, $rev->getContentFormat() );
+       }
+
        /**
         * @covers Revision::isCurrent
         */
        public function testIsCurrent()
        {
-               $page = $this->createPage( 'RevisionStorageTest_testIsCurrent', 'Lorem Ipsum' );
+               $page = $this->createPage( 'RevisionStorageTest_testIsCurrent', 'Lorem Ipsum', CONTENT_MODEL_WIKITEXT );
                $rev1 = $page->getRevision();
 
                # @todo: find out if this should be true
@@ -247,7 +341,7 @@ class RevisionStorageTest extends MediaWikiTestCase {
                $rev1x = Revision::newFromId( $rev1->getId() );
                $this->assertTrue( $rev1x->isCurrent() );
 
-               $page->doEdit( 'Bla bla', 'second rev' );
+               $page->doEditContent( ContentHandler::makeContent( 'Bla bla', $page->getTitle(), CONTENT_MODEL_WIKITEXT ), 'second rev' );
                $rev2 = $page->getRevision();
 
                # @todo: find out if this should be true
@@ -265,12 +359,13 @@ class RevisionStorageTest extends MediaWikiTestCase {
         */
        public function testGetPrevious()
        {
-               $page = $this->createPage( 'RevisionStorageTest_testGetPrevious', 'Lorem Ipsum testGetPrevious' );
+               $page = $this->createPage( 'RevisionStorageTest_testGetPrevious', 'Lorem Ipsum testGetPrevious', CONTENT_MODEL_WIKITEXT );
                $rev1 = $page->getRevision();
 
                $this->assertNull( $rev1->getPrevious() );
 
-               $page->doEdit( 'Bla bla', 'second rev testGetPrevious' );
+               $page->doEditContent( ContentHandler::makeContent( 'Bla bla', $page->getTitle(), CONTENT_MODEL_WIKITEXT ),
+                                                               'second rev testGetPrevious' );
                $rev2 = $page->getRevision();
 
                $this->assertNotNull( $rev2->getPrevious() );
@@ -282,12 +377,13 @@ class RevisionStorageTest extends MediaWikiTestCase {
         */
        public function testGetNext()
        {
-               $page = $this->createPage( 'RevisionStorageTest_testGetNext', 'Lorem Ipsum testGetNext' );
+               $page = $this->createPage( 'RevisionStorageTest_testGetNext', 'Lorem Ipsum testGetNext', CONTENT_MODEL_WIKITEXT );
                $rev1 = $page->getRevision();
 
                $this->assertNull( $rev1->getNext() );
 
-               $page->doEdit( 'Bla bla', 'second rev testGetNext' );
+               $page->doEditContent( ContentHandler::makeContent( 'Bla bla', $page->getTitle(), CONTENT_MODEL_WIKITEXT ),
+                                                               'second rev testGetNext' );
                $rev2 = $page->getRevision();
 
                $this->assertNotNull( $rev1->getNext() );
@@ -299,18 +395,20 @@ class RevisionStorageTest extends MediaWikiTestCase {
         */
        public function testNewNullRevision()
        {
-               $page = $this->createPage( 'RevisionStorageTest_testNewNullRevision', 'some testing text' );
+               $page = $this->createPage( 'RevisionStorageTest_testNewNullRevision', 'some testing text', CONTENT_MODEL_WIKITEXT );
                $orig = $page->getRevision();
 
                $dbw = wfGetDB( DB_MASTER );
                $rev = Revision::newNullRevision( $dbw, $page->getId(), 'a null revision', false );
 
-               $this->assertNotEquals( $orig->getId(), $rev->getId(), 'new null revision shold have a different id from the original revision' );
-               $this->assertEquals( $orig->getTextId(), $rev->getTextId(), 'new null revision shold have the same text id as the original revision' );
-               $this->assertEquals( 'some testing text', $rev->getText() );
+               $this->assertNotEquals( $orig->getId(), $rev->getId(),
+                                                               'new null revision shold have a different id from the original revision' );
+               $this->assertEquals( $orig->getTextId(), $rev->getTextId(),
+                                                               'new null revision shold have the same text id as the original revision' );
+               $this->assertEquals( 'some testing text', $rev->getContent()->getNativeData() );
        }
 
-       public function dataUserWasLastToEdit() {
+       public static function provideUserWasLastToEdit() {
                return array(
                        array( #0
                                3, true, # actually the last edit
@@ -328,7 +426,7 @@ class RevisionStorageTest extends MediaWikiTestCase {
        }
 
        /**
-        * @dataProvider dataUserWasLastToEdit
+        * @dataProvider provideUserWasLastToEdit
         */
        public function testUserWasLastToEdit( $sinceIdx, $expectedLast ) {
                $userA = \User::newFromName( "RevisionStorageTest_userA" );
@@ -351,9 +449,11 @@ class RevisionStorageTest extends MediaWikiTestCase {
                # zero
                $revisions[0] = new Revision( array(
                        'page' => $page->getId(),
+                       'title' => $page->getTitle(), // we need the title to determine the page's default content model
                        'timestamp' => '20120101000000',
                        'user' => $userA->getId(),
                        'text' => 'zero',
+                       'content_model' => CONTENT_MODEL_WIKITEXT,
                        'summary' => 'edit zero'
                ) );
                $revisions[0]->insertOn( $dbw );
@@ -361,9 +461,11 @@ class RevisionStorageTest extends MediaWikiTestCase {
                # one
                $revisions[1] = new Revision( array(
                        'page' => $page->getId(),
+                       'title' => $page->getTitle(), // still need the title, because $page->getId() is 0 (there's no entry in the page table)
                        'timestamp' => '20120101000100',
                        'user' => $userA->getId(),
                        'text' => 'one',
+                       'content_model' => CONTENT_MODEL_WIKITEXT,
                        'summary' => 'edit one'
                ) );
                $revisions[1]->insertOn( $dbw );
@@ -371,9 +473,11 @@ class RevisionStorageTest extends MediaWikiTestCase {
                # two
                $revisions[2] = new Revision( array(
                        'page' => $page->getId(),
+                       'title' => $page->getTitle(),
                        'timestamp' => '20120101000200',
                        'user' => $userB->getId(),
                        'text' => 'two',
+                       'content_model' => CONTENT_MODEL_WIKITEXT,
                        'summary' => 'edit two'
                ) );
                $revisions[2]->insertOn( $dbw );
@@ -381,9 +485,11 @@ class RevisionStorageTest extends MediaWikiTestCase {
                # three
                $revisions[3] = new Revision( array(
                        'page' => $page->getId(),
+                       'title' => $page->getTitle(),
                        'timestamp' => '20120101000300',
                        'user' => $userA->getId(),
                        'text' => 'three',
+                       'content_model' => CONTENT_MODEL_WIKITEXT,
                        'summary' => 'edit three'
                ) );
                $revisions[3]->insertOn( $dbw );
@@ -391,9 +497,11 @@ class RevisionStorageTest extends MediaWikiTestCase {
                # four
                $revisions[4] = new Revision( array(
                        'page' => $page->getId(),
+                       'title' => $page->getTitle(),
                        'timestamp' => '20120101000200',
                        'user' => $userA->getId(),
                        'text' => 'zero',
+                       'content_model' => CONTENT_MODEL_WIKITEXT,
                        'summary' => 'edit four'
                ) );
                $revisions[4]->insertOn( $dbw );
diff --git a/tests/phpunit/includes/RevisionStorageTest_ContentHandlerUseDB.php b/tests/phpunit/includes/RevisionStorageTest_ContentHandlerUseDB.php
new file mode 100644 (file)
index 0000000..3dfaa8d
--- /dev/null
@@ -0,0 +1,86 @@
+<?php
+
+/**
+ * @group ContentHandler
+ * @group Database
+ * ^--- important, causes temporary tables to be used instead of the real database
+ */
+class RevisionTest_ContentHandlerUseDB extends RevisionStorageTest {
+       var $saveContentHandlerNoDB = null;
+
+       function setUp() {
+               global $wgContentHandlerUseDB;
+
+               $this->saveContentHandlerNoDB = $wgContentHandlerUseDB;
+
+               $wgContentHandlerUseDB = false;
+
+               $dbw = wfGetDB( DB_MASTER );
+
+               $page_table = $dbw->tableName( 'page' );
+               $revision_table = $dbw->tableName( 'revision' );
+               $archive_table = $dbw->tableName( 'archive' );
+
+               if ( $dbw->fieldExists( $page_table, 'page_content_model' ) ) {
+                       $dbw->query( "alter table $page_table drop column page_content_model" );
+                       $dbw->query( "alter table $revision_table drop column rev_content_model" );
+                       $dbw->query( "alter table $revision_table drop column rev_content_format" );
+                       $dbw->query( "alter table $archive_table drop column ar_content_model" );
+                       $dbw->query( "alter table $archive_table drop column ar_content_format" );
+               }
+
+               parent::setUp();
+       }
+
+       function tearDown() {
+               global $wgContentHandlerUseDB;
+
+               parent::tearDown();
+
+               $wgContentHandlerUseDB = $this->saveContentHandlerNoDB;
+       }
+
+       /**
+        * @covers Revision::selectFields
+        */
+       public function testSelectFields()
+       {
+               $fields = Revision::selectFields();
+
+               $this->assertTrue( in_array( 'rev_id', $fields ), 'missing rev_id in list of fields');
+               $this->assertTrue( in_array( 'rev_page', $fields ), 'missing rev_page in list of fields');
+               $this->assertTrue( in_array( 'rev_timestamp', $fields ), 'missing rev_timestamp in list of fields');
+               $this->assertTrue( in_array( 'rev_user', $fields ), 'missing rev_user in list of fields');
+
+               $this->assertFalse( in_array( 'rev_content_model', $fields ), 'missing rev_content_model in list of fields');
+               $this->assertFalse( in_array( 'rev_content_format', $fields ), 'missing rev_content_format in list of fields');
+       }
+
+       /**
+        * @covers Revision::getContentModel
+        */
+       public function testGetContentModel()
+       {
+               $orig = $this->makeRevision( array( 'text' => 'hello hello.', 'content_model' => CONTENT_MODEL_JAVASCRIPT ) );
+               $rev = Revision::newFromId( $orig->getId() );
+
+               //NOTE: database fields for the content_model are disabled, so the model name is not retained.
+               //      We expect to get the default here instead of what was suppleid when creating the revision.
+               $this->assertEquals( CONTENT_MODEL_WIKITEXT, $rev->getContentModel() );
+       }
+
+
+       /**
+        * @covers Revision::getContentFormat
+        */
+       public function testGetContentFormat()
+       {
+               $orig = $this->makeRevision( array( 'text' => 'hello hello.', 'content_model' => CONTENT_MODEL_JAVASCRIPT, 'content_format' => 'text/javascript' ) );
+               $rev = Revision::newFromId( $orig->getId() );
+
+               $this->assertEquals( CONTENT_FORMAT_WIKITEXT, $rev->getContentFormat() );
+       }
+
+}
+
+
index d7654db..00e7119 100644 (file)
@@ -1,25 +1,56 @@
 <?php
 
+/**
+ * @group ContentHandler
+ */
 class RevisionTest extends MediaWikiTestCase {
-       var $saveGlobals = array();
-
-       function setUp() {
+       protected function setUp() {
                global $wgContLang;
-               $wgContLang = Language::factory( 'en' );
-               $globalSet = array(
+
+               parent::setUp();
+
+               $this->setMwGlobals( array(
+                       'wgContLang' => Language::factory( 'en' ),
                        'wgLegacyEncoding' => false,
                        'wgCompressRevisions' => false,
+
+                       'wgContentHandlerTextFallback' => 'ignore',
+               ) );
+
+               $this->mergeMwGlobalArrayValue(
+                       'wgExtraNamespaces',
+                       array(
+                               12312 => 'Dummy',
+                               12313 => 'Dummy_talk',
+                       )
                );
-               foreach ( $globalSet as $var => $data ) {
-                       $this->saveGlobals[$var] = $GLOBALS[$var];
-                       $GLOBALS[$var] = $data;
-               }
+
+               $this->mergeMwGlobalArrayValue(
+                       'wgNamespaceContentModels',
+                       array(
+                               12312 => 'testing',
+                       )
+               );
+
+               $this->mergeMwGlobalArrayValue(
+                       'wgContentHandlers',
+                       array(
+                               'testing' => 'DummyContentHandlerForTesting',
+                               'RevisionTestModifyableContent' => 'RevisionTestModifyableContentHandler',
+                       )
+               );
+
+               MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache
+               $wgContLang->resetNamespaces(); # reset namespace cache
        }
 
        function tearDown() {
-               foreach ( $this->saveGlobals as $var => $data ) {
-                       $GLOBALS[$var] = $data;
-               }
+               global $wgContLang;
+
+               MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache
+               $wgContLang->resetNamespaces(); # reset namespace cache
+
+               parent::tearDown();
        }
 
        function testGetRevisionText() {
@@ -107,7 +138,10 @@ class RevisionTest extends MediaWikiTestCase {
        }
 
        function testCompressRevisionTextUtf8Gzip() {
-               $GLOBALS['wgCompressRevisions'] = true;
+               global $wgCompressRevisions;
+
+               $wgCompressRevisions = true;
+
                $row = new stdClass;
                $row->old_text = "Wiki est l'\xc3\xa9cole superieur !";
                $row->old_flags = Revision::compressRevisionText( $row->old_text );
@@ -120,6 +154,298 @@ class RevisionTest extends MediaWikiTestCase {
                $this->assertEquals( "Wiki est l'\xc3\xa9cole superieur !",
                        Revision::getRevisionText( $row ), "getRevisionText" );
        }
+
+       # =================================================================================================================
+
+       /**
+        * @param string $text
+        * @param string $title
+        * @param string $model
+        * @return Revision
+        */
+       function newTestRevision( $text, $title = "Test", $model = CONTENT_MODEL_WIKITEXT, $format = null ) {
+               if ( is_string( $title ) ) {
+                       $title = Title::newFromText( $title );
+               }
+
+               $content = ContentHandler::makeContent( $text, $title, $model, $format );
+
+               $rev = new Revision(
+                       array(
+                               'id'         => 42,
+                               'page'       => 23,
+                               'title'      => $title,
+
+                               'content'    => $content,
+                               'length'     => $content->getSize(),
+                               'comment'    => "testing",
+                               'minor_edit' => false,
+
+                               'content_format' => $format,
+                       )
+               );
+
+               return $rev;
+       }
+
+       function dataGetContentModel() {
+               //NOTE: we expect the help namespace to always contain wikitext
+               return array(
+                       array( 'hello world', 'Help:Hello', null, null, CONTENT_MODEL_WIKITEXT ),
+                       array( 'hello world', 'User:hello/there.css', null, null, CONTENT_MODEL_CSS ),
+                       array( serialize('hello world'), 'Dummy:Hello', null, null, "testing" ),
+               );
+       }
+
+       /**
+        * @group Database
+        * @dataProvider dataGetContentModel
+        */
+       function testGetContentModel( $text, $title, $model, $format, $expectedModel ) {
+               $rev = $this->newTestRevision( $text, $title, $model, $format );
+
+               $this->assertEquals( $expectedModel, $rev->getContentModel() );
+       }
+
+       function dataGetContentFormat() {
+               //NOTE: we expect the help namespace to always contain wikitext
+               return array(
+                       array( 'hello world', 'Help:Hello', null, null, CONTENT_FORMAT_WIKITEXT ),
+                       array( 'hello world', 'Help:Hello', CONTENT_MODEL_CSS, null, CONTENT_FORMAT_CSS ),
+                       array( 'hello world', 'User:hello/there.css', null, null, CONTENT_FORMAT_CSS ),
+                       array( serialize('hello world'), 'Dummy:Hello', null, null, "testing" ),
+               );
+       }
+
+       /**
+        * @group Database
+        * @dataProvider dataGetContentFormat
+        */
+       function testGetContentFormat( $text, $title, $model, $format, $expectedFormat ) {
+               $rev = $this->newTestRevision( $text, $title, $model, $format );
+
+               $this->assertEquals( $expectedFormat, $rev->getContentFormat() );
+       }
+
+       function dataGetContentHandler() {
+               //NOTE: we expect the help namespace to always contain wikitext
+               return array(
+                       array( 'hello world', 'Help:Hello', null, null, 'WikitextContentHandler' ),
+                       array( 'hello world', 'User:hello/there.css', null, null, 'CssContentHandler' ),
+                       array( serialize('hello world'), 'Dummy:Hello', null, null, 'DummyContentHandlerForTesting' ),
+               );
+       }
+
+       /**
+        * @group Database
+        * @dataProvider dataGetContentHandler
+        */
+       function testGetContentHandler( $text, $title, $model, $format, $expectedClass ) {
+               $rev = $this->newTestRevision( $text, $title, $model, $format );
+
+               $this->assertEquals( $expectedClass, get_class( $rev->getContentHandler() ) );
+       }
+
+       function dataGetContent() {
+               //NOTE: we expect the help namespace to always contain wikitext
+               return array(
+                       array( 'hello world', 'Help:Hello', null, null, Revision::FOR_PUBLIC, 'hello world' ),
+                       array( serialize('hello world'), 'Hello', "testing", null, Revision::FOR_PUBLIC, serialize('hello world') ),
+                       array( serialize('hello world'), 'Dummy:Hello', null, null, Revision::FOR_PUBLIC, serialize('hello world') ),
+               );
+       }
+
+       /**
+        * @group Database
+        * @dataProvider dataGetContent
+        */
+       function testGetContent( $text, $title, $model, $format, $audience, $expectedSerialization ) {
+               $rev = $this->newTestRevision( $text, $title, $model, $format );
+               $content = $rev->getContent( $audience );
+
+               $this->assertEquals( $expectedSerialization, is_null( $content ) ? null : $content->serialize( $format ) );
+       }
+
+       function dataGetText() {
+               //NOTE: we expect the help namespace to always contain wikitext
+               return array(
+                       array( 'hello world', 'Help:Hello', null, null, Revision::FOR_PUBLIC, 'hello world' ),
+                       array( serialize('hello world'), 'Hello', "testing", null, Revision::FOR_PUBLIC, null ),
+                       array( serialize('hello world'), 'Dummy:Hello', null, null, Revision::FOR_PUBLIC, null ),
+               );
+       }
+
+       /**
+        * @group Database
+        * @dataProvider dataGetText
+        */
+       function testGetText( $text, $title, $model, $format, $audience, $expectedText ) {
+               $this->hideDeprecated( 'Revision::getText' );
+
+               $rev = $this->newTestRevision( $text, $title, $model, $format );
+
+               $this->assertEquals( $expectedText, $rev->getText( $audience ) );
+       }
+
+       /**
+        * @group Database
+        * @dataProvider dataGetText
+        */
+       function testGetRawText( $text, $title, $model, $format, $audience, $expectedText ) {
+               $this->hideDeprecated( 'Revision::getRawText' );
+
+               $rev = $this->newTestRevision( $text, $title, $model, $format );
+
+               $this->assertEquals( $expectedText, $rev->getRawText( $audience ) );
+       }
+
+
+       public function dataGetSize( ) {
+               return array(
+                       array( "hello world.", CONTENT_MODEL_WIKITEXT, 12 ),
+                       array( serialize( "hello world." ), "testing", 12 ),
+               );
+       }
+
+       /**
+        * @covers Revision::getSize
+        * @group Database
+        * @dataProvider dataGetSize
+        */
+       public function testGetSize( $text, $model, $expected_size )
+       {
+               $rev = $this->newTestRevision( $text, 'RevisionTest_testGetSize', $model );
+               $this->assertEquals( $expected_size, $rev->getSize() );
+       }
+
+       public function dataGetSha1( ) {
+               return array(
+                       array( "hello world.", CONTENT_MODEL_WIKITEXT, Revision::base36Sha1( "hello world." ) ),
+                       array( serialize( "hello world." ), "testing", Revision::base36Sha1( serialize( "hello world." ) ) ),
+               );
+       }
+
+       /**
+        * @covers Revision::getSha1
+        * @group Database
+        * @dataProvider dataGetSha1
+        */
+       public function testGetSha1( $text, $model, $expected_hash )
+       {
+               $rev = $this->newTestRevision( $text, 'RevisionTest_testGetSha1', $model );
+               $this->assertEquals( $expected_hash, $rev->getSha1() );
+       }
+
+       public function testConstructWithText() {
+               $this->hideDeprecated( "Revision::getText" );
+
+               $rev = new Revision( array(
+                                         'text' => 'hello world.',
+                                         'content_model' => CONTENT_MODEL_JAVASCRIPT
+                                    ));
+
+               $this->assertNotNull( $rev->getText(), 'no content text' );
+               $this->assertNotNull( $rev->getContent(), 'no content object available' );
+               $this->assertEquals( CONTENT_MODEL_JAVASCRIPT, $rev->getContent()->getModel() );
+               $this->assertEquals( CONTENT_MODEL_JAVASCRIPT, $rev->getContentModel() );
+       }
+
+       public function testConstructWithContent() {
+               $this->hideDeprecated( "Revision::getText" );
+
+               $title = Title::newFromText( 'RevisionTest_testConstructWithContent' );
+
+               $rev = new Revision( array(
+                                         'content' => ContentHandler::makeContent( 'hello world.', $title, CONTENT_MODEL_JAVASCRIPT ),
+                                    ));
+
+               $this->assertNotNull( $rev->getText(), 'no content text' );
+               $this->assertNotNull( $rev->getContent(), 'no content object available' );
+               $this->assertEquals( CONTENT_MODEL_JAVASCRIPT, $rev->getContent()->getModel() );
+               $this->assertEquals( CONTENT_MODEL_JAVASCRIPT, $rev->getContentModel() );
+       }
+
+       /**
+        * Tests whether $rev->getContent() returns a clone when needed.
+        *
+        * @group Database
+        */
+       function testGetContentClone( ) {
+               $content = new RevisionTestModifyableContent( "foo" );
+
+               $rev = new Revision(
+                       array(
+                               'id'         => 42,
+                               'page'       => 23,
+                               'title'      => Title::newFromText( "testGetContentClone_dummy" ),
+
+                               'content'    => $content,
+                               'length'     => $content->getSize(),
+                               'comment'    => "testing",
+                               'minor_edit' => false,
+                       )
+               );
+
+               $content = $rev->getContent( Revision::RAW );
+               $content->setText( "bar" );
+
+               $content2 = $rev->getContent( Revision::RAW );
+               $this->assertNotSame( $content, $content2, "expected a clone" ); // content is mutable, expect clone
+               $this->assertEquals( "foo", $content2->getText() ); // clone should contain the original text
+
+               $content2->setText( "bla bla" );
+               $this->assertEquals( "bar", $content->getText() ); // clones should be independent
+       }
+
+
+       /**
+        * Tests whether $rev->getContent() returns the same object repeatedly if appropriate.
+        *
+        * @group Database
+        */
+       function testGetContentUncloned() {
+               $rev = $this->newTestRevision( "hello", "testGetContentUncloned_dummy", CONTENT_MODEL_WIKITEXT );
+               $content = $rev->getContent( Revision::RAW );
+               $content2 = $rev->getContent( Revision::RAW );
+
+               // for immutable content like wikitext, this should be the same object
+               $this->assertSame( $content, $content2 );
+       }
+
+}
+
+class RevisionTestModifyableContent extends TextContent {
+       public function __construct( $text ) {
+               parent::__construct( $text, "RevisionTestModifyableContent" );
+       }
+
+       public function copy( ) {
+               return new RevisionTestModifyableContent( $this->mText );
+       }
+
+       public function getText() {
+               return $this->mText;
+       }
+
+       public function setText( $text ) {
+               $this->mText = $text;
+       }
+
 }
 
+class RevisionTestModifyableContentHandler extends TextContentHandler {
 
+       public function __construct( ) {
+               parent::__construct( "RevisionTestModifyableContent", array( CONTENT_FORMAT_TEXT ) );
+       }
+
+       public function unserializeContent( $text, $format = null ) {
+               $this->checkFormat( $format );
+
+               return new RevisionTestModifyableContent( $text );
+       }
+
+       public function makeEmptyContent() {
+               return new RevisionTestModifyableContent( '' );
+       }
+}
index 59ba0a0..2f55de4 100644 (file)
@@ -5,18 +5,23 @@ class TestSample extends MediaWikiLangTestCase {
        /**
         * Anything that needs to happen before your tests should go here.
         */
-       function setUp() {
-               global $wgContLang;
+       protected function setUp() {
+               // Be sure to do call the parent setup and teardown functions.
+               // This makes sure that all the various cleanup and restorations
+               // happen as they should (including the restoration for setMwGlobals).
                parent::setUp();
 
-               /* For example, we need to set $wgContLang for creating a new Title */
-               $wgContLang = Language::factory( 'en' );
+               // This sets the globals and will restore them automatically
+               // after each test.
+               $this->setMwGlobals( array(
+                       'wgContLang' => Language::factory( 'en' ),
+               ) );
        }
 
        /**
         * Anything cleanup you need to do should go here.
         */
-       function tearDown() {
+       protected function tearDown() {
                parent::tearDown();
        }
 
@@ -40,8 +45,10 @@ class TestSample extends MediaWikiLangTestCase {
        /**
         * If you want to run a the same test with a variety of data. use a data provider.
         * see: http://www.phpunit.de/manual/3.4/en/writing-tests-for-phpunit.html
+        *
+        * Note: Data providers are always called statically and outside setUp/tearDown!
         */
-       public function provideTitles() {
+       public static function provideTitles() {
                return array(
                        array( 'Text', NS_MEDIA, 'Media:Text' ),
                        array( 'Text', null, 'Text' ),
@@ -78,6 +85,7 @@ class TestSample extends MediaWikiLangTestCase {
         * example) as arguments to the next method (e.g. $title in
         * testTitleDepends is whatever testInitialCreatiion returned.)
         */
+
        /**
         * @depends testSetUpMainPageTitleForNextTest
         * See http://www.phpunit.de/manual/3.4/en/appendixes.annotations.html#appendixes.annotations.depends
index ac9971e..c593d2f 100644 (file)
@@ -2,7 +2,11 @@
 
 class SanitizerTest extends MediaWikiTestCase {
 
-       function setUp() {
+       protected function setUp() {
+               parent::setUp();
+
+               $this->setMwGlobals( 'wgCleanupPresentationalAttributes', true );
+
                AutoLoader::loadClass( 'Sanitizer' );
        }
 
@@ -110,20 +114,22 @@ class SanitizerTest extends MediaWikiTestCase {
                $this->assertEquals( Sanitizer::decodeTagAttributes( 'foo=&foobar;' ), array( 'foo' => '&foobar;' ), 'Entity-like items are accepted' );
        }
 
-       function testDeprecatedAttributesDisabled() {
-               $GLOBALS['wgCleanupPresentationalAttributes'] = false;
-               $this->assertEquals( ' clear="left"', Sanitizer::fixTagAttributes( 'clear="left"', 'br' ), 'Deprecated attributes are not converted to styles when enabled.' );
-       }
-
        /**
         * @dataProvider provideDeprecatedAttributes
         */
        function testDeprecatedAttributes( $input, $tag, $expected, $message = null ) {
-               $GLOBALS['wgCleanupPresentationalAttributes'] = true;
                $this->assertEquals( $expected, Sanitizer::fixTagAttributes( $input, $tag ), $message );
        }
 
-       function provideDeprecatedAttributes() {
+       function testDeprecatedAttributesDisabled() {
+               global $wgCleanupPresentationalAttributes;
+
+               $wgCleanupPresentationalAttributes = false;
+
+               $this->assertEquals( ' clear="left"', Sanitizer::fixTagAttributes( 'clear="left"', 'br' ), 'Deprecated attributes are not converted to styles when enabled.' );
+       }
+
+       public static function provideDeprecatedAttributes() {
                return array(
                        array( 'clear="left"', 'br', ' style="clear: left;"', 'Deprecated attributes are converted to styles when enabled.' ),
                        array( 'clear="all"', 'br', ' style="clear: both;"', 'clear=all is converted to clear: both; not clear: all;' ),
@@ -170,7 +176,7 @@ class SanitizerTest extends MediaWikiTestCase {
                );
        }
 
-       function provideCssCommentsFixtures() {
+       public static function provideCssCommentsFixtures() {
                /** array( <expected>, <css>, [message] ) */
                return array(
                        array( ' ', '/**/' ),
index 8589c18..8f6aafa 100644 (file)
@@ -99,7 +99,8 @@ testBrowser           = "firefox"
        private $testSuites1 = null;
 
 
-       public function setUp() {
+       protected function setUp() {
+               parent::setUp();
                if ( !defined( 'SELENIUMTEST' ) ) {
                        define( 'SELENIUMTEST', true );
                }
@@ -108,7 +109,7 @@ testBrowser                 = "firefox"
        /**
         * Clean up the temporary file used to store the selenium settings.
         */
-       public function tearDown() {
+       protected function tearDown() {
                if ( strlen( $this->tempFileName ) > 0 ) {
                        unlink( $this->tempFileName );
                        unset( $this->tempFileName );
index 57d3532..4e0d2f4 100644 (file)
@@ -25,7 +25,7 @@ function getSiteParams( $conf, $wiki ) {
 class SiteConfigurationTest extends MediaWikiTestCase {
        var $mConf;
 
-       function setUp() {
+       protected function setUp() {
                $this->mConf = new SiteConfiguration;
 
                $this->mConf->suffixes = array( 'wiki' );
index 39ce6e3..03b94ae 100644 (file)
@@ -13,14 +13,14 @@ class TemplateCategoriesTest extends MediaWikiLangTestCase {
                $user = new User();
                $user->mRights = array( 'createpage', 'edit', 'purge' );
 
-               $status = $page->doEdit( '{{Categorising template}}', 'Create a page with a template', 0, false, $user );
+               $status = $page->doEditContent( new WikitextContent( '{{Categorising template}}' ), 'Create a page with a template', 0, false, $user );
                $this->assertEquals(
                        array()
                        , $title->getParentCategories()
                );
 
                $template = WikiPage::factory( Title::newFromText( 'Template:Categorising template' ) );
-               $status = $template->doEdit( '[[Category:Solved bugs]]', 'Add a category through a template', 0, false, $user );
+               $status = $template->doEditContent( new WikitextContent( '[[Category:Solved bugs]]' ), 'Add a category through a template', 0, false, $user );
 
                // Run the job queue
                $jobs = new RunJobs;
index cd027c5..db4162f 100644 (file)
@@ -1,28 +1,21 @@
 <?php
 
 class TimeAdjustTest extends MediaWikiLangTestCase {
-       static $offset;
-
-       public function setUp() {
+       protected function setUp() {
                parent::setUp();
-               global $wgLocalTZoffset;
-               self::$offset = $wgLocalTZoffset;
 
-               $this->iniSet( 'precision', 15 );
-       }
+               $this->setMwGlobals( array(
+                       'wgLocalTZoffset' => null,
+                       'wgContLang' => Language::factory( 'en' ),
+               ) );
 
-       public function tearDown() {
-               global $wgLocalTZoffset;
-               $wgLocalTZoffset = self::$offset;
-               parent::tearDown();
+               $this->iniSet( 'precision', 15 );
        }
 
        # Test offset usage for a given language::userAdjust
        function testUserAdjust() {
                global $wgLocalTZoffset, $wgContLang;
 
-               $wgContLang = $en = Language::factory( 'en' );
-
                #  Collection of parameters for Language_t_Offset.
                # Format: date to be formatted, localTZoffset value, expected date
                $userAdjust_tests = array(
@@ -43,7 +36,7 @@ class TimeAdjustTest extends MediaWikiLangTestCase {
 
                        $this->assertEquals(
                                strval( $data[2] ),
-                               strval( $en->userAdjust( $data[0], '' ) ),
+                               strval( $wgContLang->userAdjust( $data[0], '' ) ),
                                "User adjust {$data[0]} by {$data[1]} minutes should give {$data[2]}"
                        );
                }
index 231228f..6352160 100644 (file)
@@ -4,6 +4,16 @@
  * Tests timestamp parsing and output.
  */
 class TimestampTest extends MediaWikiTestCase {
+
+       protected function setUp() {
+               parent::setUp();
+
+               $this->setMwGlobals( array(
+                       'wgLanguageCode' => 'en',
+                       'wgContLang' => Language::factory( 'en' ),
+                       'wgLang' => Language::factory( 'en' ),
+               ) );
+       }
        /**
         * Test parsing of valid timestamps and outputing to MW format.
         * @dataProvider provideValidTimestamps
@@ -44,14 +54,14 @@ class TimestampTest extends MediaWikiTestCase {
         */
        function testHumanOutput() {
                $timestamp = new MWTimestamp( time() - 3600 );
-               $this->assertEquals( "1 hour ago", $timestamp->getHumanTimestamp()->toString() );
+               $this->assertEquals( "1 hour ago", $timestamp->getHumanTimestamp()->inLanguage( 'en' )->text() );
        }
 
        /**
         * Returns a list of valid timestamps in the format:
         * array( type, timestamp_of_type, timestamp_in_MW )
         */
-       function provideValidTimestamps() {
+       public static function provideValidTimestamps() {
                return array(
                        // Various formats
                        array( TS_UNIX, '1343761268', '20120731190108' ),
index aed658b..44fd690 100644 (file)
@@ -1,8 +1,44 @@
 <?php
 
+/**
+ * @group ContentHandler
+ *
+ * @note: We don't make assumptions about the main namespace.
+ *        But we do expect the Help namespace to contain Wikitext.
+ *
+ */
 class TitleMethodsTest extends MediaWikiTestCase {
 
-       public function dataEquals() {
+       public function setup() {
+               global $wgExtraNamespaces, $wgNamespaceContentModels, $wgContLang;
+
+               $this->mergeMwGlobalArrayValue(
+                       'wgExtraNamespaces',
+                       array(
+                               12302 => 'TEST-JS',
+                               12303 => 'TEST-JS_TALK',
+                       )
+               );
+
+               $this->mergeMwGlobalArrayValue(
+                       'wgNamespaceContentModels',
+                       array(
+                               12302 => CONTENT_MODEL_JAVASCRIPT,
+                       )
+               );
+
+               MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache
+               $wgContLang->resetNamespaces(); # reset namespace cache
+       }
+
+       public function teardown() {
+               global $wgContLang;
+
+               MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache
+               $wgContLang->resetNamespaces(); # reset namespace cache
+       }
+
+       public static function provideEquals() {
                return array(
                        array( 'Main Page', 'Main Page', true ),
                        array( 'Main Page', 'Not The Main Page', false ),
@@ -15,7 +51,7 @@ class TitleMethodsTest extends MediaWikiTestCase {
        }
 
        /**
-        * @dataProvider dataEquals
+        * @dataProvider provideEquals
         */
        public function testEquals( $titleA, $titleB, $expectedBool ) {
                $titleA = Title::newFromText( $titleA );
@@ -25,7 +61,7 @@ class TitleMethodsTest extends MediaWikiTestCase {
                $this->assertEquals( $expectedBool, $titleB->equals( $titleA ) );
        }
 
-       public function dataInNamespace() {
+       public static function provideInNamespace() {
                return array(
                        array( 'Main Page', NS_MAIN, true ),
                        array( 'Main Page', NS_TALK, false ),
@@ -39,7 +75,7 @@ class TitleMethodsTest extends MediaWikiTestCase {
        }
 
        /**
-        * @dataProvider dataInNamespace
+        * @dataProvider provideInNamespace
         */
        public function testInNamespace( $title, $ns, $expectedBool ) {
                $title = Title::newFromText( $title );
@@ -54,7 +90,7 @@ class TitleMethodsTest extends MediaWikiTestCase {
                $this->assertFalse( $mainpage->inNamespaces( array( NS_PROJECT, NS_TEMPLATE ) ) );
        }
 
-       public function dataHasSubjectNamespace() {
+       public static function provideHasSubjectNamespace() {
                return array(
                        array( 'Main Page', NS_MAIN, true ),
                        array( 'Main Page', NS_TALK, true ),
@@ -68,18 +104,59 @@ class TitleMethodsTest extends MediaWikiTestCase {
        }
 
        /**
-        * @dataProvider dataHasSubjectNamespace
+        * @dataProvider provideHasSubjectNamespace
         */
        public function testHasSubjectNamespace( $title, $ns, $expectedBool ) {
                $title = Title::newFromText( $title );
                $this->assertEquals( $expectedBool, $title->hasSubjectNamespace( $ns ) );
        }
 
-       public function dataIsCssOrJsPage() {
+       public function dataGetContentModel() {
+               return array(
+                       array( 'Help:Foo', CONTENT_MODEL_WIKITEXT ),
+                       array( 'Help:Foo.js', CONTENT_MODEL_WIKITEXT ),
+                       array( 'Help:Foo/bar.js', CONTENT_MODEL_WIKITEXT ),
+                       array( 'User:Foo', CONTENT_MODEL_WIKITEXT ),
+                       array( 'User:Foo.js', CONTENT_MODEL_WIKITEXT ),
+                       array( 'User:Foo/bar.js', CONTENT_MODEL_JAVASCRIPT ),
+                       array( 'User:Foo/bar.css', CONTENT_MODEL_CSS ),
+                       array( 'User talk:Foo/bar.css', CONTENT_MODEL_WIKITEXT ),
+                       array( 'User:Foo/bar.js.xxx', CONTENT_MODEL_WIKITEXT ),
+                       array( 'User:Foo/bar.xxx', CONTENT_MODEL_WIKITEXT ),
+                       array( 'MediaWiki:Foo.js', CONTENT_MODEL_JAVASCRIPT ),
+                       array( 'MediaWiki:Foo.css', CONTENT_MODEL_CSS ),
+                       array( 'MediaWiki:Foo/bar.css', CONTENT_MODEL_CSS ),
+                       array( 'MediaWiki:Foo.JS', CONTENT_MODEL_WIKITEXT ),
+                       array( 'MediaWiki:Foo.CSS', CONTENT_MODEL_WIKITEXT ),
+                       array( 'MediaWiki:Foo.css.xxx', CONTENT_MODEL_WIKITEXT ),
+                       array( 'TEST-JS:Foo', CONTENT_MODEL_JAVASCRIPT ),
+                       array( 'TEST-JS:Foo.js', CONTENT_MODEL_JAVASCRIPT ),
+                       array( 'TEST-JS:Foo/bar.js', CONTENT_MODEL_JAVASCRIPT ),
+                       array( 'TEST-JS_TALK:Foo.js', CONTENT_MODEL_WIKITEXT ),
+               );
+       }
+
+       /**
+        * @dataProvider dataGetContentModel
+        */
+       public function testGetContentModel( $title, $expectedModelId ) {
+               $title = Title::newFromText( $title );
+               $this->assertEquals( $expectedModelId, $title->getContentModel() );
+       }
+
+       /**
+        * @dataProvider dataGetContentModel
+        */
+       public function testHasContentModel( $title, $expectedModelId ) {
+               $title = Title::newFromText( $title );
+               $this->assertTrue( $title->hasContentModel( $expectedModelId ) );
+       }
+
+       public static function provideIsCssOrJsPage() {
                return array(
-                       array( 'Foo', false ),
-                       array( 'Foo.js', false ),
-                       array( 'Foo/bar.js', false ),
+                       array( 'Help:Foo', false ),
+                       array( 'Help:Foo.js', false ),
+                       array( 'Help:Foo/bar.js', false ),
                        array( 'User:Foo', false ),
                        array( 'User:Foo.js', false ),
                        array( 'User:Foo/bar.js', false ),
@@ -92,11 +169,13 @@ class TitleMethodsTest extends MediaWikiTestCase {
                        array( 'MediaWiki:Foo.JS', false ),
                        array( 'MediaWiki:Foo.CSS', false ),
                        array( 'MediaWiki:Foo.css.xxx', false ),
+                       array( 'TEST-JS:Foo', false ),
+                       array( 'TEST-JS:Foo.js', false ),
                );
        }
 
        /**
-        * @dataProvider dataIsCssOrJsPage
+        * @dataProvider provideIsCssOrJsPage
         */
        public function testIsCssOrJsPage( $title, $expectedBool ) {
                $title = Title::newFromText( $title );
@@ -104,11 +183,11 @@ class TitleMethodsTest extends MediaWikiTestCase {
        }
 
 
-       public function dataIsCssJsSubpage() {
+       public static function provideIsCssJsSubpage() {
                return array(
-                       array( 'Foo', false ),
-                       array( 'Foo.js', false ),
-                       array( 'Foo/bar.js', false ),
+                       array( 'Help:Foo', false ),
+                       array( 'Help:Foo.js', false ),
+                       array( 'Help:Foo/bar.js', false ),
                        array( 'User:Foo', false ),
                        array( 'User:Foo.js', false ),
                        array( 'User:Foo/bar.js', true ),
@@ -119,21 +198,23 @@ class TitleMethodsTest extends MediaWikiTestCase {
                        array( 'MediaWiki:Foo.js', false ),
                        array( 'User:Foo/bar.JS', false ),
                        array( 'User:Foo/bar.CSS', false ),
+                       array( 'TEST-JS:Foo', false ),
+                       array( 'TEST-JS:Foo.js', false ),
                );
        }
 
        /**
-        * @dataProvider dataIsCssJsSubpage
+        * @dataProvider provideIsCssJsSubpage
         */
        public function testIsCssJsSubpage( $title, $expectedBool ) {
                $title = Title::newFromText( $title );
                $this->assertEquals( $expectedBool, $title->isCssJsSubpage() );
        }
 
-       public function dataIsCssSubpage() {
+       public static function provideIsCssSubpage() {
                return array(
-                       array( 'Foo', false ),
-                       array( 'Foo.css', false ),
+                       array( 'Help:Foo', false ),
+                       array( 'Help:Foo.css', false ),
                        array( 'User:Foo', false ),
                        array( 'User:Foo.js', false ),
                        array( 'User:Foo.css', false ),
@@ -143,17 +224,17 @@ class TitleMethodsTest extends MediaWikiTestCase {
        }
 
        /**
-        * @dataProvider dataIsCssSubpage
+        * @dataProvider provideIsCssSubpage
         */
        public function testIsCssSubpage( $title, $expectedBool ) {
                $title = Title::newFromText( $title );
                $this->assertEquals( $expectedBool, $title->isCssSubpage() );
        }
 
-       public function dataIsJsSubpage() {
+       public static function provideIsJsSubpage() {
                return array(
-                       array( 'Foo', false ),
-                       array( 'Foo.css', false ),
+                       array( 'Help:Foo', false ),
+                       array( 'Help:Foo.css', false ),
                        array( 'User:Foo', false ),
                        array( 'User:Foo.js', false ),
                        array( 'User:Foo.css', false ),
@@ -163,18 +244,18 @@ class TitleMethodsTest extends MediaWikiTestCase {
        }
 
        /**
-        * @dataProvider dataIsJsSubpage
+        * @dataProvider provideIsJsSubpage
         */
        public function testIsJsSubpage( $title, $expectedBool ) {
                $title = Title::newFromText( $title );
                $this->assertEquals( $expectedBool, $title->isJsSubpage() );
        }
 
-       public function dataIsWikitextPage() {
+       public static function provideIsWikitextPage() {
                return array(
-                       array( 'Foo', true ),
-                       array( 'Foo.js', true ),
-                       array( 'Foo/bar.js', true ),
+                       array( 'Help:Foo', true ),
+                       array( 'Help:Foo.js', true ),
+                       array( 'Help:Foo/bar.js', true ),
                        array( 'User:Foo', true ),
                        array( 'User:Foo.js', true ),
                        array( 'User:Foo/bar.js', false ),
@@ -187,11 +268,14 @@ class TitleMethodsTest extends MediaWikiTestCase {
                        array( 'MediaWiki:Foo/bar.css', false ),
                        array( 'User:Foo/bar.JS', true ),
                        array( 'User:Foo/bar.CSS', true ),
+                       array( 'TEST-JS:Foo', false ),
+                       array( 'TEST-JS:Foo.js', false ),
+                       array( 'TEST-JS_TALK:Foo.js', true ),
                );
        }
 
        /**
-        * @dataProvider dataIsWikitextPage
+        * @dataProvider provideIsWikitextPage
         */
        public function testIsWikitextPage( $title, $expectedBool ) {
                $title = Title::newFromText( $title );
index f62ac5d..5df8fe1 100644 (file)
@@ -4,31 +4,44 @@
  * @group Database
  */
 class TitlePermissionTest extends MediaWikiLangTestCase {
-       protected $title;
 
        /**
-        * @var User
+        * @var string
         */
-       protected $user, $anonUser, $userUser, $altUser;
+       protected $userName, $altUserName;
 
        /**
-        * @var string
+        * @var Title
         */
-       protected $userName, $altUserName;
+       protected $title;
 
-       function setUp() {
-               global $wgLocaltimezone, $wgLocalTZoffset, $wgMemc, $wgContLang, $wgLang;
-               parent::setUp();
+       /**
+        * @var User
+        */
+       protected $user, $anonUser, $userUser, $altUser;
 
-               if(!$wgMemc) {
-                       $wgMemc = new EmptyBagOStuff;
-               }
-               $wgContLang = $wgLang = Language::factory( 'en' );
+       protected function setUp() {
+               parent::setUp();
 
-               $this->userName = "Useruser";
-               $this->altUserName = "Altuseruser";
-               date_default_timezone_set( $wgLocaltimezone );
-               $wgLocalTZoffset = date( "Z" ) / 60;
+               $langObj = Language::factory( 'en' );
+               $localZone = 'UTC';
+               $localOffset = date( 'Z' ) / 60;
+
+               $this->setMwGlobals( array(
+                       'wgMemc' => new EmptyBagOStuff,
+                       'wgContLang' => $langObj,
+                       'wgLang' => $langObj,
+                       'wgLocaltimezone' => $localZone,
+                       'wgLocalTZoffset' => $localOffset,
+                       'wgNamespaceProtection' => array(
+                               NS_MEDIAWIKI => 'editinterface',
+                       ),
+                       'wgUser' => null,
+               ) );
+
+               $this->userName = 'Useruser';
+               $this->altUserName = 'Altuseruser';
+               date_default_timezone_set( $localZone );
 
                $this->title = Title::makeTitle( NS_MAIN, "Main Page" );
                if ( !isset( $this->userUser ) || !( $this->userUser instanceOf User ) ) {
@@ -53,10 +66,7 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
 
                        $this->user = $this->userUser;
                }
-       }
 
-       function tearDown() {
-               parent::tearDown();
        }
 
        function setUserPerm( $perm ) {
@@ -74,6 +84,8 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
        }
 
        function setUser( $userName = null ) {
+               global $wgUser;
+
                if ( $userName === 'anon' ) {
                        $this->user = $this->anonUser;
                } elseif ( $userName === null || $userName === $this->userName ) {
@@ -82,7 +94,6 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
                        $this->user = $this->altUser;
                }
 
-               global $wgUser;
                $wgUser = $this->user;
        }
 
@@ -338,9 +349,8 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
        }
 
        function testSpecialsAndNSPermissions() {
+               global $wgNamespaceProtection;
                $this->setUser( $this->userName );
-               global $wgUser;
-               $wgUser = $this->user;
 
                $this->setTitle( NS_SPECIAL );
 
@@ -359,8 +369,8 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
                $this->assertEquals( array( array( 'badaccess-group0' ) ),
                                                         $this->title->getUserPermissionsErrors( 'bogus', $this->user ) );
 
-               global $wgNamespaceProtection;
-               $wgNamespaceProtection[NS_USER] = array ( 'bogus' );
+               $wgNamespaceProtection[NS_USER] = array( 'bogus' );
+
                $this->setTitle( NS_USER );
                $this->setUserPerm( '' );
                $this->assertEquals( array( array( 'badaccess-group0' ), array( 'namespaceprotected', 'User' ) ),
@@ -377,6 +387,7 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
                                                         $this->title->getUserPermissionsErrors( 'bogus', $this->user ) );
 
                $wgNamespaceProtection = null;
+
                $this->setUserPerm( 'bogus' );
                $this->assertEquals( array( ),
                                                         $this->title->getUserPermissionsErrors( 'bogus', $this->user ) );
@@ -392,8 +403,6 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
 
        function testCssAndJavascriptPermissions() {
                $this->setUser( $this->userName );
-               global $wgUser;
-               $wgUser = $this->user;
 
                $this->setTitle( NS_USER, $this->altUserName . '/test.js' );
                $this->runCSSandJSPermissions(
index c74daae..0a3f6f6 100644 (file)
@@ -1,7 +1,25 @@
 <?php
 
+/**
+ *
+ * @group Database
+ *        ^--- needed for language cache stuff
+ */
 class TitleTest extends MediaWikiTestCase {
 
+       protected function setUp() {
+               parent::setUp();
+
+               $this->setMwGlobals( array(
+                       'wgLanguageCode' => 'en',
+                       'wgContLang' => Language::factory( 'en' ),
+                       // User language
+                       'wgLang' => Language::factory( 'en' ),
+                       'wgAllowUserJs' => false,
+                       'wgDefaultLanguageVariant' => false,
+               ) );
+       }
+
        function testLegalChars() {
                $titlechars = Title::legalChars();
 
@@ -16,7 +34,7 @@ class TitleTest extends MediaWikiTestCase {
        }
 
        /**
-        * @dataProvider dataBug31100
+        * @dataProvider provideBug31100
         */
        function testBug31100FixSpecialName( $text, $expectedParam ) {
                $title = Title::newFromText( $text );
@@ -30,7 +48,7 @@ class TitleTest extends MediaWikiTestCase {
                $this->assertEquals( $expectedParam, $par, "Bug 31100 regression check: Title->fixSpecialName() should preserve parameter" );
        }
 
-       function dataBug31100() {
+       public static function provideBug31100() {
                return array(
                        array( 'Special:Version', null ),
                        array( 'Special:Version/', '' ),
@@ -45,7 +63,7 @@ class TitleTest extends MediaWikiTestCase {
         * @param string $source
         * @param string $target
         * @param array|string|true $expected Required error
-        * @dataProvider dataTestIsValidMoveOperation
+        * @dataProvider provideTestIsValidMoveOperation
         */
        function testIsValidMoveOperation( $source, $target, $expected ) {
                $title = Title::newFromText( $source );
@@ -69,7 +87,7 @@ class TitleTest extends MediaWikiTestCase {
                return $result;
        }
        
-       function dataTestIsValidMoveOperation() {
+       public static function provideTestIsValidMoveOperation() {
                return array( 
                        array( 'Test', 'Test', 'selfmove' ),
                        array( 'File:Test.jpg', 'Page', 'imagenocrossnamespace' )
@@ -79,22 +97,15 @@ class TitleTest extends MediaWikiTestCase {
        /**
         * @dataProvider provideCasesForGetpageviewlanguage
         */
-       function testGetpageviewlanguage( $expected, $titleText, $contLang, $lang, $variant, $msg='' ) {
-               // Save globals
-               global $wgContLang, $wgLang, $wgAllowUserJs, $wgLanguageCode, $wgDefaultLanguageVariant;
-               $save['wgContLang']               = $wgContLang;
-               $save['wgLang']                   = $wgLang;
-               $save['wgAllowUserJs']            = $wgAllowUserJs;
-               $save['wgLanguageCode']           = $wgLanguageCode;
-               $save['wgDefaultLanguageVariant'] = $wgDefaultLanguageVariant;
-
-               // Setup test environnement:
-               $wgContLang = Language::factory( $contLang );
-               $wgLang     = Language::factory( $lang );
-               # To test out .js titles:
-               $wgAllowUserJs = true;
+       function testGetpageviewlanguage( $expected, $titleText, $contLang, $lang, $variant, $msg = '' ) {
+               global $wgLanguageCode, $wgContLang, $wgLang, $wgDefaultLanguageVariant, $wgAllowUserJs;
+
+               // Setup environnement for this test
                $wgLanguageCode = $contLang;
+               $wgContLang = Language::factory( $contLang );
+               $wgLang = Language::factory( $lang );
                $wgDefaultLanguageVariant = $variant;
+               $wgAllowUserJs = true;
 
                $title = Title::newFromText( $titleText );
                $this->assertInstanceOf( 'Title', $title,
@@ -104,13 +115,6 @@ class TitleTest extends MediaWikiTestCase {
                        $title->getPageViewLanguage()->getCode(),
                        $msg
                );
-
-               // Restore globals
-               $wgContLang               = $save['wgContLang'];
-               $wgLang                   = $save['wgLang'];
-               $wgAllowUserJs            = $save['wgAllowUserJs'];
-               $wgLanguageCode           = $save['wgLanguageCode'];
-               $wgDefaultLanguageVariant = $save['wgDefaultLanguageVariant'];
        }
 
        function provideCasesForGetpageviewlanguage() {
@@ -182,7 +186,7 @@ class TitleTest extends MediaWikiTestCase {
                );
        }
 
-       function provideRootTitleCases() {
+       public static function provideRootTitleCases() {
                return array(
                        # Title, expected base, optional message
                        array('User:John_Doe/subOne/subTwo', 'John Doe' ),
index 7a424ae..316e6f5 100644 (file)
@@ -7,22 +7,26 @@ define( 'NS_UNITTEST_TALK', 5601 );
  * @group Database
  */
 class UserTest extends MediaWikiTestCase {
-       protected $savedGroupPermissions, $savedRevokedPermissions;
 
        /**
         * @var User
         */
        protected $user;
 
-       public function setUp() {
+       protected function setUp() {
                parent::setUp();
 
-               $this->savedGroupPermissions = $GLOBALS['wgGroupPermissions'];
-               $this->savedRevokedPermissions = $GLOBALS['wgRevokePermissions'];
+               $this->setMwGlobals( array(
+                       'wgGroupPermissions' => array(),
+                       'wgRevokePermissions' => array(),
+               ) );
 
                $this->setUpPermissionGlobals();
-               $this->setUpUser();
+
+               $this->user = new User;
+               $this->user->addGroup( 'unittesters' );
        }
+
        private function setUpPermissionGlobals() {
                global $wgGroupPermissions, $wgRevokePermissions;
 
@@ -38,22 +42,12 @@ class UserTest extends MediaWikiTestCase {
                        'writetest' => true,
                        'modifytest' => true,
                );
+
                # Data for regular $wgRevokePermissions test
                $wgRevokePermissions['formertesters'] = array(
                        'runtest' => true,
                );
        }
-       private function setUpUser() {
-               $this->user = new User;
-               $this->user->addGroup( 'unittesters' );
-       }
-
-       public function tearDown() {
-               parent::tearDown();
-
-               $GLOBALS['wgGroupPermissions'] = $this->savedGroupPermissions;
-               $GLOBALS['wgRevokePermissions'] = $this->savedRevokedPermissions;
-       }
 
        public function testGroupPermissions() {
                $rights = User::getGroupPermissions( array( 'unittesters' ) );
@@ -95,7 +89,7 @@ class UserTest extends MediaWikiTestCase {
                $this->assertEquals( $expected, $result, "Groups with permission $right" );
        }
 
-       public function provideGetGroupsWithPermission() {
+       public static function provideGetGroupsWithPermission() {
                return array(
                        array(
                                array( 'unittesters', 'testwriters' ),
@@ -123,7 +117,7 @@ class UserTest extends MediaWikiTestCase {
                $this->assertEquals( $this->user->isValidUserName( $username ), $result, $message );
        }
 
-       public function provideUserNames() {
+       public static function provideUserNames() {
                return array(
                        array( '', false, 'Empty string' ),
                        array( ' ', false, 'Blank space' ),
index 1fc0b4b..153ff78 100644 (file)
@@ -1,14 +1,18 @@
 <?php
 
 class WebRequestTest extends MediaWikiTestCase {
-       static $oldServer;
+       protected $oldServer;
 
-       function setUp() {
-               self::$oldServer = $_SERVER;
+       protected function setUp() {
+               parent::setUp();
+
+               $this->oldServer = $_SERVER;
        }
 
-       function tearDown() {
-               $_SERVER = self::$oldServer;
+       protected function tearDown() {
+               $_SERVER = $this->oldServer;
+
+               parent::tearDown();
        }
 
        /**
@@ -20,7 +24,7 @@ class WebRequestTest extends MediaWikiTestCase {
                $this->assertEquals( $expected, $result, $description );
        }
 
-       function provideDetectServer() {
+       public static function provideDetectServer() {
                return array(
                        array(
                                'http://x',
@@ -107,7 +111,7 @@ class WebRequestTest extends MediaWikiTestCase {
                $this->assertEquals( $expected, $result, $description );
        }
 
-       function provideGetIP() {
+       public static function provideGetIP() {
                return array(
                        array(
                                '127.0.0.1',
@@ -189,7 +193,7 @@ class WebRequestTest extends MediaWikiTestCase {
                $request->getIP();
        }
 
-       function languageProvider() {
+       public static function provideLanguageData() {
                return array(
                        array( '', array(), 'Empty Accept-Language header' ),
                        array( 'en', array( 'en' => 1 ), 'One language' ),
@@ -206,7 +210,7 @@ class WebRequestTest extends MediaWikiTestCase {
        }
 
        /**
-        * @dataProvider languageProvider
+        * @dataProvider provideLanguageData
         */
        function testAcceptLang($acceptLanguageHeader, $expectedLanguages, $description) {
                $_SERVER = array( 'HTTP_ACCEPT_LANGUAGE' => $acceptLanguageHeader );
index 2949a3a..cf9b96b 100644 (file)
@@ -1,5 +1,6 @@
 <?php
 /**
+* @group ContentHandler
 * @group Database
 * ^--- important, causes temporary tables to be used instead of the real database
 **/
@@ -11,30 +12,33 @@ class WikiPageTest extends MediaWikiLangTestCase {
        function  __construct( $name = null, array $data = array(), $dataName = '' ) {
                parent::__construct( $name, $data, $dataName );
 
-               $this->tablesUsed = array_merge ( $this->tablesUsed,
-                                                 array( 'page',
-                                                      'revision',
-                                                      'text',
+               $this->tablesUsed = array_merge (
+                       $this->tablesUsed,
+                       array( 'page',
+                                       'revision',
+                                       'text',
 
-                                                      'recentchanges',
-                                                      'logging',
+                                       'recentchanges',
+                                       'logging',
 
-                                                      'page_props',
-                                                      'pagelinks',
-                                                      'categorylinks',
-                                                      'langlinks',
-                                                      'externallinks',
-                                                      'imagelinks',
-                                                      'templatelinks',
-                                                      'iwlinks' ) );
+                                       'page_props',
+                                       'pagelinks',
+                                       'categorylinks',
+                                       'langlinks',
+                                       'externallinks',
+                                       'imagelinks',
+                                       'templatelinks',
+                                       'iwlinks' ) );
        }
 
-       public function setUp() {
+       protected function setUp() {
                parent::setUp();
                $this->pages_to_delete = array();
+
+               LinkCache::singleton()->clear(); # avoid cached redirect status, etc
        }
 
-       public function tearDown() {
+       protected function tearDown() {
                foreach ( $this->pages_to_delete as $p ) {
                        /* @var $p WikiPage */
 
@@ -49,8 +53,15 @@ class WikiPageTest extends MediaWikiLangTestCase {
                parent::tearDown();
        }
 
-       protected function newPage( $title ) {
-               if ( is_string( $title ) ) $title = Title::newFromText( $title );
+       /**
+        * @param Title $title
+        * @param String $model
+        * @return WikiPage
+        */
+       protected function newPage( $title, $model = null ) {
+               if ( is_string( $title ) ) {
+                       $title = Title::newFromText( $title );
+               }
 
                $p = new WikiPage( $title );
 
@@ -59,30 +70,113 @@ class WikiPageTest extends MediaWikiLangTestCase {
                return $p;
        }
 
+
+       /**
+        * @param String|Title|WikiPage $page
+        * @param String $text
+        * @param int $model
+        *
+        * @return WikiPage
+        */
        protected function createPage( $page, $text, $model = null ) {
-               if ( is_string( $page ) ) $page = Title::newFromText( $page );
-               if ( $page instanceof Title ) $page = $this->newPage( $page );
+               if ( is_string( $page ) ) {
+                       $page = Title::newFromText( $page );
+               }
+
+               if ( $page instanceof Title ) {
+                       $page = $this->newPage( $page, $model );
+               }
 
-               $page->doEdit( $text, "testing", EDIT_NEW );
+               $content = ContentHandler::makeContent( $text, $page->getTitle(), $model );
+               $page->doEditContent( $content, "testing", EDIT_NEW );
 
                return $page;
        }
 
+       public function testDoEditContent() {
+               $title = Title::newFromText( "WikiPageTest_testDoEditContent" );
+
+               $page = $this->newPage( $title );
+
+               $content = ContentHandler::makeContent( "[[Lorem ipsum]] dolor sit amet, consetetur sadipscing elitr, sed diam "
+                                               . " nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat.",
+                                               $title, CONTENT_MODEL_WIKITEXT );
+
+               $page->doEditContent( $content, "[[testing]] 1" );
+
+               $this->assertTrue( $title->getArticleID() > 0, "Title object should have new page id" );
+               $this->assertTrue( $page->getId() > 0, "WikiPage should have new page id" );
+               $this->assertTrue( $title->exists(), "Title object should indicate that the page now exists" );
+               $this->assertTrue( $page->exists(), "WikiPage object should indicate that the page now exists" );
+
+               $id = $page->getId();
+
+               # ------------------------
+               $dbr = wfGetDB( DB_SLAVE );
+               $res = $dbr->select( 'pagelinks', '*', array( 'pl_from' => $id ) );
+               $n = $res->numRows();
+               $res->free();
+
+               $this->assertEquals( 1, $n, 'pagelinks should contain one link from the page' );
+
+               # ------------------------
+               $page = new WikiPage( $title );
+
+               $retrieved = $page->getContent();
+               $this->assertTrue( $content->equals( $retrieved ), 'retrieved content doesn\'t equal original' );
+
+               # ------------------------
+               $content = ContentHandler::makeContent( "At vero eos et accusam et justo duo [[dolores]] et ea rebum. "
+                                                                                               . "Stet clita kasd [[gubergren]], no sea takimata sanctus est.",
+                                                                                               $title, CONTENT_MODEL_WIKITEXT );
+
+               $page->doEditContent( $content, "testing 2" );
+
+               # ------------------------
+               $page = new WikiPage( $title );
+
+               $retrieved = $page->getContent();
+               $this->assertTrue( $content->equals( $retrieved ), 'retrieved content doesn\'t equal original' );
+
+               # ------------------------
+               $dbr = wfGetDB( DB_SLAVE );
+               $res = $dbr->select( 'pagelinks', '*', array( 'pl_from' => $id ) );
+               $n = $res->numRows();
+               $res->free();
+
+               $this->assertEquals( 2, $n, 'pagelinks should contain two links from the page' );
+       }
+
        public function testDoEdit() {
-               $title = Title::newFromText( "WikiPageTest_testDoEdit" );
+               $this->hideDeprecated( "WikiPage::doEdit" );
+               $this->hideDeprecated( "WikiPage::getText" );
+               $this->hideDeprecated( "Revision::getText" );
+
+               //NOTE: assume help namespace will default to wikitext
+               $title = Title::newFromText( "Help:WikiPageTest_testDoEdit" );
 
                $page = $this->newPage( $title );
 
                $text = "[[Lorem ipsum]] dolor sit amet, consetetur sadipscing elitr, sed diam "
-                      . " nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat.";
+                               . " nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat.";
 
-               $page->doEdit( $text, "testing 1" );
+               $page->doEdit( $text, "[[testing]] 1" );
 
+               $this->assertTrue( $title->getArticleID() > 0, "Title object should have new page id" );
+               $this->assertTrue( $page->getId() > 0, "WikiPage should have new page id" );
                $this->assertTrue( $title->exists(), "Title object should indicate that the page now exists" );
                $this->assertTrue( $page->exists(), "WikiPage object should indicate that the page now exists" );
 
                $id = $page->getId();
 
+               # ------------------------
+               $dbr = wfGetDB( DB_SLAVE );
+               $res = $dbr->select( 'pagelinks', '*', array( 'pl_from' => $id ) );
+               $n = $res->numRows();
+               $res->free();
+
+               $this->assertEquals( 1, $n, 'pagelinks should contain one link from the page' );
+
                # ------------------------
                $page = new WikiPage( $title );
 
@@ -91,7 +185,7 @@ class WikiPageTest extends MediaWikiLangTestCase {
 
                # ------------------------
                $text = "At vero eos et accusam et justo duo [[dolores]] et ea rebum. "
-                      . "Stet clita kasd [[gubergren]], no sea takimata sanctus est.";
+                               . "Stet clita kasd [[gubergren]], no sea takimata sanctus est.";
 
                $page->doEdit( $text, "testing 2" );
 
@@ -113,7 +207,10 @@ class WikiPageTest extends MediaWikiLangTestCase {
        public function testDoQuickEdit() {
                global $wgUser;
 
-               $page = $this->createPage( "WikiPageTest_testDoQuickEdit", "original text" );
+               $this->hideDeprecated( "WikiPage::doQuickEdit" );
+
+               //NOTE: assume help namespace will default to wikitext
+               $page = $this->createPage( "Help:WikiPageTest_testDoQuickEdit", "original text" );
 
                $text = "quick text";
                $page->doQuickEdit( $text, $wgUser, "testing q" );
@@ -123,13 +220,29 @@ class WikiPageTest extends MediaWikiLangTestCase {
                $this->assertEquals( $text, $page->getText() );
        }
 
+       public function testDoQuickEditContent() {
+               global $wgUser;
+
+               $page = $this->createPage( "WikiPageTest_testDoQuickEditContent", "original text", CONTENT_MODEL_WIKITEXT );
+
+               $content = ContentHandler::makeContent( "quick text", $page->getTitle(), CONTENT_MODEL_WIKITEXT );
+               $page->doQuickEditContent( $content, $wgUser, "testing q" );
+
+               # ---------------------
+               $page = new WikiPage( $page->getTitle() );
+               $this->assertTrue( $content->equals( $page->getContent() ) );
+       }
+
        public function testDoDeleteArticle() {
-               $page = $this->createPage( "WikiPageTest_testDoDeleteArticle", "[[original text]] foo" );
+               $page = $this->createPage( "WikiPageTest_testDoDeleteArticle", "[[original text]] foo", CONTENT_MODEL_WIKITEXT );
                $id = $page->getId();
 
                $page->doDeleteArticle( "testing deletion" );
 
+               $this->assertFalse( $page->getTitle()->getArticleID() > 0, "Title object should now have page id 0" );
+               $this->assertFalse( $page->getId() > 0, "WikiPage should now have page id 0" );
                $this->assertFalse( $page->exists(), "WikiPage::exists should return false after page was deleted" );
+               $this->assertNull( $page->getContent(), "WikiPage::getContent should return null after page was deleted" );
                $this->assertFalse( $page->getText(), "WikiPage::getText should return false after page was deleted" );
 
                $t = Title::newFromText( $page->getTitle()->getPrefixedText() );
@@ -145,7 +258,7 @@ class WikiPageTest extends MediaWikiLangTestCase {
        }
 
        public function testDoDeleteUpdates() {
-               $page = $this->createPage( "WikiPageTest_testDoDeleteArticle", "[[original text]] foo" );
+               $page = $this->createPage( "WikiPageTest_testDoDeleteArticle", "[[original text]] foo", CONTENT_MODEL_WIKITEXT );
                $id = $page->getId();
 
                $page->doDeleteUpdates( $id );
@@ -166,47 +279,89 @@ class WikiPageTest extends MediaWikiLangTestCase {
                $this->assertNull( $rev );
 
                # -----------------
-               $this->createPage( $page, "some text" );
+               $this->createPage( $page, "some text", CONTENT_MODEL_WIKITEXT );
 
                $rev = $page->getRevision();
 
                $this->assertEquals( $page->getLatest(), $rev->getId() );
-               $this->assertEquals( "some text", $rev->getText() );
+               $this->assertEquals( "some text", $rev->getContent()->getNativeData() );
+       }
+
+       public function testGetContent() {
+               $page = $this->newPage( "WikiPageTest_testGetContent" );
+
+               $content = $page->getContent();
+               $this->assertNull( $content );
+
+               # -----------------
+               $this->createPage( $page, "some text", CONTENT_MODEL_WIKITEXT );
+
+               $content = $page->getContent();
+               $this->assertEquals( "some text", $content->getNativeData() );
        }
 
        public function testGetText() {
+               $this->hideDeprecated( "WikiPage::getText" );
+
                $page = $this->newPage( "WikiPageTest_testGetText" );
 
                $text = $page->getText();
                $this->assertFalse( $text );
 
                # -----------------
-               $this->createPage( $page, "some text" );
+               $this->createPage( $page, "some text", CONTENT_MODEL_WIKITEXT );
 
                $text = $page->getText();
                $this->assertEquals( "some text", $text );
        }
 
        public function testGetRawText() {
+               $this->hideDeprecated( "WikiPage::getRawText" );
+
                $page = $this->newPage( "WikiPageTest_testGetRawText" );
 
                $text = $page->getRawText();
                $this->assertFalse( $text );
 
                # -----------------
-               $this->createPage( $page, "some text" );
+               $this->createPage( $page, "some text", CONTENT_MODEL_WIKITEXT );
 
                $text = $page->getRawText();
                $this->assertEquals( "some text", $text );
        }
 
-       
+       public function testGetContentModel() {
+               global $wgContentHandlerUseDB;
+
+               if ( !$wgContentHandlerUseDB ) {
+                       $this->markTestSkipped( '$wgContentHandlerUseDB is disabled' );
+               }
+
+               $page = $this->createPage( "WikiPageTest_testGetContentModel", "some text", CONTENT_MODEL_JAVASCRIPT );
+
+               $page = new WikiPage( $page->getTitle() );
+               $this->assertEquals( CONTENT_MODEL_JAVASCRIPT, $page->getContentModel() );
+       }
+
+       public function testGetContentHandler() {
+               global $wgContentHandlerUseDB;
+
+               if ( !$wgContentHandlerUseDB ) {
+                       $this->markTestSkipped( '$wgContentHandlerUseDB is disabled' );
+               }
+
+               $page = $this->createPage( "WikiPageTest_testGetContentHandler", "some text", CONTENT_MODEL_JAVASCRIPT );
+
+               $page = new WikiPage( $page->getTitle() );
+               $this->assertEquals( 'JavaScriptContentHandler', get_class( $page->getContentHandler() ) );
+       }
+
        public function testExists() {
                $page = $this->newPage( "WikiPageTest_testExists" );
                $this->assertFalse( $page->exists() );
 
                # -----------------
-               $this->createPage( $page, "some text" );
+               $this->createPage( $page, "some text", CONTENT_MODEL_WIKITEXT );
                $this->assertTrue( $page->exists() );
 
                $page = new WikiPage( $page->getTitle() );
@@ -220,7 +375,7 @@ class WikiPageTest extends MediaWikiLangTestCase {
                $this->assertFalse( $page->exists() );
        }
 
-       public function dataHasViewableContent() {
+       public static function provideHasViewableContent() {
                return array(
                        array( 'WikiPageTest_testHasViewableContent', false, true ),
                        array( 'Special:WikiPageTest_testHasViewableContent', false ),
@@ -231,14 +386,14 @@ class WikiPageTest extends MediaWikiLangTestCase {
        }
 
        /**
-        * @dataProvider dataHasViewableContent
+        * @dataProvider provideHasViewableContent
         */
        public function testHasViewableContent( $title, $viewable, $create = false ) {
                $page = $this->newPage( $title );
                $this->assertEquals( $viewable, $page->hasViewableContent() );
 
                if ( $create ) {
-                       $this->createPage( $page, "some text" );
+                       $this->createPage( $page, "some text", CONTENT_MODEL_WIKITEXT );
                        $this->assertTrue( $page->hasViewableContent() );
 
                        $page = new WikiPage( $page->getTitle() );
@@ -246,18 +401,22 @@ class WikiPageTest extends MediaWikiLangTestCase {
                }
        }
 
-       public function dataGetRedirectTarget() {
+       public static function provideGetRedirectTarget() {
                return array(
-                       array( 'WikiPageTest_testGetRedirectTarget_1', "hello world", null ),
-                       array( 'WikiPageTest_testGetRedirectTarget_2', "#REDIRECT [[hello world]]", "Hello world" ),
+                       array( 'WikiPageTest_testGetRedirectTarget_1', CONTENT_MODEL_WIKITEXT, "hello world", null ),
+                       array( 'WikiPageTest_testGetRedirectTarget_2', CONTENT_MODEL_WIKITEXT, "#REDIRECT [[hello world]]", "Hello world" ),
                );
        }
 
        /**
-        * @dataProvider dataGetRedirectTarget
+        * @dataProvider provideGetRedirectTarget
         */
-       public function testGetRedirectTarget( $title, $text, $target ) {
-               $page = $this->createPage( $title, $text );
+       public function testGetRedirectTarget( $title, $model, $text, $target ) {
+               $page = $this->createPage( $title, $text, $model );
+
+               # sanity check, because this test seems to fail for no reason for some people.
+               $c = $page->getContent();
+               $this->assertEquals( 'WikitextContent', get_class( $c ) );
 
                # now, test the actual redirect
                $t = $page->getRedirectTarget();
@@ -265,141 +424,160 @@ class WikiPageTest extends MediaWikiLangTestCase {
        }
 
        /**
-        * @dataProvider dataGetRedirectTarget
+        * @dataProvider provideGetRedirectTarget
         */
-       public function testIsRedirect( $title, $text, $target ) {
-               $page = $this->createPage( $title, $text );
+       public function testIsRedirect( $title, $model, $text, $target ) {
+               $page = $this->createPage( $title, $text, $model );
                $this->assertEquals( !is_null( $target ), $page->isRedirect() );
        }
 
-       public function dataIsCountable() {
+       public static function provideIsCountable() {
                return array(
 
                        // any
                        array( 'WikiPageTest_testIsCountable',
-                              '',
-                              'any',
-                              true
+                                       CONTENT_MODEL_WIKITEXT,
+                                       '',
+                                       'any',
+                                       true
                        ),
                        array( 'WikiPageTest_testIsCountable',
-                              'Foo',
-                              'any',
-                              true
+                                       CONTENT_MODEL_WIKITEXT,
+                                       'Foo',
+                                       'any',
+                                       true
                        ),
 
                        // comma
                        array( 'WikiPageTest_testIsCountable',
-                              'Foo',
-                              'comma',
-                              false
+                                       CONTENT_MODEL_WIKITEXT,
+                                       'Foo',
+                                       'comma',
+                                       false
                        ),
                        array( 'WikiPageTest_testIsCountable',
-                              'Foo, bar',
-                              'comma',
-                              true
+                                       CONTENT_MODEL_WIKITEXT,
+                                       'Foo, bar',
+                                       'comma',
+                                       true
                        ),
 
                        // link
                        array( 'WikiPageTest_testIsCountable',
-                              'Foo',
-                              'link',
-                              false
+                                       CONTENT_MODEL_WIKITEXT,
+                                       'Foo',
+                                       'link',
+                                       false
                        ),
                        array( 'WikiPageTest_testIsCountable',
-                              'Foo [[bar]]',
-                              'link',
-                              true
+                                       CONTENT_MODEL_WIKITEXT,
+                                       'Foo [[bar]]',
+                                       'link',
+                                       true
                        ),
 
                        // redirects
                        array( 'WikiPageTest_testIsCountable',
-                              '#REDIRECT [[bar]]',
-                              'any',
-                              false
+                                       CONTENT_MODEL_WIKITEXT,
+                                       '#REDIRECT [[bar]]',
+                                       'any',
+                                       false
                        ),
                        array( 'WikiPageTest_testIsCountable',
-                              '#REDIRECT [[bar]]',
-                              'comma',
-                              false
+                                       CONTENT_MODEL_WIKITEXT,
+                                       '#REDIRECT [[bar]]',
+                                       'comma',
+                                       false
                        ),
                        array( 'WikiPageTest_testIsCountable',
-                              '#REDIRECT [[bar]]',
-                              'link',
-                              false
+                                       CONTENT_MODEL_WIKITEXT,
+                                       '#REDIRECT [[bar]]',
+                                       'link',
+                                       false
                        ),
 
                        // not a content namespace
                        array( 'Talk:WikiPageTest_testIsCountable',
-                              'Foo',
-                              'any',
-                              false
+                                       CONTENT_MODEL_WIKITEXT,
+                                       'Foo',
+                                       'any',
+                                       false
                        ),
                        array( 'Talk:WikiPageTest_testIsCountable',
-                              'Foo, bar',
-                              'comma',
-                              false
+                                       CONTENT_MODEL_WIKITEXT,
+                                       'Foo, bar',
+                                       'comma',
+                                       false
                        ),
                        array( 'Talk:WikiPageTest_testIsCountable',
-                              'Foo [[bar]]',
-                              'link',
-                              false
+                                       CONTENT_MODEL_WIKITEXT,
+                                       'Foo [[bar]]',
+                                       'link',
+                                       false
                        ),
 
                        // not a content namespace, different model
                        array( 'MediaWiki:WikiPageTest_testIsCountable.js',
-                              'Foo',
-                              'any',
-                              false
+                                       null,
+                                       'Foo',
+                                       'any',
+                                       false
                        ),
                        array( 'MediaWiki:WikiPageTest_testIsCountable.js',
-                              'Foo, bar',
-                              'comma',
-                              false
+                                       null,
+                                       'Foo, bar',
+                                       'comma',
+                                       false
                        ),
                        array( 'MediaWiki:WikiPageTest_testIsCountable.js',
-                              'Foo [[bar]]',
-                              'link',
-                              false
+                                       null,
+                                       'Foo [[bar]]',
+                                       'link',
+                                       false
                        ),
                );
        }
 
 
        /**
-        * @dataProvider dataIsCountable
+        * @dataProvider provideIsCountable
         */
-       public function testIsCountable( $title, $text, $mode, $expected ) {
+       public function testIsCountable( $title, $model, $text, $mode, $expected ) {
                global $wgArticleCountMethod;
 
-               $old = $wgArticleCountMethod;
+               $oldArticleCountMethod = $wgArticleCountMethod;
                $wgArticleCountMethod = $mode;
 
-               $page = $this->createPage( $title, $text );
-               $editInfo = $page->prepareTextForEdit( $page->getText() );
+               $page = $this->createPage( $title, $text, $model );
+               $hasLinks = wfGetDB( DB_SLAVE )->selectField( 'pagelinks', 1,
+                                       array( 'pl_from' => $page->getId() ), __METHOD__ );
+
+               $editInfo = $page->prepareContentForEdit( $page->getContent() );
 
                $v = $page->isCountable();
                $w = $page->isCountable( $editInfo );
-               $wgArticleCountMethod = $old;
+
+               $wgArticleCountMethod = $oldArticleCountMethod;
 
                $this->assertEquals( $expected, $v, "isCountable( null ) returned unexpected value " . var_export( $v, true )
-                                                   . " instead of " . var_export( $expected, true ) . " in mode `$mode` for text \"$text\"" );
+                                                                                       . " instead of " . var_export( $expected, true ) . " in mode `$mode` for text \"$text\"" );
 
                $this->assertEquals( $expected, $w, "isCountable( \$editInfo ) returned unexpected value " . var_export( $v, true )
-                                                   . " instead of " . var_export( $expected, true ) . " in mode `$mode` for text \"$text\"" );
+                                                                                       . " instead of " . var_export( $expected, true ) . " in mode `$mode` for text \"$text\"" );
        }
 
-       public function dataGetParserOutput() {
+       public static function provideGetParserOutput() {
                return array(
-                       array("hello ''world''\n", "<p>hello <i>world</i></p>"),
+                       array( CONTENT_MODEL_WIKITEXT, "hello ''world''\n", "<p>hello <i>world</i></p>"),
                        // @todo: more...?
                );
        }
 
        /**
-        * @dataProvider dataGetParserOutput
+        * @dataProvider provideGetParserOutput
         */
-       public function testGetParserOutput( $text, $expectedHtml ) {
-               $page = $this->createPage( 'WikiPageTest_testGetParserOutput', $text );
+       public function testGetParserOutput( $model, $text, $expectedHtml ) {
+               $page = $this->createPage( 'WikiPageTest_testGetParserOutput', $text, $model );
 
                $opt = new ParserOptions();
                $po = $page->getParserOutput( $opt );
@@ -428,41 +606,49 @@ more stuff
 
 
        public function dataReplaceSection() {
+               //NOTE: assume the Help namespace to contain wikitext
                return array(
-                       array( 'WikiPageTest_testReplaceSection',
-                              WikiPageTest::$sections,
-                              "0",
-                              "No more",
-                              null,
-                              trim( preg_replace( '/^Intro/sm', 'No more', WikiPageTest::$sections ) )
-                       ),
-                       array( 'WikiPageTest_testReplaceSection',
-                              WikiPageTest::$sections,
-                              "",
-                              "No more",
-                              null,
-                              "No more"
-                       ),
-                       array( 'WikiPageTest_testReplaceSection',
-                              WikiPageTest::$sections,
-                              "2",
-                              "== TEST ==\nmore fun",
-                              null,
-                              trim( preg_replace( '/^== test ==.*== foo ==/sm', "== TEST ==\nmore fun\n\n== foo ==", WikiPageTest::$sections ) )
-                       ),
-                       array( 'WikiPageTest_testReplaceSection',
-                              WikiPageTest::$sections,
-                              "8",
-                              "No more",
-                              null,
-                              trim( WikiPageTest::$sections )
-                       ),
-                       array( 'WikiPageTest_testReplaceSection',
-                              WikiPageTest::$sections,
-                              "new",
-                              "No more",
-                              "New",
-                              trim( WikiPageTest::$sections ) . "\n\n== New ==\n\nNo more"
+                       array( 'Help:WikiPageTest_testReplaceSection',
+                                       CONTENT_MODEL_WIKITEXT,
+                                       WikiPageTest::$sections,
+                                       "0",
+                                       "No more",
+                                       null,
+                                       trim( preg_replace( '/^Intro/sm', 'No more', WikiPageTest::$sections ) )
+                       ),
+                       array( 'Help:WikiPageTest_testReplaceSection',
+                                       CONTENT_MODEL_WIKITEXT,
+                                       WikiPageTest::$sections,
+                                       "",
+                                       "No more",
+                                       null,
+                                       "No more"
+                       ),
+                       array( 'Help:WikiPageTest_testReplaceSection',
+                                       CONTENT_MODEL_WIKITEXT,
+                                       WikiPageTest::$sections,
+                                       "2",
+                                       "== TEST ==\nmore fun",
+                                       null,
+                                       trim( preg_replace( '/^== test ==.*== foo ==/sm',
+                                                                               "== TEST ==\nmore fun\n\n== foo ==",
+                                                                               WikiPageTest::$sections ) )
+                       ),
+                       array( 'Help:WikiPageTest_testReplaceSection',
+                                       CONTENT_MODEL_WIKITEXT,
+                                       WikiPageTest::$sections,
+                                       "8",
+                                       "No more",
+                                       null,
+                                       trim( WikiPageTest::$sections )
+                       ),
+                       array( 'Help:WikiPageTest_testReplaceSection',
+                                       CONTENT_MODEL_WIKITEXT,
+                                       WikiPageTest::$sections,
+                                       "new",
+                                       "No more",
+                                       "New",
+                                       trim( WikiPageTest::$sections ) . "\n\n== New ==\n\nNo more"
                        ),
                );
        }
@@ -470,14 +656,28 @@ more stuff
        /**
         * @dataProvider dataReplaceSection
         */
-       public function testReplaceSection( $title, $text, $section, $with, $sectionTitle, $expected ) {
-               $page = $this->createPage( $title, $text );
+       public function testReplaceSection( $title, $model, $text, $section, $with, $sectionTitle, $expected ) {
+               $this->hideDeprecated( "WikiPage::replaceSection" );
+
+               $page = $this->createPage( $title, $text, $model );
                $text = $page->replaceSection( $section, $with, $sectionTitle );
                $text = trim( $text );
 
                $this->assertEquals( $expected, $text );
        }
 
+       /**
+        * @dataProvider dataReplaceSection
+        */
+       public function testReplaceSectionContent( $title, $model, $text, $section, $with, $sectionTitle, $expected ) {
+               $page = $this->createPage( $title, $text, $model );
+
+               $content = ContentHandler::makeContent( $with, $page->getTitle(), $page->getContentModel() );
+               $c = $page->replaceSectionContent( $section, $content, $sectionTitle );
+
+               $this->assertEquals( $expected, is_null( $c ) ? null : trim( $c->getNativeData() ) );
+       }
+
        /* @todo FIXME: fix this!
        public function testGetUndoText() {
                global $wgDiff3;
@@ -538,19 +738,22 @@ more stuff
 
                $text = "one";
                $page = $this->newPage( "WikiPageTest_testDoRollback" );
-               $page->doEdit( $text, "section one", EDIT_NEW, false, $admin );
+               $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ),
+                                                               "section one", EDIT_NEW, false, $admin );
 
                $user1 = new User();
                $user1->setName( "127.0.1.11" );
                $text .= "\n\ntwo";
                $page = new WikiPage( $page->getTitle() );
-               $page->doEdit( $text, "adding section two", 0, false, $user1 );
+               $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ),
+                                                               "adding section two", 0, false, $user1 );
 
                $user2 = new User();
                $user2->setName( "127.0.2.13" );
                $text .= "\n\nthree";
                $page = new WikiPage( $page->getTitle() );
-               $page->doEdit( $text, "adding section three", 0, false, $user2 );
+               $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ),
+                                                               "adding section three", 0, false, $user2 );
 
                # we are having issues with doRollback spuriously failing. apparently the last revision somehow goes missing
                # or not committed under some circumstances. so, make sure the last revision has the right user name.
@@ -577,8 +780,9 @@ more stuff
                }
 
                $page = new WikiPage( $page->getTitle() );
-               $this->assertEquals( $rev2->getSha1(), $page->getRevision()->getSha1(), "rollback did not revert to the correct revision" );
-               $this->assertEquals( "one\n\ntwo", $page->getText() );
+               $this->assertEquals( $rev2->getSha1(), $page->getRevision()->getSha1(),
+                                                               "rollback did not revert to the correct revision" );
+               $this->assertEquals( "one\n\ntwo", $page->getContent()->getNativeData() );
        }
 
        /**
@@ -590,14 +794,16 @@ more stuff
 
                $text = "one";
                $page = $this->newPage( "WikiPageTest_testDoRollback" );
-               $page->doEdit( $text, "section one", EDIT_NEW, false, $admin );
+               $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle(), CONTENT_MODEL_WIKITEXT ),
+                                                               "section one", EDIT_NEW, false, $admin );
                $rev1 = $page->getRevision();
 
                $user1 = new User();
                $user1->setName( "127.0.1.11" );
                $text .= "\n\ntwo";
                $page = new WikiPage( $page->getTitle() );
-               $page->doEdit( $text, "adding section two", 0, false, $user1 );
+               $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle(), CONTENT_MODEL_WIKITEXT ),
+                                                               "adding section two", 0, false, $user1 );
 
                # now, try the rollback
                $admin->addGroup( "sysop" ); #XXX: make the test user a sysop...
@@ -609,11 +815,12 @@ more stuff
                }
 
                $page = new WikiPage( $page->getTitle() );
-               $this->assertEquals( $rev1->getSha1(), $page->getRevision()->getSha1(), "rollback did not revert to the correct revision" );
-               $this->assertEquals( "one", $page->getText() );
+               $this->assertEquals( $rev1->getSha1(), $page->getRevision()->getSha1(),
+                                                       "rollback did not revert to the correct revision" );
+               $this->assertEquals( "one", $page->getContent()->getNativeData() );
        }
 
-       public function dataGetAutosummary( ) {
+       public static function provideGetAutosummary( ) {
                return array(
                        array(
                                'Hello there, world!',
@@ -655,17 +862,20 @@ more stuff
        }
 
        /**
-        * @dataProvider dataGetAutoSummary
+        * @dataProvider provideGetAutoSummary
         */
        public function testGetAutosummary( $old, $new, $flags, $expected ) {
+               $this->hideDeprecated( "WikiPage::getAutosummary" );
+
                $page = $this->newPage( "WikiPageTest_testGetAutosummary" );
 
                $summary = $page->getAutosummary( $old, $new, $flags );
 
-               $this->assertTrue( (bool)preg_match( $expected, $summary ), "Autosummary didn't match expected pattern $expected: $summary" );
+               $this->assertTrue( (bool)preg_match( $expected, $summary ),
+                                                       "Autosummary didn't match expected pattern $expected: $summary" );
        }
 
-       public function dataGetAutoDeleteReason( ) {
+       public static function provideGetAutoDeleteReason( ) {
                return array(
                        array(
                                array(),
@@ -702,10 +912,10 @@ more stuff
                        array(
                                array(
                                        array( "first edit: "
-                                            . "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam "
-                                            . " nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. "
-                                            . "At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea "
-                                            . "takimata sanctus est Lorem ipsum dolor sit amet.'", null ),
+                                                . "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam "
+                                                . " nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. "
+                                                . "At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea "
+                                                . "takimata sanctus est Lorem ipsum dolor sit amet.'", null ),
                                ),
                                '/first edit:.*\.\.\."/',
                                false
@@ -724,12 +934,13 @@ more stuff
        }
 
        /**
-        * @dataProvider dataGetAutoDeleteReason
+        * @dataProvider provideGetAutoDeleteReason
         */
        public function testGetAutoDeleteReason( $edits, $expectedResult, $expectedHistory ) {
                global $wgUser;
 
-               $page = $this->newPage( "WikiPageTest_testGetAutoDeleteReason" );
+               //NOTE: assume Help namespace to contain wikitext
+               $page = $this->newPage( "Help:WikiPageTest_testGetAutoDeleteReason" );
 
                $c = 1;
 
@@ -739,7 +950,9 @@ more stuff
                        if ( !empty( $edit[1] ) ) $user->setName( $edit[1] );
                        else $user = $wgUser;
 
-                       $page->doEdit( $edit[0], "test edit $c", $c < 2 ? EDIT_NEW : 0, false, $user );
+                       $content = ContentHandler::makeContent( $edit[0], $page->getTitle(), $page->getContentModel() );
+
+                       $page->doEditContent( $content, "test edit $c", $c < 2 ? EDIT_NEW : 0, false, $user );
 
                        $c += 1;
                }
@@ -747,33 +960,36 @@ more stuff
                $reason = $page->getAutoDeleteReason( $hasHistory );
 
                if ( is_bool( $expectedResult ) || is_null( $expectedResult ) ) $this->assertEquals( $expectedResult, $reason );
-               else $this->assertTrue( (bool)preg_match( $expectedResult, $reason ), "Autosummary didn't match expected pattern $expectedResult: $reason" );
+               else $this->assertTrue( (bool)preg_match( $expectedResult, $reason ),
+                                                               "Autosummary didn't match expected pattern $expectedResult: $reason" );
 
-               $this->assertEquals( $expectedHistory, $hasHistory, "expected \$hasHistory to be " . var_export( $expectedHistory, true ) );
+               $this->assertEquals( $expectedHistory, $hasHistory,
+                                                       "expected \$hasHistory to be " . var_export( $expectedHistory, true ) );
 
                $page->doDeleteArticle( "done" );
        }
 
-       public function dataPreSaveTransform() {
+       public static function providePreSaveTransform() {
                return array(
                        array( 'hello this is ~~~',
-                              "hello this is [[Special:Contributions/127.0.0.1|127.0.0.1]]",
+                                       "hello this is [[Special:Contributions/127.0.0.1|127.0.0.1]]",
                        ),
                        array( 'hello \'\'this\'\' is <nowiki>~~~</nowiki>',
-                              'hello \'\'this\'\' is <nowiki>~~~</nowiki>',
+                                       'hello \'\'this\'\' is <nowiki>~~~</nowiki>',
                        ),
                );
        }
 
        /**
-        * @dataProvider dataPreSaveTransform
+        * @dataProvider providePreSaveTransform
         */
        public function testPreSaveTransform( $text, $expected ) {
                $this->hideDeprecated( 'WikiPage::preSaveTransform' );
                $user = new User();
                $user->setName("127.0.0.1");
 
-               $page = $this->newPage( "WikiPageTest_testPreloadTransform" );
+               //NOTE: assume Help namespace to contain wikitext
+               $page = $this->newPage( "Help:WikiPageTest_testPreloadTransform" );
                $text = $page->preSaveTransform( $text, $user );
 
                $this->assertEquals( $expected, $text );
diff --git a/tests/phpunit/includes/WikiPageTest_ContentHandlerUseDB.php b/tests/phpunit/includes/WikiPageTest_ContentHandlerUseDB.php
new file mode 100644 (file)
index 0000000..1af6806
--- /dev/null
@@ -0,0 +1,64 @@
+<?php
+
+/**
+ * @group ContentHandler
+ * @group Database
+ * ^--- important, causes temporary tables to be used instead of the real database
+ */
+class WikiPageTest_ContentHandlerUseDB extends WikiPageTest {
+       var $saveContentHandlerNoDB = null;
+
+       function setUp() {
+               global $wgContentHandlerUseDB;
+
+               parent::setUp();
+
+               $this->saveContentHandlerNoDB = $wgContentHandlerUseDB;
+
+               $wgContentHandlerUseDB = false;
+
+               $dbw = wfGetDB( DB_MASTER );
+
+               $page_table = $dbw->tableName( 'page' );
+               $revision_table = $dbw->tableName( 'revision' );
+               $archive_table = $dbw->tableName( 'archive' );
+
+               if ( $dbw->fieldExists( $page_table, 'page_content_model' ) ) {
+                       $dbw->query( "alter table $page_table drop column page_content_model" );
+                       $dbw->query( "alter table $revision_table drop column rev_content_model" );
+                       $dbw->query( "alter table $revision_table drop column rev_content_format" );
+                       $dbw->query( "alter table $archive_table drop column ar_content_model" );
+                       $dbw->query( "alter table $archive_table drop column ar_content_format" );
+               }
+       }
+
+       function tearDown() {
+               global $wgContentHandlerUseDB;
+
+               $wgContentHandlerUseDB = $this->saveContentHandlerNoDB;
+
+               parent::tearDown();
+       }
+
+       public function testGetContentModel() {
+               $page = $this->createPage( "WikiPageTest_testGetContentModel", "some text", CONTENT_MODEL_JAVASCRIPT );
+
+               $page = new WikiPage( $page->getTitle() );
+
+               // NOTE: since the content model is not recorded in the database,
+               //       we expect to get the default, namely CONTENT_MODEL_WIKITEXT
+               $this->assertEquals( CONTENT_MODEL_WIKITEXT, $page->getContentModel() );
+       }
+
+       public function testGetContentHandler() {
+               $page = $this->createPage( "WikiPageTest_testGetContentHandler", "some text", CONTENT_MODEL_JAVASCRIPT );
+
+               // NOTE: since the content model is not recorded in the database,
+               //       we expect to get the default, namely CONTENT_MODEL_WIKITEXT
+               $page = new WikiPage( $page->getTitle() );
+               $this->assertEquals( 'WikitextContentHandler', get_class( $page->getContentHandler() ) );
+       }
+
+}
+
+
diff --git a/tests/phpunit/includes/WikitextContentHandlerTest.php b/tests/phpunit/includes/WikitextContentHandlerTest.php
new file mode 100644 (file)
index 0000000..8aeb529
--- /dev/null
@@ -0,0 +1,199 @@
+<?php
+
+/**
+ * @group ContentHandler
+ */
+class WikitextContentHandlerTest extends MediaWikiTestCase {
+
+       /**
+        * @var ContentHandler
+        */
+       var $handler;
+
+       public function setup() {
+               $this->handler = ContentHandler::getForModelID( CONTENT_MODEL_WIKITEXT );
+       }
+
+       public function teardown() {
+       }
+
+       public function testSerializeContent( ) {
+               $content = new WikitextContent( 'hello world' );
+
+               $this->assertEquals( 'hello world', $this->handler->serializeContent( $content ) );
+               $this->assertEquals( 'hello world', $this->handler->serializeContent( $content, CONTENT_FORMAT_WIKITEXT ) );
+
+               try {
+                       $this->handler->serializeContent( $content, 'dummy/foo' );
+                       $this->fail( "serializeContent() should have failed on unknown format" );
+               } catch ( MWException $e ) {
+                       // ok, as expected
+               }
+       }
+
+       public function testUnserializeContent( ) {
+               $content = $this->handler->unserializeContent( 'hello world' );
+               $this->assertEquals( 'hello world', $content->getNativeData() );
+
+               $content = $this->handler->unserializeContent( 'hello world', CONTENT_FORMAT_WIKITEXT );
+               $this->assertEquals( 'hello world', $content->getNativeData() );
+
+               try {
+                       $this->handler->unserializeContent( 'hello world', 'dummy/foo' );
+                       $this->fail( "unserializeContent() should have failed on unknown format" );
+               } catch ( MWException $e ) {
+                       // ok, as expected
+               }
+       }
+
+       public function testMakeEmptyContent() {
+               $content = $this->handler->makeEmptyContent();
+
+               $this->assertTrue( $content->isEmpty() );
+               $this->assertEquals( '', $content->getNativeData() );
+       }
+
+       public function dataIsSupportedFormat( ) {
+               return array(
+                       array( null, true ),
+                       array( CONTENT_FORMAT_WIKITEXT, true ),
+                       array( 99887766, false ),
+               );
+       }
+
+       /**
+        * @dataProvider dataIsSupportedFormat
+        */
+       public function testIsSupportedFormat( $format, $supported ) {
+               $this->assertEquals( $supported, $this->handler->isSupportedFormat( $format ) );
+       }
+
+       public function dataMerge3( ) {
+               return array(
+                       array( "first paragraph
+
+                                       second paragraph\n",
+
+                                       "FIRST paragraph
+
+                                       second paragraph\n",
+
+                                       "first paragraph
+
+                                       SECOND paragraph\n",
+
+                                       "FIRST paragraph
+
+                                       SECOND paragraph\n",
+                       ),
+
+                       array( "first paragraph
+                                       second paragraph\n",
+
+                                  "Bla bla\n",
+
+                                  "Blubberdibla\n",
+
+                                  false,
+                       ),
+
+               );
+       }
+
+       /**
+        * @dataProvider dataMerge3
+        */
+       public function testMerge3( $old, $mine, $yours, $expected ) {
+               global $wgDiff3;
+
+               if ( !$wgDiff3 ) {
+                       $this->markTestSkipped( "Can't test merge3(), since \$wgDiff3 is not configured" );
+               }
+
+               if ( !file_exists( $wgDiff3 ) ) {
+                       #XXX: this sucks, since it uses arcane internal knowledge about TextContentHandler::merge3 and wfMerge.
+                       $this->markTestSkipped( "Can't test merge3(), since \$wgDiff3 is misconfigured: can't find $wgDiff3" );
+               }
+
+               // test merge
+               $oldContent = new WikitextContent( $old );
+               $myContent = new WikitextContent( $mine );
+               $yourContent = new WikitextContent( $yours );
+
+               $merged = $this->handler->merge3( $oldContent, $myContent, $yourContent );
+
+               $this->assertEquals( $expected, $merged ? $merged->getNativeData() : $merged );
+       }
+
+       public function dataGetAutosummary( ) {
+               return array(
+                       array(
+                               'Hello there, world!',
+                               '#REDIRECT [[Foo]]',
+                               0,
+                               '/^Redirected page .*Foo/'
+                       ),
+
+                       array(
+                               null,
+                               'Hello world!',
+                               EDIT_NEW,
+                               '/^Created page .*Hello/'
+                       ),
+
+                       array(
+                               'Hello there, world!',
+                               '',
+                               0,
+                               '/^Blanked/'
+                       ),
+
+                       array(
+                               'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut
+                               labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et
+                               ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.',
+                               'Hello world!',
+                               0,
+                               '/^Replaced .*Hello/'
+                       ),
+
+                       array(
+                               'foo',
+                               'bar',
+                               0,
+                               '/^$/'
+                       ),
+               );
+       }
+
+       /**
+        * @dataProvider dataGetAutoSummary
+        */
+       public function testGetAutosummary( $old, $new, $flags, $expected ) {
+               global $wgLanguageCode, $wgContLang;
+
+               $oldContent = is_null( $old ) ? null : new WikitextContent( $old );
+               $newContent = is_null( $new ) ? null : new WikitextContent( $new );
+
+               $summary = $this->handler->getAutosummary( $oldContent, $newContent, $flags );
+
+               $this->assertTrue( (bool)preg_match( $expected, $summary ), "Autosummary didn't match expected pattern $expected: $summary" );
+       }
+
+       /**
+        * @todo Text case requires database, should be done by a test class in the Database group
+        */
+       /*
+       public function testGetAutoDeleteReason( Title $title, &$hasHistory ) {
+       }
+       */
+
+       /**
+        * @todo Text case requires database, should be done by a test class in the Database group
+        */
+       /*
+       public function testGetUndoContent( Revision $current, Revision $undo, Revision $undoafter = null ) {
+       }
+       */
+
+}
diff --git a/tests/phpunit/includes/WikitextContentTest.php b/tests/phpunit/includes/WikitextContentTest.php
new file mode 100644 (file)
index 0000000..dac8041
--- /dev/null
@@ -0,0 +1,557 @@
+<?php
+
+/**
+ * @group ContentHandler
+ *
+ * @group Database
+ *        ^--- needed, because we do need the database to test link updates
+ */
+class WikitextContentTest extends MediaWikiTestCase {
+
+       public function setup() {
+               global $wgUser;
+
+               // anon user
+               $wgUser = new User();
+               $wgUser->setName( '127.0.0.1' );
+
+               $this->context = new RequestContext( new FauxRequest() );
+               $this->context->setTitle( Title::newFromText( "Test" ) );
+               $this->context->setUser( $wgUser );
+       }
+
+       public function newContent( $text ) {
+               return new WikitextContent( $text );
+       }
+
+
+       public function dataGetParserOutput() {
+               return array(
+                       array("WikitextContentTest_testGetParserOutput", CONTENT_MODEL_WIKITEXT, "hello ''world''\n", "<p>hello <i>world</i>\n</p>"),
+                       // @todo: more...?
+               );
+       }
+
+       /**
+        * @dataProvider dataGetParserOutput
+        */
+       public function testGetParserOutput( $title, $model, $text, $expectedHtml ) {
+               $title = Title::newFromText( $title );
+               $content = ContentHandler::makeContent( $text, $title, $model );
+
+               $po = $content->getParserOutput( $title );
+
+               $this->assertEquals( $expectedHtml, $po->getText() );
+               // @todo: assert more properties
+       }
+
+       public function dataGetSecondaryDataUpdates() {
+               return array(
+                       array("WikitextContentTest_testGetSecondaryDataUpdates_1",
+                               CONTENT_MODEL_WIKITEXT, "hello ''world''\n",
+                               array( 'LinksUpdate' => array(  'mRecursive' => true,
+                                                               'mLinks' => array() ) )
+                       ),
+                       array("WikitextContentTest_testGetSecondaryDataUpdates_2",
+                               CONTENT_MODEL_WIKITEXT, "hello [[world test 21344]]\n",
+                               array( 'LinksUpdate' => array(  'mRecursive' => true,
+                                                               'mLinks' => array( array( 'World_test_21344' => 0 ) ) ) )
+                       ),
+                       // @todo: more...?
+               );
+       }
+
+       /**
+        * @dataProvider dataGetSecondaryDataUpdates
+        * @group Database
+        */
+       public function testGetSecondaryDataUpdates( $title, $model, $text, $expectedStuff ) {
+               $title = Title::newFromText( $title );
+               $title->resetArticleID( 2342 ); //dummy id. fine as long as we don't try to execute the updates!
+
+               $content = ContentHandler::makeContent( $text, $title, $model );
+
+               $updates = $content->getSecondaryDataUpdates( $title );
+
+               // make updates accessible by class name
+               foreach ( $updates as $update ) {
+                       $class = get_class( $update );
+                       $updates[$class] = $update;
+               }
+
+               foreach ( $expectedStuff as $class => $fieldValues ) {
+                       $this->assertArrayHasKey( $class, $updates, "missing an update of type $class" );
+
+                       $update = $updates[$class];
+
+                       foreach ( $fieldValues as $field => $value ) {
+                               $v = $update->$field; #if the field doesn't exist, just crash and burn
+                               $this->assertEquals( $value, $v, "unexpected value for field $field in instance of $class" );
+                       }
+               }
+       }
+
+
+       static $sections =
+
+"Intro
+
+== stuff ==
+hello world
+
+== test ==
+just a test
+
+== foo ==
+more stuff
+";
+
+       public function dataGetSection() {
+               return array(
+                       array( WikitextContentTest::$sections,
+                                       "0",
+                                       "Intro"
+                       ),
+                       array( WikitextContentTest::$sections,
+                                       "2",
+"== test ==
+just a test"
+                       ),
+                       array( WikitextContentTest::$sections,
+                                       "8",
+                                       false
+                       ),
+               );
+       }
+
+       /**
+        * @dataProvider dataGetSection
+        */
+       public function testGetSection( $text, $sectionId, $expectedText ) {
+               $content = $this->newContent( $text );
+
+               $sectionContent = $content->getSection( $sectionId );
+
+               $this->assertEquals( $expectedText, is_null( $sectionContent ) ? null : $sectionContent->getNativeData() );
+       }
+
+       public function dataReplaceSection() {
+               return array(
+                       array( WikitextContentTest::$sections,
+                              "0",
+                              "No more",
+                              null,
+                              trim( preg_replace( '/^Intro/sm', 'No more', WikitextContentTest::$sections ) )
+                       ),
+                       array( WikitextContentTest::$sections,
+                              "",
+                              "No more",
+                              null,
+                              "No more"
+                       ),
+                       array( WikitextContentTest::$sections,
+                              "2",
+                              "== TEST ==\nmore fun",
+                              null,
+                              trim( preg_replace( '/^== test ==.*== foo ==/sm', "== TEST ==\nmore fun\n\n== foo ==", WikitextContentTest::$sections ) )
+                       ),
+                       array( WikitextContentTest::$sections,
+                              "8",
+                              "No more",
+                              null,
+                              WikitextContentTest::$sections
+                       ),
+                       array( WikitextContentTest::$sections,
+                              "new",
+                              "No more",
+                              "New",
+                              trim( WikitextContentTest::$sections ) . "\n\n\n== New ==\n\nNo more"
+                       ),
+               );
+       }
+
+       /**
+        * @dataProvider dataReplaceSection
+        */
+       public function testReplaceSection( $text, $section, $with, $sectionTitle, $expected ) {
+               $content = $this->newContent( $text );
+               $c = $content->replaceSection( $section, $this->newContent( $with ), $sectionTitle );
+
+               $this->assertEquals( $expected, is_null( $c ) ? null : $c->getNativeData() );
+       }
+
+       public function testAddSectionHeader( ) {
+               $content = $this->newContent( 'hello world' );
+               $content = $content->addSectionHeader( 'test' );
+
+               $this->assertEquals( "== test ==\n\nhello world", $content->getNativeData() );
+       }
+
+       public function dataPreSaveTransform() {
+               return array(
+                       array( 'hello this is ~~~',
+                              "hello this is [[Special:Contributions/127.0.0.1|127.0.0.1]]",
+                       ),
+                       array( 'hello \'\'this\'\' is <nowiki>~~~</nowiki>',
+                              'hello \'\'this\'\' is <nowiki>~~~</nowiki>',
+                       ),
+               );
+       }
+
+       /**
+        * @dataProvider dataPreSaveTransform
+        */
+       public function testPreSaveTransform( $text, $expected ) {
+               global $wgContLang;
+
+               $options = ParserOptions::newFromUserAndLang( $this->context->getUser(), $wgContLang );
+
+               $content = $this->newContent( $text );
+               $content = $content->preSaveTransform( $this->context->getTitle(), $this->context->getUser(), $options );
+
+               $this->assertEquals( $expected, $content->getNativeData() );
+       }
+
+       public function dataPreloadTransform() {
+               return array(
+                       array( 'hello this is ~~~',
+                              "hello this is ~~~",
+                       ),
+                       array( 'hello \'\'this\'\' is <noinclude>foo</noinclude><includeonly>bar</includeonly>',
+                              'hello \'\'this\'\' is bar',
+                       ),
+               );
+       }
+
+       /**
+        * @dataProvider dataPreloadTransform
+        */
+       public function testPreloadTransform( $text, $expected ) {
+               global $wgContLang;
+               $options = ParserOptions::newFromUserAndLang( $this->context->getUser(), $wgContLang );
+
+               $content = $this->newContent( $text );
+               $content = $content->preloadTransform( $this->context->getTitle(), $options );
+
+               $this->assertEquals( $expected, $content->getNativeData() );
+       }
+
+       public function dataGetRedirectTarget() {
+               return array(
+                       array( '#REDIRECT [[Test]]',
+                              'Test',
+                       ),
+                       array( '#REDIRECT Test',
+                              null,
+                       ),
+                       array( '* #REDIRECT [[Test]]',
+                              null,
+                       ),
+               );
+       }
+
+       /**
+        * @dataProvider dataGetRedirectTarget
+        */
+       public function testGetRedirectTarget( $text, $expected ) {
+               $content = $this->newContent( $text );
+               $t = $content->getRedirectTarget( );
+
+               if ( is_null( $expected ) ) {
+                       $this->assertNull( $t, "text should not have generated a redirect target: $text" );
+               } else {
+                       $this->assertEquals( $expected, $t->getPrefixedText() );
+               }
+       }
+
+       /**
+        * @dataProvider dataGetRedirectTarget
+        */
+       public function isRedirect( $text, $expected ) {
+               $content = $this->newContent( $text );
+
+               $this->assertEquals( !is_null($expected), $content->isRedirect() );
+       }
+
+
+       /**
+        * @todo: test needs database! Should be done by a test class in the Database group.
+        */
+       /*
+       public function getRedirectChain() {
+               $text = $this->getNativeData();
+               return Title::newFromRedirectArray( $text );
+       }
+       */
+
+       /**
+        * @todo: test needs database! Should be done by a test class in the Database group.
+        */
+       /*
+       public function getUltimateRedirectTarget() {
+               $text = $this->getNativeData();
+               return Title::newFromRedirectRecurse( $text );
+       }
+       */
+
+
+       public function dataIsCountable() {
+               return array(
+                       array( '',
+                              null,
+                              'any',
+                              true
+                       ),
+                       array( 'Foo',
+                              null,
+                              'any',
+                              true
+                       ),
+                       array( 'Foo',
+                              null,
+                              'comma',
+                              false
+                       ),
+                       array( 'Foo, bar',
+                              null,
+                              'comma',
+                              true
+                       ),
+                       array( 'Foo',
+                              null,
+                              'link',
+                              false
+                       ),
+                       array( 'Foo [[bar]]',
+                              null,
+                              'link',
+                              true
+                       ),
+                       array( 'Foo',
+                              true,
+                              'link',
+                              true
+                       ),
+                       array( 'Foo [[bar]]',
+                              false,
+                              'link',
+                              false
+                       ),
+                       array( '#REDIRECT [[bar]]',
+                              true,
+                              'any',
+                              false
+                       ),
+                       array( '#REDIRECT [[bar]]',
+                              true,
+                              'comma',
+                              false
+                       ),
+                       array( '#REDIRECT [[bar]]',
+                              true,
+                              'link',
+                              false
+                       ),
+               );
+       }
+
+
+       /**
+        * @dataProvider dataIsCountable
+        * @group Database
+        */
+       public function testIsCountable( $text, $hasLinks, $mode, $expected ) {
+               global $wgArticleCountMethod;
+
+               $old = $wgArticleCountMethod;
+               $wgArticleCountMethod = $mode;
+
+               $content = $this->newContent( $text );
+
+               $v = $content->isCountable( $hasLinks, $this->context->getTitle() );
+               $wgArticleCountMethod = $old;
+
+               $this->assertEquals( $expected, $v, "isCountable() returned unexpected value " . var_export( $v, true )
+                                                   . " instead of " . var_export( $expected, true ) . " in mode `$mode` for text \"$text\"" );
+       }
+
+       public function dataGetTextForSummary() {
+               return array(
+                       array( "hello\nworld.",
+                              16,
+                              'hello world.',
+                       ),
+                       array( 'hello world.',
+                              8,
+                              'hello...',
+                       ),
+                       array( '[[hello world]].',
+                              8,
+                              'hel...',
+                       ),
+               );
+       }
+
+       /**
+        * @dataProvider dataGetTextForSummary
+        */
+       public function testGetTextForSummary( $text, $maxlength, $expected ) {
+               $content = $this->newContent( $text );
+
+               $this->assertEquals( $expected, $content->getTextForSummary( $maxlength ) );
+       }
+
+
+       public function testGetTextForSearchIndex( ) {
+               $content = $this->newContent( "hello world." );
+
+               $this->assertEquals( "hello world.", $content->getTextForSearchIndex() );
+       }
+
+       public function testCopy() {
+               $content = $this->newContent( "hello world." );
+               $copy = $content->copy();
+
+               $this->assertTrue( $content->equals( $copy ), "copy must be equal to original" );
+               $this->assertEquals( "hello world.", $copy->getNativeData() );
+       }
+
+       public function testGetSize( ) {
+               $content = $this->newContent( "hello world." );
+
+               $this->assertEquals( 12, $content->getSize() );
+       }
+
+       public function testGetNativeData( ) {
+               $content = $this->newContent( "hello world." );
+
+               $this->assertEquals( "hello world.", $content->getNativeData() );
+       }
+
+       public function testGetWikitextForTransclusion( ) {
+               $content = $this->newContent( "hello world." );
+
+               $this->assertEquals( "hello world.", $content->getWikitextForTransclusion() );
+       }
+
+       public function testMatchMagicWord( ) {
+               $mw = MagicWord::get( "staticredirect" );
+
+               $content = $this->newContent( "#REDIRECT [[FOO]]\n__STATICREDIRECT__" );
+               $this->assertTrue( $content->matchMagicWord( $mw ), "should have matched magic word" );
+
+               $content = $this->newContent( "#REDIRECT [[FOO]]" );
+               $this->assertFalse( $content->matchMagicWord( $mw ), "should not have matched magic word" );
+       }
+
+       public function testUpdateRedirect( ) {
+               $target = Title::newFromText( "testUpdateRedirect_target" );
+
+               // test with non-redirect page
+               $content = $this->newContent( "hello world." );
+               $newContent = $content->updateRedirect( $target );
+
+               $this->assertTrue( $content->equals( $newContent ), "content should be unchanged" );
+
+               // test with actual redirect
+               $content = $this->newContent( "#REDIRECT [[Someplace]]" );
+               $newContent = $content->updateRedirect( $target );
+
+               $this->assertFalse( $content->equals( $newContent ), "content should have changed" );
+               $this->assertTrue( $newContent->isRedirect(), "new content should be a redirect" );
+
+               $this->assertEquals( $target->getFullText(), $newContent->getRedirectTarget()->getFullText() );
+       }
+
+       # =================================================================================================================
+
+       public function testGetModel() {
+               $content = $this->newContent( "hello world." );
+
+               $this->assertEquals( CONTENT_MODEL_WIKITEXT, $content->getModel() );
+       }
+
+       public function testGetContentHandler() {
+               $content = $this->newContent( "hello world." );
+
+               $this->assertEquals( CONTENT_MODEL_WIKITEXT, $content->getContentHandler()->getModelID() );
+       }
+
+       public function dataIsEmpty( ) {
+               return array(
+                       array( '', true ),
+                       array( '  ', false ),
+                       array( '0', false ),
+                       array( 'hallo welt.', false ),
+               );
+       }
+
+       /**
+        * @dataProvider dataIsEmpty
+        */
+       public function testIsEmpty( $text, $empty ) {
+               $content = $this->newContent( $text );
+
+               $this->assertEquals( $empty, $content->isEmpty() );
+       }
+
+       public function dataEquals( ) {
+               return array(
+                       array( new WikitextContent( "hallo" ), null, false ),
+                       array( new WikitextContent( "hallo" ), new WikitextContent( "hallo" ), true ),
+                       array( new WikitextContent( "hallo" ), new JavascriptContent( "hallo" ), false ),
+                       array( new WikitextContent( "hallo" ), new WikitextContent( "HALLO" ), false ),
+               );
+       }
+
+       /**
+        * @dataProvider dataEquals
+        */
+       public function testEquals( Content $a, Content $b = null, $equal = false ) {
+               $this->assertEquals( $equal, $a->equals( $b ) );
+       }
+
+       public function dataGetDeletionUpdates() {
+               return array(
+                       array("WikitextContentTest_testGetSecondaryDataUpdates_1",
+                               CONTENT_MODEL_WIKITEXT, "hello ''world''\n",
+                               array( 'LinksDeletionUpdate' => array( ) )
+                       ),
+                       array("WikitextContentTest_testGetSecondaryDataUpdates_2",
+                               CONTENT_MODEL_WIKITEXT, "hello [[world test 21344]]\n",
+                               array( 'LinksDeletionUpdate' => array( ) )
+                       ),
+                       // @todo: more...?
+               );
+       }
+
+       /**
+        * @dataProvider dataGetDeletionUpdates
+        */
+       public function testDeletionUpdates( $title, $model, $text, $expectedStuff ) {
+               $title = Title::newFromText( $title );
+               $title->resetArticleID( 2342 ); //dummy id. fine as long as we don't try to execute the updates!
+
+               $content = ContentHandler::makeContent( $text, $title, $model );
+
+               $updates = $content->getDeletionUpdates( WikiPage::factory( $title ) );
+
+               // make updates accessible by class name
+               foreach ( $updates as $update ) {
+                       $class = get_class( $update );
+                       $updates[ $class ] = $update;
+               }
+
+               foreach ( $expectedStuff as $class => $fieldValues ) {
+                       $this->assertArrayHasKey( $class, $updates, "missing an update of type $class" );
+
+                       $update = $updates[ $class ];
+
+                       foreach ( $fieldValues as $field => $value ) {
+                               $v = $update->$field; #if the field doesn't exist, just crash and burn
+                               $this->assertEquals( $value, $v, "unexpected value for field $field in instance of $class" );
+                       }
+               }
+       }
+
+}
index 2407c15..75bd922 100644 (file)
@@ -35,7 +35,7 @@ class XmlSelectTest extends MediaWikiTestCase {
         * Provides a fourth parameters representing the expected HTML output
         *
         */
-       public function provideConstructionParameters() {
+       public static function provideConstructionParameters() {
                return array(
                        /**
                         * Values are set following a 3-bit Gray code where two successive
index 7f25f58..3cc5422 100644 (file)
@@ -4,17 +4,11 @@ class XmlTest extends MediaWikiTestCase {
        private static $oldLang;
        private static $oldNamespaces;
 
-       public function setUp() {
-               global $wgLang, $wgContLang;
+       protected function setUp() {
+               parent::setUp();
 
-               self::$oldLang = $wgLang;
-               $wgLang = Language::factory( 'en' );
-
-               // Hardcode namespaces during test runs,
-               // so that html output based on existing namespaces
-               // can be properly evaluated.
-               self::$oldNamespaces = $wgContLang->getNamespaces();
-               $wgContLang->setNamespaces( array(
+               $langObj = Language::factory( 'en' );
+               $langObj->setNamespaces( array(
                        -2 => 'Media',
                        -1 => 'Special',
                        0  => '',
@@ -32,13 +26,10 @@ class XmlTest extends MediaWikiTestCase {
                        100  => 'Custom',
                        101  => 'Custom_talk',
                ) );
-       }
 
-       public function tearDown() {
-               global $wgLang, $wgContLang;
-               $wgLang = self::$oldLang;
-
-               $wgContLang->setNamespaces( self::$oldNamespaces );
+               $this->setMwGlobals( array(
+                       'wgLang' => $langObj,
+               ) );
        }
 
        public function testExpandAttributes() {
index d90a695..81b32c2 100644 (file)
@@ -3,7 +3,7 @@
 class ZipDirectoryReaderTest extends MediaWikiTestCase {
        var $zipDir, $entries;
 
-       function setUp() {
+       protected function setUp() {
                $this->zipDir = __DIR__ . '/../data/zip';
        }
 
index 5dfceee..52f1d28 100644 (file)
@@ -6,7 +6,7 @@
  */
 class ApiBlockTest extends ApiTestCase {
 
-       function setUp() {
+       protected function setUp() {
                parent::setUp();
                $this->doLogin();
        }
index 5297d6d..8eca9a3 100644 (file)
  */
 class ApiEditPageTest extends ApiTestCase {
 
-       function setUp() {
-               parent::setUp();
+       public function setup() {
+               global $wgExtraNamespaces, $wgNamespaceContentModels, $wgContentHandlers, $wgContLang;
+
+               parent::setup();
+
+               $wgExtraNamespaces[12312] = 'Dummy';
+               $wgExtraNamespaces[12313] = 'Dummy_talk';
+
+               $wgNamespaceContentModels[12312] = "testing";
+               $wgContentHandlers["testing"] = 'DummyContentHandlerForTesting';
+
+               MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache
+               $wgContLang->resetNamespaces(); # reset namespace cache
+
                $this->doLogin();
        }
 
+       public function teardown() {
+               global $wgExtraNamespaces, $wgNamespaceContentModels, $wgContentHandlers, $wgContLang;
+
+               unset( $wgExtraNamespaces[12312] );
+               unset( $wgExtraNamespaces[12313] );
+
+               unset( $wgNamespaceContentModels[12312] );
+               unset( $wgContentHandlers["testing"] );
+
+               MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache
+               $wgContLang->resetNamespaces(); # reset namespace cache
+
+               parent::teardown();
+       }
+
        function testEdit( ) {
-               $name = 'ApiEditPageTest_testEdit';
+               $name = 'Help:ApiEditPageTest_testEdit'; // assume Help namespace to default to wikitext
 
                // -- test new page --------------------------------------------
                $apiResult = $this->doApiRequestWithToken( array(
@@ -25,7 +52,7 @@ class ApiEditPageTest extends ApiTestCase {
                                'text' => 'some text', ) );
                $apiResult = $apiResult[0];
 
-               # Validate API result data
+               // Validate API result data
                $this->assertArrayHasKey( 'edit', $apiResult );
                $this->assertArrayHasKey( 'result', $apiResult['edit'] );
                $this->assertEquals( 'Success', $apiResult['edit']['result'] );
@@ -66,6 +93,33 @@ class ApiEditPageTest extends ApiTestCase {
                );
        }
 
+       function testNonTextEdit( ) {
+               $name = 'Dummy:ApiEditPageTest_testNonTextEdit';
+               $data = serialize( 'some bla bla text' );
+
+               // -- test new page --------------------------------------------
+               $apiResult = $this->doApiRequestWithToken( array(
+                       'action' => 'edit',
+                       'title' => $name,
+                       'text' => $data, ) );
+               $apiResult = $apiResult[0];
+
+               // Validate API result data
+               $this->assertArrayHasKey( 'edit', $apiResult );
+               $this->assertArrayHasKey( 'result', $apiResult['edit'] );
+               $this->assertEquals( 'Success', $apiResult['edit']['result'] );
+
+               $this->assertArrayHasKey( 'new', $apiResult['edit'] );
+               $this->assertArrayNotHasKey( 'nochange', $apiResult['edit'] );
+
+               $this->assertArrayHasKey( 'pageid', $apiResult['edit'] );
+
+               // validate resulting revision
+               $page = WikiPage::factory( Title::newFromText( $name ) );
+               $this->assertEquals( "testing", $page->getContentModel() );
+               $this->assertEquals( $data, $page->getContent()->serialize() );
+       }
+
        function testEditAppend() {
                $this->markTestIncomplete( "not yet implemented" );
        }
index d54d7df..4684c55 100644 (file)
@@ -9,7 +9,7 @@ class ApiOptionsTest extends MediaWikiLangTestCase {
 
        private static $Success = array( 'options' => 'success' );
 
-       function setUp() {
+       protected function setUp() {
                parent::setUp();
 
                $this->mUserMock = $this->getMockBuilder( 'User' )
index 2566c6c..d903714 100644 (file)
@@ -6,7 +6,7 @@
  */
 class ApiPurgeTest extends ApiTestCase {
 
-       function setUp() {
+       protected function setUp() {
                parent::setUp();
                $this->doLogin();
        }
index a4b9dc7..dbf02f7 100644 (file)
@@ -6,7 +6,7 @@
  */
 class ApiQueryTest extends ApiTestCase {
 
-       function setUp() {
+       protected function setUp() {
                parent::setUp();
                $this->doLogin();
        }
index 3ca6741..de52175 100644 (file)
@@ -8,7 +8,7 @@ abstract class ApiTestCase extends MediaWikiLangTestCase {
         */
        protected $apiContext;
 
-       function setUp() {
+       protected function setUp() {
                global $wgContLang, $wgAuth, $wgMemc, $wgRequest, $wgUser, $wgServer;
 
                parent::setUp();
index 39c7954..9f281bd 100644 (file)
@@ -8,19 +8,23 @@ abstract class ApiTestCaseUpload extends ApiTestCase {
        /**
         * Fixture -- run before every test
         */
-       public function setUp() {
-               global $wgEnableUploads, $wgEnableAPI;
+       protected function setUp() {
                parent::setUp();
 
-               $wgEnableUploads = true;
-               $wgEnableAPI = true;
+               $this->setMwGlobals( array(
+                       'wgEnableUploads' => true,
+                       'wgEnableAPI' => true,
+               ) );
+
                wfSetupSession();
 
                $this->clearFakeUploads();
        }
 
-       public function tearDown() {
+       protected function tearDown() {
                $this->clearTempUpload();
+
+               parent::tearDown();
        }
 
        /**
@@ -54,7 +58,6 @@ abstract class ApiTestCaseUpload extends ApiTestCase {
                return $this->deleteFileByTitle( Title::newFromText( $fileName, NS_FILE ) );
        }
 
-
        /**
         * Helper function -- given a file on the filesystem, find matching content in the db (and associated articles) and remove them.
         * @param $filePath String: path to file on the filesystem
@@ -104,6 +107,7 @@ abstract class ApiTestCaseUpload extends ApiTestCase {
                return true;
 
        }
+
        function fakeUploadChunk(  $fieldName, $fileName, $type, & $chunkData ){
                $tmpName = tempnam( wfTempDir(), "" );
                // copy the chunk data to temp location: 
@@ -142,7 +146,4 @@ abstract class ApiTestCaseUpload extends ApiTestCase {
                $_FILES = array();
        }
 
-
-
-
 }
index d2e9815..b7ae292 100644 (file)
@@ -7,7 +7,7 @@
  */
 class ApiWatchTest extends ApiTestCase {
 
-       function setUp() {
+       protected function setUp() {
                parent::setUp();
                $this->doLogin();
        }
@@ -29,7 +29,7 @@ class ApiWatchTest extends ApiTestCase {
 
                $data = $this->doApiRequest( array(
                        'action' => 'edit',
-                       'title' => 'UTPage',
+                       'title' => 'Help:UTPage', // Help namespace is hopefully wikitext
                        'text' => 'new text',
                        'token' => $pageinfo['edittoken'],
                        'watchlist' => 'watch' ) );
@@ -81,7 +81,7 @@ class ApiWatchTest extends ApiTestCase {
                $data = $this->doApiRequest( array(
                        'action' => 'protect',
                        'token' => $pageinfo['protecttoken'],
-                       'title' => 'UTPage',
+                       'title' => 'Help:UTPage',
                        'protections' => 'edit=sysop',
                        'watchlist' => 'unwatch' ) );
 
@@ -97,14 +97,14 @@ class ApiWatchTest extends ApiTestCase {
 
                $pageinfo = $this->getTokens();
 
-               if ( !Title::newFromText( 'UTPage' )->exists() ) {
-                       $this->markTestSkipped( "The article [[UTPage]] does not exist" ); //TODO: just create it?
+               if ( !Title::newFromText( 'Help:UTPage' )->exists() ) {
+                       $this->markTestSkipped( "The article [[Help:UTPage]] does not exist" ); //TODO: just create it?
                }
 
                $data = $this->doApiRequest( array(
                        'action' => 'query',
                        'prop' => 'revisions',
-                       'titles' => 'UTPage',
+                       'titles' => 'Help:UTPage',
                        'rvtoken' => 'rollback' ) );
 
                $this->assertArrayHasKey( 'query', $data[0] );
@@ -113,7 +113,7 @@ class ApiWatchTest extends ApiTestCase {
                $key = array_pop( $keys );
 
                if ( isset( $data[0]['query']['pages'][$key]['missing'] ) ) {
-                       $this->markTestSkipped( "Target page (UTPage) doesn't exist" );
+                       $this->markTestSkipped( "Target page (Help:UTPage) doesn't exist" );
                }
 
                $this->assertArrayHasKey( 'pageid', $data[0]['query']['pages'][$key] );
@@ -139,7 +139,7 @@ class ApiWatchTest extends ApiTestCase {
                try {
                        $data = $this->doApiRequest( array(
                                'action' => 'rollback',
-                               'title' => 'UTPage',
+                               'title' => 'Help:UTPage',
                                'user' => $revinfo['user'],
                                'token' => $pageinfo['rollbacktoken'],
                                'watchlist' => 'watch' ) );
@@ -148,7 +148,7 @@ class ApiWatchTest extends ApiTestCase {
                        $this->assertArrayHasKey( 'title', $data[0]['rollback'] );
                } catch( UsageException $ue ) {
                        if( $ue->getCodeString() == 'onlyauthor' ) {
-                               $this->markTestIncomplete( "Only one author to 'UTPage', cannot test rollback" );
+                               $this->markTestIncomplete( "Only one author to 'Help:UTPage', cannot test rollback" );
                        } else {
                                $this->fail( "Received error '" . $ue->getCodeString() . "'" );
                        }
@@ -163,7 +163,7 @@ class ApiWatchTest extends ApiTestCase {
                $data = $this->doApiRequest( array(
                        'action' => 'delete',
                        'token' => $pageinfo['deletetoken'],
-                       'title' => 'UTPage' ) );
+                       'title' => 'Help:UTPage' ) );
                $this->assertArrayHasKey( 'delete', $data[0] );
                $this->assertArrayHasKey( 'title', $data[0]['delete'] );
 
index a8b987e..52cdc78 100644 (file)
@@ -6,7 +6,7 @@
  */
 class GenderCacheTest extends MediaWikiLangTestCase {
 
-       function setUp() {
+       protected function setUp() {
                global $wgDefaultUserOptions;
                parent::setUp();
                //ensure the correct default gender
@@ -45,7 +45,7 @@ class GenderCacheTest extends MediaWikiLangTestCase {
        /**
         * test usernames
         *
-        * @dataProvider dataUserName
+        * @dataProvider provideUserGenders
         */
        function testUserName( $username, $expectedGender ) {
                $genderCache = GenderCache::singleton();
@@ -56,7 +56,7 @@ class GenderCacheTest extends MediaWikiLangTestCase {
        /**
         * genderCache should work with user objects, too
         *
-        * @dataProvider dataUserName
+        * @dataProvider provideUserGenders
         */
        function testUserObjects( $username, $expectedGender ) {
                $genderCache = GenderCache::singleton();
@@ -65,7 +65,7 @@ class GenderCacheTest extends MediaWikiLangTestCase {
                $this->assertEquals( $gender, $expectedGender, "GenderCache normal" );
        }
 
-       function dataUserName() {
+       public static function provideUserGenders() {
                return array(
                        array( 'UTMale', 'male' ),
                        array( 'UTFemale', 'female' ),
@@ -81,7 +81,7 @@ class GenderCacheTest extends MediaWikiLangTestCase {
         * test strip of subpages to avoid unnecessary queries
         * against the never existing username
         *
-        * @dataProvider dataStripSubpages
+        * @dataProvider provideStripSubpages
         */
        function testStripSubpages( $pageWithSubpage, $expectedGender ) {
                $genderCache = GenderCache::singleton();
@@ -89,7 +89,7 @@ class GenderCacheTest extends MediaWikiLangTestCase {
                $this->assertEquals( $gender, $expectedGender, "GenderCache must strip of subpages" );
        }
 
-       function dataStripSubpages() {
+       public static function provideStripSubpages() {
                return array(
                        array( 'UTMale/subpage', 'male' ),
                        array( 'UTFemale/subpage', 'female' ),
index 30bfb12..1c081b8 100644 (file)
@@ -76,7 +76,7 @@ class ProcessCacheLRUTest extends MediaWikiTestCase {
        /**
         * Value which are forbidden by the constructor
         */
-       function provideInvalidConstructorArg() {
+       public static function provideInvalidConstructorArg() {
                return array(
                        array( null ),
                        array( array() ),
@@ -131,7 +131,7 @@ class ProcessCacheLRUTest extends MediaWikiTestCase {
        /**
         * Provider for testFillingCache
         */
-       function provideCacheFilling() {
+       public static function provideCacheFilling() {
                // ($cacheMaxEntries, $entryToFill, $msg='')
                return array(
                        array( 1,  0 ),
index e37cd44..0c9f749 100644 (file)
@@ -8,15 +8,15 @@
  */
 class DatabaseSQLTest extends MediaWikiTestCase {
 
-       public function setUp() {
+       protected function setUp() {
                // TODO support other DBMS or find another way to do it
-               if( $this->db->getType() !== 'mysql' ) {
+               if ( $this->db->getType() !== 'mysql' ) {
                        $this->markTestSkipped( 'No mysql database' );
                }
        }
 
        /**
-        * @dataProvider dataSelectSQLText
+        * @dataProvider provideSelectSQLText
         */
        function testSelectSQLText( $sql, $sqlText ) {
                $this->assertEquals( trim( $this->db->selectSQLText(
@@ -29,7 +29,7 @@ class DatabaseSQLTest extends MediaWikiTestCase {
                ) ), $sqlText );
        }
 
-       function dataSelectSQLText() {
+       public static function provideSelectSQLText() {
                return array(
                        array(
                                array(
@@ -106,7 +106,7 @@ class DatabaseSQLTest extends MediaWikiTestCase {
        }
 
        /**
-        * @dataProvider dataConditional
+        * @dataProvider provideConditional
         */
        function testConditional( $sql, $sqlText ) {
                $this->assertEquals( trim( $this->db->conditional(
@@ -116,7 +116,7 @@ class DatabaseSQLTest extends MediaWikiTestCase {
                ) ), $sqlText );
        }
 
-       function dataConditional() {
+       public static function provideConditional() {
                return array(
                        array(
                                array(
index d226598..216de84 100644 (file)
@@ -24,7 +24,9 @@ class MockDatabaseSqlite extends DatabaseSqliteStandalone {
 class DatabaseSqliteTest extends MediaWikiTestCase {
        var $db;
 
-       public function setUp() {
+       protected function setUp() {
+               parent::setUp();
+
                if ( !Sqlite::isPresent() ) {
                        $this->markTestSkipped( 'No SQLite support detected' );
                }
index dc12ba5..a8a6b48 100644 (file)
@@ -7,11 +7,11 @@
 class DatabaseTest extends MediaWikiTestCase {
        var $db, $functionTest = false;
 
-       function setUp() {
+       protected function setUp() {
                $this->db = wfGetDB( DB_MASTER );
        }
 
-       function tearDown() {
+       protected function tearDown() {
                if ( $this->functionTest ) {
                        $this->dropFunctions();
                        $this->functionTest = false;
index afd1cb8..c7bea3b 100644 (file)
@@ -58,7 +58,7 @@ class TestORMRowTest extends ORMRowTest {
                return TestORMTable::singleton();
        }
 
-       public function setUp() {
+       protected function setUp() {
                parent::setUp();
 
                $dbw = wfGetDB( DB_MASTER );
index 246b291..4f338d3 100644 (file)
@@ -3,7 +3,7 @@
 class MWDebugTest extends MediaWikiTestCase {
 
 
-       function setUp() {
+       protected function setUp() {
                // Make sure MWDebug class is enabled
                static $MWDebugEnabled = false;
                if( !$MWDebugEnabled ) {
@@ -15,7 +15,7 @@ class MWDebugTest extends MediaWikiTestCase {
                wfSuppressWarnings();
        }
 
-       function tearDown() {
+       protected function tearDown() {
                wfRestoreWarnings();
        }
 
index bdb5c42..f159d5d 100644 (file)
@@ -10,7 +10,7 @@ class FileBackendTest extends MediaWikiTestCase {
        private $filesToPrune = array();
        private static $backendToUse;
 
-       function setUp() {
+       protected function setUp() {
                global $wgFileBackends;
                parent::setUp();
                $tmpPrefix = wfTempDir() . '/filebackend-unittest-' . time() . '-' . mt_rand();
@@ -50,7 +50,7 @@ class FileBackendTest extends MediaWikiTestCase {
                        'parallelize' => 'implicit',
                        'backends'    => array(
                                array(
-                                       'name'          => 'localmutlitesting1',
+                                       'name'          => 'localmultitesting1',
                                        'class'         => 'FSFileBackend',
                                        'lockManager'   => 'nullLockManager',
                                        'containerPaths' => array(
@@ -59,7 +59,7 @@ class FileBackendTest extends MediaWikiTestCase {
                                        'isMultiMaster' => false
                                ),
                                array(
-                                       'name'          => 'localmutlitesting2',
+                                       'name'          => 'localmultitesting2',
                                        'class'         => 'FSFileBackend',
                                        'lockManager'   => 'nullLockManager',
                                        'containerPaths' => array(
@@ -72,7 +72,7 @@ class FileBackendTest extends MediaWikiTestCase {
                $this->filesToPrune = array();
        }
 
-       private function baseStorePath() {
+       private static function baseStorePath() {
                return 'mwstore://localtesting';
        }
 
@@ -183,7 +183,7 @@ class FileBackendTest extends MediaWikiTestCase {
                        "FileBackend::extensionFromPath on path '$path'" );
        }
 
-       function provider_testExtensionFromPath() {
+       public static function provider_testExtensionFromPath() {
                return array(
                        array( 'mwstore://backend/container/path.txt', 'txt' ),
                        array( 'mwstore://backend/container/path.svg.png', 'png' ),
@@ -248,11 +248,11 @@ class FileBackendTest extends MediaWikiTestCase {
                $this->assertBackendPathsConsistent( array( $dest ) );
        }
 
-       public function provider_testStore() {
+       public static function provider_testStore() {
                $cases = array();
 
                $tmpName = TempFSFile::factory( "unittests_", 'txt' )->getPath();
-               $toPath = $this->baseStorePath() . '/unittest-cont1/e/fun/obj1.txt';
+               $toPath = self::baseStorePath() . '/unittest-cont1/e/fun/obj1.txt';
                $op = array( 'op' => 'store', 'src' => $tmpName, 'dst' => $toPath );
                $cases[] = array(
                        $op, // operation
@@ -337,11 +337,11 @@ class FileBackendTest extends MediaWikiTestCase {
                $this->assertBackendPathsConsistent( array( $source, $dest ) );
        }
 
-       public function provider_testCopy() {
+       public static function provider_testCopy() {
                $cases = array();
 
-               $source = $this->baseStorePath() . '/unittest-cont1/e/file.txt';
-               $dest = $this->baseStorePath() . '/unittest-cont2/a/fileMoved.txt';
+               $source = self::baseStorePath() . '/unittest-cont1/e/file.txt';
+               $dest = self::baseStorePath() . '/unittest-cont2/a/fileMoved.txt';
 
                $op = array( 'op' => 'copy', 'src' => $source, 'dst' => $dest );
                $cases[] = array(
@@ -428,11 +428,11 @@ class FileBackendTest extends MediaWikiTestCase {
                $this->assertBackendPathsConsistent( array( $source, $dest ) );
        }
 
-       public function provider_testMove() {
+       public static function provider_testMove() {
                $cases = array();
 
-               $source = $this->baseStorePath() . '/unittest-cont1/e/file.txt';
-               $dest = $this->baseStorePath() . '/unittest-cont2/a/fileMoved.txt';
+               $source = self::baseStorePath() . '/unittest-cont1/e/file.txt';
+               $dest = self::baseStorePath() . '/unittest-cont2/a/fileMoved.txt';
 
                $op = array( 'op' => 'move', 'src' => $source, 'dst' => $dest );
                $cases[] = array(
@@ -515,10 +515,10 @@ class FileBackendTest extends MediaWikiTestCase {
                $this->assertBackendPathsConsistent( array( $source ) );
        }
 
-       public function provider_testDelete() {
+       public static function provider_testDelete() {
                $cases = array();
 
-               $source = $this->baseStorePath() . '/unittest-cont1/e/myfacefile.txt';
+               $source = self::baseStorePath() . '/unittest-cont1/e/myfacefile.txt';
 
                $op = array( 'op' => 'delete', 'src' => $source );
                $cases[] = array(
@@ -611,10 +611,10 @@ class FileBackendTest extends MediaWikiTestCase {
        /**
         * @dataProvider provider_testCreate
         */
-       public function provider_testCreate() {
+       public static function provider_testCreate() {
                $cases = array();
 
-               $dest = $this->baseStorePath() . '/unittest-cont2/a/myspacefile.txt';
+               $dest = self::baseStorePath() . '/unittest-cont2/a/myspacefile.txt';
 
                $op = array( 'op' => 'create', 'content' => 'test test testing', 'dst' => $dest );
                $cases[] = array(
@@ -678,7 +678,7 @@ class FileBackendTest extends MediaWikiTestCase {
        private function doTestDoQuickOperations() {
                $backendName = $this->backendClass();
 
-               $base = $this->baseStorePath();
+               $base = self::baseStorePath();
                $files = array(
                        "$base/unittest-cont1/e/fileA.a",
                        "$base/unittest-cont1/e/fileB.a",
@@ -800,16 +800,16 @@ class FileBackendTest extends MediaWikiTestCase {
                $rand = mt_rand( 0, 2000000000 ) . time();
                $dest = wfTempDir() . "/randomfile!$rand.txt";
                $srcs = array(
-                       $this->baseStorePath() . '/unittest-cont1/e/file1.txt',
-                       $this->baseStorePath() . '/unittest-cont1/e/file2.txt',
-                       $this->baseStorePath() . '/unittest-cont1/e/file3.txt',
-                       $this->baseStorePath() . '/unittest-cont1/e/file4.txt',
-                       $this->baseStorePath() . '/unittest-cont1/e/file5.txt',
-                       $this->baseStorePath() . '/unittest-cont1/e/file6.txt',
-                       $this->baseStorePath() . '/unittest-cont1/e/file7.txt',
-                       $this->baseStorePath() . '/unittest-cont1/e/file8.txt',
-                       $this->baseStorePath() . '/unittest-cont1/e/file9.txt',
-                       $this->baseStorePath() . '/unittest-cont1/e/file10.txt'
+                       self::baseStorePath() . '/unittest-cont1/e/file1.txt',
+                       self::baseStorePath() . '/unittest-cont1/e/file2.txt',
+                       self::baseStorePath() . '/unittest-cont1/e/file3.txt',
+                       self::baseStorePath() . '/unittest-cont1/e/file4.txt',
+                       self::baseStorePath() . '/unittest-cont1/e/file5.txt',
+                       self::baseStorePath() . '/unittest-cont1/e/file6.txt',
+                       self::baseStorePath() . '/unittest-cont1/e/file7.txt',
+                       self::baseStorePath() . '/unittest-cont1/e/file8.txt',
+                       self::baseStorePath() . '/unittest-cont1/e/file9.txt',
+                       self::baseStorePath() . '/unittest-cont1/e/file10.txt'
                );
                $content = array(
                        'egfage',
@@ -911,7 +911,7 @@ class FileBackendTest extends MediaWikiTestCase {
        function provider_testGetFileStat() {
                $cases = array();
 
-               $base = $this->baseStorePath();
+               $base = self::baseStorePath();
                $cases[] = array( "$base/unittest-cont1/e/b/z/some_file.txt", "some file contents", true );
                $cases[] = array( "$base/unittest-cont1/e/b/some-other_file.txt", "", true );
                $cases[] = array( "$base/unittest-cont1/e/b/some-diff_file.txt", null, false );
@@ -966,7 +966,7 @@ class FileBackendTest extends MediaWikiTestCase {
        function provider_testGetFileContents() {
                $cases = array();
 
-               $base = $this->baseStorePath();
+               $base = self::baseStorePath();
                $cases[] = array( "$base/unittest-cont1/e/b/z/some_file.txt", "some file contents" );
                $cases[] = array( "$base/unittest-cont1/e/b/some-other_file.txt", "more file contents" );
                $cases[] = array(
@@ -1034,7 +1034,7 @@ class FileBackendTest extends MediaWikiTestCase {
        function provider_testGetLocalCopy() {
                $cases = array();
 
-               $base = $this->baseStorePath();
+               $base = self::baseStorePath();
                $cases[] = array( "$base/unittest-cont1/e/a/z/some_file.txt", "some file contents" );
                $cases[] = array( "$base/unittest-cont1/e/a/some-other_file.txt", "more file contents" );
                $cases[] = array( "$base/unittest-cont1/e/a/\$odd&.txt", "test file contents" );
@@ -1100,7 +1100,7 @@ class FileBackendTest extends MediaWikiTestCase {
        function provider_testGetLocalReference() {
                $cases = array();
 
-               $base = $this->baseStorePath();
+               $base = self::baseStorePath();
                $cases[] = array( "$base/unittest-cont1/e/a/z/some_file.txt", "some file contents" );
                $cases[] = array( "$base/unittest-cont1/e/a/some-other_file.txt", "more file contents" );
                $cases[] = array( "$base/unittest-cont1/e/a/\$odd&.txt", "test file contents" );
@@ -1127,7 +1127,7 @@ class FileBackendTest extends MediaWikiTestCase {
        }
 
        function provider_testPrepareAndClean() {
-               $base = $this->baseStorePath();
+               $base = self::baseStorePath();
                return array(
                        array( "$base/unittest-cont1/e/a/z/some_file1.txt", true ),
                        array( "$base/unittest-cont2/a/z/some_file2.txt", true ),
@@ -1175,7 +1175,7 @@ class FileBackendTest extends MediaWikiTestCase {
        private function doTestRecursiveClean() {
                $backendName = $this->backendClass();
 
-               $base = $this->baseStorePath();
+               $base = self::baseStorePath();
                $dirs = array(
                        "$base/unittest-cont1/e/a",
                        "$base/unittest-cont1/e/a/b",
@@ -1229,7 +1229,7 @@ class FileBackendTest extends MediaWikiTestCase {
        }
 
        private function doTestDoOperations() {
-               $base = $this->baseStorePath();
+               $base = self::baseStorePath();
 
                $fileA = "$base/unittest-cont1/e/a/b/fileA.txt";
                $fileAContents = '3tqtmoeatmn4wg4qe-mg3qt3 tq';
@@ -1315,7 +1315,7 @@ class FileBackendTest extends MediaWikiTestCase {
 
        // concurrency orientated
        private function doTestDoOperationsPipeline() {
-               $base = $this->baseStorePath();
+               $base = self::baseStorePath();
 
                $fileAContents = '3tqtmoeatmn4wg4qe-mg3qt3 tq';
                $fileBContents = 'g-jmq3gpqgt3qtg q3GT ';
@@ -1413,7 +1413,7 @@ class FileBackendTest extends MediaWikiTestCase {
        }
 
        private function doTestDoOperationsFailing() {
-               $base = $this->baseStorePath();
+               $base = self::baseStorePath();
 
                $fileA = "$base/unittest-cont2/a/b/fileA.txt";
                $fileAContents = '3tqtmoeatmn4wg4qe-mg3qt3 tq';
@@ -1488,7 +1488,7 @@ class FileBackendTest extends MediaWikiTestCase {
 
        private function doTestGetFileList() {
                $backendName = $this->backendClass();
-               $base = $this->baseStorePath();
+               $base = self::baseStorePath();
 
                // Should have no errors
                $iter = $this->backend->getFileList( array( 'dir' => "$base/unittest-cont-notexists" ) );
@@ -1645,7 +1645,7 @@ class FileBackendTest extends MediaWikiTestCase {
        private function doTestGetDirectoryList() {
                $backendName = $this->backendClass();
 
-               $base = $this->baseStorePath();
+               $base = self::baseStorePath();
                $files = array(
                        "$base/unittest-cont1/e/test1.txt",
                        "$base/unittest-cont1/e/test2.txt",
@@ -1896,7 +1896,7 @@ class FileBackendTest extends MediaWikiTestCase {
                foreach ( $this->filesToPrune as $file ) {
                        @unlink( $file );
                }
-               $containers = array( 'unittest-cont1', 'unittest-cont2', 'unittest-cont3' );
+               $containers = array( 'unittest-cont1', 'unittest-cont2' );
                foreach ( $containers as $container ) {
                        $this->deleteFiles( $container );
                }
@@ -1904,12 +1904,11 @@ class FileBackendTest extends MediaWikiTestCase {
        }
 
        private function deleteFiles( $container ) {
-               $base = $this->baseStorePath();
+               $base = self::baseStorePath();
                $iter = $this->backend->getFileList( array( 'dir' => "$base/$container" ) );
                if ( $iter ) {
                        foreach ( $iter as $file ) {
-                               $this->backend->delete( array( 'src' => "$base/$container/$file" ),
-                                       array( 'force' => 1, 'nonLocking' => 1 ) );
+                               $this->backend->quickDelete( array( 'src' => "$base/$container/$file" ) );
                        }
                }
                $this->backend->clean( array( 'dir' => "$base/$container", 'recursive' => 1 ) );
@@ -1925,8 +1924,4 @@ class FileBackendTest extends MediaWikiTestCase {
        function assertGoodStatus( $status, $msg ) {
                $this->assertEquals( print_r( array(), 1 ), print_r( $status->errors, 1 ), $msg );
        }
-
-       function tearDown() {
-               parent::tearDown();
-       }
 }
index 3ab56af..7d815e9 100644 (file)
@@ -5,7 +5,7 @@
  */
 class StoreBatchTest extends MediaWikiTestCase {
 
-       public function setUp() {
+       protected function setUp() {
                global $wgFileBackends;
                parent::setUp();
 
@@ -43,6 +43,17 @@ class StoreBatchTest extends MediaWikiTestCase {
                $this->createdFiles = array();
        }
 
+       protected function tearDown() {
+               $this->repo->cleanupBatch( $this->createdFiles ); // delete files
+               foreach ( $this->createdFiles as $tmp ) { // delete dirs
+                       $tmp = $this->repo->resolveVirtualUrl( $tmp );
+                       while ( $tmp = FileBackend::parentStoragePath( $tmp ) ) {
+                               $this->repo->getBackend()->clean( array( 'dir' => $tmp ) );
+                       }
+               }
+               parent::tearDown();
+       }
+
        /**
         * Store a file or virtual URL source into a media file name.
         *
@@ -109,15 +120,4 @@ class StoreBatchTest extends MediaWikiTestCase {
                $this->storecohort( "Test1.png", "$IP/skins/monobook/wiki.png", "$IP/skins/monobook/video.png", false );
                $this->storecohort( "Test2.png", "$IP/skins/monobook/wiki.png", "$IP/skins/monobook/video.png", true );
        }
-
-       public function tearDown() {
-               $this->repo->cleanupBatch( $this->createdFiles ); // delete files
-               foreach ( $this->createdFiles as $tmp ) { // delete dirs
-                       $tmp = $this->repo->resolveVirtualUrl( $tmp );
-                       while ( $tmp = FileBackend::parentStoragePath( $tmp ) ) {
-                               $this->repo->getBackend()->clean( array( 'dir' => $tmp ) );
-                       }
-               }
-               parent::tearDown();
-       }
 }
index a382775..57017a8 100644 (file)
@@ -6,25 +6,16 @@
  */
 
 class CSSMinTest extends MediaWikiTestCase {
-       protected $oldServer = null, $oldCanServer = null;
 
-       function setUp() {
+       protected function setUp() {
                parent::setUp();
 
-               // Fake $wgServer and $wgCanonicalServer
-               global $wgServer, $wgCanonicalServer;
-               $this->oldServer = $wgServer;
-               $this->oldCanServer = $wgCanonicalServer;
-               $wgServer = $wgCanonicalServer = 'http://wiki.example.org';
-       }
-
-       function tearDown() {
-               // Restore $wgServer and $wgCanonicalServer
-               global $wgServer, $wgCanonicalServer;
-               $wgServer = $this->oldServer;
-               $wgCanonicalServer = $this->oldCanServer;
+               $server = 'http://doc.example.org';
 
-               parent::tearDown();
+               $this->setMwGlobals( array(
+                       'wgServer' => $server,
+                       'wgCanonicalServer' => $server,
+               ) );
        }
 
        /**
@@ -113,7 +104,7 @@ class CSSMinTest extends MediaWikiTestCase {
                        array(
                                'Expand absolute paths',
                                array( 'foo { prop: url(/w/skin/images/bar.png); }', false, 'http://example.org/quux', false ),
-                               'foo { prop: url(http://wiki.example.org/w/skin/images/bar.png); }',
+                               'foo { prop: url(http://doc.example.org/w/skin/images/bar.png); }',
                        ),
                );
        }
index 88f87ef..cfd75d8 100644 (file)
@@ -1,7 +1,11 @@
 <?php
 class BitmapMetadataHandlerTest extends MediaWikiTestCase {
 
-       public function setUp() {
+       protected function setUp() {
+               parent::setUp();
+
+               $this->setMwGlobals( 'wgShowEXIF', false );
+
                $this->filePath = __DIR__ . '/../../data/media/';
        }
 
@@ -14,14 +18,15 @@ class BitmapMetadataHandlerTest extends MediaWikiTestCase {
         * translation (to en) where XMP should win.
         */
        public function testMultilingualCascade() {
+               global $wgShowEXIF;
+
                if ( !wfDl( 'exif' ) ) {
                        $this->markTestSkipped( "This test needs the exif extension." );
                }
                if ( !wfDl( 'xml' ) ) {
                        $this->markTestSkipped( "This test needs the xml extension." );
                }
-               global $wgShowEXIF;
-               $oldExif = $wgShowEXIF;
+
                $wgShowEXIF = true;
 
                $meta = BitmapMetadataHandler::Jpeg( $this->filePath .
@@ -37,8 +42,6 @@ class BitmapMetadataHandlerTest extends MediaWikiTestCase {
                        'Did not extract any ImageDescription info?!' );
 
                $this->assertEquals( $expected, $meta['ImageDescription'] );
-
-               $wgShowEXIF = $oldExif;
        }
 
        /**
@@ -73,6 +76,7 @@ class BitmapMetadataHandlerTest extends MediaWikiTestCase {
                $this->assertEquals( '2020:07:14 01:36:05', $meta['DateTimeDigitized'] );
                $this->assertEquals( '1997:03:02 00:01:02', $meta['DateTimeOriginal'] );
        }
+
        /**
         * File has an invalid time (+ one valid but really weird time)
         * that shouldn't be included
@@ -131,12 +135,14 @@ class BitmapMetadataHandlerTest extends MediaWikiTestCase {
                );
                $this->assertEquals( $expected, $result ); 
        }
+
        public function testPNGNative() {
                $handler = new BitmapMetadataHandler();
                $result = $handler->png( $this->filePath . 'Png-native-test.png' );
                $expected = 'http://example.com/url';
                $this->assertEquals( $expected, $result['metadata']['Identifier']['x-default'] ); 
        }
+
        public function testTiffByteOrder() {
                $handler = new BitmapMetadataHandler();
                $res = $handler->getTiffByteOrder( $this->filePath . 'test.tiff' );
index 11d9dc4..eb1a536 100644 (file)
@@ -2,18 +2,15 @@
 
 class BitmapScalingTest extends MediaWikiTestCase {
 
-       function setUp() {
-               global $wgMaxImageArea, $wgCustomConvertCommand;
-               $this->oldMaxImageArea = $wgMaxImageArea;
-               $this->oldCustomConvertCommand = $wgCustomConvertCommand;
-               $wgMaxImageArea = 1.25e7; // 3500x3500 
-               $wgCustomConvertCommand = 'dummy'; // Set so that we don't get client side rendering
-       }
-       function tearDown() {
-               global $wgMaxImageArea, $wgCustomConvertCommand;
-               $wgMaxImageArea = $this->oldMaxImageArea;
-               $wgCustomConvertCommand = $this->oldCustomConvertCommand;
+       protected function setUp() {
+               parent::setUp();
+
+               $this->setMwGlobals( array(
+                       'wgMaxImageArea' => 1.25e7, // 3500x3500
+                       'wgCustomConvertCommand' => 'dummy', // Set so that we don't get client side rendering
+               ) );
        }
+
        /**
         * @dataProvider provideNormaliseParams
         */
@@ -103,7 +100,8 @@ class BitmapScalingTest extends MediaWikiTestCase {
                                'Bigger than max image size but doesn\'t need scaling',
                        ),
                );
-       } 
+       }
+
        function testTooBigImage() {
                $file = new FakeDimensionFile( array( 4000, 4000 ) );
                $handler = new BitmapHandler;
@@ -111,6 +109,7 @@ class BitmapScalingTest extends MediaWikiTestCase {
                $this->assertEquals( 'TransformParameterError', 
                        get_class( $handler->doTransform( $file, 'dummy path', '', $params ) ) );
        }
+
        function testTooBigMustRenderImage() {
                $file = new FakeDimensionFile( array( 4000, 4000 ) );
                $file->mustRender = true;
index b2f6b7b..dd22321 100644 (file)
@@ -2,43 +2,44 @@
 
 class ExifBitmapTest extends MediaWikiTestCase {
 
-       public function setUp() {
-               global $wgShowEXIF;
-               $this->showExif = $wgShowEXIF;
-               $wgShowEXIF = true;
+       protected function setUp() {
+               parent::setUp();
+
+               $this->setMwGlobals( 'wgShowEXIF', true );
+
                $this->handler = new ExifBitmapHandler;
                if ( !wfDl( 'exif' ) ) {
                        $this->markTestSkipped( "This test needs the exif extension." );
                }
        }
 
-       public function tearDown() {
-               global $wgShowEXIF;
-               $wgShowEXIF = $this->showExif;
-       }
-
        public function testIsOldBroken() {
                $res = $this->handler->isMetadataValid( null, ExifBitmapHandler::OLD_BROKEN_FILE );
                $this->assertEquals( ExifBitmapHandler::METADATA_COMPATIBLE, $res );
        }
+
        public function testIsBrokenFile() {
                $res = $this->handler->isMetadataValid( null, ExifBitmapHandler::BROKEN_FILE );
                $this->assertEquals( ExifBitmapHandler::METADATA_GOOD, $res );
        }
+
        public function testIsInvalid() {
                $res = $this->handler->isMetadataValid( null, 'Something Invalid Here.' );
                $this->assertEquals( ExifBitmapHandler::METADATA_BAD, $res );
        }
+
        public function testGoodMetadata() {
                $meta = 'a:16:{s:10:"ImageWidth";i:20;s:11:"ImageLength";i:20;s:13:"BitsPerSample";a:3:{i:0;i:8;i:1;i:8;i:2;i:8;}s:11:"Compression";i:5;s:25:"PhotometricInterpretation";i:2;s:16:"ImageDescription";s:17:"Created with GIMP";s:12:"StripOffsets";i:8;s:11:"Orientation";i:1;s:15:"SamplesPerPixel";i:3;s:12:"RowsPerStrip";i:64;s:15:"StripByteCounts";i:238;s:11:"XResolution";s:19:"1207959552/16777216";s:11:"YResolution";s:19:"1207959552/16777216";s:19:"PlanarConfiguration";i:1;s:14:"ResolutionUnit";i:2;s:22:"MEDIAWIKI_EXIF_VERSION";i:2;}';
                $res = $this->handler->isMetadataValid( null, $meta );
                $this->assertEquals( ExifBitmapHandler::METADATA_GOOD, $res );
        }
+
        public function testIsOldGood() {
                $meta = 'a:16:{s:10:"ImageWidth";i:20;s:11:"ImageLength";i:20;s:13:"BitsPerSample";a:3:{i:0;i:8;i:1;i:8;i:2;i:8;}s:11:"Compression";i:5;s:25:"PhotometricInterpretation";i:2;s:16:"ImageDescription";s:17:"Created with GIMP";s:12:"StripOffsets";i:8;s:11:"Orientation";i:1;s:15:"SamplesPerPixel";i:3;s:12:"RowsPerStrip";i:64;s:15:"StripByteCounts";i:238;s:11:"XResolution";s:19:"1207959552/16777216";s:11:"YResolution";s:19:"1207959552/16777216";s:19:"PlanarConfiguration";i:1;s:14:"ResolutionUnit";i:2;s:22:"MEDIAWIKI_EXIF_VERSION";i:1;}';
                $res = $this->handler->isMetadataValid( null, $meta );
                $this->assertEquals( ExifBitmapHandler::METADATA_COMPATIBLE, $res );
        }
+
        // Handle metadata from paged tiff handler (gotten via instant commons)
        // gracefully.
        public function testPagedTiffHandledGracefully() {
@@ -55,6 +56,7 @@ class ExifBitmapTest extends MediaWikiTestCase {
                $res = $this->handler->convertMetadataVersion( $metadata, 2 );
                $this->assertEquals( $metadata, $res );
        }
+
        function testConvertMetadataToOld() {
                $metadata = array(
                        'foo' => array( 'First', 'Second', '_type' => 'ol' ),
@@ -73,6 +75,7 @@ class ExifBitmapTest extends MediaWikiTestCase {
                $res = $this->handler->convertMetadataVersion( $metadata, 1 );
                $this->assertEquals( $expected, $res );
        }
+
        function testConvertMetadataSoftware() {
                $metadata = array(
                        'Software' => array( array('GIMP', '1.1' ) ),
@@ -85,6 +88,7 @@ class ExifBitmapTest extends MediaWikiTestCase {
                $res = $this->handler->convertMetadataVersion( $metadata, 1 );
                $this->assertEquals( $expected, $res );
        }
+
        function testConvertMetadataSoftwareNormal() {
                $metadata = array(
                        'Software' => array( "GIMP 1.2", "vim" ),
index 6af52dd..692a5f9 100644 (file)
@@ -5,7 +5,7 @@
  */
 class ExifRotationTest extends MediaWikiTestCase {
 
-       function setUp() {
+       protected function setUp() {
                parent::setUp();
                $this->handler = new BitmapHandler();
                $filePath = __DIR__ . '/../../data/media';
@@ -33,7 +33,7 @@ class ExifRotationTest extends MediaWikiTestCase {
                $wgEnableAutoRotation = true;
        }
 
-       public function tearDown() {
+       protected function tearDown() {
                global $wgShowEXIF, $wgEnableAutoRotation;
                $wgShowEXIF = $this->show;
                $wgEnableAutoRotation = $this->oldAuto;
@@ -43,7 +43,7 @@ class ExifRotationTest extends MediaWikiTestCase {
 
        /**
         *
-        * @dataProvider providerFiles
+        * @dataProvider provideFiles
         */
        function testMetadata( $name, $type, $info ) {
                if ( !BitmapHandler::canRotate() ) {
@@ -56,7 +56,7 @@ class ExifRotationTest extends MediaWikiTestCase {
 
        /**
         *
-        * @dataProvider providerFiles
+        * @dataProvider provideFiles
         */
        function testRotationRendering( $name, $type, $info, $thumbs ) {
                if ( !BitmapHandler::canRotate() ) {
@@ -94,12 +94,13 @@ class ExifRotationTest extends MediaWikiTestCase {
                }
        }
 
+       /* Utility function */
        private function dataFile( $name, $type ) {
                return new UnregisteredLocalFile( false, $this->repo,
                        "mwstore://localtesting/data/$name", $type );
        }
 
-       function providerFiles() {
+       public static function provideFiles() {
                return array(
                        array(
                                'landscape-plain.jpg',
@@ -134,7 +135,7 @@ class ExifRotationTest extends MediaWikiTestCase {
 
        /**
         * Same as before, but with auto-rotation disabled.
-        * @dataProvider providerFilesNoAutoRotate
+        * @dataProvider provideFilesNoAutoRotate
         */
        function testMetadataNoAutoRotate( $name, $type, $info ) {
                global $wgEnableAutoRotation;
@@ -149,7 +150,7 @@ class ExifRotationTest extends MediaWikiTestCase {
 
        /**
         *
-        * @dataProvider providerFilesNoAutoRotate
+        * @dataProvider provideFilesNoAutoRotate
         */
        function testRotationRenderingNoAutoRotate( $name, $type, $info, $thumbs ) {
                global $wgEnableAutoRotation;
@@ -188,7 +189,7 @@ class ExifRotationTest extends MediaWikiTestCase {
                $wgEnableAutoRotation = true;
        }
 
-       function providerFilesNoAutoRotate() {
+       public static function provideFilesNoAutoRotate() {
                return array(
                        array(
                                'landscape-plain.jpg',
index 045777d..7cc56f6 100644 (file)
@@ -1,20 +1,16 @@
 <?php
 class ExifTest extends MediaWikiTestCase {
 
-       public function setUp() {
+       protected function setUp() {
+               parent::setUp();
+
                $this->mediaPath = __DIR__ . '/../../data/media/';
 
                if ( !wfDl( 'exif' ) ) {
                        $this->markTestSkipped( "This test needs the exif extension." );
                }
-               global $wgShowEXIF;
-               $this->showExif = $wgShowEXIF;
-               $wgShowEXIF = true;
-       }
 
-       public function tearDown() {
-               global $wgShowEXIF;
-               $wgShowEXIF = $this->showExif;
+               $this->setMwGlobals( 'wgShowEXIF', true );
        }
 
        public function testGPSExtraction() {
index 6ade670..4dadde5 100644 (file)
@@ -1,6 +1,9 @@
 <?php
 class FormatMetadataTest extends MediaWikiTestCase {
-       public function setUp() {
+
+       protected function setUp() {
+               parent::setUp();
+
                if ( !wfDl( 'exif' ) ) {
                        $this->markTestSkipped( "This test needs the exif extension." );
                }
@@ -15,13 +18,8 @@ class FormatMetadataTest extends MediaWikiTestCase {
                        'url'     => 'http://localhost/thumbtest',
                        'backend' => $this->backend
                ) );
-               global $wgShowEXIF;
-               $this->show = $wgShowEXIF;
-               $wgShowEXIF = true;
-       }
-       public function tearDown() {
-               global $wgShowEXIF;
-               $wgShowEXIF = $this->show;
+
+               $this->setMwGlobals( 'wgShowEXIF', true );
        }
 
        public function testInvalidDate() {
index 650fdd5..3a750aa 100644 (file)
@@ -1,20 +1,22 @@
 <?php
 class GIFMetadataExtractorTest extends MediaWikiTestCase {
 
-       public function setUp() {
+       protected function setUp() {
+               parent::setUp();
+
                $this->mediaPath = __DIR__ . '/../../data/media/';
        }
        /**
         * Put in a file, and see if the metadata coming out is as expected.
         * @param $filename String
         * @param $expected Array The extracted metadata.
-        * @dataProvider dataGetMetadata
+        * @dataProvider provideGetMetadata
         */
        public function testGetMetadata( $filename, $expected ) {
                $actual = GIFMetadataExtractor::getMetadata( $this->mediaPath . $filename );
                $this->assertEquals( $expected, $actual );
        }
-       public function dataGetMetadata() {
+       public static function provideGetMetadata() {
 
                $xmpNugget = <<<EOF
 <?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d'?>
index 5dcbeee..9ffc764 100644 (file)
@@ -1,7 +1,9 @@
 <?php
 class GIFHandlerTest extends MediaWikiTestCase {
 
-       public function setUp() {
+       protected function setUp() {
+               parent::setUp();
+
                $this->filePath = __DIR__ .  '/../../data/media';
                $this->backend = new FSFileBackend( array(
                        'name'           => 'localtesting',
@@ -20,17 +22,18 @@ class GIFHandlerTest extends MediaWikiTestCase {
                $res = $this->handler->getMetadata( null, $this->filePath . '/README' );
                $this->assertEquals( GIFHandler::BROKEN_FILE, $res );
        }
+
        /**
         * @param $filename String basename of the file to check
         * @param $expected boolean Expected result.
-        * @dataProvider dataIsAnimated
+        * @dataProvider provideIsAnimated
         */
        public function testIsAnimanted( $filename, $expected ) {
                $file = $this->dataFile( $filename, 'image/gif' );
                $actual = $this->handler->isAnimatedImage( $file );
                $this->assertEquals( $expected, $actual );
        }
-       public function dataIsAnimated() {
+       public static function provideIsAnimated() {
                return array(
                        array( 'animated.gif', true ),
                        array( 'nonanimated.gif', false ),
@@ -40,14 +43,14 @@ class GIFHandlerTest extends MediaWikiTestCase {
        /**
         * @param $filename String
         * @param $expected Integer Total image area
-        * @dataProvider dataGetImageArea
+        * @dataProvider provideGetImageArea
         */
        public function testGetImageArea( $filename, $expected ) {
                $file = $this->dataFile( $filename, 'image/gif' );
                $actual = $this->handler->getImageArea( $file, $file->getWidth(), $file->getHeight() );
                $this->assertEquals( $expected, $actual );
        }
-       public function dataGetImageArea() {
+       public static function provideGetImageArea() {
                return array(
                        array( 'animated.gif', 5400 ),
                        array( 'nonanimated.gif', 1350 ),
@@ -57,13 +60,13 @@ class GIFHandlerTest extends MediaWikiTestCase {
        /**
         * @param $metadata String Serialized metadata
         * @param $expected Integer One of the class constants of GIFHandler
-        * @dataProvider dataIsMetadataValid
+        * @dataProvider provideIsMetadataValid
         */
        public function testIsMetadataValid( $metadata, $expected ) {
                $actual = $this->handler->isMetadataValid( null, $metadata );
                $this->assertEquals( $expected, $actual );
        }
-       public function dataIsMetadataValid() {
+       public static function provideIsMetadataValid() {
                return array(
                        array( GIFHandler::BROKEN_FILE, GIFHandler::METADATA_GOOD ),
                        array( '', GIFHandler::METADATA_BAD ),
@@ -76,7 +79,7 @@ class GIFHandlerTest extends MediaWikiTestCase {
        /**
         * @param $filename String
         * @param $expected String Serialized array
-        * @dataProvider dataGetMetadata
+        * @dataProvider provideGetMetadata
         */
        public function testGetMetadata( $filename, $expected ) {
                $file = $this->dataFile( $filename, 'image/gif' );
@@ -84,7 +87,7 @@ class GIFHandlerTest extends MediaWikiTestCase {
                $this->assertEquals( unserialize( $expected ), unserialize( $actual ) );
        }
 
-       public function dataGetMetadata() {
+       public static function provideGetMetadata() {
                return array(
                        array( 'nonanimated.gif', 'a:4:{s:10:"frameCount";i:1;s:6:"looped";b:0;s:8:"duration";d:0.1000000000000000055511151231257827021181583404541015625;s:8:"metadata";a:2:{s:14:"GIFFileComment";a:1:{i:0;s:35:"GIF test file ⁕ Created with GIMP";}s:15:"_MW_GIF_VERSION";i:1;}}' ),
                        array( 'animated-xmp.gif', 'a:4:{s:10:"frameCount";i:4;s:6:"looped";b:1;s:8:"duration";d:2.399999999999999911182158029987476766109466552734375;s:8:"metadata";a:5:{s:6:"Artist";s:7:"Bawolff";s:16:"ImageDescription";a:2:{s:9:"x-default";s:18:"A file to test GIF";s:5:"_type";s:4:"lang";}s:15:"SublocationDest";s:13:"The interwebs";s:14:"GIFFileComment";a:1:{i:0;s:16:"GIƒ·test·file";}s:15:"_MW_GIF_VERSION";i:1;}}' ),
index 41d8119..6e1c0af 100644 (file)
@@ -8,7 +8,9 @@
  */
 class JpegMetadataExtractorTest extends MediaWikiTestCase {
 
-       public function setUp() {
+       protected function setUp() {
+               parent::setUp();
+
                $this->filePath = __DIR__ . '/../../data/media/';
        }
 
@@ -18,13 +20,13 @@ class JpegMetadataExtractorTest extends MediaWikiTestCase {
         *
         * @param $file filename
         *
-        * @dataProvider dataUtf8Comment
+        * @dataProvider provideUtf8Comment
         */
        public function testUtf8Comment( $file ) {
                $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . $file );
                $this->assertEquals( array( 'UTF-8 JPEG Comment — ¼' ), $res['COM'] );
        }
-       public function dataUtf8Comment() {
+       public static function provideUtf8Comment() {
                return array(
                        array( 'jpeg-comment-utf.jpg' ),
                        array( 'jpeg-padding-even.jpg' ),
index ea007f9..05d3661 100644 (file)
@@ -1,18 +1,15 @@
 <?php
 class JpegTest extends MediaWikiTestCase {
 
-       public function setUp() {
+       protected function setUp() {
+               parent::setUp();
+
                $this->filePath = __DIR__ . '/../../data/media/';
                if ( !wfDl( 'exif' ) ) {
                        $this->markTestSkipped( "This test needs the exif extension." );
                }
-               global $wgShowEXIF;
-               $this->show = $wgShowEXIF;
-               $wgShowEXIF = true;
-       }
-       public function tearDown() {
-               global $wgShowEXIF;
-               $wgShowEXIF = $this->show;
+
+               $this->setMwGlobals( 'wgShowEXIF', true );
        }
 
        public function testInvalidFile() {
@@ -20,6 +17,7 @@ class JpegTest extends MediaWikiTestCase {
                $res = $jpeg->getMetadata( null, $this->filePath . 'README' );
                $this->assertEquals( ExifBitmapHandler::BROKEN_FILE, $res );
        }
+
        public function testJpegMetadataExtraction() {
                $h = new JpegHandler;
                $res = $h->getMetadata( null, $this->filePath . 'test.jpg' );
index 1b1b2ec..ee1c6f8 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 class PNGMetadataExtractorTest extends MediaWikiTestCase {
 
-       function setUp() {
+       protected function setUp() {
                $this->filePath = __DIR__ . '/../../data/media/';
        }
        /**
@@ -60,6 +60,8 @@ class PNGMetadataExtractorTest extends MediaWikiTestCase {
        /**
         * Test extraction of pHYs tags, which can tell what the
         * actual resolution of the image is (aka in dots per meter).
+        */
+/*
        function testPngPhysTag () {
                $meta = PNGMetadataExtractor::getMetadata( $this->filePath .
                        'Png-native-test.png' );
@@ -71,6 +73,7 @@ class PNGMetadataExtractorTest extends MediaWikiTestCase {
                $this->assertEquals( '2835/100', $meta['YResolution'] );
                $this->assertEquals( 3, $meta['ResolutionUnit'] ); // 3 = cm
        }
+*/
 
        /**
         * Given a normal static PNG, check the animation metadata returned.
index fe73c9c..2075758 100644 (file)
@@ -1,7 +1,9 @@
 <?php
 class PNGHandlerTest extends MediaWikiTestCase {
 
-       public function setUp() {
+       protected function setUp() {
+               parent::setUp();
+
                $this->filePath = __DIR__ .  '/../../data/media';
                $this->backend = new FSFileBackend( array(
                        'name'           => 'localtesting',
@@ -23,14 +25,14 @@ class PNGHandlerTest extends MediaWikiTestCase {
        /**
         * @param $filename String basename of the file to check
         * @param $expected boolean Expected result.
-        * @dataProvider dataIsAnimated
+        * @dataProvider provideIsAnimated
         */
        public function testIsAnimanted( $filename, $expected ) {
                $file = $this->dataFile( $filename, 'image/png' );
                $actual = $this->handler->isAnimatedImage( $file );
                $this->assertEquals( $expected, $actual );
        }
-       public function dataIsAnimated() {
+       public static function provideIsAnimated() {
                return array(
                        array( 'Animated_PNG_example_bouncing_beach_ball.png', true ),
                        array( '1bit-png.png', false ),
@@ -40,14 +42,14 @@ class PNGHandlerTest extends MediaWikiTestCase {
        /**
         * @param $filename String
         * @param $expected Integer Total image area
-        * @dataProvider dataGetImageArea
+        * @dataProvider provideGetImageArea
         */
        public function testGetImageArea( $filename, $expected ) {
                $file = $this->dataFile($filename, 'image/png' );
                $actual = $this->handler->getImageArea( $file, $file->getWidth(), $file->getHeight() );
                $this->assertEquals( $expected, $actual );
        }
-       public function dataGetImageArea() {
+       public static function provideGetImageArea() {
                return array(
                        array( '1bit-png.png', 2500 ),
                        array( 'greyscale-png.png', 2500 ),
@@ -59,13 +61,13 @@ class PNGHandlerTest extends MediaWikiTestCase {
        /**
         * @param $metadata String Serialized metadata
         * @param $expected Integer One of the class constants of PNGHandler
-        * @dataProvider dataIsMetadataValid
+        * @dataProvider provideIsMetadataValid
         */
        public function testIsMetadataValid( $metadata, $expected ) {
                $actual = $this->handler->isMetadataValid( null, $metadata );
                $this->assertEquals( $expected, $actual );
        }
-       public function dataIsMetadataValid() {
+       public static function provideIsMetadataValid() {
                return array(
                        array( PNGHandler::BROKEN_FILE, PNGHandler::METADATA_GOOD ),
                        array( '', PNGHandler::METADATA_BAD ),
@@ -78,7 +80,7 @@ class PNGHandlerTest extends MediaWikiTestCase {
        /**
         * @param $filename String
         * @param $expected String Serialized array
-        * @dataProvider dataGetMetadata
+        * @dataProvider provideGetMetadata
         */
        public function testGetMetadata( $filename, $expected ) {
                $file = $this->dataFile( $filename, 'image/png' );
@@ -86,7 +88,7 @@ class PNGHandlerTest extends MediaWikiTestCase {
 //             $this->assertEquals( unserialize( $expected ), unserialize( $actual ) );
                $this->assertEquals( ( $expected ), ( $actual ) );
        }
-       public function dataGetMetadata() {
+       public static function provideGetMetadata() {
                return array(
                        array( 'rgb-na-png.png', 'a:6:{s:10:"frameCount";i:0;s:9:"loopCount";i:1;s:8:"duration";d:0;s:8:"bitDepth";i:8;s:9:"colorType";s:10:"truecolour";s:8:"metadata";a:1:{s:15:"_MW_PNG_VERSION";i:1;}}' ),
                        array( 'xmp.png', 'a:6:{s:10:"frameCount";i:0;s:9:"loopCount";i:1;s:8:"duration";d:0;s:8:"bitDepth";i:1;s:9:"colorType";s:14:"index-coloured";s:8:"metadata";a:2:{s:12:"SerialNumber";s:9:"123456789";s:15:"_MW_PNG_VERSION";i:1;}}' ), 
index 2116554..007ce46 100644 (file)
@@ -2,19 +2,19 @@
 
 class SVGMetadataExtractorTest extends MediaWikiTestCase {
 
-       function setUp() {
+       protected function setUp() {
                AutoLoader::loadClass( 'SVGMetadataExtractorTest' );
        }
 
        /**
-        * @dataProvider providerSvgFiles
+        * @dataProvider provideSvgFiles
         */
        function testGetMetadata( $infile, $expected ) {
                $this->assertMetadata( $infile, $expected );
        }
        
        /**
-        * @dataProvider providerSvgFilesWithXMLMetadata
+        * @dataProvider provideSvgFilesWithXMLMetadata
         */
        function testGetXMLMetadata( $infile, $expected ) {
                $r = new XMLReader();
@@ -38,7 +38,7 @@ class SVGMetadataExtractorTest extends MediaWikiTestCase {
                }
        }
 
-       function providerSvgFiles() {
+       public static function provideSvgFiles() {
                $base = __DIR__ . '/../../data/media';
                return array(
                        array(
@@ -81,7 +81,7 @@ class SVGMetadataExtractorTest extends MediaWikiTestCase {
                );
        }
 
-       function providerSvgFilesWithXMLMetadata() {
+       public static function provideSvgFilesWithXMLMetadata() {
                $base = __DIR__ . '/../../data/media';
                $metadata = 
     '<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
index 4c79f66..91c35c4 100644 (file)
@@ -1,19 +1,15 @@
 <?php
 class TiffTest extends MediaWikiTestCase {
 
-       public function setUp() {
-               global $wgShowEXIF;
-               $this->showExif = $wgShowEXIF;
-               $wgShowEXIF = true;
+       protected function setUp() {
+               parent::setUp();
+
+               $this->setMwGlobals( 'wgShowEXIF', true );
+
                $this->filePath = __DIR__ . '/../../data/media/';
                $this->handler = new TiffHandler;
        }
 
-       public function tearDown() {
-               global $wgShowEXIF;
-               $wgShowEXIF = $this->showExif;
-       }
-
        public function testInvalidFile() {
                if ( !wfDl( 'exif' ) ) {
                        $this->markTestIncomplete( "This test needs the exif extension." );
index 8198d3b..be02dd7 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 class XMPTest extends MediaWikiTestCase {
 
-       function setUp() {
+       protected function setUp() {
                if ( !wfDl( 'xml' ) ) {
                        $this->markTestSkipped( 'Requires libxml to do XMP parsing' );
                }
@@ -14,7 +14,7 @@ class XMPTest extends MediaWikiTestCase {
         * @param $expected Array expected result of parsing the xmp.
         * @param $info String Short sentence on what's being tested.
         *
-        * @dataProvider dataXMPParse
+        * @dataProvider provideXMPParse
         */
        public function testXMPParse( $xmp, $expected, $info ) {
                if ( !is_string( $xmp ) || !is_array( $expected ) ) {
@@ -25,7 +25,7 @@ class XMPTest extends MediaWikiTestCase {
                $this->assertEquals( $expected, $reader->getResults(), $info, 0.0000000001 );
        }
 
-       public function dataXMPParse() {
+       public static function provideXMPParse() {
                $xmpPath = __DIR__ . '/../../data/xmp/' ;
                $data = array();
 
index e2bb8d8..a2b4e1c 100644 (file)
@@ -2,7 +2,7 @@
 class XMPValidateTest extends MediaWikiTestCase {
 
        /**
-        * @dataProvider providerDate
+        * @dataProvider provideDates
         */
        function testValidateDate( $value, $expected ) {
                // The method should modify $value.
@@ -10,7 +10,7 @@ class XMPValidateTest extends MediaWikiTestCase {
                $this->assertEquals( $expected, $value );
        }
 
-       function providerDate() {
+       public static function provideDates() {
                /* For reference valid date formats are:
                 * YYYY
                 * YYYY-MM
index 0e15653..2588691 100644 (file)
@@ -13,7 +13,7 @@
                $this->assertEquals( $format, $detector->detectFormatName( $userAgent ) );
        }
 
-       public function provideTestFormatName() {
+       public static function provideTestFormatName() {
                return array(
                        array( 'android',   'Mozilla/5.0 (Linux; U; Android 2.1; en-us; Nexus One Build/ERD62) AppleWebKit/530.17 (KHTML, like Gecko) Version/4.0 Mobile Safari/530.17' ),
                        array( 'iphone2',   'Mozilla/5.0 (ipod: U;CPU iPhone OS 2_2 like Mac OS X: es_es) AppleWebKit/525.18.1 (KHTML, like Gecko) Version/3.0 Mobile/3B48b Safari/419.3' ),
index 3164531..46ef5da 100644 (file)
@@ -29,9 +29,10 @@ class MagicVariableTest extends MediaWikiTestCase {
        );
 
        /** setup a basic parser object */
-       function setUp() {
-               global $wgContLang;
-               $wgContLang = Language::factory( 'en' );
+       protected function setUp() {
+               parent::setUp();
+
+               $this->setMwGlobals( 'wgContLang', Language::factory( 'en' ) );
 
                $this->testParser = new Parser();
                $this->testParser->Options( new ParserOptions() );
@@ -47,8 +48,10 @@ class MagicVariableTest extends MediaWikiTestCase {
        }
 
        /** destroy parser (TODO: is it really neded?)*/
-       function tearDown() {
+       protected function tearDown() {
                unset( $this->testParser );
+
+               parent::tearDown();
        }
 
        ############### TESTS #############################################
index ff6e472..848991e 100644 (file)
@@ -9,7 +9,7 @@
  */
 class NewParserTest extends MediaWikiTestCase {
        static protected $articles = array();   // Array of test articles defined by the tests
-       /* The dataProvider is run on a different instance than the test, so it must be static
+       /* The data provider is run on a different instance than the test, so it must be static
         * When running tests from several files, all tests will see all articles.
         */
        static protected $backendToUse;
@@ -31,7 +31,7 @@ class NewParserTest extends MediaWikiTestCase {
 
        protected $file = false;
 
-       function setUp() {
+       protected function setUp() {
                global $wgContLang, $wgNamespaceProtection, $wgNamespaceAliases;
                global $wgHooks, $IP;
                $wgContLang = Language::factory( 'en' );
@@ -100,7 +100,7 @@ class NewParserTest extends MediaWikiTestCase {
                $wgNamespaceAliases['Image_talk'] = NS_FILE_TALK;
        }
 
-       public function tearDown() {
+       protected function tearDown() {
                foreach ( $this->savedInitialGlobals as $var => $val ) {
                        $GLOBALS[$var] = $val;
                }
index dea406c..e0f95b6 100644 (file)
@@ -2,7 +2,7 @@
 
 class ParserMethodsTest extends MediaWikiLangTestCase {
 
-       public function dataPreSaveTransform() {
+       public static function providePreSaveTransform() {
                return array(
                        array( 'hello this is ~~~',
                               "hello this is [[Special:Contributions/127.0.0.1|127.0.0.1]]",
@@ -14,7 +14,7 @@ class ParserMethodsTest extends MediaWikiLangTestCase {
        }
 
        /**
-        * @dataProvider dataPreSaveTransform
+        * @dataProvider providePreSaveTransform
         */
        public function testPreSaveTransform( $text, $expected ) {
                global $wgParser;
index 0e8ef53..d537b6e 100644 (file)
@@ -8,7 +8,7 @@ class ParserPreloadTest extends MediaWikiTestCase {
        private $testParserOptions;
        private $title;
 
-       function setUp() {
+       protected function setUp() {
                $this->testParserOptions = new ParserOptions();
 
                $this->testParser = new Parser();
@@ -18,7 +18,7 @@ class ParserPreloadTest extends MediaWikiTestCase {
                $this->title = Title::newFromText( 'Preload Test' );
        }
 
-       function tearDown() {
+       protected function tearDown() {
                unset( $this->testParser );
                unset( $this->title );
        }
index fee5674..d82fc6c 100644 (file)
@@ -5,7 +5,7 @@ class PreprocessorTest extends MediaWikiTestCase {
        var $mPPNodeCount = 0;
        var $mOptions;
 
-       function setUp() {
+       protected function setUp() {
                global $wgParserConf;
                $this->mOptions = new ParserOptions();
                $name = isset( $wgParserConf['preprocessorClass'] ) ? $wgParserConf['preprocessorClass'] : 'Preprocessor_DOM';
index 957907c..879e41f 100644 (file)
@@ -1,22 +1,17 @@
 <?php
 
 /**
- * This class is not directly tested. Instead it is extended by SearchDbTest.
  * @group Search
  * @group Database
  */
 class SearchEngineTest extends MediaWikiTestCase {
        protected $search, $pageList;
 
-       function tearDown() {
-               unset( $this->search );
-       }
-
        /**
         * Checks for database type & version.
         * Will skip current test if DB does not support search.
         */
-       function setUp() {
+       protected function setUp() {
                parent::setUp();
                // Search tests require MySQL or SQLite with FTS
                # Get database type and version
@@ -33,6 +28,10 @@ class SearchEngineTest extends MediaWikiTestCase {
                $this->search = new $searchType( $this->db );
        }
 
+       protected function tearDown() {
+               unset( $this->search );
+       }
+
        function pageExists( $title ) {
                return false;
        }
@@ -94,7 +93,7 @@ class SearchEngineTest extends MediaWikiTestCase {
                LinkCache::singleton()->clear();
 
                $page = WikiPage::factory( $title );
-               $page->doEdit( $text, $comment, 0, false, $user );
+               $page->doEditContent( ContentHandler::makeContent( $text, $title ), $comment, 0, false, $user );
 
                $this->pageList[] = array( $title, $page->getId() );
 
index 6e49a9a..7d867bc 100644 (file)
@@ -19,7 +19,11 @@ class MockSearch extends SearchEngine {
  * @group Search
  */
 class SearchUpdateTest extends MediaWikiTestCase {
-       static $searchType;
+
+       protected function setUp() {
+               parent::setUp();
+               $this->setMwGlobals( 'wgSearchType', 'MockSearch' );
+       }
 
        function update( $text, $title = 'Test', $id = 1 ) {
                $u = new SearchUpdate( $id, $title, $text );
@@ -33,19 +37,6 @@ class SearchUpdateTest extends MediaWikiTestCase {
                return $resultText;
        }
 
-       function setUp() {
-               global $wgSearchType;
-
-               self::$searchType  = $wgSearchType;
-               $wgSearchType = 'MockSearch';
-       }
-
-       function tearDown() {
-               global $wgSearchType;
-
-               $wgSearchType = self::$searchType;
-       }
-
        function testUpdateText() {
                $this->assertEquals(
                        'test',
index 2e4f4b0..f102309 100644 (file)
@@ -14,7 +14,7 @@ class SpecialRecentchangesTest extends MediaWikiTestCase {
         */
        protected $rc;
 
-       function setUp() {
+       protected function setUp() {
        }
 
        /** helper to test SpecialRecentchanges::buildMainQueryConds() */
@@ -120,7 +120,7 @@ class SpecialRecentchangesTest extends MediaWikiTestCase {
         * Provides associated namespaces to test recent changes
         * namespaces association filtering.
         */
-       public function provideNamespacesAssociations() {
+       public static function provideNamespacesAssociations() {
                return array( # (NS => Associated_NS)
                        array( NS_MAIN, NS_TALK),
                        array( NS_TALK, NS_MAIN),
index 20e42a6..b2063eb 100644 (file)
@@ -10,9 +10,6 @@
 class SpecialSearchTest extends MediaWikiTestCase {
        private $search;
 
-       function setUp() { }
-       function tearDown() { }
-
        /**
         * @covers SpecialSearch::load
         * @dataProvider provideSearchOptionsTests
index 3093334..59663ba 100644 (file)
@@ -6,7 +6,7 @@
  */
 class UploadFromUrlTest extends ApiTestCase {
 
-       public function setUp() {
+       protected function setUp() {
                global $wgEnableUploads, $wgAllowCopyUploads, $wgAllowAsyncCopyUploads;
                parent::setUp();
 
index 66fafaa..857aef5 100644 (file)
@@ -8,7 +8,7 @@ class UploadStashTest extends MediaWikiTestCase {
         */
        public static $users;
 
-       public function setUp() {
+       protected function setUp() {
                parent::setUp();
 
                // Setup a file for bug 29408
@@ -31,6 +31,18 @@ class UploadStashTest extends MediaWikiTestCase {
                );
        }
 
+       protected function tearDown() {
+               if ( file_exists( $this->bug29408File . "." ) ) {
+                       unlink( $this->bug29408File . "." );
+               }
+
+               if ( file_exists( $this->bug29408File ) ) {
+                       unlink( $this->bug29408File );
+               }
+
+               parent::tearDown();
+       }
+
        public function testBug29408() {
                global $wgUser;
                $wgUser = self::$users['uploader']->user;
@@ -62,16 +74,4 @@ class UploadStashTest extends MediaWikiTestCase {
                $request = new FauxRequest( array( 'wpFileKey' => 'testkey-test.test', 'wpSessionKey' => 'foo') );
                $this->assertTrue( UploadFromStash::isValidRequest($request), 'Check key precedence' );
        }
-
-       public function tearDown() {
-               parent::tearDown();
-
-               if( file_exists( $this->bug29408File . "." ) ) {
-                       unlink( $this->bug29408File . "." );
-               }
-
-               if( file_exists( $this->bug29408File ) ) {
-                       unlink( $this->bug29408File );
-               }
-       }
 }
index 6948f5b..d757734 100644 (file)
@@ -6,7 +6,7 @@ class UploadTest extends MediaWikiTestCase {
        protected $upload;
 
 
-       function setUp() {
+       protected function setUp() {
                global $wgHooks;
                parent::setUp();
 
@@ -17,7 +17,7 @@ class UploadTest extends MediaWikiTestCase {
                };
        }
 
-       function tearDown() {
+       protected function tearDown() {
                global $wgHooks;
                $wgHooks = $this->hooks;
        }
@@ -27,7 +27,7 @@ class UploadTest extends MediaWikiTestCase {
         * First checks the return code
         * of UploadBase::getTitle() and then the actual returned title
         * 
-        * @dataProvider dataTestTitleValidation
+        * @dataProvider provideTestTitleValidation
         */
        public function testTitleValidation( $srcFilename, $dstFilename, $code, $msg ) {
                /* Check the result code */
@@ -46,7 +46,7 @@ class UploadTest extends MediaWikiTestCase {
        /**
         * Test various forms of valid and invalid titles that can be supplied.
         */
-       public function dataTestTitleValidation() {
+       public static function provideTestTitleValidation() {
                return array(
                        /* Test a valid title */
                        array( 'ValidTitle.jpg', 'ValidTitle.jpg', UploadBase::OK, 
index 3a648de..2679088 100644 (file)
@@ -9,10 +9,10 @@
 class LanguageAmTest extends MediaWikiTestCase {
        private $lang;
 
-       function setUp() {
+       protected function setUp() {
                $this->lang = Language::factory( 'Am' );
        }
-       function tearDown() {
+       protected function tearDown() {
                unset( $this->lang );
        }
 
index b23e053..84507c5 100644 (file)
@@ -8,10 +8,10 @@
 class LanguageArTest extends MediaWikiTestCase {
        private $lang;
 
-       function setUp() {
+       protected function setUp() {
                $this->lang = Language::factory( 'Ar' );
        }
-       function tearDown() {
+       protected function tearDown() {
                unset( $this->lang );
        }
 
index 735ccc6..3135ca8 100644 (file)
@@ -9,10 +9,10 @@
 class LanguageBeTest extends MediaWikiTestCase {
        private $lang;
 
-       function setUp() {
+       protected function setUp() {
                $this->lang = Language::factory( 'Be' );
        }
-       function tearDown() {
+       protected function tearDown() {
                unset( $this->lang );
        }
 
index 765cdb8..11aacb6 100644 (file)
@@ -3,10 +3,10 @@
 class LanguageBeTaraskTest extends MediaWikiTestCase {
        private $lang;
 
-       function setUp() {
+       protected function setUp() {
                $this->lang = Language::factory( 'Be-tarask' );
        }
-       function tearDown() {
+       protected function tearDown() {
                unset( $this->lang );
        }
 
index e1e2a13..00f4e13 100644 (file)
@@ -9,10 +9,10 @@
 class LanguageBhTest extends MediaWikiTestCase {
        private $lang;
 
-       function setUp() {
+       protected function setUp() {
                $this->lang = Language::factory( 'Bh' );
        }
-       function tearDown() {
+       protected function tearDown() {
                unset( $this->lang );
        }
 
index b6631c0..39391af 100644 (file)
@@ -9,10 +9,10 @@
 class LanguageBsTest extends MediaWikiTestCase {
        private $lang;
 
-       function setUp() {
+       protected function setUp() {
                $this->lang = Language::factory( 'Bs' );
        }
-       function tearDown() {
+       protected function tearDown() {
                unset( $this->lang );
        }
 
index dda29f9..862de52 100644 (file)
@@ -9,10 +9,10 @@
 class LanguageCsTest extends MediaWikiTestCase {
        private $lang;
 
-       function setUp() {
+       protected function setUp() {
                $this->lang = Language::factory( 'cs' );
        }
-       function tearDown() {
+       protected function tearDown() {
                unset( $this->lang );
        }
 
index f8186d7..5d5d60c 100644 (file)
@@ -9,10 +9,10 @@
 class LanguageCuTest extends MediaWikiTestCase {
        private $lang;
 
-       function setUp() {
+       protected function setUp() {
                $this->lang = Language::factory( 'cu' );
        }
-       function tearDown() {
+       protected function tearDown() {
                unset( $this->lang );
        }
 
index e9f9e41..406a943 100644 (file)
@@ -9,10 +9,10 @@
 class LanguageCyTest extends MediaWikiTestCase {
        private $lang;
 
-       function setUp() {
+       protected function setUp() {
                $this->lang = Language::factory( 'cy' );
        }
-       function tearDown() {
+       protected function tearDown() {
                unset( $this->lang );
        }
 
index ab7f931..516b9ee 100644 (file)
@@ -9,10 +9,10 @@
 class LanguageDsbTest extends MediaWikiTestCase {
        private $lang;
 
-       function setUp() {
+       protected function setUp() {
                $this->lang = Language::factory( 'dsb' );
        }
-       function tearDown() {
+       protected function tearDown() {
                unset( $this->lang );
        }
 
index 8538744..0a29028 100644 (file)
@@ -9,10 +9,10 @@
 class LanguageFrTest extends MediaWikiTestCase {
        private $lang;
 
-       function setUp() {
+       protected function setUp() {
                $this->lang = Language::factory( 'fr' );
        }
-       function tearDown() {
+       protected function tearDown() {
                unset( $this->lang );
        }
 
index fbd9f11..9535cb1 100644 (file)
@@ -9,10 +9,10 @@
 class LanguageGaTest extends MediaWikiTestCase {
        private $lang;
 
-       function setUp() {
+       protected function setUp() {
                $this->lang = Language::factory( 'ga' );
        }
-       function tearDown() {
+       protected function tearDown() {
                unset( $this->lang );
        }
 
index 24574bd..e6bfdd7 100644 (file)
@@ -9,10 +9,10 @@
 class LanguageGdTest extends MediaWikiTestCase {
        private $lang;
 
-       function setUp() {
+       protected function setUp() {
                $this->lang = Language::factory( 'gd' );
        }
-       function tearDown() {
+       protected function tearDown() {
                unset( $this->lang );
        }
 
index 3d298b9..18cf225 100644 (file)
@@ -9,10 +9,10 @@
 class LanguageGvTest extends MediaWikiTestCase {
        private $lang;
 
-       function setUp() {
+       protected function setUp() {
                $this->lang = Language::factory( 'gv' );
        }
-       function tearDown() {
+       protected function tearDown() {
                unset( $this->lang );
        }
 
index 7833da7..6205320 100644 (file)
@@ -9,10 +9,10 @@
 class LanguageHeTest extends MediaWikiTestCase {
        private $lang;
 
-       function setUp() {
+       protected function setUp() {
                $this->lang = Language::factory( 'he' );
        }
-       function tearDown() {
+       protected function tearDown() {
                unset( $this->lang );
        }
 
index ead9e02..bb3b5fa 100644 (file)
@@ -9,10 +9,10 @@
 class LanguageHiTest extends MediaWikiTestCase {
        private $lang;
 
-       function setUp() {
+       protected function setUp() {
                $this->lang = Language::factory( 'Hi' );
        }
-       function tearDown() {
+       protected function tearDown() {
                unset( $this->lang );
        }
 
index 4f1c66b..6f0ca1b 100644 (file)
@@ -9,10 +9,10 @@
 class LanguageHrTest extends MediaWikiTestCase {
        private $lang;
 
-       function setUp() {
+       protected function setUp() {
                $this->lang = Language::factory( 'hr' );
        }
-       function tearDown() {
+       protected function tearDown() {
                unset( $this->lang );
        }
 
index 803c772..d336e81 100644 (file)
@@ -9,10 +9,10 @@
 class LanguageHsbTest extends MediaWikiTestCase {
        private $lang;
 
-       function setUp() {
+       protected function setUp() {
                $this->lang = Language::factory( 'hsb' );
        }
-       function tearDown() {
+       protected function tearDown() {
                unset( $this->lang );
        }
 
index adbd37e..3181868 100644 (file)
@@ -9,10 +9,10 @@
 class LanguageHuTest extends MediaWikiTestCase {
        private $lang;
 
-       function setUp() {
+       protected function setUp() {
                $this->lang = Language::factory( 'Hu' );
        }
-       function tearDown() {
+       protected function tearDown() {
                unset( $this->lang );
        }
 
index 7990bdf..2885707 100644 (file)
@@ -9,10 +9,10 @@
 class LanguageHyTest extends MediaWikiTestCase {
        private $lang;
 
-       function setUp() {
+       protected function setUp() {
                $this->lang = Language::factory( 'hy' );
        }
-       function tearDown() {
+       protected function tearDown() {
                unset( $this->lang );
        }
 
index ab88946..78ff445 100644 (file)
@@ -9,10 +9,10 @@
 class LanguageKshTest extends MediaWikiTestCase {
        private $lang;
 
-       function setUp() {
+       protected function setUp() {
                $this->lang = Language::factory( 'ksh' );
        }
-       function tearDown() {
+       protected function tearDown() {
                unset( $this->lang );
        }
 
index 0fd9167..60b4945 100644 (file)
@@ -9,10 +9,10 @@
 class LanguageLnTest extends MediaWikiTestCase {
        private $lang;
 
-       function setUp() {
+       protected function setUp() {
                $this->lang = Language::factory( 'ln' );
        }
-       function tearDown() {
+       protected function tearDown() {
                unset( $this->lang );
        }
 
index 0d7c7d3..821e028 100644 (file)
@@ -9,10 +9,10 @@
 class LanguageLtTest extends MediaWikiTestCase {
        private $lang;
 
-       function setUp() {
+       protected function setUp() {
                $this->lang = Language::factory( 'Lt' );
        }
-       function tearDown() {
+       protected function tearDown() {
                unset( $this->lang );
        }
 
index 0636da5..bb0dc6c 100644 (file)
@@ -9,10 +9,10 @@
 class LanguageLvTest extends MediaWikiTestCase {
        private $lang;
 
-       function setUp() {
+       protected function setUp() {
                $this->lang = Language::factory( 'lv' );
        }
-       function tearDown() {
+       protected function tearDown() {
                unset( $this->lang );
        }
 
index 06b5654..d6ac643 100644 (file)
@@ -9,10 +9,10 @@
 class LanguageMgTest extends MediaWikiTestCase {
        private $lang;
 
-       function setUp() {
+       protected function setUp() {
                $this->lang = Language::factory( 'mg' );
        }
-       function tearDown() {
+       protected function tearDown() {
                unset( $this->lang );
        }
 
index cf5ec3d..21a9e41 100644 (file)
@@ -9,10 +9,10 @@
 class LanguageMkTest extends MediaWikiTestCase {
        private $lang;
 
-       function setUp() {
+       protected function setUp() {
                $this->lang = Language::factory( 'mk' );
        }
-       function tearDown() {
+       protected function tearDown() {
                unset( $this->lang );
        }
 
index 8c4b0b2..9af2389 100644 (file)
@@ -9,10 +9,10 @@
 class LanguageMlTest extends MediaWikiTestCase {
        private $lang;
 
-       function setUp() {
+       protected function setUp() {
                $this->lang = Language::factory( 'Ml' );
        }
-       function tearDown() {
+       protected function tearDown() {
                unset( $this->lang );
        }
 
index 533e590..4f208d9 100644 (file)
@@ -9,10 +9,10 @@
 class LanguageMoTest extends MediaWikiTestCase {
        private $lang;
 
-       function setUp() {
+       protected function setUp() {
                $this->lang = Language::factory( 'mo' );
        }
-       function tearDown() {
+       protected function tearDown() {
                unset( $this->lang );
        }
 
index 421bb38..4d25347 100644 (file)
@@ -9,10 +9,10 @@
 class LanguageMtTest extends MediaWikiTestCase {
        private $lang;
 
-       function setUp() {
+       protected function setUp() {
                $this->lang = Language::factory( 'mt' );
        }
-       function tearDown() {
+       protected function tearDown() {
                unset( $this->lang );
        }
 
index cf979cd..c8cfbb6 100644 (file)
@@ -9,10 +9,10 @@
 class LanguageNlTest extends MediaWikiTestCase {
        private $lang;
 
-       function setUp() {
+       protected function setUp() {
                $this->lang = Language::factory( 'Nl' );
        }
-       function tearDown() {
+       protected function tearDown() {
                unset( $this->lang );
        }
 
index ea39362..1e70997 100644 (file)
@@ -9,10 +9,10 @@
 class LanguageNsoTest extends MediaWikiTestCase {
        private $lang;
 
-       function setUp() {
+       protected function setUp() {
                $this->lang = Language::factory( 'nso' );
        }
-       function tearDown() {
+       protected function tearDown() {
                unset( $this->lang );
        }
 
index e56d4b7..a543640 100644 (file)
@@ -9,10 +9,10 @@
 class LanguagePlTest extends MediaWikiTestCase {
        private $lang;
 
-       function setUp() {
+       protected function setUp() {
                $this->lang = Language::factory( 'pl' );
        }
-       function tearDown() {
+       protected function tearDown() {
                unset( $this->lang );
        }
 
index 5270f6f..9bf01a4 100644 (file)
@@ -9,10 +9,10 @@
 class LanguageRoTest extends MediaWikiTestCase {
        private $lang;
 
-       function setUp() {
+       protected function setUp() {
                $this->lang = Language::factory( 'ro' );
        }
-       function tearDown() {
+       protected function tearDown() {
                unset( $this->lang );
        }
 
index 7a1f193..189a96c 100644 (file)
 class LanguageRuTest extends MediaWikiTestCase {
        private $lang;
 
-       function setUp() {
+       protected function setUp() {
                $this->lang = Language::factory( 'ru' );
        }
-       function tearDown() {
+       protected function tearDown() {
                unset( $this->lang );
        }
 
index 065ec29..51cfd2f 100644 (file)
@@ -9,10 +9,10 @@
 class LanguageSeTest extends MediaWikiTestCase {
        private $lang;
 
-       function setUp() {
+       protected function setUp() {
                $this->lang = Language::factory( 'se' );
        }
-       function tearDown() {
+       protected function tearDown() {
                unset( $this->lang );
        }
 
index 931c82f..62f2775 100644 (file)
@@ -9,10 +9,10 @@
 class LanguageSgsTest extends MediaWikiTestCase {
        private $lang;
 
-       function setUp() {
+       protected function setUp() {
                $this->lang = Language::factory( 'Sgs' );
        }
-       function tearDown() {
+       protected function tearDown() {
                unset( $this->lang );
        }
 
index b8169ae..1d8ae7c 100644 (file)
@@ -9,10 +9,10 @@
 class LanguageShTest extends MediaWikiTestCase {
        private $lang;
 
-       function setUp() {
+       protected function setUp() {
                $this->lang = Language::factory( 'sh' );
        }
-       function tearDown() {
+       protected function tearDown() {
                unset( $this->lang );
        }
 
index 4cfd840..25823cf 100644 (file)
 class LanguageSkTest extends MediaWikiTestCase {
        private $lang;
 
-       function setUp() {
+       protected function setUp() {
                $this->lang = Language::factory( 'sk' );
        }
-       function tearDown() {
+       protected function tearDown() {
                unset( $this->lang );
        }
 
index c1f7569..76de0fc 100644 (file)
 class LanguageSlTest extends MediaWikiTestCase {
        private $lang;
 
-       function setUp() {
+       protected function setUp() {
                $this->lang = Language::factory( 'sl' );
        }
-       function tearDown() {
+       protected function tearDown() {
                unset( $this->lang );
        }
 
index b7e72e9..cf14477 100644 (file)
@@ -9,10 +9,10 @@
 class LanguageSmaTest extends MediaWikiTestCase {
        private $lang;
 
-       function setUp() {
+       protected function setUp() {
                $this->lang = Language::factory( 'sma' );
        }
-       function tearDown() {
+       protected function tearDown() {
                unset( $this->lang );
        }
 
index d44ecf8..7be4589 100644 (file)
@@ -19,10 +19,10 @@ class LanguageSrTest extends MediaWikiTestCase {
        /* Language object. Initialized before each test */
        private $lang;
 
-       function setUp() {
+       protected function setUp() {
                $this->lang = Language::factory( 'sr' );
        }
-       function tearDown() {
+       protected function tearDown() {
                unset( $this->lang );
        }
 
index bfb45c7..621c891 100644 (file)
@@ -7,10 +7,10 @@ class LanguageTest extends MediaWikiTestCase {
         */
        private $lang;
 
-       function setUp() {
+       protected function setUp() {
                $this->lang = Language::factory( 'en' );
        }
-       function tearDown() {
+       protected function tearDown() {
                unset( $this->lang );
        }
 
index 4bfaa00..175cdb9 100644 (file)
@@ -9,10 +9,10 @@
 class LanguageTiTest extends MediaWikiTestCase {
        private $lang;
 
-       function setUp() {
+       protected function setUp() {
                $this->lang = Language::factory( 'Ti' );
        }
-       function tearDown() {
+       protected function tearDown() {
                unset( $this->lang );
        }
 
index a1facd1..e03531f 100644 (file)
@@ -9,10 +9,10 @@
 class LanguageTlTest extends MediaWikiTestCase {
        private $lang;
 
-       function setUp() {
+       protected function setUp() {
                $this->lang = Language::factory( 'Tl' );
        }
-       function tearDown() {
+       protected function tearDown() {
                unset( $this->lang );
        }
 
index bda4c9d..a3cacc2 100644 (file)
@@ -9,10 +9,10 @@
 class LanguageTrTest extends MediaWikiTestCase {
        private $lang;
 
-       function setUp() {
+       protected function setUp() {
                $this->lang = Language::factory( 'Tr' );
        }
-       function tearDown() {
+       protected function tearDown() {
                unset( $this->lang );
        }
 
index 60fafb0..2b6f707 100644 (file)
 class LanguageUkTest extends MediaWikiTestCase {
        private $lang;
 
-       function setUp() {
+       protected function setUp() {
                $this->lang = Language::factory( 'Uk' );
        }
-       function tearDown() {
+       protected function tearDown() {
                unset( $this->lang );
        }
 
index 7238728..0314649 100644 (file)
@@ -19,10 +19,10 @@ class LanguageUzTest extends MediaWikiTestCase {
        /* Language object. Initialized before each test */
        private $lang;
 
-       function setUp() {
+       protected function setUp() {
                $this->lang = Language::factory( 'uz' );
        }
-       function tearDown() {
+       protected function tearDown() {
                unset( $this->lang );
        }
 
index 172f19b..08312ce 100644 (file)
@@ -9,10 +9,10 @@
 class LanguageWaTest extends MediaWikiTestCase {
        private $lang;
 
-       function setUp() {
+       protected function setUp() {
                $this->lang = Language::factory( 'Wa' );
        }
-       function tearDown() {
+       protected function tearDown() {
                unset( $this->lang );
        }
 
index d134438..0f3a6a1 100644 (file)
@@ -35,7 +35,7 @@ abstract class DumpTestCase extends MediaWikiLangTestCase {
         * @throws MWExcepion
         */
        protected function addRevision( Page $page, $text, $summary ) {
-               $status = $page->doEdit( $text, $summary );
+               $status = $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ), $summary );
                if ( $status->isGood() ) {
                        $value = $status->getValue();
                        $revision = $value['revision'];
@@ -72,7 +72,7 @@ abstract class DumpTestCase extends MediaWikiLangTestCase {
         *
         * Clears $wgUser, and reports errors from addDBData to PHPUnit
         */
-       public function setUp() {
+       protected function setUp() {
                global $wgUser;
 
                parent::setUp();
@@ -295,9 +295,12 @@ abstract class DumpTestCase extends MediaWikiLangTestCase {
         * @param $text_sha1 string: the base36 SHA-1 of the revision's text
         * @param $text string|false: (optional) The revision's string, or false to check for a
         *            revision stub
+        * @param $model String: the expected content model id (default: CONTENT_MODEL_WIKITEXT)
+        * @param $format String: the expected format model id (default: CONTENT_FORMAT_WIKITEXT)
         * @param $parentid int|false: (optional) id of the parent revision
         */
-       protected function assertRevision( $id, $summary, $text_id, $text_bytes, $text_sha1, $text = false, $parentid = false ) {
+       protected function assertRevision( $id, $summary, $text_id, $text_bytes, $text_sha1, $text = false, $parentid = false,
+                                               $model = CONTENT_MODEL_WIKITEXT, $format = CONTENT_FORMAT_WIKITEXT ) {
 
                $this->assertNodeStart( "revision" );
                $this->skipWhitespace();
@@ -315,9 +318,33 @@ abstract class DumpTestCase extends MediaWikiLangTestCase {
                $this->skipWhitespace();
 
                $this->assertTextNode( "comment", $summary );
+               $this->skipWhitespace();
+
+               if ( $this->xml->name == "text" ) {
+                       // note: <text> tag may occur here or at the very end.
+                       $text_found = true;
+                       $this->assertText( $id, $text_id, $text_bytes, $text );
+               } else {
+                       $text_found = false;
+               }
 
                $this->assertTextNode( "sha1", $text_sha1 );
 
+               $this->assertTextNode( "model", $model );
+               $this->skipWhitespace();
+
+               $this->assertTextNode( "format", $format );
+               $this->skipWhitespace();
+
+               if ( !$text_found ) {
+                       $this->assertText( $id, $text_id, $text_bytes, $text );
+               }
+
+               $this->assertNodeEnd( "revision" );
+               $this->skipWhitespace();
+       }
+
+       protected function assertText( $id, $text_id, $text_bytes, $text ) {
                $this->assertNodeStart( "text", false );
                if ( $text_bytes !== false ) {
                        $this->assertEquals( $this->xml->getAttribute( "bytes" ), $text_bytes,
@@ -344,9 +371,5 @@ abstract class DumpTestCase extends MediaWikiLangTestCase {
                        $this->assertNodeEnd( "text" );
                        $this->skipWhitespace();
                }
-
-               $this->assertNodeEnd( "revision" );
-               $this->skipWhitespace();
        }
-
 }
index 8ff8557..a029c92 100644 (file)
@@ -156,7 +156,7 @@ class BaseDumpTest extends MediaWikiTestCase {
   <siteinfo>
     <sitename>wikisvn</sitename>
     <base>http://localhost/wiki-svn/index.php/Main_Page</base>
-    <generator>MediaWiki 1.20alpha</generator>
+    <generator>MediaWiki 1.21alpha</generator>
     <case>first-letter</case>
     <namespaces>
       <namespace key="-2" case="first-letter">Media</namespace>
@@ -199,6 +199,8 @@ class BaseDumpTest extends MediaWikiTestCase {
       <comment>BackupDumperTestP1Summary1</comment>
       <sha1>0bolhl6ol7i6x0e7yq91gxgaan39j87</sha1>
       <text xml:space="preserve">BackupDumperTestP1Text1</text>
+      <model name="wikitext">1</model>
+      <format mime="text/x-wiki">1</format>
     </revision>
   </page>
 ';
@@ -216,6 +218,8 @@ class BaseDumpTest extends MediaWikiTestCase {
       <comment>BackupDumperTestP2Summary1</comment>
       <sha1>jprywrymfhysqllua29tj3sc7z39dl2</sha1>
       <text xml:space="preserve">BackupDumperTestP2Text1</text>
+      <model name="wikitext">1</model>
+      <format mime="text/x-wiki">1</format>
     </revision>
     <revision>
       <id>5</id>
@@ -227,6 +231,8 @@ class BaseDumpTest extends MediaWikiTestCase {
       <comment>BackupDumperTestP2Summary4 extra</comment>
       <sha1>6o1ciaxa6pybnqprmungwofc4lv00wv</sha1>
       <text xml:space="preserve">BackupDumperTestP2Text4 some additional Text</text>
+      <model name="wikitext">1</model>
+      <format mime="text/x-wiki">1</format>
     </revision>
   </page>
 ';
@@ -243,6 +249,8 @@ class BaseDumpTest extends MediaWikiTestCase {
       </contributor>
       <comment>Talk BackupDumperTestP1 Summary1</comment>
       <sha1>nktofwzd0tl192k3zfepmlzxoax1lpe</sha1>
+      <model name="wikitext">1</model>
+      <format mime="text/x-wiki">1</format>
       <text xml:space="preserve">Talk about BackupDumperTestP1 Text1</text>
     </revision>
   </page>
index a0bbadf..7072299 100644 (file)
@@ -74,7 +74,7 @@ class TextPassDumperTest extends DumpTestCase {
 
        }
 
-       public function setUp() {
+       protected function setUp() {
                parent::setUp();
 
                // Since we will restrict dumping by page ranges (to allow
@@ -383,7 +383,7 @@ class TextPassDumperTest extends DumpTestCase {
                $this->assertEmpty( $files, "Remaining unchecked files" );
 
                // ... and have dealt with more than one checkpoint file
-               $this->assertGreaterThan( 1, $checkpointFiles, "# of checkpoint files" );
+               $this->assertGreaterThan( 1, $checkpointFiles, "expected more than 1 checkpoint to have been created. Checkpoint interval is $checkpointAfter seconds, maybe your computer is too fast?" );
 
                $this->expectETAOutput();
        }
@@ -438,7 +438,7 @@ class TextPassDumperTest extends DumpTestCase {
   <siteinfo>
     <sitename>wikisvn</sitename>
     <base>http://localhost/wiki-svn/index.php/Main_Page</base>
-    <generator>MediaWiki 1.20alpha</generator>
+    <generator>MediaWiki 1.21alpha</generator>
     <case>first-letter</case>
     <namespaces>
       <namespace key="-2" case="first-letter">Media</namespace>
@@ -481,6 +481,8 @@ class TextPassDumperTest extends DumpTestCase {
       </contributor>
       <comment>BackupDumperTestP1Summary1</comment>
       <sha1>0bolhl6ol7i6x0e7yq91gxgaan39j87</sha1>
+      <model>wikitext</model>
+      <format>text/x-wiki</format>
       <text id="' . $this->textId1_1 . '" bytes="23" />
     </revision>
   </page>
@@ -497,6 +499,8 @@ class TextPassDumperTest extends DumpTestCase {
       </contributor>
       <comment>BackupDumperTestP2Summary1</comment>
       <sha1>jprywrymfhysqllua29tj3sc7z39dl2</sha1>
+      <model>wikitext</model>
+      <format>text/x-wiki</format>
       <text id="' . $this->textId2_1 . '" bytes="23" />
     </revision>
     <revision>
@@ -508,6 +512,8 @@ class TextPassDumperTest extends DumpTestCase {
       </contributor>
       <comment>BackupDumperTestP2Summary2</comment>
       <sha1>b7vj5ks32po5m1z1t1br4o7scdwwy95</sha1>
+      <model>wikitext</model>
+      <format>text/x-wiki</format>
       <text id="' . $this->textId2_2 . '" bytes="23" />
     </revision>
     <revision>
@@ -519,6 +525,8 @@ class TextPassDumperTest extends DumpTestCase {
       </contributor>
       <comment>BackupDumperTestP2Summary3</comment>
       <sha1>jfunqmh1ssfb8rs43r19w98k28gg56r</sha1>
+      <model>wikitext</model>
+      <format>text/x-wiki</format>
       <text id="' . $this->textId2_3 . '" bytes="23" />
     </revision>
     <revision>
@@ -530,6 +538,8 @@ class TextPassDumperTest extends DumpTestCase {
       </contributor>
       <comment>BackupDumperTestP2Summary4 extra</comment>
       <sha1>6o1ciaxa6pybnqprmungwofc4lv00wv</sha1>
+      <model>wikitext</model>
+      <format>text/x-wiki</format>
       <text id="' . $this->textId2_4 . '" bytes="44" />
     </revision>
   </page>
@@ -548,6 +558,8 @@ class TextPassDumperTest extends DumpTestCase {
       </contributor>
       <comment>Talk BackupDumperTestP1 Summary1</comment>
       <sha1>nktofwzd0tl192k3zfepmlzxoax1lpe</sha1>
+      <model>wikitext</model>
+      <format>text/x-wiki</format>
       <text id="' . $this->textId4_1 . '" bytes="35" />
     </revision>
   </page>
index 925e277..64374f8 100644 (file)
@@ -65,7 +65,7 @@ class BackupDumperPageTest extends DumpTestCase {
 
        }
 
-       public function setUp() {
+       protected function setUp() {
                parent::setUp();
 
                // Since we will restrict dumping by page ranges (to allow
index e7ffa01..fd6db0a 100644 (file)
@@ -111,7 +111,7 @@ class FetchTextTest extends MediaWikiTestCase {
         * @throws MWExcepion
         */
        private function addRevision( $page, $text, $summary ) {
-               $status = $page->doEdit( $text, $summary );
+               $status = $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ), $summary );
                if ( $status->isGood() ) {
                        $value = $status->getValue();
                        $revision = $value['revision'];
index 912d760..89337f4 100644 (file)
@@ -33,13 +33,13 @@ class SideBarTest extends MediaWikiLangTestCase {
                }
        }
 
-       function setUp() {
+       protected function setUp() {
                parent::setUp();
                $this->initMessagesHref();
                $this->skin = new SkinTemplate();
                $this->skin->getContext()->setLanguage( Language::factory( 'en' ) );
        }
-       function tearDown() {
+       protected function tearDown() {
                parent::tearDown();
                $this->skin = null;
        }
index f263811..843aaf9 100644 (file)
@@ -15,7 +15,7 @@ class UploadFromUrlTestSuite extends PHPUnit_Framework_TestSuite {
                return true;
        }
 
-       function setUp() {
+       protected function setUp() {
                global $wgParser, $wgParserConf, $IP, $messageMemc, $wgMemc,
                          $wgUser, $wgLang, $wgOut, $wgRequest, $wgStyleDirectory, $wgEnableParserCache,
                          $wgNamespaceAliases, $wgNamespaceProtection, $parserMemc;
@@ -79,7 +79,7 @@ class UploadFromUrlTestSuite extends PHPUnit_Framework_TestSuite {
                FileBackendGroup::destroySingleton();
        }
 
-       public function tearDown() {
+       protected function tearDown() {
                foreach ( $this->savedGlobals as $var => $val ) {
                        $GLOBALS[$var] = $val;
                }
index 59ae73c..be5f4ac 100644 (file)
@@ -19,6 +19,7 @@ return array(
                        'tests/qunit/suites/resources/jquery/jquery.tabIndex.test.js',
                        'tests/qunit/suites/resources/jquery/jquery.tablesorter.test.js',
                        'tests/qunit/suites/resources/jquery/jquery.textSelection.test.js',
+                       'tests/qunit/data/mediawiki.jqueryMsg.data.js',
                        'tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js',
                        'tests/qunit/suites/resources/mediawiki/mediawiki.jscompat.test.js',
                        'tests/qunit/suites/resources/mediawiki/mediawiki.test.js',
diff --git a/tests/qunit/data/generateJqueryMsgData.php b/tests/qunit/data/generateJqueryMsgData.php
new file mode 100644 (file)
index 0000000..7079e0e
--- /dev/null
@@ -0,0 +1,149 @@
+<?php
+/**
+ * This PHP script defines the spec that the mediawiki.jqueryMsg module should conform to.
+ *
+ * It does this by looking up the results of various kinds of string parsing, with various
+ * languages, in the current installation of MediaWiki. It then outputs a static specification,
+ * mapping expected inputs to outputs, which can be used fed into a unit test framework.
+ * (QUnit, Jasmine, anything, it just outputs an object with key/value pairs).
+ *
+ * This is similar to Michael Dale (mdale@mediawiki.org)'s parser tests, except that it doesn't
+ * look up the API results while doing the test, so the test run is much faster (at the cost
+ * of being out of date in rare circumstances. But mostly the parsing that we are doing in
+ * Javascript doesn't change much).
+ */
+
+/*
+ * @example QUnit
+ * <code>
+       QUnit.test( 'Output matches PHP parser', mw.libs.phpParserData.tests.length, function ( assert ) {
+               mw.messages.set( mw.libs.phpParserData.messages );
+               $.each( mw.libs.phpParserData.tests, function ( i, test ) {
+                       QUnit.stop();
+                       getMwLanguage( test.lang, function ( langClass ) {
+                               var parser = new mw.jqueryMsg.parser( { language: langClass } );
+                               assert.equal(
+                                       parser.parse( test.key, test.args ).html(),
+                                       test.result,
+                                       test.name
+                               );
+                               QUnit.start();
+                       } );
+               } );
+       });
+ * </code>
+ *
+ * @example Jasmine
+ * <code>
+       describe( 'match output to output from PHP parser', function () {
+               mw.messages.set( mw.libs.phpParserData.messages );
+               $.each( mw.libs.phpParserData.tests, function ( i, test ) {
+                       it( 'should parse ' + test.name, function () {
+                               var langClass;
+                               runs( function () {
+                                       getMwLanguage( test.lang, function ( gotIt ) {
+                                               langClass = gotIt;
+                                       });
+                               });
+                               waitsFor( function () {
+                                       return langClass !== undefined;
+                               }, 'Language class should be loaded', 1000 );
+                               runs( function () {
+                                       console.log( test.lang, 'running tests' );
+                                       var parser = new mw.jqueryMsg.parser( { language: langClass } );
+                                       expect(
+                                               parser.parse( test.key, test.args ).html()
+                                       ).toEqual( test.result );
+                               } );
+                       } );
+               } );
+       } );
+ * </code>
+ */
+
+$maintenanceDir = dirname( dirname( dirname( __DIR__ ) ) ) . '/maintenance';
+
+require( "$maintenanceDir/Maintenance.php" );
+
+class GenerateJqueryMsgData extends Maintenance {
+
+       static $keyToTestArgs = array(
+               'undelete_short' => array(
+                       array( 0 ),
+                       array( 1 ),
+                       array( 2 ),
+                       array( 5 ),
+                       array( 21 ),
+                       array( 101 )
+               ),
+               'category-subcat-count' => array(
+                       array( 0, 10 ),
+                       array( 1, 1 ),
+                       array( 1, 2 ),
+                       array( 3, 30 )
+               )
+       );
+
+       public function __construct() {
+                       parent::__construct();
+                       $this->mDescription = 'Create a specification for message parsing ini JSON format';
+                       // add any other options here
+       }
+
+       public function execute() {
+               list( $messages, $tests ) = $this->getMessagesAndTests();
+               $this->writeJavascriptFile( $messages, $tests, __DIR__ . '/mediawiki.jqueryMsg.data.js' );
+       }
+
+       private function getMessagesAndTests() {
+               $messages = array();
+               $tests = array();
+               foreach ( array( 'en', 'fr', 'ar', 'jp', 'zh' ) as $languageCode ) {
+                       foreach ( self::$keyToTestArgs as $key => $testArgs ) {
+                               foreach ($testArgs as $args) {
+                                       // Get the raw message, without any transformations.
+                                       $template = wfMessage( $key )->inLanguage( $languageCode )->plain();
+
+                                       // Get the magic-parsed version with args.
+                                       $result = wfMessage( $key, $args )->inLanguage( $languageCode )->text();
+
+                                       // Record the template, args, language, and expected result
+                                       // fake multiple languages by flattening them together.
+                                       $langKey = $languageCode . '_' . $key;
+                                       $messages[$langKey] = $template;
+                                       $tests[] = array(
+                                               'name' => $languageCode . ' ' . $key . ' ' . join( ',', $args ),
+                                               'key' => $langKey,
+                                               'args' => $args,
+                                               'result' => $result,
+                                               'lang' => $languageCode
+                                       );
+                               }
+                       }
+               }
+               return array( $messages, $tests );
+       }
+
+       private function writeJavascriptFile( $messages, $tests, $dataSpecFile ) {
+               $phpParserData = array(
+                       'messages' => $messages,
+                       'tests' => $tests,
+               );
+
+               $output =
+                       "// This file stores the output from the PHP parser for various messages, arguments,\n"
+                       . "// languages, and parser modes. Intended for use by a unit test framework by looping\n"
+                       . "// through the object and comparing its parser return value with the 'result' property.\n"
+                       . '// Last generated with ' . basename( __FILE__ ) . ' at ' . gmdate( 'r' ) . "\n"
+                       . "\n"
+                       . 'mediaWiki.libs.phpParserData = ' . FormatJson::encode( $phpParserData, true ) . ";\n";
+
+               $fp = file_put_contents( $dataSpecFile, $output );
+               if ( $fp === false ) {
+                       die( "Couldn't write to $dataSpecFile." );
+               }
+       }
+}
+
+$maintClass = "GenerateJqueryMsgData";
+require_once( "$maintenanceDir/doMaintenance.php" );
diff --git a/tests/qunit/data/mediawiki.jqueryMsg.data.js b/tests/qunit/data/mediawiki.jqueryMsg.data.js
new file mode 100644 (file)
index 0000000..05bb5c8
--- /dev/null
@@ -0,0 +1,491 @@
+// This file stores the output from the PHP parser for various messages, arguments,
+// languages, and parser modes. Intended for use by a unit test framework by looping
+// through the object and comparing its parser return value with the 'result' property.
+// Last generated with generateJqueryMsgData.php at Sun, 07 Oct 2012 07:30:16 +0000
+
+mediaWiki.libs.phpParserData = {
+       "messages": {
+               "en_undelete_short": "Undelete {{PLURAL:$1|one edit|$1 edits}}",
+               "en_category-subcat-count": "{{PLURAL:$2|This category has only the following subcategory.|This category has the following {{PLURAL:$1|subcategory|$1 subcategories}}, out of $2 total.}}",
+               "fr_undelete_short": "Restaurer $1 modification{{PLURAL:$1||s}}",
+               "fr_category-subcat-count": "Cette cat\u00e9gorie comprend {{PLURAL:$2|la sous-cat\u00e9gorie|$2 sous-cat\u00e9gories, dont {{PLURAL:$1|celle|les $1}}}} ci-dessous.",
+               "ar_undelete_short": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 {{PLURAL:$1|\u062a\u0639\u062f\u064a\u0644 \u0648\u0627\u062d\u062f|\u062a\u0639\u062f\u064a\u0644\u064a\u0646|$1 \u062a\u0639\u062f\u064a\u0644\u0627\u062a|$1 \u062a\u0639\u062f\u064a\u0644|$1 \u062a\u0639\u062f\u064a\u0644\u0627}}",
+               "ar_category-subcat-count": "{{PLURAL:$2|\u0644\u0627 \u062a\u0635\u0627\u0646\u064a\u0641 \u0641\u0631\u0639\u064a\u0629 \u0641\u064a \u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641|\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0641\u064a\u0647 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0627\u0644\u0641\u0631\u0639\u064a \u0627\u0644\u062a\u0627\u0644\u064a \u0641\u0642\u0637.|\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0641\u064a\u0647 {{PLURAL:$1||\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0627\u0644\u0641\u0631\u0639\u064a|\u0647\u0630\u064a\u0646 \u0627\u0644\u062a\u0635\u0646\u064a\u0641\u064a\u0646 \u0627\u0644\u0641\u0631\u0639\u064a\u064a\u0646|\u0647\u0630\u0647 \u0627\u0644$1 \u062a\u0635\u0627\u0646\u064a\u0641 \u0627\u0644\u0641\u0631\u0639\u064a\u0629|\u0647\u0630\u0647 \u0627\u0644$1 \u062a\u0635\u0646\u064a\u0641\u0627 \u0641\u0631\u0639\u064a\u0627|\u0647\u0630\u0647 \u0627\u0644$1 \u062a\u0635\u0646\u064a\u0641 \u0641\u0631\u0639\u064a}}\u060c \u0645\u0646 \u0625\u062c\u0645\u0627\u0644\u064a $2.}}",
+               "jp_undelete_short": "Undelete {{PLURAL:$1|one edit|$1 edits}}",
+               "jp_category-subcat-count": "{{PLURAL:$2|This category has only the following subcategory.|This category has the following {{PLURAL:$1|subcategory|$1 subcategories}}, out of $2 total.}}",
+               "zh_undelete_short": "\u6062\u590d$1\u4e2a\u88ab\u5220\u9664\u7684\u7f16\u8f91",
+               "zh_category-subcat-count": "{{PLURAL:$2|\u672c\u5206\u7c7b\u53ea\u6709\u4e0b\u5217\u4e00\u4e2a\u5b50\u5206\u7c7b\u3002|\u672c\u5206\u7c7b\u5305\u542b\u4e0b\u5217$1\u4e2a\u5b50\u5206\u7c7b\uff0c\u5171$2\u4e2a\u5b50\u5206\u7c7b\u3002}}"
+       },
+       "tests": [
+               {
+                       "name": "en undelete_short 0",
+                       "key": "en_undelete_short",
+                       "args": [
+                               0
+                       ],
+                       "result": "Undelete 0 edits",
+                       "lang": "en"
+               },
+               {
+                       "name": "en undelete_short 1",
+                       "key": "en_undelete_short",
+                       "args": [
+                               1
+                       ],
+                       "result": "Undelete one edit",
+                       "lang": "en"
+               },
+               {
+                       "name": "en undelete_short 2",
+                       "key": "en_undelete_short",
+                       "args": [
+                               2
+                       ],
+                       "result": "Undelete 2 edits",
+                       "lang": "en"
+               },
+               {
+                       "name": "en undelete_short 5",
+                       "key": "en_undelete_short",
+                       "args": [
+                               5
+                       ],
+                       "result": "Undelete 5 edits",
+                       "lang": "en"
+               },
+               {
+                       "name": "en undelete_short 21",
+                       "key": "en_undelete_short",
+                       "args": [
+                               21
+                       ],
+                       "result": "Undelete 21 edits",
+                       "lang": "en"
+               },
+               {
+                       "name": "en undelete_short 101",
+                       "key": "en_undelete_short",
+                       "args": [
+                               101
+                       ],
+                       "result": "Undelete 101 edits",
+                       "lang": "en"
+               },
+               {
+                       "name": "en category-subcat-count 0,10",
+                       "key": "en_category-subcat-count",
+                       "args": [
+                               0,
+                               10
+                       ],
+                       "result": "This category has the following 0 subcategories, out of 10 total.",
+                       "lang": "en"
+               },
+               {
+                       "name": "en category-subcat-count 1,1",
+                       "key": "en_category-subcat-count",
+                       "args": [
+                               1,
+                               1
+                       ],
+                       "result": "This category has only the following subcategory.",
+                       "lang": "en"
+               },
+               {
+                       "name": "en category-subcat-count 1,2",
+                       "key": "en_category-subcat-count",
+                       "args": [
+                               1,
+                               2
+                       ],
+                       "result": "This category has the following subcategory, out of 2 total.",
+                       "lang": "en"
+               },
+               {
+                       "name": "en category-subcat-count 3,30",
+                       "key": "en_category-subcat-count",
+                       "args": [
+                               3,
+                               30
+                       ],
+                       "result": "This category has the following 3 subcategories, out of 30 total.",
+                       "lang": "en"
+               },
+               {
+                       "name": "fr undelete_short 0",
+                       "key": "fr_undelete_short",
+                       "args": [
+                               0
+                       ],
+                       "result": "Restaurer 0 modification",
+                       "lang": "fr"
+               },
+               {
+                       "name": "fr undelete_short 1",
+                       "key": "fr_undelete_short",
+                       "args": [
+                               1
+                       ],
+                       "result": "Restaurer 1 modification",
+                       "lang": "fr"
+               },
+               {
+                       "name": "fr undelete_short 2",
+                       "key": "fr_undelete_short",
+                       "args": [
+                               2
+                       ],
+                       "result": "Restaurer 2 modifications",
+                       "lang": "fr"
+               },
+               {
+                       "name": "fr undelete_short 5",
+                       "key": "fr_undelete_short",
+                       "args": [
+                               5
+                       ],
+                       "result": "Restaurer 5 modifications",
+                       "lang": "fr"
+               },
+               {
+                       "name": "fr undelete_short 21",
+                       "key": "fr_undelete_short",
+                       "args": [
+                               21
+                       ],
+                       "result": "Restaurer 21 modifications",
+                       "lang": "fr"
+               },
+               {
+                       "name": "fr undelete_short 101",
+                       "key": "fr_undelete_short",
+                       "args": [
+                               101
+                       ],
+                       "result": "Restaurer 101 modifications",
+                       "lang": "fr"
+               },
+               {
+                       "name": "fr category-subcat-count 0,10",
+                       "key": "fr_category-subcat-count",
+                       "args": [
+                               0,
+                               10
+                       ],
+                       "result": "Cette cat\u00e9gorie comprend 10 sous-cat\u00e9gories, dont celle ci-dessous.",
+                       "lang": "fr"
+               },
+               {
+                       "name": "fr category-subcat-count 1,1",
+                       "key": "fr_category-subcat-count",
+                       "args": [
+                               1,
+                               1
+                       ],
+                       "result": "Cette cat\u00e9gorie comprend la sous-cat\u00e9gorie ci-dessous.",
+                       "lang": "fr"
+               },
+               {
+                       "name": "fr category-subcat-count 1,2",
+                       "key": "fr_category-subcat-count",
+                       "args": [
+                               1,
+                               2
+                       ],
+                       "result": "Cette cat\u00e9gorie comprend 2 sous-cat\u00e9gories, dont celle ci-dessous.",
+                       "lang": "fr"
+               },
+               {
+                       "name": "fr category-subcat-count 3,30",
+                       "key": "fr_category-subcat-count",
+                       "args": [
+                               3,
+                               30
+                       ],
+                       "result": "Cette cat\u00e9gorie comprend 30 sous-cat\u00e9gories, dont les 3 ci-dessous.",
+                       "lang": "fr"
+               },
+               {
+                       "name": "ar undelete_short 0",
+                       "key": "ar_undelete_short",
+                       "args": [
+                               0
+                       ],
+                       "result": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 \u062a\u0639\u062f\u064a\u0644 \u0648\u0627\u062d\u062f",
+                       "lang": "ar"
+               },
+               {
+                       "name": "ar undelete_short 1",
+                       "key": "ar_undelete_short",
+                       "args": [
+                               1
+                       ],
+                       "result": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 \u062a\u0639\u062f\u064a\u0644\u064a\u0646",
+                       "lang": "ar"
+               },
+               {
+                       "name": "ar undelete_short 2",
+                       "key": "ar_undelete_short",
+                       "args": [
+                               2
+                       ],
+                       "result": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 2 \u062a\u0639\u062f\u064a\u0644\u0627\u062a",
+                       "lang": "ar"
+               },
+               {
+                       "name": "ar undelete_short 5",
+                       "key": "ar_undelete_short",
+                       "args": [
+                               5
+                       ],
+                       "result": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 5 \u062a\u0639\u062f\u064a\u0644",
+                       "lang": "ar"
+               },
+               {
+                       "name": "ar undelete_short 21",
+                       "key": "ar_undelete_short",
+                       "args": [
+                               21
+                       ],
+                       "result": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 21 \u062a\u0639\u062f\u064a\u0644\u0627",
+                       "lang": "ar"
+               },
+               {
+                       "name": "ar undelete_short 101",
+                       "key": "ar_undelete_short",
+                       "args": [
+                               101
+                       ],
+                       "result": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 101 \u062a\u0639\u062f\u064a\u0644\u0627",
+                       "lang": "ar"
+               },
+               {
+                       "name": "ar category-subcat-count 0,10",
+                       "key": "ar_category-subcat-count",
+                       "args": [
+                               0,
+                               10
+                       ],
+                       "result": "\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0641\u064a\u0647 \u060c \u0645\u0646 \u0625\u062c\u0645\u0627\u0644\u064a 10.",
+                       "lang": "ar"
+               },
+               {
+                       "name": "ar category-subcat-count 1,1",
+                       "key": "ar_category-subcat-count",
+                       "args": [
+                               1,
+                               1
+                       ],
+                       "result": "\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0641\u064a\u0647 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0627\u0644\u0641\u0631\u0639\u064a \u0627\u0644\u062a\u0627\u0644\u064a \u0641\u0642\u0637.",
+                       "lang": "ar"
+               },
+               {
+                       "name": "ar category-subcat-count 1,2",
+                       "key": "ar_category-subcat-count",
+                       "args": [
+                               1,
+                               2
+                       ],
+                       "result": "\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0641\u064a\u0647 \u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0627\u0644\u0641\u0631\u0639\u064a\u060c \u0645\u0646 \u0625\u062c\u0645\u0627\u0644\u064a 2.",
+                       "lang": "ar"
+               },
+               {
+                       "name": "ar category-subcat-count 3,30",
+                       "key": "ar_category-subcat-count",
+                       "args": [
+                               3,
+                               30
+                       ],
+                       "result": "\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0641\u064a\u0647 \u0647\u0630\u0647 \u0627\u06443 \u062a\u0635\u0627\u0646\u064a\u0641 \u0627\u0644\u0641\u0631\u0639\u064a\u0629\u060c \u0645\u0646 \u0625\u062c\u0645\u0627\u0644\u064a 30.",
+                       "lang": "ar"
+               },
+               {
+                       "name": "jp undelete_short 0",
+                       "key": "jp_undelete_short",
+                       "args": [
+                               0
+                       ],
+                       "result": "Undelete 0 edits",
+                       "lang": "jp"
+               },
+               {
+                       "name": "jp undelete_short 1",
+                       "key": "jp_undelete_short",
+                       "args": [
+                               1
+                       ],
+                       "result": "Undelete one edit",
+                       "lang": "jp"
+               },
+               {
+                       "name": "jp undelete_short 2",
+                       "key": "jp_undelete_short",
+                       "args": [
+                               2
+                       ],
+                       "result": "Undelete 2 edits",
+                       "lang": "jp"
+               },
+               {
+                       "name": "jp undelete_short 5",
+                       "key": "jp_undelete_short",
+                       "args": [
+                               5
+                       ],
+                       "result": "Undelete 5 edits",
+                       "lang": "jp"
+               },
+               {
+                       "name": "jp undelete_short 21",
+                       "key": "jp_undelete_short",
+                       "args": [
+                               21
+                       ],
+                       "result": "Undelete 21 edits",
+                       "lang": "jp"
+               },
+               {
+                       "name": "jp undelete_short 101",
+                       "key": "jp_undelete_short",
+                       "args": [
+                               101
+                       ],
+                       "result": "Undelete 101 edits",
+                       "lang": "jp"
+               },
+               {
+                       "name": "jp category-subcat-count 0,10",
+                       "key": "jp_category-subcat-count",
+                       "args": [
+                               0,
+                               10
+                       ],
+                       "result": "This category has the following 0 subcategories, out of 10 total.",
+                       "lang": "jp"
+               },
+               {
+                       "name": "jp category-subcat-count 1,1",
+                       "key": "jp_category-subcat-count",
+                       "args": [
+                               1,
+                               1
+                       ],
+                       "result": "This category has only the following subcategory.",
+                       "lang": "jp"
+               },
+               {
+                       "name": "jp category-subcat-count 1,2",
+                       "key": "jp_category-subcat-count",
+                       "args": [
+                               1,
+                               2
+                       ],
+                       "result": "This category has the following subcategory, out of 2 total.",
+                       "lang": "jp"
+               },
+               {
+                       "name": "jp category-subcat-count 3,30",
+                       "key": "jp_category-subcat-count",
+                       "args": [
+                               3,
+                               30
+                       ],
+                       "result": "This category has the following 3 subcategories, out of 30 total.",
+                       "lang": "jp"
+               },
+               {
+                       "name": "zh undelete_short 0",
+                       "key": "zh_undelete_short",
+                       "args": [
+                               0
+                       ],
+                       "result": "\u6062\u590d0\u4e2a\u88ab\u5220\u9664\u7684\u7f16\u8f91",
+                       "lang": "zh"
+               },
+               {
+                       "name": "zh undelete_short 1",
+                       "key": "zh_undelete_short",
+                       "args": [
+                               1
+                       ],
+                       "result": "\u6062\u590d1\u4e2a\u88ab\u5220\u9664\u7684\u7f16\u8f91",
+                       "lang": "zh"
+               },
+               {
+                       "name": "zh undelete_short 2",
+                       "key": "zh_undelete_short",
+                       "args": [
+                               2
+                       ],
+                       "result": "\u6062\u590d2\u4e2a\u88ab\u5220\u9664\u7684\u7f16\u8f91",
+                       "lang": "zh"
+               },
+               {
+                       "name": "zh undelete_short 5",
+                       "key": "zh_undelete_short",
+                       "args": [
+                               5
+                       ],
+                       "result": "\u6062\u590d5\u4e2a\u88ab\u5220\u9664\u7684\u7f16\u8f91",
+                       "lang": "zh"
+               },
+               {
+                       "name": "zh undelete_short 21",
+                       "key": "zh_undelete_short",
+                       "args": [
+                               21
+                       ],
+                       "result": "\u6062\u590d21\u4e2a\u88ab\u5220\u9664\u7684\u7f16\u8f91",
+                       "lang": "zh"
+               },
+               {
+                       "name": "zh undelete_short 101",
+                       "key": "zh_undelete_short",
+                       "args": [
+                               101
+                       ],
+                       "result": "\u6062\u590d101\u4e2a\u88ab\u5220\u9664\u7684\u7f16\u8f91",
+                       "lang": "zh"
+               },
+               {
+                       "name": "zh category-subcat-count 0,10",
+                       "key": "zh_category-subcat-count",
+                       "args": [
+                               0,
+                               10
+                       ],
+                       "result": "\u672c\u5206\u7c7b\u5305\u542b\u4e0b\u52170\u4e2a\u5b50\u5206\u7c7b\uff0c\u517110\u4e2a\u5b50\u5206\u7c7b\u3002",
+                       "lang": "zh"
+               },
+               {
+                       "name": "zh category-subcat-count 1,1",
+                       "key": "zh_category-subcat-count",
+                       "args": [
+                               1,
+                               1
+                       ],
+                       "result": "\u672c\u5206\u7c7b\u53ea\u6709\u4e0b\u5217\u4e00\u4e2a\u5b50\u5206\u7c7b\u3002",
+                       "lang": "zh"
+               },
+               {
+                       "name": "zh category-subcat-count 1,2",
+                       "key": "zh_category-subcat-count",
+                       "args": [
+                               1,
+                               2
+                       ],
+                       "result": "\u672c\u5206\u7c7b\u5305\u542b\u4e0b\u52171\u4e2a\u5b50\u5206\u7c7b\uff0c\u51712\u4e2a\u5b50\u5206\u7c7b\u3002",
+                       "lang": "zh"
+               },
+               {
+                       "name": "zh category-subcat-count 3,30",
+                       "key": "zh_category-subcat-count",
+                       "args": [
+                               3,
+                               30
+                       ],
+                       "result": "\u672c\u5206\u7c7b\u5305\u542b\u4e0b\u52173\u4e2a\u5b50\u5206\u7c7b\uff0c\u517130\u4e2a\u5b50\u5206\u7c7b\u3002",
+                       "lang": "zh"
+               }
+       ]
+};
index b8193a9..c80296f 100644 (file)
@@ -1,6 +1,42 @@
-QUnit.module( 'mediawiki.jqueryMsg' );
+( function ( mw, $ ) {
 
-QUnit.test( 'mw.jqueryMsg Plural', 3, function ( assert ) {
+QUnit.module( 'mediawiki.jqueryMsg', QUnit.newMwEnvironment( {
+       setup: function () {
+               this.orgMwLangauge = mw.language;
+               mw.language = $.extend( true, {}, this.orgMwLangauge );
+       },
+       teardown: function () {
+               // Restore
+               mw.language = this.orgMwLangauge;
+       }
+}) );
+
+var mwLanguageCache = {};
+function getMwLanguage( langCode, cb ) {
+       if ( mwLanguageCache[langCode] !== undefined ) {
+               mwLanguageCache[langCode].add( cb );
+               return;
+       }
+       mwLanguageCache[langCode] = $.Callbacks( 'once memory' );
+       mwLanguageCache[langCode].add( cb );
+       $.ajax({
+               url: mw.util.wikiScript( 'load' ),
+               data: {
+                       skin: mw.config.get( 'skin' ),
+                       lang: langCode,
+                       debug: mw.config.get( 'debug' ),
+                       modules: 'mediawiki.language',
+                       only: 'scripts'
+               },
+               dataType: 'script'
+       }).done( function () {
+               mwLanguageCache[langCode].fire( mw.language );
+       }).fail( function () {
+               mwLanguageCache[langCode].fire( false );
+       });
+}
+
+QUnit.test( 'Plural', 3, function ( assert ) {
        var parser = mw.jqueryMsg.getMessageFunction();
 
        mw.messages.set( 'plural-msg', 'Found $1 {{PLURAL:$1|item|items}}' );
@@ -10,7 +46,7 @@ QUnit.test( 'mw.jqueryMsg Plural', 3, function ( assert ) {
 } );
 
 
-QUnit.test( 'mw.jqueryMsg Gender', 11, function ( assert ) {
+QUnit.test( 'Gender', 11, function ( assert ) {
        // TODO: These tests should be for mw.msg once mw.msg integrated with mw.jqueryMsg
        // TODO: English may not be the best language for these tests. Use a language like Arabic or Russian
        var user = mw.user,
@@ -84,8 +120,7 @@ QUnit.test( 'mw.jqueryMsg Gender', 11, function ( assert ) {
        );
 } );
 
-
-QUnit.test( 'mw.jqueryMsg Grammar', 2, function ( assert ) {
+QUnit.test( 'Grammar', 2, function ( assert ) {
        var parser = mw.jqueryMsg.getMessageFunction();
 
        // Assume the grammar form grammar_case_foo is not valid in any language
@@ -95,3 +130,25 @@ QUnit.test( 'mw.jqueryMsg Grammar', 2, function ( assert ) {
        mw.messages.set( 'grammar-msg-wrong-syntax', 'Przeszukaj {{GRAMMAR:grammar_case_xyz}}' );
        assert.equal( parser( 'grammar-msg-wrong-syntax' ), 'Przeszukaj ' , 'Grammar Test with wrong grammar template syntax' );
 } );
+
+QUnit.test( 'Output matches PHP parser', mw.libs.phpParserData.tests.length, function ( assert ) {
+       mw.messages.set( mw.libs.phpParserData.messages );
+       $.each( mw.libs.phpParserData.tests, function ( i, test ) {
+               QUnit.stop();
+               getMwLanguage( test.lang, function ( langClass ) {
+                       QUnit.start();
+                       if ( !langClass ) {
+                               assert.ok( false, 'Language "' + test.lang + '" failed to load' );
+                               return;
+                       }
+                       var parser = new mw.jqueryMsg.parser( { language: langClass } );
+                       assert.equal(
+                               parser.parse( test.key, test.args ).html(),
+                               test.result,
+                               test.name
+                       );
+               } );
+       } );
+});
+
+}( mediaWiki, jQuery ) );