Merge "jquery.ui: Use on() instead of deprecated bind() in jquery.ui.mouse"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Mon, 30 Apr 2018 05:12:17 +0000 (05:12 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Mon, 30 Apr 2018 05:12:17 +0000 (05:12 +0000)
60 files changed:
RELEASE-NOTES-1.31
autoload.php
composer.json
includes/CommentStore.php
includes/GlobalFunctions.php
includes/Storage/RevisionStore.php
includes/Storage/SqlBlobStore.php
includes/api/i18n/he.json
includes/api/i18n/ja.json
includes/installer/i18n/ja.json
includes/installer/i18n/lb.json
includes/mail/UserMailer.php
includes/media/BMP.php [deleted file]
includes/media/Bitmap.php [deleted file]
includes/media/BitmapHandler.php [new file with mode: 0644]
includes/media/BitmapHandler_ClientOnly.php [new file with mode: 0644]
includes/media/Bitmap_ClientOnly.php [deleted file]
includes/media/BmpHandler.php [new file with mode: 0644]
includes/media/DjVu.php [deleted file]
includes/media/DjVuHandler.php [new file with mode: 0644]
includes/media/ExifBitmap.php [deleted file]
includes/media/ExifBitmapHandler.php [new file with mode: 0644]
includes/media/GIF.php [deleted file]
includes/media/GIFHandler.php [new file with mode: 0644]
includes/media/Jpeg.php [deleted file]
includes/media/JpegHandler.php [new file with mode: 0644]
includes/media/PNG.php [deleted file]
includes/media/PNGHandler.php [new file with mode: 0644]
includes/media/SVG.php [deleted file]
includes/media/SvgHandler.php [new file with mode: 0644]
includes/media/Tiff.php [deleted file]
includes/media/TiffHandler.php [new file with mode: 0644]
includes/media/WebP.php [deleted file]
includes/media/WebPHandler.php [new file with mode: 0644]
includes/tidy/RaggettBase.php
includes/user/User.php
includes/watcheditem/WatchedItemStoreInterface.php
languages/i18n/ast.json
languages/i18n/bho.json
languages/i18n/ca.json
languages/i18n/cs.json
languages/i18n/el.json
languages/i18n/es.json
languages/i18n/fr.json
languages/i18n/ha.json
languages/i18n/he.json
languages/i18n/inh.json
languages/i18n/ja.json
languages/i18n/lt.json
languages/i18n/lzh.json
languages/i18n/oc.json
languages/i18n/rm.json
languages/i18n/sr-ec.json
languages/i18n/zgh.json
languages/i18n/zh-hans.json
languages/i18n/zh-hant.json
maintenance/jsduck/eg-iframe.html
resources/src/mediawiki.special/mediawiki.special.upload.js
resources/src/mediawiki/mediawiki.js
resources/src/startup.js

index 1386184..ae59234 100644 (file)
@@ -120,6 +120,7 @@ production.
 * Updated mediawiki/at-ease from 1.1.0 to 1.2.0.
 * Updated wikimedia/php-session-serializer from 1.0.4 to 1.0.6.
 * Updated wikimedia/remex-html from 1.0.2 to 1.0.3.
+* Updated wikimedia/html-formatter from 1.0.1 to 1.0.2.
 * …
 
 ==== New external libraries ====
index 12958ca..b832863 100644 (file)
@@ -201,15 +201,15 @@ $wgAutoloadLocalClasses = [
        'BenchmarkSanitizer' => __DIR__ . '/maintenance/benchmarks/benchmarkSanitizer.php',
        'BenchmarkTidy' => __DIR__ . '/maintenance/benchmarks/benchmarkTidy.php',
        'Benchmarker' => __DIR__ . '/maintenance/benchmarks/Benchmarker.php',
-       'BitmapHandler' => __DIR__ . '/includes/media/Bitmap.php',
-       'BitmapHandler_ClientOnly' => __DIR__ . '/includes/media/Bitmap_ClientOnly.php',
+       'BitmapHandler' => __DIR__ . '/includes/media/BitmapHandler.php',
+       'BitmapHandler_ClientOnly' => __DIR__ . '/includes/media/BitmapHandler_ClientOnly.php',
        'BitmapMetadataHandler' => __DIR__ . '/includes/media/BitmapMetadataHandler.php',
        'Blob' => __DIR__ . '/includes/libs/rdbms/encasing/Blob.php',
        'Block' => __DIR__ . '/includes/Block.php',
        'BlockLevelPass' => __DIR__ . '/includes/parser/BlockLevelPass.php',
        'BlockListPager' => __DIR__ . '/includes/specials/pagers/BlockListPager.php',
        'BlockLogFormatter' => __DIR__ . '/includes/logging/BlockLogFormatter.php',
-       'BmpHandler' => __DIR__ . '/includes/media/BMP.php',
+       'BmpHandler' => __DIR__ . '/includes/media/BmpHandler.php',
        'BotPassword' => __DIR__ . '/includes/user/BotPassword.php',
        'BrokenRedirectsPage' => __DIR__ . '/includes/specials/SpecialBrokenRedirects.php',
        'BufferingStatsdDataFactory' => __DIR__ . '/includes/libs/stats/BufferingStatsdDataFactory.php',
@@ -396,7 +396,7 @@ $wgAutoloadLocalClasses = [
        'DiffOpDelete' => __DIR__ . '/includes/diff/DairikiDiff.php',
        'DifferenceEngine' => __DIR__ . '/includes/diff/DifferenceEngine.php',
        'Digit2Html' => __DIR__ . '/maintenance/language/digit2html.php',
-       'DjVuHandler' => __DIR__ . '/includes/media/DjVu.php',
+       'DjVuHandler' => __DIR__ . '/includes/media/DjVuHandler.php',
        'DjVuImage' => __DIR__ . '/includes/media/DjVuImage.php',
        'DnsSrvDiscoverer' => __DIR__ . '/includes/libs/DnsSrvDiscoverer.php',
        'DoubleRedirectJob' => __DIR__ . '/includes/jobqueue/jobs/DoubleRedirectJob.php',
@@ -452,7 +452,7 @@ $wgAutoloadLocalClasses = [
        'EventRelayerNull' => __DIR__ . '/includes/libs/eventrelayer/EventRelayerNull.php',
        'ExecutableFinder' => __DIR__ . '/includes/utils/ExecutableFinder.php',
        'Exif' => __DIR__ . '/includes/media/Exif.php',
-       'ExifBitmapHandler' => __DIR__ . '/includes/media/ExifBitmap.php',
+       'ExifBitmapHandler' => __DIR__ . '/includes/media/ExifBitmapHandler.php',
        'ExplodeIterator' => __DIR__ . '/includes/libs/ExplodeIterator.php',
        'ExportProgressFilter' => __DIR__ . '/includes/export/ExportProgressFilter.php',
        'ExportSites' => __DIR__ . '/maintenance/exportSites.php',
@@ -537,7 +537,7 @@ $wgAutoloadLocalClasses = [
        'FormatMetadata' => __DIR__ . '/includes/media/FormatMetadata.php',
        'FormattedRCFeed' => __DIR__ . '/includes/rcfeed/FormattedRCFeed.php',
        'FormlessAction' => __DIR__ . '/includes/actions/FormlessAction.php',
-       'GIFHandler' => __DIR__ . '/includes/media/GIF.php',
+       'GIFHandler' => __DIR__ . '/includes/media/GIFHandler.php',
        'GIFMetadataExtractor' => __DIR__ . '/includes/media/GIFMetadataExtractor.php',
        'GanConverter' => __DIR__ . '/languages/classes/LanguageGan.php',
        'GenderCache' => __DIR__ . '/includes/cache/GenderCache.php',
@@ -699,7 +699,7 @@ $wgAutoloadLocalClasses = [
        'JobQueueSecondTestQueue' => __DIR__ . '/includes/jobqueue/JobQueueSecondTestQueue.php',
        'JobRunner' => __DIR__ . '/includes/jobqueue/JobRunner.php',
        'JobSpecification' => __DIR__ . '/includes/jobqueue/JobSpecification.php',
-       'JpegHandler' => __DIR__ . '/includes/media/Jpeg.php',
+       'JpegHandler' => __DIR__ . '/includes/media/JpegHandler.php',
        'JpegMetadataExtractor' => __DIR__ . '/includes/media/JpegMetadataExtractor.php',
        'JsonContent' => __DIR__ . '/includes/content/JsonContent.php',
        'JsonContentHandler' => __DIR__ . '/includes/content/JsonContentHandler.php',
@@ -1098,7 +1098,7 @@ $wgAutoloadLocalClasses = [
        'Orphans' => __DIR__ . '/maintenance/orphans.php',
        'OutputPage' => __DIR__ . '/includes/OutputPage.php',
        'PHPVersionCheck' => __DIR__ . '/includes/PHPVersionCheck.php',
-       'PNGHandler' => __DIR__ . '/includes/media/PNG.php',
+       'PNGHandler' => __DIR__ . '/includes/media/PNGHandler.php',
        'PNGMetadataExtractor' => __DIR__ . '/includes/media/PNGMetadataExtractor.php',
        'PPCustomFrame_DOM' => __DIR__ . '/includes/parser/Preprocessor_DOM.php',
        'PPCustomFrame_Hash' => __DIR__ . '/includes/parser/Preprocessor_Hash.php',
@@ -1506,7 +1506,7 @@ $wgAutoloadLocalClasses = [
        'StubUserLang' => __DIR__ . '/includes/StubObject.php',
        'SubmitAction' => __DIR__ . '/includes/actions/SubmitAction.php',
        'SubpageImportTitleFactory' => __DIR__ . '/includes/title/SubpageImportTitleFactory.php',
-       'SvgHandler' => __DIR__ . '/includes/media/SVG.php',
+       'SvgHandler' => __DIR__ . '/includes/media/SvgHandler.php',
        'SwiftFileBackend' => __DIR__ . '/includes/libs/filebackend/SwiftFileBackend.php',
        'SwiftFileBackendDirList' => __DIR__ . '/includes/libs/filebackend/SwiftFileBackend.php',
        'SwiftFileBackendFileList' => __DIR__ . '/includes/libs/filebackend/SwiftFileBackend.php',
@@ -1532,7 +1532,7 @@ $wgAutoloadLocalClasses = [
        'ThumbnailImage' => __DIR__ . '/includes/media/MediaTransformOutput.php',
        'ThumbnailRenderJob' => __DIR__ . '/includes/jobqueue/jobs/ThumbnailRenderJob.php',
        'TidyUpBug37714' => __DIR__ . '/maintenance/tidyUpBug37714.php',
-       'TiffHandler' => __DIR__ . '/includes/media/Tiff.php',
+       'TiffHandler' => __DIR__ . '/includes/media/TiffHandler.php',
        'Timing' => __DIR__ . '/includes/libs/Timing.php',
        'Title' => __DIR__ . '/includes/Title.php',
        'TitleArray' => __DIR__ . '/includes/TitleArray.php',
@@ -1662,7 +1662,7 @@ $wgAutoloadLocalClasses = [
        'WebInstallerUpgrade' => __DIR__ . '/includes/installer/WebInstallerUpgrade.php',
        'WebInstallerUpgradeDoc' => __DIR__ . '/includes/installer/WebInstallerUpgradeDoc.php',
        'WebInstallerWelcome' => __DIR__ . '/includes/installer/WebInstallerWelcome.php',
-       'WebPHandler' => __DIR__ . '/includes/media/WebP.php',
+       'WebPHandler' => __DIR__ . '/includes/media/WebPHandler.php',
        'WebRequest' => __DIR__ . '/includes/WebRequest.php',
        'WebRequestUpload' => __DIR__ . '/includes/WebRequestUpload.php',
        'WebResponse' => __DIR__ . '/includes/WebResponse.php',
index 8a5c5dd..6e34ec2 100644 (file)
@@ -34,7 +34,7 @@
                "wikimedia/cdb": "1.4.1",
                "wikimedia/cldr-plural-rule-parser": "1.0.0",
                "wikimedia/composer-merge-plugin": "1.4.1",
-               "wikimedia/html-formatter": "1.0.1",
+               "wikimedia/html-formatter": "1.0.2",
                "wikimedia/ip-set": "1.2.0",
                "wikimedia/object-factory": "1.0.0",
                "wikimedia/php-session-serializer": "1.0.6",
index 55f6857..e9b08e8 100644 (file)
@@ -134,7 +134,7 @@ class CommentStore {
        /**
         * Compat method allowing use of self::newKey until removed.
         * @param string|null $methodKey
-        * @throw InvalidArgumentException
+        * @throws InvalidArgumentException
         * @return string
         */
        private function getKey( $methodKey = null ) {
index 519b22c..9569bc1 100644 (file)
@@ -712,6 +712,8 @@ function wfAssembleUrl( $urlParts ) {
  *
  * @todo Need to integrate this into wfExpandUrl (see T34168)
  *
+ * @since 1.19
+ *
  * @param string $urlPath URL path, potentially containing dot-segments
  * @return string URL path with all dot-segments removed
  */
index 1329023..5b3daf4 100644 (file)
@@ -283,7 +283,7 @@ class RevisionStore
         * @param mixed $value
         * @param string $name
         *
-        * @throw IncompleteRevisionException if $value is null
+        * @throws IncompleteRevisionException if $value is null
         * @return mixed $value, if $value is not null
         */
        private function failOnNull( $value, $name ) {
@@ -300,7 +300,7 @@ class RevisionStore
         * @param mixed $value
         * @param string $name
         *
-        * @throw IncompleteRevisionException if $value is empty
+        * @throws IncompleteRevisionException if $value is empty
         * @return mixed $value, if $value is not null
         */
        private function failOnEmpty( $value, $name ) {
@@ -889,7 +889,7 @@ class RevisionStore
         * @param string|null $blobFormat MIME type indicating how $dataBlob is encoded
         * @param int $queryFlags
         *
-        * @throw RevisionAccessException
+        * @throws RevisionAccessException
         * @return Content
         */
        private function loadSlotContent(
index 70d7d42..72de2c9 100644 (file)
@@ -292,7 +292,7 @@ class SqlBlobStore implements IDBAccessObject, BlobStore {
         * @param string $blobAddress
         * @param int $queryFlags
         *
-        * @throw BlobAccessException
+        * @throws BlobAccessException
         * @return string|false
         */
        private function fetchBlob( $blobAddress, $queryFlags ) {
index aaa6aed..8b1ca0f 100644 (file)
@@ -18,7 +18,7 @@
                        "Umherirrender"
                ]
        },
-       "apihelp-main-extended-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:Special:MyLanguage/API:Main_page|ת×\99×¢×\95×\93]]\n* [[mw:Special:MyLanguage/API:FAQ|ש×\95\"ת]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api ×¨×©×\99×\9eת ×\93×\99×\95×\95ר]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce ×\94×\95×\93×¢×\95ת ×¢×\9c API]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R ×\91×\90×\92×\99×\9d ×\95×\91קש×\95ת]\n</div>\n<strong>×\9eצ×\91:</strong> ×\9b×\9c ×\94×\90פשר×\95×\99×\95ת ×©×\9e×\95צ×\92×\95ת ×\91×\93×£ ×\94×\96×\94 ×\90×\9e×\95ר×\95ת ×\9c×¢×\91×\95×\93, ×\90×\91×\9c ×\94Ö¾API ×¢×\93×\99×\99×\9f ×\91פ×\99ת×\95×\97 ×¤×¢×\99×\9c, ×\95×\99×\9b×\95×\9c ×\9c×\94שתנ×\95ת ×\91×\9b×\9c ×\96×\9e×\9f. ×¢×©×\95 ×\9e×\99× ×\95×\99 ×\9c[https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ ×¨×©×\99×\9eת ×\94×\93×\99×\95×\95ר mediawiki-api-announce] ×\9c×\94×\95×\93×¢×\95ת ×¢×\9c ×¢×\93×\9b×\95× ×\99×\9d.\n\n<strong>×\91קש×\95ת ×©×\92×\95×\99×\95ת:</strong> ×\9bש×\91קש×\95ת ×©×\92×\95×\99×\95ת × ×©×\9c×\97×\95ת ×\9cÖ¾API, ×ª×\99ש×\9c×\97 ×\9b×\95תרת HTTP ×¢×\9d ×\94×\9eפת×\97 \"MediaWiki-API-Error\" ×\95×\90×\96 ×\92×\9d ×\94ער×\9a ×©×\9c ×\94×\9b×\95תרת ×\95×\92×\9d ×§×\95×\93 ×\94ש×\92×\99×\90×\94 ×\99×\95×\92×\93ר×\95 ×\9c×\90×\95ת×\95 ×¢×¨×\9a. ×\9c×\9e×\99×\93×¢ × ×\95סף ×¨' [[mw:Special:MyLanguage/API:Errors_and_warnings|API: ×©×\92×\99×\90×\95ת ×\95×\90×\96×\94ר×\95ת]].\n\n<strong>×\91×\93×\99ק×\94:</strong> ×\9c×\91×\93×\99ק×\94 ×§×\9c×\94 ×\99×\95תר ×©×\9c ×\91קש×\95ת ×¨' [[Special:ApiSandbox]].",
+       "apihelp-main-extended-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:Special:MyLanguage/API:Main_page|ת×\99×¢×\95×\93]]\n* [[mw:Special:MyLanguage/API:FAQ|ש×\90×\9c×\95ת × ×¤×\95צ×\95ת]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api ×¨×©×\99×\9eת ×\93×\99×\95×\95ר]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce ×\94×\95×\93×¢×\95ת ×¢×\9c API]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R ×\91×\90×\92×\99×\9d ×\95×\91קש×\95ת]\n</div>\n<strong>×\9eצ×\91:</strong> ×\94Ö¾API ×©×\9c ×\9e×\93×\99×\94Ö¾×\95×\99ק×\99 ×\94×\95×\90 ×\9e×\9eשק ×\95ת×\99ק ×\95×\99צ×\99×\91 ×©× ×ª×\9e×\9a ×\95×\9eשתפר ×\91×\90×\95פ×\9f ×¡×\93×\99ר. ×\9c×\9eר×\95ת ×©×\90× ×\97× ×\95 ×\9eשת×\93×\9c×\99×\9d ×\9c×\94×\99×\9e× ×¢ ×\9e×\9b×\9a, ×\9cעת×\99×\9d ×¢×\9c×\99× ×\95 ×\9c×\91צע ×©×\99× ×\95×\99×\99×\9d ×©×¢×\9c×\95×\9c×\99×\9d ×\9cש×\91ש ×\93×\91ר×\99×\9d ×\91פ×\95נקצ×\99×\95× ×\9c×\99×\95ת ×\94×\96×\95; ×\91×\90פשר×\95ת×\9a ×\9cעש×\95ת ×\9e×\99× ×\95×\99 ×\9c[https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ ×¨×©×\99×\9eת ×\94×\93×\99×\95×\95ר mediawiki-api-announce] ×\9b×\93×\99 ×\9cק×\91×\9c ×\94×\95×\93×¢×\95ת ×¢×\9c ×¢×\93×\9b×\95× ×\99×\9d.\n\n<strong>×\91קש×\95ת ×©×\92×\95×\99×\95ת:</strong> ×\9bש×\91קש×\95ת ×©×\92×\95×\99×\95ת × ×©×\9c×\97×\95ת ×\9cÖ¾API, ×ª×\99ש×\9c×\97 ×\9b×\95תרת HTTP ×¢×\9d ×\94×\9eפת×\97 \"MediaWiki-API-Error\", ×\95×\90×\96 ×\92×\9d ×\94ער×\9a ×©×\9c ×\94×\9b×\95תרת ×\95×\92×\9d ×§×\95×\93 ×\94ש×\92×\99×\90×\94 ×\99×\95×\92×\93ר×\95 ×\9c×\90×\95ת×\95 ×¢×¨×\9a. ×\9c×\9e×\99×\93×¢ × ×\95סף, ×\90פשר ×\9c×¢×\99×\99×\9f ×\91×\93×£ [[mw:Special:MyLanguage/API:Errors_and_warnings|API: ×©×\92×\99×\90×\95ת ×\95×\90×\96×\94ר×\95ת]].\n\n<p class=\"mw-apisandbox-link\"><strong>×\91×\93×\99ק×\94:</strong> ×\9c×\91×\93×\99ק×\94 ×§×\9c×\94 ×\99×\95תר ×©×\9c ×\91קש×\95ת, ×\90פשר ×\9c×\94שת×\9eש ×\91[[Special:ApiSandbox|×\90ר×\92×\96 ×\94×\97×\95×\9c ×©×\9c API]].</p>",
        "apihelp-main-param-action": "איזו פעולה לבצע.",
        "apihelp-main-param-format": "תסדיר הפלט.",
        "apihelp-main-param-maxlag": "שיהוי מרבי יכול לשמש כשמדיה־ויקי מותקנת בצביר עם מסד נתונים משוכפל. כדי לחסוך בפעולות שגורמות יותר שיהוי בשכפול אתר, הפרמטר הזה יכול לגרום ללקוח להמתין עד ששיהוי השכפול יורד מתחת לערך שצוין. במקרה של שיהוי מוגזם, קוד השגיאה <samp>maxlag</samp> מוחזר עם הודעה כמו <samp>Waiting for $host: $lag seconds lagged</samp>.<br />ר' [[mw:Special:MyLanguage/Manual:Maxlag_parameter|מדריך למשתמש: פרמטר maxlag]] למידע נוסף.",
@@ -69,6 +69,7 @@
        "apihelp-compare-param-fromid": "מס׳ זיהוי של הדף הראשון להשוואה.",
        "apihelp-compare-param-fromrev": "גרסה ראשונה להשוואה.",
        "apihelp-compare-param-fromtext": "להשתמש בטקסט הזה במקום תוכן הגרסה שהוגדרה על־ידי <var dir=\"ltr\">fromtitle</var>, <var dir=\"ltr\">fromid</var> או <var dir=\"ltr\">fromrev</var>.",
+       "apihelp-compare-param-fromsection": "יש להשתמש רק בפסקה שצוינה בתוכן של הפרמטר 'from'.",
        "apihelp-compare-param-frompst": "לעשות התמרה לפני שמירה ב־<var>fromtext</var>.",
        "apihelp-compare-param-fromcontentmodel": "מודל התוכן של <var>fromtext</var>. אם זה לא סופק, ייעשה ניחוש על סמך פרמטרים אחרים.",
        "apihelp-compare-param-fromcontentformat": "תסדיר הסדרת תוכן של <var>fromtext</var>.",
@@ -77,6 +78,7 @@
        "apihelp-compare-param-torev": "גרסה שנייה להשוואה.",
        "apihelp-compare-param-torelative": "להשתמש בגרסה יחסית לגרסה שהוסקה מ<var dir=\"ltr\">fromtitle</var>, <var dir=\"ltr\">fromid</var> או <var dir=\"ltr\">fromrev</var>. לכל אפשריות ה־\"to\" האחרות לא תהיה השפעה.",
        "apihelp-compare-param-totext": "להשתמש בטקסט הזה במקום התוכן של הגרסה שהוגדר ב־<var dir=\"ltr\">totitle</var>, <var dir=\"ltr\">toid</var> or <var dir=\"ltr\">torev</var>.",
+       "apihelp-compare-param-tosection": "יש להשתמש רק בפסקה שצוינה בתוכן של הפרמטר 'to'.",
        "apihelp-compare-param-topst": "לעשות התמרה לפני שמירה ב־<var>totext</var>.",
        "apihelp-compare-param-tocontentmodel": "מודל התוכן של <var>totext</var>. אם זה לא סופק, ייעשה ניחוש על סמך פרמטרים אחרים.",
        "apihelp-compare-param-tocontentformat": "תסדיר הסדרת תוכן של <var>fromtext</var>.",
        "apihelp-import-extended-description": "יש לשים לב לכך שפעולת HTTP POST צריכה להיעשות בתור העלאת קובץ (כלומר, עם multipart/form-data) בזמן שליחת קובץ לפרמטר <var>xml</var>.",
        "apihelp-import-param-summary": "תקציר ייבוא עיולי יומן.",
        "apihelp-import-param-xml": "קובץ XML שהועלה.",
+       "apihelp-import-param-interwikiprefix": "לייבוא באמצעות העלאת קבצים: תחילית הבינוויקי שתוצג עבור שמות משתמשים שאינם מוכרים (וגם עבור שמות משתמשים מוכרים אם <var>$1assignknownusers</var> מוגדר).",
+       "apihelp-import-param-assignknownusers": "הקצאת העריכות למשתמשים המקומיים כאשר משתמשים בשמות זהים קיימים באתר המקומי.",
        "apihelp-import-param-interwikisource": "ליבוא בין אתרי ויקי: מאיזה ויקי לייבא.",
        "apihelp-import-param-interwikipage": "ליבוא בין אתרי ויקי: איזה דף לייבא.",
        "apihelp-import-param-fullhistory": "ליבוא בין אתרי ויקי: לייבר את ההיסטוריה המלאה, לא רק את הגרסה הנוכחית.",
        "apihelp-opensearch-summary": "חיפוש בוויקי בפרוטוקול OpenSearch.",
        "apihelp-opensearch-param-search": "מחרוזת לחיפוש.",
        "apihelp-opensearch-param-limit": "המספר המרבי של התוצאות שתוחזרנה.",
-       "apihelp-opensearch-param-namespace": "ש×\9e×\95ת ×\9eת×\97×\9d ×\9c×\97×\99פ×\95ש.",
+       "apihelp-opensearch-param-namespace": "×\9eר×\97×\91×\99 ×\94ש×\9d ×©×\91×\94×\9d ×\99ת×\91צע ×\94×\97×\99פ×\95ש. ×\9cש×\93×\94 ×\96×\94 ×\90×\99×\9f ×\9eש×\9e×¢×\95ת ×\90×\9d <var>$1search</var> ×\9eת×\97×\99×\9c ×¢×\9d ×ª×\97×\99×\9c×\99ת ×ª×§×\99× ×\94 ×©×\9c ×\9eר×\97×\91 ×©×\9d.",
        "apihelp-opensearch-param-suggest": "לא לעשות דבר אם <var>[[mw:Special:MyLanguage/Manual:$wgEnableOpenSearchSuggest|$wgEnableOpenSearchSuggest]]</var> הוא false.",
        "apihelp-opensearch-param-redirects": "איך לטפל בהפניות:\n;return:להחזיר את ההפניה עצמה.\n;resolve:להחזיר את דף היעד. יכול להחזיר פחות מ־$1limit תוצאות.\nמסיבות היסטוריות, בררת המחדל היא \"return\" עבור $1format=json ו־\"resolve\" עבור תסדירים אחרים.",
        "apihelp-opensearch-param-format": "תסדיר הפלט.",
        "apihelp-parse-param-disablepp": "יש להשתמש ב־<var>$1disablelimitreport</var> במקום.",
        "apihelp-parse-param-disableeditsection": "להשמיט את קישורי עריכת הפסקאות מפלט המפענח.",
        "apihelp-parse-param-disabletidy": "לא להריץ ניקוי HTML (למשל tidy) על פלט המפענח.",
+       "apihelp-parse-param-disablestylededuplication": "לא להסיר סגנונות כפולים בפלט של המפענח.",
        "apihelp-parse-param-generatexml": "יצירת עץ פענוח של XML (נדרש מודל תוכן <code>$1</code>; מוחלף ב־<kbd>$2prop=parsetree</kbd>).",
        "apihelp-parse-param-preview": "לפענח במצב תצוגה מקדימה.",
        "apihelp-parse-param-sectionpreview": "לפענח במצב תצוגה מקדימה של פסקה (מדליק גם את מצב תצוגה מקדימה).",
        "apihelp-query+prefixsearch-summary": "ביצוע חיפוש תחילית של כותרות דפים.",
        "apihelp-query+prefixsearch-extended-description": "למרות הדמיון בשם, המודול הזה אינו אמור להיות שווה ל־[[Special:PrefixIndex]] (\"מיוחד:דפים המתחילים ב\"); לדבר כזה, ר' <kbd>[[Special:ApiHelp/query+allpages|action=query&list=allpages]]</kbd> עם הפרמטר <kbd>apprefix</kbd>. מטרת המודול הזה דומה ל־<kbd>[[Special:ApiHelp/opensearch|action=opensearch]]</kbd>: לקבל קלט ממשתמש ולספק את הכותרות המתאימות ביותר. בהתאם לשרת מנוע החיפוש, זה יכול לכלול תיקון שגיאות כתיב, הימנעות מדפי הפניה והירסטיקות אחרות.",
        "apihelp-query+prefixsearch-param-search": "מחרוזת לחיפוש.",
-       "apihelp-query+prefixsearch-param-namespace": "ש×\9e×\95ת ×\9eת×\97×\9d ×\9c×\97×\99פ×\95ש.",
+       "apihelp-query+prefixsearch-param-namespace": "×\9eר×\97×\91×\99 ×\94ש×\9d ×©×\91×\94×\9d ×\99ת×\91צע ×\94×\97×\99פ×\95ש. ×\9cש×\93×\94 ×\96×\94 ×\90×\99×\9f ×\9eש×\9e×¢×\95ת ×\90×\9d <var>$1search</var> ×\9eת×\97×\99×\9c ×¢×\9d ×ª×\97×\99×\9c×\99ת ×ª×§×\99× ×\94 ×©×\9c ×\9eר×\97×\91 ×©×\9d.",
        "apihelp-query+prefixsearch-param-limit": "מספר התוצאות המרבי להחזרה.",
        "apihelp-query+prefixsearch-param-offset": "מספר תוצאות לדילוג.",
        "apihelp-query+prefixsearch-example-simple": "חיפוש שםות דפים שמתחילים ב־<kbd>meaning</kbd>.",
        "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+watchlist-paramvalue-prop-parsedcomment": "הוספת ההערכה המפוענחת של העריכה.",
        "apihelp-query+watchlist-paramvalue-prop-timestamp": "הוספת חותם־זמן של העריכה.",
        "apihelp-query+watchlist-paramvalue-prop-patrol": "תיוג עריכות שנבדקו.",
+       "apihelp-query+watchlist-paramvalue-prop-autopatrol": "תיוג עריכות המסומנות כבדוקות באופן אוטומטי.",
        "apihelp-query+watchlist-paramvalue-prop-sizes": "הוספת האורך החדש והישן של הדף.",
        "apihelp-query+watchlist-paramvalue-prop-notificationtimestamp": "הוספת חותם־זמן של ההודעה האחרונה למשתמש על העריכה.",
        "apihelp-query+watchlist-paramvalue-prop-loginfo": "הוספת מידע מהיומן איפה שמתאים.",
+       "apihelp-query+watchlist-paramvalue-prop-tags": "רשימת תגיות עבור הפעולה.",
        "apihelp-query+watchlist-param-show": "הצגה רק של פריטים שמתאימים לאמות המידה האלו. למשל, כדי לראות רק עריכות משניות שעשו משתמשים שנכנסו לחשבון, יש להגדיר $1show=minor|!anon.",
        "apihelp-query+watchlist-param-type": "אולי סוגי שינויים להציג:",
        "apihelp-query+watchlist-paramvalue-type-edit": "עריכות דף רגילות.",
        "apierror-chunk-too-small": "גודל הפלח המזערי הוא {{PLURAL:$1|בית אחד|$1 בתים}} בשביל פלחים לא סופיים.",
        "apierror-cidrtoobroad": "טווחי CIDR של $1 שרחבים יותר מ־/$2 אינם קבילים.",
        "apierror-compare-no-title": "לא ניתן לעשות התמרה לפני שמירה ללא כותרת. נא לנסות לציין <var>fromtitle</var> או <var>totitle</var>.",
+       "apierror-compare-nosuchfromsection": "הפסקה $1 אינה קיימת בתוכן של 'from'.",
+       "apierror-compare-nosuchtosection": "הפסקה $1 אינה קיימת בתוכן של 'to'.",
        "apierror-compare-relative-to-nothing": "אין גרסת \"from\" עבור <var>torelative</var> שתהיה יחסית.",
        "apierror-contentserializationexception": "הסדרת התוכן נכשלה: $1",
        "apierror-contenttoobig": "התוכן שסיפקת חורג מגודל הערך המרבי של {{PLURAL:$1|קילובייט אחד|$1 קילובייטים}}.",
        "apierror-invalidurlparam": "ערך בלתי־תקין עבור <var>$1urlparam</var> (ערך: <kbd>$2=$3</kbd>).",
        "apierror-invaliduser": "שם משתמש בלתי־תקין \"$1\".",
        "apierror-invaliduserid": "מזהה המשתמש <var>$1</var> אינו תקין.",
+       "apierror-maxbytes": "הפרמטר <var>$1</var> לא יכול להיות ארוך יותר {{PLURAL:$2|מבייט אחד|מ־$2 בייטים}}",
+       "apierror-maxchars": "הפרמטר <var>$1</var> לא יכול להיות ארוך יותר {{PLURAL:$2|מתו אחד|מ־$2 תווים}}",
        "apierror-maxlag-generic": "ממתין לשרת מסד נתונים: עיכוב של {{PLURAL:$1|שנייה אחת|$1 שניות}}.",
        "apierror-maxlag": "ממתין ל־$2: שיהוי של {{PLURAL:$1|שנייה אחת|$1 שניות}}.",
        "apierror-mimesearchdisabled": "חיפוש MIME כבוי במצב קמצן.",
index f33e2d5..84e1046 100644 (file)
@@ -45,6 +45,7 @@
        "apihelp-block-param-tags": "ブロック記録の項目に適用する変更タグ。",
        "apihelp-block-example-ip-simple": "IPアドレス <kbd>192.0.2.5</kbd> を <kbd>First strike<kbd> という理由で3日ブロックする",
        "apihelp-block-example-user-complex": "利用者 <kbd>Vandal</kbd> を <kbd>Vandalism</kbd> という理由で無期限ブロックし、新たなアカウント作成とメールの送信を禁止する。",
+       "apihelp-changeauthenticationdata-summary": "現在の利用者の認証データを変更します。",
        "apihelp-changeauthenticationdata-example-password": "現在の利用者のパスワードを <kbd>ExamplePassword</kbd> に変更する。",
        "apihelp-checktoken-summary": "<kbd>[[Special:ApiHelp/query+tokens|action=query&meta=tokens]]</kbd> のトークンの妥当性を確認します。",
        "apihelp-checktoken-param-type": "調べるトークンの種類。",
        "apihelp-checktoken-example-simple": "<kbd>csrf</kbd> トークンの妥当性を調べる。",
        "apihelp-clearhasmsg-summary": "現在の利用者の <code>hasmsg</code> フラグを消去します。",
        "apihelp-clearhasmsg-example-1": "現在の利用者の <code>hasmsg</code> フラグを消去する。",
+       "apihelp-clientlogin-summary": "インタラクティブフローを使用してウィキにログインします。",
        "apihelp-clientlogin-example-login": "利用者 <kbd>Example</kbd> としてのログイン処理をパスワード <kbd>ExamplePassword</kbd> で開始する",
+       "apihelp-clientlogin-example-login2": "<kbd>987654</kbd>の<var>OATHToken</var>を提供する2段階認証の<samp>UI</samp>レスポンスの後にログインを続けます。",
        "apihelp-compare-summary": "2つの版間の差分を取得します。",
        "apihelp-compare-extended-description": "\"from\" と \"to\" の両方の版番号、ページ名、もしくはページIDを渡す必要があります。",
        "apihelp-compare-param-fromtitle": "比較する1つ目のページ名。",
        "apihelp-compare-param-fromid": "比較する1つ目のページID。",
        "apihelp-compare-param-fromrev": "比較する1つ目の版。",
+       "apihelp-compare-param-frompst": "<var>fromtext</var>に保存前変換を行います。",
+       "apihelp-compare-param-fromcontentmodel": "<var>fromtext</var>のコンテンツモデル。指定されていない場合は、他のパラメータに基づいて推測されます。",
        "apihelp-compare-param-totitle": "比較する2つ目のページ名。",
        "apihelp-compare-param-toid": "比較する2つ目のページID。",
        "apihelp-compare-param-torev": "比較する2つ目の版。",
+       "apihelp-compare-param-topst": "<var>totext</var>に保存前変換を行います。",
        "apihelp-compare-param-prop": "どの情報を取得するか:",
        "apihelp-compare-paramvalue-prop-diff": "差分HTML。",
        "apihelp-compare-paramvalue-prop-diffsize": "差分HTMLのサイズ (バイト数)。",
@@ -74,6 +80,7 @@
        "apihelp-compare-paramvalue-prop-size": "'from' および 'to' の版のサイズ。",
        "apihelp-compare-example-1": "版1と2の差分を生成する。",
        "apihelp-createaccount-summary": "新しい利用者アカウントを作成します。",
+       "apihelp-createaccount-example-create": "利用者 <kbd>Example</kbd> を作成する処理をパスワード <kbd>ExamplePassword</kbd> で開始する",
        "apihelp-createaccount-param-name": "利用者名。",
        "apihelp-createaccount-param-password": "パスワード (<var>$1mailpassword</var> が設定されると無視されます)。",
        "apihelp-createaccount-param-domain": "外部認証のドメイン (省略可能)。",
@@ -85,6 +92,7 @@
        "apihelp-createaccount-param-language": "利用者の言語コードの既定値 (省略可能, 既定ではコンテンツ言語)。",
        "apihelp-createaccount-example-pass": "利用者 <kbd>testuser</kbd> をパスワード <kbd>test123</kbd> として作成する。",
        "apihelp-createaccount-example-mail": "利用者 <kbd>testmailuser</kbd>を作成し、無作為に生成されたパスワードをメールで送る。",
+       "apihelp-cspreport-param-source": "このレポートをトリガしたCSPヘッダを生成した内容",
        "apihelp-delete-summary": "ページを削除します。",
        "apihelp-delete-param-title": "削除するページ名です。<var>$1pageid</var> とは同時に使用できません。",
        "apihelp-delete-param-pageid": "削除するページIDです。<var>$1title</var> とは同時に使用できません。",
        "apihelp-edit-param-bot": "この編集をボットの編集としてマークする。",
        "apihelp-edit-param-basetimestamp": "編集前の版のタイムスタンプ。編集競合を検出するために使用されます。\n[[Special:ApiHelp/query+revisions|action=query&prop=revisions&rvprop=timestamp]] で取得できます。",
        "apihelp-edit-param-starttimestamp": "編集作業を開始したときのタイムスタンプ。編集競合を検出するために使用されます。適切な値は <var>[[Special:ApiHelp/main|curtimestamp]]</var> を使用して編集作業を開始するとき (たとえば、編集するページの本文を読み込んだとき) に取得できます。",
+       "apihelp-edit-param-recreate": "その間に削除されたページに関するエラーを上書きします。",
        "apihelp-edit-param-createonly": "すでにそのページが存在する場合は編集を行いません。",
        "apihelp-edit-param-nocreate": "そのページが存在しない場合にエラーを返します。",
        "apihelp-edit-param-watch": "そのページを現在の利用者のウォッチリストに追加します。",
        "apihelp-edit-param-undo": "この版を取り消します。$1text, $1prependtext および $1appendtext をオーバーライドします。",
        "apihelp-edit-param-undoafter": "$1undo からこの版までのすべての版を取り消します。設定しない場合、ひとつの版のみ取り消されます。",
        "apihelp-edit-param-redirect": "自動的に転送を解決します。",
+       "apihelp-edit-param-contentmodel": "新しいコンテンツのコンテンツ・モデル。",
        "apihelp-edit-param-token": "このトークンは常に最後のパラメーターとして、または少なくとも $1text パラメーターより後に送信されるべきです。",
        "apihelp-edit-example-edit": "ページを編集",
        "apihelp-edit-example-prepend": "<kbd>_&#95;NOTOC_&#95;</kbd> をページの先頭に挿入する。",
        "apihelp-expandtemplates-param-title": "ページの名前です。",
        "apihelp-expandtemplates-param-text": "変換するウィキテキストです。",
        "apihelp-expandtemplates-paramvalue-prop-wikitext": "展開されたウィキテキスト。",
+       "apihelp-expandtemplates-paramvalue-prop-jsconfigvars": "ページに固有のJavaScriptの設定変数を提供します。",
+       "apihelp-expandtemplates-paramvalue-prop-encodedjsconfigvars": "JSON文字列としてページに固有のJavaScriptの設定変数を提供します。",
        "apihelp-expandtemplates-paramvalue-prop-parsetree": "入力のXML構文解析ツリー。",
        "apihelp-expandtemplates-param-includecomments": "HTMLコメントを出力に含めるかどうか。",
        "apihelp-expandtemplates-param-generatexml": "XMLの構文解析ツリーを生成します (replaced by $1prop=parsetree)",
        "apihelp-feedrecentchanges-param-namespace": "この名前空間の結果のみに絞り込む。",
        "apihelp-feedrecentchanges-param-invert": "選択されたものを除く、すべての名前空間。",
        "apihelp-feedrecentchanges-param-associated": "関連する(トークまたはメイン)名前空間を含めます。",
+       "apihelp-feedrecentchanges-param-days": "結果を絞り込む日数。",
        "apihelp-feedrecentchanges-param-limit": "返す結果の最大数。",
        "apihelp-feedrecentchanges-param-from": "これ以降の編集を表示する。",
        "apihelp-feedrecentchanges-param-hideminor": "細部の変更を隠す。",
        "apihelp-feedrecentchanges-param-hideliu": "登録利用者による変更を隠す。",
        "apihelp-feedrecentchanges-param-hidepatrolled": "巡回済みの変更を隠す。",
        "apihelp-feedrecentchanges-param-hidemyself": "現在の利用者による編集を非表示にする。",
+       "apihelp-feedrecentchanges-param-hidecategorization": "カテゴリのメンバーの変更を非表示にする。",
        "apihelp-feedrecentchanges-param-tagfilter": "タグにより絞り込む。",
        "apihelp-feedrecentchanges-param-target": "このページからリンクされているページの変更のみを表示する。",
+       "apihelp-feedrecentchanges-param-showlinkedto": "選択したページへのリンク元での変更の表示に切り替え",
        "apihelp-feedrecentchanges-example-simple": "最近の更新を表示する。",
        "apihelp-feedrecentchanges-example-30days": "最近30日間の変更を表示する。",
        "apihelp-feedwatchlist-summary": "ウォッチリストのフィードを返します。",
        "apihelp-help-example-query": "2つの下位モジュールのヘルプ",
        "apihelp-imagerotate-summary": "1つ以上の画像を回転させます。",
        "apihelp-imagerotate-param-rotation": "画像を回転させる時計回りの角度。",
+       "apihelp-imagerotate-param-tags": "アップロード記録の項目に適用するタグ。",
        "apihelp-imagerotate-example-simple": "<kbd>File:Example.png</kbd> を <kbd>90</kbd> 度回転させる。",
        "apihelp-imagerotate-example-generator": "<kbd>Category:Flip</kbd> 内のすべての画像を <kbd>180</kbd> 度回転させる。",
        "apihelp-import-summary": "他のWikiまたはXMLファイルからページを取り込む。",
        "apihelp-import-extended-description": "<var>xml</var> パラメーターでファイルを送信する場合、ファイルのアップロードとしてHTTP POSTされなければならない (例えば、multipart/form-dataを使用する) 点に注意してください。",
        "apihelp-import-param-summary": "記録されるページ取り込みの要約。",
        "apihelp-import-param-xml": "XMLファイルをアップロード",
+       "apihelp-import-param-assignknownusers": "指定されたユーザーがこのウィキに存在する場合そのユーザーに編集を割り当てる",
        "apihelp-import-param-interwikisource": "ウィキ間の取り込みの場合: 取り込み元のウィキ。",
        "apihelp-import-param-interwikipage": "ウィキ間の取り込みの場合: 取り込むページ。",
        "apihelp-import-param-fullhistory": "ウィキ間の取り込みの場合: 現在の版のみではなく完全な履歴を取り込む。",
        "apihelp-import-param-rootpage": "このページの下位ページとして取り込む。<var>$1namespace</var> パラメータとは同時に使用できません。",
        "apihelp-import-example-import": "[[meta:Help:ParserFunctions]] をすべての履歴とともに名前空間100に取り込む。",
        "apihelp-login-summary": "ログインして認証クッキーを取得します。",
-       "apihelp-login-extended-description": "ã\83­ã\82°ã\82¤ã\83³ã\81\8cæ\88\90å\8a\9fã\81\97ã\81\9få ´å\90\88ã\80\81å¿\85è¦\81ã\81ªã\82¯ã\83\83ã\82­ã\83¼ã\81¯ HTTP å¿\9cç­\94ã\83\98ã\83\83ã\83\80ã\81«å\90«ã\81¾ã\82\8cã\81¾ã\81\99ã\80\82ã\83­ã\82°ã\82¤ã\83³ã\81«å¤±æ\95\97ã\81\97ã\81\9få ´å\90\88ã\80\81è\87ªå\8b\95å\8c\96ã\81®ã\83\91ã\82¹ã\83¯ã\83¼ã\83\89æ\8e¨å®\9aæ\94»æ\92\83ã\82\92å\88¶é\99\90ã\81\99ã\82\8bã\81\9fã\82\81ã\81«ã\80\81追å\8a ã\81®è©¦è¡\8cã\81¯é\80\9f度å\88¶é\99\90ã\81\95ã\82\8cã\82\8bã\81\93ã\81¨ã\81\8cã\81\82ã\82\8aます。",
+       "apihelp-login-extended-description": "ã\81\93ã\81®ã\82¢ã\82¯ã\82·ã\83§ã\83³ã\81¯ã\80\81[[Special:BotPasswords]]ã\81¨çµ\84ã\81¿å\90\88ã\82\8fã\81\9bã\81¦ä½¿ç\94¨ã\81\99ã\82\8bå¿\85è¦\81ã\81\8cã\81\82ã\82\8aã\81¾ã\81\99ã\80\82ã\83¡ã\82¤ã\83³ã\82¢ã\82«ã\82¦ã\83³ã\83\88ã\81®ã\83­ã\82°ã\82¤ã\83³ã\81«ä½¿ç\94¨ã\81\99ã\82\8bã\81\93ã\81¨ã\81¯æ\8e¨å¥¨ã\81\95ã\82\8cã\81ªã\81\8fã\81ªã\82\8aã\80\81è­¦å\91\8aã\81ªã\81\8f失æ\95\97ã\81\99ã\82\8bå\8f¯è\83½æ\80§ã\81\8cã\81\82ã\82\8aã\81¾ã\81\99ã\80\82ã\83¡ã\82¤ã\83³ã\82¢ã\82«ã\82¦ã\83³ã\83\88ã\81«å®\89å\85¨ã\81«ã\83­ã\82°ã\82¤ã\83³ã\81\99ã\82\8bã\81«ã\81¯ã\80\81<kbd>[[Special:ApiHelp/clientlogin|action=clientlogin]]</kbd>ã\82\92使ç\94¨ã\81\97ます。",
        "apihelp-login-param-name": "利用者名。",
        "apihelp-login-param-password": "パスワード。",
        "apihelp-login-param-domain": "ドメイン (省略可能)",
        "apihelp-managetags-param-tag": "作成、削除、有効化、または無効化するタグ。タグの作成の場合、そのタグは存在しないものでなければなりません。タグの削除の場合、そのタグが存在しなければなりません。タグの有効化の場合、そのタグが存在し、かつ拡張機能によって使用されていないものでなければなりません。タグの無効化の場合、そのタグが現在有効であって手動で定義されたものでなければなりません。",
        "apihelp-managetags-param-reason": "タグを作成、削除、有効化、または無効化する追加の理由。",
        "apihelp-managetags-param-ignorewarnings": "操作中に発生したすべての警告を無視するかどうか。",
+       "apihelp-managetags-param-tags": "タグを変更し、タグ管理記録の項目に適用します。",
        "apihelp-managetags-example-create": "<kbd>spam</kbd> という名前のタグを <kbd>For use in edit patrolling</kbd> という理由で作成する",
        "apihelp-managetags-example-delete": "<kbd>vandlaism</kbd> タグを <kbd>Misspelt</kbd> という理由で削除する",
        "apihelp-managetags-example-activate": "<kbd>spam</kbd> という名前のタグを <kbd>For use in edit patrolling</kbd> という理由で有効化する",
        "apihelp-purge-param-forcelinkupdate": "リンクテーブルを更新します。",
        "apihelp-purge-example-simple": "ページ <kbd>Main Page</kbd> および <kbd>API</kbd> をパージする。",
        "apihelp-purge-example-generator": "標準名前空間にある最初の10ページをパージする。",
+       "apihelp-query-summary": "MediaWikiからデータを取得します。",
        "apihelp-query-param-prop": "照会ページ用に、どのプロパティを取得するか。",
        "apihelp-query-param-list": "どの一覧を取得するか。",
        "apihelp-query-param-meta": "どのメタデータを取得するか。",
        "apihelp-query+allfileusages-param-from": "列挙を開始するファイルのページ名。",
        "apihelp-query+allfileusages-param-to": "列挙を終了するファイルのページ名。",
        "apihelp-query+allfileusages-param-prefix": "この値で始まるページ名のすべてのファイルを検索する。",
+       "apihelp-query+allfileusages-param-unique": "ファイル名を一度だけ表示します。<kbd>$1prop=ids</kbd> とは同時に使用できません。ジェネレーターとして使用される場合、リンク元ではなくリンク先のページを生成します。",
        "apihelp-query+allfileusages-param-prop": "どの情報を結果に含めるか:",
        "apihelp-query+allfileusages-paramvalue-prop-ids": "使用しているページのページIDを追加します ($1unique とは同時に使用できません)。",
        "apihelp-query+allfileusages-paramvalue-prop-title": "ファイルのページ名を追加します。",
        "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+allredirects-param-prefix": "この値で始まるすべてのページを検索する。",
        "apihelp-query+allredirects-param-unique": "転送先ページ名を一度だけ表示します。<kbd>$1prop=ids|fragment|interwiki</kbd> とは同時に使用できません。ジェネレーターとして使用される場合、転送元ではなく転送先のページを生成します。",
        "apihelp-query+allredirects-param-prop": "どの情報を結果に含めるか:",
+       "apihelp-query+allredirects-paramvalue-prop-ids": "転送ページのページIDを追加します ($1unique とは同時に使用できません)。",
        "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-B": "<kbd>B</kbd> で始まる転送先ページ (存在しないページも含む)を、転送元のページIDとともに表示する。",
+       "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": "列挙の始点となるタイムスタンプ。",
        "apihelp-query+allrevisions-param-end": "列挙の終点となるタイムスタンプ。",
        "apihelp-query+prefixsearch-param-namespace": "検索する名前空間。<var>$1search</var>が有効な名前空間接頭辞で始まる場合は無視されます。",
        "apihelp-query+prefixsearch-param-limit": "返す結果の最大数。",
        "apihelp-query+prefixsearch-example-simple": "<kbd>meaning</kbd> で始まるページ名を検索する。",
+       "apihelp-query+prefixsearch-param-profile": "使用するプロファイルを検索します。",
        "apihelp-query+protectedtitles-summary": "作成保護が掛けられているページを一覧表示します。",
        "apihelp-query+protectedtitles-param-namespace": "この名前空間に含まれるページのみを一覧表示します。",
        "apihelp-query+protectedtitles-param-level": "この保護レベルのページのみを一覧表示します。",
        "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-limit": "返す無作為なページの数を制限する。",
        "apihelp-query+random-param-redirect": "代わりに <kbd>$1filterredir=redirects</kbd> を使用してください。",
        "apihelp-query+recentchanges-paramvalue-prop-redirect": "編集されたページが転送ページである場合、印を付けます。",
        "apihelp-query+recentchanges-paramvalue-prop-patrolled": "巡回可能な編集について、巡回済みかどうか印を付けます。",
        "apihelp-query+recentchanges-paramvalue-prop-loginfo": "記録項目に記録の情報 (記録ID,  記録タイプなど) を追加します。",
+       "apihelp-query+recentchanges-paramvalue-prop-tags": "エントリのタグを一覧表示します。",
        "apihelp-query+recentchanges-param-token": "代わりに <kbd>[[Special:ApiHelp/query+tokens|action=query&meta=tokens]]</kbd> を使用してください。",
        "apihelp-query+recentchanges-param-limit": "返す変更の総数。",
        "apihelp-query+recentchanges-param-toponly": "最新の版である変更のみを一覧表示する。",
        "apihelp-query+search-param-search": "この値を含むページ名または本文を検索します。Wikiの検索バックエンド実装に応じて、あなたは特別な検索機能を呼び出すための文字列を検索することができます。",
        "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+templates-summary": "与えられたページでトランスクルードされているすべてのページを返します。",
        "apihelp-query+templates-param-namespace": "この名前空間のテンプレートのみ表示する。",
        "apihelp-query+templates-param-limit": "返すテンプレートの数。",
+       "apihelp-query+templates-param-dir": "一覧表示する方向。",
        "apihelp-query+templates-example-simple": "<kbd>Main Page</kbd> で使用されているテンプレートを取得する。",
        "apihelp-query+templates-example-generator": "<kbd>Main Page</kbd> で使用されているテンプレートに関する情報を取得する。",
        "apihelp-query+templates-example-namespaces": "<kbd>Main Page</kbd> でトランスクルードされている {{ns:user}} および {{ns:template}} 名前空間のページを取得する。",
        "apihelp-query+transcludedin-param-prop": "取得するプロパティ:",
        "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+transcludedin-example-simple": "<kbd>Main Page</kbd> をトランスクルードしているページの一覧を取得する。",
        "apihelp-query+transcludedin-example-generator": "<kbd>Main Page</kbd> をトランスクルードしているページに関する情報を取得する。",
        "apihelp-query+usercontribs-summary": "利用者によるすべての編集を取得します。",
        "apihelp-revisiondelete-summary": "版の削除および復元を行います。",
        "apihelp-revisiondelete-param-reason": "削除または復元の理由。",
        "apihelp-revisiondelete-example-revision": "<kbd>Main Page</kbd> の版 <kbd>12345</kbd> の本文を隠す。",
+       "apihelp-rollback-summary": "ページの最後の編集を取り消す。",
        "apihelp-rollback-param-title": "巻き戻すページ名です。<var>$1pageid</var> とは同時に使用できません。",
        "apihelp-rollback-param-pageid": "巻き戻すページのページIDです。<var>$1title</var> とは同時に使用できません。",
        "apihelp-rollback-param-tags": "巻き戻しに適用するタグ。",
        "apihelp-tokens-example-edit": "編集トークンを取得する (既定)。",
        "apihelp-unblock-summary": "利用者のブロックを解除します。",
        "apihelp-unblock-param-id": "解除するブロックのID (<kbd>list=blocks</kbd>で取得できます)。<var>$1user</var> または <var>$1userid</var> とは同時に使用できません。",
-       "apihelp-unblock-param-user": "ã\83\96ã\83­ã\83\83ã\82¯ã\82\92解é\99¤ã\81\99ã\82\8bå\88©ç\94¨è\80\85å\90\8dã\80\81IPã\82¢ã\83\89ã\83¬ã\82¹ã\81¾ã\81\9fã\81¯IPã\83¬ã\83³ã\82¸ã\80\82<var>$1id</var>とは同時に使用できません。",
+       "apihelp-unblock-param-user": "ã\83\96ã\83­ã\83\83ã\82¯ã\82\92解é\99¤ã\81\99ã\82\8bå\88©ç\94¨è\80\85å\90\8dã\80\81IPã\82¢ã\83\89ã\83¬ã\82¹ã\81¾ã\81\9fã\81¯IPã\82¢ã\83\89ã\83¬ã\82¹ã\83¬ã\83³ã\82¸ã\80\82<var>$1id</var>ã\81¾ã\81\9fã\81¯<var>$1userid</var>とは同時に使用できません。",
        "apihelp-unblock-param-reason": "ブロック解除の理由。",
        "apihelp-unblock-param-tags": "ブロック記録の項目に適用する変更タグ。",
        "apihelp-unblock-example-id": "ブロックID #<kbd>105</kbd> を解除する。",
        "api-help-flag-writerights": "このモジュールは書き込みの権限を必要とします。",
        "api-help-flag-mustbeposted": "このモジュールは POST リクエストのみを受け付けます。",
        "api-help-flag-generator": "このモジュールはジェネレーターとして使用できます。",
+       "api-help-source": "ソース: $1",
        "api-help-parameters": "{{PLURAL:$1|パラメーター}}:",
        "api-help-param-deprecated": "廃止予定です。",
        "api-help-param-required": "このパラメーターは必須です。",
        "api-help-permissions": "{{PLURAL:$1|権限}}:",
        "api-help-permissions-granted-to": "{{PLURAL:$1|権限を持つグループ}}: $2",
        "api-help-open-in-apisandbox": "<small>[サンドボックスで開く]</small>",
+       "apierror-filedoesnotexist": "ファイルが存在しません。",
+       "apierror-invaliduser": "無効なユーザー名「$1」。",
        "apierror-missingparam": "パラメーター <var>$1</var> を設定してください。",
+       "apierror-mustbeloggedin": "$1にログインしている必要があります。",
+       "apierror-noimageredirect": "画像のリダイレクトを作成する権限がありません。",
+       "apierror-nosuchpageid": "ID $1のページはありません。",
+       "apierror-permissiondenied": "$1に必要な権限がありません。",
+       "apierror-permissiondenied-generic": "アクセスが拒否されました。",
+       "apierror-readonly": "ウィキは現在読み取り専用モードです。",
        "apierror-timeout": "サーバーが決められた時間内に応答しませんでした。",
+       "apierror-unknownerror-editpage": "不明な編集ページのエラー:$1",
+       "apierror-unknownerror-nocode": "不明なエラーです。",
+       "apierror-unknownerror": "不明なエラー:「$1」",
        "apiwarn-invalidcategory": "「$1」はカテゴリではありません。",
        "apiwarn-notfile": "「$1」はファイルではありません。",
+       "apiwarn-validationfailed-cannotset": "このモジュールでは設定できません。",
+       "apiwarn-validationfailed-keytoolong": "キーが長すぎます($1バイト以上は許可されません)。",
+       "apiwarn-wgDebugAPI": "<strong>セキュリティ警告</strong>:<var>$wgDebugAPI</var>が有効です。",
+       "api-feed-error-title": "エラー ($1)",
+       "api-usage-docref": "APIの使用については$1を参照してください。",
+       "api-exception-trace": "$2の$1($3)\n$4",
        "api-credits-header": "クレジット",
        "api-credits": "API の開発者:\n* Roan Kattouw (2007年9月-2009年の主任開発者)\n* Victor Vasiliev\n* Bryan Tong Minh\n* Sam Reed\n* Yuri Astrakhan (作成者、2006年9月-2007年9月の主任開発者)\n* Brad Jorsch (2013年-現在の主任開発者)\n\nコメント、提案、質問は mediawiki-api@lists.wikimedia.org にお送りください。\nバグはこちらへご報告ください: https://phabricator.wikimedia.org/"
 }
index c54dd81..a784813 100644 (file)
        "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": "<strong>MediaWiki はインストール済みです。</strong>",
        "mainpagedocfooter": "ウィキソフトウェアの使い方に関する情報は[https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents 利用者案内]を参照してください。\n\n== はじめましょう ==\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings/ja 設定の一覧]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ/ja MediaWiki よくある質問と回答]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki リリース情報メーリングリスト]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation/ja MediaWiki のあなたの言語へのローカライズ]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam あなたのウィキでスパムと戦う方法を学ぶ]"
index 66fb4ca..cc42c42 100644 (file)
        "config-help": "Hëllef",
        "config-help-tooltip": "klickt fir opzeklappen",
        "config-nofile": "De Fichier \"$1\" gouf net fonnt. Gouf e geläscht?",
+       "config-extensions-requires": "$1 (brauch $2)",
        "config-screenshot": "Screenshot",
        "mainpagetext": "<strong>MediaWiki gouf installéiert.</strong>",
        "mainpagedocfooter": "Kuckt w.e.g. [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents d'Benotzerhandbuch] fir Informatiounen iwwer de Gebruach vun der Wiki Software.\n\n== Fir  unzefänken ==\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Hëllef bei der Konfiguratioun]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki-FAQ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mailinglëscht vun neie MediaWiki-Versiounen]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Lokaliséiert MediaWiki fir Är Sprooch]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam Léiert wéi Spam op Ärer Wiki reduzéiert gi kann]"
index fb0f2f9..7b48ad0 100644 (file)
@@ -189,6 +189,46 @@ class UserMailer {
                return self::sendInternal( $to, $from, $subject, $body, $options );
        }
 
+       /**
+        * Whether the PEAR Mail_mime library is usable. This will
+        * try and load it if it is not already.
+        *
+        * @return bool
+        */
+       private static function isMailMimeUsable() {
+               static $usable = null;
+               if ( $usable === null ) {
+                       // If the class is not already loaded, and it's in the include path,
+                       // try requiring it.
+                       if ( !class_exists( 'Mail_mime' ) && stream_resolve_include_path( 'Mail/mime.php' ) ) {
+                               require_once 'Mail/mime.php';
+                       }
+                       $usable = class_exists( 'Mail_mime' );
+               }
+
+               return $usable;
+       }
+
+       /**
+        * Whether the PEAR Mail library is usable. This will
+        * try and load it if it is not already.
+        *
+        * @return bool
+        */
+       private static function isMailUsable() {
+               static $usable = null;
+               if ( $usable === null ) {
+                       // If the class is not already loaded, and it's in the include path,
+                       // try requiring it.
+                       if ( !class_exists( 'Mail' ) && stream_resolve_include_path( 'Mail.php' ) ) {
+                               require_once 'Mail.php';
+                       }
+                       $usable = class_exists( 'Mail' );
+               }
+
+               return $usable;
+       }
+
        /**
         * Helper function fo UserMailer::send() which does the actual sending. It expects a $to
         * list which the UserMailerSplitTo hook would not split further.
@@ -296,15 +336,12 @@ class UserMailer {
                if ( is_array( $body ) ) {
                        // we are sending a multipart message
                        wfDebug( "Assembling multipart mime email\n" );
-                       if ( !stream_resolve_include_path( 'Mail/mime.php' ) ) {
+                       if ( !self::isMailMimeUsable() ) {
                                wfDebug( "PEAR Mail_Mime package is not installed. Falling back to text email.\n" );
                                // remove the html body for text email fall back
                                $body = $body['text'];
                        } else {
-                               // Check if pear/mail_mime is already loaded (via composer)
-                               if ( !class_exists( 'Mail_mime' ) ) {
-                                       require_once 'Mail/mime.php';
-                               }
+                               // pear/mail_mime is already loaded by this point
                                if ( wfIsWindows() ) {
                                        $body['text'] = str_replace( "\n", "\r\n", $body['text'] );
                                        $body['html'] = str_replace( "\n", "\r\n", $body['html'] );
@@ -352,12 +389,8 @@ class UserMailer {
 
                if ( is_array( $wgSMTP ) ) {
                        // Check if pear/mail is already loaded (via composer)
-                       if ( !class_exists( 'Mail' ) ) {
-                               // PEAR MAILER
-                               if ( !stream_resolve_include_path( 'Mail.php' ) ) {
-                                       throw new MWException( 'PEAR mail package is not installed' );
-                               }
-                               require_once 'Mail.php';
+                       if ( !self::isMailUsable() ) {
+                               throw new MWException( 'PEAR mail package is not installed' );
                        }
 
                        Wikimedia\suppressWarnings();
diff --git a/includes/media/BMP.php b/includes/media/BMP.php
deleted file mode 100644 (file)
index 0229ac1..0000000
+++ /dev/null
@@ -1,80 +0,0 @@
-<?php
-/**
- * Handler for Microsoft's bitmap format.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Media
- */
-
-/**
- * Handler for Microsoft's bitmap format; getimagesize() doesn't
- * support these files
- *
- * @ingroup Media
- */
-class BmpHandler extends BitmapHandler {
-       /**
-        * @param File $file
-        * @return bool
-        */
-       public function mustRender( $file ) {
-               return true;
-       }
-
-       /**
-        * Render files as PNG
-        *
-        * @param string $text
-        * @param string $mime
-        * @param array $params
-        * @return array
-        */
-       function getThumbType( $text, $mime, $params = null ) {
-               return [ 'png', 'image/png' ];
-       }
-
-       /**
-        * Get width and height from the bmp header.
-        *
-        * @param File|FSFile $image
-        * @param string $filename
-        * @return array
-        */
-       function getImageSize( $image, $filename ) {
-               $f = fopen( $filename, 'rb' );
-               if ( !$f ) {
-                       return false;
-               }
-               $header = fread( $f, 54 );
-               fclose( $f );
-
-               // Extract binary form of width and height from the header
-               $w = substr( $header, 18, 4 );
-               $h = substr( $header, 22, 4 );
-
-               // Convert the unsigned long 32 bits (little endian):
-               try {
-                       $w = wfUnpack( 'V', $w, 4 );
-                       $h = wfUnpack( 'V', $h, 4 );
-               } catch ( Exception $e ) {
-                       return false;
-               }
-
-               return [ $w[1], $h[1] ];
-       }
-}
diff --git a/includes/media/Bitmap.php b/includes/media/Bitmap.php
deleted file mode 100644 (file)
index cda037c..0000000
+++ /dev/null
@@ -1,607 +0,0 @@
-<?php
-/**
- * Generic handler for bitmap images.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Media
- */
-
-/**
- * Generic handler for bitmap images
- *
- * @ingroup Media
- */
-class BitmapHandler extends TransformationalImageHandler {
-
-       /**
-        * Returns which scaler type should be used. Creates parent directories
-        * for $dstPath and returns 'client' on error
-        *
-        * @param string $dstPath
-        * @param bool $checkDstPath
-        * @return string|Callable One of client, im, custom, gd, imext or an array( object, method )
-        */
-       protected function getScalerType( $dstPath, $checkDstPath = true ) {
-               global $wgUseImageResize, $wgUseImageMagick, $wgCustomConvertCommand;
-
-               if ( !$dstPath && $checkDstPath ) {
-                       # No output path available, client side scaling only
-                       $scaler = 'client';
-               } elseif ( !$wgUseImageResize ) {
-                       $scaler = 'client';
-               } elseif ( $wgUseImageMagick ) {
-                       $scaler = 'im';
-               } elseif ( $wgCustomConvertCommand ) {
-                       $scaler = 'custom';
-               } elseif ( function_exists( 'imagecreatetruecolor' ) ) {
-                       $scaler = 'gd';
-               } elseif ( class_exists( 'Imagick' ) ) {
-                       $scaler = 'imext';
-               } else {
-                       $scaler = 'client';
-               }
-
-               return $scaler;
-       }
-
-       public function makeParamString( $params ) {
-               $res = parent::makeParamString( $params );
-               if ( isset( $params['interlace'] ) && $params['interlace'] ) {
-                       return "interlaced-{$res}";
-               } else {
-                       return $res;
-               }
-       }
-
-       public function parseParamString( $str ) {
-               $remainder = preg_replace( '/^interlaced-/', '', $str );
-               $params = parent::parseParamString( $remainder );
-               if ( $params === false ) {
-                       return false;
-               }
-               $params['interlace'] = $str !== $remainder;
-               return $params;
-       }
-
-       public function validateParam( $name, $value ) {
-               if ( $name === 'interlace' ) {
-                       return $value === false || $value === true;
-               } else {
-                       return parent::validateParam( $name, $value );
-               }
-       }
-
-       /**
-        * @param File $image
-        * @param array &$params
-        * @return bool
-        */
-       function normaliseParams( $image, &$params ) {
-               global $wgMaxInterlacingAreas;
-               if ( !parent::normaliseParams( $image, $params ) ) {
-                       return false;
-               }
-               $mimeType = $image->getMimeType();
-               $interlace = isset( $params['interlace'] ) && $params['interlace']
-                       && isset( $wgMaxInterlacingAreas[$mimeType] )
-                       && $this->getImageArea( $image ) <= $wgMaxInterlacingAreas[$mimeType];
-               $params['interlace'] = $interlace;
-               return true;
-       }
-
-       /**
-        * Get ImageMagick subsampling factors for the target JPEG pixel format.
-        *
-        * @param string $pixelFormat one of 'yuv444', 'yuv422', 'yuv420'
-        * @return array of string keys
-        */
-       protected function imageMagickSubsampling( $pixelFormat ) {
-               switch ( $pixelFormat ) {
-                       case 'yuv444':
-                               return [ '1x1', '1x1', '1x1' ];
-                       case 'yuv422':
-                               return [ '2x1', '1x1', '1x1' ];
-                       case 'yuv420':
-                               return [ '2x2', '1x1', '1x1' ];
-                       default:
-                               throw new MWException( 'Invalid pixel format for JPEG output' );
-               }
-       }
-
-       /**
-        * Transform an image using ImageMagick
-        *
-        * @param File $image File associated with this thumbnail
-        * @param array $params Array with scaler params
-        *
-        * @return MediaTransformError|bool Error object if error occurred, false (=no error) otherwise
-        */
-       protected function transformImageMagick( $image, $params ) {
-               # use ImageMagick
-               global $wgSharpenReductionThreshold, $wgSharpenParameter, $wgMaxAnimatedGifArea,
-                       $wgImageMagickTempDir, $wgImageMagickConvertCommand, $wgJpegPixelFormat,
-                       $wgJpegQuality;
-
-               $quality = [];
-               $sharpen = [];
-               $scene = false;
-               $animation_pre = [];
-               $animation_post = [];
-               $decoderHint = [];
-               $subsampling = [];
-
-               if ( $params['mimeType'] == 'image/jpeg' ) {
-                       $qualityVal = isset( $params['quality'] ) ? (string)$params['quality'] : null;
-                       $quality = [ '-quality', $qualityVal ?: (string)$wgJpegQuality ]; // 80% by default
-                       if ( $params['interlace'] ) {
-                               $animation_post = [ '-interlace', 'JPEG' ];
-                       }
-                       # Sharpening, see T8193
-                       if ( ( $params['physicalWidth'] + $params['physicalHeight'] )
-                               / ( $params['srcWidth'] + $params['srcHeight'] )
-                               < $wgSharpenReductionThreshold
-                       ) {
-                               $sharpen = [ '-sharpen', $wgSharpenParameter ];
-                       }
-                       if ( version_compare( $this->getMagickVersion(), "6.5.6" ) >= 0 ) {
-                               // JPEG decoder hint to reduce memory, available since IM 6.5.6-2
-                               $decoderHint = [ '-define', "jpeg:size={$params['physicalDimensions']}" ];
-                       }
-                       if ( $wgJpegPixelFormat ) {
-                               $factors = $this->imageMagickSubsampling( $wgJpegPixelFormat );
-                               $subsampling = [ '-sampling-factor', implode( ',', $factors ) ];
-                       }
-               } elseif ( $params['mimeType'] == 'image/png' ) {
-                       $quality = [ '-quality', '95' ]; // zlib 9, adaptive filtering
-                       if ( $params['interlace'] ) {
-                               $animation_post = [ '-interlace', 'PNG' ];
-                       }
-               } elseif ( $params['mimeType'] == 'image/webp' ) {
-                       $quality = [ '-quality', '95' ]; // zlib 9, adaptive filtering
-               } elseif ( $params['mimeType'] == 'image/gif' ) {
-                       if ( $this->getImageArea( $image ) > $wgMaxAnimatedGifArea ) {
-                               // Extract initial frame only; we're so big it'll
-                               // be a total drag. :P
-                               $scene = 0;
-                       } elseif ( $this->isAnimatedImage( $image ) ) {
-                               // Coalesce is needed to scale animated GIFs properly (T3017).
-                               $animation_pre = [ '-coalesce' ];
-                               // We optimize the output, but -optimize is broken,
-                               // use optimizeTransparency instead (T13822)
-                               if ( version_compare( $this->getMagickVersion(), "6.3.5" ) >= 0 ) {
-                                       $animation_post = [ '-fuzz', '5%', '-layers', 'optimizeTransparency' ];
-                               }
-                       }
-                       if ( $params['interlace'] && version_compare( $this->getMagickVersion(), "6.3.4" ) >= 0
-                               && !$this->isAnimatedImage( $image ) ) { // interlacing animated GIFs is a bad idea
-                               $animation_post[] = '-interlace';
-                               $animation_post[] = 'GIF';
-                       }
-               } elseif ( $params['mimeType'] == 'image/x-xcf' ) {
-                       // Before merging layers, we need to set the background
-                       // to be transparent to preserve alpha, as -layers merge
-                       // merges all layers on to a canvas filled with the
-                       // background colour. After merging we reset the background
-                       // to be white for the default background colour setting
-                       // in the PNG image (which is used in old IE)
-                       $animation_pre = [
-                               '-background', 'transparent',
-                               '-layers', 'merge',
-                               '-background', 'white',
-                       ];
-                       Wikimedia\suppressWarnings();
-                       $xcfMeta = unserialize( $image->getMetadata() );
-                       Wikimedia\restoreWarnings();
-                       if ( $xcfMeta
-                               && isset( $xcfMeta['colorType'] )
-                               && $xcfMeta['colorType'] === 'greyscale-alpha'
-                               && version_compare( $this->getMagickVersion(), "6.8.9-3" ) < 0
-                       ) {
-                               // T68323 - Greyscale images not rendered properly.
-                               // So only take the "red" channel.
-                               $channelOnly = [ '-channel', 'R', '-separate' ];
-                               $animation_pre = array_merge( $animation_pre, $channelOnly );
-                       }
-               }
-
-               // Use one thread only, to avoid deadlock bugs on OOM
-               $env = [ 'OMP_NUM_THREADS' => 1 ];
-               if ( strval( $wgImageMagickTempDir ) !== '' ) {
-                       $env['MAGICK_TMPDIR'] = $wgImageMagickTempDir;
-               }
-
-               $rotation = isset( $params['disableRotation'] ) ? 0 : $this->getRotation( $image );
-               list( $width, $height ) = $this->extractPreRotationDimensions( $params, $rotation );
-
-               $cmd = call_user_func_array( 'wfEscapeShellArg', array_merge(
-                       [ $wgImageMagickConvertCommand ],
-                       $quality,
-                       // Specify white background color, will be used for transparent images
-                       // in Internet Explorer/Windows instead of default black.
-                       [ '-background', 'white' ],
-                       $decoderHint,
-                       [ $this->escapeMagickInput( $params['srcPath'], $scene ) ],
-                       $animation_pre,
-                       // For the -thumbnail option a "!" is needed to force exact size,
-                       // or ImageMagick may decide your ratio is wrong and slice off
-                       // a pixel.
-                       [ '-thumbnail', "{$width}x{$height}!" ],
-                       // Add the source url as a comment to the thumb, but don't add the flag if there's no comment
-                       ( $params['comment'] !== ''
-                               ? [ '-set', 'comment', $this->escapeMagickProperty( $params['comment'] ) ]
-                               : [] ),
-                       // T108616: Avoid exposure of local file path
-                       [ '+set', 'Thumb::URI' ],
-                       [ '-depth', 8 ],
-                       $sharpen,
-                       [ '-rotate', "-$rotation" ],
-                       $subsampling,
-                       $animation_post,
-                       [ $this->escapeMagickOutput( $params['dstPath'] ) ] ) );
-
-               wfDebug( __METHOD__ . ": running ImageMagick: $cmd\n" );
-               $retval = 0;
-               $err = wfShellExecWithStderr( $cmd, $retval, $env );
-
-               if ( $retval !== 0 ) {
-                       $this->logErrorForExternalProcess( $retval, $err, $cmd );
-
-                       return $this->getMediaTransformError( $params, "$err\nError code: $retval" );
-               }
-
-               return false; # No error
-       }
-
-       /**
-        * Transform an image using the Imagick PHP extension
-        *
-        * @param File $image File associated with this thumbnail
-        * @param array $params Array with scaler params
-        *
-        * @return MediaTransformError Error|bool object if error occurred, false (=no error) otherwise
-        */
-       protected function transformImageMagickExt( $image, $params ) {
-               global $wgSharpenReductionThreshold, $wgSharpenParameter, $wgMaxAnimatedGifArea,
-                       $wgJpegPixelFormat, $wgJpegQuality;
-
-               try {
-                       $im = new Imagick();
-                       $im->readImage( $params['srcPath'] );
-
-                       if ( $params['mimeType'] == 'image/jpeg' ) {
-                               // Sharpening, see T8193
-                               if ( ( $params['physicalWidth'] + $params['physicalHeight'] )
-                                       / ( $params['srcWidth'] + $params['srcHeight'] )
-                                       < $wgSharpenReductionThreshold
-                               ) {
-                                       // Hack, since $wgSharpenParameter is written specifically for the command line convert
-                                       list( $radius, $sigma ) = explode( 'x', $wgSharpenParameter );
-                                       $im->sharpenImage( $radius, $sigma );
-                               }
-                               $qualityVal = isset( $params['quality'] ) ? (string)$params['quality'] : null;
-                               $im->setCompressionQuality( $qualityVal ?: $wgJpegQuality );
-                               if ( $params['interlace'] ) {
-                                       $im->setInterlaceScheme( Imagick::INTERLACE_JPEG );
-                               }
-                               if ( $wgJpegPixelFormat ) {
-                                       $factors = $this->imageMagickSubsampling( $wgJpegPixelFormat );
-                                       $im->setSamplingFactors( $factors );
-                               }
-                       } elseif ( $params['mimeType'] == 'image/png' ) {
-                               $im->setCompressionQuality( 95 );
-                               if ( $params['interlace'] ) {
-                                       $im->setInterlaceScheme( Imagick::INTERLACE_PNG );
-                               }
-                       } elseif ( $params['mimeType'] == 'image/gif' ) {
-                               if ( $this->getImageArea( $image ) > $wgMaxAnimatedGifArea ) {
-                                       // Extract initial frame only; we're so big it'll
-                                       // be a total drag. :P
-                                       $im->setImageScene( 0 );
-                               } elseif ( $this->isAnimatedImage( $image ) ) {
-                                       // Coalesce is needed to scale animated GIFs properly (T3017).
-                                       $im = $im->coalesceImages();
-                               }
-                               // GIF interlacing is only available since 6.3.4
-                               $v = Imagick::getVersion();
-                               preg_match( '/ImageMagick ([0-9]+\.[0-9]+\.[0-9]+)/', $v['versionString'], $v );
-
-                               if ( $params['interlace'] && version_compare( $v[1], '6.3.4' ) >= 0 ) {
-                                       $im->setInterlaceScheme( Imagick::INTERLACE_GIF );
-                               }
-                       }
-
-                       $rotation = isset( $params['disableRotation'] ) ? 0 : $this->getRotation( $image );
-                       list( $width, $height ) = $this->extractPreRotationDimensions( $params, $rotation );
-
-                       $im->setImageBackgroundColor( new ImagickPixel( 'white' ) );
-
-                       // Call Imagick::thumbnailImage on each frame
-                       foreach ( $im as $i => $frame ) {
-                               if ( !$frame->thumbnailImage( $width, $height, /* fit */ false ) ) {
-                                       return $this->getMediaTransformError( $params, "Error scaling frame $i" );
-                               }
-                       }
-                       $im->setImageDepth( 8 );
-
-                       if ( $rotation ) {
-                               if ( !$im->rotateImage( new ImagickPixel( 'white' ), 360 - $rotation ) ) {
-                                       return $this->getMediaTransformError( $params, "Error rotating $rotation degrees" );
-                               }
-                       }
-
-                       if ( $this->isAnimatedImage( $image ) ) {
-                               wfDebug( __METHOD__ . ": Writing animated thumbnail\n" );
-                               // This is broken somehow... can't find out how to fix it
-                               $result = $im->writeImages( $params['dstPath'], true );
-                       } else {
-                               $result = $im->writeImage( $params['dstPath'] );
-                       }
-                       if ( !$result ) {
-                               return $this->getMediaTransformError( $params,
-                                       "Unable to write thumbnail to {$params['dstPath']}" );
-                       }
-               } catch ( ImagickException $e ) {
-                       return $this->getMediaTransformError( $params, $e->getMessage() );
-               }
-
-               return false;
-       }
-
-       /**
-        * Transform an image using a custom command
-        *
-        * @param File $image File associated with this thumbnail
-        * @param array $params Array with scaler params
-        *
-        * @return MediaTransformError Error|bool object if error occurred, false (=no error) otherwise
-        */
-       protected function transformCustom( $image, $params ) {
-               # Use a custom convert command
-               global $wgCustomConvertCommand;
-
-               # Variables: %s %d %w %h
-               $src = wfEscapeShellArg( $params['srcPath'] );
-               $dst = wfEscapeShellArg( $params['dstPath'] );
-               $cmd = $wgCustomConvertCommand;
-               $cmd = str_replace( '%s', $src, str_replace( '%d', $dst, $cmd ) ); # Filenames
-               $cmd = str_replace( '%h', wfEscapeShellArg( $params['physicalHeight'] ),
-                       str_replace( '%w', wfEscapeShellArg( $params['physicalWidth'] ), $cmd ) ); # Size
-               wfDebug( __METHOD__ . ": Running custom convert command $cmd\n" );
-               $retval = 0;
-               $err = wfShellExecWithStderr( $cmd, $retval );
-
-               if ( $retval !== 0 ) {
-                       $this->logErrorForExternalProcess( $retval, $err, $cmd );
-
-                       return $this->getMediaTransformError( $params, $err );
-               }
-
-               return false; # No error
-       }
-
-       /**
-        * Transform an image using the built in GD library
-        *
-        * @param File $image File associated with this thumbnail
-        * @param array $params Array with scaler params
-        *
-        * @return MediaTransformError|bool Error object if error occurred, false (=no error) otherwise
-        */
-       protected function transformGd( $image, $params ) {
-               # Use PHP's builtin GD library functions.
-               # First find out what kind of file this is, and select the correct
-               # input routine for this.
-
-               $typemap = [
-                       'image/gif' => [ 'imagecreatefromgif', 'palette', false, 'imagegif' ],
-                       'image/jpeg' => [ 'imagecreatefromjpeg', 'truecolor', true,
-                               [ __CLASS__, 'imageJpegWrapper' ] ],
-                       'image/png' => [ 'imagecreatefrompng', 'bits', false, 'imagepng' ],
-                       'image/vnd.wap.wbmp' => [ 'imagecreatefromwbmp', 'palette', false, 'imagewbmp' ],
-                       'image/xbm' => [ 'imagecreatefromxbm', 'palette', false, 'imagexbm' ],
-               ];
-
-               if ( !isset( $typemap[$params['mimeType']] ) ) {
-                       $err = 'Image type not supported';
-                       wfDebug( "$err\n" );
-                       $errMsg = wfMessage( 'thumbnail_image-type' )->text();
-
-                       return $this->getMediaTransformError( $params, $errMsg );
-               }
-               list( $loader, $colorStyle, $useQuality, $saveType ) = $typemap[$params['mimeType']];
-
-               if ( !function_exists( $loader ) ) {
-                       $err = "Incomplete GD library configuration: missing function $loader";
-                       wfDebug( "$err\n" );
-                       $errMsg = wfMessage( 'thumbnail_gd-library', $loader )->text();
-
-                       return $this->getMediaTransformError( $params, $errMsg );
-               }
-
-               if ( !file_exists( $params['srcPath'] ) ) {
-                       $err = "File seems to be missing: {$params['srcPath']}";
-                       wfDebug( "$err\n" );
-                       $errMsg = wfMessage( 'thumbnail_image-missing', $params['srcPath'] )->text();
-
-                       return $this->getMediaTransformError( $params, $errMsg );
-               }
-
-               if ( filesize( $params['srcPath'] ) === 0 ) {
-                       $err = "Image file size seems to be zero.";
-                       wfDebug( "$err\n" );
-                       $errMsg = wfMessage( 'thumbnail_image-size-zero', $params['srcPath'] )->text();
-
-                       return $this->getMediaTransformError( $params, $errMsg );
-               }
-
-               $src_image = call_user_func( $loader, $params['srcPath'] );
-
-               $rotation = function_exists( 'imagerotate' ) && !isset( $params['disableRotation'] ) ?
-                       $this->getRotation( $image ) :
-                       0;
-               list( $width, $height ) = $this->extractPreRotationDimensions( $params, $rotation );
-               $dst_image = imagecreatetruecolor( $width, $height );
-
-               // Initialise the destination image to transparent instead of
-               // the default solid black, to support PNG and GIF transparency nicely
-               $background = imagecolorallocate( $dst_image, 0, 0, 0 );
-               imagecolortransparent( $dst_image, $background );
-               imagealphablending( $dst_image, false );
-
-               if ( $colorStyle == 'palette' ) {
-                       // Don't resample for paletted GIF images.
-                       // It may just uglify them, and completely breaks transparency.
-                       imagecopyresized( $dst_image, $src_image,
-                               0, 0, 0, 0,
-                               $width, $height,
-                               imagesx( $src_image ), imagesy( $src_image ) );
-               } else {
-                       imagecopyresampled( $dst_image, $src_image,
-                               0, 0, 0, 0,
-                               $width, $height,
-                               imagesx( $src_image ), imagesy( $src_image ) );
-               }
-
-               if ( $rotation % 360 != 0 && $rotation % 90 == 0 ) {
-                       $rot_image = imagerotate( $dst_image, $rotation, 0 );
-                       imagedestroy( $dst_image );
-                       $dst_image = $rot_image;
-               }
-
-               imagesavealpha( $dst_image, true );
-
-               $funcParams = [ $dst_image, $params['dstPath'] ];
-               if ( $useQuality && isset( $params['quality'] ) ) {
-                       $funcParams[] = $params['quality'];
-               }
-               call_user_func_array( $saveType, $funcParams );
-
-               imagedestroy( $dst_image );
-               imagedestroy( $src_image );
-
-               return false; # No error
-       }
-
-       /**
-        * Callback for transformGd when transforming jpeg images.
-        *
-        * @param resource $dst_image Image resource of the original image
-        * @param string $thumbPath File path to write the thumbnail image to
-        * @param int|null $quality Quality of the thumbnail from 1-100,
-        *    or null to use default quality.
-        */
-       static function imageJpegWrapper( $dst_image, $thumbPath, $quality = null ) {
-               global $wgJpegQuality;
-
-               if ( $quality === null ) {
-                       $quality = $wgJpegQuality;
-               }
-
-               imageinterlace( $dst_image );
-               imagejpeg( $dst_image, $thumbPath, $quality );
-       }
-
-       /**
-        * Returns whether the current scaler supports rotation (im and gd do)
-        *
-        * @return bool
-        */
-       public function canRotate() {
-               $scaler = $this->getScalerType( null, false );
-               switch ( $scaler ) {
-                       case 'im':
-                               # ImageMagick supports autorotation
-                               return true;
-                       case 'imext':
-                               # Imagick::rotateImage
-                               return true;
-                       case 'gd':
-                               # GD's imagerotate function is used to rotate images, but not
-                               # all precompiled PHP versions have that function
-                               return function_exists( 'imagerotate' );
-                       default:
-                               # Other scalers don't support rotation
-                               return false;
-               }
-       }
-
-       /**
-        * @see $wgEnableAutoRotation
-        * @return bool Whether auto rotation is enabled
-        */
-       public function autoRotateEnabled() {
-               global $wgEnableAutoRotation;
-
-               if ( $wgEnableAutoRotation === null ) {
-                       // Only enable auto-rotation when we actually can
-                       return $this->canRotate();
-               }
-
-               return $wgEnableAutoRotation;
-       }
-
-       /**
-        * @param File $file
-        * @param array $params Rotate parameters.
-        *   'rotation' clockwise rotation in degrees, allowed are multiples of 90
-        * @since 1.21
-        * @return bool|MediaTransformError
-        */
-       public function rotate( $file, $params ) {
-               global $wgImageMagickConvertCommand;
-
-               $rotation = ( $params['rotation'] + $this->getRotation( $file ) ) % 360;
-               $scene = false;
-
-               $scaler = $this->getScalerType( null, false );
-               switch ( $scaler ) {
-                       case 'im':
-                               $cmd = wfEscapeShellArg( $wgImageMagickConvertCommand ) . " " .
-                                       wfEscapeShellArg( $this->escapeMagickInput( $params['srcPath'], $scene ) ) .
-                                       " -rotate " . wfEscapeShellArg( "-$rotation" ) . " " .
-                                       wfEscapeShellArg( $this->escapeMagickOutput( $params['dstPath'] ) );
-                               wfDebug( __METHOD__ . ": running ImageMagick: $cmd\n" );
-                               $retval = 0;
-                               $err = wfShellExecWithStderr( $cmd, $retval );
-                               if ( $retval !== 0 ) {
-                                       $this->logErrorForExternalProcess( $retval, $err, $cmd );
-
-                                       return new MediaTransformError( 'thumbnail_error', 0, 0, $err );
-                               }
-
-                               return false;
-                       case 'imext':
-                               $im = new Imagick();
-                               $im->readImage( $params['srcPath'] );
-                               if ( !$im->rotateImage( new ImagickPixel( 'white' ), 360 - $rotation ) ) {
-                                       return new MediaTransformError( 'thumbnail_error', 0, 0,
-                                               "Error rotating $rotation degrees" );
-                               }
-                               $result = $im->writeImage( $params['dstPath'] );
-                               if ( !$result ) {
-                                       return new MediaTransformError( 'thumbnail_error', 0, 0,
-                                               "Unable to write image to {$params['dstPath']}" );
-                               }
-
-                               return false;
-                       default:
-                               return new MediaTransformError( 'thumbnail_error', 0, 0,
-                                       "$scaler rotation not implemented" );
-               }
-       }
-}
diff --git a/includes/media/BitmapHandler.php b/includes/media/BitmapHandler.php
new file mode 100644 (file)
index 0000000..cda037c
--- /dev/null
@@ -0,0 +1,607 @@
+<?php
+/**
+ * Generic handler for bitmap images.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Media
+ */
+
+/**
+ * Generic handler for bitmap images
+ *
+ * @ingroup Media
+ */
+class BitmapHandler extends TransformationalImageHandler {
+
+       /**
+        * Returns which scaler type should be used. Creates parent directories
+        * for $dstPath and returns 'client' on error
+        *
+        * @param string $dstPath
+        * @param bool $checkDstPath
+        * @return string|Callable One of client, im, custom, gd, imext or an array( object, method )
+        */
+       protected function getScalerType( $dstPath, $checkDstPath = true ) {
+               global $wgUseImageResize, $wgUseImageMagick, $wgCustomConvertCommand;
+
+               if ( !$dstPath && $checkDstPath ) {
+                       # No output path available, client side scaling only
+                       $scaler = 'client';
+               } elseif ( !$wgUseImageResize ) {
+                       $scaler = 'client';
+               } elseif ( $wgUseImageMagick ) {
+                       $scaler = 'im';
+               } elseif ( $wgCustomConvertCommand ) {
+                       $scaler = 'custom';
+               } elseif ( function_exists( 'imagecreatetruecolor' ) ) {
+                       $scaler = 'gd';
+               } elseif ( class_exists( 'Imagick' ) ) {
+                       $scaler = 'imext';
+               } else {
+                       $scaler = 'client';
+               }
+
+               return $scaler;
+       }
+
+       public function makeParamString( $params ) {
+               $res = parent::makeParamString( $params );
+               if ( isset( $params['interlace'] ) && $params['interlace'] ) {
+                       return "interlaced-{$res}";
+               } else {
+                       return $res;
+               }
+       }
+
+       public function parseParamString( $str ) {
+               $remainder = preg_replace( '/^interlaced-/', '', $str );
+               $params = parent::parseParamString( $remainder );
+               if ( $params === false ) {
+                       return false;
+               }
+               $params['interlace'] = $str !== $remainder;
+               return $params;
+       }
+
+       public function validateParam( $name, $value ) {
+               if ( $name === 'interlace' ) {
+                       return $value === false || $value === true;
+               } else {
+                       return parent::validateParam( $name, $value );
+               }
+       }
+
+       /**
+        * @param File $image
+        * @param array &$params
+        * @return bool
+        */
+       function normaliseParams( $image, &$params ) {
+               global $wgMaxInterlacingAreas;
+               if ( !parent::normaliseParams( $image, $params ) ) {
+                       return false;
+               }
+               $mimeType = $image->getMimeType();
+               $interlace = isset( $params['interlace'] ) && $params['interlace']
+                       && isset( $wgMaxInterlacingAreas[$mimeType] )
+                       && $this->getImageArea( $image ) <= $wgMaxInterlacingAreas[$mimeType];
+               $params['interlace'] = $interlace;
+               return true;
+       }
+
+       /**
+        * Get ImageMagick subsampling factors for the target JPEG pixel format.
+        *
+        * @param string $pixelFormat one of 'yuv444', 'yuv422', 'yuv420'
+        * @return array of string keys
+        */
+       protected function imageMagickSubsampling( $pixelFormat ) {
+               switch ( $pixelFormat ) {
+                       case 'yuv444':
+                               return [ '1x1', '1x1', '1x1' ];
+                       case 'yuv422':
+                               return [ '2x1', '1x1', '1x1' ];
+                       case 'yuv420':
+                               return [ '2x2', '1x1', '1x1' ];
+                       default:
+                               throw new MWException( 'Invalid pixel format for JPEG output' );
+               }
+       }
+
+       /**
+        * Transform an image using ImageMagick
+        *
+        * @param File $image File associated with this thumbnail
+        * @param array $params Array with scaler params
+        *
+        * @return MediaTransformError|bool Error object if error occurred, false (=no error) otherwise
+        */
+       protected function transformImageMagick( $image, $params ) {
+               # use ImageMagick
+               global $wgSharpenReductionThreshold, $wgSharpenParameter, $wgMaxAnimatedGifArea,
+                       $wgImageMagickTempDir, $wgImageMagickConvertCommand, $wgJpegPixelFormat,
+                       $wgJpegQuality;
+
+               $quality = [];
+               $sharpen = [];
+               $scene = false;
+               $animation_pre = [];
+               $animation_post = [];
+               $decoderHint = [];
+               $subsampling = [];
+
+               if ( $params['mimeType'] == 'image/jpeg' ) {
+                       $qualityVal = isset( $params['quality'] ) ? (string)$params['quality'] : null;
+                       $quality = [ '-quality', $qualityVal ?: (string)$wgJpegQuality ]; // 80% by default
+                       if ( $params['interlace'] ) {
+                               $animation_post = [ '-interlace', 'JPEG' ];
+                       }
+                       # Sharpening, see T8193
+                       if ( ( $params['physicalWidth'] + $params['physicalHeight'] )
+                               / ( $params['srcWidth'] + $params['srcHeight'] )
+                               < $wgSharpenReductionThreshold
+                       ) {
+                               $sharpen = [ '-sharpen', $wgSharpenParameter ];
+                       }
+                       if ( version_compare( $this->getMagickVersion(), "6.5.6" ) >= 0 ) {
+                               // JPEG decoder hint to reduce memory, available since IM 6.5.6-2
+                               $decoderHint = [ '-define', "jpeg:size={$params['physicalDimensions']}" ];
+                       }
+                       if ( $wgJpegPixelFormat ) {
+                               $factors = $this->imageMagickSubsampling( $wgJpegPixelFormat );
+                               $subsampling = [ '-sampling-factor', implode( ',', $factors ) ];
+                       }
+               } elseif ( $params['mimeType'] == 'image/png' ) {
+                       $quality = [ '-quality', '95' ]; // zlib 9, adaptive filtering
+                       if ( $params['interlace'] ) {
+                               $animation_post = [ '-interlace', 'PNG' ];
+                       }
+               } elseif ( $params['mimeType'] == 'image/webp' ) {
+                       $quality = [ '-quality', '95' ]; // zlib 9, adaptive filtering
+               } elseif ( $params['mimeType'] == 'image/gif' ) {
+                       if ( $this->getImageArea( $image ) > $wgMaxAnimatedGifArea ) {
+                               // Extract initial frame only; we're so big it'll
+                               // be a total drag. :P
+                               $scene = 0;
+                       } elseif ( $this->isAnimatedImage( $image ) ) {
+                               // Coalesce is needed to scale animated GIFs properly (T3017).
+                               $animation_pre = [ '-coalesce' ];
+                               // We optimize the output, but -optimize is broken,
+                               // use optimizeTransparency instead (T13822)
+                               if ( version_compare( $this->getMagickVersion(), "6.3.5" ) >= 0 ) {
+                                       $animation_post = [ '-fuzz', '5%', '-layers', 'optimizeTransparency' ];
+                               }
+                       }
+                       if ( $params['interlace'] && version_compare( $this->getMagickVersion(), "6.3.4" ) >= 0
+                               && !$this->isAnimatedImage( $image ) ) { // interlacing animated GIFs is a bad idea
+                               $animation_post[] = '-interlace';
+                               $animation_post[] = 'GIF';
+                       }
+               } elseif ( $params['mimeType'] == 'image/x-xcf' ) {
+                       // Before merging layers, we need to set the background
+                       // to be transparent to preserve alpha, as -layers merge
+                       // merges all layers on to a canvas filled with the
+                       // background colour. After merging we reset the background
+                       // to be white for the default background colour setting
+                       // in the PNG image (which is used in old IE)
+                       $animation_pre = [
+                               '-background', 'transparent',
+                               '-layers', 'merge',
+                               '-background', 'white',
+                       ];
+                       Wikimedia\suppressWarnings();
+                       $xcfMeta = unserialize( $image->getMetadata() );
+                       Wikimedia\restoreWarnings();
+                       if ( $xcfMeta
+                               && isset( $xcfMeta['colorType'] )
+                               && $xcfMeta['colorType'] === 'greyscale-alpha'
+                               && version_compare( $this->getMagickVersion(), "6.8.9-3" ) < 0
+                       ) {
+                               // T68323 - Greyscale images not rendered properly.
+                               // So only take the "red" channel.
+                               $channelOnly = [ '-channel', 'R', '-separate' ];
+                               $animation_pre = array_merge( $animation_pre, $channelOnly );
+                       }
+               }
+
+               // Use one thread only, to avoid deadlock bugs on OOM
+               $env = [ 'OMP_NUM_THREADS' => 1 ];
+               if ( strval( $wgImageMagickTempDir ) !== '' ) {
+                       $env['MAGICK_TMPDIR'] = $wgImageMagickTempDir;
+               }
+
+               $rotation = isset( $params['disableRotation'] ) ? 0 : $this->getRotation( $image );
+               list( $width, $height ) = $this->extractPreRotationDimensions( $params, $rotation );
+
+               $cmd = call_user_func_array( 'wfEscapeShellArg', array_merge(
+                       [ $wgImageMagickConvertCommand ],
+                       $quality,
+                       // Specify white background color, will be used for transparent images
+                       // in Internet Explorer/Windows instead of default black.
+                       [ '-background', 'white' ],
+                       $decoderHint,
+                       [ $this->escapeMagickInput( $params['srcPath'], $scene ) ],
+                       $animation_pre,
+                       // For the -thumbnail option a "!" is needed to force exact size,
+                       // or ImageMagick may decide your ratio is wrong and slice off
+                       // a pixel.
+                       [ '-thumbnail', "{$width}x{$height}!" ],
+                       // Add the source url as a comment to the thumb, but don't add the flag if there's no comment
+                       ( $params['comment'] !== ''
+                               ? [ '-set', 'comment', $this->escapeMagickProperty( $params['comment'] ) ]
+                               : [] ),
+                       // T108616: Avoid exposure of local file path
+                       [ '+set', 'Thumb::URI' ],
+                       [ '-depth', 8 ],
+                       $sharpen,
+                       [ '-rotate', "-$rotation" ],
+                       $subsampling,
+                       $animation_post,
+                       [ $this->escapeMagickOutput( $params['dstPath'] ) ] ) );
+
+               wfDebug( __METHOD__ . ": running ImageMagick: $cmd\n" );
+               $retval = 0;
+               $err = wfShellExecWithStderr( $cmd, $retval, $env );
+
+               if ( $retval !== 0 ) {
+                       $this->logErrorForExternalProcess( $retval, $err, $cmd );
+
+                       return $this->getMediaTransformError( $params, "$err\nError code: $retval" );
+               }
+
+               return false; # No error
+       }
+
+       /**
+        * Transform an image using the Imagick PHP extension
+        *
+        * @param File $image File associated with this thumbnail
+        * @param array $params Array with scaler params
+        *
+        * @return MediaTransformError Error|bool object if error occurred, false (=no error) otherwise
+        */
+       protected function transformImageMagickExt( $image, $params ) {
+               global $wgSharpenReductionThreshold, $wgSharpenParameter, $wgMaxAnimatedGifArea,
+                       $wgJpegPixelFormat, $wgJpegQuality;
+
+               try {
+                       $im = new Imagick();
+                       $im->readImage( $params['srcPath'] );
+
+                       if ( $params['mimeType'] == 'image/jpeg' ) {
+                               // Sharpening, see T8193
+                               if ( ( $params['physicalWidth'] + $params['physicalHeight'] )
+                                       / ( $params['srcWidth'] + $params['srcHeight'] )
+                                       < $wgSharpenReductionThreshold
+                               ) {
+                                       // Hack, since $wgSharpenParameter is written specifically for the command line convert
+                                       list( $radius, $sigma ) = explode( 'x', $wgSharpenParameter );
+                                       $im->sharpenImage( $radius, $sigma );
+                               }
+                               $qualityVal = isset( $params['quality'] ) ? (string)$params['quality'] : null;
+                               $im->setCompressionQuality( $qualityVal ?: $wgJpegQuality );
+                               if ( $params['interlace'] ) {
+                                       $im->setInterlaceScheme( Imagick::INTERLACE_JPEG );
+                               }
+                               if ( $wgJpegPixelFormat ) {
+                                       $factors = $this->imageMagickSubsampling( $wgJpegPixelFormat );
+                                       $im->setSamplingFactors( $factors );
+                               }
+                       } elseif ( $params['mimeType'] == 'image/png' ) {
+                               $im->setCompressionQuality( 95 );
+                               if ( $params['interlace'] ) {
+                                       $im->setInterlaceScheme( Imagick::INTERLACE_PNG );
+                               }
+                       } elseif ( $params['mimeType'] == 'image/gif' ) {
+                               if ( $this->getImageArea( $image ) > $wgMaxAnimatedGifArea ) {
+                                       // Extract initial frame only; we're so big it'll
+                                       // be a total drag. :P
+                                       $im->setImageScene( 0 );
+                               } elseif ( $this->isAnimatedImage( $image ) ) {
+                                       // Coalesce is needed to scale animated GIFs properly (T3017).
+                                       $im = $im->coalesceImages();
+                               }
+                               // GIF interlacing is only available since 6.3.4
+                               $v = Imagick::getVersion();
+                               preg_match( '/ImageMagick ([0-9]+\.[0-9]+\.[0-9]+)/', $v['versionString'], $v );
+
+                               if ( $params['interlace'] && version_compare( $v[1], '6.3.4' ) >= 0 ) {
+                                       $im->setInterlaceScheme( Imagick::INTERLACE_GIF );
+                               }
+                       }
+
+                       $rotation = isset( $params['disableRotation'] ) ? 0 : $this->getRotation( $image );
+                       list( $width, $height ) = $this->extractPreRotationDimensions( $params, $rotation );
+
+                       $im->setImageBackgroundColor( new ImagickPixel( 'white' ) );
+
+                       // Call Imagick::thumbnailImage on each frame
+                       foreach ( $im as $i => $frame ) {
+                               if ( !$frame->thumbnailImage( $width, $height, /* fit */ false ) ) {
+                                       return $this->getMediaTransformError( $params, "Error scaling frame $i" );
+                               }
+                       }
+                       $im->setImageDepth( 8 );
+
+                       if ( $rotation ) {
+                               if ( !$im->rotateImage( new ImagickPixel( 'white' ), 360 - $rotation ) ) {
+                                       return $this->getMediaTransformError( $params, "Error rotating $rotation degrees" );
+                               }
+                       }
+
+                       if ( $this->isAnimatedImage( $image ) ) {
+                               wfDebug( __METHOD__ . ": Writing animated thumbnail\n" );
+                               // This is broken somehow... can't find out how to fix it
+                               $result = $im->writeImages( $params['dstPath'], true );
+                       } else {
+                               $result = $im->writeImage( $params['dstPath'] );
+                       }
+                       if ( !$result ) {
+                               return $this->getMediaTransformError( $params,
+                                       "Unable to write thumbnail to {$params['dstPath']}" );
+                       }
+               } catch ( ImagickException $e ) {
+                       return $this->getMediaTransformError( $params, $e->getMessage() );
+               }
+
+               return false;
+       }
+
+       /**
+        * Transform an image using a custom command
+        *
+        * @param File $image File associated with this thumbnail
+        * @param array $params Array with scaler params
+        *
+        * @return MediaTransformError Error|bool object if error occurred, false (=no error) otherwise
+        */
+       protected function transformCustom( $image, $params ) {
+               # Use a custom convert command
+               global $wgCustomConvertCommand;
+
+               # Variables: %s %d %w %h
+               $src = wfEscapeShellArg( $params['srcPath'] );
+               $dst = wfEscapeShellArg( $params['dstPath'] );
+               $cmd = $wgCustomConvertCommand;
+               $cmd = str_replace( '%s', $src, str_replace( '%d', $dst, $cmd ) ); # Filenames
+               $cmd = str_replace( '%h', wfEscapeShellArg( $params['physicalHeight'] ),
+                       str_replace( '%w', wfEscapeShellArg( $params['physicalWidth'] ), $cmd ) ); # Size
+               wfDebug( __METHOD__ . ": Running custom convert command $cmd\n" );
+               $retval = 0;
+               $err = wfShellExecWithStderr( $cmd, $retval );
+
+               if ( $retval !== 0 ) {
+                       $this->logErrorForExternalProcess( $retval, $err, $cmd );
+
+                       return $this->getMediaTransformError( $params, $err );
+               }
+
+               return false; # No error
+       }
+
+       /**
+        * Transform an image using the built in GD library
+        *
+        * @param File $image File associated with this thumbnail
+        * @param array $params Array with scaler params
+        *
+        * @return MediaTransformError|bool Error object if error occurred, false (=no error) otherwise
+        */
+       protected function transformGd( $image, $params ) {
+               # Use PHP's builtin GD library functions.
+               # First find out what kind of file this is, and select the correct
+               # input routine for this.
+
+               $typemap = [
+                       'image/gif' => [ 'imagecreatefromgif', 'palette', false, 'imagegif' ],
+                       'image/jpeg' => [ 'imagecreatefromjpeg', 'truecolor', true,
+                               [ __CLASS__, 'imageJpegWrapper' ] ],
+                       'image/png' => [ 'imagecreatefrompng', 'bits', false, 'imagepng' ],
+                       'image/vnd.wap.wbmp' => [ 'imagecreatefromwbmp', 'palette', false, 'imagewbmp' ],
+                       'image/xbm' => [ 'imagecreatefromxbm', 'palette', false, 'imagexbm' ],
+               ];
+
+               if ( !isset( $typemap[$params['mimeType']] ) ) {
+                       $err = 'Image type not supported';
+                       wfDebug( "$err\n" );
+                       $errMsg = wfMessage( 'thumbnail_image-type' )->text();
+
+                       return $this->getMediaTransformError( $params, $errMsg );
+               }
+               list( $loader, $colorStyle, $useQuality, $saveType ) = $typemap[$params['mimeType']];
+
+               if ( !function_exists( $loader ) ) {
+                       $err = "Incomplete GD library configuration: missing function $loader";
+                       wfDebug( "$err\n" );
+                       $errMsg = wfMessage( 'thumbnail_gd-library', $loader )->text();
+
+                       return $this->getMediaTransformError( $params, $errMsg );
+               }
+
+               if ( !file_exists( $params['srcPath'] ) ) {
+                       $err = "File seems to be missing: {$params['srcPath']}";
+                       wfDebug( "$err\n" );
+                       $errMsg = wfMessage( 'thumbnail_image-missing', $params['srcPath'] )->text();
+
+                       return $this->getMediaTransformError( $params, $errMsg );
+               }
+
+               if ( filesize( $params['srcPath'] ) === 0 ) {
+                       $err = "Image file size seems to be zero.";
+                       wfDebug( "$err\n" );
+                       $errMsg = wfMessage( 'thumbnail_image-size-zero', $params['srcPath'] )->text();
+
+                       return $this->getMediaTransformError( $params, $errMsg );
+               }
+
+               $src_image = call_user_func( $loader, $params['srcPath'] );
+
+               $rotation = function_exists( 'imagerotate' ) && !isset( $params['disableRotation'] ) ?
+                       $this->getRotation( $image ) :
+                       0;
+               list( $width, $height ) = $this->extractPreRotationDimensions( $params, $rotation );
+               $dst_image = imagecreatetruecolor( $width, $height );
+
+               // Initialise the destination image to transparent instead of
+               // the default solid black, to support PNG and GIF transparency nicely
+               $background = imagecolorallocate( $dst_image, 0, 0, 0 );
+               imagecolortransparent( $dst_image, $background );
+               imagealphablending( $dst_image, false );
+
+               if ( $colorStyle == 'palette' ) {
+                       // Don't resample for paletted GIF images.
+                       // It may just uglify them, and completely breaks transparency.
+                       imagecopyresized( $dst_image, $src_image,
+                               0, 0, 0, 0,
+                               $width, $height,
+                               imagesx( $src_image ), imagesy( $src_image ) );
+               } else {
+                       imagecopyresampled( $dst_image, $src_image,
+                               0, 0, 0, 0,
+                               $width, $height,
+                               imagesx( $src_image ), imagesy( $src_image ) );
+               }
+
+               if ( $rotation % 360 != 0 && $rotation % 90 == 0 ) {
+                       $rot_image = imagerotate( $dst_image, $rotation, 0 );
+                       imagedestroy( $dst_image );
+                       $dst_image = $rot_image;
+               }
+
+               imagesavealpha( $dst_image, true );
+
+               $funcParams = [ $dst_image, $params['dstPath'] ];
+               if ( $useQuality && isset( $params['quality'] ) ) {
+                       $funcParams[] = $params['quality'];
+               }
+               call_user_func_array( $saveType, $funcParams );
+
+               imagedestroy( $dst_image );
+               imagedestroy( $src_image );
+
+               return false; # No error
+       }
+
+       /**
+        * Callback for transformGd when transforming jpeg images.
+        *
+        * @param resource $dst_image Image resource of the original image
+        * @param string $thumbPath File path to write the thumbnail image to
+        * @param int|null $quality Quality of the thumbnail from 1-100,
+        *    or null to use default quality.
+        */
+       static function imageJpegWrapper( $dst_image, $thumbPath, $quality = null ) {
+               global $wgJpegQuality;
+
+               if ( $quality === null ) {
+                       $quality = $wgJpegQuality;
+               }
+
+               imageinterlace( $dst_image );
+               imagejpeg( $dst_image, $thumbPath, $quality );
+       }
+
+       /**
+        * Returns whether the current scaler supports rotation (im and gd do)
+        *
+        * @return bool
+        */
+       public function canRotate() {
+               $scaler = $this->getScalerType( null, false );
+               switch ( $scaler ) {
+                       case 'im':
+                               # ImageMagick supports autorotation
+                               return true;
+                       case 'imext':
+                               # Imagick::rotateImage
+                               return true;
+                       case 'gd':
+                               # GD's imagerotate function is used to rotate images, but not
+                               # all precompiled PHP versions have that function
+                               return function_exists( 'imagerotate' );
+                       default:
+                               # Other scalers don't support rotation
+                               return false;
+               }
+       }
+
+       /**
+        * @see $wgEnableAutoRotation
+        * @return bool Whether auto rotation is enabled
+        */
+       public function autoRotateEnabled() {
+               global $wgEnableAutoRotation;
+
+               if ( $wgEnableAutoRotation === null ) {
+                       // Only enable auto-rotation when we actually can
+                       return $this->canRotate();
+               }
+
+               return $wgEnableAutoRotation;
+       }
+
+       /**
+        * @param File $file
+        * @param array $params Rotate parameters.
+        *   'rotation' clockwise rotation in degrees, allowed are multiples of 90
+        * @since 1.21
+        * @return bool|MediaTransformError
+        */
+       public function rotate( $file, $params ) {
+               global $wgImageMagickConvertCommand;
+
+               $rotation = ( $params['rotation'] + $this->getRotation( $file ) ) % 360;
+               $scene = false;
+
+               $scaler = $this->getScalerType( null, false );
+               switch ( $scaler ) {
+                       case 'im':
+                               $cmd = wfEscapeShellArg( $wgImageMagickConvertCommand ) . " " .
+                                       wfEscapeShellArg( $this->escapeMagickInput( $params['srcPath'], $scene ) ) .
+                                       " -rotate " . wfEscapeShellArg( "-$rotation" ) . " " .
+                                       wfEscapeShellArg( $this->escapeMagickOutput( $params['dstPath'] ) );
+                               wfDebug( __METHOD__ . ": running ImageMagick: $cmd\n" );
+                               $retval = 0;
+                               $err = wfShellExecWithStderr( $cmd, $retval );
+                               if ( $retval !== 0 ) {
+                                       $this->logErrorForExternalProcess( $retval, $err, $cmd );
+
+                                       return new MediaTransformError( 'thumbnail_error', 0, 0, $err );
+                               }
+
+                               return false;
+                       case 'imext':
+                               $im = new Imagick();
+                               $im->readImage( $params['srcPath'] );
+                               if ( !$im->rotateImage( new ImagickPixel( 'white' ), 360 - $rotation ) ) {
+                                       return new MediaTransformError( 'thumbnail_error', 0, 0,
+                                               "Error rotating $rotation degrees" );
+                               }
+                               $result = $im->writeImage( $params['dstPath'] );
+                               if ( !$result ) {
+                                       return new MediaTransformError( 'thumbnail_error', 0, 0,
+                                               "Unable to write image to {$params['dstPath']}" );
+                               }
+
+                               return false;
+                       default:
+                               return new MediaTransformError( 'thumbnail_error', 0, 0,
+                                       "$scaler rotation not implemented" );
+               }
+       }
+}
diff --git a/includes/media/BitmapHandler_ClientOnly.php b/includes/media/BitmapHandler_ClientOnly.php
new file mode 100644 (file)
index 0000000..fa5b0a6
--- /dev/null
@@ -0,0 +1,59 @@
+<?php
+/**
+ * Handler for bitmap images that will be resized by clients.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Media
+ */
+
+/**
+ * Handler for bitmap images that will be resized by clients.
+ *
+ * This is not used by default but can be assigned to some image types
+ * using $wgMediaHandlers.
+ *
+ * @ingroup Media
+ */
+// phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
+class BitmapHandler_ClientOnly extends BitmapHandler {
+
+       /**
+        * @param File $image
+        * @param array &$params
+        * @return bool
+        */
+       function normaliseParams( $image, &$params ) {
+               return ImageHandler::normaliseParams( $image, $params );
+       }
+
+       /**
+        * @param File $image
+        * @param string $dstPath
+        * @param string $dstUrl
+        * @param array $params
+        * @param int $flags
+        * @return ThumbnailImage|TransformParameterError
+        */
+       function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 ) {
+               if ( !$this->normaliseParams( $image, $params ) ) {
+                       return new TransformParameterError( $params );
+               }
+
+               return new ThumbnailImage( $image, $image->getUrl(), $image->getLocalRefPath(), $params );
+       }
+}
diff --git a/includes/media/Bitmap_ClientOnly.php b/includes/media/Bitmap_ClientOnly.php
deleted file mode 100644 (file)
index fa5b0a6..0000000
+++ /dev/null
@@ -1,59 +0,0 @@
-<?php
-/**
- * Handler for bitmap images that will be resized by clients.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Media
- */
-
-/**
- * Handler for bitmap images that will be resized by clients.
- *
- * This is not used by default but can be assigned to some image types
- * using $wgMediaHandlers.
- *
- * @ingroup Media
- */
-// phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
-class BitmapHandler_ClientOnly extends BitmapHandler {
-
-       /**
-        * @param File $image
-        * @param array &$params
-        * @return bool
-        */
-       function normaliseParams( $image, &$params ) {
-               return ImageHandler::normaliseParams( $image, $params );
-       }
-
-       /**
-        * @param File $image
-        * @param string $dstPath
-        * @param string $dstUrl
-        * @param array $params
-        * @param int $flags
-        * @return ThumbnailImage|TransformParameterError
-        */
-       function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 ) {
-               if ( !$this->normaliseParams( $image, $params ) ) {
-                       return new TransformParameterError( $params );
-               }
-
-               return new ThumbnailImage( $image, $image->getUrl(), $image->getLocalRefPath(), $params );
-       }
-}
diff --git a/includes/media/BmpHandler.php b/includes/media/BmpHandler.php
new file mode 100644 (file)
index 0000000..0229ac1
--- /dev/null
@@ -0,0 +1,80 @@
+<?php
+/**
+ * Handler for Microsoft's bitmap format.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Media
+ */
+
+/**
+ * Handler for Microsoft's bitmap format; getimagesize() doesn't
+ * support these files
+ *
+ * @ingroup Media
+ */
+class BmpHandler extends BitmapHandler {
+       /**
+        * @param File $file
+        * @return bool
+        */
+       public function mustRender( $file ) {
+               return true;
+       }
+
+       /**
+        * Render files as PNG
+        *
+        * @param string $text
+        * @param string $mime
+        * @param array $params
+        * @return array
+        */
+       function getThumbType( $text, $mime, $params = null ) {
+               return [ 'png', 'image/png' ];
+       }
+
+       /**
+        * Get width and height from the bmp header.
+        *
+        * @param File|FSFile $image
+        * @param string $filename
+        * @return array
+        */
+       function getImageSize( $image, $filename ) {
+               $f = fopen( $filename, 'rb' );
+               if ( !$f ) {
+                       return false;
+               }
+               $header = fread( $f, 54 );
+               fclose( $f );
+
+               // Extract binary form of width and height from the header
+               $w = substr( $header, 18, 4 );
+               $h = substr( $header, 22, 4 );
+
+               // Convert the unsigned long 32 bits (little endian):
+               try {
+                       $w = wfUnpack( 'V', $w, 4 );
+                       $h = wfUnpack( 'V', $h, 4 );
+               } catch ( Exception $e ) {
+                       return false;
+               }
+
+               return [ $w[1], $h[1] ];
+       }
+}
diff --git a/includes/media/DjVu.php b/includes/media/DjVu.php
deleted file mode 100644 (file)
index 2541e35..0000000
+++ /dev/null
@@ -1,464 +0,0 @@
-<?php
-/**
- * Handler for DjVu images.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Media
- */
-
-/**
- * Handler for DjVu images
- *
- * @ingroup Media
- */
-class DjVuHandler extends ImageHandler {
-       const EXPENSIVE_SIZE_LIMIT = 10485760; // 10MiB
-
-       /**
-        * @return bool
-        */
-       function isEnabled() {
-               global $wgDjvuRenderer, $wgDjvuDump, $wgDjvuToXML;
-               if ( !$wgDjvuRenderer || ( !$wgDjvuDump && !$wgDjvuToXML ) ) {
-                       wfDebug( "DjVu is disabled, please set \$wgDjvuRenderer and \$wgDjvuDump\n" );
-
-                       return false;
-               } else {
-                       return true;
-               }
-       }
-
-       /**
-        * @param File $file
-        * @return bool
-        */
-       public function mustRender( $file ) {
-               return true;
-       }
-
-       /**
-        * True if creating thumbnails from the file is large or otherwise resource-intensive.
-        * @param File $file
-        * @return bool
-        */
-       public function isExpensiveToThumbnail( $file ) {
-               return $file->getSize() > static::EXPENSIVE_SIZE_LIMIT;
-       }
-
-       /**
-        * @param File $file
-        * @return bool
-        */
-       public function isMultiPage( $file ) {
-               return true;
-       }
-
-       /**
-        * @return array
-        */
-       public function getParamMap() {
-               return [
-                       'img_width' => 'width',
-                       'img_page' => 'page',
-               ];
-       }
-
-       /**
-        * @param string $name
-        * @param mixed $value
-        * @return bool
-        */
-       public function validateParam( $name, $value ) {
-               if ( $name === 'page' && trim( $value ) !== (string)intval( $value ) ) {
-                       // Extra junk on the end of page, probably actually a caption
-                       // e.g. [[File:Foo.djvu|thumb|Page 3 of the document shows foo]]
-                       return false;
-               }
-               if ( in_array( $name, [ 'width', 'height', 'page' ] ) ) {
-                       if ( $value <= 0 ) {
-                               return false;
-                       } else {
-                               return true;
-                       }
-               } else {
-                       return false;
-               }
-       }
-
-       /**
-        * @param array $params
-        * @return bool|string
-        */
-       public function makeParamString( $params ) {
-               $page = isset( $params['page'] ) ? $params['page'] : 1;
-               if ( !isset( $params['width'] ) ) {
-                       return false;
-               }
-
-               return "page{$page}-{$params['width']}px";
-       }
-
-       /**
-        * @param string $str
-        * @return array|bool
-        */
-       public function parseParamString( $str ) {
-               $m = false;
-               if ( preg_match( '/^page(\d+)-(\d+)px$/', $str, $m ) ) {
-                       return [ 'width' => $m[2], 'page' => $m[1] ];
-               } else {
-                       return false;
-               }
-       }
-
-       /**
-        * @param array $params
-        * @return array
-        */
-       function getScriptParams( $params ) {
-               return [
-                       'width' => $params['width'],
-                       'page' => $params['page'],
-               ];
-       }
-
-       /**
-        * @param File $image
-        * @param string $dstPath
-        * @param string $dstUrl
-        * @param array $params
-        * @param int $flags
-        * @return MediaTransformError|ThumbnailImage|TransformParameterError
-        */
-       function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 ) {
-               global $wgDjvuRenderer, $wgDjvuPostProcessor;
-
-               if ( !$this->normaliseParams( $image, $params ) ) {
-                       return new TransformParameterError( $params );
-               }
-               $width = $params['width'];
-               $height = $params['height'];
-               $page = $params['page'];
-
-               if ( $flags & self::TRANSFORM_LATER ) {
-                       $params = [
-                               'width' => $width,
-                               'height' => $height,
-                               'page' => $page
-                       ];
-
-                       return new ThumbnailImage( $image, $dstUrl, $dstPath, $params );
-               }
-
-               if ( !wfMkdirParents( dirname( $dstPath ), null, __METHOD__ ) ) {
-                       return new MediaTransformError(
-                               'thumbnail_error',
-                               $width,
-                               $height,
-                               wfMessage( 'thumbnail_dest_directory' )
-                       );
-               }
-
-               // Get local copy source for shell scripts
-               // Thumbnail extraction is very inefficient for large files.
-               // Provide a way to pool count limit the number of downloaders.
-               if ( $image->getSize() >= 1e7 ) { // 10MB
-                       $work = new PoolCounterWorkViaCallback( 'GetLocalFileCopy', sha1( $image->getName() ),
-                               [
-                                       'doWork' => function () use ( $image ) {
-                                               return $image->getLocalRefPath();
-                                       }
-                               ]
-                       );
-                       $srcPath = $work->execute();
-               } else {
-                       $srcPath = $image->getLocalRefPath();
-               }
-
-               if ( $srcPath === false ) { // Failed to get local copy
-                       wfDebugLog( 'thumbnail',
-                               sprintf( 'Thumbnail failed on %s: could not get local copy of "%s"',
-                                       wfHostname(), $image->getName() ) );
-
-                       return new MediaTransformError( 'thumbnail_error',
-                               $params['width'], $params['height'],
-                               wfMessage( 'filemissing' )
-                       );
-               }
-
-               # Use a subshell (brackets) to aggregate stderr from both pipeline commands
-               # before redirecting it to the overall stdout. This works in both Linux and Windows XP.
-               $cmd = '(' . wfEscapeShellArg(
-                       $wgDjvuRenderer,
-                       "-format=ppm",
-                       "-page={$page}",
-                       "-size={$params['physicalWidth']}x{$params['physicalHeight']}",
-                       $srcPath );
-               if ( $wgDjvuPostProcessor ) {
-                       $cmd .= " | {$wgDjvuPostProcessor}";
-               }
-               $cmd .= ' > ' . wfEscapeShellArg( $dstPath ) . ') 2>&1';
-               wfDebug( __METHOD__ . ": $cmd\n" );
-               $retval = '';
-               $err = wfShellExec( $cmd, $retval );
-
-               $removed = $this->removeBadFile( $dstPath, $retval );
-               if ( $retval != 0 || $removed ) {
-                       $this->logErrorForExternalProcess( $retval, $err, $cmd );
-                       return new MediaTransformError( 'thumbnail_error', $width, $height, $err );
-               } else {
-                       $params = [
-                               'width' => $width,
-                               'height' => $height,
-                               'page' => $page
-                       ];
-
-                       return new ThumbnailImage( $image, $dstUrl, $dstPath, $params );
-               }
-       }
-
-       /**
-        * Cache an instance of DjVuImage in an Image object, return that instance
-        *
-        * @param File|FSFile $image
-        * @param string $path
-        * @return DjVuImage
-        */
-       function getDjVuImage( $image, $path ) {
-               if ( !$image ) {
-                       $deja = new DjVuImage( $path );
-               } elseif ( !isset( $image->dejaImage ) ) {
-                       $deja = $image->dejaImage = new DjVuImage( $path );
-               } else {
-                       $deja = $image->dejaImage;
-               }
-
-               return $deja;
-       }
-
-       /**
-        * Get metadata, unserializing it if neccessary.
-        *
-        * @param File $file The DjVu file in question
-        * @return string XML metadata as a string.
-        * @throws MWException
-        */
-       private function getUnserializedMetadata( File $file ) {
-               $metadata = $file->getMetadata();
-               if ( substr( $metadata, 0, 3 ) === '<?xml' ) {
-                       // Old style. Not serialized but instead just a raw string of XML.
-                       return $metadata;
-               }
-
-               Wikimedia\suppressWarnings();
-               $unser = unserialize( $metadata );
-               Wikimedia\restoreWarnings();
-               if ( is_array( $unser ) ) {
-                       if ( isset( $unser['error'] ) ) {
-                               return false;
-                       } elseif ( isset( $unser['xml'] ) ) {
-                               return $unser['xml'];
-                       } else {
-                               // Should never ever reach here.
-                               throw new MWException( "Error unserializing DjVu metadata." );
-                       }
-               }
-
-               // unserialize failed. Guess it wasn't really serialized after all,
-               return $metadata;
-       }
-
-       /**
-        * Cache a document tree for the DjVu XML metadata
-        * @param File $image
-        * @param bool $gettext DOCUMENT (Default: false)
-        * @return bool|SimpleXMLElement
-        */
-       public function getMetaTree( $image, $gettext = false ) {
-               if ( $gettext && isset( $image->djvuTextTree ) ) {
-                       return $image->djvuTextTree;
-               }
-               if ( !$gettext && isset( $image->dejaMetaTree ) ) {
-                       return $image->dejaMetaTree;
-               }
-
-               $metadata = $this->getUnserializedMetadata( $image );
-               if ( !$this->isMetadataValid( $image, $metadata ) ) {
-                       wfDebug( "DjVu XML metadata is invalid or missing, should have been fixed in upgradeRow\n" );
-
-                       return false;
-               }
-
-               $trees = $this->extractTreesFromMetadata( $metadata );
-               $image->djvuTextTree = $trees['TextTree'];
-               $image->dejaMetaTree = $trees['MetaTree'];
-
-               if ( $gettext ) {
-                       return $image->djvuTextTree;
-               } else {
-                       return $image->dejaMetaTree;
-               }
-       }
-
-       /**
-        * Extracts metadata and text trees from metadata XML in string form
-        * @param string $metadata XML metadata as a string
-        * @return array
-        */
-       protected function extractTreesFromMetadata( $metadata ) {
-               Wikimedia\suppressWarnings();
-               try {
-                       // Set to false rather than null to avoid further attempts
-                       $metaTree = false;
-                       $textTree = false;
-                       $tree = new SimpleXMLElement( $metadata, LIBXML_PARSEHUGE );
-                       if ( $tree->getName() == 'mw-djvu' ) {
-                               /** @var SimpleXMLElement $b */
-                               foreach ( $tree->children() as $b ) {
-                                       if ( $b->getName() == 'DjVuTxt' ) {
-                                               // @todo File::djvuTextTree and File::dejaMetaTree are declared
-                                               // dynamically. Add a public File::$data to facilitate this?
-                                               $textTree = $b;
-                                       } elseif ( $b->getName() == 'DjVuXML' ) {
-                                               $metaTree = $b;
-                                       }
-                               }
-                       } else {
-                               $metaTree = $tree;
-                       }
-               } catch ( Exception $e ) {
-                       wfDebug( "Bogus multipage XML metadata\n" );
-               }
-               Wikimedia\restoreWarnings();
-
-               return [ 'MetaTree' => $metaTree, 'TextTree' => $textTree ];
-       }
-
-       function getImageSize( $image, $path ) {
-               return $this->getDjVuImage( $image, $path )->getImageSize();
-       }
-
-       function getThumbType( $ext, $mime, $params = null ) {
-               global $wgDjvuOutputExtension;
-               static $mime;
-               if ( !isset( $mime ) ) {
-                       $magic = MediaWiki\MediaWikiServices::getInstance()->getMimeAnalyzer();
-                       $mime = $magic->guessTypesForExtension( $wgDjvuOutputExtension );
-               }
-
-               return [ $wgDjvuOutputExtension, $mime ];
-       }
-
-       function getMetadata( $image, $path ) {
-               wfDebug( "Getting DjVu metadata for $path\n" );
-
-               $xml = $this->getDjVuImage( $image, $path )->retrieveMetaData();
-               if ( $xml === false ) {
-                       // Special value so that we don't repetitively try and decode a broken file.
-                       return serialize( [ 'error' => 'Error extracting metadata' ] );
-               } else {
-                       return serialize( [ 'xml' => $xml ] );
-               }
-       }
-
-       function getMetadataType( $image ) {
-               return 'djvuxml';
-       }
-
-       function isMetadataValid( $image, $metadata ) {
-               return !empty( $metadata ) && $metadata != serialize( [] );
-       }
-
-       function pageCount( File $image ) {
-               $info = $this->getDimensionInfo( $image );
-
-               return $info ? $info['pageCount'] : false;
-       }
-
-       function getPageDimensions( File $image, $page ) {
-               $index = $page - 1; // MW starts pages at 1
-
-               $info = $this->getDimensionInfo( $image );
-               if ( $info && isset( $info['dimensionsByPage'][$index] ) ) {
-                       return $info['dimensionsByPage'][$index];
-               }
-
-               return false;
-       }
-
-       protected function getDimensionInfo( File $file ) {
-               $cache = ObjectCache::getMainWANInstance();
-               return $cache->getWithSetCallback(
-                       $cache->makeKey( 'file-djvu', 'dimensions', $file->getSha1() ),
-                       $cache::TTL_INDEFINITE,
-                       function () use ( $file ) {
-                               $tree = $this->getMetaTree( $file );
-                               return $this->getDimensionInfoFromMetaTree( $tree );
-                       },
-                       [ 'pcTTL' => $cache::TTL_INDEFINITE ]
-               );
-       }
-
-       /**
-        * Given an XML metadata tree, returns dimension information about the document
-        * @param bool|SimpleXMLElement $metatree The file's XML metadata tree
-        * @return bool|array
-        */
-       protected function getDimensionInfoFromMetaTree( $metatree ) {
-               if ( !$metatree ) {
-                       return false;
-               }
-
-               $dimsByPage = [];
-               $count = count( $metatree->xpath( '//OBJECT' ) );
-               for ( $i = 0; $i < $count; $i++ ) {
-                       $o = $metatree->BODY[0]->OBJECT[$i];
-                       if ( $o ) {
-                               $dimsByPage[$i] = [
-                                       'width' => (int)$o['width'],
-                                       'height' => (int)$o['height'],
-                               ];
-                       } else {
-                               $dimsByPage[$i] = false;
-                       }
-               }
-
-               return [ 'pageCount' => $count, 'dimensionsByPage' => $dimsByPage ];
-       }
-
-       /**
-        * @param File $image
-        * @param int $page Page number to get information for
-        * @return bool|string Page text or false when no text found.
-        */
-       function getPageText( File $image, $page ) {
-               $tree = $this->getMetaTree( $image, true );
-               if ( !$tree ) {
-                       return false;
-               }
-
-               $o = $tree->BODY[0]->PAGE[$page - 1];
-               if ( $o ) {
-                       $txt = $o['value'];
-
-                       return $txt;
-               } else {
-                       return false;
-               }
-       }
-}
diff --git a/includes/media/DjVuHandler.php b/includes/media/DjVuHandler.php
new file mode 100644 (file)
index 0000000..2541e35
--- /dev/null
@@ -0,0 +1,464 @@
+<?php
+/**
+ * Handler for DjVu images.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Media
+ */
+
+/**
+ * Handler for DjVu images
+ *
+ * @ingroup Media
+ */
+class DjVuHandler extends ImageHandler {
+       const EXPENSIVE_SIZE_LIMIT = 10485760; // 10MiB
+
+       /**
+        * @return bool
+        */
+       function isEnabled() {
+               global $wgDjvuRenderer, $wgDjvuDump, $wgDjvuToXML;
+               if ( !$wgDjvuRenderer || ( !$wgDjvuDump && !$wgDjvuToXML ) ) {
+                       wfDebug( "DjVu is disabled, please set \$wgDjvuRenderer and \$wgDjvuDump\n" );
+
+                       return false;
+               } else {
+                       return true;
+               }
+       }
+
+       /**
+        * @param File $file
+        * @return bool
+        */
+       public function mustRender( $file ) {
+               return true;
+       }
+
+       /**
+        * True if creating thumbnails from the file is large or otherwise resource-intensive.
+        * @param File $file
+        * @return bool
+        */
+       public function isExpensiveToThumbnail( $file ) {
+               return $file->getSize() > static::EXPENSIVE_SIZE_LIMIT;
+       }
+
+       /**
+        * @param File $file
+        * @return bool
+        */
+       public function isMultiPage( $file ) {
+               return true;
+       }
+
+       /**
+        * @return array
+        */
+       public function getParamMap() {
+               return [
+                       'img_width' => 'width',
+                       'img_page' => 'page',
+               ];
+       }
+
+       /**
+        * @param string $name
+        * @param mixed $value
+        * @return bool
+        */
+       public function validateParam( $name, $value ) {
+               if ( $name === 'page' && trim( $value ) !== (string)intval( $value ) ) {
+                       // Extra junk on the end of page, probably actually a caption
+                       // e.g. [[File:Foo.djvu|thumb|Page 3 of the document shows foo]]
+                       return false;
+               }
+               if ( in_array( $name, [ 'width', 'height', 'page' ] ) ) {
+                       if ( $value <= 0 ) {
+                               return false;
+                       } else {
+                               return true;
+                       }
+               } else {
+                       return false;
+               }
+       }
+
+       /**
+        * @param array $params
+        * @return bool|string
+        */
+       public function makeParamString( $params ) {
+               $page = isset( $params['page'] ) ? $params['page'] : 1;
+               if ( !isset( $params['width'] ) ) {
+                       return false;
+               }
+
+               return "page{$page}-{$params['width']}px";
+       }
+
+       /**
+        * @param string $str
+        * @return array|bool
+        */
+       public function parseParamString( $str ) {
+               $m = false;
+               if ( preg_match( '/^page(\d+)-(\d+)px$/', $str, $m ) ) {
+                       return [ 'width' => $m[2], 'page' => $m[1] ];
+               } else {
+                       return false;
+               }
+       }
+
+       /**
+        * @param array $params
+        * @return array
+        */
+       function getScriptParams( $params ) {
+               return [
+                       'width' => $params['width'],
+                       'page' => $params['page'],
+               ];
+       }
+
+       /**
+        * @param File $image
+        * @param string $dstPath
+        * @param string $dstUrl
+        * @param array $params
+        * @param int $flags
+        * @return MediaTransformError|ThumbnailImage|TransformParameterError
+        */
+       function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 ) {
+               global $wgDjvuRenderer, $wgDjvuPostProcessor;
+
+               if ( !$this->normaliseParams( $image, $params ) ) {
+                       return new TransformParameterError( $params );
+               }
+               $width = $params['width'];
+               $height = $params['height'];
+               $page = $params['page'];
+
+               if ( $flags & self::TRANSFORM_LATER ) {
+                       $params = [
+                               'width' => $width,
+                               'height' => $height,
+                               'page' => $page
+                       ];
+
+                       return new ThumbnailImage( $image, $dstUrl, $dstPath, $params );
+               }
+
+               if ( !wfMkdirParents( dirname( $dstPath ), null, __METHOD__ ) ) {
+                       return new MediaTransformError(
+                               'thumbnail_error',
+                               $width,
+                               $height,
+                               wfMessage( 'thumbnail_dest_directory' )
+                       );
+               }
+
+               // Get local copy source for shell scripts
+               // Thumbnail extraction is very inefficient for large files.
+               // Provide a way to pool count limit the number of downloaders.
+               if ( $image->getSize() >= 1e7 ) { // 10MB
+                       $work = new PoolCounterWorkViaCallback( 'GetLocalFileCopy', sha1( $image->getName() ),
+                               [
+                                       'doWork' => function () use ( $image ) {
+                                               return $image->getLocalRefPath();
+                                       }
+                               ]
+                       );
+                       $srcPath = $work->execute();
+               } else {
+                       $srcPath = $image->getLocalRefPath();
+               }
+
+               if ( $srcPath === false ) { // Failed to get local copy
+                       wfDebugLog( 'thumbnail',
+                               sprintf( 'Thumbnail failed on %s: could not get local copy of "%s"',
+                                       wfHostname(), $image->getName() ) );
+
+                       return new MediaTransformError( 'thumbnail_error',
+                               $params['width'], $params['height'],
+                               wfMessage( 'filemissing' )
+                       );
+               }
+
+               # Use a subshell (brackets) to aggregate stderr from both pipeline commands
+               # before redirecting it to the overall stdout. This works in both Linux and Windows XP.
+               $cmd = '(' . wfEscapeShellArg(
+                       $wgDjvuRenderer,
+                       "-format=ppm",
+                       "-page={$page}",
+                       "-size={$params['physicalWidth']}x{$params['physicalHeight']}",
+                       $srcPath );
+               if ( $wgDjvuPostProcessor ) {
+                       $cmd .= " | {$wgDjvuPostProcessor}";
+               }
+               $cmd .= ' > ' . wfEscapeShellArg( $dstPath ) . ') 2>&1';
+               wfDebug( __METHOD__ . ": $cmd\n" );
+               $retval = '';
+               $err = wfShellExec( $cmd, $retval );
+
+               $removed = $this->removeBadFile( $dstPath, $retval );
+               if ( $retval != 0 || $removed ) {
+                       $this->logErrorForExternalProcess( $retval, $err, $cmd );
+                       return new MediaTransformError( 'thumbnail_error', $width, $height, $err );
+               } else {
+                       $params = [
+                               'width' => $width,
+                               'height' => $height,
+                               'page' => $page
+                       ];
+
+                       return new ThumbnailImage( $image, $dstUrl, $dstPath, $params );
+               }
+       }
+
+       /**
+        * Cache an instance of DjVuImage in an Image object, return that instance
+        *
+        * @param File|FSFile $image
+        * @param string $path
+        * @return DjVuImage
+        */
+       function getDjVuImage( $image, $path ) {
+               if ( !$image ) {
+                       $deja = new DjVuImage( $path );
+               } elseif ( !isset( $image->dejaImage ) ) {
+                       $deja = $image->dejaImage = new DjVuImage( $path );
+               } else {
+                       $deja = $image->dejaImage;
+               }
+
+               return $deja;
+       }
+
+       /**
+        * Get metadata, unserializing it if neccessary.
+        *
+        * @param File $file The DjVu file in question
+        * @return string XML metadata as a string.
+        * @throws MWException
+        */
+       private function getUnserializedMetadata( File $file ) {
+               $metadata = $file->getMetadata();
+               if ( substr( $metadata, 0, 3 ) === '<?xml' ) {
+                       // Old style. Not serialized but instead just a raw string of XML.
+                       return $metadata;
+               }
+
+               Wikimedia\suppressWarnings();
+               $unser = unserialize( $metadata );
+               Wikimedia\restoreWarnings();
+               if ( is_array( $unser ) ) {
+                       if ( isset( $unser['error'] ) ) {
+                               return false;
+                       } elseif ( isset( $unser['xml'] ) ) {
+                               return $unser['xml'];
+                       } else {
+                               // Should never ever reach here.
+                               throw new MWException( "Error unserializing DjVu metadata." );
+                       }
+               }
+
+               // unserialize failed. Guess it wasn't really serialized after all,
+               return $metadata;
+       }
+
+       /**
+        * Cache a document tree for the DjVu XML metadata
+        * @param File $image
+        * @param bool $gettext DOCUMENT (Default: false)
+        * @return bool|SimpleXMLElement
+        */
+       public function getMetaTree( $image, $gettext = false ) {
+               if ( $gettext && isset( $image->djvuTextTree ) ) {
+                       return $image->djvuTextTree;
+               }
+               if ( !$gettext && isset( $image->dejaMetaTree ) ) {
+                       return $image->dejaMetaTree;
+               }
+
+               $metadata = $this->getUnserializedMetadata( $image );
+               if ( !$this->isMetadataValid( $image, $metadata ) ) {
+                       wfDebug( "DjVu XML metadata is invalid or missing, should have been fixed in upgradeRow\n" );
+
+                       return false;
+               }
+
+               $trees = $this->extractTreesFromMetadata( $metadata );
+               $image->djvuTextTree = $trees['TextTree'];
+               $image->dejaMetaTree = $trees['MetaTree'];
+
+               if ( $gettext ) {
+                       return $image->djvuTextTree;
+               } else {
+                       return $image->dejaMetaTree;
+               }
+       }
+
+       /**
+        * Extracts metadata and text trees from metadata XML in string form
+        * @param string $metadata XML metadata as a string
+        * @return array
+        */
+       protected function extractTreesFromMetadata( $metadata ) {
+               Wikimedia\suppressWarnings();
+               try {
+                       // Set to false rather than null to avoid further attempts
+                       $metaTree = false;
+                       $textTree = false;
+                       $tree = new SimpleXMLElement( $metadata, LIBXML_PARSEHUGE );
+                       if ( $tree->getName() == 'mw-djvu' ) {
+                               /** @var SimpleXMLElement $b */
+                               foreach ( $tree->children() as $b ) {
+                                       if ( $b->getName() == 'DjVuTxt' ) {
+                                               // @todo File::djvuTextTree and File::dejaMetaTree are declared
+                                               // dynamically. Add a public File::$data to facilitate this?
+                                               $textTree = $b;
+                                       } elseif ( $b->getName() == 'DjVuXML' ) {
+                                               $metaTree = $b;
+                                       }
+                               }
+                       } else {
+                               $metaTree = $tree;
+                       }
+               } catch ( Exception $e ) {
+                       wfDebug( "Bogus multipage XML metadata\n" );
+               }
+               Wikimedia\restoreWarnings();
+
+               return [ 'MetaTree' => $metaTree, 'TextTree' => $textTree ];
+       }
+
+       function getImageSize( $image, $path ) {
+               return $this->getDjVuImage( $image, $path )->getImageSize();
+       }
+
+       function getThumbType( $ext, $mime, $params = null ) {
+               global $wgDjvuOutputExtension;
+               static $mime;
+               if ( !isset( $mime ) ) {
+                       $magic = MediaWiki\MediaWikiServices::getInstance()->getMimeAnalyzer();
+                       $mime = $magic->guessTypesForExtension( $wgDjvuOutputExtension );
+               }
+
+               return [ $wgDjvuOutputExtension, $mime ];
+       }
+
+       function getMetadata( $image, $path ) {
+               wfDebug( "Getting DjVu metadata for $path\n" );
+
+               $xml = $this->getDjVuImage( $image, $path )->retrieveMetaData();
+               if ( $xml === false ) {
+                       // Special value so that we don't repetitively try and decode a broken file.
+                       return serialize( [ 'error' => 'Error extracting metadata' ] );
+               } else {
+                       return serialize( [ 'xml' => $xml ] );
+               }
+       }
+
+       function getMetadataType( $image ) {
+               return 'djvuxml';
+       }
+
+       function isMetadataValid( $image, $metadata ) {
+               return !empty( $metadata ) && $metadata != serialize( [] );
+       }
+
+       function pageCount( File $image ) {
+               $info = $this->getDimensionInfo( $image );
+
+               return $info ? $info['pageCount'] : false;
+       }
+
+       function getPageDimensions( File $image, $page ) {
+               $index = $page - 1; // MW starts pages at 1
+
+               $info = $this->getDimensionInfo( $image );
+               if ( $info && isset( $info['dimensionsByPage'][$index] ) ) {
+                       return $info['dimensionsByPage'][$index];
+               }
+
+               return false;
+       }
+
+       protected function getDimensionInfo( File $file ) {
+               $cache = ObjectCache::getMainWANInstance();
+               return $cache->getWithSetCallback(
+                       $cache->makeKey( 'file-djvu', 'dimensions', $file->getSha1() ),
+                       $cache::TTL_INDEFINITE,
+                       function () use ( $file ) {
+                               $tree = $this->getMetaTree( $file );
+                               return $this->getDimensionInfoFromMetaTree( $tree );
+                       },
+                       [ 'pcTTL' => $cache::TTL_INDEFINITE ]
+               );
+       }
+
+       /**
+        * Given an XML metadata tree, returns dimension information about the document
+        * @param bool|SimpleXMLElement $metatree The file's XML metadata tree
+        * @return bool|array
+        */
+       protected function getDimensionInfoFromMetaTree( $metatree ) {
+               if ( !$metatree ) {
+                       return false;
+               }
+
+               $dimsByPage = [];
+               $count = count( $metatree->xpath( '//OBJECT' ) );
+               for ( $i = 0; $i < $count; $i++ ) {
+                       $o = $metatree->BODY[0]->OBJECT[$i];
+                       if ( $o ) {
+                               $dimsByPage[$i] = [
+                                       'width' => (int)$o['width'],
+                                       'height' => (int)$o['height'],
+                               ];
+                       } else {
+                               $dimsByPage[$i] = false;
+                       }
+               }
+
+               return [ 'pageCount' => $count, 'dimensionsByPage' => $dimsByPage ];
+       }
+
+       /**
+        * @param File $image
+        * @param int $page Page number to get information for
+        * @return bool|string Page text or false when no text found.
+        */
+       function getPageText( File $image, $page ) {
+               $tree = $this->getMetaTree( $image, true );
+               if ( !$tree ) {
+                       return false;
+               }
+
+               $o = $tree->BODY[0]->PAGE[$page - 1];
+               if ( $o ) {
+                       $txt = $o['value'];
+
+                       return $txt;
+               } else {
+                       return false;
+               }
+       }
+}
diff --git a/includes/media/ExifBitmap.php b/includes/media/ExifBitmap.php
deleted file mode 100644 (file)
index 4267210..0000000
+++ /dev/null
@@ -1,245 +0,0 @@
-<?php
-/**
- * Handler for bitmap images with exif metadata.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Media
- */
-
-/**
- * Stuff specific to JPEG and (built-in) TIFF handler.
- * All metadata related, since both JPEG and TIFF support Exif.
- *
- * @ingroup Media
- */
-class ExifBitmapHandler extends BitmapHandler {
-       const BROKEN_FILE = '-1'; // error extracting metadata
-       const OLD_BROKEN_FILE = '0'; // outdated error extracting metadata.
-
-       function convertMetadataVersion( $metadata, $version = 1 ) {
-               // basically flattens arrays.
-               $version = intval( explode( ';', $version, 2 )[0] );
-               if ( $version < 1 || $version >= 2 ) {
-                       return $metadata;
-               }
-
-               $avoidHtml = true;
-
-               if ( !is_array( $metadata ) ) {
-                       $metadata = unserialize( $metadata );
-               }
-               if ( !isset( $metadata['MEDIAWIKI_EXIF_VERSION'] ) || $metadata['MEDIAWIKI_EXIF_VERSION'] != 2 ) {
-                       return $metadata;
-               }
-
-               // Treat Software as a special case because in can contain
-               // an array of (SoftwareName, Version).
-               if ( isset( $metadata['Software'] )
-                       && is_array( $metadata['Software'] )
-                       && is_array( $metadata['Software'][0] )
-                       && isset( $metadata['Software'][0][0] )
-                       && isset( $metadata['Software'][0][1] )
-               ) {
-                       $metadata['Software'] = $metadata['Software'][0][0] . ' (Version '
-                               . $metadata['Software'][0][1] . ')';
-               }
-
-               $formatter = new FormatMetadata;
-
-               // ContactInfo also has to be dealt with specially
-               if ( isset( $metadata['Contact'] ) ) {
-                       $metadata['Contact'] =
-                               $formatter->collapseContactInfo(
-                                       $metadata['Contact'] );
-               }
-
-               foreach ( $metadata as &$val ) {
-                       if ( is_array( $val ) ) {
-                               $val = $formatter->flattenArrayReal( $val, 'ul', $avoidHtml );
-                       }
-               }
-               $metadata['MEDIAWIKI_EXIF_VERSION'] = 1;
-
-               return $metadata;
-       }
-
-       /**
-        * @param File $image
-        * @param array $metadata
-        * @return bool|int
-        */
-       function isMetadataValid( $image, $metadata ) {
-               global $wgShowEXIF;
-               if ( !$wgShowEXIF ) {
-                       # Metadata disabled and so an empty field is expected
-                       return self::METADATA_GOOD;
-               }
-               if ( $metadata === self::OLD_BROKEN_FILE ) {
-                       # Old special value indicating that there is no Exif data in the file.
-                       # or that there was an error well extracting the metadata.
-                       wfDebug( __METHOD__ . ": back-compat version\n" );
-
-                       return self::METADATA_COMPATIBLE;
-               }
-               if ( $metadata === self::BROKEN_FILE ) {
-                       return self::METADATA_GOOD;
-               }
-               Wikimedia\suppressWarnings();
-               $exif = unserialize( $metadata );
-               Wikimedia\restoreWarnings();
-               if ( !isset( $exif['MEDIAWIKI_EXIF_VERSION'] )
-                       || $exif['MEDIAWIKI_EXIF_VERSION'] != Exif::version()
-               ) {
-                       if ( isset( $exif['MEDIAWIKI_EXIF_VERSION'] )
-                               && $exif['MEDIAWIKI_EXIF_VERSION'] == 1
-                       ) {
-                               // back-compatible but old
-                               wfDebug( __METHOD__ . ": back-compat version\n" );
-
-                               return self::METADATA_COMPATIBLE;
-                       }
-                       # Wrong (non-compatible) version
-                       wfDebug( __METHOD__ . ": wrong version\n" );
-
-                       return self::METADATA_BAD;
-               }
-
-               return self::METADATA_GOOD;
-       }
-
-       /**
-        * @param File $image
-        * @param bool|IContextSource $context Context to use (optional)
-        * @return array|bool
-        */
-       function formatMetadata( $image, $context = false ) {
-               $meta = $this->getCommonMetaArray( $image );
-               if ( count( $meta ) === 0 ) {
-                       return false;
-               }
-
-               return $this->formatMetadataHelper( $meta, $context );
-       }
-
-       public function getCommonMetaArray( File $file ) {
-               $metadata = $file->getMetadata();
-               if ( $metadata === self::OLD_BROKEN_FILE
-                       || $metadata === self::BROKEN_FILE
-                       || $this->isMetadataValid( $file, $metadata ) === self::METADATA_BAD
-               ) {
-                       // So we don't try and display metadata from PagedTiffHandler
-                       // for example when using InstantCommons.
-                       return [];
-               }
-
-               $exif = unserialize( $metadata );
-               if ( !$exif ) {
-                       return [];
-               }
-               unset( $exif['MEDIAWIKI_EXIF_VERSION'] );
-
-               return $exif;
-       }
-
-       function getMetadataType( $image ) {
-               return 'exif';
-       }
-
-       /**
-        * Wrapper for base classes ImageHandler::getImageSize() that checks for
-        * rotation reported from metadata and swaps the sizes to match.
-        *
-        * @param File|FSFile $image
-        * @param string $path
-        * @return array
-        */
-       function getImageSize( $image, $path ) {
-               $gis = parent::getImageSize( $image, $path );
-
-               // Don't just call $image->getMetadata(); FSFile::getPropsFromPath() calls us with a bogus object.
-               // This may mean we read EXIF data twice on initial upload.
-               if ( $this->autoRotateEnabled() ) {
-                       $meta = $this->getMetadata( $image, $path );
-                       $rotation = $this->getRotationForExif( $meta );
-               } else {
-                       $rotation = 0;
-               }
-
-               if ( $rotation == 90 || $rotation == 270 ) {
-                       $width = $gis[0];
-                       $gis[0] = $gis[1];
-                       $gis[1] = $width;
-               }
-
-               return $gis;
-       }
-
-       /**
-        * On supporting image formats, try to read out the low-level orientation
-        * of the file and return the angle that the file needs to be rotated to
-        * be viewed.
-        *
-        * This information is only useful when manipulating the original file;
-        * the width and height we normally work with is logical, and will match
-        * any produced output views.
-        *
-        * @param File $file
-        * @return int 0, 90, 180 or 270
-        */
-       public function getRotation( $file ) {
-               if ( !$this->autoRotateEnabled() ) {
-                       return 0;
-               }
-
-               $data = $file->getMetadata();
-
-               return $this->getRotationForExif( $data );
-       }
-
-       /**
-        * Given a chunk of serialized Exif metadata, return the orientation as
-        * degrees of rotation.
-        *
-        * @param string $data
-        * @return int 0, 90, 180 or 270
-        * @todo FIXME: Orientation can include flipping as well; see if this is an issue!
-        */
-       protected function getRotationForExif( $data ) {
-               if ( !$data ) {
-                       return 0;
-               }
-               Wikimedia\suppressWarnings();
-               $data = unserialize( $data );
-               Wikimedia\restoreWarnings();
-               if ( isset( $data['Orientation'] ) ) {
-                       # See http://sylvana.net/jpegcrop/exif_orientation.html
-                       switch ( $data['Orientation'] ) {
-                               case 8:
-                                       return 90;
-                               case 3:
-                                       return 180;
-                               case 6:
-                                       return 270;
-                               default:
-                                       return 0;
-                       }
-               }
-
-               return 0;
-       }
-}
diff --git a/includes/media/ExifBitmapHandler.php b/includes/media/ExifBitmapHandler.php
new file mode 100644 (file)
index 0000000..4267210
--- /dev/null
@@ -0,0 +1,245 @@
+<?php
+/**
+ * Handler for bitmap images with exif metadata.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Media
+ */
+
+/**
+ * Stuff specific to JPEG and (built-in) TIFF handler.
+ * All metadata related, since both JPEG and TIFF support Exif.
+ *
+ * @ingroup Media
+ */
+class ExifBitmapHandler extends BitmapHandler {
+       const BROKEN_FILE = '-1'; // error extracting metadata
+       const OLD_BROKEN_FILE = '0'; // outdated error extracting metadata.
+
+       function convertMetadataVersion( $metadata, $version = 1 ) {
+               // basically flattens arrays.
+               $version = intval( explode( ';', $version, 2 )[0] );
+               if ( $version < 1 || $version >= 2 ) {
+                       return $metadata;
+               }
+
+               $avoidHtml = true;
+
+               if ( !is_array( $metadata ) ) {
+                       $metadata = unserialize( $metadata );
+               }
+               if ( !isset( $metadata['MEDIAWIKI_EXIF_VERSION'] ) || $metadata['MEDIAWIKI_EXIF_VERSION'] != 2 ) {
+                       return $metadata;
+               }
+
+               // Treat Software as a special case because in can contain
+               // an array of (SoftwareName, Version).
+               if ( isset( $metadata['Software'] )
+                       && is_array( $metadata['Software'] )
+                       && is_array( $metadata['Software'][0] )
+                       && isset( $metadata['Software'][0][0] )
+                       && isset( $metadata['Software'][0][1] )
+               ) {
+                       $metadata['Software'] = $metadata['Software'][0][0] . ' (Version '
+                               . $metadata['Software'][0][1] . ')';
+               }
+
+               $formatter = new FormatMetadata;
+
+               // ContactInfo also has to be dealt with specially
+               if ( isset( $metadata['Contact'] ) ) {
+                       $metadata['Contact'] =
+                               $formatter->collapseContactInfo(
+                                       $metadata['Contact'] );
+               }
+
+               foreach ( $metadata as &$val ) {
+                       if ( is_array( $val ) ) {
+                               $val = $formatter->flattenArrayReal( $val, 'ul', $avoidHtml );
+                       }
+               }
+               $metadata['MEDIAWIKI_EXIF_VERSION'] = 1;
+
+               return $metadata;
+       }
+
+       /**
+        * @param File $image
+        * @param array $metadata
+        * @return bool|int
+        */
+       function isMetadataValid( $image, $metadata ) {
+               global $wgShowEXIF;
+               if ( !$wgShowEXIF ) {
+                       # Metadata disabled and so an empty field is expected
+                       return self::METADATA_GOOD;
+               }
+               if ( $metadata === self::OLD_BROKEN_FILE ) {
+                       # Old special value indicating that there is no Exif data in the file.
+                       # or that there was an error well extracting the metadata.
+                       wfDebug( __METHOD__ . ": back-compat version\n" );
+
+                       return self::METADATA_COMPATIBLE;
+               }
+               if ( $metadata === self::BROKEN_FILE ) {
+                       return self::METADATA_GOOD;
+               }
+               Wikimedia\suppressWarnings();
+               $exif = unserialize( $metadata );
+               Wikimedia\restoreWarnings();
+               if ( !isset( $exif['MEDIAWIKI_EXIF_VERSION'] )
+                       || $exif['MEDIAWIKI_EXIF_VERSION'] != Exif::version()
+               ) {
+                       if ( isset( $exif['MEDIAWIKI_EXIF_VERSION'] )
+                               && $exif['MEDIAWIKI_EXIF_VERSION'] == 1
+                       ) {
+                               // back-compatible but old
+                               wfDebug( __METHOD__ . ": back-compat version\n" );
+
+                               return self::METADATA_COMPATIBLE;
+                       }
+                       # Wrong (non-compatible) version
+                       wfDebug( __METHOD__ . ": wrong version\n" );
+
+                       return self::METADATA_BAD;
+               }
+
+               return self::METADATA_GOOD;
+       }
+
+       /**
+        * @param File $image
+        * @param bool|IContextSource $context Context to use (optional)
+        * @return array|bool
+        */
+       function formatMetadata( $image, $context = false ) {
+               $meta = $this->getCommonMetaArray( $image );
+               if ( count( $meta ) === 0 ) {
+                       return false;
+               }
+
+               return $this->formatMetadataHelper( $meta, $context );
+       }
+
+       public function getCommonMetaArray( File $file ) {
+               $metadata = $file->getMetadata();
+               if ( $metadata === self::OLD_BROKEN_FILE
+                       || $metadata === self::BROKEN_FILE
+                       || $this->isMetadataValid( $file, $metadata ) === self::METADATA_BAD
+               ) {
+                       // So we don't try and display metadata from PagedTiffHandler
+                       // for example when using InstantCommons.
+                       return [];
+               }
+
+               $exif = unserialize( $metadata );
+               if ( !$exif ) {
+                       return [];
+               }
+               unset( $exif['MEDIAWIKI_EXIF_VERSION'] );
+
+               return $exif;
+       }
+
+       function getMetadataType( $image ) {
+               return 'exif';
+       }
+
+       /**
+        * Wrapper for base classes ImageHandler::getImageSize() that checks for
+        * rotation reported from metadata and swaps the sizes to match.
+        *
+        * @param File|FSFile $image
+        * @param string $path
+        * @return array
+        */
+       function getImageSize( $image, $path ) {
+               $gis = parent::getImageSize( $image, $path );
+
+               // Don't just call $image->getMetadata(); FSFile::getPropsFromPath() calls us with a bogus object.
+               // This may mean we read EXIF data twice on initial upload.
+               if ( $this->autoRotateEnabled() ) {
+                       $meta = $this->getMetadata( $image, $path );
+                       $rotation = $this->getRotationForExif( $meta );
+               } else {
+                       $rotation = 0;
+               }
+
+               if ( $rotation == 90 || $rotation == 270 ) {
+                       $width = $gis[0];
+                       $gis[0] = $gis[1];
+                       $gis[1] = $width;
+               }
+
+               return $gis;
+       }
+
+       /**
+        * On supporting image formats, try to read out the low-level orientation
+        * of the file and return the angle that the file needs to be rotated to
+        * be viewed.
+        *
+        * This information is only useful when manipulating the original file;
+        * the width and height we normally work with is logical, and will match
+        * any produced output views.
+        *
+        * @param File $file
+        * @return int 0, 90, 180 or 270
+        */
+       public function getRotation( $file ) {
+               if ( !$this->autoRotateEnabled() ) {
+                       return 0;
+               }
+
+               $data = $file->getMetadata();
+
+               return $this->getRotationForExif( $data );
+       }
+
+       /**
+        * Given a chunk of serialized Exif metadata, return the orientation as
+        * degrees of rotation.
+        *
+        * @param string $data
+        * @return int 0, 90, 180 or 270
+        * @todo FIXME: Orientation can include flipping as well; see if this is an issue!
+        */
+       protected function getRotationForExif( $data ) {
+               if ( !$data ) {
+                       return 0;
+               }
+               Wikimedia\suppressWarnings();
+               $data = unserialize( $data );
+               Wikimedia\restoreWarnings();
+               if ( isset( $data['Orientation'] ) ) {
+                       # See http://sylvana.net/jpegcrop/exif_orientation.html
+                       switch ( $data['Orientation'] ) {
+                               case 8:
+                                       return 90;
+                               case 3:
+                                       return 180;
+                               case 6:
+                                       return 270;
+                               default:
+                                       return 0;
+                       }
+               }
+
+               return 0;
+       }
+}
diff --git a/includes/media/GIF.php b/includes/media/GIF.php
deleted file mode 100644 (file)
index d65f872..0000000
+++ /dev/null
@@ -1,211 +0,0 @@
-<?php
-/**
- * Handler for GIF images.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Media
- */
-
-/**
- * Handler for GIF images.
- *
- * @ingroup Media
- */
-class GIFHandler extends BitmapHandler {
-       const BROKEN_FILE = '0'; // value to store in img_metadata if error extracting metadata.
-
-       function getMetadata( $image, $filename ) {
-               try {
-                       $parsedGIFMetadata = BitmapMetadataHandler::GIF( $filename );
-               } catch ( Exception $e ) {
-                       // Broken file?
-                       wfDebug( __METHOD__ . ': ' . $e->getMessage() . "\n" );
-
-                       return self::BROKEN_FILE;
-               }
-
-               return serialize( $parsedGIFMetadata );
-       }
-
-       /**
-        * @param File $image
-        * @param bool|IContextSource $context Context to use (optional)
-        * @return array|bool
-        */
-       function formatMetadata( $image, $context = false ) {
-               $meta = $this->getCommonMetaArray( $image );
-               if ( count( $meta ) === 0 ) {
-                       return false;
-               }
-
-               return $this->formatMetadataHelper( $meta, $context );
-       }
-
-       /**
-        * Return the standard metadata elements for #filemetadata parser func.
-        * @param File $image
-        * @return array|bool
-        */
-       public function getCommonMetaArray( File $image ) {
-               $meta = $image->getMetadata();
-
-               if ( !$meta ) {
-                       return [];
-               }
-               $meta = unserialize( $meta );
-               if ( !isset( $meta['metadata'] ) ) {
-                       return [];
-               }
-               unset( $meta['metadata']['_MW_GIF_VERSION'] );
-
-               return $meta['metadata'];
-       }
-
-       /**
-        * @todo Add unit tests
-        *
-        * @param File $image
-        * @return bool
-        */
-       function getImageArea( $image ) {
-               $ser = $image->getMetadata();
-               if ( $ser ) {
-                       $metadata = unserialize( $ser );
-
-                       return $image->getWidth() * $image->getHeight() * $metadata['frameCount'];
-               } else {
-                       return $image->getWidth() * $image->getHeight();
-               }
-       }
-
-       /**
-        * @param File $image
-        * @return bool
-        */
-       function isAnimatedImage( $image ) {
-               $ser = $image->getMetadata();
-               if ( $ser ) {
-                       $metadata = unserialize( $ser );
-                       if ( $metadata['frameCount'] > 1 ) {
-                               return true;
-                       }
-               }
-
-               return false;
-       }
-
-       /**
-        * We cannot animate thumbnails that are bigger than a particular size
-        * @param File $file
-        * @return bool
-        */
-       function canAnimateThumbnail( $file ) {
-               global $wgMaxAnimatedGifArea;
-               $answer = $this->getImageArea( $file ) <= $wgMaxAnimatedGifArea;
-
-               return $answer;
-       }
-
-       function getMetadataType( $image ) {
-               return 'parsed-gif';
-       }
-
-       function isMetadataValid( $image, $metadata ) {
-               if ( $metadata === self::BROKEN_FILE ) {
-                       // Do not repetitivly regenerate metadata on broken file.
-                       return self::METADATA_GOOD;
-               }
-
-               Wikimedia\suppressWarnings();
-               $data = unserialize( $metadata );
-               Wikimedia\restoreWarnings();
-
-               if ( !$data || !is_array( $data ) ) {
-                       wfDebug( __METHOD__ . " invalid GIF metadata\n" );
-
-                       return self::METADATA_BAD;
-               }
-
-               if ( !isset( $data['metadata']['_MW_GIF_VERSION'] )
-                       || $data['metadata']['_MW_GIF_VERSION'] != GIFMetadataExtractor::VERSION
-               ) {
-                       wfDebug( __METHOD__ . " old but compatible GIF metadata\n" );
-
-                       return self::METADATA_COMPATIBLE;
-               }
-
-               return self::METADATA_GOOD;
-       }
-
-       /**
-        * @param File $image
-        * @return string
-        */
-       function getLongDesc( $image ) {
-               global $wgLang;
-
-               $original = parent::getLongDesc( $image );
-
-               Wikimedia\suppressWarnings();
-               $metadata = unserialize( $image->getMetadata() );
-               Wikimedia\restoreWarnings();
-
-               if ( !$metadata || $metadata['frameCount'] <= 1 ) {
-                       return $original;
-               }
-
-               /* Preserve original image info string, but strip the last char ')' so we can add even more */
-               $info = [];
-               $info[] = $original;
-
-               if ( $metadata['looped'] ) {
-                       $info[] = wfMessage( 'file-info-gif-looped' )->parse();
-               }
-
-               if ( $metadata['frameCount'] > 1 ) {
-                       $info[] = wfMessage( 'file-info-gif-frames' )->numParams( $metadata['frameCount'] )->parse();
-               }
-
-               if ( $metadata['duration'] ) {
-                       $info[] = $wgLang->formatTimePeriod( $metadata['duration'] );
-               }
-
-               return $wgLang->commaList( $info );
-       }
-
-       /**
-        * Return the duration of the GIF file.
-        *
-        * Shown in the &query=imageinfo&iiprop=size api query.
-        *
-        * @param File $file
-        * @return float The duration of the file.
-        */
-       public function getLength( $file ) {
-               $serMeta = $file->getMetadata();
-               Wikimedia\suppressWarnings();
-               $metadata = unserialize( $serMeta );
-               Wikimedia\restoreWarnings();
-
-               if ( !$metadata || !isset( $metadata['duration'] ) || !$metadata['duration'] ) {
-                       return 0.0;
-               } else {
-                       return (float)$metadata['duration'];
-               }
-       }
-}
diff --git a/includes/media/GIFHandler.php b/includes/media/GIFHandler.php
new file mode 100644 (file)
index 0000000..d65f872
--- /dev/null
@@ -0,0 +1,211 @@
+<?php
+/**
+ * Handler for GIF images.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Media
+ */
+
+/**
+ * Handler for GIF images.
+ *
+ * @ingroup Media
+ */
+class GIFHandler extends BitmapHandler {
+       const BROKEN_FILE = '0'; // value to store in img_metadata if error extracting metadata.
+
+       function getMetadata( $image, $filename ) {
+               try {
+                       $parsedGIFMetadata = BitmapMetadataHandler::GIF( $filename );
+               } catch ( Exception $e ) {
+                       // Broken file?
+                       wfDebug( __METHOD__ . ': ' . $e->getMessage() . "\n" );
+
+                       return self::BROKEN_FILE;
+               }
+
+               return serialize( $parsedGIFMetadata );
+       }
+
+       /**
+        * @param File $image
+        * @param bool|IContextSource $context Context to use (optional)
+        * @return array|bool
+        */
+       function formatMetadata( $image, $context = false ) {
+               $meta = $this->getCommonMetaArray( $image );
+               if ( count( $meta ) === 0 ) {
+                       return false;
+               }
+
+               return $this->formatMetadataHelper( $meta, $context );
+       }
+
+       /**
+        * Return the standard metadata elements for #filemetadata parser func.
+        * @param File $image
+        * @return array|bool
+        */
+       public function getCommonMetaArray( File $image ) {
+               $meta = $image->getMetadata();
+
+               if ( !$meta ) {
+                       return [];
+               }
+               $meta = unserialize( $meta );
+               if ( !isset( $meta['metadata'] ) ) {
+                       return [];
+               }
+               unset( $meta['metadata']['_MW_GIF_VERSION'] );
+
+               return $meta['metadata'];
+       }
+
+       /**
+        * @todo Add unit tests
+        *
+        * @param File $image
+        * @return bool
+        */
+       function getImageArea( $image ) {
+               $ser = $image->getMetadata();
+               if ( $ser ) {
+                       $metadata = unserialize( $ser );
+
+                       return $image->getWidth() * $image->getHeight() * $metadata['frameCount'];
+               } else {
+                       return $image->getWidth() * $image->getHeight();
+               }
+       }
+
+       /**
+        * @param File $image
+        * @return bool
+        */
+       function isAnimatedImage( $image ) {
+               $ser = $image->getMetadata();
+               if ( $ser ) {
+                       $metadata = unserialize( $ser );
+                       if ( $metadata['frameCount'] > 1 ) {
+                               return true;
+                       }
+               }
+
+               return false;
+       }
+
+       /**
+        * We cannot animate thumbnails that are bigger than a particular size
+        * @param File $file
+        * @return bool
+        */
+       function canAnimateThumbnail( $file ) {
+               global $wgMaxAnimatedGifArea;
+               $answer = $this->getImageArea( $file ) <= $wgMaxAnimatedGifArea;
+
+               return $answer;
+       }
+
+       function getMetadataType( $image ) {
+               return 'parsed-gif';
+       }
+
+       function isMetadataValid( $image, $metadata ) {
+               if ( $metadata === self::BROKEN_FILE ) {
+                       // Do not repetitivly regenerate metadata on broken file.
+                       return self::METADATA_GOOD;
+               }
+
+               Wikimedia\suppressWarnings();
+               $data = unserialize( $metadata );
+               Wikimedia\restoreWarnings();
+
+               if ( !$data || !is_array( $data ) ) {
+                       wfDebug( __METHOD__ . " invalid GIF metadata\n" );
+
+                       return self::METADATA_BAD;
+               }
+
+               if ( !isset( $data['metadata']['_MW_GIF_VERSION'] )
+                       || $data['metadata']['_MW_GIF_VERSION'] != GIFMetadataExtractor::VERSION
+               ) {
+                       wfDebug( __METHOD__ . " old but compatible GIF metadata\n" );
+
+                       return self::METADATA_COMPATIBLE;
+               }
+
+               return self::METADATA_GOOD;
+       }
+
+       /**
+        * @param File $image
+        * @return string
+        */
+       function getLongDesc( $image ) {
+               global $wgLang;
+
+               $original = parent::getLongDesc( $image );
+
+               Wikimedia\suppressWarnings();
+               $metadata = unserialize( $image->getMetadata() );
+               Wikimedia\restoreWarnings();
+
+               if ( !$metadata || $metadata['frameCount'] <= 1 ) {
+                       return $original;
+               }
+
+               /* Preserve original image info string, but strip the last char ')' so we can add even more */
+               $info = [];
+               $info[] = $original;
+
+               if ( $metadata['looped'] ) {
+                       $info[] = wfMessage( 'file-info-gif-looped' )->parse();
+               }
+
+               if ( $metadata['frameCount'] > 1 ) {
+                       $info[] = wfMessage( 'file-info-gif-frames' )->numParams( $metadata['frameCount'] )->parse();
+               }
+
+               if ( $metadata['duration'] ) {
+                       $info[] = $wgLang->formatTimePeriod( $metadata['duration'] );
+               }
+
+               return $wgLang->commaList( $info );
+       }
+
+       /**
+        * Return the duration of the GIF file.
+        *
+        * Shown in the &query=imageinfo&iiprop=size api query.
+        *
+        * @param File $file
+        * @return float The duration of the file.
+        */
+       public function getLength( $file ) {
+               $serMeta = $file->getMetadata();
+               Wikimedia\suppressWarnings();
+               $metadata = unserialize( $serMeta );
+               Wikimedia\restoreWarnings();
+
+               if ( !$metadata || !isset( $metadata['duration'] ) || !$metadata['duration'] ) {
+                       return 0.0;
+               } else {
+                       return (float)$metadata['duration'];
+               }
+       }
+}
diff --git a/includes/media/Jpeg.php b/includes/media/Jpeg.php
deleted file mode 100644 (file)
index 287c198..0000000
+++ /dev/null
@@ -1,290 +0,0 @@
-<?php
-/**
- * Handler for JPEG images.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Media
- */
-
-/**
- * JPEG specific handler.
- * Inherits most stuff from BitmapHandler, just here to do the metadata handler differently.
- *
- * Metadata stuff common to Jpeg and built-in Tiff (not PagedTiffHandler) is
- * in ExifBitmapHandler.
- *
- * @ingroup Media
- */
-class JpegHandler extends ExifBitmapHandler {
-       const SRGB_EXIF_COLOR_SPACE = 'sRGB';
-       const SRGB_ICC_PROFILE_DESCRIPTION = 'sRGB IEC61966-2.1';
-
-       function normaliseParams( $image, &$params ) {
-               if ( !parent::normaliseParams( $image, $params ) ) {
-                       return false;
-               }
-               if ( isset( $params['quality'] ) && !self::validateQuality( $params['quality'] ) ) {
-                       return false;
-               }
-               return true;
-       }
-
-       public function validateParam( $name, $value ) {
-               if ( $name === 'quality' ) {
-                       return self::validateQuality( $value );
-               } else {
-                       return parent::validateParam( $name, $value );
-               }
-       }
-
-       /** Validate and normalize quality value to be between 1 and 100 (inclusive).
-        * @param int $value Quality value, will be converted to integer or 0 if invalid
-        * @return bool True if the value is valid
-        */
-       private static function validateQuality( $value ) {
-               return $value === 'low';
-       }
-
-       public function makeParamString( $params ) {
-               // Prepend quality as "qValue-". This has to match parseParamString() below
-               $res = parent::makeParamString( $params );
-               if ( $res && isset( $params['quality'] ) ) {
-                       $res = "q{$params['quality']}-$res";
-               }
-               return $res;
-       }
-
-       public function parseParamString( $str ) {
-               // $str contains "qlow-200px" or "200px" strings because thumb.php would strip the filename
-               // first - check if the string begins with "qlow-", and if so, treat it as quality.
-               // Pass the first portion, or the whole string if "qlow-" not found, to the parent
-               // The parsing must match the makeParamString() above
-               $res = false;
-               $m = false;
-               if ( preg_match( '/q([^-]+)-(.*)$/', $str, $m ) ) {
-                       $v = $m[1];
-                       if ( self::validateQuality( $v ) ) {
-                               $res = parent::parseParamString( $m[2] );
-                               if ( $res ) {
-                                       $res['quality'] = $v;
-                               }
-                       }
-               } else {
-                       $res = parent::parseParamString( $str );
-               }
-               return $res;
-       }
-
-       function getScriptParams( $params ) {
-               $res = parent::getScriptParams( $params );
-               if ( isset( $params['quality'] ) ) {
-                       $res['quality'] = $params['quality'];
-               }
-               return $res;
-       }
-
-       function getMetadata( $image, $filename ) {
-               try {
-                       $meta = BitmapMetadataHandler::Jpeg( $filename );
-                       if ( !is_array( $meta ) ) {
-                               // This should never happen, but doesn't hurt to be paranoid.
-                               throw new MWException( 'Metadata array is not an array' );
-                       }
-                       $meta['MEDIAWIKI_EXIF_VERSION'] = Exif::version();
-
-                       return serialize( $meta );
-               } catch ( Exception $e ) {
-                       // BitmapMetadataHandler throws an exception in certain exceptional
-                       // cases like if file does not exist.
-                       wfDebug( __METHOD__ . ': ' . $e->getMessage() . "\n" );
-
-                       /* This used to use 0 (ExifBitmapHandler::OLD_BROKEN_FILE) for the cases
-                        *   * No metadata in the file
-                        *   * Something is broken in the file.
-                        * However, if the metadata support gets expanded then you can't tell if the 0 is from
-                        * a broken file, or just no props found. A broken file is likely to stay broken, but
-                        * a file which had no props could have props once the metadata support is improved.
-                        * Thus switch to using -1 to denote only a broken file, and use an array with only
-                        * MEDIAWIKI_EXIF_VERSION to denote no props.
-                        */
-
-                       return ExifBitmapHandler::BROKEN_FILE;
-               }
-       }
-
-       /**
-        * @param File $file
-        * @param array $params Rotate parameters.
-        *    'rotation' clockwise rotation in degrees, allowed are multiples of 90
-        * @since 1.21
-        * @return bool|MediaTransformError
-        */
-       public function rotate( $file, $params ) {
-               global $wgJpegTran;
-
-               $rotation = ( $params['rotation'] + $this->getRotation( $file ) ) % 360;
-
-               if ( $wgJpegTran && is_executable( $wgJpegTran ) ) {
-                       $cmd = wfEscapeShellArg( $wgJpegTran ) .
-                               " -rotate " . wfEscapeShellArg( $rotation ) .
-                               " -outfile " . wfEscapeShellArg( $params['dstPath'] ) .
-                               " " . wfEscapeShellArg( $params['srcPath'] );
-                       wfDebug( __METHOD__ . ": running jpgtran: $cmd\n" );
-                       $retval = 0;
-                       $err = wfShellExecWithStderr( $cmd, $retval );
-                       if ( $retval !== 0 ) {
-                               $this->logErrorForExternalProcess( $retval, $err, $cmd );
-
-                               return new MediaTransformError( 'thumbnail_error', 0, 0, $err );
-                       }
-
-                       return false;
-               } else {
-                       return parent::rotate( $file, $params );
-               }
-       }
-
-       public function supportsBucketing() {
-               return true;
-       }
-
-       public function sanitizeParamsForBucketing( $params ) {
-               $params = parent::sanitizeParamsForBucketing( $params );
-
-               // Quality needs to be cleared for bucketing. Buckets need to be default quality
-               if ( isset( $params['quality'] ) ) {
-                       unset( $params['quality'] );
-               }
-
-               return $params;
-       }
-
-       /**
-        * @inheritDoc
-        */
-       protected function transformImageMagick( $image, $params ) {
-               global $wgUseTinyRGBForJPGThumbnails;
-
-               $ret = parent::transformImageMagick( $image, $params );
-
-               if ( $ret ) {
-                       return $ret;
-               }
-
-               if ( $wgUseTinyRGBForJPGThumbnails ) {
-                       // T100976 If the profile embedded in the JPG is sRGB, swap it for the smaller
-                       // (and free) TinyRGB
-
-                       /**
-                        * We'll want to replace the color profile for JPGs:
-                        * * in the sRGB color space, or with the sRGB profile
-                        *   (other profiles will be left untouched)
-                        * * without color space or profile, in which case browsers
-                        *   should assume sRGB, but don't always do (e.g. on wide-gamut
-                        *   monitors (unless it's meant for low bandwith)
-                        * @see https://phabricator.wikimedia.org/T134498
-                        */
-                       $colorSpaces = [ self::SRGB_EXIF_COLOR_SPACE, '-' ];
-                       $profiles = [ self::SRGB_ICC_PROFILE_DESCRIPTION ];
-
-                       // we'll also add TinyRGB profile to images lacking a profile, but
-                       // only if they're not low quality (which are meant to save bandwith
-                       // and we don't want to increase the filesize by adding a profile)
-                       if ( isset( $params['quality'] ) && $params['quality'] > 30 ) {
-                               $profiles[] = '-';
-                       }
-
-                       $this->swapICCProfile(
-                               $params['dstPath'],
-                               $colorSpaces,
-                               $profiles,
-                               realpath( __DIR__ ) . '/tinyrgb.icc'
-                       );
-               }
-
-               return false;
-       }
-
-       /**
-        * Swaps an embedded ICC profile for another, if found.
-        * Depends on exiftool, no-op if not installed.
-        * @param string $filepath File to be manipulated (will be overwritten)
-        * @param array $colorSpaces Only process files with this/these Color Space(s)
-        * @param array $oldProfileStrings Exact name(s) of color profile to look for
-        *  (the one that will be replaced)
-        * @param string $profileFilepath ICC profile file to apply to the file
-        * @since 1.26
-        * @return bool
-        */
-       public function swapICCProfile( $filepath, array $colorSpaces,
-                                                                       array $oldProfileStrings, $profileFilepath
-       ) {
-               global $wgExiftool;
-
-               if ( !$wgExiftool || !is_executable( $wgExiftool ) ) {
-                       return false;
-               }
-
-               $cmd = wfEscapeShellArg( $wgExiftool,
-                       '-EXIF:ColorSpace',
-                       '-ICC_Profile:ProfileDescription',
-                       '-S',
-                       '-T',
-                       $filepath
-               );
-
-               $output = wfShellExecWithStderr( $cmd, $retval );
-
-               // Explode EXIF data into an array with [0 => Color Space, 1 => Device Model Desc]
-               $data = explode( "\t", trim( $output ) );
-
-               if ( $retval !== 0 ) {
-                       return false;
-               }
-
-               // 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';
-               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';
-               if ( empty( preg_grep( $profileRegex, $oldProfileStrings ) ) ) {
-                       // We can't establish that this file has the expected ICC profile, don't process it
-                       return false;
-               }
-
-               $cmd = wfEscapeShellArg( $wgExiftool,
-                       '-overwrite_original',
-                       '-icc_profile<=' . $profileFilepath,
-                       $filepath
-               );
-
-               $output = wfShellExecWithStderr( $cmd, $retval );
-
-               if ( $retval !== 0 ) {
-                       $this->logErrorForExternalProcess( $retval, $output, $cmd );
-
-                       return false;
-               }
-
-               return true;
-       }
-}
diff --git a/includes/media/JpegHandler.php b/includes/media/JpegHandler.php
new file mode 100644 (file)
index 0000000..287c198
--- /dev/null
@@ -0,0 +1,290 @@
+<?php
+/**
+ * Handler for JPEG images.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Media
+ */
+
+/**
+ * JPEG specific handler.
+ * Inherits most stuff from BitmapHandler, just here to do the metadata handler differently.
+ *
+ * Metadata stuff common to Jpeg and built-in Tiff (not PagedTiffHandler) is
+ * in ExifBitmapHandler.
+ *
+ * @ingroup Media
+ */
+class JpegHandler extends ExifBitmapHandler {
+       const SRGB_EXIF_COLOR_SPACE = 'sRGB';
+       const SRGB_ICC_PROFILE_DESCRIPTION = 'sRGB IEC61966-2.1';
+
+       function normaliseParams( $image, &$params ) {
+               if ( !parent::normaliseParams( $image, $params ) ) {
+                       return false;
+               }
+               if ( isset( $params['quality'] ) && !self::validateQuality( $params['quality'] ) ) {
+                       return false;
+               }
+               return true;
+       }
+
+       public function validateParam( $name, $value ) {
+               if ( $name === 'quality' ) {
+                       return self::validateQuality( $value );
+               } else {
+                       return parent::validateParam( $name, $value );
+               }
+       }
+
+       /** Validate and normalize quality value to be between 1 and 100 (inclusive).
+        * @param int $value Quality value, will be converted to integer or 0 if invalid
+        * @return bool True if the value is valid
+        */
+       private static function validateQuality( $value ) {
+               return $value === 'low';
+       }
+
+       public function makeParamString( $params ) {
+               // Prepend quality as "qValue-". This has to match parseParamString() below
+               $res = parent::makeParamString( $params );
+               if ( $res && isset( $params['quality'] ) ) {
+                       $res = "q{$params['quality']}-$res";
+               }
+               return $res;
+       }
+
+       public function parseParamString( $str ) {
+               // $str contains "qlow-200px" or "200px" strings because thumb.php would strip the filename
+               // first - check if the string begins with "qlow-", and if so, treat it as quality.
+               // Pass the first portion, or the whole string if "qlow-" not found, to the parent
+               // The parsing must match the makeParamString() above
+               $res = false;
+               $m = false;
+               if ( preg_match( '/q([^-]+)-(.*)$/', $str, $m ) ) {
+                       $v = $m[1];
+                       if ( self::validateQuality( $v ) ) {
+                               $res = parent::parseParamString( $m[2] );
+                               if ( $res ) {
+                                       $res['quality'] = $v;
+                               }
+                       }
+               } else {
+                       $res = parent::parseParamString( $str );
+               }
+               return $res;
+       }
+
+       function getScriptParams( $params ) {
+               $res = parent::getScriptParams( $params );
+               if ( isset( $params['quality'] ) ) {
+                       $res['quality'] = $params['quality'];
+               }
+               return $res;
+       }
+
+       function getMetadata( $image, $filename ) {
+               try {
+                       $meta = BitmapMetadataHandler::Jpeg( $filename );
+                       if ( !is_array( $meta ) ) {
+                               // This should never happen, but doesn't hurt to be paranoid.
+                               throw new MWException( 'Metadata array is not an array' );
+                       }
+                       $meta['MEDIAWIKI_EXIF_VERSION'] = Exif::version();
+
+                       return serialize( $meta );
+               } catch ( Exception $e ) {
+                       // BitmapMetadataHandler throws an exception in certain exceptional
+                       // cases like if file does not exist.
+                       wfDebug( __METHOD__ . ': ' . $e->getMessage() . "\n" );
+
+                       /* This used to use 0 (ExifBitmapHandler::OLD_BROKEN_FILE) for the cases
+                        *   * No metadata in the file
+                        *   * Something is broken in the file.
+                        * However, if the metadata support gets expanded then you can't tell if the 0 is from
+                        * a broken file, or just no props found. A broken file is likely to stay broken, but
+                        * a file which had no props could have props once the metadata support is improved.
+                        * Thus switch to using -1 to denote only a broken file, and use an array with only
+                        * MEDIAWIKI_EXIF_VERSION to denote no props.
+                        */
+
+                       return ExifBitmapHandler::BROKEN_FILE;
+               }
+       }
+
+       /**
+        * @param File $file
+        * @param array $params Rotate parameters.
+        *    'rotation' clockwise rotation in degrees, allowed are multiples of 90
+        * @since 1.21
+        * @return bool|MediaTransformError
+        */
+       public function rotate( $file, $params ) {
+               global $wgJpegTran;
+
+               $rotation = ( $params['rotation'] + $this->getRotation( $file ) ) % 360;
+
+               if ( $wgJpegTran && is_executable( $wgJpegTran ) ) {
+                       $cmd = wfEscapeShellArg( $wgJpegTran ) .
+                               " -rotate " . wfEscapeShellArg( $rotation ) .
+                               " -outfile " . wfEscapeShellArg( $params['dstPath'] ) .
+                               " " . wfEscapeShellArg( $params['srcPath'] );
+                       wfDebug( __METHOD__ . ": running jpgtran: $cmd\n" );
+                       $retval = 0;
+                       $err = wfShellExecWithStderr( $cmd, $retval );
+                       if ( $retval !== 0 ) {
+                               $this->logErrorForExternalProcess( $retval, $err, $cmd );
+
+                               return new MediaTransformError( 'thumbnail_error', 0, 0, $err );
+                       }
+
+                       return false;
+               } else {
+                       return parent::rotate( $file, $params );
+               }
+       }
+
+       public function supportsBucketing() {
+               return true;
+       }
+
+       public function sanitizeParamsForBucketing( $params ) {
+               $params = parent::sanitizeParamsForBucketing( $params );
+
+               // Quality needs to be cleared for bucketing. Buckets need to be default quality
+               if ( isset( $params['quality'] ) ) {
+                       unset( $params['quality'] );
+               }
+
+               return $params;
+       }
+
+       /**
+        * @inheritDoc
+        */
+       protected function transformImageMagick( $image, $params ) {
+               global $wgUseTinyRGBForJPGThumbnails;
+
+               $ret = parent::transformImageMagick( $image, $params );
+
+               if ( $ret ) {
+                       return $ret;
+               }
+
+               if ( $wgUseTinyRGBForJPGThumbnails ) {
+                       // T100976 If the profile embedded in the JPG is sRGB, swap it for the smaller
+                       // (and free) TinyRGB
+
+                       /**
+                        * We'll want to replace the color profile for JPGs:
+                        * * in the sRGB color space, or with the sRGB profile
+                        *   (other profiles will be left untouched)
+                        * * without color space or profile, in which case browsers
+                        *   should assume sRGB, but don't always do (e.g. on wide-gamut
+                        *   monitors (unless it's meant for low bandwith)
+                        * @see https://phabricator.wikimedia.org/T134498
+                        */
+                       $colorSpaces = [ self::SRGB_EXIF_COLOR_SPACE, '-' ];
+                       $profiles = [ self::SRGB_ICC_PROFILE_DESCRIPTION ];
+
+                       // we'll also add TinyRGB profile to images lacking a profile, but
+                       // only if they're not low quality (which are meant to save bandwith
+                       // and we don't want to increase the filesize by adding a profile)
+                       if ( isset( $params['quality'] ) && $params['quality'] > 30 ) {
+                               $profiles[] = '-';
+                       }
+
+                       $this->swapICCProfile(
+                               $params['dstPath'],
+                               $colorSpaces,
+                               $profiles,
+                               realpath( __DIR__ ) . '/tinyrgb.icc'
+                       );
+               }
+
+               return false;
+       }
+
+       /**
+        * Swaps an embedded ICC profile for another, if found.
+        * Depends on exiftool, no-op if not installed.
+        * @param string $filepath File to be manipulated (will be overwritten)
+        * @param array $colorSpaces Only process files with this/these Color Space(s)
+        * @param array $oldProfileStrings Exact name(s) of color profile to look for
+        *  (the one that will be replaced)
+        * @param string $profileFilepath ICC profile file to apply to the file
+        * @since 1.26
+        * @return bool
+        */
+       public function swapICCProfile( $filepath, array $colorSpaces,
+                                                                       array $oldProfileStrings, $profileFilepath
+       ) {
+               global $wgExiftool;
+
+               if ( !$wgExiftool || !is_executable( $wgExiftool ) ) {
+                       return false;
+               }
+
+               $cmd = wfEscapeShellArg( $wgExiftool,
+                       '-EXIF:ColorSpace',
+                       '-ICC_Profile:ProfileDescription',
+                       '-S',
+                       '-T',
+                       $filepath
+               );
+
+               $output = wfShellExecWithStderr( $cmd, $retval );
+
+               // Explode EXIF data into an array with [0 => Color Space, 1 => Device Model Desc]
+               $data = explode( "\t", trim( $output ) );
+
+               if ( $retval !== 0 ) {
+                       return false;
+               }
+
+               // 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';
+               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';
+               if ( empty( preg_grep( $profileRegex, $oldProfileStrings ) ) ) {
+                       // We can't establish that this file has the expected ICC profile, don't process it
+                       return false;
+               }
+
+               $cmd = wfEscapeShellArg( $wgExiftool,
+                       '-overwrite_original',
+                       '-icc_profile<=' . $profileFilepath,
+                       $filepath
+               );
+
+               $output = wfShellExecWithStderr( $cmd, $retval );
+
+               if ( $retval !== 0 ) {
+                       $this->logErrorForExternalProcess( $retval, $output, $cmd );
+
+                       return false;
+               }
+
+               return true;
+       }
+}
diff --git a/includes/media/PNG.php b/includes/media/PNG.php
deleted file mode 100644 (file)
index 6748b26..0000000
+++ /dev/null
@@ -1,203 +0,0 @@
-<?php
-/**
- * Handler for PNG images.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Media
- */
-
-/**
- * Handler for PNG images.
- *
- * @ingroup Media
- */
-class PNGHandler extends BitmapHandler {
-       const BROKEN_FILE = '0';
-
-       /**
-        * @param File|FSFile $image
-        * @param string $filename
-        * @return string
-        */
-       function getMetadata( $image, $filename ) {
-               try {
-                       $metadata = BitmapMetadataHandler::PNG( $filename );
-               } catch ( Exception $e ) {
-                       // Broken file?
-                       wfDebug( __METHOD__ . ': ' . $e->getMessage() . "\n" );
-
-                       return self::BROKEN_FILE;
-               }
-
-               return serialize( $metadata );
-       }
-
-       /**
-        * @param File $image
-        * @param bool|IContextSource $context Context to use (optional)
-        * @return array|bool
-        */
-       function formatMetadata( $image, $context = false ) {
-               $meta = $this->getCommonMetaArray( $image );
-               if ( count( $meta ) === 0 ) {
-                       return false;
-               }
-
-               return $this->formatMetadataHelper( $meta, $context );
-       }
-
-       /**
-        * Get a file type independent array of metadata.
-        *
-        * @param File $image
-        * @return array The metadata array
-        */
-       public function getCommonMetaArray( File $image ) {
-               $meta = $image->getMetadata();
-
-               if ( !$meta ) {
-                       return [];
-               }
-               $meta = unserialize( $meta );
-               if ( !isset( $meta['metadata'] ) ) {
-                       return [];
-               }
-               unset( $meta['metadata']['_MW_PNG_VERSION'] );
-
-               return $meta['metadata'];
-       }
-
-       /**
-        * @param File $image
-        * @return bool
-        */
-       function isAnimatedImage( $image ) {
-               $ser = $image->getMetadata();
-               if ( $ser ) {
-                       $metadata = unserialize( $ser );
-                       if ( $metadata['frameCount'] > 1 ) {
-                               return true;
-                       }
-               }
-
-               return false;
-       }
-
-       /**
-        * We do not support making APNG thumbnails, so always false
-        * @param File $image
-        * @return bool False
-        */
-       function canAnimateThumbnail( $image ) {
-               return false;
-       }
-
-       function getMetadataType( $image ) {
-               return 'parsed-png';
-       }
-
-       function isMetadataValid( $image, $metadata ) {
-               if ( $metadata === self::BROKEN_FILE ) {
-                       // Do not repetitivly regenerate metadata on broken file.
-                       return self::METADATA_GOOD;
-               }
-
-               Wikimedia\suppressWarnings();
-               $data = unserialize( $metadata );
-               Wikimedia\restoreWarnings();
-
-               if ( !$data || !is_array( $data ) ) {
-                       wfDebug( __METHOD__ . " invalid png metadata\n" );
-
-                       return self::METADATA_BAD;
-               }
-
-               if ( !isset( $data['metadata']['_MW_PNG_VERSION'] )
-                       || $data['metadata']['_MW_PNG_VERSION'] != PNGMetadataExtractor::VERSION
-               ) {
-                       wfDebug( __METHOD__ . " old but compatible png metadata\n" );
-
-                       return self::METADATA_COMPATIBLE;
-               }
-
-               return self::METADATA_GOOD;
-       }
-
-       /**
-        * @param File $image
-        * @return string
-        */
-       function getLongDesc( $image ) {
-               global $wgLang;
-               $original = parent::getLongDesc( $image );
-
-               Wikimedia\suppressWarnings();
-               $metadata = unserialize( $image->getMetadata() );
-               Wikimedia\restoreWarnings();
-
-               if ( !$metadata || $metadata['frameCount'] <= 0 ) {
-                       return $original;
-               }
-
-               $info = [];
-               $info[] = $original;
-
-               if ( $metadata['loopCount'] == 0 ) {
-                       $info[] = wfMessage( 'file-info-png-looped' )->parse();
-               } elseif ( $metadata['loopCount'] > 1 ) {
-                       $info[] = wfMessage( 'file-info-png-repeat' )->numParams( $metadata['loopCount'] )->parse();
-               }
-
-               if ( $metadata['frameCount'] > 0 ) {
-                       $info[] = wfMessage( 'file-info-png-frames' )->numParams( $metadata['frameCount'] )->parse();
-               }
-
-               if ( $metadata['duration'] ) {
-                       $info[] = $wgLang->formatTimePeriod( $metadata['duration'] );
-               }
-
-               return $wgLang->commaList( $info );
-       }
-
-       /**
-        * Return the duration of an APNG file.
-        *
-        * Shown in the &query=imageinfo&iiprop=size api query.
-        *
-        * @param File $file
-        * @return float The duration of the file.
-        */
-       public function getLength( $file ) {
-               $serMeta = $file->getMetadata();
-               Wikimedia\suppressWarnings();
-               $metadata = unserialize( $serMeta );
-               Wikimedia\restoreWarnings();
-
-               if ( !$metadata || !isset( $metadata['duration'] ) || !$metadata['duration'] ) {
-                       return 0.0;
-               } else {
-                       return (float)$metadata['duration'];
-               }
-       }
-
-       // PNGs should be easy to support, but it will need some sharpening applied
-       // and another user test to check if the perceived quality change is noticeable
-       public function supportsBucketing() {
-               return false;
-       }
-}
diff --git a/includes/media/PNGHandler.php b/includes/media/PNGHandler.php
new file mode 100644 (file)
index 0000000..6748b26
--- /dev/null
@@ -0,0 +1,203 @@
+<?php
+/**
+ * Handler for PNG images.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Media
+ */
+
+/**
+ * Handler for PNG images.
+ *
+ * @ingroup Media
+ */
+class PNGHandler extends BitmapHandler {
+       const BROKEN_FILE = '0';
+
+       /**
+        * @param File|FSFile $image
+        * @param string $filename
+        * @return string
+        */
+       function getMetadata( $image, $filename ) {
+               try {
+                       $metadata = BitmapMetadataHandler::PNG( $filename );
+               } catch ( Exception $e ) {
+                       // Broken file?
+                       wfDebug( __METHOD__ . ': ' . $e->getMessage() . "\n" );
+
+                       return self::BROKEN_FILE;
+               }
+
+               return serialize( $metadata );
+       }
+
+       /**
+        * @param File $image
+        * @param bool|IContextSource $context Context to use (optional)
+        * @return array|bool
+        */
+       function formatMetadata( $image, $context = false ) {
+               $meta = $this->getCommonMetaArray( $image );
+               if ( count( $meta ) === 0 ) {
+                       return false;
+               }
+
+               return $this->formatMetadataHelper( $meta, $context );
+       }
+
+       /**
+        * Get a file type independent array of metadata.
+        *
+        * @param File $image
+        * @return array The metadata array
+        */
+       public function getCommonMetaArray( File $image ) {
+               $meta = $image->getMetadata();
+
+               if ( !$meta ) {
+                       return [];
+               }
+               $meta = unserialize( $meta );
+               if ( !isset( $meta['metadata'] ) ) {
+                       return [];
+               }
+               unset( $meta['metadata']['_MW_PNG_VERSION'] );
+
+               return $meta['metadata'];
+       }
+
+       /**
+        * @param File $image
+        * @return bool
+        */
+       function isAnimatedImage( $image ) {
+               $ser = $image->getMetadata();
+               if ( $ser ) {
+                       $metadata = unserialize( $ser );
+                       if ( $metadata['frameCount'] > 1 ) {
+                               return true;
+                       }
+               }
+
+               return false;
+       }
+
+       /**
+        * We do not support making APNG thumbnails, so always false
+        * @param File $image
+        * @return bool False
+        */
+       function canAnimateThumbnail( $image ) {
+               return false;
+       }
+
+       function getMetadataType( $image ) {
+               return 'parsed-png';
+       }
+
+       function isMetadataValid( $image, $metadata ) {
+               if ( $metadata === self::BROKEN_FILE ) {
+                       // Do not repetitivly regenerate metadata on broken file.
+                       return self::METADATA_GOOD;
+               }
+
+               Wikimedia\suppressWarnings();
+               $data = unserialize( $metadata );
+               Wikimedia\restoreWarnings();
+
+               if ( !$data || !is_array( $data ) ) {
+                       wfDebug( __METHOD__ . " invalid png metadata\n" );
+
+                       return self::METADATA_BAD;
+               }
+
+               if ( !isset( $data['metadata']['_MW_PNG_VERSION'] )
+                       || $data['metadata']['_MW_PNG_VERSION'] != PNGMetadataExtractor::VERSION
+               ) {
+                       wfDebug( __METHOD__ . " old but compatible png metadata\n" );
+
+                       return self::METADATA_COMPATIBLE;
+               }
+
+               return self::METADATA_GOOD;
+       }
+
+       /**
+        * @param File $image
+        * @return string
+        */
+       function getLongDesc( $image ) {
+               global $wgLang;
+               $original = parent::getLongDesc( $image );
+
+               Wikimedia\suppressWarnings();
+               $metadata = unserialize( $image->getMetadata() );
+               Wikimedia\restoreWarnings();
+
+               if ( !$metadata || $metadata['frameCount'] <= 0 ) {
+                       return $original;
+               }
+
+               $info = [];
+               $info[] = $original;
+
+               if ( $metadata['loopCount'] == 0 ) {
+                       $info[] = wfMessage( 'file-info-png-looped' )->parse();
+               } elseif ( $metadata['loopCount'] > 1 ) {
+                       $info[] = wfMessage( 'file-info-png-repeat' )->numParams( $metadata['loopCount'] )->parse();
+               }
+
+               if ( $metadata['frameCount'] > 0 ) {
+                       $info[] = wfMessage( 'file-info-png-frames' )->numParams( $metadata['frameCount'] )->parse();
+               }
+
+               if ( $metadata['duration'] ) {
+                       $info[] = $wgLang->formatTimePeriod( $metadata['duration'] );
+               }
+
+               return $wgLang->commaList( $info );
+       }
+
+       /**
+        * Return the duration of an APNG file.
+        *
+        * Shown in the &query=imageinfo&iiprop=size api query.
+        *
+        * @param File $file
+        * @return float The duration of the file.
+        */
+       public function getLength( $file ) {
+               $serMeta = $file->getMetadata();
+               Wikimedia\suppressWarnings();
+               $metadata = unserialize( $serMeta );
+               Wikimedia\restoreWarnings();
+
+               if ( !$metadata || !isset( $metadata['duration'] ) || !$metadata['duration'] ) {
+                       return 0.0;
+               } else {
+                       return (float)$metadata['duration'];
+               }
+       }
+
+       // PNGs should be easy to support, but it will need some sharpening applied
+       // and another user test to check if the perceived quality change is noticeable
+       public function supportsBucketing() {
+               return false;
+       }
+}
diff --git a/includes/media/SVG.php b/includes/media/SVG.php
deleted file mode 100644 (file)
index 9085421..0000000
+++ /dev/null
@@ -1,593 +0,0 @@
-<?php
-/**
- * Handler for SVG images.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Media
- */
-use Wikimedia\ScopedCallback;
-
-/**
- * Handler for SVG images.
- *
- * @ingroup Media
- */
-class SvgHandler extends ImageHandler {
-       const SVG_METADATA_VERSION = 2;
-
-       /** @var array A list of metadata tags that can be converted
-        *  to the commonly used exif tags. This allows messages
-        *  to be reused, and consistent tag names for {{#formatmetadata:..}}
-        */
-       private static $metaConversion = [
-               'originalwidth' => 'ImageWidth',
-               'originalheight' => 'ImageLength',
-               'description' => 'ImageDescription',
-               'title' => 'ObjectName',
-       ];
-
-       function isEnabled() {
-               global $wgSVGConverters, $wgSVGConverter;
-               if ( !isset( $wgSVGConverters[$wgSVGConverter] ) ) {
-                       wfDebug( "\$wgSVGConverter is invalid, disabling SVG rendering.\n" );
-
-                       return false;
-               } else {
-                       return true;
-               }
-       }
-
-       public function mustRender( $file ) {
-               return true;
-       }
-
-       function isVectorized( $file ) {
-               return true;
-       }
-
-       /**
-        * @param File $file
-        * @return bool
-        */
-       function isAnimatedImage( $file ) {
-               # @todo Detect animated SVGs
-               $metadata = $file->getMetadata();
-               if ( $metadata ) {
-                       $metadata = $this->unpackMetadata( $metadata );
-                       if ( isset( $metadata['animated'] ) ) {
-                               return $metadata['animated'];
-                       }
-               }
-
-               return false;
-       }
-
-       /**
-        * Which languages (systemLanguage attribute) is supported.
-        *
-        * @note This list is not guaranteed to be exhaustive.
-        * To avoid OOM errors, we only look at first bit of a file.
-        * Thus all languages on this list are present in the file,
-        * but its possible for the file to have a language not on
-        * this list.
-        *
-        * @param File $file
-        * @return array Array of language codes, or empty if no language switching supported.
-        */
-       public function getAvailableLanguages( File $file ) {
-               $metadata = $file->getMetadata();
-               $langList = [];
-               if ( $metadata ) {
-                       $metadata = $this->unpackMetadata( $metadata );
-                       if ( isset( $metadata['translations'] ) ) {
-                               foreach ( $metadata['translations'] as $lang => $langType ) {
-                                       if ( $langType === SVGReader::LANG_FULL_MATCH ) {
-                                               $langList[] = strtolower( $lang );
-                                       }
-                               }
-                       }
-               }
-               return array_unique( $langList );
-       }
-
-       /**
-        * SVG's systemLanguage matching rules state:
-        * 'The `systemLanguage` attribute ... [e]valuates to "true" if one of the languages indicated
-        * by user preferences exactly equals one of the languages given in the value of this parameter,
-        * or if one of the languages indicated by user preferences exactly equals a prefix of one of
-        * the languages given in the value of this parameter such that the first tag character
-        * following the prefix is "-".'
-        *
-        * Return the first element of $svgLanguages that matches $userPreferredLanguage
-        *
-        * @see https://www.w3.org/TR/SVG/struct.html#SystemLanguageAttribute
-        * @param string $userPreferredLanguage
-        * @param array $svgLanguages
-        * @return string|null
-        */
-       public function getMatchedLanguage( $userPreferredLanguage, array $svgLanguages ) {
-               foreach ( $svgLanguages as $svgLang ) {
-                       if ( strcasecmp( $svgLang, $userPreferredLanguage ) === 0 ) {
-                               return $svgLang;
-                       }
-                       $trimmedSvgLang = $svgLang;
-                       while ( strpos( $trimmedSvgLang, '-' ) !== false ) {
-                               $trimmedSvgLang = substr( $trimmedSvgLang, 0, strrpos( $trimmedSvgLang, '-' ) );
-                               if ( strcasecmp( $trimmedSvgLang, $userPreferredLanguage ) === 0 ) {
-                                       return $svgLang;
-                               }
-                       }
-               }
-               return null;
-       }
-
-       /**
-        * What language to render file in if none selected
-        *
-        * @param File $file Language code
-        * @return string
-        */
-       public function getDefaultRenderLanguage( File $file ) {
-               return 'en';
-       }
-
-       /**
-        * We do not support making animated svg thumbnails
-        * @param File $file
-        * @return bool
-        */
-       function canAnimateThumbnail( $file ) {
-               return false;
-       }
-
-       /**
-        * @param File $image
-        * @param array &$params
-        * @return bool
-        */
-       function normaliseParams( $image, &$params ) {
-               global $wgSVGMaxSize;
-               if ( !parent::normaliseParams( $image, $params ) ) {
-                       return false;
-               }
-               # Don't make an image bigger than wgMaxSVGSize on the smaller side
-               if ( $params['physicalWidth'] <= $params['physicalHeight'] ) {
-                       if ( $params['physicalWidth'] > $wgSVGMaxSize ) {
-                               $srcWidth = $image->getWidth( $params['page'] );
-                               $srcHeight = $image->getHeight( $params['page'] );
-                               $params['physicalWidth'] = $wgSVGMaxSize;
-                               $params['physicalHeight'] = File::scaleHeight( $srcWidth, $srcHeight, $wgSVGMaxSize );
-                       }
-               } else {
-                       if ( $params['physicalHeight'] > $wgSVGMaxSize ) {
-                               $srcWidth = $image->getWidth( $params['page'] );
-                               $srcHeight = $image->getHeight( $params['page'] );
-                               $params['physicalWidth'] = File::scaleHeight( $srcHeight, $srcWidth, $wgSVGMaxSize );
-                               $params['physicalHeight'] = $wgSVGMaxSize;
-                       }
-               }
-
-               return true;
-       }
-
-       /**
-        * @param File $image
-        * @param string $dstPath
-        * @param string $dstUrl
-        * @param array $params
-        * @param int $flags
-        * @return bool|MediaTransformError|ThumbnailImage|TransformParameterError
-        */
-       function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 ) {
-               if ( !$this->normaliseParams( $image, $params ) ) {
-                       return new TransformParameterError( $params );
-               }
-               $clientWidth = $params['width'];
-               $clientHeight = $params['height'];
-               $physicalWidth = $params['physicalWidth'];
-               $physicalHeight = $params['physicalHeight'];
-               $lang = isset( $params['lang'] ) ? $params['lang'] : $this->getDefaultRenderLanguage( $image );
-
-               if ( $flags & self::TRANSFORM_LATER ) {
-                       return new ThumbnailImage( $image, $dstUrl, $dstPath, $params );
-               }
-
-               $metadata = $this->unpackMetadata( $image->getMetadata() );
-               if ( isset( $metadata['error'] ) ) { // sanity check
-                       $err = wfMessage( 'svg-long-error', $metadata['error']['message'] );
-
-                       return new MediaTransformError( 'thumbnail_error', $clientWidth, $clientHeight, $err );
-               }
-
-               if ( !wfMkdirParents( dirname( $dstPath ), null, __METHOD__ ) ) {
-                       return new MediaTransformError( 'thumbnail_error', $clientWidth, $clientHeight,
-                               wfMessage( 'thumbnail_dest_directory' ) );
-               }
-
-               $srcPath = $image->getLocalRefPath();
-               if ( $srcPath === false ) { // Failed to get local copy
-                       wfDebugLog( 'thumbnail',
-                               sprintf( 'Thumbnail failed on %s: could not get local copy of "%s"',
-                                       wfHostname(), $image->getName() ) );
-
-                       return new MediaTransformError( 'thumbnail_error',
-                               $params['width'], $params['height'],
-                               wfMessage( 'filemissing' )
-                       );
-               }
-
-               // Make a temp dir with a symlink to the local copy in it.
-               // This plays well with rsvg-convert policy for external entities.
-               // https://git.gnome.org/browse/librsvg/commit/?id=f01aded72c38f0e18bc7ff67dee800e380251c8e
-               $tmpDir = wfTempDir() . '/svg_' . wfRandomString( 24 );
-               $lnPath = "$tmpDir/" . basename( $srcPath );
-               $ok = mkdir( $tmpDir, 0771 );
-               if ( !$ok ) {
-                       wfDebugLog( 'thumbnail',
-                               sprintf( 'Thumbnail failed on %s: could not create temporary directory %s',
-                                       wfHostname(), $tmpDir ) );
-                       return new MediaTransformError( 'thumbnail_error',
-                               $params['width'], $params['height'],
-                               wfMessage( 'thumbnail-temp-create' )->text()
-                       );
-               }
-               $ok = symlink( $srcPath, $lnPath );
-               /** @noinspection PhpUnusedLocalVariableInspection */
-               $cleaner = new ScopedCallback( function () use ( $tmpDir, $lnPath ) {
-                       Wikimedia\suppressWarnings();
-                       unlink( $lnPath );
-                       rmdir( $tmpDir );
-                       Wikimedia\restoreWarnings();
-               } );
-               if ( !$ok ) {
-                       wfDebugLog( 'thumbnail',
-                               sprintf( 'Thumbnail failed on %s: could not link %s to %s',
-                                       wfHostname(), $lnPath, $srcPath ) );
-                       return new MediaTransformError( 'thumbnail_error',
-                               $params['width'], $params['height'],
-                               wfMessage( 'thumbnail-temp-create' )
-                       );
-               }
-
-               $status = $this->rasterize( $lnPath, $dstPath, $physicalWidth, $physicalHeight, $lang );
-               if ( $status === true ) {
-                       return new ThumbnailImage( $image, $dstUrl, $dstPath, $params );
-               } else {
-                       return $status; // MediaTransformError
-               }
-       }
-
-       /**
-        * Transform an SVG file to PNG
-        * This function can be called outside of thumbnail contexts
-        * @param string $srcPath
-        * @param string $dstPath
-        * @param string $width
-        * @param string $height
-        * @param bool|string $lang Language code of the language to render the SVG in
-        * @throws MWException
-        * @return bool|MediaTransformError
-        */
-       public function rasterize( $srcPath, $dstPath, $width, $height, $lang = false ) {
-               global $wgSVGConverters, $wgSVGConverter, $wgSVGConverterPath;
-               $err = false;
-               $retval = '';
-               if ( isset( $wgSVGConverters[$wgSVGConverter] ) ) {
-                       if ( is_array( $wgSVGConverters[$wgSVGConverter] ) ) {
-                               // This is a PHP callable
-                               $func = $wgSVGConverters[$wgSVGConverter][0];
-                               $args = array_merge( [ $srcPath, $dstPath, $width, $height, $lang ],
-                                       array_slice( $wgSVGConverters[$wgSVGConverter], 1 ) );
-                               if ( !is_callable( $func ) ) {
-                                       throw new MWException( "$func is not callable" );
-                               }
-                               $err = call_user_func_array( $func, $args );
-                               $retval = (bool)$err;
-                       } else {
-                               // External command
-                               $cmd = str_replace(
-                                       [ '$path/', '$width', '$height', '$input', '$output' ],
-                                       [ $wgSVGConverterPath ? wfEscapeShellArg( "$wgSVGConverterPath/" ) : "",
-                                               intval( $width ),
-                                               intval( $height ),
-                                               wfEscapeShellArg( $srcPath ),
-                                               wfEscapeShellArg( $dstPath ) ],
-                                       $wgSVGConverters[$wgSVGConverter]
-                               );
-
-                               $env = [];
-                               if ( $lang !== false ) {
-                                       $env['LANG'] = $lang;
-                               }
-
-                               wfDebug( __METHOD__ . ": $cmd\n" );
-                               $err = wfShellExecWithStderr( $cmd, $retval, $env );
-                       }
-               }
-               $removed = $this->removeBadFile( $dstPath, $retval );
-               if ( $retval != 0 || $removed ) {
-                       $this->logErrorForExternalProcess( $retval, $err, $cmd );
-                       return new MediaTransformError( 'thumbnail_error', $width, $height, $err );
-               }
-
-               return true;
-       }
-
-       public static function rasterizeImagickExt( $srcPath, $dstPath, $width, $height ) {
-               $im = new Imagick( $srcPath );
-               $im->setImageFormat( 'png' );
-               $im->setBackgroundColor( 'transparent' );
-               $im->setImageDepth( 8 );
-
-               if ( !$im->thumbnailImage( intval( $width ), intval( $height ), /* fit */ false ) ) {
-                       return 'Could not resize image';
-               }
-               if ( !$im->writeImage( $dstPath ) ) {
-                       return "Could not write to $dstPath";
-               }
-       }
-
-       /**
-        * @param File|FSFile $file
-        * @param string $path Unused
-        * @param bool|array $metadata
-        * @return array
-        */
-       function getImageSize( $file, $path, $metadata = false ) {
-               if ( $metadata === false && $file instanceof File ) {
-                       $metadata = $file->getMetadata();
-               }
-               $metadata = $this->unpackMetadata( $metadata );
-
-               if ( isset( $metadata['width'] ) && isset( $metadata['height'] ) ) {
-                       return [ $metadata['width'], $metadata['height'], 'SVG',
-                               "width=\"{$metadata['width']}\" height=\"{$metadata['height']}\"" ];
-               } else { // error
-                       return [ 0, 0, 'SVG', "width=\"0\" height=\"0\"" ];
-               }
-       }
-
-       function getThumbType( $ext, $mime, $params = null ) {
-               return [ 'png', 'image/png' ];
-       }
-
-       /**
-        * Subtitle for the image. Different from the base
-        * class so it can be denoted that SVG's have
-        * a "nominal" resolution, and not a fixed one,
-        * as well as so animation can be denoted.
-        *
-        * @param File $file
-        * @return string
-        */
-       function getLongDesc( $file ) {
-               global $wgLang;
-
-               $metadata = $this->unpackMetadata( $file->getMetadata() );
-               if ( isset( $metadata['error'] ) ) {
-                       return wfMessage( 'svg-long-error', $metadata['error']['message'] )->text();
-               }
-
-               $size = $wgLang->formatSize( $file->getSize() );
-
-               if ( $this->isAnimatedImage( $file ) ) {
-                       $msg = wfMessage( 'svg-long-desc-animated' );
-               } else {
-                       $msg = wfMessage( 'svg-long-desc' );
-               }
-
-               $msg->numParams( $file->getWidth(), $file->getHeight() )->params( $size );
-
-               return $msg->parse();
-       }
-
-       /**
-        * @param File|FSFile $file
-        * @param string $filename
-        * @return string Serialised metadata
-        */
-       function getMetadata( $file, $filename ) {
-               $metadata = [ 'version' => self::SVG_METADATA_VERSION ];
-               try {
-                       $metadata += SVGMetadataExtractor::getMetadata( $filename );
-               } catch ( Exception $e ) { // @todo SVG specific exceptions
-                       // File not found, broken, etc.
-                       $metadata['error'] = [
-                               'message' => $e->getMessage(),
-                               'code' => $e->getCode()
-                       ];
-                       wfDebug( __METHOD__ . ': ' . $e->getMessage() . "\n" );
-               }
-
-               return serialize( $metadata );
-       }
-
-       function unpackMetadata( $metadata ) {
-               Wikimedia\suppressWarnings();
-               $unser = unserialize( $metadata );
-               Wikimedia\restoreWarnings();
-               if ( isset( $unser['version'] ) && $unser['version'] == self::SVG_METADATA_VERSION ) {
-                       return $unser;
-               } else {
-                       return false;
-               }
-       }
-
-       function getMetadataType( $image ) {
-               return 'parsed-svg';
-       }
-
-       function isMetadataValid( $image, $metadata ) {
-               $meta = $this->unpackMetadata( $metadata );
-               if ( $meta === false ) {
-                       return self::METADATA_BAD;
-               }
-               if ( !isset( $meta['originalWidth'] ) ) {
-                       // Old but compatible
-                       return self::METADATA_COMPATIBLE;
-               }
-
-               return self::METADATA_GOOD;
-       }
-
-       protected function visibleMetadataFields() {
-               $fields = [ 'objectname', 'imagedescription' ];
-
-               return $fields;
-       }
-
-       /**
-        * @param File $file
-        * @param bool|IContextSource $context Context to use (optional)
-        * @return array|bool
-        */
-       function formatMetadata( $file, $context = false ) {
-               $result = [
-                       'visible' => [],
-                       'collapsed' => []
-               ];
-               $metadata = $file->getMetadata();
-               if ( !$metadata ) {
-                       return false;
-               }
-               $metadata = $this->unpackMetadata( $metadata );
-               if ( !$metadata || isset( $metadata['error'] ) ) {
-                       return false;
-               }
-
-               /* @todo Add a formatter
-               $format = new FormatSVG( $metadata );
-               $formatted = $format->getFormattedData();
-               */
-
-               // Sort fields into visible and collapsed
-               $visibleFields = $this->visibleMetadataFields();
-
-               $showMeta = false;
-               foreach ( $metadata as $name => $value ) {
-                       $tag = strtolower( $name );
-                       if ( isset( self::$metaConversion[$tag] ) ) {
-                               $tag = strtolower( self::$metaConversion[$tag] );
-                       } else {
-                               // Do not output other metadata not in list
-                               continue;
-                       }
-                       $showMeta = true;
-                       self::addMeta( $result,
-                               in_array( $tag, $visibleFields ) ? 'visible' : 'collapsed',
-                               'exif',
-                               $tag,
-                               $value
-                       );
-               }
-
-               return $showMeta ? $result : false;
-       }
-
-       /**
-        * @param string $name Parameter name
-        * @param mixed $value Parameter value
-        * @return bool Validity
-        */
-       public function validateParam( $name, $value ) {
-               if ( in_array( $name, [ 'width', 'height' ] ) ) {
-                       // Reject negative heights, widths
-                       return ( $value > 0 );
-               } elseif ( $name == 'lang' ) {
-                       // Validate $code
-                       if ( $value === '' || !Language::isValidCode( $value ) ) {
-                               return false;
-                       }
-
-                       return true;
-               }
-
-               // Only lang, width and height are acceptable keys
-               return false;
-       }
-
-       /**
-        * @param array $params Name=>value pairs of parameters
-        * @return string Filename to use
-        */
-       public function makeParamString( $params ) {
-               $lang = '';
-               if ( isset( $params['lang'] ) && $params['lang'] !== 'en' ) {
-                       $lang = 'lang' . strtolower( $params['lang'] ) . '-';
-               }
-               if ( !isset( $params['width'] ) ) {
-                       return false;
-               }
-
-               return "$lang{$params['width']}px";
-       }
-
-       public function parseParamString( $str ) {
-               $m = false;
-               if ( preg_match( '/^lang([a-z]+(?:-[a-z]+)*)-(\d+)px$/i', $str, $m ) ) {
-                       return [ 'width' => array_pop( $m ), 'lang' => $m[1] ];
-               } elseif ( preg_match( '/^(\d+)px$/', $str, $m ) ) {
-                       return [ 'width' => $m[1], 'lang' => 'en' ];
-               } else {
-                       return false;
-               }
-       }
-
-       public function getParamMap() {
-               return [ 'img_lang' => 'lang', 'img_width' => 'width' ];
-       }
-
-       /**
-        * @param array $params
-        * @return array
-        */
-       function getScriptParams( $params ) {
-               $scriptParams = [ 'width' => $params['width'] ];
-               if ( isset( $params['lang'] ) ) {
-                       $scriptParams['lang'] = $params['lang'];
-               }
-
-               return $scriptParams;
-       }
-
-       public function getCommonMetaArray( File $file ) {
-               $metadata = $file->getMetadata();
-               if ( !$metadata ) {
-                       return [];
-               }
-               $metadata = $this->unpackMetadata( $metadata );
-               if ( !$metadata || isset( $metadata['error'] ) ) {
-                       return [];
-               }
-               $stdMetadata = [];
-               foreach ( $metadata as $name => $value ) {
-                       $tag = strtolower( $name );
-                       if ( $tag === 'originalwidth' || $tag === 'originalheight' ) {
-                               // Skip these. In the exif metadata stuff, it is assumed these
-                               // are measured in px, which is not the case here.
-                               continue;
-                       }
-                       if ( isset( self::$metaConversion[$tag] ) ) {
-                               $tag = self::$metaConversion[$tag];
-                               $stdMetadata[$tag] = $value;
-                       }
-               }
-
-               return $stdMetadata;
-       }
-}
diff --git a/includes/media/SvgHandler.php b/includes/media/SvgHandler.php
new file mode 100644 (file)
index 0000000..9085421
--- /dev/null
@@ -0,0 +1,593 @@
+<?php
+/**
+ * Handler for SVG images.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Media
+ */
+use Wikimedia\ScopedCallback;
+
+/**
+ * Handler for SVG images.
+ *
+ * @ingroup Media
+ */
+class SvgHandler extends ImageHandler {
+       const SVG_METADATA_VERSION = 2;
+
+       /** @var array A list of metadata tags that can be converted
+        *  to the commonly used exif tags. This allows messages
+        *  to be reused, and consistent tag names for {{#formatmetadata:..}}
+        */
+       private static $metaConversion = [
+               'originalwidth' => 'ImageWidth',
+               'originalheight' => 'ImageLength',
+               'description' => 'ImageDescription',
+               'title' => 'ObjectName',
+       ];
+
+       function isEnabled() {
+               global $wgSVGConverters, $wgSVGConverter;
+               if ( !isset( $wgSVGConverters[$wgSVGConverter] ) ) {
+                       wfDebug( "\$wgSVGConverter is invalid, disabling SVG rendering.\n" );
+
+                       return false;
+               } else {
+                       return true;
+               }
+       }
+
+       public function mustRender( $file ) {
+               return true;
+       }
+
+       function isVectorized( $file ) {
+               return true;
+       }
+
+       /**
+        * @param File $file
+        * @return bool
+        */
+       function isAnimatedImage( $file ) {
+               # @todo Detect animated SVGs
+               $metadata = $file->getMetadata();
+               if ( $metadata ) {
+                       $metadata = $this->unpackMetadata( $metadata );
+                       if ( isset( $metadata['animated'] ) ) {
+                               return $metadata['animated'];
+                       }
+               }
+
+               return false;
+       }
+
+       /**
+        * Which languages (systemLanguage attribute) is supported.
+        *
+        * @note This list is not guaranteed to be exhaustive.
+        * To avoid OOM errors, we only look at first bit of a file.
+        * Thus all languages on this list are present in the file,
+        * but its possible for the file to have a language not on
+        * this list.
+        *
+        * @param File $file
+        * @return array Array of language codes, or empty if no language switching supported.
+        */
+       public function getAvailableLanguages( File $file ) {
+               $metadata = $file->getMetadata();
+               $langList = [];
+               if ( $metadata ) {
+                       $metadata = $this->unpackMetadata( $metadata );
+                       if ( isset( $metadata['translations'] ) ) {
+                               foreach ( $metadata['translations'] as $lang => $langType ) {
+                                       if ( $langType === SVGReader::LANG_FULL_MATCH ) {
+                                               $langList[] = strtolower( $lang );
+                                       }
+                               }
+                       }
+               }
+               return array_unique( $langList );
+       }
+
+       /**
+        * SVG's systemLanguage matching rules state:
+        * 'The `systemLanguage` attribute ... [e]valuates to "true" if one of the languages indicated
+        * by user preferences exactly equals one of the languages given in the value of this parameter,
+        * or if one of the languages indicated by user preferences exactly equals a prefix of one of
+        * the languages given in the value of this parameter such that the first tag character
+        * following the prefix is "-".'
+        *
+        * Return the first element of $svgLanguages that matches $userPreferredLanguage
+        *
+        * @see https://www.w3.org/TR/SVG/struct.html#SystemLanguageAttribute
+        * @param string $userPreferredLanguage
+        * @param array $svgLanguages
+        * @return string|null
+        */
+       public function getMatchedLanguage( $userPreferredLanguage, array $svgLanguages ) {
+               foreach ( $svgLanguages as $svgLang ) {
+                       if ( strcasecmp( $svgLang, $userPreferredLanguage ) === 0 ) {
+                               return $svgLang;
+                       }
+                       $trimmedSvgLang = $svgLang;
+                       while ( strpos( $trimmedSvgLang, '-' ) !== false ) {
+                               $trimmedSvgLang = substr( $trimmedSvgLang, 0, strrpos( $trimmedSvgLang, '-' ) );
+                               if ( strcasecmp( $trimmedSvgLang, $userPreferredLanguage ) === 0 ) {
+                                       return $svgLang;
+                               }
+                       }
+               }
+               return null;
+       }
+
+       /**
+        * What language to render file in if none selected
+        *
+        * @param File $file Language code
+        * @return string
+        */
+       public function getDefaultRenderLanguage( File $file ) {
+               return 'en';
+       }
+
+       /**
+        * We do not support making animated svg thumbnails
+        * @param File $file
+        * @return bool
+        */
+       function canAnimateThumbnail( $file ) {
+               return false;
+       }
+
+       /**
+        * @param File $image
+        * @param array &$params
+        * @return bool
+        */
+       function normaliseParams( $image, &$params ) {
+               global $wgSVGMaxSize;
+               if ( !parent::normaliseParams( $image, $params ) ) {
+                       return false;
+               }
+               # Don't make an image bigger than wgMaxSVGSize on the smaller side
+               if ( $params['physicalWidth'] <= $params['physicalHeight'] ) {
+                       if ( $params['physicalWidth'] > $wgSVGMaxSize ) {
+                               $srcWidth = $image->getWidth( $params['page'] );
+                               $srcHeight = $image->getHeight( $params['page'] );
+                               $params['physicalWidth'] = $wgSVGMaxSize;
+                               $params['physicalHeight'] = File::scaleHeight( $srcWidth, $srcHeight, $wgSVGMaxSize );
+                       }
+               } else {
+                       if ( $params['physicalHeight'] > $wgSVGMaxSize ) {
+                               $srcWidth = $image->getWidth( $params['page'] );
+                               $srcHeight = $image->getHeight( $params['page'] );
+                               $params['physicalWidth'] = File::scaleHeight( $srcHeight, $srcWidth, $wgSVGMaxSize );
+                               $params['physicalHeight'] = $wgSVGMaxSize;
+                       }
+               }
+
+               return true;
+       }
+
+       /**
+        * @param File $image
+        * @param string $dstPath
+        * @param string $dstUrl
+        * @param array $params
+        * @param int $flags
+        * @return bool|MediaTransformError|ThumbnailImage|TransformParameterError
+        */
+       function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 ) {
+               if ( !$this->normaliseParams( $image, $params ) ) {
+                       return new TransformParameterError( $params );
+               }
+               $clientWidth = $params['width'];
+               $clientHeight = $params['height'];
+               $physicalWidth = $params['physicalWidth'];
+               $physicalHeight = $params['physicalHeight'];
+               $lang = isset( $params['lang'] ) ? $params['lang'] : $this->getDefaultRenderLanguage( $image );
+
+               if ( $flags & self::TRANSFORM_LATER ) {
+                       return new ThumbnailImage( $image, $dstUrl, $dstPath, $params );
+               }
+
+               $metadata = $this->unpackMetadata( $image->getMetadata() );
+               if ( isset( $metadata['error'] ) ) { // sanity check
+                       $err = wfMessage( 'svg-long-error', $metadata['error']['message'] );
+
+                       return new MediaTransformError( 'thumbnail_error', $clientWidth, $clientHeight, $err );
+               }
+
+               if ( !wfMkdirParents( dirname( $dstPath ), null, __METHOD__ ) ) {
+                       return new MediaTransformError( 'thumbnail_error', $clientWidth, $clientHeight,
+                               wfMessage( 'thumbnail_dest_directory' ) );
+               }
+
+               $srcPath = $image->getLocalRefPath();
+               if ( $srcPath === false ) { // Failed to get local copy
+                       wfDebugLog( 'thumbnail',
+                               sprintf( 'Thumbnail failed on %s: could not get local copy of "%s"',
+                                       wfHostname(), $image->getName() ) );
+
+                       return new MediaTransformError( 'thumbnail_error',
+                               $params['width'], $params['height'],
+                               wfMessage( 'filemissing' )
+                       );
+               }
+
+               // Make a temp dir with a symlink to the local copy in it.
+               // This plays well with rsvg-convert policy for external entities.
+               // https://git.gnome.org/browse/librsvg/commit/?id=f01aded72c38f0e18bc7ff67dee800e380251c8e
+               $tmpDir = wfTempDir() . '/svg_' . wfRandomString( 24 );
+               $lnPath = "$tmpDir/" . basename( $srcPath );
+               $ok = mkdir( $tmpDir, 0771 );
+               if ( !$ok ) {
+                       wfDebugLog( 'thumbnail',
+                               sprintf( 'Thumbnail failed on %s: could not create temporary directory %s',
+                                       wfHostname(), $tmpDir ) );
+                       return new MediaTransformError( 'thumbnail_error',
+                               $params['width'], $params['height'],
+                               wfMessage( 'thumbnail-temp-create' )->text()
+                       );
+               }
+               $ok = symlink( $srcPath, $lnPath );
+               /** @noinspection PhpUnusedLocalVariableInspection */
+               $cleaner = new ScopedCallback( function () use ( $tmpDir, $lnPath ) {
+                       Wikimedia\suppressWarnings();
+                       unlink( $lnPath );
+                       rmdir( $tmpDir );
+                       Wikimedia\restoreWarnings();
+               } );
+               if ( !$ok ) {
+                       wfDebugLog( 'thumbnail',
+                               sprintf( 'Thumbnail failed on %s: could not link %s to %s',
+                                       wfHostname(), $lnPath, $srcPath ) );
+                       return new MediaTransformError( 'thumbnail_error',
+                               $params['width'], $params['height'],
+                               wfMessage( 'thumbnail-temp-create' )
+                       );
+               }
+
+               $status = $this->rasterize( $lnPath, $dstPath, $physicalWidth, $physicalHeight, $lang );
+               if ( $status === true ) {
+                       return new ThumbnailImage( $image, $dstUrl, $dstPath, $params );
+               } else {
+                       return $status; // MediaTransformError
+               }
+       }
+
+       /**
+        * Transform an SVG file to PNG
+        * This function can be called outside of thumbnail contexts
+        * @param string $srcPath
+        * @param string $dstPath
+        * @param string $width
+        * @param string $height
+        * @param bool|string $lang Language code of the language to render the SVG in
+        * @throws MWException
+        * @return bool|MediaTransformError
+        */
+       public function rasterize( $srcPath, $dstPath, $width, $height, $lang = false ) {
+               global $wgSVGConverters, $wgSVGConverter, $wgSVGConverterPath;
+               $err = false;
+               $retval = '';
+               if ( isset( $wgSVGConverters[$wgSVGConverter] ) ) {
+                       if ( is_array( $wgSVGConverters[$wgSVGConverter] ) ) {
+                               // This is a PHP callable
+                               $func = $wgSVGConverters[$wgSVGConverter][0];
+                               $args = array_merge( [ $srcPath, $dstPath, $width, $height, $lang ],
+                                       array_slice( $wgSVGConverters[$wgSVGConverter], 1 ) );
+                               if ( !is_callable( $func ) ) {
+                                       throw new MWException( "$func is not callable" );
+                               }
+                               $err = call_user_func_array( $func, $args );
+                               $retval = (bool)$err;
+                       } else {
+                               // External command
+                               $cmd = str_replace(
+                                       [ '$path/', '$width', '$height', '$input', '$output' ],
+                                       [ $wgSVGConverterPath ? wfEscapeShellArg( "$wgSVGConverterPath/" ) : "",
+                                               intval( $width ),
+                                               intval( $height ),
+                                               wfEscapeShellArg( $srcPath ),
+                                               wfEscapeShellArg( $dstPath ) ],
+                                       $wgSVGConverters[$wgSVGConverter]
+                               );
+
+                               $env = [];
+                               if ( $lang !== false ) {
+                                       $env['LANG'] = $lang;
+                               }
+
+                               wfDebug( __METHOD__ . ": $cmd\n" );
+                               $err = wfShellExecWithStderr( $cmd, $retval, $env );
+                       }
+               }
+               $removed = $this->removeBadFile( $dstPath, $retval );
+               if ( $retval != 0 || $removed ) {
+                       $this->logErrorForExternalProcess( $retval, $err, $cmd );
+                       return new MediaTransformError( 'thumbnail_error', $width, $height, $err );
+               }
+
+               return true;
+       }
+
+       public static function rasterizeImagickExt( $srcPath, $dstPath, $width, $height ) {
+               $im = new Imagick( $srcPath );
+               $im->setImageFormat( 'png' );
+               $im->setBackgroundColor( 'transparent' );
+               $im->setImageDepth( 8 );
+
+               if ( !$im->thumbnailImage( intval( $width ), intval( $height ), /* fit */ false ) ) {
+                       return 'Could not resize image';
+               }
+               if ( !$im->writeImage( $dstPath ) ) {
+                       return "Could not write to $dstPath";
+               }
+       }
+
+       /**
+        * @param File|FSFile $file
+        * @param string $path Unused
+        * @param bool|array $metadata
+        * @return array
+        */
+       function getImageSize( $file, $path, $metadata = false ) {
+               if ( $metadata === false && $file instanceof File ) {
+                       $metadata = $file->getMetadata();
+               }
+               $metadata = $this->unpackMetadata( $metadata );
+
+               if ( isset( $metadata['width'] ) && isset( $metadata['height'] ) ) {
+                       return [ $metadata['width'], $metadata['height'], 'SVG',
+                               "width=\"{$metadata['width']}\" height=\"{$metadata['height']}\"" ];
+               } else { // error
+                       return [ 0, 0, 'SVG', "width=\"0\" height=\"0\"" ];
+               }
+       }
+
+       function getThumbType( $ext, $mime, $params = null ) {
+               return [ 'png', 'image/png' ];
+       }
+
+       /**
+        * Subtitle for the image. Different from the base
+        * class so it can be denoted that SVG's have
+        * a "nominal" resolution, and not a fixed one,
+        * as well as so animation can be denoted.
+        *
+        * @param File $file
+        * @return string
+        */
+       function getLongDesc( $file ) {
+               global $wgLang;
+
+               $metadata = $this->unpackMetadata( $file->getMetadata() );
+               if ( isset( $metadata['error'] ) ) {
+                       return wfMessage( 'svg-long-error', $metadata['error']['message'] )->text();
+               }
+
+               $size = $wgLang->formatSize( $file->getSize() );
+
+               if ( $this->isAnimatedImage( $file ) ) {
+                       $msg = wfMessage( 'svg-long-desc-animated' );
+               } else {
+                       $msg = wfMessage( 'svg-long-desc' );
+               }
+
+               $msg->numParams( $file->getWidth(), $file->getHeight() )->params( $size );
+
+               return $msg->parse();
+       }
+
+       /**
+        * @param File|FSFile $file
+        * @param string $filename
+        * @return string Serialised metadata
+        */
+       function getMetadata( $file, $filename ) {
+               $metadata = [ 'version' => self::SVG_METADATA_VERSION ];
+               try {
+                       $metadata += SVGMetadataExtractor::getMetadata( $filename );
+               } catch ( Exception $e ) { // @todo SVG specific exceptions
+                       // File not found, broken, etc.
+                       $metadata['error'] = [
+                               'message' => $e->getMessage(),
+                               'code' => $e->getCode()
+                       ];
+                       wfDebug( __METHOD__ . ': ' . $e->getMessage() . "\n" );
+               }
+
+               return serialize( $metadata );
+       }
+
+       function unpackMetadata( $metadata ) {
+               Wikimedia\suppressWarnings();
+               $unser = unserialize( $metadata );
+               Wikimedia\restoreWarnings();
+               if ( isset( $unser['version'] ) && $unser['version'] == self::SVG_METADATA_VERSION ) {
+                       return $unser;
+               } else {
+                       return false;
+               }
+       }
+
+       function getMetadataType( $image ) {
+               return 'parsed-svg';
+       }
+
+       function isMetadataValid( $image, $metadata ) {
+               $meta = $this->unpackMetadata( $metadata );
+               if ( $meta === false ) {
+                       return self::METADATA_BAD;
+               }
+               if ( !isset( $meta['originalWidth'] ) ) {
+                       // Old but compatible
+                       return self::METADATA_COMPATIBLE;
+               }
+
+               return self::METADATA_GOOD;
+       }
+
+       protected function visibleMetadataFields() {
+               $fields = [ 'objectname', 'imagedescription' ];
+
+               return $fields;
+       }
+
+       /**
+        * @param File $file
+        * @param bool|IContextSource $context Context to use (optional)
+        * @return array|bool
+        */
+       function formatMetadata( $file, $context = false ) {
+               $result = [
+                       'visible' => [],
+                       'collapsed' => []
+               ];
+               $metadata = $file->getMetadata();
+               if ( !$metadata ) {
+                       return false;
+               }
+               $metadata = $this->unpackMetadata( $metadata );
+               if ( !$metadata || isset( $metadata['error'] ) ) {
+                       return false;
+               }
+
+               /* @todo Add a formatter
+               $format = new FormatSVG( $metadata );
+               $formatted = $format->getFormattedData();
+               */
+
+               // Sort fields into visible and collapsed
+               $visibleFields = $this->visibleMetadataFields();
+
+               $showMeta = false;
+               foreach ( $metadata as $name => $value ) {
+                       $tag = strtolower( $name );
+                       if ( isset( self::$metaConversion[$tag] ) ) {
+                               $tag = strtolower( self::$metaConversion[$tag] );
+                       } else {
+                               // Do not output other metadata not in list
+                               continue;
+                       }
+                       $showMeta = true;
+                       self::addMeta( $result,
+                               in_array( $tag, $visibleFields ) ? 'visible' : 'collapsed',
+                               'exif',
+                               $tag,
+                               $value
+                       );
+               }
+
+               return $showMeta ? $result : false;
+       }
+
+       /**
+        * @param string $name Parameter name
+        * @param mixed $value Parameter value
+        * @return bool Validity
+        */
+       public function validateParam( $name, $value ) {
+               if ( in_array( $name, [ 'width', 'height' ] ) ) {
+                       // Reject negative heights, widths
+                       return ( $value > 0 );
+               } elseif ( $name == 'lang' ) {
+                       // Validate $code
+                       if ( $value === '' || !Language::isValidCode( $value ) ) {
+                               return false;
+                       }
+
+                       return true;
+               }
+
+               // Only lang, width and height are acceptable keys
+               return false;
+       }
+
+       /**
+        * @param array $params Name=>value pairs of parameters
+        * @return string Filename to use
+        */
+       public function makeParamString( $params ) {
+               $lang = '';
+               if ( isset( $params['lang'] ) && $params['lang'] !== 'en' ) {
+                       $lang = 'lang' . strtolower( $params['lang'] ) . '-';
+               }
+               if ( !isset( $params['width'] ) ) {
+                       return false;
+               }
+
+               return "$lang{$params['width']}px";
+       }
+
+       public function parseParamString( $str ) {
+               $m = false;
+               if ( preg_match( '/^lang([a-z]+(?:-[a-z]+)*)-(\d+)px$/i', $str, $m ) ) {
+                       return [ 'width' => array_pop( $m ), 'lang' => $m[1] ];
+               } elseif ( preg_match( '/^(\d+)px$/', $str, $m ) ) {
+                       return [ 'width' => $m[1], 'lang' => 'en' ];
+               } else {
+                       return false;
+               }
+       }
+
+       public function getParamMap() {
+               return [ 'img_lang' => 'lang', 'img_width' => 'width' ];
+       }
+
+       /**
+        * @param array $params
+        * @return array
+        */
+       function getScriptParams( $params ) {
+               $scriptParams = [ 'width' => $params['width'] ];
+               if ( isset( $params['lang'] ) ) {
+                       $scriptParams['lang'] = $params['lang'];
+               }
+
+               return $scriptParams;
+       }
+
+       public function getCommonMetaArray( File $file ) {
+               $metadata = $file->getMetadata();
+               if ( !$metadata ) {
+                       return [];
+               }
+               $metadata = $this->unpackMetadata( $metadata );
+               if ( !$metadata || isset( $metadata['error'] ) ) {
+                       return [];
+               }
+               $stdMetadata = [];
+               foreach ( $metadata as $name => $value ) {
+                       $tag = strtolower( $name );
+                       if ( $tag === 'originalwidth' || $tag === 'originalheight' ) {
+                               // Skip these. In the exif metadata stuff, it is assumed these
+                               // are measured in px, which is not the case here.
+                               continue;
+                       }
+                       if ( isset( self::$metaConversion[$tag] ) ) {
+                               $tag = self::$metaConversion[$tag];
+                               $stdMetadata[$tag] = $value;
+                       }
+               }
+
+               return $stdMetadata;
+       }
+}
diff --git a/includes/media/Tiff.php b/includes/media/Tiff.php
deleted file mode 100644 (file)
index f0f4cda..0000000
+++ /dev/null
@@ -1,107 +0,0 @@
-<?php
-/**
- * Handler for Tiff images.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Media
- */
-
-/**
- * Handler for Tiff images.
- *
- * @ingroup Media
- */
-class TiffHandler extends ExifBitmapHandler {
-       const EXPENSIVE_SIZE_LIMIT = 10485760; // TIFF files over 10M are considered expensive to thumbnail
-
-       /**
-        * Conversion to PNG for inline display can be disabled here...
-        * Note scaling should work with ImageMagick, but may not with GD scaling.
-        *
-        * Files pulled from an another MediaWiki instance via ForeignAPIRepo /
-        * InstantCommons will have thumbnails managed from the remote instance,
-        * so we can skip this check.
-        *
-        * @param File $file
-        * @return bool
-        */
-       public function canRender( $file ) {
-               global $wgTiffThumbnailType;
-
-               return (bool)$wgTiffThumbnailType
-                       || $file->getRepo() instanceof ForeignAPIRepo;
-       }
-
-       /**
-        * Browsers don't support TIFF inline generally...
-        * For inline display, we need to convert to PNG.
-        *
-        * @param File $file
-        * @return bool
-        */
-       public function mustRender( $file ) {
-               return true;
-       }
-
-       /**
-        * @param string $ext
-        * @param string $mime
-        * @param array $params
-        * @return bool
-        */
-       function getThumbType( $ext, $mime, $params = null ) {
-               global $wgTiffThumbnailType;
-
-               return $wgTiffThumbnailType;
-       }
-
-       /**
-        * @param File|FSFile $image
-        * @param string $filename
-        * @throws MWException
-        * @return string
-        */
-       function getMetadata( $image, $filename ) {
-               global $wgShowEXIF;
-
-               if ( $wgShowEXIF ) {
-                       try {
-                               $meta = BitmapMetadataHandler::Tiff( $filename );
-                               if ( !is_array( $meta ) ) {
-                                       // This should never happen, but doesn't hurt to be paranoid.
-                                       throw new MWException( 'Metadata array is not an array' );
-                               }
-                               $meta['MEDIAWIKI_EXIF_VERSION'] = Exif::version();
-
-                               return serialize( $meta );
-                       } catch ( Exception $e ) {
-                               // BitmapMetadataHandler throws an exception in certain exceptional
-                               // cases like if file does not exist.
-                               wfDebug( __METHOD__ . ': ' . $e->getMessage() . "\n" );
-
-                               return ExifBitmapHandler::BROKEN_FILE;
-                       }
-               } else {
-                       return '';
-               }
-       }
-
-       public function isExpensiveToThumbnail( $file ) {
-               return $file->getSize() > static::EXPENSIVE_SIZE_LIMIT;
-       }
-}
diff --git a/includes/media/TiffHandler.php b/includes/media/TiffHandler.php
new file mode 100644 (file)
index 0000000..f0f4cda
--- /dev/null
@@ -0,0 +1,107 @@
+<?php
+/**
+ * Handler for Tiff images.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Media
+ */
+
+/**
+ * Handler for Tiff images.
+ *
+ * @ingroup Media
+ */
+class TiffHandler extends ExifBitmapHandler {
+       const EXPENSIVE_SIZE_LIMIT = 10485760; // TIFF files over 10M are considered expensive to thumbnail
+
+       /**
+        * Conversion to PNG for inline display can be disabled here...
+        * Note scaling should work with ImageMagick, but may not with GD scaling.
+        *
+        * Files pulled from an another MediaWiki instance via ForeignAPIRepo /
+        * InstantCommons will have thumbnails managed from the remote instance,
+        * so we can skip this check.
+        *
+        * @param File $file
+        * @return bool
+        */
+       public function canRender( $file ) {
+               global $wgTiffThumbnailType;
+
+               return (bool)$wgTiffThumbnailType
+                       || $file->getRepo() instanceof ForeignAPIRepo;
+       }
+
+       /**
+        * Browsers don't support TIFF inline generally...
+        * For inline display, we need to convert to PNG.
+        *
+        * @param File $file
+        * @return bool
+        */
+       public function mustRender( $file ) {
+               return true;
+       }
+
+       /**
+        * @param string $ext
+        * @param string $mime
+        * @param array $params
+        * @return bool
+        */
+       function getThumbType( $ext, $mime, $params = null ) {
+               global $wgTiffThumbnailType;
+
+               return $wgTiffThumbnailType;
+       }
+
+       /**
+        * @param File|FSFile $image
+        * @param string $filename
+        * @throws MWException
+        * @return string
+        */
+       function getMetadata( $image, $filename ) {
+               global $wgShowEXIF;
+
+               if ( $wgShowEXIF ) {
+                       try {
+                               $meta = BitmapMetadataHandler::Tiff( $filename );
+                               if ( !is_array( $meta ) ) {
+                                       // This should never happen, but doesn't hurt to be paranoid.
+                                       throw new MWException( 'Metadata array is not an array' );
+                               }
+                               $meta['MEDIAWIKI_EXIF_VERSION'] = Exif::version();
+
+                               return serialize( $meta );
+                       } catch ( Exception $e ) {
+                               // BitmapMetadataHandler throws an exception in certain exceptional
+                               // cases like if file does not exist.
+                               wfDebug( __METHOD__ . ': ' . $e->getMessage() . "\n" );
+
+                               return ExifBitmapHandler::BROKEN_FILE;
+                       }
+               } else {
+                       return '';
+               }
+       }
+
+       public function isExpensiveToThumbnail( $file ) {
+               return $file->getSize() > static::EXPENSIVE_SIZE_LIMIT;
+       }
+}
diff --git a/includes/media/WebP.php b/includes/media/WebP.php
deleted file mode 100644 (file)
index 295a978..0000000
+++ /dev/null
@@ -1,309 +0,0 @@
-<?php
-/**
- * Handler for Google's WebP format <https://developers.google.com/speed/webp/>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Media
- */
-
-/**
- * Handler for Google's WebP format <https://developers.google.com/speed/webp/>
- *
- * @ingroup Media
- */
-class WebPHandler extends BitmapHandler {
-       const BROKEN_FILE = '0'; // value to store in img_metadata if error extracting metadata.
-       /**
-        * @var int Minimum chunk header size to be able to read all header types
-        */
-       const MINIMUM_CHUNK_HEADER_LENGTH = 18;
-       /**
-        * @var int version of the metadata stored in db records
-        */
-       const _MW_WEBP_VERSION = 1;
-
-       const VP8X_ICC = 32;
-       const VP8X_ALPHA = 16;
-       const VP8X_EXIF = 8;
-       const VP8X_XMP = 4;
-       const VP8X_ANIM = 2;
-
-       public function getMetadata( $image, $filename ) {
-               $parsedWebPData = self::extractMetadata( $filename );
-               if ( !$parsedWebPData ) {
-                       return self::BROKEN_FILE;
-               }
-
-               $parsedWebPData['metadata']['_MW_WEBP_VERSION'] = self::_MW_WEBP_VERSION;
-               return serialize( $parsedWebPData );
-       }
-
-       public function getMetadataType( $image ) {
-               return 'parsed-webp';
-       }
-
-       public function isMetadataValid( $image, $metadata ) {
-               if ( $metadata === self::BROKEN_FILE ) {
-                               // Do not repetitivly regenerate metadata on broken file.
-                               return self::METADATA_GOOD;
-               }
-
-               Wikimedia\suppressWarnings();
-               $data = unserialize( $metadata );
-               Wikimedia\restoreWarnings();
-
-               if ( !$data || !is_array( $data ) ) {
-                               wfDebug( __METHOD__ . " invalid WebP metadata\n" );
-
-                               return self::METADATA_BAD;
-               }
-
-               if ( !isset( $data['metadata']['_MW_WEBP_VERSION'] )
-                               || $data['metadata']['_MW_WEBP_VERSION'] != self::_MW_WEBP_VERSION
-               ) {
-                               wfDebug( __METHOD__ . " old but compatible WebP metadata\n" );
-
-                               return self::METADATA_COMPATIBLE;
-               }
-               return self::METADATA_GOOD;
-       }
-
-       /**
-        * Extracts the image size and WebP type from a file
-        *
-        * @param string $filename
-        * @return array|bool Header data array with entries 'compression', 'width' and 'height',
-        * where 'compression' can be 'lossy', 'lossless', 'animated' or 'unknown'. False if
-        * file is not a valid WebP file.
-        */
-       public static function extractMetadata( $filename ) {
-               wfDebugLog( 'WebP', __METHOD__ . ": Extracting metadata from $filename\n" );
-
-               $info = RiffExtractor::findChunksFromFile( $filename, 100 );
-               if ( $info === false ) {
-                       wfDebugLog( 'WebP', __METHOD__ . ": Not a valid RIFF file\n" );
-                       return false;
-               }
-
-               if ( $info['fourCC'] != 'WEBP' ) {
-                       wfDebugLog( 'WebP', __METHOD__ . ': FourCC was not WEBP: ' .
-                               bin2hex( $info['fourCC'] ) . " \n" );
-                       return false;
-               }
-
-               $metadata = self::extractMetadataFromChunks( $info['chunks'], $filename );
-               if ( !$metadata ) {
-                       wfDebugLog( 'WebP', __METHOD__ . ": No VP8 chunks found\n" );
-                       return false;
-               }
-
-               return $metadata;
-       }
-
-       /**
-        * Extracts the image size and WebP type from a file based on the chunk list
-        * @param array $chunks Chunks as extracted by RiffExtractor
-        * @param string $filename
-        * @return array Header data array with entries 'compression', 'width' and 'height', where
-        * 'compression' can be 'lossy', 'lossless', 'animated' or 'unknown'
-        */
-       public static function extractMetadataFromChunks( $chunks, $filename ) {
-               $vp8Info = [];
-
-               foreach ( $chunks as $chunk ) {
-                       if ( !in_array( $chunk['fourCC'], [ 'VP8 ', 'VP8L', 'VP8X' ] ) ) {
-                               // Not a chunk containing interesting metadata
-                               continue;
-                       }
-
-                       $chunkHeader = file_get_contents( $filename, false, null,
-                               $chunk['start'], self::MINIMUM_CHUNK_HEADER_LENGTH );
-                       wfDebugLog( 'WebP', __METHOD__ . ": {$chunk['fourCC']}\n" );
-
-                       switch ( $chunk['fourCC'] ) {
-                               case 'VP8 ':
-                                       return array_merge( $vp8Info,
-                                               self::decodeLossyChunkHeader( $chunkHeader ) );
-                               case 'VP8L':
-                                       return array_merge( $vp8Info,
-                                               self::decodeLosslessChunkHeader( $chunkHeader ) );
-                               case 'VP8X':
-                                       $vp8Info = array_merge( $vp8Info,
-                                               self::decodeExtendedChunkHeader( $chunkHeader ) );
-                                       // Continue looking for other chunks to improve the metadata
-                                       break;
-                       }
-               }
-               return $vp8Info;
-       }
-
-       /**
-        * Decodes a lossy chunk header
-        * @param string $header First few bytes of the header, expected to be at least 18 bytes long
-        * @return bool|array See WebPHandler::decodeHeader
-        */
-       protected static function decodeLossyChunkHeader( $header ) {
-               // Bytes 0-3 are 'VP8 '
-               // Bytes 4-7 are the VP8 stream size
-               // Bytes 8-10 are the frame tag
-               // Bytes 11-13 are 0x9D 0x01 0x2A called the sync code
-               $syncCode = substr( $header, 11, 3 );
-               if ( $syncCode != "\x9D\x01\x2A" ) {
-                       wfDebugLog( 'WebP', __METHOD__ . ': Invalid sync code: ' .
-                               bin2hex( $syncCode ) . "\n" );
-                       return [];
-               }
-               // Bytes 14-17 are image size
-               $imageSize = unpack( 'v2', substr( $header, 14, 4 ) );
-               // Image sizes are 14 bit, 2 MSB are scaling parameters which are ignored here
-               return [
-                       'compression' => 'lossy',
-                       'width' => $imageSize[1] & 0x3FFF,
-                       'height' => $imageSize[2] & 0x3FFF
-               ];
-       }
-
-       /**
-        * Decodes a lossless chunk header
-        * @param string $header First few bytes of the header, expected to be at least 13 bytes long
-        * @return bool|array See WebPHandler::decodeHeader
-        */
-       public static function decodeLosslessChunkHeader( $header ) {
-               // Bytes 0-3 are 'VP8L'
-               // Bytes 4-7 are chunk stream size
-               // Byte 8 is 0x2F called the signature
-               if ( $header{8} != "\x2F" ) {
-                       wfDebugLog( 'WebP', __METHOD__ . ': Invalid signature: ' .
-                               bin2hex( $header{8} ) . "\n" );
-                       return [];
-               }
-               // Bytes 9-12 contain the image size
-               // Bits 0-13 are width-1; bits 15-27 are height-1
-               $imageSize = unpack( 'C4', substr( $header, 9, 4 ) );
-               return [
-                               'compression' => 'lossless',
-                               'width' => ( $imageSize[1] | ( ( $imageSize[2] & 0x3F ) << 8 ) ) + 1,
-                               'height' => ( ( ( $imageSize[2] & 0xC0 ) >> 6 ) |
-                                               ( $imageSize[3] << 2 ) | ( ( $imageSize[4] & 0x03 ) << 10 ) ) + 1
-               ];
-       }
-
-       /**
-        * Decodes an extended chunk header
-        * @param string $header First few bytes of the header, expected to be at least 18 bytes long
-        * @return bool|array See WebPHandler::decodeHeader
-        */
-       public static function decodeExtendedChunkHeader( $header ) {
-               // Bytes 0-3 are 'VP8X'
-               // Byte 4-7 are chunk length
-               // Byte 8-11 are a flag bytes
-               $flags = unpack( 'c', substr( $header, 8, 1 ) );
-
-               // Byte 12-17 are image size (24 bits)
-               $width = unpack( 'V', substr( $header, 12, 3 ) . "\x00" );
-               $height = unpack( 'V', substr( $header, 15, 3 ) . "\x00" );
-
-               return [
-                       'compression' => 'unknown',
-                       'animated' => ( $flags[1] & self::VP8X_ANIM ) == self::VP8X_ANIM,
-                       'transparency' => ( $flags[1] & self::VP8X_ALPHA ) == self::VP8X_ALPHA,
-                       'width' => ( $width[1] & 0xFFFFFF ) + 1,
-                       'height' => ( $height[1] & 0xFFFFFF ) + 1
-               ];
-       }
-
-       public function getImageSize( $file, $path, $metadata = false ) {
-               if ( $file === null ) {
-                       $metadata = self::getMetadata( $file, $path );
-               }
-               if ( $metadata === false && $file instanceof File ) {
-                       $metadata = $file->getMetadata();
-               }
-
-               Wikimedia\suppressWarnings();
-               $metadata = unserialize( $metadata );
-               Wikimedia\restoreWarnings();
-
-               if ( $metadata == false ) {
-                       return false;
-               }
-               return [ $metadata['width'], $metadata['height'] ];
-       }
-
-       /**
-        * @param File $file
-        * @return bool True, not all browsers support WebP
-        */
-       public function mustRender( $file ) {
-               return true;
-       }
-
-       /**
-        * @param File $file
-        * @return bool False if we are unable to render this image
-        */
-       public function canRender( $file ) {
-               if ( self::isAnimatedImage( $file ) ) {
-                       return false;
-               }
-               return true;
-       }
-
-       /**
-        * @param File $image
-        * @return bool
-        */
-       public function isAnimatedImage( $image ) {
-               $ser = $image->getMetadata();
-               if ( $ser ) {
-                       $metadata = unserialize( $ser );
-                       if ( isset( $metadata['animated'] ) && $metadata['animated'] === true ) {
-                               return true;
-                       }
-               }
-
-               return false;
-       }
-
-       public function canAnimateThumbnail( $file ) {
-               return false;
-       }
-
-       /**
-        * Render files as PNG
-        *
-        * @param string $ext
-        * @param string $mime
-        * @param array|null $params
-        * @return array
-        */
-       public function getThumbType( $ext, $mime, $params = null ) {
-               return [ 'png', 'image/png' ];
-       }
-
-       /**
-        * Must use "im" for XCF
-        *
-        * @param string $dstPath
-        * @param bool $checkDstPath
-        * @return string
-        */
-       protected function getScalerType( $dstPath, $checkDstPath = true ) {
-               return 'im';
-       }
-}
diff --git a/includes/media/WebPHandler.php b/includes/media/WebPHandler.php
new file mode 100644 (file)
index 0000000..295a978
--- /dev/null
@@ -0,0 +1,309 @@
+<?php
+/**
+ * Handler for Google's WebP format <https://developers.google.com/speed/webp/>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Media
+ */
+
+/**
+ * Handler for Google's WebP format <https://developers.google.com/speed/webp/>
+ *
+ * @ingroup Media
+ */
+class WebPHandler extends BitmapHandler {
+       const BROKEN_FILE = '0'; // value to store in img_metadata if error extracting metadata.
+       /**
+        * @var int Minimum chunk header size to be able to read all header types
+        */
+       const MINIMUM_CHUNK_HEADER_LENGTH = 18;
+       /**
+        * @var int version of the metadata stored in db records
+        */
+       const _MW_WEBP_VERSION = 1;
+
+       const VP8X_ICC = 32;
+       const VP8X_ALPHA = 16;
+       const VP8X_EXIF = 8;
+       const VP8X_XMP = 4;
+       const VP8X_ANIM = 2;
+
+       public function getMetadata( $image, $filename ) {
+               $parsedWebPData = self::extractMetadata( $filename );
+               if ( !$parsedWebPData ) {
+                       return self::BROKEN_FILE;
+               }
+
+               $parsedWebPData['metadata']['_MW_WEBP_VERSION'] = self::_MW_WEBP_VERSION;
+               return serialize( $parsedWebPData );
+       }
+
+       public function getMetadataType( $image ) {
+               return 'parsed-webp';
+       }
+
+       public function isMetadataValid( $image, $metadata ) {
+               if ( $metadata === self::BROKEN_FILE ) {
+                               // Do not repetitivly regenerate metadata on broken file.
+                               return self::METADATA_GOOD;
+               }
+
+               Wikimedia\suppressWarnings();
+               $data = unserialize( $metadata );
+               Wikimedia\restoreWarnings();
+
+               if ( !$data || !is_array( $data ) ) {
+                               wfDebug( __METHOD__ . " invalid WebP metadata\n" );
+
+                               return self::METADATA_BAD;
+               }
+
+               if ( !isset( $data['metadata']['_MW_WEBP_VERSION'] )
+                               || $data['metadata']['_MW_WEBP_VERSION'] != self::_MW_WEBP_VERSION
+               ) {
+                               wfDebug( __METHOD__ . " old but compatible WebP metadata\n" );
+
+                               return self::METADATA_COMPATIBLE;
+               }
+               return self::METADATA_GOOD;
+       }
+
+       /**
+        * Extracts the image size and WebP type from a file
+        *
+        * @param string $filename
+        * @return array|bool Header data array with entries 'compression', 'width' and 'height',
+        * where 'compression' can be 'lossy', 'lossless', 'animated' or 'unknown'. False if
+        * file is not a valid WebP file.
+        */
+       public static function extractMetadata( $filename ) {
+               wfDebugLog( 'WebP', __METHOD__ . ": Extracting metadata from $filename\n" );
+
+               $info = RiffExtractor::findChunksFromFile( $filename, 100 );
+               if ( $info === false ) {
+                       wfDebugLog( 'WebP', __METHOD__ . ": Not a valid RIFF file\n" );
+                       return false;
+               }
+
+               if ( $info['fourCC'] != 'WEBP' ) {
+                       wfDebugLog( 'WebP', __METHOD__ . ': FourCC was not WEBP: ' .
+                               bin2hex( $info['fourCC'] ) . " \n" );
+                       return false;
+               }
+
+               $metadata = self::extractMetadataFromChunks( $info['chunks'], $filename );
+               if ( !$metadata ) {
+                       wfDebugLog( 'WebP', __METHOD__ . ": No VP8 chunks found\n" );
+                       return false;
+               }
+
+               return $metadata;
+       }
+
+       /**
+        * Extracts the image size and WebP type from a file based on the chunk list
+        * @param array $chunks Chunks as extracted by RiffExtractor
+        * @param string $filename
+        * @return array Header data array with entries 'compression', 'width' and 'height', where
+        * 'compression' can be 'lossy', 'lossless', 'animated' or 'unknown'
+        */
+       public static function extractMetadataFromChunks( $chunks, $filename ) {
+               $vp8Info = [];
+
+               foreach ( $chunks as $chunk ) {
+                       if ( !in_array( $chunk['fourCC'], [ 'VP8 ', 'VP8L', 'VP8X' ] ) ) {
+                               // Not a chunk containing interesting metadata
+                               continue;
+                       }
+
+                       $chunkHeader = file_get_contents( $filename, false, null,
+                               $chunk['start'], self::MINIMUM_CHUNK_HEADER_LENGTH );
+                       wfDebugLog( 'WebP', __METHOD__ . ": {$chunk['fourCC']}\n" );
+
+                       switch ( $chunk['fourCC'] ) {
+                               case 'VP8 ':
+                                       return array_merge( $vp8Info,
+                                               self::decodeLossyChunkHeader( $chunkHeader ) );
+                               case 'VP8L':
+                                       return array_merge( $vp8Info,
+                                               self::decodeLosslessChunkHeader( $chunkHeader ) );
+                               case 'VP8X':
+                                       $vp8Info = array_merge( $vp8Info,
+                                               self::decodeExtendedChunkHeader( $chunkHeader ) );
+                                       // Continue looking for other chunks to improve the metadata
+                                       break;
+                       }
+               }
+               return $vp8Info;
+       }
+
+       /**
+        * Decodes a lossy chunk header
+        * @param string $header First few bytes of the header, expected to be at least 18 bytes long
+        * @return bool|array See WebPHandler::decodeHeader
+        */
+       protected static function decodeLossyChunkHeader( $header ) {
+               // Bytes 0-3 are 'VP8 '
+               // Bytes 4-7 are the VP8 stream size
+               // Bytes 8-10 are the frame tag
+               // Bytes 11-13 are 0x9D 0x01 0x2A called the sync code
+               $syncCode = substr( $header, 11, 3 );
+               if ( $syncCode != "\x9D\x01\x2A" ) {
+                       wfDebugLog( 'WebP', __METHOD__ . ': Invalid sync code: ' .
+                               bin2hex( $syncCode ) . "\n" );
+                       return [];
+               }
+               // Bytes 14-17 are image size
+               $imageSize = unpack( 'v2', substr( $header, 14, 4 ) );
+               // Image sizes are 14 bit, 2 MSB are scaling parameters which are ignored here
+               return [
+                       'compression' => 'lossy',
+                       'width' => $imageSize[1] & 0x3FFF,
+                       'height' => $imageSize[2] & 0x3FFF
+               ];
+       }
+
+       /**
+        * Decodes a lossless chunk header
+        * @param string $header First few bytes of the header, expected to be at least 13 bytes long
+        * @return bool|array See WebPHandler::decodeHeader
+        */
+       public static function decodeLosslessChunkHeader( $header ) {
+               // Bytes 0-3 are 'VP8L'
+               // Bytes 4-7 are chunk stream size
+               // Byte 8 is 0x2F called the signature
+               if ( $header{8} != "\x2F" ) {
+                       wfDebugLog( 'WebP', __METHOD__ . ': Invalid signature: ' .
+                               bin2hex( $header{8} ) . "\n" );
+                       return [];
+               }
+               // Bytes 9-12 contain the image size
+               // Bits 0-13 are width-1; bits 15-27 are height-1
+               $imageSize = unpack( 'C4', substr( $header, 9, 4 ) );
+               return [
+                               'compression' => 'lossless',
+                               'width' => ( $imageSize[1] | ( ( $imageSize[2] & 0x3F ) << 8 ) ) + 1,
+                               'height' => ( ( ( $imageSize[2] & 0xC0 ) >> 6 ) |
+                                               ( $imageSize[3] << 2 ) | ( ( $imageSize[4] & 0x03 ) << 10 ) ) + 1
+               ];
+       }
+
+       /**
+        * Decodes an extended chunk header
+        * @param string $header First few bytes of the header, expected to be at least 18 bytes long
+        * @return bool|array See WebPHandler::decodeHeader
+        */
+       public static function decodeExtendedChunkHeader( $header ) {
+               // Bytes 0-3 are 'VP8X'
+               // Byte 4-7 are chunk length
+               // Byte 8-11 are a flag bytes
+               $flags = unpack( 'c', substr( $header, 8, 1 ) );
+
+               // Byte 12-17 are image size (24 bits)
+               $width = unpack( 'V', substr( $header, 12, 3 ) . "\x00" );
+               $height = unpack( 'V', substr( $header, 15, 3 ) . "\x00" );
+
+               return [
+                       'compression' => 'unknown',
+                       'animated' => ( $flags[1] & self::VP8X_ANIM ) == self::VP8X_ANIM,
+                       'transparency' => ( $flags[1] & self::VP8X_ALPHA ) == self::VP8X_ALPHA,
+                       'width' => ( $width[1] & 0xFFFFFF ) + 1,
+                       'height' => ( $height[1] & 0xFFFFFF ) + 1
+               ];
+       }
+
+       public function getImageSize( $file, $path, $metadata = false ) {
+               if ( $file === null ) {
+                       $metadata = self::getMetadata( $file, $path );
+               }
+               if ( $metadata === false && $file instanceof File ) {
+                       $metadata = $file->getMetadata();
+               }
+
+               Wikimedia\suppressWarnings();
+               $metadata = unserialize( $metadata );
+               Wikimedia\restoreWarnings();
+
+               if ( $metadata == false ) {
+                       return false;
+               }
+               return [ $metadata['width'], $metadata['height'] ];
+       }
+
+       /**
+        * @param File $file
+        * @return bool True, not all browsers support WebP
+        */
+       public function mustRender( $file ) {
+               return true;
+       }
+
+       /**
+        * @param File $file
+        * @return bool False if we are unable to render this image
+        */
+       public function canRender( $file ) {
+               if ( self::isAnimatedImage( $file ) ) {
+                       return false;
+               }
+               return true;
+       }
+
+       /**
+        * @param File $image
+        * @return bool
+        */
+       public function isAnimatedImage( $image ) {
+               $ser = $image->getMetadata();
+               if ( $ser ) {
+                       $metadata = unserialize( $ser );
+                       if ( isset( $metadata['animated'] ) && $metadata['animated'] === true ) {
+                               return true;
+                       }
+               }
+
+               return false;
+       }
+
+       public function canAnimateThumbnail( $file ) {
+               return false;
+       }
+
+       /**
+        * Render files as PNG
+        *
+        * @param string $ext
+        * @param string $mime
+        * @param array|null $params
+        * @return array
+        */
+       public function getThumbType( $ext, $mime, $params = null ) {
+               return [ 'png', 'image/png' ];
+       }
+
+       /**
+        * Must use "im" for XCF
+        *
+        * @param string $dstPath
+        * @param bool $checkDstPath
+        * @return string
+        */
+       protected function getScalerType( $dstPath, $checkDstPath = true ) {
+               return 'im';
+       }
+}
index a3717b2..245982e 100644 (file)
@@ -2,6 +2,8 @@
 
 namespace MediaWiki\Tidy;
 
+use MWException;
+
 abstract class RaggettBase extends TidyDriverBase {
        /**
         * Generic interface for wrapping and unwrapping HTML for Dave Raggett's tidy.
index 3335e59..b8186d6 100644 (file)
@@ -4559,7 +4559,7 @@ class User implements IDBAccessObject, UserIdentity {
         * site.
         *
         * @param string $val Input value to compare
-        * @param string $salt Optional function-specific data for hashing
+        * @param string|array $salt Optional function-specific data for hashing
         * @param WebRequest|null $request Object to use or null to use $wgRequest
         * @param int $maxage Fail tokens older than this, in seconds
         * @return bool Whether the token matches
@@ -4573,7 +4573,7 @@ class User implements IDBAccessObject, UserIdentity {
         * ignoring the suffix.
         *
         * @param string $val Input value to compare
-        * @param string $salt Optional function-specific data for hashing
+        * @param string|array $salt Optional function-specific data for hashing
         * @param WebRequest|null $request Object to use or null to use $wgRequest
         * @param int $maxage Fail tokens older than this, in seconds
         * @return bool Whether the token matches
index a450ae5..30d1cbb 100644 (file)
@@ -19,6 +19,7 @@
  * @ingroup Watchlist
  */
 use MediaWiki\Linker\LinkTarget;
+use Wikimedia\Rdbms\DBUnexpectedError;
 
 /**
  * @author Addshore
index 65c7dea..87e1e63 100644 (file)
        "prefs-dateformat": "Formatu de data",
        "prefs-timeoffset": "Diferencia horaria",
        "prefs-advancedediting": "Opciones xenerales",
+       "prefs-developertools": "Ferramientes pa desendolcadores",
        "prefs-editor": "Editor",
        "prefs-preview": "Vista previa",
        "prefs-advancedrc": "Opciones avanzaes",
index 6d19efd..ce20657 100644 (file)
        "savechanges": "बदलाव सहेजीं",
        "publishpage": "पन्ना प्रकाशित करीं",
        "publishchanges": "बदलाव प्रकाशित करीं",
+       "savearticle-start": "पन्ना सहेजीं...",
+       "savechanges-start": "बदलाव सहेजीं...",
+       "publishpage-start": "पन्ना प्रकाशित करीं...",
+       "publishchanges-start": "बदलाव प्रकाशित करीं...",
        "preview": "झलक",
        "showpreview": "झलक देखीं",
        "showdiff": "बदलाव देखीं",
        "noarticletext-nopermission": "ए पन्ना मे अभी कौनों सामग्री नइखे।\nरउआँ दुसरा पन्ना में [[Special:Search/{{PAGENAME}}|ए टाइटिल के खोज]] कर सकत बानीं,\nया <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} या संबंधित लॉग खोज सकत बानी]</span>, बाकी रउआ के ई पन्ना बनावे के परमीशन नइखे।",
        "missing-revision": "\"{{FULLPAGENAME}}\" पन्ना के संशोधन #$1 उपलब्ध नइखे।\n\nसाधारण रुप से इ एगो हटावल गइल पन्ना के पुरान लिंक पर क्लिक कइला से होखेला।\nअधिक जानकारी खातिर आप [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} हटावे के लॉग] देख सकत बानी।",
        "userpage-userdoesnotexist": "सदस्य खाता \"$1\" पंजीकृत नइखे।\nकृपया जाँच लीं कि आप इ पन्ना संपादित अथवा निर्मित करे के चाहत बानी कि ना।",
-       "userpage-userdoesnotexist-view": "सदसà¥\8dय à¤\96ाता \"$1\" à¤ªà¤\82à¤\9cà¥\80à¤\95à¥\83त à¤¨à¤\88à¤\96à¥\87 à¤­à¤\88ल।",
+       "userpage-userdoesnotexist-view": "पà¥\8dरयà¥\8bà¤\97à¤\95रà¥\8dता à¤\96ाता \"$1\" à¤°à¤\9cिसà¥\8dà¤\9fरà¥\8dड à¤¨à¤\87à¤\96à¥\87 à¤­à¤\87ल।",
        "blocked-notice-logextract": "ई प्रयोगकर्ता के ई समय निष्क्रीय कर दिहल गईल बा।\nनविनतम नष्ट लौग प्रविष्टी उद्धरण खातिर निचे दिहल बा:",
        "clearyourcache": "<strong>नोट:</strong> सहेजे के बाद, बदलाव देखे खातिर आपके अपने ब्राउजर के कैशे खाली करे के पड़ सकत बा।\n* <strong>फायरफॉक्स / सफारी:</strong><em>शिफ्ट</em> दबा के <em>रीलोड</em> पर क्लिक करीं, या फिर <em>Ctrl-F5</em> या <em>Ctrl-R</em> दबाईं (मैक पर <em>⌘-R</em>)\n* <strong>गूगल क्रोम:</strong> <em>Ctrl-Shift-R</em> दबाईं (मैक पर <em>⌘-Shift-R</em>)\n* <strong>इंटरनेट एक्स्प्लोरर:</strong> <em>Ctrl</em> दबा के  <em>Refresh</em> पर क्लिक करीं, या <em>Ctrl-F5</em> दबईं\n* <strong>ओपेरा:</strong> <em>Menu → Settings</em> में जाईं (मैक में <em>Opera → Preferences</em>) आ एकरे बाद <em>Privacy & security → Clear browsing data → Cached images and files</em> क्लिक करीं।",
        "usercssyoucanpreview": "<strong>टिप:</strong> आपन नया CSS के टेस्ट करे खातिर सहेजे से पहिले \"{{int:showpreview}}\" बटन के प्रयोग करीं।",
+       "userjsonyoucanpreview": "<strong>टिप:</strong> आपन नया JSON के टेस्ट करे खातिर सहेजे से पहिले \"{{int:showpreview}}\" बटन के प्रयोग करीं।",
        "userjsyoucanpreview": "<strong>टिप:</strong> आपन नया जावास्क्रिप्ट के टेस्ट करे खातिर सहेजे से पहिले \"{{int:showpreview}}\" बटन के प्रयोग करीं।",
        "usercsspreview": "<strong>याद रहे की आप अपनी सदस्य CSS के खाली नमूना भर देखत बानी।\nई अबहिन ले सहेजल ना गइल बाटे।</strong>",
+       "userjsonpreview": "<strong>याद रहे की आप अपने JSON config के खाली टेस्ट करत बानी/नमूना देखत बानी।\nई अबहिन सहेजल ना गइल बाटे।</strong>",
        "userjspreview": "<strong>याद रहे की आप अपनी सदस्य जावास्क्रिप्ट के खाली टेस्ट करत बानी/नमूना देखत बानी।\nई अबहिन सहेजल ना गइल बाटे।</strong>",
        "sitecsspreview": "<strong>याद रहे की आप ए CSS क खाली नमूना देखत बानी।\nई अबहिन ले सहेजल ना गइल बा!</strong>",
+       "sitejsonpreview": "<strong>याद रहे की आप ए JSON config क खाली नमूना देखत बानी।\nई अबहिन ले सहेजल ना गइल बा!</strong>",
        "sitejspreview": "<strong>याद रहे की आप ए जावास्क्रिप्ट कोड क खाली नमूना देखत बानी।\nई अबहिन ले सहेजल ना गइल बा!</strong>",
-       "userinvalidconfigtitle": "<strong>चेतावनी:</strong> कौनों skin \"$1\"नइखे।\nCustom .css आ .js पन्ना सभ छोटका अक्षर में टाइटिल इस्तेमाल करे लें जइसे की, {{ns:user}}:Foo/vector.css ना की {{ns:user}}:Foo/Vector.css।",
+       "userinvalidconfigtitle": "<strong>चेतावनी:</strong> कौनों skin \"$1\"नइखे।\nCustom .css, ,json, आ .js पन्ना सभ छोटका अक्षर में टाइटिल इस्तेमाल करे लें जइसे की, {{ns:user}}:Foo/vector.css ना की {{ns:user}}:Foo/Vector.css",
        "updated": "(अपडेट करल गईल)",
        "note": "'''सूचना:'''",
-       "previewnote": "'''याद रखीं, इ एगो झलक मात्र हो।'''\nराउर बदलाव अभी तक सुरक्षित नईखे करल गईल!",
+       "previewnote": "<strong>याद रखीं, इ एगो झलक भर हवे।</strong>\nराउर बदलाव अभिन सहेजल ना गइल बा!",
        "continue-editing": "संपादन क्षेत्र में जाईं",
        "previewconflict": "ई नमूना ई देखावत बा की अगर रउआँ ए संपादन बक्सा में मौजूद पाठ के सहेजब त ऊ कइसन देखाई पड़ी।",
        "session_fail_preview": "माफ करीं! एह सत्र के आँकड़ा के गायब हो गइला के कारण आपके संपादन के प्रॉसेस करे में हमनी के असमर्थ बानी जा।\nहो सकेला आप लॉगआउट हो गइल होखीं।\n<strong>जाँच लेईं कि आप अभी लॉगिन बानी आ दुबारा कोसिस करीं</strong>।\nअगर तबो काम ना होखे तब [[Special:UserLogout|लॉगआउट कइके]] आ दोबारा लॉग इन कइ के कोसिस करी, आ जाँच करीं कि आपके ब्राउजर एह साइट से कुकीज सभ के मंजूर करत बा।",
        "longpageerror": "<strong>खराबी: आप जवन पाठ लिख के दिहले बानी ऊ {{PLURAL:$1|एक किलोबाइट|$1 किलोबाइट्स}} के बाटे, जेवन अधिकतम सीमा {{PLURAL:$2|एक किलोबाइट|$2 किलोबाइट्स}} से ढेर बा।</strong>\nई सहेजल ना जा सकेला।",
        "readonlywarning": "<strong>चेतावनी: एह समय मरम्मत खातिर डेटाबेस लॉक कइल गइल बा, एही कारन आप तुरंते एही समय आपन संपादन ना सहेज पाइब।</strong>\nरउआँ अपनी पाठ (टेक्स्ट) के कौनों पाठ फाइल (टेक्स्ट फाइल) में बाद खातिर सहेज के रख लीं।\n\nजे सिस्टम प्रबंधक एकरा के लॉक कइले बा ऊ नीचे लिखल कारण दिहले बा: $1",
        "protectedpagewarning": "<strong>चेतावनी: ई पन्ना सुरक्षित कइल गइल बा जेवना से कि एकरा के खाली प्रबंधक (Admin) विशेषाधिकार वाला सदस्य लोग संपादित क सकत बा।</strong>\nप्रसंग बूझे खातिर सबसे नया लॉग एंट्री नीचे दिहल जात बा:",
-       "semiprotectedpagewarning": "<strong>नà¥\8bà¤\9f:</strong> à¤\88 à¤ªà¤¨à¥\8dना à¤¸à¥\81रà¤\95à¥\8dषित à¤\95à¤\87ल à¤\97à¤\87ल à¤¬à¤¾ à¤\95ि à¤\8fà¤\95रा à¤\95à¥\87 à¤\96ालà¥\80 à¤°à¤\9cिसà¥\8dà¤\9fरà¥\8dड à¤¸à¤¦à¤¸à¥\8dय à¤²à¥\8bà¤\97 à¤¸à¤\82पादित à¤\95 à¤¸à¤\95त à¤¬à¤¾à¥¤\nसभसà¥\87 à¤¨à¤¯à¤¾ à¤²à¥\89à¤\97 à¤\8fà¤\82à¤\9fà¥\8dरà¥\80 à¤¨à¥\80à¤\9aà¥\87 à¤ªà¥\8dरसà¤\82à¤\97 à¤¬à¤¤à¤¾à¤µà¥\87 à¤\96ातिर दिहल जात बा:",
+       "semiprotectedpagewarning": "<strong>नà¥\8bà¤\9f:</strong> à¤\88 à¤ªà¤¨à¥\8dना à¤¸à¥\81रà¤\95à¥\8dषित à¤\95à¤\87ल à¤\97à¤\87ल à¤¬à¤¾ à¤\95ि à¤\8fà¤\95रा à¤\95à¥\87 à¤\96ालà¥\80 à¤\91à¤\9fà¥\8bà¤\95नà¥\8dफरà¥\8dम à¤ªà¥\8dरयà¥\8bà¤\97à¤\95रà¥\8dता à¤²à¥\8bà¤\97 à¤¸à¤\82पादित à¤\95 à¤¸à¤\95त à¤¬à¤¾à¥¤\nनà¥\80à¤\9aà¥\87 à¤ªà¥\8dरसà¤\82à¤\97 à¤¬à¤¤à¤¾à¤µà¥\87 à¤\96ातिर à¤¸à¤­à¤¸à¥\87 à¤¨à¤¯à¤¾ à¤²à¥\89à¤\97 à¤\8fà¤\82à¤\9fà¥\8dरà¥\80 दिहल जात बा:",
        "cascadeprotectedwarning": "<strong>चेतावनी:</strong> ई पन्ना सुरक्षित बा जवना से कि खाली [[Special:ListGroupRights|बिसेस अधिकार]] वाला प्रयोगकर्ता लोग संपादन क सकेला काहें से की ई नीचे दिहल गइल छतनार-सुरक्षा वाला {{PLURAL:$1|पन्ना|पन्ना सभ}} में ट्रांसक्लूड हो रहल बाटे:",
        "titleprotectedwarning": "<strong>चेतावनी: ई पन्ना सुरक्षित कइल गइल बा की एकरा के बनावे खातिर [[Special:ListGroupRights|विशेष अधिकार]] होखल जरूरी बा।</strong>\nसंदर्भ खातिर नीचे सबसे नया लॉग एंट्री दिहल जात बा:",
        "templatesused": "ए पन्ना पर इस्तेमाल {{PLURAL:$1|टेम्पलेट|टेम्पलेट कुल}}:",
        "nextn-title": "अगिला $1 {{PLURAL:$1|परिणाम}}",
        "shown-title": "प्रति पन्ना $1 {{PLURAL:$1|परिणाम}} देखाईं",
        "viewprevnext": "देखीं ($1 {{int:pipe-separator}} $2) ($3)",
-       "searchmenu-exists": "'''इ विकि पर ''[[:$1]]'' नाम से एगो पन्ना उपलब्ध बा'''",
+       "searchmenu-exists": "<strong>एह विकि पर \"[[:$1]]\" नाँव से एगो पन्ना मौजूद बा।</strong> {{PLURAL:$2|0=|खोज में मिलल अउरियो रिजल्ट सभ देखीं}}",
        "searchmenu-new": "<strong> ए विकि पर \"[[:$1]]\" नाँव के पन्ना बनाईं !</strong> {{PLURAL:$2|0=|अपनी खोज से मिलल पन्ना भी देखीं|खोज के परिणाम भी देखीं।}}",
        "searchprofile-articles": "सामग्री पन्ना",
        "searchprofile-images": "मल्टीमीडिया",
        "licenses-edit": "लाइसेंस बिकल्प संपादन",
        "license-nopreview": "(नमूना देखल उपलब्ध नइखे)",
        "imgfile": "फाइल",
-       "listfiles": "फाà¤\87ल à¤¸à¥\82à¤\9aà¥\80",
+       "listfiles": "फाà¤\87ल à¤²à¤¿à¤¸à¥\8dà¤\9f",
        "listfiles_thumb": "चिप्पी",
        "listfiles_date": "तिथि",
        "listfiles_name": "नाँव",
        "filehist-thumbtext": "$1 ले के संस्करण के चिप्पी रूप।",
        "filehist-nothumb": "बिन थम्बनेल",
        "filehist-user": "प्रयोगकर्ता",
-       "filehist-dimensions": "à¤\86याम",
+       "filehist-dimensions": "डाà¤\87मà¥\87à¤\82शन",
        "filehist-filesize": "फाईल के आकार",
        "filehist-comment": "टिप्पणी",
        "imagelinks": "फाइल के उपयोग",
        "sharedupload": "इ फाईल $1 से बा आ दुसर परियोजना में प्रयोग करल जा सकत बा।",
        "sharedupload-desc-there": "इ फाईल $1 से बा आ दुसर परियोजना में प्रयोग करल जा सकत बा। अधिक जानकारी खातिर कृपया [$2 फाईल विवरण पन्ना] देखीं।",
        "sharedupload-desc-here": "ई फाइल $1 से बा आ अउरी प्रोजेक्ट भी एकर इस्तेमाल कर सकत बाड़ें। \nएकर विवरण [$2 फाइल विवरण पन्ना] नीचे देखावल गइल बा।",
-       "filepage-nofile": "à¤\87 à¤¨à¤¾à¤® à¤¸à¥\87 à¤\95à¥\8cनà¥\8b à¤«à¤¾à¤\88ल à¤\89पलबà¥\8dध à¤¨à¤\88खे।",
+       "filepage-nofile": "à¤\8fह à¤¨à¤¾à¤® à¤¸à¥\87 à¤\95à¥\8cनà¥\8b à¤«à¤¾à¤\87ल à¤®à¥\8cà¤\9cà¥\82द à¤¨à¤\87खे।",
        "filepage-nofile-link": "इ नाम से कौनो फाईल उपलब्ध नईखे, लेकिन रउआ [$1 के अपलोड कर] सकत बानी।",
        "uploadnewversion-linktext": "इ फाईल के नया संस्करण लादीं।",
        "shared-repo-from": "$1 से",
        "protectedtitles": "सुरक्षित शीर्षक",
        "protectedtitlesempty": "कौनों टाइटिल के सुरक्षा एह पैमान पर नइखे।",
        "protectedtitles-submit": "शीर्षक देखीं",
-       "listusers": "सदसà¥\8dयसà¥\82à¤\9aà¥\80",
+       "listusers": "पà¥\8dरयà¥\8bà¤\97à¤\95रà¥\8dता à¤²à¤¿à¤¸à¥\8dà¤\9f",
        "listusers-editsonly": "उहे सदस्य देखाईं जे संपादन कइले होखे",
        "listusers-creationsort": "बनवले की तारीख की हिसाब से सरियाईं",
        "listusers-desc": "घटत क्रम से सरियाईं",
        "trackingcategories": "नजर रखे वाला श्रेणीसभ",
        "trackingcategories-msg": "निगरानी श्रेणी",
        "trackingcategories-name": "संदेस नाँव",
-       "emailuser": "à¤\88 प्रयोगकर्ता के ईमेल करीं",
+       "emailuser": "à¤\8fह प्रयोगकर्ता के ईमेल करीं",
        "emailusername": "प्रयोगकर्तानाँव:",
        "emailfrom": "भेजे वाला:",
        "emailto": "पावे वाला:",
        "mycontris": "योगदान",
        "anoncontribs": "योगदान",
        "contribsub2": "{{GENDER:$3|$1}} ($2) खातिर",
-       "nocontribs": "à¤\88 à¤®à¤¾à¤¨à¤¦à¤\82ड à¤¸à¥\87 à¤®à¤¿à¤²à¤¤ à¤\9cà¥\81लत कौनो बदलाव ना मिलल।",
+       "nocontribs": "à¤\8fह à¤ªà¥\88माना à¤¸à¥\87 à¤®à¥\88à¤\9a à¤\95रत कौनो बदलाव ना मिलल।",
        "uctop": "(वर्तमान)",
        "month": "महीना से (आ ओ से पहिले):",
        "year": "साल से (आ ओ से पहिले):",
        "whatlinkshere-title": "पन्ना जेवन \"$1\" से जुड़ल बा",
        "whatlinkshere-page": "पन्ना:",
        "linkshere": "<strong>[[:$1]]</strong> से नीचे दिहल पन्ना जुड़ल बाने:",
-       "nolinkshere": "'''[[:$1]]''' à¤¸à¥\87 à¤\95à¥\8cनà¥\8b à¤ªà¤¨à¥\8dना à¤¨à¤\88खे जुड़ल।",
+       "nolinkshere": "'''[[:$1]]''' à¤¸à¥\87 à¤\95à¥\8cनà¥\8b à¤ªà¤¨à¥\8dना à¤¨à¤\87खे जुड़ल।",
        "nolinkshere-ns": "चुनल गईल सन्दर्भ में '''[[:$1]]''' से कौनो पन्ना ना जुड़ेला।",
        "isredirect": "अनुप्रेषित पन्ना",
        "istemplate": "ट्रांस्क्लूजन",
        "whatlinkshere-hideimages": "$1 फाइल कड़ी",
        "whatlinkshere-filters": "छननी",
        "blockip": "{{GENDER:$1|सदस्य}} अवरोधित करीं",
-       "ipboptions": "२ घंटे:2 hours,१ दिन:1 day,३ दिन:3 days,१ हफ्ता:1 week,२ हफ्ते:2 weeks,१ महिना:1 month,३ महिने:3 months,६ महिने:6 months,१ साल:1 year,हमेशा खातिर:infinite",
+       "ipboptions": "2 घंटा:2 hours,1 दिन:1 day,3 दिन:3 days,1 हप्ता:1 week,2 हप्ता:2 weeks,1 महीना:1 month,3 महीना:3 months,6 महीना:6 months,1 साल:1 year,अनिश्चित समय खातिर:infinite",
        "blocklist": "अवरोधित प्रयोगकर्तासभ",
        "infiniteblock": "अनिश्चितकाल",
        "blocklink": "रोक लगाईं",
        "unblocklink": "ताला खोलीं",
        "change-blocklink": "ब्लॉक बदलीं",
        "contribslink": "योगदान",
-       "blocklogpage": "निषà¥\8dà¤\95à¥\8dरिय à¤\96ाता",
+       "blocklogpage": "रà¥\8bà¤\95 à¤²à¥\89à¤\97",
        "blocklogentry": "[[$1]] के ब्लॉक कइल गइल, समाप्ती के अवधि $2 $3",
        "reblock-logentry": "[[$1]] खातिर रोक सेटिंग बदलल गइल आ अब समाप्ती समय बा $2 $3",
        "block-log-flags-nocreate": "खाता निर्माण सक्षम नइखे",
        "tooltip-ca-addsection": "एगो नया खंड शुरु करीं",
        "tooltip-ca-viewsource": "ई पन्ना सुरक्षित कइल गइल बा। आप एकर स्रोत देख सकत बानी।",
        "tooltip-ca-history": "ए पन्ना के पछिला संशोधन",
-       "tooltip-ca-protect": "à¤\87 à¤ªà¤¨à¥\8dना à¤\95à¥\87 à¤¸à¤\82रà¤\95à¥\8dषित à¤\95रà¥\80à¤\82।",
+       "tooltip-ca-protect": "à¤\88 à¤ªà¤¨à¥\8dना à¤¸à¥\81रà¤\95à¥\8dषित à¤\95रà¥\80à¤\82",
        "tooltip-ca-unprotect": "ई पन्ना के सुरक्षा बदलीं।",
        "tooltip-ca-delete": "ई पन्ना मिटाईं",
        "tooltip-ca-move": "एह पन्ना के स्थानांतरण करीं",
index a8a23a2..48bdebd 100644 (file)
        "filedelete-archive-read-only": "El directori d'arxiu «$1» no té permisos d'escriptura per al servidor web.",
        "previousdiff": "← Edició anterior",
        "nextdiff": "Edició següent →",
-       "mediawarning": "'''Advertència''': Aquest fitxer podria contenir codi maliciós.\nSi l'executeu, podeu comprometre la seguretat del vostre sistema.",
+       "mediawarning": "<strong>Advertiment</strong>: aquest fitxer podria contenir codi maliciós.\nSi l’executeu, podeu comprometre la seguretat del vostre sistema.",
        "imagemaxsize": "Límit de mida d'imatges:<br />''(per a pàgines de descripció de fitxers)''",
        "thumbsize": "Mida de la miniatura:",
        "widthheight": "$1 × $2",
index 1adef2e..ac44fe2 100644 (file)
        "change-blocklink": "změnit blok",
        "contribslink": "příspěvky",
        "emaillink": "poslat e-mail",
-       "autoblocker": "Automatické zablokování kvůli tomu, že vaši IP adresu nedávno {{GENDER:$1|používal uživatel|používala uživatelka}} „[[User:$1|$1]]“.\nDůvod zablokování {{GENDER:$1|uživatele $1|uživatelky $1}}: „$2“",
+       "autoblocker": "Automatické zablokování kvůli tomu, že vaši IP adresu nedávno používal uživatel „[[User:$1|$1]]“.\nDůvod zablokování uživatele $1: „$2“",
        "blocklogpage": "Kniha zablokování",
        "blocklog-showlog": "{{GENDER:$1|Tento uživatel byl dříve blokován.|Tato uživatelka byla dříve blokována.|Tento uživatel byl dříve blokován.}}\nZde je pro přehled zobrazen výpis z knihy zablokování:",
        "blocklog-showsuppresslog": "{{GENDER:$1|Tento uživatel byl zablokován a skryt|Tato uživatelka byla zablokována a skryta}}. Zde je pro přehled zobrazen výpis záznamu utajení:",
index fcdaba0..9790e70 100644 (file)
        "rcfilters-watchlist-showupdated": "Σελίδες που έχουν υποστεί αλλαγές από την τελευταία φορά που τις επισκεφθήκατε εμφανίζονται με '''έντονους χαρακτήρες'''.",
        "rcfilters-preference-label": "Απόκρυψη της βελτιωμένης έκδοσης των Πρόσφατων Αλλαγών",
        "rcfilters-preference-help": "Αναστέλλει τον επανασχεδιασμό διεπαφής 2017 και όλα τα εργαλεία που προστέθηκαν στη συνέχεια και από τότε.",
+       "rcfilters-filter-showlinkedfrom-option-label": "<strong>Σελίδες που συνδέονται από</strong> τη επιλεγμένη σελίδα",
+       "rcfilters-filter-showlinkedto-label": "Εμφάνιση αλλαγών σε σελίδες που συνδέουν σε",
+       "rcfilters-target-page-placeholder": "Εισαγάγετε όνομα σελίδας (ή κατηγορίας)",
        "rcnotefrom": "Παρακάτω {{PLURAL:$5|είναι η αλλαγή|είναι οι αλλαγές}} από <strong>$3, $4</strong> (έως <strong>$1</strong> που εμφανίζεται).",
+       "rclistfromreset": "Επαναφορά ρύθμισης ημερομηνίας",
        "rclistfrom": "Εμφάνιση νέων αλλαγών αρχίζοντας από τις $3 στις $2",
        "rcshowhideminor": "$1 μικροεπεξεργασιών",
        "rcshowhideminor-show": "Εμφάνιση",
        "pageswithprop-legend": "Σελίδες με ιδιότητα σελίδας",
        "pageswithprop-text": "Αυτή η σελίδα ταξινομεί σελίδες που χρησιμοποιούν μια συγκεκριμένη ιδιότητα σελίδας.",
        "pageswithprop-prop": "Όνομα ιδιότητας:",
+       "pageswithprop-reverse": "Ταξινόμηση σε αντίστροφη σειρά",
+       "pageswithprop-sortbyvalue": "Ταξινόμηση ανά τιμή ιδιότητας",
        "pageswithprop-submit": "Μετάβαση",
        "pageswithprop-prophidden-long": "τιμή ιδιότητας μακρού κειμένου κρυμμένη ($1)",
        "pageswithprop-prophidden-binary": "τιμή ιδιότητας δυαδικών δεδομένων κρυμμένη ($1)",
        "log-action-filter-contentmodel-new": "Δημιουργία σελίδας με μη προεπιλεγμένο μοντέλο περιεχομένου",
        "log-action-filter-delete-delete": "Διαγραφή σελίδας",
        "log-action-filter-delete-restore": "Ξεδιαγραφή σελίδας",
+       "log-action-filter-delete-event": "Διαγραφή μητρώου",
+       "log-action-filter-delete-revision": "Διαγραφή αναθεώρησης",
        "log-action-filter-import-interwiki": "Εισαγωγή Transwiki",
        "log-action-filter-import-upload": "Εισαγωγή μέσω ανεβάσματος XML",
        "log-action-filter-managetags-create": "Δημιουργία ετικέτας",
        "log-action-filter-managetags-delete": "Διαγραφή ετικέττας",
+       "log-action-filter-managetags-activate": "Ενεργοποίηση ετικέτας",
+       "log-action-filter-managetags-deactivate": "Απενεργοποίηση ετικέτας",
+       "log-action-filter-newusers-create": "Δημιουργία από ανώνυμο χρήστη",
+       "log-action-filter-newusers-create2": "Δημιουργία από εγγεγραμμένο χρήστη",
        "log-action-filter-newusers-autocreate": "Αυτόματη δημιουργία",
        "log-action-filter-patrol-patrol": "Χειροκίνητη περιπολία",
        "log-action-filter-patrol-autopatrol": "Αυτόματη περιπολία",
        "log-action-filter-protect-move_prot": "Μετακίνηση προστασίας",
        "log-action-filter-rights-rights": "Χειροκίνητη αλλαγή",
        "log-action-filter-rights-autopromote": "Αυτόματη αλλαγή",
+       "log-action-filter-suppress-event": "Καταστολή μητρώου",
+       "log-action-filter-suppress-revision": "Καταστολή αναθεώρησης",
+       "log-action-filter-suppress-delete": "Καταστολή σελίδας",
        "log-action-filter-upload-upload": "Νέα μεταφόρτωση",
        "log-action-filter-upload-overwrite": "Επαναμεταφόρτωση",
        "authmanager-create-disabled": "Η δημιουργία λογαριασμού έχει απενεργοποιηθεί.",
        "changecredentials-submit": "Αλλαγή πιστοποιητικών",
        "removecredentials": "Αφαίρεση πιστοποιητικών",
        "removecredentials-submit": "Αφαίρεση πιστοποιητικών",
+       "credentialsform-provider": "Τύπος πιστοποιητικών:",
        "credentialsform-account": "Όνομα λογαριασμού:",
        "cannotlink-no-provider-title": "Δεν υπάρχουν συνδέσιμοι λογαριασμοί",
        "cannotlink-no-provider": "Δεν υπάρχουν συνδέσιμοι λογαριασμοί.",
index a69aa69..eb55654 100644 (file)
        "filedelete-archive-read-only": "El servidor web no logra escribir en el directorio archivo \"$1\".",
        "previousdiff": "← Edición anterior",
        "nextdiff": "Edición siguiente →",
-       "mediawarning": "<strong>Advertencia:</strong> este archivo puede contener código malicioso.\nEjecutarlo podría comprometer la seguridad de tu equipo.",
+       "mediawarning": "<strong>Atención:</strong> este archivo puede contener código malicioso.\nEjecutarlo podría comprometer la seguridad de tu equipo.",
        "imagemaxsize": "Límite de tamaño de imagen:<br />''(para páginas de descripción de archivo)''",
        "thumbsize": "Tamaño de las vistas en miniatura:",
        "widthheight": "$1 × $2",
index 7956809..59ada69 100644 (file)
        "revdelete-hide-text": "Texte de la révision",
        "revdelete-hide-image": "Masquer le contenu du fichier",
        "revdelete-hide-name": "Masquer la cible et les paramètres",
-       "revdelete-hide-comment": "Modifier le résumé",
+       "revdelete-hide-comment": "Résumé de modification",
        "revdelete-hide-user": "Nom d’utilisateur/Adresse IP de l’éditeur",
        "revdelete-hide-restricted": "Supprimer ces données aux administrateurs ainsi qu'aux autres",
        "revdelete-radio-same": "(ne pas changer)",
index 1b82d03..408d8c9 100644 (file)
@@ -8,7 +8,7 @@
                        "Ammarpad"
                ]
        },
-       "tog-underline": "A shaya zaruruwa",
+       "tog-underline": "Link underlining:",
        "tog-hideminor": "A ɓoye ƙananan gyare-gyare na baya-bayan nan",
        "tog-hidepatrolled": "A ɓoye gyare-gyaren kan ido a cikin gyare-gyare bayan-bayan nan",
        "tog-newpageshidepatrolled": "A ɓoye shafuna kan ido a cikin sabbin shafuna",
        "missingarticle-rev": "(lambar zubi: $1)",
        "badtitletext": "Kan shafin da aka nema bai da ma'ana, ko kango ne, ko kuma wani kai ne na tsakanin harsuna ko shire-shire da bai da mahaɗi mai kyau.\nTana yiyuwa yana da harafi ko haruffa da ba su karɓuwa cikin kanu.",
        "viewsource": "Duba tushe",
+       "ns-specialprotected": "Shafuka na musamman ba za a iya gyra su ba.",
+       "logouttext": "Yanzu kun yi login out.",
+       "cannotlogoutnow-title": "Ba za ku iya login out ba yanzu. Ku sake gwadawa.",
+       "welcomeuser": "Barka da zuwa, $1!",
+       "welcomecreation-msg": "Yanzu kayi kirkiri sabon account.",
        "yourname": "Sunan ma'aikaci:",
        "userlogin-yourname": "Suna mai amfani",
        "userlogin-yourname-ph": "Shiga sunanka mai amfani",
        "logout": "Fita",
        "userlogout": "Fita",
        "createaccount": "ƙirƙira asusu",
+       "userlogin-resetpassword-link": "Ka manta lambobin sirrinka?",
        "createacct-emailrequired": "adireshin i-mel",
        "createacct-emailoptional": "adireshin i-mel (zaɓi)",
        "createacct-email-ph": "shiga adireshinka i-mel",
index 9a6121a..a882a43 100644 (file)
        "rcfilters-savedqueries-remove": "הסרה",
        "rcfilters-savedqueries-new-name-label": "שם",
        "rcfilters-savedqueries-new-name-placeholder": "תיאור מטרת המסנן",
-       "rcfilters-savedqueries-apply-label": "יצירת מסנן",
+       "rcfilters-savedqueries-apply-label": "×\99צ×\99רת ×\94×\9eסנ×\9f",
        "rcfilters-savedqueries-apply-and-setdefault-label": "יצירת מסנן התחלתי",
        "rcfilters-savedqueries-cancel-label": "ביטול",
        "rcfilters-savedqueries-add-new-title": "שמירת הגדרות המסננים הנוכחיות",
        "recentchanges-page-added-to-category": "הדף [[:$1]] נוסף לקטגוריה",
        "recentchanges-page-added-to-category-bundled": "הדף [[:$1]] נוסף לקטגוריה, [[Special:WhatLinksHere/$1|והוא מוכלל בדפים אחרים]]",
        "recentchanges-page-removed-from-category": "הדף [[:$1]] הוסר מהקטגוריה",
-       "recentchanges-page-removed-from-category-bundled": "הדף [[:$1]] הוסר מהקטגוריה, ו[[Special:WhatLinksHere/$1|הוא מוכלל בדפים אחרים]]",
+       "recentchanges-page-removed-from-category-bundled": "הדף [[:$1]] הוסר מהקטגוריה, [[Special:WhatLinksHere/$1|והוא מוכלל בדפים אחרים]]",
        "autochange-username": "שינוי אוטומטי של מדיה־ויקי",
        "upload": "העלאת קובץ",
        "uploadbtn": "העלאת הקובץ",
-       "reuploaddesc": "×\91×\99×\98×\95×\9c ×\94×\94×¢×\9c×\90×\94 ×\95×\97×\96ר×\94 ×\9c×\98×\95פס ×\94×¢×\9c×\90ת ×§×\91צ×\99×\9d ×\9cשרת",
+       "reuploaddesc": "×\91×\99×\98×\95×\9c ×\94×\94×¢×\9c×\90×\94 ×\95×\97×\96ר×\94 ×\9c×\98×\95פס ×\94×¢×\9c×\90ת ×\94ק×\91צ×\99×\9d",
        "upload-tryagain": "שליחת התיאור החדש של הקובץ",
        "upload-tryagain-nostash": "שליחת הקובץ המועלה מחדש והתיאור המעודכן",
        "uploadnologin": "לא נכנסת לחשבון",
        "uploadnologintext": "נדרשת $1 כדי להעלות קבצים.",
        "upload_directory_missing": "שרת האינטרנט אינו יכול ליצור את תיקיית ההעלאות ($1) החסרה.",
        "upload_directory_read_only": "שרת האינטרנט אינו יכול לכתוב בתיקיית ההעלאות ($1).",
-       "uploaderror": "ש×\92×\99×\90×\94 ×\91×\94×¢×\9c×\90ת ×\94ק×\95×\91×¥",
-       "upload-recreate-warning": "'''אזהרה: קובץ בשם זה נמחק או הועבר.'''\n\nיומני המחיקות וההעברות של הדף מוצגים להלן:",
-       "uploadtext": "×\94שת×\9eש×\95 ×\91×\98×\95פס ×\9c×\94×\9c×\9f ×\9b×\93×\99 ×\9c×\94×¢×\9c×\95ת ×§×\91צ×\99×\9d.\n×\9b×\93×\99 ×\9cר×\90×\95ת ×\90×\95 ×\9c×\97פש ×§×\91צ×\99×\9d ×©×\94×\95×¢×\9c×\95 ×\91×¢×\91ר ×\90× ×\90 ×¤× ×\95 ×\9c[[Special:FileList|רש×\99×\9eת ×\94ק×\91צ×\99×\9d ×©×\94×\95×¢×\9c×\95]], ×\95×\9b×\9e×\95 ×\9b×\9f, ×\94×¢×\9c×\90×\95ת (×\9b×\95×\9c×\9c ×\94×¢×\9c×\90×\95ת ×©×\9c ×\92רס×\94 ×\97×\93ש×\94) ×\9e×\95צ×\92×\95ת ×\91[[Special:Log/upload|×\99×\95×\9e×\9f ×\94×\94×¢×\9c×\90×\95ת]], ×\95×\9e×\97×\99ק×\95ת ×\91[[Special:Log/delete|×\99×\95×\9e×\9f ×\94×\9e×\97×\99ק×\95ת]].\n\n×\9b×\93×\99 ×\9c×\9b×\9c×\95×\9c ×§×\95×\91×¥ ×\91×\93×£, ×\94שת×\9eש×\95 ×\91ק×\99ש×\95ר ×\91×\90×\97ת ×\94צ×\95ר×\95ת ×\94×\91×\90×\95ת:\n* '''<code><nowiki>[[</nowiki>{{ns:file}}<nowiki>:File.jpg]]</nowiki></code>''' ×\9cש×\99×\9e×\95ש ×\91×\92רס×\94 ×\94×\9e×\9c×\90×\94 ×©×\9c ×\94ק×\95×\91×¥\n* '''<code><nowiki>[[</nowiki>{{ns:file}}<nowiki>:File.png|200px|thumb|left|×\98קס×\98 ×ª×\99×\90×\95ר]]</nowiki></code>''' ×\9cש×\99×\9e×\95ש ×\91×\92רס×\94 ×\9e×\95ק×\98נת ×\91ר×\95×\97×\91 200 ×¤×\99קס×\9c×\99×\9d ×\91ת×\99×\91×\94 ×\91צ×\93 ×©×\9e×\90×\9c ×©×\9c ×\94×\93×£, ×¢×\9d '×\98קס×\98 ×ª×\99×\90×\95ר' ×\9bת×\99×\90×\95ר\n* '''<code><nowiki>[[</nowiki>{{ns:media}}<nowiki>:File.ogg]]</nowiki></code>''' לקישור ישיר לקובץ בלי להציגו",
+       "uploaderror": "ש×\92×\99×\90×\94 ×\91×\94×¢×\9c×\90×\94",
+       "upload-recreate-warning": "<strong>אזהרה: קובץ בשם זה נמחק או הועבר.</strong>\n\nיומני המחיקות וההעברות של הדף מוצגים להלן:",
+       "uploadtext": "× ×\99ת×\9f ×\9c×\94שת×\9eש ×\91×\98×\95פס ×©×\9c×\94×\9c×\9f ×\9b×\93×\99 ×\9c×\94×¢×\9c×\95ת ×§×\91צ×\99×\9d.\n×\91×\90פשר×\95ת×\9a ×\9cר×\90×\95ת ×\90×\95 ×\9c×\97פש ×§×\91צ×\99×\9d ×©×\94×\95×¢×\9c×\95 ×\91×¢×\91ר ×\91[[Special:FileList|רש×\99×\9eת ×\94ק×\91צ×\99×\9d ×©×\94×\95×¢×\9c×\95]]. ×\9b×\9e×\95Ö¾×\9b×\9f, ×\94×¢×\9c×\90×\95ת (×\9b×\95×\9c×\9c ×\94×¢×\9c×\90×\95ת ×©×\9c ×\92רס×\94 ×\97×\93ש×\94) ×\9e×\95צ×\92×\95ת ×\91[[Special:Log/upload|×\99×\95×\9e×\9f ×\94×\94×¢×\9c×\90×\95ת]], ×\95×\9e×\97×\99ק×\95ת ×\9e×\95צ×\92×\95ת ×\91[[Special:Log/delete|×\99×\95×\9e×\9f ×\94×\9e×\97×\99ק×\95ת]].\n\n×\9b×\93×\99 ×\9c×\9b×\9c×\95×\9c ×§×\95×\91×¥ ×\91×\93×£, ×\99ש ×\9c×\94שת×\9eש ×\91ק×\99ש×\95ר ×\91×\90×\97ת ×\94צ×\95ר×\95ת ×\94×\91×\90×\95ת:\n* <strong><code><nowiki>[[</nowiki>{{ns:file}}<nowiki>:File.jpg]]</nowiki></code></strong> ×\9cש×\99×\9e×\95ש ×\91×\92רס×\94 ×\94×\9e×\9c×\90×\94 ×©×\9c ×\94ק×\95×\91×¥\n* <strong><code><nowiki>[[</nowiki>{{ns:file}}<nowiki>:File.png|200px|thumb|left|×\98קס×\98 ×ª×\99×\90×\95ר]]</nowiki></code></strong> ×\9cש×\99×\9e×\95ש ×\91×\92רס×\94 ×\9e×\95ק×\98נת ×\91ר×\95×\97×\91 200 ×¤×\99קס×\9c×\99×\9d ×\91ת×\99×\91×\94 ×\91צ×\93 ×©×\9e×\90×\9c ×©×\9c ×\94×\93×£ ×¢×\9d ×\98קס×\98 ×\9cת×\99×\90×\95ר\n* <strong><code><nowiki>[[</nowiki>{{ns:media}}<nowiki>:File.ogg]]</nowiki></code></strong> לקישור ישיר לקובץ בלי להציגו",
        "upload-permitted": "{{PLURAL:$2|סוג קובץ מותר|סוגי קבצים מותרים}}: $1.",
        "upload-preferred": "{{PLURAL:$2|סוג קובץ מומלץ|סוגי קבצים מומלצים}}: $1.",
        "upload-prohibited": "{{PLURAL:$2|סוג קובץ אסור|סוגי קבצים אסורים}}: $1.",
        "uploadlogpage": "יומן העלאות",
-       "uploadlogpagetext": "×\9c×\94×\9c×\9f ×¨×©×\99×\9e×\94 ×©×\9c ×\94×¢×\9c×\90×\95ת ×\94ק×\91צ×\99×\9d ×\94×\90×\97ר×\95× ×\95ת ×©×\91×\95צע×\95.\nר×\90×\95 ×\90ת [[Special:NewFiles|גלריית הקבצים החדשים]] להצגה ויזואלית שלהם.",
+       "uploadlogpagetext": "×\9c×\94×\9c×\9f ×¨×©×\99×\9e×\94 ×©×\9c ×\94×¢×\9c×\90×\95ת ×\94ק×\91צ×\99×\9d ×\94×\90×\97ר×\95× ×\95ת ×©×\91×\95צע×\95.\n×\90פשר ×\9c×¢×\99×\99×\9f ×\91[[Special:NewFiles|גלריית הקבצים החדשים]] להצגה ויזואלית שלהם.",
        "filename": "שם הקובץ",
        "filedesc": "תקציר",
        "fileuploadsummary": "תיאור:",
        "ignorewarning": "התעלמות מהאזהרה ושמירת הקובץ בכל זאת",
        "ignorewarnings": "התעלמות מכל האזהרות",
        "minlength1": "שמות קבצים צריכים להיות בני תו אחד לפחות.",
-       "illegalfilename": "ש×\9d ×\94ק×\95×\91×¥ \"$1\" ×\9e×\9b×\99×\9c ×ª×\95×\95×\99×\9d ×©×\90×\99× ×\9d ×\9e×\95תר×\99×\9d ×\91×\9b×\95תר×\95ת ×\93פ×\99×\9d.\n× ×\90 ×\9cשנ×\95ת ×\90ת ×\94ש×\9d ולנסות להעלותו שנית.",
+       "illegalfilename": "ש×\9d ×\94ק×\95×\91×¥ \"$1\" ×\9e×\9b×\99×\9c ×ª×\95×\95×\99×\9d ×©×\90×\99× ×\9d ×\9e×\95תר×\99×\9d ×\91×\9b×\95תר×\95ת ×\93פ×\99×\9d.\n× ×\90 ×\9cשנ×\95ת ×\90ת ×©×\9d ×\94ק×\95×\91×¥ ולנסות להעלותו שנית.",
        "filename-toolong": "שמות של קבצים לא יכולים להיות ארוכים יותר מ־240 בתים.",
        "badfilename": "שם הקובץ שונה ל־\"$1\".",
        "filetype-mime-mismatch": "סיומת הקובץ \".$1\" אינה מתאימה לסוג ה־MIME שנמצא לקובץ זה ($2).",
index 7c7e94a..41b9181 100644 (file)
        "tog-watchdeletion": "Зем беш йола оагIонашта а файлашта а тIатоха аз дIаяьккха оагIонаши файлаши",
        "tog-minordefault": "Массаза зIамига долаш санна белгалде хувцамаш.",
        "tog-previewontop": "Хьалххе бӀаргтохар хьагойта хувцама кора хьалхашкахь",
-       "tog-previewonfirst": "Хувцам бе дехьаваьлча хьалххе бӀаргтохар хьахьокха",
+       "tog-previewonfirst": "Ð¥Ñ\83вÑ\86ам Ð±Ðµ Ð°Ñ\8cнна Ð´ÐµÑ\85Ñ\8cаваÑ\8cлÑ\87а Ñ\85Ñ\8cалÑ\85Ñ\85е Ð±Ó\80аÑ\80гÑ\82оÑ\85аÑ\80 Ñ\85Ñ\8cаÑ\85Ñ\8cокÑ\85а",
        "tog-enotifwatchlistpages": "Электронни почте гIолла хоам бе сога нагахь аз зем беш йола оагIонаши файлаши цхьанне хийцача",
        "tog-enotifusertalkpages": "Электронни почте гIолла хоам бе сога са дувца оттадара оагIув хийцача",
        "tog-enotifminoredits": "ОагIонаштеи файлаштеи даь хинна хувцамаш геттара зIамига дале а хоам бе сога",
        "tog-enotifrevealaddr": "ДӀабӀаргадайта са поштан цӀай нахá дӀатӀаухача цхьа хӀама хайташ долча хоамаш чу",
-       "tog-shownumberswatching": "Ер оагIув шоаш зембеча оагIонашта юкъеяьккхача доакьошхой таьрахь гойта",
+       "tog-shownumberswatching": "Ер оагIув шоаш зембеча оагIонашта юкъеяьккха болча доакьошхой таьрахь хьагойта",
        "tog-oldsig": "Хьа карара кулг яздар:",
        "tog-fancysig": "Кулг яздара ший йола вики-разметка (автоматически тIахьожаярг йоацаш)",
-       "tog-uselivepreview": "Хьахьокха хьалххе бӀаргтохар оагӀув юха хьа ца елаш",
+       "tog-uselivepreview": "Хьахьокха хьалххе бӀаргтохар оагӀув юха хьа а ца елаш",
        "tog-forceeditsummary": "ДIахьалхадаккха, нагахьа санна хувцама йоазонца сурт оттадара моттиг хьалъйизанза яле",
        "tog-watchlisthideown": "Са зем бара хьаязъяьр чура хувцамаш къайладаха",
        "tog-watchlisthidebots": "Зем бара хьаязъяьр чура ботий хувцамаш къайладаха",
        "category-media-header": "\"$1\" яхача оагIата чура файлаш",
        "category-empty": "''Ер оагIат хӀанза яьсса я (цхьаккха оагIонаш е файлаш йоацаш).''",
        "hidden-categories": "{{PLURAL:$1|1=Къайла оагIат|Къайла оагIаташ}}",
-       "hidden-category-category": "Ð\9aÑ\8aайла ÐºÐ°Ñ\82егоÑ\80еш",
+       "hidden-category-category": "Ð\9aÑ\8aайла Ð¾Ð°Ð³Ó\80аÑ\82аш",
        "category-subcat-count": "{{PLURAL:$2|Укх оагIата чу я алхха ер кIалоагIат.|Укх оагIата чу гуш я $2-нен юкъера $1 {{PLURAL:$1|кIалоагIат}} }}",
        "category-subcat-count-limited": "Укх категори чу {{PLURAL:$1|кIалхара категори|$1 кIалхара категореш}} я.",
        "category-article-count": "{{PLURAL:$2|Укх оагIата чу цаI мара оагIув яц.|Укх оагIата чу я $2 оагӀув, царех оагӀонгахьа {{PLURAL:$1|хьагойт $1 оагӀув}}}}",
        "viewtalkpage": "Дувца оттадара бIаргтоха",
        "otherlanguages": "Кхыча меттаех",
        "redirectedfrom": "($1 дIа-сахьожаяьй укхаз)",
-       "redirectpagesub": "Ð\9eагIÑ\83в-дIа-Ñ\81аÑ\85Ñ\8cожадаÑ\80",
+       "redirectpagesub": "Ð\94Iа-Ñ\85Ñ\8cа Ñ\85Ñ\8cожаваÑ\80а Ð¾Ð°Ð³IÑ\83в",
        "redirectto": "ДIа-хьа хьожавар укхаза:",
        "lastmodifiedat": "Ер оагӀув тӀеххьара хийца хиннай укх ха́на: $1, $2.",
        "viewcount": "Укх оагIонга хьежа хиннаб $1{{PLURAL:$1|-зза}}.",
        "versionrequiredtext": "Укх оагIонца болх бергболаш $1 версех йола MediaWiki эша. Хьажа [[Special:Version|програмни Iалашдарах бола хоамага]].",
        "ok": "Мег",
        "retrievedfrom": "Хьаст — «$1»",
-       "youhavenewmessages": "{{PLURAL:$3|Хьога денад}} $1 ($2).",
+       "youhavenewmessages": "{{PLURAL:$3|Хьога}} $1 бéнаб ($2).",
        "youhavenewmessagesfromusers": "{{PLURAL:$4|Хьога кхаьчад}} $1 {{PLURAL:$3|1=$3 доакъашхочунгара|$3 доакъашхоштагара|1=кхыволча доакъашхочунгара}} ($2).",
-       "newmessageslinkplural": "{{PLURAL:$1|керда хоам|999=керда хоамаш}}",
+       "newmessageslinkplural": "{{PLURAL:$1|керда хоам}}",
        "newmessagesdifflinkplural": "{{PLURAL:$1|тӀехьара хувцам|999=тӀехьара хувцамаш}}",
        "youhavenewmessagesmulti": "Хьога кхаьчад керда хоамаш $1 чу",
        "editsection": "нийсде",
        "accmailtitle": "КъайладIоагӀа дӀадахьийтад",
        "newarticle": "(Kерда)",
        "newarticletext": "Шо тIатовжама гIолла дехьадаьннад йолаш йоацача оагӀон тӀа.\nИз хьакхолларгьйолаш кӀалхагӀа доалача корачу текст Iочуязъе (нагахьа санна кхетаде хала дале [$1 новкъосталара оагӀонга] хьажа).\nЦаховш укхаза нийсденнадале, шоай браузера чу '''Юха''' (Назад) яха тоIаера тӀа пӀелг тоӀабе.",
-       "anontalkpagetext": "----\n<em>Ð\95Ñ\80 Ð²Ð¾Ð²Ð·Ð°Ñ\88 Ð²Ð¾Ð°Ñ\86аÑ\87а (Ñ\88ий Ð´Ð°Ð³Ð°Ñ\80а Ð¹Ð¾Ð°Ð·Ñ\83в ÐºÑ\85Ñ\8b Ð° Ñ\85Ñ\8cакÑ\85олланза) Ð´Ð¾Ð°ÐºÑ\8aаÑ\88Ñ\85оÑ\87Ñ\83н Ð´Ñ\83вÑ\86а Ð¾Ñ\82Ñ\82адаÑ\80а Ð¾Ð°Ð³IÑ\83в Ñ\8f.</em>\nЦÑ\83 Ð±Ð°Ñ\85Ñ\8cане Ñ\82Ñ\85о Ð´ÐµÐºÑ\85аÑ\80ийла Ñ\86Ñ\83 Ñ\81ага Ð¸Ð´ÐµÐ½Ñ\82иÑ\84икаÑ\86и ÐµÑ\80 Ð´Ñ\83Ñ\85Ñ\8cа Ñ\86Ñ\83н IP-Ñ\86Ó\80ай Ñ\85Ñ\8cаÑ\85Ñ\8cокÑ\85а.\nÐ\98з Ñ\86Ó\80ай Ð»ÐµÐ»Ð°Ð´ÐµÑ\88 Ñ\85ила Ð¼ÐµÐ³ Ð¼Ð°Ñ\81еÑ\85к ÐºÑ\85Ñ\8bболÑ\87а Ð´Ð¾Ð°ÐºÑ\8aаÑ\88Ñ\85ой Ð°.\nÐ¥Ñ\8cо Ð²Ð¾Ð²Ð·Ð°Ñ\88 Ð²Ð¾Ð°Ñ\86а Ð´Ð¾Ð°ÐºÑ\8aаÑ\88Ñ\85о Ð²Ð°Ð»Ðµ,еÑ\80 Ñ\85оам Ñ\85Ñ\8cона Ð±Ð¾Ð°Ð³IаÑ\88 Ð±Ð°Ñ\86 Ð°Ñ\8cнна Ñ\85еÑ\82аÑ\88 вале, дехар да [[Special:CreateAccount|хьакхолла дагара йоазув]] е [[Special:UserLogin|хьавовзийта ражá]], тIехьагIа хье тувлавайтаргвоацаш бовзаш боацача доакъашхошца.",
-       "noarticletext": "ХIанза укх оагӀон тӀа текст яц.\nШун аьттув ба [[Special:Search/{{PAGENAME}}|цу тайпара цӀи хьоаяр лаха]] кхыйолча оагIонаш тIа, иштта\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} тептарий дIаяздаьраш лаха] е\n'''[{{fullurl:{{FULLPAGENAME}}|action=edit}} иззамо цӀи йолаш оагӀув хьакхолла]'''</span>.",
-       "noarticletext-nopermission": "Ð¥Iанз Ñ\83кÑ\85 Ð¾Ð°Ð³Ó\80он Ñ\82Ó\80а Ñ\82екÑ\81Ñ\82 Ñ\8fÑ\86.\nШÑ\83н Ð°Ñ\8cÑ\82Ñ\82Ñ\83в Ð±Ð° ÐºÑ\85Ñ\8bйолÑ\87а Ð¾Ð°Ð³IонаÑ\88 Ñ\82Iа [[Special:Search/{{PAGENAME}}|Ñ\86Ñ\83 Ñ\82айпаÑ\80а Ñ\86Ó\80и Ñ\85Ñ\8cоÑ\85аÑ\8fÑ\80 Ð»Ð°Ñ\85а]], Ð¸Ñ\88Ñ\82Ñ\82а <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} Ñ\82епÑ\82аÑ\80ай дIаяздаьраш лаха].</span> Ер оагӀув хьакхолла Хьа бокъо яц.",
+       "anontalkpagetext": "----\n<em>Ð\95Ñ\80 Ð´Ð° Ð²Ð¾Ð²Ð·Ð°Ñ\88 Ð²Ð¾Ð°Ñ\86аÑ\87а (Ñ\88ий Ð´Ð°Ð³Ð°Ñ\80а Ð¹Ð¾Ð°Ð·Ñ\83в ÐºÑ\85Ñ\8b Ð° Ñ\85Ñ\8cакÑ\85олланза) Ð´Ð¾Ð°ÐºÑ\8aаÑ\88Ñ\85оÑ\87Ñ\83н Ð¾Ð°Ð³IÑ\83в Ñ\8eвÑ\86аÑ\80.</em>\nЦÑ\83 Ð±Ð°Ñ\85Ñ\8cане Ñ\82Ñ\85о Ð´ÐµÐºÑ\85аÑ\80ийла Ð´Ð° Ñ\86Ñ\83 Ñ\81агá Ð¸Ð´ÐµÐ½Ñ\82иÑ\84икаÑ\86и ÐµÑ\80 Ð´Ñ\83Ñ\85Ñ\8cа Ñ\86Ñ\83н IP-Ñ\86Ó\80ай Ñ\85Ñ\8cаÑ\85Ñ\8cокÑ\85а.\nÐ\98з Ñ\86Ó\80ай Ð»ÐµÐ»Ð°Ð´ÐµÑ\88 Ñ\85ила Ð¼ÐµÐ³ Ð¼Ð°Ñ\81еÑ\85к ÐºÑ\85Ñ\8bболÑ\87а Ð´Ð¾Ð°ÐºÑ\8aаÑ\88Ñ\85оÑ\88а Ð°.\nÐ¥Ñ\8cо Ð²Ð¾Ð²Ð·Ð°Ñ\88 Ð²Ð¾Ð°Ñ\86а Ð´Ð¾Ð°ÐºÑ\8aаÑ\88Ñ\85о Ð²Ð°Ð»Ðµ, ÐµÑ\80 Ñ\85оам Ñ\85Ñ\8cона Ð±Ð¾Ð°Ð³IаÑ\88 Ð±Ð°Ñ\86 Ð°Ñ\8cнна Ñ\85еÑ\82аÑ\88 Ð° вале, дехар да [[Special:CreateAccount|хьакхолла дагара йоазув]] е [[Special:UserLogin|хьавовзийта ражá]], тIехьагIа хье тувлавайтаргвоацаш бовзаш боацача доакъашхошца.",
+       "noarticletext": "ХIанз укх оагӀон тӀа текст яц.\nШун аьттув ба [[Special:Search/{{PAGENAME}}|цу тайпара цӀи хьоахаяр лаха]] кхыйолча оагIонаш тIа, иштта\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} тептарий дIаяздаьраш лаха] е\n'''[{{fullurl:{{FULLPAGENAME}}|action=edit}} иззамо цӀи йолаш оагӀув хьакхолла]'''</span>.",
+       "noarticletext-nopermission": "Ð¥Iанз Ñ\83кÑ\85 Ð¾Ð°Ð³Ó\80он Ñ\82Ó\80а Ñ\82екÑ\81Ñ\82 Ñ\8fÑ\86.\nШÑ\83н Ð°Ñ\8cÑ\82Ñ\82Ñ\83в Ð±Ð° ÐºÑ\85Ñ\8bйолÑ\87а Ð¾Ð°Ð³IонаÑ\88 Ñ\82Iа [[Special:Search/{{PAGENAME}}|Ñ\86Ñ\83 Ñ\82айпаÑ\80а Ñ\86Ó\80и Ñ\85Ñ\8cоÑ\85аÑ\8fÑ\80 Ð»Ð°Ñ\85а]], Ð¸Ñ\88Ñ\82Ñ\82а <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} Ñ\82епÑ\82аÑ\80ий дIаяздаьраш лаха].</span> Ер оагӀув хьакхолла Хьа бокъо яц.",
        "userpage-userdoesnotexist-view": "«$1» яха дагара йоазув долаш дац.",
        "clearyourcache": "<strong>Теркал де.</strong> Хетаргахьа, оагIув дIаязъяь яьлча шоай браузера кэш IоцIанъе езаргья шун, даь хувцамаш гургдолаш.\n* <strong>Firefox / Safari:</strong> <em>Shift</em> яха лак тоIояь лоаттаеш инструментий цхьа дакъа тIа тоIае <em>Обновить</em> е <em>Ctrl-F5</em> тоIае е <em>Ctrl-R</em> (<em>⌘-R</em> Mac тIа)\n* <strong>Google Chrome:</strong> ТоIае <em>Ctrl-Shift-R</em> (<em>⌘-Shift-R</em> Mac тIа)\n* <strong>Internet Explorer:</strong> <em>Ctrl</em> яха лак тоIояь лоаттаеш, тоIае <em>Обновить</em> е <em>Ctrl-F5</em> тоIае\n* <strong>Opera:</strong> ДехьагIо <em>Menu → Настройки</em> (<em>Opera → Настройки</em> Mac тIа), тIаккха <em>Безопасность → Очистить историю посещений → Кэшированные изображения и файлы</em>",
        "note": "'''Белгалдоахар:'''",
        "historyempty": "(яьсса)",
        "history-feed-title": "Хувцамий истори",
        "history-feed-description": "Укх оагӀон хувцамий истори вике чу",
-       "history-feed-item-nocomment": "$1 → укх хана: $2",
+       "history-feed-item-nocomment": "$1 → укх хáна: $2",
        "rev-delundel": "хьахьокха/къайлаяккха",
        "rev-showdeleted": "хьахьокха",
        "revdelete-show-file-submit": "XӀа-а",
        "search-section": "(дáкъа «$1»)",
        "search-file-match": "(цхьатара хул файла чударца)",
        "search-suggest": "Хьона эшар ер хила мега: $1",
-       "search-interwiki-caption": "Ð\93аÑ\80гаÑ\80Ñ\87а Ð¿Ñ\80оекÑ\82еÑ\85 Ñ\85иннар",
+       "search-interwiki-caption": "Ð\9aÑ\85Ñ\8bйолÑ\87а Ð³Ð°Ñ\80гаÑ\80Ñ\87а Ð¿Ñ\80оекÑ\82аÑ\88ка ÐºÐ¾Ñ\80адаÑ\8cр",
        "search-interwiki-default": "Хьахиннараш укхазар $1:",
        "search-interwiki-more": "(кхы а)",
        "search-relatedarticle": "ВIашагIдувзаденна",
        "powersearch-togglenone": "Цхьаккха",
        "powersearch-remember": "Дагалáца хержар кхы тӀехьагӀа лохача хана накъадаргдолаш",
        "preferences": "ГIирс тоаяраш",
-       "mypreferences": "Ð\93IиÑ\80Ñ\81аш",
+       "mypreferences": "Ð\9eÑ\82Ñ\82амаш",
        "prefs-skin": "ТIера кийчдара тема",
        "skin-preview": "Хьалххе бIаргтохар",
        "prefs-personal": "Доакъашхочун дараш",
        "rc-change-size-new": "Хувцам баьнначул тӀехьагIа бола боарам: $1 {{PLURAL:$1|байт}}",
        "rc-enhanced-expand": "Хьахьокха ма дарра",
        "rc-enhanced-hide": "Къайладаккха ма дарра дар",
-       "rc-old-title": "духхьара кхелла хиннай «$1» цӀи йолаш",
+       "rc-old-title": "юххьанцара кхелла хиннай «$1» цӀи йолаш",
        "recentchangeslinked": "ВIашагIдувзаденна нийсдараш",
        "recentchangeslinked-feed": "ВIашагIдувзаденна нийсдараш",
        "recentchangeslinked-toolbox": "ВIашагIдувзаденна хувцамаш",
        "filehist-comment": "Белгалдаккхар",
        "imagelinks": "Файлах пайда эцар",
        "linkstoimage": "{{PLURAL:$1|1=ТIехьайоагIача $1 оагIо тIахьожаву|ТIехьайоагIача $1 оагIонаш тIахьожаву}} укх файла тIа:",
-       "linkstoimage-more": "$1-ннел дуккхагIа {{PLURAL:$1|оагIув}} я укх файла тIахьожавеш.\nУкх хьаязъяьра чу белгалъяй цу файла {{PLURAL:$1|алхха $1 тIахьожаярг}}.\nТIакхача йиш я иштта [[Special:WhatLinksHere/$2|бIарчча хьаязъяьра]].",
+       "linkstoimage-more": "$1-ннел дуккхагIа {{PLURAL:$1|оагIув}} я укх файлá тIахьожавеш.\nУкх хьаязъяьра чу хьахьекхаб цу файла алхха {{PLURAL:$1|$1 тIатовжам}}.\nЦхьабакъда [[Special:WhatLinksHere/$2|бIарчча хьаязъяьра]] а тIакхача йиш я хьа.",
        "nolinkstoimage": "Укх файла тӏатовжаш оагӏонаш яц.",
        "linkstoimage-redirect": "$1 (файлови дӀа-хьа хьожавар) $2",
        "sharedupload": "Ер файл $1 чура я, из пайда эцаш лелае мегаш я кхыйола проекташ чу.",
        "movethispage": "ЦIи хувца укх оагIон",
        "pager-newer-n": "$1 дукхагIа {{PLURAL:$1|керда}}",
        "pager-older-n": "{{PLURAL:$1|къаьнара дара|къаьнара дараш|къаьнара долaчарех}} $1",
-       "booksources": "Ð\94жейнай Ñ\85Ñ\8cаÑ\81Ñ\82аÑ\88 (иÑ\81Ñ\82оÑ\87ники)",
+       "booksources": "Ð\9aинижкий Ñ\85Ñ\8cаÑ\81Ñ\82аÑ\88",
        "booksources-search-legend": "Джейнах лаьца хоам лахар",
        "booksources-search": "Хьалáха",
        "specialloguserlabel": "Доакъашхо:",
        "speciallogtitlelabel": "Эшар (цӀи е доакъашхо):",
        "log": "Тептараш",
        "all-logs-page": "Деррига тIакхача йиш йола тептараш",
-       "alllogstext": "{{SITENAME}} Ñ\81айÑ\82а Ñ\82епÑ\82аÑ\80ий Ñ\8eкÑ\8aаÑ\80а Ñ\85Ñ\8cаÑ\8fзÑ\8aÑ\8fÑ\8cÑ\80.\nÐ¥Ñ\8cа Ð¹Ð¸Ñ\88 Ñ\8f Ñ\85Ñ\8cаÑ\85иннаÑ\80 Ñ\85Ñ\8cаÑ\85аÑ\80жа Ñ\82епÑ\82аÑ\80а Ñ\82айпаÑ\85, Ð´Ð¾Ð°ÐºÑ\8aаÑ\88Ñ\85оÑ\87Ñ\83н Ñ\86IеÑ\80а Ñ\82айпаÑ\85 (Ñ\80егиÑ\81Ñ\82Ñ\80 Ð»Ð¾Ð°Ñ\80Ñ\85IаÑ\88 Ñ\8f) Ðµ Ñ\85Ñ\8cаÑ\85аÑ\8fÑ\8cÑ\87а Ð¾Ð°Ð³Iон Ñ\82айпаÑ\85 (Ñ\80егиÑ\81Ñ\82Ñ\80 Ð»Ð¾Ð°Ñ\80Ñ\85IаÑ\88 Ñ\8f).",
+       "alllogstext": "{{SITENAME}} Ñ\8fÑ\85аÑ\87а Ñ\81айÑ\82а Ñ\82епÑ\82аÑ\80ий Ñ\8eкÑ\8aаÑ\80а Ñ\85Ñ\8cаÑ\8fзÑ\8aÑ\8fÑ\8cÑ\80.\nÐ¥Ñ\8cалеÑ\85аÑ\80аÑ\88 ÐºÑ\8aеÑ\80да Ð¹Ð¸Ñ\88 Ñ\8f Ñ\82епÑ\82аÑ\80а Ñ\82айпаÑ\85, Ð´Ð¾Ð°ÐºÑ\8aаÑ\88Ñ\85оÑ\87Ñ\83н Ñ\86IеÑ\80аÑ\85 (Ñ\80егиÑ\81Ñ\82Ñ\80 Ð»Ð¾Ð°Ñ\80Ñ\85IаÑ\88 Ñ\8f) Ðµ Ñ\85Ñ\8cоаÑ\85аÑ\8fÑ\8cÑ\87а Ð¾Ð°Ð³IонаÑ\85 (Ñ\83кÑ\85аза Ð° Ð¸Ñ\88Ñ\82Ñ\82а Ñ\80егиÑ\81Ñ\82Ñ\80 Ð»Ð¾Ð°Ñ\80Ñ\85I).",
        "logempty": "Укх оагӀон дӀаяздаьраш тептара чу дац.",
        "allpages": "Еррига оагIонаш",
        "prevpage": "Хьалха йоагIа оагIув ($1)",
-       "allpagesfrom": "Ð¥Ñ\8cаÑ\85Ñ\8cокÑ\85а Ð¾Ð°Ð³Ó\80онаÑ\88 дӀайолалуш йола укх алапех:",
+       "allpagesfrom": "Ð\93Ñ\83Ñ\87аÑ\8fÑ\85а Ð¾Ð°Ð³Ó\80онаÑ\88, дӀайолалуш йола укх алапех:",
        "allpagesto": "Хьахьокхар соцадé укхун тӀа:",
        "allarticles": "Еррига оагIонаш",
        "allpagessubmit": "Кхоачашде",
        "whatlinkshere": "Тӏатовжамаш укхаза",
        "whatlinkshere-title": "«$1» яхача оагӏонна тӏатовжаш йола оагӏонаш",
        "whatlinkshere-page": "ОагIув:",
-       "linkshere": "«'''[[:$1]]'''» яхача оагIонна тIахьожавеш я тIехьайоагIа:",
+       "linkshere": "«'''[[:$1]]'''» ← укхунна тӀахьожавеш я тӀехьайоагӀа оагӀонаш:",
        "nolinkshere": "Кхыйолча оагӏонашкара '''[[:$1]]''' яхача оагӏон тIатовжамаш доацаш да.",
-       "isredirect": "оагIÑ\83в-дIа-Ñ\81аÑ\85Ñ\8cожадаÑ\80",
+       "isredirect": "дIа-Ñ\85Ñ\8cа Ñ\85Ñ\8cожаваÑ\80а Ð¾Ð°Ð³IÑ\83в",
        "istemplate": "юкъейоалаяр",
        "isimage": "Файлови тӏатовжам",
        "whatlinkshere-prev": "{{PLURAL:$1|1=хьалхайоагIа|хьалхайоагIараш}} $1",
        "importlogpage": "Импорта тептар",
        "tooltip-pt-userpage": "{{GENDER:|Хьа}} доакъашхочун оагIув",
        "tooltip-pt-mytalk": "{{GENDER:|Хьа}} дувца оттадара оагIув",
-       "tooltip-pt-preferences": "{{GENDER:|Ð¥Ñ\8cа Ð³IиÑ\80Ñ\81аш}}",
+       "tooltip-pt-preferences": "{{GENDER:|Ð¥Ñ\8cа Ð¾Ñ\82Ñ\82амаш}}",
        "tooltip-pt-watchlist": "Iа зем бу оагIонаш",
        "tooltip-pt-mycontris": "{{GENDER:|хьа}} хувцамаш",
        "tooltip-pt-login": "Укхаза хьай цIи аьле чувала/яла йиша я, амма из параз дац",
        "tooltip-feed-rss": "RSS чу гойтар укх оагIон",
        "tooltip-feed-atom": "Укх оагIонна лаьрххIа Atom чу трансляци яр",
        "tooltip-t-contributions": "{{GENDER:$1|Укх доакъашхочо хийца}} йола оагIонаш",
-       "tooltip-t-emailuser": "ДIахьийта каьхат {{GENDER:$1|укх доакъашхочун}}",
+       "tooltip-t-emailuser": "ДIадахьийта каьхат {{GENDER:$1|укх доакъашхочунга}}",
        "tooltip-t-upload": "Файлаш чуяккха",
        "tooltip-t-specialpages": "ГIулакха оагIонаш",
        "tooltip-t-print": "Укх оагӏон зарба тохара эрш",
        "watchlisttools-raw": "Массаза йола текст санна хувца",
        "signature": "[[{{ns:user}}:$1|$2]] ([[{{ns:user_talk}}:$1|дувца оттадар]])",
        "duplicate-defaultsort": "Теркам. Долча тайпара дIанийсдара дIоагIа «$2» юхакъоастаду долча тайпара дIанийсдара хьалха хинна дIоагIа «$1».",
-       "version": "Ð\92еÑ\80Ñ\81и",
+       "version": "ЭÑ\80Ñ\88",
        "version-specialpages": "ГIулакха оагӀонаш",
        "version-version": "($1)",
-       "version-software-version": "Ð\92еÑ\80Ñ\81и",
-       "redirect": "Файла идентификатора тIара, доакъашхочун тIара, оагIон тIара, версин е тептара тIара дIа-сахьожадар",
-       "redirect-summary": "Укх белха оагIо дIа-сахьожаву файла (файлан цIера тIара), оагIонна (оагIон тIара е оагIон эрша идентификатора тIара), доакъашхочун оагIонна (доакъашхочун таьрахьа идентификатора тIара) е тептара йоазонна (тептара идентификатора тIара). Пайда эцар: [[{{#Special:Redirect}}/file/Example.jpg]], [[{{#Special:Redirect}}/page/64308]], [[{{#Special:Redirect}}/revision/328429]], [[{{#Special:Redirect}}/user/101]] или [[{{#Special:Redirect}}/logid/186]].",
+       "version-software-version": "ЭÑ\80Ñ\88",
+       "redirect": "Файла идентификатора тIара, доакъашхочун тIара, оагIон тIара, эрша тIара е тептара тIара дIа-хьа хьожавар",
+       "redirect-summary": "Укх белха оагIоно дIа-хьа хьожаву файлá (файла цIера тIара), оагIонна (оагIон тIара е оагIон эрша идентификатора тIара), доакъашхочун оагIонна (доакъашхочун таьрахьа идентификатора тIара) е тептара йоазонна (тептара идентификатора тIара). Пайда эцар: [[{{#Special:Redirect}}/file/Example.jpg]], [[{{#Special:Redirect}}/page/64308]], [[{{#Special:Redirect}}/revision/328429]], [[{{#Special:Redirect}}/user/101]] или [[{{#Special:Redirect}}/logid/186]].",
        "redirect-submit": "Дехьавала",
        "redirect-lookup": "Лахар:",
        "redirect-value": "Боарам:",
        "tags-title": "Белгалонаш",
        "tags-tag": "Белгалон цӀи",
        "tags-hitcount-header": "Белгалдаь нийсдараш",
-       "tags-active-yes": "XIаа",
+       "tags-active-yes": "XӀау",
        "tags-active-no": "A",
        "tags-edit": "нийсде",
        "tags-hitcount": "$1 {{PLURAL:$1|1=хувцам|хувцамаш}}",
        "tags-create-submit": "Хьакхолла",
        "compare-page1": "ЦхьоаллагIа оагIув",
        "compare-page2": "ШоллагIа оагӀув",
-       "compare-rev1": "ЦÑ\85Ñ\8cоаллагIа Ð²ÐµÑ\80Ñ\81и",
-       "compare-rev2": "ШоллагӀа верси",
+       "compare-rev1": "Ð¥Ñ\8cалÑ\85аÑ\80а Ñ\8dÑ\80Ñ\88",
+       "compare-rev2": "ШоллагӀа эрш",
        "htmlform-submit": "ДIадахьийта",
        "htmlform-reset": "Хувцамаш юхадаккха",
        "htmlform-selectorother-other": "Кхыдар",
        "logentry-newusers-create": "{{GENDER:$2|Доакъашхочо хьакхеллад}} дагара йоазув $1",
        "logentry-newusers-autocreate": "Ше-ше кхеллай {{GENDER:$2|доакъашхочун}} $1 дагара йоазув",
        "logentry-upload-upload": "$1 {{GENDER:$2|чуяьккхай}} $3",
-       "logentry-upload-overwrite": "$1 доакъашхочо {{GENDER:$2|чуяьккхай}} керда верси $3",
+       "logentry-upload-overwrite": "$1 доакъашхочо {{GENDER:$2|чуяьккхай}} $3 яхачун керда эрш",
        "rightsnone": "(яц)",
        "searchsuggest-search": "Хьалаха {{grammar:prepositional|{{SITENAME}}}} чу",
        "duration-days": "{{PLURAL:$1|ди}}",
        "expand_templates_preview": "Хьалххе бIаргтохар",
        "pagelang-name": "ОагIув",
-       "special-characters-group-latin": "Ð\9bаÑ\82иной",
+       "special-characters-group-latin": "Ð\9bаÑ\82иний",
        "special-characters-group-greek": "Эллиной",
        "special-characters-group-cyrillic": "Кириллица",
        "special-characters-group-arabic": "Iарбий",
index 00dbfe6..5cf1530 100644 (file)
        "prefs-dateformat": "日付と時刻の形式",
        "prefs-timeoffset": "時差",
        "prefs-advancedediting": "全般オプション",
+       "prefs-developertools": "開発者用ツール",
        "prefs-editor": "エディター",
        "prefs-preview": "プレビュー",
        "prefs-advancedrc": "詳細の設定",
index 022064c..cb423c5 100644 (file)
        "savechanges": "Išsaugoti pakeitimus",
        "publishpage": "Išsaugoti puslapį",
        "publishchanges": "Išsaugoti pakeitimus",
+       "publishchanges-start": "Išsaugoti pakeitimus…",
        "preview": "Peržiūra",
        "showpreview": "Rodyti peržiūrą",
        "showdiff": "Rodyti skirtumus",
index f2831d1..c694116 100644 (file)
        "viewsourceold": "察源碼",
        "editlink": "纂",
        "viewsourcelink": "察源碼",
-       "editsectionhint": "纂段:$1",
+       "editsectionhint": "所纂章名:$1",
        "toc": "章",
        "showtoc": "示",
        "hidetoc": "藏",
        "searchrelated": "關",
        "searchall": "全",
        "showingresults": "見'''$1'''尋,自'''$2'''始:",
-       "search-nonefound": "詢中無結。",
+       "search-nonefound": "無所獲。",
        "powersearch-legend": "尋",
        "powersearch-ns": "尋名集:",
        "powersearch-togglelabel": "核:",
        "newpageletter": "新",
        "boteditletter": "僕",
        "number_of_watching_users_pageview": "[放有$1哨]",
-       "rc-change-size-new": "既纂,本文有$1字節",
+       "rc-change-size-new": "纂後有$1字節",
        "newsectionsummary": "/* $1 */ 新節",
        "rc-enhanced-expand": "示細",
        "rc-enhanced-hide": "藏細",
index 44ec0a3..a8eddc8 100644 (file)
        "createacct-email-ph": "Entratz vòstra adreça de corrièr electronic",
        "createacct-another-email-ph": "Picar l'adreça de corrièr electronic",
        "createaccountmail": "Utilizar un senhal aleatòri temporari e lo mandar a l’adreça de corrièl especificada",
+       "createaccountmail-help": "Pòt s'utilizar per crear un compte per una autra persona sens conéisser lo mot de passa.",
        "createacct-realname": "Nom vertadièr (facultatiu)",
        "createacct-reason": "Motiu",
        "createacct-reason-ph": "Perqué creatz un autre compte",
        "wrongpassword": "Lo nom d'utilizaire o lo senhal es incorrècte.\nEnsajatz tornarmai.",
        "wrongpasswordempty": "Lo senhal picat èra void. Se vos plai, ensajatz tornarmai.",
        "passwordtooshort": "Vòstre senhal deu conténer al mens {{PLURAL:$1|1 caractèr|$1 caractèrs}}.",
+       "passwordtoolong": "Mots de passa pòdon pas aver mai de  {{PLURAL:$1|1 caractèr|$1 caractèrs}}.",
+       "passwordtoopopular": "Es pas possible d'utilizar mots de passa fòrça comuns. Vos cal causir un mot de passa mai malaisit  de deschifrar.",
        "password-name-match": "Vòstre senhal deu èsser diferent de vòstre nom d’utilizaire.",
        "password-login-forbidden": "L'usatge d'aquestes nom d'utilizaire e senhal es pas autorisat",
        "mailmypassword": "Reïnicializar lo senhal",
        "passwordreset-emailelement": "Utilizaire: \n$1\n\nSenhal temporari: \n$2",
        "passwordreset-emailsentemail": "Se aquesta adreça de corrièl es associada a vòstre compte, alara un corrièl de reïnicializacion de senhal serà mandat.",
        "passwordreset-emailsentusername": "Se i a una adreça de corrièr electronic associada a aqueste nom d’utilizaire, alara un corrièl de reïnicializacion senhal serà mandat.",
+       "passwordreset-nocaller": "Cal provesir un apelaire",
        "passwordreset-nosuchcaller": "L’apelant existís pas : $1",
+       "passwordreset-ignored": "Lo restabliment del mot de passa s'es pas plan realizat. Benlèu i aviá pas cap de fornidor configurat?",
        "passwordreset-invalidemail": "Adreça de corrièr electronic invalida",
+       "passwordreset-nodata": "Pas cap de nom d'usatgièr o d'adreça electronica foguèron provesits",
        "changeemail": "Cambiar o suprimir l'adreça electronica",
        "changeemail-header": "Completatz aqueste formulari per modificar vòstra adreça de corrièl. Se volètz suprimir l’associacion d’una adreça de corrièl amb vòstre compte, daissatz la novèla adreça de corrièl voida al moment de la somission del formulari.",
        "changeemail-no-info": "Vos cal èsser connectat per aver accès a aquesta pagina.",
        "changeemail-oldemail": "Adreça electronica actuala:",
        "changeemail-newemail": "Novela adreça electronica:",
+       "changeemail-newemail-help": "Vos cal daissar aquel camp void se volètz suprimir la vòstra adreça electronica. Mas, se suprimètz aquela adreça poiretz pas tornar inicializar lo mot de passa se l'avètz doblidat e recebretz pas de corrièrs electronics dempuèi aquel wiki.",
        "changeemail-none": "(pas cap)",
        "changeemail-password": "Vòstre senhal sus {{SITENAME}} :",
        "changeemail-submit": "Cambiar l'adreça electronica :",
        "changeemail-throttled": "Avètz fait tròp de temptativas de connexion.\nEsperatz $1 abans d’ensajar tornarmai.",
+       "changeemail-nochange": "Vos cal picar una novèla adreça electronica, diferenta de la precedenta.",
        "resettokens": "Reïnicializar los getons",
        "resettokens-text": "Aici, podètz reïnicializar los getons que permeton d’accedir a d'unas donadas privadas associadas a vòstre compte.\n\nLo vos caldriá far se las avètz partejats accidentalament amb qualqu'un o se vòstre compte es estat compromés.",
        "resettokens-no-tokens": "I a pas cap de geton de reïnicializar.",
        "anoneditwarning": "<strong>Atencion :<strong> sètz pas connectat.\nVòstra adreça IP serà visibla per tot lo monde se fasètz de modificacions. Se <strong>[$1 vos connectatz]</strong> o <strong>[$2 creatz un compte]</strong>, vòstras modificacions seràn atribuidas a vòstre nom d’utilizaire, entre autres avantatges.",
        "anonpreviewwarning": "''Sètz pas identificat. Salvar enregistrarà vòstra adreça IP dins l’istoric de las modificacions de la pagina.''",
        "missingsummary": "'''Atencion :''' avètz pas modificat lo resumit de vòstra modificacion. Se clicatz tornarmai sul boton « Salvar », lo salvament serà fait sens avertiment mai.",
+       "selfredirect": "<strong>Atencion:</strong> Sètz a redirigir aquela pagina cap a se meteissa.\nPodètz aver especificat un faus objectiu per la redireccion, o benlèu avètz modificar una pagina incorrècta.\nSe tornatz faire un clic \"$1\" , la redireccion serà çaquelà creada.",
        "missingcommenttext": "Mercé de metre un comentari.",
        "missingcommentheader": "<strong>Rapèl :</strong> Avètz pas provesit cap de subjècte per aqueste comentari.\nSe clicatz tornamai sus « {{int:Savearticle}} », vòstra modificacion serà enregistrada sens subjècte.",
        "summary-preview": "Apercebut del resumit de modificacion :",
        "subject-preview": "Apercebut del subjècte :",
+       "previewerrortext": "S'es produsida una error quand ensagèretz  de previsualizar los cambiaments.",
        "blockedtitle": "L'utilizaire es blocat",
        "blockedtext": "'''Vòstre compte d'utilizaire o vòstra adreça IP es estat blocat'''\n\nLo blocatge es estat efectuat per $1.\nLa rason invocada es la seguenta : ''$2''.\n\n* Començament del blocatge : $8\n* Expiracion del blocatge : $6\n* Compte blocat : $7.\n\nPodètz contactar $1 o un autre [[{{MediaWiki:Grouppage-sysop}}|administrator]] per ne discutir.\nPodètz pas utilizar la foncion « Mandar un corrièr electronic a aqueste utilizaire » que se una adreça de corrièr valida es especificada dins vòstras [[Special:Preferences|preferéncias]].\nVòstra adreça IP actuala es $3 e vòstre identificant de blocatge es #$5.\nIncluissètz aquesta adreça dins tota requèsta.",
        "autoblockedtext": "Vòstra adreça IP es estada blocada automaticament perque es estada utilizada per un autre utilizaire, ele-meteis blocat per $1.\nLa rason invocadaa es :\n\n:''$2''\n\n* Començament del blocatge : $8\n* Expiracion del blocatge : $6\n* Compte blocat : $7\n\nPodètz contactar $1 o un dels autres [[{{MediaWiki:Grouppage-sysop}}|administrators]] per discutir d'aqueste blocatge.\n\nNotatz que podètz pas utilizar la foncionalitat \"Mandar un messatge a aqueste utilizaire\" tant qu'auretz pas  una adreça e-mail enregistrada dins vòstras [[Special:Preferences|preferéncias]] e tant que seretz pas blocat per son utilizacion.\n\nVòstra adreça IP actuala es $3, e lo numèro de blocatge es $5.\nPrecisatz aquestas indicacions dins totas las requèstas que faretz.",
+       "systemblockedtext": "Lo vòstre nom d'usatgièr o adreça IP foguèt estat blocat automaticament pel MediaWiki.\nLo motiu balhat es:\n\n:<em>$2</em>\n\n* Començament del blocatge: $8\n* Fin del delai de blocatge: $6\n* Element pertocat: $7\n\nLa vòstra adreça IP actuala es $3.\nApondètz las donadas de mai amont per cada demanda  que faretz.",
        "blockednoreason": "Cap de rason balhada",
        "whitelistedittext": "Vos cal èsser $1 per modificar las paginas.",
        "confirmedittext": "Vos cal confirmar vòstra adreça electronica abans de modificar l'enciclopèdia. Picatz e validatz vòstra adreça electronica amb l'ajuda de la pagina [[Special:Preferences|preferéncias]].",
        "yourtext": "Vòstre tèxte",
        "storedversion": "Version enregistrada",
        "editingold": "'''Atencion : sètz a modificar una version obsolèta d'aquesta pagina. Se salvatz, totas las modificacions efectuadas dempuèi aquesta version seràn perdudas.'''",
+       "unicode-support-fail": "Sembla que lo vòstre navigador es pas compatible amb Unicode. Aquò es necessari per modificar las paginas, la vòstra edicion foguèt pas salvagardada.",
        "yourdiff": "Diferéncias",
        "copyrightwarning": "Totas las contribucions a {{SITENAME}} son consideradas coma publicadas jols tèrmes de la $2 (vejatz $1 per mai de detalhs). Se desiratz pas que vòstres escrits sián modificats e distribuits a volontat, mercés de los sometre pas aicí.<br /> Nos prometètz tanben qu'avètz escrit aquò vos-meteis, o que l’avètz copiat d’una font provenent del domeni public, o d’una ressorsa liura.'''UTILIZETZ PAS DE TRABALHS JOS COPYRIGHT SENS AUTORIZACION EXPRÈSSA !'''",
        "copyrightwarning2": "Totas las contribucions a {{SITENAME}} pòdon èsser modificadas o suprimidas per d’autres utilizaires. Se desiratz pas que vòstres escrits sián modificats e distribuits a volontat, mercés de los sometre pas aicí.<br /> Tanben nos prometètz qu'avètz escrit aquò vos-meteis, o que l’avètz copiat d’una font provenent del domeni public, o d’una ressorsa liura. (vejatz $1 per mai de detalhs). '''UTILIZETZ PAS DE TRABALHS JOS COPYRIGHT SENS AUTORIZACION EXPRÈSSA !'''",
+       "editpage-cannot-use-custom-model": "Lo modèl de contengut d'aquela pagina pòt pas èsser cambiat.",
        "longpageerror": "'''ERROR : Lo tèxte qu'avètz somés fa {{PLURAL:$1|un Kio|$1 Kio}}, çò que depassa lo limit fixat a {{PLURAL:$2|un Kio|$2 Kio}}.'''. Pòt pas èsser salvat.",
        "readonlywarning": "<strong>AVERTIMENT : La basa de donadas es estada verrolhada per d'operacions de mantenença. Doncas, poiretz pas publicar vòstras modificacions pel moment.</strong>\nL’administrator sistèma qu'an verrolhada la basa de donadas a donat l’explicacion seguenta : $1",
        "protectedpagewarning": "'''AVERTIMENT : Aquesta pagina es protegida. Sols los utilizaires qu'an l'estatut d'administrator la p�don modificar. ''' La darri�ra entrada del jornal es afichada �aij�s per refer�ncia :",
        "permissionserrors": "Error de permission",
        "permissionserrorstext": "Avètz pas la permission d’efectuar l’operacion demandada per {{PLURAL:$1|la rason seguenta|las rasons seguentas}} :",
        "permissionserrorstext-withaction": "Sètz pas autorizat(ada) a $2, per {{PLURAL:$1|la rason seguenta|las rasons seguentas}} :",
+       "contentmodelediterror": "Podètz pas modificar aquela revision perque lo sieu modèl de contengut es <code>$1</code>, qu'es diferent del modèl de contengut actual de la pagina <code>$2</code>.",
        "recreate-moveddeleted-warn": "'''Atencion : sètz a tornar crear una pagina qu'es estada suprimida precedentament.'''\n\nDemandatz-vos s'es vertadièrament apropriat de contunhar de l’editar.\nL’istoric de las supressions e dels cambiaments de nom es afichat çaijós :",
        "moveddeleted-notice": "Aquesta pagina es estada suprimida.\nLo jornal de las supressions, de las proteccions e dels desplaçaments de la pagina es afichat çaijós per referéncia.",
+       "moveddeleted-notice-recent": "Desolat, aquela pagina foguèt recentament suprimida (en las darrièras 24 oras).\nPodètz consultar lo registre de las supressions, proteccions e dels renomenatges de la pagina çai-jos.",
        "log-fulllog": "Veire lo jornal complet",
        "edit-hook-aborted": "Modificacion fracassada per croquet.\nCap d'explicacion pas balhada.",
        "edit-gone-missing": "A pas pogut metre a jorn la pagina.\nSembla que siá estada suprimida.",
        "postedit-confirmation-created": "La pagina es estada creada.",
        "postedit-confirmation-restored": "La pagina es estada restablida.",
        "postedit-confirmation-saved": "Vòstra modificacion es estada salvada.",
+       "postedit-confirmation-published": "La vòstra modificacion foguèt publicada.",
        "edit-already-exists": "La pagina novèla a pogut èsser creada .\nExistís ja.",
        "defaultmessagetext": "Messatge per defaut",
        "content-failed-to-parse": "Fracàs de l'analisi del contengut de $2 pel modèl $1: $3",
        "invalid-content-data": "Donadas del contengut invalidas",
        "content-not-allowed-here": "Lo contengut \"$1\" es pas autorizat sus la pagina [[$2]]",
        "editwarning-warning": "Quitar aquesta pagina vos farà pèrdre totas las modificacions qu'avètz faitas.\nSe sètz connectat, podètz desactivar aqueste avertiment dins la seccion « {{int:prefs-editing}} » de vòstras preferéncias.",
+       "editpage-invalidcontentmodel-title": "Modèl de contengut pas permés",
+       "editpage-invalidcontentmodel-text": "Lo modèl de contengut «$1» es pas permés.",
        "editpage-notsupportedcontentformat-title": "Format de contengut pas pres en carga",
        "editpage-notsupportedcontentformat-text": "Lo format de contengut $1 es pas pres en carga pel modèl de contengut $2 .",
        "content-model-wikitext": "wikitèxte",
index 6d1dcd0..b89d6f8 100644 (file)
        "minoredit": "Midà be bagatellas",
        "watchthis": "Observar quest artitgel",
        "savearticle": "Memorisar la pagina",
+       "publishchanges": "Publitgar midadas",
        "preview": "Prevista",
        "showpreview": "Mussar prevista",
        "showdiff": "Mussar midadas",
index 5dfc2db..f7a70a0 100644 (file)
        "rcfilters-filtergroup-changetype": "Врста измене",
        "rcfilters-filter-pageedits-label": "Измене страница",
        "rcfilters-filter-pageedits-description": "Измене вики садржаја, расправа, описа категорија…",
-       "rcfilters-filter-newpages-label": "СÑ\82ваÑ\80ање страница",
+       "rcfilters-filter-newpages-label": "Ð\9fÑ\80авÑ\99ење страница",
        "rcfilters-filter-newpages-description": "Измене којима се стварају нове странице.",
        "rcfilters-filter-categorization-label": "Измене категорија",
        "rcfilters-filter-categorization-description": "Записи о страницама додатим или уклоњеним из категорија.",
index 00f4e0f..a2d6bbc 100644 (file)
        "nstab-help": "ⵜⴰⵙⵏⴰ ⵏ ⵜⵡⵉⵙⵉ",
        "nstab-category": "ⴰⵙⵎⵉⵍ",
        "mainpage-nstab": "ⵜⴰⵙⵏⴰ ⵏ ⵓⵙⵏⵓⴱⴳ",
+       "nosuchspecialpage": "ⴰⵡⴷ ⵢⴰⵜ ⵜⴰⵙⵏⴰ ⵉⵥⵍⵉⵏ.",
+       "nospecialpagetext": "<strong>ⵜⴻⵜⵜⵔⴷ ⵢⴰⵜ ⵜⴰⵙⵏⴰ ⵉⵥⵍⵉⵏ ⵓⵔ ⵉⵅⴷⵉⵎⵏ</strong>",
        "error": "ⵜⴰⵣⴳⵍⵜ",
        "databaseerror-error": "ⵜⴰⵣⴳⵍⵜ: $1",
        "badtitle": "ⴳⴰⵔ ⴰⵣⵡⵍ",
        "searchprofile-advanced-tooltip": "ⵔⵣⵓ ⴳ ⵜⵉⵔⵉⵡⵉⵏ ⵏ ⵉⵙⵎⴰⵡⵏ ⵉⵜⵡⴰⵏⵉⵎⴰⵏ",
        "search-result-size": "$1 ({{PLURAL:$2|1 ⵜⴳⵓⵔⵉ|$2 ⵜⴳⵓⵔⵉⵡⵉⵏ}})",
        "search-redirect": "(ⵓⵖⵓⵍ ⵙⴳ $1)",
+       "search-section": "(ⴰⵙⴱⴹⵓ $1)",
        "search-suggest": "ⵉⵙ ⵜⵅⵙⴷ ⴰⴷ ⵜⵉⵏⵉⴷ: $1",
        "search-interwiki-more": "(ⵓⴳⴳⴰⵔ)",
        "searchall": "ⴰⴽⴽ",
        "enhancedrc-history": "ⴰⵎⵣⵔⴰⵢ",
        "recentchanges": "ⵉⵙⵏⴼⵍⵏ ⵉⵎⴳⴳⵓⵔⴰ",
        "recentchanges-legend": "ⵜⵉⴷⵖⵔⵉⵏ ⵏ ⵉⵙⵏⴼⵍⵏ ⵉⵎⴳⴳⵓⵔⴰ",
+       "recentchanges-summary": "ⴹⴼⵔ ⵉⵙⵏⵉⴼⵉⵍⵏ ⵉⵎⴳⴳⵓⵔⴰ ⴰⴽⴽ ⵖⴼ ⵓⵡⵉⴽⵉ ⴷⴳ ⵜⴰⵙⵏⴰ ⴰⴷ.",
        "recentchanges-label-newpage": "ⵉⵙⵏⴼⵍⵓⵍ ⵓⵙⵏⴼⵍ ⴰ ⵢⴰⵜ ⵜⴰⵙⵏⴰ ⵜⴰⵎⴰⵢⵏⵓⵜ",
        "recentchanges-label-minor": "ⵡⴰ ⴷ ⴰⵙⵏⴼⵍ ⵓⵎⵥⵉⵢ",
        "recentchanges-label-bot": "ⴰⵙⵏⴼⵍ ⴰⴷ ⵉⵜⵡⴰⵙⴽⴰⵔ ⵙ ⵓⴱⵓⵜ",
        "mycontris": "ⵜⵓⵎⵓⵜⵉⵏ",
        "anoncontribs": "ⵜⵓⵎⵓⵜⵉⵏ",
        "contribsub2": "ⵉ {{GENDER:$3|$1}} ($2)",
+       "uctop": "(ⴰⵎⵉⵔⴰⵏ)",
        "month": "ⵙⴳ ⵡⴰⵢⵢⵓⵔ (and earlier):",
        "year": "ⵙⴳ ⵓⵙⴳⴳⵯⴰⵙ (and earlier):",
        "sp-contributions-newbies": "ⵙⴽⵏ ⵜⵓⵎⵓⵜⵉⵏ ⵏ ⵉⵎⵉⴹⴰⵏ ⵉⵎⴰⵢⵏⵓⵜⵏ ⴽⴰⵏ",
        "sp-contributions-uploads": "ⵉⵙⴽⵜⴰⵔⵏ",
        "sp-contributions-talk": "ⵎⵙⴰⵡⴰⵍ",
        "sp-contributions-search": "ⵔⵣⵓ ⵖⴼ ⵜⵓⵎⵓⵜⵉⵏ",
+       "sp-contributions-username": "ⵜⴰⵏⵙⴰ ⵏ IP ⵏⵖ ⵉⵙⵎ ⵓⵎⵔⵉⵙ:",
+       "sp-contributions-toponly": "ⵙⴽⵏ ⵖⴰⵙ ⵉⵙⵏⵉⴼⵉⵍⵏ ⵉⴳⴰⵏ ⵉⵣⵣⵔⴰⵢⵏ ⵉⵎⴳⴳⵓⵔⴰ",
        "sp-contributions-newonly": "ⵙⴽⵏ ⵖⴰⵙ ⵉⵙⵏⵍⵏ ⵏⵏⴰ ⵉⴳⴰⵏ ⵉⵙⵏⵓⵍⴼⵓⵜⵏ ⵏ ⵜⴰⵙⵏⴰ",
        "sp-contributions-submit": "ⵔⵣⵓ",
        "whatlinkshere": "ⵎⴰ ⴰⵢⴷ ⵉⵇⵇⵏⵏ ⵙ ⴷⴰ",
        "whatlinkshere-hideimages": "$1 ⵉⵣⴷⴰⵢⵏ ⵖⵔ ⵓⴼⵉⵍⵢⵓ",
        "whatlinkshere-filters": "ⵜⵉⵙⵜⵜⴰⵢⵉⵏ",
        "ipbreason": "ⵜⴰⵎⵏⵜⵉⵍⵜ:",
-       "ipboptions": "2 âµ\8f âµ\9câµ\99âµ\94â´°â´³âµ\89âµ\8f:2 âµ\8f âµ\9câµ\99âµ\94â´°â´³âµ\89âµ\8f,1 âµ\8f âµ¡â´°âµ\99âµ\99:1 âµ\8f âµ¡â´°âµ\99âµ\99,3 âµ\8f âµ¡âµ\93âµ\99âµ\99â´°âµ\8f:3 âµ\8f âµ¡âµ\93âµ\99âµ\99â´°âµ\8f,1 âµ\8f âµ\89âµ\8eâ´°âµ\8dâ´°âµ\99âµ\99:1 âµ\8f âµ\89âµ\8eâ´°âµ\8dâ´°âµ\99âµ\99k,2 âµ\8f âµ\89âµ\8eâ´°âµ\8dâ´°âµ\99âµ\99âµ\8f:2 âµ\8f âµ\89âµ\8eâ´°âµ\8dâ´°âµ\99âµ\99âµ\8f,1 âµ\8f âµ¡â´°âµ¢âµ¢âµ\93âµ\94:1 âµ\8f âµ¡â´°âµ¢âµ¢âµ\93âµ\94,3 âµ\8f âµ¡â´°âµ¢âµ¢âµ\93âµ\94âµ\8f:3 âµ\8f âµ¡â´°âµ¢âµ¢âµ\93âµ\94âµ\8f,6 âµ\8f âµ¡â´°âµ¢âµ¢âµ\93âµ\94âµ\8f:6 âµ\8f âµ¡â´°âµ¢âµ¢âµ\93âµ\94âµ\8f,1 âµ\8f âµ\93âµ\99ⴳⴳⴰâµ\99:1 âµ\8f âµ\93âµ\99ⴳⴳⴰâµ\99,indefinite:infinite",
+       "ipboptions": "2 âµ\9câµ\99âµ\94â´°â´³âµ\89âµ\8f:2 âµ\9câµ\99âµ\94â´°â´³âµ\89âµ\8f,1 âµ¡â´°âµ\99âµ\99:1 âµ¡â´°âµ\99âµ\99,3 âµ¡âµ\93âµ\99âµ\99â´°âµ\8f:3 âµ\8f âµ¡âµ\93âµ\99âµ\99â´°âµ\8f,1 âµ\89âµ\8eâ´°âµ\8dâ´°âµ\99âµ\99:1 âµ\89âµ\8eâ´°âµ\8dâ´°âµ\99âµ\99,2 âµ\89âµ\8eâ´°âµ\8dâ´°âµ\99âµ\99âµ\8f:2 âµ\89âµ\8eâ´°âµ\8dâ´°âµ\99âµ\99âµ\8f,1 âµ¡â´°âµ¢âµ¢âµ\93âµ\94:1 âµ¡â´°âµ¢âµ¢âµ\93âµ\94,3 âµ\89ⵢⵢâµ\89âµ\94âµ\8f:3 âµ\89ⵢⵢâµ\89âµ\94âµ\8f,6 âµ\89ⵢⵢâµ\89âµ\94âµ\8f:6 âµ\89ⵢⵢâµ\89âµ\94âµ\8f,1 âµ\93âµ\99ⴳⴳⴰâµ\99:1 âµ\93âµ\99ⴳⴳⴰâµ\99,â´°âµ\94âµ\93âµ\99âµ\8eâµ\89âµ\8d:âµ\9câ´°âµ\94âµ\9câ´°âµ\8dâµ\8dâ´°",
        "blocklist-reason": "ⵜⴰⵎⵏⵜⵉⵍⵜ",
        "blocklink": "ⴳⴷⵍ",
        "contribslink": "ⵜⵓⵎⵓⵜⵉⵏ",
        "tooltip-save": "ⵃⴹⵓ ⵉⵙⵏⴼⴰⵍ ⵏⵏⴽ",
        "tooltip-preview": "ⵣⵔ ⵣⵡⴰⵔ ⵉⵙⵏⴼⵍⵏ ⵏⴽ. ⴼⴰⴷ ⴰⴷ ⵜⵏ ⵜⵙⵓⵙⵔⴷ.",
        "tooltip-diff": "ⵙⴽⵏ ⵎⴰⵏ ⵉⵙⵏⴼⴰⵍ ⵜⴳⴳⵉⴷ ⵉ ⵓⴹⵔⵉⵙ",
+       "tooltip-compareselectedversions": "ⵥⵔ ⴰⵎⵣⴰⵔⴰⵢ ⴳⵔ ⵙⵉⵏ ⵉⵣⵣⵔⴰⵢⵏ ⵉⵜⵜⵓⵙⵜⴰⵢⵏ ⵏ ⵜⴰⵙⵏⴰ ⴰⴷ",
        "tooltip-watch": "ⵔⵏⵓ ⵜⴰⵙⵏⴰ ⴰ ⵉ ⵜⵍⴳⴰⵎⵜ ⵏ ⵓⴹⴼⴼⵓⵔ {{GENDER:|ⵏⵏⴽ|ⵏⵏⵎ}}",
        "tooltip-rollback": "\"ⵔⴰⵔ\" ⵙⵙⵔ ⴰⵙⵏⴼⵍ ⵏⵖ ⵉⵙⵏⴼⴰⵍⵏ ⵏ ⵓⵎⴰⴷⵔⴰⵡ ⴰⵎⴳⴳⴰⵔⵓ ⴳ ⵜⴰⵙⵏⴰ ⴷ ⵙ ⵢⴰⵏ ⵓⴽⵍⵉⴽ",
        "tooltip-summary": "ⴰⵔⴰ ⴽⵔⴰ ⵏ ⵓⵙⴳⵣⵍ ⵎⵥⵥⵉⵢⵏ",
index cea4151..692c366 100644 (file)
        "exif-copyrighted-false": "版权状态未设定",
        "exif-photometricinterpretation-0": "黑白(白为0)",
        "exif-photometricinterpretation-1": "黑白(黑为0)",
+       "exif-photometricinterpretation-4": "透明遮罩",
+       "exif-photometricinterpretation-5": "分隔(可能是CMYK)",
+       "exif-photometricinterpretation-32803": "色彩滤镜矩阵",
        "exif-unknowndate": "未知日期",
        "exif-orientation-1": "标准",
        "exif-orientation-2": "水平翻转",
        "watchlisttools-view": "查看相关更改",
        "watchlisttools-edit": "查看并编辑监视列表",
        "watchlisttools-raw": "编辑原始监视列表",
+       "hijri-calendar-m1": "穆哈兰姆月",
+       "hijri-calendar-m2": "色法尔月",
+       "hijri-calendar-m3": "赖比尔·敖外鲁月",
+       "hijri-calendar-m4": "赖比尔·阿色尼月",
+       "hijri-calendar-m5": "主马达·敖外鲁月",
+       "hijri-calendar-m6": "主马达·阿色尼月",
+       "hijri-calendar-m7": "赖哲卜月",
+       "hijri-calendar-m8": "舍尔邦月",
+       "hijri-calendar-m9": "赖买丹月",
+       "hijri-calendar-m10": "闪瓦鲁月",
+       "hijri-calendar-m11": "都尔喀尔德月",
+       "hijri-calendar-m12": "都尔黑哲月",
+       "hebrew-calendar-m1": "提斯利月",
+       "hebrew-calendar-m2": "玛西班月",
+       "hebrew-calendar-m3": "基斯流月",
+       "hebrew-calendar-m4": "提别月",
+       "hebrew-calendar-m5": "细罢特月",
+       "hebrew-calendar-m6": "亚达月",
+       "hebrew-calendar-m6a": "第一亚达月",
+       "hebrew-calendar-m6b": "第二亚达月",
+       "hebrew-calendar-m7": "尼散月",
+       "hebrew-calendar-m8": "以珥月",
+       "hebrew-calendar-m9": "西弯月",
+       "hebrew-calendar-m10": "搭模斯月",
+       "hebrew-calendar-m11": "埃波月",
+       "hebrew-calendar-m12": "以禄月",
        "signature": "[[{{ns:user}}:$1|$2]]([[{{ns:user_talk}}:$1|讨论]])",
        "timezone-local": "本地",
        "duplicate-defaultsort": "<strong>警告:</strong>默认排序关键词“$2”覆盖了之前的默认排序关键词“$1”。",
index 5d8e44c..3a14a15 100644 (file)
@@ -96,7 +96,8 @@
                        "Laundry Machine",
                        "和平至上",
                        "Sanmosa",
-                       "Dongzn"
+                       "Dongzn",
+                       "Shangkuanlc"
                ]
        },
        "tog-underline": "底線標示連結:",
index 91e0bc1..4c02998 100644 (file)
@@ -35,8 +35,7 @@
        </script>
        <script>
                // Mock startup.js
-               var mwPerformance = { mark: function () {} },
-                       mwNow = Date.now;
+               var mwNow = Date.now;
 
                function startUp() {
                        mw.config = new mw.Map();
index 9120e2a..144659a 100644 (file)
 
                                if ( meta && meta.tiff && meta.tiff.Orientation ) {
                                        rotation = ( 360 - ( function () {
-                                               // See includes/media/Bitmap.php
+                                               // See BitmapHandler class in PHP
                                                switch ( meta.tiff.Orientation.value ) {
                                                        case 8:
                                                                return 90;
index 3fe276b..fbd4530 100644 (file)
                                // For addEmbeddedCSS()
                                cssBuffer = '',
                                cssBufferTimer = null,
-                               cssCallbacks = $.Callbacks(),
+                               cssCallbacks = [],
                                rAF = window.requestAnimationFrame || setTimeout;
 
                        function getMarker() {
                         */
                        function addEmbeddedCSS( cssText, callback ) {
                                function fireCallbacks() {
-                                       var oldCallbacks = cssCallbacks;
+                                       var i,
+                                               oldCallbacks = cssCallbacks;
                                        // Reset cssCallbacks variable so it's not polluted by any calls to
                                        // addEmbeddedCSS() from one of the callbacks (T105973)
-                                       cssCallbacks = $.Callbacks();
-                                       oldCallbacks.fire().empty();
+                                       cssCallbacks = [];
+                                       for ( i = 0; i < oldCallbacks.length; i++ ) {
+                                               oldCallbacks[ i ]();
+                                       }
                                }
 
                                if ( callback ) {
-                                       cssCallbacks.add( callback );
+                                       cssCallbacks.push( callback );
                                }
 
                                // Yield once before creating the <style> tag. This lets multiple stylesheets
                        return $.when.apply( $, all );
                } );
                loading.then( function () {
-                       /* global mwPerformance */
-                       mwPerformance.mark( 'mwLoadEnd' );
+                       if ( window.performance && performance.mark ) {
+                               performance.mark( 'mwLoadEnd' );
+                       }
                        mw.hook( 'resourceloader.loadEnd' ).fire();
                } );
        } );
index cc313c7..41bcbaa 100644 (file)
@@ -5,11 +5,8 @@
  * - Beware: Do not call mwNow before the isCompatible() check.
  */
 
-/* global mw, mwPerformance, mwNow, isCompatible, $VARS, $CODE */
+/* global mw, mwNow, isCompatible, $VARS, $CODE */
 
-window.mwPerformance = ( window.performance && performance.mark ) ? performance : {
-       mark: function () {}
-};
 // Define now() here to ensure valid comparison with mediaWikiLoadEnd (T153819).
 window.mwNow = ( function () {
        var perf = window.performance,
@@ -151,8 +148,9 @@ window.isCompatible = function ( str ) {
        }
 
        window.mediaWikiLoadStart = mwNow();
-       mwPerformance.mark( 'mwLoadStart' );
-
+       if ( window.performance && performance.mark ) {
+               performance.mark( 'mwStartup' );
+       }
        script = document.createElement( 'script' );
        script.src = $VARS.baseModulesUri;
        script.onload = function () {