Merge "resourceloader: Remove redundant 'group' setting for StartupModule"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Wed, 19 Jun 2019 12:26:04 +0000 (12:26 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Wed, 19 Jun 2019 12:26:04 +0000 (12:26 +0000)
188 files changed:
.phan/config.php
.phan/internal_stubs/README [new file with mode: 0644]
.phan/internal_stubs/imagick.phan_php [new file with mode: 0644]
.phan/internal_stubs/pcntl.phan_php [new file with mode: 0644]
.phan/internal_stubs/redis.phan_php [new file with mode: 0644]
.phan/internal_stubs/sockets.phan_php [new file with mode: 0644]
RELEASE-NOTES-1.34
autoload.php
composer.json
docs/hooks.txt
docs/linkcache.txt
docs/magicword.txt
docs/memcached.txt
docs/php-memcached/Documentation
includes/Autopromote.php
includes/DefaultSettings.php
includes/DevelopmentSettings.php
includes/Html.php
includes/MediaWiki.php
includes/Message.php [deleted file]
includes/OutputPage.php
includes/PHPVersionCheck.php
includes/PathRouter.php
includes/Permissions/PermissionManager.php
includes/Title.php
includes/TrackingCategories.php
includes/WebRequest.php
includes/actions/HistoryAction.php
includes/actions/RawAction.php
includes/api/ApiCSPReport.php
includes/api/ApiMain.php
includes/api/ApiQueryBase.php
includes/api/i18n/es.json
includes/block/BlockManager.php
includes/block/CompositeBlock.php
includes/changetags/ChangeTags.php
includes/debug/logger/monolog/CeeFormatter.php
includes/deferred/LinksUpdate.php
includes/filerepo/file/ForeignDBFile.php
includes/filerepo/file/LocalFile.php
includes/filerepo/file/OldLocalFile.php
includes/filerepo/file/UnregisteredLocalFile.php
includes/htmlform/HTMLFormField.php
includes/htmlform/fields/HTMLSelectAndOtherField.php
includes/installer/WebInstaller.php
includes/installer/WebInstallerOptions.php
includes/installer/WebInstallerOutput.php
includes/installer/i18n/cs.json
includes/installer/i18n/io.json
includes/installer/i18n/ja.json
includes/installer/i18n/nl.json
includes/installer/i18n/sl.json
includes/jobqueue/JobSpecification.php
includes/language/LanguageCode.php [new file with mode: 0644]
includes/language/Message.php [new file with mode: 0644]
includes/language/MessageLocalizer.php [new file with mode: 0644]
includes/libs/rdbms/database/DBConnRef.php
includes/libs/rdbms/database/Database.php
includes/libs/rdbms/database/DatabaseMysqlBase.php
includes/libs/rdbms/database/DatabaseMysqli.php
includes/libs/rdbms/database/DatabaseSqlite.php
includes/libs/rdbms/database/IDatabase.php
includes/libs/rdbms/loadbalancer/LoadBalancerSingle.php
includes/libs/replacers/DoubleReplacer.php [deleted file]
includes/libs/replacers/HashtableReplacer.php [deleted file]
includes/libs/replacers/RegexlikeReplacer.php [deleted file]
includes/libs/replacers/Replacer.php [deleted file]
includes/page/PageArchive.php
includes/page/WikiPage.php
includes/parser/PPCustomFrame_DOM.php
includes/parser/PPFrame_DOM.php
includes/parser/PPNode_DOM.php
includes/parser/PPTemplateFrame_DOM.php
includes/parser/Parser.php
includes/parser/Preprocessor_DOM.php
includes/parser/Sanitizer.php
includes/rcfeed/RedisPubSubFeedEngine.php
includes/resourceloader/ResourceLoaderCircularDependencyError.php [new file with mode: 0644]
includes/resourceloader/ResourceLoaderContext.php
includes/resourceloader/ResourceLoaderFileModule.php
includes/resourceloader/ResourceLoaderImageModule.php
includes/resourceloader/ResourceLoaderLessVarFileModule.php
includes/resourceloader/ResourceLoaderStartUpModule.php
includes/search/SearchEngine.php
includes/search/SearchHighlighter.php
includes/search/SearchResult.php
includes/search/SearchResultSet.php
includes/search/SqlSearchResultSet.php
includes/shell/Command.php
includes/specialpage/SpecialPage.php
includes/specialpage/SpecialPageFactory.php
includes/specials/SpecialUnblock.php
includes/specials/SpecialUserrights.php
includes/specials/SpecialVersion.php
includes/upload/UploadStash.php
includes/user/BotPassword.php
includes/user/User.php
includes/widget/search/FullSearchResultWidget.php
includes/widget/search/InterwikiSearchResultWidget.php
includes/widget/search/SearchResultWidget.php
includes/widget/search/SimpleSearchResultWidget.php
languages/Language.php
languages/LanguageCode.php [deleted file]
languages/MessageLocalizer.php [deleted file]
languages/classes/LanguageZh.php
languages/i18n/aeb-arab.json
languages/i18n/an.json
languages/i18n/ar.json
languages/i18n/arz.json
languages/i18n/ast.json
languages/i18n/be-tarask.json
languages/i18n/ca.json
languages/i18n/cdo.json
languages/i18n/crh-cyrl.json
languages/i18n/crh-latn.json
languages/i18n/cs.json
languages/i18n/cv.json
languages/i18n/diq.json
languages/i18n/en.json
languages/i18n/eo.json
languages/i18n/es.json
languages/i18n/exif/qqq.json
languages/i18n/fa.json
languages/i18n/fr.json
languages/i18n/fy.json
languages/i18n/he.json
languages/i18n/hu.json
languages/i18n/hyw.json
languages/i18n/ia.json
languages/i18n/io.json
languages/i18n/ja.json
languages/i18n/kiu.json
languages/i18n/ko.json
languages/i18n/lzh.json
languages/i18n/mk.json
languages/i18n/my.json
languages/i18n/nds-nl.json
languages/i18n/nl.json
languages/i18n/nqo.json
languages/i18n/pl.json
languages/i18n/pt-br.json
languages/i18n/qqq.json
languages/i18n/roa-tara.json
languages/i18n/ru.json
languages/i18n/sdc.json
languages/i18n/sh.json
languages/i18n/sl.json
languages/i18n/vec.json
languages/i18n/yo.json
languages/messages/MessagesAz.php
maintenance/deduplicateArchiveRevId.php
maintenance/generateSitemap.php
maintenance/populateArchiveRevId.php
mw-config/config.css
mw-config/config.js
resources/src/jquery/jquery.makeCollapsible.js
resources/src/jquery/jquery.textSelection.js
resources/src/mediawiki.Uri/Uri.js
tests/common/TestsAutoLoader.php
tests/parser/ParserTestRunner.php
tests/phpunit/MediaWikiUnitTestCase.php [new file with mode: 0644]
tests/phpunit/includes/GlobalFunctions/wfUrlencodeTest.php
tests/phpunit/includes/HtmlTest.php
tests/phpunit/includes/Revision/RevisionStoreTest.php
tests/phpunit/includes/StatusTest.php
tests/phpunit/includes/Storage/NameTableStoreTest.php
tests/phpunit/includes/TestLogger.php
tests/phpunit/includes/TitleTest.php
tests/phpunit/includes/api/query/ApiQueryTestBase.php
tests/phpunit/includes/db/DatabaseSqliteTest.php
tests/phpunit/includes/filerepo/file/ForeignDBFileTest.php [new file with mode: 0644]
tests/phpunit/includes/libs/HashRingTest.php
tests/phpunit/includes/libs/IPTest.php
tests/phpunit/includes/libs/rdbms/database/DBConnRefTest.php
tests/phpunit/includes/parser/PreprocessorTest.php
tests/phpunit/includes/password/PasswordFactoryTest.php [deleted file]
tests/phpunit/includes/resourceloader/ResourceLoaderContextTest.php
tests/phpunit/includes/resourceloader/ResourceLoaderFileModuleTest.php
tests/phpunit/includes/resourceloader/ResourceLoaderImageTest.php
tests/phpunit/includes/resourceloader/ResourceLoaderStartUpModuleTest.php
tests/phpunit/includes/specials/SpecialSearchTest.php
tests/phpunit/includes/utils/BatchRowUpdateTest.php
tests/phpunit/includes/utils/UIDGeneratorTest.php
tests/phpunit/maintenance/categoriesRdfTest.php
tests/phpunit/suite.xml
tests/phpunit/unit-tests.xml [new file with mode: 0644]
tests/phpunit/unit/includes/password/PasswordFactoryTest.php [new file with mode: 0644]
tests/phpunit/unit/initUnitTests.php [new file with mode: 0644]

index 3478977..8746ada 100644 (file)
@@ -43,8 +43,12 @@ $cfg['file_list'] = array_merge(
 );
 
 $cfg['autoload_internal_extension_signatures'] = [
+       'imagick' => '.phan/internal_stubs/imagick.phan_php',
        'memcached' => '.phan/internal_stubs/memcached.phan_php',
        'oci8' => '.phan/internal_stubs/oci8.phan_php',
+       'pcntl' => '.phan/internal_stubs/pcntl.phan_php',
+       'redis' => '.phan/internal_stubs/redis.phan_php',
+       'sockets' => '.phan/internal_stubs/sockets.phan_php',
        'sqlsrv' => '.phan/internal_stubs/sqlsrv.phan_php',
        'tideways' => '.phan/internal_stubs/tideways.phan_php',
 ];
diff --git a/.phan/internal_stubs/README b/.phan/internal_stubs/README
new file mode 100644 (file)
index 0000000..c57d596
--- /dev/null
@@ -0,0 +1,5 @@
+See <https://github.com/phan/phan/wiki/How-To-Use-Stubs#generating-stubs> for
+how to generate internal stubs for phan.
+
+The stubs should be generated using the PHP version that is our lowest
+requirement.
diff --git a/.phan/internal_stubs/imagick.phan_php b/.phan/internal_stubs/imagick.phan_php
new file mode 100644 (file)
index 0000000..c4f355b
--- /dev/null
@@ -0,0 +1,1204 @@
+<?php
+// These stubs were generated by the phan stub generator.
+// @phan-stub-for-extension imagick@3.4.3RC2
+
+namespace {
+class Imagick implements \Iterator, \Traversable, \Countable {
+
+    // constants
+    const COLOR_BLACK = 11;
+    const COLOR_BLUE = 12;
+    const COLOR_CYAN = 13;
+    const COLOR_GREEN = 14;
+    const COLOR_RED = 15;
+    const COLOR_YELLOW = 16;
+    const COLOR_MAGENTA = 17;
+    const COLOR_OPACITY = 18;
+    const COLOR_ALPHA = 19;
+    const COLOR_FUZZ = 20;
+    const IMAGICK_EXTNUM = 30403;
+    const IMAGICK_EXTVER = '3.4.3RC2';
+    const QUANTUM_RANGE = 65535;
+    const USE_ZEND_MM = 0;
+    const COMPOSITE_DEFAULT = 40;
+    const COMPOSITE_UNDEFINED = 0;
+    const COMPOSITE_NO = 1;
+    const COMPOSITE_ADD = 2;
+    const COMPOSITE_ATOP = 3;
+    const COMPOSITE_BLEND = 4;
+    const COMPOSITE_BUMPMAP = 5;
+    const COMPOSITE_CLEAR = 7;
+    const COMPOSITE_COLORBURN = 8;
+    const COMPOSITE_COLORDODGE = 9;
+    const COMPOSITE_COLORIZE = 10;
+    const COMPOSITE_COPYBLACK = 11;
+    const COMPOSITE_COPYBLUE = 12;
+    const COMPOSITE_COPY = 13;
+    const COMPOSITE_COPYCYAN = 14;
+    const COMPOSITE_COPYGREEN = 15;
+    const COMPOSITE_COPYMAGENTA = 16;
+    const COMPOSITE_COPYOPACITY = 17;
+    const COMPOSITE_COPYRED = 18;
+    const COMPOSITE_COPYYELLOW = 19;
+    const COMPOSITE_DARKEN = 20;
+    const COMPOSITE_DSTATOP = 21;
+    const COMPOSITE_DST = 22;
+    const COMPOSITE_DSTIN = 23;
+    const COMPOSITE_DSTOUT = 24;
+    const COMPOSITE_DSTOVER = 25;
+    const COMPOSITE_DIFFERENCE = 26;
+    const COMPOSITE_DISPLACE = 27;
+    const COMPOSITE_DISSOLVE = 28;
+    const COMPOSITE_EXCLUSION = 29;
+    const COMPOSITE_HARDLIGHT = 30;
+    const COMPOSITE_HUE = 31;
+    const COMPOSITE_IN = 32;
+    const COMPOSITE_LIGHTEN = 33;
+    const COMPOSITE_LUMINIZE = 35;
+    const COMPOSITE_MINUS = 36;
+    const COMPOSITE_MODULATE = 37;
+    const COMPOSITE_MULTIPLY = 38;
+    const COMPOSITE_OUT = 39;
+    const COMPOSITE_OVER = 40;
+    const COMPOSITE_OVERLAY = 41;
+    const COMPOSITE_PLUS = 42;
+    const COMPOSITE_REPLACE = 43;
+    const COMPOSITE_SATURATE = 44;
+    const COMPOSITE_SCREEN = 45;
+    const COMPOSITE_SOFTLIGHT = 46;
+    const COMPOSITE_SRCATOP = 47;
+    const COMPOSITE_SRC = 48;
+    const COMPOSITE_SRCIN = 49;
+    const COMPOSITE_SRCOUT = 50;
+    const COMPOSITE_SRCOVER = 51;
+    const COMPOSITE_SUBTRACT = 52;
+    const COMPOSITE_THRESHOLD = 53;
+    const COMPOSITE_XOR = 54;
+    const COMPOSITE_CHANGEMASK = 6;
+    const COMPOSITE_LINEARLIGHT = 34;
+    const COMPOSITE_DIVIDE = 55;
+    const COMPOSITE_DISTORT = 56;
+    const COMPOSITE_BLUR = 57;
+    const COMPOSITE_PEGTOPLIGHT = 58;
+    const COMPOSITE_VIVIDLIGHT = 59;
+    const COMPOSITE_PINLIGHT = 60;
+    const COMPOSITE_LINEARDODGE = 61;
+    const COMPOSITE_LINEARBURN = 62;
+    const COMPOSITE_MATHEMATICS = 63;
+    const COMPOSITE_MODULUSADD = 2;
+    const COMPOSITE_MODULUSSUBTRACT = 52;
+    const COMPOSITE_MINUSDST = 36;
+    const COMPOSITE_DIVIDEDST = 55;
+    const COMPOSITE_DIVIDESRC = 64;
+    const COMPOSITE_MINUSSRC = 65;
+    const COMPOSITE_DARKENINTENSITY = 66;
+    const COMPOSITE_LIGHTENINTENSITY = 67;
+    const COMPOSITE_HARDMIX = 68;
+    const MONTAGEMODE_FRAME = 1;
+    const MONTAGEMODE_UNFRAME = 2;
+    const MONTAGEMODE_CONCATENATE = 3;
+    const STYLE_NORMAL = 1;
+    const STYLE_ITALIC = 2;
+    const STYLE_OBLIQUE = 3;
+    const STYLE_ANY = 4;
+    const FILTER_UNDEFINED = 0;
+    const FILTER_POINT = 1;
+    const FILTER_BOX = 2;
+    const FILTER_TRIANGLE = 3;
+    const FILTER_HERMITE = 4;
+    const FILTER_HANNING = 5;
+    const FILTER_HAMMING = 6;
+    const FILTER_BLACKMAN = 7;
+    const FILTER_GAUSSIAN = 8;
+    const FILTER_QUADRATIC = 9;
+    const FILTER_CUBIC = 10;
+    const FILTER_CATROM = 11;
+    const FILTER_MITCHELL = 12;
+    const FILTER_LANCZOS = 22;
+    const FILTER_BESSEL = 13;
+    const FILTER_SINC = 14;
+    const FILTER_KAISER = 16;
+    const FILTER_WELSH = 17;
+    const FILTER_PARZEN = 18;
+    const FILTER_LAGRANGE = 21;
+    const FILTER_SENTINEL = 31;
+    const FILTER_BOHMAN = 19;
+    const FILTER_BARTLETT = 20;
+    const FILTER_JINC = 13;
+    const FILTER_SINCFAST = 15;
+    const FILTER_ROBIDOUX = 26;
+    const FILTER_LANCZOSSHARP = 23;
+    const FILTER_LANCZOS2 = 24;
+    const FILTER_LANCZOS2SHARP = 25;
+    const FILTER_ROBIDOUXSHARP = 27;
+    const FILTER_COSINE = 28;
+    const FILTER_SPLINE = 29;
+    const FILTER_LANCZOSRADIUS = 30;
+    const IMGTYPE_UNDEFINED = 0;
+    const IMGTYPE_BILEVEL = 1;
+    const IMGTYPE_GRAYSCALE = 2;
+    const IMGTYPE_GRAYSCALEMATTE = 3;
+    const IMGTYPE_PALETTE = 4;
+    const IMGTYPE_PALETTEMATTE = 5;
+    const IMGTYPE_TRUECOLOR = 6;
+    const IMGTYPE_TRUECOLORMATTE = 7;
+    const IMGTYPE_COLORSEPARATION = 8;
+    const IMGTYPE_COLORSEPARATIONMATTE = 9;
+    const IMGTYPE_OPTIMIZE = 10;
+    const IMGTYPE_PALETTEBILEVELMATTE = 11;
+    const RESOLUTION_UNDEFINED = 0;
+    const RESOLUTION_PIXELSPERINCH = 1;
+    const RESOLUTION_PIXELSPERCENTIMETER = 2;
+    const COMPRESSION_UNDEFINED = 0;
+    const COMPRESSION_NO = 1;
+    const COMPRESSION_BZIP = 2;
+    const COMPRESSION_FAX = 6;
+    const COMPRESSION_GROUP4 = 7;
+    const COMPRESSION_JPEG = 8;
+    const COMPRESSION_JPEG2000 = 9;
+    const COMPRESSION_LOSSLESSJPEG = 10;
+    const COMPRESSION_LZW = 11;
+    const COMPRESSION_RLE = 12;
+    const COMPRESSION_ZIP = 13;
+    const COMPRESSION_DXT1 = 3;
+    const COMPRESSION_DXT3 = 4;
+    const COMPRESSION_DXT5 = 5;
+    const COMPRESSION_ZIPS = 14;
+    const COMPRESSION_PIZ = 15;
+    const COMPRESSION_PXR24 = 16;
+    const COMPRESSION_B44 = 17;
+    const COMPRESSION_B44A = 18;
+    const COMPRESSION_LZMA = 19;
+    const COMPRESSION_JBIG1 = 20;
+    const COMPRESSION_JBIG2 = 21;
+    const PAINT_POINT = 1;
+    const PAINT_REPLACE = 2;
+    const PAINT_FLOODFILL = 3;
+    const PAINT_FILLTOBORDER = 4;
+    const PAINT_RESET = 5;
+    const GRAVITY_NORTHWEST = 1;
+    const GRAVITY_NORTH = 2;
+    const GRAVITY_NORTHEAST = 3;
+    const GRAVITY_WEST = 4;
+    const GRAVITY_CENTER = 5;
+    const GRAVITY_EAST = 6;
+    const GRAVITY_SOUTHWEST = 7;
+    const GRAVITY_SOUTH = 8;
+    const GRAVITY_SOUTHEAST = 9;
+    const GRAVITY_FORGET = 0;
+    const GRAVITY_STATIC = 10;
+    const STRETCH_NORMAL = 1;
+    const STRETCH_ULTRACONDENSED = 2;
+    const STRETCH_EXTRACONDENSED = 3;
+    const STRETCH_CONDENSED = 4;
+    const STRETCH_SEMICONDENSED = 5;
+    const STRETCH_SEMIEXPANDED = 6;
+    const STRETCH_EXPANDED = 7;
+    const STRETCH_EXTRAEXPANDED = 8;
+    const STRETCH_ULTRAEXPANDED = 9;
+    const STRETCH_ANY = 10;
+    const ALIGN_UNDEFINED = 0;
+    const ALIGN_LEFT = 1;
+    const ALIGN_CENTER = 2;
+    const ALIGN_RIGHT = 3;
+    const DECORATION_NO = 1;
+    const DECORATION_UNDERLINE = 2;
+    const DECORATION_OVERLINE = 3;
+    const DECORATION_LINETROUGH = 4;
+    const DECORATION_LINETHROUGH = 4;
+    const NOISE_UNIFORM = 1;
+    const NOISE_GAUSSIAN = 2;
+    const NOISE_MULTIPLICATIVEGAUSSIAN = 3;
+    const NOISE_IMPULSE = 4;
+    const NOISE_LAPLACIAN = 5;
+    const NOISE_POISSON = 6;
+    const NOISE_RANDOM = 7;
+    const CHANNEL_UNDEFINED = 0;
+    const CHANNEL_RED = 1;
+    const CHANNEL_GRAY = 1;
+    const CHANNEL_CYAN = 1;
+    const CHANNEL_GREEN = 2;
+    const CHANNEL_MAGENTA = 2;
+    const CHANNEL_BLUE = 4;
+    const CHANNEL_YELLOW = 4;
+    const CHANNEL_ALPHA = 8;
+    const CHANNEL_OPACITY = 8;
+    const CHANNEL_MATTE = 8;
+    const CHANNEL_BLACK = 32;
+    const CHANNEL_INDEX = 32;
+    const CHANNEL_ALL = 134217727;
+    const CHANNEL_DEFAULT = 134217719;
+    const CHANNEL_RGBA = 15;
+    const CHANNEL_TRUEALPHA = 64;
+    const CHANNEL_RGBS = 128;
+    const CHANNEL_GRAY_CHANNELS = 128;
+    const CHANNEL_SYNC = 256;
+    const CHANNEL_COMPOSITES = 47;
+    const METRIC_UNDEFINED = 0;
+    const METRIC_ABSOLUTEERRORMETRIC = 1;
+    const METRIC_MEANABSOLUTEERROR = 2;
+    const METRIC_MEANERRORPERPIXELMETRIC = 3;
+    const METRIC_MEANSQUAREERROR = 4;
+    const METRIC_PEAKABSOLUTEERROR = 5;
+    const METRIC_PEAKSIGNALTONOISERATIO = 6;
+    const METRIC_ROOTMEANSQUAREDERROR = 7;
+    const METRIC_NORMALIZEDCROSSCORRELATIONERRORMETRIC = 8;
+    const METRIC_FUZZERROR = 9;
+    const METRIC_PERCEPTUALHASH_ERROR = 255;
+    const PIXEL_CHAR = 1;
+    const PIXEL_DOUBLE = 2;
+    const PIXEL_FLOAT = 3;
+    const PIXEL_INTEGER = 4;
+    const PIXEL_LONG = 5;
+    const PIXEL_QUANTUM = 6;
+    const PIXEL_SHORT = 7;
+    const EVALUATE_UNDEFINED = 0;
+    const EVALUATE_ADD = 1;
+    const EVALUATE_AND = 2;
+    const EVALUATE_DIVIDE = 3;
+    const EVALUATE_LEFTSHIFT = 4;
+    const EVALUATE_MAX = 5;
+    const EVALUATE_MIN = 6;
+    const EVALUATE_MULTIPLY = 7;
+    const EVALUATE_OR = 8;
+    const EVALUATE_RIGHTSHIFT = 9;
+    const EVALUATE_SET = 10;
+    const EVALUATE_SUBTRACT = 11;
+    const EVALUATE_XOR = 12;
+    const EVALUATE_POW = 13;
+    const EVALUATE_LOG = 14;
+    const EVALUATE_THRESHOLD = 15;
+    const EVALUATE_THRESHOLDBLACK = 16;
+    const EVALUATE_THRESHOLDWHITE = 17;
+    const EVALUATE_GAUSSIANNOISE = 18;
+    const EVALUATE_IMPULSENOISE = 19;
+    const EVALUATE_LAPLACIANNOISE = 20;
+    const EVALUATE_MULTIPLICATIVENOISE = 21;
+    const EVALUATE_POISSONNOISE = 22;
+    const EVALUATE_UNIFORMNOISE = 23;
+    const EVALUATE_COSINE = 24;
+    const EVALUATE_SINE = 25;
+    const EVALUATE_ADDMODULUS = 26;
+    const EVALUATE_MEAN = 27;
+    const EVALUATE_ABS = 28;
+    const EVALUATE_EXPONENTIAL = 29;
+    const EVALUATE_MEDIAN = 30;
+    const EVALUATE_SUM = 31;
+    const EVALUATE_ROOT_MEAN_SQUARE = 32;
+    const COLORSPACE_UNDEFINED = 0;
+    const COLORSPACE_RGB = 1;
+    const COLORSPACE_GRAY = 2;
+    const COLORSPACE_TRANSPARENT = 3;
+    const COLORSPACE_OHTA = 4;
+    const COLORSPACE_LAB = 5;
+    const COLORSPACE_XYZ = 6;
+    const COLORSPACE_YCBCR = 7;
+    const COLORSPACE_YCC = 8;
+    const COLORSPACE_YIQ = 9;
+    const COLORSPACE_YPBPR = 10;
+    const COLORSPACE_YUV = 11;
+    const COLORSPACE_CMYK = 12;
+    const COLORSPACE_SRGB = 13;
+    const COLORSPACE_HSB = 14;
+    const COLORSPACE_HSL = 15;
+    const COLORSPACE_HWB = 16;
+    const COLORSPACE_REC601LUMA = 17;
+    const COLORSPACE_REC709LUMA = 19;
+    const COLORSPACE_LOG = 21;
+    const COLORSPACE_CMY = 22;
+    const COLORSPACE_LUV = 23;
+    const COLORSPACE_HCL = 24;
+    const COLORSPACE_LCH = 25;
+    const COLORSPACE_LMS = 26;
+    const COLORSPACE_LCHAB = 27;
+    const COLORSPACE_LCHUV = 28;
+    const COLORSPACE_SCRGB = 29;
+    const COLORSPACE_HSI = 30;
+    const COLORSPACE_HSV = 31;
+    const COLORSPACE_HCLP = 32;
+    const COLORSPACE_YDBDR = 33;
+    const COLORSPACE_REC601YCBCR = 18;
+    const COLORSPACE_REC709YCBCR = 20;
+    const COLORSPACE_XYY = 34;
+    const VIRTUALPIXELMETHOD_UNDEFINED = 0;
+    const VIRTUALPIXELMETHOD_BACKGROUND = 1;
+    const VIRTUALPIXELMETHOD_CONSTANT = 2;
+    const VIRTUALPIXELMETHOD_EDGE = 4;
+    const VIRTUALPIXELMETHOD_MIRROR = 5;
+    const VIRTUALPIXELMETHOD_TILE = 7;
+    const VIRTUALPIXELMETHOD_TRANSPARENT = 8;
+    const VIRTUALPIXELMETHOD_MASK = 9;
+    const VIRTUALPIXELMETHOD_BLACK = 10;
+    const VIRTUALPIXELMETHOD_GRAY = 11;
+    const VIRTUALPIXELMETHOD_WHITE = 12;
+    const VIRTUALPIXELMETHOD_HORIZONTALTILE = 13;
+    const VIRTUALPIXELMETHOD_VERTICALTILE = 14;
+    const VIRTUALPIXELMETHOD_HORIZONTALTILEEDGE = 15;
+    const VIRTUALPIXELMETHOD_VERTICALTILEEDGE = 16;
+    const VIRTUALPIXELMETHOD_CHECKERTILE = 17;
+    const PREVIEW_UNDEFINED = 0;
+    const PREVIEW_ROTATE = 1;
+    const PREVIEW_SHEAR = 2;
+    const PREVIEW_ROLL = 3;
+    const PREVIEW_HUE = 4;
+    const PREVIEW_SATURATION = 5;
+    const PREVIEW_BRIGHTNESS = 6;
+    const PREVIEW_GAMMA = 7;
+    const PREVIEW_SPIFF = 8;
+    const PREVIEW_DULL = 9;
+    const PREVIEW_GRAYSCALE = 10;
+    const PREVIEW_QUANTIZE = 11;
+    const PREVIEW_DESPECKLE = 12;
+    const PREVIEW_REDUCENOISE = 13;
+    const PREVIEW_ADDNOISE = 14;
+    const PREVIEW_SHARPEN = 15;
+    const PREVIEW_BLUR = 16;
+    const PREVIEW_THRESHOLD = 17;
+    const PREVIEW_EDGEDETECT = 18;
+    const PREVIEW_SPREAD = 19;
+    const PREVIEW_SOLARIZE = 20;
+    const PREVIEW_SHADE = 21;
+    const PREVIEW_RAISE = 22;
+    const PREVIEW_SEGMENT = 23;
+    const PREVIEW_SWIRL = 24;
+    const PREVIEW_IMPLODE = 25;
+    const PREVIEW_WAVE = 26;
+    const PREVIEW_OILPAINT = 27;
+    const PREVIEW_CHARCOALDRAWING = 28;
+    const PREVIEW_JPEG = 29;
+    const RENDERINGINTENT_UNDEFINED = 0;
+    const RENDERINGINTENT_SATURATION = 1;
+    const RENDERINGINTENT_PERCEPTUAL = 2;
+    const RENDERINGINTENT_ABSOLUTE = 3;
+    const RENDERINGINTENT_RELATIVE = 4;
+    const INTERLACE_UNDEFINED = 0;
+    const INTERLACE_NO = 1;
+    const INTERLACE_LINE = 2;
+    const INTERLACE_PLANE = 3;
+    const INTERLACE_PARTITION = 4;
+    const INTERLACE_GIF = 5;
+    const INTERLACE_JPEG = 6;
+    const INTERLACE_PNG = 7;
+    const FILLRULE_UNDEFINED = 0;
+    const FILLRULE_EVENODD = 1;
+    const FILLRULE_NONZERO = 2;
+    const PATHUNITS_UNDEFINED = 0;
+    const PATHUNITS_USERSPACE = 1;
+    const PATHUNITS_USERSPACEONUSE = 2;
+    const PATHUNITS_OBJECTBOUNDINGBOX = 3;
+    const LINECAP_UNDEFINED = 0;
+    const LINECAP_BUTT = 1;
+    const LINECAP_ROUND = 2;
+    const LINECAP_SQUARE = 3;
+    const LINEJOIN_UNDEFINED = 0;
+    const LINEJOIN_MITER = 1;
+    const LINEJOIN_ROUND = 2;
+    const LINEJOIN_BEVEL = 3;
+    const RESOURCETYPE_UNDEFINED = 0;
+    const RESOURCETYPE_AREA = 1;
+    const RESOURCETYPE_DISK = 2;
+    const RESOURCETYPE_FILE = 3;
+    const RESOURCETYPE_MAP = 4;
+    const RESOURCETYPE_MEMORY = 5;
+    const RESOURCETYPE_TIME = 7;
+    const RESOURCETYPE_THROTTLE = 8;
+    const RESOURCETYPE_THREAD = 6;
+    const RESOURCETYPE_WIDTH = 9;
+    const RESOURCETYPE_HEIGHT = 10;
+    const DISPOSE_UNRECOGNIZED = 0;
+    const DISPOSE_UNDEFINED = 0;
+    const DISPOSE_NONE = 1;
+    const DISPOSE_BACKGROUND = 2;
+    const DISPOSE_PREVIOUS = 3;
+    const INTERPOLATE_UNDEFINED = 0;
+    const INTERPOLATE_AVERAGE = 1;
+    const INTERPOLATE_BICUBIC = 2;
+    const INTERPOLATE_BILINEAR = 3;
+    const INTERPOLATE_FILTER = 4;
+    const INTERPOLATE_INTEGER = 5;
+    const INTERPOLATE_MESH = 6;
+    const INTERPOLATE_NEARESTNEIGHBOR = 7;
+    const INTERPOLATE_SPLINE = 8;
+    const INTERPOLATE_AVERAGE_9 = 9;
+    const INTERPOLATE_AVERAGE_16 = 10;
+    const INTERPOLATE_BLEND = 11;
+    const INTERPOLATE_BACKGROUND_COLOR = 12;
+    const INTERPOLATE_CATROM = 13;
+    const LAYERMETHOD_UNDEFINED = 0;
+    const LAYERMETHOD_COALESCE = 1;
+    const LAYERMETHOD_COMPAREANY = 2;
+    const LAYERMETHOD_COMPARECLEAR = 3;
+    const LAYERMETHOD_COMPAREOVERLAY = 4;
+    const LAYERMETHOD_DISPOSE = 5;
+    const LAYERMETHOD_OPTIMIZE = 6;
+    const LAYERMETHOD_OPTIMIZEPLUS = 8;
+    const LAYERMETHOD_OPTIMIZETRANS = 9;
+    const LAYERMETHOD_COMPOSITE = 12;
+    const LAYERMETHOD_OPTIMIZEIMAGE = 7;
+    const LAYERMETHOD_REMOVEDUPS = 10;
+    const LAYERMETHOD_REMOVEZERO = 11;
+    const LAYERMETHOD_TRIMBOUNDS = 16;
+    const ORIENTATION_UNDEFINED = 0;
+    const ORIENTATION_TOPLEFT = 1;
+    const ORIENTATION_TOPRIGHT = 2;
+    const ORIENTATION_BOTTOMRIGHT = 3;
+    const ORIENTATION_BOTTOMLEFT = 4;
+    const ORIENTATION_LEFTTOP = 5;
+    const ORIENTATION_RIGHTTOP = 6;
+    const ORIENTATION_RIGHTBOTTOM = 7;
+    const ORIENTATION_LEFTBOTTOM = 8;
+    const DISTORTION_UNDEFINED = 0;
+    const DISTORTION_AFFINE = 1;
+    const DISTORTION_AFFINEPROJECTION = 2;
+    const DISTORTION_ARC = 9;
+    const DISTORTION_BILINEAR = 6;
+    const DISTORTION_PERSPECTIVE = 4;
+    const DISTORTION_PERSPECTIVEPROJECTION = 5;
+    const DISTORTION_SCALEROTATETRANSLATE = 3;
+    const DISTORTION_POLYNOMIAL = 8;
+    const DISTORTION_POLAR = 10;
+    const DISTORTION_DEPOLAR = 11;
+    const DISTORTION_BARREL = 14;
+    const DISTORTION_SHEPARDS = 16;
+    const DISTORTION_SENTINEL = 18;
+    const DISTORTION_BARRELINVERSE = 15;
+    const DISTORTION_BILINEARFORWARD = 6;
+    const DISTORTION_BILINEARREVERSE = 7;
+    const DISTORTION_RESIZE = 17;
+    const DISTORTION_CYLINDER2PLANE = 12;
+    const DISTORTION_PLANE2CYLINDER = 13;
+    const LAYERMETHOD_MERGE = 13;
+    const LAYERMETHOD_FLATTEN = 14;
+    const LAYERMETHOD_MOSAIC = 15;
+    const ALPHACHANNEL_ACTIVATE = 1;
+    const ALPHACHANNEL_RESET = 7;
+    const ALPHACHANNEL_SET = 8;
+    const ALPHACHANNEL_UNDEFINED = 0;
+    const ALPHACHANNEL_COPY = 3;
+    const ALPHACHANNEL_DEACTIVATE = 4;
+    const ALPHACHANNEL_EXTRACT = 5;
+    const ALPHACHANNEL_OPAQUE = 6;
+    const ALPHACHANNEL_SHAPE = 9;
+    const ALPHACHANNEL_TRANSPARENT = 10;
+    const ALPHACHANNEL_ASSOCIATE = 13;
+    const ALPHACHANNEL_DISSOCIATE = 14;
+    const SPARSECOLORMETHOD_UNDEFINED = 0;
+    const SPARSECOLORMETHOD_BARYCENTRIC = 1;
+    const SPARSECOLORMETHOD_BILINEAR = 7;
+    const SPARSECOLORMETHOD_POLYNOMIAL = 8;
+    const SPARSECOLORMETHOD_SPEPARDS = 16;
+    const SPARSECOLORMETHOD_VORONOI = 18;
+    const SPARSECOLORMETHOD_INVERSE = 19;
+    const SPARSECOLORMETHOD_MANHATTAN = 20;
+    const DITHERMETHOD_UNDEFINED = 0;
+    const DITHERMETHOD_NO = 1;
+    const DITHERMETHOD_RIEMERSMA = 2;
+    const DITHERMETHOD_FLOYDSTEINBERG = 3;
+    const FUNCTION_UNDEFINED = 0;
+    const FUNCTION_POLYNOMIAL = 1;
+    const FUNCTION_SINUSOID = 2;
+    const ALPHACHANNEL_BACKGROUND = 2;
+    const FUNCTION_ARCSIN = 3;
+    const FUNCTION_ARCTAN = 4;
+    const ALPHACHANNEL_FLATTEN = 11;
+    const ALPHACHANNEL_REMOVE = 12;
+    const STATISTIC_GRADIENT = 1;
+    const STATISTIC_MAXIMUM = 2;
+    const STATISTIC_MEAN = 3;
+    const STATISTIC_MEDIAN = 4;
+    const STATISTIC_MINIMUM = 5;
+    const STATISTIC_MODE = 6;
+    const STATISTIC_NONPEAK = 7;
+    const STATISTIC_STANDARD_DEVIATION = 8;
+    const STATISTIC_ROOT_MEAN_SQUARE = 9;
+    const MORPHOLOGY_CONVOLVE = 1;
+    const MORPHOLOGY_CORRELATE = 2;
+    const MORPHOLOGY_ERODE = 3;
+    const MORPHOLOGY_DILATE = 4;
+    const MORPHOLOGY_ERODE_INTENSITY = 5;
+    const MORPHOLOGY_DILATE_INTENSITY = 6;
+    const MORPHOLOGY_DISTANCE = 7;
+    const MORPHOLOGY_OPEN = 8;
+    const MORPHOLOGY_CLOSE = 9;
+    const MORPHOLOGY_OPEN_INTENSITY = 10;
+    const MORPHOLOGY_CLOSE_INTENSITY = 11;
+    const MORPHOLOGY_SMOOTH = 12;
+    const MORPHOLOGY_EDGE_IN = 13;
+    const MORPHOLOGY_EDGE_OUT = 14;
+    const MORPHOLOGY_EDGE = 15;
+    const MORPHOLOGY_TOP_HAT = 16;
+    const MORPHOLOGY_BOTTOM_HAT = 17;
+    const MORPHOLOGY_HIT_AND_MISS = 18;
+    const MORPHOLOGY_THINNING = 19;
+    const MORPHOLOGY_THICKEN = 20;
+    const MORPHOLOGY_VORONOI = 21;
+    const MORPHOLOGY_ITERATIVE = 22;
+    const KERNEL_UNITY = 1;
+    const KERNEL_GAUSSIAN = 2;
+    const KERNEL_DIFFERENCE_OF_GAUSSIANS = 3;
+    const KERNEL_LAPLACIAN_OF_GAUSSIANS = 4;
+    const KERNEL_BLUR = 5;
+    const KERNEL_COMET = 6;
+    const KERNEL_LAPLACIAN = 7;
+    const KERNEL_SOBEL = 8;
+    const KERNEL_FREI_CHEN = 9;
+    const KERNEL_ROBERTS = 10;
+    const KERNEL_PREWITT = 11;
+    const KERNEL_COMPASS = 12;
+    const KERNEL_KIRSCH = 13;
+    const KERNEL_DIAMOND = 14;
+    const KERNEL_SQUARE = 15;
+    const KERNEL_RECTANGLE = 16;
+    const KERNEL_OCTAGON = 17;
+    const KERNEL_DISK = 18;
+    const KERNEL_PLUS = 19;
+    const KERNEL_CROSS = 20;
+    const KERNEL_RING = 21;
+    const KERNEL_PEAKS = 22;
+    const KERNEL_EDGES = 23;
+    const KERNEL_CORNERS = 24;
+    const KERNEL_DIAGONALS = 25;
+    const KERNEL_LINE_ENDS = 26;
+    const KERNEL_LINE_JUNCTIONS = 27;
+    const KERNEL_RIDGES = 28;
+    const KERNEL_CONVEX_HULL = 29;
+    const KERNEL_THIN_SE = 30;
+    const KERNEL_SKELETON = 31;
+    const KERNEL_CHEBYSHEV = 32;
+    const KERNEL_MANHATTAN = 33;
+    const KERNEL_OCTAGONAL = 34;
+    const KERNEL_EUCLIDEAN = 35;
+    const KERNEL_USER_DEFINED = 36;
+    const KERNEL_BINOMIAL = 37;
+    const DIRECTION_LEFT_TO_RIGHT = 2;
+    const DIRECTION_RIGHT_TO_LEFT = 1;
+    const NORMALIZE_KERNEL_NONE = 0;
+    const NORMALIZE_KERNEL_VALUE = 8192;
+    const NORMALIZE_KERNEL_CORRELATE = 65536;
+    const NORMALIZE_KERNEL_PERCENT = 4096;
+
+    // methods
+    public function optimizeimagelayers() {}
+    public function compareimagelayers($LAYER) {}
+    public function pingimageblob($imageContents) {}
+    public function pingimagefile($fp) {}
+    public function transposeimage() {}
+    public function transverseimage() {}
+    public function trimimage($fuzz) {}
+    public function waveimage($amplitude, $waveLenght) {}
+    public function vignetteimage($blackPoint, $whitePoint, $x, $y) {}
+    public function uniqueimagecolors() {}
+    public function getimagematte() {}
+    public function setimagematte($enable) {}
+    public function adaptiveresizeimage($columns, $rows, $bestfit = null, $legacy = null) {}
+    public function sketchimage($radius, $sigma, $angle) {}
+    public function shadeimage($gray, $azimuth, $elevation) {}
+    public function getsizeoffset() {}
+    public function setsizeoffset($columns, $rows, $offset) {}
+    public function adaptiveblurimage($radius, $sigma, $CHANNEL = null) {}
+    public function contraststretchimage($blackPoint, $whitePoint, $CHANNEL = null) {}
+    public function adaptivesharpenimage($radius, $sigma, $CHANNEL = null) {}
+    public function randomthresholdimage($low, $high, $CHANNELTYPE = null) {}
+    public function roundcornersimage($xRounding, $yRounding, $strokeWidth = null, $displace = null, $sizeCorrection = null) {}
+    public function roundcorners($xRounding, $yRounding, $strokeWidth = null, $displace = null, $sizeCorrection = null) {}
+    public function setiteratorindex($index) {}
+    public function getiteratorindex() {}
+    public function transformimage($crop, $geometry) {}
+    public function setimageopacity($opacity) {}
+    public function orderedposterizeimage($threshold_map, $CHANNEL = null) {}
+    public function polaroidimage(\ImagickDraw $ImagickDraw, $angle) {}
+    public function getimageproperty($name) {}
+    public function setimageproperty($name, $value) {}
+    public function deleteimageproperty($name) {}
+    public function identifyformat($embedText) {}
+    public function setimageinterpolatemethod($INTERPOLATE) {}
+    public function getimageinterpolatemethod() {}
+    public function linearstretchimage($blackPoint, $whitePoint) {}
+    public function getimagelength() {}
+    public function extentimage($width, $height, $x, $y) {}
+    public function getimageorientation() {}
+    public function setimageorientation($ORIENTATION) {}
+    public function paintfloodfillimage($CHANNEL, $fill, $fuzz, $bordercolor, $x, $y) {}
+    public function clutimage(\Imagick $Imagick, $CHANNELTYPE = null) {}
+    public function getimageproperties($pattern = null, $values = null) {}
+    public function getimageprofiles($pattern = null, $values = null) {}
+    public function distortimage($method, $arguments, $bestfit) {}
+    public function writeimagefile($handle, $format = null) {}
+    public function writeimagesfile($handle, $format = null) {}
+    public function resetimagepage($page) {}
+    public function setimageclipmask(\Imagick $Imagick) {}
+    public function getimageclipmask() {}
+    public function animateimages($server_name) {}
+    public function recolorimage($matrix) {}
+    public function setfont($font) {}
+    public function getfont() {}
+    public function setpointsize($pointsize) {}
+    public function getpointsize() {}
+    public function mergeimagelayers($LAYERMETHOD) {}
+    public function setimagealphachannel($ALPHACHANNELTYPE) {}
+    public function floodfillpaintimage($fill, $fuzz, $bordercolor, $x, $y, $invert, $CHANNEL = null) {}
+    public function opaquepaintimage($target, $fill, $fuzz, $invert, $CHANNEL = null) {}
+    public function transparentpaintimage($target, $alpha, $fuzz, $invert) {}
+    public function liquidrescaleimage($columns, $rows, $delta_x, $rigidity) {}
+    public function encipherimage($passphrase) {}
+    public function decipherimage($passphrase) {}
+    public function setgravity($GRAVITY) {}
+    public function getgravity() {}
+    public function getimagechannelrange($CHANNEL) {}
+    public function getimagealphachannel() {}
+    public function getimagechanneldistortions(\Imagick $Imagick, $METRICTYPE = null, $CHANNEL = null) {}
+    public function setimagegravity($GRAVITY) {}
+    public function getimagegravity() {}
+    public function importimagepixels($x, $y, $width, $height, $map, $storage, $PIXEL) {}
+    public function deskewimage($threshold) {}
+    public function segmentimage($COLORSPACE, $cluster_threshold, $smooth_threshold, $verbose = null) {}
+    public function sparsecolorimage($SPARSE_METHOD, $arguments, $CHANNEL = null) {}
+    public function remapimage(\Imagick $Imagick, $DITHER) {}
+    public function exportimagepixels($x, $y, $width, $height, $map, $STORAGE) {}
+    public function getimagechannelkurtosis($CHANNEL = null) {}
+    public function functionimage($FUNCTION, $arguments) {}
+    public function transformimagecolorspace($COLORSPACE) {}
+    public function haldclutimage(\Imagick $Imagick, $CHANNEL = null) {}
+    public function autolevelimage($CHANNEL = null) {}
+    public function blueshiftimage($factor = null) {}
+    public function getimageartifact($artifact) {}
+    public function setimageartifact($artifact, $value) {}
+    public function deleteimageartifact($artifact) {}
+    public function getcolorspace() {}
+    public function setcolorspace($COLORSPACE) {}
+    public function clampimage($CHANNEL = null) {}
+    public function smushimages($stack, $offset) {}
+    public function __construct($files = null) {}
+    public function __toString() {}
+    public function count($mode = null) {}
+    public function getpixeliterator() {}
+    public function getpixelregioniterator($x, $y, $columns, $rows, $modify) {}
+    public function readimage($filename) {}
+    public function readimages($filenames) {}
+    public function readimageblob($imageContents, $filename = null) {}
+    public function setimageformat($imageFormat) {}
+    public function scaleimage($width, $height, $bestfit = null, $legacy = null) {}
+    public function writeimage($filename = null) {}
+    public function writeimages($filename, $adjoin) {}
+    public function blurimage($radius, $sigma, $CHANNELTYPE = null) {}
+    public function thumbnailimage($width, $height, $bestfit = null, $fill = null, $legacy = null) {}
+    public function cropthumbnailimage($width, $height, $legacy = null) {}
+    public function getimagefilename() {}
+    public function setimagefilename($filename) {}
+    public function getimageformat() {}
+    public function getimagemimetype() {}
+    public function removeimage() {}
+    public function destroy() {}
+    public function clear() {}
+    public function clone() {}
+    public function getimagesize() {}
+    public function getimageblob() {}
+    public function getimagesblob() {}
+    public function setfirstiterator() {}
+    public function setlastiterator() {}
+    public function resetiterator() {}
+    public function previousimage() {}
+    public function nextimage() {}
+    public function haspreviousimage() {}
+    public function hasnextimage() {}
+    public function setimageindex($index) {}
+    public function getimageindex() {}
+    public function commentimage($comment) {}
+    public function cropimage($width, $height, $x, $y) {}
+    public function labelimage($label) {}
+    public function getimagegeometry() {}
+    public function drawimage(\ImagickDraw $ImagickDraw) {}
+    public function setimagecompressionquality($quality) {}
+    public function getimagecompressionquality() {}
+    public function setimagecompression($COMPRESSION) {}
+    public function getimagecompression() {}
+    public function annotateimage(\ImagickDraw $ImagickDraw, $x, $y, $angle, $text) {}
+    public function compositeimage(\Imagick $Imagick, $COMPOSITE, $x, $y, $CHANNELTYPE = null) {}
+    public function modulateimage($brightness, $saturation, $hue) {}
+    public function getimagecolors() {}
+    public function montageimage(\ImagickDraw $ImagickDraw, $tileGeometry, $thumbnailGeometry, $MONTAGEMODE, $frame) {}
+    public function identifyimage($appendRawOutput = null) {}
+    public function thresholdimage($threshold, $CHANNELTYPE = null) {}
+    public function adaptivethresholdimage($width, $height, $offset) {}
+    public function blackthresholdimage($color) {}
+    public function whitethresholdimage($color) {}
+    public function appendimages($stack) {}
+    public function charcoalimage($radius, $sigma) {}
+    public function normalizeimage($CHANNEL = null) {}
+    public function oilpaintimage($radius) {}
+    public function posterizeimage($levels, $dither) {}
+    public function radialblurimage($angle, $CHANNEL = null) {}
+    public function raiseimage($width, $height, $x, $y, $raise) {}
+    public function resampleimage($xResolution, $yResolution, $FILTER, $blur) {}
+    public function resizeimage($x, $y, $filter = null, $blur = null, $bestfit = null, $legacy = null) {}
+    public function rollimage($x, $y) {}
+    public function rotateimage($color, $degrees) {}
+    public function sampleimage($columns, $rows) {}
+    public function solarizeimage($threshold) {}
+    public function shadowimage($opacity, $sigma, $x, $y) {}
+    public function setimageattribute($key, $value) {}
+    public function setimagebackgroundcolor($color) {}
+    public function setimagecompose($COMPOSITE) {}
+    public function setimagedelay($delay) {}
+    public function setimagedepth($depth) {}
+    public function setimagegamma($gamma) {}
+    public function setimageiterations($iterations) {}
+    public function setimagemattecolor($color) {}
+    public function setimagepage($width, $height, $x, $y) {}
+    public function setimageprogressmonitor($filename) {}
+    public function setprogressmonitor($callback) {}
+    public function setimageresolution($xResolution, $yResolution) {}
+    public function setimagescene($scene) {}
+    public function setimagetickspersecond($ticksPerSecond) {}
+    public function setimagetype($IMGTYPE) {}
+    public function setimageunits($RESOLUTION) {}
+    public function sharpenimage($radius, $sigma, $CHANNEL = null) {}
+    public function shaveimage($columns, $rows) {}
+    public function shearimage($color, $xShear, $yShear) {}
+    public function spliceimage($width, $height, $x, $y) {}
+    public function pingimage($filename) {}
+    public function readimagefile($fp) {}
+    public function displayimage($serverName) {}
+    public function displayimages($serverName) {}
+    public function spreadimage($radius) {}
+    public function swirlimage($degrees) {}
+    public function stripimage() {}
+    public static function queryformats($pattern) {}
+    public static function queryfonts($pattern) {}
+    public function queryfontmetrics(\ImagickDraw $ImagickDraw, $text, $multiline = null) {}
+    public function steganoimage(\Imagick $Imagick, $offset) {}
+    public function addnoiseimage($NOISE, $CHANNEL = null) {}
+    public function motionblurimage($radius, $sigma, $angle, $CHANNEL = null) {}
+    public function mosaicimages() {}
+    public function morphimages($frames) {}
+    public function minifyimage() {}
+    public function affinetransformimage(\ImagickDraw $ImagickDraw) {}
+    public function averageimages() {}
+    public function borderimage($color, $width, $height) {}
+    public static function calculatecrop($orig_width, $orig_height, $desired_width, $desired_height, $legacy = null) {}
+    public function chopimage($width, $height, $x, $y) {}
+    public function clipimage() {}
+    public function clippathimage($pathname, $inside) {}
+    public function clipimagepath($pathname, $inside) {}
+    public function coalesceimages() {}
+    public function colorfloodfillimage($fill_color, $fuzz, $border_color, $y, $x) {}
+    public function colorizeimage($colorize_color, $opacity, $legacy = null) {}
+    public function compareimagechannels(\Imagick $Imagick, $CHANNEL, $METRIC) {}
+    public function compareimages(\Imagick $Imagick, $METRIC) {}
+    public function contrastimage($sharpen) {}
+    public function combineimages() {}
+    public function convolveimage($kernel, $CHANNEL = null) {}
+    public function cyclecolormapimage($displace) {}
+    public function deconstructimages() {}
+    public function despeckleimage() {}
+    public function edgeimage($radius) {}
+    public function embossimage($radius, $sigma) {}
+    public function enhanceimage() {}
+    public function equalizeimage() {}
+    public function evaluateimage($EVALUATE, $constant, $CHANNEL = null) {}
+    public function evaluateimages($EVALUATE) {}
+    public function flattenimages() {}
+    public function flipimage() {}
+    public function flopimage() {}
+    public function forwardfouriertransformimage($magnitude) {}
+    public function frameimage($color, $width, $height, $innerBevel, $outerBevel) {}
+    public function fximage($expression, $CHANNEL = null) {}
+    public function gammaimage($gamma, $CHANNEL = null) {}
+    public function gaussianblurimage($radius, $sigma, $CHANNEL = null) {}
+    public function getimageattribute($key) {}
+    public function getimagebackgroundcolor() {}
+    public function getimageblueprimary() {}
+    public function getimagebordercolor() {}
+    public function getimagechanneldepth($CHANNEL) {}
+    public function getimagechanneldistortion(\Imagick $Imagick, $CHANNEL, $METRIC) {}
+    public function getimagechannelextrema($CHANNEL) {}
+    public function getimagechannelmean($CHANNEL) {}
+    public function getimagechannelstatistics() {}
+    public function getimagecolormapcolor($index) {}
+    public function getimagecolorspace() {}
+    public function getimagecompose() {}
+    public function getimagedelay() {}
+    public function getimagedepth() {}
+    public function getimagedistortion(\Imagick $Imagick, $METRIC) {}
+    public function getimageextrema() {}
+    public function getimagedispose() {}
+    public function getimagegamma() {}
+    public function getimagegreenprimary() {}
+    public function getimageheight() {}
+    public function getimagehistogram() {}
+    public function getimageinterlacescheme() {}
+    public function getimageiterations() {}
+    public function getimagemattecolor() {}
+    public function getimagepage() {}
+    public function getimagepixelcolor($x, $y) {}
+    public function getimageprofile($name) {}
+    public function getimageredprimary() {}
+    public function getimagerenderingintent() {}
+    public function getimageresolution() {}
+    public function getimagescene() {}
+    public function getimagesignature() {}
+    public function getimagetickspersecond() {}
+    public function getimagetype() {}
+    public function getimageunits() {}
+    public function getimagevirtualpixelmethod() {}
+    public function getimagewhitepoint() {}
+    public function getimagewidth() {}
+    public function getnumberimages() {}
+    public function getimagetotalinkdensity() {}
+    public function getimageregion($width, $height, $x, $y) {}
+    public function implodeimage($radius) {}
+    public function inversefouriertransformimage($complement, $magnitude) {}
+    public function levelimage($blackPoint, $gamma, $whitePoint, $CHANNEL = null) {}
+    public function magnifyimage() {}
+    public function mapimage(\Imagick $Imagick, $dither) {}
+    public function mattefloodfillimage($alpha, $fuzz, $color, $x, $y) {}
+    public function medianfilterimage($radius) {}
+    public function negateimage($gray, $CHANNEL = null) {}
+    public function paintopaqueimage($target_color, $fill_color, $fuzz, $CHANNEL = null) {}
+    public function painttransparentimage($target_color, $alpha, $fuzz) {}
+    public function previewimages($PREVIEW) {}
+    public function profileimage($name, $profile) {}
+    public function quantizeimage($numColors, $COLORSPACE, $treeDepth, $dither, $measureError) {}
+    public function quantizeimages($numColors, $COLORSPACE, $treeDepth, $dither, $measureError) {}
+    public function reducenoiseimage($radius) {}
+    public function removeimageprofile($name) {}
+    public function separateimagechannel($CHANNEL) {}
+    public function sepiatoneimage($threshold) {}
+    public function setimagebias($bias) {}
+    public function setimagebiasquantum($bias) {}
+    public function setimageblueprimary($x, $y) {}
+    public function setimagebordercolor($color) {}
+    public function setimagechanneldepth($CHANNEL, $depth) {}
+    public function setimagecolormapcolor($index, $color) {}
+    public function setimagecolorspace($COLORSPACE) {}
+    public function setimagedispose($DISPOSETYPE) {}
+    public function setimageextent($columns, $rows) {}
+    public function setimagegreenprimary($x, $y) {}
+    public function setimageinterlacescheme($INTERLACE) {}
+    public function setimageprofile($name, $profile) {}
+    public function setimageredprimary($x, $y) {}
+    public function setimagerenderingintent($RENDERINGINTENT) {}
+    public function setimagevirtualpixelmethod($VIRTUALPIXELMETHOD) {}
+    public function setimagewhitepoint($x, $y) {}
+    public function sigmoidalcontrastimage($sharpen, $contrast, $midpoint, $CHANNEL = null) {}
+    public function stereoimage(\Imagick $Imagick) {}
+    public function textureimage(\Imagick $Imagick) {}
+    public function tintimage($tint_color, $opacity, $legacy = null) {}
+    public function unsharpmaskimage($radius, $sigma, $amount, $threshold, $CHANNEL = null) {}
+    public function getimage() {}
+    public function addimage(\Imagick $Imagick) {}
+    public function setimage(\Imagick $Imagick) {}
+    public function newimage($columns, $rows, $background_color, $format = null) {}
+    public function newpseudoimage($columns, $rows, $pseudoString) {}
+    public function getcompression() {}
+    public function getcompressionquality() {}
+    public static function getcopyright() {}
+    public static function getconfigureoptions($pattern = null) {}
+    public static function getfeatures() {}
+    public function getfilename() {}
+    public function getformat() {}
+    public static function gethomeurl() {}
+    public function getinterlacescheme() {}
+    public function getoption($key) {}
+    public static function getpackagename() {}
+    public function getpage() {}
+    public static function getquantum() {}
+    public static function gethdrienabled() {}
+    public static function getquantumdepth() {}
+    public static function getquantumrange() {}
+    public static function getreleasedate() {}
+    public static function getresource($resource_type) {}
+    public static function getresourcelimit($resource_type) {}
+    public function getsamplingfactors() {}
+    public function getsize() {}
+    public static function getversion() {}
+    public function setbackgroundcolor($color) {}
+    public function setcompression($compression) {}
+    public function setcompressionquality($compressionquality) {}
+    public function setfilename($filename) {}
+    public function setformat($format) {}
+    public function setinterlacescheme($INTERLACE) {}
+    public function setoption($key, $value) {}
+    public function setpage($width, $height, $x, $y) {}
+    public static function setresourcelimit($RESOURCETYPE, $limit) {}
+    public function setresolution($xResolution, $yResolution) {}
+    public function setsamplingfactors($factors) {}
+    public function setsize($columns, $rows) {}
+    public function settype($IMGTYPE) {}
+    public function key() {}
+    public function next() {}
+    public function rewind() {}
+    public function valid() {}
+    public function current() {}
+    public function brightnesscontrastimage($brightness, $contrast, $CHANNEL = null) {}
+    public function colormatriximage($color_matrix) {}
+    public function selectiveblurimage($radius, $sigma, $threshold, $CHANNEL) {}
+    public function rotationalblurimage($angle, $CHANNEL = null) {}
+    public function statisticimage($type, $width, $height, $CHANNEL = null) {}
+    public function subimagematch(\Imagick $Imagick, &$offset = null, &$similarity = null, &$similarity_threshold = null, &$metric = null) {}
+    public function similarityimage(\Imagick $Imagick, &$offset = null, &$similarity = null, &$similarity_threshold = null, &$metric = null) {}
+    public static function setregistry($key, $value) {}
+    public static function getregistry($key) {}
+    public static function listregistry() {}
+    public function morphology($morphologyMethod, $iterations, \ImagickKernel $ImagickKernel, $CHANNEL = null) {}
+    public function filter(\ImagickKernel $ImagickKernel, $CHANNEL = null) {}
+    public function setantialias($antialias) {}
+    public function getantialias() {}
+    public function colordecisionlistimage($antialias) {}
+    public function autogammaimage($CHANNEL) {}
+    public function autoorient() {}
+    public function compositeimagegravity(\Imagick $Imagick, $COMPOSITE, $GRAVITY) {}
+    public function localcontrastimage($radius, $strength) {}
+}
+
+class ImagickDraw {
+
+    // methods
+    public function resetvectorgraphics() {}
+    public function gettextkerning() {}
+    public function settextkerning($kerning) {}
+    public function gettextinterwordspacing() {}
+    public function settextinterwordspacing($spacing) {}
+    public function gettextinterlinespacing() {}
+    public function settextinterlinespacing($spacing) {}
+    public function __construct() {}
+    public function setfillcolor($color) {}
+    public function setfillalpha($alpha) {}
+    public function setresolution($x_resolution, $y_resolution) {}
+    public function setstrokecolor($color) {}
+    public function setstrokealpha($alpha) {}
+    public function setstrokewidth($width) {}
+    public function clear() {}
+    public function circle($ox, $oy, $px, $py) {}
+    public function annotation($x, $y, $text) {}
+    public function settextantialias($antialias) {}
+    public function settextencoding($encoding) {}
+    public function setfont($font) {}
+    public function setfontfamily($fontfamily) {}
+    public function setfontsize($pointsize) {}
+    public function setfontstyle($STYLE) {}
+    public function setfontweight($weight) {}
+    public function getfont() {}
+    public function getfontfamily() {}
+    public function getfontsize() {}
+    public function getfontstyle() {}
+    public function getfontweight() {}
+    public function destroy() {}
+    public function rectangle($x1, $y1, $x2, $y2) {}
+    public function roundrectangle($x1, $y1, $x2, $y2, $rx, $ry) {}
+    public function ellipse($ox, $oy, $px, $py, $start, $end) {}
+    public function skewx($degrees) {}
+    public function skewy($degrees) {}
+    public function translate($x, $y) {}
+    public function line($sx, $sy, $ex, $ey) {}
+    public function arc($sx, $sy, $ex, $ey, $sd, $ed) {}
+    public function matte($x, $y, $METHOD) {}
+    public function polygon($coordinates) {}
+    public function point($x, $y) {}
+    public function gettextdecoration() {}
+    public function gettextencoding() {}
+    public function getfontstretch() {}
+    public function setfontstretch($STRETCH) {}
+    public function setstrokeantialias($antialias) {}
+    public function settextalignment($ALIGN) {}
+    public function settextdecoration($DECORATION) {}
+    public function settextundercolor($color) {}
+    public function setviewbox($sx, $sy, $ex, $ey) {}
+    public function clone() {}
+    public function affine($affineMatrix) {}
+    public function bezier($coordinateArray) {}
+    public function composite($COMPOSE, $x, $y, $width, $height, \Imagick $Imagick) {}
+    public function color($x, $y, $PAINTMETHOD) {}
+    public function comment($comment) {}
+    public function getclippath() {}
+    public function getcliprule() {}
+    public function getclipunits() {}
+    public function getfillcolor() {}
+    public function getfillopacity() {}
+    public function getfillrule() {}
+    public function getgravity() {}
+    public function getstrokeantialias() {}
+    public function getstrokecolor() {}
+    public function getstrokedasharray() {}
+    public function getstrokedashoffset() {}
+    public function getstrokelinecap() {}
+    public function getstrokelinejoin() {}
+    public function getstrokemiterlimit() {}
+    public function getstrokeopacity() {}
+    public function getstrokewidth() {}
+    public function gettextalignment() {}
+    public function gettextantialias() {}
+    public function getvectorgraphics() {}
+    public function gettextundercolor() {}
+    public function pathclose() {}
+    public function pathcurvetoabsolute($x1, $y1, $x2, $y2, $x, $y) {}
+    public function pathcurvetorelative($x1, $y1, $x2, $y2, $x, $y) {}
+    public function pathcurvetoquadraticbezierabsolute($x1, $y1, $x, $y) {}
+    public function pathcurvetoquadraticbezierrelative($x1, $y1, $x, $y) {}
+    public function pathcurvetoquadraticbeziersmoothabsolute($x, $y) {}
+    public function pathcurvetoquadraticbeziersmoothrelative($x, $y) {}
+    public function pathcurvetosmoothabsolute($x1, $y1, $x, $y) {}
+    public function pathcurvetosmoothrelative($x1, $y1, $x, $y) {}
+    public function pathellipticarcabsolute($rx, $ry, $xAxisRotation, $largeArc, $sweep, $x, $y) {}
+    public function pathellipticarcrelative($rx, $ry, $xAxisRotation, $largeArc, $sweep, $x, $y) {}
+    public function pathfinish() {}
+    public function pathlinetoabsolute($x, $y) {}
+    public function pathlinetorelative($x, $y) {}
+    public function pathlinetohorizontalabsolute($y) {}
+    public function pathlinetohorizontalrelative($x) {}
+    public function pathlinetoverticalabsolute($y) {}
+    public function pathlinetoverticalrelative($x) {}
+    public function pathmovetoabsolute($x, $y) {}
+    public function pathmovetorelative($x, $y) {}
+    public function pathstart() {}
+    public function polyline($coordinateArray) {}
+    public function popclippath() {}
+    public function popdefs() {}
+    public function poppattern() {}
+    public function pushclippath($clipMask) {}
+    public function pushdefs() {}
+    public function pushpattern($pattern_id, $x, $y, $width, $height) {}
+    public function render() {}
+    public function rotate($degrees) {}
+    public function scale($x, $y) {}
+    public function setclippath($clipMask) {}
+    public function setcliprule($FILLRULE) {}
+    public function setclipunits($PATHUNITS) {}
+    public function setfillopacity($fillOpacity) {}
+    public function setfillpatternurl($url) {}
+    public function setfillrule($FILLRULE) {}
+    public function setgravity($GRAVITY) {}
+    public function setstrokepatternurl($url) {}
+    public function setstrokedashoffset($offset) {}
+    public function setstrokelinecap($LINECAP) {}
+    public function setstrokelinejoin($LINEJOIN) {}
+    public function setstrokemiterlimit($miterLimit) {}
+    public function setstrokeopacity($strokeOpacity) {}
+    public function setvectorgraphics($xml) {}
+    public function pop() {}
+    public function push() {}
+    public function setstrokedasharray($dashArray) {}
+    public function getopacity() {}
+    public function setopacity($opacity) {}
+    public function getfontresolution() {}
+    public function setfontresolution($x, $y) {}
+    public function getbordercolor() {}
+    public function setbordercolor($bordercolor) {}
+    public function setdensity($density) {}
+    public function getdensity() {}
+    public function gettextdirection() {}
+    public function settextdirection($direction) {}
+}
+
+class ImagickDrawException extends \Exception {
+
+    // properties
+    protected $message;
+    protected $code;
+    protected $file;
+    protected $line;
+}
+
+class ImagickException extends \Exception {
+
+    // properties
+    protected $message;
+    protected $code;
+    protected $file;
+    protected $line;
+}
+
+class ImagickKernel {
+
+    // methods
+    private function __construct() {}
+    public static function frommatrix($array, $array = null) {}
+    public static function frombuiltin($kerneltype, $paramstring) {}
+    public function addkernel(\ImagickKernel $ImagickKernel) {}
+    public function getmatrix() {}
+    public function separate() {}
+    public function scale() {}
+    public function addunitykernel() {}
+}
+
+class ImagickKernelException extends \Exception {
+
+    // properties
+    protected $message;
+    protected $code;
+    protected $file;
+    protected $line;
+}
+
+class ImagickPixel {
+
+    // methods
+    public function gethsl() {}
+    public function sethsl($hue, $saturation, $luminosity) {}
+    public function getcolorvaluequantum($color) {}
+    public function setcolorvaluequantum($color_value) {}
+    public function getindex() {}
+    public function setindex($index) {}
+    public function __construct($color = null) {}
+    public function setcolor($color) {}
+    public function setcolorvalue($color, $value) {}
+    public function getcolorvalue($color) {}
+    public function clear() {}
+    public function destroy() {}
+    public function issimilar($color, $fuzz = null) {}
+    public function ispixelsimilarquantum($color, $fuzz = null) {}
+    public function ispixelsimilar($color, $fuzz = null) {}
+    public function getcolor($normalized = null) {}
+    public function getcolorquantum() {}
+    public function getcolorasstring() {}
+    public function getcolorcount() {}
+    public function setcolorcount($colorCount) {}
+    public function clone() {}
+    public function setcolorfrompixel(\ImagickPixel $srcPixel) {}
+}
+
+class ImagickPixelException extends \Exception {
+
+    // properties
+    protected $message;
+    protected $code;
+    protected $file;
+    protected $line;
+}
+
+class ImagickPixelIterator implements \Iterator, \Traversable {
+
+    // methods
+    public function __construct(\Imagick $Imagick) {}
+    public function newpixeliterator() {}
+    public function newpixelregioniterator() {}
+    public function getiteratorrow() {}
+    public function setiteratorrow($row) {}
+    public function setiteratorfirstrow() {}
+    public function setiteratorlastrow() {}
+    public function getpreviousiteratorrow() {}
+    public function getcurrentiteratorrow() {}
+    public function getnextiteratorrow() {}
+    public function resetiterator() {}
+    public function synciterator() {}
+    public function destroy() {}
+    public function clear() {}
+    public static function getpixeliterator(\Imagick $Imagick) {}
+    public static function getpixelregioniterator(\Imagick $Imagick, $x, $y, $columns, $rows) {}
+    public function key() {}
+    public function next() {}
+    public function rewind() {}
+    public function current() {}
+    public function valid() {}
+}
+
+class ImagickPixelIteratorException extends \Exception {
+
+    // properties
+    protected $message;
+    protected $code;
+    protected $file;
+    protected $line;
+}
+
+}
diff --git a/.phan/internal_stubs/pcntl.phan_php b/.phan/internal_stubs/pcntl.phan_php
new file mode 100644 (file)
index 0000000..392dc30
--- /dev/null
@@ -0,0 +1,139 @@
+<?php
+// These stubs were generated by the phan stub generator.
+// @phan-stub-for-extension pcntl@7.0.33-0+deb9u3
+
+namespace {
+function pcntl_alarm($seconds) {}
+function pcntl_errno() {}
+function pcntl_exec($path, $args = null, $envs = null) {}
+function pcntl_fork() {}
+function pcntl_get_last_error() {}
+function pcntl_getpriority($pid = null, $process_identifier = null) {}
+function pcntl_setpriority($priority, $pid = null, $process_identifier = null) {}
+function pcntl_signal($signo, $handler, $restart_syscalls = null) {}
+function pcntl_signal_dispatch() {}
+function pcntl_sigprocmask($how, $set, &$oldset = null) {}
+function pcntl_sigtimedwait($set, &$info = null, $seconds = null, $nanoseconds = null) {}
+function pcntl_sigwaitinfo($set, &$info = null) {}
+function pcntl_strerror($errno) {}
+function pcntl_wait(&$status, $options = null, &$rusage = null) {}
+function pcntl_waitpid($pid, &$status, $options = null, &$rusage = null) {}
+function pcntl_wexitstatus($status) {}
+function pcntl_wifcontinued($status) {}
+function pcntl_wifexited($status) {}
+function pcntl_wifsignaled($status) {}
+function pcntl_wifstopped($status) {}
+function pcntl_wstopsig($status) {}
+function pcntl_wtermsig($status) {}
+const BUS_ADRALN = 1;
+const BUS_ADRERR = 2;
+const BUS_OBJERR = 3;
+const CLD_CONTINUED = 6;
+const CLD_DUMPED = 3;
+const CLD_EXITED = 1;
+const CLD_KILLED = 2;
+const CLD_STOPPED = 5;
+const CLD_TRAPPED = 4;
+const FPE_FLTDIV = 3;
+const FPE_FLTINV = 7;
+const FPE_FLTOVF = 4;
+const FPE_FLTRES = 6;
+const FPE_FLTSUB = 8;
+const FPE_FLTUND = 7;
+const FPE_INTDIV = 1;
+const FPE_INTOVF = 2;
+const ILL_BADSTK = 8;
+const ILL_COPROC = 7;
+const ILL_ILLADR = 3;
+const ILL_ILLOPC = 1;
+const ILL_ILLOPN = 2;
+const ILL_ILLTRP = 4;
+const ILL_PRVOPC = 5;
+const ILL_PRVREG = 6;
+const PCNTL_E2BIG = 7;
+const PCNTL_EACCES = 13;
+const PCNTL_EAGAIN = 11;
+const PCNTL_ECHILD = 10;
+const PCNTL_EFAULT = 14;
+const PCNTL_EINTR = 4;
+const PCNTL_EINVAL = 22;
+const PCNTL_EIO = 5;
+const PCNTL_EISDIR = 21;
+const PCNTL_ELIBBAD = 80;
+const PCNTL_ELOOP = 40;
+const PCNTL_EMFILE = 24;
+const PCNTL_ENAMETOOLONG = 36;
+const PCNTL_ENFILE = 23;
+const PCNTL_ENOENT = 2;
+const PCNTL_ENOEXEC = 8;
+const PCNTL_ENOMEM = 12;
+const PCNTL_ENOTDIR = 20;
+const PCNTL_EPERM = 1;
+const PCNTL_ESRCH = 3;
+const PCNTL_ETXTBSY = 26;
+const POLL_ERR = 4;
+const POLL_HUP = 6;
+const POLL_IN = 1;
+const POLL_MSG = 3;
+const POLL_OUT = 2;
+const POLL_PRI = 5;
+const PRIO_PGRP = 1;
+const PRIO_PROCESS = 0;
+const PRIO_USER = 2;
+const SEGV_ACCERR = 2;
+const SEGV_MAPERR = 1;
+const SIGABRT = 6;
+const SIGALRM = 14;
+const SIGBABY = 31;
+const SIGBUS = 7;
+const SIGCHLD = 17;
+const SIGCLD = 17;
+const SIGCONT = 18;
+const SIGFPE = 8;
+const SIGHUP = 1;
+const SIGILL = 4;
+const SIGINT = 2;
+const SIGIO = 29;
+const SIGIOT = 6;
+const SIGKILL = 9;
+const SIGPIPE = 13;
+const SIGPOLL = 29;
+const SIGPROF = 27;
+const SIGPWR = 30;
+const SIGQUIT = 3;
+const SIGSEGV = 11;
+const SIGSTKFLT = 16;
+const SIGSTOP = 19;
+const SIGSYS = 31;
+const SIGTERM = 15;
+const SIGTRAP = 5;
+const SIGTSTP = 20;
+const SIGTTIN = 21;
+const SIGTTOU = 22;
+const SIGURG = 23;
+const SIGUSR1 = 10;
+const SIGUSR2 = 12;
+const SIGVTALRM = 26;
+const SIGWINCH = 28;
+const SIGXCPU = 24;
+const SIGXFSZ = 25;
+const SIG_BLOCK = 0;
+const SIG_DFL = 0;
+const SIG_ERR = -1;
+const SIG_IGN = 1;
+const SIG_SETMASK = 2;
+const SIG_UNBLOCK = 1;
+const SI_ASYNCIO = -4;
+const SI_KERNEL = 128;
+const SI_MESGQ = -3;
+const SI_QUEUE = -1;
+const SI_SIGIO = -5;
+const SI_TIMER = -2;
+const SI_TKILL = -6;
+const SI_USER = 0;
+const TRAP_BRKPT = 1;
+const TRAP_TRACE = 2;
+const WCONTINUED = 8;
+const WNOHANG = 1;
+const WUNTRACED = 2;
+}
diff --git a/.phan/internal_stubs/redis.phan_php b/.phan/internal_stubs/redis.phan_php
new file mode 100644 (file)
index 0000000..29efb47
--- /dev/null
@@ -0,0 +1,490 @@
+<?php
+// These stubs were generated by the phan stub generator.
+// @phan-stub-for-extension redis@3.1.1
+
+namespace {
+class Redis {
+
+    // constants
+    const REDIS_NOT_FOUND = 0;
+    const REDIS_STRING = 1;
+    const REDIS_SET = 2;
+    const REDIS_LIST = 3;
+    const REDIS_ZSET = 4;
+    const REDIS_HASH = 5;
+    const PIPELINE = 2;
+    const ATOMIC = 0;
+    const MULTI = 1;
+    const OPT_SERIALIZER = 1;
+    const OPT_PREFIX = 2;
+    const OPT_READ_TIMEOUT = 3;
+    const SERIALIZER_NONE = 0;
+    const SERIALIZER_PHP = 1;
+    const SERIALIZER_IGBINARY = 2;
+    const OPT_SCAN = 4;
+    const SCAN_RETRY = 1;
+    const SCAN_NORETRY = 0;
+    const AFTER = 'after';
+    const BEFORE = 'before';
+
+    // methods
+    public function __construct() {}
+    public function __destruct() {}
+    public function connect() {}
+    public function pconnect() {}
+    public function close() {}
+    public function ping() {}
+    public function echo() {}
+    public function get() {}
+    public function set() {}
+    public function setex() {}
+    public function psetex() {}
+    public function setnx() {}
+    public function getSet() {}
+    public function randomKey() {}
+    public function renameKey() {}
+    public function renameNx() {}
+    public function getMultiple() {}
+    public function exists() {}
+    public function delete() {}
+    public function incr() {}
+    public function incrBy() {}
+    public function incrByFloat() {}
+    public function decr() {}
+    public function decrBy() {}
+    public function type() {}
+    public function append() {}
+    public function getRange() {}
+    public function setRange() {}
+    public function getBit() {}
+    public function setBit() {}
+    public function strlen() {}
+    public function getKeys() {}
+    public function sort() {}
+    public function sortAsc() {}
+    public function sortAscAlpha() {}
+    public function sortDesc() {}
+    public function sortDescAlpha() {}
+    public function lPush() {}
+    public function rPush() {}
+    public function lPushx() {}
+    public function rPushx() {}
+    public function lPop() {}
+    public function rPop() {}
+    public function blPop() {}
+    public function brPop() {}
+    public function lSize() {}
+    public function lRemove() {}
+    public function listTrim() {}
+    public function lGet() {}
+    public function lGetRange() {}
+    public function lSet() {}
+    public function lInsert() {}
+    public function sAdd() {}
+    public function sAddArray() {}
+    public function sSize() {}
+    public function sRemove() {}
+    public function sMove() {}
+    public function sPop() {}
+    public function sRandMember() {}
+    public function sContains() {}
+    public function sMembers() {}
+    public function sInter() {}
+    public function sInterStore() {}
+    public function sUnion() {}
+    public function sUnionStore() {}
+    public function sDiff() {}
+    public function sDiffStore() {}
+    public function setTimeout() {}
+    public function save() {}
+    public function bgSave() {}
+    public function lastSave() {}
+    public function flushDB() {}
+    public function flushAll() {}
+    public function dbSize() {}
+    public function auth() {}
+    public function ttl() {}
+    public function pttl() {}
+    public function persist() {}
+    public function info() {}
+    public function select() {}
+    public function move() {}
+    public function bgrewriteaof() {}
+    public function slaveof() {}
+    public function object() {}
+    public function bitop() {}
+    public function bitcount() {}
+    public function bitpos() {}
+    public function mset() {}
+    public function msetnx() {}
+    public function rpoplpush() {}
+    public function brpoplpush() {}
+    public function zAdd() {}
+    public function zDelete() {}
+    public function zRange() {}
+    public function zRevRange() {}
+    public function zRangeByScore() {}
+    public function zRevRangeByScore() {}
+    public function zRangeByLex() {}
+    public function zRevRangeByLex() {}
+    public function zLexCount() {}
+    public function zRemRangeByLex() {}
+    public function zCount() {}
+    public function zDeleteRangeByScore() {}
+    public function zDeleteRangeByRank() {}
+    public function zCard() {}
+    public function zScore() {}
+    public function zRank() {}
+    public function zRevRank() {}
+    public function zInter() {}
+    public function zUnion() {}
+    public function zIncrBy() {}
+    public function expireAt() {}
+    public function pexpire() {}
+    public function pexpireAt() {}
+    public function hGet() {}
+    public function hSet() {}
+    public function hSetNx() {}
+    public function hDel() {}
+    public function hLen() {}
+    public function hKeys() {}
+    public function hVals() {}
+    public function hGetAll() {}
+    public function hExists() {}
+    public function hIncrBy() {}
+    public function hIncrByFloat() {}
+    public function hMset() {}
+    public function hMget() {}
+    public function multi() {}
+    public function discard() {}
+    public function exec() {}
+    public function pipeline() {}
+    public function watch() {}
+    public function unwatch() {}
+    public function publish() {}
+    public function subscribe() {}
+    public function psubscribe() {}
+    public function unsubscribe() {}
+    public function punsubscribe() {}
+    public function time() {}
+    public function role() {}
+    public function eval() {}
+    public function evalsha() {}
+    public function script() {}
+    public function debug() {}
+    public function dump() {}
+    public function restore() {}
+    public function migrate() {}
+    public function getLastError() {}
+    public function clearLastError() {}
+    public function _prefix() {}
+    public function _serialize() {}
+    public function _unserialize() {}
+    public function client() {}
+    public function command() {}
+    public function scan(&$i_iterator, $str_pattern = null, $i_count = null) {}
+    public function hscan($str_key, &$i_iterator, $str_pattern = null, $i_count = null) {}
+    public function zscan($str_key, &$i_iterator, $str_pattern = null, $i_count = null) {}
+    public function sscan($str_key, &$i_iterator, $str_pattern = null, $i_count = null) {}
+    public function pfadd() {}
+    public function pfcount() {}
+    public function pfmerge() {}
+    public function getOption() {}
+    public function setOption() {}
+    public function config() {}
+    public function slowlog() {}
+    public function rawcommand() {}
+    public function geoadd() {}
+    public function geohash() {}
+    public function geopos() {}
+    public function geodist() {}
+    public function georadius() {}
+    public function georadiusbymember() {}
+    public function getHost() {}
+    public function getPort() {}
+    public function getDBNum() {}
+    public function getTimeout() {}
+    public function getReadTimeout() {}
+    public function getPersistentID() {}
+    public function getAuth() {}
+    public function isConnected() {}
+    public function getMode() {}
+    public function wait() {}
+    public function pubsub() {}
+    public function open() {}
+    public function popen() {}
+    public function lLen() {}
+    public function sGetMembers() {}
+    public function mget() {}
+    public function expire() {}
+    public function zunionstore() {}
+    public function zinterstore() {}
+    public function zRemove() {}
+    public function zRem() {}
+    public function zRemoveRangeByScore() {}
+    public function zRemRangeByScore() {}
+    public function zRemRangeByRank() {}
+    public function zSize() {}
+    public function substr() {}
+    public function rename() {}
+    public function del() {}
+    public function keys() {}
+    public function lrem() {}
+    public function ltrim() {}
+    public function lindex() {}
+    public function lrange() {}
+    public function scard() {}
+    public function srem() {}
+    public function sismember() {}
+    public function zReverseRange() {}
+    public function sendEcho() {}
+    public function evaluate() {}
+    public function evaluateSha() {}
+}
+
+class RedisArray {
+
+    // methods
+    public function __construct() {}
+    public function __call($function_name, $arguments) {}
+    public function _hosts() {}
+    public function _target() {}
+    public function _instance() {}
+    public function _function() {}
+    public function _distributor() {}
+    public function _rehash() {}
+    public function select() {}
+    public function info() {}
+    public function ping() {}
+    public function flushdb() {}
+    public function flushall() {}
+    public function mget() {}
+    public function mset() {}
+    public function del() {}
+    public function getOption() {}
+    public function setOption() {}
+    public function keys() {}
+    public function save() {}
+    public function bgsave() {}
+    public function multi() {}
+    public function exec() {}
+    public function discard() {}
+    public function unwatch() {}
+    public function delete() {}
+    public function getMultiple() {}
+}
+
+class RedisCluster {
+
+    // constants
+    const REDIS_NOT_FOUND = 0;
+    const REDIS_STRING = 1;
+    const REDIS_SET = 2;
+    const REDIS_LIST = 3;
+    const REDIS_ZSET = 4;
+    const REDIS_HASH = 5;
+    const ATOMIC = 0;
+    const MULTI = 1;
+    const OPT_SERIALIZER = 1;
+    const OPT_PREFIX = 2;
+    const OPT_READ_TIMEOUT = 3;
+    const SERIALIZER_NONE = 0;
+    const SERIALIZER_PHP = 1;
+    const SERIALIZER_IGBINARY = 2;
+    const OPT_SCAN = 4;
+    const SCAN_RETRY = 1;
+    const SCAN_NORETRY = 0;
+    const OPT_SLAVE_FAILOVER = 5;
+    const FAILOVER_NONE = 0;
+    const FAILOVER_ERROR = 1;
+    const FAILOVER_DISTRIBUTE = 2;
+    const FAILOVER_DISTRIBUTE_SLAVES = 3;
+    const AFTER = 'after';
+    const BEFORE = 'before';
+
+    // methods
+    public function __construct() {}
+    public function close() {}
+    public function get() {}
+    public function set() {}
+    public function mget() {}
+    public function mset() {}
+    public function msetnx() {}
+    public function del() {}
+    public function setex() {}
+    public function psetex() {}
+    public function setnx() {}
+    public function getset() {}
+    public function exists() {}
+    public function keys() {}
+    public function type() {}
+    public function lpop() {}
+    public function rpop() {}
+    public function lset() {}
+    public function spop() {}
+    public function lpush() {}
+    public function rpush() {}
+    public function blpop() {}
+    public function brpop() {}
+    public function rpushx() {}
+    public function lpushx() {}
+    public function linsert() {}
+    public function lindex() {}
+    public function lrem() {}
+    public function brpoplpush() {}
+    public function rpoplpush() {}
+    public function llen() {}
+    public function scard() {}
+    public function smembers() {}
+    public function sismember() {}
+    public function sadd() {}
+    public function saddarray() {}
+    public function srem() {}
+    public function sunion() {}
+    public function sunionstore() {}
+    public function sinter() {}
+    public function sinterstore() {}
+    public function sdiff() {}
+    public function sdiffstore() {}
+    public function srandmember() {}
+    public function strlen() {}
+    public function persist() {}
+    public function ttl() {}
+    public function pttl() {}
+    public function zcard() {}
+    public function zcount() {}
+    public function zremrangebyscore() {}
+    public function zscore() {}
+    public function zadd() {}
+    public function zincrby() {}
+    public function hlen() {}
+    public function hkeys() {}
+    public function hvals() {}
+    public function hget() {}
+    public function hgetall() {}
+    public function hexists() {}
+    public function hincrby() {}
+    public function hset() {}
+    public function hsetnx() {}
+    public function hmget() {}
+    public function hmset() {}
+    public function hdel() {}
+    public function hincrbyfloat() {}
+    public function dump() {}
+    public function zrank() {}
+    public function zrevrank() {}
+    public function incr() {}
+    public function decr() {}
+    public function incrby() {}
+    public function decrby() {}
+    public function incrbyfloat() {}
+    public function expire() {}
+    public function pexpire() {}
+    public function expireat() {}
+    public function pexpireat() {}
+    public function append() {}
+    public function getbit() {}
+    public function setbit() {}
+    public function bitop() {}
+    public function bitpos() {}
+    public function bitcount() {}
+    public function lget() {}
+    public function getrange() {}
+    public function ltrim() {}
+    public function lrange() {}
+    public function zremrangebyrank() {}
+    public function publish() {}
+    public function rename() {}
+    public function renamenx() {}
+    public function pfcount() {}
+    public function pfadd() {}
+    public function pfmerge() {}
+    public function setrange() {}
+    public function restore() {}
+    public function smove() {}
+    public function zrange() {}
+    public function zrevrange() {}
+    public function zrangebyscore() {}
+    public function zrevrangebyscore() {}
+    public function zrangebylex() {}
+    public function zrevrangebylex() {}
+    public function zlexcount() {}
+    public function zremrangebylex() {}
+    public function zunionstore() {}
+    public function zinterstore() {}
+    public function zrem() {}
+    public function sort() {}
+    public function object() {}
+    public function subscribe() {}
+    public function psubscribe() {}
+    public function unsubscribe() {}
+    public function punsubscribe() {}
+    public function eval() {}
+    public function evalsha() {}
+    public function scan(&$i_iterator, $str_node, $str_pattern = null, $i_count = null) {}
+    public function sscan($str_key, &$i_iterator, $str_pattern = null, $i_count = null) {}
+    public function zscan($str_key, &$i_iterator, $str_pattern = null, $i_count = null) {}
+    public function hscan($str_key, &$i_iterator, $str_pattern = null, $i_count = null) {}
+    public function getmode() {}
+    public function getlasterror() {}
+    public function clearlasterror() {}
+    public function getoption() {}
+    public function setoption() {}
+    public function _prefix() {}
+    public function _serialize() {}
+    public function _unserialize() {}
+    public function _masters() {}
+    public function _redir() {}
+    public function multi() {}
+    public function exec() {}
+    public function discard() {}
+    public function watch() {}
+    public function unwatch() {}
+    public function save() {}
+    public function bgsave() {}
+    public function flushdb() {}
+    public function flushall() {}
+    public function dbsize() {}
+    public function bgrewriteaof() {}
+    public function lastsave() {}
+    public function info() {}
+    public function role() {}
+    public function time() {}
+    public function randomkey() {}
+    public function ping() {}
+    public function echo() {}
+    public function command() {}
+    public function rawcommand() {}
+    public function cluster() {}
+    public function client() {}
+    public function config() {}
+    public function pubsub() {}
+    public function script() {}
+    public function slowlog() {}
+    public function geoadd() {}
+    public function geohash() {}
+    public function geopos() {}
+    public function geodist() {}
+    public function georadius() {}
+    public function georadiusbymember() {}
+}
+
+class RedisClusterException extends \RuntimeException {
+
+    // properties
+    protected $message;
+    protected $code;
+    protected $file;
+    protected $line;
+}
+
+class RedisException extends \RuntimeException {
+
+    // properties
+    protected $message;
+    protected $code;
+    protected $file;
+    protected $line;
+}
+
+}
diff --git a/.phan/internal_stubs/sockets.phan_php b/.phan/internal_stubs/sockets.phan_php
new file mode 100644 (file)
index 0000000..d16f363
--- /dev/null
@@ -0,0 +1,211 @@
+<?php
+// These stubs were generated by the phan stub generator.
+// @phan-stub-for-extension sockets@7.0.33-0+deb9u3
+
+namespace {
+function socket_accept($socket) {}
+function socket_bind($socket, $addr, $port = null) {}
+function socket_clear_error($socket = null) {}
+function socket_close($socket) {}
+function socket_cmsg_space($level, $type) {}
+function socket_connect($socket, $addr, $port = null) {}
+function socket_create($domain, $type, $protocol) {}
+function socket_create_listen($port, $backlog = null) {}
+function socket_create_pair($domain, $type, $protocol, &$fd) {}
+function socket_export_stream($socket) {}
+function socket_get_option($socket, $level, $optname) {}
+function socket_getopt($socket, $level, $optname) {}
+function socket_getpeername($socket, &$addr, &$port = null) {}
+function socket_getsockname($socket, &$addr, &$port = null) {}
+function socket_import_stream($stream) {}
+function socket_last_error($socket = null) {}
+function socket_listen($socket, $backlog = null) {}
+function socket_read($socket, $length, $type = null) {}
+function socket_recv($socket, &$buf, $len, $flags) {}
+function socket_recvfrom($socket, &$buf, $len, $flags, &$name, &$port = null) {}
+function socket_recvmsg($socket, &$msghdr, $flags) {}
+function socket_select(&$read_fds, &$write_fds, &$except_fds, $tv_sec, $tv_usec = null) {}
+function socket_send($socket, $buf, $len, $flags) {}
+function socket_sendmsg($socket, $msghdr, $flags) {}
+function socket_sendto($socket, $buf, $len, $flags, $addr, $port = null) {}
+function socket_set_block($socket) {}
+function socket_set_nonblock($socket) {}
+function socket_set_option($socket, $level, $optname, $optval) {}
+function socket_setopt($socket, $level, $optname, $optval) {}
+function socket_shutdown($socket, $how = null) {}
+function socket_strerror($errno) {}
+function socket_write($socket, $buf, $length = null) {}
+const AF_INET = 2;
+const AF_INET6 = 10;
+const AF_UNIX = 1;
+const IPPROTO_IP = 0;
+const IPPROTO_IPV6 = 41;
+const IPV6_HOPLIMIT = 52;
+const IPV6_MULTICAST_HOPS = 18;
+const IPV6_MULTICAST_IF = 17;
+const IPV6_MULTICAST_LOOP = 19;
+const IPV6_PKTINFO = 50;
+const IPV6_RECVHOPLIMIT = 51;
+const IPV6_RECVPKTINFO = 49;
+const IPV6_RECVTCLASS = 66;
+const IPV6_TCLASS = 67;
+const IPV6_UNICAST_HOPS = 16;
+const IPV6_V6ONLY = 26;
+const IP_MULTICAST_IF = 32;
+const IP_MULTICAST_LOOP = 34;
+const IP_MULTICAST_TTL = 33;
+const MCAST_BLOCK_SOURCE = 43;
+const MCAST_JOIN_GROUP = 42;
+const MCAST_JOIN_SOURCE_GROUP = 46;
+const MCAST_LEAVE_GROUP = 45;
+const MCAST_LEAVE_SOURCE_GROUP = 47;
+const MCAST_UNBLOCK_SOURCE = 44;
+const MSG_CMSG_CLOEXEC = 1073741824;
+const MSG_CONFIRM = 2048;
+const MSG_CTRUNC = 8;
+const MSG_DONTROUTE = 4;
+const MSG_DONTWAIT = 64;
+const MSG_EOF = 512;
+const MSG_EOR = 128;
+const MSG_ERRQUEUE = 8192;
+const MSG_MORE = 32768;
+const MSG_NOSIGNAL = 16384;
+const MSG_OOB = 1;
+const MSG_PEEK = 2;
+const MSG_TRUNC = 32;
+const MSG_WAITALL = 256;
+const MSG_WAITFORONE = 65536;
+const PHP_BINARY_READ = 2;
+const PHP_NORMAL_READ = 1;
+const SCM_CREDENTIALS = 2;
+const SCM_RIGHTS = 1;
+const SOCKET_E2BIG = 7;
+const SOCKET_EACCES = 13;
+const SOCKET_EADDRINUSE = 98;
+const SOCKET_EADDRNOTAVAIL = 99;
+const SOCKET_EADV = 68;
+const SOCKET_EAFNOSUPPORT = 97;
+const SOCKET_EAGAIN = 11;
+const SOCKET_EALREADY = 114;
+const SOCKET_EBADE = 52;
+const SOCKET_EBADF = 9;
+const SOCKET_EBADFD = 77;
+const SOCKET_EBADMSG = 74;
+const SOCKET_EBADR = 53;
+const SOCKET_EBADRQC = 56;
+const SOCKET_EBADSLT = 57;
+const SOCKET_EBUSY = 16;
+const SOCKET_ECHRNG = 44;
+const SOCKET_ECOMM = 70;
+const SOCKET_ECONNABORTED = 103;
+const SOCKET_ECONNREFUSED = 111;
+const SOCKET_ECONNRESET = 104;
+const SOCKET_EDESTADDRREQ = 89;
+const SOCKET_EDQUOT = 122;
+const SOCKET_EEXIST = 17;
+const SOCKET_EFAULT = 14;
+const SOCKET_EHOSTDOWN = 112;
+const SOCKET_EHOSTUNREACH = 113;
+const SOCKET_EIDRM = 43;
+const SOCKET_EINPROGRESS = 115;
+const SOCKET_EINTR = 4;
+const SOCKET_EINVAL = 22;
+const SOCKET_EIO = 5;
+const SOCKET_EISCONN = 106;
+const SOCKET_EISDIR = 21;
+const SOCKET_EISNAM = 120;
+const SOCKET_EL2HLT = 51;
+const SOCKET_EL2NSYNC = 45;
+const SOCKET_EL3HLT = 46;
+const SOCKET_EL3RST = 47;
+const SOCKET_ELNRNG = 48;
+const SOCKET_ELOOP = 40;
+const SOCKET_EMEDIUMTYPE = 124;
+const SOCKET_EMFILE = 24;
+const SOCKET_EMLINK = 31;
+const SOCKET_EMSGSIZE = 90;
+const SOCKET_EMULTIHOP = 72;
+const SOCKET_ENAMETOOLONG = 36;
+const SOCKET_ENETDOWN = 100;
+const SOCKET_ENETRESET = 102;
+const SOCKET_ENETUNREACH = 101;
+const SOCKET_ENFILE = 23;
+const SOCKET_ENOANO = 55;
+const SOCKET_ENOBUFS = 105;
+const SOCKET_ENOCSI = 50;
+const SOCKET_ENODATA = 61;
+const SOCKET_ENODEV = 19;
+const SOCKET_ENOENT = 2;
+const SOCKET_ENOLCK = 37;
+const SOCKET_ENOLINK = 67;
+const SOCKET_ENOMEDIUM = 123;
+const SOCKET_ENOMEM = 12;
+const SOCKET_ENOMSG = 42;
+const SOCKET_ENONET = 64;
+const SOCKET_ENOPROTOOPT = 92;
+const SOCKET_ENOSPC = 28;
+const SOCKET_ENOSR = 63;
+const SOCKET_ENOSTR = 60;
+const SOCKET_ENOSYS = 38;
+const SOCKET_ENOTBLK = 15;
+const SOCKET_ENOTCONN = 107;
+const SOCKET_ENOTDIR = 20;
+const SOCKET_ENOTEMPTY = 39;
+const SOCKET_ENOTSOCK = 88;
+const SOCKET_ENOTTY = 25;
+const SOCKET_ENOTUNIQ = 76;
+const SOCKET_ENXIO = 6;
+const SOCKET_EOPNOTSUPP = 95;
+const SOCKET_EPERM = 1;
+const SOCKET_EPFNOSUPPORT = 96;
+const SOCKET_EPIPE = 32;
+const SOCKET_EPROTO = 71;
+const SOCKET_EPROTONOSUPPORT = 93;
+const SOCKET_EPROTOTYPE = 91;
+const SOCKET_EREMCHG = 78;
+const SOCKET_EREMOTE = 66;
+const SOCKET_EREMOTEIO = 121;
+const SOCKET_ERESTART = 85;
+const SOCKET_EROFS = 30;
+const SOCKET_ESHUTDOWN = 108;
+const SOCKET_ESOCKTNOSUPPORT = 94;
+const SOCKET_ESPIPE = 29;
+const SOCKET_ESRMNT = 69;
+const SOCKET_ESTRPIPE = 86;
+const SOCKET_ETIME = 62;
+const SOCKET_ETIMEDOUT = 110;
+const SOCKET_ETOOMANYREFS = 109;
+const SOCKET_EUNATCH = 49;
+const SOCKET_EUSERS = 87;
+const SOCKET_EWOULDBLOCK = 11;
+const SOCKET_EXDEV = 18;
+const SOCKET_EXFULL = 54;
+const SOCK_DGRAM = 2;
+const SOCK_RAW = 3;
+const SOCK_RDM = 4;
+const SOCK_SEQPACKET = 5;
+const SOCK_STREAM = 1;
+const SOL_SOCKET = 1;
+const SOL_TCP = 6;
+const SOL_UDP = 17;
+const SOMAXCONN = 128;
+const SO_BINDTODEVICE = 25;
+const SO_BROADCAST = 6;
+const SO_DEBUG = 1;
+const SO_DONTROUTE = 5;
+const SO_ERROR = 4;
+const SO_KEEPALIVE = 9;
+const SO_LINGER = 13;
+const SO_OOBINLINE = 10;
+const SO_PASSCRED = 16;
+const SO_RCVBUF = 8;
+const SO_RCVLOWAT = 18;
+const SO_RCVTIMEO = 20;
+const SO_REUSEADDR = 2;
+const SO_REUSEPORT = 15;
+const SO_SNDBUF = 7;
+const SO_SNDLOWAT = 19;
+const SO_SNDTIMEO = 21;
+const SO_TYPE = 3;
+const TCP_NODELAY = 1;
+}
index cc1015c..819c202 100644 (file)
@@ -209,6 +209,8 @@ because of Phabricator reports.
   specified, deprecated in 1.30, have been removed.
 * BufferingStatsdDataFactory::getBuffer(), deprecated in 1.30, has been removed.
 * The constant DB_SLAVE, deprecated in 1.28, has been removed. Use DB_REPLICA.
+* Replacer, DoubleReplacer, HashtableReplacer and RegexlikeReplacer
+  (deprecated in 1.32) have been removed. Closures should be used instead.
 * …
 
 === Deprecations in 1.34 ===
@@ -265,6 +267,9 @@ because of Phabricator reports.
 * ResourceLoaderContext::getConfig and ResourceLoaderContext::getLogger have
   been deprecated. Inside ResourceLoaderModule subclasses, use the local methods
   instead. Elsewhere, use the methods from the ResourceLoader class.
+* The Preprocessor_DOM implementation has been deprecated.  It will be
+  removed in a future release.  Use the Preprocessor_Hash implementation
+  instead.
 
 === Other changes in 1.34 ===
 * …
index ae044f4..698dbf2 100644 (file)
@@ -416,7 +416,6 @@ $wgAutoloadLocalClasses = [
        'DnsSrvDiscoverer' => __DIR__ . '/includes/libs/DnsSrvDiscoverer.php',
        'DoubleRedirectJob' => __DIR__ . '/includes/jobqueue/jobs/DoubleRedirectJob.php',
        'DoubleRedirectsPage' => __DIR__ . '/includes/specials/SpecialDoubleRedirects.php',
-       'DoubleReplacer' => __DIR__ . '/includes/libs/replacers/DoubleReplacer.php',
        'DummyLinker' => __DIR__ . '/includes/DummyLinker.php',
        'DummySearchIndexFieldDefinition' => __DIR__ . '/includes/search/DummySearchIndexFieldDefinition.php',
        'DummyTermColorer' => __DIR__ . '/maintenance/term/MWTerm.php',
@@ -631,7 +630,6 @@ $wgAutoloadLocalClasses = [
        'HashConfig' => __DIR__ . '/includes/config/HashConfig.php',
        'HashRing' => __DIR__ . '/includes/libs/HashRing.php',
        'HashSiteStore' => __DIR__ . '/includes/site/HashSiteStore.php',
-       'HashtableReplacer' => __DIR__ . '/includes/libs/replacers/HashtableReplacer.php',
        'HistoryAction' => __DIR__ . '/includes/actions/HistoryAction.php',
        'HistoryBlob' => __DIR__ . '/includes/historyblob/HistoryBlob.php',
        'HistoryBlobCurStub' => __DIR__ . '/includes/historyblob/HistoryBlobCurStub.php',
@@ -738,7 +736,7 @@ $wgAutoloadLocalClasses = [
        'LanguageAz' => __DIR__ . '/languages/classes/LanguageAz.php',
        'LanguageBe_tarask' => __DIR__ . '/languages/classes/LanguageBe_tarask.php',
        'LanguageBs' => __DIR__ . '/languages/classes/LanguageBs.php',
-       'LanguageCode' => __DIR__ . '/languages/LanguageCode.php',
+       'LanguageCode' => __DIR__ . '/includes/language/LanguageCode.php',
        'LanguageConverter' => __DIR__ . '/languages/LanguageConverter.php',
        'LanguageCrh' => __DIR__ . '/languages/classes/LanguageCrh.php',
        'LanguageCu' => __DIR__ . '/languages/classes/LanguageCu.php',
@@ -978,12 +976,12 @@ $wgAutoloadLocalClasses = [
        'MergeLogFormatter' => __DIR__ . '/includes/logging/MergeLogFormatter.php',
        'MergeMessageFileList' => __DIR__ . '/maintenance/mergeMessageFileList.php',
        'MergeableUpdate' => __DIR__ . '/includes/deferred/MergeableUpdate.php',
-       'Message' => __DIR__ . '/includes/Message.php',
+       'Message' => __DIR__ . '/includes/language/Message.php',
        'MessageBlobStore' => __DIR__ . '/includes/resourceloader/MessageBlobStore.php',
        'MessageCache' => __DIR__ . '/includes/cache/MessageCache.php',
        'MessageCacheUpdate' => __DIR__ . '/includes/deferred/MessageCacheUpdate.php',
        'MessageContent' => __DIR__ . '/includes/content/MessageContent.php',
-       'MessageLocalizer' => __DIR__ . '/languages/MessageLocalizer.php',
+       'MessageLocalizer' => __DIR__ . '/includes/language/MessageLocalizer.php',
        'MessageSpecifier' => __DIR__ . '/includes/libs/MessageSpecifier.php',
        'MigrateActors' => __DIR__ . '/maintenance/includes/MigrateActors.php',
        'MigrateArchiveText' => __DIR__ . '/maintenance/migrateArchiveText.php',
@@ -1218,14 +1216,12 @@ $wgAutoloadLocalClasses = [
        'RefreshImageMetadata' => __DIR__ . '/maintenance/refreshImageMetadata.php',
        'RefreshLinks' => __DIR__ . '/maintenance/refreshLinks.php',
        'RefreshLinksJob' => __DIR__ . '/includes/jobqueue/jobs/RefreshLinksJob.php',
-       'RegexlikeReplacer' => __DIR__ . '/includes/libs/replacers/RegexlikeReplacer.php',
        'RemexStripTagHandler' => __DIR__ . '/includes/parser/RemexStripTagHandler.php',
        'RemoveInvalidEmails' => __DIR__ . '/maintenance/removeInvalidEmails.php',
        'RemoveUnusedAccounts' => __DIR__ . '/maintenance/removeUnusedAccounts.php',
        'RenameDbPrefix' => __DIR__ . '/maintenance/renameDbPrefix.php',
        'RenderAction' => __DIR__ . '/includes/actions/RenderAction.php',
        'ReplacementArray' => __DIR__ . '/includes/libs/ReplacementArray.php',
-       'Replacer' => __DIR__ . '/includes/libs/replacers/Replacer.php',
        'ReplicatedBagOStuff' => __DIR__ . '/includes/libs/objectcache/ReplicatedBagOStuff.php',
        'RepoGroup' => __DIR__ . '/includes/filerepo/RepoGroup.php',
        'RequestContext' => __DIR__ . '/includes/context/RequestContext.php',
@@ -1235,6 +1231,7 @@ $wgAutoloadLocalClasses = [
        'ResetUserTokens' => __DIR__ . '/maintenance/resetUserTokens.php',
        'ResourceFileCache' => __DIR__ . '/includes/cache/ResourceFileCache.php',
        'ResourceLoader' => __DIR__ . '/includes/resourceloader/ResourceLoader.php',
+       'ResourceLoaderCircularDependencyError' => __DIR__ . '/includes/resourceloader/ResourceLoaderCircularDependencyError.php',
        'ResourceLoaderClientHtml' => __DIR__ . '/includes/resourceloader/ResourceLoaderClientHtml.php',
        'ResourceLoaderContext' => __DIR__ . '/includes/resourceloader/ResourceLoaderContext.php',
        'ResourceLoaderFileModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderFileModule.php',
index e224412..7a90804 100644 (file)
@@ -77,7 +77,8 @@
                "wikimedia/testing-access-wrapper": "~1.0",
                "wmde/hamcrest-html-matchers": "^0.1.0",
                "mediawiki/mediawiki-phan-config": "0.6.0",
-               "symfony/yaml": "3.4.28"
+               "symfony/yaml": "3.4.28",
+               "johnkary/phpunit-speedtrap": "^1.0 | ^2.0"
        },
        "replace": {
                "symfony/polyfill-ctype": "1.99",
index 976d5c2..b275adc 100644 (file)
@@ -3004,7 +3004,8 @@ $terms: Search terms, for highlighting
 &$titleSnippet: Label for the link representing the search result. Typically the
   article title.
 $result: The SearchResult object
-$terms: String of the search terms entered
+$terms: array of search terms extracted by SearchDatabase search engines
+  (may not be populated by other search engines).
 $specialSearch: The SpecialSearch object
 &$query: Array of query string parameters for the link representing the search
   result.
index 13b6961..cf28762 100644 (file)
@@ -13,8 +13,8 @@ purposes of updating the link tables. This application is now deprecated.
 
 To create a batch, you can use the following code:
 
-$pages = array( 'Main Page', 'Project:Help', /* ... */ );
-$titles = array();
+$pages = [ 'Main Page', 'Project:Help', /* ... */ ];
+$titles = [];
 
 foreach( $pages as $page ){
        $titles[] = Title::newFromText( $page );
index 6b4d37e..42f701c 100644 (file)
@@ -28,16 +28,16 @@ Create a file called ExtensionName.i18n.magic.php with the following contents:
 ----
 <?php
 
-$magicWords = array();
+$magicWords = [];
 
-$magicWords['en'] = array(
+$magicWords['en'] = [
        // Case sensitive.
-       'mag_custom' => array( 1, 'CUSTOM' ),
-);
+       'mag_custom' => [ 1, 'CUSTOM' ],
+];
 
-$magicWords['es'] = array(
-       'mag_custom' => array( 1, 'ADUANERO' ),
-);
+$magicWords['es'] = [
+       'mag_custom' => [ 1, 'ADUANERO' ],
+];
 ----
 
 $wgExtensionMessagesFiles['ExtensionNameMagic'] = __DIR__ . '/ExtensionName.i18n.magic.php';
@@ -62,16 +62,16 @@ Create a file called ExtensionName.i18n.magic.php with the following contents:
 ----
 <?php
 
-$magicWords = array();
+$magicWords = [];
 
-$magicWords['en'] = array(
+$magicWords['en'] = [
        // Case insensitive.
-       'mag_custom' => array( 0, 'custom' ),
-);
+       'mag_custom' => [ 0, 'custom' ],
+];
 
-$magicWords['es'] = array(
-       'mag_custom' => array( 0, 'aduanero' ),
-);
+$magicWords['es'] = [
+       'mag_custom' => [ 0, 'aduanero' ],
+];
 ----
 
 $wgExtensionMessagesFiles['ExtensionNameMagic'] = __DIR__ . '/ExtensionName.i18n.magic.php';
index 1e68fb7..ba325fe 100644 (file)
@@ -61,7 +61,7 @@ on port 11211, using up to 64MB of memory)
 In your LocalSettings.php file, set:
 
        $wgMainCacheType = CACHE_MEMCACHED;
-       $wgMemCachedServers = array( "127.0.0.1:11211" );
+       $wgMemCachedServers = [ "127.0.0.1:11211" ];
 
 The wiki should then use memcached to cache various data. To use
 multiple servers (physically separate boxes or multiple caches
@@ -70,10 +70,10 @@ to the array. To increase the weight of a server (say, because
 it has twice the memory of the others and you want to spread
 usage evenly), make its entry a subarray:
 
-  $wgMemCachedServers = array(
+  $wgMemCachedServers = [
     "127.0.0.1:11211", # one gig on this box
-    array("192.168.0.1:11211", 2 ) # two gigs on the other box
-  );
+    [ "192.168.0.1:11211", 2 ] # two gigs on the other box
+  ];
 
 == PHP client for memcached ==
 
index 6a0dce6..ef9724b 100644 (file)
@@ -166,7 +166,7 @@ EXAMPLE:
 require 'MemCachedClient.inc.php';
 
 // set the servers, with the last one having an integer weight value of 3
-$options["servers"] = array("10.0.0.15:11000","10.0.0.16:11001",array("10.0.0.17:11002", 3));
+$options["servers"] = ["10.0.0.15:11000","10.0.0.16:11001",["10.0.0.17:11002", 3]];
 $options["debug"] = false;
 
 $memc = new MemCachedClient($options);
@@ -175,7 +175,7 @@ $memc = new MemCachedClient($options);
 /***********************
  * STORE AN ARRAY
  ***********************/
-$myarr = array("one","two", 3);
+$myarr = ["one","two", 3];
 $memc->set("key_one", $myarr);
 $val = $memc->get("key_one");
 print $val[0]."\n";    // prints 'one'
index 02c9d01..a413037 100644 (file)
@@ -89,12 +89,12 @@ class Autopromote {
 
        /**
         * Recursively check a condition.  Conditions are in the form
-        *   array( '&' or '|' or '^' or '!', cond1, cond2, ... )
+        *   [ '&' or '|' or '^' or '!', cond1, cond2, ... ]
         * where cond1, cond2, ... are themselves conditions; *OR*
         *   APCOND_EMAILCONFIRMED, *OR*
-        *   array( APCOND_EMAILCONFIRMED ), *OR*
-        *   array( APCOND_EDITCOUNT, number of edits ), *OR*
-        *   array( APCOND_AGE, seconds since registration ), *OR*
+        *   [ APCOND_EMAILCONFIRMED ], *OR*
+        *   [ APCOND_EDITCOUNT, number of edits ], *OR*
+        *   [ APCOND_AGE, seconds since registration ], *OR*
         *   similar constructs defined by extensions.
         * This function evaluates the former type recursively, and passes off to
         * self::checkCondition for evaluation of the latter type.
index 1be573d..2f793b5 100644 (file)
@@ -4153,6 +4153,9 @@ $wgInvalidRedirectTargets = [ 'Filepath', 'Mypage', 'Mytalk', 'Redirect' ];
  *                    If this parameter is not given, it uses Preprocessor_DOM if the
  *                    DOM module is available, otherwise it uses Preprocessor_Hash.
  *
+ * The Preprocessor_DOM class is deprecated, and will be removed in a future
+ * release.
+ *
  * The entire associative array will be passed through to the constructor as
  * the first parameter. Note that only Setup.php can use this variable --
  * the configuration will change at runtime via Parser member functions, so
@@ -5424,20 +5427,20 @@ $wgAutoConfirmCount = 0;
  *
  * The basic syntax for `$wgAutopromote` is:
  *
- *     $wgAutopromote = array(
+ *     $wgAutopromote = [
  *         'groupname' => cond,
  *         'group2' => cond2,
- *     );
+ *     ];
  *
  * A `cond` may be:
  *  - a single condition without arguments:
  *      Note that Autopromote wraps a single non-array value into an array
  *      e.g. `APCOND_EMAILCONFIRMED` OR
- *           array( `APCOND_EMAILCONFIRMED` )
+ *           [ `APCOND_EMAILCONFIRMED` ]
  *  - a single condition with arguments:
- *      e.g. `array( APCOND_EDITCOUNT, 100 )`
+ *      e.g. `[ APCOND_EDITCOUNT, 100 ]`
  *  - a set of conditions:
- *      e.g. `array( 'operand', cond1, cond2, ... )`
+ *      e.g. `[ 'operand', cond1, cond2, ... ]`
  *
  * When constructing a set of conditions, the following conditions are available:
  *  - `&` (**AND**):
@@ -5448,25 +5451,25 @@ $wgAutoConfirmCount = 0;
  *      promote if user matches **ONLY ONE OF THE CONDITIONS**
  *  - `!` (**NOT**):
  *      promote if user matces **NO** condition
- *  - array( APCOND_EMAILCONFIRMED ):
+ *  - [ APCOND_EMAILCONFIRMED ]:
  *      true if user has a confirmed e-mail
- *  - array( APCOND_EDITCOUNT, number of edits ):
+ *  - [ APCOND_EDITCOUNT, number of edits ]:
  *      true if user has the at least the number of edits as the passed parameter
- *  - array( APCOND_AGE, seconds since registration ):
+ *  - [ APCOND_AGE, seconds since registration ]:
  *      true if the length of time since the user created his/her account
  *      is at least the same length of time as the passed parameter
- *  - array( APCOND_AGE_FROM_EDIT, seconds since first edit ):
+ *  - [ APCOND_AGE_FROM_EDIT, seconds since first edit ]:
  *      true if the length of time since the user made his/her first edit
  *      is at least the same length of time as the passed parameter
- *  - array( APCOND_INGROUPS, group1, group2, ... ):
+ *  - [ APCOND_INGROUPS, group1, group2, ... ]:
  *      true if the user is a member of each of the passed groups
- *  - array( APCOND_ISIP, ip ):
+ *  - [ APCOND_ISIP, ip ]:
  *      true if the user has the passed IP address
- *  - array( APCOND_IPINRANGE, range ):
+ *  - [ APCOND_IPINRANGE, range ]:
  *      true if the user has an IP address in the range of the passed parameter
- *  - array( APCOND_BLOCKED ):
+ *  - [ APCOND_BLOCKED ]:
  *      true if the user is blocked
- *  - array( APCOND_ISBOT ):
+ *  - [ APCOND_ISBOT ]:
  *      true if the user is a bot
  *  - similar constructs can be defined by extensions
  *
@@ -6420,7 +6423,7 @@ $wgDeprecationReleaseLimit = false;
  *
  * @code
  *   $wgProfiler['class'] = 'ProfilerXhprof';
- *   $wgProfiler['output'] = array( 'ProfilerOutputDb' );
+ *   $wgProfiler['output'] = [ 'ProfilerOutputDb' ];
  *   $wgProfiler['sampling'] = 50; // one every 50 requests
  * @endcode
  *
index c558aee..d2f26b3 100644 (file)
@@ -53,3 +53,7 @@ if ( $logDir ) {
        $wgDebugLogGroups['error'] = "$logDir/mw-error.log";
 }
 unset( $logDir );
+
+// Disable rate-limiting to allow integration tests to run unthrottled
+// in CI and for devs locally (T225796)
+$wgRateLimits = [];
index aa51243..fdc348b 100644 (file)
@@ -518,7 +518,7 @@ class Html {
                                        $newValue = [];
                                        foreach ( $value as $k => $v ) {
                                                if ( is_string( $v ) ) {
-                                                       // String values should be normal `array( 'foo' )`
+                                                       // String values should be normal `[ 'foo' ]`
                                                        // Just append them
                                                        if ( !isset( $value[$v] ) ) {
                                                                // As a special case don't set 'foo' if a
index ca77121..69f23c1 100644 (file)
@@ -23,8 +23,8 @@
 use MediaWiki\Logger\LoggerFactory;
 use Psr\Log\LoggerInterface;
 use MediaWiki\MediaWikiServices;
+use Wikimedia\Rdbms\ILBFactory;
 use Wikimedia\Rdbms\ChronologyProtector;
-use Wikimedia\Rdbms\LBFactory;
 use Wikimedia\Rdbms\DBConnectionError;
 use Liuggio\StatsdClient\Sender\SocketSender;
 
@@ -580,15 +580,15 @@ class MediaWiki {
        public static function preOutputCommit(
                IContextSource $context, callable $postCommitWork = null
        ) {
-               // Either all DBs should commit or none
-               ignore_user_abort( true );
-
                $config = $context->getConfig();
                $request = $context->getRequest();
                $output = $context->getOutput();
                $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
 
-               // Commit all changes
+               // Try to make sure that all RDBMs, session, and other storage updates complete
+               ignore_user_abort( true );
+
+               // Commit all RDBMs changes from the main transaction round
                $lbFactory->commitMasterChanges(
                        __METHOD__,
                        // Abort if any transaction was too big
@@ -596,47 +596,31 @@ class MediaWiki {
                );
                wfDebug( __METHOD__ . ': primary transaction round committed' );
 
-               // Run updates that need to block the user or affect output (this is the last chance)
+               // Run updates that need to block the client or affect output (this is the last chance)
                DeferredUpdates::doUpdates( 'run', DeferredUpdates::PRESEND );
                wfDebug( __METHOD__ . ': pre-send deferred updates completed' );
-               // T214471: persist the session to avoid race conditions on subsequent requests
-               $request->getSession()->save();
-
-               // Should the client return, their request should observe the new ChronologyProtector
-               // DB positions. This request might be on a foreign wiki domain, so synchronously update
-               // the DB positions in all datacenters to be safe. If this output is not a redirect,
-               // then OutputPage::output() will be relatively slow, meaning that running it in
-               // $postCommitWork should help mask the latency of those updates.
-               $flags = $lbFactory::SHUTDOWN_CHRONPROT_SYNC;
-               $strategy = 'cookie+sync';
-
-               $allowHeaders = !( $output->isDisabled() || headers_sent() );
-               if ( $output->getRedirect() && $lbFactory->hasOrMadeRecentMasterChanges( INF ) ) {
-                       // OutputPage::output() will be fast, so $postCommitWork is useless for masking
-                       // the latency of synchronously updating the DB positions in all datacenters.
-                       // Try to make use of the time the client spends following redirects instead.
-                       $domainDistance = self::getUrlDomainDistance( $output->getRedirect() );
-                       if ( $domainDistance === 'local' && $allowHeaders ) {
-                               $flags = $lbFactory::SHUTDOWN_CHRONPROT_ASYNC;
-                               $strategy = 'cookie'; // use same-domain cookie and keep the URL uncluttered
-                       } elseif ( $domainDistance === 'remote' ) {
-                               $flags = $lbFactory::SHUTDOWN_CHRONPROT_ASYNC;
-                               $strategy = 'cookie+url'; // cross-domain cookie might not work
-                       }
-               }
-
+               // Persist the session to avoid race conditions on subsequent requests by the client
+               $request->getSession()->save(); // T214471
+               wfDebug( __METHOD__ . ': session changes committed' );
+
+               // Figure out whether to wait for DB replication now or to use some method that assures
+               // that subsequent requests by the client will use the DB replication positions written
+               // during the shutdown() call below; the later requires working around replication lag
+               // of the store containing DB replication positions (e.g. dynomite, mcrouter).
+               list( $flags, $strategy ) = self::getChronProtStrategy( $lbFactory, $output );
                // Record ChronologyProtector positions for DBs affected in this request at this point
                $cpIndex = null;
                $cpClientId = null;
                $lbFactory->shutdown( $flags, $postCommitWork, $cpIndex, $cpClientId );
                wfDebug( __METHOD__ . ': LBFactory shutdown completed' );
 
+               $allowHeaders = !( $output->isDisabled() || headers_sent() );
                if ( $cpIndex > 0 ) {
                        if ( $allowHeaders ) {
                                $now = time();
                                $expires = $now + ChronologyProtector::POSITION_COOKIE_TTL;
                                $options = [ 'prefix' => '' ];
-                               $value = LBFactory::makeCookieValueFromCPIndex( $cpIndex, $now, $cpClientId );
+                               $value = $lbFactory::makeCookieValueFromCPIndex( $cpIndex, $now, $cpClientId );
                                $request->response()->setCookie( 'cpPosIndex', $value, $expires, $options );
                        }
 
@@ -654,31 +638,66 @@ class MediaWiki {
                        }
                }
 
-               // Set a cookie to tell all CDN edge nodes to "stick" the user to the DC that handles this
-               // POST request (e.g. the "master" data center). Also have the user briefly bypass CDN so
-               // ChronologyProtector works for cacheable URLs.
-               if ( $request->wasPosted() && $lbFactory->hasOrMadeRecentMasterChanges() ) {
-                       $expires = time() + $config->get( 'DataCenterUpdateStickTTL' );
-                       $options = [ 'prefix' => '' ];
-                       $request->response()->setCookie( 'UseDC', 'master', $expires, $options );
-                       $request->response()->setCookie( 'UseCDNCache', 'false', $expires, $options );
-               }
+               if ( $allowHeaders ) {
+                       // Set a cookie to tell all CDN edge nodes to "stick" the user to the DC that
+                       // handles this POST request (e.g. the "master" data center). Also have the user
+                       // briefly bypass CDN so ChronologyProtector works for cacheable URLs.
+                       if ( $request->wasPosted() && $lbFactory->hasOrMadeRecentMasterChanges() ) {
+                               $expires = time() + $config->get( 'DataCenterUpdateStickTTL' );
+                               $options = [ 'prefix' => '' ];
+                               $request->response()->setCookie( 'UseDC', 'master', $expires, $options );
+                               $request->response()->setCookie( 'UseCDNCache', 'false', $expires, $options );
+                       }
+
+                       // Avoid letting a few seconds of replica DB lag cause a month of stale data.
+                       // This logic is also intimately related to the value of $wgCdnReboundPurgeDelay.
+                       if ( $lbFactory->laggedReplicaUsed() ) {
+                               $maxAge = $config->get( 'CdnMaxageLagged' );
+                               $output->lowerCdnMaxage( $maxAge );
+                               $request->response()->header( "X-Database-Lagged: true" );
+                               wfDebugLog( 'replication',
+                                       "Lagged DB used; CDN cache TTL limited to $maxAge seconds" );
+                       }
 
-               // Avoid letting a few seconds of replica DB lag cause a month of stale data. This logic is
-               // also intimately related to the value of $wgCdnReboundPurgeDelay.
-               if ( $lbFactory->laggedReplicaUsed() ) {
-                       $maxAge = $config->get( 'CdnMaxageLagged' );
-                       $output->lowerCdnMaxage( $maxAge );
-                       $request->response()->header( "X-Database-Lagged: true" );
-                       wfDebugLog( 'replication', "Lagged DB used; CDN cache TTL limited to $maxAge seconds" );
+                       // Avoid long-term cache pollution due to message cache rebuild timeouts (T133069)
+                       if ( MessageCache::singleton()->isDisabled() ) {
+                               $maxAge = $config->get( 'CdnMaxageSubstitute' );
+                               $output->lowerCdnMaxage( $maxAge );
+                               $request->response()->header( "X-Response-Substitute: true" );
+                       }
                }
+       }
+
+       /**
+        * @param ILBFactory $lbFactory
+        * @param OutputPage $output
+        * @return array
+        */
+       private static function getChronProtStrategy( ILBFactory $lbFactory, OutputPage $output ) {
+               // Should the client return, their request should observe the new ChronologyProtector
+               // DB positions. This request might be on a foreign wiki domain, so synchronously update
+               // the DB positions in all datacenters to be safe. If this output is not a redirect,
+               // then OutputPage::output() will be relatively slow, meaning that running it in
+               // $postCommitWork should help mask the latency of those updates.
+               $flags = $lbFactory::SHUTDOWN_CHRONPROT_SYNC;
+               $strategy = 'cookie+sync';
 
-               // Avoid long-term cache pollution due to message cache rebuild timeouts (T133069)
-               if ( MessageCache::singleton()->isDisabled() ) {
-                       $maxAge = $config->get( 'CdnMaxageSubstitute' );
-                       $output->lowerCdnMaxage( $maxAge );
-                       $request->response()->header( "X-Response-Substitute: true" );
+               $allowHeaders = !( $output->isDisabled() || headers_sent() );
+               if ( $output->getRedirect() && $lbFactory->hasOrMadeRecentMasterChanges( INF ) ) {
+                       // OutputPage::output() will be fast, so $postCommitWork is useless for masking
+                       // the latency of synchronously updating the DB positions in all datacenters.
+                       // Try to make use of the time the client spends following redirects instead.
+                       $domainDistance = self::getUrlDomainDistance( $output->getRedirect() );
+                       if ( $domainDistance === 'local' && $allowHeaders ) {
+                               $flags = $lbFactory::SHUTDOWN_CHRONPROT_ASYNC;
+                               $strategy = 'cookie'; // use same-domain cookie and keep the URL uncluttered
+                       } elseif ( $domainDistance === 'remote' ) {
+                               $flags = $lbFactory::SHUTDOWN_CHRONPROT_ASYNC;
+                               $strategy = 'cookie+url'; // cross-domain cookie might not work
+                       }
                }
+
+               return [ $flags, $strategy ];
        }
 
        /**
@@ -917,7 +936,7 @@ class MediaWiki {
 
                // Commit and close up!
                $lbFactory->commitMasterChanges( __METHOD__ );
-               $lbFactory->shutdown( LBFactory::SHUTDOWN_NO_CHRONPROT );
+               $lbFactory->shutdown( $lbFactory::SHUTDOWN_NO_CHRONPROT );
 
                wfDebug( "Request ended normally\n" );
        }
diff --git a/includes/Message.php b/includes/Message.php
deleted file mode 100644 (file)
index 0b3113f..0000000
+++ /dev/null
@@ -1,1396 +0,0 @@
-<?php
-/**
- * Fetching and processing of interface messages.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @author Niklas Laxström
- */
-use MediaWiki\MediaWikiServices;
-
-/**
- * The Message class provides methods which fulfil two basic services:
- *  - fetching interface messages
- *  - processing messages into a variety of formats
- *
- * First implemented with MediaWiki 1.17, the Message class is intended to
- * replace the old wfMsg* functions that over time grew unusable.
- * @see https://www.mediawiki.org/wiki/Manual:Messages_API for equivalences
- * between old and new functions.
- *
- * You should use the wfMessage() global function which acts as a wrapper for
- * the Message class. The wrapper let you pass parameters as arguments.
- *
- * The most basic usage cases would be:
- *
- * @code
- *     // Initialize a Message object using the 'some_key' message key
- *     $message = wfMessage( 'some_key' );
- *
- *     // Using two parameters those values are strings 'value1' and 'value2':
- *     $message = wfMessage( 'some_key',
- *          'value1', 'value2'
- *     );
- * @endcode
- *
- * @section message_global_fn Global function wrapper:
- *
- * Since wfMessage() returns a Message instance, you can chain its call with
- * a method. Some of them return a Message instance too so you can chain them.
- * You will find below several examples of wfMessage() usage.
- *
- * Fetching a message text for interface message:
- *
- * @code
- *    $button = Xml::button(
- *         wfMessage( 'submit' )->text()
- *    );
- * @endcode
- *
- * A Message instance can be passed parameters after it has been constructed,
- * use the params() method to do so:
- *
- * @code
- *     wfMessage( 'welcome-to' )
- *         ->params( $wgSitename )
- *         ->text();
- * @endcode
- *
- * {{GRAMMAR}} and friends work correctly:
- *
- * @code
- *    wfMessage( 'are-friends',
- *        $user, $friend
- *    );
- *    wfMessage( 'bad-message' )
- *         ->rawParams( '<script>...</script>' )
- *         ->escaped();
- * @endcode
- *
- * @section message_language Changing language:
- *
- * Messages can be requested in a different language or in whatever current
- * content language is being used. The methods are:
- *     - Message->inContentLanguage()
- *     - Message->inLanguage()
- *
- * Sometimes the message text ends up in the database, so content language is
- * needed:
- *
- * @code
- *    wfMessage( 'file-log',
- *        $user, $filename
- *    )->inContentLanguage()->text();
- * @endcode
- *
- * Checking whether a message exists:
- *
- * @code
- *    wfMessage( 'mysterious-message' )->exists()
- *    // returns a boolean whether the 'mysterious-message' key exist.
- * @endcode
- *
- * If you want to use a different language:
- *
- * @code
- *    $userLanguage = $user->getOption( 'language' );
- *    wfMessage( 'email-header' )
- *         ->inLanguage( $userLanguage )
- *         ->plain();
- * @endcode
- *
- * @note You can parse the text only in the content or interface languages
- *
- * @section message_compare_old Comparison with old wfMsg* functions:
- *
- * Use full parsing:
- *
- * @code
- *     // old style:
- *     wfMsgExt( 'key', [ 'parseinline' ], 'apple' );
- *     // new style:
- *     wfMessage( 'key', 'apple' )->parse();
- * @endcode
- *
- * Parseinline is used because it is more useful when pre-building HTML.
- * In normal use it is better to use OutputPage::(add|wrap)WikiMsg.
- *
- * Places where HTML cannot be used. {{-transformation is done.
- * @code
- *     // old style:
- *     wfMsgExt( 'key', [ 'parsemag' ], 'apple', 'pear' );
- *     // new style:
- *     wfMessage( 'key', 'apple', 'pear' )->text();
- * @endcode
- *
- * Shortcut for escaping the message too, similar to wfMsgHTML(), but
- * parameters are not replaced after escaping by default.
- * @code
- *     $escaped = wfMessage( 'key' )
- *          ->rawParams( 'apple' )
- *          ->escaped();
- * @endcode
- *
- * @section message_appendix Appendix:
- *
- * @todo
- * - test, can we have tests?
- * - this documentation needs to be extended
- *
- * @see https://www.mediawiki.org/wiki/WfMessage()
- * @see https://www.mediawiki.org/wiki/New_messages_API
- * @see https://www.mediawiki.org/wiki/Localisation
- *
- * @since 1.17
- */
-class Message implements MessageSpecifier, Serializable {
-       /** Use message text as-is */
-       const FORMAT_PLAIN = 'plain';
-       /** Use normal wikitext -> HTML parsing (the result will be wrapped in a block-level HTML tag) */
-       const FORMAT_BLOCK_PARSE = 'block-parse';
-       /** Use normal wikitext -> HTML parsing but strip the block-level wrapper */
-       const FORMAT_PARSE = 'parse';
-       /** Transform {{..}} constructs but don't transform to HTML */
-       const FORMAT_TEXT = 'text';
-       /** Transform {{..}} constructs, HTML-escape the result */
-       const FORMAT_ESCAPED = 'escaped';
-
-       /**
-        * Mapping from Message::listParam() types to Language methods.
-        * @var array
-        */
-       protected static $listTypeMap = [
-               'comma' => 'commaList',
-               'semicolon' => 'semicolonList',
-               'pipe' => 'pipeList',
-               'text' => 'listToText',
-       ];
-
-       /**
-        * In which language to get this message. True, which is the default,
-        * means the current user language, false content language.
-        *
-        * @var bool
-        */
-       protected $interface = true;
-
-       /**
-        * In which language to get this message. Overrides the $interface setting.
-        *
-        * @var Language|bool Explicit language object, or false for user language
-        */
-       protected $language = false;
-
-       /**
-        * @var string The message key. If $keysToTry has more than one element,
-        * this may change to one of the keys to try when fetching the message text.
-        */
-       protected $key;
-
-       /**
-        * @var string[] List of keys to try when fetching the message.
-        */
-       protected $keysToTry;
-
-       /**
-        * @var array List of parameters which will be substituted into the message.
-        */
-       protected $parameters = [];
-
-       /**
-        * @var string
-        * @deprecated
-        */
-       protected $format = 'parse';
-
-       /**
-        * @var bool Whether database can be used.
-        */
-       protected $useDatabase = true;
-
-       /**
-        * @var Title Title object to use as context.
-        */
-       protected $title = null;
-
-       /**
-        * @var Content Content object representing the message.
-        */
-       protected $content = null;
-
-       /**
-        * @var string
-        */
-       protected $message;
-
-       /**
-        * @since 1.17
-        * @param string|string[]|MessageSpecifier $key Message key, or array of
-        * message keys to try and use the first non-empty message for, or a
-        * MessageSpecifier to copy from.
-        * @param array $params Message parameters.
-        * @param Language|null $language [optional] Language to use (defaults to current user language).
-        * @throws InvalidArgumentException
-        */
-       public function __construct( $key, $params = [], Language $language = null ) {
-               if ( $key instanceof MessageSpecifier ) {
-                       if ( $params ) {
-                               throw new InvalidArgumentException(
-                                       '$params must be empty if $key is a MessageSpecifier'
-                               );
-                       }
-                       $params = $key->getParams();
-                       $key = $key->getKey();
-               }
-
-               if ( !is_string( $key ) && !is_array( $key ) ) {
-                       throw new InvalidArgumentException( '$key must be a string or an array' );
-               }
-
-               $this->keysToTry = (array)$key;
-
-               if ( empty( $this->keysToTry ) ) {
-                       throw new InvalidArgumentException( '$key must not be an empty list' );
-               }
-
-               $this->key = reset( $this->keysToTry );
-
-               $this->parameters = array_values( $params );
-               // User language is only resolved in getLanguage(). This helps preserve the
-               // semantic intent of "user language" across serialize() and unserialize().
-               $this->language = $language ?: false;
-       }
-
-       /**
-        * @see Serializable::serialize()
-        * @since 1.26
-        * @return string
-        */
-       public function serialize() {
-               return serialize( [
-                       'interface' => $this->interface,
-                       'language' => $this->language ? $this->language->getCode() : false,
-                       'key' => $this->key,
-                       'keysToTry' => $this->keysToTry,
-                       'parameters' => $this->parameters,
-                       'format' => $this->format,
-                       'useDatabase' => $this->useDatabase,
-                       'titlestr' => $this->title ? $this->title->getFullText() : null,
-               ] );
-       }
-
-       /**
-        * @see Serializable::unserialize()
-        * @since 1.26
-        * @param string $serialized
-        */
-       public function unserialize( $serialized ) {
-               $data = unserialize( $serialized );
-               if ( !is_array( $data ) ) {
-                       throw new InvalidArgumentException( __METHOD__ . ': Invalid serialized data' );
-               }
-
-               $this->interface = $data['interface'];
-               $this->key = $data['key'];
-               $this->keysToTry = $data['keysToTry'];
-               $this->parameters = $data['parameters'];
-               $this->format = $data['format'];
-               $this->useDatabase = $data['useDatabase'];
-               $this->language = $data['language'] ? Language::factory( $data['language'] ) : false;
-
-               if ( isset( $data['titlestr'] ) ) {
-                       $this->title = Title::newFromText( $data['titlestr'] );
-               } elseif ( isset( $data['title'] ) && $data['title'] instanceof Title ) {
-                       // Old serializations from before December 2018
-                       $this->title = $data['title'];
-               } else {
-                       $this->title = null; // Explicit for sanity
-               }
-       }
-
-       /**
-        * @since 1.24
-        *
-        * @return bool True if this is a multi-key message, that is, if the key provided to the
-        * constructor was a fallback list of keys to try.
-        */
-       public function isMultiKey() {
-               return count( $this->keysToTry ) > 1;
-       }
-
-       /**
-        * @since 1.24
-        *
-        * @return string[] The list of keys to try when fetching the message text,
-        * in order of preference.
-        */
-       public function getKeysToTry() {
-               return $this->keysToTry;
-       }
-
-       /**
-        * Returns the message key.
-        *
-        * If a list of multiple possible keys was supplied to the constructor, this method may
-        * return any of these keys. After the message has been fetched, this method will return
-        * the key that was actually used to fetch the message.
-        *
-        * @since 1.21
-        *
-        * @return string
-        */
-       public function getKey() {
-               return $this->key;
-       }
-
-       /**
-        * Returns the message parameters.
-        *
-        * @since 1.21
-        *
-        * @return array
-        */
-       public function getParams() {
-               return $this->parameters;
-       }
-
-       /**
-        * Returns the message format.
-        *
-        * @since 1.21
-        *
-        * @return string
-        * @deprecated since 1.29 formatting is not stateful
-        */
-       public function getFormat() {
-               wfDeprecated( __METHOD__, '1.29' );
-               return $this->format;
-       }
-
-       /**
-        * Returns the Language of the Message.
-        *
-        * @since 1.23
-        *
-        * @return Language
-        */
-       public function getLanguage() {
-               // Defaults to false which means current user language
-               return $this->language ?: RequestContext::getMain()->getLanguage();
-       }
-
-       /**
-        * Factory function that is just wrapper for the real constructor. It is
-        * intended to be used instead of the real constructor, because it allows
-        * chaining method calls, while new objects don't.
-        *
-        * @since 1.17
-        *
-        * @param string|string[]|MessageSpecifier $key
-        * @param mixed $param,... Parameters as strings.
-        *
-        * @return Message
-        */
-       public static function newFromKey( $key /*...*/ ) {
-               $params = func_get_args();
-               array_shift( $params );
-               return new self( $key, $params );
-       }
-
-       /**
-        * Transform a MessageSpecifier or a primitive value used interchangeably with
-        * specifiers (a message key string, or a key + params array) into a proper Message.
-        *
-        * Also accepts a MessageSpecifier inside an array: that's not considered a valid format
-        * but is an easy error to make due to how StatusValue stores messages internally.
-        * Further array elements are ignored in that case.
-        *
-        * @param string|array|MessageSpecifier $value
-        * @return Message
-        * @throws InvalidArgumentException
-        * @since 1.27
-        */
-       public static function newFromSpecifier( $value ) {
-               $params = [];
-               if ( is_array( $value ) ) {
-                       $params = $value;
-                       $value = array_shift( $params );
-               }
-
-               if ( $value instanceof Message ) { // Message, RawMessage, ApiMessage, etc
-                       $message = clone $value;
-               } elseif ( $value instanceof MessageSpecifier ) {
-                       $message = new Message( $value );
-               } elseif ( is_string( $value ) ) {
-                       $message = new Message( $value, $params );
-               } else {
-                       throw new InvalidArgumentException( __METHOD__ . ': invalid argument type '
-                               . gettype( $value ) );
-               }
-
-               return $message;
-       }
-
-       /**
-        * Factory function accepting multiple message keys and returning a message instance
-        * for the first message which is non-empty. If all messages are empty then an
-        * instance of the first message key is returned.
-        *
-        * @since 1.18
-        *
-        * @param string|string[] $keys,... Message keys, or first argument as an array of all the
-        * message keys.
-        *
-        * @return Message
-        */
-       public static function newFallbackSequence( /*...*/ ) {
-               $keys = func_get_args();
-               if ( func_num_args() == 1 ) {
-                       if ( is_array( $keys[0] ) ) {
-                               // Allow an array to be passed as the first argument instead
-                               $keys = array_values( $keys[0] );
-                       } else {
-                               // Optimize a single string to not need special fallback handling
-                               $keys = $keys[0];
-                       }
-               }
-               return new self( $keys );
-       }
-
-       /**
-        * Get a title object for a mediawiki message, where it can be found in the mediawiki namespace.
-        * The title will be for the current language, if the message key is in
-        * $wgForceUIMsgAsContentMsg it will be append with the language code (except content
-        * language), because Message::inContentLanguage will also return in user language.
-        *
-        * @see $wgForceUIMsgAsContentMsg
-        * @return Title
-        * @since 1.26
-        */
-       public function getTitle() {
-               global $wgForceUIMsgAsContentMsg;
-
-               $contLang = MediaWikiServices::getInstance()->getContentLanguage();
-               $lang = $this->getLanguage();
-               $title = $this->key;
-               if (
-                       !$lang->equals( $contLang )
-                       && in_array( $this->key, (array)$wgForceUIMsgAsContentMsg )
-               ) {
-                       $title .= '/' . $lang->getCode();
-               }
-
-               return Title::makeTitle(
-                       NS_MEDIAWIKI, $contLang->ucfirst( strtr( $title, ' ', '_' ) ) );
-       }
-
-       /**
-        * Adds parameters to the parameter list of this message.
-        *
-        * @since 1.17
-        *
-        * @param mixed $args,... Parameters as strings or arrays from
-        *  Message::numParam() and the like, or a single array of parameters.
-        *
-        * @return Message $this
-        */
-       public function params( /*...*/ ) {
-               $args = func_get_args();
-
-               // If $args has only one entry and it's an array, then it's either a
-               // non-varargs call or it happens to be a call with just a single
-               // "special" parameter. Since the "special" parameters don't have any
-               // numeric keys, we'll test that to differentiate the cases.
-               if ( count( $args ) === 1 && isset( $args[0] ) && is_array( $args[0] ) ) {
-                       if ( $args[0] === [] ) {
-                               $args = [];
-                       } else {
-                               foreach ( $args[0] as $key => $value ) {
-                                       if ( is_int( $key ) ) {
-                                               $args = $args[0];
-                                               break;
-                                       }
-                               }
-                       }
-               }
-
-               $this->parameters = array_merge( $this->parameters, array_values( $args ) );
-               return $this;
-       }
-
-       /**
-        * Add parameters that are substituted after parsing or escaping.
-        * In other words the parsing process cannot access the contents
-        * of this type of parameter, and you need to make sure it is
-        * sanitized beforehand.  The parser will see "$n", instead.
-        *
-        * @since 1.17
-        *
-        * @param mixed $params,... Raw parameters as strings, or a single argument that is
-        * an array of raw parameters.
-        *
-        * @return Message $this
-        */
-       public function rawParams( /*...*/ ) {
-               $params = func_get_args();
-               if ( isset( $params[0] ) && is_array( $params[0] ) ) {
-                       $params = $params[0];
-               }
-               foreach ( $params as $param ) {
-                       $this->parameters[] = self::rawParam( $param );
-               }
-               return $this;
-       }
-
-       /**
-        * Add parameters that are numeric and will be passed through
-        * Language::formatNum before substitution
-        *
-        * @since 1.18
-        *
-        * @param mixed $param,... Numeric parameters, or a single argument that is
-        * an array of numeric parameters.
-        *
-        * @return Message $this
-        */
-       public function numParams( /*...*/ ) {
-               $params = func_get_args();
-               if ( isset( $params[0] ) && is_array( $params[0] ) ) {
-                       $params = $params[0];
-               }
-               foreach ( $params as $param ) {
-                       $this->parameters[] = self::numParam( $param );
-               }
-               return $this;
-       }
-
-       /**
-        * Add parameters that are durations of time and will be passed through
-        * Language::formatDuration before substitution
-        *
-        * @since 1.22
-        *
-        * @param int|int[] $param,... Duration parameters, or a single argument that is
-        * an array of duration parameters.
-        *
-        * @return Message $this
-        */
-       public function durationParams( /*...*/ ) {
-               $params = func_get_args();
-               if ( isset( $params[0] ) && is_array( $params[0] ) ) {
-                       $params = $params[0];
-               }
-               foreach ( $params as $param ) {
-                       $this->parameters[] = self::durationParam( $param );
-               }
-               return $this;
-       }
-
-       /**
-        * Add parameters that are expiration times and will be passed through
-        * Language::formatExpiry before substitution
-        *
-        * @since 1.22
-        *
-        * @param string|string[] $param,... Expiry parameters, or a single argument that is
-        * an array of expiry parameters.
-        *
-        * @return Message $this
-        */
-       public function expiryParams( /*...*/ ) {
-               $params = func_get_args();
-               if ( isset( $params[0] ) && is_array( $params[0] ) ) {
-                       $params = $params[0];
-               }
-               foreach ( $params as $param ) {
-                       $this->parameters[] = self::expiryParam( $param );
-               }
-               return $this;
-       }
-
-       /**
-        * Add parameters that are time periods and will be passed through
-        * Language::formatTimePeriod before substitution
-        *
-        * @since 1.22
-        *
-        * @param int|int[] $param,... Time period parameters, or a single argument that is
-        * an array of time period parameters.
-        *
-        * @return Message $this
-        */
-       public function timeperiodParams( /*...*/ ) {
-               $params = func_get_args();
-               if ( isset( $params[0] ) && is_array( $params[0] ) ) {
-                       $params = $params[0];
-               }
-               foreach ( $params as $param ) {
-                       $this->parameters[] = self::timeperiodParam( $param );
-               }
-               return $this;
-       }
-
-       /**
-        * Add parameters that are file sizes and will be passed through
-        * Language::formatSize before substitution
-        *
-        * @since 1.22
-        *
-        * @param int|int[] $param,... Size parameters, or a single argument that is
-        * an array of size parameters.
-        *
-        * @return Message $this
-        */
-       public function sizeParams( /*...*/ ) {
-               $params = func_get_args();
-               if ( isset( $params[0] ) && is_array( $params[0] ) ) {
-                       $params = $params[0];
-               }
-               foreach ( $params as $param ) {
-                       $this->parameters[] = self::sizeParam( $param );
-               }
-               return $this;
-       }
-
-       /**
-        * Add parameters that are bitrates and will be passed through
-        * Language::formatBitrate before substitution
-        *
-        * @since 1.22
-        *
-        * @param int|int[] $param,... Bit rate parameters, or a single argument that is
-        * an array of bit rate parameters.
-        *
-        * @return Message $this
-        */
-       public function bitrateParams( /*...*/ ) {
-               $params = func_get_args();
-               if ( isset( $params[0] ) && is_array( $params[0] ) ) {
-                       $params = $params[0];
-               }
-               foreach ( $params as $param ) {
-                       $this->parameters[] = self::bitrateParam( $param );
-               }
-               return $this;
-       }
-
-       /**
-        * Add parameters that are plaintext and will be passed through without
-        * the content being evaluated.  Plaintext parameters are not valid as
-        * arguments to parser functions. This differs from self::rawParams in
-        * that the Message class handles escaping to match the output format.
-        *
-        * @since 1.25
-        *
-        * @param string|string[] $param,... plaintext parameters, or a single argument that is
-        * an array of plaintext parameters.
-        *
-        * @return Message $this
-        */
-       public function plaintextParams( /*...*/ ) {
-               $params = func_get_args();
-               if ( isset( $params[0] ) && is_array( $params[0] ) ) {
-                       $params = $params[0];
-               }
-               foreach ( $params as $param ) {
-                       $this->parameters[] = self::plaintextParam( $param );
-               }
-               return $this;
-       }
-
-       /**
-        * Set the language and the title from a context object
-        *
-        * @since 1.19
-        *
-        * @param IContextSource $context
-        *
-        * @return Message $this
-        */
-       public function setContext( IContextSource $context ) {
-               $this->inLanguage( $context->getLanguage() );
-               $this->title( $context->getTitle() );
-               $this->interface = true;
-
-               return $this;
-       }
-
-       /**
-        * Request the message in any language that is supported.
-        *
-        * As a side effect interface message status is unconditionally
-        * turned off.
-        *
-        * @since 1.17
-        * @param Language|string $lang Language code or Language object.
-        * @return Message $this
-        * @throws MWException
-        */
-       public function inLanguage( $lang ) {
-               $previousLanguage = $this->language;
-
-               if ( $lang instanceof Language ) {
-                       $this->language = $lang;
-               } elseif ( is_string( $lang ) ) {
-                       if ( !$this->language instanceof Language || $this->language->getCode() != $lang ) {
-                               $this->language = Language::factory( $lang );
-                       }
-               } elseif ( $lang instanceof StubUserLang ) {
-                       $this->language = false;
-               } else {
-                       $type = gettype( $lang );
-                       throw new MWException( __METHOD__ . " must be "
-                               . "passed a String or Language object; $type given"
-                       );
-               }
-
-               if ( $this->language !== $previousLanguage ) {
-                       // The language has changed. Clear the message cache.
-                       $this->message = null;
-               }
-               $this->interface = false;
-               return $this;
-       }
-
-       /**
-        * Request the message in the wiki's content language,
-        * unless it is disabled for this message.
-        *
-        * @since 1.17
-        * @see $wgForceUIMsgAsContentMsg
-        *
-        * @return Message $this
-        */
-       public function inContentLanguage() {
-               global $wgForceUIMsgAsContentMsg;
-               if ( in_array( $this->key, (array)$wgForceUIMsgAsContentMsg ) ) {
-                       return $this;
-               }
-
-               $this->inLanguage( MediaWikiServices::getInstance()->getContentLanguage() );
-               return $this;
-       }
-
-       /**
-        * Allows manipulating the interface message flag directly.
-        * Can be used to restore the flag after setting a language.
-        *
-        * @since 1.20
-        *
-        * @param bool $interface
-        *
-        * @return Message $this
-        */
-       public function setInterfaceMessageFlag( $interface ) {
-               $this->interface = (bool)$interface;
-               return $this;
-       }
-
-       /**
-        * Enable or disable database use.
-        *
-        * @since 1.17
-        *
-        * @param bool $useDatabase
-        *
-        * @return Message $this
-        */
-       public function useDatabase( $useDatabase ) {
-               $this->useDatabase = (bool)$useDatabase;
-               $this->message = null;
-               return $this;
-       }
-
-       /**
-        * Set the Title object to use as context when transforming the message
-        *
-        * @since 1.18
-        *
-        * @param Title $title
-        *
-        * @return Message $this
-        */
-       public function title( $title ) {
-               $this->title = $title;
-               return $this;
-       }
-
-       /**
-        * Returns the message as a Content object.
-        *
-        * @return Content
-        */
-       public function content() {
-               if ( !$this->content ) {
-                       $this->content = new MessageContent( $this );
-               }
-
-               return $this->content;
-       }
-
-       /**
-        * Returns the message parsed from wikitext to HTML.
-        *
-        * @since 1.17
-        *
-        * @param string|null $format One of the FORMAT_* constants. Null means use whatever was used
-        *   the last time (this is for B/C and should be avoided).
-        *
-        * @return string HTML
-        * @suppress SecurityCheck-DoubleEscaped phan false positive
-        */
-       public function toString( $format = null ) {
-               if ( $format === null ) {
-                       $ex = new LogicException( __METHOD__ . ' using implicit format: ' . $this->format );
-                       \MediaWiki\Logger\LoggerFactory::getInstance( 'message-format' )->warning(
-                               $ex->getMessage(), [ 'exception' => $ex, 'format' => $this->format, 'key' => $this->key ] );
-                       $format = $this->format;
-               }
-               $string = $this->fetchMessage();
-
-               if ( $string === false ) {
-                       // Err on the side of safety, ensure that the output
-                       // is always html safe in the event the message key is
-                       // missing, since in that case its highly likely the
-                       // message key is user-controlled.
-                       // '⧼' is used instead of '<' to side-step any
-                       // double-escaping issues.
-                       // (Keep synchronised with mw.Message#toString in JS.)
-                       return '⧼' . htmlspecialchars( $this->key ) . '⧽';
-               }
-
-               # Replace $* with a list of parameters for &uselang=qqx.
-               if ( strpos( $string, '$*' ) !== false ) {
-                       $paramlist = '';
-                       if ( $this->parameters !== [] ) {
-                               $paramlist = ': $' . implode( ', $', range( 1, count( $this->parameters ) ) );
-                       }
-                       $string = str_replace( '$*', $paramlist, $string );
-               }
-
-               # Replace parameters before text parsing
-               $string = $this->replaceParameters( $string, 'before', $format );
-
-               # Maybe transform using the full parser
-               if ( $format === self::FORMAT_PARSE ) {
-                       $string = $this->parseText( $string );
-                       $string = Parser::stripOuterParagraph( $string );
-               } elseif ( $format === self::FORMAT_BLOCK_PARSE ) {
-                       $string = $this->parseText( $string );
-               } elseif ( $format === self::FORMAT_TEXT ) {
-                       $string = $this->transformText( $string );
-               } elseif ( $format === self::FORMAT_ESCAPED ) {
-                       $string = $this->transformText( $string );
-                       $string = htmlspecialchars( $string, ENT_QUOTES, 'UTF-8', false );
-               }
-
-               # Raw parameter replacement
-               $string = $this->replaceParameters( $string, 'after', $format );
-
-               return $string;
-       }
-
-       /**
-        * Magic method implementation of the above (for PHP >= 5.2.0), so we can do, eg:
-        *     $foo = new Message( $key );
-        *     $string = "<abbr>$foo</abbr>";
-        *
-        * @since 1.18
-        *
-        * @return string
-        */
-       public function __toString() {
-               // PHP doesn't allow __toString to throw exceptions and will
-               // trigger a fatal error if it does. So, catch any exceptions.
-
-               try {
-                       return $this->toString( self::FORMAT_PARSE );
-               } catch ( Exception $ex ) {
-                       try {
-                               trigger_error( "Exception caught in " . __METHOD__ . " (message " . $this->key . "): "
-                                       . $ex, E_USER_WARNING );
-                       } catch ( Exception $ex ) {
-                               // Doh! Cause a fatal error after all?
-                       }
-
-                       return '⧼' . htmlspecialchars( $this->key ) . '⧽';
-               }
-       }
-
-       /**
-        * Fully parse the text from wikitext to HTML.
-        *
-        * @since 1.17
-        *
-        * @return string Parsed HTML.
-        */
-       public function parse() {
-               $this->format = self::FORMAT_PARSE;
-               return $this->toString( self::FORMAT_PARSE );
-       }
-
-       /**
-        * Returns the message text. {{-transformation is done.
-        *
-        * @since 1.17
-        *
-        * @return string Unescaped message text.
-        */
-       public function text() {
-               $this->format = self::FORMAT_TEXT;
-               return $this->toString( self::FORMAT_TEXT );
-       }
-
-       /**
-        * Returns the message text as-is, only parameters are substituted.
-        *
-        * @since 1.17
-        *
-        * @return string Unescaped untransformed message text.
-        */
-       public function plain() {
-               $this->format = self::FORMAT_PLAIN;
-               return $this->toString( self::FORMAT_PLAIN );
-       }
-
-       /**
-        * Returns the parsed message text which is always surrounded by a block element.
-        *
-        * @since 1.17
-        *
-        * @return string HTML
-        */
-       public function parseAsBlock() {
-               $this->format = self::FORMAT_BLOCK_PARSE;
-               return $this->toString( self::FORMAT_BLOCK_PARSE );
-       }
-
-       /**
-        * Returns the message text. {{-transformation is done and the result
-        * is escaped excluding any raw parameters.
-        *
-        * @since 1.17
-        *
-        * @return string Escaped message text.
-        */
-       public function escaped() {
-               $this->format = self::FORMAT_ESCAPED;
-               return $this->toString( self::FORMAT_ESCAPED );
-       }
-
-       /**
-        * Check whether a message key has been defined currently.
-        *
-        * @since 1.17
-        *
-        * @return bool
-        */
-       public function exists() {
-               return $this->fetchMessage() !== false;
-       }
-
-       /**
-        * Check whether a message does not exist, or is an empty string
-        *
-        * @since 1.18
-        * @todo FIXME: Merge with isDisabled()?
-        *
-        * @return bool
-        */
-       public function isBlank() {
-               $message = $this->fetchMessage();
-               return $message === false || $message === '';
-       }
-
-       /**
-        * Check whether a message does not exist, is an empty string, or is "-".
-        *
-        * @since 1.18
-        *
-        * @return bool
-        */
-       public function isDisabled() {
-               $message = $this->fetchMessage();
-               return $message === false || $message === '' || $message === '-';
-       }
-
-       /**
-        * @since 1.17
-        *
-        * @param mixed $raw
-        *
-        * @return array Array with a single "raw" key.
-        */
-       public static function rawParam( $raw ) {
-               return [ 'raw' => $raw ];
-       }
-
-       /**
-        * @since 1.18
-        *
-        * @param mixed $num
-        *
-        * @return array Array with a single "num" key.
-        */
-       public static function numParam( $num ) {
-               return [ 'num' => $num ];
-       }
-
-       /**
-        * @since 1.22
-        *
-        * @param int $duration
-        *
-        * @return int[] Array with a single "duration" key.
-        */
-       public static function durationParam( $duration ) {
-               return [ 'duration' => $duration ];
-       }
-
-       /**
-        * @since 1.22
-        *
-        * @param string $expiry
-        *
-        * @return string[] Array with a single "expiry" key.
-        */
-       public static function expiryParam( $expiry ) {
-               return [ 'expiry' => $expiry ];
-       }
-
-       /**
-        * @since 1.22
-        *
-        * @param int $period
-        *
-        * @return int[] Array with a single "period" key.
-        */
-       public static function timeperiodParam( $period ) {
-               return [ 'period' => $period ];
-       }
-
-       /**
-        * @since 1.22
-        *
-        * @param int $size
-        *
-        * @return int[] Array with a single "size" key.
-        */
-       public static function sizeParam( $size ) {
-               return [ 'size' => $size ];
-       }
-
-       /**
-        * @since 1.22
-        *
-        * @param int $bitrate
-        *
-        * @return int[] Array with a single "bitrate" key.
-        */
-       public static function bitrateParam( $bitrate ) {
-               return [ 'bitrate' => $bitrate ];
-       }
-
-       /**
-        * @since 1.25
-        *
-        * @param string $plaintext
-        *
-        * @return string[] Array with a single "plaintext" key.
-        */
-       public static function plaintextParam( $plaintext ) {
-               return [ 'plaintext' => $plaintext ];
-       }
-
-       /**
-        * @since 1.29
-        *
-        * @param array $list
-        * @param string $type 'comma', 'semicolon', 'pipe', 'text'
-        * @return array Array with "list" and "type" keys.
-        */
-       public static function listParam( array $list, $type = 'text' ) {
-               if ( !isset( self::$listTypeMap[$type] ) ) {
-                       throw new InvalidArgumentException(
-                               "Invalid type '$type'. Known types are: " . implode( ', ', array_keys( self::$listTypeMap ) )
-                       );
-               }
-               return [ 'list' => $list, 'type' => $type ];
-       }
-
-       /**
-        * Substitutes any parameters into the message text.
-        *
-        * @since 1.17
-        *
-        * @param string $message The message text.
-        * @param string $type Either "before" or "after".
-        * @param string $format One of the FORMAT_* constants.
-        *
-        * @return string
-        */
-       protected function replaceParameters( $message, $type, $format ) {
-               // A temporary marker for $1 parameters that is only valid
-               // in non-attribute contexts. However if the entire message is escaped
-               // then we don't want to use it because it will be mangled in all contexts
-               // and its unnessary as ->escaped() messages aren't html.
-               $marker = $format === self::FORMAT_ESCAPED ? '$' : '$\'"';
-               $replacementKeys = [];
-               foreach ( $this->parameters as $n => $param ) {
-                       list( $paramType, $value ) = $this->extractParam( $param, $format );
-                       if ( $type === 'before' ) {
-                               if ( $paramType === 'before' ) {
-                                       $replacementKeys['$' . ( $n + 1 )] = $value;
-                               } else /* $paramType === 'after' */ {
-                                       // To protect against XSS from replacing parameters
-                                       // inside html attributes, we convert $1 to $'"1.
-                                       // In the event that one of the parameters ends up
-                                       // in an attribute, either the ' or the " will be
-                                       // escaped, breaking the replacement and avoiding XSS.
-                                       $replacementKeys['$' . ( $n + 1 )] = $marker . ( $n + 1 );
-                               }
-                       } elseif ( $paramType === 'after' ) {
-                               $replacementKeys[$marker . ( $n + 1 )] = $value;
-                       }
-               }
-               return strtr( $message, $replacementKeys );
-       }
-
-       /**
-        * Extracts the parameter type and preprocessed the value if needed.
-        *
-        * @since 1.18
-        *
-        * @param mixed $param Parameter as defined in this class.
-        * @param string $format One of the FORMAT_* constants.
-        *
-        * @return array Array with the parameter type (either "before" or "after") and the value.
-        */
-       protected function extractParam( $param, $format ) {
-               if ( is_array( $param ) ) {
-                       if ( isset( $param['raw'] ) ) {
-                               return [ 'after', $param['raw'] ];
-                       } elseif ( isset( $param['num'] ) ) {
-                               // Replace number params always in before step for now.
-                               // No support for combined raw and num params
-                               return [ 'before', $this->getLanguage()->formatNum( $param['num'] ) ];
-                       } elseif ( isset( $param['duration'] ) ) {
-                               return [ 'before', $this->getLanguage()->formatDuration( $param['duration'] ) ];
-                       } elseif ( isset( $param['expiry'] ) ) {
-                               return [ 'before', $this->getLanguage()->formatExpiry( $param['expiry'] ) ];
-                       } elseif ( isset( $param['period'] ) ) {
-                               return [ 'before', $this->getLanguage()->formatTimePeriod( $param['period'] ) ];
-                       } elseif ( isset( $param['size'] ) ) {
-                               return [ 'before', $this->getLanguage()->formatSize( $param['size'] ) ];
-                       } elseif ( isset( $param['bitrate'] ) ) {
-                               return [ 'before', $this->getLanguage()->formatBitrate( $param['bitrate'] ) ];
-                       } elseif ( isset( $param['plaintext'] ) ) {
-                               return [ 'after', $this->formatPlaintext( $param['plaintext'], $format ) ];
-                       } elseif ( isset( $param['list'] ) ) {
-                               return $this->formatListParam( $param['list'], $param['type'], $format );
-                       } else {
-                               if ( !is_scalar( $param ) ) {
-                                       $param = serialize( $param );
-                               }
-                               \MediaWiki\Logger\LoggerFactory::getInstance( 'Bug58676' )->warning(
-                                       'Invalid parameter for message "{msgkey}": {param}',
-                                       [
-                                               'exception' => new Exception,
-                                               'msgkey' => $this->getKey(),
-                                               'param' => htmlspecialchars( $param ),
-                                       ]
-                               );
-
-                               return [ 'before', '[INVALID]' ];
-                       }
-               } elseif ( $param instanceof Message ) {
-                       // Match language, flags, etc. to the current message.
-                       $msg = clone $param;
-                       if ( $msg->language !== $this->language || $msg->useDatabase !== $this->useDatabase ) {
-                               // Cache depends on these parameters
-                               $msg->message = null;
-                       }
-                       $msg->interface = $this->interface;
-                       $msg->language = $this->language;
-                       $msg->useDatabase = $this->useDatabase;
-                       $msg->title = $this->title;
-
-                       // DWIM
-                       if ( $format === 'block-parse' ) {
-                               $format = 'parse';
-                       }
-                       $msg->format = $format;
-
-                       // Message objects should not be before parameters because
-                       // then they'll get double escaped. If the message needs to be
-                       // escaped, it'll happen right here when we call toString().
-                       return [ 'after', $msg->toString( $format ) ];
-               } else {
-                       return [ 'before', $param ];
-               }
-       }
-
-       /**
-        * Wrapper for what ever method we use to parse wikitext.
-        *
-        * @since 1.17
-        *
-        * @param string $string Wikitext message contents.
-        *
-        * @return string Wikitext parsed into HTML.
-        */
-       protected function parseText( $string ) {
-               $out = MessageCache::singleton()->parse(
-                       $string,
-                       $this->title,
-                       /*linestart*/true,
-                       $this->interface,
-                       $this->getLanguage()
-               );
-
-               return $out instanceof ParserOutput
-                       ? $out->getText( [
-                               'enableSectionEditLinks' => false,
-                               // Wrapping messages in an extra <div> is probably not expected. If
-                               // they're outside the content area they probably shouldn't be
-                               // targeted by CSS that's targeting the parser output, and if
-                               // they're inside they already are from the outer div.
-                               'unwrap' => true,
-                       ] )
-                       : $out;
-       }
-
-       /**
-        * Wrapper for what ever method we use to {{-transform wikitext.
-        *
-        * @since 1.17
-        *
-        * @param string $string Wikitext message contents.
-        *
-        * @return string Wikitext with {{-constructs replaced with their values.
-        */
-       protected function transformText( $string ) {
-               return MessageCache::singleton()->transform(
-                       $string,
-                       $this->interface,
-                       $this->getLanguage(),
-                       $this->title
-               );
-       }
-
-       /**
-        * Wrapper for what ever method we use to get message contents.
-        *
-        * @since 1.17
-        *
-        * @return string
-        * @throws MWException If message key array is empty.
-        */
-       protected function fetchMessage() {
-               if ( $this->message === null ) {
-                       $cache = MessageCache::singleton();
-
-                       foreach ( $this->keysToTry as $key ) {
-                               $message = $cache->get( $key, $this->useDatabase, $this->getLanguage() );
-                               if ( $message !== false && $message !== '' ) {
-                                       break;
-                               }
-                       }
-
-                       // NOTE: The constructor makes sure keysToTry isn't empty,
-                       //       so we know that $key and $message are initialized.
-                       $this->key = $key;
-                       $this->message = $message;
-               }
-               return $this->message;
-       }
-
-       /**
-        * Formats a message parameter wrapped with 'plaintext'. Ensures that
-        * the entire string is displayed unchanged when displayed in the output
-        * format.
-        *
-        * @since 1.25
-        *
-        * @param string $plaintext String to ensure plaintext output of
-        * @param string $format One of the FORMAT_* constants.
-        *
-        * @return string Input plaintext encoded for output to $format
-        */
-       protected function formatPlaintext( $plaintext, $format ) {
-               switch ( $format ) {
-                       case self::FORMAT_TEXT:
-                       case self::FORMAT_PLAIN:
-                               return $plaintext;
-
-                       case self::FORMAT_PARSE:
-                       case self::FORMAT_BLOCK_PARSE:
-                       case self::FORMAT_ESCAPED:
-                       default:
-                               return htmlspecialchars( $plaintext, ENT_QUOTES );
-               }
-       }
-
-       /**
-        * Formats a list of parameters as a concatenated string.
-        * @since 1.29
-        * @param array $params
-        * @param string $listType
-        * @param string $format One of the FORMAT_* constants.
-        * @return array Array with the parameter type (either "before" or "after") and the value.
-        */
-       protected function formatListParam( array $params, $listType, $format ) {
-               if ( !isset( self::$listTypeMap[$listType] ) ) {
-                       $warning = 'Invalid list type for message "' . $this->getKey() . '": '
-                               . htmlspecialchars( $listType )
-                               . ' (params are ' . htmlspecialchars( serialize( $params ) ) . ')';
-                       trigger_error( $warning, E_USER_WARNING );
-                       $e = new Exception;
-                       wfDebugLog( 'Bug58676', $warning . "\n" . $e->getTraceAsString() );
-                       return [ 'before', '[INVALID]' ];
-               }
-               $func = self::$listTypeMap[$listType];
-
-               // Handle an empty list sensibly
-               if ( !$params ) {
-                       return [ 'before', $this->getLanguage()->$func( [] ) ];
-               }
-
-               // First, determine what kinds of list items we have
-               $types = [];
-               $vars = [];
-               $list = [];
-               foreach ( $params as $n => $p ) {
-                       list( $type, $value ) = $this->extractParam( $p, $format );
-                       $types[$type] = true;
-                       $list[] = $value;
-                       $vars[] = '$' . ( $n + 1 );
-               }
-
-               // Easy case: all are 'before' or 'after', so just join the
-               // values and use the same type.
-               if ( count( $types ) === 1 ) {
-                       return [ key( $types ), $this->getLanguage()->$func( $list ) ];
-               }
-
-               // Hard case: We need to process each value per its type, then
-               // return the concatenated values as 'after'. We handle this by turning
-               // the list into a RawMessage and processing that as a parameter.
-               $vars = $this->getLanguage()->$func( $vars );
-               return $this->extractParam( new RawMessage( $vars, $params ), $format );
-       }
-}
index 5833677..57cd74a 100644 (file)
@@ -1723,7 +1723,7 @@ class OutputPage extends ContextSource {
        /**
         * Get the files used on this page
         *
-        * @return array (dbKey => array('time' => MW timestamp or null, 'sha1' => sha1 or ''))
+        * @return array [ dbKey => [ 'time' => MW timestamp or null, 'sha1' => sha1 or '' ] ]
         * @since 1.18
         */
        public function getFileSearchOptions() {
index 2d9216d..b63a84d 100644 (file)
@@ -123,10 +123,7 @@ class PHPVersionCheck {
                $phpInfo = $this->getPHPInfo();
                $minimumVersion = $phpInfo['minSupported'];
                $otherInfo = $this->getPHPInfo( $phpInfo['implementation'] === 'HHVM' ? 'PHP' : 'HHVM' );
-               if (
-                       !function_exists( 'version_compare' )
-                       || version_compare( $phpInfo['version'], $minimumVersion ) < 0
-               ) {
+               if ( version_compare( $phpInfo['version'], $minimumVersion ) < 0 ) {
                        $shortText = "MediaWiki $this->mwVersion requires at least {$phpInfo['implementation']}"
                                . " version $minimumVersion or {$otherInfo['implementation']} version "
                                . "{$otherInfo['minSupported']}, you are using {$phpInfo['implementation']} "
index eb52d7c..2882e66 100644 (file)
@@ -53,8 +53,8 @@
  *   - In a pattern $1, $2, etc... will be replaced with the relevant contents
  *   - If you used a keyed array as a path pattern, $key will be replaced with
  *     the relevant contents
- *   - The default behavior is equivalent to `array( 'title' => '$1' )`,
- *     if you don't want the title parameter you can explicitly use `array( 'title' => false )`
+ *   - The default behavior is equivalent to `[ 'title' => '$1' ]`,
+ *     if you don't want the title parameter you can explicitly use `[ 'title' => false ]`
  *   - You can specify a value that won't have replacements in it
  *     using `'foo' => [ 'value' => 'bar' ];`
  *
@@ -80,7 +80,7 @@ class PathRouter {
        /**
         * Protected helper to do the actual bulk work of adding a single pattern.
         * This is in a separate method so that add() can handle the difference between
-        * a single string $path and an array() $path that contains multiple path
+        * a single string $path and an array $path that contains multiple path
         * patterns each with an associated $key to pass on.
         * @param string $path
         * @param array $params
@@ -247,9 +247,9 @@ class PathRouter {
                }
 
                // We know the difference between null (no matches) and
-               // array() (a match with no data) but our WebRequest caller
-               // expects array() even when we have no matches so return
-               // a array() when we have null
+               // [] (a match with no data) but our WebRequest caller
+               // expects [] even when we have no matches so return
+               // a [] when we have null
                return $matches ?? [];
        }
 
index e443803..202014f 100644 (file)
@@ -324,7 +324,7 @@ class PermissionManager {
         * Add the resulting error code to the errors array
         *
         * @param array $errors List of current errors
-        * @param array $result Result of errors
+        * @param array|string|MessageSpecifier|false $result Result of errors
         *
         * @return array List of errors
         */
index b7b28af..f69f1a4 100644 (file)
@@ -1979,7 +1979,7 @@ class Title implements LinkTarget, IDBAccessObject {
         *
         * @param string|string[] $query An optional query string,
         *   not used for interwiki links. Can be specified as an associative array as well,
-        *   e.g., array( 'action' => 'edit' ) (keys and values will be URL-escaped).
+        *   e.g., [ 'action' => 'edit' ] (keys and values will be URL-escaped).
         *   Some query patterns will trigger various shorturl path replacements.
         * @param string|string[]|bool $query2 An optional secondary query array. This one MUST
         *   be an array. If a string is passed it will be interpreted as a deprecated
@@ -2256,7 +2256,7 @@ class Title implements LinkTarget, IDBAccessObject {
         * Add the resulting error code to the errors array
         *
         * @param array $errors List of current errors
-        * @param array $result Result of errors
+        * @param array|string|MessageSpecifier|false $result Result of errors
         *
         * @return array List of errors
         */
index ebdbc42..d9e185e 100644 (file)
@@ -60,7 +60,7 @@ class TrackingCategories {
 
        /**
         * Read the global and extract title objects from the corresponding messages
-        * @return array Array( 'msg' => Title, 'cats' => Title[] )
+        * @return array [ 'msg' => Title, 'cats' => Title[] ]
         */
        public function getTrackingCategories() {
                $categories = array_merge(
index 76d94b2..6593e49 100644 (file)
@@ -1140,7 +1140,7 @@ HTML;
        /**
         * Parse the Accept-Language header sent by the client into an array
         *
-        * @return array Array( languageCode => q-value ) sorted by q-value in
+        * @return array [ languageCode => q-value ] sorted by q-value in
         *   descending order then appearing time in the header in ascending order.
         * May contain the "language" '*', which applies to languages other than those explicitly listed.
         * This is aligned with rfc2616 section 14.4
index 538b0a1..b1d5a50 100644 (file)
@@ -142,6 +142,7 @@ class HistoryAction extends FormlessAction {
 
        /**
         * Print the history page for an article.
+        * @return string|null
         */
        function onView() {
                $out = $this->getOutput();
@@ -151,7 +152,7 @@ class HistoryAction extends FormlessAction {
                 * Allow client caching.
                 */
                if ( $out->checkLastModified( $this->page->getTouched() ) ) {
-                       return; // Client cache fresh and headers sent, nothing more to do.
+                       return null; // Client cache fresh and headers sent, nothing more to do.
                }
 
                $this->preCacheMessages();
@@ -185,7 +186,7 @@ class HistoryAction extends FormlessAction {
                $feedType = $request->getRawVal( 'feed' );
                if ( $feedType !== null ) {
                        $this->feed( $feedType );
-                       return;
+                       return null;
                }
 
                $this->addHelpLink(
@@ -216,7 +217,7 @@ class HistoryAction extends FormlessAction {
                                ]
                        );
 
-                       return;
+                       return null;
                }
 
                $ts = $this->getTimestampFromRequest( $request );
@@ -300,6 +301,8 @@ class HistoryAction extends FormlessAction {
                        $pager->getNavigationBar()
                );
                $out->preventClickjacking( $pager->getPreventClickjacking() );
+
+               return null;
        }
 
        /**
index 505c9d5..3e4e614 100644 (file)
@@ -50,6 +50,7 @@ class RawAction extends FormlessAction {
 
        /**
         * @suppress SecurityCheck-XSS Non html mime type
+        * @return string|null
         */
        function onView() {
                $this->getOutput()->disable();
@@ -58,11 +59,11 @@ class RawAction extends FormlessAction {
                $config = $this->context->getConfig();
 
                if ( !$request->checkUrlExtension() ) {
-                       return;
+                       return null;
                }
 
                if ( $this->getOutput()->checkLastModified( $this->page->getTouched() ) ) {
-                       return; // Client cache fresh and headers sent, nothing more to do.
+                       return null; // Client cache fresh and headers sent, nothing more to do.
                }
 
                $contentType = $this->getContentType();
@@ -173,6 +174,8 @@ class RawAction extends FormlessAction {
                }
 
                echo $text;
+
+               return null;
        }
 
        /**
index 6271128..f53d2b9 100644 (file)
@@ -54,7 +54,7 @@ class ApiCSPReport extends ApiBase {
                        // XXX Is it ok to put untrusted data into log??
                        'csp-report' => $report,
                        'method' => __METHOD__,
-                       'user' => $this->getUser()->getName(),
+                       'user_id' => $this->getUser()->getId() || 'logged-out',
                        'user-agent' => $userAgent,
                        'source' => $this->getParameter( 'source' ),
                ] );
@@ -104,11 +104,11 @@ class ApiCSPReport extends ApiBase {
                        ) ||
                        (
                                isset( $report['blocked-uri'] ) &&
-                               isset( $falsePositives[$report['blocked-uri']] )
+                               $this->matchUrlPattern( $report['blocked-uri'], $falsePositives )
                        ) ||
                        (
                                isset( $report['source-file'] ) &&
-                               isset( $falsePositives[$report['source-file']] )
+                               $this->matchUrlPattern( $report['source-file'], $falsePositives )
                        )
                ) {
                        // False positive due to:
@@ -119,6 +119,39 @@ class ApiCSPReport extends ApiBase {
                return $flags;
        }
 
+       /**
+        * @param string $url
+        * @param string[] $patterns
+        * @return bool
+        */
+       private function matchUrlPattern( $url, array $patterns ) {
+               if ( isset( $patterns[ $url ] ) ) {
+                       return true;
+               }
+
+               $bits = wfParseUrl( $url );
+               unset( $bits['user'], $bits['pass'], $bits['query'], $bits['fragment'] );
+               $bits['path'] = '';
+               $serverUrl = wfAssembleUrl( $bits );
+               if ( isset( $patterns[$serverUrl] ) ) {
+                       // The origin of the url matches a pattern,
+                       // e.g. "https://example.org" matches "https://example.org/foo/b?a#r"
+                       return true;
+               }
+               foreach ( $patterns as $pattern => $val ) {
+                       // We only use this pattern if it ends in a slash, this prevents
+                       // "/foos" from matching "/foo", and "https://good.combo.bad" matching
+                       // "https://good.com".
+                       if ( substr( $pattern, -1 ) === '/' && strpos( $url, $pattern ) === 0 ) {
+                               // The pattern starts with the same as the url
+                               // e.g. "https://example.org/foo/" matches "https://example.org/foo/b?a#r"
+                               return true;
+                       }
+               }
+
+               return false;
+       }
+
        /**
         * Output an api error if post body is obviously not OK.
         */
@@ -176,15 +209,32 @@ class ApiCSPReport extends ApiBase {
                        $flagText = '[' . implode( ', ', $flags ) . ']';
                }
 
-               $blockedFile = $report['blocked-uri'] ?? 'n/a';
+               $blockedOrigin = isset( $report['blocked-uri'] )
+                       ? $this->originFromUrl( $report['blocked-uri'] )
+                       : 'n/a';
                $page = $report['document-uri'] ?? 'n/a';
-               $line = isset( $report['line-number'] ) ? ':' . $report['line-number'] : '';
+               $line = isset( $report['line-number'] )
+                       ? ':' . $report['line-number']
+                       : '';
                $warningText = $flagText .
-                       ' Received CSP report: <' . $blockedFile .
-                       '> blocked from being loaded on <' . $page . '>' . $line;
+                       ' Received CSP report: <' . $blockedOrigin . '>' .
+                       ' blocked from being loaded on <' . $page . '>' . $line;
                return $warningText;
        }
 
+       /**
+        * @param string $url
+        * @return string
+        */
+       private function originFromUrl( $url ) {
+               $bits = wfParseUrl( $url );
+               unset( $bits['user'], $bits['pass'], $bits['query'], $bits['fragment'] );
+               $bits['path'] = '';
+               $serverUrl = wfAssembleUrl( $bits );
+               // e.g. "https://example.org" from "https://example.org/foo/b?a#r"
+               return $serverUrl;
+       }
+
        /**
         * Stop processing the request, and output/log an error
         *
index b845c57..ed17e07 100644 (file)
@@ -1644,7 +1644,7 @@ class ApiMain extends ApiBase {
                        '$schema' => '/mediawiki/api/request/0.0.1',
                        'meta' => [
                                'request_id' => WebRequest::getRequestId(),
-                               'id' => UIDGenerator::newUUIDv1(),
+                               'id' => UIDGenerator::newUUIDv4(),
                                'dt' => wfTimestamp( TS_ISO_8601 ),
                                'domain' => $this->getConfig()->get( 'ServerName' ),
                                'stream' => 'mediawiki.api-request'
index 47ff0fb..59ec4f6 100644 (file)
@@ -252,7 +252,7 @@ abstract class ApiQueryBase extends ApiBase {
        }
 
        /**
-        * Equivalent to addWhere(array($field => $value))
+        * Equivalent to addWhere( [ $field => $value ] )
         * @param string $field Field name
         * @param string|string[] $value Value; ignored if null or empty array
         */
index d4ffd44..af345d5 100644 (file)
        "apihelp-query+langlinks-param-dir": "La dirección en que ordenar la lista.",
        "apihelp-query+langlinks-param-inlanguagecode": "Código de idioma para los nombres de idiomas localizados.",
        "apihelp-query+langlinks-example-simple": "Obtener los enlaces interlingüísticos de la página <kbd>Main Page</kbd>.",
+       "apihelp-query+languageinfo-summary": "Devolver información sobre los idiomas disponibles.",
+       "apihelp-query+languageinfo-paramvalue-prop-code": "El código lingüístico (es específico de MediaWiki, pero existen coincidencias con otras normas.)",
+       "apihelp-query+languageinfo-paramvalue-prop-dir": "La dirección de escritura del idioma (bien <code>ltr</code> o bien <code>rtl</code>).",
+       "apihelp-query+languageinfo-example-autonym-name-de": "Obtener los endónimos y los nombres alemanes de todos los idiomas compatibles.",
+       "apihelp-query+languageinfo-example-fallbacks-variants-oc": "Obtener los idiomas de reserva y las variantes del occitano.",
+       "apihelp-query+languageinfo-example-bcp47-dir": "Obtener el código lingüístico BCP-47 y la dirección de todos los idiomas compatibles.",
        "apihelp-query+links-summary": "Devuelve todos los enlaces de las páginas dadas.",
        "apihelp-query+links-param-namespace": "Mostrar solo los enlaces en estos espacios de nombres.",
        "apihelp-query+links-param-limit": "Cuántos enlaces se devolverán.",
index 60ae2f8..41ff893 100644 (file)
@@ -202,12 +202,17 @@ class BlockManager {
                        ] );
                }
 
+               // Filter out any duplicated blocks, e.g. from the cookie
+               $blocks = $this->getUniqueBlocks( $blocks );
+
                if ( count( $blocks ) > 0 ) {
                        if ( count( $blocks ) === 1 ) {
                                $block = $blocks[ 0 ];
                        } else {
                                $block = new CompositeBlock( [
                                        'address' => $ip,
+                                       'byText' => 'MediaWiki default',
+                                       'reason' => wfMessage( 'blockedtext-composite-reason' )->plain(),
                                        'originalBlocks' => $blocks,
                                ] );
                        }
@@ -217,6 +222,28 @@ class BlockManager {
                return null;
        }
 
+       /**
+        * Given a list of blocks, return a list blocks where each block either has a
+        * unique ID or has ID null.
+        *
+        * @param AbstractBlock[] $blocks
+        * @return AbstractBlock[]
+        */
+       private function getUniqueBlocks( $blocks ) {
+               $blockIds = [];
+               $uniqueBlocks = [];
+               foreach ( $blocks as $block ) {
+                       $id = $block->getId();
+                       if ( $id === null ) {
+                               $uniqueBlocks[] = $block;
+                       } elseif ( !isset( $blockIds[$id] ) ) {
+                               $uniqueBlocks[] = $block;
+                               $blockIds[$block->getId()] = true;
+                       }
+               }
+               return $uniqueBlocks;
+       }
+
        /**
         * Try to load a block from an ID given in a cookie value.
         *
index fda1505..8efd7de 100644 (file)
@@ -106,13 +106,27 @@ class CompositeBlock extends AbstractBlock {
                return $this->originalBlocks;
        }
 
+       /**
+        * @inheritDoc
+        */
+       public function getExpiry() {
+               $maxExpiry = null;
+               foreach ( $this->originalBlocks as $block ) {
+                       $expiry = $block->getExpiry();
+                       if ( $maxExpiry === null || $expiry === '' || $expiry > $maxExpiry ) {
+                               $maxExpiry = $expiry;
+                       }
+               }
+               return $maxExpiry;
+       }
+
        /**
         * @inheritDoc
         */
        public function getPermissionsError( IContextSource $context ) {
                $params = $this->getBlockErrorParams( $context );
 
-               $msg = $this->isSitewide() ? 'blockedtext' : 'blockedtext-partial';
+               $msg = 'blockedtext-composite';
 
                array_unshift( $params, $msg );
 
index 9146429..cf6ed17 100644 (file)
@@ -603,8 +603,8 @@ class ChangeTags {
         * ChangeTags::updateTags() instead, unless directly handling a user request
         * to add or remove tags from an existing revision or log entry.
         *
-        * @param array|null $tagsToAdd If none, pass array() or null
-        * @param array|null $tagsToRemove If none, pass array() or null
+        * @param array|null $tagsToAdd If none, pass [] or null
+        * @param array|null $tagsToRemove If none, pass [] or null
         * @param int|null $rc_id The rc_id of the change to add the tags to
         * @param int|null $rev_id The rev_id of the change to add the tags to
         * @param int|null $log_id The log_id of the change to add the tags to
@@ -1229,11 +1229,13 @@ class ChangeTags {
                $dbw = wfGetDB( DB_MASTER );
                $dbw->startAtomic( __METHOD__ );
 
+               // fetch tag id, this must be done before calling undefineTag(), see T225564
+               $tagId = MediaWikiServices::getInstance()->getChangeTagDefStore()->getId( $tag );
+
                // set ctd_user_defined = 0
                self::undefineTag( $tag );
 
                // delete from change_tag
-               $tagId = MediaWikiServices::getInstance()->getChangeTagDefStore()->getId( $tag );
                $dbw->delete( 'change_tag', [ 'ct_tag_id' => $tagId ], __METHOD__ );
                $dbw->delete( 'change_tag_def', [ 'ctd_name' => $tag ], __METHOD__ );
                $dbw->endAtomic( __METHOD__ );
index 4b0c6cb..dc50543 100644 (file)
@@ -15,7 +15,7 @@ class CeeFormatter extends LogstashFormatter {
        /**
         * Format records with a cee cookie
         * @param array $record
-        * @return array
+        * @return mixed
         */
        public function format( array $record ) {
                return "@cee: " . parent::format( $record );
index 9adb2b0..266d768 100644 (file)
@@ -965,7 +965,7 @@ class LinksUpdate extends DataUpdate implements EnqueueableDataUpdate {
 
        /**
         * Get an array of existing inline interwiki links, as a 2-D array
-        * @return array (prefix => array(dbkey => 1))
+        * @return array [ prefix => [ dbkey => 1 ] ]
         */
        private function getExistingInterwikis() {
                $res = $this->getDB()->select( 'iwlinks', [ 'iwl_prefix', 'iwl_title' ],
index e083a4e..7edefd5 100644 (file)
@@ -31,31 +31,6 @@ use Wikimedia\Rdbms\DBUnexpectedError;
  * @ingroup FileAbstraction
  */
 class ForeignDBFile extends LocalFile {
-       /**
-        * @param Title $title
-        * @param FileRepo $repo
-        * @param null $unused
-        * @return ForeignDBFile
-        */
-       static function newFromTitle( $title, $repo, $unused = null ) {
-               return new self( $title, $repo );
-       }
-
-       /**
-        * Create a ForeignDBFile from a title
-        * Do not call this except from inside a repo class.
-        *
-        * @param stdClass $row
-        * @param FileRepo $repo
-        * @return ForeignDBFile
-        */
-       static function newFromRow( $row, $repo ) {
-               $title = Title::makeTitle( NS_FILE, $row->img_name );
-               $file = new self( $title, $repo );
-               $file->loadFromRow( $row );
-
-               return $file;
-       }
 
        /**
         * @param string $srcPath
index 54bcea3..1e1bde3 100644 (file)
@@ -150,10 +150,10 @@ class LocalFile extends File {
         * @param FileRepo $repo
         * @param null $unused
         *
-        * @return self
+        * @return static
         */
        static function newFromTitle( $title, $repo, $unused = null ) {
-               return new self( $title, $repo );
+               return new static( $title, $repo );
        }
 
        /**
@@ -163,11 +163,11 @@ class LocalFile extends File {
         * @param stdClass $row
         * @param FileRepo $repo
         *
-        * @return self
+        * @return static
         */
        static function newFromRow( $row, $repo ) {
                $title = Title::makeTitle( NS_FILE, $row->img_name );
-               $file = new self( $title, $repo );
+               $file = new static( $title, $repo );
                $file->loadFromRow( $row );
 
                return $file;
@@ -190,12 +190,12 @@ class LocalFile extends File {
                        $conds['img_timestamp'] = $dbr->timestamp( $timestamp );
                }
 
-               $fileQuery = self::getQueryInfo();
+               $fileQuery = static::getQueryInfo();
                $row = $dbr->selectRow(
                        $fileQuery['tables'], $fileQuery['fields'], $conds, __METHOD__, [], $fileQuery['joins']
                );
                if ( $row ) {
-                       return self::newFromRow( $row, $repo );
+                       return static::newFromRow( $row, $repo );
                } else {
                        return false;
                }
index 3cdbfc2..584e001 100644 (file)
@@ -42,7 +42,7 @@ class OldLocalFile extends LocalFile {
         * @param Title $title
         * @param FileRepo $repo
         * @param string|int|null $time
-        * @return self
+        * @return static
         * @throws MWException
         */
        static function newFromTitle( $title, $repo, $time = null ) {
@@ -51,27 +51,27 @@ class OldLocalFile extends LocalFile {
                        throw new MWException( __METHOD__ . ' got null for $time parameter' );
                }
 
-               return new self( $title, $repo, $time, null );
+               return new static( $title, $repo, $time, null );
        }
 
        /**
         * @param Title $title
         * @param FileRepo $repo
         * @param string $archiveName
-        * @return self
+        * @return static
         */
        static function newFromArchiveName( $title, $repo, $archiveName ) {
-               return new self( $title, $repo, null, $archiveName );
+               return new static( $title, $repo, null, $archiveName );
        }
 
        /**
         * @param stdClass $row
         * @param FileRepo $repo
-        * @return self
+        * @return static
         */
        static function newFromRow( $row, $repo ) {
                $title = Title::makeTitle( NS_FILE, $row->oi_name );
-               $file = new self( $title, $repo, null, $row->oi_archive_name );
+               $file = new static( $title, $repo, null, $row->oi_archive_name );
                $file->loadFromRow( $row, 'oi_' );
 
                return $file;
@@ -95,12 +95,12 @@ class OldLocalFile extends LocalFile {
                        $conds['oi_timestamp'] = $dbr->timestamp( $timestamp );
                }
 
-               $fileQuery = self::getQueryInfo();
+               $fileQuery = static::getQueryInfo();
                $row = $dbr->selectRow(
                        $fileQuery['tables'], $fileQuery['fields'], $conds, __METHOD__, [], $fileQuery['joins']
                );
                if ( $row ) {
-                       return self::newFromRow( $row, $repo );
+                       return static::newFromRow( $row, $repo );
                } else {
                        return false;
                }
index fde68bb..2865ce5 100644 (file)
@@ -55,19 +55,19 @@ class UnregisteredLocalFile extends File {
        /**
         * @param string $path Storage path
         * @param string $mime
-        * @return UnregisteredLocalFile
+        * @return static
         */
        static function newFromPath( $path, $mime ) {
-               return new self( false, false, $path, $mime );
+               return new static( false, false, $path, $mime );
        }
 
        /**
         * @param Title $title
         * @param FileRepo $repo
-        * @return UnregisteredLocalFile
+        * @return static
         */
        static function newFromTitle( $title, $repo ) {
-               return new self( $title, $repo, false, false );
+               return new static( $title, $repo, false, false );
        }
 
        /**
index 16dc465..ff805d8 100644 (file)
@@ -866,7 +866,7 @@ abstract class HTMLFormField {
         * that return value has no taint.
         *
         * @param string $value The value of the input
-        * @return array array( $errors, $errorClass )
+        * @return array [ $errors, $errorClass ]
         * @return-taint none
         */
        public function getErrorsAndErrorClass( $value ) {
index f137bf1..85cbbb1 100644 (file)
@@ -141,6 +141,35 @@ class HTMLSelectAndOtherField extends HTMLSelectField {
                return new MediaWiki\Widget\SelectWithInputWidget( $params );
        }
 
+       /**
+        * @inheritDoc
+        */
+       public function getDefault() {
+               $default = parent::getDefault();
+
+               // Default values of empty form
+               $final = '';
+               $list = 'other';
+               $text = '';
+
+               if ( $default !== null ) {
+                       $final = $default;
+                       // Assume the default is a text value, with the 'other' option selected.
+                       // Then check if that assumption is correct, and update $list and $text if not.
+                       $text = $final;
+                       foreach ( $this->mFlatOptions as $option ) {
+                               $match = $option . $this->msg( 'colon-separator' )->inContentLanguage()->text();
+                               if ( strpos( $final, $match ) === 0 ) {
+                                       $list = $option;
+                                       $text = substr( $final, strlen( $match ) );
+                                       break;
+                               }
+                       }
+               }
+
+               return [ $final, $list, $text ];
+       }
+
        /**
         * @param WebRequest $request
         *
@@ -163,22 +192,9 @@ class HTMLSelectAndOtherField extends HTMLSelectField {
                        } else {
                                $final = $list . $this->msg( 'colon-separator' )->inContentLanguage()->text() . $text;
                        }
-               } else {
-                       $final = $this->getDefault();
-
-                       $list = 'other';
-                       $text = $final;
-                       foreach ( $this->mFlatOptions as $option ) {
-                               $match = $option . $this->msg( 'colon-separator' )->inContentLanguage()->text();
-                               if ( strpos( $text, $match ) === 0 ) {
-                                       $list = $option;
-                                       $text = substr( $text, strlen( $match ) );
-                                       break;
-                               }
-                       }
+                       return [ $final, $list, $text ];
                }
-
-               return [ $final, $list, $text ];
+               return $this->getDefault();
        }
 
        public function getSize() {
@@ -197,7 +213,7 @@ class HTMLSelectAndOtherField extends HTMLSelectField {
 
                if ( isset( $this->mParams['required'] )
                        && $this->mParams['required'] !== false
-                       && $value[1] === ''
+                       && $value[0] === ''
                ) {
                        return $this->msg( 'htmlform-required' );
                }
index 0a6be86..20018d0 100644 (file)
@@ -124,6 +124,13 @@ class WebInstaller extends Installer {
         */
        protected $tabIndex = 1;
 
+       /**
+        * Numeric index of the help box
+        *
+        * @var int
+        */
+       protected $helpBoxId = 1;
+
        /**
         * Name of the page we're on
         *
@@ -680,11 +687,13 @@ class WebInstaller extends Installer {
                $args = array_map( 'htmlspecialchars', $args );
                $text = wfMessage( $msg, $args )->useDatabase( false )->plain();
                $html = $this->parse( $text, true );
+               $id = 'helpBox-' . $this->helpBoxId++;
 
                return "<div class=\"config-help-field-container\">\n" .
-                       "<span class=\"config-help-field-hint\" title=\"" .
+                       "<input type=\"checkbox\" class=\"config-help-field-checkbox\" id=\"$id\" />" .
+                       "<label class=\"config-help-field-hint\" for=\"$id\" title=\"" .
                        wfMessage( 'config-help-tooltip' )->escaped() . "\">" .
-                       wfMessage( 'config-help' )->escaped() . "</span>\n" .
+                       wfMessage( 'config-help' )->escaped() . "</label>\n" .
                        "<div class=\"config-help-field-data\">" . $html . "</div>\n" .
                        "</div>\n";
        }
index bc25179..2412319 100644 (file)
@@ -364,7 +364,7 @@ class WebInstallerOptions extends WebInstallerPage {
                ] );
                $styleUrl = $server . dirname( dirname( $this->parent->getUrl() ) ) .
                        '/mw-config/config-cc.css';
-               $iframeUrl = '//creativecommons.org/license/?' .
+               $iframeUrl = 'https://creativecommons.org/license/?' .
                        wfArrayToCgi( [
                                'partner' => 'MediaWiki',
                                'exit_url' => $exitUrl,
index b061d0d..65e7457 100644 (file)
@@ -285,7 +285,7 @@ class WebInstallerOutput {
 <?php echo Html::openElement( 'body', [ 'class' => $this->getLanguage()->getDir() ] ) . "\n"; ?>
 <div id="mw-page-base"></div>
 <div id="mw-head-base"></div>
-<div id="content" class="mw-body">
+<div id="content" class="mw-body" role="main">
 <div id="bodyContent" class="mw-body-content">
 
 <h1><?php $this->outputTitle(); ?></h1>
@@ -304,9 +304,7 @@ class WebInstallerOutput {
 
 <div id="mw-panel">
        <div class="portal" id="p-logo">
-               <a style="background-image: url(images/installer-logo.png);"
-                       href="https://www.mediawiki.org/"
-                       title="Main Page"></a>
+               <a href="https://www.mediawiki.org/" title="Main Page"></a>
        </div>
 <?php
        $message = wfMessage( 'config-sidebar' )->plain();
@@ -325,13 +323,14 @@ class WebInstallerOutput {
        public function outputShortHeader() {
 ?>
 <?php echo Html::htmlHeader( $this->getHeadAttribs() ); ?>
+
 <head>
-       <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
        <meta name="robots" content="noindex, nofollow" />
+       <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
        <title><?php $this->outputTitle(); ?></title>
        <?php echo $this->getCssUrl() . "\n"; ?>
-       <?php echo $this->getJQuery(); ?>
-       <?php echo Html::linkedScript( 'config.js' ); ?>
+       <?php echo $this->getJQuery() . "\n"; ?>
+       <?php echo Html::linkedScript( 'config.js' ) . "\n"; ?>
 </head>
 
 <body style="background-image: none">
index 62f5eb5..cf341e4 100644 (file)
        "config-missing-db-name": "Musíte zadat hodnotu pro „{{int:config-db-name}}“.",
        "config-missing-db-host": "Musíte zadat hodnotu pro „{{int:config-db-host}}“.",
        "config-missing-db-server-oracle": "Musíte zadat hodnotu pro „{{int:config-db-host-oracle}}“.",
-       "config-invalid-db-server-oracle": "Chybné databázové TNS „$1“.\nPoužívejte buď „TNS Name“ nebo „Easy Connect“ (viz [http://docs.oracle.com/cd/E11882_01/network.112/e10836/naming.htm Oracle Naming Methods]).",
+       "config-invalid-db-server-oracle": "Chybné databázové TNS „$1“.\nPoužívejte buď „TNS Name“ nebo „Easy Connect“ (vizte [http://docs.oracle.com/cd/E11882_01/network.112/e10836/naming.htm Oracle Naming Methods]).",
        "config-invalid-db-name": "Chybné jméno databáze „$1“.\nPoužívejte pouze ASCII písmena (a-z, A-Z), čísla (0-9), podtržítko (_) a spojovník (-).",
        "config-invalid-db-prefix": "Chybný databázový prefix „$1“.\nPoužívejte pouze ASCII písmena (a-z, A-Z), čísla (0-9), podtržítko (_) a spojovník (-).",
        "config-connection-error": "$1.\n\nZkontrolujte server, uživatelské jméno a heslo a zkuste to znovu. Pokud jako adresu databázového serveru používáte „localhost“, zkuste použít „127.0.0.1“ (a naopak).",
index 4e9781b..eb3042b 100644 (file)
@@ -38,6 +38,7 @@
        "config-env-bad": "Omno verifikesis.\nVu NE POVAS intalar MediaWiki.",
        "config-env-php": "PHP $1 instalesis.",
        "config-env-hhvm": "HHVM $1 instalesis.",
+       "config-unicode-pure-php-warning": "<strong>Atencez:</strong> La [https://php.net/manual/en/book.intl.php prolonguro PHP intl] ne esas disponebla por traktar skribo-normaligo \"Unicode\". Vice, uzesas la plu lenta laborado en pura PHP.\nSe vu administras pagini multe vizitata, vu mustas lektar la [https://www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations skribo-normaligo Unicode].",
        "config-apc": "[https://www.php.net/apc APC] instalesis",
        "config-apcu": "[https://www.php.net/apcu APCu] instalesis",
        "config-profile-private": "Privata wiki",
index f8318f0..934c7c6 100644 (file)
@@ -71,8 +71,8 @@
        "config-env-bad": "環境を確認しました。\nMediaWiki のインストールはできません。",
        "config-env-php": "PHP $1がインストールされています。",
        "config-env-hhvm": "HHVM $1 がインストールされています。",
-       "config-unicode-using-intl": "Unicode正規化に[https://pecl.php.net/intl intl PECL 拡張機能]を使用。",
-       "config-unicode-pure-php-warning": "<strong>警告:</strong> Unicode 正規化の処理に [https://pecl.php.net/intl intl PECL 拡張機能]を利用できないため、処理が遅いピュア PHP の実装を代わりに使用しています。\n高トラフィックのサイトを運営する場合は、[https://www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations Unicode 正規化]をお読みください。",
+       "config-unicode-using-intl": "Unicode正規化に[https://php.net/manual/en/book.intl.php PHP intl \n 拡張機能]を使用。",
+       "config-unicode-pure-php-warning": "<strong>警告:</strong> Unicode 正規化の処理に[https://php.net/manual/en/book.intl.php PHP intl 拡張機能]を利用できないため、処理が遅いピュア PHP の実装を代わりに使用しています。\n高トラフィックのサイトを運営する場合、[https://www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations Unicode 正規化]は必ず読むよう推奨されます。",
        "config-unicode-update-warning": "<strong>警告:</strong> インストールされているバージョンの Unicode 正規化ラッパーは、[http://site.icu-project.org/ ICU プロジェクト]のライブラリの古いバージョンを使用しています。\nUnicode を少しでも利用する可能性がある場合は、[https://www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations アップグレード]してください。",
        "config-no-db": "適切なデータベース ドライバーが見つかりませんでした! PHP にデータベース ドライバーをインストールする必要があります。\n以下の種類のデータベース{{PLURAL:$2|のタイプ}}に対応しています: $1\n\nPHP を自分でコンパイルした場合は、例えば <code>./configure --with-mysqli</code> を実行して、データベース クライアントを使用できるように再設定してください。\nDebian または Ubuntu のパッケージから PHP をインストールした場合は、モジュール (例: <code>php-mysql</code>) もインストールする必要があります。",
        "config-outdated-sqlite": "<strong>警告:</strong> あなたは SQLite $1 を使用していますが、最低限必要なバージョン $2 より古いバージョンです。SQLite は利用できません。",
index 1b34fc6..6a62fb6 100644 (file)
@@ -69,8 +69,8 @@
        "config-env-bad": "De omgeving is gecontroleerd.\nU kunt MediaWiki niet installeren.",
        "config-env-php": "PHP $1 is geïnstalleerd.",
        "config-env-hhvm": "HHVM $1 is geïnstalleerd.",
-       "config-unicode-using-intl": "Voor Unicode-normalisatie wordt de [https://pecl.php.net/intl PECL-extensie intl] gebruikt.",
-       "config-unicode-pure-php-warning": "<strong>Waarschuwing:</strong> de [https://pecl.php.net/intl PECL-extensie intl] is niet beschikbaar om de Unicodenormalisatie af te handelen en daarom wordt de langzamere PHP-implementatie gebruikt.\nAls u MediaWiki voor een website met veel verkeer installeert, lees u dan in over [https://www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations Unicodenormalisatie].",
+       "config-unicode-using-intl": "Voor Unicode-normalisatie wordt de [https://php.net/manual/en/book.intl.php PHP-extensie intl] gebruikt.",
+       "config-unicode-pure-php-warning": "<strong>Waarschuwing:</strong> de [https://php.net/manual/en/book.intl.php PHP-uitbreiding intl] is niet beschikbaar om de Unicodenormalisatie af te handelen en daarom wordt de langzamere PHP-implementatie gebruikt.\nAls u MediaWiki voor een website met veel verkeer installeert, lees u dan in over [https://www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations Unicodenormalisatie].",
        "config-unicode-update-warning": "<strong>Waarschuwing:</strong> de geïnstalleerde versie van de Unicodenormalisatiewrapper maakt gebruik van een oudere versie van [http://site.icu-project.org/ de bibliotheek van het ICU-project].\nU moet [https://www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations bijwerken] als Unicode voor u van belang is.",
        "config-no-db": "Het was niet mogelijk een geschikte databasedriver te vinden voor PHP! U moet een databasedriver installeren voor PHP.\n{{PLURAL:$2|Het volgende databasetype wordt|De volgende databasetypes worden}} ondersteund: $1.\n\nAls u PHP zelf hebt gecompileerd, wijzig dan uw instellingen zodat een databasedriver wordt geactiveerd, bijvoorbeeld via <code>./configure --with-mysqli</code>.\nAls u PHP hebt geïnstalleerd via een Debian- of Ubuntu-package, installeer dan ook bijvoorbeeld de module <code>php-mysql</code>.",
        "config-outdated-sqlite": "''' Waarschuwing:''' u gebruikt SQLite $2. SQLite is niet beschikbaar omdat de minimaal vereiste versie $1 is.",
index 5471fdb..7543691 100644 (file)
@@ -50,7 +50,7 @@
        "config-env-bad": "Okolje je pregledano.\nNe morete namestiti MediaWiki.",
        "config-env-php": "Nameščen je PHP $1.",
        "config-env-hhvm": "HHVM $1 je nameščen.",
-       "config-unicode-using-intl": "Uporaba [https://pecl.php.net/intl razširitve PECL intl] za normalizacijo unikoda.",
+       "config-unicode-using-intl": "Uporaba [https://php.net/manual/en/book.intl.php PHP-razširitve intl] za normalizacijo unikoda.",
        "config-memory-raised": "PHP-jev <code>memory_limit</code> je $1, dvignjen na $2.",
        "config-apc": "[https://www.php.net/apc APC] je nameščen",
        "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache] je nameščen",
index 80a46d0..19ff967 100644 (file)
@@ -27,8 +27,8 @@
  * @code
  * $job = new JobSpecification(
  *             'null',
- *             array( 'lives' => 1, 'usleep' => 100, 'pi' => 3.141569 ),
- *             array( 'removeDuplicates' => 1 )
+ *             [ 'lives' => 1, 'usleep' => 100, 'pi' => 3.141569 ],
+ *             [ 'removeDuplicates' => 1 ]
  * );
  * JobQueueGroup::singleton()->push( $job )
  * @endcode
diff --git a/includes/language/LanguageCode.php b/includes/language/LanguageCode.php
new file mode 100644 (file)
index 0000000..7d954d3
--- /dev/null
@@ -0,0 +1,204 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Language
+ */
+
+/**
+ * Methods for dealing with language codes.
+ * @todo Move some of the code-related static methods out of Language into this class
+ *
+ * @since 1.29
+ * @ingroup Language
+ */
+class LanguageCode {
+       /**
+        * Mapping of deprecated language codes that were used in previous
+        * versions of MediaWiki to up-to-date, current language codes.
+        * These may or may not be valid BCP 47 codes; they are included here
+        * because MediaWiki renamed these particular codes at some point.
+        *
+        * @var array Mapping from deprecated MediaWiki-internal language code
+        *   to replacement MediaWiki-internal language code.
+        *
+        * @since 1.30
+        * @see https://meta.wikimedia.org/wiki/Special_language_codes
+        */
+       private static $deprecatedLanguageCodeMapping = [
+               // Note that als is actually a valid ISO 639 code (Tosk Albanian), but it
+               // was previously used in MediaWiki for Alsatian, which comes under gsw
+               'als' => 'gsw', // T25215
+               'bat-smg' => 'sgs', // T27522
+               'be-x-old' => 'be-tarask', // T11823
+               'fiu-vro' => 'vro', // T31186
+               'roa-rup' => 'rup', // T17988
+               'zh-classical' => 'lzh', // T30443
+               'zh-min-nan' => 'nan', // T30442
+               'zh-yue' => 'yue', // T30441
+       ];
+
+       /**
+        * Mapping of non-standard language codes used in MediaWiki to
+        * standardized BCP 47 codes.  These are not deprecated (yet?):
+        * IANA may eventually recognize the subtag, in which case the `-x-`
+        * infix could be removed, or else we could rename the code in
+        * MediaWiki, in which case they'd move up to the above mapping
+        * of deprecated codes.
+        *
+        * As a rule, we preserve all distinctions made by MediaWiki
+        * internally.  For example, `de-formal` becomes `de-x-formal`
+        * instead of just `de` because MediaWiki distinguishes `de-formal`
+        * from `de` (for example, for interface translations).  Similarly,
+        * BCP 47 indicates that `kk-Cyrl` SHOULD not be used because it
+        * "typically does not add information", but in our case MediaWiki
+        * LanguageConverter distinguishes `kk` (render content in a mix of
+        * Kurdish variants) from `kk-Cyrl` (convert content to be uniformly
+        * Cyrillic).  As the BCP 47 requirement is a SHOULD not a MUST,
+        * `kk-Cyrl` is a valid code, although some validators may emit
+        * a warning note.
+        *
+        * @var array Mapping from nonstandard MediaWiki-internal codes to
+        *   BCP 47 codes
+        *
+        * @since 1.32
+        * @see https://meta.wikimedia.org/wiki/Special_language_codes
+        * @see https://phabricator.wikimedia.org/T125073
+        */
+       private static $nonstandardLanguageCodeMapping = [
+               // All codes returned by Language::fetchLanguageNames() validated
+               // against IANA registry at
+               //   https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry
+               // with help of validator at
+               //   http://schneegans.de/lv/
+               'cbk-zam' => 'cbk', // T124657
+               'de-formal' => 'de-x-formal',
+               'eml' => 'egl', // T36217
+               'en-rtl' => 'en-x-rtl',
+               'es-formal' => 'es-x-formal',
+               'hu-formal' => 'hu-x-formal',
+               'map-bms' => 'jv-x-bms', // [[en:Banyumasan_dialect]] T125073
+               'mo' => 'ro-Cyrl-MD', // T125073
+               'nrm' => 'nrf', // [[en:Norman_language]] T25216
+               'nl-informal' => 'nl-x-informal',
+               'roa-tara' => 'nap-x-tara', // [[en:Tarantino_dialect]]
+               'simple' => 'en-simple',
+               'sr-ec' => 'sr-Cyrl', // T117845
+               'sr-el' => 'sr-Latn', // T117845
+
+               // Although these next codes aren't *wrong* per se, including
+               // both the script and the country code helps compatibility with
+               // other BCP 47 users. Note that MW also uses `zh-Hans`/`zh-Hant`,
+               // without a country code, and those should be left alone.
+               // (See $variantfallbacks in LanguageZh.php for Hans/Hant id.)
+               'zh-cn' => 'zh-Hans-CN',
+               'zh-sg' => 'zh-Hans-SG',
+               'zh-my' => 'zh-Hans-MY',
+               'zh-tw' => 'zh-Hant-TW',
+               'zh-hk' => 'zh-Hant-HK',
+               'zh-mo' => 'zh-Hant-MO',
+       ];
+
+       /**
+        * Returns a mapping of deprecated language codes that were used in previous
+        * versions of MediaWiki to up-to-date, current language codes.
+        *
+        * This array is merged into $wgDummyLanguageCodes in Setup.php, along with
+        * the fake language codes 'qqq' and 'qqx', which are used internally by
+        * MediaWiki's localisation system.
+        *
+        * @return string[]
+        *
+        * @since 1.29
+        */
+       public static function getDeprecatedCodeMapping() {
+               return self::$deprecatedLanguageCodeMapping;
+       }
+
+       /**
+        * Returns a mapping of non-standard language codes used by
+        * (current and previous version of) MediaWiki, mapped to standard
+        * BCP 47 names.
+        *
+        * This array is exported to JavaScript to ensure
+        * mediawiki.language.bcp47 stays in sync with LanguageCode::bcp47().
+        *
+        * @return string[]
+        *
+        * @since 1.32
+        */
+       public static function getNonstandardLanguageCodeMapping() {
+               $result = [];
+               foreach ( self::$deprecatedLanguageCodeMapping as $code => $ignore ) {
+                       $result[$code] = self::bcp47( $code );
+               }
+               foreach ( self::$nonstandardLanguageCodeMapping as $code => $ignore ) {
+                       $result[$code] = self::bcp47( $code );
+               }
+               return $result;
+       }
+
+       /**
+        * Replace deprecated language codes that were used in previous
+        * versions of MediaWiki to up-to-date, current language codes.
+        * Other values will returned unchanged.
+        *
+        * @param string $code Old language code
+        * @return string New language code
+        *
+        * @since 1.30
+        */
+       public static function replaceDeprecatedCodes( $code ) {
+               return self::$deprecatedLanguageCodeMapping[$code] ?? $code;
+       }
+
+       /**
+        * Get the normalised IETF language tag
+        * See unit test for examples.
+        * See mediawiki.language.bcp47 for the JavaScript implementation.
+        *
+        * @param string $code The language code.
+        * @return string A language code complying with BCP 47 standards.
+        *
+        * @since 1.31
+        */
+       public static function bcp47( $code ) {
+               $code = self::replaceDeprecatedCodes( strtolower( $code ) );
+               if ( isset( self::$nonstandardLanguageCodeMapping[$code] ) ) {
+                       $code = self::$nonstandardLanguageCodeMapping[$code];
+               }
+               $codeSegment = explode( '-', $code );
+               $codeBCP = [];
+               foreach ( $codeSegment as $segNo => $seg ) {
+                       // when previous segment is x, it is a private segment and should be lc
+                       if ( $segNo > 0 && strtolower( $codeSegment[( $segNo - 1 )] ) == 'x' ) {
+                               $codeBCP[$segNo] = strtolower( $seg );
+                       // ISO 3166 country code
+                       } elseif ( ( strlen( $seg ) == 2 ) && ( $segNo > 0 ) ) {
+                               $codeBCP[$segNo] = strtoupper( $seg );
+                       // ISO 15924 script code
+                       } elseif ( ( strlen( $seg ) == 4 ) && ( $segNo > 0 ) ) {
+                               $codeBCP[$segNo] = ucfirst( strtolower( $seg ) );
+                       // Use lowercase for other cases
+                       } else {
+                               $codeBCP[$segNo] = strtolower( $seg );
+                       }
+               }
+               $langCode = implode( '-', $codeBCP );
+               return $langCode;
+       }
+}
diff --git a/includes/language/Message.php b/includes/language/Message.php
new file mode 100644 (file)
index 0000000..0b3113f
--- /dev/null
@@ -0,0 +1,1396 @@
+<?php
+/**
+ * Fetching and processing of interface messages.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @author Niklas Laxström
+ */
+use MediaWiki\MediaWikiServices;
+
+/**
+ * The Message class provides methods which fulfil two basic services:
+ *  - fetching interface messages
+ *  - processing messages into a variety of formats
+ *
+ * First implemented with MediaWiki 1.17, the Message class is intended to
+ * replace the old wfMsg* functions that over time grew unusable.
+ * @see https://www.mediawiki.org/wiki/Manual:Messages_API for equivalences
+ * between old and new functions.
+ *
+ * You should use the wfMessage() global function which acts as a wrapper for
+ * the Message class. The wrapper let you pass parameters as arguments.
+ *
+ * The most basic usage cases would be:
+ *
+ * @code
+ *     // Initialize a Message object using the 'some_key' message key
+ *     $message = wfMessage( 'some_key' );
+ *
+ *     // Using two parameters those values are strings 'value1' and 'value2':
+ *     $message = wfMessage( 'some_key',
+ *          'value1', 'value2'
+ *     );
+ * @endcode
+ *
+ * @section message_global_fn Global function wrapper:
+ *
+ * Since wfMessage() returns a Message instance, you can chain its call with
+ * a method. Some of them return a Message instance too so you can chain them.
+ * You will find below several examples of wfMessage() usage.
+ *
+ * Fetching a message text for interface message:
+ *
+ * @code
+ *    $button = Xml::button(
+ *         wfMessage( 'submit' )->text()
+ *    );
+ * @endcode
+ *
+ * A Message instance can be passed parameters after it has been constructed,
+ * use the params() method to do so:
+ *
+ * @code
+ *     wfMessage( 'welcome-to' )
+ *         ->params( $wgSitename )
+ *         ->text();
+ * @endcode
+ *
+ * {{GRAMMAR}} and friends work correctly:
+ *
+ * @code
+ *    wfMessage( 'are-friends',
+ *        $user, $friend
+ *    );
+ *    wfMessage( 'bad-message' )
+ *         ->rawParams( '<script>...</script>' )
+ *         ->escaped();
+ * @endcode
+ *
+ * @section message_language Changing language:
+ *
+ * Messages can be requested in a different language or in whatever current
+ * content language is being used. The methods are:
+ *     - Message->inContentLanguage()
+ *     - Message->inLanguage()
+ *
+ * Sometimes the message text ends up in the database, so content language is
+ * needed:
+ *
+ * @code
+ *    wfMessage( 'file-log',
+ *        $user, $filename
+ *    )->inContentLanguage()->text();
+ * @endcode
+ *
+ * Checking whether a message exists:
+ *
+ * @code
+ *    wfMessage( 'mysterious-message' )->exists()
+ *    // returns a boolean whether the 'mysterious-message' key exist.
+ * @endcode
+ *
+ * If you want to use a different language:
+ *
+ * @code
+ *    $userLanguage = $user->getOption( 'language' );
+ *    wfMessage( 'email-header' )
+ *         ->inLanguage( $userLanguage )
+ *         ->plain();
+ * @endcode
+ *
+ * @note You can parse the text only in the content or interface languages
+ *
+ * @section message_compare_old Comparison with old wfMsg* functions:
+ *
+ * Use full parsing:
+ *
+ * @code
+ *     // old style:
+ *     wfMsgExt( 'key', [ 'parseinline' ], 'apple' );
+ *     // new style:
+ *     wfMessage( 'key', 'apple' )->parse();
+ * @endcode
+ *
+ * Parseinline is used because it is more useful when pre-building HTML.
+ * In normal use it is better to use OutputPage::(add|wrap)WikiMsg.
+ *
+ * Places where HTML cannot be used. {{-transformation is done.
+ * @code
+ *     // old style:
+ *     wfMsgExt( 'key', [ 'parsemag' ], 'apple', 'pear' );
+ *     // new style:
+ *     wfMessage( 'key', 'apple', 'pear' )->text();
+ * @endcode
+ *
+ * Shortcut for escaping the message too, similar to wfMsgHTML(), but
+ * parameters are not replaced after escaping by default.
+ * @code
+ *     $escaped = wfMessage( 'key' )
+ *          ->rawParams( 'apple' )
+ *          ->escaped();
+ * @endcode
+ *
+ * @section message_appendix Appendix:
+ *
+ * @todo
+ * - test, can we have tests?
+ * - this documentation needs to be extended
+ *
+ * @see https://www.mediawiki.org/wiki/WfMessage()
+ * @see https://www.mediawiki.org/wiki/New_messages_API
+ * @see https://www.mediawiki.org/wiki/Localisation
+ *
+ * @since 1.17
+ */
+class Message implements MessageSpecifier, Serializable {
+       /** Use message text as-is */
+       const FORMAT_PLAIN = 'plain';
+       /** Use normal wikitext -> HTML parsing (the result will be wrapped in a block-level HTML tag) */
+       const FORMAT_BLOCK_PARSE = 'block-parse';
+       /** Use normal wikitext -> HTML parsing but strip the block-level wrapper */
+       const FORMAT_PARSE = 'parse';
+       /** Transform {{..}} constructs but don't transform to HTML */
+       const FORMAT_TEXT = 'text';
+       /** Transform {{..}} constructs, HTML-escape the result */
+       const FORMAT_ESCAPED = 'escaped';
+
+       /**
+        * Mapping from Message::listParam() types to Language methods.
+        * @var array
+        */
+       protected static $listTypeMap = [
+               'comma' => 'commaList',
+               'semicolon' => 'semicolonList',
+               'pipe' => 'pipeList',
+               'text' => 'listToText',
+       ];
+
+       /**
+        * In which language to get this message. True, which is the default,
+        * means the current user language, false content language.
+        *
+        * @var bool
+        */
+       protected $interface = true;
+
+       /**
+        * In which language to get this message. Overrides the $interface setting.
+        *
+        * @var Language|bool Explicit language object, or false for user language
+        */
+       protected $language = false;
+
+       /**
+        * @var string The message key. If $keysToTry has more than one element,
+        * this may change to one of the keys to try when fetching the message text.
+        */
+       protected $key;
+
+       /**
+        * @var string[] List of keys to try when fetching the message.
+        */
+       protected $keysToTry;
+
+       /**
+        * @var array List of parameters which will be substituted into the message.
+        */
+       protected $parameters = [];
+
+       /**
+        * @var string
+        * @deprecated
+        */
+       protected $format = 'parse';
+
+       /**
+        * @var bool Whether database can be used.
+        */
+       protected $useDatabase = true;
+
+       /**
+        * @var Title Title object to use as context.
+        */
+       protected $title = null;
+
+       /**
+        * @var Content Content object representing the message.
+        */
+       protected $content = null;
+
+       /**
+        * @var string
+        */
+       protected $message;
+
+       /**
+        * @since 1.17
+        * @param string|string[]|MessageSpecifier $key Message key, or array of
+        * message keys to try and use the first non-empty message for, or a
+        * MessageSpecifier to copy from.
+        * @param array $params Message parameters.
+        * @param Language|null $language [optional] Language to use (defaults to current user language).
+        * @throws InvalidArgumentException
+        */
+       public function __construct( $key, $params = [], Language $language = null ) {
+               if ( $key instanceof MessageSpecifier ) {
+                       if ( $params ) {
+                               throw new InvalidArgumentException(
+                                       '$params must be empty if $key is a MessageSpecifier'
+                               );
+                       }
+                       $params = $key->getParams();
+                       $key = $key->getKey();
+               }
+
+               if ( !is_string( $key ) && !is_array( $key ) ) {
+                       throw new InvalidArgumentException( '$key must be a string or an array' );
+               }
+
+               $this->keysToTry = (array)$key;
+
+               if ( empty( $this->keysToTry ) ) {
+                       throw new InvalidArgumentException( '$key must not be an empty list' );
+               }
+
+               $this->key = reset( $this->keysToTry );
+
+               $this->parameters = array_values( $params );
+               // User language is only resolved in getLanguage(). This helps preserve the
+               // semantic intent of "user language" across serialize() and unserialize().
+               $this->language = $language ?: false;
+       }
+
+       /**
+        * @see Serializable::serialize()
+        * @since 1.26
+        * @return string
+        */
+       public function serialize() {
+               return serialize( [
+                       'interface' => $this->interface,
+                       'language' => $this->language ? $this->language->getCode() : false,
+                       'key' => $this->key,
+                       'keysToTry' => $this->keysToTry,
+                       'parameters' => $this->parameters,
+                       'format' => $this->format,
+                       'useDatabase' => $this->useDatabase,
+                       'titlestr' => $this->title ? $this->title->getFullText() : null,
+               ] );
+       }
+
+       /**
+        * @see Serializable::unserialize()
+        * @since 1.26
+        * @param string $serialized
+        */
+       public function unserialize( $serialized ) {
+               $data = unserialize( $serialized );
+               if ( !is_array( $data ) ) {
+                       throw new InvalidArgumentException( __METHOD__ . ': Invalid serialized data' );
+               }
+
+               $this->interface = $data['interface'];
+               $this->key = $data['key'];
+               $this->keysToTry = $data['keysToTry'];
+               $this->parameters = $data['parameters'];
+               $this->format = $data['format'];
+               $this->useDatabase = $data['useDatabase'];
+               $this->language = $data['language'] ? Language::factory( $data['language'] ) : false;
+
+               if ( isset( $data['titlestr'] ) ) {
+                       $this->title = Title::newFromText( $data['titlestr'] );
+               } elseif ( isset( $data['title'] ) && $data['title'] instanceof Title ) {
+                       // Old serializations from before December 2018
+                       $this->title = $data['title'];
+               } else {
+                       $this->title = null; // Explicit for sanity
+               }
+       }
+
+       /**
+        * @since 1.24
+        *
+        * @return bool True if this is a multi-key message, that is, if the key provided to the
+        * constructor was a fallback list of keys to try.
+        */
+       public function isMultiKey() {
+               return count( $this->keysToTry ) > 1;
+       }
+
+       /**
+        * @since 1.24
+        *
+        * @return string[] The list of keys to try when fetching the message text,
+        * in order of preference.
+        */
+       public function getKeysToTry() {
+               return $this->keysToTry;
+       }
+
+       /**
+        * Returns the message key.
+        *
+        * If a list of multiple possible keys was supplied to the constructor, this method may
+        * return any of these keys. After the message has been fetched, this method will return
+        * the key that was actually used to fetch the message.
+        *
+        * @since 1.21
+        *
+        * @return string
+        */
+       public function getKey() {
+               return $this->key;
+       }
+
+       /**
+        * Returns the message parameters.
+        *
+        * @since 1.21
+        *
+        * @return array
+        */
+       public function getParams() {
+               return $this->parameters;
+       }
+
+       /**
+        * Returns the message format.
+        *
+        * @since 1.21
+        *
+        * @return string
+        * @deprecated since 1.29 formatting is not stateful
+        */
+       public function getFormat() {
+               wfDeprecated( __METHOD__, '1.29' );
+               return $this->format;
+       }
+
+       /**
+        * Returns the Language of the Message.
+        *
+        * @since 1.23
+        *
+        * @return Language
+        */
+       public function getLanguage() {
+               // Defaults to false which means current user language
+               return $this->language ?: RequestContext::getMain()->getLanguage();
+       }
+
+       /**
+        * Factory function that is just wrapper for the real constructor. It is
+        * intended to be used instead of the real constructor, because it allows
+        * chaining method calls, while new objects don't.
+        *
+        * @since 1.17
+        *
+        * @param string|string[]|MessageSpecifier $key
+        * @param mixed $param,... Parameters as strings.
+        *
+        * @return Message
+        */
+       public static function newFromKey( $key /*...*/ ) {
+               $params = func_get_args();
+               array_shift( $params );
+               return new self( $key, $params );
+       }
+
+       /**
+        * Transform a MessageSpecifier or a primitive value used interchangeably with
+        * specifiers (a message key string, or a key + params array) into a proper Message.
+        *
+        * Also accepts a MessageSpecifier inside an array: that's not considered a valid format
+        * but is an easy error to make due to how StatusValue stores messages internally.
+        * Further array elements are ignored in that case.
+        *
+        * @param string|array|MessageSpecifier $value
+        * @return Message
+        * @throws InvalidArgumentException
+        * @since 1.27
+        */
+       public static function newFromSpecifier( $value ) {
+               $params = [];
+               if ( is_array( $value ) ) {
+                       $params = $value;
+                       $value = array_shift( $params );
+               }
+
+               if ( $value instanceof Message ) { // Message, RawMessage, ApiMessage, etc
+                       $message = clone $value;
+               } elseif ( $value instanceof MessageSpecifier ) {
+                       $message = new Message( $value );
+               } elseif ( is_string( $value ) ) {
+                       $message = new Message( $value, $params );
+               } else {
+                       throw new InvalidArgumentException( __METHOD__ . ': invalid argument type '
+                               . gettype( $value ) );
+               }
+
+               return $message;
+       }
+
+       /**
+        * Factory function accepting multiple message keys and returning a message instance
+        * for the first message which is non-empty. If all messages are empty then an
+        * instance of the first message key is returned.
+        *
+        * @since 1.18
+        *
+        * @param string|string[] $keys,... Message keys, or first argument as an array of all the
+        * message keys.
+        *
+        * @return Message
+        */
+       public static function newFallbackSequence( /*...*/ ) {
+               $keys = func_get_args();
+               if ( func_num_args() == 1 ) {
+                       if ( is_array( $keys[0] ) ) {
+                               // Allow an array to be passed as the first argument instead
+                               $keys = array_values( $keys[0] );
+                       } else {
+                               // Optimize a single string to not need special fallback handling
+                               $keys = $keys[0];
+                       }
+               }
+               return new self( $keys );
+       }
+
+       /**
+        * Get a title object for a mediawiki message, where it can be found in the mediawiki namespace.
+        * The title will be for the current language, if the message key is in
+        * $wgForceUIMsgAsContentMsg it will be append with the language code (except content
+        * language), because Message::inContentLanguage will also return in user language.
+        *
+        * @see $wgForceUIMsgAsContentMsg
+        * @return Title
+        * @since 1.26
+        */
+       public function getTitle() {
+               global $wgForceUIMsgAsContentMsg;
+
+               $contLang = MediaWikiServices::getInstance()->getContentLanguage();
+               $lang = $this->getLanguage();
+               $title = $this->key;
+               if (
+                       !$lang->equals( $contLang )
+                       && in_array( $this->key, (array)$wgForceUIMsgAsContentMsg )
+               ) {
+                       $title .= '/' . $lang->getCode();
+               }
+
+               return Title::makeTitle(
+                       NS_MEDIAWIKI, $contLang->ucfirst( strtr( $title, ' ', '_' ) ) );
+       }
+
+       /**
+        * Adds parameters to the parameter list of this message.
+        *
+        * @since 1.17
+        *
+        * @param mixed $args,... Parameters as strings or arrays from
+        *  Message::numParam() and the like, or a single array of parameters.
+        *
+        * @return Message $this
+        */
+       public function params( /*...*/ ) {
+               $args = func_get_args();
+
+               // If $args has only one entry and it's an array, then it's either a
+               // non-varargs call or it happens to be a call with just a single
+               // "special" parameter. Since the "special" parameters don't have any
+               // numeric keys, we'll test that to differentiate the cases.
+               if ( count( $args ) === 1 && isset( $args[0] ) && is_array( $args[0] ) ) {
+                       if ( $args[0] === [] ) {
+                               $args = [];
+                       } else {
+                               foreach ( $args[0] as $key => $value ) {
+                                       if ( is_int( $key ) ) {
+                                               $args = $args[0];
+                                               break;
+                                       }
+                               }
+                       }
+               }
+
+               $this->parameters = array_merge( $this->parameters, array_values( $args ) );
+               return $this;
+       }
+
+       /**
+        * Add parameters that are substituted after parsing or escaping.
+        * In other words the parsing process cannot access the contents
+        * of this type of parameter, and you need to make sure it is
+        * sanitized beforehand.  The parser will see "$n", instead.
+        *
+        * @since 1.17
+        *
+        * @param mixed $params,... Raw parameters as strings, or a single argument that is
+        * an array of raw parameters.
+        *
+        * @return Message $this
+        */
+       public function rawParams( /*...*/ ) {
+               $params = func_get_args();
+               if ( isset( $params[0] ) && is_array( $params[0] ) ) {
+                       $params = $params[0];
+               }
+               foreach ( $params as $param ) {
+                       $this->parameters[] = self::rawParam( $param );
+               }
+               return $this;
+       }
+
+       /**
+        * Add parameters that are numeric and will be passed through
+        * Language::formatNum before substitution
+        *
+        * @since 1.18
+        *
+        * @param mixed $param,... Numeric parameters, or a single argument that is
+        * an array of numeric parameters.
+        *
+        * @return Message $this
+        */
+       public function numParams( /*...*/ ) {
+               $params = func_get_args();
+               if ( isset( $params[0] ) && is_array( $params[0] ) ) {
+                       $params = $params[0];
+               }
+               foreach ( $params as $param ) {
+                       $this->parameters[] = self::numParam( $param );
+               }
+               return $this;
+       }
+
+       /**
+        * Add parameters that are durations of time and will be passed through
+        * Language::formatDuration before substitution
+        *
+        * @since 1.22
+        *
+        * @param int|int[] $param,... Duration parameters, or a single argument that is
+        * an array of duration parameters.
+        *
+        * @return Message $this
+        */
+       public function durationParams( /*...*/ ) {
+               $params = func_get_args();
+               if ( isset( $params[0] ) && is_array( $params[0] ) ) {
+                       $params = $params[0];
+               }
+               foreach ( $params as $param ) {
+                       $this->parameters[] = self::durationParam( $param );
+               }
+               return $this;
+       }
+
+       /**
+        * Add parameters that are expiration times and will be passed through
+        * Language::formatExpiry before substitution
+        *
+        * @since 1.22
+        *
+        * @param string|string[] $param,... Expiry parameters, or a single argument that is
+        * an array of expiry parameters.
+        *
+        * @return Message $this
+        */
+       public function expiryParams( /*...*/ ) {
+               $params = func_get_args();
+               if ( isset( $params[0] ) && is_array( $params[0] ) ) {
+                       $params = $params[0];
+               }
+               foreach ( $params as $param ) {
+                       $this->parameters[] = self::expiryParam( $param );
+               }
+               return $this;
+       }
+
+       /**
+        * Add parameters that are time periods and will be passed through
+        * Language::formatTimePeriod before substitution
+        *
+        * @since 1.22
+        *
+        * @param int|int[] $param,... Time period parameters, or a single argument that is
+        * an array of time period parameters.
+        *
+        * @return Message $this
+        */
+       public function timeperiodParams( /*...*/ ) {
+               $params = func_get_args();
+               if ( isset( $params[0] ) && is_array( $params[0] ) ) {
+                       $params = $params[0];
+               }
+               foreach ( $params as $param ) {
+                       $this->parameters[] = self::timeperiodParam( $param );
+               }
+               return $this;
+       }
+
+       /**
+        * Add parameters that are file sizes and will be passed through
+        * Language::formatSize before substitution
+        *
+        * @since 1.22
+        *
+        * @param int|int[] $param,... Size parameters, or a single argument that is
+        * an array of size parameters.
+        *
+        * @return Message $this
+        */
+       public function sizeParams( /*...*/ ) {
+               $params = func_get_args();
+               if ( isset( $params[0] ) && is_array( $params[0] ) ) {
+                       $params = $params[0];
+               }
+               foreach ( $params as $param ) {
+                       $this->parameters[] = self::sizeParam( $param );
+               }
+               return $this;
+       }
+
+       /**
+        * Add parameters that are bitrates and will be passed through
+        * Language::formatBitrate before substitution
+        *
+        * @since 1.22
+        *
+        * @param int|int[] $param,... Bit rate parameters, or a single argument that is
+        * an array of bit rate parameters.
+        *
+        * @return Message $this
+        */
+       public function bitrateParams( /*...*/ ) {
+               $params = func_get_args();
+               if ( isset( $params[0] ) && is_array( $params[0] ) ) {
+                       $params = $params[0];
+               }
+               foreach ( $params as $param ) {
+                       $this->parameters[] = self::bitrateParam( $param );
+               }
+               return $this;
+       }
+
+       /**
+        * Add parameters that are plaintext and will be passed through without
+        * the content being evaluated.  Plaintext parameters are not valid as
+        * arguments to parser functions. This differs from self::rawParams in
+        * that the Message class handles escaping to match the output format.
+        *
+        * @since 1.25
+        *
+        * @param string|string[] $param,... plaintext parameters, or a single argument that is
+        * an array of plaintext parameters.
+        *
+        * @return Message $this
+        */
+       public function plaintextParams( /*...*/ ) {
+               $params = func_get_args();
+               if ( isset( $params[0] ) && is_array( $params[0] ) ) {
+                       $params = $params[0];
+               }
+               foreach ( $params as $param ) {
+                       $this->parameters[] = self::plaintextParam( $param );
+               }
+               return $this;
+       }
+
+       /**
+        * Set the language and the title from a context object
+        *
+        * @since 1.19
+        *
+        * @param IContextSource $context
+        *
+        * @return Message $this
+        */
+       public function setContext( IContextSource $context ) {
+               $this->inLanguage( $context->getLanguage() );
+               $this->title( $context->getTitle() );
+               $this->interface = true;
+
+               return $this;
+       }
+
+       /**
+        * Request the message in any language that is supported.
+        *
+        * As a side effect interface message status is unconditionally
+        * turned off.
+        *
+        * @since 1.17
+        * @param Language|string $lang Language code or Language object.
+        * @return Message $this
+        * @throws MWException
+        */
+       public function inLanguage( $lang ) {
+               $previousLanguage = $this->language;
+
+               if ( $lang instanceof Language ) {
+                       $this->language = $lang;
+               } elseif ( is_string( $lang ) ) {
+                       if ( !$this->language instanceof Language || $this->language->getCode() != $lang ) {
+                               $this->language = Language::factory( $lang );
+                       }
+               } elseif ( $lang instanceof StubUserLang ) {
+                       $this->language = false;
+               } else {
+                       $type = gettype( $lang );
+                       throw new MWException( __METHOD__ . " must be "
+                               . "passed a String or Language object; $type given"
+                       );
+               }
+
+               if ( $this->language !== $previousLanguage ) {
+                       // The language has changed. Clear the message cache.
+                       $this->message = null;
+               }
+               $this->interface = false;
+               return $this;
+       }
+
+       /**
+        * Request the message in the wiki's content language,
+        * unless it is disabled for this message.
+        *
+        * @since 1.17
+        * @see $wgForceUIMsgAsContentMsg
+        *
+        * @return Message $this
+        */
+       public function inContentLanguage() {
+               global $wgForceUIMsgAsContentMsg;
+               if ( in_array( $this->key, (array)$wgForceUIMsgAsContentMsg ) ) {
+                       return $this;
+               }
+
+               $this->inLanguage( MediaWikiServices::getInstance()->getContentLanguage() );
+               return $this;
+       }
+
+       /**
+        * Allows manipulating the interface message flag directly.
+        * Can be used to restore the flag after setting a language.
+        *
+        * @since 1.20
+        *
+        * @param bool $interface
+        *
+        * @return Message $this
+        */
+       public function setInterfaceMessageFlag( $interface ) {
+               $this->interface = (bool)$interface;
+               return $this;
+       }
+
+       /**
+        * Enable or disable database use.
+        *
+        * @since 1.17
+        *
+        * @param bool $useDatabase
+        *
+        * @return Message $this
+        */
+       public function useDatabase( $useDatabase ) {
+               $this->useDatabase = (bool)$useDatabase;
+               $this->message = null;
+               return $this;
+       }
+
+       /**
+        * Set the Title object to use as context when transforming the message
+        *
+        * @since 1.18
+        *
+        * @param Title $title
+        *
+        * @return Message $this
+        */
+       public function title( $title ) {
+               $this->title = $title;
+               return $this;
+       }
+
+       /**
+        * Returns the message as a Content object.
+        *
+        * @return Content
+        */
+       public function content() {
+               if ( !$this->content ) {
+                       $this->content = new MessageContent( $this );
+               }
+
+               return $this->content;
+       }
+
+       /**
+        * Returns the message parsed from wikitext to HTML.
+        *
+        * @since 1.17
+        *
+        * @param string|null $format One of the FORMAT_* constants. Null means use whatever was used
+        *   the last time (this is for B/C and should be avoided).
+        *
+        * @return string HTML
+        * @suppress SecurityCheck-DoubleEscaped phan false positive
+        */
+       public function toString( $format = null ) {
+               if ( $format === null ) {
+                       $ex = new LogicException( __METHOD__ . ' using implicit format: ' . $this->format );
+                       \MediaWiki\Logger\LoggerFactory::getInstance( 'message-format' )->warning(
+                               $ex->getMessage(), [ 'exception' => $ex, 'format' => $this->format, 'key' => $this->key ] );
+                       $format = $this->format;
+               }
+               $string = $this->fetchMessage();
+
+               if ( $string === false ) {
+                       // Err on the side of safety, ensure that the output
+                       // is always html safe in the event the message key is
+                       // missing, since in that case its highly likely the
+                       // message key is user-controlled.
+                       // '⧼' is used instead of '<' to side-step any
+                       // double-escaping issues.
+                       // (Keep synchronised with mw.Message#toString in JS.)
+                       return '⧼' . htmlspecialchars( $this->key ) . '⧽';
+               }
+
+               # Replace $* with a list of parameters for &uselang=qqx.
+               if ( strpos( $string, '$*' ) !== false ) {
+                       $paramlist = '';
+                       if ( $this->parameters !== [] ) {
+                               $paramlist = ': $' . implode( ', $', range( 1, count( $this->parameters ) ) );
+                       }
+                       $string = str_replace( '$*', $paramlist, $string );
+               }
+
+               # Replace parameters before text parsing
+               $string = $this->replaceParameters( $string, 'before', $format );
+
+               # Maybe transform using the full parser
+               if ( $format === self::FORMAT_PARSE ) {
+                       $string = $this->parseText( $string );
+                       $string = Parser::stripOuterParagraph( $string );
+               } elseif ( $format === self::FORMAT_BLOCK_PARSE ) {
+                       $string = $this->parseText( $string );
+               } elseif ( $format === self::FORMAT_TEXT ) {
+                       $string = $this->transformText( $string );
+               } elseif ( $format === self::FORMAT_ESCAPED ) {
+                       $string = $this->transformText( $string );
+                       $string = htmlspecialchars( $string, ENT_QUOTES, 'UTF-8', false );
+               }
+
+               # Raw parameter replacement
+               $string = $this->replaceParameters( $string, 'after', $format );
+
+               return $string;
+       }
+
+       /**
+        * Magic method implementation of the above (for PHP >= 5.2.0), so we can do, eg:
+        *     $foo = new Message( $key );
+        *     $string = "<abbr>$foo</abbr>";
+        *
+        * @since 1.18
+        *
+        * @return string
+        */
+       public function __toString() {
+               // PHP doesn't allow __toString to throw exceptions and will
+               // trigger a fatal error if it does. So, catch any exceptions.
+
+               try {
+                       return $this->toString( self::FORMAT_PARSE );
+               } catch ( Exception $ex ) {
+                       try {
+                               trigger_error( "Exception caught in " . __METHOD__ . " (message " . $this->key . "): "
+                                       . $ex, E_USER_WARNING );
+                       } catch ( Exception $ex ) {
+                               // Doh! Cause a fatal error after all?
+                       }
+
+                       return '⧼' . htmlspecialchars( $this->key ) . '⧽';
+               }
+       }
+
+       /**
+        * Fully parse the text from wikitext to HTML.
+        *
+        * @since 1.17
+        *
+        * @return string Parsed HTML.
+        */
+       public function parse() {
+               $this->format = self::FORMAT_PARSE;
+               return $this->toString( self::FORMAT_PARSE );
+       }
+
+       /**
+        * Returns the message text. {{-transformation is done.
+        *
+        * @since 1.17
+        *
+        * @return string Unescaped message text.
+        */
+       public function text() {
+               $this->format = self::FORMAT_TEXT;
+               return $this->toString( self::FORMAT_TEXT );
+       }
+
+       /**
+        * Returns the message text as-is, only parameters are substituted.
+        *
+        * @since 1.17
+        *
+        * @return string Unescaped untransformed message text.
+        */
+       public function plain() {
+               $this->format = self::FORMAT_PLAIN;
+               return $this->toString( self::FORMAT_PLAIN );
+       }
+
+       /**
+        * Returns the parsed message text which is always surrounded by a block element.
+        *
+        * @since 1.17
+        *
+        * @return string HTML
+        */
+       public function parseAsBlock() {
+               $this->format = self::FORMAT_BLOCK_PARSE;
+               return $this->toString( self::FORMAT_BLOCK_PARSE );
+       }
+
+       /**
+        * Returns the message text. {{-transformation is done and the result
+        * is escaped excluding any raw parameters.
+        *
+        * @since 1.17
+        *
+        * @return string Escaped message text.
+        */
+       public function escaped() {
+               $this->format = self::FORMAT_ESCAPED;
+               return $this->toString( self::FORMAT_ESCAPED );
+       }
+
+       /**
+        * Check whether a message key has been defined currently.
+        *
+        * @since 1.17
+        *
+        * @return bool
+        */
+       public function exists() {
+               return $this->fetchMessage() !== false;
+       }
+
+       /**
+        * Check whether a message does not exist, or is an empty string
+        *
+        * @since 1.18
+        * @todo FIXME: Merge with isDisabled()?
+        *
+        * @return bool
+        */
+       public function isBlank() {
+               $message = $this->fetchMessage();
+               return $message === false || $message === '';
+       }
+
+       /**
+        * Check whether a message does not exist, is an empty string, or is "-".
+        *
+        * @since 1.18
+        *
+        * @return bool
+        */
+       public function isDisabled() {
+               $message = $this->fetchMessage();
+               return $message === false || $message === '' || $message === '-';
+       }
+
+       /**
+        * @since 1.17
+        *
+        * @param mixed $raw
+        *
+        * @return array Array with a single "raw" key.
+        */
+       public static function rawParam( $raw ) {
+               return [ 'raw' => $raw ];
+       }
+
+       /**
+        * @since 1.18
+        *
+        * @param mixed $num
+        *
+        * @return array Array with a single "num" key.
+        */
+       public static function numParam( $num ) {
+               return [ 'num' => $num ];
+       }
+
+       /**
+        * @since 1.22
+        *
+        * @param int $duration
+        *
+        * @return int[] Array with a single "duration" key.
+        */
+       public static function durationParam( $duration ) {
+               return [ 'duration' => $duration ];
+       }
+
+       /**
+        * @since 1.22
+        *
+        * @param string $expiry
+        *
+        * @return string[] Array with a single "expiry" key.
+        */
+       public static function expiryParam( $expiry ) {
+               return [ 'expiry' => $expiry ];
+       }
+
+       /**
+        * @since 1.22
+        *
+        * @param int $period
+        *
+        * @return int[] Array with a single "period" key.
+        */
+       public static function timeperiodParam( $period ) {
+               return [ 'period' => $period ];
+       }
+
+       /**
+        * @since 1.22
+        *
+        * @param int $size
+        *
+        * @return int[] Array with a single "size" key.
+        */
+       public static function sizeParam( $size ) {
+               return [ 'size' => $size ];
+       }
+
+       /**
+        * @since 1.22
+        *
+        * @param int $bitrate
+        *
+        * @return int[] Array with a single "bitrate" key.
+        */
+       public static function bitrateParam( $bitrate ) {
+               return [ 'bitrate' => $bitrate ];
+       }
+
+       /**
+        * @since 1.25
+        *
+        * @param string $plaintext
+        *
+        * @return string[] Array with a single "plaintext" key.
+        */
+       public static function plaintextParam( $plaintext ) {
+               return [ 'plaintext' => $plaintext ];
+       }
+
+       /**
+        * @since 1.29
+        *
+        * @param array $list
+        * @param string $type 'comma', 'semicolon', 'pipe', 'text'
+        * @return array Array with "list" and "type" keys.
+        */
+       public static function listParam( array $list, $type = 'text' ) {
+               if ( !isset( self::$listTypeMap[$type] ) ) {
+                       throw new InvalidArgumentException(
+                               "Invalid type '$type'. Known types are: " . implode( ', ', array_keys( self::$listTypeMap ) )
+                       );
+               }
+               return [ 'list' => $list, 'type' => $type ];
+       }
+
+       /**
+        * Substitutes any parameters into the message text.
+        *
+        * @since 1.17
+        *
+        * @param string $message The message text.
+        * @param string $type Either "before" or "after".
+        * @param string $format One of the FORMAT_* constants.
+        *
+        * @return string
+        */
+       protected function replaceParameters( $message, $type, $format ) {
+               // A temporary marker for $1 parameters that is only valid
+               // in non-attribute contexts. However if the entire message is escaped
+               // then we don't want to use it because it will be mangled in all contexts
+               // and its unnessary as ->escaped() messages aren't html.
+               $marker = $format === self::FORMAT_ESCAPED ? '$' : '$\'"';
+               $replacementKeys = [];
+               foreach ( $this->parameters as $n => $param ) {
+                       list( $paramType, $value ) = $this->extractParam( $param, $format );
+                       if ( $type === 'before' ) {
+                               if ( $paramType === 'before' ) {
+                                       $replacementKeys['$' . ( $n + 1 )] = $value;
+                               } else /* $paramType === 'after' */ {
+                                       // To protect against XSS from replacing parameters
+                                       // inside html attributes, we convert $1 to $'"1.
+                                       // In the event that one of the parameters ends up
+                                       // in an attribute, either the ' or the " will be
+                                       // escaped, breaking the replacement and avoiding XSS.
+                                       $replacementKeys['$' . ( $n + 1 )] = $marker . ( $n + 1 );
+                               }
+                       } elseif ( $paramType === 'after' ) {
+                               $replacementKeys[$marker . ( $n + 1 )] = $value;
+                       }
+               }
+               return strtr( $message, $replacementKeys );
+       }
+
+       /**
+        * Extracts the parameter type and preprocessed the value if needed.
+        *
+        * @since 1.18
+        *
+        * @param mixed $param Parameter as defined in this class.
+        * @param string $format One of the FORMAT_* constants.
+        *
+        * @return array Array with the parameter type (either "before" or "after") and the value.
+        */
+       protected function extractParam( $param, $format ) {
+               if ( is_array( $param ) ) {
+                       if ( isset( $param['raw'] ) ) {
+                               return [ 'after', $param['raw'] ];
+                       } elseif ( isset( $param['num'] ) ) {
+                               // Replace number params always in before step for now.
+                               // No support for combined raw and num params
+                               return [ 'before', $this->getLanguage()->formatNum( $param['num'] ) ];
+                       } elseif ( isset( $param['duration'] ) ) {
+                               return [ 'before', $this->getLanguage()->formatDuration( $param['duration'] ) ];
+                       } elseif ( isset( $param['expiry'] ) ) {
+                               return [ 'before', $this->getLanguage()->formatExpiry( $param['expiry'] ) ];
+                       } elseif ( isset( $param['period'] ) ) {
+                               return [ 'before', $this->getLanguage()->formatTimePeriod( $param['period'] ) ];
+                       } elseif ( isset( $param['size'] ) ) {
+                               return [ 'before', $this->getLanguage()->formatSize( $param['size'] ) ];
+                       } elseif ( isset( $param['bitrate'] ) ) {
+                               return [ 'before', $this->getLanguage()->formatBitrate( $param['bitrate'] ) ];
+                       } elseif ( isset( $param['plaintext'] ) ) {
+                               return [ 'after', $this->formatPlaintext( $param['plaintext'], $format ) ];
+                       } elseif ( isset( $param['list'] ) ) {
+                               return $this->formatListParam( $param['list'], $param['type'], $format );
+                       } else {
+                               if ( !is_scalar( $param ) ) {
+                                       $param = serialize( $param );
+                               }
+                               \MediaWiki\Logger\LoggerFactory::getInstance( 'Bug58676' )->warning(
+                                       'Invalid parameter for message "{msgkey}": {param}',
+                                       [
+                                               'exception' => new Exception,
+                                               'msgkey' => $this->getKey(),
+                                               'param' => htmlspecialchars( $param ),
+                                       ]
+                               );
+
+                               return [ 'before', '[INVALID]' ];
+                       }
+               } elseif ( $param instanceof Message ) {
+                       // Match language, flags, etc. to the current message.
+                       $msg = clone $param;
+                       if ( $msg->language !== $this->language || $msg->useDatabase !== $this->useDatabase ) {
+                               // Cache depends on these parameters
+                               $msg->message = null;
+                       }
+                       $msg->interface = $this->interface;
+                       $msg->language = $this->language;
+                       $msg->useDatabase = $this->useDatabase;
+                       $msg->title = $this->title;
+
+                       // DWIM
+                       if ( $format === 'block-parse' ) {
+                               $format = 'parse';
+                       }
+                       $msg->format = $format;
+
+                       // Message objects should not be before parameters because
+                       // then they'll get double escaped. If the message needs to be
+                       // escaped, it'll happen right here when we call toString().
+                       return [ 'after', $msg->toString( $format ) ];
+               } else {
+                       return [ 'before', $param ];
+               }
+       }
+
+       /**
+        * Wrapper for what ever method we use to parse wikitext.
+        *
+        * @since 1.17
+        *
+        * @param string $string Wikitext message contents.
+        *
+        * @return string Wikitext parsed into HTML.
+        */
+       protected function parseText( $string ) {
+               $out = MessageCache::singleton()->parse(
+                       $string,
+                       $this->title,
+                       /*linestart*/true,
+                       $this->interface,
+                       $this->getLanguage()
+               );
+
+               return $out instanceof ParserOutput
+                       ? $out->getText( [
+                               'enableSectionEditLinks' => false,
+                               // Wrapping messages in an extra <div> is probably not expected. If
+                               // they're outside the content area they probably shouldn't be
+                               // targeted by CSS that's targeting the parser output, and if
+                               // they're inside they already are from the outer div.
+                               'unwrap' => true,
+                       ] )
+                       : $out;
+       }
+
+       /**
+        * Wrapper for what ever method we use to {{-transform wikitext.
+        *
+        * @since 1.17
+        *
+        * @param string $string Wikitext message contents.
+        *
+        * @return string Wikitext with {{-constructs replaced with their values.
+        */
+       protected function transformText( $string ) {
+               return MessageCache::singleton()->transform(
+                       $string,
+                       $this->interface,
+                       $this->getLanguage(),
+                       $this->title
+               );
+       }
+
+       /**
+        * Wrapper for what ever method we use to get message contents.
+        *
+        * @since 1.17
+        *
+        * @return string
+        * @throws MWException If message key array is empty.
+        */
+       protected function fetchMessage() {
+               if ( $this->message === null ) {
+                       $cache = MessageCache::singleton();
+
+                       foreach ( $this->keysToTry as $key ) {
+                               $message = $cache->get( $key, $this->useDatabase, $this->getLanguage() );
+                               if ( $message !== false && $message !== '' ) {
+                                       break;
+                               }
+                       }
+
+                       // NOTE: The constructor makes sure keysToTry isn't empty,
+                       //       so we know that $key and $message are initialized.
+                       $this->key = $key;
+                       $this->message = $message;
+               }
+               return $this->message;
+       }
+
+       /**
+        * Formats a message parameter wrapped with 'plaintext'. Ensures that
+        * the entire string is displayed unchanged when displayed in the output
+        * format.
+        *
+        * @since 1.25
+        *
+        * @param string $plaintext String to ensure plaintext output of
+        * @param string $format One of the FORMAT_* constants.
+        *
+        * @return string Input plaintext encoded for output to $format
+        */
+       protected function formatPlaintext( $plaintext, $format ) {
+               switch ( $format ) {
+                       case self::FORMAT_TEXT:
+                       case self::FORMAT_PLAIN:
+                               return $plaintext;
+
+                       case self::FORMAT_PARSE:
+                       case self::FORMAT_BLOCK_PARSE:
+                       case self::FORMAT_ESCAPED:
+                       default:
+                               return htmlspecialchars( $plaintext, ENT_QUOTES );
+               }
+       }
+
+       /**
+        * Formats a list of parameters as a concatenated string.
+        * @since 1.29
+        * @param array $params
+        * @param string $listType
+        * @param string $format One of the FORMAT_* constants.
+        * @return array Array with the parameter type (either "before" or "after") and the value.
+        */
+       protected function formatListParam( array $params, $listType, $format ) {
+               if ( !isset( self::$listTypeMap[$listType] ) ) {
+                       $warning = 'Invalid list type for message "' . $this->getKey() . '": '
+                               . htmlspecialchars( $listType )
+                               . ' (params are ' . htmlspecialchars( serialize( $params ) ) . ')';
+                       trigger_error( $warning, E_USER_WARNING );
+                       $e = new Exception;
+                       wfDebugLog( 'Bug58676', $warning . "\n" . $e->getTraceAsString() );
+                       return [ 'before', '[INVALID]' ];
+               }
+               $func = self::$listTypeMap[$listType];
+
+               // Handle an empty list sensibly
+               if ( !$params ) {
+                       return [ 'before', $this->getLanguage()->$func( [] ) ];
+               }
+
+               // First, determine what kinds of list items we have
+               $types = [];
+               $vars = [];
+               $list = [];
+               foreach ( $params as $n => $p ) {
+                       list( $type, $value ) = $this->extractParam( $p, $format );
+                       $types[$type] = true;
+                       $list[] = $value;
+                       $vars[] = '$' . ( $n + 1 );
+               }
+
+               // Easy case: all are 'before' or 'after', so just join the
+               // values and use the same type.
+               if ( count( $types ) === 1 ) {
+                       return [ key( $types ), $this->getLanguage()->$func( $list ) ];
+               }
+
+               // Hard case: We need to process each value per its type, then
+               // return the concatenated values as 'after'. We handle this by turning
+               // the list into a RawMessage and processing that as a parameter.
+               $vars = $this->getLanguage()->$func( $vars );
+               return $this->extractParam( new RawMessage( $vars, $params ), $format );
+       }
+}
diff --git a/includes/language/MessageLocalizer.php b/includes/language/MessageLocalizer.php
new file mode 100644 (file)
index 0000000..9a1796b
--- /dev/null
@@ -0,0 +1,43 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Language
+ */
+
+/**
+ * Interface for localizing messages in MediaWiki
+ *
+ * @since 1.30
+ * @ingroup Language
+ */
+interface MessageLocalizer {
+
+       /**
+        * This is the method for getting translated interface messages.
+        *
+        * @see https://www.mediawiki.org/wiki/Manual:Messages_API
+        * @see Message::__construct
+        *
+        * @param string|string[]|MessageSpecifier $key Message key, or array of keys,
+        *   or a MessageSpecifier.
+        * @param mixed $params,... Normal message parameters
+        * @return Message
+        */
+       public function msg( $key /*...*/ );
+
+}
index b216892..8af6bb3 100644 (file)
@@ -35,7 +35,7 @@ class DBConnRef implements IDatabase {
        public function __construct( ILoadBalancer $lb, $conn, $role ) {
                $this->lb = $lb;
                $this->role = $role;
-               if ( $conn instanceof Database ) {
+               if ( $conn instanceof IDatabase && !( $conn instanceof DBConnRef ) ) {
                        $this->conn = $conn; // live handle
                } elseif ( is_array( $conn ) && count( $conn ) >= 4 && $conn[self::FLD_DOMAIN] !== false ) {
                        $this->params = $conn;
@@ -461,7 +461,7 @@ class DBConnRef implements IDatabase {
                return $this->__call( __FUNCTION__, func_get_args() );
        }
 
-       public function buildLike() {
+       public function buildLike( $param ) {
                return $this->__call( __FUNCTION__, func_get_args() );
        }
 
@@ -740,6 +740,19 @@ class DBConnRef implements IDatabase {
                return $this->__call( __FUNCTION__, func_get_args() );
        }
 
+       public function __toString() {
+               if ( $this->conn === null ) {
+                       // spl_object_id is PHP >= 7.2
+                       $id = function_exists( 'spl_object_id' )
+                               ? spl_object_id( $this )
+                               : spl_object_hash( $this );
+
+                       return $this->getType() . ' object #' . $id;
+               }
+
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
        /**
         * Error out if the role is not DB_MASTER
         *
index c6b1662..5451476 100644 (file)
@@ -2718,11 +2718,11 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                        $s );
        }
 
-       public function buildLike() {
-               $params = func_get_args();
-
-               if ( count( $params ) > 0 && is_array( $params[0] ) ) {
-                       $params = $params[0];
+       public function buildLike( $param, ...$params ) {
+               if ( is_array( $param ) ) {
+                       $params = $param;
+               } else {
+                       $params = func_get_args();
                }
 
                $s = '';
@@ -4666,12 +4666,24 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                return $this->conn;
        }
 
-       /**
-        * @since 1.19
-        * @return string
-        */
        public function __toString() {
-               return (string)$this->conn;
+               // spl_object_id is PHP >= 7.2
+               $id = function_exists( 'spl_object_id' )
+                       ? spl_object_id( $this )
+                       : spl_object_hash( $this );
+
+               $description = $this->getType() . ' object #' . $id;
+               if ( is_resource( $this->conn ) ) {
+                       $description .= ' (' . (string)$this->conn . ')'; // "resource id #<ID>"
+               } elseif ( is_object( $this->conn ) ) {
+                       // spl_object_id is PHP >= 7.2
+                       $handleId = function_exists( 'spl_object_id' )
+                               ? spl_object_id( $this->conn )
+                               : spl_object_hash( $this->conn );
+                       $description .= " (handle id #$handleId)";
+               }
+
+               return $description;
        }
 
        /**
index e871ab9..b5f83da 100644 (file)
@@ -369,7 +369,7 @@ abstract class DatabaseMysqlBase extends Database {
         * Fetch a result row as an associative and numeric array
         *
         * @param resource $res Raw result
-        * @return array
+        * @return array|false
         */
        abstract protected function mysqlFetchArray( $res );
 
index 1a5cdab..703c64d 100644 (file)
@@ -203,7 +203,7 @@ class DatabaseMysqli extends DatabaseMysqlBase {
 
        /**
         * @param mysqli_result $res
-        * @return bool
+        * @return array|false
         */
        protected function mysqlFetchArray( $res ) {
                $array = $res->fetch_array();
@@ -307,21 +307,6 @@ class DatabaseMysqli extends DatabaseMysqlBase {
                return $conn->real_escape_string( (string)$s );
        }
 
-       /**
-        * Give an id for the connection
-        *
-        * mysql driver used resource id, but mysqli objects cannot be cast to string.
-        * @return string
-        */
-       public function __toString() {
-               if ( $this->conn instanceof mysqli ) {
-                       return (string)$this->conn->thread_id;
-               } else {
-                       // mConn might be false or something.
-                       return (string)$this->conn;
-               }
-       }
-
        /**
         * @return mysqli
         */
index 3722ef4..aff3774 100644 (file)
@@ -217,7 +217,7 @@ class DatabaseSqlite extends Database {
                        $this->query( 'PRAGMA case_sensitive_like = 1' );
 
                        $sync = $this->connectionVariables['synchronous'] ?? null;
-                       if ( in_array( $sync, [ 'EXTRA', 'FULL', 'NORMAL' ], true ) ) {
+                       if ( in_array( $sync, [ 'EXTRA', 'FULL', 'NORMAL', 'OFF' ], true ) ) {
                                $this->query( "PRAGMA synchronous = $sync" );
                        }
 
@@ -1119,15 +1119,6 @@ class DatabaseSqlite extends Database {
                return true;
        }
 
-       /**
-        * @return string
-        */
-       public function __toString() {
-               return is_object( $this->conn )
-                       ? 'SQLite ' . (string)$this->conn->getAttribute( PDO::ATTR_SERVER_VERSION )
-                       : '(not connected)';
-       }
-
        /**
         * @return PDO
         */
index 89a66e8..faed1bf 100644 (file)
@@ -1220,9 +1220,10 @@ interface IDatabase {
         *   $query .= $dbr->buildLike( $pattern );
         *
         * @since 1.16
+        * @param array[]|string|LikeMatch $param
         * @return string Fully built LIKE statement
         */
-       public function buildLike();
+       public function buildLike( $param );
 
        /**
         * Returns a token for buildLike() that denotes a '_' to be used in a LIKE query
@@ -2199,6 +2200,15 @@ interface IDatabase {
         * @since 1.31
         */
        public function setIndexAliases( array $aliases );
+
+       /**
+        * Get a debugging string that mentions the database type, the ID of this instance,
+        * and the ID of any underlying connection resource or driver object if one is present
+        *
+        * @return string "<db type> object #<X>" or "<db type> object #<X> (resource/handle id #<Y>)"
+        * @since 1.34
+        */
+       public function __toString();
 }
 
 /**
index fcddfcf..4c68833 100644 (file)
@@ -86,6 +86,10 @@ class LoadBalancerSingle extends LoadBalancer {
        protected function reallyOpenConnection( array $server, DatabaseDomain $domain ) {
                return $this->db;
        }
+
+       public function __destruct() {
+               // do nothing since the connection was injected
+       }
 }
 
 /**
diff --git a/includes/libs/replacers/DoubleReplacer.php b/includes/libs/replacers/DoubleReplacer.php
deleted file mode 100644 (file)
index 9d05e06..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-<?php
-/**
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- */
-
-/**
- * Class to perform secondary replacement within each replacement string
- *
- * @deprecated since 1.32, use a Closure instead
- */
-class DoubleReplacer extends Replacer {
-       /**
-        * @param mixed $from
-        * @param mixed $to
-        * @param int $index
-        */
-       public function __construct( $from, $to, $index = 0 ) {
-               wfDeprecated( __METHOD__, '1.32' );
-               $this->from = $from;
-               $this->to = $to;
-               $this->index = $index;
-       }
-
-       /**
-        * @param array $matches
-        * @return mixed
-        */
-       public function replace( array $matches ) {
-               return str_replace( $this->from, $this->to, $matches[$this->index] );
-       }
-}
diff --git a/includes/libs/replacers/HashtableReplacer.php b/includes/libs/replacers/HashtableReplacer.php
deleted file mode 100644 (file)
index 8247694..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-<?php
-/**
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- */
-
-/**
- * Class to perform replacement based on a simple hashtable lookup
- *
- * @deprecated since 1.32, use a Closure instead
- */
-class HashtableReplacer extends Replacer {
-       private $table, $index;
-
-       /**
-        * @param array $table
-        * @param int $index
-        */
-       public function __construct( $table, $index = 0 ) {
-               wfDeprecated( __METHOD__, '1.32' );
-               $this->table = $table;
-               $this->index = $index;
-       }
-
-       /**
-        * @param array $matches
-        * @return mixed
-        */
-       public function replace( array $matches ) {
-               return $this->table[$matches[$this->index]];
-       }
-}
diff --git a/includes/libs/replacers/RegexlikeReplacer.php b/includes/libs/replacers/RegexlikeReplacer.php
deleted file mode 100644 (file)
index bdc4dc0..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-<?php
-/**
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- */
-
-/**
- * Class to replace regex matches with a string similar to that used in preg_replace()
- *
- * @deprecated since 1.32, use a Closure instead
- */
-class RegexlikeReplacer extends Replacer {
-       private $r;
-
-       /**
-        * @param string $r
-        */
-       public function __construct( $r ) {
-               wfDeprecated( __METHOD__, '1.32' );
-               $this->r = $r;
-       }
-
-       /**
-        * @param array $matches
-        * @return string
-        */
-       public function replace( array $matches ) {
-               $pairs = [];
-               foreach ( $matches as $i => $match ) {
-                       $pairs["\$$i"] = $match;
-               }
-
-               return strtr( $this->r, $pairs );
-       }
-}
diff --git a/includes/libs/replacers/Replacer.php b/includes/libs/replacers/Replacer.php
deleted file mode 100644 (file)
index 5425eed..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-<?php
-/**
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- */
-
-/**
- * Base class for "replacers", objects used in preg_replace_callback() and
- * StringUtils::delimiterReplaceCallback()
- *
- * @deprecated since 1.32, use a Closure instead
- */
-abstract class Replacer {
-       /**
-        * @return array
-        */
-       public function cb() {
-               wfDeprecated( __METHOD__, '1.32' );
-               return [ $this, 'replace' ];
-       }
-
-       /**
-        * @param array $matches
-        * @return string
-        */
-       abstract public function replace( array $matches );
-}
index cdaf062..d69a433 100644 (file)
@@ -406,8 +406,8 @@ class PageArchive {
         * @param User|null $user User performing the action, or null to use $wgUser
         * @param string|string[]|null $tags Change tags to add to log entry
         *   ($user should be able to add the specified tags before this is called)
-        * @return array|bool array(number of file revisions restored, number of image revisions
-        *   restored, log message) on success, false on failure.
+        * @return array|bool number of file revisions restored, number of image revisions
+        *   restored, log message ] on success, false on failure.
         */
        public function undelete( $timestamps, $comment = '', $fileVersions = [],
                $unsuppress = false, User $user = null, $tags = null
index e29e2b1..d65d87b 100644 (file)
@@ -3077,7 +3077,7 @@ class WikiPage implements Page, IDBAccessObject {
         * (with ChangeTags::canAddTagsAccompanyingChange)
         *
         * @return array Array of errors, each error formatted as
-        *   array(messagekey, param1, param2, ...).
+        *   [ messagekey, param1, param2, ... ].
         * On success, the array is empty.  This array can also be passed to
         * OutputPage::showPermissionsErrorPage().
         */
index 70663a0..d274558 100644 (file)
@@ -21,6 +21,7 @@
 
 /**
  * Expansion frame with custom arguments
+ * @deprecated since 1.34, use PPCustomFrame_Hash
  * @ingroup Parser
  */
 // phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
index a7fea00..03ee6d9 100644 (file)
@@ -21,6 +21,7 @@
 
 /**
  * An expansion frame, used as a context to expand the result of preprocessToObj()
+ * @deprecated since 1.34, use PPFrame_Hash
  * @ingroup Parser
  */
 // phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
index 8a435ba..26a4791 100644 (file)
@@ -20,6 +20,7 @@
  */
 
 /**
+ * @deprecated since 1.34, use PPNode_Hash_{Tree,Text,Array,Attr}
  * @ingroup Parser
  */
 // phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
index 52cb9cb..b4c8743 100644 (file)
@@ -21,6 +21,7 @@
 
 /**
  * Expansion frame with template arguments
+ * @deprecated since 1.34, use PPTemplateFrame_Hash
  * @ingroup Parser
  */
 // phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
index 486fdf4..c61de38 100644 (file)
@@ -421,21 +421,10 @@ class Parser {
         * Which class should we use for the preprocessor if not otherwise specified?
         *
         * @since 1.34
+        * @deprecated since 1.34, removing configurability of preprocessor
         * @return string
         */
        public static function getDefaultPreprocessorClass() {
-               if ( wfIsHHVM() ) {
-                       # Under HHVM Preprocessor_Hash is much faster than Preprocessor_DOM
-                       return Preprocessor_Hash::class;
-               }
-               if ( extension_loaded( 'domxml' ) ) {
-                       # PECL extension that conflicts with the core DOM extension (T15770)
-                       wfDebug( "Warning: you have the obsolete domxml extension for PHP. Please remove it!\n" );
-                       return Preprocessor_Hash::class;
-               }
-               if ( extension_loaded( 'dom' ) ) {
-                       return Preprocessor_DOM::class;
-               }
                return Preprocessor_Hash::class;
        }
 
index 0f0496b..9e510d2 100644 (file)
@@ -19,6 +19,7 @@
  *
  * @file
  * @ingroup Parser
+ * @deprecated since 1.34, use Preprocessor_Hash
  */
 
 /**
@@ -37,6 +38,7 @@ class Preprocessor_DOM extends Preprocessor {
        const CACHE_PREFIX = 'preprocess-xml';
 
        public function __construct( $parser ) {
+               wfDeprecated( __METHOD__, '1.34' ); // T204945
                $this->parser = $parser;
                $mem = ini_get( 'memory_limit' );
                $this->memoryLimit = false;
index f76e3a9..d8e5e3e 100644 (file)
@@ -1245,7 +1245,7 @@ class Sanitizer {
         *   HTML5 definition of id attribute
         *
         * @param string $id Id to escape
-        * @param string|array $options String or array of strings (default is array()):
+        * @param string|array $options String or array of strings (default is []):
         *   'noninitial': This is a non-initial fragment of an id, not a full id,
         *       so don't pay attention if the first character isn't valid at the
         *       beginning of an id.
@@ -1948,7 +1948,7 @@ class Sanitizer {
                        # rbc
                        'rb'         => $common,
                        'rp'         => $common,
-                       'rt'         => $common, # array_merge( $common, array( 'rbspan' ) ),
+                       'rt'         => $common, # array_merge( $common, [ 'rbspan' ] ),
                        'rtc'        => $common,
 
                        # MathML root element, where used for extensions
index c954df1..66b1529 100644 (file)
  *
  * @par Example:
  * @code
- * $wgRCFeeds['redis'] = array(
+ * $wgRCFeeds['redis'] = [
  *      'formatter' => 'JSONRCFeedFormatter',
  *      'uri'       => "redis://127.0.0.1:6379/rc.$wgDBname",
- * );
+ * ];
  * @endcode
  *
  * @since 1.22
diff --git a/includes/resourceloader/ResourceLoaderCircularDependencyError.php b/includes/resourceloader/ResourceLoaderCircularDependencyError.php
new file mode 100644 (file)
index 0000000..7cd53fe
--- /dev/null
@@ -0,0 +1,26 @@
+<?php
+/**
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+/**
+ * @internal For use by ResourceLoaderStartUpModule only
+ */
+class ResourceLoaderCircularDependencyError extends Exception {
+}
index c596a23..f57f13b 100644 (file)
@@ -190,8 +190,10 @@ class ResourceLoaderContext implements MessageLocalizer {
         */
        public function getDirection() {
                if ( $this->direction === null ) {
-                       $this->direction = $this->getRequest()->getRawVal( 'dir' );
-                       if ( !$this->direction ) {
+                       $direction = $this->getRequest()->getRawVal( 'dir' );
+                       if ( $direction === 'ltr' || $direction === 'rtl' ) {
+                               $this->direction = $direction;
+                       } else {
                                // Determine directionality based on user language (T8100)
                                $this->direction = Language::factory( $this->getLanguage() )->getDir();
                        }
index 015c828..47c8987 100644 (file)
@@ -334,7 +334,7 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
         *     to $IP
         * @param string|null $remoteBasePath Path to use if not provided in module definition. Defaults
         *     to $wgResourceBasePath
-        * @return array Array( localBasePath, remoteBasePath )
+        * @return array [ localBasePath, remoteBasePath ]
         */
        public static function extractBasePaths(
                $options = [],
@@ -619,9 +619,24 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
                        $options[$member] = $this->{$member};
                }
 
+               $packageFiles = $this->expandPackageFiles( $context );
+               if ( $packageFiles ) {
+                       // Extract the minimum needed:
+                       // - The 'main' pointer (included as-is).
+                       // - The 'files' array, simplied to only which files exist (the keys of
+                       //   this array), and something that represents their non-file content.
+                       //   For packaged files that reflect files directly from disk, the
+                       //   'getFileHashes' method tracks this already.
+                       //   It is important that the keys of the 'files' array are preserved,
+                       //   as they affect the module output.
+                       $packageFiles['files'] = array_map( function ( $fileInfo ) {
+                               return $fileInfo['definitionSummary'] ?? ( $fileInfo['content'] ?? null );
+                       }, $packageFiles['files'] );
+               }
+
                $summary[] = [
                        'options' => $options,
-                       'packageFiles' => $this->expandPackageFiles( $context ),
+                       'packageFiles' => $packageFiles,
                        'fileHashes' => $this->getFileHashes( $context ),
                        'messageBlob' => $this->getMessageBlob( $context ),
                ];
@@ -1068,16 +1083,22 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
        }
 
        /**
-        * Expand the packageFiles definition into something that's (almost) the right format for
-        * getPackageFiles() to return. This expands shorthands, resolves config vars and callbacks,
-        * but does not expand file paths or read the actual contents of files. Those things are done
-        * by getPackageFiles().
+        * Internal helper for use by getPackageFiles(), getFileHashes() and getDefinitionSummary().
+        *
+        * This expands the 'packageFiles' definition into something that's (almost) the right format
+        * for getPackageFiles() to return. It expands shorthands, resolves config vars, and handles
+        * summarising any non-file data for getVersionHash(). For file-based data, getFileHashes()
+        * handles it instead, which also ends up in getDefinitionSummary().
         *
-        * This is split up in this way so that getFileHashes() can get a list of file names, and
-        * getDefinitionSummary() can get config vars and callback results in their expanded form.
+        * What it does not do is reading the actual contents of any specified files, nor invoking
+        * the computation callbacks. Those things are done by getPackageFiles() instead to improve
+        * backend performance by only doing this work when the module response is needed, and not
+        * when merely computing the version hash for StartupModule, or when checking
+        * If-None-Match headers for a HTTP 304 response.
         *
         * @param ResourceLoaderContext $context
         * @return array|null
+        * @throws MWException If the 'packageFiles' definition is invalid.
         */
        private function expandPackageFiles( ResourceLoaderContext $context ) {
                $hash = $context->getHash();
@@ -1113,19 +1134,32 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
                                }
                        }
 
+                       // Perform expansions (except 'file' and 'callback'), creating one of these keys:
+                       // - 'content': literal value.
+                       // - 'filePath': content to be read from a file.
+                       // - 'callback': content computed by a callable.
                        if ( isset( $fileInfo['content'] ) ) {
                                $expanded['content'] = $fileInfo['content'];
                        } elseif ( isset( $fileInfo['file'] ) ) {
                                $expanded['filePath'] = $fileInfo['file'];
                        } elseif ( isset( $fileInfo['callback'] ) ) {
-                               if ( is_callable( $fileInfo['callback'] ) ) {
-                                       $expanded['content'] = $fileInfo['callback']( $context );
-                               } else {
+                               if ( !is_callable( $fileInfo['callback'] ) ) {
                                        $msg = __METHOD__ . ": invalid callback for package file \"{$fileInfo['name']}\"" .
                                                " in module \"{$this->getName()}\"";
                                        wfDebugLog( 'resourceloader', $msg );
                                        throw new MWException( $msg );
                                }
+                               if ( isset( $fileInfo['versionCallback'] ) ) {
+                                       if ( !is_callable( $fileInfo['versionCallback'] ) ) {
+                                               throw new MWException( __METHOD__ . ": invalid versionCallback for file" .
+                                                       " \"{$fileInfo['name']}\" in module \"{$this->getName()}\"" );
+                                       }
+                                       $expanded['definitionSummary'] = ( $fileInfo['versionCallback'] )( $context );
+                                       // Don't invoke 'callback' here as it may be expensive (T223260).
+                                       $expanded['callback'] = $fileInfo['callback'];
+                               } else {
+                                       $expanded['content'] = ( $fileInfo['callback'] )( $context );
+                               }
                        } elseif ( isset( $fileInfo['config'] ) ) {
                                if ( $type !== 'data' ) {
                                        $msg = __METHOD__ . ": invalid use of \"config\" for package file \"{$fileInfo['name']}\" " .
@@ -1184,6 +1218,8 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
 
                // Expand file contents
                foreach ( $expandedPackageFiles['files'] as &$fileInfo ) {
+                       // Turn any 'filePath' or 'callback' key into actual 'content',
+                       // and remove the key after that.
                        if ( isset( $fileInfo['filePath'] ) ) {
                                $localPath = $this->getLocalPath( $fileInfo['filePath'] );
                                if ( !file_exists( $localPath ) ) {
@@ -1198,7 +1234,13 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
                                }
                                $fileInfo['content'] = $content;
                                unset( $fileInfo['filePath'] );
+                       } elseif ( isset( $fileInfo['callback'] ) ) {
+                               $fileInfo['content'] = ( $fileInfo['callback'] )( $context );
+                               unset( $fileInfo['callback'] );
                        }
+
+                       // Not needed for client response, exists for getDefinitionSummary().
+                       unset( $fileInfo['definitionSummary'] );
                }
 
                return $expandedPackageFiles;
index db292cc..90b18eb 100644 (file)
@@ -39,7 +39,7 @@ class ResourceLoaderImageModule extends ResourceLoaderModule {
 
        protected $origin = self::ORIGIN_CORE_SITEWIDE;
 
-       /** @var ResourceLoaderImage[]|null */
+       /** @var ResourceLoaderImage[][]|null */
        protected $imageObjects = null;
        /** @var array */
        protected $images = [];
index c4e517a..0269ec3 100644 (file)
@@ -33,7 +33,7 @@ class ResourceLoaderLessVarFileModule extends ResourceLoaderFileModule {
         *
         * @param string $blob
         * @param array $exclusions
-        * @return array $blob
+        * @return object $blob
         */
        protected function excludeMessagesFromBlob( $blob, $exclusions ) {
                $data = json_decode( $blob, true );
index 26e64d1..b90b618 100644 (file)
@@ -124,29 +124,50 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
         *
         * @param array $registryData
         * @param string $moduleName
+        * @param string[] $handled Internal parameter for recursion. (Optional)
         * @return array
+        * @throws ResourceLoaderCircularDependencyError
         */
-       protected static function getImplicitDependencies( array $registryData, $moduleName ) {
+       protected static function getImplicitDependencies(
+               array $registryData,
+               $moduleName,
+               array $handled = []
+       ) {
                static $dependencyCache = [];
 
-               // The list of implicit dependencies won't be altered, so we can
-               // cache them without having to worry.
+               // No modules will be added or changed server-side after this point,
+               // so we can safely cache parts of the tree for re-use.
                if ( !isset( $dependencyCache[$moduleName] ) ) {
                        if ( !isset( $registryData[$moduleName] ) ) {
-                               // Dependencies may not exist
-                               $dependencyCache[$moduleName] = [];
+                               // Unknown module names are allowed here, this is only an optimisation.
+                               // Checks for illegal and unknown dependencies happen as PHPUnit structure tests,
+                               // and also client-side at run-time.
+                               $flat = [];
                        } else {
                                $data = $registryData[$moduleName];
-                               $dependencyCache[$moduleName] = $data['dependencies'];
+                               $flat = $data['dependencies'];
 
+                               // Prevent recursion
+                               $handled[] = $moduleName;
                                foreach ( $data['dependencies'] as $dependency ) {
-                                       // Recursively get the dependencies of the dependencies
-                                       $dependencyCache[$moduleName] = array_merge(
-                                               $dependencyCache[$moduleName],
-                                               self::getImplicitDependencies( $registryData, $dependency )
-                                       );
+                                       if ( in_array( $dependency, $handled, true ) ) {
+                                               // If we encounter a circular dependency, then stop the optimiser and leave the
+                                               // original dependencies array unmodified. Circular dependencies are not
+                                               // supported in ResourceLoader. Awareness of them exists here so that we can
+                                               // optimise the registry when it isn't broken, and otherwise transport the
+                                               // registry unchanged. The client will handle this further.
+                                               throw new ResourceLoaderCircularDependencyError();
+                                       } else {
+                                               // Recursively add the dependencies of the dependencies
+                                               $flat = array_merge(
+                                                       $flat,
+                                                       self::getImplicitDependencies( $registryData, $dependency, $handled )
+                                               );
+                                       }
                                }
                        }
+
+                       $dependencyCache[$moduleName] = $flat;
                }
 
                return $dependencyCache[$moduleName];
@@ -173,10 +194,16 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
        public static function compileUnresolvedDependencies( array &$registryData ) {
                foreach ( $registryData as $name => &$data ) {
                        $dependencies = $data['dependencies'];
-                       foreach ( $data['dependencies'] as $dependency ) {
-                               $implicitDependencies = self::getImplicitDependencies( $registryData, $dependency );
-                               $dependencies = array_diff( $dependencies, $implicitDependencies );
+                       try {
+                               foreach ( $data['dependencies'] as $dependency ) {
+                                       $implicitDependencies = self::getImplicitDependencies( $registryData, $dependency );
+                                       $dependencies = array_diff( $dependencies, $implicitDependencies );
+                               }
+                       } catch ( ResourceLoaderCircularDependencyError $err ) {
+                               // Leave unchanged
+                               $dependencies = $data['dependencies'];
                        }
+
                        // Rebuild keys
                        $data['dependencies'] = array_values( $dependencies );
                }
@@ -325,6 +352,7 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
         * @private For internal use by SpecialJavaScriptTest
         * @since 1.32
         * @return array
+        * @codeCoverageIgnore
         */
        public function getBaseModulesInternal() {
                return $this->getBaseModules();
index 9771e88..fa6e7fd 100644 (file)
@@ -46,7 +46,7 @@ abstract class SearchEngine {
        /** @var int */
        protected $offset = 0;
 
-       /** @var array|string */
+       /** @var string[] */
        protected $searchTerms = [];
 
        /** @var bool */
@@ -106,7 +106,7 @@ abstract class SearchEngine {
         * be converted to final in 1.34. Override self::doSearchArchiveTitle().
         *
         * @param string $term Raw search term
-        * @return Status<Title[]>
+        * @return Status
         * @since 1.29
         */
        public function searchArchiveTitle( $term ) {
@@ -117,7 +117,7 @@ abstract class SearchEngine {
         * Perform a title search in the article archive.
         *
         * @param string $term Raw search term
-        * @return Status<Title[]>
+        * @return Status
         * @since 1.32
         */
        protected function doSearchArchiveTitle( $term ) {
index 469502f..6c01f79 100644 (file)
@@ -44,7 +44,7 @@ class SearchHighlighter {
         * Wikitext highlighting when $wgAdvancedSearchHighlighting = true
         *
         * @param string $text
-        * @param array $terms Terms to highlight (not html escaped but
+        * @param string[] $terms Terms to highlight (not html escaped but
         *   regex escaped via SearchDatabase::regexTerm())
         * @param int $contextlines
         * @param int $contextchars
@@ -502,7 +502,7 @@ class SearchHighlighter {
         * Used when $wgAdvancedSearchHighlighting is false.
         *
         * @param string $text
-        * @param array $terms Escaped for regex by SearchDatabase::regexTerm()
+        * @param string[] $terms Escaped for regex by SearchDatabase::regexTerm()
         * @param int $contextlines
         * @param int $contextchars
         * @return string
index 7e51432..a27d719 100644 (file)
@@ -147,7 +147,7 @@ class SearchResult {
        }
 
        /**
-        * @param array $terms Terms to highlight
+        * @param string[] $terms Terms to highlight
         * @return string Highlighted text snippet, null (and not '') if not supported
         */
        function getTextSnippet( $terms ) {
index 3d3b446..92e2a17 100644 (file)
@@ -95,7 +95,7 @@ class SearchResultSet implements Countable, IteratorAggregate {
         * the search terms as parsed by this engine in a text extract.
         * STUB
         *
-        * @return array
+        * @return string[]
         */
        function termMatches() {
                return [];
index 022dc0a..f4e4a23 100644 (file)
@@ -1,20 +1,22 @@
 <?php
 
-use Wikimedia\Rdbms\ResultWrapper;
+use Wikimedia\Rdbms\IResultWrapper;
 
 /**
  * This class is used for different SQL-based search engines shipped with MediaWiki
  * @ingroup Search
  */
 class SqlSearchResultSet extends SearchResultSet {
-       /** @var ResultWrapper Result object from database */
+       /** @noinspection PhpMissingParentConstructorInspection */
+
+       /** @var IResultWrapper Result object from database */
        protected $resultSet;
-       /** @var string Requested search query */
+       /** @var string[] Requested search query */
        protected $terms;
        /** @var int|null Total number of hits for $terms */
        protected $totalHits;
 
-       function __construct( ResultWrapper $resultSet, $terms, $total = null ) {
+       function __construct( IResultWrapper $resultSet, $terms, $total = null ) {
                $this->resultSet = $resultSet;
                $this->terms = $terms;
                $this->totalHits = $total;
@@ -51,7 +53,7 @@ class SqlSearchResultSet extends SearchResultSet {
 
        function free() {
                if ( $this->resultSet === false ) {
-                       return false;
+                       return;
                }
 
                $this->resultSet->free();
index 20b9445..7742075 100644 (file)
@@ -73,14 +73,14 @@ class Command {
        private $cgroup = false;
 
        /**
-        * bitfield with restrictions
+        * Bitfield with restrictions
         *
         * @var int
         */
        protected $restrictions = 0;
 
        /**
-        * Constructor. Don't call directly, instead use Shell::command()
+        * Don't call directly, instead use Shell::command()
         *
         * @throws ShellDisabledError
         */
@@ -93,7 +93,7 @@ class Command {
        }
 
        /**
-        * Destructor. Makes sure programmer didn't forget to execute the command after all
+        * Makes sure the programmer didn't forget to execute the command after all
         */
        public function __destruct() {
                if ( !$this->everExecuted ) {
index eba406e..d7e39d5 100644 (file)
@@ -456,10 +456,10 @@ class SpecialPage implements MessageLocalizer {
         * For example, if a page supports subpages "foo", "bar" and "baz" (as in Special:PageName/foo,
         * etc.):
         *
-        *   - `prefixSearchSubpages( "ba" )` should return `array( "bar", "baz" )`
-        *   - `prefixSearchSubpages( "f" )` should return `array( "foo" )`
-        *   - `prefixSearchSubpages( "z" )` should return `array()`
-        *   - `prefixSearchSubpages( "" )` should return `array( foo", "bar", "baz" )`
+        *   - `prefixSearchSubpages( "ba" )` should return `[ "bar", "baz" ]`
+        *   - `prefixSearchSubpages( "f" )` should return `[ "foo" ]`
+        *   - `prefixSearchSubpages( "z" )` should return `[]`
+        *   - `prefixSearchSubpages( "" )` should return `[ foo", "bar", "baz" ]`
         *
         * @param string $search Prefix to search for
         * @param int $limit Maximum number of results to return (usually 10)
index 1053bda..9a793c3 100644 (file)
@@ -361,7 +361,7 @@ class SpecialPageFactory {
         * subpage.
         *
         * @param string $alias
-        * @return array Array( String, String|null ), or array( null, null ) if the page is invalid
+        * @return array [ String, String|null ], or [ null, null ] if the page is invalid
         */
        public function resolveAlias( $alias ) {
                $bits = explode( '/', $alias, 2 );
index bedd2c5..c7e2a37 100644 (file)
@@ -162,7 +162,7 @@ class SpecialUnblock extends SpecialPage {
         * Submit callback for an HTMLForm object
         * @param array $data
         * @param HTMLForm $form
-        * @return array|bool Array(message key, parameters)
+        * @return array|bool [ message key, parameters ]
         */
        public static function processUIUnblock( array $data, HTMLForm $form ) {
                return self::processUnblock( $data, $form->getContext() );
@@ -177,7 +177,7 @@ class SpecialUnblock extends SpecialPage {
         * @param array $data
         * @param IContextSource $context
         * @throws ErrorPageError
-        * @return array|bool Array( Array( message key, parameters ) ) on failure, True on success
+        * @return array|bool [ [ message key, parameters ] ] on failure, True on success
         */
        public static function processUnblock( array $data, IContextSource $context ) {
                $performer = $context->getUser();
index 87bc259..fc54890 100644 (file)
@@ -1001,12 +1001,12 @@ class UserrightsPage extends SpecialPage {
        /**
         * Returns $this->getUser()->changeableGroups()
         *
-        * @return array Array(
-        *   'add' => array( addablegroups ),
-        *   'remove' => array( removablegroups ),
-        *   'add-self' => array( addablegroups to self ),
-        *   'remove-self' => array( removable groups from self )
-        *  )
+        * @return array [
+        *   'add' => [ addablegroups ],
+        *   'remove' => [ removablegroups ],
+        *   'add-self' => [ addablegroups to self ],
+        *   'remove-self' => [ removable groups from self ]
+        *  ]
         */
        function changeableGroups() {
                return $this->getUser()->changeableGroups();
index 0c4959a..5456ce7 100644 (file)
@@ -812,7 +812,7 @@ class SpecialVersion extends SpecialPage {
                }
 
                // ... and generate the description; which can be a parameterized l10n message
-               // in the form array( <msgname>, <parameter>, <parameter>... ) or just a straight
+               // in the form [ <msgname>, <parameter>, <parameter>... ] or just a straight
                // up string
                if ( isset( $extension['descriptionmsg'] ) ) {
                        // Localized description of extension
index d39975d..215bd20 100644 (file)
@@ -433,7 +433,7 @@ class UploadStash {
         * List all files in the stash.
         *
         * @throws UploadStashNotLoggedInException
-        * @return array
+        * @return array|false
         */
        public function listFiles() {
                if ( !$this->isLoggedIn ) {
index 6db219d..df5edef 100644 (file)
@@ -21,7 +21,7 @@
 use MediaWiki\Auth\AuthenticationResponse;
 use MediaWiki\MediaWikiServices;
 use MediaWiki\Session\BotPasswordSessionProvider;
-use Wikimedia\Rdbms\IMaintainableDatabase;
+use Wikimedia\Rdbms\IDatabase;
 
 /**
  * Utility class for bot passwords
@@ -71,7 +71,7 @@ class BotPassword implements IDBAccessObject {
        /**
         * Get a database connection for the bot passwords database
         * @param int $db Index of the connection to get, e.g. DB_MASTER or DB_REPLICA.
-        * @return IMaintainableDatabase
+        * @return IDatabase
         */
        public static function getDB( $db ) {
                global $wgBotPasswordsCluster, $wgBotPasswordsDatabase;
index e5dfceb..3a57c0b 100644 (file)
@@ -950,12 +950,12 @@ class User implements IDBAccessObject, UserIdentity {
                        $result = (int)$s->user_id;
                }
 
-               self::$idCacheByName[$name] = $result;
-
-               if ( count( self::$idCacheByName ) > 1000 ) {
+               if ( count( self::$idCacheByName ) >= 1000 ) {
                        self::$idCacheByName = [];
                }
 
+               self::$idCacheByName[$name] = $result;
+
                return $result;
        }
 
@@ -3297,7 +3297,7 @@ class User implements IDBAccessObject, UserIdentity {
         * and 'all', which forces a reset of *all* preferences and overrides everything else.
         *
         * @param array|string $resetKinds Which kinds of preferences to reset. Defaults to
-        *  array( 'registered', 'registered-multiselect', 'registered-checkmatrix', 'unused' )
+        *  [ 'registered', 'registered-multiselect', 'registered-checkmatrix', 'unused' ]
         *  for backwards-compatibility.
         * @param IContextSource|null $context Context source used when $resetKinds
         *  does not contain 'all', passed to getOptionKinds().
@@ -5044,10 +5044,10 @@ class User implements IDBAccessObject, UserIdentity {
         * Returns an array of the groups that a particular group can add/remove.
         *
         * @param string $group The group to check for whether it can add/remove
-        * @return array Array( 'add' => array( addablegroups ),
-        *     'remove' => array( removablegroups ),
-        *     'add-self' => array( addablegroups to self),
-        *     'remove-self' => array( removable groups from self) )
+        * @return array [ 'add' => [ addablegroups ],
+        *     'remove' => [ removablegroups ],
+        *     'add-self' => [ addablegroups to self ],
+        *     'remove-self' => [ removable groups from self ] ]
         */
        public static function changeableByGroup( $group ) {
                global $wgAddGroups, $wgRemoveGroups, $wgGroupsAddToSelf, $wgGroupsRemoveFromSelf;
@@ -5117,10 +5117,10 @@ class User implements IDBAccessObject, UserIdentity {
 
        /**
         * Returns an array of groups that this user can add and remove
-        * @return array Array( 'add' => array( addablegroups ),
-        *  'remove' => array( removablegroups ),
-        *  'add-self' => array( addablegroups to self),
-        *  'remove-self' => array( removable groups from self) )
+        * @return array [ 'add' => [ addablegroups ],
+        *  'remove' => [ removablegroups ],
+        *  'add-self' => [ addablegroups to self ],
+        *  'remove-self' => [ removable groups from self ] ]
         */
        public function changeableGroups() {
                if ( $this->isAllowed( 'userrights' ) ) {
index 74d15a4..f648535 100644 (file)
@@ -31,7 +31,7 @@ class FullSearchResultWidget implements SearchResultWidget {
 
        /**
         * @param SearchResult $result The result to render
-        * @param string $terms Terms to be highlighted (@see SearchResult::getTextSnippet)
+        * @param string[] $terms Terms to be highlighted (@see SearchResult::getTextSnippet)
         * @param int $position The result position, including offset
         * @return string HTML
         */
@@ -121,7 +121,7 @@ class FullSearchResultWidget implements SearchResultWidget {
         * title with highlighted words).
         *
         * @param SearchResult $result
-        * @param string $terms
+        * @param string[] $terms
         * @param int $position
         * @return string HTML
         */
index 095c30a..745bc12 100644 (file)
@@ -24,7 +24,7 @@ class InterwikiSearchResultWidget implements SearchResultWidget {
 
        /**
         * @param SearchResult $result The result to render
-        * @param string $terms Terms to be highlighted (@see SearchResult::getTextSnippet)
+        * @param string[] $terms Terms to be highlighted (@see SearchResult::getTextSnippet)
         * @param int $position The result position, including offset
         * @return string HTML
         */
index 3fbdbef..4f0a271 100644 (file)
@@ -10,7 +10,7 @@ use SearchResult;
 interface SearchResultWidget {
        /**
         * @param SearchResult $result The result to render
-        * @param string $terms Terms to be highlighted (@see SearchResult::getTextSnippet)
+        * @param string[] $terms Terms to be highlighted (@see SearchResult::getTextSnippet)
         * @param int $position The zero indexed result position, including offset
         * @return string HTML
         */
index 552cbaf..86a04b1 100644 (file)
@@ -26,7 +26,7 @@ class SimpleSearchResultWidget implements SearchResultWidget {
 
        /**
         * @param SearchResult $result The result to render
-        * @param string $terms Terms to be highlighted (@see SearchResult::getTextSnippet)
+        * @param string[] $terms Terms to be highlighted (@see SearchResult::getTextSnippet)
         * @param int $position The result position, including offset
         * @return string HTML
         */
index 4e663c2..fd8aedf 100644 (file)
@@ -2967,8 +2967,8 @@ class Language {
        }
 
        /**
-        * @param array $termsArray
-        * @return array
+        * @param string[] $termsArray
+        * @return string[]
         */
        function convertForSearchResult( $termsArray ) {
                # some languages, e.g. Chinese, need to do a conversion
@@ -4537,7 +4537,7 @@ class Language {
         *
         * @since 1.22
         * @param string $code Language code
-        * @return array Array( fallbacks, site fallbacks )
+        * @return array [ fallbacks, site fallbacks ]
         */
        public static function getFallbacksIncludingSiteLanguage( $code ) {
                global $wgLanguageCode;
diff --git a/languages/LanguageCode.php b/languages/LanguageCode.php
deleted file mode 100644 (file)
index 7d954d3..0000000
+++ /dev/null
@@ -1,204 +0,0 @@
-<?php
-/**
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Language
- */
-
-/**
- * Methods for dealing with language codes.
- * @todo Move some of the code-related static methods out of Language into this class
- *
- * @since 1.29
- * @ingroup Language
- */
-class LanguageCode {
-       /**
-        * Mapping of deprecated language codes that were used in previous
-        * versions of MediaWiki to up-to-date, current language codes.
-        * These may or may not be valid BCP 47 codes; they are included here
-        * because MediaWiki renamed these particular codes at some point.
-        *
-        * @var array Mapping from deprecated MediaWiki-internal language code
-        *   to replacement MediaWiki-internal language code.
-        *
-        * @since 1.30
-        * @see https://meta.wikimedia.org/wiki/Special_language_codes
-        */
-       private static $deprecatedLanguageCodeMapping = [
-               // Note that als is actually a valid ISO 639 code (Tosk Albanian), but it
-               // was previously used in MediaWiki for Alsatian, which comes under gsw
-               'als' => 'gsw', // T25215
-               'bat-smg' => 'sgs', // T27522
-               'be-x-old' => 'be-tarask', // T11823
-               'fiu-vro' => 'vro', // T31186
-               'roa-rup' => 'rup', // T17988
-               'zh-classical' => 'lzh', // T30443
-               'zh-min-nan' => 'nan', // T30442
-               'zh-yue' => 'yue', // T30441
-       ];
-
-       /**
-        * Mapping of non-standard language codes used in MediaWiki to
-        * standardized BCP 47 codes.  These are not deprecated (yet?):
-        * IANA may eventually recognize the subtag, in which case the `-x-`
-        * infix could be removed, or else we could rename the code in
-        * MediaWiki, in which case they'd move up to the above mapping
-        * of deprecated codes.
-        *
-        * As a rule, we preserve all distinctions made by MediaWiki
-        * internally.  For example, `de-formal` becomes `de-x-formal`
-        * instead of just `de` because MediaWiki distinguishes `de-formal`
-        * from `de` (for example, for interface translations).  Similarly,
-        * BCP 47 indicates that `kk-Cyrl` SHOULD not be used because it
-        * "typically does not add information", but in our case MediaWiki
-        * LanguageConverter distinguishes `kk` (render content in a mix of
-        * Kurdish variants) from `kk-Cyrl` (convert content to be uniformly
-        * Cyrillic).  As the BCP 47 requirement is a SHOULD not a MUST,
-        * `kk-Cyrl` is a valid code, although some validators may emit
-        * a warning note.
-        *
-        * @var array Mapping from nonstandard MediaWiki-internal codes to
-        *   BCP 47 codes
-        *
-        * @since 1.32
-        * @see https://meta.wikimedia.org/wiki/Special_language_codes
-        * @see https://phabricator.wikimedia.org/T125073
-        */
-       private static $nonstandardLanguageCodeMapping = [
-               // All codes returned by Language::fetchLanguageNames() validated
-               // against IANA registry at
-               //   https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry
-               // with help of validator at
-               //   http://schneegans.de/lv/
-               'cbk-zam' => 'cbk', // T124657
-               'de-formal' => 'de-x-formal',
-               'eml' => 'egl', // T36217
-               'en-rtl' => 'en-x-rtl',
-               'es-formal' => 'es-x-formal',
-               'hu-formal' => 'hu-x-formal',
-               'map-bms' => 'jv-x-bms', // [[en:Banyumasan_dialect]] T125073
-               'mo' => 'ro-Cyrl-MD', // T125073
-               'nrm' => 'nrf', // [[en:Norman_language]] T25216
-               'nl-informal' => 'nl-x-informal',
-               'roa-tara' => 'nap-x-tara', // [[en:Tarantino_dialect]]
-               'simple' => 'en-simple',
-               'sr-ec' => 'sr-Cyrl', // T117845
-               'sr-el' => 'sr-Latn', // T117845
-
-               // Although these next codes aren't *wrong* per se, including
-               // both the script and the country code helps compatibility with
-               // other BCP 47 users. Note that MW also uses `zh-Hans`/`zh-Hant`,
-               // without a country code, and those should be left alone.
-               // (See $variantfallbacks in LanguageZh.php for Hans/Hant id.)
-               'zh-cn' => 'zh-Hans-CN',
-               'zh-sg' => 'zh-Hans-SG',
-               'zh-my' => 'zh-Hans-MY',
-               'zh-tw' => 'zh-Hant-TW',
-               'zh-hk' => 'zh-Hant-HK',
-               'zh-mo' => 'zh-Hant-MO',
-       ];
-
-       /**
-        * Returns a mapping of deprecated language codes that were used in previous
-        * versions of MediaWiki to up-to-date, current language codes.
-        *
-        * This array is merged into $wgDummyLanguageCodes in Setup.php, along with
-        * the fake language codes 'qqq' and 'qqx', which are used internally by
-        * MediaWiki's localisation system.
-        *
-        * @return string[]
-        *
-        * @since 1.29
-        */
-       public static function getDeprecatedCodeMapping() {
-               return self::$deprecatedLanguageCodeMapping;
-       }
-
-       /**
-        * Returns a mapping of non-standard language codes used by
-        * (current and previous version of) MediaWiki, mapped to standard
-        * BCP 47 names.
-        *
-        * This array is exported to JavaScript to ensure
-        * mediawiki.language.bcp47 stays in sync with LanguageCode::bcp47().
-        *
-        * @return string[]
-        *
-        * @since 1.32
-        */
-       public static function getNonstandardLanguageCodeMapping() {
-               $result = [];
-               foreach ( self::$deprecatedLanguageCodeMapping as $code => $ignore ) {
-                       $result[$code] = self::bcp47( $code );
-               }
-               foreach ( self::$nonstandardLanguageCodeMapping as $code => $ignore ) {
-                       $result[$code] = self::bcp47( $code );
-               }
-               return $result;
-       }
-
-       /**
-        * Replace deprecated language codes that were used in previous
-        * versions of MediaWiki to up-to-date, current language codes.
-        * Other values will returned unchanged.
-        *
-        * @param string $code Old language code
-        * @return string New language code
-        *
-        * @since 1.30
-        */
-       public static function replaceDeprecatedCodes( $code ) {
-               return self::$deprecatedLanguageCodeMapping[$code] ?? $code;
-       }
-
-       /**
-        * Get the normalised IETF language tag
-        * See unit test for examples.
-        * See mediawiki.language.bcp47 for the JavaScript implementation.
-        *
-        * @param string $code The language code.
-        * @return string A language code complying with BCP 47 standards.
-        *
-        * @since 1.31
-        */
-       public static function bcp47( $code ) {
-               $code = self::replaceDeprecatedCodes( strtolower( $code ) );
-               if ( isset( self::$nonstandardLanguageCodeMapping[$code] ) ) {
-                       $code = self::$nonstandardLanguageCodeMapping[$code];
-               }
-               $codeSegment = explode( '-', $code );
-               $codeBCP = [];
-               foreach ( $codeSegment as $segNo => $seg ) {
-                       // when previous segment is x, it is a private segment and should be lc
-                       if ( $segNo > 0 && strtolower( $codeSegment[( $segNo - 1 )] ) == 'x' ) {
-                               $codeBCP[$segNo] = strtolower( $seg );
-                       // ISO 3166 country code
-                       } elseif ( ( strlen( $seg ) == 2 ) && ( $segNo > 0 ) ) {
-                               $codeBCP[$segNo] = strtoupper( $seg );
-                       // ISO 15924 script code
-                       } elseif ( ( strlen( $seg ) == 4 ) && ( $segNo > 0 ) ) {
-                               $codeBCP[$segNo] = ucfirst( strtolower( $seg ) );
-                       // Use lowercase for other cases
-                       } else {
-                               $codeBCP[$segNo] = strtolower( $seg );
-                       }
-               }
-               $langCode = implode( '-', $codeBCP );
-               return $langCode;
-       }
-}
diff --git a/languages/MessageLocalizer.php b/languages/MessageLocalizer.php
deleted file mode 100644 (file)
index 9a1796b..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-<?php
-/**
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Language
- */
-
-/**
- * Interface for localizing messages in MediaWiki
- *
- * @since 1.30
- * @ingroup Language
- */
-interface MessageLocalizer {
-
-       /**
-        * This is the method for getting translated interface messages.
-        *
-        * @see https://www.mediawiki.org/wiki/Manual:Messages_API
-        * @see Message::__construct
-        *
-        * @param string|string[]|MessageSpecifier $key Message key, or array of keys,
-        *   or a MessageSpecifier.
-        * @param mixed $params,... Normal message parameters
-        * @return Message
-        */
-       public function msg( $key /*...*/ );
-
-}
index 455678d..9b9720d 100644 (file)
@@ -188,8 +188,8 @@ class LanguageZh extends LanguageZh_hans {
        }
 
        /**
-        * @param array $termsArray
-        * @return array
+        * @param string[] $termsArray
+        * @return string[]
         */
        function convertForSearchResult( $termsArray ) {
                $terms = implode( '|', $termsArray );
index ad6680d..cc70f69 100644 (file)
        "actionthrottled": "لا يمكن عمل المزيد من هذا الفعل",
        "actionthrottledtext": "كإجراء ضد السبام، أنت ممنوع من إجراء هذا الفعل عدد كبير من المرات في فترة زمنية قصيرة، ولقد تجاوزت هذا الحد.\nمن فضلك حاول مرة ثانية خلال عدة دقائق.",
        "protectedpagetext": "هذه الصفحة تمت حمايتها لمنع التعديل.",
-       "viewsourcetext": "يمكنك رؤية ونسخ مصدر هذه الصفحة:",
+       "viewsourcetext": "يمكنك رؤية ونسخ مصدر هذه الصفحة.",
        "viewyourtext": "يمكنك رؤية ونسخ مصدر <strong>تعديلاتك</strong> في هذه الصفحة.",
-       "protectedinterface": "هذه الصفحة توفر نص الواجهة للبرنامج، وهي مقفلة لمنع التخريب.",
+       "protectedinterface": "توفر هذه الصفحة نص الواجهة للبرنامج في هذا الويكي، وهي محمية لمنع سوء استخدامها.\nلإضافة أو تغيير الترجمات لكل الويكيات، رجاء استخدم [https://translatewiki.net/ translatewiki.net]، مشروع الترجمة الخاص بميدياويكي.",
        "editinginterface": "<strong>تحذير:</strong> أنت تقوم بتحرير صفحة تستخدم في الواجهة النصية للبرنامج.\nسوف تؤثر التغييرات على هذه الصفحة على مظهر واجهة المستخدم للمستخدمين الآخرين.",
        "cascadeprotected": "تمت حماية هذه الصفحة من التعديل لأنها مدمجة في {{PLURAL:$1||الصفحة التالية، والتي|الصفحتين التاليتين، واللتين|الصفحات التالية، والتي}} تم استعمال خاصية \"حماية الصفحات المدمجة\" {{PLURAL:$1||بها|بهما|بها}}:\n$2",
        "namespaceprotected": "لا تمتلك الصلاحية لتعديل الصفحات في نطاق <strong>$1</strong>.",
        "pt-userlogout": "أخرج",
        "php-mail-error-unknown": "خطأ غير معروف في وظيفة البريد PHP's mail()",
        "user-mail-no-addy": "لقد حاولت إرسال بريد إلكتروني دون عنوان بريد إلكتروني.",
-       "resetpass_announce": "تم تسجيل دخولك بكلمة سر مؤقتة.\nللدخول بشكل نهائي، يجب عليك ضبط كلمة سر جديدة هنا:",
+       "resetpass_announce": "لإنهاء عملية تسجيل الدخول، يجب تعيين كلمة سر جديدة.",
        "resetpass_header": "غير كلمة سر الحساب",
        "oldpassword": "كلمة السر القديمة:",
        "newpassword": "كلمة السر الجديدة:",
        "mergelog": "سجل الدمج",
        "revertmerge": "إلغاء الدمج",
        "mergelogpagetext": "بالأسفل قائمة بأحدث عمليات الدمج لتاريخ صفحة ما إلى أخرى.",
-       "history-title": " «$1»: تاريخ المراجعة",
-       "difference-title": "«$1»: الفرق بينات المراجعتين",
-       "difference-title-multipage": "«$1» و«$2»: الفرق بين الصفحتين",
+       "history-title": "تاريخ \"$1\"",
+       "difference-title": "الفرق بينات المراجعتين ل«$1»",
+       "difference-title-multipage": "الفرق بين الصفحتين «$1» و«$2»",
        "difference-multipage": "(الفرق بين الصفحتين)",
        "lineno": "سطر $1:",
        "compareselectedversions": "قارن بين النسختين المختارتين",
        "recentchangeslinked-to": "أظهر التغييرات للصفحات الموصولة للصفحة المعطاة عوضاً عن ذلك",
        "upload": "صبّ فشياي",
        "uploadlogpage": "سجل الرفع",
-       "filedesc": "ملخص:",
+       "filedesc": "ملخص",
        "license": "ترخيص:",
        "file-anchor-link": "فيشياي",
        "filehist": "تاريخ الپاج",
        "restriction-edit": "تبديل",
        "undeletelink": "اعرض/استعد",
        "undeleteviewlink": "اعرض",
-       "namespace": "النطاق",
+       "namespace": "النطاق:",
        "invert": "اعكس الاختيار",
        "blanknamespace": "(رئيسي)",
        "contributions": "مساهمات {{GENDER:$1|المستعمل|المستعملة}}",
        "tooltip-n-randompage": "خرّج پاج بالزهر",
        "tooltip-feed-atom": "تلقيم أتوم لهذه الصفحة",
        "tooltip-t-contributions": "ليستة مساهمات ها {{GENDER:$1|المستعمل|المستعملة}}",
-       "tooltip-t-emailuser": "أرسÙ\84 Ø±Ø³Ø§Ù\84Ø© Ø¥Ù\84Ù\83ترÙ\88Ù\86Ù\8aة {{GENDER:$1|لهذا المستخدم|لهذه المستخدمة}}",
+       "tooltip-t-emailuser": "إرساÙ\84 Ø±Ø³Ø§Ù\84ة {{GENDER:$1|لهذا المستخدم|لهذه المستخدمة}}",
        "tooltip-t-upload": "صبّ فيشيايات",
        "tooltip-ca-nstab-user": "اعرض صفحة المستخدم",
        "tooltip-ca-nstab-special": "هذي پاج سپاسيال، و ما تنجّمش تبدّل فيها شي",
index f6435e7..bff03ea 100644 (file)
        "undelete-revision": "Versión borrata de $1 (editada por $3, o $4 a las $5):",
        "undeleterevision-missing": "Versión no conforme u no trobata. Regular que o vinclo sía incorrecto u que ixa versión s'haiga restaurato u borrato d'o fichero.",
        "undelete-nodiff": "No s'ha trobato garra versión anterior.",
-       "undeletebtn": "Restaurar!",
+       "undeletebtn": "Restaurar",
        "undeletelink": "amostrar/restaurar",
        "undeleteviewlink": "veyer",
        "undeleteinvert": "Contornar selección",
index 39495af..e168b0e 100644 (file)
        "otherlanguages": "بلغات أخرى",
        "redirectedfrom": "(بالتحويل من $1)",
        "redirectpagesub": "صفحة تحويل",
-       "redirectto": "تحويل إلى",
+       "redirectto": "تحويل إلى:",
        "lastmodifiedat": "آخر تعديل لهذه الصفحة كان يوم $1، الساعة $2.",
        "viewcount": "{{PLURAL:$1|لم تعرض هذه الصفحة أبدا|تم عرض هذه الصفحة مرة واحدة|تم عرض هذه الصفحة مرتين|تم عرض هذه الصفحة $1 مرات|تم عرض هذه الصفحة $1 مرة}}.",
        "protectedpage": "صفحة محمية",
        "selfredirect": "<strong>تحذير:</strong> أنت تقوم بتحويل الصفحة إلى نفسها.\nربما حددت الهدف الخطأ للتحويلة أو أنك تقوم بتحرير الصفحة الخطأ.\n\nإذا نقرت على «$1» مرة أخرى، سيتم إنشاء التحويلة رغم الخطأ.",
        "missingcommenttext": "من فضلك أدخل تعليقا.",
        "missingcommentheader": "<strong>تنبيه:</strong>  لم تقم بوضع موضوع/عنوان لهذا التعليق.\nإذا قمت بالضغط على \"$1\" مجددا، سيتم حفظ تعليقك بدون عنوان.",
-       "summary-preview": "معاينة ملخص تحرير",
+       "summary-preview": "معاينة ملخص تحرير:",
        "subject-preview": "معاينة الموضوع:",
        "previewerrortext": "حدث خطأ أثناء محاولة معاينة تغييراتك.",
        "blockedtitle": "المستخدم ممنوع",
        "autoblockedtext": "مُنِع عنوان آيبيك تلقائيا لأن مستخدما آخرا منعه $1 استخدمه.\nالسبب المعطى هو التالي:\n\n:<em>$2</em>\n\n* بداية المنع: $8\n* انتهاء المنع: $6\n* الممنوع المقصود: $7\n\nيمكنك أن تتصل ب $1 أو أحد [[{{MediaWiki:Grouppage-sysop}}|الإداريين]] الآخرين لمناقشة المنع.\n\nلاحظ أنه لا يمكنك استخدام خاصية \"{{int:emailuser}}\" إلا لو كان لديك عنوان بريد إلكتروني صحيح مسجل في [[Special:Preferences|تفضيلاتك]] ولم يتم منعك من استخدامه.\n\nعنوان آيبيك الحالي $3، ورقم المنع #$5.\nمن فضلك اذكر كل التفاصيل بالأعلى في أي استعلامات تقوم بها.",
        "systemblockedtext": "اسم المستخدم أو عنوان الأيبي الخاص بك تم منعه تلقائيا بواسطة ميدياويكي.\nالسبب المعطى هو:\n\n:<em>$2</em>\n\n* بداية المنع: $8\n* نهاية المنع: $6\n* المقصود بالمنع: $7\n\nعنوان الأيبي الحالي الخاص بك هو $3.\nمن فضلك ضمن كل التفاصيل بالأعلى في أي استعلام تقوم به.",
        "blockednoreason": "لا سبب معطى",
+       "blockedtext-composite": "<strong>تم منع اسم المستخدم أو عنوان الآيبي الخاص بك.</strong>\n\nالسبب المعطى هو:\n\n:<em>$2</em>.\n\n* بداية المنع: $8\n*  نهاية صلاحية أطول منع: $6\n\nعنوان الآيبي الحالي الخاص بك هو $3.\nيُرجَى تضمين جميع التفاصيل أعلاه في أية استفسارات تقوم بها.",
+       "blockedtext-composite-reason": "هناك عدة عمليات منع ضد حسابك و/أو عنوان الآيبي الخاص بك",
        "whitelistedittext": "يجب عليك $1 لتتمكن من تعديل الصفحات.",
        "confirmedittext": "يجب عليك تأكيد بريدك الإلكتروني قبل تعديل الصفحات.\nمن فضلك اكتب وأكد بريدك الإلكتروني من خلال [[Special:Preferences|تفضيلاتك]].",
        "nosuchsectiontitle": "تعذر إيجاد القسم",
        "mergelogpagetext": "بالأسفل قائمة بأحدث عمليات الدمج لتاريخ صفحة ما إلى أخرى.",
        "history-title": "تاريخ \"$1\"",
        "difference-title": "الفرق بين المراجعتين ل\"$1\"",
-       "difference-title-multipage": "«$1» و«$2»: الفرق بين الصفحتين",
+       "difference-title-multipage": "الفرق بين الصفحتين «$1» و«$2»",
        "difference-multipage": "(الفرق بين الصفحتين)",
        "lineno": "سطر $1:",
        "compareselectedversions": "قارن بين النسختين المختارتين",
        "uctop": "حالية",
        "month": "من شهر (وأقدم):",
        "year": "من سنة (وأقدم):",
-       "date": "من تاريخ (وأقدم).",
+       "date": "من تاريخ (وأقدم):",
        "sp-contributions-newbies": "اعرض مساهمات الحسابات الجديدة فقط",
        "sp-contributions-newbies-sub": "للحسابات الجديدة",
        "sp-contributions-newbies-title": "مساهمات المستخدم للحسابات الجديدة",
        "tooltip-feed-rss": "تلقيم أر إس إس لهذه الصفحة",
        "tooltip-feed-atom": "تلقيم أتوم لهذه الصفحة",
        "tooltip-t-contributions": "رؤية قائمة مساهمات {{GENDER:$1|هذا المستخدم|هذه المستخدمة}}",
-       "tooltip-t-emailuser": "أرسÙ\84 Ø±Ø³Ø§Ù\84Ø© Ø¥Ù\84Ù\83ترÙ\88Ù\86Ù\8aة {{GENDER:$1|لهذا المستخدم|لهذه المستخدمة}}",
+       "tooltip-t-emailuser": "إرساÙ\84 Ø±Ø³Ø§Ù\84ة {{GENDER:$1|لهذا المستخدم|لهذه المستخدمة}}",
        "tooltip-t-info": "المزيد من المعلومات عن هذه الصفحة",
        "tooltip-t-upload": "ارفع ملفات",
        "tooltip-t-specialpages": "قائمة بكل الصفحات الخاصة",
        "previousdiff": "→ التعديل السابق",
        "nextdiff": "التعديل اللاحق ←",
        "mediawarning": "<strong>تحذير:</strong> قد يحتوي نوع هذا الملف على كود خبيث.\nيمكن عند تشغيله السيطرة على نظامك.",
-       "imagemaxsize": "حد حجم الصورة في صفحات وصف الملفات",
+       "imagemaxsize": "حد حجم الصورة في صفحات وصف الملفات:",
        "thumbsize": "حجم العرض المصغر:",
        "widthheightpage": "$1×$2، {{PLURAL:$3|لا صفحات|صفحة واحدة|صفحتان|$3 صفحات|$3 صفحة}}",
        "file-info": "حجم الملف: $1، نوع MIME: $2",
        "version-poweredby-others": "آخرون",
        "version-poweredby-translators": "مترجمو ترانسليت ويكي دوت نت",
        "version-credits-summary": "نود أن نعرف بالأشخاص التالية أسماؤهم لمساهمتهم في [[Special:Version|ميدياويكي]].",
-       "version-license-info": "Ù\85Ù\8aدÙ\8aاÙ\88Ù\8aÙ\83Ù\8a Ø¨Ø±Ù\86اÙ\85ج Ø­Ø±Ø\8c Ù\8aØ­Ù\82 Ù\84Ù\83 ØªÙ\88زÙ\8aعÙ\87 Ù\88/Ø£Ù\88 ØªØ¹Ø¯Ù\8aÙ\84Ù\87 Ù\88Ù\81Ù\82اÙ\8b Ù\84بÙ\86Ù\88د Ø±Ø®ØµØ© Ø¬Ù\86Ù\88 Ø§Ù\84عÙ\85Ù\88Ù\85Ù\8aØ© Ù\83Ù\85ا Ù\86شرتÙ\87ا Ù\85ؤسسة Ø§Ù\84برÙ\85جÙ\8aات Ø§Ù\84حرةØ\8c Ø§Ù\84إصدار Ø§Ù\84ثاÙ\86Ù\8a Ø£Ù\88 (Ù\88Ù\81Ù\82ا Ù\84اختÙ\8aارÙ\83 Ø£Ù\86ت) Ø£Ù\8a Ø¥ØµØ¯Ø§Ø± Ù\84احÙ\82.\n\nÙ\87ذا Ø§Ù\84برÙ\86اÙ\85ج Ù\8aÙ\88زع Ø¹Ù\84Ù\89 Ø£Ù\85Ù\84 Ø£Ù\86 Ù\8aÙ\83Ù\88Ù\86 Ù\85Ù\81Ù\8aداÙ\8bØ\8c Ù\88Ù\84Ù\83Ù\86 <em>دÙ\88Ù\86 Ø£Ù\8aØ© Ø¶Ù\85اÙ\86ات</em>Ø\8c Ø¨Ù\85ا Ù\81Ù\8a Ø°Ù\84Ù\83 Ø¶Ù\85اÙ\86ات <strong>اÙ\84تسÙ\88Ù\8aÙ\82</strong> Ø£Ù\88 <strong>اÙ\84Ù\85Ù\84اءÙ\85Ø© Ù\84غرض Ù\85عÙ\8aÙ\86</strong>. Ø§Ù\86ظر Ø±Ø®ØµØ© ØºÙ\86Ù\88 Ø§Ù\84عÙ\85Ù\88Ù\85Ù\8aØ© Ù\84Ù\85زÙ\8aد Ù\85Ù\86 Ø§Ù\84تÙ\81اصÙ\8aÙ\84.\n\nÙ\8aÙ\86بغÙ\8a Ø£Ù\86 ØªÙ\83Ù\88Ù\86 Ù\82د ØªÙ\84Ù\82Ù\8aت Ù\86سخة Ù\85Ù\86 Ø±Ø®ØµØ© Ø¬Ù\86Ù\88 Ø§Ù\84عÙ\85Ù\88Ù\85Ù\8aØ© Ø¥Ø°Ø§ Ù\84Ù\85 Ù\8aتÙ\85 Ø°Ù\84Ù\83Ø\8c Ø§Ù\83تب Ø¥Ù\84Ù\89: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA Ø£Ù\88 [//www.gnu.org/licenses/old-licenses/gpl-2.0.html Ø§Ù\82رأ على الإنترنت].",
+       "version-license-info": "Ù\85Ù\8aدÙ\8aاÙ\88Ù\8aÙ\83Ù\8a Ø¨Ø±Ù\86اÙ\85ج Ø­Ø±Ø\8c Ù\8aØ­Ù\82 Ù\84Ù\83 ØªÙ\88زÙ\8aعÙ\87 Ù\88/Ø£Ù\88 ØªØ¹Ø¯Ù\8aÙ\84Ù\87 Ù\88Ù\81Ù\82اÙ\8b Ù\84بÙ\86Ù\88د Ø±Ø®ØµØ© Ø¬Ù\86Ù\88 Ø§Ù\84عÙ\85Ù\88Ù\85Ù\8aØ© Ù\83Ù\85ا Ù\86شرتÙ\87ا Ù\85ؤسسة Ø§Ù\84برÙ\85جÙ\8aات Ø§Ù\84حرةØ\8c Ø§Ù\84إصدار Ø§Ù\84ثاÙ\86Ù\8a Ø£Ù\88 (Ù\88Ù\81Ù\82ا Ù\84اختÙ\8aارÙ\83 Ø£Ù\86ت) Ø£Ù\8a Ø¥ØµØ¯Ø§Ø± Ù\84احÙ\82.\n\nÙ\87ذا Ø§Ù\84برÙ\86اÙ\85ج Ù\8aÙ\88زع Ø¹Ù\84Ù\89 Ø£Ù\85Ù\84 Ø£Ù\86 Ù\8aÙ\83Ù\88Ù\86 Ù\85Ù\81Ù\8aداÙ\8bØ\8c Ù\88Ù\84Ù\83Ù\86 <em>دÙ\88Ù\86 Ø£Ù\8aØ© Ø¶Ù\85اÙ\86ات</em>Ø\8c Ø¨Ù\85ا Ù\81Ù\8a Ø°Ù\84Ù\83 Ø¶Ù\85اÙ\86ات <strong>اÙ\84تسÙ\88Ù\8aÙ\82</strong> Ø£Ù\88 <strong>اÙ\84Ù\85Ù\84اءÙ\85Ø© Ù\84غرض Ù\85عÙ\8aÙ\86</strong>. Ø§Ù\86ظر Ø±Ø®ØµØ© Ø¬Ù\86Ù\88 Ø§Ù\84عÙ\85Ù\88Ù\85Ù\8aØ© Ù\84Ù\85زÙ\8aد Ù\85Ù\86 Ø§Ù\84تÙ\81اصÙ\8aÙ\84.\n\nÙ\8aÙ\86بغÙ\8a Ø£Ù\86 ØªÙ\83Ù\88Ù\86 Ù\82د ØªÙ\84Ù\82Ù\8aت [{{SERVER}}{{SCRIPTPATH}}/COPYING Ù\86سخة Ù\85Ù\86 Ø±Ø®ØµØ© Ø¬Ù\86Ù\88 Ø§Ù\84عÙ\85Ù\88Ù\85Ù\8aØ©] Ø¥Ø°Ø§ Ù\84Ù\85 Ù\8aتÙ\85 Ø°Ù\84Ù\83Ø\8c Ø§Ù\83تب Ø¥Ù\84Ù\89 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA Ø£Ù\88 [//www.gnu.org/licenses/old-licenses/gpl-2.0.html Ø§Ù\82رأÙ\87ا على الإنترنت].",
        "version-software": "البرنامج المثبت",
        "version-software-product": "المنتج",
        "version-software-version": "النسخة",
        "redirect-summary": "هذه الصفحة الخاصة تحوّل إلى ملف (باسمه) أو صفحة (برقم إحدى مراجعاتها) أو إلى صفحة مستخدم (برقمه التعريفي) أو إلى مدخلة سجل (برقم السجل). الاستخدام [[{{#Special:Redirect}}/file/Example.jpg]] أو [[{{#Special:Redirect}}/revision/328429]] أو [[{{#Special:Redirect}}/user/101]] أو [[{{#Special:Redirect}}/logid/186]].",
        "redirect-submit": "اذهب",
        "redirect-lookup": "ابحث في:",
-       "redirect-value": "الوجهة",
+       "redirect-value": "الوجهة:",
        "redirect-user": "رقم مستخدم",
        "redirect-page": "معرف الصفحة",
        "redirect-revision": "مراجعة صفحة",
        "tags-activate-submit": "تفعيل",
        "tags-deactivate-title": "عطل الوسم",
        "tags-deactivate-question": "أنت على وشك تعطيل الوسم \"$1\".",
-       "tags-deactivate-reason": "سبب",
+       "tags-deactivate-reason": "اÙ\84سبب:",
        "tags-deactivate-not-allowed": "من غير الممكن تعطيل الوسم \"$1\".",
        "tags-deactivate-submit": "عطل",
        "tags-apply-no-permission": "ليس لديك إذن لتطبيق علامات التغيير جنبا إلى جنب مع التغييرات.",
        "duration-centuries": "{{PLURAL:$1||قرن واحد|قرنان|$1 قرون|$1 قرنًا|$1 قرن}}",
        "duration-millennia": "{{PLURAL:$1||ألفية واحدة|ألفيتان|$1 ألفيات|$1 ألفية}}",
        "rotate-comment": "تدوير الصورة  {{PLURAL:$1||درجة واحدة|درجتان|$1 درجات|$1 درجة}} باتجاه عقارب الساعة",
-       "limitreport-title": "بيانات تحليلية",
+       "limitreport-title": "بيانات تحليلية:",
        "limitreport-cputime": "زمن المعالجة المستغرق",
        "limitreport-cputime-value": "{{PLURAL:$1|أقل من ثانية|ثانية واحدة|ثانيتان|$1 ثوان|$1 ثانية}}",
        "limitreport-walltime": "الزمن الحقيقي المستغرق",
index 9bc0ab2..c53c1e4 100644 (file)
        "otherlanguages": "بلغات تانيه",
        "redirectedfrom": "(تحويل من $1)",
        "redirectpagesub": "صفحة تحويل",
-       "redirectto": "تحويل ل",
+       "redirectto": "تحويل ل:",
        "lastmodifiedat": "الصفحه دى اتعدلت اخر مره فى $1,‏ $2.",
        "viewcount": "الصفحة دى اتدخل عليها{{PLURAL:$1|مرة واحدة|مرتين|$1 مرات|$1 مرة}}.",
        "protectedpage": "صفحه محميه",
        "protectedpagetext": "الصفحة دى اتحمت من التعديل.",
        "viewsourcetext": "ممكن تشوف وتنسخ مصدر الصفحه دى",
        "protectedinterface": "الصفحة دى هى اللى بتوفر نص الواجهة بتاعة البرنامج،وهى مقفولة لمنع التخريب.\nعلشان إضافة أو تغيير الترجمات لجميع مشاريع الويكي،  لو سمحت روح على [https://translatewiki.net/ translatewiki.net]، مشروع ترجمة ميدياويكى",
-       "editinginterface": "<strong>تحذير</strong> : أنت بتعدل صفحة بتستخدم فى الواجهة النصية  بتاعة البرنامج. \nالتغييرات فى الصفحة دى ها تأثر على مظهر واجهة اليوزر لليوزرز التانيين. \nعلشان إضافة أو تغيير الترجمات لجميع مشاريع الويكي،  لو سمحت روح على [https://translatewiki.net/ translatewiki.net]، مشروع ترجمة ميدياويكى",
+       "editinginterface": "<strong>تحذير:</strong> أنت بتعدل صفحة بتستخدم فى الواجهة النصية  بتاعة البرنامج. \nالتغييرات فى الصفحة دى ها تأثر على مظهر واجهة اليوزر لليوزرز التانيين.",
        "cascadeprotected": "الصفحة دى محمية من التعديل، بسبب انها مدمجة فى {{PLURAL:$1|الصفحة|الصفحتين|الصفحات}} دي، اللى مستعمل فيها خاصية \"حماية الصفحات المدمجة\" :\n$2",
        "namespaceprotected": "ما عندكش صلاحية تعديل الصفحات  اللى فى نطاق <strong>$1</strong>.",
        "ns-specialprotected": "الصفحات المخصوصة مش ممكن تعديلها.",
        "userlogin-yourname-ph": "اكتب اسم اليوزر بتاعك",
        "createacct-another-username-ph": "اكتب اسم يوزر",
        "yourpassword": "الباسوورد:",
-       "userlogin-yourpassword": "الباسورد:",
+       "userlogin-yourpassword": "الباسورد",
        "yourpasswordagain": "اكتب الباسورد تاني:",
        "createacct-yourpasswordagain": "أكد كلمه السر",
        "yourdomainname": "النطاق بتاعك:",
        "userlogin-helplink2": "مساعده ف الدخول",
        "createacct-email-ph": "اكتب عنوان الإيميل بتاعك",
        "createaccountmail": "استخدم باسورد مؤقته و إبعتها ع الايميل المحدد ده",
-       "createacct-reason": "سبب:",
+       "createacct-reason": "اÙ\84سبب",
        "createacct-submit": "افتح حسابك",
        "createacct-benefit-body1": "$1 {{PLURAL:$1|تعديل|تعديلات}}",
        "createacct-benefit-body2": "{{PLURAL:$1|صفحه|صفحات}}",
        "pt-createaccount": "افتح حساب",
        "pt-userlogout": "خروج",
        "changepassword": "غير الباسورد",
-       "resetpass_announce": " علشان تخلص عملية  تسجيل الدخول ،لازم تعملك باسورد جديده:",
+       "resetpass_announce": "علشان تخلص عملية  تسجيل الدخول، لازم تعملك باسورد جديده.",
        "resetpass_text": "<!-- أضف نصا هنا -->",
        "resetpass_header": "غيّر الباسورد بتاعة الحساب",
        "oldpassword": "الباسورد القديمة:",
        "missingcommenttext": "لو سمحت اكتب تعليق تحت.",
        "missingcommentheader": "<strong>خد بالك:</strong> انت ما كتبتش عنوان\\موضوع للتعليق دا\nلو دوست على $1 مرة تانيه، تعليقك ح يتحفظ من غير عنوان.",
        "summary-preview": "بروفه للملخص:",
-       "subject-preview": "بروفة للعنوان/للموضوع",
+       "subject-preview": "بروفة للعنوان/للموضوع:",
        "blockedtitle": "اليوزر ممنوع",
        "blockedtext": "<strong>تم منع اسم اليوزر أو عنوان الاى بى بتاعك .</strong>\n\nاللى عمل المنع $1.\nسبب المنع هو: <em>$2</em>.\n\n* بداية المنع: $8\n* انتهاء المنع: $6\n* الممنوع المقصود: $7\n\nممكن التواصل مع $1 لمناقشة المنع، أو مع واحد من [[{{MediaWiki:Grouppage-sysop}}|الاداريين]] عن المنع.\nافتكر انه مش ممكن تستخدم الخاصيه \"{{int:emailuser}}\" الا اذا كنت سجلت عنوان ايميل صحيح فى صفحة [[Special:Preferences|التفضيلات]] بتاعتك\nو ما تكونش اتمنعت من استعمالها.\nعنوان الاى بى بتاعك حاليا هو $3 وكود المنع هو #$5.\nمن فضلك ضيف كل التفاصيل اللى فوق فى اى رساله للتساؤل عن المنع.",
        "autoblockedtext": "عنوان الأيبى بتاعك اتمنع اتوماتيكى  علشان فى يوزر تانى استخدمه واللى هو كمان ممنوع بــ $1.\nالسبب هو:\n\n:<em>$2</em>\n\n* بداية المنع: $8\n* انهاية المنع: $6\n* الممنوع المقصود: $7\n\nممكن تتصل  ب $1 أو واحد من [[{{MediaWiki:Grouppage-sysop}}|الإداريين]] االتانيين لمناقشة المنع.\n\nلاحظ أنه مش ممكن استخدام خاصية \"{{int:emailuser}}\" إلا اذا كان عندك ايميل صحيح متسجل فى [[Special:Preferences|تفضيلاتك]].\n\nعنوان الأيبى الحالى الخاص بك هو $3، رقم المنع هو #$5.\nلو سمحت تذكر الرقم دا فى اى استفسار.",
        "mergelog": "سجل الدمج",
        "revertmerge": "استرجاع الدمج",
        "mergelogpagetext": "فى تحت لستة بأحدث عمليات الدمج لتاريخ صفحة فى التانية.",
-       "history-title": " «$1»: تاريخ التعديل",
-       "difference-title": "«$1»: الفرق بين النسختين",
+       "history-title": "تاريخ التعديل بتاع «$1»",
+       "difference-title": "الفرق بين النسختين بتاع «$1»",
        "difference-multipage": "(الفرق بين الصفحتين)",
        "lineno": "سطر $1:",
        "compareselectedversions": "قارن بين النسختين المختارتين",
        "recentchangesdays": "عدد الأيام المعروضة فى اخرالتغييرات:",
        "recentchangesdays-max": "(الحد الاقصى $1 {{PLURAL:$1|يوم|ايام}})",
        "recentchangescount": "عدد التعديلات اللى بتظهر اوتوماتيكى فى اخر التغييرات, تواريخ الصفحه, و فى السجلات, :",
-       "prefs-help-recentchangescount": "بÙ\8aحتÙ\88Ù\89 Ø¹Ù\84Ù\89 Ø§Ø­Ø¯Ø« Ø§Ù\84تغÙ\8aÙ\8aرات Ø\8c ØªÙ\88ارÙ\8aØ® Ø§Ù\84صÙ\81حات Ù\88 Ø§Ù\84سجÙ\84ات.",
+       "prefs-help-recentchangescount": "اÙ\82صÙ\89 Ø±Ù\82Ù\85: 1000",
        "savedprefs": "التفضيلات بتاعتك اتحفظت.",
-       "timezonelegend": "منطقة التوقيت",
-       "localtime": "التوقيت المحلى",
+       "timezonelegend": "منطقة التوقيت:",
+       "localtime": "التوقيت المحلى:",
        "timezoneuseserverdefault": "استخدم الويكى الافتراضى ($1)",
        "timezoneuseoffset": "تانى (حدد الفرق)",
-       "servertime": "وقت السيرفر",
+       "servertime": "وقت السيرفر:",
        "guesstimezone": "دخل التوقيت من البراوزر",
        "timezoneregion-africa": "افريقيا",
        "timezoneregion-america": "امريكا",
        "prefs-help-signature": "التعليقات فى صفحات النقاش لازم تتوقع ب\"<nowiki>~~~~</nowiki>\" واللى حتتحول لتوقيعك وتاريخ.",
        "badsig": "الامضا الخام بتاعتك مش صح.\nاتإكد من التاجز بتاعة الHTML.",
        "badsiglength": "الامضا بتاعتك اطول م اللازم.\nلازم تكون اصغر من$1 {{PLURAL:$1|حرف|حرف}}.",
-       "yourgender": "النوع:",
+       "yourgender": "ازاى بتفضل ان البرنامج يخاطبك؟",
        "gender-unknown": "مش متحدد",
        "gender-male": "ذكر",
        "gender-female": "انثى",
-       "prefs-help-gender": "اختÙ\8aارÙ\8a: Ø¨Ù\8aستعÙ\85Ù\84Ù\88Ù\87 Ù\81Ù\89  Ø§Ù\84Ù\85خاطبة Ø§Ù\84Ù\85عتÙ\85دة Ø¹Ù\84Ù\89 Ø§Ù\84Ù\86Ù\88ع Ø¨Ø§Ù\84سÙ\88Ù\81تÙ\88Ù\8aر. المعلومه دى ح تكون علنيه.",
+       "prefs-help-gender": "عÙ\85Ù\84 Ø§Ù\84تÙ\81ضÙ\8aÙ\84 Ø¯Ù\87 Ø§Ø®ØªÙ\8aارÙ\89.\nبÙ\8aستعÙ\85Ù\84Ù\88Ù\87 Ù\81Ù\89  Ø§Ù\84Ù\85خاطبة Ø§Ù\84Ù\85عتÙ\85دة Ø¹Ù\84Ù\89 Ø§Ù\84Ù\86Ù\88ع Ø¨Ø§Ù\84سÙ\88Ù\81تÙ\88Ù\8aر.\nالمعلومه دى ح تكون علنيه.",
        "email": "الإيميل",
        "prefs-help-realname": "الاسم الحقيقى اختيارى.\nلو إخترت تكتبه, حيستعمل بس علشان شغلك يتنسب لإسمك.",
        "prefs-help-email": "عنوان اللإيميل اختيارى ، بس لازم علشان لو نسيت الپاسوورد..",
        "saveusergroups": "حفظ مجموعات {{GENDER:$1|اليوزر}}",
        "userrights-groupsmember": "عضو في:",
        "userrights-groupsmember-auto": "عضو ضمنى فى :",
-       "userrights-groups-help": "إنت ممكن تغير المجموعات اللى اليوزر دا عضو فيها .\n* صندوق متعلم يعنى اليوزر دا عضو فى المجموعة دي.\n* صندوق مش متعلم يعنى  اليوزر دا مش عضو فى المجموعة دي.\n* علامة * يعنى انك مش ممكن تشيل المجموعات بعد ما تضيفها و العكس بالعكس.",
+       "userrights-groups-help": "إنت ممكن تغير المجموعات اللى اليوزر دا عضو فيها:\n* صندوق متعلم يعنى اليوزر دا عضو فى المجموعة دى.\n* صندوق مش متعلم يعنى  اليوزر دا مش عضو فى المجموعة دى.\n* علامة * يعنى انك مش ممكن تشيل المجموعات بعد ما تضيفها و العكس بالعكس.",
        "userrights-reason": "السبب:",
        "userrights-no-interwiki": "أنت  مش من حقك تعدل صلاحيات اليوزرز على الويكيات التانية.",
        "userrights-nodatabase": "قاعدة البيانات $1  مش موجودة أو مش محلية.",
        "recentchanges-label-minor": "ده تعديل صغير",
        "recentchanges-label-bot": "التعديل ده عمله بوت",
        "recentchanges-label-unpatrolled": "التعديل ده مإتراجعش لسه",
-       "recentchanges-legend-heading": "<strong>شرح</strong>",
+       "recentchanges-legend-heading": "<strong>شرح:</strong>",
        "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (بص كمان على [[Special:NewPages|قايمه الصفحات الجديده]])",
        "rcnotefrom": "{{PLURAL:$5|ده التعديل|دى التعديلات}} من اول <strong>$3, $4</strong> (لغايه<strong>$1</strong> معروضه).",
        "rclistfrom": "اظهر التعديلات بدايه من $3 $2",
        "upload-file-error": "غلط داخلي",
        "upload-file-error-text": "حصل غلط داخلى واحنا بنحاول نعمل ملف مؤقت على السيرفر.\nلو سمحت اتصل [[Special:ListUsers/sysop|بسيسوب]].",
        "upload-misc-error": "غلط مش معروف فى التحميل",
-       "upload-misc-error-text": "حصل غلط مش معروف وإنت بتحمل.\nلو سمحت تتاكد أن اليوأرإل صح و ممكن تدخل عليه و بعدين حاول تاني.\nإذا المشكلة تنتها موجودة،اتصل بإدارى نظام.",
+       "upload-misc-error-text": "حصل غلط مش معروف وإنت بتحمل.\nلو سمحت تتاكد أن اليو أر إل صح و ممكن تدخل عليه و بعدين حاول تانى.\nإذا المشكلة تنتها موجودة، اتصل [[Special:ListUsers/sysop|بإدارى نظام]].",
        "upload-too-many-redirects": "الـ URL فيه تحويلات اكتر من اللازم",
        "upload-http-error": "حصل غلط فى الـHTTB :$1",
        "img-auth-accessdenied": "الوصول مش مسموح بيه",
        "brokenredirects-edit": "تحرير",
        "brokenredirects-delete": "مسح",
        "withoutinterwiki": "صفحات من غير وصلات للغات تانيه",
-       "withoutinterwiki-summary": "الصفحات دى  مالهاش لينكات لنسخ بلغات تانية:",
+       "withoutinterwiki-summary": "الصفحات دى  مالهاش لينكات لنسخ بلغات تانية.",
        "withoutinterwiki-legend": "بريفيكس",
        "withoutinterwiki-submit": "عرض",
        "fewestrevisions": "اقل المقالات فى عدد التعديلات",
        "activeusers-noresult": "مالقيناش اى يوزر",
        "listgrouprights": "حقوق مجموعات اليوزرز",
        "listgrouprights-summary": "دى لستة بمجموعات اليوزرز المتعرفة فى الويكى دا، بالحقوق اللى معاهم.\nممكن تلاقى معلومات زيادة عن الحقوق بتاعة كل واحد  [[{{MediaWiki:Listgrouprights-helppage}}|هنا]].",
-       "listgrouprights-key": "* <span class=\"listgrouprights-granted\">حق ممنوح</span>\n* <span class=\"listgrouprights-revoked\">حق متصادر</span>",
+       "listgrouprights-key": "شرح:\n* <span class=\"listgrouprights-granted\">حق ممنوح</span>\n* <span class=\"listgrouprights-revoked\">حق متصادر</span>",
        "listgrouprights-group": "المجموعة",
        "listgrouprights-rights": "الحقوق",
        "listgrouprights-helppage": "Help: حقوق المجموعات",
        "move-page": "انقل $1",
        "move-page-legend": "انقل الصفحة",
        "movepagetext": "لو استعملت النموذج ده ممكن تغير اسم الصفحه، و تنقل تاريخها للاسم الجديد.\nهاتبتدى تحويله من العنوان القديم للصفحه بالعنوان الجديد.\nلكن،  الوصلات فى الصفحات اللى بتتوصل بالصفحه دى مش ها تتغيير.\nاتأكد من ان مافيش [[Special:DoubleRedirects|وصلات متتاليه]] او [[Special:BrokenRedirects|وصلات مقطوعه]]، للتأكد من أن المقالات تتصل مع بعضها بشكل مناسب.\n\nلاحظ ان الصفحه <strong>مش</strong> هاتتنقل لو كان فيه صفحه بالاسم الجديد، إلا إذا كانت صفحة فاضيه، أو صفحة تحويل، ومالهاش تاريخ.\nو ده معناه أنك مش ها تقدر تحط صفحه مكان صفحه، كمان ممكن ارجاع الصفحه لمكانها فى حال تم النقل بشكل غلط.\n\n<strong>تحذير!</strong>\nنقل الصفحه ممكن يكون له اثار كبيرة، وتغييرات مش متوقعه بالنسبة للصفحات المشهوره.\nمن فضلك  اتأكد من فهم عواقب نقل الصفحات قبل ما تقوم بنقل الصفحه.",
-       "movepagetalktext": "صفحة المناقشه بتاعة المقاله هاتتنقل برضه، لو كانت موجوده. لكن صفحة المناقشه '''مش''' هاتتنقل فى الحالات دى:\n* نقل الصفحة عبر نطاقات  مختلفه.\n*فيه  صفحة مناقشه موجوده تحت العنوان الجديد للمقاله.\n* لو انت شلت اختيار نقل صفحة المناقشه .\n\nوفى الحالات  دى، لو عايز  تنقل صفحة المناقشه  لازم تنقل أو تدمج محتوياتها  يدويا.",
+       "movepagetalktext": "لو علمت على الاختيار ده، فصفحه المناقشه حتتنقل اوتوماتيك للعنوان الجديد، الا لو كان فيه صفحة مناقشه مش فاضيه هناك.\n\nوفى الحاله  دى، لو عايز  تنقل صفحة المناقشه لازم تنقل أو تدمج محتوياتها  يدويا.",
        "moveuserpage-warning": "<strong>خد بالك:</strong> انت ح تعمل نقل لصفحه بتاعة يوزر. لو سمحت تعمل حسابك ان الصفحه هى بس اللى ح تتنقل و اسم اليوزر <em>مش</em> ح يتغير.",
        "movenologintext": "لازم تكون يوزر متسجل و تعمل [[Special:UserLogin|دخول]] علشان تنقل الصفحة.",
        "movenotallowed": "ماعندكش الصلاحية لنقل الصفحات.",
        "confirmemail_sendfailed": "{{SITENAME}} ماقدرش يبعت ايميل التأكيد.\nلو سمحت تتأكد من الايميل بتاعك.\n\nالغلط اللى حصل: $1",
        "confirmemail_invalid": "كود تفعيل غلط.\nيمكن صلاحيته تكون انتهت.",
        "confirmemail_needlogin": "لازم $1 علشان تأكد الايميل بتاعك.",
-       "confirmemail_success": "الايميل بتاعك اتأكد خلاص.\nممكن دلوقتى تسجل دخولك و تستمتع بالويكي.",
+       "confirmemail_success": "الايميل بتاعك اتأكد خلاص.\nممكن دلوقتى [[Special:UserLogin|تسجل دخولك]] و تستمتع بالويكى.",
        "confirmemail_loggedin": "الايميل بتاعك اتأكد خلاص.",
        "confirmemail_subject": "تأكيد الايميل من {{SITENAME}}",
        "confirmemail_body": "فى واحد، ممكن يكون إنتا، من عنوان الأيبى $1،\nفتح حساب \"$2\" بعنوان الايميل دا فى {{SITENAME}}.\n\nعلشان نتأكد أن  الحساب دا بتاعك فعلا و علشان كمان تفعيل خواص الايميل فى {{SITENAME}}، افتح اللينك دى فى البراوزر بتاعك :\n\n$3\n\nإذا *ماكنتش* إنتا اللى فتحت الحساب ، دوس على اللينك دى علشان تلغى تأكيد الايميل\n:\n\n$5\n\nكود التفعيل دا ح ينتهى $4.",
index ed29781..beaab1d 100644 (file)
        "token_suffix_mismatch": "'''La to edición nun s'aceutó porque'l to navegador mutiló los caráuteres de puntuación nel editor.'''\nLa edición nun foi aceutada pa prevenir corrupciones na páxina de testu.\nDacuando esto pasa por usar un serviciu proxy anónimu basáu en web que tenga fallos.",
        "edit_form_incomplete": "'''Delles partes del formulariu d'edición nun llegaron al sirvidor; comprueba que les ediciones tean intactes y vuelvi a tentalo.'''",
        "editing": "Edición de «$1»",
-       "creating": "Creando $1",
+       "creating": "Creación de «$1»",
        "editingsection": "Editando $1 (seición)",
        "editingcomment": "Editando $1 (seición nueva)",
        "editconflict": "Conflictu d'edición: $1",
        "passwordpolicies-policyflag-suggestchangeonlogin": "suxerir cambiu al aniciar sesión",
        "easydeflate-invaliddeflate": "El conteníu dau nun ta comprimíu correutamente",
        "unprotected-js": "Por razones de seguridá, JavaScript nun puede cargase dende páxines ensin protexer. Crea javascript sólo nel espaciu de nomes MediaWiki: o como subpáxina d'usuariu",
-       "userlogout-continue": "Si desees zarrar la sesión [$1 sigui na páxina de finar sesión]."
+       "userlogout-continue": "¿Desees zarrar la sesión?"
 }
index fec1795..4d850a1 100644 (file)
        "autoblockedtext": "Ваш IP-адрас быў аўтаматычна заблякаваны, таму што ён ужываўся іншым удзельнікам, які быў заблякаваны $1.\nПрычына гэтага:\n\n:<em>$2</em>\n\n* Блякаваньне пачалося: $8\n* Блякаваньне скончыцца: $6\n* Быў заблякаваны: $7\n\nВы можаце скантактавацца з $1 ці з адным зь іншых [[{{MediaWiki:Grouppage-sysop}}|адміністратараў]], каб абмеркаваць блякаваньне.\n\nЗаўважце, што вы ня зможаце ўжываць магчымасьць «{{int:emailuser}}», пакуль ня будзе пазначаны дзейны адрас электроннай пошты ў вашых [[Special:Preferences|наладах удзельніка]], і калі гэта вам не было забаронена.\n\nВаш цяперашні IP-адрас — $3, ідэнтыфікатар блякаваньня — #$5.\nКалі ласка, улучайце ўсю вышэйпададзеную інфармацыю ва ўсе запыты, што вы будзеце рабіць.",
        "systemblockedtext": "Вашае імя ўдзельніка ці IP-адрас былі аўтаматычна заблякаваныя MediaWiki.\nЗ наступнай прычыны:\n\n:<em>$2</em>\n\n* Пачатак блякаваньня: $8\n* Сканчэньне блякаваньня: $6\n* Мэта блякаваньня: $7\n\nВаш цяперашні IP-адрас — $3.\nКалі ласка, уключайце ўсе пададзеныя вышэй дэталі ва ўсе запыты, што вы робіце.",
        "blockednoreason": "прычына не пазначана",
+       "blockedtext-composite": "<strong>Вашае імя ўдзельніка ці IP-адрас былі заблякаваныя.</strong>\n\nПададзеная прычына:\n\n:<em>$2</em>.\n\n* Пачатак блякаваньня: $8\n* Сканчэньне найдаўжэйшага з блякаваньняў: $6\n\nВаш цяперашні IP-адрас — $3.\nКалі ласка, дадайце ўсе падрабязнасьці, прыведзеныя вышэй, у запыты, што вы будзеце рабіць.",
+       "blockedtext-composite-reason": "Маецца некалькі блякаваньняў вашага рахунку і/ці IP-адрасу",
        "whitelistedittext": "Вам трэба $1, каб рэдагаваць старонкі.",
        "confirmedittext": "Вы мусіце пацьвердзіць Ваш адрас электроннай пошты перад рэдагаваньнем старонак. Калі ласка, пазначце і пацьвердзіце адрас электроннай пошты праз Вашы [[Special:Preferences|налады]].",
        "nosuchsectiontitle": "Немагчыма знайсьці сэкцыю",
        "watchlist-options": "Налады сьпісу назіраньня",
        "watching": "Дадаецца ў сьпіс назіраньня…",
        "unwatching": "Выдаляецца са сьпісу назіраньня…",
-       "watcherrortext": "УзÑ\8cнÑ\96кла Ð¿Ð°Ð¼Ñ\8bлка Ð¿Ð°Ð´Ñ\87аÑ\81 Ð·Ñ\8cменÑ\8b Ð\92ашага сьпісу назіраньня для «$1».",
+       "watcherrortext": "УзÑ\8cнÑ\96кла Ð¿Ð°Ð¼Ñ\8bлка Ð¿Ð°Ð´Ñ\87аÑ\81 Ð·Ñ\8cменÑ\8b Ð½Ð°Ð»Ð°Ð´Ð°Ñ\9e Ð²ашага сьпісу назіраньня для «$1».",
        "enotif_reset": "Пазначыць усе старонкі як прагледжаныя",
        "enotif_impersonal_salutation": "Удзельнік {{GRAMMAR:родны|{{SITENAME}}}}",
-       "enotif_subject_deleted": "СÑ\82аÑ\80онка {{GRAMMAR:Ñ\80однÑ\8b|{{SITENAME}}}} Â«$1» Ð±Ñ\8bла Ð²Ñ\8bдаленаÑ\8f {{GENDER:$2|Ñ\83дзелÑ\8cнÑ\96кам|Ñ\83дзельніцай}} $2",
+       "enotif_subject_deleted": "СÑ\82аÑ\80онка {{GRAMMAR:Ñ\80однÑ\8b|{{SITENAME}}}} Â«$1» Ð±Ñ\8bла Ð²Ñ\8bдаленаÑ\8f {{GENDER:$2|Ñ\9eдзелÑ\8cнÑ\96кам|Ñ\9eдзельніцай}} $2",
        "enotif_subject_created": "Старонка {{GRAMMAR:родны|{{SITENAME}}}} «$1» была створаная {{GENDER:$2|удзельнікам|удзельніцай}} $2",
        "enotif_subject_moved": "Старонка {{GRAMMAR:родны|{{SITENAME}}}} «$1» была перанесеная {{GENDER:$2|удзельнікам|удзельніцай}} $2",
        "enotif_subject_restored": "Старонка {{GRAMMAR:родны|{{SITENAME}}}} «$1» была адноўленая {{GENDER:$2|удзельнікам|удзельніцай}} $2",
index fe1c4d6..314ad7b 100644 (file)
        "undeleterevision-missing": "La revisió no és vàlida o no hi és. Podeu tenir-hi un enllaç incorrecte, o bé pot haver-se restaurat o eliminat de l'arxiu.",
        "undeleterevision-duplicate-revid": "No s'ha pogut restaurar {{PLURAL:$1|una revisió|$1 revisions}}, perquè {{PLURAL:$1|el seu|els seus}} <code>rev_id</code> ja s'estaven fent servir.",
        "undelete-nodiff": "No s'ha trobat cap revisió anterior.",
-       "undeletebtn": "Restaura!",
+       "undeletebtn": "Restaura",
        "undeletelink": "mira/restaura",
        "undeleteviewlink": "veure",
        "undeleteinvert": "Invertir selecció",
        "passwordpolicies-policyflag-forcechange": "cal canviar a l'inici de sessió",
        "passwordpolicies-policyflag-suggestchangeonlogin": "suggereix canvi a l'inici de sessió",
        "easydeflate-invaliddeflate": "El contingut proporcionat no està deflactat adequadament",
-       "unprotected-js": "Per motius de seguretat, el JavaScript no es pot carregar de les pàgines desprotegides. Creeu javascript en l'espai de noms MediaWiki o en una subpàgina d'usuari"
+       "unprotected-js": "Per motius de seguretat, el JavaScript no es pot carregar de les pàgines desprotegides. Creeu javascript en l'espai de noms MediaWiki o en una subpàgina d'usuari",
+       "userlogout-continue": "Voleu finalitzar la sessió?"
 }
index 359d6ed..9da77d9 100644 (file)
@@ -61,6 +61,7 @@
        "tog-norollbackdiff": "Cék-hèng huòi-gūng ī-hâiu ng-sāi hiēng-sê chă-biék",
        "tog-useeditwarning": "我編輯頁面其時候離開,起動警告我蜀下",
        "tog-prefershttps": "Láuk-diē ī-hâiu tié-lāu sāi ăng-ciòng lièng-giék",
+       "tog-showrollbackconfirmation": "Dók huòi-tó̤i liêng-ciék gì sì-hâiu hiēng-sê káuk-nêng tì-sê",
        "underline-always": "直頭",
        "underline-never": "頭𡅏無",
        "underline-default": "皮膚或者瀏覽器默認其",
        "returnto": "轉去$1。",
        "tagline": "Chók-cê̤ṳ {{SITENAME}}",
        "help": "Bŏng-cô",
+       "help-mediawiki": "MediaWiki gì siók-mìng",
        "search": "Sìng-tō̤",
        "searchbutton": "Sìng-tō̤",
        "go": "去",
index 48797ce..dd1630d 100644 (file)
@@ -10,7 +10,8 @@
                        "Умар",
                        "Macofe",
                        "Danvintius Bookix",
-                       "Stephanecbisson"
+                       "Stephanecbisson",
+                       "Fitoschido"
                ]
        },
        "tog-underline": "Багълантыларнынъ тюбюни сызув:",
        "undelete": "Ёкъ этильген саифелерни косьтер",
        "undeletepage": "Саифенинъ ёкъ этильген версияларына козь ат ве кери кетир.",
        "viewdeletedpage": "Ёкъ этильген саифелерге бакъ",
-       "undeletebtn": "Кери кетир!",
+       "undeletebtn": "Кери кетир",
        "undeletelink": "косьтер/кери кетир",
        "undeletecomment": "Себеп:",
        "undelete-header": "Кеченлерде ёкъ этильген саифелерни корьмек ичюн [[Special:Log/delete|ёкъ этюв журналына]] бакъынъыз.",
index 18a354f..0ba17ed 100644 (file)
@@ -6,7 +6,8 @@
                        "Urhixidur",
                        "아라",
                        "Macofe",
-                       "Stephanecbisson"
+                       "Stephanecbisson",
+                       "Fitoschido"
                ]
        },
        "tog-underline": "Bağlantılarnıñ tübüni sızuv:",
        "undelete": "Yoq etilgen saifelerni köster",
        "undeletepage": "Saifeniñ yoq etilgen versiyalarına köz at ve keri ketir.",
        "viewdeletedpage": "Yoq etilgen saifelerge baq",
-       "undeletebtn": "Keri ketir!",
+       "undeletebtn": "Keri ketir",
        "undeletelink": "köster/keri ketir",
        "undeletecomment": "Sebep:",
        "undelete-header": "Keçenlerde yoq etilgen saifelerni körmek içün [[Special:Log/delete|yoq etüv jurnalına]] baqıñız.",
index 71904d1..9e18328 100644 (file)
        "autoblockedtext": "Vaše IP adresa byla automaticky zablokována, protože ji používal jiný uživatel, kterého zablokoval $1.\nUdaný důvod blokování:\n\n:<em>$2</em>\n\n* Začátek blokování: $8\n* Konec blokování: $6\n* Původně blokovaný uživatel: $7\n\nZablokování můžete prodiskutovat se správcem $1 nebo některým z dalších [[{{MediaWiki:Grouppage-sysop}}|správců]].\n\nUvědomte si však, že funkci „{{int:emailuser}}“ nemůžete použít, pokud nemáte ve svém [[Special:Preferences|uživatelském nastavení]] zadaný platný e-mail a nebylo vám zablokováno jeho užívání.\n\nVaše současná IP adresa je $3, číslo vašeho zablokování je #$5.\nProsíme, uveďte tyto údaje při komunikaci se správci.",
        "systemblockedtext": "Vaše IP adresa byla automaticky zablokována softwarem MediaWiki.\nUdaný důvod blokování:\n\n:<em>$2</em>\n\n* Začátek blokování: $8\n* Konec blokování: $6\n* Původně blokovaný uživatel: $7\n\nVaše současná IP adresa je $3.\nProsíme, uveďte tyto údaje při komunikaci se správci.",
        "blockednoreason": "důvod nebyl zadán",
+       "blockedtext-composite": "<strong>Vaše uživatelské jméno nebo IP adresa byla zablokována.</strong>\n\nUdaný důvod blokování:\n\n:<em>$2</em>\n\n* Začátek blokování: $8\n* Konec nejdelšího blokování: $6\n\nVaše současná IP adresa je $3.\nProsíme, uveďte tyto údaje při komunikaci se správci.",
        "whitelistedittext": "Pro editaci se musíte $1.",
        "confirmedittext": "Pro editaci stránek je vyžadováno potvrzení vaší e-mailové adresy.\nNa stránce [[Special:Preferences|nastavení]] zadejte a nechte potvrdit svou e-mailovou adresu.",
        "nosuchsectiontitle": "Sekce nenalezena",
        "uploadstash-zero-length": "Soubor má nulovou délku.",
        "invalid-chunk-offset": "Neplatný posun bloku",
        "img-auth-accessdenied": "Přístup odepřen",
-       "img-auth-nopathinfo": "Chybí informace o cestě.\nVáš server musí být nastaven tak, aby předával proměnné REQUEST_URI nebo PATH_INFO.\nPokud je, zkuste zapnout $wgUsePathInfo.\nViz https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Image_Authorization.",
+       "img-auth-nopathinfo": "Chybí informace o cestě.\nVáš server musí být nastaven tak, aby předával proměnné REQUEST_URI nebo PATH_INFO.\nPokud je, zkuste zapnout $wgUsePathInfo.\nVizte https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Image_Authorization.",
        "img-auth-notindir": "Požadovaná cesta nespadá pod nakonfigurovaný adresář s načtenými soubory.",
        "img-auth-badtitle": "Z „$1“ nelze vytvořit platný název stránky.",
        "img-auth-nofile": "Soubor „$1“ neexistuje.",
        "unusedimages": "Nepoužívané soubory",
        "wantedcategories": "Chybějící kategorie",
        "wantedpages": "Chybějící stránky",
-       "wantedpages-summary": "Seznam neexistujících stránek, na které vede nejvíce odkazů, kromě stránek, na které odkazují jen přesměrování. Pro seznam neexistujících stránek, na které odkazují přesměrování, viz [[{{#special:BrokenRedirects}}|seznam přerušených přesměrování]].",
+       "wantedpages-summary": "Seznam neexistujících stránek, na které vede nejvíce odkazů, kromě stránek, na které odkazují jen přesměrování. Pro seznam neexistujících stránek, na které odkazují přesměrování, vizte [[{{#special:BrokenRedirects}}|seznam přerušených přesměrování]].",
        "wantedpages-badtitle": "Výsledky obsahují neplatný název: $1",
        "wantedfiles": "Chybějící soubory",
        "wantedfiletext-cat": "Následující soubory se používají, ale neexistují. Soubory ze vzdálených úložišť zde mohou být uvedeny, přestože existují. Taková falešná pozitiva budou zobrazena <del>přeškrtnutě</del>. Stránky, které vkládají neexistující soubory, jsou navíc uvedeny v [[:$1]].",
        "index-category-desc": "Stránka obsahuje kouzelné slovo <code><nowiki>__INDEX__</nowiki></code> (a je ve jmenném prostoru, ve kterém je tento příznak dovolen), takže je indexována roboty, přestože by normálně nebyla.",
        "post-expand-template-inclusion-category-desc": "Stránka je po rozbalení všech šablon větší než <code>$wgMaxArticleSize</code>, takže některé šablony rozbaleny nebyly.",
        "post-expand-template-argument-category-desc": "Stránka je po rozbalení argumentu šablony (něco v trojitých závorkách, např. <code>{{{Foo}}}</code>) větší než <code>$wgMaxArticleSize</code>.",
-       "expensive-parserfunction-category-desc": "Stránka používá příliš mnoho náročných funkcí syntaktického analyzátoru (jako <code>#ifexist</code>). Viz [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:$wgExpensiveParserFunctionLimit Manual:$wgExpensiveParserFunctionLimit].",
+       "expensive-parserfunction-category-desc": "Stránka používá příliš mnoho náročných funkcí syntaktického analyzátoru (jako <code>#ifexist</code>). Vizte [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:$wgExpensiveParserFunctionLimit Manual:$wgExpensiveParserFunctionLimit].",
        "broken-file-category-desc": "Stránka obsahuje nefunkční odkaz na soubor (odkaz pro vložení souboru, který neexistuje).",
        "hidden-category-category-desc": "Kategorie ve svém textu obsahuje <code><nowiki>__HIDDENCAT__</nowiki></code>, což způsobuje, že se na stránkách implicitně nezobrazuje v rámečku odkazů na kategorie.",
        "trackingcategories-nodesc": "Popis není k dispozici.",
        "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í:",
        "blocklogentry": "blokuje „[[$1]]“ s časem vypršení $2 $3",
        "reblock-logentry": "mění nastavení bloku „[[$1]]“ s časem vypršení $2 $3",
-       "blocklogtext": "Toto je kniha úkonů blokování a odblokování uživatelů.\nAutomaticky blokované IP adresy nejsou vypsány.\nViz též [[Special:BlockList|seznam všech probíhajících bloků]].",
+       "blocklogtext": "Toto je kniha úkonů blokování a odblokování uživatelů.\nAutomaticky blokované IP adresy nejsou vypsány.\nVizte též [[Special:BlockList|seznam všech probíhajících bloků]].",
        "unblocklogentry": "odblokovává „$1“",
        "block-log-flags-anononly": "pouze anonymní uživatelé",
        "block-log-flags-nocreate": "vytváření účtů zablokováno",
index 51f10b8..70eb328 100644 (file)
@@ -12,7 +12,8 @@
                        "Chuvash2014",
                        "Macofe",
                        "Chuvash",
-                       "Marat-avgust"
+                       "Marat-avgust",
+                       "Fitoschido"
                ]
        },
        "tog-underline": "Ссылкăсене аялтан туртса палармалла:",
        "undelete": "Кăларса пăрахнă страницăсене пăх",
        "viewdeletedpage": "Кăларса пăрахнă страницăсене пăх",
        "undeleterevisions": "$1 {{PLURAL:$1|верси|версисене}} пăса утнă",
-       "undeletebtn": "Каялла тавăр!",
+       "undeletebtn": "Каялла тавăр",
        "undeleteviewlink": "пăх",
        "undelete-search-box": "Кăларса пăрахнă страницăсен хушшинчи шырав",
        "undelete-search-submit": "Шыра",
index 838980e..9c1c150 100644 (file)
        "nstab-category": "Kategoriye",
        "mainpage-nstab": "Pela seri",
        "nosuchaction": "Fealiyeto wınasi çıniyo",
-       "nosuchactiontext": "URL ra kar qebul nêbı.\nŞıma belka URL şaş nuşt, ya zi gıreyi şaş ra ameyi.\nKeyepelê {{SITENAME}} eşkeno xeta eşkera bıkero.",
+       "nosuchactiontext": "URL ra kar qebul nêbı.\nŞıma belka URL şaş nuşt, ya zi gıreyi şaş ra ameyi.\nKeyepelê {{SITENAME}} eşkeno xeta aşkera bıkero.",
        "nosuchspecialpage": "Pela hısusiya wınasiyên çıniya.",
        "nospecialpagetext": "<strong>To yew pela xasa nêvêrdiye waşte.</strong>\n\nSeba lista pelanê xasanê vêrdeyan reca kena: [[Special:SpecialPages|{{int:specialpages}}]].",
        "error": "Xeta",
        "publishchanges": "Vurnayışan qeyd ke",
        "savearticle-start": "Pele qeyd ke...",
        "savechanges-start": "Vurnayışan qeyd ke...",
-       "publishpage-start": "Pele weşane...",
-       "publishchanges-start": "Vurnayışan weşane...",
+       "publishpage-start": "Riperri aşkera ke...",
+       "publishchanges-start": "Vırnayışan aşkera ke...",
        "preview": "Verqayt",
        "showpreview": "Verasayışi bımocne",
        "showdiff": "Vurnayışan bımocne",
        "emptyfile": "dosya ya ke şıma bar kerda veng asena, nameyê dosyayi şaş nusyaya belka.",
        "windows-nonascii-filename": "Na wiki namen de dosyayan de xısusi karaxtera karkerdışa peşti nêdana.",
        "fileexists": "Nê namey ra yew dosya xora esta. Kerem kerên, <strong>[[:$1]]</strong> kontrol kerê {{GENDER:|şıma}} ke emin niyê naye bıvurnê.   \n[[$1|thumb]]",
-       "filepageexists": "qey na dosya pelê eşkera kerdışi <strong>[[:$1]]</strong> na adresi de ca ra vıraziyayo labele no name de yew dosya nêasena.\nkılmnuşteyê şıma nêasena eke şıma qayili bıvini gani şıma pê dest bıvurni\n[[$1|resimo qıc]]",
+       "filepageexists": "Seba na dosyay riperrê aşkera kerdışi <strong>[[:$1]]</strong> nê adresi de ca ra vıraziyao, labelê no name de jû dosya nêasena.\nKılmnuştey şıma nêaseno. Eke şıma qailê bıvênê, gani şıma pê dest bıvırnê\n[[$1|resimo qıc]]",
        "fileexists-extension": "zey no nameyê dosyayi yewna nameyê dosyayi esta: [[$2|thumb]]\n* dosyaya ke bar biya: <strong>[[:$1]]</strong>\n* dosyaya ke ca ra esta: <strong>[[:$2]]</strong>\nkerem kere yewna name bıvıcinê",
        "fileexists-thumbnail-yes": "na dosya wina asena ke versiyona yew resmê qıc biyayeya ''(thumbnail)''. [[$1|thumb]]\nkerem kerê <strong>[[:$1]]</strong> na dosya konrol bıkerê .",
        "file-thumbnail-no": "nameyê na dosyayi pê ney <strong>$1</strong> dest keno pê.\nna manena ke versiyona yew resmê qıc biyaye ya ''(thumbnail)''",
        "upload-options": "Tercihanê bar kerdişî",
        "watchthisupload": "Ena dosya seyr bike",
        "filewasdeleted": "no name de yew dosya yew wexto nızdi de bar biya u dıma zi serkaran hewn a kerdo. wexya ke şıma dosya bar keni bıewnê no pel $1.",
-       "filename-bad-prefix": "name yo ke şıma bar keni zey nameyê kamerayê dijital î, pê ney '''\"$1\"''' destpêkeno .\nkerem kere yewna nameyo eşkera bıvicinê.",
+       "filename-bad-prefix": "nameo ke şıma bar kenê, zey namey kameraya dicitalo, pê '''\"$1\"''' sıfte keno.\nKerem kerên, nameyê do eşkera'o bin weçinên.",
        "filename-prefix-blacklist": " #<!-- leave this line exactly as it is --> <pre>\n# Syntax is as follows:\n#   * Everything from a \"#\" character to the end of the line is a comment\n#   * Every non-blank line is a prefix for typical file names assigned automatically by digital cameras\nCIMG # Casio\nDSC_ # Nikon\nDSCF # Fuji\nDSCN # Nikon\nDUW # some mobile phones\nIMG # generic\nJD # Jenoptik\nMGP # Pentax\nPICT # misc.\n #</pre> <!-- leave this line exactly as it is -->",
        "upload-proto-error": "Porotokol raşt ni yo.",
        "upload-proto-error-text": "Bar kerdişê durî gani  URLî estbiye ke pe <code>http://</code> ya zi <code>ftp://</code> başli beno.",
        "allpagesfrom": "Herfa kı pa liste bo:",
        "allpagesto": "Perranê ke ena herfe qediyenê bımotne:",
        "allarticles": "Peli pêro",
-       "allinnamespace": "Peli pênro ( $1 cayênameyî)",
+       "allinnamespace": "Peli pêro (Caynamey: $1)",
        "allpagessubmit": "Şo",
        "allpagesprefix": "herfê ke şıma tiya de nuşti, pê ney herfan pelê ke destpêkenê liste ker:",
        "allpagesbadtitle": "pel o ke şıma kewenî cı, nameyê no peli de gıreyê zıwanan u wikiyi re elaqa esto, ê ra cıkewtış qebul niyo. ya zi sernameyan de karakterê qedexeyi tede esto.",
        "sp-contributions-logs": "qeydi",
        "sp-contributions-talk": "werênayış",
        "sp-contributions-userrights": "idareyê heqanê {{GENDER:$1|karberan}}",
-       "sp-contributions-blocked-notice": "verniyê no/na karber/e geriyayo/a\nqê referansi qeydê vernigrewtışi cêr de eşkera biyo:",
+       "sp-contributions-blocked-notice": "Eno karber/ena karbere emanet blokekerdeyo/blokekerdiya.\nCıkewtışo tewr peyêno ke bloke biyo, cêr seba referansi belikerdeyo:",
        "sp-contributions-blocked-notice-anon": "Eno adresê IPi bloke biyo.\nCıkewtışo tewr peyêno ke bloke biyo, cêr seba referansi belikerdeyo:",
        "sp-contributions-search": "Dekerdena cı geyrê",
        "sp-contributions-username": "Adresa IPy ya zi nameyê karberi:",
index 851a6b2..4c32ec6 100644 (file)
        "autoblockedtext": "Your IP address has been automatically blocked because it was used by another user, who was blocked by $1.\nThe reason given is:\n\n:<em>$2</em>\n\n* Start of block: $8\n* Expiration of block: $6\n* Intended blockee: $7\n\nYou may contact $1 or one of the other [[{{MediaWiki:Grouppage-sysop}}|administrators]] to discuss the block.\n\nNote that you may not use the \"{{int:emailuser}}\" feature unless you have a valid email address registered in your [[Special:Preferences|user preferences]] and you have not been blocked from using it.\n\nYour current IP address is $3, and the block ID is #$5.\nPlease include all above details in any queries you make.",
        "systemblockedtext": "Your username or IP address has been automatically blocked by MediaWiki.\nThe reason given is:\n\n:<em>$2</em>\n\n* Start of block: $8\n* Expiration of block: $6\n* Intended blockee: $7\n\nYour current IP address is $3.\nPlease include all above details in any queries you make.",
        "blockednoreason": "no reason given",
+       "blockedtext-composite": "<strong>Your username or IP address has been blocked.</strong>\n\nThe reason given is:\n\n:<em>$2</em>.\n\n* Start of block: $8\n* Expiration of longest block: $6\n\nYour current IP address is $3.\nPlease include all above details in any queries you make.",
+       "blockedtext-composite-reason": "There are multiple blocks against your account and/or IP address",
        "whitelistedittext": "Please $1 to edit pages.",
        "confirmedittext": "You must confirm your email address before editing pages.\nPlease set and validate your email address through your [[Special:Preferences|user preferences]].",
        "nosuchsectiontitle": "Cannot find section",
index fdf7e8c..d94550f 100644 (file)
        "autoblockedtext": "Via IP-adreso estas aŭtomate forbarita, ĉar uzis ĝin alia uzanto, kiun baris $1.\nLa donita kialo estas jena:\n\n:<em>$2</em>\n\n*Komenco de forbaro: $8\n*Limdato de la blokado: $6\n*Intencis forbari uzanton: $7\n\nVi povas kontakti $1 aŭ iun ajn el la aliaj [[{{MediaWiki:Grouppage-sysop}}|administrantojn]] por diskuti la blokon.\n\nNotu, ke vi ne povas uzi la servon \"{{int:emailuser}}\" krom se vi havas validan retpoŝt-adreson registritan en viaj [[Special:Preferences|preferojn]], kaj vi estas ne blokita kontraŭ ĝia uzado.\n\nVia nuna IP-adreso estas $3, kaj la forbaro-identigo estas $5.\nBonvolu inkluzivi tiujn detalojn en iuj ajn demandoj kiun vi farus.",
        "systemblockedtext": "Via salutnomo aŭ IPa adreso estis aŭtomate forbarita de MediaWiki.\nLa kialo donita estas:\n\n:<em>$2</em>\n\n* Komenco de forbaro: $8\n* Eksvalidiĝo de forbaro: $6\n* Intenca forbarulo: $7\n\nVia nuna IP-adreso estas $3.\nBonvolu inkluzivi ĉiujn suprajn detalojn en ajnaj demandoj kiujn vi faras.",
        "blockednoreason": "neniu kialo estis donita",
+       "blockedtext-composite": "<strong>Oni forbaris vian salutnomon aŭ IP-adreson.</strong>\n\nLa donita kialo estas:\n\n:<em>$2</em>.\n\n* Komenco de forbaro: $8\n* Fino de plej longa forbaro: $6\n\nVia aktuala IP-adreso estas $3.\nPlease include all above details in any queries you make.",
+       "blockedtext-composite-reason": "Estas pluraj forbaroj kontraŭ via konto kaj/aŭ IP-adreso",
        "whitelistedittext": "Vi devas $1 por redakti paĝojn.",
        "confirmedittext": "Vi devas konfirmi vian retpoŝtan adreson antaŭ ol redakti paĝojn. Bonvolu agordi kaj validigi vian retadreson per viaj [[Special:Preferences|preferoj]].",
        "nosuchsectiontitle": "Ne povas trovi sekcion",
        "move-page": "Alinomi $1",
        "move-page-legend": "Alinomi paĝon",
        "movepagetext": "Per la jena formulo vi povas ŝanĝi la nomon de iu paĝo, kunportante ĝian historion de redaktoj al la nova nomo.\nLa antaŭa titolo fariĝos alidirektilo al la nova titolo.\nVi povas ĝisdatigi alidirektilojn kiu indikas la originalan titolon aŭtomate.\nSe vi elektas ĝisdatigi permane, bonvolu kontroli [[Special:DoubleRedirects|duoblajn]] aŭ [[Special:BrokenRedirects|rompitajn alidirektilojn]].\nVi estas responsa por certigi ke ligilojn direktas fidinde.\n\nNotu, ke la paĝo '''ne''' estos movita se jam ekzistas paĝo ĉe la nova titolo, krom se tiu loko estas malplena aŭ alidirektilo al ĉi tiu paĝo, kaj sen antaŭa redaktohistorio.\nPro tio, vi ja povos removi la paĝon je la antaŭa titolo se vi mistajpus, kaj ne povas forviŝi ekzistantan paĝon per movo.\n\n'''Note:'''\nTio povas esti drasta kaj neatendita ŝanĝo por populara paĝo;\nbonvolu certigi vin, ke vi komprenas ties konsekvencojn antaŭ ol vi antaŭeniru.",
-       "movepagetext-noredirectfixer": "Per jena formularo vi povas alinomigi paĝon, kaj movi tutan ĝian redaktohistorion al la nova nomo. \nLa antaŭa titolo alidirektigos onin al la nova titolo.\nKontrolu pri [[Special:DoubleRedirects|duoblajn]] aŭ [[Special:BrokenRedirects|nefunkciantajn alidirektilojn]].\nVi respondecas pri tio ke ligoj restas montrantaj ĝustadirekten.\n\nKonsciu ke la paĝo '''ne'' estas movota se jam ekzistas paĝo havanta la novan titolon, krom se ĝi estas alidirektilo sen antaŭa redaktohistorio.\nTio ĉi signifas ke vi povas alinomigi paĝon reen al antaŭa nomo se vi eraras, kaj vi ke vi ne povas anstataŭigi ekzistantan paĝon.\n\n'''Rimarko:''\nEblas ke tio ĉi estas drasta kaj neatendita ŝanĝo de populara paĝo;\nAntaŭ daŭrigi, bonvolu certiĝi, ke vi komprenas la konsekvencojn de tiuj ĉi ŝanĝo.",
+       "movepagetext-noredirectfixer": "Per jena formularo vi povas alinomigi paĝon, kaj movi tutan ĝian redaktohistorion al la nova nomo. \nLa antaŭa titolo alidirektigos onin al la nova titolo.\nKontrolu pri [[Special:DoubleRedirects|duoblajn]] aŭ [[Special:BrokenRedirects|nefunkciantajn alidirektilojn]].\nVi respondecas pri tio ke ligoj restas montrantaj ĝustadirekten.\n\nKonsciu ke la paĝo <strong>ne</strong> estas movota se jam ekzistas paĝo havanta la novan titolon, krom se ĝi estas alidirektilo sen antaŭa redaktohistorio.\nTio ĉi signifas ke vi povas alinomigi paĝon reen al antaŭa nomo se vi eraras, kaj vi ke vi ne povas anstataŭigi ekzistantan paĝon.\n\n<strong>Rimarko:</strong>\nEblas ke tio ĉi estas drasta kaj neatendita ŝanĝo de populara paĝo;\nAntaŭ daŭrigi, bonvolu certiĝi, ke vi komprenas la konsekvencojn de tiuj ĉi ŝanĝo.",
        "movepagetalktext": "Se vi validas tiun elektobutono, la asociata diskutpaĝo estos aŭtomate alinomita al nova titolo, krom se malplena diskutpaĝo jam ekzistas.\n\nTiujokaze, vi alinomigendos aŭ kunfandendos malaŭtomate la paĝon se vi tion deziras.",
        "moveuserpage-warning": "<strong>Averto:</strong> Vi preskaŭ alinomas paĝon de uzanto. Bonvolu noti ke nur la paĝo estos alinomita kaj la uzanto mem <em>ne</em> estos alinomita.",
        "movecategorypage-warning": "<strong>Averto:</strong> Vi baldaŭ movos kategorian paĝon. Bonvolu noti ke, nur la paĝo estos movita, kaj la paĝoj en la malnova kategorio <em>ne</em> transiros en la novan kategorion.",
        "passwordpolicies-policyflag-suggestchangeonlogin": "sugesti ŝanĝadon dum ensaluto",
        "easydeflate-invaliddeflate": "Provizita enhavo ne estas ĝuste densigita",
        "unprotected-js": "Pro sekurecaj kialoj, JavaScript ne povas esti ŝargata el neprotektataj paĝoj. Bonvolu nur krei JavaScript en la nomspaco MediaWiki: aŭ kiel subpaĝo de Uzanto.",
-       "userlogout-continue": "Se vi vola elsaluti, bonvolu  [$1 iri al la elsaluta paĝo]."
+       "userlogout-continue": "Ĉu vi volas elsaluti?"
 }
index 018131b..34a3ec0 100644 (file)
        "mw-widgets-abandonedit-title": "¿Seguro?",
        "mw-widgets-copytextlayout-copy": "Copiar",
        "mw-widgets-copytextlayout-copy-fail": "No se pudo copiar en el portapapeles.",
-       "mw-widgets-copytextlayout-copy-success": "Copiado en el portapapeles",
+       "mw-widgets-copytextlayout-copy-success": "Copiado en el portapapeles.",
        "mw-widgets-dateinput-no-date": "Ninguna fecha seleccionada",
        "mw-widgets-dateinput-placeholder-day": "AAAA-MM-DD",
        "mw-widgets-dateinput-placeholder-month": "AAAA-MM",
        "passwordpolicies-policyflag-suggestchangeonlogin": "sugerir cambio al acceder a la cuenta",
        "easydeflate-invaliddeflate": "El contenido proporcionado no esta comprimido correctamente",
        "unprotected-js": "Por razones de seguridad, JavaScript no se puede cargar desde páginas desprotegidas. Crea javascript solo en MediaWiki: espacio de nombres o como subpágina de usuario",
-       "userlogout-continue": "Si deseas cerrar sesión, [$1 continúa a la página de cierre de sesión]."
+       "userlogout-continue": "¿Quieres finalizar la sesión?"
 }
index 6eac350..c9afaa9 100644 (file)
        "exif-copyrighted": "Copyright status. This is a true or false field showing either Copyrighted or Public Domain. It should be noted that Copyrighted includes freely-licensed works.",
        "exif-copyrightowner": "{{exif-qqq}}\n\nCopyright owner. Can have more than one person or entity.",
        "exif-usageterms": "Terms under which you're allowed to use the image/media.",
-       "exif-webstatement": "{{exif-qqq}}\n\nURL detailing the copyright status of the image, and how you're allowed to use the image. Often this is a link to a creative commons license, however the creative commons people recommend using a page that generally contains specific information about the image, and recommend using {{msg-mw|exif-licenseurl}} for linking to the license. See http://wiki.creativecommons.org/XMP",
+       "exif-webstatement": "{{exif-qqq}}\n\nURL detailing the copyright status of the image, and how you're allowed to use the image. Often this is a link to a creative commons license, however the creative commons people recommend using a page that generally contains specific information about the image, and recommend using {{msg-mw|exif-licenseurl}} for linking to the license. See https://wiki.creativecommons.org/wiki/XMP",
        "exif-originaldocumentid": "A unique ID of the original document (image) that this document (image) is based on.",
        "exif-licenseurl": "{{exif-qqq}}\n\nURL for copyright license. This is almost always a creative commons license since this information comes from the creative commons namespace of XMP (but could be a link to any type of license). See also {{msg-mw|exif-webstatement}}",
        "exif-morepermissionsurl": "A URL where you can \"buy\" (or otherwise negotiate) to get more rights for the image.",
index eeebca2..7293225 100644 (file)
        "revdelete-log": "دلیل:",
        "revdelete-submit": "اعمال بر {{PLURAL:$1|نسخهٔ|نسخه‌های}} انتخاب شده",
        "revdelete-success": "'''پیدایی نسخه به روز شد.'''",
-       "revdelete-failure": "'''Ù¾Û\8cداÛ\8cÛ\8c Ù\86سخÙ\87â\80\8cÙ\87ا Ù\82ابÙ\84 Ø±Ù\88زاÙ\85دسازÛ\8c نیست:'''\n$1",
+       "revdelete-failure": "'''Ù¾Û\8cداÛ\8cÛ\8c Ù\86سخÙ\87â\80\8cÙ\87ا Ù\82ابÙ\84 Ø¨Ù\87 Ø±Ù\88ز Ú©Ø±Ø¯Ù\86 نیست:'''\n$1",
        "logdelete-success": "تغییر پیدایی مورد انجام شد.",
        "logdelete-failure": "'''پیدایی سیاهه‌ها قابل تنظیم نیست:'''\n$1",
        "revdel-restore": "تغییر پیدایی",
        "backend-fail-batchsize": "دسته‌ای مشتمل بر $1 {{PLURAL:$1|عملکرد|عملکرد}} پرونده به پشتیبان ذخیره داده شد؛ حداکثر مجاز $2 {{PLURAL:$2|عملکرد|عملکرد}} است.",
        "backend-fail-usable": "امکان خواندن یا نوشتن پروندهٔ $1 وجود نداشت چرا که سطح دسترسی کافی نیست یا شاخه/محفظهٔ مورد نظر وجود ندارد.",
        "filejournal-fail-dbconnect": "امکان وصل شدن به پایگاه داده دفترخانه برای پشتیبان ذخیره‌سازی «$1» وجود نداشت.",
-       "filejournal-fail-dbquery": "اÙ\85کاÙ\86 Ø±Ù\88زاÙ\85دسازÛ\8c Ø¯Ø§Ø¯Ú¯Ø§Ù\86 دفترخانه برای پشتیبان ذخیره‌سازی «$1» وجود نداشت.",
+       "filejournal-fail-dbquery": "اÙ\85کاÙ\86 Ø¨Ù\87 Ø±Ù\88ز Ú©Ø±Ø¯Ù\86 Ù¾Ø§Û\8cگاÙ\87 Ø¯Ø§Ø¯Ù\87 دفترخانه برای پشتیبان ذخیره‌سازی «$1» وجود نداشت.",
        "lockmanager-notlocked": "نمی‌توان قفل «$1» را گشود؛ چون قفل نشده‌است.",
        "lockmanager-fail-closelock": "امکان بستن پروندهٔ قفل‌شدهٔ «$1» وجود ندارد.",
        "lockmanager-fail-deletelock": "امکان حذف پروندهٔ قفل‌شدهٔ «$1» وجود ندارد.",
index f92ca53..e2e5507 100644 (file)
        "autoblockedtext": "Votre adresse IP a été bloquée automatiquement car elle a été utilisée par un autre utilisateur, lui-même bloqué par $1.\nLa raison invoquée est :\n\n: <em>$2</em>.\n\n* Début du blocage : $8\n* Expiration du blocage : $6\n* Compte bloqué : $7\n\nVous pouvez contacter $1 ou l’un des autres [[{{MediaWiki:Grouppage-sysop}}|administrateurs]] pour discuter de ce blocage.\n\nNotez que vous ne pourrez utiliser la fonctionnalité « {{int:emailuser}} » que si vous avez une adresse de courriel validée dans vos [[Special:Preferences|préférences]] et que cette fonctionnalité ne vous a pas été désactivée.\n\nVotre adresse IP actuelle est $3, et le numéro de blocage est $5.\nVeuillez inclure tous les détails ci-dessus dans chacune des requêtes que vous ferez.",
        "systemblockedtext": "Votre nom d'utilisateur ou votre adresse IP ont été bloqués automatiquement par MediaWiki.\nLa raison donnée est la suivante:\n\n: <em>$2</em>.\n\n* Le début du blocage: $8\n* Expiration du délai de blocage: $6\n* Elément concerné: $7\n\nVotre adresse IP actuelle est $3.\nVeuillez inclure tous les détails ci-dessus dans chacune des requêtes que vous ferez.",
        "blockednoreason": "aucune raison donnée",
+       "blockedtext-composite": "<strong>Votre nom d'utilisateur ou votre adresse IP ont été bloqués.</strong>\n\nLa raison invoquées est :\n\n:<em>$2</em>.\n\n* Début du blocage : $8\n* Expiration du blocage le plus long : $6\n\nVotre adresse IP actuelle est $3.\nVeuillez inclure tous les détails ci-dessus dans chaque demande que vous ferez.",
+       "blockedtext-composite-reason": "Il existe plusieurs blocages sur votre compte et/ou votre adresse IP",
        "whitelistedittext": "Vous devez vous $1 pour avoir la permission de modifier le contenu.",
        "confirmedittext": "Vous devez confirmer votre adresse de courriel avant de modifier les pages.\nVeuillez entrer et valider votre adresse de courriel dans vos [[Special:Preferences|préférences]].",
        "nosuchsectiontitle": "Impossible de trouver la section",
index 534f1dd..cd3def4 100644 (file)
        "currentevents-url": "Project:Rinnende saken",
        "disclaimers": "Foarbehâld",
        "disclaimerpage": "Project:Algemien foarbehâld",
-       "edithelp": "Bewurk-help",
+       "edithelp": "Bewurkhelp",
        "helppage-top-gethelp": "Help",
        "mainpage": "Haadside",
        "mainpage-description": "Haadside",
        "summary": "Gearfetting:",
        "subject": "Underwerp:",
        "minoredit": "Dit is fan lytse betsjutting",
-       "watchthis": "Folgje dizze side",
+       "watchthis": "Dizze side folgje",
        "savearticle": "Side bewarje",
        "publishpage": "Side fêstlizze",
        "publishchanges": "Feroarings publisearje",
        "currentrevisionlink": "Rinnende ferzje",
        "cur": "no",
        "next": "folgjende",
-       "last": "foarige",
+       "last": "frg.",
        "page_first": "earste",
        "page_last": "lêste",
-       "histlegend": "Ferskil oanjaan: Markearje de rûntsjes fan 'e te ferlykjen ferzjes, en druk op Enter of de knop ûnderoan.<br />\nLeginda: <strong>({{int:cur}})</strong> = ferskil mei de lêste ferzje, <strong>({{int:last}})</strong> = ferskil mei de eardere ferzje, <strong>{{int:minoreditletter}}</strong> = fan lytse betsjutting.",
+       "histlegend": "Ferskil oanjaan: Markearje de rûntsjes fan 'e te ferlykjen ferzjes, en druk op Enter of de knop ûnderoan.<br />\nLeginda: <strong>({{int:cur}})</strong> = ferskil mei de lêste ferzje, <strong>({{int:last}})</strong> = ferskil mei de foargeande ferzje, <strong>{{int:minoreditletter}}</strong> = fan lytse betsjutting.",
        "history-fieldset-title": "Ferzjes filterje",
        "histfirst": "âldste",
        "histlast": "nijste",
        "searchprofile-everything-tooltip": "Alle ynhâld trochsykje (ynklusyf oerlissiden)",
        "searchprofile-advanced-tooltip": "Sykje yn oanjûne nammeromten",
        "search-result-size": "$1 ({{PLURAL:$2|1 wurd|$2 wurden}})",
-       "search-redirect": "(trochferwizing $1)",
+       "search-redirect": "(trochwiisd fan $1)",
        "search-section": "(seksje $1)",
        "search-category": "(kategory $1)",
        "search-suggest": "Bedoele jo: $1",
        "newsectionsummary": "/* $1 */ nije seksje",
        "rc-enhanced-expand": "Details werjaan",
        "rc-enhanced-hide": "Details ferskûlje",
-       "recentchangeslinked": "Folgje keppelings",
-       "recentchangeslinked-feed": "Folgje keppelings",
-       "recentchangeslinked-toolbox": "Folgje keppelings",
-       "recentchangeslinked-title": "Feroarings yn ferbân mei \"$1\"",
+       "recentchangeslinked": "Keppelings folgje",
+       "recentchangeslinked-feed": "Keppelings folgje",
+       "recentchangeslinked-toolbox": "Keppelings folgje",
+       "recentchangeslinked-title": "Feroarings besibbe mei \"$1\"",
        "recentchangeslinked-summary": "Jou in sidenamme, en besjoch de feroarings op siden dy't keppele binne fan as nei dy side. (Jou {{ns:category}}:Kategorynamme om de leden fan in kategory te besjen). Wizigings oan siden op [[Special:Watchlist|jo Folchlist]] wurde <strong>fet</strong> werjûn.",
        "recentchangeslinked-page": "Sidenamme:",
        "recentchangeslinked-to": "Feroarings oan siden mei ferwizings nei dizze side besjen",
        "sharedupload-desc-here": "Dit bestân komt fan $1, en kin ek troch oare projekten brûkt wurde.\nDe beskriuwing op syn [$2 bestânsside] dêre wurdt hjirûnder werjûn.",
        "filepage-nofile": "Der bestiet gjin bestân mei sa'n namme.",
        "filepage-nofile-link": "Der bestiet gjin bestân mei sa'n namme [bied $1 oan].",
-       "uploadnewversion-linktext": "Bied in nije ferzje fan dit bestân oan",
+       "uploadnewversion-linktext": "In nije ferzje fan dit bestân oanbiede",
        "shared-repo-from": "fan $1",
+       "upload-disallowed-here": "Jo kinne gjin nije ferzje fan dit bestân oanbiede.",
        "filerevert": "$1 weromsette",
        "filerevert-legend": "Bestân weromsette",
        "filerevert-intro": "Jo binne '''[[Media:$1|$1]]''' oan it weromdraaien ta de [$4 ferzje op $2, $3].",
index 0c6121d..86526a1 100644 (file)
        "autoblockedtext": "כתובת ה־IP שלך נחסמה באופן אוטומטי כיוון שמשתמש אחר, שנחסם על־ידי $1, השתמש בה.\nהסיבה שניתנה לחסימה היא:\n\n:<em>$2</em>\n\n* תחילת החסימה: $8\n* פקיעת החסימה: $6\n* החסימה שבוצעה: $7\n\nבאפשרותך ליצור קשר עם $1 או עם כל אחד מ[[{{MediaWiki:Grouppage-sysop}}|מפעילי המערכת]] האחרים כדי לדון בחסימה.\n\nכמו־כן, באפשרותך להשתמש בתכונת \"{{int:emailuser}}\", אלא אם לא ציינת כתובת דוא\"ל תקפה ב[[Special:Preferences|העדפות המשתמש שלך]] או אם נחסמת משליחת דוא\"ל.\n\nכתובת ה־IP הנוכחית שלך היא $3, ומספר החסימה שלך הוא #$5.\nיש לציין את כל הפרטים הללו בכל פנייה לבירור החסימה.",
        "systemblockedtext": "שם המשתמש או כתובת ה־IP שלך נחסמו באופן אוטומטי על־ידי תוכנת מדיה־ויקי.\nהסיבה שניתנה לחסימה היא:\n\n:<em>$2</em>\n\n* תחילת החסימה: $8\n* פקיעת החסימה: $6\n* החסימה שבוצעה: $7\n\nכתובת ה־IP הנוכחית שלך היא $3.\nיש לציין את כל הפרטים הללו בכל פנייה לבירור החסימה.",
        "blockednoreason": "לא ניתנה סיבה",
+       "blockedtext-composite": "<strong>שם המשתמש או כתובת ה־IP שלכם נחסמו מעריכה.</strong>\n\nהסיבה שניתנה היא:\n\n:<em>$2</em>.\n\n* תחילת החסימה: $8\n* פקיעת החסימה הארוכה ביותר: $6\n\nכתובת ה־IP הנוכחית שלך היא $3.\nיש לספק את כל המידע הנ\"ל עבור כל השאילתות שאתם מבצעים.",
+       "blockedtext-composite-reason": "ישנן מספר חסימות על החשבון שלך ו/או כתובת ה־IP שלך",
        "whitelistedittext": "נדרשת $1 כדי לערוך דפים.",
        "confirmedittext": "יש לאמת את כתובת הדוא\"ל לפני עריכת דפים.\nנא להגדיר ולאמת את כתובת הדוא\"ל שלך באמצעות [[Special:Preferences|העדפות המשתמש]] שלך.",
        "nosuchsectiontitle": "הפסקה לא נמצאה",
index dc73bef..dd290a3 100644 (file)
        "autoblockedtext": "Az IP-címed automatikusan blokkolva lett, mert korábban egy olyan szerkesztő használta, akit $1 blokkolt, az alábbi indoklással:\n\n:''$2''\n\n*A blokk kezdete: '''$8'''\n*A blokk lejárata: '''$6'''\n*Blokkolt szerkesztő: '''$7'''\n\nKapcsolatba léphetsz $1 szerkesztőnkkel, vagy egy másik [[{{MediaWiki:Grouppage-sysop}}|adminisztrátorral]], és megbeszélheted vele a blokkolást.\n\nAz „{{int:emailuser}}” funkciót csak akkor használhatod, ha érvényes e-mail címet adtál meg\n[[Special:Preferences|fiókbeállításaidban]], és nem blokkolták a használatát.\n\nJelenlegi IP-címed: $3, a blokkolás azonosítószáma: #$5.\nKérjük, hogy érdeklődés esetén mindkettőt add meg.",
        "systemblockedtext": "A felhasználónevedet vagy IP-címedet automatikusan blokkolta a MediaWiki.\nA blokkolás indoka:\n\n:<em>$2</em>\n\n* A blokk kezdete: $8\n* A blokk lejárata: $6\n* Blokkolt szerkesztő: $7\n\nA jelenlegi IP-címed: $3.\nKérjük, hogy érdeklődés esetén minden fenti részletet adj meg.",
        "blockednoreason": "nem adott meg okot",
+       "blockedtext-composite": "<strong>A felhasználónevedet vagy IP-címedet blokkolták.</strong>\nA blokkolás indoka:\n\n:<em>$2</em>\n\n* A blokk kezdete: $8\n* A leghoszabb blokk lejárata: $6\n\nA jelenlegi IP-címed: $3.\nKérjük, hogy érdeklődés esetén minden fenti részletet adj meg.",
+       "blockedtext-composite-reason": "Fiókoddal és/vagy IP-címeddel szemben több blokk is érvényben van",
        "whitelistedittext": "Lapok szerkesztéséhez $1.",
        "confirmedittext": "Lapok szerkesztése előtt meg kell erősítened az e-mail címedet. Kérjük, hogy a [[Special:Preferences|szerkesztői beállításaidban]] add meg, majd erősítsd meg az e-mail címedet.",
        "nosuchsectiontitle": "A szakasz nem található",
index 85d9246..09b4518 100644 (file)
        "prevn": "նախորդ {{PLURAL:$1|$1}}",
        "nextn": "յաջորդ {{PLURAL:$1|$1}}",
        "prev-page": "նախորդ էջ",
-       "next-page": "յաջորդ էջ",
+       "next-page": "յաջորդ էջը",
        "prevn-title": "Նախորդ $1 {{PLURAL:$1|արդիւնքը|արդիւնքները}}",
        "nextn-title": "Յաջորդ $1 {{PLURAL:$1|արդիւնքը|արդիւնքները}}",
        "shown-title": "Իւրաքանչիւր էջի վրայ ցուցնել $1 {{PLURAL:$1|արդիւնք|արդիւնքներ}}",
        "search-external": "Արտաքին որոնում",
        "preferences": "Նախընտրութիւններ",
        "mypreferences": "Նախընտրութիւններ",
-       "skin-preview": "Նախադիտել",
+       "skin-preview": "Կանխաստուգել",
        "prefs-watchlist": "Հսկողութեան ցանկ",
        "prefs-editwatchlist-clear": "Մաքրել հսկողութեան ցանկը",
        "saveprefs": "Յիշել",
        "prefs-info": "Հիմնական տուեալներ",
        "prefs-signature": "Ստորագրութիւն",
        "prefs-editor": "Խմբագրող",
-       "prefs-preview": "Նախադիտել",
+       "prefs-preview": "Կանխաստուգել",
        "group": "Խումբ.",
        "group-bot": "Մեքենայիկներ",
        "group-sysop": "Վարիչներ",
        "all-logs-page": "Բոլոր հանրային տեղեկատետրերը",
        "alllogstext": "{{SITENAME}} կայքի տեղեկատետրերու միացեալ ցանկ։\nԿրնաք արդիւնքները սահմանափակել ըստ տեղեկատետրի տեսակին, մասնակիցի անունին կամ համապատասխան էջին։",
        "logempty": "Համապատասխան տարրեր չկան տեղեկատետերին մէջ։",
+       "checkbox-none": "Ոչ մէկ",
        "allpages": "Բոլոր էջերը",
        "allarticles": "Բոլոր էջերը",
        "allpagessubmit": "‎Յառաջանալ",
        "logentry-newusers-autocreate": "$1 մասնակցային հաշիւը {{GENDER:$2|ստեղծուած է}} ինքնաբերաբար",
        "logentry-upload-upload": "$1 {{GENDER:$2|ներբեռնուած է}} $3",
        "logentry-upload-overwrite": "$1 {{GENDER:$2|վերբեռնեց}} $3ի նոր տարբերակ",
+       "rightsnone": "(ոչ մէկ)",
        "feedback-cancel": "Չեղարկել",
        "searchsuggest-search": "Որոնել {{SITENAME}} կայքին մէջ",
        "duration-days": "$1 {{PLURAL:$1|օր}}",
+       "expand_templates_preview": "Կանխաստուգել",
        "special-characters-group-latin": "Լատիներէն",
        "special-characters-group-arabic": "Արաբերէն",
        "randomrootpage": "Պատահական արմատ էջ"
index fba1d63..63969ab 100644 (file)
        "autoblockedtext": "Tu adresse IP ha essite automaticamente blocate perque un altere usator lo usava qui esseva blocate per $1.\nLe motivo presentate es:\n\n:<em>$2</em>\n\n* Initio del blocada: $8\n* Expiration del blocada: $6\n* Blocato intendite: $7\n\nTu pote contactar $1 o un del altere [[{{MediaWiki:Grouppage-sysop}}|administratores]] pro discuter le blocada.\n\nNota que tu pote solmente utilisar le function \"{{int:emailuser}}\" si tu ha registrate un adresse de e-mail valide in tu [[Special:Preferences|preferentias de usator]] e tu non ha essite blocate de usar lo.\n\nTu adresse IP actual es $3, e le ID del blocada es #$5.\nPer favor include tote le detalios supra specificate in omne correspondentia.",
        "systemblockedtext": "Tu nomine de usator o adresse IP ha essite blocate automaticamente per MediaWiki.\nLe motivo presentate es:\n\n:<em>$2</em>\n\n* Initio del blocada: $8\n* Expiration del blocada: $6\n* Blocato intendite: $7\n\nTu adresse IP actual es $3.\nPer favor, include tote le detalios enumerate hic supra in omne questiones que tu pone.",
        "blockednoreason": "nulle motivo specificate",
+       "blockedtext-composite": "<strong>Tu nomine de usator o adresse IP ha essite blocate.</strong>\n\nLe motivo presentate es:\n\n:<em>$2</em>.\n\n* Initio del blocada: $8\n* Expiration del blocada le plus longe: $6\n\nTu adresse IP actual es $3.\nPer favor, include tote le detalios enumerate hic supra in omne questiones que tu pone.",
+       "blockedtext-composite-reason": "Il ha plure blocadas contra tu conto e/o adresse IP",
        "whitelistedittext": "Tu debe $1 pro poter modificar paginas.",
        "confirmedittext": "Tu debe confirmar tu adresse de e-mail pro poter modificar paginas.\nPer favor entra e valida tu adresse de e-mail per medio de tu [[Special:Preferences|preferentias de usator]].",
        "nosuchsectiontitle": "Section non trovate",
index d695f7e..41e4c54 100644 (file)
        "virus-scanfailed": "skano ne sucesis (kodexo $1)",
        "virus-unknownscanner": "antiviruso nekonocata:",
        "logouttext": "<strong>Vu ekirabas.</strong>\n\nAtencez ke kelka pagini posible duras montresar quaze vu ne ekiris, til ke vu vakuigos la tempala-magazino di la navigilo.",
+       "logout-failed": "Ne povas ekirar nun: $1",
        "cannotlogoutnow-title": "Ne povas ekirar nun",
        "cannotlogoutnow-text": "Ekirar ne esas posibla kande vu uzas $1.",
        "welcomeuser": "Esez bonvenanta, $1!",
        "botpasswords-newpassword": "La nova pasovorto por enirar <strong>$1</strong> esas <strong>$2</strong>.\n<em>Voluntez memorigar to por futura refero.</em> <br> (Por anciena ''bot-''i, qui bezonas la nomo di 'login' esar la sama kam l'eventuala nomo dil uzero, vu anke povas uzar <strong>$3</strong> kom uzero-nomo, e <strong>$4</strong> kom pasovorto.)",
        "botpasswords-no-provider": "\"BotPasswordsSessionProvider\" ne esas disponebla.",
        "botpasswords-restriction-failed": "Restrikti pri pasovorti koncerne ''bot''-i impedas vua 'log in'.",
+       "botpasswords-invalid-name": "L'uzero-nomo informata ne kontenas separilo di 'bot'-pasovorto (\"$1\")",
        "botpasswords-not-exist": "L'uzero \"$1\" ne havas pasovorto nomizita \"$2\" por lua 'bot'.",
        "botpasswords-needs-reset": "La pasovorto por la 'bot' nomizita \"$1\" dal {{GENDER:$2|uzero}} \"$2\" mustas rikreesar.",
        "botpasswords-locked": "Vu ne povas facar 'login' per robotala pasovorto (bot password), pro ke vua konto blokusesis.",
        "subject-preview": "Previdado di la temo:",
        "previewerrortext": "Eventis eroro kande on probis krear previdado pri vua modifikuri.",
        "blockedtitle": "La uzero esas blokusita",
+       "blocked-email-user": "<strong>Vu blokusesis pri sendar e-posto. Vu ankore povas redaktar altra pagini en ca wiki.</strong> Vu povas konocar omna detali pri la blokuso en la [[Special:MyContributions|pagino pri vua kontributadi]].\n\nLa blokuso facesis da $1.\n\nLa motivo esis <em>$2</em>.\n\n* Komenco di la blokuso: $8\n* La blokuso finos ye: $6\n* Motivo por blokuso: $7\n* Nombro dil blokuso #$5",
        "blockedtext-partial": "<strong>Vua uzero-nomo od IP-adreso blokusesis koncerne modifikuri en ca pagino. Vu ankore povas redaktar altra pagini en ca Wiki.</strong> Vu povas vidar omna detali pri la blokuso en [[Special:MyContributions|account contributions]].\n\n$1 blokusis vu. La motivo esis <em>$2</em>.\n\n* Komenco dil blokuso: $8\n* Fino dil blokuso: $6\n* Motivo dil blokuso: $7\n* Blokuso #$5",
        "blockedtext": "<strong>Vua uzantonomo od IP-adreso blokusesis.</strong>\n\n$1 blokusis vu.\nLa motivo esis <em>$2</em>.\n\n* Komenco di la blokuso: $8\n* Fino di la blokuso: $6\n* Motivo dil blokuso: $7\n\nVu povas kontaktar $1 od altra [[{{MediaWiki:Grouppage-sysop}}|administrero]] por diskutar la blokuso.\nVu ne povas uzar \"email this user\" por sendar e-posto ecepte se valida email indikesis en tua [[Special:Preferences|preferaji dil uzanto]], e se vu ne blokusesis por uzar ol.\nVua nuna IP-adreso esas $3, e la ID dil blokuso esas #$5.\nVoluntez inkluzor omna detali adsupre en omna demandi quin vu facos.",
        "autoblockedtext": "<strong>Vua uzantonomo od IP-adreso blokusesis.</strong>\n\n$1 blokusis vu.\nLa motivo esis <em>$2</em>.\n\n* Komenco di la blokuso: $8\n* Fino di la blokuso: $6\n* Persono blokusata: $7\n\nVu povas kontaktar $1 od altra [[{{MediaWiki:Grouppage-sysop}}|administrero]] por diskutar pri la blokuso.\nVu ne povas uzar \"email this user\" por sendar e-posto, ecepte se valida email indikesis en tua [[Special:Preferences|preferaji dil uzero]], e se vu ne blokusesis por uzar ol.\nVua nuna IP-adreso esas $3, e la ID dil blokuso esas #$5.\nVoluntez inkluzor omna detali adsupre en omna demandi quin vu facos.",
        "systemblockedtext": "Vua uzero-nomo od IP-adreso blokusabis automatale da MediaWiki.\nLa motivo esas:\n\n:<em>$2</em>\n\n* Komenco di la blokuso: $8\n* Fino di la blokuso: $6\n* Persono blokuzata: $7\n\nVua nuna IP-adreso esas $3.\nVoluntez inkluzar omna detalii furnisita adsupre, en irga demandi quin vu facos.",
-       "blockednoreason": "nula motivo donesis",
+       "blockednoreason": "nula motivo informesis",
        "whitelistedittext": "Vu mustas $1 por redaktar pagini.",
        "confirmedittext": "Vu mustas konfirmar vua adreso di e-posto ante ke vu povas redaktar pagini. Voluntez informar e validigar vua e-posto adreso tra vua [[Special:Preferences|preferaji di uzero]].",
        "nosuchsectiontitle": "On ne povis trovar la seciono",
        "ipb-blocklist-contribs": "Kontributadi dil uzero {{GENDER:$1|$1}}",
        "block-actions": "Agadi blokusota:",
        "block-expiry": "Expiro:",
+       "block-options": "Plusa agadi:",
+       "block-reason": "Motivo:",
        "unblockip": "Desblokusar uzero",
        "unblockiptext": "Uzez la sequanta formularo por restaurar la skribo-aceso ad IP-adreso qua blokusesis antee.",
        "ipusubmit": "Desblokusar",
        "ipblocklist": "Blokusita uzanti",
+       "blocklist-reason": "Motivo",
        "ipblocklist-submit": "Serchar",
        "ipblocklist-otherblocks": "Altra {{PLURAL:$1|blokuso|blokusi}}",
        "infiniteblock": "nefinita",
        "mw-widgets-dateinput-no-date": "Nula dato selektita",
        "mw-widgets-dateinput-placeholder-day": "YYYY-MM-DD",
        "mw-widgets-titleinput-description-redirect": "Ridirektar ad $1",
+       "mw-widgets-usersmultiselect-placeholder": "Adjuntez pluse...",
+       "mw-widgets-titlesmultiselect-placeholder": "Adjuntez pluse...",
        "date-range-to": "Til (dato):",
        "sessionprovider-nocookies": "''Bisquiti'' forsan esas desacendita. Certigez ke vu acendar ''bisquiti'' e riprobez.",
        "randomrootpage": "Hazarda radikopagino",
index 94f5ab0..e27a850 100644 (file)
        "action-deletechangetags": "データベースからタグの削除",
        "action-purge": "このページのキャッシュ破棄",
        "action-apihighlimits": "API要求でのより高い制限値の使用",
+       "action-autoconfirmed": "IPベースの速度制限を受けない",
        "action-bigdelete": "大きな履歴があるページの削除",
        "action-blockemail": "利用者のメール送信のブロック",
+       "action-bot": "自動処理と認識させる",
        "action-editprotected": "「{{int:protect-level-sysop}}」の保護を設定されたページの編集",
        "action-editsemiprotected": "「{{int:protect-level-autoconfirmed}}」の保護を設定されたページの編集",
        "action-editinterface": "ユーザーインターフェースの編集",
        "action-editmyuserjson": "自分のJSONファイルの編集",
        "action-editmyuserjs": "自分のJavaScriptファイルの編集",
        "action-viewsuppressed": "すべての利用者から隠された版の閲覧",
+       "action-hideuser": "利用者名をブロックして公開記録から隠す",
        "action-ipblock-exempt": "IPブロック、自動ブロック、広域ブロックの回避",
        "action-unblockself": "自分に対するブロックの解除",
+       "action-noratelimit": "速度制限を受けない",
        "action-reupload-own": "自分がアップロードした既存のファイルへの上書き",
+       "action-nominornewtalk": "議論ページの細部の編集をした際に、新着メッセージとして通知しない",
        "action-markbotedits": "巻き戻しをボットの編集として扱う",
        "action-patrolmarks": "最近の更新での巡回済み印の閲覧",
        "action-override-export-depth": "リンク先ページの5階層まで含めた書き出し",
        "passwordpolicies-policyflag-suggestchangeonlogin": "ログイン時に変更を提案",
        "easydeflate-invaliddeflate": "提供されたコンテンツが適切に圧縮されていません",
        "unprotected-js": "セキュリティ上の理由から、JavaScriptは保護されていないページからは読み込みできません。MediaWiki: 名前空間内、利用者下位ページのいずれかでのみjavascriptを作成してください。",
-       "userlogout-continue": "ã\83­ã\82°ã\82¢ã\82¦ã\83\88ã\82\92è¡\8cã\81\84ã\81\9fã\81\84å ´å\90\88ã\80\81[$1 ã\83­ã\82°ã\82¢ã\82¦ã\83\88ã\83\9aã\83¼ã\82¸ã\81\8bã\82\89å®\9fæ\96½]ã\81\97ã\81¦ã\81\8fã\81 ã\81\95ã\81\84ã\80\82"
+       "userlogout-continue": "ã\83­ã\82°ã\82¢ã\82¦ã\83\88ã\81\97ã\81¾ã\81\99ã\81\8bï¼\9f"
 }
index 759c803..9c0eaf4 100644 (file)
        "email": "E-poste",
        "prefs-help-realname": "Namo rastıkên serbesto.\nSıma ke ney bıgurenê, karê sıma de no namdarêni dano.",
        "prefs-help-email": "Dayışê adresa e-postey keyfiyo, labelê seba eyarê parola lazıma, wexto ke şıma naye xo vira kerê.",
-       "prefs-help-email-others": "Şıma şenê weçinê ke ê bini be yew gırey pela şımaya karberi ya zi pela werênayışi sera şıma de ebe e-poste irtıbat kewê.\nKaberê bini ke şıma de kewti irtıbat, adresa e-postey şıma eşkera nêbena.",
+       "prefs-help-email-others": "Şıma şenê weçinê ke ê bini be yew gırey pela şımaya karberi ya zi pela werênayışi sera şıma de ebe e-poste irtıbat kewê.\nKaberê bini ke şıma de kewti irtıbat, adresa e-postey şıma aşkera nêbena.",
        "prefs-help-email-required": "Adresa emaili lazıma.",
        "prefs-signature": "İmza",
        "prefs-diffs": "Ferqi",
index 53d3cc7..1a85840 100644 (file)
                        "Ryuch",
                        "Delim",
                        "Comjun04",
-                       "Son77391"
+                       "Son77391",
+                       "Jango"
                ]
        },
        "tog-underline": "링크에 밑줄 긋기:",
-       "tog-hideminor": "ìµ\9cê·¼ ë°\94ë\80\9cì\97\90ì\84\9c ì\82¬ì\86\8cí\95\9c í\8e¸ì§\91ì\9d\84 숨기기",
+       "tog-hideminor": "ìµ\9cê·¼ ë³\80ê²½í\95\9c ì\82¬ì\86\8cí\95\9c í\8e¸ì§\91 숨기기",
        "tog-hidepatrolled": "최근 바뀜에서 점검한 편집을 숨기기",
        "tog-newpageshidepatrolled": "새 문서 목록에서 검토한 문서를 숨기기",
        "tog-hidecategorization": "페이지 분류 숨기기",
        "autoblockedtext": "당신의 IP 주소는 $1님이 차단한 사용자가 사용했던 IP이기 때문에 자동으로 차단되었습니다.\n차단된 이유는 다음과 같습니다:\n\n:<em>$2</em>\n\n* 차단이 시작된 시간: $8\n* 차단이 끝나는 시간: $6\n* 차단된 사용자: $7\n\n$1 또는 [[{{MediaWiki:Grouppage-sysop}}|다른 관리자]]에게 차단에 대해 문의할 수 있습니다.\n\n[[Special:Preferences|사용자 환경 설정]]에 올바른 이메일 주소가 있어야만 \"이메일 보내기\" 기능을 사용할 수 있습니다. 또한 이메일 보내기 기능이 차단되어 있으면 이메일을 보낼 수 없습니다.\n\n현재 IP 주소는 $3이고, 차단 ID는 #$5입니다.\n문의할 때에 이 정보를 같이 알려주세요.",
        "systemblockedtext": "당신의 사용자 이름 또는 IP 주소가 자동으로 미디어위키에 의해 차단되었습니다.\n이유는 다음과 같습니다:\n\n:<em>$2</em>\n\n* 차단 시작: $8\n* 차단 만료: $6\n* 차단 대상: $7\n\n당신의 현재 IP 주소는 $3입니다.\n문의에 대해 상기의 상세 설명을 모두 포함해 주십시오.",
        "blockednoreason": "이유를 입력하지 않음",
+       "blockedtext-composite": "<strong>당신의 사용자 이름 또는 IP 주소가 미디어위키에 의해 차단되었습니다.\n\n이유는 다음과 같습니다:\n\n:<em>$2</em>\n\n* 차단 시작: $8\n* 차단 만료: $6\n\n당신의 현재 IP 주소는 $3입니다.\n문의에 대해 상기의 상세 설명을 모두 포함해 주십시오.",
        "whitelistedittext": "문서를 편집하기 전에 $1해야 합니다.",
        "confirmedittext": "문서를 고치려면 이메일 인증 절차가 필요합니다.\n[[Special:Preferences|사용자 환경 설정]]에서 이메일 주소를 입력하고 이메일 주소 인증을 해주시기 바랍니다.",
        "nosuchsectiontitle": "문단을 찾을 수 없음",
        "confirm-unwatch-top": "이 문서를 주시문서 목록에서 뺄까요?",
        "confirm-rollback-button": "확인",
        "confirm-rollback-top": "이 문서의 편집을 되돌리시겠습니까?",
+       "confirm-rollback-bottom": "이 작업은 선택된 변경 사항을 즉시 롤백합니다",
        "confirm-mcrrestore-title": "판 복구",
        "confirm-mcrundo-title": "변경사항 취소",
        "mcrundofailed": "실행 취소를 실패했습니다",
index f23a157..e4b3eb3 100644 (file)
        "group-autoconfirmed-member": "自證其簿",
        "group-bot-member": "僕",
        "group-sysop-member": "有秩",
-       "group-interface-admin-member": "司空",
+       "group-interface-admin-member": "{{GENDER:$1|司空}}",
        "group-bureaucrat-member": "門下",
        "group-suppress-member": "監",
        "grouppage-user": "{{ns:project}}:簿",
index 8441adf..34d19c4 100644 (file)
@@ -42,7 +42,7 @@
        "tog-watchmoves": "Додавај ги страниците и податотеките што ги преместувам во набљудуваните",
        "tog-watchdeletion": "Додавај ги страниците и податотеките што ги бришам во набљудуваните",
        "tog-watchuploads": "Ставај ги податотеките што ги подигам во набљудуваните",
-       "tog-watchrollback": "Додај ги страниците сум ги отповикал во набљудувани",
+       "tog-watchrollback": "Додавај ги страниците сум ги отповикал во набљудуваните",
        "tog-minordefault": "Обележувај ги сите уредувања како ситни по основно",
        "tog-previewontop": "Прикажи преглед пред кутијата за уредување",
        "tog-previewonfirst": "Прикажи преглед при првото уредување",
        "blockedtext-partial": "<strong>На вашето корисничко име или IP-адреса му е забрането да прави измени на страницава. Можете сепак да уредувате други страници на ова вики.</strong> Сите поединости за забраната ќе ги најдете во [[Special:MyContributions|придонесите на сметката]].\n\nЗабраната ја дал $1.\n\nНаведената причина гласи <em>$2</em>.\n\n* Почеток на забраната: $8\n* Истек на забраната: $6\n* Предвиден забраненик: $7\n* Назнака на забраната #$5",
        "blockedtext": "<strong>Вашето корисничко име или IP-адреса е блокирано.</strong>\n\nБлокирањето е направено од страна на $1.\nДаденото образложение е <em>$2</em>.\n\n* Почеток на блокирањето: $8\n* Истекување на блокирањето: $6\n* Корисникот што требало да биде блокиран: $7\n\nМоже да контактирате со $1 или некој друг [[{{MediaWiki:Grouppage-sysop}}|администратор]] за да разговарате во врска со блокирањето.\nМожете да ја искористите можноста „{{int:emailuser}}“ ако е назначена важечка е-поштенска адреса во [[Special:Preferences|вашите нагодувања]] и не ви е забрането да ја користите.\nВашата сегашна IP-адреса е $3, а назнака на блокирањето гласи #$5.\nВе молиме наведете ги сите подробности прикажани погоре, во вашата евентуална реакција.",
        "autoblockedtext": "Вашата IP-адреса е автоматски блокирана бидејќи била користена од страна на друг корисник, кој бил блокиран од $1.\nДаденото образложение е следново:\n\n:<em>$2</em>\n\n* Почеток на блокирањето: $8\n* Истекување на блокирањето: $6\n* Со намера да се блокира: $7\n\nМоже да контактирате со $1 или некој друг [[{{MediaWiki:Grouppage-sysop}}|администратор]] за да разговарате во врска со ова блокирање.\n\nИмајте предвид дека можеби нема да можете да ја искористите можноста „{{int:emailuser}}“ доколку не е назначена важечка е-поштенска адреса во [[Special:Preferences|вашите нагодувања]] и ви е забрането користење на истата.\n\nВашата IP-адреса е $3, a назнака на блокирањетo е $5.\nВе молиме наведете ги овие подробности доколку реагирате на блокирањето.",
-       "systemblockedtext": "Ð\92аÑ\88еÑ\82о ÐºÐ¾Ñ\80иÑ\81ниÑ\87ко Ð¸Ð¼Ðµ Ð¸Ð»Ð¸ IP-адÑ\80еÑ\81а Ðµ Ð°Ð²Ñ\82омаÑ\82Ñ\81ки Ð±Ð»Ð¾ÐºÐ¸Ñ\80ано Ð¾Ð´ Ð\9cедиÑ\98аÐ\92ики.\nÐ\9fонÑ\83дена Ð¿Ñ\80иÑ\87ина:\n\n:<em>$2</em>\n\n* Почеток на блокот: $8\n* Истек на блокот: $6\n* Блокот е наменет за: $7\n\nВашата тековна IP-адреса гласи $3.\nПрепишете ги сите горенаведени поединости доколку сакате да се распрашате кај надлежните во врска со блокот.",
+       "systemblockedtext": "Ð\92аÑ\88еÑ\82о ÐºÐ¾Ñ\80иÑ\81ниÑ\87ко Ð¸Ð¼Ðµ Ð¸Ð»Ð¸ IP-адÑ\80еÑ\81а Ðµ Ð°Ð²Ñ\82омаÑ\82Ñ\81ки Ð±Ð»Ð¾ÐºÐ¸Ñ\80ано Ð¾Ð´ Ð\9cедиÑ\98аÐ\92ики.\nÐ\9dаведенаÑ\82а Ð¿Ñ\80иÑ\87ина Ð³Ð»Ð°Ñ\81и:\n\n:<em>$2</em>\n\n* Почеток на блокот: $8\n* Истек на блокот: $6\n* Блокот е наменет за: $7\n\nВашата тековна IP-адреса гласи $3.\nПрепишете ги сите горенаведени поединости доколку сакате да се распрашате кај надлежните во врска со блокот.",
        "blockednoreason": "не е наведена причина",
+       "blockedtext-composite": "<strong>Вашето корисничко име или IP-адреса е блокирано.</strong>\n\nНаведената причина гласи:\n\n:<em>$2</em>.\n\n* Почеток на блокот: $8\n* Истек на најдолгиот блок: $6\n\nВашата тековна IP-адреса гласи $3.\nПрепишете ги сите горенаведени поединости доколку сакате да се распрашате кај надлежните во врска со блокот.",
+       "blockedtext-composite-reason": "Вашата сметка или IP-адреса има неколку блокови",
        "whitelistedittext": "Мора да сте $1 за да уредувате страници.",
        "confirmedittext": "Морате да ја потврдите вашата е-поштенска адреса пред да уредувате страници.\nПоставете ја и валидирајте ја вашата е-поштенска адреса преку вашите [[Special:Preferences|нагодувања]].",
        "nosuchsectiontitle": "Не можам да го пронајдам заглавието",
        "accmailtitle": "Лозинката е испратена.",
        "accmailtext": "На $2 е спратена е случајно создадена лозинка за [[User talk:$1|$1]] е испратена. Истата може да се смени на страницата ''[[Special:ChangePassword|Менување на лозинка]]'' откако ќе се најавите.",
        "newarticle": "(нова)",
-       "newarticletext": "Дојдовте на врска до страница што не постои.\nЗа да ја создадете страницата, напишете текст во полето подолу ([$1 помош]). Ако сте овде по грешка, само систнете на копчето '''назад''' во вашиот прелистувач.",
+       "newarticletext": "Дојдовте на врска до страница која сѐ уште не постои.\nЗа да ја создадете страницата, напишете текст во полето подолу ([$1 помош]). Ако сте овде по грешка, само систнете на копчето '''назад''' во вашиот прелистувач.",
        "anontalkpagetext": "----\n<em>Ова е разговорна страница со анонимен корисник кој сè уште не регистрирал корисничка сметка или не ја користи.<em>\nЗатоа мораме да ја користиме неговата бројчена IP-адреса за да го препознаеме.\nЕдна ваква IP-адреса може да ја делат повеќе корисници.\nАко сте анонимен корисник и сметате дека кон вас се упатени нерелевантни коментари, тогаш [[Special:CreateAccount|создајте корисничка сметка]] или [[Special:UserLogin|најавете се]] за да избегнете поистоветување со други анонимни корисници во иднина.''",
        "noarticletext": "Таква страница сè уште не постои.\nМожете да проверите [[Special:Search/{{PAGENAME}}|дали насловот се споменува]] во други статии,\nда ги <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} пребарате дневниците],\nили да [{{fullurl:{{FULLPAGENAME}}|action=edit}} ја создадете]</span>.",
        "noarticletext-nopermission": "Таква страница сè уште не постои.\nМожете да проверите [[Special:Search/{{PAGENAME}}|дали насловот се споменува]] во други статии или пак да <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} пребарате поврзаните дневници]</span>, но немате дозвола да ја создадете страницата.",
index b5128d8..7584b6b 100644 (file)
        "recentchangescount": "လတ်တလော အပြောင်းအလဲများ၊ စာမျက်နှာ ရာဇဝင်များနှင့် မှတ်တမ်းများတွင် ပုံသေအားဖြင့် ပြသရန် တည်းဖြတ်မှုအရေအတွက် -",
        "prefs-help-recentchangescount": "အများဆုံးအရေအတွက် - ၁ဝဝဝ",
        "prefs-help-watchlist-token2": "ဤသည် သင့်စောင့်ကြည့်စာရင်း၏ web feed ရှိ လျို့ဝှက်သော့ ဖြစ်ပါသည်။ သင်၏စောင့်ကြည့်စာရင်းကို ဖတ်ရှုနိုင်သော မည်သူ့ကိုမဆို ယင်းအားမမျှဝေပါနှင့်။ သင်လိုအပ်ပါက [[Special:ResetTokens|ယင်းအား ပြန်ချိန်နိုင်ပါသည်]]။",
+       "prefs-help-tokenmanagement": "သင့်စောင့်ကြည့်စာရင်း၏ web feed ကို ဝင်ရောက်နိုင်သော သင့်အကောင့်လုံခြုံရေး သော့ခလုတ်ကို တွေ့မြင်၊ ပြန်လည်ချိန်ညှိနိုင်ပါသည်။ သော့ခလုတ်ကိုသိသည့် မည်သူမဆို သင့်စောင့်ကြည့်စာရင်းကို ဖတ်ရှုနိုင်သည်၊ ထို့ကြောင့် ယင်းအား မမျှဝေပါနှင့်။",
        "savedprefs": "သင့်ရွေးချယ်မှုတို့ကို သိမ်းပြီးပါပြီ။",
        "savedrights": "{{GENDER:$1|$1}}၏ အသုံးပြု အခွင့်အရေးများကို သိမ်းပြီးပါပြီ။",
        "timezonelegend": "အချိန်ဇုန် -",
        "rcfilters-watchlist-showupdated": "သင်နောက်ဆုံးကြည့်ရှုခဲ့ပြီးနောက် ပြောင်းလဲမှုရှိခဲ့သော စာမျက်နှာများကို <strong>စာလုံးမဲ</strong> ဖြင့် ပြသထားသည်။",
        "rcfilters-preference-label": "လတ်တလောအပြောင်းအလဲများ၏ မွမ်းမံထားသောဗားရှင်းကို ဝှက်ရန်",
        "rcfilters-watchlist-preference-label": "စောင့်ကြည့်စာရင်း၏ မွမ်းမံထားသောဗားရှင်းကို ဝှက်ရန်",
+       "rcfilters-watchlist-preference-help": "စစ်ထုတ်ရှာဖွေခြင်း သို့မဟုတ် မီးမောင်းထိုးပြခြင်း လုပ်ဆောင်ချက်မပါဘဲ စောင့်ကြည့်စာရင်းကို ခေါ်ယူမည်။",
        "rcfilters-target-page-placeholder": "စာမျက်နှာနာမည် (သို့မဟုတ် ကဏ္ဍ) ရိုက်ထည့်ပါ",
        "rcnotefrom": "အောက်ပါတို့မှာ <strong>$3၊ $4</strong> မှစ၍ {{PLURAL:$5|ပြောင်းလဲမှု|ပြောင်းလဲမှုများ}} ဖြစ်သည်  (<strong>$1</strong> အထိ ပြထား)။",
        "rclistfromreset": "ရက်စွဲရွေးချယ်မှုအား ပြန်စရန်",
        "upload-description": "ဖိုင်ဖော်ပြချက်",
        "upload-options": "ဖိုင်တင်သည့် ရွေးချယ်မှုများ",
        "watchthisupload": "ဤဖိုင်အား စောင့်ကြည့်ရန်",
+       "upload-proto-error": "မမှန်ကန်သော လုပ်နည်းလုပ်ထုံး",
        "upload-file-error": "အတွင်းပိုင်းအမှား",
        "upload-misc-error": "upload တင်ရာတွင် အမည်မသိ အမှား",
        "upload-dialog-title": "ဖိုင်​တင်​ရန်​",
        "emailccme": "ကျွန်ုပ်ပို့လိုက်သော အီးမေးကော်ပီကို ကျွန်ုပ်ထံ ပြန်ပို့ပါ။",
        "emailsent": "အီးမေးပို့လိုက်ပြီ",
        "emailsenttext": "သင့်အီးမေးမက်ဆေ့ကို ပို့လိုက်ပြီးပြီ ဖြစ်သည်။",
+       "usermessage-summary": "စနစ်စာတို ချန်ထားခြင်း။",
        "usermessage-editor": "စနစ်မက်ဆင်ဂျာ",
        "watchlist": "စောင့်ကြည့်စာရင်း",
        "mywatchlist": "စောင့်ကြည့်စာရင်း",
        "blocklist-userblocks": "အကောင့်ပိတ်ပင်မှုများ ဝှက်",
        "blocklist-tempblocks": "ယာယီပိတ်ပင်မှုများ ဝှက်",
        "blocklist-addressblocks": "အိုင်ပီတစ်ခုတည်းပိတ်ပင်မှု ဝှက်",
+       "blocklist-type": "အမျိုးအစား:",
        "blocklist-type-opt-all": "အားလုံး",
        "blocklist-type-opt-partial": "တစ်စိတ်တစ်ပိုင်း",
        "blocklist-rangeblocks": "အကွာအဝေးလိုက် ပိတ်ပင်မှုများ ဝှက်",
index 741d7f8..de39a2e 100644 (file)
        "about": "Informasie",
        "article": "Artikel",
        "newwindow": "(niej vienster)",
-       "cancel": "Aofbreken",
+       "cancel": "Afbreaken",
        "moredotdotdot": "Meer...",
        "morenotlisted": "Disse lieste is niet kompleet...",
        "mypage": "Gebrukerszied",
        "externaldberror": "Der gung iets fout bie de externe authentisering, of je maggen je gebrukersprofiel niet bewarken.",
        "login": "Anmelden",
        "nav-login-createaccount": "Anmelden",
-       "logout": "Ofmelden",
+       "logout": "Afmelden",
        "userlogout": "Aofmelden",
        "notloggedin": "Neet an-emelded",
        "userlogin-noaccount": "Heb jy noch geen gebrukersname?",
        "publishpage": "Zied uutbrengen",
        "publishchanges": "Wiezigingen uutbrengen",
        "preview": "Naokieken",
-       "showpreview": "Bewarking naokieken",
-       "showdiff": "Verschil bekieken",
+       "showpreview": "Bewarking nåkyken",
+       "showdiff": "Verskil bekyken",
        "blankarticle": "<strong>Waorschuwing:</strong> de zied die'j anmaken willen is leeg.\nA'j noen weer op \"$1\" klikken, dan wördt de zied an-emaakt zonder enige inhoud.",
        "anoneditwarning": "<strong>Waorschuwing:</strong> je bin niet an-emeld.\nJoew IP-adres zal op-esleugen wörden a'j wiezigingen op disse zied anbrengen. A'j je eigen <strong>[$1 anmelden]</strong> of <strong>[$2 inschrieven]</strong> dan koemen joew bewarkingen onder joew gebrukersnaam te staon, samen mit aandere veurdelen.",
        "anonpreviewwarning": "''Je bin niet an-emeld.''\n''Deur de bewarking op te slaon wörden joew IP-adres op-esleugen in de ziedgeschiedenisse.''",
index 4adfca2..09d06dd 100644 (file)
        "autoblockedtext": "Uw IP-adres is automatisch geblokkeerd, omdat het gebruikt is door een andere gebruiker, die geblokkeerd is door $1.\nDe opgegeven reden is:\n\n:''$2''\n\n* Aanvang blokkade: $8\n* Einde blokkade: $6\n* Bedoeld te blokkeren: $7\n\nU kunt contact opnemen met $1 of een andere [[{{MediaWiki:Grouppage-sysop}}|beheerder]] om de blokkade te bespreken.\n\nU kunt geen gebruik maken van de functie \"{{int:emailuser}}\", tenzij u een geldig e-mailadres hebt opgegeven in uw [[Special:Preferences|voorkeuren]], en het gebruik van deze functie niet is geblokkeerd.\n\nUw huidige IP-adres is $3 en het blokkadenummer is #$5.\nVermeld alle bovenstaande gegevens als u ergens op deze blokkade reageert.",
        "systemblockedtext": "Uw gebruikersaccount of IP-adres is automatisch geblokkeerd door MediaWiki.\nDe opgegeven reden is:\n\n:<em>$2</em>\n\n* Aanvang blokkade: $8\n* Einde blokkade: $6\n* Bedoeld te blokkeren: $7\n\nUw huidige IP-adres is $3.\nVermeld alle bovenstaande gegevens als u ergens op deze blokkade reageert.",
        "blockednoreason": "geen reden opgegeven",
+       "blockedtext-composite": "Uw gebruikersaccount of IP-adres is geblokkeerd.\n\nDe opgegeven reden is:\n\n:<em>$2</em>\n\n* Aanvang blokkade: $8\n* Einde van de langste blokkade: $6\n\nUw huidige IP-adres is $3.\nVermeld alle bovenstaande gegevens als u ergens op deze blokkade reageert.",
+       "blockedtext-composite-reason": "Er zijn meerdere blokkades tegen uw account en/of IP-adres",
        "whitelistedittext": "U moet $1 om pagina's te bewerken.",
        "confirmedittext": "U moet uw e-mailadres bevestigen voor u kunt bewerken.\nVoer uw e-mailadres in en bevestig het via uw [[Special:Preferences|voorkeuren]].",
        "nosuchsectiontitle": "Deze subkop bestaat niet",
index 900278b..e9d817b 100644 (file)
        "printableversion": "ߓߐߞߏߣߊ߲߫ ߜߌ߬ߙߌ߲߬ߘߌ߬ߕߊ",
        "permalink": "ߛߘߌ߬ߜߋ߲߬ ߓߟߏߕߍ߰ߓߊߟߌ",
        "print": "ߜߌ߬ߙߌ߲߬ߘߌ߬ߟߌ",
-       "view": "ß\8a߬ ß\98ß\90ß\9eß\8a߬ß\99ß\8a߲߬",
+       "view": "ߦß\8c߬ß\98ß\8a߬ß\9fß\8c",
        "view-foreign": "ߊ߬ ߦߋ߫ ߦߊ߲߬ $1",
        "edit": "ߊ߬ ߢߟߊߞߎߘߦߊ߫",
        "edit-local": "ߕߌ߲߬ߞߎߘߎ߲ ߞߊ߲߬ߛߓߍߟߌ ߡߊߦߟߍ߬ߡߊ߲߫",
        "botpasswords-no-central-id": "ߖߐ߲߬ߛߊ߫ ߌ ߘߌ߫ ߓߏߕ ߕߊ߬ߡߌ߲߬ߞߊ߲ ߠߊߓߊ߯ߙߊ߫߸ ߌ ߞߊ߫ ߞߊ߲߫ ߞߊ߬ ߜߊ߲߬ߞߎ߲߬ߠߌ߲߬ ߕߊ߲ߓߊ߲ߓߐߣߍ߲ ߞߍ߫.",
        "botpasswords-existing": "ߕߋ߲߭ߕߋ߲߭ ߓߏߕ ߕߊ߬ߡߌ߲߬ߞߊ߲",
        "botpasswords-createnew": "ߓߏߕ ߕߊ߬ߡߌ߲߬ߞߊ߲߬ ߞߎߘߊ߫ ߛߌ߲ߘߌ߫",
+       "botpasswords-editexisting": "ߓߏߕ ߕߋ߲߬ߕߋ߲߬ ߕߊߡߌ߲ߞߊ߲ ߡߊߦߟߍ߬ߡߊ߲߬",
        "botpasswords-label-needsreset": "(ߕߊ߬ߡߌ߲߬ߞߊ߲ ߤߊ߬ߕߊ߬ߦߋ߬ߣߍ߲߫ ߡߝߊ߬ߟߋ߲߬ߠߌ߲ ߠߊ߫)",
        "botpasswords-label-appid": "ߓߏߕ ߕߐ߮:",
        "botpasswords-label-create": "ߊ߬ ߛߌ߲ߘߌ߫",
        "botpasswords-label-cancel": "ߊ߬ ߘߐߛߊ߬",
        "botpasswords-label-delete": "ߊ߬ ߖߏ߬ߛߌ߬",
        "botpasswords-label-resetpassword": "ߕߊ߬ߡߌ߲߬ߞߊ߲ ߡߊߦߟߍ߬ߡߊ߲߬",
+       "botpasswords-label-grants-column": "ߘߌ߬ߢߍ߬ ߓߘߊ߫ ߞߍ߫",
        "botpasswords-bad-appid": "ߓߏߕ ߕߐ߮  \"$1\" ߓߍ߲߬ ߣߍ߲߬ ߕߍ߫.",
        "botpasswords-insert-failed": "ߓߏߕ ߕߐ߮ ߟߊߘߏ߲߬ߠߌ߲ ߓߘߊ߫ ߗߌߙߏ߲߫  \"$1\" ߊ߬ ߕߎ߲߬ ߓߘߊ߫ ߟߊߘߏ߲߭ ߠߋ߬ ߓߊ߬؟",
        "botpasswords-update-failed": "ߓߏߕ ߕߐ߮ ߟߏ߲ߘߐߦߊߟߌ ߓߘߊ߫ ߗߌߙߏ߲߫  \"$1\" ߊ߬ ߓߘߊ߫ ߖߏ߬ߛߌ߫ ߟߋ߬ ߓߊ߬؟",
        "prefs-i18n": "ߡߊ߲߬ߕߏ߬ߕߍ߬ߦߊ߬ߟߌ",
        "prefs-signature": "ߞߟߊ߬ߣߐ߮",
        "prefs-dateformat": "ߕߎ߬ߡߊ߬ߘߊ ߖߙߎߡߎ߲",
+       "prefs-timeoffset": "ߕߎ߬ߡߊ ߘߐߓߍ߲߬",
        "prefs-advancedediting": "ߢߣߊߕߊߟߌ ߞߙߎߞߙߍ",
        "prefs-developertools": "ߟߊ߬ߥߙߎ߬ߞߌ߬ߟߊ ߖߐ߯ߙߊ߲ ߠߎ߬",
        "prefs-editor": "ߛߓߍߦߟߊ",
        "prefs-advancedwatchlist": "ߢߣߊߕߊߟߌ ߖߊ߲߬ߝߊ߬ߣߍ߲",
        "prefs-displayrc": "ߟߊ߬ߓߊ߰ߙߊ߬ߟߌ ߢߣߊߕߊߟߌ",
        "prefs-displaywatchlist": "ߟߊ߬ߓߊ߰ߙߊ߬ߟߌ ߢߣߊߕߊߟߌ",
+       "prefs-changesrc": "ߡߊ߬ߦߟߍ߬ߡߊ߲߬ߠߌ߲ ߓߘߊ߫ ߦߌ߬ߘߊ߬",
+       "prefs-changeswatchlist": "ߡߊ߬ߦߟߍ߬ߡߊ߲߬ߠߌ߲ ߓߘߊ߫ ߦߌ߬ߘߊ߬",
+       "prefs-pageswatchlist": "ߞߐߜߍ߫ ߜߋ߬ߟߎ߲߬ߣߍ߲ ߠߎ߬",
+       "prefs-tokenwatchlist": "ߖߐߟߐ߲ߞߐ",
+       "prefs-help-prefershttps": "ߟߊ߬ߝߌ߬ߛߦߊ߬ߟߌ ߣߌ߲߬ ߘߴߊ߬ ߝߏ߲߬ߝߏ߲ ߟߴߌ ߟߊ߫ ߜߊ߲߬ߞߎ߲߬ߠߌ߲ ߣߊ߬ߕߐ ߞߊ߲߬.",
+       "userrights": "ߟߊ߬ߓߊ߰ߙߊ߬ߟߊ ߤߊߞߍ",
+       "userrights-lookup-user": "ߟߊ߬ߓߊ߰ߙߊ߬ߟߊ ߘߏ߫ ߛߎߥߊ߲ߘߌ߫",
+       "userrights-user-editname": "ߟߊ߬ߓߊ߰ߙߊ߬ ߕߐ߮ ߘߏ߫ ߟߊߘߏ߲߬:",
+       "editusergroup": "ߞߙߎ߫ ߟߊߓߊ߯ߙߕߊ ߟߊߢߎ߲߫",
+       "editinguser": "{{GENDER:$1|ߟߊ߬ߓߊ߰ߙߊ߬ߟߊ}} ߟߊ߫ ߟߊ߬ߓߊ߰ߙߊ߬ߟߌ߬ ߤߊߞߍ ߡߊߦߟߍߡߊ߲ ߦߴߌ ߘߐ߫ <strong> [[User:$1|$1]]</strong> $2",
+       "viewinguserrights": "{{GENDER:$1|ߟߊ߬ߓߊ߰ߙߊ߬ߟߊ}} ߤߊߞߍ ߦߌ߬ߘߊ ߦߴߌ ߘߐ߫ <strong> [[User:$1|$1]]</strong> $2",
+       "userrights-editusergroup": "{{GENDER:$1|ߟߊ߬ߓߊ߰ߙߊ߬ߟߌ߬}} ߞߙߎ ߡߊߦߟߍ߬ߡߊ߲߫",
+       "userrights-viewusergroup": "{{GENDER:$1|ߟߊ߬ߓߊ߰ߙߊ߬ߟߌ߬}} ߞߙߎ ߡߊߦߟߍ߬ߡߊ߲ ߦߴߌ ߘߐ߫",
+       "saveusergroups": "{{GENDER:$1|ߟߊ߬ߓߊ߰ߙߊ߬ߟߌ߬}} ߞߙߎ ߟߊߞߎ߲߬ߘߎ߬",
+       "userrights-groupsmember": "ߛߌ߲߬ߝߏ߲ ߠߎ߬:",
+       "userrights-reason": "ߊ߬ ߛߊߓߎ:",
+       "userrights-changeable-col": "ߌ ߘߌ߫ ߛߋ߫ ߞߙߎ ߡߍ߲ ߠߎ߬ ߡߊߦߟߍ߬ߡߊ߲߬ ߠߊ߫",
+       "userrights-unchangeable-col": "ߌ ߕߴߛߋ߫ ߞߙߎ ߡߍ߲ ߠߎ߬ ߡߊߦߟߍ߬ߡߊ߲߬ ߠߊ߫",
+       "userrights-expiry-current": "ߊ߬ ߛߕߊ ߓߘߊ߫ ߝߊ߫ $1",
+       "userrights-expiry-none": "ߊ߬ ߛߕߊ ߡߊ߫ ߝߊ߫ ߡߎߣߎ߲߬",
+       "userrights-expiry": "ߊ߬ ߛߕߊ ߓߘߊ߫ ߝߊ߫:",
+       "userrights-expiry-existing": "ߕߋ߲߭ߕߋ߲߭ ߛߕߊߝߊ߫ ߕߎߡߊ: $3߸ $2",
+       "userrights-expiry-othertime": "ߕߎ߬ߡߊ߬ ߜߘߍ:",
+       "userrights-expiry-options": "ߕߟߋ߬ ߁: ߕߟߋ߬ ߁߸ ߞߎ߲߬ߢߐ߰ ߁: ߞߎ߲߬ߢߐ߰ ߁߸ ߞߊߙߏ߫ ߁: ߞߊߙߏ߫ ߁߸ ߞߊߙߏ߫ ߃: ߞߊߙߏ߫ ߃߸ ߞߊߙߏ߫ ߆: ߞߊߙߏ߫ ߆߸ ߛߊ߲߬ ߁: ߛߊ߲߬ ߁",
+       "group": "ߞߙߎ:",
+       "group-user": "ߟߊ߬ߓߊ߰ߙߊ߬ߟߊ",
+       "group-autoconfirmed": "ߟߊ߬ߓߊ߰ߙߊ߬ߟߊ߬ ߞߍߒߖߘߍߦߋ߫ ߟߊߛߙߋߦߊߣߍ߲",
        "group-bot": "ߓߏߕ",
        "group-sysop": "ߞߎ߲߬ߠߊ߬ߛߌ߰ߟߊ",
+       "group-all": "(ߊ߬ ߓߍ߯)",
+       "group-user-member": "{{GENDER:$1|ߟߊ߬ߓߊ߰ߙߊ߬ߟߊ}}",
+       "grouppage-user": "{{ns:project}}: ߟߊ߬ߓߊ߰ߙߊ߬ߟߊ",
        "grouppage-bot": "{{ns:project}}:ߓߏߕ",
        "grouppage-sysop": "{{ns:project}}:ߡߊ߬ߡߙߊ߬ߟߌ߬ߟߊ",
+       "right-read": "ߞߐߜߍ ߘߐߞߊ߬ߙߊ߲߬",
+       "right-edit": "ߞߐߜߍ ߡߊߦߟߍ߬ߡߊ߲߫",
+       "right-createpage": "ߞߐߜߍ ߘߏ߫ ߛߌ߲ߘߌ߫ (ߡߍ߲ ߕߍ߫ ߓߊ߬ߘߏ߬ߓߊ߬ߘߌ߬ߦߊ߬ ߞߐߜߍ ߝߋ߲߫ ߘߌ߫)",
+       "right-createtalk": "ߓߊ߬ߘߏ߬ߓߊ߬ߘߌ߬ߦߊ߬ ߞߐߜߍ ߛߌ߲ߘߌ߫",
+       "right-createaccount": "ߖߊ߬ߕߋ߬ߘߊ߬ ߟߊߓߊ߯ߙߕߊ߫ ߞߎߘߊ߫ ߛߌ߲ߘߌ߫",
        "right-writeapi": "ߛߓߍߟߌ API ߟߊߓߊ߯ߙߊ߫",
        "newuserlogpage": "ߖߊ߬ߕߋ߬ߘߊ߬ ߓߘߊ߫ ߟߊߞߊ߬ ߌ ߜߊ߲߬ߞߎ߲߬",
        "rightslog": "ߟߊ߬ߓߊ߰ߙߊ߬ߟߊ ߜߊ߲߬ߞߎ߲߬ ߢߊ߬ ߓߘߍ",
        "filehist-user": "ߟߊ߬ߓߊ߰ߙߊ߬ߟߊ",
        "filehist-dimensions": "ߛߎߡߊ߲ߘߐ",
        "filehist-comment": "ߞߊ߲߬ߝߐߟߌ",
-       "imagelinks": "ߞߐߕߐ߮ ߟߊߓߊ߯ߙߊ",
+       "imagelinks": "ߞߐߕߐ߮ ߟߊߓߊ߯ߙߊߟߌ",
        "linkstoimage": "ߞߐߕߐ߮ ߣߌ߲߬ {{PLURAL:$1|ߞߐߜߍ ߟߎ߬|$1 ߞߐߜߍ ߟߎ߬}}:",
        "linkstoimage-more": "ߞߐߕߐ߮ ߣߌ߲߬ $1 {{PLURAL:$1|page uses|pages use}} ߠߊߓߊ߯ߙߊߓߊ߮ ߞߊߛߌߦߊ߫.\nߛߙߍߘߍ ߢߌ߲߬ ߠߎ߬ ߦߋ߫ {{PLURAL:$1|first page|first $1 pages}} ߞߐߕߐ߮ ߣߌ߲߬ ߞߋߟߋ߲߫ ߠߊߓߊ߯ߙߊߓߊ߮ ߟߎ߬ ߛߙߍߘߍ ߟߋ߬ ߦߌ߬ߘߊ߬ ߟߊ߫.\nߛߘߌ߬ߜߋ߲߬ [[Special:WhatLinksHere/$2|full list]] ߓߟߏߡߊߞߊ߬ߣߍ߲ ߦߋ߫ ߦߋ߲߬.",
        "nolinkstoimage": " ߞߐߜߍ߫ ߛߌ߫ ߡߊ߫ ߞߐߕߐ߮ ߣߌ߲߬ ߠߊߓߊ߯ߙߊ߫ ߡߎߣߎ߲߬",
index d13d5d6..2e9a11d 100644 (file)
        "autoblockedtext": "Ten adres IP został zablokowany automatycznie, gdyż korzysta z niego inny użytkownik, zablokowany przez administratora $1.\nPowód blokady:\n\n:<em>$2</em>\n\n* Początek blokady: $8\n* Wygaśnięcie blokady: $6\n* Zablokowany został: $7\n\nMożesz skontaktować się z $1 lub jednym z pozostałych [[{{MediaWiki:Grouppage-sysop}}|administratorów]] w celu uzyskania informacji o blokadzie.\n\nNie możesz użyć funkcji „{{int:emailuser}}”, jeśli brak jest poprawnego adresu e‐mail w Twoich [[Special:Preferences|preferencjach]] lub jeśli taka możliwość została Ci zablokowana.\n\nTwój obecny adres IP to $3, a numer identyfikacyjny blokady to #$5.\nProsimy o podanie obu tych numerów przy wyjaśnianiu blokady.",
        "systemblockedtext": "Twoja nazwa użytkownika lub adres IP zostały automatycznie zablokowane przez MediaWiki.\nPodany powód to:\n\n:<em>$2</em>\n\n* Początek blokady: $8\n* Wygaśnięcie blokady: $6\n* Zamierzano zablokować: $7\n\nTwój obecny adres IP to $3.\nProsimy o dołączenie powyższych szczegółów w jakichkolwiek zadawanych pytaniach.",
        "blockednoreason": "nie podano przyczyny",
+       "blockedtext-composite": "<strong>Twoja nazwa użytkownika lub adres IP zostały zablokowane.</strong>\n\nPodany powód to:\n\n:<em>$2</em>\n\n* Początek blokady: $8\n* Wygaśnięcie blokady: $6\n\nTwój obecny adres IP to $3.\nProsimy o dołączenie powyższych szczegółów w jakichkolwiek zadawanych pytaniach.",
+       "blockedtext-composite-reason": "Na twoje konto i/lub adresy IP nałożono wiele blokad.",
        "whitelistedittext": "Musisz $1, by edytować strony.",
        "confirmedittext": "Edytowanie jest możliwe dopiero po zweryfikowaniu adresu e‐mail.\nPodaj adres e‐mail i potwierdź go w swoich [[Special:Preferences|ustawieniach użytkownika]].",
        "nosuchsectiontitle": "Nie można znaleźć sekcji",
index 8128d97..dff37f4 100644 (file)
        "autoblockedtext": "O seu endereço IP foi bloqueado de forma automática porque foi utilizado recentemente por outro usuário, o qual foi bloqueado por $1.\nO motivo apresentado foi:\n\n:<em>$2</em>\n\n* Início do bloqueio: $8\n* Expiração do bloqueio: $6\n* Destinatário do bloqueio: $7\n\nPode contactar $1 ou outro [[{{MediaWiki:Grouppage-sysop}}|administrador]] para discutir o bloqueio.\n\nNote que para utilizar a funcionalidade \"{{int:emailuser}}\" precisa de ter um endereço de e-mail válido nas suas [[Special:Preferences|preferências]] e de não lhe ter sido bloqueado o uso desta funcionalidade.\n\nO seu endereço IP neste momento é $3 e a identificação (ID) do bloqueio é #$5.\nInclua todos os detalhes acima em quaisquer contatos relacionados com este bloqueio, por favor.",
        "systemblockedtext": "O seu nome de usuário ou endereço IP foram bloqueados automaticamente pelo MediaWiki.\nO motivo fornecido é:\n\n:<em>$2</em>\n\n* Início do bloqueio: $8\n* Expiração do bloqueio: $6\n* Destinatário do bloqueio: $7\n\nO seu endereço IP atual é $3.\nInclua todos os detalhes acima em quaisquer contatos sobre este assunto, por favor.",
        "blockednoreason": "sem motivo especificado",
+       "blockedtext-composite": "<strong>Seu nome de usuário ou endereço IP foi bloqueado.</strong>\n\nO motivo fornecido é:\n\n:<em>$2</em>.\n\n* Início do bloqueio: $8\n* Expiração do bloqueio mais longo: $6\n\nSeu endereço IP atual é $3.\nPor favor inclua todos os detalhes acima em qualquer questão que você faça.",
+       "blockedtext-composite-reason": "Existem vários bloqueios contra sua conta e/ou endereço IP",
        "whitelistedittext": "Você precisa $1 para poder editar páginas.",
        "confirmedittext": "Você precisa confirmar o seu endereço de e-mail antes de começar a editar páginas.\nPor favor, introduza um e valide-o através das suas [[Special:Preferences|preferências de usuário]].",
        "nosuchsectiontitle": "Não foi possível encontrar a seção",
index e0da190..507bbfd 100644 (file)
        "autoblockedtext": "Text displayed to automatically blocked users.\n\n\"email this user\" should be consistent with {{msg-mw|Emailuser}}.\n\nParameters:\n* $1 - the blocking sysop (with a link to his/her userpage)\n* $2 - the reason for the block (in case of autoblocks: {{msg-mw|autoblocker}})\n* $3 - the current IP address of the blocked user\n* $4 - (Unused) the blocking sysop's username (plain text, without the link). Use it for GENDER.\n* $5 - the unique numeric identifier of the applied autoblock\n* $6 - the expiry of the block\n* $7 - the intended target of the block (what the blocking user specified in the blocking form)\n* $8 - the timestamp when the block started\nSee also:\n* {{msg-mw|Grouppage-sysop}}\n* {{msg-mw|Blockedtext|notext=1}}\n* {{msg-mw|Systemblockedtext|notext=1}}",
        "systemblockedtext": "Text displayed to requests blocked by MediaWiki configuration.\n\n\"email this user\" should be consistent with {{msg-mw|Emailuser}}.\n\nParameters:\n* $1 - (Unused) A dummy user attributed as the blocker, possibly as a link to a user page.\n* $2 - the reason for the block\n* $3 - the current IP address of the blocked user\n* $4 - (Unused) the dummy blocking user's username (plain text, without the link).\n* $5 - A short string indicating the type of system block.\n* $6 - the expiry of the block\n* $7 - the intended target of the block\n* $8 - the timestamp when the block started\nSee also:\n* {{msg-mw|Grouppage-sysop}}\n* {{msg-mw|Blockedtext|notext=1}}\n* {{msg-mw|Autoblockedtext|notext=1}}",
        "blockednoreason": "Substituted with <code>$2</code> in the following message if the reason is not given:\n* {{msg-mw|cantcreateaccount-text}}.\n{{Identical|No reason given}}",
+       "blockedtext-composite": "Text displayed to requests blocked by more than one block.\n\n\"email this user\" should be consistent with {{msg-mw|Emailuser}}.\n\nParameters:\n* $1 - (Unused) A dummy user attributed as the blocker, possibly as a link to a user page.\n* $2 - the reason for the block\n* $3 - the current IP address of the blocked user\n* $4 - (Unused) the dummy blocking user's username (plain text, without the link).\n* $5 - (Unused) placeholder for the block ID.\n* $6 - the expiry of the block with the longest duration\n* $7 - (Unused) the intended target of the block\n* $8 - the timestamp when the block started\nSee also:\n* {{msg-mw|Systemblockedtext|notext=1}}",
+       "blockedtext-composite-reason": "Reason given to blocked users who are affected by more than one block.\n\nSee also:\n* {{msg-mw|blockedtext-composite}}",
        "whitelistedittext": "Used as error message. Parameters:\n* $1 - a link to [[Special:UserLogin]] with {{msg-mw|loginreqlink}} as link description\n* $2 - an URL to the same\n\nSee also:\n* {{msg-mw|Nocreatetext}}\n* {{msg-mw|Uploadnologintext}}\n* {{msg-mw|Loginreqpagetext}}",
        "confirmedittext": "Used as error message.",
        "nosuchsectiontitle": "Used as error message when the user has attempted to edit a nonexistent section.",
index c60aecb..fe264b6 100644 (file)
        "virus-scanfailed": "condrolle fallite (codece $1)",
        "virus-unknownscanner": "antivirus scanusciute:",
        "logouttext": "'''Tu tè scollegate.'''\n\nNote Bbuene ca certe pàggene ponne condinuà a essere viste cumme ce tu ste angore collegate, fine a quanne a cache d'u browser no se sdevache.",
+       "logout-failed": "Non ge puè assè mò: $1",
        "cannotlogoutnow-title": "Non ge puè assè mò",
        "cannotlogoutnow-text": "Non ge puè assè quanne ste ause $1.",
        "welcomeuser": "Bovègne, $1!",
        "action-changetags": "Aggiunge e live arbitrariamende tag sus a le revisiune individuale e vôsce de l'archivije",
        "action-deletechangetags": "scangille le tag da 'u database",
        "action-purge": "aggiorne sta pàgene",
+       "action-editinterface": "cange l'inderfacce utende",
+       "action-editusercss": "cange 'u CSS de l'otre utinde",
+       "action-edituserjson": "cange 'u JSON de l'otre utinde",
+       "action-edituserjs": "cange 'u JavaScript de l'otre utinde",
+       "action-editsitecss": "cange 'u CSS d'u site",
+       "action-editsitejson": "cange 'u JSON d'u site",
+       "action-editsitejs": "cange 'u JavaScript d'u site",
+       "action-editmyusercss": "cange le file tune de CSS",
+       "action-editmyuserjson": "cange le file tune de JSON",
+       "action-editmyuserjs": "cange le file tune de JavaScript",
+       "action-viewsuppressed": "'ndruche le revisiune scunnute da tutte le utinde",
+       "action-hideuser": "bluecche 'nu cunde utende, scunnènnele da 'u pubbliche",
+       "action-ipblock-exempt": "zumbe le blocche de l'IP, auto blocche e le blocche a indervalle",
+       "action-unblockself": "sbluecche da sule",
+       "action-noratelimit": "non g'à state tuccate da le limite de le pundegge",
+       "action-reupload-own": "sovrascrive 'nu file esistende carichete da quacchedune",
+       "action-nominornewtalk": "no scè ausanne le cangiaminde stuèdeche jndr'à le pàggene de le 'ngazzaminde quanne lasse messagge nuève",
+       "action-markbotedits": "marche le cangiaminde annullate cumme cangiaminde de bot",
+       "action-patrolmarks": "'ndruche le cangiaminde recende marcate cumme a condrollate",
+       "action-override-export-depth": "l'esportazione de pàggene inglude pàggene collegate 'mbonde a 'na profonnetà de 5",
+       "action-suppressredirect": "no scè ccrejanne 'nu ridirezionamende da 'u nome vecchije quanne spueste 'na pàgene",
        "nchanges": "$1 {{PLURAL:$1|cangiaminde|cangiaminde}}",
        "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|da l'urtema visite}}",
        "enhancedrc-history": "cunde",
index a852dcc..43629e3 100644 (file)
        "autoblockedtext": "Ваш IP-адрес автоматически заблокирован в связи с тем, что он ранее использовался кем-то из участников, заблокированных администратором $1. \nБыла указана следующая причина блокировки:\n\n: «$2».\n\n* Начало блокировки: $8\n* Окончание блокировки: $6\n* Цель блокировки: $7\n\nВы можете связаться с $1 или любым другим [[{{MediaWiki:Grouppage-sysop}}|администратором]], чтобы обсудить блокировку.\n\nОбратите внимание, что вы не сможете использовать функцию «{{int:emailuser}}», если в своих [[Special:Preferences|персональных настройках]] не задали или не подтвердили корректный адрес электронной почты, или если ваша блокировка включает запрет отправки писем подобным образом.\n\nВаш IP-адрес — $3, идентификатор блокировки — #$5.\nПожалуйста, указывайте эти сведения в любых своих обращениях.",
        "systemblockedtext": "Ваше имя участника или IP-адрес были автоматически заблокированы MediaWiki.\nУказана следующая причина:\n\n:<em>$2</em>\n\n* Начало блокировки: $8\n* Окончание блокировки: $6\n* Цель блокировки: $7\n\nВаш текущий IP-адрес $3.\nПожалуйста, указывайте все эти сведения в любых своих обращениях.",
        "blockednoreason": "причина не указана",
+       "blockedtext-composite": "<strong>Ваше имя участника или IP-адрес были заблокированы.</strong>\nУказана следующая причина:\n\n:<em>$2</em>\n\n* Начало блокировки: $8\n* Окончание блокировки: $6\n\nВаш текущий IP-адрес $3.\nПожалуйста, указывайте все эти сведения в любых своих обращениях.",
+       "blockedtext-composite-reason": "Есть несколько блокировок вашей учётной записи и/или IP-адреса",
        "whitelistedittext": "Вы должны $1 для изменения страниц.",
        "confirmedittext": "Вы должны подтвердить свой адрес электронной почты перед правкой страниц.\nПожалуйста, введите и подтвердите свой адрес электронной почты в своих [[Special:Preferences|персональных настройках]].",
        "nosuchsectiontitle": "Невозможно найти раздел",
index a17018b..ab3e9f5 100644 (file)
        "tog-numberheadings": "Numarazioni otomàtigga di li tìturi di sezzioni",
        "tog-editondblclick": "Mudìfigga di li pàgini attrabessu dóppiu clic",
        "tog-editsectiononrightclick": "Mudìfigga di li sezzioni attrabessu lu clic dresthu i' lu tìturu",
-       "tog-watchcreations": "Aggiungi li pàgini criaddi a l'abbaidaddi ippiziari",
-       "tog-watchdefault": "Aggiungi li pàgini mudìfiggaddi a l'abbaidaddi ippiziari",
-       "tog-watchmoves": "Aggiungi li pàgini ippusthaddi a l'abbaidaddi ippiziari",
-       "tog-watchdeletion": "Aggiungi li pàgini canzilladdi a l'abbaidaddi ippiziari",
+       "tog-watchcreations": "Aggiungi li pàgini criaddi e l'archìbii carriggaddi da me a l'abbaiddaddi ippiziari.",
+       "tog-watchdefault": "Aggiungi li pàgini e l'archìbii mudifiggaddi da me a l'abbaiddaddi ippiziari.",
+       "tog-watchmoves": "Aggiungi li pàgini e li schedarii ippusthaddi da me a l'abbaiddaddi ippiziari.",
+       "tog-watchdeletion": "Aggiungi li pàgini e li schedarii chi àggiu canzilladdu a l'abbaiddaddi ippiziari.",
+       "tog-watchuploads": "Aggiugnì nobi archìbii chi carriggu a l'abbaiddaddi ippiziari méi",
        "tog-minordefault": "Indica tutti li mudìfigghi cumenti 'minori' in otomàtiggu",
        "tog-previewontop": "Musthra l'antiprimma sobra la casella di mudìfigga",
        "tog-previewonfirst": "Musthra l'antiprimma pa la primma mudìfigga",
-       "tog-enotifwatchlistpages": "Signàrami pa postha erettrònica li mudìfigghi a li pàgini abbaidaddi",
+       "tog-enotifwatchlistpages": "Signàrami pa postha erettrònica li mudìfigghi a li pàgini o schedarii abbaiddaddi.",
        "tog-enotifusertalkpages": "Signàrami pa postha erettrònica li mudìfigghi a la me' pàgina di dischussioni",
-       "tog-enotifminoredits": "Signàrami pa postha erettrònica puru li mudìfigghi minori",
+       "tog-enotifminoredits": "Signàrami pa postha erettrònica puru li mudìfigghi minori.",
        "tog-enotifrevealaddr": "Rivera lu me' indirizzu di postha erettrònica i' l'imbasciaddi d'avvisu",
        "tog-shownumberswatching": "Musthra lu nùmaru d'utenti ch'àni la pàgina abbaidadda",
-       "tog-oldsig": "Fimma esisthenti",
+       "tog-oldsig": "Fimma esisthenti.",
        "tog-fancysig": "Interpreta i cumandi wiki i' la fimma (chena cullegaumentu otomatiggu)",
-       "tog-uselivepreview": "Attiba la funzioni ''Live preview'' (dumanda JavaScript; ippirimintari)",
+       "tog-uselivepreview": "Attiba la funzioni ''Live preview''. (dumanda JavaScript; ippirimintari)",
        "tog-forceeditsummary": "Dumanda cunfèimma si l'oggettu di la mudìfigga è bioddu",
        "tog-watchlisthideown": "Cua li me' mudìfigghi i' l'abbaidaddi ippiziari",
        "tog-watchlisthidebots": "Cua li mudìfigghi di li bot i' l'abbaidaddi ippiziari",
        "tog-watchlisthideminor": "Cua li mudìfigghi minori i' l'abbaidaddi ippiziari",
+       "tog-watchlisthideliu": "Cuà mudìfigghi da utenti intraddi di la listha di pàgini sottu osseivvazioni",
+       "tog-watchlistreloadautomatically": "Sempri turrà a carriggà la listha di li pàgini sottu osseivvazioni candu un filthru è ciambaddu (dumanda JavaScript)",
        "tog-ccmeonemails": "Inviammi una còpia di l'imbasciaddi ippididdi a l'althri utenti",
        "tog-diffonly": "No visuarizzà lu cuntinuddu di la pàgina daboi lu cunfrontu tra versioni",
        "tog-showhiddencats": "Musthrà li categuri cuaddi",
index 5b15466..48bf8f8 100644 (file)
        "nchanges": "$1 {{PLURAL:$1|izmjena|izmjene|izmjena}}",
        "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|izmjena od Vaše posljedne posjete}}",
        "enhancedrc-history": "historija",
-       "recentchanges": "Nedavne izmjene / Скорашње измене",
+       "recentchanges": "Nedavne promjene / Недавне промене",
        "recentchanges-legend": "Postavke za Nedavne promjene",
        "recentchanges-summary": "Na ovoj stranici možete pratiti nedavne izmjene.",
        "recentchanges-noresult": "Bez promjena tokom cijelog perioda koji ispunjava ove kriterije.",
index 77ae281..fad8fb8 100644 (file)
        "blockedtext-partial": "<strong>Vaše uporabniško ime ali IP-naslov je bil blokiran pred spreminjanjem te strani. Še vedno lahko urejate druge strani na tem wikiju.</strong> Polne podrobnosti blokade si lahko ogledate na [[Special:MyContributions|prispevkih računa]].\n\nBlokado je opravil(-a) $1.\n\nPodani razlog je <em>$2</em>.\n\n* Začetek blokade: $8\n* Potek blokade: $6\n* Blokirani uporabnik: $7\n* ID blokade #$5",
        "blockedtext": "<strong>Urejanje z vašim uporabniškim imenom oziroma IP-naslovom je onemogočeno.</strong>\n\nBlokiral vas je $1.\nPodani razlog je <em>$2</em>.\n\n* Začetek blokade: $8\n* Potek blokade: $6\n* Blokirani uporabnik: $7\n\nO blokiranju se lahko pogovorite z uporabnikom/-co $1 ali katerim drugim [[{{MediaWiki:Grouppage-sysop}}|administratorjem]].\nVedite, da lahko ukaz »{{int:emailuser}}« uporabite le, če ste v [[Special:Preferences|nastavitvah]] vpisali in potrdili svoj elektronski naslov in ta ni blokiran.\nVaš IP-naslov je $3, številka blokade pa #$5.\nProsimo, vključite ju v vse morebitne poizvedbe.",
        "autoblockedtext": "Vaš IP-naslov je bil samodejno blokiran, saj je bil uporabljen s strani drugega uporabnika, ki ga je blokiral $1.\nRazlog za to je bil naslednji:\n\n:<em>$2</em>\n\n* Začetek blokade: $8\n* Konec blokade: $6\n* Blokirani uporabnik: $7\n\nKontaktirate lahko $1 ali katerega od drugih [[{{MediaWiki:Grouppage-sysop}}|administratorjev]], da razpravljate o blokadi.\n\nVedite, da lahko funkcijo »{{int:emailuser}}« uporabljate le, če ste v svoje [[Special:Preferences|uporabniške nastavitve]] vnesli veljaven e-poštni naslov, in vam njena uporaba ni bila preprečena.\n\nVaš trenutni IP-naslov je $3, ID blokiranja pa #$5. Prosimo, vključite ta ID v vsako zastavljeno vprašanje.",
-       "systemblockedtext": "Vaše uporabniško ime ali IP-naslov je MediaWiki samodejn blokiral.\nPodani razlog je:\n\n:<em>$2</em>\n\n* Začetek blokade: $8\n* Potek blokade: $6\n* Blokirani uporabnik: $7\n\nVaš trenutni IP-naslov je $3.\nProsimo, da v svoje poizvedbe vključite vse zgornje podatke.",
+       "systemblockedtext": "Vaše uporabniško ime ali IP-naslov je MediaWiki samodejno blokiral.\nPodani razlog je:\n\n:<em>$2</em>\n\n* Začetek blokade: $8\n* Potek blokade: $6\n* Blokirani uporabnik: $7\n\nVaš trenutni IP-naslov je $3.\nProsimo, da v svoje poizvedbe vključite vse zgornje podatke.",
        "blockednoreason": "razlog ni podan",
+       "blockedtext-composite": "<strong>Vaše uporabniško ime ali IP-naslov je bil blokiran.</strong>\n\nPodani razlog je:\n\n:<em>$2</em>\n\n* Začetek blokade: $8\n* Potek najdaljše blokade: $6\n\nVaš trenutni IP-naslov je $3.\nProsimo, da v svoje poizvedbe vključite vse zgornje podatke.",
+       "blockedtext-composite-reason": "Za vaš račun in/ali IP-naslov je nastavljenih več blokad.",
        "whitelistedittext": "Za urejanje strani se morate $1.",
        "confirmedittext": "Pred urejanjem strani morate potrditi svoj e-poštni naslov.\nProsimo, da ga z uporabo [[Special:Preferences|uporabniških nastavitev]] vpišete in potrdite.",
        "nosuchsectiontitle": "Ne najdem razdelka",
index abaa944..ff50d0b 100644 (file)
        "undelete-revision": "Revision scancelà de la pagina $1 (inserìa su $4 el $5) de $3:",
        "undeleterevision-missing": "Revision mìa valida o mancante. O el colegamento no'l xe mìa giusto, opure la revision la xe stà zà ripristinà o eliminà da l'archivio.",
        "undelete-nodiff": "No xe stà catà nissuna revision precedente.",
-       "undeletebtn": "RIPRISTINA!",
+       "undeletebtn": "Ripristina",
        "undeletelink": "varda/ripristina",
        "undeleteviewlink": "varda",
        "undeleteinvert": "Inverti selession",
index 1248a58..d581da9 100644 (file)
        "returnto": "Padà sí $1.",
        "tagline": "Lát'ọwọ́ {{SITENAME}}",
        "help": "Ìrànlọ́wọ́",
+       "help-mediawiki": "Ìrànwọ́ nípa MediaWiki",
        "search": "Àwárí",
+       "search-ignored-headings": "#<!-- fi ìlà yìí sílẹ̀ bó ṣe wà --> <pre>\n# Àwọn àkọlé tí ìwárí kò ní kọbiara sí.\n# Àwọn àtúnṣe tuntun yíò hàn láìpẹ́ lẹ́yìn tí àkọlé bá ti jẹ́ títòjọ.\n# Ẹ ṣe itúntòjọ ojúewé pẹ̀lu àtúnṣe agbòfo.\n# Bí ìlàkọ rẹ̀ yíò ṣe rí nìyí:\n# * Ohun gbogbo láti àmì-lẹ́tà \"#\" títí dé òpin oríìlà jẹ́ àròyé. \n# * Gbogbo oríilà aláìlófo jẹ́ àkọlé gangan tí kò ní kọbiara sí, lẹ́tà gbàngbà àti ohun gbogbo.\nÌtọ́kasí\nÀwọn ìjápọ̀ òde\nẸ tun wo\n#</pre> <!-- fi ìlà yìí sílẹ̀ bó ṣe wà -->",
        "searchbutton": "Àwárí",
        "go": "Rìnsó",
        "searcharticle": "Lọ",
        "laggedslavemode": "'''Ìkìlọ̀:''' Ojúewé náà le mọ́ nìí àwọn àtúnṣe tuntun.",
        "readonly": "Títìpa ibùdó dátà",
        "enterlockreason": "Ẹ ṣàlàyé ìtìpa náà, àti ìgbàtí ẹ rò pé ìtìpa náà yíò kúrò.",
-       "readonlytext": "Ibùdó dátà jẹ́ títìpa sí àwọn ìkówọlé tuntun àti sí àwọn àtúnṣe míràn, bóyá fún ìtọ́jú ibùdó dátà gbogbo ìgbà, lẹ́yìn èyí yíò padà sí ní ṣiṣẹ́.\n\nOlùmójútó tó tìípa ṣe àlàyé yìí: $1",
+       "readonlytext": "Ibùdó dátà tijẹ́ títìpa lásìkò yìí sí àwọn ìkówọlé tuntun àti sí àwọn àtúnṣe míràn, bóyá fún ìṣètọ́jú ibùdó dátà gbogbo ìgbà, lẹ́yìn èyí yíò padà sí ní ṣiṣẹ́.\n\nOlùmójútó tó tìípa ṣe àlàyé yìí: $1",
        "missing-article": "Ibùdó dátà kò rí ìkọ̀wé fún ojúewé kan tóyẹ kí ó rí, pẹ̀lú orúkọ \"$1\" $2.\n\nOhun tó ún fa èyí ní ìtẹ̀lé ìjapọ̀ \"ìyàtọ́\" tótipẹ́ tàbí ìjápọ̀ ìtàn ojúewé tí a ti parẹ́.\n\nTí kì bá ṣe bẹ́ẹ̀, ó lè jẹ́ pé ẹ ti rí àsìṣe nínú atòlànà kọ̀mpútà náà.\nẸjọ̀wọ́ ẹ fi èyí tó [[Special:ListUsers/sysop|alámùójútó]] kan létí, kí ẹ sí mọ́ gbàgbé láti fúun ní URL ọ̀hún.",
        "missingarticle-rev": "(àtúnyẹ̀wò#: $1)",
        "missingarticle-diff": "(Ìyàtọ̀: $1, $2)",
        "badarticleerror": "Ìgbéṣẹ̀ yìí kò ṣe é ṣe lórí ojúewé yìí.",
        "cannotdelete": "Ojúewé tàbí fáìlì \"$1\" kò ṣe é parẹ́.\nOníṣe mìíràn le ti paárẹ́.",
        "cannotdelete-title": "Kò le pa ojúewè \"$1\" rẹ́",
+       "delete-scheduled": "Ojúewé \"$1\" ti jẹ́ pípètò fún ìparẹ́.\nẸ jọ̀wọ́ ẹ mú sùúrù.",
        "delete-hook-aborted": "Hook ti ṣe ìdádúró ìparẹ́.\nKò ṣe àlàyé kankan.",
+       "no-null-revision": "Àtùnyẹ́wò agbòfo fún ojúewé \"$1\" kò ṣe é dásílẹ̀",
        "badtitle": "Àkọ́lé búburú",
        "badtitletext": "Àkọlé ojúewé tí ẹ bèrè fún kò ní ìbáramu, jẹ́ òfo, tàbí áṣìṣe wà nínú ìjápọ̀ àkọlé láàrin èdè tàbí láàrin wiki.\nÓ ṣe é ṣe kó jẹ́pé ó ní ìkan tàbí ọ̀pọ̀ àmi-lẹ́tà tí kò ṣe é lò nínú àkọlé.",
+       "title-invalid-empty": "Àkọlé ojúewé ajẹ́títọrọ ní òfo tàbí ó ní orúkọ fún orúkọàyè nìkàn.",
+       "title-invalid-utf8": "Àkọlé ojúewé ajẹ́títọrọ ní ìtèléùntèlé UTF-8 tí kò yẹ.",
+       "title-invalid-interwiki": "Àkọlé ojúewé ajẹ́títọrọ ní ìjápọ̀ interwiki tí kò ṣe é lò nìnú àkọlé.",
+       "title-invalid-talk-namespace": "Àkọlé ojúewé ajẹ́títọrọ tọ́ka sí ojúewé ọ̀rọ̀ tí kò sí.",
+       "title-invalid-characters": "Àkọlé ojúewé ajẹ́títọrọ ní àwọn àmì-lẹ́tà tí kò yẹ: \"$1\".",
        "perfcached": "Ìwònyí jẹ́ dátà láti inú cache nítoríẹ̀ ó le mọ́ jẹ̀ẹ́ tuntun. Ó pọ̀jùlọ {{PLURAL:$1|èsì kan|èsì $1}} wà nínú cache.",
        "perfcachedts": "Ìwònyí jẹ́ dátà láti inú cache, ọjọ́ tí a ṣe àtúnṣe rẹ̀ gbẹ̀yìn ni $1. Ó pọ̀jùlọ {{PLURAL:$4|èsì kan|èsì $4}} wà nínú cache.",
        "querypage-no-updates": "Àtúnṣe sí ojúewé yìí kò ṣe é ṣe lọ́wọ́lọ́wọ́.\nÀwọn ìpèsè tuntun kò ní hàn báyìí ná.",
        "sig_tip": "Ìtọwọ́bọ̀wé yín pẹ̀lú àsìkò àti déètì",
        "hr_tip": "Ìlà gbọlọjọ (ẹ lọ̀ọ́ pẹ̀lú àkíyèsì)",
        "summary": "Àkótán:",
-       "subject": "Orí ọ̀rọ̀/àkọlé:",
+       "subject": "Ìdálé-ọ̀rọ̀:",
        "minoredit": "Àtúnṣe kékeré nìyí",
        "watchthis": "M'ójútó ojúewé yìí",
        "savearticle": "Ìdásí ojúewé",
+       "savechanges": "Ìfipamọ́ àtúnṣe",
        "publishpage": "Ṣàtẹ̀jáde ojú ewé",
        "publishchanges": "Ṣàtẹ̀jáde àtúnṣe",
+       "savearticle-start": "Ìfipamọ́ ojúewé...",
+       "savechanges-start": "Ìfipamọ́ àtúnṣe...",
+       "publishpage-start": "Ìtẹ̀jáde àtúnṣe...",
+       "publishchanges-start": "Ìtẹ̀jáde àtúnṣe...",
        "preview": "Àyẹ̀wò",
        "showpreview": "Àkọ́yẹ̀wò",
        "showdiff": "Ìfihàn àwọn àtúnṣe",
+       "blankarticle": "<strong>Ìkìlọ̀:</strong> Ojúewé tí ẹ̀ úndá kò ní ùnkankan nínú.\nTí ẹ bá tún tẹ klik \"$1\", ojúewé náà yíò jẹ́dídá sílẹ̀ láì ní ùnkankan nínú.",
        "anoneditwarning": "<strong>Ìkìlọ̀:</strong> Ẹ kò tíì wọlé.\nÀdírẹ́ẹ̀sì IP yín yíò hàn jáde tí ẹ bá ṣe àtùnṣe. Tí ẹ bá <strong>[$1 wọlé]</strong> tàbí <strong>[$2 dá àkópamọ́]</strong>, àwọn àtúnṣe yín yíò hàn pẹ̀lú orúkọ-oníṣe yín, pẹ̀lú àwọn ànfàní míràn.",
        "anonpreviewwarning": "''Ẹ kò tíì wọlé. Àdírẹ́ẹ̀sì IP yín yíò jẹ́ kíkọsílẹ̀ sínú ìwé ìtàn àtúnṣe ojúewé yìí tí ẹ bá ṣàmúpamọ́ rẹ̀.''",
        "missingsummary": "'''Ìránlétí:''' Ẹ kò pèsè àkótán fún àtúnṣe yìí\nTí ẹ bá tẹ Ìmúpamọ́ lẹ́ẹ̀kansi, àtúnṣe yín yíò jẹ̀ mímúpamọ́ láìní kankan.",
+       "selfredirect": "<strong>Ìkìlọ̀:</strong> Ẹ̀ ún ṣàtúnjúwe ojúewé yìí sí ara rẹ̀.\nÓ le jẹ́ pé ọ̀tọ̀ nibi tí ẹ fẹ́ ṣàtúnjúwe rẹ̀ sí, tàbí pé ẹ̀ ún ṣàtúnṣe ojúewé ọ̀tọ̀.\nTí ẹ bá tún tẹ klik \"$1\", àtúnjúwe náà yíò jẹ́ dídá sílẹ̀.",
        "missingcommenttext": "Jọ̀wọ́ fi èrò ọkàn rẹ sílẹ̀.",
        "missingcommentheader": "'''Ìránlétí:''' Ẹ kò pèsè àkọlé/oríọ̀rọ̀ kankan fún àríwí yìí.\nTí ẹ bá tẹ \"$1\" lẹ́ẹ̀kansi, àtúnṣe yín yíò jẹ́ mímúpamọ́ láìní kankan.",
        "summary-preview": "Àkọ́yẹ̀wò àkótán àtúnṣe:",
        "subject-preview": "Àkọ́yẹ̀wò àkọlé ọ̀rọ̀:",
+       "previewerrortext": "Àsìṣe kan ṣẹlẹ̀ nígbà tí à ún gbìyànjú láti ṣàtúngbéyẹ̀wò àwọn àtúnṣe yín.",
        "blockedtitle": "Ìdínà oníṣe",
+       "blocked-email-user": "<strong>Orúkọ oníṣe yín tijẹ́ dídílọ́nà láti fi email ránṣẹ́. Ẹ sì le ṣàtùnṣe àwọn ojúewé míràn lórí wiki yìí.</strong> Ẹ lè wo gbogbo ẹ̀kúnrẹ́rẹ́ ìdínà náà nínú [[Special:MyContributions|àwọn àfikún àdápamọ́]].\n\nÌdínà náà wá látọwọ́ $1.\n\nÌdíẹ̀ tó sọ ni <em>$2</em>.\n\n* Ìbẹ̀rẹ̀ ìdínà: $8\n* Ìparí ìdínà: $6\n* Ẹni tí a fẹ́ dínà: $7\n* ID ìdínà #$5",
+       "blockedtext-partial": "<strong>Orúkọ oníṣe yín tàbí àdírẹ́ẹ̀sì IP yín tijẹ́ dídílọ́nà láti ṣàtúnṣe sí ojúewé yìí. Ẹ sì le ṣàtùnṣe àwọn ojúewé míràn lórí wiki yìí.</strong> Ẹ lè wo gbogbo ẹ̀kúnrẹ́rẹ́ ìdínà náà nínú [[Special:MyContributions|àwọn àfikún àdápamọ́]].\n\nÌdínà náà wá látọwọ́ $1.\n\nÌdíẹ̀ tó sọ ni <em>$2</em>.\n\n* Ìbẹ̀rẹ̀ ìdínà: $8\n* Ìparí ìdínà: $6\n* Ẹni tí a fẹ́ dínà: $7\n* ID ìdínà #$5",
        "blockedtext": "<strong>Orúkọ oníṣe yín tàbí àdírẹ́sì IP yín ti jẹ́ dídílọ́nà.</strong>\n\n$1 ni ó ṣe ìdínà.\nÌdí tó fun ni <em>$2</em>.\n\n* Ìbẹ̀rẹ̀ ìdínà: $8\n* Òpin ìdínà: $6\n* Ẹni tí a fẹ́ dínà: $7\n\nẸ ṣ'èránṣẹ́ sí $1 tàbí [[{{MediaWiki:Grouppage-sysop}}|alámùójútó]] mìíràn láti fọ̀rọ̀wérọ̀ lórí ìdínà ọ̀ún.\nẸ kò le è lo \"{{int:emailuser}}\" àyàfi tí àdírẹ́sì e-mail tó dájú bá wà ní [[Special:Preferences|àwọn ìfẹ́ràn àpamọ́]] yín tí wọn kò sì ti dínà yín láti lò ó.\nÀdírẹ́sì IP yín lọ́wọ́lọ́wọ́ ni $3, bẹ́ ẹ̀ sì ni ID fún ìdínà yín ni #$5.\nẸ jọ̀wọ́ ẹ fi gbogbo ẹ̀kúnrẹ́rẹ́ òkè yìí kún ìbérè tí ẹ bá ṣe.",
-       "autoblockedtext": "Àdírẹ́sì IP yín ti jẹ́ dídílọ́nà ní fúnrararẹ̀ nítorí pé ó jẹ́ lílò látọwọ́ oníṣe míràn tí ó jẹ́ dídílọ́nà látọwọ́ $1.\nÌdíẹ̀ tó ṣe jẹ́ bẹ́ẹ̀ nìyí:\n\n:''$2''\n\n\n* Ìbẹ̀rẹ̀ ìdínà: $8\n* Ìparí ìdínà: $6\n* Ẹni tí a fẹ́ dínà: $7\n\nẸ le ránṣẹ́ sí $1 tàbí ìkan láàrin [[{{MediaWiki:Grouppage-sysop}}|àwọn olùmójútó]] mìíràn láti fọ̀rọ̀wérọ̀ lórí ìdínà ọ̀ún.\n\nÀkíyèsí pé ẹ le mọ́ le lo ìní ''Ẹ fi e-mail ránṣẹ́ sí oníṣe yìí'' tí àdírẹ́sì e-mail tó tọ́ jẹ́ fífilórúkọsílẹ̀ sínú [[Special:Preferences|àwọn ìfẹ́ràn oníṣe]] yín tí wọn kò sì ti dínà yín láti lò ó.\n\nÀdírẹ́sì IP yín lọ́wọ́lọ́wọ́ ni $3, bẹ́ ẹ̀ sì ni ID fún ìdínà yín ni #$5.\nẸ jọ̀wọ́ ẹ fi gbogbo ẹ̀kúnrẹ́rẹ́ òkè yìí pọ̀mọ́ ìbérè tí ẹ bá ṣe.",
+       "autoblockedtext": "Àdírẹ́sì IP yín ti jẹ́ dídílọ́nà ní fúnrararẹ̀ nítorí pé ó jẹ́ lílò látọwọ́ oníṣe míràn tí ó jẹ́ dídílọ́nà látọwọ́ $1.\nÌdíẹ̀ tó ṣe jẹ́ bẹ́ẹ̀ nìyí:\n\n:<em>$2</em>\n\n\n* Ìbẹ̀rẹ̀ ìdínà: $8\n* Ìparí ìdínà: $6\n* Ẹni tí a fẹ́ dínà: $7\n\nẸ le ránṣẹ́ sí $1 tàbí ìkan láàrin [[{{MediaWiki:Grouppage-sysop}}|àwọn olùmójútó]] mìíràn láti fọ̀rọ̀wérọ̀ lórí ìdínà ọ̀ún.\n\nÀkíyèsí pé ẹ le mọ́ le lo ìní \"{{int:emailuser}}\" àyàfi tí ẹ bá ní àdírẹ́sì email tó yẹ nínú [[Special:Preferences|àwọn ìfẹ́ràn oníṣe]] yín tí wọn kò sì ti dínà yín láti lò ó.\n\nÀdírẹ́sì IP yín lọ́wọ́lọ́wọ́ ni $3, bẹ́ ẹ̀ sì ni ID fún ìdínà yín ni #$5.\nẸ jọ̀wọ́ ẹ fi gbogbo ẹ̀kúnrẹ́rẹ́ òkè yìí pọ̀mọ́ ìbérè tí ẹ bá ṣe.",
        "blockednoreason": "kó sí àlàyé kankan",
        "whitelistedittext": "Ẹ gbọ́dọ̀ $1 láti ṣ'àtúnṣe àwọn ojúewé.",
        "confirmedittext": "Ẹ gbọ́dọ̀ ṣe ìmúdájú àdírẹ́ẹ̀sì e-mail yín kí ẹ tó le è mọ ṣ'àtúnṣe àwọn ojúewé.\nẸjọ̀wọ́ ẹ ṣètò bẹ́ sìni ki ẹ fọwọ́sí àdírẹ́ẹ̀sì e-mail nínú [[Special:Preferences|àwọn ìfẹ́ràn ọníṣe]] yín.",
        "histfirst": "pípẹ́jùlọ",
        "histlast": "tuntunjùlọ",
        "historysize": "({{PLURAL:$1|1 byte|$1 bytes}})",
-       "historyempty": "(òfo)",
+       "historyempty": "òfo",
        "history-feed-title": "Ìtàn àtúnyẹ̀wò",
        "history-feed-description": "Ìtàn àtúnyẹ̀wò fún ojúewé yìí ní orí wiki",
        "history-feed-item-nocomment": "$1 ní $2",
        "userrights-expiry-current": "Yíòparí $1",
        "userrights-expiry-none": "Kò ní parí",
        "userrights-expiry": "Ìparí:",
+       "userrights-expiry-options": "ọjọ́ 1:1 day,ọ̀sẹ̀ 1:1 week,oṣù 1:1 month,oṣù 3:3 months,oṣù 6:6 months,ọdún 1:1 year",
        "group": "Ìdìpọ̀:",
        "group-user": "Àwọn oníṣe",
        "group-autoconfirmed": "Àwọn oníṣe aláàmúdájúarawọn",
        "rcfilters-savedqueries-apply-label": "Ìdáálẹ̀ ajọ̀",
        "rcfilters-savedqueries-apply-and-setdefault-label": "Ìdáálẹ̀ ajọ̀ ìbẹ̀rẹ̀",
        "rcfilters-savedqueries-cancel-label": "Fagilé",
+       "rcfilters-filter-humans-label": "Ti ènìyàn (kìí ṣe ti bot)",
+       "rcfilters-filter-pageedits-label": "Àwọn àtúnṣe ojúewé",
+       "rcfilters-filter-pageedits-description": "Àwọn àtúnṣe sí àkóónú wiki, ọ̀rọ̀, àpèjúwe ẹ̀ka...",
+       "rcfilters-filter-newpages-label": "Àwọn ìdá ojúewé",
+       "rcfilters-filter-newpages-description": "Àwọn àtúnṣe tó dá ojúewé tuntun.",
+       "rcfilters-filter-categorization-label": "Àwọn àtúnṣe ẹ̀ka",
+       "rcfilters-liveupdates-button": "Àtúnṣe ìsinsìnyí",
+       "rcfilters-liveupdates-button-title-on": "Pa àtúnṣe ìsinsìnyí dé",
+       "rcfilters-liveupdates-button-title-off": "Ìfihàn àwọn àtúnṣe tuntun bí wọ́n ṣe ún ṣẹlẹ̀",
        "rcnotefrom": "Nísàlẹ̀ ni {{PLURAL:$5|àtúnṣe|àwọn àtúnṣe}} wà láti <strong>$3, $4</strong> (títí dé <strong>$1</strong> ló hàn).",
        "rclistfrom": "Àfihàn àwọn àtúnṣe tuntun nípa bíbẹ̀rẹ̀ láti $3 $2",
        "rcshowhideminor": "$1 àwọn àtúnṣe kékéèké",
        "unusedtemplateswlh": "àwọn ìjápọ̀ míràn",
        "randompage": "Ojúewé àrìnàkò",
        "randompage-nopages": "Kò sí ojúewé kankan nínú {{PLURAL:$2|orúkọàyè|àwọn orúkọàyè}} ìsàlẹ̀ yìí: $1",
+       "randomincategory-nopages": "Kò sí ojúewé kankan nínú ẹ̀ka [[:Category:$1|$1]].",
+       "randomincategory-category": "Ẹ̀ka:",
+       "randomincategory-submit": "Lọ",
        "randomredirect": "Àtúndarí àrìnàkò",
        "randomredirect-nopages": "Kò sí àtúnjúwe kankan nínú orúkọàyè \"$1\".",
        "statistics": "Àwọn statistiki",
        "pager-older-n": "{{PLURAL:$1|pípẹ́jùlọ 1|pípẹ́jùlọ $1}}",
        "suppress": "Alábẹ̀wò",
        "querypage-disabled": "Ojúewé pàtàkì yìí jẹ́ ìdálẹ́kun nítorí ìsiṣẹ́.",
+       "apihelp-no-such-module": "Module \"$1\" kò sí.",
        "booksources": "Àwọn orísun ìwé",
        "booksources-search-legend": "Àwáàrí fún áwọn ìwé ìtọ́ka",
        "booksources-search": "Ṣàwárí",
        "mycontris": "Àwọn àfikún",
        "anoncontribs": "Àwọn àfikún",
        "contribsub2": "Fún {{GENDER:$3|$1}} ($2)",
+       "contributions-subtitle": "Fún {{GENDER:$3|$1}}",
        "contributions-userdoesnotexist": "Oníṣẹ́ yìí \"$1\" kò forúkọ sílẹ̀",
        "nocontribs": "Kò sí àtúnṣe tuntun tó bá àwárí mu.",
        "uctop": "lówọ́",
        "version-hooks": "Àwọn hook",
        "version-hook-name": "Orúkọ hook",
        "version-version": "($1)",
-       "version-license": "Ìwé àṣẹ",
+       "version-license": "Ìwé-àṣẹ MediaWiki",
+       "version-ext-license": "Ìwé-àṣe",
        "version-poweredby-credits": "Agbára ìṣiṣẹ́ wiki yìí wá látọwọ́ '''[https://www.mediawiki.org/ MediaWiki]''', copyright © 2001-$1 $2.",
        "version-poweredby-others": "àwọn mìíràn",
+       "version-poweredby-translators": "àwọn olùyédèsómíràn translatewiki.net",
        "version-credits-summary": "Ìdùnnú wa ni láti rántí àwọn ẹni wọ̀nyí fún ìdáwọ́lé wọn sí [[Special:Version|MediaWiki]].",
        "version-software": "Atòlànà kọ̀mpútà kíkànsínú",
        "version-software-product": "Èso",
        "htmlform-submit": "Fúnsílẹ̀",
        "htmlform-reset": "Ìdápadà àwọn àtúnṣe",
        "htmlform-selectorother-other": "Òmíràn",
+       "htmlform-date-placeholder": "YYYY-MM-DD",
+       "htmlform-time-placeholder": "HH:MM:SS",
+       "htmlform-datetime-placeholder": "YYYY-MM-DD HH:MM:SS",
        "logentry-delete-delete": "$1 pa ojúewé $3 rẹ́",
        "logentry-delete-restore": "$1 ti mú ojúewé $3 ($4) {{GENDER:$2|padàwá}}",
        "logentry-delete-event": "$1 ṣe àyípadà ìhànsí {{PLURAL:$5|ìṣẹ̀lẹ̀ àkọọ́lẹ̀ kan|àwọn ìṣẹ̀lẹ̀ àkọọ́lẹ̀ $5}} lórí $3: $4",
        "special-characters-group-khmer": "Khmer",
        "randomrootpage": "Ojúewé ìtẹ́dìí àrìnàkò",
        "edit-error-short": "Àṣìṣe: $1",
-       "edit-error-long": "Àwọn àsìṣe:\n\n\n$1"
+       "edit-error-long": "Àwọn àsìṣe:\n\n$1"
 }
index 45afe2a..675d537 100644 (file)
@@ -59,3 +59,4 @@ $magicWords = [
 ];
 
 $separatorTransformTable = [ ',' => '.', '.' => ',' ];
+$linkTrail = '/^([a-zçəğıöşü]+)(.*)$/sDu';
index 2442caa..a1d4e99 100644 (file)
@@ -33,8 +33,13 @@ class DeduplicateArchiveRevId extends LoggedUpdateMaintenance {
 
        protected function doDBUpdates() {
                $this->output( "Deduplicating ar_rev_id...\n" );
-
                $dbw = $this->getDB( DB_MASTER );
+               // Sanity check. If this is a new install, we don't need to do anything here.
+               if ( PopulateArchiveRevId::isNewInstall( $dbw ) ) {
+                       $this->output( "New install, nothing to do here.\n" );
+                       return true;
+               }
+
                PopulateArchiveRevId::checkMysqlAutoIncrementBug( $dbw );
 
                $minId = $dbw->selectField( 'archive', 'MIN(ar_rev_id)', [], __METHOD__ );
index 7d43f21..05dd0d0 100644 (file)
@@ -27,6 +27,7 @@
  */
 
 use MediaWiki\MediaWikiServices;
+use Wikimedia\Rdbms\IResultWrapper;
 
 require_once __DIR__ . '/Maintenance.php';
 
@@ -299,7 +300,7 @@ class GenerateSitemap extends Maintenance {
         * Return a database resolution of all the pages in a given namespace
         *
         * @param int $namespace Limit the query to this namespace
-        * @return Resource
+        * @return IResultWrapper
         */
        function getPageRes( $namespace ) {
                return $this->dbr->select( 'page',
index 96fcebf..c85e194 100644 (file)
@@ -43,6 +43,15 @@ class PopulateArchiveRevId extends LoggedUpdateMaintenance {
                $this->setBatchSize( 100 );
        }
 
+       /**
+        * @param IDatabase $dbw
+        * @return bool
+        */
+       public static function isNewInstall( IDatabase $dbw ) {
+               return $dbw->selectRowCount( 'archive' ) === 0 &&
+                       $dbw->selectRowCount( 'revision' ) === 1;
+       }
+
        protected function getUpdateKey() {
                return __CLASS__;
        }
index 8b3b39e..6084c84 100644 (file)
        display: none;
 }
 
+.config-help-field-checkbox {
+       display: none;
+}
+
 /* tooltip styles */
 .config-help-field-hint {
-       display: none;
        margin-left: 2px;
-       margin-bottom: -8px;
        padding: 0 0 0 15px;
        /* @embed */
        background-image: url( images/help-question.gif );
        border: 1px solid #5dc9f4;
        margin-left: 20px;
 }
+
+.config-help-field-checkbox:not( :checked ) ~ .config-help-field-data {
+       display: none;
+}
+
+#p-logo a {
+       background-image: url( images/installer-logo.png );
+}
index 521072e..235ff4a 100644 (file)
                        $label.text( labelText.replace( '$1', value ) );
                }
 
-               // Set up the help system
-               $( '.config-help-field-data' ).hide()
-                       .closest( '.config-help-field-container' ).find( '.config-help-field-hint' )
-                       .show()
-                       .on( 'click', function () {
-                               // FIXME: Use CSS transition
-                               // eslint-disable-next-line no-jquery/no-slide
-                               $( this ).closest( '.config-help-field-container' ).find( '.config-help-field-data' )
-                                       .slideToggle( 'fast' );
-                       } );
-
                // Show/hide code for DB-specific options
                // FIXME: Do we want slow, fast, or even non-animated (instantaneous) showing/hiding here?
                $( '.dbRadio' ).each( function () {
index 09306f6..3e4081a 100644 (file)
@@ -5,7 +5,7 @@
  * familiarise yourself with that CSS before making any changes to this code.
  *
  * Dual licensed:
- * - CC BY 3.0 <http://creativecommons.org/licenses/by/3.0>
+ * - CC BY 3.0 <https://creativecommons.org/licenses/by/3.0>
  * - GPL2 <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>
  *
  * @class jQuery.plugin.makeCollapsible
index 82aa24f..1257f66 100644 (file)
@@ -2,7 +2,7 @@
  * These plugins provide extra functionality for interaction with textareas.
  *
  * - encapsulateSelection: Ported from skins/common/edit.js by Trevor Parscal
- *   © 2009 Wikimedia Foundation (GPLv2) - http://www.wikimedia.org
+ *   © 2009 Wikimedia Foundation (GPLv2) - https://www.wikimedia.org
  * - getCaretPosition, scrollToCaretPosition: Ported from Wikia's LinkSuggest extension
  *   https://github.com/Wikia/app/blob/c0cd8b763/extensions/wikia/LinkSuggest/js/jquery.wikia.linksuggest.js
  *   © 2010 Inez Korczyński (korczynski@gmail.com) & Jesús Martínez Novo (martineznovo@gmail.com) (GPLv2)
index c7c061e..4343ecc 100644 (file)
                                q = {};
                                // using replace to iterate over a string
                                if ( uri.query ) {
-                                       uri.query.replace( /(?:^|&)([^&=]*)(?:(=)([^&]*))?/g, function ( $0, $1, $2, $3 ) {
-                                               var k, v;
-                                               if ( $1 ) {
-                                                       k = Uri.decode( $1 );
-                                                       v = ( $2 === '' || $2 === undefined ) ? null : Uri.decode( $3 );
+                                       uri.query.replace( /(?:^|&)([^&=]*)(?:(=)([^&]*))?/g, function ( match, k, eq, v ) {
+                                               if ( k ) {
+                                                       k = Uri.decode( k );
+                                                       v = ( eq === '' || eq === undefined ) ? null : Uri.decode( v );
 
                                                        // If overrideKeys, always (re)set top level value.
                                                        // If not overrideKeys but this key wasn't set before, then we set it as well.
index 861111a..3b643a5 100644 (file)
@@ -60,6 +60,7 @@ $wgAutoloadClasses += [
        'MediaWikiPHPUnitResultPrinter' => "$testDir/phpunit/MediaWikiPHPUnitResultPrinter.php",
        'MediaWikiPHPUnitTestListener' => "$testDir/phpunit/MediaWikiPHPUnitTestListener.php",
        'MediaWikiTestCase' => "$testDir/phpunit/MediaWikiTestCase.php",
+       'MediaWikiUnitTestCase' => "$testDir/phpunit/MediaWikiUnitTestCase.php",
        'MediaWikiTestResult' => "$testDir/phpunit/MediaWikiTestResult.php",
        'MediaWikiTestRunner' => "$testDir/phpunit/MediaWikiTestRunner.php",
        'PHPUnit4And6Compat' => "$testDir/phpunit/PHPUnit4And6Compat.php",
index 3b63c19..7d46e83 100644 (file)
@@ -797,6 +797,13 @@ class ParserTestRunner {
 
                $class = $wgParserConf['class'];
                $parser = new $class( [ 'preprocessorClass' => $preprocessor ] + $wgParserConf );
+               if ( $preprocessor ) {
+                       # Suppress deprecation warning for Preprocessor_DOM while testing
+                       Wikimedia\suppressWarnings();
+                       wfDeprecated( 'Preprocessor_DOM::__construct' );
+                       Wikimedia\restoreWarnings();
+                       $parser->getPreprocessor();
+               }
                ParserTestParserHook::setup( $parser );
 
                return $parser;
diff --git a/tests/phpunit/MediaWikiUnitTestCase.php b/tests/phpunit/MediaWikiUnitTestCase.php
new file mode 100644 (file)
index 0000000..407be20
--- /dev/null
@@ -0,0 +1,29 @@
+<?php
+/**
+ * Base class for MediaWiki unit tests.
+ *
+ * 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 Testing
+ */
+
+use PHPUnit\Framework\TestCase;
+
+abstract class MediaWikiUnitTestCase extends TestCase {
+       use PHPUnit4And6Compat;
+       use MediaWikiCoversValidator;
+}
index 5d9f63d..f9735c1 100644 (file)
@@ -69,7 +69,7 @@ class WfUrlencodeTest extends MediaWikiTestCase {
                        }
                } else {
                        throw new MWException( __METHOD__ . " given invalid expectation for "
-                               . "'$server'. Should be a string or an array( <http server name> => <string> ).\n" );
+                               . "'$server'. Should be a string or an array [ <http server name> => <string> ].\n" );
                }
        }
 
index 999e0bb..388b914 100644 (file)
@@ -316,7 +316,7 @@ class HtmlTest extends MediaWikiTestCase {
 
        /**
         * How do we handle duplicate keys in HTML attributes expansion?
-        * We could pass a "class" the values: 'GREEN' and array( 'GREEN' => false )
+        * We could pass a "class" the values: 'GREEN' and [ 'GREEN' => false ]
         * The latter will take precedence.
         *
         * Feature added by r96188
index 5246e36..a8c8581 100644 (file)
@@ -16,7 +16,7 @@ use MediaWikiTestCase;
 use MWException;
 use Title;
 use WANObjectCache;
-use Wikimedia\Rdbms\Database;
+use Wikimedia\Rdbms\IDatabase;
 use Wikimedia\Rdbms\LoadBalancer;
 use Wikimedia\TestingAccessWrapper;
 use WikitextContent;
@@ -70,10 +70,10 @@ class RevisionStoreTest extends MediaWikiTestCase {
        }
 
        /**
-        * @return \PHPUnit_Framework_MockObject_MockObject|Database
+        * @return \PHPUnit_Framework_MockObject_MockObject|IDatabase
         */
        private function getMockDatabase() {
-               return $this->getMockBuilder( Database::class )
+               return $this->getMockBuilder( IDatabase::class )
                        ->disableOriginalConstructor()->getMock();
        }
 
index 6e62afd..37ebf4c 100644 (file)
@@ -237,7 +237,7 @@ class StatusTest extends MediaWikiLangTestCase {
        }
 
        /**
-        * @param array $messageDetails E.g. array( 'KEY' => array(/PARAMS/) )
+        * @param array $messageDetails E.g. [ 'KEY' => [ /PARAMS/ ] ]
         * @return Message[]
         */
        protected function getMockMessages( $messageDetails ) {
index ca87b49..47d3b92 100644 (file)
@@ -10,7 +10,7 @@ use MediaWiki\Storage\NameTableStore;
 use MediaWikiTestCase;
 use Psr\Log\NullLogger;
 use WANObjectCache;
-use Wikimedia\Rdbms\Database;
+use Wikimedia\Rdbms\IDatabase;
 use Wikimedia\Rdbms\LoadBalancer;
 use Wikimedia\TestingAccessWrapper;
 
@@ -57,37 +57,25 @@ class NameTableStoreTest extends MediaWikiTestCase {
        }
 
        private function getCallCheckingDb( $insertCalls, $selectCalls ) {
-               $mock = $this->getMockBuilder( Database::class )
+               $proxiedMethods = [
+                       'select' => $selectCalls,
+                       'insert' => $insertCalls,
+                       'affectedRows' => null,
+                       'insertId' => null,
+                       'getSessionLagStatus' => null,
+                       'writesPending' => null,
+                       'onTransactionPreCommitOrIdle' => null
+               ];
+               $mock = $this->getMockBuilder( IDatabase::class )
                        ->disableOriginalConstructor()
                        ->getMock();
-               $mock->expects( $this->exactly( $insertCalls ) )
-                       ->method( 'insert' )
-                       ->willReturnCallback( function ( ...$args ) {
-                               return call_user_func_array( [ $this->db, 'insert' ], $args );
-                       } );
-               $mock->expects( $this->exactly( $selectCalls ) )
-                       ->method( 'select' )
-                       ->willReturnCallback( function ( ...$args ) {
-                               return call_user_func_array( [ $this->db, 'select' ], $args );
-                       } );
-               $mock->expects( $this->exactly( $insertCalls ) )
-                       ->method( 'affectedRows' )
-                       ->willReturnCallback( function ( ...$args ) {
-                               return call_user_func_array( [ $this->db, 'affectedRows' ], $args );
-                       } );
-               $mock->expects( $this->any() )
-                       ->method( 'insertId' )
-                       ->willReturnCallback( function ( ...$args ) {
-                               return call_user_func_array( [ $this->db, 'insertId' ], $args );
-                       } );
-               $mock->expects( $this->any() )
-                       ->method( 'query' )
-                       ->willReturn( [] );
-               $mock->expects( $this->any() )
-                       ->method( 'isOpen' )
-                       ->willReturn( true );
-               $wrapper = TestingAccessWrapper::newFromObject( $mock );
-               $wrapper->queryLogger = new NullLogger();
+               foreach ( $proxiedMethods as $method => $count ) {
+                       $mock->expects( is_int( $count ) ? $this->exactly( $count ) : $this->any() )
+                               ->method( $method )
+                               ->willReturnCallback( function ( ...$args ) use ( $method ) {
+                                       return call_user_func_array( [ $this->db, $method ], $args );
+                               } );
+               }
                return $mock;
        }
 
index e50e1bc..fd45732 100644 (file)
@@ -73,8 +73,8 @@ class TestLogger extends \Psr\Log\AbstractLogger {
 
        /**
         * Return the collected logs
-        * @return array Array of array( string $level, string $message ), or
-        *   array( string $level, string $message, array $context ) if $collectContext was true.
+        * @return array Array of [ string $level, string $message ], or
+        *   [ string $level, string $message, array $context ] if $collectContext was true.
         */
        public function getBuffer() {
                return $this->buffer;
index d6c3401..529d9fb 100644 (file)
@@ -338,7 +338,7 @@ class TitleTest extends MediaWikiTestCase {
        public function testWgWhitelistReadRegexp( $whitelistRegexp, $source, $action, $expected ) {
                // $wgWhitelistReadRegexp must be an array. Since the provided test cases
                // usually have only one regex, it is more concise to write the lonely regex
-               // as a string. Thus we cast to an array() to honor $wgWhitelistReadRegexp
+               // as a string. Thus we cast to a [] to honor $wgWhitelistReadRegexp
                // type requisite.
                if ( is_string( $whitelistRegexp ) ) {
                        $whitelistRegexp = [ $whitelistRegexp ];
index 7869bbd..71a77b6 100644 (file)
@@ -86,7 +86,7 @@ STR;
        /**
         * Checks that the request's result matches the expected results.
         * Assumes no rawcontinue and a complete batch.
-        * @param array $values Array is a two element array( request, expected_results )
+        * @param array $values Array is a two element [ request, expected_results ]
         * @param array|null $session
         * @param bool $appendModule
         * @param User|null $user
index 857988c..0f5c1f2 100644 (file)
@@ -540,7 +540,7 @@ class DatabaseSqliteTest extends MediaWikiTestCase {
 
                $toString = (string)$db;
 
-               $this->assertContains( 'SQLite ', $toString );
+               $this->assertContains( 'sqlite object', $toString );
        }
 
        /**
diff --git a/tests/phpunit/includes/filerepo/file/ForeignDBFileTest.php b/tests/phpunit/includes/filerepo/file/ForeignDBFileTest.php
new file mode 100644 (file)
index 0000000..3c92ecb
--- /dev/null
@@ -0,0 +1,16 @@
+<?php
+
+/** @covers ForeignDBFile */
+class ForeignDBFileTest extends \PHPUnit\Framework\TestCase {
+
+       use PHPUnit4And6Compat;
+
+       public function testShouldConstructCorrectInstanceFromTitle() {
+               $title = Title::makeTitle( NS_FILE, 'Awesome_file' );
+               $repoMock = $this->createMock( LocalRepo::class );
+
+               $file = ForeignDBFile::newFromTitle( $title, $repoMock );
+
+               $this->assertInstanceOf( ForeignDBFile::class, $file );
+       }
+}
index acaeb02..4afe3b5 100644 (file)
@@ -316,8 +316,8 @@ EOT;
 
                // Hash of known correct values from C code
                $this->assertEquals(
-                       'c69ac9eb7a8a630c0cded201cefeaace',
-                       md5( $ketama_test( 1e5 ) ),
+                       'd1a4912a80e4654ec2e4e462c8b911c6',
+                       md5( $ketama_test( 1e3 ) ),
                        'Ketama mode (large, MD5 check)'
                );
 
index 9ec53c0..9f2fb1c 100644 (file)
@@ -481,7 +481,7 @@ class IPTest extends PHPUnit\Framework\TestCase {
                $this->assertFalseCIDR( '192.0.2.0/33', "mask > 32" );
 
                // Check internal logic
-               # 0 mask always result in array(0,0)
+               # 0 mask always result in [ 0, 0 ]
                $this->assertEquals( [ 0, 0 ], IP::parseCIDR( '192.0.0.2/0' ) );
                $this->assertEquals( [ 0, 0 ], IP::parseCIDR( '0.0.0.0/0' ) );
                $this->assertEquals( [ 0, 0 ], IP::parseCIDR( '255.255.255.255/0' ) );
index 33e5c3b..833ac2c 100644 (file)
@@ -1,9 +1,8 @@
 <?php
 
-use Wikimedia\Rdbms\Database;
+use Wikimedia\Rdbms\IDatabase;
 use Wikimedia\Rdbms\DBConnRef;
 use Wikimedia\Rdbms\FakeResultWrapper;
-use Wikimedia\Rdbms\IDatabase;
 use Wikimedia\Rdbms\ILoadBalancer;
 use Wikimedia\Rdbms\ResultWrapper;
 
@@ -40,7 +39,7 @@ class DBConnRefTest extends PHPUnit\Framework\TestCase {
         * @return IDatabase
         */
        private function getDatabaseMock() {
-               $db = $this->getMockBuilder( Database::class )
+               $db = $this->getMockBuilder( IDatabase::class )
                        ->disableOriginalConstructor()
                        ->getMock();
 
@@ -60,12 +59,6 @@ class DBConnRefTest extends PHPUnit\Framework\TestCase {
                $db->method( 'isOpen' )->willReturnCallback( function () use ( &$open ) {
                        return $open;
                } );
-               $db->method( 'open' )->willReturnCallback( function () use ( &$open ) {
-                       $open = true;
-
-                       return $open;
-               } );
-               $db->method( '__toString' )->willReturn( 'MOCK_DB' );
 
                return $db;
        }
index 6b3e05d..3b2b105 100644 (file)
@@ -48,6 +48,9 @@ class PreprocessorTest extends MediaWikiTestCase {
                $this->mOptions = ParserOptions::newFromUserAndLang( new User,
                        MediaWikiServices::getInstance()->getContentLanguage() );
 
+               # Suppress deprecation warning for Preprocessor_DOM while testing
+               $this->hideDeprecated( 'Preprocessor_DOM::__construct' );
+
                $this->mPreprocessors = [];
                foreach ( self::$classNames as $className ) {
                        $this->mPreprocessors[$className] = new $className( $this );
diff --git a/tests/phpunit/includes/password/PasswordFactoryTest.php b/tests/phpunit/includes/password/PasswordFactoryTest.php
deleted file mode 100644 (file)
index a7b3557..0000000
+++ /dev/null
@@ -1,124 +0,0 @@
-<?php
-
-/**
- * @covers PasswordFactory
- */
-class PasswordFactoryTest extends MediaWikiTestCase {
-       public function testConstruct() {
-               $pf = new PasswordFactory();
-               $this->assertEquals( [ '' ], array_keys( $pf->getTypes() ) );
-               $this->assertEquals( '', $pf->getDefaultType() );
-
-               $pf = new PasswordFactory( [
-                       'foo' => [ 'class' => 'FooPassword' ],
-                       'bar' => [ 'class' => 'BarPassword', 'baz' => 'boom' ],
-               ], 'foo' );
-               $this->assertEquals( [ '', 'foo', 'bar' ], array_keys( $pf->getTypes() ) );
-               $this->assertArraySubset( [ 'class' => 'BarPassword', 'baz' => 'boom' ], $pf->getTypes()['bar'] );
-               $this->assertEquals( 'foo', $pf->getDefaultType() );
-       }
-
-       public function testRegister() {
-               $pf = new PasswordFactory;
-               $pf->register( 'foo', [ 'class' => InvalidPassword::class ] );
-               $this->assertArrayHasKey( 'foo', $pf->getTypes() );
-       }
-
-       public function testSetDefaultType() {
-               $pf = new PasswordFactory;
-               $pf->register( '1', [ 'class' => InvalidPassword::class ] );
-               $pf->register( '2', [ 'class' => InvalidPassword::class ] );
-               $pf->setDefaultType( '1' );
-               $this->assertSame( '1', $pf->getDefaultType() );
-               $pf->setDefaultType( '2' );
-               $this->assertSame( '2', $pf->getDefaultType() );
-       }
-
-       /**
-        * @expectedException Exception
-        */
-       public function testSetDefaultTypeError() {
-               $pf = new PasswordFactory;
-               $pf->setDefaultType( 'bogus' );
-       }
-
-       public function testInit() {
-               $config = new HashConfig( [
-                       'PasswordConfig' => [
-                               'foo' => [ 'class' => InvalidPassword::class ],
-                       ],
-                       'PasswordDefault' => 'foo'
-               ] );
-               $pf = new PasswordFactory;
-               $pf->init( $config );
-               $this->assertSame( 'foo', $pf->getDefaultType() );
-               $this->assertArrayHasKey( 'foo', $pf->getTypes() );
-       }
-
-       public function testNewFromCiphertext() {
-               $pf = new PasswordFactory;
-               $pf->register( 'B', [ 'class' => MWSaltedPassword::class ] );
-               $pw = $pf->newFromCiphertext( ':B:salt:d529e941509eb9e9b9cfaeae1fe7ca23' );
-               $this->assertInstanceOf( MWSaltedPassword::class, $pw );
-       }
-
-       public function provideNewFromCiphertextErrors() {
-               return [ [ 'blah' ], [ ':blah:' ] ];
-       }
-
-       /**
-        * @dataProvider provideNewFromCiphertextErrors
-        * @expectedException PasswordError
-        */
-       public function testNewFromCiphertextErrors( $hash ) {
-               $pf = new PasswordFactory;
-               $pf->register( 'B', [ 'class' => MWSaltedPassword::class ] );
-               $pf->newFromCiphertext( $hash );
-       }
-
-       public function testNewFromType() {
-               $pf = new PasswordFactory;
-               $pf->register( 'B', [ 'class' => MWSaltedPassword::class ] );
-               $pw = $pf->newFromType( 'B' );
-               $this->assertInstanceOf( MWSaltedPassword::class, $pw );
-       }
-
-       /**
-        * @expectedException PasswordError
-        */
-       public function testNewFromTypeError() {
-               $pf = new PasswordFactory;
-               $pf->register( 'B', [ 'class' => MWSaltedPassword::class ] );
-               $pf->newFromType( 'bogus' );
-       }
-
-       public function testNewFromPlaintext() {
-               $pf = new PasswordFactory;
-               $pf->register( 'A', [ 'class' => MWOldPassword::class ] );
-               $pf->register( 'B', [ 'class' => MWSaltedPassword::class ] );
-               $pf->setDefaultType( 'A' );
-
-               $this->assertInstanceOf( InvalidPassword::class, $pf->newFromPlaintext( null ) );
-               $this->assertInstanceOf( MWOldPassword::class, $pf->newFromPlaintext( 'password' ) );
-               $this->assertInstanceOf( MWSaltedPassword::class,
-                       $pf->newFromPlaintext( 'password', $pf->newFromType( 'B' ) ) );
-       }
-
-       public function testNeedsUpdate() {
-               $pf = new PasswordFactory;
-               $pf->register( 'A', [ 'class' => MWOldPassword::class ] );
-               $pf->register( 'B', [ 'class' => MWSaltedPassword::class ] );
-               $pf->setDefaultType( 'A' );
-
-               $this->assertFalse( $pf->needsUpdate( $pf->newFromType( 'A' ) ) );
-               $this->assertTrue( $pf->needsUpdate( $pf->newFromType( 'B' ) ) );
-       }
-
-       public function testGenerateRandomPasswordString() {
-               $this->assertSame( 13, strlen( PasswordFactory::generateRandomPasswordString( 13 ) ) );
-       }
-
-       public function testNewInvalidPassword() {
-               $this->assertInstanceOf( InvalidPassword::class, PasswordFactory::newInvalidPassword() );
-       }
-}
index 2ec8ea9..c3d5ec1 100644 (file)
@@ -78,6 +78,38 @@ class ResourceLoaderContextTest extends PHPUnit\Framework\TestCase {
                $this->assertEquals( 'zh|fallback|||styles|||||', $ctx->getHash() );
        }
 
+       public static function provideDirection() {
+               yield 'LTR language' => [
+                       [ 'lang' => 'en' ],
+                       'ltr',
+               ];
+               yield 'RTL language' => [
+                       [ 'lang' => 'he' ],
+                       'rtl',
+               ];
+               yield 'explicit LTR' => [
+                       [ 'lang' => 'he', 'dir' => 'ltr' ],
+                       'ltr',
+               ];
+               yield 'explicit RTL' => [
+                       [ 'lang' => 'en', 'dir' => 'rtl' ],
+                       'rtl',
+               ];
+               // Not supported, but tested to cover the case and detect change
+               yield 'invalid dir' => [
+                       [ 'lang' => 'he', 'dir' => 'xyz' ],
+                       'rtl',
+               ];
+       }
+
+       /**
+        * @dataProvider provideDirection
+        */
+       public function testDirection( array $params, $expected ) {
+               $ctx = new ResourceLoaderContext( $this->getResourceLoader(), new FauxRequest( $params ) );
+               $this->assertEquals( $expected, $ctx->getDirection() );
+       }
+
        public function testShouldInclude() {
                $ctx = new ResourceLoaderContext( $this->getResourceLoader(), new FauxRequest( [] ) );
                $this->assertTrue( $ctx->shouldIncludeScripts(), 'Scripts in combined' );
index 2aa0d27..1585cbc 100644 (file)
@@ -373,6 +373,68 @@ class ResourceLoaderFileModuleTest extends ResourceLoaderTestCase {
                        'lessVars' => [ 'key' => 'value' ],
                ];
                yield 'identical Less variables' => [ $x, $x, true ];
+
+               $a = [
+                       'packageFiles' => [ [ 'name' => 'data.json', 'callback' => function () {
+                               return [ 'aaa' ];
+                       } ] ]
+               ];
+               $b = [
+                       'packageFiles' => [ [ 'name' => 'data.json', 'callback' => function () {
+                               return [ 'bbb' ];
+                       } ] ]
+               ];
+               yield 'packageFiles with different callback' => [ $a, $b, false ];
+
+               $a = [
+                       'packageFiles' => [ [ 'name' => 'aaa.json', 'callback' => function () {
+                               return [ 'x' ];
+                       } ] ]
+               ];
+               $b = [
+                       'packageFiles' => [ [ 'name' => 'bbb.json', 'callback' => function () {
+                               return [ 'x' ];
+                       } ] ]
+               ];
+               yield 'packageFiles with different file name and a callback' => [ $a, $b, false ];
+
+               $a = [
+                       'packageFiles' => [ [ 'name' => 'data.json', 'versionCallback' => function () {
+                               return [ 'A-version' ];
+                       }, 'callback' => function () {
+                               throw new Exception( 'Unexpected computation' );
+                       } ] ]
+               ];
+               $b = [
+                       'packageFiles' => [ [ 'name' => 'data.json', 'versionCallback' => function () {
+                               return [ 'B-version' ];
+                       }, 'callback' => function () {
+                               throw new Exception( 'Unexpected computation' );
+                       } ] ]
+               ];
+               yield 'packageFiles with different versionCallback' => [ $a, $b, false ];
+
+               $a = [
+                       'packageFiles' => [ [ 'name' => 'aaa.json',
+                               'versionCallback' => function () {
+                                       return [ 'X-version' ];
+                               },
+                               'callback' => function () {
+                                       throw new Exception( 'Unexpected computation' );
+                               }
+                       ] ]
+               ];
+               $b = [
+                       'packageFiles' => [ [ 'name' => 'bbb.json',
+                               'versionCallback' => function () {
+                                       return [ 'X-version' ];
+                               },
+                               'callback' => function () {
+                                       throw new Exception( 'Unexpected computation' );
+                               }
+                       ] ]
+               ];
+               yield 'packageFiles with different file name and a versionCallback' => [ $a, $b, false ];
        }
 
        /**
@@ -471,7 +533,7 @@ class ResourceLoaderFileModuleTest extends ResourceLoaderTestCase {
                                        'main' => 'init.js'
                                ]
                        ],
-                       [
+                       'package file with callback' => [
                                $base + [
                                        'packageFiles' => [
                                                [ 'name' => 'foo.json', 'content' => [ 'Hello' => 'world' ] ],
@@ -518,6 +580,34 @@ class ResourceLoaderFileModuleTest extends ResourceLoaderTestCase {
                                        'lang' => 'fy'
                                ]
                        ],
+                       'package file with callback and versionCallback' => [
+                               $base + [
+                                       'packageFiles' => [
+                                               [ 'name' => 'bar.js', 'content' => "console.log('Hello');" ],
+                                               [ 'name' => 'data.json', 'versionCallback' => function ( $context ) {
+                                                       return $context->getLanguage();
+                                               }, 'callback' => function ( $context ) {
+                                                       return [ 'langCode' => $context->getLanguage() ];
+                                               } ],
+                                       ]
+                               ],
+                               [
+                                       'files' => [
+                                               'bar.js' => [
+                                                       'type' => 'script',
+                                                       'content' => "console.log('Hello');",
+                                               ],
+                                               'data.json' => [
+                                                       'type' => 'data',
+                                                       'content' => [ 'langCode' => 'fy' ]
+                                               ],
+                                       ],
+                                       'main' => 'bar.js'
+                               ],
+                               [
+                                       'lang' => 'fy'
+                               ]
+                       ],
                        [
                                $base + [
                                        'packageFiles' => [
@@ -526,7 +616,7 @@ class ResourceLoaderFileModuleTest extends ResourceLoaderTestCase {
                                ],
                                false
                        ],
-                       [
+                       'package file with invalid callback' => [
                                $base + [
                                        'packageFiles' => [
                                                [ 'name' => 'foo.json', 'callback' => 'functionThatDoesNotExist142857' ]
index b0512fa..c3fc55a 100644 (file)
@@ -62,7 +62,6 @@ class ResourceLoaderImageTest extends ResourceLoaderTestCase {
                        'he' => 'rtl',
                        'ar' => 'rtl',
                ];
-               static $contexts = [];
 
                $image = $this->getTestImage( $imageName );
                $context = $this->getResourceLoaderContext( [
index b5dd008..99f5e1b 100644 (file)
@@ -105,6 +105,83 @@ mw.loader.register( [
         "c",
         "{blankVer}"
     ]
+] );',
+                       ] ],
+                       [ [
+                               // Regression test for T223402.
+                               'msg' => 'Optimise the dependency tree (indirect circular dependency)',
+                               'modules' => [
+                                       'top' => new ResourceLoaderTestModule( [ 'dependencies' => [ 'middle1', 'util' ] ] ),
+                                       'middle1' => new ResourceLoaderTestModule( [ 'dependencies' => [ 'middle2', 'util' ] ] ),
+                                       'middle2' => new ResourceLoaderTestModule( [ 'dependencies' => [ 'bottom' ] ] ),
+                                       'bottom' => new ResourceLoaderTestModule( [ 'dependencies' => [ 'top' ] ] ),
+                                       'util' => new ResourceLoaderTestModule( [ 'dependencies' => [] ] ),
+                               ],
+                               'out' => '
+mw.loader.addSource( {
+    "local": "/w/load.php"
+} );
+mw.loader.register( [
+    [
+        "top",
+        "{blankVer}",
+        [
+            1,
+            4
+        ]
+    ],
+    [
+        "middle1",
+        "{blankVer}",
+        [
+            2,
+            4
+        ]
+    ],
+    [
+        "middle2",
+        "{blankVer}",
+        [
+            3
+        ]
+    ],
+    [
+        "bottom",
+        "{blankVer}",
+        [
+            0
+        ]
+    ],
+    [
+        "util",
+        "{blankVer}"
+    ]
+] );',
+                       ] ],
+                       [ [
+                               // Regression test for T223402.
+                               'msg' => 'Optimise the dependency tree (direct circular dependency)',
+                               'modules' => [
+                                       'top' => new ResourceLoaderTestModule( [ 'dependencies' => [ 'util', 'top' ] ] ),
+                                       'util' => new ResourceLoaderTestModule( [ 'dependencies' => [] ] ),
+                               ],
+                               'out' => '
+mw.loader.addSource( {
+    "local": "/w/load.php"
+} );
+mw.loader.register( [
+    [
+        "top",
+        "{blankVer}",
+        [
+            1,
+            0
+        ]
+    ],
+    [
+        "util",
+        "{blankVer}"
+    ]
 ] );',
                        ] ],
                        [ [
index 4f4fa25..4dd6c80 100644 (file)
@@ -15,9 +15,9 @@ class SpecialSearchTest extends MediaWikiTestCase {
         * @covers SpecialSearch::load
         * @dataProvider provideSearchOptionsTests
         * @param array $requested Request parameters. For example:
-        *   array( 'ns5' => true, 'ns6' => true). Null to use default options.
+        *   [ 'ns5' => true, 'ns6' => true ]. Null to use default options.
         * @param array $userOptions User options to test with. For example:
-        *   array('searchNs5' => 1 );. Null to use default options.
+        *   [ 'searchNs5' => 1 ];. Null to use default options.
         * @param string $expectedProfile An expected search profile name
         * @param array $expectedNS Expected namespaces
         * @param string $message
index 52b1433..dd21add 100644 (file)
@@ -49,7 +49,7 @@ class BatchRowUpdateTest extends MediaWikiTestCase {
                        $this->assertEquals( $response[$pos], $rows, "Testing row in position $pos" );
                        $pos++;
                }
-               // -1 is because the final array() marks the end and isnt included
+               // -1 is because the final [] marks the end and isn't included
                $this->assertEquals( count( $response ) - 1, $pos );
        }
 
index 6b81a66..e600021 100644 (file)
@@ -67,7 +67,7 @@ class UIDGeneratorTest extends PHPUnit\Framework\TestCase {
        }
 
        /**
-        * array( method, length, bits, hostbits )
+        * [ method, length, bits, hostbits ]
         * NOTE: When adding a new method name here please update the covers tags for the tests!
         */
        public static function provider_testTimestampedUID() {
index 5068e70..be38aff 100644 (file)
@@ -58,7 +58,7 @@ class CategoriesRdfTest extends MediaWikiLangTestCase {
                        'wgServer' => 'http://acme.test',
                        'wgCanonicalServer' => 'http://acme.test',
                        'wgArticlePath' => '/wiki/$1',
-                       'wgRightsUrl' => '//creativecommons.org/licenses/by-sa/3.0/',
+                       'wgRightsUrl' => 'https://creativecommons.org/licenses/by-sa/3.0/',
                ] );
 
                $dumpScript =
index de68fec..cc6ac31 100644 (file)
                <testsuite name="documentation">
                        <directory>documentation</directory>
                </testsuite>
+               <testsuite name="unit">
+                       <directory>unit</directory>
+               </testsuite>
        </testsuites>
        <groups>
                <exclude>
-                       <group>Utility</group>
                        <group>Broken</group>
-                       <group>Stub</group>
                </exclude>
        </groups>
        <filter>
                        </exclude>
                </whitelist>
        </filter>
+       <listeners>
+               <listener class="JohnKary\PHPUnit\Listener\SpeedTrapListener">
+                       <arguments>
+                               <array>
+                                       <element key="slowThreshold">
+                                               <integer>50</integer>
+                                       </element>
+                                       <element key="reportLength">
+                                               <integer>50</integer>
+                                       </element>
+                               </array>
+                       </arguments>
+               </listener>
+       </listeners>
 </phpunit>
diff --git a/tests/phpunit/unit-tests.xml b/tests/phpunit/unit-tests.xml
new file mode 100644 (file)
index 0000000..cd4118c
--- /dev/null
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<phpunit bootstrap="unit/initUnitTests.php"
+                xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.8/phpunit.xsd"
+
+                colors="true"
+                backupGlobals="false"
+                convertErrorsToExceptions="true"
+                convertNoticesToExceptions="true"
+                convertWarningsToExceptions="true"
+                forceCoversAnnotation="true"
+                stopOnFailure="false"
+                timeoutForSmallTests="10"
+                timeoutForMediumTests="30"
+                timeoutForLargeTests="60"
+                beStrictAboutTestsThatDoNotTestAnything="true"
+                beStrictAboutOutputDuringTests="true"
+                beStrictAboutTestSize="true"
+                verbose="false">
+       <testsuites>
+               <testsuite name="tests">
+                       <directory>unit</directory>
+               </testsuite>
+       </testsuites>
+       <groups>
+               <exclude>
+                       <group>Broken</group>
+               </exclude>
+       </groups>
+       <filter>
+               <whitelist addUncoveredFilesFromWhitelist="true">
+                       <directory suffix=".php">../../includes</directory>
+                       <directory suffix=".php">../../languages</directory>
+                       <directory suffix=".php">../../maintenance</directory>
+                       <exclude>
+                               <directory suffix=".php">../../languages/messages</directory>
+                               <file>../../languages/data/normalize-ar.php</file>
+                               <file>../../languages/data/normalize-ml.php</file>
+                       </exclude>
+               </whitelist>
+       </filter>
+</phpunit>
diff --git a/tests/phpunit/unit/includes/password/PasswordFactoryTest.php b/tests/phpunit/unit/includes/password/PasswordFactoryTest.php
new file mode 100644 (file)
index 0000000..cbfddd4
--- /dev/null
@@ -0,0 +1,124 @@
+<?php
+
+/**
+ * @covers PasswordFactory
+ */
+class PasswordFactoryTest extends MediaWikiUnitTestCase {
+       public function testConstruct() {
+               $pf = new PasswordFactory();
+               $this->assertEquals( [ '' ], array_keys( $pf->getTypes() ) );
+               $this->assertEquals( '', $pf->getDefaultType() );
+
+               $pf = new PasswordFactory( [
+                       'foo' => [ 'class' => 'FooPassword' ],
+                       'bar' => [ 'class' => 'BarPassword', 'baz' => 'boom' ],
+               ], 'foo' );
+               $this->assertEquals( [ '', 'foo', 'bar' ], array_keys( $pf->getTypes() ) );
+               $this->assertArraySubset( [ 'class' => 'BarPassword', 'baz' => 'boom' ], $pf->getTypes()['bar'] );
+               $this->assertEquals( 'foo', $pf->getDefaultType() );
+       }
+
+       public function testRegister() {
+               $pf = new PasswordFactory;
+               $pf->register( 'foo', [ 'class' => InvalidPassword::class ] );
+               $this->assertArrayHasKey( 'foo', $pf->getTypes() );
+       }
+
+       public function testSetDefaultType() {
+               $pf = new PasswordFactory;
+               $pf->register( '1', [ 'class' => InvalidPassword::class ] );
+               $pf->register( '2', [ 'class' => InvalidPassword::class ] );
+               $pf->setDefaultType( '1' );
+               $this->assertSame( '1', $pf->getDefaultType() );
+               $pf->setDefaultType( '2' );
+               $this->assertSame( '2', $pf->getDefaultType() );
+       }
+
+       /**
+        * @expectedException Exception
+        */
+       public function testSetDefaultTypeError() {
+               $pf = new PasswordFactory;
+               $pf->setDefaultType( 'bogus' );
+       }
+
+       public function testInit() {
+               $config = new HashConfig( [
+                       'PasswordConfig' => [
+                               'foo' => [ 'class' => InvalidPassword::class ],
+                       ],
+                       'PasswordDefault' => 'foo'
+               ] );
+               $pf = new PasswordFactory;
+               $pf->init( $config );
+               $this->assertSame( 'foo', $pf->getDefaultType() );
+               $this->assertArrayHasKey( 'foo', $pf->getTypes() );
+       }
+
+       public function testNewFromCiphertext() {
+               $pf = new PasswordFactory;
+               $pf->register( 'B', [ 'class' => MWSaltedPassword::class ] );
+               $pw = $pf->newFromCiphertext( ':B:salt:d529e941509eb9e9b9cfaeae1fe7ca23' );
+               $this->assertInstanceOf( MWSaltedPassword::class, $pw );
+       }
+
+       public function provideNewFromCiphertextErrors() {
+               return [ [ 'blah' ], [ ':blah:' ] ];
+       }
+
+       /**
+        * @dataProvider provideNewFromCiphertextErrors
+        * @expectedException PasswordError
+        */
+       public function testNewFromCiphertextErrors( $hash ) {
+               $pf = new PasswordFactory;
+               $pf->register( 'B', [ 'class' => MWSaltedPassword::class ] );
+               $pf->newFromCiphertext( $hash );
+       }
+
+       public function testNewFromType() {
+               $pf = new PasswordFactory;
+               $pf->register( 'B', [ 'class' => MWSaltedPassword::class ] );
+               $pw = $pf->newFromType( 'B' );
+               $this->assertInstanceOf( MWSaltedPassword::class, $pw );
+       }
+
+       /**
+        * @expectedException PasswordError
+        */
+       public function testNewFromTypeError() {
+               $pf = new PasswordFactory;
+               $pf->register( 'B', [ 'class' => MWSaltedPassword::class ] );
+               $pf->newFromType( 'bogus' );
+       }
+
+       public function testNewFromPlaintext() {
+               $pf = new PasswordFactory;
+               $pf->register( 'A', [ 'class' => MWOldPassword::class ] );
+               $pf->register( 'B', [ 'class' => MWSaltedPassword::class ] );
+               $pf->setDefaultType( 'A' );
+
+               $this->assertInstanceOf( InvalidPassword::class, $pf->newFromPlaintext( null ) );
+               $this->assertInstanceOf( MWOldPassword::class, $pf->newFromPlaintext( 'password' ) );
+               $this->assertInstanceOf( MWSaltedPassword::class,
+                       $pf->newFromPlaintext( 'password', $pf->newFromType( 'B' ) ) );
+       }
+
+       public function testNeedsUpdate() {
+               $pf = new PasswordFactory;
+               $pf->register( 'A', [ 'class' => MWOldPassword::class ] );
+               $pf->register( 'B', [ 'class' => MWSaltedPassword::class ] );
+               $pf->setDefaultType( 'A' );
+
+               $this->assertFalse( $pf->needsUpdate( $pf->newFromType( 'A' ) ) );
+               $this->assertTrue( $pf->needsUpdate( $pf->newFromType( 'B' ) ) );
+       }
+
+       public function testGenerateRandomPasswordString() {
+               $this->assertSame( 13, strlen( PasswordFactory::generateRandomPasswordString( 13 ) ) );
+       }
+
+       public function testNewInvalidPassword() {
+               $this->assertInstanceOf( InvalidPassword::class, PasswordFactory::newInvalidPassword() );
+       }
+}
diff --git a/tests/phpunit/unit/initUnitTests.php b/tests/phpunit/unit/initUnitTests.php
new file mode 100644 (file)
index 0000000..2121877
--- /dev/null
@@ -0,0 +1,69 @@
+<?php
+/**
+ * PHPUnit bootstrap file for the unit test suite.
+ *
+ * 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 Testing
+ */
+
+if ( PHP_SAPI !== 'cli' ) {
+       die( 'This file is only meant to be executed indirectly by PHPUnit\'s bootstrap process!' );
+}
+
+/**
+ * PHPUnit includes the bootstrap file inside a method body, while most MediaWiki startup files
+ * assume to be included in the global scope.
+ * This utility provides a way to include these files: it makes all globals available in the
+ * inclusion scope before including the file, then exports all new or changed globals.
+ *
+ * @param string $fileName the file to include
+ */
+function wfRequireOnceInGlobalScope( $fileName ) {
+       // phpcs:disable MediaWiki.Usage.ForbiddenFunctions.extract
+       extract( $GLOBALS, EXTR_REFS | EXTR_SKIP );
+       // phpcs:enable
+
+       require_once $fileName;
+
+       foreach ( get_defined_vars() as $varName => $value ) {
+               $GLOBALS[$varName] = $value;
+       }
+}
+
+define( 'MEDIAWIKI', true );
+define( 'MW_PHPUNIT_TEST', true );
+
+// We don't use a settings file here but some code still assumes that one exists
+define( 'MW_CONFIG_FILE', 'LocalSettings.php' );
+
+$IP = realpath( __DIR__ . '/../../..' );
+
+// these variables must be defined before setup runs
+$GLOBALS['IP'] = $IP;
+$GLOBALS['wgCommandLineMode'] = true;
+
+require_once "$IP/tests/common/TestSetup.php";
+
+wfRequireOnceInGlobalScope( "$IP/includes/AutoLoader.php" );
+wfRequireOnceInGlobalScope( "$IP/includes/Defines.php" );
+wfRequireOnceInGlobalScope( "$IP/includes/DefaultSettings.php" );
+wfRequireOnceInGlobalScope( "$IP/includes/GlobalFunctions.php" );
+
+require_once "$IP/tests/common/TestsAutoLoader.php";
+
+TestSetup::applyInitialConfig();