MediaWiki  master
Language.php
Go to the documentation of this file.
1 <?php
31 
36 class Language {
41  const AS_AUTONYMS = null;
42 
47  const ALL = 'all';
48 
54  const SUPPORTED = 'mwfile';
55 
59  public $mConverter;
60 
61  public $mVariants, $mCode, $mLoaded = false;
62  public $mMagicExtensions = [];
63  private $mHtmlCode = null, $mParentLanguage = false;
64 
65  public $dateFormatStrings = [];
67 
69  protected $namespaceNames;
71 
75  public $transformData = [];
76 
80  public static $dataCache;
81 
82  public static $mLangObjCache = [];
83 
88  const MESSAGES_FALLBACKS = 0;
89 
94  const STRICT_FALLBACKS = 1;
95 
96  public static $mWeekdayMsgs = [
97  'sunday', 'monday', 'tuesday', 'wednesday', 'thursday',
98  'friday', 'saturday'
99  ];
100 
101  public static $mWeekdayAbbrevMsgs = [
102  'sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'
103  ];
104 
105  public static $mMonthMsgs = [
106  'january', 'february', 'march', 'april', 'may_long', 'june',
107  'july', 'august', 'september', 'october', 'november',
108  'december'
109  ];
110  public static $mMonthGenMsgs = [
111  'january-gen', 'february-gen', 'march-gen', 'april-gen', 'may-gen', 'june-gen',
112  'july-gen', 'august-gen', 'september-gen', 'october-gen', 'november-gen',
113  'december-gen'
114  ];
115  public static $mMonthAbbrevMsgs = [
116  'jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug',
117  'sep', 'oct', 'nov', 'dec'
118  ];
119 
120  public static $mIranianCalendarMonthMsgs = [
121  'iranian-calendar-m1', 'iranian-calendar-m2', 'iranian-calendar-m3',
122  'iranian-calendar-m4', 'iranian-calendar-m5', 'iranian-calendar-m6',
123  'iranian-calendar-m7', 'iranian-calendar-m8', 'iranian-calendar-m9',
124  'iranian-calendar-m10', 'iranian-calendar-m11', 'iranian-calendar-m12'
125  ];
126 
127  public static $mHebrewCalendarMonthMsgs = [
128  'hebrew-calendar-m1', 'hebrew-calendar-m2', 'hebrew-calendar-m3',
129  'hebrew-calendar-m4', 'hebrew-calendar-m5', 'hebrew-calendar-m6',
130  'hebrew-calendar-m7', 'hebrew-calendar-m8', 'hebrew-calendar-m9',
131  'hebrew-calendar-m10', 'hebrew-calendar-m11', 'hebrew-calendar-m12',
132  'hebrew-calendar-m6a', 'hebrew-calendar-m6b'
133  ];
134 
136  'hebrew-calendar-m1-gen', 'hebrew-calendar-m2-gen', 'hebrew-calendar-m3-gen',
137  'hebrew-calendar-m4-gen', 'hebrew-calendar-m5-gen', 'hebrew-calendar-m6-gen',
138  'hebrew-calendar-m7-gen', 'hebrew-calendar-m8-gen', 'hebrew-calendar-m9-gen',
139  'hebrew-calendar-m10-gen', 'hebrew-calendar-m11-gen', 'hebrew-calendar-m12-gen',
140  'hebrew-calendar-m6a-gen', 'hebrew-calendar-m6b-gen'
141  ];
142 
143  public static $mHijriCalendarMonthMsgs = [
144  'hijri-calendar-m1', 'hijri-calendar-m2', 'hijri-calendar-m3',
145  'hijri-calendar-m4', 'hijri-calendar-m5', 'hijri-calendar-m6',
146  'hijri-calendar-m7', 'hijri-calendar-m8', 'hijri-calendar-m9',
147  'hijri-calendar-m10', 'hijri-calendar-m11', 'hijri-calendar-m12'
148  ];
149 
154  public static $durationIntervals = [
155  'millennia' => 31556952000,
156  'centuries' => 3155695200,
157  'decades' => 315569520,
158  'years' => 31556952, // 86400 * ( 365 + ( 24 * 3 + 25 ) / 400 )
159  'weeks' => 604800,
160  'days' => 86400,
161  'hours' => 3600,
162  'minutes' => 60,
163  'seconds' => 1,
164  ];
165 
172  private static $fallbackLanguageCache = [];
173 
178  private static $grammarTransformations;
179 
184  private static $languageNameCache;
185 
189  private static $lre = "\u{202A}"; // U+202A LEFT-TO-RIGHT EMBEDDING
190  private static $rle = "\u{202B}"; // U+202B RIGHT-TO-LEFT EMBEDDING
191  private static $pdf = "\u{202C}"; // U+202C POP DIRECTIONAL FORMATTING
192 
204  // @codeCoverageIgnoreStart
205  // phpcs:ignore Generic.Files.LineLength
206  private static $strongDirRegex = '/(?:([\x{41}-\x{5a}\x{61}-\x{7a}\x{aa}\x{b5}\x{ba}\x{c0}-\x{d6}\x{d8}-\x{f6}\x{f8}-\x{2b8}\x{2bb}-\x{2c1}\x{2d0}\x{2d1}\x{2e0}-\x{2e4}\x{2ee}\x{370}-\x{373}\x{376}\x{377}\x{37a}-\x{37d}\x{37f}\x{386}\x{388}-\x{38a}\x{38c}\x{38e}-\x{3a1}\x{3a3}-\x{3f5}\x{3f7}-\x{482}\x{48a}-\x{52f}\x{531}-\x{556}\x{559}-\x{55f}\x{561}-\x{587}\x{589}\x{903}-\x{939}\x{93b}\x{93d}-\x{940}\x{949}-\x{94c}\x{94e}-\x{950}\x{958}-\x{961}\x{964}-\x{980}\x{982}\x{983}\x{985}-\x{98c}\x{98f}\x{990}\x{993}-\x{9a8}\x{9aa}-\x{9b0}\x{9b2}\x{9b6}-\x{9b9}\x{9bd}-\x{9c0}\x{9c7}\x{9c8}\x{9cb}\x{9cc}\x{9ce}\x{9d7}\x{9dc}\x{9dd}\x{9df}-\x{9e1}\x{9e6}-\x{9f1}\x{9f4}-\x{9fa}\x{a03}\x{a05}-\x{a0a}\x{a0f}\x{a10}\x{a13}-\x{a28}\x{a2a}-\x{a30}\x{a32}\x{a33}\x{a35}\x{a36}\x{a38}\x{a39}\x{a3e}-\x{a40}\x{a59}-\x{a5c}\x{a5e}\x{a66}-\x{a6f}\x{a72}-\x{a74}\x{a83}\x{a85}-\x{a8d}\x{a8f}-\x{a91}\x{a93}-\x{aa8}\x{aaa}-\x{ab0}\x{ab2}\x{ab3}\x{ab5}-\x{ab9}\x{abd}-\x{ac0}\x{ac9}\x{acb}\x{acc}\x{ad0}\x{ae0}\x{ae1}\x{ae6}-\x{af0}\x{af9}\x{b02}\x{b03}\x{b05}-\x{b0c}\x{b0f}\x{b10}\x{b13}-\x{b28}\x{b2a}-\x{b30}\x{b32}\x{b33}\x{b35}-\x{b39}\x{b3d}\x{b3e}\x{b40}\x{b47}\x{b48}\x{b4b}\x{b4c}\x{b57}\x{b5c}\x{b5d}\x{b5f}-\x{b61}\x{b66}-\x{b77}\x{b83}\x{b85}-\x{b8a}\x{b8e}-\x{b90}\x{b92}-\x{b95}\x{b99}\x{b9a}\x{b9c}\x{b9e}\x{b9f}\x{ba3}\x{ba4}\x{ba8}-\x{baa}\x{bae}-\x{bb9}\x{bbe}\x{bbf}\x{bc1}\x{bc2}\x{bc6}-\x{bc8}\x{bca}-\x{bcc}\x{bd0}\x{bd7}\x{be6}-\x{bf2}\x{c01}-\x{c03}\x{c05}-\x{c0c}\x{c0e}-\x{c10}\x{c12}-\x{c28}\x{c2a}-\x{c39}\x{c3d}\x{c41}-\x{c44}\x{c58}-\x{c5a}\x{c60}\x{c61}\x{c66}-\x{c6f}\x{c7f}\x{c82}\x{c83}\x{c85}-\x{c8c}\x{c8e}-\x{c90}\x{c92}-\x{ca8}\x{caa}-\x{cb3}\x{cb5}-\x{cb9}\x{cbd}-\x{cc4}\x{cc6}-\x{cc8}\x{cca}\x{ccb}\x{cd5}\x{cd6}\x{cde}\x{ce0}\x{ce1}\x{ce6}-\x{cef}\x{cf1}\x{cf2}\x{d02}\x{d03}\x{d05}-\x{d0c}\x{d0e}-\x{d10}\x{d12}-\x{d3a}\x{d3d}-\x{d40}\x{d46}-\x{d48}\x{d4a}-\x{d4c}\x{d4e}\x{d57}\x{d5f}-\x{d61}\x{d66}-\x{d75}\x{d79}-\x{d7f}\x{d82}\x{d83}\x{d85}-\x{d96}\x{d9a}-\x{db1}\x{db3}-\x{dbb}\x{dbd}\x{dc0}-\x{dc6}\x{dcf}-\x{dd1}\x{dd8}-\x{ddf}\x{de6}-\x{def}\x{df2}-\x{df4}\x{e01}-\x{e30}\x{e32}\x{e33}\x{e40}-\x{e46}\x{e4f}-\x{e5b}\x{e81}\x{e82}\x{e84}\x{e87}\x{e88}\x{e8a}\x{e8d}\x{e94}-\x{e97}\x{e99}-\x{e9f}\x{ea1}-\x{ea3}\x{ea5}\x{ea7}\x{eaa}\x{eab}\x{ead}-\x{eb0}\x{eb2}\x{eb3}\x{ebd}\x{ec0}-\x{ec4}\x{ec6}\x{ed0}-\x{ed9}\x{edc}-\x{edf}\x{f00}-\x{f17}\x{f1a}-\x{f34}\x{f36}\x{f38}\x{f3e}-\x{f47}\x{f49}-\x{f6c}\x{f7f}\x{f85}\x{f88}-\x{f8c}\x{fbe}-\x{fc5}\x{fc7}-\x{fcc}\x{fce}-\x{fda}\x{1000}-\x{102c}\x{1031}\x{1038}\x{103b}\x{103c}\x{103f}-\x{1057}\x{105a}-\x{105d}\x{1061}-\x{1070}\x{1075}-\x{1081}\x{1083}\x{1084}\x{1087}-\x{108c}\x{108e}-\x{109c}\x{109e}-\x{10c5}\x{10c7}\x{10cd}\x{10d0}-\x{1248}\x{124a}-\x{124d}\x{1250}-\x{1256}\x{1258}\x{125a}-\x{125d}\x{1260}-\x{1288}\x{128a}-\x{128d}\x{1290}-\x{12b0}\x{12b2}-\x{12b5}\x{12b8}-\x{12be}\x{12c0}\x{12c2}-\x{12c5}\x{12c8}-\x{12d6}\x{12d8}-\x{1310}\x{1312}-\x{1315}\x{1318}-\x{135a}\x{1360}-\x{137c}\x{1380}-\x{138f}\x{13a0}-\x{13f5}\x{13f8}-\x{13fd}\x{1401}-\x{167f}\x{1681}-\x{169a}\x{16a0}-\x{16f8}\x{1700}-\x{170c}\x{170e}-\x{1711}\x{1720}-\x{1731}\x{1735}\x{1736}\x{1740}-\x{1751}\x{1760}-\x{176c}\x{176e}-\x{1770}\x{1780}-\x{17b3}\x{17b6}\x{17be}-\x{17c5}\x{17c7}\x{17c8}\x{17d4}-\x{17da}\x{17dc}\x{17e0}-\x{17e9}\x{1810}-\x{1819}\x{1820}-\x{1877}\x{1880}-\x{18a8}\x{18aa}\x{18b0}-\x{18f5}\x{1900}-\x{191e}\x{1923}-\x{1926}\x{1929}-\x{192b}\x{1930}\x{1931}\x{1933}-\x{1938}\x{1946}-\x{196d}\x{1970}-\x{1974}\x{1980}-\x{19ab}\x{19b0}-\x{19c9}\x{19d0}-\x{19da}\x{1a00}-\x{1a16}\x{1a19}\x{1a1a}\x{1a1e}-\x{1a55}\x{1a57}\x{1a61}\x{1a63}\x{1a64}\x{1a6d}-\x{1a72}\x{1a80}-\x{1a89}\x{1a90}-\x{1a99}\x{1aa0}-\x{1aad}\x{1b04}-\x{1b33}\x{1b35}\x{1b3b}\x{1b3d}-\x{1b41}\x{1b43}-\x{1b4b}\x{1b50}-\x{1b6a}\x{1b74}-\x{1b7c}\x{1b82}-\x{1ba1}\x{1ba6}\x{1ba7}\x{1baa}\x{1bae}-\x{1be5}\x{1be7}\x{1bea}-\x{1bec}\x{1bee}\x{1bf2}\x{1bf3}\x{1bfc}-\x{1c2b}\x{1c34}\x{1c35}\x{1c3b}-\x{1c49}\x{1c4d}-\x{1c7f}\x{1cc0}-\x{1cc7}\x{1cd3}\x{1ce1}\x{1ce9}-\x{1cec}\x{1cee}-\x{1cf3}\x{1cf5}\x{1cf6}\x{1d00}-\x{1dbf}\x{1e00}-\x{1f15}\x{1f18}-\x{1f1d}\x{1f20}-\x{1f45}\x{1f48}-\x{1f4d}\x{1f50}-\x{1f57}\x{1f59}\x{1f5b}\x{1f5d}\x{1f5f}-\x{1f7d}\x{1f80}-\x{1fb4}\x{1fb6}-\x{1fbc}\x{1fbe}\x{1fc2}-\x{1fc4}\x{1fc6}-\x{1fcc}\x{1fd0}-\x{1fd3}\x{1fd6}-\x{1fdb}\x{1fe0}-\x{1fec}\x{1ff2}-\x{1ff4}\x{1ff6}-\x{1ffc}\x{200e}\x{2071}\x{207f}\x{2090}-\x{209c}\x{2102}\x{2107}\x{210a}-\x{2113}\x{2115}\x{2119}-\x{211d}\x{2124}\x{2126}\x{2128}\x{212a}-\x{212d}\x{212f}-\x{2139}\x{213c}-\x{213f}\x{2145}-\x{2149}\x{214e}\x{214f}\x{2160}-\x{2188}\x{2336}-\x{237a}\x{2395}\x{249c}-\x{24e9}\x{26ac}\x{2800}-\x{28ff}\x{2c00}-\x{2c2e}\x{2c30}-\x{2c5e}\x{2c60}-\x{2ce4}\x{2ceb}-\x{2cee}\x{2cf2}\x{2cf3}\x{2d00}-\x{2d25}\x{2d27}\x{2d2d}\x{2d30}-\x{2d67}\x{2d6f}\x{2d70}\x{2d80}-\x{2d96}\x{2da0}-\x{2da6}\x{2da8}-\x{2dae}\x{2db0}-\x{2db6}\x{2db8}-\x{2dbe}\x{2dc0}-\x{2dc6}\x{2dc8}-\x{2dce}\x{2dd0}-\x{2dd6}\x{2dd8}-\x{2dde}\x{3005}-\x{3007}\x{3021}-\x{3029}\x{302e}\x{302f}\x{3031}-\x{3035}\x{3038}-\x{303c}\x{3041}-\x{3096}\x{309d}-\x{309f}\x{30a1}-\x{30fa}\x{30fc}-\x{30ff}\x{3105}-\x{312d}\x{3131}-\x{318e}\x{3190}-\x{31ba}\x{31f0}-\x{321c}\x{3220}-\x{324f}\x{3260}-\x{327b}\x{327f}-\x{32b0}\x{32c0}-\x{32cb}\x{32d0}-\x{32fe}\x{3300}-\x{3376}\x{337b}-\x{33dd}\x{33e0}-\x{33fe}\x{3400}-\x{4db5}\x{4e00}-\x{9fd5}\x{a000}-\x{a48c}\x{a4d0}-\x{a60c}\x{a610}-\x{a62b}\x{a640}-\x{a66e}\x{a680}-\x{a69d}\x{a6a0}-\x{a6ef}\x{a6f2}-\x{a6f7}\x{a722}-\x{a787}\x{a789}-\x{a7ad}\x{a7b0}-\x{a7b7}\x{a7f7}-\x{a801}\x{a803}-\x{a805}\x{a807}-\x{a80a}\x{a80c}-\x{a824}\x{a827}\x{a830}-\x{a837}\x{a840}-\x{a873}\x{a880}-\x{a8c3}\x{a8ce}-\x{a8d9}\x{a8f2}-\x{a8fd}\x{a900}-\x{a925}\x{a92e}-\x{a946}\x{a952}\x{a953}\x{a95f}-\x{a97c}\x{a983}-\x{a9b2}\x{a9b4}\x{a9b5}\x{a9ba}\x{a9bb}\x{a9bd}-\x{a9cd}\x{a9cf}-\x{a9d9}\x{a9de}-\x{a9e4}\x{a9e6}-\x{a9fe}\x{aa00}-\x{aa28}\x{aa2f}\x{aa30}\x{aa33}\x{aa34}\x{aa40}-\x{aa42}\x{aa44}-\x{aa4b}\x{aa4d}\x{aa50}-\x{aa59}\x{aa5c}-\x{aa7b}\x{aa7d}-\x{aaaf}\x{aab1}\x{aab5}\x{aab6}\x{aab9}-\x{aabd}\x{aac0}\x{aac2}\x{aadb}-\x{aaeb}\x{aaee}-\x{aaf5}\x{ab01}-\x{ab06}\x{ab09}-\x{ab0e}\x{ab11}-\x{ab16}\x{ab20}-\x{ab26}\x{ab28}-\x{ab2e}\x{ab30}-\x{ab65}\x{ab70}-\x{abe4}\x{abe6}\x{abe7}\x{abe9}-\x{abec}\x{abf0}-\x{abf9}\x{ac00}-\x{d7a3}\x{d7b0}-\x{d7c6}\x{d7cb}-\x{d7fb}\x{e000}-\x{fa6d}\x{fa70}-\x{fad9}\x{fb00}-\x{fb06}\x{fb13}-\x{fb17}\x{ff21}-\x{ff3a}\x{ff41}-\x{ff5a}\x{ff66}-\x{ffbe}\x{ffc2}-\x{ffc7}\x{ffca}-\x{ffcf}\x{ffd2}-\x{ffd7}\x{ffda}-\x{ffdc}\x{10000}-\x{1000b}\x{1000d}-\x{10026}\x{10028}-\x{1003a}\x{1003c}\x{1003d}\x{1003f}-\x{1004d}\x{10050}-\x{1005d}\x{10080}-\x{100fa}\x{10100}\x{10102}\x{10107}-\x{10133}\x{10137}-\x{1013f}\x{101d0}-\x{101fc}\x{10280}-\x{1029c}\x{102a0}-\x{102d0}\x{10300}-\x{10323}\x{10330}-\x{1034a}\x{10350}-\x{10375}\x{10380}-\x{1039d}\x{1039f}-\x{103c3}\x{103c8}-\x{103d5}\x{10400}-\x{1049d}\x{104a0}-\x{104a9}\x{10500}-\x{10527}\x{10530}-\x{10563}\x{1056f}\x{10600}-\x{10736}\x{10740}-\x{10755}\x{10760}-\x{10767}\x{11000}\x{11002}-\x{11037}\x{11047}-\x{1104d}\x{11066}-\x{1106f}\x{11082}-\x{110b2}\x{110b7}\x{110b8}\x{110bb}-\x{110c1}\x{110d0}-\x{110e8}\x{110f0}-\x{110f9}\x{11103}-\x{11126}\x{1112c}\x{11136}-\x{11143}\x{11150}-\x{11172}\x{11174}-\x{11176}\x{11182}-\x{111b5}\x{111bf}-\x{111c9}\x{111cd}\x{111d0}-\x{111df}\x{111e1}-\x{111f4}\x{11200}-\x{11211}\x{11213}-\x{1122e}\x{11232}\x{11233}\x{11235}\x{11238}-\x{1123d}\x{11280}-\x{11286}\x{11288}\x{1128a}-\x{1128d}\x{1128f}-\x{1129d}\x{1129f}-\x{112a9}\x{112b0}-\x{112de}\x{112e0}-\x{112e2}\x{112f0}-\x{112f9}\x{11302}\x{11303}\x{11305}-\x{1130c}\x{1130f}\x{11310}\x{11313}-\x{11328}\x{1132a}-\x{11330}\x{11332}\x{11333}\x{11335}-\x{11339}\x{1133d}-\x{1133f}\x{11341}-\x{11344}\x{11347}\x{11348}\x{1134b}-\x{1134d}\x{11350}\x{11357}\x{1135d}-\x{11363}\x{11480}-\x{114b2}\x{114b9}\x{114bb}-\x{114be}\x{114c1}\x{114c4}-\x{114c7}\x{114d0}-\x{114d9}\x{11580}-\x{115b1}\x{115b8}-\x{115bb}\x{115be}\x{115c1}-\x{115db}\x{11600}-\x{11632}\x{1163b}\x{1163c}\x{1163e}\x{11641}-\x{11644}\x{11650}-\x{11659}\x{11680}-\x{116aa}\x{116ac}\x{116ae}\x{116af}\x{116b6}\x{116c0}-\x{116c9}\x{11700}-\x{11719}\x{11720}\x{11721}\x{11726}\x{11730}-\x{1173f}\x{118a0}-\x{118f2}\x{118ff}\x{11ac0}-\x{11af8}\x{12000}-\x{12399}\x{12400}-\x{1246e}\x{12470}-\x{12474}\x{12480}-\x{12543}\x{13000}-\x{1342e}\x{14400}-\x{14646}\x{16800}-\x{16a38}\x{16a40}-\x{16a5e}\x{16a60}-\x{16a69}\x{16a6e}\x{16a6f}\x{16ad0}-\x{16aed}\x{16af5}\x{16b00}-\x{16b2f}\x{16b37}-\x{16b45}\x{16b50}-\x{16b59}\x{16b5b}-\x{16b61}\x{16b63}-\x{16b77}\x{16b7d}-\x{16b8f}\x{16f00}-\x{16f44}\x{16f50}-\x{16f7e}\x{16f93}-\x{16f9f}\x{1b000}\x{1b001}\x{1bc00}-\x{1bc6a}\x{1bc70}-\x{1bc7c}\x{1bc80}-\x{1bc88}\x{1bc90}-\x{1bc99}\x{1bc9c}\x{1bc9f}\x{1d000}-\x{1d0f5}\x{1d100}-\x{1d126}\x{1d129}-\x{1d166}\x{1d16a}-\x{1d172}\x{1d183}\x{1d184}\x{1d18c}-\x{1d1a9}\x{1d1ae}-\x{1d1e8}\x{1d360}-\x{1d371}\x{1d400}-\x{1d454}\x{1d456}-\x{1d49c}\x{1d49e}\x{1d49f}\x{1d4a2}\x{1d4a5}\x{1d4a6}\x{1d4a9}-\x{1d4ac}\x{1d4ae}-\x{1d4b9}\x{1d4bb}\x{1d4bd}-\x{1d4c3}\x{1d4c5}-\x{1d505}\x{1d507}-\x{1d50a}\x{1d50d}-\x{1d514}\x{1d516}-\x{1d51c}\x{1d51e}-\x{1d539}\x{1d53b}-\x{1d53e}\x{1d540}-\x{1d544}\x{1d546}\x{1d54a}-\x{1d550}\x{1d552}-\x{1d6a5}\x{1d6a8}-\x{1d6da}\x{1d6dc}-\x{1d714}\x{1d716}-\x{1d74e}\x{1d750}-\x{1d788}\x{1d78a}-\x{1d7c2}\x{1d7c4}-\x{1d7cb}\x{1d800}-\x{1d9ff}\x{1da37}-\x{1da3a}\x{1da6d}-\x{1da74}\x{1da76}-\x{1da83}\x{1da85}-\x{1da8b}\x{1f110}-\x{1f12e}\x{1f130}-\x{1f169}\x{1f170}-\x{1f19a}\x{1f1e6}-\x{1f202}\x{1f210}-\x{1f23a}\x{1f240}-\x{1f248}\x{1f250}\x{1f251}\x{20000}-\x{2a6d6}\x{2a700}-\x{2b734}\x{2b740}-\x{2b81d}\x{2b820}-\x{2cea1}\x{2f800}-\x{2fa1d}\x{f0000}-\x{ffffd}\x{100000}-\x{10fffd}])|([\x{590}\x{5be}\x{5c0}\x{5c3}\x{5c6}\x{5c8}-\x{5ff}\x{7c0}-\x{7ea}\x{7f4}\x{7f5}\x{7fa}-\x{815}\x{81a}\x{824}\x{828}\x{82e}-\x{858}\x{85c}-\x{89f}\x{200f}\x{fb1d}\x{fb1f}-\x{fb28}\x{fb2a}-\x{fb4f}\x{10800}-\x{1091e}\x{10920}-\x{10a00}\x{10a04}\x{10a07}-\x{10a0b}\x{10a10}-\x{10a37}\x{10a3b}-\x{10a3e}\x{10a40}-\x{10ae4}\x{10ae7}-\x{10b38}\x{10b40}-\x{10e5f}\x{10e7f}-\x{10fff}\x{1e800}-\x{1e8cf}\x{1e8d7}-\x{1edff}\x{1ef00}-\x{1efff}\x{608}\x{60b}\x{60d}\x{61b}-\x{64a}\x{66d}-\x{66f}\x{671}-\x{6d5}\x{6e5}\x{6e6}\x{6ee}\x{6ef}\x{6fa}-\x{710}\x{712}-\x{72f}\x{74b}-\x{7a5}\x{7b1}-\x{7bf}\x{8a0}-\x{8e2}\x{fb50}-\x{fd3d}\x{fd40}-\x{fdcf}\x{fdf0}-\x{fdfc}\x{fdfe}\x{fdff}\x{fe70}-\x{fefe}\x{1ee00}-\x{1eeef}\x{1eef2}-\x{1eeff}]))/u';
207  // @codeCoverageIgnoreEnd
208 
215  static function factory( $code ) {
217 
218  if ( isset( $wgDummyLanguageCodes[$code] ) ) {
219  $code = $wgDummyLanguageCodes[$code];
220  }
221 
222  // get the language object to process
223  $langObj = self::$mLangObjCache[$code] ?? self::newFromCode( $code );
224 
225  // merge the language object in to get it up front in the cache
226  self::$mLangObjCache = array_merge( [ $code => $langObj ], self::$mLangObjCache );
227  // get rid of the oldest ones in case we have an overflow
228  self::$mLangObjCache = array_slice( self::$mLangObjCache, 0, $wgLangObjCacheSize, true );
229 
230  return $langObj;
231  }
232 
240  protected static function newFromCode( $code, $fallback = false ) {
241  if ( !self::isValidCode( $code ) ) {
242  throw new MWException( "Invalid language code \"$code\"" );
243  }
244 
245  if ( !self::isValidBuiltInCode( $code ) ) {
246  // It's not possible to customise this code with class files, so
247  // just return a Language object. This is to support uselang= hacks.
248  $lang = new Language;
249  $lang->mCode = $code;
250  return $lang;
251  }
252 
253  // Check if there is a language class for the code
254  $class = self::classFromCode( $code, $fallback );
255  // LanguageCode does not inherit Language
256  if ( class_exists( $class ) && is_a( $class, 'Language', true ) ) {
257  $lang = new $class;
258  return $lang;
259  }
260 
261  // Keep trying the fallback list until we find an existing class
262  $fallbacks = self::getFallbacksFor( $code );
263  foreach ( $fallbacks as $fallbackCode ) {
264  if ( !self::isValidBuiltInCode( $fallbackCode ) ) {
265  throw new MWException( "Invalid fallback '$fallbackCode' in fallback sequence for '$code'" );
266  }
267 
268  $class = self::classFromCode( $fallbackCode );
269  if ( class_exists( $class ) ) {
270  $lang = new $class;
271  $lang->mCode = $code;
272  return $lang;
273  }
274  }
275 
276  throw new MWException( "Invalid fallback sequence for language '$code'" );
277  }
278 
284  public static function clearCaches() {
285  if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
286  throw new MWException( __METHOD__ . ' must not be used outside tests' );
287  }
288  self::$dataCache = null;
289  // Reinitialize $dataCache, since it's expected to always be available
290  self::getLocalisationCache();
291  self::$mLangObjCache = [];
292  self::$fallbackLanguageCache = [];
293  self::$grammarTransformations = null;
294  self::$languageNameCache = null;
295  }
296 
305  public static function isSupportedLanguage( $code ) {
306  if ( !self::isValidBuiltInCode( $code ) ) {
307  return false;
308  }
309 
310  if ( $code === 'qqq' ) {
311  return false;
312  }
313 
314  return is_readable( self::getMessagesFileName( $code ) ) ||
315  is_readable( self::getJsonMessagesFileName( $code ) );
316  }
317 
333  public static function isWellFormedLanguageTag( $code, $lenient = false ) {
334  $alpha = '[a-z]';
335  $digit = '[0-9]';
336  $alphanum = '[a-z0-9]';
337  $x = 'x'; # private use singleton
338  $singleton = '[a-wy-z]'; # other singleton
339  $s = $lenient ? '[-_]' : '-';
340 
341  $language = "$alpha{2,8}|$alpha{2,3}$s$alpha{3}";
342  $script = "$alpha{4}"; # ISO 15924
343  $region = "(?:$alpha{2}|$digit{3})"; # ISO 3166-1 alpha-2 or UN M.49
344  $variant = "(?:$alphanum{5,8}|$digit$alphanum{3})";
345  $extension = "$singleton(?:$s$alphanum{2,8})+";
346  $privateUse = "$x(?:$s$alphanum{1,8})+";
347 
348  # Define certain grandfathered codes, since otherwise the regex is pretty useless.
349  # Since these are limited, this is safe even later changes to the registry --
350  # the only oddity is that it might change the type of the tag, and thus
351  # the results from the capturing groups.
352  # https://www.iana.org/assignments/language-subtag-registry
353 
354  $grandfathered = "en{$s}GB{$s}oed"
355  . "|i{$s}(?:ami|bnn|default|enochian|hak|klingon|lux|mingo|navajo|pwn|tao|tay|tsu)"
356  . "|no{$s}(?:bok|nyn)"
357  . "|sgn{$s}(?:BE{$s}(?:fr|nl)|CH{$s}de)"
358  . "|zh{$s}min{$s}nan";
359 
360  $variantList = "$variant(?:$s$variant)*";
361  $extensionList = "$extension(?:$s$extension)*";
362 
363  $langtag = "(?:($language)"
364  . "(?:$s$script)?"
365  . "(?:$s$region)?"
366  . "(?:$s$variantList)?"
367  . "(?:$s$extensionList)?"
368  . "(?:$s$privateUse)?)";
369 
370  # The final breakdown, with capturing groups for each of these components
371  # The variants, extensions, grandfathered, and private-use may have interior '-'
372 
373  $root = "^(?:$langtag|$privateUse|$grandfathered)$";
374 
375  return (bool)preg_match( "/$root/", strtolower( $code ) );
376  }
377 
387  public static function isValidCode( $code ) {
388  static $cache = [];
389  Assert::parameterType( 'string', $code, '$code' );
390  if ( !isset( $cache[$code] ) ) {
391  // People think language codes are html safe, so enforce it.
392  // Ideally we should only allow a-zA-Z0-9-
393  // but, .+ and other chars are often used for {{int:}} hacks
394  // see bugs T39564, T39587, T38938
395  $cache[$code] =
396  // Protect against path traversal
397  strcspn( $code, ":/\\\000&<>'\"" ) === strlen( $code )
398  && !preg_match( MediaWikiTitleCodec::getTitleInvalidRegex(), $code );
399  }
400  return $cache[$code];
401  }
402 
412  public static function isValidBuiltInCode( $code ) {
413  Assert::parameterType( 'string', $code, '$code' );
414 
415  return (bool)preg_match( '/^[a-z0-9-]{2,}$/', $code );
416  }
417 
426  public static function isKnownLanguageTag( $tag ) {
427  // Quick escape for invalid input to avoid exceptions down the line
428  // when code tries to process tags which are not valid at all.
429  if ( !self::isValidBuiltInCode( $tag ) ) {
430  return false;
431  }
432 
433  if ( isset( MediaWiki\Languages\Data\Names::$names[$tag] )
434  || self::fetchLanguageName( $tag, $tag ) !== ''
435  ) {
436  return true;
437  }
438 
439  return false;
440  }
441 
447  public static function getLocalisationCache() {
448  if ( is_null( self::$dataCache ) ) {
450  $class = $wgLocalisationCacheConf['class'];
451  self::$dataCache = new $class( $wgLocalisationCacheConf );
452  }
453  return self::$dataCache;
454  }
455 
456  function __construct() {
457  $this->mConverter = new FakeConverter( $this );
458  // Set the code to the name of the descendant
459  if ( static::class === 'Language' ) {
460  $this->mCode = 'en';
461  } else {
462  $this->mCode = str_replace( '_', '-', strtolower( substr( static::class, 8 ) ) );
463  }
464  self::getLocalisationCache();
465  }
466 
470  function __destruct() {
471  foreach ( $this as $name => $value ) {
472  unset( $this->$name );
473  }
474  }
475 
480  function initContLang() {
481  }
482 
487  public function getFallbackLanguages() {
488  return self::getFallbacksFor( $this->mCode );
489  }
490 
495  public function getBookstoreList() {
496  return self::$dataCache->getItem( $this->mCode, 'bookstoreList' );
497  }
498 
505  public function getNamespaces() {
506  if ( is_null( $this->namespaceNames ) ) {
508 
509  $validNamespaces = MWNamespace::getCanonicalNamespaces();
510 
511  $this->namespaceNames = $wgExtraNamespaces +
512  self::$dataCache->getItem( $this->mCode, 'namespaceNames' );
513  $this->namespaceNames += $validNamespaces;
514 
515  $this->namespaceNames[NS_PROJECT] = $wgMetaNamespace;
516  if ( $wgMetaNamespaceTalk ) {
517  $this->namespaceNames[NS_PROJECT_TALK] = $wgMetaNamespaceTalk;
518  } else {
519  $talk = $this->namespaceNames[NS_PROJECT_TALK];
520  $this->namespaceNames[NS_PROJECT_TALK] =
521  $this->fixVariableInNamespace( $talk );
522  }
523 
524  # Sometimes a language will be localised but not actually exist on this wiki.
525  foreach ( $this->namespaceNames as $key => $text ) {
526  if ( !isset( $validNamespaces[$key] ) ) {
527  unset( $this->namespaceNames[$key] );
528  }
529  }
530 
531  # The above mixing may leave namespaces out of canonical order.
532  # Re-order by namespace ID number...
533  ksort( $this->namespaceNames );
534 
535  Hooks::run( 'LanguageGetNamespaces', [ &$this->namespaceNames ] );
536  }
537 
538  return $this->namespaceNames;
539  }
540 
545  public function setNamespaces( array $namespaces ) {
546  $this->namespaceNames = $namespaces;
547  $this->mNamespaceIds = null;
548  }
549 
553  public function resetNamespaces() {
554  $this->namespaceNames = null;
555  $this->mNamespaceIds = null;
556  $this->namespaceAliases = null;
557  }
558 
565  public function getFormattedNamespaces() {
566  $ns = $this->getNamespaces();
567  foreach ( $ns as $k => $v ) {
568  $ns[$k] = strtr( $v, '_', ' ' );
569  }
570  return $ns;
571  }
572 
584  public function getNsText( $index ) {
585  $ns = $this->getNamespaces();
586  return $ns[$index] ?? false;
587  }
588 
602  public function getFormattedNsText( $index ) {
603  $ns = $this->getNsText( $index );
604  return strtr( $ns, '_', ' ' );
605  }
606 
615  public function getGenderNsText( $index, $gender ) {
617 
618  $ns = $wgExtraGenderNamespaces +
619  (array)self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' );
620 
621  return $ns[$index][$gender] ?? $this->getNsText( $index );
622  }
623 
630  public function needsGenderDistinction() {
632  if ( count( $wgExtraGenderNamespaces ) > 0 ) {
633  // $wgExtraGenderNamespaces overrides everything
634  return true;
635  } elseif ( isset( $wgExtraNamespaces[NS_USER] ) && isset( $wgExtraNamespaces[NS_USER_TALK] ) ) {
637  // $wgExtraNamespaces overrides any gender aliases specified in i18n files
638  return false;
639  } else {
640  // Check what is in i18n files
641  $aliases = self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' );
642  return count( $aliases ) > 0;
643  }
644  }
645 
654  function getLocalNsIndex( $text ) {
655  $lctext = $this->lc( $text );
656  $ids = $this->getNamespaceIds();
657  return $ids[$lctext] ?? false;
658  }
659 
663  public function getNamespaceAliases() {
664  if ( is_null( $this->namespaceAliases ) ) {
665  $aliases = self::$dataCache->getItem( $this->mCode, 'namespaceAliases' );
666  if ( !$aliases ) {
667  $aliases = [];
668  } else {
669  foreach ( $aliases as $name => $index ) {
670  if ( $index === NS_PROJECT_TALK ) {
671  unset( $aliases[$name] );
672  $name = $this->fixVariableInNamespace( $name );
673  $aliases[$name] = $index;
674  }
675  }
676  }
677 
679  $genders = $wgExtraGenderNamespaces +
680  (array)self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' );
681  foreach ( $genders as $index => $forms ) {
682  foreach ( $forms as $alias ) {
683  $aliases[$alias] = $index;
684  }
685  }
686 
687  # Also add converted namespace names as aliases, to avoid confusion.
688  $convertedNames = [];
689  foreach ( $this->getVariants() as $variant ) {
690  if ( $variant === $this->mCode ) {
691  continue;
692  }
693  foreach ( $this->getNamespaces() as $ns => $_ ) {
694  $convertedNames[$this->getConverter()->convertNamespace( $ns, $variant )] = $ns;
695  }
696  }
697 
698  $this->namespaceAliases = $aliases + $convertedNames;
699 
700  # Filter out aliases to namespaces that don't exist, e.g. from extensions
701  # that aren't loaded here but are included in the l10n cache.
702  # (array_intersect preserves keys from its first argument)
703  $this->namespaceAliases = array_intersect(
704  $this->namespaceAliases,
705  array_keys( $this->getNamespaces() )
706  );
707  }
708 
710  }
711 
715  public function getNamespaceIds() {
716  if ( is_null( $this->mNamespaceIds ) ) {
717  global $wgNamespaceAliases;
718  # Put namespace names and aliases into a hashtable.
719  # If this is too slow, then we should arrange it so that it is done
720  # before caching. The catch is that at pre-cache time, the above
721  # class-specific fixup hasn't been done.
722  $this->mNamespaceIds = [];
723  foreach ( $this->getNamespaces() as $index => $name ) {
724  $this->mNamespaceIds[$this->lc( $name )] = $index;
725  }
726  foreach ( $this->getNamespaceAliases() as $name => $index ) {
727  $this->mNamespaceIds[$this->lc( $name )] = $index;
728  }
729  if ( $wgNamespaceAliases ) {
730  foreach ( $wgNamespaceAliases as $name => $index ) {
731  $this->mNamespaceIds[$this->lc( $name )] = $index;
732  }
733  }
734  }
735  return $this->mNamespaceIds;
736  }
737 
745  public function getNsIndex( $text ) {
746  $lctext = $this->lc( $text );
747  $ns = MWNamespace::getCanonicalIndex( $lctext );
748  if ( $ns !== null ) {
749  return $ns;
750  }
751  $ids = $this->getNamespaceIds();
752  return $ids[$lctext] ?? false;
753  }
754 
762  public function getVariantname( $code, $usemsg = true ) {
763  $msg = "variantname-$code";
764  if ( $usemsg && wfMessage( $msg )->exists() ) {
765  return $this->getMessageFromDB( $msg );
766  }
767  $name = self::fetchLanguageName( $code );
768  if ( $name ) {
769  return $name; # if it's defined as a language name, show that
770  } else {
771  # otherwise, output the language code
772  return $code;
773  }
774  }
775 
779  public function getDatePreferences() {
780  return self::$dataCache->getItem( $this->mCode, 'datePreferences' );
781  }
782 
786  function getDateFormats() {
787  return self::$dataCache->getItem( $this->mCode, 'dateFormats' );
788  }
789 
793  public function getDefaultDateFormat() {
794  $df = self::$dataCache->getItem( $this->mCode, 'defaultDateFormat' );
795  if ( $df === 'dmy or mdy' ) {
796  global $wgAmericanDates;
797  return $wgAmericanDates ? 'mdy' : 'dmy';
798  } else {
799  return $df;
800  }
801  }
802 
806  public function getDatePreferenceMigrationMap() {
807  return self::$dataCache->getItem( $this->mCode, 'datePreferenceMigrationMap' );
808  }
809 
813  public function getExtraUserToggles() {
814  return (array)self::$dataCache->getItem( $this->mCode, 'extraUserToggles' );
815  }
816 
821  function getUserToggle( $tog ) {
822  return $this->getMessageFromDB( "tog-$tog" );
823  }
824 
836  public static function fetchLanguageNames( $inLanguage = self::AS_AUTONYMS, $include = 'mw' ) {
837  $cacheKey = $inLanguage === self::AS_AUTONYMS ? 'null' : $inLanguage;
838  $cacheKey .= ":$include";
839  if ( self::$languageNameCache === null ) {
840  self::$languageNameCache = new HashBagOStuff( [ 'maxKeys' => 20 ] );
841  }
842 
843  $ret = self::$languageNameCache->get( $cacheKey );
844  if ( !$ret ) {
845  $ret = self::fetchLanguageNamesUncached( $inLanguage, $include );
846  self::$languageNameCache->set( $cacheKey, $ret );
847  }
848  return $ret;
849  }
850 
861  private static function fetchLanguageNamesUncached(
862  $inLanguage = self::AS_AUTONYMS,
863  $include = 'mw'
864  ) {
865  global $wgExtraLanguageNames, $wgUsePigLatinVariant;
866 
867  // If passed an invalid language code to use, fallback to en
868  if ( $inLanguage !== self::AS_AUTONYMS && !self::isValidCode( $inLanguage ) ) {
869  $inLanguage = 'en';
870  }
871 
872  $names = [];
873 
874  if ( $inLanguage ) {
875  # TODO: also include when $inLanguage is null, when this code is more efficient
876  Hooks::run( 'LanguageGetTranslatedLanguageNames', [ &$names, $inLanguage ] );
877  }
878 
879  $mwNames = $wgExtraLanguageNames + MediaWiki\Languages\Data\Names::$names;
880  if ( $wgUsePigLatinVariant ) {
881  // Pig Latin (for variant development)
882  $mwNames['en-x-piglatin'] = 'Igpay Atinlay';
883  }
884 
885  foreach ( $mwNames as $mwCode => $mwName ) {
886  # - Prefer own MediaWiki native name when not using the hook
887  # - For other names just add if not added through the hook
888  if ( $mwCode === $inLanguage || !isset( $names[$mwCode] ) ) {
889  $names[$mwCode] = $mwName;
890  }
891  }
892 
893  if ( $include === self::ALL ) {
894  ksort( $names );
895  return $names;
896  }
897 
898  $returnMw = [];
899  $coreCodes = array_keys( $mwNames );
900  foreach ( $coreCodes as $coreCode ) {
901  $returnMw[$coreCode] = $names[$coreCode];
902  }
903 
904  if ( $include === self::SUPPORTED ) {
905  $namesMwFile = [];
906  # We do this using a foreach over the codes instead of a directory
907  # loop so that messages files in extensions will work correctly.
908  foreach ( $returnMw as $code => $value ) {
909  if ( is_readable( self::getMessagesFileName( $code ) )
910  || is_readable( self::getJsonMessagesFileName( $code ) )
911  ) {
912  $namesMwFile[$code] = $names[$code];
913  }
914  }
915 
916  ksort( $namesMwFile );
917  return $namesMwFile;
918  }
919 
920  ksort( $returnMw );
921  # 'mw' option; default if it's not one of the other two options (all/mwfile)
922  return $returnMw;
923  }
924 
933  public static function fetchLanguageName(
934  $code,
935  $inLanguage = self::AS_AUTONYMS,
936  $include = self::ALL
937  ) {
938  $code = strtolower( $code );
939  $array = self::fetchLanguageNames( $inLanguage, $include );
940  return !array_key_exists( $code, $array ) ? '' : $array[$code];
941  }
942 
949  public function getMessageFromDB( $msg ) {
950  return $this->msg( $msg )->text();
951  }
952 
959  protected function msg( $msg ) {
960  return wfMessage( $msg )->inLanguage( $this );
961  }
962 
967  public function getMonthName( $key ) {
968  return $this->getMessageFromDB( self::$mMonthMsgs[$key - 1] );
969  }
970 
974  public function getMonthNamesArray() {
975  $monthNames = [ '' ];
976  for ( $i = 1; $i < 13; $i++ ) {
977  $monthNames[] = $this->getMonthName( $i );
978  }
979  return $monthNames;
980  }
981 
986  public function getMonthNameGen( $key ) {
987  return $this->getMessageFromDB( self::$mMonthGenMsgs[$key - 1] );
988  }
989 
994  public function getMonthAbbreviation( $key ) {
995  return $this->getMessageFromDB( self::$mMonthAbbrevMsgs[$key - 1] );
996  }
997 
1001  public function getMonthAbbreviationsArray() {
1002  $monthNames = [ '' ];
1003  for ( $i = 1; $i < 13; $i++ ) {
1004  $monthNames[] = $this->getMonthAbbreviation( $i );
1005  }
1006  return $monthNames;
1007  }
1008 
1013  public function getWeekdayName( $key ) {
1014  return $this->getMessageFromDB( self::$mWeekdayMsgs[$key - 1] );
1015  }
1016 
1021  function getWeekdayAbbreviation( $key ) {
1022  return $this->getMessageFromDB( self::$mWeekdayAbbrevMsgs[$key - 1] );
1023  }
1024 
1029  function getIranianCalendarMonthName( $key ) {
1030  return $this->getMessageFromDB( self::$mIranianCalendarMonthMsgs[$key - 1] );
1031  }
1032 
1037  function getHebrewCalendarMonthName( $key ) {
1038  return $this->getMessageFromDB( self::$mHebrewCalendarMonthMsgs[$key - 1] );
1039  }
1040 
1046  return $this->getMessageFromDB( self::$mHebrewCalendarMonthGenMsgs[$key - 1] );
1047  }
1048 
1053  function getHijriCalendarMonthName( $key ) {
1054  return $this->getMessageFromDB( self::$mHijriCalendarMonthMsgs[$key - 1] );
1055  }
1056 
1065  private static function dateTimeObjFormat( &$dateTimeObj, $ts, $zone, $code ) {
1066  if ( !$dateTimeObj ) {
1067  $dateTimeObj = DateTime::createFromFormat(
1068  'YmdHis', $ts, $zone ?: new DateTimeZone( 'UTC' )
1069  );
1070  }
1071  return $dateTimeObj->format( $code );
1072  }
1073 
1143  public function sprintfDate( $format, $ts, DateTimeZone $zone = null, &$ttl = 'unused' ) {
1144  $s = '';
1145  $raw = false;
1146  $roman = false;
1147  $hebrewNum = false;
1148  $dateTimeObj = false;
1149  $rawToggle = false;
1150  $iranian = false;
1151  $hebrew = false;
1152  $hijri = false;
1153  $thai = false;
1154  $minguo = false;
1155  $tenno = false;
1156 
1157  $usedSecond = false;
1158  $usedMinute = false;
1159  $usedHour = false;
1160  $usedAMPM = false;
1161  $usedDay = false;
1162  $usedWeek = false;
1163  $usedMonth = false;
1164  $usedYear = false;
1165  $usedISOYear = false;
1166  $usedIsLeapYear = false;
1167 
1168  $usedHebrewMonth = false;
1169  $usedIranianMonth = false;
1170  $usedHijriMonth = false;
1171  $usedHebrewYear = false;
1172  $usedIranianYear = false;
1173  $usedHijriYear = false;
1174  $usedTennoYear = false;
1175 
1176  if ( strlen( $ts ) !== 14 ) {
1177  throw new MWException( __METHOD__ . ": The timestamp $ts should have 14 characters" );
1178  }
1179 
1180  if ( !ctype_digit( $ts ) ) {
1181  throw new MWException( __METHOD__ . ": The timestamp $ts should be a number" );
1182  }
1183 
1184  $formatLength = strlen( $format );
1185  for ( $p = 0; $p < $formatLength; $p++ ) {
1186  $num = false;
1187  $code = $format[$p];
1188  if ( $code == 'x' && $p < $formatLength - 1 ) {
1189  $code .= $format[++$p];
1190  }
1191 
1192  if ( ( $code === 'xi'
1193  || $code === 'xj'
1194  || $code === 'xk'
1195  || $code === 'xm'
1196  || $code === 'xo'
1197  || $code === 'xt' )
1198  && $p < $formatLength - 1 ) {
1199  $code .= $format[++$p];
1200  }
1201 
1202  switch ( $code ) {
1203  case 'xx':
1204  $s .= 'x';
1205  break;
1206  case 'xn':
1207  $raw = true;
1208  break;
1209  case 'xN':
1210  $rawToggle = !$rawToggle;
1211  break;
1212  case 'xr':
1213  $roman = true;
1214  break;
1215  case 'xh':
1216  $hebrewNum = true;
1217  break;
1218  case 'xg':
1219  $usedMonth = true;
1220  $s .= $this->getMonthNameGen( substr( $ts, 4, 2 ) );
1221  break;
1222  case 'xjx':
1223  $usedHebrewMonth = true;
1224  if ( !$hebrew ) {
1225  $hebrew = self::tsToHebrew( $ts );
1226  }
1227  $s .= $this->getHebrewCalendarMonthNameGen( $hebrew[1] );
1228  break;
1229  case 'd':
1230  $usedDay = true;
1231  $num = substr( $ts, 6, 2 );
1232  break;
1233  case 'D':
1234  $usedDay = true;
1235  $s .= $this->getWeekdayAbbreviation(
1236  self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'w' ) + 1
1237  );
1238  break;
1239  case 'j':
1240  $usedDay = true;
1241  $num = intval( substr( $ts, 6, 2 ) );
1242  break;
1243  case 'xij':
1244  $usedDay = true;
1245  if ( !$iranian ) {
1246  $iranian = self::tsToIranian( $ts );
1247  }
1248  $num = $iranian[2];
1249  break;
1250  case 'xmj':
1251  $usedDay = true;
1252  if ( !$hijri ) {
1253  $hijri = self::tsToHijri( $ts );
1254  }
1255  $num = $hijri[2];
1256  break;
1257  case 'xjj':
1258  $usedDay = true;
1259  if ( !$hebrew ) {
1260  $hebrew = self::tsToHebrew( $ts );
1261  }
1262  $num = $hebrew[2];
1263  break;
1264  case 'l':
1265  $usedDay = true;
1266  $s .= $this->getWeekdayName(
1267  self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'w' ) + 1
1268  );
1269  break;
1270  case 'F':
1271  $usedMonth = true;
1272  $s .= $this->getMonthName( substr( $ts, 4, 2 ) );
1273  break;
1274  case 'xiF':
1275  $usedIranianMonth = true;
1276  if ( !$iranian ) {
1277  $iranian = self::tsToIranian( $ts );
1278  }
1279  $s .= $this->getIranianCalendarMonthName( $iranian[1] );
1280  break;
1281  case 'xmF':
1282  $usedHijriMonth = true;
1283  if ( !$hijri ) {
1284  $hijri = self::tsToHijri( $ts );
1285  }
1286  $s .= $this->getHijriCalendarMonthName( $hijri[1] );
1287  break;
1288  case 'xjF':
1289  $usedHebrewMonth = true;
1290  if ( !$hebrew ) {
1291  $hebrew = self::tsToHebrew( $ts );
1292  }
1293  $s .= $this->getHebrewCalendarMonthName( $hebrew[1] );
1294  break;
1295  case 'm':
1296  $usedMonth = true;
1297  $num = substr( $ts, 4, 2 );
1298  break;
1299  case 'M':
1300  $usedMonth = true;
1301  $s .= $this->getMonthAbbreviation( substr( $ts, 4, 2 ) );
1302  break;
1303  case 'n':
1304  $usedMonth = true;
1305  $num = intval( substr( $ts, 4, 2 ) );
1306  break;
1307  case 'xin':
1308  $usedIranianMonth = true;
1309  if ( !$iranian ) {
1310  $iranian = self::tsToIranian( $ts );
1311  }
1312  $num = $iranian[1];
1313  break;
1314  case 'xmn':
1315  $usedHijriMonth = true;
1316  if ( !$hijri ) {
1317  $hijri = self::tsToHijri( $ts );
1318  }
1319  $num = $hijri[1];
1320  break;
1321  case 'xjn':
1322  $usedHebrewMonth = true;
1323  if ( !$hebrew ) {
1324  $hebrew = self::tsToHebrew( $ts );
1325  }
1326  $num = $hebrew[1];
1327  break;
1328  case 'xjt':
1329  $usedHebrewMonth = true;
1330  if ( !$hebrew ) {
1331  $hebrew = self::tsToHebrew( $ts );
1332  }
1333  $num = $hebrew[3];
1334  break;
1335  case 'Y':
1336  $usedYear = true;
1337  $num = substr( $ts, 0, 4 );
1338  break;
1339  case 'xiY':
1340  $usedIranianYear = true;
1341  if ( !$iranian ) {
1342  $iranian = self::tsToIranian( $ts );
1343  }
1344  $num = $iranian[0];
1345  break;
1346  case 'xmY':
1347  $usedHijriYear = true;
1348  if ( !$hijri ) {
1349  $hijri = self::tsToHijri( $ts );
1350  }
1351  $num = $hijri[0];
1352  break;
1353  case 'xjY':
1354  $usedHebrewYear = true;
1355  if ( !$hebrew ) {
1356  $hebrew = self::tsToHebrew( $ts );
1357  }
1358  $num = $hebrew[0];
1359  break;
1360  case 'xkY':
1361  $usedYear = true;
1362  if ( !$thai ) {
1363  $thai = self::tsToYear( $ts, 'thai' );
1364  }
1365  $num = $thai[0];
1366  break;
1367  case 'xoY':
1368  $usedYear = true;
1369  if ( !$minguo ) {
1370  $minguo = self::tsToYear( $ts, 'minguo' );
1371  }
1372  $num = $minguo[0];
1373  break;
1374  case 'xtY':
1375  $usedTennoYear = true;
1376  if ( !$tenno ) {
1377  $tenno = self::tsToYear( $ts, 'tenno' );
1378  }
1379  $num = $tenno[0];
1380  break;
1381  case 'y':
1382  $usedYear = true;
1383  $num = substr( $ts, 2, 2 );
1384  break;
1385  case 'xiy':
1386  $usedIranianYear = true;
1387  if ( !$iranian ) {
1388  $iranian = self::tsToIranian( $ts );
1389  }
1390  $num = substr( $iranian[0], -2 );
1391  break;
1392  case 'xit':
1393  $usedIranianYear = true;
1394  if ( !$iranian ) {
1395  $iranian = self::tsToIranian( $ts );
1396  }
1397  $num = self::$IRANIAN_DAYS[$iranian[1] - 1];
1398  break;
1399  case 'xiz':
1400  $usedIranianYear = true;
1401  if ( !$iranian ) {
1402  $iranian = self::tsToIranian( $ts );
1403  }
1404  $num = $iranian[3];
1405  break;
1406  case 'a':
1407  $usedAMPM = true;
1408  $s .= intval( substr( $ts, 8, 2 ) ) < 12 ? 'am' : 'pm';
1409  break;
1410  case 'A':
1411  $usedAMPM = true;
1412  $s .= intval( substr( $ts, 8, 2 ) ) < 12 ? 'AM' : 'PM';
1413  break;
1414  case 'g':
1415  $usedHour = true;
1416  $h = substr( $ts, 8, 2 );
1417  $num = $h % 12 ?: 12;
1418  break;
1419  case 'G':
1420  $usedHour = true;
1421  $num = intval( substr( $ts, 8, 2 ) );
1422  break;
1423  case 'h':
1424  $usedHour = true;
1425  $h = substr( $ts, 8, 2 );
1426  $num = sprintf( '%02d', $h % 12 ?: 12 );
1427  break;
1428  case 'H':
1429  $usedHour = true;
1430  $num = substr( $ts, 8, 2 );
1431  break;
1432  case 'i':
1433  $usedMinute = true;
1434  $num = substr( $ts, 10, 2 );
1435  break;
1436  case 's':
1437  $usedSecond = true;
1438  $num = substr( $ts, 12, 2 );
1439  break;
1440  case 'c':
1441  case 'r':
1442  $usedSecond = true;
1443  // fall through
1444  case 'e':
1445  case 'O':
1446  case 'P':
1447  case 'T':
1448  $s .= self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1449  break;
1450  case 'w':
1451  case 'N':
1452  case 'z':
1453  $usedDay = true;
1454  $num = self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1455  break;
1456  case 'W':
1457  $usedWeek = true;
1458  $num = self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1459  break;
1460  case 't':
1461  $usedMonth = true;
1462  $num = self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1463  break;
1464  case 'L':
1465  $usedIsLeapYear = true;
1466  $num = self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1467  break;
1468  case 'o':
1469  $usedISOYear = true;
1470  $num = self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1471  break;
1472  case 'U':
1473  $usedSecond = true;
1474  // fall through
1475  case 'I':
1476  case 'Z':
1477  $num = self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1478  break;
1479  case '\\':
1480  # Backslash escaping
1481  if ( $p < $formatLength - 1 ) {
1482  $s .= $format[++$p];
1483  } else {
1484  $s .= '\\';
1485  }
1486  break;
1487  case '"':
1488  # Quoted literal
1489  if ( $p < $formatLength - 1 ) {
1490  $endQuote = strpos( $format, '"', $p + 1 );
1491  if ( $endQuote === false ) {
1492  # No terminating quote, assume literal "
1493  $s .= '"';
1494  } else {
1495  $s .= substr( $format, $p + 1, $endQuote - $p - 1 );
1496  $p = $endQuote;
1497  }
1498  } else {
1499  # Quote at end of string, assume literal "
1500  $s .= '"';
1501  }
1502  break;
1503  default:
1504  $s .= $format[$p];
1505  }
1506  if ( $num !== false ) {
1507  if ( $rawToggle || $raw ) {
1508  $s .= $num;
1509  $raw = false;
1510  } elseif ( $roman ) {
1511  $s .= self::romanNumeral( $num );
1512  $roman = false;
1513  } elseif ( $hebrewNum ) {
1514  $s .= self::hebrewNumeral( $num );
1515  $hebrewNum = false;
1516  } else {
1517  $s .= $this->formatNum( $num, true );
1518  }
1519  }
1520  }
1521 
1522  if ( $ttl === 'unused' ) {
1523  // No need to calculate the TTL, the caller wont use it anyway.
1524  } elseif ( $usedSecond ) {
1525  $ttl = 1;
1526  } elseif ( $usedMinute ) {
1527  $ttl = 60 - substr( $ts, 12, 2 );
1528  } elseif ( $usedHour ) {
1529  $ttl = 3600 - substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
1530  } elseif ( $usedAMPM ) {
1531  $ttl = 43200 - ( substr( $ts, 8, 2 ) % 12 ) * 3600 -
1532  substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
1533  } elseif (
1534  $usedDay ||
1535  $usedHebrewMonth ||
1536  $usedIranianMonth ||
1537  $usedHijriMonth ||
1538  $usedHebrewYear ||
1539  $usedIranianYear ||
1540  $usedHijriYear ||
1541  $usedTennoYear
1542  ) {
1543  // @todo Someone who understands the non-Gregorian calendars
1544  // should write proper logic for them so that they don't need purged every day.
1545  $ttl = 86400 - substr( $ts, 8, 2 ) * 3600 -
1546  substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
1547  } else {
1548  $possibleTtls = [];
1549  $timeRemainingInDay = 86400 - substr( $ts, 8, 2 ) * 3600 -
1550  substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
1551  if ( $usedWeek ) {
1552  $possibleTtls[] =
1553  ( 7 - self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'N' ) ) * 86400 +
1554  $timeRemainingInDay;
1555  } elseif ( $usedISOYear ) {
1556  // December 28th falls on the last ISO week of the year, every year.
1557  // The last ISO week of a year can be 52 or 53.
1558  $lastWeekOfISOYear = DateTime::createFromFormat(
1559  'Ymd',
1560  substr( $ts, 0, 4 ) . '1228',
1561  $zone ?: new DateTimeZone( 'UTC' )
1562  )->format( 'W' );
1563  $currentISOWeek = self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'W' );
1564  $weeksRemaining = $lastWeekOfISOYear - $currentISOWeek;
1565  $timeRemainingInWeek =
1566  ( 7 - self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'N' ) ) * 86400
1567  + $timeRemainingInDay;
1568  $possibleTtls[] = $weeksRemaining * 604800 + $timeRemainingInWeek;
1569  }
1570 
1571  if ( $usedMonth ) {
1572  $possibleTtls[] =
1573  ( self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 't' ) -
1574  substr( $ts, 6, 2 ) ) * 86400
1575  + $timeRemainingInDay;
1576  } elseif ( $usedYear ) {
1577  $possibleTtls[] =
1578  ( self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'L' ) + 364 -
1579  self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'z' ) ) * 86400
1580  + $timeRemainingInDay;
1581  } elseif ( $usedIsLeapYear ) {
1582  $year = substr( $ts, 0, 4 );
1583  $timeRemainingInYear =
1584  ( self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'L' ) + 364 -
1585  self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'z' ) ) * 86400
1586  + $timeRemainingInDay;
1587  $mod = $year % 4;
1588  if ( $mod || ( !( $year % 100 ) && $year % 400 ) ) {
1589  // this isn't a leap year. see when the next one starts
1590  $nextCandidate = $year - $mod + 4;
1591  if ( $nextCandidate % 100 || !( $nextCandidate % 400 ) ) {
1592  $possibleTtls[] = ( $nextCandidate - $year - 1 ) * 365 * 86400 +
1593  $timeRemainingInYear;
1594  } else {
1595  $possibleTtls[] = ( $nextCandidate - $year + 3 ) * 365 * 86400 +
1596  $timeRemainingInYear;
1597  }
1598  } else {
1599  // this is a leap year, so the next year isn't
1600  $possibleTtls[] = $timeRemainingInYear;
1601  }
1602  }
1603 
1604  if ( $possibleTtls ) {
1605  $ttl = min( $possibleTtls );
1606  }
1607  }
1608 
1609  return $s;
1610  }
1611 
1612  private static $GREG_DAYS = [ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ];
1613  private static $IRANIAN_DAYS = [ 31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29 ];
1614 
1627  private static function tsToIranian( $ts ) {
1628  $gy = substr( $ts, 0, 4 ) - 1600;
1629  $gm = substr( $ts, 4, 2 ) - 1;
1630  $gd = substr( $ts, 6, 2 ) - 1;
1631 
1632  # Days passed from the beginning (including leap years)
1633  $gDayNo = 365 * $gy
1634  + floor( ( $gy + 3 ) / 4 )
1635  - floor( ( $gy + 99 ) / 100 )
1636  + floor( ( $gy + 399 ) / 400 );
1637 
1638  // Add days of the past months of this year
1639  for ( $i = 0; $i < $gm; $i++ ) {
1640  $gDayNo += self::$GREG_DAYS[$i];
1641  }
1642 
1643  // Leap years
1644  if ( $gm > 1 && ( ( $gy % 4 === 0 && $gy % 100 !== 0 || ( $gy % 400 == 0 ) ) ) ) {
1645  $gDayNo++;
1646  }
1647 
1648  // Days passed in current month
1649  $gDayNo += (int)$gd;
1650 
1651  $jDayNo = $gDayNo - 79;
1652 
1653  $jNp = floor( $jDayNo / 12053 );
1654  $jDayNo %= 12053;
1655 
1656  $jy = 979 + 33 * $jNp + 4 * floor( $jDayNo / 1461 );
1657  $jDayNo %= 1461;
1658 
1659  if ( $jDayNo >= 366 ) {
1660  $jy += floor( ( $jDayNo - 1 ) / 365 );
1661  $jDayNo = floor( ( $jDayNo - 1 ) % 365 );
1662  }
1663 
1664  $jz = $jDayNo;
1665 
1666  for ( $i = 0; $i < 11 && $jDayNo >= self::$IRANIAN_DAYS[$i]; $i++ ) {
1667  $jDayNo -= self::$IRANIAN_DAYS[$i];
1668  }
1669 
1670  $jm = $i + 1;
1671  $jd = $jDayNo + 1;
1672 
1673  return [ $jy, $jm, $jd, $jz ];
1674  }
1675 
1687  private static function tsToHijri( $ts ) {
1688  $year = substr( $ts, 0, 4 );
1689  $month = substr( $ts, 4, 2 );
1690  $day = substr( $ts, 6, 2 );
1691 
1692  $zyr = $year;
1693  $zd = $day;
1694  $zm = $month;
1695  $zy = $zyr;
1696 
1697  if (
1698  ( $zy > 1582 ) || ( ( $zy == 1582 ) && ( $zm > 10 ) ) ||
1699  ( ( $zy == 1582 ) && ( $zm == 10 ) && ( $zd > 14 ) )
1700  ) {
1701  $zjd = (int)( ( 1461 * ( $zy + 4800 + (int)( ( $zm - 14 ) / 12 ) ) ) / 4 ) +
1702  (int)( ( 367 * ( $zm - 2 - 12 * ( (int)( ( $zm - 14 ) / 12 ) ) ) ) / 12 ) -
1703  (int)( ( 3 * (int)( ( ( $zy + 4900 + (int)( ( $zm - 14 ) / 12 ) ) / 100 ) ) ) / 4 ) +
1704  $zd - 32075;
1705  } else {
1706  $zjd = 367 * $zy - (int)( ( 7 * ( $zy + 5001 + (int)( ( $zm - 9 ) / 7 ) ) ) / 4 ) +
1707  (int)( ( 275 * $zm ) / 9 ) + $zd + 1729777;
1708  }
1709 
1710  $zl = $zjd - 1948440 + 10632;
1711  $zn = (int)( ( $zl - 1 ) / 10631 );
1712  $zl = $zl - 10631 * $zn + 354;
1713  $zj = ( (int)( ( 10985 - $zl ) / 5316 ) ) * ( (int)( ( 50 * $zl ) / 17719 ) ) +
1714  ( (int)( $zl / 5670 ) ) * ( (int)( ( 43 * $zl ) / 15238 ) );
1715  $zl = $zl - ( (int)( ( 30 - $zj ) / 15 ) ) * ( (int)( ( 17719 * $zj ) / 50 ) ) -
1716  ( (int)( $zj / 16 ) ) * ( (int)( ( 15238 * $zj ) / 43 ) ) + 29;
1717  $zm = (int)( ( 24 * $zl ) / 709 );
1718  $zd = $zl - (int)( ( 709 * $zm ) / 24 );
1719  $zy = 30 * $zn + $zj - 30;
1720 
1721  return [ $zy, $zm, $zd ];
1722  }
1723 
1739  private static function tsToHebrew( $ts ) {
1740  # Parse date
1741  $year = substr( $ts, 0, 4 );
1742  $month = substr( $ts, 4, 2 );
1743  $day = substr( $ts, 6, 2 );
1744 
1745  # Calculate Hebrew year
1746  $hebrewYear = $year + 3760;
1747 
1748  # Month number when September = 1, August = 12
1749  $month += 4;
1750  if ( $month > 12 ) {
1751  # Next year
1752  $month -= 12;
1753  $year++;
1754  $hebrewYear++;
1755  }
1756 
1757  # Calculate day of year from 1 September
1758  $dayOfYear = $day;
1759  for ( $i = 1; $i < $month; $i++ ) {
1760  if ( $i == 6 ) {
1761  # February
1762  $dayOfYear += 28;
1763  # Check if the year is leap
1764  if ( $year % 400 == 0 || ( $year % 4 == 0 && $year % 100 > 0 ) ) {
1765  $dayOfYear++;
1766  }
1767  } elseif ( $i == 8 || $i == 10 || $i == 1 || $i == 3 ) {
1768  $dayOfYear += 30;
1769  } else {
1770  $dayOfYear += 31;
1771  }
1772  }
1773 
1774  # Calculate the start of the Hebrew year
1775  $start = self::hebrewYearStart( $hebrewYear );
1776 
1777  # Calculate next year's start
1778  if ( $dayOfYear <= $start ) {
1779  # Day is before the start of the year - it is the previous year
1780  # Next year's start
1781  $nextStart = $start;
1782  # Previous year
1783  $year--;
1784  $hebrewYear--;
1785  # Add days since previous year's 1 September
1786  $dayOfYear += 365;
1787  if ( ( $year % 400 == 0 ) || ( $year % 100 != 0 && $year % 4 == 0 ) ) {
1788  # Leap year
1789  $dayOfYear++;
1790  }
1791  # Start of the new (previous) year
1792  $start = self::hebrewYearStart( $hebrewYear );
1793  } else {
1794  # Next year's start
1795  $nextStart = self::hebrewYearStart( $hebrewYear + 1 );
1796  }
1797 
1798  # Calculate Hebrew day of year
1799  $hebrewDayOfYear = $dayOfYear - $start;
1800 
1801  # Difference between year's days
1802  $diff = $nextStart - $start;
1803  # Add 12 (or 13 for leap years) days to ignore the difference between
1804  # Hebrew and Gregorian year (353 at least vs. 365/6) - now the
1805  # difference is only about the year type
1806  if ( ( $year % 400 == 0 ) || ( $year % 100 != 0 && $year % 4 == 0 ) ) {
1807  $diff += 13;
1808  } else {
1809  $diff += 12;
1810  }
1811 
1812  # Check the year pattern, and is leap year
1813  # 0 means an incomplete year, 1 means a regular year, 2 means a complete year
1814  # This is mod 30, to work on both leap years (which add 30 days of Adar I)
1815  # and non-leap years
1816  $yearPattern = $diff % 30;
1817  # Check if leap year
1818  $isLeap = $diff >= 30;
1819 
1820  # Calculate day in the month from number of day in the Hebrew year
1821  # Don't check Adar - if the day is not in Adar, we will stop before;
1822  # if it is in Adar, we will use it to check if it is Adar I or Adar II
1823  $hebrewDay = $hebrewDayOfYear;
1824  $hebrewMonth = 1;
1825  $days = 0;
1826  while ( $hebrewMonth <= 12 ) {
1827  # Calculate days in this month
1828  if ( $isLeap && $hebrewMonth == 6 ) {
1829  # Leap year - has Adar I, with 30 days, and Adar II, with 29 days
1830  $days = 30;
1831  if ( $hebrewDay <= $days ) {
1832  # Day in Adar I
1833  $hebrewMonth = 13;
1834  } else {
1835  # Subtract the days of Adar I
1836  $hebrewDay -= $days;
1837  # Try Adar II
1838  $days = 29;
1839  if ( $hebrewDay <= $days ) {
1840  # Day in Adar II
1841  $hebrewMonth = 14;
1842  }
1843  }
1844  } elseif ( $hebrewMonth == 2 && $yearPattern == 2 ) {
1845  # Cheshvan in a complete year (otherwise as the rule below)
1846  $days = 30;
1847  } elseif ( $hebrewMonth == 3 && $yearPattern == 0 ) {
1848  # Kislev in an incomplete year (otherwise as the rule below)
1849  $days = 29;
1850  } else {
1851  # Odd months have 30 days, even have 29
1852  $days = 30 - ( $hebrewMonth - 1 ) % 2;
1853  }
1854  if ( $hebrewDay <= $days ) {
1855  # In the current month
1856  break;
1857  } else {
1858  # Subtract the days of the current month
1859  $hebrewDay -= $days;
1860  # Try in the next month
1861  $hebrewMonth++;
1862  }
1863  }
1864 
1865  return [ $hebrewYear, $hebrewMonth, $hebrewDay, $days ];
1866  }
1867 
1877  private static function hebrewYearStart( $year ) {
1878  $a = intval( ( 12 * ( $year - 1 ) + 17 ) % 19 );
1879  $b = intval( ( $year - 1 ) % 4 );
1880  $m = 32.044093161144 + 1.5542417966212 * $a + $b / 4.0 - 0.0031777940220923 * ( $year - 1 );
1881  if ( $m < 0 ) {
1882  $m--;
1883  }
1884  $Mar = intval( $m );
1885  if ( $m < 0 ) {
1886  $m++;
1887  }
1888  $m -= $Mar;
1889 
1890  $c = intval( ( $Mar + 3 * ( $year - 1 ) + 5 * $b + 5 ) % 7 );
1891  if ( $c == 0 && $a > 11 && $m >= 0.89772376543210 ) {
1892  $Mar++;
1893  } elseif ( $c == 1 && $a > 6 && $m >= 0.63287037037037 ) {
1894  $Mar += 2;
1895  } elseif ( $c == 2 || $c == 4 || $c == 6 ) {
1896  $Mar++;
1897  }
1898 
1899  $Mar += intval( ( $year - 3761 ) / 100 ) - intval( ( $year - 3761 ) / 400 ) - 24;
1900  return $Mar;
1901  }
1902 
1915  private static function tsToYear( $ts, $cName ) {
1916  $gy = substr( $ts, 0, 4 );
1917  $gm = substr( $ts, 4, 2 );
1918  $gd = substr( $ts, 6, 2 );
1919 
1920  if ( !strcmp( $cName, 'thai' ) ) {
1921  # Thai solar dates
1922  # Add 543 years to the Gregorian calendar
1923  # Months and days are identical
1924  $gy_offset = $gy + 543;
1925  # fix for dates between 1912 and 1941
1926  # https://en.wikipedia.org/?oldid=836596673#New_year
1927  if ( $gy >= 1912 && $gy <= 1940 ) {
1928  if ( $gm <= 3 ) {
1929  $gy_offset--;
1930  }
1931  $gm = ( $gm - 3 ) % 12;
1932  }
1933  } elseif ( ( !strcmp( $cName, 'minguo' ) ) || !strcmp( $cName, 'juche' ) ) {
1934  # Minguo dates
1935  # Deduct 1911 years from the Gregorian calendar
1936  # Months and days are identical
1937  $gy_offset = $gy - 1911;
1938  } elseif ( !strcmp( $cName, 'tenno' ) ) {
1939  # Nengō dates up to Meiji period
1940  # Deduct years from the Gregorian calendar
1941  # depending on the nengo periods
1942  # Months and days are identical
1943  if ( ( $gy < 1912 )
1944  || ( ( $gy == 1912 ) && ( $gm < 7 ) )
1945  || ( ( $gy == 1912 ) && ( $gm == 7 ) && ( $gd < 31 ) )
1946  ) {
1947  # Meiji period
1948  $gy_gannen = $gy - 1868 + 1;
1949  $gy_offset = $gy_gannen;
1950  if ( $gy_gannen == 1 ) {
1951  $gy_offset = '元';
1952  }
1953  $gy_offset = '明治' . $gy_offset;
1954  } elseif (
1955  ( ( $gy == 1912 ) && ( $gm == 7 ) && ( $gd == 31 ) ) ||
1956  ( ( $gy == 1912 ) && ( $gm >= 8 ) ) ||
1957  ( ( $gy > 1912 ) && ( $gy < 1926 ) ) ||
1958  ( ( $gy == 1926 ) && ( $gm < 12 ) ) ||
1959  ( ( $gy == 1926 ) && ( $gm == 12 ) && ( $gd < 26 ) )
1960  ) {
1961  # Taishō period
1962  $gy_gannen = $gy - 1912 + 1;
1963  $gy_offset = $gy_gannen;
1964  if ( $gy_gannen == 1 ) {
1965  $gy_offset = '元';
1966  }
1967  $gy_offset = '大正' . $gy_offset;
1968  } elseif (
1969  ( ( $gy == 1926 ) && ( $gm == 12 ) && ( $gd >= 26 ) ) ||
1970  ( ( $gy > 1926 ) && ( $gy < 1989 ) ) ||
1971  ( ( $gy == 1989 ) && ( $gm == 1 ) && ( $gd < 8 ) )
1972  ) {
1973  # Shōwa period
1974  $gy_gannen = $gy - 1926 + 1;
1975  $gy_offset = $gy_gannen;
1976  if ( $gy_gannen == 1 ) {
1977  $gy_offset = '元';
1978  }
1979  $gy_offset = '昭和' . $gy_offset;
1980  } elseif (
1981  ( ( $gy == 1989 ) && ( $gm == 1 ) && ( $gd >= 8 ) ) ||
1982  ( ( $gy > 1989 ) && ( $gy < 2019 ) ) ||
1983  ( ( $gy == 2019 ) && ( $gm < 5 ) )
1984  ) {
1985  # Heisei period
1986  $gy_gannen = $gy - 1989 + 1;
1987  $gy_offset = $gy_gannen;
1988  if ( $gy_gannen == 1 ) {
1989  $gy_offset = '元';
1990  }
1991  $gy_offset = '平成' . $gy_offset;
1992  } else {
1993  # Reiwa period
1994  $gy_gannen = $gy - 2019 + 1;
1995  $gy_offset = $gy_gannen;
1996  if ( $gy_gannen == 1 ) {
1997  $gy_offset = '元';
1998  }
1999  $gy_offset = '令和' . $gy_offset;
2000  }
2001  } else {
2002  $gy_offset = $gy;
2003  }
2004 
2005  return [ $gy_offset, $gm, $gd ];
2006  }
2007 
2021  private static function strongDirFromContent( $text = '' ) {
2022  if ( !preg_match( self::$strongDirRegex, $text, $matches ) ) {
2023  return null;
2024  }
2025  if ( $matches[1] === '' ) {
2026  return 'rtl';
2027  }
2028  return 'ltr';
2029  }
2030 
2038  static function romanNumeral( $num ) {
2039  static $table = [
2040  [ '', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X' ],
2041  [ '', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC', 'C' ],
2042  [ '', 'C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM', 'M' ],
2043  [ '', 'M', 'MM', 'MMM', 'MMMM', 'MMMMM', 'MMMMMM', 'MMMMMMM',
2044  'MMMMMMMM', 'MMMMMMMMM', 'MMMMMMMMMM' ]
2045  ];
2046 
2047  $num = intval( $num );
2048  if ( $num > 10000 || $num <= 0 ) {
2049  return $num;
2050  }
2051 
2052  $s = '';
2053  for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) {
2054  if ( $num >= $pow10 ) {
2055  $s .= $table[$i][(int)floor( $num / $pow10 )];
2056  }
2057  $num = $num % $pow10;
2058  }
2059  return $s;
2060  }
2061 
2069  static function hebrewNumeral( $num ) {
2070  static $table = [
2071  [ '', 'א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ז', 'ח', 'ט', 'י' ],
2072  [ '', 'י', 'כ', 'ל', 'מ', 'נ', 'ס', 'ע', 'פ', 'צ', 'ק' ],
2073  [ '',
2074  [ 'ק' ],
2075  [ 'ר' ],
2076  [ 'ש' ],
2077  [ 'ת' ],
2078  [ 'ת', 'ק' ],
2079  [ 'ת', 'ר' ],
2080  [ 'ת', 'ש' ],
2081  [ 'ת', 'ת' ],
2082  [ 'ת', 'ת', 'ק' ],
2083  [ 'ת', 'ת', 'ר' ],
2084  ],
2085  [ '', 'א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ז', 'ח', 'ט', 'י' ]
2086  ];
2087 
2088  $num = intval( $num );
2089  if ( $num > 9999 || $num <= 0 ) {
2090  return $num;
2091  }
2092 
2093  // Round thousands have special notations
2094  if ( $num === 1000 ) {
2095  return "א' אלף";
2096  } elseif ( $num % 1000 === 0 ) {
2097  return $table[0][$num / 1000] . "' אלפים";
2098  }
2099 
2100  $letters = [];
2101 
2102  for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) {
2103  if ( $num >= $pow10 ) {
2104  if ( $num === 15 || $num === 16 ) {
2105  $letters[] = $table[0][9];
2106  $letters[] = $table[0][$num - 9];
2107  $num = 0;
2108  } else {
2109  $letters = array_merge(
2110  $letters,
2111  (array)$table[$i][intval( $num / $pow10 )]
2112  );
2113 
2114  if ( $pow10 === 1000 ) {
2115  $letters[] = "'";
2116  }
2117  }
2118  }
2119 
2120  $num = $num % $pow10;
2121  }
2122 
2123  $preTransformLength = count( $letters );
2124  if ( $preTransformLength === 1 ) {
2125  // Add geresh (single quote) to one-letter numbers
2126  $letters[] = "'";
2127  } else {
2128  $lastIndex = $preTransformLength - 1;
2129  $letters[$lastIndex] = str_replace(
2130  [ 'כ', 'מ', 'נ', 'פ', 'צ' ],
2131  [ 'ך', 'ם', 'ן', 'ף', 'ץ' ],
2132  $letters[$lastIndex]
2133  );
2134 
2135  // Add gershayim (double quote) to multiple-letter numbers,
2136  // but exclude numbers with only one letter after the thousands
2137  // (1001-1009, 1020, 1030, 2001-2009, etc.)
2138  if ( $letters[1] === "'" && $preTransformLength === 3 ) {
2139  $letters[] = "'";
2140  } else {
2141  array_splice( $letters, -1, 0, '"' );
2142  }
2143  }
2144 
2145  return implode( $letters );
2146  }
2147 
2156  public function userAdjust( $ts, $tz = false ) {
2157  global $wgUser, $wgLocalTZoffset;
2158 
2159  if ( $tz === false ) {
2160  $tz = $wgUser->getOption( 'timecorrection' );
2161  }
2162 
2163  $data = explode( '|', $tz, 3 );
2164 
2165  if ( $data[0] == 'ZoneInfo' ) {
2166  try {
2167  $userTZ = new DateTimeZone( $data[2] );
2168  $date = new DateTime( $ts, new DateTimeZone( 'UTC' ) );
2169  $date->setTimezone( $userTZ );
2170  return $date->format( 'YmdHis' );
2171  } catch ( Exception $e ) {
2172  // Unrecognized timezone, default to 'Offset' with the stored offset.
2173  $data[0] = 'Offset';
2174  }
2175  }
2176 
2177  if ( $data[0] == 'System' || $tz == '' ) {
2178  # Global offset in minutes.
2179  $minDiff = $wgLocalTZoffset;
2180  } elseif ( $data[0] == 'Offset' ) {
2181  $minDiff = intval( $data[1] );
2182  } else {
2183  $data = explode( ':', $tz );
2184  if ( count( $data ) == 2 ) {
2185  $data[0] = intval( $data[0] );
2186  $data[1] = intval( $data[1] );
2187  $minDiff = abs( $data[0] ) * 60 + $data[1];
2188  if ( $data[0] < 0 ) {
2189  $minDiff = -$minDiff;
2190  }
2191  } else {
2192  $minDiff = intval( $data[0] ) * 60;
2193  }
2194  }
2195 
2196  # No difference ? Return time unchanged
2197  if ( $minDiff == 0 ) {
2198  return $ts;
2199  }
2200 
2201  Wikimedia\suppressWarnings(); // E_STRICT system time bitching
2202  # Generate an adjusted date; take advantage of the fact that mktime
2203  # will normalize out-of-range values so we don't have to split $minDiff
2204  # into hours and minutes.
2205  $t = mktime( (
2206  (int)substr( $ts, 8, 2 ) ), # Hours
2207  (int)substr( $ts, 10, 2 ) + $minDiff, # Minutes
2208  (int)substr( $ts, 12, 2 ), # Seconds
2209  (int)substr( $ts, 4, 2 ), # Month
2210  (int)substr( $ts, 6, 2 ), # Day
2211  (int)substr( $ts, 0, 4 ) ); # Year
2212 
2213  $date = date( 'YmdHis', $t );
2214  Wikimedia\restoreWarnings();
2215 
2216  return $date;
2217  }
2218 
2234  function dateFormat( $usePrefs = true ) {
2235  global $wgUser;
2236 
2237  if ( is_bool( $usePrefs ) ) {
2238  if ( $usePrefs ) {
2239  $datePreference = $wgUser->getDatePreference();
2240  } else {
2241  $datePreference = (string)User::getDefaultOption( 'date' );
2242  }
2243  } else {
2244  $datePreference = (string)$usePrefs;
2245  }
2246 
2247  // return int
2248  if ( $datePreference == '' ) {
2249  return 'default';
2250  }
2251 
2252  return $datePreference;
2253  }
2254 
2265  function getDateFormatString( $type, $pref ) {
2266  $wasDefault = false;
2267  if ( $pref == 'default' ) {
2268  $wasDefault = true;
2269  $pref = $this->getDefaultDateFormat();
2270  }
2271 
2272  if ( !isset( $this->dateFormatStrings[$type][$pref] ) ) {
2273  $df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" );
2274 
2275  if ( $type === 'pretty' && $df === null ) {
2276  $df = $this->getDateFormatString( 'date', $pref );
2277  }
2278 
2279  if ( !$wasDefault && $df === null ) {
2280  $pref = $this->getDefaultDateFormat();
2281  $df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" );
2282  }
2283 
2284  $this->dateFormatStrings[$type][$pref] = $df;
2285  }
2286  return $this->dateFormatStrings[$type][$pref];
2287  }
2288 
2299  public function date( $ts, $adj = false, $format = true, $timecorrection = false ) {
2300  $ts = wfTimestamp( TS_MW, $ts );
2301  if ( $adj ) {
2302  $ts = $this->userAdjust( $ts, $timecorrection );
2303  }
2304  $df = $this->getDateFormatString( 'date', $this->dateFormat( $format ) );
2305  return $this->sprintfDate( $df, $ts );
2306  }
2307 
2318  public function time( $ts, $adj = false, $format = true, $timecorrection = false ) {
2319  $ts = wfTimestamp( TS_MW, $ts );
2320  if ( $adj ) {
2321  $ts = $this->userAdjust( $ts, $timecorrection );
2322  }
2323  $df = $this->getDateFormatString( 'time', $this->dateFormat( $format ) );
2324  return $this->sprintfDate( $df, $ts );
2325  }
2326 
2338  public function timeanddate( $ts, $adj = false, $format = true, $timecorrection = false ) {
2339  $ts = wfTimestamp( TS_MW, $ts );
2340  if ( $adj ) {
2341  $ts = $this->userAdjust( $ts, $timecorrection );
2342  }
2343  $df = $this->getDateFormatString( 'both', $this->dateFormat( $format ) );
2344  return $this->sprintfDate( $df, $ts );
2345  }
2346 
2357  public function formatDuration( $seconds, array $chosenIntervals = [] ) {
2358  $intervals = $this->getDurationIntervals( $seconds, $chosenIntervals );
2359 
2360  $segments = [];
2361 
2362  foreach ( $intervals as $intervalName => $intervalValue ) {
2363  // Messages: duration-seconds, duration-minutes, duration-hours, duration-days, duration-weeks,
2364  // duration-years, duration-decades, duration-centuries, duration-millennia
2365  $message = wfMessage( 'duration-' . $intervalName )->numParams( $intervalValue );
2366  $segments[] = $message->inLanguage( $this )->escaped();
2367  }
2368 
2369  return $this->listToText( $segments );
2370  }
2371 
2383  public function getDurationIntervals( $seconds, array $chosenIntervals = [] ) {
2384  if ( empty( $chosenIntervals ) ) {
2385  $chosenIntervals = [
2386  'millennia',
2387  'centuries',
2388  'decades',
2389  'years',
2390  'days',
2391  'hours',
2392  'minutes',
2393  'seconds'
2394  ];
2395  }
2396 
2397  $intervals = array_intersect_key( self::$durationIntervals, array_flip( $chosenIntervals ) );
2398  $sortedNames = array_keys( $intervals );
2399  $smallestInterval = array_pop( $sortedNames );
2400 
2401  $segments = [];
2402 
2403  foreach ( $intervals as $name => $length ) {
2404  $value = floor( $seconds / $length );
2405 
2406  if ( $value > 0 || ( $name == $smallestInterval && empty( $segments ) ) ) {
2407  $seconds -= $value * $length;
2408  $segments[$name] = $value;
2409  }
2410  }
2411 
2412  return $segments;
2413  }
2414 
2434  private function internalUserTimeAndDate( $type, $ts, User $user, array $options ) {
2435  $ts = wfTimestamp( TS_MW, $ts );
2436  $options += [ 'timecorrection' => true, 'format' => true ];
2437  if ( $options['timecorrection'] !== false ) {
2438  if ( $options['timecorrection'] === true ) {
2439  $offset = $user->getOption( 'timecorrection' );
2440  } else {
2441  $offset = $options['timecorrection'];
2442  }
2443  $ts = $this->userAdjust( $ts, $offset );
2444  }
2445  if ( $options['format'] === true ) {
2446  $format = $user->getDatePreference();
2447  } else {
2448  $format = $options['format'];
2449  }
2450  $df = $this->getDateFormatString( $type, $this->dateFormat( $format ) );
2451  return $this->sprintfDate( $df, $ts );
2452  }
2453 
2473  public function userDate( $ts, User $user, array $options = [] ) {
2474  return $this->internalUserTimeAndDate( 'date', $ts, $user, $options );
2475  }
2476 
2496  public function userTime( $ts, User $user, array $options = [] ) {
2497  return $this->internalUserTimeAndDate( 'time', $ts, $user, $options );
2498  }
2499 
2519  public function userTimeAndDate( $ts, User $user, array $options = [] ) {
2520  return $this->internalUserTimeAndDate( 'both', $ts, $user, $options );
2521  }
2522 
2538  public function getHumanTimestamp(
2539  MWTimestamp $time, MWTimestamp $relativeTo = null, User $user = null
2540  ) {
2541  if ( $relativeTo === null ) {
2542  $relativeTo = new MWTimestamp();
2543  }
2544  if ( $user === null ) {
2545  $user = RequestContext::getMain()->getUser();
2546  }
2547 
2548  // Adjust for the user's timezone.
2549  $offsetThis = $time->offsetForUser( $user );
2550  $offsetRel = $relativeTo->offsetForUser( $user );
2551 
2552  $ts = '';
2553  if ( Hooks::run( 'GetHumanTimestamp', [ &$ts, $time, $relativeTo, $user, $this ] ) ) {
2554  $ts = $this->getHumanTimestampInternal( $time, $relativeTo, $user );
2555  }
2556 
2557  // Reset the timezone on the objects.
2558  $time->timestamp->sub( $offsetThis );
2559  $relativeTo->timestamp->sub( $offsetRel );
2560 
2561  return $ts;
2562  }
2563 
2575  private function getHumanTimestampInternal(
2576  MWTimestamp $ts, MWTimestamp $relativeTo, User $user
2577  ) {
2578  $diff = $ts->diff( $relativeTo );
2579  $diffDay = (bool)( (int)$ts->timestamp->format( 'w' ) -
2580  (int)$relativeTo->timestamp->format( 'w' ) );
2581  $days = $diff->days ?: (int)$diffDay;
2582  if ( $diff->invert || $days > 5
2583  && $ts->timestamp->format( 'Y' ) !== $relativeTo->timestamp->format( 'Y' )
2584  ) {
2585  // Timestamps are in different years: use full timestamp
2586  // Also do full timestamp for future dates
2590  $format = $this->getDateFormatString( 'both', $user->getDatePreference() ?: 'default' );
2591  $ts = $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) );
2592  } elseif ( $days > 5 ) {
2593  // Timestamps are in same year, but more than 5 days ago: show day and month only.
2594  $format = $this->getDateFormatString( 'pretty', $user->getDatePreference() ?: 'default' );
2595  $ts = $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) );
2596  } elseif ( $days > 1 ) {
2597  // Timestamp within the past week: show the day of the week and time
2598  $format = $this->getDateFormatString( 'time', $user->getDatePreference() ?: 'default' );
2599  $weekday = self::$mWeekdayMsgs[$ts->timestamp->format( 'w' )];
2600  // Messages:
2601  // sunday-at, monday-at, tuesday-at, wednesday-at, thursday-at, friday-at, saturday-at
2602  $ts = wfMessage( "$weekday-at" )
2603  ->inLanguage( $this )
2604  ->params( $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) ) )
2605  ->text();
2606  } elseif ( $days == 1 ) {
2607  // Timestamp was yesterday: say 'yesterday' and the time.
2608  $format = $this->getDateFormatString( 'time', $user->getDatePreference() ?: 'default' );
2609  $ts = wfMessage( 'yesterday-at' )
2610  ->inLanguage( $this )
2611  ->params( $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) ) )
2612  ->text();
2613  } elseif ( $diff->h > 1 || $diff->h == 1 && $diff->i > 30 ) {
2614  // Timestamp was today, but more than 90 minutes ago: say 'today' and the time.
2615  $format = $this->getDateFormatString( 'time', $user->getDatePreference() ?: 'default' );
2616  $ts = wfMessage( 'today-at' )
2617  ->inLanguage( $this )
2618  ->params( $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) ) )
2619  ->text();
2620 
2621  // From here on in, the timestamp was soon enough ago so that we can simply say
2622  // XX units ago, e.g., "2 hours ago" or "5 minutes ago"
2623  } elseif ( $diff->h == 1 ) {
2624  // Less than 90 minutes, but more than an hour ago.
2625  $ts = wfMessage( 'hours-ago' )->inLanguage( $this )->numParams( 1 )->text();
2626  } elseif ( $diff->i >= 1 ) {
2627  // A few minutes ago.
2628  $ts = wfMessage( 'minutes-ago' )->inLanguage( $this )->numParams( $diff->i )->text();
2629  } elseif ( $diff->s >= 30 ) {
2630  // Less than a minute, but more than 30 sec ago.
2631  $ts = wfMessage( 'seconds-ago' )->inLanguage( $this )->numParams( $diff->s )->text();
2632  } else {
2633  // Less than 30 seconds ago.
2634  $ts = wfMessage( 'just-now' )->text();
2635  }
2636 
2637  return $ts;
2638  }
2639 
2644  public function getMessage( $key ) {
2645  return self::$dataCache->getSubitem( $this->mCode, 'messages', $key );
2646  }
2647 
2651  function getAllMessages() {
2652  return self::$dataCache->getItem( $this->mCode, 'messages' );
2653  }
2654 
2661  public function iconv( $in, $out, $string ) {
2662  # Even with //IGNORE iconv can whine about illegal characters in
2663  # *input* string. We just ignore those too.
2664  # REF: https://bugs.php.net/bug.php?id=37166
2665  # REF: https://phabricator.wikimedia.org/T18885
2666  Wikimedia\suppressWarnings();
2667  $text = iconv( $in, $out . '//IGNORE', $string );
2668  Wikimedia\restoreWarnings();
2669  return $text;
2670  }
2671 
2672  // callback functions for ucwords(), ucwordbreaks()
2673 
2679  return $this->ucfirst( $matches[1] );
2680  }
2681 
2687  return mb_strtoupper( $matches[0] );
2688  }
2689 
2695  return mb_strtoupper( $matches[0] );
2696  }
2697 
2705  public function ucfirst( $str ) {
2706  $o = ord( $str );
2707  if ( $o < 96 ) { // if already uppercase...
2708  return $str;
2709  } elseif ( $o < 128 ) {
2710  return ucfirst( $str ); // use PHP's ucfirst()
2711  } else {
2712  // fall back to more complex logic in case of multibyte strings
2713  return $this->uc( $str, true );
2714  }
2715  }
2716 
2725  public function uc( $str, $first = false ) {
2726  if ( $first ) {
2727  if ( $this->isMultibyte( $str ) ) {
2728  return $this->mbUpperChar( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
2729  } else {
2730  return ucfirst( $str );
2731  }
2732  } else {
2733  return $this->isMultibyte( $str ) ? mb_strtoupper( $str ) : strtoupper( $str );
2734  }
2735  }
2736 
2750  protected function mbUpperChar( $char ) {
2752  if ( array_key_exists( $char, $wgOverrideUcfirstCharacters ) ) {
2753  return $wgOverrideUcfirstCharacters[$char];
2754  } else {
2755  return mb_strtoupper( $char );
2756  }
2757  }
2758 
2763  function lcfirst( $str ) {
2764  $o = ord( $str );
2765  if ( !$o ) {
2766  return strval( $str );
2767  } elseif ( $o >= 128 ) {
2768  return $this->lc( $str, true );
2769  } elseif ( $o > 96 ) {
2770  return $str;
2771  } else {
2772  $str[0] = strtolower( $str[0] );
2773  return $str;
2774  }
2775  }
2776 
2782  function lc( $str, $first = false ) {
2783  if ( $first ) {
2784  if ( $this->isMultibyte( $str ) ) {
2785  return mb_strtolower( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
2786  } else {
2787  return strtolower( substr( $str, 0, 1 ) ) . substr( $str, 1 );
2788  }
2789  } else {
2790  return $this->isMultibyte( $str ) ? mb_strtolower( $str ) : strtolower( $str );
2791  }
2792  }
2793 
2798  function isMultibyte( $str ) {
2799  return strlen( $str ) !== mb_strlen( $str );
2800  }
2801 
2806  function ucwords( $str ) {
2807  if ( $this->isMultibyte( $str ) ) {
2808  $str = $this->lc( $str );
2809 
2810  // regexp to find first letter in each word (i.e. after each space)
2811  $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)| ([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
2812 
2813  // function to use to capitalize a single char
2814  return preg_replace_callback(
2815  $replaceRegexp,
2816  [ $this, 'ucwordsCallbackMB' ],
2817  $str
2818  );
2819  } else {
2820  return ucwords( strtolower( $str ) );
2821  }
2822  }
2823 
2830  function ucwordbreaks( $str ) {
2831  if ( $this->isMultibyte( $str ) ) {
2832  $str = $this->lc( $str );
2833 
2834  // since \b doesn't work for UTF-8, we explicitely define word break chars
2835  $breaks = "[ \-\(\)\}\{\.,\?!]";
2836 
2837  // find first letter after word break
2838  $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)|" .
2839  "$breaks([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
2840 
2841  return preg_replace_callback(
2842  $replaceRegexp,
2843  [ $this, 'ucwordbreaksCallbackMB' ],
2844  $str
2845  );
2846  } else {
2847  return preg_replace_callback(
2848  '/\b([\w\x80-\xff]+)\b/',
2849  [ $this, 'ucwordbreaksCallbackAscii' ],
2850  $str
2851  );
2852  }
2853  }
2854 
2870  function caseFold( $s ) {
2871  return $this->uc( $s );
2872  }
2873 
2879  function checkTitleEncoding( $s ) {
2880  if ( is_array( $s ) ) {
2881  throw new MWException( 'Given array to checkTitleEncoding.' );
2882  }
2883  if ( StringUtils::isUtf8( $s ) ) {
2884  return $s;
2885  }
2886 
2887  return $this->iconv( $this->fallback8bitEncoding(), 'utf-8', $s );
2888  }
2889 
2894  return self::$dataCache->getItem( $this->mCode, 'fallback8bitEncoding' );
2895  }
2896 
2905  function hasWordBreaks() {
2906  return true;
2907  }
2908 
2916  function segmentByWord( $string ) {
2917  return $string;
2918  }
2919 
2927  function normalizeForSearch( $string ) {
2928  return self::convertDoubleWidth( $string );
2929  }
2930 
2939  protected static function convertDoubleWidth( $string ) {
2940  static $full = null;
2941  static $half = null;
2942 
2943  if ( $full === null ) {
2944  $fullWidth = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
2945  $halfWidth = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
2946  $full = str_split( $fullWidth, 3 );
2947  $half = str_split( $halfWidth );
2948  }
2949 
2950  $string = str_replace( $full, $half, $string );
2951  return $string;
2952  }
2953 
2959  protected static function insertSpace( $string, $pattern ) {
2960  $string = preg_replace( $pattern, " $1 ", $string );
2961  $string = preg_replace( '/ +/', ' ', $string );
2962  return $string;
2963  }
2964 
2969  function convertForSearchResult( $termsArray ) {
2970  # some languages, e.g. Chinese, need to do a conversion
2971  # in order for search results to be displayed correctly
2972  return $termsArray;
2973  }
2974 
2981  function firstChar( $s ) {
2982  $matches = [];
2983  preg_match(
2984  '/^([\x00-\x7f]|[\xc0-\xdf][\x80-\xbf]|' .
2985  '[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xf7][\x80-\xbf]{3})/',
2986  $s,
2987  $matches
2988  );
2989 
2990  if ( isset( $matches[1] ) ) {
2991  if ( strlen( $matches[1] ) != 3 ) {
2992  return $matches[1];
2993  }
2994 
2995  // Break down Hangul syllables to grab the first jamo
2996  $code = UtfNormal\Utils::utf8ToCodepoint( $matches[1] );
2997  if ( $code < 0xac00 || 0xd7a4 <= $code ) {
2998  return $matches[1];
2999  } elseif ( $code < 0xb098 ) {
3000  return "\u{3131}";
3001  } elseif ( $code < 0xb2e4 ) {
3002  return "\u{3134}";
3003  } elseif ( $code < 0xb77c ) {
3004  return "\u{3137}";
3005  } elseif ( $code < 0xb9c8 ) {
3006  return "\u{3139}";
3007  } elseif ( $code < 0xbc14 ) {
3008  return "\u{3141}";
3009  } elseif ( $code < 0xc0ac ) {
3010  return "\u{3142}";
3011  } elseif ( $code < 0xc544 ) {
3012  return "\u{3145}";
3013  } elseif ( $code < 0xc790 ) {
3014  return "\u{3147}";
3015  } elseif ( $code < 0xcc28 ) {
3016  return "\u{3148}";
3017  } elseif ( $code < 0xce74 ) {
3018  return "\u{314A}";
3019  } elseif ( $code < 0xd0c0 ) {
3020  return "\u{314B}";
3021  } elseif ( $code < 0xd30c ) {
3022  return "\u{314C}";
3023  } elseif ( $code < 0xd558 ) {
3024  return "\u{314D}";
3025  } else {
3026  return "\u{314E}";
3027  }
3028  } else {
3029  return '';
3030  }
3031  }
3032 
3036  function initEncoding() {
3037  wfDeprecated( __METHOD__, '1.28' );
3038  // No-op.
3039  }
3040 
3046  function recodeForEdit( $s ) {
3047  wfDeprecated( __METHOD__, '1.28' );
3048  return $s;
3049  }
3050 
3056  function recodeInput( $s ) {
3057  wfDeprecated( __METHOD__, '1.28' );
3058  return $s;
3059  }
3060 
3072  public function normalize( $s ) {
3073  global $wgAllUnicodeFixes;
3074  $s = UtfNormal\Validator::cleanUp( $s );
3075  if ( $wgAllUnicodeFixes ) {
3076  $s = $this->transformUsingPairFile( 'normalize-ar.php', $s );
3077  $s = $this->transformUsingPairFile( 'normalize-ml.php', $s );
3078  }
3079 
3080  return $s;
3081  }
3082 
3097  protected function transformUsingPairFile( $file, $string ) {
3098  if ( !isset( $this->transformData[$file] ) ) {
3099  global $IP;
3100  $data = require "$IP/languages/data/{$file}";
3101  $this->transformData[$file] = new ReplacementArray( $data );
3102  }
3103  return $this->transformData[$file]->replace( $string );
3104  }
3105 
3111  function isRTL() {
3112  return self::$dataCache->getItem( $this->mCode, 'rtl' );
3113  }
3114 
3119  function getDir() {
3120  return $this->isRTL() ? 'rtl' : 'ltr';
3121  }
3122 
3131  function alignStart() {
3132  return $this->isRTL() ? 'right' : 'left';
3133  }
3134 
3143  function alignEnd() {
3144  return $this->isRTL() ? 'left' : 'right';
3145  }
3146 
3158  function getDirMarkEntity( $opposite = false ) {
3159  if ( $opposite ) {
3160  return $this->isRTL() ? '&lrm;' : '&rlm;';
3161  }
3162  return $this->isRTL() ? '&rlm;' : '&lrm;';
3163  }
3164 
3175  function getDirMark( $opposite = false ) {
3176  $lrm = "\u{200E}"; # LEFT-TO-RIGHT MARK, commonly abbreviated LRM
3177  $rlm = "\u{200F}"; # RIGHT-TO-LEFT MARK, commonly abbreviated RLM
3178  if ( $opposite ) {
3179  return $this->isRTL() ? $lrm : $rlm;
3180  }
3181  return $this->isRTL() ? $rlm : $lrm;
3182  }
3183 
3187  function capitalizeAllNouns() {
3188  return self::$dataCache->getItem( $this->mCode, 'capitalizeAllNouns' );
3189  }
3190 
3198  function getArrow( $direction = 'forwards' ) {
3199  switch ( $direction ) {
3200  case 'forwards':
3201  return $this->isRTL() ? '←' : '→';
3202  case 'backwards':
3203  return $this->isRTL() ? '→' : '←';
3204  case 'left':
3205  return '←';
3206  case 'right':
3207  return '→';
3208  case 'up':
3209  return '↑';
3210  case 'down':
3211  return '↓';
3212  }
3213  }
3214 
3220  function linkPrefixExtension() {
3221  return self::$dataCache->getItem( $this->mCode, 'linkPrefixExtension' );
3222  }
3223 
3228  function getMagicWords() {
3229  return self::$dataCache->getItem( $this->mCode, 'magicWords' );
3230  }
3231 
3237  function getMagic( $mw ) {
3238  $rawEntry = $this->mMagicExtensions[$mw->mId] ??
3239  self::$dataCache->getSubitem( $this->mCode, 'magicWords', $mw->mId );
3240 
3241  if ( !is_array( $rawEntry ) ) {
3242  wfWarn( "\"$rawEntry\" is not a valid magic word for \"$mw->mId\"" );
3243  } else {
3244  $mw->mCaseSensitive = $rawEntry[0];
3245  $mw->mSynonyms = array_slice( $rawEntry, 1 );
3246  }
3247  }
3248 
3254  function addMagicWordsByLang( $newWords ) {
3255  $fallbackChain = $this->getFallbackLanguages();
3256  $fallbackChain = array_reverse( $fallbackChain );
3257  foreach ( $fallbackChain as $code ) {
3258  if ( isset( $newWords[$code] ) ) {
3259  $this->mMagicExtensions = $newWords[$code] + $this->mMagicExtensions;
3260  }
3261  }
3262  }
3263 
3270  // Cache aliases because it may be slow to load them
3271  if ( is_null( $this->mExtendedSpecialPageAliases ) ) {
3272  // Initialise array
3273  $this->mExtendedSpecialPageAliases =
3274  self::$dataCache->getItem( $this->mCode, 'specialPageAliases' );
3275  }
3276 
3278  }
3279 
3286  function emphasize( $text ) {
3287  return "<em>$text</em>";
3288  }
3289 
3312  public function formatNum( $number, $nocommafy = false ) {
3313  global $wgTranslateNumerals;
3314  if ( !$nocommafy ) {
3315  $number = $this->commafy( $number );
3316  $s = $this->separatorTransformTable();
3317  if ( $s ) {
3318  $number = strtr( $number, $s );
3319  }
3320  }
3321 
3322  if ( $wgTranslateNumerals ) {
3323  $s = $this->digitTransformTable();
3324  if ( $s ) {
3325  $number = strtr( $number, $s );
3326  }
3327  }
3328 
3329  return (string)$number;
3330  }
3331 
3340  public function formatNumNoSeparators( $number ) {
3341  return $this->formatNum( $number, true );
3342  }
3343 
3348  public function parseFormattedNumber( $number ) {
3349  $s = $this->digitTransformTable();
3350  if ( $s ) {
3351  // eliminate empty array values such as ''. (T66347)
3352  $s = array_filter( $s );
3353  $number = strtr( $number, array_flip( $s ) );
3354  }
3355 
3356  $s = $this->separatorTransformTable();
3357  if ( $s ) {
3358  // eliminate empty array values such as ''. (T66347)
3359  $s = array_filter( $s );
3360  $number = strtr( $number, array_flip( $s ) );
3361  }
3362 
3363  $number = strtr( $number, [ ',' => '' ] );
3364  return $number;
3365  }
3366 
3373  function commafy( $number ) {
3376  if ( $number === null ) {
3377  return '';
3378  }
3379 
3380  if ( !$digitGroupingPattern || $digitGroupingPattern === "###,###,###" ) {
3381  // Default grouping is at thousands, use the same for ###,###,### pattern too.
3382  // In some languages it's conventional not to insert a thousands separator
3383  // in numbers that are four digits long (1000-9999).
3384  if ( $minimumGroupingDigits ) {
3385  // Number of '#' characters after last comma in the grouping pattern.
3386  // The pattern is hardcoded here, but this would vary for different patterns.
3387  $primaryGroupingSize = 3;
3388  // Maximum length of a number to suppress digit grouping for.
3389  $maximumLength = $minimumGroupingDigits + $primaryGroupingSize - 1;
3390  if ( preg_match( '/^\-?\d{1,' . $maximumLength . '}(\.\d+)?$/', $number ) ) {
3391  return $number;
3392  }
3393  }
3394  return strrev( (string)preg_replace( '/(\d{3})(?=\d)(?!\d*\.)/', '$1,', strrev( $number ) ) );
3395  } else {
3396  // Ref: http://cldr.unicode.org/translation/number-patterns
3397  $sign = "";
3398  if ( intval( $number ) < 0 ) {
3399  // For negative numbers apply the algorithm like positive number and add sign.
3400  $sign = "-";
3401  $number = substr( $number, 1 );
3402  }
3403  $integerPart = [];
3404  $decimalPart = [];
3405  $numMatches = preg_match_all( "/(#+)/", $digitGroupingPattern, $matches );
3406  preg_match( "/\d+/", $number, $integerPart );
3407  preg_match( "/\.\d*/", $number, $decimalPart );
3408  $groupedNumber = ( count( $decimalPart ) > 0 ) ? $decimalPart[0] : "";
3409  if ( $groupedNumber === $number ) {
3410  // the string does not have any number part. Eg: .12345
3411  return $sign . $groupedNumber;
3412  }
3413  $start = $end = ( $integerPart ) ? strlen( $integerPart[0] ) : 0;
3414  while ( $start > 0 ) {
3415  $match = $matches[0][$numMatches - 1];
3416  $matchLen = strlen( $match );
3417  $start = $end - $matchLen;
3418  if ( $start < 0 ) {
3419  $start = 0;
3420  }
3421  $groupedNumber = substr( $number, $start, $end - $start ) . $groupedNumber;
3422  $end = $start;
3423  if ( $numMatches > 1 ) {
3424  // use the last pattern for the rest of the number
3425  $numMatches--;
3426  }
3427  if ( $start > 0 ) {
3428  $groupedNumber = "," . $groupedNumber;
3429  }
3430  }
3431  return $sign . $groupedNumber;
3432  }
3433  }
3434 
3439  return self::$dataCache->getItem( $this->mCode, 'digitGroupingPattern' );
3440  }
3441 
3445  function digitTransformTable() {
3446  return self::$dataCache->getItem( $this->mCode, 'digitTransformTable' );
3447  }
3448 
3453  return self::$dataCache->getItem( $this->mCode, 'separatorTransformTable' );
3454  }
3455 
3460  return self::$dataCache->getItem( $this->mCode, 'minimumGroupingDigits' );
3461  }
3462 
3471  public function listToText( array $list ) {
3472  $itemCount = count( $list );
3473  if ( $itemCount < 1 ) {
3474  return '';
3475  }
3476  $text = array_pop( $list );
3477  if ( $itemCount > 1 ) {
3478  $and = $this->msg( 'and' )->escaped();
3479  $space = $this->msg( 'word-separator' )->escaped();
3480  $comma = '';
3481  if ( $itemCount > 2 ) {
3482  $comma = $this->msg( 'comma-separator' )->escaped();
3483  }
3484  $text = implode( $comma, $list ) . $and . $space . $text;
3485  }
3486  return $text;
3487  }
3488 
3495  function commaList( array $list ) {
3496  return implode(
3497  wfMessage( 'comma-separator' )->inLanguage( $this )->escaped(),
3498  $list
3499  );
3500  }
3501 
3508  function semicolonList( array $list ) {
3509  return implode(
3510  wfMessage( 'semicolon-separator' )->inLanguage( $this )->escaped(),
3511  $list
3512  );
3513  }
3514 
3520  function pipeList( array $list ) {
3521  return implode(
3522  wfMessage( 'pipe-separator' )->inLanguage( $this )->escaped(),
3523  $list
3524  );
3525  }
3526 
3542  function truncateForDatabase( $string, $length, $ellipsis = '...', $adjustLength = true ) {
3543  return $this->truncateInternal(
3544  $string, $length, $ellipsis, $adjustLength, 'strlen', 'substr'
3545  );
3546  }
3547 
3566  function truncateForVisual( $string, $length, $ellipsis = '...', $adjustLength = true ) {
3567  // Passing encoding to mb_strlen and mb_substr is optional.
3568  // Encoding defaults to mb_internal_encoding(), which is set to UTF-8 in Setup.php, so
3569  // explicit specification of encoding is skipped.
3570  // Note: Both multibyte methods are callables invoked in truncateInternal.
3571  return $this->truncateInternal(
3572  $string, $length, $ellipsis, $adjustLength, 'mb_strlen', 'mb_substr'
3573  );
3574  }
3575 
3592  private function truncateInternal(
3593  $string, $length, $ellipsis, $adjustLength, callable $measureLength, callable $getSubstring
3594  ) {
3595  # Check if there is no need to truncate
3596  if ( $measureLength( $string ) <= abs( $length ) ) {
3597  return $string; // no need to truncate
3598  }
3599 
3600  # Use the localized ellipsis character
3601  if ( $ellipsis == '...' ) {
3602  $ellipsis = wfMessage( 'ellipsis' )->inLanguage( $this )->escaped();
3603  }
3604  if ( $length == 0 ) {
3605  return $ellipsis; // convention
3606  }
3607 
3608  $stringOriginal = $string;
3609  # If ellipsis length is >= $length then we can't apply $adjustLength
3610  if ( $adjustLength && $measureLength( $ellipsis ) >= abs( $length ) ) {
3611  $string = $ellipsis; // this can be slightly unexpected
3612  # Otherwise, truncate and add ellipsis...
3613  } else {
3614  $ellipsisLength = $adjustLength ? $measureLength( $ellipsis ) : 0;
3615  if ( $length > 0 ) {
3616  $length -= $ellipsisLength;
3617  $string = $getSubstring( $string, 0, $length ); // xyz...
3618  $string = $this->removeBadCharLast( $string );
3619  $string = rtrim( $string ) . $ellipsis;
3620  } else {
3621  $length += $ellipsisLength;
3622  $string = $getSubstring( $string, $length ); // ...xyz
3623  $string = $this->removeBadCharFirst( $string );
3624  $string = $ellipsis . ltrim( $string );
3625  }
3626  }
3627 
3628  # Do not truncate if the ellipsis makes the string longer/equal (T24181).
3629  # This check is *not* redundant if $adjustLength, due to the single case where
3630  # LEN($ellipsis) > ABS($limit arg); $stringOriginal could be shorter than $string.
3631  if ( $measureLength( $string ) < $measureLength( $stringOriginal ) ) {
3632  return $string;
3633  } else {
3634  return $stringOriginal;
3635  }
3636  }
3637 
3645  protected function removeBadCharLast( $string ) {
3646  if ( $string != '' ) {
3647  $char = ord( $string[strlen( $string ) - 1] );
3648  $m = [];
3649  if ( $char >= 0xc0 ) {
3650  # We got the first byte only of a multibyte char; remove it.
3651  $string = substr( $string, 0, -1 );
3652  } elseif ( $char >= 0x80 &&
3653  // Use the /s modifier (PCRE_DOTALL) so (.*) also matches newlines
3654  preg_match( '/^(.*)(?:[\xe0-\xef][\x80-\xbf]|' .
3655  '[\xf0-\xf7][\x80-\xbf]{1,2})$/s', $string, $m )
3656  ) {
3657  # We chopped in the middle of a character; remove it
3658  $string = $m[1];
3659  }
3660  }
3661  return $string;
3662  }
3663 
3671  protected function removeBadCharFirst( $string ) {
3672  if ( $string != '' ) {
3673  $char = ord( $string[0] );
3674  if ( $char >= 0x80 && $char < 0xc0 ) {
3675  # We chopped in the middle of a character; remove the whole thing
3676  $string = preg_replace( '/^[\x80-\xbf]+/', '', $string );
3677  }
3678  }
3679  return $string;
3680  }
3681 
3697  function truncateHtml( $text, $length, $ellipsis = '...' ) {
3698  # Use the localized ellipsis character
3699  if ( $ellipsis == '...' ) {
3700  $ellipsis = wfMessage( 'ellipsis' )->inLanguage( $this )->escaped();
3701  }
3702  # Check if there is clearly no need to truncate
3703  if ( $length <= 0 ) {
3704  return $ellipsis; // no text shown, nothing to format (convention)
3705  } elseif ( strlen( $text ) <= $length ) {
3706  return $text; // string short enough even *with* HTML (short-circuit)
3707  }
3708 
3709  $dispLen = 0; // innerHTML legth so far
3710  $testingEllipsis = false; // checking if ellipses will make string longer/equal?
3711  $tagType = 0; // 0-open, 1-close
3712  $bracketState = 0; // 1-tag start, 2-tag name, 0-neither
3713  $entityState = 0; // 0-not entity, 1-entity
3714  $tag = $ret = ''; // accumulated tag name, accumulated result string
3715  $openTags = []; // open tag stack
3716  $maybeState = null; // possible truncation state
3717 
3718  $textLen = strlen( $text );
3719  $neLength = max( 0, $length - strlen( $ellipsis ) ); // non-ellipsis len if truncated
3720  for ( $pos = 0; true; ++$pos ) {
3721  # Consider truncation once the display length has reached the maximim.
3722  # We check if $dispLen > 0 to grab tags for the $neLength = 0 case.
3723  # Check that we're not in the middle of a bracket/entity...
3724  if ( $dispLen && $dispLen >= $neLength && $bracketState == 0 && !$entityState ) {
3725  if ( !$testingEllipsis ) {
3726  $testingEllipsis = true;
3727  # Save where we are; we will truncate here unless there turn out to
3728  # be so few remaining characters that truncation is not necessary.
3729  if ( !$maybeState ) { // already saved? ($neLength = 0 case)
3730  $maybeState = [ $ret, $openTags ]; // save state
3731  }
3732  } elseif ( $dispLen > $length && $dispLen > strlen( $ellipsis ) ) {
3733  # String in fact does need truncation, the truncation point was OK.
3734  // @phan-suppress-next-line PhanTypeInvalidExpressionArrayDestructuring
3735  list( $ret, $openTags ) = $maybeState; // reload state
3736  $ret = $this->removeBadCharLast( $ret ); // multi-byte char fix
3737  $ret .= $ellipsis; // add ellipsis
3738  break;
3739  }
3740  }
3741  if ( $pos >= $textLen ) {
3742  break; // extra iteration just for above checks
3743  }
3744 
3745  # Read the next char...
3746  $ch = $text[$pos];
3747  $lastCh = $pos ? $text[$pos - 1] : '';
3748  $ret .= $ch; // add to result string
3749  if ( $ch == '<' ) {
3750  $this->truncate_endBracket( $tag, $tagType, $lastCh, $openTags ); // for bad HTML
3751  $entityState = 0; // for bad HTML
3752  $bracketState = 1; // tag started (checking for backslash)
3753  } elseif ( $ch == '>' ) {
3754  $this->truncate_endBracket( $tag, $tagType, $lastCh, $openTags );
3755  $entityState = 0; // for bad HTML
3756  $bracketState = 0; // out of brackets
3757  } elseif ( $bracketState == 1 ) {
3758  if ( $ch == '/' ) {
3759  $tagType = 1; // close tag (e.g. "</span>")
3760  } else {
3761  $tagType = 0; // open tag (e.g. "<span>")
3762  $tag .= $ch;
3763  }
3764  $bracketState = 2; // building tag name
3765  } elseif ( $bracketState == 2 ) {
3766  if ( $ch != ' ' ) {
3767  $tag .= $ch;
3768  } else {
3769  // Name found (e.g. "<a href=..."), add on tag attributes...
3770  $pos += $this->truncate_skip( $ret, $text, "<>", $pos + 1 );
3771  }
3772  } elseif ( $bracketState == 0 ) {
3773  if ( $entityState ) {
3774  if ( $ch == ';' ) {
3775  $entityState = 0;
3776  $dispLen++; // entity is one displayed char
3777  }
3778  } else {
3779  if ( $neLength == 0 && !$maybeState ) {
3780  // Save state without $ch. We want to *hit* the first
3781  // display char (to get tags) but not *use* it if truncating.
3782  $maybeState = [ substr( $ret, 0, -1 ), $openTags ];
3783  }
3784  if ( $ch == '&' ) {
3785  $entityState = 1; // entity found, (e.g. "&#160;")
3786  } else {
3787  $dispLen++; // this char is displayed
3788  // Add the next $max display text chars after this in one swoop...
3789  $max = ( $testingEllipsis ? $length : $neLength ) - $dispLen;
3790  $skipped = $this->truncate_skip( $ret, $text, "<>&", $pos + 1, $max );
3791  $dispLen += $skipped;
3792  $pos += $skipped;
3793  }
3794  }
3795  }
3796  }
3797  // Close the last tag if left unclosed by bad HTML
3798  $this->truncate_endBracket( $tag, $text[$textLen - 1], $tagType, $openTags );
3799  while ( count( $openTags ) > 0 ) {
3800  $ret .= '</' . array_pop( $openTags ) . '>'; // close open tags
3801  }
3802  return $ret;
3803  }
3804 
3816  private function truncate_skip( &$ret, $text, $search, $start, $len = null ) {
3817  if ( $len === null ) {
3818  $len = -1; // -1 means "no limit" for strcspn
3819  } elseif ( $len < 0 ) {
3820  $len = 0; // sanity
3821  }
3822  $skipCount = 0;
3823  if ( $start < strlen( $text ) ) {
3824  $skipCount = strcspn( $text, $search, $start, $len );
3825  $ret .= substr( $text, $start, $skipCount );
3826  }
3827  return $skipCount;
3828  }
3829 
3839  private function truncate_endBracket( &$tag, $tagType, $lastCh, &$openTags ) {
3840  $tag = ltrim( $tag );
3841  if ( $tag != '' ) {
3842  if ( $tagType == 0 && $lastCh != '/' ) {
3843  $openTags[] = $tag; // tag opened (didn't close itself)
3844  } elseif ( $tagType == 1 ) {
3845  if ( $openTags && $tag == $openTags[count( $openTags ) - 1] ) {
3846  array_pop( $openTags ); // tag closed
3847  }
3848  }
3849  $tag = '';
3850  }
3851  }
3852 
3861  function convertGrammar( $word, $case ) {
3862  global $wgGrammarForms;
3863  if ( isset( $wgGrammarForms[$this->getCode()][$case][$word] ) ) {
3864  return $wgGrammarForms[$this->getCode()][$case][$word];
3865  }
3866 
3867  $grammarTransformations = $this->getGrammarTransformations();
3868 
3869  if ( isset( $grammarTransformations[$case] ) ) {
3870  $forms = $grammarTransformations[$case];
3871 
3872  // Some names of grammar rules are aliases for other rules.
3873  // In such cases the value is a string rather than object,
3874  // so load the actual rules.
3875  if ( is_string( $forms ) ) {
3876  $forms = $grammarTransformations[$forms];
3877  }
3878 
3879  foreach ( array_values( $forms ) as $rule ) {
3880  $form = $rule[0];
3881 
3882  if ( $form === '@metadata' ) {
3883  continue;
3884  }
3885 
3886  $replacement = $rule[1];
3887 
3888  $regex = '/' . addcslashes( $form, '/' ) . '/u';
3889  $patternMatches = preg_match( $regex, $word );
3890 
3891  if ( $patternMatches === false ) {
3892  wfLogWarning(
3893  'An error occurred while processing grammar. ' .
3894  "Word: '$word'. Regex: /$form/."
3895  );
3896  } elseif ( $patternMatches === 1 ) {
3897  $word = preg_replace( $regex, $replacement, $word );
3898 
3899  break;
3900  }
3901  }
3902  }
3903 
3904  return $word;
3905  }
3906 
3912  function getGrammarForms() {
3913  global $wgGrammarForms;
3914  if ( isset( $wgGrammarForms[$this->getCode()] )
3915  && is_array( $wgGrammarForms[$this->getCode()] )
3916  ) {
3917  return $wgGrammarForms[$this->getCode()];
3918  }
3919 
3920  return [];
3921  }
3922 
3932  public function getGrammarTransformations() {
3933  $languageCode = $this->getCode();
3934 
3935  if ( self::$grammarTransformations === null ) {
3936  self::$grammarTransformations = new MapCacheLRU( 10 );
3937  }
3938 
3939  if ( self::$grammarTransformations->has( $languageCode ) ) {
3940  return self::$grammarTransformations->get( $languageCode );
3941  }
3942 
3943  $data = [];
3944 
3945  $grammarDataFile = __DIR__ . "/data/grammarTransformations/$languageCode.json";
3946  if ( is_readable( $grammarDataFile ) ) {
3948  file_get_contents( $grammarDataFile ),
3949  true
3950  );
3951 
3952  if ( $data === null ) {
3953  throw new MWException( "Invalid grammar data for \"$languageCode\"." );
3954  }
3955 
3956  self::$grammarTransformations->set( $languageCode, $data );
3957  }
3958 
3959  return $data;
3960  }
3961 
3981  function gender( $gender, $forms ) {
3982  if ( !count( $forms ) ) {
3983  return '';
3984  }
3985  $forms = $this->preConvertPlural( $forms, 2 );
3986  if ( $gender === 'male' ) {
3987  return $forms[0];
3988  }
3989  if ( $gender === 'female' ) {
3990  return $forms[1];
3991  }
3992  return $forms[2] ?? $forms[0];
3993  }
3994 
4010  function convertPlural( $count, $forms ) {
4011  // Handle explicit n=pluralform cases
4012  $forms = $this->handleExplicitPluralForms( $count, $forms );
4013  if ( is_string( $forms ) ) {
4014  return $forms;
4015  }
4016  if ( !count( $forms ) ) {
4017  return '';
4018  }
4019 
4020  $pluralForm = $this->getPluralRuleIndexNumber( $count );
4021  $pluralForm = min( $pluralForm, count( $forms ) - 1 );
4022  return $forms[$pluralForm];
4023  }
4024 
4040  protected function handleExplicitPluralForms( $count, array $forms ) {
4041  foreach ( $forms as $index => $form ) {
4042  if ( preg_match( '/\d+=/i', $form ) ) {
4043  $pos = strpos( $form, '=' );
4044  if ( substr( $form, 0, $pos ) === (string)$count ) {
4045  return substr( $form, $pos + 1 );
4046  }
4047  unset( $forms[$index] );
4048  }
4049  }
4050  return array_values( $forms );
4051  }
4052 
4061  protected function preConvertPlural( /* Array */ $forms, $count ) {
4062  return array_pad( $forms, $count, end( $forms ) );
4063  }
4064 
4081  public function embedBidi( $text = '' ) {
4082  $dir = self::strongDirFromContent( $text );
4083  if ( $dir === 'ltr' ) {
4084  // Wrap in LEFT-TO-RIGHT EMBEDDING ... POP DIRECTIONAL FORMATTING
4085  return self::$lre . $text . self::$pdf;
4086  }
4087  if ( $dir === 'rtl' ) {
4088  // Wrap in RIGHT-TO-LEFT EMBEDDING ... POP DIRECTIONAL FORMATTING
4089  return self::$rle . $text . self::$pdf;
4090  }
4091  // No strong directionality: do not wrap
4092  return $text;
4093  }
4094 
4108  function translateBlockExpiry( $str, User $user = null, $now = 0 ) {
4109  $duration = SpecialBlock::getSuggestedDurations( $this );
4110  foreach ( $duration as $show => $value ) {
4111  if ( strcmp( $str, $value ) == 0 ) {
4112  return htmlspecialchars( trim( $show ) );
4113  }
4114  }
4115 
4116  if ( wfIsInfinity( $str ) ) {
4117  foreach ( $duration as $show => $value ) {
4118  if ( wfIsInfinity( $value ) ) {
4119  return htmlspecialchars( trim( $show ) );
4120  }
4121  }
4122  }
4123 
4124  // If all else fails, return a standard duration or timestamp description.
4125  $time = strtotime( $str, $now );
4126  if ( $time === false ) { // Unknown format. Return it as-is in case.
4127  return $str;
4128  } elseif ( $time !== strtotime( $str, $now + 1 ) ) { // It's a relative timestamp.
4129  // The result differs based on current time, so the difference
4130  // is a fixed duration length.
4131  return $this->formatDuration( $time - $now );
4132  } else { // It's an absolute timestamp.
4133  if ( $time === 0 ) {
4134  // wfTimestamp() handles 0 as current time instead of epoch.
4135  $time = '19700101000000';
4136  }
4137  if ( $user ) {
4138  return $this->userTimeAndDate( $time, $user );
4139  }
4140  return $this->timeanddate( $time );
4141  }
4142  }
4143 
4151  public function segmentForDiff( $text ) {
4152  return $text;
4153  }
4154 
4161  public function unsegmentForDiff( $text ) {
4162  return $text;
4163  }
4164 
4171  public function getConverter() {
4172  return $this->mConverter;
4173  }
4174 
4183  public function autoConvert( $text, $variant = false ) {
4184  return $this->mConverter->autoConvert( $text, $variant );
4185  }
4186 
4193  public function autoConvertToAllVariants( $text ) {
4194  return $this->mConverter->autoConvertToAllVariants( $text );
4195  }
4196 
4208  public function convert( $text ) {
4209  return $this->mConverter->convert( $text );
4210  }
4211 
4218  public function convertTitle( $title ) {
4219  return $this->mConverter->convertTitle( $title );
4220  }
4221 
4230  public function convertNamespace( $ns, $variant = null ) {
4231  return $this->mConverter->convertNamespace( $ns, $variant );
4232  }
4233 
4239  public function hasVariants() {
4240  return count( $this->getVariants() ) > 1;
4241  }
4242 
4253  public function hasVariant( $variant ) {
4254  return $variant && ( $variant === $this->mConverter->validateVariant( $variant ) );
4255  }
4256 
4263  public function convertHtml( $text ) {
4264  return htmlspecialchars( $this->convert( $text ) );
4265  }
4266 
4271  public function convertCategoryKey( $key ) {
4272  return $this->mConverter->convertCategoryKey( $key );
4273  }
4274 
4281  public function getVariants() {
4282  return $this->mConverter->getVariants();
4283  }
4284 
4288  public function getPreferredVariant() {
4289  return $this->mConverter->getPreferredVariant();
4290  }
4291 
4295  public function getDefaultVariant() {
4296  return $this->mConverter->getDefaultVariant();
4297  }
4298 
4302  public function getURLVariant() {
4303  return $this->mConverter->getURLVariant();
4304  }
4305 
4318  public function findVariantLink( &$link, &$nt, $ignoreOtherCond = false ) {
4319  $this->mConverter->findVariantLink( $link, $nt, $ignoreOtherCond );
4320  }
4321 
4328  function getExtraHashOptions() {
4329  return $this->mConverter->getExtraHashOptions();
4330  }
4331 
4339  public function getParsedTitle() {
4340  return $this->mConverter->getParsedTitle();
4341  }
4342 
4349  public function updateConversionTable( Title $title ) {
4350  $this->mConverter->updateConversionTable( $title );
4351  }
4352 
4359  public function linkTrail() {
4360  return self::$dataCache->getItem( $this->mCode, 'linkTrail' );
4361  }
4362 
4369  public function linkPrefixCharset() {
4370  return self::$dataCache->getItem( $this->mCode, 'linkPrefixCharset' );
4371  }
4372 
4380  public function getParentLanguage() {
4381  if ( $this->mParentLanguage !== false ) {
4382  return $this->mParentLanguage;
4383  }
4384 
4385  $code = explode( '-', $this->getCode() )[0];
4386  if ( !in_array( $code, LanguageConverter::$languagesWithVariants ) ) {
4387  $this->mParentLanguage = null;
4388  return null;
4389  }
4390  $lang = self::factory( $code );
4391  if ( !$lang->hasVariant( $this->getCode() ) ) {
4392  $this->mParentLanguage = null;
4393  return null;
4394  }
4395 
4396  $this->mParentLanguage = $lang;
4397  return $lang;
4398  }
4399 
4407  public function equals( Language $lang ) {
4408  return $lang === $this || $lang->getCode() === $this->mCode;
4409  }
4410 
4419  public function getCode() {
4420  return $this->mCode;
4421  }
4422 
4433  public function getHtmlCode() {
4434  if ( is_null( $this->mHtmlCode ) ) {
4435  $this->mHtmlCode = LanguageCode::bcp47( $this->getCode() );
4436  }
4437  return $this->mHtmlCode;
4438  }
4439 
4447  public static function getCodeFromFileName( $filename, $prefix = 'Language', $suffix = '.php' ) {
4448  $m = null;
4449  preg_match( '/' . preg_quote( $prefix, '/' ) . '([A-Z][a-z_]+)' .
4450  preg_quote( $suffix, '/' ) . '/', $filename, $m );
4451  if ( !count( $m ) ) {
4452  return false;
4453  }
4454  return str_replace( '_', '-', strtolower( $m[1] ) );
4455  }
4456 
4462  public static function classFromCode( $code, $fallback = true ) {
4463  if ( $fallback && $code == 'en' ) {
4464  return 'Language';
4465  } else {
4466  return 'Language' . str_replace( '-', '_', ucfirst( $code ) );
4467  }
4468  }
4469 
4478  public static function getFileName( $prefix, $code, $suffix = '.php' ) {
4479  if ( !self::isValidBuiltInCode( $code ) ) {
4480  throw new MWException( "Invalid language code \"$code\"" );
4481  }
4482 
4483  return $prefix . str_replace( '-', '_', ucfirst( $code ) ) . $suffix;
4484  }
4485 
4490  public static function getMessagesFileName( $code ) {
4491  global $IP;
4492  $file = self::getFileName( "$IP/languages/messages/Messages", $code, '.php' );
4493  Hooks::run( 'Language::getMessagesFileName', [ $code, &$file ] );
4494  return $file;
4495  }
4496 
4503  public static function getJsonMessagesFileName( $code ) {
4504  global $IP;
4505 
4506  if ( !self::isValidBuiltInCode( $code ) ) {
4507  throw new MWException( "Invalid language code \"$code\"" );
4508  }
4509 
4510  return "$IP/languages/i18n/$code.json";
4511  }
4512 
4520  public static function getFallbackFor( $code ) {
4521  $fallbacks = self::getFallbacksFor( $code );
4522  if ( $fallbacks ) {
4523  return $fallbacks[0];
4524  }
4525  return false;
4526  }
4527 
4538  public static function getFallbacksFor( $code, $mode = self::MESSAGES_FALLBACKS ) {
4539  if ( $code === 'en' || !self::isValidBuiltInCode( $code ) ) {
4540  return [];
4541  }
4542  switch ( $mode ) {
4543  case self::MESSAGES_FALLBACKS:
4544  // For unknown languages, fallbackSequence returns an empty array,
4545  // hardcode fallback to 'en' in that case as English messages are
4546  // always defined.
4547  return self::getLocalisationCache()->getItem( $code, 'fallbackSequence' ) ?: [ 'en' ];
4548  case self::STRICT_FALLBACKS:
4549  // Use this mode when you don't want to fallback to English unless
4550  // explicitly defined, for example when you have language-variant icons
4551  // and an international language-independent fallback.
4552  return self::getLocalisationCache()->getItem( $code, 'originalFallbackSequence' );
4553  default:
4554  throw new MWException( "Invalid fallback mode \"$mode\"" );
4555  }
4556  }
4557 
4566  public static function getFallbacksIncludingSiteLanguage( $code ) {
4567  global $wgLanguageCode;
4568 
4569  // Usually, we will only store a tiny number of fallback chains, so we
4570  // keep them in static memory.
4571  $cacheKey = "{$code}-{$wgLanguageCode}";
4572 
4573  if ( !array_key_exists( $cacheKey, self::$fallbackLanguageCache ) ) {
4574  $fallbacks = self::getFallbacksFor( $code );
4575 
4576  // Append the site's fallback chain, including the site language itself
4577  $siteFallbacks = self::getFallbacksFor( $wgLanguageCode );
4578  array_unshift( $siteFallbacks, $wgLanguageCode );
4579 
4580  // Eliminate any languages already included in the chain
4581  $siteFallbacks = array_diff( $siteFallbacks, $fallbacks );
4582 
4583  self::$fallbackLanguageCache[$cacheKey] = [ $fallbacks, $siteFallbacks ];
4584  }
4585  return self::$fallbackLanguageCache[$cacheKey];
4586  }
4587 
4597  public static function getMessagesFor( $code ) {
4598  return self::getLocalisationCache()->getItem( $code, 'messages' );
4599  }
4600 
4609  public static function getMessageFor( $key, $code ) {
4610  return self::getLocalisationCache()->getSubitem( $code, 'messages', $key );
4611  }
4612 
4621  public static function getMessageKeysFor( $code ) {
4622  return self::getLocalisationCache()->getSubitemList( $code, 'messages' );
4623  }
4624 
4629  function fixVariableInNamespace( $talk ) {
4630  if ( strpos( $talk, '$1' ) === false ) {
4631  return $talk;
4632  }
4633 
4634  global $wgMetaNamespace;
4635  $talk = str_replace( '$1', $wgMetaNamespace, $talk );
4636 
4637  # Allow grammar transformations
4638  # Allowing full message-style parsing would make simple requests
4639  # such as action=raw much more expensive than they need to be.
4640  # This will hopefully cover most cases.
4641  $talk = preg_replace_callback( '/{{grammar:(.*?)\|(.*?)}}/i',
4642  [ $this, 'replaceGrammarInNamespace' ], $talk );
4643  return str_replace( ' ', '_', $talk );
4644  }
4645 
4650  function replaceGrammarInNamespace( $m ) {
4651  return $this->convertGrammar( trim( $m[2] ), trim( $m[1] ) );
4652  }
4653 
4664  public function formatExpiry( $expiry, $format = true, $infinity = 'infinity' ) {
4665  static $dbInfinity;
4666  if ( $dbInfinity === null ) {
4667  $dbInfinity = wfGetDB( DB_REPLICA )->getInfinity();
4668  }
4669 
4670  if ( $expiry == '' || $expiry === 'infinity' || $expiry == $dbInfinity ) {
4671  return $format === true
4672  ? $this->getMessageFromDB( 'infiniteblock' )
4673  : $infinity;
4674  } else {
4675  return $format === true
4676  ? $this->timeanddate( $expiry, /* User preference timezone */ true )
4677  : wfTimestamp( $format, $expiry );
4678  }
4679  }
4680 
4694  function formatTimePeriod( $seconds, $format = [] ) {
4695  if ( !is_array( $format ) ) {
4696  $format = [ 'avoid' => $format ]; // For backwards compatibility
4697  }
4698  if ( !isset( $format['avoid'] ) ) {
4699  $format['avoid'] = false;
4700  }
4701  if ( !isset( $format['noabbrevs'] ) ) {
4702  $format['noabbrevs'] = false;
4703  }
4704  $secondsMsg = wfMessage(
4705  $format['noabbrevs'] ? 'seconds' : 'seconds-abbrev' )->inLanguage( $this );
4706  $minutesMsg = wfMessage(
4707  $format['noabbrevs'] ? 'minutes' : 'minutes-abbrev' )->inLanguage( $this );
4708  $hoursMsg = wfMessage(
4709  $format['noabbrevs'] ? 'hours' : 'hours-abbrev' )->inLanguage( $this );
4710  $daysMsg = wfMessage(
4711  $format['noabbrevs'] ? 'days' : 'days-abbrev' )->inLanguage( $this );
4712 
4713  if ( round( $seconds * 10 ) < 100 ) {
4714  $s = $this->formatNum( sprintf( "%.1f", round( $seconds * 10 ) / 10 ) );
4715  $s = $secondsMsg->params( $s )->text();
4716  } elseif ( round( $seconds ) < 60 ) {
4717  $s = $this->formatNum( round( $seconds ) );
4718  $s = $secondsMsg->params( $s )->text();
4719  } elseif ( round( $seconds ) < 3600 ) {
4720  $minutes = floor( $seconds / 60 );
4721  $secondsPart = round( fmod( $seconds, 60 ) );
4722  if ( $secondsPart == 60 ) {
4723  $secondsPart = 0;
4724  $minutes++;
4725  }
4726  $s = $minutesMsg->params( $this->formatNum( $minutes ) )->text();
4727  $s .= ' ';
4728  $s .= $secondsMsg->params( $this->formatNum( $secondsPart ) )->text();
4729  } elseif ( round( $seconds ) <= 2 * 86400 ) {
4730  $hours = floor( $seconds / 3600 );
4731  $minutes = floor( ( $seconds - $hours * 3600 ) / 60 );
4732  $secondsPart = round( $seconds - $hours * 3600 - $minutes * 60 );
4733  if ( $secondsPart == 60 ) {
4734  $secondsPart = 0;
4735  $minutes++;
4736  }
4737  if ( $minutes == 60 ) {
4738  $minutes = 0;
4739  $hours++;
4740  }
4741  $s = $hoursMsg->params( $this->formatNum( $hours ) )->text();
4742  $s .= ' ';
4743  $s .= $minutesMsg->params( $this->formatNum( $minutes ) )->text();
4744  if ( !in_array( $format['avoid'], [ 'avoidseconds', 'avoidminutes' ] ) ) {
4745  $s .= ' ' . $secondsMsg->params( $this->formatNum( $secondsPart ) )->text();
4746  }
4747  } else {
4748  $days = floor( $seconds / 86400 );
4749  if ( $format['avoid'] === 'avoidminutes' ) {
4750  $hours = round( ( $seconds - $days * 86400 ) / 3600 );
4751  if ( $hours == 24 ) {
4752  $hours = 0;
4753  $days++;
4754  }
4755  $s = $daysMsg->params( $this->formatNum( $days ) )->text();
4756  $s .= ' ';
4757  $s .= $hoursMsg->params( $this->formatNum( $hours ) )->text();
4758  } elseif ( $format['avoid'] === 'avoidseconds' ) {
4759  $hours = floor( ( $seconds - $days * 86400 ) / 3600 );
4760  $minutes = round( ( $seconds - $days * 86400 - $hours * 3600 ) / 60 );
4761  if ( $minutes == 60 ) {
4762  $minutes = 0;
4763  $hours++;
4764  }
4765  if ( $hours == 24 ) {
4766  $hours = 0;
4767  $days++;
4768  }
4769  $s = $daysMsg->params( $this->formatNum( $days ) )->text();
4770  $s .= ' ';
4771  $s .= $hoursMsg->params( $this->formatNum( $hours ) )->text();
4772  $s .= ' ';
4773  $s .= $minutesMsg->params( $this->formatNum( $minutes ) )->text();
4774  } else {
4775  $s = $daysMsg->params( $this->formatNum( $days ) )->text();
4776  $s .= ' ';
4777  $s .= $this->formatTimePeriod( $seconds - $days * 86400, $format );
4778  }
4779  }
4780  return $s;
4781  }
4782 
4794  function formatBitrate( $bps ) {
4795  return $this->formatComputingNumbers( $bps, 1000, "bitrate-$1bits" );
4796  }
4797 
4804  function formatComputingNumbers( $size, $boundary, $messageKey ) {
4805  if ( $size <= 0 ) {
4806  return str_replace( '$1', $this->formatNum( $size ),
4807  $this->getMessageFromDB( str_replace( '$1', '', $messageKey ) )
4808  );
4809  }
4810  $sizes = [ '', 'kilo', 'mega', 'giga', 'tera', 'peta', 'exa', 'zeta', 'yotta' ];
4811  $index = 0;
4812 
4813  $maxIndex = count( $sizes ) - 1;
4814  while ( $size >= $boundary && $index < $maxIndex ) {
4815  $index++;
4816  $size /= $boundary;
4817  }
4818 
4819  // For small sizes no decimal places necessary
4820  $round = 0;
4821  if ( $index > 1 ) {
4822  // For MB and bigger two decimal places are smarter
4823  $round = 2;
4824  }
4825  $msg = str_replace( '$1', $sizes[$index], $messageKey );
4826 
4827  $size = round( $size, $round );
4828  $text = $this->getMessageFromDB( $msg );
4829  return str_replace( '$1', $this->formatNum( $size ), $text );
4830  }
4831 
4842  function formatSize( $size ) {
4843  return $this->formatComputingNumbers( $size, 1024, "size-$1bytes" );
4844  }
4845 
4855  function specialList( $page, $details, $oppositedm = true ) {
4856  if ( !$details ) {
4857  return $page;
4858  }
4859 
4860  $dirmark = ( $oppositedm ? $this->getDirMark( true ) : '' ) . $this->getDirMark();
4861  return $page .
4862  $dirmark .
4863  $this->msg( 'word-separator' )->escaped() .
4864  $this->msg( 'parentheses' )->rawParams( $details )->escaped();
4865  }
4866 
4879  public function viewPrevNext( Title $title, $offset, $limit,
4880  array $query = [], $atend = false
4881  ) {
4882  // @todo FIXME: Why on earth this needs one message for the text and another one for tooltip?
4883 
4884  # Make 'previous' link
4885  $prev = wfMessage( 'prevn' )->inLanguage( $this )->title( $title )->numParams( $limit )->text();
4886  if ( $offset > 0 ) {
4887  $plink = $this->numLink( $title, max( $offset - $limit, 0 ), $limit,
4888  $query, $prev, 'prevn-title', 'mw-prevlink' );
4889  } else {
4890  $plink = htmlspecialchars( $prev );
4891  }
4892 
4893  # Make 'next' link
4894  $next = wfMessage( 'nextn' )->inLanguage( $this )->title( $title )->numParams( $limit )->text();
4895  if ( $atend ) {
4896  $nlink = htmlspecialchars( $next );
4897  } else {
4898  $nlink = $this->numLink( $title, $offset + $limit, $limit,
4899  $query, $next, 'nextn-title', 'mw-nextlink' );
4900  }
4901 
4902  # Make links to set number of items per page
4903  $numLinks = [];
4904  foreach ( [ 20, 50, 100, 250, 500 ] as $num ) {
4905  $numLinks[] = $this->numLink( $title, $offset, $num,
4906  $query, $this->formatNum( $num ), 'shown-title', 'mw-numlink' );
4907  }
4908 
4909  return wfMessage( 'viewprevnext' )->inLanguage( $this )->title( $title
4910  )->rawParams( $plink, $nlink, $this->pipeList( $numLinks ) )->escaped();
4911  }
4912 
4925  private function numLink( Title $title, $offset, $limit, array $query, $link,
4926  $tooltipMsg, $class
4927  ) {
4928  $query = [ 'limit' => $limit, 'offset' => $offset ] + $query;
4929  $tooltip = wfMessage( $tooltipMsg )->inLanguage( $this )->title( $title )
4930  ->numParams( $limit )->text();
4931 
4932  return Html::element( 'a', [ 'href' => $title->getLocalURL( $query ),
4933  'title' => $tooltip, 'class' => $class ], $link );
4934  }
4935 
4941  public function getConvRuleTitle() {
4942  return $this->mConverter->getConvRuleTitle();
4943  }
4944 
4950  public function getCompiledPluralRules() {
4951  $pluralRules = self::$dataCache->getItem( strtolower( $this->mCode ), 'compiledPluralRules' );
4952  $fallbacks = self::getFallbacksFor( $this->mCode );
4953  if ( !$pluralRules ) {
4954  foreach ( $fallbacks as $fallbackCode ) {
4955  $pluralRules = self::$dataCache->getItem( strtolower( $fallbackCode ), 'compiledPluralRules' );
4956  if ( $pluralRules ) {
4957  break;
4958  }
4959  }
4960  }
4961  return $pluralRules;
4962  }
4963 
4969  public function getPluralRules() {
4970  $pluralRules = self::$dataCache->getItem( strtolower( $this->mCode ), 'pluralRules' );
4971  $fallbacks = self::getFallbacksFor( $this->mCode );
4972  if ( !$pluralRules ) {
4973  foreach ( $fallbacks as $fallbackCode ) {
4974  $pluralRules = self::$dataCache->getItem( strtolower( $fallbackCode ), 'pluralRules' );
4975  if ( $pluralRules ) {
4976  break;
4977  }
4978  }
4979  }
4980  return $pluralRules;
4981  }
4982 
4988  public function getPluralRuleTypes() {
4989  $pluralRuleTypes = self::$dataCache->getItem( strtolower( $this->mCode ), 'pluralRuleTypes' );
4990  $fallbacks = self::getFallbacksFor( $this->mCode );
4991  if ( !$pluralRuleTypes ) {
4992  foreach ( $fallbacks as $fallbackCode ) {
4993  $pluralRuleTypes = self::$dataCache->getItem( strtolower( $fallbackCode ), 'pluralRuleTypes' );
4994  if ( $pluralRuleTypes ) {
4995  break;
4996  }
4997  }
4998  }
4999  return $pluralRuleTypes;
5000  }
5001 
5007  public function getPluralRuleIndexNumber( $number ) {
5008  $pluralRules = $this->getCompiledPluralRules();
5009  $form = Evaluator::evaluateCompiled( $number, $pluralRules );
5010  return $form;
5011  }
5012 
5021  public function getPluralRuleType( $number ) {
5022  $index = $this->getPluralRuleIndexNumber( $number );
5023  $pluralRuleTypes = $this->getPluralRuleTypes();
5024  return $pluralRuleTypes[$index] ?? 'other';
5025  }
5026 }
The wiki should then use memcached to cache various data To use multiple just add more items to the array To increase the weight of a make its entry a array("192.168.0.1:11211", 2))
static getSuggestedDurations(Language $lang=null, $includeOther=true)
Get an array of suggested block durations from MediaWiki:Ipboptions.
static newFromCode( $code, $fallback=false)
Create a language object for a given language code.
Definition: Language.php:240
getMessage( $key)
Definition: Language.php:2644
getPluralRuleType( $number)
Find the plural rule type appropriate for the given number For example, if the language is set to Ara...
Definition: Language.php:5021
getIranianCalendarMonthName( $key)
Definition: Language.php:1029
static $mMonthAbbrevMsgs
Definition: Language.php:115
$mParentLanguage
Definition: Language.php:63
getSpecialPageAliases()
Get special page names, as an associative array canonical name => array of valid names, including aliases.
Definition: Language.php:3269
getPluralRuleTypes()
Get the plural rule types for the language.
Definition: Language.php:4988
firstChar( $s)
Get the first character of a string.
Definition: Language.php:2981
getHumanTimestamp(MWTimestamp $time, MWTimestamp $relativeTo=null, User $user=null)
Get the timestamp in a human-friendly relative format, e.g., "3 days ago".
Definition: Language.php:2538
static getLocalisationCache()
Get the LocalisationCache instance.
Definition: Language.php:447
Wrapper around strtr() that holds replacements.
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
Definition: deferred.txt:11
ucwords( $str)
Definition: Language.php:2806
wfWarn( $msg, $callerOffset=1, $level=E_USER_NOTICE)
Send a warning either to the debug log or in a PHP error depending on $wgDevelopmentWarnings.
static hebrewNumeral( $num)
Hebrew Gematria number formatting up to 9999.
Definition: Language.php:2069
translateBlockExpiry( $str, User $user=null, $now=0)
Definition: Language.php:4108
null for the local wiki Added should default to null in handler for backwards compatibility add a value to it if you want to add a cookie that have to vary cache options can modify $query
Definition: hooks.txt:1585
ucwordbreaksCallbackAscii( $matches)
Definition: Language.php:2678
$data
Utility to generate mapping file used in mw.Title (phpCharToUpper.json)
static element( $element, $attribs=[], $contents='')
Identical to rawElement(), but HTML-escapes $contents (like Xml::element()).
Definition: Html.php:232
segmentByWord( $string)
Some languages such as Chinese require word segmentation, Specify such segmentation when overridden i...
Definition: Language.php:2916
normalize( $s)
Convert a UTF-8 string to normal form C.
Definition: Language.php:3072
handleExplicitPluralForms( $count, array $forms)
Handles explicit plural forms for Language::convertPlural()
Definition: Language.php:4040
static getTitleInvalidRegex()
Returns a simple regex that will match on characters and sequences invalid in titles.
static LocalisationCache $dataCache
Definition: Language.php:80
initEncoding()
Definition: Language.php:3036
getDurationIntervals( $seconds, array $chosenIntervals=[])
Takes a number of seconds and returns an array with a set of corresponding intervals.
Definition: Language.php:2383
getVariantname( $code, $usemsg=true)
short names for language variants used for language conversion links.
Definition: Language.php:762
setNamespaces(array $namespaces)
Arbitrarily set all of the namespace names at once.
Definition: Language.php:545
static HashBagOStuff null $languageNameCache
Cache for language names.
Definition: Language.php:184
removeBadCharLast( $string)
Remove bytes that represent an incomplete Unicode character at the end of string (e.g.
Definition: Language.php:3645
static romanNumeral( $num)
Roman number formatting up to 10000.
Definition: Language.php:2038
getWeekdayAbbreviation( $key)
Definition: Language.php:1021
getDirMark( $opposite=false)
A hidden direction mark (LRM or RLM), depending on the language direction.
Definition: Language.php:3175
getHtmlCode()
Get the code in BCP 47 format which we can use inside of html lang="" tags.
Definition: Language.php:4433
static isUtf8( $value)
Test whether a string is valid UTF-8.
Definition: StringUtils.php:41
truncate_skip(&$ret, $text, $search, $start, $len=null)
truncateHtml() helper function like strcspn() but adds the skipped chars to $ret
Definition: Language.php:3816
$IP
Definition: WebStart.php:41
equals(Language $lang)
Compare with an other language object.
Definition: Language.php:4407
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses & $ret
Definition: hooks.txt:1982
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
lcfirst( $str)
Definition: Language.php:2763
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Definition: router.php:42
static $IRANIAN_DAYS
Definition: Language.php:1613
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException' returning false will NOT prevent logging $e
Definition: hooks.txt:2159
offsetForUser(User $user)
Adjust the timestamp depending on the given user&#39;s preferences.
Definition: MWTimestamp.php:79
getHebrewCalendarMonthNameGen( $key)
Definition: Language.php:1045
alignEnd()
Return &#39;right&#39; or &#39;left&#39; as appropriate alignment for line-end for this language&#39;s text direction...
Definition: Language.php:3143
static insertSpace( $string, $pattern)
Definition: Language.php:2959
getFallbackLanguages()
Definition: Language.php:487
userTimeAndDate( $ts, User $user, array $options=[])
Get the formatted date and time for the given timestamp and formatted for the given user...
Definition: Language.php:2519
globals txt Globals are evil The original MediaWiki code relied on globals for processing context far too often MediaWiki development since then has been a story of slowly moving context out of global variables and into objects Storing processing context in object member variables allows those objects to be reused in a much more flexible way Consider the elegance of
database rows
Definition: globals.txt:10
getMonthNameGen( $key)
Definition: Language.php:986
$wgLangObjCacheSize
Language cache size, or really how many languages can we handle simultaneously without degrading to c...
if(!isset( $args[0])) $lang
$namespaceAliases
Definition: Language.php:70
hasVariants()
Check if this is a language with variants.
Definition: Language.php:4239
getOption( $oname, $defaultOverride=null, $ignoreHidden=false)
Get the user&#39;s current setting for a given option.
Definition: User.php:3160
getCompiledPluralRules()
Get the compiled plural rules for the language.
Definition: Language.php:4950
getLocalNsIndex( $text)
Get a namespace key by value, case insensitive.
Definition: Language.php:654
convertCategoryKey( $key)
Definition: Language.php:4271
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
getLocalURL( $query='', $query2=false)
Get a URL with no fragment or server name (relative URL) from a Title object.
Definition: Title.php:1979
truncateForDatabase( $string, $length, $ellipsis='...', $adjustLength=true)
Truncate a string to a specified length in bytes, appending an optional string (e.g.
Definition: Language.php:3542
getHebrewCalendarMonthName( $key)
Definition: Language.php:1037
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring which defines all default service and specifies how they depend on each other("wiring"). When a new service is added to MediaWiki core
This code would result in ircNotify being run twice when an article is and once for brion Hooks can return three possible true was required This is the default since MediaWiki *some string
Definition: hooks.txt:175
$value
static getCanonicalIndex( $name)
Returns the index for a given canonical name, or NULL The input must be converted to lower case first...
const ALL
Return all known languages in fetchLanguageName(s).
Definition: Language.php:47
$wgMetaNamespace
Name of the project namespace.
fallback8bitEncoding()
Definition: Language.php:2893
wfIsInfinity( $str)
Determine input string is represents as infinity.
unsegmentForDiff( $text)
and unsegment to show the result
Definition: Language.php:4161
truncate_endBracket(&$tag, $tagType, $lastCh, &$openTags)
truncateHtml() helper function (a) push or pop $tag from $openTags as needed (b) clear $tag value ...
Definition: Language.php:3839
A fake language variant converter.
linkTrail()
A regular expression to match legal word-trailing characters which should be merged onto a link of th...
Definition: Language.php:4359
getAllMessages()
Definition: Language.php:2651
static getCanonicalNamespaces()
Returns array of all defined namespaces with their canonical (English) names.
$wgNamespaceAliases
Namespace aliases.
wfLogWarning( $msg, $callerOffset=1, $level=E_USER_WARNING)
Send a warning as a PHP error and the debug log.
getPluralRuleIndexNumber( $number)
Find the index number of the plural rule appropriate for the given number.
Definition: Language.php:5007
getVariants()
Get the list of variants supported by this language see sample implementation in LanguageZh.php.
Definition: Language.php:4281
A helper class for throttling authentication attempts.
$dateFormatStrings
Definition: Language.php:65
static hebrewYearStart( $year)
This calculates the Hebrew year start, as days since 1 September.
Definition: Language.php:1877
getNamespaceAliases()
Definition: Language.php:663
getWeekdayName( $key)
Definition: Language.php:1013
minimumGroupingDigits()
Definition: Language.php:3459
static $mMonthMsgs
Definition: Language.php:105
convertForSearchResult( $termsArray)
Definition: Language.php:2969
see documentation in includes Linker php for Linker::makeImageLink & $time
Definition: hooks.txt:1799
$wgExtraGenderNamespaces
Same as above, but for namespaces with gender distinction.
static getFileName( $prefix, $code, $suffix='.php')
Get the name of a file for a certain language code.
Definition: Language.php:4478
convertTitle( $title)
Convert a Title object to a string in the preferred variant.
Definition: Language.php:4218
static $mLangObjCache
Definition: Language.php:82
static classFromCode( $code, $fallback=true)
Definition: Language.php:4462
internalUserTimeAndDate( $type, $ts, User $user, array $options)
Internal helper function for userDate(), userTime() and userTimeAndDate()
Definition: Language.php:2434
array $wgOverrideUcfirstCharacters
List of Unicode characters for which capitalization is overridden in Language::ucfirst.
$wgTranslateNumerals
For Hindi and Arabic use local numerals instead of Western style (0-9) numerals in interface...
getHijriCalendarMonthName( $key)
Definition: Language.php:1053
static $mWeekdayAbbrevMsgs
Definition: Language.php:101
getMonthAbbreviation( $key)
Definition: Language.php:994
lc( $str, $first=false)
Definition: Language.php:2782
ucwordbreaksCallbackMB( $matches)
Definition: Language.php:2686
$mMagicExtensions
Definition: Language.php:62
static $pdf
Definition: Language.php:191
static isValidBuiltInCode( $code)
Returns true if a language code is of a valid form for the purposes of internal customisation of Medi...
Definition: Language.php:412
usually copyright or history_copyright This message must be in HTML not wikitext & $link
Definition: hooks.txt:3050
The User object encapsulates all of the user-specific settings (user_id, name, rights, email address, options, last login time).
Definition: User.php:48
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not it can be in the form of< username >< more info > e g for bot passwords intended to be added to log contexts Fields it might only if the login was with a bot password it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output $out
Definition: hooks.txt:780
__construct()
Definition: Language.php:456
truncateHtml( $text, $length, $ellipsis='...')
Truncate a string of valid HTML to a specified length in bytes, appending an optional string (e...
Definition: Language.php:3697
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return true
Definition: hooks.txt:1982
ucfirst( $str)
Make a string&#39;s first character uppercase.
Definition: Language.php:2705
$wgLanguageCode
Site language code.
semicolonList(array $list)
Take a list of strings and build a locale-friendly semicolon-separated list, using the local semicolo...
Definition: Language.php:3508
userAdjust( $ts, $tz=false)
Used by date() and time() to adjust the time output.
Definition: Language.php:2156
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
static getMessageFor( $key, $code)
Get a message for a given language.
Definition: Language.php:4609
static $lre
Unicode directional formatting characters, for embedBidi()
Definition: Language.php:189
formatNum( $number, $nocommafy=false)
Normally we output all numbers in plain en_US style, that is 293,291.235 for twohundredninetythreetho...
Definition: Language.php:3312
static getFallbackFor( $code)
Get the first fallback for a given language.
Definition: Language.php:4520
timeanddate( $ts, $adj=false, $format=true, $timecorrection=false)
Definition: Language.php:2338
formatExpiry( $expiry, $format=true, $infinity='infinity')
Decode an expiry (block, protection, etc) which has come from the DB.
Definition: Language.php:4664
listToText(array $list)
Take a list of strings and build a locale-friendly comma-separated list, using the local comma-separa...
Definition: Language.php:3471
static tsToYear( $ts, $cName)
Algorithm to convert Gregorian dates to Thai solar dates, Minguo dates or Minguo dates.
Definition: Language.php:1915
const NS_PROJECT
Definition: Defines.php:68
static $mHijriCalendarMonthMsgs
Definition: Language.php:143
linkPrefixCharset()
A regular expression character set to match legal word-prefixing characters which should be merged on...
Definition: Language.php:4369
either a unescaped string or a HtmlArmor object after in associative array form externallinks including delete and has completed for all link tables whether this was an auto creation use $formDescriptor instead default is conds Array Extra conditions for the No matching items in log is displayed if loglist is empty msgKey Array If you want a nice box with a set this to the key of the message First element is the message additional optional elements are parameters for the key that are processed with wfMessage() -> params() ->parseAsBlock() - offset Set to overwrite offset parameter in $wgRequest set to '' to unset offset - wrap String Wrap the message in html(usually something like "&lt
static getMain()
Get the RequestContext object associated with the main request.
getPreferredVariant()
Definition: Language.php:4288
LanguageConverter $mConverter
Definition: Language.php:59
recodeForEdit( $s)
Definition: Language.php:3046
static getMessagesFor( $code)
Get all messages for a given language WARNING: this may take a long time.
Definition: Language.php:4597
$minimumGroupingDigits
removeBadCharFirst( $string)
Remove bytes that represent an incomplete Unicode character at the start of string (e...
Definition: Language.php:3671
getDirMarkEntity( $opposite=false)
A hidden direction mark (LRM or RLM), depending on the language direction.
Definition: Language.php:3158
__destruct()
Reduce memory usage.
Definition: Language.php:470
static strongDirFromContent( $text='')
Gets directionality of the first strongly directional codepoint, for embedBidi()
Definition: Language.php:2021
getMagic( $mw)
Fill a MagicWord object with data from here.
Definition: Language.php:3237
static getMessageKeysFor( $code)
Get all message keys for a given language.
Definition: Language.php:4621
$wgLocalisationCacheConf
Localisation cache configuration.
static getFallbacksIncludingSiteLanguage( $code)
Get the ordered list of fallback languages, ending with the fallback language chain for the site lang...
Definition: Language.php:4566
getDefaultVariant()
Definition: Language.php:4295
getNsText( $index)
Get a namespace value by key.
Definition: Language.php:584
const STRICT_FALLBACKS
Return a strict fallback chain in getFallbacksFor.
Definition: Language.php:94
viewPrevNext(Title $title, $offset, $limit, array $query=[], $atend=false)
Generate (prev x| next x) (20|50|100...) type links for paging.
Definition: Language.php:4879
getConverter()
Return the LanguageConverter used in the Language.
Definition: Language.php:4171
static $mIranianCalendarMonthMsgs
Definition: Language.php:120
const NS_PROJECT_TALK
Definition: Defines.php:69
autoConvertToAllVariants( $text)
convert text to all supported variants
Definition: Language.php:4193
getBookstoreList()
Exports $wgBookstoreListEn.
Definition: Language.php:495
capitalizeAllNouns()
Definition: Language.php:3187
static decode( $value, $assoc=false)
Decodes a JSON string.
Definition: FormatJson.php:174
$wgDummyLanguageCodes
Functionally the same as $wgExtraLanguageCodes, but deprecated.
emphasize( $text)
Italic is unsuitable for some languages.
Definition: Language.php:3286
date( $ts, $adj=false, $format=true, $timecorrection=false)
Definition: Language.php:2299
isMultibyte( $str)
Definition: Language.php:2798
segmentForDiff( $text)
languages like Chinese need to be segmented in order for the diff to be of any use ...
Definition: Language.php:4151
$wgAllUnicodeFixes
Set this to always convert certain Unicode sequences to modern ones regardless of the content languag...
$cache
Definition: mcc.php:33
normalizeForSearch( $string)
Some languages have special punctuation need to be normalized.
Definition: Language.php:2927
static $strongDirRegex
Directionality test regex for embedBidi().
Definition: Language.php:206
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped & $options
Definition: hooks.txt:1982
static tsToHijri( $ts)
Converting Gregorian dates to Hijri dates.
Definition: Language.php:1687
needsGenderDistinction()
Whether this language uses gender-dependent namespace aliases.
Definition: Language.php:630
getURLVariant()
Definition: Language.php:4302
caseFold( $s)
Return a case-folded representation of $s.
Definition: Language.php:2870
updateConversionTable(Title $title)
Refresh the cache of conversion tables when MediaWiki:Conversiontable* is updated.
Definition: Language.php:4349
mbUpperChar( $char)
Convert character to uppercase, allowing overrides of the default mb_upper behaviour, which is buggy in many ways.
Definition: Language.php:2750
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not null
Definition: hooks.txt:780
static $rle
Definition: Language.php:190
$wgMetaNamespaceTalk
Name of the project talk namespace.
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:925
static tsToIranian( $ts)
Algorithm by Roozbeh Pournader and Mohammad Toossi to convert Gregorian dates to Iranian dates...
Definition: Language.php:1627
getNamespaceIds()
Definition: Language.php:715
static factory( $code)
Get a cached or new language object for a given language code.
Definition: Language.php:215
dateFormat( $usePrefs=true)
This is meant to be used by time(), date(), and timeanddate() to get the date preference they&#39;re supp...
Definition: Language.php:2234
static $mHebrewCalendarMonthMsgs
Definition: Language.php:127
pipeList(array $list)
Same as commaList, but separate it with the pipe instead.
Definition: Language.php:3520
getGenderNsText( $index, $gender)
Returns gender-dependent namespace alias if available.
Definition: Language.php:615
getFormattedNsText( $index)
A convenience function that returns the same thing as getNsText() except with &#39;_&#39; changed to &#39; &#39;...
Definition: Language.php:602
$mExtendedSpecialPageAliases
Definition: Language.php:66
namespace and then decline to actually register it & $namespaces
Definition: hooks.txt:925
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not it can be in the form of< username >< more info > e g for bot passwords intended to be added to log contexts Fields it might only if the login was with a bot password it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output modifiable & $code
Definition: hooks.txt:780
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
Definition: distributors.txt:9
getPluralRules()
Get the plural rules for the language.
Definition: Language.php:4969
getDefaultDateFormat()
Definition: Language.php:793
embedBidi( $text='')
Wraps argument with unicode control characters for directionality safety.
Definition: Language.php:4081
resetNamespaces()
Resets all of the namespace caches.
Definition: Language.php:553
recodeInput( $s)
Definition: Language.php:3056
getDateFormatString( $type, $pref)
Get a format string for a given type and preference.
Definition: Language.php:2265
static MapCacheLRU null $grammarTransformations
Cache for grammar rules data.
Definition: Language.php:178
time( $ts, $adj=false, $format=true, $timecorrection=false)
Definition: Language.php:2318
getMagicWords()
Get all magic words from cache.
Definition: Language.php:3228
getGrammarTransformations()
Get the grammar transformations data for the language.
Definition: Language.php:3932
getMonthNamesArray()
Definition: Language.php:974
numLink(Title $title, $offset, $limit, array $query, $link, $tooltipMsg, $class)
Helper function for viewPrevNext() that generates links.
Definition: Language.php:4925
static getMessagesFileName( $code)
Definition: Language.php:4490
static getDefaultOption( $opt)
Get a given default option value.
Definition: User.php:1809
convertGrammar( $word, $case)
Grammatical transformations, needed for inflected languages Invoked by putting {{grammar:case|word}} ...
Definition: Language.php:3861
digitTransformTable()
Definition: Language.php:3445
getConvRuleTitle()
Get the conversion rule title, if any.
Definition: Language.php:4941
formatTimePeriod( $seconds, $format=[])
Formats a time given in seconds into a string representation of that time.
Definition: Language.php:4694
userDate( $ts, User $user, array $options=[])
Get the formatted date for the given timestamp and formatted for the given user.
Definition: Language.php:2473
getNsIndex( $text)
Get a namespace key by value, case insensitive.
Definition: Language.php:745
addMagicWordsByLang( $newWords)
Add magic words to the extension array.
Definition: Language.php:3254
autoConvert( $text, $variant=false)
convert text to a variant
Definition: Language.php:4183
static fetchLanguageName( $code, $inLanguage=self::AS_AUTONYMS, $include=self::ALL)
Definition: Language.php:933
static getJsonMessagesFileName( $code)
Definition: Language.php:4503
static isWellFormedLanguageTag( $code, $lenient=false)
Returns true if a language code string is a well-formed language tag according to RFC 5646...
Definition: Language.php:333
ucwordbreaks( $str)
capitalize words at word breaks
Definition: Language.php:2830
$wgExtraNamespaces
Additional namespaces.
static convertDoubleWidth( $string)
convert double-width roman characters to single-width.
Definition: Language.php:2939
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:35
getHumanTimestampInternal(MWTimestamp $ts, MWTimestamp $relativeTo, User $user)
Convert an MWTimestamp into a pretty human-readable timestamp using the given user preferences and re...
Definition: Language.php:2575
getCode()
Get the internal language code for this language object.
Definition: Language.php:4419
digitGroupingPattern()
Definition: Language.php:3438
specialList( $page, $details, $oppositedm=true)
Make a list item, used by various special pages.
Definition: Language.php:4855
static isKnownLanguageTag( $tag)
Returns true if a language code is an IETF tag known to MediaWiki.
Definition: Language.php:426
Using a hook running we can avoid having all this option specific stuff in our mainline code Using the function We ve cleaned up the code here by removing clumps of infrequently used code and moving them off somewhere else It s much easier for someone working with this code to see what s _really_ going and make changes or fix bugs In we can take all the code that deals with the little used title reversing options(say) and put it in one place. Instead of having little title-reversing if-blocks spread all over the codebase in showAnArticle
$fallback
Definition: MessagesAb.php:11
$digitGroupingPattern
Definition: MessagesAs.php:167
static isSupportedLanguage( $code)
Checks whether any localisation is available for that language tag in MediaWiki (MessagesXx.php exists).
Definition: Language.php:305
convertNamespace( $ns, $variant=null)
Convert a namespace index to a string in the preferred variant.
Definition: Language.php:4230
getArrow( $direction='forwards')
An arrow, depending on the language direction.
Definition: Language.php:3198
formatSize( $size)
Format a size in bytes for output, using an appropriate unit (B, KB, MB, GB, TB, PB, EB, ZB or YB) according to the magnitude in question.
Definition: Language.php:4842
convertHtml( $text)
Perform output conversion on a string, and encode for safe HTML output.
Definition: Language.php:4263
you have access to all of the normal MediaWiki so you can get a DB use the etc For full docs on the Maintenance class
Definition: maintenance.txt:52
array null $namespaceNames
Definition: Language.php:69
commafy( $number)
Adds commas to a given number.
Definition: Language.php:3373
$transformData
ReplacementArray object caches.
Definition: Language.php:75
static getCodeFromFileName( $filename, $prefix='Language', $suffix='.php')
Get the language code from a file name.
Definition: Language.php:4447
getExtraHashOptions()
returns language specific options used by User::getPageRenderHash() for example, the preferred langua...
Definition: Language.php:4328
static getFallbacksFor( $code, $mode=self::MESSAGES_FALLBACKS)
Get the ordered list of fallback languages.
Definition: Language.php:4538
getParsedTitle()
For languages that support multiple variants, the title of an article may be displayed differently in...
Definition: Language.php:4339
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
static tsToHebrew( $ts)
Converting Gregorian dates to Hebrew dates.
Definition: Language.php:1739
preConvertPlural($forms, $count)
Checks that convertPlural was given an array and pads it to requested amount of forms by copying the ...
Definition: Language.php:4061
findVariantLink(&$link, &$nt, $ignoreOtherCond=false)
If a language supports multiple variants, it is possible that non-existing link in one variant actual...
Definition: Language.php:4318
$wgGrammarForms
Some languages need different word forms, usually for different cases.
const SUPPORTED
Return in fetchLanguageName(s) only the languages for which we have at least some localisation...
Definition: Language.php:54
hasVariant( $variant)
Strict check if the language has the specific variant.
Definition: Language.php:4253
formatBitrate( $bps)
Format a bitrate for output, using an appropriate unit (bps, kbps, Mbps, Gbps, Tbps, Pbps, Ebps, Zbps or Ybps) according to the magnitude in question.
Definition: Language.php:4794
const AS_AUTONYMS
Return autonyms in fetchLanguageName(s).
Definition: Language.php:41
transformUsingPairFile( $file, $string)
Transform a string using serialized data stored in the given file (which must be in the serialized su...
Definition: Language.php:3097
separatorTransformTable()
Definition: Language.php:3452
isRTL()
For right-to-left language support.
Definition: Language.php:3111
getDatePreference()
Get the user&#39;s preferred date format.
Definition: User.php:3483
sprintfDate( $format, $ts, DateTimeZone $zone=null, &$ttl='unused')
This is a workalike of PHP&#39;s date() function, but with better internationalisation, a reduced set of format characters, and a better escaping format.
Definition: Language.php:1143
getParentLanguage()
Get the "parent" language which has a converter to convert a "compatible" language (in another varian...
Definition: Language.php:4380
static clearCaches()
Intended for tests that may change configuration in a way that invalidates caches.
Definition: Language.php:284
getDir()
Return the correct HTML &#39;dir&#39; attribute value for this language.
Definition: Language.php:3119
getFormattedNamespaces()
A convenience function that returns getNamespaces() with spaces instead of underscores in values...
Definition: Language.php:565
$wgLocalTZoffset
Set an offset from UTC in minutes to use for the default timezone setting for anonymous users and new...
iconv( $in, $out, $string)
Definition: Language.php:2661
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:271
static $mMonthGenMsgs
Definition: Language.php:110
static $mWeekdayMsgs
Definition: Language.php:96
linkPrefixExtension()
To allow "foo[[bar]]" to extend the link over the whole word "foobar".
Definition: Language.php:3220
msg( $msg)
Get message object in this language.
Definition: Language.php:959
const MESSAGES_FALLBACKS
Return a fallback chain for messages in getFallbacksFor.
Definition: Language.php:88
const DB_REPLICA
Definition: defines.php:25
$mNamespaceIds
Definition: Language.php:70
uc( $str, $first=false)
Convert a string to uppercase.
Definition: Language.php:2725
getGrammarForms()
Get the grammar forms for the content language.
Definition: Language.php:3912
fixVariableInNamespace( $talk)
Definition: Language.php:4629
static dateTimeObjFormat(&$dateTimeObj, $ts, $zone, $code)
Pass through result from $dateTimeObj->format()
Definition: Language.php:1065
=Architecture==Two class hierarchies are used to provide the functionality associated with the different content models:*Content interface(and AbstractContent base class) define functionality that acts on the concrete content of a page, and *ContentHandler base class provides functionality specific to a content model, but not acting on concrete content. The most important function of ContentHandler is to act as a factory for the appropriate implementation of Content. These Content objects are to be used by MediaWiki everywhere, instead of passing page content around as text. All manipulation and analysis of page content must be done via the appropriate methods of the Content object. For each content model, a subclass of ContentHandler has to be registered with $wgContentHandlers. The ContentHandler object for a given content model can be obtained using ContentHandler::getForModelID($id). Also Title, WikiPage and Revision now have getContentHandler() methods for convenience. ContentHandler objects are singletons that provide functionality specific to the content type, but not directly acting on the content of some page. ContentHandler::makeEmptyContent() and ContentHandler::unserializeContent() can be used to create a Content object of the appropriate type. However, it is recommended to instead use WikiPage::getContent() resp. Revision::getContent() to get a page 's content as a Content object. These two methods should be the ONLY way in which page content is accessed. Another important function of ContentHandler objects is to define custom action handlers for a content model, see ContentHandler::getActionOverrides(). This is similar to what WikiPage::getActionOverrides() was already doing.==Serialization==With the ContentHandler facility, page content no longer has to be text based. Objects implementing the Content interface are used to represent and handle the content internally. For storage and data exchange, each content model supports at least one serialization format via ContentHandler::serializeContent($content). The list of supported formats for a given content model can be accessed using ContentHandler::getSupportedFormats(). Content serialization formats are identified using MIME type like strings. The following formats are built in:*text/x-wiki - wikitext *text/javascript - for js pages *text/css - for css pages *text/plain - for future use, e.g. with plain text messages. *text/html - for future use, e.g. with plain html messages. *application/vnd.php.serialized - for future use with the api and for extensions *application/json - for future use with the api, and for use by extensions *application/xml - for future use with the api, and for use by extensions In PHP, use the corresponding CONTENT_FORMAT_XXX constant. Note that when using the API to access page content, especially action=edit, action=parse and action=query &prop=revisions, the model and format of the content should always be handled explicitly. Without that information, interpretation of the provided content is not reliable. The same applies to XML dumps generated via maintenance/dumpBackup.php or Special:Export. Also note that the API will provide encapsulated, serialized content - so if the API was called with format=json, and contentformat is also json(or rather, application/json), the page content is represented as a string containing an escaped json structure. Extensions that use JSON to serialize some types of page content may provide specialized API modules that allow access to that content in a more natural form.==Compatibility==The ContentHandler facility is introduced in a way that should allow all existing code to keep functioning at least for pages that contain wikitext or other text based content. However, a number of functions and hooks have been deprecated in favor of new versions that are aware of the page 's content model, and will now generate warnings when used. Most importantly, the following functions have been deprecated:*Revisions::getText() is deprecated in favor Revisions::getContent() *WikiPage::getText() is deprecated in favor WikiPage::getContent() Also, the old Article::getContent()(which returns text) is superceded by Article::getContentObject(). However, both methods should be avoided since they do not provide clean access to the page 's actual content. For instance, they may return a system message for non-existing pages. Use WikiPage::getContent() instead. Code that relies on a textual representation of the page content should eventually be rewritten. However, ContentHandler::getContentText() provides a stop-gap that can be used to get text for a page. Its behavior is controlled by $wgContentHandlerTextFallback it
formatComputingNumbers( $size, $boundary, $messageKey)
Definition: Language.php:4804
initContLang()
Hook which will be called if this is the content language.
Definition: Language.php:480
userTime( $ts, User $user, array $options=[])
Get the formatted time for the given timestamp and formatted for the given user.
Definition: Language.php:2496
ucwordsCallbackMB( $matches)
Definition: Language.php:2694
getNamespaces()
Returns an array of localised namespaces indexed by their numbers.
Definition: Language.php:505
static isValidCode( $code)
Returns true if a language code string is of a valid form, whether or not it exists.
Definition: Language.php:387
commaList(array $list)
Take a list of strings and build a locale-friendly comma-separated list, using the local comma-separa...
Definition: Language.php:3495
static $mHebrewCalendarMonthGenMsgs
Definition: Language.php:135
const NS_USER_TALK
Definition: Defines.php:67
static array $languagesWithVariants
languages supporting variants
truncateForVisual( $string, $length, $ellipsis='...', $adjustLength=true)
Truncate a string to a specified number of characters, appending an optional string (e...
Definition: Language.php:3566
gender( $gender, $forms)
Provides an alternative text depending on specified gender.
Definition: Language.php:3981
getMessageFromDB( $msg)
Get a message from the MediaWiki namespace.
Definition: Language.php:949
getMonthAbbreviationsArray()
Definition: Language.php:1001
hasWordBreaks()
Most writing systems use whitespace to break up words.
Definition: Language.php:2905
convert( $text)
convert text to different variants of a language.
Definition: Language.php:4208
alignStart()
Return &#39;left&#39; or &#39;right&#39; as appropriate alignment for line-start for this language&#39;s text direction...
Definition: Language.php:3131
checkTitleEncoding( $s)
TODO: $s is not always a string per T218883.
Definition: Language.php:2879
static bcp47( $code)
Get the normalised IETF language tag See unit test for examples.
convertPlural( $count, $forms)
Plural form transformations, needed for some languages.
Definition: Language.php:4010
return true to allow those checks to and false if checking is done & $user
Definition: hooks.txt:1473
formatNumNoSeparators( $number)
Front-end for non-commafied formatNum.
Definition: Language.php:3340
parseFormattedNumber( $number)
Definition: Language.php:3348
truncateInternal( $string, $length, $ellipsis, $adjustLength, callable $measureLength, callable $getSubstring)
Internal method used for truncation.
Definition: Language.php:3592
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:200
replaceGrammarInNamespace( $m)
Definition: Language.php:4650
static $GREG_DAYS
Definition: Language.php:1612
getMonthName( $key)
Definition: Language.php:967
formatDuration( $seconds, array $chosenIntervals=[])
Takes a number of seconds and turns it into a text using values such as hours and minutes...
Definition: Language.php:2357
$matches