MediaWiki  master
Language.php
Go to the documentation of this file.
1 <?php
30 
35 class Language {
40  const AS_AUTONYMS = null;
41 
46  const ALL = 'all';
47 
53  const SUPPORTED = 'mwfile';
54 
58  public $mConverter;
59 
60  public $mVariants, $mCode, $mLoaded = false;
61  public $mMagicExtensions = [];
62  private $mHtmlCode = null, $mParentLanguage = false;
63 
64  public $dateFormatStrings = [];
66 
68  protected $namespaceNames;
70 
74  public $transformData = [];
75 
79  static public $dataCache;
80 
81  static public $mLangObjCache = [];
82 
87  const MESSAGES_FALLBACKS = 0;
88 
93  const STRICT_FALLBACKS = 1;
94 
95  static public $mWeekdayMsgs = [
96  'sunday', 'monday', 'tuesday', 'wednesday', 'thursday',
97  'friday', 'saturday'
98  ];
99 
100  static public $mWeekdayAbbrevMsgs = [
101  'sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'
102  ];
103 
104  static public $mMonthMsgs = [
105  'january', 'february', 'march', 'april', 'may_long', 'june',
106  'july', 'august', 'september', 'october', 'november',
107  'december'
108  ];
109  static public $mMonthGenMsgs = [
110  'january-gen', 'february-gen', 'march-gen', 'april-gen', 'may-gen', 'june-gen',
111  'july-gen', 'august-gen', 'september-gen', 'october-gen', 'november-gen',
112  'december-gen'
113  ];
114  static public $mMonthAbbrevMsgs = [
115  'jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug',
116  'sep', 'oct', 'nov', 'dec'
117  ];
118 
119  static public $mIranianCalendarMonthMsgs = [
120  'iranian-calendar-m1', 'iranian-calendar-m2', 'iranian-calendar-m3',
121  'iranian-calendar-m4', 'iranian-calendar-m5', 'iranian-calendar-m6',
122  'iranian-calendar-m7', 'iranian-calendar-m8', 'iranian-calendar-m9',
123  'iranian-calendar-m10', 'iranian-calendar-m11', 'iranian-calendar-m12'
124  ];
125 
126  static public $mHebrewCalendarMonthMsgs = [
127  'hebrew-calendar-m1', 'hebrew-calendar-m2', 'hebrew-calendar-m3',
128  'hebrew-calendar-m4', 'hebrew-calendar-m5', 'hebrew-calendar-m6',
129  'hebrew-calendar-m7', 'hebrew-calendar-m8', 'hebrew-calendar-m9',
130  'hebrew-calendar-m10', 'hebrew-calendar-m11', 'hebrew-calendar-m12',
131  'hebrew-calendar-m6a', 'hebrew-calendar-m6b'
132  ];
133 
135  'hebrew-calendar-m1-gen', 'hebrew-calendar-m2-gen', 'hebrew-calendar-m3-gen',
136  'hebrew-calendar-m4-gen', 'hebrew-calendar-m5-gen', 'hebrew-calendar-m6-gen',
137  'hebrew-calendar-m7-gen', 'hebrew-calendar-m8-gen', 'hebrew-calendar-m9-gen',
138  'hebrew-calendar-m10-gen', 'hebrew-calendar-m11-gen', 'hebrew-calendar-m12-gen',
139  'hebrew-calendar-m6a-gen', 'hebrew-calendar-m6b-gen'
140  ];
141 
142  static public $mHijriCalendarMonthMsgs = [
143  'hijri-calendar-m1', 'hijri-calendar-m2', 'hijri-calendar-m3',
144  'hijri-calendar-m4', 'hijri-calendar-m5', 'hijri-calendar-m6',
145  'hijri-calendar-m7', 'hijri-calendar-m8', 'hijri-calendar-m9',
146  'hijri-calendar-m10', 'hijri-calendar-m11', 'hijri-calendar-m12'
147  ];
148 
153  static public $durationIntervals = [
154  'millennia' => 31556952000,
155  'centuries' => 3155695200,
156  'decades' => 315569520,
157  'years' => 31556952, // 86400 * ( 365 + ( 24 * 3 + 25 ) / 400 )
158  'weeks' => 604800,
159  'days' => 86400,
160  'hours' => 3600,
161  'minutes' => 60,
162  'seconds' => 1,
163  ];
164 
171  static private $fallbackLanguageCache = [];
172 
177  static private $grammarTransformations;
178 
183  static private $languageNameCache;
184 
188  static private $lre = "\u{202A}"; // U+202A LEFT-TO-RIGHT EMBEDDING
189  static private $rle = "\u{202B}"; // U+202B RIGHT-TO-LEFT EMBEDDING
190  static private $pdf = "\u{202C}"; // U+202C POP DIRECTIONAL FORMATTING
191 
203  // @codeCoverageIgnoreStart
204  // phpcs:ignore Generic.Files.LineLength
205  static private $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';
206  // @codeCoverageIgnoreEnd
207 
214  static function factory( $code ) {
216 
217  if ( isset( $wgDummyLanguageCodes[$code] ) ) {
218  $code = $wgDummyLanguageCodes[$code];
219  }
220 
221  // get the language object to process
222  $langObj = self::$mLangObjCache[$code] ?? self::newFromCode( $code );
223 
224  // merge the language object in to get it up front in the cache
225  self::$mLangObjCache = array_merge( [ $code => $langObj ], self::$mLangObjCache );
226  // get rid of the oldest ones in case we have an overflow
227  self::$mLangObjCache = array_slice( self::$mLangObjCache, 0, $wgLangObjCacheSize, true );
228 
229  return $langObj;
230  }
231 
239  protected static function newFromCode( $code, $fallback = false ) {
240  if ( !self::isValidCode( $code ) ) {
241  throw new MWException( "Invalid language code \"$code\"" );
242  }
243 
244  if ( !self::isValidBuiltInCode( $code ) ) {
245  // It's not possible to customise this code with class files, so
246  // just return a Language object. This is to support uselang= hacks.
247  $lang = new Language;
248  $lang->setCode( $code );
249  return $lang;
250  }
251 
252  // Check if there is a language class for the code
253  $class = self::classFromCode( $code, $fallback );
254  // LanguageCode does not inherit Language
255  if ( class_exists( $class ) && is_a( $class, 'Language', true ) ) {
256  $lang = new $class;
257  return $lang;
258  }
259 
260  // Keep trying the fallback list until we find an existing class
261  $fallbacks = self::getFallbacksFor( $code );
262  foreach ( $fallbacks as $fallbackCode ) {
263  if ( !self::isValidBuiltInCode( $fallbackCode ) ) {
264  throw new MWException( "Invalid fallback '$fallbackCode' in fallback sequence for '$code'" );
265  }
266 
267  $class = self::classFromCode( $fallbackCode );
268  if ( class_exists( $class ) ) {
269  $lang = new $class;
270  $lang->setCode( $code );
271  return $lang;
272  }
273  }
274 
275  throw new MWException( "Invalid fallback sequence for language '$code'" );
276  }
277 
283  public static function clearCaches() {
284  if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
285  throw new MWException( __METHOD__ . ' must not be used outside tests' );
286  }
287  self::$dataCache = null;
288  // Reinitialize $dataCache, since it's expected to always be available
289  self::getLocalisationCache();
290  self::$mLangObjCache = [];
291  self::$fallbackLanguageCache = [];
292  self::$grammarTransformations = null;
293  self::$languageNameCache = null;
294  }
295 
304  public static function isSupportedLanguage( $code ) {
305  if ( !self::isValidBuiltInCode( $code ) ) {
306  return false;
307  }
308 
309  if ( $code === 'qqq' ) {
310  return false;
311  }
312 
313  return is_readable( self::getMessagesFileName( $code ) ) ||
314  is_readable( self::getJsonMessagesFileName( $code ) );
315  }
316 
332  public static function isWellFormedLanguageTag( $code, $lenient = false ) {
333  $alpha = '[a-z]';
334  $digit = '[0-9]';
335  $alphanum = '[a-z0-9]';
336  $x = 'x'; # private use singleton
337  $singleton = '[a-wy-z]'; # other singleton
338  $s = $lenient ? '[-_]' : '-';
339 
340  $language = "$alpha{2,8}|$alpha{2,3}$s$alpha{3}";
341  $script = "$alpha{4}"; # ISO 15924
342  $region = "(?:$alpha{2}|$digit{3})"; # ISO 3166-1 alpha-2 or UN M.49
343  $variant = "(?:$alphanum{5,8}|$digit$alphanum{3})";
344  $extension = "$singleton(?:$s$alphanum{2,8})+";
345  $privateUse = "$x(?:$s$alphanum{1,8})+";
346 
347  # Define certain grandfathered codes, since otherwise the regex is pretty useless.
348  # Since these are limited, this is safe even later changes to the registry --
349  # the only oddity is that it might change the type of the tag, and thus
350  # the results from the capturing groups.
351  # https://www.iana.org/assignments/language-subtag-registry
352 
353  $grandfathered = "en{$s}GB{$s}oed"
354  . "|i{$s}(?:ami|bnn|default|enochian|hak|klingon|lux|mingo|navajo|pwn|tao|tay|tsu)"
355  . "|no{$s}(?:bok|nyn)"
356  . "|sgn{$s}(?:BE{$s}(?:fr|nl)|CH{$s}de)"
357  . "|zh{$s}min{$s}nan";
358 
359  $variantList = "$variant(?:$s$variant)*";
360  $extensionList = "$extension(?:$s$extension)*";
361 
362  $langtag = "(?:($language)"
363  . "(?:$s$script)?"
364  . "(?:$s$region)?"
365  . "(?:$s$variantList)?"
366  . "(?:$s$extensionList)?"
367  . "(?:$s$privateUse)?)";
368 
369  # The final breakdown, with capturing groups for each of these components
370  # The variants, extensions, grandfathered, and private-use may have interior '-'
371 
372  $root = "^(?:$langtag|$privateUse|$grandfathered)$";
373 
374  return (bool)preg_match( "/$root/", strtolower( $code ) );
375  }
376 
386  public static function isValidCode( $code ) {
387  static $cache = [];
388  if ( !isset( $cache[$code] ) ) {
389  // People think language codes are html safe, so enforce it.
390  // Ideally we should only allow a-zA-Z0-9-
391  // but, .+ and other chars are often used for {{int:}} hacks
392  // see bugs T39564, T39587, T38938
393  $cache[$code] =
394  // Protect against path traversal
395  strcspn( $code, ":/\\\000&<>'\"" ) === strlen( $code )
396  && !preg_match( MediaWikiTitleCodec::getTitleInvalidRegex(), $code );
397  }
398  return $cache[$code];
399  }
400 
411  public static function isValidBuiltInCode( $code ) {
412  if ( !is_string( $code ) ) {
413  if ( is_object( $code ) ) {
414  $addmsg = " of class " . get_class( $code );
415  } else {
416  $addmsg = '';
417  }
418  $type = gettype( $code );
419  throw new MWException( __METHOD__ . " must be passed a string, $type given$addmsg" );
420  }
421 
422  return (bool)preg_match( '/^[a-z0-9-]{2,}$/', $code );
423  }
424 
433  public static function isKnownLanguageTag( $tag ) {
434  // Quick escape for invalid input to avoid exceptions down the line
435  // when code tries to process tags which are not valid at all.
436  if ( !self::isValidBuiltInCode( $tag ) ) {
437  return false;
438  }
439 
440  if ( isset( MediaWiki\Languages\Data\Names::$names[$tag] )
441  || self::fetchLanguageName( $tag, $tag ) !== ''
442  ) {
443  return true;
444  }
445 
446  return false;
447  }
448 
454  public static function getLocalisationCache() {
455  if ( is_null( self::$dataCache ) ) {
457  $class = $wgLocalisationCacheConf['class'];
458  self::$dataCache = new $class( $wgLocalisationCacheConf );
459  }
460  return self::$dataCache;
461  }
462 
463  function __construct() {
464  $this->mConverter = new FakeConverter( $this );
465  // Set the code to the name of the descendant
466  if ( static::class === 'Language' ) {
467  $this->mCode = 'en';
468  } else {
469  $this->mCode = str_replace( '_', '-', strtolower( substr( static::class, 8 ) ) );
470  }
471  self::getLocalisationCache();
472  }
473 
477  function __destruct() {
478  foreach ( $this as $name => $value ) {
479  unset( $this->$name );
480  }
481  }
482 
487  function initContLang() {
488  }
489 
494  public function getFallbackLanguages() {
495  return self::getFallbacksFor( $this->mCode );
496  }
497 
502  public function getBookstoreList() {
503  return self::$dataCache->getItem( $this->mCode, 'bookstoreList' );
504  }
505 
512  public function getNamespaces() {
513  if ( is_null( $this->namespaceNames ) ) {
515 
516  $validNamespaces = MWNamespace::getCanonicalNamespaces();
517 
518  $this->namespaceNames = $wgExtraNamespaces +
519  self::$dataCache->getItem( $this->mCode, 'namespaceNames' );
520  $this->namespaceNames += $validNamespaces;
521 
522  $this->namespaceNames[NS_PROJECT] = $wgMetaNamespace;
523  if ( $wgMetaNamespaceTalk ) {
524  $this->namespaceNames[NS_PROJECT_TALK] = $wgMetaNamespaceTalk;
525  } else {
526  $talk = $this->namespaceNames[NS_PROJECT_TALK];
527  $this->namespaceNames[NS_PROJECT_TALK] =
528  $this->fixVariableInNamespace( $talk );
529  }
530 
531  # Sometimes a language will be localised but not actually exist on this wiki.
532  foreach ( $this->namespaceNames as $key => $text ) {
533  if ( !isset( $validNamespaces[$key] ) ) {
534  unset( $this->namespaceNames[$key] );
535  }
536  }
537 
538  # The above mixing may leave namespaces out of canonical order.
539  # Re-order by namespace ID number...
540  ksort( $this->namespaceNames );
541 
542  Hooks::run( 'LanguageGetNamespaces', [ &$this->namespaceNames ] );
543  }
544 
545  return $this->namespaceNames;
546  }
547 
552  public function setNamespaces( array $namespaces ) {
553  $this->namespaceNames = $namespaces;
554  $this->mNamespaceIds = null;
555  }
556 
560  public function resetNamespaces() {
561  $this->namespaceNames = null;
562  $this->mNamespaceIds = null;
563  $this->namespaceAliases = null;
564  }
565 
572  public function getFormattedNamespaces() {
573  $ns = $this->getNamespaces();
574  foreach ( $ns as $k => $v ) {
575  $ns[$k] = strtr( $v, '_', ' ' );
576  }
577  return $ns;
578  }
579 
591  public function getNsText( $index ) {
592  $ns = $this->getNamespaces();
593  return $ns[$index] ?? false;
594  }
595 
609  public function getFormattedNsText( $index ) {
610  $ns = $this->getNsText( $index );
611  return strtr( $ns, '_', ' ' );
612  }
613 
622  public function getGenderNsText( $index, $gender ) {
624 
625  $ns = $wgExtraGenderNamespaces +
626  (array)self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' );
627 
628  return $ns[$index][$gender] ?? $this->getNsText( $index );
629  }
630 
637  public function needsGenderDistinction() {
639  if ( count( $wgExtraGenderNamespaces ) > 0 ) {
640  // $wgExtraGenderNamespaces overrides everything
641  return true;
642  } elseif ( isset( $wgExtraNamespaces[NS_USER] ) && isset( $wgExtraNamespaces[NS_USER_TALK] ) ) {
644  // $wgExtraNamespaces overrides any gender aliases specified in i18n files
645  return false;
646  } else {
647  // Check what is in i18n files
648  $aliases = self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' );
649  return count( $aliases ) > 0;
650  }
651  }
652 
661  function getLocalNsIndex( $text ) {
662  $lctext = $this->lc( $text );
663  $ids = $this->getNamespaceIds();
664  return $ids[$lctext] ?? false;
665  }
666 
670  public function getNamespaceAliases() {
671  if ( is_null( $this->namespaceAliases ) ) {
672  $aliases = self::$dataCache->getItem( $this->mCode, 'namespaceAliases' );
673  if ( !$aliases ) {
674  $aliases = [];
675  } else {
676  foreach ( $aliases as $name => $index ) {
677  if ( $index === NS_PROJECT_TALK ) {
678  unset( $aliases[$name] );
679  $name = $this->fixVariableInNamespace( $name );
680  $aliases[$name] = $index;
681  }
682  }
683  }
684 
686  $genders = $wgExtraGenderNamespaces +
687  (array)self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' );
688  foreach ( $genders as $index => $forms ) {
689  foreach ( $forms as $alias ) {
690  $aliases[$alias] = $index;
691  }
692  }
693 
694  # Also add converted namespace names as aliases, to avoid confusion.
695  $convertedNames = [];
696  foreach ( $this->getVariants() as $variant ) {
697  if ( $variant === $this->mCode ) {
698  continue;
699  }
700  foreach ( $this->getNamespaces() as $ns => $_ ) {
701  $convertedNames[$this->getConverter()->convertNamespace( $ns, $variant )] = $ns;
702  }
703  }
704 
705  $this->namespaceAliases = $aliases + $convertedNames;
706 
707  # Filter out aliases to namespaces that don't exist, e.g. from extensions
708  # that aren't loaded here but are included in the l10n cache.
709  # (array_intersect preserves keys from its first argument)
710  $this->namespaceAliases = array_intersect(
711  $this->namespaceAliases,
712  array_keys( $this->getNamespaces() )
713  );
714  }
715 
717  }
718 
722  public function getNamespaceIds() {
723  if ( is_null( $this->mNamespaceIds ) ) {
724  global $wgNamespaceAliases;
725  # Put namespace names and aliases into a hashtable.
726  # If this is too slow, then we should arrange it so that it is done
727  # before caching. The catch is that at pre-cache time, the above
728  # class-specific fixup hasn't been done.
729  $this->mNamespaceIds = [];
730  foreach ( $this->getNamespaces() as $index => $name ) {
731  $this->mNamespaceIds[$this->lc( $name )] = $index;
732  }
733  foreach ( $this->getNamespaceAliases() as $name => $index ) {
734  $this->mNamespaceIds[$this->lc( $name )] = $index;
735  }
736  if ( $wgNamespaceAliases ) {
737  foreach ( $wgNamespaceAliases as $name => $index ) {
738  $this->mNamespaceIds[$this->lc( $name )] = $index;
739  }
740  }
741  }
742  return $this->mNamespaceIds;
743  }
744 
752  public function getNsIndex( $text ) {
753  $lctext = $this->lc( $text );
754  $ns = MWNamespace::getCanonicalIndex( $lctext );
755  if ( $ns !== null ) {
756  return $ns;
757  }
758  $ids = $this->getNamespaceIds();
759  return $ids[$lctext] ?? false;
760  }
761 
769  public function getVariantname( $code, $usemsg = true ) {
770  $msg = "variantname-$code";
771  if ( $usemsg && wfMessage( $msg )->exists() ) {
772  return $this->getMessageFromDB( $msg );
773  }
774  $name = self::fetchLanguageName( $code );
775  if ( $name ) {
776  return $name; # if it's defined as a language name, show that
777  } else {
778  # otherwise, output the language code
779  return $code;
780  }
781  }
782 
786  public function getDatePreferences() {
787  return self::$dataCache->getItem( $this->mCode, 'datePreferences' );
788  }
789 
793  function getDateFormats() {
794  return self::$dataCache->getItem( $this->mCode, 'dateFormats' );
795  }
796 
800  public function getDefaultDateFormat() {
801  $df = self::$dataCache->getItem( $this->mCode, 'defaultDateFormat' );
802  if ( $df === 'dmy or mdy' ) {
803  global $wgAmericanDates;
804  return $wgAmericanDates ? 'mdy' : 'dmy';
805  } else {
806  return $df;
807  }
808  }
809 
813  public function getDatePreferenceMigrationMap() {
814  return self::$dataCache->getItem( $this->mCode, 'datePreferenceMigrationMap' );
815  }
816 
820  public function getExtraUserToggles() {
821  return (array)self::$dataCache->getItem( $this->mCode, 'extraUserToggles' );
822  }
823 
828  function getUserToggle( $tog ) {
829  return $this->getMessageFromDB( "tog-$tog" );
830  }
831 
843  public static function fetchLanguageNames( $inLanguage = self::AS_AUTONYMS, $include = 'mw' ) {
844  $cacheKey = $inLanguage === self::AS_AUTONYMS ? 'null' : $inLanguage;
845  $cacheKey .= ":$include";
846  if ( self::$languageNameCache === null ) {
847  self::$languageNameCache = new HashBagOStuff( [ 'maxKeys' => 20 ] );
848  }
849 
850  $ret = self::$languageNameCache->get( $cacheKey );
851  if ( !$ret ) {
852  $ret = self::fetchLanguageNamesUncached( $inLanguage, $include );
853  self::$languageNameCache->set( $cacheKey, $ret );
854  }
855  return $ret;
856  }
857 
868  private static function fetchLanguageNamesUncached(
869  $inLanguage = self::AS_AUTONYMS,
870  $include = 'mw'
871  ) {
872  global $wgExtraLanguageNames, $wgUsePigLatinVariant;
873 
874  // If passed an invalid language code to use, fallback to en
875  if ( $inLanguage !== self::AS_AUTONYMS && !self::isValidCode( $inLanguage ) ) {
876  $inLanguage = 'en';
877  }
878 
879  $names = [];
880 
881  if ( $inLanguage ) {
882  # TODO: also include when $inLanguage is null, when this code is more efficient
883  Hooks::run( 'LanguageGetTranslatedLanguageNames', [ &$names, $inLanguage ] );
884  }
885 
886  $mwNames = $wgExtraLanguageNames + MediaWiki\Languages\Data\Names::$names;
887  if ( $wgUsePigLatinVariant ) {
888  // Pig Latin (for variant development)
889  $mwNames['en-x-piglatin'] = 'Igpay Atinlay';
890  }
891 
892  foreach ( $mwNames as $mwCode => $mwName ) {
893  # - Prefer own MediaWiki native name when not using the hook
894  # - For other names just add if not added through the hook
895  if ( $mwCode === $inLanguage || !isset( $names[$mwCode] ) ) {
896  $names[$mwCode] = $mwName;
897  }
898  }
899 
900  if ( $include === self::ALL ) {
901  ksort( $names );
902  return $names;
903  }
904 
905  $returnMw = [];
906  $coreCodes = array_keys( $mwNames );
907  foreach ( $coreCodes as $coreCode ) {
908  $returnMw[$coreCode] = $names[$coreCode];
909  }
910 
911  if ( $include === self::SUPPORTED ) {
912  $namesMwFile = [];
913  # We do this using a foreach over the codes instead of a directory
914  # loop so that messages files in extensions will work correctly.
915  foreach ( $returnMw as $code => $value ) {
916  if ( is_readable( self::getMessagesFileName( $code ) )
917  || is_readable( self::getJsonMessagesFileName( $code ) )
918  ) {
919  $namesMwFile[$code] = $names[$code];
920  }
921  }
922 
923  ksort( $namesMwFile );
924  return $namesMwFile;
925  }
926 
927  ksort( $returnMw );
928  # 'mw' option; default if it's not one of the other two options (all/mwfile)
929  return $returnMw;
930  }
931 
940  public static function fetchLanguageName(
941  $code,
942  $inLanguage = self::AS_AUTONYMS,
943  $include = self::ALL
944  ) {
945  $code = strtolower( $code );
946  $array = self::fetchLanguageNames( $inLanguage, $include );
947  return !array_key_exists( $code, $array ) ? '' : $array[$code];
948  }
949 
956  public function getMessageFromDB( $msg ) {
957  return $this->msg( $msg )->text();
958  }
959 
966  protected function msg( $msg ) {
967  return wfMessage( $msg )->inLanguage( $this );
968  }
969 
974  public function getMonthName( $key ) {
975  return $this->getMessageFromDB( self::$mMonthMsgs[$key - 1] );
976  }
977 
981  public function getMonthNamesArray() {
982  $monthNames = [ '' ];
983  for ( $i = 1; $i < 13; $i++ ) {
984  $monthNames[] = $this->getMonthName( $i );
985  }
986  return $monthNames;
987  }
988 
993  public function getMonthNameGen( $key ) {
994  return $this->getMessageFromDB( self::$mMonthGenMsgs[$key - 1] );
995  }
996 
1001  public function getMonthAbbreviation( $key ) {
1002  return $this->getMessageFromDB( self::$mMonthAbbrevMsgs[$key - 1] );
1003  }
1004 
1008  public function getMonthAbbreviationsArray() {
1009  $monthNames = [ '' ];
1010  for ( $i = 1; $i < 13; $i++ ) {
1011  $monthNames[] = $this->getMonthAbbreviation( $i );
1012  }
1013  return $monthNames;
1014  }
1015 
1020  public function getWeekdayName( $key ) {
1021  return $this->getMessageFromDB( self::$mWeekdayMsgs[$key - 1] );
1022  }
1023 
1028  function getWeekdayAbbreviation( $key ) {
1029  return $this->getMessageFromDB( self::$mWeekdayAbbrevMsgs[$key - 1] );
1030  }
1031 
1036  function getIranianCalendarMonthName( $key ) {
1037  return $this->getMessageFromDB( self::$mIranianCalendarMonthMsgs[$key - 1] );
1038  }
1039 
1044  function getHebrewCalendarMonthName( $key ) {
1045  return $this->getMessageFromDB( self::$mHebrewCalendarMonthMsgs[$key - 1] );
1046  }
1047 
1053  return $this->getMessageFromDB( self::$mHebrewCalendarMonthGenMsgs[$key - 1] );
1054  }
1055 
1060  function getHijriCalendarMonthName( $key ) {
1061  return $this->getMessageFromDB( self::$mHijriCalendarMonthMsgs[$key - 1] );
1062  }
1063 
1072  private static function dateTimeObjFormat( &$dateTimeObj, $ts, $zone, $code ) {
1073  if ( !$dateTimeObj ) {
1074  $dateTimeObj = DateTime::createFromFormat(
1075  'YmdHis', $ts, $zone ?: new DateTimeZone( 'UTC' )
1076  );
1077  }
1078  return $dateTimeObj->format( $code );
1079  }
1080 
1150  public function sprintfDate( $format, $ts, DateTimeZone $zone = null, &$ttl = 'unused' ) {
1151  $s = '';
1152  $raw = false;
1153  $roman = false;
1154  $hebrewNum = false;
1155  $dateTimeObj = false;
1156  $rawToggle = false;
1157  $iranian = false;
1158  $hebrew = false;
1159  $hijri = false;
1160  $thai = false;
1161  $minguo = false;
1162  $tenno = false;
1163 
1164  $usedSecond = false;
1165  $usedMinute = false;
1166  $usedHour = false;
1167  $usedAMPM = false;
1168  $usedDay = false;
1169  $usedWeek = false;
1170  $usedMonth = false;
1171  $usedYear = false;
1172  $usedISOYear = false;
1173  $usedIsLeapYear = false;
1174 
1175  $usedHebrewMonth = false;
1176  $usedIranianMonth = false;
1177  $usedHijriMonth = false;
1178  $usedHebrewYear = false;
1179  $usedIranianYear = false;
1180  $usedHijriYear = false;
1181  $usedTennoYear = false;
1182 
1183  if ( strlen( $ts ) !== 14 ) {
1184  throw new MWException( __METHOD__ . ": The timestamp $ts should have 14 characters" );
1185  }
1186 
1187  if ( !ctype_digit( $ts ) ) {
1188  throw new MWException( __METHOD__ . ": The timestamp $ts should be a number" );
1189  }
1190 
1191  $formatLength = strlen( $format );
1192  for ( $p = 0; $p < $formatLength; $p++ ) {
1193  $num = false;
1194  $code = $format[$p];
1195  if ( $code == 'x' && $p < $formatLength - 1 ) {
1196  $code .= $format[++$p];
1197  }
1198 
1199  if ( ( $code === 'xi'
1200  || $code === 'xj'
1201  || $code === 'xk'
1202  || $code === 'xm'
1203  || $code === 'xo'
1204  || $code === 'xt' )
1205  && $p < $formatLength - 1 ) {
1206  $code .= $format[++$p];
1207  }
1208 
1209  switch ( $code ) {
1210  case 'xx':
1211  $s .= 'x';
1212  break;
1213  case 'xn':
1214  $raw = true;
1215  break;
1216  case 'xN':
1217  $rawToggle = !$rawToggle;
1218  break;
1219  case 'xr':
1220  $roman = true;
1221  break;
1222  case 'xh':
1223  $hebrewNum = true;
1224  break;
1225  case 'xg':
1226  $usedMonth = true;
1227  $s .= $this->getMonthNameGen( substr( $ts, 4, 2 ) );
1228  break;
1229  case 'xjx':
1230  $usedHebrewMonth = true;
1231  if ( !$hebrew ) {
1232  $hebrew = self::tsToHebrew( $ts );
1233  }
1234  $s .= $this->getHebrewCalendarMonthNameGen( $hebrew[1] );
1235  break;
1236  case 'd':
1237  $usedDay = true;
1238  $num = substr( $ts, 6, 2 );
1239  break;
1240  case 'D':
1241  $usedDay = true;
1242  $s .= $this->getWeekdayAbbreviation(
1243  self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'w' ) + 1
1244  );
1245  break;
1246  case 'j':
1247  $usedDay = true;
1248  $num = intval( substr( $ts, 6, 2 ) );
1249  break;
1250  case 'xij':
1251  $usedDay = true;
1252  if ( !$iranian ) {
1253  $iranian = self::tsToIranian( $ts );
1254  }
1255  $num = $iranian[2];
1256  break;
1257  case 'xmj':
1258  $usedDay = true;
1259  if ( !$hijri ) {
1260  $hijri = self::tsToHijri( $ts );
1261  }
1262  $num = $hijri[2];
1263  break;
1264  case 'xjj':
1265  $usedDay = true;
1266  if ( !$hebrew ) {
1267  $hebrew = self::tsToHebrew( $ts );
1268  }
1269  $num = $hebrew[2];
1270  break;
1271  case 'l':
1272  $usedDay = true;
1273  $s .= $this->getWeekdayName(
1274  self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'w' ) + 1
1275  );
1276  break;
1277  case 'F':
1278  $usedMonth = true;
1279  $s .= $this->getMonthName( substr( $ts, 4, 2 ) );
1280  break;
1281  case 'xiF':
1282  $usedIranianMonth = true;
1283  if ( !$iranian ) {
1284  $iranian = self::tsToIranian( $ts );
1285  }
1286  $s .= $this->getIranianCalendarMonthName( $iranian[1] );
1287  break;
1288  case 'xmF':
1289  $usedHijriMonth = true;
1290  if ( !$hijri ) {
1291  $hijri = self::tsToHijri( $ts );
1292  }
1293  $s .= $this->getHijriCalendarMonthName( $hijri[1] );
1294  break;
1295  case 'xjF':
1296  $usedHebrewMonth = true;
1297  if ( !$hebrew ) {
1298  $hebrew = self::tsToHebrew( $ts );
1299  }
1300  $s .= $this->getHebrewCalendarMonthName( $hebrew[1] );
1301  break;
1302  case 'm':
1303  $usedMonth = true;
1304  $num = substr( $ts, 4, 2 );
1305  break;
1306  case 'M':
1307  $usedMonth = true;
1308  $s .= $this->getMonthAbbreviation( substr( $ts, 4, 2 ) );
1309  break;
1310  case 'n':
1311  $usedMonth = true;
1312  $num = intval( substr( $ts, 4, 2 ) );
1313  break;
1314  case 'xin':
1315  $usedIranianMonth = true;
1316  if ( !$iranian ) {
1317  $iranian = self::tsToIranian( $ts );
1318  }
1319  $num = $iranian[1];
1320  break;
1321  case 'xmn':
1322  $usedHijriMonth = true;
1323  if ( !$hijri ) {
1324  $hijri = self::tsToHijri( $ts );
1325  }
1326  $num = $hijri[1];
1327  break;
1328  case 'xjn':
1329  $usedHebrewMonth = true;
1330  if ( !$hebrew ) {
1331  $hebrew = self::tsToHebrew( $ts );
1332  }
1333  $num = $hebrew[1];
1334  break;
1335  case 'xjt':
1336  $usedHebrewMonth = true;
1337  if ( !$hebrew ) {
1338  $hebrew = self::tsToHebrew( $ts );
1339  }
1340  $num = $hebrew[3];
1341  break;
1342  case 'Y':
1343  $usedYear = true;
1344  $num = substr( $ts, 0, 4 );
1345  break;
1346  case 'xiY':
1347  $usedIranianYear = true;
1348  if ( !$iranian ) {
1349  $iranian = self::tsToIranian( $ts );
1350  }
1351  $num = $iranian[0];
1352  break;
1353  case 'xmY':
1354  $usedHijriYear = true;
1355  if ( !$hijri ) {
1356  $hijri = self::tsToHijri( $ts );
1357  }
1358  $num = $hijri[0];
1359  break;
1360  case 'xjY':
1361  $usedHebrewYear = true;
1362  if ( !$hebrew ) {
1363  $hebrew = self::tsToHebrew( $ts );
1364  }
1365  $num = $hebrew[0];
1366  break;
1367  case 'xkY':
1368  $usedYear = true;
1369  if ( !$thai ) {
1370  $thai = self::tsToYear( $ts, 'thai' );
1371  }
1372  $num = $thai[0];
1373  break;
1374  case 'xoY':
1375  $usedYear = true;
1376  if ( !$minguo ) {
1377  $minguo = self::tsToYear( $ts, 'minguo' );
1378  }
1379  $num = $minguo[0];
1380  break;
1381  case 'xtY':
1382  $usedTennoYear = true;
1383  if ( !$tenno ) {
1384  $tenno = self::tsToYear( $ts, 'tenno' );
1385  }
1386  $num = $tenno[0];
1387  break;
1388  case 'y':
1389  $usedYear = true;
1390  $num = substr( $ts, 2, 2 );
1391  break;
1392  case 'xiy':
1393  $usedIranianYear = true;
1394  if ( !$iranian ) {
1395  $iranian = self::tsToIranian( $ts );
1396  }
1397  $num = substr( $iranian[0], -2 );
1398  break;
1399  case 'xit':
1400  $usedIranianYear = true;
1401  if ( !$iranian ) {
1402  $iranian = self::tsToIranian( $ts );
1403  }
1404  $num = self::$IRANIAN_DAYS[$iranian[1] - 1];
1405  break;
1406  case 'xiz':
1407  $usedIranianYear = true;
1408  if ( !$iranian ) {
1409  $iranian = self::tsToIranian( $ts );
1410  }
1411  $num = $iranian[3];
1412  break;
1413  case 'a':
1414  $usedAMPM = true;
1415  $s .= intval( substr( $ts, 8, 2 ) ) < 12 ? 'am' : 'pm';
1416  break;
1417  case 'A':
1418  $usedAMPM = true;
1419  $s .= intval( substr( $ts, 8, 2 ) ) < 12 ? 'AM' : 'PM';
1420  break;
1421  case 'g':
1422  $usedHour = true;
1423  $h = substr( $ts, 8, 2 );
1424  $num = $h % 12 ? $h % 12 : 12;
1425  break;
1426  case 'G':
1427  $usedHour = true;
1428  $num = intval( substr( $ts, 8, 2 ) );
1429  break;
1430  case 'h':
1431  $usedHour = true;
1432  $h = substr( $ts, 8, 2 );
1433  $num = sprintf( '%02d', $h % 12 ? $h % 12 : 12 );
1434  break;
1435  case 'H':
1436  $usedHour = true;
1437  $num = substr( $ts, 8, 2 );
1438  break;
1439  case 'i':
1440  $usedMinute = true;
1441  $num = substr( $ts, 10, 2 );
1442  break;
1443  case 's':
1444  $usedSecond = true;
1445  $num = substr( $ts, 12, 2 );
1446  break;
1447  case 'c':
1448  case 'r':
1449  $usedSecond = true;
1450  // fall through
1451  case 'e':
1452  case 'O':
1453  case 'P':
1454  case 'T':
1455  $s .= self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1456  break;
1457  case 'w':
1458  case 'N':
1459  case 'z':
1460  $usedDay = true;
1461  $num = self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1462  break;
1463  case 'W':
1464  $usedWeek = true;
1465  $num = self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1466  break;
1467  case 't':
1468  $usedMonth = true;
1469  $num = self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1470  break;
1471  case 'L':
1472  $usedIsLeapYear = true;
1473  $num = self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1474  break;
1475  case 'o':
1476  $usedISOYear = true;
1477  $num = self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1478  break;
1479  case 'U':
1480  $usedSecond = true;
1481  // fall through
1482  case 'I':
1483  case 'Z':
1484  $num = self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1485  break;
1486  case '\\':
1487  # Backslash escaping
1488  if ( $p < $formatLength - 1 ) {
1489  $s .= $format[++$p];
1490  } else {
1491  $s .= '\\';
1492  }
1493  break;
1494  case '"':
1495  # Quoted literal
1496  if ( $p < $formatLength - 1 ) {
1497  $endQuote = strpos( $format, '"', $p + 1 );
1498  if ( $endQuote === false ) {
1499  # No terminating quote, assume literal "
1500  $s .= '"';
1501  } else {
1502  $s .= substr( $format, $p + 1, $endQuote - $p - 1 );
1503  $p = $endQuote;
1504  }
1505  } else {
1506  # Quote at end of string, assume literal "
1507  $s .= '"';
1508  }
1509  break;
1510  default:
1511  $s .= $format[$p];
1512  }
1513  if ( $num !== false ) {
1514  if ( $rawToggle || $raw ) {
1515  $s .= $num;
1516  $raw = false;
1517  } elseif ( $roman ) {
1518  $s .= self::romanNumeral( $num );
1519  $roman = false;
1520  } elseif ( $hebrewNum ) {
1521  $s .= self::hebrewNumeral( $num );
1522  $hebrewNum = false;
1523  } else {
1524  $s .= $this->formatNum( $num, true );
1525  }
1526  }
1527  }
1528 
1529  if ( $ttl === 'unused' ) {
1530  // No need to calculate the TTL, the caller wont use it anyway.
1531  } elseif ( $usedSecond ) {
1532  $ttl = 1;
1533  } elseif ( $usedMinute ) {
1534  $ttl = 60 - substr( $ts, 12, 2 );
1535  } elseif ( $usedHour ) {
1536  $ttl = 3600 - substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
1537  } elseif ( $usedAMPM ) {
1538  $ttl = 43200 - ( substr( $ts, 8, 2 ) % 12 ) * 3600 -
1539  substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
1540  } elseif (
1541  $usedDay ||
1542  $usedHebrewMonth ||
1543  $usedIranianMonth ||
1544  $usedHijriMonth ||
1545  $usedHebrewYear ||
1546  $usedIranianYear ||
1547  $usedHijriYear ||
1548  $usedTennoYear
1549  ) {
1550  // @todo Someone who understands the non-Gregorian calendars
1551  // should write proper logic for them so that they don't need purged every day.
1552  $ttl = 86400 - substr( $ts, 8, 2 ) * 3600 -
1553  substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
1554  } else {
1555  $possibleTtls = [];
1556  $timeRemainingInDay = 86400 - substr( $ts, 8, 2 ) * 3600 -
1557  substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
1558  if ( $usedWeek ) {
1559  $possibleTtls[] =
1560  ( 7 - self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'N' ) ) * 86400 +
1561  $timeRemainingInDay;
1562  } elseif ( $usedISOYear ) {
1563  // December 28th falls on the last ISO week of the year, every year.
1564  // The last ISO week of a year can be 52 or 53.
1565  $lastWeekOfISOYear = DateTime::createFromFormat(
1566  'Ymd',
1567  substr( $ts, 0, 4 ) . '1228',
1568  $zone ?: new DateTimeZone( 'UTC' )
1569  )->format( 'W' );
1570  $currentISOWeek = self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'W' );
1571  $weeksRemaining = $lastWeekOfISOYear - $currentISOWeek;
1572  $timeRemainingInWeek =
1573  ( 7 - self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'N' ) ) * 86400
1574  + $timeRemainingInDay;
1575  $possibleTtls[] = $weeksRemaining * 604800 + $timeRemainingInWeek;
1576  }
1577 
1578  if ( $usedMonth ) {
1579  $possibleTtls[] =
1580  ( self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 't' ) -
1581  substr( $ts, 6, 2 ) ) * 86400
1582  + $timeRemainingInDay;
1583  } elseif ( $usedYear ) {
1584  $possibleTtls[] =
1585  ( self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'L' ) + 364 -
1586  self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'z' ) ) * 86400
1587  + $timeRemainingInDay;
1588  } elseif ( $usedIsLeapYear ) {
1589  $year = substr( $ts, 0, 4 );
1590  $timeRemainingInYear =
1591  ( self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'L' ) + 364 -
1592  self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'z' ) ) * 86400
1593  + $timeRemainingInDay;
1594  $mod = $year % 4;
1595  if ( $mod || ( !( $year % 100 ) && $year % 400 ) ) {
1596  // this isn't a leap year. see when the next one starts
1597  $nextCandidate = $year - $mod + 4;
1598  if ( $nextCandidate % 100 || !( $nextCandidate % 400 ) ) {
1599  $possibleTtls[] = ( $nextCandidate - $year - 1 ) * 365 * 86400 +
1600  $timeRemainingInYear;
1601  } else {
1602  $possibleTtls[] = ( $nextCandidate - $year + 3 ) * 365 * 86400 +
1603  $timeRemainingInYear;
1604  }
1605  } else {
1606  // this is a leap year, so the next year isn't
1607  $possibleTtls[] = $timeRemainingInYear;
1608  }
1609  }
1610 
1611  if ( $possibleTtls ) {
1612  $ttl = min( $possibleTtls );
1613  }
1614  }
1615 
1616  return $s;
1617  }
1618 
1619  private static $GREG_DAYS = [ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ];
1620  private static $IRANIAN_DAYS = [ 31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29 ];
1621 
1634  private static function tsToIranian( $ts ) {
1635  $gy = substr( $ts, 0, 4 ) - 1600;
1636  $gm = substr( $ts, 4, 2 ) - 1;
1637  $gd = substr( $ts, 6, 2 ) - 1;
1638 
1639  # Days passed from the beginning (including leap years)
1640  $gDayNo = 365 * $gy
1641  + floor( ( $gy + 3 ) / 4 )
1642  - floor( ( $gy + 99 ) / 100 )
1643  + floor( ( $gy + 399 ) / 400 );
1644 
1645  // Add days of the past months of this year
1646  for ( $i = 0; $i < $gm; $i++ ) {
1647  $gDayNo += self::$GREG_DAYS[$i];
1648  }
1649 
1650  // Leap years
1651  if ( $gm > 1 && ( ( $gy % 4 === 0 && $gy % 100 !== 0 || ( $gy % 400 == 0 ) ) ) ) {
1652  $gDayNo++;
1653  }
1654 
1655  // Days passed in current month
1656  $gDayNo += (int)$gd;
1657 
1658  $jDayNo = $gDayNo - 79;
1659 
1660  $jNp = floor( $jDayNo / 12053 );
1661  $jDayNo %= 12053;
1662 
1663  $jy = 979 + 33 * $jNp + 4 * floor( $jDayNo / 1461 );
1664  $jDayNo %= 1461;
1665 
1666  if ( $jDayNo >= 366 ) {
1667  $jy += floor( ( $jDayNo - 1 ) / 365 );
1668  $jDayNo = floor( ( $jDayNo - 1 ) % 365 );
1669  }
1670 
1671  $jz = $jDayNo;
1672 
1673  for ( $i = 0; $i < 11 && $jDayNo >= self::$IRANIAN_DAYS[$i]; $i++ ) {
1674  $jDayNo -= self::$IRANIAN_DAYS[$i];
1675  }
1676 
1677  $jm = $i + 1;
1678  $jd = $jDayNo + 1;
1679 
1680  return [ $jy, $jm, $jd, $jz ];
1681  }
1682 
1694  private static function tsToHijri( $ts ) {
1695  $year = substr( $ts, 0, 4 );
1696  $month = substr( $ts, 4, 2 );
1697  $day = substr( $ts, 6, 2 );
1698 
1699  $zyr = $year;
1700  $zd = $day;
1701  $zm = $month;
1702  $zy = $zyr;
1703 
1704  if (
1705  ( $zy > 1582 ) || ( ( $zy == 1582 ) && ( $zm > 10 ) ) ||
1706  ( ( $zy == 1582 ) && ( $zm == 10 ) && ( $zd > 14 ) )
1707  ) {
1708  $zjd = (int)( ( 1461 * ( $zy + 4800 + (int)( ( $zm - 14 ) / 12 ) ) ) / 4 ) +
1709  (int)( ( 367 * ( $zm - 2 - 12 * ( (int)( ( $zm - 14 ) / 12 ) ) ) ) / 12 ) -
1710  (int)( ( 3 * (int)( ( ( $zy + 4900 + (int)( ( $zm - 14 ) / 12 ) ) / 100 ) ) ) / 4 ) +
1711  $zd - 32075;
1712  } else {
1713  $zjd = 367 * $zy - (int)( ( 7 * ( $zy + 5001 + (int)( ( $zm - 9 ) / 7 ) ) ) / 4 ) +
1714  (int)( ( 275 * $zm ) / 9 ) + $zd + 1729777;
1715  }
1716 
1717  $zl = $zjd - 1948440 + 10632;
1718  $zn = (int)( ( $zl - 1 ) / 10631 );
1719  $zl = $zl - 10631 * $zn + 354;
1720  $zj = ( (int)( ( 10985 - $zl ) / 5316 ) ) * ( (int)( ( 50 * $zl ) / 17719 ) ) +
1721  ( (int)( $zl / 5670 ) ) * ( (int)( ( 43 * $zl ) / 15238 ) );
1722  $zl = $zl - ( (int)( ( 30 - $zj ) / 15 ) ) * ( (int)( ( 17719 * $zj ) / 50 ) ) -
1723  ( (int)( $zj / 16 ) ) * ( (int)( ( 15238 * $zj ) / 43 ) ) + 29;
1724  $zm = (int)( ( 24 * $zl ) / 709 );
1725  $zd = $zl - (int)( ( 709 * $zm ) / 24 );
1726  $zy = 30 * $zn + $zj - 30;
1727 
1728  return [ $zy, $zm, $zd ];
1729  }
1730 
1746  private static function tsToHebrew( $ts ) {
1747  # Parse date
1748  $year = substr( $ts, 0, 4 );
1749  $month = substr( $ts, 4, 2 );
1750  $day = substr( $ts, 6, 2 );
1751 
1752  # Calculate Hebrew year
1753  $hebrewYear = $year + 3760;
1754 
1755  # Month number when September = 1, August = 12
1756  $month += 4;
1757  if ( $month > 12 ) {
1758  # Next year
1759  $month -= 12;
1760  $year++;
1761  $hebrewYear++;
1762  }
1763 
1764  # Calculate day of year from 1 September
1765  $dayOfYear = $day;
1766  for ( $i = 1; $i < $month; $i++ ) {
1767  if ( $i == 6 ) {
1768  # February
1769  $dayOfYear += 28;
1770  # Check if the year is leap
1771  if ( $year % 400 == 0 || ( $year % 4 == 0 && $year % 100 > 0 ) ) {
1772  $dayOfYear++;
1773  }
1774  } elseif ( $i == 8 || $i == 10 || $i == 1 || $i == 3 ) {
1775  $dayOfYear += 30;
1776  } else {
1777  $dayOfYear += 31;
1778  }
1779  }
1780 
1781  # Calculate the start of the Hebrew year
1782  $start = self::hebrewYearStart( $hebrewYear );
1783 
1784  # Calculate next year's start
1785  if ( $dayOfYear <= $start ) {
1786  # Day is before the start of the year - it is the previous year
1787  # Next year's start
1788  $nextStart = $start;
1789  # Previous year
1790  $year--;
1791  $hebrewYear--;
1792  # Add days since previous year's 1 September
1793  $dayOfYear += 365;
1794  if ( ( $year % 400 == 0 ) || ( $year % 100 != 0 && $year % 4 == 0 ) ) {
1795  # Leap year
1796  $dayOfYear++;
1797  }
1798  # Start of the new (previous) year
1799  $start = self::hebrewYearStart( $hebrewYear );
1800  } else {
1801  # Next year's start
1802  $nextStart = self::hebrewYearStart( $hebrewYear + 1 );
1803  }
1804 
1805  # Calculate Hebrew day of year
1806  $hebrewDayOfYear = $dayOfYear - $start;
1807 
1808  # Difference between year's days
1809  $diff = $nextStart - $start;
1810  # Add 12 (or 13 for leap years) days to ignore the difference between
1811  # Hebrew and Gregorian year (353 at least vs. 365/6) - now the
1812  # difference is only about the year type
1813  if ( ( $year % 400 == 0 ) || ( $year % 100 != 0 && $year % 4 == 0 ) ) {
1814  $diff += 13;
1815  } else {
1816  $diff += 12;
1817  }
1818 
1819  # Check the year pattern, and is leap year
1820  # 0 means an incomplete year, 1 means a regular year, 2 means a complete year
1821  # This is mod 30, to work on both leap years (which add 30 days of Adar I)
1822  # and non-leap years
1823  $yearPattern = $diff % 30;
1824  # Check if leap year
1825  $isLeap = $diff >= 30;
1826 
1827  # Calculate day in the month from number of day in the Hebrew year
1828  # Don't check Adar - if the day is not in Adar, we will stop before;
1829  # if it is in Adar, we will use it to check if it is Adar I or Adar II
1830  $hebrewDay = $hebrewDayOfYear;
1831  $hebrewMonth = 1;
1832  $days = 0;
1833  while ( $hebrewMonth <= 12 ) {
1834  # Calculate days in this month
1835  if ( $isLeap && $hebrewMonth == 6 ) {
1836  # Leap year - has Adar I, with 30 days, and Adar II, with 29 days
1837  $days = 30;
1838  if ( $hebrewDay <= $days ) {
1839  # Day in Adar I
1840  $hebrewMonth = 13;
1841  } else {
1842  # Subtract the days of Adar I
1843  $hebrewDay -= $days;
1844  # Try Adar II
1845  $days = 29;
1846  if ( $hebrewDay <= $days ) {
1847  # Day in Adar II
1848  $hebrewMonth = 14;
1849  }
1850  }
1851  } elseif ( $hebrewMonth == 2 && $yearPattern == 2 ) {
1852  # Cheshvan in a complete year (otherwise as the rule below)
1853  $days = 30;
1854  } elseif ( $hebrewMonth == 3 && $yearPattern == 0 ) {
1855  # Kislev in an incomplete year (otherwise as the rule below)
1856  $days = 29;
1857  } else {
1858  # Odd months have 30 days, even have 29
1859  $days = 30 - ( $hebrewMonth - 1 ) % 2;
1860  }
1861  if ( $hebrewDay <= $days ) {
1862  # In the current month
1863  break;
1864  } else {
1865  # Subtract the days of the current month
1866  $hebrewDay -= $days;
1867  # Try in the next month
1868  $hebrewMonth++;
1869  }
1870  }
1871 
1872  return [ $hebrewYear, $hebrewMonth, $hebrewDay, $days ];
1873  }
1874 
1884  private static function hebrewYearStart( $year ) {
1885  $a = intval( ( 12 * ( $year - 1 ) + 17 ) % 19 );
1886  $b = intval( ( $year - 1 ) % 4 );
1887  $m = 32.044093161144 + 1.5542417966212 * $a + $b / 4.0 - 0.0031777940220923 * ( $year - 1 );
1888  if ( $m < 0 ) {
1889  $m--;
1890  }
1891  $Mar = intval( $m );
1892  if ( $m < 0 ) {
1893  $m++;
1894  }
1895  $m -= $Mar;
1896 
1897  $c = intval( ( $Mar + 3 * ( $year - 1 ) + 5 * $b + 5 ) % 7 );
1898  if ( $c == 0 && $a > 11 && $m >= 0.89772376543210 ) {
1899  $Mar++;
1900  } elseif ( $c == 1 && $a > 6 && $m >= 0.63287037037037 ) {
1901  $Mar += 2;
1902  } elseif ( $c == 2 || $c == 4 || $c == 6 ) {
1903  $Mar++;
1904  }
1905 
1906  $Mar += intval( ( $year - 3761 ) / 100 ) - intval( ( $year - 3761 ) / 400 ) - 24;
1907  return $Mar;
1908  }
1909 
1922  private static function tsToYear( $ts, $cName ) {
1923  $gy = substr( $ts, 0, 4 );
1924  $gm = substr( $ts, 4, 2 );
1925  $gd = substr( $ts, 6, 2 );
1926 
1927  if ( !strcmp( $cName, 'thai' ) ) {
1928  # Thai solar dates
1929  # Add 543 years to the Gregorian calendar
1930  # Months and days are identical
1931  $gy_offset = $gy + 543;
1932  # fix for dates between 1912 and 1941
1933  # https://en.wikipedia.org/?oldid=836596673#New_year
1934  if ( $gy >= 1912 && $gy <= 1940 ) {
1935  if ( $gm <= 3 ) {
1936  $gy_offset--;
1937  }
1938  $gm = ( $gm - 3 ) % 12;
1939  }
1940  } elseif ( ( !strcmp( $cName, 'minguo' ) ) || !strcmp( $cName, 'juche' ) ) {
1941  # Minguo dates
1942  # Deduct 1911 years from the Gregorian calendar
1943  # Months and days are identical
1944  $gy_offset = $gy - 1911;
1945  } elseif ( !strcmp( $cName, 'tenno' ) ) {
1946  # Nengō dates up to Meiji period
1947  # Deduct years from the Gregorian calendar
1948  # depending on the nengo periods
1949  # Months and days are identical
1950  if ( ( $gy < 1912 )
1951  || ( ( $gy == 1912 ) && ( $gm < 7 ) )
1952  || ( ( $gy == 1912 ) && ( $gm == 7 ) && ( $gd < 31 ) )
1953  ) {
1954  # Meiji period
1955  $gy_gannen = $gy - 1868 + 1;
1956  $gy_offset = $gy_gannen;
1957  if ( $gy_gannen == 1 ) {
1958  $gy_offset = '元';
1959  }
1960  $gy_offset = '明治' . $gy_offset;
1961  } elseif (
1962  ( ( $gy == 1912 ) && ( $gm == 7 ) && ( $gd == 31 ) ) ||
1963  ( ( $gy == 1912 ) && ( $gm >= 8 ) ) ||
1964  ( ( $gy > 1912 ) && ( $gy < 1926 ) ) ||
1965  ( ( $gy == 1926 ) && ( $gm < 12 ) ) ||
1966  ( ( $gy == 1926 ) && ( $gm == 12 ) && ( $gd < 26 ) )
1967  ) {
1968  # Taishō period
1969  $gy_gannen = $gy - 1912 + 1;
1970  $gy_offset = $gy_gannen;
1971  if ( $gy_gannen == 1 ) {
1972  $gy_offset = '元';
1973  }
1974  $gy_offset = '大正' . $gy_offset;
1975  } elseif (
1976  ( ( $gy == 1926 ) && ( $gm == 12 ) && ( $gd >= 26 ) ) ||
1977  ( ( $gy > 1926 ) && ( $gy < 1989 ) ) ||
1978  ( ( $gy == 1989 ) && ( $gm == 1 ) && ( $gd < 8 ) )
1979  ) {
1980  # Shōwa period
1981  $gy_gannen = $gy - 1926 + 1;
1982  $gy_offset = $gy_gannen;
1983  if ( $gy_gannen == 1 ) {
1984  $gy_offset = '元';
1985  }
1986  $gy_offset = '昭和' . $gy_offset;
1987  } else {
1988  # Heisei period
1989  $gy_gannen = $gy - 1989 + 1;
1990  $gy_offset = $gy_gannen;
1991  if ( $gy_gannen == 1 ) {
1992  $gy_offset = '元';
1993  }
1994  $gy_offset = '平成' . $gy_offset;
1995  }
1996  } else {
1997  $gy_offset = $gy;
1998  }
1999 
2000  return [ $gy_offset, $gm, $gd ];
2001  }
2002 
2016  private static function strongDirFromContent( $text = '' ) {
2017  if ( !preg_match( self::$strongDirRegex, $text, $matches ) ) {
2018  return null;
2019  }
2020  if ( $matches[1] === '' ) {
2021  return 'rtl';
2022  }
2023  return 'ltr';
2024  }
2025 
2033  static function romanNumeral( $num ) {
2034  static $table = [
2035  [ '', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X' ],
2036  [ '', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC', 'C' ],
2037  [ '', 'C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM', 'M' ],
2038  [ '', 'M', 'MM', 'MMM', 'MMMM', 'MMMMM', 'MMMMMM', 'MMMMMMM',
2039  'MMMMMMMM', 'MMMMMMMMM', 'MMMMMMMMMM' ]
2040  ];
2041 
2042  $num = intval( $num );
2043  if ( $num > 10000 || $num <= 0 ) {
2044  return $num;
2045  }
2046 
2047  $s = '';
2048  for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) {
2049  if ( $num >= $pow10 ) {
2050  $s .= $table[$i][(int)floor( $num / $pow10 )];
2051  }
2052  $num = $num % $pow10;
2053  }
2054  return $s;
2055  }
2056 
2064  static function hebrewNumeral( $num ) {
2065  static $table = [
2066  [ '', 'א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ז', 'ח', 'ט', 'י' ],
2067  [ '', 'י', 'כ', 'ל', 'מ', 'נ', 'ס', 'ע', 'פ', 'צ', 'ק' ],
2068  [ '',
2069  [ 'ק' ],
2070  [ 'ר' ],
2071  [ 'ש' ],
2072  [ 'ת' ],
2073  [ 'ת', 'ק' ],
2074  [ 'ת', 'ר' ],
2075  [ 'ת', 'ש' ],
2076  [ 'ת', 'ת' ],
2077  [ 'ת', 'ת', 'ק' ],
2078  [ 'ת', 'ת', 'ר' ],
2079  ],
2080  [ '', 'א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ז', 'ח', 'ט', 'י' ]
2081  ];
2082 
2083  $num = intval( $num );
2084  if ( $num > 9999 || $num <= 0 ) {
2085  return $num;
2086  }
2087 
2088  // Round thousands have special notations
2089  if ( $num === 1000 ) {
2090  return "א' אלף";
2091  } elseif ( $num % 1000 === 0 ) {
2092  return $table[0][$num / 1000] . "' אלפים";
2093  }
2094 
2095  $letters = [];
2096 
2097  for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) {
2098  if ( $num >= $pow10 ) {
2099  if ( $num === 15 || $num === 16 ) {
2100  $letters[] = $table[0][9];
2101  $letters[] = $table[0][$num - 9];
2102  $num = 0;
2103  } else {
2104  $letters = array_merge(
2105  $letters,
2106  (array)$table[$i][intval( $num / $pow10 )]
2107  );
2108 
2109  if ( $pow10 === 1000 ) {
2110  $letters[] = "'";
2111  }
2112  }
2113  }
2114 
2115  $num = $num % $pow10;
2116  }
2117 
2118  $preTransformLength = count( $letters );
2119  if ( $preTransformLength === 1 ) {
2120  // Add geresh (single quote) to one-letter numbers
2121  $letters[] = "'";
2122  } else {
2123  $lastIndex = $preTransformLength - 1;
2124  $letters[$lastIndex] = str_replace(
2125  [ 'כ', 'מ', 'נ', 'פ', 'צ' ],
2126  [ 'ך', 'ם', 'ן', 'ף', 'ץ' ],
2127  $letters[$lastIndex]
2128  );
2129 
2130  // Add gershayim (double quote) to multiple-letter numbers,
2131  // but exclude numbers with only one letter after the thousands
2132  // (1001-1009, 1020, 1030, 2001-2009, etc.)
2133  if ( $letters[1] === "'" && $preTransformLength === 3 ) {
2134  $letters[] = "'";
2135  } else {
2136  array_splice( $letters, -1, 0, '"' );
2137  }
2138  }
2139 
2140  return implode( $letters );
2141  }
2142 
2151  public function userAdjust( $ts, $tz = false ) {
2152  global $wgUser, $wgLocalTZoffset;
2153 
2154  if ( $tz === false ) {
2155  $tz = $wgUser->getOption( 'timecorrection' );
2156  }
2157 
2158  $data = explode( '|', $tz, 3 );
2159 
2160  if ( $data[0] == 'ZoneInfo' ) {
2161  try {
2162  $userTZ = new DateTimeZone( $data[2] );
2163  $date = new DateTime( $ts, new DateTimeZone( 'UTC' ) );
2164  $date->setTimezone( $userTZ );
2165  return $date->format( 'YmdHis' );
2166  } catch ( Exception $e ) {
2167  // Unrecognized timezone, default to 'Offset' with the stored offset.
2168  $data[0] = 'Offset';
2169  }
2170  }
2171 
2172  if ( $data[0] == 'System' || $tz == '' ) {
2173  # Global offset in minutes.
2174  $minDiff = $wgLocalTZoffset;
2175  } elseif ( $data[0] == 'Offset' ) {
2176  $minDiff = intval( $data[1] );
2177  } else {
2178  $data = explode( ':', $tz );
2179  if ( count( $data ) == 2 ) {
2180  $data[0] = intval( $data[0] );
2181  $data[1] = intval( $data[1] );
2182  $minDiff = abs( $data[0] ) * 60 + $data[1];
2183  if ( $data[0] < 0 ) {
2184  $minDiff = -$minDiff;
2185  }
2186  } else {
2187  $minDiff = intval( $data[0] ) * 60;
2188  }
2189  }
2190 
2191  # No difference ? Return time unchanged
2192  if ( $minDiff == 0 ) {
2193  return $ts;
2194  }
2195 
2196  Wikimedia\suppressWarnings(); // E_STRICT system time bitching
2197  # Generate an adjusted date; take advantage of the fact that mktime
2198  # will normalize out-of-range values so we don't have to split $minDiff
2199  # into hours and minutes.
2200  $t = mktime( (
2201  (int)substr( $ts, 8, 2 ) ), # Hours
2202  (int)substr( $ts, 10, 2 ) + $minDiff, # Minutes
2203  (int)substr( $ts, 12, 2 ), # Seconds
2204  (int)substr( $ts, 4, 2 ), # Month
2205  (int)substr( $ts, 6, 2 ), # Day
2206  (int)substr( $ts, 0, 4 ) ); # Year
2207 
2208  $date = date( 'YmdHis', $t );
2209  Wikimedia\restoreWarnings();
2210 
2211  return $date;
2212  }
2213 
2229  function dateFormat( $usePrefs = true ) {
2230  global $wgUser;
2231 
2232  if ( is_bool( $usePrefs ) ) {
2233  if ( $usePrefs ) {
2234  $datePreference = $wgUser->getDatePreference();
2235  } else {
2236  $datePreference = (string)User::getDefaultOption( 'date' );
2237  }
2238  } else {
2239  $datePreference = (string)$usePrefs;
2240  }
2241 
2242  // return int
2243  if ( $datePreference == '' ) {
2244  return 'default';
2245  }
2246 
2247  return $datePreference;
2248  }
2249 
2260  function getDateFormatString( $type, $pref ) {
2261  $wasDefault = false;
2262  if ( $pref == 'default' ) {
2263  $wasDefault = true;
2264  $pref = $this->getDefaultDateFormat();
2265  }
2266 
2267  if ( !isset( $this->dateFormatStrings[$type][$pref] ) ) {
2268  $df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" );
2269 
2270  if ( $type === 'pretty' && $df === null ) {
2271  $df = $this->getDateFormatString( 'date', $pref );
2272  }
2273 
2274  if ( !$wasDefault && $df === null ) {
2275  $pref = $this->getDefaultDateFormat();
2276  $df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" );
2277  }
2278 
2279  $this->dateFormatStrings[$type][$pref] = $df;
2280  }
2281  return $this->dateFormatStrings[$type][$pref];
2282  }
2283 
2294  public function date( $ts, $adj = false, $format = true, $timecorrection = false ) {
2295  $ts = wfTimestamp( TS_MW, $ts );
2296  if ( $adj ) {
2297  $ts = $this->userAdjust( $ts, $timecorrection );
2298  }
2299  $df = $this->getDateFormatString( 'date', $this->dateFormat( $format ) );
2300  return $this->sprintfDate( $df, $ts );
2301  }
2302 
2313  public function time( $ts, $adj = false, $format = true, $timecorrection = false ) {
2314  $ts = wfTimestamp( TS_MW, $ts );
2315  if ( $adj ) {
2316  $ts = $this->userAdjust( $ts, $timecorrection );
2317  }
2318  $df = $this->getDateFormatString( 'time', $this->dateFormat( $format ) );
2319  return $this->sprintfDate( $df, $ts );
2320  }
2321 
2333  public function timeanddate( $ts, $adj = false, $format = true, $timecorrection = false ) {
2334  $ts = wfTimestamp( TS_MW, $ts );
2335  if ( $adj ) {
2336  $ts = $this->userAdjust( $ts, $timecorrection );
2337  }
2338  $df = $this->getDateFormatString( 'both', $this->dateFormat( $format ) );
2339  return $this->sprintfDate( $df, $ts );
2340  }
2341 
2352  public function formatDuration( $seconds, array $chosenIntervals = [] ) {
2353  $intervals = $this->getDurationIntervals( $seconds, $chosenIntervals );
2354 
2355  $segments = [];
2356 
2357  foreach ( $intervals as $intervalName => $intervalValue ) {
2358  // Messages: duration-seconds, duration-minutes, duration-hours, duration-days, duration-weeks,
2359  // duration-years, duration-decades, duration-centuries, duration-millennia
2360  $message = wfMessage( 'duration-' . $intervalName )->numParams( $intervalValue );
2361  $segments[] = $message->inLanguage( $this )->escaped();
2362  }
2363 
2364  return $this->listToText( $segments );
2365  }
2366 
2378  public function getDurationIntervals( $seconds, array $chosenIntervals = [] ) {
2379  if ( empty( $chosenIntervals ) ) {
2380  $chosenIntervals = [
2381  'millennia',
2382  'centuries',
2383  'decades',
2384  'years',
2385  'days',
2386  'hours',
2387  'minutes',
2388  'seconds'
2389  ];
2390  }
2391 
2392  $intervals = array_intersect_key( self::$durationIntervals, array_flip( $chosenIntervals ) );
2393  $sortedNames = array_keys( $intervals );
2394  $smallestInterval = array_pop( $sortedNames );
2395 
2396  $segments = [];
2397 
2398  foreach ( $intervals as $name => $length ) {
2399  $value = floor( $seconds / $length );
2400 
2401  if ( $value > 0 || ( $name == $smallestInterval && empty( $segments ) ) ) {
2402  $seconds -= $value * $length;
2403  $segments[$name] = $value;
2404  }
2405  }
2406 
2407  return $segments;
2408  }
2409 
2429  private function internalUserTimeAndDate( $type, $ts, User $user, array $options ) {
2430  $ts = wfTimestamp( TS_MW, $ts );
2431  $options += [ 'timecorrection' => true, 'format' => true ];
2432  if ( $options['timecorrection'] !== false ) {
2433  if ( $options['timecorrection'] === true ) {
2434  $offset = $user->getOption( 'timecorrection' );
2435  } else {
2436  $offset = $options['timecorrection'];
2437  }
2438  $ts = $this->userAdjust( $ts, $offset );
2439  }
2440  if ( $options['format'] === true ) {
2441  $format = $user->getDatePreference();
2442  } else {
2443  $format = $options['format'];
2444  }
2445  $df = $this->getDateFormatString( $type, $this->dateFormat( $format ) );
2446  return $this->sprintfDate( $df, $ts );
2447  }
2448 
2468  public function userDate( $ts, User $user, array $options = [] ) {
2469  return $this->internalUserTimeAndDate( 'date', $ts, $user, $options );
2470  }
2471 
2491  public function userTime( $ts, User $user, array $options = [] ) {
2492  return $this->internalUserTimeAndDate( 'time', $ts, $user, $options );
2493  }
2494 
2514  public function userTimeAndDate( $ts, User $user, array $options = [] ) {
2515  return $this->internalUserTimeAndDate( 'both', $ts, $user, $options );
2516  }
2517 
2533  public function getHumanTimestamp(
2534  MWTimestamp $time, MWTimestamp $relativeTo = null, User $user = null
2535  ) {
2536  if ( $relativeTo === null ) {
2537  $relativeTo = new MWTimestamp();
2538  }
2539  if ( $user === null ) {
2540  $user = RequestContext::getMain()->getUser();
2541  }
2542 
2543  // Adjust for the user's timezone.
2544  $offsetThis = $time->offsetForUser( $user );
2545  $offsetRel = $relativeTo->offsetForUser( $user );
2546 
2547  $ts = '';
2548  if ( Hooks::run( 'GetHumanTimestamp', [ &$ts, $time, $relativeTo, $user, $this ] ) ) {
2549  $ts = $this->getHumanTimestampInternal( $time, $relativeTo, $user );
2550  }
2551 
2552  // Reset the timezone on the objects.
2553  $time->timestamp->sub( $offsetThis );
2554  $relativeTo->timestamp->sub( $offsetRel );
2555 
2556  return $ts;
2557  }
2558 
2570  private function getHumanTimestampInternal(
2571  MWTimestamp $ts, MWTimestamp $relativeTo, User $user
2572  ) {
2573  $diff = $ts->diff( $relativeTo );
2574  $diffDay = (bool)( (int)$ts->timestamp->format( 'w' ) -
2575  (int)$relativeTo->timestamp->format( 'w' ) );
2576  $days = $diff->days ?: (int)$diffDay;
2577  if ( $diff->invert || $days > 5
2578  && $ts->timestamp->format( 'Y' ) !== $relativeTo->timestamp->format( 'Y' )
2579  ) {
2580  // Timestamps are in different years: use full timestamp
2581  // Also do full timestamp for future dates
2585  $format = $this->getDateFormatString( 'both', $user->getDatePreference() ?: 'default' );
2586  $ts = $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) );
2587  } elseif ( $days > 5 ) {
2588  // Timestamps are in same year, but more than 5 days ago: show day and month only.
2589  $format = $this->getDateFormatString( 'pretty', $user->getDatePreference() ?: 'default' );
2590  $ts = $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) );
2591  } elseif ( $days > 1 ) {
2592  // Timestamp within the past week: show the day of the week and time
2593  $format = $this->getDateFormatString( 'time', $user->getDatePreference() ?: 'default' );
2594  $weekday = self::$mWeekdayMsgs[$ts->timestamp->format( 'w' )];
2595  // Messages:
2596  // sunday-at, monday-at, tuesday-at, wednesday-at, thursday-at, friday-at, saturday-at
2597  $ts = wfMessage( "$weekday-at" )
2598  ->inLanguage( $this )
2599  ->params( $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) ) )
2600  ->text();
2601  } elseif ( $days == 1 ) {
2602  // Timestamp was yesterday: say 'yesterday' and the time.
2603  $format = $this->getDateFormatString( 'time', $user->getDatePreference() ?: 'default' );
2604  $ts = wfMessage( 'yesterday-at' )
2605  ->inLanguage( $this )
2606  ->params( $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) ) )
2607  ->text();
2608  } elseif ( $diff->h > 1 || $diff->h == 1 && $diff->i > 30 ) {
2609  // Timestamp was today, but more than 90 minutes ago: say 'today' and the time.
2610  $format = $this->getDateFormatString( 'time', $user->getDatePreference() ?: 'default' );
2611  $ts = wfMessage( 'today-at' )
2612  ->inLanguage( $this )
2613  ->params( $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) ) )
2614  ->text();
2615 
2616  // From here on in, the timestamp was soon enough ago so that we can simply say
2617  // XX units ago, e.g., "2 hours ago" or "5 minutes ago"
2618  } elseif ( $diff->h == 1 ) {
2619  // Less than 90 minutes, but more than an hour ago.
2620  $ts = wfMessage( 'hours-ago' )->inLanguage( $this )->numParams( 1 )->text();
2621  } elseif ( $diff->i >= 1 ) {
2622  // A few minutes ago.
2623  $ts = wfMessage( 'minutes-ago' )->inLanguage( $this )->numParams( $diff->i )->text();
2624  } elseif ( $diff->s >= 30 ) {
2625  // Less than a minute, but more than 30 sec ago.
2626  $ts = wfMessage( 'seconds-ago' )->inLanguage( $this )->numParams( $diff->s )->text();
2627  } else {
2628  // Less than 30 seconds ago.
2629  $ts = wfMessage( 'just-now' )->text();
2630  }
2631 
2632  return $ts;
2633  }
2634 
2639  public function getMessage( $key ) {
2640  return self::$dataCache->getSubitem( $this->mCode, 'messages', $key );
2641  }
2642 
2646  function getAllMessages() {
2647  return self::$dataCache->getItem( $this->mCode, 'messages' );
2648  }
2649 
2656  public function iconv( $in, $out, $string ) {
2657  # Even with //IGNORE iconv can whine about illegal characters in
2658  # *input* string. We just ignore those too.
2659  # REF: https://bugs.php.net/bug.php?id=37166
2660  # REF: https://phabricator.wikimedia.org/T18885
2661  Wikimedia\suppressWarnings();
2662  $text = iconv( $in, $out . '//IGNORE', $string );
2663  Wikimedia\restoreWarnings();
2664  return $text;
2665  }
2666 
2667  // callback functions for ucwords(), ucwordbreaks()
2668 
2674  return $this->ucfirst( $matches[1] );
2675  }
2676 
2682  return mb_strtoupper( $matches[0] );
2683  }
2684 
2690  return mb_strtoupper( $matches[0] );
2691  }
2692 
2700  public function ucfirst( $str ) {
2701  $o = ord( $str );
2702  if ( $o < 96 ) { // if already uppercase...
2703  return $str;
2704  } elseif ( $o < 128 ) {
2705  return ucfirst( $str ); // use PHP's ucfirst()
2706  } else {
2707  // fall back to more complex logic in case of multibyte strings
2708  return $this->uc( $str, true );
2709  }
2710  }
2711 
2720  public function uc( $str, $first = false ) {
2721  if ( $first ) {
2722  if ( $this->isMultibyte( $str ) ) {
2723  return mb_strtoupper( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
2724  } else {
2725  return ucfirst( $str );
2726  }
2727  } else {
2728  return $this->isMultibyte( $str ) ? mb_strtoupper( $str ) : strtoupper( $str );
2729  }
2730  }
2731 
2736  function lcfirst( $str ) {
2737  $o = ord( $str );
2738  if ( !$o ) {
2739  return strval( $str );
2740  } elseif ( $o >= 128 ) {
2741  return $this->lc( $str, true );
2742  } elseif ( $o > 96 ) {
2743  return $str;
2744  } else {
2745  $str[0] = strtolower( $str[0] );
2746  return $str;
2747  }
2748  }
2749 
2755  function lc( $str, $first = false ) {
2756  if ( $first ) {
2757  if ( $this->isMultibyte( $str ) ) {
2758  return mb_strtolower( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
2759  } else {
2760  return strtolower( substr( $str, 0, 1 ) ) . substr( $str, 1 );
2761  }
2762  } else {
2763  return $this->isMultibyte( $str ) ? mb_strtolower( $str ) : strtolower( $str );
2764  }
2765  }
2766 
2771  function isMultibyte( $str ) {
2772  return strlen( $str ) !== mb_strlen( $str );
2773  }
2774 
2779  function ucwords( $str ) {
2780  if ( $this->isMultibyte( $str ) ) {
2781  $str = $this->lc( $str );
2782 
2783  // regexp to find first letter in each word (i.e. after each space)
2784  $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)| ([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
2785 
2786  // function to use to capitalize a single char
2787  return preg_replace_callback(
2788  $replaceRegexp,
2789  [ $this, 'ucwordsCallbackMB' ],
2790  $str
2791  );
2792  } else {
2793  return ucwords( strtolower( $str ) );
2794  }
2795  }
2796 
2803  function ucwordbreaks( $str ) {
2804  if ( $this->isMultibyte( $str ) ) {
2805  $str = $this->lc( $str );
2806 
2807  // since \b doesn't work for UTF-8, we explicitely define word break chars
2808  $breaks = "[ \-\(\)\}\{\.,\?!]";
2809 
2810  // find first letter after word break
2811  $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)|" .
2812  "$breaks([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
2813 
2814  return preg_replace_callback(
2815  $replaceRegexp,
2816  [ $this, 'ucwordbreaksCallbackMB' ],
2817  $str
2818  );
2819  } else {
2820  return preg_replace_callback(
2821  '/\b([\w\x80-\xff]+)\b/',
2822  [ $this, 'ucwordbreaksCallbackAscii' ],
2823  $str
2824  );
2825  }
2826  }
2827 
2843  function caseFold( $s ) {
2844  return $this->uc( $s );
2845  }
2846 
2852  function checkTitleEncoding( $s ) {
2853  if ( is_array( $s ) ) {
2854  throw new MWException( 'Given array to checkTitleEncoding.' );
2855  }
2856  if ( StringUtils::isUtf8( $s ) ) {
2857  return $s;
2858  }
2859 
2860  return $this->iconv( $this->fallback8bitEncoding(), 'utf-8', $s );
2861  }
2862 
2867  return self::$dataCache->getItem( $this->mCode, 'fallback8bitEncoding' );
2868  }
2869 
2878  function hasWordBreaks() {
2879  return true;
2880  }
2881 
2889  function segmentByWord( $string ) {
2890  return $string;
2891  }
2892 
2900  function normalizeForSearch( $string ) {
2901  return self::convertDoubleWidth( $string );
2902  }
2903 
2912  protected static function convertDoubleWidth( $string ) {
2913  static $full = null;
2914  static $half = null;
2915 
2916  if ( $full === null ) {
2917  $fullWidth = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
2918  $halfWidth = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
2919  $full = str_split( $fullWidth, 3 );
2920  $half = str_split( $halfWidth );
2921  }
2922 
2923  $string = str_replace( $full, $half, $string );
2924  return $string;
2925  }
2926 
2932  protected static function insertSpace( $string, $pattern ) {
2933  $string = preg_replace( $pattern, " $1 ", $string );
2934  $string = preg_replace( '/ +/', ' ', $string );
2935  return $string;
2936  }
2937 
2942  function convertForSearchResult( $termsArray ) {
2943  # some languages, e.g. Chinese, need to do a conversion
2944  # in order for search results to be displayed correctly
2945  return $termsArray;
2946  }
2947 
2954  function firstChar( $s ) {
2955  $matches = [];
2956  preg_match(
2957  '/^([\x00-\x7f]|[\xc0-\xdf][\x80-\xbf]|' .
2958  '[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xf7][\x80-\xbf]{3})/',
2959  $s,
2960  $matches
2961  );
2962 
2963  if ( isset( $matches[1] ) ) {
2964  if ( strlen( $matches[1] ) != 3 ) {
2965  return $matches[1];
2966  }
2967 
2968  // Break down Hangul syllables to grab the first jamo
2969  $code = UtfNormal\Utils::utf8ToCodepoint( $matches[1] );
2970  if ( $code < 0xac00 || 0xd7a4 <= $code ) {
2971  return $matches[1];
2972  } elseif ( $code < 0xb098 ) {
2973  return "\u{3131}";
2974  } elseif ( $code < 0xb2e4 ) {
2975  return "\u{3134}";
2976  } elseif ( $code < 0xb77c ) {
2977  return "\u{3137}";
2978  } elseif ( $code < 0xb9c8 ) {
2979  return "\u{3139}";
2980  } elseif ( $code < 0xbc14 ) {
2981  return "\u{3141}";
2982  } elseif ( $code < 0xc0ac ) {
2983  return "\u{3142}";
2984  } elseif ( $code < 0xc544 ) {
2985  return "\u{3145}";
2986  } elseif ( $code < 0xc790 ) {
2987  return "\u{3147}";
2988  } elseif ( $code < 0xcc28 ) {
2989  return "\u{3148}";
2990  } elseif ( $code < 0xce74 ) {
2991  return "\u{314A}";
2992  } elseif ( $code < 0xd0c0 ) {
2993  return "\u{314B}";
2994  } elseif ( $code < 0xd30c ) {
2995  return "\u{314C}";
2996  } elseif ( $code < 0xd558 ) {
2997  return "\u{314D}";
2998  } else {
2999  return "\u{314E}";
3000  }
3001  } else {
3002  return '';
3003  }
3004  }
3005 
3009  function initEncoding() {
3010  wfDeprecated( __METHOD__, '1.28' );
3011  // No-op.
3012  }
3013 
3019  function recodeForEdit( $s ) {
3020  wfDeprecated( __METHOD__, '1.28' );
3021  return $s;
3022  }
3023 
3029  function recodeInput( $s ) {
3030  wfDeprecated( __METHOD__, '1.28' );
3031  return $s;
3032  }
3033 
3045  public function normalize( $s ) {
3046  global $wgAllUnicodeFixes;
3047  $s = UtfNormal\Validator::cleanUp( $s );
3048  if ( $wgAllUnicodeFixes ) {
3049  $s = $this->transformUsingPairFile( 'normalize-ar.php', $s );
3050  $s = $this->transformUsingPairFile( 'normalize-ml.php', $s );
3051  }
3052 
3053  return $s;
3054  }
3055 
3070  protected function transformUsingPairFile( $file, $string ) {
3071  if ( !isset( $this->transformData[$file] ) ) {
3072  global $IP;
3073  $data = require "$IP/languages/data/{$file}";
3074  $this->transformData[$file] = new ReplacementArray( $data );
3075  }
3076  return $this->transformData[$file]->replace( $string );
3077  }
3078 
3084  function isRTL() {
3085  return self::$dataCache->getItem( $this->mCode, 'rtl' );
3086  }
3087 
3092  function getDir() {
3093  return $this->isRTL() ? 'rtl' : 'ltr';
3094  }
3095 
3104  function alignStart() {
3105  return $this->isRTL() ? 'right' : 'left';
3106  }
3107 
3116  function alignEnd() {
3117  return $this->isRTL() ? 'left' : 'right';
3118  }
3119 
3131  function getDirMarkEntity( $opposite = false ) {
3132  if ( $opposite ) {
3133  return $this->isRTL() ? '&lrm;' : '&rlm;';
3134  }
3135  return $this->isRTL() ? '&rlm;' : '&lrm;';
3136  }
3137 
3148  function getDirMark( $opposite = false ) {
3149  $lrm = "\u{200E}"; # LEFT-TO-RIGHT MARK, commonly abbreviated LRM
3150  $rlm = "\u{200F}"; # RIGHT-TO-LEFT MARK, commonly abbreviated RLM
3151  if ( $opposite ) {
3152  return $this->isRTL() ? $lrm : $rlm;
3153  }
3154  return $this->isRTL() ? $rlm : $lrm;
3155  }
3156 
3160  function capitalizeAllNouns() {
3161  return self::$dataCache->getItem( $this->mCode, 'capitalizeAllNouns' );
3162  }
3163 
3171  function getArrow( $direction = 'forwards' ) {
3172  switch ( $direction ) {
3173  case 'forwards':
3174  return $this->isRTL() ? '←' : '→';
3175  case 'backwards':
3176  return $this->isRTL() ? '→' : '←';
3177  case 'left':
3178  return '←';
3179  case 'right':
3180  return '→';
3181  case 'up':
3182  return '↑';
3183  case 'down':
3184  return '↓';
3185  }
3186  }
3187 
3193  function linkPrefixExtension() {
3194  return self::$dataCache->getItem( $this->mCode, 'linkPrefixExtension' );
3195  }
3196 
3201  function getMagicWords() {
3202  return self::$dataCache->getItem( $this->mCode, 'magicWords' );
3203  }
3204 
3210  function getMagic( $mw ) {
3211  $rawEntry = $this->mMagicExtensions[$mw->mId] ??
3212  self::$dataCache->getSubitem( $this->mCode, 'magicWords', $mw->mId );
3213 
3214  if ( !is_array( $rawEntry ) ) {
3215  wfWarn( "\"$rawEntry\" is not a valid magic word for \"$mw->mId\"" );
3216  } else {
3217  $mw->mCaseSensitive = $rawEntry[0];
3218  $mw->mSynonyms = array_slice( $rawEntry, 1 );
3219  }
3220  }
3221 
3227  function addMagicWordsByLang( $newWords ) {
3228  $fallbackChain = $this->getFallbackLanguages();
3229  $fallbackChain = array_reverse( $fallbackChain );
3230  foreach ( $fallbackChain as $code ) {
3231  if ( isset( $newWords[$code] ) ) {
3232  $this->mMagicExtensions = $newWords[$code] + $this->mMagicExtensions;
3233  }
3234  }
3235  }
3236 
3243  // Cache aliases because it may be slow to load them
3244  if ( is_null( $this->mExtendedSpecialPageAliases ) ) {
3245  // Initialise array
3246  $this->mExtendedSpecialPageAliases =
3247  self::$dataCache->getItem( $this->mCode, 'specialPageAliases' );
3248  }
3249 
3251  }
3252 
3259  function emphasize( $text ) {
3260  return "<em>$text</em>";
3261  }
3262 
3285  public function formatNum( $number, $nocommafy = false ) {
3286  global $wgTranslateNumerals;
3287  if ( !$nocommafy ) {
3288  $number = $this->commafy( $number );
3289  $s = $this->separatorTransformTable();
3290  if ( $s ) {
3291  $number = strtr( $number, $s );
3292  }
3293  }
3294 
3295  if ( $wgTranslateNumerals ) {
3296  $s = $this->digitTransformTable();
3297  if ( $s ) {
3298  $number = strtr( $number, $s );
3299  }
3300  }
3301 
3302  return (string)$number;
3303  }
3304 
3313  public function formatNumNoSeparators( $number ) {
3314  return $this->formatNum( $number, true );
3315  }
3316 
3321  public function parseFormattedNumber( $number ) {
3322  $s = $this->digitTransformTable();
3323  if ( $s ) {
3324  // eliminate empty array values such as ''. (T66347)
3325  $s = array_filter( $s );
3326  $number = strtr( $number, array_flip( $s ) );
3327  }
3328 
3329  $s = $this->separatorTransformTable();
3330  if ( $s ) {
3331  // eliminate empty array values such as ''. (T66347)
3332  $s = array_filter( $s );
3333  $number = strtr( $number, array_flip( $s ) );
3334  }
3335 
3336  $number = strtr( $number, [ ',' => '' ] );
3337  return $number;
3338  }
3339 
3346  function commafy( $number ) {
3349  if ( $number === null ) {
3350  return '';
3351  }
3352 
3353  if ( !$digitGroupingPattern || $digitGroupingPattern === "###,###,###" ) {
3354  // Default grouping is at thousands, use the same for ###,###,### pattern too.
3355  // In some languages it's conventional not to insert a thousands separator
3356  // in numbers that are four digits long (1000-9999).
3357  if ( $minimumGroupingDigits ) {
3358  // Number of '#' characters after last comma in the grouping pattern.
3359  // The pattern is hardcoded here, but this would vary for different patterns.
3360  $primaryGroupingSize = 3;
3361  // Maximum length of a number to suppress digit grouping for.
3362  $maximumLength = $minimumGroupingDigits + $primaryGroupingSize - 1;
3363  if ( preg_match( '/^\-?\d{1,' . $maximumLength . '}(\.\d+)?$/', $number ) ) {
3364  return $number;
3365  }
3366  }
3367  return strrev( (string)preg_replace( '/(\d{3})(?=\d)(?!\d*\.)/', '$1,', strrev( $number ) ) );
3368  } else {
3369  // Ref: http://cldr.unicode.org/translation/number-patterns
3370  $sign = "";
3371  if ( intval( $number ) < 0 ) {
3372  // For negative numbers apply the algorithm like positive number and add sign.
3373  $sign = "-";
3374  $number = substr( $number, 1 );
3375  }
3376  $integerPart = [];
3377  $decimalPart = [];
3378  $numMatches = preg_match_all( "/(#+)/", $digitGroupingPattern, $matches );
3379  preg_match( "/\d+/", $number, $integerPart );
3380  preg_match( "/\.\d*/", $number, $decimalPart );
3381  $groupedNumber = ( count( $decimalPart ) > 0 ) ? $decimalPart[0] : "";
3382  if ( $groupedNumber === $number ) {
3383  // the string does not have any number part. Eg: .12345
3384  return $sign . $groupedNumber;
3385  }
3386  $start = $end = ( $integerPart ) ? strlen( $integerPart[0] ) : 0;
3387  while ( $start > 0 ) {
3388  $match = $matches[0][$numMatches - 1];
3389  $matchLen = strlen( $match );
3390  $start = $end - $matchLen;
3391  if ( $start < 0 ) {
3392  $start = 0;
3393  }
3394  $groupedNumber = substr( $number, $start, $end - $start ) . $groupedNumber;
3395  $end = $start;
3396  if ( $numMatches > 1 ) {
3397  // use the last pattern for the rest of the number
3398  $numMatches--;
3399  }
3400  if ( $start > 0 ) {
3401  $groupedNumber = "," . $groupedNumber;
3402  }
3403  }
3404  return $sign . $groupedNumber;
3405  }
3406  }
3407 
3412  return self::$dataCache->getItem( $this->mCode, 'digitGroupingPattern' );
3413  }
3414 
3418  function digitTransformTable() {
3419  return self::$dataCache->getItem( $this->mCode, 'digitTransformTable' );
3420  }
3421 
3426  return self::$dataCache->getItem( $this->mCode, 'separatorTransformTable' );
3427  }
3428 
3433  return self::$dataCache->getItem( $this->mCode, 'minimumGroupingDigits' );
3434  }
3435 
3444  public function listToText( array $list ) {
3445  $itemCount = count( $list );
3446  if ( $itemCount < 1 ) {
3447  return '';
3448  }
3449  $text = array_pop( $list );
3450  if ( $itemCount > 1 ) {
3451  $and = $this->msg( 'and' )->escaped();
3452  $space = $this->msg( 'word-separator' )->escaped();
3453  $comma = '';
3454  if ( $itemCount > 2 ) {
3455  $comma = $this->msg( 'comma-separator' )->escaped();
3456  }
3457  $text = implode( $comma, $list ) . $and . $space . $text;
3458  }
3459  return $text;
3460  }
3461 
3468  function commaList( array $list ) {
3469  return implode(
3470  wfMessage( 'comma-separator' )->inLanguage( $this )->escaped(),
3471  $list
3472  );
3473  }
3474 
3481  function semicolonList( array $list ) {
3482  return implode(
3483  wfMessage( 'semicolon-separator' )->inLanguage( $this )->escaped(),
3484  $list
3485  );
3486  }
3487 
3493  function pipeList( array $list ) {
3494  return implode(
3495  wfMessage( 'pipe-separator' )->inLanguage( $this )->escaped(),
3496  $list
3497  );
3498  }
3499 
3515  function truncateForDatabase( $string, $length, $ellipsis = '...', $adjustLength = true ) {
3516  return $this->truncateInternal(
3517  $string, $length, $ellipsis, $adjustLength, 'strlen', 'substr'
3518  );
3519  }
3520 
3539  function truncateForVisual( $string, $length, $ellipsis = '...', $adjustLength = true ) {
3540  // Passing encoding to mb_strlen and mb_substr is optional.
3541  // Encoding defaults to mb_internal_encoding(), which is set to UTF-8 in Setup.php, so
3542  // explicit specification of encoding is skipped.
3543  // Note: Both multibyte methods are callables invoked in truncateInternal.
3544  return $this->truncateInternal(
3545  $string, $length, $ellipsis, $adjustLength, 'mb_strlen', 'mb_substr'
3546  );
3547  }
3548 
3565  private function truncateInternal(
3566  $string, $length, $ellipsis, $adjustLength, $measureLength, $getSubstring
3567  ) {
3568  if ( !is_callable( $measureLength ) || !is_callable( $getSubstring ) ) {
3569  throw new InvalidArgumentException( 'Invalid callback provided' );
3570  }
3571 
3572  # Check if there is no need to truncate
3573  if ( $measureLength( $string ) <= abs( $length ) ) {
3574  return $string; // no need to truncate
3575  }
3576 
3577  # Use the localized ellipsis character
3578  if ( $ellipsis == '...' ) {
3579  $ellipsis = wfMessage( 'ellipsis' )->inLanguage( $this )->escaped();
3580  }
3581  if ( $length == 0 ) {
3582  return $ellipsis; // convention
3583  }
3584 
3585  $stringOriginal = $string;
3586  # If ellipsis length is >= $length then we can't apply $adjustLength
3587  if ( $adjustLength && $measureLength( $ellipsis ) >= abs( $length ) ) {
3588  $string = $ellipsis; // this can be slightly unexpected
3589  # Otherwise, truncate and add ellipsis...
3590  } else {
3591  $ellipsisLength = $adjustLength ? $measureLength( $ellipsis ) : 0;
3592  if ( $length > 0 ) {
3593  $length -= $ellipsisLength;
3594  $string = $getSubstring( $string, 0, $length ); // xyz...
3595  $string = $this->removeBadCharLast( $string );
3596  $string = rtrim( $string );
3597  $string = $string . $ellipsis;
3598  } else {
3599  $length += $ellipsisLength;
3600  $string = $getSubstring( $string, $length ); // ...xyz
3601  $string = $this->removeBadCharFirst( $string );
3602  $string = ltrim( $string );
3603  $string = $ellipsis . $string;
3604  }
3605  }
3606 
3607  # Do not truncate if the ellipsis makes the string longer/equal (T24181).
3608  # This check is *not* redundant if $adjustLength, due to the single case where
3609  # LEN($ellipsis) > ABS($limit arg); $stringOriginal could be shorter than $string.
3610  if ( $measureLength( $string ) < $measureLength( $stringOriginal ) ) {
3611  return $string;
3612  } else {
3613  return $stringOriginal;
3614  }
3615  }
3616 
3624  protected function removeBadCharLast( $string ) {
3625  if ( $string != '' ) {
3626  $char = ord( $string[strlen( $string ) - 1] );
3627  $m = [];
3628  if ( $char >= 0xc0 ) {
3629  # We got the first byte only of a multibyte char; remove it.
3630  $string = substr( $string, 0, -1 );
3631  } elseif ( $char >= 0x80 &&
3632  // Use the /s modifier (PCRE_DOTALL) so (.*) also matches newlines
3633  preg_match( '/^(.*)(?:[\xe0-\xef][\x80-\xbf]|' .
3634  '[\xf0-\xf7][\x80-\xbf]{1,2})$/s', $string, $m )
3635  ) {
3636  # We chopped in the middle of a character; remove it
3637  $string = $m[1];
3638  }
3639  }
3640  return $string;
3641  }
3642 
3650  protected function removeBadCharFirst( $string ) {
3651  if ( $string != '' ) {
3652  $char = ord( $string[0] );
3653  if ( $char >= 0x80 && $char < 0xc0 ) {
3654  # We chopped in the middle of a character; remove the whole thing
3655  $string = preg_replace( '/^[\x80-\xbf]+/', '', $string );
3656  }
3657  }
3658  return $string;
3659  }
3660 
3676  function truncateHtml( $text, $length, $ellipsis = '...' ) {
3677  # Use the localized ellipsis character
3678  if ( $ellipsis == '...' ) {
3679  $ellipsis = wfMessage( 'ellipsis' )->inLanguage( $this )->escaped();
3680  }
3681  # Check if there is clearly no need to truncate
3682  if ( $length <= 0 ) {
3683  return $ellipsis; // no text shown, nothing to format (convention)
3684  } elseif ( strlen( $text ) <= $length ) {
3685  return $text; // string short enough even *with* HTML (short-circuit)
3686  }
3687 
3688  $dispLen = 0; // innerHTML legth so far
3689  $testingEllipsis = false; // checking if ellipses will make string longer/equal?
3690  $tagType = 0; // 0-open, 1-close
3691  $bracketState = 0; // 1-tag start, 2-tag name, 0-neither
3692  $entityState = 0; // 0-not entity, 1-entity
3693  $tag = $ret = ''; // accumulated tag name, accumulated result string
3694  $openTags = []; // open tag stack
3695  $maybeState = null; // possible truncation state
3696 
3697  $textLen = strlen( $text );
3698  $neLength = max( 0, $length - strlen( $ellipsis ) ); // non-ellipsis len if truncated
3699  for ( $pos = 0; true; ++$pos ) {
3700  # Consider truncation once the display length has reached the maximim.
3701  # We check if $dispLen > 0 to grab tags for the $neLength = 0 case.
3702  # Check that we're not in the middle of a bracket/entity...
3703  if ( $dispLen && $dispLen >= $neLength && $bracketState == 0 && !$entityState ) {
3704  if ( !$testingEllipsis ) {
3705  $testingEllipsis = true;
3706  # Save where we are; we will truncate here unless there turn out to
3707  # be so few remaining characters that truncation is not necessary.
3708  if ( !$maybeState ) { // already saved? ($neLength = 0 case)
3709  $maybeState = [ $ret, $openTags ]; // save state
3710  }
3711  } elseif ( $dispLen > $length && $dispLen > strlen( $ellipsis ) ) {
3712  # String in fact does need truncation, the truncation point was OK.
3713  list( $ret, $openTags ) = $maybeState; // reload state
3714  $ret = $this->removeBadCharLast( $ret ); // multi-byte char fix
3715  $ret .= $ellipsis; // add ellipsis
3716  break;
3717  }
3718  }
3719  if ( $pos >= $textLen ) {
3720  break; // extra iteration just for above checks
3721  }
3722 
3723  # Read the next char...
3724  $ch = $text[$pos];
3725  $lastCh = $pos ? $text[$pos - 1] : '';
3726  $ret .= $ch; // add to result string
3727  if ( $ch == '<' ) {
3728  $this->truncate_endBracket( $tag, $tagType, $lastCh, $openTags ); // for bad HTML
3729  $entityState = 0; // for bad HTML
3730  $bracketState = 1; // tag started (checking for backslash)
3731  } elseif ( $ch == '>' ) {
3732  $this->truncate_endBracket( $tag, $tagType, $lastCh, $openTags );
3733  $entityState = 0; // for bad HTML
3734  $bracketState = 0; // out of brackets
3735  } elseif ( $bracketState == 1 ) {
3736  if ( $ch == '/' ) {
3737  $tagType = 1; // close tag (e.g. "</span>")
3738  } else {
3739  $tagType = 0; // open tag (e.g. "<span>")
3740  $tag .= $ch;
3741  }
3742  $bracketState = 2; // building tag name
3743  } elseif ( $bracketState == 2 ) {
3744  if ( $ch != ' ' ) {
3745  $tag .= $ch;
3746  } else {
3747  // Name found (e.g. "<a href=..."), add on tag attributes...
3748  $pos += $this->truncate_skip( $ret, $text, "<>", $pos + 1 );
3749  }
3750  } elseif ( $bracketState == 0 ) {
3751  if ( $entityState ) {
3752  if ( $ch == ';' ) {
3753  $entityState = 0;
3754  $dispLen++; // entity is one displayed char
3755  }
3756  } else {
3757  if ( $neLength == 0 && !$maybeState ) {
3758  // Save state without $ch. We want to *hit* the first
3759  // display char (to get tags) but not *use* it if truncating.
3760  $maybeState = [ substr( $ret, 0, -1 ), $openTags ];
3761  }
3762  if ( $ch == '&' ) {
3763  $entityState = 1; // entity found, (e.g. "&#160;")
3764  } else {
3765  $dispLen++; // this char is displayed
3766  // Add the next $max display text chars after this in one swoop...
3767  $max = ( $testingEllipsis ? $length : $neLength ) - $dispLen;
3768  $skipped = $this->truncate_skip( $ret, $text, "<>&", $pos + 1, $max );
3769  $dispLen += $skipped;
3770  $pos += $skipped;
3771  }
3772  }
3773  }
3774  }
3775  // Close the last tag if left unclosed by bad HTML
3776  $this->truncate_endBracket( $tag, $text[$textLen - 1], $tagType, $openTags );
3777  while ( count( $openTags ) > 0 ) {
3778  $ret .= '</' . array_pop( $openTags ) . '>'; // close open tags
3779  }
3780  return $ret;
3781  }
3782 
3794  private function truncate_skip( &$ret, $text, $search, $start, $len = null ) {
3795  if ( $len === null ) {
3796  $len = -1; // -1 means "no limit" for strcspn
3797  } elseif ( $len < 0 ) {
3798  $len = 0; // sanity
3799  }
3800  $skipCount = 0;
3801  if ( $start < strlen( $text ) ) {
3802  $skipCount = strcspn( $text, $search, $start, $len );
3803  $ret .= substr( $text, $start, $skipCount );
3804  }
3805  return $skipCount;
3806  }
3807 
3817  private function truncate_endBracket( &$tag, $tagType, $lastCh, &$openTags ) {
3818  $tag = ltrim( $tag );
3819  if ( $tag != '' ) {
3820  if ( $tagType == 0 && $lastCh != '/' ) {
3821  $openTags[] = $tag; // tag opened (didn't close itself)
3822  } elseif ( $tagType == 1 ) {
3823  if ( $openTags && $tag == $openTags[count( $openTags ) - 1] ) {
3824  array_pop( $openTags ); // tag closed
3825  }
3826  }
3827  $tag = '';
3828  }
3829  }
3830 
3839  function convertGrammar( $word, $case ) {
3840  global $wgGrammarForms;
3841  if ( isset( $wgGrammarForms[$this->getCode()][$case][$word] ) ) {
3842  return $wgGrammarForms[$this->getCode()][$case][$word];
3843  }
3844 
3845  $grammarTransformations = $this->getGrammarTransformations();
3846 
3847  if ( isset( $grammarTransformations[$case] ) ) {
3848  $forms = $grammarTransformations[$case];
3849 
3850  // Some names of grammar rules are aliases for other rules.
3851  // In such cases the value is a string rather than object,
3852  // so load the actual rules.
3853  if ( is_string( $forms ) ) {
3854  $forms = $grammarTransformations[$forms];
3855  }
3856 
3857  foreach ( array_values( $forms ) as $rule ) {
3858  $form = $rule[0];
3859 
3860  if ( $form === '@metadata' ) {
3861  continue;
3862  }
3863 
3864  $replacement = $rule[1];
3865 
3866  $regex = '/' . addcslashes( $form, '/' ) . '/u';
3867  $patternMatches = preg_match( $regex, $word );
3868 
3869  if ( $patternMatches === false ) {
3870  wfLogWarning(
3871  'An error occurred while processing grammar. ' .
3872  "Word: '$word'. Regex: /$form/."
3873  );
3874  } elseif ( $patternMatches === 1 ) {
3875  $word = preg_replace( $regex, $replacement, $word );
3876 
3877  break;
3878  }
3879  }
3880  }
3881 
3882  return $word;
3883  }
3884 
3890  function getGrammarForms() {
3891  global $wgGrammarForms;
3892  if ( isset( $wgGrammarForms[$this->getCode()] )
3893  && is_array( $wgGrammarForms[$this->getCode()] )
3894  ) {
3895  return $wgGrammarForms[$this->getCode()];
3896  }
3897 
3898  return [];
3899  }
3900 
3910  public function getGrammarTransformations() {
3911  $languageCode = $this->getCode();
3912 
3913  if ( self::$grammarTransformations === null ) {
3914  self::$grammarTransformations = new MapCacheLRU( 10 );
3915  }
3916 
3917  if ( self::$grammarTransformations->has( $languageCode ) ) {
3918  return self::$grammarTransformations->get( $languageCode );
3919  }
3920 
3921  $data = [];
3922 
3923  $grammarDataFile = __DIR__ . "/data/grammarTransformations/$languageCode.json";
3924  if ( is_readable( $grammarDataFile ) ) {
3925  $data = FormatJson::decode(
3926  file_get_contents( $grammarDataFile ),
3927  true
3928  );
3929 
3930  if ( $data === null ) {
3931  throw new MWException( "Invalid grammar data for \"$languageCode\"." );
3932  }
3933 
3934  self::$grammarTransformations->set( $languageCode, $data );
3935  }
3936 
3937  return $data;
3938  }
3939 
3959  function gender( $gender, $forms ) {
3960  if ( !count( $forms ) ) {
3961  return '';
3962  }
3963  $forms = $this->preConvertPlural( $forms, 2 );
3964  if ( $gender === 'male' ) {
3965  return $forms[0];
3966  }
3967  if ( $gender === 'female' ) {
3968  return $forms[1];
3969  }
3970  return $forms[2] ?? $forms[0];
3971  }
3972 
3988  function convertPlural( $count, $forms ) {
3989  // Handle explicit n=pluralform cases
3990  $forms = $this->handleExplicitPluralForms( $count, $forms );
3991  if ( is_string( $forms ) ) {
3992  return $forms;
3993  }
3994  if ( !count( $forms ) ) {
3995  return '';
3996  }
3997 
3998  $pluralForm = $this->getPluralRuleIndexNumber( $count );
3999  $pluralForm = min( $pluralForm, count( $forms ) - 1 );
4000  return $forms[$pluralForm];
4001  }
4002 
4018  protected function handleExplicitPluralForms( $count, array $forms ) {
4019  foreach ( $forms as $index => $form ) {
4020  if ( preg_match( '/\d+=/i', $form ) ) {
4021  $pos = strpos( $form, '=' );
4022  if ( substr( $form, 0, $pos ) === (string)$count ) {
4023  return substr( $form, $pos + 1 );
4024  }
4025  unset( $forms[$index] );
4026  }
4027  }
4028  return array_values( $forms );
4029  }
4030 
4039  protected function preConvertPlural( /* Array */ $forms, $count ) {
4040  while ( count( $forms ) < $count ) {
4041  $forms[] = $forms[count( $forms ) - 1];
4042  }
4043  return $forms;
4044  }
4045 
4062  public function embedBidi( $text = '' ) {
4063  $dir = self::strongDirFromContent( $text );
4064  if ( $dir === 'ltr' ) {
4065  // Wrap in LEFT-TO-RIGHT EMBEDDING ... POP DIRECTIONAL FORMATTING
4066  return self::$lre . $text . self::$pdf;
4067  }
4068  if ( $dir === 'rtl' ) {
4069  // Wrap in RIGHT-TO-LEFT EMBEDDING ... POP DIRECTIONAL FORMATTING
4070  return self::$rle . $text . self::$pdf;
4071  }
4072  // No strong directionality: do not wrap
4073  return $text;
4074  }
4075 
4089  function translateBlockExpiry( $str, User $user = null, $now = 0 ) {
4090  $duration = SpecialBlock::getSuggestedDurations( $this );
4091  foreach ( $duration as $show => $value ) {
4092  if ( strcmp( $str, $value ) == 0 ) {
4093  return htmlspecialchars( trim( $show ) );
4094  }
4095  }
4096 
4097  if ( wfIsInfinity( $str ) ) {
4098  foreach ( $duration as $show => $value ) {
4099  if ( wfIsInfinity( $value ) ) {
4100  return htmlspecialchars( trim( $show ) );
4101  }
4102  }
4103  }
4104 
4105  // If all else fails, return a standard duration or timestamp description.
4106  $time = strtotime( $str, $now );
4107  if ( $time === false ) { // Unknown format. Return it as-is in case.
4108  return $str;
4109  } elseif ( $time !== strtotime( $str, $now + 1 ) ) { // It's a relative timestamp.
4110  // The result differs based on current time, so the difference
4111  // is a fixed duration length.
4112  return $this->formatDuration( $time - $now );
4113  } else { // It's an absolute timestamp.
4114  if ( $time === 0 ) {
4115  // wfTimestamp() handles 0 as current time instead of epoch.
4116  $time = '19700101000000';
4117  }
4118  if ( $user ) {
4119  return $this->userTimeAndDate( $time, $user );
4120  }
4121  return $this->timeanddate( $time );
4122  }
4123  }
4124 
4132  public function segmentForDiff( $text ) {
4133  return $text;
4134  }
4135 
4142  public function unsegmentForDiff( $text ) {
4143  return $text;
4144  }
4145 
4152  public function getConverter() {
4153  return $this->mConverter;
4154  }
4155 
4164  public function autoConvert( $text, $variant = false ) {
4165  return $this->mConverter->autoConvert( $text, $variant );
4166  }
4167 
4174  public function autoConvertToAllVariants( $text ) {
4175  return $this->mConverter->autoConvertToAllVariants( $text );
4176  }
4177 
4189  public function convert( $text ) {
4190  return $this->mConverter->convert( $text );
4191  }
4192 
4199  public function convertTitle( $title ) {
4200  return $this->mConverter->convertTitle( $title );
4201  }
4202 
4211  public function convertNamespace( $ns, $variant = null ) {
4212  return $this->mConverter->convertNamespace( $ns, $variant );
4213  }
4214 
4220  public function hasVariants() {
4221  return count( $this->getVariants() ) > 1;
4222  }
4223 
4234  public function hasVariant( $variant ) {
4235  return $variant && ( $variant === $this->mConverter->validateVariant( $variant ) );
4236  }
4237 
4244  public function convertHtml( $text ) {
4245  return htmlspecialchars( $this->convert( $text ) );
4246  }
4247 
4252  public function convertCategoryKey( $key ) {
4253  return $this->mConverter->convertCategoryKey( $key );
4254  }
4255 
4262  public function getVariants() {
4263  return $this->mConverter->getVariants();
4264  }
4265 
4269  public function getPreferredVariant() {
4270  return $this->mConverter->getPreferredVariant();
4271  }
4272 
4276  public function getDefaultVariant() {
4277  return $this->mConverter->getDefaultVariant();
4278  }
4279 
4283  public function getURLVariant() {
4284  return $this->mConverter->getURLVariant();
4285  }
4286 
4299  public function findVariantLink( &$link, &$nt, $ignoreOtherCond = false ) {
4300  $this->mConverter->findVariantLink( $link, $nt, $ignoreOtherCond );
4301  }
4302 
4309  function getExtraHashOptions() {
4310  return $this->mConverter->getExtraHashOptions();
4311  }
4312 
4320  public function getParsedTitle() {
4321  return $this->mConverter->getParsedTitle();
4322  }
4323 
4330  public function updateConversionTable( Title $title ) {
4331  $this->mConverter->updateConversionTable( $title );
4332  }
4333 
4350  public function markNoConversion( $text, $noParse = false ) {
4351  wfDeprecated( __METHOD__, '1.32' );
4352  // Excluding protocal-relative URLs may avoid many false positives.
4353  if ( $noParse || preg_match( '/^(?:' . wfUrlProtocolsWithoutProtRel() . ')/', $text ) ) {
4354  return $this->mConverter->markNoConversion( $text );
4355  } else {
4356  return $text;
4357  }
4358  }
4359 
4366  public function linkTrail() {
4367  return self::$dataCache->getItem( $this->mCode, 'linkTrail' );
4368  }
4369 
4376  public function linkPrefixCharset() {
4377  return self::$dataCache->getItem( $this->mCode, 'linkPrefixCharset' );
4378  }
4379 
4387  public function getParentLanguage() {
4388  if ( $this->mParentLanguage !== false ) {
4389  return $this->mParentLanguage;
4390  }
4391 
4392  $code = explode( '-', $this->getCode() )[0];
4393  if ( !in_array( $code, LanguageConverter::$languagesWithVariants ) ) {
4394  $this->mParentLanguage = null;
4395  return null;
4396  }
4397  $lang = self::factory( $code );
4398  if ( !$lang->hasVariant( $this->getCode() ) ) {
4399  $this->mParentLanguage = null;
4400  return null;
4401  }
4402 
4403  $this->mParentLanguage = $lang;
4404  return $lang;
4405  }
4406 
4414  public function equals( Language $lang ) {
4415  return $lang === $this || $lang->getCode() === $this->mCode;
4416  }
4417 
4426  public function getCode() {
4427  return $this->mCode;
4428  }
4429 
4440  public function getHtmlCode() {
4441  if ( is_null( $this->mHtmlCode ) ) {
4442  $this->mHtmlCode = LanguageCode::bcp47( $this->getCode() );
4443  }
4444  return $this->mHtmlCode;
4445  }
4446 
4451  public function setCode( $code ) {
4452  $this->mCode = $code;
4453  // Ensure we don't leave incorrect cached data lying around
4454  $this->mHtmlCode = null;
4455  $this->mParentLanguage = false;
4456  }
4457 
4465  public static function getCodeFromFileName( $filename, $prefix = 'Language', $suffix = '.php' ) {
4466  $m = null;
4467  preg_match( '/' . preg_quote( $prefix, '/' ) . '([A-Z][a-z_]+)' .
4468  preg_quote( $suffix, '/' ) . '/', $filename, $m );
4469  if ( !count( $m ) ) {
4470  return false;
4471  }
4472  return str_replace( '_', '-', strtolower( $m[1] ) );
4473  }
4474 
4480  public static function classFromCode( $code, $fallback = true ) {
4481  if ( $fallback && $code == 'en' ) {
4482  return 'Language';
4483  } else {
4484  return 'Language' . str_replace( '-', '_', ucfirst( $code ) );
4485  }
4486  }
4487 
4496  public static function getFileName( $prefix, $code, $suffix = '.php' ) {
4497  if ( !self::isValidBuiltInCode( $code ) ) {
4498  throw new MWException( "Invalid language code \"$code\"" );
4499  }
4500 
4501  return $prefix . str_replace( '-', '_', ucfirst( $code ) ) . $suffix;
4502  }
4503 
4508  public static function getMessagesFileName( $code ) {
4509  global $IP;
4510  $file = self::getFileName( "$IP/languages/messages/Messages", $code, '.php' );
4511  Hooks::run( 'Language::getMessagesFileName', [ $code, &$file ] );
4512  return $file;
4513  }
4514 
4521  public static function getJsonMessagesFileName( $code ) {
4522  global $IP;
4523 
4524  if ( !self::isValidBuiltInCode( $code ) ) {
4525  throw new MWException( "Invalid language code \"$code\"" );
4526  }
4527 
4528  return "$IP/languages/i18n/$code.json";
4529  }
4530 
4538  public static function getFallbackFor( $code ) {
4539  $fallbacks = self::getFallbacksFor( $code );
4540  if ( $fallbacks ) {
4541  return $fallbacks[0];
4542  }
4543  return false;
4544  }
4545 
4556  public static function getFallbacksFor( $code, $mode = self::MESSAGES_FALLBACKS ) {
4557  if ( $code === 'en' || !self::isValidBuiltInCode( $code ) ) {
4558  return [];
4559  }
4560  switch ( $mode ) {
4561  case self::MESSAGES_FALLBACKS:
4562  // For unknown languages, fallbackSequence returns an empty array,
4563  // hardcode fallback to 'en' in that case as English messages are
4564  // always defined.
4565  return self::getLocalisationCache()->getItem( $code, 'fallbackSequence' ) ?: [ 'en' ];
4566  case self::STRICT_FALLBACKS:
4567  // Use this mode when you don't want to fallback to English unless
4568  // explicitly defined, for example when you have language-variant icons
4569  // and an international language-independent fallback.
4570  return self::getLocalisationCache()->getItem( $code, 'originalFallbackSequence' );
4571  default:
4572  throw new MWException( "Invalid fallback mode \"$mode\"" );
4573  }
4574  }
4575 
4584  public static function getFallbacksIncludingSiteLanguage( $code ) {
4585  global $wgLanguageCode;
4586 
4587  // Usually, we will only store a tiny number of fallback chains, so we
4588  // keep them in static memory.
4589  $cacheKey = "{$code}-{$wgLanguageCode}";
4590 
4591  if ( !array_key_exists( $cacheKey, self::$fallbackLanguageCache ) ) {
4592  $fallbacks = self::getFallbacksFor( $code );
4593 
4594  // Append the site's fallback chain, including the site language itself
4595  $siteFallbacks = self::getFallbacksFor( $wgLanguageCode );
4596  array_unshift( $siteFallbacks, $wgLanguageCode );
4597 
4598  // Eliminate any languages already included in the chain
4599  $siteFallbacks = array_diff( $siteFallbacks, $fallbacks );
4600 
4601  self::$fallbackLanguageCache[$cacheKey] = [ $fallbacks, $siteFallbacks ];
4602  }
4603  return self::$fallbackLanguageCache[$cacheKey];
4604  }
4605 
4615  public static function getMessagesFor( $code ) {
4616  return self::getLocalisationCache()->getItem( $code, 'messages' );
4617  }
4618 
4627  public static function getMessageFor( $key, $code ) {
4628  return self::getLocalisationCache()->getSubitem( $code, 'messages', $key );
4629  }
4630 
4639  public static function getMessageKeysFor( $code ) {
4640  return self::getLocalisationCache()->getSubitemList( $code, 'messages' );
4641  }
4642 
4647  function fixVariableInNamespace( $talk ) {
4648  if ( strpos( $talk, '$1' ) === false ) {
4649  return $talk;
4650  }
4651 
4652  global $wgMetaNamespace;
4653  $talk = str_replace( '$1', $wgMetaNamespace, $talk );
4654 
4655  # Allow grammar transformations
4656  # Allowing full message-style parsing would make simple requests
4657  # such as action=raw much more expensive than they need to be.
4658  # This will hopefully cover most cases.
4659  $talk = preg_replace_callback( '/{{grammar:(.*?)\|(.*?)}}/i',
4660  [ $this, 'replaceGrammarInNamespace' ], $talk );
4661  return str_replace( ' ', '_', $talk );
4662  }
4663 
4668  function replaceGrammarInNamespace( $m ) {
4669  return $this->convertGrammar( trim( $m[2] ), trim( $m[1] ) );
4670  }
4671 
4682  public function formatExpiry( $expiry, $format = true, $infinity = 'infinity' ) {
4683  static $dbInfinity;
4684  if ( $dbInfinity === null ) {
4685  $dbInfinity = wfGetDB( DB_REPLICA )->getInfinity();
4686  }
4687 
4688  if ( $expiry == '' || $expiry === 'infinity' || $expiry == $dbInfinity ) {
4689  return $format === true
4690  ? $this->getMessageFromDB( 'infiniteblock' )
4691  : $infinity;
4692  } else {
4693  return $format === true
4694  ? $this->timeanddate( $expiry, /* User preference timezone */ true )
4695  : wfTimestamp( $format, $expiry );
4696  }
4697  }
4698 
4712  function formatTimePeriod( $seconds, $format = [] ) {
4713  if ( !is_array( $format ) ) {
4714  $format = [ 'avoid' => $format ]; // For backwards compatibility
4715  }
4716  if ( !isset( $format['avoid'] ) ) {
4717  $format['avoid'] = false;
4718  }
4719  if ( !isset( $format['noabbrevs'] ) ) {
4720  $format['noabbrevs'] = false;
4721  }
4722  $secondsMsg = wfMessage(
4723  $format['noabbrevs'] ? 'seconds' : 'seconds-abbrev' )->inLanguage( $this );
4724  $minutesMsg = wfMessage(
4725  $format['noabbrevs'] ? 'minutes' : 'minutes-abbrev' )->inLanguage( $this );
4726  $hoursMsg = wfMessage(
4727  $format['noabbrevs'] ? 'hours' : 'hours-abbrev' )->inLanguage( $this );
4728  $daysMsg = wfMessage(
4729  $format['noabbrevs'] ? 'days' : 'days-abbrev' )->inLanguage( $this );
4730 
4731  if ( round( $seconds * 10 ) < 100 ) {
4732  $s = $this->formatNum( sprintf( "%.1f", round( $seconds * 10 ) / 10 ) );
4733  $s = $secondsMsg->params( $s )->text();
4734  } elseif ( round( $seconds ) < 60 ) {
4735  $s = $this->formatNum( round( $seconds ) );
4736  $s = $secondsMsg->params( $s )->text();
4737  } elseif ( round( $seconds ) < 3600 ) {
4738  $minutes = floor( $seconds / 60 );
4739  $secondsPart = round( fmod( $seconds, 60 ) );
4740  if ( $secondsPart == 60 ) {
4741  $secondsPart = 0;
4742  $minutes++;
4743  }
4744  $s = $minutesMsg->params( $this->formatNum( $minutes ) )->text();
4745  $s .= ' ';
4746  $s .= $secondsMsg->params( $this->formatNum( $secondsPart ) )->text();
4747  } elseif ( round( $seconds ) <= 2 * 86400 ) {
4748  $hours = floor( $seconds / 3600 );
4749  $minutes = floor( ( $seconds - $hours * 3600 ) / 60 );
4750  $secondsPart = round( $seconds - $hours * 3600 - $minutes * 60 );
4751  if ( $secondsPart == 60 ) {
4752  $secondsPart = 0;
4753  $minutes++;
4754  }
4755  if ( $minutes == 60 ) {
4756  $minutes = 0;
4757  $hours++;
4758  }
4759  $s = $hoursMsg->params( $this->formatNum( $hours ) )->text();
4760  $s .= ' ';
4761  $s .= $minutesMsg->params( $this->formatNum( $minutes ) )->text();
4762  if ( !in_array( $format['avoid'], [ 'avoidseconds', 'avoidminutes' ] ) ) {
4763  $s .= ' ' . $secondsMsg->params( $this->formatNum( $secondsPart ) )->text();
4764  }
4765  } else {
4766  $days = floor( $seconds / 86400 );
4767  if ( $format['avoid'] === 'avoidminutes' ) {
4768  $hours = round( ( $seconds - $days * 86400 ) / 3600 );
4769  if ( $hours == 24 ) {
4770  $hours = 0;
4771  $days++;
4772  }
4773  $s = $daysMsg->params( $this->formatNum( $days ) )->text();
4774  $s .= ' ';
4775  $s .= $hoursMsg->params( $this->formatNum( $hours ) )->text();
4776  } elseif ( $format['avoid'] === 'avoidseconds' ) {
4777  $hours = floor( ( $seconds - $days * 86400 ) / 3600 );
4778  $minutes = round( ( $seconds - $days * 86400 - $hours * 3600 ) / 60 );
4779  if ( $minutes == 60 ) {
4780  $minutes = 0;
4781  $hours++;
4782  }
4783  if ( $hours == 24 ) {
4784  $hours = 0;
4785  $days++;
4786  }
4787  $s = $daysMsg->params( $this->formatNum( $days ) )->text();
4788  $s .= ' ';
4789  $s .= $hoursMsg->params( $this->formatNum( $hours ) )->text();
4790  $s .= ' ';
4791  $s .= $minutesMsg->params( $this->formatNum( $minutes ) )->text();
4792  } else {
4793  $s = $daysMsg->params( $this->formatNum( $days ) )->text();
4794  $s .= ' ';
4795  $s .= $this->formatTimePeriod( $seconds - $days * 86400, $format );
4796  }
4797  }
4798  return $s;
4799  }
4800 
4812  function formatBitrate( $bps ) {
4813  return $this->formatComputingNumbers( $bps, 1000, "bitrate-$1bits" );
4814  }
4815 
4822  function formatComputingNumbers( $size, $boundary, $messageKey ) {
4823  if ( $size <= 0 ) {
4824  return str_replace( '$1', $this->formatNum( $size ),
4825  $this->getMessageFromDB( str_replace( '$1', '', $messageKey ) )
4826  );
4827  }
4828  $sizes = [ '', 'kilo', 'mega', 'giga', 'tera', 'peta', 'exa', 'zeta', 'yotta' ];
4829  $index = 0;
4830 
4831  $maxIndex = count( $sizes ) - 1;
4832  while ( $size >= $boundary && $index < $maxIndex ) {
4833  $index++;
4834  $size /= $boundary;
4835  }
4836 
4837  // For small sizes no decimal places necessary
4838  $round = 0;
4839  if ( $index > 1 ) {
4840  // For MB and bigger two decimal places are smarter
4841  $round = 2;
4842  }
4843  $msg = str_replace( '$1', $sizes[$index], $messageKey );
4844 
4845  $size = round( $size, $round );
4846  $text = $this->getMessageFromDB( $msg );
4847  return str_replace( '$1', $this->formatNum( $size ), $text );
4848  }
4849 
4860  function formatSize( $size ) {
4861  return $this->formatComputingNumbers( $size, 1024, "size-$1bytes" );
4862  }
4863 
4873  function specialList( $page, $details, $oppositedm = true ) {
4874  if ( !$details ) {
4875  return $page;
4876  }
4877 
4878  $dirmark = ( $oppositedm ? $this->getDirMark( true ) : '' ) . $this->getDirMark();
4879  return $page .
4880  $dirmark .
4881  $this->msg( 'word-separator' )->escaped() .
4882  $this->msg( 'parentheses' )->rawParams( $details )->escaped();
4883  }
4884 
4895  public function viewPrevNext( Title $title, $offset, $limit,
4896  array $query = [], $atend = false
4897  ) {
4898  // @todo FIXME: Why on earth this needs one message for the text and another one for tooltip?
4899 
4900  # Make 'previous' link
4901  $prev = wfMessage( 'prevn' )->inLanguage( $this )->title( $title )->numParams( $limit )->text();
4902  if ( $offset > 0 ) {
4903  $plink = $this->numLink( $title, max( $offset - $limit, 0 ), $limit,
4904  $query, $prev, 'prevn-title', 'mw-prevlink' );
4905  } else {
4906  $plink = htmlspecialchars( $prev );
4907  }
4908 
4909  # Make 'next' link
4910  $next = wfMessage( 'nextn' )->inLanguage( $this )->title( $title )->numParams( $limit )->text();
4911  if ( $atend ) {
4912  $nlink = htmlspecialchars( $next );
4913  } else {
4914  $nlink = $this->numLink( $title, $offset + $limit, $limit,
4915  $query, $next, 'nextn-title', 'mw-nextlink' );
4916  }
4917 
4918  # Make links to set number of items per page
4919  $numLinks = [];
4920  foreach ( [ 20, 50, 100, 250, 500 ] as $num ) {
4921  $numLinks[] = $this->numLink( $title, $offset, $num,
4922  $query, $this->formatNum( $num ), 'shown-title', 'mw-numlink' );
4923  }
4924 
4925  return wfMessage( 'viewprevnext' )->inLanguage( $this )->title( $title
4926  )->rawParams( $plink, $nlink, $this->pipeList( $numLinks ) )->escaped();
4927  }
4928 
4941  private function numLink( Title $title, $offset, $limit, array $query, $link,
4942  $tooltipMsg, $class
4943  ) {
4944  $query = [ 'limit' => $limit, 'offset' => $offset ] + $query;
4945  $tooltip = wfMessage( $tooltipMsg )->inLanguage( $this )->title( $title )
4946  ->numParams( $limit )->text();
4947 
4948  return Html::element( 'a', [ 'href' => $title->getLocalURL( $query ),
4949  'title' => $tooltip, 'class' => $class ], $link );
4950  }
4951 
4957  public function getConvRuleTitle() {
4958  return $this->mConverter->getConvRuleTitle();
4959  }
4960 
4966  public function getCompiledPluralRules() {
4967  $pluralRules = self::$dataCache->getItem( strtolower( $this->mCode ), 'compiledPluralRules' );
4968  $fallbacks = self::getFallbacksFor( $this->mCode );
4969  if ( !$pluralRules ) {
4970  foreach ( $fallbacks as $fallbackCode ) {
4971  $pluralRules = self::$dataCache->getItem( strtolower( $fallbackCode ), 'compiledPluralRules' );
4972  if ( $pluralRules ) {
4973  break;
4974  }
4975  }
4976  }
4977  return $pluralRules;
4978  }
4979 
4985  public function getPluralRules() {
4986  $pluralRules = self::$dataCache->getItem( strtolower( $this->mCode ), 'pluralRules' );
4987  $fallbacks = self::getFallbacksFor( $this->mCode );
4988  if ( !$pluralRules ) {
4989  foreach ( $fallbacks as $fallbackCode ) {
4990  $pluralRules = self::$dataCache->getItem( strtolower( $fallbackCode ), 'pluralRules' );
4991  if ( $pluralRules ) {
4992  break;
4993  }
4994  }
4995  }
4996  return $pluralRules;
4997  }
4998 
5004  public function getPluralRuleTypes() {
5005  $pluralRuleTypes = self::$dataCache->getItem( strtolower( $this->mCode ), 'pluralRuleTypes' );
5006  $fallbacks = self::getFallbacksFor( $this->mCode );
5007  if ( !$pluralRuleTypes ) {
5008  foreach ( $fallbacks as $fallbackCode ) {
5009  $pluralRuleTypes = self::$dataCache->getItem( strtolower( $fallbackCode ), 'pluralRuleTypes' );
5010  if ( $pluralRuleTypes ) {
5011  break;
5012  }
5013  }
5014  }
5015  return $pluralRuleTypes;
5016  }
5017 
5023  public function getPluralRuleIndexNumber( $number ) {
5024  $pluralRules = $this->getCompiledPluralRules();
5025  $form = Evaluator::evaluateCompiled( $number, $pluralRules );
5026  return $form;
5027  }
5028 
5037  public function getPluralRuleType( $number ) {
5038  $index = $this->getPluralRuleIndexNumber( $number );
5039  $pluralRuleTypes = $this->getPluralRuleTypes();
5040  return $pluralRuleTypes[$index] ?? 'other';
5041  }
5042 }
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:239
getMessage( $key)
Definition: Language.php:2639
getPluralRuleType( $number)
Find the plural rule type appropriate for the given number For example, if the language is set to Ara...
Definition: Language.php:5037
getIranianCalendarMonthName( $key)
Definition: Language.php:1036
static $mMonthAbbrevMsgs
Definition: Language.php:114
$mParentLanguage
Definition: Language.php:62
getSpecialPageAliases()
Get special page names, as an associative array canonical name => array of valid names, including aliases.
Definition: Language.php:3242
getPluralRuleTypes()
Get the plural rule types for the language.
Definition: Language.php:5004
firstChar( $s)
Get the first character of a string.
Definition: Language.php:2954
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:2533
static getLocalisationCache()
Get the LocalisationCache instance.
Definition: Language.php:454
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:2779
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:2064
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub 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:785
translateBlockExpiry( $str, User $user=null, $now=0)
Definition: Language.php:4089
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:1599
ucwordbreaksCallbackAscii( $matches)
Definition: Language.php:2673
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:2889
normalize( $s)
Convert a UTF-8 string to normal form C.
Definition: Language.php:3045
handleExplicitPluralForms( $count, array $forms)
Handles explicit plural forms for Language::convertPlural()
Definition: Language.php:4018
static getCanonicalNamespaces( $rebuild=false)
Returns array of all defined namespaces with their canonical (English) names.
static getTitleInvalidRegex()
Returns a simple regex that will match on characters and sequences invalid in titles.
static LocalisationCache $dataCache
Definition: Language.php:79
initEncoding()
Definition: Language.php:3009
getDurationIntervals( $seconds, array $chosenIntervals=[])
Takes a number of seconds and returns an array with a set of corresponding intervals.
Definition: Language.php:2378
getVariantname( $code, $usemsg=true)
short names for language variants used for language conversion links.
Definition: Language.php:769
setNamespaces(array $namespaces)
Arbitrarily set all of the namespace names at once.
Definition: Language.php:552
static HashBagOStuff null $languageNameCache
Cache for language names.
Definition: Language.php:183
removeBadCharLast( $string)
Remove bytes that represent an incomplete Unicode character at the end of string (e.g.
Definition: Language.php:3624
static romanNumeral( $num)
Roman number formatting up to 10000.
Definition: Language.php:2033
getWeekdayAbbreviation( $key)
Definition: Language.php:1028
getDirMark( $opposite=false)
A hidden direction mark (LRM or RLM), depending on the language direction.
Definition: Language.php:3148
getHtmlCode()
Get the code in BCP 47 format which we can use inside of html lang="" tags.
Definition: Language.php:4440
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:3794
$IP
Definition: WebStart.php:41
equals(Language $lang)
Compare with an other language object.
Definition: Language.php:4414
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:1996
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
lcfirst( $str)
Definition: Language.php:2736
static $IRANIAN_DAYS
Definition: Language.php:1620
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException' returning false will NOT prevent logging $e
Definition: hooks.txt:2173
offsetForUser(User $user)
Adjust the timestamp depending on the given user&#39;s preferences.
Definition: MWTimestamp.php:79
getHebrewCalendarMonthNameGen( $key)
Definition: Language.php:1052
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:3116
static insertSpace( $string, $pattern)
Definition: Language.php:2932
getFallbackLanguages()
Definition: Language.php:494
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:2514
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:993
$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:69
hasVariants()
Check if this is a language with variants.
Definition: Language.php:4220
getOption( $oname, $defaultOverride=null, $ignoreHidden=false)
Get the user&#39;s current setting for a given option.
Definition: User.php:3184
getCompiledPluralRules()
Get the compiled plural rules for the language.
Definition: Language.php:4966
getLocalNsIndex( $text)
Get a namespace key by value, case insensitive.
Definition: Language.php:661
convertCategoryKey( $key)
Definition: Language.php:4252
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:1982
truncateForDatabase( $string, $length, $ellipsis='...', $adjustLength=true)
Truncate a string to a specified length in bytes, appending an optional string (e.g.
Definition: Language.php:3515
getHebrewCalendarMonthName( $key)
Definition: Language.php:1044
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:46
$wgMetaNamespace
Name of the project namespace.
fallback8bitEncoding()
Definition: Language.php:2866
wfIsInfinity( $str)
Determine input string is represents as infinity.
unsegmentForDiff( $text)
and unsegment to show the result
Definition: Language.php:4142
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:3817
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:4366
getAllMessages()
Definition: Language.php:2646
wfUrlProtocolsWithoutProtRel()
Like wfUrlProtocols(), but excludes &#39;//&#39; from the protocol list.
$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:5023
getVariants()
Get the list of variants supported by this language see sample implementation in LanguageZh.php.
Definition: Language.php:4262
A helper class for throttling authentication attempts.
$dateFormatStrings
Definition: Language.php:64
static hebrewYearStart( $year)
This calculates the Hebrew year start, as days since 1 September.
Definition: Language.php:1884
getNamespaceAliases()
Definition: Language.php:670
getWeekdayName( $key)
Definition: Language.php:1020
minimumGroupingDigits()
Definition: Language.php:3432
static $mMonthMsgs
Definition: Language.php:104
convertForSearchResult( $termsArray)
Definition: Language.php:2942
see documentation in includes Linker php for Linker::makeImageLink & $time
Definition: hooks.txt:1813
$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:4496
convertTitle( $title)
Convert a Title object to a string in the preferred variant.
Definition: Language.php:4199
static $mLangObjCache
Definition: Language.php:81
static classFromCode( $code, $fallback=true)
Definition: Language.php:4480
internalUserTimeAndDate( $type, $ts, User $user, array $options)
Internal helper function for userDate(), userTime() and userTimeAndDate()
Definition: Language.php:2429
$wgTranslateNumerals
For Hindi and Arabic use local numerals instead of Western style (0-9) numerals in interface...
getHijriCalendarMonthName( $key)
Definition: Language.php:1060
static $mWeekdayAbbrevMsgs
Definition: Language.php:100
getMonthAbbreviation( $key)
Definition: Language.php:1001
lc( $str, $first=false)
Definition: Language.php:2755
ucwordbreaksCallbackMB( $matches)
Definition: Language.php:2681
$mMagicExtensions
Definition: Language.php:61
static $pdf
Definition: Language.php:190
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:411
usually copyright or history_copyright This message must be in HTML not wikitext & $link
Definition: hooks.txt:3043
The User object encapsulates all of the user-specific settings (user_id, name, rights, email address, options, last login time).
Definition: User.php:47
__construct()
Definition: Language.php:463
truncateHtml( $text, $length, $ellipsis='...')
Truncate a string of valid HTML to a specified length in bytes, appending an optional string (e...
Definition: Language.php:3676
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:1996
ucfirst( $str)
Make a string&#39;s first character uppercase.
Definition: Language.php:2700
$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:3481
userAdjust( $ts, $tz=false)
Used by date() and time() to adjust the time output.
Definition: Language.php:2151
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:4627
static $lre
Unicode directional formatting characters, for embedBidi()
Definition: Language.php:188
formatNum( $number, $nocommafy=false)
Normally we output all numbers in plain en_US style, that is 293,291.235 for twohundredninetythreetho...
Definition: Language.php:3285
static getFallbackFor( $code)
Get the first fallback for a given language.
Definition: Language.php:4538
timeanddate( $ts, $adj=false, $format=true, $timecorrection=false)
Definition: Language.php:2333
formatExpiry( $expiry, $format=true, $infinity='infinity')
Decode an expiry (block, protection, etc) which has come from the DB.
Definition: Language.php:4682
listToText(array $list)
Take a list of strings and build a locale-friendly comma-separated list, using the local comma-separa...
Definition: Language.php:3444
static tsToYear( $ts, $cName)
Algorithm to convert Gregorian dates to Thai solar dates, Minguo dates or Minguo dates.
Definition: Language.php:1922
const NS_PROJECT
Definition: Defines.php:68
static $mHijriCalendarMonthMsgs
Definition: Language.php:142
linkPrefixCharset()
A regular expression character set to match legal word-prefixing characters which should be merged on...
Definition: Language.php:4376
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:4269
LanguageConverter $mConverter
Definition: Language.php:58
recodeForEdit( $s)
Definition: Language.php:3019
static getMessagesFor( $code)
Get all messages for a given language WARNING: this may take a long time.
Definition: Language.php:4615
$minimumGroupingDigits
removeBadCharFirst( $string)
Remove bytes that represent an incomplete Unicode character at the start of string (e...
Definition: Language.php:3650
getDirMarkEntity( $opposite=false)
A hidden direction mark (LRM or RLM), depending on the language direction.
Definition: Language.php:3131
__destruct()
Reduce memory usage.
Definition: Language.php:477
static strongDirFromContent( $text='')
Gets directionality of the first strongly directional codepoint, for embedBidi()
Definition: Language.php:2016
getMagic( $mw)
Fill a MagicWord object with data from here.
Definition: Language.php:3210
static getMessageKeysFor( $code)
Get all message keys for a given language.
Definition: Language.php:4639
$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:4584
getDefaultVariant()
Definition: Language.php:4276
getNsText( $index)
Get a namespace value by key.
Definition: Language.php:591
const STRICT_FALLBACKS
Return a strict fallback chain in getFallbacksFor.
Definition: Language.php:93
viewPrevNext(Title $title, $offset, $limit, array $query=[], $atend=false)
Generate (prev x| next x) (20|50|100...) type links for paging.
Definition: Language.php:4895
getConverter()
Return the LanguageConverter used in the Language.
Definition: Language.php:4152
static $mIranianCalendarMonthMsgs
Definition: Language.php:119
const NS_PROJECT_TALK
Definition: Defines.php:69
autoConvertToAllVariants( $text)
convert text to all supported variants
Definition: Language.php:4174
getBookstoreList()
Exports $wgBookstoreListEn.
Definition: Language.php:502
capitalizeAllNouns()
Definition: Language.php:3160
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:3259
date( $ts, $adj=false, $format=true, $timecorrection=false)
Definition: Language.php:2294
isMultibyte( $str)
Definition: Language.php:2771
segmentForDiff( $text)
languages like Chinese need to be segmented in order for the diff to be of any use ...
Definition: Language.php:4132
$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:2900
static $strongDirRegex
Directionality test regex for embedBidi().
Definition: Language.php:205
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:1996
static tsToHijri( $ts)
Converting Gregorian dates to Hijri dates.
Definition: Language.php:1694
needsGenderDistinction()
Whether this language uses gender-dependent namespace aliases.
Definition: Language.php:637
getURLVariant()
Definition: Language.php:4283
caseFold( $s)
Return a case-folded representation of $s.
Definition: Language.php:2843
updateConversionTable(Title $title)
Refresh the cache of conversion tables when MediaWiki:Conversiontable* is updated.
Definition: Language.php:4330
static $rle
Definition: Language.php:189
$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:936
static tsToIranian( $ts)
Algorithm by Roozbeh Pournader and Mohammad Toossi to convert Gregorian dates to Iranian dates...
Definition: Language.php:1634
getNamespaceIds()
Definition: Language.php:722
static factory( $code)
Get a cached or new language object for a given language code.
Definition: Language.php:214
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:2229
static $mHebrewCalendarMonthMsgs
Definition: Language.php:126
pipeList(array $list)
Same as commaList, but separate it with the pipe instead.
Definition: Language.php:3493
getGenderNsText( $index, $gender)
Returns gender-dependent namespace alias if available.
Definition: Language.php:622
getFormattedNsText( $index)
A convenience function that returns the same thing as getNsText() except with &#39;_&#39; changed to &#39; &#39;...
Definition: Language.php:609
$mExtendedSpecialPageAliases
Definition: Language.php:65
namespace and then decline to actually register it & $namespaces
Definition: hooks.txt:936
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub 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:785
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:4985
getDefaultDateFormat()
Definition: Language.php:800
embedBidi( $text='')
Wraps argument with unicode control characters for directionality safety.
Definition: Language.php:4062
resetNamespaces()
Resets all of the namespace caches.
Definition: Language.php:560
recodeInput( $s)
Definition: Language.php:3029
getDateFormatString( $type, $pref)
Get a format string for a given type and preference.
Definition: Language.php:2260
static MapCacheLRU null $grammarTransformations
Cache for grammar rules data.
Definition: Language.php:177
time( $ts, $adj=false, $format=true, $timecorrection=false)
Definition: Language.php:2313
getMagicWords()
Get all magic words from cache.
Definition: Language.php:3201
getGrammarTransformations()
Get the grammar transformations data for the language.
Definition: Language.php:3910
getMonthNamesArray()
Definition: Language.php:981
numLink(Title $title, $offset, $limit, array $query, $link, $tooltipMsg, $class)
Helper function for viewPrevNext() that generates links.
Definition: Language.php:4941
static getMessagesFileName( $code)
Definition: Language.php:4508
static getDefaultOption( $opt)
Get a given default option value.
Definition: User.php:1813
convertGrammar( $word, $case)
Grammatical transformations, needed for inflected languages Invoked by putting {{grammar:case|word}} ...
Definition: Language.php:3839
digitTransformTable()
Definition: Language.php:3418
getConvRuleTitle()
Get the conversion rule title, if any.
Definition: Language.php:4957
formatTimePeriod( $seconds, $format=[])
Formats a time given in seconds into a string representation of that time.
Definition: Language.php:4712
userDate( $ts, User $user, array $options=[])
Get the formatted date for the given timestamp and formatted for the given user.
Definition: Language.php:2468
getNsIndex( $text)
Get a namespace key by value, case insensitive.
Definition: Language.php:752
addMagicWordsByLang( $newWords)
Add magic words to the extension array.
Definition: Language.php:3227
autoConvert( $text, $variant=false)
convert text to a variant
Definition: Language.php:4164
static fetchLanguageName( $code, $inLanguage=self::AS_AUTONYMS, $include=self::ALL)
Definition: Language.php:940
static getJsonMessagesFileName( $code)
Definition: Language.php:4521
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:332
ucwordbreaks( $str)
capitalize words at word breaks
Definition: Language.php:2803
$wgExtraNamespaces
Additional namespaces.
static convertDoubleWidth( $string)
convert double-width roman characters to single-width.
Definition: Language.php:2912
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:2570
getCode()
Get the internal language code for this language object.
Definition: Language.php:4426
digitGroupingPattern()
Definition: Language.php:3411
specialList( $page, $details, $oppositedm=true)
Make a list item, used by various special pages.
Definition: Language.php:4873
static isKnownLanguageTag( $tag)
Returns true if a language code is an IETF tag known to MediaWiki.
Definition: Language.php:433
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:304
convertNamespace( $ns, $variant=null)
Convert a namespace index to a string in the preferred variant.
Definition: Language.php:4211
getArrow( $direction='forwards')
An arrow, depending on the language direction.
Definition: Language.php:3171
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:4860
convertHtml( $text)
Perform output conversion on a string, and encode for safe HTML output.
Definition: Language.php:4244
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:68
commafy( $number)
Adds commas to a given number.
Definition: Language.php:3346
$transformData
ReplacementArray object caches.
Definition: Language.php:74
static getCodeFromFileName( $filename, $prefix='Language', $suffix='.php')
Get the language code from a file name.
Definition: Language.php:4465
getExtraHashOptions()
returns language specific options used by User::getPageRenderHash() for example, the preferred langua...
Definition: Language.php:4309
static getFallbacksFor( $code, $mode=self::MESSAGES_FALLBACKS)
Get the ordered list of fallback languages.
Definition: Language.php:4556
getParsedTitle()
For languages that support multiple variants, the title of an article may be displayed differently in...
Definition: Language.php:4320
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:1746
preConvertPlural($forms, $count)
Checks that convertPlural was given an array and pads it to requested amount of forms by copying the ...
Definition: Language.php:4039
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:4299
$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:53
hasVariant( $variant)
Strict check if the language has the specific variant.
Definition: Language.php:4234
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:4812
const AS_AUTONYMS
Return autonyms in fetchLanguageName(s).
Definition: Language.php:40
transformUsingPairFile( $file, $string)
Transform a string using serialized data stored in the given file (which must be in the serialized su...
Definition: Language.php:3070
separatorTransformTable()
Definition: Language.php:3425
isRTL()
For right-to-left language support.
Definition: Language.php:3084
getDatePreference()
Get the user&#39;s preferred date format.
Definition: User.php:3507
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:1150
getParentLanguage()
Get the "parent" language which has a converter to convert a "compatible" language (in another varian...
Definition: Language.php:4387
static clearCaches()
Intended for tests that may change configuration in a way that invalidates caches.
Definition: Language.php:283
getDir()
Return the correct HTML &#39;dir&#39; attribute value for this language.
Definition: Language.php:3092
getFormattedNamespaces()
A convenience function that returns getNamespaces() with spaces instead of underscores in values...
Definition: Language.php:572
$wgLocalTZoffset
Set an offset from UTC in minutes to use for the default timezone setting for anonymous users and new...
setCode( $code)
Definition: Language.php:4451
iconv( $in, $out, $string)
Definition: Language.php:2656
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:276
static $mMonthGenMsgs
Definition: Language.php:109
static $mWeekdayMsgs
Definition: Language.php:95
linkPrefixExtension()
To allow "foo[[bar]]" to extend the link over the whole word "foobar".
Definition: Language.php:3193
msg( $msg)
Get message object in this language.
Definition: Language.php:966
const MESSAGES_FALLBACKS
Return a fallback chain for messages in getFallbacksFor.
Definition: Language.php:87
const DB_REPLICA
Definition: defines.php:25
$mNamespaceIds
Definition: Language.php:69
uc( $str, $first=false)
Convert a string to uppercase.
Definition: Language.php:2720
getGrammarForms()
Get the grammar forms for the content language.
Definition: Language.php:3890
fixVariableInNamespace( $talk)
Definition: Language.php:4647
markNoConversion( $text, $noParse=false)
Prepare external link text for conversion.
Definition: Language.php:4350
static dateTimeObjFormat(&$dateTimeObj, $ts, $zone, $code)
Pass through result from $dateTimeObj->format()
Definition: Language.php:1072
=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:4822
initContLang()
Hook which will be called if this is the content language.
Definition: Language.php:487
userTime( $ts, User $user, array $options=[])
Get the formatted time for the given timestamp and formatted for the given user.
Definition: Language.php:2491
ucwordsCallbackMB( $matches)
Definition: Language.php:2689
getNamespaces()
Returns an array of localised namespaces indexed by their numbers.
Definition: Language.php:512
static isValidCode( $code)
Returns true if a language code string is of a valid form, whether or not it exists.
Definition: Language.php:386
commaList(array $list)
Take a list of strings and build a locale-friendly comma-separated list, using the local comma-separa...
Definition: Language.php:3468
static $mHebrewCalendarMonthGenMsgs
Definition: Language.php:134
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:3539
gender( $gender, $forms)
Provides an alternative text depending on specified gender.
Definition: Language.php:3959
truncateInternal( $string, $length, $ellipsis, $adjustLength, $measureLength, $getSubstring)
Internal method used for truncation.
Definition: Language.php:3565
getMessageFromDB( $msg)
Get a message from the MediaWiki namespace.
Definition: Language.php:956
getMonthAbbreviationsArray()
Definition: Language.php:1008
hasWordBreaks()
Most writing systems use whitespace to break up words.
Definition: Language.php:2878
convert( $text)
convert text to different variants of a language.
Definition: Language.php:4189
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:3104
checkTitleEncoding( $s)
Definition: Language.php:2852
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:3988
return true to allow those checks to and false if checking is done & $user
Definition: hooks.txt:1487
formatNumNoSeparators( $number)
Front-end for non-commafied formatNum.
Definition: Language.php:3313
parseFormattedNumber( $number)
Definition: Language.php:3321
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:4668
static $GREG_DAYS
Definition: Language.php:1619
getMonthName( $key)
Definition: Language.php:974
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:2352
$matches