Merge "resourceloader: Define the default language and skin in constants"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Wed, 19 Jun 2019 14:10:33 +0000 (14:10 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Wed, 19 Jun 2019 14:10:33 +0000 (14:10 +0000)
140 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/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/block/BlockManager.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/WebInstallerOutput.php
includes/installer/i18n/cs.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/ILoadBalancer.php
includes/libs/rdbms/loadbalancer/LoadBalancer.php
includes/libs/rdbms/loadbalancer/LoadBalancerSingle.php
includes/libs/rdbms/loadmonitor/LoadMonitor.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/Sanitizer.php
includes/rcfeed/RedisPubSubFeedEngine.php
includes/resourceloader/ResourceLoaderCircularDependencyError.php [new file with mode: 0644]
includes/resourceloader/ResourceLoaderClientHtml.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/SpecialJavaScriptTest.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/be-tarask.json
languages/i18n/cdo.json
languages/i18n/cs.json
languages/i18n/es.json
languages/i18n/hyw.json
languages/i18n/my.json
languages/i18n/nqo.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
tests/common/TestsAutoLoader.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/IPTest.php
tests/phpunit/includes/libs/rdbms/database/DBConnRefTest.php
tests/phpunit/includes/password/PasswordFactoryTest.php [deleted file]
tests/phpunit/includes/resourceloader/ResourceLoaderClientHtmlTest.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/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 5917e3c..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 ===
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 7d6318d..2f793b5 100644 (file)
@@ -5427,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**):
@@ -5451,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
  *
