From: jenkins-bot Date: Mon, 24 Sep 2018 13:48:21 +0000 (+0000) Subject: Merge "RevisionStoreDbTestBase, remove redundant needsDB override" X-Git-Tag: 1.34.0-rc.0~4012 X-Git-Url: https://git.heureux-cyclage.org/?p=lhc%2Fweb%2Fwiklou.git;a=commitdiff_plain;h=74d04edec385aa86ee01943b9a27475d79f74e78;hp=babb418439588b611f0e259438372523936e257e Merge "RevisionStoreDbTestBase, remove redundant needsDB override" --- diff --git a/.eslintrc.json b/.eslintrc.json index da5d409765..c0767517ea 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,13 +1,13 @@ { "extends": "wikimedia", "env": { - "browser": true, - "jquery": true + "browser": true }, "globals": { "require": false, "module": false, - "mediaWiki": false, + "mw": false, + "$": false, "OO": false }, "rules": { @@ -18,6 +18,16 @@ "property": "map", "message": "Please use Array.prototype.map instead" }, + { + "object": "$", + "property": "inArray", + "message": "Please use Array.prototype.indexOf instead" + }, + { + "object": "$", + "property": "each", + "message": "Please consider different approaches to $.each, especially when using Array's. You can override this warning if necessary with eslint-disable-next-line." + }, { "object": "$", "property": "isArray", @@ -37,6 +47,11 @@ "object": "$", "property": "trim", "message": "Please use String.prototype.trim instead" + }, + { + "object": "$", + "property": "proxy", + "message": "Please use Function.prototype.bind instead" } ], "dot-notation": 0, diff --git a/.gitattributes b/.gitattributes index 786c09f0e5..81b7a33173 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5,6 +5,7 @@ *~ export-ignore #*# export-ignore .* export-ignore +*.htaccess -export-ignore package.json export-ignore README.mediawiki export-ignore Gemfile* export-ignore diff --git a/.gitignore b/.gitignore index d440e728ca..248931ea2a 100644 --- a/.gitignore +++ b/.gitignore @@ -43,7 +43,6 @@ sftp-config.json /maintenance/dev/data /AdminSettings.php /LocalSettings.php -/StartProfiler.php # Building & testing npm-debug.log diff --git a/.phpcs.xml b/.phpcs.xml index 944c3e20e1..2bce5b2a59 100644 --- a/.phpcs.xml +++ b/.phpcs.xml @@ -2,21 +2,18 @@ - - - @@ -137,7 +134,6 @@ */maintenance/7zip.inc */maintenance/CodeCleanerGlobalsPass.inc */maintenance/archives/upgradeLogging\.php - */maintenance/backup.inc */maintenance/benchmarks/bench_HTTP_HTTPS\.php */maintenance/benchmarks/bench_Wikimedia_base_convert\.php */maintenance/benchmarks/bench_delete_truncate\.php @@ -228,7 +224,6 @@ */includes/api/ApiOpenSearch\.php */includes/api/ApiRsd\.php */includes/api/ApiUsageException\.php - */includes/auth/AuthManagerAuthPlugin\.php */includes/AuthPlugin\.php */includes/cache/CacheDependency\.php */includes/cache/CacheHelper\.php @@ -356,5 +351,4 @@ ^skins/ AdminSettings\.php LocalSettings\.php - StartProfiler\.php diff --git a/HISTORY b/HISTORY index bc74a6b700..46650366e5 100644 --- a/HISTORY +++ b/HISTORY @@ -2,6 +2,32 @@ Change notes from older releases. For current info see RELEASE-NOTES-1.32. = MediaWiki 1.31 = +== MediaWiki 1.31.1 == + +This is a security and maintenance release of the MediaWiki 1.31 branch. + +=== Changes since MediaWiki 1.31.0 === +* (T169545, CVE-2018-0503) SECURITY: $wgRateLimits entry for 'user' overrides + 'newbie'. +* (T194605, CVE-2018-0505) SECURITY: BotPasswords can bypass CentralAuth's + account lock. +* (T199029, CVE-2018-13258) SECURITY: Tarball was missing .htaccess files. +* (T197229) Bundle Nuke extension, it was accidentally omitted. +* (T193995) Fix undefined patchPath() method call in parser tests. +* (T198687) Fix various selectFields methods to use the string 'NULL', not null. +* Special:BotPasswords now requires reauthentication. +* (T191608, T187638) Add 'logid' parameter to Special:Log. +* (T193829) Indicate when a Bot Password needs reset. +* (T198037) GitInfo: Don't try shelling out if it's disabled. +* (T151415) Log email changes. +* (T197206) Fix performance regression when multiple DB used without caching. +* (T197030) PHPSessionHandler: Suppress headers warnings in initialize(). +* (T182377, T196793) Exif: Guard against uncountable tag values. +* (T200861) Fix total breakage of SQLite web upgrade. +* (T200864) Fix pingback over-reporting on non-MySQL databases +* (T202550) Unbreak SpecialListusersHeaderForm and SpecialListusersHeader + hooks. + == MediaWiki 1.31.0 == === Changes since MediaWiki 1.31.0-rc.2 === @@ -488,6 +514,43 @@ There's usually someone online in #mediawiki on irc.freenode.net. = MediaWiki 1.30 = +== MediaWiki 1.30.1 == + +This is a security and maintenance release of the MediaWiki 1.30 branch. + +=== Changes since MediaWiki 1.30.0 === +* (T169545, CVE-2018-0503) SECURITY: $wgRateLimits entry for 'user' overrides + 'newbie'. +* (T194605, CVE-2018-0505) SECURITY: BotPasswords can bypass CentralAuth's + account lock. +* (T87572) Make FormatMetadata::flattenArrayReal() work for an associative array. +* Updated composer/spdx-licenses from 1.1.4 to 1.3.0 (development dependency). +* (T189567) the CLI installer (maintenance/install.php) learned to detect and + include extensions. Pass --with-extensions to enable that feature. +* (T190503) Let built-in web server (maintenance/dev) handle .php requests. +* (T167507) selenium: Run Chrome headlessly. +* selenium: Pass -no-sandbox to Chrome under Docker. +* (T179190) selenium: Move logic for running tests from package.json to selenium.sh +* (T192584) Stop incorrectly passing USE INDEX to RecentChange::newFromConds(). +* Add default edit rate limit of 90 edits/minute for all users. +* (T186565) Fix PHP Notice from `ob_end_flush()` in `FileRepo::streamFile()`. +* oojs/oojs-ui updated to remove an unnecessary dependancy. +* (T196125) php-memcached 3.0 (provided with PHP 7.0) is now supported. +* (T118683) Fix exception from &$user deref on HHVM in the TitleMoveComplete hook. +* (T196672) The mtime of extension.json files is now able to be zero +* (T180403) Validate $length in padleft/padright parser functions. +* (T143790) Make $wgEmailConfirmToEdit only affect edit actions. +* (T193995) Fix undefined patchPath() method call in parser tests. +* Special:BotPasswords now requires reauthentication. +* (T191608, T187638) Add 'logid' parameter to Special:Log. +* (T193829) Indicate when a Bot Password needs reset. +* (T151415) Log email changes. +* (T200861) Fix total breakage of SQLite web upgrade. +* (T202550) Unbreak SpecialListusersHeaderForm and SpecialListusersHeader + hooks. +* (T190539) Explicitly require Postgres 9.1. +* (T118420) Unbreak Oracle installer. + == MediaWiki 1.30.0 == === Changes since MediaWiki 1.30.0-rc.0 === @@ -751,6 +814,49 @@ changes to languages because of Phabricator reports. = MediaWiki 1.29 = +== MediaWiki 1.29.3 == + +This is a security and maintenance release of the MediaWiki 1.29 branch. + +=== Changes since 1.29.2 === +* (T169545, CVE-2018-0503) SECURITY: $wgRateLimits entry for 'user' overrides + 'newbie'. +* (T194605, CVE-2018-0505) SECURITY: BotPasswords can bypass CentralAuth's + account lock. +* (T180551) Fix LanguageSrTest for language converter +* (T180552) Fix langauge converter parser test with self-close tags +* (T180537) Remove $wgAuth usage from wrapOldPasswords.php +* (T180485) InputBox: Have inputbox langconvert certain attributes +* (T161732, T181547) Upgraded Moment.js from v2.15.0 to v2.19.3. +* (T172927) Drop vendor from MW release branch +* (T87572) Make FormatMetadata::flattenArrayReal() work for an associative array +* Updated composer/spdx-licenses from 1.1.4 to 1.3.0 (development dependency). +* (T189567) the CLI installer (maintenance/install.php) learned to detect and + include extensions. Pass --with-extensions to enable that feature. +* (T182381) Mask deprecated call in WatchedItemUnitTest +* (T190503) Let built-in web server (maintenance/dev) handle .php requests. +* The karma qunit tests would fail on some configuration due to headers already + sent. Check headers_sent() before sending cpPosTime headers +* (T167507) selenium: Run Chrome headlessly. +* selenium: Pass -no-sandbox to Chrome under Docker +* (T191247) Use MediaWiki\SuppressWarnings around trigger_error('') instead @ +* (T75174, T161041) Unit test ChangesListSpecialPageTest::testFilterUserExpLevel + fails under SQLite. +* (T192584) Stop incorrectly passing USE INDEX to RecentChange::newFromConds(). +* (T179190) selenium: Move test running logic from package.json to selenium.sh. +* (T117839, T193200) PDFHandler: Fix for pdfinfo changes in poppler-utils 0.48. +* Add default edit rate limit of 90 edits/minute for all users. +* (T196125) php-memcached 3.0 (provided with PHP 7.0) is now supported. +* (T196672) The mtime of extension.json files is now able to be zero +* (T180403) Validate $length in padleft/padright parser functions. +* (T143790) Make $wgEmailConfirmToEdit only affect edit actions. +* (T194237) Special:BotPasswords now requires reauthentication. +* (T191608, T187638) Add 'logid' parameter to Special:Log. +* (T176097) resourceloader: Disable a flaky MessageBlobStoreTest case +* (T193829) Indicate when a Bot Password needs reset. +* (T151415) Log email changes. +* (T118420) Unbreak Oracle installer. + == MediaWiki 1.29.2 == This is a security and maintenance release of the MediaWiki 1.29 branch. @@ -1526,6 +1632,34 @@ There's usually someone online in #mediawiki on irc.freenode.net. = MediaWiki 1.27 = +== MediaWiki 1.27.5 == + +This is a security and maintenance release of the MediaWiki 1.27 branch. + +=== Changes since 1.27.4 === +* (T169545, CVE-2018-0503) SECURITY: $wgRateLimits entry for 'user' overrides + 'newbie'. +* (T194605, CVE-2018-0505) SECURITY: BotPasswords can bypass CentralAuth's + account lock. +* Upgraded Moment.js from v2.8.4 to v2.19.3. +* (T160298) Fixed Special:ActiveUsers due to bad backport. +* (T87572) Make FormatMetadata::flattenArrayReal() work for an associative array. +* Updated list of SPDX licenses for extensions. +* (T189567) the CLI installer (maintenance/install.php) learned to detect and + include extensions. Pass --with-extensions to enable that feature. +* (T192584) Stop incorrectly passing USE INDEX to RecentChange::newFromConds(). +* Add default edit rate limit of 90 edits/minute for all users. +* (T196125) php-memcached 3.0 (provided with PHP 7.0) is now supported. +* (T196672) The mtime of extension.json files is now able to be zero. +* (T118683) Fix exception from &$user deref on HHVM in the TitleMoveComplete hook. +* (T180403) Validate $length in padleft/padright parser functions. +* (T143790) Make $wgEmailConfirmToEdit only affect edit actions. +* Special:BotPasswords now requires reauthentication. +* (T191608, T187638) Add 'logid' parameter to Special:Log. +* (T193829) Indicate when a Bot Password needs reset. +* (T151415) Log email changes. +* (T118420) Unbreak Oracle installer. + == MediaWiki 1.27.4 == This is a security and maintenance release of the MediaWiki 1.27 branch. diff --git a/RELEASE-NOTES-1.32 b/RELEASE-NOTES-1.32 index 0ed2631625..cfe90a016c 100644 --- a/RELEASE-NOTES-1.32 +++ b/RELEASE-NOTES-1.32 @@ -6,37 +6,53 @@ MediaWiki 1.32 is an alpha-quality branch and is not recommended for use in production. === Configuration changes in 1.32 === -* (T115414) The $wgEnableAPI and $wgEnableWriteAPI settings, deprecated in 1.31, - have been removed. -* The $wgUseAjax setting, deprecated in 1.31, is now ignored. -* The $wgSiteSupportPage setting, unused since 1.5, was removed. -* The $wgBrowserBlacklist setting, deprecated in 1.30, was removed. -* The default quality of JPEG thumbnails generated by GD was reduced from 95 to - 80. The quality of JPEG thumbnails is now configurable through the new setting - $wgJpegQuality (default 80). This aligns the quality to what ImageMagick uses. -* $wgExperimentalHtmlIds, deprecated since 1.30, has been removed. The - 'html5-legacy' value for $wgFragmentMode is no longer accepted. -* The experimental Html5Internal and Html5Depurate tidy drivers were removed. - RemexHtml, which is the default, should be used instead. -* (T135963) You can now define a Content Security Policy for your wiki. This - adds a defense-in-depth feature to stop an attacker who has found a bug in - the parser allowing them to insert malicious attributes. Disabled by default, - you can configure this via $wgCSPHeader and $wgCSPReportOnlyHeader. -* New configuration variable has been added: $wgCookieSetOnIpBlock. - This determines whether to set a cookie when an IP user is blocked. Doing so - means that a blocked user, even after moving to a new IP address, will still - be blocked. -* The archive table's ar_rev_id field is now unique. -* Special:BotPasswords now requires reauthentication. -* (T194414) The default watchlist view time has been increased from 3 to 7 days. -* The right to edit sitewide Javascript (e.g. MediaWiki:Common.js), CSS or JSON - was separated from 'editinterface' and is available under - 'editsitejs'/'editsitecss'/'editsitejson'. Having 'editinterface' is still - necessary to edit such pages. -* A new user group, 'interface-admin', is added for controlling access to - sitewide CSS/JS (and editing other users' CSS/JS). No other group has - 'editsitecss', 'editusercss', 'editsitejs' or 'edituserjs' by default. -* A new grant group, 'editsiteconfig', is added for granting the above rights. + +==== New configuration ==== +* $wgJpegQuality – The quality of JPEG thumbnails is now configurable through + this setting. The default is 80, which matches the quality of JPEG thumbnails + previously generated by ImageMagick. The quality of JPEG thumbnails generated + by GD was previously 95, but now uses the $wgJpegQuality setting as well. +* $wgCookieSetOnIpBlock - This determines whether to set a cookie when an IP + user is blocked. Doing so means that a blocked user, even after moving to a + new IP address, will still be blocked. +* $wgRawHtmlMessages – This new configuration setting is added for listing + messages which are displayed as raw HTML. +* $wgCSPHeader and $wgCSPReportOnlyHeader – You can now define a + "Content Security Policy" for your wiki. This adds a defense-in-depth feature + to stop an attacker who has found a bug in the parser allowing them to insert + malicious attributes. Disabled by default. (T135963) +* $wgGroupPermissions – A new user group, 'interface-admin', is added for + controlling access to sitewide CSS/JS (and editing other users' CSS/JS). No + other group has 'editsitecss', 'editusercss', 'editsitejs' or 'edituserjs' + by default. +* $wgGrantPermissions – A new grant group, 'editsiteconfig', is added for + granting the above rights. + +==== Changed configuration ==== +* $wgUseAjax – This setting, deprecated in 1.31, is now ignored. +* $wgDefaultUserOptions – The default watchlist view time (watchlistdays) has + been increased from 3 to 7 days. (T194414) +* $wgGroupPermissions – The right to edit sitewide Javascript + (e.g. MediaWiki:Common.js), CSS or JSON was separated from 'editinterface' + and is available under 'editsitejs'/'editsitecss'/'editsitejson'. Having + 'editinterface' is still necessary to edit such pages. +* $wgMultiContentRevisionSchemaMigrationStage now defaults to writing both the + old and the new schema, but reading the new schema, so Multi-Content Revisions + (MCR) are now functional per default. The new default value of the setting is + SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW. + +==== Removed configuration ==== +* $wgEnableAPI and $wgEnableWriteAPI – These settings, deprecated in 1.31, + have been removed. (T115414) +* $wgSiteSupportPage – This setting, unused since 1.5, was removed. +* $wgBrowserBlacklist – This setting, deprecated in 1.30, was removed. +* $wgExperimentalHtmlIds – This setting, deprecated since 1.30, was removed. + The 'html5-legacy' value for $wgFragmentMode is no longer accepted. +* $wgPasswordSenderName - This setting, ignored since 1.23 by MediaWiki and + most extensions, is no longer set. Instead, you can modify the system + message `emailsender`. +* $wgTidyConfig – The experimental Html5Internal and Html5Depurate tidy drivers + were removed. RemexHtml, which is the default, should be used instead. === New features in 1.32 === * (T112474) Generalized the ResourceLoader mechanism for overriding modules @@ -68,12 +84,24 @@ production. additional links to the subtitle of a history page. * The 'GetLinkColours' hook now receives an additional $title parameter, the Title object of the page being parsed, on which the links will be shown. +* (T194731) DifferenceEngine supports multiple slots. Added SlotDiffRenderer to + render diffs between two Content objects, and DifferenceEngine::setRevisions() + to render diffs between two custom (potentially multi-content) revisions. + Added GetSlotDiffRenderer hook which works like GetDifferenceEngine for slots. +* Added a temporary action=mcrundo to the web UI, as the normal undo logic + can't yet handle MCR and deadlines are forcing is to put off fixing that. + This action should be considered deprecated and should not be used directly. +* Extensions overriding ContentHandler::getUndoContent() will need to be + updated for the changed method signature. === External library changes in 1.32 === + +==== New external libraries ==== +* Added wikimedia/xmp-reader 0.6.0 * … -==== Upgraded external libraries ==== -* Updated QUnit from 2.4.0 to 2.6.0. +==== Changed external libraries ==== +* Updated qunitjs from 2.4.0 to 2.6.2. * Updated wikimedia/scoped-callback from 1.0.0 to 2.0.0. ** ScopedCallback objects can no longer be serialized. * Updated wikimedia/wrappedstring from 2.3.0 to 3.0.1. @@ -82,17 +110,15 @@ production. * Updated jquery.i18n from 1.0.4 to 1.0.5. * Updated wikimedia/timestamp from 1.0.0 to 2.0.0. * Updated wikimedia/remex-html from 1.0.3 to 2.0.0. +* Updated jquery from v3.2.1 to v3.3.1. -==== New external libraries ==== -* Added wikimedia/xmp-reader 0.6.0 -* … - -==== Removed and replaced external libraries ==== +==== Removed external libraries ==== * … === Bug fixes in 1.32 === * SpecialPage::execute() will now only call checkLoginSecurityLevel() if getLoginSecurityLevel() returns non-false. +* (T43720, T46197) Improved page display title handling for category pages === Action API changes in 1.32 === * Added templated parameters. @@ -129,6 +155,18 @@ production. * action=query&prop=deletedrevisions, action=query&list=allrevisions, and action=query&list=alldeletedrevisions are changed similarly to &prop=revisions (see the three previous items). +* (T174032) action=compare now supports multi-content revisions. + * It has a 'slots' parameter to select diffing of individual slots. The + default behavior is to return one combined diff. + * The 'fromtext', 'fromsection', 'fromcontentmodel', 'fromcontentformat', + 'totext', 'tosection', 'tocontentmodel', and 'tocontentformat' parameters + are deprecated. Specify the new 'fromslots' and 'toslots' to identify which + slots have text supplied and the corresponding templated parameters for + each slot. + * The behavior of 'fromsection' and 'tosection' of extracting one section's + content is not being preserved. 'fromsection-{slot}' and 'tosection-{slot}' + instead expand the given text as if for a section edit. This effectively + declines T183823 in favor of T185723. === Action API internal changes in 1.32 === * Added 'ApiParseMakeOutputPage' hook. @@ -194,6 +232,7 @@ because of Phabricator reports. * The mediawiki.widgets.visibleByteLimit module alias, deprecated in 1.32, was removed. Use mediawiki.widgets.visibleLengthLimit instead. * The jquery.farbtastic module, unused since 1.18, was removed. +* The 'jquery.expandableField' module, unused since 1.22, was removed. * (T181318) The $wgStyleVersion setting and its appendage to various script and style URLs in OutputPage, deprecated in 1.31, was removed. * The hooks 'PreferencesFormPreSave' and 'PreferencesGetLegend' may provide @@ -249,10 +288,38 @@ because of Phabricator reports. resetServiceForTesting( 'MagicWordFactory' ) on a MediaWikiServices. * mw.util.init() has been removed. This function is not needed anymore and was a no-op function since 1.30. +* SpecialPageFactory::resetList() is a no-op. Call overrideMwServices() + instead. +* MediaWiki no longer supports a StartProfiler.php file. + Define $wgProfiler via LocalSettings.php instead. +* The mw.loader.addSource() is now considered a private method, and no longer + supports the `id, url` signature. Use the `Object` parameter instead. +* The backwards-compatibility code in HTMLForm to add a drop-down control to an + option that is not set to be a drop-down if the "mw-chosen" class is present, + is now removed. +* Several collations were removed. They were workarounds for bugs in the ICU + library and they are no longer needed (as of ICU 57.1): + * 'uppercase-se' (NorthernSamiUppercaseCollation) - use 'uca-se' instead + * 'xx-uca-et' (CollationEt) - use 'uca-et' instead + * 'xx-uca-fa' (CollationFa) - use 'uca-fa' instead +* The hooks 'SpecialRecentChangesFilters' & 'SpecialWatchlistFilters' deprecated + in 1.23 were removed. Instead, use 'ChangesListSpecialPageStructuredFilters'. + The ChangesListSpecialPage code for these legacy hooks, and their use in + SpecialRecentchanges.php and SpecialWatchlist, was also removed: + * ChangesListSpecialPage->getCustomFilters() + * ChangesListSpecialPage->getFilterGroupDefinitionFromLegacyCustomFilters() + * ChangesListSpecialPage::customFilters +* The global function wfUseMW, deprecated since 1.26, has now been removed. Use + the "requires" property of static extension registration instead. +* $wgSpecialPages no longer accepts array syntax, deprecated since 1.18. +* The MailAddress constructor can no longer be called with a User object, + behaviour which has been deprecated since 1.24. +* LBFactory, deprecated since 1.28, has been removed. Instead, use + Wikimedia\Rdbms\LBFactory. +* The MimeMagic class, deprecated since 1.28 has been removed. Get a + MimeAnalyzer instance from MediaWikiServices instead. === Deprecations in 1.32 === -* Use of a StartProfiler.php file is deprecated in favour of placing - configuration in LocalSettings.php. * HTMLForm::setSubmitProgressive() is deprecated. No need to call it. Submit button is already marked as progressive. * Skin::setupSkinUserCss() is deprecated. Adding of modules to load @@ -263,7 +330,6 @@ because of Phabricator reports. * Overriding SearchEngine::{searchText,searchTitle,searchArchiveTitle} in extending classes is deprecated. Extend related doSearch* methods instead. -* CollationFa has been removed completely as it's not needed anymore * The following 'mediawiki.api' plugin modules were merged into mediawiki.api and deprecated: mediawiki.api.category, mediawiki.api.edit, mediawiki.api.login, mediawiki.api.options, mediawiki.api.parse, @@ -337,22 +403,79 @@ because of Phabricator reports. Set $wgShowExceptionDetails and/or $wgShowHostnames instead. * The $wgShowDBErrorBacktrace global is deprecated and nonfunctional. Set $wgShowExceptionDetails instead. -* Public access to the DifferenceEngine properties mOldid, mNewid, mOldPage, - mNewPage, mOldContent, mNewContent, mRevisionsLoaded, mTextLoaded and - mCacheHit is deprecated. Use getOldid() / getNewid() for the first two, - do your own lookup for page/content. mNewRev / mOldRev remains public. +* Public access to the DifferenceEngine properties mOldid, mNewid, mOldRev, + mNewRev, mOldPage, mNewPage, mOldContent, mNewContent, mRevisionsLoaded, + mTextLoaded and mCacheHit is deprecated. Use getOldid() / getNewid() / + getOldRevision() / getNewRevision() for the first four (note that the + revision ones return a RevisionRecord, not a Revision), do your own lookup + for page/content. * The $wgExternalDiffEngine value 'wikidiff2' is deprecated. To use wikidiff2 just enable the PHP extension, and it will be autodetected. +* (T194731) DifferenceEngine properties mOldContent and mNewContent and methods + setContent(), generateContentDiffBody(), generateTextDiffBody() and textDiff() + are deprecated. To interact with a single slot, use a SlotDiffRenderer (and + subclass it to customize diff rendering); to diff custom (e.g. unsaved) + content, use setRevisions(). Subclassing DifferenceEngine should only be done + to customize page-level diff properties (such as the navigation header). * The wfUseMW function, soft-deprecated in 1.26, is now hard deprecated. * All MagicWord static methods are now deprecated. Use the MagicWordFactory methods instead. * PasswordFactory::init is deprecated. To get a password factory with the standard configuration, use MediaWikiServices::getPasswordFactory. +* $wgContLang is deprecated, use MediaWikiServices::getContentLanguage() + instead. +* $wgParser is deprecated, use MediaWikiServices::getParser() instead. +* wfGetMainCache() is deprecated, use ObjectCache::getLocalClusterInstance() + instead. +* wfGetCache() is deprecated, use ObjectCache::getInstance() instead. +* All SpecialPageFactory static methods are deprecated. Instead, call the + methods on a SpecialPageFactory instance, which may be obtained from + MediaWikiServices. +* mw.user.stickyRandomId was renamed to the more explicit + mw.user.getPageviewToken to better capture its function. +* Passing Revision objects to ContentHandler::getUndoContent() is deprecated, + Content object should be passed instead. +* (T197179) Parameters 'notice', 'notice-messages', 'notice-message', + previously used by OOUI HTMLForm fields, are now deprecated. Use + 'help', 'help-message', 'help-messages' instead. +* (T197179) HTMLFormField::getNotices() is now deprecated. +* The jquery.localize module is now deprecated. Use jquery.i18n instead. +* The SecondaryDataUpdates hook was deprecated in favor of RevisionDataUpdates, + or overriding ContentHandler::getSecondaryDataUpdates (T194038). +* The WikiPageDeletionUpdates hook was deprecated in favor of + PageDeletionDataUpdates, or overriding ContentHandler::getDeletionDataUpdates + (T194038). +* Content::getSecondaryDataUpdates has been deprecated in favor of + ContentHandler::getSecondaryDataUpdates() for overriding by extensions + (T194038). + Application logic should call WikiPage::doSecondaryDataUpdates() (T194037). +* Content::getDeletionUpdates has been deprecated in favor of + ContentHandler::getDeletionUpdates() for overriding by extensions (T194038). + Application logic should call WikiPage::doSecondaryDataUpdates() (T194037). +* (T198214) Old Tidy-related configuration settings, which were soft-deprecated + in MediaWiki 1.26, have now been hard deprecated. This affects $wgUseTidy, + $wgTidyBin, $wgTidyConf, $wgTidyOpts, $wgTidyInternal, and $wgDebugTidy. Use + $wgTidyConfig instead. +* All Tidy configurations other than Remex have been hard deprecated; + future parsers will not emit compatible output for these configurations. +* QuickTemplate::msgHtml() and BaseTemplate::msgHtml() have been deprecated + as they promote bad practises. I18n messages should always be properly + escaped. === Other changes in 1.32 === * (T198811) The following tables have had their UNIQUE indexes turned into proper PRIMARY KEYs for increased maintainability: interwiki, page_props, protected_titles and site_identifiers. +* OOUI HTMLForm will now display help text inline after the input field, + rather than in a popup. Previous behavior can be restored by using + `'help-inline' => false`. +* The archive table's ar_rev_id field is now unique. +* Special:BotPasswords now requires reauthentication. +* (T174023) Multi-Content Revision (MCR) capabilities were introduced into the + storage layer and have basic support for display. No user interface exists + yet for creating or managing content in slots beides the main slot. See + for more + information. * … == Compatibility == diff --git a/api.php b/api.php index 9c5ac95716..9cf75787ba 100644 --- a/api.php +++ b/api.php @@ -72,7 +72,11 @@ try { if ( !$processor instanceof ApiMain ) { throw new MWException( 'ApiBeforeMain hook set $processor to a non-ApiMain class' ); } -} catch ( Exception $e ) { +} catch ( Exception $e ) { // @todo Remove this block when HHVM is no longer supported + // Crap. Try to report the exception in API format to be friendly to clients. + ApiMain::handleApiBeforeMainException( $e ); + $processor = false; +} catch ( Throwable $e ) { // Crap. Try to report the exception in API format to be friendly to clients. ApiMain::handleApiBeforeMainException( $e ); $processor = false; @@ -99,7 +103,9 @@ if ( $wgAPIRequestLog ) { try { $manager = $processor->getModuleManager(); $module = $manager->getModule( $wgRequest->getVal( 'action' ), 'action' ); - } catch ( Exception $ex ) { + } catch ( Exception $ex ) { // @todo Remove this block when HHVM is no longer supported + $module = null; + } catch ( Throwable $ex ) { $module = null; } if ( !$module || $module->mustBePosted() ) { diff --git a/autoload.php b/autoload.php index 1c1eeeff22..f63ccb5055 100644 --- a/autoload.php +++ b/autoload.php @@ -12,6 +12,7 @@ $wgAutoloadLocalClasses = [ 'ActiveUsersPager' => __DIR__ . '/includes/specials/pagers/ActiveUsersPager.php', 'ActivityUpdateJob' => __DIR__ . '/includes/jobqueue/jobs/ActivityUpdateJob.php', 'ActorMigration' => __DIR__ . '/includes/ActorMigration.php', + 'AddChangeTag' => __DIR__ . '/maintenance/addChangeTag.php', 'AddRFCandPMIDInterwiki' => __DIR__ . '/maintenance/addRFCandPMIDInterwiki.php', 'AddSite' => __DIR__ . '/maintenance/addSite.php', 'AjaxDispatcher' => __DIR__ . '/includes/AjaxDispatcher.php', @@ -173,7 +174,7 @@ $wgAutoloadLocalClasses = [ 'AvroValidator' => __DIR__ . '/includes/utils/AvroValidator.php', 'BacklinkCache' => __DIR__ . '/includes/cache/BacklinkCache.php', 'BacklinkJobUtils' => __DIR__ . '/includes/jobqueue/utils/BacklinkJobUtils.php', - 'BackupDumper' => __DIR__ . '/maintenance/backup.inc', + 'BackupDumper' => __DIR__ . '/maintenance/includes/BackupDumper.php', 'BackupReader' => __DIR__ . '/maintenance/importDump.php', 'BadRequestError' => __DIR__ . '/includes/exception/BadRequestError.php', 'BadTitleError' => __DIR__ . '/includes/exception/BadTitleError.php', @@ -187,7 +188,6 @@ $wgAutoloadLocalClasses = [ 'BcryptPassword' => __DIR__ . '/includes/password/BcryptPassword.php', 'BenchHttpHttps' => __DIR__ . '/maintenance/benchmarks/bench_HTTP_HTTPS.php', 'BenchIfSwitch' => __DIR__ . '/maintenance/benchmarks/bench_if_switch.php', - 'BenchStrtrStrReplace' => __DIR__ . '/maintenance/benchmarks/bench_strtr_str_replace.php', 'BenchUtf8TitleCheck' => __DIR__ . '/maintenance/benchmarks/bench_utf8_title_check.php', 'BenchWfIsWindows' => __DIR__ . '/maintenance/benchmarks/bench_wfIsWindows.php', 'BenchWikimediaBaseConvert' => __DIR__ . '/maintenance/benchmarks/bench_Wikimedia_base_convert.php', @@ -200,6 +200,7 @@ $wgAutoloadLocalClasses = [ 'BenchmarkParse' => __DIR__ . '/maintenance/benchmarks/benchmarkParse.php', 'BenchmarkPurge' => __DIR__ . '/maintenance/benchmarks/benchmarkPurge.php', 'BenchmarkSanitizer' => __DIR__ . '/maintenance/benchmarks/benchmarkSanitizer.php', + 'BenchmarkStringReplacement' => __DIR__ . '/maintenance/benchmarks/benchmarkStringReplacement.php', 'BenchmarkTidy' => __DIR__ . '/maintenance/benchmarks/benchmarkTidy.php', 'BenchmarkTitleValue' => __DIR__ . '/maintenance/benchmarks/benchmarkTitleValue.php', 'Benchmarker' => __DIR__ . '/maintenance/benchmarks/Benchmarker.php', @@ -284,7 +285,6 @@ $wgAutoloadLocalClasses = [ 'CodeContentHandler' => __DIR__ . '/includes/content/CodeContentHandler.php', 'Collation' => __DIR__ . '/includes/collation/Collation.php', 'CollationCkb' => __DIR__ . '/includes/collation/CollationCkb.php', - 'CollationEt' => __DIR__ . '/includes/collation/CollationEt.php', 'CommandLineInc' => __DIR__ . '/maintenance/commandLine.inc', 'CommandLineInstaller' => __DIR__ . '/maintenance/install.php', 'CommentStore' => __DIR__ . '/includes/CommentStore.php', @@ -405,6 +405,7 @@ $wgAutoloadLocalClasses = [ 'DiffOpCopy' => __DIR__ . '/includes/diff/DairikiDiff.php', 'DiffOpDelete' => __DIR__ . '/includes/diff/DairikiDiff.php', 'DifferenceEngine' => __DIR__ . '/includes/diff/DifferenceEngine.php', + 'DifferenceEngineSlotDiffRenderer' => __DIR__ . '/includes/diff/DifferenceEngineSlotDiffRenderer.php', 'Digit2Html' => __DIR__ . '/maintenance/language/digit2html.php', 'DjVuHandler' => __DIR__ . '/includes/media/DjVuHandler.php', 'DjVuImage' => __DIR__ . '/includes/media/DjVuImage.php', @@ -716,7 +717,6 @@ $wgAutoloadLocalClasses = [ 'JsonContentHandler' => __DIR__ . '/includes/content/JsonContentHandler.php', 'KkConverter' => __DIR__ . '/languages/classes/LanguageKk.php', 'KuConverter' => __DIR__ . '/languages/classes/LanguageKu.php', - 'LBFactory' => __DIR__ . '/includes/libs/rdbms/lbfactory/LBFactory.php', 'LCStore' => __DIR__ . '/includes/cache/localisation/LCStore.php', 'LCStoreCDB' => __DIR__ . '/includes/cache/localisation/LCStoreCDB.php', 'LCStoreDB' => __DIR__ . '/includes/cache/localisation/LCStoreDB.php', @@ -842,12 +842,14 @@ $wgAutoloadLocalClasses = [ 'Maintenance' => __DIR__ . '/maintenance/Maintenance.php', 'MakeTestEdits' => __DIR__ . '/maintenance/makeTestEdits.php', 'MalformedTitleException' => __DIR__ . '/includes/title/MalformedTitleException.php', + 'ManageForeignResources' => __DIR__ . '/maintenance/resources/manageForeignResources.php', 'ManageJobs' => __DIR__ . '/maintenance/manageJobs.php', 'ManualLogEntry' => __DIR__ . '/includes/logging/LogEntry.php', 'MapCacheLRU' => __DIR__ . '/includes/libs/MapCacheLRU.php', 'MappedIterator' => __DIR__ . '/includes/libs/MappedIterator.php', 'MarkpatrolledAction' => __DIR__ . '/includes/actions/MarkpatrolledAction.php', 'McTest' => __DIR__ . '/maintenance/mctest.php', + 'McrUndoAction' => __DIR__ . '/includes/actions/McrUndoAction.php', 'MediaHandler' => __DIR__ . '/includes/media/MediaHandler.php', 'MediaHandlerFactory' => __DIR__ . '/includes/media/MediaHandlerFactory.php', 'MediaStatisticsPage' => __DIR__ . '/includes/specials/SpecialMediaStatistics.php', @@ -859,41 +861,6 @@ $wgAutoloadLocalClasses = [ 'MediaWikiSite' => __DIR__ . '/includes/site/MediaWikiSite.php', 'MediaWikiTitleCodec' => __DIR__ . '/includes/title/MediaWikiTitleCodec.php', 'MediaWikiVersionFetcher' => __DIR__ . '/includes/MediaWikiVersionFetcher.php', - 'MediaWiki\\Auth\\AbstractAuthenticationProvider' => __DIR__ . '/includes/auth/AbstractAuthenticationProvider.php', - 'MediaWiki\\Auth\\AbstractPasswordPrimaryAuthenticationProvider' => __DIR__ . '/includes/auth/AbstractPasswordPrimaryAuthenticationProvider.php', - 'MediaWiki\\Auth\\AbstractPreAuthenticationProvider' => __DIR__ . '/includes/auth/AbstractPreAuthenticationProvider.php', - 'MediaWiki\\Auth\\AbstractPrimaryAuthenticationProvider' => __DIR__ . '/includes/auth/AbstractPrimaryAuthenticationProvider.php', - 'MediaWiki\\Auth\\AbstractSecondaryAuthenticationProvider' => __DIR__ . '/includes/auth/AbstractSecondaryAuthenticationProvider.php', - 'MediaWiki\\Auth\\AuthManager' => __DIR__ . '/includes/auth/AuthManager.php', - 'MediaWiki\\Auth\\AuthManagerAuthPlugin' => __DIR__ . '/includes/auth/AuthManagerAuthPlugin.php', - 'MediaWiki\\Auth\\AuthManagerAuthPluginUser' => __DIR__ . '/includes/auth/AuthManagerAuthPlugin.php', - 'MediaWiki\\Auth\\AuthPluginPrimaryAuthenticationProvider' => __DIR__ . '/includes/auth/AuthPluginPrimaryAuthenticationProvider.php', - 'MediaWiki\\Auth\\AuthenticationProvider' => __DIR__ . '/includes/auth/AuthenticationProvider.php', - 'MediaWiki\\Auth\\AuthenticationRequest' => __DIR__ . '/includes/auth/AuthenticationRequest.php', - 'MediaWiki\\Auth\\AuthenticationResponse' => __DIR__ . '/includes/auth/AuthenticationResponse.php', - 'MediaWiki\\Auth\\ButtonAuthenticationRequest' => __DIR__ . '/includes/auth/ButtonAuthenticationRequest.php', - 'MediaWiki\\Auth\\CheckBlocksSecondaryAuthenticationProvider' => __DIR__ . '/includes/auth/CheckBlocksSecondaryAuthenticationProvider.php', - 'MediaWiki\\Auth\\ConfirmLinkAuthenticationRequest' => __DIR__ . '/includes/auth/ConfirmLinkAuthenticationRequest.php', - 'MediaWiki\\Auth\\ConfirmLinkSecondaryAuthenticationProvider' => __DIR__ . '/includes/auth/ConfirmLinkSecondaryAuthenticationProvider.php', - 'MediaWiki\\Auth\\CreateFromLoginAuthenticationRequest' => __DIR__ . '/includes/auth/CreateFromLoginAuthenticationRequest.php', - 'MediaWiki\\Auth\\CreatedAccountAuthenticationRequest' => __DIR__ . '/includes/auth/CreatedAccountAuthenticationRequest.php', - 'MediaWiki\\Auth\\CreationReasonAuthenticationRequest' => __DIR__ . '/includes/auth/CreationReasonAuthenticationRequest.php', - 'MediaWiki\\Auth\\EmailNotificationSecondaryAuthenticationProvider' => __DIR__ . '/includes/auth/EmailNotificationSecondaryAuthenticationProvider.php', - 'MediaWiki\\Auth\\LegacyHookPreAuthenticationProvider' => __DIR__ . '/includes/auth/LegacyHookPreAuthenticationProvider.php', - 'MediaWiki\\Auth\\LocalPasswordPrimaryAuthenticationProvider' => __DIR__ . '/includes/auth/LocalPasswordPrimaryAuthenticationProvider.php', - 'MediaWiki\\Auth\\PasswordAuthenticationRequest' => __DIR__ . '/includes/auth/PasswordAuthenticationRequest.php', - 'MediaWiki\\Auth\\PasswordDomainAuthenticationRequest' => __DIR__ . '/includes/auth/PasswordDomainAuthenticationRequest.php', - 'MediaWiki\\Auth\\PreAuthenticationProvider' => __DIR__ . '/includes/auth/PreAuthenticationProvider.php', - 'MediaWiki\\Auth\\PrimaryAuthenticationProvider' => __DIR__ . '/includes/auth/PrimaryAuthenticationProvider.php', - 'MediaWiki\\Auth\\RememberMeAuthenticationRequest' => __DIR__ . '/includes/auth/RememberMeAuthenticationRequest.php', - 'MediaWiki\\Auth\\ResetPasswordSecondaryAuthenticationProvider' => __DIR__ . '/includes/auth/ResetPasswordSecondaryAuthenticationProvider.php', - 'MediaWiki\\Auth\\SecondaryAuthenticationProvider' => __DIR__ . '/includes/auth/SecondaryAuthenticationProvider.php', - 'MediaWiki\\Auth\\TemporaryPasswordAuthenticationRequest' => __DIR__ . '/includes/auth/TemporaryPasswordAuthenticationRequest.php', - 'MediaWiki\\Auth\\TemporaryPasswordPrimaryAuthenticationProvider' => __DIR__ . '/includes/auth/TemporaryPasswordPrimaryAuthenticationProvider.php', - 'MediaWiki\\Auth\\ThrottlePreAuthenticationProvider' => __DIR__ . '/includes/auth/ThrottlePreAuthenticationProvider.php', - 'MediaWiki\\Auth\\Throttler' => __DIR__ . '/includes/auth/Throttler.php', - 'MediaWiki\\Auth\\UserDataAuthenticationRequest' => __DIR__ . '/includes/auth/UserDataAuthenticationRequest.php', - 'MediaWiki\\Auth\\UsernameAuthenticationRequest' => __DIR__ . '/includes/auth/UsernameAuthenticationRequest.php', 'MediaWiki\\Config\\ConfigRepository' => __DIR__ . '/includes/config/ConfigRepository.php', 'MediaWiki\\DB\\PatchFileLocation' => __DIR__ . '/includes/db/PatchFileLocation.php', 'MediaWiki\\Diff\\ComplexityException' => __DIR__ . '/includes/diff/ComplexityException.php', @@ -927,11 +894,16 @@ $wgAutoloadLocalClasses = [ 'MediaWiki\\MediaWikiServices' => __DIR__ . '/includes/MediaWikiServices.php', 'MediaWiki\\OutputHandler' => __DIR__ . '/includes/OutputHandler.php', 'MediaWiki\\ProcOpenError' => __DIR__ . '/includes/exception/ProcOpenError.php', + 'MediaWiki\\Revision\\RenderedRevision' => __DIR__ . '/includes/Revision/RenderedRevision.php', + 'MediaWiki\\Revision\\RevisionRenderer' => __DIR__ . '/includes/Revision/RevisionRenderer.php', + 'MediaWiki\\Revision\\SlotRenderingProvider' => __DIR__ . '/includes/Revision/SlotRenderingProvider.php', 'MediaWiki\\Search\\ParserOutputSearchDataExtractor' => __DIR__ . '/includes/search/ParserOutputSearchDataExtractor.php', 'MediaWiki\\ShellDisabledError' => __DIR__ . '/includes/exception/ShellDisabledError.php', 'MediaWiki\\Site\\MediaWikiPageNameNormalizer' => __DIR__ . '/includes/site/MediaWikiPageNameNormalizer.php', + 'MediaWiki\\Special\\SpecialPageFactory' => __DIR__ . '/includes/specialpage/SpecialPageFactory.php', 'MediaWiki\\User\\UserIdentity' => __DIR__ . '/includes/user/UserIdentity.php', 'MediaWiki\\User\\UserIdentityValue' => __DIR__ . '/includes/user/UserIdentityValue.php', + 'MediaWiki\\Widget\\CheckMatrixWidget' => __DIR__ . '/includes/widget/CheckMatrixWidget.php', 'MediaWiki\\Widget\\ComplexNamespaceInputWidget' => __DIR__ . '/includes/widget/ComplexNamespaceInputWidget.php', 'MediaWiki\\Widget\\ComplexTitleInputWidget' => __DIR__ . '/includes/widget/ComplexTitleInputWidget.php', 'MediaWiki\\Widget\\DateInputWidget' => __DIR__ . '/includes/widget/DateInputWidget.php', @@ -978,7 +950,6 @@ $wgAutoloadLocalClasses = [ 'MigrateFileRepoLayout' => __DIR__ . '/maintenance/migrateFileRepoLayout.php', 'MigrateUserGroup' => __DIR__ . '/maintenance/migrateUserGroup.php', 'MimeAnalyzer' => __DIR__ . '/includes/libs/mime/MimeAnalyzer.php', - 'MimeMagic' => __DIR__ . '/includes/MimeMagic.php', 'MinifyScript' => __DIR__ . '/maintenance/minify.php', 'MostcategoriesPage' => __DIR__ . '/includes/specials/SpecialMostcategories.php', 'MostimagesPage' => __DIR__ . '/includes/specials/SpecialMostimages.php', @@ -1131,6 +1102,7 @@ $wgAutoloadLocalClasses = [ 'PreferencesFormLegacy' => __DIR__ . '/includes/specials/forms/PreferencesFormLegacy.php', 'PreferencesFormOOUI' => __DIR__ . '/includes/specials/forms/PreferencesFormOOUI.php', 'PrefixSearch' => __DIR__ . '/includes/PrefixSearch.php', + 'PrefixingStatsdDataFactoryProxy' => __DIR__ . '/includes/libs/stats/PrefixingStatsdDataFactoryProxy.php', 'PreprocessDump' => __DIR__ . '/maintenance/preprocessDump.php', 'Preprocessor' => __DIR__ . '/includes/parser/Preprocessor.php', 'Preprocessor_DOM' => __DIR__ . '/includes/parser/Preprocessor_DOM.php', @@ -1341,6 +1313,7 @@ $wgAutoloadLocalClasses = [ 'SkinFallbackTemplate' => __DIR__ . '/includes/skins/SkinFallbackTemplate.php', 'SkinTemplate' => __DIR__ . '/includes/skins/SkinTemplate.php', 'SlideshowImageGallery' => __DIR__ . '/includes/gallery/SlideshowImageGallery.php', + 'SlotDiffRenderer' => __DIR__ . '/includes/diff/SlotDiffRenderer.php', 'SpecialActiveUsers' => __DIR__ . '/includes/specials/SpecialActiveusers.php', 'SpecialAllMessages' => __DIR__ . '/includes/specials/SpecialAllMessages.php', 'SpecialAllMyUploads' => __DIR__ . '/includes/specials/SpecialMyRedirectPages.php', @@ -1392,7 +1365,7 @@ $wgAutoloadLocalClasses = [ 'SpecialPage' => __DIR__ . '/includes/specialpage/SpecialPage.php', 'SpecialPageAction' => __DIR__ . '/includes/actions/SpecialPageAction.php', 'SpecialPageData' => __DIR__ . '/includes/specials/SpecialPageData.php', - 'SpecialPageFactory' => __DIR__ . '/includes/specialpage/SpecialPageFactory.php', + 'SpecialPageFactory' => __DIR__ . '/includes/specialpage/SpecialPageFactory_deprecated.php', 'SpecialPageLanguage' => __DIR__ . '/includes/specials/SpecialPageLanguage.php', 'SpecialPagesWithProp' => __DIR__ . '/includes/specials/SpecialPagesWithProp.php', 'SpecialPasswordPolicies' => __DIR__ . '/includes/specials/SpecialPasswordPolicies.php', @@ -1473,12 +1446,13 @@ $wgAutoloadLocalClasses = [ 'TextContent' => __DIR__ . '/includes/content/TextContent.php', 'TextContentHandler' => __DIR__ . '/includes/content/TextContentHandler.php', 'TextPassDumper' => __DIR__ . '/maintenance/dumpTextPass.php', + 'TextSlotDiffRenderer' => __DIR__ . '/includes/diff/TextSlotDiffRenderer.php', 'TextStatsOutput' => __DIR__ . '/maintenance/language/StatOutputs.php', 'TgConverter' => __DIR__ . '/languages/classes/LanguageTg.php', 'ThrottledError' => __DIR__ . '/includes/exception/ThrottledError.php', 'ThumbnailImage' => __DIR__ . '/includes/media/MediaTransformOutput.php', 'ThumbnailRenderJob' => __DIR__ . '/includes/jobqueue/jobs/ThumbnailRenderJob.php', - 'TidyUpBug37714' => __DIR__ . '/maintenance/tidyUpBug37714.php', + 'TidyUpT39714' => __DIR__ . '/maintenance/tidyUpT39714.php', 'TiffHandler' => __DIR__ . '/includes/media/TiffHandler.php', 'Timing' => __DIR__ . '/includes/libs/Timing.php', 'Title' => __DIR__ . '/includes/Title.php', @@ -1681,6 +1655,7 @@ $wgAutoloadLocalClasses = [ 'Wikimedia\\Rdbms\\SessionConsistentConnectionManager' => __DIR__ . '/includes/libs/rdbms/connectionmanager/SessionConsistentConnectionManager.php', 'Wikimedia\\Rdbms\\Subquery' => __DIR__ . '/includes/libs/rdbms/encasing/Subquery.php', 'Wikimedia\\Rdbms\\TransactionProfiler' => __DIR__ . '/includes/libs/rdbms/TransactionProfiler.php', + 'Wikimedia\\StaticArrayWriter' => __DIR__ . '/includes/libs/StaticArrayWriter.php', 'WikitextContent' => __DIR__ . '/includes/content/WikitextContent.php', 'WikitextContentHandler' => __DIR__ . '/includes/content/WikitextContentHandler.php', 'WikitextLogFormatter' => __DIR__ . '/includes/logging/WikitextLogFormatter.php', diff --git a/composer.json b/composer.json index 454e27c6ac..e99fe8193f 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ "ext-mbstring": "*", "ext-xml": "*", "liuggio/statsd-php-client": "1.0.18", - "oojs/oojs-ui": "0.28.0", + "oojs/oojs-ui": "0.28.2", "oyejorge/less.php": "1.7.0.14", "pear/mail": "1.4.1", "pear/mail_mime": "1.10.2", @@ -48,7 +48,7 @@ "wikimedia/running-stat": "1.2.1", "wikimedia/scoped-callback": "2.0.0", "wikimedia/utfnormal": "2.0.0", - "wikimedia/timestamp": "2.0.0", + "wikimedia/timestamp": "2.1.1", "wikimedia/wait-condition-loop": "1.0.1", "wikimedia/wrappedstring": "3.0.1", "wikimedia/xmp-reader": "0.6.0", @@ -57,11 +57,12 @@ "require-dev": { "cache/integration-tests": "0.16.0", "composer/spdx-licenses": "1.4.0", + "giorgiosironi/eris": "^0.10.0", "hamcrest/hamcrest-php": "^2.0", "jakub-onderka/php-parallel-lint": "0.9.2", "jetbrains/phpstorm-stubs": "dev-master#38ff1a581b297f7901e961b8c923862ea80c3b96", "justinrainbow/json-schema": "~5.2", - "mediawiki/mediawiki-codesniffer": "21.0.0", + "mediawiki/mediawiki-codesniffer": "22.0.0", "monolog/monolog": "~1.22.1", "nikic/php-parser": "3.1.3", "seld/jsonlint": "1.7.1", diff --git a/docs/design.txt b/docs/design.txt deleted file mode 100644 index 5c04addef3..0000000000 --- a/docs/design.txt +++ /dev/null @@ -1,106 +0,0 @@ -design.txt - -This is a brief overview of the new design. - -More thorough and up-to-date information is available on the documentation -wiki at https://www.mediawiki.org/ - -Primary classes: - - User - Encapsulates the state of the user viewing/using the site. Can be queried - for things like the user's settings, name, etc. Handles the details of - getting and saving to the "user" table of the database, and dealing with - sessions and cookies. - - OutputPage - Encapsulates the entire HTML page that will be sent in response to any - server request. It is used by calling its functions to add text, headers, - etc., in any order, and then calling output() to send it all. It could be - easily changed to send incrementally if that becomes useful, but I prefer - the flexibility. This should also do the output encoding. The system - allocates a global one in $wgOut. - - Title - Represents the title of an article, and does all the work of translating - among various forms such as plain text, URL, database key, etc. For - convenience, and for historical reasons, it also represents a few features - of articles that don't involve their text, such as access rights. - See also title.txt. - - Article - Encapsulates access to the "page" table of the database. The object - represents a an article, and maintains state such as text (in Wikitext - format), flags, etc. - - Revision - Encapsulates individual page revision data and access to the - revision/text/blobs storage system. Higher-level code should never touch - text storage directly; this class mediates it. - - Skin - Encapsulates a "look and feel" for the wiki. All of the functions that - render HTML, and make choices about how to render it, are here, and are - called from various other places when needed (most notably, - OutputPage::addWikiText()). The StandardSkin object is a complete - implementation, and is meant to be subclassed with other skins that may - override some of its functions. The User object contains a reference to a - skin (according to that user's preference), and so rather than having a - global skin object we just rely on the global User and get the skin with - $wgUser->getSkin(). - See also skin.txt. - - Language - Represents the language used for incidental text, and also has some - character encoding functions and other locale stuff. The current user - interface language is instantiated as $wgLang, and the local content - language as $wgContLang; be sure to use the *correct* language object - depending upon the circumstances. - See also language.txt. - - Parser - Class used to transform wikitext to html. - - LinkCache - Keeps information on existence of articles. See linkcache.txt. - -Naming/coding conventions: - - These are meant to be descriptive, not dictatorial; I won't presume to tell - you how to program, I'm just describing the methods I chose to use for myself. - If you do choose to follow these guidelines, it will probably be easier for - you to collaborate with others on the project, but if you want to contribute - without bothering, by all means do so (and don't be surprised if I reformat - your code). - - - I have the code indented with tabs to save file size and so that users can - set their tab stops to any depth they like. I use 4-space tab stops, which - work well. I also use K&R brace matching style. I know that's a religious - issue for some, so if you want to use a style that puts opening braces on - the next line, that's OK too, but please don't use a style where closing - braces don't align with either the opening brace on its own line or the - statement that opened the block--that's confusing as hell. - - - Certain functions and class members are marked with /* private */, rather - than being marked as such. This is a hold-over from PHP 4, which didn't - support proper visibilities. You should not access things marked in this - manner outside the class/inheritance line as this code is subjected to be - updated in a manner that enforces this at some time in the near future, and - things will break. New code should use the standard method of setting - visibilities as normal. - - - Globals are particularly evil in PHP; it sets a lot of them automatically - from cookies, query strings, and such, leading to namespace conflicts; when - a variable name is used in a function, it is silently declared as a new - local masking the global, so you'll get weird error because you forgot the - global declaration; lack of static class member variables means you have to - use globals for them, etc. Evil, evil. - - I think I've managed to pare down the number of globals we use to a scant - few dozen or so, and I've prefixed them all with "wg" so you can spot errors - better (odds are, if you see a "wg" variable being used in a function that - doesn't declare it global, that's probably an error). - - Other conventions: Top-level functions are wfFuncname(), names of session - variables are wsName, cookies wcName, and form field values wpName ("p" for - "POST"). diff --git a/docs/distributors.txt b/docs/distributors.txt index 729dffa3a1..f2af458937 100644 --- a/docs/distributors.txt +++ b/docs/distributors.txt @@ -174,8 +174,6 @@ perhaps configure it to use them (see Configuration section of this document): "$wgAntivirus = 'clamav';". * DjVuLibre: Allows processing of DjVu files. To enable this, set "$wgDjvuDump = 'djvudump'; $wgDjvuRenderer = 'ddjvu'; $wgDjvuTxt = 'djvutxt';". - * HTML Tidy: Fixes errors in HTML at runtime. Can be enabled with - "$wgUseTidy = true;". * ImageMagick: For resizing images. "$wgUseImageMagick = true;" will enable it. PHP's GD can also be used, but ImageMagick is preferable. * HTTP cache such as Varnish or Squid: can provide a drastic speedup and a diff --git a/docs/extension.schema.v1.json b/docs/extension.schema.v1.json index c9a887dd9c..e6ec971e51 100644 --- a/docs/extension.schema.v1.json +++ b/docs/extension.schema.v1.json @@ -55,13 +55,24 @@ }, "requires": { "type": "object", - "description": "Indicates what versions of MediaWiki core or extensions are required. This syntax may be extended in the future, for example to check dependencies between other services.", + "description": "Indicates what versions of PHP, MediaWiki core or extensions are required. This syntax may be extended in the future, for example to check dependencies between other services.", "additionalProperties": false, "properties": { "MediaWiki": { "type": "string", "description": "Version constraint string against MediaWiki core." }, + "platform": { + "type": "object", + "description": "Indicates version constraints against platform services.", + "additionalProperties": false, + "properties": { + "php": { + "type": "string", + "description": "Version constraint string against PHP." + } + } + }, "extensions": { "type": "object", "description": "Set of version constraint strings against specific extensions." @@ -668,6 +679,13 @@ "type": "string" } }, + "RawHtmlMessages": { + "type": "array", + "description": "Messages which are rendered as raw HTML", + "items": { + "type": "string" + } + }, "callback": { "type": [ "array", diff --git a/docs/extension.schema.v2.json b/docs/extension.schema.v2.json index 24212a9a54..93bf0d908a 100644 --- a/docs/extension.schema.v2.json +++ b/docs/extension.schema.v2.json @@ -56,13 +56,24 @@ }, "requires": { "type": "object", - "description": "Indicates what versions of MediaWiki core or extensions are required. This syntax may be extended in the future, for example to check dependencies between other services.", + "description": "Indicates what versions of PHP, MediaWiki core or extensions are required. This syntax may be extended in the future, for example to check dependencies between other services.", "additionalProperties": false, "properties": { "MediaWiki": { "type": "string", "description": "Version constraint string against MediaWiki core." }, + "platform": { + "type": "object", + "description": "Indicates version constraints against platform services.", + "additionalProperties": false, + "properties": { + "php": { + "type": "string", + "description": "Version constraint string against PHP." + } + } + }, "extensions": { "type": "object", "description": "Set of version constraint strings against specific extensions." @@ -690,6 +701,13 @@ "type": "string" } }, + "RawHtmlMessages": { + "type": "array", + "description": "Messages which are rendered as raw HTML", + "items": { + "type": "string" + } + }, "callback": { "type": [ "array", diff --git a/docs/hooks.txt b/docs/hooks.txt index 219c51f30e..063bbe5ba0 100644 --- a/docs/hooks.txt +++ b/docs/hooks.txt @@ -348,6 +348,12 @@ $from: From address $subject: Subject of the email $body: Body of the message +'AncientPagesQuery': Allow extensions to modify the query used by +Special:AncientPages. +&$tables: tables to join in the query +&$conds: conditions for the query +&$joinConds: join conditions for the query + 'APIAfterExecute': After calling the execute() method of an API module. Use this to extend core API modules. &$module: Module object @@ -618,8 +624,8 @@ a chance to hide their (unrelated) log entries. AND in the final query) $logTypes: Array of log types being queried -'ArticleAfterFetchContentObject': After fetching content of an article from the -database. +'ArticleAfterFetchContentObject': DEPRECATED since 1.32, use ArticleRevisionViewCustom +to control output. After fetching content of an article from the database. &$article: the article (object) being loaded from the database &$content: the content of the article, as a Content object @@ -634,12 +640,21 @@ this to change the content in this area or how it is loaded. $diffEngine: the DifferenceEngine $output: the OutputPage object -'ArticleContentViewCustom': Allows to output the text of the article in a -different format than wikitext. Note that it is preferable to implement proper -handing for a custom data type using the ContentHandler facility. +'ArticleRevisionViewCustom': Allows custom rendering of an article's content. +Note that it is preferable to implement proper handing for a custom data type using +the ContentHandler facility. +$revision: content of the page, as a RevisionRecord object, or null if the revision + could not be loaded. May also be a fake that wraps content supplied by an extension. +$title: title of the page +$oldid: the requested revision id, or 0 for the currrent revision. +$output: a ParserOutput object + +'ArticleContentViewCustom': DEPRECATED since 1.32, use ArticleRevisionViewCustom instead, +or provide an appropriate ContentHandler. Allows to output the text of the article in a +different format than wikitext. $content: content of the page, as a Content object $title: title of the page -$output: reference to $wgOut +$output: a ParserOutput object 'ArticleDelete': Before an article is deleted. &$wikiPage: the WikiPage (object) being deleted @@ -769,8 +784,8 @@ $article: the article $article: Article object $patrolFooterShown: boolean whether patrol footer is shown -'ArticleViewHeader': Before the parser cache is about to be tried for article -viewing. +'ArticleViewHeader': Control article output. Called before the parser cache is about +to be tried for article viewing. &$article: the article &$pcache: whether to try the parser cache or not &$outputDone: whether the output for this page finished or not. Set to @@ -1670,15 +1685,13 @@ $title: Title object that we need to get a sortkey for &$sortkey: Sortkey to use. 'GetDifferenceEngine': Called when getting a new difference engine interface -object Return false for valid object in $differenceEngine or true for the -default difference engine. +object. Can be used to decorate or replace the default difference engine. $context: IContextSource context to be used for diff $old: Revision ID to show and diff with $new: Either a revision ID or one of the strings 'cur', 'prev' or 'next' $refreshCache: If set, refreshes the diff cache $unhide: If set, allow viewing deleted revs -&$differenceEngine: output parameter, difference engine object to be used for - diff +&$differenceEngine: The difference engine object to be used for the diff 'GetDoubleUnderscoreIDs': Modify the list of behavior switch (double underscore) magic words. Called by MagicWord. @@ -1785,6 +1798,12 @@ $relativeTo: MWTimestamp object of the relative (user-adjusted) timestamp $user: User whose preferences are being used to make timestamp $lang: Language that will be used to render the timestamp +'GetSlotDiffRenderer': Replace or wrap the standard SlotDiffRenderer for some +content type. +$contentHandler: ContentHandler for which the slot diff renderer is fetched. +&$slotDiffRenderer: SlotDiffRenderer to change or replace. +$context: IContextSource + 'getUserPermissionsErrors': Add a permissions error when permissions errors are checked for. Use instead of userCan for most cases. Return false if the user can't do it, and populate $result with the reason in the form of @@ -2334,7 +2353,7 @@ $title: title of the message (string) $code: code (string) denoting the language to try. 'MimeMagicGuessFromContent': Allows MW extensions guess the MIME by content. -$mimeMagic: Instance of MimeMagic. +$mimeMagic: Instance of MimeAnalyzer. &$head: First 1024 bytes of the file in a string (in - Do not alter!). &$tail: More or equal than last 65558 bytes of the file in a string (in - Do not alter!). @@ -2343,7 +2362,7 @@ $file: File path. 'MimeMagicImproveFromExtension': Allows MW extensions to further improve the MIME type detected by considering the file extension. -$mimeMagic: Instance of MimeMagic. +$mimeMagic: Instance of MimeAnalyzer. $ext: File extension. &$mime: MIME type (in/out). @@ -2351,7 +2370,7 @@ $ext: File extension. and the list mapping MIME types to file extensions. As an extension author, you are encouraged to submit patches to MediaWiki's core to add new MIME types to mime.types. -$mimeMagic: Instance of MimeMagic. +$mimeMagic: Instance of MimeAnalyzer. Use $mimeMagic->addExtraInfo( $stringOfInfo ); for adding new MIME info to the list. Use $mimeMagic->addExtraTypes( $stringOfTypes ); @@ -2527,6 +2546,12 @@ $originalRevId: if the edit restores or repeats an earlier revision (such as a (Used to be called $baseRevId.) $undidRevId: the rev ID (or 0) this edit undid +'PageDeletionDataUpdates': Called when constructing a list of DeferrableUpdate to be +executed when a page is deleted. +$title The Title of the page being deleted. +$revision A RevisionRecord representing the page's current revision at the time of deletion. +&$updates A list of DeferrableUpdate that can be manipulated by the hook handler. + 'PageHistoryBeforeList': When a history page list is about to be constructed. &$article: the article that the history is loading for $context: RequestContext object @@ -2900,6 +2925,13 @@ called after the addition of 'qunit' and MediaWiki testing resources. added to any module. &$ResourceLoader: object +'RevisionDataUpdates': Called when constructing a list of DeferrableUpdate to be +executed to record secondary data about a revision. +$title The Title of the page the revision belongs to +$renderedRevision a RenderedRevision object representing the new revision and providing access + to the RevisionRecord as well as ParserOutput of that revision. +&$updates A list of DeferrableUpdate that can be manipulated by the hook handler. + 'RevisionRecordInserted': Called after a revision is inserted into the database. $revisionRecord: the RevisionRecord that has just been inserted. @@ -2959,9 +2991,9 @@ result augmentors. Note that lists should be in the format name => object and the names in both lists should be distinct. -'SecondaryDataUpdates': Allows modification of the list of DataUpdates to -perform when page content is modified. Currently called by -AbstractContent::getSecondaryDataUpdates. +'SecondaryDataUpdates': DEPRECATED! Use RevisionDataUpdates or override +ContentHandler::getSecondaryDataUpdates instead. +Allows modification of the list of DataUpdates to perform when page content is modified. $title: Title of the page that is being edited. $oldContent: Content object representing the page's content before the edit. $recursive: bool indicating whether DataUpdates should trigger recursive @@ -3301,14 +3333,6 @@ use this to change some selection criteria or substitute a different title. &$title: If the hook returns false, a Title object to use instead of the result from the normal query -'SpecialRecentChangesFilters': DEPRECATED since 1.23! Use -ChangesListSpecialPageStructuredFilters instead. -Called after building form options at RecentChanges. -$special: the special page object -&$filters: associative array of filter definitions. The keys are the HTML - name/URL parameters. Each key maps to an associative array with a 'msg' - (message key) and a 'default' value. - 'SpecialRecentChangesPanel': Called when building form options in SpecialRecentChanges. &$extraOpts: array of added items, to which can be added @@ -3423,14 +3447,6 @@ Special:Upload. $wgVersion: Current $wgVersion for you to use &$versionUrl: Raw url to link to (eg: release notes) -'SpecialWatchlistFilters': DEPRECATED since 1.23! Use -ChangesListSpecialPageStructuredFilters instead. -Called after building form options at Watchlist. -$special: the special page object -&$filters: associative array of filter definitions. The keys are the HTML - name/URL parameters. Each key maps to an associative array with a 'msg' - (message key) and a 'default' value. - 'SpecialWatchlistGetNonRevisionTypes': Called when building sql query for SpecialWatchlist. Allows extensions to register custom values they have inserted to rc_type so they can be returned as part of the watchlist. @@ -4035,10 +4051,9 @@ dumps. 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 DeferrableUpdates 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. +'WikiPageDeletionUpdates': DEPRECATED! Use PageDeletionDataUpdates or +override ContentHandler::getDeletionDataUpdates instead. +Manipulates the list of DeferrableUpdates to be applied when a page is deleted. $page: the WikiPage $content: the Content to generate updates for, or null in case the page revision could not be loaded. The delete will succeed despite this. diff --git a/docs/scripts.txt b/docs/scripts.txt index dff428c5a2..b2501d4469 100644 --- a/docs/scripts.txt +++ b/docs/scripts.txt @@ -28,19 +28,9 @@ Primary scripts: that points to the search engines of the wiki. profileinfo.php - Allow users to see the profiling information that are stored in the - database. - - To save the profiling information in the database (required to use this - script), you have to modify StartProfiler.php to use the Profiler class and - not the stub profiler which is enabled by default. - You will also need to set $wgProfiler['output'] to 'db' in LocalSettings.php - to force the profiler to save the informations in the database and apply the - maintenance/archives/patch-profiling.sql patch to the database. - - To enable the profileinfo.php itself, you'll need to set $wgDBadminuser - and $wgDBadminpassword in your LocalSettings.php, as well as $wgEnableProfileInfo - See also https://www.mediawiki.org/wiki/Manual:Profiling . + Simple interface for displaying request profiles that were stored in the + database. For more information, see the documentation in that file, and at + https://www.mediawiki.org/wiki/Manual:Profiling. thumb.php Script used to resize images if it is configured to be done when the web diff --git a/includes/AjaxDispatcher.php b/includes/AjaxDispatcher.php index 5f825c8b5a..f6c9075136 100644 --- a/includes/AjaxDispatcher.php +++ b/includes/AjaxDispatcher.php @@ -104,6 +104,9 @@ class AjaxDispatcher { * they should be carefully handled in the function processing the * request. * + * phan-taint-check triggers as it is not smart enough to understand + * the early return if func_name not in AjaxExportList. + * @suppress SecurityCheck-XSS * @param User $user */ function performAction( User $user ) { diff --git a/includes/AutoLoader.php b/includes/AutoLoader.php index 9fc063a285..5482f6a4e3 100644 --- a/includes/AutoLoader.php +++ b/includes/AutoLoader.php @@ -129,16 +129,17 @@ class AutoLoader { */ public static function getAutoloadNamespaces() { return [ + 'MediaWiki\\Auth\\' => __DIR__ . '/auth/', 'MediaWiki\\Edit\\' => __DIR__ . '/edit/', 'MediaWiki\\EditPage\\' => __DIR__ . '/editpage/', - 'MediaWiki\\Linker\\' => __DIR__ .'/linker/', - 'MediaWiki\\Preferences\\' => __DIR__ .'/preferences/', - 'MediaWiki\\Services\\' => __DIR__ .'/services/', - 'MediaWiki\\Session\\' => __DIR__ .'/session/', - 'MediaWiki\\Shell\\' => __DIR__ .'/shell/', - 'MediaWiki\\Sparql\\' => __DIR__ .'/sparql/', - 'MediaWiki\\Storage\\' => __DIR__ .'/Storage/', - 'MediaWiki\\Tidy\\' => __DIR__ .'/tidy/', + 'MediaWiki\\Linker\\' => __DIR__ . '/linker/', + 'MediaWiki\\Preferences\\' => __DIR__ . '/preferences/', + 'MediaWiki\\Services\\' => __DIR__ . '/services/', + 'MediaWiki\\Session\\' => __DIR__ . '/session/', + 'MediaWiki\\Shell\\' => __DIR__ . '/shell/', + 'MediaWiki\\Sparql\\' => __DIR__ . '/sparql/', + 'MediaWiki\\Storage\\' => __DIR__ . '/Storage/', + 'MediaWiki\\Tidy\\' => __DIR__ . '/tidy/', ]; } } diff --git a/includes/CategoryViewer.php b/includes/CategoryViewer.php index 46a1473a91..a07e1b4775 100644 --- a/includes/CategoryViewer.php +++ b/includes/CategoryViewer.php @@ -424,7 +424,7 @@ class CategoryViewer extends ContextSource { * @return string */ function getPagesSection() { - $ti = wfEscapeWikiText( $this->title->getText() ); + $name = $this->getOutput()->getUnprefixedDisplayTitle(); # Don't show articles section if there are none. $r = ''; @@ -440,7 +440,7 @@ class CategoryViewer extends ContextSource { if ( $rescnt > 0 ) { $r = "
\n"; - $r .= '

' . $this->msg( 'category_header', $ti )->parse() . "

\n"; + $r .= '

' . $this->msg( 'category_header' )->rawParams( $name )->parse() . "

\n"; $r .= $countmsg; $r .= $this->getSectionPagingLinks( 'page' ); $r .= $this->formatList( $this->articles, $this->articles_start_char ); @@ -454,6 +454,7 @@ class CategoryViewer extends ContextSource { * @return string */ function getImageSection() { + $name = $this->getOutput()->getUnprefixedDisplayTitle(); $r = ''; $rescnt = $this->showGallery ? $this->gallery->count() : count( $this->imgsNoGallery ); $dbcnt = $this->cat->getFileCount(); @@ -463,10 +464,7 @@ class CategoryViewer extends ContextSource { if ( $rescnt > 0 ) { $r .= "
\n"; $r .= '

' . - $this->msg( - 'category-media-header', - wfEscapeWikiText( $this->title->getText() ) - )->text() . + $this->msg( 'category-media-header' )->rawParams( $name )->parse() . "

\n"; $r .= $countmsg; $r .= $this->getSectionPagingLinks( 'file' ); diff --git a/includes/CommentStore.php b/includes/CommentStore.php index bf3e3d6608..9969b78782 100644 --- a/includes/CommentStore.php +++ b/includes/CommentStore.php @@ -115,8 +115,7 @@ class CommentStore { */ public static function newKey( $key ) { global $wgCommentTableSchemaMigrationStage; - // TODO uncomment once not used in extensions - // wfDeprecated( __METHOD__, '1.31' ); + wfDeprecated( __METHOD__, '1.31' ); $store = new CommentStore( MediaWikiServices::getInstance()->getContentLanguage(), $wgCommentTableSchemaMigrationStage ); $store->key = $key; diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php index e81909abb5..3ef524366d 100644 --- a/includes/DefaultSettings.php +++ b/includes/DefaultSettings.php @@ -1380,14 +1380,14 @@ $wgAntivirusRequired = true; $wgVerifyMimeType = true; /** - * Sets the MIME type definition file to use by MimeMagic.php. + * Sets the MIME type definition file to use by includes/libs/mime/MimeAnalyzer.php. * Set to null, to use built-in defaults only. * example: $wgMimeTypeFile = '/etc/mime.types'; */ $wgMimeTypeFile = 'includes/mime.types'; /** - * Sets the MIME type info file to use by MimeMagic.php. + * Sets the MIME type info file to use by includes/libs/mime/MimeAnalyzer.php. * Set to null, to use built-in defaults only. */ $wgMimeInfoFile = 'includes/mime.info'; @@ -1646,13 +1646,6 @@ $wgEmergencyContact = false; */ $wgPasswordSender = false; -/** - * Sender name for e-mail notifications. - * - * @deprecated since 1.23; use the system message 'emailsender' instead. - */ -$wgPasswordSenderName = 'MediaWiki Mail'; - /** * Reply-To address for e-mail notifications. * @@ -3295,7 +3288,7 @@ $wgUseMediaWikiUIEverywhere = false; * * @since 1.32 */ -$wgOOUIPreferences = false; +$wgOOUIPreferences = true; /** * Whether to label the store-to-database-and-show-to-others button in the editor @@ -3800,14 +3793,14 @@ $wgResourceLoaderMaxQueryLength = false; $wgResourceLoaderValidateJS = true; /** - * If set to true, statically-sourced (file-backed) JavaScript resources will - * be parsed for validity before being bundled up into ResourceLoader modules. + * When enabled, execution of JavaScript modules is profiled client-side. + * + * Instrumentation happens in mw.loader.profiler. + * Use `mw.inspect('time')` from the browser console to display the data. * - * This can be helpful for development by providing better error messages in - * default (non-debug) mode, but JavaScript parsing is slow and memory hungry - * and may fail on large pre-bundled frameworks. + * @since 1.32 */ -$wgResourceLoaderValidateStaticJS = false; +$wgResourceLoaderEnableJSProfiler = false; /** * Whether ResourceLoader should attempt to persist modules in localStorage on @@ -4274,17 +4267,26 @@ $wgAllowImageTag = false; * library; historically, Dave Raggett's "HTML Tidy" was typically used. * See https://www.w3.org/People/Raggett/tidy/ * + * Setting this to null is deprecated. + * * If this is null and $wgUseTidy is true, the deprecated configuration * parameters will be used instead. * * If this is null and $wgUseTidy is false, a pure PHP fallback will be used. + * (Equivalent to setting `$wgTidyConfig['driver'] = 'disabled'`.) * * Keys are: * - driver: May be: + * - RemexHtml: Use the RemexHtml library in PHP * - RaggettInternalHHVM: Use the limited-functionality HHVM extension + * Deprecated since 1.32. * - RaggettInternalPHP: Use the PECL extension + * Deprecated since 1.32. * - RaggettExternal: Shell out to an external binary (tidyBin) - * - RemexHtml: Use the RemexHtml library in PHP + * Deprecated since 1.32. + * - disabled: Disable tidy pass and use a hacky pure PHP workaround + * (this is what setting $wgUseTidy to false used to do) + * Deprecated since 1.32. * * - tidyConfigFile: Path to configuration file for any of the Raggett drivers * - debugComment: True to add a comment to the output with warning messages @@ -4295,37 +4297,38 @@ $wgTidyConfig = [ 'driver' => 'RemexHtml' ]; /** * Set this to true to use the deprecated tidy configuration parameters. - * @deprecated use $wgTidyConfig + * @deprecated since 1.26, use $wgTidyConfig['driver'] = 'disabled' */ $wgUseTidy = false; /** * The path to the tidy binary. - * @deprecated Use $wgTidyConfig['tidyBin'] + * @deprecated since 1.26, use $wgTidyConfig['tidyBin'] */ $wgTidyBin = 'tidy'; /** * The path to the tidy config file - * @deprecated Use $wgTidyConfig['tidyConfigFile'] + * @deprecated since 1.26, use $wgTidyConfig['tidyConfigFile'] */ $wgTidyConf = $IP . '/includes/tidy/tidy.conf'; /** * The command line options to the tidy binary - * @deprecated Use $wgTidyConfig['tidyCommandLine'] + * @deprecated since 1.26, use $wgTidyConfig['tidyCommandLine'] */ $wgTidyOpts = ''; /** * Set this to true to use the tidy extension - * @deprecated Use $wgTidyConfig['driver'] + * @deprecated since 1.26, use $wgTidyConfig['driver'] */ $wgTidyInternal = extension_loaded( 'tidy' ); /** * Put tidy warnings in HTML comments * Only works for internal tidy. + * @deprecated since 1.26, use $wgTidyConfig['debugComment'] */ $wgDebugTidy = false; @@ -4406,7 +4409,7 @@ $wgPreprocessorCacheThreshold = 1000; $wgEnableScaryTranscluding = false; /** - * Expiry time for transcluded templates cached in transcache database table. + * Expiry time for transcluded templates cached in object cache. * Only used $wgEnableInterwikiTranscluding is set to true. */ $wgTranscludeCacheExpiry = 3600; @@ -4840,7 +4843,7 @@ $wgReservedUsernames = [ 'Maintenance script', // Maintenance scripts which perform editing, image import script 'Template namespace initialisation script', // Used in 1.2->1.3 upgrade 'ScriptImporter', // Default user name used by maintenance/importSiteScripts.php - 'Unknown user', // Used in WikiImporter when importing revisions with no author + 'Unknown user', // Used in WikiImporter and RevisionStore for revisions with no author 'msg:double-redirect-fixer', // Automatic double redirect fix 'msg:usermessage-editor', // Default user for leaving user messages 'msg:proxyblocker', // For $wgProxyList and Special:Blockme (removed in 1.22) @@ -6355,8 +6358,6 @@ $wgDeprecationReleaseLimit = false; * Profiler configuration. * * To use a profiler, set $wgProfiler in LocalSetings.php. - * For backwards-compatibility, it is also allowed to set the variable from - * a separate file called StartProfiler.php, which MediaWiki will include. * * Example: * @@ -6411,6 +6412,13 @@ $wgDeprecationReleaseLimit = false; */ $wgProfiler = []; +/** + * Allow the profileinfo.php entrypoint to be used. + * + * @since 1.5.0 + */ +$wgEnableProfileInfo = false; + /** * Only record profiling info for pages that took longer than this * @deprecated since 1.25: set $wgProfiler['threshold'] instead. @@ -7361,7 +7369,7 @@ $wgExtensionMessagesFiles = []; * @code * $wgMessagesDirs['Example'] = [ * __DIR__ . '/lib/ve/i18n', - * __DIR__ . '/lib/oojs-ui/i18n', + * __DIR__ . '/lib/ooui/i18n', * __DIR__ . '/i18n', * ] * @endcode @@ -8008,6 +8016,7 @@ $wgActions = [ 'history' => true, 'info' => true, 'markpatrolled' => true, + 'mcrundo' => McrUndoAction::class, 'protect' => true, 'purge' => true, 'raw' => true, @@ -8844,6 +8853,24 @@ $wgCSPHeader = false; */ $wgCSPReportOnlyHeader = false; +/** + * List of messages which might contain raw HTML. + * Extensions should add their messages here. The list is used for access control: + * changing messages listed here will require editsitecss and editsitejs rights. + * + * Message names must be given with underscores rather than spaces and with lowercase first letter. + * + * @since 1.32 + * @var string[] + */ +$wgRawHtmlMessages = [ + 'copyright', + 'history_copyright', + 'googlesearch', + 'feedback-terms', + 'feedback-termsofuse', +]; + /** * Mapping of event channels (or channel categories) to EventRelayer configuration. * @@ -8981,7 +9008,7 @@ $wgCommentTableSchemaMigrationStage = MIGRATION_OLD; * @since 1.32 * @var int An appropriate combination of SCHEMA_COMPAT_XXX flags. */ -$wgMultiContentRevisionSchemaMigrationStage = SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD; +$wgMultiContentRevisionSchemaMigrationStage = SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW; /** * Actor table schema migration stage. @@ -9011,7 +9038,7 @@ $wgExpiryWidgetNoDatePicker = false; * @since 1.32 * @var int One of the MIGRATION_* constants */ -$wgChangeTagsSchemaMigrationStage = MIGRATION_OLD; +$wgChangeTagsSchemaMigrationStage = MIGRATION_WRITE_BOTH; /** * Temporarily flag to use change_tag_def table as backend of change tag statistics. diff --git a/includes/EditPage.php b/includes/EditPage.php index d1f874ead7..f1f0572bac 100644 --- a/includes/EditPage.php +++ b/includes/EditPage.php @@ -684,7 +684,10 @@ class EditPage { # checking, etc. if ( 'initial' == $this->formtype || $this->firsttime ) { if ( $this->initialiseForm() === false ) { - $this->noSuchSectionPage(); + $out = $this->context->getOutput(); + if ( $out->getRedirect() === '' ) { // mcrundo hack redirects, don't override it + $this->noSuchSectionPage(); + } return; } @@ -1055,7 +1058,7 @@ class EditPage { $this->sectiontitle = $request->getVal( 'preloadtitle' ); // Once wpSummary isn't being use for setting section titles, we should delete this. $this->summary = $request->getVal( 'preloadtitle' ); - } elseif ( $this->section != 'new' && $request->getVal( 'summary' ) ) { + } elseif ( $this->section != 'new' && $request->getVal( 'summary' ) !== '' ) { $this->summary = $request->getText( 'summary' ); if ( $this->summary !== '' ) { $this->hasPresetSummary = true; @@ -1220,8 +1223,13 @@ class EditPage { !$oldrev->isDeleted( Revision::DELETED_TEXT ) ) { if ( WikiPage::hasDifferencesOutsideMainSlot( $undorev, $oldrev ) ) { - // Cannot yet undo edits that involve anything other the main slot. - $undoMsg = 'main-slot-only'; + // Hack for undo while EditPage can't handle multi-slot editing + $this->context->getOutput()->redirect( $this->mTitle->getFullURL( [ + 'action' => 'mcrundo', + 'undo' => $undo, + 'undoafter' => $undoafter, + ] ) ); + return false; } else { $content = $this->page->getUndoContent( $undorev, $oldrev ); @@ -1681,7 +1689,7 @@ class EditPage { // is if an extension hook aborted from inside ArticleSave. // Render the status object into $this->hookError // FIXME this sucks, we should just use the Status object throughout - $this->hookError = '
' ."\n" . $status->getWikiText() . + $this->hookError = '
' . "\n" . $status->getWikiText() . '
'; return true; } @@ -1774,7 +1782,7 @@ ERROR; if ( $this->summary === '' ) { $cleanSectionTitle = $wgParser->stripSectionName( $this->sectiontitle ); return $this->context->msg( 'newsectionsummary' ) - ->rawParams( $cleanSectionTitle )->inContentLanguage()->text(); + ->plaintextParams( $cleanSectionTitle )->inContentLanguage()->text(); } } elseif ( $this->summary !== '' ) { $sectionanchor = $this->guessSectionName( $this->summary ); @@ -1782,7 +1790,7 @@ ERROR; # in the revision summary. $cleanSummary = $wgParser->stripSectionName( $this->summary ); return $this->context->msg( 'newsectionsummary' ) - ->rawParams( $cleanSummary )->inContentLanguage()->text(); + ->plaintextParams( $cleanSummary )->inContentLanguage()->text(); } return $this->summary; } @@ -2482,6 +2490,8 @@ ERROR; $displayTitle = isset( $this->mParserOutput ) ? $this->mParserOutput->getDisplayTitle() : false; if ( $displayTitle === false ) { $displayTitle = $contextTitle->getPrefixedText(); + } else { + $out->setDisplayTitle( $displayTitle ); } $out->setPageTitle( $this->context->msg( $msg, $displayTitle ) ); @@ -2861,7 +2871,7 @@ ERROR; $this->autoSumm = md5( '' ); } - $autosumm = $this->autoSumm ?: md5( $this->summary ); + $autosumm = $this->autoSumm !== '' ? $this->autoSumm : md5( $this->summary ); $out->addHTML( Html::hidden( 'wpAutoSummary', $autosumm ) ); $out->addHTML( Html::hidden( 'oldid', $this->oldid ) ); @@ -3778,7 +3788,7 @@ ERROR; /** * Get the last log record of this page being deleted, if ever. This is - * used to detect whether a delete occured during editing. + * used to detect whether a delete occurred during editing. * @return bool|stdClass */ protected function getLastDelete() { @@ -3989,6 +3999,12 @@ ERROR; $parserOptions->setIsPreview( true ); $parserOptions->setIsSectionPreview( !is_null( $this->section ) && $this->section !== '' ); $parserOptions->enableLimitReport(); + + // XXX: we could call $parserOptions->setCurrentRevisionCallback here to force the + // current revision to be null during PST, until setupFakeRevision is called on + // the ParserOptions. Currently, we rely on Parser::getRevisionObject() to ignore + // existing revisions in preview mode. + return $parserOptions; } @@ -4004,9 +4020,14 @@ ERROR; protected function doPreviewParse( Content $content ) { $user = $this->context->getUser(); $parserOptions = $this->getPreviewParserOptions(); + + // NOTE: preSaveTransform doesn't have a fake revision to operate on. + // Parser::getRevisionObject() will return null in preview mode, + // causing the context user to be used for {{subst:REVISIONUSER}}. + // XXX: Alternatively, we could also call setupFakeRevision() a second time: + // once before PST with $content, and then after PST with $pstContent. $pstContent = $content->preSaveTransform( $this->mTitle, $user, $parserOptions ); - $scopedCallback = $parserOptions->setupFakeRevision( - $this->mTitle, $pstContent, $user ); + $scopedCallback = $parserOptions->setupFakeRevision( $this->mTitle, $pstContent, $user ); $parserOutput = $pstContent->getParserOutput( $this->mTitle, null, $parserOptions ); ScopedCallback::consume( $scopedCallback ); return [ diff --git a/includes/GlobalFunctions.php b/includes/GlobalFunctions.php index 567db853ee..336cb89e99 100644 --- a/includes/GlobalFunctions.php +++ b/includes/GlobalFunctions.php @@ -30,7 +30,6 @@ use MediaWiki\Session\SessionManager; use MediaWiki\MediaWikiServices; use MediaWiki\Shell\Shell; use Wikimedia\ScopedCallback; -use Wikimedia\Rdbms\DBReplicationWaitError; use Wikimedia\WrappedString; /** @@ -2504,38 +2503,6 @@ function wfUsePHP( $req_ver ) { } } -/** - * This function works like "use VERSION" in Perl except it checks the version - * of MediaWiki, the program will die with a backtrace if the current version - * of MediaWiki is less than the version provided. - * - * This is useful for extensions which due to their nature are not kept in sync - * with releases - * - * Note: Due to the behavior of PHP's version_compare() which is used in this - * function, if you want to allow the 'wmf' development versions add a 'c' (or - * any single letter other than 'a', 'b' or 'p') as a post-fix to your - * targeted version number. For example if you wanted to allow any variation - * of 1.22 use `wfUseMW( '1.22c' )`. Using an 'a' or 'b' instead of 'c' will - * not result in the same comparison due to the internal logic of - * version_compare(). - * - * @see perldoc -f use - * - * @deprecated since 1.26, use the "requires" property of extension.json - * @param string|int|float $req_ver The version to check, can be a string, an integer, or a float - * @throws MWException - */ -function wfUseMW( $req_ver ) { - global $wgVersion; - - wfDeprecated( __FUNCTION__, '1.26' ); - - if ( version_compare( $wgVersion, (string)$req_ver, '<' ) ) { - throw new MWException( "MediaWiki $req_ver required--this is only $wgVersion" ); - } -} - /** * Return the final portion of a pathname. * Reimplemented because PHP5's "basename()" is buggy with multibyte text. @@ -2674,28 +2641,6 @@ function wfGetPrecompiledData( $name ) { return false; } -/** - * @since 1.32 - * @param string[] $data Array with string keys/values to export - * @param string $header - * @return string PHP code - */ -function wfMakeStaticArrayFile( array $data, $header = 'Automatically generated' ) { - $format = "\t%s => %s,\n"; - $code = " $value ) { - $code .= sprintf( - $format, - var_export( $key, true ), - var_export( $value, true ) - ); - } - $code .= "];\n"; - return $code; -} - /** * Make a cache key for the local wiki. * @@ -2944,17 +2889,13 @@ function wfGetNull() { * @param float|null $ifWritesSince Only wait if writes were done since this UNIX timestamp * @param string|bool $wiki Wiki identifier accepted by wfGetLB * @param string|bool $cluster Cluster name accepted by LBFactory. Default: false. - * @param int|null $timeout Max wait time. Default: 1 day (cli), ~10 seconds (web) + * @param int|null $timeout Max wait time. Default: 60 seconds (cli), 1 second (web) * @return bool Success (able to connect and no timeouts reached) * @deprecated since 1.27 Use LBFactory::waitForReplication */ function wfWaitForSlaves( $ifWritesSince = null, $wiki = false, $cluster = false, $timeout = null ) { - if ( $timeout === null ) { - $timeout = wfIsCLI() ? 60 : 10; - } - if ( $cluster === '*' ) { $cluster = false; $wiki = false; @@ -2962,20 +2903,18 @@ function wfWaitForSlaves( $wiki = wfWikiID(); } - try { - $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory(); - $lbFactory->waitForReplication( [ - 'wiki' => $wiki, - 'cluster' => $cluster, - 'timeout' => $timeout, - // B/C: first argument used to be "max seconds of lag"; ignore such values - 'ifWritesSince' => ( $ifWritesSince > 1e9 ) ? $ifWritesSince : null - ] ); - } catch ( DBReplicationWaitError $e ) { - return false; + $opts = [ + 'wiki' => $wiki, + 'cluster' => $cluster, + // B/C: first argument used to be "max seconds of lag"; ignore such values + 'ifWritesSince' => ( $ifWritesSince > 1e9 ) ? $ifWritesSince : null + ]; + if ( $timeout !== null ) { + $opts['timeout'] = $timeout; } - return true; + $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory(); + return $lbFactory->waitForReplication( $opts ); } /** @@ -3119,6 +3058,7 @@ function wfBCP47( $code ) { /** * Get a specific cache object. * + * @deprecated since 1.32, use ObjectCache::getInstance() instead * @param int|string $cacheType A CACHE_* constants, or other key in $wgObjectCaches * @return BagOStuff */ @@ -3129,11 +3069,11 @@ function wfGetCache( $cacheType ) { /** * Get the main cache object * + * @deprecated since 1.32, use ObjectCache::getLocalClusterInstance() instead * @return BagOStuff */ function wfGetMainCache() { - global $wgMainCacheType; - return ObjectCache::getInstance( $wgMainCacheType ); + return ObjectCache::getLocalClusterInstance(); } /** diff --git a/includes/Html.php b/includes/Html.php index 32375c1f57..aac492c921 100644 --- a/includes/Html.php +++ b/includes/Html.php @@ -552,10 +552,13 @@ class Html { } /** - * Output a "" or (for XML) literal "]]>". + * It is unsupported for the contents to contain the sequence ` $className ], $html ); diff --git a/includes/LinkFilter.php b/includes/LinkFilter.php index 17b4d56635..3b03f87976 100644 --- a/includes/LinkFilter.php +++ b/includes/LinkFilter.php @@ -65,7 +65,7 @@ class LinkFilter { * @return string Regex pattern, for preg_match() */ private static function makeRegex( $filterEntry, $protocol ) { - $regex = '!' . preg_quote( $protocol ); + $regex = '!' . preg_quote( $protocol, '!' ); if ( substr( $filterEntry, 0, 2 ) == '*.' ) { $regex .= '(?:[A-Za-z0-9.-]+\.|)'; $filterEntry = substr( $filterEntry, 2 ); diff --git a/includes/Linker.php b/includes/Linker.php index 56e377d838..da8daf4c8b 100644 --- a/includes/Linker.php +++ b/includes/Linker.php @@ -205,7 +205,8 @@ class Linker { */ public static function normaliseSpecialPage( LinkTarget $target ) { if ( $target->getNamespace() == NS_SPECIAL && !$target->isExternal() ) { - list( $name, $subpage ) = SpecialPageFactory::resolveAlias( $target->getDBkey() ); + list( $name, $subpage ) = MediaWikiServices::getInstance()->getSpecialPageFactory()-> + resolveAlias( $target->getDBkey() ); if ( !$name ) { return $target; } @@ -430,7 +431,11 @@ class Linker { $s = $thumb->toHtml( $params ); } if ( $frameParams['align'] != '' ) { - $s = "
{$s}
"; + $s = Html::rawElement( + 'div', + [ 'class' => 'float' . $frameParams['align'] ], + $s + ); } return str_replace( "\n", ' ', $prefix . $s . $postfix ); } @@ -1071,10 +1076,6 @@ class Linker { * @author Erik Moeller * @since 1.16.3. $wikiId added in 1.26 * - * Note: there's not always a title to pass to this function. - * Since you can't set a default parameter for a reference, I've turned it - * temporarily to a value pass. Should be adjusted further. --brion - * * @param string $comment * @param Title|null $title Title object (to generate link to the section in autocomment) * or null @@ -1207,7 +1208,8 @@ class Linker { * @param string|null $wikiId Id of the wiki to link to (if not the local wiki), * as used by WikiMap. * - * @return string + * @return string HTML + * @return-taint onlysafefor_html */ public static function formatLinksInComment( $comment, $title = null, $local = false, $wikiId = null @@ -1215,6 +1217,7 @@ class Linker { return preg_replace_callback( '/ \[\[ + \s*+ # ignore leading whitespace, the *+ quantifier disallows backtracking :? # ignore optional leading colon ([^\]|]+) # 1. link target; page names cannot include ] or | (?:\| diff --git a/includes/MediaWiki.php b/includes/MediaWiki.php index 2a84556586..4636ba349e 100644 --- a/includes/MediaWiki.php +++ b/includes/MediaWiki.php @@ -250,14 +250,15 @@ class MediaWiki { // Redirect loops, titleless URL, $wgUsePathInfo URLs, and URLs with a variant } elseif ( !$this->tryNormaliseRedirect( $title ) ) { // Prevent information leak via Special:MyPage et al (T109724) + $spFactory = MediaWikiServices::getInstance()->getSpecialPageFactory(); if ( $title->isSpecialPage() ) { - $specialPage = SpecialPageFactory::getPage( $title->getDBkey() ); + $specialPage = $spFactory->getPage( $title->getDBkey() ); if ( $specialPage instanceof RedirectSpecialPage ) { $specialPage->setContext( $this->context ); if ( $this->config->get( 'HideIdentifiableRedirects' ) && $specialPage->personallyIdentifiableTarget() ) { - list( , $subpage ) = SpecialPageFactory::resolveAlias( $title->getDBkey() ); + list( , $subpage ) = $spFactory->resolveAlias( $title->getDBkey() ); $target = $specialPage->getRedirect( $subpage ); // target can also be true. We let that case fall through to normal processing. if ( $target instanceof Title ) { @@ -284,7 +285,7 @@ class MediaWiki { // Special pages ($title may have changed since if statement above) if ( $title->isSpecialPage() ) { // Actions that need to be made when we have a special pages - SpecialPageFactory::executePath( $title, $this->context ); + $spFactory->executePath( $title, $this->context ); } else { // ...otherwise treat it as an article view. The article // may still be a wikipage redirect to another article or URL. @@ -338,7 +339,8 @@ class MediaWiki { } if ( $title->isSpecialPage() ) { - list( $name, $subpage ) = SpecialPageFactory::resolveAlias( $title->getDBkey() ); + list( $name, $subpage ) = MediaWikiServices::getInstance()->getSpecialPageFactory()-> + resolveAlias( $title->getDBkey() ); if ( $name ) { $title = SpecialPage::getTitleFor( $name, $subpage ); } @@ -1055,7 +1057,8 @@ class MediaWiki { $invokedWithSuccess = true; if ( $sock ) { - $special = SpecialPageFactory::getPage( 'RunJobs' ); + $special = MediaWikiServices::getInstance()->getSpecialPageFactory()-> + getPage( 'RunJobs' ); $url = $special->getPageTitle()->getCanonicalURL( $query ); $req = ( "POST $url HTTP/1.1\r\n" . diff --git a/includes/MediaWikiServices.php b/includes/MediaWikiServices.php index 326c12ce0a..b236ca1da1 100644 --- a/includes/MediaWikiServices.php +++ b/includes/MediaWikiServices.php @@ -12,12 +12,16 @@ use GenderCache; use GlobalVarConfig; use Hooks; use IBufferingStatsdDataFactory; +use Liuggio\StatsdClient\Factory\StatsdDataFactoryInterface; use MediaWiki\Http\HttpRequestFactory; use MediaWiki\Preferences\PreferencesFactory; use MediaWiki\Shell\CommandFactory; +use MediaWiki\Revision\RevisionRenderer; +use MediaWiki\Special\SpecialPageFactory; use MediaWiki\Storage\BlobStore; use MediaWiki\Storage\BlobStoreFactory; use MediaWiki\Storage\NameTableStore; +use MediaWiki\Storage\NameTableStoreFactory; use MediaWiki\Storage\RevisionFactory; use MediaWiki\Storage\RevisionLookup; use MediaWiki\Storage\RevisionStore; @@ -449,7 +453,7 @@ class MediaWikiServices extends ServiceContainer { * @return NameTableStore */ public function getChangeTagDefStore() { - return $this->getService( 'ChangeTagDefStore' ); + return $this->getService( 'NameTableStoreFactory' )->getChangeTagDef(); } /** @@ -497,7 +501,7 @@ class MediaWikiServices extends ServiceContainer { * @return NameTableStore */ public function getContentModelStore() { - return $this->getService( 'ContentModelStore' ); + return $this->getService( 'NameTableStoreFactory' )->getContentModels(); } /** @@ -661,6 +665,13 @@ class MediaWikiServices extends ServiceContainer { /** * @since 1.32 + * @return NameTableStoreFactory + */ + public function getNameTableStoreFactory() { + return $this->getService( 'NameTableStoreFactory' ); + } + + /** * @return OldRevisionImporter */ public function getOldRevisionImporter() { @@ -701,7 +712,7 @@ class MediaWikiServices extends ServiceContainer { /** * @since 1.32 - * @return IBufferingStatsdDataFactory + * @return StatsdDataFactoryInterface */ public function getPerDbNameStatsdDataFactory() { return $this->getService( 'PerDbNameStatsdDataFactory' ); @@ -747,6 +758,14 @@ class MediaWikiServices extends ServiceContainer { return $this->getService( 'RevisionLookup' ); } + /** + * @since 1.32 + * @return RevisionRenderer + */ + public function getRevisionRenderer() { + return $this->getService( 'RevisionRenderer' ); + } + /** * @since 1.31 * @return RevisionStore @@ -825,7 +844,15 @@ class MediaWikiServices extends ServiceContainer { * @return NameTableStore */ public function getSlotRoleStore() { - return $this->getService( 'SlotRoleStore' ); + return $this->getService( 'NameTableStoreFactory' )->getSlotRoles(); + } + + /** + * @since 1.32 + * @return SpecialPageFactory + */ + public function getSpecialPageFactory() : SpecialPageFactory { + return $this->getService( 'SpecialPageFactory' ); } /** diff --git a/includes/Message.php b/includes/Message.php index e2fe254f60..3bd775537f 100644 --- a/includes/Message.php +++ b/includes/Message.php @@ -473,13 +473,13 @@ class Message implements MessageSpecifier, Serializable { global $wgForceUIMsgAsContentMsg; $contLang = MediaWikiServices::getInstance()->getContentLanguage(); + $lang = $this->getLanguage(); $title = $this->key; if ( - !$this->language->equals( $contLang ) + !$lang->equals( $contLang ) && in_array( $this->key, (array)$wgForceUIMsgAsContentMsg ) ) { - $code = $this->language->getCode(); - $title .= '/' . $code; + $title .= '/' . $lang->getCode(); } return Title::makeTitle( diff --git a/includes/MimeMagic.php b/includes/MimeMagic.php deleted file mode 100644 index 6152d2262f..0000000000 --- a/includes/MimeMagic.php +++ /dev/null @@ -1,43 +0,0 @@ -getMimeAnalyzer(); - Assert::postcondition( - $instance instanceof MimeMagic, - __METHOD__ . ' should return an instance of ' . self::class - ); - return $instance; - } -} diff --git a/includes/MovePage.php b/includes/MovePage.php index ec44b6eb69..2ad315811c 100644 --- a/includes/MovePage.php +++ b/includes/MovePage.php @@ -523,15 +523,6 @@ class MovePage { $newpage = WikiPage::factory( $nt ); - # Save a null revision in the page's history notifying of the move - $nullRevision = Revision::newNullRevision( $dbw, $oldid, $comment, true, $user ); - if ( !is_object( $nullRevision ) ) { - throw new MWException( 'No valid null revision produced in ' . __METHOD__ ); - } - - $nullRevId = $nullRevision->insertOn( $dbw ); - $logEntry->setAssociatedRevId( $nullRevId ); - # Change the name of the target page: $dbw->update( 'page', /* SET */ [ @@ -542,6 +533,15 @@ class MovePage { __METHOD__ ); + # Save a null revision in the page's history notifying of the move + $nullRevision = Revision::newNullRevision( $dbw, $oldid, $comment, true, $user ); + if ( !is_object( $nullRevision ) ) { + throw new MWException( 'No valid null revision produced in ' . __METHOD__ ); + } + + $nullRevId = $nullRevision->insertOn( $dbw ); + $logEntry->setAssociatedRevId( $nullRevId ); + if ( !$redirectContent ) { // Clean up the old title *before* reset article id - T47348 WikiPage::onArticleDelete( $this->oldTitle ); diff --git a/includes/OutputPage.php b/includes/OutputPage.php index f6d5dc9b98..99a4c2b895 100644 --- a/includes/OutputPage.php +++ b/includes/OutputPage.php @@ -58,6 +58,15 @@ class OutputPage extends ContextSource { * @var string The contents of

*/ private $mPageTitle = ''; + /** + * @var string The displayed title of the page. Different from page title + * if overridden by display title magic word or hooks. Can contain safe + * HTML. Different from page title which may contain messages such as + * "Editing X" which is displayed in h1. This can be used for other places + * where the page name is referred on the page. + */ + private $displayTitle; + /** * @var string Contains all of the "" content. Should be private we * got set/get accessors and the append() method. @@ -964,6 +973,48 @@ class OutputPage extends ContextSource { return $this->mPageTitle; } + /** + * Same as page title but only contains name of the page, not any other text. + * + * @since 1.32 + * @param string $html Page title text. + * @see OutputPage::setPageTitle + */ + public function setDisplayTitle( $html ) { + $this->displayTitle = $html; + } + + /** + * Returns page display title. + * + * Performs some normalization, but this not as strict the magic word. + * + * @since 1.32 + * @return string HTML + */ + public function getDisplayTitle() { + $html = $this->displayTitle; + if ( $html === null ) { + $html = $this->getTitle()->getPrefixedText(); + } + + return Sanitizer::normalizeCharReferences( Sanitizer::removeHTMLtags( $html ) ); + } + + /** + * Returns page display title without namespace prefix if possible. + * + * @since 1.32 + * @return string HTML + */ + public function getUnprefixedDisplayTitle() { + $text = $this->getDisplayTitle(); + $nsPrefix = $this->getTitle()->getNsText() . ':'; + $prefix = preg_quote( $nsPrefix, '/' ); + + return preg_replace( "/^$prefix/i", '', $text ); + } + /** * Set the Title object to use * @@ -2364,10 +2415,6 @@ class OutputPage extends ContextSource { if ( !$this->mArticleBodyOnly ) { $sk = $this->getSkin(); - - if ( $sk->shouldPreloadLogo() ) { - $this->addLogoPreloadLinkHeaders(); - } } $linkHeader = $this->getLinkHeader(); @@ -2758,6 +2805,18 @@ class OutputPage extends ContextSource { foreach ( $this->contentOverrideCallbacks as $callback ) { $content = $callback( $title ); if ( $content !== null ) { + $text = ContentHandler::getContentText( $content ); + if ( strpos( $text, '' ) !== false ) { + // Proactively replace this so that we can display a message + // to the user, instead of letting it go to Html::inlineScript(), + // where it would be considered a server-side issue. + $titleFormatted = $title->getPrefixedText(); + $content = new JavaScriptContent( + Xml::encodeJsCall( 'mw.log.error', [ + "Cannot preview $titleFormatted due to script-closing tag." + ] ) + ); + } return $content; } } @@ -3060,6 +3119,7 @@ class OutputPage extends ContextSource { $curRevisionId = 0; $articleId = 0; $canonicalSpecialPageName = false; # T23115 + $services = MediaWikiServices::getInstance(); $title = $this->getTitle(); $ns = $title->getNamespace(); @@ -3075,7 +3135,8 @@ class OutputPage extends ContextSource { if ( $ns == NS_SPECIAL ) { list( $canonicalSpecialPageName, /*...*/ ) = - SpecialPageFactory::resolveAlias( $title->getDBkey() ); + $services->getSpecialPageFactory()-> + resolveAlias( $title->getDBkey() ); } elseif ( $this->canUseWikiPage() ) { $wikiPage = $this->getWikiPage(); $curRevisionId = $wikiPage->getLatest(); @@ -3140,7 +3201,7 @@ class OutputPage extends ContextSource { $vars['wgUserNewMsgRevisionId'] = $user->getNewMessageRevisionId(); } - $contLang = MediaWikiServices::getInstance()->getContentLanguage(); + $contLang = $services->getContentLanguage(); if ( $contLang->hasVariants() ) { $vars['wgUserVariant'] = $contLang->getPreferredVariant(); } @@ -3913,80 +3974,6 @@ class OutputPage extends ContextSource { ] ); } - /** - * Add Link headers for preloading the wiki's logo. - * - * @since 1.26 - */ - protected function addLogoPreloadLinkHeaders() { - $logo = ResourceLoaderSkinModule::getLogo( $this->getConfig() ); - - $tags = []; - $logosPerDppx = []; - $logos = []; - - if ( !is_array( $logo ) ) { - // No media queries required if we only have one variant - $this->addLinkHeader( '<' . $logo . '>;rel=preload;as=image' ); - return; - } - - if ( isset( $logo['svg'] ) ) { - // No media queries required if we only have a 1x and svg variant - // because all preload-capable browsers support SVGs - $this->addLinkHeader( '<' . $logo['svg'] . '>;rel=preload;as=image' ); - return; - } - - foreach ( $logo as $dppx => $src ) { - // Keys are in this format: "1.5x" - $dppx = substr( $dppx, 0, -1 ); - $logosPerDppx[$dppx] = $src; - } - - // Because PHP can't have floats as array keys - uksort( $logosPerDppx, function ( $a , $b ) { - $a = floatval( $a ); - $b = floatval( $b ); - // Sort from smallest to largest (e.g. 1x, 1.5x, 2x) - return $a <=> $b; - } ); - - foreach ( $logosPerDppx as $dppx => $src ) { - $logos[] = [ 'dppx' => $dppx, 'src' => $src ]; - } - - $logosCount = count( $logos ); - // Logic must match ResourceLoaderSkinModule: - // - 1x applies to resolution < 1.5dppx - // - 1.5x applies to resolution >= 1.5dppx && < 2dppx - // - 2x applies to resolution >= 2dppx - // Note that min-resolution and max-resolution are both inclusive. - for ( $i = 0; $i < $logosCount; $i++ ) { - if ( $i === 0 ) { - // Smallest dppx - // min-resolution is ">=" (larger than or equal to) - // "not min-resolution" is essentially "<" - $media_query = 'not all and (min-resolution: ' . $logos[ 1 ]['dppx'] . 'dppx)'; - } elseif ( $i !== $logosCount - 1 ) { - // In between - // Media query expressions can only apply "not" to the entire expression - // (e.g. can't express ">= 1.5 and not >= 2). - // Workaround: Use <= 1.9999 in place of < 2. - $upper_bound = floatval( $logos[ $i + 1 ]['dppx'] ) - 0.000001; - $media_query = '(min-resolution: ' . $logos[ $i ]['dppx'] . - 'dppx) and (max-resolution: ' . $upper_bound . 'dppx)'; - } else { - // Largest dppx - $media_query = '(min-resolution: ' . $logos[ $i ]['dppx'] . 'dppx)'; - } - - $this->addLinkHeader( - '<' . $logos[$i]['src'] . '>;rel=preload;as=image;media=' . $media_query - ); - } - } - /** * Get (and set if not yet set) the CSP nonce. * diff --git a/includes/Preferences.php b/includes/Preferences.php index d66da930b1..6d379dcb49 100644 --- a/includes/Preferences.php +++ b/includes/Preferences.php @@ -35,11 +35,12 @@ class Preferences { * @return DefaultPreferencesFactory */ protected static function getDefaultPreferencesFactory() { + $services = MediaWikiServices::getInstance(); $authManager = AuthManager::singleton(); - $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer(); - $config = MediaWikiServices::getInstance()->getMainConfig(); + $linkRenderer = $services->getLinkRenderer(); + $config = $services->getMainConfig(); $preferencesFactory = new DefaultPreferencesFactory( - $config, MediaWikiServices::getInstance()->getContentLanguage(), $authManager, + $config, $services->getContentLanguage(), $authManager, $linkRenderer ); return $preferencesFactory; diff --git a/includes/PrefixSearch.php b/includes/PrefixSearch.php index e32b439755..7bc7a084a5 100644 --- a/includes/PrefixSearch.php +++ b/includes/PrefixSearch.php @@ -173,13 +173,14 @@ abstract class PrefixSearch { $subpageSearch = $searchParts[1] ?? null; // Handle subpage search separately. + $spFactory = MediaWikiServices::getInstance()->getSpecialPageFactory(); if ( $subpageSearch !== null ) { // Try matching the full search string as a page name $specialTitle = Title::makeTitleSafe( NS_SPECIAL, $searchKey ); if ( !$specialTitle ) { return []; } - $special = SpecialPageFactory::getPage( $specialTitle->getText() ); + $special = $spFactory->getPage( $specialTitle->getText() ); if ( $special ) { $subpages = $special->prefixSearchSubpages( $subpageSearch, $limit, $offset ); return array_map( function ( $sub ) use ( $specialTitle ) { @@ -198,12 +199,12 @@ abstract class PrefixSearch { // Unlike SpecialPage itself, we want the canonical forms of both // canonical and alias title forms... $keys = []; - foreach ( SpecialPageFactory::getNames() as $page ) { + foreach ( $spFactory->getNames() as $page ) { $keys[$contLang->caseFold( $page )] = [ 'page' => $page, 'rank' => 0 ]; } foreach ( $contLang->getSpecialPageAliases() as $page => $aliases ) { - if ( !in_array( $page, SpecialPageFactory::getNames() ) ) {# T22885 + if ( !in_array( $page, $spFactory->getNames() ) ) {# T22885 continue; } diff --git a/includes/Revision.php b/includes/Revision.php index 6d684a8052..1e35ddaeaf 100644 --- a/includes/Revision.php +++ b/includes/Revision.php @@ -350,6 +350,7 @@ class Revision implements IDBAccessObject { */ public static function selectFields() { global $wgContentHandlerUseDB, $wgActorTableSchemaMigrationStage; + global $wgMultiContentRevisionSchemaMigrationStage; if ( $wgActorTableSchemaMigrationStage > MIGRATION_WRITE_BOTH ) { // If code is using this instead of self::getQueryInfo(), there's a @@ -361,6 +362,18 @@ class Revision implements IDBAccessObject { ); } + if ( !( $wgMultiContentRevisionSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) ) { + // If code is using this instead of self::getQueryInfo(), there's a + // decent chance it's going to try to directly access + // $row->rev_text_id or $row->rev_content_model and we can't give it + // useful values here once those aren't being written anymore, + // and may not exist at all. + throw new BadMethodCallException( + 'Cannot use ' . __METHOD__ . ' when $wgMultiContentRevisionSchemaMigrationStage ' + . 'does not have SCHEMA_COMPAT_WRITE_OLD set.' + ); + } + wfDeprecated( __METHOD__, '1.31' ); $fields = [ @@ -396,6 +409,7 @@ class Revision implements IDBAccessObject { */ public static function selectArchiveFields() { global $wgContentHandlerUseDB, $wgActorTableSchemaMigrationStage; + global $wgMultiContentRevisionSchemaMigrationStage; if ( $wgActorTableSchemaMigrationStage > MIGRATION_WRITE_BOTH ) { // If code is using this instead of self::getQueryInfo(), there's a @@ -407,6 +421,18 @@ class Revision implements IDBAccessObject { ); } + if ( !( $wgMultiContentRevisionSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) ) { + // If code is using this instead of self::getQueryInfo(), there's a + // decent chance it's going to try to directly access + // $row->ar_text_id or $row->ar_content_model and we can't give it + // useful values here once those aren't being written anymore, + // and may not exist at all. + throw new BadMethodCallException( + 'Cannot use ' . __METHOD__ . ' when $wgMultiContentRevisionSchemaMigrationStage ' + . 'does not have SCHEMA_COMPAT_WRITE_OLD set.' + ); + } + wfDeprecated( __METHOD__, '1.31' ); $fields = [ diff --git a/includes/Revision/RenderedRevision.php b/includes/Revision/RenderedRevision.php new file mode 100644 index 0000000000..fa16c61304 --- /dev/null +++ b/includes/Revision/RenderedRevision.php @@ -0,0 +1,380 @@ +title = $title; + $this->options = $options; + + $this->setRevisionInternal( $revision ); + + $this->combineOutput = $combineOutput; + $this->saveParseLogger = new NullLogger(); + + if ( $audience === RevisionRecord::FOR_THIS_USER && !$forUser ) { + throw new InvalidArgumentException( + 'User must be specified when setting audience to FOR_THIS_USER' + ); + } + + $this->audience = $audience; + $this->forUser = $forUser; + } + + /** + * @param LoggerInterface $saveParseLogger + */ + public function setSaveParseLogger( LoggerInterface $saveParseLogger ) { + $this->saveParseLogger = $saveParseLogger; + } + + /** + * @return bool Whether the revision's content has been hidden from unprivileged users. + */ + public function isContentDeleted() { + return $this->revision->isDeleted( RevisionRecord::DELETED_TEXT ); + } + + /** + * @return RevisionRecord + */ + public function getRevision() { + return $this->revision; + } + + /** + * @return ParserOptions + */ + public function getOptions() { + return $this->options; + } + + /** + * @param array $hints Hints given as an associative array. Known keys: + * - 'generate-html' => bool: Whether the caller is interested in output HTML (as opposed + * to just meta-data). Default is to generate HTML. + * + * @return ParserOutput + */ + public function getRevisionParserOutput( array $hints = [] ) { + $withHtml = $hints['generate-html'] ?? true; + + if ( !$this->revisionOutput + || ( $withHtml && !$this->revisionOutput->hasText() ) + ) { + $output = call_user_func( $this->combineOutput, $this, $hints ); + + Assert::postcondition( + $output instanceof ParserOutput, + 'Callback did not return a ParserOutput object!' + ); + + $this->revisionOutput = $output; + } + + return $this->revisionOutput; + } + + /** + * @param string $role + * @param array $hints Hints given as an associative array. Known keys: + * - 'generate-html' => bool: Whether the caller is interested in output HTML (as opposed + * to just meta-data). Default is to generate HTML. + * + * @throws SuppressedDataException if the content is not accessible for the audience + * specified in the constructor. + * @return ParserOutput + */ + public function getSlotParserOutput( $role, array $hints = [] ) { + $withHtml = $hints['generate-html'] ?? true; + + if ( !isset( $this->slotsOutput[ $role ] ) + || ( $withHtml && !$this->slotsOutput[ $role ]->hasText() ) + ) { + $content = $this->revision->getContent( $role, $this->audience, $this->forUser ); + + if ( !$content ) { + throw new SuppressedDataException( + 'Access to the content has been suppressed for this audience' + ); + } else { + $output = $content->getParserOutput( + $this->title, + $this->revision->getId(), + $this->options, + $withHtml + ); + + if ( $withHtml && !$output->hasText() ) { + throw new LogicException( + 'HTML generation was requested, but ' + . get_class( $content ) + . '::getParserOutput() returns a ParserOutput with no text set.' + ); + } + + // Detach watcher, to ensure option use is not recorded in the wrong ParserOutput. + $this->options->registerWatcher( null ); + } + + $this->slotsOutput[ $role ] = $output; + } + + return $this->slotsOutput[$role]; + } + + /** + * Updates the RevisionRecord after the revision has been saved. This can be used to discard + * and cached ParserOutput so parser functions like {{REVISIONTIMESTAMP}} or {{REVISIONID}} + * are re-evaluated. + * + * @note There should be no need to call this for null-edits. + * + * @param RevisionRecord $rev + */ + public function updateRevision( RevisionRecord $rev ) { + if ( $rev->getId() === $this->revision->getId() ) { + return; + } + + if ( $this->revision->getId() ) { + throw new LogicException( 'RenderedRevision already has a revision with ID ' + . $this->revision->getId(), ', can\'t update to revision with ID ' . $rev->getId() ); + } + + if ( !$this->revision->getSlots()->hasSameContent( $rev->getSlots() ) ) { + throw new LogicException( 'Cannot update to a revision with different content!' ); + } + + $this->setRevisionInternal( $rev ); + + $this->pruneRevisionSensitiveOutput( $this->revision->getId() ); + } + + /** + * Prune any output that depends on the revision ID. + * + * @param int|bool $actualRevId The actual rev id, to check the used speculative rev ID + * against, or false to not purge on vary-revision-id, or true to purge on + * vary-revision-id unconditionally. + */ + private function pruneRevisionSensitiveOutput( $actualRevId ) { + if ( $this->revisionOutput ) { + if ( $this->outputVariesOnRevisionMetaData( $this->revisionOutput, $actualRevId ) ) { + $this->revisionOutput = null; + } + } else { + $this->saveParseLogger->debug( __METHOD__ . ": no prepared revision output...\n" ); + } + + foreach ( $this->slotsOutput as $role => $output ) { + if ( $this->outputVariesOnRevisionMetaData( $output, $actualRevId ) ) { + unset( $this->slotsOutput[$role] ); + } + } + } + + /** + * @param RevisionRecord $revision + */ + private function setRevisionInternal( RevisionRecord $revision ) { + $this->revision = $revision; + + // Force the parser to use $this->revision to resolve magic words like {{REVISIONUSER}} + // if the revision is either known to be complete, or it doesn't have a revision ID set. + // If it's incomplete and we have a revision ID, the parser can do better by loading + // the revision from the database if needed to handle a magic word. + // + // The following considerations inform the logic described above: + // + // 1) If we have a saved revision already loaded, we want the parser to use it, instead of + // loading it again. + // + // 2) If the revision is a fake that wraps some kind of synthetic content, such as an + // error message from Article, it should be used directly and things like {{REVISIONUSER}} + // should not expected to work, since there may not even be an actual revision to + // refer to. + // + // 3) If the revision is a fake constructed around a Title, a Content object, and + // a revision ID, to provide backwards compatibility to code that has access to those + // but not to a complete RevisionRecord for rendering, then we want the Parser to + // load the actual revision from the database when it encounters a magic word like + // {{REVISIONUSER}}, but we don't want to load that revision ahead of time just in case. + // + // 4) Previewing an edit to a template should use the submitted unsaved + // MutableRevisionRecord for self-transclusions in the template's documentation (see T7278). + // That revision would be complete except for the ID field. + // + // 5) Pre-save transform would provide a RevisionRecord that has all meta-data but is + // incomplete due to not yet having content set. However, since it doesn't have a revision + // ID either, the below code would still force it to be used, allowing + // {{subst::REVISIONUSER}} to function as expected. + + if ( $this->revision->isReadyForInsertion() || !$this->revision->getId() ) { + $title = $this->title; + $oldCallback = $this->options->getCurrentRevisionCallback(); + $this->options->setCurrentRevisionCallback( + function ( Title $parserTitle, $parser = false ) use ( $title, $oldCallback ) { + if ( $title->equals( $parserTitle ) ) { + $legacyRevision = new Revision( $this->revision ); + return $legacyRevision; + } else { + return call_user_func( $oldCallback, $parserTitle, $parser ); + } + } + ); + } + } + + /** + * @param ParserOutput $out + * @param int|bool $actualRevId The actual rev id, to check the used speculative rev ID + * against, or false to not purge on vary-revision-id, or true to purge on + * vary-revision-id unconditionally. + * @return bool + */ + private function outputVariesOnRevisionMetaData( ParserOutput $out, $actualRevId ) { + $method = __METHOD__; + + if ( $out->getFlag( 'vary-revision' ) ) { + // XXX: Would be just keep the output if the speculative revision ID was correct, + // but that can go wrong for some edge cases, like {{PAGEID}} during page creation. + // For that specific case, it would perhaps nice to have a vary-page flag. + $this->saveParseLogger->info( + "$method: Prepared output has vary-revision...\n" + ); + return true; + } elseif ( $out->getFlag( 'vary-revision-id' ) + && $actualRevId !== false + && ( $actualRevId === true || $out->getSpeculativeRevIdUsed() !== $actualRevId ) + ) { + $this->saveParseLogger->info( + "$method: Prepared output has vary-revision-id with wrong ID...\n" + ); + return true; + } else { + // NOTE: In the original fix for T135261, the output was discarded if 'vary-user' was + // set for a null-edit. The reason was that the original rendering in that case was + // targeting the user making the null-edit, not the user who made the original edit, + // causing {{REVISIONUSER}} to return the wrong name. + // This case is now expected to be handled by the code in RevisionRenderer that + // constructs the ParserOptions: For a null-edit, setCurrentRevisionCallback is called + // with the old, existing revision. + + wfDebug( "$method: Keeping prepared output...\n" ); + return false; + } + } + +} diff --git a/includes/Revision/RevisionRenderer.php b/includes/Revision/RevisionRenderer.php new file mode 100644 index 0000000000..f71f9e71d7 --- /dev/null +++ b/includes/Revision/RevisionRenderer.php @@ -0,0 +1,218 @@ +loadBalancer = $loadBalancer; + $this->wikiId = $wikiId; + + $this->saveParseLogger = new NullLogger(); + } + + /** + * @param RevisionRecord $rev + * @param ParserOptions|null $options + * @param User|null $forUser User for privileged access. Default is unprivileged (public) + * access, unless the 'audience' hint is set to something else RevisionRecord::RAW. + * @param array $hints Hints given as an associative array. Known keys: + * - 'use-master' Use master when rendering for the parser cache during save. + * Default is to use a replica. + * - 'audience' the audience to use for content access. Default is + * RevisionRecord::FOR_PUBLIC if $forUser is not set, RevisionRecord::FOR_THIS_USER + * if $forUser is set. Can be set to RevisionRecord::RAW to disable audience checks. + * + * @return RenderedRevision|null The rendered revision, or null if the audience checks fails. + */ + public function getRenderedRevision( + RevisionRecord $rev, + ParserOptions $options = null, + User $forUser = null, + array $hints = [] + ) { + if ( $rev->getWikiId() !== $this->wikiId ) { + throw new InvalidArgumentException( 'Mismatching wiki ID ' . $rev->getWikiId() ); + } + + $audience = $hints['audience'] + ?? ( $forUser ? RevisionRecord::FOR_THIS_USER : RevisionRecord::FOR_PUBLIC ); + + if ( !$rev->audienceCan( RevisionRecord::DELETED_TEXT, $audience, $forUser ) ) { + // Returning null here is awkward, but consist with the signature of + // Revision::getContent() and RevisionRecord::getContent(). + return null; + } + + if ( !$options ) { + $options = ParserOptions::newCanonical( $forUser ?: 'canonical' ); + } + + $useMaster = $hints['use-master'] ?? false; + + $dbIndex = $useMaster + ? DB_MASTER // use latest values + : DB_REPLICA; // T154554 + + $options->setSpeculativeRevIdCallback( function () use ( $dbIndex ) { + return $this->getSpeculativeRevId( $dbIndex ); + } ); + + $title = Title::newFromLinkTarget( $rev->getPageAsLinkTarget() ); + + $renderedRevision = new RenderedRevision( + $title, + $rev, + $options, + function ( RenderedRevision $rrev, array $hints ) { + return $this->combineSlotOutput( $rrev, $hints ); + }, + $audience, + $forUser + ); + + $renderedRevision->setSaveParseLogger( $this->saveParseLogger ); + + return $renderedRevision; + } + + private function getSpeculativeRevId( $dbIndex ) { + // Use a fresh master connection in order to see the latest data, by avoiding + // stale data from REPEATABLE-READ snapshots. + // HACK: But don't use a fresh connection in unit tests, since it would not have + // the fake tables. This should be handled by the LoadBalancer! + $flags = defined( 'MW_PHPUNIT_TEST' ) || $dbIndex === DB_REPLICA + ? 0 : ILoadBalancer::CONN_TRX_AUTOCOMMIT; + + $db = $this->loadBalancer->getConnectionRef( $dbIndex, [], $this->wikiId, $flags ); + + return 1 + (int)$db->selectField( + 'revision', + 'MAX(rev_id)', + [], + __METHOD__ + ); + } + + /** + * This implements the layout for combining the output of multiple slots. + * + * @todo Use placement hints from SlotRoleHandlers instead of hard-coding the layout. + * + * @param RenderedRevision $rrev + * @param array $hints see RenderedRevision::getRevisionParserOutput() + * + * @return ParserOutput + */ + private function combineSlotOutput( RenderedRevision $rrev, array $hints = [] ) { + $revision = $rrev->getRevision(); + $slots = $revision->getSlots()->getSlots(); + + $withHtml = $hints['generate-html'] ?? true; + + // short circuit if there is only the main slot + if ( array_keys( $slots ) === [ 'main' ] ) { + return $rrev->getSlotParserOutput( 'main' ); + } + + // TODO: put fancy layout logic here, see T200915. + + // move main slot to front + if ( isset( $slots['main'] ) ) { + $slots = [ 'main' => $slots['main'] ] + $slots; + } + + $combinedOutput = new ParserOutput( null ); + $slotOutput = []; + + $options = $rrev->getOptions(); + $options->registerWatcher( [ $combinedOutput, 'recordOption' ] ); + + foreach ( $slots as $role => $slot ) { + $out = $rrev->getSlotParserOutput( $role, $hints ); + $slotOutput[$role] = $out; + + $combinedOutput->mergeInternalMetaDataFrom( $out, $role ); + $combinedOutput->mergeTrackingMetaDataFrom( $out ); + } + + if ( $withHtml ) { + $html = ''; + $first = true; + /** @var ParserOutput $out */ + foreach ( $slotOutput as $role => $out ) { + if ( $first ) { + // skip header for the first slot + $first = false; + } else { + // NOTE: this placeholder is hydrated by ParserOutput::getText(). + $headText = Html::element( 'mw:slotheader', [], $role ); + $html .= Html::rawElement( 'h1', [ 'class' => 'mw-slot-header' ], $headText ); + } + + $html .= $out->getRawText(); + $combinedOutput->mergeHtmlMetaDataFrom( $out ); + } + + $combinedOutput->setText( $html ); + } + + $options->registerWatcher( null ); + return $combinedOutput; + } + +} diff --git a/includes/Revision/SlotRenderingProvider.php b/includes/Revision/SlotRenderingProvider.php new file mode 100644 index 0000000000..740f0f2c9f --- /dev/null +++ b/includes/Revision/SlotRenderingProvider.php @@ -0,0 +1,32 @@ + bool: Whether the caller is interested in output HTML (as opposed + * to just meta-data). Default is to generate HTML. + * + * @throws SuppressedDataException if the content is not accessible for the audience + * specified in the constructor. + * @return ParserOutput + */ + public function getSlotParserOutput( $role, array $hints = [] ); + +} diff --git a/includes/ServiceWiring.php b/includes/ServiceWiring.php index 344c31d478..cf2def22cc 100644 --- a/includes/ServiceWiring.php +++ b/includes/ServiceWiring.php @@ -37,6 +37,7 @@ * MediaWiki code base. */ +use Liuggio\StatsdClient\Factory\StatsdDataFactoryInterface; use MediaWiki\Auth\AuthManager; use MediaWiki\Config\ConfigRepository; use MediaWiki\Interwiki\ClassicInterwikiLookup; @@ -48,9 +49,11 @@ use MediaWiki\MediaWikiServices; use MediaWiki\Preferences\PreferencesFactory; use MediaWiki\Preferences\DefaultPreferencesFactory; use MediaWiki\Shell\CommandFactory; +use MediaWiki\Special\SpecialPageFactory; use MediaWiki\Storage\BlobStore; +use MediaWiki\Revision\RevisionRenderer; use MediaWiki\Storage\BlobStoreFactory; -use MediaWiki\Storage\NameTableStore; +use MediaWiki\Storage\NameTableStoreFactory; use MediaWiki\Storage\RevisionFactory; use MediaWiki\Storage\RevisionLookup; use MediaWiki\Storage\RevisionStore; @@ -70,31 +73,13 @@ return [ 'BlobStoreFactory' => function ( MediaWikiServices $services ) : BlobStoreFactory { return new BlobStoreFactory( - $services->getDBLoadBalancer(), + $services->getDBLoadBalancerFactory(), $services->getMainWANObjectCache(), $services->getMainConfig(), $services->getContentLanguage() ); }, - 'ChangeTagDefStore' => function ( MediaWikiServices $services ) : NameTableStore { - return new NameTableStore( - $services->getDBLoadBalancer(), - $services->getMainWANObjectCache(), - LoggerFactory::getInstance( 'NameTableSqlStore' ), - 'change_tag_def', - 'ctd_id', - 'ctd_name', - null, - false, - function ( $insertFields ) { - $insertFields['ctd_user_defined'] = 0; - $insertFields['ctd_count'] = 0; - return $insertFields; - } - ); - }, - 'CommentStore' => function ( MediaWikiServices $services ) : CommentStore { return new CommentStore( $services->getContentLanguage(), @@ -125,23 +110,6 @@ return [ return Language::factory( $services->getMainConfig()->get( 'LanguageCode' ) ); }, - 'ContentModelStore' => function ( MediaWikiServices $services ) : NameTableStore { - return new NameTableStore( - $services->getDBLoadBalancer(), - $services->getMainWANObjectCache(), - LoggerFactory::getInstance( 'NameTableSqlStore' ), - 'content_models', - 'model_id', - 'model_name' - /** - * No strtolower normalization is added to the service as there are examples of - * extensions that do not stick to this assumption. - * - extensions/examples/DataPages define( 'CONTENT_MODEL_XML_DATA','XML_DATA' ); - * - extensions/Scribunto define( 'CONTENT_MODEL_SCRIBUNTO', 'Scribunto' ); - */ - ); - }, - 'CryptHKDF' => function ( MediaWikiServices $services ) : CryptHKDF { $config = $services->getMainConfig(); @@ -353,8 +321,15 @@ return [ }; } - // XXX: MimeMagic::singleton currently requires this service to return an instance of MimeMagic - return new MimeMagic( $params ); + return new MimeAnalyzer( $params ); + }, + + 'NameTableStoreFactory' => function ( MediaWikiServices $services ) : NameTableStoreFactory { + return new NameTableStoreFactory( + $services->getDBLoadBalancerFactory(), + $services->getMainWANObjectCache(), + LoggerFactory::getInstance( 'NameTableSqlStore' ) + ); }, 'OldRevisionImporter' => function ( MediaWikiServices $services ) : OldRevisionImporter { @@ -385,7 +360,8 @@ return [ $services->getMainConfig()->get( 'ParserConf' ), $services->getMagicWordFactory(), $services->getContentLanguage(), - wfUrlProtocols() + wfUrlProtocols(), + $services->getSpecialPageFactory() ); }, @@ -398,11 +374,12 @@ return [ }, 'PerDbNameStatsdDataFactory' => - function ( MediaWikiServices $services ) : IBufferingStatsdDataFactory { + function ( MediaWikiServices $services ) : StatsdDataFactoryInterface { $config = $services->getMainConfig(); $wiki = $config->get( 'DBname' ); - return new BufferingStatsdDataFactory( - rtrim( $services->getMainConfig()->get( 'StatsdMetricPrefix' ), '.' ) . '.' . $wiki + return new PrefixingStatsdDataFactoryProxy( + $services->getStatsdDataFactory(), + $wiki ); }, @@ -441,6 +418,10 @@ return [ return $services->getRevisionStore(); }, + 'RevisionRenderer' => function ( MediaWikiServices $services ) : RevisionRenderer { + return new RevisionRenderer( $services->getDBLoadBalancer() ); + }, + 'RevisionStore' => function ( MediaWikiServices $services ) : RevisionStore { return $services->getRevisionStoreFactory()->getRevisionStore(); }, @@ -450,6 +431,7 @@ return [ $store = new RevisionStoreFactory( $services->getDBLoadBalancerFactory(), $services->getBlobStoreFactory(), + $services->getNameTableStoreFactory(), $services->getMainWANObjectCache(), $services->getCommentStore(), $services->getActorMigration(), @@ -533,15 +515,10 @@ return [ return $factory; }, - 'SlotRoleStore' => function ( MediaWikiServices $services ) : NameTableStore { - return new NameTableStore( - $services->getDBLoadBalancer(), - $services->getMainWANObjectCache(), - LoggerFactory::getInstance( 'NameTableSqlStore' ), - 'slot_roles', - 'role_id', - 'role_name', - 'strtolower' + 'SpecialPageFactory' => function ( MediaWikiServices $services ) : SpecialPageFactory { + return new SpecialPageFactory( + $services->getMainConfig(), + $services->getContentLanguage() ); }, diff --git a/includes/Setup.php b/includes/Setup.php index 9923ae2c53..43bc2d8de3 100644 --- a/includes/Setup.php +++ b/includes/Setup.php @@ -86,11 +86,6 @@ MediaWiki\HeaderCallback::register(); * Load LocalSettings.php */ -if ( is_readable( "$IP/StartProfiler.php" ) ) { - // @deprecated since 1.32: Use LocalSettings.php instead. - require "$IP/StartProfiler.php"; -} - if ( defined( 'MW_CONFIG_CALLBACK' ) ) { call_user_func( MW_CONFIG_CALLBACK ); } else { @@ -774,7 +769,7 @@ if ( $wgCommandLineMode ) { wfDebug( $debug ); } -$wgMemc = wfGetMainCache(); +$wgMemc = ObjectCache::getLocalClusterInstance(); $messageMemc = wfGetMessageCacheStorage(); wfDebugLog( 'caches', @@ -898,6 +893,7 @@ $wgOut = RequestContext::getMain()->getOutput(); // BackCompat /** * @var Parser $wgParser + * @deprecated since 1.32, use MediaWikiServices::getParser() instead */ $wgParser = new StubObject( 'wgParser', function () { return MediaWikiServices::getInstance()->getParser(); diff --git a/includes/Storage/BlobStoreFactory.php b/includes/Storage/BlobStoreFactory.php index 63ca74def4..4e1f97ffe1 100644 --- a/includes/Storage/BlobStoreFactory.php +++ b/includes/Storage/BlobStoreFactory.php @@ -23,7 +23,7 @@ namespace MediaWiki\Storage; use Config; use Language; use WANObjectCache; -use Wikimedia\Rdbms\LoadBalancer; +use Wikimedia\Rdbms\LBFactory; /** * Service for instantiating BlobStores @@ -35,9 +35,9 @@ use Wikimedia\Rdbms\LoadBalancer; class BlobStoreFactory { /** - * @var LoadBalancer + * @var LBFactory */ - private $loadBalancer; + private $lbFactory; /** * @var WANObjectCache @@ -55,12 +55,12 @@ class BlobStoreFactory { private $contLang; public function __construct( - LoadBalancer $loadBalancer, + LBFactory $lbFactory, WANObjectCache $cache, Config $mainConfig, Language $contLang ) { - $this->loadBalancer = $loadBalancer; + $this->lbFactory = $lbFactory; $this->cache = $cache; $this->config = $mainConfig; $this->contLang = $contLang; @@ -85,8 +85,9 @@ class BlobStoreFactory { * @return SqlBlobStore */ public function newSqlBlobStore( $wikiId = false ) { + $lb = $this->lbFactory->getMainLB( $wikiId ); $store = new SqlBlobStore( - $this->loadBalancer, + $lb, $this->cache, $wikiId ); diff --git a/includes/Storage/DerivedPageDataUpdater.php b/includes/Storage/DerivedPageDataUpdater.php index a00766fa74..e34e406f1d 100644 --- a/includes/Storage/DerivedPageDataUpdater.php +++ b/includes/Storage/DerivedPageDataUpdater.php @@ -27,23 +27,24 @@ use CategoryMembershipChangeJob; use Content; use ContentHandler; use DataUpdate; +use DeferrableUpdate; use DeferredUpdates; use Hooks; use IDBAccessObject; use InvalidArgumentException; use JobQueueGroup; use Language; +use LinksDeletionUpdate; use LinksUpdate; use LogicException; use MediaWiki\Edit\PreparedEdit; -use MediaWiki\MediaWikiServices; +use MediaWiki\Revision\RenderedRevision; +use MediaWiki\Revision\RevisionRenderer; use MediaWiki\User\UserIdentity; use MessageCache; use ParserCache; use ParserOptions; use ParserOutput; -use Psr\Log\LoggerInterface; -use Psr\Log\NullLogger; use RecentChangesUpdateJob; use ResourceLoaderWikiModule; use Revision; @@ -52,6 +53,7 @@ use SiteStatsUpdate; use Title; use User; use Wikimedia\Assert\Assert; +use Wikimedia\Rdbms\LBFactory; use WikiPage; /** @@ -112,11 +114,6 @@ class DerivedPageDataUpdater implements IDBAccessObject { */ private $contLang; - /** - * @var LoggerInterface - */ - private $saveParseLogger; - /** * @var JobQueueGroup */ @@ -127,6 +124,11 @@ class DerivedPageDataUpdater implements IDBAccessObject { */ private $messageCache; + /** + * @var LBFactory + */ + private $loadbalancerFactory; + /** * @var string see $wgArticleCountMethod */ @@ -138,15 +140,22 @@ class DerivedPageDataUpdater implements IDBAccessObject { private $rcWatchCategoryMembership = false; /** - * See $options on prepareUpdate. + * Stores (most of) the $options parameter of prepareUpdate(). + * @see prepareUpdate() */ private $options = [ 'changed' => true, 'created' => false, 'moved' => false, 'restored' => false, + 'oldrevision' => null, 'oldcountable' => null, 'oldredirect' => null, + 'triggeringUser' => null, + // causeAction/causeAgent default to 'unknown' but that's handled where it's read, + // to make the life of prepareUpdate() callers easier. + 'causeAction' => null, + 'causeAgent' => null, ]; /** @@ -158,8 +167,7 @@ class DerivedPageDataUpdater implements IDBAccessObject { * * Contains the following fields: * - oldRevision (RevisionRecord|null): the revision that was current before the change - * associated with this update. Might not be set, use getOldRevision() instead of direct - * access. + * associated with this update. Might not be set, use getParentRevision(). * - oldId (int|null): the id of the above revision. 0 if there is no such revision (the change * was about creating a new page); null if not known (that should not happen). * - oldIsRedirect (bool|null): whether the page was a redirect before the change. Lazy-loaded, @@ -177,31 +185,24 @@ class DerivedPageDataUpdater implements IDBAccessObject { private $slotsUpdate = null; /** - * @var MutableRevisionSlots|null - */ - private $pstContentSlots = null; - - /** - * @var object[] anonymous objects with two fields, using slot roles as keys: - * - hasHtml: whether the output contains HTML - * - ParserOutput: the slot's parser output + * @var RevisionRecord|null */ - private $slotsOutput = []; + private $parentRevision = null; /** - * @var ParserOutput|null + * @var RevisionRecord|null */ - private $canonicalParserOutput = null; + private $revision = null; /** - * @var ParserOptions|null + * @var RenderedRevision */ - private $canonicalParserOptions = null; + private $renderedRevision = null; /** - * @var RevisionRecord + * @var RevisionRenderer */ - private $revision = null; + private $revisionRenderer; /** * A stage identifier for managing the life cycle of this instance. @@ -248,31 +249,34 @@ class DerivedPageDataUpdater implements IDBAccessObject { /** * @param WikiPage $wikiPage , * @param RevisionStore $revisionStore + * @param RevisionRenderer $revisionRenderer * @param ParserCache $parserCache * @param JobQueueGroup $jobQueueGroup * @param MessageCache $messageCache * @param Language $contLang - * @param LoggerInterface|null $saveParseLogger + * @param LBFactory $loadbalancerFactory */ public function __construct( WikiPage $wikiPage, RevisionStore $revisionStore, + RevisionRenderer $revisionRenderer, ParserCache $parserCache, JobQueueGroup $jobQueueGroup, MessageCache $messageCache, Language $contLang, - LoggerInterface $saveParseLogger = null + LBFactory $loadbalancerFactory ) { $this->wikiPage = $wikiPage; $this->parserCache = $parserCache; $this->revisionStore = $revisionStore; + $this->revisionRenderer = $revisionRenderer; $this->jobQueueGroup = $jobQueueGroup; $this->messageCache = $messageCache; $this->contLang = $contLang; - - // XXX: replace all wfDebug calls with a Logger. Do we nede more than one logger here? - $this->saveParseLogger = $saveParseLogger ?: new NullLogger(); + // XXX only needed for waiting for slaves to catch up; there should be a narrower + // interface for that. + $this->loadbalancerFactory = $loadbalancerFactory; } /** @@ -353,7 +357,9 @@ class DerivedPageDataUpdater implements IDBAccessObject { return false; } - if ( $revision && $this->revision && $this->revision->getId() !== $revision->getId() ) { + if ( $revision && $this->revision && $this->revision->getId() + && $this->revision->getId() !== $revision->getId() + ) { return false; } @@ -378,6 +384,7 @@ class DerivedPageDataUpdater implements IDBAccessObject { if ( $this->revision && $user + && $this->revision->getUser( RevisionRecord::RAW ) && $this->revision->getUser( RevisionRecord::RAW )->getName() !== $user->getName() ) { return false; @@ -385,6 +392,7 @@ class DerivedPageDataUpdater implements IDBAccessObject { if ( $revision && $this->user + && $this->revision->getUser( RevisionRecord::RAW ) && $revision->getUser( RevisionRecord::RAW )->getName() !== $this->user->getName() ) { return false; @@ -398,9 +406,9 @@ class DerivedPageDataUpdater implements IDBAccessObject { return false; } - if ( $this->pstContentSlots - && $revision - && !$this->pstContentSlots->hasSameContent( $revision->getSlots() ) + if ( $revision + && $this->revision + && !$this->revision->getSlots()->hasSameContent( $revision->getSlots() ) ) { return false; } @@ -454,29 +462,34 @@ class DerivedPageDataUpdater implements IDBAccessObject { } /** - * Returns the revision that was current before the edit. This would be null if the edit - * created the page, or the revision's parent for a regular edit, or the revision itself - * for a null-edit. - * Only defined after calling grabCurrentRevision() or prepareContent() or prepareUpdate()! + * Returns the parent revision of the new revision wrapped by this update. + * If the update is a null-edit, this will return the parent of the current (and new) revision. + * This will return null if the revision wrapped by this update created the page. + * Only defined after calling prepareContent() or prepareUpdate()! * - * @return RevisionRecord|null the revision that was current before the edit, or null if - * the edit created the page. + * @return RevisionRecord|null the parent revision of the new revision, or null if + * the update created the page. */ - private function getOldRevision() { - $this->assertHasPageState( __METHOD__ ); + private function getParentRevision() { + $this->assertPrepared( __METHOD__ ); - // If 'oldRevision' is not set, load it! - // Useful if $this->oldPageState is initialized by prepareUpdate. - if ( !array_key_exists( 'oldRevision', $this->pageState ) ) { - /** @var int $oldId */ - $oldId = $this->pageState['oldId']; - $flags = $this->useMaster() ? RevisionStore::READ_LATEST : 0; - $this->pageState['oldRevision'] = $oldId - ? $this->revisionStore->getRevisionById( $oldId, $flags ) - : null; + if ( $this->parentRevision ) { + return $this->parentRevision; } - return $this->pageState['oldRevision']; + if ( !$this->pageState['oldId'] ) { + // If there was no current revision, there is no parent revision, + // since the page didn't exist. + return null; + } + + $oldId = $this->revision->getParentId(); + $flags = $this->useMaster() ? RevisionStore::READ_LATEST : 0; + $this->parentRevision = $oldId + ? $this->revisionStore->getRevisionById( $oldId, $flags ) + : null; + + return $this->parentRevision; } /** @@ -493,8 +506,8 @@ class DerivedPageDataUpdater implements IDBAccessObject { * @note After prepareUpdate() was called, grabCurrentRevision() will throw an exception * to avoid confusion, since the page's current revision is then the new revision after * the edit, which was presumably passed to prepareUpdate() as the $revision parameter. - * Use getOldRevision() instead to access the revision that used to be current before the - * edit. + * Use getParentRevision() instead to access the revision that is the parent of the + * new revision. * * @return RevisionRecord|null the page's current revision, or null if the page does not * yet exist. @@ -533,16 +546,18 @@ class DerivedPageDataUpdater implements IDBAccessObject { * @return bool */ public function isContentPrepared() { - return $this->pstContentSlots !== null; + return $this->revision !== null; } /** * Whether prepareUpdate() has been called on this instance. * + * @note will also return null in case of a null-edit! + * * @return bool */ public function isUpdatePrepared() { - return $this->revision !== null; + return $this->revision !== null && $this->revision->getId() !== null; } /** @@ -554,25 +569,17 @@ class DerivedPageDataUpdater implements IDBAccessObject { } /** - * @return string - */ - private function getTimestampNow() { - // TODO: allow an override to be injected for testing - return wfTimestampNow(); - } - - /** - * Whether the content of the target revision is publicly visible. + * Whether the content is deleted and thus not visible to the public. * * @return bool */ - public function isContentPublic() { + public function isContentDeleted() { if ( $this->revision ) { - // XXX: if that revision is the current revision, this can be skipped - return !$this->revision->isDeleted( RevisionRecord::DELETED_TEXT ); + // XXX: if that revision is the current revision, this should be skipped + return $this->revision->isDeleted( RevisionRecord::DELETED_TEXT ); } else { - // If the content has not been saved yet, it cannot have been suppressed yet. - return true; + // If the content has not been saved yet, it cannot have been deleted yet. + return false; } } @@ -635,7 +642,7 @@ class DerivedPageDataUpdater implements IDBAccessObject { return false; } - if ( !$this->isContentPublic() ) { + if ( $this->isContentDeleted() ) { // This should be irrelevant: countability only applies to the current revision, // and the current revision is never suppressed. return false; @@ -739,7 +746,6 @@ class DerivedPageDataUpdater implements IDBAccessObject { $this->slotsOutput = []; $this->canonicalParserOutput = null; - $this->canonicalParserOptions = null; // The edit may have already been prepared via api.php?action=stashedit $stashedEdit = false; @@ -769,14 +775,31 @@ class DerivedPageDataUpdater implements IDBAccessObject { $this->slotsUpdate = $slotsUpdate; if ( $parentRevision ) { - // start out by inheriting all parent slots - $this->pstContentSlots = MutableRevisionSlots::newFromParentRevisionSlots( - $parentRevision->getSlots()->getSlots() - ); + $this->revision = MutableRevisionRecord::newFromParentRevision( $parentRevision ); } else { - $this->pstContentSlots = new MutableRevisionSlots(); + $this->revision = new MutableRevisionRecord( $title ); } + // NOTE: user and timestamp must be set, so they can be used for + // {{subst:REVISIONUSER}} and {{subst:REVISIONTIMESTAMP}} in PST! + $this->revision->setTimestamp( wfTimestampNow() ); + $this->revision->setUser( $user ); + + // Set up ParserOptions to operate on the new revision + $oldCallback = $userPopts->getCurrentRevisionCallback(); + $userPopts->setCurrentRevisionCallback( + function ( Title $parserTitle, $parser = false ) use ( $title, $oldCallback ) { + if ( $parserTitle->equals( $title ) ) { + $legacyRevision = new Revision( $this->revision ); + return $legacyRevision; + } else { + return call_user_func( $oldCallback, $parserTitle, $parser ); + } + } + ); + + $pstContentSlots = $this->revision->getSlots(); + foreach ( $slotsUpdate->getModifiedRoles() as $role ) { $slot = $slotsUpdate->getModifiedSlot( $role ); @@ -793,18 +816,78 @@ class DerivedPageDataUpdater implements IDBAccessObject { $pstSlot = SlotRecord::newUnsaved( $role, $pstContent ); } - $this->pstContentSlots->setSlot( $pstSlot ); + $pstContentSlots->setSlot( $pstSlot ); } foreach ( $slotsUpdate->getRemovedRoles() as $role ) { - $this->pstContentSlots->removeSlot( $role ); + $pstContentSlots->removeSlot( $role ); } $this->options['created'] = ( $parentRevision === null ); $this->options['changed'] = ( $parentRevision === null - || !$this->pstContentSlots->hasSameContent( $parentRevision->getSlots() ) ); + || !$pstContentSlots->hasSameContent( $parentRevision->getSlots() ) ); $this->doTransition( 'has-content' ); + + if ( !$this->options['changed'] ) { + // null-edit! + + // TODO: move this into MutableRevisionRecord + // TODO: This needs to behave differently for a forced dummy edit! + $this->revision->setId( $parentRevision->getId() ); + $this->revision->setTimestamp( $parentRevision->getTimestamp() ); + $this->revision->setPageId( $parentRevision->getPageId() ); + $this->revision->setParentId( $parentRevision->getParentId() ); + $this->revision->setUser( $parentRevision->getUser( RevisionRecord::RAW ) ); + $this->revision->setComment( $parentRevision->getComment( RevisionRecord::RAW ) ); + $this->revision->setMinorEdit( $parentRevision->isMinor() ); + $this->revision->setVisibility( $parentRevision->getVisibility() ); + + // prepareUpdate() is redundant for null-edits + $this->doTransition( 'has-revision' ); + } else { + $this->parentRevision = $parentRevision; + } + } + + /** + * Returns the update's target revision - that is, the revision that will be the current + * revision after the update. + * + * @note Callers must treat the returned RevisionRecord's content as immutable, even + * if it is a MutableRevisionRecord instance. Other aspects of a MutableRevisionRecord + * returned from here, such as the user or the comment, may be changed, but may not + * be reflected in ParserOutput until after prepareUpdate() has been called. + * + * @todo This is currently used by PageUpdater::makeNewRevision() to construct an unsaved + * MutableRevisionRecord instance. Introduce something like an UnsavedRevisionFactory service + * for that purpose instead! + * + * @return RevisionRecord + */ + public function getRevision() { + $this->assertPrepared( __METHOD__ ); + return $this->revision; + } + + /** + * @return RenderedRevision + */ + public function getRenderedRevision() { + if ( !$this->renderedRevision ) { + $this->assertPrepared( __METHOD__ ); + + // NOTE: we want a canonical rendering, so don't pass $this->user or ParserOptions + // NOTE: the revision is either new or current, so we can bypass audience checks. + $this->renderedRevision = $this->revisionRenderer->getRenderedRevision( + $this->revision, + null, + null, + [ 'use-master' => $this->useMaster(), 'audience' => RevisionRecord::RAW ] + ); + } + + return $this->renderedRevision; } private function assertHasPageState( $method ) { @@ -817,13 +900,21 @@ class DerivedPageDataUpdater implements IDBAccessObject { } private function assertPrepared( $method ) { - if ( !$this->pstContentSlots ) { + if ( !$this->revision ) { throw new LogicException( 'Must call prepareContent() or prepareUpdate() before calling ' . $method ); } } + private function assertHasRevision( $method ) { + if ( !$this->revision->getId() ) { + throw new LogicException( + 'Must call prepareUpdate() before calling ' . $method + ); + } + } + /** * Whether the edit creates the page. * @@ -872,11 +963,14 @@ class DerivedPageDataUpdater implements IDBAccessObject { /** * Returns the slots of the target revision, after PST. * + * @note Callers must treat the returned RevisionSlots instance as immutable, even + * if it is a MutableRevisionSlots instance. + * * @return RevisionSlots */ public function getSlots() { $this->assertPrepared( __METHOD__ ); - return $this->pstContentSlots; + return $this->revision->getSlots(); } /** @@ -888,13 +982,7 @@ class DerivedPageDataUpdater implements IDBAccessObject { $this->assertPrepared( __METHOD__ ); if ( !$this->slotsUpdate ) { - if ( !$this->revision ) { - // This should not be possible: if assertPrepared() returns true, - // at least one of $this->slotsUpdate or $this->revision should be set. - throw new LogicException( 'No revision nor a slots update is known!' ); - } - - $old = $this->getOldRevision(); + $old = $this->getParentRevision(); $this->slotsUpdate = RevisionSlotsUpdate::newFromRevisionSlots( $this->revision->getSlots(), $old ? $old->getSlots() : null @@ -957,8 +1045,8 @@ class DerivedPageDataUpdater implements IDBAccessObject { * - moved: bool, whether the page was moved (default false) * - restored: bool, whether the page was undeleted (default false) * - oldrevision: Revision object for the pre-update revision (default null) - * - parseroutput: The canonical ParserOutput of $revision (default null) - * - triggeringuser: The user triggering the update (UserIdentity, default null) + * - triggeringUser: The user triggering the update (UserIdentity, defaults to the + * user who created the revision) * - oldredirect: bool, null, or string 'no-change' (default null): * - bool: whether the page was counted as a redirect before that * revision, only used in changed is true and created is false @@ -970,6 +1058,10 @@ class DerivedPageDataUpdater implements IDBAccessObject { * is true, do update the article count * - 'no-change': don't update the article count, ever * When set to null, pageState['oldCountable'] will be used instead if available. + * - causeAction: an arbitrary string identifying the reason for the update. + * See DataUpdate::getCauseAction(). (default 'unknown') + * - causeAgent: name of the user who caused the update. See DataUpdate::getCauseAgent(). + * (string, default 'unknown') */ public function prepareUpdate( RevisionRecord $revision, array $options = [] ) { Assert::parameter( @@ -980,15 +1072,9 @@ class DerivedPageDataUpdater implements IDBAccessObject { 'must be a RevisionRecord (or Revision)' ); Assert::parameter( - !isset( $options['parseroutput'] ) - || $options['parseroutput'] instanceof ParserOutput, - '$options["parseroutput"]', - 'must be a ParserOutput' - ); - Assert::parameter( - !isset( $options['triggeringuser'] ) - || $options['triggeringuser'] instanceof UserIdentity, - '$options["triggeringuser"]', + !isset( $options['triggeringUser'] ) + || $options['triggeringUser'] instanceof UserIdentity, + '$options["triggeringUser"]', 'must be a UserIdentity' ); @@ -998,21 +1084,21 @@ class DerivedPageDataUpdater implements IDBAccessObject { ); } - if ( $this->revision ) { + if ( $this->revision && $this->revision->getId() ) { if ( $this->revision->getId() === $revision->getId() ) { return; // nothing to do! } else { throw new LogicException( 'Trying to re-use DerivedPageDataUpdater with revision ' - .$revision->getId() + . $revision->getId() . ', but it\'s already bound to revision ' . $this->revision->getId() ); } } - if ( $this->pstContentSlots - && !$this->pstContentSlots->hasSameContent( $revision->getSlots() ) + if ( $this->revision + && !$this->revision->getSlots()->hasSameContent( $revision->getSlots() ) ) { throw new LogicException( 'The Revision provided has mismatching content!' @@ -1065,7 +1151,7 @@ class DerivedPageDataUpdater implements IDBAccessObject { if ( !$this->user->equals( $user ) ) { throw new LogicException( 'The Revision provided has a mismatching actor: expected ' - .$this->user->getName() + . $this->user->getName() . ', got ' . $user->getName() ); @@ -1107,7 +1193,6 @@ class DerivedPageDataUpdater implements IDBAccessObject { $this->options['created'] = ( $this->pageState['oldId'] === 0 ); $this->revision = $revision; - $this->pstContentSlots = $revision->getSlots(); $this->doTransition( 'has-revision' ); @@ -1118,74 +1203,14 @@ class DerivedPageDataUpdater implements IDBAccessObject { } // Prune any output that depends on the revision ID. - if ( $this->canonicalParserOutput ) { - if ( $this->outputVariesOnRevisionMetaData( $this->canonicalParserOutput, __METHOD__ ) ) { - $this->canonicalParserOutput = null; - } - } else { - $this->saveParseLogger->debug( __METHOD__ . ": No prepared canonical output...\n" ); - } - - if ( $this->slotsOutput ) { - foreach ( $this->slotsOutput as $role => $prep ) { - if ( $this->outputVariesOnRevisionMetaData( $prep->output, __METHOD__ ) ) { - unset( $this->slotsOutput[$role] ); - } - } - } else { - $this->saveParseLogger->debug( __METHOD__ . ": No prepared output...\n" ); - } - - // reset ParserOptions, so the actual revision ID is used in future ParserOutput generation - $this->canonicalParserOptions = null; - - // Avoid re-generating the canonical ParserOutput if it's known. - // We just trust that the caller is passing the correct ParserOutput! - if ( isset( $options['parseroutput'] ) ) { - $this->canonicalParserOutput = $options['parseroutput']; + if ( $this->renderedRevision ) { + $this->renderedRevision->updateRevision( $revision ); } // TODO: optionally get ParserOutput from the ParserCache here. // Move the logic used by RefreshLinksJob here! } - /** - * @param ParserOutput $out - * @param string $method - * @return bool - */ - private function outputVariesOnRevisionMetaData( ParserOutput $out, $method = __METHOD__ ) { - if ( $out->getFlag( 'vary-revision' ) ) { - // XXX: Just keep the output if the speculative revision ID was correct, like below? - $this->saveParseLogger->info( - "$method: Prepared output has vary-revision...\n" - ); - return true; - } elseif ( $out->getFlag( 'vary-revision-id' ) - && $out->getSpeculativeRevIdUsed() !== $this->revision->getId() - ) { - $this->saveParseLogger->info( - "$method: Prepared output has vary-revision-id with wrong ID...\n" - ); - return true; - } elseif ( $out->getFlag( 'vary-user' ) - && !$this->options['changed'] - ) { - // When Alice makes a null-edit on top of Bob's edit, - // {{REVISIONUSER}} must resolve to "Bob", not "Alice", see T135261. - // TODO: to avoid this, we should check for null-edits in makeCanonicalparserOptions, - // and set setCurrentRevisionCallback to return the existing revision when appropriate. - // See also the comment there [dk 2018-05] - $this->saveParseLogger->info( - "$method: Prepared output has vary-user and is null-edit...\n" - ); - return true; - } else { - wfDebug( "$method: Keeping prepared output...\n" ); - return false; - } - } - /** * @deprecated This only exists for B/C, use the getters on DerivedPageDataUpdater directly! * @return PreparedEdit @@ -1198,11 +1223,11 @@ class DerivedPageDataUpdater implements IDBAccessObject { $preparedEdit->popts = $this->getCanonicalParserOptions(); $preparedEdit->output = $this->getCanonicalParserOutput(); - $preparedEdit->pstContent = $this->pstContentSlots->getContent( 'main' ); + $preparedEdit->pstContent = $this->revision->getContent( 'main' ); $preparedEdit->newContent = $slotsUpdate->isModifiedSlot( 'main' ) ? $slotsUpdate->getModifiedSlot( 'main' )->getContent() - : $this->pstContentSlots->getContent( 'main' ); // XXX: can we just remove this? + : $this->revision->getContent( 'main' ); // XXX: can we just remove this? $preparedEdit->oldContent = null; // unused. // XXX: could get this from the parent revision $preparedEdit->revid = $this->revision ? $this->revision->getId() : null; $preparedEdit->timestamp = $preparedEdit->output->getCacheTime(); @@ -1211,163 +1236,132 @@ class DerivedPageDataUpdater implements IDBAccessObject { return $preparedEdit; } - /** - * @return bool - */ - private function isContentAccessible() { - // XXX: when we move this to a RevisionHtmlProvider, the audience may be configurable! - return $this->isContentPublic(); - } - /** * @param string $role * @param bool $generateHtml * @return ParserOutput */ public function getSlotParserOutput( $role, $generateHtml = true ) { - // TODO: factor this out into a RevisionHtmlProvider that can also be used for viewing. - - $this->assertPrepared( __METHOD__ ); - - if ( isset( $this->slotsOutput[$role] ) ) { - $entry = $this->slotsOutput[$role]; - - if ( $entry->hasHtml || !$generateHtml ) { - return $entry->output; - } - } - - if ( !$this->isContentAccessible() ) { - // empty output - $output = new ParserOutput(); - } else { - $content = $this->getRawContent( $role ); - - $output = $content->getParserOutput( - $this->getTitle(), - $this->revision ? $this->revision->getId() : null, - $this->getCanonicalParserOptions(), - $generateHtml - ); - } - - $this->slotsOutput[$role] = (object)[ - 'output' => $output, - 'hasHtml' => $generateHtml, - ]; - - $output->setCacheTime( $this->getTimestampNow() ); - - return $output; + return $this->getRenderedRevision()->getSlotParserOutput( + $role, + [ 'generate-html' => $generateHtml ] + ); } /** * @return ParserOutput */ public function getCanonicalParserOutput() { - if ( $this->canonicalParserOutput ) { - return $this->canonicalParserOutput; - } - - // TODO: MCR: logic for combining the output of multiple slot goes here! - // TODO: factor this out into a RevisionHtmlProvider that can also be used for viewing. - $this->canonicalParserOutput = $this->getSlotParserOutput( 'main' ); - - return $this->canonicalParserOutput; + return $this->getRenderedRevision()->getRevisionParserOutput(); } /** * @return ParserOptions */ public function getCanonicalParserOptions() { - if ( $this->canonicalParserOptions ) { - return $this->canonicalParserOptions; + return $this->getRenderedRevision()->getOptions(); + } + + /** + * @param bool $recursive + * + * @return DeferrableUpdate[] + */ + public function getSecondaryDataUpdates( $recursive = false ) { + if ( $this->isContentDeleted() ) { + // This shouldn't happen, since the current content is always public, + // and DataUpates are only needed for current content. + return []; } - // TODO: ParserOptions should *not* be controlled by the ContentHandler! - // See T190712 for how to fix this for Wikibase. - $this->canonicalParserOptions = $this->wikiPage->makeParserOptions( 'canonical' ); + $output = $this->getCanonicalParserOutput(); - //TODO: if $this->revision is not set but we already know that we pending update is a - // null-edit, we should probably use the page's current revision here. - // That would avoid the need for the !$this->options['changed'] branch in - // outputVariesOnRevisionMetaData [dk 2018-05] + // Construct a LinksUpdate for the combined canonical output. + $linksUpdate = new LinksUpdate( + $this->getTitle(), + $output, + $recursive + ); - if ( $this->revision ) { - // Make sure we use the appropriate revision ID when generating output - $title = $this->getTitle(); - $oldCallback = $this->canonicalParserOptions->getCurrentRevisionCallback(); - $this->canonicalParserOptions->setCurrentRevisionCallback( - function ( Title $parserTitle, $parser = false ) use ( $title, &$oldCallback ) { - if ( $parserTitle->equals( $title ) ) { - $legacyRevision = new Revision( $this->revision ); - return $legacyRevision; - } else { - return call_user_func( $oldCallback, $parserTitle, $parser ); - } - } + $allUpdates = [ $linksUpdate ]; + + // NOTE: Run updates for all slots, not just the modified slots! Otherwise, + // info for an inherited slot may end up being removed. This is also needed + // to ensure that purges are effective. + $renderedRevision = $this->getRenderedRevision(); + foreach ( $this->getSlots()->getSlotRoles() as $role ) { + $slot = $this->getRawSlot( $role ); + $content = $slot->getContent(); + $handler = $content->getContentHandler(); + + $updates = $handler->getSecondaryDataUpdates( + $this->getTitle(), + $content, + $role, + $renderedRevision ); - } else { - // NOTE: we only get here without READ_LATEST if called directly by application logic - $dbIndex = $this->useMaster() - ? DB_MASTER // use the best possible guess - : DB_REPLICA; // T154554 - - $this->canonicalParserOptions->setSpeculativeRevIdCallback( - function () use ( $dbIndex ) { - // TODO: inject LoadBalancer! - $lb = MediaWikiServices::getInstance()->getDBLoadBalancer(); - // Use a fresh connection in order to see the latest data, by avoiding - // stale data from REPEATABLE-READ snapshots. - // HACK: But don't use a fresh connection in unit tests, since it would not have - // the fake tables. This should be handled by the LoadBalancer! - $flags = defined( 'MW_PHPUNIT_TEST' ) ? 0 : $lb::CONN_TRX_AUTOCOMMIT; - $db = $lb->getConnectionRef( $dbIndex, [], $this->getWikiId(), $flags ); - - return 1 + (int)$db->selectField( - 'revision', - 'MAX(rev_id)', - [], - __METHOD__ - ); - } + $allUpdates = array_merge( $allUpdates, $updates ); + + // TODO: remove B/C hack in 1.32! + // NOTE: we assume that the combined output contains all relevant meta-data for + // all slots! + $legacyUpdates = $content->getSecondaryDataUpdates( + $this->getTitle(), + null, + $recursive, + $output ); + + // HACK: filter out redundant and incomplete LinksUpdates + $legacyUpdates = array_filter( $legacyUpdates, function ( $update ) { + return !( $update instanceof LinksUpdate ); + } ); + + $allUpdates = array_merge( $allUpdates, $legacyUpdates ); } - return $this->canonicalParserOptions; - } + // XXX: if a slot was removed by an earlier edit, but deletion updates failed to run at + // that time, we don't know for which slots to run deletion updates when purging a page. + // We'd have to examine the entire history of the page to determine that. Perhaps there + // could be a "try extra hard" mode for that case that would run a DB query to find all + // roles/models ever used on the page. On the other hand, removing slots should be quite + // rare, so perhaps this isn't worth the trouble. - /** - * @param bool $recursive - * - * @return DataUpdate[] - */ - public function getSecondaryDataUpdates( $recursive = false ) { - // TODO: MCR: getSecondaryDataUpdates() needs a complete overhaul to avoid DataUpdates - // from different slots overwriting each other in the database. Plan: - // * replace direct calls to Content::getSecondaryDataUpdates() with calls to this method - // * Construct LinksUpdate here, on the combined ParserOutput, instead of in AbstractContent - // for each slot. - // * Pass $slot into getSecondaryDataUpdates() - probably be introducing a new duplicate - // version of this function in ContentHandler. - // * The new method gets the PreparedEdit, but no $recursive flag (that's for LinksUpdate) - // * Hack: call both the old and the new getSecondaryDataUpdates method here; Pass - // the per-slot ParserOutput to the old method, for B/C. - // * Hack: If there is more than one slot, filter LinksUpdate from the DataUpdates - // returned by getSecondaryDataUpdates, and use a LinksUpdated for the combined output - // instead. - // * Call the SecondaryDataUpdates hook here (or kill it - its signature doesn't make sense) - - $content = $this->getSlots()->getContent( 'main' ); - - // NOTE: $output is the combined output, to be shown in the default view. - $output = $this->getCanonicalParserOutput(); + // TODO: consolidate with similar logic in WikiPage::getDeletionUpdates() + $wikiPage = $this->getWikiPage(); + $parentRevision = $this->getParentRevision(); + foreach ( $this->getRemovedSlotRoles() as $role ) { + // HACK: we should get the content model of the removed slot from a SlotRoleHandler! + // For now, find the slot in the parent revision - if the slot was removed, it should + // always exist in the parent revision. + $parentSlot = $parentRevision->getSlot( $role, RevisionRecord::RAW ); + $content = $parentSlot->getContent(); + $handler = $content->getContentHandler(); + + $updates = $handler->getDeletionUpdates( + $this->getTitle(), + $role + ); + $allUpdates = array_merge( $allUpdates, $updates ); - $updates = $content->getSecondaryDataUpdates( - $this->getTitle(), null, $recursive, $output + // TODO: remove B/C hack in 1.32! + $legacyUpdates = $content->getDeletionUpdates( $wikiPage ); + + // HACK: filter out redundant and incomplete LinksDeletionUpdate + $legacyUpdates = array_filter( $legacyUpdates, function ( $update ) { + return !( $update instanceof LinksDeletionUpdate ); + } ); + + $allUpdates = array_merge( $allUpdates, $legacyUpdates ); + } + + // TODO: hard deprecate SecondaryDataUpdates in favor of RevisionDataUpdates in 1.33! + Hooks::run( + 'RevisionDataUpdates', + [ $this->getTitle(), $renderedRevision, &$allUpdates ] ); - return $updates; + return $allUpdates; } /** @@ -1387,45 +1381,16 @@ class DerivedPageDataUpdater implements IDBAccessObject { $wikiPage = $this->getWikiPage(); // TODO: use only for legacy hooks! - // NOTE: this may trigger the first parsing of the new content after an edit (when not - // using pre-generated stashed output). - // XXX: we may want to use the PoolCounter here. This would perhaps allow the initial parse - // to be perform post-send. The client could already follow a HTTP redirect to the - // page view, but would then have to wait for a response until rendering is complete. - $output = $this->getCanonicalParserOutput(); - - // Save it to the parser cache. - // Make sure the cache time matches page_touched to avoid double parsing. - $this->parserCache->save( - $output, $wikiPage, $this->getCanonicalParserOptions(), - $this->revision->getTimestamp(), $this->revision->getId() - ); - $legacyUser = User::newFromIdentity( $this->user ); $legacyRevision = new Revision( $this->revision ); - // Update the links tables and other secondary data - $recursive = $this->options['changed']; // T52785 - $updates = $this->getSecondaryDataUpdates( $recursive ); + $this->doParserCacheUpdate(); - foreach ( $updates as $update ) { - // TODO: make an $option field for the cause - $update->setCause( 'edit-page', $this->user->getName() ); - if ( $update instanceof LinksUpdate ) { - $update->setRevision( $legacyRevision ); - - if ( !empty( $this->options['triggeringuser'] ) ) { - /** @var UserIdentity|User $triggeringUser */ - $triggeringUser = $this->options['triggeringuser']; - if ( !$triggeringUser instanceof User ) { - $triggeringUser = User::newFromIdentity( $triggeringUser ); - } - - $update->setTriggeringUser( $triggeringUser ); - } - } - DeferredUpdates::addUpdate( $update ); - } + $this->doSecondaryDataUpdates( [ + // T52785 do not update any other pages on a null edit + 'recursive' => $this->options['changed'], + 'defer' => DeferredUpdates::POSTSEND, + ] ); // TODO: MCR: check if *any* changed slot supports categories! if ( $this->rcWatchCategoryMembership @@ -1495,7 +1460,7 @@ class DerivedPageDataUpdater implements IDBAccessObject { // TODO: make search infrastructure aware of slots! $mainSlot = $this->revision->getSlot( 'main' ); - if ( !$mainSlot->isInherited() && $this->isContentPublic() ) { + if ( !$mainSlot->isInherited() && !$this->isContentDeleted() ) { DeferredUpdates::addUpdate( new SearchUpdate( $id, $dbKey, $mainSlot->getContent() ) ); } @@ -1530,7 +1495,7 @@ class DerivedPageDataUpdater implements IDBAccessObject { if ( $title->getNamespace() == NS_MEDIAWIKI && $this->getRevisionSlotsUpdate()->isModifiedSlot( 'main' ) ) { - $mainContent = $this->isContentPublic() ? $this->getRawContent( 'main' ) : null; + $mainContent = $this->isContentDeleted() ? null : $this->getRawContent( 'main' ); $this->messageCache->updateMessageOverride( $title, $mainContent ); } @@ -1542,7 +1507,7 @@ class DerivedPageDataUpdater implements IDBAccessObject { WikiPage::onArticleEdit( $title, $legacyRevision, $this->getTouchedSlotRoles() ); } - $oldRevision = $this->getOldRevision(); + $oldRevision = $this->getParentRevision(); $oldLegacyRevision = $oldRevision ? new Revision( $oldRevision ) : null; // TODO: In the wiring, register a listener for this on the new PageEventEmitter @@ -1553,4 +1518,93 @@ class DerivedPageDataUpdater implements IDBAccessObject { $this->doTransition( 'done' ); } + /** + * Do secondary data updates (such as updating link tables). + * + * MCR note: this method is temporarily exposed via WikiPage::doSecondaryDataUpdates. + * + * @param array $options + * - recursive: make the update recursive, i.e. also update pages which transclude the + * current page or otherwise depend on it (default: false) + * - defer: one of the DeferredUpdates constants, or false to run immediately after waiting + * for replication of the changes from the SecondaryDataUpdates hooks (default: false) + * - transactionTicket: a transaction ticket from LBFactory::getEmptyTransactionTicket(), + * only when defer is false (default: null) + * @since 1.32 + */ + public function doSecondaryDataUpdates( array $options = [] ) { + $this->assertHasRevision( __METHOD__ ); + $options += [ + 'recursive' => false, + 'defer' => false, + 'transactionTicket' => null, + ]; + $deferValues = [ false, DeferredUpdates::PRESEND, DeferredUpdates::POSTSEND ]; + if ( !in_array( $options['defer'], $deferValues, true ) ) { + throw new InvalidArgumentException( 'invalid value for defer: ' . $options['defer'] ); + } + Assert::parameterType( 'integer|null', $options['transactionTicket'], + '$options[\'transactionTicket\']' ); + + $updates = $this->getSecondaryDataUpdates( $options['recursive'] ); + + $triggeringUser = $this->options['triggeringUser'] ?? $this->user; + if ( !$triggeringUser instanceof User ) { + $triggeringUser = User::newFromIdentity( $triggeringUser ); + } + $causeAction = $this->options['causeAction'] ?? 'unknown'; + $causeAgent = $this->options['causeAgent'] ?? 'unknown'; + $legacyRevision = new Revision( $this->revision ); + + if ( $options['defer'] === false && $options['transactionTicket'] !== null ) { + // For legacy hook handlers doing updates via LinksUpdateConstructed, make sure + // any pending writes they made get flushed before the doUpdate() calls below. + // This avoids snapshot-clearing errors in LinksUpdate::acquirePageLock(). + $this->loadbalancerFactory->commitAndWaitForReplication( + __METHOD__, $options['transactionTicket'] + ); + } + + foreach ( $updates as $update ) { + if ( $update instanceof DataUpdate ) { + $update->setCause( $causeAction, $causeAgent ); + } + if ( $update instanceof LinksUpdate ) { + $update->setRevision( $legacyRevision ); + $update->setTriggeringUser( $triggeringUser ); + } + if ( $options['defer'] === false ) { + if ( $options['transactionTicket'] !== null ) { + $update->setTransactionTicket( $options['transactionTicket'] ); + } + $update->doUpdate(); + } else { + DeferredUpdates::addUpdate( $update, $options['defer'] ); + } + } + } + + public function doParserCacheUpdate() { + $this->assertHasRevision( __METHOD__ ); + + $wikiPage = $this->getWikiPage(); // TODO: ParserCache should accept a RevisionRecord instead + + // NOTE: this may trigger the first parsing of the new content after an edit (when not + // using pre-generated stashed output). + // XXX: we may want to use the PoolCounter here. This would perhaps allow the initial parse + // to be performed post-send. The client could already follow a HTTP redirect to the + // page view, but would then have to wait for a response until rendering is complete. + $output = $this->getCanonicalParserOutput(); + + // Save it to the parser cache. Use the revision timestamp in the case of a + // freshly saved edit, as that matches page_touched and a mismatch would trigger an + // unnecessary reparse. + $timestamp = $this->options['changed'] ? $this->revision->getTimestamp() + : $output->getTimestamp(); + $this->parserCache->save( + $output, $wikiPage, $this->getCanonicalParserOptions(), + $timestamp, $this->revision->getId() + ); + } + } diff --git a/includes/Storage/MutableRevisionRecord.php b/includes/Storage/MutableRevisionRecord.php index 1aa1165d99..72d6547bba 100644 --- a/includes/Storage/MutableRevisionRecord.php +++ b/includes/Storage/MutableRevisionRecord.php @@ -314,6 +314,17 @@ class MutableRevisionRecord extends RevisionRecord { return $this->mSha1; } + /** + * Returns the slots defined for this revision as a MutableRevisionSlots instance, + * which can be modified to defined the slots for this revision. + * + * @return MutableRevisionSlots + */ + public function getSlots() { + // Overwritten just guarantee the more narrow return type. + return parent::getSlots(); + } + /** * Invalidate cached aggregate values such as hash and size. */ diff --git a/includes/Storage/NameTableStore.php b/includes/Storage/NameTableStore.php index 52e8f5b993..6c7919d254 100644 --- a/includes/Storage/NameTableStore.php +++ b/includes/Storage/NameTableStore.php @@ -27,7 +27,6 @@ use Wikimedia\Assert\Assert; use Wikimedia\Rdbms\Database; use Wikimedia\Rdbms\IDatabase; use Wikimedia\Rdbms\ILoadBalancer; -use Wikimedia\Rdbms\LoadBalancer; /** * @author Addshore @@ -35,7 +34,7 @@ use Wikimedia\Rdbms\LoadBalancer; */ class NameTableStore { - /** @var LoadBalancer */ + /** @var ILoadBalancer */ private $loadBalancer; /** @var WANObjectCache */ @@ -159,11 +158,13 @@ class NameTableStore { if ( $searchResult === false ) { $id = $this->store( $name ); if ( $id === null ) { - // RACE: $name was already in the db, probably just inserted, so load from master - // Use DBO_TRX to avoid missing inserts due to other threads or REPEATABLE-READs - $table = $this->loadTable( - $this->getDBConnection( DB_MASTER, LoadBalancer::CONN_TRX_AUTOCOMMIT ) - ); + // RACE: $name was already in the db, probably just inserted, so load from master. + // Use DBO_TRX to avoid missing inserts due to other threads or REPEATABLE-READs. + // ...but not during unit tests, because we need the fake DB tables of the default + // connection. + $connFlags = defined( 'MW_PHPUNIT_TEST' ) ? 0 : ILoadBalancer::CONN_TRX_AUTOCOMMIT; + $table = $this->reloadMap( $connFlags ); + $searchResult = array_search( $name, $table, true ); if ( $searchResult === false ) { // Insert failed due to IGNORE flag, but DB_MASTER didn't give us the data @@ -172,14 +173,15 @@ class NameTableStore { $this->logger->error( $m ); throw new NameTableAccessException( $m ); } - $this->purgeWANCache( - function () { - $this->cache->reap( $this->getCacheKey(), INF ); - } - ); + } elseif ( isset( $table[$id] ) ) { + throw new NameTableAccessException( + "Expected unused ID from database insert for '$name' " + . " into '{$this->table}', but ID $id is already associated with" + . " the name '{$table[$id]}'! This may indicate database corruption!" ); } else { $table[$id] = $name; $searchResult = $id; + // As store returned an ID we know we inserted so delete from WAN cache $this->purgeWANCache( function () { @@ -193,6 +195,31 @@ class NameTableStore { return $searchResult; } + /** + * Reloads the name table from the master database, and purges the WAN cache entry. + * + * @note This should only be called in situations where the local cache has been detected + * to be out of sync with the database. There should be no reason to call this method + * from outside the NameTabelStore during normal operation. This method may however be + * useful in unit tests. + * + * @param int $connFlags ILoadBalancer::CONN_XXX flags. Optional. + * + * @return \string[] The freshly reloaded name map + */ + public function reloadMap( $connFlags = 0 ) { + $this->tableCache = $this->loadTable( + $this->getDBConnection( DB_MASTER, $connFlags ) + ); + $this->purgeWANCache( + function () { + $this->cache->reap( $this->getCacheKey(), INF ); + } + ); + + return $this->tableCache; + } + /** * Get the id of the given name. * If the name doesn't exist this will throw. diff --git a/includes/Storage/NameTableStoreFactory.php b/includes/Storage/NameTableStoreFactory.php new file mode 100644 index 0000000000..02ea9a7656 --- /dev/null +++ b/includes/Storage/NameTableStoreFactory.php @@ -0,0 +1,149 @@ + [ + 'idField' => 'ctd_id', + 'nameField' => 'ctd_name', + 'normalizationCallback' => null, + 'insertCallback' => function ( $insertFields ) { + $insertFields['ctd_user_defined'] = 0; + $insertFields['ctd_count'] = 0; + return $insertFields; + } + ], + + 'content_models' => [ + 'idField' => 'model_id', + 'nameField' => 'model_name', + /** + * No strtolower normalization is added to the service as there are examples of + * extensions that do not stick to this assumption. + * - extensions/examples/DataPages define( 'CONTENT_MODEL_XML_DATA','XML_DATA' ); + * - extensions/Scribunto define( 'CONTENT_MODEL_SCRIBUNTO', 'Scribunto' ); + */ + ], + + 'slot_roles' => [ + 'idField' => 'role_id', + 'nameField' => 'role_name', + 'normalizationCallback' => 'strtolower', + ], + ]; + return self::$info; + } + + public function __construct( + ILBFactory $lbFactory, + WANObjectCache $cache, + LoggerInterface $logger + ) { + $this->lbFactory = $lbFactory; + $this->cache = $cache; + $this->logger = $logger; + } + + /** + * Get a NameTableStore for a specific table + * + * @param string $tableName The table name + * @param string|false $wiki The target wiki ID, or false for the current wiki + * @return NameTableStore + */ + public function get( $tableName, $wiki = false ) : NameTableStore { + $infos = self::getTableInfo(); + if ( !isset( $infos[$tableName] ) ) { + throw new \InvalidArgumentException( "Invalid table name \$tableName" ); + } + if ( $wiki === wfWikiID() ) { + $wiki = false; + } + if ( isset( $this->stores[$tableName][$wiki] ) ) { + return $this->stores[$tableName][$wiki]; + } + + $info = $infos[$tableName]; + $store = new NameTableStore( + $this->lbFactory->getMainLB( $wiki ), + $this->cache, + $this->logger, + $tableName, + $info['idField'], + $info['nameField'], + $info['normalizationCallback'] ?? null, + $wiki, + $info['insertCallback'] ?? null + ); + $this->stores[$tableName][$wiki] = $store; + return $store; + } + + /** + * Get a NameTableStore for the change_tag_def table + * + * @param string|bool $wiki + * @return NameTableStore + */ + public function getChangeTagDef( $wiki = false ) : NameTableStore { + return $this->get( 'change_tag_def', $wiki ); + } + + /** + * Get a NameTableStore for the content_models table + * + * @param string|bool $wiki + * @return NameTableStore + */ + public function getContentModels( $wiki = false ) : NameTableStore { + return $this->get( 'content_models', $wiki ); + } + + /** + * Get a NameTableStore for the slot_roles table + * + * @param string|bool $wiki + * @return NameTableStore + */ + public function getSlotRoles( $wiki = false ) : NameTableStore { + return $this->get( 'slot_roles', $wiki ); + } +} diff --git a/includes/Storage/PageUpdater.php b/includes/Storage/PageUpdater.php index c6795ea83a..1621213c49 100644 --- a/includes/Storage/PageUpdater.php +++ b/includes/Storage/PageUpdater.php @@ -344,13 +344,18 @@ class PageUpdater { // TODO: MCR: check the role and the content's model against the list of supported // roles, see T194046. - if ( $role !== 'main' ) { - throw new InvalidArgumentException( 'Only the main slot is presently supported' ); - } - $this->slotsUpdate->modifyContent( $role, $content ); } + /** + * Set the new slot for the given slot role + * + * @param SlotRecord $slot + */ + public function setSlot( SlotRecord $slot ) { + $this->slotsUpdate->modifySlot( $slot ); + } + /** * Explicitly inherit a slot from some earlier revision. * @@ -835,7 +840,6 @@ class PageUpdater { * * @param CommentStoreComment $comment * @param User $user - * @param string $timestamp * @param int $flags * @param Status $status * @@ -844,7 +848,6 @@ class PageUpdater { private function makeNewRevision( CommentStoreComment $comment, User $user, - $timestamp, $flags, Status $status ) { @@ -852,7 +855,11 @@ class PageUpdater { $title = $this->getTitle(); $parent = $this->grabParentRevision(); - $rev = new MutableRevisionRecord( $title, $this->getWikiId() ); + // XXX: we expect to get a MutableRevisionRecord here, but that's a bit brittle! + // TODO: introduce something like an UnsavedRevisionFactory service instead! + /** @var MutableRevisionRecord $rev */ + $rev = $this->derivedDataUpdater->getRevision(); + $rev->setPageId( $title->getArticleID() ); if ( $parent ) { @@ -864,20 +871,15 @@ class PageUpdater { $rev->setComment( $comment ); $rev->setUser( $user ); - $rev->setTimestamp( $timestamp ); $rev->setMinorEdit( ( $flags & EDIT_MINOR ) > 0 ); - foreach ( $this->derivedDataUpdater->getSlots()->getSlots() as $slot ) { + foreach ( $rev->getSlots()->getSlots() as $slot ) { $content = $slot->getContent(); // XXX: We may push this up to the "edit controller" level, see T192777. // TODO: change the signature of PrepareSave to not take a WikiPage! $prepStatus = $content->prepareSave( $wikiPage, $flags, $oldid, $user ); - if ( $prepStatus->isOK() ) { - $rev->setSlot( $slot ); - } - // TODO: MCR: record which problem arose in which slot. $status->merge( $prepStatus ); } @@ -899,9 +901,6 @@ class PageUpdater { // Update article, but only if changed. $status = Status::newGood( [ 'new' => false, 'revision' => null, 'revision-record' => null ] ); - // Convenience variables - $now = $this->getTimestampNow(); - $oldRev = $this->grabParentRevision(); $oldid = $oldRev ? $oldRev->getId() : 0; @@ -915,7 +914,6 @@ class PageUpdater { $newRevisionRecord = $this->makeNewRevision( $summary, $user, - $now, $flags, $status ); @@ -924,6 +922,8 @@ class PageUpdater { return $status; } + $now = $newRevisionRecord->getTimestamp(); + // XXX: we may want a flag that allows a null revision to be forced! $changed = $this->derivedDataUpdater->isChange(); @@ -1055,12 +1055,9 @@ class PageUpdater { $status = Status::newGood( [ 'new' => true, 'revision' => null, 'revision-record' => null ] ); - $now = $this->getTimestampNow(); - $newRevisionRecord = $this->makeNewRevision( $summary, $user, - $now, $flags, $status ); @@ -1069,6 +1066,8 @@ class PageUpdater { return $status; } + $now = $newRevisionRecord->getTimestamp(); + $dbw = $this->getDBConnectionRef( DB_MASTER ); $dbw->startAtomic( __METHOD__ ); @@ -1182,6 +1181,10 @@ class PageUpdater { $wikiPage, $newRevisionRecord, $user, $summary, $flags, $status, $hints ) { + // set debug data + $hints['causeAction'] = 'edit-page'; + $hints['causeAgent'] = $user->getName(); + $newLegacyRevision = new Revision( $newRevisionRecord ); $mainContent = $newRevisionRecord->getContent( 'main', RevisionRecord::RAW ); diff --git a/includes/Storage/RevisionArchiveRecord.php b/includes/Storage/RevisionArchiveRecord.php index 213ee3cd1b..173da51907 100644 --- a/includes/Storage/RevisionArchiveRecord.php +++ b/includes/Storage/RevisionArchiveRecord.php @@ -167,4 +167,13 @@ class RevisionArchiveRecord extends RevisionRecord { return parent::getTimestamp(); } + /** + * @see RevisionStore::isComplete + * + * @return bool always true. + */ + public function isReadyForInsertion() { + return true; + } + } diff --git a/includes/Storage/RevisionRecord.php b/includes/Storage/RevisionRecord.php index 17c56ea0ea..8c31a3caf7 100644 --- a/includes/Storage/RevisionRecord.php +++ b/includes/Storage/RevisionRecord.php @@ -532,4 +532,29 @@ abstract class RevisionRecord { } } + /** + * Returns whether this RevisionRecord is ready for insertion, that is, whether it contains all + * information needed to save it to the database. This should trivially be true for + * RevisionRecords loaded from the database. + * + * Note that this may return true even if getId() or getPage() return null or 0, since these + * are generally assigned while the revision is saved to the database, and may not be available + * before. + * + * @return bool + */ + public function isReadyForInsertion() { + // NOTE: don't check getSize() and getSha1(), since that may cause the full content to + // be loaded in order to calculate the values. Just assume these methods will not return + // null if mSlots is not empty. + + // NOTE: getId() and getPageId() may return null before a revision is saved, so don't + //check them. + + return $this->getTimestamp() !== null + && $this->getComment( self::RAW ) !== null + && $this->getUser( self::RAW ) !== null + && $this->mSlots->getSlotRoles() !== []; + } + } diff --git a/includes/Storage/RevisionStore.php b/includes/Storage/RevisionStore.php index 5769527796..61b428f13c 100644 --- a/includes/Storage/RevisionStore.php +++ b/includes/Storage/RevisionStore.php @@ -466,6 +466,12 @@ class RevisionStore $this->failOnNull( $user->getId(), 'user field' ); $this->failOnEmpty( $user->getName(), 'user_text field' ); + if ( !$rev->isReadyForInsertion() ) { + // This is here for future-proofing. At the time this check being added, it + // was redundant to the individual checks above. + throw new IncompleteRevisionException( 'Revision is incomplete' ); + } + // TODO: we shouldn't need an actual Title here. $title = Title::newFromLinkTarget( $rev->getPageAsLinkTarget() ); $pageId = $this->failOnEmpty( $rev->getPageId(), 'rev_page field' ); // check this early @@ -519,11 +525,11 @@ class RevisionStore $slot = $rev->getSlot( $role, RevisionRecord::RAW ); Assert::postcondition( $slot->getContent() !== null, - $role . ' slot must have content' + $role . ' slot must have content' ); Assert::postcondition( $slot->hasRevision(), - $role . ' slot must have a revision associated' + $role . ' slot must have a revision associated' ); } @@ -566,9 +572,14 @@ class RevisionStore foreach ( $slotRoles as $role ) { $slot = $rev->getSlot( $role, RevisionRecord::RAW ); - if ( $slot->hasRevision() ) { - // If the SlotRecord already has a revision ID set, this means it already exists - // in the database, and should already belong to the current revision. + // If the SlotRecord already has a revision ID set, this means it already exists + // in the database, and should already belong to the current revision. + // However, a slot may already have a revision, but no content ID, if the slot + // is emulated based on the archive table, because we are in SCHEMA_COMPAT_READ_OLD + // mode, and the respective archive row was not yet migrated to the new schema. + // In that case, a new slot row (and content row) must be inserted even during + // undeletion. + if ( $slot->hasRevision() && $slot->hasContentId() ) { // TODO: properly abort transaction if the assertion fails! Assert::parameter( $slot->getRevision() === $revisionId, @@ -612,6 +623,8 @@ class RevisionStore * @param IDatabase $dbw * @param int $revisionId * @param string &$blobAddress (may change!) + * + * @return int the text row id */ private function updateRevisionTextId( IDatabase $dbw, $revisionId, &$blobAddress ) { $textId = $this->blobStore->getTextIdFromAddress( $blobAddress ); @@ -631,6 +644,8 @@ class RevisionStore [ 'rev_id' => $revisionId ], __METHOD__ ); + + return $textId; } /** @@ -654,11 +669,16 @@ class RevisionStore $blobAddress = $this->storeContentBlob( $protoSlot, $title, $blobHints ); } + $contentId = null; + // Write the main slot's text ID to the revision table for backwards compatibility if ( $protoSlot->getRole() === 'main' && $this->hasMcrSchemaFlags( SCHEMA_COMPAT_WRITE_OLD ) ) { - $this->updateRevisionTextId( $dbw, $revisionId, $blobAddress ); + // If SCHEMA_COMPAT_WRITE_NEW is also set, the fake content ID is overwritten + // with the real content ID below. + $textId = $this->updateRevisionTextId( $dbw, $revisionId, $blobAddress ); + $contentId = $this->emulateContentId( $textId ); } if ( $this->hasMcrSchemaFlags( SCHEMA_COMPAT_WRITE_NEW ) ) { @@ -669,8 +689,6 @@ class RevisionStore } $this->insertSlotRowOn( $protoSlot, $dbw, $revisionId, $contentId ); - } else { - $contentId = null; } $savedSlot = SlotRecord::newSaved( @@ -746,6 +764,76 @@ class RevisionStore if ( !isset( $revisionRow['rev_id'] ) ) { // only if auto-increment was used $revisionRow['rev_id'] = intval( $dbw->insertId() ); + + if ( $dbw->getType() === 'mysql' ) { + // (T202032) MySQL until 8.0 and MariaDB until some version after 10.1.34 don't save the + // auto-increment value to disk, so on server restart it might reuse IDs from deleted + // revisions. We can fix that with an insert with an explicit rev_id value, if necessary. + + $maxRevId = intval( $dbw->selectField( 'archive', 'MAX(ar_rev_id)', '', __METHOD__ ) ); + $table = 'archive'; + if ( $this->hasMcrSchemaFlags( SCHEMA_COMPAT_WRITE_NEW ) ) { + $maxRevId2 = intval( $dbw->selectField( 'slots', 'MAX(slot_revision_id)', '', __METHOD__ ) ); + if ( $maxRevId2 >= $maxRevId ) { + $maxRevId = $maxRevId2; + $table = 'slots'; + } + } + + if ( $maxRevId >= $revisionRow['rev_id'] ) { + $this->logger->debug( + '__METHOD__: Inserted revision {revid} but {table} has revisions up to {maxrevid}.' + . ' Trying to fix it.', + [ + 'revid' => $revisionRow['rev_id'], + 'table' => $table, + 'maxrevid' => $maxRevId, + ] + ); + + if ( !$dbw->lock( 'fix-for-T202032', __METHOD__ ) ) { + throw new MWException( 'Failed to get database lock for T202032' ); + } + $fname = __METHOD__; + $dbw->onTransactionResolution( function ( $trigger, $dbw ) use ( $fname ) { + $dbw->unlock( 'fix-for-T202032', $fname ); + } ); + + $dbw->delete( 'revision', [ 'rev_id' => $revisionRow['rev_id'] ], __METHOD__ ); + + // The locking here is mostly to make MySQL bypass the REPEATABLE-READ transaction + // isolation (weird MySQL "feature"). It does seem to block concurrent auto-incrementing + // inserts too, though, at least on MariaDB 10.1.29. + // + // Don't try to lock `revision` in this way, it'll deadlock if there are concurrent + // transactions in this code path thanks to the row lock from the original ->insert() above. + // + // And we have to use raw SQL to bypass the "aggregation used with a locking SELECT" warning + // that's for non-MySQL DBs. + $row1 = $dbw->query( + $dbw->selectSqlText( 'archive', [ 'v' => "MAX(ar_rev_id)" ], '', __METHOD__ ) . ' FOR UPDATE' + )->fetchObject(); + if ( $this->hasMcrSchemaFlags( SCHEMA_COMPAT_WRITE_NEW ) ) { + $row2 = $dbw->query( + $dbw->selectSqlText( 'slots', [ 'v' => "MAX(slot_revision_id)" ], '', __METHOD__ ) + . ' FOR UPDATE' + )->fetchObject(); + } else { + $row2 = null; + } + $maxRevId = max( + $maxRevId, + $row1 ? intval( $row1->v ) : 0, + $row2 ? intval( $row2->v ) : 0 + ); + + // If we don't have SCHEMA_COMPAT_WRITE_NEW, all except the first of any concurrent + // transactions will throw a duplicate key error here. It doesn't seem worth trying + // to avoid that. + $revisionRow['rev_id'] = $maxRevId + 1; + $dbw->insert( 'revision', $revisionRow, __METHOD__ ); + } + } } $commentCallback( $revisionRow['rev_id'] ); @@ -1124,6 +1212,7 @@ class RevisionStore $mainSlotRow->role_name = 'main'; $mainSlotRow->model_name = null; $mainSlotRow->slot_revision_id = null; + $mainSlotRow->slot_content_id = null; $mainSlotRow->content_address = null; $content = null; @@ -1174,6 +1263,12 @@ class RevisionStore $mainSlotRow->format_name = isset( $row->rev_content_format ) ? strval( $row->rev_content_format ) : null; + + if ( isset( $row->rev_text_id ) && intval( $row->rev_text_id ) > 0 ) { + // Overwritten below for SCHEMA_COMPAT_WRITE_NEW + $mainSlotRow->slot_content_id + = $this->emulateContentId( intval( $row->rev_text_id ) ); + } } elseif ( is_array( $row ) ) { $mainSlotRow->slot_revision_id = isset( $row['id'] ) ? intval( $row['id'] ) : null; @@ -1213,6 +1308,12 @@ class RevisionStore $mainSlotRow->format_name = $handler->getDefaultFormat(); } } + + if ( isset( $row['text_id'] ) && intval( $row['text_id'] ) > 0 ) { + // Overwritten below for SCHEMA_COMPAT_WRITE_NEW + $mainSlotRow->slot_content_id + = $this->emulateContentId( intval( $row['text_id'] ) ); + } } else { throw new MWException( 'Revision constructor passed invalid row format.' ); } @@ -1250,18 +1351,38 @@ class RevisionStore }; } - // NOTE: this callback will be looped through RevisionSlot::newInherited(), allowing - // the inherited slot to have the same content_id as the original slot. In that case, - // $slot will be the inherited slot, while $mainSlotRow still refers to the original slot. - $mainSlotRow->slot_content_id = - function ( SlotRecord $slot ) use ( $queryFlags, $mainSlotRow ) { - $db = $this->getDBConnectionRefForQueryFlags( $queryFlags ); - return $this->findSlotContentId( $db, $mainSlotRow->slot_revision_id, 'main' ); - }; + if ( $this->hasMcrSchemaFlags( SCHEMA_COMPAT_WRITE_NEW ) ) { + // NOTE: this callback will be looped through RevisionSlot::newInherited(), allowing + // the inherited slot to have the same content_id as the original slot. In that case, + // $slot will be the inherited slot, while $mainSlotRow still refers to the original slot. + $mainSlotRow->slot_content_id = + function ( SlotRecord $slot ) use ( $queryFlags, $mainSlotRow ) { + $db = $this->getDBConnectionRefForQueryFlags( $queryFlags ); + return $this->findSlotContentId( $db, $mainSlotRow->slot_revision_id, 'main' ); + }; + } return new SlotRecord( $mainSlotRow, $content ); } + /** + * Provides a content ID to use with emulated SlotRecords in SCHEMA_COMPAT_OLD mode, + * based on the revision's text ID (rev_text_id or ar_text_id, respectively). + * Note that in SCHEMA_COMPAT_WRITE_BOTH, a callback to findSlotContentId() should be used + * instead, since in that mode, some revision rows may already have a real content ID, + * while other's don't - and for the ones that don't, we should indicate that it + * is missing and cause SlotRecords::hasContentId() to return false. + * + * @param int $textId + * @return int The emulated content ID + */ + private function emulateContentId( $textId ) { + // Return a negative number to ensure the ID is distinct from any real content IDs + // that will be assigned in SCHEMA_COMPAT_WRITE_NEW mode and read in SCHEMA_COMPAT_READ_NEW + // mode. + return -$textId; + } + /** * Loads a Content object based on a slot row. * @@ -1477,6 +1598,10 @@ class RevisionStore $slots = []; foreach ( $res as $row ) { + // resolve role names and model names from in-memory cache, instead of joining. + $row->role_name = $this->slotRoleStore->getName( (int)$row->slot_role_id ); + $row->model_name = $this->contentModelStore->getName( (int)$row->content_model ); + $contentCallback = function ( SlotRecord $slot ) use ( $queryFlags, $row ) { return $this->loadSlotContent( $slot, null, null, null, $queryFlags ); }; @@ -1586,8 +1711,8 @@ class RevisionStore $row->ar_actor ?? null ); } catch ( InvalidArgumentException $ex ) { - wfWarn( __METHOD__ . ': ' . $ex->getMessage() ); - $user = new UserIdentityValue( 0, '', 0 ); + wfWarn( __METHOD__ . ': ' . $title->getPrefixedDBkey() . ': ' . $ex->getMessage() ); + $user = new UserIdentityValue( 0, 'Unknown user', 0 ); } $db = $this->getDBConnectionRefForQueryFlags( $queryFlags ); @@ -1634,8 +1759,8 @@ class RevisionStore $row->rev_actor ?? null ); } catch ( InvalidArgumentException $ex ) { - wfWarn( __METHOD__ . ': ' . $ex->getMessage() ); - $user = new UserIdentityValue( 0, '', 0 ); + wfWarn( __METHOD__ . ': ' . $title->getPrefixedDBkey() . ': ' . $ex->getMessage() ); + $user = new UserIdentityValue( 0, 'Unknown user', 0 ); } $db = $this->getDBConnectionRefForQueryFlags( $queryFlags ); @@ -2174,7 +2299,9 @@ class RevisionStore // NOTE: even when this class is set to not read from the old schema, callers // should still be able to join against the text table, as long as we are still // writing the old schema for compatibility. - wfDeprecated( __METHOD__ . ' with `text` option', '1.32' ); + // TODO: This should trigger a deprecation warning eventually (T200918), but not + // before all known usages are removed (see T198341 and T201164). + // wfDeprecated( __METHOD__ . ' with `text` option', '1.32' ); } $ret['tables'][] = 'text'; @@ -2196,6 +2323,9 @@ class RevisionStore * * @param array $options Any combination of the following strings * - 'content': Join with the content table, and select content meta-data fields + * - 'model': Join with the content_models table, and select the model_name field. + * Only applicable if 'content' is also set. + * - 'role': Join with the slot_roles table, and select the role_name field * * @return array With three keys: * - tables: (string[]) to include in the `$table` to `IDatabase->select()` @@ -2232,26 +2362,39 @@ class RevisionStore } } else { $ret['tables'][] = 'slots'; - $ret['tables'][] = 'slot_roles'; $ret['fields'] = array_merge( $ret['fields'], [ 'slot_revision_id', 'slot_content_id', 'slot_origin', - 'role_name' + 'slot_role_id', ] ); - $ret['joins']['slot_roles'] = [ 'INNER JOIN', [ 'slot_role_id = role_id' ] ]; + + if ( in_array( 'role', $options, true ) ) { + // Use left join to attach role name, so we still find the revision row even + // if the role name is missing. This triggers a more obvious failure mode. + $ret['tables'][] = 'slot_roles'; + $ret['joins']['slot_roles'] = [ 'LEFT JOIN', [ 'slot_role_id = role_id' ] ]; + $ret['fields'][] = 'role_name'; + } if ( in_array( 'content', $options, true ) ) { $ret['tables'][] = 'content'; - $ret['tables'][] = 'content_models'; $ret['fields'] = array_merge( $ret['fields'], [ 'content_size', 'content_sha1', 'content_address', - 'model_name' + 'content_model', ] ); $ret['joins']['content'] = [ 'INNER JOIN', [ 'slot_content_id = content_id' ] ]; - $ret['joins']['content_models'] = [ 'INNER JOIN', [ 'content_model = model_id' ] ]; + + if ( in_array( 'model', $options, true ) ) { + // Use left join to attach model name, so we still find the revision row even + // if the model name is missing. This triggers a more obvious failure mode. + $ret['tables'][] = 'content_models'; + $ret['joins']['content_models'] = [ 'LEFT JOIN', [ 'content_model = model_id' ] ]; + $ret['fields'][] = 'model_name'; + } + } } diff --git a/includes/Storage/RevisionStoreFactory.php b/includes/Storage/RevisionStoreFactory.php index 9419b40939..aaaafc15d1 100644 --- a/includes/Storage/RevisionStoreFactory.php +++ b/includes/Storage/RevisionStoreFactory.php @@ -67,9 +67,13 @@ class RevisionStoreFactory { */ private $contentHandlerUseDB; + /** @var NameTableStoreFactory */ + private $nameTables; + /** * @param ILBFactory $dbLoadBalancerFactory * @param BlobStoreFactory $blobStoreFactory + * @param NameTableStoreFactory $nameTables * @param WANObjectCache $cache * @param CommentStore $commentStore * @param ActorMigration $actorMigration @@ -81,6 +85,7 @@ class RevisionStoreFactory { public function __construct( ILBFactory $dbLoadBalancerFactory, BlobStoreFactory $blobStoreFactory, + NameTableStoreFactory $nameTables, WANObjectCache $cache, CommentStore $commentStore, ActorMigration $actorMigration, @@ -91,6 +96,7 @@ class RevisionStoreFactory { Assert::parameterType( 'integer', $migrationStage, '$migrationStage' ); $this->dbLoadBalancerFactory = $dbLoadBalancerFactory; $this->blobStoreFactory = $blobStoreFactory; + $this->nameTables = $nameTables; $this->cache = $cache; $this->commentStore = $commentStore; $this->actorMigration = $actorMigration; @@ -98,7 +104,6 @@ class RevisionStoreFactory { $this->loggerProvider = $loggerProvider; $this->contentHandlerUseDB = $contentHandlerUseDB; } - /** /** * @since 1.32 @@ -115,8 +120,8 @@ class RevisionStoreFactory { $this->blobStoreFactory->newSqlBlobStore( $wikiId ), $this->cache, // Pass local cache instance; Leave cache sharing to RevisionStore. $this->commentStore, - $this->getContentModelStore( $wikiId ), - $this->getSlotRoleStore( $wikiId ), + $this->nameTables->getContentModels( $wikiId ), + $this->nameTables->getSlotRoles( $wikiId ), $this->mcrMigrationStage, $this->actorMigration, $wikiId @@ -127,43 +132,4 @@ class RevisionStoreFactory { return $store; } - - /** - * @param string $wikiId - * @return NameTableStore - */ - private function getContentModelStore( $wikiId ) { - // XXX: a dedicated ContentModelStore subclass would avoid hard-coding - // knowledge about the schema here. - return new NameTableStore( - $this->dbLoadBalancerFactory->getMainLB( $wikiId ), - $this->cache, // Pass local cache instance; Leave cache sharing to NameTableStore. - $this->loggerProvider->getLogger( 'NameTableSqlStore' ), - 'content_models', - 'model_id', - 'model_name', - null, - $wikiId - ); - } - - /** - * @param string $wikiId - * @return NameTableStore - */ - private function getSlotRoleStore( $wikiId ) { - // XXX: a dedicated ContentModelStore subclass would avoid hard-coding - // knowledge about the schema here. - return new NameTableStore( - $this->dbLoadBalancerFactory->getMainLB( $wikiId ), - $this->cache, // Pass local cache instance; Leave cache sharing to NameTableStore. - $this->loggerProvider->getLogger( 'NameTableSqlStore' ), - 'slot_roles', - 'role_id', - 'role_name', - 'strtolower', - $wikiId - ); - } - } diff --git a/includes/Storage/RevisionStoreRecord.php b/includes/Storage/RevisionStoreRecord.php index d092f22ed9..6148c44366 100644 --- a/includes/Storage/RevisionStoreRecord.php +++ b/includes/Storage/RevisionStoreRecord.php @@ -207,4 +207,13 @@ class RevisionStoreRecord extends RevisionRecord { return parent::getTimestamp(); } + /** + * @see RevisionStore::isComplete + * + * @return bool always true. + */ + public function isReadyForInsertion() { + return true; + } + } diff --git a/includes/Storage/SlotRecord.php b/includes/Storage/SlotRecord.php index dff4b031d4..c7eb735db3 100644 --- a/includes/Storage/SlotRecord.php +++ b/includes/Storage/SlotRecord.php @@ -451,6 +451,16 @@ class SlotRecord { * content has been stored in the content table. While building a new revision, * SlotRecords will not have an ID associated. * + * Also, during schema migration, hasContentId() may return false when encountering an + * un-migrated database entry in SCHEMA_COMPAT_WRITE_BOTH mode. + * It will however always return true for saved revisions on SCHEMA_COMPAT_READ_NEW mode, + * or without SCHEMA_COMPAT_WRITE_NEW mode. In the latter case, an emulated content ID + * is used, derived from the revision's text ID. + * + * Note that hasContentId() returning false while hasRevision() returns true always + * indicates an unmigrated row in SCHEMA_COMPAT_WRITE_BOTH mode, as described above. + * For an unsaved slot, both these methods would return false. + * * @since 1.32 * * @return bool @@ -494,6 +504,9 @@ class SlotRecord { * This information should be irrelevant to application logic, it is here to allow * the construction of a full row for the revision table. * + * Note that this method may return an emulated value during schema migration in + * SCHEMA_COMPAT_WRITE_OLD mode. See RevisionStore::emulateContentId for more information. + * * @return int */ public function getContentId() { diff --git a/includes/TemplateParser.php b/includes/TemplateParser.php index 4210a965ba..75494b1ed8 100644 --- a/includes/TemplateParser.php +++ b/includes/TemplateParser.php @@ -41,9 +41,7 @@ class TemplateParser { /** * @var int Compilation flags passed to LightnCandy */ - // Do not add more flags here without discussion. - // If you do add more flags, be sure to update unit tests as well. - protected $compileFlags = LightnCandy::FLAG_ERROR_EXCEPTION; + protected $compileFlags; /** * @param string|null $templateDir @@ -52,6 +50,10 @@ class TemplateParser { public function __construct( $templateDir = null, $forceRecompile = false ) { $this->templateDir = $templateDir ?: __DIR__ . '/templates'; $this->forceRecompile = $forceRecompile; + + // Do not add more flags here without discussion. + // If you do add more flags, be sure to update unit tests as well. + $this->compileFlags = LightnCandy::FLAG_ERROR_EXCEPTION | LightnCandy::FLAG_MUSTACHELOOKUP; } /** diff --git a/includes/Title.php b/includes/Title.php index 12186394c5..59164e07e7 100644 --- a/includes/Title.php +++ b/includes/Title.php @@ -134,8 +134,15 @@ class Title implements LinkTarget { /** @var bool Boolean for initialisation on demand */ public $mRestrictionsLoaded = false; - /** @var string Text form including namespace/interwiki, initialised on demand */ - protected $mPrefixedText = null; + /** + * Text form including namespace/interwiki, initialised on demand + * + * Only public to share cache with TitleFormatter + * + * @private + * @var string + */ + public $prefixedText = null; /** @var mixed Cached value for getTitleProtection (create protection) */ public $mTitleProtection; @@ -1119,7 +1126,9 @@ class Title implements LinkTarget { */ public function isSpecial( $name ) { if ( $this->isSpecialPage() ) { - list( $thisName, /* $subpage */ ) = SpecialPageFactory::resolveAlias( $this->mDbkeyform ); + list( $thisName, /* $subpage */ ) = + MediaWikiServices::getInstance()->getSpecialPageFactory()-> + resolveAlias( $this->mDbkeyform ); if ( $name == $thisName ) { return true; } @@ -1135,9 +1144,10 @@ class Title implements LinkTarget { */ public function fixSpecialName() { if ( $this->isSpecialPage() ) { - list( $canonicalName, $par ) = SpecialPageFactory::resolveAlias( $this->mDbkeyform ); + $spFactory = MediaWikiServices::getInstance()->getSpecialPageFactory(); + list( $canonicalName, $par ) = $spFactory->resolveAlias( $this->mDbkeyform ); if ( $canonicalName ) { - $localName = SpecialPageFactory::getLocalNameFor( $canonicalName, $par ); + $localName = $spFactory->getLocalNameFor( $canonicalName, $par ); if ( $localName != $this->mDbkeyform ) { return self::makeTitle( NS_SPECIAL, $localName ); } @@ -1470,6 +1480,22 @@ class Title implements LinkTarget { ); } + /** + * Is this a message which can contain raw HTML? + * + * @return bool + * @since 1.32 + */ + public function isRawHtmlMessage() { + global $wgRawHtmlMessages; + + if ( !$this->inNamespace( NS_MEDIAWIKI ) ) { + return false; + } + $message = lcfirst( $this->getRootTitle()->getDBkey() ); + return in_array( $message, $wgRawHtmlMessages, true ); + } + /** * Is this a talk page of some sort? * @@ -1639,7 +1665,7 @@ class Title implements LinkTarget { if ( $nsText === false ) { // See T165149. Awkward, but better than erroneously linking to the main namespace. $nsText = MediaWikiServices::getInstance()->getContentLanguage()-> - getNsText( NS_SPECIAL ) . ":Badtitle/NS{$this->mNamespace}"; + getNsText( NS_SPECIAL ) . ":Badtitle/NS{$this->mNamespace}"; } $p .= $nsText . ':'; @@ -1666,12 +1692,12 @@ class Title implements LinkTarget { * @return string The prefixed title, with spaces */ public function getPrefixedText() { - if ( $this->mPrefixedText === null ) { + if ( $this->prefixedText === null ) { $s = $this->prefix( $this->mTextform ); $s = strtr( $s, '_', ' ' ); - $this->mPrefixedText = $s; + $this->prefixedText = $s; } - return $this->mPrefixedText; + return $this->prefixedText; } /** @@ -2382,6 +2408,13 @@ class Title implements LinkTarget { $error = [ 'sitejsonprotected', $action ]; } elseif ( $this->isSiteJsConfigPage() && !$user->isAllowed( 'editsitejs' ) ) { $error = [ 'sitejsprotected', $action ]; + } elseif ( $this->isRawHtmlMessage() ) { + // Raw HTML can be used to deploy CSS or JS so require rights for both. + if ( !$user->isAllowed( 'editsitejs' ) ) { + $error = [ 'sitejsprotected', $action ]; + } elseif ( !$user->isAllowed( 'editsitecss' ) ) { + $error = [ 'sitecssprotected', $action ]; + } } if ( $error ) { @@ -2413,25 +2446,34 @@ class Title implements LinkTarget { # Protect css/json/js subpages of user pages # XXX: this might be better using restrictions - if ( $action != 'patrol' ) { - if ( preg_match( '/^' . preg_quote( $user->getName(), '/' ) . '\//', $this->mTextform ) ) { - if ( - $this->isUserCssConfigPage() - && !$user->isAllowedAny( 'editmyusercss', 'editusercss' ) - ) { - $errors[] = [ 'mycustomcssprotected', $action ]; - } elseif ( - $this->isUserJsonConfigPage() - && !$user->isAllowedAny( 'editmyuserjson', 'edituserjson' ) - ) { - $errors[] = [ 'mycustomjsonprotected', $action ]; - } elseif ( - $this->isUserJsConfigPage() - && !$user->isAllowedAny( 'editmyuserjs', 'edituserjs' ) - ) { - $errors[] = [ 'mycustomjsprotected', $action ]; - } - } else { + if ( $action === 'patrol' ) { + return []; + } + + if ( preg_match( '/^' . preg_quote( $user->getName(), '/' ) . '\//', $this->mTextform ) ) { + // Users need editmyuser* to edit their own CSS/JSON/JS subpages. + if ( + $this->isUserCssConfigPage() + && !$user->isAllowedAny( 'editmyusercss', 'editusercss' ) + ) { + $errors[] = [ 'mycustomcssprotected', $action ]; + } elseif ( + $this->isUserJsonConfigPage() + && !$user->isAllowedAny( 'editmyuserjson', 'edituserjson' ) + ) { + $errors[] = [ 'mycustomjsonprotected', $action ]; + } elseif ( + $this->isUserJsConfigPage() + && !$user->isAllowedAny( 'editmyuserjs', 'edituserjs' ) + ) { + $errors[] = [ 'mycustomjsprotected', $action ]; + } + } else { + // Users need editmyuser* to edit their own CSS/JSON/JS subpages, except for + // deletion/suppression which cannot be used for attacks and we want to avoid the + // situation where an unprivileged user can post abusive content on their subpages + // and only very highly privileged users could remove it. + if ( !in_array( $action, [ 'delete', 'deleterevision', 'suppressrevision' ], true ) ) { if ( $this->isUserCssConfigPage() && !$user->isAllowed( 'editusercss' ) @@ -2705,7 +2747,9 @@ class Title implements LinkTarget { } elseif ( $this->isSpecialPage() ) { # If it's a special page, ditch the subpage bit and check again $name = $this->mDbkeyform; - list( $name, /* $subpage */ ) = SpecialPageFactory::resolveAlias( $name ); + list( $name, /* $subpage */ ) = + MediaWikiServices::getInstance()->getSpecialPageFactory()-> + resolveAlias( $name ); if ( $name ) { $pure = SpecialPage::getTitleFor( $name )->getPrefixedText(); if ( in_array( $pure, $wgWhitelistRead, true ) ) { @@ -3310,11 +3354,12 @@ class Title implements LinkTarget { $id = $this->getArticleID(); if ( $id ) { $cache = ObjectCache::getMainWANInstance(); + $fname = __METHOD__; $rows = $cache->getWithSetCallback( // Page protections always leave a new null revision $cache->makeKey( 'page-restrictions', $id, $this->getLatestRevID() ), $cache::TTL_DAY, - function ( $curValue, &$ttl, array &$setOpts ) { + function ( $curValue, &$ttl, array &$setOpts ) use ( $fname ) { $dbr = wfGetDB( DB_REPLICA ); $setOpts += Database::getCacheSetOptions( $dbr ); @@ -3324,7 +3369,7 @@ class Title implements LinkTarget { 'page_restrictions', [ 'pr_type', 'pr_expiry', 'pr_level', 'pr_cascade' ], [ 'pr_page' => $this->getArticleID() ], - __METHOD__ + $fname ) ); } @@ -4383,7 +4428,7 @@ class Title implements LinkTarget { $revQuery['joins'] ); if ( $row ) { - return new Revision( $row ); + return new Revision( $row, 0, $this ); } } return null; @@ -4678,7 +4723,8 @@ class Title implements LinkTarget { return (bool)wfFindFile( $this ); case NS_SPECIAL: // valid special page - return SpecialPageFactory::exists( $this->mDbkeyform ); + return MediaWikiServices::getInstance()->getSpecialPageFactory()-> + exists( $this->mDbkeyform ); case NS_MAIN: // selflink, possibly with fragment return $this->mDbkeyform == ''; diff --git a/includes/WebRequest.php b/includes/WebRequest.php index 327dd54c0d..ed10615b0c 100644 --- a/includes/WebRequest.php +++ b/includes/WebRequest.php @@ -856,7 +856,7 @@ class WebRequest { * @return string */ public function getFullRequestURL() { - return wfGetServerUrl( PROTO_CURRENT ) . $this->getRequestURL(); + return wfGetServerUrl( PROTO_CURRENT ) . $this->getRequestURL(); } /** diff --git a/includes/Xml.php b/includes/Xml.php index 9744aeefcc..d1a56bf78e 100644 --- a/includes/Xml.php +++ b/includes/Xml.php @@ -454,7 +454,7 @@ class Xml { /** * Convenience function to build an HTML submit button * When $wgUseMediaWikiUIEverywhere is true it will default to a progressive button - * @param string $value Label text for the button + * @param string $value Label text for the button (unescaped) * @param array $attribs Optional custom attributes * @return string HTML */ diff --git a/includes/actions/InfoAction.php b/includes/actions/InfoAction.php index dc661aa63c..11b8badabf 100644 --- a/includes/actions/InfoAction.php +++ b/includes/actions/InfoAction.php @@ -233,13 +233,14 @@ class InfoAction extends FormlessAction { ]; // Is it a redirect? If so, where to? - if ( $title->isRedirect() ) { + $redirectTarget = $this->page->getRedirectTarget(); + if ( $redirectTarget !== null ) { $pageInfo['header-basic'][] = [ $this->msg( 'pageinfo-redirectsto' ), - $linkRenderer->makeLink( $this->page->getRedirectTarget() ) . + $linkRenderer->makeLink( $redirectTarget ) . $this->msg( 'word-separator' )->escaped() . $this->msg( 'parentheses' )->rawParams( $linkRenderer->makeLink( - $this->page->getRedirectTarget(), + $redirectTarget, $this->msg( 'pageinfo-redirectsto-info' )->text(), [], [ 'action' => 'info' ] diff --git a/includes/actions/McrUndoAction.php b/includes/actions/McrUndoAction.php new file mode 100644 index 0000000000..90d1f686cd --- /dev/null +++ b/includes/actions/McrUndoAction.php @@ -0,0 +1,376 @@ +persist(); + + // Some stuff copied from EditAction + $this->useTransactionalTimeLimit(); + + $out = $this->getOutput(); + $out->setRobotPolicy( 'noindex,nofollow' ); + if ( $this->getContext()->getConfig()->get( 'UseMediaWikiUIEverywhere' ) ) { + $out->addModuleStyles( [ + 'mediawiki.ui.input', + 'mediawiki.ui.checkbox', + ] ); + } + + // IP warning headers copied from EditPage + // (should more be copied?) + if ( wfReadOnly() ) { + $out->wrapWikiMsg( + "
\n$1\n
", + [ 'readonlywarning', wfReadOnlyReason() ] + ); + } elseif ( $this->context->getUser()->isAnon() ) { + if ( !$this->getRequest()->getCheck( 'wpPreview' ) ) { + $out->wrapWikiMsg( + "
\n$1\n
", + [ 'anoneditwarning', + // Log-in link + SpecialPage::getTitleFor( 'Userlogin' )->getFullURL( [ + 'returnto' => $this->getTitle()->getPrefixedDBkey() + ] ), + // Sign-up link + SpecialPage::getTitleFor( 'CreateAccount' )->getFullURL( [ + 'returnto' => $this->getTitle()->getPrefixedDBkey() + ] ) + ] + ); + } else { + $out->wrapWikiMsg( "
\n$1
", + 'anonpreviewwarning' + ); + } + } + + parent::show(); + } + + protected function checkCanExecute( User $user ) { + parent::checkCanExecute( $user ); + + $this->undoafter = $this->getRequest()->getInt( 'undoafter' ); + $this->undo = $this->getRequest()->getInt( 'undo' ); + + if ( $this->undo == 0 || $this->undoafter == 0 ) { + throw new ErrorPageError( 'mcrundofailed', 'mcrundo-missingparam' ); + } + + $curRev = $this->page->getRevision(); + if ( !$curRev ) { + throw new ErrorPageError( 'mcrundofailed', 'nopagetext' ); + } + $this->curRev = $curRev->getRevisionRecord(); + $this->cur = $this->getRequest()->getInt( 'cur', $this->curRev->getId() ); + + $revisionLookup = MediaWikiServices::getInstance()->getRevisionLookup(); + + $undoRev = $revisionLookup->getRevisionById( $this->undo ); + $oldRev = $revisionLookup->getRevisionById( $this->undoafter ); + + if ( $undoRev === null || $oldRev === null || + $undoRev->isDeleted( RevisionRecord::DELETED_TEXT ) || + $oldRev->isDeleted( RevisionRecord::DELETED_TEXT ) + ) { + throw new ErrorPageError( 'mcrundofailed', 'undo-norev' ); + } + + return true; + } + + /** + * @return MutableRevisionRecord + */ + private function getNewRevision() { + $revisionLookup = MediaWikiServices::getInstance()->getRevisionLookup(); + + $undoRev = $revisionLookup->getRevisionById( $this->undo ); + $oldRev = $revisionLookup->getRevisionById( $this->undoafter ); + $curRev = $this->curRev; + + $isLatest = $curRev->getId() === $undoRev->getId(); + + if ( $undoRev === null || $oldRev === null || + $undoRev->isDeleted( RevisionRecord::DELETED_TEXT ) || + $oldRev->isDeleted( RevisionRecord::DELETED_TEXT ) + ) { + throw new ErrorPageError( 'mcrundofailed', 'undo-norev' ); + } + + if ( $isLatest ) { + // Short cut! Undoing the current revision means we just restore the old. + return MutableRevisionRecord::newFromParentRevision( $oldRev ); + } + + $newRev = MutableRevisionRecord::newFromParentRevision( $curRev ); + + // Figure out the roles that need merging by first collecting all roles + // and then removing the ones that don't. + $rolesToMerge = array_unique( array_merge( + $oldRev->getSlotRoles(), + $undoRev->getSlotRoles(), + $curRev->getSlotRoles() + ) ); + + // Any roles with the same content in $oldRev and $undoRev can be + // inherited because undo won't change them. + $rolesToMerge = array_intersect( + $rolesToMerge, $oldRev->getSlots()->getRolesWithDifferentContent( $undoRev->getSlots() ) + ); + if ( !$rolesToMerge ) { + throw new ErrorPageError( 'mcrundofailed', 'undo-nochange' ); + } + + // Any roles with the same content in $oldRev and $curRev were already reverted + // and so can be inherited. + $rolesToMerge = array_intersect( + $rolesToMerge, $oldRev->getSlots()->getRolesWithDifferentContent( $curRev->getSlots() ) + ); + if ( !$rolesToMerge ) { + throw new ErrorPageError( 'mcrundofailed', 'undo-nochange' ); + } + + // Any roles with the same content in $undoRev and $curRev weren't + // changed since and so can be reverted to $oldRev. + $diffRoles = array_intersect( + $rolesToMerge, $undoRev->getSlots()->getRolesWithDifferentContent( $curRev->getSlots() ) + ); + foreach ( array_diff( $rolesToMerge, $diffRoles ) as $role ) { + if ( $oldRev->hasSlot( $role ) ) { + $newRev->inheritSlot( $oldRev->getSlot( $role, RevisionRecord::RAW ) ); + } else { + $newRev->removeSlot( $role ); + } + } + $rolesToMerge = $diffRoles; + + // Any slot additions or removals not handled by the above checks can't be undone. + // There will be only one of the three revisions missing the slot: + // - !old means it was added in the undone revisions and modified after. + // Should it be removed entirely for the undo, or should the modified version be kept? + // - !undo means it was removed in the undone revisions and then readded with different content. + // Which content is should be kept, the old or the new? + // - !cur means it was changed in the undone revisions and then deleted after. + // Did someone delete vandalized content instead of undoing (meaning we should ideally restore + // it), or should it stay gone? + foreach ( $rolesToMerge as $role ) { + if ( !$oldRev->hasSlot( $role ) || !$undoRev->hasSlot( $role ) || !$curRev->hasSlot( $role ) ) { + throw new ErrorPageError( 'mcrundofailed', 'undo-failure' ); + } + } + + // Try to merge anything that's left. + foreach ( $rolesToMerge as $role ) { + $oldContent = $oldRev->getSlot( $role, RevisionRecord::RAW )->getContent(); + $undoContent = $undoRev->getSlot( $role, RevisionRecord::RAW )->getContent(); + $curContent = $curRev->getSlot( $role, RevisionRecord::RAW )->getContent(); + $newContent = $undoContent->getContentHandler() + ->getUndoContent( $curContent, $undoContent, $oldContent, $isLatest ); + if ( !$newContent ) { + throw new ErrorPageError( 'mcrundofailed', 'undo-failure' ); + } + $newRev->setSlot( SlotRecord::newUnsaved( $role, $newContent ) ); + } + + return $newRev; + } + + private function generateDiff() { + $newRev = $this->getNewRevision(); + if ( $newRev->hasSameContent( $this->curRev ) ) { + throw new ErrorPageError( 'mcrundofailed', 'undo-nochange' ); + } + + $diffEngine = new DifferenceEngine( $this->context ); + $diffEngine->setRevisions( $this->curRev, $newRev ); + + $oldtitle = $this->context->msg( 'currentrev' )->parse(); + $newtitle = $this->context->msg( 'yourtext' )->parse(); + + if ( $this->getRequest()->getCheck( 'wpPreview' ) ) { + $diffEngine->renderNewRevision(); + return ''; + } else { + $diffText = $diffEngine->getDiff( $oldtitle, $newtitle ); + $diffEngine->showDiffStyle(); + return '
' . $diffText . '
'; + } + } + + public function onSubmit( $data ) { + global $wgUseRCPatrol; + + if ( !$this->getRequest()->getCheck( 'wpSave' ) ) { + // Diff or preview + return false; + } + + $updater = $this->page->getPage()->newPageUpdater( $this->context->getUser() ); + $curRev = $updater->grabParentRevision(); + if ( !$curRev ) { + throw new ErrorPageError( 'mcrundofailed', 'nopagetext' ); + } + + if ( $this->cur !== $curRev->getId() ) { + return Status::newFatal( 'mcrundo-changed' ); + } + + $newRev = $this->getNewRevision(); + if ( !$newRev->hasSameContent( $curRev ) ) { + // Copy new slots into the PageUpdater, and remove any removed slots. + // TODO: This interface is awful, there should be a way to just pass $newRev. + // TODO: MCR: test this once we can store multiple slots + foreach ( $newRev->getSlots()->getSlots() as $slot ) { + $updater->setSlot( $slot ); + } + foreach ( $curRev->getSlotRoles() as $role ) { + if ( !$newRev->hasSlot( $role ) ) { + $updater->removeSlot( $role ); + } + } + + $updater->setOriginalRevisionId( false ); + $updater->setUndidRevisionId( $this->undo ); + + // TODO: Ugh. + if ( $wgUseRCPatrol && $this->getTitle()->userCan( 'autopatrol', $this->getUser() ) ) { + $updater->setRcPatrolStatus( RecentChange::PRC_AUTOPATROLLED ); + } + + $updater->saveRevision( + CommentStoreComment::newUnsavedComment( trim( $this->getRequest()->getVal( 'wpSummary' ) ) ), + EDIT_AUTOSUMMARY | EDIT_UPDATE + ); + + return $updater->getStatus(); + } + + return Status::newGood(); + } + + protected function usesOOUI() { + return true; + } + + protected function getFormFields() { + $request = $this->getRequest(); + $config = $this->context->getConfig(); + $oldCommentSchema = $config->get( 'CommentTableSchemaMigrationStage' ) === MIGRATION_OLD; + $ret = [ + 'diff' => [ + 'type' => 'info', + 'vertical-label' => true, + 'raw' => true, + 'default' => function () { + return $this->generateDiff(); + } + ], + 'summary' => [ + 'type' => 'text', + 'id' => 'wpSummary', + 'name' => 'wpSummary', + 'cssclass' => 'mw-summary', + 'label-message' => 'summary', + 'maxlength' => $oldCommentSchema ? 200 : CommentStore::COMMENT_CHARACTER_LIMIT, + 'value' => $request->getVal( 'wpSummary', '' ), + 'size' => 60, + 'spellcheck' => 'true', + ], + 'summarypreview' => [ + 'type' => 'info', + 'label-message' => 'summary-preview', + 'raw' => true, + ], + ]; + + if ( $request->getCheck( 'wpSummary' ) ) { + $ret['summarypreview']['default'] = Xml::tags( 'div', [ 'class' => 'mw-summary-preview' ], + Linker::commentBlock( trim( $request->getVal( 'wpSummary' ) ), $this->getTitle(), false ) + ); + } else { + unset( $ret['summarypreview'] ); + } + + return $ret; + } + + protected function alterForm( HTMLForm $form ) { + $form->setWrapperLegendMsg( 'confirm-mcrundo-title' ); + + $labelAsPublish = $this->context->getConfig()->get( 'EditSubmitButtonLabelPublish' ); + + $form->setSubmitName( 'wpSave' ); + $form->setSubmitTooltip( $labelAsPublish ? 'publish' : 'save' ); + $form->setSubmitTextMsg( $labelAsPublish ? 'publishchanges' : 'savechanges' ); + $form->showCancel( true ); + $form->setCancelTarget( $this->getTitle() ); + $form->addButton( [ + 'name' => 'wpPreview', + 'value' => '1', + 'label-message' => 'showpreview', + 'attribs' => Linker::tooltipAndAccesskeyAttribs( 'preview' ), + ] ); + $form->addButton( [ + 'name' => 'wpDiff', + 'value' => '1', + 'label-message' => 'showdiff', + 'attribs' => Linker::tooltipAndAccesskeyAttribs( 'diff' ), + ] ); + + $form->addHiddenField( 'undo', $this->undo ); + $form->addHiddenField( 'undoafter', $this->undoafter ); + $form->addHiddenField( 'cur', $this->curRev->getId() ); + } + + public function onSuccess() { + $this->getOutput()->redirect( $this->getTitle()->getFullURL() ); + } + + protected function preText() { + return '
'; + } +} diff --git a/includes/actions/RawAction.php b/includes/actions/RawAction.php index 50eb28a3cf..817c9fd126 100644 --- a/includes/actions/RawAction.php +++ b/includes/actions/RawAction.php @@ -47,6 +47,9 @@ class RawAction extends FormlessAction { return false; } + /** + * @suppress SecurityCheck-XSS Non html mime type + */ function onView() { $this->getOutput()->disable(); $request = $this->getRequest(); diff --git a/includes/actions/RollbackAction.php b/includes/actions/RollbackAction.php index 9d336e4613..dc7b00ea41 100644 --- a/includes/actions/RollbackAction.php +++ b/includes/actions/RollbackAction.php @@ -151,7 +151,6 @@ class RollbackAction extends FormlessAction { ); $de->showDiff( '', '' ); } - return; } protected function getDescription() { diff --git a/includes/actions/SpecialPageAction.php b/includes/actions/SpecialPageAction.php index e59b6d61ad..8a231cbe5f 100644 --- a/includes/actions/SpecialPageAction.php +++ b/includes/actions/SpecialPageAction.php @@ -18,6 +18,8 @@ * @ingroup Actions */ +use MediaWiki\MediaWikiServices; + /** * An action that just passes the request to the relevant special page * @@ -92,6 +94,7 @@ class SpecialPageAction extends FormlessAction { } // map actions to (whitelisted) special pages - return SpecialPageFactory::getPage( self::$actionToSpecialPageMapping[$action] ); + return MediaWikiServices::getInstance()->getSpecialPageFactory()-> + getPage( self::$actionToSpecialPageMapping[$action] ); } } diff --git a/includes/api/ApiComparePages.php b/includes/api/ApiComparePages.php index 93c35d3d23..02cadbd400 100644 --- a/includes/api/ApiComparePages.php +++ b/includes/api/ApiComparePages.php @@ -19,141 +19,136 @@ * @file */ +use MediaWiki\MediaWikiServices; +use MediaWiki\Storage\MutableRevisionRecord; +use MediaWiki\Storage\RevisionRecord; +use MediaWiki\Storage\RevisionStore; + class ApiComparePages extends ApiBase { - private $guessed = false, $guessedTitle, $guessedModel, $props; + /** @var RevisionStore */ + private $revisionStore; + + private $guessedTitle = false, $props; + + public function __construct( ApiMain $mainModule, $moduleName, $modulePrefix = '' ) { + parent::__construct( $mainModule, $moduleName, $modulePrefix ); + $this->revisionStore = MediaWikiServices::getInstance()->getRevisionStore(); + } public function execute() { $params = $this->extractRequestParams(); // Parameter validation - $this->requireAtLeastOneParameter( $params, 'fromtitle', 'fromid', 'fromrev', 'fromtext' ); - $this->requireAtLeastOneParameter( $params, 'totitle', 'toid', 'torev', 'totext', 'torelative' ); + $this->requireAtLeastOneParameter( + $params, 'fromtitle', 'fromid', 'fromrev', 'fromtext', 'fromslots' + ); + $this->requireAtLeastOneParameter( + $params, 'totitle', 'toid', 'torev', 'totext', 'torelative', 'toslots' + ); $this->props = array_flip( $params['prop'] ); // Cache responses publicly by default. This may be overridden later. $this->getMain()->setCacheMode( 'public' ); - // Get the 'from' Revision and Content - list( $fromRev, $fromContent, $relRev ) = $this->getDiffContent( 'from', $params ); + // Get the 'from' RevisionRecord + list( $fromRev, $fromRelRev, $fromValsRev ) = $this->getDiffRevision( 'from', $params ); - // Get the 'to' Revision and Content + // Get the 'to' RevisionRecord if ( $params['torelative'] !== null ) { - if ( !$relRev ) { + if ( !$fromRelRev ) { $this->dieWithError( 'apierror-compare-relative-to-nothing' ); } switch ( $params['torelative'] ) { case 'prev': // Swap 'from' and 'to' - $toRev = $fromRev; - $toContent = $fromContent; - $fromRev = $relRev->getPrevious(); - $fromContent = $fromRev - ? $fromRev->getContent( Revision::FOR_THIS_USER, $this->getUser() ) - : $toContent->getContentHandler()->makeEmptyContent(); - if ( !$fromContent ) { - $this->dieWithError( - [ 'apierror-missingcontent-revid', $fromRev->getId() ], 'missingcontent' - ); - } + list( $toRev, $toRelRev2, $toValsRev ) = [ $fromRev, $fromRelRev, $fromValsRev ]; + $fromRev = $this->revisionStore->getPreviousRevision( $fromRelRev ); + $fromRelRev = $fromRev; + $fromValsRev = $fromRev; break; case 'next': - $toRev = $relRev->getNext(); - $toContent = $toRev - ? $toRev->getContent( Revision::FOR_THIS_USER, $this->getUser() ) - : $fromContent; - if ( !$toContent ) { - $this->dieWithError( [ 'apierror-missingcontent-revid', $toRev->getId() ], 'missingcontent' ); - } + $toRev = $this->revisionStore->getNextRevision( $fromRelRev ); + $toRelRev = $toRev; + $toValsRev = $toRev; break; case 'cur': - $title = $relRev->getTitle(); - $id = $title->getLatestRevID(); - $toRev = $id ? Revision::newFromId( $id ) : null; + $title = $fromRelRev->getPageAsLinkTarget(); + $toRev = $this->revisionStore->getRevisionByTitle( $title ); if ( !$toRev ) { + $title = Title::newFromLinkTarget( $title ); $this->dieWithError( [ 'apierror-missingrev-title', wfEscapeWikiText( $title->getPrefixedText() ) ], 'nosuchrevid' ); } - $toContent = $toRev->getContent( Revision::FOR_THIS_USER, $this->getUser() ); - if ( !$toContent ) { - $this->dieWithError( [ 'apierror-missingcontent-revid', $toRev->getId() ], 'missingcontent' ); - } + $toRelRev = $toRev; + $toValsRev = $toRev; break; } - $relRev2 = null; } else { - list( $toRev, $toContent, $relRev2 ) = $this->getDiffContent( 'to', $params ); + list( $toRev, $toRelRev, $toValsRev ) = $this->getDiffRevision( 'to', $params ); } - // Should never happen, but just in case... - if ( !$fromContent || !$toContent ) { + // Handle missing from or to revisions + if ( !$fromRev || !$toRev ) { $this->dieWithError( 'apierror-baddiff' ); } - // Extract sections, if told to - if ( isset( $params['fromsection'] ) ) { - $fromContent = $fromContent->getSection( $params['fromsection'] ); - if ( !$fromContent ) { - $this->dieWithError( - [ 'apierror-compare-nosuchfromsection', wfEscapeWikiText( $params['fromsection'] ) ], - 'nosuchfromsection' - ); - } + // Handle revdel + if ( !$fromRev->audienceCan( + RevisionRecord::DELETED_TEXT, RevisionRecord::FOR_THIS_USER, $this->getUser() + ) ) { + $this->dieWithError( [ 'apierror-missingcontent-revid', $fromRev->getId() ], 'missingcontent' ); } - if ( isset( $params['tosection'] ) ) { - $toContent = $toContent->getSection( $params['tosection'] ); - if ( !$toContent ) { - $this->dieWithError( - [ 'apierror-compare-nosuchtosection', wfEscapeWikiText( $params['tosection'] ) ], - 'nosuchtosection' - ); - } + if ( !$toRev->audienceCan( + RevisionRecord::DELETED_TEXT, RevisionRecord::FOR_THIS_USER, $this->getUser() + ) ) { + $this->dieWithError( [ 'apierror-missingcontent-revid', $toRev->getId() ], 'missingcontent' ); } // Get the diff $context = new DerivativeContext( $this->getContext() ); - if ( $relRev && $relRev->getTitle() ) { - $context->setTitle( $relRev->getTitle() ); - } elseif ( $relRev2 && $relRev2->getTitle() ) { - $context->setTitle( $relRev2->getTitle() ); + if ( $fromRelRev && $fromRelRev->getPageAsLinkTarget() ) { + $context->setTitle( Title::newFromLinkTarget( $fromRelRev->getPageAsLinkTarget() ) ); + } elseif ( $toRelRev && $toRelRev->getPageAsLinkTarget() ) { + $context->setTitle( Title::newFromLinkTarget( $toRelRev->getPageAsLinkTarget() ) ); } else { - $this->guessTitleAndModel(); - if ( $this->guessedTitle ) { - $context->setTitle( $this->guessedTitle ); + $guessedTitle = $this->guessTitle(); + if ( $guessedTitle ) { + $context->setTitle( $guessedTitle ); } } - $de = $fromContent->getContentHandler()->createDifferenceEngine( - $context, - $fromRev ? $fromRev->getId() : 0, - $toRev ? $toRev->getId() : 0, - /* $rcid = */ null, - /* $refreshCache = */ false, - /* $unhide = */ true - ); - $de->setContent( $fromContent, $toContent ); - $difftext = $de->getDiffBody(); - if ( $difftext === false ) { - $this->dieWithError( 'apierror-baddiff' ); + $de = new DifferenceEngine( $context ); + $de->setRevisions( $fromRev, $toRev ); + if ( $params['slots'] === null ) { + $difftext = $de->getDiffBody(); + if ( $difftext === false ) { + $this->dieWithError( 'apierror-baddiff' ); + } + } else { + $difftext = []; + foreach ( $params['slots'] as $role ) { + $difftext[$role] = $de->getDiffBodyForRole( $role ); + } } // Fill in the response $vals = []; - $this->setVals( $vals, 'from', $fromRev ); - $this->setVals( $vals, 'to', $toRev ); + $this->setVals( $vals, 'from', $fromValsRev ); + $this->setVals( $vals, 'to', $toValsRev ); if ( isset( $this->props['rel'] ) ) { - if ( $fromRev ) { - $rev = $fromRev->getPrevious(); + if ( !$fromRev instanceof MutableRevisionRecord ) { + $rev = $this->revisionStore->getPreviousRevision( $fromRev ); if ( $rev ) { $vals['prev'] = $rev->getId(); } } - if ( $toRev ) { - $rev = $toRev->getNext(); + if ( !$toRev instanceof MutableRevisionRecord ) { + $rev = $this->revisionStore->getNextRevision( $toRev ); if ( $rev ) { $vals['next'] = $rev->getId(); } @@ -161,10 +156,18 @@ class ApiComparePages extends ApiBase { } if ( isset( $this->props['diffsize'] ) ) { - $vals['diffsize'] = strlen( $difftext ); + $vals['diffsize'] = 0; + foreach ( (array)$difftext as $text ) { + $vals['diffsize'] += strlen( $text ); + } } if ( isset( $this->props['diff'] ) ) { - ApiResult::setContentValue( $vals, 'body', $difftext ); + if ( is_array( $difftext ) ) { + ApiResult::setArrayType( $difftext, 'kvp', 'diff' ); + $vals['bodies'] = $difftext; + } else { + ApiResult::setContentValue( $vals, 'body', $difftext ); + } } // Diffs can be really big and there's little point in having @@ -174,49 +177,55 @@ class ApiComparePages extends ApiBase { } /** - * Guess an appropriate default Title and content model for this request + * Load a revision by ID * - * Fills in $this->guessedTitle based on the first of 'fromrev', - * 'fromtitle', 'fromid', 'torev', 'totitle', and 'toid' that's present and - * valid. + * Falls back to checking the archive table if appropriate. * - * Fills in $this->guessedModel based on the Revision or Title used to - * determine $this->guessedTitle, or the 'fromcontentmodel' or - * 'tocontentmodel' parameters if no title was guessed. + * @param int $id + * @return RevisionRecord|null */ - private function guessTitleAndModel() { - if ( $this->guessed ) { - return; + private function getRevisionById( $id ) { + $rev = $this->revisionStore->getRevisionById( $id ); + if ( !$rev && $this->getUser()->isAllowedAny( 'deletedtext', 'undelete' ) ) { + // Try the 'archive' table + $arQuery = $this->revisionStore->getArchiveQueryInfo(); + $row = $this->getDB()->selectRow( + $arQuery['tables'], + array_merge( + $arQuery['fields'], + [ 'ar_namespace', 'ar_title' ] + ), + [ 'ar_rev_id' => $id ], + __METHOD__, + [], + $arQuery['joins'] + ); + if ( $row ) { + $rev = $this->revisionStore->newRevisionFromArchiveRow( $row ); + $rev->isArchive = true; + } } + return $rev; + } - $this->guessed = true; + /** + * Guess an appropriate default Title for this request + * + * @return Title|null + */ + private function guessTitle() { + if ( $this->guessedTitle !== false ) { + return $this->guessedTitle; + } + + $this->guessedTitle = null; $params = $this->extractRequestParams(); foreach ( [ 'from', 'to' ] as $prefix ) { if ( $params["{$prefix}rev"] !== null ) { - $revId = $params["{$prefix}rev"]; - $rev = Revision::newFromId( $revId ); - if ( !$rev ) { - // Titles of deleted revisions aren't secret, per T51088 - $arQuery = Revision::getArchiveQueryInfo(); - $row = $this->getDB()->selectRow( - $arQuery['tables'], - array_merge( - $arQuery['fields'], - [ 'ar_namespace', 'ar_title' ] - ), - [ 'ar_rev_id' => $revId ], - __METHOD__, - [], - $arQuery['joins'] - ); - if ( $row ) { - $rev = Revision::newFromArchiveRow( $row ); - } - } + $rev = $this->getRevisionById( $params["{$prefix}rev"] ); if ( $rev ) { - $this->guessedTitle = $rev->getTitle(); - $this->guessedModel = $rev->getContentModel(); + $this->guessedTitle = Title::newFromLinkTarget( $rev->getPageAsLinkTarget() ); break; } } @@ -238,38 +247,84 @@ class ApiComparePages extends ApiBase { } } - if ( !$this->guessedModel ) { - if ( $this->guessedTitle ) { - $this->guessedModel = $this->guessedTitle->getContentModel(); - } elseif ( $params['fromcontentmodel'] !== null ) { - $this->guessedModel = $params['fromcontentmodel']; - } elseif ( $params['tocontentmodel'] !== null ) { - $this->guessedModel = $params['tocontentmodel']; + return $this->guessedTitle; + } + + /** + * Guess an appropriate default content model for this request + * @param string $role Slot for which to guess the model + * @return string|null Guessed content model + */ + private function guessModel( $role ) { + $params = $this->extractRequestParams(); + + $title = null; + foreach ( [ 'from', 'to' ] as $prefix ) { + if ( $params["{$prefix}rev"] !== null ) { + $rev = $this->getRevisionById( $params["{$prefix}rev"] ); + if ( $rev ) { + if ( $rev->hasSlot( $role ) ) { + return $rev->getSlot( $role, RevisionRecord::RAW )->getModel(); + } + } } } + + $guessedTitle = $this->guessTitle(); + if ( $guessedTitle && $role === 'main' ) { + // @todo: Use SlotRoleRegistry and do this for all slots + return $guessedTitle->getContentModel(); + } + + if ( isset( $params["fromcontentmodel-$role"] ) ) { + return $params["fromcontentmodel-$role"]; + } + if ( isset( $params["tocontentmodel-$role"] ) ) { + return $params["tocontentmodel-$role"]; + } + + if ( $role === 'main' ) { + if ( isset( $params['fromcontentmodel'] ) ) { + return $params['fromcontentmodel']; + } + if ( isset( $params['tocontentmodel'] ) ) { + return $params['tocontentmodel']; + } + } + + return null; } /** - * Get the Revision and Content for one side of the diff + * Get the RevisionRecord for one side of the diff * - * This uses the appropriate set of 'rev', 'id', 'title', 'text', 'pst', - * 'contentmodel', and 'contentformat' parameters to determine what content + * This uses the appropriate set of parameters to determine what content * should be diffed. * * Returns three values: - * - The revision used to retrieve the content, if any - * - The content to be diffed - * - The revision specified, if any, even if not used to retrieve the - * Content + * - A RevisionRecord holding the content + * - The revision specified, if any, even if content was supplied + * - The revision to pass to setVals(), if any * * @param string $prefix 'from' or 'to' * @param array $params - * @return array [ Revision|null, Content, Revision|null ] + * @return array [ RevisionRecord|null, RevisionRecord|null, RevisionRecord|null ] */ - private function getDiffContent( $prefix, array $params ) { + private function getDiffRevision( $prefix, array $params ) { + // Back compat params + $this->requireMaxOneParameter( $params, "{$prefix}text", "{$prefix}slots" ); + $this->requireMaxOneParameter( $params, "{$prefix}section", "{$prefix}slots" ); + if ( $params["{$prefix}text"] !== null ) { + $params["{$prefix}slots"] = [ 'main' ]; + $params["{$prefix}text-main"] = $params["{$prefix}text"]; + $params["{$prefix}section-main"] = null; + $params["{$prefix}contentmodel-main"] = $params["{$prefix}contentmodel"]; + $params["{$prefix}contentformat-main"] = $params["{$prefix}contentformat"]; + } + $title = null; $rev = null; - $suppliedContent = $params["{$prefix}text"] !== null; + $suppliedContent = $params["{$prefix}slots"] !== null; // Get the revision and title, if applicable $revId = null; @@ -308,94 +363,163 @@ class ApiComparePages extends ApiBase { } } if ( $revId !== null ) { - $rev = Revision::newFromId( $revId ); - if ( !$rev && $this->getUser()->isAllowedAny( 'deletedtext', 'undelete' ) ) { - // Try the 'archive' table - $arQuery = Revision::getArchiveQueryInfo(); - $row = $this->getDB()->selectRow( - $arQuery['tables'], - array_merge( - $arQuery['fields'], - [ 'ar_namespace', 'ar_title' ] - ), - [ 'ar_rev_id' => $revId ], - __METHOD__, - [], - $arQuery['joins'] - ); - if ( $row ) { - $rev = Revision::newFromArchiveRow( $row ); - $rev->isArchive = true; - } - } + $rev = $this->getRevisionById( $revId ); if ( !$rev ) { $this->dieWithError( [ 'apierror-nosuchrevid', $revId ] ); } - $title = $rev->getTitle(); + $title = Title::newFromLinkTarget( $rev->getPageAsLinkTarget() ); // If we don't have supplied content, return here. Otherwise, // continue on below with the supplied content. if ( !$suppliedContent ) { - $content = $rev->getContent( Revision::FOR_THIS_USER, $this->getUser() ); - if ( !$content ) { - $this->dieWithError( [ 'apierror-missingcontent-revid', $revId ], 'missingcontent' ); + $newRev = $rev; + + // Deprecated 'fromsection'/'tosection' + if ( isset( $params["{$prefix}section"] ) ) { + $section = $params["{$prefix}section"]; + $newRev = MutableRevisionRecord::newFromParentRevision( $rev ); + $content = $rev->getContent( 'main', RevisionRecord::FOR_THIS_USER, $this->getUser() ); + if ( !$content ) { + $this->dieWithError( + [ 'apierror-missingcontent-revid-role', $rev->getId(), 'main' ], 'missingcontent' + ); + } + $content = $content ? $content->getSection( $section ) : null; + if ( !$content ) { + $this->dieWithError( + [ "apierror-compare-nosuch{$prefix}section", wfEscapeWikiText( $section ) ], + "nosuch{$prefix}section" + ); + } + $newRev->setContent( 'main', $content ); } - return [ $rev, $content, $rev ]; + + return [ $newRev, $rev, $rev ]; } } // Override $content based on supplied text - $model = $params["{$prefix}contentmodel"]; - $format = $params["{$prefix}contentformat"]; - - if ( !$model && $rev ) { - $model = $rev->getContentModel(); - } - if ( !$model && $title ) { - $model = $title->getContentModel(); - } - if ( !$model ) { - $this->guessTitleAndModel(); - $model = $this->guessedModel; - } - if ( !$model ) { - $model = CONTENT_MODEL_WIKITEXT; - $this->addWarning( [ 'apiwarn-compare-nocontentmodel', $model ] ); - } - if ( !$title ) { - $this->guessTitleAndModel(); - $title = $this->guessedTitle; + $title = $this->guessTitle(); } - - try { - $content = ContentHandler::makeContent( $params["{$prefix}text"], $title, $model, $format ); - } catch ( MWContentSerializationException $ex ) { - $this->dieWithException( $ex, [ - 'wrap' => ApiMessage::create( 'apierror-contentserializationexception', 'parseerror' ) + if ( $rev ) { + $newRev = MutableRevisionRecord::newFromParentRevision( $rev ); + } else { + $newRev = $this->revisionStore->newMutableRevisionFromArray( [ + 'title' => $title ?: Title::makeTitle( NS_SPECIAL, 'Badtitle/' . __METHOD__ ) ] ); } + foreach ( $params["{$prefix}slots"] as $role ) { + $text = $params["{$prefix}text-{$role}"]; + if ( $text === null ) { + // The 'main' role can't be deleted + if ( $role === 'main' ) { + $this->dieWithError( [ 'apierror-compare-maintextrequired', $prefix ] ); + } - if ( $params["{$prefix}pst"] ) { - if ( !$title ) { - $this->dieWithError( 'apierror-compare-no-title' ); + // These parameters make no sense without text. Reject them to avoid + // confusion. + foreach ( [ 'section', 'contentmodel', 'contentformat' ] as $param ) { + if ( isset( $params["{$prefix}{$param}-{$role}"] ) ) { + $this->dieWithError( [ + 'apierror-compare-notext', + wfEscapeWikiText( "{$prefix}{$param}-{$role}" ), + wfEscapeWikiText( "{$prefix}text-{$role}" ), + ] ); + } + } + + $newRev->removeSlot( $role ); + continue; + } + + $model = $params["{$prefix}contentmodel-{$role}"]; + $format = $params["{$prefix}contentformat-{$role}"]; + + if ( !$model && $rev && $rev->hasSlot( $role ) ) { + $model = $rev->getSlot( $role, RevisionRecord::RAW )->getModel(); + } + if ( !$model && $title && $role === 'main' ) { + // @todo: Use SlotRoleRegistry and do this for all slots + $model = $title->getContentModel(); + } + if ( !$model ) { + $model = $this->guessModel( $role ); + } + if ( !$model ) { + $model = CONTENT_MODEL_WIKITEXT; + $this->addWarning( [ 'apiwarn-compare-nocontentmodel', $model ] ); } - $popts = ParserOptions::newFromContext( $this->getContext() ); - $content = $content->preSaveTransform( $title, $this->getUser(), $popts ); - } - return [ null, $content, $rev ]; + try { + $content = ContentHandler::makeContent( $text, $title, $model, $format ); + } catch ( MWContentSerializationException $ex ) { + $this->dieWithException( $ex, [ + 'wrap' => ApiMessage::create( 'apierror-contentserializationexception', 'parseerror' ) + ] ); + } + + if ( $params["{$prefix}pst"] ) { + if ( !$title ) { + $this->dieWithError( 'apierror-compare-no-title' ); + } + $popts = ParserOptions::newFromContext( $this->getContext() ); + $content = $content->preSaveTransform( $title, $this->getUser(), $popts ); + } + + $section = $params["{$prefix}section-{$role}"]; + if ( $section !== null && $section !== '' ) { + if ( !$rev ) { + $this->dieWithError( "apierror-compare-no{$prefix}revision" ); + } + $oldContent = $rev->getContent( $role, RevisionRecord::FOR_THIS_USER, $this->getUser() ); + if ( !$oldContent ) { + $this->dieWithError( + [ 'apierror-missingcontent-revid-role', $rev->getId(), wfEscapeWikiText( $role ) ], + 'missingcontent' + ); + } + if ( !$oldContent->getContentHandler()->supportsSections() ) { + $this->dieWithError( [ 'apierror-sectionsnotsupported', $content->getModel() ] ); + } + try { + $content = $oldContent->replaceSection( $section, $content, '' ); + } catch ( Exception $ex ) { + // Probably a content model mismatch. + $content = null; + } + if ( !$content ) { + $this->dieWithError( [ 'apierror-sectionreplacefailed' ] ); + } + } + + // Deprecated 'fromsection'/'tosection' + if ( $role === 'main' && isset( $params["{$prefix}section"] ) ) { + $section = $params["{$prefix}section"]; + $content = $content->getSection( $section ); + if ( !$content ) { + $this->dieWithError( + [ "apierror-compare-nosuch{$prefix}section", wfEscapeWikiText( $section ) ], + "nosuch{$prefix}section" + ); + } + } + + $newRev->setContent( $role, $content ); + } + return [ $newRev, $rev, null ]; } /** - * Set value fields from a Revision object + * Set value fields from a RevisionRecord object + * * @param array &$vals Result array to set data into * @param string $prefix 'from' or 'to' - * @param Revision|null $rev + * @param RevisionRecord|null $rev */ private function setVals( &$vals, $prefix, $rev ) { if ( $rev ) { - $title = $rev->getTitle(); + $title = $rev->getPageAsLinkTarget(); if ( isset( $this->props['ids'] ) ) { $vals["{$prefix}id"] = $title->getArticleID(); $vals["{$prefix}revid"] = $rev->getId(); @@ -408,41 +532,42 @@ class ApiComparePages extends ApiBase { } $anyHidden = false; - if ( $rev->isDeleted( Revision::DELETED_TEXT ) ) { + if ( $rev->isDeleted( RevisionRecord::DELETED_TEXT ) ) { $vals["{$prefix}texthidden"] = true; $anyHidden = true; } - if ( $rev->isDeleted( Revision::DELETED_USER ) ) { + if ( $rev->isDeleted( RevisionRecord::DELETED_USER ) ) { $vals["{$prefix}userhidden"] = true; $anyHidden = true; } - if ( isset( $this->props['user'] ) && - $rev->userCan( Revision::DELETED_USER, $this->getUser() ) - ) { - $vals["{$prefix}user"] = $rev->getUserText( Revision::RAW ); - $vals["{$prefix}userid"] = $rev->getUser( Revision::RAW ); + if ( isset( $this->props['user'] ) ) { + $user = $rev->getUser( RevisionRecord::FOR_THIS_USER, $this->getUser() ); + if ( $user ) { + $vals["{$prefix}user"] = $user->getName(); + $vals["{$prefix}userid"] = $user->getId(); + } } - if ( $rev->isDeleted( Revision::DELETED_COMMENT ) ) { + if ( $rev->isDeleted( RevisionRecord::DELETED_COMMENT ) ) { $vals["{$prefix}commenthidden"] = true; $anyHidden = true; } - if ( $rev->userCan( Revision::DELETED_COMMENT, $this->getUser() ) ) { - if ( isset( $this->props['comment'] ) ) { - $vals["{$prefix}comment"] = $rev->getComment( Revision::RAW ); - } - if ( isset( $this->props['parsedcomment'] ) ) { + if ( isset( $this->props['comment'] ) || isset( $this->props['parsedcomment'] ) ) { + $comment = $rev->getComment( RevisionRecord::FOR_THIS_USER, $this->getUser() ); + if ( $comment !== null ) { + if ( isset( $this->props['comment'] ) ) { + $vals["{$prefix}comment"] = $comment->text; + } $vals["{$prefix}parsedcomment"] = Linker::formatComment( - $rev->getComment( Revision::RAW ), - $rev->getTitle() + $comment->text, Title::newFromLinkTarget( $title ) ); } } if ( $anyHidden ) { $this->getMain()->setCacheMode( 'private' ); - if ( $rev->isDeleted( Revision::DELETED_RESTRICTED ) ) { + if ( $rev->isDeleted( RevisionRecord::DELETED_RESTRICTED ) ) { $vals["{$prefix}suppressed"] = true; } } @@ -455,6 +580,12 @@ class ApiComparePages extends ApiBase { } public function getAllowedParams() { + $slotRoles = MediaWikiServices::getInstance()->getSlotRoleStore()->getMap(); + if ( !in_array( 'main', $slotRoles, true ) ) { + $slotRoles[] = 'main'; + } + sort( $slotRoles, SORT_STRING ); + // Parameters for the 'from' and 'to' content $fromToParams = [ 'title' => null, @@ -464,24 +595,58 @@ class ApiComparePages extends ApiBase { 'rev' => [ ApiBase::PARAM_TYPE => 'integer' ], - 'text' => [ - ApiBase::PARAM_TYPE => 'text' + + 'slots' => [ + ApiBase::PARAM_TYPE => $slotRoles, + ApiBase::PARAM_ISMULTI => true, + ], + 'text-{slot}' => [ + ApiBase::PARAM_TEMPLATE_VARS => [ 'slot' => 'slots' ], // fixed below + ApiBase::PARAM_TYPE => 'text', + ], + 'section-{slot}' => [ + ApiBase::PARAM_TEMPLATE_VARS => [ 'slot' => 'slots' ], // fixed below + ApiBase::PARAM_TYPE => 'string', + ], + 'contentformat-{slot}' => [ + ApiBase::PARAM_TEMPLATE_VARS => [ 'slot' => 'slots' ], // fixed below + ApiBase::PARAM_TYPE => ContentHandler::getAllContentFormats(), + ], + 'contentmodel-{slot}' => [ + ApiBase::PARAM_TEMPLATE_VARS => [ 'slot' => 'slots' ], // fixed below + ApiBase::PARAM_TYPE => ContentHandler::getContentModels(), ], - 'section' => null, 'pst' => false, + + 'text' => [ + ApiBase::PARAM_TYPE => 'text', + ApiBase::PARAM_DEPRECATED => true, + ], 'contentformat' => [ ApiBase::PARAM_TYPE => ContentHandler::getAllContentFormats(), + ApiBase::PARAM_DEPRECATED => true, ], 'contentmodel' => [ ApiBase::PARAM_TYPE => ContentHandler::getContentModels(), - ] + ApiBase::PARAM_DEPRECATED => true, + ], + 'section' => [ + ApiBase::PARAM_DFLT => null, + ApiBase::PARAM_DEPRECATED => true, + ], ]; $ret = []; foreach ( $fromToParams as $k => $v ) { + if ( isset( $v[ApiBase::PARAM_TEMPLATE_VARS]['slot'] ) ) { + $v[ApiBase::PARAM_TEMPLATE_VARS]['slot'] = 'fromslots'; + } $ret["from$k"] = $v; } foreach ( $fromToParams as $k => $v ) { + if ( isset( $v[ApiBase::PARAM_TEMPLATE_VARS]['slot'] ) ) { + $v[ApiBase::PARAM_TEMPLATE_VARS]['slot'] = 'toslots'; + } $ret["to$k"] = $v; } @@ -508,6 +673,12 @@ class ApiComparePages extends ApiBase { ApiBase::PARAM_HELP_MSG_PER_VALUE => [], ]; + $ret['slots'] = [ + ApiBase::PARAM_TYPE => $slotRoles, + ApiBase::PARAM_ISMULTI => true, + ApiBase::PARAM_ALL => true, + ]; + return $ret; } diff --git a/includes/api/ApiLogin.php b/includes/api/ApiLogin.php index 0248f25ef6..14491da19f 100644 --- a/includes/api/ApiLogin.php +++ b/includes/api/ApiLogin.php @@ -132,7 +132,8 @@ class ApiLogin extends ApiBase { $loginType = 'BotPassword'; } elseif ( !$botLoginData[2] || $status->hasMessage( 'login-throttled' ) || - $status->hasMessage( 'botpasswords-needs-reset' ) + $status->hasMessage( 'botpasswords-needs-reset' ) || + $status->hasMessage( 'botpasswords-locked' ) ) { $authRes = 'Failed'; $message = $status->getMessage(); diff --git a/includes/api/ApiMain.php b/includes/api/ApiMain.php index 03d29524a8..3b305f9e5c 100644 --- a/includes/api/ApiMain.php +++ b/includes/api/ApiMain.php @@ -534,7 +534,11 @@ class ApiMain extends ApiBase { MediaWikiServices::getInstance()->getStatsdDataFactory()->timing( 'api.' . $this->mModule->getModuleName() . '.executeTiming', 1000 * $runTime ); - } catch ( Exception $e ) { + } catch ( Exception $e ) { // @todo Remove this block when HHVM is no longer supported + $this->handleException( $e ); + $this->logRequest( microtime( true ) - $t, $e ); + $isError = true; + } catch ( Throwable $e ) { $this->handleException( $e ); $this->logRequest( microtime( true ) - $t, $e ); $isError = true; @@ -558,9 +562,9 @@ class ApiMain extends ApiBase { * Handle an exception as an API response * * @since 1.23 - * @param Exception $e + * @param Exception|Throwable $e */ - protected function handleException( Exception $e ) { + protected function handleException( $e ) { // T65145: Rollback any open database transactions if ( !( $e instanceof ApiUsageException || $e instanceof UsageException ) ) { // UsageExceptions are intentional, so don't rollback if that's the case @@ -600,7 +604,10 @@ class ApiMain extends ApiBase { foreach ( $ex->getStatusValue()->getErrors() as $error ) { try { $this->mPrinter->addWarning( $error ); - } catch ( Exception $ex2 ) { + } catch ( Exception $ex2 ) { // @todo Remove this block when HHVM is no longer supported + // WTF? + $this->addWarning( $error ); + } catch ( Throwable $ex2 ) { // WTF? $this->addWarning( $error ); } @@ -631,17 +638,20 @@ class ApiMain extends ApiBase { * friendly to clients. If it fails, it will rethrow the exception. * * @since 1.23 - * @param Exception $e - * @throws Exception + * @param Exception|Throwable $e + * @throws Exception|Throwable */ - public static function handleApiBeforeMainException( Exception $e ) { + public static function handleApiBeforeMainException( $e ) { ob_start(); try { $main = new self( RequestContext::getMain(), false ); $main->handleException( $e ); $main->logRequest( 0, $e ); - } catch ( Exception $e2 ) { + } catch ( Exception $e2 ) { // @todo Remove this block when HHVM is no longer supported + // Nope, even that didn't work. Punt. + throw $e; + } catch ( Throwable $e2 ) { // Nope, even that didn't work. Punt. throw $e; } @@ -1009,7 +1019,7 @@ class ApiMain extends ApiBase { * text around the exception's (presumably English) message as a single * error (no warnings). * - * @param Exception $e + * @param Exception|Throwable $e * @param string $type 'error' or 'warning' * @return ApiMessage[] * @since 1.27 @@ -1054,7 +1064,7 @@ class ApiMain extends ApiBase { /** * Replace the result data with the information about an exception. - * @param Exception $e + * @param Exception|Throwable $e * @return string[] Error codes */ protected function substituteResultWithError( $e ) { @@ -1609,7 +1619,7 @@ class ApiMain extends ApiBase { /** * Log the preceding request * @param float $time Time in seconds - * @param Exception|null $e Exception caught while processing the request + * @param Exception|Throwable|null $e Exception caught while processing the request */ protected function logRequest( $time, $e = null ) { $request = $this->getRequest(); diff --git a/includes/api/ApiPageSet.php b/includes/api/ApiPageSet.php index 3786e8d90c..26846f4332 100644 --- a/includes/api/ApiPageSet.php +++ b/includes/api/ApiPageSet.php @@ -1170,6 +1170,8 @@ class ApiPageSet extends ApiBase { private function processTitlesArray( $titles ) { $usernames = []; $linkBatch = new LinkBatch(); + $services = MediaWikiServices::getInstance(); + $contLang = $services->getContentLanguage(); foreach ( $titles as $title ) { if ( is_string( $title ) ) { @@ -1197,7 +1199,6 @@ class ApiPageSet extends ApiBase { $this->mInterwikiTitles[$unconvertedTitle] = $titleObj->getInterwiki(); } else { // Variants checking - $contLang = MediaWikiServices::getInstance()->getContentLanguage(); if ( $this->mConvertTitles && $contLang->hasVariants() && !$titleObj->exists() ) { @@ -1217,7 +1218,8 @@ class ApiPageSet extends ApiBase { $this->mAllSpecials[$ns][$dbkey] = $this->mFakePageId; $target = null; if ( $ns === NS_SPECIAL && $this->mResolveRedirects ) { - $special = SpecialPageFactory::getPage( $dbkey ); + $spFactory = $services->getSpecialPageFactory(); + $special = $spFactory->getPage( $dbkey ); if ( $special instanceof RedirectSpecialArticle ) { // Only RedirectSpecialArticle is intended to redirect to an article, other kinds of // RedirectSpecialPage are probably applying weird URL parameters we don't want to handle. @@ -1225,7 +1227,7 @@ class ApiPageSet extends ApiBase { $context->setTitle( $titleObj ); $context->setRequest( new FauxRequest ); $special->setContext( $context ); - list( /* $alias */, $subpage ) = SpecialPageFactory::resolveAlias( $dbkey ); + list( /* $alias */, $subpage ) = $spFactory->resolveAlias( $dbkey ); $target = $special->getRedirect( $subpage ); } } @@ -1263,7 +1265,7 @@ class ApiPageSet extends ApiBase { } } // Get gender information - $genderCache = MediaWikiServices::getInstance()->getGenderCache(); + $genderCache = $services->getGenderCache(); $genderCache->doQuery( $usernames, __METHOD__ ); return $linkBatch; diff --git a/includes/api/ApiParse.php b/includes/api/ApiParse.php index 3a604715bc..5c25b5a0e5 100644 --- a/includes/api/ApiParse.php +++ b/includes/api/ApiParse.php @@ -341,7 +341,7 @@ class ApiParse extends ApiBase { $result_array['text'] = $p_result->getText( [ 'allowTOC' => !$params['disabletoc'], 'enableSectionEditLinks' => !$params['disableeditsection'], - 'unwrap' => $params['wrapoutputclass'] === '', + 'wrapperDivClass' => $params['wrapoutputclass'], 'deduplicateStyles' => !$params['disablestylededuplication'], ] ); $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'text'; diff --git a/includes/api/ApiPurge.php b/includes/api/ApiPurge.php index bb0be68484..bb1f3d3bf9 100644 --- a/includes/api/ApiPurge.php +++ b/includes/api/ApiPurge.php @@ -1,5 +1,4 @@ pingLimiter( 'linkpurge' ) ) { - $popts = $page->makeParserOptions( 'canonical' ); - - # Parse content; note that HTML generation is only needed if we want to cache the result. - $content = $page->getContent( Revision::RAW ); - if ( $content ) { - $enableParserCache = $this->getConfig()->get( 'EnableParserCache' ); - $p_result = $content->getParserOutput( - $title, - $page->getLatest(), - $popts, - $enableParserCache + # Logging to better see expensive usage patterns + if ( $forceRecursiveLinkUpdate ) { + LoggerFactory::getInstance( 'RecursiveLinkPurge' )->info( + "Recursive link purge enqueued for {title}", + [ + 'user' => $this->getUser()->getName(), + 'title' => $title->getPrefixedText() + ] ); - - # Logging to better see expensive usage patterns - if ( $forceRecursiveLinkUpdate ) { - LoggerFactory::getInstance( 'RecursiveLinkPurge' )->info( - "Recursive link purge enqueued for {title}", - [ - 'user' => $this->getUser()->getName(), - 'title' => $title->getPrefixedText() - ] - ); - } - - # Update the links tables - $updates = $content->getSecondaryDataUpdates( - $title, null, $forceRecursiveLinkUpdate, $p_result ); - foreach ( $updates as $update ) { - $update->setCause( 'api-purge', $this->getUser()->getName() ); - DeferredUpdates::addUpdate( $update, DeferredUpdates::PRESEND ); - } - - $r['linkupdate'] = true; - - if ( $enableParserCache ) { - $pcache = MediaWikiServices::getInstance()->getParserCache(); - $pcache->save( $p_result, $page, $popts ); - } } + + $page->updateParserCache( [ + 'causeAction' => 'api-purge', + 'causeAgent' => $this->getUser()->getName(), + ] ); + $page->doSecondaryDataUpdates( [ + 'recursive' => $forceRecursiveLinkUpdate, + 'causeAction' => 'api-purge', + 'causeAgent' => $this->getUser()->getName(), + 'defer' => DeferredUpdates::PRESEND, + ] ); + $r['linkupdate'] = true; } else { $this->addWarning( 'apierror-ratelimited' ); $forceLinkUpdate = false; diff --git a/includes/api/ApiQueryAllDeletedRevisions.php b/includes/api/ApiQueryAllDeletedRevisions.php index 50afc7de3d..bae68855f9 100644 --- a/includes/api/ApiQueryAllDeletedRevisions.php +++ b/includes/api/ApiQueryAllDeletedRevisions.php @@ -24,6 +24,7 @@ */ use MediaWiki\MediaWikiServices; +use MediaWiki\Storage\NameTableAccessException; use MediaWiki\Storage\RevisionRecord; /** @@ -42,6 +43,8 @@ class ApiQueryAllDeletedRevisions extends ApiQueryRevisionsBase { * @return void */ protected function run( ApiPageSet $resultPageSet = null ) { + global $wgChangeTagsSchemaMigrationStage; + // Before doing anything at all, let's check permissions $this->checkUserRightsAny( 'deletedhistory' ); @@ -73,7 +76,7 @@ class ApiQueryAllDeletedRevisions extends ApiQueryRevisionsBase { if ( !is_null( $params[$param] ) ) { $p = $this->getModulePrefix(); $this->dieWithError( - [ 'apierror-invalidparammix-cannotusewith', $p.$param, "{$p}user" ], + [ 'apierror-invalidparammix-cannotusewith', $p . $param, "{$p}user" ], 'invalidparammix' ); } @@ -83,7 +86,7 @@ class ApiQueryAllDeletedRevisions extends ApiQueryRevisionsBase { if ( !is_null( $params[$param] ) ) { $p = $this->getModulePrefix(); $this->dieWithError( - [ 'apierror-invalidparammix-mustusewith', $p.$param, "{$p}user" ], + [ 'apierror-invalidparammix-mustusewith', $p . $param, "{$p}user" ], 'invalidparammix' ); } @@ -136,7 +139,17 @@ class ApiQueryAllDeletedRevisions extends ApiQueryRevisionsBase { $this->addJoinConds( [ 'change_tag' => [ 'INNER JOIN', [ 'ar_rev_id=ct_rev_id' ] ] ] ); - $this->addWhereFld( 'ct_tag', $params['tag'] ); + if ( $wgChangeTagsSchemaMigrationStage > MIGRATION_WRITE_BOTH ) { + $changeTagDefStore = MediaWikiServices::getInstance()->getChangeTagDefStore(); + try { + $this->addWhereFld( 'ct_tag_id', $changeTagDefStore->getId( $params['tag'] ) ); + } catch ( NameTableAccessException $exception ) { + // Return nothing. + $this->addWhere( '1=0' ); + } + } else { + $this->addWhereFld( 'ct_tag', $params['tag'] ); + } } if ( $this->fetchContent ) { diff --git a/includes/api/ApiQueryDeletedRevisions.php b/includes/api/ApiQueryDeletedRevisions.php index c3af71bff8..47b746a737 100644 --- a/includes/api/ApiQueryDeletedRevisions.php +++ b/includes/api/ApiQueryDeletedRevisions.php @@ -24,6 +24,7 @@ */ use MediaWiki\MediaWikiServices; +use MediaWiki\Storage\NameTableAccessException; use MediaWiki\Storage\RevisionRecord; /** @@ -38,6 +39,8 @@ class ApiQueryDeletedRevisions extends ApiQueryRevisionsBase { } protected function run( ApiPageSet $resultPageSet = null ) { + global $wgChangeTagsSchemaMigrationStage; + $user = $this->getUser(); // Before doing anything at all, let's check permissions $this->checkUserRightsAny( 'deletedhistory' ); @@ -88,7 +91,17 @@ class ApiQueryDeletedRevisions extends ApiQueryRevisionsBase { $this->addJoinConds( [ 'change_tag' => [ 'INNER JOIN', [ 'ar_rev_id=ct_rev_id' ] ] ] ); - $this->addWhereFld( 'ct_tag', $params['tag'] ); + if ( $wgChangeTagsSchemaMigrationStage > MIGRATION_WRITE_BOTH ) { + $changeTagDefStore = MediaWikiServices::getInstance()->getChangeTagDefStore(); + try { + $this->addWhereFld( 'ct_tag_id', $changeTagDefStore->getId( $params['tag'] ) ); + } catch ( NameTableAccessException $exception ) { + // Return nothing. + $this->addWhere( '1=0' ); + } + } else { + $this->addWhereFld( 'ct_tag', $params['tag'] ); + } } if ( $this->fetchContent ) { @@ -289,7 +302,7 @@ class ApiQueryDeletedRevisions extends ApiQueryRevisionsBase { protected function getExamplesMessages() { return [ 'action=query&prop=deletedrevisions&titles=Main%20Page|Talk:Main%20Page&' . - 'drvprop=user|comment|content' + 'drvslots=*&drvprop=user|comment|content' => 'apihelp-query+deletedrevisions-example-titles', 'action=query&prop=deletedrevisions&revids=123456' => 'apihelp-query+deletedrevisions-example-revids', diff --git a/includes/api/ApiQueryDeletedrevs.php b/includes/api/ApiQueryDeletedrevs.php index 83d00a9330..e84b9b2247 100644 --- a/includes/api/ApiQueryDeletedrevs.php +++ b/includes/api/ApiQueryDeletedrevs.php @@ -20,6 +20,9 @@ * @file */ +use MediaWiki\MediaWikiServices; +use MediaWiki\Storage\NameTableAccessException; + /** * Query module to enumerate all deleted revisions. * @@ -33,6 +36,8 @@ class ApiQueryDeletedrevs extends ApiQueryBase { } public function execute() { + global $wgChangeTagsSchemaMigrationStage; + // Before doing anything at all, let's check permissions $this->checkUserRightsAny( 'deletedhistory' ); @@ -140,7 +145,17 @@ class ApiQueryDeletedrevs extends ApiQueryBase { $this->addJoinConds( [ 'change_tag' => [ 'INNER JOIN', [ 'ar_rev_id=ct_rev_id' ] ] ] ); - $this->addWhereFld( 'ct_tag', $params['tag'] ); + if ( $wgChangeTagsSchemaMigrationStage > MIGRATION_WRITE_BOTH ) { + $changeTagDefStore = MediaWikiServices::getInstance()->getChangeTagDefStore(); + try { + $this->addWhereFld( 'ct_tag_id', $changeTagDefStore->getId( $params['tag'] ) ); + } catch ( NameTableAccessException $exception ) { + // Return nothing. + $this->addWhere( '1=0' ); + } + } else { + $this->addWhereFld( 'ct_tag', $params['tag'] ); + } } if ( $fld_content ) { diff --git a/includes/api/ApiQueryInfo.php b/includes/api/ApiQueryInfo.php index 0cf6b04c82..3b7b00de80 100644 --- a/includes/api/ApiQueryInfo.php +++ b/includes/api/ApiQueryInfo.php @@ -522,7 +522,7 @@ class ApiQueryInfo extends ApiQueryBase { } if ( $this->params['testactions'] ) { - $limit = $this->getMain()->canApiHighLimits() ? self::LIMIT_SML1 : self::LIMIT_SML2; + $limit = $this->getMain()->canApiHighLimits() ? self::LIMIT_SML2 : self::LIMIT_SML1; if ( $this->countTestedActions >= $limit ) { return null; // force a continuation } diff --git a/includes/api/ApiQueryLogEvents.php b/includes/api/ApiQueryLogEvents.php index a6b97dd895..39be2c17fd 100644 --- a/includes/api/ApiQueryLogEvents.php +++ b/includes/api/ApiQueryLogEvents.php @@ -20,6 +20,9 @@ * @file */ +use MediaWiki\MediaWikiServices; +use MediaWiki\Storage\NameTableAccessException; + /** * Query action to List the log events, with optional filtering by various parameters. * @@ -39,6 +42,8 @@ class ApiQueryLogEvents extends ApiQueryBase { $fld_details = false, $fld_tags = false; public function execute() { + global $wgChangeTagsSchemaMigrationStage; + $params = $this->extractRequestParams(); $db = $this->getDB(); $this->commentStore = CommentStore::getStore(); @@ -113,7 +118,17 @@ class ApiQueryLogEvents extends ApiQueryBase { $this->addTables( 'change_tag' ); $this->addJoinConds( [ 'change_tag' => [ 'INNER JOIN', [ 'log_id=ct_log_id' ] ] ] ); - $this->addWhereFld( 'ct_tag', $params['tag'] ); + if ( $wgChangeTagsSchemaMigrationStage > MIGRATION_WRITE_BOTH ) { + $changeTagDefStore = MediaWikiServices::getInstance()->getChangeTagDefStore(); + try { + $this->addWhereFld( 'ct_tag_id', $changeTagDefStore->getId( $params['tag'] ) ); + } catch ( NameTableAccessException $exception ) { + // Return nothing. + $this->addWhere( '1=0' ); + } + } else { + $this->addWhereFld( 'ct_tag', $params['tag'] ); + } } if ( !is_null( $params['action'] ) ) { diff --git a/includes/api/ApiQueryRecentChanges.php b/includes/api/ApiQueryRecentChanges.php index a5be58b7ef..01b9c9affb 100644 --- a/includes/api/ApiQueryRecentChanges.php +++ b/includes/api/ApiQueryRecentChanges.php @@ -20,6 +20,8 @@ * @file */ +use MediaWiki\MediaWikiServices; +use MediaWiki\Storage\NameTableAccessException; use MediaWiki\Storage\RevisionRecord; /** @@ -141,6 +143,8 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase { * @param ApiPageSet|null $resultPageSet */ public function run( $resultPageSet = null ) { + global $wgChangeTagsSchemaMigrationStage; + $user = $this->getUser(); /* Get the parameters of the request. */ $params = $this->extractRequestParams(); @@ -361,7 +365,17 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase { if ( !is_null( $params['tag'] ) ) { $this->addTables( 'change_tag' ); $this->addJoinConds( [ 'change_tag' => [ 'INNER JOIN', [ 'rc_id=ct_rc_id' ] ] ] ); - $this->addWhereFld( 'ct_tag', $params['tag'] ); + if ( $wgChangeTagsSchemaMigrationStage > MIGRATION_WRITE_BOTH ) { + $changeTagDefStore = MediaWikiServices::getInstance()->getChangeTagDefStore(); + try { + $this->addWhereFld( 'ct_tag_id', $changeTagDefStore->getId( $params['tag'] ) ); + } catch ( NameTableAccessException $exception ) { + // Return nothing. + $this->addWhere( '1=0' ); + } + } else { + $this->addWhereFld( 'ct_tag', $params['tag'] ); + } } // Paranoia: avoid brute force searches (T19342) diff --git a/includes/api/ApiQueryRevisions.php b/includes/api/ApiQueryRevisions.php index 5e7b864b54..9109a5ea7d 100644 --- a/includes/api/ApiQueryRevisions.php +++ b/includes/api/ApiQueryRevisions.php @@ -21,6 +21,7 @@ */ use MediaWiki\MediaWikiServices; +use MediaWiki\Storage\NameTableAccessException; use MediaWiki\Storage\RevisionRecord; /** @@ -83,6 +84,8 @@ class ApiQueryRevisions extends ApiQueryRevisionsBase { } protected function run( ApiPageSet $resultPageSet = null ) { + global $wgChangeTagsSchemaMigrationStage; + $params = $this->extractRequestParams( false ); $revisionStore = MediaWikiServices::getInstance()->getRevisionStore(); @@ -170,7 +173,17 @@ class ApiQueryRevisions extends ApiQueryRevisionsBase { $this->addJoinConds( [ 'change_tag' => [ 'INNER JOIN', [ 'rev_id=ct_rev_id' ] ] ] ); - $this->addWhereFld( 'ct_tag', $params['tag'] ); + if ( $wgChangeTagsSchemaMigrationStage > MIGRATION_WRITE_BOTH ) { + $changeTagDefStore = MediaWikiServices::getInstance()->getChangeTagDefStore(); + try { + $this->addWhereFld( 'ct_tag_id', $changeTagDefStore->getId( $params['tag'] ) ); + } catch ( NameTableAccessException $exception ) { + // Return nothing. + $this->addWhere( '1=0' ); + } + } else { + $this->addWhereFld( 'ct_tag', $params['tag'] ); + } } if ( $resultPageSet === null && $this->fetchContent ) { @@ -486,7 +499,7 @@ class ApiQueryRevisions extends ApiQueryRevisionsBase { protected function getExamplesMessages() { return [ 'action=query&prop=revisions&titles=API|Main%20Page&' . - 'rvprop=timestamp|user|comment|content' + 'rvslots=*&rvprop=timestamp|user|comment|content' => 'apihelp-query+revisions-example-content', 'action=query&prop=revisions&titles=Main%20Page&rvlimit=5&' . 'rvprop=timestamp|user|comment' diff --git a/includes/api/ApiQuerySearch.php b/includes/api/ApiQuerySearch.php index d4f0396c93..b90dd5d6af 100644 --- a/includes/api/ApiQuerySearch.php +++ b/includes/api/ApiQuerySearch.php @@ -399,13 +399,14 @@ class ApiQuerySearch extends ApiQueryGeneratorBase { // If we have more than one engine the list of available sorts is // difficult to represent. For now don't expose it. - $alternatives = MediaWiki\MediaWikiServices::getInstance() + $services = MediaWiki\MediaWikiServices::getInstance(); + $alternatives = $services ->getSearchEngineConfig() ->getSearchTypes(); if ( count( $alternatives ) == 1 ) { $this->allowedParams['sort'] = [ ApiBase::PARAM_DFLT => 'relevance', - ApiBase::PARAM_TYPE => MediaWiki\MediaWikiServices::getInstance() + ApiBase::PARAM_TYPE => $services ->newSearchEngine() ->getValidSorts(), ]; diff --git a/includes/api/ApiQuerySiteinfo.php b/includes/api/ApiQuerySiteinfo.php index eaa3bc12cc..697eab69ba 100644 --- a/includes/api/ApiQuerySiteinfo.php +++ b/includes/api/ApiQuerySiteinfo.php @@ -341,8 +341,9 @@ class ApiQuerySiteinfo extends ApiQueryBase { protected function appendSpecialPageAliases( $property ) { $data = []; - $aliases = MediaWikiServices::getInstance()->getContentLanguage()->getSpecialPageAliases(); - foreach ( SpecialPageFactory::getNames() as $specialpage ) { + $services = MediaWikiServices::getInstance(); + $aliases = $services->getContentLanguage()->getSpecialPageAliases(); + foreach ( $services->getSpecialPageFactory()->getNames() as $specialpage ) { if ( isset( $aliases[$specialpage] ) ) { $arr = [ 'realname' => $specialpage, 'aliases' => $aliases[$specialpage] ]; ApiResult::setIndexedTagName( $arr['aliases'], 'alias' ); diff --git a/includes/api/ApiQueryUserContribs.php b/includes/api/ApiQueryUserContribs.php index 3aa61832fe..75670dd445 100644 --- a/includes/api/ApiQueryUserContribs.php +++ b/includes/api/ApiQueryUserContribs.php @@ -21,6 +21,7 @@ */ use MediaWiki\MediaWikiServices; +use MediaWiki\Storage\NameTableAccessException; use MediaWiki\Storage\RevisionRecord; /** @@ -437,7 +438,7 @@ class ApiQueryUserContribs extends ApiQueryBase { * @return bool */ private function prepareQuery( array $users, $limit, $which ) { - global $wgActorTableSchemaMigrationStage; + global $wgActorTableSchemaMigrationStage, $wgChangeTagsSchemaMigrationStage; $this->resetQueryParams(); $db = $this->getDB(); @@ -607,7 +608,17 @@ class ApiQueryUserContribs extends ApiQueryBase { $this->addJoinConds( [ 'change_tag' => [ 'INNER JOIN', [ $idField . ' = ct_rev_id' ] ] ] ); - $this->addWhereFld( 'ct_tag', $this->params['tag'] ); + if ( $wgChangeTagsSchemaMigrationStage > MIGRATION_WRITE_BOTH ) { + $changeTagDefStore = MediaWikiServices::getInstance()->getChangeTagDefStore(); + try { + $this->addWhereFld( 'ct_tag_id', $changeTagDefStore->getId( $this->params['tag'] ) ); + } catch ( NameTableAccessException $exception ) { + // Return nothing. + $this->addWhere( '1=0' ); + } + } else { + $this->addWhereFld( 'ct_tag', $this->params['tag'] ); + } } return true; diff --git a/includes/api/ApiStashEdit.php b/includes/api/ApiStashEdit.php index a3e3e575e7..ab9ae8e4e1 100644 --- a/includes/api/ApiStashEdit.php +++ b/includes/api/ApiStashEdit.php @@ -234,6 +234,7 @@ class ApiStashEdit extends ApiBase { return self::ERROR_CACHE; } } else { + // @todo Doesn't seem reachable, see @todo in buildStashValue $logger->info( "Uncacheable parser output for key '{cachekey}' ('{title}') [{code}].", [ 'cachekey' => $key, 'title' => $titleStr, 'code' => $code ] ); return self::ERROR_UNCACHEABLE; @@ -410,6 +411,9 @@ class ApiStashEdit extends ApiBase { } if ( $ttl <= 0 ) { + // @todo It doesn't seem like this can occur, because it would mean an entry older than + // getCacheExpiry() seconds, which is much longer than PRESUME_FRESH_TTL_SEC, and + // anything older than PRESUME_FRESH_TTL_SEC will have been thrown out already. return [ null, 0, 'no_ttl' ]; } diff --git a/includes/api/SearchApi.php b/includes/api/SearchApi.php index fc6eddf800..70942f8eb2 100644 --- a/includes/api/SearchApi.php +++ b/includes/api/SearchApi.php @@ -156,7 +156,7 @@ trait SearchApi { $searchEngine = MediaWikiServices::getInstance()->getSearchEngineFactory()->create( $type ); $limit = $params['limit']; $searchEngine->setNamespaces( $params['namespace'] ); - $offset = isset( $params['offset'] ) ? $params['offset'] : null; + $offset = $params['offset'] ?? null; $searchEngine->setLimitOffset( $limit, $offset ); // Initialize requested search profiles. diff --git a/includes/api/i18n/ar.json b/includes/api/i18n/ar.json index 241e71afd8..d6af2f16b2 100644 --- a/includes/api/i18n/ar.json +++ b/includes/api/i18n/ar.json @@ -64,20 +64,30 @@ "apihelp-compare-param-fromtitle": "العنوان الأول للمقارنة.", "apihelp-compare-param-fromid": "رقم الصفحة الأول للمقارنة.", "apihelp-compare-param-fromrev": "أول مراجعة للمقارنة.", - "apihelp-compare-param-fromtext": "استخدم هذا النص بدلا من محتوى المراجعة المحدد بواسطة fromtitle، fromid أو fromrev.", + "apihelp-compare-param-frompst": "قم بإجراء تحويل ما قبل الحفظ على fromtext-{slot}.", + "apihelp-compare-param-fromslots": "تجاوز محتوى المراجعة المحددة بواسطة fromtitle أو fromid أو fromrev.\n\nيحدد هذا الوسيط الفتحات المراد تعديلها، استخدم fromtext-{slot} وfromcontentmodel-{slot} وfromcontentformat-{slot} لتحديد محتوى لكل فتحة.", + "apihelp-compare-param-fromtext-{slot}": "نص الفتحة المحددة، إذا تم حذفها، تتم إزالة الفتحة من المراجعة.", + "apihelp-compare-param-fromsection-{slot}": "عندما يكون fromtext-{slot} هو محتوى قسم واحد، فهذا هو رقم القسم، سيتم دمجه في المراجعة المحددة بواسطة fromtitle أو fromid أو fromrev كما لو كانت لتعديل القسم.", + "apihelp-compare-param-fromcontentmodel-{slot}": "نموذج محتوى fromtext-{slot}، إذا لم يتم توفيره، فسيتم تخمينه استنادا إلى الوسائط الأخرى.", + "apihelp-compare-param-fromcontentformat-{slot}": "تنسيق تسلسل محتوى fromtext-{slot}.", + "apihelp-compare-param-fromtext": "حدد fromslots=main واستخدم fromtext-main كبديل.", + "apihelp-compare-param-fromcontentmodel": "حدد fromslots=main واستخدم fromcontentmodel-main كبديل.", + "apihelp-compare-param-fromcontentformat": "حدد fromslots=main واستخدم fromcontentformat-main كبديل.", "apihelp-compare-param-fromsection": "استخدم فقط القسم المحدد في المحتوى 'من' المحدد.", - "apihelp-compare-param-frompst": "قم بإجراء تحويل ما قبل الحفظ على fromtext.", - "apihelp-compare-param-fromcontentmodel": "نموذج محتوى fromtext، إذا لم يتم توفيره، فسيتم تخمينه استنادا إلى الوسائط الأخرى.", - "apihelp-compare-param-fromcontentformat": "تنسيق محتوى تسلسل fromtext.", "apihelp-compare-param-totitle": "العنوان الثاني للمقارنة.", "apihelp-compare-param-toid": "رقم الصفحة الثاني للمقارنة.", "apihelp-compare-param-torev": "المراجعة الثانية للمقارنة.", "apihelp-compare-param-torelative": "استخدم مراجعة متعلقة بالمراجعة المحددة من fromtitle أو fromid أو fromrev، سيتم تجاهل جميع خيارات 'إلى' الأخرى.", - "apihelp-compare-param-totext": "استخدم هذا النص بدلا من محتوى المراجعة المحدد بواسطة totitle أو toid أو torev.", - "apihelp-compare-param-tosection": "استخدم فقط القسم المحدد في المحتوى 'إلى' المحدد.", "apihelp-compare-param-topst": "قم بإجراء تحويل ما قبل الحفظ على totext.", - "apihelp-compare-param-tocontentmodel": "نموذج محتوى totext، إذا لم يتم توفيره، فسيتم تخمينه استنادا إلى الوسائط الأخرى.", - "apihelp-compare-param-tocontentformat": "تنسيق محتوى تسلسل totext.", + "apihelp-compare-param-toslots": "تجاوز محتوى المراجعة المحددة بواسطة totitle أو toid أو torev.\n\nيحدد هذا الوسيط الفتحات المراد تعديلها، استخدم totext-{slot} وtocontentmodel-{slot} وtocontentformat-{slot} لتحديد محتوى لكل فتحة.", + "apihelp-compare-param-totext-{slot}": "نص الفتحة المحددة، إذا تم حذفه، تتم إزالة الفتحة من المراجعة.", + "apihelp-compare-param-tosection-{slot}": "عندما يكون totext-{slot} هو محتوى قسم واحد، فهذا هو رقم القسم، سيتم دمجه في المراجعة المحددة بواسطة totitle أو toid أو torev كما لو كانت لتعديل القسم.", + "apihelp-compare-param-tocontentmodel-{slot}": "نموذج محتوى totext-{slot}، إذا لم يتم توفيره، فسيتم تخمينه استنادا إلى الوسائط الأخرى.", + "apihelp-compare-param-tocontentformat-{slot}": "تنسيق تسلسل محتوى totext-{slot}.", + "apihelp-compare-param-totext": "حدد toslots=main واستخدم totext-main كبديل.", + "apihelp-compare-param-tocontentmodel": "حدد toslots=main واستخدم tocontentmodel-main كبديل.", + "apihelp-compare-param-tocontentformat": "حدد toslots=main واستخدم tocontentformat-main كبديل.", + "apihelp-compare-param-tosection": "استخدم فقط القسم المحدد في المحتوى 'إلى' المحدد.", "apihelp-compare-param-prop": "أية قطعة من المعلومات للحصول عليها.", "apihelp-compare-paramvalue-prop-diff": "HTML الفرق.", "apihelp-compare-paramvalue-prop-diffsize": "حجم HTML الفرق، بالبايت.", @@ -88,6 +98,7 @@ "apihelp-compare-paramvalue-prop-comment": "التعليق على المراجعات 'من' و'إلى'.", "apihelp-compare-paramvalue-prop-parsedcomment": "التعليق المحلل على المراجعات 'من' و'إلى'.", "apihelp-compare-paramvalue-prop-size": "حجم المراجعات 'من' و'إلى'.", + "apihelp-compare-param-slots": "إرجاع فرق فردي لهذه الفتحات، بدلا من فرق واحد مشترك لجميع فتحات.", "apihelp-compare-example-1": "إنشاء فرق بين المراجعة 1 و2.", "apihelp-createaccount-summary": "انشاء حساب مستخدم جديد", "apihelp-createaccount-param-preservestate": "إذا تم عرض [[Special:ApiHelp/query+authmanagerinfo|action=query&meta=authmanagerinfo]] بشكل صحيح لـhasprimarypreservedstate، فقد تم تعليم طلبات primary-required لكي يجب حذفها، إذا عرضت قيمة غير فارغة لـpreservedusername فيجب استخدام اسم المستخدم هذا للوسيط username.", @@ -681,7 +692,7 @@ "apihelp-query+deletedrevisions-param-start": "الطابع الزمني لبدء العد منه، تم التجاهل عند معالجة قائمة بمعرفات المراجعة.", "apihelp-query+deletedrevisions-param-end": "الطابع الزمني لإيقاف التعداد فيه، تم التجاهل عند معالجة قائمة بمعرفات المراجعة.", "apihelp-query+deletedrevisions-param-tag": "إدراج المراجعات الموسومة بهذ الوسم فقط.", - "apihelp-query+deletedrevisions-param-user": "إددراج المراجعات بواسطة هذا المستخدم فقط.", + "apihelp-query+deletedrevisions-param-user": "إدراج المراجعات بواسطة هذا المستخدم فقط.", "apihelp-query+deletedrevisions-param-excludeuser": "لا تسرد المراجعات بواسطة هذا المستخدم.", "apihelp-query+deletedrevisions-example-titles": "إدراج المراجعات المحذوفة من الصفحات Main Page وTalk:Main Page، بمحتوى.", "apihelp-query+deletedrevisions-example-revids": "إدراج المعلومات الخاصة بالمراجعة المحذوفة 123456.", @@ -865,6 +876,8 @@ "apihelp-query+iwbacklinks-paramvalue-prop-iwprefix": "يضيف بادئة الإنترويكي.", "apihelp-query+iwbacklinks-paramvalue-prop-iwtitle": "يضيف عنوان الإنترويكي.", "apihelp-query+iwbacklinks-param-dir": "الاتجاه للإدراج فيه.", + "apihelp-query+iwbacklinks-example-simple": "الحصول على الصفحات التي تصل إلى [[wikibooks:Test]].", + "apihelp-query+iwbacklinks-example-generator": "الحصول على معلومات عن الصفحات التي تصل إلى [[wikibooks:Test]].", "apihelp-query+iwlinks-summary": "يعرض جميع روابط الإنترويكي من الصفحات المحددة.", "apihelp-query+iwlinks-param-url": "ما إذا كنت تريد الحصول على المسار الكامل (لا يمكن استخدامه مع $1prop).", "apihelp-query+iwlinks-param-prop": "الخصائص الإضافية التي يمكنك الحصول عليها لكل رابط بين اللغات:", @@ -1066,6 +1079,7 @@ "apihelp-query+revisions+base-paramvalue-prop-tags": "وسوم للمراجعة.", "apihelp-query+revisions+base-paramvalue-prop-roles": "أدرج أدوار فتحة المحتوى الموجودة في المراجعة.", "apihelp-query+revisions+base-paramvalue-prop-parsetree": "استخدم [[Special:ApiHelp/expandtemplates|action=expandtemplates]] أو [[Special:ApiHelp/parse|action=parse]] بدلا من ذلك، شجرة تحليل XML لمحتوى المراجعة (تتطلب نموذج المحتوى $1).", + "apihelp-query+revisions+base-param-slots": "أي الفتحات المراجعة لتعيد البيانات، عندما يتم تضمين الخصائص ذات الصلة بالفتحات في $1props، إذا تم حذفها، فسيتم إرجاع البيانات من فتحة main بتنسيق متوافق مع الإصدارات السابقة.", "apihelp-query+revisions+base-param-limit": "الحد من عدد المراجعات التي سيتم إرجاعها.", "apihelp-query+revisions+base-param-expandtemplates": "استخدم [[Special:ApiHelp/expandtemplates|action=expandtemplates]] بدلا من ذلك، قم بتوسيع القوالب في محتوى المراجعة (يتطلب $1prop=content).", "apihelp-query+revisions+base-param-generatexml": "استخدم [[Special:ApiHelp/expandtemplates|action=expandtemplates]] أو [[Special:ApiHelp/parse|action=parse]] بدلا من ذلك، قم بتوليد شجرة تحليل XML لمحتوى المراجعة (تتطلب $1prop=content).", @@ -1440,14 +1454,16 @@ "apihelp-json-param-callback": "إذا تم تحديده، فسيقوم بإخراج الإخراج في استدعاء دالة معينة، للسلامة; سيتم تقييد جميع البيانات الخاصة بالمستخدم.", "apihelp-json-param-utf8": "إذا تم تحديده، يقوم بترميز معظم (وليس كل) الأحرف غير ASCII كـUTF-8 بدلا من استبدالها بتسلسلات الهروب السداسية العشرية، افتراضي عندما لا يكون formatversion 1.", "apihelp-json-param-ascii": "إذا تم تحديده، يشفر كل غير ASCII باستخدام تسلسلات الهروب السداسية العشرية، افتراضي عندما يكون formatversion 1.", + "apihelp-json-param-formatversion": "تنسيق الإخراج: \n;1:تنسيق متوافق مع الإصدارات السابقة (مصفوفات منطقية بتنسيق XML، ومفاتيح * لعقد المحتوى، وما إلى ذلك).\n;2:التنسيق الحديث التجريبي، التفاصيل قد تتغير!\n;الأحدث: استخدم أحدث تنسيق (حاليا 2)، قد يتغير دون سابق إنذار.", "apihelp-jsonfm-summary": "بيانات الإخراج بتنسيق JSON (الطباعة بـHTML).", "apihelp-none-summary": "عدم إخراج أي شيء.", "apihelp-php-summary": "بيانات الإخراج بتنسيق PHP المتسلسل.", + "apihelp-php-param-formatversion": "تنسيق الإخراج: \n;1:تنسيق متوافق مع الإصدارات السابقة (مصفوفات منطقية بتنسيق XML، ومفاتيح * لعقد المحتوى، وما إلى ذلك).\n;2:التنسيق الحديث التجريبي، التفاصيل قد تتغير!\n;الأحدث: استخدم أحدث تنسيق (حاليا 2)، قد يتغير دون سابق إنذار.", "apihelp-phpfm-summary": "بيانات الإخراج بتنسيق JSON (الطباعة بـHTML).", "apihelp-rawfm-summary": "بيانات الإخراج، بما في ذلك عناصر تصحيح الأخطاء، بتنسيق JSON (الطباعة بـHTML).", "apihelp-xml-summary": "بيانات الإخراج بتنسيق XML.", "apihelp-xml-param-xslt": "إذا تم تحديده، سيضيف الصفحة المسماة كورقة أنماط XSL، يجب أن تكون القيمة عنوانا في نطاق {{ns:MediaWiki}} ينتهي بـ.xsl.", - "apihelp-xml-param-includexmlnamespace": "If specified, adds an XML namespace.\nإذا تم تحديدها، سيضيف نطاق XML.", + "apihelp-xml-param-includexmlnamespace": "\nإذا تم تحديدها، سيضيف نطاق XML.", "apihelp-xmlfm-summary": "بيانات الإخراج بتنسيق XML (الطباعة بـHTML).", "api-format-title": "ناتج API ميدياويكي", "api-format-prettyprint-header": "هذا هو تمثيل HTML لتنسيق $1، HTML مفيد في تصحيح الأخطاء، ولكنه غير مناسب لاستخدام التطبيق. \n\nحدد الوسيط format لتغيير نسق المخرجات، لمشاهدة تمثيل غير HTML لتنسيق $1; اضبط format=$2.\n\nراجع [[mw:Special:MyLanguage/API|التوثيق كاملا]]، أو [[Special:ApiHelp/main|مساعدة API]] لمزيد من المعلومات.", @@ -1516,7 +1532,7 @@ "api-help-param-direction": "في أي اتجاه للتعداد:\n;الأحدث: سرد الأقدم أولا، ملاحظة: يجب أن يكون $1start قبل $1end.\n;older:List newest first (default). Note: $1start has to be later than $1end.\n;الأقدم: سرد الأحدث أولا (افتراضي)، ملاحظة: يجب أن يكون $1start بعد $1end.", "api-help-param-continue": "عندما تتوفر المزيد من النتائج، استخدم هذا للمتابعة", "api-help-param-no-description": "(لا يوجد وصف)", - "api-help-param-maxbytes": "Cلا يمكن أن يكون أطول من $1 {{PLURAL:$1|بايت}}.", + "api-help-param-maxbytes": "لا يمكن أن يكون أطول من $1 {{PLURAL:$1|بايت}}.", "api-help-param-maxchars": "Cلا يمكن أن يكون أطول من $1 {{PLURAL:$1|حرف|أحرف}}.", "api-help-examples": "{{PLURAL:$1|مثال|أمثلة}}:", "api-help-permissions": "{{PLURAL:$1|الإذن|الأذونات}}:", @@ -1579,9 +1595,13 @@ "apierror-changeauth-norequest": "فشل في إنشاء طلب التغيير.", "apierror-chunk-too-small": "الحد الأدنى لحجم القطعة هو $1 {{PLURAL:$1|بايت}} للقطع غير النهائية.", "apierror-cidrtoobroad": "لا يُقبَل مدى $1 CIDR أكبر من /$2.", + "apierror-compare-maintextrequired": "الوسيط $1text-main مطلوب عندما يكون $1slots يحتوي على main (لا يمكن حذف الفتحة الرئيسية).", "apierror-compare-no-title": "لا يمكن الحفظ المسبق للحفظ بدون عنوان; حاول تحديد fromtitle أو totitle.", "apierror-compare-nosuchfromsection": "لا يوجد قسم $1 في المحتوى 'من'.", "apierror-compare-nosuchtosection": "لا يوجد قسم $1 في المحتوى 'إلى'.", + "apierror-compare-nofromrevision": "ليس 'من' مراجعة، حدد fromrev أو fromtitle أو fromid.", + "apierror-compare-notext": "لا يمكن استخدام الوسيط $1 بدون $2.", + "apierror-compare-notorevision": "ليس 'إلى' مراجعة، حدد torev أو totitle أو toid.", "apierror-compare-relative-to-nothing": "لا توجد مراجعة 'من' لـtorelative لتكون نسبة.", "apierror-contentserializationexception": "فشل تسلسل المحتوى: $1", "apierror-contenttoobig": "يتجاوز المحتوى الذي أدخلته حد حجم المقالة البالغ $1 {{PLURAL:$1|كيلوبايت}}.", @@ -1629,6 +1649,7 @@ "apierror-mimesearchdisabled": "تم تعطيل بحث MIME في وضع Miser.", "apierror-missingcontent-pageid": "محتوى مفقود لمعرف الصفحة $1.", "apierror-missingcontent-revid": "محتوى مفقود لمعرف المراجعة $1.", + "apierror-missingcontent-revid-role": "محتوى مفقود لمعرف المراجعة $1 للدور $2.", "apierror-missingparam-at-least-one-of": "مطلوب {{PLURAL:$2|الوسيط|واحد على الأقل من الوسائط}} $1.", "apierror-missingparam-one-of": "مطلوب {{PLURAL:$2|الوسيط|واحد على الأقل من الوسائط}} $1.", "apierror-missingparam": "يجب تعيين الوسيط $1.", @@ -1743,6 +1764,7 @@ "apiwarn-deprecation-login-botpw": "تم إيقاف تسجيل الدخول إلى الحساب الرئيسي عبر action=login وقد يتوقف عن العمل دون سابق إنذار، لمتابعة تسجيل الدخول باستخدام action=login; راجع [[Special:BotPasswords]]، لمتابعة استخدام تسجيل الدخول إلى الحساب الرئيسي بأمان; راجع action=clientlogin.", "apiwarn-deprecation-login-nobotpw": "تم إيقاف تسجيل الدخول إلى الحساب الرئيسي عبر action=login، وقد يتوقف عن العمل دون سابق إنذار، لتسجيل الدخول بأمان; راجع action=clientlogin.", "apiwarn-deprecation-login-token": "تم إيقاف عمل رمز مميز عبر action=login ;استخدم action=query&meta=tokens&type=login بدلا من ذلك.", + "apiwarn-deprecation-missingparam": "نظرا لعدم تحديد $1; تم استخدام تنسيق قديم للإخراج، تم إيقاف هذا التنسيق، وسيتم دائما استخدام التنسيق الجديد في المستقبل.", "apiwarn-deprecation-parameter": "تم إيقاف الوسيط $1.", "apiwarn-deprecation-parse-headitems": "تم إيقاف prop=headitems منذ ميدياويكي 1.28; استخدم prop=headhtml عند إنشاء مستندات HTML جديدة، أو prop=modules|jsconfigvars عند تحديث مستند من جانب العميل.", "apiwarn-deprecation-purge-get": "تم إيقاف استخدام action=purge عبر GET; استخدم POST بدلا من ذلك.", @@ -1762,6 +1784,7 @@ "apiwarn-parse-nocontentmodel": "لم يتم إعطاء title أو contentmodel، على افتراض $1.", "apiwarn-parse-revidwithouttext": "تم استخدام revid بدون text، وتم طلب خصائص الصفحة المحللة، هل تقصد استخدام oldid بدلا من revid؟", "apiwarn-parse-titlewithouttext": "تم استخدام title بدون text، وتم طلب خصائص الصفحة المحللة، هل تقصد استخدام page بدلا من title؟", + "apiwarn-redirectsandrevids": "لا يمكن استخدام دقة تحويلة مع الوسيط revids، أية تحويلات لنقطة revids لم يتم حلها.", "apiwarn-tokennotallowed": "الإجراء \"$1\" غير مسموح به للمستخدم الحالي.", "apiwarn-tokens-origin": "قد لا يتم الحصول على الرموز عند عدم تطبيق السياسة الأصلية.", "apiwarn-truncatedresult": "تم اقتطاع هذه النتيجة لأنها قد تكون أكبر من حد الـ$1 بايت.", diff --git a/includes/api/i18n/de.json b/includes/api/i18n/de.json index 5bdc3c900e..3b6f0cd613 100644 --- a/includes/api/i18n/de.json +++ b/includes/api/i18n/de.json @@ -824,6 +824,7 @@ "apihelp-query+protectedtitles-paramvalue-prop-level": "Ergänzt den Schutzstatus.", "apihelp-query+protectedtitles-example-simple": "Listet geschützte Titel auf.", "apihelp-query+querypage-param-limit": "Anzahl der zurückzugebenden Ergebnisse.", + "apihelp-query+random-summary": "Ruft einen Satz an zufälligen Seiten ab.", "apihelp-query+recentchanges-summary": "Listet die letzten Änderungen auf.", "apihelp-query+recentchanges-param-user": "Listet nur Änderungen von diesem Benutzer auf.", "apihelp-query+recentchanges-param-excludeuser": "Listet keine Änderungen von diesem Benutzer auf.", @@ -1085,6 +1086,10 @@ "apierror-badparameter": "Ungültiger Wert für den Parameter $1.", "apierror-badquery": "Ungültige Abfrage.", "apierror-cannot-async-upload-file": "Die Parameter async und file können nicht kombiniert werden. Falls du eine asynchrone Verarbeitung deiner hochgeladenen Datei wünschst, lade sie zuerst mithilfe des Parameters stash auf den Speicher hoch. Veröffentliche anschließend die gespeicherte Datei asynchron mithilfe filekey und async.", + "apierror-compare-maintextrequired": "Der Parameter $1text-main ist erforderlich, wenn $1slots main enthält (kann nicht den Hauptschlitz löschen).", + "apierror-compare-nofromrevision": "Keine Version „from“. fromrev, fromtitle oder fromid angeben.", + "apierror-compare-notext": "Der Parameter $1 kann nicht ohne $2 verwendet werden.", + "apierror-compare-notorevision": "Keine Version „to“. torev, totitle oder toid angeben.", "apierror-emptypage": "Das Erstellen neuer leerer Seiten ist nicht erlaubt.", "apierror-filedoesnotexist": "Die Datei ist nicht vorhanden.", "apierror-import-unknownerror": "Unbekannter Fehler beim Importieren: $1.", @@ -1093,6 +1098,7 @@ "apierror-invaliduserid": "Die Benutzerkennung $1 ist nicht gültig.", "apierror-maxbytes": "Der Parameter $1 kann nicht länger sein als {{PLURAL:$2|ein Byte|$2 Bytes}}", "apierror-maxchars": "Der Parameter $1 kann nicht länger sein als {{PLURAL:$2|ein|$2}} Zeichen", + "apierror-missingcontent-revid-role": "Fehlender Inhalt für die Versionskennung $1 für die Rolle $2.", "apierror-nosuchsection": "Es gibt keinen Abschnitt $1.", "apierror-nosuchuserid": "Es gibt keinen Benutzer mit der Kennung $1.", "apierror-offline": "Aufgrund von Problemen bei der Netzwerkverbindung kannst du nicht weitermachen. Stelle sicher, dass du eine funktionierende Internetverbindung hast und versuche es erneut.", diff --git a/includes/api/i18n/en.json b/includes/api/i18n/en.json index 3c74f25feb..253380c978 100644 --- a/includes/api/i18n/en.json +++ b/includes/api/i18n/en.json @@ -64,20 +64,30 @@ "apihelp-compare-param-fromtitle": "First title to compare.", "apihelp-compare-param-fromid": "First page ID to compare.", "apihelp-compare-param-fromrev": "First revision to compare.", - "apihelp-compare-param-fromtext": "Use this text instead of the content of the revision specified by fromtitle, fromid or fromrev.", + "apihelp-compare-param-frompst": "Do a pre-save transform on fromtext-{slot}.", + "apihelp-compare-param-fromslots": "Override content of the revision specified by fromtitle, fromid or fromrev.\n\nThis parameter specifies the slots that are to be modified. Use fromtext-{slot}, fromcontentmodel-{slot}, and fromcontentformat-{slot} to specify content for each slot.", + "apihelp-compare-param-fromtext-{slot}": "Text of the specified slot. If omitted, the slot is removed from the revision.", + "apihelp-compare-param-fromsection-{slot}": "When fromtext-{slot} is the content of a single section, this is the section number. It will be merged into the revision specified by fromtitle, fromid or fromrev as if for a section edit.", + "apihelp-compare-param-fromcontentmodel-{slot}": "Content model of fromtext-{slot}. If not supplied, it will be guessed based on the other parameters.", + "apihelp-compare-param-fromcontentformat-{slot}": "Content serialization format of fromtext-{slot}.", + "apihelp-compare-param-fromtext": "Specify fromslots=main and use fromtext-main instead.", + "apihelp-compare-param-fromcontentmodel": "Specify fromslots=main and use fromcontentmodel-main instead.", + "apihelp-compare-param-fromcontentformat": "Specify fromslots=main and use fromcontentformat-main instead.", "apihelp-compare-param-fromsection": "Only use the specified section of the specified 'from' content.", - "apihelp-compare-param-frompst": "Do a pre-save transform on fromtext.", - "apihelp-compare-param-fromcontentmodel": "Content model of fromtext. If not supplied, it will be guessed based on the other parameters.", - "apihelp-compare-param-fromcontentformat": "Content serialization format of fromtext.", "apihelp-compare-param-totitle": "Second title to compare.", "apihelp-compare-param-toid": "Second page ID to compare.", "apihelp-compare-param-torev": "Second revision to compare.", "apihelp-compare-param-torelative": "Use a revision relative to the revision determined from fromtitle, fromid or fromrev. All of the other 'to' options will be ignored.", - "apihelp-compare-param-totext": "Use this text instead of the content of the revision specified by totitle, toid or torev.", - "apihelp-compare-param-tosection": "Only use the specified section of the specified 'to' content.", "apihelp-compare-param-topst": "Do a pre-save transform on totext.", - "apihelp-compare-param-tocontentmodel": "Content model of totext. If not supplied, it will be guessed based on the other parameters.", - "apihelp-compare-param-tocontentformat": "Content serialization format of totext.", + "apihelp-compare-param-toslots": "Override content of the revision specified by totitle, toid or torev.\n\nThis parameter specifies the slots that are to be modified. Use totext-{slot}, tocontentmodel-{slot}, and tocontentformat-{slot} to specify content for each slot.", + "apihelp-compare-param-totext-{slot}": "Text of the specified slot. If omitted, the slot is removed from the revision.", + "apihelp-compare-param-tosection-{slot}": "When totext-{slot} is the content of a single section, this is the section number. It will be merged into the revision specified by totitle, toid or torev as if for a section edit.", + "apihelp-compare-param-tocontentmodel-{slot}": "Content model of totext-{slot}. If not supplied, it will be guessed based on the other parameters.", + "apihelp-compare-param-tocontentformat-{slot}": "Content serialization format of totext-{slot}.", + "apihelp-compare-param-totext": "Specify toslots=main and use totext-main instead.", + "apihelp-compare-param-tocontentmodel": "Specify toslots=main and use tocontentmodel-main instead.", + "apihelp-compare-param-tocontentformat": "Specify toslots=main and use tocontentformat-main instead.", + "apihelp-compare-param-tosection": "Only use the specified section of the specified 'to' content.", "apihelp-compare-param-prop": "Which pieces of information to get.", "apihelp-compare-paramvalue-prop-diff": "The diff HTML.", "apihelp-compare-paramvalue-prop-diffsize": "The size of the diff HTML, in bytes.", @@ -88,6 +98,7 @@ "apihelp-compare-paramvalue-prop-comment": "The comment on the 'from' and 'to' revisions.", "apihelp-compare-paramvalue-prop-parsedcomment": "The parsed comment on the 'from' and 'to' revisions.", "apihelp-compare-paramvalue-prop-size": "The size of the 'from' and 'to' revisions.", + "apihelp-compare-param-slots": "Return individual diffs for these slots, rather than one combined diff for all slots.", "apihelp-compare-example-1": "Create a diff between revision 1 and 2.", "apihelp-createaccount-summary": "Create a new user account.", @@ -1706,9 +1717,13 @@ "apierror-changeauth-norequest": "Failed to create change request.", "apierror-chunk-too-small": "Minimum chunk size is $1 {{PLURAL:$1|byte|bytes}} for non-final chunks.", "apierror-cidrtoobroad": "$1 CIDR ranges broader than /$2 are not accepted.", + "apierror-compare-maintextrequired": "Parameter $1text-main is required when $1slots contains main (cannot delete the main slot).", "apierror-compare-no-title": "Cannot pre-save transform without a title. Try specifying fromtitle or totitle.", "apierror-compare-nosuchfromsection": "There is no section $1 in the 'from' content.", "apierror-compare-nosuchtosection": "There is no section $1 in the 'to' content.", + "apierror-compare-nofromrevision": "No 'from' revision. Specify fromrev, fromtitle, or fromid.", + "apierror-compare-notext": "Parameter $1 cannot be used without $2.", + "apierror-compare-notorevision": "No 'to' revision. Specify torev, totitle, or toid.", "apierror-compare-relative-to-nothing": "No 'from' revision for torelative to be relative to.", "apierror-contentserializationexception": "Content serialization failed: $1", "apierror-contenttoobig": "The content you supplied exceeds the article size limit of $1 {{PLURAL:$1|kilobyte|kilobytes}}.", @@ -1756,6 +1771,7 @@ "apierror-mimesearchdisabled": "MIME search is disabled in Miser Mode.", "apierror-missingcontent-pageid": "Missing content for page ID $1.", "apierror-missingcontent-revid": "Missing content for revision ID $1.", + "apierror-missingcontent-revid-role": "Missing content for revision ID $1 for role $2.", "apierror-missingparam-at-least-one-of": "{{PLURAL:$2|The parameter|At least one of the parameters}} $1 is required.", "apierror-missingparam-one-of": "{{PLURAL:$2|The parameter|One of the parameters}} $1 is required.", "apierror-missingparam": "The $1 parameter must be set.", diff --git a/includes/api/i18n/fr.json b/includes/api/i18n/fr.json index a62b2bae4f..1c0bb0e950 100644 --- a/includes/api/i18n/fr.json +++ b/includes/api/i18n/fr.json @@ -84,20 +84,30 @@ "apihelp-compare-param-fromtitle": "Premier titre à comparer.", "apihelp-compare-param-fromid": "ID de la première page à comparer.", "apihelp-compare-param-fromrev": "Première révision à comparer.", - "apihelp-compare-param-fromtext": "Utiliser ce texte au lieu du contenu de la révision spécifié par fromtitle, fromid ou fromrev.", + "apihelp-compare-param-frompst": "Faire une transformation avant enregistrement sur fromtext-{slot}.", + "apihelp-compare-param-fromslots": "Substituer le contenu de la révision spécifiée par fromtitle, fromid ou fromrev.\n\nCe paramètre spécifie les slots à modifier. Utilisez fromtext-{slot}, fromcontentmodel-{slot}, et fromcontentformat-{slot} pour spécifier le contenu de chaque slot.", + "apihelp-compare-param-fromtext-{slot}": "Texte du slot spécifié. Si absent, le slot est supprimé de la révision.", + "apihelp-compare-param-fromsection-{slot}": "Si fromtext-{slot} est le contenu d'une seule section, c'est le numéro de la section. Il sera fusionné dans la révision spécifiée par fromtitle, fromid ou fromrev comme pour les modifications de section.", + "apihelp-compare-param-fromcontentmodel-{slot}": "Modèle de contenu de fromtext-{slot}. Si non fourni, il sera déduit en fonction de la valeur des autres paramètres.", + "apihelp-compare-param-fromcontentformat-{slot}": "Format de sérialisation de contenu de fromtext-{slot}.", + "apihelp-compare-param-fromtext": "Spécifiez fromslots=main et utilisez fromtext-main à la place.", + "apihelp-compare-param-fromcontentmodel": "Spécifiez fromslots=main et utilisez fromcontentmodel-main à la place.", + "apihelp-compare-param-fromcontentformat": "Spécifiez fromslots=main et utilisez fromcontentformat-main à la place.", "apihelp-compare-param-fromsection": "N'utiliser que la section spécifiée du contenu 'from'.", - "apihelp-compare-param-frompst": "Faire une transformation avant enregistrement sur fromtext.", - "apihelp-compare-param-fromcontentmodel": "Modèle de contenu de fromtext. Si non fourni, il sera déduit d’après les autres paramètres.", - "apihelp-compare-param-fromcontentformat": "Sérialisation du contenu de fromtext.", "apihelp-compare-param-totitle": "Second titre à comparer.", "apihelp-compare-param-toid": "ID de la seconde page à comparer.", "apihelp-compare-param-torev": "Seconde révision à comparer.", "apihelp-compare-param-torelative": "Utiliser une révision relative à la révision déterminée de fromtitle, fromid ou fromrev. Toutes les autres options 'to' seront ignorées.", - "apihelp-compare-param-totext": "Utiliser ce texte au lieu du contenu de la révision spécifié par totitle, toid ou torev.", - "apihelp-compare-param-tosection": "N'utiliser que la section spécifiée du contenu 'to'.", "apihelp-compare-param-topst": "Faire une transformation avant enregistrement sur totext.", - "apihelp-compare-param-tocontentmodel": "Modèle de contenu de totext. Si non fourni, il sera deviné d’après les autres paramètres.", - "apihelp-compare-param-tocontentformat": "Format de sérialisation du contenu de totext.", + "apihelp-compare-param-toslots": "Substitue le contenu de la révision spécifiée par totitle, toid ou torev.\n\nCe paramètre spécifie les slots qui vont être modifiés. Utilisez totext-{slot}, tocontentmodel-{slot}, et tocontentformat-{slot} pour spécifier le contenu de chaque slot.", + "apihelp-compare-param-totext-{slot}": "Texte de la relation spécifiée. Si absent, le slot est supprimé de la révision.", + "apihelp-compare-param-tosection-{slot}": "Si totext-{slot} est le contenu d'une seule section, c'est le numéro de la section. Il sera fusionné dans la révision spécifiée par totitle, toid ou torev comme pour les modifications de section.", + "apihelp-compare-param-tocontentmodel-{slot}": "Modèle de contenu de totext-{slot}. Si non fourni, il sera déduit en fonction de la valeur des autres paramètres.", + "apihelp-compare-param-tocontentformat-{slot}": "Format de sérialisation du contenu de totext-{slot}.", + "apihelp-compare-param-totext": "Spécifiez toslots=main et utilisez totext-main à la place.", + "apihelp-compare-param-tocontentmodel": "Spécifiez toslots=main et utilisez tocontentmodel-main à la place.", + "apihelp-compare-param-tocontentformat": "Spécifiez toslots=main et utilisez tocontentformat-main à la place.", + "apihelp-compare-param-tosection": "N'utiliser que la section spécifiée du contenu 'to'.", "apihelp-compare-param-prop": "Quelles informations obtenir.", "apihelp-compare-paramvalue-prop-diff": "Le diff HTML.", "apihelp-compare-paramvalue-prop-diffsize": "La taille du diff HTML en octets.", @@ -108,6 +118,7 @@ "apihelp-compare-paramvalue-prop-comment": "Le commentaire des révisions 'depuis' et 'vers'.", "apihelp-compare-paramvalue-prop-parsedcomment": "Le commentaire analysé des révisions 'depuis' et 'vers'.", "apihelp-compare-paramvalue-prop-size": "La taille des révisions 'depuis' et 'vers'.", + "apihelp-compare-param-slots": "Retourne les diffs individuels pour ces slots, plutôt qu'un diff combiné pour tous les slots.", "apihelp-compare-example-1": "Créer une différence entre les révisions 1 et 2", "apihelp-createaccount-summary": "Créer un nouveau compte utilisateur.", "apihelp-createaccount-param-preservestate": "Si [[Special:ApiHelp/query+authmanagerinfo|action=query&meta=authmanagerinfo]] a retourné true pour hasprimarypreservedstate, les demandes marquées comme primary-required doivent être omises. Si elle a retourné une valeur non vide pour preservedusername, ce nom d'utilisateur doit être utilisé pour le paramètre username.", @@ -1604,9 +1615,13 @@ "apierror-changeauth-norequest": "Échec à la création de la requête de modification.", "apierror-chunk-too-small": "La taille minimale d’un segment est de $1 {{PLURAL:$1|octet|octets}} pour les segments hors le dernier.", "apierror-cidrtoobroad": "Les plages CIDR $1 plus large que /$2 ne sont pas acceptées.", + "apierror-compare-maintextrequired": "Le paramètre $1text-main est obligatoire lorsque $1slots contient main (impossible de supprimer le slot principal).", "apierror-compare-no-title": "Impossible de faire une transformation avant enregistrement sans titre. Essayez de spécifier fromtitle ou totitle.", "apierror-compare-nosuchfromsection": "Il n'y a pas de section $1 dans le contenu 'from'.", "apierror-compare-nosuchtosection": "Il n'y a pas de section $1 dans le contenu 'to'.", + "apierror-compare-nofromrevision": "Aucune révision 'from'. Spécifiez fromrev, fromtitle, ou fromid.", + "apierror-compare-notext": "Le paramètre $1 ne peut pas être utilisé sans $2.", + "apierror-compare-notorevision": "Aucune révision 'to'. Spécifiez torev, totitle, ou toid.", "apierror-compare-relative-to-nothing": "Pas de révision 'depuis' pour torelative à laquelle se rapporter.", "apierror-contentserializationexception": "Échec de sérialisation du contenu : $1", "apierror-contenttoobig": "Le contenu que vous avez fourni dépasse la limite de taille d’un article, qui est de $1 {{PLURAL:$1|kilooctet|kilooctets}}.", @@ -1654,6 +1669,7 @@ "apierror-mimesearchdisabled": "La recherche MIME est désactivée en mode Misère.", "apierror-missingcontent-pageid": "Contenu manquant pour la page d’ID $1.", "apierror-missingcontent-revid": "Contenu de la révision d’ID $1 manquant.", + "apierror-missingcontent-revid-role": "Contenu absent pour l'ID de révision $1 pour le rôle $2.", "apierror-missingparam-at-least-one-of": "{{PLURAL:$2|Le paramètre|Au moins un des paramètres}} $1 est obligatoire.", "apierror-missingparam-one-of": "{{PLURAL:$2|Le paramètre|Un des paramètres}} $1 est obligatoire.", "apierror-missingparam": "Le paramètre $1 doit être défini.", diff --git a/includes/api/i18n/gl.json b/includes/api/i18n/gl.json index 5103bd35d9..1217013467 100644 --- a/includes/api/i18n/gl.json +++ b/includes/api/i18n/gl.json @@ -16,12 +16,12 @@ "Athena in Wonderland" ] }, - "apihelp-main-extended-description": "
\n* [[mw:Special:MyLanguage/API:Main_page|Documentación]]\n* [[mw:Special:MyLanguage/API:FAQ|FAQ]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api Lista de discusión]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce Anuncios da API]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R Erros e solicitudes]\n
\nEstado: Tódalas funcionalidades mostradas nesta páxina deberían estar funcionando, pero a API aínda está desenrolo, e pode ser modificada en calquera momento. Apúntese na [https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ lista de discusión mediawiki-api-announce] para estar informado acerca das actualizacións.\n\nSolicitudes incorrectas: Cando se envían solicitudes incorrectas á API, envíase unha cabeceira HTTP coa chave \"MediaWiki-API-Error\" e, a seguir, tanto o valor da cabeceira como o código de erro retornado serán definidos co mesmo valor. Para máis información, consulte [[mw:Special:MyLanguage/API:Errors_and_warnings|API: Erros e avisos]].\n\nTest: Para facilitar as probas das peticións da API, consulte [[Special:ApiSandbox]].", + "apihelp-main-extended-description": "
\n* [[mw:Special:MyLanguage/API:Main_page|Documentación]]\n* [[mw:Special:MyLanguage/API:FAQ|FAQ]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api Lista de discusión]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce Anuncios da API]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R Erros e solicitudes]\n
\nEstado: A API de MediaWiki é interface estable e consolidada que é soportada e mellorada constantemente. Aínda que intentamos evitalo, ocasionalmente precisamos facer cambios importantes, pode apuntarse na [https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ lista de discusión mediawiki-api-announce] para estar informado acerca das actualizacións.\n\nSolicitudes incorrectas: Cando se envían solicitudes incorrectas á API, envíase unha cabeceira HTTP coa chave \"MediaWiki-API-Error\" e, a seguir, tanto o valor da cabeceira como o código de erro retornado serán definidos co mesmo valor. Para máis información, consulte [[mw:Special:MyLanguage/API:Errors_and_warnings|API: Erros e avisos]].\n\n

Test: Para facilitar as probas das peticións da API, consulte [[Special:ApiSandbox]].

", "apihelp-main-param-action": "Que acción se realizará.", "apihelp-main-param-format": "O formato de saída.", "apihelp-main-param-maxlag": "O retardo máximo pode usarse cando MediaWiki está instalada nun cluster de base de datos replicadas. Para gardar accións que causen calquera retardo máis de replicación do sitio, este parámetro pode facer que o cliente espere ata que o retardo de replicación sexa menor que o valor especificado. No caso de retardo excesivo, é devolto o código de erro maxlag cunha mensaxe como esperando por $host: $lag segundos de retardo.
Para máis información, ver [[mw:Special:MyLanguage/Manual:Maxlag_parameter|Manual: Maxlag parameter]].", - "apihelp-main-param-smaxage": "Fixar a cabeceira HTTP de control de caché s-maxage a esos segundos. Os erros nunca se gardan na caché.", - "apihelp-main-param-maxage": "Fixar a cabeceira HTTP de control de caché max-age a esos segundos. Os erros nunca se gardan na caché.", + "apihelp-main-param-smaxage": "Fixar a cabeceira HTTP de control de caché s-maxage a eses segundos. Os erros nunca se gardan na caché.", + "apihelp-main-param-maxage": "Fixar a cabeceira HTTP de control de caché max-age a eses segundos. Os erros nunca se gardan na caché.", "apihelp-main-param-assert": "Verificar se o usuario está conectado como usuario ou ten a marca de bot.", "apihelp-main-param-assertuser": "Verificar que o usuario actual é o usuario nomeado.", "apihelp-main-param-requestid": "Calquera valor dado aquí será incluído na resposta. Pode usarse para distingir peticións.", @@ -74,7 +74,7 @@ "apihelp-compare-paramvalue-prop-diff": "O diff HTML.", "apihelp-compare-paramvalue-prop-diffsize": "O tamaño do diff HTML, en bytes.", "apihelp-compare-paramvalue-prop-size": "Tamaño das revisións 'desde' e 'a'.", - "apihelp-compare-example-1": "Mostrar diferencias entre a revisión 1 e a 2", + "apihelp-compare-example-1": "Amosar diferencias entre a revisión 1 e a 2.", "apihelp-createaccount-summary": "Crear unha nova conta de usuario.", "apihelp-createaccount-param-preservestate": "SE [[Special:ApiHelp/query+authmanagerinfo|action=query&meta=authmanagerinfo]] devolve o valor \"certo\" para hasprimarypreservedstate, as consultas marcadas como primary-required deben ser omitidas. Se devolve un valor non baleiro para preservedusername, ese nome de usuario debe usarse para o parámetro username.", "apihelp-createaccount-example-create": "Comezar o proceso de crear un usuario Exemplo con contrasinal ExemploContrasinal.", @@ -165,12 +165,12 @@ "apihelp-feedcontributions-param-year": "Desde o ano (e anteriores).", "apihelp-feedcontributions-param-month": "Desde o mes de (e anteriores).", "apihelp-feedcontributions-param-tagfilter": "Filtrar as contribucións que teñan estas etiquetas.", - "apihelp-feedcontributions-param-deletedonly": "Mostrar só as contribuciones eliminadas.", - "apihelp-feedcontributions-param-toponly": "Mostrar só as edicións que que son as ultimas revisións.", - "apihelp-feedcontributions-param-newonly": "Mostrar só as edicións que crearon páxinas.", + "apihelp-feedcontributions-param-deletedonly": "Amosar só as contribucións eliminadas.", + "apihelp-feedcontributions-param-toponly": "Amosar só as edicións que que son as ultimas revisións.", + "apihelp-feedcontributions-param-newonly": "Amosar só as edicións que crearon páxinas.", "apihelp-feedcontributions-param-hideminor": "Ocultar edicións menores.", - "apihelp-feedcontributions-param-showsizediff": "Mostrar diferenza de tamaño entre edicións.", - "apihelp-feedcontributions-example-simple": "Mostrar as contribucións do usuario Example.", + "apihelp-feedcontributions-param-showsizediff": "Amosar diferenza de tamaño entre edicións.", + "apihelp-feedcontributions-example-simple": "Amosar as contribucións do usuario Example.", "apihelp-feedrecentchanges-summary": "Devolve un ficheiro de cambios recentes.", "apihelp-feedrecentchanges-param-feedformat": "O formato da saída.", "apihelp-feedrecentchanges-param-namespace": "Espazo de nomes ó que limitar os resultados.", @@ -178,7 +178,7 @@ "apihelp-feedrecentchanges-param-associated": "Incluir o espazo de nomes asociado (conversa ou principal).", "apihelp-feedrecentchanges-param-days": "Días a limitar os resultados", "apihelp-feedrecentchanges-param-limit": "Número máximo de resultados a visualizar.", - "apihelp-feedrecentchanges-param-from": "Mostrar modificacións desde entón.", + "apihelp-feedrecentchanges-param-from": "Amosar modificacións desde entón.", "apihelp-feedrecentchanges-param-hideminor": "Ocultar cambios menores.", "apihelp-feedrecentchanges-param-hidebots": "Ocultar cambios feitos por bots.", "apihelp-feedrecentchanges-param-hideanons": "Ocultar os cambios realizados por usuarios anónimos.", @@ -187,10 +187,10 @@ "apihelp-feedrecentchanges-param-hidemyself": "Ocultar os cambios realizados polo usuario actual.", "apihelp-feedrecentchanges-param-hidecategorization": "Agochar os cambios de pertenza á categoría.", "apihelp-feedrecentchanges-param-tagfilter": "Filtrar por etiqueta.", - "apihelp-feedrecentchanges-param-target": "Mostrar só os cambios nas páxinas ligadas a esta.", - "apihelp-feedrecentchanges-param-showlinkedto": "Mostrar os cambios nas páxinas ligadas coa páxina seleccionada.", - "apihelp-feedrecentchanges-example-simple": "Mostrar os cambios recentes", - "apihelp-feedrecentchanges-example-30days": "Mostrar os cambios recentes limitados a 30 días", + "apihelp-feedrecentchanges-param-target": "Amosar só os cambios nas páxinas ligadas a esta.", + "apihelp-feedrecentchanges-param-showlinkedto": "Amosar os cambios nas páxinas ligadas coa páxina seleccionada.", + "apihelp-feedrecentchanges-example-simple": "Amosar os cambios recentes.", + "apihelp-feedrecentchanges-example-30days": "Amosar os cambios recentes limitados a 30 días.", "apihelp-feedwatchlist-summary": "Devolve o fluxo dunha lista de vixiancia.", "apihelp-feedwatchlist-param-feedformat": "O formato da saída.", "apihelp-feedwatchlist-param-hours": "Lista as páxinas modificadas desde estas horas ata agora.", @@ -202,7 +202,7 @@ "apihelp-filerevert-param-comment": "Comentario de carga.", "apihelp-filerevert-param-archivename": "Nome de ficheiro da revisión á que reverter.", "apihelp-filerevert-example-revert": "Reverter Wiki.png á versión do 2011-03-05T15:27:40Z.", - "apihelp-help-summary": "Mostrar axuda para os módulos indicados.", + "apihelp-help-summary": "Amosar axuda para os módulos indicados.", "apihelp-help-param-modules": "Módulos para mostar axuda (valores dos parámetros acción e formato, ou principal). Pode especificar submódulos con un +.", "apihelp-help-param-submodules": "Incluír axuda para os submódulos do módulo nomeado.", "apihelp-help-param-recursivesubmodules": "Incluír axuda para os submódulos de forma recursiva.", @@ -304,7 +304,7 @@ "apihelp-paraminfo-param-pagesetmodule": "Obter información sobre o módulo pageset (proporcionando títulos= e amigos).", "apihelp-paraminfo-param-formatmodules": "Lista dos nomes de módulo de formato (valores do parámetro formato). No canto use $1modules.", "apihelp-paraminfo-example-1": "Amosar información para [[Special:ApiHelp/parse|action=parse]], [[Special:ApiHelp/jsonfm|format=jsonfm]], [[Special:ApiHelp/query+allpages|action=query&list=allpages]], e [[Special:ApiHelp/query+siteinfo|action=query&meta=siteinfo]].", - "apihelp-paraminfo-example-2": "Mostrar a información para tódolos submódulos de [[Special:ApiHelp/query|action=query]].", + "apihelp-paraminfo-example-2": "Amosar a información para tódolos submódulos de [[Special:ApiHelp/query|action=query]].", "apihelp-parse-summary": "Fai a análise sintáctica do contido e devolve o resultado da análise.", "apihelp-parse-extended-description": "Vexa varios módulos propostos de [[Special:ApiHelp/query|action=query]] para obter información sobre a versión actual dunha páxina.\n\nHai varias formas de especificar o texto a analizar:\n# Especificar unha páxina ou revisión, usando $1page, $1pageid, ou $1oldid.\n# Especificando contido explícitamente, usando $1text, $1title, and $1contentmodel.\n# Especificando só un resumo a analizar. $1prop debe ter un valor baleiro.", "apihelp-parse-param-title": "Título da páxina á que pertence o texto. Se non se indica, debe especificarse $1contentmodel, e [[API]] usarase como o título.", @@ -429,7 +429,7 @@ "apihelp-query+allfileusages-param-from": "Título do ficheiro no que comezar a enumerar.", "apihelp-query+allfileusages-param-to": "Título do ficheiro no que rematar de enumerar.", "apihelp-query+allfileusages-param-prefix": "Buscar tódolos títulos de ficheiro que comezan con este valor.", - "apihelp-query+allfileusages-param-unique": "Mostrar só nomes de ficheiro distintos. Non pode usarse con $1prop=ids.\nCando se usa como xenerador, produce páxinas obxectivo no canto de páxinas fonte.", + "apihelp-query+allfileusages-param-unique": "Amosar só nomes de ficheiro distintos. Non pode usarse con $1prop=ids.\nCando se usa como xerador, produce páxinas obxectivo no canto de páxinas fonte.", "apihelp-query+allfileusages-param-prop": "Que partes de información incluír:", "apihelp-query+allfileusages-paramvalue-prop-ids": "Engade os IDs das páxinas usadas (non pode usarse con $1unique).", "apihelp-query+allfileusages-paramvalue-prop-title": "Engade o nome do ficheiro.", @@ -451,19 +451,19 @@ "apihelp-query+allimages-param-maxsize": "Limitar a imaxes con como máximo este número de bytes.", "apihelp-query+allimages-param-sha1": "Función hash SHA1 da imaxe. Invalida $1sha1base36.", "apihelp-query+allimages-param-sha1base36": "Función hash SHA1 da imaxe en base 36 (usada en MediaWiki).", - "apihelp-query+allimages-param-user": "Mostrar só ficheiros subidos por este usuario. Só pode usarse con $1sort=timestamp. Non se pode usar xunto a $1filterbots.", + "apihelp-query+allimages-param-user": "Amosar só ficheiros subidos por este usuario. Só pode usarse con $1sort=timestamp. Non se pode usar xunto a $1filterbots.", "apihelp-query+allimages-param-filterbots": "Como filtrar ficheiros subidos por bots. Só pode usarse con $1sort=timestamp. Non pode usarse xunto con $1user.", "apihelp-query+allimages-param-mime": "Que tipos MIME buscar, por exemplo imaxe/jpeg.", "apihelp-query+allimages-param-limit": "Cantas imaxes mostar en total.", - "apihelp-query+allimages-example-B": "Mostrar unha lista de ficheiros que comezan por B.", - "apihelp-query+allimages-example-recent": "Mostrar unha lista de ficheiros subidos recentemente, similares a [[Special:NewFiles]].", - "apihelp-query+allimages-example-mimetypes": "Mostrar unha lista de ficheiros con tipo MIME image/png ou image/gif", + "apihelp-query+allimages-example-B": "Amosar unha lista de ficheiros que comezan por B.", + "apihelp-query+allimages-example-recent": "Amosar unha lista de ficheiros subidos recentemente, similares a [[Special:NewFiles]].", + "apihelp-query+allimages-example-mimetypes": "Amosar unha lista de ficheiros con tipo MIME image/png ou image/gif", "apihelp-query+allimages-example-generator": "Mostar información sobre catro ficheiros que comecen pola letra T.", "apihelp-query+alllinks-summary": "Numerar tódalas ligazóns que apuntan a un nome de espazos determinado.", "apihelp-query+alllinks-param-from": "Título da ligazón na que comezar a enumerar.", "apihelp-query+alllinks-param-to": "Título da ligazón na que rematar de enumerar.", "apihelp-query+alllinks-param-prefix": "Buscar tódolos títulos ligados que comezan con este valor.", - "apihelp-query+alllinks-param-unique": "Mostrar só títulos ligados distintos. Non pode usarse con $1prop=ids.\nCando se usa como xenerador, produce páxinas obxectivo no canto de páxinas fonte.", + "apihelp-query+alllinks-param-unique": "Amosar só títulos ligados distintos. Non pode usarse con $1prop=ids.\nCando se usa como xerador, produce páxinas obxectivo no canto de páxinas fonte.", "apihelp-query+alllinks-param-prop": "Que partes de información incluír:", "apihelp-query+alllinks-paramvalue-prop-ids": "Engade o ID da páxina da ligazón (non pode usarse con $1unique).", "apihelp-query+alllinks-paramvalue-prop-title": "Engade o título da ligazón.", @@ -489,7 +489,7 @@ "apihelp-query+allmessages-param-title": "Nome de páxina a usar como contexto cando se analice a mensaxe (para a opción $1enableparser)", "apihelp-query+allmessages-param-prefix": "Devolver mensaxes con este prefixo.", "apihelp-query+allmessages-example-ipb": "Mostar mensaxes que comecen por ipb-.", - "apihelp-query+allmessages-example-de": "Mostrar mensaxes august e mainpage en Alemán.", + "apihelp-query+allmessages-example-de": "Amosar mensaxes august e mainpage en Alemán.", "apihelp-query+allpages-summary": "Numerar tódalas páxinas secuencialmente nun espazo de nomes determinado.", "apihelp-query+allpages-param-from": "Título da páxina na que comezar a enumerar.", "apihelp-query+allpages-param-to": "Título da páxina na que rematar de enumerar.", @@ -503,16 +503,16 @@ "apihelp-query+allpages-param-prfiltercascade": "Filtrar proteccións baseadas en cascada (ignoradas se $1prtype non ten valor).", "apihelp-query+allpages-param-limit": "Número total de páxinas a devolver.", "apihelp-query+allpages-param-dir": "Dirección na cal listar.", - "apihelp-query+allpages-param-filterlanglinks": "Filtro baseado en si unha páxina ten ligazóns de lingua. Decátese de que esto pode non considerar as ligazóns de lingua engadidas polas extensións.", + "apihelp-query+allpages-param-filterlanglinks": "Filtro baseado en se unha páxina ten ligazóns de lingua. Decátese de que isto pode non considerar as ligazóns de lingua engadidas polas extensións.", "apihelp-query+allpages-param-prexpiry": "Que finalización de protección pola que filtrar a páxina:\n;indefinida: Só obter páxinas coa finalización de protección indefinida.\n;definite: Só obter páxinas cunha finalización de protección definida.\n;all: Obter páxinas con calquera finalización de protección.", - "apihelp-query+allpages-example-B": "Mostrar unha lista de páxinas que comezan pola letra B.", - "apihelp-query+allpages-example-generator": "Mostrar inforfmación sobre 4 páxinas que comecen pola letra T.", + "apihelp-query+allpages-example-B": "Amosar unha lista de páxinas que comezan pola letra B.", + "apihelp-query+allpages-example-generator": "Amosar información sobre 4 páxinas que comecen pola letra T.", "apihelp-query+allpages-example-generator-revisions": "Motrar o contido das dúas primeiras páxinas que non sexan redirección que comecen por Re.", "apihelp-query+allredirects-summary": "Lista tódalas redireccións a un espazo de nomes.", "apihelp-query+allredirects-param-from": "Título da redirección na que comezar a enumerar.", "apihelp-query+allredirects-param-to": "Título da redirección na que rematar de enumerar.", "apihelp-query+allredirects-param-prefix": "Buscar todas as páxinas que comecen con este valor.", - "apihelp-query+allredirects-param-unique": "Só mostrar páxinas obxectivo distintas. Non pode usarse con $1prop=ids|fragment|interwiki.\nCando se usa como xenerador, produce páxinas obxectivo no canto de páxinas fonte.", + "apihelp-query+allredirects-param-unique": "Só amosar páxinas obxectivo distintas. Non pode usarse con $1prop=ids|fragment|interwiki.\nCando se usa como xerador, produce páxinas obxectivo no canto de páxinas fonte.", "apihelp-query+allredirects-param-prop": "Que información incluír:", "apihelp-query+allredirects-paramvalue-prop-ids": "Engade o ID da páxina da redirección (non pode usarse con $1unique).", "apihelp-query+allredirects-paramvalue-prop-title": "Engade o título da redirección.", @@ -544,7 +544,7 @@ "apihelp-query+alltransclusions-param-from": "Título da transclusión na que comezar a enumerar.", "apihelp-query+alltransclusions-param-to": "Título da transclusión na que rematar de enumerar.", "apihelp-query+alltransclusions-param-prefix": "Buscar todos os títulos transcluídos que comezan con este valor.", - "apihelp-query+alltransclusions-param-unique": "Mostrar só títulos transcluídos distintos. Non pode usarse con $1prop=ids.\nCando se usa como xenerador, produce páxinas obxectivo no canto de páxinas fonte.", + "apihelp-query+alltransclusions-param-unique": "Amosar só títulos transcluídos distintos. Non pode usarse con $1prop=ids.\nCando se usa como xerador, produce páxinas obxectivo no canto de páxinas fonte.", "apihelp-query+alltransclusions-param-prop": "Que partes de información incluír:", "apihelp-query+alltransclusions-paramvalue-prop-ids": "Engade o ID da páxina da páxina transcluída (non pode usarse con $1unique).", "apihelp-query+alltransclusions-paramvalue-prop-title": "Engade o título da transclusión.", @@ -590,7 +590,7 @@ "apihelp-query+backlinks-param-filterredir": "Como filtrar as redireccións. Se o valor é nonredirects cando $1redirect está activa, só se aplica ó segundo nivel.", "apihelp-query+backlinks-param-limit": "Cantas páxinas devolver. Se $1redirect está activa, aplícase o límite a cada nivel de forma separada (isto significa que poden devolverse ata 2 * $1limit resultados).", "apihelp-query+backlinks-param-redirect": "Se a ligazón sobre unha páxina é unha redirección, atopa tamén todas as páxinas que ligan con esa redirección. O límite máximo divídese á metade.", - "apihelp-query+backlinks-example-simple": "Mostrar ligazóns á Main page.", + "apihelp-query+backlinks-example-simple": "Amosar ligazóns á Main page.", "apihelp-query+backlinks-example-generator": "Obter a información das páxinas que ligan á Main page.", "apihelp-query+blocks-summary": "Listar todos os usuarios e direccións IP bloqueados.", "apihelp-query+blocks-param-start": "Selo de tempo para comezar a enumeración.", @@ -610,7 +610,7 @@ "apihelp-query+blocks-paramvalue-prop-reason": "Engade a razón dada para o bloqueo.", "apihelp-query+blocks-paramvalue-prop-range": "Engade o rango de direccións IP afectadas polo bloqueo.", "apihelp-query+blocks-paramvalue-prop-flags": "Etiqueta o bloqueo con (autoblock, anononly, etc.).", - "apihelp-query+blocks-param-show": "Só mostrar elementos correspondentes a eses criterios.\nPor exemplo, para ver só bloques indefinidos en direccións IP, ponga $1show=ip|!temp.", + "apihelp-query+blocks-param-show": "Só amosar elementos correspondentes a eses criterios.\nPor exemplo, para ver só bloques indefinidos en direccións IP, poña $1show=ip|!temp.", "apihelp-query+blocks-example-simple": "Listar bloques.", "apihelp-query+blocks-example-users": "Lista de bloques de usuarios Alice e Bob.", "apihelp-query+categories-summary": "Listar todas as categorías ás que pertencen as páxinas.", @@ -657,7 +657,7 @@ "apihelp-query+contributors-param-rights": "Incluír só ós usuarios cos dereitos dados. Non se inclúen os dereitos dados a grupos implícitos nin autopromocionados como *, usuario ou autoconfirmado.", "apihelp-query+contributors-param-excluderights": "Excluír usuarios cos dereitos dados. Non se inclúen os dereitos dados a grupos implícitos nin autopromocionados como *, usuario ou autoconfirmado.", "apihelp-query+contributors-param-limit": "Número total de contribuidores a devolver.", - "apihelp-query+contributors-example-simple": "Mostrar os contribuidores á páxina Main Page.", + "apihelp-query+contributors-example-simple": "Amosar os contribuidores á páxina Main Page.", "apihelp-query+deletedrevisions-summary": "Obter información sobre as revisións eliminadas.", "apihelp-query+deletedrevisions-extended-description": "Pode usarse de varias formas:\n#Obter as revisións borradas dun conxunto de páxinas, indicando os títulos ou os IDs das páxinas. Ordenado por título e selo de tempo.\n#Obter datos sobre un conxunto de revisións borradas, indicando os seus IDs e os seus IDs de revisión. Ordenado por ID de revisión.", "apihelp-query+deletedrevisions-param-start": "Selo de tempo no que comezar a enumeración. Ignorado cando se está procesando unha lista de IDs de revisións.", @@ -700,7 +700,7 @@ "apihelp-query+embeddedin-param-dir": "Dirección na cal listar.", "apihelp-query+embeddedin-param-filterredir": "Como filtrar para redireccións.", "apihelp-query+embeddedin-param-limit": "Número total de páxinas a devolver.", - "apihelp-query+embeddedin-example-simple": "Mostrar as páxinas que inclúan Template:Stub.", + "apihelp-query+embeddedin-example-simple": "Amosar as páxinas que inclúan Template:Stub.", "apihelp-query+embeddedin-example-generator": "Obter información sobre as páxinas que inclúen Template:Stub.", "apihelp-query+extlinks-summary": "Devolve todas as URLs externas (sen ser interwikis) das páxinas dadas.", "apihelp-query+extlinks-param-limit": "Cantas ligazóns devolver.", @@ -740,9 +740,9 @@ "apihelp-query+filearchive-paramvalue-prop-metadata": "Lista os metadatos Exif da versión da imaxe.", "apihelp-query+filearchive-paramvalue-prop-bitdepth": "Engade a profundidade de bit da versión.", "apihelp-query+filearchive-paramvalue-prop-archivename": "Engade o nome do ficheiro da versión do ficheiro para as versións que non son a última.", - "apihelp-query+filearchive-example-simple": "Mostrar unha lista de tódolos fichieiros eliminados.", + "apihelp-query+filearchive-example-simple": "Amosar unha lista de tódolos fichieiros eliminados.", "apihelp-query+filerepoinfo-summary": "Devolver a meta información sobre os repositorios de imaxes configurados na wiki.", - "apihelp-query+filerepoinfo-param-prop": "Que propiedades do repositorio mostrar (pode haber máis dispoñible nalgunhas wikis):\n;apiurl:URL ó API do repositorio - útil para obter información das imaxes no host.\n;name:A clave do repositorio - usada p. ex. nas variables de retorno de [[mw:Special:MyLanguage/Manual:$wgForeignFileRepos|$wgForeignFileRepos]] e [[Special:ApiHelp/query+imageinfo|imageinfo]]\n;displayname:O nome lexible do wiki repositorio.\n;rooturl:URL raíz dos camiños de imaxe.\n;local:Se o repositorio é o repositorio local ou non.", + "apihelp-query+filerepoinfo-param-prop": "Que propiedades obter do repositorio (as propiedades dispoñibles poden variar noutras wikis).", "apihelp-query+filerepoinfo-example-simple": "Obter infomación sobre os repositorios de ficheiros", "apihelp-query+fileusage-summary": "Atopar tódalas páxinas que usan os ficheiros dados.", "apihelp-query+fileusage-param-prop": "Que propiedades obter:", @@ -750,8 +750,8 @@ "apihelp-query+fileusage-paramvalue-prop-title": "Título de cada páxina.", "apihelp-query+fileusage-paramvalue-prop-redirect": "Marca de se a páxina é unha redirección.", "apihelp-query+fileusage-param-namespace": "Só incluír páxinas nestes espazos de nomes.", - "apihelp-query+fileusage-param-limit": "Cantos mostrar.", - "apihelp-query+fileusage-param-show": "Mostrar só elementos que cumpren estes criterios:\n;redirect:Só mostra redireccións.\n;!redirect:Só mostra as que non son redireccións.", + "apihelp-query+fileusage-param-limit": "Cantos devolver.", + "apihelp-query+fileusage-param-show": "Amosar só elementos que cumpren estes criterios:\n;redirect:Só amosa redireccións.\n;!redirect:Só amosa as que non son redireccións.", "apihelp-query+fileusage-example-simple": "Obter unha lista de páxinas usando [[:File:Example.jpg]]", "apihelp-query+fileusage-example-generator": "Obter infomación sobre páxinas que usan [[:File:Example.jpg]]", "apihelp-query+imageinfo-summary": "Devolve información de ficheiros e historial de subidas.", @@ -804,7 +804,7 @@ "apihelp-query+imageusage-param-filterredir": "Como filtrar redireccións. Se se fixa a non redirección cando está activo $1redirect, isto só se aplica ó segundo nivel.", "apihelp-query+imageusage-param-limit": "Cantas páxinas devolver. Se $1redirect está activa, aplícase o límite a cada nivel de forma separada (isto significa que poden devolverse ata 2 * $1limit resultados).", "apihelp-query+imageusage-param-redirect": "Se a ligazón sobre unha páxina é unha redirección, atopa tamén todas as páxinas que ligan con esa redirección. O límite máximo divídese á metade.", - "apihelp-query+imageusage-example-simple": "Mostrar as páxinas que usan [[:File:Albert Einstein Head.jpg]].", + "apihelp-query+imageusage-example-simple": "Amosar as páxinas que usan [[:File:Albert Einstein Head.jpg]].", "apihelp-query+imageusage-example-generator": "Obter información sobre as páxinas que usan [[:File:Albert Einstein Head.jpg]].", "apihelp-query+info-summary": "Obter información básica da páxina.", "apihelp-query+info-param-prop": "Que propiedades adicionais obter:", @@ -867,7 +867,7 @@ "apihelp-query+langlinks-param-inlanguagecode": "Código de lingua para nomes de lingua localizados.", "apihelp-query+langlinks-example-simple": "Obter ligazóns interlingua da páxina Main Page.", "apihelp-query+links-summary": "Devolve todas as ligazóns das páxinas indicadas.", - "apihelp-query+links-param-namespace": "Mostra ligazóns só neste espazo de nomes.", + "apihelp-query+links-param-namespace": "Amosa ligazóns só neste espazo de nomes.", "apihelp-query+links-param-limit": "Cantas ligazóns devolver.", "apihelp-query+links-param-titles": "Listar só as ligazóns a eses títulos. Útil para verificar se unha páxina concreta liga a un título determinado.", "apihelp-query+links-param-dir": "Dirección na cal listar.", @@ -880,8 +880,8 @@ "apihelp-query+linkshere-paramvalue-prop-title": "Título de cada páxina.", "apihelp-query+linkshere-paramvalue-prop-redirect": "Marca de se a páxina é unha redirección.", "apihelp-query+linkshere-param-namespace": "Só incluír páxinas nestes espazos de nomes.", - "apihelp-query+linkshere-param-limit": "Cantos mostrar.", - "apihelp-query+linkshere-param-show": "Mostrar só elementos que cumpren estes criterios:\n;redirect:Só mostra redireccións.\n;!redirect:Só mostra as que non son redireccións.", + "apihelp-query+linkshere-param-limit": "Cantos devolver.", + "apihelp-query+linkshere-param-show": "Amosar só elementos que cumpren estes criterios:\n;redirect:Só amosa redireccións.\n;!redirect:Só amosa as que non son redireccións.", "apihelp-query+linkshere-example-simple": "Obter unha lista que ligan á [[Main Page]]", "apihelp-query+linkshere-example-generator": "Obter a información das páxinas que ligan á [[Main Page]].", "apihelp-query+logevents-summary": "Obter os eventos dos rexistros.", @@ -896,8 +896,8 @@ "apihelp-query+logevents-paramvalue-prop-parsedcomment": "Engade o comentario analizado do evento.", "apihelp-query+logevents-paramvalue-prop-details": "Lista detalles adicionais do evento.", "apihelp-query+logevents-paramvalue-prop-tags": "Lista as etiquetas do evento.", - "apihelp-query+logevents-param-type": "Filtrar as entradas do rexistro para mostrar só as deste tipo.", - "apihelp-query+logevents-param-action": "Filtrar accións no rexistro para mostrar só esta acción. Ignora $1type. Na lista de posibles valores, valores coa máscara asterisco como action/* poden ter diferentes cadeas despois da barra (/).", + "apihelp-query+logevents-param-type": "Filtrar as entradas do rexistro para amosar só as deste tipo.", + "apihelp-query+logevents-param-action": "Filtrar accións no rexistro para amosar só esta acción. Ignora $1type. Na lista de posibles valores, valores coa máscara asterisco como action/* poden ter diferentes cadeas despois da barra (/).", "apihelp-query+logevents-param-start": "Selo de tempo no que comezar a enumeración.", "apihelp-query+logevents-param-end": "Selo de tempo para rematar a enumeración.", "apihelp-query+logevents-param-user": "Filtrar entradas ás feitas polo usuario indicado.", @@ -913,7 +913,7 @@ "apihelp-query+pageprops-summary": "Obter varias propiedades de páxina definidas no contido da páxina.", "apihelp-query+pageprops-param-prop": "Listar só estas propiedades de páxina ([[Special:ApiHelp/query+pagepropnames|action=query&list=pagepropnames]] devolve os nomes das propiedades de páxina usados). Útil para verificar se as páxinas usan unha determinada propiedade de páxina.", "apihelp-query+pageprops-example-simple": "Obter as propiedades para as páxinas Main Page e MediaWiki", - "apihelp-query+pageswithprop-summary": "Mostrar a lista de páxinas que empregan unha propiedade determinada.", + "apihelp-query+pageswithprop-summary": "Amosar a lista de páxinas que empregan unha propiedade determinada.", "apihelp-query+pageswithprop-param-propname": "Propiedade de páxina para a que enumerar as páxinas ([[Special:ApiHelp/query+pagepropnames|action=query&list=pagepropnames]] devolve os nomes das propiedades de páxina en uso).", "apihelp-query+pageswithprop-param-prop": "Que información incluír:", "apihelp-query+pageswithprop-paramvalue-prop-ids": "Engade o ID da páxina.", @@ -933,7 +933,7 @@ "apihelp-query+prefixsearch-param-profile": "Buscar o perfil a usar.", "apihelp-query+protectedtitles-summary": "Listar todos os títulos protexidos en creación.", "apihelp-query+protectedtitles-param-namespace": "Só listar títulos nestes espazos de nomes.", - "apihelp-query+protectedtitles-param-level": "Só listar títulos con estos niveis de protección.", + "apihelp-query+protectedtitles-param-level": "Só listar títulos con estes niveis de protección.", "apihelp-query+protectedtitles-param-limit": "Número total de páxinas a devolver.", "apihelp-query+protectedtitles-param-start": "Comezar a listar neste selo de tempo de protección.", "apihelp-query+protectedtitles-param-end": "Rematar de listar neste selo de tempo de protección.", @@ -982,9 +982,9 @@ "apihelp-query+recentchanges-paramvalue-prop-tags": "Lista as etiquetas da entrada.", "apihelp-query+recentchanges-paramvalue-prop-sha1": "Engade o control de contido para as entradas asociadas a unha revisión.", "apihelp-query+recentchanges-param-token": "Usar [[Special:ApiHelp/query+tokens|action=query&meta=tokens]] no canto diso.", - "apihelp-query+recentchanges-param-show": "Só mostrar elementos que cumpran esos criterios. Por exemplo, para ver só edicións menores feitas por usuarios conectados, activar $1show=minor|!anon.", + "apihelp-query+recentchanges-param-show": "Só amosar elementos que cumpran eses criterios. Por exemplo, para ver só edicións menores feitas por usuarios conectados, activar $1show=minor|!anon.", "apihelp-query+recentchanges-param-limit": "Número total de páxinas a devolver.", - "apihelp-query+recentchanges-param-type": "Que tipos de cambios mostrar.", + "apihelp-query+recentchanges-param-type": "Que tipos de cambios amosar.", "apihelp-query+recentchanges-param-toponly": "Listar só cambios que son a última revisión.", "apihelp-query+recentchanges-param-generaterevisions": "Cando é usado como xerador, xera identificadore de revisión no canto de títulos. As entradas de modificacións recentes sen identificadores de revisión asociados (p. ex. a maioría das entradas de rexistro) non xerarán nada.", "apihelp-query+recentchanges-example-simple": "Listar cambios recentes.", @@ -996,7 +996,7 @@ "apihelp-query+redirects-paramvalue-prop-fragment": "Fragmento de cada redirección, se hai algún.", "apihelp-query+redirects-param-namespace": "Só incluir páxinas nestes espacios de nomes.", "apihelp-query+redirects-param-limit": "Cantos redireccións devolver.", - "apihelp-query+redirects-param-show": "Só mostrar elementos que cumpran estos criterios:\n;fragment:Só mostrar redireccións que teñan un fragmento.\n;!fragment:Só mostrar redireccións que non teñan un fragmento.", + "apihelp-query+redirects-param-show": "Só amosar elementos que cumpran estes criterios:\n;fragment:Só amosar redireccións que teñan un fragmento.\n;!fragment:Só amosar redireccións que non teñan un fragmento.", "apihelp-query+redirects-example-simple": "Obter unha lista de redireccións á [[Main Page]]", "apihelp-query+redirects-example-generator": "Obter información sobre tódalas redireccións á [[Main Page]]", "apihelp-query+revisions-summary": "Obter información da revisión.", @@ -1011,12 +1011,12 @@ "apihelp-query+revisions-param-tag": "Só listar revisións marcadas con esta etiqueta.", "apihelp-query+revisions-param-token": "Que identificadores obter para cada revisión.", "apihelp-query+revisions-example-content": "Obter datos con contido da última revisión dos títulos API e Main Page.", - "apihelp-query+revisions-example-last5": "Mostrar as cinco últimas revisión da Páxina Principal.", + "apihelp-query+revisions-example-last5": "Amosar as cinco últimas revisión da Páxina Principal.", "apihelp-query+revisions-example-first5": "Mostar as cinco primeiras revisións da Páxina Principal.", - "apihelp-query+revisions-example-first5-after": "Mostrar as cinco primeiras revisións da Páxina Principal feitas despois de 2006-05-01.", - "apihelp-query+revisions-example-first5-not-localhost": "Mostrar as cinco primeiras revisións da Páxina Principal que non foron feitas polo usuario anónimo 127.0.0.1.", - "apihelp-query+revisions-example-first5-user": "Mostrar as cinco primeiras revisión da Páxina Principal feitas polo usuario MediaWiki default.", - "apihelp-query+revisions+base-param-prop": "Que propiedades mostrar para cada modificación:", + "apihelp-query+revisions-example-first5-after": "Amosar as cinco primeiras revisións da Páxina Principal feitas despois de 2006-05-01.", + "apihelp-query+revisions-example-first5-not-localhost": "Amosar as cinco primeiras revisións da Páxina Principal que non foron feitas polo usuario anónimo 127.0.0.1.", + "apihelp-query+revisions-example-first5-user": "Amosar as cinco primeiras revisión da Páxina Principal feitas polo usuario MediaWiki default.", + "apihelp-query+revisions+base-param-prop": "Que propiedades amosar para cada modificación:", "apihelp-query+revisions+base-paramvalue-prop-ids": "O identificador da modificación.", "apihelp-query+revisions+base-paramvalue-prop-flags": "Marcas de modificación (menor).", "apihelp-query+revisions+base-paramvalue-prop-timestamp": "O selo de tempo da modificación.", @@ -1116,7 +1116,7 @@ "apihelp-query+tags-paramvalue-prop-active": "Se a etiqueta aínda está a ser usada.", "apihelp-query+tags-example-simple": "Listar as marcas dispoñibles", "apihelp-query+templates-summary": "Devolve todas as páxinas incluídas na páxina indicada.", - "apihelp-query+templates-param-namespace": "Mostrar os modelos só nestes espazos de nomes.", + "apihelp-query+templates-param-namespace": "Amosar os modelos só nestes espazos de nomes.", "apihelp-query+templates-param-limit": "Número de modelos a devolver.", "apihelp-query+templates-param-templates": "Listar só eses modelos. Útil para verificar se unha páxina concreta ten un modelo determinado.", "apihelp-query+templates-param-dir": "Dirección na cal listar.", @@ -1133,11 +1133,11 @@ "apihelp-query+transcludedin-paramvalue-prop-title": "Título de cada páxina.", "apihelp-query+transcludedin-paramvalue-prop-redirect": "Marca si a páxina é unha redirección.", "apihelp-query+transcludedin-param-namespace": "Só incluir páxinas nestes espacios de nomes.", - "apihelp-query+transcludedin-param-limit": "Cantos mostrar.", - "apihelp-query+transcludedin-param-show": "Mostrar só elementos que cumpren estes criterios:\n;redirect:Só mostra redireccións.\n;!redirect:Só mostra as que non son redireccións.", + "apihelp-query+transcludedin-param-limit": "Cantos devolver.", + "apihelp-query+transcludedin-param-show": "Amosar só elementos que cumpren estes criterios:\n;redirect:Só amosa redireccións.\n;!redirect:Só amosa as que non son redireccións.", "apihelp-query+transcludedin-example-simple": "Obter unha lista de páxinas que inclúen a Main Page.", "apihelp-query+transcludedin-example-generator": "Obter información sobre as páxinas que inclúen Main Page.", - "apihelp-query+usercontribs-summary": "Mostrar tódalas edicións dun usuario.", + "apihelp-query+usercontribs-summary": "Amosar tódalas edicións dun usuario.", "apihelp-query+usercontribs-param-limit": "Máximo número de contribucións a mostar.", "apihelp-query+usercontribs-param-start": "Selo de tempo de comezo ó que volver.", "apihelp-query+usercontribs-param-end": "Selo de tempo de fin ó que volver.", @@ -1156,11 +1156,11 @@ "apihelp-query+usercontribs-paramvalue-prop-flags": "Engade os indicadores da modificación.", "apihelp-query+usercontribs-paramvalue-prop-patrolled": "Marca as modificacións vixiadas.", "apihelp-query+usercontribs-paramvalue-prop-tags": "Lista as etiquetas da modificación.", - "apihelp-query+usercontribs-param-show": "Só mostrar elementos que cumpran estos criterios, p.ex. só edicións menores: $2show=!minor.\n\nSe está fixado $2show=patrolled ou $2show=!patrolled, as modificacións máis antigas que [[mw:Special:MyLanguage/Manual:$wgRCMaxAge|$wgRCMaxAge]] ($1 {{PLURAL:$1|segundo|segundos}}) non se mostrarán.", + "apihelp-query+usercontribs-param-show": "Só amosar elementos que cumpran estes criterios, p.ex. só edicións menores: $2show=!minor.\n\nSe está fixado $2show=patrolled ou $2show=!patrolled, as modificacións máis antigas que [[mw:Special:MyLanguage/Manual:$wgRCMaxAge|$wgRCMaxAge]] ($1 {{PLURAL:$1|segundo|segundos}}) non se amosarán.", "apihelp-query+usercontribs-param-tag": "Só listar revisións marcadas con esta etiqueta.", "apihelp-query+usercontribs-param-toponly": "Listar só cambios que son a última revisión.", - "apihelp-query+usercontribs-example-user": "Mostrar as contribucións do usuario Exemplo.", - "apihelp-query+usercontribs-example-ipprefix": "Mostrar contribucións de tódalas direccións IP que comezan por 192.0.2..", + "apihelp-query+usercontribs-example-user": "Amosar as contribucións do usuario Exemplo.", + "apihelp-query+usercontribs-example-ipprefix": "Amosar contribucións de tódalas direccións IP que comezan por 192.0.2..", "apihelp-query+userinfo-summary": "Obter información sobre o usuario actual.", "apihelp-query+userinfo-param-prop": "Que pezas de información incluír:", "apihelp-query+userinfo-paramvalue-prop-blockinfo": "Marca se o usuario actual está bloqueado, por que, e por que razón.", @@ -1208,7 +1208,7 @@ "apihelp-query+watchlist-param-namespace": "Filtrar os cambios a só os espazos de nomes indicados.", "apihelp-query+watchlist-param-user": "Só listar cambios deste usuario.", "apihelp-query+watchlist-param-excludeuser": "Non listar cambios deste usuario.", - "apihelp-query+watchlist-param-limit": "Cantos resultados totais mostrar por petición.", + "apihelp-query+watchlist-param-limit": "Cantos resultados totais amosar por petición.", "apihelp-query+watchlist-param-prop": "Que propiedades adicionais obter:", "apihelp-query+watchlist-paramvalue-prop-ids": "Engade os identificadores das revisións e os identificadores das páxinas.", "apihelp-query+watchlist-paramvalue-prop-title": "Engade o título da páxina.", @@ -1222,8 +1222,8 @@ "apihelp-query+watchlist-paramvalue-prop-sizes": "Engade o tamaño antigo e novo da páxina.", "apihelp-query+watchlist-paramvalue-prop-notificationtimestamp": "Engade o selo de tempo da última vez en que o usuario foi avisado da modificación.", "apihelp-query+watchlist-paramvalue-prop-loginfo": "Engade información do rexistro cando sexa axeitado.", - "apihelp-query+watchlist-param-show": "Só mostrar elementos que cumpran esos criterios. Por exemplo, para ver só edicións menores feitas por usuarios conectados, activar $1show=minor|!anon.", - "apihelp-query+watchlist-param-type": "Que tipos de cambios mostrar:", + "apihelp-query+watchlist-param-show": "Só amosar elementos que cumpran eses criterios. Por exemplo, para ver só edicións menores feitas por usuarios conectados, activar $1show=minor|!anon.", + "apihelp-query+watchlist-param-type": "Que tipos de cambios amosar:", "apihelp-query+watchlist-paramvalue-type-edit": "Edicións comúns a páxinas.", "apihelp-query+watchlist-paramvalue-type-external": "Cambios externos.", "apihelp-query+watchlist-paramvalue-type-new": "Creacións de páxinas.", @@ -1239,10 +1239,10 @@ "apihelp-query+watchlist-example-wlowner": "Listar a última revisión das páxinas cambiadas recentemente da lista de vixiancia do usuario Example.", "apihelp-query+watchlistraw-summary": "Obter todas as páxinas da lista de vixiancia do usuario actual.", "apihelp-query+watchlistraw-param-namespace": "Só listar páxinas nestes espazos de nomes.", - "apihelp-query+watchlistraw-param-limit": "Cantos resultados totais mostrar por petición.", + "apihelp-query+watchlistraw-param-limit": "Cantos resultados totais amosar por petición.", "apihelp-query+watchlistraw-param-prop": "Que propiedades adicionais obter:", "apihelp-query+watchlistraw-paramvalue-prop-changed": "Engade o selo de tempo da última notificación ó usuario dunha modificación.", - "apihelp-query+watchlistraw-param-show": "Só listar os elementos que cumplen estos criterios.", + "apihelp-query+watchlistraw-param-show": "Só listar os elementos que cumpren estes criterios.", "apihelp-query+watchlistraw-param-owner": "Usado con $1token para acceder á lista de páxinas de vixiancia doutro usuario.", "apihelp-query+watchlistraw-param-token": "Identificador de seguridade (dispoñible nas [[Special:Preferences#mw-prefsection-watchlist|preferencias]] de usuario) para permitir o acceso a outros á súa páxina de vixiancia.", "apihelp-query+watchlistraw-param-dir": "Dirección na cal listar.", @@ -1263,7 +1263,7 @@ "apihelp-revisiondelete-param-target": "Título de páxina para o borrado da revisión, se requerido para o tipo.", "apihelp-revisiondelete-param-ids": "Identificadores para as revisións a ser borradas.", "apihelp-revisiondelete-param-hide": "Que ocultar para cada revisión.", - "apihelp-revisiondelete-param-show": "Que mostrar para cada revisión.", + "apihelp-revisiondelete-param-show": "Que amosar para cada revisión.", "apihelp-revisiondelete-param-suppress": "Eliminar os datos dos administradores así coma dos doutros.", "apihelp-revisiondelete-param-reason": "Razón para o borrado ou restaurado.", "apihelp-revisiondelete-param-tags": "Etiquetas a aplicar á entrada no rexistro de borrados.", diff --git a/includes/api/i18n/he.json b/includes/api/i18n/he.json index cb018dc696..4bfb522552 100644 --- a/includes/api/i18n/he.json +++ b/includes/api/i18n/he.json @@ -68,20 +68,20 @@ "apihelp-compare-param-fromtitle": "כותרת ראשונה להשוואה.", "apihelp-compare-param-fromid": "מס׳ זיהוי של הדף הראשון להשוואה.", "apihelp-compare-param-fromrev": "גרסה ראשונה להשוואה.", - "apihelp-compare-param-fromtext": "להשתמש בטקסט הזה במקום תוכן הגרסה שהוגדרה על־ידי fromtitle, fromid או fromrev.", - "apihelp-compare-param-fromsection": "יש להשתמש רק בפסקה שצוינה בתוכן של הפרמטר 'from'.", "apihelp-compare-param-frompst": "לעשות התמרה לפני שמירה ב־fromtext.", + "apihelp-compare-param-fromtext": "להשתמש בטקסט הזה במקום תוכן הגרסה שהוגדרה על־ידי fromtitle, fromid או fromrev.", "apihelp-compare-param-fromcontentmodel": "מודל התוכן של fromtext. אם זה לא סופק, ייעשה ניחוש על סמך פרמטרים אחרים.", "apihelp-compare-param-fromcontentformat": "תסדיר הסדרת תוכן של fromtext.", + "apihelp-compare-param-fromsection": "יש להשתמש רק בפסקה שצוינה בתוכן של הפרמטר 'from'.", "apihelp-compare-param-totitle": "כותרת שנייה להשוואה.", "apihelp-compare-param-toid": "מס׳ מזהה של הדף השני להשוואה.", "apihelp-compare-param-torev": "גרסה שנייה להשוואה.", "apihelp-compare-param-torelative": "להשתמש בגרסה יחסית לגרסה שהוסקה מfromtitle, fromid או fromrev. לכל אפשריות ה־\"to\" האחרות לא תהיה השפעה.", - "apihelp-compare-param-totext": "להשתמש בטקסט הזה במקום התוכן של הגרסה שהוגדר ב־totitle, toid or torev.", - "apihelp-compare-param-tosection": "יש להשתמש רק בפסקה שצוינה בתוכן של הפרמטר 'to'.", "apihelp-compare-param-topst": "לעשות התמרה לפני שמירה ב־totext.", + "apihelp-compare-param-totext": "להשתמש בטקסט הזה במקום התוכן של הגרסה שהוגדר ב־totitle, toid or torev.", "apihelp-compare-param-tocontentmodel": "מודל התוכן של totext. אם זה לא סופק, ייעשה ניחוש על סמך פרמטרים אחרים.", "apihelp-compare-param-tocontentformat": "תסדיר הסדרת תוכן של fromtext.", + "apihelp-compare-param-tosection": "יש להשתמש רק בפסקה שצוינה בתוכן של הפרמטר 'to'.", "apihelp-compare-param-prop": "אילו פריטי מידע לקבל.", "apihelp-compare-paramvalue-prop-diff": "ה־HTML של ההשוואה.", "apihelp-compare-paramvalue-prop-diffsize": "גודל ה־HTML של ההשוואה, בבתים.", diff --git a/includes/api/i18n/hu.json b/includes/api/i18n/hu.json index 4451f194e9..1211693b63 100644 --- a/includes/api/i18n/hu.json +++ b/includes/api/i18n/hu.json @@ -10,7 +10,7 @@ "Dj" ] }, - "apihelp-main-extended-description": "
\n* [[mw:Special:MyLanguage/API:Main_page|Dokumentáció]]\n* [[mw:Special:MyLanguage/API:FAQ|GYIK]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api Levelezőlista]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce API-bejelentések]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R Hibabejelentések és kérések]\n
\nStátusz: Minden ezen a lapon látható funkciónak működnie kell, de az API jelenleg is aktív fejlesztés alatt áll, és bármikor változhat. Iratkozz fel a [https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ mediawiki-api-announce levelezőlistára] a frissítések követéséhez.\n\nHibás kérések: Ha az API hibás kérést kap, egy HTTP-fejlécet küld vissza „MediaWiki-API-Error” kulccsal, és a fejléc értéke és a visszaküldött hibakód ugyanarra az értékre lesz állítva. További információért lásd: [[mw:Special:MyLanguage/API:Errors_and_warnings|API: Hibák és figyelmeztetések]].\n\n

Tesztelés: Az API-kérések könnyebb teszteléséhez használható az [[Special:ApiSandbox|API-homokozó]].

", + "apihelp-main-extended-description": "
\n* [[mw:Special:MyLanguage/API:Main_page|Dokumentáció]]\n* [[mw:Special:MyLanguage/API:FAQ|GYIK]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api Levelezőlista]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce API-bejelentések]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R Hibabejelentések és kérések]\n
\nÁllapot: A MediaWiki API egy érett és stabil interfész, ami aktív támogatásban és fejlesztésben részesül. Bár próbáljuk elkerülni, de néha szükség van visszafelé nem kompatibilis változtatásokra; iratkozz fel a [https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ mediawiki-api-announce levelezőlistára] a frissítések követéséhez.\n\nHibás kérések: Ha az API hibás kérést kap, egy HTTP-fejlécet küld vissza „MediaWiki-API-Error” kulccsal, és a fejléc értéke és a visszaküldött hibakód ugyanarra az értékre lesz állítva. További információért lásd: [[mw:Special:MyLanguage/API:Errors_and_warnings|API: Hibák és figyelmeztetések]].\n\n

Tesztelés: Az API-kérések könnyebb teszteléséhez használható az [[Special:ApiSandbox|API-homokozó]].

", "apihelp-main-param-action": "Milyen műveletet hajtson végre.", "apihelp-main-param-format": "A kimenet formátuma.", "apihelp-main-param-smaxage": "Az s-maxage gyorsítótár-vezérlő HTTP-fejléc beállítása ennyi másodpercre. A hibák soha nincsenek gyorsítótárazva.", diff --git a/includes/api/i18n/it.json b/includes/api/i18n/it.json index ceb2d42c20..5a76e4c890 100644 --- a/includes/api/i18n/it.json +++ b/includes/api/i18n/it.json @@ -688,6 +688,7 @@ "api-help-authmanagerhelper-returnurl": "URL di ritorno per i flussi di autenticazione di terze parti, deve essere assoluto. E' necessario fornirlo, oppure va fornito $1continue.\n\nAlla ricezione di una risposta REDIRECT, in genere si apre un browser o una vista web all'URL specificato redirecttarget per un flusso di autenticazione di terze parti. Quando questo è completato, la terza parte invierà il browser o la vista web a questo URL. Dovresti estrarre qualsiasi parametro POST o della richiesta dall'URL e passarli come un request $1continue a questo modulo API.", "api-help-authmanagerhelper-continue": "Questa richiesta è una continuazione dopo una precedente risposta UI o REDIRECT. È necessario fornirlo, oppure fornire $1returnurl.", "api-help-authmanagerhelper-additional-params": "Questo modulo accetta parametri aggiuntivi a seconda delle richieste di autenticazione disponibili. Utilizza [[Special:ApiHelp/query+authmanagerinfo|action=query&meta=authmanagerinfo]] con amirequestsfor=$1 (o una precedente risposta da questo modulo, se applicabile) per determinare le richieste disponibili e i campi usati da queste.", + "apierror-compare-notext": "Il parametro $1 non può essere usato senza $2.", "apierror-invalidoldimage": "Il parametro oldimage ha un formato non valido.", "apierror-invaliduserid": "L'ID utente $1 non è valido.", "apierror-maxbytes": "Il parametro $1 non può essere più lungo di $2 {{PLURAL:$2|byte}}", diff --git a/includes/api/i18n/ja.json b/includes/api/i18n/ja.json index 399fc1fd17..d943e47148 100644 --- a/includes/api/i18n/ja.json +++ b/includes/api/i18n/ja.json @@ -13,7 +13,8 @@ "Kkairri", "ネイ", "Omotecho", - "Yusuke1109" + "Yusuke1109", + "Suyama" ] }, "apihelp-main-extended-description": "
\n* [[mw:Special:MyLanguage/API:Main_page|説明文書]]\n* [[mw:Special:MyLanguage/API:FAQ|よくある質問]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api メーリングリスト]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce API 告知]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R バグの報告とリクエスト]\n
\n状態: MediaWiki APIは、積極的にサポートされ、改善された成熟した安定したインターフェースです。避けようとはしていますが、時には壊れた変更が加えられるかもしれません。アップデートの通知を受け取るには、[https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ the mediawiki-api-announce メーリングリスト]に参加してください。\n\n誤ったリクエスト: 誤ったリクエストが API に送られた場合、\"MediaWiki-API-Error\" HTTP ヘッダーが送信され、そのヘッダーの値と送り返されるエラーコードは同じ値にセットされます。より詳しい情報は [[mw:Special:MyLanguage/API:Errors_and_warnings|API: Errors and warnings]] を参照してください。\n\n

テスト: API のリクエストのテストは、[[Special:ApiSandbox]]で簡単に行えます。

", @@ -62,16 +63,16 @@ "apihelp-compare-param-fromtitle": "比較する1つ目のページ名。", "apihelp-compare-param-fromid": "比較する1つ目のページID。", "apihelp-compare-param-fromrev": "比較する1つ目の版。", - "apihelp-compare-param-fromtext": "fromtitle, fromid or fromrev で指定された版の内容の代わりに、このテキストを使用します。", - "apihelp-compare-param-fromsection": "'from' の内容のうち指定された節のみを使用します。", - "apihelp-compare-param-frompst": "fromtextに保存前変換を行います。", + "apihelp-compare-param-frompst": "fromtext-{slot}に保存前変換を行います。", + "apihelp-compare-param-fromtext": "fromslots=mainを指定し、代わりにfromtext-main を使用してください。", "apihelp-compare-param-fromcontentmodel": "fromtextのコンテンツモデル。指定されていない場合は、他のパラメータに基づいて推測されます。", + "apihelp-compare-param-fromsection": "'from' の内容のうち指定された節のみを使用します。", "apihelp-compare-param-totitle": "比較する2つ目のページ名。", "apihelp-compare-param-toid": "比較する2つ目のページID。", "apihelp-compare-param-torev": "比較する2つ目の版。", - "apihelp-compare-param-tosection": "'to' の内容のうち指定された節のみを使用します。", "apihelp-compare-param-topst": "totextに保存前変換を行います。", "apihelp-compare-param-tocontentmodel": "totext のコンテンツモデル。指定されていない場合は、他のパラメータに基づいて推測されます。", + "apihelp-compare-param-tosection": "'to' の内容のうち指定された節のみを使用します。", "apihelp-compare-param-prop": "どの情報を取得するか:", "apihelp-compare-paramvalue-prop-diff": "差分HTML。", "apihelp-compare-paramvalue-prop-diffsize": "差分HTMLのサイズ (バイト数)。", @@ -384,13 +385,13 @@ "apihelp-query+allfileusages-paramvalue-prop-ids": "使用しているページのページIDを追加します ($1unique とは同時に使用できません)。", "apihelp-query+allfileusages-paramvalue-prop-title": "ファイルのページ名を追加します。", "apihelp-query+allfileusages-param-limit": "返す項目の総数。", - "apihelp-query+allfileusages-param-dir": "一覧表示する方向。", + "apihelp-query+allfileusages-param-dir": "昇順・降順の別。", "apihelp-query+allfileusages-example-unique": "ユニークなファイルを一覧表示する。", "apihelp-query+allfileusages-example-unique-generator": "ファイル名を、存在しないものに印をつけて、すべて取得する。", "apihelp-query+allfileusages-example-generator": "ファイルを含むページを取得します。", "apihelp-query+allimages-summary": "順次すべての画像を列挙します。", "apihelp-query+allimages-param-sort": "並べ替えに使用するプロパティ。", - "apihelp-query+allimages-param-dir": "一覧表示する方向。", + "apihelp-query+allimages-param-dir": "昇順・降順の別。", "apihelp-query+allimages-param-from": "列挙の始点となる画像タイトル。$1sort=name を指定した場合のみ使用できます。", "apihelp-query+allimages-param-to": "列挙の終点となる画像のページ名。$1sort=name を指定した場合のみ使用できます。", "apihelp-query+allimages-param-start": "列挙の始点となるタイムスタンプ。$1sort=timestamp を指定した場合のみ使用できます。", @@ -417,7 +418,7 @@ "apihelp-query+alllinks-paramvalue-prop-title": "リンクのページ名を追加します。", "apihelp-query+alllinks-param-namespace": "列挙する名前空間。", "apihelp-query+alllinks-param-limit": "返す項目の総数。", - "apihelp-query+alllinks-param-dir": "一覧表示する方向。", + "apihelp-query+alllinks-param-dir": "昇順・降順の別。", "apihelp-query+alllinks-example-B": "B で始まるリンクされたページ (存在しないページも含む)を、リンク元のページIDとともに表示する。", "apihelp-query+alllinks-example-unique": "ユニークなリンクのタイトルを一覧。", "apihelp-query+alllinks-example-unique-generator": "リンクされているページを、存在しないものに印をつけて、すべて取得する。", @@ -442,7 +443,7 @@ "apihelp-query+allpages-param-prtype": "保護されているページに絞り込む。", "apihelp-query+allpages-param-prlevel": "保護レベルで絞り込む ($1type= パラメーターと同時に使用しなければなりません)。", "apihelp-query+allpages-param-limit": "返すページの総数。", - "apihelp-query+allpages-param-dir": "一覧表示する方向。", + "apihelp-query+allpages-param-dir": "昇順・降順の別。", "apihelp-query+allpages-example-B": "B で始まるページの一覧を表示する。", "apihelp-query+allpages-example-generator": "T で始まる4つのページに関する情報を表示する。", "apihelp-query+allpages-example-generator-revisions": "Re で始まる最初の非リダイレクトの2ページの内容を表示する。", @@ -456,7 +457,7 @@ "apihelp-query+allredirects-paramvalue-prop-title": "転送ページのページ名を追加します。", "apihelp-query+allredirects-param-namespace": "列挙する名前空間。", "apihelp-query+allredirects-param-limit": "返す項目の総数。", - "apihelp-query+allredirects-param-dir": "一覧表示する方向。", + "apihelp-query+allredirects-param-dir": "昇順・降順の別。", "apihelp-query+allredirects-example-B": "B で始まる転送先ページ (存在しないページも含む)を、転送元のページIDとともに表示する。", "apihelp-query+allredirects-example-unique": "一意のターゲットページを一覧表示します。", "apihelp-query+allredirects-example-unique-generator": "存在しないものに印をつけて、すべて取得する。", @@ -483,7 +484,7 @@ "apihelp-query+alltransclusions-paramvalue-prop-title": "参照読み込みのページ名を追加します。", "apihelp-query+alltransclusions-param-namespace": "列挙する名前空間。", "apihelp-query+alltransclusions-param-limit": "返す項目の総数。", - "apihelp-query+alltransclusions-param-dir": "一覧表示する方向。", + "apihelp-query+alltransclusions-param-dir": "昇順・降順の別。", "apihelp-query+alltransclusions-example-B": "参照読み込みされているページ (存在しないページも含む) を、参照元のページIDとともに、B で始まるものから一覧表示する。", "apihelp-query+alltransclusions-example-unique-generator": "参照読み込みされたページを、存在しないものに印をつけて、すべて取得する。", "apihelp-query+alltransclusions-example-generator": "参照読み込みを含んでいるページを取得する。", @@ -508,7 +509,7 @@ "apihelp-query+backlinks-param-title": "検索するページ名。$1pageid とは同時に使用できません。", "apihelp-query+backlinks-param-pageid": "検索するページID。$1titleとは同時に使用できません。", "apihelp-query+backlinks-param-namespace": "列挙する名前空間。", - "apihelp-query+backlinks-param-dir": "一覧表示する方向。", + "apihelp-query+backlinks-param-dir": "昇順・降順の別。", "apihelp-query+backlinks-param-limit": "返すページの総数。$1redirect を有効化した場合は、各レベルに対し個別にlimitが適用されます (つまり、最大で 2 * $1limit 件の結果が返されます)。", "apihelp-query+backlinks-example-simple": "Main page へのリンクを表示する。", "apihelp-query+backlinks-example-generator": "Main page にリンクしているページの情報を取得する。", @@ -534,10 +535,12 @@ "apihelp-query+blocks-example-users": "利用者Alice および Bob のブロックを一覧表示する。", "apihelp-query+categories-summary": "ページが属するすべてのカテゴリを一覧表示します。", "apihelp-query+categories-param-prop": "各カテゴリについて取得する追加のプロパティ:", + "apihelp-query+categories-paramvalue-prop-sortkey": "返されるカテゴリのソートキー(sortkey、UTF-8による表示)と人間可読のソートキー (sortkeyprefix) を追加します。", "apihelp-query+categories-paramvalue-prop-timestamp": "カテゴリが追加されたときのタイムスタンプを追加します。", "apihelp-query+categories-paramvalue-prop-hidden": "__HIDDENCAT__で隠されているカテゴリに印を付ける。", "apihelp-query+categories-param-show": "どの種類のカテゴリを表示するか。", "apihelp-query+categories-param-limit": "返すカテゴリの数。", + "apihelp-query+categories-param-dir": "昇順・降順の別。", "apihelp-query+categories-example-simple": "ページ Albert Einstein が属しているカテゴリの一覧を取得する。", "apihelp-query+categories-example-generator": "ページ Albert Einstein で使われているすべてのカテゴリに関する情報を取得する。", "apihelp-query+categoryinfo-summary": "与えられたカテゴリに関する情報を返します。", @@ -562,6 +565,7 @@ "apihelp-query+contributors-summary": "ページへのログインした投稿者の一覧と匿名投稿者の数を取得します。", "apihelp-query+contributors-param-limit": "返す投稿者の数。", "apihelp-query+contributors-example-simple": "Main Page への投稿者を表示する。", + "apihelp-query+deletedrevisions-summary": "削除された版の情報を取得します。", "apihelp-query+deletedrevisions-param-start": "列挙の始点となるタイムスタンプ。版IDの一覧を処理するときには無視されます。", "apihelp-query+deletedrevisions-param-end": "列挙の終点となるタイムスタンプ。版IDの一覧を処理するときには無視されます。", "apihelp-query+deletedrevisions-param-tag": "このタグが付与された版のみ表示します。", @@ -569,6 +573,7 @@ "apihelp-query+deletedrevisions-param-excludeuser": "この利用者による版を一覧表示しない。", "apihelp-query+deletedrevisions-example-titles": "ページ Main Page および Talk:Main Page の削除された版とその内容を一覧表示する。", "apihelp-query+deletedrevisions-example-revids": "削除された版 123456 に関する情報を一覧表示する。", + "apihelp-query+deletedrevs-summary": "削除された版を一覧表示します。", "apihelp-query+deletedrevs-paraminfo-modes": "{{PLURAL:$1|モード}}: $2", "apihelp-query+deletedrevs-param-start": "列挙の始点となるタイムスタンプ。", "apihelp-query+deletedrevs-param-end": "列挙の終点となるタイムスタンプ。", @@ -585,9 +590,16 @@ "apihelp-query+deletedrevs-example-mode3-main": "標準名前空間にある削除された最初の50版を一覧表示する(モード 3)。", "apihelp-query+deletedrevs-example-mode3-talk": "{{ns:talk}}名前空間にある削除された最初の50版を一覧表示する(モード 3)。", "apihelp-query+disabled-summary": "このクエリモジュールは無効化されています。", + "apihelp-query+duplicatefiles-summary": "ハッシュ値に基づいて与えられたファイルの全ての重複ファイルを返します。", + "apihelp-query+duplicatefiles-param-limit": "返す重複ファイルの件数", + "apihelp-query+duplicatefiles-param-dir": "昇順・降順の別。", + "apihelp-query+duplicatefiles-param-localonly": "ローカルに保存されたファイルのみを調べる。", + "apihelp-query+duplicatefiles-example-simple": "[[:File:Albert Einstein Head.jpg]]の重複ファイルを調べる。", + "apihelp-query+duplicatefiles-example-generated": "全ての重複ファイルを調べる。", "apihelp-query+embeddedin-param-title": "検索するページ名。$1pageid とは同時に使用できません。", "apihelp-query+embeddedin-param-pageid": "検索するページID. $1titleとは同時に使用できません。", "apihelp-query+embeddedin-param-namespace": "列挙する名前空間。", + "apihelp-query+embeddedin-param-dir": "昇順・降順の別。", "apihelp-query+embeddedin-param-filterredir": "転送ページを絞り込む方法。", "apihelp-query+embeddedin-param-limit": "返すページの総数。", "apihelp-query+embeddedin-example-simple": "Template:Stub を参照読み込みしているページを表示する。", @@ -609,7 +621,7 @@ "apihelp-query+filearchive-summary": "削除されたファイルをすべて順に列挙します。", "apihelp-query+filearchive-param-from": "列挙の始点となる画像のページ名。", "apihelp-query+filearchive-param-to": "列挙の終点となる画像のページ名。", - "apihelp-query+filearchive-param-dir": "一覧表示する方向。", + "apihelp-query+filearchive-param-dir": "昇順・降順の別。", "apihelp-query+filearchive-param-sha1": "画像の SHA1 ハッシュ値。$1sha1base36 をオーバーライドします。", "apihelp-query+filearchive-param-prop": "どの画像情報を取得するか:", "apihelp-query+filearchive-paramvalue-prop-timestamp": "バージョンがアップロードされたタイムスタンプを追加します。", @@ -622,6 +634,7 @@ "apihelp-query+filearchive-paramvalue-prop-archivename": "非最新バージョンのアーカイブバージョンのファイル名を追加します。", "apihelp-query+filearchive-example-simple": "削除されたファイルの一覧を表示する。", "apihelp-query+filerepoinfo-example-simple": "ファイルリポジトリについての情報を取得します。", + "apihelp-query+fileusage-summary": "与えられたファイルを利用しているすべてのページを返します。", "apihelp-query+fileusage-param-prop": "取得するプロパティ:", "apihelp-query+fileusage-paramvalue-prop-pageid": "各ページのページID。", "apihelp-query+fileusage-paramvalue-prop-title": "各ページのページ名。", @@ -629,6 +642,7 @@ "apihelp-query+fileusage-param-namespace": "この名前空間に含まれるページのみを一覧表示します。", "apihelp-query+fileusage-example-simple": "[[:File:Example.jpg]] を使用しているページの一覧を取得する。", "apihelp-query+fileusage-example-generator": "[[:File:Example.jpg]] を使用しているページの情報を取得する。", + "apihelp-query+imageinfo-summary": "ファイルの情報とアップロード履歴を返します。", "apihelp-query+imageinfo-param-prop": "取得するファイル情報:", "apihelp-query+imageinfo-paramvalue-prop-url": "ファイルと説明ページへのURLを提供します。", "apihelp-query+imageinfo-paramvalue-prop-size": "バイト単位でファイルや高さ、幅、ページ数のサイズを追加します(該当する場合)。", @@ -643,11 +657,13 @@ "apihelp-query+imageinfo-example-simple": "[[:File:Albert Einstein Head.jpg]] の現在のバージョンに関する情報を取得する。", "apihelp-query+images-summary": "与えられたページに含まれるすべてのファイルを返します。", "apihelp-query+images-param-limit": "返す画像の数。", + "apihelp-query+images-param-dir": "昇順・降順の別。", "apihelp-query+images-example-simple": "[[Main Page]] で使用されているファイルの一覧を取得する。", "apihelp-query+images-example-generator": "[[Main Page]] で使用されているファイルに関する情報を取得する。", "apihelp-query+imageusage-param-title": "検索するページ名。$1pageid とは同時に使用できません。", "apihelp-query+imageusage-param-pageid": "検索するページID. $1titleとは同時に使用できません。", "apihelp-query+imageusage-param-namespace": "列挙する名前空間。", + "apihelp-query+imageusage-param-dir": "昇順・降順の別。", "apihelp-query+imageusage-example-simple": "[[:File:Albert Einstein Head.jpg]] を使用しているページを表示する。", "apihelp-query+imageusage-example-generator": "[[:File:Albert Einstein Head.jpg]] を使用しているページに関する情報を取得する。", "apihelp-query+info-summary": "ページの基本的な情報を取得します。", @@ -661,7 +677,7 @@ "apihelp-query+iwbacklinks-param-prop": "取得するプロパティ:", "apihelp-query+iwbacklinks-paramvalue-prop-iwprefix": "インターウィキ接頭辞を追加します。", "apihelp-query+iwbacklinks-paramvalue-prop-iwtitle": "ウィキ間リンクのページ名を追加します。", - "apihelp-query+iwbacklinks-param-dir": "一覧表示する方向。", + "apihelp-query+iwbacklinks-param-dir": "昇順・降順の別。", "apihelp-query+iwbacklinks-example-simple": "[[wikibooks:Test]] へリンクしているページを取得する。", "apihelp-query+iwbacklinks-example-generator": "[[wikibooks:Test]] へリンクしているページの情報を取得する。", "apihelp-query+iwlinks-summary": "ページからのすべてのウィキ間リンクを返します。", @@ -671,15 +687,16 @@ "apihelp-query+iwlinks-param-limit": "返すウィキ間リンクの数。", "apihelp-query+iwlinks-param-prefix": "この接頭辞のウィキ間リンクのみを返す。", "apihelp-query+iwlinks-param-title": "検索するウィキ間リンク。$1 と同時に使用しなければなりません。", - "apihelp-query+iwlinks-param-dir": "一覧表示する方向。", + "apihelp-query+iwlinks-param-dir": "昇順・降順の別。", "apihelp-query+iwlinks-example-simple": "Main Page にあるウィキ間リンクを取得する。", + "apihelp-query+langbacklinks-summary": "与えられた言語間リンクにリンクしているすべてのページを返します。", "apihelp-query+langbacklinks-param-lang": "言語間リンクの言語。", "apihelp-query+langbacklinks-param-title": "検索する言語間リンク。$1lang と同時に使用しなければなりません。", "apihelp-query+langbacklinks-param-limit": "返すページの総数。", "apihelp-query+langbacklinks-param-prop": "取得するプロパティ:", "apihelp-query+langbacklinks-paramvalue-prop-lllang": "言語間リンクの言語コードを追加します。", "apihelp-query+langbacklinks-paramvalue-prop-lltitle": "言語間リンクのページ名を追加します。", - "apihelp-query+langbacklinks-param-dir": "一覧表示する方向。", + "apihelp-query+langbacklinks-param-dir": "昇順・降順の別。", "apihelp-query+langbacklinks-example-simple": "[[:fr:Test]] へリンクしているページを取得する。", "apihelp-query+langbacklinks-example-generator": "[[:fr:Test]] へリンクしているページの情報を取得する。", "apihelp-query+langlinks-summary": "ページからのすべての言語間リンクを返します。", @@ -690,14 +707,16 @@ "apihelp-query+langlinks-paramvalue-prop-autonym": "ネイティブ言語名を追加します。", "apihelp-query+langlinks-param-lang": "この言語コードの言語間リンクのみを返す。", "apihelp-query+langlinks-param-title": "検索するリンク。$1langと同時に使用しなければなりません。", - "apihelp-query+langlinks-param-dir": "一覧表示する方向。", + "apihelp-query+langlinks-param-dir": "昇順・降順の別。", "apihelp-query+langlinks-example-simple": "Main Page にある言語間リンクを取得する。", "apihelp-query+links-summary": "ページからのすべてのリンクを返します。", "apihelp-query+links-param-namespace": "この名前空間へのリンクのみ表示する。", "apihelp-query+links-param-limit": "返すリンクの数。", + "apihelp-query+links-param-dir": "昇順・降順の別。", "apihelp-query+links-example-simple": "Main Page からのリンクを取得する。", "apihelp-query+links-example-generator": "Main Page からリンクされているページに関する情報を取得する。", "apihelp-query+links-example-namespaces": "Main Page からの {{ns:user}} および {{ns:template}} 名前空間へのリンクを取得する。", + "apihelp-query+linkshere-summary": "与えられたページにリンクしているすべてのページを返します。", "apihelp-query+linkshere-param-prop": "取得するプロパティ:", "apihelp-query+linkshere-paramvalue-prop-pageid": "各ページのページID。", "apihelp-query+linkshere-paramvalue-prop-title": "各ページのページ名。", @@ -807,7 +826,7 @@ "apihelp-query+revisions+base-paramvalue-prop-size": "その版の長さ (バイト) 。", "apihelp-query+revisions+base-paramvalue-prop-comment": "その版の利用者によるコメント。", "apihelp-query+revisions+base-paramvalue-prop-parsedcomment": "その版の利用者による、構文解析されたコメント。", - "apihelp-query+revisions+base-paramvalue-prop-content": "その版のテキスト。", + "apihelp-query+revisions+base-paramvalue-prop-content": "各リビジョンスロットの内容。", "apihelp-query+revisions+base-paramvalue-prop-tags": "その版のタグ。", "apihelp-query+revisions+base-param-limit": "返す版の数を制限する。", "apihelp-query+search-summary": "全文検索を行います。", @@ -841,7 +860,7 @@ "apihelp-query+templates-summary": "与えられたページでトランスクルードされているすべてのページを返します。", "apihelp-query+templates-param-namespace": "この名前空間のテンプレートのみ表示する。", "apihelp-query+templates-param-limit": "返すテンプレートの数。", - "apihelp-query+templates-param-dir": "一覧表示する方向。", + "apihelp-query+templates-param-dir": "昇順・降順の別。", "apihelp-query+templates-example-simple": "Main Page で使用されているテンプレートを取得する。", "apihelp-query+templates-example-generator": "Main Page で使用されているテンプレートに関する情報を取得する。", "apihelp-query+templates-example-namespaces": "Main Page でトランスクルードされている {{ns:user}} および {{ns:template}} 名前空間のページを取得する。", @@ -902,7 +921,7 @@ "apihelp-query+watchlistraw-summary": "現在の利用者のウォッチリストにあるすべてのページを取得します。", "apihelp-query+watchlistraw-param-namespace": "この名前空間に含まれるページのみを一覧表示します。", "apihelp-query+watchlistraw-param-prop": "追加で取得するプロパティ:", - "apihelp-query+watchlistraw-param-dir": "一覧表示する方向。", + "apihelp-query+watchlistraw-param-dir": "昇順・降順の別。", "apihelp-query+watchlistraw-example-generator": "現在の利用者のウォッチリスト上のページに関する情報を取得する。", "apihelp-resetpassword-example-user": "利用者 Example にパスワード再設定の電子メールを送信する。", "apihelp-revisiondelete-summary": "版の削除および復元を行います。", @@ -1001,6 +1020,8 @@ "api-help-datatypes-header": "データ型", "api-help-param-list": "{{PLURAL:$1|1=値 (次の値のいずれか1つ)|2=値 ({{!}}もしくは[[Special:ApiHelp/main#main/datatypes|別の文字列]]で区切る)}}: $2", "api-help-param-list-can-be-empty": "{{PLURAL:$1|0=空欄にしてください|空欄にするか、または $2}}", + "api-help-param-limit": "$1より多くは受け付けません。", + "api-help-param-limit2": "$1(botは$2)より多くは受け付けません。", "api-help-param-integer-min": "{{PLURAL:$1|値}}は $2 以上にしてください。", "api-help-param-integer-max": "{{PLURAL:$1|値}}は $3 以下にしてください。", "api-help-param-integer-minmax": "{{PLURAL:$1|値}}は $2 以上 $3 以下にしてください。", @@ -1020,6 +1041,7 @@ "api-help-permissions": "{{PLURAL:$1|権限}}:", "api-help-permissions-granted-to": "{{PLURAL:$1|権限を持つグループ}}: $2", "api-help-open-in-apisandbox": "[サンドボックスで開く]", + "apierror-botsnotsupported": "この API インターフェースはボットをサポートしていません。", "apierror-filedoesnotexist": "ファイルが存在しません。", "apierror-invaliduser": "無効なユーザー名「$1」。", "apierror-missingparam": "パラメーター $1 を設定してください。", diff --git a/includes/api/i18n/ko.json b/includes/api/i18n/ko.json index e37bbbc5cf..631e681e53 100644 --- a/includes/api/i18n/ko.json +++ b/includes/api/i18n/ko.json @@ -69,20 +69,26 @@ "apihelp-compare-param-fromtitle": "비교할 첫 이름.", "apihelp-compare-param-fromid": "비교할 첫 문서 ID.", "apihelp-compare-param-fromrev": "비교할 첫 판.", - "apihelp-compare-param-fromtext": "fromtitle, fromid 또는 fromrev로 지정된 판의 내용 대신 이 텍스트를 사용합니다.", + "apihelp-compare-param-frompst": "fromtext-{slot}에 사전 저장 변환을 수행합니다.", + "apihelp-compare-param-fromtext-{slot}": "지정된 슬롯의 텍스트입니다. 생략할 경우 판에서 슬롯이 제거됩니다.", + "apihelp-compare-param-fromcontentmodel-{slot}": "fromtext-{slot}의 콘텐츠 모델입니다. 지정하지 않으면 다른 변수를 참고하여 추정합니다.", + "apihelp-compare-param-fromcontentformat-{slot}": "fromtext-{slot}의 콘텐츠 직렬화 포맷입니다.", + "apihelp-compare-param-fromtext": "fromslots=main을 지정하고 fromtext-main을 대신 사용합니다.", + "apihelp-compare-param-fromcontentmodel": "fromslots=main을 지정하고 fromcontentmodel-main을 대신 사용합니다.", + "apihelp-compare-param-fromcontentformat": "fromslots=main을 지정하고 fromcontentformat-main을 대신 사용합니다.", "apihelp-compare-param-fromsection": "지정된 'from' 내용의 지정된 문단만 사용합니다.", - "apihelp-compare-param-frompst": "fromtext에 사전 저장 변환을 수행합니다.", - "apihelp-compare-param-fromcontentmodel": "fromtext의 콘텐츠 모델입니다. 지정하지 않으면 다른 변수를 참고하여 추정합니다.", - "apihelp-compare-param-fromcontentformat": "fromtext의 콘텐츠 직렬화 포맷입니다.", "apihelp-compare-param-totitle": "비교할 두 번째 제목.", "apihelp-compare-param-toid": "비교할 두 번째 문서 ID.", "apihelp-compare-param-torev": "비교할 두 번째 판.", "apihelp-compare-param-torelative": "fromtitle, fromid 또는 fromrev에서 결정된 판과 상대적인 판을 사용합니다. 다른 'to' 옵션들은 모두 무시됩니다.", - "apihelp-compare-param-totext": "totitle, toid 또는 torev로 지정된 판의 내용 대신 이 텍스트를 사용합니다.", - "apihelp-compare-param-tosection": "지정된 'to' 내용의 지정된 문단만 사용합니다.", "apihelp-compare-param-topst": "totext에 사전 저장 변환을 수행합니다.", - "apihelp-compare-param-tocontentmodel": "totext의 콘텐츠 모델입니다. 지정하지 않으면 다른 변수를 참고하여 추정합니다.", - "apihelp-compare-param-tocontentformat": "totext의 콘텐츠 직렬화 포맷입니다.", + "apihelp-compare-param-totext-{slot}": "지정된 슬롯의 텍스트입니다. 생략하면 이 슬롯은 판에서 제거됩니다.", + "apihelp-compare-param-tocontentmodel-{slot}": "totext-{slot}의 콘텐츠 모델입니다. 지정하지 않으면 다른 변수를 참고하여 추정합니다.", + "apihelp-compare-param-tocontentformat-{slot}": "totext-{slot}의 콘텐츠 직렬화 포맷입니다.", + "apihelp-compare-param-totext": "toslots=main을 지정하고 totext-main을 대신 사용합니다.", + "apihelp-compare-param-tocontentmodel": "toslots=main을 지정하고 tocontentmodel-main을 대신 사용합니다.", + "apihelp-compare-param-tocontentformat": "toslots=main을 지정하고 tocontentformat-main을 대신 사용합니다.", + "apihelp-compare-param-tosection": "지정된 'to' 내용의 지정된 문단만 사용합니다.", "apihelp-compare-param-prop": "가져올 정보입니다.", "apihelp-compare-paramvalue-prop-diff": "HTML의 차이입니다.", "apihelp-compare-paramvalue-prop-diffsize": "HTML 차이의 크기(바이트 단위)입니다.", @@ -623,6 +629,7 @@ "apihelp-query+watchlist-paramvalue-prop-loginfo": "적절한 곳에 로그 정보를 추가합니다.", "apihelp-query+watchlistraw-summary": "현재 사용자의 주시문서 목록의 모든 문서를 가져옵니다.", "apihelp-removeauthenticationdata-summary": "현재 사용자의 인증 데이터를 제거합니다.", + "apihelp-removeauthenticationdata-example-simple": "FooAuthenticationRequest에 대한 현재 사용자의 데이터의 제거를 시도합니다.", "apihelp-resetpassword-summary": "비밀번호 재설정 이메일을 사용자에게 보냅니다.", "apihelp-resetpassword-param-user": "재설정할 사용자입니다.", "apihelp-resetpassword-param-email": "재설정할 사용자의 이메일 주소입니다.", @@ -730,14 +737,17 @@ "apihelp-watch-example-unwatch": "대문 문서의 주시를 해제합니다.", "apihelp-watch-example-generator": "일반 이름공간의 일부 첫 문서들을 주시합니다.", "apihelp-format-example-generic": "쿼리 결과를 $1 포맷으로 반환합니다.", + "apihelp-format-param-wrappedhtml": "가독성 높은 HTML과 관련 리소스로더 모듈을 JSON 객체로 반환합니다.", "apihelp-json-summary": "데이터를 JSON 형식으로 출력합니다.", "apihelp-json-param-formatversion": "출력 형식:\n;1:하위 호환 포맷 (XML 스타일 불린, 콘텐츠 노드를 위한 * 키 등).\n;2:실험적인 모던 포맷. 상세 내용은 바뀔 수 있습니다!\n;latest:최신 포맷(현재 2)을 이용하지만 경고 없이 바뀔 수 있습니다.", "apihelp-jsonfm-summary": "데이터를 JSON 포맷으로 출력합니다. (HTML의 가독성 증가)", "apihelp-none-summary": "아무 것도 출력하지 않습니다.", "apihelp-php-summary": "데이터를 직렬화된 PHP 포맷으로 출력합니다.", + "apihelp-php-param-formatversion": "출력 형식:\n;1:하위 호환 포맷 (XML 스타일 불리언, 콘텐츠 노드를 위한 * 키 등).\n;2:실험적인 모던 포맷. 상세 내용은 바뀔 수 있습니다!\n;latest:최신 포맷(현재 2)을 이용하지만 경고 없이 바뀔 수 있습니다.", "apihelp-phpfm-summary": "데이터를 PHP 포맷(HTML의 가독성 증가)으로 출력합니다.", "apihelp-rawfm-summary": "디버깅 요소를 포함하여 데이터를 JSON 형식으로 출력합니다. (HTML의 가독성 증가)", "apihelp-xml-summary": "데이터를 XML 형식으로 출력합니다.", + "apihelp-xml-param-xslt": "지정하면 명명된 페이지를 XSL 스타일시트로 추가합니다. 값은 .xsl로 끝나는 {{ns:MediaWiki}} 이름공간의 제목이어야 합니다.", "apihelp-xml-param-includexmlnamespace": "지정하면 XML 이름공간을 추가합니다.", "apihelp-xmlfm-summary": "데이터를 XML 포맷(가독성 높은 HTML 방식)으로 출력합니다.", "api-format-title": "미디어위키 API 결과", @@ -835,6 +845,8 @@ "apierror-cantimport-upload": "업로드된 페이지를 가져올 권한이 없습니다.", "apierror-cantimport": "페이지를 가져올 권한이 없습니다.", "apierror-cantsend": "로그인하지 않았거나 인증된 이메일 주소가 없거나 다른 사용자로 이메일을 보낼 권한이 없기 때문에 이메일을 보낼 수 없습니다.", + "apierror-compare-maintextrequired": "$1slots에 main이 포함되어 있다면 $1text-main 변수는 필수입니다. (메인 슬롯을 삭제할 수 없습니다)", + "apierror-compare-notext": "$1 변수는 $2 없이 사용할 수 없습니다.", "apierror-emptynewsection": "비어있는 새 문단을 만들 수 없습니다.", "apierror-emptypage": "새 문서로 빈 문서를 만들 수 없습니다.", "apierror-exceptioncaught": "[$1] 예외가 발생했습니다: $2", @@ -857,6 +869,7 @@ "apierror-maxlag-generic": "데이터베이스 서버 대기 중: $1 {{PLURAL:$1|초}} 지연되었습니다.", "apierror-maxlag": "$2 대기 중: $1 {{PLURAL:$1|초}} 지연되었습니다.", "apierror-missingcontent-revid": "ID $1 판에 해당하는 내용이 없습니다.", + "apierror-missingcontent-revid-role": "역할 $2에 대해 판 ID $1의 내용이 없습니다.", "apierror-missingparam": "$1 변수는 설정해야 합니다.", "apierror-missingtitle": "지정한 페이지가 존재하지 않습니다.", "apierror-missingtitle-byname": "$1 문서가 존재하지 않습니다.", diff --git a/includes/api/i18n/nb.json b/includes/api/i18n/nb.json index be6e798f5d..5e30c1a094 100644 --- a/includes/api/i18n/nb.json +++ b/includes/api/i18n/nb.json @@ -53,22 +53,22 @@ "apihelp-clearhasmsg-example-1": "Fjern hasmsg-flagget for aktuell bruker.", "apihelp-clientlogin-summary": "Logg inn på wikien med den interaktive flyten.", "apihelp-clientlogin-example-login": "Start prosessen med å logge inn til wikien som bruker Example med passord ExamplePassword.", - "apihelp-clientlogin-example-login2": "Fortsett å logge inn etter en UI-respons for tofaktor-autentisering, ved å oppgi en OATHToken på 987654.", + "apihelp-clientlogin-example-login2": "Fortsett å logge inn etter en UI-respons for totrinns pålogging, ved å oppgi en OATHToken på 987654.", "apihelp-compare-summary": "Hent forskjellen mellom to sider.", "apihelp-compare-extended-description": "Et revisjonsnummer, en sidetittel eller en side-ID for både «fra» og «til» må sendes.", "apihelp-compare-param-fromtitle": "Første tittel å sammenligne.", "apihelp-compare-param-fromid": "Første side-ID å sammenligne.", "apihelp-compare-param-fromrev": "Første revisjon å sammenligne.", - "apihelp-compare-param-fromtext": "Bruk denne teksten i stedet for innholdet i revisjonen som angis med fromtitle, fromid eller fromrev.", "apihelp-compare-param-frompst": "Gjør en transformering av fromtext før lagring.", + "apihelp-compare-param-fromtext": "Bruk denne teksten i stedet for innholdet i revisjonen som angis med fromtitle, fromid eller fromrev.", "apihelp-compare-param-fromcontentmodel": "Innholdsmodell for fromtext. Om den ikke angis vil den gjettes basert på de andre parameterne.", "apihelp-compare-param-fromcontentformat": "Innholdsserialiseringsformat for fromtext.", "apihelp-compare-param-totitle": "Andre tittel å sammenligne.", "apihelp-compare-param-toid": "Andre side-ID å sammenligne.", "apihelp-compare-param-torev": "Andre revisjon å sammenligne.", "apihelp-compare-param-torelative": "Bruk en revisjon som er relativ til revisjonen som hentes fra fromtitle, fromid eller fromrev. Alle de andre «to»-alternativene vil ignoreres.", - "apihelp-compare-param-totext": "Bruk denne teksten i stedet for innholdet i revisjonen spesifisert av totitle, toid eller torev.", "apihelp-compare-param-topst": "Gjør en transformering av totext før lagring.", + "apihelp-compare-param-totext": "Bruk denne teksten i stedet for innholdet i revisjonen spesifisert av totitle, toid eller torev.", "apihelp-compare-param-tocontentmodel": "Innholdsmodellen til totext. Om denne ikke angis vil den bli gjettet ut fra andre parametere.", "apihelp-compare-param-tocontentformat": "Innholdsserialiseringsformat for totext.", "apihelp-compare-param-prop": "Hvilke informasjonsdeler som skal hentes.", diff --git a/includes/api/i18n/pl.json b/includes/api/i18n/pl.json index 1ded789288..a0429ac0bd 100644 --- a/includes/api/i18n/pl.json +++ b/includes/api/i18n/pl.json @@ -53,7 +53,7 @@ "apihelp-checktoken-example-simple": "Sprawdź poprawność tokenu csrf.", "apihelp-clearhasmsg-summary": "Czyści flagę hasmsg dla bieżącego użytkownika.", "apihelp-clearhasmsg-example-1": "Wyczyść flagę hasmsg dla bieżącego użytkownika.", - "apihelp-compare-summary": "Zauważ różnicę między dwoma stronami", + "apihelp-compare-summary": "Pokaż porównanie dwóch stron.", "apihelp-compare-param-fromtitle": "Pierwszy tytuł do porównania.", "apihelp-compare-param-fromid": "ID pierwszej strony do porównania.", "apihelp-compare-param-fromrev": "Pierwsza wersja do porównania.", diff --git a/includes/api/i18n/pt-br.json b/includes/api/i18n/pt-br.json index 7378ac45f8..9c3f11bac1 100644 --- a/includes/api/i18n/pt-br.json +++ b/includes/api/i18n/pt-br.json @@ -67,20 +67,30 @@ "apihelp-compare-param-fromtitle": "Primeiro título para comparar.", "apihelp-compare-param-fromid": "Primeiro ID de página para comparar.", "apihelp-compare-param-fromrev": "Primeira revisão para comparar.", - "apihelp-compare-param-fromtext": "Use este texto em vez do conteúdo da revisão especificada por fromtitle, fromid ou fromrev.", + "apihelp-compare-param-frompst": "Fazer uma transformação anterior à gravação, de fromtext-{slot}.", + "apihelp-compare-param-fromslots": "Substituir o conteúdo da revisão especificada por fromtitle, fromid ou fromrev.\n\nEste parâmetro especifica os segmentos que deverão ser modificados. Use fromtext-{slot}, fromcontentmodel-{slot} e fromcontentformat-{slot} para especificar conteúdo para cada segmento.", + "apihelp-compare-param-fromtext-{slot}": "Texto do slot especificado. Se omitido, o slot é removido da revisão.", + "apihelp-compare-param-fromsection-{slot}": "Quando fromtext-{slot} é o conteúdo de uma única secção, este é o número da seção. Será fundido na revisão especificada por fromtitle, fromid ou fromrev tal como acontece na edição de uma secção.", + "apihelp-compare-param-fromcontentmodel-{slot}": "Modelo de conteúdo de fromtext-{slot}. Se não for fornecido, ele será deduzido a partir dos outros parâmetros.", + "apihelp-compare-param-fromcontentformat-{slot}": "Formato de serialização de conteúdo de fromtext-{slot}.", + "apihelp-compare-param-fromtext": "Especificar fromslots=main e usar fromtext-main.", + "apihelp-compare-param-fromcontentmodel": "Especificar fromslots=main e usar fromcontentmodel-main.", + "apihelp-compare-param-fromcontentformat": "Especificar fromslots=main e usar fromcontentformat-main.", "apihelp-compare-param-fromsection": "Utilizar apenas a secção especificada do conteúdo 'from' especificado.", - "apihelp-compare-param-frompst": "Faz uma transformação pré-salvar em fromtext.", - "apihelp-compare-param-fromcontentmodel": "Modelo de conteúdo de fromtext. Se não for fornecido, será adivinhado com base nos outros parâmetros.", - "apihelp-compare-param-fromcontentformat": "Formato de serialização de conteúdo de fromtext.", "apihelp-compare-param-totitle": "Segundo título para comparar.", "apihelp-compare-param-toid": "Segundo ID de página para comparar.", "apihelp-compare-param-torev": "Segunda revisão para comparar.", "apihelp-compare-param-torelative": "Use uma revisão relativa à revisão determinada de fromtitle, fromid ou fromrev. Todas as outras opções 'to' serão ignoradas.", - "apihelp-compare-param-totext": "Use este texto em vez do conteúdo da revisão especificada por totitle, toid ou torev.", - "apihelp-compare-param-tosection": "Utilizar apenas a secção especificada do conteúdo 'to' especificado.", "apihelp-compare-param-topst": "Faz uma transformação pré-salvar em totext.", - "apihelp-compare-param-tocontentmodel": "Modelo de conteúdo de totext. Se não for fornecido, será adivinhado com base nos outros parâmetros.", - "apihelp-compare-param-tocontentformat": "Formato de serialização de conteúdo de totext.", + "apihelp-compare-param-toslots": "Substituir o conteúdo da revisão especificada por totitle, toid ou torev.\n\nEste parâmetro especifica os slots que devem ser modificados. Usar totext-{slot}, tocontentmodel-{slot}, e tocontentformat-{slot} para especificar o conteúdo de cada slot.", + "apihelp-compare-param-totext-{slot}": "Texto do slot especificado. Se omitido, o slot é removido da revisão.", + "apihelp-compare-param-tosection-{slot}": "Quando totext-{slot} é o conteúdo de uma única secção, este é o número da secção. Será fundido na revisão especificada por totitle, toid ou torev tal como acontece na edição de uma secção.", + "apihelp-compare-param-tocontentmodel-{slot}": "Modelo de conteúdo de totext-{slot}. Se não for fornecido, ele será deduzido a partir dos outros parâmetros.", + "apihelp-compare-param-tocontentformat-{slot}": "Formato de seriação do conteúdo de totext-{slot}.", + "apihelp-compare-param-totext": "Especificar toslots=main e usar totext-main.", + "apihelp-compare-param-tocontentmodel": "Especificar toslots=main e usar tocontentmodel-main.", + "apihelp-compare-param-tocontentformat": "Especificar toslots=main e usar tocontentformat-main.", + "apihelp-compare-param-tosection": "Utilizar apenas a secção especificada do conteúdo 'to' especificado.", "apihelp-compare-param-prop": "Quais peças de informação incluir.", "apihelp-compare-paramvalue-prop-diff": "O dif do HTML.", "apihelp-compare-paramvalue-prop-diffsize": "O tamanho do diff HTML, em bytes.", @@ -91,6 +101,7 @@ "apihelp-compare-paramvalue-prop-comment": "O comentário das revisões 'from' e 'to'.", "apihelp-compare-paramvalue-prop-parsedcomment": "O comentário analisado sobre as revisões 'from' e 'to'.", "apihelp-compare-paramvalue-prop-size": "O tamanho das revisões 'from' e 'to'.", + "apihelp-compare-param-slots": "Devolve os diffs individuais para estes slots, em vez de um diff combinado para todos os slots.", "apihelp-compare-example-1": "Criar um diff entre a revisão 1 e 2.", "apihelp-createaccount-summary": "Criar uma nova conta de usuário.", "apihelp-createaccount-param-preservestate": "Se [[Special:ApiHelp/query+authmanagerinfo|action=query&meta=authmanagerinfo]] retornar true para hasprimarypreservedstate, pedidos marcados como hasprimarypreservedstate devem ser omitidos. Se retornou um valor não vazio para preservedusername, esse nome de usuário deve ser usado pelo parâmetro username.", @@ -1587,9 +1598,13 @@ "apierror-changeauth-norequest": "Falha ao criar pedido de mudança.", "apierror-chunk-too-small": "O tamanho mínimo do bloco é $1 {{PLURAL:$1|byte|bytes}} para os pedaços não finais.", "apierror-cidrtoobroad": "Os intervalos CIDR $1 maiores que /$2 não são aceitos.", + "apierror-compare-maintextrequired": "O parâmetro $1text-main é obrigatório quando $1slots contém main (não se pode eliminar o segmento principal).", "apierror-compare-no-title": "Não é possível pré-salvar a transformação sem um título. Tente especificar fromtitle ou totitle.", "apierror-compare-nosuchfromsection": "Não há nenhuma secção $1 no conteúdo 'from'.", "apierror-compare-nosuchtosection": "Não há nenhuma seção $1 no conteúdo 'to'.", + "apierror-compare-nofromrevision": "Não foi especificada uma revisão 'from'. Especificar fromrev, fromtitle ou fromid.", + "apierror-compare-notext": "O parâmetro $1 não pode ser usado sem $2.", + "apierror-compare-notorevision": "Não foi especificada uma revisão 'to'. Especificar torev, totitle ou toid.", "apierror-compare-relative-to-nothing": "Nenhuma revisão 'from' para torelative para ser relativa à.", "apierror-contentserializationexception": "Falha na serialização de conteúdo: $1", "apierror-contenttoobig": "O conteúdo fornecido excede o limite de tamanho do artigo de $1 {{PLURAL: $1|kilobyte|kilobytes}}.", @@ -1637,6 +1652,7 @@ "apierror-mimesearchdisabled": "A pesquisa MIME está desativada no Miser Mode.", "apierror-missingcontent-pageid": "Falta conteúdo para a ID da página $1.", "apierror-missingcontent-revid": "Falta conteúdo para a ID de revisão $1.", + "apierror-missingcontent-revid-role": "Conteúdo ausente para o ID de revisão $1 para a função $2.", "apierror-missingparam-at-least-one-of": "{{PLURAL:$2|O parâmetro|Ao menos um dos parâmetros}} $1 é necessário.", "apierror-missingparam-one-of": "{{PLURAL:$2|O parâmetro|Um dos parâmetros}} $1 é necessário.", "apierror-missingparam": "O parâmetro $1 precisa ser definido.", diff --git a/includes/api/i18n/pt.json b/includes/api/i18n/pt.json index 0733a2a212..b5da399b54 100644 --- a/includes/api/i18n/pt.json +++ b/includes/api/i18n/pt.json @@ -62,20 +62,30 @@ "apihelp-compare-param-fromtitle": "Primeiro título a comparar.", "apihelp-compare-param-fromid": "Primeiro identificador de página a comparar.", "apihelp-compare-param-fromrev": "Primeira revisão a comparar.", - "apihelp-compare-param-fromtext": "Usar este texto em vez do conteúdo da revisão especificada por fromtitle, fromid ou fromrev.", + "apihelp-compare-param-frompst": "Fazer uma transformação anterior à gravação, de fromtext-{slot}.", + "apihelp-compare-param-fromslots": "Substituir o conteúdo da revisão especificada por fromtitle, fromid ou fromrev.\n\nEste parâmetro especifica os segmentos que deverão ser modificados. Use fromtext-{slot}, fromcontentmodel-{slot} e fromcontentformat-{slot} para especificar conteúdo para cada segmento.", + "apihelp-compare-param-fromtext-{slot}": "Texto do segmento especificado. Se for omitido, o segmento é removido da revisão.", + "apihelp-compare-param-fromsection-{slot}": "Quando fromtext-{slot} é o conteúdo de uma única secção, este é o número da secção. Será fundido na revisão especificada por fromtitle, fromid ou fromrev tal como acontece na edição de uma secção.", + "apihelp-compare-param-fromcontentmodel-{slot}": "Modelo de conteúdo de fromtext-{slot}. Se não for fornecido, ele será deduzido a partir dos outros parâmetros.", + "apihelp-compare-param-fromcontentformat-{slot}": "Formato de seriação do conteúdo de fromtext-{slot}.", + "apihelp-compare-param-fromtext": "Especificar fromslots=main e usar fromtext-main.", + "apihelp-compare-param-fromcontentmodel": "Especificar fromslots=main e usar fromcontentmodel-main.", + "apihelp-compare-param-fromcontentformat": "Especificar fromslots=main e usar fromcontentformat-main.", "apihelp-compare-param-fromsection": "Utilizar apenas a secção especificada do conteúdo 'from' especificado.", - "apihelp-compare-param-frompst": "Fazer uma transformação anterior à gravação, de fromtext.", - "apihelp-compare-param-fromcontentmodel": "Modelo de conteúdo de fromtext. Se não for fornecido, ele será deduzido a partir dos outros parâmetros.", - "apihelp-compare-param-fromcontentformat": "Formato de seriação do conteúdo de fromtext.", "apihelp-compare-param-totitle": "Segundo título a comparar.", "apihelp-compare-param-toid": "Segundo identificador de página a comparar.", "apihelp-compare-param-torev": "Segunda revisão a comparar.", "apihelp-compare-param-torelative": "Usar uma revisão relativa à revisão determinada a partir de fromtitle, fromid ou fromrev. Todas as outras opções 'to' serão ignoradas.", - "apihelp-compare-param-totext": "Usar este texto em vez do conteúdo da revisão especificada por totitle, toid ou torev.", - "apihelp-compare-param-tosection": "Utilizar apenas a secção especificada do conteúdo 'to' especificado.", "apihelp-compare-param-topst": "Fazer uma transformação anterior à gravação, de totext.", - "apihelp-compare-param-tocontentmodel": "Modelo de conteúdo de totext. Se não for fornecido, ele será deduzido a partir dos outros parâmetros.", - "apihelp-compare-param-tocontentformat": "Formato de seriação do conteúdo de totext.", + "apihelp-compare-param-toslots": "Substituir o conteúdo da revisão especificada por totitle, toid ou torev.\n\nEste parâmetro especifica os segmentos que deverão ser modificados. Use totext-{slot}, tocontentmodel-{slot} e tocontentformat-{slot} para especificar conteúdo para cada segmento.", + "apihelp-compare-param-totext-{slot}": "Texto do segmento especificado. Se for omitido, o segmento é removido da revisão.", + "apihelp-compare-param-tosection-{slot}": "Quando totext-{slot} é o conteúdo de uma única secção, este é o número da secção. Será fundido na revisão especificada por totitle, toid ou torev tal como acontece na edição de uma secção.", + "apihelp-compare-param-tocontentmodel-{slot}": "Modelo de conteúdo de totext-{slot}. Se não for fornecido, ele será deduzido a partir dos outros parâmetros.", + "apihelp-compare-param-tocontentformat-{slot}": "Formato de seriação do conteúdo de totext-{slot}.", + "apihelp-compare-param-totext": "Especificar toslots=main e usar totext-main.", + "apihelp-compare-param-tocontentmodel": "Especificar toslots=main e usar tocontentmodel-main.", + "apihelp-compare-param-tocontentformat": "Especificar toslots=main e usar tocontentformat-main.", + "apihelp-compare-param-tosection": "Utilizar apenas a secção especificada do conteúdo 'to' especificado.", "apihelp-compare-param-prop": "As informações que devem ser obtidas.", "apihelp-compare-paramvalue-prop-diff": "O HTML da lista de diferenças.", "apihelp-compare-paramvalue-prop-diffsize": "O tamanho do HTML da lista de diferenças, em bytes.", @@ -86,6 +96,7 @@ "apihelp-compare-paramvalue-prop-comment": "O comentário das revisões 'from' e 'to'.", "apihelp-compare-paramvalue-prop-parsedcomment": "O comentário após análise sintática, das revisões 'from' e 'to'.", "apihelp-compare-paramvalue-prop-size": "O tamanho das revisões 'from' e 'to'.", + "apihelp-compare-param-slots": "Devolver as diferenças individuais destes segmentos, em vez de uma lista combinada para todos os segmentos.", "apihelp-compare-example-1": "Criar uma lista de diferenças entre as revisões 1 e 2.", "apihelp-createaccount-summary": "Criar uma conta de utilizador nova.", "apihelp-createaccount-param-preservestate": "Se [[Special:ApiHelp/query+authmanagerinfo|action=query&meta=authmanagerinfo]] devolveu o valor verdadeiro para hasprimarypreservedstate, pedidos marcados como primary-required devem ser omitidos. Se devolveu um valor não vazio em preservedusername, esse nome de utilizador tem de ser usado no parâmetro username.", @@ -819,7 +830,7 @@ "apihelp-query+imageinfo-param-badfilecontexttitle": "Se $2prop=badfile estiver definido, este é o título da página usado ao calcular a [[MediaWiki:Bad image list]]", "apihelp-query+imageinfo-param-localonly": "Procurar ficheiros só no repositório local.", "apihelp-query+imageinfo-example-simple": "Obter informação sobre a versão atual do ficheiro [[:File:Albert Einstein Head.jpg]].", - "apihelp-query+imageinfo-example-dated": "Obter informação sobre as versões de [[:File:Test.jpg]] a partir de 2008.", + "apihelp-query+imageinfo-example-dated": "Obter informação sobre as versões de [[:File:Test.jpg]] desde 2008.", "apihelp-query+images-summary": "Devolve todos os ficheiros contidos nas páginas indicadas.", "apihelp-query+images-param-limit": "O número de ficheiros a serem devolvidos.", "apihelp-query+images-param-images": "Listar só estes ficheiros. Útil para verificar se uma determinada página tem um determinado ficheiro.", @@ -1582,9 +1593,13 @@ "apierror-changeauth-norequest": "A criação do pedido de modificação falhou.", "apierror-chunk-too-small": "O tamanho de segmento mínimo é $1 {{PLURAL:$1|byte|bytes}} para segmentos que não sejam segmentos finais.", "apierror-cidrtoobroad": "Não são aceites intervalos CIDR $1 maiores do que /$2.", + "apierror-compare-maintextrequired": "O parâmetro $1text-main é obrigatório quando $1slots contém main (não se pode eliminar o segmento principal).", "apierror-compare-no-title": "Não é possível transformar antes da gravação, sem um título. Tente especificar fromtitle ou totitle.", "apierror-compare-nosuchfromsection": "Não há nenhuma secção $1 no conteúdo 'from'.", "apierror-compare-nosuchtosection": "Não há nenhuma secção $1 no conteúdo 'to'.", + "apierror-compare-nofromrevision": "Não foi especificada uma revisão 'from'. Especificar fromrev, fromtitle ou fromid.", + "apierror-compare-notext": "O parâmetro $1 não pode ser usado sem $2.", + "apierror-compare-notorevision": "Não foi especificada uma revisão 'to'. Especificar torev, totitle ou toid.", "apierror-compare-relative-to-nothing": "Não existe uma revisão 'from' em relação à qual torelative possa ser relativo.", "apierror-contentserializationexception": "A seriação do conteúdo falhou: $1", "apierror-contenttoobig": "O conteúdo que forneceu excede o tamanho máximo dos artigos que é $1 {{PLURAL:$1|kilobyte|kilobytes}}.", @@ -1632,6 +1647,7 @@ "apierror-mimesearchdisabled": "A pesquisa MIME é desativada no modo avarento.", "apierror-missingcontent-pageid": "Conteúdo em falta para a página com o identificador $1.", "apierror-missingcontent-revid": "Conteúdo em falta para a revisão com o identificador $1.", + "apierror-missingcontent-revid-role": "O identificador de revisão $1 para a função $2 não tem conteúdo.", "apierror-missingparam-at-least-one-of": "{{PLURAL:$2|O parâmetro|Pelo menos um dos parâmetros}} $1 é obrigatório.", "apierror-missingparam-one-of": "{{PLURAL:$2|O parâmetro|Um dos parâmetros}} $1 é obrigatório.", "apierror-missingparam": "O parâmetro $1 tem de ser definido.", diff --git a/includes/api/i18n/qqq.json b/includes/api/i18n/qqq.json index f158f27dd3..eb3fdef0e4 100644 --- a/includes/api/i18n/qqq.json +++ b/includes/api/i18n/qqq.json @@ -67,20 +67,30 @@ "apihelp-compare-param-fromtitle": "{{doc-apihelp-param|compare|fromtitle}}", "apihelp-compare-param-fromid": "{{doc-apihelp-param|compare|fromid}}", "apihelp-compare-param-fromrev": "{{doc-apihelp-param|compare|fromrev}}", - "apihelp-compare-param-fromtext": "{{doc-apihelp-param|compare|fromtext}}", - "apihelp-compare-param-fromsection": "{{doc-apihelp-param|compare|fromsection}}", "apihelp-compare-param-frompst": "{{doc-apihelp-param|compare|frompst}}", + "apihelp-compare-param-fromslots": "{{doc-apihelp-param|compare|fromslots}}", + "apihelp-compare-param-fromtext-{slot}": "{{doc-apihelp-param|compare|fromtext-{slot} }}", + "apihelp-compare-param-fromsection-{slot}": "{{doc-apihelp-param|compare|fromsection-{slot} }}", + "apihelp-compare-param-fromcontentmodel-{slot}": "{{doc-apihelp-param|compare|fromcontentmodel-{slot} }}", + "apihelp-compare-param-fromcontentformat-{slot}": "{{doc-apihelp-param|compare|fromcontentformat-{slot} }}", + "apihelp-compare-param-fromtext": "{{doc-apihelp-param|compare|fromtext}}", "apihelp-compare-param-fromcontentmodel": "{{doc-apihelp-param|compare|fromcontentmodel}}", "apihelp-compare-param-fromcontentformat": "{{doc-apihelp-param|compare|fromcontentformat}}", + "apihelp-compare-param-fromsection": "{{doc-apihelp-param|compare|fromsection}}", "apihelp-compare-param-totitle": "{{doc-apihelp-param|compare|totitle}}", "apihelp-compare-param-toid": "{{doc-apihelp-param|compare|toid}}", "apihelp-compare-param-torev": "{{doc-apihelp-param|compare|torev}}", "apihelp-compare-param-torelative": "{{doc-apihelp-param|compare|torelative}}", - "apihelp-compare-param-totext": "{{doc-apihelp-param|compare|totext}}", - "apihelp-compare-param-tosection": "{{doc-apihelp-param|compare|tosection}}", "apihelp-compare-param-topst": "{{doc-apihelp-param|compare|topst}}", + "apihelp-compare-param-toslots": "{{doc-apihelp-param|compare|toslots}}", + "apihelp-compare-param-totext-{slot}": "{{doc-apihelp-param|compare|totext-{slot} }}", + "apihelp-compare-param-tosection-{slot}": "{{doc-apihelp-param|compare|tosection-{slot} }}", + "apihelp-compare-param-tocontentmodel-{slot}": "{{doc-apihelp-param|compare|tocontentmodel-{slot} }}", + "apihelp-compare-param-tocontentformat-{slot}": "{{doc-apihelp-param|compare|tocontentformat-{slot} }}", + "apihelp-compare-param-totext": "{{doc-apihelp-param|compare|totext}}", "apihelp-compare-param-tocontentmodel": "{{doc-apihelp-param|compare|tocontentmodel}}", "apihelp-compare-param-tocontentformat": "{{doc-apihelp-param|compare|tocontentformat}}", + "apihelp-compare-param-tosection": "{{doc-apihelp-param|compare|tosection}}", "apihelp-compare-param-prop": "{{doc-apihelp-param|compare|prop}}", "apihelp-compare-paramvalue-prop-diff": "{{doc-apihelp-paramvalue|compare|prop|diff}}", "apihelp-compare-paramvalue-prop-diffsize": "{{doc-apihelp-paramvalue|compare|prop|diffsize}}", @@ -91,6 +101,7 @@ "apihelp-compare-paramvalue-prop-comment": "{{doc-apihelp-paramvalue|compare|prop|comment}}", "apihelp-compare-paramvalue-prop-parsedcomment": "{{doc-apihelp-paramvalue|compare|prop|parsedcomment}}", "apihelp-compare-paramvalue-prop-size": "{{doc-apihelp-paramvalue|compare|prop|size}}", + "apihelp-compare-param-slots": "{{doc-apihelp-param|compare|slots}}", "apihelp-compare-example-1": "{{doc-apihelp-example|compare}}", "apihelp-createaccount-summary": "{{doc-apihelp-summary|createaccount}}", "apihelp-createaccount-param-preservestate": "{{doc-apihelp-param|createaccount|preservestate|info=This message is displayed in addition to {{msg-mw|api-help-authmanagerhelper-preservestate}}.}}", @@ -1594,9 +1605,13 @@ "apierror-changeauth-norequest": "{{doc-apierror}}", "apierror-chunk-too-small": "{{doc-apierror}}\n\nParameters:\n* $1 - Minimum size in bytes.", "apierror-cidrtoobroad": "{{doc-apierror}}\n\nParameters:\n* $1 - \"IPv4\" or \"IPv6\"\n* $2 - Minimum CIDR mask length.", + "apierror-compare-maintextrequired": "{{doc-apierror}}\n\nParameters:\n* $1 - Parameter name prefix, 'to' or 'from'.", "apierror-compare-no-title": "{{doc-apierror}}", "apierror-compare-nosuchfromsection": "{{doc-apierror}}\n\nParameters:\n* $1 - Section identifier. Probably a number or \"T-\" followed by a number.", "apierror-compare-nosuchtosection": "{{doc-apierror}}\n\nParameters:\n* $1 - Section identifier. Probably a number or \"T-\" followed by a number.", + "apierror-compare-nofromrevision": "{{doc-apierror}}", + "apierror-compare-notext": "{{doc-apierror}}\n\nParameters:\n* $1 - Parameter that is not allowed without totext-{role}/fromtext-{role}.\n* $2 - The specific totext-{role}/fromtext-{role} parameter that must be present.", + "apierror-compare-notorevision": "{{doc-apierror}}", "apierror-compare-relative-to-nothing": "{{doc-apierror}}", "apierror-contentserializationexception": "{{doc-apierror}}\n\nParameters:\n* $1 - Exception text, may end with punctuation. Currently this is probably English, hopefully we'll fix that in the future.", "apierror-contenttoobig": "{{doc-apierror}}\n\nParameters:\n* $1 - Maximum article size in kilobytes.", @@ -1644,6 +1659,7 @@ "apierror-mimesearchdisabled": "{{doc-apierror}}", "apierror-missingcontent-pageid": "{{doc-apierror}}\n\nParameters:\n* $1 - Page ID number.", "apierror-missingcontent-revid": "{{doc-apierror}}\n\nParameters:\n* $1 - Revision ID number", + "apierror-missingcontent-revid-role": "{{doc-apierror}}\n\nParameters:\n* $1 - Revision ID number\n* $2 - Role name", "apierror-missingparam-at-least-one-of": "{{doc-apierror}}\n\nParameters:\n* $1 - List of parameter names.\n* $2 - Number of parameters.", "apierror-missingparam-one-of": "{{doc-apierror}}\n\nParameters:\n* $1 - List of parameter names.\n* $2 - Number of parameters.", "apierror-missingparam": "{{doc-apierror}}\n\nParameters:\n* $1 - Parameter name.", diff --git a/includes/api/i18n/ru.json b/includes/api/i18n/ru.json index d712765cb1..4450b6cbb5 100644 --- a/includes/api/i18n/ru.json +++ b/includes/api/i18n/ru.json @@ -88,20 +88,21 @@ "apihelp-compare-param-fromtitle": "Заголовок первой сравниваемой страницы.", "apihelp-compare-param-fromid": "Идентификатор первой сравниваемой страницы.", "apihelp-compare-param-fromrev": "Первая сравниваемая версия.", - "apihelp-compare-param-fromtext": "Используйте этот текст вместо содержимого версии, заданной fromtitle, fromid или fromrev.", - "apihelp-compare-param-fromsection": "Использовать только указанную секцию из содержимого «from».", "apihelp-compare-param-frompst": "Выполнить преобразование перед записью правки (PST) над fromtext.", + "apihelp-compare-param-fromtext": "Используйте этот текст вместо содержимого версии, заданной fromtitle, fromid или fromrev.", "apihelp-compare-param-fromcontentmodel": "Модель содержимого fromtext. Если не задана, будет угадана по другим параметрам.", "apihelp-compare-param-fromcontentformat": "Формат сериализации содержимого fromtext.", + "apihelp-compare-param-fromsection": "Использовать только указанную секцию из содержимого «from».", "apihelp-compare-param-totitle": "Заголовок второй сравниваемой страницы.", "apihelp-compare-param-toid": "Идентификатор второй сравниваемой страницы.", "apihelp-compare-param-torev": "Вторая сравниваемая версия.", "apihelp-compare-param-torelative": "Использовать версию, относящуюся к определённой fromtitle, fromid или fromrev. Все другие опции 'to' будут проигнорированы.", - "apihelp-compare-param-totext": "Используйте этот текст вместо содержимого версии, заданной totitle, toid или torev.", - "apihelp-compare-param-tosection": "Использовать только указанную секцию из содержимого «to».", "apihelp-compare-param-topst": "Выполнить преобразование перед записью правки (PST) над totext.", + "apihelp-compare-param-tocontentmodel-{slot}": "Модель содержимого totext-{slot}. Если не задана, будет угадана по другим параметрам.", + "apihelp-compare-param-totext": "Используйте этот текст вместо содержимого версии, заданной totitle, toid или torev.", "apihelp-compare-param-tocontentmodel": "Модель содержимого totext. Если не задана, будет угадана по другим параметрам.", "apihelp-compare-param-tocontentformat": "Формат сериализации содержимого totext.", + "apihelp-compare-param-tosection": "Использовать только указанную секцию из содержимого «to».", "apihelp-compare-param-prop": "Какую информацию получить.", "apihelp-compare-paramvalue-prop-diff": "HTML-код разницы.", "apihelp-compare-paramvalue-prop-diffsize": "Размер HTML-кода разницы в байтах.", diff --git a/includes/api/i18n/sv.json b/includes/api/i18n/sv.json index 20dc919f8a..1cb6f5c38f 100644 --- a/includes/api/i18n/sv.json +++ b/includes/api/i18n/sv.json @@ -16,7 +16,8 @@ "Rockyfelle", "Macofe", "Magol", - "Bengtsson96" + "Bengtsson96", + "Larske" ] }, "apihelp-main-extended-description": "
\n* [[mw:Special:MyLanguage/API:Main_page|Dokumentation]]\n* [[mw:Special:MyLanguage/API:FAQ|Vanliga frågor]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api Sändlista]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce API-nyheter]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R Buggar och begäran]\n
\nStatus: Alla funktioner som visas på denna sida bör fungera, men API:et är fortfarande under utveckling och kan ändras när som helst. Prenumerera på [https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ sändlistan mediawiki-api-announce] för uppdateringsaviseringar.\n\nFelaktiga begäran: När felaktiga begäran skickas till API:et kommer en HTTP-header skickas med nyckeln \"MediaWiki-API-Error\" och sedan kommer både värdet i headern och felkoden som skickades tillbaka anges som samma värde. För mer information se [[mw:Special:MyLanguage/API:Errors_and_warnings|API: Fel och varningar]].\n\n

Testning: För enkelt testning av API-begäran, se [[Special:ApiSandbox]].

", @@ -447,7 +448,7 @@ "apihelp-query+images-param-limit": "Hur många filer att returnera.", "apihelp-query+images-param-dir": "Riktningen att lista mot.", "apihelp-query+images-example-simple": "Hämta en lista över filer som används på [[Main Page]].", - "apihelp-query+imageusage-summary": "Hitta alla sidor som användare angiven bildtitel.", + "apihelp-query+imageusage-summary": "Hitta alla sidor som använder angiven bildtitel.", "apihelp-query+imageusage-param-dir": "Riktningen att lista mot.", "apihelp-query+imageusage-example-simple": "Visa sidor med hjälp av [[:File:Albert Einstein Head.jpg]].", "apihelp-query+imageusage-example-generator": "Hämta information om sidor med hjälp av [[:File:Albert Einstein Head.jpg]].", diff --git a/includes/api/i18n/uk.json b/includes/api/i18n/uk.json index e9078e0b65..c9eb3b4b82 100644 --- a/includes/api/i18n/uk.json +++ b/includes/api/i18n/uk.json @@ -66,20 +66,20 @@ "apihelp-compare-param-fromtitle": "Перший заголовок для порівняння.", "apihelp-compare-param-fromid": "Перший ID сторінки для порівняння.", "apihelp-compare-param-fromrev": "Перша версія для порівняння.", + "apihelp-compare-param-frompst": "Зробити трансформацію перед збереженням на fromtext-{slot}.", "apihelp-compare-param-fromtext": "Використати цей текст замість контенту версії, вказаної через fromtitle, fromid або fromrev.", - "apihelp-compare-param-fromsection": "Використовувати лише вказану секцію із заданого вмісту «from».", - "apihelp-compare-param-frompst": "Зробити трансформацію перед збереженням на fromtext.", "apihelp-compare-param-fromcontentmodel": "Контентна модель fromtext. Якщо не вказано, буде використано припущення на основі інших параметрів.", "apihelp-compare-param-fromcontentformat": "Формат серіалізації контенту fromtext.", + "apihelp-compare-param-fromsection": "Використовувати лише вказану секцію із заданого вмісту «from».", "apihelp-compare-param-totitle": "Другий заголовок для порівняння.", "apihelp-compare-param-toid": "Другий ID сторінки для порівняння.", "apihelp-compare-param-torev": "Друга версія для порівняння.", "apihelp-compare-param-torelative": "Використати версію, яка стосується версії, визначеної через fromtitle, fromid або fromrev. Усі інші опції 'to' буде проігноровано.", - "apihelp-compare-param-totext": "Використати цей текст замість контенту версії, вказаної через totitle, toid або torev.", - "apihelp-compare-param-tosection": "Використовувати лише вказану секцію із заданого вмісту «to».", "apihelp-compare-param-topst": "Виконати трансформацію перед збереженням на totext.", + "apihelp-compare-param-totext": "Використати цей текст замість контенту версії, вказаної через totitle, toid або torev.", "apihelp-compare-param-tocontentmodel": "Контентна модель totext. Якщо не вказано, буде використано припущення на основі інших параметрів.", "apihelp-compare-param-tocontentformat": "Формат серіалізації контенту totext.", + "apihelp-compare-param-tosection": "Використовувати лише вказану секцію із заданого вмісту «to».", "apihelp-compare-param-prop": "Які уривки інформації отримати.", "apihelp-compare-paramvalue-prop-diff": "HTML різниці версій.", "apihelp-compare-paramvalue-prop-diffsize": "Розмір HTML різниці версій, у байтах.", diff --git a/includes/api/i18n/zh-hans.json b/includes/api/i18n/zh-hans.json index 8d618ea068..60cf5751ee 100644 --- a/includes/api/i18n/zh-hans.json +++ b/includes/api/i18n/zh-hans.json @@ -25,7 +25,8 @@ "Umherirrender", "NeverBehave", "Wbxshiori", - "Wxyveronica" + "Wxyveronica", + "WhitePhosphorus" ] }, "apihelp-main-extended-description": "
\n* [[mw:Special:MyLanguage/API:Main_page|文档]]\n* [[mw:Special:MyLanguage/API:FAQ|常见问题]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api 邮件列表]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce API公告]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R 程序错误与功能请求]\n
\n状态信息:MediaWiki API是一个成熟稳定的,不断受到支持和改进的界面。尽管我们尽力避免,但偶尔也需要作出重大更新;请订阅[https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ mediawiki-api-announce 邮件列表]以便获得更新通知。\n\n错误请求:当API收到错误请求时,HTTP header将会返回一个包含\"MediaWiki-API-Error\"的值,随后header的值与error code将会送回并设置为相同的值。详细信息请参阅[[mw:Special:MyLanguage/API:Errors_and_warnings|API:错误与警告]]。\n\n

测试中:测试API请求的易用性,请参见[[Special:ApiSandbox]]。

", @@ -78,20 +79,20 @@ "apihelp-compare-param-fromtitle": "要比较的第一个标题。", "apihelp-compare-param-fromid": "要比较的第一个页面 ID。", "apihelp-compare-param-fromrev": "要比较的第一个修订版本。", - "apihelp-compare-param-fromtext": "使用该文本而不是由fromtitle、fromid或fromrev指定的修订版本内容。", - "apihelp-compare-param-fromsection": "只使用指定“from”内容的指定章节。", "apihelp-compare-param-frompst": "在fromtext执行预保存转变。", + "apihelp-compare-param-fromtext": "使用该文本而不是由fromtitle、fromid或fromrev指定的修订版本内容。", "apihelp-compare-param-fromcontentmodel": "fromtext的内容模型。如果未指定,这将基于其他参数猜想。", "apihelp-compare-param-fromcontentformat": "fromtext的内容序列化格式。", + "apihelp-compare-param-fromsection": "只使用指定“from”内容的指定章节。", "apihelp-compare-param-totitle": "要比较的第二个标题。", "apihelp-compare-param-toid": "要比较的第二个页面 ID。", "apihelp-compare-param-torev": "要比较的第二个修订版本。", "apihelp-compare-param-torelative": "使用与定义自fromtitle、fromid或fromrev的修订版本相关的修订版本。所有其他“to”的选项将被忽略。", - "apihelp-compare-param-totext": "使用该文本而不是由totitle、toid或torev指定的修订版本内容。", - "apihelp-compare-param-tosection": "只使用指定“to”内容的指定章节。", "apihelp-compare-param-topst": "在totext执行预保存转换。", + "apihelp-compare-param-totext": "使用该文本而不是由totitle、toid或torev指定的修订版本内容。", "apihelp-compare-param-tocontentmodel": "totext的内容模型。如果未指定,这将基于其他参数猜想。", "apihelp-compare-param-tocontentformat": "totext的内容序列化格式。", + "apihelp-compare-param-tosection": "只使用指定“to”内容的指定章节。", "apihelp-compare-param-prop": "要获取的信息束。", "apihelp-compare-paramvalue-prop-diff": "差异HTML。", "apihelp-compare-paramvalue-prop-diffsize": "差异HTML的大小(字节)。", @@ -1050,7 +1051,7 @@ "apihelp-query+redirects-example-generator": "获取所有重定向至[[Main Page]]的信息。", "apihelp-query+revisions-summary": "获取修订版本信息。", "apihelp-query+revisions-extended-description": "可用于以下几个方面:\n# 通过设置标题或页面ID获取一批页面(最新修订)的数据。\n# 通过使用带start、end或limit的标题或页面ID获取给定页面的多个修订。\n# 通过revid设置一批修订的ID获取它们的数据。", - "apihelp-query+revisions-paraminfo-singlepageonly": "可能只能与单一页面使用(模式#2)。", + "apihelp-query+revisions-paraminfo-singlepageonly": "只能在单一页面模式中使用(模式#2)。", "apihelp-query+revisions-param-startid": "从这个修订版本时间戳开始列举。修订版本必须存在,但未必与该页面相关。", "apihelp-query+revisions-param-endid": "在这个修订版本时间戳停止列举。修订版本必须存在,但未必与该页面相关。", "apihelp-query+revisions-param-start": "从哪个修订版本时间戳开始列举。", @@ -1662,7 +1663,7 @@ "apierror-mustbeloggedin-linkaccounts": "您必须登录以链接账户。", "apierror-mustbeloggedin-removeauth": "您必须登录以移除身份验证数据。", "apierror-mustbeloggedin-uploadstash": "上传暂存功能只对已登录用户可用。", - "apierror-mustbeloggedin": "您必须登录至$1。", + "apierror-mustbeloggedin": "您必须登录才能$1。", "apierror-mustbeposted": "$1模块需要POST请求。", "apierror-mustpostparams": "以下{{PLURAL:$2|参数}}在查询字符串中被找到,但必须在POST正文中:$1。", "apierror-noapiwrite": "通过API编辑此wiki已禁用。请确保$wgEnableWriteAPI=true;声明包含在wiki的LocalSettings.php文件中。", diff --git a/includes/api/i18n/zh-hant.json b/includes/api/i18n/zh-hant.json index 98ecec692a..55c0671c18 100644 --- a/includes/api/i18n/zh-hant.json +++ b/includes/api/i18n/zh-hant.json @@ -16,7 +16,8 @@ "Wwycheuk", "Wbxshiori", "Sanmosa", - "Kly" + "Kly", + "WhitePhosphorus" ] }, "apihelp-main-extended-description": "
\n* [[mw:Special:MyLanguage/API:Main_page|說明文件]]\n* [[mw:Special:MyLanguage/API:FAQ|常見問題]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api 郵寄清單]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce API公告]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R 報告錯誤及請求功能]\n
\n狀態資訊:MediaWiki API 已是成熟、穩定,並積極支援以改善的介面。儘管我們儘可能避免,但仍偶有需要重大變更的情況,請訂閱[https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ mediawiki-api-announce 郵寄清單]以便獲得更新通知。\n\n錯誤的請求:當 API 收到錯誤的請求,會發出以「MediaWiki-API-Error」為鍵的 HTTP 標頭欄位,隨後標頭欄位的值,以及傳回的錯誤碼會設為相同值。詳細資訊請參閱 [[mw:Special:MyLanguage/API:Errors_and_warnings|API: 錯誤與警告]]。\n\n

測試:要簡化 API 請求的測試過程,請見 [[Special:ApiSandbox]]。

", @@ -32,6 +33,7 @@ "apihelp-main-param-responselanginfo": "在結果中包括uselang和errorlang所用的語言。", "apihelp-block-summary": "封鎖使用者。", "apihelp-block-param-user": "要封鎖的使用者名稱、IP 位址或 IP 範圍。不能與 $1userid 一起使用", + "apihelp-block-param-userid": "要封鎖的使用者 ID。不可與 $1user 一同使用。", "apihelp-block-param-reason": "封鎖原因。", "apihelp-block-param-anononly": "僅封鎖匿名使用者 (禁止這個 IP 位址的匿名使用者編輯)。", "apihelp-block-param-nocreate": "禁止建立帳號。", @@ -41,9 +43,11 @@ "apihelp-block-param-allowusertalk": "允許使用者編輯自己的對話頁面 (依據 [[mw:Special:MyLanguage/Manual:$wgBlockAllowsUTEdit|$wgBlockAllowsUTEdit]] 的設定)。", "apihelp-block-param-reblock": "若使用者已被封鎖,覆寫既有的封鎖設定值。", "apihelp-block-param-watchuser": "監視使用者或 IP 位址的使用者頁面與對話頁面。", + "apihelp-block-param-tags": "在封鎖日誌裡更改套用到項目的標籤。", "apihelp-block-example-ip-simple": "封鎖 IP 位址 192.0.2.5 三天,原因為 First strike。", "apihelp-block-example-user-complex": "永久封鎖 IP 位址 Vandal,原因為 Vandalism。", "apihelp-changeauthenticationdata-summary": "為目前使用者變更身分核對資料。", + "apihelp-changeauthenticationdata-example-password": "嘗試更改目前使用者的密碼至 ExamplePassword。", "apihelp-checktoken-summary": "檢查來自 [[Special:ApiHelp/query+tokens|action=query&meta=tokens]] 的密鑰有效性。", "apihelp-checktoken-param-type": "要測試的密鑰類型。", "apihelp-checktoken-param-token": "要測試的密鑰。", @@ -56,10 +60,26 @@ "apihelp-compare-param-fromtitle": "要比對的第一個標題。", "apihelp-compare-param-fromid": "要比對的第一個頁面 ID。", "apihelp-compare-param-fromrev": "要比對的第一個修訂。", + "apihelp-compare-param-fromcontentformat-{slot}": "fromtext-{slot} 的內容序列化格式。", + "apihelp-compare-param-fromtext": "指定 fromslots=main 並改用 fromtext-main。", + "apihelp-compare-param-fromcontentmodel": "指定 fromslots=main 並改使用 fromcontentmodel-main。", + "apihelp-compare-param-fromcontentformat": "指定 fromslots=main 並改使用 fromcontentformat-main。", "apihelp-compare-param-totitle": "要比對的第二個標題。", "apihelp-compare-param-toid": "要比對的第二個頁面 ID。", "apihelp-compare-param-torev": "要比對的第二個修訂。", + "apihelp-compare-param-topst": "在 totext 執行預先保存轉換。", + "apihelp-compare-param-totext": "指定 toslots=main 並改用 totext-main。", + "apihelp-compare-param-tocontentmodel": "指定 toslots=main 並改使用 tocontentmodel-main。", + "apihelp-compare-param-tocontentformat": "指定 toslots=main 並改使用 tocontentformat-main。", "apihelp-compare-param-prop": "要取得的資訊部份。", + "apihelp-compare-paramvalue-prop-diff": "HTML 差異。", + "apihelp-compare-paramvalue-prop-diffsize": "以位元組為單位的 HTML 差異大小。", + "apihelp-compare-paramvalue-prop-ids": "「from」與「to」修訂的頁面與修訂 ID。", + "apihelp-compare-paramvalue-prop-title": "「from」與「to」修訂的頁面標題。", + "apihelp-compare-paramvalue-prop-user": "「from」與「to」修訂的使用者名稱與 ID。", + "apihelp-compare-paramvalue-prop-comment": "「from」與「to」修訂的註釋。", + "apihelp-compare-paramvalue-prop-parsedcomment": "「from」與「to」修訂的解析註釋。", + "apihelp-compare-paramvalue-prop-size": "「from」與「to」修訂的大小。", "apihelp-compare-example-1": "建立修訂 1 與 1 的差異檔", "apihelp-createaccount-summary": "建立新使用者帳號。", "apihelp-createaccount-param-name": "使用者名稱。", @@ -101,9 +121,14 @@ "apihelp-edit-param-nocreate": "若頁面不存在,則產生錯誤。", "apihelp-edit-param-watch": "加入目前頁面至您的監視清單。", "apihelp-edit-param-unwatch": "從您的監視清單中移除目前頁面。", + "apihelp-edit-param-watchlist": "無條件使用設置將頁面加入或移除目前使用者的監視清單或者是不更改監視清單。", + "apihelp-edit-param-prependtext": "添加此文字至頁面開頭。覆蓋$1text。", + "apihelp-edit-param-undo": "撤銷此修訂。覆蓋$1text、$1prependtext與$1appendtext。", + "apihelp-edit-param-redirect": "自動化解決重新導向。", "apihelp-edit-param-contentformat": "用於輸入文字的內容序列化格式。", "apihelp-edit-param-contentmodel": "新內容的內容模組。", "apihelp-edit-example-edit": "編輯頁面", + "apihelp-edit-example-prepend": "前置頁面的 __NOTOC__。", "apihelp-emailuser-summary": "寄送電子郵件給使用者。", "apihelp-emailuser-param-target": "電子郵件的收件使用者。", "apihelp-emailuser-param-subject": "郵件主旨。", @@ -113,37 +138,54 @@ "apihelp-expandtemplates-summary": "展開所有於 wikitext 中模板。", "apihelp-expandtemplates-param-title": "頁面標題。", "apihelp-expandtemplates-param-text": "要轉換的 Wikitext。", + "apihelp-expandtemplates-paramvalue-prop-wikitext": "展開的 wiki 文字。", + "apihelp-expandtemplates-paramvalue-prop-jsconfigvars": "指定頁面的 JavaScript 設置變量。", + "apihelp-expandtemplates-paramvalue-prop-encodedjsconfigvars": "指定頁面的 JavaScript 設置變量為 JSON 字串。", "apihelp-feedcontributions-summary": "回傳使用者貢獻 Feed。", "apihelp-feedcontributions-param-feedformat": "Feed 的格式。", + "apihelp-feedcontributions-param-user": "要取得哪些使用者的貢獻。", "apihelp-feedcontributions-param-year": "起始年份(更早之前)。", "apihelp-feedcontributions-param-month": "起始月份(更早之前)。", + "apihelp-feedcontributions-param-tagfilter": "篩選有這些標籤的貢獻。", "apihelp-feedcontributions-param-deletedonly": "僅顯示已刪除的貢獻。", "apihelp-feedcontributions-param-toponly": "只顯示最新修訂的編輯。", "apihelp-feedcontributions-param-newonly": "只顯示建立頁面的編輯。", - "apihelp-feedcontributions-param-hideminor": "隱藏小修改。", + "apihelp-feedcontributions-param-hideminor": "隱藏小編輯。", "apihelp-feedcontributions-param-showsizediff": "顯示修訂版本之間的差異大小。", "apihelp-feedcontributions-example-simple": "返回使用者Example的貢獻。", "apihelp-feedrecentchanges-summary": "返回最近變更摘要。", "apihelp-feedrecentchanges-param-feedformat": "摘要格式。", "apihelp-feedrecentchanges-param-namespace": "用於限制結果的命名空間。", "apihelp-feedrecentchanges-param-invert": "除所選定者外的所有命名空間。", + "apihelp-feedrecentchanges-param-days": "用於限制結果的天數。", "apihelp-feedrecentchanges-param-limit": "回傳的結果數量上限。", - "apihelp-feedrecentchanges-param-hideminor": "隱藏小編輯。", + "apihelp-feedrecentchanges-param-from": "顯示自那時以來的更改。", + "apihelp-feedrecentchanges-param-hideminor": "隱藏小更改。", "apihelp-feedrecentchanges-param-hidebots": "隱藏由機器人做的變更。", "apihelp-feedrecentchanges-param-hideanons": "隱藏匿名使用者做的變更。", "apihelp-feedrecentchanges-param-hideliu": "隱藏已註冊使用者做的變更。", "apihelp-feedrecentchanges-param-hidepatrolled": "隱藏已巡查的變更。", + "apihelp-feedrecentchanges-param-hidemyself": "隱藏由目前使用者做出的更改。", + "apihelp-feedrecentchanges-param-hidecategorization": "隱藏分類成員更改。", + "apihelp-feedrecentchanges-param-tagfilter": "按標籤篩選。", "apihelp-feedrecentchanges-example-simple": "顯示近期變更。", "apihelp-feedrecentchanges-example-30days": "顯示近期30天內的變動", "apihelp-feedwatchlist-summary": "返回監視清單 feed。", "apihelp-feedwatchlist-param-feedformat": "Feed 的格式。", + "apihelp-feedwatchlist-param-linktosections": "若可以的話,直接連結至更改過的段落。", + "apihelp-filerevert-summary": "回退檔案至舊的版本。", + "apihelp-filerevert-param-filename": "目標檔案名稱,不需包含「File:」這樣的前綴字元。", "apihelp-filerevert-param-comment": "上載意見。", + "apihelp-help-summary": "顯示指定模組的說明。", "apihelp-help-example-main": "主模組使用說明", "apihelp-help-example-recursive": "一個頁面中的所有說明。", "apihelp-help-example-help": "說明模組自身的說明。", "apihelp-help-example-query": "兩個查詢子模組的說明。", "apihelp-imagerotate-summary": "旋轉一張或多張圖片。", "apihelp-imagerotate-param-rotation": "順時針旋轉圖片的度數。", + "apihelp-imagerotate-param-tags": "在更新日誌裡套用到項目的標籤。", + "apihelp-imagerotate-example-simple": "90 度旋轉 File:Example.png。", + "apihelp-imagerotate-example-generator": "180 度旋轉所有在 Category:Flip 裡的圖片。", "apihelp-import-summary": "從其它 wiki 或 XML 檔案來匯入頁面。", "apihelp-import-param-summary": "匯入摘要。", "apihelp-import-param-xml": "上載的 XML 檔。", @@ -153,6 +195,8 @@ "apihelp-import-param-templates": "用於跨 wiki 匯入:匯入一切包含的模板。", "apihelp-import-param-namespace": "匯入至此命名空間。無法與 $1rootpage 一起使用。", "apihelp-import-param-rootpage": "匯入作為此頁面的子頁面。無法與 $1namespace 一起使用。", + "apihelp-import-example-import": "以完整歷史紀錄匯入 [[meta:Help:ParserFunctions]] 至命名空間 100。", + "apihelp-linkaccount-example-link": "開始進行從 Example 連結至帳號的程序。", "apihelp-login-summary": "登入並取得身分核對 cookies", "apihelp-login-param-name": "使用者名稱。", "apihelp-login-param-password": "密碼。", @@ -160,6 +204,8 @@ "apihelp-login-example-login": "登入", "apihelp-logout-summary": "登出並清除 session 資料。", "apihelp-logout-example-logout": "登出當前使用者", + "apihelp-managetags-summary": "執行相關到更改標籤的管理任務。", + "apihelp-managetags-param-ignorewarnings": "是否在處理期間發生問題時忽略任何警告。", "apihelp-managetags-param-tags": "在標籤管理日誌裡更改套用到項目的標籤。", "apihelp-mergehistory-summary": "合併頁面歷史", "apihelp-mergehistory-param-reason": "合併歷史的原因。", @@ -185,14 +231,39 @@ "apihelp-opensearch-param-suggest": "若[[mw:Special:MyLanguage/Manual:$wgEnableOpenSearchSuggest|$wgEnableOpenSearchSuggest]]設定為false,則不做任何事。", "apihelp-opensearch-param-redirects": "如何處理重定向:\n;return:傳回重定向本身。\n;resolve:傳回目標頁面,傳回的結果數目可能少於$1limit。\n由於歷史原因,$1format=json的預設值為「return」,其他格式則為「resolve」。", "apihelp-opensearch-param-format": "輸出的格式。", + "apihelp-opensearch-example-te": "找出以 Te 為開頭的頁面。", "apihelp-options-summary": "更改目前使用者的偏好設定。", "apihelp-options-param-reset": "重設偏好設定為網站預設值。", + "apihelp-options-param-optionname": "選項名稱,其應設定為由 $1optionvalue 所提供的值。", + "apihelp-options-param-optionvalue": "由 $1optionname 所指定,用於選項的值。", "apihelp-options-example-reset": "重設所有偏好設定", "apihelp-options-example-change": "更改skin和hideminor偏好設定。", + "apihelp-options-example-complex": "重置所有偏好設定,然後再設定 skin 與 nickname。", "apihelp-paraminfo-summary": "獲得有關 API 模組的資訊。", + "apihelp-paraminfo-param-helpformat": "說明字串的格式。", + "apihelp-paraminfo-param-formatmodules": "格式模組名稱清單(format 參數的值)。請改用 $1modules 。", + "apihelp-paraminfo-example-1": "顯示 [[Special:ApiHelp/parse|action=parse]]、[[Special:ApiHelp/jsonfm|format=jsonfm]]、[[Special:ApiHelp/query+allpages|action=query&list=allpages]]、和 [[Special:ApiHelp/query+siteinfo|action=query&meta=siteinfo]] 的資訊。", + "apihelp-paraminfo-example-2": "顯示 [[Special:ApiHelp/query|action=query]] 所有子模組的資訊。", + "apihelp-parse-summary": "解析內容併回傳解析器輸出。", + "apihelp-parse-param-text": "要解析的文字。使用 $1title 或 $1contentmodel 來控制內容模組。", "apihelp-parse-param-summary": "解析摘要。", "apihelp-parse-param-pageid": "解析此頁面的內容。覆蓋 $1page。", + "apihelp-parse-param-redirects": "若 $1page 或者 $1pageid 被設定成重新導向,則解析它。", "apihelp-parse-param-prop": "要取得的資訊部份:", + "apihelp-parse-paramvalue-prop-text": "提供 wiki 文字的解析文字。", + "apihelp-parse-paramvalue-prop-langlinks": "在已解析的 wiki 文字提供語言連結。", + "apihelp-parse-paramvalue-prop-categories": "在已解析的 wiki 文字提供分類。", + "apihelp-parse-paramvalue-prop-categorieshtml": "提供分類的 HTML 版本。", + "apihelp-parse-paramvalue-prop-links": "在已解析的 wiki 文字提供內部連結。", + "apihelp-parse-paramvalue-prop-templates": "在已解析的 wiki 文字提供模板。", + "apihelp-parse-paramvalue-prop-images": "在已解析的 wiki 文字提供圖片。", + "apihelp-parse-paramvalue-prop-externallinks": "在已解析的 wiki 文字提供外部連結。", + "apihelp-parse-paramvalue-prop-sections": "在已解析的 wiki 文字提供段落。", + "apihelp-parse-paramvalue-prop-revid": "添加已解析頁面的修訂 ID。", + "apihelp-parse-paramvalue-prop-displaytitle": "添加已解析 wiki 文字的標題。", + "apihelp-parse-paramvalue-prop-headitems": "提供放置頁面裡的 <head> 之項目。", + "apihelp-parse-paramvalue-prop-headhtml": "取得頁面已解析的 <head>。", + "apihelp-parse-paramvalue-prop-iwlinks": "在已解析的 wiki 文字提供跨 wiki 連結。", "apihelp-parse-param-disablepp": "請改用$1disablelimitreport。", "apihelp-parse-param-preview": "在預覽模式下解析。", "apihelp-parse-example-page": "解析頁面。", @@ -202,6 +273,7 @@ "apihelp-patrol-summary": "巡查頁面或修訂。", "apihelp-patrol-param-rcid": "要巡查的最近變更 ID。", "apihelp-patrol-param-revid": "要巡查的修訂 ID。", + "apihelp-patrol-param-tags": "在巡查日誌裡更改套用到項目的標籤。", "apihelp-patrol-example-rcid": "巡查一次最近變更。", "apihelp-patrol-example-revid": "巡查一個修訂。", "apihelp-protect-summary": "變更頁面的保護層級。", @@ -215,51 +287,120 @@ "apihelp-protect-param-watch": "如果被設定,就將被(解除)保護的頁面加至目前使用者的監視列表。", "apihelp-protect-param-watchlist": "無條件地將該頁面加入至或移除自目前使用者的監視列表、使用偏好設定或不更改監視。", "apihelp-protect-example-protect": "保護一個頁面。", + "apihelp-protect-example-unprotect": "透過設定為 all(註:代表任何人都可以執行操作),來解除對頁面的保護。", + "apihelp-protect-example-unprotect2": "透過設定為沒有限制,來解除對頁面的保護。", "apihelp-purge-summary": "為指定標題清除快取。", "apihelp-purge-param-forcelinkupdate": "更新連結表格。", + "apihelp-purge-example-simple": "清除 Main Page 與 API 頁面。", "apihelp-purge-example-generator": "重新整理主要命名空間的前10個頁面。", "apihelp-query-summary": "擷取來自及有關MediaWiki的數據。", + "apihelp-query-param-prop": "替已查詢頁面所要取得的屬性。", "apihelp-query-param-list": "要取得的清單。", "apihelp-query-param-meta": "要取得的詮釋資料。", + "apihelp-query-param-export": "匯出所有指定或已產生頁面的目前修訂。", + "apihelp-query-param-iwurl": "若標題是跨 wiki 連結,是否取得完整的 URL。", + "apihelp-query-param-rawcontinue": "回傳原始的 query-continue 資料來繼續。", + "apihelp-query-example-revisions": "索取 Main Page 的[[Special:ApiHelp/query+siteinfo|站台資訊]]與[[Special:ApiHelp/query+revisions|修訂]]。", + "apihelp-query-example-allpages": "索取以 API/ 為開頭的頁面修訂。", "apihelp-query+allcategories-summary": "列舉所有分類。", "apihelp-query+allcategories-param-from": "起始列舉的分類。", "apihelp-query+allcategories-param-to": "終止列舉的分類。", "apihelp-query+allcategories-param-prefix": "搜尋以此值為開頭的所有分類標題。", "apihelp-query+allcategories-param-dir": "排序的方向。", + "apihelp-query+allcategories-param-min": "僅回傳至少有這樣多成員的分類。", + "apihelp-query+allcategories-param-max": "僅回傳最多有這樣多成員的分類。", "apihelp-query+allcategories-param-limit": "要回傳的分類數量。", "apihelp-query+allcategories-param-prop": "要取得的屬性。", "apihelp-query+allcategories-paramvalue-prop-size": "在分類裡添加頁面數。", "apihelp-query+alldeletedrevisions-summary": "依使用者或所在命名空間來列出所有已刪除的修訂。", + "apihelp-query+alldeletedrevisions-paraminfo-useronly": "僅與 $3user 一同使用。", + "apihelp-query+alldeletedrevisions-paraminfo-nonuseronly": "不能與 $3user 一同使用。", "apihelp-query+alldeletedrevisions-param-start": "起始列舉的時間戳記。", + "apihelp-query+alldeletedrevisions-param-end": "終止列舉的時間戳記。", "apihelp-query+alldeletedrevisions-param-from": "在此標題開始列出。", "apihelp-query+alldeletedrevisions-param-to": "在此標題停止列出。", + "apihelp-query+alldeletedrevisions-param-prefix": "搜尋以此值為開頭的所有頁面標題。", + "apihelp-query+alldeletedrevisions-param-tag": "僅列出以此標籤所標記的修訂。", "apihelp-query+alldeletedrevisions-param-user": "此列出由該使用者作出的修訂。", "apihelp-query+alldeletedrevisions-param-excludeuser": "不要列出由該使用者作出的修訂。", "apihelp-query+alldeletedrevisions-param-namespace": "僅列出此命名空間的頁面。", + "apihelp-query+alldeletedrevisions-example-user": "列出由使用者 Example 做出的最近 50 個貢獻。", + "apihelp-query+alldeletedrevisions-example-ns-main": "列出在主命名空間的前 50 個已刪除修訂。", + "apihelp-query+allfileusages-summary": "列出所有檔案用途,包含不存在的。", + "apihelp-query+allfileusages-param-from": "要起始列舉的檔案標題。", + "apihelp-query+allfileusages-param-to": "要終止列舉的檔案標題。", + "apihelp-query+allfileusages-param-prefix": "搜尋以此值為開頭的所有檔案標題。", + "apihelp-query+allfileusages-param-prop": "要包含到的資訊部份:", "apihelp-query+allfileusages-paramvalue-prop-title": "添加檔案標題。", "apihelp-query+allfileusages-param-limit": "要回傳的項目總數。", "apihelp-query+allfileusages-param-dir": "列出時所採用的方向。", "apihelp-query+allfileusages-example-unique": "列出唯一的檔案標題。", + "apihelp-query+allfileusages-example-unique-generator": "取得所有檔案標題,標記為遺失。", "apihelp-query+allfileusages-example-generator": "取得包含檔案的頁面。", + "apihelp-query+allimages-summary": "按順序列舉所有圖片。", + "apihelp-query+allimages-param-sort": "作為排序順序的屬性。", "apihelp-query+allimages-param-dir": "列出時所採用的方向。", + "apihelp-query+allimages-param-from": "要開始列舉的圖片標題。僅能與 $1sort=name 一起使用。", + "apihelp-query+allimages-param-to": "要停止列舉的圖片標題。僅能與 $1sort=name 一起使用。", + "apihelp-query+allimages-param-start": "要開始列舉的時間戳記。僅能與 $1sort=timestamp 一起使用。", + "apihelp-query+allimages-param-end": "要停止列舉的時間戳記。僅能與 $1sort=timestamp 一起使用。", + "apihelp-query+allimages-param-prefix": "搜尋所有以此值為開頭的圖片。僅能與 $1sort=name 一起使用。", "apihelp-query+allimages-param-minsize": "限制圖片至少要有這樣多的位元組。", "apihelp-query+allimages-param-maxsize": "限制圖片最多只能這樣多的位元組。", + "apihelp-query+allimages-param-sha1": "圖片的 SHA1 雜湊值。覆蓋 $1sha1base36。", + "apihelp-query+allimages-param-sha1base36": "以 base 36 的圖片 SHA1 雜湊值(使用在 MediaWiki)。", "apihelp-query+allimages-param-mime": "所要搜尋的 MIME 類型,例如:image/jpeg。", "apihelp-query+allimages-param-limit": "要回傳的圖片總數。", + "apihelp-query+allimages-example-B": "搜尋以字母 B 為開頭的所有檔案清單。", + "apihelp-query+allimages-example-recent": "顯示近期已上傳檔案的清單,類似於 [[Special:NewFiles]]。", + "apihelp-query+allimages-example-generator": "顯示 4 個以 T 為開頭的檔案之資訊。", + "apihelp-query+alllinks-param-from": "要起始列舉的連結標題。", + "apihelp-query+alllinks-param-to": "要終止列舉的連結標題。", + "apihelp-query+alllinks-param-prefix": "搜尋以此值為開頭的所有連結標題。", + "apihelp-query+alllinks-param-prop": "要包含的資訊部份:", "apihelp-query+alllinks-paramvalue-prop-title": "添加連結標題。", "apihelp-query+alllinks-param-namespace": "要列舉的命名空間。", "apihelp-query+alllinks-param-limit": "要回傳的項目總數。", "apihelp-query+alllinks-param-dir": "列出時所採用的方向。", + "apihelp-query+alllinks-example-unique": "列出唯一的連結標題。", + "apihelp-query+alllinks-example-unique-generator": "取得所有已連結標題,標記為遺失。", + "apihelp-query+alllinks-example-generator": "取得包含連結的頁面。", "apihelp-query+allmessages-summary": "返回來自該網站的訊息。", "apihelp-query+allmessages-param-prop": "要取得的屬性。", + "apihelp-query+allmessages-param-nocontent": "若有設定,請不要包含在輸出裡的訊息內容。", + "apihelp-query+allmessages-param-args": "要替代訊息的引數。", + "apihelp-query+allmessages-param-filter": "僅回傳名稱包含此字串的訊息。", + "apihelp-query+allmessages-param-customised": "僅回傳在此自定義狀況下的訊息。", "apihelp-query+allmessages-param-lang": "以此語言來回傳訊息。", "apihelp-query+allmessages-param-from": "以此訊息來回傳訊息開頭。", "apihelp-query+allmessages-param-to": "以此訊息來回傳訊息結尾。", + "apihelp-query+allmessages-param-prefix": "回傳帶有前綴的訊息。", + "apihelp-query+allmessages-example-ipb": "顯示以 ipb- 起始的訊息。", + "apihelp-query+allmessages-example-de": "顯示在德語裡的 august 與 mainpage 訊息。", + "apihelp-query+allpages-summary": "依序列舉在指定命名空間的所有頁面。", + "apihelp-query+allpages-param-from": "起始列舉的頁面標題。", + "apihelp-query+allpages-param-to": "終止列舉的頁面標題。", + "apihelp-query+allpages-param-prefix": "搜尋以此值為開頭的所有頁面標題。", + "apihelp-query+allpages-param-namespace": "要列舉的命名空間。", "apihelp-query+allpages-param-filterredir": "要列出的頁面。", + "apihelp-query+allpages-param-minsize": "限制頁面至少要有這樣多的位元組。", + "apihelp-query+allpages-param-maxsize": "限制頁面最多只能這樣多的位元組。", + "apihelp-query+allpages-param-prtype": "僅限受保護的頁面。", "apihelp-query+allpages-param-limit": "要回傳的頁面總數。", + "apihelp-query+allpages-param-dir": "列出時所採用的方向。", + "apihelp-query+allpages-example-B": "顯示以字母 B 為開頭的所有頁面清單。", + "apihelp-query+allpages-example-generator": "顯示 4 個以 T 為開頭的頁面之資訊。", "apihelp-query+allredirects-summary": "列出至命名空間的所有重新導向。", + "apihelp-query+allredirects-param-from": "要起始列舉的重新導向標題。", + "apihelp-query+allredirects-param-to": "要終止列舉的重新導向標題。", + "apihelp-query+allredirects-param-prefix": "搜尋以此值為開頭的所有目標頁面。", + "apihelp-query+allredirects-param-prop": "要包含的資訊部份:", + "apihelp-query+allredirects-paramvalue-prop-title": "添加重新導向的標題。", "apihelp-query+allredirects-param-namespace": "要列舉的命名空間。", "apihelp-query+allredirects-param-limit": "要回傳的項目總數。", + "apihelp-query+allredirects-param-dir": "列出時所採用的方向。", + "apihelp-query+allredirects-example-unique": "列出唯一目標頁面。", + "apihelp-query+allredirects-example-unique-generator": "取得所有目標頁面,標記為遺失。", "apihelp-query+allredirects-example-generator": "取得包含重新導向的頁面。", "apihelp-query+allrevisions-summary": "列出所有修訂版本。", "apihelp-query+allrevisions-param-start": "起始列舉的時間戳記。", @@ -267,151 +408,415 @@ "apihelp-query+allrevisions-param-user": "此列出由該使用者作出的修訂。", "apihelp-query+allrevisions-param-excludeuser": "不要列出由該使用者作出的修訂。", "apihelp-query+allrevisions-param-namespace": "僅列出此命名空間的頁面。", + "apihelp-query+allrevisions-example-user": "列出由使用者 Example 做出的最近 50 個貢獻。", + "apihelp-query+allrevisions-example-ns-main": "列出在主命名空間的前 50 個修訂。", "apihelp-query+mystashedfiles-param-prop": "要索取的檔案屬性。", "apihelp-query+mystashedfiles-paramvalue-prop-size": "索取檔案大小與圖片尺寸。", "apihelp-query+mystashedfiles-paramvalue-prop-type": "索取檔案的 MIME 類型以及媒體類型。", "apihelp-query+mystashedfiles-param-limit": "要取得的檔案數量。", + "apihelp-query+alltransclusions-param-prop": "要包含到的資訊部份:", + "apihelp-query+alltransclusions-paramvalue-prop-title": "添加嵌入的標題。", + "apihelp-query+alltransclusions-param-namespace": "要列舉的命名空間。", "apihelp-query+alltransclusions-param-limit": "要回傳的項目總數。", "apihelp-query+alltransclusions-param-dir": "列出時所採用的方向。", + "apihelp-query+allusers-summary": "列舉所有已註冊使用者。", "apihelp-query+allusers-param-from": "起始列舉的使用者名稱。", + "apihelp-query+allusers-param-to": "終止列舉的使用者名稱。", + "apihelp-query+allusers-param-prefix": "搜尋以此值為開頭的所有使用者。", "apihelp-query+allusers-param-dir": "排序的方向。", + "apihelp-query+allusers-param-group": "僅包含在指定群組的使用者。", + "apihelp-query+allusers-param-excludegroup": "排除指定群組中的使用者", + "apihelp-query+allusers-param-prop": "要包含的資訊部份:", + "apihelp-query+allusers-paramvalue-prop-blockinfo": "添加有關使用者目前封鎖的資訊。", + "apihelp-query+allusers-paramvalue-prop-rights": "列出使用者所擁有的權限。", + "apihelp-query+allusers-paramvalue-prop-editcount": "添加使用者的編輯次數。", + "apihelp-query+allusers-param-limit": "要回傳的使用者名稱總數。", + "apihelp-query+allusers-param-witheditsonly": "僅列出有做過編輯的使用者。", + "apihelp-query+allusers-param-activeusers": "僅列出在最近 $1 {{PLURAL:$1|天|天}}裡活躍的使用者。", "apihelp-query+allusers-example-Y": "列出以Y開頭的使用者。", "apihelp-query+authmanagerinfo-summary": "取得目前身分核對狀態的資訊。", + "apihelp-query+backlinks-summary": "找出連結至指定頁面的所有頁面。", + "apihelp-query+backlinks-param-title": "要搜尋的標題。不能與 $1pageid 一起使用。", + "apihelp-query+backlinks-param-pageid": "要搜尋的頁面 ID。不能與 $1title 一起使用。", "apihelp-query+backlinks-param-namespace": "要列舉的命名空間。", "apihelp-query+backlinks-param-dir": "列出時所採用的方向。", + "apihelp-query+backlinks-example-simple": "顯示至 Main page 的連結。", + "apihelp-query+backlinks-example-generator": "取得連結至 Main page 的相關頁面資訊。", "apihelp-query+blocks-summary": "列出所有被封鎖使用者與 IP 位址。", "apihelp-query+blocks-param-start": "起始列舉的時間戳記。", "apihelp-query+blocks-param-end": "終止列舉的時間戳記。", + "apihelp-query+blocks-param-ids": "要列出的封鎖 ID 清單(可選)。", + "apihelp-query+blocks-param-users": "要搜尋的使用者清單(可選)。", + "apihelp-query+blocks-param-ip": "取得套用在此 IP 位址或 CIDR 範圍的所有封鎖與所包含的範圍封鎖。不可與 $3users 一起使用。CIDR 範圍不可超過 IPv4/$1 或 IPv6/$2。", "apihelp-query+blocks-param-prop": "要取得的屬性。", "apihelp-query+blocks-paramvalue-prop-id": "添加封鎖 ID。", "apihelp-query+blocks-paramvalue-prop-user": "添加已封鎖使用者的使用者名稱。", "apihelp-query+blocks-paramvalue-prop-userid": "添加已封鎖使用者的使用者 ID。", "apihelp-query+blocks-paramvalue-prop-by": "添加進行封鎖中的使用者之使用者名稱。", "apihelp-query+blocks-paramvalue-prop-byid": "添加進行封鎖中的使用者之使用者 ID。", + "apihelp-query+blocks-paramvalue-prop-timestamp": "添加當封鎖生效的時間戳記。", + "apihelp-query+blocks-paramvalue-prop-expiry": "添加當封鎖到期的時間戳記。", + "apihelp-query+blocks-paramvalue-prop-reason": "添加封鎖的原因。", + "apihelp-query+blocks-example-simple": "列出封鎖。", + "apihelp-query+blocks-example-users": "列出使用者 Alice 與 Bob 的封鎖。", + "apihelp-query+categories-summary": "列出頁面隸屬的所有分類。", + "apihelp-query+categories-param-prop": "為各分類所要取得的額外屬性:", + "apihelp-query+categories-param-show": "要顯示出的分類種類。", "apihelp-query+categories-param-limit": "要回傳的分類數量。", + "apihelp-query+categories-param-dir": "列出時所採用的方向。", + "apihelp-query+categories-example-simple": "取得屬於在頁面 Albert Einstein 的分類清單。", + "apihelp-query+categories-example-generator": "取得使用在 Albert Einstein 頁面裡所有分類的相關資訊。", "apihelp-query+categoryinfo-summary": "回傳有關指定分類的資訊。", + "apihelp-query+categoryinfo-example-simple": "取得有關 Category:Foo 與 Category:Bar 的資訊。", "apihelp-query+categorymembers-summary": "在指定的分類中列出所有頁面。", + "apihelp-query+categorymembers-param-prop": "要包含的資訊部份:", "apihelp-query+categorymembers-paramvalue-prop-ids": "添加頁面 ID。", + "apihelp-query+categorymembers-paramvalue-prop-title": "添加標題與頁面的命名空間 ID。", + "apihelp-query+categorymembers-paramvalue-prop-timestamp": "添加在頁面有被包含時的時間戳記。", "apihelp-query+categorymembers-param-limit": "回傳的頁面數量上限。", + "apihelp-query+categorymembers-param-sort": "作為排序順序的屬性。", + "apihelp-query+categorymembers-param-start": "起始列出的時間戳記。僅能與 $1sort=timestamp 一起使用。", + "apihelp-query+categorymembers-param-end": "結束列出的時間戳記。僅能與 $1sort=timestamp 一起使用。", "apihelp-query+categorymembers-param-startsortkey": "請改用 $1starthexsortkey。", "apihelp-query+categorymembers-param-endsortkey": "請改用 $1endhexsortkey。", + "apihelp-query+categorymembers-example-simple": "取得在 Category:Physics 裡前 10 項的頁面。", + "apihelp-query+categorymembers-example-generator": "取得在 Category:Physics 裡前 10 個頁面的頁面資訊。", "apihelp-query+contributors-param-limit": "要回傳的貢獻人員數量。", + "apihelp-query+contributors-example-simple": "顯示頁面 Main Page 的貢獻者。", "apihelp-query+deletedrevisions-summary": "取得已刪除修訂的資訊。", + "apihelp-query+deletedrevisions-param-tag": "僅列出以此標籤所標記的修訂。", "apihelp-query+deletedrevisions-param-user": "此列出由該使用者作出的修訂。", "apihelp-query+deletedrevisions-param-excludeuser": "不要列出由該使用者作出的修訂。", + "apihelp-query+deletedrevisions-example-revids": "列出已刪除修訂 123456 的資訊。", "apihelp-query+deletedrevs-summary": "列出已刪除的修訂。", "apihelp-query+deletedrevs-paraminfo-modes": "{{PLURAL:$1|模式|模式}}:$2", "apihelp-query+deletedrevs-param-start": "起始列舉的時間戳記。", "apihelp-query+deletedrevs-param-end": "終止列舉的時間戳記。", "apihelp-query+deletedrevs-param-from": "在此標題開始列出。", "apihelp-query+deletedrevs-param-to": "在此標題停止列出。", + "apihelp-query+deletedrevs-param-prefix": "搜尋以此值為開頭的所有頁面標題。", + "apihelp-query+deletedrevs-param-unique": "各頁面僅列出一個修訂。", + "apihelp-query+deletedrevs-param-tag": "僅列出以此標籤所標記的修訂。", "apihelp-query+deletedrevs-param-user": "此列出由該使用者作出的修訂。", "apihelp-query+deletedrevs-param-excludeuser": "不要列出由該使用者作出的修訂。", "apihelp-query+deletedrevs-param-namespace": "僅列出此命名空間的頁面。", + "apihelp-query+deletedrevs-param-limit": "修訂能列出的最大數量。", + "apihelp-query+deletedrevs-example-mode3-main": "列出在主命名空間的前 50 個已刪除修訂(模式 3)。", + "apihelp-query+deletedrevs-example-mode3-talk": "列出在{{ns:talk}}命名空間的前 50 個已刪除頁面(模式 3)。", "apihelp-query+disabled-summary": "已停用此查詢模組。", "apihelp-query+duplicatefiles-param-limit": "要回傳的重複檔案數量。", "apihelp-query+duplicatefiles-param-dir": "列出時所採用的方向。", + "apihelp-query+duplicatefiles-param-localonly": "僅查看在本地端儲存庫的檔案。", + "apihelp-query+duplicatefiles-example-simple": "尋找重複 [[:File:Albert Einstein Head.jpg]] 的檔案。", "apihelp-query+duplicatefiles-example-generated": "查看全部有重複到的檔案。", + "apihelp-query+embeddedin-param-title": "要搜尋的標題。不能與 $1pageid 一起使用。", + "apihelp-query+embeddedin-param-pageid": "要搜尋的頁面 ID。不能與 $1title 一起使用。", + "apihelp-query+embeddedin-param-namespace": "要列舉的命名空間。", "apihelp-query+embeddedin-param-dir": "列出時所採用的方向。", "apihelp-query+embeddedin-param-filterredir": "如何過濾重新導向。", "apihelp-query+embeddedin-param-limit": "要回傳的頁面總數。", "apihelp-query+extlinks-summary": "回傳所有指定頁面的外部 URL (非 interwiki)。", "apihelp-query+extlinks-param-limit": "要回傳的連結數量。", + "apihelp-query+extlinks-example-simple": "取得 Main Page 的外部連結清單。", + "apihelp-query+exturlusage-summary": "列舉包含指定 URL 的頁面。", + "apihelp-query+exturlusage-param-prop": "要包含的資訊部份:", "apihelp-query+exturlusage-paramvalue-prop-ids": "添加頁面 ID。", + "apihelp-query+exturlusage-paramvalue-prop-title": "添加標題與頁面的命名空間 ID。", + "apihelp-query+exturlusage-paramvalue-prop-url": "添加用於頁面的 URL。", + "apihelp-query+exturlusage-param-query": "不帶協定的搜尋字串。請查看 [[Special:LinkSearch]]。請留空以列出所有外部連結。", "apihelp-query+exturlusage-param-namespace": "要列舉的頁面命名空間。", "apihelp-query+exturlusage-param-limit": "要回傳的頁面數量。", + "apihelp-query+exturlusage-example-simple": "顯示連結至 https://www.mediawiki.org 的頁面。", + "apihelp-query+filearchive-summary": "依序列舉所有已刪除檔案。", + "apihelp-query+filearchive-param-from": "起始列舉的圖片標題。", + "apihelp-query+filearchive-param-to": "終止列舉的圖片標題。", + "apihelp-query+filearchive-param-prefix": "搜尋以此值為開頭的所有圖片標題。", "apihelp-query+filearchive-param-limit": "要回傳的圖片總數。", "apihelp-query+filearchive-param-dir": "列出時所採用的方向。", "apihelp-query+filearchive-param-sha1": "圖片的 SHA1 雜湊值。覆蓋 $1sha1base36。", + "apihelp-query+filearchive-param-sha1base36": "以 base 36 的圖片 SHA1 雜湊值(使用在 MediaWiki)。", "apihelp-query+filearchive-param-prop": "要取得的圖片資訊:", "apihelp-query+filearchive-paramvalue-prop-sha1": "替圖片添加 SHA-1 雜湊值。", + "apihelp-query+filearchive-paramvalue-prop-timestamp": "添加上傳版本的時間戳記。", + "apihelp-query+filearchive-paramvalue-prop-user": "添加上傳該圖片版本的使用者。", + "apihelp-query+filearchive-paramvalue-prop-description": "添加圖片版本的描述。", + "apihelp-query+filearchive-paramvalue-prop-parseddescription": "解析版本的描述。", "apihelp-query+filearchive-paramvalue-prop-mime": "添加圖片的 MIME。", "apihelp-query+filearchive-paramvalue-prop-mediatype": "添加圖片的媒體類型。", + "apihelp-query+filearchive-paramvalue-prop-metadata": "列出圖片版本的 Exif 詮釋資料。", + "apihelp-query+filearchive-paramvalue-prop-bitdepth": "添加版本的位元深度。", + "apihelp-query+filearchive-paramvalue-prop-archivename": "添加非最新版本的存檔版本檔案名稱。", + "apihelp-query+filearchive-example-simple": "顯示所有已刪除檔案的清單。", + "apihelp-query+filerepoinfo-paramvalue-prop-displayname": "人類可讀的儲存庫 wiki 名稱。", + "apihelp-query+filerepoinfo-paramvalue-prop-initialCapital": "檔案是否隱式地以大寫字母開頭。", + "apihelp-query+filerepoinfo-paramvalue-prop-local": "儲存庫是否為本地端。", + "apihelp-query+filerepoinfo-paramvalue-prop-rootUrl": "圖片路徑的根 URL 路徑。", + "apihelp-query+filerepoinfo-paramvalue-prop-scriptDirUrl": "用於儲存庫 wiki 的 MediaWiki 安裝之根 URL 路徑。", + "apihelp-query+filerepoinfo-paramvalue-prop-thumbUrl": "縮圖路徑的根 URL 路徑。", + "apihelp-query+filerepoinfo-paramvalue-prop-url": "公共區域 URL 路徑。", + "apihelp-query+filerepoinfo-example-simple": "取得檔案儲存庫的資訊。", + "apihelp-query+fileusage-summary": "尋找使用到指定檔案的所有頁面。", "apihelp-query+fileusage-param-prop": "要取得的屬性。", "apihelp-query+fileusage-paramvalue-prop-pageid": "各頁面的頁面 ID。", "apihelp-query+fileusage-paramvalue-prop-title": "各頁面的標題。", "apihelp-query+fileusage-paramvalue-prop-redirect": "若頁面為重新導向,則做出標記。", "apihelp-query+fileusage-param-namespace": "僅包含這些命名空間的頁面。", "apihelp-query+fileusage-param-limit": "要回傳的數量。", + "apihelp-query+fileusage-param-show": "僅顯示符合這些準則的項目:\n;redirect:僅顯示重新導向。\n;!redirect:僅顯示非重新導向。", + "apihelp-query+fileusage-example-simple": "取得使用到 [[:File:Example.jpg]] 的頁面清單。", + "apihelp-query+fileusage-example-generator": "取得使用到 [[:File:Example.jpg]] 的頁面相關資訊。", "apihelp-query+imageinfo-summary": "回傳檔案資訊與上傳日誌。", "apihelp-query+imageinfo-param-prop": "要取得的檔案資訊:", + "apihelp-query+imageinfo-paramvalue-prop-timestamp": "添加上傳版本的時間戳記。", + "apihelp-query+imageinfo-paramvalue-prop-user": "添加上傳了各檔案版本的使用者。", + "apihelp-query+imageinfo-paramvalue-prop-userid": "添加上傳了各檔案版本的使用者 ID。", + "apihelp-query+imageinfo-paramvalue-prop-comment": "版本的註釋。", + "apihelp-query+imageinfo-paramvalue-prop-parsedcomment": "解析版本上的註釋。", + "apihelp-query+imageinfo-paramvalue-prop-canonicaltitle": "添加檔案的規範標題。", + "apihelp-query+imageinfo-paramvalue-prop-url": "提供檔案與描述頁面的 URL。", "apihelp-query+imageinfo-paramvalue-prop-sha1": "替檔案添加 SHA-1 雜湊值。", "apihelp-query+imageinfo-paramvalue-prop-mime": "替檔案添加 MIME 類型。", "apihelp-query+imageinfo-paramvalue-prop-mediatype": "添加檔案的媒體類型。", + "apihelp-query+imageinfo-paramvalue-prop-badfile": "無論檔案是否在 [[MediaWiki:Bad image list]] 都添加", "apihelp-query+imageinfo-param-limit": "每個檔案要回傳的檔案修訂數量。", + "apihelp-query+imageinfo-param-start": "列出的起始時間戳記。", + "apihelp-query+imageinfo-param-end": "列出的終止時間戳記。", + "apihelp-query+imageinfo-param-urlheight": "與 $1urlwidth 相似。", + "apihelp-query+imageinfo-param-localonly": "僅查看在本地端儲存庫的檔案。", + "apihelp-query+imageinfo-example-simple": "取得關於 [[:File:Albert Einstein Head.jpg]] 目前版本的資訊.", + "apihelp-query+imageinfo-example-dated": "索取 [[:File:Test.jpg]] 自 2008 年以來的版本資訊。", "apihelp-query+images-summary": "回傳指定頁面中包含的所有檔案。", "apihelp-query+images-param-limit": "要回傳的檔案數量。", "apihelp-query+images-param-dir": "列出時所採用的方向。", "apihelp-query+images-example-simple": "取得使用在 [[Main Page]] 的檔案清單。", + "apihelp-query+images-example-generator": "取得在 [[Main Page]] 所有使用到檔案的相關資訊。", + "apihelp-query+imageusage-summary": "尋找使用到指定圖片標題的所有頁面。", + "apihelp-query+imageusage-param-title": "要搜尋的標題。不能與 $1pageid 一起使用。", + "apihelp-query+imageusage-param-pageid": "要搜尋的頁面 ID。不能與 $1title 一起使用。", "apihelp-query+imageusage-param-namespace": "要列舉的命名空間。", "apihelp-query+imageusage-param-dir": "列出時所採用的方向。", + "apihelp-query+imageusage-example-simple": "顯示有使用 [[:File:Albert Einstein Head.jpg]] 的頁面。", + "apihelp-query+imageusage-example-generator": "取得關於有使用到 [[:File:Albert Einstein Head.jpg]] 的頁面資訊.", "apihelp-query+info-summary": "取得基本頁面訊息。", "apihelp-query+info-param-prop": "要取得的額外屬性:", + "apihelp-query+info-paramvalue-prop-protection": "列出各頁面的保護層級。", + "apihelp-query+info-paramvalue-prop-watched": "列出各頁面的監視狀態。", + "apihelp-query+info-paramvalue-prop-watchers": "監視者的數目,如有允許的話。", + "apihelp-query+info-paramvalue-prop-visitingwatchers": "有訪問頁面近期編輯數的各頁面監視者數目,如有允許的話。", + "apihelp-query+info-paramvalue-prop-readable": "使用者是否可閱讀此頁面。", + "apihelp-query+info-param-testactions": "測試目前使用者是否可執行頁面上的某項操作。", + "apihelp-query+info-param-token": "請改用 [[Special:ApiHelp/query+tokens|action=query&meta=tokens]]。", + "apihelp-query+info-example-simple": "取得有關頁面 Main Page 的資訊。", + "apihelp-query+iwbacklinks-summary": "找出連結至指定跨 wiki 連結的所有頁面。", + "apihelp-query+iwbacklinks-param-prefix": "跨 wiki 前綴。", + "apihelp-query+iwbacklinks-param-limit": "要回傳的頁面總數。", "apihelp-query+iwbacklinks-param-prop": "要取得的屬性。", + "apihelp-query+iwbacklinks-paramvalue-prop-iwprefix": "添加跨 wiki 前綴。", + "apihelp-query+iwbacklinks-paramvalue-prop-iwtitle": "添加跨 wiki 標題。", + "apihelp-query+iwbacklinks-param-dir": "列出時所採用的方向。", + "apihelp-query+iwbacklinks-example-simple": "取得連結至 [[wikibooks:Test]] 的頁面。", + "apihelp-query+iwbacklinks-example-generator": "取得連結至 [[wikibooks:Test]] 的頁面相關資訊。", "apihelp-query+iwlinks-summary": "回傳指定頁面的所有 interwiki 連結。", + "apihelp-query+iwlinks-param-url": "是否取得完整的 URL(不能與 $1prop 一同使用)。", "apihelp-query+iwlinks-paramvalue-prop-url": "添加完整的 URL。", "apihelp-query+iwlinks-param-limit": "要回傳的跨 Wiki 連結數量。", + "apihelp-query+iwlinks-param-prefix": "僅回傳帶有此前綴的跨 wiki 連結。", + "apihelp-query+iwlinks-param-dir": "列出時所採用的方向。", + "apihelp-query+iwlinks-example-simple": "從頁面 Main Page 取得跨 wiki 連結。", + "apihelp-query+langbacklinks-summary": "找出連結至指定語言連結的所有頁面。", + "apihelp-query+langbacklinks-param-lang": "用於語言的語言連結。", + "apihelp-query+langbacklinks-param-title": "要搜尋的語言連結。必須與$1lang一同使用。", "apihelp-query+langbacklinks-param-limit": "要回傳的頁面總數。", "apihelp-query+langbacklinks-param-prop": "要取得的屬性。", + "apihelp-query+langbacklinks-paramvalue-prop-lllang": "添加用於語言連結的語言代碼。", + "apihelp-query+langbacklinks-paramvalue-prop-lltitle": "添加語言連結標題。", + "apihelp-query+langbacklinks-param-dir": "列出時所採用的方向。", + "apihelp-query+langbacklinks-example-simple": "取得連結至 [[:fr:Test]] 的頁面。", + "apihelp-query+langbacklinks-example-generator": "取得連結至 [[:fr:Test]] 的頁面相關資訊。", "apihelp-query+langlinks-summary": "回傳指定頁面的所有跨語言連結。", "apihelp-query+langlinks-param-limit": "要回傳的 langlinks 數量。", + "apihelp-query+langlinks-param-url": "是否取得完整的 URL(不能與 $1prop 一同使用)。", + "apihelp-query+langlinks-param-prop": "為各跨語言連結所要取得的額外屬性:", "apihelp-query+langlinks-paramvalue-prop-url": "添加完整的 URL。", + "apihelp-query+langlinks-paramvalue-prop-autonym": "添加本地語言名稱。", + "apihelp-query+langlinks-param-lang": "僅回傳帶有此語言代碼的語言連結。", "apihelp-query+langlinks-param-dir": "列出時所採用的方向。", "apihelp-query+langlinks-param-inlanguagecode": "用於本地化語言名稱的語言代碼。", + "apihelp-query+langlinks-example-simple": "從頁面 Main Page 取得跨語言連結。", "apihelp-query+links-summary": "回傳指定頁面的所有連結。", + "apihelp-query+links-param-namespace": "僅顯示在這些命名空間的連結。", "apihelp-query+links-param-limit": "要回傳的連結數量。", + "apihelp-query+links-param-dir": "列出時所採用的方向。", + "apihelp-query+links-example-simple": "從頁面 Main Page 取得連結。", + "apihelp-query+links-example-generator": "取得在 Main Page 頁面的連結頁面相關資訊。", + "apihelp-query+linkshere-summary": "找出連結至指定頁面的所有頁面。", "apihelp-query+linkshere-param-prop": "要取得的屬性。", + "apihelp-query+linkshere-paramvalue-prop-pageid": "各頁面的頁面 ID。", "apihelp-query+linkshere-paramvalue-prop-title": "各頁面的標題。", "apihelp-query+linkshere-paramvalue-prop-redirect": "若頁面為重新導向,則做出標記。", + "apihelp-query+linkshere-param-namespace": "僅包含這些命名空間的頁面。", "apihelp-query+linkshere-param-limit": "要回傳的數量。", + "apihelp-query+linkshere-param-show": "僅顯示符合這些準則的項目:\n;redirect:僅顯示重新導向。\n;!redirect:僅顯示非重新導向。", + "apihelp-query+linkshere-example-simple": "取得連結至 [[Main Page]] 的頁面清單。", + "apihelp-query+linkshere-example-generator": "取得連結至 [[Main Page]] 的相關頁面資訊。", "apihelp-query+logevents-summary": "從日誌中獲取事件。", "apihelp-query+logevents-param-prop": "要取得的屬性。", + "apihelp-query+logevents-paramvalue-prop-ids": "添加日誌事件的 ID。", + "apihelp-query+logevents-paramvalue-prop-title": "添加日誌事件的頁面標題。", + "apihelp-query+logevents-paramvalue-prop-type": "添加日誌事件的類型。", + "apihelp-query+logevents-paramvalue-prop-user": "添加承擔日誌事件的使用者。", + "apihelp-query+logevents-paramvalue-prop-userid": "添加承擔日誌事件的使用者 ID。", + "apihelp-query+logevents-paramvalue-prop-timestamp": "添加日誌事件的時間戳記。", + "apihelp-query+logevents-paramvalue-prop-comment": "添加日誌事件的註釋。", + "apihelp-query+logevents-paramvalue-prop-parsedcomment": "添加日誌事件的解析註釋。", + "apihelp-query+logevents-paramvalue-prop-details": "列出日誌事件的額外詳細資訊。", + "apihelp-query+logevents-paramvalue-prop-tags": "列出日誌事件的標籤。", + "apihelp-query+logevents-param-type": "篩選僅為此類型的日誌項目。", "apihelp-query+logevents-param-start": "起始列舉的時間戳記。", "apihelp-query+logevents-param-end": "結束列舉的時間戳記。", + "apihelp-query+logevents-param-user": "篩選由指定使用者所產生出的項目。", + "apihelp-query+logevents-param-title": "篩選與這些頁面關聯的項目。", + "apihelp-query+logevents-param-namespace": "篩選在這些指定命名空間裡的項目。", + "apihelp-query+logevents-param-prefix": "篩選以此前綴為開頭的項目。", + "apihelp-query+logevents-param-tag": "僅列出以此標籤所標記的事件項目。", "apihelp-query+logevents-param-limit": "要回傳的事件項目總數。", + "apihelp-query+logevents-example-simple": "列出近期日誌事件。", + "apihelp-query+pagepropnames-summary": "列出所有在 wiki 使用的頁面屬性名稱。", "apihelp-query+pagepropnames-param-limit": "回傳的名稱數量上限。", + "apihelp-query+pagepropnames-example-simple": "取得前 10 個屬性名稱。", + "apihelp-query+pageprops-example-simple": "取得頁面 Main Page 與 MediaWiki 的屬性。", + "apihelp-query+pageswithprop-summary": "列出使用到指定頁面屬性的所有頁面。", + "apihelp-query+pageswithprop-param-prop": "要包含到的資訊部份:", "apihelp-query+pageswithprop-paramvalue-prop-ids": "添加頁面 ID。", + "apihelp-query+pageswithprop-paramvalue-prop-title": "添加標題與頁面的命名空間 ID。", + "apihelp-query+pageswithprop-paramvalue-prop-value": "添加頁面屬性的值。", "apihelp-query+pageswithprop-param-limit": "回傳的頁面數量上限。", + "apihelp-query+prefixsearch-summary": "執行頁面標題的前綴搜尋。", "apihelp-query+prefixsearch-param-search": "搜尋字串。", "apihelp-query+prefixsearch-param-namespace": "搜尋的命名空間。若 $1search 以有效的命名空間前綴為開頭則會被忽略。", "apihelp-query+prefixsearch-param-limit": "回傳的結果數量上限。", "apihelp-query+prefixsearch-param-offset": "要略過的結果數量。", + "apihelp-query+prefixsearch-example-simple": "搜尋開頭為 meaning 的頁面標題。", + "apihelp-query+prefixsearch-param-profile": "搜尋要使用的配置。", + "apihelp-query+protectedtitles-param-namespace": "僅列出這些命名空間的標題。", + "apihelp-query+protectedtitles-param-level": "僅列出具有這些保護層級的標題。", "apihelp-query+protectedtitles-param-limit": "要回傳的頁面總數。", "apihelp-query+protectedtitles-param-prop": "要取得的屬性。", + "apihelp-query+protectedtitles-paramvalue-prop-user": "添加做出添加保護操作的使用者。", + "apihelp-query+protectedtitles-paramvalue-prop-userid": "添加做出添加保護操作的使用者 ID。", + "apihelp-query+protectedtitles-paramvalue-prop-comment": "添加保護的註釋。", + "apihelp-query+protectedtitles-paramvalue-prop-parsedcomment": "添加保護的解析註釋。", + "apihelp-query+protectedtitles-paramvalue-prop-level": "添加保護層級。", + "apihelp-query+protectedtitles-example-simple": "列出已保護的標題。", + "apihelp-query+querypage-summary": "取得透過特殊頁面 QueryPage-based 所提供的清單。", + "apihelp-query+querypage-param-page": "特殊頁面的名稱。註:區分大小寫。", "apihelp-query+querypage-param-limit": "回傳的結果數量。", + "apihelp-query+querypage-example-ancientpages": "回傳來自 [[Special:Ancientpages]] 的結果。", + "apihelp-query+random-summary": "取得隨機頁面集合", + "apihelp-query+random-param-namespace": "僅回傳在這些命名空間的頁面。", + "apihelp-query+random-param-redirect": "請改用 $1filterredir=redirects。", + "apihelp-query+random-param-filterredir": "如何過濾重新導向。", + "apihelp-query+random-example-simple": "從主命名空間回傳兩個隨機頁面。", + "apihelp-query+random-example-generator": "從主命名空間回傳兩個隨機頁面的相關頁面資訊。", "apihelp-query+recentchanges-summary": "列舉出最近變更。", "apihelp-query+recentchanges-param-start": "起始列舉的時間戳記。", "apihelp-query+recentchanges-param-end": "結束列舉的時間戳記。", + "apihelp-query+recentchanges-param-namespace": "篩選僅為這些命名空間的更改。", "apihelp-query+recentchanges-param-user": "此列出由該使用者作出的更改。", "apihelp-query+recentchanges-param-excludeuser": "不要列出由該使用者作出的更改。", + "apihelp-query+recentchanges-param-tag": "僅列出以此標籤所標記的更改。", + "apihelp-query+recentchanges-param-prop": "包含的額外資訊部份:", + "apihelp-query+recentchanges-paramvalue-prop-comment": "添加編輯的註釋。", + "apihelp-query+recentchanges-paramvalue-prop-parsedcomment": "添加編輯的解析註釋。", + "apihelp-query+recentchanges-paramvalue-prop-flags": "添加編輯的標籤。", + "apihelp-query+recentchanges-paramvalue-prop-timestamp": "添加編輯的時間戳記。", + "apihelp-query+recentchanges-paramvalue-prop-title": "添加編輯的頁面標題。", + "apihelp-query+recentchanges-paramvalue-prop-redirect": "若頁面為重新導向則標記編輯。", + "apihelp-query+recentchanges-paramvalue-prop-tags": "列出項目的標籤。", + "apihelp-query+recentchanges-param-token": "請改用 [[Special:ApiHelp/query+tokens|action=query&meta=tokens]]。", "apihelp-query+recentchanges-param-limit": "要回傳變更總數。", + "apihelp-query+recentchanges-param-type": "要顯示的更改類型。", + "apihelp-query+recentchanges-param-toponly": "僅列出最新修訂的更改。", + "apihelp-query+recentchanges-param-title": "篩選與這些頁面關聯的項目。", "apihelp-query+recentchanges-example-simple": "最近變更清單", "apihelp-query+redirects-summary": "回傳連結至指定頁面的所有重新導向。", "apihelp-query+redirects-param-prop": "要取得的屬性。", "apihelp-query+redirects-paramvalue-prop-pageid": "各重新導向的頁面 ID。", "apihelp-query+redirects-paramvalue-prop-title": "各重新導向的標題。", + "apihelp-query+redirects-paramvalue-prop-fragment": "各重新導向的片段,若有的話。", + "apihelp-query+redirects-param-namespace": "僅包含這些命名空間的頁面。", "apihelp-query+redirects-param-limit": "要回傳的重新導向數量。", + "apihelp-query+redirects-example-simple": "取得 [[Main Page]] 的重新導向清單", + "apihelp-query+redirects-example-generator": "取得所有重新導向至 [[Main Page]] 的資訊。", "apihelp-query+revisions-summary": "取得修訂的資訊。", + "apihelp-query+revisions-param-user": "僅包含由使用者做出的修訂。", + "apihelp-query+revisions-param-excludeuser": "不包含由使用者做出的修訂。", + "apihelp-query+revisions-param-tag": "僅列出以此標籤所標記的修訂。", + "apihelp-query+revisions-example-content": "取得用於標題 API 與 Main Page 最新修訂內容的資料。", + "apihelp-query+revisions-example-last5": "取得 Main Page 的最近 5 筆修訂。", + "apihelp-query+revisions-example-first5": "取得 Main Page 的最早前 5 筆修訂。", + "apihelp-query+revisions-example-first5-after": "取得 Main Page 自 2006-05-01 後做的前 5 筆修訂。", + "apihelp-query+revisions-example-first5-not-localhost": "取得 Main Page 裡並非由匿名使用者 127.0.0.1 所做出的最早前 5 筆修訂。", + "apihelp-query+revisions-example-first5-user": "取得 Main Page 裡由使用者 MediaWiki default 所做出的最早前 5 筆修訂。", "apihelp-query+revisions+base-paramvalue-prop-ids": "修訂 ID。", + "apihelp-query+revisions+base-paramvalue-prop-flags": "修訂標籤(小修改)。", + "apihelp-query+revisions+base-paramvalue-prop-timestamp": "修訂的時間戳記。", + "apihelp-query+revisions+base-paramvalue-prop-user": "做出修訂的使用者。", + "apihelp-query+revisions+base-paramvalue-prop-userid": "修訂創建者的使用者 ID", + "apihelp-query+revisions+base-paramvalue-prop-size": "修訂的長度(位元組)。", + "apihelp-query+revisions+base-paramvalue-prop-sha1": "修訂的 SHA-1(base 16)。", "apihelp-query+revisions+base-paramvalue-prop-tags": "修訂標籤。", + "apihelp-query+revisions+base-param-limit": "限制所回傳的修訂數量。", + "apihelp-query+search-summary": "執行全文搜尋。", + "apihelp-query+search-param-namespace": "僅以這些命名空間搜尋。", + "apihelp-query+search-param-what": "要執行的搜尋類型。", "apihelp-query+search-param-info": "要回傳的詮釋資料。", "apihelp-query+search-param-prop": "要回傳的屬性:", + "apihelp-query+search-paramvalue-prop-size": "添加以位元組為單位的頁面大小。", + "apihelp-query+search-paramvalue-prop-wordcount": "添加頁面的字數。", + "apihelp-query+search-paramvalue-prop-timestamp": "添加頁面自上一次編輯的時間戳記。", + "apihelp-query+search-paramvalue-prop-snippet": "添加已解析的頁面片段。", + "apihelp-query+search-paramvalue-prop-titlesnippet": "添加已解析的頁面標題片段。", + "apihelp-query+search-paramvalue-prop-redirectsnippet": "添加已解析的重新導向標題片段。", + "apihelp-query+search-paramvalue-prop-redirecttitle": "添加符合重新導向的標題。", + "apihelp-query+search-paramvalue-prop-sectionsnippet": "添加已解析的符合段落標題片段。", + "apihelp-query+search-paramvalue-prop-sectiontitle": "添加符合段落的標題。", + "apihelp-query+search-paramvalue-prop-categorysnippet": "添加已解析的符合分類片段。", + "apihelp-query+search-paramvalue-prop-isfilematch": "添加表明搜尋是否符合檔案內容的布林值。", + "apihelp-query+search-paramvalue-prop-extensiondata": "添加由擴充所產生的額外資料。", "apihelp-query+search-paramvalue-prop-score": "已忽略", "apihelp-query+search-paramvalue-prop-hasrelated": "已忽略", "apihelp-query+search-param-limit": "要回傳的頁面總數。", + "apihelp-query+search-param-interwiki": "若可用的話,在搜尋裡包含跨 wiki 結果。", + "apihelp-query+search-param-backend": "是否搜尋使用的後端,若否則為預設。", + "apihelp-query+search-param-sort": "設定回傳結果的排序。", + "apihelp-query+search-example-simple": "搜尋 meaning。", + "apihelp-query+search-example-text": "搜尋 meaning 的文字。", + "apihelp-query+siteinfo-summary": "回傳有關站台的一般資訊。", + "apihelp-query+siteinfo-param-prop": "要取得的資訊:", + "apihelp-query+siteinfo-paramvalue-prop-general": "全面系統資訊。", + "apihelp-query+siteinfo-paramvalue-prop-specialpagealiases": "特殊頁面別名清單。", + "apihelp-query+siteinfo-param-showalldb": "列出所有資料庫伺服器,不是只有最延遲的那台。", + "apihelp-query+siteinfo-param-numberingroup": "列出在使用者群組裡的使用者數目。", + "apihelp-query+siteinfo-param-inlanguagecode": "用於本地化語言的語言代碼(盡可能)與外觀名稱。", "apihelp-query+siteinfo-example-simple": "索取站台資訊。", "apihelp-query+siteinfo-example-interwiki": "索取本地端跨 wiki 前綴的清單。", + "apihelp-query+siteinfo-example-replag": "檢查目前的響應延遲。", "apihelp-query+stashimageinfo-summary": "回傳多筆儲藏檔案的檔案資訊。", "apihelp-query+stashimageinfo-example-simple": "回傳儲藏檔案的檔案資訊。", "apihelp-query+tags-summary": "列出變更標記。", + "apihelp-query+tags-param-limit": "能列出標籤的最大數量。", "apihelp-query+tags-param-prop": "要取得的屬性。", "apihelp-query+tags-paramvalue-prop-name": "添加標籤名稱。", "apihelp-query+tags-paramvalue-prop-displayname": "添加標籤的系統訊息。", "apihelp-query+tags-paramvalue-prop-description": "添加標籤的描述。", + "apihelp-query+tags-paramvalue-prop-active": "標籤是否仍被套用。", "apihelp-query+tags-example-simple": "列出可用標籤。", "apihelp-query+templates-summary": "回傳指定頁面中所有引用的頁面。", + "apihelp-query+templates-param-namespace": "僅顯示在這些命名空間的模板。", "apihelp-query+templates-param-limit": "要回傳的模板數量。", "apihelp-query+templates-param-dir": "列出時所採用的方向。", + "apihelp-query+templates-example-simple": "取得在頁面 Main Page 使用到的模坂。", "apihelp-query+tokens-param-type": "要求的權杖類型。", "apihelp-query+tokens-example-simple": "接收 csrf 密鑰 (預設)。", "apihelp-query+tokens-example-types": "接收監視密鑰以及巡邏密鑰。", @@ -419,32 +824,127 @@ "apihelp-query+transcludedin-paramvalue-prop-pageid": "各頁面的頁面 ID。", "apihelp-query+transcludedin-paramvalue-prop-title": "各頁面的標題。", "apihelp-query+transcludedin-paramvalue-prop-redirect": "若頁面為重新導向,則做出標記。", + "apihelp-query+transcludedin-param-namespace": "僅包含這些命名空間的頁面。", "apihelp-query+transcludedin-param-limit": "回傳的數量。", + "apihelp-query+usercontribs-summary": "按使用者來取得所有編輯。", "apihelp-query+usercontribs-param-limit": "回傳的貢獻數量上限。", + "apihelp-query+usercontribs-param-namespace": "僅列出這些命名空間的貢獻。", + "apihelp-query+usercontribs-param-prop": "包含的額外資訊部份:", + "apihelp-query+usercontribs-paramvalue-prop-ids": "添加頁面 ID 與修訂 ID。", + "apihelp-query+usercontribs-paramvalue-prop-title": "添加標題與頁面的命名空間 ID。", + "apihelp-query+usercontribs-paramvalue-prop-timestamp": "添加編輯的時間戳記。", + "apihelp-query+usercontribs-paramvalue-prop-comment": "添加編輯的註釋。", + "apihelp-query+usercontribs-paramvalue-prop-parsedcomment": "添加編輯的解析註釋。", + "apihelp-query+usercontribs-paramvalue-prop-size": "添加編輯的新大小。", + "apihelp-query+usercontribs-paramvalue-prop-flags": "添加編輯的標籤。", + "apihelp-query+usercontribs-paramvalue-prop-patrolled": "標記已巡查編輯。", + "apihelp-query+usercontribs-paramvalue-prop-autopatrolled": "標記自動巡查編輯。", + "apihelp-query+usercontribs-paramvalue-prop-tags": "列出編輯的標籤。", + "apihelp-query+usercontribs-param-tag": "僅列出以此標籤所標記的修訂。", + "apihelp-query+usercontribs-param-toponly": "僅列出最新修訂的更改。", + "apihelp-query+usercontribs-example-user": "顯示使用者 Example 的貢獻。", + "apihelp-query+usercontribs-example-ipprefix": "顯示所有來自於前綴為 192.0.2. 的 IP 地址貢獻。", + "apihelp-query+userinfo-summary": "取得目前使用者的資訊。", + "apihelp-query+userinfo-param-prop": "要包含的資訊部份:", + "apihelp-query+userinfo-paramvalue-prop-groups": "列出目前使用者所隸屬的所有群組。", + "apihelp-query+userinfo-paramvalue-prop-rights": "列出目前使用者所擁有的權限。", + "apihelp-query+userinfo-paramvalue-prop-options": "列出目前使用者已設定過的所有偏好設定。", + "apihelp-query+userinfo-paramvalue-prop-editcount": "添加目前使用者的編輯數。", + "apihelp-query+userinfo-paramvalue-prop-ratelimits": "列出所有套用到目前使用者的速率限制。", + "apihelp-query+userinfo-paramvalue-prop-realname": "添加使用者的真實姓名。", + "apihelp-query+userinfo-paramvalue-prop-email": "添加使用者的電子郵件地址與電子郵件驗證日期。", + "apihelp-query+userinfo-paramvalue-prop-registrationdate": "添加使用者的註冊日期。", + "apihelp-query+userinfo-example-simple": "取得目前使用者的資訊。", + "apihelp-query+userinfo-example-data": "取得目前使用者的額外資訊。", "apihelp-query+users-summary": "取得有關使用者清單的資訊。", + "apihelp-query+users-param-prop": "要包含的資訊部份:", + "apihelp-query+users-paramvalue-prop-groups": "列出各使用者所隸屬的所有群組。", + "apihelp-query+users-paramvalue-prop-rights": "列出各使用者所擁有的權限。", + "apihelp-query+users-paramvalue-prop-editcount": "添加使用者的編輯數。", + "apihelp-query+users-paramvalue-prop-registration": "添加使用者的註冊時間戳記。", + "apihelp-query+users-param-users": "要獲取的使用者清單。", + "apihelp-query+users-param-userids": "要獲取的使用者 ID 清單。", + "apihelp-query+users-param-token": "請改用 [[Special:ApiHelp/query+tokens|action=query&meta=tokens]]。", "apihelp-query+watchlist-param-start": "起始列舉的時間戳記。", "apihelp-query+watchlist-param-end": "結束列舉的時間戳記。", + "apihelp-query+watchlist-param-user": "此列出由該使用者作出的更改。", + "apihelp-query+watchlist-param-excludeuser": "不要列出由該使用者作出的更改。", "apihelp-query+watchlist-param-limit": "每個請求要回傳的結果總數。", + "apihelp-query+watchlist-param-prop": "要取得的額外屬性:", + "apihelp-query+watchlist-paramvalue-prop-ids": "添加修訂 ID 與頁面 ID。", "apihelp-query+watchlist-paramvalue-prop-title": "添加頁面標題。", "apihelp-query+watchlist-paramvalue-prop-flags": "添加編輯的標籤。", + "apihelp-query+watchlist-paramvalue-prop-user": "添加有做出編輯的使用者。", + "apihelp-query+watchlist-paramvalue-prop-userid": "添加有做出編輯的使用者 ID。", + "apihelp-query+watchlist-paramvalue-prop-comment": "添加編輯的註釋。", + "apihelp-query+watchlist-paramvalue-prop-parsedcomment": "添加編輯的解析註釋。", + "apihelp-query+watchlist-paramvalue-prop-timestamp": "添加編輯的時間戳記。", + "apihelp-query+watchlist-paramvalue-prop-tags": "列出項目的標籤。", + "apihelp-query+watchlist-param-type": "要顯示的更改類型:", + "apihelp-query+watchlist-paramvalue-type-edit": "一般頁面編輯。", + "apihelp-query+watchlist-paramvalue-type-external": "外部更改。", + "apihelp-query+watchlist-paramvalue-type-new": "頁面建立。", "apihelp-query+watchlist-paramvalue-type-log": "日誌項目。", "apihelp-query+watchlist-paramvalue-type-categorize": "分類成員更改。", + "apihelp-query+watchlistraw-summary": "列出在目前使用者的監視清單裡頭所有頁面。", + "apihelp-query+watchlistraw-param-namespace": "僅列出在指定命名空間的頁面。", "apihelp-query+watchlistraw-param-limit": "每個請求要回傳的結果總數。", + "apihelp-query+watchlistraw-param-prop": "要取得的額外屬性:", + "apihelp-query+watchlistraw-param-show": "僅列出符合這些準則的項目。", "apihelp-query+watchlistraw-param-dir": "列出時所採用的方向。", + "apihelp-query+watchlistraw-example-simple": "列出在目前使用者的監視清單裡頭頁面。", "apihelp-removeauthenticationdata-summary": "為目前使用者移除身分核對資料。", + "apihelp-resetpassword-summary": "寄送重新設定密碼的電子郵件給使用者。", + "apihelp-resetpassword-example-user": "向使用者 Example 寄送重新設定密碼用的電子郵件。", "apihelp-revisiondelete-summary": "刪除和取消刪除修訂。", + "apihelp-revisiondelete-param-hide": "各修訂所要隱藏的內容。", + "apihelp-revisiondelete-param-show": "各修訂所要取消隱藏的內容。", + "apihelp-revisiondelete-param-reason": "刪除或取消刪除的原因。", + "apihelp-revisiondelete-param-tags": "在刪除日誌裡套用到項目的標籤。", + "apihelp-rollback-summary": "撤修頁面的最後一次編輯。", + "apihelp-rollback-param-tags": "套用到回退的標籤。", + "apihelp-rollback-param-summary": "自定義編輯摘要。若為空,則使用預設摘要。", + "apihelp-rollback-param-watchlist": "無條件使用設置將頁面加入或移除目前使用者的監視清單或者是不更改監視清單。", + "apihelp-setnotificationtimestamp-param-entirewatchlist": "在所有已監視頁面運作。", + "apihelp-setnotificationtimestamp-example-page": "重新設定用於 Main page 的通知狀態。", + "apihelp-setnotificationtimestamp-example-allpages": "重新設定在 {{ns:user}} 命名空間裡頁面的通知狀態。", + "apihelp-setpagelanguage-summary": "更改頁面的語言。", + "apihelp-setpagelanguage-extended-description-disabled": "您不被允許在此 wiki 上變更頁面的語言。\n\n請啟用 [[mw:Special:MyLanguage/Manual:$wgPageLanguageUseDB|$wgPageLanguageUseDB]] 來進行此操作。", + "apihelp-setpagelanguage-param-reason": "變更的原因。", + "apihelp-setpagelanguage-example-language": "更改 Main Page 的語言成巴斯克語。", + "apihelp-stashedit-summary": "在分享快取裡預備編輯。", "apihelp-stashedit-param-title": "正在編輯此頁面的標題。", "apihelp-stashedit-param-text": "頁面內容。", + "apihelp-stashedit-param-contentmodel": "新內容的內容模組。", + "apihelp-stashedit-param-contentformat": "用於輸入文字的內容序列化格式。", + "apihelp-stashedit-param-baserevid": "基本修訂的修訂 ID。", + "apihelp-stashedit-param-summary": "更改摘要。", + "apihelp-tag-param-reason": "變更的原因。", "apihelp-tokens-summary": "取得資料修改動作的密鑰。", "apihelp-tokens-extended-description": "此模組已因支援 [[Special:ApiHelp/query+tokens|action=query&meta=tokens]] 而停用。", "apihelp-unblock-summary": "解除封鎖一位使用者。", + "apihelp-unblock-param-user": "要封鎖的使用者名稱、IP 位址或 IP 範圍。不能與 $1id 或 $1userid 一起使用", + "apihelp-unblock-param-userid": "要封鎖的使用者 ID。不可與 $1id 或 $1user 一同使用。", "apihelp-unblock-param-reason": "解除封鎖的原因。", + "apihelp-unblock-param-tags": "在封鎖日誌裡更改套用到項目的標籤。", "apihelp-unblock-example-id": "解除封銷 ID #105。", + "apihelp-undelete-summary": "恢復已刪除頁面的修訂。", "apihelp-undelete-param-title": "要恢復的頁面標題。", "apihelp-undelete-param-reason": "還原的原因。", + "apihelp-undelete-param-tags": "在刪除日誌裡更改套用到項目的標籤。", + "apihelp-undelete-example-page": "取消刪除頁面 Main Page。", + "apihelp-undelete-example-revisions": "取消刪除 Main Page 的兩筆修訂。", + "apihelp-unlinkaccount-summary": "移除目前使用者所連結到的第三方帳號。", + "apihelp-upload-summary": "上傳檔案,或取得等待上傳的狀態。", "apihelp-upload-param-filename": "目標檔案名稱。", "apihelp-upload-param-comment": "上傳註釋。如果 $1text 未指定的話,也會作為新檔案用的初始頁面文字。", + "apihelp-upload-param-text": "用於新檔案的初始頁面文字。", + "apihelp-upload-param-watch": "監視頁面。", + "apihelp-upload-param-ignorewarnings": "忽略所有警告。", "apihelp-upload-param-file": "檔案內容。", + "apihelp-upload-param-url": "索取檔案的來源 URL。", + "apihelp-upload-param-chunk": "大量內容。", + "apihelp-upload-param-async": "在可能的情況下讓潛在的大型檔案非同步處理。", "apihelp-upload-example-url": "從 URL 上傳。", "apihelp-userrights-summary": "變更一位使用者的群組成員。", "apihelp-userrights-param-user": "使用者名稱。", @@ -453,7 +953,11 @@ "apihelp-userrights-param-remove": "從這些群組移除使用者。", "apihelp-userrights-param-reason": "變更的原因。", "apihelp-validatepassword-param-password": "要驗證的密碼。", + "apihelp-validatepassword-param-email": "電子郵件地址,用於當測試帳號建立時使用。", + "apihelp-validatepassword-param-realname": "真實姓名,用於當測試帳號建立時使用。", + "apihelp-validatepassword-example-1": "驗證目前使用者的密碼 foobar。", "apihelp-watch-example-watch": "監視頁面 Main Page。", + "apihelp-watch-example-unwatch": "取消監視頁面 Main Page。", "apihelp-format-example-generic": "以 $1 格式傳回查詢結果。", "apihelp-json-summary": "使用 JSON 格式輸出資料。", "apihelp-jsonfm-summary": "使用 JSON 格式輸出資料 (使用 HTML 格式顯示)。", @@ -462,18 +966,22 @@ "apihelp-phpfm-summary": "使用序列化 PHP 格式輸出資料 (使用 HTML 格式顯示)。", "apihelp-rawfm-summary": "使用 JSON 格式的除錯元素輸出資料 (使用 HTML 格式顯示)。", "apihelp-xml-summary": "使用 XML 格式輸出資料。", + "apihelp-xml-param-includexmlnamespace": "若有指定,添加一個 XML 命名空間。", "apihelp-xmlfm-summary": "使用 XML 格式輸出資料 (使用 HTML 格式顯示)。", "api-format-title": "MediaWiki API 結果", "api-format-prettyprint-header": "這是$1格式的HTML呈現。HTML適合用於除錯,但不適合應用程式使用。\n\n指定format參數以更改輸出格式。要檢視$1格式的非HTML呈現,設定format=$2。\n\n參考 [[mw:Special:MyLanguage/API|完整說明文件]] 或 [[Special:ApiHelp/main|API說明]] 以取得更多資訊。", "api-format-prettyprint-header-only-html": "這是用來除錯的HTML呈現,不適合實際應用。\n\n參見[[mw:Special:MyLanguage/API|完整文件]]或[[Special:ApiHelp/main|API幫助]]以取得更多資訊。", "api-format-prettyprint-header-hyperlinked": "這是$1格式的HTML實現。HTML對除錯很有用,但不適合應用程式使用。\n\n指定format參數以更改輸出格式。要查看$1格式的非HTML實現,設置[$3 format=$2]。\n\n參見[[mw:API|完整文件]],或[[Special:ApiHelp/main|API幫助]]以獲取更多信息。", "api-format-prettyprint-status": "此回應將會傳回HTTP狀態$1 $2。", + "api-login-fail-badsessionprovider": "當使用$1無法登入。", + "api-login-fail-sameorigin": "當未套用相同原有方針時無法登入。", "api-pageset-param-titles": "要使用的標題清單。", "api-pageset-param-pageids": "要使用的頁面 ID 清單。", "api-pageset-param-revids": "要使用的修訂 ID 清單。", "api-help-title": "MediaWiki API 說明", "api-help-lead": "此頁為自動產生的 MediaWiki API 說明文件頁面。\n\n說明文件與範例:https://www.mediawiki.org/wiki/API", "api-help-main-header": "主要模組", + "api-help-undocumented-module": "沒有用於模組 $1 的說明文件。", "api-help-flag-deprecated": "此模組已停用。", "api-help-flag-internal": "此模組是內部的或不穩定的。它的操作可能更改而不另行通知。", "api-help-flag-readrights": "此模組需要讀取權限。", @@ -506,6 +1014,7 @@ "api-help-param-upload": "必須使用 multipart/form-data 以檔案上傳的方式傳送。", "api-help-param-multi-separate": "將幾個值以 | 或 [[Special:ApiHelp/main#main/datatypes|alternative]] 分隔。", "api-help-param-multi-max": "上限值為 {{PLURAL:$1|$1}} (機器人為 {{PLURAL:$2|$2}})。", + "api-help-param-multi-max-simple": "值的最大數量為 {{PLURAL:$1|$1}}。", "api-help-param-multi-all": "要指定所有值,請使用$1。", "api-help-param-default": "預設值:$1", "api-help-param-default-empty": "預設值:(空)", @@ -516,6 +1025,8 @@ "api-help-param-direction": "列舉的方向:\n;newer:最舊的優先。注意:$1start應在$1end之前。\n;older:最新的優先(預設)。注意:$1start應在$1end之後。", "api-help-param-continue": "當有更多結果可用時,使用這個繼續。", "api-help-param-no-description": "(無描述)", + "api-help-param-maxbytes": "不能超過 $1 {{PLURAL:$1|位元組|位元組}}。", + "api-help-param-maxchars": "不能超過 $1 個{{PLURAL:$1|字元|字元}}。", "api-help-examples": "{{PLURAL:$1|範例}}:", "api-help-permissions": "{{PLURAL:$1|權限}}:", "api-help-permissions-granted-to": "{{PLURAL:$1|已授權給}}: $2", @@ -529,33 +1040,102 @@ "api-help-authmanagerhelper-returnurl": "為第三方身份驗證流程傳回URL,必須為絕對值。需要此值或$1continue兩者之一。\n\n在接收REDIRECT回應時,一般狀況下您將打開瀏覽器或網站瀏覽功能到特定的redirecttarget URL以進行第三方身份驗證流程。當它完成時,第三方會將瀏覽器或網站瀏覽功能送至此URL。您應當提取任何來自URL的查詢或POST參數,並將之作為$1continue請求傳遞至此API模組。", "api-help-authmanagerhelper-continue": "此請求是在先前的UI或REDIRECT回應之後的後續動作。必須為此值或$1returnurl。", "api-help-authmanagerhelper-additional-params": "此模組允許額外參數,取決於可用的身份驗證請求。使用[[Special:ApiHelp/query+authmanagerinfo|action=query&meta=authmanagerinfo]]与amirequestsfor=$1(或之前來自此模組的回應,如果合適)以決定可用請求及其使用的欄位。", + "apierror-appendnotsupported": "無法附加到使用內容模組 $1 的頁面。", + "apierror-articleexists": "您所嘗試建立的條目剛剛已被創建。", + "apierror-assertbotfailed": "斷言使用者擁有的 bot 權限失效。", + "apierror-assertnameduserfailed": "斷言使用者「$1」出錯。", + "apierror-assertuserfailed": "斷言使用者已登入失敗。", + "apierror-autoblocked": "您的 IP 位址已經被自動封鎖,因為它曾經被一名已封鎖的使用者使用過。", + "apierror-badgenerator-notgenerator": "模組 $1 不能作為產生器。", + "apierror-badgenerator-unknown": "未知的 generator=$1。", "apierror-badip": "IP 參數無效。", "apierror-badmd5": "提供的 MD5 雜湊不正確。", + "apierror-badmodule-badsubmodule": "模組 $1 不包含子模組「$2」。", + "apierror-badmodule-nosubmodules": "模組 $1 沒有子模組。", + "apierror-badparameter": "參數 $1 的值無效。", "apierror-badquery": "無效的查詢。", + "apierror-blockedfrommail": "您已被封鎖,無法發送電子郵件。", + "apierror-blocked": "您已被封鎖,無法編輯。", + "apierror-botsnotsupported": "此介面不支援機器人。", + "apierror-cannotviewtitle": "您不被允許檢視$1。", + "apierror-cantblock-email": "您沒有權限來封鎖使用者透過 wiki 來發送電子郵件。", + "apierror-cantblock": "您沒有權限來解封使用者。", + "apierror-cantchangecontentmodel": "您沒有權限來更改頁面的內容模組。", + "apierror-canthide": "您沒有權限來從封鎖日誌隱藏使用者名稱。", + "apierror-cantimport-upload": "您沒有權限來匯入已上傳頁面。", + "apierror-cantimport": "您沒有權限來匯入頁面。", + "apierror-changeauth-norequest": "建立更改請求失敗。", + "apierror-contentserializationexception": "內容序列化失敗:$1", "apierror-copyuploadbadurl": "不允許從此 URL 來上傳。", + "apierror-csp-report": "處理 CSP 報告時錯誤:$1。", + "apierror-emptypage": "不允許建立空白的新頁面。", "apierror-filedoesnotexist": "檔案不存在。", "apierror-filenopath": "無法取得本地端檔案路徑。", + "apierror-filetypecannotberotated": "無法旋轉的檔案類型。", + "apierror-imageusage-badtitle": "$1的標題必須是檔案。", "apierror-import-unknownerror": "未知的匯入錯誤:$1", + "apierror-invalidcategory": "您所輸入的分類名稱無效。", + "apierror-invalidlang": "用於參數 $1 的語言代碼無效。", + "apierror-invalidoldimage": "oldimage 參數含有無效格式。", + "apierror-invalidparammix-cannotusewith": "參數 $1 不能與 $2 一起使用。", + "apierror-invalidparammix-mustusewith": "$1 參數僅能與 $2 一起使用。", + "apierror-invalidparammix": "{{PLURAL:$2|參數}} $1 不能一起使用。", + "apierror-invalidsha1base36hash": "所提供的 SHA1Base36 雜湊無效。", "apierror-invalidsha1hash": "所提供的 SHA1 雜湊無效。", + "apierror-invalidtitle": "錯誤標題「$1」。", + "apierror-invalidurlparam": "$1urlparam 的值無效($2=$3)。", + "apierror-invaliduser": "無效的使用者名稱「$1」。", + "apierror-invaliduserid": "使用者 ID $1 無效。", + "apierror-maxbytes": "參數 $1 不能大於 $2 {{PLURAL:$2|位元組|位元組}}", + "apierror-maxchars": "參數 $1 不能多於 $2 個{{PLURAL:$2|字元|字元}}", + "apierror-maxlag-generic": "正等待資料庫伺服器:已延遲 $1 {{PLURAL:$1|秒|秒}}。", + "apierror-maxlag": "正等待$2:已延遲 $1 {{PLURAL:$1|秒|秒}}。", + "apierror-mimesearchdisabled": "MIME 搜尋在 Miser 模式裡被停用。", + "apierror-missingcontent-pageid": "遺失頁面 ID 為 $1 的內容。", + "apierror-missingcontent-revid": "遺失修訂 ID 為 $1 的內容。", + "apierror-missingparam-one-of": "{{PLURAL:$2|參數|參數其一}} $1 為必要。", "apierror-missingparam": "$1參數必須被設定。", + "apierror-missingrev-pageid": "沒有頁面 ID 為 $1 的目前修訂。", + "apierror-missingrev-title": "沒有標題為$1的目前修訂。", "apierror-missingtitle": "您所指定的頁面不存在。", + "apierror-missingtitle-byname": "頁面$1不存在。", + "apierror-moduledisabled": "模組 $1 已停用。", "apierror-mustbeloggedin-changeauth": "必須登入,才能變更身分核對資取。", "apierror-mustbeloggedin-generic": "您必須登入。", + "apierror-mustbeloggedin-linkaccounts": "您必須登入到連結帳號。", "apierror-mustbeloggedin-removeauth": "必須登入,才能移除身分核對資取。", + "apierror-mustbeloggedin": "您必須登入才能$1。", "apierror-nodeleteablefile": "沒有這樣檔案的舊版本。", "apierror-noedit-anon": "匿名使用者不可編輯頁面。", "apierror-noedit": "您沒有權限來編輯頁面。", "apierror-nouploadmodule": "未設定上傳模組。", + "apierror-pagecannotexist": "命名空間不允許實際頁面。", "apierror-permissiondenied": "您沒有權限$1。", + "apierror-permissiondenied-generic": "權限不足。", "apierror-permissiondenied-unblock": "您沒有權限來解封使用者。", + "apierror-protect-invalidaction": "無效的保護類型「$1」。", + "apierror-protect-invalidlevel": "無效的保護層級「$1」。", + "apierror-readapidenied": "您需要有閱讀權限來使用此模組。", "apierror-readonly": "Wiki 目前為唯讀模式。", "apierror-reauthenticate": "於本工作階段還未核對身分,請重新核對。", + "apierror-revwrongpage": "r$1 不是$2的修訂。", + "apierror-searchdisabled": "$1搜尋已停用。", + "apierror-sizediffdisabled": "大小差異功能在 Miser 模式裡已停用。", + "apierror-stashwrongowner": "錯誤擁有者:$1", + "apierror-systemblocked": "您已被 MediaWiki 給自動封鎖。", "apierror-timeout": "伺服器未有在預計的時間內回應。", "apierror-unknownerror-editpage": "不明編輯頁面錯誤:$1。", "apierror-unknownerror-nocode": "不明錯誤。", "apierror-unknownerror": "不明錯誤:\"$1\"。", "apierror-unknownformat": "無法識別的格式\"$1\"。", + "apierror-unrecognizedparams": "無法識別的{{PLURAL:$2|參數|參數}}:$1。", "apierror-upload-missingresult": "狀態資料裡沒有結果。", + "apiwarn-deprecation-httpsexpected": "當應為 HTTPS 時,HTTP 要被使用。", + "apiwarn-invalidcategory": "「$1」不是一個分類。", + "apiwarn-invalidtitle": "「$1」不是一個有效標題。", + "apiwarn-notfile": "「$1」不是一個檔案。", + "apiwarn-validationfailed-badpref": "不是有效的偏好設定。", + "apiwarn-validationfailed-cannotset": "不能透過此模組設定。", "api-feed-error-title": "錯誤($1)", "api-credits-header": "製作群", "api-credits": "API 開發人員:\n* Roan Kattouw (首席開發者 Sep 2007–2009)\n* Victor Vasiliev\n* Bryan Tong Minh\n* Sam Reed\n* Yuri Astrakhan (創立者,首席開發者 Sep 2006–Sep 2007)\n* Brad Jorsch (首席開發者 2013–present)\n\n請傳送您的評論、建議以及問題至 mediawiki-api@lists.wikimedia.org\n或者回報問題至 https://phabricator.wikimedia.org/。" diff --git a/includes/auth/AuthManagerAuthPlugin.php b/includes/auth/AuthManagerAuthPlugin.php index 4f84b4c67d..01d992fe74 100644 --- a/includes/auth/AuthManagerAuthPlugin.php +++ b/includes/auth/AuthManagerAuthPlugin.php @@ -199,33 +199,3 @@ class AuthManagerAuthPlugin extends \AuthPlugin { return []; } } - -/** - * @since 1.27 - * @deprecated since 1.27 - */ -class AuthManagerAuthPluginUser extends \AuthPluginUser { - /** @var User */ - private $user; - - function __construct( $user ) { - $this->user = $user; - } - - public function getId() { - return $this->user->getId(); - } - - public function isLocked() { - return $this->user->isLocked(); - } - - public function isHidden() { - return $this->user->isHidden(); - } - - public function resetAuthToken() { - \MediaWiki\Session\SessionManager::singleton()->invalidateSessionsForUser( $this->user ); - return true; - } -} diff --git a/includes/auth/AuthManagerAuthPluginUser.php b/includes/auth/AuthManagerAuthPluginUser.php new file mode 100644 index 0000000000..e31be6019d --- /dev/null +++ b/includes/auth/AuthManagerAuthPluginUser.php @@ -0,0 +1,53 @@ +user = $user; + } + + public function getId() { + return $this->user->getId(); + } + + public function isLocked() { + return $this->user->isLocked(); + } + + public function isHidden() { + return $this->user->isHidden(); + } + + public function resetAuthToken() { + \MediaWiki\Session\SessionManager::singleton()->invalidateSessionsForUser( $this->user ); + return true; + } +} diff --git a/includes/cache/LinkBatch.php b/includes/cache/LinkBatch.php index 86dd3384a0..7a0826e466 100644 --- a/includes/cache/LinkBatch.php +++ b/includes/cache/LinkBatch.php @@ -224,12 +224,13 @@ class LinkBatch { if ( $this->isEmpty() ) { return false; } + $services = MediaWikiServices::getInstance(); - if ( !MediaWikiServices::getInstance()->getContentLanguage()->needsGenderDistinction() ) { + if ( !$services->getContentLanguage()->needsGenderDistinction() ) { return false; } - $genderCache = MediaWikiServices::getInstance()->getGenderCache(); + $genderCache = $services->getGenderCache(); $genderCache->doLinkBatch( $this->data, $this->caller ); return true; diff --git a/includes/cache/MessageCache.php b/includes/cache/MessageCache.php index f09b5bf7ce..7a1b988643 100644 --- a/includes/cache/MessageCache.php +++ b/includes/cache/MessageCache.php @@ -107,15 +107,16 @@ class MessageCache { public static function singleton() { if ( self::$instance === null ) { global $wgUseDatabaseMessages, $wgMsgCacheExpiry, $wgUseLocalMessageCache; + $services = MediaWikiServices::getInstance(); self::$instance = new self( - MediaWikiServices::getInstance()->getMainWANObjectCache(), + $services->getMainWANObjectCache(), wfGetMessageCacheStorage(), $wgUseLocalMessageCache - ? MediaWikiServices::getInstance()->getLocalServerObjectCache() + ? $services->getLocalServerObjectCache() : new EmptyBagOStuff(), $wgUseDatabaseMessages, $wgMsgCacheExpiry, - MediaWikiServices::getInstance()->getContentLanguage() + $services->getContentLanguage() ); } diff --git a/includes/cache/localisation/LCStoreStaticArray.php b/includes/cache/localisation/LCStoreStaticArray.php index 38700b85b5..3b6da73944 100644 --- a/includes/cache/localisation/LCStoreStaticArray.php +++ b/includes/cache/localisation/LCStoreStaticArray.php @@ -20,6 +20,8 @@ * @file */ +use Wikimedia\StaticArrayWriter; + /** * @since 1.26 */ @@ -84,9 +86,7 @@ class LCStoreStaticArray implements LCStore { } if ( is_array( $value ) ) { // [A]rray - return [ 'a', array_map( function ( $v ) { - return LCStoreStaticArray::encode( $v ); - }, $value ) ]; + return [ 'a', array_map( 'LCStoreStaticArray::encode', $value ) ]; } throw new RuntimeException( 'Cannot encode ' . var_export( $value, true ) ); @@ -109,9 +109,7 @@ class LCStoreStaticArray implements LCStore { case 's': return unserialize( $data ); case 'a': - return array_map( function ( $v ) { - return LCStoreStaticArray::decode( $v ); - }, $data ); + return array_map( 'LCStoreStaticArray::decode', $data ); default: throw new RuntimeException( 'Unable to decode ' . var_export( $encoded, true ) ); @@ -119,13 +117,12 @@ class LCStoreStaticArray implements LCStore { } public function finishWrite() { - file_put_contents( - $this->fname, - "data[$this->currentLang], true ) . ';' + $writer = new StaticArrayWriter(); + $out = $writer->create( + $this->data[$this->currentLang], + 'Generated by LCStoreStaticArray.php -- do not edit!' ); + file_put_contents( $this->fname, $out ); $this->currentLang = null; $this->fname = null; } diff --git a/includes/cache/localisation/LocalisationCache.php b/includes/cache/localisation/LocalisationCache.php index 75e5e19bce..d0381cf04d 100644 --- a/includes/cache/localisation/LocalisationCache.php +++ b/includes/cache/localisation/LocalisationCache.php @@ -800,7 +800,7 @@ class LocalisationCache { return [ 'core' => "$IP/languages/i18n", 'api' => "$IP/includes/api/i18n", - 'oojs-ui' => "$IP/resources/lib/oojs-ui/i18n", + 'oojs-ui' => "$IP/resources/lib/ooui/i18n", ] + $messagesDirs; } @@ -838,17 +838,23 @@ class LocalisationCache { } # Fill in the fallback if it's not there already - if ( is_null( $coreData['fallback'] ) ) { - $coreData['fallback'] = $code === 'en' ? false : 'en'; - } - if ( $coreData['fallback'] === false ) { - $coreData['fallbackSequence'] = []; + if ( ( is_null( $coreData['fallback'] ) || $coreData['fallback'] === false ) && $code === 'en' ) { + $coreData['fallback'] = false; + $coreData['originalFallbackSequence'] = $coreData['fallbackSequence'] = []; } else { - $coreData['fallbackSequence'] = array_map( 'trim', explode( ',', $coreData['fallback'] ) ); + if ( !is_null( $coreData['fallback'] ) ) { + $coreData['fallbackSequence'] = array_map( 'trim', explode( ',', $coreData['fallback'] ) ); + } else { + $coreData['fallbackSequence'] = []; + } $len = count( $coreData['fallbackSequence'] ); - # Ensure that the sequence ends at en - if ( $coreData['fallbackSequence'][$len - 1] !== 'en' ) { + # Before we add the 'en' fallback for messages, keep a copy of + # the original fallback sequence + $coreData['originalFallbackSequence'] = $coreData['fallbackSequence']; + + # Ensure that the sequence ends at 'en' for messages + if ( !$len || $coreData['fallbackSequence'][$len - 1] !== 'en' ) { $coreData['fallbackSequence'][] = 'en'; } } diff --git a/includes/changes/ChangesListBooleanFilter.php b/includes/changes/ChangesListBooleanFilter.php index fc37882c02..c781d717cc 100644 --- a/includes/changes/ChangesListBooleanFilter.php +++ b/includes/changes/ChangesListBooleanFilter.php @@ -29,13 +29,6 @@ use Wikimedia\Rdbms\IDatabase; * @since 1.29 */ class ChangesListBooleanFilter extends ChangesListFilter { - // This can sometimes be different on Special:RecentChanges - // and Special:Watchlist, due to the double-legacy hooks - // (SpecialRecentChangesFilters and SpecialWatchlistFilters) - - // but there will be separate sets of ChangesListFilterGroup and ChangesListFilter instances - // for those pages (it should work even if they're both loaded - // at once, but that can't happen). /** * Main unstructured UI i18n key * diff --git a/includes/changes/OldChangesList.php b/includes/changes/OldChangesList.php index 7ba12fbddd..a2af01cc49 100644 --- a/includes/changes/OldChangesList.php +++ b/includes/changes/OldChangesList.php @@ -20,6 +20,8 @@ * @file */ +use MediaWiki\MediaWikiServices; + class OldChangesList extends ChangesList { /** @@ -90,7 +92,8 @@ class OldChangesList extends ChangesList { } // Log entries (old format) or log targets, and special pages } elseif ( $rc->mAttribs['rc_namespace'] == NS_SPECIAL ) { - list( $name, $htmlubpage ) = SpecialPageFactory::resolveAlias( $rc->mAttribs['rc_title'] ); + list( $name, $htmlubpage ) = MediaWikiServices::getInstance()->getSpecialPageFactory()-> + resolveAlias( $rc->mAttribs['rc_title'] ); if ( $name == 'Log' ) { $this->insertLog( $html, $rc->getTitle(), $htmlubpage ); } diff --git a/includes/changetags/ChangeTags.php b/includes/changetags/ChangeTags.php index b5bf488acb..45a35c0ead 100644 --- a/includes/changetags/ChangeTags.php +++ b/includes/changetags/ChangeTags.php @@ -22,6 +22,7 @@ */ use MediaWiki\MediaWikiServices; +use MediaWiki\Storage\NameTableAccessException; use Wikimedia\Rdbms\Database; class ChangeTags { @@ -87,6 +88,7 @@ class ChangeTags { * @return array Array with two items: (html, classes) * - html: String: HTML for displaying the tags (empty string when param $tags is empty) * - classes: Array of strings: CSS classes used in the generated html, one class for each tag + * @return-taint onlysafefor_htmlnoent */ public static function formatSummaryRow( $tags, $page, IContextSource $context = null ) { if ( !$tags ) { @@ -201,9 +203,8 @@ class ChangeTags { } $taglessDesc = Sanitizer::stripAllTags( $originalDesc->parse() ); - $escapedDesc = Sanitizer::escapeHtmlAllowEntities( $taglessDesc ); - return $context->getLanguage()->truncateForVisual( $escapedDesc, $length ); + return $context->getLanguage()->truncateForVisual( $taglessDesc, $length ); } /** @@ -343,11 +344,10 @@ class ChangeTags { } // insert a row into change_tag for each new tag + $changeTagDefStore = MediaWikiServices::getInstance()->getChangeTagDefStore(); if ( count( $tagsToAdd ) ) { $changeTagMapping = []; if ( $wgChangeTagsSchemaMigrationStage > MIGRATION_OLD ) { - $changeTagDefStore = MediaWikiServices::getInstance()->getChangeTagDefStore(); - foreach ( $tagsToAdd as $tag ) { $changeTagMapping[$tag] = $changeTagDefStore->acquireId( $tag ); } @@ -362,13 +362,18 @@ class ChangeTags { $tagsRows = []; foreach ( $tagsToAdd as $tag ) { + if ( $wgChangeTagsSchemaMigrationStage > MIGRATION_WRITE_BOTH ) { + $tagName = null; + } else { + $tagName = $tag; + } // Filter so we don't insert NULLs as zero accidentally. // Keep in mind that $rc_id === null means "I don't care/know about the // rc_id, just delete $tag on this revision/log entry". It doesn't // mean "only delete tags on this revision/log WHERE rc_id IS NULL". $tagsRows[] = array_filter( [ - 'ct_tag' => $tag, + 'ct_tag' => $tagName, 'ct_rc_id' => $rc_id, 'ct_log_id' => $log_id, 'ct_rev_id' => $rev_id, @@ -385,12 +390,20 @@ class ChangeTags { // delete from change_tag if ( count( $tagsToRemove ) ) { foreach ( $tagsToRemove as $tag ) { + if ( $wgChangeTagsSchemaMigrationStage > MIGRATION_WRITE_BOTH ) { + $tagName = null; + $tagId = $changeTagDefStore->getId( $tag ); + } else { + $tagName = $tag; + $tagId = null; + } $conds = array_filter( [ - 'ct_tag' => $tag, + 'ct_tag' => $tagName, 'ct_rc_id' => $rc_id, 'ct_log_id' => $log_id, - 'ct_rev_id' => $rev_id + 'ct_rev_id' => $rev_id, + 'ct_tag_id' => $tagId, ] ); $dbw->delete( 'change_tag', $conds, __METHOD__ ); @@ -770,7 +783,7 @@ class ChangeTags { public static function modifyDisplayQuery( &$tables, &$fields, &$conds, &$join_conds, &$options, $filter_tag = '' ) { - global $wgUseTagFilter; + global $wgUseTagFilter, $wgChangeTagsSchemaMigrationStage; // Normalize to arrays $tables = (array)$tables; @@ -791,8 +804,18 @@ class ChangeTags { throw new MWException( 'Unable to determine appropriate JOIN condition for tagging.' ); } + $tagTables[] = 'change_tag'; + if ( $wgChangeTagsSchemaMigrationStage > MIGRATION_WRITE_BOTH ) { + $tagTables[] = 'change_tag_def'; + $join_cond_ts_tags = [ $join_cond, 'ct_tag_id=ctd_id' ]; + $field = 'ctd_name'; + } else { + $field = 'ct_tag'; + $join_cond_ts_tags = $join_cond; + } + $fields['ts_tags'] = wfGetDB( DB_REPLICA )->buildGroupConcatField( - ',', 'change_tag', 'ct_tag', $join_cond + ',', $tagTables, $field, $join_cond_ts_tags ); if ( $wgUseTagFilter && $filter_tag ) { @@ -801,7 +824,23 @@ class ChangeTags { $tables[] = 'change_tag'; $join_conds['change_tag'] = [ 'INNER JOIN', $join_cond ]; - $conds['ct_tag'] = $filter_tag; + if ( $wgChangeTagsSchemaMigrationStage > MIGRATION_WRITE_BOTH ) { + $filterTagIds = []; + $changeTagDefStore = MediaWikiServices::getInstance()->getChangeTagDefStore(); + foreach ( (array)$filter_tag as $filterTagName ) { + try { + $filterTagIds[] = $changeTagDefStore->getId( $filterTagName ); + } catch ( NameTableAccessException $exception ) { + // Return nothing. + $conds[] = '0'; + break; + }; + } + $conds['ct_tag_id'] = $filterTagIds; + } else { + $conds['ct_tag'] = $filter_tag; + } + if ( is_array( $filter_tag ) && count( $filter_tag ) > 1 && !in_array( 'DISTINCT', $options ) @@ -1184,7 +1223,7 @@ class ChangeTags { * Extensions should NOT use this function; they can use the ListDefinedTags * hook instead. * - * Includes a call to ChangeTag::canDeleteTag(), so your code doesn't need to + * Includes a call to ChangeTag::canCreateTag(), so your code doesn't need to * do that. * * @param string $tag @@ -1237,10 +1276,17 @@ class ChangeTags { // delete from valid_tag and/or set ctd_user_defined = 0 self::undefineTag( $tag ); + if ( $wgChangeTagsSchemaMigrationStage > MIGRATION_WRITE_BOTH ) { + $tagId = MediaWikiServices::getInstance()->getChangeTagDefStore()->getId( $tag ); + $conditions = [ 'ct_tag_id' => $tagId ]; + } else { + $conditions = [ 'ct_tag' => $tag ]; + } + // find out which revisions use this tag, so we can delete from tag_summary $result = $dbw->select( 'change_tag', - [ 'ct_rc_id', 'ct_log_id', 'ct_rev_id', 'ct_tag' ], - [ 'ct_tag' => $tag ], + [ 'ct_rc_id', 'ct_log_id', 'ct_rev_id' ], + $conditions, __METHOD__ ); foreach ( $result as $row ) { // remove the tag from the relevant row of tag_summary @@ -1251,7 +1297,12 @@ class ChangeTags { } // delete from change_tag - $dbw->delete( 'change_tag', [ 'ct_tag' => $tag ], __METHOD__ ); + if ( $wgChangeTagsSchemaMigrationStage > MIGRATION_WRITE_BOTH ) { + $tagId = MediaWikiServices::getInstance()->getChangeTagDefStore()->getId( $tag ); + $dbw->delete( 'change_tag', [ 'ct_tag_id' => $tagId ], __METHOD__ ); + } else { + $dbw->delete( 'change_tag', [ 'ct_tag' => $tag ], __METHOD__ ); + } if ( $wgChangeTagsSchemaMigrationStage > MIGRATION_OLD ) { $dbw->delete( 'change_tag_def', [ 'ctd_name' => $tag ], __METHOD__ ); diff --git a/includes/collation/Collation.php b/includes/collation/Collation.php index 25b3d94574..ab3c6fb432 100644 --- a/includes/collation/Collation.php +++ b/includes/collation/Collation.php @@ -62,8 +62,6 @@ abstract class Collation { return new IcuCollation( 'root-u-kn' ); case 'xx-uca-ckb': return new CollationCkb; - case 'xx-uca-et': - return new CollationEt; case 'uppercase-ab': return new AbkhazUppercaseCollation; case 'uppercase-ba': diff --git a/includes/collation/CollationEt.php b/includes/collation/CollationEt.php deleted file mode 100644 index ca7b765314..0000000000 --- a/includes/collation/CollationEt.php +++ /dev/null @@ -1,60 +0,0 @@ - [], 'eo' => [ "Ĉ", "Ĝ", "Ĥ", "Ĵ", "Ŝ", "Ŭ" ], 'es' => [ "Ñ" ], - 'et' => [ "Š", "Ž", "Õ", "Ä", "Ö", "Ü", "W" ], // added W for CollationEt (xx-uca-et) + 'et' => [ "Š", "Ž", "Õ", "Ä", "Ö", "Ü" ], 'eu' => [ "Ñ" ], // not in libicu 'fa' => [ // RTL, let's put each letter on a new line diff --git a/includes/content/AbstractContent.php b/includes/content/AbstractContent.php index b6211b0604..733d85a065 100644 --- a/includes/content/AbstractContent.php +++ b/includes/content/AbstractContent.php @@ -505,6 +505,7 @@ abstract class AbstractContent implements Content { } $po = new ParserOutput(); + $options->registerWatcher( [ $po, 'recordOption' ] ); if ( Hooks::run( 'ContentGetParserOutput', [ $this, $title, $revId, $options, $generateHtml, &$po ] ) @@ -518,6 +519,7 @@ abstract class AbstractContent implements Content { } Hooks::run( 'ContentAlterParserOutput', [ $this, $title, $po ] ); + $options->registerWatcher( null ); return $po; } diff --git a/includes/content/Content.php b/includes/content/Content.php index 000bff2d64..bb3fb107d7 100644 --- a/includes/content/Content.php +++ b/includes/content/Content.php @@ -284,13 +284,8 @@ interface Content { * 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. + * @deprecated since 1.32, call and override + * ContentHandler::getSecondaryDataUpdates instead. * * @note Implementations should call the SecondaryDataUpdates hook, like * AbstractContent does. @@ -481,8 +476,10 @@ interface Content { * the current state of the database. * * @since 1.21 + * @deprecated since 1.32, call and override + * ContentHandler::getDeletionUpdates instead. * - * @param WikiPage $page The deleted page + * @param WikiPage $page The page the content was deleted from. * @param ParserOutput|null $parserOutput Optional parser output object * for efficient access to meta-information about the content object. * Provide if you have one handy. diff --git a/includes/content/ContentHandler.php b/includes/content/ContentHandler.php index 004e38a5ef..fab043a2ba 100644 --- a/includes/content/ContentHandler.php +++ b/includes/content/ContentHandler.php @@ -1,8 +1,4 @@ getDiffEngineClass(); - return new $diffEngineClass( $context, $old, $new, $rcid, $refreshCache, $unhide ); + $differenceEngine = new $diffEngineClass( $context, $old, $new, $rcid, $refreshCache, $unhide ); + Hooks::run( 'GetDifferenceEngine', [ $context, $old, $new, $refreshCache, $unhide, + &$differenceEngine ] ); + return $differenceEngine; + } + + /** + * Get an appropriate SlotDiffRenderer for this content model. + * @since 1.32 + * @param IContextSource $context + * @return SlotDiffRenderer + */ + final public function getSlotDiffRenderer( IContextSource $context ) { + $slotDiffRenderer = $this->getSlotDiffRendererInternal( $context ); + if ( get_class( $slotDiffRenderer ) === TextSlotDiffRenderer::class ) { + // To keep B/C, when SlotDiffRenderer is not overridden for a given content type + // but DifferenceEngine is, use that instead. + $differenceEngine = $this->createDifferenceEngine( $context ); + if ( get_class( $differenceEngine ) !== DifferenceEngine::class ) { + // TODO turn this into a deprecation warning in a later release + LoggerFactory::getInstance( 'diff' )->info( + 'Falling back to DifferenceEngineSlotDiffRenderer', [ + 'modelID' => $this->getModelID(), + 'DifferenceEngine' => get_class( $differenceEngine ), + ] ); + $slotDiffRenderer = new DifferenceEngineSlotDiffRenderer( $differenceEngine ); + } + } + Hooks::run( 'GetSlotDiffRenderer', [ $this, &$slotDiffRenderer, $context ] ); + return $slotDiffRenderer; + } + + /** + * Return the SlotDiffRenderer appropriate for this content handler. + * @param IContextSource $context + * @return SlotDiffRenderer + */ + protected function getSlotDiffRendererInternal( IContextSource $context ) { + $contentLanguage = MediaWikiServices::getInstance()->getContentLanguage(); + $statsdDataFactory = MediaWikiServices::getInstance()->getStatsdDataFactory(); + $slotDiffRenderer = new TextSlotDiffRenderer(); + $slotDiffRenderer->setStatsdDataFactory( $statsdDataFactory ); + // XXX using the page language would be better, but it's unclear how that should be injected + $slotDiffRenderer->setLanguage( $contentLanguage ); + $slotDiffRenderer->setWikiDiff2MovedParagraphDetectionCutoff( + $context->getConfig()->get( 'WikiDiff2MovedParagraphDetectionCutoff' ) + ); + + $engine = DifferenceEngine::getEngine(); + if ( $engine === false ) { + $slotDiffRenderer->setEngine( TextSlotDiffRenderer::ENGINE_PHP ); + } elseif ( $engine === 'wikidiff2' ) { + $slotDiffRenderer->setEngine( TextSlotDiffRenderer::ENGINE_WIKIDIFF2 ); + } else { + $slotDiffRenderer->setEngine( TextSlotDiffRenderer::ENGINE_EXTERNAL, $engine ); + } + + return $slotDiffRenderer; } /** @@ -1065,31 +1131,52 @@ abstract class ContentHandler { * must exist and must not be deleted. * * @since 1.21 + * @since 1.32 accepts Content objects for all parameters instead of Revision objects. + * Passing Revision objects is deprecated. * - * @param Revision $current The current text - * @param Revision $undo The revision to undo - * @param Revision $undoafter Must be an earlier revision than $undo + * @param Revision|Content $current The current text + * @param Revision|Content $undo The content of the revision to undo + * @param Revision|Content $undoafter Must be from an earlier revision than $undo + * @param bool $undoIsLatest Set true if $undo is from the current revision (since 1.32) * * @return mixed Content on success, false on failure */ - public function getUndoContent( Revision $current, Revision $undo, Revision $undoafter ) { - $cur_content = $current->getContent(); + public function getUndoContent( $current, $undo, $undoafter, $undoIsLatest = false ) { + Assert::parameterType( Revision::class . '|' . Content::class, $current, '$current' ); + if ( $current instanceof Content ) { + Assert::parameter( $undo instanceof Content, '$undo', + 'Must be Content when $current is Content' ); + Assert::parameter( $undoafter instanceof Content, '$undoafter', + 'Must be Content when $current is Content' ); + $cur_content = $current; + $undo_content = $undo; + $undoafter_content = $undoafter; + } else { + Assert::parameter( $undo instanceof Revision, '$undo', + 'Must be Revision when $current is Revision' ); + Assert::parameter( $undoafter instanceof Revision, '$undoafter', + 'Must be Revision when $current is Revision' ); - if ( empty( $cur_content ) ) { - return false; // no page - } + $cur_content = $current->getContent(); - $undo_content = $undo->getContent(); - $undoafter_content = $undoafter->getContent(); + if ( empty( $cur_content ) ) { + return false; // no page + } + + $undo_content = $undo->getContent(); + $undoafter_content = $undoafter->getContent(); + + if ( !$undo_content || !$undoafter_content ) { + return false; // no content to undo + } - if ( !$undo_content || !$undoafter_content ) { - return false; // no content to undo + $undoIsLatest = $current->getId() === $undo->getId(); } try { $this->checkModelID( $cur_content->getModel() ); $this->checkModelID( $undo_content->getModel() ); - if ( $current->getId() !== $undo->getId() ) { + if ( !$undoIsLatest ) { // If we are undoing the most recent revision, // its ok to revert content model changes. However // if we are undoing a revision in the middle, then @@ -1303,14 +1390,20 @@ abstract class ContentHandler { * @return ParserOutput */ public function getParserOutputForIndexing( WikiPage $page, ParserCache $cache = null ) { + // TODO: MCR: ContentHandler should be called per slot, not for the whole page. + // See T190066. $parserOptions = $page->makeParserOptions( 'canonical' ); - $revId = $page->getRevision()->getId(); if ( $cache ) { $parserOutput = $cache->get( $page, $parserOptions ); } + if ( empty( $parserOutput ) ) { + $renderer = MediaWikiServices::getInstance()->getRevisionRenderer(); $parserOutput = - $page->getContent()->getParserOutput( $page->getTitle(), $revId, $parserOptions ); + $renderer->getRenderedRevision( + $page->getRevision()->getRevisionRecord(), + $parserOptions + )->getRevisionParserOutput(); if ( $cache ) { $cache->save( $parserOutput, $page, $parserOptions ); } @@ -1318,4 +1411,75 @@ abstract class ContentHandler { return $parserOutput; } + /** + * Returns a list of DeferrableUpdate objects for recording information about the + * given Content in some secondary data store. + * + * Application logic should not call this method directly. Instead, it should call + * DerivedPageDataUpdater::getSecondaryDataUpdates(). + * + * @note Implementations must not return a LinksUpdate instance. Instead, a LinksUpdate + * is created by the calling code in DerivedPageDataUpdater, on the combined ParserOutput + * of all slots, not for each slot individually. This is in contrast to the old + * getSecondaryDataUpdates method defined by AbstractContent, which returned a LinksUpdate. + * + * @note Implementations should not call $content->getParserOutput, they should call + * $slotOutput->getSlotRendering( $role, false ) instead if they need to access a ParserOutput + * of $content. This allows existing ParserOutput objects to be re-used, while avoiding + * creating a ParserOutput when none is needed. + * + * @param Title $title The title of the page to supply the updates for + * @param Content $content The content to generate data updates for. + * @param string $role The role (slot) in which the content is being used. Which updates + * are performed should generally not depend on the role the content has, but the + * DeferrableUpdates themselves may need to know the role, to track to which slot the + * data refers, and to avoid overwriting data of the same kind from another slot. + * @param SlotRenderingProvider $slotOutput A provider that can be used to gain access to + * a ParserOutput of $content by calling $slotOutput->getSlotParserOutput( $role, false ). + * @return DeferrableUpdate[] A list of DeferrableUpdate objects for putting information + * about this content object somewhere. The default implementation returns an empty + * array. + * @since 1.32 + */ + public function getSecondaryDataUpdates( + Title $title, + Content $content, + $role, + SlotRenderingProvider $slotOutput + ) { + return []; + } + + /** + * Returns a list of DeferrableUpdate objects for removing information about content + * in some secondary data store. This is used when a page is deleted, and also when + * a slot is removed from a page. + * + * Application logic should not call this method directly. Instead, it should call + * WikiPage::getSecondaryDataUpdates(). + * + * @note Implementations must not return a LinksDeletionUpdate instance. Instead, a + * LinksDeletionUpdate is created by the calling code in WikiPage. + * This is in contrast to the old getDeletionUpdates method defined by AbstractContent, + * which returned a LinksUpdate. + * + * @note Implementations should not rely on the page's current content, but rather the current + * state of the secondary data store. + * + * @param Title $title The title of the page to supply the updates for + * @param string $role The role (slot) in which the content is being used. Which updates + * are performed should generally not depend on the role the content has, but the + * DeferrableUpdates themselves may need to know the role, to track to which slot the + * data refers, and to avoid overwriting data of the same kind from another slot. + * + * @return DeferrableUpdate[] A list of DeferrableUpdate objects for putting information + * about this content object somewhere. The default implementation returns an empty + * array. + * + * @since 1.32 + */ + public function getDeletionUpdates( Title $title, $role ) { + return []; + } + } diff --git a/includes/content/TextContent.php b/includes/content/TextContent.php index 20bce3701a..0198a0de11 100644 --- a/includes/content/TextContent.php +++ b/includes/content/TextContent.php @@ -253,6 +253,7 @@ class TextContent extends AbstractContent { $html = ''; } + $output->clearWrapperDivClass(); $output->setText( $html ); } diff --git a/includes/context/DerivativeContext.php b/includes/context/DerivativeContext.php index acf6fcb9fc..9817c3fc4f 100644 --- a/includes/context/DerivativeContext.php +++ b/includes/context/DerivativeContext.php @@ -296,6 +296,7 @@ class DerivativeContext extends ContextSource implements MutableContext { public function msg( $key ) { $args = func_get_args(); + // phpcs:ignore MediaWiki.Usage.ExtendClassUsage.FunctionVarUsage return wfMessage( ...$args )->setContext( $this ); } } diff --git a/includes/db/CloneDatabase.php b/includes/db/CloneDatabase.php index 1564fab225..5f09555e7a 100644 --- a/includes/db/CloneDatabase.php +++ b/includes/db/CloneDatabase.php @@ -52,6 +52,9 @@ class CloneDatabase { public function __construct( IMaintainableDatabase $db, array $tablesToClone, $newTablePrefix, $oldTablePrefix = null, $dropCurrentTables = true ) { + if ( !$tablesToClone ) { + throw new InvalidArgumentException( 'Empty list of tables to clone' ); + } $this->db = $db; $this->tablesToClone = $tablesToClone; $this->newTablePrefix = $newTablePrefix; diff --git a/includes/db/DatabaseOracle.php b/includes/db/DatabaseOracle.php index 49777627af..876b9bb9fa 100644 --- a/includes/db/DatabaseOracle.php +++ b/includes/db/DatabaseOracle.php @@ -81,15 +81,6 @@ class DatabaseOracle extends Database { return false; } - /** - * Usually aborts on failure - * @param string $server - * @param string $user - * @param string $password - * @param string $dbName - * @throws DBConnectionError - * @return resource|null - */ function open( $server, $user, $password, $dbName ) { global $wgDBOracleDRCP; if ( !function_exists( 'oci_connect' ) ) { @@ -173,7 +164,7 @@ class DatabaseOracle extends Database { $this->doQuery( 'ALTER SESSION SET NLS_TIMESTAMP_TZ_FORMAT=\'DD-MM-YYYY HH24:MI:SS.FF6\'' ); $this->doQuery( 'ALTER SESSION SET NLS_NUMERIC_CHARACTERS=\'.,\'' ); - return $this->conn; + return (bool)$this->conn; } /** diff --git a/includes/deferred/LinksDeletionUpdate.php b/includes/deferred/LinksDeletionUpdate.php index f370e43ffd..5ab83c62c6 100644 --- a/includes/deferred/LinksDeletionUpdate.php +++ b/includes/deferred/LinksDeletionUpdate.php @@ -71,6 +71,9 @@ class LinksDeletionUpdate extends DataUpdate implements EnqueueableDataUpdate { // Make sure all links update threads see the changes of each other. // This handles the case when updates have to batched into several COMMITs. $scopedLock = LinksUpdate::acquirePageLock( $this->getDB(), $id ); + if ( !$scopedLock ) { + throw new RuntimeException( "Could not acquire lock for page ID '{$id}'." ); + } } $title = $this->page->getTitle(); diff --git a/includes/deferred/LinksUpdate.php b/includes/deferred/LinksUpdate.php index ae3c66008c..dbe387be73 100644 --- a/includes/deferred/LinksUpdate.php +++ b/includes/deferred/LinksUpdate.php @@ -21,6 +21,7 @@ */ use Wikimedia\Rdbms\IDatabase; +use MediaWiki\Logger\LoggerFactory; use MediaWiki\MediaWikiServices; use Wikimedia\ScopedCallback; @@ -163,6 +164,9 @@ class LinksUpdate extends DataUpdate implements EnqueueableDataUpdate { // Make sure all links update threads see the changes of each other. // This handles the case when updates have to batched into several COMMITs. $scopedLock = self::acquirePageLock( $this->getDB(), $this->mId ); + if ( !$scopedLock ) { + throw new RuntimeException( "Could not acquire lock for page ID '{$this->mId}'." ); + } } // Avoid PHP 7.1 warning from passing $this by reference @@ -190,15 +194,19 @@ class LinksUpdate extends DataUpdate implements EnqueueableDataUpdate { * @param IDatabase $dbw * @param int $pageId * @param string $why One of (job, atomicity) - * @return ScopedCallback - * @throws RuntimeException + * @return ScopedCallback|null * @since 1.27 */ public static function acquirePageLock( IDatabase $dbw, $pageId, $why = 'atomicity' ) { $key = "LinksUpdate:$why:pageid:$pageId"; $scopedLock = $dbw->getScopedLockAndFlush( $key, __METHOD__, 15 ); if ( !$scopedLock ) { - throw new RuntimeException( "Could not acquire lock '$key'." ); + $logger = LoggerFactory::getInstance( 'SecondaryDataUpdate' ); + $logger->info( "Could not acquire lock '{key}' for page ID '{page_id}'.", [ + 'key' => $key, + 'page_id' => $pageId, + ] ); + return null; } return $scopedLock; @@ -584,10 +592,11 @@ class LinksUpdate extends DataUpdate implements EnqueueableDataUpdate { global $wgCategoryCollation; $diffs = array_diff_assoc( $this->mCategories, $existing ); $arr = []; + $contLang = MediaWikiServices::getInstance()->getContentLanguage(); + $collation = Collation::singleton(); foreach ( $diffs as $name => $prefix ) { $nt = Title::makeTitleSafe( NS_CATEGORY, $name ); - MediaWikiServices::getInstance()->getContentLanguage()-> - findVariantLink( $name, $nt, true ); + $contLang->findVariantLink( $name, $nt, true ); $type = MWNamespace::getCategoryLinkType( $this->mTitle->getNamespace() ); @@ -595,8 +604,7 @@ class LinksUpdate extends DataUpdate implements EnqueueableDataUpdate { # things are forced to sort as '*' or something, they'll # sort properly in the category rather than in page_id # order or such. - $sortkey = Collation::singleton()->getSortKey( - $this->mTitle->getCategorySortkey( $prefix ) ); + $sortkey = $collation->getSortKey( $this->mTitle->getCategorySortkey( $prefix ) ); $arr[] = [ 'cl_from' => $this->mId, diff --git a/includes/deferred/SearchUpdate.php b/includes/deferred/SearchUpdate.php index 105877bd35..3625476398 100644 --- a/includes/deferred/SearchUpdate.php +++ b/includes/deferred/SearchUpdate.php @@ -75,13 +75,14 @@ class SearchUpdate implements DeferrableUpdate { * Perform actual update for the entry */ public function doUpdate() { - $config = MediaWikiServices::getInstance()->getSearchEngineConfig(); + $services = MediaWikiServices::getInstance(); + $config = $services->getSearchEngineConfig(); if ( $config->getConfig()->get( 'DisableSearchUpdate' ) || !$this->id ) { return; } - $seFactory = MediaWikiServices::getInstance()->getSearchEngineFactory(); + $seFactory = $services->getSearchEngineFactory(); foreach ( $config->getSearchTypes() as $type ) { $search = $seFactory->create( $type ); if ( !$search->supports( 'search-update' ) ) { @@ -117,14 +118,16 @@ class SearchUpdate implements DeferrableUpdate { * @return string */ public function updateText( $text, SearchEngine $se = null ) { + $services = MediaWikiServices::getInstance(); + $contLang = $services->getContentLanguage(); # Language-specific strip/conversion - $text = MediaWikiServices::getInstance()->getContentLanguage()->normalizeForSearch( $text ); - $se = $se ?: MediaWikiServices::getInstance()->newSearchEngine(); + $text = $contLang->normalizeForSearch( $text ); + $se = $se ?: $services->newSearchEngine(); $lc = $se->legalSearchChars() . '&#;'; # Strip HTML markup $text = preg_replace( "/<\\/?\\s*[A-Za-z][^>]*?>/", - ' ', MediaWikiServices::getInstance()->getContentLanguage()->lc( " " . $text . " " ) ); + ' ', $contLang->lc( " " . $text . " " ) ); $text = preg_replace( "/(^|\\n)==\\s*([^\\n]+)\\s*==(\\s)/sD", "\\1\\2 \\2 \\2\\3", $text ); # Emphasize headings @@ -199,13 +202,14 @@ class SearchUpdate implements DeferrableUpdate { * @return string A stripped-down title string ready for the search index */ private function getNormalizedTitle( SearchEngine $search ) { + $contLang = MediaWikiServices::getInstance()->getContentLanguage(); $ns = $this->title->getNamespace(); $title = $this->title->getText(); $lc = $search->legalSearchChars() . '&#;'; - $t = MediaWikiServices::getInstance()->getContentLanguage()->normalizeForSearch( $title ); + $t = $contLang->normalizeForSearch( $title ); $t = preg_replace( "/[^{$lc}]+/", ' ', $t ); - $t = MediaWikiServices::getInstance()->getContentLanguage()->lc( $t ); + $t = $contLang->lc( $t ); # Handle 's, s' $t = preg_replace( "/([{$lc}]+)'s( |$)/", "\\1 \\1's ", $t ); diff --git a/includes/diff/DifferenceEngine.php b/includes/diff/DifferenceEngine.php index 5138655763..9602bd20d5 100644 --- a/includes/diff/DifferenceEngine.php +++ b/includes/diff/DifferenceEngine.php @@ -20,12 +20,30 @@ * @file * @ingroup DifferenceEngine */ -use MediaWiki\MediaWikiServices; -use MediaWiki\Shell\Shell; + +use MediaWiki\Storage\RevisionRecord; /** - * @todo document + * DifferenceEngine is responsible for rendering the difference between two revisions as HTML. + * This includes interpreting URL parameters, retrieving revision data, checking access permissions, + * selecting and invoking the diff generator class for the individual slots, doing post-processing + * on the generated diff, adding the rest of the HTML (such as headers) and writing the whole thing + * to OutputPage. + * + * DifferenceEngine can be subclassed by extensions, by customizing + * ContentHandler::createDifferenceEngine; the content handler will be selected based on the + * content model of the main slot (of the new revision, when the two are different). + * That might change after PageTypeHandler gets introduced. + * + * In the past, the class was also used for slot-level diff generation, and extensions might still + * subclass it and add such functionality. When that is the case (sepcifically, when a + * ContentHandler returns a standard SlotDiffRenderer but a nonstandard DifferenceEngine) + * DifferenceEngineSlotDiffRenderer will be used to convert the old behavior into the new one. + * * @ingroup DifferenceEngine + * + * @todo This class is huge and poorly defined. It should be split into a controller responsible + * for interpreting query parameters, retrieving data and checking permissions; and a HTML renderer. */ class DifferenceEngine extends ContextSource { @@ -39,35 +57,88 @@ class DifferenceEngine extends ContextSource { */ const DIFF_VERSION = '1.12'; - /** @var int Revision ID or 0 for current */ + /** + * Revision ID for the old revision. 0 for the revision previous to $mNewid, false + * if the diff does not have an old revision (e.g. 'oldid=&diff=prev'), + * or the revision does not exist, null if the revision is unsaved. + * @var int|false|null + */ protected $mOldid; - /** @var int|string Revision ID or null for current or an alias such as 'next' */ + /** + * Revision ID for the new revision. 0 for the last revision of the current page + * (as defined by the request context), false if the revision does not exist, null + * if it is unsaved, or an alias such as 'next'. + * @var int|string|false|null + */ protected $mNewid; - private $mOldTags; - private $mNewTags; - - /** @var Content|null */ - protected $mOldContent; - - /** @var Content|null */ - protected $mNewContent; + /** + * Old revision (left pane). + * Allowed to be an unsaved revision, unlikely that's ever needed though. + * False when the old revision does not exist; this can happen when using + * diff=prev on the first revision. Null when the revision should exist but + * doesn't (e.g. load failure); loadRevisionData() will return false in that + * case. Also null until lazy-loaded. Ignored completely when isContentOverridden + * is set. + * Since 1.32 public access is deprecated. + * @var Revision|null|false + */ + protected $mOldRev; - /** @var Language */ - protected $mDiffLang; + /** + * New revision (right pane). + * Note that this might be an unsaved revision (e.g. for edit preview). + * Null in case of load failure; diff methods will just return an error message in that case, + * and loadRevisionData() will return false. Also null until lazy-loaded. Ignored completely + * when isContentOverridden is set. + * Since 1.32 public access is deprecated. + * @var Revision|null + */ + protected $mNewRev; - /** @var Title */ + /** + * Title of $mOldRev or null if the old revision does not exist or does not belong to a page. + * Since 1.32 public access is deprecated and the property can be null. + * @var Title|null + */ protected $mOldPage; - /** @var Title */ + /** + * Title of $mNewRev or null if the new revision does not exist or does not belong to a page. + * Since 1.32 public access is deprecated and the property can be null. + * @var Title|null + */ protected $mNewPage; - /** @var Revision|null */ - public $mOldRev; + /** + * Change tags of $mOldRev or null if it does not exist / is not saved. + * @var string[]|null + */ + private $mOldTags; + + /** + * Change tags of $mNewRev or null if it does not exist / is not saved. + * @var string[]|null + */ + private $mNewTags; + + /** + * @var Content|null + * @deprecated since 1.32, content slots are now handled by the corresponding SlotDiffRenderer. + * This property is set to the content of the main slot, but not actually used for the main diff. + */ + private $mOldContent; + + /** + * @var Content|null + * @deprecated since 1.32, content slots are now handled by the corresponding SlotDiffRenderer. + * This property is set to the content of the main slot, but not actually used for the main diff. + */ + private $mNewContent; - /** @var Revision|null */ - public $mNewRev; + /** @var Language */ + protected $mDiffLang; /** @var bool Have the revisions IDs been loaded */ private $mRevisionsIdsLoaded = false; @@ -80,7 +151,10 @@ class DifferenceEngine extends ContextSource { /** * Was the content overridden via setContent()? - * If the content was overridden, most internal state (e.g. mOldid or mOldRev) should be ignored. + * If the content was overridden, most internal state (e.g. mOldid or mOldRev) should be ignored + * and only mOldContent and mNewContent is reliable. + * (Note that setRevisions() does not set this flag as in that case all properties are + * overriden and remain consistent with each other, so no special handling is needed.) * @var bool */ protected $isContentOverridden = false; @@ -109,6 +183,17 @@ class DifferenceEngine extends ContextSource { /** @var bool Refresh the diff cache */ protected $mRefreshCache = false; + /** @var SlotDiffRenderer[] DifferenceEngine classes for the slots, keyed by role name. */ + protected $slotDiffRenderers = null; + + /** + * Temporary hack for B/C while slot diff related methods of DifferenceEngine are being + * deprecated. When true, we are inside a DifferenceEngineSlotDiffRenderer and + * $slotDiffRenderers should not be used. + * @var bool + */ + protected $isSlotDiffRenderer = false; + /**#@-*/ /** @@ -124,6 +209,8 @@ class DifferenceEngine extends ContextSource { ) { $this->deprecatePublicProperty( 'mOldid', '1.32', __CLASS__ ); $this->deprecatePublicProperty( 'mNewid', '1.32', __CLASS__ ); + $this->deprecatePublicProperty( 'mOldRev', '1.32', __CLASS__ ); + $this->deprecatePublicProperty( 'mNewRev', '1.32', __CLASS__ ); $this->deprecatePublicProperty( 'mOldPage', '1.32', __CLASS__ ); $this->deprecatePublicProperty( 'mNewPage', '1.32', __CLASS__ ); $this->deprecatePublicProperty( 'mOldContent', '1.32', __CLASS__ ); @@ -145,6 +232,91 @@ class DifferenceEngine extends ContextSource { } /** + * @return SlotDiffRenderer[] Diff renderers for each slot, keyed by role name. + * Includes slots only present in one of the revisions. + */ + protected function getSlotDiffRenderers() { + if ( $this->isSlotDiffRenderer ) { + throw new LogicException( __METHOD__ . ' called in slot diff renderer mode' ); + } + + if ( $this->slotDiffRenderers === null ) { + if ( !$this->loadRevisionData() ) { + return []; + } + + $slotContents = $this->getSlotContents(); + $this->slotDiffRenderers = array_map( function ( $contents ) { + /** @var $content Content */ + $content = $contents['new'] ?: $contents['old']; + return $content->getContentHandler()->getSlotDiffRenderer( $this->getContext() ); + }, $slotContents ); + } + return $this->slotDiffRenderers; + } + + /** + * Mark this DifferenceEngine as a slot renderer (as opposed to a page renderer). + * This is used in legacy mode when the DifferenceEngine is wrapped in a + * DifferenceEngineSlotDiffRenderer. + * @internal For use by DifferenceEngineSlotDiffRenderer only. + */ + public function markAsSlotDiffRenderer() { + $this->isSlotDiffRenderer = true; + } + + /** + * Get the old and new content objects for all slots. + * This method does not do any permission checks. + * @return array [ role => [ 'old' => SlotRecord|null, 'new' => SlotRecord|null ], ... ] + */ + protected function getSlotContents() { + if ( $this->isContentOverridden ) { + return [ + 'main' => [ + 'old' => $this->mOldContent, + 'new' => $this->mNewContent, + ] + ]; + } elseif ( !$this->loadRevisionData() ) { + return []; + } + + $newSlots = $this->mNewRev->getRevisionRecord()->getSlots()->getSlots(); + if ( $this->mOldRev ) { + $oldSlots = $this->mOldRev->getRevisionRecord()->getSlots()->getSlots(); + } else { + $oldSlots = []; + } + // The order here will determine the visual order of the diff. The current logic is + // slots of the new revision first in natural order, then deleted ones. This is ad hoc + // and should not be relied on - in the future we may want the ordering to depend + // on the page type. + $roles = array_merge( array_keys( $newSlots ), array_keys( $oldSlots ) ); + + $slots = []; + foreach ( $roles as $role ) { + $slots[$role] = [ + 'old' => isset( $oldSlots[$role] ) ? $oldSlots[$role]->getContent() : null, + 'new' => isset( $newSlots[$role] ) ? $newSlots[$role]->getContent() : null, + ]; + } + // move main slot to front + if ( isset( $slots['main'] ) ) { + $slots = [ 'main' => $slots['main'] ] + $slots; + } + return $slots; + } + + public function getTitle() { + // T202454 avoid errors when there is no title + return parent::getTitle() ?: Title::makeTitle( NS_SPECIAL, 'BadTitle/DifferenceEngine' ); + } + + /** + * Set reduced line numbers mode. + * When set, line X is not displayed when X is 1, for example to increase readability and + * conserve space with many small diffs. * @param bool $value */ public function setReducedLineNumbers( $value = true ) { @@ -173,7 +345,11 @@ class DifferenceEngine extends ContextSource { } /** - * @return int + * Get the ID of old revision (left pane) of the diff. 0 for the revision + * previous to getNewid(), false if the old revision does not exist, null + * if it's unsaved. + * To get a real revision ID instead of 0, call loadRevisionData() first. + * @return int|false|null */ public function getOldid() { $this->loadRevisionIds(); @@ -182,7 +358,10 @@ class DifferenceEngine extends ContextSource { } /** - * @return bool|int + * Get the ID of new revision (right pane) of the diff. 0 for the current revision, + * false if the new revision does not exist, null if it's unsaved. + * To get a real revision ID instead of 0, call loadRevisionData() first. + * @return int|false|null */ public function getNewid() { $this->loadRevisionIds(); @@ -190,6 +369,25 @@ class DifferenceEngine extends ContextSource { return $this->mNewid; } + /** + * Get the left side of the diff. + * Could be null when the first revision of the page is diffed to 'prev' (or in the case of + * load failure). + * @return RevisionRecord|null + */ + public function getOldRevision() { + return $this->mOldRev ? $this->mOldRev->getRevisionRecord() : null; + } + + /** + * Get the right side of the diff. + * Should not be null but can still happen in the case of load failure. + * @return RevisionRecord|null + */ + public function getNewRevision() { + return $this->mNewRev ? $this->mNewRev->getRevisionRecord() : null; + } + /** * Look up a special:Undelete link to the given deleted revision id, * as a workaround for being unable to load deleted diffs in currently. @@ -280,8 +478,11 @@ class DifferenceEngine extends ContextSource { } $user = $this->getUser(); - $permErrors = $this->mNewPage->getUserPermissionsErrors( 'read', $user ); - if ( $this->mOldPage ) { # mOldPage might not be set, see below. + $permErrors = []; + if ( $this->mNewPage ) { + $permErrors = $this->mNewPage->getUserPermissionsErrors( 'read', $user ); + } + if ( $this->mOldPage ) { $permErrors = wfMergeErrorArrays( $permErrors, $this->mOldPage->getUserPermissionsErrors( 'read', $user ) ); } @@ -311,7 +512,9 @@ class DifferenceEngine extends ContextSource { # a diff between a version V and its previous version V' AND the version V # is the first version of that article. In that case, V' does not exist. if ( $this->mOldRev === false ) { - $out->setPageTitle( $this->msg( 'difference-title', $this->mNewPage->getPrefixedText() ) ); + if ( $this->mNewPage ) { + $out->setPageTitle( $this->msg( 'difference-title', $this->mNewPage->getPrefixedText() ) ); + } $samePage = true; $oldHeader = ''; // Allow extensions to change the $oldHeader variable @@ -319,7 +522,10 @@ class DifferenceEngine extends ContextSource { } else { Hooks::run( 'DiffViewHeader', [ $this, $this->mOldRev, $this->mNewRev ] ); - if ( $this->mNewPage->equals( $this->mOldPage ) ) { + if ( !$this->mOldPage || !$this->mNewPage ) { + // XXX say something to the user? + $samePage = false; + } elseif ( $this->mNewPage->equals( $this->mOldPage ) ) { $out->setPageTitle( $this->msg( 'difference-title', $this->mNewPage->getPrefixedText() ) ); $samePage = true; } else { @@ -329,7 +535,7 @@ class DifferenceEngine extends ContextSource { $samePage = false; } - if ( $samePage && $this->mNewPage->quickUserCan( 'edit', $user ) ) { + if ( $samePage && $this->mNewPage && $this->mNewPage->quickUserCan( 'edit', $user ) ) { if ( $this->mNewRev->isCurrent() && $this->mNewPage->userCan( 'rollback', $user ) ) { $rollbackLink = Linker::generateRollback( $this->mNewRev, $this->getContext() ); if ( $rollbackLink ) { @@ -356,7 +562,7 @@ class DifferenceEngine extends ContextSource { } # Make "previous revision link" - if ( $samePage && $this->mOldRev->getPrevious() ) { + if ( $samePage && $this->mOldPage && $this->mOldRev->getPrevious() ) { $prevlink = Linker::linkKnown( $this->mOldPage, $this->msg( 'previousdiff' )->escaped(), @@ -409,7 +615,7 @@ class DifferenceEngine extends ContextSource { # Make "next revision link" # Skip next link on the top revision - if ( $samePage && !$this->mNewRev->isCurrent() ) { + if ( $samePage && $this->mNewPage && !$this->mNewRev->isCurrent() ) { $nextlink = Linker::linkKnown( $this->mNewPage, $this->msg( 'nextdiff' )->escaped(), @@ -517,7 +723,7 @@ class DifferenceEngine extends ContextSource { if ( $this->mMarkPatrolledLink === null ) { $linkInfo = $this->getMarkPatrolledLinkInfo(); // If false, there is no patrol link needed/allowed - if ( !$linkInfo ) { + if ( !$linkInfo || !$this->mNewPage ) { $this->mMarkPatrolledLink = ''; } else { $this->mMarkPatrolledLink = ' [' . @@ -553,7 +759,7 @@ class DifferenceEngine extends ContextSource { // Prepare a change patrol link, if applicable if ( // Is patrolling enabled and the user allowed to? - $wgUseRCPatrol && $this->mNewPage->quickUserCan( 'patrol', $user ) && + $wgUseRCPatrol && $this->mNewPage && $this->mNewPage->quickUserCan( 'patrol', $user ) && // Only do this if the revision isn't more than 6 hours older // than the Max RC age (6h because the RC might not be cleaned out regularly) RecentChange::isInRCLifespan( $this->mNewRev->getTimestamp(), 21600 ) @@ -616,8 +822,20 @@ class DifferenceEngine extends ContextSource { /** * Show the new revision of the page. + * + * @note Not supported after calling setContent(). */ public function renderNewRevision() { + if ( $this->isContentOverridden ) { + // The code below only works with a Revision object. We could construct a fake revision + // (here or in setContent), but since this does not seem needed at the moment, + // we'll just fail for now. + throw new LogicException( + __METHOD__ + . ' is not supported after calling setContent(). Use setRevisions() instead.' + ); + } + $out = $this->getOutput(); $revHeader = $this->getRevisionHeader( $this->mNewRev ); # Add "current version as of X" title @@ -626,14 +844,27 @@ class DifferenceEngine extends ContextSource { # Page content may be handled by a hooked call instead... if ( Hooks::run( 'ArticleContentOnDiff', [ $this, $out ] ) ) { $this->loadNewText(); + if ( !$this->mNewPage ) { + // New revision is unsaved; bail out. + // TODO in theory rendering the new revision is a meaningful thing to do + // even if it's unsaved, but a lot of untangling is required to do it safely. + return; + } + $out->setRevisionId( $this->mNewid ); $out->setRevisionTimestamp( $this->mNewRev->getTimestamp() ); $out->setArticleFlag( true ); - if ( !Hooks::run( 'ArticleContentViewCustom', - [ $this->mNewContent, $this->mNewPage, $out ] ) + if ( !Hooks::run( 'ArticleRevisionViewCustom', + [ $this->mNewRev->getRevisionRecord(), $this->mNewPage, $out ] ) + ) { + // Handled by extension + // NOTE: sync with hooks called in Article::view() + } elseif ( !Hooks::run( 'ArticleContentViewCustom', + [ $this->mNewContent, $this->mNewPage, $out ], '1.32' ) ) { // Handled by extension + // NOTE: sync with hooks called in Article::view() } else { // Normal page if ( $this->getTitle()->equals( $this->mNewPage ) ) { @@ -677,6 +908,13 @@ class DifferenceEngine extends ContextSource { * @return ParserOutput|bool False if the revision was not found */ protected function getParserOutput( WikiPage $page, Revision $rev ) { + if ( !$rev->getId() ) { + // WikiPage::getParserOutput wants a revision ID. Passing 0 will incorrectly show + // the current revision, so fail instead. If need be, WikiPage::getParserOutput + // could be made to accept a Revision or RevisionRecord instead of the id. + return false; + } + $parserOptions = $page->makeParserOptions( $this->getContext() ); $parserOutput = $page->getParserOutput( $parserOptions, $rev->getId() ); @@ -714,7 +952,12 @@ class DifferenceEngine extends ContextSource { * Add style sheets for diff display. */ public function showDiffStyle() { - $this->getOutput()->addModuleStyles( 'mediawiki.diff.styles' ); + if ( !$this->isSlotDiffRenderer ) { + $this->getOutput()->addModuleStyles( 'mediawiki.diff.styles' ); + foreach ( $this->getSlotDiffRenderers() as $slotDiffRenderer ) { + $slotDiffRenderer->addModules( $this->getOutput() ); + } + } } /** @@ -751,25 +994,28 @@ class DifferenceEngine extends ContextSource { public function getDiffBody() { $this->mCacheHit = true; // Check if the diff should be hidden from this user - if ( !$this->loadRevisionData() ) { - return false; - } elseif ( $this->mOldRev && - !$this->mOldRev->userCan( Revision::DELETED_TEXT, $this->getUser() ) - ) { - return false; - } elseif ( $this->mNewRev && - !$this->mNewRev->userCan( Revision::DELETED_TEXT, $this->getUser() ) - ) { - return false; - } - // Short-circuit - if ( $this->mOldRev === false || ( $this->mOldRev && $this->mNewRev - && $this->mOldRev->getId() == $this->mNewRev->getId() ) - ) { - if ( Hooks::run( 'DifferenceEngineShowEmptyOldContent', [ $this ] ) ) { - return ''; + if ( !$this->isContentOverridden ) { + if ( !$this->loadRevisionData() ) { + return false; + } elseif ( $this->mOldRev && + !$this->mOldRev->userCan( Revision::DELETED_TEXT, $this->getUser() ) + ) { + return false; + } elseif ( $this->mNewRev && + !$this->mNewRev->userCan( Revision::DELETED_TEXT, $this->getUser() ) + ) { + return false; + } + // Short-circuit + if ( $this->mOldRev === false || ( $this->mOldRev && $this->mNewRev && + $this->mOldRev->getId() && $this->mOldRev->getId() == $this->mNewRev->getId() ) + ) { + if ( Hooks::run( 'DifferenceEngineShowEmptyOldContent', [ $this ] ) ) { + return ''; + } } } + // Cacheable? $key = false; $cache = ObjectCache::getMainWANInstance(); @@ -800,7 +1046,20 @@ class DifferenceEngine extends ContextSource { return false; } - $difftext = $this->generateContentDiffBody( $this->mOldContent, $this->mNewContent ); + $difftext = ''; + // We've checked for revdelete at the beginning of this method; it's OK to ignore + // read permissions here. + $slotContents = $this->getSlotContents(); + foreach ( $this->getSlotDiffRenderers() as $role => $slotDiffRenderer ) { + $slotDiff = $slotDiffRenderer->getDiff( $slotContents[$role]['old'], + $slotContents[$role]['new'] ); + if ( $slotDiff && $role !== 'main' ) { + // TODO use human-readable role name at least + $slotTitle = $role; + $difftext .= $this->getSlotHeader( $slotTitle ); + } + $difftext .= $slotDiff; + } // Avoid PHP 7.1 warning from passing $this by reference $diffEngine = $this; @@ -822,6 +1081,49 @@ class DifferenceEngine extends ContextSource { return $difftext; } + /** + * Get the diff table body for one slot, without header + * + * @param string $role + * @return string|false + */ + public function getDiffBodyForRole( $role ) { + $diffRenderers = $this->getSlotDiffRenderers(); + if ( !isset( $diffRenderers[$role] ) ) { + return false; + } + + $slotContents = $this->getSlotContents(); + $slotDiff = $diffRenderers[$role]->getDiff( $slotContents[$role]['old'], + $slotContents[$role]['new'] ); + if ( !$slotDiff ) { + return false; + } + + if ( $role !== 'main' ) { + // TODO use human-readable role name at least + $slotTitle = $role; + $slotDiff = $this->getSlotHeader( $slotTitle ) . $slotDiff; + } + + return $this->localiseDiff( $slotDiff ); + } + + /** + * Get a slot header for inclusion in a diff body (as a table row). + * + * @param string $headerText The text of the header + * @return string + * + */ + protected function getSlotHeader( $headerText ) { + // The old revision is missing on oldid=&diff=prev; only 2 columns in that case. + $columnCount = $this->mOldRev ? 4 : 2; + $userLang = $this->getLanguage()->getHtmlCode(); + return Html::rawElement( 'tr', [ 'class' => 'mw-diff-slot-header', 'lang' => $userLang ], + Html::element( 'th', [ 'colspan' => $columnCount ], $headerText ) ); + } + /** * Returns the cache key for diff body text or content. * @@ -867,98 +1169,112 @@ class DifferenceEngine extends ContextSource { $params[] = $this->getConfig()->get( 'WikiDiff2MovedParagraphDetectionCutoff' ); } + if ( !$this->isSlotDiffRenderer ) { + foreach ( $this->getSlotDiffRenderers() as $slotDiffRenderer ) { + $params = array_merge( $params, $slotDiffRenderer->getExtraCacheKeys() ); + } + } + + return $params; + } + + /** + * Implements DifferenceEngineSlotDiffRenderer::getExtraCacheKeys(). Only used when + * DifferenceEngine is wrapped in DifferenceEngineSlotDiffRenderer. + * @return array + * @internal for use by DifferenceEngineSlotDiffRenderer only + * @deprecated + */ + public function getExtraCacheKeys() { + // This method is called when the DifferenceEngine is used for a slot diff. We only care + // about special things, not the revision IDs, which are added to the cache key by the + // page-level DifferenceEngine, and which might not have a valid value for this object. + $this->mOldid = 123456789; + $this->mNewid = 987654321; + + // This will repeat a bunch of unnecessary key fields for each slot. Not nice but harmless. + $cacheString = $this->getDiffBodyCacheKey(); + if ( $cacheString ) { + return [ $cacheString ]; + } + + $params = $this->getDiffBodyCacheKeyParams(); + + // Try to get rid of the standard keys to keep the cache key human-readable: + // call the getDiffBodyCacheKeyParams implementation of the base class, and if + // the child class includes the same keys, drop them. + // Uses an obscure PHP feature where static calls to non-static methods are allowed + // as long as we are already in a non-static method of the same class, and the call context + // ($this) will be inherited. + // phpcs:ignore Squiz.Classes.SelfMemberReference.NotUsed + $standardParams = DifferenceEngine::getDiffBodyCacheKeyParams(); + if ( array_slice( $params, 0, count( $standardParams ) ) === $standardParams ) { + $params = array_slice( $params, count( $standardParams ) ); + } + return $params; } /** * Generate a diff, no caching. * - * This implementation uses generateTextDiffBody() to generate a diff based on the default - * serialization of the given Content objects. This will fail if $old or $new are not - * instances of TextContent. - * - * Subclasses may override this to provide a different rendering for the diff, - * perhaps taking advantage of the content's native form. This is required for all content - * models that are not text based. - * * @since 1.21 * * @param Content $old Old content * @param Content $new New content * - * @throws MWException If old or new content is not an instance of TextContent. + * @throws Exception If old or new content is not an instance of TextContent. * @return bool|string + * + * @deprecated since 1.32, use a SlotDiffRenderer instead. */ public 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 ); + $slotDiffRenderer = $new->getContentHandler()->getSlotDiffRenderer( $this->getContext() ); + if ( + $slotDiffRenderer instanceof DifferenceEngineSlotDiffRenderer + && $this->isSlotDiffRenderer + ) { + // Oops, we are just about to enter an infinite loop (the slot-level DifferenceEngine + // called a DifferenceEngineSlotDiffRenderer that wraps the same DifferenceEngine class). + // This will happen when a content model has no custom slot diff renderer, it does have + // a custom difference engine, but that does not override this method. + throw new Exception( get_class( $this ) . ': could not maintain backwards compatibility. ' + . 'Please use a SlotDiffRenderer.' ); + } + return $slotDiffRenderer->getDiff( $old, $new ) . $this->getDebugString(); } /** * Generate a diff, no caching * - * @todo move this to TextDifferenceEngine, make DifferenceEngine abstract. At some point. - * * @param string $otext Old text, must be already segmented * @param string $ntext New text, must be already segmented * + * @throws Exception If content handling for text content is configured in a way + * that makes maintaining B/C hard. * @return bool|string + * + * @deprecated since 1.32, use a TextSlotDiffRenderer instead. */ public function generateTextDiffBody( $otext, $ntext ) { - $diff = function () use ( $otext, $ntext ) { - $time = microtime( true ); - - $result = $this->textDiff( $otext, $ntext ); - - $time = intval( ( microtime( true ) - $time ) * 1000 ); - MediaWikiServices::getInstance()->getStatsdDataFactory()->timing( 'diff_time', $time ); - // Log requests slower than 99th percentile - if ( $time > 100 && $this->mOldPage && $this->mNewPage ) { - wfDebugLog( 'diff', - "$time ms diff: {$this->mOldid} -> {$this->mNewid} {$this->mNewPage}" ); - } - - return $result; - }; - - /** - * @param Status $status - * @throws FatalError - */ - $error = function ( $status ) { - throw new FatalError( $status->getWikiText() ); - }; - - // Use PoolCounter if the diff looks like it can be expensive - if ( strlen( $otext ) + strlen( $ntext ) > 20000 ) { - $work = new PoolCounterWorkViaCallback( 'diff', - md5( $otext ) . md5( $ntext ), - [ 'doWork' => $diff, 'error' => $error ] - ); - return $work->execute(); - } - - return $diff(); + $slotDiffRenderer = ContentHandler::getForModelID( CONTENT_MODEL_TEXT ) + ->getSlotDiffRenderer( $this->getContext() ); + if ( !( $slotDiffRenderer instanceof TextSlotDiffRenderer ) ) { + // Someone used the GetSlotDiffRenderer hook to replace the renderer. + // This is too unlikely to happen to bother handling properly. + throw new Exception( 'The slot diff renderer for text content should be a ' + . 'TextSlotDiffRenderer subclass' ); + } + return $slotDiffRenderer->getTextDiff( $otext, $ntext ) . $this->getDebugString(); } /** * Process $wgExternalDiffEngine and get a sane, usable engine * * @return bool|string 'wikidiff2', path to an executable, or false + * @internal For use by this class and TextSlotDiffRenderer only. */ - private function getEngine() { + public static function getEngine() { global $wgExternalDiffEngine; // We use the global here instead of Config because we write to the value, // and Config is not mutable. @@ -989,91 +1305,23 @@ class DifferenceEngine extends ContextSource { * * @param string $otext Old text, must be already segmented * @param string $ntext New text, must be already segmented + * + * @throws Exception If content handling for text content is configured in a way + * that makes maintaining B/C hard. * @return bool|string + * + * @deprecated since 1.32, use a TextSlotDiffRenderer instead. */ protected function textDiff( $otext, $ntext ) { - $otext = str_replace( "\r\n", "\n", $otext ); - $ntext = str_replace( "\r\n", "\n", $ntext ); - - $engine = $this->getEngine(); - - // Better external diff engine, the 2 may some day be dropped - // This one does the escaping and segmenting itself - if ( $engine === 'wikidiff2' ) { - $wikidiff2Version = phpversion( 'wikidiff2' ); - if ( - $wikidiff2Version !== false && - version_compare( $wikidiff2Version, '1.5.0', '>=' ) - ) { - $text = wikidiff2_do_diff( - $otext, - $ntext, - 2, - $this->getConfig()->get( 'WikiDiff2MovedParagraphDetectionCutoff' ) - ); - } else { - // Don't pass the 4th parameter for compatibility with older versions of wikidiff2 - $text = wikidiff2_do_diff( - $otext, - $ntext, - 2 - ); - - // Log a warning in case the configuration value is set to not silently ignore it - if ( $this->getConfig()->get( 'WikiDiff2MovedParagraphDetectionCutoff' ) > 0 ) { - wfLogWarning( '$wgWikiDiff2MovedParagraphDetectionCutoff is set but has no - effect since the used version of WikiDiff2 does not support it.' ); - } - } - - $text .= $this->debug( 'wikidiff2' ); - - return $text; - } elseif ( $engine !== false ) { - # Diff via the shell - $tmpDir = wfTempDir(); - $tempName1 = tempnam( $tmpDir, 'diff_' ); - $tempName2 = tempnam( $tmpDir, 'diff_' ); - - $tempFile1 = fopen( $tempName1, "w" ); - if ( !$tempFile1 ) { - return false; - } - $tempFile2 = fopen( $tempName2, "w" ); - if ( !$tempFile2 ) { - return false; - } - fwrite( $tempFile1, $otext ); - fwrite( $tempFile2, $ntext ); - fclose( $tempFile1 ); - fclose( $tempFile2 ); - $cmd = [ $engine, $tempName1, $tempName2 ]; - $result = Shell::command( $cmd ) - ->execute(); - $exitCode = $result->getExitCode(); - if ( $exitCode !== 0 ) { - throw new Exception( "External diff command returned code {$exitCode}. Stderr: " - . wfEscapeWikiText( $result->getStderr() ) - ); - } - $difftext = $result->getStdout(); - $difftext .= $this->debug( "external $engine" ); - unlink( $tempName1 ); - unlink( $tempName2 ); - - return $difftext; - } - - # Native PHP diff - $contLang = MediaWikiServices::getInstance()->getContentLanguage(); - $ota = explode( "\n", $contLang->segmentForDiff( $otext ) ); - $nta = explode( "\n", $contLang->segmentForDiff( $ntext ) ); - $diffs = new Diff( $ota, $nta ); - $formatter = new TableDiffFormatter(); - $difftext = $contLang->unsegmentForDiff( $formatter->format( $diffs ) ); - $difftext .= $this->debug( 'native PHP' ); - - return $difftext; + $slotDiffRenderer = ContentHandler::getForModelID( CONTENT_MODEL_TEXT ) + ->getSlotDiffRenderer( $this->getContext() ); + if ( !( $slotDiffRenderer instanceof TextSlotDiffRenderer ) ) { + // Someone used the GetSlotDiffRenderer hook to replace the renderer. + // This is too unlikely to happen to bother handling properly. + throw new Exception( 'The slot diff renderer for text content should be a ' + . 'TextSlotDiffRenderer subclass' ); + } + return $slotDiffRenderer->getTextDiff( $otext, $ntext ) . $this->getDebugString(); } /** @@ -1100,6 +1348,17 @@ class DifferenceEngine extends ContextSource { " -->\n"; } + private function getDebugString() { + $engine = self::getEngine(); + if ( $engine === 'wikidiff2' ) { + return $this->debug( 'wikidiff2' ); + } elseif ( $engine === false ) { + return $this->debug( 'native PHP' ); + } else { + return $this->debug( "external $engine" ); + } + } + /** * Localise diff output * @@ -1170,10 +1429,12 @@ class DifferenceEngine extends ContextSource { * @return string */ public function getMultiNotice() { - if ( !is_object( $this->mOldRev ) || !is_object( $this->mNewRev ) ) { - return ''; - } elseif ( !$this->mOldPage->equals( $this->mNewPage ) ) { - // Comparing two different pages? Count would be meaningless. + // The notice only make sense if we are diffing two saved revisions of the same page. + if ( + !$this->mOldRev || !$this->mNewRev + || !$this->mOldPage || !$this->mNewPage + || !$this->mOldPage->equals( $this->mNewPage ) + ) { return ''; } @@ -1353,6 +1614,7 @@ class DifferenceEngine extends ContextSource { * @param Content $oldContent * @param Content $newContent * @since 1.21 + * @deprecated since 1.32, use setRevisions or ContentHandler::getSlotDiffRenderer. */ public function setContent( Content $oldContent, Content $newContent ) { $this->mOldContent = $oldContent; @@ -1361,6 +1623,39 @@ class DifferenceEngine extends ContextSource { $this->mTextLoaded = 2; $this->mRevisionsLoaded = true; $this->isContentOverridden = true; + $this->slotDiffRenderers = null; + } + + /** + * Use specified text instead of loading from the database. + * @param RevisionRecord|null $oldRevision + * @param RevisionRecord $newRevision + */ + public function setRevisions( + RevisionRecord $oldRevision = null, RevisionRecord $newRevision + ) { + if ( $oldRevision ) { + $this->mOldRev = new Revision( $oldRevision ); + $this->mOldid = $oldRevision->getId(); + $this->mOldPage = Title::newFromLinkTarget( $oldRevision->getPageAsLinkTarget() ); + // This method is meant for edit diffs and such so there is no reason to provide a + // revision that's not readable to the user, but check it just in case. + $this->mOldContent = $oldRevision ? $oldRevision->getContent( 'main', + RevisionRecord::FOR_THIS_USER, $this->getUser() ) : null; + } else { + $this->mOldPage = null; + $this->mOldRev = $this->mOldid = false; + } + $this->mNewRev = new Revision( $newRevision ); + $this->mNewid = $newRevision->getId(); + $this->mNewPage = Title::newFromLinkTarget( $newRevision->getPageAsLinkTarget() ); + $this->mNewContent = $newRevision->getContent( 'main', + RevisionRecord::FOR_THIS_USER, $this->getUser() ); + + $this->mRevisionsIdsLoaded = $this->mRevisionsLoaded = true; + $this->mTextLoaded = !!$oldRevision + 1; + $this->isContentOverridden = false; + $this->slotDiffRenderers = null; } /** @@ -1383,7 +1678,7 @@ class DifferenceEngine extends ContextSource { * @param int $old Revision id, e.g. from URL parameter 'oldid' * @param int|string $new Revision id or strings 'next' or 'prev', e.g. from URL parameter 'diff' * - * @return int[] List of two revision ids, older first, later second. + * @return array List of two revision ids, older first, later second. * Zero signifies invalid argument passed. * false signifies that there is no previous/next revision ($old is the oldest/newest one). */ @@ -1431,20 +1726,21 @@ class DifferenceEngine extends ContextSource { } /** - * Load revision metadata for the specified articles. If newid is 0, then compare - * the old article in oldid to the current article; if oldid is 0, then - * compare the current article to the immediately previous one (ignoring the - * value of newid). + * Load revision metadata for the specified revisions. If newid is 0, then compare + * the old revision in oldid to the current revision of the current page (as defined + * by the request context); if oldid is 0, then compare the revision in newid to the + * immediately previous one. * * If oldid is false, leave the corresponding revision object set - * to false. This is impossible via ordinary user input, and is provided for - * API convenience. + * to false. This can happen with 'diff=prev' pointing to a non-existent revision, + * and is also used directly by the API. * - * @return bool Whether both revisions were loaded successfully. + * @return bool Whether both revisions were loaded successfully. Setting mOldRev + * to false counts as successful loading. */ public function loadRevisionData() { if ( $this->mRevisionsLoaded ) { - return $this->isContentOverridden || $this->mNewRev && $this->mOldRev; + return $this->isContentOverridden || $this->mNewRev && !is_null( $this->mOldRev ); } // Whether it succeeds or fails, we don't want to try again @@ -1469,7 +1765,11 @@ class DifferenceEngine extends ContextSource { // Update the new revision ID in case it was 0 (makes life easier doing UI stuff) $this->mNewid = $this->mNewRev->getId(); - $this->mNewPage = $this->mNewRev->getTitle(); + if ( $this->mNewid ) { + $this->mNewPage = $this->mNewRev->getTitle(); + } else { + $this->mNewPage = null; + } // Load the old revision object $this->mOldRev = false; @@ -1491,8 +1791,10 @@ class DifferenceEngine extends ContextSource { return false; } - if ( $this->mOldRev ) { + if ( $this->mOldRev && $this->mOldRev->getId() ) { $this->mOldPage = $this->mOldRev->getTitle(); + } else { + $this->mOldPage = null; } // Load tags information for both revisions @@ -1519,12 +1821,16 @@ class DifferenceEngine extends ContextSource { /** * Load the text of the revisions, as well as revision data. + * When the old revision is missing (mOldRev is false), loading mOldContent is not attempted. * * @return bool Whether the content of both revisions could be loaded successfully. + * (When mOldRev is false, that still counts as a success.) + * */ public function loadText() { if ( $this->mTextLoaded == 2 ) { - return $this->loadRevisionData() && $this->mOldContent && $this->mNewContent; + return $this->loadRevisionData() && ( $this->mOldRev === false || $this->mOldContent ) + && $this->mNewContent; } // Whether it succeeds or fails, we don't want to try again @@ -1541,12 +1847,10 @@ class DifferenceEngine extends ContextSource { } } - if ( $this->mNewRev ) { - $this->mNewContent = $this->mNewRev->getContent( Revision::FOR_THIS_USER, $this->getUser() ); - Hooks::run( 'DifferenceEngineLoadTextAfterNewContentIsLoaded', [ $this ] ); - if ( $this->mNewContent === null ) { - return false; - } + $this->mNewContent = $this->mNewRev->getContent( Revision::FOR_THIS_USER, $this->getUser() ); + Hooks::run( 'DifferenceEngineLoadTextAfterNewContentIsLoaded', [ $this ] ); + if ( $this->mNewContent === null ) { + return false; } return true; diff --git a/includes/diff/DifferenceEngineSlotDiffRenderer.php b/includes/diff/DifferenceEngineSlotDiffRenderer.php new file mode 100644 index 0000000000..0c632b97ec --- /dev/null +++ b/includes/diff/DifferenceEngineSlotDiffRenderer.php @@ -0,0 +1,79 @@ +differenceEngine = clone $differenceEngine; + + // Set state to loaded. This should not matter to any of the methods invoked by + // the adapter, but just in case a load does get triggered somehow, make sure it's a no-op. + $fakeContent = ContentHandler::getForModelID( CONTENT_MODEL_WIKITEXT )->makeEmptyContent(); + $this->differenceEngine->setContent( $fakeContent, $fakeContent ); + + $this->differenceEngine->markAsSlotDiffRenderer(); + } + + /** @inheritDoc */ + public function getDiff( Content $oldContent = null, Content $newContent = null ) { + if ( !$oldContent && !$newContent ) { + throw new InvalidArgumentException( '$oldContent and $newContent cannot both be null' ); + } + if ( !$oldContent || !$newContent ) { + $someContent = $newContent ?: $oldContent; + $emptyContent = $someContent->getContentHandler()->makeEmptyContent(); + $oldContent = $oldContent ?: $emptyContent; + $newContent = $newContent ?: $emptyContent; + } + return $this->differenceEngine->generateContentDiffBody( $oldContent, $newContent ); + } + + public function addModules( OutputPage $output ) { + $oldContext = null; + if ( $output !== $this->differenceEngine->getOutput() ) { + $oldContext = $this->differenceEngine->getContext(); + $newContext = new DerivativeContext( $oldContext ); + $newContext->setOutput( $output ); + $this->differenceEngine->setContext( $newContext ); + } + $this->differenceEngine->showDiffStyle(); + if ( $oldContext ) { + $this->differenceEngine->setContext( $oldContext ); + } + } + + public function getExtraCacheKeys() { + return $this->differenceEngine->getExtraCacheKeys(); + } + +} diff --git a/includes/diff/SlotDiffRenderer.php b/includes/diff/SlotDiffRenderer.php new file mode 100644 index 0000000000..f20b4169db --- /dev/null +++ b/includes/diff/SlotDiffRenderer.php @@ -0,0 +1,65 @@ +getSlotDiffRenderer( RequestContext::getMain() ) + * + * @ingroup DifferenceEngine + */ +class TextSlotDiffRenderer extends SlotDiffRenderer { + + /** Use the PHP diff implementation (DiffEngine). */ + const ENGINE_PHP = 'php'; + + /** Use the wikidiff2 PHP module. */ + const ENGINE_WIKIDIFF2 = 'wikidiff2'; + + /** Use an external executable. */ + const ENGINE_EXTERNAL = 'external'; + + /** @var IBufferingStatsdDataFactory|null */ + private $statsdDataFactory; + + /** @var Language|null The language this content is in. */ + private $language; + + /** + * Number of paragraph moves the algorithm should attempt to detect. + * Only used with the wikidiff2 engine. + * @var int + * @see $wgWikiDiff2MovedParagraphDetectionCutoff + */ + private $wikiDiff2MovedParagraphDetectionCutoff = 0; + + /** @var string One of the ENGINE_* constants. */ + private $engine = self::ENGINE_PHP; + + /** @var string Path to an executable to be used as the diff engine. */ + private $externalEngine; + + /** + * Convenience helper to use getTextDiff without an instance. + * @param string $oldText + * @param string $newText + * @return string + */ + public static function diff( $oldText, $newText ) { + /** @var $slotDiffRenderer TextSlotDiffRenderer */ + $slotDiffRenderer = ContentHandler::getForModelID( CONTENT_MODEL_TEXT ) + ->getSlotDiffRenderer( RequestContext::getMain() ); + return $slotDiffRenderer->getTextDiff( $oldText, $newText ); + } + + public function setStatsdDataFactory( IBufferingStatsdDataFactory $statsdDataFactory ) { + $this->statsdDataFactory = $statsdDataFactory; + } + + public function setLanguage( Language $language ) { + $this->language = $language; + } + /** + * @param int $cutoff + * @see $wgWikiDiff2MovedParagraphDetectionCutoff + */ + public function setWikiDiff2MovedParagraphDetectionCutoff( $cutoff ) { + Assert::parameterType( 'integer', $cutoff, '$cutoff' ); + $this->wikiDiff2MovedParagraphDetectionCutoff = $cutoff; + } + + /** + * Set which diff engine to use. + * @param string $type One of the ENGINE_* constants. + * @param string|null $executable Path to an external exectable, only when type is ENGINE_EXTERNAL. + */ + public function setEngine( $type, $executable = null ) { + $engines = [ self::ENGINE_PHP, self::ENGINE_WIKIDIFF2, self::ENGINE_EXTERNAL ]; + Assert::parameter( in_array( $type, $engines, true ), '$type', + 'must be one of the TextSlotDiffRenderer::ENGINE_* constants' ); + if ( $type === self::ENGINE_EXTERNAL ) { + Assert::parameter( is_string( $executable ) && is_executable( $executable ), '$executable', + 'must be a path to a valid executable' ); + } else { + Assert::parameter( is_null( $executable ), '$executable', + 'must not be set unless $type is ENGINE_EXTERNAL' ); + } + $this->engine = $type; + $this->externalEngine = $executable; + } + + /** @inheritDoc */ + public function getDiff( Content $oldContent = null, Content $newContent = null ) { + if ( !$oldContent && !$newContent ) { + throw new InvalidArgumentException( '$oldContent and $newContent cannot both be null' ); + } elseif ( $oldContent && !( $oldContent instanceof TextContent ) ) { + throw new InvalidArgumentException( __CLASS__ . ' does not handle ' . get_class( $oldContent ) ); + } elseif ( $newContent && !( $newContent instanceof TextContent ) ) { + throw new InvalidArgumentException( __CLASS__ . ' does not handle ' . get_class( $newContent ) ); + } + + if ( !$oldContent ) { + $oldContent = $newContent->getContentHandler()->makeEmptyContent(); + } elseif ( !$newContent ) { + $newContent = $oldContent->getContentHandler()->makeEmptyContent(); + } + + $oldText = $oldContent->serialize(); + $newText = $newContent->serialize(); + + return $this->getTextDiff( $oldText, $newText ); + } + + /** + * Diff the text representations of two content objects (or just two pieces of text in general). + * @param string $oldText + * @param string $newText + * @return string + */ + public function getTextDiff( $oldText, $newText ) { + Assert::parameterType( 'string', $oldText, '$oldText' ); + Assert::parameterType( 'string', $newText, '$newText' ); + + $diff = function () use ( $oldText, $newText ) { + $time = microtime( true ); + + $result = $this->getTextDiffInternal( $oldText, $newText ); + + $time = intval( ( microtime( true ) - $time ) * 1000 ); + if ( $this->statsdDataFactory ) { + $this->statsdDataFactory->timing( 'diff_time', $time ); + } + + // TODO reimplement this using T142313 + /* + // Log requests slower than 99th percentile + if ( $time > 100 && $this->mOldPage && $this->mNewPage ) { + wfDebugLog( 'diff', + "$time ms diff: {$this->mOldid} -> {$this->mNewid} {$this->mNewPage}" ); + } + */ + + return $result; + }; + + /** + * @param Status $status + * @throws FatalError + */ + $error = function ( $status ) { + throw new FatalError( $status->getWikiText() ); + }; + + // Use PoolCounter if the diff looks like it can be expensive + if ( strlen( $oldText ) + strlen( $newText ) > 20000 ) { + $work = new PoolCounterWorkViaCallback( 'diff', + md5( $oldText ) . md5( $newText ), + [ 'doWork' => $diff, 'error' => $error ] + ); + return $work->execute(); + } + + return $diff(); + } + + /** + * Diff the text representations of two content objects (or just two pieces of text in general). + * This does the actual diffing, getTextDiff() wraps it with logging and resource limiting. + * @param string $oldText + * @param string $newText + * @return string + * @throws Exception + */ + protected function getTextDiffInternal( $oldText, $newText ) { + // TODO move most of this into three parallel implementations of a text diff generator + // class, choose which one to use via dependecy injection + + $oldText = str_replace( "\r\n", "\n", $oldText ); + $newText = str_replace( "\r\n", "\n", $newText ); + + // Better external diff engine, the 2 may some day be dropped + // This one does the escaping and segmenting itself + if ( $this->engine === self::ENGINE_WIKIDIFF2 ) { + $wikidiff2Version = phpversion( 'wikidiff2' ); + if ( + $wikidiff2Version !== false && + version_compare( $wikidiff2Version, '1.5.0', '>=' ) && + version_compare( $wikidiff2Version, '1.8.0', '<' ) + ) { + $text = wikidiff2_do_diff( + $oldText, + $newText, + 2, + $this->wikiDiff2MovedParagraphDetectionCutoff + ); + } else { + // Don't pass the 4th parameter introduced in version 1.5.0 and removed in version 1.8.0 + $text = wikidiff2_do_diff( + $oldText, + $newText, + 2 + ); + + // Log a warning in case the configuration value is set to not silently ignore it + if ( $this->wikiDiff2MovedParagraphDetectionCutoff > 0 ) { + wfLogWarning( '$wgWikiDiff2MovedParagraphDetectionCutoff is set but has no + effect since the used version of WikiDiff2 does not support it.' ); + } + } + + return $text; + } elseif ( $this->engine === self::ENGINE_EXTERNAL ) { + # Diff via the shell + $tmpDir = wfTempDir(); + $tempName1 = tempnam( $tmpDir, 'diff_' ); + $tempName2 = tempnam( $tmpDir, 'diff_' ); + + $tempFile1 = fopen( $tempName1, "w" ); + if ( !$tempFile1 ) { + return false; + } + $tempFile2 = fopen( $tempName2, "w" ); + if ( !$tempFile2 ) { + return false; + } + fwrite( $tempFile1, $oldText ); + fwrite( $tempFile2, $newText ); + fclose( $tempFile1 ); + fclose( $tempFile2 ); + $cmd = [ $this->externalEngine, $tempName1, $tempName2 ]; + $result = Shell::command( $cmd ) + ->execute(); + $exitCode = $result->getExitCode(); + if ( $exitCode !== 0 ) { + throw new Exception( "External diff command returned code {$exitCode}. Stderr: " + . wfEscapeWikiText( $result->getStderr() ) + ); + } + $difftext = $result->getStdout(); + unlink( $tempName1 ); + unlink( $tempName2 ); + + return $difftext; + } elseif ( $this->engine === self::ENGINE_PHP ) { + if ( $this->language ) { + $oldText = $this->language->segmentForDiff( $oldText ); + $newText = $this->language->segmentForDiff( $newText ); + } + $ota = explode( "\n", $oldText ); + $nta = explode( "\n", $newText ); + $diffs = new Diff( $ota, $nta ); + $formatter = new TableDiffFormatter(); + $difftext = $formatter->format( $diffs ); + if ( $this->language ) { + $difftext = $this->language->unsegmentForDiff( $difftext ); + } + + return $difftext; + } + throw new LogicException( 'Invalid engine: ' . $this->engine ); + } + +} diff --git a/includes/exception/MWException.php b/includes/exception/MWException.php index b3e9422b8d..652a87dcfc 100644 --- a/includes/exception/MWException.php +++ b/includes/exception/MWException.php @@ -73,15 +73,23 @@ class MWException extends Exception { * @return string Message with arguments replaced */ public function msg( $key, $fallback /*[, params...] */ ) { + global $wgSitename; $args = array_slice( func_get_args(), 2 ); + $res = false; if ( $this->useMessageCache() ) { try { - return wfMessage( $key, $args )->text(); + $res = wfMessage( $key, $args )->text(); } catch ( Exception $e ) { } } - return wfMsgReplaceArgs( $fallback, $args ); + if ( $res === false ) { + $res = wfMsgReplaceArgs( $fallback, $args ); + // If an exception happens inside message rendering, + // {{SITENAME}} sometimes won't be replaced. + $res = preg_replace( '/\{\{SITENAME\}\}/', $wgSitename, $res ); + } + return $res; } /** @@ -154,6 +162,16 @@ class MWException extends Exception { global $wgOut, $wgSitename; if ( $this->useOutputPage() ) { $wgOut->prepareErrorPage( $this->getPageTitle() ); + // Manually set the html title, since sometimes + // {{SITENAME}} does not get replaced for exceptions + // happening inside message rendering. + $wgOut->setHTMLTitle( + $this->msg( + 'pagetitle', + "$1 - $wgSitename", + $this->getPageTitle() + ) + ); $wgOut->addHTML( $this->getHTML() ); diff --git a/includes/exception/MWExceptionHandler.php b/includes/exception/MWExceptionHandler.php index 00dca9ddf6..bd823b5e07 100644 --- a/includes/exception/MWExceptionHandler.php +++ b/includes/exception/MWExceptionHandler.php @@ -176,8 +176,21 @@ class MWExceptionHandler { return self::handleFatalError( ...func_get_args() ); } - // Map error constant to error name (reverse-engineer PHP error - // reporting) + // Map PHP error constant to a PSR-3 severity level. + // Avoid use of "DEBUG" or "INFO" levels, unless the + // error should evade error monitoring and alerts. + // + // To decide the log level, ask yourself: "Has the + // program's behaviour diverged from what the written + // code expected?" + // + // For example, use of a deprecated method or violating a strict standard + // has no impact on functional behaviour (Warning). On the other hand, + // accessing an undefined variable makes behaviour diverge from what the + // author intended/expected. PHP recovers from an undefined variables by + // yielding null and continuing execution, but it remains a change in + // behaviour given the null was not part of the code and is likely not + // accounted for. switch ( $level ) { case E_RECOVERABLE_ERROR: $levelName = 'Error'; @@ -186,23 +199,27 @@ class MWExceptionHandler { case E_WARNING: case E_CORE_WARNING: case E_COMPILE_WARNING: - case E_USER_WARNING: $levelName = 'Warning'; - $severity = LogLevel::WARNING; + $severity = LogLevel::ERROR; break; case E_NOTICE: - case E_USER_NOTICE: $levelName = 'Notice'; - $severity = LogLevel::INFO; + $severity = LogLevel::ERROR; + break; + case E_USER_WARNING: + case E_USER_NOTICE: + // Used by wfWarn(), MWDebug::warning() + $levelName = 'Warning'; + $severity = LogLevel::WARNING; break; case E_STRICT: $levelName = 'Strict Standards'; - $severity = LogLevel::DEBUG; + $severity = LogLevel::WARNING; break; case E_DEPRECATED: case E_USER_DEPRECATED: $levelName = 'Deprecated'; - $severity = LogLevel::INFO; + $severity = LogLevel::WARNING; break; default: $levelName = 'Unknown error'; diff --git a/includes/exception/MWExceptionRenderer.php b/includes/exception/MWExceptionRenderer.php index 49cf71e624..1f1cabeae1 100644 --- a/includes/exception/MWExceptionRenderer.php +++ b/includes/exception/MWExceptionRenderer.php @@ -197,12 +197,17 @@ class MWExceptionRenderer { * @return string Message with arguments replaced */ private static function msg( $key, $fallback /*[, params...] */ ) { + global $wgSitename; $args = array_slice( func_get_args(), 2 ); try { - return wfMessage( $key, $args )->text(); + $res = wfMessage( $key, $args )->text(); } catch ( Exception $e ) { - return wfMsgReplaceArgs( $fallback, $args ); + $res = wfMsgReplaceArgs( $fallback, $args ); + // If an exception happens inside message rendering, + // {{SITENAME}} sometimes won't be replaced. + $res = preg_replace( '/\{\{SITENAME\}\}/', $wgSitename, $res ); } + return $res; } /** diff --git a/includes/export/WikiExporter.php b/includes/export/WikiExporter.php index 6c7a449372..b0185843e2 100644 --- a/includes/export/WikiExporter.php +++ b/includes/export/WikiExporter.php @@ -263,6 +263,8 @@ class WikiExporter { * @throws Exception */ protected function dumpFrom( $cond = '', $orderRevs = false ) { + global $wgMultiContentRevisionSchemaMigrationStage; + # For logging dumps... if ( $this->history & self::LOGS ) { $where = []; @@ -330,8 +332,17 @@ class WikiExporter { } # For page dumps... } else { + if ( !( $wgMultiContentRevisionSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) ) { + // TODO: Make XmlDumpWriter use a RevisionStore! (see T198706 and T174031) + throw new MWException( + 'Cannot use WikiExporter with SCHEMA_COMPAT_WRITE_OLD mode disabled!' + . ' Support for dumping from the new schema is not implemented yet!' + ); + } + $revOpts = [ 'page' ]; if ( $this->text != self::STUB ) { + // TODO: remove the text and make XmlDumpWriter use a RevisionStore instead! (T198706) $revOpts[] = 'text'; } $revQuery = Revision::getQueryInfo( $revOpts ); @@ -343,7 +354,8 @@ class WikiExporter { ]; unset( $join['page'] ); - $fields = array_merge( $revQuery['fields'], [ 'page_restrictions' ] ); + // TODO: remove rev_text_id and make XmlDumpWriter use a RevisionStore instead! (T198706) + $fields = array_merge( $revQuery['fields'], [ 'page_restrictions, rev_text_id' ] ); $conds = []; if ( $cond !== '' ) { diff --git a/includes/filerepo/LocalRepo.php b/includes/filerepo/LocalRepo.php index 789ed4c0a6..c889e56723 100644 --- a/includes/filerepo/LocalRepo.php +++ b/includes/filerepo/LocalRepo.php @@ -278,6 +278,7 @@ class LocalRepo extends FileRepo { $applyMatchingFiles = function ( ResultWrapper $res, &$searchSet, &$finalFiles ) use ( $fileMatchesSearch, $flags ) { + $contLang = MediaWikiServices::getInstance()->getContentLanguage(); $info = $this->getInfo(); foreach ( $res as $row ) { $file = $this->newFileFromRow( $row ); @@ -286,8 +287,7 @@ class LocalRepo extends FileRepo { $dbKeysLook = [ strtr( $file->getName(), ' ', '_' ) ]; if ( !empty( $info['initialCapital'] ) ) { // Search keys for "hi.png" and "Hi.png" should use the "Hi.png file" - $dbKeysLook[] = MediaWikiServices::getInstance()->getContentLanguage()-> - lcfirst( $file->getName() ); + $dbKeysLook[] = $contLang->lcfirst( $file->getName() ); } foreach ( $dbKeysLook as $dbKey ) { if ( isset( $searchSet[$dbKey] ) diff --git a/includes/filerepo/file/ForeignAPIFile.php b/includes/filerepo/file/ForeignAPIFile.php index 2ed4853f17..fd3dc8bba6 100644 --- a/includes/filerepo/file/ForeignAPIFile.php +++ b/includes/filerepo/file/ForeignAPIFile.php @@ -355,11 +355,12 @@ class ForeignAPIFile extends File { } function purgeDescriptionPage() { + $services = MediaWikiServices::getInstance(); $url = $this->repo->getDescriptionRenderUrl( - $this->getName(), MediaWikiServices::getInstance()->getContentLanguage()->getCode() ); + $this->getName(), $services->getContentLanguage()->getCode() ); $key = $this->repo->getLocalCacheKey( 'RemoteFileDescription', 'url', md5( $url ) ); - MediaWikiServices::getInstance()->getMainWANObjectCache()->delete( $key ); + $services->getMainWANObjectCache()->delete( $key ); } /** diff --git a/includes/filerepo/file/LocalFile.php b/includes/filerepo/file/LocalFile.php index 7920e9cb4b..b6c249b4a2 100644 --- a/includes/filerepo/file/LocalFile.php +++ b/includes/filerepo/file/LocalFile.php @@ -63,7 +63,7 @@ class LocalFile extends File { /** @var string MEDIATYPE_xxx (bitmap, drawing, audio...) */ protected $media_type; - /** @var string MIME type, determined by MimeMagic::guessMimeType */ + /** @var string MIME type, determined by MimeAnalyzer::guessMimeType */ protected $mime; /** @var int Size in bytes (loadFromXxx) */ @@ -2116,16 +2116,21 @@ class LocalFile extends File { * @return string|false */ function getDescriptionText( Language $lang = null ) { - $revision = Revision::newFromTitle( $this->title, false, Revision::READ_NORMAL ); + $store = MediaWikiServices::getInstance()->getRevisionStore(); + $revision = $store->getRevisionByTitle( $this->title, 0, Revision::READ_NORMAL ); if ( !$revision ) { return false; } - $content = $revision->getContent(); - if ( !$content ) { + + $renderer = MediaWikiServices::getInstance()->getRevisionRenderer(); + $rendered = $renderer->getRenderedRevision( $revision, new ParserOptions( null, $lang ) ); + + if ( !$rendered ) { + // audience check failed return false; } - $pout = $content->getParserOutput( $this->title, null, new ParserOptions( null, $lang ) ); + $pout = $rendered->getRevisionParserOutput(); return $pout->getText(); } diff --git a/includes/gallery/PackedImageGallery.php b/includes/gallery/PackedImageGallery.php index d1c945528d..7aa8c78893 100644 --- a/includes/gallery/PackedImageGallery.php +++ b/includes/gallery/PackedImageGallery.php @@ -107,6 +107,5 @@ class PackedImageGallery extends TraditionalImageGallery { * @param int $num */ public function setPerRow( $num ) { - return; } } diff --git a/includes/htmlform/HTMLForm.php b/includes/htmlform/HTMLForm.php index 442a7cf6fa..52a18eb7b8 100644 --- a/includes/htmlform/HTMLForm.php +++ b/includes/htmlform/HTMLForm.php @@ -57,10 +57,12 @@ use Wikimedia\ObjectFactory; * 'cssclass' -- CSS class * 'csshelpclass' -- CSS class used to style help text * 'dir' -- Direction of the element. - * 'options' -- associative array mapping labels to values. + * 'options' -- associative array mapping raw text labels to values. * Some field types support multi-level arrays. + * Overwrites 'options-message'. * 'options-messages' -- associative array mapping message keys to values. * Some field types support multi-level arrays. + * Overwrites 'options' and 'options-message'. * 'options-message' -- message key or object to be parsed to extract the list of * options (like 'ipbreason-dropdown'). * 'label-message' -- message key or object for a message to use as the label. @@ -76,11 +78,12 @@ use Wikimedia\ObjectFactory; * 'help-messages' -- array of message keys/objects. As above, each item can * be an array of msg key and then parameters. * Overwrites 'help'. - * 'notice' -- message text for a message to use as a notice in the field. - * Currently used by OOUI form fields only. - * 'notice-messages' -- array of message keys/objects to use for notice. - * Overrides 'notice'. - * 'notice-message' -- message key or object to use as a notice. + * 'help-inline' -- Whether help text (defined using options above) will be shown + * inline after the input field, rather than in a popup. + * Defaults to true. Only used by OOUI form fields. + * 'notice' -- (deprecated, use 'help' instead) + * 'notice-messages' -- (deprecated, use 'help-messages' instead) + * 'notice-message' -- (deprecated, use 'help-message' instead) * 'required' -- passed through to the object, indicating that it * is a required field. * 'size' -- the length of text fields @@ -1026,6 +1029,7 @@ class HTMLForm extends ContextSource { * @param bool|string|array|Status $submitResult Output from HTMLForm::trySubmit() * * @return string HTML + * @return-taint escaped */ public function getHTML( $submitResult ) { # For good measure (it is the default) @@ -1829,7 +1833,7 @@ class HTMLForm extends ContextSource { * * @param string $key * - * @return string + * @return string Plain text (not HTML-escaped) */ public function getLegend( $key ) { return $this->msg( "{$this->mMessagePrefix}-$key" )->text(); diff --git a/includes/htmlform/HTMLFormField.php b/includes/htmlform/HTMLFormField.php index 97e4b50551..890299525e 100644 --- a/includes/htmlform/HTMLFormField.php +++ b/includes/htmlform/HTMLFormField.php @@ -462,6 +462,16 @@ abstract class HTMLFormField { if ( isset( $params['hide-if'] ) ) { $this->mHideIf = $params['hide-if']; } + + if ( isset( $this->mParams['notice-message'] ) ) { + wfDeprecated( "'notice-message' parameter in HTMLForm", '1.32' ); + } + if ( isset( $this->mParams['notice-messages'] ) ) { + wfDeprecated( "'notice-messages' parameter in HTMLForm", '1.32' ); + } + if ( isset( $this->mParams['notice'] ) ) { + wfDeprecated( "'notice' parameter in HTMLForm", '1.32' ); + } } /** @@ -607,7 +617,7 @@ abstract class HTMLFormField { $error = new OOUI\HtmlSnippet( $error ); } - $notices = $this->getNotices(); + $notices = $this->getNotices( 'skip deprecation' ); foreach ( $notices as &$notice ) { $notice = new OOUI\HtmlSnippet( $notice ); } @@ -619,6 +629,7 @@ abstract class HTMLFormField { 'errors' => $errors, 'notices' => $notices, 'infusable' => $infusable, + 'helpInline' => $this->isHelpInline(), ]; $preloadModules = false; @@ -679,8 +690,8 @@ abstract class HTMLFormField { * @return bool */ protected function shouldInfuseOOUI() { - // Always infuse fields with help text, since the interface for it is nicer with JS - return $this->getHelpText() !== null; + // Always infuse fields with popup help text, since the interface for it is nicer with JS + return $this->getHelpText() !== null && !$this->isHelpInline(); } /** @@ -851,12 +862,29 @@ abstract class HTMLFormField { return $helptext; } + /** + * Determine if the help text should be displayed inline. + * + * Only applies to OOUI forms. + * + * @since 1.31 + * @return bool + */ + public function isHelpInline() { + return $this->mParams['help-inline'] ?? true; + } + /** * Determine form errors to display and their classes * @since 1.20 * + * phan-taint-check gets confused with returning both classes + * and errors and thinks double escaping is happening, so specify + * that return value has no taint. + * * @param string $value The value of the input * @return array array( $errors, $errorClass ) + * @return-taint none */ public function getErrorsAndErrorClass( $value ) { $errors = $this->validate( $value, $this->mParent->mFieldData ); @@ -902,9 +930,16 @@ abstract class HTMLFormField { * Determine notices to display for the field. * * @since 1.28 + * @deprecated since 1.32 + * @param string $skipDeprecation Pass 'skip deprecation' to avoid the deprecation + * warning (since 1.32) * @return string[] */ - public function getNotices() { + public function getNotices( $skipDeprecation = null ) { + if ( $skipDeprecation !== 'skip deprecation' ) { + wfDeprecated( __METHOD__, '1.32' ); + } + $notices = []; if ( isset( $this->mParams['notice-message'] ) ) { @@ -1119,6 +1154,12 @@ abstract class HTMLFormField { * Formats one or more errors as accepted by field validation-callback. * * @param string|Message|array $errors Array of strings or Message instances + * To work around limitations in phan-taint-check the calling + * class has taintedness disabled. So instead we pretend that + * this method outputs html, since the result is eventually + * outputted anyways without escaping and this allows us to verify + * stuff is safe even though the caller has taintedness cleared. + * @param-taint $errors exec_html * @return string HTML * @since 1.18 */ diff --git a/includes/htmlform/fields/HTMLCheckMatrix.php b/includes/htmlform/fields/HTMLCheckMatrix.php index da68a626a6..281d348b9b 100644 --- a/includes/htmlform/fields/HTMLCheckMatrix.php +++ b/includes/htmlform/fields/HTMLCheckMatrix.php @@ -9,17 +9,18 @@ * * Options: * - columns - * - Required list of columns in the matrix. + * - Required associative array mapping column labels (as HTML) to their tags. * - rows - * - Required list of rows in the matrix. + * - Required associative array mapping row labels (as HTML) to their tags. * - force-options-on - * - Accepts array of column-row tags to be displayed as enabled but unavailable to change + * - Array of column-row tags to be displayed as enabled but unavailable to change. * - force-options-off - * - Accepts array of column-row tags to be displayed as disabled but unavailable to change. + * - Array of column-row tags to be displayed as disabled but unavailable to change. * - tooltips - * - Optional array mapping row label to tooltip content + * - Optional associative array mapping row labels to tooltips (as text, will be escaped). * - tooltip-class * - Optional CSS class used on tooltip container span. Defaults to mw-icon-question. + * Not used by OOUI form fields. */ class HTMLCheckMatrix extends HTMLFormField implements HTMLNestedFilterable { static private $requiredParams = [ @@ -129,7 +130,7 @@ class HTMLCheckMatrix extends HTMLFormField implements HTMLNestedFilterable { $thisAttribs['class'] = 'checkmatrix-forced checkmatrix-forced-on'; } - $checkbox = $this->getOneCheckbox( $checked, $attribs + $thisAttribs ); + $checkbox = $this->getOneCheckboxHTML( $checked, $attribs + $thisAttribs ); $rowContents .= Html::rawElement( 'td', @@ -148,24 +149,33 @@ class HTMLCheckMatrix extends HTMLFormField implements HTMLNestedFilterable { return $html; } - protected function getOneCheckbox( $checked, $attribs ) { - if ( $this->mParent instanceof OOUIHTMLForm ) { - return new OOUI\CheckboxInputWidget( [ - 'name' => "{$this->mName}[]", - 'selected' => $checked, - ] + OOUI\Element::configFromHtmlAttributes( - $attribs - ) ); - } else { - $checkbox = Xml::check( "{$this->mName}[]", $checked, $attribs ); - if ( $this->mParent->getConfig()->get( 'UseMediaWikiUIEverywhere' ) ) { - $checkbox = Html::openElement( 'div', [ 'class' => 'mw-ui-checkbox' ] ) . - $checkbox . - Html::element( 'label', [ 'for' => $attribs['id'] ] ) . - Html::closeElement( 'div' ); - } - return $checkbox; + public function getInputOOUI( $value ) { + $attribs = $this->getAttributes( [ 'disabled', 'tabindex' ] ); + + return new MediaWiki\Widget\CheckMatrixWidget( + [ + 'name' => $this->mName, + 'infusable' => true, + 'id' => $this->mID, + 'rows' => $this->mParams['rows'], + 'columns' => $this->mParams['columns'], + 'tooltips' => $this->mParams['tooltips'] ?? [], + 'forcedOff' => $this->mParams['force-options-off'] ?? [], + 'forcedOn' => $this->mParams['force-options-on'] ?? [], + 'values' => $value, + ] + OOUI\Element::configFromHtmlAttributes( $attribs ) + ); + } + + protected function getOneCheckboxHTML( $checked, $attribs ) { + $checkbox = Xml::check( "{$this->mName}[]", $checked, $attribs ); + if ( $this->mParent->getConfig()->get( 'UseMediaWikiUIEverywhere' ) ) { + $checkbox = Html::openElement( 'div', [ 'class' => 'mw-ui-checkbox' ] ) . + $checkbox . + Html::element( 'label', [ 'for' => $attribs['id'] ] ) . + Html::closeElement( 'div' ); } + return $checkbox; } protected function isTagForcedOff( $tag ) { @@ -262,4 +272,12 @@ class HTMLCheckMatrix extends HTMLFormField implements HTMLNestedFilterable { return $res; } + + protected function getOOUIModules() { + return [ 'mediawiki.widgets.CheckMatrixWidget' ]; + } + + protected function shouldInfuseOOUI() { + return true; + } } diff --git a/includes/htmlform/fields/HTMLFloatField.php b/includes/htmlform/fields/HTMLFloatField.php index 72381f0161..15426fdb95 100644 --- a/includes/htmlform/fields/HTMLFloatField.php +++ b/includes/htmlform/fields/HTMLFloatField.php @@ -43,4 +43,8 @@ class HTMLFloatField extends HTMLTextField { return true; } + + protected function getInputWidget( $params ) { + return new OOUI\NumberInputWidget( $params ); + } } diff --git a/includes/htmlform/fields/HTMLMultiSelectField.php b/includes/htmlform/fields/HTMLMultiSelectField.php index 2038606597..e9ecc40f10 100644 --- a/includes/htmlform/fields/HTMLMultiSelectField.php +++ b/includes/htmlform/fields/HTMLMultiSelectField.php @@ -22,8 +22,7 @@ class HTMLMultiSelectField extends HTMLFormField implements HTMLNestedFilterable $this->mParams['disabled-options'] = []; } - // For backwards compatibility, also handle the old way with 'cssclass' => 'mw-chosen' - if ( isset( $params['dropdown'] ) || strpos( $this->mClass, 'mw-chosen' ) !== false ) { + if ( isset( $params['dropdown'] ) ) { $this->mClass .= ' mw-htmlform-dropdown'; } diff --git a/includes/htmlform/fields/HTMLTextField.php b/includes/htmlform/fields/HTMLTextField.php index b51182a2da..60c63d6a07 100644 --- a/includes/htmlform/fields/HTMLTextField.php +++ b/includes/htmlform/fields/HTMLTextField.php @@ -85,18 +85,19 @@ class HTMLTextField extends HTMLFormField { 'type', 'min', 'max', - 'pattern', - 'title', 'step', - 'list', + 'title', 'maxlength', 'tabindex', 'disabled', 'required', 'autofocus', - 'multiple', 'readonly', 'autocomplete', + // Only used in HTML mode: + 'pattern', + 'list', + 'multiple', ]; $attribs += $this->getAttributes( $allowedParams ); @@ -117,6 +118,7 @@ class HTMLTextField extends HTMLFormField { switch ( $this->mParams['type'] ) { case 'int': $type = 'number'; + $attribs['step'] = 1; break; case 'float': $type = 'number'; @@ -152,17 +154,22 @@ class HTMLTextField extends HTMLFormField { # @todo Enforce pattern, step, required, readonly on the server side as # well $allowedParams = [ + 'type', + 'min', + 'max', + 'step', + 'title', + 'maxlength', + 'tabindex', + 'disabled', + 'required', 'autofocus', + 'readonly', + 'autocomplete', + // Only used in OOUI mode: 'autosize', - 'disabled', 'flags', 'indicator', - 'maxlength', - 'readonly', - 'required', - 'tabindex', - 'type', - 'autocomplete', ]; $attribs += OOUI\Element::configFromHtmlAttributes( @@ -181,6 +188,9 @@ class HTMLTextField extends HTMLFormField { } $type = $this->getType( $attribs ); + if ( isset( $attribs['step'] ) && $attribs['step'] === 'any' ) { + $attribs['step'] = null; + } return $this->getInputWidget( [ 'id' => $this->mID, diff --git a/includes/installer/DatabaseUpdater.php b/includes/installer/DatabaseUpdater.php index e49a846679..82ccce2129 100644 --- a/includes/installer/DatabaseUpdater.php +++ b/includes/installer/DatabaseUpdater.php @@ -34,6 +34,8 @@ require_once __DIR__ . '/../../maintenance/Maintenance.php'; * @since 1.17 */ abstract class DatabaseUpdater { + const REPLICATION_WAIT_TIMEOUT = 300; + /** * Array of updates to perform on the database * @@ -484,7 +486,7 @@ abstract class DatabaseUpdater { flush(); if ( $ret !== false ) { $updatesDone[] = $origParams; - $lbFactory->waitForReplication(); + $lbFactory->waitForReplication( [ 'timeout' => self::REPLICATION_WAIT_TIMEOUT ] ); } else { $updatesSkipped[] = [ $func, $params, $origParams ]; } @@ -1153,21 +1155,6 @@ abstract class DatabaseUpdater { } } - /** - * Updates the timestamps in the transcache table - * @return bool - */ - protected function doUpdateTranscacheField() { - if ( $this->updateRowExists( 'convert transcache field' ) ) { - $this->output( "...transcache tc_time already converted.\n" ); - - return true; - } - - return $this->applyPatch( 'patch-tc-timestamp.sql', false, - "Converting tc_time from UNIX epoch to MediaWiki timestamp" ); - } - /** * Update CategoryLinks collation */ diff --git a/includes/installer/MssqlUpdater.php b/includes/installer/MssqlUpdater.php index 50c4517939..17b1d7e533 100644 --- a/includes/installer/MssqlUpdater.php +++ b/includes/installer/MssqlUpdater.php @@ -144,6 +144,13 @@ class MssqlUpdater extends DatabaseUpdater { [ 'addIndex', 'protected_titles', 'PRIMARY', 'patch-protected_titles-pk.sql' ], [ 'addIndex', 'page_props', 'PRIMARY', 'patch-page_props-pk.sql' ], [ 'addIndex', 'site_identifiers', 'PRIMARY', 'patch-site_identifiers-pk.sql' ], + [ 'addIndex', 'recentchanges', 'rc_this_oldid', 'patch-recentchanges-rc_this_oldid-index.sql' ], + [ 'dropTable', 'transcache' ], + [ 'runMaintenance', PopulateChangeTagDef::class, 'maintenance/populateChangeTagDef.php' ], + [ 'addIndex', 'change_tag', 'change_tag_rc_tag_id', + 'patch-change_tag-change_tag_rc_tag_id.sql' ], + [ 'addField', 'ipblocks', 'ipb_sitewide', 'patch-ipb_sitewide.sql' ], + [ 'addTable', 'ipblocks_restrictions', 'patch-ipblocks_restrictions-table.sql' ], ]; } diff --git a/includes/installer/MysqlInstaller.php b/includes/installer/MysqlInstaller.php index 45f932a83b..1b0780bc8c 100644 --- a/includes/installer/MysqlInstaller.php +++ b/includes/installer/MysqlInstaller.php @@ -485,18 +485,32 @@ class MysqlInstaller extends DatabaseInstaller { /** @var Database $conn */ $conn = $status->value; $dbName = $this->getVar( 'wgDBname' ); - if ( !$conn->selectDB( $dbName ) ) { + if ( !$this->databaseExists( $dbName ) ) { $conn->query( "CREATE DATABASE " . $conn->addIdentifierQuotes( $dbName ) . "CHARACTER SET utf8", __METHOD__ ); - $conn->selectDB( $dbName ); } + $conn->selectDB( $dbName ); $this->setupSchemaVars(); return $status; } + /** + * Try to see if a given database exists + * @param string $dbName Database name to check + * @return bool + */ + private function databaseExists( $dbName ) { + $encDatabase = $this->db->addQuotes( $dbName ); + + return $this->db->query( + "SELECT 1 FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = $encDatabase", + __METHOD__ + )->numRows() > 0; + } + /** * @return Status */ diff --git a/includes/installer/MysqlUpdater.php b/includes/installer/MysqlUpdater.php index ac0a520435..6430ecee5b 100644 --- a/includes/installer/MysqlUpdater.php +++ b/includes/installer/MysqlUpdater.php @@ -84,7 +84,6 @@ class MysqlUpdater extends DatabaseUpdater { [ 'doUserGroupsUpdate' ], [ 'addField', 'site_stats', 'ss_total_pages', 'patch-ss_total_articles.sql' ], [ 'addTable', 'user_newtalk', 'patch-usernewtalk.sql' ], - [ 'addTable', 'transcache', 'patch-transcache.sql' ], [ 'addField', 'interwiki', 'iw_trans', 'patch-interwiki-trans.sql' ], // 1.6 @@ -170,9 +169,8 @@ class MysqlUpdater extends DatabaseUpdater { [ 'doLogUsertextPopulation' ], [ 'doLogSearchPopulation' ], [ 'addTable', 'l10n_cache', 'patch-l10n_cache.sql' ], - [ 'addIndex', 'change_tag', 'change_tag_rc_tag', 'patch-change_tag-indexes.sql' ], + [ 'addIndex', 'tag_summary', 'tag_summary_rc_id', 'patch-change_tag-indexes.sql' ], [ 'addField', 'redirect', 'rd_interwiki', 'patch-rd_interwiki.sql' ], - [ 'doUpdateTranscacheField' ], [ 'doUpdateMimeMinorField' ], // 1.17 @@ -319,7 +317,6 @@ class MysqlUpdater extends DatabaseUpdater { [ 'renameIndex', 'querycache_info', 'qci_type', 'PRIMARY', false, 'patch-querycache_info-fix-pk.sql' ], [ 'renameIndex', 'site_stats', 'ss_row_id', 'PRIMARY', false, 'patch-site_stats-fix-pk.sql' ], - [ 'renameIndex', 'transcache', 'tc_url_idx', 'PRIMARY', false, 'patch-transcache-fix-pk.sql' ], [ 'renameIndex', 'user_former_groups', 'ufg_user_group', 'PRIMARY', false, 'patch-user_former_groups-fix-pk.sql' ], [ 'renameIndex', 'user_properties', 'user_properties_user_property', 'PRIMARY', false, @@ -367,6 +364,13 @@ class MysqlUpdater extends DatabaseUpdater { 'patch-protected_titles-fix-pk.sql' ], [ 'renameIndex', 'site_identifiers', 'site_ids_type', 'PRIMARY', false, 'patch-site_identifiers-fix-pk.sql' ], + [ 'addIndex', 'recentchanges', 'rc_this_oldid', 'patch-recentchanges-rc_this_oldid-index.sql' ], + [ 'dropTable', 'transcache' ], + [ 'runMaintenance', PopulateChangeTagDef::class, 'maintenance/populateChangeTagDef.php' ], + [ 'addIndex', 'change_tag', 'change_tag_rc_tag_id', + 'patch-change_tag-change_tag_rc_tag_id.sql' ], + [ 'addField', 'ipblocks', 'ipb_sitewide', 'patch-ipb_sitewide.sql' ], + [ 'addTable', 'ipblocks_restrictions', 'patch-ipblocks_restrictions-table.sql' ], ]; } @@ -597,7 +601,7 @@ class MysqlUpdater extends DatabaseUpdater { foreach ( $rows as $row ) { if ( $prev_title == $row->cur_title && $prev_namespace == $row->cur_namespace ) { - $deleteId[] = $row->cur_id; + $deleteId[] = (int)$row->cur_id; } $prev_title = $row->cur_title; $prev_namespace = $row->cur_namespace; @@ -925,7 +929,8 @@ class MysqlUpdater extends DatabaseUpdater { $count = ( $count + 1 ) % 100; if ( $count == 0 ) { $lbFactory = $services->getDBLoadBalancerFactory(); - $lbFactory->waitForReplication( [ 'wiki' => wfWikiID() ] ); + $lbFactory->waitForReplication( [ + 'wiki' => wfWikiID(), 'timeout' => self::REPLICATION_WAIT_TIMEOUT ] ); } $this->db->insert( 'templatelinks', [ diff --git a/includes/installer/OracleUpdater.php b/includes/installer/OracleUpdater.php index c9ed53f8e7..5833299e3f 100644 --- a/includes/installer/OracleUpdater.php +++ b/includes/installer/OracleUpdater.php @@ -155,6 +155,13 @@ class OracleUpdater extends DatabaseUpdater { [ 'addField', 'change_tag', 'ct_tag_id', 'patch-change_tag-tag_id.sql' ], [ 'addIndex', 'archive', 'ar_revid_uniq', 'patch-archive-ar_rev_id-unique.sql' ], [ 'populateContentTables' ], + [ 'addIndex', 'recentchanges', 'rc_this_oldid', 'patch-recentchanges-rc_this_oldid-index.sql' ], + [ 'dropTable', 'transcache' ], + [ 'runMaintenance', PopulateChangeTagDef::class, 'maintenance/populateChangeTagDef.php' ], + [ 'addIndex', 'change_tag', 'change_tag_i03', + 'patch-change_tag-change_tag_rc_tag_id.sql' ], + [ 'addField', 'ipblocks', 'ipb_sitewide', 'patch-ipb_sitewide.sql' ], + [ 'addTable', 'ipblocks_restrictions', 'patch-ipblocks_restrictions-table.sql' ], // KEEP THIS AT THE BOTTOM!! [ 'doRebuildDuplicateFunction' ], diff --git a/includes/installer/PostgresUpdater.php b/includes/installer/PostgresUpdater.php index 932c9412fa..8fd5370367 100644 --- a/includes/installer/PostgresUpdater.php +++ b/includes/installer/PostgresUpdater.php @@ -590,6 +590,13 @@ class PostgresUpdater extends DatabaseUpdater { [ 'addIndex', 'interwiki', 'interwiki_pkey', 'patch-interwiki-pk.sql' ], [ 'addIndex', 'protected_titles', 'protected_titles_pkey', 'patch-protected_titles-pk.sql' ], [ 'addIndex', 'site_identifiers', 'site_identifiers_pkey', 'patch-site_identifiers-pk.sql' ], + [ 'addPgIndex', 'recentchanges', 'rc_this_oldid', '(rc_this_oldid)' ], + [ 'dropTable', 'transcache' ], + [ 'runMaintenance', PopulateChangeTagDef::class, 'maintenance/populateChangeTagDef.php' ], + [ 'addIndex', 'change_tag', 'change_tag_rc_tag_id', + 'patch-change_tag-change_tag_rc_tag_id.sql' ], + [ 'addPgField', 'ipblocks', 'ipb_sitewide', 'SMALLINT NOT NULL DEFAULT 1' ], + [ 'addTable', 'ipblocks_restrictions', 'patch-ipblocks_restrictions-table.sql' ], ]; } diff --git a/includes/installer/SqliteUpdater.php b/includes/installer/SqliteUpdater.php index 80eb84330b..eb2d8c2040 100644 --- a/includes/installer/SqliteUpdater.php +++ b/includes/installer/SqliteUpdater.php @@ -54,9 +54,8 @@ class SqliteUpdater extends DatabaseUpdater { [ 'doLogUsertextPopulation' ], [ 'doLogSearchPopulation' ], [ 'addTable', 'l10n_cache', 'patch-l10n_cache.sql' ], - [ 'addIndex', 'change_tag', 'change_tag_rc_tag', 'patch-change_tag-indexes.sql' ], + [ 'addIndex', 'tag_summary', 'tag_summary_rc_id', 'patch-change_tag-indexes.sql' ], [ 'addField', 'redirect', 'rd_interwiki', 'patch-rd_interwiki.sql' ], - [ 'doUpdateTranscacheField' ], [ 'sqliteSetupSearchindex' ], // 1.17 @@ -184,7 +183,6 @@ class SqliteUpdater extends DatabaseUpdater { [ 'renameIndex', 'querycache_info', 'qci_type', 'PRIMARY', false, 'patch-querycache_info-fix-pk.sql' ], [ 'renameIndex', 'site_stats', 'ss_row_id', 'PRIMARY', false, 'patch-site_stats-fix-pk.sql' ], - [ 'renameIndex', 'transcache', 'tc_url_idx', 'PRIMARY', false, 'patch-transcache-fix-pk.sql' ], [ 'renameIndex', 'user_former_groups', 'ufg_user_group', 'PRIMARY', false, 'patch-user_former_groups-fix-pk.sql' ], [ 'renameIndex', 'user_properties', 'user_properties_user_property', 'PRIMARY', false, @@ -231,6 +229,13 @@ class SqliteUpdater extends DatabaseUpdater { 'patch-protected_titles-fix-pk.sql' ], [ 'renameIndex', 'site_identifiers', 'site_ids_type', 'PRIMARY', false, 'patch-site_identifiers-fix-pk.sql' ], + [ 'addIndex', 'recentchanges', 'rc_this_oldid', 'patch-recentchanges-rc_this_oldid-index.sql' ], + [ 'dropTable', 'transcache' ], + [ 'runMaintenance', PopulateChangeTagDef::class, 'maintenance/populateChangeTagDef.php' ], + [ 'addIndex', 'change_tag', 'change_tag_rc_tag_id', + 'patch-change_tag-change_tag_rc_tag_id.sql' ], + [ 'addField', 'ipblocks', 'ipb_sitewide', 'patch-ipb_sitewide.sql' ], + [ 'addTable', 'ipblocks_restrictions', 'patch-ipblocks_restrictions-table.sql' ], ]; } diff --git a/includes/installer/i18n/bg.json b/includes/installer/i18n/bg.json index 6b2f33f817..3805dfa71e 100644 --- a/includes/installer/i18n/bg.json +++ b/includes/installer/i18n/bg.json @@ -23,9 +23,9 @@ "config-session-expired": "Срокът на валидност на данните от сесията са изтекли.\nПродължителността на сесиите е настроена на $1.\nТова може да бъде увеличено чрез настройване на session.gc_maxlifetime в php.ini.\nНеобходимо е рестартиране на инсталационния процес.", "config-no-session": "Данните за сесията бяха загубени!\nПроверете вашия php.ini и се уверете, че на session.save_path е настроена подходящата директория.", "config-your-language": "Вашият език:", - "config-your-language-help": "Избиране на език за използване по време на инсталацията.", + "config-your-language-help": "Избор на език за използване по време на инсталацията.", "config-wiki-language": "Език на уикито:", - "config-wiki-language-help": "Избиране на език, на който ще е основното съдържание на уикито.", + "config-wiki-language-help": "Избор на език, на който ще е основното съдържание на уикито.", "config-back": "← Връщане", "config-continue": "Продължаване →", "config-page-language": "Език", @@ -50,20 +50,20 @@ "config-sidebar": "* [https://www.mediawiki.org Сайт на МедияУики]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents Наръчник на потребителя]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents Наръчник на администратора]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ ЧЗВ]\n----\n* Документация\n* Бележки за версията\n* Авторски права\n* Обновяване", "config-env-good": "Средата беше проверена.\nИнсталирането на МедияУики е възможно.", "config-env-bad": "Средата беше проверена.\nНе е възможна инсталация на МедияУики.", - "config-env-php": "Инсталирана е версия на PHP $1.", + "config-env-php": "PHP $1 е инсталирано.", "config-env-hhvm": "HHVM $1 е инсталиран.", "config-unicode-using-intl": "Използване на разширението [https://pecl.php.net/intl intl PECL] за нормализация на Уникод.", "config-unicode-pure-php-warning": "Внимание: [https://pecl.php.net/intl Разширението intl PECL] не е налично за справяне с нормализацията на Уникод, превключване към по-бавното изпълнение на чист PHP.\nАко сайтът е с голям трафик, препоръчително е да се запознаете с [https://www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations нормализацията на Уникод].", "config-unicode-update-warning": "Предупреждение: Инсталираната версия на Обвивката за нормализация на Unicode използва по-старата версия на библиотеката на [http://site.icu-project.org/ проекта ICU].\nНеобходимо е да [https://www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations инсталирате по-нова версия], в случай че сте загрижени за използването на Unicode.", "config-no-db": "Не може да бъде открит подходящ драйвер за база данни! Необходимо е да инсталирате драйвер за база данни за PHP.\n{{PLURAL:$2|Поддържа се следния тип|Поддържат се следните типове}} бази от данни: $1.\n\nАко сами сте компилирали PHP, преконфигурирайте го с включен клиент за база данни, например чрез използване на ./configure --with-mysqli.\nАко сте инсталирали PHP от пакет за Debian или Ubuntu, необходимо е също така да инсталирате и модула php-mysql.", "config-outdated-sqlite": "Внимание: имате инсталиран SQLite $1, а минималната допустима версия е $2. SQLite ще бъде недостъпна за ползване.", - "config-no-fts3": "'''Предупреждение''': SQLite е компилирана без [//sqlite.org/fts3.html модула FTS3], затова възможностите за търсене няма да са достъпни.", + "config-no-fts3": "Предупреждение: SQLite е компилирана без [//sqlite.org/fts3.html модула FTS3], затова възможностите за търсене няма да са достъпни.", "config-pcre-old": "Фатална грешка: Изисква се PCRE версия $1 или по-нова.\nИзпълнимият файл на PHP е свързан с PCRE версия $2.\n[https://www.mediawiki.org/wiki/Manual:Errors_and_symptoms/Повече информация за PCRE].", - "config-pcre-no-utf8": "'''Фатално''': Модулът PCRE на PHP изглежда е компилиран без поддръжка на PCRE_UTF8.\nЗа да функционира правилно, МедияУики изисква поддръжка на UTF-8.", + "config-pcre-no-utf8": "Фатално: Модулът PCRE на PHP изглежда е компилиран без поддръжка на PCRE_UTF8.\nЗа да функционира правилно, МедияУики изисква поддръжка на UTF-8.", "config-memory-raised": "memory_limit на PHP е $1, увеличаване до $2.", "config-memory-bad": "Внимание: memory_limit на PHP е $1.\nСтойността вероятно е твърде ниска.\nВъзможно е инсталацията да се провали!", "config-apc": "[https://secure.php.net/apc APC] е инсталиран", - "config-apcu": "[https://secure.php.net/apc APC] е инсталиран", + "config-apcu": "[https://secure.php.net/apcu APCu] е инсталиран", "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache] е инсталиран", "config-no-cache-apcu": "Внимание: [https://secure.php.net/apcu APCu] и [https://www.iis.net/downloads/microsoft/wincache-extension WinCache] не могат да бъдат открити.\nКеширането на обекти не е активирано.", "config-mod-security": "Предупреждение: [https://modsecurity.org/ mod_security]/mod_security2 е включено на вашия уеб сървър. Много от обичайните му конфигурации пораждат проблеми с МедияУики и друг софтуер, който позволява публикуване на произволно съдържание.\nАко е възможно, моля изключете го. В противен случай се обърнете към [https://modsecurity.org/documentation/ документацията на mod_security] или се свържете с поддръжката на хостинга си, ако се сблъскате със случайни грешки.", @@ -73,19 +73,19 @@ "config-imagemagick": "Открит е ImageMagick: $1.\nПреоразмеряването на картинки ще бъде включено ако качването на файлове бъде разрешено.", "config-gd": "Открита е вградена графичната библиотека GD.\nАко качването на файлове бъде включено, ще бъде включена възможността за преоразмеряване на картинки.", "config-no-scaling": "Не са открити библиотеките GD или ImageMagick.\nПреоразмеряването на картинки ще бъде изключено.", - "config-no-uri": "'''Грешка:''' Не може да се определи текущия адрес.\nИнсталация беше прекратена.", + "config-no-uri": "Грешка: Не може да се определи текущия адрес.\nИнсталация беше прекратена.", "config-no-cli-uri": "Внимание: Не е зададен параметър --scriptpath, стойност по подразбиране: $1.", - "config-using-server": "Използване на сървърното име \"$1\".", - "config-using-uri": "Използване на сървърния адрес (URL) \"$1$2\".", + "config-using-server": "Използване на сървърното име „$1“.", + "config-using-uri": "Използване на сървърния адрес (URL) „$1$2“.", "config-uploads-not-safe": "Внимание: Папката по подразбиране за качване $1 е уязвима от изпълнение на зловредни скриптове.\nВъпреки че МедияУики извършва проверка за заплахи в сигурността на всички качени файлове, силно препоръчително е да се [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security#Upload_security затвори тази уязвимост в сигурността] преди разрешаване за качване на файлове.", "config-no-cli-uploads-check": "Предупреждение: Директорията по подразбиране за качване на файлове ($1) не е проверена за уязвимости при изпълнение на произволен скрипт по време на инсталацията от командния ред.", "config-brokenlibxml": "Вашата система използва комбинация от версии на PHP и libxml2, които са с много грешки и могат да причинят скрити повреди на данните в МедияУики или други уеб приложения.\nНеобходимо е обновяване до libxml2 2.7.3 или по-нова версия ([https://bugs.php.net/bug.php?id=45996 докладвана грешка при PHP]).\nИнсталацията беше прекратена.", "config-suhosin-max-value-length": "Suhosin е инсталиран и ограничава дължината GET параметъра length на $1 байта. Компонентът на МедияУики ResourceLoader ще може да пренебрегне частично това ограничение, но това ще намали производителността. По възможност е препоръчително да се настрои suhosin.get.max_value_length на 1024 или по-голяма стойност в php.ini и в LocalSettings.php да се настрои $wgResourceLoaderMaxQueryLength със същата стойност.", "config-using-32bit": "Внимание: изглежда, че системата Ви работи с 32-битови числа. Това [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:32-bit не се препоръчва].", "config-db-type": "Тип на базата от данни:", - "config-db-host": "Хост на базата от данни:", + "config-db-host": "Сървър на базата от данни:", "config-db-host-help": "Ако базата от данни е на друг сървър, в кутията се въвежда името на хоста или IP адреса.\n\nАко се използва споделен уеб хостинг, доставчикът на услугата би трябвало да е предоставил в документацията си коректния хост.\n\nАко инсталацията протича на Windows-сървър и се използва MySQL, използването на \"localhost\" може да е неприемливо. В такива случаи се използва \"127.0.0.1\" за локален IP адрес.\n\nПри използване на PostgreSQL, това поле се оставя празно, за свързване чрез Unix socket.", - "config-db-host-oracle": "TNS на базата данни:", + "config-db-host-oracle": "TNS на базата от данни:", "config-db-host-oracle-help": "Въведете валидно [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm Local Connect Name]; файлът tnsnames.ora трябва да бъде видим за инсталацията.
Ако използвате клиентска библиотека версия 10g или по-нова можете да използвате метода [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm Easy Connect].", "config-db-wiki-settings": "Идентифициране на това уики", "config-db-name": "Име на базата от данни:", @@ -107,20 +107,20 @@ "config-db-port": "Порт на базата от данни:", "config-db-schema": "Схема за МедияУики", "config-db-schema-help": "Схемата по-горе обикновено е коректна.\nПромени се извършват ако наистина е необходимо.", - "config-pg-test-error": "Невъзможно свързване с базата данни '''$1''': $2", + "config-pg-test-error": "Невъзможно свързване с базата данни $1: $2", "config-sqlite-dir": "Директория за данни на SQLite:", "config-sqlite-dir-help": "SQLite съхранява всички данни в един файл.\n\nПо време на инсталацията уеб сървърът трябва да има права за писане в посочената директория.\n\nТя не трябва да е достъпна през уеб, затова не е там, където са PHP файловете.\n\nИнсталаторът ще съхрани заедно с нея файл .htaccess, но ако този метод пропадне, някой може да придобие достъп до суровите данни от базата от данни.\nТова включва сурови данни за потребителите (адреси за е-поща, хеширани пароли), както и изтрити версии на страници и друга чувствителна и с ограничен достъп информация от и за уикито.\n\nБазата от данни е препоръчително да се разположи на друго място, например в /var/lib/mediawiki/yourwiki.", "config-oracle-def-ts": "Таблично пространство по подразбиране:", "config-oracle-temp-ts": "Временно таблично пространство:", - "config-type-mysql": "MySQL (или съвместима)", + "config-type-mysql": "MariaDB, MySQL (или съвместима)", "config-type-mssql": "Microsoft SQL сървър", "config-support-info": "МедияУики поддържа следните системи за бази от данни:\n\n$1\n\nАко не виждате желаната за използване система в списъка по-долу, следвайте инструкциите за активиране на поддръжка по-горе.", - "config-dbsupport-mysql": "* [{{int:version-db-mysql-url}} MySQL] е най-важна за МедияУики и се поддържа най-добре. МедияУики работи също така с [{{int:version-db-mariadb-url}} MariaDB] и [{{int:version-db-percona-url}} Percona Server], които са съвместими с MySQL.\n([https://secure.php.net/manual/bg/mysqli.installation.php Как се компилира PHP с поддръжка на MySQL])", + "config-dbsupport-mysql": "* [{{int:version-db-mariadb-url}} MariaDB] е най-важна за МедияУики и се поддържа най-добре. МедияУики работи също така с [{{int:version-db-mysql-url}} MySQL] и [{{int:version-db-percona-url}} Percona Server], които са съвместими с MariaDB.\n([https://secure.php.net/manual/en/mysqli.installation.php Как се компилира PHP с поддръжка на MySQL])", "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] е популярна система за управление на бази от данни, алтернатива на MySQL. ([https://secure.php.net/manual/bg/pgsql.installation.php Как се компилира PHP с поддръжка на PostgreSQL])", - "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite] е олекотена система за бази от данни, която е много добре поддържана. ([http://www.php.net/manual/bg/pdo.installation.php Как се компилира PHP с поддръжка на SQLite], използва PDO)", - "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] е комерсиална корпоративна база от данни. ([http://www.php.net/manual/en/oci8.installation.php Как се компилира PHP с поддръжка на OCI8])", + "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite] е олекотена система за бази от данни, която е много добре поддържана. ([https://secure.php.net/manual/en/pdo.installation.php Как се компилира PHP с поддръжка на SQLite], използва PDO)", + "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] е комерсиална корпоративна база от данни. ([https://secure.php.net/manual/en/oci8.installation.php Как се компилира PHP с поддръжка на OCI8])", "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] е комерсиална корпоративна база от данни за Windows. ([https://secure.php.net/manual/en/sqlsrv.installation.php Как да се компилира PHP с поддръжка на SQLSRV])", - "config-header-mysql": "Настройки за MySQL", + "config-header-mysql": "Настройки на MariaDB/MySQL", "config-header-postgres": "Настройки за PostgreSQL", "config-header-sqlite": "Настройки за SQLite", "config-header-oracle": "Настройки за Oracle", @@ -150,16 +150,16 @@ "config-can-upgrade": "В базата от данни има таблици за МедияУики.\nЗа надграждането им за MediaWiki $1, натиска се Продължаване.", "config-upgrade-done": "Обновяването приключи.\n\nВече е възможно [$1 да използвате уикито].\n\nАко е необходимо, възможно е файлът LocalSettings.php да бъде създаден отново чрез натискане на бутона по-долу.\nТова не е препоръчително действие, освен ако не срещате затруднения с уикито.", "config-upgrade-done-no-regenerate": "Обновяването приключи.\n\nВече е възможно [$1 да използвате уикито].", - "config-regenerate": "Създаване на LocalSettings.php →", + "config-regenerate": "Повторно създаване на LocalSettings.php →", "config-show-table-status": "Заявката SHOW TABLE STATUS не сполучи!", - "config-unknown-collation": "'''Предупреждение:''' Базата от данни използва неразпозната колация.", + "config-unknown-collation": "Предупреждение: Базата от данни използва неразпозната колация.", "config-db-web-account": "Сметка за уеб достъп до базата от данни", "config-db-web-help": "Избиране на потребителско име и парола, които уеб сървърът ще използва да се свързва с базата от данни при обичайната работа на уикито.", "config-db-web-account-same": "Използване на същата сметка като при инсталацията.", "config-db-web-create": "Създаване на сметката, ако все още не съществува", "config-db-web-no-create-privs": "Посочената сметка за инсталацията не разполага с достатъчно права за създаване на нова сметка.\nНеобходимо е посочената сметка вече да съществува.", "config-mysql-engine": "Хранилище на данни:", - "config-mysql-innodb": "InnoDB", + "config-mysql-innodb": "InnoDB (препоръчително)", "config-mysql-myisam": "MyISAM", "config-mysql-myisam-dep": "Внимание: Избрана е MyISAM като система за складиране в MySQL, която не се препоръчва за използване с МедияУики, защото:\n* почти не поддържа паралелност заради заключване на таблиците\n* е по-податлива на повреди в сравнение с други системи\n* кодът на МедияУики не винаги поддържа MyISAM коректно\n\nАко инсталацията на MySQL поддържа InnoDB, силно е препоръчително да се използва тя.\nАко инсталацията на MySQL не поддържа InnoDB, вероятно е време за обновяване.", "config-mysql-only-myisam-dep": "Внимание: MyISAM e единственият наличен на тази машина тип на таблиците за MySQL и не е препоръчителен за употреба при МедияУики защото:\n* има слаба поддръжка на конкурентност на заявките, поради закючването на таблиците\n* е много по-податлив на грешки в базите от данни от другите типове таблици\n* кодът на МедияУики не винаги работи с MyISAM както трябва\n\nВашият MySQL не поддържа InnoDB, така че може би е дошло време за актуализиране.", @@ -186,7 +186,7 @@ "config-admin-password-confirm": "Парола (повторно):", "config-admin-help": "Въвежда се предпочитаното потребителско име, например „Иванчо Иванчев“.\nТова ще е потребителското име, което администраторът ще използва за влизане в уикито.", "config-admin-name-blank": "Необходимо е да бъде въведено потребителско име на администратора.", - "config-admin-name-invalid": "Посоченото потребителско име \"$1\" е невалидно.\nНеобходимо е да се посочи друго.", + "config-admin-name-invalid": "Посоченото потребителско име „$1“ е невалидно.\nНеобходимо е да се посочи друго.", "config-admin-password-blank": "Въведете парола за администраторската сметка.", "config-admin-password-mismatch": "Двете въведени пароли не съвпадат.", "config-admin-email": "Адрес за електронна поща:", @@ -220,7 +220,7 @@ "config-license-help": "Много публични уикита поставят всички приноси под [https://freedomdefined.org/Definition/Bg свободен лиценз].\nТова помага за създаването на усещане за общност и насърчава дългосрочните приноси. \nТова не е необходимо като цяло за частно или корпоративно уики.\n\nАко искате да използвате текстове от Уикипедия, и искате Уикипедия да може да приема текстове, копирани от вашето уики, трябва да изберете лиценз {{int:config-license-cc-by-sa}}.\n\nЛицензът за свободна документация на GNU е старият лиценз на съдържанието на Уикипедия.\nТой все още е валиден лиценз, но някои негови условия са трудни за разбиране и правят по-сложни повторното използване и интерпретацията.", "config-email-settings": "Настройки за е-поща", "config-enable-email": "Разрешаване на изходящи е-писма", - "config-enable-email-help": "За да работят възможностите за използване на е-поща, необходимо е [Config-dbsupport-oracle/manual/en/mail.configuration.php настройките за поща на PHP] да бъдат конфигурирани правилно.\nАко няма да се използват услугите за е-поща в уикито, те могат да бъдат изключени тук.", + "config-enable-email-help": "За да работят възможностите за използване на е-поща, необходимо е [https://secure.php.net/manual/en/mail.configuration.ph настройките за поща на PHP] да бъдат конфигурирани правилно.\nАко няма да се използват услугите за е-поща в уикито, те могат да бъдат изключени тук.", "config-email-user": "Позволяване на потребителите да си изпращат е-писма през уикито", "config-email-user-help": "Позволяване на потребителите да си изпращат е-писма ако са разрешили това в настройките си.", "config-email-usertalk": "Оповестяване при промяна на потребителската беседа", @@ -309,6 +309,7 @@ "config-nofile": "Файлът „$1“ не може да бъде открит. Да не е бил изтрит?", "config-extension-link": "Знаете ли, че това уики поддържа [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Extensions разширения]?\n\nМожете да разгледате [https://www.mediawiki.org/wiki/Special:MyLanguage/Category:Extensions_by_category разширенията по категория] или [https://www.mediawiki.org/wiki/Extension_Matrix Матрицата на разширенията] за пълен списък на разширенията.", "config-skins-screenshots": "$1 (снимки на екрана: $2)", + "config-extensions-requires": "$1 (изисква $2)", "config-screenshot": "снимка на екран", "mainpagetext": "МедияУики беше успешно инсталирано.", "mainpagedocfooter": "Разгледайте [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents ръководството] за подробна информация относно използването на уики софтуера.\n\n== Първи стъпки ==\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Настройки за конфигуриране]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ ЧЗВ за МедияУики]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Пощенски списък относно нови версии на МедияУики]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Локализиране на МедияУики]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam Научете как да се справяте със спама във вашето уики]" diff --git a/includes/installer/i18n/gl.json b/includes/installer/i18n/gl.json index 67c15434fe..c5d34f35b1 100644 --- a/includes/installer/i18n/gl.json +++ b/includes/installer/i18n/gl.json @@ -202,7 +202,7 @@ "config-subscribe-help": "Esta é unha lista de correos de baixo volume usada para anuncios sobre lanzamentos de novas versións, incluíndo avisos de seguridade importantes.\nDebería subscribirse a ela e actualizar a súa instalación MediaWiki cando saian as novas versións.", "config-subscribe-noemail": "Intentou subscribirse á lista de correo dos anuncios de novos lanzamentos sen proporcionar o enderezo de correo electrónico.\nDea un enderezo de correo electrónico se quere efectuar a subscrición á lista de correo.", "config-pingback": "Compartir datos de esta instalación cos desenvolvedores de MediaWiki", - "config-pingback-help": "Se seleccionas esta opción, MediaWiki enviará periodicamente unha mensaxe a https://www.mediawiki.org con datos básicos sobre esta instancia Mediawiki. Estos datos inclúen, por exemplo, o tipo de sistema, versión de PHP e a base de datos escollida. A Fundación Wikimedia comparte estos datos cos desenvolvedores de MediaWiki para axudar a guiar o traballo futuro de desenvolvemento. Serán enviados os seguintes datos do seu sistemaː\n
$1
", + "config-pingback-help": "Se selecciona esta opción, MediaWiki enviará periodicamente unha mensaxe a https://www.mediawiki.org con datos básicos sobre esta instancia Mediawiki. Estes datos inclúen, por exemplo, o tipo de sistema, versión de PHP e a base de datos escollida. A Fundación Wikimedia comparte estes datos cos desenvolvedores de MediaWiki para axudar a guiar o traballo futuro de desenvolvemento. Serán enviados os seguintes datos do seu sistemaː\n
$1
", "config-almost-done": "Xa case rematou!\nNeste paso pode saltar o resto da configuración e instalar o wiki agora mesmo.", "config-optional-continue": "Facédeme máis preguntas.", "config-optional-skip": "Xa estou canso. Instalade o wiki.", diff --git a/includes/installer/i18n/nap.json b/includes/installer/i18n/nap.json index e391609344..8183ed7c34 100644 --- a/includes/installer/i18n/nap.json +++ b/includes/installer/i18n/nap.json @@ -61,6 +61,7 @@ "config-memory-raised": "'O valore 'e PHP memory_limit è $1, aumentato a $2.", "config-memory-bad": "Attenziò: 'o valore 'e PHP memory_limit è $1.\nProbabbilmente troppo basso.\n'A installazione se putesse scassà!", "config-apc": "[https://secure.php.net/apc APC] è installato", + "config-apcu": "[https://secure.php.net/apcu APCu] è installato", "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache] è installato", "config-no-cache-apcu": "Attenziò: [https://secure.php.net/apcu APCu], [http://xcache.lighttpd.net/ XCache] o [https://www.iis.net/downloads/microsoft/wincache-extension WinCache] nun so' state truvate.\n'A funziona caching 'e ll'oggette non è apicciata.", "config-mod-security": "Attenziò: 'O servitore web vuosto téne [https://modsecurity.org/ mod_security]/mod_security2 appicciato. Ce stanno tante mpustaziune commune ca 'o facessero causà prubbleme a MediaWiki e ll'ati software ca permettessero ll'utente 'e pubbrecà cuntenute.\nSi putite, stutate sta funziona. Sinò, riferite 'a [https://modsecurity.org/documentation/ documentaziona ncopp' 'o mod_security] o cuntattate 'o host vuosto pe' ve dà supporto quanno se scummogliasse cocch'errore.", @@ -78,6 +79,7 @@ "config-no-cli-uploads-check": "Attenziò: 'a cartella predefinita p' 'e carreche ($1) nun è stata cuntrullata p' 'a vulnerabbelità ncopp'a l'esecuzione arbitraria 'e script pe' tramente ca se fà l'installazione 'a linea 'e commando.", "config-brokenlibxml": "'O sistema vuosto ave na combinazione 'e verziune 'e PHP e libxml2 nguacchiata ca putesse scassà 'e date 'e MediaWiki 'n manera annascunnusa e pure l'ati apprecaziune p' 'o web.\nAgghiurnate a libxml2 2.7.3 o cchiù muderno ([https://bugs.php.net/bug.php?id=45996 'o bug studiato d' 'o lato PHP]).\nInstallaziona spezzata.", "config-suhosin-max-value-length": "Suhosin è installato e miette lemmeto 'o parametro GET length a $1 byte.\n'O componente MediaWiki ResourceLoader funzionarrà aggirann'a stu lemmeto, ma luvanno prestaziune.\nSi pussibile, avit'a mpustà suhosin.get.max_value_length a 1024 o cchiù auto 'n php.ini, e mpustà $wgResourceLoaderMaxQueryLength a 'o stesso valore 'n LocalSettings.php.", + "config-using-32bit": "Attenziò: 'o sistema vuosto pare c'ausasse nummere sane 'e 32-bit. Chest' [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:32-bit è scunzigliat].", "config-db-type": "Tipo 'e database:", "config-db-host": "Host d' 'o database:", "config-db-host-help": "Si 'o server database vuosto stà mpizzato dint' 'a nu server differente, miette 'o nomme d' 'o host o l'indirizzo IP sujo.\n\nSi state ausanno nu servizio spartuto web hosting, 'aggenzia 'e hosting v'avess'a dà 'o nomme buono 'e nomme host dint' 'a documentaziona suoja.\n\nSi state installanno chisto dint'a nu server Windows cu MySQL, ausanno \"localhost\" può darse ca nun funziona p' 'o nomme server. Si chisto nun funziona, mettite \"127.0.0.1\" p' 'o ndirizzo locale vuosto.\n\nSi state ausanno PostgreSQL, lassate abbacante stu campo pe' v'accucchià cu nu socket Unix.", @@ -111,12 +113,12 @@ "config-type-mysql": "MariaDB, MySQL o compatibbele", "config-type-mssql": "Microsoft SQL Server", "config-support-info": "MediaWiki supporta 'e sisteme 'e database ccà abbascio:\n\n$1\n\nSi nfra chiste ccà nun vedite 'o sistema 'e database ca vulite ausà, allora avite liegge 'e instruziune ccà ncoppa pe' ne dà supporto.", - "config-dbsupport-mysql": "* [{{int:version-db-mysql-url}} MySQL] è 'a configurazione cchiù mmeglio p' 'o MediaWiki e è chilla meglio suppurtata. MediaWiki può faticà pure cu' [{{int:version-db-mariadb-url}} MariaDB] e [{{int:version-db-percona-url}} Percona Server], ca fossero MySQL cumpatibbele. ([https://secure.php.net/manual/en/mysqli.installation.php Comme s'adda fà pe' cumpilà PHP cu suppuorto MySQL])", - "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] è nu sistema canusciuto 'e database open source ca fosse n'alternativa a MySQL. Putess'avé cocch'errore p'arricettà, e nun è cunzigliato 'e ll'ausà dint'a n'ambiente 'e produziona. ([https://secure.php.net/manual/en/pgsql.installation.php Comme s'avess'a cumpilà PHP cu suppuorto PostgreSQL])", - "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite] è nu sistema 'e database leggero, ca fosse assaje buono suppurtato. ([http://www.php.net/manual/en/pdo.installation.php Comme cumpilà PHP cu suppuorto SQLite], aùsa PDO)", - "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] è nu database 'e na fraveca commerciale. ([http://www.php.net/manual/en/oci8.installation.php Comme cumpilà PHP cu suppuorto OCI8])", + "config-dbsupport-mysql": "* [{{int:version-db-mariadb-url}} MariaDB] è 'a configurazione cchiù mmeglio p' 'o MediaWiki e è chilla meglio suppurtata. MediaWiki può faticà pure cu' [{{int:version-db-mariadb-url}} MariaDB] e [{{int:version-db-percona-url}} Percona Server], ca fossero MariaDB cumpatibbele. ([https://secure.php.net/manual/en/mysqli.installation.php Comme s'adda fà pe' cumpilà PHP cu suppuorto MySQL])", + "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] è nu sistema canusciuto 'e database open source ca fosse n'alternativa a MySQL.\n([https://secure.php.net/manual/en/pgsql.installation.php Comme s'avess'a cumpilà PHP cu suppuorto PostgreSQL])", + "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite] è nu sistema 'e database leggero, ca fosse assaje buono suppurtato.\n([https://secure.php.net/manual/en/pdo.installation.php Comme cumpilà PHP cu suppuorto SQLite], aùsa PDO)", + "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] è nu database 'e na fraveca commerciale. ([https://secure.php.net/manual/en/oci8.installation.php Comme cumpilà PHP cu suppuorto OCI8])", "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] è nu database 'e na fraveca commerciale p' 'o Windows. ([https://secure.php.net/manual/en/sqlsrv.installation.php Comme cumpilà PHP cu suppuorto SQLSRV])", - "config-header-mysql": "Mpustaziune MySQL", + "config-header-mysql": "Mpustaziune MariaDB/MySQL", "config-header-postgres": "Mpustaziune PostgreSQL", "config-header-sqlite": "Mpustaziune SQLite", "config-header-oracle": "Mpustaziune Oracle", @@ -155,7 +157,7 @@ "config-db-web-create": "Crìa 'o cunto si nun esiste ancora", "config-db-web-no-create-privs": "'O cunto ausato pe' ne fà l'installazione nun tene diritte necessarie pe' ne putè crià n'atu cunto.\n'O cunto zegnàto ccà adda esistere già.", "config-mysql-engine": "Mutore d'astipo:", - "config-mysql-innodb": "InnoDB", + "config-mysql-innodb": "InnoDB (fosse 'o cunzigliato)", "config-mysql-myisam": "MyISAM", "config-mysql-myisam-dep": "Attenziò: avite scigliuto MyISAM comm' 'o mutore 'archiviaziona MySQL, ca nun è raccummannato pe' l'ausà cu MediaWiki, pecché:\n* supporta debolmente 'a concorrenza p' 'o blocco d' 'a tabbella\n* è cchiù inchine 'a corruzione 'e l'ati mutore\n* 'o codece 'e base 'e MediaWiki nun gestisce sempe MyISAM comme l'avess'a gistiunà\n\nSi ll'installazione vosta MySQL suppuorta InnoDB, è autamente raccummandato ca si scigliesse a 'o posto suo.\nSi 'a installazione MySQL nun suppurtasse InnoDB, forse è 'o mumento 'e ll'agghiurnà.", "config-mysql-only-myisam-dep": "Attenziò: MyISAM è l'uneco mutore p'astipà date ca se trova mo' a disposizione p' 'o MySQL dint'a sta macchina, e nun fosse raccumandato 'e s'ausà cu MediaWiki, pecché:\n* suppurtasse minimamente concorrenza pe' bbìa 'e bluccà tabbelle\n* è cchiù facile ca jesse a se scassà cchiù 'e l'ati mutore\n* 'o codece MediaWiki nun maniasse sempe MyISAM comme l'avesse 'e manià\n\nL'installazione MySQL nun suppurtasse InnoDB, può darse ca chist'è 'o mumento pe' ve ll'agghiurnà.", @@ -193,6 +195,8 @@ "config-subscribe": "Mettiteve dint' 'a [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce mailing list 'e ll'annunciazione 'e verziune d' 'o software rilassate].", "config-subscribe-help": "Chest'è na mailing list a basso traffeco ca se dedicasse a se ffà annunzie 'e verziune nove, piglianno pure mpurtante nutarelle ca riguardassero 'a sicurezza.\nFosse cunzigliato 'e se nzegnà e s'agghiurnà l'installazione 'e MediaWiki quanno na verziona nova fosse pubbreca.", "config-subscribe-noemail": "Vuje avite tentato 'e ve trasì dint' 'a mailing list addedecata a se fà annunzie ncopp' 'e verziune nove senza ve dà n'indirizzo email.\nNzertanno n'indirizzo email se vulite affettuà l'iscrizione dint'a mailing list.", + "config-pingback": "Sparte 'e date 'e st'installazione ch' 'e sviluppature 'e MediaWiki.", + "config-pingback-help": "Si vuje sciglite st'opzione, MediaWiki cuntattasse spisso https://www.mediawiki.org ch' 'e date 'e base e st'istanza MediaWiki. Dint' 'a sta categoria 'e date, tràseno, p'esempie, 'o tipo 'e sistema, 'a virziona 'e PHP e database 'e backend scigliuto. 'A Wikimedia Foundation spartisse sti date ch' 'e sviluppature Mediawiki p' 'a puté aiutà a guidà 'e fatiche 'e sviluppo future. P' 'o sistema d' 'o vuosto 'e date se mannan'accussì:\n
$1
", "config-almost-done": "Avite quase fernuto!\nMo' putite zumpà 'a parta r' 'a configurazione e sempricemente installà 'a wiki.", "config-optional-continue": "Spiate cchiù dimanne.", "config-optional-skip": "Me so' scucciato già, installa surtanto 'o wiki.", @@ -241,7 +245,7 @@ "config-cache-options": "Mpustaziune p' 'a cache d'oggette:", "config-cache-help": "'O caching 'uggette s'ausa pe' puté migliurà 'a velocità 'e MediaWiki a fforza 'e ffà caching d' 'e date cchiù spisso ausàte.\nE Mezze a gruosse site se songo ncuraggiate a ll'appiccià chiste, e site piccerilli vedarranno migliuramente pure.", "config-cache-none": "Nisciuna memorizzazione n cache (nisciuna funziunalità è luvata, ma 'a velocità se putesse ffà a meno dint' 'e wiki cchiù gruosse)", - "config-cache-accel": "Mettere 'n cache oggette PHP (APC, XCache o WinCache)", + "config-cache-accel": "Mettere 'n cache oggette PHP (APC, APCu o WinCache)", "config-cache-memcached": "Aúsa 'o Memcached (richiede cchiù mpustaziune 'installazione e configuraziona)", "config-memcached-servers": "Server memcached:", "config-memcached-help": "Elenco 'e ll'indirizzi IP p' 'e putè ausà p' 'o Memcached.\nS'avess'a specificà uno pe' riga e scrivere 'a porta 'e trasuta. P'esempio:\n 127.0.0.1:11211\n 192.168.1.25:1234", @@ -291,14 +295,20 @@ "config-install-subscribe-fail": "Nun se pò sottoscrivere mediawiki-announce: $1", "config-install-subscribe-notpossible": "cURL nun è installato e allow_url_fopen nun è disponibbele.", "config-install-mainpage": "Crianno 'a paggena prencepale ch' 'e cuntenute predefinite", + "config-install-mainpage-exists": "'A paggena principale esiste già, è zumpata", "config-install-extension-tables": "Crianno tabelle pe' estenziune appicciate", "config-install-mainpage-failed": "Nun se pò nzertà 'a paggena prencepale: $1", - "config-install-done": "Cumplimente!\nAvite installato MediaWiki apposto.\n\n'O prugramma 'installazione ha generato nu file LocalSettings.php ca cuntene tuttuquante 'e mpustaziune.\n\nAvit'a scarrecà chisto e 'o nzertà dint' 'a cartella bbase d' 'o wiki vuosto ('a stessa addò fosse prisente l' index.php). 'A scarreca avess'a partì automaticamente.\n\nSi nu download nun s'avviasse, o si è stato annullato, putite riavvià cliccanno ncopp' 'o cullegamento 'e seguito:\n\n$3\n\nNota: si ascite mò 'a ll'installazione senza manco scarrecà 'o file 'e configurazione che s'è criato, po chesto nun sarrà cchiù dispunibbele.\n\nQuanno fosse tutto fernuto allora [$2 trasite dint' 'o wiki vuosto].", + "config-install-done": "Cumplimente!\nAvite installato MediaWiki.\n\n'O prugramma 'installazione ha generato nu file LocalSettings.php ca cuntene tuttuquante 'e mpustaziune.\n\nAvit'a scarrecà chisto e 'o nzertà dint' 'a cartella bbase d' 'o wiki vuosto ('a stessa addò fosse prisente l' index.php). 'A scarreca avess'a partì automaticamente.\n\nSi nu download nun s'avviasse, o si è stato annullato, putite riavvià cliccanno ncopp' 'o cullegamento 'e seguito:\n\n$3\n\nNota: si ascite mò 'a ll'installazione senza manco scarrecà 'o file 'e configurazione che s'è criato, po chesto nun sarrà cchiù dispunibbele.\n\nQuanno fosse tutto fernuto allora [$2 trasite dint' 'o wiki vuosto].", + "config-install-done-path": "Cumplimente!\nAvite installato MediaWiki.\n\n'O prugramma 'installazione ha generato nu file LocalSettings.php ca cuntene tuttuquante 'e mpustaziune.\n\nAvit'a scarrecà chisto, e l'avit'azzeccà dint' 'o $4. 'A scarreca avess'a partì automaticamente.\n\nSi nu download nun s'avviasse, o si è stato annullato, putite riavvià cliccanno ncopp' 'o cullegamento 'e seguito:\n\n$3\n\nNota: si ascite mò 'a ll'installazione senza manco scarrecà 'o file 'e configurazione che s'è criato, po chesto nun sarrà cchiù dispunibbele.\n\nQuanno fosse tutto fernuto allora [$2 trasite dint' 'o wiki vuosto].", + "config-install-success": "Avite installato MediaWiki apposto. Mo' putite vedé ncopp'a <$1$2> 'o wiki vuosto.\nSi avite cocche dubbio, vedite 'e trasí dint' 'a l'elenco 'e quistione FAQ:\n o vedite 'e cuntattà cocche forum 'e suppuorto dint' 'a chilla paggena.", "config-download-localsettings": "Scarreca LocalSettings.php", "config-help": "ajùto", "config-help-tooltip": "cliccà pe' 'o spannere", "config-nofile": "'O file \"$1\" nun se trova. Forse è stato scancellato?", "config-extension-link": "'O sapevate ch' 'o wiki vuosto suppurtasse 'e [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Extensions estensiune]?\n\nPutite navigà nfra chiste [https://www.mediawiki.org/wiki/Special:MyLanguage/Category:Extensions_by_category estensiune pe' categurìa].", - "mainpagetext": "MediaWiki è stato nstallato.", - "mainpagedocfooter": "Iate a cunzultà [https://meta.wikimedia.org/wiki/Help:Contents User's Guide] pe' n'avé nfurmaziune ncopp' 'o modo aùso d' 'o software wiki.\n\n== P'accummincià ==\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Elenco 'e mpustaziune pe' sta configuraziona]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ FAQ 'e Mediawiki]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Elenco 'e nutizie 'e Mediawiki]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Localizzazzione 'e MediaWiki p' 'a lengua vuosta]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam Mparate a cumbattere 'o spammo dint' 'a wiki d' 'a vosta]" + "config-skins-screenshots": "$1 (screenshots: $2)", + "config-extensions-requires": "$1 (vuless' 'o $2)", + "config-screenshot": "screenshot", + "mainpagetext": "MediaWiki è stato installato.", + "mainpagedocfooter": "Iate a cunzultà [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents User's Guide] pe' n'avé nfurmaziune ncopp' 'o modo aùso d' 'o software wiki.\n\n== P'accummincià ==\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Elenco 'e mpustaziune pe' sta configuraziona]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ FAQ 'e Mediawiki]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Elenco 'e nutizie 'e Mediawiki]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Localizzazzione 'e MediaWiki p' 'a lengua vuosta]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam Mparate a cumbattere 'o spammo dint' 'a wiki d' 'a vosta]" } diff --git a/includes/installer/i18n/pl.json b/includes/installer/i18n/pl.json index 1d4d515a56..ae4ce21127 100644 --- a/includes/installer/i18n/pl.json +++ b/includes/installer/i18n/pl.json @@ -246,9 +246,9 @@ "config-email-watchlist": "Włącz powiadomienie o zmianach stron obserwowanych", "config-email-watchlist-help": "Pozwól użytkownikom otrzymywać powiadomienia o zmianach na stronach obserwowanych, jeśli będą mieć włączoną tę funkcję w swoich preferencjach.", "config-email-auth": "Włącz uwierzytelnianie e‐mailem", - "config-email-auth-help": "Jeśli ta opcja jest włączona, użytkownicy będą musieli potwierdzić swoje adresy e-mail przy użyciu wysłanego do nich łącza, gdy będą je ustawiać lub zmieniać.\nTylko uwierzytelnione adresy e-mail mogą otrzymywać wiadomości od innych użytkowników lub mailowe powiadomienia o zmianach.\nUstawienie tej opcji jest'''zalecane''' na publicznych wiki ze względu na potencjalne nadużycia funkcji poczty e-mail.", + "config-email-auth-help": "Jeśli ta opcja jest włączona, użytkownicy będą musieli potwierdzić swoje adresy e-mail przy użyciu wysłanego do nich łącza, gdy będą je ustawiać lub zmieniać.\nTylko uwierzytelnione adresy e-mail mogą otrzymywać wiadomości od innych użytkowników lub mailowe powiadomienia o zmianach.\nUstawienie tej opcji jest zalecane na publicznych wiki ze względu na potencjalne nadużycia funkcji poczty e-mail.", "config-email-sender": "Zwrotny adres e‐mail", - "config-email-sender-help": "Wprowadź adres e-mail używany jako adres zwrotny wiadomości wychodzących.\nTo tam będą wysyłane szturchnięcia.\nWiele serwerów poczty wymaga, by co najmniej część nazwy domeny była prawidłowa.", + "config-email-sender-help": "Wprowadź adres e-mail używany jako adres zwrotny wiadomości wychodzących.\nTo tam będą wysyłane zwroty z serwerów pocztowych.\nWiele serwerów poczty wymaga, by co najmniej część nazwy domeny była prawidłowa.", "config-upload-settings": "Przesyłanie obrazków i plików", "config-upload-enable": "Włącz przesyłanie plików na serwer", "config-upload-help": "Przesyłanie plików potencjalnie wystawia serwer na zagrożenia.\nWięcej informacji na ten temat można znaleźć w [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security sekcji zabezpieczeń] podręcznika.\n\nAby włączyć przesyłanie plików, zmień właściwości podkatalogu images katalogu głównego MediaWiki tak, aby serwer sieci web mógł zapisywać do niego.\nNastępnie włącz tę opcję.", diff --git a/includes/installer/i18n/roa-tara.json b/includes/installer/i18n/roa-tara.json index fcdaf00a55..0a59f1008b 100644 --- a/includes/installer/i18n/roa-tara.json +++ b/includes/installer/i18n/roa-tara.json @@ -40,18 +40,18 @@ "config-db-password": "Password d'u database:", "config-db-port": "Porte d'u database:", "config-db-schema": "Scheme pe MediaUicchi:", - "config-type-mysql": "MySQL (o combatibbile)", + "config-type-mysql": "MariaDFB, MySQL, o combatibbile", "config-type-postgres": "PostgreSQL", "config-type-sqlite": "SQLite", "config-type-oracle": "Oracle", "config-type-mssql": "Microsoft SQL Server", - "config-header-mysql": "'Mbostaziune de MySQL", + "config-header-mysql": "'Mbostaziune de MariaDB/MySQL", "config-header-postgres": "'Mbostaziune de PostgreSQL", "config-header-sqlite": "'Mbostaziune de SQLite", "config-header-oracle": "'Mbostaziune de Oracle", "config-header-mssql": "'Mbostaziune de Microsoft SQL Server", "config-invalid-db-type": "Tipe de database invalide.", - "config-mysql-innodb": "InnoDB", + "config-mysql-innodb": "InnoDB (conzigliate)", "config-mysql-myisam": "MyISAM", "config-ns-generic": "Proggette", "config-admin-email": "Indirizze e-mail:", diff --git a/includes/installer/i18n/sr-ec.json b/includes/installer/i18n/sr-ec.json index 3bdc7617f8..e4da990818 100644 --- a/includes/installer/i18n/sr-ec.json +++ b/includes/installer/i18n/sr-ec.json @@ -9,15 +9,19 @@ "Zoranzoki21", "Acamicamacaraca", "Obsuser", - "BadDog" + "BadDog", + "Prevodim" ] }, "config-desc": "Инсталација за Медијавики", "config-title": "Инсталација Медијавикија $1", "config-information": "Информација", "config-localsettings-upgrade": "Откривена је датотека LocalSettings.php.\nДа бисте надоградили инсталацију, унесите вредности од $wgUpgradeKey у оквиру испод.\nНаћи ћете га у LocalSettings.php.", + "config-localsettings-cli-upgrade": "Датотека LocalSettings.php се налази на Вашем систему.\nУколико желите да ажурирате ову инсталацију, молимо покрените update.php", "config-localsettings-key": "Кључ за уградњу:", "config-localsettings-badkey": "Наведени кључ за надоградњу је неисправан.", + "config-upgrade-key-missing": "Постојећа инсталација Медијавикија је пронађена.\nКако бисте ажурирали ову инсталацију, молимо ставите следећу линију кôда на крај ваше LocalSettings.php датотеке.\n\n$1", + "config-localsettings-incomplete": "Постојећи LocalSettings.php изгледа некомплетно.\nПроменљива $1 није постављена.\nПромените LocalSettings.php тако што ћете поставити променљиву, па кликните на „{{int:Config-continue}}”.", "config-session-error": "Грешка при започињању сесије: $1", "config-session-expired": "Ваши подаци о сесији су истекли.\nСесије су подешене да трају $1.\nЊихов рок можете повећати постављањем session.gc_maxlifetime у php.ini.\nПоново покрените инсталацију.", "config-no-session": "Ваши подаци о сесији су изгубљени!\nПроверите Ваш php.ini и обезбедите да је session.save_path постављен на одговарајући директоријум.", @@ -50,6 +54,9 @@ "config-env-hhvm": "HHVM $1 је инсталиран.", "config-apc": "[https://secure.php.net/apc APC] је инсталиран", "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache] је инсталиран", + "config-diff3-bad": "GNU diff3 није пронађен.", + "config-git": "Пронађен је Git софтвер за контролу верзија: $1", + "config-git-bad": "Није пронађен Git софтвер за контролу верзија.", "config-db-type": "Тип базе података:", "config-db-host": "Хост базе података", "config-db-wiki-settings": "Идентификуј овај вики", @@ -57,6 +64,7 @@ "config-db-name-oracle": "Шема базе података:", "config-db-username": "Корисничко име базе података:", "config-db-password": "Лозинка базе података:", + "config-db-prefix": "Префикс табеле у бази података:", "config-db-port": "Порт базе података:", "config-db-schema": "Шема за Медијавики:", "config-type-mysql": "MariaDB, MySQL, или компактибилан", @@ -65,9 +73,14 @@ "config-type-oracle": "Oracle", "config-type-mssql": "Microsoft SQL Server", "config-header-mysql": "MariaDB/MySQL подешавања", + "config-header-postgres": "Подешавања PostgreSQL-а", + "config-header-sqlite": "Подешавања SQLite-а", + "config-header-oracle": "Подешавања Oracle-а", "config-header-mssql": "Подешавања Microsoft SQL Server-а", "config-invalid-db-type": "Неважећи тип базе података.", "config-mssql-old": "Потребан је Microsoft SQL Server $1 или новији. Ви имате $2.", + "config-sqlite-readonly": "Датотека $1 није записива.", + "config-regenerate": "Регенериши LocalSettings.php →", "config-mysql-innodb": "InnoDB (препоручено)", "config-mysql-myisam": "MyISAM", "config-mssql-auth": "Тип потврде идентитета:", @@ -90,6 +103,7 @@ "config-admin-password-mismatch": "Лозинке које сте унели се не поклапају.", "config-admin-email": "Имејл адреса:", "config-admin-error-bademail": "Унели сте неисправну имејл адресу.", + "config-optional-continue": "Постави ми још питања.", "config-optional-skip": "Досадно ми је, само инсталирај вики.", "config-profile-wiki": "Отворен вики", "config-profile-no-anon": "Неопходно је отворити налог", @@ -121,7 +135,11 @@ "config-install-step-done": "готово", "config-install-step-failed": "није успело", "config-install-extensions": "Обухвата екстензије", + "config-install-database": "Подешавам базу података", "config-install-schema": "Прављење шеме", + "config-install-pg-schema-not-exist": "Шема PostgreSQL не постоји.", + "config-install-user": "Правим корисника базе података", + "config-install-user-alreadyexists": "Корисник „$1” већ постоји", "config-install-tables": "Прављење табела", "config-install-stats": "Покрећем статистику", "config-install-keys": "Генеришем тајне кључеве", @@ -136,6 +154,7 @@ "config-nofile": "Не могу да пронађем датотеку „$1”. Није ли она била избрисана?", "config-skins-screenshots": "„$1” (снимци екрана: $2)", "config-skins-screenshot": "$1 ($2)", + "config-extensions-requires": "$1 (захтева $2)", "config-screenshot": "снимак екрана", "mainpagetext": "Медијавики је инсталиран.", "mainpagedocfooter": "Погледајте [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents кориснички водич] за коришћење програма.\n\n== Увод ==\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Помоћ у вези са подешавањима]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Често постављана питања]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Дописни списак о издањима Медијавикија]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam Научите како да се борите против спама на својој вики]" diff --git a/includes/installer/i18n/tr.json b/includes/installer/i18n/tr.json index 93d44e4c60..ada4d365c0 100644 --- a/includes/installer/i18n/tr.json +++ b/includes/installer/i18n/tr.json @@ -19,7 +19,8 @@ "Elftrkn", "Vito Genovese", "Incelemeelemani", - "Hedda" + "Hedda", + "By erdo can" ] }, "config-desc": "MediaWiki yükleyicisi", @@ -92,6 +93,7 @@ "config-using-uri": "Sunucu URLsi olarak \"$1$2\" kullanılıyor.", "config-uploads-not-safe": "Uyarı: Yüklemeler için varsayılan dizininiz $1, rastgele komut dosyalarının yürütülmesine karşı savunmasızdır.\nMediaWiki, karşıya yüklenen tüm dosyaları güvenlik tehditlerine karşı denetlese de, yüklemeleri etkinleştirmeden önce [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security#Upload_security bu güvenlik açığını kapatmanız] önemle tavsiye edilir.", "config-no-cli-uploads-check": "Uyarı: Yüklemeler için varsayılan dizininiz ($1), CLI yüklemesi sırasında rastgele kod yürütme güvenlik açığı açısından denetlenmez.", + "config-brokenlibxml": "Sisteminizde, \"buggy\" olan ve MediaWiki ve diğer web uygulamalarında gizli veri bozulmasına neden olabilecek PHP ve libxml2 sürümlerinin bir kombinasyonu vardır.\nLibxml2 2.7.3 veya sonraki bir sürüme yükseltin ([https://bugs.php.net/bug.php?id=45996 PHP ile dosyalanmış hata]).\nKurulum iptal edildi.", "config-db-type": "Veritabanı tipi:", "config-db-host": "Veritabanı sunucusu:", "config-db-host-help": "Veritabanı sunucunuz farklı bir sunucu üzerinde ise, ana bilgisayar adını veya IP adresini buraya girin.\n\nPaylaşılan ağ barındırma hizmeti kullanıyorsanız, barındırma sağlayıcınız size doğru bir ana bilgisayar adını kendi belgelerinde vermiştir.\n\nEğer MySQL kullanan bir Windows sunucusuna yükleme yapıyorsanız, sunucu adı olarak \"localhost\" kullanırsanız çalışmayabilir. Çalışmazsa, yerel IP adresi için \"127.0.0.1\" deneyin.\n\nPostgreSQL kullanıyorsanız, bu alanı bir Unix soketi ile bağlanmak için boş bırakın.", @@ -251,6 +253,6 @@ "config-extension-link": "Vikinizin [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Extensions eklentileri] desteklediğini biliyor musunuz?\n\n[https://www.mediawiki.org/wiki/Special:MyLanguage/Category:Extensions_by_category Eklentileri kategorilerine göre] inceleyebilir ya da tüm eklentilerin listesini görmek için [https://www.mediawiki.org/wiki/Extension_Matrix Eklenti Matrisine] bakabilirsiniz.", "config-skins-screenshots": "$1 (ekran görüntüleri: $2)", "config-screenshot": "ekran görüntüsü", - "mainpagetext": "'''MediaWiki başarı ile kuruldu.'''", - "mainpagedocfooter": "Viki yazılımının kullanımı hakkında bilgi almak için [https://meta.wikimedia.org/wiki/Help:Contents kullanıcı rehberine] bakınız.\n\n== Yeni Başlayanlar ==\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Yapılandırma ayarlarının listesi]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki SSS]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki e-posta listesi]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Kendi diliniz için MediaWiki yerelleştirmesi]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam Kendi vikinizde spam ile nasıl savaşılacağını öğrennin]" + "mainpagetext": "MediaWiki başarı ile kuruldu.", + "mainpagedocfooter": "Viki yazılımının kullanımı hakkında bilgi almak için [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents kullanıcı rehberine] bakınız.\n\n== Yeni Başlayanlar ==\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Yapılandırma ayarlarının listesi]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki SSS]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki e-posta listesi]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Kendi diliniz için MediaWiki yerelleştirmesi]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam Kendi vikinizde spam ile nasıl savaşılacağını öğrennin]" } diff --git a/includes/installer/i18n/uk.json b/includes/installer/i18n/uk.json index 3f31990769..986964af57 100644 --- a/includes/installer/i18n/uk.json +++ b/includes/installer/i18n/uk.json @@ -119,15 +119,15 @@ "config-sqlite-dir-help": "SQLite зберігає усі дані в єдиному файлі.\n\nПапка, яку Ви вказуєте, має бути доступна серверу для запису під час встановлення.\n\nВона '''не''' повинна бути доступна через інтернет, тому ми і не поміщуємо її туди, де Ваші файли PHP.\n\nІнсталятор пропише у неї файл .htaccess, але якщо це не спрацює, хтось може отримати доступ до Вашої вихідної бази даних, яка містить вихідні дані користувача (адреси електронної пошти, хеші паролів), а також видалені версії та інші обмежені дані на вікі.\n\nЗа можливості розташуйте базу даних десь окремо, наприклад в /var/lib/mediawiki/yourwiki.", "config-oracle-def-ts": "Простір таблиць за замовчуванням:", "config-oracle-temp-ts": "Тимчасовий простір таблиць:", - "config-type-mysql": "MySQL (або сумісний)", + "config-type-mysql": "\nMariaDB, MySQL (або сумісні)", "config-type-mssql": "Microsoft SQL Server", "config-support-info": "MediaWiki підтримує такі системи баз даних:\n\n$1\n\nЯкщо Ви не бачите серед перерахованих систему баз даних, яку використовуєте, виконайте вказівки, вказані вище, щоб увімкнути підтримку.", - "config-dbsupport-mysql": "* [{{int:version-db-mysql-url}} MySQL] є основною для MediaWiki і найкраще підтримується. MediaWiki також працює із [{{int:version-db-mariadb-url}} MariaDB] та [{{int:version-db-percona-url}} Percona Server], які сумісні з MySQL. ([https://secure.php.net/manual/en/mysqli.installation.php як зібрати PHP з допомогою MySQL])", + "config-dbsupport-mysql": "* [{{int:version-db-mariadb-url}} MariaDB] є основною ціллю для MediaWiki і найкраще підтримується. MediaWiki також працює з [{{int:version-db-mysql-url}} MySQL] та [{{int:version-db-percona-url}} Percona Server], які сумісні з MariaDB. ([https://secure.php.net/manual/en/mysqli.installation.php Як зібрати PHP з підтримкою MySQL])", "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] — популярна відкрита СУБД, альтернатива MySQL. ([https://secure.php.net/manual/en/pgsql.installation.php як зібрати PHP з допомогою PostgreSQL]).", "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite] — легка система баз даних, яка дуже добре підтримується. ([http://www.php.net/manual/en/pdo.installation.php Як зібрати PHP з допомогою SQLite], що використовує PDO)", "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] — комерційна база даних масштабу підприємства. ([http://www.php.net/manual/en/oci8.installation.php Як зібрати PHP з підтримкою OCI8])", "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] — це комерційна база даних для Windows масштабу підприємства. ([https://secure.php.net/manual/en/sqlsrv.installation.php Як зібрати PHP з підтримкою SQLSRV])", - "config-header-mysql": "Налаштування MySQL", + "config-header-mysql": "Налаштування MariaDB/MySQL", "config-header-postgres": "Налаштування PostgreSQL", "config-header-sqlite": "Налаштування SQLite", "config-header-oracle": "Налаштування Oracle", @@ -166,7 +166,7 @@ "config-db-web-create": "Створити обліковий запис, якщо його ще не існує", "config-db-web-no-create-privs": "Обліковий запис, вказаний Вами для встановлення, не має достатніх повноважень для створення облікового запису.\nОбліковий запис, який Ви вказуєте тут, уже повинен існувати.", "config-mysql-engine": "Двигун бази даних:", - "config-mysql-innodb": "InnoDB", + "config-mysql-innodb": "InnoDB (рекомендовано)", "config-mysql-myisam": "MyISAM", "config-mysql-myisam-dep": "'''Увага''': Ви обрали MyISAM для зберігання даних MySQL, що не рекомендовано для роботи з MediaWiki, оскільки:\n* він слабко підтримує паралелізм через блокування таблиць\n* він більш схильний до ушкоджень, ніж інші двигуни\n* база коду MediaWiki не завжди працює з MyISAM так, як мала б.\n\nЯкщо Ваша інсталяція MySQL підтримує InnoDB, дуже рекомендується вибрати цей двигун.\nЯкщо Ваша інсталяція MySQL не підтримує InnoDB, можливо настав час її оновити.", "config-mysql-only-myisam-dep": "\"'Зауваження:\"' MyISAM є єдиним механізмом для зберігання MySQL на цій машині, який не рекомендується для використання з MediaWiki, оскільки:\n* слабо підтримує паралелізм через блокування таблиць\n* більш схильний до пошкоджень, ніж інші двигуни\n* код MediaWiki не завжди розглядає MyISAM, як повинен\n\nТвоє встановлення MySQL не підтримує InnoDB, можливо, потрібно оновити.", diff --git a/includes/installer/i18n/yue.json b/includes/installer/i18n/yue.json index 44bcb0e122..49d534a184 100644 --- a/includes/installer/i18n/yue.json +++ b/includes/installer/i18n/yue.json @@ -1,5 +1,49 @@ { - "@metadata": [], - "mainpagetext": "'''MediaWiki已經裝好。'''", - "mainpagedocfooter": "參閱[https://meta.wikimedia.org/wiki/Help:Contents 用戶指引](英),裏面有資料講點用wiki軟件。\n\n==開始使用==\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings 配置設定清單](英)\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki 常見問題](英)\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki 發佈郵件名單](英)" + "@metadata": { + "authors": [ + "Hello903hello" + ] + }, + "config-desc": "MediaWiki安裝程式", + "config-title": "安裝MediaWiki$1", + "config-information": "資訊", + "config-your-language": "你嘅語言", + "config-your-language-help": "揀安裝程序入面用嘅語言。", + "config-wiki-language": "Wiki語言:", + "config-wiki-language-help": "揀將要安裝嘅wiki多數情況主要用嘅語言。", + "config-back": "← 返去", + "config-continue": "繼續 →", + "config-page-language": "語言", + "config-page-welcome": "歡迎使用MediaWiki!", + "config-page-dbconnect": "連線到資料庫", + "config-page-upgrade": "升級安裝咗嘅版本", + "config-page-dbsettings": "資料庫設定", + "config-page-name": "名", + "config-page-options": "選項", + "config-page-install": "安裝", + "config-page-complete": "搞掂!", + "config-page-restart": "重新開始安裝", + "config-page-readme": "讀我", + "config-page-releasenotes": "發行解", + "config-page-copying": "複製緊", + "config-page-upgradedoc": "升級緊", + "config-page-existingwiki": "現有wiki", + "config-restart": "係,重新開始", + "config-env-php": "安裝咗PHP$1", + "config-env-hhvm": "安裝咗HHVM$1", + "config-outdated-sqlite": "警告:你安裝咗SQLite $1,但係佢嘅版本低過最低要求版本 $2。你將會用毋到SQLite。", + "config-apc": "[https://secure.php.net/apc APC]安裝咗", + "config-apcu": "[https://secure.php.net/apcu APCu]安裝咗", + "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache]安裝咗", + "config-diff3-bad": "搵毋到GNU diff3。", + "config-db-type": "資料庫類型:", + "config-install-mainpage-exists": "頭版已經存在,跳緊", + "config-install-success": "MediaWiki已經成功安裝咗。你而家可以去 <$1$2> 去睇你嘅wiki。\n如果你有任何問題,請去常見問題一覽:\n 或者去嗰版上面任何一個論壇搵幫手。", + "config-download-localsettings": "下載LocalSettings.php", + "config-help": "幫手", + "config-nofile": "搵毋到檔案「$1」。佢係毋係俾人刪咗?", + "config-skins-screenshots": "$1(螢幕截圖: $2)", + "config-screenshot": "螢幕截圖", + "mainpagetext": "MediaWiki已經裝好。", + "mainpagedocfooter": "請睇[https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents 用戶指引](英文),裏面有資料講點用wiki軟件。\n\n==開始使用==\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings 配置設定清單](英文)\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki 常見問題](英文)\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki 發佈郵件名單](英文)\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources 用你講嘅文來本地化MediaWiki]\t\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam 學下點樣喺你嘅wiki度對付破壞]" } diff --git a/includes/jobqueue/JobQueueDB.php b/includes/jobqueue/JobQueueDB.php index 689326e8b6..0b85fbe8be 100644 --- a/includes/jobqueue/JobQueueDB.php +++ b/includes/jobqueue/JobQueueDB.php @@ -264,8 +264,6 @@ class JobQueueDB extends JobQueue { if ( $flags & self::QOS_ATOMIC ) { $dbw->endAtomic( $method ); } - - return; } /** diff --git a/includes/jobqueue/JobRunner.php b/includes/jobqueue/JobRunner.php index 7c5d0ae703..dab9b14d19 100644 --- a/includes/jobqueue/JobRunner.php +++ b/includes/jobqueue/JobRunner.php @@ -29,7 +29,6 @@ use Psr\Log\LoggerInterface; use Wikimedia\ScopedCallback; use Wikimedia\Rdbms\LBFactory; use Wikimedia\Rdbms\DBError; -use Wikimedia\Rdbms\DBReplicationWaitError; /** * Job queue runner utility methods @@ -225,21 +224,16 @@ class JobRunner implements LoggerAwareInterface { // other wikis in the farm (on different masters) get a chance. $timePassed = microtime( true ) - $lastCheckTime; if ( $timePassed >= self::LAG_CHECK_PERIOD || $timePassed < 0 ) { - try { - $lbFactory->waitForReplication( [ - 'ifWritesSince' => $lastCheckTime, - 'timeout' => self::MAX_ALLOWED_LAG - ] ); - } catch ( DBReplicationWaitError $e ) { + $success = $lbFactory->waitForReplication( [ + 'ifWritesSince' => $lastCheckTime, + 'timeout' => self::MAX_ALLOWED_LAG, + ] ); + if ( !$success ) { $response['reached'] = 'replica-lag-limit'; break; } $lastCheckTime = microtime( true ); } - // Don't let any queue replica DBs/backups fall behind - if ( $jobsPopped > 0 && ( $jobsPopped % 100 ) == 0 ) { - $group->waitForBackups(); - } // Bail if near-OOM instead of in a job if ( !$this->checkMemoryOK() ) { diff --git a/includes/jobqueue/jobs/CategoryMembershipChangeJob.php b/includes/jobqueue/jobs/CategoryMembershipChangeJob.php index a0c70abe71..c39823ff9b 100644 --- a/includes/jobqueue/jobs/CategoryMembershipChangeJob.php +++ b/includes/jobqueue/jobs/CategoryMembershipChangeJob.php @@ -232,12 +232,14 @@ class CategoryMembershipChangeJob extends Job { * @return string[] category names */ private function getCategoriesAtRev( WikiPage $page, Revision $rev, $parseTimestamp ) { - $content = $rev->getContent(); + $renderer = MediaWikiServices::getInstance()->getRevisionRenderer(); $options = $page->makeParserOptions( 'canonical' ); $options->setTimestamp( $parseTimestamp ); + // This could possibly use the parser cache if it checked the revision ID, // but that's more complicated than it's worth. - $output = $content->getParserOutput( $page->getTitle(), $rev->getId(), $options ); + $output = $renderer->getRenderedRevision( $rev->getRevisionRecord(), $options ) + ->getRevisionParserOutput(); // array keys will cast numeric category names to ints // so we need to cast them back to strings to avoid breaking things! diff --git a/includes/jobqueue/jobs/DeleteLinksJob.php b/includes/jobqueue/jobs/DeleteLinksJob.php index 0a1419212b..8328abfb6b 100644 --- a/includes/jobqueue/jobs/DeleteLinksJob.php +++ b/includes/jobqueue/jobs/DeleteLinksJob.php @@ -47,6 +47,10 @@ class DeleteLinksJob extends Job { // Serialize links updates by page ID so they see each others' changes $scopedLock = LinksUpdate::acquirePageLock( wfGetDB( DB_MASTER ), $pageId, 'job' ); + if ( $scopedLock === null ) { + $this->setLastError( 'LinksUpdate already running for this page, try again later.' ); + return false; + } if ( WikiPage::newFromID( $pageId, WikiPage::READ_LATEST ) ) { // The page was restored somehow or something went wrong diff --git a/includes/jobqueue/jobs/RecentChangesUpdateJob.php b/includes/jobqueue/jobs/RecentChangesUpdateJob.php index 8f508283d7..223ae324b8 100644 --- a/includes/jobqueue/jobs/RecentChangesUpdateJob.php +++ b/includes/jobqueue/jobs/RecentChangesUpdateJob.php @@ -19,7 +19,6 @@ * @ingroup JobQueue */ use MediaWiki\MediaWikiServices; -use Wikimedia\Rdbms\DBReplicationWaitError; /** * Job for pruning recent changes @@ -105,11 +104,9 @@ class RecentChangesUpdateJob extends Job { $dbw->delete( 'recentchanges', [ 'rc_id' => $rcIds ], __METHOD__ ); Hooks::run( 'RecentChangesPurgeRows', [ $rows ] ); // There might be more, so try waiting for replica DBs - try { - $factory->commitAndWaitForReplication( - __METHOD__, $ticket, [ 'timeout' => 3 ] - ); - } catch ( DBReplicationWaitError $e ) { + if ( !$factory->commitAndWaitForReplication( + __METHOD__, $ticket, [ 'timeout' => 3 ] + ) ) { // Another job will continue anyway break; } diff --git a/includes/jobqueue/jobs/RefreshLinksJob.php b/includes/jobqueue/jobs/RefreshLinksJob.php index 8854c6560f..0b6d30776b 100644 --- a/includes/jobqueue/jobs/RefreshLinksJob.php +++ b/includes/jobqueue/jobs/RefreshLinksJob.php @@ -21,7 +21,6 @@ * @ingroup JobQueue */ use MediaWiki\MediaWikiServices; -use Wikimedia\Rdbms\DBReplicationWaitError; /** * Job to update link tables for pages @@ -89,13 +88,11 @@ class RefreshLinksJob extends Job { // From then on, we know that any template changes at the time the base job was // enqueued will be reflected in backlink page parses when the leaf jobs run. if ( !isset( $this->params['range'] ) ) { - try { - $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory(); - $lbFactory->waitForReplication( [ + $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory(); + if ( !$lbFactory->waitForReplication( [ 'wiki' => wfWikiID(), 'timeout' => self::LAG_WAIT_TIMEOUT - ] ); - } catch ( DBReplicationWaitError $e ) { // only try so hard + ] ) ) { // only try so hard $stats = MediaWikiServices::getInstance()->getStatsdDataFactory(); $stats->increment( 'refreshlinks.lag_wait_failed' ); } @@ -146,6 +143,11 @@ class RefreshLinksJob extends Job { $dbw = $lbFactory->getMainLB()->getConnection( DB_MASTER ); /** @noinspection PhpUnusedLocalVariableInspection */ $scopedLock = LinksUpdate::acquirePageLock( $dbw, $page->getId(), 'job' ); + if ( $scopedLock === null ) { + // Another job is already updating the page, likely for an older revision (T170596). + $this->setLastError( 'LinksUpdate already running for this page, try again later.' ); + return false; + } // Get the latest ID *after* acquirePageLock() flushed the transaction. // This is used to detect edits/moves after loadPageData() but before the scope lock. // The works around the chicken/egg problem of determining the scope lock key. @@ -245,43 +247,24 @@ class RefreshLinksJob extends Job { $stats->increment( 'refreshlinks.parser_uncached' ); } - $updates = $content->getSecondaryDataUpdates( - $title, - null, - !empty( $this->params['useRecursiveLinksUpdate'] ), - $parserOutput - ); - - // For legacy hook handlers doing updates via LinksUpdateConstructed, make sure - // any pending writes they made get flushed before the doUpdate() calls below. - // This avoids snapshot-clearing errors in LinksUpdate::acquirePageLock(). - $lbFactory->commitAndWaitForReplication( __METHOD__, $ticket ); - - foreach ( $updates as $update ) { - // Carry over cause in case so the update can do extra logging - $update->setCause( $this->params['causeAction'], $this->params['causeAgent'] ); - // FIXME: This code probably shouldn't be here? - // Needed by things like Echo notifications which need - // to know which user caused the links update - if ( $update instanceof LinksUpdate ) { - $update->setRevision( $revision ); - if ( !empty( $this->params['triggeringUser'] ) ) { - $userInfo = $this->params['triggeringUser']; - if ( $userInfo['userId'] ) { - $user = User::newFromId( $userInfo['userId'] ); - } else { - // Anonymous, use the username - $user = User::newFromName( $userInfo['userName'], false ); - } - $update->setTriggeringUser( $user ); - } + $options = [ + 'recursive' => !empty( $this->params['useRecursiveLinksUpdate'] ), + // Carry over cause so the update can do extra logging + 'causeAction' => $this->params['causeAction'], + 'causeAgent' => $this->params['causeAgent'], + 'defer' => false, + 'transactionTicket' => $ticket, + ]; + if ( !empty( $this->params['triggeringUser'] ) ) { + $userInfo = $this->params['triggeringUser']; + if ( $userInfo['userId'] ) { + $options['triggeringUser'] = User::newFromId( $userInfo['userId'] ); + } else { + // Anonymous, use the username + $options['triggeringUser'] = User::newFromName( $userInfo['userName'], false ); } } - - foreach ( $updates as $update ) { - $update->setTransactionTicket( $ticket ); - $update->doUpdate(); - } + $page->doSecondaryDataUpdates( $options ); InfoAction::invalidateCache( $title ); diff --git a/includes/jobqueue/jobs/ThumbnailRenderJob.php b/includes/jobqueue/jobs/ThumbnailRenderJob.php index 49eabbba25..f87a33691d 100644 --- a/includes/jobqueue/jobs/ThumbnailRenderJob.php +++ b/includes/jobqueue/jobs/ThumbnailRenderJob.php @@ -103,8 +103,10 @@ class ThumbnailRenderJob extends Job { wfDebug( __METHOD__ . ": hitting url {$thumbUrl}\n" ); + // T203135 We don't wait for the request to complete, as this is mostly fire & forget. + // Looking at the HTTP status of requests that take less than 1s is a sanity check. $request = MWHttpRequest::factory( $thumbUrl, - [ 'method' => 'HEAD', 'followRedirects' => true ], + [ 'method' => 'HEAD', 'followRedirects' => true, 'timeout' => 1 ], __METHOD__ ); @@ -122,6 +124,10 @@ class ThumbnailRenderJob extends Job { return true; } elseif ( $statusCode ) { $this->setLastError( __METHOD__ . ": incorrect HTTP status $statusCode when hitting $thumbUrl" ); + } elseif ( $status->hasMessage( 'http-timed-out' ) ) { + // T203135 we ignore timeouts, as it would be inefficient for this job to wait for + // minutes for the slower thumbnails to complete. + return true; } else { $this->setLastError( __METHOD__ . ': HTTP request failure: ' . Status::wrap( $status )->getWikiText( null, null, 'en' ) ); diff --git a/includes/json/FormatJson.php b/includes/json/FormatJson.php index 1ab17a06f5..fbcb3bd3b8 100644 --- a/includes/json/FormatJson.php +++ b/includes/json/FormatJson.php @@ -271,7 +271,7 @@ class FormatJson { $lookAhead = ( $idx + 1 < $maxLen ) ? $str[$idx + 1] : ''; $lookBehind = ( $idx - 1 >= 0 ) ? $str[$idx - 1] : ''; if ( $inString ) { - continue; + break; } elseif ( !$inComment && ( $lookAhead === '/' || $lookAhead === '*' ) diff --git a/includes/libs/JavaScriptMinifier.php b/includes/libs/JavaScriptMinifier.php index 3015825249..73f3e70739 100644 --- a/includes/libs/JavaScriptMinifier.php +++ b/includes/libs/JavaScriptMinifier.php @@ -334,6 +334,8 @@ class JavaScriptMinifier { self::ACTION_GOTO => self::PAREN_EXPRESSION, ], ], + // Property assignment - This is an object literal declaration. + // For example: `{ key: value }` self::PROPERTY_ASSIGNMENT => [ self::TYPE_COLON => [ self::ACTION_GOTO => self::PROPERTY_EXPRESSION, @@ -520,6 +522,7 @@ class JavaScriptMinifier { self::ACTION_GOTO => self::STATEMENT, ], ], + // Property expression - The value of a key in an object literal. self::PROPERTY_EXPRESSION => [ self::TYPE_BRACE_OPEN => [ self::ACTION_PUSH => self::PROPERTY_EXPRESSION_OP, @@ -547,7 +550,8 @@ class JavaScriptMinifier { self::ACTION_GOTO => self::PROPERTY_EXPRESSION, ], self::TYPE_HOOK => [ - self::ACTION_GOTO => self::PROPERTY_EXPRESSION, + self::ACTION_PUSH => self::PROPERTY_EXPRESSION, + self::ACTION_GOTO => self::EXPRESSION_TERNARY, ], self::TYPE_COMMA => [ self::ACTION_GOTO => self::PROPERTY_ASSIGNMENT, diff --git a/includes/libs/RiffExtractor.php b/includes/libs/RiffExtractor.php index 304b99b8a4..c060380a71 100644 --- a/includes/libs/RiffExtractor.php +++ b/includes/libs/RiffExtractor.php @@ -96,4 +96,4 @@ class RiffExtractor { public static function extractUInt32( $string ) { return unpack( 'V', $string )[1]; } -}; +} diff --git a/includes/libs/StaticArrayWriter.php b/includes/libs/StaticArrayWriter.php new file mode 100644 index 0000000000..1e0e1dc9ce --- /dev/null +++ b/includes/libs/StaticArrayWriter.php @@ -0,0 +1,72 @@ + $value ) { + $code .= $this->encode( $key, $value, 1 ); + } + $code .= "];\n"; + return $code; + } + + /** + * Recursively turn one k/v pair into properly-indented PHP + * + * @param string|int $key + * @param array|mixed $value + * @param int $indent Indentation level + * + * @return string + */ + private function encode( $key, $value, $indent ) { + $tabs = str_repeat( "\t", $indent ); + $line = $tabs . + var_export( $key, true ) . + ' => '; + if ( is_array( $value ) ) { + $line .= "[\n"; + foreach ( $value as $key2 => $value2 ) { + $line .= $this->encode( $key2, $value2, $indent + 1 ); + } + $line .= "$tabs]"; + } else { + $line .= var_export( $value, true ); + } + + $line .= ",\n"; + return $line; + } +} diff --git a/includes/libs/filebackend/fsfile/TempFSFile.php b/includes/libs/filebackend/fsfile/TempFSFile.php index 00d2028790..321424f94b 100644 --- a/includes/libs/filebackend/fsfile/TempFSFile.php +++ b/includes/libs/filebackend/fsfile/TempFSFile.php @@ -61,7 +61,7 @@ class TempFSFile extends FSFile { if ( !is_string( $tmpDirectory ) ) { $tmpDirectory = self::getUsableTempDirectory(); } - $path = wfTempDir() . '/' . $prefix . $hex . $ext; + $path = $tmpDirectory . '/' . $prefix . $hex . $ext; Wikimedia\suppressWarnings(); $newFileHandle = fopen( $path, 'x' ); Wikimedia\restoreWarnings(); diff --git a/includes/libs/objectcache/WANObjectCache.php b/includes/libs/objectcache/WANObjectCache.php index 716641fcbc..3af820b883 100644 --- a/includes/libs/objectcache/WANObjectCache.php +++ b/includes/libs/objectcache/WANObjectCache.php @@ -27,6 +27,8 @@ use Psr\Log\NullLogger; /** * Multi-datacenter aware caching interface * + * ### Using WANObjectCache + * * All operations go to the local datacenter cache, except for delete(), * touchCheckKey(), and resetCheckKey(), which broadcast to all datacenters. * @@ -36,34 +38,63 @@ use Psr\Log\NullLogger; * The preferred way to do this logic is through getWithSetCallback(). * When querying the store on cache miss, the closest DB replica * should be used. Try to avoid heavyweight DB master or quorum reads. - * When the source data changes, a purge method should be called. - * Since purges are expensive, they should be avoided. One can do so if: - * - a) The object cached is immutable; or - * - b) Validity is checked against the source after get(); or - * - c) Using a modest TTL is reasonably correct and performant * + * To ensure consumers of the cache see new values in a timely manner, + * you either need to follow either the validation strategy, or the + * purge strategy. + * + * The validation strategy refers to the natural avoidance of stale data + * by one of the following means: + * + * - A) The cached value is immutable. + * If the consumer has access to an identifier that uniquely describes a value, + * cached value need not change. Instead, the key can change. This also allows + * all servers to access their perceived current version. This is important + * in context of multiple deployed versions of your application and/or cross-dc + * database replication, to ensure deterministic values without oscillation. + * - B) Validity is checked against the source after get(). + * This is the inverse of A. The unique identifier is embedded inside the value + * and validated after on retreival. If outdated, the value is recomputed. + * - C) The value is cached with a modest TTL (without validation). + * If value recomputation is reasonably performant, and the value is allowed to + * be stale, one should consider using TTL only – using the value's age as + * method of validation. + * + * The purge strategy refers to the the approach whereby your application knows that + * source data has changed and can react by purging the relevant cache keys. + * As purges are expensive, this strategy should be avoided if possible. * The simplest purge method is delete(). * - * There are three supported ways to handle broadcasted operations: - * - a) Configure the 'purge' EventRelayer to point to a valid PubSub endpoint - * that has subscribed listeners on the cache servers applying the cache updates. - * - b) Ommit the 'purge' EventRelayer parameter and set up mcrouter as the underlying cache + * No matter which strategy you choose, callers must not rely on updates or purges + * being immediately visible to other servers. It should be treated similarly as + * one would a database replica. + * + * The need for immediate updates should be avoided. If needed, solutions must be + * sought outside WANObjectCache. + * + * ### Deploying WANObjectCache + * + * There are three supported ways to set up broadcasted operations: + * + * - A) Configure the 'purge' EventRelayer to point to a valid PubSub endpoint + * that has subscribed listeners on the cache servers applying the cache updates. + * - B) Omit the 'purge' EventRelayer parameter and set up mcrouter as the underlying cache * backend, using a memcached BagOStuff class for the 'cache' parameter. The 'region' - * and 'cluster' parameters must be provided and 'mcrouterAware' must be set to 'true'. + * and 'cluster' parameters must be provided and 'mcrouterAware' must be set to `true`. * Configure mcrouter as follows: * - 1) Use Route Prefixing based on region (datacenter) and cache cluster. - * See https://github.com/facebook/mcrouter/wiki/Routing-Prefix and - * https://github.com/facebook/mcrouter/wiki/Multi-cluster-broadcast-setup + * See https://github.com/facebook/mcrouter/wiki/Routing-Prefix and + * https://github.com/facebook/mcrouter/wiki/Multi-cluster-broadcast-setup. * - 2) To increase the consistency of delete() and touchCheckKey() during cache - * server membership changes, you can use the OperationSelectorRoute to - * configure 'set' and 'delete' operations to go to all servers in the cache - * cluster, instead of just one server determined by hashing. - * See https://github.com/facebook/mcrouter/wiki/List-of-Route-Handles - * - c) Ommit the 'purge' EventRelayer parameter and set up dynomite as cache middleware - * between the web servers and either memcached or redis. This will also broadcast all - * key setting operations, not just purges, which can be useful for cache warming. - * Writes are eventually consistent via the Dynamo replication model. - * See https://github.com/Netflix/dynomite + * server membership changes, you can use the OperationSelectorRoute to + * configure 'set' and 'delete' operations to go to all servers in the cache + * cluster, instead of just one server determined by hashing. + * See https://github.com/facebook/mcrouter/wiki/List-of-Route-Handles. + * - C) Omit the 'purge' EventRelayer parameter and set up dynomite as cache middleware + * between the web servers and either memcached or redis. This will broadcast all + * key setting operations, not just purges, which can be useful for cache warming. + * Writes are eventually consistent via the Dynamo replication model. + * See https://github.com/Netflix/dynomite. * * Broadcasted operations like delete() and touchCheckKey() are done asynchronously * in all datacenters this way, though the local one should likely be near immediate. diff --git a/includes/libs/rdbms/ChronologyProtector.php b/includes/libs/rdbms/ChronologyProtector.php index 45179cc835..938e5345db 100644 --- a/includes/libs/rdbms/ChronologyProtector.php +++ b/includes/libs/rdbms/ChronologyProtector.php @@ -78,9 +78,8 @@ class ChronologyProtector implements LoggerAwareInterface { */ public function __construct( BagOStuff $store, array $client, $posIndex = null ) { $this->store = $store; - $this->clientId = isset( $client['clientId'] ) - ? $client['clientId'] - : md5( $client['ip'] . "\n" . $client['agent'] ); + $this->clientId = $client['clientId'] ?? + md5( $client['ip'] . "\n" . $client['agent'] ); $this->key = $store->makeGlobalKey( __CLASS__, $this->clientId, 'v2' ); $this->waitForPosIndex = $posIndex; diff --git a/includes/libs/rdbms/database/DBConnRef.php b/includes/libs/rdbms/database/DBConnRef.php index eba1657f29..0de90c9d04 100644 --- a/includes/libs/rdbms/database/DBConnRef.php +++ b/includes/libs/rdbms/database/DBConnRef.php @@ -178,10 +178,6 @@ class DBConnRef implements IDatabase { return $this->__call( __FUNCTION__, func_get_args() ); } - public function open( $server, $user, $password, $dbName ) { - return $this->__call( __FUNCTION__, func_get_args() ); - } - public function fetchObject( $res ) { return $this->__call( __FUNCTION__, func_get_args() ); } diff --git a/includes/libs/rdbms/database/Database.php b/includes/libs/rdbms/database/Database.php index e35e0827b0..e276d09992 100644 --- a/includes/libs/rdbms/database/Database.php +++ b/includes/libs/rdbms/database/Database.php @@ -266,7 +266,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware /** @var int[] Prior flags member variable values */ private $priorFlags = []; - /** @var object|string Class name or object With profileIn/profileOut methods */ + /** @var mixed Class name or object With profileIn/profileOut methods */ protected $profiler; /** @var TransactionProfiler */ protected $trxProfiler; @@ -373,6 +373,18 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware } } + /** + * Open a new connection to the database (closing any existing one) + * + * @param string $server Database server host + * @param string $user Database user name + * @param string $password Database user password + * @param string $dbName Database name + * @return bool + * @throws DBConnectionError + */ + abstract protected function open( $server, $user, $password, $dbName ); + /** * Construct a Database subclass instance given a database type and parameters * @@ -3496,7 +3508,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware list( $phpCallback ) = $callback; $phpCallback( $this ); } catch ( Exception $ex ) { - $this->errorLogger( $ex ); + ( $this->errorLogger )( $ex ); $e = $e ?: $ex; } } @@ -4018,7 +4030,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware * a wrapper. Nowadays, raw database objects are never exposed to external * callers, so this is unnecessary in external code. * - * @param bool|ResultWrapper|resource|object $result + * @param bool|ResultWrapper|resource $result * @return bool|ResultWrapper */ protected function resultObject( $result ) { diff --git a/includes/libs/rdbms/database/DatabaseMssql.php b/includes/libs/rdbms/database/DatabaseMssql.php index fed6f14616..1246e4463f 100644 --- a/includes/libs/rdbms/database/DatabaseMssql.php +++ b/includes/libs/rdbms/database/DatabaseMssql.php @@ -77,16 +77,7 @@ class DatabaseMssql extends Database { parent::__construct( $params ); } - /** - * Usually aborts on failure - * @param string $server - * @param string $user - * @param string $password - * @param string $dbName - * @throws DBConnectionError - * @return bool|resource|null - */ - public function open( $server, $user, $password, $dbName ) { + protected function open( $server, $user, $password, $dbName ) { # Test for driver support, to avoid suppressed fatal error if ( !function_exists( 'sqlsrv_connect' ) ) { throw new DBConnectionError( @@ -130,7 +121,7 @@ class DatabaseMssql extends Database { $this->opened = true; - return $this->conn; + return (bool)$this->conn; } /** @@ -243,7 +234,7 @@ class DatabaseMssql extends Database { } /** - * @param MssqlResultWrapper $res + * @param IResultWrapper $res * @return stdClass */ public function fetchObject( $res ) { @@ -252,7 +243,7 @@ class DatabaseMssql extends Database { } /** - * @param MssqlResultWrapper $res + * @param IResultWrapper $res * @return array */ public function fetchRow( $res ) { diff --git a/includes/libs/rdbms/database/DatabaseMysqlBase.php b/includes/libs/rdbms/database/DatabaseMysqlBase.php index 57fab54936..0f575516b4 100644 --- a/includes/libs/rdbms/database/DatabaseMysqlBase.php +++ b/includes/libs/rdbms/database/DatabaseMysqlBase.php @@ -120,15 +120,7 @@ abstract class DatabaseMysqlBase extends Database { return 'mysql'; } - /** - * @param string $server - * @param string $user - * @param string $password - * @param string $dbName - * @throws Exception|DBConnectionError - * @return bool - */ - public function open( $server, $user, $password, $dbName ) { + protected function open( $server, $user, $password, $dbName ) { # Close/unset connection handle $this->close(); diff --git a/includes/libs/rdbms/database/DatabasePostgres.php b/includes/libs/rdbms/database/DatabasePostgres.php index a959d72ba8..691a4b72e1 100644 --- a/includes/libs/rdbms/database/DatabasePostgres.php +++ b/includes/libs/rdbms/database/DatabasePostgres.php @@ -86,7 +86,7 @@ class DatabasePostgres extends Database { return false; } - public function open( $server, $user, $password, $dbName ) { + protected function open( $server, $user, $password, $dbName ) { # Test for Postgres support, to avoid suppressed fatal error if ( !function_exists( 'pg_connect' ) ) { throw new DBConnectionError( @@ -861,6 +861,9 @@ __INDEXATTR__; return false; } + /** + * @suppress SecurityCheck-SQLInjection array_map not recognized T204911 + */ public function listTables( $prefix = null, $fname = __METHOD__ ) { $eschemas = implode( ',', array_map( [ $this, 'addQuotes' ], $this->getCoreSchemas() ) ); $result = $this->query( diff --git a/includes/libs/rdbms/database/DatabaseSqlite.php b/includes/libs/rdbms/database/DatabaseSqlite.php index 25fbba09e5..c8edc3901c 100644 --- a/includes/libs/rdbms/database/DatabaseSqlite.php +++ b/includes/libs/rdbms/database/DatabaseSqlite.php @@ -155,24 +155,14 @@ class DatabaseSqlite extends Database { return false; } - /** 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 string $server - * @param string $user Unused - * @param string $pass - * @param string $dbName - * - * @throws DBConnectionError - * @return bool - */ - function open( $server, $user, $pass, $dbName ) { + protected function open( $server, $user, $pass, $dbName ) { $this->close(); $fileName = self::generateFileName( $this->dbDir, $dbName ); if ( !is_readable( $fileName ) ) { $this->conn = false; throw new DBConnectionError( $this, "SQLite database not accessible" ); } + // Only $dbName is used, the other parameters are irrelevant for SQLite databases $this->openFile( $fileName, $dbName ); return (bool)$this->conn; @@ -984,7 +974,9 @@ class DatabaseSqlite extends Database { } $sql = $obj->sql; $sql = preg_replace( - '/(?<=\W)"?' . preg_quote( trim( $this->addIdentifierQuotes( $oldName ), '"' ) ) . '"?(?=\W)/', + '/(?<=\W)"?' . + preg_quote( trim( $this->addIdentifierQuotes( $oldName ), '"' ), '/' ) . + '"?(?=\W)/', $this->addIdentifierQuotes( $newName ), $sql, 1 diff --git a/includes/libs/rdbms/database/IDatabase.php b/includes/libs/rdbms/database/IDatabase.php index 7da259d9f0..f97db3a7ca 100644 --- a/includes/libs/rdbms/database/IDatabase.php +++ b/includes/libs/rdbms/database/IDatabase.php @@ -370,18 +370,6 @@ interface IDatabase { */ public function getType(); - /** - * Open a new connection to the database (closing any existing one) - * - * @param string $server Database server host - * @param string $user Database user name - * @param string $password Database user password - * @param string $dbName Database name - * @return bool - * @throws DBConnectionError - */ - public function open( $server, $user, $password, $dbName ); - /** * Fetch the next row from the given result object, in object form. * Fields can be retrieved with $row->fieldname, with fields acting like diff --git a/includes/libs/rdbms/exception/DBQueryError.php b/includes/libs/rdbms/exception/DBQueryError.php index 4bdd8f6154..b1353b793e 100644 --- a/includes/libs/rdbms/exception/DBQueryError.php +++ b/includes/libs/rdbms/exception/DBQueryError.php @@ -45,7 +45,7 @@ class DBQueryError extends DBExpectedError { public function __construct( IDatabase $db, $error, $errno, $sql, $fname, $message = null ) { if ( $message === null ) { if ( $db instanceof Database && $db->wasConnectionError( $errno ) ) { - $message = "A connection error occured. \n" . + $message = "A connection error occurred. \n" . "Query: $sql\n" . "Function: $fname\n" . "Error: $errno $error\n"; diff --git a/includes/libs/rdbms/exception/DBReplicationWaitError.php b/includes/libs/rdbms/exception/DBReplicationWaitError.php index c5dd8ae19c..74abace87f 100644 --- a/includes/libs/rdbms/exception/DBReplicationWaitError.php +++ b/includes/libs/rdbms/exception/DBReplicationWaitError.php @@ -23,6 +23,7 @@ namespace Wikimedia\Rdbms; /** * Exception class for replica DB wait timeouts + * @deprecated since 1.32 * @ingroup Database */ class DBReplicationWaitError extends DBExpectedError { diff --git a/includes/libs/rdbms/lbfactory/ILBFactory.php b/includes/libs/rdbms/lbfactory/ILBFactory.php index 23699c7a88..8e35456904 100644 --- a/includes/libs/rdbms/lbfactory/ILBFactory.php +++ b/includes/libs/rdbms/lbfactory/ILBFactory.php @@ -269,9 +269,9 @@ interface ILBFactory { * @param array $opts Optional fields that include: * - domain : wait on the load balancer DBs that handles the given domain ID * - cluster : wait on the given external load balancer DBs - * - timeout : Max wait time. Default: ~60 seconds + * - timeout : Max wait time. Default: 60 seconds for CLI, 1 second for web. * - ifWritesSince: Only wait if writes were done since this UNIX timestamp - * @throws DBReplicationWaitError If a timeout or error occurred waiting on a DB cluster + * @return bool True on success, false if a timeout or error occurred while waiting */ public function waitForReplication( array $opts = [] ); @@ -301,7 +301,7 @@ interface ILBFactory { * @param string $fname Caller name (e.g. __METHOD__) * @param mixed $ticket Result of getEmptyTransactionTicket() * @param array $opts Options to waitForReplication() - * @throws DBReplicationWaitError + * @return bool True if the wait was successful, false on timeout */ public function commitAndWaitForReplication( $fname, $ticket, array $opts = [] ); diff --git a/includes/libs/rdbms/lbfactory/LBFactory.php b/includes/libs/rdbms/lbfactory/LBFactory.php index be1a8511dc..1612f41dca 100644 --- a/includes/libs/rdbms/lbfactory/LBFactory.php +++ b/includes/libs/rdbms/lbfactory/LBFactory.php @@ -375,7 +375,7 @@ abstract class LBFactory implements ILBFactory { $opts += [ 'domain' => false, 'cluster' => false, - 'timeout' => $this->cliMode ? 60 : 10, + 'timeout' => $this->cliMode ? 60 : 1, 'ifWritesSince' => null ]; @@ -432,13 +432,7 @@ abstract class LBFactory implements ILBFactory { } } - if ( $failed ) { - throw new DBReplicationWaitError( - null, - "Could not wait for replica DBs to catch up to " . - implode( ', ', $failed ) - ); - } + return !$failed; } public function setWaitForReplicationListener( $name, callable $callback = null ) { @@ -478,12 +472,13 @@ abstract class LBFactory implements ILBFactory { } $this->commitMasterChanges( $fnameEffective ); - $this->waitForReplication( $opts ); + $waitSucceeded = $this->waitForReplication( $opts ); // If a nested caller committed on behalf of $fname, start another empty $fname // transaction, leaving the caller with the same empty transaction state as before. if ( $fnameEffective !== $fname ) { $this->beginMasterChanges( $fnameEffective ); } + return $waitSucceeded; } public function getChronologyProtectorTouched( $dbName ) { @@ -733,8 +728,3 @@ abstract class LBFactory implements ILBFactory { $this->destroy(); } } - -/** - * @deprecated since 1.29 - */ -class_alias( LBFactory::class, 'LBFactory' ); diff --git a/includes/libs/rdbms/lbfactory/LBFactorySingle.php b/includes/libs/rdbms/lbfactory/LBFactorySingle.php index 2c1a782a53..60044baa9c 100644 --- a/includes/libs/rdbms/lbfactory/LBFactorySingle.php +++ b/includes/libs/rdbms/lbfactory/LBFactorySingle.php @@ -56,7 +56,11 @@ class LBFactorySingle extends LBFactory { * @since 1.28 */ public static function newFromConnection( IDatabase $db, array $params = [] ) { - return new static( [ 'connection' => $db ] + $params ); + return new static( array_merge( + [ 'localDomain' => $db->getDomainID() ], + $params, + [ 'connection' => $db ] + ) ); } /** diff --git a/includes/libs/rdbms/loadbalancer/LoadBalancer.php b/includes/libs/rdbms/loadbalancer/LoadBalancer.php index fbc3be96be..00b413017b 100644 --- a/includes/libs/rdbms/loadbalancer/LoadBalancer.php +++ b/includes/libs/rdbms/loadbalancer/LoadBalancer.php @@ -938,10 +938,6 @@ class LoadBalancer implements ILoadBalancer { $server = $this->servers[$i]; $server['serverIndex'] = $i; $server['autoCommitOnly'] = $autoCommit; - if ( $this->localDomain->getDatabase() !== null ) { - // Use the local domain table prefix if the local domain is specified - $server['tablePrefix'] = $this->localDomain->getTablePrefix(); - } $conn = $this->reallyOpenConnection( $server, $this->localDomain ); $host = $this->getServerName( $i ); if ( $conn->isOpen() ) { @@ -1037,7 +1033,6 @@ class LoadBalancer implements ILoadBalancer { $this->errorConnection = $conn; $conn = false; } else { - $conn->tablePrefix( $prefix ); // as specified // Note that if $domain is an empty string, getDomainID() might not match it $this->conns[$connInUseKey][$i][$conn->getDomainID()] = $conn; $this->connLogger->debug( __METHOD__ . ": opened new connection for $i/$domain" ); @@ -1081,20 +1076,20 @@ class LoadBalancer implements ILoadBalancer { * Returns a Database object whether or not the connection was successful. * * @param array $server - * @param DatabaseDomain $domainOverride Use an unspecified domain to not select any database + * @param DatabaseDomain $domain Domain the connection is for, possibly unspecified * @return Database * @throws DBAccessError * @throws InvalidArgumentException */ - protected function reallyOpenConnection( array $server, DatabaseDomain $domainOverride ) { + protected function reallyOpenConnection( array $server, DatabaseDomain $domain ) { if ( $this->disabled ) { throw new DBAccessError(); } - // Handle $domainOverride being a specified or an unspecified domain - if ( $domainOverride->getDatabase() === null ) { - // Normally, an RDBMS requires a DB name specified on connection and the $server - // configuration array is assumed to already specify an appropriate DB name. + if ( $domain->getDatabase() === null ) { + // The database domain does not specify a DB name and some database systems require a + // valid DB specified on connection. The $server configuration array contains a default + // DB name to use for connections in such cases. if ( $server['type'] === 'mysql' ) { // For MySQL, DATABASE and SCHEMA are synonyms, connections need not specify a DB, // and the DB name in $server might not exist due to legacy reasons (the default @@ -1102,10 +1097,16 @@ class LoadBalancer implements ILoadBalancer { $server['dbname'] = null; } } else { - $server['dbname'] = $domainOverride->getDatabase(); - $server['schema'] = $domainOverride->getSchema(); + $server['dbname'] = $domain->getDatabase(); + } + + if ( $domain->getSchema() !== null ) { + $server['schema'] = $domain->getSchema(); } + // It is always possible to connect with any prefix, even the empty string + $server['tablePrefix'] = $domain->getTablePrefix(); + // Let the handle know what the cluster master is (e.g. "db1052") $masterName = $this->getServerName( $this->getWriterIndex() ); $server['clusterMasterHost'] = $masterName; diff --git a/includes/libs/rdbms/loadbalancer/LoadBalancerSingle.php b/includes/libs/rdbms/loadbalancer/LoadBalancerSingle.php index 1b72502b14..5c0af119be 100644 --- a/includes/libs/rdbms/loadbalancer/LoadBalancerSingle.php +++ b/includes/libs/rdbms/loadbalancer/LoadBalancerSingle.php @@ -54,7 +54,8 @@ class LoadBalancerSingle extends LoadBalancer { ], 'trxProfiler' => $params['trxProfiler'] ?? null, 'srvCache' => $params['srvCache'] ?? null, - 'wanCache' => $params['wanCache'] ?? null + 'wanCache' => $params['wanCache'] ?? null, + 'localDomain' => $params['localDomain'] ?? $this->db->getDomainID() ] ); if ( isset( $params['readOnlyReason'] ) ) { @@ -69,7 +70,11 @@ class LoadBalancerSingle extends LoadBalancer { * @since 1.28 */ public static function newFromConnection( IDatabase $db, array $params = [] ) { - return new static( [ 'connection' => $db ] + $params ); + return new static( array_merge( + [ 'localDomain' => $db->getDomainID() ], + $params, + [ 'connection' => $db ] + ) ); } protected function reallyOpenConnection( array $server, DatabaseDomain $domainOverride ) { diff --git a/includes/libs/stats/PrefixingStatsdDataFactoryProxy.php b/includes/libs/stats/PrefixingStatsdDataFactoryProxy.php new file mode 100644 index 0000000000..136a614c3c --- /dev/null +++ b/includes/libs/stats/PrefixingStatsdDataFactoryProxy.php @@ -0,0 +1,96 @@ +factory = $factory; + $this->prefix = rtrim( $prefix, '.' ); + } + + /** + * @param string $key + * @return string + */ + private function addPrefixToKey( $key ) { + return $this->prefix . '.' . $key; + } + + public function timing( $key, $time ) { + return $this->factory->timing( $this->addPrefixToKey( $key ), $time ); + } + + public function gauge( $key, $value ) { + return $this->factory->gauge( $this->addPrefixToKey( $key ), $value ); + } + + public function set( $key, $value ) { + return $this->factory->set( $this->addPrefixToKey( $key ), $value ); + } + + public function increment( $key ) { + return $this->factory->increment( $this->addPrefixToKey( $key ) ); + } + + public function decrement( $key ) { + return $this->factory->decrement( $this->addPrefixToKey( $key ) ); + } + + public function updateCount( $key, $delta ) { + return $this->factory->updateCount( $this->addPrefixToKey( $key ), $delta ); + } + + public function produceStatsdData( + $key, + $value = 1, + $metric = StatsdDataInterface::STATSD_METRIC_COUNT + ) { + return $this->factory->produceStatsdData( + $this->addPrefixToKey( $key ), + $value, + $metric + ); + } +} diff --git a/includes/libs/virtualrest/ParsoidVirtualRESTService.php b/includes/libs/virtualrest/ParsoidVirtualRESTService.php index 73ecc9dc26..3f8a11b324 100644 --- a/includes/libs/virtualrest/ParsoidVirtualRESTService.php +++ b/includes/libs/virtualrest/ParsoidVirtualRESTService.php @@ -181,7 +181,7 @@ class ParsoidVirtualRESTService extends VirtualRESTService { unset( $req['query']['oldid'] ); } } elseif ( $reqType === 'transform' ) { - $req['url'] .= 'transform/'. $parts[3] . '/to/' . $parts[5]; + $req['url'] .= 'transform/' . $parts[3] . '/to/' . $parts[5]; // the title if ( isset( $parts[6] ) ) { $req['url'] .= '/' . $parts[6]; diff --git a/includes/logging/LogPage.php b/includes/logging/LogPage.php index 673c9292fa..af99940cb1 100644 --- a/includes/logging/LogPage.php +++ b/includes/logging/LogPage.php @@ -294,9 +294,11 @@ class LogPage { return $title->getPrefixedText(); } - $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer(); + $services = MediaWikiServices::getInstance(); + $linkRenderer = $services->getLinkRenderer(); if ( $title->isSpecialPage() ) { - list( $name, $par ) = SpecialPageFactory::resolveAlias( $title->getDBkey() ); + list( $name, $par ) = $services->getSpecialPageFactory()-> + resolveAlias( $title->getDBkey() ); # Use the language name for log titles, rather than Log/X if ( $name == 'Log' ) { diff --git a/includes/logging/ProtectLogFormatter.php b/includes/logging/ProtectLogFormatter.php index 02a6972003..ba02457319 100644 --- a/includes/logging/ProtectLogFormatter.php +++ b/includes/logging/ProtectLogFormatter.php @@ -150,9 +150,10 @@ class ProtectLogFormatter extends LogFormatter { public function formatParametersForApi() { $ret = parent::formatParametersForApi(); if ( isset( $ret['details'] ) && is_array( $ret['details'] ) ) { + $contLang = MediaWikiServices::getInstance()->getContentLanguage(); foreach ( $ret['details'] as &$detail ) { if ( isset( $detail['expiry'] ) ) { - $detail['expiry'] = MediaWikiServices::getInstance()->getContentLanguage()-> + $detail['expiry'] = $contLang-> formatExpiry( $detail['expiry'], TS_ISO_8601, 'infinite' ); } } diff --git a/includes/mail/MailAddress.php b/includes/mail/MailAddress.php index 1b66c389b3..000bbe316b 100644 --- a/includes/mail/MailAddress.php +++ b/includes/mail/MailAddress.php @@ -46,22 +46,14 @@ class MailAddress { public $address; /** - * @param string $address String with an email address, or a User object + * @param string $address String with an email address * @param string|null $name Human-readable name if a string address is given * @param string|null $realName Human-readable real name if a string address is given */ function __construct( $address, $name = null, $realName = null ) { - if ( is_object( $address ) && $address instanceof User ) { - // Old calling format, now deprecated - wfDeprecated( __METHOD__ . ' with a User object', '1.24' ); - $this->address = $address->getEmail(); - $this->name = $address->getName(); - $this->realName = $address->getRealName(); - } else { - $this->address = strval( $address ); - $this->name = strval( $name ); - $this->realName = strval( $realName ); - } + $this->address = strval( $address ); + $this->name = strval( $name ); + $this->realName = strval( $realName ); } /** diff --git a/includes/media/JpegHandler.php b/includes/media/JpegHandler.php index 287c198c57..91bed4a82c 100644 --- a/includes/media/JpegHandler.php +++ b/includes/media/JpegHandler.php @@ -259,13 +259,13 @@ class JpegHandler extends ExifBitmapHandler { // Make a regex out of the source data to match it to an array of color // spaces in a case-insensitive way - $colorSpaceRegex = '/'.preg_quote( $data[0], '/' ).'/i'; + $colorSpaceRegex = '/' . preg_quote( $data[0], '/' ) . '/i'; if ( empty( preg_grep( $colorSpaceRegex, $colorSpaces ) ) ) { // We can't establish that this file matches the color space, don't process it return false; } - $profileRegex = '/'.preg_quote( $data[1], '/' ).'/i'; + $profileRegex = '/' . preg_quote( $data[1], '/' ) . '/i'; if ( empty( preg_grep( $profileRegex, $oldProfileStrings ) ) ) { // We can't establish that this file has the expected ICC profile, don't process it return false; diff --git a/includes/page/Article.php b/includes/page/Article.php index 3a7b18e4c0..6a42d58c28 100644 --- a/includes/page/Article.php +++ b/includes/page/Article.php @@ -20,6 +20,8 @@ * @file */ use MediaWiki\MediaWikiServices; +use MediaWiki\Storage\MutableRevisionRecord; +use MediaWiki\Storage\RevisionRecord; /** * Class for viewing MediaWiki article and history. @@ -33,47 +35,84 @@ use MediaWiki\MediaWikiServices; * moved to separate EditPage and HTMLFileCache classes. */ class Article implements Page { - /** @var IContextSource The context this Article is executed in */ + /** + * @var IContextSource|null The context this Article is executed in. + * If null, RequestContext::getMain() is used. + */ protected $mContext; - /** @var WikiPage The WikiPage object of this instance */ + /** @var WikiPage|null The WikiPage object of this instance */ protected $mPage; - /** @var ParserOptions ParserOptions object for $wgUser articles */ - public $mParserOptions; - /** - * @var string Text of the revision we are working on - * @todo BC cruft + * @var ParserOptions|null ParserOptions object for $wgUser articles. + * Initialized by getParserOptions by calling $this->mPage->makeParserOptions(). */ - public $mContent; + public $mParserOptions; /** - * @var Content Content of the revision we are working on + * @var Content|null Content of the main slot of $this->mRevision. + * @note This variable is read only, setting it has no effect. + * Extensions that wish to override the output of Article::view should use a hook. + * @todo MCR: Remove in 1.33 + * @deprecated since 1.32 * @since 1.21 */ public $mContentObject; - /** @var bool Is the content ($mContent) already loaded? */ + /** + * @var bool Is the target revision loaded? Set by fetchRevisionRecord(). + * + * @deprecated since 1.32. Whether content has been loaded should not be relevant to + * code outside this class. + */ public $mContentLoaded = false; - /** @var int|null The oldid of the article that is to be shown, 0 for the current revision */ + /** + * @var int|null The oldid of the article that was requested to be shown, + * 0 for the current revision. + * @see $mRevIdFetched + */ public $mOldId; - /** @var Title Title from which we were redirected here */ + /** @var Title|null Title from which we were redirected here, if any. */ public $mRedirectedFrom = null; /** @var string|bool URL to redirect to or false if none */ public $mRedirectUrl = false; - /** @var int Revision ID of revision we are working on */ + /** + * @var int Revision ID of revision that was loaded. + * @see $mOldId + * @deprecated since 1.32, use getRevIdFetched() instead. + */ public $mRevIdFetched = 0; - /** @var Revision Revision we are working on */ + /** + * @var Status|null represents the outcome of fetchRevisionRecord(). + * $fetchResult->value is the RevisionRecord object, if the operation was successful. + * + * The information in $fetchResult is duplicated by the following deprecated public fields: + * $mRevIdFetched, $mContentLoaded. $mRevision (and $mContentObject) also typically duplicate + * information of the loaded revision, but may be overwritten by extensions or due to errors. + */ + private $fetchResult = null; + + /** + * @var Revision|null Revision to be shown. Initialized by getOldIDFromRequest() + * or fetchContentObject(). Normally loaded from the database, but may be replaced + * by an extension, or be a fake representing an error message or some such. + * While the output of Article::view is typically based on this revision, + * it may be overwritten by error messages or replaced by extensions. + */ public $mRevision = null; - /** @var ParserOutput */ - public $mParserOutput; + /** + * @var ParserOutput|null|false The ParserOutput generated for viewing the page, + * initialized by view(). If no ParserOutput could be generated, this is set to false. + * @deprecated since 1.32 + */ + public $mParserOutput = null; /** * @var bool Whether render() was called. With the way subclasses work @@ -119,7 +158,7 @@ class Article implements Page { */ public static function newFromTitle( $title, IContextSource $context ) { if ( NS_MEDIA == $title->getNamespace() ) { - // FIXME: where should this go? + // XXX: This should not be here, but where should it go? $title = Title::makeTitle( NS_FILE, $title->getDBkey() ); } @@ -201,6 +240,11 @@ class Article implements Page { $this->mRedirectedFrom = null; # Title object if set $this->mRevIdFetched = 0; $this->mRedirectUrl = false; + $this->mRevision = null; + $this->mContentObject = null; + $this->fetchResult = null; + + // TODO hard-deprecate direct access to public fields $this->mPage->clear(); } @@ -216,25 +260,15 @@ class Article implements Page { * 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 + * @deprecated since 1.32, use getRevisionFetched() or fetchRevisionRecord() instead. + * + * @return Content * * @since 1.21 */ protected function getContentObject() { if ( $this->mPage->getId() === 0 ) { - # If this is a MediaWiki:x message, then load the messages - # and return the message value for x. - if ( $this->getTitle()->getNamespace() == NS_MEDIAWIKI ) { - $text = $this->getTitle()->getDefaultMessageText(); - if ( $text === false ) { - $text = ''; - } - - $content = ContentHandler::makeContent( $text, $this->getTitle() ); - } else { - $message = $this->getContext()->getUser()->isLoggedIn() ? 'noarticletext' : 'noarticletextanon'; - $content = new MessageContent( $message, null, 'parsemag' ); - } + $content = $this->getSubstituteContent(); } else { $this->fetchContentObject(); $content = $this->mContentObject; @@ -244,7 +278,49 @@ class Article implements Page { } /** - * @return int The oldid of the article that is to be shown, 0 for the current revision + * Returns Content object to use when the page does not exist. + * + * @return Content + */ + private function getSubstituteContent() { + # If this is a MediaWiki:x message, then load the messages + # and return the message value for x. + if ( $this->getTitle()->getNamespace() == NS_MEDIAWIKI ) { + $text = $this->getTitle()->getDefaultMessageText(); + if ( $text === false ) { + $text = ''; + } + + $content = ContentHandler::makeContent( $text, $this->getTitle() ); + } else { + $message = $this->getContext()->getUser()->isLoggedIn() ? 'noarticletext' : 'noarticletextanon'; + $content = new MessageContent( $message, null, 'parsemag' ); + } + + return $content; + } + + /** + * Returns ParserOutput to use when a page does not exist. In some cases, we still want to show + * "virtual" content, e.g. in the MediaWiki namespace, or in the File namespace for non-local + * files. + * + * @param ParserOptions $options + * + * @return ParserOutput + */ + protected function getEmptyPageParserOutput( ParserOptions $options ) { + $content = $this->getSubstituteContent(); + + return $content->getParserOutput( $this->getTitle(), 0, $options ); + } + + /** + * @see getOldIDFromRequest() + * @see getRevIdFetched() + * + * @return int The oldid of the article that is was requested in the constructor or via the + * context's WebRequest. */ public function getOldID() { if ( is_null( $this->mOldId ) ) { @@ -279,7 +355,7 @@ class Article implements Page { if ( $this->mRevision !== null ) { // Revision title doesn't match the page title given? if ( $this->mPage->getId() != $this->mRevision->getPage() ) { - $function = get_class( $this->mPage ). '::newFromID'; + $function = get_class( $this->mPage ) . '::newFromID'; $this->mPage = $function( $this->mRevision->getPage() ); } } @@ -302,6 +378,8 @@ class Article implements Page { } } + $this->mRevIdFetched = $this->mRevision ? $this->mRevision->getId() : 0; + return $oldid; } @@ -309,6 +387,7 @@ class Article implements Page { * Get text content object * Does *NOT* follow redirects. * @todo When is this null? + * @deprecated since 1.32, use fetchRevisionRecord() instead. * * @note Code that wants to retrieve page content from the database should * use WikiPage::getContent(). @@ -318,74 +397,139 @@ class Article implements Page { * @since 1.21 */ protected function fetchContentObject() { - if ( $this->mContentLoaded ) { - return $this->mContentObject; + if ( !$this->mContentLoaded ) { + $this->fetchRevisionRecord(); + } + + return $this->mContentObject; + } + + /** + * Fetches the revision to work on. + * The revision is typically loaded from the database, but may also be a fake representing + * an error message or content supplied by an extension. Refer to $this->fetchResult for + * the revision actually loaded from the database, and any errors encountered while doing + * that. + * + * @return RevisionRecord|null + */ + protected function fetchRevisionRecord() { + if ( $this->fetchResult ) { + return $this->mRevision ? $this->mRevision->getRevisionRecord() : null; } $this->mContentLoaded = true; - $this->mContent = null; + $this->mContentObject = null; $oldid = $this->getOldID(); - # Pre-fill content with error message so that if something - # fails we'll have something telling us what we intended. - // XXX: this isn't page content but a UI message. horrible. - $this->mContentObject = new MessageContent( 'missing-revision', [ $oldid ] ); + // $this->mRevision might already be fetched by getOldIDFromRequest() + if ( !$this->mRevision ) { + if ( !$oldid ) { + $this->mRevision = $this->mPage->getRevision(); - if ( $oldid ) { - # $this->mRevision might already be fetched by getOldIDFromRequest() - if ( !$this->mRevision ) { + if ( !$this->mRevision ) { + wfDebug( __METHOD__ . " failed to find page data for title " . + $this->getTitle()->getPrefixedText() . "\n" ); + + // Just for sanity, output for this case is done by showMissingArticle(). + $this->fetchResult = Status::newFatal( 'noarticletext' ); + $this->applyContentOverride( $this->makeFetchErrorContent() ); + return null; + } + } else { $this->mRevision = Revision::newFromId( $oldid ); + if ( !$this->mRevision ) { - wfDebug( __METHOD__ . " failed to retrieve specified revision, id $oldid\n" ); - return false; + wfDebug( __METHOD__ . " failed to load revision, rev_id $oldid\n" ); + + $this->fetchResult = Status::newFatal( 'missing-revision', $oldid ); + $this->applyContentOverride( $this->makeFetchErrorContent() ); + return null; } } - } else { - $oldid = $this->mPage->getLatest(); - if ( !$oldid ) { - wfDebug( __METHOD__ . " failed to find page data for title " . - $this->getTitle()->getPrefixedText() . "\n" ); - return false; - } + } + + $this->mRevIdFetched = $this->mRevision->getId(); + $this->fetchResult = Status::newGood( $this->mRevision ); + + if ( !$this->mRevision->userCan( Revision::DELETED_TEXT, $this->getContext()->getUser() ) ) { + wfDebug( __METHOD__ . " failed to retrieve content of revision " . + $this->mRevision->getId() . "\n" ); + + // Just for sanity, output for this case is done by showDeletedRevisionHeader(). + $this->fetchResult = Status::newFatal( 'rev-deleted-text-permission' ); + $this->applyContentOverride( $this->makeFetchErrorContent() ); + return null; + } + + if ( Hooks::isRegistered( 'ArticleAfterFetchContentObject' ) ) { + $contentObject = $this->mRevision->getContent( + Revision::FOR_THIS_USER, + $this->getContext()->getUser() + ); - # Update error message with correct oldid - $this->mContentObject = new MessageContent( 'missing-revision', [ $oldid ] ); + $hookContentObject = $contentObject; - $this->mRevision = $this->mPage->getRevision(); + // Avoid PHP 7.1 warning of passing $this by reference + $articlePage = $this; + + Hooks::run( + 'ArticleAfterFetchContentObject', + [ &$articlePage, &$hookContentObject ], + '1.32' + ); - if ( !$this->mRevision ) { - wfDebug( __METHOD__ . " failed to retrieve current page, rev_id $oldid\n" ); - return false; + if ( $hookContentObject !== $contentObject ) { + // A hook handler is trying to override the content + $this->applyContentOverride( $hookContentObject ); } } - // @todo FIXME: Horrible, horrible! This content-loading interface just plain sucks. - // We should instead work with the Revision object when we need it... - // Loads if user is allowed - $content = $this->mRevision->getContent( + // For B/C only + $this->mContentObject = $this->mRevision->getContent( Revision::FOR_THIS_USER, $this->getContext()->getUser() ); - if ( !$content ) { - wfDebug( __METHOD__ . " failed to retrieve content of revision " . - $this->mRevision->getId() . "\n" ); - return false; + return $this->mRevision->getRevisionRecord(); + } + + /** + * Returns a Content object representing any error in $this->fetchContent, or null + * if there is no such error. + * + * @return Content|null + */ + private function makeFetchErrorContent() { + if ( !$this->fetchResult || $this->fetchResult->isOK() ) { + return null; } - $this->mContentObject = $content; - $this->mRevIdFetched = $this->mRevision->getId(); + return new MessageContent( $this->fetchResult->getMessage() ); + } - // Avoid PHP 7.1 warning of passing $this by reference - $articlePage = $this; + /** + * Applies a content override by constructing a fake Revision object and assigning + * it to mRevision. The fake revision will not have a user, timestamp or summary set. + * + * This mechanism exists mainly to accommodate extensions that use the + * ArticleAfterFetchContentObject. Once that hook has been removed, there should no longer + * be a need for a fake revision object. fetchRevisionRecord() presently also uses this mechanism + * to report errors, but that could be changed to use $this->fetchResult instead. + * + * @param Content $override Content to be used instead of the actual page content, + * coming from an extension or representing an error message. + */ + private function applyContentOverride( Content $override ) { + // Construct a fake revision + $rev = new MutableRevisionRecord( $this->getTitle() ); + $rev->setContent( 'main', $override ); - Hooks::run( - 'ArticleAfterFetchContentObject', - [ &$articlePage, &$this->mContentObject ] - ); + $this->mRevision = new Revision( $rev ); - return $this->mContentObject; + // For B/C only + $this->mContentObject = $override; } /** @@ -404,25 +548,32 @@ class Article implements Page { /** * Get the fetched Revision object depending on request parameters or null - * on failure. + * on failure. The revision returned may be a fake representing an error message or + * wrapping content supplied by an extension. Refer to $this->fetchResult for the + * revision actually loaded from the database. * * @since 1.19 * @return Revision|null */ public function getRevisionFetched() { - $this->fetchContentObject(); + $this->fetchRevisionRecord(); - return $this->mRevision; + if ( $this->fetchResult->isOK() ) { + return $this->mRevision; + } } /** * Use this to fetch the rev ID used on page views * + * Before fetchRevisionRecord was called, this returns the page's latest revision, + * regardless of what getOldID() returns. + * * @return int Revision ID of last article revision */ public function getRevIdFetched() { - if ( $this->mRevIdFetched ) { - return $this->mRevIdFetched; + if ( $this->fetchResult && $this->fetchResult->isOK() ) { + return $this->fetchResult->value->getId(); } else { return $this->mPage->getLatest(); } @@ -558,11 +709,9 @@ class Article implements Page { } break; case 3: - # This will set $this->mRevision if needed - $this->fetchContentObject(); - # Are we looking at an old revision - if ( $oldid && $this->mRevision ) { + $rev = $this->fetchRevisionRecord(); + if ( $oldid && $this->fetchResult->isOK() ) { $this->setOldSubtitle( $oldid ); if ( !$this->showDeletedRevisionHeader() ) { @@ -586,10 +735,21 @@ class Article implements Page { "
\n$1\n
", 'clearyourcache' ); + } elseif ( !Hooks::run( 'ArticleRevisionViewCustom', [ + $rev, + $this->getTitle(), + $oldid, + $outputPage, + ] ) + ) { + // NOTE: sync with hooks called in DifferenceEngine::renderNewRevision() + // Allow extensions do their own custom view for certain pages + $outputDone = true; } elseif ( !Hooks::run( 'ArticleContentViewCustom', - [ $this->fetchContentObject(), $this->getTitle(), $outputPage ] ) + [ $this->fetchContentObject(), $this->getTitle(), $outputPage ], '1.32' ) ) { - # Allow extensions do their own custom view for certain pages + // NOTE: sync with hooks called in DifferenceEngine::renderNewRevision() + // Allow extensions do their own custom view for certain pages $outputDone = true; } break; @@ -597,12 +757,32 @@ class Article implements Page { # Run the parse, protected by a pool counter wfDebug( __METHOD__ . ": doing uncached parse\n" ); - $content = $this->getContentObject(); - $poolArticleView = new PoolWorkArticleView( $this->getPage(), $parserOptions, - $this->getRevIdFetched(), $useParserCache, $content ); + $rev = $this->fetchRevisionRecord(); + $error = null; - if ( !$poolArticleView->execute() ) { + if ( $rev ) { + $poolArticleView = new PoolWorkArticleView( + $this->getPage(), + $parserOptions, + $this->getRevIdFetched(), + $useParserCache, + $rev + ); + $ok = $poolArticleView->execute(); $error = $poolArticleView->getError(); + $this->mParserOutput = $poolArticleView->getParserOutput() ?: null; + + # Don't cache a dirty ParserOutput object + if ( $poolArticleView->getIsDirty() ) { + $outputPage->setCdnMaxage( 0 ); + $outputPage->addHTML( "\n" ); + } + } else { + $ok = false; + } + + if ( !$ok ) { if ( $error ) { $outputPage->clearHTML(); // for release() errors $outputPage->enableClientCache( false ); @@ -615,18 +795,13 @@ class Article implements Page { return; } - $this->mParserOutput = $poolArticleView->getParserOutput(); - $outputPage->addParserOutput( $this->mParserOutput, $poOptions ); - if ( $content->getRedirectTarget() ) { - $outputPage->addSubtitle( "" . - $this->getContext()->msg( 'redirectpagesub' )->parse() . "" ); + if ( $this->mParserOutput ) { + $outputPage->addParserOutput( $this->mParserOutput, $poOptions ); } - # Don't cache a dirty ParserOutput object - if ( $poolArticleView->getIsDirty() ) { - $outputPage->setCdnMaxage( 0 ); - $outputPage->addHTML( "\n" ); + if ( $rev && $this->getRevisionRedirectTarget( $rev ) ) { + $outputPage->addSubtitle( "" . + $this->getContext()->msg( 'redirectpagesub' )->parse() . "" ); } $outputDone = true; @@ -637,11 +812,14 @@ class Article implements Page { } } - # Get the ParserOutput actually *displayed* here. - # Note that $this->mParserOutput is the *current*/oldid version output. + // Get the ParserOutput actually *displayed* here. + // Note that $this->mParserOutput is the *current*/oldid version output. + // Note that the ArticleViewHeader hook is allowed to set $outputDone to a + // ParserOutput instance. $pOutput = ( $outputDone instanceof ParserOutput ) + // phpcs:ignore MediaWiki.Usage.NestedInlineTernary.UnparenthesizedTernary -- FIXME T203805 ? $outputDone // object fetched by hook - : $this->mParserOutput; + : $this->mParserOutput ?: null; // ParserOutput or null, avoid false # Adjust title for main page & pages with displaytitle if ( $pOutput ) { @@ -664,12 +842,12 @@ class Article implements Page { $outputPage->adaptCdnTTL( $this->mPage->getTimestamp(), IExpiringStore::TTL_DAY ); # Check for any __NOINDEX__ tags on the page using $pOutput - $policy = $this->getRobotPolicy( 'view', $pOutput ); + $policy = $this->getRobotPolicy( 'view', $pOutput ?: null ); $outputPage->setIndexPolicy( $policy['index'] ); - $outputPage->setFollowPolicy( $policy['follow'] ); + $outputPage->setFollowPolicy( $policy['follow'] ); // FIXME: test this $this->showViewFooter(); - $this->mPage->doViewUpdates( $user, $oldid ); + $this->mPage->doViewUpdates( $user, $oldid ); // FIXME: test this # Load the postEdit module if the user just saved this revision # See also EditPage::setPostEditCookie @@ -680,19 +858,34 @@ class Article implements Page { # Clear the cookie. This also prevents caching of the response. $request->response()->clearCookie( $cookieKey ); $outputPage->addJsConfigVars( 'wgPostEdit', $postEdit ); - $outputPage->addModules( 'mediawiki.action.view.postEdit' ); + $outputPage->addModules( 'mediawiki.action.view.postEdit' ); // FIXME: test this } } + /** + * @param RevisionRecord $revision + * @return null|Title + */ + private function getRevisionRedirectTarget( RevisionRecord $revision ) { + // TODO: find a *good* place for the code that determines the redirect target for + // a given revision! + // NOTE: Use main slot content. Compare code in DerivedPageDataUpdater::revisionIsRedirect. + $content = $revision->getContent( 'main' ); + return $content ? $content->getRedirectTarget() : null; + } + /** * Adjust title for pages with displaytitle, -{T|}- or language conversion * @param ParserOutput $pOutput */ public function adjustDisplayTitle( ParserOutput $pOutput ) { + $out = $this->getContext()->getOutput(); + # Adjust the title if it was set by displaytitle, -{T|}- or language conversion $titleText = $pOutput->getTitleText(); if ( strval( $titleText ) !== '' ) { - $this->getContext()->getOutput()->setPageTitle( $titleText ); + $out->setPageTitle( $titleText ); + $out->setDisplayTitle( $titleText ); } } @@ -1248,7 +1441,9 @@ class Article implements Page { # Show error message $oldid = $this->getOldID(); if ( !$oldid && $title->getNamespace() === NS_MEDIAWIKI && $title->hasSourceText() ) { - $outputPage->addParserOutput( $this->getContentObject()->getParserOutput( $title ) ); + // use fake Content object for system message + $parserOptions = ParserOptions::newCanonical( 'canonical' ); + $outputPage->addParserOutput( $this->getEmptyPageParserOutput( $parserOptions ) ); } else { if ( $oldid ) { $text = wfMessage( 'missing-revision', $oldid )->plain(); @@ -1657,7 +1852,7 @@ class Article implements Page { __METHOD__ ); - // @todo FIXME: i18n issue/patchwork message + // @todo i18n issue/patchwork message $context->getOutput()->addHTML( '' . $context->msg( 'historywarning' )->numParams( $revisions )->parse() . @@ -1684,7 +1879,7 @@ class Article implements Page { /** * Output deletion confirmation dialog - * @todo FIXME: Move to another file? + * @todo Move to another file? * @param string $reason Prefilled reason */ public function confirmDelete( $reason ) { @@ -1961,7 +2156,7 @@ class Article implements Page { /** * Lightweight method to get the parser output for a page, checking the parser cache - * and so on. Doesn't consider most of the stuff that WikiPage::view is forced to + * and so on. Doesn't consider most of the stuff that Article::view() is forced to * consider, so it's not appropriate to use there. * * @since 1.16 (r52326) for LiquidThreads @@ -2110,8 +2305,13 @@ class Article implements Page { * Call to WikiPage function for backwards compatibility. * @see WikiPage::doDeleteUpdates */ - public function doDeleteUpdates( $id, Content $content = null ) { - return $this->mPage->doDeleteUpdates( $id, $content ); + public function doDeleteUpdates( + $id, + Content $content = null, + $revision = null, + User $user = null + ) { + $this->mPage->doDeleteUpdates( $id, $content, $revision, $user ); } /** diff --git a/includes/page/ImageHistoryList.php b/includes/page/ImageHistoryList.php index 1f87d93360..0a07c6884f 100644 --- a/includes/page/ImageHistoryList.php +++ b/includes/page/ImageHistoryList.php @@ -266,7 +266,7 @@ class ImageHistoryList extends ContextSource { } else { $row .= '' . Linker::formatComment( $description, $this->title ) . ''; + '">' . Linker::formatComment( $description, $this->title ) . ''; } $rowClass = null; diff --git a/includes/page/ImagePage.php b/includes/page/ImagePage.php index 58f25d4650..d3d6da7935 100644 --- a/includes/page/ImagePage.php +++ b/includes/page/ImagePage.php @@ -272,18 +272,20 @@ class ImagePage extends Article { } /** - * Overloading Article's getContentObject method. + * Overloading Article's getEmptyPageParserOutput method. * * Omit noarticletext if sharedupload; text will be fetched from the * shared upload server if possible. - * @return string + * + * @param ParserOptions $options + * @return ParserOutput */ - public function getContentObject() { + public function getEmptyPageParserOutput( ParserOptions $options ) { $this->loadFile(); if ( $this->mPage->getFile() && !$this->mPage->getFile()->isLocal() && 0 == $this->getId() ) { - return null; + return new ParserOutput(); } - return parent::getContentObject(); + return parent::getEmptyPageParserOutput( $options ); } private function getLanguageForRendering( WebRequest $request, File $file ) { diff --git a/includes/page/WikiPage.php b/includes/page/WikiPage.php index c3df0e5b47..74e31791e9 100644 --- a/includes/page/WikiPage.php +++ b/includes/page/WikiPage.php @@ -23,11 +23,13 @@ use MediaWiki\Edit\PreparedEdit; use MediaWiki\Logger\LoggerFactory; use MediaWiki\MediaWikiServices; +use MediaWiki\Revision\RevisionRenderer; use MediaWiki\Storage\DerivedPageDataUpdater; use MediaWiki\Storage\PageUpdater; use MediaWiki\Storage\RevisionRecord; use MediaWiki\Storage\RevisionSlotsUpdate; use MediaWiki\Storage\RevisionStore; +use MediaWiki\Storage\SlotRecord; use Wikimedia\Assert\Assert; use Wikimedia\Rdbms\FakeResultWrapper; use Wikimedia\Rdbms\IDatabase; @@ -223,6 +225,13 @@ class WikiPage implements Page, IDBAccessObject { return MediaWikiServices::getInstance()->getRevisionStore(); } + /** + * @return RevisionRenderer + */ + private function getRevisionRenderer() { + return MediaWikiServices::getInstance()->getRevisionRenderer(); + } + /** * @return ParserCache */ @@ -761,6 +770,18 @@ class WikiPage implements Page, IDBAccessObject { return null; } + /** + * Get the latest revision + * @return RevisionRecord|null + */ + public function getRevisionRecord() { + $this->loadLastEdit(); + if ( $this->mLastRevision ) { + return $this->mLastRevision->getRevisionRecord(); + } + return null; + } + /** * Get the content of the current revision. No side-effects... * @@ -931,6 +952,7 @@ class WikiPage implements Page, IDBAccessObject { // links. $hasLinks = (bool)count( $editInfo->output->getLinks() ); } else { + // NOTE: keep in sync with revisionRenderer::getLinkCount $hasLinks = (bool)wfGetDB( DB_REPLICA )->selectField( 'pagelinks', 1, [ 'pl_from' => $this->getId() ], __METHOD__ ); } @@ -1155,6 +1177,8 @@ class WikiPage implements Page, IDBAccessObject { * The parser cache will be used if possible. Cache misses that result * in parser runs are debounced with PoolCounter. * + * XXX merge this with updateParserCache()? + * * @since 1.19 * @param ParserOptions $parserOptions ParserOptions to use for the parse operation * @param null|int $oldid Revision ID to get the text from, passing null or 0 will @@ -1630,11 +1654,12 @@ class WikiPage implements Page, IDBAccessObject { $derivedDataUpdater = new DerivedPageDataUpdater( $this, // NOTE: eventually, PageUpdater should not know about WikiPage $this->getRevisionStore(), + $this->getRevisionRenderer(), $this->getParserCache(), JobQueueGroup::singleton(), MessageCache::singleton(), MediaWikiServices::getInstance()->getContentLanguage(), - LoggerFactory::getInstance( 'SaveParse' ) + MediaWikiServices::getInstance()->getDBLoadBalancerFactory() ); $derivedDataUpdater->setRcWatchCategoryMembership( $wgRCWatchCategoryMembership ); @@ -1948,7 +1973,13 @@ class WikiPage implements Page, IDBAccessObject { $updater->prepareContent( $user, $slots, $useCache ); if ( $revision ) { - $updater->prepareUpdate( $revision ); + $updater->prepareUpdate( + $revision, + [ + 'causeAction' => 'prepare-edit', + 'causeAgent' => $user->getName(), + ] + ); } } @@ -1961,7 +1992,7 @@ class WikiPage implements Page, IDBAccessObject { * Purges pages that include this page if the text was changed here. * Every 100th edit, prune the recent changes table. * - * @deprecated since 1.32, use PageUpdater::doEditUpdates instead. + * @deprecated since 1.32, use PageUpdater::doUpdates instead. * * @param Revision $revision * @param User $user User object that did the revision @@ -1977,8 +2008,17 @@ class WikiPage implements Page, IDBAccessObject { * - null: if created is false, don't update the article count; if created * is true, do update the article count * - 'no-change': don't update the article count, ever + * - causeAction: an arbitrary string identifying the reason for the update. + * See DataUpdate::getCauseAction(). (default 'edit-page') + * - causeAgent: name of the user who caused the update. See DataUpdate::getCauseAgent(). + * (string, defaults to the passed user) */ public function doEditUpdates( Revision $revision, User $user, array $options = [] ) { + $options += [ + 'causeAction' => 'edit-page', + 'causeAgent' => $user->getName(), + ]; + $revision = $revision->getRevisionRecord(); $updater = $this->getDerivedDataUpdater( $user, $revision ); @@ -1988,6 +2028,76 @@ class WikiPage implements Page, IDBAccessObject { $updater->doUpdates(); } + /** + * Update the parser cache. + * + * @note This is a temporary workaround until there is a proper data updater class. + * It will become deprecated soon. + * + * @param array $options + * - causeAction: an arbitrary string identifying the reason for the update. + * See DataUpdate::getCauseAction(). (default 'edit-page') + * - causeAgent: name of the user who caused the update (string, defaults to the + * user who created the revision) + * @since 1.32 + */ + public function updateParserCache( array $options = [] ) { + $revision = $this->getRevisionRecord(); + if ( !$revision || !$revision->getId() ) { + LoggerFactory::getInstance( 'wikipage' )->info( + __METHOD__ . 'called with ' . ( $revision ? 'unsaved' : 'no' ) . ' revision' + ); + return; + } + $user = User::newFromIdentity( $revision->getUser( RevisionRecord::RAW ) ); + + $updater = $this->getDerivedDataUpdater( $user, $revision ); + $updater->prepareUpdate( $revision, $options ); + $updater->doParserCacheUpdate(); + } + + /** + * Do secondary data updates (such as updating link tables). + * Secondary data updates are only a small part of the updates needed after saving + * a new revision; normally PageUpdater::doUpdates should be used instead (which includes + * secondary data updates). This method is provided for partial purges. + * + * @note This is a temporary workaround until there is a proper data updater class. + * It will become deprecated soon. + * + * @param array $options + * - recursive (bool, default true): whether to do a recursive update (update pages that + * depend on this page, e.g. transclude it). This will set the $recursive parameter of + * Content::getSecondaryDataUpdates. Typically this should be true unless the update + * was something that did not really change the page, such as a null edit. + * - triggeringUser: The user triggering the update (UserIdentity, defaults to the + * user who created the revision) + * - causeAction: an arbitrary string identifying the reason for the update. + * See DataUpdate::getCauseAction(). (default 'unknown') + * - causeAgent: name of the user who caused the update (string, default 'unknown') + * - defer: one of the DeferredUpdates constants, or false to run immediately (default: false). + * Note that even when this is set to false, some updates might still get deferred (as + * some update might directly add child updates to DeferredUpdates). + * - transactionTicket: a transaction ticket from LBFactory::getEmptyTransactionTicket(), + * only when defer is false (default: null) + * @since 1.32 + */ + public function doSecondaryDataUpdates( array $options = [] ) { + $options['recursive'] = $options['recursive'] ?? true; + $revision = $this->getRevisionRecord(); + if ( !$revision || !$revision->getId() ) { + LoggerFactory::getInstance( 'wikipage' )->info( + __METHOD__ . 'called with ' . ( $revision ? 'unsaved' : 'no' ) . ' revision' + ); + return; + } + $user = User::newFromIdentity( $revision->getUser( RevisionRecord::RAW ) ); + + $updater = $this->getDerivedDataUpdater( $user, $revision ); + $updater->prepareUpdate( $revision, $options ); + $updater->doSecondaryDataUpdates( $options ); + } + /** * Update the article's restriction field, and leave a log entry. * This works for protection both existing and non-existing pages. @@ -2367,10 +2477,11 @@ class WikiPage implements Page, IDBAccessObject { public function protectDescriptionLog( array $limit, array $expiry ) { $protectDescriptionLog = ''; + $dirMark = MediaWikiServices::getInstance()->getContentLanguage()->getDirMark(); foreach ( array_filter( $limit ) as $action => $restrictions ) { $expiryText = $this->formatExpiry( $expiry[$action] ); $protectDescriptionLog .= - MediaWikiServices::getInstance()->getContentLanguage()->getDirMark() . + $dirMark . "[$action=$restrictions] ($expiryText)"; } @@ -2710,15 +2821,21 @@ class WikiPage implements Page, IDBAccessObject { * Do some database updates after deletion * * @param int $id The page_id value of the page being deleted - * @param Content|null $content Optional page content to be used when determining + * @param Content|null $content 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. - * @param Revision|null $revision The latest page revision + * @param RevisionRecord|Revision|null $revision The current page revision at the time of + * deletion, used when determining the required updates. This may be needed because + * $this->getRevision() may already return null when the page proper was deleted. * @param User|null $user The user that caused the deletion */ public function doDeleteUpdates( $id, Content $content = null, Revision $revision = null, User $user = null ) { + if ( $id !== $this->getId() ) { + throw new InvalidArgumentException( 'Mismatching page ID' ); + } + try { $countable = $this->isCountable(); } catch ( Exception $ex ) { @@ -2733,7 +2850,9 @@ class WikiPage implements Page, IDBAccessObject { ) ); // Delete pagelinks, update secondary indexes, etc - $updates = $this->getDeletionUpdates( $content ); + $updates = $this->getDeletionUpdates( + $revision ? $revision->getRevisionRecord() : $content + ); foreach ( $updates as $update ) { DeferredUpdates::addUpdate( $update ); } @@ -3151,6 +3270,9 @@ class WikiPage implements Page, IDBAccessObject { // Image redirects RepoGroup::singleton()->getLocalRepo()->invalidateImageRedirect( $title ); + + // Purge cross-wiki cache entities referencing this page + self::purgeInterwikiCheckKey( $title ); } /** @@ -3189,14 +3311,41 @@ class WikiPage implements Page, IDBAccessObject { // Clear file cache for this page only HTMLFileCache::clearFileCache( $title ); + // Purge ?action=info cache $revid = $revision ? $revision->getId() : null; DeferredUpdates::addCallableUpdate( function () use ( $title, $revid ) { InfoAction::invalidateCache( $title, $revid ); } ); + + // Purge cross-wiki cache entities referencing this page + self::purgeInterwikiCheckKey( $title ); } /**#@-*/ + /** + * Purge the check key for cross-wiki cache entries referencing this page + * + * @param Title $title + */ + private static function purgeInterwikiCheckKey( Title $title ) { + global $wgEnableScaryTranscluding; + + if ( !$wgEnableScaryTranscluding ) { + return; // @todo: perhaps this wiki is only used as a *source* for content? + } + + DeferredUpdates::addCallableUpdate( function () use ( $title ) { + $cache = MediaWikiServices::getInstance()->getMainWANObjectCache(); + $cache->resetCheckKey( + // Do not include the namespace since there can be multiple aliases to it + // due to different namespace text definitions on different wikis. This only + // means that some cache invalidations happen that are not strictly needed. + $cache->makeGlobalKey( 'interwiki-page', wfWikiID(), $title->getDBkey() ) + ); + } ); + } + /** * Returns a list of categories this page is a member of. * Results will include hidden categories @@ -3405,32 +3554,68 @@ class WikiPage implements Page, IDBAccessObject { * 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. + * @param RevisionRecord|Content|null $rev The revision being deleted. Also accepts a Content + * object for backwards compatibility. * @return DeferrableUpdate[] */ - 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. + public function getDeletionUpdates( $rev = null ) { + if ( !$rev ) { + wfDeprecated( __METHOD__ . ' without a RevisionRecord', '1.32' ); + try { - $content = $this->getContent( Revision::RAW ); + $rev = $this->getRevisionRecord(); } catch ( Exception $ex ) { // If we can't load the content, something is wrong. Perhaps that's why // the user is trying to delete the page, so let's not fail in that case. // Note that doDeleteArticleReal() will already have logged an issue with // loading the content. + wfDebug( __METHOD__ . ' failed to load current revision of page ' . $this->getId() ); } } - if ( !$content ) { - $updates = []; + if ( !$rev ) { + $slotContent = []; + } elseif ( $rev instanceof Content ) { + wfDeprecated( __METHOD__ . ' with a Content object instead of a RevisionRecord', '1.32' ); + + $slotContent = [ 'main' => $rev ]; } else { - $updates = $content->getDeletionUpdates( $this ); + $slotContent = array_map( function ( SlotRecord $slot ) { + return $slot->getContent( Revision::RAW ); + }, $rev->getSlots()->getSlots() ); } - Hooks::run( 'WikiPageDeletionUpdates', [ $this, $content, &$updates ] ); - return $updates; + $allUpdates = [ new LinksDeletionUpdate( $this ) ]; + + // NOTE: once Content::getDeletionUpdates() is removed, we only need to content + // model here, not the content object! + // TODO: consolidate with similar logic in DerivedPageDataUpdater::getSecondaryDataUpdates() + /** @var Content $content */ + foreach ( $slotContent as $role => $content ) { + $handler = $content->getContentHandler(); + + $updates = $handler->getDeletionUpdates( + $this->getTitle(), + $role + ); + $allUpdates = array_merge( $allUpdates, $updates ); + + // TODO: remove B/C hack in 1.32! + $legacyUpdates = $content->getDeletionUpdates( $this ); + + // HACK: filter out redundant and incomplete LinksDeletionUpdate + $legacyUpdates = array_filter( $legacyUpdates, function ( $update ) { + return !( $update instanceof LinksDeletionUpdate ); + } ); + + $allUpdates = array_merge( $allUpdates, $legacyUpdates ); + } + + Hooks::run( 'PageDeletionDataUpdates', [ $this->getTitle(), $rev, &$allUpdates ] ); + + // TODO: hard deprecate old hook in 1.33 + Hooks::run( 'WikiPageDeletionUpdates', [ $this, $content, &$allUpdates ] ); + return $allUpdates; } /** diff --git a/includes/pager/IndexPager.php b/includes/pager/IndexPager.php index b00ec3a893..05af4fdb84 100644 --- a/includes/pager/IndexPager.php +++ b/includes/pager/IndexPager.php @@ -80,6 +80,7 @@ abstract class IndexPager extends ContextSource implements Pager { public $mDefaultLimit = 50; public $mOffset, $mLimit; public $mQueryDone = false; + /** @var IDatabase */ public $mDb; public $mPastTheEndRow; @@ -472,7 +473,7 @@ abstract class IndexPager extends ContextSource implements Pager { } if ( in_array( $type, [ 'asc', 'desc' ] ) ) { - $attrs['title'] = wfMessage( $type == 'asc' ? 'sort-ascending' : 'sort-descending' )->text(); + $attrs['title'] = $this->msg( $type == 'asc' ? 'sort-ascending' : 'sort-descending' )->text(); } if ( $type ) { diff --git a/includes/parser/CoreParserFunctions.php b/includes/parser/CoreParserFunctions.php index ae7ca6d428..d44ac8c8f0 100644 --- a/includes/parser/CoreParserFunctions.php +++ b/includes/parser/CoreParserFunctions.php @@ -98,11 +98,6 @@ class CoreParserFunctions { $args = array_slice( func_get_args(), 2 ); $message = wfMessage( $part1, $args ) ->inLanguage( $parser->getOptions()->getUserLangObj() ); - if ( !$message->exists() ) { - // When message does not exists, the message name is surrounded by angle - // and can result in a tag, therefore escape the angles - return $message->escaped(); - } return [ $message->plain(), 'noparse' => false ]; } else { return [ 'found' => false ]; @@ -950,7 +945,8 @@ class CoreParserFunctions { } public static function special( $parser, $text ) { - list( $page, $subpage ) = SpecialPageFactory::resolveAlias( $text ); + list( $page, $subpage ) = MediaWikiServices::getInstance()->getSpecialPageFactory()-> + resolveAlias( $text ); if ( $page ) { $title = SpecialPage::getTitleFor( $page, $subpage ); return $title->getPrefixedText(); diff --git a/includes/parser/MWTidy.php b/includes/parser/MWTidy.php index 5788986f2e..a73c28cdba 100644 --- a/includes/parser/MWTidy.php +++ b/includes/parser/MWTidy.php @@ -71,6 +71,7 @@ class MWTidy { $config = $wgTidyConfig; } elseif ( $wgUseTidy ) { // b/c configuration + wfDeprecated( '$wgUseTidy', '1.26' ); $config = [ 'tidyConfigFile' => $wgTidyConf, 'debugComment' => $wgDebugTidy, @@ -86,6 +87,7 @@ class MWTidy { $config['driver'] = 'RaggettExternal'; } } else { + wfDeprecated( '$wgTidyConfig = null and $wgUseTidy = false', '1.26' ); return false; } self::$instance = self::factory( $config ); diff --git a/includes/parser/Parser.php b/includes/parser/Parser.php index 1fc2f9272b..dc2bb0c6b3 100644 --- a/includes/parser/Parser.php +++ b/includes/parser/Parser.php @@ -22,6 +22,7 @@ */ use MediaWiki\Linker\LinkRenderer; use MediaWiki\MediaWikiServices; +use MediaWiki\Special\SpecialPageFactory; use Wikimedia\ScopedCallback; /** @@ -269,16 +270,20 @@ class Parser { /** @var ParserFactory */ private $factory; + /** @var SpecialPageFactory */ + private $specialPageFactory; + /** * @param array $conf See $wgParserConf documentation * @param MagicWordFactory|null $magicWordFactory * @param Language|null $contLang Content language * @param ParserFactory|null $factory * @param string|null $urlProtocols As returned from wfUrlProtocols() + * @param SpecialPageFactory|null $spFactory */ public function __construct( array $conf = [], MagicWordFactory $magicWordFactory = null, Language $contLang = null, - ParserFactory $factory = null, $urlProtocols = null + ParserFactory $factory = null, $urlProtocols = null, SpecialPageFactory $spFactory = null ) { $this->mConf = $conf; $this->mUrlProtocols = $urlProtocols ?? wfUrlProtocols(); @@ -287,8 +292,8 @@ class Parser { self::EXT_LINK_URL_CLASS . '*)\p{Zs}*([^\]\\x00-\\x08\\x0a-\\x1F\\x{FFFD}]*?)\]/Su'; if ( isset( $conf['preprocessorClass'] ) ) { $this->mPreprocessorClass = $conf['preprocessorClass']; - } elseif ( defined( 'HPHP_VERSION' ) ) { - # Preprocessor_Hash is much faster than Preprocessor_DOM under HipHop + } elseif ( wfIsHHVM() ) { + # Under HHVM Preprocessor_Hash is much faster than Preprocessor_DOM $this->mPreprocessorClass = Preprocessor_Hash::class; } elseif ( extension_loaded( 'domxml' ) ) { # PECL extension that conflicts with the core DOM extension (T15770) @@ -301,12 +306,14 @@ class Parser { } wfDebug( __CLASS__ . ": using preprocessor: {$this->mPreprocessorClass}\n" ); + $services = MediaWikiServices::getInstance(); $this->magicWordFactory = $magicWordFactory ?? - MediaWikiServices::getInstance()->getMagicWordFactory(); + $services->getMagicWordFactory(); - $this->contLang = $contLang ?? MediaWikiServices::getInstance()->getContentLanguage(); + $this->contLang = $contLang ?? $services->getContentLanguage(); - $this->factory = $factory ?? MediaWikiServices::getInstance()->getParserFactory(); + $this->factory = $factory ?? $services->getParserFactory(); + $this->specialPageFactory = $spFactory ?? $services->getSpecialPageFactory(); } /** @@ -418,12 +425,14 @@ class Parser { * Do not call this function recursively. * * @param string $text Text we want to parse + * @param-taint $text escapes_htmlnoent * @param Title $title * @param ParserOptions $options * @param bool $linestart * @param bool $clearState * @param int|null $revid Number to pass in {{REVISIONID}} * @return ParserOutput A ParserOutput + * @return-taint escaped */ public function parse( $text, Title $title, ParserOptions $options, @@ -510,7 +519,7 @@ class Parser { # with CSS (T37247) $class = $this->mOptions->getWrapOutputClass(); if ( $class !== false && !$this->mOptions->getInterfaceMessage() ) { - $text = Html::rawElement( 'div', [ 'class' => $class ], $text ); + $this->mOutput->addWrapperDivClass( $class ); } $this->mOutput->setText( $text ); @@ -664,8 +673,10 @@ class Parser { * $text are not expanded * * @param string $text Text extension wants to have parsed + * @param-taint $text escapes_htmlnoent * @param bool|PPFrame $frame The frame to use for expanding any template variables * @return string UNSAFE half-parsed HTML + * @return-taint escaped */ public function recursiveTagParse( $text, $frame = false ) { // Avoid PHP 7.1 warning from passing $this by reference @@ -690,8 +701,10 @@ class Parser { * @since 1.25 * * @param string $text Text extension wants to have parsed + * @param-taint $text escapes_htmlnoent * @param bool|PPFrame $frame The frame to use for expanding any template variables * @return string Fully parsed HTML + * @return-taint escaped */ public function recursiveTagParseFully( $text, $frame = false ) { $text = $this->recursiveTagParse( $text, $frame ); @@ -1306,6 +1319,7 @@ class Parser { * @private * * @param string $text The text to parse + * @param-taint $text escapes_html * @param bool $isMain Whether this is being called from the main parse() function * @param PPFrame|bool $frame A pre-processor frame * @@ -2685,9 +2699,19 @@ class Parser { $this->mOutput->setFlag( 'vary-revision-id' ); wfDebug( __METHOD__ . ": {{REVISIONID}} used, setting vary-revision-id...\n" ); $value = $this->mRevisionId; - if ( !$value && $this->mOptions->getSpeculativeRevIdCallback() ) { - $value = call_user_func( $this->mOptions->getSpeculativeRevIdCallback() ); - $this->mOutput->setSpeculativeRevIdUsed( $value ); + + if ( !$value ) { + $rev = $this->getRevisionObject(); + if ( $rev ) { + $value = $rev->getId(); + } + } + + if ( !$value ) { + $value = $this->mOptions->getSpeculativeRevId(); + if ( $value ) { + $this->mOutput->setSpeculativeRevIdUsed( $value ); + } } break; case 'revisionday': @@ -3244,7 +3268,7 @@ class Parser { && $this->mOptions->getAllowSpecialInclusion() && $this->ot['html'] ) { - $specialPage = SpecialPageFactory::getPage( $title->getDBkey() ); + $specialPage = $this->specialPageFactory->getPage( $title->getDBkey() ); // Pass the template arguments as URL parameters. // "uselang" will have no effect since the Language object // is forced to the one defined in ParserOptions. @@ -3270,8 +3294,7 @@ class Parser { $context->setUser( User::newFromName( '127.0.0.1', false ) ); } $context->setLanguage( $this->mOptions->getUserLangObj() ); - $ret = SpecialPageFactory::capturePath( - $title, $context, $this->getLinkRenderer() ); + $ret = $this->specialPageFactory->capturePath( $title, $context, $this->getLinkRenderer() ); if ( $ret ) { $text = $context->getOutput()->getHTML(); $this->mOutput->addOutputPageMetadata( $context->getOutput() ); @@ -3777,57 +3800,68 @@ class Parser { * Transclude an interwiki link. * * @param Title $title - * @param string $action + * @param string $action Usually one of (raw, render) * * @return string */ public function interwikiTransclude( $title, $action ) { - global $wgEnableScaryTranscluding; + global $wgEnableScaryTranscluding, $wgTranscludeCacheExpiry; if ( !$wgEnableScaryTranscluding ) { return wfMessage( 'scarytranscludedisabled' )->inContentLanguage()->text(); } $url = $title->getFullURL( [ 'action' => $action ] ); - - if ( strlen( $url ) > 255 ) { + if ( strlen( $url ) > 1024 ) { return wfMessage( 'scarytranscludetoolong' )->inContentLanguage()->text(); } - return $this->fetchScaryTemplateMaybeFromCache( $url ); - } - /** - * @param string $url - * @return mixed|string - */ - public function fetchScaryTemplateMaybeFromCache( $url ) { - global $wgTranscludeCacheExpiry; - $dbr = wfGetDB( DB_REPLICA ); - $tsCond = $dbr->timestamp( time() - $wgTranscludeCacheExpiry ); - $obj = $dbr->selectRow( 'transcache', [ 'tc_time', 'tc_contents' ], - [ 'tc_url' => $url, "tc_time >= " . $dbr->addQuotes( $tsCond ) ] ); - if ( $obj ) { - return $obj->tc_contents; - } - - $req = MWHttpRequest::factory( $url, [], __METHOD__ ); - $status = $req->execute(); // Status object - if ( $status->isOK() ) { - $text = $req->getContent(); - } elseif ( $req->getStatus() != 200 ) { + $wikiId = $title->getTransWikiID(); // remote wiki ID or false + + $fname = __METHOD__; + $cache = MediaWikiServices::getInstance()->getMainWANObjectCache(); + + $data = $cache->getWithSetCallback( + $cache->makeGlobalKey( + 'interwiki-transclude', + ( $wikiId !== false ) ? $wikiId : 'external', + sha1( $url ) + ), + $wgTranscludeCacheExpiry, + function ( $oldValue, &$ttl ) use ( $url, $fname, $cache ) { + $req = MWHttpRequest::factory( $url, [], $fname ); + + $status = $req->execute(); // Status object + if ( !$status->isOK() ) { + $ttl = $cache::TTL_UNCACHEABLE; + } elseif ( $req->getResponseHeader( 'X-Database-Lagged' ) !== null ) { + $ttl = min( $cache::TTL_LAGGED, $ttl ); + } + + return [ + 'text' => $status->isOK() ? $req->getContent() : null, + 'code' => $req->getStatus() + ]; + }, + [ + 'checkKeys' => ( $wikiId !== false ) + ? [ $cache->makeGlobalKey( 'interwiki-page', $wikiId, $title->getDBkey() ) ] + : [], + 'pcGroup' => 'interwiki-transclude:5', + 'pcTTL' => $cache::TTL_PROC_LONG + ] + ); + + if ( is_string( $data['text'] ) ) { + $text = $data['text']; + } elseif ( $data['code'] != 200 ) { // Though we failed to fetch the content, this status is useless. - return wfMessage( 'scarytranscludefailed-httpstatus' ) - ->params( $url, $req->getStatus() /* HTTP status */ )->inContentLanguage()->text(); + $text = wfMessage( 'scarytranscludefailed-httpstatus' ) + ->params( $url, $data['code'] )->inContentLanguage()->text(); } else { - return wfMessage( 'scarytranscludefailed', $url )->inContentLanguage()->text(); + $text = wfMessage( 'scarytranscludefailed', $url )->inContentLanguage()->text(); } - $dbw = wfGetDB( DB_MASTER ); - $dbw->replace( 'transcache', [ 'tc_url' ], [ - 'tc_url' => $url, - 'tc_time' => $dbw->timestamp( time() ), - 'tc_contents' => $text - ] ); return $text; } @@ -5733,18 +5767,31 @@ class Parser { if ( !is_null( $this->mRevisionObject ) ) { return $this->mRevisionObject; } - if ( is_null( $this->mRevisionId ) ) { - return null; - } + // NOTE: try to get the RevisionObject even if mRevisionId is null. + // This is useful when parsing revision that has not yet been saved. + // However, if we get back a saved revision even though we are in + // preview mode, we'll have to ignore it, see below. + // NOTE: This callback may be used to inject an OLD revision that was + // already loaded, so "current" is a bit of a misnomer. We can't just + // skip it if mRevisionId is set. $rev = call_user_func( $this->mOptions->getCurrentRevisionCallback(), $this->getTitle(), $this ); - # If the parse is for a new revision, then the callback should have - # already been set to force the object and should match mRevisionId. - # If not, try to fetch by mRevisionId for sanity. - if ( $rev && $rev->getId() != $this->mRevisionId ) { + if ( $this->mRevisionId === null && $rev && $rev->getId() ) { + // We are in preview mode (mRevisionId is null), and the current revision callback + // returned an existing revision. Ignore it and return null, it's probably the page's + // current revision, which is not what we want here. Note that we do want to call the + // callback to allow the unsaved revision to be injected here, e.g. for + // self-transclusion previews. + return null; + } + + // If the parse is for a new revision, then the callback should have + // already been set to force the object and should match mRevisionId. + // If not, try to fetch by mRevisionId for sanity. + if ( $this->mRevisionId && $rev && $rev->getId() != $this->mRevisionId ) { $rev = Revision::newFromId( $this->mRevisionId ); } diff --git a/includes/parser/ParserCache.php b/includes/parser/ParserCache.php index 5e6081d33d..43c72b18f8 100644 --- a/includes/parser/ParserCache.php +++ b/includes/parser/ParserCache.php @@ -301,6 +301,10 @@ class ParserCache { $cacheTime = null, $revId = null ) { + if ( !$parserOutput->hasText() ) { + throw new InvalidArgumentException( 'Attempt to cache a ParserOutput with no text set!' ); + } + $expire = $parserOutput->getCacheExpiry(); if ( $expire > 0 && !$this->mMemc instanceof EmptyBagOStuff ) { $cacheTime = $cacheTime ?: wfTimestampNow(); diff --git a/includes/parser/ParserFactory.php b/includes/parser/ParserFactory.php index 646f8552cd..4238b2750a 100644 --- a/includes/parser/ParserFactory.php +++ b/includes/parser/ParserFactory.php @@ -1,5 +1,4 @@ conf = $conf; $this->magicWordFactory = $magicWordFactory; $this->contLang = $contLang; $this->urlProtocols = $urlProtocols; + $this->specialPageFactory = $spFactory; } /** @@ -58,6 +65,6 @@ class ParserFactory { */ public function create() : Parser { return new Parser( $this->conf, $this->magicWordFactory, $this->contLang, $this, - $this->urlProtocols ); + $this->urlProtocols, $this->specialPageFactory ); } } diff --git a/includes/parser/ParserOptions.php b/includes/parser/ParserOptions.php index b30c1163b8..a8da3ce930 100644 --- a/includes/parser/ParserOptions.php +++ b/includes/parser/ParserOptions.php @@ -61,6 +61,7 @@ class ParserOptions { */ private static $lazyOptions = [ 'dateformat' => [ __CLASS__, 'initDateFormat' ], + 'speculativeRevId' => [ __CLASS__, 'initSpeculativeRevId' ], ]; /** @@ -831,9 +832,38 @@ class ParserOptions { return $this->setOptionLegacy( 'templateCallback', $x ); } + /** + * A guess for {{REVISIONID}}, calculated using the callback provided via + * setSpeculativeRevIdCallback(). For consistency, the value will be calculated upon the + * first call of this method, and re-used for subsequent calls. + * + * If no callback was defined via setSpeculativeRevIdCallback(), this method will return false. + * + * @since 1.32 + * @return int|false + */ + public function getSpeculativeRevId() { + return $this->getOption( 'speculativeRevId' ); + } + + /** + * Callback registered with ParserOptions::$lazyOptions, triggered by getSpeculativeRevId(). + * + * @param ParserOptions $popt + * @return bool|false + */ + private static function initSpeculativeRevId( ParserOptions $popt ) { + $cb = $popt->getOption( 'speculativeRevIdCallback' ); + $id = $cb ? $cb() : null; + + // returning null would result in this being re-called every access + return $id ?? false; + } + /** * Callback to generate a guess for {{REVISIONID}} * @since 1.28 + * @deprecated since 1.32, use getSpeculativeRevId() instead! * @return callable|null */ public function getSpeculativeRevIdCallback() { @@ -847,6 +877,7 @@ class ParserOptions { * @return callable|null Old value */ public function setSpeculativeRevIdCallback( $x ) { + $this->setOption( 'speculativeRevId', null ); // reset return $this->setOptionLegacy( 'speculativeRevIdCallback', $x ); } @@ -1081,6 +1112,7 @@ class ParserOptions { 'currentRevisionCallback' => [ Parser::class, 'statelessFetchRevision' ], 'templateCallback' => [ Parser::class, 'statelessFetchTemplate' ], 'speculativeRevIdCallback' => null, + 'speculativeRevId' => null, ]; Hooks::run( 'ParserOptionsRegister', [ diff --git a/includes/parser/ParserOutput.php b/includes/parser/ParserOutput.php index 182648a94f..445981bbc8 100644 --- a/includes/parser/ParserOutput.php +++ b/includes/parser/ParserOutput.php @@ -31,9 +31,9 @@ class ParserOutput extends CacheTime { const SUPPORTS_UNWRAP_TRANSFORM = 1; /** - * @var string $mText The output text + * @var string|null $mText The output text */ - public $mText; + public $mText = null; /** * @var array $mLanguageLinks List of the full text of language links, @@ -212,6 +212,11 @@ class ParserOutput extends CacheTime { /** @var int|null Assumed rev ID for {{REVISIONID}} if no revision is set */ private $mSpeculativeRevId; + /** string CSS classes to use for the wrapping div, stored in the array keys. + * If no class is given, no wrapper is added. + */ + private $mWrapperDivClasses = []; + /** @var int Upper bound of expiry based on parse duration */ private $mMaxAdaptiveExpiry = INF; @@ -227,6 +232,15 @@ class ParserOutput extends CacheTime { const SLOW_AR_TTL = 3600; // adaptive TTL for "slow" pages const MIN_AR_TTL = 15; // min adaptive TTL (for sanity, pool counter, and edit stashing) + /** + * @param string|null $text HTML. Use null to indicate that this ParserOutput contains only + * meta-data, and the HTML output is undetermined, as opposed to empty. Passing null + * here causes hasText() to return false. + * @param array $languageLinks + * @param array $categoryLinks + * @param bool $unused + * @param string $titletext + */ public function __construct( $text = '', $languageLinks = [], $categoryLinks = [], $unused = false, $titletext = '' ) { @@ -236,6 +250,20 @@ class ParserOutput extends CacheTime { $this->mTitleText = $titletext; } + /** + * Returns true if text was passed to the constructor, or set using setText(). Returns false + * if null was passed to the $text parameter of the constructor to indicate that this + * ParserOutput only contains meta-data, and the HTML output is undetermined. + * + * @since 1.32 + * + * @return bool Whether this ParserOutput contains rendered text. If this returns false, the + * ParserOutput contains meta-data only. + */ + public function hasText() { + return ( $this->mText !== null ); + } + /** * Get the cacheable text with markers still in it. The * return value is suitable for writing back via setText() but is not valid @@ -245,6 +273,10 @@ class ParserOutput extends CacheTime { * @since 1.27 */ public function getRawText() { + if ( $this->mText === null ) { + throw new LogicException( 'This ParserOutput contains no text!' ); + } + return $this->mText; } @@ -258,7 +290,12 @@ class ParserOutput extends CacheTime { * - enableSectionEditLinks: (bool) Include section edit links, assuming * section edit link tokens are present in the HTML. Default is true, * but might be statefully overridden. - * - unwrap: (bool) Remove a wrapping mw-parser-output div. Default is false. + * - unwrap: (bool) Return text without a wrapper div. Default is false, + * meaning a wrapper div will be added if getWrapperDivClass() returns + * a non-empty string. + * - wrapperDivClass: (string) Wrap the output in a div and apply the given + * CSS class to that div. This overrides the output of getWrapperDivClass(). + * Setting this to an empty string has the same effect as 'unwrap' => true. * - deduplicateStyles: (bool) When true, which is the default, ` - - - - - + + + + - - - - - - - - -Loading the library -------------------- - -While the distribution directory is chock-full of files, you will normally load only the following three: - -* `oojs-ui.js`, containing the full library; -* One of `oojs-ui-wikimediaui.css` or `oojs-ui-apex.css`, containing theme-specific styles; and -* One of `oojs-ui-wikimediaui.js` or `oojs-ui-apex.js`, containing theme-specific code - -You can load additional icon packs from files named `oojs-ui-wikimediaui-icons-*.css` or `oojs-ui-apex-icons-*.css`. - -The remaining files make it possible to load only parts of the whole library. - -Furthermore, every CSS file has a right-to-left (RTL) version available, to be used on pages using right-to-left languages if your environment doesn't automatically flip them as needed. - - -Issue tracker -------------- - -Found a bug or missing feature? Please report it in our [issue tracker Phabricator](https://phabricator.wikimedia.org/maniphest/task/edit/form/1/?projects=PHID-PROJ-dgmoevjqeqlerleqzzx5)! - - -Contributing ------------- - -We are always delighted when people contribute patches. To setup your development environment: - - -1. Clone the repo: `$ git clone https://phabricator.wikimedia.org/diffusion/GOJU/oojs-ui.git oojs-ui` - -2. Move into the library directory:
`$ cd oojs-ui` - -3. Install [composer](https://getcomposer.org/download/) and make sure running `composer` will execute it (*e.g.* add it to `$PATH` in POSIX environments). - -4. Install dev dependencies:
`$ npm install` - -5. Build the library (you can alternatively use `grunt quick-build` if you don't need to rebuild the PNGs):
`$ grunt build` - -6. You can see a suite of demos in `/demos` by executing:
`$ npm run-script demos` - -7. You can also copy the distribution files from the dist directory into your project. - - -We use [Gerrit](https://gerrit.wikimedia.org/) for code review, and [Phabricator](https://phabricator.wikimedia.org) to track issues. To contribute patches or join discussions all you need is a [developer account](https://wikitech.wikimedia.org/w/index.php?title=Special:CreateAccount&returnto=Help%3AGetting+Started). - -* If you've found a bug, or wish to request a feature [raise a ticket on Phabricator](https://phabricator.wikimedia.org/maniphest/task/edit/form/1/?projects=PHID-PROJ-dgmoevjqeqlerleqzzx5). -* To submit your patch, follow [the "getting started" quick-guide](https://www.mediawiki.org/wiki/Gerrit/Getting_started). We try to review patches within a week. -* We automatically lint and style-check changes to JavaScript, PHP, LESS/CSS, Ruby and JSON files. You can test these yourself with `npm test` and `composer test` locally before pushing changes. SVG files should be squashed in advance of committing with [SVGO](https://github.com/svg/svgo) using `svgo --pretty --disable=removeXMLProcInst --disable=cleanupIDs `. - -A new version of the library is released most weeks on Tuesdays. - -Community ---------- - -Get updates, ask questions and join the discussion with maintainers and contributors: - -* Join the Wikimedia Developers mailing list, [wikitech-l](https://lists.wikimedia.org/mailman/listinfo/wikitech-l). -* Chat with the maintainers on `#wikimedia-dev` on `irc.freenode.net`. -* Ask questions on [StackOverflow](https://stackoverflow.com/tags/oojs-ui/info). -* Watchlist the [documentation](https://www.mediawiki.org/wiki/OOUI) on MediaWiki to stay updated. - - -Versioning ----------- - -We use the [Semantic Versioning guidelines](http://semver.org/). - -Releases will be numbered in the following format: - -`..` - - -Release ----------- - -Release process: -
-
-    $ cd path/to/oojs-ui/
-    $ git remote update
-    $ git checkout -B release -t origin/master
-
-    # Ensure tests pass
-    $ npm install && composer update && npm test && composer test
-
-    # Avoid using "npm version patch" because that creates
-    # both a commit and a tag, and we shouldn't tag until after
-    # the commit is merged.
-
-    # Update release notes
-    # Copy the resulting list into a new section at the top of History.md and edit
-    # into five sub-sections, in order:
-    # * Breaking changes
-    # * Deprecations
-    # * Features
-    # * Styles
-    # * Code
-    $ git log --format='* %s (%aN)' --no-merges --reverse v$(node -e 'console.log(require("./package.json").version);')...HEAD | grep -v "Localisation updates from" | sort
-    $ edit History.md
-
-    # Update the version number
-    $ edit package.json
-
-    $ git add -p
-    $ git commit -m "Tag vX.X.X"
-    $ git review
-
-    # After merging:
-    $ git remote update
-    $ git checkout origin/master
-    $ git tag "vX.X.X"
-    $ npm run publish-build && git push --tags && npm publish
-
-
diff --git a/resources/lib/oojs-ui/i18n/ace.json b/resources/lib/oojs-ui/i18n/ace.json deleted file mode 100644 index 0fdc1a8930..0000000000 --- a/resources/lib/oojs-ui/i18n/ace.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Si Gam Acèh" - ] - }, - "ooui-outline-control-move-down": "Pinah item u yup", - "ooui-outline-control-move-up": "Pinah item u ateuëh", - "ooui-toolbar-more": "Lom" -} diff --git a/resources/lib/oojs-ui/i18n/af.json b/resources/lib/oojs-ui/i18n/af.json deleted file mode 100644 index 14990f945e..0000000000 --- a/resources/lib/oojs-ui/i18n/af.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Naudefj", - "Fwolff", - "Fitoschido" - ] - }, - "ooui-outline-control-move-down": "Skuif item af", - "ooui-outline-control-move-up": "Skuif item op", - "ooui-outline-control-remove": "Verwyder item", - "ooui-toolbar-more": "Meer", - "ooui-toolgroup-expand": "Meer", - "ooui-toolgroup-collapse": "Minder", - "ooui-dialog-message-accept": "Regso", - "ooui-dialog-message-reject": "Kanselleer", - "ooui-dialog-process-error": "Iets het verkeerd gegaan", - "ooui-dialog-process-dismiss": "Sluit", - "ooui-dialog-process-retry": "Probeer weer", - "ooui-dialog-process-continue": "Gaan voort", - "ooui-selectfile-button-select": "Kies 'n lêer", - "ooui-selectfile-not-supported": "Lêers kan nie gekies word nie", - "ooui-selectfile-placeholder": "Geen lêer is gekies nie", - "ooui-selectfile-dragdrop-placeholder": "Laat val die lêer hier", - "ooui-field-help": "Hulp" -} diff --git a/resources/lib/oojs-ui/i18n/ais.json b/resources/lib/oojs-ui/i18n/ais.json deleted file mode 100644 index 42eb8c2e95..0000000000 --- a/resources/lib/oojs-ui/i18n/ais.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Bunukwiki", - "Benel" - ] - }, - "ooui-outline-control-move-down": "miliad kasacacay tasasa’", - "ooui-outline-control-move-up": "miliad kasacacay tapabaw", - "ooui-outline-control-remove": "misipu kasacacay", - "ooui-toolbar-more": "yadah", - "ooui-toolgroup-expand": "yadah", - "ooui-toolgroup-collapse": "ma’ngadis mangalep", - "ooui-item-remove": "milimad", - "ooui-dialog-message-accept": "malucekay", - "ooui-dialog-message-reject": "palawpes", - "ooui-dialog-process-error": "tahkal ku caykapulitaay a mungangaw", - "ooui-dialog-process-dismiss": "edeben", - "ooui-dialog-process-retry": "pitaneng henay aca", - "ooui-dialog-process-continue": "palalid", - "ooui-selectfile-button-select": "mipili’ cacay a tangan", - "ooui-selectfile-not-supported": "la’cus midama mipili’ay a tangan", - "ooui-selectfile-placeholder": "caay henay mipili’ tu tangan", - "ooui-selectfile-dragdrop-placeholder": "mutengteng tangan katukuh itini" -} diff --git a/resources/lib/oojs-ui/i18n/am.json b/resources/lib/oojs-ui/i18n/am.json deleted file mode 100644 index bfe9d5c3b8..0000000000 --- a/resources/lib/oojs-ui/i18n/am.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Elfalem" - ] - } -} diff --git a/resources/lib/oojs-ui/i18n/ar.json b/resources/lib/oojs-ui/i18n/ar.json deleted file mode 100644 index ce7f9998e5..0000000000 --- a/resources/lib/oojs-ui/i18n/ar.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Ciphers", - "Claw eg", - "Elfalem", - "Jdforrester", - "Mido", - "OsamaK", - "زكريا", - "مشعل الحربي", - "ترجمان05", - "Abanima", - "محمد أحمد عبد الفتاح", - "Hiba Alshawi", - "Meno25", - "ديفيد" - ] - }, - "ooui-outline-control-move-down": "انقل العنصر للأسفل", - "ooui-outline-control-move-up": "انقل العنصر للأعلى", - "ooui-outline-control-remove": "أزل العنصر", - "ooui-toolbar-more": "مزيد", - "ooui-toolgroup-expand": "مزيد", - "ooui-toolgroup-collapse": "أقل", - "ooui-item-remove": "إزالة", - "ooui-dialog-message-accept": "موافق", - "ooui-dialog-message-reject": "ألغ", - "ooui-dialog-process-error": "حدث خطأ", - "ooui-dialog-process-dismiss": "أغلق", - "ooui-dialog-process-retry": "حاول مرة أخرى", - "ooui-dialog-process-continue": "استمر", - "ooui-selectfile-button-select": "اختر ملفا", - "ooui-selectfile-not-supported": "اختيار الملفات غير مدعوم", - "ooui-selectfile-placeholder": "لم يتم اختيار أي ملف", - "ooui-selectfile-dragdrop-placeholder": "اترك الملف هنا", - "ooui-field-help": "مساعدة" -} diff --git a/resources/lib/oojs-ui/i18n/arc.json b/resources/lib/oojs-ui/i18n/arc.json deleted file mode 100644 index de5b7aff36..0000000000 --- a/resources/lib/oojs-ui/i18n/arc.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Basharh" - ] - } -} diff --git a/resources/lib/oojs-ui/i18n/arq.json b/resources/lib/oojs-ui/i18n/arq.json deleted file mode 100644 index 61eb384406..0000000000 --- a/resources/lib/oojs-ui/i18n/arq.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Bachounda" - ] - }, - "ooui-outline-control-move-down": "هبط الشيئ للتحت", - "ooui-outline-control-move-up": "طلع الشيئ للفوق", - "ooui-outline-control-remove": "أمحي العنصر", - "ooui-toolbar-more": "زيادة", - "ooui-toolgroup-expand": "زيادة", - "ooui-toolgroup-collapse": "قليل", - "ooui-dialog-message-accept": "مليح", - "ooui-dialog-message-reject": "رجَع", - "ooui-dialog-process-error": "حاجه ما خدمتش مليح", - "ooui-dialog-process-dismiss": "أرفضها", - "ooui-dialog-process-retry": "عاود جرب", - "ooui-dialog-process-continue": "واصل", - "ooui-selectfile-not-supported": "تحديد الفيشيات ما هوش محدد", - "ooui-selectfile-placeholder": "ما اختاريتش حتا ملف" -} diff --git a/resources/lib/oojs-ui/i18n/as.json b/resources/lib/oojs-ui/i18n/as.json deleted file mode 100644 index 54a4244135..0000000000 --- a/resources/lib/oojs-ui/i18n/as.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Gitartha.bordoloi", - "Dibya Dutta", - "IKHazarika" - ] - }, - "ooui-outline-control-move-down": "সমল তললৈ স্থানান্তৰ কৰক", - "ooui-outline-control-move-up": "সমল ওপৰলৈ স্থানান্তৰ কৰক", - "ooui-outline-control-remove": "সমল আঁতৰাওক", - "ooui-toolbar-more": "অধিক", - "ooui-toolgroup-expand": "অধিক", - "ooui-toolgroup-collapse": "কম দেখাওক", - "ooui-dialog-message-accept": "বাৰু", - "ooui-dialog-message-reject": "বাতিল কৰক", - "ooui-dialog-process-error": "কিবা ত্ৰুটি হৈছে", - "ooui-dialog-process-dismiss": "বাতিল", - "ooui-dialog-process-retry": "পুনৰ চেষ্টা কৰক", - "ooui-dialog-process-continue": "অব্যাহত ৰাখক", - "ooui-selectfile-button-select": "ফাইল নিৰ্বাচন কৰক", - "ooui-selectfile-not-supported": "নথি নিৰ্বাচন সমৰ্থন কৰা নাই", - "ooui-selectfile-placeholder": "কোনো নথি নিৰ্বাচিত কৰা হোৱা নাই", - "ooui-selectfile-dragdrop-placeholder": "ইয়াত ফাইল এৰক" -} diff --git a/resources/lib/oojs-ui/i18n/ast.json b/resources/lib/oojs-ui/i18n/ast.json deleted file mode 100644 index b62f8f39fd..0000000000 --- a/resources/lib/oojs-ui/i18n/ast.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Basharh", - "Bishnu Saikia", - "Xuacu" - ] - }, - "ooui-outline-control-move-down": "Mover abaxo l'elementu", - "ooui-outline-control-move-up": "Mover arriba l'elementu", - "ooui-outline-control-remove": "Desaniciar elementu", - "ooui-toolbar-more": "Más", - "ooui-toolgroup-expand": "Más", - "ooui-toolgroup-collapse": "Menos", - "ooui-item-remove": "Desaniciar", - "ooui-dialog-message-accept": "Aceutar", - "ooui-dialog-message-reject": "Encaboxar", - "ooui-dialog-process-error": "Daqué funcionó mal", - "ooui-dialog-process-dismiss": "Descartar", - "ooui-dialog-process-retry": "Vuelvi a intentalo", - "ooui-dialog-process-continue": "Siguir", - "ooui-selectfile-button-select": "Seleicionar un ficheru", - "ooui-selectfile-not-supported": "Nun hai encontu pa la seleición de ficheros", - "ooui-selectfile-placeholder": "Nun se seleicionó nengún ficheru", - "ooui-selectfile-dragdrop-placeholder": "Soltar el ficheru equí", - "ooui-field-help": "Ayuda" -} diff --git a/resources/lib/oojs-ui/i18n/awa.json b/resources/lib/oojs-ui/i18n/awa.json deleted file mode 100644 index f78ed3269e..0000000000 --- a/resources/lib/oojs-ui/i18n/awa.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "@metadata": { - "authors": [ - "1AnuraagPandey" - ] - }, - "ooui-toolbar-more": "अउर" -} diff --git a/resources/lib/oojs-ui/i18n/az.json b/resources/lib/oojs-ui/i18n/az.json deleted file mode 100644 index 96b95d1aad..0000000000 --- a/resources/lib/oojs-ui/i18n/az.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Cekli829", - "Interfase", - "Jduranboger", - "Wertuose" - ] - }, - "ooui-outline-control-move-down": "Bəndi aşağı apar", - "ooui-outline-control-move-up": "Bəndi yuxarı apar", - "ooui-outline-control-remove": "Bəndi sil", - "ooui-toolbar-more": "Daha artıq", - "ooui-toolgroup-collapse": "Daha az" -} diff --git a/resources/lib/oojs-ui/i18n/azb.json b/resources/lib/oojs-ui/i18n/azb.json deleted file mode 100644 index 313122ce89..0000000000 --- a/resources/lib/oojs-ui/i18n/azb.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Sadiqr" - ] - }, - "ooui-dialog-message-reject": "وازگئچ", - "ooui-dialog-process-continue": "داوام ائت", - "ooui-selectfile-button-select": "بیر فایل سئچ", - "ooui-selectfile-placeholder": "هئچ فایل سئچیلمه‌ییب" -} diff --git a/resources/lib/oojs-ui/i18n/ba.json b/resources/lib/oojs-ui/i18n/ba.json deleted file mode 100644 index d8c99aafdf..0000000000 --- a/resources/lib/oojs-ui/i18n/ba.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "@metadata": { - "authors": [ - "AiseluRB", - "Amire80", - "Assele", - "Haqmar", - "Sagan", - "Рустам Нурыев", - "Азат Хәлилов" - ] - }, - "ooui-outline-control-move-down": "Элементты аҫҡа күсерергә", - "ooui-outline-control-move-up": "Элементты өҫкә күсерергә", - "ooui-outline-control-remove": "Биттәрҙе юйырға", - "ooui-toolbar-more": "Тағы", - "ooui-toolgroup-expand": "Күберәк", - "ooui-toolgroup-collapse": "Аҙыраҡ", - "ooui-dialog-message-accept": "Тамам", - "ooui-dialog-message-reject": "Кире алырға", - "ooui-dialog-process-error": "Нимәлер килеп сыҡманы.", - "ooui-dialog-process-dismiss": "Йәшерергә", - "ooui-dialog-process-retry": "Ҡабатлап ҡарарға.", - "ooui-dialog-process-continue": "Дауам итергә", - "ooui-selectfile-button-select": "Файлды һайлағыҙ", - "ooui-selectfile-not-supported": "Файл һайлау хупланмай.", - "ooui-selectfile-placeholder": "Файл һайланмаған", - "ooui-selectfile-dragdrop-placeholder": "Файлды бында күсерегеҙ" -} diff --git a/resources/lib/oojs-ui/i18n/bcc.json b/resources/lib/oojs-ui/i18n/bcc.json deleted file mode 100644 index a340a881ff..0000000000 --- a/resources/lib/oojs-ui/i18n/bcc.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Baloch Afghanistan" - ] - }, - "ooui-dialog-message-accept": "اوکی", - "ooui-dialog-process-retry": "پدا کوشش کورتین" -} diff --git a/resources/lib/oojs-ui/i18n/bcl.json b/resources/lib/oojs-ui/i18n/bcl.json deleted file mode 100644 index bc2251e8b2..0000000000 --- a/resources/lib/oojs-ui/i18n/bcl.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Geopoet", - "Sky Harbor" - ] - }, - "ooui-outline-control-move-down": "Balyuhon an aytem paibaba", - "ooui-outline-control-move-up": "Balyuhon an aytem paitaas", - "ooui-toolbar-more": "Kadugangan" -} diff --git a/resources/lib/oojs-ui/i18n/be-tarask.json b/resources/lib/oojs-ui/i18n/be-tarask.json deleted file mode 100644 index 59415a2ea1..0000000000 --- a/resources/lib/oojs-ui/i18n/be-tarask.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "@metadata": { - "authors": [ - "EugeneZelenko", - "Wizardist", - "Чаховіч Уладзіслаў", - "Zedlik", - "Red Winged Duck", - "Renessaince" - ] - }, - "ooui-outline-control-move-down": "Перасунуць элемэнт ніжэй", - "ooui-outline-control-move-up": "Перасунуць элемэнт вышэй", - "ooui-outline-control-remove": "Выдаліць пункт", - "ooui-toolbar-more": "Болей", - "ooui-toolgroup-expand": "Болей", - "ooui-toolgroup-collapse": "Меней", - "ooui-item-remove": "Выдаліць", - "ooui-dialog-message-accept": "Добра", - "ooui-dialog-message-reject": "Скасаваць", - "ooui-dialog-process-error": "Нешта пайшло ня так", - "ooui-dialog-process-dismiss": "Прапусьціць", - "ooui-dialog-process-retry": "Паспрабаваць зноў", - "ooui-dialog-process-continue": "Працягваць", - "ooui-selectfile-button-select": "Абраць файл", - "ooui-selectfile-not-supported": "Выбар файлу не падтрымліваецца", - "ooui-selectfile-placeholder": "Ніводзін файл не абраны", - "ooui-selectfile-dragdrop-placeholder": "Перацягніце файл сюды", - "ooui-field-help": "Дапамога" -} diff --git a/resources/lib/oojs-ui/i18n/be.json b/resources/lib/oojs-ui/i18n/be.json deleted file mode 100644 index 7b4e54686d..0000000000 --- a/resources/lib/oojs-ui/i18n/be.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Чаховіч Уладзіслаў", - "Artificial123", - "Goshaproject", - "Mechanizatar" - ] - }, - "ooui-outline-control-move-down": "Перамясціць элемент ўніз", - "ooui-outline-control-move-up": "Перамясціць элемент уверх", - "ooui-outline-control-remove": "Выдаліць элемент", - "ooui-toolbar-more": "Яшчэ", - "ooui-toolgroup-expand": "Яшчэ", - "ooui-toolgroup-collapse": "Менш", - "ooui-dialog-message-accept": "ОК", - "ooui-dialog-message-reject": "Адмяніць", - "ooui-dialog-process-error": "Штось пайшло не так…", - "ooui-dialog-process-dismiss": "Прапусціць", - "ooui-dialog-process-retry": "Паспрабаваць яшчэ раз", - "ooui-dialog-process-continue": "Працягнуць", - "ooui-selectfile-button-select": "Выбраць файл", - "ooui-selectfile-not-supported": "Выбраны файл не падтрымліваецца", - "ooui-selectfile-placeholder": "Файл не выбраны" -} diff --git a/resources/lib/oojs-ui/i18n/bg.json b/resources/lib/oojs-ui/i18n/bg.json deleted file mode 100644 index afa167243d..0000000000 --- a/resources/lib/oojs-ui/i18n/bg.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "@metadata": { - "authors": [ - "DCLXVI", - "Hristofor.mirchev", - "පසිඳු කාවින්ද", - "Mitzev", - "Aquilax", - "Vodnokon4e", - "StanProg" - ] - }, - "ooui-outline-control-move-down": "Преместване на елемента надолу", - "ooui-outline-control-move-up": "Преместване на елемента нагоре", - "ooui-outline-control-remove": "Премахване на обекта", - "ooui-toolbar-more": "Още", - "ooui-toolgroup-expand": "Още", - "ooui-toolgroup-collapse": "По-малко", - "ooui-dialog-message-accept": "OK", - "ooui-dialog-message-reject": "Отказ", - "ooui-dialog-process-error": "Нещо се обърка", - "ooui-dialog-process-dismiss": "Затваряне", - "ooui-dialog-process-retry": "Опитайте отново", - "ooui-dialog-process-continue": "Продължаване", - "ooui-selectfile-button-select": "Избиране на файл", - "ooui-selectfile-not-supported": "Избраният файл не се поддържа", - "ooui-selectfile-placeholder": "Не е избран файл", - "ooui-selectfile-dragdrop-placeholder": "Пуснете файла тук", - "ooui-field-help": "Помощ" -} diff --git a/resources/lib/oojs-ui/i18n/bho.json b/resources/lib/oojs-ui/i18n/bho.json deleted file mode 100644 index 9697db02be..0000000000 --- a/resources/lib/oojs-ui/i18n/bho.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "@metadata": { - "authors": [ - "SatyamMishra" - ] - }, - "ooui-outline-control-move-down": "आइटम नीचे घसकाईं", - "ooui-outline-control-move-up": "आइटम ऊपर घसकाईं", - "ooui-outline-control-remove": "आइटम हटाईं", - "ooui-toolbar-more": "अउरी", - "ooui-toolgroup-expand": "अउरी", - "ooui-toolgroup-collapse": "कम", - "ooui-dialog-message-accept": "ओके", - "ooui-dialog-message-reject": "कैंसिल", - "ooui-dialog-process-error": "कुछ गड़बड़ी हो गइल", - "ooui-dialog-process-dismiss": "रद्द", - "ooui-dialog-process-retry": "दोबारा कोसिस करीं", - "ooui-dialog-process-continue": "जारी राखीं", - "ooui-selectfile-button-select": "एगो फाइल चुनीं", - "ooui-selectfile-not-supported": "फाइल के चुनाव के सपोर्ट नइखे", - "ooui-selectfile-placeholder": "कौनों फाइल नइखे चुनल गइल", - "ooui-selectfile-dragdrop-placeholder": "फाइल इहाँ ड्रॉप करीं" -} diff --git a/resources/lib/oojs-ui/i18n/bn.json b/resources/lib/oojs-ui/i18n/bn.json deleted file mode 100644 index 90f96f9034..0000000000 --- a/resources/lib/oojs-ui/i18n/bn.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Aftab1995", - "Bellayet", - "Jayantanth", - "Nasir8891", - "Runab", - "Sayak Sarkar", - "Aftabuzzaman", - "RYasmeen (WMF)", - "NahidSultan", - "আফতাবুজ্জামান" - ] - }, - "ooui-outline-control-move-down": "আইটেম নিচে স্থানান্তর", - "ooui-outline-control-move-up": "আইটেম উপরে স্থানান্তর", - "ooui-outline-control-remove": "আইটেম সরান", - "ooui-toolbar-more": "আরও", - "ooui-toolgroup-expand": "আরও", - "ooui-toolgroup-collapse": "কম দেখাও", - "ooui-item-remove": "সরান", - "ooui-dialog-message-accept": "ঠিক আছে", - "ooui-dialog-message-reject": "বাতিল", - "ooui-dialog-process-error": "কিছু একটায় ত্রুটি হয়েছে", - "ooui-dialog-process-dismiss": "বাতিল করুন", - "ooui-dialog-process-retry": "আবার চেষ্টা করুন", - "ooui-dialog-process-continue": "অগ্রসর হোন", - "ooui-selectfile-button-select": "একটি ফাইল নির্বাচন করুন", - "ooui-selectfile-not-supported": "চিত্র নির্বাচন সমর্থিত নয়", - "ooui-selectfile-placeholder": "কোন চিত্র নির্বাচিত হয়নি", - "ooui-selectfile-dragdrop-placeholder": "এখানে ফাইল ছাড়ুন" -} diff --git a/resources/lib/oojs-ui/i18n/bqi.json b/resources/lib/oojs-ui/i18n/bqi.json deleted file mode 100644 index a0e53b3684..0000000000 --- a/resources/lib/oojs-ui/i18n/bqi.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Mogoeilor" - ] - }, - "ooui-outline-control-move-down": "ڤا دڤۈن بوردن آیتم", - "ooui-outline-control-move-up": "ڤارو بردن آیتم", - "ooui-outline-control-remove": "ڤورداشتن آیتم", - "ooui-toolbar-more": "بیشتر", - "ooui-toolgroup-expand": "بیشتر", - "ooui-toolgroup-collapse": "کمتر", - "ooui-dialog-message-accept": "خۈڤإ", - "ooui-dialog-message-reject": "أنجومشيڤ کردن", - "ooui-dialog-process-error": "یأ چي ايچو إشتوا إ", - "ooui-dialog-process-retry": "ز نۉ تلاش کونين", - "ooui-dialog-process-continue": "ديندا گرهڌن", - "ooui-selectfile-button-select": "گولإڤورچين کردن جانیا", - "ooui-selectfile-not-supported": "گول ڤورچی کردن جانیا کونشتکاری نڤابیڌ", - "ooui-selectfile-placeholder": "هيژ جانيایي گولإ ڤورچين نڤابيڌإ", - "ooui-selectfile-dragdrop-placeholder": "جانيانأ ڤأنين ايچو" -} diff --git a/resources/lib/oojs-ui/i18n/br.json b/resources/lib/oojs-ui/i18n/br.json deleted file mode 100644 index 498d13331b..0000000000 --- a/resources/lib/oojs-ui/i18n/br.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Fohanno", - "Fulup", - "Y-M D", - "Maoris", - "Gwendal" - ] - }, - "ooui-outline-control-move-down": "Lakaat an elfenn da ziskenn", - "ooui-outline-control-move-up": "Lakaat an elfenn da bignat", - "ooui-outline-control-remove": "Tennañ an elfenn", - "ooui-toolbar-more": "Muioc'h", - "ooui-toolgroup-expand": "Muioc'h", - "ooui-toolgroup-collapse": "Nebeutoc'h", - "ooui-item-remove": "Dilemel", - "ooui-dialog-message-accept": "Mat eo", - "ooui-dialog-message-reject": "Nullañ", - "ooui-dialog-process-error": "Un dra bennak a-dreuz a zo bet", - "ooui-dialog-process-dismiss": "Disteurel", - "ooui-dialog-process-retry": "Klask en-dro", - "ooui-dialog-process-continue": "Kenderc'hel", - "ooui-selectfile-button-select": "Diuzañ ur restr", - "ooui-selectfile-not-supported": "N'eo ket skoret an diuzañ restroù", - "ooui-selectfile-placeholder": "N'eus bet diuzet restr ebet", - "ooui-selectfile-dragdrop-placeholder": "Lezel ar restr amañ", - "ooui-field-help": "Skoazell" -} diff --git a/resources/lib/oojs-ui/i18n/bs.json b/resources/lib/oojs-ui/i18n/bs.json deleted file mode 100644 index 2bc85233e8..0000000000 --- a/resources/lib/oojs-ui/i18n/bs.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "@metadata": { - "authors": [ - "DzWiki", - "Semso98", - "Srdjan m" - ] - }, - "ooui-outline-control-move-down": "Premjesti stavku dolje", - "ooui-outline-control-move-up": "Premjesti stavku gore", - "ooui-outline-control-remove": "Ukloni stavku", - "ooui-toolbar-more": "Više", - "ooui-toolgroup-expand": "Više", - "ooui-toolgroup-collapse": "Manje", - "ooui-item-remove": "Ukloni", - "ooui-dialog-message-accept": "U redu", - "ooui-dialog-message-reject": "Otkaži", - "ooui-dialog-process-error": "Nešto nije u redu", - "ooui-dialog-process-dismiss": "Odbaci", - "ooui-dialog-process-retry": "Pokušaj ponovo", - "ooui-dialog-process-continue": "Nastavi", - "ooui-selectfile-button-select": "Izaberite datoteku", - "ooui-selectfile-not-supported": "Izbor datoteke nije podržan", - "ooui-selectfile-placeholder": "Datoteka nije izabrana", - "ooui-selectfile-dragdrop-placeholder": "Prevucite datoteku ovdje" -} diff --git a/resources/lib/oojs-ui/i18n/ca.json b/resources/lib/oojs-ui/i18n/ca.json deleted file mode 100644 index e1d8753b5f..0000000000 --- a/resources/lib/oojs-ui/i18n/ca.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Alvaro Vidal-Abarca", - "Amire80", - "Arnaugir", - "Pginer", - "QuimGil", - "SMP", - "Vriullop", - "Toniher", - "Edustus", - "Davidpar", - "Maceleiro", - "Kippelboy", - "Macofe", - "Fitoschido" - ] - }, - "ooui-outline-control-move-down": "Baixa l'element", - "ooui-outline-control-move-up": "Puja l'element", - "ooui-outline-control-remove": "Esborra l'ítem", - "ooui-toolbar-more": "Més", - "ooui-toolgroup-expand": "Més", - "ooui-toolgroup-collapse": "Menys", - "ooui-item-remove": "Suprimeix", - "ooui-dialog-message-accept": "D'acord", - "ooui-dialog-message-reject": "Cancel·la", - "ooui-dialog-process-error": "Alguna cosa no ha funcionat", - "ooui-dialog-process-dismiss": "Descarta", - "ooui-dialog-process-retry": "Torneu-ho a provar", - "ooui-dialog-process-continue": "Continua", - "ooui-selectfile-button-select": "Seleccioneu un fitxer", - "ooui-selectfile-not-supported": "El tipus de fitxer no és compatible", - "ooui-selectfile-placeholder": "No s'ha seleccionat cap fitxer", - "ooui-selectfile-dragdrop-placeholder": "Deseu els arxius aquí", - "ooui-field-help": "Ajuda" -} diff --git a/resources/lib/oojs-ui/i18n/cdo.json b/resources/lib/oojs-ui/i18n/cdo.json deleted file mode 100644 index cb46b4371c..0000000000 --- a/resources/lib/oojs-ui/i18n/cdo.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Yejianfei" - ] - }, - "ooui-outline-control-move-down": "下移項目", - "ooui-outline-control-move-up": "上移項目", - "ooui-outline-control-remove": "移除項目", - "ooui-toolbar-more": "更価", - "ooui-toolgroup-expand": "更価", - "ooui-toolgroup-collapse": "更少", - "ooui-dialog-message-accept": "確定", - "ooui-dialog-message-reject": "取消", - "ooui-dialog-process-error": "什乇出毛病了", - "ooui-dialog-process-dismiss": "關閉", - "ooui-dialog-process-retry": "重試", - "ooui-dialog-process-continue": "繼續", - "ooui-selectfile-button-select": "選擇蜀萆文件", - "ooui-selectfile-not-supported": "𣍐支持選擇其文件", - "ooui-selectfile-placeholder": "未選文件", - "ooui-selectfile-dragdrop-placeholder": "共文件拖遘嚽塊" -} diff --git a/resources/lib/oojs-ui/i18n/ce.json b/resources/lib/oojs-ui/i18n/ce.json deleted file mode 100644 index 3f8dfb13ec..0000000000 --- a/resources/lib/oojs-ui/i18n/ce.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Amire80", - "Умар" - ] - }, - "ooui-outline-control-move-down": "Лаха яккха элемент", - "ooui-outline-control-move-up": "Лаккха яккха элемент", - "ooui-outline-control-remove": "ДӀадаха меттиг", - "ooui-toolbar-more": "Кхин", - "ooui-toolgroup-expand": "Дукха", - "ooui-toolgroup-collapse": "КӀезиг", - "ooui-dialog-message-accept": "ХӀаъ", - "ooui-dialog-message-reject": "Цаоьшу", - "ooui-dialog-process-continue": "Кхин дӀа", - "ooui-selectfile-button-select": "Харжа файл", - "ooui-selectfile-placeholder": "Файл хаьржина яц" -} diff --git a/resources/lib/oojs-ui/i18n/ckb.json b/resources/lib/oojs-ui/i18n/ckb.json deleted file mode 100644 index 999fae047a..0000000000 --- a/resources/lib/oojs-ui/i18n/ckb.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Calak", - "Muhammed taha", - "Serwan", - "Pirehelokan", - "Sarchia" - ] - }, - "ooui-toolbar-more": "زیاتر", - "ooui-toolgroup-expand": "زیاتر", - "ooui-toolgroup-collapse": "کەمتر", - "ooui-dialog-message-accept": "باشە", - "ooui-dialog-message-reject": "پاشگەزبوونەوە", - "ooui-dialog-process-error": "ھەڵەیەک ڕووی داوە", - "ooui-dialog-process-dismiss": "لێگەڕان", - "ooui-dialog-process-retry": "دیسان ھەوڵ بدە", - "ooui-dialog-process-continue": "درێژە بدە", - "ooui-selectfile-button-select": "پەڕگەیەک دەستنیشان بکە", - "ooui-selectfile-placeholder": "ھیچ فایلێک ھەڵنەبژێراوە", - "ooui-selectfile-dragdrop-placeholder": "پەڕگەکان بخەرە ئێرە" -} diff --git a/resources/lib/oojs-ui/i18n/co.json b/resources/lib/oojs-ui/i18n/co.json deleted file mode 100644 index 01d181d7b9..0000000000 --- a/resources/lib/oojs-ui/i18n/co.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Paulu" - ] - }, - "ooui-outline-control-move-down": "Fà falà l'ogettu", - "ooui-outline-control-move-up": "Fà cullà l'ogettu" -} diff --git a/resources/lib/oojs-ui/i18n/crh-cyrl.json b/resources/lib/oojs-ui/i18n/crh-cyrl.json deleted file mode 100644 index ccc0026981..0000000000 --- a/resources/lib/oojs-ui/i18n/crh-cyrl.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Don Alessandro" - ] - }, - "ooui-toolbar-more": "Даа зияде" -} diff --git a/resources/lib/oojs-ui/i18n/crh-latn.json b/resources/lib/oojs-ui/i18n/crh-latn.json deleted file mode 100644 index 7ad7b0bbc9..0000000000 --- a/resources/lib/oojs-ui/i18n/crh-latn.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Don Alessandro" - ] - }, - "ooui-toolbar-more": "Daa ziyade" -} diff --git a/resources/lib/oojs-ui/i18n/cs.json b/resources/lib/oojs-ui/i18n/cs.json deleted file mode 100644 index 5b78b1fd11..0000000000 --- a/resources/lib/oojs-ui/i18n/cs.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Chmee2", - "Jkjk", - "Juandev", - "Koo6", - "Littledogboy", - "Michaelbrabec", - "Mormegil", - "Polda18", - "Tchoř", - "ශ්වෙත", - "Vojtěch Dostál", - "Matěj Suchánek", - "Martin Urbanec" - ] - }, - "ooui-outline-control-move-down": "Přesunout položku dolů", - "ooui-outline-control-move-up": "Přesunout položku nahoru", - "ooui-outline-control-remove": "Odstranit položku", - "ooui-toolbar-more": "Další", - "ooui-toolgroup-expand": "Více", - "ooui-toolgroup-collapse": "Méně", - "ooui-item-remove": "Odebrat", - "ooui-dialog-message-accept": "OK", - "ooui-dialog-message-reject": "Storno", - "ooui-dialog-process-error": "Něco se pokazilo", - "ooui-dialog-process-dismiss": "Zavřít", - "ooui-dialog-process-retry": "Zkusit znovu", - "ooui-dialog-process-continue": "Pokračovat", - "ooui-selectfile-button-select": "Vybrat soubor", - "ooui-selectfile-not-supported": "Výběr souboru není podporován", - "ooui-selectfile-placeholder": "Nebyl vybrán žádný soubor", - "ooui-selectfile-dragdrop-placeholder": "Umístěte soubor sem", - "ooui-field-help": "Pomoc" -} diff --git a/resources/lib/oojs-ui/i18n/cu.json b/resources/lib/oojs-ui/i18n/cu.json deleted file mode 100644 index d627de06eb..0000000000 --- a/resources/lib/oojs-ui/i18n/cu.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "@metadata": { - "authors": [ - "ОйЛ" - ] - }, - "ooui-toolbar-more": "вѧщє", - "ooui-toolgroup-expand": "вѧщє", - "ooui-dialog-process-error": "нѣчьто ꙁълѣ сѧ авило" -} diff --git a/resources/lib/oojs-ui/i18n/cy.json b/resources/lib/oojs-ui/i18n/cy.json deleted file mode 100644 index a6238e082f..0000000000 --- a/resources/lib/oojs-ui/i18n/cy.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Lloffiwr", - "Robin Owain", - "ОйЛ", - "DChan (WMF)", - "Jdforrester", - "Ed g2s" - ] - }, - "ooui-outline-control-move-down": "Symud yr eitem i lawr", - "ooui-outline-control-move-up": "Symud yr eitem i fyny", - "ooui-outline-control-remove": "Tynnu'r eitem", - "ooui-toolbar-more": "Rhagor", - "ooui-toolgroup-expand": "Mwy", - "ooui-toolgroup-collapse": "Llai", - "ooui-item-remove": "Tynnu", - "ooui-dialog-message-accept": "Iawn", - "ooui-dialog-message-reject": "Canslo", - "ooui-dialog-process-error": "Aeth rhywbeth o’i le", - "ooui-dialog-process-dismiss": "Gadael", - "ooui-dialog-process-retry": "Ailgeisio", - "ooui-dialog-process-continue": "Parhau", - "ooui-selectfile-button-select": "Dewis ffeil", - "ooui-selectfile-not-supported": "Nid oes modd dewis ffeil", - "ooui-selectfile-placeholder": "Dim ffeil wedi'i dewis", - "ooui-selectfile-dragdrop-placeholder": "Gollwng ffeil yma", - "ooui-field-help": "Cymorth" -} diff --git a/resources/lib/oojs-ui/i18n/da.json b/resources/lib/oojs-ui/i18n/da.json deleted file mode 100644 index 0a75f1f393..0000000000 --- a/resources/lib/oojs-ui/i18n/da.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Cgtdk", - "Christian List", - "EileenSanda", - "Laketown", - "Palnatoke", - "Simeondahl", - "Tehnix", - "Macofe", - "Peter Alberti", - "Joedalton", - "Saederup92" - ] - }, - "ooui-outline-control-move-down": "Flyt ned", - "ooui-outline-control-move-up": "Flyt op", - "ooui-outline-control-remove": "Fjern element", - "ooui-toolbar-more": "Mere", - "ooui-toolgroup-expand": "Mere", - "ooui-toolgroup-collapse": "Færre", - "ooui-item-remove": "Fjern", - "ooui-dialog-message-accept": "OK", - "ooui-dialog-message-reject": "Afbryd", - "ooui-dialog-process-error": "Noget gik galt", - "ooui-dialog-process-retry": "Prøv igen", - "ooui-dialog-process-continue": "Fortsæt", - "ooui-selectfile-button-select": "Vælg en fil", - "ooui-selectfile-placeholder": "Ingen filer er valgt", - "ooui-selectfile-dragdrop-placeholder": "Smid filen her", - "ooui-field-help": "Hjælp" -} diff --git a/resources/lib/oojs-ui/i18n/de.json b/resources/lib/oojs-ui/i18n/de.json deleted file mode 100644 index 2b01df96df..0000000000 --- a/resources/lib/oojs-ui/i18n/de.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "@metadata": { - "authors": [ - "APPER", - "G.Hagedorn", - "Inkowik", - "Jcornelius", - "Jdforrester", - "Kghbln", - "Metalhead64", - "Murma174", - "Se4598", - "Tomabrafix" - ] - }, - "ooui-outline-control-move-down": "Element nach unten verschieben", - "ooui-outline-control-move-up": "Element nach oben verschieben", - "ooui-outline-control-remove": "Element entfernen", - "ooui-toolbar-more": "Mehr", - "ooui-toolgroup-expand": "Mehr", - "ooui-toolgroup-collapse": "Weniger", - "ooui-item-remove": "Entfernen", - "ooui-dialog-message-accept": "Okay", - "ooui-dialog-message-reject": "Abbrechen", - "ooui-dialog-process-error": "Etwas ist schief gelaufen", - "ooui-dialog-process-dismiss": "Ausblenden", - "ooui-dialog-process-retry": "Erneut versuchen", - "ooui-dialog-process-continue": "Fortfahren", - "ooui-selectfile-button-select": "Eine Datei auswählen", - "ooui-selectfile-not-supported": "Die Dateiauswahl wird nicht unterstützt", - "ooui-selectfile-placeholder": "Keine Datei ausgewählt", - "ooui-selectfile-dragdrop-placeholder": "Dateien hier ablegen", - "ooui-field-help": "Hilfe" -} diff --git a/resources/lib/oojs-ui/i18n/diq.json b/resources/lib/oojs-ui/i18n/diq.json deleted file mode 100644 index bf6b087337..0000000000 --- a/resources/lib/oojs-ui/i18n/diq.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Erdemaslancan", - "Gorizon", - "Kghbln", - "Marmase", - "Mirzali", - "Se4598", - "Kumkumuk" - ] - }, - "ooui-outline-control-move-down": "Bendi bere cêr", - "ooui-outline-control-move-up": "Bendi bere cor", - "ooui-outline-control-remove": "Obcey wedare", - "ooui-toolbar-more": "Zewbi", - "ooui-toolgroup-expand": "Dehana", - "ooui-toolgroup-collapse": "Deha tayn", - "ooui-dialog-message-accept": "TEMAM", - "ooui-dialog-message-reject": "Bıtexelne", - "ooui-dialog-process-error": "Tayê çi ğelet şi...", - "ooui-dialog-process-dismiss": "Racın", - "ooui-dialog-process-retry": "Fına bıcerbın", - "ooui-dialog-process-continue": "Dewam ke", - "ooui-selectfile-button-select": "Yu dosya weçinê", - "ooui-selectfile-not-supported": "Dosya weçinayış desteg nêvine na", - "ooui-selectfile-placeholder": "Dosya nêwçineya", - "ooui-selectfile-dragdrop-placeholder": "Dosya tiyara ake" -} diff --git a/resources/lib/oojs-ui/i18n/dsb.json b/resources/lib/oojs-ui/i18n/dsb.json deleted file mode 100644 index 7ad3f200c7..0000000000 --- a/resources/lib/oojs-ui/i18n/dsb.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Michawiki" - ] - }, - "ooui-outline-control-move-down": "Element dołoj pśesunuś", - "ooui-outline-control-move-up": "Element górjej pśesunuś", - "ooui-outline-control-remove": "Zapisk wótpóraś", - "ooui-toolbar-more": "Wěcej" -} diff --git a/resources/lib/oojs-ui/i18n/dty.json b/resources/lib/oojs-ui/i18n/dty.json deleted file mode 100644 index 21742b697d..0000000000 --- a/resources/lib/oojs-ui/i18n/dty.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "@metadata": { - "authors": [ - "जनक राज भट्ट" - ] - }, - "ooui-outline-control-move-down": "वस्तुलाई तल साददे", - "ooui-outline-control-move-up": "वस्तुलाई मथि साददे", - "ooui-outline-control-remove": "वस्तुलाई हटुन्या", - "ooui-toolbar-more": "झिक्क", - "ooui-toolgroup-expand": "झिक्क", - "ooui-toolgroup-collapse": "थोका", - "ooui-dialog-message-accept": "हुन्छ", - "ooui-dialog-message-reject": "रद्द", - "ooui-dialog-process-dismiss": "खारेज गद्दे", - "ooui-dialog-process-retry": "दोसरया प्रयास गर", - "ooui-dialog-process-continue": "जारी राख्या" -} diff --git a/resources/lib/oojs-ui/i18n/egl.json b/resources/lib/oojs-ui/i18n/egl.json deleted file mode 100644 index 624ecaa32e..0000000000 --- a/resources/lib/oojs-ui/i18n/egl.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Lévi", - "Gloria sah" - ] - }, - "ooui-outline-control-move-down": "Spôsta in bâs", - "ooui-outline-control-move-up": "Spôsta in êlt", - "ooui-outline-control-remove": "Armōv l'elemèint", - "ooui-toolbar-more": "Êter", - "ooui-dialog-message-accept": "'D acòrdi", - "ooui-dialog-message-reject": "Scanślèr" -} diff --git a/resources/lib/oojs-ui/i18n/el.json b/resources/lib/oojs-ui/i18n/el.json deleted file mode 100644 index bbc5d64681..0000000000 --- a/resources/lib/oojs-ui/i18n/el.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Astralnet", - "Dipa1965", - "Evropi", - "FocalPoint", - "Geraki", - "Glavkos", - "Nikosguard", - "Tifa93", - "Stam.nikos", - "Nikosgranturismogt" - ] - }, - "ooui-outline-control-move-down": "Μετακίνηση στοιχείου προς τα κάτω", - "ooui-outline-control-move-up": "Μετακίνηση στοιχείου προς τα επάνω", - "ooui-outline-control-remove": "Αφαίρεση στοιχείου", - "ooui-toolbar-more": "Περισσότερα", - "ooui-toolgroup-expand": "Περισσότερα", - "ooui-toolgroup-collapse": "Λιγότερα", - "ooui-item-remove": "Αφαίρεση", - "ooui-dialog-message-accept": "ΟΚ", - "ooui-dialog-message-reject": "Ακύρωση", - "ooui-dialog-process-error": "Κάτι πήγε στραβά", - "ooui-dialog-process-dismiss": "Απόρριψη", - "ooui-dialog-process-retry": "Δοκιμάστε ξανά", - "ooui-dialog-process-continue": "Συνέχεια", - "ooui-selectfile-button-select": "Επιλέξτε ένα αρχείο", - "ooui-selectfile-not-supported": "Επιλογή αρχείου δεν υποστηρίζεται", - "ooui-selectfile-placeholder": "Κανένα αρχείο δεν είναι επιλεγμένο", - "ooui-selectfile-dragdrop-placeholder": "Σύρετε το αρχείο εδώ", - "ooui-field-help": "Βοήθεια" -} diff --git a/resources/lib/oojs-ui/i18n/eml.json b/resources/lib/oojs-ui/i18n/eml.json deleted file mode 100644 index 6d9e8bf0b6..0000000000 --- a/resources/lib/oojs-ui/i18n/eml.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Gloria sah", - "Lévi" - ] - }, - "ooui-outline-control-move-down": "Spôsta in bâs", - "ooui-outline-control-move-up": "Spôsta in êlta", - "ooui-outline-control-remove": "Tór vìa 'l elemèint", - "ooui-toolbar-more": "Êter", - "ooui-dialog-message-accept": "'D acòrdi", - "ooui-dialog-message-reject": "Scanślèr" -} diff --git a/resources/lib/oojs-ui/i18n/en-ca.json b/resources/lib/oojs-ui/i18n/en-ca.json deleted file mode 100644 index 1a8e31bee2..0000000000 --- a/resources/lib/oojs-ui/i18n/en-ca.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Skyllful" - ] - }, - "ooui-outline-control-move-down": "Move item down", - "ooui-outline-control-move-up": "Move item up", - "ooui-outline-control-remove": "Remove item", - "ooui-toolbar-more": "More", - "ooui-toolgroup-expand": "More", - "ooui-toolgroup-collapse": "Less", - "ooui-dialog-message-accept": "OK", - "ooui-dialog-message-reject": "Cancel", - "ooui-dialog-process-error": "Something went wrong", - "ooui-dialog-process-dismiss": "Dismiss", - "ooui-dialog-process-retry": "Try again", - "ooui-dialog-process-continue": "Continue", - "ooui-selectfile-not-supported": "File(s) not supported", - "ooui-selectfile-placeholder": "No file selected", - "ooui-selectfile-dragdrop-placeholder": "Drop file here (or click to browse your computer)" -} diff --git a/resources/lib/oojs-ui/i18n/en-gb.json b/resources/lib/oojs-ui/i18n/en-gb.json deleted file mode 100644 index 5bdc6f47b8..0000000000 --- a/resources/lib/oojs-ui/i18n/en-gb.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Pierpao" - ] - }, - "ooui-dialog-message-accept": "OK", - "ooui-dialog-message-reject": "Cancel", - "ooui-dialog-process-dismiss": "Dismiss", - "ooui-dialog-process-retry": "Try again", - "ooui-dialog-process-continue": "Continue", - "ooui-selectfile-button-select": "Select a file", - "ooui-selectfile-not-supported": "File selection is not supported", - "ooui-selectfile-placeholder": "No file is selected" -} diff --git a/resources/lib/oojs-ui/i18n/en.json b/resources/lib/oojs-ui/i18n/en.json deleted file mode 100644 index 7ccd746d77..0000000000 --- a/resources/lib/oojs-ui/i18n/en.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Trevor Parscal", - "Ed Sanders", - "James D. Forrester", - "Raimond Spekking", - "Erik Moeller", - "Moriel Schottlender", - "Yuki Shira", - "Siebrand Mazeland", - "Rob Moen", - "Timo Tijhof", - "Roan Kattouw", - "Christian Williams", - "Amir E. Aharoni" - ] - }, - "ooui-outline-control-move-down": "Move item down", - "ooui-outline-control-move-up": "Move item up", - "ooui-outline-control-remove": "Remove item", - "ooui-toolbar-more": "More", - "ooui-toolgroup-expand": "More", - "ooui-toolgroup-collapse": "Fewer", - "ooui-item-remove": "Remove", - "ooui-dialog-message-accept": "OK", - "ooui-dialog-message-reject": "Cancel", - "ooui-dialog-process-error": "Something went wrong", - "ooui-dialog-process-dismiss": "Dismiss", - "ooui-dialog-process-retry": "Try again", - "ooui-dialog-process-continue": "Continue", - "ooui-selectfile-button-select": "Select a file", - "ooui-selectfile-not-supported": "File selection is not supported", - "ooui-selectfile-placeholder": "No file is selected", - "ooui-selectfile-dragdrop-placeholder": "Drop file here", - "ooui-field-help": "Help" -} diff --git a/resources/lib/oojs-ui/i18n/eo.json b/resources/lib/oojs-ui/i18n/eo.json deleted file mode 100644 index d27da01340..0000000000 --- a/resources/lib/oojs-ui/i18n/eo.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Happy5214", - "KuboF", - "Shirayuki", - "Yekrats", - "Kvardek du", - "Psychoslave", - "Fitoschido" - ] - }, - "ooui-outline-control-move-down": "Movi eron suben", - "ooui-outline-control-move-up": "Movi eron supren", - "ooui-outline-control-remove": "Forigi eron", - "ooui-toolbar-more": "Pli", - "ooui-toolgroup-expand": "Pli", - "ooui-toolgroup-collapse": "Mapli", - "ooui-dialog-message-accept": "Bone", - "ooui-dialog-message-reject": "Nuligi", - "ooui-dialog-process-error": "Io rompiĝis", - "ooui-dialog-process-dismiss": "Elimini", - "ooui-dialog-process-retry": "Reprovi", - "ooui-dialog-process-continue": "Daŭrigi", - "ooui-selectfile-button-select": "Elekti dosieron", - "ooui-selectfile-not-supported": "Dosieroselekto ne estas subtenata.", - "ooui-selectfile-placeholder": "Vi ne selektis dosieron", - "ooui-selectfile-dragdrop-placeholder": "Ĵetu dosieron ĉi tie.", - "ooui-field-help": "Helpo" -} diff --git a/resources/lib/oojs-ui/i18n/es.json b/resources/lib/oojs-ui/i18n/es.json deleted file mode 100644 index 35943b0263..0000000000 --- a/resources/lib/oojs-ui/i18n/es.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Armando-Martin", - "Aruizdr", - "Benfutbol10", - "DJ Nietzsche", - "Erdemaslancan", - "Fitoschido", - "Imre", - "Invadinado", - "Jdforrester", - "Jduranboger", - "PoLuX124", - "Ralgis", - "Thehelpfulone", - "Gloria sah", - "Macofe" - ] - }, - "ooui-outline-control-move-down": "Bajar elemento", - "ooui-outline-control-move-up": "Subir elemento", - "ooui-outline-control-remove": "Eliminar elemento", - "ooui-toolbar-more": "Más", - "ooui-toolgroup-expand": "Más", - "ooui-toolgroup-collapse": "Menos", - "ooui-item-remove": "Quitar", - "ooui-dialog-message-accept": "Aceptar", - "ooui-dialog-message-reject": "Cancelar", - "ooui-dialog-process-error": "Algo salió mal", - "ooui-dialog-process-dismiss": "Descartar", - "ooui-dialog-process-retry": "Intentar de nuevo", - "ooui-dialog-process-continue": "Continuar", - "ooui-selectfile-button-select": "Selecciona un archivo", - "ooui-selectfile-not-supported": "No se admite la selección de archivos", - "ooui-selectfile-placeholder": "Ningún archivo seleccionado", - "ooui-selectfile-dragdrop-placeholder": "Suelta el archivo aquí", - "ooui-field-help": "Ayuda" -} diff --git a/resources/lib/oojs-ui/i18n/et.json b/resources/lib/oojs-ui/i18n/et.json deleted file mode 100644 index 326baaff1c..0000000000 --- a/resources/lib/oojs-ui/i18n/et.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Avjoska", - "Pikne", - "Suwa" - ] - }, - "ooui-outline-control-move-down": "Liiguta üksust allapoole", - "ooui-outline-control-move-up": "Liiguta üksust ülespoole", - "ooui-outline-control-remove": "Eemalda üksus", - "ooui-toolbar-more": "Veel", - "ooui-toolgroup-expand": "Veel", - "ooui-toolgroup-collapse": "Vähem", - "ooui-dialog-message-accept": "Sobib", - "ooui-dialog-message-reject": "Loobu", - "ooui-dialog-process-error": "Midagi läks valesti", - "ooui-dialog-process-dismiss": "Sule", - "ooui-dialog-process-retry": "Proovi uuesti", - "ooui-dialog-process-continue": "Jätka", - "ooui-selectfile-button-select": "Vali fail", - "ooui-selectfile-not-supported": "Faili valiku tugi puudub", - "ooui-selectfile-placeholder": "Faili ei ole valitud", - "ooui-selectfile-dragdrop-placeholder": "Lohista fail siia" -} diff --git a/resources/lib/oojs-ui/i18n/eu.json b/resources/lib/oojs-ui/i18n/eu.json deleted file mode 100644 index f87d11fd63..0000000000 --- a/resources/lib/oojs-ui/i18n/eu.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "@metadata": { - "authors": [ - "An13sa", - "Unai Fdz. de Betoño", - "Xabier Armendaritz", - "Subi", - "Sator", - "Mikel Ibaiba", - "Fitoschido" - ] - }, - "ooui-outline-control-move-down": "Mugitu itema beherantz", - "ooui-outline-control-move-up": "Mugitu itema gorantz", - "ooui-outline-control-remove": "Elementua kendu", - "ooui-toolbar-more": "Gehiago", - "ooui-toolgroup-expand": "Gehiago", - "ooui-toolgroup-collapse": "Gutxiago", - "ooui-item-remove": "Ezabatu", - "ooui-dialog-message-accept": "Ados", - "ooui-dialog-message-reject": "Utzi", - "ooui-dialog-process-error": "Zerbaitek huts egin du", - "ooui-dialog-process-dismiss": "Utzi", - "ooui-dialog-process-retry": "Saiatu berriro", - "ooui-dialog-process-continue": "Jarraitu", - "ooui-selectfile-button-select": "Fitxategi bat aukeratu", - "ooui-selectfile-not-supported": "Fitxategi aukeraketa ez da onartzen", - "ooui-selectfile-placeholder": "Ez da fitxategirik hautatu", - "ooui-selectfile-dragdrop-placeholder": "Fitxategia hemen utzi", - "ooui-field-help": "Laguntza" -} diff --git a/resources/lib/oojs-ui/i18n/fa.json b/resources/lib/oojs-ui/i18n/fa.json deleted file mode 100644 index e6e44637b7..0000000000 --- a/resources/lib/oojs-ui/i18n/fa.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Dalba", - "Ebraminio", - "Jdforrester", - "Ladsgroup", - "Mjbmr", - "Nojan Madinehi", - "Reza1615", - "Taha", - "درفش کاویانی", - "Armin1392", - "Alirezaaa", - "Leyth", - "الناز", - "فلورانس", - "Alireza Ivaz" - ] - }, - "ooui-outline-control-move-down": "انتقال مورد به پایین", - "ooui-outline-control-move-up": "انتقال مورد به بالا", - "ooui-outline-control-remove": "حذف مورد", - "ooui-toolbar-more": "بیشتر", - "ooui-toolgroup-expand": "بیشتر", - "ooui-toolgroup-collapse": "کمتر", - "ooui-item-remove": "حذف", - "ooui-dialog-message-accept": "تأیید", - "ooui-dialog-message-reject": "لغو", - "ooui-dialog-process-error": "مشکلی وجود دارد", - "ooui-dialog-process-dismiss": "رد", - "ooui-dialog-process-retry": "دوباره امتحان کنید", - "ooui-dialog-process-continue": "ادامه", - "ooui-selectfile-button-select": "یک فایل انتخاب کنید", - "ooui-selectfile-not-supported": "انتخاب پرونده پشتیبانی نمی‌شود", - "ooui-selectfile-placeholder": "هیچ پرونده‌ای انتخاب نشده است", - "ooui-selectfile-dragdrop-placeholder": "فایل را اینجا رها کنید", - "ooui-field-help": "راهنما" -} diff --git a/resources/lib/oojs-ui/i18n/fi.json b/resources/lib/oojs-ui/i18n/fi.json deleted file mode 100644 index 4c38f00899..0000000000 --- a/resources/lib/oojs-ui/i18n/fi.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Beluga", - "Crt", - "Harriv", - "Linnea", - "Nedergard", - "Nike", - "Olli", - "Pxos", - "Samoasambia", - "Silvonen", - "Skalman", - "Stryn", - "VezonThunder", - "Alluk.", - "Pyscowicz" - ] - }, - "ooui-outline-control-move-down": "Siirrä kohdetta alaspäin", - "ooui-outline-control-move-up": "Siirrä kohdetta ylöspäin", - "ooui-outline-control-remove": "Poista kohde", - "ooui-toolbar-more": "Lisää", - "ooui-toolgroup-expand": "Näytä lisää", - "ooui-toolgroup-collapse": "Näytä vähemmän", - "ooui-item-remove": "Poista", - "ooui-dialog-message-accept": "OK", - "ooui-dialog-message-reject": "Peru", - "ooui-dialog-process-error": "Jokin meni pieleen", - "ooui-dialog-process-dismiss": "Hylkää", - "ooui-dialog-process-retry": "Yritä uudelleen", - "ooui-dialog-process-continue": "Jatka", - "ooui-selectfile-button-select": "Valitse tiedosto", - "ooui-selectfile-not-supported": "Tiedoston valitsemista ei tueta", - "ooui-selectfile-placeholder": "Tiedostoa ei ole valittu", - "ooui-selectfile-dragdrop-placeholder": "Pudota tiedosto tähän", - "ooui-field-help": "Ohje" -} diff --git a/resources/lib/oojs-ui/i18n/fo.json b/resources/lib/oojs-ui/i18n/fo.json deleted file mode 100644 index 6230cc9b4f..0000000000 --- a/resources/lib/oojs-ui/i18n/fo.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "@metadata": { - "authors": [ - "EileenSanda" - ] - }, - "ooui-outline-control-move-down": "Flyt lutin niður", - "ooui-outline-control-move-up": "Flyt lutin upp", - "ooui-outline-control-remove": "Tak ein lut burtur", - "ooui-toolbar-more": "Meira", - "ooui-toolgroup-expand": "Meira", - "ooui-toolgroup-collapse": "Færri", - "ooui-dialog-message-accept": "OK", - "ooui-dialog-message-reject": "Avbrót", - "ooui-dialog-process-error": "Okkurt gekk galið", - "ooui-dialog-process-dismiss": "Lat aftur", - "ooui-dialog-process-retry": "Royn aftur", - "ooui-dialog-process-continue": "Halt fram" -} diff --git a/resources/lib/oojs-ui/i18n/fr.json b/resources/lib/oojs-ui/i18n/fr.json deleted file mode 100644 index a42687536e..0000000000 --- a/resources/lib/oojs-ui/i18n/fr.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Automatik", - "Benoit Rochon", - "Boniface", - "Brunoperel", - "Crochet.david", - "DavidL", - "Dereckson", - "Gomoko", - "Guillom", - "Hello71", - "Jean-Frédéric", - "Linedwell", - "Ltrlg", - "Metroitendo", - "NemesisIII", - "Nicolas NALLET", - "Npettiaux", - "Rastus Vernon", - "Seb35", - "Sherbrooke", - "Tpt", - "Trizek", - "Urhixidur", - "Verdy p", - "Wyz", - "SnowedEarth", - "Jdforrester", - "Wladek92", - "Harmonia Amanda", - "The RedBurn" - ] - }, - "ooui-outline-control-move-down": "Descendre l’élément", - "ooui-outline-control-move-up": "Monter l’élément", - "ooui-outline-control-remove": "Supprimer l’élément", - "ooui-toolbar-more": "Plus", - "ooui-toolgroup-expand": "Plus", - "ooui-toolgroup-collapse": "Moins", - "ooui-item-remove": "Supprimer", - "ooui-dialog-message-accept": "OK", - "ooui-dialog-message-reject": "Annuler", - "ooui-dialog-process-error": "Quelque chose s'est mal passé", - "ooui-dialog-process-dismiss": "Fermer", - "ooui-dialog-process-retry": "Réessayer", - "ooui-dialog-process-continue": "Continuer", - "ooui-selectfile-button-select": "Sélectionner un fichier", - "ooui-selectfile-not-supported": "La sélection de fichier n’est pas prise en charge", - "ooui-selectfile-placeholder": "Aucun fichier sélectionné", - "ooui-selectfile-dragdrop-placeholder": "Déposer le fichier ici", - "ooui-field-help": "Aide" -} diff --git a/resources/lib/oojs-ui/i18n/frr.json b/resources/lib/oojs-ui/i18n/frr.json deleted file mode 100644 index 54d0fb22d4..0000000000 --- a/resources/lib/oojs-ui/i18n/frr.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "@metadata": { - "authors": [ - "ChrisPtDe", - "Murma174" - ] - }, - "ooui-outline-control-move-down": "Element efter onern sküüw", - "ooui-outline-control-move-up": "Element efter boowen sküüw", - "ooui-outline-control-remove": "Element wechnem", - "ooui-toolbar-more": "Muar" -} diff --git a/resources/lib/oojs-ui/i18n/fur.json b/resources/lib/oojs-ui/i18n/fur.json deleted file mode 100644 index 83c2fd9ea3..0000000000 --- a/resources/lib/oojs-ui/i18n/fur.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Klenje", - "Tocaibon" - ] - }, - "ooui-outline-control-move-down": "sposte sot", - "ooui-outline-control-move-up": "sposte in su", - "ooui-toolbar-more": "Altri" -} diff --git a/resources/lib/oojs-ui/i18n/fy.json b/resources/lib/oojs-ui/i18n/fy.json deleted file mode 100644 index a552c22c95..0000000000 --- a/resources/lib/oojs-ui/i18n/fy.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Robin0van0der0vliet", - "Jdforrester", - "Robin van der Vliet" - ] - }, - "ooui-toolbar-more": "Mear", - "ooui-toolgroup-expand": "Mear", - "ooui-toolgroup-collapse": "Minder", - "ooui-item-remove": "Fuortsmite", - "ooui-dialog-message-accept": "OK", - "ooui-dialog-message-reject": "Annulearje", - "ooui-field-help": "Help" -} diff --git a/resources/lib/oojs-ui/i18n/gd.json b/resources/lib/oojs-ui/i18n/gd.json deleted file mode 100644 index 6a83c9c027..0000000000 --- a/resources/lib/oojs-ui/i18n/gd.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "@metadata": { - "authors": [ - "GunChleoc" - ] - }, - "ooui-outline-control-move-down": "Gluais nì sìos", - "ooui-outline-control-move-up": "Gluais nì suas", - "ooui-outline-control-remove": "Thoir air falbh an nì", - "ooui-toolbar-more": "Barrachd", - "ooui-dialog-message-accept": "Ceart ma-thà", - "ooui-dialog-message-reject": "Sguir dheth" -} diff --git a/resources/lib/oojs-ui/i18n/gl.json b/resources/lib/oojs-ui/i18n/gl.json deleted file mode 100644 index 22174f796a..0000000000 --- a/resources/lib/oojs-ui/i18n/gl.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Alison", - "Kscanne", - "Toliño", - "Elisardojm", - "Banjo", - "Fitoschido" - ] - }, - "ooui-outline-control-move-down": "Mover o elemento abaixo", - "ooui-outline-control-move-up": "Mover o elemento arriba", - "ooui-outline-control-remove": "Eliminar o elemento", - "ooui-toolbar-more": "Máis", - "ooui-toolgroup-expand": "Máis", - "ooui-toolgroup-collapse": "Menos", - "ooui-item-remove": "Eliminar", - "ooui-dialog-message-accept": "Aceptar", - "ooui-dialog-message-reject": "Cancelar", - "ooui-dialog-process-error": "Algo foi mal", - "ooui-dialog-process-dismiss": "Agochar", - "ooui-dialog-process-retry": "Inténteo de novo", - "ooui-dialog-process-continue": "Continuar", - "ooui-selectfile-button-select": "Seleccionar un ficheiro", - "ooui-selectfile-not-supported": "Non está soportada a selección de ficheiros", - "ooui-selectfile-placeholder": "Non se seleccionou ningún ficheiro", - "ooui-selectfile-dragdrop-placeholder": "Solte un ficheiro aquí", - "ooui-field-help": "Axuda" -} diff --git a/resources/lib/oojs-ui/i18n/glk.json b/resources/lib/oojs-ui/i18n/glk.json deleted file mode 100644 index e602062163..0000000000 --- a/resources/lib/oojs-ui/i18n/glk.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "@metadata": { - "authors": [ - "V6rg", - "شیخ" - ] - }, - "ooui-outline-control-move-down": "مأسمکه جابجا بۊکۊن جير", - "ooui-outline-control-move-up": "مأسمکه جابجا بۊکۊن جؤر", - "ooui-outline-control-remove": "مأسمکه حذفأکۊن", - "ooui-toolbar-more": "ويشتر", - "ooui-toolgroup-expand": "ويشتر", - "ooui-toolgroup-collapse": "کمتر", - "ooui-dialog-message-accept": "خؤ", - "ooui-dialog-message-reject": "لغو", - "ooui-dialog-process-error": "ىک مؤشکلي هنأ", - "ooui-dialog-process-dismiss": "وأبدي", - "ooui-dialog-process-retry": "هندئه حقسأى بۊکۊنين", - "ooui-dialog-process-continue": "ايدامه", - "ooui-selectfile-button-select": "ىکته فاىله دؤجين بۊکۊنين", - "ooui-selectfile-not-supported": "نشأنه فاىله دؤجين گۊدن", - "ooui-selectfile-placeholder": "هيچ فاىلي دؤجين نۊبؤ", - "ooui-selectfile-dragdrop-placeholder": "فاىله ائره رها بکۊنين" -} diff --git a/resources/lib/oojs-ui/i18n/gom-latn.json b/resources/lib/oojs-ui/i18n/gom-latn.json deleted file mode 100644 index afd471002d..0000000000 --- a/resources/lib/oojs-ui/i18n/gom-latn.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "@metadata": { - "authors": [ - "The Discoverer" - ] - }, - "ooui-toolbar-more": "Anik", - "ooui-toolgroup-expand": "Anik", - "ooui-dialog-message-reject": "Rodd'dd kor", - "ooui-dialog-process-retry": "Porot proyotn kor", - "ooui-selectfile-button-select": "Ek fayl nivodd", - "ooui-selectfile-placeholder": "Khuimchech fayl nivddunk nam", - "ooui-selectfile-dragdrop-placeholder": "Fayl hanga udoi" -} diff --git a/resources/lib/oojs-ui/i18n/gor.json b/resources/lib/oojs-ui/i18n/gor.json deleted file mode 100644 index 9fcf1390cc..0000000000 --- a/resources/lib/oojs-ui/i18n/gor.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Marwan Mohamad", - "Fitoschido" - ] - }, - "ooui-outline-control-move-down": "Heyiya botu ode tibawa", - "ooui-outline-control-move-up": "Heyiya botu ode yitaato", - "ooui-outline-control-remove": "Yinggila botu", - "ooui-toolbar-more": "Pe'eentapo", - "ooui-toolgroup-expand": "Pe'eentapo", - "ooui-toolgroup-collapse": "ngoolo botu", - "ooui-dialog-message-accept": "Jo", - "ooui-dialog-message-reject": "Bataliya", - "ooui-dialog-process-error": "Woluwo u yilotalawa", - "ooui-dialog-process-dismiss": "He'uti", - "ooui-dialog-process-retry": "Yimontali pooli", - "ooui-dialog-process-continue": "Turusi", - "ooui-selectfile-button-select": "Tulawota berkas tuwawu", - "ooui-selectfile-not-supported": "Berkas tilulawoto ja motuhatawa", - "ooui-selectfile-placeholder": "Diya'a berkas u letulawoto", - "ooui-selectfile-dragdrop-placeholder": "Dutuwa berkas teeya", - "ooui-field-help": "Wubodu" -} diff --git a/resources/lib/oojs-ui/i18n/gu.json b/resources/lib/oojs-ui/i18n/gu.json deleted file mode 100644 index e7dfeb2043..0000000000 --- a/resources/lib/oojs-ui/i18n/gu.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Ashok modhvadia", - "KartikMistry", - "The Discoverer", - "NehalDaveND", - "Dsvyas" - ] - }, - "ooui-outline-control-move-down": "વસ્તુ નીચે ખસેડો", - "ooui-outline-control-move-up": "વસ્તુ ઉપર ખસેડો", - "ooui-outline-control-remove": "વસ્તુ હટાવો", - "ooui-toolbar-more": "વધુ", - "ooui-toolgroup-expand": "વધુ", - "ooui-toolgroup-collapse": "ઓછા", - "ooui-dialog-message-accept": "બરાબર", - "ooui-dialog-message-reject": "રદ કરો", - "ooui-dialog-process-error": "કંઇક ગરબડ થઇ", - "ooui-dialog-process-dismiss": "વિસર્જન", - "ooui-dialog-process-retry": "ફરી પ્રયત્ન કરો", - "ooui-dialog-process-continue": "ચાલુ રાખો", - "ooui-selectfile-button-select": "ફાઈલ પસંદ કરો", - "ooui-selectfile-not-supported": "ફાઇલ પસંદગીની જોગવાઈ નથી", - "ooui-selectfile-placeholder": "કોઇ ફાઇલ પસંદ નથી કરાઈ", - "ooui-selectfile-dragdrop-placeholder": "અહીં ફાઇલ મૂકો" -} diff --git a/resources/lib/oojs-ui/i18n/he.json b/resources/lib/oojs-ui/i18n/he.json deleted file mode 100644 index e40820d6df..0000000000 --- a/resources/lib/oojs-ui/i18n/he.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Amire80", - "ExampleTomer", - "Guycn2", - "Matanya", - "Mooeypoo", - "Orsa", - "Shimmin Beg", - "אור שפירא", - "חיים", - "ערן", - "פוילישער", - "קיפודנחש" - ] - }, - "ooui-outline-control-move-down": "להזיז את הפריט מטה", - "ooui-outline-control-move-up": "להזיז את הפריט מעלה", - "ooui-outline-control-remove": "להסיר את הפריט", - "ooui-toolbar-more": "עוד", - "ooui-toolgroup-expand": "יותר", - "ooui-toolgroup-collapse": "פחות", - "ooui-item-remove": "הסרה", - "ooui-dialog-message-accept": "אישור", - "ooui-dialog-message-reject": "ביטול", - "ooui-dialog-process-error": "משהו השתבש", - "ooui-dialog-process-dismiss": "לוותר", - "ooui-dialog-process-retry": "לנסות שוב", - "ooui-dialog-process-continue": "המשך", - "ooui-selectfile-button-select": "נא לבחור קובץ", - "ooui-selectfile-not-supported": "בחירת קבצים אינה נתמכת", - "ooui-selectfile-placeholder": "לא נבחר שום קובץ", - "ooui-selectfile-dragdrop-placeholder": "נא לשחרר את הקובץ כאן", - "ooui-field-help": "עזרה" -} diff --git a/resources/lib/oojs-ui/i18n/hi.json b/resources/lib/oojs-ui/i18n/hi.json deleted file mode 100644 index 2bb5559dc6..0000000000 --- a/resources/lib/oojs-ui/i18n/hi.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Ansumang", - "Devayon", - "Rajesh", - "Siddhartha Ghai", - "Goelujjwal", - "Ankita-ks", - "Param Mudgal", - "Sfic", - "Rishi.Singh" - ] - }, - "ooui-outline-control-move-down": "प्रविष्टि नीचे ले जाएँ", - "ooui-outline-control-move-up": "प्रविष्टि ऊपर ले जाएँ", - "ooui-outline-control-remove": "आइटम हटाएँ", - "ooui-toolbar-more": "अधिक", - "ooui-toolgroup-expand": "अधिक", - "ooui-toolgroup-collapse": "कम", - "ooui-item-remove": "हटायें", - "ooui-dialog-message-accept": "ठीक है", - "ooui-dialog-message-reject": "रद्द करें", - "ooui-dialog-process-error": "कुछ गलत हुअा है", - "ooui-dialog-process-dismiss": "ख़ारिज करें", - "ooui-dialog-process-retry": "पुनः प्रयास करें", - "ooui-dialog-process-continue": "जारी रखें", - "ooui-selectfile-button-select": "फ़ाइल चुनें", - "ooui-selectfile-not-supported": "फ़ाइल का चयन समर्थित नहीं है", - "ooui-selectfile-placeholder": "कोई फाइल चुनी नही गई हेै", - "ooui-selectfile-dragdrop-placeholder": "फ़ाइल यहाँ डालें" -} diff --git a/resources/lib/oojs-ui/i18n/hif-latn.json b/resources/lib/oojs-ui/i18n/hif-latn.json deleted file mode 100644 index 9e903ca5f1..0000000000 --- a/resources/lib/oojs-ui/i18n/hif-latn.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Thakurji" - ] - }, - "ooui-outline-control-move-down": "Item ke niche karo", - "ooui-outline-control-move-up": "Item ke uppar karo", - "ooui-outline-control-remove": "Item ke hatao", - "ooui-toolbar-more": "Aur", - "ooui-toolgroup-expand": "Aur", - "ooui-toolgroup-collapse": "Kamtii", - "ooui-dialog-message-accept": "OK", - "ooui-dialog-message-reject": "Cancel karo", - "ooui-dialog-process-error": "Koi chij wrong hoe gais", - "ooui-dialog-process-dismiss": "Dismiss karo", - "ooui-dialog-process-retry": "Fir se try karo", - "ooui-dialog-process-continue": "Continue", - "ooui-selectfile-button-select": "Ek file ke select karo", - "ooui-selectfile-not-supported": "File selection ke support nai karaa jaawe hai", - "ooui-selectfile-placeholder": "Koi file ke nai select karaa gais hai", - "ooui-selectfile-dragdrop-placeholder": "Hian pe file ke girao" -} diff --git a/resources/lib/oojs-ui/i18n/hr.json b/resources/lib/oojs-ui/i18n/hr.json deleted file mode 100644 index 90bb1a60cc..0000000000 --- a/resources/lib/oojs-ui/i18n/hr.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "@metadata": { - "authors": [ - "MaGa", - "Roberta F.", - "SpeedyGonsales", - "Zeljko.filipin", - "Bugoslav" - ] - }, - "ooui-outline-control-move-down": "Premjesti stavku dolje", - "ooui-outline-control-move-up": "Premjesti stavku gore", - "ooui-outline-control-remove": "Ukloni", - "ooui-toolbar-more": "Više", - "ooui-toolgroup-expand": "Više", - "ooui-toolgroup-collapse": "Manje", - "ooui-item-remove": "Ukloni", - "ooui-dialog-message-accept": "U redu", - "ooui-dialog-message-reject": "Odustani", - "ooui-dialog-process-error": "Nešto nije u redu", - "ooui-dialog-process-dismiss": "Zatvori", - "ooui-dialog-process-retry": "Pokušajte ponovo", - "ooui-dialog-process-continue": "Nastavi", - "ooui-selectfile-button-select": "Odaberi datoteku", - "ooui-selectfile-not-supported": "Izbor datoteke nije podržan", - "ooui-selectfile-placeholder": "Datoteka nije označena", - "ooui-selectfile-dragdrop-placeholder": "Povucite datoteku ovdje", - "ooui-field-help": "Pomoć" -} diff --git a/resources/lib/oojs-ui/i18n/hrx.json b/resources/lib/oojs-ui/i18n/hrx.json deleted file mode 100644 index 1534af7664..0000000000 --- a/resources/lib/oojs-ui/i18n/hrx.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Midnight Gambler" - ] - }, - "ooui-toolbar-more": "Meahr", - "ooui-toolgroup-expand": "Meahr", - "ooui-dialog-message-accept": "OK", - "ooui-dialog-message-reject": "Abbreche", - "ooui-dialog-process-dismiss": "Ausblenne" -} diff --git a/resources/lib/oojs-ui/i18n/hsb.json b/resources/lib/oojs-ui/i18n/hsb.json deleted file mode 100644 index 00894e4e37..0000000000 --- a/resources/lib/oojs-ui/i18n/hsb.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "@metadata": { - "authors": [ - "J budissin", - "Michawiki" - ] - }, - "ooui-outline-control-move-down": "Zapisk dele přesunyć", - "ooui-outline-control-move-up": "Zapisk horje přesunyć", - "ooui-outline-control-remove": "Zapisk wotstronić", - "ooui-toolbar-more": "Wjace", - "ooui-toolgroup-expand": "Wjace", - "ooui-toolgroup-collapse": "Mjenje", - "ooui-dialog-message-accept": "W porjadku", - "ooui-dialog-message-reject": "Přetorhnyć", - "ooui-dialog-process-error": "Něšto je so nimokuliło", - "ooui-dialog-process-dismiss": "Schować", - "ooui-dialog-process-retry": "Hišće raz spytać", - "ooui-dialog-process-continue": "Dale" -} diff --git a/resources/lib/oojs-ui/i18n/hu-formal.json b/resources/lib/oojs-ui/i18n/hu-formal.json deleted file mode 100644 index 34aa0ae638..0000000000 --- a/resources/lib/oojs-ui/i18n/hu-formal.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Misibacsi" - ] - }, - "ooui-outline-control-move-down": "Elem mozgatása lefelé", - "ooui-outline-control-move-up": "Elem mozgatása felfelé", - "ooui-outline-control-remove": "Elem eltávolítása", - "ooui-toolbar-more": "Tovább...", - "ooui-toolgroup-expand": "Tovább", - "ooui-toolgroup-collapse": "Kevesebb", - "ooui-dialog-message-accept": "Rendben", - "ooui-dialog-message-reject": "Mégse", - "ooui-dialog-process-error": "Valami elromlott.", - "ooui-dialog-process-dismiss": "Mégse", - "ooui-dialog-process-retry": "Próbálja újra", - "ooui-dialog-process-continue": "Folytatás", - "ooui-selectfile-not-supported": "A fájl kiválasztása nincs támogatva", - "ooui-selectfile-placeholder": "Nincs fájl kiválasztva" -} diff --git a/resources/lib/oojs-ui/i18n/hu.json b/resources/lib/oojs-ui/i18n/hu.json deleted file mode 100644 index 95d4188d0f..0000000000 --- a/resources/lib/oojs-ui/i18n/hu.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Dj", - "Einstein2", - "Misibacsi", - "ViDam", - "Tacsipacsi", - "Csega", - "Kishajnalka", - "Rodrigo", - "Bencemac" - ] - }, - "ooui-outline-control-move-down": "Elem mozgatása lefelé", - "ooui-outline-control-move-up": "Elem mozgatása felfelé", - "ooui-outline-control-remove": "Elem eltávolítása", - "ooui-toolbar-more": "Több", - "ooui-toolgroup-expand": "Több", - "ooui-toolgroup-collapse": "Kevesebb", - "ooui-item-remove": "Eltávolítás", - "ooui-dialog-message-accept": "OK", - "ooui-dialog-message-reject": "Mégse", - "ooui-dialog-process-error": "Valami elromlott", - "ooui-dialog-process-dismiss": "Elrejt", - "ooui-dialog-process-retry": "Próbáld újra", - "ooui-dialog-process-continue": "Folytatás", - "ooui-selectfile-button-select": "Fájl kiválasztása", - "ooui-selectfile-not-supported": "A fájl kiválasztása nincs támogatva", - "ooui-selectfile-placeholder": "Nincs fájl kiválasztva", - "ooui-selectfile-dragdrop-placeholder": "Dobd ide a fájlt", - "ooui-field-help": "Súgó" -} diff --git a/resources/lib/oojs-ui/i18n/hy.json b/resources/lib/oojs-ui/i18n/hy.json deleted file mode 100644 index 598fee35f0..0000000000 --- a/resources/lib/oojs-ui/i18n/hy.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Vacio", - "Xelgen", - "Դավիթ Սարոյան", - "Vahe Gharakhanyan", - "Kareyac" - ] - }, - "ooui-outline-control-move-down": "Իջեցնել ներքև", - "ooui-outline-control-move-up": "Բարձրացնել կետը", - "ooui-outline-control-remove": "Հեռացնել տարրը", - "ooui-toolbar-more": "Ավելին", - "ooui-toolgroup-expand": "Ավելին", - "ooui-toolgroup-collapse": "Պակաս", - "ooui-item-remove": "Հեռացնել", - "ooui-dialog-message-accept": "Լավ", - "ooui-dialog-message-reject": "Չեղարկել", - "ooui-dialog-process-error": "Ինչ-որ սխալ է տեղի ունեցել", - "ooui-dialog-process-dismiss": "Փակել", - "ooui-dialog-process-retry": "Կրկին փորձել", - "ooui-dialog-process-continue": "Շարունակել", - "ooui-selectfile-button-select": "Ընտրել նիշք", - "ooui-selectfile-not-supported": "Ֆայլի ընտրությունը չի պաշտպանվում", - "ooui-selectfile-placeholder": "Ֆայլն ընտրված չէ", - "ooui-selectfile-dragdrop-placeholder": "Ֆայլը գցել այստե" -} diff --git a/resources/lib/oojs-ui/i18n/ia.json b/resources/lib/oojs-ui/i18n/ia.json deleted file mode 100644 index 8b3bcd8034..0000000000 --- a/resources/lib/oojs-ui/i18n/ia.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "@metadata": { - "authors": [ - "McDutchie" - ] - }, - "ooui-outline-control-move-down": "Displaciar elemento in basso", - "ooui-outline-control-move-up": "Displaciar elemento in alto", - "ooui-outline-control-remove": "Remover elemento", - "ooui-toolbar-more": "Plus", - "ooui-toolgroup-expand": "Plus", - "ooui-toolgroup-collapse": "Minus", - "ooui-item-remove": "Remover", - "ooui-dialog-message-accept": "OK", - "ooui-dialog-message-reject": "Cancellar", - "ooui-dialog-process-error": "Qualcosa ha vadite mal", - "ooui-dialog-process-dismiss": "Clauder", - "ooui-dialog-process-retry": "Reprobar", - "ooui-dialog-process-continue": "Continuar", - "ooui-selectfile-button-select": "Selige un file", - "ooui-selectfile-not-supported": "Le selection de files non es supportate", - "ooui-selectfile-placeholder": "Nulle file es seligite", - "ooui-selectfile-dragdrop-placeholder": "Depone file hic" -} diff --git a/resources/lib/oojs-ui/i18n/id.json b/resources/lib/oojs-ui/i18n/id.json deleted file mode 100644 index 10c7e22d29..0000000000 --- a/resources/lib/oojs-ui/i18n/id.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Farras", - "Ilham151096", - "Iwan Novirion", - "Iyan", - "Kenrick95", - "McDutchie", - "Rv77ax", - "William Surya Permana", - "Rachmat.Wahidi", - "Rachmat04", - "Gombang" - ] - }, - "ooui-outline-control-move-down": "Pindahkan butir ke bawah", - "ooui-outline-control-move-up": "Pindahkan butir ke atas", - "ooui-outline-control-remove": "Hapus butir", - "ooui-toolbar-more": "Lainnya", - "ooui-toolgroup-expand": "Selengkapnya", - "ooui-toolgroup-collapse": "Secukupnya", - "ooui-item-remove": "Hapus", - "ooui-dialog-message-accept": "Oke", - "ooui-dialog-message-reject": "Batal", - "ooui-dialog-process-error": "Ada yang tidak beres", - "ooui-dialog-process-dismiss": "Tutup", - "ooui-dialog-process-retry": "Coba lagi", - "ooui-dialog-process-continue": "Lanjutkan", - "ooui-selectfile-button-select": "Pilih berkas", - "ooui-selectfile-not-supported": "Peilihan berkas tidak didukung", - "ooui-selectfile-placeholder": "Tidak ada berkas yang terpilih", - "ooui-selectfile-dragdrop-placeholder": "Letakkan berkas di sini" -} diff --git a/resources/lib/oojs-ui/i18n/ie.json b/resources/lib/oojs-ui/i18n/ie.json deleted file mode 100644 index 241cc3311a..0000000000 --- a/resources/lib/oojs-ui/i18n/ie.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Makuba" - ] - }, - "ooui-outline-control-move-down": "Mover element a infra", - "ooui-outline-control-move-up": "Mover element a supra", - "ooui-toolbar-more": "Plu" -} diff --git a/resources/lib/oojs-ui/i18n/ilo.json b/resources/lib/oojs-ui/i18n/ilo.json deleted file mode 100644 index 122a5ef367..0000000000 --- a/resources/lib/oojs-ui/i18n/ilo.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Lam-ang" - ] - }, - "ooui-outline-control-move-down": "Ipababa ti banag", - "ooui-outline-control-move-up": "Ipangato ti banag", - "ooui-outline-control-remove": "Ikkaten ti banag", - "ooui-toolbar-more": "Adu pay", - "ooui-toolgroup-expand": "Adu pay", - "ooui-toolgroup-collapse": "Basbassit", - "ooui-item-remove": "Ikkaten", - "ooui-dialog-message-accept": "Sige", - "ooui-dialog-message-reject": "Ukasen", - "ooui-dialog-process-error": "Adda madi a napasamak", - "ooui-dialog-process-dismiss": "Pugsayen", - "ooui-dialog-process-retry": "Padasen manen", - "ooui-dialog-process-continue": "Agtuloy", - "ooui-selectfile-button-select": "Agpili iti papeles", - "ooui-selectfile-not-supported": "Saan a masuportaran ti panagpili ti papeles", - "ooui-selectfile-placeholder": "Awan ti napili a papeles", - "ooui-selectfile-dragdrop-placeholder": "Itinnag ti papeles ditoy" -} diff --git a/resources/lib/oojs-ui/i18n/inh.json b/resources/lib/oojs-ui/i18n/inh.json deleted file mode 100644 index 73b6470546..0000000000 --- a/resources/lib/oojs-ui/i18n/inh.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Adam-Yourist", - "ElizaMag", - "Tusholi" - ] - }, - "ooui-outline-control-move-down": "Элемент Iолохеяккха", - "ooui-outline-control-move-up": "Элемент Iолакхеяккха", - "ooui-outline-control-remove": "ДIаяккха пункт", - "ooui-toolbar-more": "Кхы а", - "ooui-toolgroup-expand": "Дукха", - "ooui-toolgroup-collapse": "КӀезига", - "ooui-dialog-message-accept": "ОК", - "ooui-dialog-message-reject": "Эшац", - "ooui-dialog-process-error": "Харцахьа хилар цхьа хIама", - "ooui-dialog-process-dismiss": "ДIакъовла", - "ooui-dialog-process-retry": "Кхы цкъа де гIорта", - "ooui-dialog-process-continue": "ДIаьхде", - "ooui-selectfile-button-select": "Файл хьахаржа", - "ooui-selectfile-not-supported": "Файл харжа вIаштаь дац", - "ooui-selectfile-placeholder": "Файл хержа яц", - "ooui-selectfile-dragdrop-placeholder": "Укхаза хьадехьаяккха файл" -} diff --git a/resources/lib/oojs-ui/i18n/io.json b/resources/lib/oojs-ui/i18n/io.json deleted file mode 100644 index dafb3905da..0000000000 --- a/resources/lib/oojs-ui/i18n/io.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Idojc", - "Joao Xavier" - ] - }, - "ooui-outline-control-move-down": "Movar elemento adsube", - "ooui-outline-control-move-up": "Movar elemento adsupere", - "ooui-outline-control-remove": "Forigar elemento", - "ooui-toolbar-more": "Plu multa", - "ooui-toolgroup-expand": "Plu multa", - "ooui-toolgroup-collapse": "Min multa", - "ooui-item-remove": "Eliminar", - "ooui-dialog-message-accept": "Aplikar", - "ooui-dialog-message-reject": "Anular", - "ooui-dialog-process-error": "Ulo faliis", - "ooui-dialog-process-dismiss": "Celar", - "ooui-dialog-process-retry": "Riprobar", - "ooui-dialog-process-continue": "Durar", - "ooui-selectfile-button-select": "Selektar dokumento", - "ooui-selectfile-not-supported": "Dokumento-selekto ne esas suportata", - "ooui-selectfile-placeholder": "Nula dokumento selektesis", - "ooui-selectfile-dragdrop-placeholder": "Pozar dokumento hike" -} diff --git a/resources/lib/oojs-ui/i18n/is.json b/resources/lib/oojs-ui/i18n/is.json deleted file mode 100644 index 5a0d3e458e..0000000000 --- a/resources/lib/oojs-ui/i18n/is.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Maxí", - "Snævar", - "Sveinn í Felli" - ] - }, - "ooui-outline-control-move-down": "Færa atriði niður", - "ooui-outline-control-move-up": "Færa atriði upp", - "ooui-outline-control-remove": "Fjarlægja atriði", - "ooui-toolbar-more": "Fleira", - "ooui-toolgroup-expand": "Fleira", - "ooui-toolgroup-collapse": "Færra", - "ooui-item-remove": "Fjarlægja", - "ooui-dialog-message-accept": "Í lagi", - "ooui-dialog-message-reject": "Hætta við", - "ooui-dialog-process-error": "Eitthvað mistókst", - "ooui-dialog-process-dismiss": "Loka", - "ooui-dialog-process-retry": "Reyna aftur", - "ooui-dialog-process-continue": "Halda áfram", - "ooui-selectfile-button-select": "Velja skrá", - "ooui-selectfile-not-supported": "Skráar val er ekki stutt.", - "ooui-selectfile-placeholder": "Engin skrá er valin", - "ooui-selectfile-dragdrop-placeholder": "Slepptu skránni hérna", - "ooui-field-help": "Hjálp" -} diff --git a/resources/lib/oojs-ui/i18n/it.json b/resources/lib/oojs-ui/i18n/it.json deleted file mode 100644 index e90d4cfd78..0000000000 --- a/resources/lib/oojs-ui/i18n/it.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Beta16", - "Darth Kule", - "Doc.mari", - "Eleonora negri", - "Elitre", - "F. Cosoleto", - "FRacco", - "Gianfranco", - "Minerva Titani", - "Raoli", - "Una giornata uggiosa '94", - "Ontsed", - "Alexmar983", - "Nemo bis", - "Jdforrester", - "Fringio" - ] - }, - "ooui-outline-control-move-down": "Sposta in basso", - "ooui-outline-control-move-up": "Sposta in alto", - "ooui-outline-control-remove": "Rimuovi elemento", - "ooui-toolbar-more": "Altro", - "ooui-toolgroup-expand": "Altro", - "ooui-toolgroup-collapse": "Meno", - "ooui-item-remove": "Rimuovi", - "ooui-dialog-message-accept": "OK", - "ooui-dialog-message-reject": "Annulla", - "ooui-dialog-process-error": "Qualcosa è andato storto", - "ooui-dialog-process-dismiss": "Nascondi", - "ooui-dialog-process-retry": "Riprova", - "ooui-dialog-process-continue": "Continua", - "ooui-selectfile-button-select": "Seleziona un file", - "ooui-selectfile-not-supported": "La selezione del file non è supportata", - "ooui-selectfile-placeholder": "Nessun file è selezionato", - "ooui-selectfile-dragdrop-placeholder": "Posiziona i file qui", - "ooui-field-help": "Aiuto" -} diff --git a/resources/lib/oojs-ui/i18n/ja.json b/resources/lib/oojs-ui/i18n/ja.json deleted file mode 100644 index 08cea4b72a..0000000000 --- a/resources/lib/oojs-ui/i18n/ja.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Fryed-peach", - "Miya", - "Penn Station", - "Shirayuki", - "Takot", - "Los688", - "Sujiniku", - "Translatealcd", - "Otokoume", - "Rxy", - "S2KTS" - ] - }, - "ooui-outline-control-move-down": "項目を下に移動させる", - "ooui-outline-control-move-up": "項目を上に移動させる", - "ooui-outline-control-remove": "項目を除去", - "ooui-toolbar-more": "その他", - "ooui-toolgroup-expand": "続き", - "ooui-toolgroup-collapse": "折り畳む", - "ooui-item-remove": "削除", - "ooui-dialog-message-accept": "OK", - "ooui-dialog-message-reject": "キャンセル", - "ooui-dialog-process-error": "エラーが発生しました…", - "ooui-dialog-process-dismiss": "閉じる", - "ooui-dialog-process-retry": "もう一度お試しください", - "ooui-dialog-process-continue": "続行", - "ooui-selectfile-button-select": "ファイルを選択", - "ooui-selectfile-not-supported": "ファイルの選択はサポートされていません", - "ooui-selectfile-placeholder": "ファイルが選択されていません", - "ooui-selectfile-dragdrop-placeholder": "ファイルをここにドロップ", - "ooui-field-help": "ヘルプ" -} diff --git a/resources/lib/oojs-ui/i18n/jv.json b/resources/lib/oojs-ui/i18n/jv.json deleted file mode 100644 index 5ade01560d..0000000000 --- a/resources/lib/oojs-ui/i18n/jv.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Gleki", - "NoiX180", - "Pras", - "Jadinegara", - "Meursault2004" - ] - }, - "ooui-outline-control-move-down": "Lih barang mangisor", - "ooui-outline-control-move-up": "Lih barang mandhuwur", - "ooui-outline-control-remove": "Buwang barang", - "ooui-toolbar-more": "Liyané", - "ooui-toolgroup-expand": "Liyané", - "ooui-toolgroup-collapse": "Sacukupé", - "ooui-dialog-message-accept": "Oké", - "ooui-dialog-message-reject": "Wurung", - "ooui-dialog-process-error": "Ana sing salah", - "ooui-dialog-process-dismiss": "Tutup", - "ooui-dialog-process-retry": "Jajalen manèh", - "ooui-dialog-process-continue": "Bacutaké", - "ooui-selectfile-button-select": "Pilih barkas", - "ooui-selectfile-not-supported": "Ora bisa milih barkas", - "ooui-selectfile-placeholder": "Ora ana barkas sing dipilih", - "ooui-selectfile-dragdrop-placeholder": "Dèkèk barkas ing kéné" -} diff --git a/resources/lib/oojs-ui/i18n/ka.json b/resources/lib/oojs-ui/i18n/ka.json deleted file mode 100644 index c6d55873a7..0000000000 --- a/resources/lib/oojs-ui/i18n/ka.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "@metadata": { - "authors": [ - "BRUTE", - "David1010", - "Gleki", - "ITshnik", - "MIKHEIL", - "NoiX180", - "Pras", - "Tokoko", - "Kintrbr" - ] - }, - "ooui-outline-control-move-down": "ელემენტის ქვემოთ გადატანა", - "ooui-outline-control-move-up": "ელემენტის ზემოთ გადატანა", - "ooui-outline-control-remove": "წაშლა", - "ooui-toolbar-more": "მეტი", - "ooui-toolgroup-expand": "მეტი", - "ooui-toolgroup-collapse": "რამდენიმე", - "ooui-item-remove": "წაშლა", - "ooui-dialog-message-accept": "კარგი", - "ooui-dialog-message-reject": "გაუქმება", - "ooui-dialog-process-error": "მოხდა რაღაც შეცდომა", - "ooui-dialog-process-dismiss": "დამალვა", - "ooui-dialog-process-retry": "კიდევ სცადეთ", - "ooui-dialog-process-continue": "გაგრძელება", - "ooui-selectfile-button-select": "აირჩიეთ ფაილი", - "ooui-selectfile-not-supported": "ფაილის არჩევა არ არის მხარდაჭერილი", - "ooui-selectfile-placeholder": "ფაილი არ არის არჩეული", - "ooui-selectfile-dragdrop-placeholder": "ჩააგდეთ ფაილი აქ" -} diff --git a/resources/lib/oojs-ui/i18n/kab.json b/resources/lib/oojs-ui/i18n/kab.json deleted file mode 100644 index 55016b22a8..0000000000 --- a/resources/lib/oojs-ui/i18n/kab.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Belkacem77" - ] - }, - "ooui-outline-control-move-down": "Awi aferdi d akesser", - "ooui-outline-control-move-up": "Awi aferdis d asawen", - "ooui-outline-control-remove": "Kkes aferdis", - "ooui-toolbar-more": "Ugar", - "ooui-toolgroup-expand": "Ugar", - "ooui-toolgroup-collapse": "Drus", - "ooui-item-remove": "Kkes", - "ooui-dialog-message-accept": "IH", - "ooui-dialog-message-reject": "Sefsex", - "ooui-dialog-process-error": "Yella wayen yeḍran", - "ooui-dialog-process-dismiss": "Mdel", - "ooui-dialog-process-retry": "Ɛreḍ tikelt-nniden", - "ooui-dialog-process-continue": "Kemmel", - "ooui-selectfile-button-select": "Fren afaylu", - "ooui-selectfile-not-supported": "Afran n ufaylu ur yettusefrak ara", - "ooui-selectfile-placeholder": "Ulac afaylu yettwafernen", - "ooui-selectfile-dragdrop-placeholder": "Sers afaylu dagi" -} diff --git a/resources/lib/oojs-ui/i18n/khw.json b/resources/lib/oojs-ui/i18n/khw.json deleted file mode 100644 index f0ce207318..0000000000 --- a/resources/lib/oojs-ui/i18n/khw.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Rachitrali" - ] - }, - "ooui-toolbar-more": "مزید", - "ooui-toolgroup-expand": "مزید", - "ooui-toolgroup-collapse": "ای کما", - "ooui-dialog-message-accept": "ٹھیک شیر", - "ooui-dialog-message-reject": "کھینسل" -} diff --git a/resources/lib/oojs-ui/i18n/kk-cyrl.json b/resources/lib/oojs-ui/i18n/kk-cyrl.json deleted file mode 100644 index 779ba7b995..0000000000 --- a/resources/lib/oojs-ui/i18n/kk-cyrl.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Arystanbek" - ] - }, - "ooui-outline-control-move-down": "Элементті төмен жылжыту", - "ooui-outline-control-move-up": "Элементті жоғары жылжыту", - "ooui-outline-control-remove": "Элементті алып тастау", - "ooui-toolbar-more": "толығырақ", - "ooui-toolgroup-expand": "Тағы", - "ooui-toolgroup-collapse": "Азырақ", - "ooui-dialog-message-accept": "OK", - "ooui-dialog-message-reject": "Қажет емес", - "ooui-dialog-process-error": "Бірдеңеден қате кетті", - "ooui-dialog-process-dismiss": "Тоқтату", - "ooui-dialog-process-retry": "Қайта байқап көріңіз", - "ooui-dialog-process-continue": "Жалғастыру", - "ooui-selectfile-button-select": "Файлды таңдау", - "ooui-selectfile-not-supported": "Файл таңдауды қолдамайды", - "ooui-selectfile-placeholder": "Файл таңдалмады", - "ooui-selectfile-dragdrop-placeholder": "Файлды мында жылжыту" -} diff --git a/resources/lib/oojs-ui/i18n/km.json b/resources/lib/oojs-ui/i18n/km.json deleted file mode 100644 index c8f71d3f2f..0000000000 --- a/resources/lib/oojs-ui/i18n/km.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Sovichet", - "គីមស៊្រុន" - ] - }, - "ooui-outline-control-move-down": "រុញ​ធាតុទៅ​ក្រោម", - "ooui-outline-control-move-up": "រុញធាតុទៅ​លើ", - "ooui-outline-control-remove": "ដកធាតុចេញ", - "ooui-toolbar-more": "បន្ថែមទៀត", - "ooui-toolgroup-expand": "មើលច្រើន", - "ooui-toolgroup-collapse": "មើលតិច", - "ooui-item-remove": "ដកចេញ", - "ooui-dialog-message-accept": "យល់ព្រម", - "ooui-dialog-message-reject": "បោះបង់", - "ooui-dialog-process-error": "មានបញ្ហាអ្វីមួយ", - "ooui-dialog-process-dismiss": "បិទ", - "ooui-dialog-process-retry": "ព្យាយាមម្ដងទៀត", - "ooui-dialog-process-continue": "បន្ត", - "ooui-selectfile-button-select": "ជ្រើសរើសឯកសារ", - "ooui-selectfile-not-supported": "ការជ្រើសរើសឯកសារមិនអាចប្រើបានទេ", - "ooui-selectfile-placeholder": "គ្មានឯកសារណាមួយត្រូវបានជ្រើសរើស", - "ooui-selectfile-dragdrop-placeholder": "ទម្លាក់ឯកសារនៅទីនេះ", - "ooui-field-help": "ជំនួយ" -} diff --git a/resources/lib/oojs-ui/i18n/kn.json b/resources/lib/oojs-ui/i18n/kn.json deleted file mode 100644 index 741cfb33cd..0000000000 --- a/resources/lib/oojs-ui/i18n/kn.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Vikassy", - "Nayvik", - "Omshivaprakash", - "Pavanaja", - "Yogesh" - ] - }, - "ooui-outline-control-move-down": "ವಸ್ತುವನ್ನು ಕೆಳಗೆ ಸರಿಸು", - "ooui-outline-control-move-up": "ವಸ್ತುವನ್ನು ಮೇಲೆ ಸರಿಸು", - "ooui-outline-control-remove": "ವಸ್ತುವನ್ನು ತೆಗೆ", - "ooui-toolbar-more": "ಇನ್ನಷ್ಟು", - "ooui-toolgroup-expand": "ಇನ್ನಷ್ಟು", - "ooui-toolgroup-collapse": "ಕೆಲವೇ ಕೆಲವು", - "ooui-dialog-message-accept": "ಸರಿ", - "ooui-dialog-message-reject": "ರದ್ದುಮಾಡು", - "ooui-dialog-process-error": "ಏನೋ ಎಡವಟ್ಟಾಗಿದೆ....", - "ooui-dialog-process-dismiss": "ತೆಗೆದುಹಾಕು", - "ooui-dialog-process-retry": "ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ", - "ooui-dialog-process-continue": "ಮುಂದುವರೆಸು", - "ooui-selectfile-button-select": "ಕಡತವನ್ನು ಆಯ್ಕೆಮಾಡಿ", - "ooui-selectfile-placeholder": "ಕಡತವು ಆಯ್ಕೆಯಾಗಿಲ್ಲ", - "ooui-selectfile-dragdrop-placeholder": "ಇಲ್ಲಿ ಕಡತವನ್ನು ಬಿಡಿ" -} diff --git a/resources/lib/oojs-ui/i18n/ko.json b/resources/lib/oojs-ui/i18n/ko.json deleted file mode 100644 index 2adf18c0c0..0000000000 --- a/resources/lib/oojs-ui/i18n/ko.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Freebiekr", - "Hym411", - "Kwj2772", - "LFM", - "아라", - "고기랑", - "Ryuch", - "Revi", - "Infinity", - "Hwangjy9", - "Ykhwong" - ] - }, - "ooui-outline-control-move-down": "항목을 아래로 이동", - "ooui-outline-control-move-up": "항목을 위로 이동", - "ooui-outline-control-remove": "항목 제거", - "ooui-toolbar-more": "더 보기", - "ooui-toolgroup-expand": "더 보기", - "ooui-toolgroup-collapse": "덜 보기", - "ooui-item-remove": "제거", - "ooui-dialog-message-accept": "확인", - "ooui-dialog-message-reject": "취소", - "ooui-dialog-process-error": "무언가가 잘못되었습니다", - "ooui-dialog-process-dismiss": "숨기기", - "ooui-dialog-process-retry": "다시 시도하세요", - "ooui-dialog-process-continue": "계속", - "ooui-selectfile-button-select": "파일을 선택하세요", - "ooui-selectfile-not-supported": "파일 선택은 지원하지 않습니다", - "ooui-selectfile-placeholder": "선택한 파일 없음", - "ooui-selectfile-dragdrop-placeholder": "여기에 파일을 놓으세요", - "ooui-field-help": "도움말" -} diff --git a/resources/lib/oojs-ui/i18n/krc.json b/resources/lib/oojs-ui/i18n/krc.json deleted file mode 100644 index 6f17b34637..0000000000 --- a/resources/lib/oojs-ui/i18n/krc.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Iltever", - "Ernác" - ] - }, - "ooui-outline-control-move-down": "Элементни тюбюне кёчюр", - "ooui-outline-control-move-up": "Элементни башына кёчюр", - "ooui-outline-control-remove": "Пунктну кетер", - "ooui-toolbar-more": "Энтда", - "ooui-toolgroup-expand": "Энтда", - "ooui-toolgroup-collapse": "Артха", - "ooui-dialog-message-accept": "OK", - "ooui-dialog-message-reject": "Ызына ал", - "ooui-dialog-process-error": "Не эсе да табсыз кетди", - "ooui-dialog-process-dismiss": "Джаб", - "ooui-dialog-process-retry": "Энтда сынаб кёр", - "ooui-dialog-process-continue": "Бардыр", - "ooui-selectfile-not-supported": "Файл сайлау тутулмайды", - "ooui-selectfile-placeholder": "Бир файл да сайланмагъанды" -} diff --git a/resources/lib/oojs-ui/i18n/krl.json b/resources/lib/oojs-ui/i18n/krl.json deleted file mode 100644 index 6ff25ebe00..0000000000 --- a/resources/lib/oojs-ui/i18n/krl.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Mashoi7" - ] - }, - "ooui-toolbar-more": "Enämpi", - "ooui-toolgroup-expand": "Enämpi", - "ooui-toolgroup-collapse": "Vähempi" -} diff --git a/resources/lib/oojs-ui/i18n/ksh.json b/resources/lib/oojs-ui/i18n/ksh.json deleted file mode 100644 index f99c29fd68..0000000000 --- a/resources/lib/oojs-ui/i18n/ksh.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Purodha" - ] - }, - "ooui-outline-control-move-down": "Öm eine Plaz noh onge schiehbe", - "ooui-outline-control-move-up": "Öm eine Plaz noh bovve schiehbe", - "ooui-outline-control-remove": "Dä Plaz läddesch maache → fott domet!", - "ooui-toolbar-more": "Mih", - "ooui-toolgroup-expand": "Mih", - "ooui-toolgroup-collapse": "Winnijer", - "ooui-dialog-message-accept": "Lohß Jonn!", - "ooui-dialog-message-reject": "Ophühre", - "ooui-dialog-process-error": "Öhnsjädd es scheif jejange", - "ooui-dialog-process-dismiss": "Maach fott, ha_sch jelässe", - "ooui-dialog-process-retry": "Norr_ens versöhke", - "ooui-dialog-process-continue": "Wigger maache", - "ooui-selectfile-button-select": "Söhg en Dattei uß", - "ooui-selectfile-not-supported": "Mer ogerschtözze et Datteij_Ußwähle nit.", - "ooui-selectfile-placeholder": "Kein Dattei es ußjewählt" -} diff --git a/resources/lib/oojs-ui/i18n/ku-latn.json b/resources/lib/oojs-ui/i18n/ku-latn.json deleted file mode 100644 index 9954744933..0000000000 --- a/resources/lib/oojs-ui/i18n/ku-latn.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "@metadata": { - "authors": [ - "George Animal", - "Bikarhêner" - ] - }, - "ooui-toolbar-more": "Bêhtir", - "ooui-toolgroup-expand": "Bêhtir", - "ooui-toolgroup-collapse": "Kêmtir", - "ooui-dialog-message-accept": "Baş e", - "ooui-dialog-message-reject": "Betal bike", - "ooui-dialog-process-retry": "Dîsa hewl bide", - "ooui-dialog-process-continue": "Bidomîne", - "ooui-selectfile-button-select": "Dosyeyekê hilbijêre", - "ooui-selectfile-placeholder": "Ti dosye nehatiye hilbijartin" -} diff --git a/resources/lib/oojs-ui/i18n/kw.json b/resources/lib/oojs-ui/i18n/kw.json deleted file mode 100644 index a6c6d8ab51..0000000000 --- a/resources/lib/oojs-ui/i18n/kw.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "@metadata": { - "authors": [ - "George Animal", - "Nrowe", - "Purodha" - ] - } -} diff --git a/resources/lib/oojs-ui/i18n/ky.json b/resources/lib/oojs-ui/i18n/ky.json deleted file mode 100644 index e2b8ab7a02..0000000000 --- a/resources/lib/oojs-ui/i18n/ky.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Chorobek", - "George Animal", - "Nrowe", - "Tynchtyk Chorotegin", - "Викиней" - ] - } -} diff --git a/resources/lib/oojs-ui/i18n/la.json b/resources/lib/oojs-ui/i18n/la.json deleted file mode 100644 index 9b161e906d..0000000000 --- a/resources/lib/oojs-ui/i18n/la.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Jdforrester", - "Fitoschido" - ] - }, - "ooui-toolbar-more": "Plus", - "ooui-toolgroup-expand": "Plus", - "ooui-toolgroup-collapse": "Paucior", - "ooui-dialog-message-accept": "Assentior", - "ooui-dialog-message-reject": "Dimittere", - "ooui-dialog-process-dismiss": "Dimittere", - "ooui-dialog-process-retry": "Retemptare", - "ooui-dialog-process-continue": "Pergere", - "ooui-field-help": "Auxilium" -} diff --git a/resources/lib/oojs-ui/i18n/lb.json b/resources/lib/oojs-ui/i18n/lb.json deleted file mode 100644 index 0d30583f4d..0000000000 --- a/resources/lib/oojs-ui/i18n/lb.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Autokrator", - "Chorobek", - "Robby", - "Soued031", - "Tynchtyk Chorotegin", - "UV", - "Викиней" - ] - }, - "ooui-outline-control-move-down": "Element erof réckelen", - "ooui-outline-control-move-up": "Element erop réckelen", - "ooui-outline-control-remove": "Element ewechhuelen", - "ooui-toolbar-more": "Méi", - "ooui-toolgroup-expand": "Méi", - "ooui-toolgroup-collapse": "Manner", - "ooui-item-remove": "Ewechhuelen", - "ooui-dialog-message-accept": "OK", - "ooui-dialog-message-reject": "Ofbriechen", - "ooui-dialog-process-error": "Et ass eppes schif gaang", - "ooui-dialog-process-dismiss": "Verwerfen", - "ooui-dialog-process-retry": "Nach eng Kéier probéieren", - "ooui-dialog-process-continue": "Virufueren", - "ooui-selectfile-button-select": "E Fichier eraussichen", - "ooui-selectfile-not-supported": "D'Eraussiche vu Fichiere gëtt net ënnerstëtzt.", - "ooui-selectfile-placeholder": "Et ass kee Fichier erausgesicht", - "ooui-selectfile-dragdrop-placeholder": "Fichier hei ofleeën", - "ooui-field-help": "Hëllef" -} diff --git a/resources/lib/oojs-ui/i18n/li.json b/resources/lib/oojs-ui/i18n/li.json deleted file mode 100644 index 43ae794853..0000000000 --- a/resources/lib/oojs-ui/i18n/li.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Pahles", - "Ooswesthoesbes" - ] - }, - "ooui-outline-control-move-down": "Item nao ónger verplaatse", - "ooui-outline-control-move-up": "Item nao bove verplaetse", - "ooui-outline-control-remove": "Item ewegsjaffe", - "ooui-toolbar-more": "Mieë", - "ooui-toolgroup-expand": "Mieë", - "ooui-toolgroup-collapse": "Minder", - "ooui-item-remove": "Sjaf eweg", - "ooui-dialog-message-accept": "Ok", - "ooui-dialog-message-reject": "Aafbraeke", - "ooui-dialog-process-error": "Dao is get misgegange", - "ooui-dialog-process-dismiss": "Sjlete", - "ooui-dialog-process-retry": "Perbeer obbenuujts", - "ooui-dialog-process-continue": "Doorgaon", - "ooui-selectfile-button-select": "Kees e bestandj", - "ooui-selectfile-not-supported": "Selektie van 'n besjtandj waert neet óngersteund", - "ooui-selectfile-placeholder": "Dao is gein besjtandj geselekteerd", - "ooui-selectfile-dragdrop-placeholder": "Sleip e bestandj hieroppes" -} diff --git a/resources/lib/oojs-ui/i18n/lki.json b/resources/lib/oojs-ui/i18n/lki.json deleted file mode 100644 index ab6db1438b..0000000000 --- a/resources/lib/oojs-ui/i18n/lki.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Hosseinblue", - "Arash71" - ] - }, - "ooui-outline-control-move-down": "جاوواز کردن ئإ هووار", - "ooui-outline-control-move-up": "جاوواز کردن ئإ بِلِنگ", - "ooui-outline-control-remove": "حذف مورد", - "ooui-toolbar-more": "ویشتر/فرۀتر", - "ooui-toolgroup-expand": "ویشتر/فرۀتر", - "ooui-toolgroup-collapse": "کۀمتر", - "ooui-dialog-message-accept": "خوو/ باشد", - "ooui-dialog-message-reject": "ئآهووسانن/لغو", - "ooui-dialog-process-error": "مشکلی هۀس", - "ooui-dialog-process-dismiss": "رد کردن", - "ooui-dialog-process-retry": "دووآرۀ تلاش کۀ", - "ooui-dialog-process-continue": "ادامه-دؤم گرتن", - "ooui-selectfile-button-select": "فایلئ انتخاب کۀ", - "ooui-selectfile-not-supported": "انتخاب پرونده پشتیبانی نمی‌شود", - "ooui-selectfile-placeholder": "هیچ پرونده‌ای انتخاب نشده است", - "ooui-selectfile-dragdrop-placeholder": "فایل را اینجا رها کنید" -} diff --git a/resources/lib/oojs-ui/i18n/lmo.json b/resources/lib/oojs-ui/i18n/lmo.json deleted file mode 100644 index 87309db0d6..0000000000 --- a/resources/lib/oojs-ui/i18n/lmo.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Ninonino" - ] - }, - "ooui-outline-control-move-down": "Spòsta 'n zó", - "ooui-outline-control-move-up": "Spòsta 'n sö", - "ooui-toolbar-more": "Amò" -} diff --git a/resources/lib/oojs-ui/i18n/lt.json b/resources/lib/oojs-ui/i18n/lt.json deleted file mode 100644 index ea13406751..0000000000 --- a/resources/lib/oojs-ui/i18n/lt.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Audriusa", - "Eitvys200", - "Mantak111", - "Albertas", - "Manvydasz" - ] - }, - "ooui-outline-control-move-down": "Perkelti elementą žemyn", - "ooui-outline-control-move-up": "Perkelti elementą aukštyn", - "ooui-outline-control-remove": "Šalinti įrašą", - "ooui-toolbar-more": "Daugiau", - "ooui-toolgroup-expand": "Daugiau", - "ooui-toolgroup-collapse": "Mažiau", - "ooui-item-remove": "Pašalinti", - "ooui-dialog-message-accept": "Gerai", - "ooui-dialog-message-reject": "Atšaukti", - "ooui-dialog-process-error": "Kažkas nutiko ne taip", - "ooui-dialog-process-dismiss": "Paslėpti", - "ooui-dialog-process-retry": "Bandykite dar kartą", - "ooui-dialog-process-continue": "Tęsti", - "ooui-selectfile-button-select": "Pasirinkti failą", - "ooui-selectfile-not-supported": "Failų pasirinkimas nepalaikomas", - "ooui-selectfile-placeholder": "Nėra pasirinktų failų", - "ooui-selectfile-dragdrop-placeholder": "Atitempkite failą čia" -} diff --git a/resources/lib/oojs-ui/i18n/luz.json b/resources/lib/oojs-ui/i18n/luz.json deleted file mode 100644 index d48a9dfa3d..0000000000 --- a/resources/lib/oojs-ui/i18n/luz.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "@metadata": { - "authors": [ - "علی ساکی لرستانی" - ] - }, - "ooui-outline-control-move-down": "انتقال مورد وه دومن", - "ooui-outline-control-move-up": "انتقال مورد وه بالا", - "ooui-outline-control-remove": "حذف مورد", - "ooui-toolbar-more": "هنی", - "ooui-toolgroup-expand": "هنی", - "ooui-toolgroup-collapse": "کم تر", - "ooui-dialog-message-accept": "خووه", - "ooui-dialog-message-reject": "لغو", - "ooui-dialog-process-error": "یه چیایی اشتباه ویده", - "ooui-dialog-process-dismiss": "منفصل کردن", - "ooui-dialog-process-retry": "دوباره تلاش کردن", - "ooui-dialog-process-continue": "ادامه دائن", - "ooui-selectfile-not-supported": "فایل انتخابی پشتیبانی نوابیه", - "ooui-selectfile-placeholder": "فایلی انتخاب نوابیه" -} diff --git a/resources/lib/oojs-ui/i18n/lv.json b/resources/lib/oojs-ui/i18n/lv.json deleted file mode 100644 index 103dff74c6..0000000000 --- a/resources/lib/oojs-ui/i18n/lv.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Admresdeserv.", - "Audriusa", - "Eitvys200", - "Papuass", - "PeterisP" - ] - }, - "ooui-outline-control-move-down": "Pārvietot vienumu uz leju", - "ooui-outline-control-move-up": "Pārvietot vienumu uz augšu", - "ooui-outline-control-remove": "Noņemt vienumu", - "ooui-toolbar-more": "Vairāk", - "ooui-toolgroup-expand": "Vairāk", - "ooui-toolgroup-collapse": "Mazāk", - "ooui-item-remove": "Noņemt", - "ooui-dialog-message-accept": "Labi", - "ooui-dialog-message-reject": "Atcelt", - "ooui-dialog-process-error": "Kaut kas nogāja greizi", - "ooui-dialog-process-dismiss": "Paslēpt", - "ooui-dialog-process-retry": "Mēģināt vēlreiz", - "ooui-dialog-process-continue": "Turpināt", - "ooui-selectfile-button-select": "Izvēlies failu", - "ooui-selectfile-not-supported": "Failu izvēle nav atbalstīta", - "ooui-selectfile-placeholder": "Nav izvēlēts neviens fails", - "ooui-selectfile-dragdrop-placeholder": "Nomet failu šeit", - "ooui-field-help": "Palīdzība" -} diff --git a/resources/lib/oojs-ui/i18n/lzh.json b/resources/lib/oojs-ui/i18n/lzh.json deleted file mode 100644 index d0f1bd2acc..0000000000 --- a/resources/lib/oojs-ui/i18n/lzh.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Joe young yu", - "Itsmine", - "SolidBlock" - ] - }, - "ooui-outline-control-move-down": "遷下", - "ooui-outline-control-move-up": "遷上", - "ooui-outline-control-remove": "去物", - "ooui-toolbar-more": "餘", - "ooui-dialog-message-accept": "可" -} diff --git a/resources/lib/oojs-ui/i18n/mg.json b/resources/lib/oojs-ui/i18n/mg.json deleted file mode 100644 index 4c6967b8e6..0000000000 --- a/resources/lib/oojs-ui/i18n/mg.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Jagwar", - "Fitoschido" - ] - }, - "ooui-outline-control-move-down": "Hampidina ilay zavatra", - "ooui-outline-control-move-up": "Hampiakatra ilay zavatra", - "ooui-outline-control-remove": "Hanala iay zavatra", - "ooui-toolbar-more": "Be kokoa", - "ooui-toolgroup-expand": "Be kokoa", - "ooui-toolgroup-collapse": "Kely kokoa", - "ooui-dialog-message-accept": "OK", - "ooui-dialog-message-reject": "Avela", - "ooui-dialog-process-error": "Nisy hadisoana nitranga", - "ooui-dialog-process-dismiss": "Esorina", - "ooui-dialog-process-retry": "Andramana indray", - "ooui-dialog-process-continue": "Tohizana", - "ooui-selectfile-button-select": "Misafidia rakitra iray", - "ooui-selectfile-not-supported": "Tsy zaka ny fisafidiana rakitra", - "ooui-selectfile-placeholder": "Tsy misy rakitra voafidy", - "ooui-selectfile-dragdrop-placeholder": "Hametraka rakitra eto", - "ooui-field-help": "Fanoroana" -} diff --git a/resources/lib/oojs-ui/i18n/min.json b/resources/lib/oojs-ui/i18n/min.json deleted file mode 100644 index b8790d3123..0000000000 --- a/resources/lib/oojs-ui/i18n/min.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Iwan Novirion", - "Jagwar" - ] - }, - "ooui-outline-control-move-down": "Pindahan ko ka bawah", - "ooui-outline-control-move-up": "Pindahan ko ka ateh", - "ooui-outline-control-remove": "Hapuih ko", - "ooui-toolbar-more": "Lainnyo", - "ooui-dialog-message-accept": "Yo", - "ooui-dialog-message-reject": "Batal" -} diff --git a/resources/lib/oojs-ui/i18n/mk.json b/resources/lib/oojs-ui/i18n/mk.json deleted file mode 100644 index 8de0d80e57..0000000000 --- a/resources/lib/oojs-ui/i18n/mk.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Bjankuloski06", - "Brest", - "Iwan Novirion" - ] - }, - "ooui-outline-control-move-down": "Помести надолу", - "ooui-outline-control-move-up": "Помести нагоре", - "ooui-outline-control-remove": "Отстрани ставка", - "ooui-toolbar-more": "Повеќе", - "ooui-toolgroup-expand": "Повеќе", - "ooui-toolgroup-collapse": "Помалку", - "ooui-item-remove": "Отстрани", - "ooui-dialog-message-accept": "ОК", - "ooui-dialog-message-reject": "Откажи", - "ooui-dialog-process-error": "Нешто не е во ред", - "ooui-dialog-process-dismiss": "Тргни", - "ooui-dialog-process-retry": "Обиди се пак", - "ooui-dialog-process-continue": "Продолжи", - "ooui-selectfile-button-select": "Одберете податотека", - "ooui-selectfile-not-supported": "Изборот на податотеки не е поддржан", - "ooui-selectfile-placeholder": "Немате одбрано податотека", - "ooui-selectfile-dragdrop-placeholder": "Тука пуштете ја податотеката", - "ooui-field-help": "Помош" -} diff --git a/resources/lib/oojs-ui/i18n/ml.json b/resources/lib/oojs-ui/i18n/ml.json deleted file mode 100644 index aba355aa18..0000000000 --- a/resources/lib/oojs-ui/i18n/ml.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Kavya Manohar", - "Praveenp", - "Santhosh.thottingal", - "Vssun", - "Ranjithsiji" - ] - }, - "ooui-outline-control-move-down": "ഇനം താഴേയ്ക്ക് മാറ്റുക", - "ooui-outline-control-move-up": "ഇനം മുകളിലേയ്ക്ക് മാറ്റുക", - "ooui-outline-control-remove": "ഇനം നീക്കംചെയ്യുക", - "ooui-toolbar-more": "കൂടുതൽ", - "ooui-toolgroup-expand": "കൂടുതൽ", - "ooui-toolgroup-collapse": "കുറച്ച്", - "ooui-item-remove": "നീക്കം ചെയ്യുക", - "ooui-dialog-message-accept": "ശരി", - "ooui-dialog-message-reject": "റദ്ദാക്കുക", - "ooui-dialog-process-error": "എന്തോ പ്രശ്നമുണ്ടായി", - "ooui-dialog-process-dismiss": "ഒഴിവാക്കുക", - "ooui-dialog-process-retry": "വീണ്ടും ശ്രമിക്കുക", - "ooui-dialog-process-continue": "തുടരുക", - "ooui-selectfile-button-select": "പ്രമാണം തിരഞ്ഞെടുക്കുക", - "ooui-selectfile-not-supported": "പ്രമാണം തിരഞ്ഞെടുക്കൽ പിന്തുണയ്ക്കുന്നില്ല", - "ooui-selectfile-placeholder": "പ്രമാണങ്ങൾ ഒന്നും തിരഞ്ഞെടുത്തിട്ടില്ല", - "ooui-selectfile-dragdrop-placeholder": "പ്രമാണം ഇവിടെ ഇടുക", - "ooui-field-help": "സഹായം" -} diff --git a/resources/lib/oojs-ui/i18n/mn.json b/resources/lib/oojs-ui/i18n/mn.json deleted file mode 100644 index 500aca78e4..0000000000 --- a/resources/lib/oojs-ui/i18n/mn.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Munkhzaya.E" - ] - }, - "ooui-toolbar-more": "Илүү", - "ooui-toolgroup-expand": "Илүү", - "ooui-toolgroup-collapse": "Цөөн", - "ooui-dialog-message-accept": "За", - "ooui-dialog-message-reject": "Цуцлах", - "ooui-dialog-process-error": "Ямар нэг алдаа гарсан", - "ooui-dialog-process-dismiss": "Нуух", - "ooui-dialog-process-retry": "Дахин оролдох", - "ooui-dialog-process-continue": "Цааш явах", - "ooui-selectfile-button-select": "Файлаа сонгох", - "ooui-selectfile-not-supported": "Сонгосол файл нь дэмжигдэхгүй байна", - "ooui-selectfile-placeholder": "Файл сонгоогүй байна", - "ooui-selectfile-dragdrop-placeholder": "Файлаа энд хадгалах" -} diff --git a/resources/lib/oojs-ui/i18n/mr.json b/resources/lib/oojs-ui/i18n/mr.json deleted file mode 100644 index e4a6bd16ca..0000000000 --- a/resources/lib/oojs-ui/i18n/mr.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Kaajawa", - "Mahitgar", - "Praju23", - "V.narsikar", - "Ydyashad", - "संतोष दहिवळ", - "NehalDaveND", - "Sau6402", - "Sureshkhole" - ] - }, - "ooui-outline-control-move-down": "घटक (आयटम) खाली सरकवा", - "ooui-outline-control-move-up": "घटक (आयटम) वर सरकवा", - "ooui-outline-control-remove": "बाब हटवा", - "ooui-toolbar-more": "अधिक", - "ooui-toolgroup-expand": "अधिक", - "ooui-toolgroup-collapse": "कमी", - "ooui-item-remove": "हटवा", - "ooui-dialog-message-accept": "ठिक आहे", - "ooui-dialog-message-reject": "रद्द करा", - "ooui-dialog-process-error": "काहीतरी गडबड झाली", - "ooui-dialog-process-dismiss": "रद्द करा", - "ooui-dialog-process-retry": "पुन्हा प्रयत्न करा", - "ooui-dialog-process-continue": "चालू ठेवा", - "ooui-selectfile-button-select": "संचिका निवडा", - "ooui-selectfile-not-supported": "संचिका निवडणे साहाय्यीकृत नाही", - "ooui-selectfile-placeholder": "संचिका निवडल्या गेली नाही", - "ooui-selectfile-dragdrop-placeholder": "संचिका येथे टाका" -} diff --git a/resources/lib/oojs-ui/i18n/ms.json b/resources/lib/oojs-ui/i18n/ms.json deleted file mode 100644 index 458945793f..0000000000 --- a/resources/lib/oojs-ui/i18n/ms.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Anakmalaysia", - "Aurora", - "Pizza1016", - "Karmadunya9-" - ] - }, - "ooui-outline-control-move-down": "Alihkan perkara ke bawah", - "ooui-outline-control-move-up": "Alihkan perkara ke atas", - "ooui-outline-control-remove": "Buang perkara", - "ooui-toolbar-more": "Selebihnya", - "ooui-toolgroup-expand": "Selengkapnya", - "ooui-toolgroup-collapse": "Secukupnya", - "ooui-item-remove": "Buang", - "ooui-dialog-message-accept": "OK", - "ooui-dialog-message-reject": "Batal", - "ooui-dialog-process-error": "Ada masalah", - "ooui-dialog-process-dismiss": "Singkir", - "ooui-dialog-process-retry": "Cuba lagi", - "ooui-dialog-process-continue": "Teruskan", - "ooui-selectfile-button-select": "Pilih fail", - "ooui-selectfile-not-supported": "Pilihan fail tidak disokong", - "ooui-selectfile-placeholder": "Tiada fail yang dipilih", - "ooui-selectfile-dragdrop-placeholder": "Letakkan fail di sini" -} diff --git a/resources/lib/oojs-ui/i18n/my.json b/resources/lib/oojs-ui/i18n/my.json deleted file mode 100644 index 09c0441190..0000000000 --- a/resources/lib/oojs-ui/i18n/my.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Dr Lotus Black" - ] - }, - "ooui-toolbar-more": "ပို၍", - "ooui-toolgroup-expand": "ပို၍", - "ooui-item-remove": "ဖယ်ရှားရန်", - "ooui-dialog-message-accept": "အိုကေ", - "ooui-dialog-message-reject": "မလုပ်တော့", - "ooui-dialog-process-error": "တစ်ခုခု မှားယွင်းသွားခဲ့ပါသည်", - "ooui-dialog-process-dismiss": "ဖြုတ်ရန်", - "ooui-dialog-process-retry": "နောက်တစ်ဖန် ကြိုးစားပါ", - "ooui-dialog-process-continue": "ဆက်လက်", - "ooui-selectfile-button-select": "ဖိုင်တစ်ခု ရွေးချယ်ရန်", - "ooui-selectfile-placeholder": "ဖိုင် ရွေးချယ်မထားပါ", - "ooui-field-help": "အကူအညီ" -} diff --git a/resources/lib/oojs-ui/i18n/myv.json b/resources/lib/oojs-ui/i18n/myv.json deleted file mode 100644 index faf1db7003..0000000000 --- a/resources/lib/oojs-ui/i18n/myv.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Rueter" - ] - }, - "ooui-toolbar-more": "Седе ламо", - "ooui-toolgroup-expand": "Седе ламо", - "ooui-toolgroup-collapse": "Седе аламо", - "ooui-item-remove": "Нардамс", - "ooui-dialog-message-accept": "Маштови", - "ooui-dialog-message-reject": "Саемс мекев", - "ooui-dialog-process-error": "Мезе-бути аволь истя", - "ooui-dialog-process-retry": "Варчамс одов", - "ooui-dialog-process-continue": "Поладомс", - "ooui-selectfile-button-select": "Кочкамс файла" -} diff --git a/resources/lib/oojs-ui/i18n/nan.json b/resources/lib/oojs-ui/i18n/nan.json deleted file mode 100644 index d94118d7c4..0000000000 --- a/resources/lib/oojs-ui/i18n/nan.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Luuva" - ] - }, - "ooui-outline-control-move-down": "Hāng-bo̍k sóa ē-té", - "ooui-outline-control-move-up": "Hāng-bo̍k sóa téng-bīn", - "ooui-outline-control-remove": "Sóa cháu hāng-bo̍k", - "ooui-toolbar-more": "Khah chē", - "ooui-toolgroup-expand": "Khah chē", - "ooui-toolgroup-collapse": "Khah kiám", - "ooui-dialog-message-accept": "Liáu-kái", - "ooui-dialog-message-reject": "Chhú-siau", - "ooui-dialog-process-error": "Ū mi̍h bô hó-sè", - "ooui-dialog-process-dismiss": "Koaiⁿ tiāu", - "ooui-dialog-process-retry": "Koh chhì khòaⁿ-māi", - "ooui-dialog-process-continue": "Kè-sio̍k", - "ooui-selectfile-button-select": "Soán-tek 1-ê tóng-àn", - "ooui-selectfile-not-supported": "Só͘ soán ê tóng-àn bô siū chi-chhî", - "ooui-selectfile-placeholder": "Iáu-bē soán tóng-àn", - "ooui-selectfile-dragdrop-placeholder": "Kā tóng-àn tàn chia" -} diff --git a/resources/lib/oojs-ui/i18n/nap.json b/resources/lib/oojs-ui/i18n/nap.json deleted file mode 100644 index b7e37b49b6..0000000000 --- a/resources/lib/oojs-ui/i18n/nap.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Chelin", - "Chrisportelli", - "PiRSquared17", - "C.R.", - "Candalua" - ] - }, - "ooui-outline-control-move-down": "Mòve abbascio", - "ooui-outline-control-move-up": "Mòve ncoppa", - "ooui-outline-control-remove": "Leva elemento", - "ooui-toolbar-more": "Atro", - "ooui-toolgroup-expand": "Cchiù", - "ooui-toolgroup-collapse": "Meno", - "ooui-dialog-message-accept": "OK", - "ooui-dialog-message-reject": "Scancella", - "ooui-dialog-process-error": "Cocchosa è ghiuta malamente", - "ooui-dialog-process-dismiss": "Passa 'a vacca", - "ooui-dialog-process-retry": "Prova n'ata vota", - "ooui-dialog-process-continue": "Continua", - "ooui-selectfile-button-select": "Sceglie nu file", - "ooui-selectfile-not-supported": "Filtro 'e selezione nun suppurtato", - "ooui-selectfile-placeholder": "Nun s'è scigliuto nisciuno file", - "ooui-selectfile-dragdrop-placeholder": "Lassa 'o file ccà" -} diff --git a/resources/lib/oojs-ui/i18n/nb.json b/resources/lib/oojs-ui/i18n/nb.json deleted file mode 100644 index de8c5bb9e2..0000000000 --- a/resources/lib/oojs-ui/i18n/nb.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Danmichaelo", - "Event", - "Jeblad", - "Laaknor", - "Njardarlogar", - "Jdforrester", - "Apple farmer", - "Jon Harald Søby", - "Orf3us" - ] - }, - "ooui-outline-control-move-down": "Flytt ned", - "ooui-outline-control-move-up": "Flytt opp", - "ooui-outline-control-remove": "Fjern element", - "ooui-toolbar-more": "Mer", - "ooui-toolgroup-expand": "Mer", - "ooui-toolgroup-collapse": "Færre", - "ooui-item-remove": "Fjern", - "ooui-dialog-message-accept": "OK", - "ooui-dialog-message-reject": "Avbryt", - "ooui-dialog-process-error": "Noe gikk galt", - "ooui-dialog-process-dismiss": "Lukk", - "ooui-dialog-process-retry": "Prøv igjen", - "ooui-dialog-process-continue": "Fortsett", - "ooui-selectfile-button-select": "Velg en fil", - "ooui-selectfile-not-supported": "Filvalg er ikke støttet", - "ooui-selectfile-placeholder": "Ingen fil er valgt", - "ooui-selectfile-dragdrop-placeholder": "Slipp fil her", - "ooui-field-help": "Hjelp" -} diff --git a/resources/lib/oojs-ui/i18n/nds-nl.json b/resources/lib/oojs-ui/i18n/nds-nl.json deleted file mode 100644 index d3db318b52..0000000000 --- a/resources/lib/oojs-ui/i18n/nds-nl.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Servien" - ] - }, - "ooui-outline-control-move-down": "Onderwarp ummeneer zetten", - "ooui-outline-control-move-up": "Onderwarp umhoge zetten", - "ooui-outline-control-remove": "Element vortdoon", - "ooui-toolbar-more": "Meer", - "ooui-toolgroup-expand": "Meer", - "ooui-toolgroup-collapse": "Minder", - "ooui-dialog-message-accept": "Okee", - "ooui-dialog-message-reject": "Aofbreken", - "ooui-dialog-process-error": "Der gung iets fout", - "ooui-dialog-process-dismiss": "Sluten", - "ooui-dialog-process-retry": "Opniej proberen", - "ooui-dialog-process-continue": "Deurgaon" -} diff --git a/resources/lib/oojs-ui/i18n/nds.json b/resources/lib/oojs-ui/i18n/nds.json deleted file mode 100644 index 9dee2f5543..0000000000 --- a/resources/lib/oojs-ui/i18n/nds.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Zylbath", - "Joachim Mos" - ] - }, - "ooui-outline-control-move-down": "Element na ünnen schuven", - "ooui-outline-control-move-up": "Element na baven schuven", - "ooui-toolbar-more": "Mehr", - "ooui-dialog-message-accept": "OK", - "ooui-dialog-message-reject": "Afbreken", - "ooui-dialog-process-error": "Do is wat in'e Büx goan", - "ooui-dialog-process-continue": "Wiedermaken", - "ooui-selectfile-button-select": "En Datei utwählen" -} diff --git a/resources/lib/oojs-ui/i18n/ne.json b/resources/lib/oojs-ui/i18n/ne.json deleted file mode 100644 index c7d286136f..0000000000 --- a/resources/lib/oojs-ui/i18n/ne.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "@metadata": { - "authors": [ - "RajeshPandey", - "सरोज कुमार ढकाल", - "Ganesh Paudel", - "Nirajan pant" - ] - }, - "ooui-outline-control-move-down": "वस्तुलाई तल सार्ने", - "ooui-outline-control-move-up": "वस्तुलाई माथि सार्ने", - "ooui-outline-control-remove": "वस्तुलाई हटाउने", - "ooui-toolbar-more": "थप", - "ooui-toolgroup-expand": "थप", - "ooui-toolgroup-collapse": "कम", - "ooui-item-remove": "हटाउनुहोस्", - "ooui-dialog-message-accept": "हुन्छ", - "ooui-dialog-message-reject": "रद्द गर्ने", - "ooui-dialog-process-dismiss": "खारेज गर्ने", - "ooui-dialog-process-retry": "पुन प्रयास गर्नुहोस", - "ooui-dialog-process-continue": "जारी राख्ने" -} diff --git a/resources/lib/oojs-ui/i18n/nl.json b/resources/lib/oojs-ui/i18n/nl.json deleted file mode 100644 index c6fd278e8e..0000000000 --- a/resources/lib/oojs-ui/i18n/nl.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Bluyten", - "Breghtje", - "Catrope", - "Flightmare", - "Hansmuller", - "Jdforrester", - "Keegan", - "Konovalov", - "RajeshPandey", - "Romaine", - "SPQRobin", - "Saruman", - "Siebrand", - "Southparkfan", - "सरोज कुमार ढकाल", - "Sjoerddebruin", - "Gloria sah", - "Mainframe98" - ] - }, - "ooui-outline-control-move-down": "Item omlaag verplaatsen", - "ooui-outline-control-move-up": "Item omhoog verplaatsen", - "ooui-outline-control-remove": "Item verwijderen", - "ooui-toolbar-more": "Meer", - "ooui-toolgroup-expand": "Meer", - "ooui-toolgroup-collapse": "Minder", - "ooui-item-remove": "Verwijderen", - "ooui-dialog-message-accept": "OK", - "ooui-dialog-message-reject": "Annuleren", - "ooui-dialog-process-error": "Er is iets misgegaan", - "ooui-dialog-process-dismiss": "Sluiten", - "ooui-dialog-process-retry": "Opnieuw proberen", - "ooui-dialog-process-continue": "Doorgaan", - "ooui-selectfile-button-select": "Selecteer een bestand", - "ooui-selectfile-not-supported": "Selectie van een bestand wordt niet ondersteund", - "ooui-selectfile-placeholder": "Er is geen bestand geselecteerd", - "ooui-selectfile-dragdrop-placeholder": "Sleep hier een bestand heen", - "ooui-field-help": "Hulp" -} diff --git a/resources/lib/oojs-ui/i18n/nn.json b/resources/lib/oojs-ui/i18n/nn.json deleted file mode 100644 index a32e7a41c8..0000000000 --- a/resources/lib/oojs-ui/i18n/nn.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Jeblad", - "Njardarlogar" - ] - }, - "ooui-outline-control-move-down": "Flytt element ned", - "ooui-outline-control-move-up": "Flytt element opp", - "ooui-toolbar-more": "Meir", - "ooui-toolgroup-expand": "Meir", - "ooui-toolgroup-collapse": "Færre", - "ooui-dialog-message-reject": "Bryt av", - "ooui-dialog-process-error": "Noko gjekk gale", - "ooui-dialog-process-dismiss": "Lat att", - "ooui-dialog-process-continue": "Hald fram", - "ooui-selectfile-button-select": "Vel ei fil", - "ooui-selectfile-placeholder": "Inga fil er vald", - "ooui-selectfile-dragdrop-placeholder": "Slepp fil her" -} diff --git a/resources/lib/oojs-ui/i18n/oc.json b/resources/lib/oojs-ui/i18n/oc.json deleted file mode 100644 index 24a5966f34..0000000000 --- a/resources/lib/oojs-ui/i18n/oc.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Cedric31", - "Gloria sah" - ] - }, - "ooui-outline-control-move-down": "Far davalar l’element", - "ooui-outline-control-move-up": "Far montar l’element", - "ooui-outline-control-remove": "Suprimir l’element", - "ooui-toolbar-more": "Mai", - "ooui-toolgroup-expand": "Mai", - "ooui-toolgroup-collapse": "Mens", - "ooui-dialog-message-accept": "D'acòrdi", - "ooui-dialog-message-reject": "Anullar", - "ooui-dialog-process-error": "Quicòm a trucat", - "ooui-dialog-process-dismiss": "Regetar", - "ooui-dialog-process-retry": "Ensajatz tornamai", - "ooui-dialog-process-continue": "Contunhar", - "ooui-selectfile-button-select": "Seleccionar un fichièr", - "ooui-selectfile-not-supported": "Lo tipe de fichièr es pas compatible", - "ooui-selectfile-placeholder": "Cap de fichièr pas seleccionat", - "ooui-selectfile-dragdrop-placeholder": "Depausar lo fichièr aicí" -} diff --git a/resources/lib/oojs-ui/i18n/olo.json b/resources/lib/oojs-ui/i18n/olo.json deleted file mode 100644 index 1dc994ebf1..0000000000 --- a/resources/lib/oojs-ui/i18n/olo.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Mashoi7" - ] - }, - "ooui-outline-control-move-down": "Siirrä kohteh alah", - "ooui-outline-control-move-up": "Siirrä kohteh yläh", - "ooui-outline-control-remove": "Ota kohteh iäre", - "ooui-toolbar-more": "Enämbi", - "ooui-toolgroup-expand": "Enämbi", - "ooui-toolgroup-collapse": "Vähembi", - "ooui-dialog-message-accept": "OK", - "ooui-dialog-message-reject": "Hylgiä", - "ooui-dialog-process-error": "Mitah haireh rodih", - "ooui-dialog-process-dismiss": "Hylgiä", - "ooui-dialog-process-retry": "Opi vie", - "ooui-dialog-process-continue": "Jatka", - "ooui-selectfile-button-select": "Valliče failu", - "ooui-selectfile-not-supported": "Failan valličendua ei tuveta", - "ooui-selectfile-placeholder": "Failua ei ole vallittu", - "ooui-selectfile-dragdrop-placeholder": "Kirvota failu täh" -} diff --git a/resources/lib/oojs-ui/i18n/om.json b/resources/lib/oojs-ui/i18n/om.json deleted file mode 100644 index 31344be74d..0000000000 --- a/resources/lib/oojs-ui/i18n/om.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Cedric31", - "Tumsaa" - ] - }, - "ooui-outline-control-move-down": "Gad buusi", - "ooui-outline-control-move-up": "Ol baasi", - "ooui-outline-control-remove": "Balleessi", - "ooui-toolbar-more": "Dabalata", - "ooui-toolgroup-expand": "Dabalata", - "ooui-toolgroup-collapse": "Xiqqaa", - "ooui-dialog-message-accept": "Tole", - "ooui-dialog-message-reject": "Dhiisi", - "ooui-dialog-process-error": "Dogoggorri wayii ummameera", - "ooui-dialog-process-dismiss": "Didi", - "ooui-dialog-process-retry": "Itti deebi'ii yaali", - "ooui-dialog-process-continue": "Itti fufi", - "ooui-selectfile-button-select": "Faayilii filadhu", - "ooui-selectfile-not-supported": "Faayilii filachuun hin danda'amu.", - "ooui-selectfile-placeholder": "Faayiliin wayiiyyuu hin filatamne", - "ooui-selectfile-dragdrop-placeholder": "Faayilii as kaa'i" -} diff --git a/resources/lib/oojs-ui/i18n/or.json b/resources/lib/oojs-ui/i18n/or.json deleted file mode 100644 index 7d96dcb705..0000000000 --- a/resources/lib/oojs-ui/i18n/or.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Odisha1", - "Psubhashish", - "ଶିତିକଣ୍ଠ ଦାଶ", - "Jnanaranjan Sahu" - ] - }, - "ooui-outline-control-move-down": "ବସ୍ତୁଟିକୁ ତଳକୁ ଘୁଞ୍ଚାନ୍ତୁ", - "ooui-outline-control-move-up": "ବସ୍ତୁଟିକୁ ଉପରକୁ ଘୁଞ୍ଚାନ୍ତୁ", - "ooui-outline-control-remove": "ବସ୍ତୁଟିକୁ ଲିଭାନ୍ତୁ", - "ooui-toolbar-more": "ଅଧିକ", - "ooui-toolgroup-expand": "ଅଧିକ", - "ooui-toolgroup-collapse": "ଅଳ୍ପ", - "ooui-dialog-message-accept": "ହେଉ", - "ooui-dialog-message-reject": "ନାକଚ", - "ooui-dialog-process-error": "ଅସୁବିଧାଟିଏ ଘଟିଲା", - "ooui-dialog-process-dismiss": "ଖାରଜ", - "ooui-dialog-process-retry": "ଆଉ ଥରେ ଚେଷ୍ଟା କରନ୍ତୁ", - "ooui-dialog-process-continue": "ଚାଲୁରଖିବେ", - "ooui-selectfile-not-supported": "ଫାଇଲ ବାଛିବା ସୁବିଧା ନାହିଁ", - "ooui-selectfile-placeholder": "କୌଣସି ଫାଇଲ ବଛାଯାଇନାହିଁ" -} diff --git a/resources/lib/oojs-ui/i18n/pa.json b/resources/lib/oojs-ui/i18n/pa.json deleted file mode 100644 index a69d76f9cd..0000000000 --- a/resources/lib/oojs-ui/i18n/pa.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Amikeco", - "Babanwalia", - "Bouron", - "Nasir8891", - "Satdeep gill" - ] - }, - "ooui-outline-control-move-down": "ਨੀਚੇ ਲੈਕੇ ਜਾਓ", - "ooui-outline-control-move-up": "ਉੱਤੇ ਲੈਕੇ ਜਾਓ", - "ooui-outline-control-remove": "ਆਈਟਮ ਹਟਾਓ", - "ooui-toolbar-more": "ਹੋਰ", - "ooui-toolgroup-expand": "ਹੋਰ", - "ooui-toolgroup-collapse": "ਥੋੜ੍ਹੇ", - "ooui-dialog-message-accept": "ਠੀਕ ਹੈ", - "ooui-dialog-message-reject": "ਰੱਦ ਕਰੋ", - "ooui-dialog-process-error": "ਕੁਝ ਗਲਤ ਹੋ ਗਿਆ", - "ooui-dialog-process-dismiss": "ਰੱਦ ਕਰੋ", - "ooui-dialog-process-retry": "ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ", - "ooui-dialog-process-continue": "ਜਾਰੀ ਰੱਖੋ", - "ooui-selectfile-button-select": "ਫ਼ਾਈਲ ਚੁਣੋ", - "ooui-selectfile-not-supported": "ਚੁਣੀ ਗਈ ਫ਼ਾਈਲ ਖੋਲੀ ਨਹੀਂ ਜਾ ਸਕਦੀ", - "ooui-selectfile-placeholder": "ਕੋਈ ਫ਼ਾਈਲ ਚੁਣੀ ਨਹੀਂ ਗਈ", - "ooui-selectfile-dragdrop-placeholder": "ਫ਼ਾਈਲ ਇੱਥੇ ਸਿੱਟੋ" -} diff --git a/resources/lib/oojs-ui/i18n/pfl.json b/resources/lib/oojs-ui/i18n/pfl.json deleted file mode 100644 index 02d08426ef..0000000000 --- a/resources/lib/oojs-ui/i18n/pfl.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Manuae" - ] - }, - "ooui-outline-control-move-down": "Bweeschs nunna", - "ooui-outline-control-move-up": "Bweeschs nuff", - "ooui-outline-control-remove": "Leschs", - "ooui-toolbar-more": "Mea", - "ooui-dialog-message-accept": "OK", - "ooui-dialog-message-reject": "Abbresche" -} diff --git a/resources/lib/oojs-ui/i18n/pl.json b/resources/lib/oojs-ui/i18n/pl.json deleted file mode 100644 index affc066a66..0000000000 --- a/resources/lib/oojs-ui/i18n/pl.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Babanwalia", - "Chrumps", - "Matma Rex", - "Mikołka", - "Nasir8891", - "Odie2", - "Rzuwig", - "Tar Lócesilion", - "Ty221", - "WTM", - "Woytecr", - "Wpedzich", - "Jacenty359", - "Matik7", - "Gloria sah", - "Andrzej aa", - "The Polish", - "Railfail536" - ] - }, - "ooui-outline-control-move-down": "Przesuń w dół", - "ooui-outline-control-move-up": "Przesuń w górę", - "ooui-outline-control-remove": "Usuń element", - "ooui-toolbar-more": "Więcej", - "ooui-toolgroup-expand": "Więcej", - "ooui-toolgroup-collapse": "Mniej", - "ooui-item-remove": "Usuń", - "ooui-dialog-message-accept": "OK", - "ooui-dialog-message-reject": "Anuluj", - "ooui-dialog-process-error": "Coś poszło nie tak", - "ooui-dialog-process-dismiss": "Powrót", - "ooui-dialog-process-retry": "Spróbuj ponownie", - "ooui-dialog-process-continue": "Kontynuuj", - "ooui-selectfile-button-select": "Wybierz plik", - "ooui-selectfile-not-supported": "Wybór pliku nie jest obsługiwany", - "ooui-selectfile-placeholder": "Nie wybrano pliku", - "ooui-selectfile-dragdrop-placeholder": "Upuść plik tutaj", - "ooui-field-help": "Pomoc" -} diff --git a/resources/lib/oojs-ui/i18n/pms.json b/resources/lib/oojs-ui/i18n/pms.json deleted file mode 100644 index c8b5bc7eb0..0000000000 --- a/resources/lib/oojs-ui/i18n/pms.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Borichèt", - "Dragonòt", - "පසිඳු කාවින්ද" - ] - }, - "ooui-outline-control-move-down": "Fé calé giù l'element", - "ooui-outline-control-move-up": "Fé monté l'element", - "ooui-outline-control-remove": "Gavé j'element", - "ooui-toolbar-more": "Ëd pi", - "ooui-toolgroup-expand": "Pi", - "ooui-toolgroup-collapse": "Men", - "ooui-dialog-message-accept": "Va bin", - "ooui-dialog-message-reject": "Scancelé", - "ooui-dialog-process-error": "Quaicòs a l'é andà mal", - "ooui-dialog-process-dismiss": "Stërmé", - "ooui-dialog-process-retry": "Preuva torna", - "ooui-dialog-process-continue": "Continua", - "ooui-selectfile-not-supported": "La selession d'archivi a l'é nen mantnùa", - "ooui-selectfile-placeholder": "Gnun archivi selessionà" -} diff --git a/resources/lib/oojs-ui/i18n/pnb.json b/resources/lib/oojs-ui/i18n/pnb.json deleted file mode 100644 index 386871c164..0000000000 --- a/resources/lib/oojs-ui/i18n/pnb.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Saanvel", - "Abbas dhothar" - ] - }, - "ooui-outline-control-move-down": "شیہ تھلے کرو", - "ooui-outline-control-move-up": "شیہ اتے کرو", - "ooui-outline-control-remove": "شیہ مٹاؤ", - "ooui-toolbar-more": "ہور", - "ooui-toolgroup-expand": "ہور", - "ooui-toolgroup-collapse": "گھٹ", - "ooui-dialog-message-accept": "OK", - "ooui-dialog-message-reject": "مکاؤ", - "ooui-dialog-process-error": "کوئی رپھڑ پے گیا اے۔", - "ooui-dialog-process-dismiss": "مکاؤ", - "ooui-dialog-process-retry": "فیر کرو", - "ooui-dialog-process-continue": "چلاؤ", - "ooui-selectfile-button-select": "فائل چنو", - "ooui-selectfile-placeholder": "کوئی فائل نئی چنی ہوئی", - "ooui-selectfile-dragdrop-placeholder": "فائل ایتھے پاؤ" -} diff --git a/resources/lib/oojs-ui/i18n/ps.json b/resources/lib/oojs-ui/i18n/ps.json deleted file mode 100644 index 579740fb0d..0000000000 --- a/resources/lib/oojs-ui/i18n/ps.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Ahmed-Najib-Biabani-Ibrahimkhel" - ] - }, - "ooui-outline-control-move-down": "توکی ښکته راوړل", - "ooui-outline-control-move-up": "توکی پورته راوړل", - "ooui-outline-control-remove": "توکی غورځول", - "ooui-toolbar-more": "نور", - "ooui-toolgroup-expand": "نور", - "ooui-toolgroup-collapse": "لږ تر لږ", - "ooui-dialog-message-accept": "ښه", - "ooui-dialog-message-reject": "ناگارل", - "ooui-dialog-process-error": "يوه ستونزه رامنځ ته شوه", - "ooui-dialog-process-dismiss": "تړل", - "ooui-dialog-process-retry": "بيا هڅه", - "ooui-dialog-process-continue": "پرله پورې", - "ooui-selectfile-button-select": "يوه دوتنه وټاکئ", - "ooui-selectfile-not-supported": "د دوتنې د ټاکنې ملاتړ نه دی شوی", - "ooui-selectfile-placeholder": "کومه دوتنه نه ده ټاکل شوې", - "ooui-selectfile-dragdrop-placeholder": "دوتنه مو دلته خوشې کړئ" -} diff --git a/resources/lib/oojs-ui/i18n/pt-br.json b/resources/lib/oojs-ui/i18n/pt-br.json deleted file mode 100644 index 11c5ff3fd9..0000000000 --- a/resources/lib/oojs-ui/i18n/pt-br.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Cainamarques", - "Dianakc", - "Fúlvio", - "Helder.wiki", - "HenriqueCrang", - "Jaideraf", - "Luckas", - "OTAVIO1981", - 555, - "TheEduGobi", - "TheGabrielZaum", - "Felipe L. Ewald", - "Eduardo Addad de Oliveira" - ] - }, - "ooui-outline-control-move-down": "Mover item para baixo", - "ooui-outline-control-move-up": "Mover item para cima", - "ooui-outline-control-remove": "Remover item", - "ooui-toolbar-more": "Mais", - "ooui-toolgroup-expand": "Mais", - "ooui-toolgroup-collapse": "Menos", - "ooui-item-remove": "Remover", - "ooui-dialog-message-accept": "OK", - "ooui-dialog-message-reject": "Cancelar", - "ooui-dialog-process-error": "Algo deu errado", - "ooui-dialog-process-dismiss": "Dispensar", - "ooui-dialog-process-retry": "Tente novamente", - "ooui-dialog-process-continue": "Continuar", - "ooui-selectfile-button-select": "Selecionar um arquivo", - "ooui-selectfile-not-supported": "O selecionamento de arquivos não é suportado", - "ooui-selectfile-placeholder": "Nenhum arquivo selecionado", - "ooui-selectfile-dragdrop-placeholder": "Arraste o arquivo para cá", - "ooui-field-help": "Ajuda" -} diff --git a/resources/lib/oojs-ui/i18n/pt.json b/resources/lib/oojs-ui/i18n/pt.json deleted file mode 100644 index 7117cd904d..0000000000 --- a/resources/lib/oojs-ui/i18n/pt.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Cainamarques", - "Fúlvio", - "GoEThe", - "Hamilton Abreu", - "Helder.wiki", - "Jaideraf", - "Jdforrester", - "Luckas", - "Vitorvicentevalente", - "SandroHc", - "Jkb8", - "Athena in Wonderland" - ] - }, - "ooui-outline-control-move-down": "Mover item para baixo", - "ooui-outline-control-move-up": "Mover item para cima", - "ooui-outline-control-remove": "Remover elemento", - "ooui-toolbar-more": "Mais", - "ooui-toolgroup-expand": "Mais", - "ooui-toolgroup-collapse": "Menos", - "ooui-item-remove": "Remover", - "ooui-dialog-message-accept": "Aceitar", - "ooui-dialog-message-reject": "Cancelar", - "ooui-dialog-process-error": "Algo correu mal", - "ooui-dialog-process-dismiss": "Ignorar", - "ooui-dialog-process-retry": "Tentar novamente", - "ooui-dialog-process-continue": "Continuar", - "ooui-selectfile-button-select": "Selecionar ficheiro", - "ooui-selectfile-not-supported": "A seleção de ficheiros não é suportada", - "ooui-selectfile-placeholder": "Nenhum ficheiro selecionado", - "ooui-selectfile-dragdrop-placeholder": "Soltar ficheiro aqui", - "ooui-field-help": "Ajuda" -} diff --git a/resources/lib/oojs-ui/i18n/qqq.json b/resources/lib/oojs-ui/i18n/qqq.json deleted file mode 100644 index 6c879fd3d6..0000000000 --- a/resources/lib/oojs-ui/i18n/qqq.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Amire80", - "Beta16", - "Erik Moeller", - "Jdforrester", - "Lloffiwr", - "Mooeypoo", - "Mormegil", - "Nike", - "PoLuX124", - "Purodha", - "Raymond", - "Sagan", - "Sayak Sarkar", - "Shirayuki", - "Siebrand", - "Trevor Parscal", - "Liuxinyu970226", - "Robby" - ] - }, - "ooui-outline-control-move-down": "Tool tip for a button that moves items in a list down one place", - "ooui-outline-control-move-up": "Tool tip for a button that moves items in a list up one place", - "ooui-outline-control-remove": "Tool tip for a button that removes items from a list.\n{{Identical|Remove item}}", - "ooui-toolbar-more": "Label for the toolbar group that contains a list of all other available tools.\n{{Identical|More}}", - "ooui-toolgroup-expand": "Label for the fake tool that expands the full list of tools in a toolbar group.\n\nSee also:\n* {{msg-mw|Ooui-toolgroup-collapse}}\n{{Identical|More}}", - "ooui-toolgroup-collapse": "Label for the fake tool that collapses the full list of tools in a toolbar group.\n\nSee also:\n* {{msg-mw|Ooui-toolgroup-expand}}\n{{Identical|Fewer}}", - "ooui-item-remove": "Text for the action of removing an item\n{{Identical|Remove}}", - "ooui-dialog-message-accept": "Default label for the accept button of a message dialog\n{{Identical|OK}}", - "ooui-dialog-message-reject": "Default label for the reject button of a message dialog\n{{Identical|Cancel}}", - "ooui-dialog-process-error": "Title for process dialog error description", - "ooui-dialog-process-dismiss": "Label for process dialog dismiss error button, visible when describing errors\n{{Identical|Dismiss}}", - "ooui-dialog-process-retry": "Label for process dialog retry action button, visible when describing recoverable errors\n{{Identical|Try again}}", - "ooui-dialog-process-continue": "Label for process dialog retry action button, visible when describing only warnings\n{{Identical|Continue}}", - "ooui-selectfile-button-select": "Label for the file selection widget's select file button", - "ooui-selectfile-not-supported": "Label for the file selection widget if file selection is not supported", - "ooui-selectfile-placeholder": "Label for the file selection widget when no file is currently selected", - "ooui-selectfile-dragdrop-placeholder": "Label for the file selection widget's drop target", - "ooui-field-help": "Label for the help icon attached to a form field\n{{Identical|Help}}" -} diff --git a/resources/lib/oojs-ui/i18n/qu.json b/resources/lib/oojs-ui/i18n/qu.json deleted file mode 100644 index 6a91da2185..0000000000 --- a/resources/lib/oojs-ui/i18n/qu.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "@metadata": { - "authors": [ - "AlimanRuna", - "Jduranboger", - "Fitoschido" - ] - }, - "ooui-outline-control-move-down": "Qallawata uraykuchiy", - "ooui-outline-control-move-up": "Qallawata huqariy", - "ooui-outline-control-remove": "P'anqa sutikunata qichuy", - "ooui-toolbar-more": "Aswan", - "ooui-field-help": "Yanapa" -} diff --git a/resources/lib/oojs-ui/i18n/ro.json b/resources/lib/oojs-ui/i18n/ro.json deleted file mode 100644 index 69daa1806c..0000000000 --- a/resources/lib/oojs-ui/i18n/ro.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "@metadata": { - "authors": [ - "AlimanRuna", - "Firilacroco", - "Minisarm", - "Stelistcristi", - "Gloria sah" - ] - }, - "ooui-outline-control-move-down": "Mută elementul mai jos", - "ooui-outline-control-move-up": "Mută elementul mai sus", - "ooui-outline-control-remove": "Elimină elementul", - "ooui-toolbar-more": "Mai mult", - "ooui-toolgroup-expand": "Mai multe", - "ooui-toolgroup-collapse": "Mai puține", - "ooui-dialog-message-accept": "OK", - "ooui-dialog-message-reject": "Revocare", - "ooui-dialog-process-error": "Ceva nu a funcționat", - "ooui-dialog-process-dismiss": "Renunțare", - "ooui-dialog-process-retry": "Reîncearcă", - "ooui-dialog-process-continue": "Continuă", - "ooui-selectfile-button-select": "Alege un fișier", - "ooui-selectfile-not-supported": "Selecția de fișiere nu este acceptată", - "ooui-selectfile-placeholder": "Niciun fișier selectat", - "ooui-selectfile-dragdrop-placeholder": "Trageți fișierul aici" -} diff --git a/resources/lib/oojs-ui/i18n/roa-tara.json b/resources/lib/oojs-ui/i18n/roa-tara.json deleted file mode 100644 index f396902a2b..0000000000 --- a/resources/lib/oojs-ui/i18n/roa-tara.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Joetaras" - ] - }, - "ooui-outline-control-move-down": "Spuèste 'a vôsce sotte", - "ooui-outline-control-move-up": "Spuèste 'a vôsce sus", - "ooui-outline-control-remove": "Live 'a vôsce", - "ooui-toolbar-more": "De cchiù", - "ooui-toolgroup-expand": "De cchiù", - "ooui-toolgroup-collapse": "De mene", - "ooui-item-remove": "Live", - "ooui-dialog-message-accept": "OK", - "ooui-dialog-message-reject": "Annulle", - "ooui-dialog-process-error": "Quacche cose ha sciute stuèrte", - "ooui-dialog-process-dismiss": "Scitte", - "ooui-dialog-process-retry": "Pruève arrete", - "ooui-dialog-process-continue": "Condinue", - "ooui-selectfile-button-select": "Scacchie 'nu file", - "ooui-selectfile-not-supported": "'U scacchiamende d'u file non g'è supportate", - "ooui-selectfile-placeholder": "Nisciune file scacchiate", - "ooui-selectfile-dragdrop-placeholder": "Scitte 'u file aqquà", - "ooui-field-help": "Aijute" -} diff --git a/resources/lib/oojs-ui/i18n/ru.json b/resources/lib/oojs-ui/i18n/ru.json deleted file mode 100644 index 8ca32054ce..0000000000 --- a/resources/lib/oojs-ui/i18n/ru.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Amire80", - "DR", - "Eugrus", - "Iluvatar", - "KPu3uC B Poccuu", - "Kalan", - "MaxBioHazard", - "NBS", - "Niklem", - "Okras", - "Ole Yves", - "Putnik", - "Sunpriat", - "Yury Katkov", - "Умар", - "Камалист", - "Meshkov.a", - "Mailman", - "Stjn" - ] - }, - "ooui-outline-control-move-down": "Переместить элемент вниз", - "ooui-outline-control-move-up": "Переместить элемент вверх", - "ooui-outline-control-remove": "Удалить пункт", - "ooui-toolbar-more": "Ещё", - "ooui-toolgroup-expand": "Больше", - "ooui-toolgroup-collapse": "Меньше", - "ooui-item-remove": "Удалить", - "ooui-dialog-message-accept": "ОК", - "ooui-dialog-message-reject": "Отмена", - "ooui-dialog-process-error": "Что-то пошло не так", - "ooui-dialog-process-dismiss": "Закрыть", - "ooui-dialog-process-retry": "Попробовать ещё раз", - "ooui-dialog-process-continue": "Продолжить", - "ooui-selectfile-button-select": "Выберите файл", - "ooui-selectfile-not-supported": "Выбор файла не поддерживается", - "ooui-selectfile-placeholder": "Не выбран файл", - "ooui-selectfile-dragdrop-placeholder": "Перетащите файл сюда", - "ooui-field-help": "Справка" -} diff --git a/resources/lib/oojs-ui/i18n/sa.json b/resources/lib/oojs-ui/i18n/sa.json deleted file mode 100644 index 49f038c73d..0000000000 --- a/resources/lib/oojs-ui/i18n/sa.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "@metadata": { - "authors": [ - "NehalDaveND" - ] - }, - "ooui-outline-control-remove": "वस्तु निष्कास्यताम्", - "ooui-toolbar-more": "अधिकम्", - "ooui-toolgroup-expand": "अधिकम्", - "ooui-dialog-message-accept": "अस्तु", - "ooui-dialog-message-reject": "निरस्यताम्", - "ooui-dialog-process-retry": "पुनः चेष्ट्यताम्", - "ooui-dialog-process-continue": "निरन्तरम्" -} diff --git a/resources/lib/oojs-ui/i18n/sah.json b/resources/lib/oojs-ui/i18n/sah.json deleted file mode 100644 index 1e4bb555dd..0000000000 --- a/resources/lib/oojs-ui/i18n/sah.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Gazeb", - "HalanTul" - ] - }, - "ooui-outline-control-move-down": "Аллара түһэрэн биэр", - "ooui-outline-control-move-up": "Үөһэ таһааран биэр", - "ooui-outline-control-remove": "Сот", - "ooui-toolbar-more": "Эбии", - "ooui-toolgroup-expand": "Эбии", - "ooui-toolgroup-collapse": "Кыччат", - "ooui-dialog-message-accept": "Сөп", - "ooui-dialog-message-reject": "Салҕаама", - "ooui-dialog-process-error": "Туга эрэ сатаммата", - "ooui-dialog-process-dismiss": "Сап", - "ooui-dialog-process-retry": "Хатылаан көр", - "ooui-dialog-process-continue": "Салгыы", - "ooui-selectfile-button-select": "Билэни тал", - "ooui-selectfile-not-supported": "Билэни талыы өйөммөт", - "ooui-selectfile-placeholder": "Биир да билэ талыллыбатах", - "ooui-selectfile-dragdrop-placeholder": "Билэни манна сыҕарыт" -} diff --git a/resources/lib/oojs-ui/i18n/scn.json b/resources/lib/oojs-ui/i18n/scn.json deleted file mode 100644 index 22a212f9af..0000000000 --- a/resources/lib/oojs-ui/i18n/scn.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Gazeb", - "Gmelfi", - "HalanTul", - "Gloria sah" - ] - }, - "ooui-outline-control-move-down": "Sposta di sutta", - "ooui-outline-control-move-up": "Sposta di supra", - "ooui-toolbar-more": "Àutri cosi" -} diff --git a/resources/lib/oojs-ui/i18n/sco.json b/resources/lib/oojs-ui/i18n/sco.json deleted file mode 100644 index 794d71f720..0000000000 --- a/resources/lib/oojs-ui/i18n/sco.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "@metadata": { - "authors": [ - "John Reid", - "Foxj" - ] - }, - "ooui-outline-control-move-down": "Muiv eetem doon", - "ooui-outline-control-move-up": "Muiv eetem up", - "ooui-outline-control-remove": "Remuiv eetem", - "ooui-toolbar-more": "Mair", - "ooui-toolgroup-expand": "Mair", - "ooui-toolgroup-collapse": "Less", - "ooui-dialog-message-accept": "OK", - "ooui-dialog-message-reject": "Cancel", - "ooui-dialog-process-error": "Sommit went wrang", - "ooui-dialog-process-dismiss": "Close", - "ooui-dialog-process-retry": "Hae aniter gae", - "ooui-dialog-process-continue": "Conteena", - "ooui-selectfile-not-supported": "Cannae pick ony files", - "ooui-selectfile-placeholder": "Nae file selectit" -} diff --git a/resources/lib/oojs-ui/i18n/sd.json b/resources/lib/oojs-ui/i18n/sd.json deleted file mode 100644 index dc14339c38..0000000000 --- a/resources/lib/oojs-ui/i18n/sd.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Mehtab ahmed" - ] - }, - "ooui-outline-control-move-down": "شيءِ کي هيٺ چوريو", - "ooui-outline-control-move-up": "شيءِ کي مٿي چوريو", - "ooui-outline-control-remove": "شيءِ هٽايو", - "ooui-toolbar-more": "وڌيڪ", - "ooui-toolgroup-expand": "وڌيڪ", - "ooui-toolgroup-collapse": "گھٽ تر", - "ooui-dialog-message-accept": "ٺيڪ", - "ooui-dialog-message-reject": "رد", - "ooui-dialog-process-error": "ڪا غلطي ٿي", - "ooui-dialog-process-dismiss": "برخاست ڪريو", - "ooui-dialog-process-retry": "ٻيهر ڪوشش ڪريو", - "ooui-dialog-process-continue": "جاري رکو", - "ooui-selectfile-button-select": "ڪو فائيل چونڊِو", - "ooui-selectfile-not-supported": "فائيل جي چونڊ سپورٽ نٿي ڪئي وڃي", - "ooui-selectfile-placeholder": "ڪوبه فائيل چونڊيو نه ويو آهي", - "ooui-selectfile-dragdrop-placeholder": "فائيل کي هتي ڪيرايو" -} diff --git a/resources/lib/oojs-ui/i18n/sh.json b/resources/lib/oojs-ui/i18n/sh.json deleted file mode 100644 index 532ba3f737..0000000000 --- a/resources/lib/oojs-ui/i18n/sh.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "@metadata": { - "authors": [ - "OC Ripper", - "Sf" - ] - }, - "ooui-outline-control-move-down": "Pomakni stavku dolje", - "ooui-outline-control-move-up": "Premjesti stavku gore", - "ooui-outline-control-remove": "Ukloni stavku", - "ooui-toolbar-more": "Više", - "ooui-toolgroup-expand": "Više", - "ooui-toolgroup-collapse": "Manje", - "ooui-dialog-message-accept": "U redu", - "ooui-dialog-message-reject": "Otkaži", - "ooui-dialog-process-error": "Nešto je pošlo naopako", - "ooui-dialog-process-dismiss": "Odbaci", - "ooui-dialog-process-retry": "Pokušajte ponovo", - "ooui-dialog-process-continue": "Nastavi", - "ooui-selectfile-button-select": "Izaberi datoteku", - "ooui-selectfile-not-supported": "Izbor datoteke nije podržan", - "ooui-selectfile-placeholder": "Nijedna datoteka nije odabrana", - "ooui-selectfile-dragdrop-placeholder": "Prevuci datoteku ovdje" -} diff --git a/resources/lib/oojs-ui/i18n/shn.json b/resources/lib/oojs-ui/i18n/shn.json deleted file mode 100644 index a93e616aa5..0000000000 --- a/resources/lib/oojs-ui/i18n/shn.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Saimawnkham" - ] - }, - "ooui-outline-control-move-down": "ၶၢႆႉလူင်းၽၢႆႇတႂ်ႈ", - "ooui-outline-control-move-up": "ၶၢႆႉၶိုၼ်ႈၽၢႆႇၼိူဝ်", - "ooui-outline-control-remove": "ထွၼ်ပႅတ်ႈ ဢၼ်ၶဝ်ႈပႃး", - "ooui-toolbar-more": "ၼမ်ႉလိူဝ်", - "ooui-toolgroup-expand": "ၼမ်လိူဝ်", - "ooui-toolgroup-collapse": "ဢေႇလိူဝ်", - "ooui-dialog-message-accept": "ဢူဝ်ႇၶေႇ", - "ooui-dialog-message-reject": "ဢမ်ႇႁဵတ်း", - "ooui-dialog-process-error": "သေဢၼ်ဢၼ်ၽိတ်းပိူင်ႈဝႆႉ", - "ooui-dialog-process-dismiss": "လူတ်းၵၢၼ်", - "ooui-dialog-process-retry": "ၶတ်းၸႂ်ထႅင်ႈ", - "ooui-dialog-process-continue": "သိုပ်ႇၼႃႈ", - "ooui-selectfile-button-select": "လိူၵ်ႈၾၢႆႇ", - "ooui-selectfile-not-supported": "လွင်ႈလိူၵ်ႈၽၢႆႇၼႆႉ ဢမ်ႇၵမ်ႉထႅမ်ဝႆႉပၼ်", - "ooui-selectfile-placeholder": "ဢမ်ႇလႆႈလိူၵ်ႈ ၾၢႆႇသင်ဝႆႉ", - "ooui-selectfile-dragdrop-placeholder": "ဢဝ်ၾၢႆႇ သႂ်ႇတီႈၼႆႉ" -} diff --git a/resources/lib/oojs-ui/i18n/si.json b/resources/lib/oojs-ui/i18n/si.json deleted file mode 100644 index 5988773b14..0000000000 --- a/resources/lib/oojs-ui/i18n/si.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Singhalawap", - "පසිඳු කාවින්ද", - "ශ්වෙත" - ] - }, - "ooui-outline-control-move-down": "අයිතමය පහලටදමන්න", - "ooui-outline-control-move-up": "අයිතමය ඉහලටදමන්න" -} diff --git a/resources/lib/oojs-ui/i18n/sk.json b/resources/lib/oojs-ui/i18n/sk.json deleted file mode 100644 index 6de4f7aab5..0000000000 --- a/resources/lib/oojs-ui/i18n/sk.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Mimarik", - "Teslaton", - "Kusavica", - "TomášPolonec" - ] - }, - "ooui-outline-control-move-down": "Posunúť položku nadol", - "ooui-outline-control-move-up": "Posunúť položku nahor", - "ooui-outline-control-remove": "Odstrániť položku", - "ooui-toolbar-more": "Viac", - "ooui-toolgroup-expand": "Viac", - "ooui-toolgroup-collapse": "Menej", - "ooui-item-remove": "Odstrániť", - "ooui-dialog-message-accept": "OK", - "ooui-dialog-message-reject": "Zrušiť", - "ooui-dialog-process-error": "Niečo sa pokazilo", - "ooui-dialog-process-dismiss": "Zrušiť", - "ooui-dialog-process-retry": "Skúsiť znova", - "ooui-dialog-process-continue": "Pokračovať", - "ooui-selectfile-button-select": "Vybrať súbor", - "ooui-selectfile-not-supported": "Výber súboru nie je podporovaný", - "ooui-selectfile-placeholder": "Nie je vybraný žiadny súbor", - "ooui-selectfile-dragdrop-placeholder": "Sem umiestni súbor", - "ooui-field-help": "Pomoc" -} diff --git a/resources/lib/oojs-ui/i18n/skr-arab.json b/resources/lib/oojs-ui/i18n/skr-arab.json deleted file mode 100644 index 1a2af2b07e..0000000000 --- a/resources/lib/oojs-ui/i18n/skr-arab.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Saraiki" - ] - }, - "ooui-toolbar-more": "ٻئے", - "ooui-toolgroup-expand": "ٻئے", - "ooui-toolgroup-collapse": "گھٹ", - "ooui-item-remove": "ہٹاؤ", - "ooui-dialog-message-accept": "ٹھیک ہے", - "ooui-dialog-message-reject": "منسوخ", - "ooui-dialog-process-error": "کجھ خراب تھی ڳئے", - "ooui-dialog-process-dismiss": "مکاؤ", - "ooui-dialog-process-retry": "ولدا کوشش کرو", - "ooui-dialog-process-continue": "جاری رکھو", - "ooui-selectfile-button-select": "فائل چݨو", - "ooui-selectfile-placeholder": "کوئی فائل کائنی چُݨی" -} diff --git a/resources/lib/oojs-ui/i18n/sl.json b/resources/lib/oojs-ui/i18n/sl.json deleted file mode 100644 index 8e1354994e..0000000000 --- a/resources/lib/oojs-ui/i18n/sl.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Dbc334", - "Eleassar", - "Pinky sl", - "Yerpo", - "Upwinxp" - ] - }, - "ooui-outline-control-move-down": "Prestavi predmet nižje", - "ooui-outline-control-move-up": "Prestavi predmet višje", - "ooui-outline-control-remove": "Odstrani vnos", - "ooui-toolbar-more": "Več", - "ooui-toolgroup-expand": "Več", - "ooui-toolgroup-collapse": "Manj", - "ooui-item-remove": "Odstrani", - "ooui-dialog-message-accept": "V redu", - "ooui-dialog-message-reject": "Prekliči", - "ooui-dialog-process-error": "Nekaj je šlo narobe", - "ooui-dialog-process-dismiss": "Skrij", - "ooui-dialog-process-retry": "Poskusi znova", - "ooui-dialog-process-continue": "Nadaljuj", - "ooui-selectfile-button-select": "Izberite datoteko", - "ooui-selectfile-not-supported": "Izbira datoteke ni podprta", - "ooui-selectfile-placeholder": "Nobena datoteka ni izbrana", - "ooui-selectfile-dragdrop-placeholder": "Tukaj spustite datoteko", - "ooui-field-help": "Pomoč" -} diff --git a/resources/lib/oojs-ui/i18n/sq.json b/resources/lib/oojs-ui/i18n/sq.json deleted file mode 100644 index 679f1a6f80..0000000000 --- a/resources/lib/oojs-ui/i18n/sq.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Euriditi", - "Kushtrim", - "Elioqoshi", - "GretaDoci", - "Gertakapllani", - "Techlik", - "Liridon" - ] - }, - "ooui-outline-control-move-down": "Zhvendose artikullin më poshtë", - "ooui-outline-control-move-up": "Zhvendose artikullin më lart", - "ooui-outline-control-remove": "Hiq artikullin", - "ooui-toolbar-more": "Më tepër...", - "ooui-toolgroup-expand": "Më tepër...", - "ooui-toolgroup-collapse": "Më pak", - "ooui-dialog-message-accept": "Në rregull", - "ooui-dialog-message-reject": "Anullo", - "ooui-dialog-process-error": "Diçka shkoi keq", - "ooui-dialog-process-dismiss": "Largoje", - "ooui-dialog-process-retry": "Provo përsëri", - "ooui-dialog-process-continue": "Vazhdo", - "ooui-selectfile-button-select": "Përzgjidhni një skedë", - "ooui-selectfile-not-supported": "Skedari i përzgjedhur nuk përkrahet", - "ooui-selectfile-placeholder": "Nuk është zgjedhur asnjë skedar", - "ooui-selectfile-dragdrop-placeholder": "Vendose skedën këtu" -} diff --git a/resources/lib/oojs-ui/i18n/sr-ec.json b/resources/lib/oojs-ui/i18n/sr-ec.json deleted file mode 100644 index 167adba30b..0000000000 --- a/resources/lib/oojs-ui/i18n/sr-ec.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Milicevic01", - "Nikola Smolenski", - "Милан Јелисавчић", - "Zoranzoki21", - "Obsuser", - "Prevodim", - "BadDog" - ] - }, - "ooui-outline-control-move-down": "Премести ставку надоле", - "ooui-outline-control-move-up": "Премести ставку нагоре", - "ooui-outline-control-remove": "Уклони ставку", - "ooui-toolbar-more": "Више", - "ooui-toolgroup-expand": "Више", - "ooui-toolgroup-collapse": "Мање", - "ooui-item-remove": "Уклони", - "ooui-dialog-message-accept": "У реду", - "ooui-dialog-message-reject": "Откажи", - "ooui-dialog-process-error": "Нешто није у реду", - "ooui-dialog-process-dismiss": "Одбаци", - "ooui-dialog-process-retry": "Покушај поново", - "ooui-dialog-process-continue": "Настави", - "ooui-selectfile-button-select": "Изабери датотеку", - "ooui-selectfile-not-supported": "Избор датотеке није подржан", - "ooui-selectfile-placeholder": "Није изабрана ниједна датотека", - "ooui-selectfile-dragdrop-placeholder": "Превуците датотеку овде" -} diff --git a/resources/lib/oojs-ui/i18n/sr-el.json b/resources/lib/oojs-ui/i18n/sr-el.json deleted file mode 100644 index cd286e809b..0000000000 --- a/resources/lib/oojs-ui/i18n/sr-el.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Milicevic01", - "Prevodim" - ] - }, - "ooui-outline-control-move-down": "Premesti stavku na dole", - "ooui-outline-control-move-up": "Premesti stavku na gore", - "ooui-outline-control-remove": "Ukloni stavku", - "ooui-toolbar-more": "Više", - "ooui-toolgroup-expand": "Više", - "ooui-toolgroup-collapse": "Manje", - "ooui-item-remove": "Ukloni", - "ooui-dialog-message-accept": "U redu", - "ooui-dialog-message-reject": "Otkaži", - "ooui-dialog-process-error": "Nešto je pošlo naopako", - "ooui-dialog-process-dismiss": "Odbaci", - "ooui-dialog-process-retry": "Pokušaj ponovo", - "ooui-dialog-process-continue": "Nastavi", - "ooui-selectfile-button-select": "Izaberi datoteku", - "ooui-selectfile-not-supported": "Odabir datoteke nije podržan", - "ooui-selectfile-placeholder": "Nije izabrana nijedna datoteka", - "ooui-selectfile-dragdrop-placeholder": "Prevuci datoteku ovde" -} diff --git a/resources/lib/oojs-ui/i18n/su.json b/resources/lib/oojs-ui/i18n/su.json deleted file mode 100644 index 2824d5bd43..0000000000 --- a/resources/lib/oojs-ui/i18n/su.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Kandar", - "Uchup19" - ] - }, - "ooui-outline-control-move-down": "Pindahkeun ka handap", - "ooui-outline-control-move-up": "Pindahkeun ka luhur", - "ooui-outline-control-remove": "Hapus", - "ooui-toolbar-more": "Lobaan", - "ooui-toolgroup-expand": "Lobaan", - "ooui-toolgroup-collapse": "Saeutikan", - "ooui-item-remove": "Pupus", - "ooui-dialog-message-accept": "Heug", - "ooui-dialog-message-reject": "Bolay", - "ooui-dialog-process-error": "Aya nu teu bener", - "ooui-dialog-process-dismiss": "Tutup", - "ooui-dialog-process-retry": "Cobaan deui", - "ooui-dialog-process-continue": "Teruskeun", - "ooui-selectfile-button-select": "Pilih berkas", - "ooui-selectfile-not-supported": "Pamilihan berkas teu dirojong", - "ooui-selectfile-placeholder": "Taya berkas anu dipilih", - "ooui-selectfile-dragdrop-placeholder": "Leupaskeun berkas di dieu" -} diff --git a/resources/lib/oojs-ui/i18n/sv.json b/resources/lib/oojs-ui/i18n/sv.json deleted file mode 100644 index c49b39258c..0000000000 --- a/resources/lib/oojs-ui/i18n/sv.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Ainali", - "Haxpett", - "Jopparn", - "Knuckles", - "Magol", - "Milicevic01", - "Per", - "Sendelbach", - "Skalman", - "WikiPhoenix", - "Lokal Profil", - "Warrakkk", - "Bengtsson96" - ] - }, - "ooui-outline-control-move-down": "Flytta ned objekt", - "ooui-outline-control-move-up": "Flytta upp objekt", - "ooui-outline-control-remove": "Ta bort objekt", - "ooui-toolbar-more": "Mer", - "ooui-toolgroup-expand": "Fler", - "ooui-toolgroup-collapse": "Färre", - "ooui-item-remove": "Ta bort", - "ooui-dialog-message-accept": "OK", - "ooui-dialog-message-reject": "Avbryt", - "ooui-dialog-process-error": "Något gick fel", - "ooui-dialog-process-dismiss": "Stäng", - "ooui-dialog-process-retry": "Försök igen", - "ooui-dialog-process-continue": "Fortsätt", - "ooui-selectfile-button-select": "Välj en fil", - "ooui-selectfile-not-supported": "Filval stöds inte", - "ooui-selectfile-placeholder": "Ingen fil är vald", - "ooui-selectfile-dragdrop-placeholder": "Släpp filen här", - "ooui-field-help": "Hjälp" -} diff --git a/resources/lib/oojs-ui/i18n/sw.json b/resources/lib/oojs-ui/i18n/sw.json deleted file mode 100644 index 598acbcde5..0000000000 --- a/resources/lib/oojs-ui/i18n/sw.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Lloffiwr", - "Muddyb Blast Producer", - "Muddyb" - ] - }, - "ooui-outline-control-move-down": "Sogeza kipengee chini", - "ooui-outline-control-move-up": "Sogeza kipengee juu", - "ooui-outline-control-remove": "Toa kitu", - "ooui-toolbar-more": "Zaidi", - "ooui-dialog-message-accept": "Sawa", - "ooui-dialog-message-reject": "Batilisha", - "ooui-dialog-process-retry": "Jaribu tena" -} diff --git a/resources/lib/oojs-ui/i18n/ta.json b/resources/lib/oojs-ui/i18n/ta.json deleted file mode 100644 index 6e7b249483..0000000000 --- a/resources/lib/oojs-ui/i18n/ta.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Jayarathina", - "Sank", - "Shanmugamp7", - "மதனாஹரன்", - "ElangoRamanujam", - "Info-farmer" - ] - }, - "ooui-outline-control-move-down": "உருப்படியை கீழிடு", - "ooui-outline-control-move-up": "உருப்படியை மேலிடு", - "ooui-outline-control-remove": "உருப்படியை நீக்கு", - "ooui-toolbar-more": "மேலும்", - "ooui-toolgroup-expand": "மேலும்", - "ooui-toolgroup-collapse": "குறைவாக", - "ooui-dialog-message-accept": "சரி", - "ooui-dialog-message-reject": "கைவிடுக", - "ooui-dialog-process-error": "ஏதோ தவறாகியுள்ளது", - "ooui-dialog-process-dismiss": "அகற்று", - "ooui-dialog-process-retry": "மீண்டும் முயல்க", - "ooui-dialog-process-continue": "தொடரவும்", - "ooui-selectfile-not-supported": "கோப்புத்தேர்வு ஆதரவாக இல்லை", - "ooui-selectfile-placeholder": "எக்கோப்பும் தெரிவாகவில்லை" -} diff --git a/resources/lib/oojs-ui/i18n/tay.json b/resources/lib/oojs-ui/i18n/tay.json deleted file mode 100644 index 12a6f957f6..0000000000 --- a/resources/lib/oojs-ui/i18n/tay.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Hitaypayan", - "Translatealcd" - ] - }, - "ooui-outline-control-move-down": "Hz’aniy tay mkyahu’ quw binkgan lmlamu’", - "ooui-outline-control-move-up": "Hz’aniy tay mkraya’ quw binkgan lmlamu’", - "ooui-outline-control-remove": "Laxiy quw pin’ubuy binkgan lmlamu", - "ooui-toolbar-more": "Pzyux na’", - "ooui-toolgroup-collapse": "Cikuy hazi’", - "ooui-item-remove": "Laxan", - "ooui-dialog-message-accept": "Wal balay", - "ooui-dialog-message-reject": "Laxan", - "ooui-dialog-process-dismiss": "Ql’iy", - "ooui-dialog-process-retry": "Tlamiy lawziy", - "ooui-dialog-process-continue": "Siy lhingiy", - "ooui-selectfile-placeholder": "Ini’ wzyagiy na’ Tang’an" -} diff --git a/resources/lib/oojs-ui/i18n/te.json b/resources/lib/oojs-ui/i18n/te.json deleted file mode 100644 index dfa0eb4bce..0000000000 --- a/resources/lib/oojs-ui/i18n/te.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Arjunaraoc", - "Jayarathina", - "Sank", - "Shanmugamp7", - "Veeven", - "Visdaviva", - "மதனாஹரன்", - "రహ్మానుద్దీన్", - "Chaduvari" - ] - }, - "ooui-outline-control-move-down": "అంశాన్ని కిందికి జరుపు", - "ooui-outline-control-move-up": "అంశాన్ని పైకి జరుపు", - "ooui-outline-control-remove": "అంశాన్ని తీసివేయి", - "ooui-toolbar-more": "మరిన్ని", - "ooui-toolgroup-expand": "మరిన్ని", - "ooui-toolgroup-collapse": "కొన్ని", - "ooui-item-remove": "తొలగించు", - "ooui-dialog-message-accept": "సరే", - "ooui-dialog-message-reject": "రద్దుచేయి", - "ooui-dialog-process-error": "ఏదో పొరపాటు జరిగింది", - "ooui-dialog-process-dismiss": "తీసివేయి", - "ooui-dialog-process-retry": "మళ్ళీ ప్రయత్నించు", - "ooui-dialog-process-continue": "కొనసాగించు", - "ooui-selectfile-button-select": "దస్త్రాన్ని ఎంచుకోండి", - "ooui-selectfile-not-supported": "దస్త్రపు ఎంపిక అందుబాటులో లేదు", - "ooui-selectfile-placeholder": "దస్త్రం దేన్నీ ఎంచుకోలేదు", - "ooui-selectfile-dragdrop-placeholder": "దస్త్రాన్ని ఇక్కడ పడేయండి", - "ooui-field-help": "సహాయం" -} diff --git a/resources/lib/oojs-ui/i18n/tg-cyrl.json b/resources/lib/oojs-ui/i18n/tg-cyrl.json deleted file mode 100644 index 1429bedda1..0000000000 --- a/resources/lib/oojs-ui/i18n/tg-cyrl.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Ibrahim" - ] - }, - "ooui-outline-control-move-down": "Ҳаракати мавод ба поён", - "ooui-outline-control-move-up": "Ҳаракати мавод ба боло", - "ooui-outline-control-remove": "Ҳазви мавод", - "ooui-toolbar-more": "Бештар" -} diff --git a/resources/lib/oojs-ui/i18n/th.json b/resources/lib/oojs-ui/i18n/th.json deleted file mode 100644 index 94527935cd..0000000000 --- a/resources/lib/oojs-ui/i18n/th.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Supasate", - "Taweetham" - ] - }, - "ooui-outline-control-move-down": "เลื่อนรายการลง", - "ooui-outline-control-move-up": "ย้ายรายการขึ้น" -} diff --git a/resources/lib/oojs-ui/i18n/tl.json b/resources/lib/oojs-ui/i18n/tl.json deleted file mode 100644 index c0dbd5fd2c..0000000000 --- a/resources/lib/oojs-ui/i18n/tl.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "@metadata": { - "authors": [ - "AnakngAraw", - "Sky Harbor", - "Jewel457" - ] - }, - "ooui-outline-control-move-down": "Ilipat ang aytem pababa", - "ooui-outline-control-move-up": "Ilipat ang aytem pataas", - "ooui-outline-control-remove": "Tanggalin ang aytem", - "ooui-toolbar-more": "Marami pa", - "ooui-toolgroup-expand": "Maraming iba pa", - "ooui-toolgroup-collapse": "Kakaunti", - "ooui-dialog-message-accept": "Sige", - "ooui-dialog-message-reject": "Huwag ituloy", - "ooui-dialog-process-error": "May pagkakamali", - "ooui-dialog-process-dismiss": "Isa-isantabi", - "ooui-dialog-process-retry": "Subuking muli", - "ooui-dialog-process-continue": "Magpatuloy", - "ooui-selectfile-not-supported": "Ang pagpili ng file ay hindi kinakatigan", - "ooui-selectfile-placeholder": "Walang piniling file" -} diff --git a/resources/lib/oojs-ui/i18n/tr.json b/resources/lib/oojs-ui/i18n/tr.json deleted file mode 100644 index 36adc50ad8..0000000000 --- a/resources/lib/oojs-ui/i18n/tr.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Emperyan", - "Incelemeelemani", - "LuCKY", - "Maidis", - "Rapsar", - "Talha Samil Cakir", - "TurkishStyles", - "Sayginer", - "Meelo", - "McAang", - "Uğurkent", - "1917 Ekim Devrimi", - "Hedda" - ] - }, - "ooui-outline-control-move-down": "Ögeyi aşağı taşı", - "ooui-outline-control-move-up": "Ögeyi yukarı taşı", - "ooui-outline-control-remove": "Ögeyi kaldır", - "ooui-toolbar-more": "Daha fazla", - "ooui-toolgroup-expand": "Dahası", - "ooui-toolgroup-collapse": "Daha az", - "ooui-item-remove": "Kaldır", - "ooui-dialog-message-accept": "Tamam", - "ooui-dialog-message-reject": "İptal", - "ooui-dialog-process-error": "Bir şeyler yanlış gitti", - "ooui-dialog-process-dismiss": "Kapat", - "ooui-dialog-process-retry": "Tekrar dene", - "ooui-dialog-process-continue": "Devam et", - "ooui-selectfile-button-select": "Dosya seç", - "ooui-selectfile-not-supported": "Dosya seçimi desteklenmiyor", - "ooui-selectfile-placeholder": "Herhangi bir dosya seçilmedi", - "ooui-selectfile-dragdrop-placeholder": "Dosyayı buraya aç", - "ooui-field-help": "Yardım" -} diff --git a/resources/lib/oojs-ui/i18n/tt-cyrl.json b/resources/lib/oojs-ui/i18n/tt-cyrl.json deleted file mode 100644 index 335e509e32..0000000000 --- a/resources/lib/oojs-ui/i18n/tt-cyrl.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Ajdar", - "Ильнар" - ] - }, - "ooui-outline-control-move-down": "Элементны аска күчерү", - "ooui-outline-control-move-up": "Элементны өскә күчерү", - "ooui-outline-control-remove": "Пунктны бетерү", - "ooui-toolbar-more": "Тагын", - "ooui-toolgroup-expand": "Күбрәк", - "ooui-toolgroup-collapse": "Азрак", - "ooui-dialog-message-accept": "ОК", - "ooui-dialog-message-reject": "Баш тарту", - "ooui-dialog-process-error": "Нәрсәдер килеп чыкмады", - "ooui-dialog-process-dismiss": "Ябу", - "ooui-dialog-process-retry": "Кабатлау", - "ooui-dialog-process-continue": "Дәвам итү", - "ooui-selectfile-button-select": "Файлны сайлагыз", - "ooui-selectfile-not-supported": "Файл сайлау хупланмый", - "ooui-selectfile-placeholder": "Файл сайланмаган", - "ooui-selectfile-dragdrop-placeholder": "Файлны монда куегыз" -} diff --git a/resources/lib/oojs-ui/i18n/tw.json b/resources/lib/oojs-ui/i18n/tw.json deleted file mode 100644 index 2cc3fb16dc..0000000000 --- a/resources/lib/oojs-ui/i18n/tw.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Celestinesucess", - "Ed g2s" - ] - }, - "ooui-outline-control-move-down": "Pia ade kɔ ase", - "ooui-outline-control-move-up": "Pia ade kɔ soro", - "ooui-toolbar-more": "Bio", - "ooui-toolgroup-expand": "Bio", - "ooui-toolgroup-collapse": "Kakraa", - "ooui-item-remove": "Yi", - "ooui-dialog-message-accept": "Yoo", - "ooui-dialog-message-reject": "Twa mu", - "ooui-dialog-process-error": "Bibi ankɔ yie", - "ooui-dialog-process-retry": "Yɛ bio", - "ooui-dialog-process-continue": "Kɔso", - "ooui-field-help": "Mmoa" -} diff --git a/resources/lib/oojs-ui/i18n/ug-arab.json b/resources/lib/oojs-ui/i18n/ug-arab.json deleted file mode 100644 index b19dd0d36c..0000000000 --- a/resources/lib/oojs-ui/i18n/ug-arab.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Sahran", - "Tel'et", - "Tifinaghes" - ] - }, - "ooui-outline-control-move-down": "تۆۋەنگە يۆتكە", - "ooui-outline-control-move-up": "يۇقۇرىغا يۆتكە", - "ooui-outline-control-remove": "ئۆچۈر", - "ooui-toolbar-more": "تېخىمۇ كۆپ", - "ooui-toolgroup-expand": "تېخىمۇ كۆپ", - "ooui-toolgroup-collapse": "ئاز", - "ooui-item-remove": "چىقىرىۋەت", - "ooui-dialog-message-accept": "تامام", - "ooui-dialog-message-reject": "ۋاز كەچ", - "ooui-dialog-process-error": "نامەلۇم خاتالىق كۆرۈلدى", - "ooui-dialog-process-dismiss": "چىقىرىۋەت", - "ooui-dialog-process-retry": "قايتا سىنا", - "ooui-dialog-process-continue": "داۋاملاشتۇر", - "ooui-selectfile-button-select": "بىر ھۆججەت تاللا", - "ooui-selectfile-not-supported": "تاللانغان ھۆججەتتە مەسىلە بار", - "ooui-selectfile-placeholder": "ھۆججەت تاللانمىدى", - "ooui-selectfile-dragdrop-placeholder": "ھۆججەتنى بۇ يەرگە تاشلاڭ" -} diff --git a/resources/lib/oojs-ui/i18n/uk.json b/resources/lib/oojs-ui/i18n/uk.json deleted file mode 100644 index d5c312d12b..0000000000 --- a/resources/lib/oojs-ui/i18n/uk.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "@metadata": { - "authors": [ - "AS", - "Aced", - "Ahonc", - "Andriykopanytsia", - "Base", - "Perohanych", - "RLuts", - "Sahran", - "Sergento", - "Steve.rusyn", - "SteveR", - "Tel'et", - "Tifinaghes", - "Ата", - "Piramidion", - "A1", - "Dars", - "Esk78" - ] - }, - "ooui-outline-control-move-down": "Перемістити елемент униз", - "ooui-outline-control-move-up": "Перемістити елемент вгору", - "ooui-outline-control-remove": "Видалити елемент", - "ooui-toolbar-more": "Більше", - "ooui-toolgroup-expand": "Більше", - "ooui-toolgroup-collapse": "Менше", - "ooui-item-remove": "Вилучити", - "ooui-dialog-message-accept": "Готово", - "ooui-dialog-message-reject": "Скасувати", - "ooui-dialog-process-error": "Щось пішло не так", - "ooui-dialog-process-dismiss": "Приховати", - "ooui-dialog-process-retry": "Спробуйте ще раз", - "ooui-dialog-process-continue": "Продовжити", - "ooui-selectfile-button-select": "Оберіть файл", - "ooui-selectfile-not-supported": "Вибір файлу не підтримується", - "ooui-selectfile-placeholder": "Жодного файлу не вибрано", - "ooui-selectfile-dragdrop-placeholder": "Помістіть файл сюди", - "ooui-field-help": "Допомога" -} diff --git a/resources/lib/oojs-ui/i18n/ur.json b/resources/lib/oojs-ui/i18n/ur.json deleted file mode 100644 index fcf9b1f469..0000000000 --- a/resources/lib/oojs-ui/i18n/ur.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Muhammad Shuaib", - "Zainab Meher", - "BukhariSaeed" - ] - }, - "ooui-outline-control-move-down": "مد نیچے کھسکائیں", - "ooui-outline-control-move-up": " مداوپرلےجائیں", - "ooui-outline-control-remove": " مدحذف کریں", - "ooui-toolbar-more": "مزید", - "ooui-toolgroup-expand": "مزید", - "ooui-toolgroup-collapse": "کم کریں", - "ooui-item-remove": "ھٹادیں", - "ooui-dialog-message-accept": "ٹھیک", - "ooui-dialog-message-reject": "منسوخ کریں", - "ooui-dialog-process-error": "کچھ غلط ہو گیا ہے", - "ooui-dialog-process-dismiss": "موقوف کریں", - "ooui-dialog-process-retry": "دوبارہ کوشش کریں", - "ooui-dialog-process-continue": "جاری رکھیں", - "ooui-selectfile-button-select": "فائل منتخب کریں", - "ooui-selectfile-not-supported": "فائل کا انتخاب معاونت شدہ نہیں", - "ooui-selectfile-placeholder": "کوئی فائل منتخب نہیں کی گئ", - "ooui-selectfile-dragdrop-placeholder": "فائل یہاں چھوڑیں" -} diff --git a/resources/lib/oojs-ui/i18n/uz.json b/resources/lib/oojs-ui/i18n/uz.json deleted file mode 100644 index 7c6263e4f7..0000000000 --- a/resources/lib/oojs-ui/i18n/uz.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "@metadata": { - "authors": [ - "CoderSI", - "Noor2020", - "Sociologist", - "පසිඳු කාවින්ද" - ] - }, - "ooui-outline-control-move-down": "Elementni pastga koʻchirish", - "ooui-outline-control-move-up": "Elementni yuqoriga koʻchirish", - "ooui-toolbar-more": "Yana" -} diff --git a/resources/lib/oojs-ui/i18n/vec.json b/resources/lib/oojs-ui/i18n/vec.json deleted file mode 100644 index 4a1685b6c7..0000000000 --- a/resources/lib/oojs-ui/i18n/vec.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Candalua", - "GatoSelvadego", - "Gloria sah", - "Fitoschido" - ] - }, - "ooui-outline-control-move-down": "Sposta in baso", - "ooui-outline-control-move-up": "Sposta in sima", - "ooui-toolbar-more": "Altro", - "ooui-toolgroup-expand": "Piassè", - "ooui-toolgroup-collapse": "Manco", - "ooui-dialog-message-accept": "Va ben", - "ooui-dialog-message-reject": "Fa gnente", - "ooui-dialog-process-error": "Xe 'ndà storto calcossa", - "ooui-dialog-process-dismiss": "Scondi", - "ooui-dialog-process-retry": "Proa da novo", - "ooui-dialog-process-continue": "Và vanti", - "ooui-selectfile-button-select": "Siegli un file", - "ooui-selectfile-dragdrop-placeholder": "Mola zo el file chì rento", - "ooui-field-help": "Ajuto" -} diff --git a/resources/lib/oojs-ui/i18n/vep.json b/resources/lib/oojs-ui/i18n/vep.json deleted file mode 100644 index b6ad0929c6..0000000000 --- a/resources/lib/oojs-ui/i18n/vep.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Sebranik" - ] - }, - "ooui-toolgroup-expand": "Enamba" -} diff --git a/resources/lib/oojs-ui/i18n/vi.json b/resources/lib/oojs-ui/i18n/vi.json deleted file mode 100644 index 1664d9b9e7..0000000000 --- a/resources/lib/oojs-ui/i18n/vi.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Cheers!", - "Jdforrester", - "Minh Nguyen", - "Max20091", - "Anh88" - ] - }, - "ooui-outline-control-move-down": "Chuyển mục xuống", - "ooui-outline-control-move-up": "Chuyển mục lên", - "ooui-outline-control-remove": "Xóa mục", - "ooui-toolbar-more": "Thêm", - "ooui-toolgroup-expand": "Mở rộng", - "ooui-toolgroup-collapse": "Rút gọn", - "ooui-item-remove": "Loại bỏ", - "ooui-dialog-message-accept": "OK", - "ooui-dialog-message-reject": "Hủy bỏ", - "ooui-dialog-process-error": "Có thứ gì đó bị lỗi", - "ooui-dialog-process-dismiss": "Bỏ qua", - "ooui-dialog-process-retry": "Thử lại", - "ooui-dialog-process-continue": "Tiếp tục", - "ooui-selectfile-button-select": "Chọn tập tin", - "ooui-selectfile-not-supported": "Không hỗ trợ việc chọn tập tin", - "ooui-selectfile-placeholder": "Không có tập tin nào được chọn", - "ooui-selectfile-dragdrop-placeholder": "Thả tập tin vào đây", - "ooui-field-help": "Trợ giúp" -} diff --git a/resources/lib/oojs-ui/i18n/vo.json b/resources/lib/oojs-ui/i18n/vo.json deleted file mode 100644 index 3510ca9322..0000000000 --- a/resources/lib/oojs-ui/i18n/vo.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Malafaya" - ] - }, - "ooui-toolbar-more": "Pluikos" -} diff --git a/resources/lib/oojs-ui/i18n/war.json b/resources/lib/oojs-ui/i18n/war.json deleted file mode 100644 index b0ea30cac4..0000000000 --- a/resources/lib/oojs-ui/i18n/war.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "@metadata": { - "authors": [ - "JinJian" - ] - }, - "ooui-outline-control-move-down": "Ibalhin paubos", - "ooui-outline-control-move-up": "Ibalhin paigbaw", - "ooui-outline-control-remove": "Tanggala", - "ooui-toolbar-more": "Damo pa", - "ooui-toolgroup-expand": "Damo pa", - "ooui-toolgroup-collapse": "Guruguti", - "ooui-dialog-message-accept": "OK", - "ooui-dialog-message-reject": "Igpabaliwaray", - "ooui-dialog-process-error": "Mayda sayop nga nahitabo", - "ooui-dialog-process-retry": "Utroha", - "ooui-dialog-process-continue": "Padayon", - "ooui-selectfile-button-select": "Pagpili hin file" -} diff --git a/resources/lib/oojs-ui/i18n/wuu.json b/resources/lib/oojs-ui/i18n/wuu.json deleted file mode 100644 index 64816249d1..0000000000 --- a/resources/lib/oojs-ui/i18n/wuu.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Malafaya", - "十弌", - "飞舞回堂前" - ] - }, - "ooui-toolbar-more": "更多" -} diff --git a/resources/lib/oojs-ui/i18n/xmf.json b/resources/lib/oojs-ui/i18n/xmf.json deleted file mode 100644 index 4109c36eba..0000000000 --- a/resources/lib/oojs-ui/i18n/xmf.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "@metadata": { - "authors": [ - "David1010", - "Silovan" - ] - }, - "ooui-outline-control-move-down": "ელემენტის ქვემოთ გადატანა", - "ooui-outline-control-move-up": "ელემენტის ზემოთ გადატანა", - "ooui-outline-control-remove": "ელემენტის წაშლა", - "ooui-toolbar-more": "უმოსი", - "ooui-toolgroup-expand": "უმოსი", - "ooui-toolgroup-collapse": "რამდენიმე", - "ooui-dialog-message-accept": "ჯგირი", - "ooui-dialog-message-reject": "გოუქვაფა", - "ooui-dialog-process-error": "მოხდა რაღაც შეცდომა", - "ooui-dialog-process-dismiss": "დამალვა", - "ooui-dialog-process-retry": "კიდევ სცადეთ", - "ooui-dialog-process-continue": "გაგრძელება", - "ooui-selectfile-button-select": "გეგშაგორით ფაილი", - "ooui-selectfile-not-supported": "ფაილიშ აშაგორუა ვა რე ხენწყილი", - "ooui-selectfile-placeholder": "ფაილი ვა რე გიშაგორილი", - "ooui-selectfile-dragdrop-placeholder": "ქინაჸათით ფაილი ათაქ" -} diff --git a/resources/lib/oojs-ui/i18n/yi.json b/resources/lib/oojs-ui/i18n/yi.json deleted file mode 100644 index 9fe75fce03..0000000000 --- a/resources/lib/oojs-ui/i18n/yi.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Malafaya", - "פוילישער", - "十弌" - ] - }, - "ooui-outline-control-move-down": "רוקן עלעמענט אראפ", - "ooui-outline-control-move-up": "רוקן עלעמענט ארויף", - "ooui-outline-control-remove": "אַראָפנעמען איינס", - "ooui-toolbar-more": "נאך", - "ooui-toolgroup-expand": "נאך", - "ooui-toolgroup-collapse": "ווייניגער", - "ooui-item-remove": "אַראָפּנעמען", - "ooui-dialog-message-accept": "יאָ", - "ooui-dialog-message-reject": "אַנולירן", - "ooui-dialog-process-error": "עפעס איז דורכגעפאלן", - "ooui-dialog-process-dismiss": "צומאַכן", - "ooui-dialog-process-retry": "פרובירט נאכאמאל", - "ooui-dialog-process-continue": "פֿארזעצן", - "ooui-selectfile-button-select": "קלויבט א טעקע", - "ooui-selectfile-not-supported": "טעקע אויסווייל נישט געשטיצט", - "ooui-selectfile-placeholder": "קיין טעקע נישט אויסגעוויילט" -} diff --git a/resources/lib/oojs-ui/i18n/yo.json b/resources/lib/oojs-ui/i18n/yo.json deleted file mode 100644 index d979fc1310..0000000000 --- a/resources/lib/oojs-ui/i18n/yo.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Demmy" - ] - }, - "ooui-outline-control-move-down": "Sún onítòún sí sàlẹ̀", - "ooui-outline-control-move-up": "Sún onítòún s'ókè", - "ooui-toolbar-more": "Míràn" -} diff --git a/resources/lib/oojs-ui/i18n/yue.json b/resources/lib/oojs-ui/i18n/yue.json deleted file mode 100644 index 6a9e902b12..0000000000 --- a/resources/lib/oojs-ui/i18n/yue.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Deryck Chan", - "William915", - "Shinjiman", - "Ktchankt", - "Hello903hello" - ] - }, - "ooui-outline-control-move-down": "向下搬", - "ooui-outline-control-move-up": "向上搬", - "ooui-outline-control-remove": "拎走", - "ooui-toolbar-more": "更多", - "ooui-toolgroup-expand": "更多", - "ooui-toolgroup-collapse": "少啲", - "ooui-dialog-message-accept": "好", - "ooui-dialog-message-reject": "取消", - "ooui-dialog-process-error": "唔對路", - "ooui-dialog-process-dismiss": "閂咗佢", - "ooui-dialog-process-retry": "再試過", - "ooui-dialog-process-continue": "繼續", - "ooui-selectfile-button-select": "揀檔案", - "ooui-selectfile-not-supported": "未有文件選擇功能", - "ooui-selectfile-placeholder": "無揀到文件", - "ooui-selectfile-dragdrop-placeholder": "放檔案響度" -} diff --git a/resources/lib/oojs-ui/i18n/zh-hans.json b/resources/lib/oojs-ui/i18n/zh-hans.json deleted file mode 100644 index ca3c3be6ca..0000000000 --- a/resources/lib/oojs-ui/i18n/zh-hans.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Anakmalaysia", - "Bencmq", - "Demmy", - "Hydra", - "Hzy980512", - "Liangent", - "Liuxinyu970226", - "Qiyue2001", - "Shirayuki", - "Shizhao", - "TianyinLee", - "Xiaomingyan", - "Yfdyh000", - "Zhangjintao", - "乌拉跨氪", - "Great Brightstar", - "Nbdd0121", - "Yejianfei" - ] - }, - "ooui-outline-control-move-down": "向下移动一项", - "ooui-outline-control-move-up": "向上移动一项", - "ooui-outline-control-remove": "移除项目", - "ooui-toolbar-more": "更多", - "ooui-toolgroup-expand": "更多", - "ooui-toolgroup-collapse": "更少", - "ooui-item-remove": "移除", - "ooui-dialog-message-accept": "确定", - "ooui-dialog-message-reject": "取消", - "ooui-dialog-process-error": "发生了一些错误", - "ooui-dialog-process-dismiss": "关闭", - "ooui-dialog-process-retry": "重试", - "ooui-dialog-process-continue": "继续", - "ooui-selectfile-button-select": "选择一个文件", - "ooui-selectfile-not-supported": "不支持文件选择器", - "ooui-selectfile-placeholder": "没有选定文件", - "ooui-selectfile-dragdrop-placeholder": "将文件拖动至此", - "ooui-field-help": "帮助" -} diff --git a/resources/lib/oojs-ui/i18n/zh-hant.json b/resources/lib/oojs-ui/i18n/zh-hant.json deleted file mode 100644 index 1e05886393..0000000000 --- a/resources/lib/oojs-ui/i18n/zh-hant.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Anakmalaysia", - "Ch.Andrew", - "Hydra", - "Justincheng12345", - "Liflon", - "Liuxinyu970226", - "Qiyue2001", - "Radish10cm", - "Shirayuki", - "Simon Shek", - "Spring Roll Conan", - "Waihorace", - "Cwlin0416", - "LNDDYL", - "Shangkuanlc", - "A2093064", - "Kly" - ] - }, - "ooui-outline-control-move-down": "項目下移", - "ooui-outline-control-move-up": "項目上移", - "ooui-outline-control-remove": "移除項目", - "ooui-toolbar-more": "更多", - "ooui-toolgroup-expand": "更多", - "ooui-toolgroup-collapse": "更少", - "ooui-item-remove": "移除", - "ooui-dialog-message-accept": "確定", - "ooui-dialog-message-reject": "取消", - "ooui-dialog-process-error": "發生不明錯誤", - "ooui-dialog-process-dismiss": "關閉", - "ooui-dialog-process-retry": "再試一次", - "ooui-dialog-process-continue": "繼續", - "ooui-selectfile-button-select": "選擇一個檔案", - "ooui-selectfile-not-supported": "無法支援所選擇的檔案", - "ooui-selectfile-placeholder": "尚未選擇檔案", - "ooui-selectfile-dragdrop-placeholder": "拖曳檔案到此處", - "ooui-field-help": "說明" -} diff --git a/resources/lib/oojs-ui/images/grab.cur b/resources/lib/oojs-ui/images/grab.cur deleted file mode 100644 index fba3ddc807..0000000000 Binary files a/resources/lib/oojs-ui/images/grab.cur and /dev/null differ diff --git a/resources/lib/oojs-ui/images/grabbing.cur b/resources/lib/oojs-ui/images/grabbing.cur deleted file mode 100644 index 41aaa62a59..0000000000 Binary files a/resources/lib/oojs-ui/images/grabbing.cur and /dev/null differ diff --git a/resources/lib/oojs-ui/oojs-ui-apex.js b/resources/lib/oojs-ui/oojs-ui-apex.js deleted file mode 100644 index 5953523158..0000000000 --- a/resources/lib/oojs-ui/oojs-ui-apex.js +++ /dev/null @@ -1,45 +0,0 @@ -/*! - * OOUI v0.28.0 - * https://www.mediawiki.org/wiki/OOUI - * - * Copyright 2011–2018 OOUI Team and other contributors. - * Released under the MIT license - * http://oojs.mit-license.org - * - * Date: 2018-08-14T23:16:18Z - */ -( function ( OO ) { - -'use strict'; - -/** - * @class - * @extends OO.ui.Theme - * - * @constructor - */ -OO.ui.ApexTheme = function OoUiApexTheme() { - // Parent constructor - OO.ui.ApexTheme.parent.call( this ); -}; - -/* Setup */ - -OO.inheritClass( OO.ui.ApexTheme, OO.ui.Theme ); - -/* Methods */ - -/** - * @inheritdoc - */ -OO.ui.ApexTheme.prototype.getDialogTransitionDuration = function () { - return 250; -}; - -/* Instantiation */ - -OO.ui.theme = new OO.ui.ApexTheme(); - -}( OO ) ); - -//# sourceMappingURL=oojs-ui-apex.js.map.json \ No newline at end of file diff --git a/resources/lib/oojs-ui/oojs-ui-apex.js.map.json b/resources/lib/oojs-ui/oojs-ui-apex.js.map.json deleted file mode 100644 index f50934341c..0000000000 --- a/resources/lib/oojs-ui/oojs-ui-apex.js.map.json +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"sources":["../src/intro.js.txt","../src/themes/apex/ApexTheme.js","../src/outro.js.txt"],"names":[],"mappings":";;;;;;;;;;AAAA,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;AACnB;AACA,CAAC,GAAG,CAAC,MAAM,EAAE;;ACFb,GAAG;AACH,CAAC,CAAC,CAAC,CAAC,KAAK;AACT,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,CAAC,KAAK;AACvB,CAAC,CAAC;AACF,CAAC,CAAC,CAAC,CAAC,WAAW;AACf,CAAC,EAAE;AACH,EAAE,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,aAAa,EAAE,CAAC,CAAC;AAC5C,CAAC,EAAE,CAAC,MAAM,CAAC,WAAW;AACtB,CAAC,EAAE,CAAC,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE;AACrC,EAAE;AACF;AACA,EAAE,CAAC,KAAK,CAAC,EAAE;AACX;AACA,EAAE,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE;AAChD;AACA,EAAE,CAAC,OAAO,CAAC,EAAE;AACb;AACA,GAAG;AACH,CAAC,CAAC,CAAC,CAAC,UAAU;AACd,CAAC,EAAE;AACH,EAAE,CAAC,EAAE,CAAC,SAAS,CAAC,SAAS,CAAC,2BAA2B,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;AACrE,CAAC,MAAM,CAAC,GAAG,CAAC;AACZ,EAAE;AACF;AACA,EAAE,CAAC,aAAa,CAAC,EAAE;AACnB;AACA,EAAE,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,SAAS,GAAG;;AC1BpC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE","file":"oojs-ui-apex.js","sourcesContent":["( function ( OO ) {\n\n'use strict';\n","/**\n * @class\n * @extends OO.ui.Theme\n *\n * @constructor\n */\nOO.ui.ApexTheme = function OoUiApexTheme() {\n\t// Parent constructor\n\tOO.ui.ApexTheme.parent.call( this );\n};\n\n/* Setup */\n\nOO.inheritClass( OO.ui.ApexTheme, OO.ui.Theme );\n\n/* Methods */\n\n/**\n * @inheritdoc\n */\nOO.ui.ApexTheme.prototype.getDialogTransitionDuration = function () {\n\treturn 250;\n};\n\n/* Instantiation */\n\nOO.ui.theme = new OO.ui.ApexTheme();\n","}( OO ) );\n"]} \ No newline at end of file diff --git a/resources/lib/oojs-ui/oojs-ui-core-apex.css b/resources/lib/oojs-ui/oojs-ui-core-apex.css deleted file mode 100644 index 5a0e33fee9..0000000000 --- a/resources/lib/oojs-ui/oojs-ui-core-apex.css +++ /dev/null @@ -1,1588 +0,0 @@ -/*! - * OOUI v0.28.0 - * https://www.mediawiki.org/wiki/OOUI - * - * Copyright 2011–2018 OOUI Team and other contributors. - * Released under the MIT license - * http://oojs.mit-license.org - * - * Date: 2018-08-14T23:16:22Z - */ -.oo-ui-element-hidden { - display: none !important; -} -.oo-ui-buttonElement { - display: inline-block; - line-height: normal; - vertical-align: middle; -} -.oo-ui-buttonElement > .oo-ui-buttonElement-button { - cursor: pointer; - display: inline-block; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - vertical-align: middle; - font-family: inherit; - font-size: inherit; - white-space: nowrap; - -webkit-touch-callout: none; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} -.oo-ui-buttonElement > .oo-ui-buttonElement-button::-moz-focus-inner { - border-color: transparent; - padding: 0; -} -.oo-ui-buttonElement.oo-ui-widget-disabled > .oo-ui-buttonElement-button { - cursor: default; -} -.oo-ui-buttonElement-frameless { - position: relative; -} -.oo-ui-buttonElement-framed > .oo-ui-buttonElement-button { - vertical-align: top; - text-align: center; -} -.oo-ui-buttonElement > .oo-ui-buttonElement-button { - color: #333; - position: relative; - border-radius: 3px; -} -.oo-ui-buttonElement > .oo-ui-buttonElement-button:focus { - outline: 0; -} -.oo-ui-buttonElement > input.oo-ui-buttonElement-button, -.oo-ui-buttonElement.oo-ui-labelElement .oo-ui-labelElement-label { - line-height: 1.875em; -} -.oo-ui-buttonElement.oo-ui-indicatorElement > .oo-ui-buttonElement-button > .oo-ui-indicatorElement-indicator, -.oo-ui-buttonElement.oo-ui-labelElement > .oo-ui-buttonElement-button > .oo-ui-labelElement-label, -.oo-ui-buttonElement-frameless.oo-ui-iconElement > .oo-ui-buttonElement-button > .oo-ui-iconElement-icon { - display: inline-block; - vertical-align: middle; -} -.oo-ui-buttonElement.oo-ui-iconElement .oo-ui-indicatorElement-indicator { - margin-left: 0.46875em; -} -.oo-ui-buttonElement.oo-ui-indicatorElement .oo-ui-indicatorElement-indicator { - margin: 0.46875em; -} -.oo-ui-buttonElement-frameless > .oo-ui-buttonElement-button > .oo-ui-iconElement-icon { - -webkit-transition: opacity 250ms; - -moz-transition: opacity 250ms; - transition: opacity 250ms; - -webkit-transform: translateZ(0); - transform: translateZ(0); -} -.oo-ui-buttonElement-frameless > .oo-ui-buttonElement-button > .oo-ui-labelElement-label { - color: #333; -} -.oo-ui-buttonElement-frameless > .oo-ui-buttonElement-button:hover > .oo-ui-iconElement-icon, -.oo-ui-buttonElement-frameless > .oo-ui-buttonElement-button:focus > .oo-ui-iconElement-icon { - opacity: 1; -} -.oo-ui-buttonElement-frameless > .oo-ui-buttonElement-button:hover > .oo-ui-labelElement-label, -.oo-ui-buttonElement-frameless > .oo-ui-buttonElement-button:focus > .oo-ui-labelElement-label { - color: #000; -} -.oo-ui-buttonElement-frameless.oo-ui-labelElement:first-child, -.oo-ui-buttonElement-frameless.oo-ui-iconElement:first-child { - margin-left: -0.3125em; -} -.oo-ui-buttonElement-frameless.oo-ui-labelElement > .oo-ui-buttonElement-button, -.oo-ui-buttonElement-frameless.oo-ui-iconElement > .oo-ui-buttonElement-button { - padding: 0.3125em 0.3125em; -} -.oo-ui-buttonElement-frameless.oo-ui-labelElement.oo-ui-indicatorElement > .oo-ui-buttonElement-button, -.oo-ui-buttonElement-frameless.oo-ui-iconElement.oo-ui-indicatorElement > .oo-ui-buttonElement-button { - padding: 0.3125em 0.3125em; -} -.oo-ui-buttonElement-frameless.oo-ui-labelElement > .oo-ui-buttonElement-button > .oo-ui-labelElement-label { - color: #333; - margin-left: 0.25em; -} -.oo-ui-buttonElement-frameless.oo-ui-indicatorElement > .oo-ui-buttonElement-button { - padding: 0; -} -.oo-ui-buttonElement-frameless > input.oo-ui-buttonElement-button { - padding-left: 0.25em; - color: #333; -} -.oo-ui-buttonElement-frameless > input.oo-ui-buttonElement-button:hover, -.oo-ui-buttonElement-frameless > input.oo-ui-buttonElement-button:focus { - color: #000; -} -.oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-iconElement > .oo-ui-buttonElement-button:focus, -.oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-labelElement > .oo-ui-buttonElement-button:focus { - border-color: #ace; - box-shadow: 0 0 2px 2px #ace; -} -.oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-iconElement > .oo-ui-buttonElement-button:focus:active, -.oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-labelElement > .oo-ui-buttonElement-button:focus:active { - border-color: #fff; - border-color: transparent; - box-shadow: none; -} -.oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-indicatorElement:not( .oo-ui-iconElement ):not( .oo-ui-labelElement ) > .oo-ui-buttonElement-button { - border-radius: 3px; -} -.oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-indicatorElement:not( .oo-ui-iconElement ):not( .oo-ui-labelElement ) > .oo-ui-buttonElement-button:focus { - box-shadow: 0 0 2px 2px #ace; -} -.oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-indicatorElement:not( .oo-ui-iconElement ):not( .oo-ui-labelElement ) > .oo-ui-buttonElement-button:focus:active { - box-shadow: none; -} -.oo-ui-buttonElement-frameless.oo-ui-flaggedElement-progressive > .oo-ui-buttonElement-button > .oo-ui-labelElement-label { - color: #087ecc; -} -.oo-ui-buttonElement-frameless.oo-ui-flaggedElement-destructive > .oo-ui-buttonElement-button > .oo-ui-labelElement-label { - color: #d45353; -} -.oo-ui-buttonElement-frameless.oo-ui-widget-disabled > .oo-ui-buttonElement-button > .oo-ui-iconElement-icon { - opacity: 0.2; -} -.oo-ui-buttonElement-frameless.oo-ui-widget-disabled > .oo-ui-buttonElement-button > .oo-ui-labelElement-label { - color: #ccc; -} -.oo-ui-buttonElement-framed > .oo-ui-buttonElement-button { - background-color: #ebebeb; - background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0, #fafafa), color-stop(100%, #ddd)); - background-image: -webkit-linear-gradient(top, #fafafa 0, #ddd 100%); - background-image: -moz-linear-gradient(top, #fafafa 0, #ddd 100%); - background-image: linear-gradient(to bottom, #fafafa 0, #ddd 100%); - border: 1px solid #ccc; - padding: 0.234375em 0.78125em; - text-shadow: 0 1px 1px rgba(255, 255, 255, 0.5); - -webkit-transition: border-color 100ms; - -moz-transition: border-color 100ms; - transition: border-color 100ms; -} -.oo-ui-buttonElement-framed > .oo-ui-buttonElement-button:hover { - border-color: #aaa; -} -.oo-ui-buttonElement-framed > .oo-ui-buttonElement-button:focus { - border-color: #ace; - box-shadow: 0 0 2px 2px #ace; -} -.oo-ui-buttonElement-framed > input.oo-ui-buttonElement-button, -.oo-ui-buttonElement-framed.oo-ui-labelElement > .oo-ui-buttonElement-button > .oo-ui-labelElement-label { - line-height: 1.875em; -} -.oo-ui-buttonElement-framed.oo-ui-widget-enabled > .oo-ui-buttonElement-button:active, -.oo-ui-buttonElement-framed.oo-ui-buttonElement-active > .oo-ui-buttonElement-button, -.oo-ui-buttonElement-framed.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button { - background-color: #ebebeb; - background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0, #ddd), color-stop(100%, #fafafa)); - background-image: -webkit-linear-gradient(top, #ddd 0, #fafafa 100%); - background-image: -moz-linear-gradient(top, #ddd 0, #fafafa 100%); - background-image: linear-gradient(to bottom, #ddd 0, #fafafa 100%); - color: #000; - border-color: #ccc; - box-shadow: inset 0 1px 4px 0 rgba(0, 0, 0, 0.07); -} -.oo-ui-buttonElement-framed.oo-ui-widget-enabled > .oo-ui-buttonElement-button:active:focus, -.oo-ui-buttonElement-framed.oo-ui-buttonElement-active > .oo-ui-buttonElement-button:focus, -.oo-ui-buttonElement-framed.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button:focus { - border-color: #ace; - box-shadow: inset 0 1px 4px 0 rgba(0, 0, 0, 0.07), 0 0 2px 2px #ace; -} -.oo-ui-buttonElement-framed.oo-ui-iconElement > .oo-ui-buttonElement-button > .oo-ui-iconElement-icon { - margin-left: -0.34375em; - margin-right: -0.34375em; - display: inline-block; - vertical-align: middle; -} -.oo-ui-buttonElement-framed.oo-ui-iconElement.oo-ui-labelElement > .oo-ui-buttonElement-button > .oo-ui-iconElement-icon { - margin-right: 0.3em; -} -.oo-ui-buttonElement-framed.oo-ui-indicatorElement > .oo-ui-buttonElement-button > .oo-ui-indicatorElement-indicator { - margin-left: -0.005em; - margin-right: -0.005em; -} -.oo-ui-buttonElement-framed.oo-ui-indicatorElement.oo-ui-labelElement > .oo-ui-buttonElement-button > .oo-ui-indicatorElement-indicator, -.oo-ui-buttonElement-framed.oo-ui-indicatorElement.oo-ui-iconElement:not( .oo-ui-labelElement ) > .oo-ui-buttonElement-button > .oo-ui-indicatorElement-indicator { - margin-left: 0.46875em; - margin-right: -0.275em; -} -.oo-ui-buttonElement-framed.oo-ui-flaggedElement-progressive > .oo-ui-buttonElement-button { - background-color: #cde7f4; - background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0, #eaf4fa), color-stop(100%, #b0d9ee)); - background-image: -webkit-linear-gradient(top, #eaf4fa 0, #b0d9ee 100%); - background-image: -moz-linear-gradient(top, #eaf4fa 0, #b0d9ee 100%); - background-image: linear-gradient(to bottom, #eaf4fa 0, #b0d9ee 100%); - border: 1px solid #a6cee1; -} -.oo-ui-buttonElement-framed.oo-ui-flaggedElement-progressive > .oo-ui-buttonElement-button:hover { - border-color: #9dc2d4; -} -.oo-ui-buttonElement-framed.oo-ui-flaggedElement-progressive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:active, -.oo-ui-buttonElement-framed.oo-ui-flaggedElement-progressive.oo-ui-buttonElement-active > .oo-ui-buttonElement-button, -.oo-ui-buttonElement-framed.oo-ui-flaggedElement-progressive.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button { - background-color: #cde7f4; - background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0, #b0d9ee), color-stop(100%, #eaf4fa)); - background-image: -webkit-linear-gradient(top, #b0d9ee 0, #eaf4fa 100%); - background-image: -moz-linear-gradient(top, #b0d9ee 0, #eaf4fa 100%); - background-image: linear-gradient(to bottom, #b0d9ee 0, #eaf4fa 100%); - border: 1px solid #a6cee1; -} -.oo-ui-buttonElement-framed.oo-ui-flaggedElement-destructive > .oo-ui-buttonElement-button { - color: #d45353; -} -.oo-ui-buttonElement-framed.oo-ui-widget-disabled > .oo-ui-buttonElement-button, -.oo-ui-buttonElement-framed.oo-ui-widget-disabled.oo-ui-buttonElement-active > .oo-ui-buttonElement-button, -.oo-ui-buttonElement-framed.oo-ui-widget-disabled.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button { - opacity: 0.5; - -webkit-transform: translateZ(0); - transform: translateZ(0); - box-shadow: none; - color: #333; - background: #eee; - border-color: #ccc; -} -.oo-ui-buttonElement-framed.oo-ui-widget-disabled > .oo-ui-buttonElement-button:hover, -.oo-ui-buttonElement-framed.oo-ui-widget-disabled.oo-ui-buttonElement-active > .oo-ui-buttonElement-button:hover, -.oo-ui-buttonElement-framed.oo-ui-widget-disabled.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button:hover, -.oo-ui-buttonElement-framed.oo-ui-widget-disabled > .oo-ui-buttonElement-button:focus, -.oo-ui-buttonElement-framed.oo-ui-widget-disabled.oo-ui-buttonElement-active > .oo-ui-buttonElement-button:focus, -.oo-ui-buttonElement-framed.oo-ui-widget-disabled.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button:focus { - border-color: #ccc; - box-shadow: none; -} -.oo-ui-clippableElement-clippable { - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - min-height: 3.125em; -} -.oo-ui-floatableElement { - position: absolute; -} -.oo-ui-iconElement-icon { - background-size: contain; - background-position: center center; - background-repeat: no-repeat; - position: absolute; - top: 0; - min-width: 20px; - width: 1.875em; - min-height: 20px; - height: 100%; -} -.oo-ui-iconElement-noIcon { - display: none; -} -.oo-ui-iconElement-icon { - position: static; - top: auto; - width: 1.5625em; - height: 1.5625em; - min-width: auto; - min-height: auto; - margin: 0.15625em; - opacity: 0.8; -} -.oo-ui-indicatorElement-indicator { - background-size: contain; - background-position: center center; - background-repeat: no-repeat; - position: absolute; - top: 0; - min-width: 12px; - width: 0.9375em; - min-height: 12px; - height: 100%; -} -.oo-ui-indicatorElement-noIndicator { - display: none; -} -.oo-ui-indicatorElement-indicator { - position: static; - top: auto; - height: 0.9375em; - opacity: 0.8; -} -.oo-ui-labelElement .oo-ui-labelElement-label, -.oo-ui-labelElement.oo-ui-labelElement-label { - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; -} -.oo-ui-labelElement .oo-ui-labelElement-label-highlight { - font-weight: bold; -} -.oo-ui-pendingElement-pending { - background-image: /* @embed */ url(themes/wikimediaui/images/textures/pending.gif); -} -.oo-ui-fieldLayout { - display: block; - margin-top: 0.75em; -} -.oo-ui-fieldLayout:before, -.oo-ui-fieldLayout:after { - content: ' '; - display: table; -} -.oo-ui-fieldLayout:after { - clear: both; -} -.oo-ui-fieldLayout.oo-ui-fieldLayout-align-left > .oo-ui-fieldLayout-body > .oo-ui-fieldLayout-header, -.oo-ui-fieldLayout.oo-ui-fieldLayout-align-right > .oo-ui-fieldLayout-body > .oo-ui-fieldLayout-header, -.oo-ui-fieldLayout.oo-ui-fieldLayout-align-left > .oo-ui-fieldLayout-body > .oo-ui-fieldLayout-help, -.oo-ui-fieldLayout.oo-ui-fieldLayout-align-right > .oo-ui-fieldLayout-body > .oo-ui-fieldLayout-help, -.oo-ui-fieldLayout.oo-ui-fieldLayout-align-left > .oo-ui-fieldLayout-body > .oo-ui-fieldLayout-field, -.oo-ui-fieldLayout.oo-ui-fieldLayout-align-right > .oo-ui-fieldLayout-body > .oo-ui-fieldLayout-field { - display: block; - float: left; -} -.oo-ui-fieldLayout.oo-ui-fieldLayout-align-right > .oo-ui-fieldLayout-body > .oo-ui-fieldLayout-header { - text-align: right; -} -.oo-ui-fieldLayout.oo-ui-fieldLayout-align-inline > .oo-ui-fieldLayout-body { - display: table; - width: 100%; -} -.oo-ui-fieldLayout.oo-ui-fieldLayout-align-inline > .oo-ui-fieldLayout-body > .oo-ui-fieldLayout-header, -.oo-ui-fieldLayout.oo-ui-fieldLayout-align-inline > .oo-ui-fieldLayout-body > .oo-ui-fieldLayout-field { - display: table-cell; -} -.oo-ui-fieldLayout.oo-ui-fieldLayout-align-inline > .oo-ui-fieldLayout-body > .oo-ui-fieldLayout-header { - vertical-align: middle; -} -.oo-ui-fieldLayout.oo-ui-fieldLayout-align-inline > .oo-ui-fieldLayout-body > .oo-ui-fieldLayout-field { - width: 1px; - vertical-align: top; -} -.oo-ui-fieldLayout.oo-ui-fieldLayout-align-top > .oo-ui-fieldLayout-body > .oo-ui-fieldLayout-header, -.oo-ui-fieldLayout.oo-ui-fieldLayout-align-top > .oo-ui-fieldLayout-body > .oo-ui-fieldLayout-field { - display: block; -} -.oo-ui-fieldLayout .oo-ui-fieldLayout-help { - float: right; -} -.oo-ui-fieldLayout .oo-ui-fieldLayout-help > .oo-ui-buttonElement-button > .oo-ui-labelElement-label { - display: block; - position: absolute !important; - /* stylelint-disable-line declaration-no-important */ - clip: rect(1px, 1px, 1px, 1px); - width: 1px; - height: 1px; - margin: -1px; - border: 0; - padding: 0; - overflow: hidden; -} -.oo-ui-fieldLayout .oo-ui-fieldLayout-help > .oo-ui-popupWidget > .oo-ui-popupWidget-popup { - z-index: 1; -} -.oo-ui-fieldLayout.oo-ui-fieldLayout-align-left > .oo-ui-fieldLayout-body > .oo-ui-fieldLayout-help, -.oo-ui-fieldLayout.oo-ui-fieldLayout-align-right > .oo-ui-fieldLayout-body > .oo-ui-fieldLayout-help { - margin-right: 0; - margin-left: -2.5em; -} -.oo-ui-fieldLayout.oo-ui-fieldLayout-align-left > .oo-ui-fieldLayout-body > .oo-ui-fieldLayout-field, -.oo-ui-fieldLayout.oo-ui-fieldLayout-align-right > .oo-ui-fieldLayout-body > .oo-ui-fieldLayout-field { - width: 60%; -} -.oo-ui-fieldLayout.oo-ui-fieldLayout-align-left.oo-ui-labelElement > .oo-ui-fieldLayout-body > .oo-ui-fieldLayout-header, -.oo-ui-fieldLayout.oo-ui-fieldLayout-align-right.oo-ui-labelElement > .oo-ui-fieldLayout-body > .oo-ui-fieldLayout-header { - margin-right: 5%; - width: 35%; -} -.oo-ui-fieldLayout.oo-ui-fieldLayout-align-left.oo-ui-labelElement > .oo-ui-fieldLayout-body > .oo-ui-fieldLayout-header > .oo-ui-labelElement-label, -.oo-ui-fieldLayout.oo-ui-fieldLayout-align-right.oo-ui-labelElement > .oo-ui-fieldLayout-body > .oo-ui-fieldLayout-header > .oo-ui-labelElement-label { - display: block; - padding-top: 0.5em; -} -.oo-ui-fieldLayout.oo-ui-fieldLayout-align-inline { - margin-top: 1em; -} -.oo-ui-fieldLayout.oo-ui-fieldLayout-align-inline > .oo-ui-fieldLayout-body { - max-width: 50em; -} -.oo-ui-fieldLayout.oo-ui-fieldLayout-align-inline.oo-ui-labelElement > .oo-ui-fieldLayout-body > .oo-ui-fieldLayout-header { - padding-left: 0.5em; -} -.oo-ui-fieldLayout:first-child { - margin-top: 0; -} -.oo-ui-fieldLayout.oo-ui-fieldLayout-align-top.oo-ui-labelElement > .oo-ui-fieldLayout-body > .oo-ui-fieldLayout-header { - max-width: 50em; - margin-bottom: 0.25em; -} -.oo-ui-fieldLayout.oo-ui-fieldLayout-align-top > .oo-ui-fieldLayout-body > .oo-ui-inline-help { - margin-top: 0.25em; -} -.oo-ui-fieldLayout.oo-ui-fieldLayout-align-top .oo-ui-fieldLayout-help, -.oo-ui-fieldLayout.oo-ui-fieldLayout-align-inline .oo-ui-fieldLayout-help { - margin-top: -0.3em; - margin-right: -0.625em; -} -.oo-ui-fieldLayout > .oo-ui-popupButtonWidget { - margin-right: 0; - margin-top: 0.25em; -} -.oo-ui-fieldLayout > .oo-ui-popupButtonWidget:last-child { - margin-right: 0; -} -.oo-ui-fieldLayout-disabled > .oo-ui-fieldLayout-body > .oo-ui-fieldLayout-header > .oo-ui-labelElement-label { - color: #ccc; -} -.oo-ui-fieldLayout-messages { - list-style: none none; - margin: 0; - padding: 0; - margin-top: 0.25em; - margin-left: 0.25em; -} -.oo-ui-fieldLayout-messages > li { - margin: 0; - padding: 0; -} -.oo-ui-fieldLayout-messages .oo-ui-iconWidget { - display: none; -} -.oo-ui-fieldLayout-messages .oo-ui-fieldLayout-messages-error { - color: #d45353; -} -.oo-ui-fieldLayout-messages .oo-ui-labelWidget { - padding: 0.1em 0; - line-height: 1.5em; - vertical-align: middle; -} -.oo-ui-actionFieldLayout-input, -.oo-ui-actionFieldLayout-button { - display: table-cell; - vertical-align: middle; -} -.oo-ui-actionFieldLayout-button { - width: 1%; - white-space: nowrap; -} -.oo-ui-actionFieldLayout.oo-ui-fieldLayout-align-top { - max-width: 50em; -} -.oo-ui-actionFieldLayout .oo-ui-actionFieldLayout-input .oo-ui-widget:not( .oo-ui-textInputWidget ) { - margin-right: 1em; -} -.oo-ui-actionFieldLayout .oo-ui-actionFieldLayout-input .oo-ui-widget.oo-ui-textInputWidget > .oo-ui-inputWidget-input { - border-radius: 3px 0 0 3px; - position: relative; -} -.oo-ui-actionFieldLayout .oo-ui-actionFieldLayout-button .oo-ui-buttonElement-framed > .oo-ui-buttonElement-button { - border-radius: 0 3px 3px 0; - margin-left: -1px; -} -.oo-ui-actionFieldLayout .oo-ui-actionFieldLayout-input > .oo-ui-textInputWidget > .oo-ui-inputWidget-input:hover, -.oo-ui-actionFieldLayout .oo-ui-actionFieldLayout-input > .oo-ui-textInputWidget > .oo-ui-inputWidget-input:hover ~ *, -.oo-ui-actionFieldLayout .oo-ui-actionFieldLayout-input > .oo-ui-textInputWidget > .oo-ui-inputWidget-input:focus, -.oo-ui-actionFieldLayout .oo-ui-actionFieldLayout-input > .oo-ui-textInputWidget > .oo-ui-inputWidget-input:focus ~ * { - z-index: 1; -} -.oo-ui-actionFieldLayout .oo-ui-actionFieldLayout-button > .oo-ui-buttonElement > .oo-ui-buttonElement-button:hover, -.oo-ui-actionFieldLayout .oo-ui-actionFieldLayout-button > .oo-ui-buttonElement > .oo-ui-buttonElement-button:focus { - z-index: 1; -} -.oo-ui-fieldsetLayout { - position: relative; - min-width: 0; - margin: 0; - border: 0; - padding: 0.01px 0 0 0; -} -body:not( :-moz-handler-blocked ) .oo-ui-fieldsetLayout { - display: table-cell; -} -.oo-ui-fieldsetLayout > .oo-ui-fieldsetLayout-header { - display: none; -} -.oo-ui-fieldsetLayout.oo-ui-iconElement > .oo-ui-fieldsetLayout-header, -.oo-ui-fieldsetLayout.oo-ui-labelElement > .oo-ui-fieldsetLayout-header { - color: inherit; - display: inline-table; - box-sizing: border-box; - padding: 0; - white-space: normal; - float: left; - width: 100%; -} -.oo-ui-fieldsetLayout-group { - clear: both; -} -.oo-ui-fieldsetLayout .oo-ui-fieldsetLayout-help { - float: right; -} -.oo-ui-fieldsetLayout .oo-ui-fieldsetLayout-help > .oo-ui-buttonElement-button > .oo-ui-labelElement-label { - display: block; - position: absolute !important; - /* stylelint-disable-line declaration-no-important */ - clip: rect(1px, 1px, 1px, 1px); - width: 1px; - height: 1px; - margin: -1px; - border: 0; - padding: 0; - overflow: hidden; -} -.oo-ui-fieldsetLayout .oo-ui-fieldsetLayout-help > .oo-ui-popupWidget > .oo-ui-popupWidget-popup { - z-index: 1; -} -.oo-ui-fieldsetLayout .oo-ui-fieldsetLayout-header { - max-width: 50em; -} -.oo-ui-fieldsetLayout + .oo-ui-fieldsetLayout, -.oo-ui-fieldsetLayout + .oo-ui-formLayout { - margin-top: 2em; -} -.oo-ui-fieldsetLayout.oo-ui-labelElement > .oo-ui-fieldsetLayout-header > .oo-ui-labelElement-label { - display: inline-block; - font-size: 1.15em; - margin-bottom: 0.5em; - padding: 0.25em 0; - font-weight: bold; -} -.oo-ui-fieldsetLayout.oo-ui-iconElement > .oo-ui-fieldsetLayout-header > .oo-ui-labelElement-label { - padding-left: 2em; - line-height: 1.8em; -} -.oo-ui-fieldsetLayout.oo-ui-iconElement > .oo-ui-fieldsetLayout-header > .oo-ui-iconElement-icon { - display: block; - position: absolute; - top: 0.25em; - left: 0; -} -.oo-ui-fieldsetLayout > .oo-ui-fieldsetLayout-header > .oo-ui-popupButtonWidget { - margin-right: 0; -} -.oo-ui-fieldsetLayout > .oo-ui-fieldsetLayout-header > .oo-ui-popupButtonWidget:last-child { - margin-right: 0; -} -.oo-ui-formLayout + .oo-ui-fieldsetLayout, -.oo-ui-formLayout + .oo-ui-formLayout { - margin-top: 2em; -} -.oo-ui-panelLayout { - position: relative; -} -.oo-ui-panelLayout-scrollable { - overflow: auto; -} -.oo-ui-panelLayout-expanded { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; -} -.oo-ui-panelLayout-padded { - padding: 1.25em; -} -.oo-ui-panelLayout-framed { - border: 1px solid #ccc; - border-radius: 3px; -} -.oo-ui-panelLayout-padded.oo-ui-panelLayout-framed { - margin: 1em 0; -} -.oo-ui-horizontalLayout > .oo-ui-widget { - display: inline-block; - vertical-align: middle; -} -.oo-ui-horizontalLayout > .oo-ui-layout { - display: inline-block; -} -.oo-ui-horizontalLayout > .oo-ui-layout, -.oo-ui-horizontalLayout > .oo-ui-widget { - margin-right: 0.5em; -} -.oo-ui-horizontalLayout > .oo-ui-layout:last-child, -.oo-ui-horizontalLayout > .oo-ui-widget:last-child { - margin-right: 0; -} -.oo-ui-horizontalLayout > .oo-ui-layout { - margin-top: 0; -} -.oo-ui-horizontalLayout > .oo-ui-widget { - margin-bottom: 0.5em; -} -.oo-ui-optionWidget { - position: relative; - display: block; - border: 0; -} -.oo-ui-optionWidget.oo-ui-widget-enabled { - cursor: pointer; -} -.oo-ui-optionWidget.oo-ui-widget-disabled { - cursor: default; -} -.oo-ui-optionWidget.oo-ui-labelElement > .oo-ui-labelElement-label { - display: block; - white-space: nowrap; - text-overflow: ellipsis; - overflow: hidden; -} -.oo-ui-optionWidget-highlighted { - background-color: #e1f3ff; -} -.oo-ui-optionWidget .oo-ui-labelElement-label { - line-height: 1.5em; -} -.oo-ui-selectWidget-depressed .oo-ui-optionWidget-selected { - background-color: #a7dcff; -} -.oo-ui-selectWidget-pressed .oo-ui-optionWidget-pressed, -.oo-ui-selectWidget-pressed .oo-ui-optionWidget-pressed.oo-ui-optionWidget-highlighted, -.oo-ui-selectWidget-pressed .oo-ui-optionWidget-pressed.oo-ui-optionWidget-highlighted.oo-ui-optionWidget-selected { - background-color: #a7dcff; -} -.oo-ui-optionWidget.oo-ui-widget-disabled { - color: #ccc; -} -.oo-ui-decoratedOptionWidget { - padding: 0.5em 2em 0.5em 3em; -} -.oo-ui-decoratedOptionWidget .oo-ui-iconElement-icon, -.oo-ui-decoratedOptionWidget .oo-ui-indicatorElement-indicator { - position: absolute; - top: 0; - height: 100%; -} -.oo-ui-decoratedOptionWidget .oo-ui-iconElement-icon { - left: 0.5em; - margin: 0; -} -.oo-ui-decoratedOptionWidget .oo-ui-indicatorElement-indicator { - right: 0.5em; -} -.oo-ui-decoratedOptionWidget.oo-ui-widget-disabled .oo-ui-iconElement-icon, -.oo-ui-decoratedOptionWidget.oo-ui-widget-disabled .oo-ui-indicatorElement-indicator { - opacity: 0.2; -} -.oo-ui-radioOptionWidget { - display: table; - width: 100%; - padding: 0.3125em 0; -} -.oo-ui-radioOptionWidget .oo-ui-radioInputWidget, -.oo-ui-radioOptionWidget.oo-ui-labelElement > .oo-ui-labelElement-label { - display: table-cell; - vertical-align: top; -} -.oo-ui-radioOptionWidget .oo-ui-radioInputWidget { - width: 1px; -} -.oo-ui-radioOptionWidget.oo-ui-labelElement > .oo-ui-labelElement-label { - white-space: normal; -} -.oo-ui-radioOptionWidget.oo-ui-optionWidget-selected { - background-color: transparent; -} -.oo-ui-radioOptionWidget.oo-ui-labelElement .oo-ui-labelElement-label { - padding-left: 0.5em; -} -.oo-ui-radioOptionWidget .oo-ui-radioInputWidget { - margin-right: 0; -} -.oo-ui-labelWidget { - display: inline-block; -} -.oo-ui-labelWidget.oo-ui-inline-help { - display: block; - color: #595959; - font-size: 0.9375em; -} -.oo-ui-iconWidget { - vertical-align: middle; - line-height: 2.5em; - display: inline-block; - position: static; - top: auto; - height: 1.5625em; - margin: 0.15625em; -} -.oo-ui-iconWidget.oo-ui-widget-disabled { - opacity: 0.2; -} -.oo-ui-indicatorWidget { - vertical-align: middle; - line-height: 2.5em; - margin: 0.46875em; - display: inline-block; - position: static; - top: auto; - height: 1.875em; -} -.oo-ui-indicatorWidget.oo-ui-widget-disabled { - opacity: 0.2; -} -.oo-ui-buttonWidget { - margin-right: 0.5em; -} -.oo-ui-buttonWidget:last-child { - margin-right: 0; -} -.oo-ui-buttonGroupWidget { - display: inline-block; - white-space: nowrap; - border-radius: 3px; - margin-right: 0.5em; - z-index: 0; - position: relative; -} -.oo-ui-buttonGroupWidget .oo-ui-buttonWidget.oo-ui-buttonElement-active .oo-ui-buttonElement-button { - cursor: default; -} -.oo-ui-buttonGroupWidget:last-child { - margin-right: 0; -} -.oo-ui-buttonGroupWidget .oo-ui-buttonElement { - margin-right: 0; -} -.oo-ui-buttonGroupWidget .oo-ui-buttonElement:last-child { - margin-right: 0; -} -.oo-ui-buttonGroupWidget .oo-ui-buttonElement-framed .oo-ui-buttonElement-button { - border-radius: 0; - margin-left: -1px; -} -.oo-ui-buttonGroupWidget .oo-ui-buttonElement-framed:first-child .oo-ui-buttonElement-button { - border-bottom-left-radius: 3px; - border-top-left-radius: 3px; - margin-left: 0; -} -.oo-ui-buttonGroupWidget .oo-ui-buttonElement-framed:last-child .oo-ui-buttonElement-button { - border-bottom-right-radius: 3px; - border-top-right-radius: 3px; -} -.oo-ui-buttonGroupWidget.oo-ui-widget-enabled .oo-ui-buttonElement.oo-ui-widget-enabled > .oo-ui-buttonElement-button:active { - z-index: 1; -} -.oo-ui-buttonGroupWidget.oo-ui-widget-enabled .oo-ui-buttonElement.oo-ui-widget-enabled > .oo-ui-buttonElement-button:focus { - z-index: 2; -} -.oo-ui-buttonGroupWidget.oo-ui-widget-enabled .oo-ui-buttonElement.oo-ui-buttonElement-active > .oo-ui-buttonElement-button { - z-index: 3; -} -.oo-ui-buttonGroupWidget.oo-ui-widget-enabled .oo-ui-buttonElement.oo-ui-widget-disabled > .oo-ui-buttonElement-button { - z-index: -1; -} -.oo-ui-buttonGroupWidget.oo-ui-widget-enabled .oo-ui-buttonElement.oo-ui-toggleWidget-on + .oo-ui-toggleWidget-on > .oo-ui-buttonElement-button, -.oo-ui-buttonGroupWidget.oo-ui-widget-enabled .oo-ui-buttonElement.oo-ui-toggleWidget-on + .oo-ui-toggleWidget-on > .oo-ui-buttonElement-button:active { - z-index: 3; -} -.oo-ui-popupWidget { - position: absolute; -} -.oo-ui-popupWidget-popup { - position: relative; - overflow: hidden; - z-index: 1; -} -.oo-ui-popupWidget-anchor { - display: none; - z-index: 1; -} -.oo-ui-popupWidget-anchored .oo-ui-popupWidget-anchor { - display: block; - position: absolute; - background-repeat: no-repeat; -} -.oo-ui-popupWidget-anchored .oo-ui-popupWidget-anchor:before, -.oo-ui-popupWidget-anchored .oo-ui-popupWidget-anchor:after { - content: ''; - position: absolute; - width: 0; - height: 0; - border-style: solid; - border-color: transparent; -} -.oo-ui-popupWidget-anchored-top .oo-ui-popupWidget-anchor { - left: 0; -} -.oo-ui-popupWidget-anchored-top .oo-ui-popupWidget-anchor:before, -.oo-ui-popupWidget-anchored-top .oo-ui-popupWidget-anchor:after { - border-top: 0; -} -.oo-ui-popupWidget-anchored-bottom .oo-ui-popupWidget-anchor { - left: 0; -} -.oo-ui-popupWidget-anchored-bottom .oo-ui-popupWidget-anchor:before, -.oo-ui-popupWidget-anchored-bottom .oo-ui-popupWidget-anchor:after { - border-bottom: 0; -} -.oo-ui-popupWidget-anchored-start .oo-ui-popupWidget-anchor { - top: 0; -} -.oo-ui-popupWidget-anchored-start .oo-ui-popupWidget-anchor:before, -.oo-ui-popupWidget-anchored-start .oo-ui-popupWidget-anchor:after { - border-left: 0; -} -.oo-ui-popupWidget-anchored-end .oo-ui-popupWidget-anchor { - top: 0; -} -.oo-ui-popupWidget-anchored-end .oo-ui-popupWidget-anchor:before, -.oo-ui-popupWidget-anchored-end .oo-ui-popupWidget-anchor:after { - border-right: 0; -} -.oo-ui-popupWidget-head { - -webkit-touch-callout: none; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} -.oo-ui-popupWidget-head > .oo-ui-buttonWidget { - position: absolute; -} -.oo-ui-popupWidget-head > .oo-ui-labelElement-label { - float: left; - cursor: default; -} -.oo-ui-popupWidget-body { - clear: both; -} -.oo-ui-popupWidget-body.oo-ui-clippableElement-clippable { - min-height: 1em; -} -.oo-ui-popupWidget-popup { - background-color: #fff; - border: 1px solid #ccc; - border-radius: 3px; - box-shadow: 0 0.15em 0.5em 0 rgba(0, 0, 0, 0.2); -} -.oo-ui-popupWidget-anchored-top { - margin-top: 6px; -} -.oo-ui-popupWidget-anchored-top .oo-ui-popupWidget-anchor { - top: -6px; -} -.oo-ui-popupWidget-anchored-top .oo-ui-popupWidget-anchor:before { - bottom: -7px; - left: -6px; - border-bottom-color: #aaa; - border-width: 7px; -} -.oo-ui-popupWidget-anchored-top .oo-ui-popupWidget-anchor:after { - bottom: -7px; - left: -5px; - border-bottom-color: #fff; - border-width: 6px; -} -.oo-ui-popupWidget-anchored-bottom { - margin-bottom: 6px; -} -.oo-ui-popupWidget-anchored-bottom .oo-ui-popupWidget-anchor { - bottom: -6px; -} -.oo-ui-popupWidget-anchored-bottom .oo-ui-popupWidget-anchor:before { - top: -7px; - left: -6px; - border-top-color: #aaa; - border-width: 7px; -} -.oo-ui-popupWidget-anchored-bottom .oo-ui-popupWidget-anchor:after { - top: -7px; - left: -5px; - border-top-color: #fff; - border-width: 6px; -} -.oo-ui-popupWidget-anchored-start { - margin-left: 6px; -} -.oo-ui-popupWidget-anchored-start .oo-ui-popupWidget-anchor { - left: -6px; -} -.oo-ui-popupWidget-anchored-start .oo-ui-popupWidget-anchor:before { - right: -7px; - top: -6px; - border-right-color: #aaa; - border-width: 7px; -} -.oo-ui-popupWidget-anchored-start .oo-ui-popupWidget-anchor:after { - right: -7px; - top: -5px; - border-right-color: #fff; - border-width: 6px; -} -.oo-ui-popupWidget-anchored-end { - margin-right: 6px; -} -.oo-ui-popupWidget-anchored-end .oo-ui-popupWidget-anchor { - right: -6px; -} -.oo-ui-popupWidget-anchored-end .oo-ui-popupWidget-anchor:before { - left: -7px; - top: -6px; - border-left-color: #aaa; - border-width: 7px; -} -.oo-ui-popupWidget-anchored-end .oo-ui-popupWidget-anchor:after { - left: -7px; - top: -5px; - border-left-color: #fff; - border-width: 6px; -} -.oo-ui-popupWidget-transitioning .oo-ui-popupWidget-popup { - -webkit-transition: width 100ms, height 100ms, left 100ms; - -moz-transition: width 100ms, height 100ms, left 100ms; - transition: width 100ms, height 100ms, left 100ms; -} -.oo-ui-popupWidget-head { - height: 2.5em; -} -.oo-ui-popupWidget-head > .oo-ui-buttonWidget { - top: 0.25em; - right: 0.25em; -} -.oo-ui-popupWidget-head > .oo-ui-labelElement-label { - margin: 0.75em 2.125em 0.75em 1em; -} -.oo-ui-popupWidget-body { - line-height: 1.4; -} -.oo-ui-popupWidget-body-padded { - margin: 0.75em 1em; -} -.oo-ui-popupButtonWidget { - position: relative; -} -.oo-ui-popupButtonWidget .oo-ui-popupWidget { - cursor: auto; -} -.oo-ui-inputWidget { - margin-right: 0.5em; -} -.oo-ui-inputWidget:last-child { - margin-right: 0; -} -.oo-ui-buttonInputWidget > button, -.oo-ui-buttonInputWidget > input { - background-color: transparent; - margin: 0; - border: 0; - padding: 0; -} -.oo-ui-checkboxInputWidget { - display: inline-block; -} -.oo-ui-checkboxInputWidget-checkIcon { - display: none; -} -.oo-ui-checkboxMultiselectInputWidget .oo-ui-fieldLayout { - margin-top: 0; -} -.oo-ui-checkboxMultiselectInputWidget .oo-ui-fieldLayout .oo-ui-fieldLayout-body { - padding: 0.3125em 0; -} -.oo-ui-dropdownInputWidget { - position: relative; - vertical-align: middle; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - width: 100%; - max-width: 50em; -} -.oo-ui-dropdownInputWidget .oo-ui-dropdownWidget, -.oo-ui-dropdownInputWidget.oo-ui-dropdownInputWidget-php select { - display: block; -} -.oo-ui-dropdownInputWidget select { - display: none; - background-position: -9999em 0; - background-repeat: no-repeat; - width: 100%; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; -} -.oo-ui-dropdownInputWidget.oo-ui-widget-enabled select { - cursor: pointer; -} -.oo-ui-dropdownInputWidget select { - background-color: #fff; - height: 2.5em; - padding: 0.5em; - font-size: inherit; - font-family: inherit; - border: #ccc; - border-radius: 3px; -} -.oo-ui-dropdownInputWidget option { - font-size: inherit; - font-family: inherit; - height: 1.5em; - padding: 0.5em 1em; -} -.oo-ui-dropdownInputWidget.oo-ui-widget-enabled select:hover { - border-color: #aaa; -} -.oo-ui-dropdownInputWidget.oo-ui-widget-enabled select:focus { - border-color: #ace; - outline: 0; - box-shadow: 0 0 2px 2px #ace; -} -.oo-ui-dropdownInputWidget.oo-ui-widget-disabled select { - color: #ccc; - border-color: #ddd; - background-color: #f3f3f3; -} -.oo-ui-radioInputWidget { - display: inline-block; -} -.oo-ui-radioSelectInputWidget .oo-ui-fieldLayout { - margin-top: 0; -} -.oo-ui-radioSelectInputWidget .oo-ui-fieldLayout .oo-ui-fieldLayout-body { - padding: 0.3125em 0; -} -.oo-ui-textInputWidget { - position: relative; - vertical-align: middle; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - width: 100%; - max-width: 50em; -} -.oo-ui-textInputWidget input, -.oo-ui-textInputWidget textarea { - display: block; - width: 100%; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; -} -.oo-ui-textInputWidget textarea { - overflow: auto; -} -.oo-ui-textInputWidget textarea.oo-ui-textInputWidget-autosized { - resize: none; -} -.oo-ui-textInputWidget [type='number'] { - -moz-appearance: textfield; -} -.oo-ui-textInputWidget [type='number']::-webkit-outer-spin-button, -.oo-ui-textInputWidget [type='number']::-webkit-inner-spin-button { - -webkit-appearance: none; - margin: 0; -} -.oo-ui-textInputWidget [type='search'] { - -webkit-appearance: none; -} -.oo-ui-textInputWidget [type='search']::-ms-clear { - display: none; -} -.oo-ui-textInputWidget [type='search']::-webkit-search-decoration, -.oo-ui-textInputWidget [type='search']::-webkit-search-cancel-button { - display: none; -} -.oo-ui-textInputWidget.oo-ui-widget-enabled > .oo-ui-iconElement-icon, -.oo-ui-textInputWidget.oo-ui-widget-enabled > .oo-ui-indicatorElement-indicator { - cursor: text; -} -.oo-ui-textInputWidget.oo-ui-widget-enabled.oo-ui-textInputWidget-type-search > .oo-ui-indicatorElement-indicator { - cursor: pointer; -} -.oo-ui-textInputWidget.oo-ui-widget-disabled > * { - -webkit-touch-callout: none; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} -.oo-ui-textInputWidget.oo-ui-labelElement > .oo-ui-labelElement-label { - display: block; -} -.oo-ui-textInputWidget > .oo-ui-iconElement-icon, -.oo-ui-textInputWidget-labelPosition-before > .oo-ui-labelElement-label { - left: 0; -} -.oo-ui-textInputWidget > .oo-ui-indicatorElement-indicator, -.oo-ui-textInputWidget-labelPosition-after > .oo-ui-labelElement-label { - right: 0; -} -.oo-ui-textInputWidget-labelPosition-after.oo-ui-labelElement ::-ms-clear { - display: none; -} -.oo-ui-textInputWidget > .oo-ui-labelElement-label { - position: absolute; - top: 0; -} -.oo-ui-textInputWidget-php > .oo-ui-iconElement-icon, -.oo-ui-textInputWidget-php > .oo-ui-indicatorElement-indicator, -.oo-ui-textInputWidget-php > .oo-ui-labelElement-label { - pointer-events: none; -} -.oo-ui-textInputWidget input, -.oo-ui-textInputWidget textarea { - background-color: #fff; - color: #000; - border: 1px solid #ccc; - border-radius: 3px; - padding: 0.546875em 0.5em 0.625em 0.5em; - box-shadow: 0 0 0 #fff, inset 0 0.1em 0.2em #ddd; - font-size: inherit; - font-family: inherit; - -webkit-transition: border-color 250ms, box-shadow 250ms; - -moz-transition: border-color 250ms, box-shadow 250ms; - transition: border-color 250ms, box-shadow 250ms; -} -.oo-ui-textInputWidget input.oo-ui-pendingElement-pending, -.oo-ui-textInputWidget textarea.oo-ui-pendingElement-pending { - background-color: transparent; -} -.oo-ui-textInputWidget input::-webkit-input-placeholder, -.oo-ui-textInputWidget textarea::-webkit-input-placeholder { - color: #767676; - opacity: 1; -} -.oo-ui-textInputWidget input:-ms-input-placeholder, -.oo-ui-textInputWidget textarea:-ms-input-placeholder { - color: #767676; - opacity: 1; -} -.oo-ui-textInputWidget input::-moz-placeholder, -.oo-ui-textInputWidget textarea::-moz-placeholder { - color: #767676; - opacity: 1; -} -.oo-ui-textInputWidget input:-moz-placeholder, -.oo-ui-textInputWidget textarea:-moz-placeholder { - color: #767676; - opacity: 1; -} -.oo-ui-textInputWidget input::placeholder, -.oo-ui-textInputWidget textarea::placeholder { - color: #767676; - opacity: 1; -} -.oo-ui-textInputWidget input { - line-height: 1.172em; -} -.oo-ui-textInputWidget textarea { - line-height: 1.275; -} -.oo-ui-textInputWidget.oo-ui-widget-enabled input:focus, -.oo-ui-textInputWidget.oo-ui-widget-enabled textarea:focus { - outline: 0; - border-color: #ace; - box-shadow: 0 0 2px 2px #ace; -} -.oo-ui-textInputWidget.oo-ui-widget-enabled input[readonly], -.oo-ui-textInputWidget.oo-ui-widget-enabled textarea[readonly] { - background-color: #f3f3f3; -} -.oo-ui-textInputWidget.oo-ui-widget-enabled.oo-ui-flaggedElement-invalid input, -.oo-ui-textInputWidget.oo-ui-widget-enabled.oo-ui-flaggedElement-invalid textarea { - background-color: #fdd; -} -.oo-ui-textInputWidget.oo-ui-widget-disabled input, -.oo-ui-textInputWidget.oo-ui-widget-disabled textarea { - color: #ccc; - text-shadow: 0 1px 1px #fff; - border-color: #ddd; - background-color: #f3f3f3; -} -.oo-ui-textInputWidget.oo-ui-widget-disabled > .oo-ui-iconElement-icon, -.oo-ui-textInputWidget.oo-ui-widget-disabled > .oo-ui-indicatorElement-indicator { - opacity: 0.2; -} -.oo-ui-textInputWidget.oo-ui-widget-disabled > .oo-ui-labelElement-label { - color: #ddd; - text-shadow: 0 1px 1px #fff; -} -.oo-ui-textInputWidget.oo-ui-iconElement > .oo-ui-iconElement-icon, -.oo-ui-textInputWidget.oo-ui-indicatorElement > .oo-ui-indicatorElement-indicator { - position: absolute; - top: 0; - height: 100%; - margin: 0 0.15625em; -} -.oo-ui-textInputWidget.oo-ui-iconElement input, -.oo-ui-textInputWidget.oo-ui-iconElement textarea { - padding-left: 2.475em; -} -.oo-ui-textInputWidget.oo-ui-iconElement > .oo-ui-iconElement-icon { - max-height: 2.5em; - margin-left: 0.45625em; -} -.oo-ui-textInputWidget.oo-ui-indicatorElement input, -.oo-ui-textInputWidget.oo-ui-indicatorElement textarea { - padding-right: 2.4875em; -} -.oo-ui-textInputWidget.oo-ui-indicatorElement > .oo-ui-indicatorElement-indicator { - max-height: 2.5em; - margin-right: 0.775em; -} -.oo-ui-textInputWidget > .oo-ui-labelElement-label { - display: none; - top: 1px; - padding: 0.546875em 0.4em 0.625em 0.4em; - line-height: 1.172em; - color: #767676; -} -.oo-ui-textInputWidget-labelPosition-after.oo-ui-indicatorElement > .oo-ui-labelElement-label { - margin-right: 2.0875em; -} -.oo-ui-textInputWidget-labelPosition-before.oo-ui-iconElement > .oo-ui-labelElement-label { - margin-left: 2.075em; -} -.oo-ui-menuSelectWidget { - position: absolute; - width: 100%; - z-index: 4; - background-color: #fff; - margin-top: -1px; - margin-bottom: -1px; - border: 1px solid #ccc; - border-radius: 0 0 3px 3px; - box-shadow: 0 0.15em 1em 0 rgba(0, 0, 0, 0.2); -} -.oo-ui-menuSelectWidget.oo-ui-clippableElement-clippable { - min-height: 2.6em; -} -.oo-ui-menuSelectWidget-invisible { - display: none; -} -.oo-ui-menuOptionWidget .oo-ui-menuOptionWidget-checkIcon { - display: none; -} -.oo-ui-menuOptionWidget.oo-ui-optionWidget > .oo-ui-indicatorElement-indicator { - display: none; -} -.oo-ui-menuOptionWidget.oo-ui-optionWidget-selected { - background-color: transparent; -} -.oo-ui-menuOptionWidget.oo-ui-optionWidget-selected > .oo-ui-iconElement-icon { - display: none; -} -.oo-ui-menuOptionWidget.oo-ui-optionWidget-selected .oo-ui-menuOptionWidget-checkIcon { - display: block; -} -.oo-ui-menuOptionWidget.oo-ui-optionWidget-highlighted, -.oo-ui-menuOptionWidget.oo-ui-optionWidget-highlighted.oo-ui-optionWidget-selected { - background-color: #e1f3ff; -} -.oo-ui-menuSectionOptionWidget { - padding: 0.33em 0.75em; - color: #767676; -} -.oo-ui-menuSectionOptionWidget.oo-ui-widget-enabled { - cursor: default; -} -.oo-ui-dropdownWidget { - display: inline-block; - position: relative; - width: 100%; - max-width: 50em; - background-color: #fff; - margin-right: 0.5em; -} -.oo-ui-dropdownWidget-handle { - position: relative; - width: 100%; - display: block; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - cursor: default; - -webkit-touch-callout: none; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; -} -.oo-ui-dropdownWidget.oo-ui-widget-enabled .oo-ui-dropdownWidget-handle { - cursor: pointer; -} -.oo-ui-dropdownWidget:last-child { - margin-right: 0; -} -.oo-ui-dropdownWidget-handle { - height: 2.5em; - border: 1px solid #ccc; - border-radius: 3px; -} -.oo-ui-dropdownWidget-handle:hover { - border-color: #aaa; -} -.oo-ui-dropdownWidget-handle:focus { - outline: 0; -} -.oo-ui-dropdownWidget-handle .oo-ui-iconElement-icon, -.oo-ui-dropdownWidget-handle .oo-ui-indicatorElement-indicator { - position: absolute; - top: 0; - height: 100%; -} -.oo-ui-dropdownWidget-handle .oo-ui-iconElement-icon { - left: 0.25em; - margin: 0 0.3em; -} -.oo-ui-dropdownWidget-handle .oo-ui-labelElement-label { - line-height: 2.5em; - margin: 0 0.5em; -} -.oo-ui-dropdownWidget-handle .oo-ui-indicatorElement-indicator { - right: 0; - margin: 0 0.775em; -} -.oo-ui-dropdownWidget.oo-ui-widget-enabled .oo-ui-dropdownWidget-handle:focus { - border-color: #ace; - box-shadow: 0 0 2px 2px #ace; -} -.oo-ui-dropdownWidget.oo-ui-widget-disabled .oo-ui-dropdownWidget-handle { - color: #ccc; - text-shadow: 0 1px 1px #fff; - border-color: #ddd; - background-color: #f3f3f3; -} -.oo-ui-dropdownWidget.oo-ui-widget-disabled .oo-ui-dropdownWidget-handle .oo-ui-indicatorElement-indicator { - opacity: 0.2; -} -.oo-ui-dropdownWidget.oo-ui-iconElement .oo-ui-dropdownWidget-handle .oo-ui-labelElement-label { - margin-left: 3em; -} -.oo-ui-dropdownWidget.oo-ui-indicatorElement .oo-ui-dropdownWidget-handle .oo-ui-labelElement-label { - margin-right: 2em; -} -.oo-ui-comboBoxInputWidget { - display: inline-block; - position: relative; - width: 100%; - max-width: 50em; - margin-right: 0.5em; -} -.oo-ui-comboBoxInputWidget-field { - display: table; - width: 100%; - table-layout: fixed; -} -.oo-ui-comboBoxInputWidget .oo-ui-inputWidget-input { - display: table-cell; - vertical-align: middle; - position: relative; - overflow: hidden; -} -.oo-ui-comboBoxInputWidget-dropdownButton { - display: table-cell; -} -.oo-ui-comboBoxInputWidget-dropdownButton > .oo-ui-buttonElement-button { - display: block; - overflow: hidden; -} -.oo-ui-comboBoxInputWidget.oo-ui-comboBoxInputWidget-empty .oo-ui-comboBoxInputWidget-dropdownButton { - display: none; -} -.oo-ui-comboBoxInputWidget-php ::-webkit-calendar-picker-indicator { - opacity: 0; - position: absolute; - right: 0; - top: 0; - width: 2.5em; - height: 2.5em; - padding: 0; -} -.oo-ui-comboBoxInputWidget-php > .oo-ui-indicatorWidget { - display: block; - position: absolute; - top: 0; - height: 100%; - pointer-events: none; -} -.oo-ui-comboBoxInputWidget:last-child { - margin-right: 0; -} -.oo-ui-comboBoxInputWidget-dropdownButton { - position: absolute; - top: 0; - right: 0; - visibility: hidden; -} -.oo-ui-comboBoxInputWidget-dropdownButton .oo-ui-buttonElement-button { - padding: 0; -} -.oo-ui-comboBoxInputWidget-dropdownButton .oo-ui-buttonElement-button .oo-ui-indicatorElement-indicator.oo-ui-indicator-down { - visibility: visible; - margin: 0.775em; -} -.oo-ui-comboBoxInputWidget-php .oo-ui-indicatorWidget { - right: 0; - max-height: 2.5em; - margin: 0; - margin-right: 0.775em; -} -.oo-ui-comboBoxInputWidget.oo-ui-widget-disabled .oo-ui-textInputWidget.oo-ui-indicatorElement .oo-ui-indicatorElement-indicator { - cursor: default; - opacity: 0.2; -} -.oo-ui-multioptionWidget { - position: relative; - display: block; -} -.oo-ui-multioptionWidget.oo-ui-widget-enabled { - cursor: pointer; -} -.oo-ui-multioptionWidget.oo-ui-widget-disabled { - cursor: default; -} -.oo-ui-multioptionWidget.oo-ui-labelElement .oo-ui-labelElement-label { - display: block; - white-space: nowrap; - text-overflow: ellipsis; - overflow: hidden; -} -.oo-ui-multioptionWidget .oo-ui-labelElement-label { - line-height: 1.5em; -} -.oo-ui-multioptionWidget.oo-ui-widget-disabled { - color: #ccc; -} -.oo-ui-checkboxMultioptionWidget { - display: table; - width: 100%; - padding: 0.3125em 0; -} -.oo-ui-checkboxMultioptionWidget .oo-ui-checkboxInputWidget, -.oo-ui-checkboxMultioptionWidget.oo-ui-labelElement > .oo-ui-labelElement-label { - display: table-cell; - vertical-align: top; -} -.oo-ui-checkboxMultioptionWidget .oo-ui-checkboxInputWidget { - width: 1px; -} -.oo-ui-checkboxMultioptionWidget.oo-ui-labelElement > .oo-ui-labelElement-label { - white-space: normal; -} -.oo-ui-checkboxMultioptionWidget.oo-ui-labelElement .oo-ui-labelElement-label { - padding-left: 0.5em; -} -.oo-ui-checkboxMultioptionWidget .oo-ui-checkboxInputWidget { - margin-right: 0; -} -.oo-ui-progressBarWidget { - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - max-width: 50em; - background-color: #fff; - border: 1px solid #ccc; - border-radius: 3px; - overflow: hidden; -} -.oo-ui-progressBarWidget-bar { - background-color: #cde7f4; - background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0, #eaf4fa), color-stop(100%, #b0d9ee)); - background-image: -webkit-linear-gradient(top, #eaf4fa 0, #b0d9ee 100%); - background-image: -moz-linear-gradient(top, #eaf4fa 0, #b0d9ee 100%); - background-image: linear-gradient(to bottom, #eaf4fa 0, #b0d9ee 100%); - height: 1em; - border-right: 1px solid #ccc; - -webkit-transition: width 250ms; - -moz-transition: width 250ms; - transition: width 250ms; -} -.oo-ui-progressBarWidget-indeterminate .oo-ui-progressBarWidget-bar { - width: 40%; - border-left: 1px solid #a6cee1; - -webkit-animation: oo-ui-progressBarWidget-slide 2s infinite linear; - -moz-animation: oo-ui-progressBarWidget-slide 2s infinite linear; - animation: oo-ui-progressBarWidget-slide 2s infinite linear; - -webkit-transform: translate(-25%); - -moz-transform: translate(-25%); - -ms-transform: translate(-25%); - transform: translate(-25%); -} -.oo-ui-progressBarWidget.oo-ui-widget-disabled { - opacity: 0.6; -} -@-webkit-keyframes oo-ui-progressBarWidget-slide { - from { - -webkit-transform: translate(-100%); - -moz-transform: translate(-100%); - -ms-transform: translate(-100%); - transform: translate(-100%); - } - to { - -webkit-transform: translate(350%); - -moz-transform: translate(350%); - -ms-transform: translate(350%); - transform: translate(350%); - } -} -@-moz-keyframes oo-ui-progressBarWidget-slide { - from { - -webkit-transform: translate(-100%); - -moz-transform: translate(-100%); - -ms-transform: translate(-100%); - transform: translate(-100%); - } - to { - -webkit-transform: translate(350%); - -moz-transform: translate(350%); - -ms-transform: translate(350%); - transform: translate(350%); - } -} -@keyframes oo-ui-progressBarWidget-slide { - from { - -webkit-transform: translate(-100%); - -moz-transform: translate(-100%); - -ms-transform: translate(-100%); - transform: translate(-100%); - } - to { - -webkit-transform: translate(350%); - -moz-transform: translate(350%); - -ms-transform: translate(350%); - transform: translate(350%); - } -} -.oo-ui-numberInputWidget { - display: inline-block; - position: relative; - max-width: 50em; -} -.oo-ui-numberInputWidget-buttoned .oo-ui-buttonWidget, -.oo-ui-numberInputWidget-buttoned .oo-ui-inputWidget-input { - display: table-cell; - height: 100%; -} -.oo-ui-numberInputWidget-field { - display: table; - table-layout: fixed; - width: 100%; -} -.oo-ui-numberInputWidget-field > .oo-ui-buttonWidget { - width: 2.25em; -} -.oo-ui-numberInputWidget-buttoned .oo-ui-buttonElement-button .oo-ui-iconElement-icon { - min-width: 20px; - width: 1.5625em; -} -.oo-ui-numberInputWidget-buttoned .oo-ui-inputWidget-input { - border-radius: 0; -} -.oo-ui-numberInputWidget-minusButton > .oo-ui-buttonElement-button { - border-top-right-radius: 0; - border-bottom-right-radius: 0; - border-right-width: 0; -} -.oo-ui-numberInputWidget-plusButton > .oo-ui-buttonElement-button { - border-top-left-radius: 0; - border-bottom-left-radius: 0; - border-left-width: 0; -} -.oo-ui-defaultOverlay { - position: absolute; - top: 0; - /* @noflip */ - left: 0; -} diff --git a/resources/lib/oojs-ui/oojs-ui-core-wikimediaui.css b/resources/lib/oojs-ui/oojs-ui-core-wikimediaui.css deleted file mode 100644 index a8edc68c62..0000000000 --- a/resources/lib/oojs-ui/oojs-ui-core-wikimediaui.css +++ /dev/null @@ -1,2015 +0,0 @@ -/*! - * OOUI v0.28.0 - * https://www.mediawiki.org/wiki/OOUI - * - * Copyright 2011–2018 OOUI Team and other contributors. - * Released under the MIT license - * http://oojs.mit-license.org - * - * Date: 2018-08-14T23:16:22Z - */ -/** - * WikimediaUI Base v0.11.0 - * Wikimedia Foundation user interface base variables - */ -.oo-ui-element-hidden { - display: none !important; -} -.oo-ui-buttonElement { - display: inline-block; - line-height: normal; - vertical-align: middle; -} -.oo-ui-buttonElement > .oo-ui-buttonElement-button { - cursor: pointer; - display: inline-block; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - vertical-align: middle; - font-family: inherit; - font-size: inherit; - white-space: nowrap; - -webkit-touch-callout: none; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} -.oo-ui-buttonElement > .oo-ui-buttonElement-button::-moz-focus-inner { - border-color: transparent; - padding: 0; -} -.oo-ui-buttonElement.oo-ui-widget-disabled > .oo-ui-buttonElement-button { - cursor: default; -} -.oo-ui-buttonElement-frameless { - position: relative; -} -.oo-ui-buttonElement-framed > .oo-ui-buttonElement-button { - vertical-align: top; - text-align: center; -} -.oo-ui-buttonElement > .oo-ui-buttonElement-button { - position: relative; - border-radius: 2px; - padding-top: 2.14285714em; - font-weight: bold; - text-decoration: none; -} -.oo-ui-buttonElement > .oo-ui-buttonElement-button:focus { - outline: 0; -} -.oo-ui-buttonElement > input.oo-ui-buttonElement-button { - -webkit-appearance: none; -} -.oo-ui-buttonElement.oo-ui-labelElement > .oo-ui-buttonElement-button { - line-height: 1; -} -.oo-ui-buttonElement.oo-ui-labelElement > input.oo-ui-buttonElement-button, -.oo-ui-buttonElement.oo-ui-labelElement > .oo-ui-buttonElement-button > .oo-ui-labelElement-label { - line-height: 1.07142857em; -} -.oo-ui-buttonElement.oo-ui-labelElement.oo-ui-indicatorElement > .oo-ui-buttonElement-button { - padding-right: 2.28571429em; -} -.oo-ui-buttonElement.oo-ui-iconElement .oo-ui-iconElement-icon, -.oo-ui-buttonElement.oo-ui-indicatorElement .oo-ui-indicatorElement-indicator { - -webkit-transform: translateZ(0); - transform: translateZ(0); -} -.oo-ui-buttonElement.oo-ui-indicatorElement.oo-ui-labelElement > .oo-ui-buttonElement-button > .oo-ui-indicatorElement-indicator, -.oo-ui-buttonElement.oo-ui-indicatorElement.oo-ui-iconElement > .oo-ui-buttonElement-button > .oo-ui-indicatorElement-indicator { - right: 0.85714286em; -} -.oo-ui-buttonElement.oo-ui-widget-enabled > .oo-ui-buttonElement-button { - -webkit-transition: background-color 100ms, color 100ms, border-color 100ms, box-shadow 100ms; - -moz-transition: background-color 100ms, color 100ms, border-color 100ms, box-shadow 100ms; - transition: background-color 100ms, color 100ms, border-color 100ms, box-shadow 100ms; -} -.oo-ui-buttonElement.oo-ui-widget-enabled > .oo-ui-buttonElement-button > .oo-ui-iconElement-icon, -.oo-ui-buttonElement.oo-ui-widget-enabled > .oo-ui-buttonElement-button > .oo-ui-indicatorElement-indicator { - opacity: 0.87; - -webkit-transition: opacity 100ms; - -moz-transition: opacity 100ms; - transition: opacity 100ms; -} -.oo-ui-buttonElement.oo-ui-widget-enabled > .oo-ui-buttonElement-button > .oo-ui-iconElement-icon.oo-ui-image-invert, -.oo-ui-buttonElement.oo-ui-widget-enabled > .oo-ui-buttonElement-button > .oo-ui-indicatorElement-indicator.oo-ui-image-invert { - opacity: 1; -} -.oo-ui-buttonElement.oo-ui-widget-enabled > .oo-ui-buttonElement-button:hover > .oo-ui-iconElement-icon, -.oo-ui-buttonElement.oo-ui-widget-enabled > .oo-ui-buttonElement-button:hover > .oo-ui-indicatorElement-indicator { - opacity: 0.73; -} -.oo-ui-buttonElement.oo-ui-widget-enabled > .oo-ui-buttonElement-button:hover > .oo-ui-iconElement-icon.oo-ui-image-invert, -.oo-ui-buttonElement.oo-ui-widget-enabled > .oo-ui-buttonElement-button:hover > .oo-ui-indicatorElement-indicator.oo-ui-image-invert { - opacity: 1; -} -.oo-ui-buttonElement.oo-ui-widget-enabled.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button > .oo-ui-iconElement-icon, -.oo-ui-buttonElement.oo-ui-widget-enabled.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button > .oo-ui-indicatorElement-indicator { - opacity: 1; -} -.oo-ui-buttonElement-frameless.oo-ui-iconElement:first-child { - margin-left: -0.42857143em; -} -.oo-ui-buttonElement-frameless.oo-ui-iconElement > .oo-ui-buttonElement-button { - min-width: 1.42857143em; - min-height: 1.42857143em; - border-color: #fff; - border-color: transparent; - border-style: solid; - border-width: 1px; - padding-top: 2.14285714em; - padding-left: 2.14285714em; -} -.oo-ui-buttonElement-frameless.oo-ui-iconElement > .oo-ui-buttonElement-button > .oo-ui-iconElement-icon { - left: 0.35714286em; -} -.oo-ui-buttonElement-frameless.oo-ui-labelElement:first-child { - margin-left: -0.14285714em; -} -.oo-ui-buttonElement-frameless.oo-ui-labelElement.oo-ui-iconElement:first-child { - margin-left: -0.42857143em; -} -.oo-ui-buttonElement-frameless.oo-ui-labelElement > .oo-ui-buttonElement-button { - border-color: #fff; - border-color: transparent; - border-style: solid; - border-width: 1px; - padding: 0.57142857em 0.14285714em 0.5em; -} -.oo-ui-buttonElement-frameless.oo-ui-labelElement.oo-ui-iconElement > .oo-ui-buttonElement-button { - padding-left: 2.14285714em; -} -.oo-ui-buttonElement-frameless.oo-ui-indicatorElement > .oo-ui-buttonElement-button { - min-width: 12px; - min-height: 12px; - padding-top: 0; -} -.oo-ui-buttonElement-frameless.oo-ui-indicatorElement.oo-ui-iconElement > .oo-ui-buttonElement-button { - padding-left: 3.85714286em; - padding-top: 2.14285714em; -} -.oo-ui-buttonElement-frameless.oo-ui-indicatorElement.oo-ui-labelElement > .oo-ui-buttonElement-button { - padding-left: 0.14285714em; - padding-top: 0.57142857em; -} -.oo-ui-buttonElement-frameless.oo-ui-indicatorElement.oo-ui-iconElement.oo-ui-labelElement > .oo-ui-buttonElement-button { - padding-left: 2.14285714em; -} -.oo-ui-buttonElement-frameless.oo-ui-widget-enabled > .oo-ui-buttonElement-button { - color: #222; -} -.oo-ui-buttonElement-frameless.oo-ui-widget-enabled > .oo-ui-buttonElement-button:hover { - color: #444; -} -.oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-iconElement > .oo-ui-buttonElement-button:focus, -.oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-labelElement > .oo-ui-buttonElement-button:focus { - border-color: #36c; - box-shadow: inset 0 0 0 1px #36c; -} -.oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-iconElement > .oo-ui-buttonElement-button:focus:active, -.oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-labelElement > .oo-ui-buttonElement-button:focus:active { - border-color: #fff; - border-color: transparent; - box-shadow: none; -} -.oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-indicatorElement:not( .oo-ui-iconElement ):not( .oo-ui-labelElement ) > .oo-ui-buttonElement-button { - border-radius: 1px; -} -.oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-indicatorElement:not( .oo-ui-iconElement ):not( .oo-ui-labelElement ) > .oo-ui-buttonElement-button:focus { - box-shadow: 0 0 0 2px #36c; -} -.oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-indicatorElement:not( .oo-ui-iconElement ):not( .oo-ui-labelElement ) > .oo-ui-buttonElement-button:focus:active { - box-shadow: none; -} -.oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-buttonElement-pressed > input.oo-ui-buttonElement-button, -.oo-ui-buttonElement-frameless.oo-ui-widget-enabled > .oo-ui-buttonElement-button:active { - color: #000; - border-color: #fff; - border-color: transparent; - box-shadow: none; -} -.oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-flaggedElement-progressive > .oo-ui-buttonElement-button { - color: #36c; -} -.oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-flaggedElement-progressive > .oo-ui-buttonElement-button:hover { - color: #447ff5; -} -.oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-flaggedElement-progressive > .oo-ui-buttonElement-button:active, -.oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-flaggedElement-progressive > .oo-ui-buttonElement-button:active:focus, -.oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-flaggedElement-progressive.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button { - color: #2a4b8d; - box-shadow: none; -} -.oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-flaggedElement-destructive > .oo-ui-buttonElement-button { - color: #d33; -} -.oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-flaggedElement-destructive > .oo-ui-buttonElement-button:hover { - color: #ff4242; -} -.oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-flaggedElement-destructive > .oo-ui-buttonElement-button:active, -.oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-flaggedElement-destructive > .oo-ui-buttonElement-button:active:focus, -.oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-flaggedElement-destructive.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button { - color: #b32424; - box-shadow: none; -} -.oo-ui-buttonElement-frameless.oo-ui-widget-enabled[class*='oo-ui-flaggedElement'] > .oo-ui-buttonElement-button > .oo-ui-iconElement-icon, -.oo-ui-buttonElement-frameless.oo-ui-widget-enabled[class*='oo-ui-flaggedElement'] > .oo-ui-buttonElement-button > .oo-ui-indicatorElement-indicator { - opacity: 1; -} -.oo-ui-buttonElement-frameless.oo-ui-widget-enabled[class*='oo-ui-flaggedElement'] > .oo-ui-buttonElement-button:hover > .oo-ui-iconElement-icon, -.oo-ui-buttonElement-frameless.oo-ui-widget-enabled[class*='oo-ui-flaggedElement'] > .oo-ui-buttonElement-button:hover > .oo-ui-indicatorElement-indicator { - opacity: 0.73; -} -.oo-ui-buttonElement-frameless.oo-ui-widget-disabled > .oo-ui-buttonElement-button { - color: #72777d; -} -.oo-ui-buttonElement-frameless.oo-ui-widget-disabled > .oo-ui-buttonElement-button > .oo-ui-iconElement-icon, -.oo-ui-buttonElement-frameless.oo-ui-widget-disabled > .oo-ui-buttonElement-button > .oo-ui-indicatorElement-indicator { - opacity: 0.51; -} -.oo-ui-buttonElement-framed > .oo-ui-buttonElement-button { - border-style: solid; - border-width: 1px; - border-radius: 2px; - padding-left: 0.85714286em; - padding-right: 0.85714286em; -} -.oo-ui-buttonElement-framed.oo-ui-iconElement > .oo-ui-buttonElement-button { - padding-top: 2.14285714em; - padding-bottom: 0; - padding-left: 2.14285714em; -} -.oo-ui-buttonElement-framed.oo-ui-iconElement > .oo-ui-buttonElement-button > .oo-ui-iconElement-icon { - left: 0.78571429em; -} -.oo-ui-buttonElement-framed.oo-ui-iconElement.oo-ui-labelElement > .oo-ui-buttonElement-button, -.oo-ui-buttonElement-framed.oo-ui-iconElement.oo-ui-indicatorElement > .oo-ui-buttonElement-button { - padding-left: 2.64285714em; -} -.oo-ui-buttonElement-framed.oo-ui-indicatorElement > .oo-ui-buttonElement-button { - padding-top: 2.14285714em; - padding-right: 2.14285714em; - padding-bottom: 0; -} -.oo-ui-buttonElement-framed.oo-ui-indicatorElement > .oo-ui-buttonElement-button > .oo-ui-indicatorElement-indicator { - right: 1.07142857em; -} -.oo-ui-buttonElement-framed.oo-ui-indicatorElement.oo-ui-labelElement > .oo-ui-buttonElement-button { - padding-right: 2.28571429em; -} -.oo-ui-buttonElement-framed.oo-ui-labelElement > .oo-ui-buttonElement-button { - padding-top: 0.57142857em; - padding-bottom: 0.5em; -} -.oo-ui-buttonElement-framed.oo-ui-widget-disabled > .oo-ui-buttonElement-button { - background-color: #c8ccd1; - color: #fff; - border-color: #c8ccd1; -} -.oo-ui-buttonElement-framed.oo-ui-widget-enabled > .oo-ui-buttonElement-button { - background-color: #f8f9fa; - color: #222; - border-color: #a2a9b1; -} -.oo-ui-buttonElement-framed.oo-ui-widget-enabled > .oo-ui-buttonElement-button:hover { - background-color: #fff; - color: #444; - border-color: #a2a9b1; -} -.oo-ui-buttonElement-framed.oo-ui-widget-enabled > .oo-ui-buttonElement-button:focus { - border-color: #36c; - box-shadow: inset 0 0 0 1px #36c; -} -.oo-ui-buttonElement-framed.oo-ui-widget-enabled > .oo-ui-buttonElement-button:active, -.oo-ui-buttonElement-framed.oo-ui-widget-enabled > .oo-ui-buttonElement-button:active:focus, -.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button { - background-color: #c8ccd1; - color: #000; - border-color: #72777d; - box-shadow: none; -} -.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-buttonElement-active > .oo-ui-buttonElement-button { - background-color: #2a4b8d; - color: #fff; - border-color: #2a4b8d; -} -.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-buttonElement-active > .oo-ui-buttonElement-button:focus { - border-color: #36c; - box-shadow: inset 0 0 0 1px #36c, inset 0 0 0 2px #fff; -} -.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-progressive > .oo-ui-buttonElement-button { - color: #36c; -} -.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-progressive > .oo-ui-buttonElement-button:hover { - background-color: #fff; - border-color: #447ff5; -} -.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-progressive > .oo-ui-buttonElement-button:active, -.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-progressive > .oo-ui-buttonElement-button:active:focus, -.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-progressive.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button, -.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-progressive.oo-ui-buttonElement-active > .oo-ui-buttonElement-button, -.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-progressive.oo-ui-popupToolGroup-active > .oo-ui-buttonElement-button { - background-color: #eff3fa; - color: #2a4b8d; - border-color: #2a4b8d; - box-shadow: none; -} -.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-progressive > .oo-ui-buttonElement-button:focus { - border-color: #36c; - box-shadow: inset 0 0 0 1px #36c; -} -.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-destructive > .oo-ui-buttonElement-button { - color: #d73333; -} -.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-destructive > .oo-ui-buttonElement-button:hover { - background-color: #fff; - border-color: #ff4242; -} -.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-destructive > .oo-ui-buttonElement-button:active, -.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-destructive > .oo-ui-buttonElement-button:active:focus, -.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-destructive.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button, -.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-destructive.oo-ui-buttonElement-active > .oo-ui-buttonElement-button, -.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-destructive.oo-ui-popupToolGroup-active > .oo-ui-buttonElement-button { - background-color: #ffffff; - color: #b32424; - border-color: #b32424; - box-shadow: none; -} -.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-destructive > .oo-ui-buttonElement-button:focus { - border-color: #d33; - box-shadow: inset 0 0 0 1px #d33; -} -.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-progressive > .oo-ui-buttonElement-button { - color: #fff; - background-color: #36c; - border-color: #36c; -} -.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-progressive > .oo-ui-buttonElement-button:hover { - background-color: #447ff5; - border-color: #447ff5; -} -.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-progressive > .oo-ui-buttonElement-button:active, -.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-progressive > .oo-ui-buttonElement-button:active:focus, -.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-progressive.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button, -.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-progressive.oo-ui-buttonElement-active > .oo-ui-buttonElement-button, -.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-progressive.oo-ui-popupToolGroup-active > .oo-ui-buttonElement-button { - color: #fff; - background-color: #2a4b8d; - border-color: #2a4b8d; - box-shadow: none; -} -.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-progressive > .oo-ui-buttonElement-button:focus { - border-color: #36c; - box-shadow: inset 0 0 0 1px #36c, inset 0 0 0 2px #fff; -} -.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-destructive > .oo-ui-buttonElement-button { - color: #fff; - background-color: #d33; - border-color: #d33; -} -.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-destructive > .oo-ui-buttonElement-button:hover { - background-color: #ff4242; - border-color: #ff4242; -} -.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-destructive > .oo-ui-buttonElement-button:active, -.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-destructive > .oo-ui-buttonElement-button:active:focus, -.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-destructive.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button, -.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-destructive.oo-ui-buttonElement-active > .oo-ui-buttonElement-button, -.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-destructive.oo-ui-popupToolGroup-active > .oo-ui-buttonElement-button { - color: #fff; - background-color: #b32424; - border-color: #b32424; - box-shadow: none; -} -.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-destructive > .oo-ui-buttonElement-button:focus { - border-color: #d33; - box-shadow: inset 0 0 0 1px #d33, inset 0 0 0 2px #fff; -} -.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary > .oo-ui-buttonElement-button > .oo-ui-iconElement-icon, -.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary > .oo-ui-buttonElement-button > .oo-ui-indicatorElement-indicator { - opacity: 1; -} -.oo-ui-clippableElement-clippable { - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - min-height: 3.125em; -} -.oo-ui-floatableElement { - position: absolute; -} -.oo-ui-iconElement-icon { - background-size: contain; - background-position: center center; - background-repeat: no-repeat; - position: absolute; - top: 0; - min-width: 20px; - width: 1.42857143em; - min-height: 20px; - height: 100%; -} -.oo-ui-iconElement-noIcon { - display: none; -} -.oo-ui-indicatorElement-indicator { - background-size: contain; - background-position: center center; - background-repeat: no-repeat; - position: absolute; - top: 0; - min-width: 12px; - width: 0.85714286em; - min-height: 12px; - height: 100%; -} -.oo-ui-indicatorElement-noIndicator { - display: none; -} -.oo-ui-labelElement .oo-ui-labelElement-label, -.oo-ui-labelElement.oo-ui-labelElement-label { - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; -} -.oo-ui-labelElement .oo-ui-labelElement-label { - line-height: 1.42857143em; -} -.oo-ui-labelElement .oo-ui-labelElement-label-highlight { - font-weight: bold; -} -.oo-ui-pendingElement-pending { - background-image: /* @embed */ url(themes/wikimediaui/images/textures/pending.gif); -} -.oo-ui-fieldLayout { - display: block; - margin-top: 1.14285714em; -} -.oo-ui-fieldLayout:before, -.oo-ui-fieldLayout:after { - content: ' '; - display: table; -} -.oo-ui-fieldLayout:after { - clear: both; -} -.oo-ui-fieldLayout.oo-ui-fieldLayout-align-left > .oo-ui-fieldLayout-body > .oo-ui-fieldLayout-header, -.oo-ui-fieldLayout.oo-ui-fieldLayout-align-right > .oo-ui-fieldLayout-body > .oo-ui-fieldLayout-header, -.oo-ui-fieldLayout.oo-ui-fieldLayout-align-left > .oo-ui-fieldLayout-body > .oo-ui-fieldLayout-help, -.oo-ui-fieldLayout.oo-ui-fieldLayout-align-right > .oo-ui-fieldLayout-body > .oo-ui-fieldLayout-help, -.oo-ui-fieldLayout.oo-ui-fieldLayout-align-left > .oo-ui-fieldLayout-body > .oo-ui-fieldLayout-field, -.oo-ui-fieldLayout.oo-ui-fieldLayout-align-right > .oo-ui-fieldLayout-body > .oo-ui-fieldLayout-field { - display: block; - float: left; -} -.oo-ui-fieldLayout.oo-ui-fieldLayout-align-right > .oo-ui-fieldLayout-body > .oo-ui-fieldLayout-header { - text-align: right; -} -.oo-ui-fieldLayout.oo-ui-fieldLayout-align-inline > .oo-ui-fieldLayout-body { - display: table; - width: 100%; -} -.oo-ui-fieldLayout.oo-ui-fieldLayout-align-inline > .oo-ui-fieldLayout-body > .oo-ui-fieldLayout-header, -.oo-ui-fieldLayout.oo-ui-fieldLayout-align-inline > .oo-ui-fieldLayout-body > .oo-ui-fieldLayout-field { - display: table-cell; -} -.oo-ui-fieldLayout.oo-ui-fieldLayout-align-inline > .oo-ui-fieldLayout-body > .oo-ui-fieldLayout-header { - vertical-align: middle; -} -.oo-ui-fieldLayout.oo-ui-fieldLayout-align-inline > .oo-ui-fieldLayout-body > .oo-ui-fieldLayout-field { - width: 1px; - vertical-align: top; -} -.oo-ui-fieldLayout.oo-ui-fieldLayout-align-top > .oo-ui-fieldLayout-body > .oo-ui-fieldLayout-header, -.oo-ui-fieldLayout.oo-ui-fieldLayout-align-top > .oo-ui-fieldLayout-body > .oo-ui-fieldLayout-field { - display: block; -} -.oo-ui-fieldLayout .oo-ui-fieldLayout-help { - float: right; -} -.oo-ui-fieldLayout .oo-ui-fieldLayout-help > .oo-ui-buttonElement-button > .oo-ui-labelElement-label { - display: block; - position: absolute !important; - /* stylelint-disable-line declaration-no-important */ - clip: rect(1px, 1px, 1px, 1px); - width: 1px; - height: 1px; - margin: -1px; - border: 0; - padding: 0; - overflow: hidden; -} -.oo-ui-fieldLayout .oo-ui-fieldLayout-help > .oo-ui-popupWidget > .oo-ui-popupWidget-popup { - z-index: 1; -} -.oo-ui-fieldLayout.oo-ui-labelElement, -.oo-ui-fieldLayout.oo-ui-fieldLayout-align-inline { - margin-top: 0.85714286em; -} -.oo-ui-fieldLayout:first-child, -.oo-ui-fieldLayout.oo-ui-labelElement:first-child, -.oo-ui-fieldLayout.oo-ui-fieldLayout-align-inline:first-child { - margin-top: 0; -} -.oo-ui-fieldLayout.oo-ui-labelElement > .oo-ui-fieldLayout-body > .oo-ui-fieldLayout-header { - padding-bottom: 0.28571429em; -} -.oo-ui-fieldLayout.oo-ui-labelElement.oo-ui-fieldLayout-align-top > .oo-ui-fieldLayout-body > .oo-ui-fieldLayout-header, -.oo-ui-fieldLayout.oo-ui-labelElement.oo-ui-fieldLayout-align-inline > .oo-ui-fieldLayout-body { - max-width: 50em; -} -.oo-ui-fieldLayout.oo-ui-labelElement.oo-ui-fieldLayout-align-left > .oo-ui-fieldLayout-body > .oo-ui-fieldLayout-header, -.oo-ui-fieldLayout.oo-ui-labelElement.oo-ui-fieldLayout-align-right > .oo-ui-fieldLayout-body > .oo-ui-fieldLayout-header { - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - width: 40%; - padding-right: 2.64285714em; -} -.oo-ui-fieldLayout.oo-ui-labelElement.oo-ui-fieldLayout-align-left > .oo-ui-fieldLayout-body > .oo-ui-fieldLayout-header > .oo-ui-labelElement-label, -.oo-ui-fieldLayout.oo-ui-labelElement.oo-ui-fieldLayout-align-right > .oo-ui-fieldLayout-body > .oo-ui-fieldLayout-header > .oo-ui-labelElement-label { - display: block; - padding-top: 0.28571429em; -} -.oo-ui-fieldLayout.oo-ui-labelElement.oo-ui-fieldLayout-align-left > .oo-ui-fieldLayout-body > .oo-ui-fieldLayout-help, -.oo-ui-fieldLayout.oo-ui-labelElement.oo-ui-fieldLayout-align-right > .oo-ui-fieldLayout-body > .oo-ui-fieldLayout-help { - margin-right: 0; - margin-left: -2.35714286em; -} -.oo-ui-fieldLayout.oo-ui-labelElement.oo-ui-fieldLayout-align-left > .oo-ui-fieldLayout-body > .oo-ui-fieldLayout-field, -.oo-ui-fieldLayout.oo-ui-labelElement.oo-ui-fieldLayout-align-right > .oo-ui-fieldLayout-body > .oo-ui-fieldLayout-field { - width: 60%; -} -.oo-ui-fieldLayout.oo-ui-labelElement.oo-ui-fieldLayout-align-inline > .oo-ui-fieldLayout-body > .oo-ui-fieldLayout-header { - padding-top: 0; - padding-bottom: 0; - padding-left: 0.42857143em; -} -.oo-ui-fieldLayout .oo-ui-fieldLayout-help { - margin-right: 0; -} -.oo-ui-fieldLayout .oo-ui-fieldLayout-help:last-child { - margin-right: 0; -} -.oo-ui-fieldLayout .oo-ui-fieldLayout-help .oo-ui-buttonElement-button { - padding-top: 1.42857143em; - padding-right: 0; -} -.oo-ui-fieldLayout.oo-ui-fieldLayout-align-top > .oo-ui-fieldLayout-body > .oo-ui-inline-help { - margin-top: 0.28571429em; -} -.oo-ui-fieldLayout.oo-ui-fieldLayout-align-top .oo-ui-fieldLayout-help, -.oo-ui-fieldLayout.oo-ui-fieldLayout-align-inline .oo-ui-fieldLayout-help { - margin-top: -0.42857143em; - margin-right: -0.57142857em; - margin-left: 0; -} -.oo-ui-fieldLayout-messages { - list-style: none none; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - max-width: 50em; - margin: 0; - padding: 0.28571429em 0.85714286em; -} -.oo-ui-fieldLayout-messages > li { - display: table; - margin: 0.28571429em 0 0; - padding: 0; -} -.oo-ui-fieldLayout-messages .oo-ui-iconElement.oo-ui-iconElement-icon { - display: table-cell; - position: static; - top: auto; - height: 1.42857143em; -} -.oo-ui-fieldLayout-messages .oo-ui-labelWidget { - display: table-cell; - padding-left: 0.42857143em; - vertical-align: middle; -} -.oo-ui-fieldLayout-disabled > .oo-ui-fieldLayout-body > .oo-ui-fieldLayout-header > .oo-ui-labelElement-label { - color: #72777d; -} -.oo-ui-actionFieldLayout-input, -.oo-ui-actionFieldLayout-button { - display: table-cell; - vertical-align: middle; -} -.oo-ui-actionFieldLayout-button { - width: 1%; - white-space: nowrap; -} -.oo-ui-actionFieldLayout.oo-ui-fieldLayout-align-top { - max-width: 50em; -} -.oo-ui-actionFieldLayout .oo-ui-actionFieldLayout-input .oo-ui-widget:not( .oo-ui-textInputWidget ) { - margin-right: 0.57142857em; -} -.oo-ui-actionFieldLayout .oo-ui-actionFieldLayout-input .oo-ui-widget.oo-ui-textInputWidget > .oo-ui-inputWidget-input { - border-radius: 2px 0 0 2px; - position: relative; -} -.oo-ui-actionFieldLayout .oo-ui-actionFieldLayout-button .oo-ui-buttonElement-framed > .oo-ui-buttonElement-button { - border-radius: 0 2px 2px 0; - margin-left: -1px; -} -.oo-ui-actionFieldLayout .oo-ui-actionFieldLayout-button .oo-ui-buttonElement-frameless { - margin-left: 0.14285714em; -} -.oo-ui-actionFieldLayout .oo-ui-actionFieldLayout-input > .oo-ui-textInputWidget > .oo-ui-inputWidget-input:hover, -.oo-ui-actionFieldLayout .oo-ui-actionFieldLayout-input > .oo-ui-textInputWidget > .oo-ui-inputWidget-input:hover ~ *, -.oo-ui-actionFieldLayout .oo-ui-actionFieldLayout-input > .oo-ui-textInputWidget > .oo-ui-inputWidget-input:focus, -.oo-ui-actionFieldLayout .oo-ui-actionFieldLayout-input > .oo-ui-textInputWidget > .oo-ui-inputWidget-input:focus ~ * { - z-index: 1; -} -.oo-ui-actionFieldLayout .oo-ui-actionFieldLayout-button > .oo-ui-buttonElement > .oo-ui-buttonElement-button:hover, -.oo-ui-actionFieldLayout .oo-ui-actionFieldLayout-button > .oo-ui-buttonElement > .oo-ui-buttonElement-button:focus { - z-index: 1; -} -.oo-ui-fieldsetLayout { - position: relative; - min-width: 0; - margin: 0; - border: 0; - padding: 0.01px 0 0 0; -} -body:not( :-moz-handler-blocked ) .oo-ui-fieldsetLayout { - display: table-cell; -} -.oo-ui-fieldsetLayout > .oo-ui-fieldsetLayout-header { - display: none; -} -.oo-ui-fieldsetLayout.oo-ui-iconElement > .oo-ui-fieldsetLayout-header, -.oo-ui-fieldsetLayout.oo-ui-labelElement > .oo-ui-fieldsetLayout-header { - color: inherit; - display: inline-table; - box-sizing: border-box; - padding: 0; - white-space: normal; - float: left; - width: 100%; -} -.oo-ui-fieldsetLayout-group { - clear: both; -} -.oo-ui-fieldsetLayout .oo-ui-fieldsetLayout-help { - float: right; -} -.oo-ui-fieldsetLayout .oo-ui-fieldsetLayout-help > .oo-ui-buttonElement-button > .oo-ui-labelElement-label { - display: block; - position: absolute !important; - /* stylelint-disable-line declaration-no-important */ - clip: rect(1px, 1px, 1px, 1px); - width: 1px; - height: 1px; - margin: -1px; - border: 0; - padding: 0; - overflow: hidden; -} -.oo-ui-fieldsetLayout .oo-ui-fieldsetLayout-help > .oo-ui-popupWidget > .oo-ui-popupWidget-popup { - z-index: 1; -} -.oo-ui-fieldsetLayout .oo-ui-fieldsetLayout-header { - max-width: 50em; -} -.oo-ui-fieldsetLayout .oo-ui-fieldsetLayout-header .oo-ui-iconElement-icon { - height: 1.42857143em; -} -.oo-ui-fieldsetLayout.oo-ui-iconElement > .oo-ui-fieldsetLayout-header .oo-ui-iconElement-icon { - display: block; -} -.oo-ui-fieldsetLayout + .oo-ui-fieldsetLayout, -.oo-ui-fieldsetLayout + .oo-ui-formLayout { - margin-top: 1.71428571em; -} -.oo-ui-fieldsetLayout.oo-ui-labelElement > .oo-ui-fieldsetLayout-header > .oo-ui-labelElement-label { - display: inline-block; - margin-bottom: 0.5em; - font-size: 1.14285714em; - font-weight: bold; -} -.oo-ui-fieldsetLayout.oo-ui-iconElement > .oo-ui-fieldsetLayout-header > .oo-ui-labelElement-label { - padding-left: 1.625em; -} -.oo-ui-fieldsetLayout .oo-ui-fieldsetLayout-help { - margin-right: 0; - margin-right: -0.57142857em; -} -.oo-ui-fieldsetLayout .oo-ui-fieldsetLayout-help:last-child { - margin-right: 0; -} -.oo-ui-fieldsetLayout .oo-ui-fieldsetLayout-help:last-child { - margin-right: -0.57142857em; -} -.oo-ui-fieldsetLayout .oo-ui-fieldsetLayout-help .oo-ui-buttonElement-button { - padding-top: 1.42857143em; - padding-right: 0; -} -.oo-ui-formLayout + .oo-ui-fieldsetLayout, -.oo-ui-formLayout + .oo-ui-formLayout { - margin-top: 1.71428571em; -} -.oo-ui-panelLayout { - position: relative; -} -.oo-ui-panelLayout-scrollable { - overflow: auto; -} -.oo-ui-panelLayout-expanded { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; -} -.oo-ui-panelLayout-padded { - padding: 1.14285714em; -} -.oo-ui-panelLayout-padded.oo-ui-formLayout > .oo-ui-fieldsetLayout .oo-ui-labelElement-label, -.oo-ui-panelLayout-padded.oo-ui-formLayout > .oo-ui-fieldsetLayout .oo-ui-iconElement-icon { - margin-top: -0.42857143em; -} -.oo-ui-panelLayout-framed { - border: 1px solid #a2a9b1; - border-radius: 2px; -} -.oo-ui-panelLayout-padded.oo-ui-panelLayout-framed { - margin: 0.85714286em 0; -} -.oo-ui-horizontalLayout > .oo-ui-widget { - display: inline-block; - vertical-align: middle; -} -.oo-ui-horizontalLayout > .oo-ui-layout { - display: inline-block; -} -.oo-ui-horizontalLayout > .oo-ui-layout, -.oo-ui-horizontalLayout > .oo-ui-widget { - margin-right: 0.5em; -} -.oo-ui-horizontalLayout > .oo-ui-layout:last-child, -.oo-ui-horizontalLayout > .oo-ui-widget:last-child { - margin-right: 0; -} -.oo-ui-horizontalLayout > .oo-ui-layout { - margin-top: 0; -} -.oo-ui-horizontalLayout > .oo-ui-widget { - margin-bottom: 0.5em; -} -.oo-ui-optionWidget { - position: relative; - display: block; -} -.oo-ui-optionWidget.oo-ui-widget-enabled { - cursor: pointer; -} -.oo-ui-optionWidget.oo-ui-widget-disabled { - cursor: default; -} -.oo-ui-optionWidget.oo-ui-labelElement > .oo-ui-labelElement-label { - display: block; - white-space: nowrap; - text-overflow: ellipsis; - overflow: hidden; -} -.oo-ui-optionWidget-selected .oo-ui-buttonElement-button > .oo-ui-iconElement-icon { - opacity: 1; -} -.oo-ui-optionWidget.oo-ui-widget-disabled { - color: #72777d; -} -.oo-ui-decoratedOptionWidget { - padding: 0.64285714em 0.85714286em 0.57142857em; - line-height: 1; -} -.oo-ui-decoratedOptionWidget.oo-ui-iconElement { - padding-left: 2.64285714em; -} -.oo-ui-decoratedOptionWidget .oo-ui-iconElement-icon { - left: 0.78571429em; -} -.oo-ui-decoratedOptionWidget .oo-ui-labelElement-label { - line-height: 1.07142857em; -} -.oo-ui-decoratedOptionWidget.oo-ui-indicatorElement { - padding-right: 2.28571429em; -} -.oo-ui-decoratedOptionWidget .oo-ui-indicatorElement-indicator { - right: 0.85714286em; -} -.oo-ui-decoratedOptionWidget.oo-ui-widget-enabled:hover .oo-ui-iconElement-icon, -.oo-ui-decoratedOptionWidget.oo-ui-widget-enabled:hover .oo-ui-indicatorElement-indicator { - opacity: 0.73; -} -.oo-ui-decoratedOptionWidget.oo-ui-widget-enabled .oo-ui-iconElement-icon, -.oo-ui-decoratedOptionWidget.oo-ui-widget-enabled .oo-ui-indicatorElement-indicator { - opacity: 0.87; - -webkit-transition: opacity 100ms; - -moz-transition: opacity 100ms; - transition: opacity 100ms; -} -.oo-ui-decoratedOptionWidget.oo-ui-widget-disabled .oo-ui-iconElement-icon, -.oo-ui-decoratedOptionWidget.oo-ui-widget-disabled .oo-ui-indicatorElement-indicator { - opacity: 0.51; -} -.oo-ui-radioSelectWidget:focus { - outline: 0; -} -.oo-ui-radioSelectWidget:focus [type='radio']:checked + span:before { - border-color: #fff; -} -.oo-ui-radioOptionWidget { - display: table; - width: 100%; - padding: 0.28571429em 0; -} -.oo-ui-radioOptionWidget .oo-ui-radioInputWidget, -.oo-ui-radioOptionWidget.oo-ui-labelElement > .oo-ui-labelElement-label { - display: table-cell; - vertical-align: top; -} -.oo-ui-radioOptionWidget .oo-ui-radioInputWidget { - width: 1px; -} -.oo-ui-radioOptionWidget.oo-ui-labelElement > .oo-ui-labelElement-label { - white-space: normal; -} -.oo-ui-radioOptionWidget.oo-ui-labelElement .oo-ui-labelElement-label { - padding-left: 0.42857143em; -} -.oo-ui-radioOptionWidget .oo-ui-radioInputWidget { - margin-right: 0; -} -.oo-ui-labelWidget { - display: inline-block; -} -.oo-ui-labelWidget.oo-ui-inline-help { - display: block; - color: #54595d; - font-size: 0.92857143em; -} -.oo-ui-iconWidget { - vertical-align: middle; - line-height: 2.5; - display: inline-block; - position: static; - top: auto; - height: 1.42857143em; -} -.oo-ui-iconWidget.oo-ui-widget-disabled { - opacity: 0.51; -} -.oo-ui-indicatorWidget { - vertical-align: middle; - line-height: 2.5; - margin: 0.42857143em; - display: inline-block; - position: static; - top: auto; - height: 0.85714286em; -} -.oo-ui-indicatorWidget.oo-ui-widget-disabled { - opacity: 0.51; -} -.oo-ui-buttonWidget { - margin-right: 0.5em; -} -.oo-ui-buttonWidget:last-child { - margin-right: 0; -} -.oo-ui-buttonGroupWidget { - display: inline-block; - white-space: nowrap; - border-radius: 2px; - margin-right: 0.5em; - z-index: 0; - position: relative; -} -.oo-ui-buttonGroupWidget .oo-ui-buttonWidget.oo-ui-buttonElement-active .oo-ui-buttonElement-button { - cursor: default; -} -.oo-ui-buttonGroupWidget:last-child { - margin-right: 0; -} -.oo-ui-buttonGroupWidget .oo-ui-buttonElement { - margin-right: 0; - z-index: 0; -} -.oo-ui-buttonGroupWidget .oo-ui-buttonElement:last-child { - margin-right: 0; -} -.oo-ui-buttonGroupWidget .oo-ui-buttonElement-framed .oo-ui-buttonElement-button { - margin-left: -1px; - border-radius: 0; -} -.oo-ui-buttonGroupWidget .oo-ui-buttonElement-framed:first-child .oo-ui-buttonElement-button { - margin-left: 0; - border-bottom-left-radius: 2px; - border-top-left-radius: 2px; -} -.oo-ui-buttonGroupWidget .oo-ui-buttonElement-framed:last-child .oo-ui-buttonElement-button { - border-bottom-right-radius: 2px; - border-top-right-radius: 2px; -} -.oo-ui-buttonGroupWidget .oo-ui-buttonElement-framed.oo-ui-widget-disabled + .oo-ui-widget-disabled > .oo-ui-buttonElement-button { - border-left-color: #fff; -} -.oo-ui-buttonGroupWidget.oo-ui-widget-enabled .oo-ui-buttonElement.oo-ui-widget-enabled > .oo-ui-buttonElement-button:active { - z-index: 1; -} -.oo-ui-buttonGroupWidget.oo-ui-widget-enabled .oo-ui-buttonElement.oo-ui-widget-enabled > .oo-ui-buttonElement-button:focus { - z-index: 2; -} -.oo-ui-buttonGroupWidget.oo-ui-widget-enabled .oo-ui-buttonElement.oo-ui-buttonElement-active > .oo-ui-buttonElement-button { - z-index: 3; -} -.oo-ui-buttonGroupWidget.oo-ui-widget-enabled .oo-ui-buttonElement.oo-ui-widget-disabled > .oo-ui-buttonElement-button { - z-index: -1; -} -.oo-ui-buttonGroupWidget.oo-ui-widget-enabled .oo-ui-buttonElement.oo-ui-toggleWidget-on + .oo-ui-toggleWidget-on > .oo-ui-buttonElement-button, -.oo-ui-buttonGroupWidget.oo-ui-widget-enabled .oo-ui-buttonElement.oo-ui-toggleWidget-on + .oo-ui-toggleWidget-on > .oo-ui-buttonElement-button:active { - border-left-color: #a2a9b1; - z-index: 3; -} -.oo-ui-popupWidget { - position: absolute; -} -.oo-ui-popupWidget-popup { - position: relative; - overflow: hidden; - z-index: 1; -} -.oo-ui-popupWidget-anchor { - display: none; - z-index: 1; -} -.oo-ui-popupWidget-anchored .oo-ui-popupWidget-anchor { - display: block; - position: absolute; - background-repeat: no-repeat; -} -.oo-ui-popupWidget-anchored .oo-ui-popupWidget-anchor:before, -.oo-ui-popupWidget-anchored .oo-ui-popupWidget-anchor:after { - content: ''; - position: absolute; - width: 0; - height: 0; - border-style: solid; - border-color: transparent; -} -.oo-ui-popupWidget-anchored-top .oo-ui-popupWidget-anchor { - left: 0; -} -.oo-ui-popupWidget-anchored-top .oo-ui-popupWidget-anchor:before, -.oo-ui-popupWidget-anchored-top .oo-ui-popupWidget-anchor:after { - border-top: 0; -} -.oo-ui-popupWidget-anchored-bottom .oo-ui-popupWidget-anchor { - left: 0; -} -.oo-ui-popupWidget-anchored-bottom .oo-ui-popupWidget-anchor:before, -.oo-ui-popupWidget-anchored-bottom .oo-ui-popupWidget-anchor:after { - border-bottom: 0; -} -.oo-ui-popupWidget-anchored-start .oo-ui-popupWidget-anchor { - top: 0; -} -.oo-ui-popupWidget-anchored-start .oo-ui-popupWidget-anchor:before, -.oo-ui-popupWidget-anchored-start .oo-ui-popupWidget-anchor:after { - border-left: 0; -} -.oo-ui-popupWidget-anchored-end .oo-ui-popupWidget-anchor { - top: 0; -} -.oo-ui-popupWidget-anchored-end .oo-ui-popupWidget-anchor:before, -.oo-ui-popupWidget-anchored-end .oo-ui-popupWidget-anchor:after { - border-right: 0; -} -.oo-ui-popupWidget-head { - -webkit-touch-callout: none; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} -.oo-ui-popupWidget-head > .oo-ui-buttonWidget { - position: absolute; -} -.oo-ui-popupWidget-head > .oo-ui-labelElement-label { - float: left; - cursor: default; -} -.oo-ui-popupWidget-body { - clear: both; -} -.oo-ui-popupWidget-body.oo-ui-clippableElement-clippable { - min-height: 1em; -} -.oo-ui-popupWidget-popup { - background-color: #fff; - border: 1px solid #a2a9b1; - border-radius: 2px; - box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.25); -} -.oo-ui-popupWidget-anchored-top { - margin-top: 9px; -} -.oo-ui-popupWidget-anchored-top .oo-ui-popupWidget-anchor { - top: -9px; -} -.oo-ui-popupWidget-anchored-top .oo-ui-popupWidget-anchor:before { - bottom: -10px; - left: -9px; - border-bottom-color: #a2a9b1; - border-width: 10px; -} -.oo-ui-popupWidget-anchored-top .oo-ui-popupWidget-anchor:after { - bottom: -10px; - left: -8px; - border-bottom-color: #fff; - border-width: 9px; -} -.oo-ui-popupWidget-anchored-bottom { - margin-bottom: 9px; -} -.oo-ui-popupWidget-anchored-bottom .oo-ui-popupWidget-anchor { - bottom: -9px; -} -.oo-ui-popupWidget-anchored-bottom .oo-ui-popupWidget-anchor:before { - top: -10px; - left: -9px; - border-top-color: #a2a9b1; - border-width: 10px; -} -.oo-ui-popupWidget-anchored-bottom .oo-ui-popupWidget-anchor:after { - top: -10px; - left: -8px; - border-top-color: #fff; - border-width: 9px; -} -.oo-ui-popupWidget-anchored-start { - margin-left: 9px; -} -.oo-ui-popupWidget-anchored-start .oo-ui-popupWidget-anchor { - left: -9px; -} -.oo-ui-popupWidget-anchored-start .oo-ui-popupWidget-anchor:before { - right: -10px; - top: -9px; - border-right-color: #a2a9b1; - border-width: 10px; -} -.oo-ui-popupWidget-anchored-start .oo-ui-popupWidget-anchor:after { - right: -10px; - top: -8px; - border-right-color: #fff; - border-width: 9px; -} -.oo-ui-popupWidget-anchored-end { - margin-right: 9px; -} -.oo-ui-popupWidget-anchored-end .oo-ui-popupWidget-anchor { - right: -9px; -} -.oo-ui-popupWidget-anchored-end .oo-ui-popupWidget-anchor:before { - left: -10px; - top: -9px; - border-left-color: #a2a9b1; - border-width: 10px; -} -.oo-ui-popupWidget-anchored-end .oo-ui-popupWidget-anchor:after { - left: -10px; - top: -8px; - border-left-color: #fff; - border-width: 9px; -} -.oo-ui-popupWidget-transitioning .oo-ui-popupWidget-popup { - -webkit-transition: width 100ms, height 100ms, left 100ms; - -moz-transition: width 100ms, height 100ms, left 100ms; - transition: width 100ms, height 100ms, left 100ms; -} -.oo-ui-popupWidget-head > .oo-ui-labelElement-label { - margin: 0.64285714em 2.64285714em 0.57142857em 0.85714286em; - line-height: 1.07142857em; -} -.oo-ui-popupWidget-head > .oo-ui-buttonWidget { - right: 0; -} -.oo-ui-popupWidget-body { - line-height: 1.42857143em; -} -.oo-ui-popupWidget-body-padded { - margin: 0.64285714em 0.85714286em 0.57142857em; -} -.oo-ui-popupWidget-body-padded > :first-child { - margin-top: 0; -} -.oo-ui-popupWidget-footer { - margin: 0.64285714em 0.85714286em 0.57142857em; -} -.oo-ui-popupButtonWidget { - position: relative; -} -.oo-ui-popupButtonWidget .oo-ui-popupWidget { - cursor: auto; -} -.oo-ui-inputWidget { - margin-right: 0.5em; -} -.oo-ui-inputWidget:last-child { - margin-right: 0; -} -.oo-ui-buttonInputWidget > button, -.oo-ui-buttonInputWidget > input { - background-color: transparent; - margin: 0; - border: 0; - padding: 0; -} -.oo-ui-checkboxInputWidget { - display: inline-block; - position: relative; - line-height: 1.42857143em; - white-space: nowrap; -} -.oo-ui-checkboxInputWidget * { - font: inherit; - vertical-align: middle; -} -.oo-ui-checkboxInputWidget [type='checkbox'] { - position: relative; - max-width: none; - width: 1.42857143em; - height: 1.42857143em; - margin: 0; - opacity: 0; - z-index: 1; -} -.oo-ui-checkboxInputWidget [type='checkbox'] + span { - background-color: #fff; - background-size: 0 0; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - position: absolute; - left: 0; - width: 1.42857143em; - height: 1.42857143em; - border: 1px solid #72777d; - border-radius: 2px; -} -.oo-ui-checkboxInputWidget [type='checkbox']:checked + span { - background-size: 1em 1em; -} -.oo-ui-checkboxInputWidget [type='checkbox']:disabled + span { - background-color: #c8ccd1; - border-color: #c8ccd1; -} -.oo-ui-checkboxInputWidget.oo-ui-widget-enabled [type='checkbox'] { - cursor: pointer; -} -.oo-ui-checkboxInputWidget.oo-ui-widget-enabled [type='checkbox'] + span { - cursor: pointer; - -webkit-transition: background-color 100ms, border-color 100ms, box-shadow 100ms; - -moz-transition: background-color 100ms, border-color 100ms, box-shadow 100ms; - transition: background-color 100ms, border-color 100ms, box-shadow 100ms; -} -.oo-ui-checkboxInputWidget.oo-ui-widget-enabled [type='checkbox']:focus + span { - border-color: #36c; - box-shadow: inset 0 0 0 1px #36c; -} -.oo-ui-checkboxInputWidget.oo-ui-widget-enabled [type='checkbox']:hover + span { - border-color: #36c; -} -.oo-ui-checkboxInputWidget.oo-ui-widget-enabled [type='checkbox']:active + span { - background-color: #2a4b8d; - border-color: #2a4b8d; - box-shadow: inset 0 0 0 1px #2a4b8d; -} -.oo-ui-checkboxInputWidget.oo-ui-widget-enabled [type='checkbox']:checked + span { - background-color: #36c; - border-color: #36c; -} -.oo-ui-checkboxInputWidget.oo-ui-widget-enabled [type='checkbox']:checked:focus + span { - background-color: #36c; - border-color: #36c; - box-shadow: inset 0 0 0 1px #36c, inset 0 0 0 2px #fff; -} -.oo-ui-checkboxInputWidget.oo-ui-widget-enabled [type='checkbox']:checked:hover + span { - background-color: #447ff5; - border-color: #447ff5; -} -.oo-ui-checkboxInputWidget.oo-ui-widget-enabled [type='checkbox']:checked:active + span { - background-color: #2a4b8d; - border-color: #2a4b8d; - box-shadow: inset 0 0 0 1px #2a4b8d; -} -.oo-ui-checkboxMultiselectInputWidget .oo-ui-fieldLayout { - margin-top: 0.28571429em; -} -.oo-ui-dropdownInputWidget { - position: relative; - vertical-align: middle; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - width: 100%; - max-width: 50em; -} -.oo-ui-dropdownInputWidget .oo-ui-dropdownWidget, -.oo-ui-dropdownInputWidget.oo-ui-dropdownInputWidget-php select { - display: block; -} -.oo-ui-dropdownInputWidget select { - display: none; - background-position: -9999em 0; - background-repeat: no-repeat; - width: 100%; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; -} -.oo-ui-dropdownInputWidget.oo-ui-widget-enabled select { - cursor: pointer; -} -.oo-ui-dropdownInputWidget-php { - border-right: 1px solid #a2a9b1; - border-radius: 2px; - overflow-x: hidden; -} -.oo-ui-dropdownInputWidget select { - -webkit-appearance: none; - -moz-appearance: none; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - border: 1px solid #a2a9b1; - border-radius: 2px; - padding: 0.57142857em 0.85714286em 0.5em; - font-size: inherit; - font-family: inherit; - vertical-align: middle; -} -.oo-ui-dropdownInputWidget select::-ms-expand { - display: none; -} -.oo-ui-dropdownInputWidget select:not( [no-ie] ) { - background-position: right 1.75em center; - width: calc( 100% + 1em ); - height: 2.28571429em; - padding: 0 0 0 0.85714286em; -} -.oo-ui-dropdownInputWidget option { - font-size: inherit; - font-family: inherit; - height: 1.5em; - padding: 0.57142857em 0.85714286em; -} -.oo-ui-dropdownInputWidget.oo-ui-widget-enabled select { - background-color: #f8f9fa; - color: #222; - -webkit-transition: background-color 100ms, border-color 100ms, box-shadow 100ms; - -moz-transition: background-color 100ms, border-color 100ms, box-shadow 100ms; - transition: background-color 100ms, border-color 100ms, box-shadow 100ms; -} -.oo-ui-dropdownInputWidget.oo-ui-widget-enabled select:hover { - background-color: #fff; - color: #444; - border-color: #a2a9b1; -} -.oo-ui-dropdownInputWidget.oo-ui-widget-enabled select:active { - color: #000; - border-color: #72777d; -} -.oo-ui-dropdownInputWidget.oo-ui-widget-enabled select:focus { - border-color: #36c; - outline: 0; - box-shadow: inset 0 0 0 1px #36c; -} -.oo-ui-dropdownInputWidget.oo-ui-widget-disabled select { - background-color: #eaecf0; - color: #72777d; - border-color: #c8ccd1; -} -.oo-ui-radioInputWidget { - display: inline-block; - position: relative; - line-height: 1.42857143em; - white-space: nowrap; -} -.oo-ui-radioInputWidget * { - font: inherit; - vertical-align: middle; -} -.oo-ui-radioInputWidget [type='radio'] { - position: relative; - max-width: none; - width: 1.42857143em; - height: 1.42857143em; - margin: 0; - opacity: 0; - z-index: 1; -} -.oo-ui-radioInputWidget [type='radio'] + span { - background-color: #fff; - position: absolute; - left: 0; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - width: 1.42857143em; - height: 1.42857143em; - border: 1px solid #72777d; - border-radius: 100%; -} -.oo-ui-radioInputWidget [type='radio'] + span:before { - content: ' '; - position: absolute; - top: -4px; - left: -4px; - right: -4px; - bottom: -4px; - border: 1px solid transparent; - border-radius: 100%; -} -.oo-ui-radioInputWidget [type='radio']:checked + span, -.oo-ui-radioInputWidget [type='radio']:checked:hover + span, -.oo-ui-radioInputWidget [type='radio']:checked:focus:hover + span { - border-width: 0.42857143em; -} -.oo-ui-radioInputWidget [type='radio']:disabled + span { - background-color: #c8ccd1; - border-color: #c8ccd1; -} -.oo-ui-radioInputWidget [type='radio']:disabled:checked + span { - background-color: #fff; -} -.oo-ui-radioInputWidget.oo-ui-widget-enabled [type='radio'] { - cursor: pointer; -} -.oo-ui-radioInputWidget.oo-ui-widget-enabled [type='radio'] + span { - cursor: pointer; - -webkit-transition: background-color 100ms, border-color 100ms, border-width 100ms; - -moz-transition: background-color 100ms, border-color 100ms, border-width 100ms; - transition: background-color 100ms, border-color 100ms, border-width 100ms; -} -.oo-ui-radioInputWidget.oo-ui-widget-enabled [type='radio']:hover + span { - border-color: #36c; -} -.oo-ui-radioInputWidget.oo-ui-widget-enabled [type='radio']:active + span { - background-color: #2a4b8d; - border-color: #2a4b8d; -} -.oo-ui-radioInputWidget.oo-ui-widget-enabled [type='radio']:checked + span { - border-color: #36c; -} -.oo-ui-radioInputWidget.oo-ui-widget-enabled [type='radio']:checked:focus + span:before { - border-color: #fff; -} -.oo-ui-radioInputWidget.oo-ui-widget-enabled [type='radio']:checked:hover + span { - border-color: #447ff5; -} -.oo-ui-radioInputWidget.oo-ui-widget-enabled [type='radio']:checked:active + span { - border-color: #2a4b8d; - box-shadow: inset 0 0 0 1px #2a4b8d; -} -.oo-ui-radioInputWidget.oo-ui-widget-enabled [type='radio']:checked:active + span:before { - border-color: #2a4b8d; -} -.oo-ui-radioSelectInputWidget .oo-ui-fieldLayout { - margin-top: 0.28571429em; -} -.oo-ui-textInputWidget { - position: relative; - vertical-align: middle; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - width: 100%; - max-width: 50em; -} -.oo-ui-textInputWidget input, -.oo-ui-textInputWidget textarea { - display: block; - width: 100%; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; -} -.oo-ui-textInputWidget textarea { - overflow: auto; -} -.oo-ui-textInputWidget textarea.oo-ui-textInputWidget-autosized { - resize: none; -} -.oo-ui-textInputWidget [type='number'] { - -moz-appearance: textfield; -} -.oo-ui-textInputWidget [type='number']::-webkit-outer-spin-button, -.oo-ui-textInputWidget [type='number']::-webkit-inner-spin-button { - -webkit-appearance: none; - margin: 0; -} -.oo-ui-textInputWidget [type='search'] { - -webkit-appearance: none; -} -.oo-ui-textInputWidget [type='search']::-ms-clear { - display: none; -} -.oo-ui-textInputWidget [type='search']::-webkit-search-decoration, -.oo-ui-textInputWidget [type='search']::-webkit-search-cancel-button { - display: none; -} -.oo-ui-textInputWidget.oo-ui-widget-enabled > .oo-ui-iconElement-icon, -.oo-ui-textInputWidget.oo-ui-widget-enabled > .oo-ui-indicatorElement-indicator { - cursor: text; -} -.oo-ui-textInputWidget.oo-ui-widget-enabled.oo-ui-textInputWidget-type-search > .oo-ui-indicatorElement-indicator { - cursor: pointer; -} -.oo-ui-textInputWidget.oo-ui-widget-disabled > * { - -webkit-touch-callout: none; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} -.oo-ui-textInputWidget.oo-ui-labelElement > .oo-ui-labelElement-label { - display: block; -} -.oo-ui-textInputWidget > .oo-ui-iconElement-icon, -.oo-ui-textInputWidget-labelPosition-before > .oo-ui-labelElement-label { - left: 0; -} -.oo-ui-textInputWidget > .oo-ui-indicatorElement-indicator, -.oo-ui-textInputWidget-labelPosition-after > .oo-ui-labelElement-label { - right: 0; -} -.oo-ui-textInputWidget-labelPosition-after.oo-ui-labelElement ::-ms-clear { - display: none; -} -.oo-ui-textInputWidget > .oo-ui-labelElement-label { - position: absolute; - top: 0; -} -.oo-ui-textInputWidget-php > .oo-ui-iconElement-icon, -.oo-ui-textInputWidget-php > .oo-ui-indicatorElement-indicator, -.oo-ui-textInputWidget-php > .oo-ui-labelElement-label { - pointer-events: none; -} -.oo-ui-textInputWidget input, -.oo-ui-textInputWidget textarea { - -webkit-appearance: none; - margin: 0; - font-size: inherit; - font-family: inherit; - background-color: #fff; - color: #000; - border: 1px solid #a2a9b1; - border-radius: 2px; - padding: 0.57142857em 0.57142857em 0.5em; -} -.oo-ui-textInputWidget input { - line-height: 1.07142857em; -} -.oo-ui-textInputWidget textarea { - line-height: 1.286; -} -.oo-ui-textInputWidget .oo-ui-pendingElement-pending { - background-color: transparent; -} -.oo-ui-textInputWidget.oo-ui-widget-enabled input, -.oo-ui-textInputWidget.oo-ui-widget-enabled textarea { - box-shadow: inset 0 0 0 1px transparent; - -webkit-transition: border-color 250ms, box-shadow 250ms; - -moz-transition: border-color 250ms, box-shadow 250ms; - transition: border-color 250ms, box-shadow 250ms; -} -.oo-ui-textInputWidget.oo-ui-widget-enabled input::-webkit-input-placeholder, -.oo-ui-textInputWidget.oo-ui-widget-enabled textarea::-webkit-input-placeholder { - color: #72777d; - opacity: 1; -} -.oo-ui-textInputWidget.oo-ui-widget-enabled input:-ms-input-placeholder, -.oo-ui-textInputWidget.oo-ui-widget-enabled textarea:-ms-input-placeholder { - color: #72777d; - opacity: 1; -} -.oo-ui-textInputWidget.oo-ui-widget-enabled input::-moz-placeholder, -.oo-ui-textInputWidget.oo-ui-widget-enabled textarea::-moz-placeholder { - color: #72777d; - opacity: 1; -} -.oo-ui-textInputWidget.oo-ui-widget-enabled input:-moz-placeholder, -.oo-ui-textInputWidget.oo-ui-widget-enabled textarea:-moz-placeholder { - color: #72777d; - opacity: 1; -} -.oo-ui-textInputWidget.oo-ui-widget-enabled input::placeholder, -.oo-ui-textInputWidget.oo-ui-widget-enabled textarea::placeholder { - color: #72777d; - opacity: 1; -} -.oo-ui-textInputWidget.oo-ui-widget-enabled input:focus, -.oo-ui-textInputWidget.oo-ui-widget-enabled textarea:focus { - outline: 0; - border-color: #36c; - box-shadow: inset 0 0 0 1px #36c; -} -.oo-ui-textInputWidget.oo-ui-widget-enabled input[readonly], -.oo-ui-textInputWidget.oo-ui-widget-enabled textarea[readonly] { - background-color: #f8f9fa; -} -.oo-ui-textInputWidget.oo-ui-widget-enabled:hover input, -.oo-ui-textInputWidget.oo-ui-widget-enabled:hover textarea { - border-color: #72777d; -} -.oo-ui-textInputWidget.oo-ui-widget-enabled:hover input:focus, -.oo-ui-textInputWidget.oo-ui-widget-enabled:hover textarea:focus { - border-color: #36c; -} -@media screen and (min-width: 0) { - .oo-ui-textInputWidget.oo-ui-widget-enabled textarea:focus { - outline: 1px solid #36c; - outline-offset: -2px; - } - .oo-ui-textInputWidget.oo-ui-widget-enabled.oo-ui-flaggedElement-invalid textarea:focus { - outline-color: #d33; - } -} -.oo-ui-textInputWidget.oo-ui-widget-enabled.oo-ui-flaggedElement-invalid input, -.oo-ui-textInputWidget.oo-ui-widget-enabled.oo-ui-flaggedElement-invalid textarea { - border-color: #d33; -} -.oo-ui-textInputWidget.oo-ui-widget-enabled.oo-ui-flaggedElement-invalid input:hover, -.oo-ui-textInputWidget.oo-ui-widget-enabled.oo-ui-flaggedElement-invalid textarea:hover { - border-color: #d33; -} -.oo-ui-textInputWidget.oo-ui-widget-enabled.oo-ui-flaggedElement-invalid input:focus, -.oo-ui-textInputWidget.oo-ui-widget-enabled.oo-ui-flaggedElement-invalid textarea:focus { - border-color: #d33; - box-shadow: inset 0 0 0 1px #d33; -} -.oo-ui-textInputWidget.oo-ui-widget-disabled input, -.oo-ui-textInputWidget.oo-ui-widget-disabled textarea { - background-color: #eaecf0; - -webkit-text-fill-color: #72777d; - color: #72777d; - text-shadow: 0 1px 1px #fff; - border-color: #c8ccd1; -} -.oo-ui-textInputWidget.oo-ui-widget-disabled > .oo-ui-iconElement-icon, -.oo-ui-textInputWidget.oo-ui-widget-disabled > .oo-ui-indicatorElement-indicator { - opacity: 0.51; -} -.oo-ui-textInputWidget.oo-ui-widget-disabled > .oo-ui-labelElement-label { - color: #72777d; - text-shadow: 0 1px 1px #fff; -} -.oo-ui-textInputWidget.oo-ui-iconElement input, -.oo-ui-textInputWidget.oo-ui-iconElement textarea { - padding-left: 2.64285714em; -} -.oo-ui-textInputWidget.oo-ui-iconElement > .oo-ui-iconElement-icon { - left: 0.57142857em; -} -.oo-ui-textInputWidget.oo-ui-iconElement textarea + .oo-ui-iconElement-icon { - max-height: 2.28571429em; -} -.oo-ui-textInputWidget > .oo-ui-labelElement-label { - color: #72777d; - margin-top: 1px; - padding: 0.57142857em 0.85714286em 0.5em 0.57142857em; - line-height: 1.07142857em; -} -.oo-ui-textInputWidget.oo-ui-indicatorElement input, -.oo-ui-textInputWidget.oo-ui-indicatorElement textarea { - padding-right: 2em; -} -.oo-ui-textInputWidget.oo-ui-indicatorElement.oo-ui-textInputWidget-labelPosition-after > .oo-ui-labelElement-label { - padding-right: 0; -} -.oo-ui-textInputWidget.oo-ui-indicatorElement > .oo-ui-indicatorElement-indicator { - max-height: 2.28571429em; - margin-right: 0.85714286em; -} -.oo-ui-textInputWidget-labelPosition-after.oo-ui-indicatorElement > .oo-ui-labelElement-label { - margin-right: 2.28571429em; -} -.oo-ui-textInputWidget-labelPosition-before.oo-ui-iconElement > .oo-ui-labelElement-label { - padding-left: 2.64285714em; -} -.oo-ui-menuSelectWidget { - position: absolute; - width: 100%; - z-index: 4; - background-color: #fff; - margin-top: -1px; - margin-bottom: -1px; - border: 1px solid #a2a9b1; - border-radius: 0 0 2px 2px; - box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.25); -} -.oo-ui-menuSelectWidget.oo-ui-clippableElement-clippable { - min-height: 2.6em; -} -.oo-ui-menuSelectWidget-invisible { - display: none; -} -.oo-ui-menuOptionWidget { - -webkit-transition: background-color 100ms, color 100ms; - -moz-transition: background-color 100ms, color 100ms; - transition: background-color 100ms, color 100ms; -} -.oo-ui-menuOptionWidget-checkIcon { - display: none; -} -.oo-ui-menuOptionWidget.oo-ui-optionWidget-highlighted { - background-color: #eaecf0; - color: #000; -} -.oo-ui-menuOptionWidget.oo-ui-optionWidget-selected { - background-color: #eaf3ff; - color: #36c; -} -.oo-ui-menuOptionWidget.oo-ui-optionWidget-selected.oo-ui-menuOptionWidget.oo-ui-optionWidget-highlighted, -.oo-ui-menuOptionWidget.oo-ui-optionWidget-pressed.oo-ui-menuOptionWidget.oo-ui-optionWidget-highlighted { - background-color: rgba(41, 98, 204, 0.1); - color: #36c; -} -.oo-ui-menuSectionOptionWidget { - color: #72777d; - padding: 0.64285714em 0.85714286em 0.28571429em; - font-weight: bold; -} -.oo-ui-menuSectionOptionWidget.oo-ui-widget-enabled { - cursor: default; -} -.oo-ui-menuSectionOptionWidget ~ .oo-ui-menuOptionWidget { - padding-left: 1.71428571em; -} -.oo-ui-menuSectionOptionWidget ~ .oo-ui-menuOptionWidget.oo-ui-iconElement { - padding-left: 3.5em; -} -.oo-ui-menuSectionOptionWidget ~ .oo-ui-menuOptionWidget.oo-ui-iconElement .oo-ui-iconElement-icon { - left: 1.71428571em; -} -.oo-ui-dropdownWidget { - display: inline-block; - position: relative; - width: 100%; - max-width: 50em; - margin-right: 0.5em; -} -.oo-ui-dropdownWidget-handle { - position: relative; - width: 100%; - display: block; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - cursor: default; - -webkit-touch-callout: none; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; -} -.oo-ui-dropdownWidget.oo-ui-widget-enabled .oo-ui-dropdownWidget-handle { - cursor: pointer; -} -.oo-ui-dropdownWidget:last-child { - margin-right: 0; -} -.oo-ui-dropdownWidget-handle { - min-height: 2.28571429em; - border: 1px solid #a2a9b1; - border-radius: 2px; - padding: 0.57142857em 0.85714286em 0.5em; - line-height: 1; -} -.oo-ui-dropdownWidget-handle .oo-ui-iconElement-icon { - left: 0.85714286em; -} -.oo-ui-dropdownWidget-handle .oo-ui-indicatorElement-indicator { - right: 0.85714286em; -} -.oo-ui-dropdownWidget-handle .oo-ui-labelElement-label { - line-height: 1.07142857em; -} -.oo-ui-dropdownWidget.oo-ui-iconElement .oo-ui-dropdownWidget-handle { - padding-left: 2.64285714em; -} -.oo-ui-dropdownWidget.oo-ui-indicatorElement .oo-ui-dropdownWidget-handle { - padding-right: 1.71428571em; -} -.oo-ui-dropdownWidget.oo-ui-widget-enabled .oo-ui-dropdownWidget-handle { - background-color: #f8f9fa; - color: #222; - -webkit-transition: background-color 100ms, border-color 100ms, box-shadow 100ms; - -moz-transition: background-color 100ms, border-color 100ms, box-shadow 100ms; - transition: background-color 100ms, border-color 100ms, box-shadow 100ms; -} -.oo-ui-dropdownWidget.oo-ui-widget-enabled .oo-ui-dropdownWidget-handle:hover { - background-color: #fff; - color: #444; - border-color: #a2a9b1; -} -.oo-ui-dropdownWidget.oo-ui-widget-enabled .oo-ui-dropdownWidget-handle:hover .oo-ui-iconElement-icon, -.oo-ui-dropdownWidget.oo-ui-widget-enabled .oo-ui-dropdownWidget-handle:hover .oo-ui-indicatorElement-indicator { - opacity: 0.73; -} -.oo-ui-dropdownWidget.oo-ui-widget-enabled .oo-ui-dropdownWidget-handle:active { - color: #000; - border-color: #72777d; -} -.oo-ui-dropdownWidget.oo-ui-widget-enabled .oo-ui-dropdownWidget-handle:focus { - border-color: #36c; - outline: 0; - box-shadow: inset 0 0 0 1px #36c; -} -.oo-ui-dropdownWidget.oo-ui-widget-enabled .oo-ui-dropdownWidget-handle .oo-ui-iconElement-icon, -.oo-ui-dropdownWidget.oo-ui-widget-enabled .oo-ui-dropdownWidget-handle .oo-ui-indicatorElement-indicator { - opacity: 0.87; - -webkit-transition: opacity 100ms; - -moz-transition: opacity 100ms; - transition: opacity 100ms; -} -.oo-ui-dropdownWidget.oo-ui-widget-enabled.oo-ui-dropdownWidget-open .oo-ui-dropdownWidget-handle { - background-color: #fff; -} -.oo-ui-dropdownWidget.oo-ui-widget-enabled.oo-ui-dropdownWidget-open .oo-ui-dropdownWidget-handle .oo-ui-iconElement-icon, -.oo-ui-dropdownWidget.oo-ui-widget-enabled.oo-ui-dropdownWidget-open .oo-ui-dropdownWidget-handle .oo-ui-indicatorElement-indicator { - opacity: 1; -} -.oo-ui-dropdownWidget.oo-ui-widget-disabled .oo-ui-dropdownWidget-handle { - color: #72777d; - text-shadow: 0 1px 1px #fff; - border-color: #c8ccd1; - background-color: #eaecf0; -} -.oo-ui-dropdownWidget.oo-ui-widget-disabled .oo-ui-dropdownWidget-handle:focus { - outline: 0; -} -.oo-ui-dropdownWidget.oo-ui-widget-disabled .oo-ui-dropdownWidget-handle .oo-ui-indicatorElement-indicator { - opacity: 0.15; -} -.oo-ui-comboBoxInputWidget { - display: inline-block; - position: relative; -} -.oo-ui-comboBoxInputWidget-field { - display: table; - width: 100%; - table-layout: fixed; -} -.oo-ui-comboBoxInputWidget .oo-ui-inputWidget-input { - display: table-cell; - vertical-align: middle; - position: relative; - overflow: hidden; -} -.oo-ui-comboBoxInputWidget-dropdownButton { - display: table-cell; -} -.oo-ui-comboBoxInputWidget-dropdownButton > .oo-ui-buttonElement-button { - display: block; - overflow: hidden; -} -.oo-ui-comboBoxInputWidget.oo-ui-comboBoxInputWidget-empty .oo-ui-comboBoxInputWidget-dropdownButton { - display: none; -} -.oo-ui-comboBoxInputWidget-php ::-webkit-calendar-picker-indicator { - opacity: 0; - position: absolute; - right: 0; - top: 0; - width: 2.5em; - height: 2.5em; - padding: 0; -} -.oo-ui-comboBoxInputWidget-php > .oo-ui-indicatorWidget { - display: block; - position: absolute; - top: 0; - height: 100%; - pointer-events: none; -} -.oo-ui-comboBoxInputWidget input { - height: 2.28571429em; - border-top-right-radius: 0; - border-bottom-right-radius: 0; - border-right-width: 0; -} -.oo-ui-comboBoxInputWidget.oo-ui-comboBoxInputWidget-empty input, -.oo-ui-comboBoxInputWidget-php input { - border-top-right-radius: 2px; - border-bottom-right-radius: 2px; - border-right-width: 1px; -} -.oo-ui-comboBoxInputWidget-dropdownButton.oo-ui-indicatorElement { - width: 2.64285714em; -} -.oo-ui-comboBoxInputWidget-dropdownButton.oo-ui-indicatorElement .oo-ui-buttonElement-button { - min-width: 37px; - min-height: 2.28571429em; - padding-left: 0; -} -.oo-ui-comboBoxInputWidget-dropdownButton.oo-ui-indicatorElement .oo-ui-buttonElement-button > .oo-ui-indicatorElement-indicator { - right: 0.85714286em; -} -.oo-ui-comboBoxInputWidget-dropdownButton.oo-ui-indicatorElement .oo-ui-buttonElement-button, -.oo-ui-comboBoxInputWidget-dropdownButton.oo-ui-indicatorElement .oo-ui-buttonElement-button:focus { - border-top-left-radius: 0; - border-bottom-left-radius: 0; -} -.oo-ui-comboBoxInputWidget-php .oo-ui-indicatorWidget { - right: 0.85714286em; - margin: 0; -} -.oo-ui-comboBoxInputWidget-open .oo-ui-comboBoxInputWidget-dropdownButton > .oo-ui-buttonElement-button { - background-color: #fff; -} -.oo-ui-comboBoxInputWidget-open .oo-ui-comboBoxInputWidget-dropdownButton > .oo-ui-buttonElement-button .oo-ui-indicatorElement-indicator { - opacity: 1; -} -.oo-ui-comboBoxInputWidget.oo-ui-widget-disabled .oo-ui-indicatorElement-indicator { - opacity: 1; -} -.oo-ui-multioptionWidget { - position: relative; - display: block; -} -.oo-ui-multioptionWidget.oo-ui-widget-enabled { - cursor: pointer; -} -.oo-ui-multioptionWidget.oo-ui-widget-disabled { - cursor: default; -} -.oo-ui-multioptionWidget.oo-ui-labelElement .oo-ui-labelElement-label { - display: block; - white-space: nowrap; - text-overflow: ellipsis; - overflow: hidden; -} -.oo-ui-multioptionWidget.oo-ui-widget-disabled { - color: #72777d; -} -.oo-ui-checkboxMultioptionWidget { - display: table; - width: 100%; - padding: 0.28571429em 0; -} -.oo-ui-checkboxMultioptionWidget .oo-ui-checkboxInputWidget, -.oo-ui-checkboxMultioptionWidget.oo-ui-labelElement > .oo-ui-labelElement-label { - display: table-cell; - vertical-align: top; -} -.oo-ui-checkboxMultioptionWidget .oo-ui-checkboxInputWidget { - width: 1px; -} -.oo-ui-checkboxMultioptionWidget.oo-ui-labelElement > .oo-ui-labelElement-label { - white-space: normal; -} -.oo-ui-checkboxMultioptionWidget.oo-ui-labelElement .oo-ui-labelElement-label { - padding-left: 0.42857143em; -} -.oo-ui-checkboxMultioptionWidget .oo-ui-checkboxInputWidget { - margin-right: 0; -} -.oo-ui-progressBarWidget { - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - max-width: 50em; - background-color: #fff; - border: 1px solid #a2a9b1; - border-radius: 2px; - overflow: hidden; -} -.oo-ui-progressBarWidget-bar { - height: 1em; - -webkit-transition: width 100ms; - -moz-transition: width 100ms; - transition: width 100ms; -} -.oo-ui-progressBarWidget-indeterminate .oo-ui-progressBarWidget-bar { - -webkit-animation: oo-ui-progressBarWidget-slide 2s infinite linear; - -moz-animation: oo-ui-progressBarWidget-slide 2s infinite linear; - animation: oo-ui-progressBarWidget-slide 2s infinite linear; - width: 40%; - -webkit-transform: translate(-25%); - -moz-transform: translate(-25%); - -ms-transform: translate(-25%); - transform: translate(-25%); - border-left-width: 1px; -} -.oo-ui-progressBarWidget.oo-ui-widget-enabled .oo-ui-progressBarWidget-bar { - background-color: #36c; -} -.oo-ui-progressBarWidget.oo-ui-widget-disabled .oo-ui-progressBarWidget-bar { - background-color: #c8ccd1; -} -@-webkit-keyframes oo-ui-progressBarWidget-slide { - from { - -webkit-transform: translate(-100%); - -moz-transform: translate(-100%); - -ms-transform: translate(-100%); - transform: translate(-100%); - } - to { - -webkit-transform: translate(350%); - -moz-transform: translate(350%); - -ms-transform: translate(350%); - transform: translate(350%); - } -} -@-moz-keyframes oo-ui-progressBarWidget-slide { - from { - -webkit-transform: translate(-100%); - -moz-transform: translate(-100%); - -ms-transform: translate(-100%); - transform: translate(-100%); - } - to { - -webkit-transform: translate(350%); - -moz-transform: translate(350%); - -ms-transform: translate(350%); - transform: translate(350%); - } -} -@keyframes oo-ui-progressBarWidget-slide { - from { - -webkit-transform: translate(-100%); - -moz-transform: translate(-100%); - -ms-transform: translate(-100%); - transform: translate(-100%); - } - to { - -webkit-transform: translate(350%); - -moz-transform: translate(350%); - -ms-transform: translate(350%); - transform: translate(350%); - } -} -.oo-ui-numberInputWidget { - display: inline-block; - position: relative; - max-width: 50em; -} -.oo-ui-numberInputWidget-buttoned .oo-ui-buttonWidget, -.oo-ui-numberInputWidget-buttoned .oo-ui-inputWidget-input { - display: table-cell; - height: 100%; -} -.oo-ui-numberInputWidget-field { - display: table; - table-layout: fixed; - width: 100%; -} -.oo-ui-numberInputWidget-buttoned .oo-ui-buttonWidget { - width: 2.64285714em; -} -.oo-ui-numberInputWidget-buttoned .oo-ui-buttonWidget .oo-ui-buttonElement-button { - display: block; - min-width: 37px; - min-height: 2.28571429em; - padding-left: 0; - padding-right: 0; -} -.oo-ui-numberInputWidget-buttoned .oo-ui-buttonWidget .oo-ui-buttonElement-button .oo-ui-iconElement-icon { - left: 0.57142857em; -} -.oo-ui-numberInputWidget-buttoned .oo-ui-inputWidget-input { - border-radius: 0; - max-height: 2.28571429em; -} -.oo-ui-numberInputWidget-minusButton > .oo-ui-buttonElement-button { - border-top-right-radius: 0; - border-bottom-right-radius: 0; - border-right-width: 0; -} -.oo-ui-numberInputWidget-plusButton > .oo-ui-buttonElement-button { - border-top-left-radius: 0; - border-bottom-left-radius: 0; - border-left-width: 0; -} -.oo-ui-numberInputWidget.oo-ui-widget-disabled.oo-ui-numberInputWidget-buttoned .oo-ui-iconElement-icon { - opacity: 1; -} -.oo-ui-defaultOverlay { - position: absolute; - top: 0; - /* @noflip */ - left: 0; -} diff --git a/resources/lib/oojs-ui/oojs-ui-core.js b/resources/lib/oojs-ui/oojs-ui-core.js deleted file mode 100644 index 6f22972dc5..0000000000 --- a/resources/lib/oojs-ui/oojs-ui-core.js +++ /dev/null @@ -1,12487 +0,0 @@ -/*! - * OOUI v0.28.0 - * https://www.mediawiki.org/wiki/OOUI - * - * Copyright 2011–2018 OOUI Team and other contributors. - * Released under the MIT license - * http://oojs.mit-license.org - * - * Date: 2018-08-14T23:16:18Z - */ -( function ( OO ) { - -'use strict'; - -/** - * Namespace for all classes, static methods and static properties. - * - * @class - * @singleton - */ -OO.ui = {}; - -OO.ui.bind = $.proxy; - -/** - * @property {Object} - */ -OO.ui.Keys = { - UNDEFINED: 0, - BACKSPACE: 8, - DELETE: 46, - LEFT: 37, - RIGHT: 39, - UP: 38, - DOWN: 40, - ENTER: 13, - END: 35, - HOME: 36, - TAB: 9, - PAGEUP: 33, - PAGEDOWN: 34, - ESCAPE: 27, - SHIFT: 16, - SPACE: 32 -}; - -/** - * Constants for MouseEvent.which - * - * @property {Object} - */ -OO.ui.MouseButtons = { - LEFT: 1, - MIDDLE: 2, - RIGHT: 3 -}; - -/** - * @property {number} - * @private - */ -OO.ui.elementId = 0; - -/** - * Generate a unique ID for element - * - * @return {string} ID - */ -OO.ui.generateElementId = function () { - OO.ui.elementId++; - return 'ooui-' + OO.ui.elementId; -}; - -/** - * Check if an element is focusable. - * Inspired by :focusable in jQueryUI v1.11.4 - 2015-04-14 - * - * @param {jQuery} $element Element to test - * @return {boolean} Element is focusable - */ -OO.ui.isFocusableElement = function ( $element ) { - var nodeName, - element = $element[ 0 ]; - - // Anything disabled is not focusable - if ( element.disabled ) { - return false; - } - - // Check if the element is visible - if ( !( - // This is quicker than calling $element.is( ':visible' ) - $.expr.pseudos.visible( element ) && - // Check that all parents are visible - !$element.parents().addBack().filter( function () { - return $.css( this, 'visibility' ) === 'hidden'; - } ).length - ) ) { - return false; - } - - // Check if the element is ContentEditable, which is the string 'true' - if ( element.contentEditable === 'true' ) { - return true; - } - - // Anything with a non-negative numeric tabIndex is focusable. - // Use .prop to avoid browser bugs - if ( $element.prop( 'tabIndex' ) >= 0 ) { - return true; - } - - // Some element types are naturally focusable - // (indexOf is much faster than regex in Chrome and about the - // same in FF: https://jsperf.com/regex-vs-indexof-array2) - nodeName = element.nodeName.toLowerCase(); - if ( [ 'input', 'select', 'textarea', 'button', 'object' ].indexOf( nodeName ) !== -1 ) { - return true; - } - - // Links and areas are focusable if they have an href - if ( ( nodeName === 'a' || nodeName === 'area' ) && $element.attr( 'href' ) !== undefined ) { - return true; - } - - return false; -}; - -/** - * Find a focusable child - * - * @param {jQuery} $container Container to search in - * @param {boolean} [backwards] Search backwards - * @return {jQuery} Focusable child, or an empty jQuery object if none found - */ -OO.ui.findFocusable = function ( $container, backwards ) { - var $focusable = $( [] ), - // $focusableCandidates is a superset of things that - // could get matched by isFocusableElement - $focusableCandidates = $container - .find( 'input, select, textarea, button, object, a, area, [contenteditable], [tabindex]' ); - - if ( backwards ) { - $focusableCandidates = Array.prototype.reverse.call( $focusableCandidates ); - } - - $focusableCandidates.each( function () { - var $this = $( this ); - if ( OO.ui.isFocusableElement( $this ) ) { - $focusable = $this; - return false; - } - } ); - return $focusable; -}; - -/** - * Get the user's language and any fallback languages. - * - * These language codes are used to localize user interface elements in the user's language. - * - * In environments that provide a localization system, this function should be overridden to - * return the user's language(s). The default implementation returns English (en) only. - * - * @return {string[]} Language codes, in descending order of priority - */ -OO.ui.getUserLanguages = function () { - return [ 'en' ]; -}; - -/** - * Get a value in an object keyed by language code. - * - * @param {Object.} obj Object keyed by language code - * @param {string|null} [lang] Language code, if omitted or null defaults to any user language - * @param {string} [fallback] Fallback code, used if no matching language can be found - * @return {Mixed} Local value - */ -OO.ui.getLocalValue = function ( obj, lang, fallback ) { - var i, len, langs; - - // Requested language - if ( obj[ lang ] ) { - return obj[ lang ]; - } - // Known user language - langs = OO.ui.getUserLanguages(); - for ( i = 0, len = langs.length; i < len; i++ ) { - lang = langs[ i ]; - if ( obj[ lang ] ) { - return obj[ lang ]; - } - } - // Fallback language - if ( obj[ fallback ] ) { - return obj[ fallback ]; - } - // First existing language - for ( lang in obj ) { - return obj[ lang ]; - } - - return undefined; -}; - -/** - * Check if a node is contained within another node - * - * Similar to jQuery#contains except a list of containers can be supplied - * and a boolean argument allows you to include the container in the match list - * - * @param {HTMLElement|HTMLElement[]} containers Container node(s) to search in - * @param {HTMLElement} contained Node to find - * @param {boolean} [matchContainers] Include the container(s) in the list of nodes to match, otherwise only match descendants - * @return {boolean} The node is in the list of target nodes - */ -OO.ui.contains = function ( containers, contained, matchContainers ) { - var i; - if ( !Array.isArray( containers ) ) { - containers = [ containers ]; - } - for ( i = containers.length - 1; i >= 0; i-- ) { - if ( ( matchContainers && contained === containers[ i ] ) || $.contains( containers[ i ], contained ) ) { - return true; - } - } - return false; -}; - -/** - * Return a function, that, as long as it continues to be invoked, will not - * be triggered. The function will be called after it stops being called for - * N milliseconds. If `immediate` is passed, trigger the function on the - * leading edge, instead of the trailing. - * - * Ported from: http://underscorejs.org/underscore.js - * - * @param {Function} func Function to debounce - * @param {number} [wait=0] Wait period in milliseconds - * @param {boolean} [immediate] Trigger on leading edge - * @return {Function} Debounced function - */ -OO.ui.debounce = function ( func, wait, immediate ) { - var timeout; - return function () { - var context = this, - args = arguments, - later = function () { - timeout = null; - if ( !immediate ) { - func.apply( context, args ); - } - }; - if ( immediate && !timeout ) { - func.apply( context, args ); - } - if ( !timeout || wait ) { - clearTimeout( timeout ); - timeout = setTimeout( later, wait ); - } - }; -}; - -/** - * Puts a console warning with provided message. - * - * @param {string} message Message - */ -OO.ui.warnDeprecation = function ( message ) { - if ( OO.getProp( window, 'console', 'warn' ) !== undefined ) { - // eslint-disable-next-line no-console - console.warn( message ); - } -}; - -/** - * Returns a function, that, when invoked, will only be triggered at most once - * during a given window of time. If called again during that window, it will - * wait until the window ends and then trigger itself again. - * - * As it's not knowable to the caller whether the function will actually run - * when the wrapper is called, return values from the function are entirely - * discarded. - * - * @param {Function} func Function to throttle - * @param {number} wait Throttle window length, in milliseconds - * @return {Function} Throttled function - */ -OO.ui.throttle = function ( func, wait ) { - var context, args, timeout, - previous = 0, - run = function () { - timeout = null; - previous = OO.ui.now(); - func.apply( context, args ); - }; - return function () { - // Check how long it's been since the last time the function was - // called, and whether it's more or less than the requested throttle - // period. If it's less, run the function immediately. If it's more, - // set a timeout for the remaining time -- but don't replace an - // existing timeout, since that'd indefinitely prolong the wait. - var remaining = wait - ( OO.ui.now() - previous ); - context = this; - args = arguments; - if ( remaining <= 0 ) { - // Note: unless wait was ridiculously large, this means we'll - // automatically run the first time the function was called in a - // given period. (If you provide a wait period larger than the - // current Unix timestamp, you *deserve* unexpected behavior.) - clearTimeout( timeout ); - run(); - } else if ( !timeout ) { - timeout = setTimeout( run, remaining ); - } - }; -}; - -/** - * A (possibly faster) way to get the current timestamp as an integer - * - * @return {number} Current timestamp, in milliseconds since the Unix epoch - */ -OO.ui.now = Date.now || function () { - return new Date().getTime(); -}; - -/** - * Reconstitute a JavaScript object corresponding to a widget created by - * the PHP implementation. - * - * This is an alias for `OO.ui.Element.static.infuse()`. - * - * @param {string|HTMLElement|jQuery} idOrNode - * A DOM id (if a string) or node for the widget to infuse. - * @param {Object} [config] Configuration options - * @return {OO.ui.Element} - * The `OO.ui.Element` corresponding to this (infusable) document node. - */ -OO.ui.infuse = function ( idOrNode, config ) { - return OO.ui.Element.static.infuse( idOrNode, config ); -}; - -( function () { - /** - * Message store for the default implementation of OO.ui.msg - * - * Environments that provide a localization system should not use this, but should override - * OO.ui.msg altogether. - * - * @private - */ - var messages = { - // Tool tip for a button that moves items in a list down one place - 'ooui-outline-control-move-down': 'Move item down', - // Tool tip for a button that moves items in a list up one place - 'ooui-outline-control-move-up': 'Move item up', - // Tool tip for a button that removes items from a list - 'ooui-outline-control-remove': 'Remove item', - // Label for the toolbar group that contains a list of all other available tools - 'ooui-toolbar-more': 'More', - // Label for the fake tool that expands the full list of tools in a toolbar group - 'ooui-toolgroup-expand': 'More', - // Label for the fake tool that collapses the full list of tools in a toolbar group - 'ooui-toolgroup-collapse': 'Fewer', - // Default label for the tooltip for the button that removes a tag item - 'ooui-item-remove': 'Remove', - // Default label for the accept button of a confirmation dialog - 'ooui-dialog-message-accept': 'OK', - // Default label for the reject button of a confirmation dialog - 'ooui-dialog-message-reject': 'Cancel', - // Title for process dialog error description - 'ooui-dialog-process-error': 'Something went wrong', - // Label for process dialog dismiss error button, visible when describing errors - 'ooui-dialog-process-dismiss': 'Dismiss', - // Label for process dialog retry action button, visible when describing only recoverable errors - 'ooui-dialog-process-retry': 'Try again', - // Label for process dialog retry action button, visible when describing only warnings - 'ooui-dialog-process-continue': 'Continue', - // Label for the file selection widget's select file button - 'ooui-selectfile-button-select': 'Select a file', - // Label for the file selection widget if file selection is not supported - 'ooui-selectfile-not-supported': 'File selection is not supported', - // Label for the file selection widget when no file is currently selected - 'ooui-selectfile-placeholder': 'No file is selected', - // Label for the file selection widget's drop target - 'ooui-selectfile-dragdrop-placeholder': 'Drop file here' - }; - - /** - * Get a localized message. - * - * After the message key, message parameters may optionally be passed. In the default implementation, - * any occurrences of $1 are replaced with the first parameter, $2 with the second parameter, etc. - * Alternative implementations of OO.ui.msg may use any substitution system they like, as long as - * they support unnamed, ordered message parameters. - * - * In environments that provide a localization system, this function should be overridden to - * return the message translated in the user's language. The default implementation always returns - * English messages. An example of doing this with [jQuery.i18n](https://github.com/wikimedia/jquery.i18n) - * follows. - * - * @example - * var i, iLen, button, - * messagePath = 'oojs-ui/dist/i18n/', - * languages = [ $.i18n().locale, 'ur', 'en' ], - * languageMap = {}; - * - * for ( i = 0, iLen = languages.length; i < iLen; i++ ) { - * languageMap[ languages[ i ] ] = messagePath + languages[ i ].toLowerCase() + '.json'; - * } - * - * $.i18n().load( languageMap ).done( function() { - * // Replace the built-in `msg` only once we've loaded the internationalization. - * // OOUI uses `OO.ui.deferMsg` for all initially-loaded messages. So long as - * // you put off creating any widgets until this promise is complete, no English - * // will be displayed. - * OO.ui.msg = $.i18n; - * - * // A button displaying "OK" in the default locale - * button = new OO.ui.ButtonWidget( { - * label: OO.ui.msg( 'ooui-dialog-message-accept' ), - * icon: 'check' - * } ); - * $( 'body' ).append( button.$element ); - * - * // A button displaying "OK" in Urdu - * $.i18n().locale = 'ur'; - * button = new OO.ui.ButtonWidget( { - * label: OO.ui.msg( 'ooui-dialog-message-accept' ), - * icon: 'check' - * } ); - * $( 'body' ).append( button.$element ); - * } ); - * - * @param {string} key Message key - * @param {...Mixed} [params] Message parameters - * @return {string} Translated message with parameters substituted - */ - OO.ui.msg = function ( key ) { - var message = messages[ key ], - params = Array.prototype.slice.call( arguments, 1 ); - if ( typeof message === 'string' ) { - // Perform $1 substitution - message = message.replace( /\$(\d+)/g, function ( unused, n ) { - var i = parseInt( n, 10 ); - return params[ i - 1 ] !== undefined ? params[ i - 1 ] : '$' + n; - } ); - } else { - // Return placeholder if message not found - message = '[' + key + ']'; - } - return message; - }; -}() ); - -/** - * Package a message and arguments for deferred resolution. - * - * Use this when you are statically specifying a message and the message may not yet be present. - * - * @param {string} key Message key - * @param {...Mixed} [params] Message parameters - * @return {Function} Function that returns the resolved message when executed - */ -OO.ui.deferMsg = function () { - var args = arguments; - return function () { - return OO.ui.msg.apply( OO.ui, args ); - }; -}; - -/** - * Resolve a message. - * - * If the message is a function it will be executed, otherwise it will pass through directly. - * - * @param {Function|string} msg Deferred message, or message text - * @return {string} Resolved message - */ -OO.ui.resolveMsg = function ( msg ) { - if ( $.isFunction( msg ) ) { - return msg(); - } - return msg; -}; - -/** - * @param {string} url - * @return {boolean} - */ -OO.ui.isSafeUrl = function ( url ) { - // Keep this function in sync with php/Tag.php - var i, protocolWhitelist; - - function stringStartsWith( haystack, needle ) { - return haystack.substr( 0, needle.length ) === needle; - } - - protocolWhitelist = [ - 'bitcoin', 'ftp', 'ftps', 'geo', 'git', 'gopher', 'http', 'https', 'irc', 'ircs', - 'magnet', 'mailto', 'mms', 'news', 'nntp', 'redis', 'sftp', 'sip', 'sips', 'sms', 'ssh', - 'svn', 'tel', 'telnet', 'urn', 'worldwind', 'xmpp' - ]; - - if ( url === '' ) { - return true; - } - - for ( i = 0; i < protocolWhitelist.length; i++ ) { - if ( stringStartsWith( url, protocolWhitelist[ i ] + ':' ) ) { - return true; - } - } - - // This matches '//' too - if ( stringStartsWith( url, '/' ) || stringStartsWith( url, './' ) ) { - return true; - } - if ( stringStartsWith( url, '?' ) || stringStartsWith( url, '#' ) ) { - return true; - } - - return false; -}; - -/** - * Check if the user has a 'mobile' device. - * - * For our purposes this means the user is primarily using an - * on-screen keyboard, touch input instead of a mouse and may - * have a physically small display. - * - * It is left up to implementors to decide how to compute this - * so the default implementation always returns false. - * - * @return {boolean} User is on a mobile device - */ -OO.ui.isMobile = function () { - return false; -}; - -/** - * Get the additional spacing that should be taken into account when displaying elements that are - * clipped to the viewport, e.g. dropdown menus and popups. This is meant to be overridden to avoid - * such menus overlapping any fixed headers/toolbars/navigation used by the site. - * - * @return {Object} Object with the properties 'top', 'right', 'bottom', 'left', each representing - * the extra spacing from that edge of viewport (in pixels) - */ -OO.ui.getViewportSpacing = function () { - return { - top: 0, - right: 0, - bottom: 0, - left: 0 - }; -}; - -/** - * Get the default overlay, which is used by various widgets when they are passed `$overlay: true`. - * See . - * - * @return {jQuery} Default overlay node - */ -OO.ui.getDefaultOverlay = function () { - if ( !OO.ui.$defaultOverlay ) { - OO.ui.$defaultOverlay = $( '
' ).addClass( 'oo-ui-defaultOverlay' ); - $( 'body' ).append( OO.ui.$defaultOverlay ); - } - return OO.ui.$defaultOverlay; -}; - -/*! - * Mixin namespace. - */ - -/** - * Namespace for OOUI mixins. - * - * Mixins are named according to the type of object they are intended to - * be mixed in to. For example, OO.ui.mixin.GroupElement is intended to be - * mixed in to an instance of OO.ui.Element, and OO.ui.mixin.GroupWidget - * is intended to be mixed in to an instance of OO.ui.Widget. - * - * @class - * @singleton - */ -OO.ui.mixin = {}; - -/** - * Each Element represents a rendering in the DOM—a button or an icon, for example, or anything - * that is visible to a user. Unlike {@link OO.ui.Widget widgets}, plain elements usually do not have events - * connected to them and can't be interacted with. - * - * @abstract - * @class - * - * @constructor - * @param {Object} [config] Configuration options - * @cfg {string[]} [classes] The names of the CSS classes to apply to the element. CSS styles are added - * to the top level (e.g., the outermost div) of the element. See the [OOUI documentation on MediaWiki][2] - * for an example. - * [2]: https://www.mediawiki.org/wiki/OOUI/Widgets/Buttons_and_Switches#cssExample - * @cfg {string} [id] The HTML id attribute used in the rendered tag. - * @cfg {string} [text] Text to insert - * @cfg {Array} [content] An array of content elements to append (after #text). - * Strings will be html-escaped; use an OO.ui.HtmlSnippet to append raw HTML. - * Instances of OO.ui.Element will have their $element appended. - * @cfg {jQuery} [$content] Content elements to append (after #text). - * @cfg {jQuery} [$element] Wrapper element. Defaults to a new element with #getTagName. - * @cfg {Mixed} [data] Custom data of any type or combination of types (e.g., string, number, array, object). - * Data can also be specified with the #setData method. - */ -OO.ui.Element = function OoUiElement( config ) { - if ( OO.ui.isDemo ) { - this.initialConfig = config; - } - // Configuration initialization - config = config || {}; - - // Properties - this.$ = $; - this.elementId = null; - this.visible = true; - this.data = config.data; - this.$element = config.$element || - $( document.createElement( this.getTagName() ) ); - this.elementGroup = null; - - // Initialization - if ( Array.isArray( config.classes ) ) { - this.$element.addClass( config.classes.join( ' ' ) ); - } - if ( config.id ) { - this.setElementId( config.id ); - } - if ( config.text ) { - this.$element.text( config.text ); - } - if ( config.content ) { - // The `content` property treats plain strings as text; use an - // HtmlSnippet to append HTML content. `OO.ui.Element`s get their - // appropriate $element appended. - this.$element.append( config.content.map( function ( v ) { - if ( typeof v === 'string' ) { - // Escape string so it is properly represented in HTML. - return document.createTextNode( v ); - } else if ( v instanceof OO.ui.HtmlSnippet ) { - // Bypass escaping. - return v.toString(); - } else if ( v instanceof OO.ui.Element ) { - return v.$element; - } - return v; - } ) ); - } - if ( config.$content ) { - // The `$content` property treats plain strings as HTML. - this.$element.append( config.$content ); - } -}; - -/* Setup */ - -OO.initClass( OO.ui.Element ); - -/* Static Properties */ - -/** - * The name of the HTML tag used by the element. - * - * The static value may be ignored if the #getTagName method is overridden. - * - * @static - * @inheritable - * @property {string} - */ -OO.ui.Element.static.tagName = 'div'; - -/* Static Methods */ - -/** - * Reconstitute a JavaScript object corresponding to a widget created - * by the PHP implementation. - * - * @param {string|HTMLElement|jQuery} idOrNode - * A DOM id (if a string) or node for the widget to infuse. - * @param {Object} [config] Configuration options - * @return {OO.ui.Element} - * The `OO.ui.Element` corresponding to this (infusable) document node. - * For `Tag` objects emitted on the HTML side (used occasionally for content) - * the value returned is a newly-created Element wrapping around the existing - * DOM node. - */ -OO.ui.Element.static.infuse = function ( idOrNode, config ) { - var obj = OO.ui.Element.static.unsafeInfuse( idOrNode, config, false ); - // Verify that the type matches up. - // FIXME: uncomment after T89721 is fixed, see T90929. - /* - if ( !( obj instanceof this['class'] ) ) { - throw new Error( 'Infusion type mismatch!' ); - } - */ - return obj; -}; - -/** - * Implementation helper for `infuse`; skips the type check and has an - * extra property so that only the top-level invocation touches the DOM. - * - * @private - * @param {string|HTMLElement|jQuery} idOrNode - * @param {Object} [config] Configuration options - * @param {jQuery.Promise} [domPromise] A promise that will be resolved - * when the top-level widget of this infusion is inserted into DOM, - * replacing the original node; only used internally. - * @return {OO.ui.Element} - */ -OO.ui.Element.static.unsafeInfuse = function ( idOrNode, config, domPromise ) { - // look for a cached result of a previous infusion. - var id, $elem, error, data, cls, parts, parent, obj, top, state, infusedChildren; - if ( typeof idOrNode === 'string' ) { - id = idOrNode; - $elem = $( document.getElementById( id ) ); - } else { - $elem = $( idOrNode ); - id = $elem.attr( 'id' ); - } - if ( !$elem.length ) { - if ( typeof idOrNode === 'string' ) { - error = 'Widget not found: ' + idOrNode; - } else if ( idOrNode && idOrNode.selector ) { - error = 'Widget not found: ' + idOrNode.selector; - } else { - error = 'Widget not found'; - } - throw new Error( error ); - } - if ( $elem[ 0 ].oouiInfused ) { - $elem = $elem[ 0 ].oouiInfused; - } - data = $elem.data( 'ooui-infused' ); - if ( data ) { - // cached! - if ( data === true ) { - throw new Error( 'Circular dependency! ' + id ); - } - if ( domPromise ) { - // pick up dynamic state, like focus, value of form inputs, scroll position, etc. - state = data.constructor.static.gatherPreInfuseState( $elem, data ); - // restore dynamic state after the new element is re-inserted into DOM under infused parent - domPromise.done( data.restorePreInfuseState.bind( data, state ) ); - infusedChildren = $elem.data( 'ooui-infused-children' ); - if ( infusedChildren && infusedChildren.length ) { - infusedChildren.forEach( function ( data ) { - var state = data.constructor.static.gatherPreInfuseState( $elem, data ); - domPromise.done( data.restorePreInfuseState.bind( data, state ) ); - } ); - } - } - return data; - } - data = $elem.attr( 'data-ooui' ); - if ( !data ) { - throw new Error( 'No infusion data found: ' + id ); - } - try { - data = JSON.parse( data ); - } catch ( _ ) { - data = null; - } - if ( !( data && data._ ) ) { - throw new Error( 'No valid infusion data found: ' + id ); - } - if ( data._ === 'Tag' ) { - // Special case: this is a raw Tag; wrap existing node, don't rebuild. - return new OO.ui.Element( $.extend( {}, config, { $element: $elem } ) ); - } - parts = data._.split( '.' ); - cls = OO.getProp.apply( OO, [ window ].concat( parts ) ); - if ( cls === undefined ) { - throw new Error( 'Unknown widget type: id: ' + id + ', class: ' + data._ ); - } - - // Verify that we're creating an OO.ui.Element instance - parent = cls.parent; - - while ( parent !== undefined ) { - if ( parent === OO.ui.Element ) { - // Safe - break; - } - - parent = parent.parent; - } - - if ( parent !== OO.ui.Element ) { - throw new Error( 'Unknown widget type: id: ' + id + ', class: ' + data._ ); - } - - if ( !domPromise ) { - top = $.Deferred(); - domPromise = top.promise(); - } - $elem.data( 'ooui-infused', true ); // prevent loops - data.id = id; // implicit - infusedChildren = []; - data = OO.copy( data, null, function deserialize( value ) { - var infused; - if ( OO.isPlainObject( value ) ) { - if ( value.tag ) { - infused = OO.ui.Element.static.unsafeInfuse( value.tag, config, domPromise ); - infusedChildren.push( infused ); - // Flatten the structure - infusedChildren.push.apply( infusedChildren, infused.$element.data( 'ooui-infused-children' ) || [] ); - infused.$element.removeData( 'ooui-infused-children' ); - return infused; - } - if ( value.html !== undefined ) { - return new OO.ui.HtmlSnippet( value.html ); - } - } - } ); - // allow widgets to reuse parts of the DOM - data = cls.static.reusePreInfuseDOM( $elem[ 0 ], data ); - // pick up dynamic state, like focus, value of form inputs, scroll position, etc. - state = cls.static.gatherPreInfuseState( $elem[ 0 ], data ); - // rebuild widget - // eslint-disable-next-line new-cap - obj = new cls( $.extend( {}, config, data ) ); - // If anyone is holding a reference to the old DOM element, - // let's allow them to OO.ui.infuse() it and do what they expect, see T105828. - // Do not use jQuery.data(), as using it on detached nodes leaks memory in 1.x line by design. - $elem[ 0 ].oouiInfused = obj.$element; - // now replace old DOM with this new DOM. - if ( top ) { - // An efficient constructor might be able to reuse the entire DOM tree of the original element, - // so only mutate the DOM if we need to. - if ( $elem[ 0 ] !== obj.$element[ 0 ] ) { - $elem.replaceWith( obj.$element ); - } - top.resolve(); - } - obj.$element.data( 'ooui-infused', obj ); - obj.$element.data( 'ooui-infused-children', infusedChildren ); - // set the 'data-ooui' attribute so we can identify infused widgets - obj.$element.attr( 'data-ooui', '' ); - // restore dynamic state after the new element is inserted into DOM - domPromise.done( obj.restorePreInfuseState.bind( obj, state ) ); - return obj; -}; - -/** - * Pick out parts of `node`'s DOM to be reused when infusing a widget. - * - * This method **must not** make any changes to the DOM, only find interesting pieces and add them - * to `config` (which should then be returned). Actual DOM juggling should then be done by the - * constructor, which will be given the enhanced config. - * - * @protected - * @param {HTMLElement} node - * @param {Object} config - * @return {Object} - */ -OO.ui.Element.static.reusePreInfuseDOM = function ( node, config ) { - return config; -}; - -/** - * Gather the dynamic state (focus, value of form inputs, scroll position, etc.) of an HTML DOM node - * (and its children) that represent an Element of the same class and the given configuration, - * generated by the PHP implementation. - * - * This method is called just before `node` is detached from the DOM. The return value of this - * function will be passed to #restorePreInfuseState after the newly created widget's #$element - * is inserted into DOM to replace `node`. - * - * @protected - * @param {HTMLElement} node - * @param {Object} config - * @return {Object} - */ -OO.ui.Element.static.gatherPreInfuseState = function () { - return {}; -}; - -/** - * Get a jQuery function within a specific document. - * - * @static - * @param {jQuery|HTMLElement|HTMLDocument|Window} context Context to bind the function to - * @param {jQuery} [$iframe] HTML iframe element that contains the document, omit if document is - * not in an iframe - * @return {Function} Bound jQuery function - */ -OO.ui.Element.static.getJQuery = function ( context, $iframe ) { - function wrapper( selector ) { - return $( selector, wrapper.context ); - } - - wrapper.context = this.getDocument( context ); - - if ( $iframe ) { - wrapper.$iframe = $iframe; - } - - return wrapper; -}; - -/** - * Get the document of an element. - * - * @static - * @param {jQuery|HTMLElement|HTMLDocument|Window} obj Object to get the document for - * @return {HTMLDocument|null} Document object - */ -OO.ui.Element.static.getDocument = function ( obj ) { - // jQuery - selections created "offscreen" won't have a context, so .context isn't reliable - return ( obj[ 0 ] && obj[ 0 ].ownerDocument ) || - // Empty jQuery selections might have a context - obj.context || - // HTMLElement - obj.ownerDocument || - // Window - obj.document || - // HTMLDocument - ( obj.nodeType === Node.DOCUMENT_NODE && obj ) || - null; -}; - -/** - * Get the window of an element or document. - * - * @static - * @param {jQuery|HTMLElement|HTMLDocument|Window} obj Context to get the window for - * @return {Window} Window object - */ -OO.ui.Element.static.getWindow = function ( obj ) { - var doc = this.getDocument( obj ); - return doc.defaultView; -}; - -/** - * Get the direction of an element or document. - * - * @static - * @param {jQuery|HTMLElement|HTMLDocument|Window} obj Context to get the direction for - * @return {string} Text direction, either 'ltr' or 'rtl' - */ -OO.ui.Element.static.getDir = function ( obj ) { - var isDoc, isWin; - - if ( obj instanceof jQuery ) { - obj = obj[ 0 ]; - } - isDoc = obj.nodeType === Node.DOCUMENT_NODE; - isWin = obj.document !== undefined; - if ( isDoc || isWin ) { - if ( isWin ) { - obj = obj.document; - } - obj = obj.body; - } - return $( obj ).css( 'direction' ); -}; - -/** - * Get the offset between two frames. - * - * TODO: Make this function not use recursion. - * - * @static - * @param {Window} from Window of the child frame - * @param {Window} [to=window] Window of the parent frame - * @param {Object} [offset] Offset to start with, used internally - * @return {Object} Offset object, containing left and top properties - */ -OO.ui.Element.static.getFrameOffset = function ( from, to, offset ) { - var i, len, frames, frame, rect; - - if ( !to ) { - to = window; - } - if ( !offset ) { - offset = { top: 0, left: 0 }; - } - if ( from.parent === from ) { - return offset; - } - - // Get iframe element - frames = from.parent.document.getElementsByTagName( 'iframe' ); - for ( i = 0, len = frames.length; i < len; i++ ) { - if ( frames[ i ].contentWindow === from ) { - frame = frames[ i ]; - break; - } - } - - // Recursively accumulate offset values - if ( frame ) { - rect = frame.getBoundingClientRect(); - offset.left += rect.left; - offset.top += rect.top; - if ( from !== to ) { - this.getFrameOffset( from.parent, offset ); - } - } - return offset; -}; - -/** - * Get the offset between two elements. - * - * The two elements may be in a different frame, but in that case the frame $element is in must - * be contained in the frame $anchor is in. - * - * @static - * @param {jQuery} $element Element whose position to get - * @param {jQuery} $anchor Element to get $element's position relative to - * @return {Object} Translated position coordinates, containing top and left properties - */ -OO.ui.Element.static.getRelativePosition = function ( $element, $anchor ) { - var iframe, iframePos, - pos = $element.offset(), - anchorPos = $anchor.offset(), - elementDocument = this.getDocument( $element ), - anchorDocument = this.getDocument( $anchor ); - - // If $element isn't in the same document as $anchor, traverse up - while ( elementDocument !== anchorDocument ) { - iframe = elementDocument.defaultView.frameElement; - if ( !iframe ) { - throw new Error( '$element frame is not contained in $anchor frame' ); - } - iframePos = $( iframe ).offset(); - pos.left += iframePos.left; - pos.top += iframePos.top; - elementDocument = iframe.ownerDocument; - } - pos.left -= anchorPos.left; - pos.top -= anchorPos.top; - return pos; -}; - -/** - * Get element border sizes. - * - * @static - * @param {HTMLElement} el Element to measure - * @return {Object} Dimensions object with `top`, `left`, `bottom` and `right` properties - */ -OO.ui.Element.static.getBorders = function ( el ) { - var doc = el.ownerDocument, - win = doc.defaultView, - style = win.getComputedStyle( el, null ), - $el = $( el ), - top = parseFloat( style ? style.borderTopWidth : $el.css( 'borderTopWidth' ) ) || 0, - left = parseFloat( style ? style.borderLeftWidth : $el.css( 'borderLeftWidth' ) ) || 0, - bottom = parseFloat( style ? style.borderBottomWidth : $el.css( 'borderBottomWidth' ) ) || 0, - right = parseFloat( style ? style.borderRightWidth : $el.css( 'borderRightWidth' ) ) || 0; - - return { - top: top, - left: left, - bottom: bottom, - right: right - }; -}; - -/** - * Get dimensions of an element or window. - * - * @static - * @param {HTMLElement|Window} el Element to measure - * @return {Object} Dimensions object with `borders`, `scroll`, `scrollbar` and `rect` properties - */ -OO.ui.Element.static.getDimensions = function ( el ) { - var $el, $win, - doc = el.ownerDocument || el.document, - win = doc.defaultView; - - if ( win === el || el === doc.documentElement ) { - $win = $( win ); - return { - borders: { top: 0, left: 0, bottom: 0, right: 0 }, - scroll: { - top: $win.scrollTop(), - left: $win.scrollLeft() - }, - scrollbar: { right: 0, bottom: 0 }, - rect: { - top: 0, - left: 0, - bottom: $win.innerHeight(), - right: $win.innerWidth() - } - }; - } else { - $el = $( el ); - return { - borders: this.getBorders( el ), - scroll: { - top: $el.scrollTop(), - left: $el.scrollLeft() - }, - scrollbar: { - right: $el.innerWidth() - el.clientWidth, - bottom: $el.innerHeight() - el.clientHeight - }, - rect: el.getBoundingClientRect() - }; - } -}; - -/** - * Get the number of pixels that an element's content is scrolled to the left. - * - * Adapted from . - * Original code copyright 2012 Wei-Ko Kao, licensed under the MIT License. - * - * This function smooths out browser inconsistencies (nicely described in the README at - * ) and produces a result consistent - * with Firefox's 'scrollLeft', which seems the sanest. - * - * @static - * @method - * @param {HTMLElement|Window} el Element to measure - * @return {number} Scroll position from the left. - * If the element's direction is LTR, this is a positive number between `0` (initial scroll position) - * and `el.scrollWidth - el.clientWidth` (furthest possible scroll position). - * If the element's direction is RTL, this is a negative number between `0` (initial scroll position) - * and `-el.scrollWidth + el.clientWidth` (furthest possible scroll position). - */ -OO.ui.Element.static.getScrollLeft = ( function () { - var rtlScrollType = null; - - function test() { - var $definer = $( '
A
' ), - definer = $definer[ 0 ]; - - $definer.appendTo( 'body' ); - if ( definer.scrollLeft > 0 ) { - // Safari, Chrome - rtlScrollType = 'default'; - } else { - definer.scrollLeft = 1; - if ( definer.scrollLeft === 0 ) { - // Firefox, old Opera - rtlScrollType = 'negative'; - } else { - // Internet Explorer, Edge - rtlScrollType = 'reverse'; - } - } - $definer.remove(); - } - - return function getScrollLeft( el ) { - var isRoot = el.window === el || - el === el.ownerDocument.body || - el === el.ownerDocument.documentElement, - scrollLeft = isRoot ? $( window ).scrollLeft() : el.scrollLeft, - // All browsers use the correct scroll type ('negative') on the root, so don't - // do any fixups when looking at the root element - direction = isRoot ? 'ltr' : $( el ).css( 'direction' ); - - if ( direction === 'rtl' ) { - if ( rtlScrollType === null ) { - test(); - } - if ( rtlScrollType === 'reverse' ) { - scrollLeft = -scrollLeft; - } else if ( rtlScrollType === 'default' ) { - scrollLeft = scrollLeft - el.scrollWidth + el.clientWidth; - } - } - - return scrollLeft; - }; -}() ); - -/** - * Get the root scrollable element of given element's document. - * - * On Blink-based browsers (Chrome etc.), `document.documentElement` can't be used to get or set - * the scrollTop property; instead we have to use `document.body`. Changing and testing the value - * lets us use 'body' or 'documentElement' based on what is working. - * - * https://code.google.com/p/chromium/issues/detail?id=303131 - * - * @static - * @param {HTMLElement} el Element to find root scrollable parent for - * @return {HTMLElement} Scrollable parent, `document.body` or `document.documentElement` - * depending on browser - */ -OO.ui.Element.static.getRootScrollableElement = function ( el ) { - var scrollTop, body; - - if ( OO.ui.scrollableElement === undefined ) { - body = el.ownerDocument.body; - scrollTop = body.scrollTop; - body.scrollTop = 1; - - // In some browsers (observed in Chrome 56 on Linux Mint 18.1), - // body.scrollTop doesn't become exactly 1, but a fractional value like 0.76 - if ( Math.round( body.scrollTop ) === 1 ) { - body.scrollTop = scrollTop; - OO.ui.scrollableElement = 'body'; - } else { - OO.ui.scrollableElement = 'documentElement'; - } - } - - return el.ownerDocument[ OO.ui.scrollableElement ]; -}; - -/** - * Get closest scrollable container. - * - * Traverses up until either a scrollable element or the root is reached, in which case the root - * scrollable element will be returned (see #getRootScrollableElement). - * - * @static - * @param {HTMLElement} el Element to find scrollable container for - * @param {string} [dimension] Dimension of scrolling to look for; `x`, `y` or omit for either - * @return {HTMLElement} Closest scrollable container - */ -OO.ui.Element.static.getClosestScrollableContainer = function ( el, dimension ) { - var i, val, - // Browsers do not correctly return the computed value of 'overflow' when 'overflow-x' and - // 'overflow-y' have different values, so we need to check the separate properties. - props = [ 'overflow-x', 'overflow-y' ], - $parent = $( el ).parent(); - - if ( dimension === 'x' || dimension === 'y' ) { - props = [ 'overflow-' + dimension ]; - } - - // Special case for the document root (which doesn't really have any scrollable container, since - // it is the ultimate scrollable container, but this is probably saner than null or exception) - if ( $( el ).is( 'html, body' ) ) { - return this.getRootScrollableElement( el ); - } - - while ( $parent.length ) { - if ( $parent[ 0 ] === this.getRootScrollableElement( el ) ) { - return $parent[ 0 ]; - } - i = props.length; - while ( i-- ) { - val = $parent.css( props[ i ] ); - // We assume that elements with 'overflow' (in any direction) set to 'hidden' will never be - // scrolled in that direction, but they can actually be scrolled programatically. The user can - // unintentionally perform a scroll in such case even if the application doesn't scroll - // programatically, e.g. when jumping to an anchor, or when using built-in find functionality. - // This could cause funny issues... - if ( val === 'auto' || val === 'scroll' ) { - return $parent[ 0 ]; - } - } - $parent = $parent.parent(); - } - // The element is unattached... return something mostly sane - return this.getRootScrollableElement( el ); -}; - -/** - * Scroll element into view. - * - * @static - * @param {HTMLElement} el Element to scroll into view - * @param {Object} [config] Configuration options - * @param {string} [config.duration='fast'] jQuery animation duration value - * @param {string} [config.direction] Scroll in only one direction, e.g. 'x' or 'y', omit - * to scroll in both directions - * @return {jQuery.Promise} Promise which resolves when the scroll is complete - */ -OO.ui.Element.static.scrollIntoView = function ( el, config ) { - var position, animations, container, $container, elementDimensions, containerDimensions, $window, - deferred = $.Deferred(); - - // Configuration initialization - config = config || {}; - - animations = {}; - container = this.getClosestScrollableContainer( el, config.direction ); - $container = $( container ); - elementDimensions = this.getDimensions( el ); - containerDimensions = this.getDimensions( container ); - $window = $( this.getWindow( el ) ); - - // Compute the element's position relative to the container - if ( $container.is( 'html, body' ) ) { - // If the scrollable container is the root, this is easy - position = { - top: elementDimensions.rect.top, - bottom: $window.innerHeight() - elementDimensions.rect.bottom, - left: elementDimensions.rect.left, - right: $window.innerWidth() - elementDimensions.rect.right - }; - } else { - // Otherwise, we have to subtract el's coordinates from container's coordinates - position = { - top: elementDimensions.rect.top - ( containerDimensions.rect.top + containerDimensions.borders.top ), - bottom: containerDimensions.rect.bottom - containerDimensions.borders.bottom - containerDimensions.scrollbar.bottom - elementDimensions.rect.bottom, - left: elementDimensions.rect.left - ( containerDimensions.rect.left + containerDimensions.borders.left ), - right: containerDimensions.rect.right - containerDimensions.borders.right - containerDimensions.scrollbar.right - elementDimensions.rect.right - }; - } - - if ( !config.direction || config.direction === 'y' ) { - if ( position.top < 0 ) { - animations.scrollTop = containerDimensions.scroll.top + position.top; - } else if ( position.top > 0 && position.bottom < 0 ) { - animations.scrollTop = containerDimensions.scroll.top + Math.min( position.top, -position.bottom ); - } - } - if ( !config.direction || config.direction === 'x' ) { - if ( position.left < 0 ) { - animations.scrollLeft = containerDimensions.scroll.left + position.left; - } else if ( position.left > 0 && position.right < 0 ) { - animations.scrollLeft = containerDimensions.scroll.left + Math.min( position.left, -position.right ); - } - } - if ( !$.isEmptyObject( animations ) ) { - $container.stop( true ).animate( animations, config.duration === undefined ? 'fast' : config.duration ); - $container.queue( function ( next ) { - deferred.resolve(); - next(); - } ); - } else { - deferred.resolve(); - } - return deferred.promise(); -}; - -/** - * Force the browser to reconsider whether it really needs to render scrollbars inside the element - * and reserve space for them, because it probably doesn't. - * - * Workaround primarily for , but also - * similar bugs in other browsers. "Just" forcing a reflow is not sufficient in all cases, we need - * to first actually detach (or hide, but detaching is simpler) all children, *then* force a reflow, - * and then reattach (or show) them back. - * - * @static - * @param {HTMLElement} el Element to reconsider the scrollbars on - */ -OO.ui.Element.static.reconsiderScrollbars = function ( el ) { - var i, len, scrollLeft, scrollTop, nodes = []; - // Save scroll position - scrollLeft = el.scrollLeft; - scrollTop = el.scrollTop; - // Detach all children - while ( el.firstChild ) { - nodes.push( el.firstChild ); - el.removeChild( el.firstChild ); - } - // Force reflow - void el.offsetHeight; - // Reattach all children - for ( i = 0, len = nodes.length; i < len; i++ ) { - el.appendChild( nodes[ i ] ); - } - // Restore scroll position (no-op if scrollbars disappeared) - el.scrollLeft = scrollLeft; - el.scrollTop = scrollTop; -}; - -/* Methods */ - -/** - * Toggle visibility of an element. - * - * @param {boolean} [show] Make element visible, omit to toggle visibility - * @fires visible - * @chainable - */ -OO.ui.Element.prototype.toggle = function ( show ) { - show = show === undefined ? !this.visible : !!show; - - if ( show !== this.isVisible() ) { - this.visible = show; - this.$element.toggleClass( 'oo-ui-element-hidden', !this.visible ); - this.emit( 'toggle', show ); - } - - return this; -}; - -/** - * Check if element is visible. - * - * @return {boolean} element is visible - */ -OO.ui.Element.prototype.isVisible = function () { - return this.visible; -}; - -/** - * Get element data. - * - * @return {Mixed} Element data - */ -OO.ui.Element.prototype.getData = function () { - return this.data; -}; - -/** - * Set element data. - * - * @param {Mixed} data Element data - * @chainable - */ -OO.ui.Element.prototype.setData = function ( data ) { - this.data = data; - return this; -}; - -/** - * Set the element has an 'id' attribute. - * - * @param {string} id - * @chainable - */ -OO.ui.Element.prototype.setElementId = function ( id ) { - this.elementId = id; - this.$element.attr( 'id', id ); - return this; -}; - -/** - * Ensure that the element has an 'id' attribute, setting it to an unique value if it's missing, - * and return its value. - * - * @return {string} - */ -OO.ui.Element.prototype.getElementId = function () { - if ( this.elementId === null ) { - this.setElementId( OO.ui.generateElementId() ); - } - return this.elementId; -}; - -/** - * Check if element supports one or more methods. - * - * @param {string|string[]} methods Method or list of methods to check - * @return {boolean} All methods are supported - */ -OO.ui.Element.prototype.supports = function ( methods ) { - var i, len, - support = 0; - - methods = Array.isArray( methods ) ? methods : [ methods ]; - for ( i = 0, len = methods.length; i < len; i++ ) { - if ( $.isFunction( this[ methods[ i ] ] ) ) { - support++; - } - } - - return methods.length === support; -}; - -/** - * Update the theme-provided classes. - * - * @localdoc This is called in element mixins and widget classes any time state changes. - * Updating is debounced, minimizing overhead of changing multiple attributes and - * guaranteeing that theme updates do not occur within an element's constructor - */ -OO.ui.Element.prototype.updateThemeClasses = function () { - OO.ui.theme.queueUpdateElementClasses( this ); -}; - -/** - * Get the HTML tag name. - * - * Override this method to base the result on instance information. - * - * @return {string} HTML tag name - */ -OO.ui.Element.prototype.getTagName = function () { - return this.constructor.static.tagName; -}; - -/** - * Check if the element is attached to the DOM - * - * @return {boolean} The element is attached to the DOM - */ -OO.ui.Element.prototype.isElementAttached = function () { - return $.contains( this.getElementDocument(), this.$element[ 0 ] ); -}; - -/** - * Get the DOM document. - * - * @return {HTMLDocument} Document object - */ -OO.ui.Element.prototype.getElementDocument = function () { - // Don't cache this in other ways either because subclasses could can change this.$element - return OO.ui.Element.static.getDocument( this.$element ); -}; - -/** - * Get the DOM window. - * - * @return {Window} Window object - */ -OO.ui.Element.prototype.getElementWindow = function () { - return OO.ui.Element.static.getWindow( this.$element ); -}; - -/** - * Get closest scrollable container. - * - * @return {HTMLElement} Closest scrollable container - */ -OO.ui.Element.prototype.getClosestScrollableElementContainer = function () { - return OO.ui.Element.static.getClosestScrollableContainer( this.$element[ 0 ] ); -}; - -/** - * Get group element is in. - * - * @return {OO.ui.mixin.GroupElement|null} Group element, null if none - */ -OO.ui.Element.prototype.getElementGroup = function () { - return this.elementGroup; -}; - -/** - * Set group element is in. - * - * @param {OO.ui.mixin.GroupElement|null} group Group element, null if none - * @chainable - */ -OO.ui.Element.prototype.setElementGroup = function ( group ) { - this.elementGroup = group; - return this; -}; - -/** - * Scroll element into view. - * - * @param {Object} [config] Configuration options - * @return {jQuery.Promise} Promise which resolves when the scroll is complete - */ -OO.ui.Element.prototype.scrollElementIntoView = function ( config ) { - if ( - !this.isElementAttached() || - !this.isVisible() || - ( this.getElementGroup() && !this.getElementGroup().isVisible() ) - ) { - return $.Deferred().resolve(); - } - return OO.ui.Element.static.scrollIntoView( this.$element[ 0 ], config ); -}; - -/** - * Restore the pre-infusion dynamic state for this widget. - * - * This method is called after #$element has been inserted into DOM. The parameter is the return - * value of #gatherPreInfuseState. - * - * @protected - * @param {Object} state - */ -OO.ui.Element.prototype.restorePreInfuseState = function () { -}; - -/** - * Wraps an HTML snippet for use with configuration values which default - * to strings. This bypasses the default html-escaping done to string - * values. - * - * @class - * - * @constructor - * @param {string} [content] HTML content - */ -OO.ui.HtmlSnippet = function OoUiHtmlSnippet( content ) { - // Properties - this.content = content; -}; - -/* Setup */ - -OO.initClass( OO.ui.HtmlSnippet ); - -/* Methods */ - -/** - * Render into HTML. - * - * @return {string} Unchanged HTML snippet. - */ -OO.ui.HtmlSnippet.prototype.toString = function () { - return this.content; -}; - -/** - * Layouts are containers for elements and are used to arrange other widgets of arbitrary type in a way - * that is centrally controlled and can be updated dynamically. Layouts can be, and usually are, combined. - * See {@link OO.ui.FieldsetLayout FieldsetLayout}, {@link OO.ui.FieldLayout FieldLayout}, {@link OO.ui.FormLayout FormLayout}, - * {@link OO.ui.PanelLayout PanelLayout}, {@link OO.ui.StackLayout StackLayout}, {@link OO.ui.PageLayout PageLayout}, - * {@link OO.ui.HorizontalLayout HorizontalLayout}, and {@link OO.ui.BookletLayout BookletLayout} for more information and examples. - * - * @abstract - * @class - * @extends OO.ui.Element - * @mixins OO.EventEmitter - * - * @constructor - * @param {Object} [config] Configuration options - */ -OO.ui.Layout = function OoUiLayout( config ) { - // Configuration initialization - config = config || {}; - - // Parent constructor - OO.ui.Layout.parent.call( this, config ); - - // Mixin constructors - OO.EventEmitter.call( this ); - - // Initialization - this.$element.addClass( 'oo-ui-layout' ); -}; - -/* Setup */ - -OO.inheritClass( OO.ui.Layout, OO.ui.Element ); -OO.mixinClass( OO.ui.Layout, OO.EventEmitter ); - -/** - * Widgets are compositions of one or more OOUI elements that users can both view - * and interact with. All widgets can be configured and modified via a standard API, - * and their state can change dynamically according to a model. - * - * @abstract - * @class - * @extends OO.ui.Element - * @mixins OO.EventEmitter - * - * @constructor - * @param {Object} [config] Configuration options - * @cfg {boolean} [disabled=false] Disable the widget. Disabled widgets cannot be used and their - * appearance reflects this state. - */ -OO.ui.Widget = function OoUiWidget( config ) { - // Initialize config - config = $.extend( { disabled: false }, config ); - - // Parent constructor - OO.ui.Widget.parent.call( this, config ); - - // Mixin constructors - OO.EventEmitter.call( this ); - - // Properties - this.disabled = null; - this.wasDisabled = null; - - // Initialization - this.$element.addClass( 'oo-ui-widget' ); - this.setDisabled( !!config.disabled ); -}; - -/* Setup */ - -OO.inheritClass( OO.ui.Widget, OO.ui.Element ); -OO.mixinClass( OO.ui.Widget, OO.EventEmitter ); - -/* Events */ - -/** - * @event disable - * - * A 'disable' event is emitted when the disabled state of the widget changes - * (i.e. on disable **and** enable). - * - * @param {boolean} disabled Widget is disabled - */ - -/** - * @event toggle - * - * A 'toggle' event is emitted when the visibility of the widget changes. - * - * @param {boolean} visible Widget is visible - */ - -/* Methods */ - -/** - * Check if the widget is disabled. - * - * @return {boolean} Widget is disabled - */ -OO.ui.Widget.prototype.isDisabled = function () { - return this.disabled; -}; - -/** - * Set the 'disabled' state of the widget. - * - * When a widget is disabled, it cannot be used and its appearance is updated to reflect this state. - * - * @param {boolean} disabled Disable widget - * @chainable - */ -OO.ui.Widget.prototype.setDisabled = function ( disabled ) { - var isDisabled; - - this.disabled = !!disabled; - isDisabled = this.isDisabled(); - if ( isDisabled !== this.wasDisabled ) { - this.$element.toggleClass( 'oo-ui-widget-disabled', isDisabled ); - this.$element.toggleClass( 'oo-ui-widget-enabled', !isDisabled ); - this.$element.attr( 'aria-disabled', isDisabled.toString() ); - this.emit( 'disable', isDisabled ); - this.updateThemeClasses(); - } - this.wasDisabled = isDisabled; - - return this; -}; - -/** - * Update the disabled state, in case of changes in parent widget. - * - * @chainable - */ -OO.ui.Widget.prototype.updateDisabled = function () { - this.setDisabled( this.disabled ); - return this; -}; - -/** - * Get an ID of a labelable node which is part of this widget, if any, to be used for `