@@ -6423,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..2023d91 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() {
@@ -3222,7 +3222,7 @@ class OutputPage extends ContextSource {
                                ),
                                [ 'html5shiv' ],
                                ResourceLoaderModule::TYPE_SCRIPTS,
-                               [ 'sync' => true ],
+                               [ 'raw' => '1', 'sync' => '1' ],
                                $this->getCSPNonce()
                        ) .
                        '<![endif]-->';
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 b11e76f..41ff893 100644 (file)
@@ -202,6 +202,9 @@ 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 ];
@@ -219,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 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 45784e4..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>
@@ -323,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 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 0258878..a2202b6 100644 (file)
@@ -86,6 +86,8 @@ interface ILoadBalancer {
 
        /** @var int DB handle should have DBO_TRX disabled and the caller will leave it as such */
        const CONN_TRX_AUTOCOMMIT = 1;
+       /** @var int Return null on connection failure instead of throwing an exception */
+       const CONN_SILENCE_ERRORS = 2;
 
        /** @var string Manager of ILoadBalancer instances is running post-commit callbacks */
        const STAGE_POSTCOMMIT_CALLBACKS = 'stage-postcommit-callbacks';
@@ -148,10 +150,13 @@ interface ILoadBalancer {
        /**
         * Get the index of the reader connection, which may be a replica DB
         *
-        * This takes into account load ratios and lag times. It should
-        * always return a consistent index during a given invocation.
+        * This takes into account load ratios and lag times. It should return a consistent
+        * index during the life time of the load balancer. This initially checks replica DBs
+        * for connectivity to avoid returning an unusable server. This means that connections
+        * might be attempted by calling this method (usally one at the most but possibly more).
+        * Subsequent calls with the same $group will not need to make new connection attempts
+        * since the acquired connection for each group is preserved.
         *
-        * Side effect: opens connections to databases
         * @param string|bool $group Query group, or false for the generic group
         * @param string|bool $domain Domain ID, or false for the current domain
         * @throws DBError
@@ -224,8 +229,8 @@ interface ILoadBalancer {
         *
         * @note This method throws DBAccessError if ILoadBalancer::disable() was called
         *
-        * @throws DBError
-        * @return Database
+        * @throws DBError If any error occurs that prevents the yielding of a (live) IDatabase
+        * @return IDatabase|bool This returns false on failure if CONN_SILENCE_ERRORS is set
         */
        public function getConnection( $i, $groups = [], $domain = false, $flags = 0 );
 
@@ -300,31 +305,6 @@ interface ILoadBalancer {
         */
        public function getMaintenanceConnectionRef( $i, $groups = [], $domain = false, $flags = 0 );
 
-       /**
-        * Open a connection to the server given by the specified index
-        *
-        * The index must be an actual index into the array. If a connection to the server is
-        * already open and not considered an "in use" foreign connection, this simply returns it.
-        *
-        * Avoid using CONN_TRX_AUTOCOMMIT for databases with ATTR_DB_LEVEL_LOCKING (e.g. sqlite)
-        * in order to avoid deadlocks. ILoadBalancer::getServerAttributes() can be used to check
-        * such flags beforehand.
-        *
-        * If the caller uses $domain or sets CONN_TRX_AUTOCOMMIT in $flags, then it must
-        * also call ILoadBalancer::reuseConnection() on the handle when finished using it.
-        * In all other cases, this is not necessary, though not harmful either.
-        * Avoid the use of begin() or startAtomic() on any such connections.
-        *
-        * @note This method throws DBAccessError if ILoadBalancer::disable() was called
-        *
-        * @param int $i Server index (does not support DB_MASTER/DB_REPLICA)
-        * @param string|bool $domain Domain ID, or false for the current domain
-        * @param int $flags Bitfield of CONN_* class constants (e.g. CONN_TRX_AUTOCOMMIT)
-        * @return Database|bool Returns false on errors
-        * @throws DBAccessError
-        */
-       public function openConnection( $i, $domain = false, $flags = 0 );
-
        /**
         * @return int
         */
index ffb7a34..62f4582 100644 (file)
@@ -107,7 +107,7 @@ class LoadBalancer implements ILoadBalancer {
        private $genericReadIndex = -1;
        /** @var int[] The group replica DB indexes keyed by group */
        private $readIndexByGroup = [];
-       /** @var bool|DBMasterPos False if not set */
+       /** @var bool|DBMasterPos Replication sync position or false if not set */
        private $waitForPos;
        /** @var bool Whether the generic reader fell back to a lagged replica DB */
        private $laggedReplicaMode = false;
@@ -270,6 +270,49 @@ class LoadBalancer implements ILoadBalancer {
        }
 
        /**
+        * @param int $flags
+        * @return bool
+        */
+       private function sanitizeConnectionFlags( $flags ) {
+               if ( ( $flags & self::CONN_TRX_AUTOCOMMIT ) === self::CONN_TRX_AUTOCOMMIT ) {
+                       // Assuming all servers are of the same type (or similar), which is overwhelmingly
+                       // the case, use the master server information to get the attributes. The information
+                       // for $i cannot be used since it might be DB_REPLICA, which might require connection
+                       // attempts in order to be resolved into a real server index.
+                       $attributes = $this->getServerAttributes( $this->getWriterIndex() );
+                       if ( $attributes[Database::ATTR_DB_LEVEL_LOCKING] ) {
+                               // Callers sometimes want to (a) escape REPEATABLE-READ stateness without locking
+                               // rows (e.g. FOR UPDATE) or (b) make small commits during a larger transactions
+                               // to reduce lock contention. None of these apply for sqlite and using separate
+                               // connections just causes self-deadlocks.
+                               $flags &= ~self::CONN_TRX_AUTOCOMMIT;
+                               $this->connLogger->info( __METHOD__ .
+                                       ': ignoring CONN_TRX_AUTOCOMMIT to avoid deadlocks.' );
+                       }
+               }
+
+               return $flags;
+       }
+
+       /**
+        * @param IDatabase $conn
+        * @param int $flags
+        * @throws DBUnexpectedError
+        */
+       private function enforceConnectionFlags( IDatabase $conn, $flags ) {
+               if ( ( $flags & self::CONN_TRX_AUTOCOMMIT ) == self::CONN_TRX_AUTOCOMMIT ) {
+                       if ( $conn->trxLevel() ) { // sanity
+                               throw new DBUnexpectedError(
+                                       $conn,
+                                       'Handle requested with CONN_TRX_AUTOCOMMIT yet it has a transaction'
+                               );
+                       }
+
+                       $conn->clearFlag( $conn::DBO_TRX ); // auto-commit mode
+               }
+       }
+
+               /**
         * Get a LoadMonitor instance
         *
         * @return ILoadMonitor
@@ -354,7 +397,7 @@ class LoadBalancer implements ILoadBalancer {
         * @param int $i
         * @param array $groups
         * @param string|bool $domain
-        * @return int
+        * @return int The index of a specific server (replica DBs are checked for connectivity)
         */
        private function getConnectionIndex( $i, $groups, $domain ) {
                // Check one "group" per default: the generic pool
@@ -364,9 +407,9 @@ class LoadBalancer implements ILoadBalancer {
                        ? $defaultGroups
                        : (array)$groups;
 
-               if ( $i == self::DB_MASTER ) {
+               if ( $i === self::DB_MASTER ) {
                        $i = $this->getWriterIndex();
-               } elseif ( $i == self::DB_REPLICA ) {
+               } elseif ( $i === self::DB_REPLICA ) {
                        # Try to find an available server in any the query groups (in order)
                        foreach ( $groups as $group ) {
                                $groupIndex = $this->getReaderIndex( $group, $domain );
@@ -378,7 +421,7 @@ class LoadBalancer implements ILoadBalancer {
                }
 
                # Operation-based index
-               if ( $i == self::DB_REPLICA ) {
+               if ( $i === self::DB_REPLICA ) {
                        $this->lastError = 'Unknown error'; // reset error string
                        # Try the general server pool if $groups are unavailable.
                        $i = ( $groups === [ false ] )
@@ -389,7 +432,7 @@ class LoadBalancer implements ILoadBalancer {
                                $this->lastError = 'No working replica DB server: ' . $this->lastError;
                                // Throw an exception
                                $this->reportConnectionError();
-                               return null; // not reached
+                               return null; // unreachable due to exception
                        }
                }
 
@@ -427,6 +470,7 @@ class LoadBalancer implements ILoadBalancer {
                $this->getLoadMonitor()->scaleLoads( $loads, $domain );
 
                // Pick a server to use, accounting for weights, load, lag, and "waitForPos"
+               $this->lazyLoadReplicationPositions(); // optimizes server candidate selection
                list( $i, $laggedReplicaMode ) = $this->pickReaderIndex( $loads, $domain );
                if ( $i === false ) {
                        // DB connection unsuccessful
@@ -510,6 +554,7 @@ class LoadBalancer implements ILoadBalancer {
                        } else {
                                $i = false;
                                if ( $this->waitForPos && $this->waitForPos->asOfTime() ) {
+                                       $this->replLogger->debug( __METHOD__ . ": replication positions detected" );
                                        // "chronologyCallback" sets "waitForPos" for session consistency.
                                        // This triggers doWait() after connect, so it's especially good to
                                        // avoid lagged servers so as to avoid excessive delay in that method.
@@ -542,7 +587,7 @@ class LoadBalancer implements ILoadBalancer {
                        $serverName = $this->getServerName( $i );
                        $this->connLogger->debug( __METHOD__ . ": Using reader #$i: $serverName..." );
 
-                       $conn = $this->openConnection( $i, $domain );
+                       $conn = $this->getConnection( $i, [], $domain, self::CONN_SILENCE_ERRORS );
                        if ( !$conn ) {
                                $this->connLogger->warning( __METHOD__ . ": Failed connecting to $i/$domain" );
                                unset( $currentLoads[$i] ); // avoid this server next iteration
@@ -714,20 +759,20 @@ class LoadBalancer implements ILoadBalancer {
                                );
 
                                return false;
-                       } else {
-                               $conn = $this->openConnection( $index, self::DOMAIN_ANY );
-                               if ( !$conn ) {
-                                       $this->replLogger->warning(
-                                               __METHOD__ . ': failed to connect to {dbserver}',
-                                               [ 'dbserver' => $server ]
-                                       );
+                       }
+                       // Open a temporary new connection in order to wait for replication
+                       $conn = $this->getConnection( $index, [], self::DOMAIN_ANY, self::CONN_SILENCE_ERRORS );
+                       if ( !$conn ) {
+                               $this->replLogger->warning(
+                                       __METHOD__ . ': failed to connect to {dbserver}',
+                                       [ 'dbserver' => $server ]
+                               );
 
-                                       return false;
-                               }
-                               // Avoid connection spam in waitForAll() when connections
-                               // are made just for the sake of doing this lag check.
-                               $close = true;
+                               return false;
                        }
+                       // Avoid connection spam in waitForAll() when connections
+                       // are made just for the sake of doing this lag check.
+                       $close = true;
                }
 
                $this->replLogger->info(
@@ -773,39 +818,32 @@ class LoadBalancer implements ILoadBalancer {
        }
 
        public function getConnection( $i, $groups = [], $domain = false, $flags = 0 ) {
-               if ( $i === null || $i === false ) {
+               if ( !is_int( $i ) ) {
                        throw new InvalidArgumentException( "Cannot connect without a server index" );
+               } elseif ( $groups && $i > 0 ) {
+                       throw new InvalidArgumentException( "Got query groups with server index #$i" );
                }
 
                $domain = $this->resolveDomainID( $domain );
+               $flags = $this->sanitizeConnectionFlags( $flags );
                $masterOnly = ( $i == self::DB_MASTER || $i == $this->getWriterIndex() );
 
-               if ( ( $flags & self::CONN_TRX_AUTOCOMMIT ) === self::CONN_TRX_AUTOCOMMIT ) {
-                       // Assuming all servers are of the same type (or similar), which is overwhelmingly
-                       // the case, use the master server information to get the attributes. The information
-                       // for $i cannot be used since it might be DB_REPLICA, which might require connection
-                       // attempts in order to be resolved into a real server index.
-                       $attributes = $this->getServerAttributes( $this->getWriterIndex() );
-                       if ( $attributes[Database::ATTR_DB_LEVEL_LOCKING] ) {
-                               // Callers sometimes want to (a) escape REPEATABLE-READ stateness without locking
-                               // rows (e.g. FOR UPDATE) or (b) make small commits during a larger transactions
-                               // to reduce lock contention. None of these apply for sqlite and using separate
-                               // connections just causes self-deadlocks.
-                               $flags &= ~self::CONN_TRX_AUTOCOMMIT;
-                               $this->connLogger->info( __METHOD__ .
-                                       ': ignoring CONN_TRX_AUTOCOMMIT to avoid deadlocks.' );
-                       }
-               }
-
                // Number of connections made before getting the server index and handle
                $priorConnectionsMade = $this->connsOpened;
-               // Decide which server to use (might trigger new connections)
+
+               // Choose a server if $i is DB_MASTER/DB_REPLICA (might trigger new connections)
                $serverIndex = $this->getConnectionIndex( $i, $groups, $domain );
                // Get an open connection to that server (might trigger a new connection)
-               $conn = $this->openConnection( $serverIndex, $domain, $flags );
-               if ( !$conn ) {
-                       $this->reportConnectionError();
-                       return null; // unreachable due to exception
+               $conn = $this->localDomain->equals( $domain )
+                       ? $this->getLocalConnection( $serverIndex, $flags )
+                       : $this->getForeignConnection( $serverIndex, $domain, $flags );
+               // Throw an error or bail out if the connection attempt failed
+               if ( !( $conn instanceof IDatabase ) ) {
+                       if ( ( $flags & self::CONN_SILENCE_ERRORS ) != self::CONN_SILENCE_ERRORS ) {
+                               $this->reportConnectionError();
+                       }
+
+                       return false;
                }
 
                // Profile any new connections caused by this method
@@ -815,6 +853,15 @@ class LoadBalancer implements ILoadBalancer {
                        $this->trxProfiler->recordConnection( $host, $dbname, $masterOnly );
                }
 
+               if ( !$conn->isOpen() ) {
+                       // Connection was made but later unrecoverably lost for some reason.
+                       // Do not return a handle that will just throw exceptions on use,
+                       // but let the calling code (e.g. getReaderIndex) try another server.
+                       $this->errorConnection = $conn;
+                       return false;
+               }
+
+               $this->enforceConnectionFlags( $conn, $flags );
                if ( $serverIndex == $this->getWriterIndex() ) {
                        // If the load balancer is read-only, perhaps due to replication lag, then master
                        // DB handles will reflect that. Note that Database::assertIsWritableMaster() takes
@@ -917,43 +964,15 @@ class LoadBalancer implements ILoadBalancer {
                        : self::DB_REPLICA;
        }
 
+       /**
+        * @param int $i
+        * @param bool $domain
+        * @param int $flags
+        * @return Database|bool Live database handle or false on failure
+        * @deprecated Since 1.34 Use getConnection() instead
+        */
        public function openConnection( $i, $domain = false, $flags = 0 ) {
-               $domain = $this->resolveDomainID( $domain );
-
-               if ( !$this->connectionAttempted && $this->chronologyCallback ) {
-                       // Load any "waitFor" positions before connecting so that doWait() is triggered
-                       $this->connLogger->debug( __METHOD__ . ': calling initLB() before first connection.' );
-                       $this->connectionAttempted = true;
-                       ( $this->chronologyCallback )( $this );
-               }
-
-               $conn = $this->localDomain->equals( $domain )
-                       ? $this->openLocalConnection( $i, $flags )
-                       : $this->openForeignConnection( $i, $domain, $flags );
-
-               if ( $conn instanceof IDatabase && !$conn->isOpen() ) {
-                       // Connection was made but later unrecoverably lost for some reason.
-                       // Do not return a handle that will just throw exceptions on use,
-                       // but let the calling code (e.g. getReaderIndex) try another server.
-                       $this->errorConnection = $conn;
-                       $conn = false;
-               }
-
-               if (
-                       $conn instanceof IDatabase &&
-                       ( ( $flags & self::CONN_TRX_AUTOCOMMIT ) == self::CONN_TRX_AUTOCOMMIT )
-               ) {
-                       if ( $conn->trxLevel() ) { // sanity
-                               throw new DBUnexpectedError(
-                                       $conn,
-                                       'Handle requested with CONN_TRX_AUTOCOMMIT yet it has a transaction'
-                               );
-                       }
-
-                       $conn->clearFlag( $conn::DBO_TRX ); // auto-commit mode
-               }
-
-               return $conn;
+               return $this->getConnection( $i, [], $domain, $flags | self::CONN_SILENCE_ERRORS );
        }
 
        /**
@@ -968,7 +987,7 @@ class LoadBalancer implements ILoadBalancer {
         * @param int $flags Class CONN_* constant bitfield
         * @return Database
         */
-       private function openLocalConnection( $i, $flags = 0 ) {
+       private function getLocalConnection( $i, $flags = 0 ) {
                // Connection handles required to be in auto-commit mode use a separate connection
                // pool since the main pool is effected by implicit and explicit transaction rounds
                $autoCommit = ( ( $flags & self::CONN_TRX_AUTOCOMMIT ) == self::CONN_TRX_AUTOCOMMIT );
@@ -1033,7 +1052,7 @@ class LoadBalancer implements ILoadBalancer {
         * @return Database|bool Returns false on connection error
         * @throws DBError When database selection fails
         */
-       private function openForeignConnection( $i, $domain, $flags = 0 ) {
+       private function getForeignConnection( $i, $domain, $flags = 0 ) {
                $domainInstance = DatabaseDomain::newFromId( $domain );
                // Connection handles required to be in auto-commit mode use a separate connection
                // pool since the main pool is effected by implicit and explicit transaction rounds
@@ -1235,9 +1254,22 @@ class LoadBalancer implements ILoadBalancer {
                        }
                }
 
+               $this->lazyLoadReplicationPositions(); // session consistency
+
                return $db;
        }
 
+       /**
+        * Make sure that any "waitForPos" positions are loaded and available to doWait()
+        */
+       private function lazyLoadReplicationPositions() {
+               if ( !$this->connectionAttempted && $this->chronologyCallback ) {
+                       $this->connectionAttempted = true;
+                       ( $this->chronologyCallback )( $this ); // generally calls waitFor()
+                       $this->connLogger->debug( __METHOD__ . ': executed chronology callback.' );
+               }
+       }
+
        /**
         * @throws DBConnectionError
         */
@@ -1944,11 +1976,12 @@ class LoadBalancer implements ILoadBalancer {
 
                if ( !$pos ) {
                        // Get the current master position, opening a connection if needed
-                       $masterConn = $this->getAnyOpenConnection( $this->getWriterIndex() );
+                       $index = $this->getWriterIndex();
+                       $masterConn = $this->getAnyOpenConnection( $index );
                        if ( $masterConn ) {
                                $pos = $masterConn->getMasterPos();
                        } else {
-                               $masterConn = $this->openConnection( $this->getWriterIndex(), self::DOMAIN_ANY );
+                               $masterConn = $this->getConnection( $index, [], self::DOMAIN_ANY, self::CONN_SILENCE_ERRORS );
                                if ( !$masterConn ) {
                                        throw new DBReplicationWaitError(
                                                null,
@@ -1961,12 +1994,15 @@ class LoadBalancer implements ILoadBalancer {
                }
 
                if ( $pos instanceof DBMasterPos ) {
+                       $start = microtime( true );
                        $result = $conn->masterPosWait( $pos, $timeout );
+                       $seconds = max( microtime( true ) - $start, 0 );
                        if ( $result == -1 || is_null( $result ) ) {
-                               $msg = __METHOD__ . ': timed out waiting on {host} pos {pos}';
+                               $msg = __METHOD__ . ': timed out waiting on {host} pos {pos} [{seconds}s]';
                                $this->replLogger->warning( $msg, [
                                        'host' => $conn->getServer(),
                                        'pos' => $pos,
+                                       'seconds' => round( $seconds, 6 ),
                                        'trace' => ( new RuntimeException() )->getTraceAsString()
                                ] );
                                $ok = false;
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
+       }
 }
 
 /**
index 180baed..aa1e9b2 100644 (file)
@@ -154,12 +154,12 @@ class LoadMonitor implements ILoadMonitor {
 
                        # Handles with open transactions are avoided since they might be subject
                        # to REPEATABLE-READ snapshots, which could affect the lag estimate query.
-                       $flags = ILoadBalancer::CONN_TRX_AUTOCOMMIT;
+                       $flags = ILoadBalancer::CONN_TRX_AUTOCOMMIT | ILoadBalancer::CONN_SILENCE_ERRORS;
                        $conn = $this->parent->getAnyOpenConnection( $i, $flags );
                        if ( $conn ) {
                                $close = false; // already open
                        } else {
-                               $conn = $this->parent->openConnection( $i, ILoadBalancer::DOMAIN_ANY, $flags );
+                               $conn = $this->parent->getConnection( $i, [], ILoadBalancer::DOMAIN_ANY, $flags );
                                $close = true; // new connection
                        }
 
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 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 6061fb5..7f2f85f 100644 (file)
@@ -303,7 +303,7 @@ JAVASCRIPT;
 
                // Async scripts. Once the startup is loaded, inline RLQ scripts will run.
                // Pass-through a custom 'target' from OutputPage (T143066).
-               $startupQuery = [];
+               $startupQuery = [ 'raw' => '1' ];
                foreach ( [ 'target', 'safemode' ] as $param ) {
                        if ( $this->options[$param] !== null ) {
                                $startupQuery[$param] = (string)$this->options[$param];
index 87a4515..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 = [],
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 efed418..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();
@@ -425,11 +453,4 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
                // and hash it to determine the version (as used by E-Tag HTTP response header).
                return true;
        }
-
-       /**
-        * @return string
-        */
-       public function getGroup() {
-               return 'startup';
-       }
 }
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 c984af8..3e9676c 100644 (file)
@@ -174,7 +174,7 @@ JAVASCRIPT
                // load before qunit/export.
                $scripts = $out->makeResourceLoaderLink( 'jquery.qunit',
                        ResourceLoaderModule::TYPE_SCRIPTS,
-                       [ 'raw' => true, 'sync' => true ]
+                       [ 'raw' => '1', 'sync' => '1' ]
                );
 
                $head = implode( "\n", [ $styles, $scripts ] );
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 2de90ed..3a57c0b 100644 (file)
@@ -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 a8d6b30..4d850a1 100644 (file)
        "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": "Немагчыма знайсьці сэкцыю",
        "watcherrortext": "Узьнікла памылка падчас зьмены наладаў вашага сьпісу назіраньня для «$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 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 d66c6e7..9e18328 100644 (file)
        "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 d2d70e7..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",
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 6a2f2d9..7584b6b 100644 (file)
        "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 d0575b6..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\" ߊ߬ ߓߘߊ߫ ߖߏ߬ߛߌ߫ ߟߋ߬ ߓߊ߬؟",
        "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 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 9647842..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 );
        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 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",
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 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;
        }
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 03a3e24..408a0a2 100644 (file)
@@ -118,7 +118,7 @@ Deprecation message.' ]
                        . '});</script>' . "\n"
                        . '<link rel="stylesheet" href="/w/load.php?lang=nl&amp;modules=test.styles.deprecated%2Cpure&amp;only=styles"/>' . "\n"
                        . '<style>.private{}</style>' . "\n"
-                       . '<script async="" src="/w/load.php?lang=nl&amp;modules=startup&amp;only=scripts"></script>';
+                       . '<script async="" src="/w/load.php?lang=nl&amp;modules=startup&amp;only=scripts&amp;raw=1"></script>';
                // phpcs:enable
                $expected = self::expandVariables( $expected );
 
@@ -136,7 +136,7 @@ Deprecation message.' ]
 
                // phpcs:disable Generic.Files.LineLength
                $expected = '<script>document.documentElement.className=document.documentElement.className.replace(/(^|\s)client-nojs(\s|$)/,"$1client-js$2");</script>' . "\n"
-                       . '<script async="" src="/w/load.php?lang=nl&amp;modules=startup&amp;only=scripts&amp;target=example"></script>';
+                       . '<script async="" src="/w/load.php?lang=nl&amp;modules=startup&amp;only=scripts&amp;raw=1&amp;target=example"></script>';
                // phpcs:enable
 
                $this->assertSame( $expected, (string)$client->getHeadHtml() );
@@ -153,7 +153,7 @@ Deprecation message.' ]
 
                // phpcs:disable Generic.Files.LineLength
                $expected = '<script>document.documentElement.className=document.documentElement.className.replace(/(^|\s)client-nojs(\s|$)/,"$1client-js$2");</script>' . "\n"
-                       . '<script async="" src="/w/load.php?lang=nl&amp;modules=startup&amp;only=scripts&amp;safemode=1"></script>';
+                       . '<script async="" src="/w/load.php?lang=nl&amp;modules=startup&amp;only=scripts&amp;raw=1&amp;safemode=1"></script>';
                // phpcs:enable
 
                $this->assertSame( $expected, (string)$client->getHeadHtml() );
@@ -170,7 +170,7 @@ Deprecation message.' ]
 
                // phpcs:disable Generic.Files.LineLength
                $expected = '<script>document.documentElement.className=document.documentElement.className.replace(/(^|\s)client-nojs(\s|$)/,"$1client-js$2");</script>' . "\n"
-                       . '<script async="" src="/w/load.php?lang=nl&amp;modules=startup&amp;only=scripts"></script>';
+                       . '<script async="" src="/w/load.php?lang=nl&amp;modules=startup&amp;only=scripts&amp;raw=1"></script>';
                // phpcs:enable
 
                $this->assertSame( $expected, (string)$client->getHeadHtml() );
@@ -224,18 +224,18 @@ Deprecation message.' ]
                        ],
                        [
                                'context' => [],
-                               // Eg. startup module
-                               'modules' => [ 'test.scripts.raw' ],
+                               'modules' => [ 'test.scripts' ],
                                'only' => ResourceLoaderModule::TYPE_SCRIPTS,
-                               'extra' => [],
-                               'output' => '<script async="" src="/w/load.php?lang=nl&amp;modules=test.scripts.raw&amp;only=scripts"></script>',
+                               // Eg. startup module
+                               'extra' => [ 'raw' => '1' ],
+                               'output' => '<script async="" src="/w/load.php?lang=nl&amp;modules=test.scripts&amp;only=scripts&amp;raw=1"></script>',
                        ],
                        [
                                'context' => [],
-                               'modules' => [ 'test.scripts.raw' ],
+                               'modules' => [ 'test.scripts' ],
                                'only' => ResourceLoaderModule::TYPE_SCRIPTS,
-                               'extra' => [ 'sync' => '1' ],
-                               'output' => '<script src="/w/load.php?lang=nl&amp;modules=test.scripts.raw&amp;only=scripts&amp;sync=1"></script>',
+                               'extra' => [ 'raw' => '1', 'sync' => '1' ],
+                               'output' => '<script src="/w/load.php?lang=nl&amp;modules=test.scripts&amp;only=scripts&amp;raw=1&amp;sync=1"></script>',
                        ],
                        [
                                'context' => [],
@@ -418,7 +418,6 @@ Deprecation message.' ]
                        'test.scripts' => [],
                        'test.scripts.user' => [ 'group' => 'user' ],
                        'test.scripts.user.empty' => [ 'group' => 'user', 'isKnownEmpty' => true ],
-                       'test.scripts.raw' => [ 'isRaw' => true ],
                        'test.scripts.shouldembed' => [ 'shouldEmbed' => true ],
 
                        'test.ordering.a' => [ 'shouldEmbed' => false ],
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 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();