MediaWiki  1.28.1
Language.php
Go to the documentation of this file.
1 <?php
30 
35 class Language {
39  public $mConverter;
40 
41  public $mVariants, $mCode, $mLoaded = false;
42  public $mMagicExtensions = [], $mMagicHookDone = false;
43  private $mHtmlCode = null, $mParentLanguage = false;
44 
45  public $dateFormatStrings = [];
47 
49 
53  public $transformData = [];
54 
58  static public $dataCache;
59 
60  static public $mLangObjCache = [];
61 
62  static public $mWeekdayMsgs = [
63  'sunday', 'monday', 'tuesday', 'wednesday', 'thursday',
64  'friday', 'saturday'
65  ];
66 
67  static public $mWeekdayAbbrevMsgs = [
68  'sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'
69  ];
70 
71  static public $mMonthMsgs = [
72  'january', 'february', 'march', 'april', 'may_long', 'june',
73  'july', 'august', 'september', 'october', 'november',
74  'december'
75  ];
76  static public $mMonthGenMsgs = [
77  'january-gen', 'february-gen', 'march-gen', 'april-gen', 'may-gen', 'june-gen',
78  'july-gen', 'august-gen', 'september-gen', 'october-gen', 'november-gen',
79  'december-gen'
80  ];
81  static public $mMonthAbbrevMsgs = [
82  'jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug',
83  'sep', 'oct', 'nov', 'dec'
84  ];
85 
86  static public $mIranianCalendarMonthMsgs = [
87  'iranian-calendar-m1', 'iranian-calendar-m2', 'iranian-calendar-m3',
88  'iranian-calendar-m4', 'iranian-calendar-m5', 'iranian-calendar-m6',
89  'iranian-calendar-m7', 'iranian-calendar-m8', 'iranian-calendar-m9',
90  'iranian-calendar-m10', 'iranian-calendar-m11', 'iranian-calendar-m12'
91  ];
92 
93  static public $mHebrewCalendarMonthMsgs = [
94  'hebrew-calendar-m1', 'hebrew-calendar-m2', 'hebrew-calendar-m3',
95  'hebrew-calendar-m4', 'hebrew-calendar-m5', 'hebrew-calendar-m6',
96  'hebrew-calendar-m7', 'hebrew-calendar-m8', 'hebrew-calendar-m9',
97  'hebrew-calendar-m10', 'hebrew-calendar-m11', 'hebrew-calendar-m12',
98  'hebrew-calendar-m6a', 'hebrew-calendar-m6b'
99  ];
100 
102  'hebrew-calendar-m1-gen', 'hebrew-calendar-m2-gen', 'hebrew-calendar-m3-gen',
103  'hebrew-calendar-m4-gen', 'hebrew-calendar-m5-gen', 'hebrew-calendar-m6-gen',
104  'hebrew-calendar-m7-gen', 'hebrew-calendar-m8-gen', 'hebrew-calendar-m9-gen',
105  'hebrew-calendar-m10-gen', 'hebrew-calendar-m11-gen', 'hebrew-calendar-m12-gen',
106  'hebrew-calendar-m6a-gen', 'hebrew-calendar-m6b-gen'
107  ];
108 
109  static public $mHijriCalendarMonthMsgs = [
110  'hijri-calendar-m1', 'hijri-calendar-m2', 'hijri-calendar-m3',
111  'hijri-calendar-m4', 'hijri-calendar-m5', 'hijri-calendar-m6',
112  'hijri-calendar-m7', 'hijri-calendar-m8', 'hijri-calendar-m9',
113  'hijri-calendar-m10', 'hijri-calendar-m11', 'hijri-calendar-m12'
114  ];
115 
120  static public $durationIntervals = [
121  'millennia' => 31556952000,
122  'centuries' => 3155695200,
123  'decades' => 315569520,
124  'years' => 31556952, // 86400 * ( 365 + ( 24 * 3 + 25 ) / 400 )
125  'weeks' => 604800,
126  'days' => 86400,
127  'hours' => 3600,
128  'minutes' => 60,
129  'seconds' => 1,
130  ];
131 
138  static private $fallbackLanguageCache = [];
139 
144  static private $grammarTransformations;
145 
150  static private $languageNameCache;
151 
155  static private $lre = "\xE2\x80\xAA"; // U+202A LEFT-TO-RIGHT EMBEDDING
156  static private $rle = "\xE2\x80\xAB"; // U+202B RIGHT-TO-LEFT EMBEDDING
157  static private $pdf = "\xE2\x80\xAC"; // U+202C POP DIRECTIONAL FORMATTING
158 
170  // @codingStandardsIgnoreStart
171  // @codeCoverageIgnoreStart
172  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';
173  // @codeCoverageIgnoreEnd
174  // @codingStandardsIgnoreEnd
175 
181  static function factory( $code ) {
183 
184  if ( isset( $wgDummyLanguageCodes[$code] ) ) {
185  $code = $wgDummyLanguageCodes[$code];
186  }
187 
188  // get the language object to process
189  $langObj = isset( self::$mLangObjCache[$code] )
190  ? self::$mLangObjCache[$code]
191  : self::newFromCode( $code );
192 
193  // merge the language object in to get it up front in the cache
194  self::$mLangObjCache = array_merge( [ $code => $langObj ], self::$mLangObjCache );
195  // get rid of the oldest ones in case we have an overflow
196  self::$mLangObjCache = array_slice( self::$mLangObjCache, 0, $wgLangObjCacheSize, true );
197 
198  return $langObj;
199  }
200 
207  protected static function newFromCode( $code ) {
208  if ( !Language::isValidCode( $code ) ) {
209  throw new MWException( "Invalid language code \"$code\"" );
210  }
211 
213  // It's not possible to customise this code with class files, so
214  // just return a Language object. This is to support uselang= hacks.
215  $lang = new Language;
216  $lang->setCode( $code );
217  return $lang;
218  }
219 
220  // Check if there is a language class for the code
221  $class = self::classFromCode( $code );
222  if ( class_exists( $class ) ) {
223  $lang = new $class;
224  return $lang;
225  }
226 
227  // Keep trying the fallback list until we find an existing class
228  $fallbacks = Language::getFallbacksFor( $code );
229  foreach ( $fallbacks as $fallbackCode ) {
230  if ( !Language::isValidBuiltInCode( $fallbackCode ) ) {
231  throw new MWException( "Invalid fallback '$fallbackCode' in fallback sequence for '$code'" );
232  }
233 
234  $class = self::classFromCode( $fallbackCode );
235  if ( class_exists( $class ) ) {
236  $lang = new $class;
237  $lang->setCode( $code );
238  return $lang;
239  }
240  }
241 
242  throw new MWException( "Invalid fallback sequence for language '$code'" );
243  }
244 
253  public static function isSupportedLanguage( $code ) {
254  if ( !self::isValidBuiltInCode( $code ) ) {
255  return false;
256  }
257 
258  if ( $code === 'qqq' ) {
259  return false;
260  }
261 
262  return is_readable( self::getMessagesFileName( $code ) ) ||
263  is_readable( self::getJsonMessagesFileName( $code ) );
264  }
265 
281  public static function isWellFormedLanguageTag( $code, $lenient = false ) {
282  $alpha = '[a-z]';
283  $digit = '[0-9]';
284  $alphanum = '[a-z0-9]';
285  $x = 'x'; # private use singleton
286  $singleton = '[a-wy-z]'; # other singleton
287  $s = $lenient ? '[-_]' : '-';
288 
289  $language = "$alpha{2,8}|$alpha{2,3}$s$alpha{3}";
290  $script = "$alpha{4}"; # ISO 15924
291  $region = "(?:$alpha{2}|$digit{3})"; # ISO 3166-1 alpha-2 or UN M.49
292  $variant = "(?:$alphanum{5,8}|$digit$alphanum{3})";
293  $extension = "$singleton(?:$s$alphanum{2,8})+";
294  $privateUse = "$x(?:$s$alphanum{1,8})+";
295 
296  # Define certain grandfathered codes, since otherwise the regex is pretty useless.
297  # Since these are limited, this is safe even later changes to the registry --
298  # the only oddity is that it might change the type of the tag, and thus
299  # the results from the capturing groups.
300  # https://www.iana.org/assignments/language-subtag-registry
301 
302  $grandfathered = "en{$s}GB{$s}oed"
303  . "|i{$s}(?:ami|bnn|default|enochian|hak|klingon|lux|mingo|navajo|pwn|tao|tay|tsu)"
304  . "|no{$s}(?:bok|nyn)"
305  . "|sgn{$s}(?:BE{$s}(?:fr|nl)|CH{$s}de)"
306  . "|zh{$s}min{$s}nan";
307 
308  $variantList = "$variant(?:$s$variant)*";
309  $extensionList = "$extension(?:$s$extension)*";
310 
311  $langtag = "(?:($language)"
312  . "(?:$s$script)?"
313  . "(?:$s$region)?"
314  . "(?:$s$variantList)?"
315  . "(?:$s$extensionList)?"
316  . "(?:$s$privateUse)?)";
317 
318  # The final breakdown, with capturing groups for each of these components
319  # The variants, extensions, grandfathered, and private-use may have interior '-'
320 
321  $root = "^(?:$langtag|$privateUse|$grandfathered)$";
322 
323  return (bool)preg_match( "/$root/", strtolower( $code ) );
324  }
325 
335  public static function isValidCode( $code ) {
336  static $cache = [];
337  if ( !isset( $cache[$code] ) ) {
338  // People think language codes are html safe, so enforce it.
339  // Ideally we should only allow a-zA-Z0-9-
340  // but, .+ and other chars are often used for {{int:}} hacks
341  // see bugs T39564, T39587, T38938
342  $cache[$code] =
343  // Protect against path traversal
344  strcspn( $code, ":/\\\000&<>'\"" ) === strlen( $code )
345  && !preg_match( MediaWikiTitleCodec::getTitleInvalidRegex(), $code );
346  }
347  return $cache[$code];
348  }
349 
360  public static function isValidBuiltInCode( $code ) {
361 
362  if ( !is_string( $code ) ) {
363  if ( is_object( $code ) ) {
364  $addmsg = " of class " . get_class( $code );
365  } else {
366  $addmsg = '';
367  }
368  $type = gettype( $code );
369  throw new MWException( __METHOD__ . " must be passed a string, $type given$addmsg" );
370  }
371 
372  return (bool)preg_match( '/^[a-z0-9-]{2,}$/', $code );
373  }
374 
383  public static function isKnownLanguageTag( $tag ) {
384  // Quick escape for invalid input to avoid exceptions down the line
385  // when code tries to process tags which are not valid at all.
386  if ( !self::isValidBuiltInCode( $tag ) ) {
387  return false;
388  }
389 
390  if ( isset( MediaWiki\Languages\Data\Names::$names[$tag] )
391  || self::fetchLanguageName( $tag, $tag ) !== ''
392  ) {
393  return true;
394  }
395 
396  return false;
397  }
398 
404  public static function getLocalisationCache() {
405  if ( is_null( self::$dataCache ) ) {
407  $class = $wgLocalisationCacheConf['class'];
408  self::$dataCache = new $class( $wgLocalisationCacheConf );
409  }
410  return self::$dataCache;
411  }
412 
413  function __construct() {
414  $this->mConverter = new FakeConverter( $this );
415  // Set the code to the name of the descendant
416  if ( get_class( $this ) == 'Language' ) {
417  $this->mCode = 'en';
418  } else {
419  $this->mCode = str_replace( '_', '-', strtolower( substr( get_class( $this ), 8 ) ) );
420  }
421  self::getLocalisationCache();
422  }
423 
427  function __destruct() {
428  foreach ( $this as $name => $value ) {
429  unset( $this->$name );
430  }
431  }
432 
437  function initContLang() {
438  }
439 
444  public function getFallbackLanguages() {
445  return self::getFallbacksFor( $this->mCode );
446  }
447 
452  public function getBookstoreList() {
453  return self::$dataCache->getItem( $this->mCode, 'bookstoreList' );
454  }
455 
462  public function getNamespaces() {
463  if ( is_null( $this->namespaceNames ) ) {
465 
466  $this->namespaceNames = self::$dataCache->getItem( $this->mCode, 'namespaceNames' );
467  $validNamespaces = MWNamespace::getCanonicalNamespaces();
468 
469  $this->namespaceNames = $wgExtraNamespaces + $this->namespaceNames + $validNamespaces;
470 
471  $this->namespaceNames[NS_PROJECT] = $wgMetaNamespace;
472  if ( $wgMetaNamespaceTalk ) {
473  $this->namespaceNames[NS_PROJECT_TALK] = $wgMetaNamespaceTalk;
474  } else {
475  $talk = $this->namespaceNames[NS_PROJECT_TALK];
476  $this->namespaceNames[NS_PROJECT_TALK] =
477  $this->fixVariableInNamespace( $talk );
478  }
479 
480  # Sometimes a language will be localised but not actually exist on this wiki.
481  foreach ( $this->namespaceNames as $key => $text ) {
482  if ( !isset( $validNamespaces[$key] ) ) {
483  unset( $this->namespaceNames[$key] );
484  }
485  }
486 
487  # The above mixing may leave namespaces out of canonical order.
488  # Re-order by namespace ID number...
489  ksort( $this->namespaceNames );
490 
491  Hooks::run( 'LanguageGetNamespaces', [ &$this->namespaceNames ] );
492  }
493 
494  return $this->namespaceNames;
495  }
496 
501  public function setNamespaces( array $namespaces ) {
502  $this->namespaceNames = $namespaces;
503  $this->mNamespaceIds = null;
504  }
505 
509  public function resetNamespaces() {
510  $this->namespaceNames = null;
511  $this->mNamespaceIds = null;
512  $this->namespaceAliases = null;
513  }
514 
521  public function getFormattedNamespaces() {
522  $ns = $this->getNamespaces();
523  foreach ( $ns as $k => $v ) {
524  $ns[$k] = strtr( $v, '_', ' ' );
525  }
526  return $ns;
527  }
528 
540  public function getNsText( $index ) {
541  $ns = $this->getNamespaces();
542  return isset( $ns[$index] ) ? $ns[$index] : false;
543  }
544 
558  public function getFormattedNsText( $index ) {
559  $ns = $this->getNsText( $index );
560  return strtr( $ns, '_', ' ' );
561  }
562 
571  public function getGenderNsText( $index, $gender ) {
573 
574  $ns = $wgExtraGenderNamespaces +
575  (array)self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' );
576 
577  return isset( $ns[$index][$gender] ) ? $ns[$index][$gender] : $this->getNsText( $index );
578  }
579 
586  public function needsGenderDistinction() {
588  if ( count( $wgExtraGenderNamespaces ) > 0 ) {
589  // $wgExtraGenderNamespaces overrides everything
590  return true;
591  } elseif ( isset( $wgExtraNamespaces[NS_USER] ) && isset( $wgExtraNamespaces[NS_USER_TALK] ) ) {
593  // $wgExtraNamespaces overrides any gender aliases specified in i18n files
594  return false;
595  } else {
596  // Check what is in i18n files
597  $aliases = self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' );
598  return count( $aliases ) > 0;
599  }
600  }
601 
610  function getLocalNsIndex( $text ) {
611  $lctext = $this->lc( $text );
612  $ids = $this->getNamespaceIds();
613  return isset( $ids[$lctext] ) ? $ids[$lctext] : false;
614  }
615 
619  public function getNamespaceAliases() {
620  if ( is_null( $this->namespaceAliases ) ) {
621  $aliases = self::$dataCache->getItem( $this->mCode, 'namespaceAliases' );
622  if ( !$aliases ) {
623  $aliases = [];
624  } else {
625  foreach ( $aliases as $name => $index ) {
626  if ( $index === NS_PROJECT_TALK ) {
627  unset( $aliases[$name] );
628  $name = $this->fixVariableInNamespace( $name );
629  $aliases[$name] = $index;
630  }
631  }
632  }
633 
635  $genders = $wgExtraGenderNamespaces +
636  (array)self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' );
637  foreach ( $genders as $index => $forms ) {
638  foreach ( $forms as $alias ) {
639  $aliases[$alias] = $index;
640  }
641  }
642 
643  # Also add converted namespace names as aliases, to avoid confusion.
644  $convertedNames = [];
645  foreach ( $this->getVariants() as $variant ) {
646  if ( $variant === $this->mCode ) {
647  continue;
648  }
649  foreach ( $this->getNamespaces() as $ns => $_ ) {
650  $convertedNames[$this->getConverter()->convertNamespace( $ns, $variant )] = $ns;
651  }
652  }
653 
654  $this->namespaceAliases = $aliases + $convertedNames;
655  }
656 
658  }
659 
663  public function getNamespaceIds() {
664  if ( is_null( $this->mNamespaceIds ) ) {
666  # Put namespace names and aliases into a hashtable.
667  # If this is too slow, then we should arrange it so that it is done
668  # before caching. The catch is that at pre-cache time, the above
669  # class-specific fixup hasn't been done.
670  $this->mNamespaceIds = [];
671  foreach ( $this->getNamespaces() as $index => $name ) {
672  $this->mNamespaceIds[$this->lc( $name )] = $index;
673  }
674  foreach ( $this->getNamespaceAliases() as $name => $index ) {
675  $this->mNamespaceIds[$this->lc( $name )] = $index;
676  }
677  if ( $wgNamespaceAliases ) {
678  foreach ( $wgNamespaceAliases as $name => $index ) {
679  $this->mNamespaceIds[$this->lc( $name )] = $index;
680  }
681  }
682  }
683  return $this->mNamespaceIds;
684  }
685 
693  public function getNsIndex( $text ) {
694  $lctext = $this->lc( $text );
695  $ns = MWNamespace::getCanonicalIndex( $lctext );
696  if ( $ns !== null ) {
697  return $ns;
698  }
699  $ids = $this->getNamespaceIds();
700  return isset( $ids[$lctext] ) ? $ids[$lctext] : false;
701  }
702 
710  public function getVariantname( $code, $usemsg = true ) {
711  $msg = "variantname-$code";
712  if ( $usemsg && wfMessage( $msg )->exists() ) {
713  return $this->getMessageFromDB( $msg );
714  }
715  $name = self::fetchLanguageName( $code );
716  if ( $name ) {
717  return $name; # if it's defined as a language name, show that
718  } else {
719  # otherwise, output the language code
720  return $code;
721  }
722  }
723 
727  public function getDatePreferences() {
728  return self::$dataCache->getItem( $this->mCode, 'datePreferences' );
729  }
730 
734  function getDateFormats() {
735  return self::$dataCache->getItem( $this->mCode, 'dateFormats' );
736  }
737 
741  public function getDefaultDateFormat() {
742  $df = self::$dataCache->getItem( $this->mCode, 'defaultDateFormat' );
743  if ( $df === 'dmy or mdy' ) {
744  global $wgAmericanDates;
745  return $wgAmericanDates ? 'mdy' : 'dmy';
746  } else {
747  return $df;
748  }
749  }
750 
754  public function getDatePreferenceMigrationMap() {
755  return self::$dataCache->getItem( $this->mCode, 'datePreferenceMigrationMap' );
756  }
757 
762  function getImageFile( $image ) {
763  return self::$dataCache->getSubitem( $this->mCode, 'imageFiles', $image );
764  }
765 
770  public function getImageFiles() {
771  return self::$dataCache->getItem( $this->mCode, 'imageFiles' );
772  }
773 
777  public function getExtraUserToggles() {
778  return (array)self::$dataCache->getItem( $this->mCode, 'extraUserToggles' );
779  }
780 
785  function getUserToggle( $tog ) {
786  return $this->getMessageFromDB( "tog-$tog" );
787  }
788 
800  public static function fetchLanguageNames( $inLanguage = null, $include = 'mw' ) {
801  $cacheKey = $inLanguage === null ? 'null' : $inLanguage;
802  $cacheKey .= ":$include";
803  if ( self::$languageNameCache === null ) {
804  self::$languageNameCache = new HashBagOStuff( [ 'maxKeys' => 20 ] );
805  }
806 
807  $ret = self::$languageNameCache->get( $cacheKey );
808  if ( !$ret ) {
809  $ret = self::fetchLanguageNamesUncached( $inLanguage, $include );
810  self::$languageNameCache->set( $cacheKey, $ret );
811  }
812  return $ret;
813  }
814 
825  private static function fetchLanguageNamesUncached( $inLanguage = null, $include = 'mw' ) {
826  global $wgExtraLanguageNames;
827 
828  // If passed an invalid language code to use, fallback to en
829  if ( $inLanguage !== null && !Language::isValidCode( $inLanguage ) ) {
830  $inLanguage = 'en';
831  }
832 
833  $names = [];
834 
835  if ( $inLanguage ) {
836  # TODO: also include when $inLanguage is null, when this code is more efficient
837  Hooks::run( 'LanguageGetTranslatedLanguageNames', [ &$names, $inLanguage ] );
838  }
839 
840  $mwNames = $wgExtraLanguageNames + MediaWiki\Languages\Data\Names::$names;
841  foreach ( $mwNames as $mwCode => $mwName ) {
842  # - Prefer own MediaWiki native name when not using the hook
843  # - For other names just add if not added through the hook
844  if ( $mwCode === $inLanguage || !isset( $names[$mwCode] ) ) {
845  $names[$mwCode] = $mwName;
846  }
847  }
848 
849  if ( $include === 'all' ) {
850  ksort( $names );
851  return $names;
852  }
853 
854  $returnMw = [];
855  $coreCodes = array_keys( $mwNames );
856  foreach ( $coreCodes as $coreCode ) {
857  $returnMw[$coreCode] = $names[$coreCode];
858  }
859 
860  if ( $include === 'mwfile' ) {
861  $namesMwFile = [];
862  # We do this using a foreach over the codes instead of a directory
863  # loop so that messages files in extensions will work correctly.
864  foreach ( $returnMw as $code => $value ) {
865  if ( is_readable( self::getMessagesFileName( $code ) )
866  || is_readable( self::getJsonMessagesFileName( $code ) )
867  ) {
868  $namesMwFile[$code] = $names[$code];
869  }
870  }
871 
872  ksort( $namesMwFile );
873  return $namesMwFile;
874  }
875 
876  ksort( $returnMw );
877  # 'mw' option; default if it's not one of the other two options (all/mwfile)
878  return $returnMw;
879  }
880 
888  public static function fetchLanguageName( $code, $inLanguage = null, $include = 'all' ) {
889  $code = strtolower( $code );
890  $array = self::fetchLanguageNames( $inLanguage, $include );
891  return !array_key_exists( $code, $array ) ? '' : $array[$code];
892  }
893 
900  public function getMessageFromDB( $msg ) {
901  return $this->msg( $msg )->text();
902  }
903 
910  protected function msg( $msg ) {
911  return wfMessage( $msg )->inLanguage( $this );
912  }
913 
918  public function getMonthName( $key ) {
919  return $this->getMessageFromDB( self::$mMonthMsgs[$key - 1] );
920  }
921 
925  public function getMonthNamesArray() {
926  $monthNames = [ '' ];
927  for ( $i = 1; $i < 13; $i++ ) {
928  $monthNames[] = $this->getMonthName( $i );
929  }
930  return $monthNames;
931  }
932 
937  public function getMonthNameGen( $key ) {
938  return $this->getMessageFromDB( self::$mMonthGenMsgs[$key - 1] );
939  }
940 
945  public function getMonthAbbreviation( $key ) {
946  return $this->getMessageFromDB( self::$mMonthAbbrevMsgs[$key - 1] );
947  }
948 
952  public function getMonthAbbreviationsArray() {
953  $monthNames = [ '' ];
954  for ( $i = 1; $i < 13; $i++ ) {
955  $monthNames[] = $this->getMonthAbbreviation( $i );
956  }
957  return $monthNames;
958  }
959 
964  public function getWeekdayName( $key ) {
965  return $this->getMessageFromDB( self::$mWeekdayMsgs[$key - 1] );
966  }
967 
972  function getWeekdayAbbreviation( $key ) {
973  return $this->getMessageFromDB( self::$mWeekdayAbbrevMsgs[$key - 1] );
974  }
975 
980  function getIranianCalendarMonthName( $key ) {
981  return $this->getMessageFromDB( self::$mIranianCalendarMonthMsgs[$key - 1] );
982  }
983 
988  function getHebrewCalendarMonthName( $key ) {
989  return $this->getMessageFromDB( self::$mHebrewCalendarMonthMsgs[$key - 1] );
990  }
991 
996  function getHebrewCalendarMonthNameGen( $key ) {
997  return $this->getMessageFromDB( self::$mHebrewCalendarMonthGenMsgs[$key - 1] );
998  }
999 
1004  function getHijriCalendarMonthName( $key ) {
1005  return $this->getMessageFromDB( self::$mHijriCalendarMonthMsgs[$key - 1] );
1006  }
1007 
1016  private static function dateTimeObjFormat( &$dateTimeObj, $ts, $zone, $code ) {
1017  if ( !$dateTimeObj ) {
1018  $dateTimeObj = DateTime::createFromFormat(
1019  'YmdHis', $ts, $zone ?: new DateTimeZone( 'UTC' )
1020  );
1021  }
1022  return $dateTimeObj->format( $code );
1023  }
1024 
1094  public function sprintfDate( $format, $ts, DateTimeZone $zone = null, &$ttl = 'unused' ) {
1095  $s = '';
1096  $raw = false;
1097  $roman = false;
1098  $hebrewNum = false;
1099  $dateTimeObj = false;
1100  $rawToggle = false;
1101  $iranian = false;
1102  $hebrew = false;
1103  $hijri = false;
1104  $thai = false;
1105  $minguo = false;
1106  $tenno = false;
1107 
1108  $usedSecond = false;
1109  $usedMinute = false;
1110  $usedHour = false;
1111  $usedAMPM = false;
1112  $usedDay = false;
1113  $usedWeek = false;
1114  $usedMonth = false;
1115  $usedYear = false;
1116  $usedISOYear = false;
1117  $usedIsLeapYear = false;
1118 
1119  $usedHebrewMonth = false;
1120  $usedIranianMonth = false;
1121  $usedHijriMonth = false;
1122  $usedHebrewYear = false;
1123  $usedIranianYear = false;
1124  $usedHijriYear = false;
1125  $usedTennoYear = false;
1126 
1127  if ( strlen( $ts ) !== 14 ) {
1128  throw new MWException( __METHOD__ . ": The timestamp $ts should have 14 characters" );
1129  }
1130 
1131  if ( !ctype_digit( $ts ) ) {
1132  throw new MWException( __METHOD__ . ": The timestamp $ts should be a number" );
1133  }
1134 
1135  $formatLength = strlen( $format );
1136  for ( $p = 0; $p < $formatLength; $p++ ) {
1137  $num = false;
1138  $code = $format[$p];
1139  if ( $code == 'x' && $p < $formatLength - 1 ) {
1140  $code .= $format[++$p];
1141  }
1142 
1143  if ( ( $code === 'xi'
1144  || $code === 'xj'
1145  || $code === 'xk'
1146  || $code === 'xm'
1147  || $code === 'xo'
1148  || $code === 'xt' )
1149  && $p < $formatLength - 1 ) {
1150  $code .= $format[++$p];
1151  }
1152 
1153  switch ( $code ) {
1154  case 'xx':
1155  $s .= 'x';
1156  break;
1157  case 'xn':
1158  $raw = true;
1159  break;
1160  case 'xN':
1161  $rawToggle = !$rawToggle;
1162  break;
1163  case 'xr':
1164  $roman = true;
1165  break;
1166  case 'xh':
1167  $hebrewNum = true;
1168  break;
1169  case 'xg':
1170  $usedMonth = true;
1171  $s .= $this->getMonthNameGen( substr( $ts, 4, 2 ) );
1172  break;
1173  case 'xjx':
1174  $usedHebrewMonth = true;
1175  if ( !$hebrew ) {
1176  $hebrew = self::tsToHebrew( $ts );
1177  }
1178  $s .= $this->getHebrewCalendarMonthNameGen( $hebrew[1] );
1179  break;
1180  case 'd':
1181  $usedDay = true;
1182  $num = substr( $ts, 6, 2 );
1183  break;
1184  case 'D':
1185  $usedDay = true;
1186  $s .= $this->getWeekdayAbbreviation(
1187  Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'w' ) + 1
1188  );
1189  break;
1190  case 'j':
1191  $usedDay = true;
1192  $num = intval( substr( $ts, 6, 2 ) );
1193  break;
1194  case 'xij':
1195  $usedDay = true;
1196  if ( !$iranian ) {
1197  $iranian = self::tsToIranian( $ts );
1198  }
1199  $num = $iranian[2];
1200  break;
1201  case 'xmj':
1202  $usedDay = true;
1203  if ( !$hijri ) {
1204  $hijri = self::tsToHijri( $ts );
1205  }
1206  $num = $hijri[2];
1207  break;
1208  case 'xjj':
1209  $usedDay = true;
1210  if ( !$hebrew ) {
1211  $hebrew = self::tsToHebrew( $ts );
1212  }
1213  $num = $hebrew[2];
1214  break;
1215  case 'l':
1216  $usedDay = true;
1217  $s .= $this->getWeekdayName(
1218  Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'w' ) + 1
1219  );
1220  break;
1221  case 'F':
1222  $usedMonth = true;
1223  $s .= $this->getMonthName( substr( $ts, 4, 2 ) );
1224  break;
1225  case 'xiF':
1226  $usedIranianMonth = true;
1227  if ( !$iranian ) {
1228  $iranian = self::tsToIranian( $ts );
1229  }
1230  $s .= $this->getIranianCalendarMonthName( $iranian[1] );
1231  break;
1232  case 'xmF':
1233  $usedHijriMonth = true;
1234  if ( !$hijri ) {
1235  $hijri = self::tsToHijri( $ts );
1236  }
1237  $s .= $this->getHijriCalendarMonthName( $hijri[1] );
1238  break;
1239  case 'xjF':
1240  $usedHebrewMonth = true;
1241  if ( !$hebrew ) {
1242  $hebrew = self::tsToHebrew( $ts );
1243  }
1244  $s .= $this->getHebrewCalendarMonthName( $hebrew[1] );
1245  break;
1246  case 'm':
1247  $usedMonth = true;
1248  $num = substr( $ts, 4, 2 );
1249  break;
1250  case 'M':
1251  $usedMonth = true;
1252  $s .= $this->getMonthAbbreviation( substr( $ts, 4, 2 ) );
1253  break;
1254  case 'n':
1255  $usedMonth = true;
1256  $num = intval( substr( $ts, 4, 2 ) );
1257  break;
1258  case 'xin':
1259  $usedIranianMonth = true;
1260  if ( !$iranian ) {
1261  $iranian = self::tsToIranian( $ts );
1262  }
1263  $num = $iranian[1];
1264  break;
1265  case 'xmn':
1266  $usedHijriMonth = true;
1267  if ( !$hijri ) {
1268  $hijri = self::tsToHijri( $ts );
1269  }
1270  $num = $hijri[1];
1271  break;
1272  case 'xjn':
1273  $usedHebrewMonth = true;
1274  if ( !$hebrew ) {
1275  $hebrew = self::tsToHebrew( $ts );
1276  }
1277  $num = $hebrew[1];
1278  break;
1279  case 'xjt':
1280  $usedHebrewMonth = true;
1281  if ( !$hebrew ) {
1282  $hebrew = self::tsToHebrew( $ts );
1283  }
1284  $num = $hebrew[3];
1285  break;
1286  case 'Y':
1287  $usedYear = true;
1288  $num = substr( $ts, 0, 4 );
1289  break;
1290  case 'xiY':
1291  $usedIranianYear = true;
1292  if ( !$iranian ) {
1293  $iranian = self::tsToIranian( $ts );
1294  }
1295  $num = $iranian[0];
1296  break;
1297  case 'xmY':
1298  $usedHijriYear = true;
1299  if ( !$hijri ) {
1300  $hijri = self::tsToHijri( $ts );
1301  }
1302  $num = $hijri[0];
1303  break;
1304  case 'xjY':
1305  $usedHebrewYear = true;
1306  if ( !$hebrew ) {
1307  $hebrew = self::tsToHebrew( $ts );
1308  }
1309  $num = $hebrew[0];
1310  break;
1311  case 'xkY':
1312  $usedYear = true;
1313  if ( !$thai ) {
1314  $thai = self::tsToYear( $ts, 'thai' );
1315  }
1316  $num = $thai[0];
1317  break;
1318  case 'xoY':
1319  $usedYear = true;
1320  if ( !$minguo ) {
1321  $minguo = self::tsToYear( $ts, 'minguo' );
1322  }
1323  $num = $minguo[0];
1324  break;
1325  case 'xtY':
1326  $usedTennoYear = true;
1327  if ( !$tenno ) {
1328  $tenno = self::tsToYear( $ts, 'tenno' );
1329  }
1330  $num = $tenno[0];
1331  break;
1332  case 'y':
1333  $usedYear = true;
1334  $num = substr( $ts, 2, 2 );
1335  break;
1336  case 'xiy':
1337  $usedIranianYear = true;
1338  if ( !$iranian ) {
1339  $iranian = self::tsToIranian( $ts );
1340  }
1341  $num = substr( $iranian[0], -2 );
1342  break;
1343  case 'xit':
1344  $usedIranianYear = true;
1345  if ( !$iranian ) {
1346  $iranian = self::tsToIranian( $ts );
1347  }
1348  $num = self::$IRANIAN_DAYS[$iranian[1] - 1];
1349  break;
1350  case 'xiz':
1351  $usedIranianYear = true;
1352  if ( !$iranian ) {
1353  $iranian = self::tsToIranian( $ts );
1354  }
1355  $num = $iranian[3];
1356  break;
1357  case 'a':
1358  $usedAMPM = true;
1359  $s .= intval( substr( $ts, 8, 2 ) ) < 12 ? 'am' : 'pm';
1360  break;
1361  case 'A':
1362  $usedAMPM = true;
1363  $s .= intval( substr( $ts, 8, 2 ) ) < 12 ? 'AM' : 'PM';
1364  break;
1365  case 'g':
1366  $usedHour = true;
1367  $h = substr( $ts, 8, 2 );
1368  $num = $h % 12 ? $h % 12 : 12;
1369  break;
1370  case 'G':
1371  $usedHour = true;
1372  $num = intval( substr( $ts, 8, 2 ) );
1373  break;
1374  case 'h':
1375  $usedHour = true;
1376  $h = substr( $ts, 8, 2 );
1377  $num = sprintf( '%02d', $h % 12 ? $h % 12 : 12 );
1378  break;
1379  case 'H':
1380  $usedHour = true;
1381  $num = substr( $ts, 8, 2 );
1382  break;
1383  case 'i':
1384  $usedMinute = true;
1385  $num = substr( $ts, 10, 2 );
1386  break;
1387  case 's':
1388  $usedSecond = true;
1389  $num = substr( $ts, 12, 2 );
1390  break;
1391  case 'c':
1392  case 'r':
1393  $usedSecond = true;
1394  // fall through
1395  case 'e':
1396  case 'O':
1397  case 'P':
1398  case 'T':
1399  $s .= Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1400  break;
1401  case 'w':
1402  case 'N':
1403  case 'z':
1404  $usedDay = true;
1405  $num = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1406  break;
1407  case 'W':
1408  $usedWeek = true;
1409  $num = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1410  break;
1411  case 't':
1412  $usedMonth = true;
1413  $num = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1414  break;
1415  case 'L':
1416  $usedIsLeapYear = true;
1417  $num = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1418  break;
1419  case 'o':
1420  $usedISOYear = true;
1421  $num = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1422  break;
1423  case 'U':
1424  $usedSecond = true;
1425  // fall through
1426  case 'I':
1427  case 'Z':
1428  $num = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1429  break;
1430  case '\\':
1431  # Backslash escaping
1432  if ( $p < $formatLength - 1 ) {
1433  $s .= $format[++$p];
1434  } else {
1435  $s .= '\\';
1436  }
1437  break;
1438  case '"':
1439  # Quoted literal
1440  if ( $p < $formatLength - 1 ) {
1441  $endQuote = strpos( $format, '"', $p + 1 );
1442  if ( $endQuote === false ) {
1443  # No terminating quote, assume literal "
1444  $s .= '"';
1445  } else {
1446  $s .= substr( $format, $p + 1, $endQuote - $p - 1 );
1447  $p = $endQuote;
1448  }
1449  } else {
1450  # Quote at end of string, assume literal "
1451  $s .= '"';
1452  }
1453  break;
1454  default:
1455  $s .= $format[$p];
1456  }
1457  if ( $num !== false ) {
1458  if ( $rawToggle || $raw ) {
1459  $s .= $num;
1460  $raw = false;
1461  } elseif ( $roman ) {
1462  $s .= Language::romanNumeral( $num );
1463  $roman = false;
1464  } elseif ( $hebrewNum ) {
1465  $s .= self::hebrewNumeral( $num );
1466  $hebrewNum = false;
1467  } else {
1468  $s .= $this->formatNum( $num, true );
1469  }
1470  }
1471  }
1472 
1473  if ( $ttl === 'unused' ) {
1474  // No need to calculate the TTL, the caller wont use it anyway.
1475  } elseif ( $usedSecond ) {
1476  $ttl = 1;
1477  } elseif ( $usedMinute ) {
1478  $ttl = 60 - substr( $ts, 12, 2 );
1479  } elseif ( $usedHour ) {
1480  $ttl = 3600 - substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
1481  } elseif ( $usedAMPM ) {
1482  $ttl = 43200 - ( substr( $ts, 8, 2 ) % 12 ) * 3600 -
1483  substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
1484  } elseif (
1485  $usedDay ||
1486  $usedHebrewMonth ||
1487  $usedIranianMonth ||
1488  $usedHijriMonth ||
1489  $usedHebrewYear ||
1490  $usedIranianYear ||
1491  $usedHijriYear ||
1492  $usedTennoYear
1493  ) {
1494  // @todo Someone who understands the non-Gregorian calendars
1495  // should write proper logic for them so that they don't need purged every day.
1496  $ttl = 86400 - substr( $ts, 8, 2 ) * 3600 -
1497  substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
1498  } else {
1499  $possibleTtls = [];
1500  $timeRemainingInDay = 86400 - substr( $ts, 8, 2 ) * 3600 -
1501  substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
1502  if ( $usedWeek ) {
1503  $possibleTtls[] =
1504  ( 7 - Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'N' ) ) * 86400 +
1505  $timeRemainingInDay;
1506  } elseif ( $usedISOYear ) {
1507  // December 28th falls on the last ISO week of the year, every year.
1508  // The last ISO week of a year can be 52 or 53.
1509  $lastWeekOfISOYear = DateTime::createFromFormat(
1510  'Ymd',
1511  substr( $ts, 0, 4 ) . '1228',
1512  $zone ?: new DateTimeZone( 'UTC' )
1513  )->format( 'W' );
1514  $currentISOWeek = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'W' );
1515  $weeksRemaining = $lastWeekOfISOYear - $currentISOWeek;
1516  $timeRemainingInWeek =
1517  ( 7 - Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'N' ) ) * 86400
1518  + $timeRemainingInDay;
1519  $possibleTtls[] = $weeksRemaining * 604800 + $timeRemainingInWeek;
1520  }
1521 
1522  if ( $usedMonth ) {
1523  $possibleTtls[] =
1524  ( Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 't' ) -
1525  substr( $ts, 6, 2 ) ) * 86400
1526  + $timeRemainingInDay;
1527  } elseif ( $usedYear ) {
1528  $possibleTtls[] =
1529  ( Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'L' ) + 364 -
1530  Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'z' ) ) * 86400
1531  + $timeRemainingInDay;
1532  } elseif ( $usedIsLeapYear ) {
1533  $year = substr( $ts, 0, 4 );
1534  $timeRemainingInYear =
1535  ( Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'L' ) + 364 -
1536  Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'z' ) ) * 86400
1537  + $timeRemainingInDay;
1538  $mod = $year % 4;
1539  if ( $mod || ( !( $year % 100 ) && $year % 400 ) ) {
1540  // this isn't a leap year. see when the next one starts
1541  $nextCandidate = $year - $mod + 4;
1542  if ( $nextCandidate % 100 || !( $nextCandidate % 400 ) ) {
1543  $possibleTtls[] = ( $nextCandidate - $year - 1 ) * 365 * 86400 +
1544  $timeRemainingInYear;
1545  } else {
1546  $possibleTtls[] = ( $nextCandidate - $year + 3 ) * 365 * 86400 +
1547  $timeRemainingInYear;
1548  }
1549  } else {
1550  // this is a leap year, so the next year isn't
1551  $possibleTtls[] = $timeRemainingInYear;
1552  }
1553  }
1554 
1555  if ( $possibleTtls ) {
1556  $ttl = min( $possibleTtls );
1557  }
1558  }
1559 
1560  return $s;
1561  }
1562 
1563  private static $GREG_DAYS = [ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ];
1564  private static $IRANIAN_DAYS = [ 31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29 ];
1565 
1578  private static function tsToIranian( $ts ) {
1579  $gy = substr( $ts, 0, 4 ) -1600;
1580  $gm = substr( $ts, 4, 2 ) -1;
1581  $gd = substr( $ts, 6, 2 ) -1;
1582 
1583  # Days passed from the beginning (including leap years)
1584  $gDayNo = 365 * $gy
1585  + floor( ( $gy + 3 ) / 4 )
1586  - floor( ( $gy + 99 ) / 100 )
1587  + floor( ( $gy + 399 ) / 400 );
1588 
1589  // Add days of the past months of this year
1590  for ( $i = 0; $i < $gm; $i++ ) {
1591  $gDayNo += self::$GREG_DAYS[$i];
1592  }
1593 
1594  // Leap years
1595  if ( $gm > 1 && ( ( $gy % 4 === 0 && $gy % 100 !== 0 || ( $gy % 400 == 0 ) ) ) ) {
1596  $gDayNo++;
1597  }
1598 
1599  // Days passed in current month
1600  $gDayNo += (int)$gd;
1601 
1602  $jDayNo = $gDayNo - 79;
1603 
1604  $jNp = floor( $jDayNo / 12053 );
1605  $jDayNo %= 12053;
1606 
1607  $jy = 979 + 33 * $jNp + 4 * floor( $jDayNo / 1461 );
1608  $jDayNo %= 1461;
1609 
1610  if ( $jDayNo >= 366 ) {
1611  $jy += floor( ( $jDayNo - 1 ) / 365 );
1612  $jDayNo = floor( ( $jDayNo - 1 ) % 365 );
1613  }
1614 
1615  $jz = $jDayNo;
1616 
1617  for ( $i = 0; $i < 11 && $jDayNo >= self::$IRANIAN_DAYS[$i]; $i++ ) {
1618  $jDayNo -= self::$IRANIAN_DAYS[$i];
1619  }
1620 
1621  $jm = $i + 1;
1622  $jd = $jDayNo + 1;
1623 
1624  return [ $jy, $jm, $jd, $jz ];
1625  }
1626 
1638  private static function tsToHijri( $ts ) {
1639  $year = substr( $ts, 0, 4 );
1640  $month = substr( $ts, 4, 2 );
1641  $day = substr( $ts, 6, 2 );
1642 
1643  $zyr = $year;
1644  $zd = $day;
1645  $zm = $month;
1646  $zy = $zyr;
1647 
1648  if (
1649  ( $zy > 1582 ) || ( ( $zy == 1582 ) && ( $zm > 10 ) ) ||
1650  ( ( $zy == 1582 ) && ( $zm == 10 ) && ( $zd > 14 ) )
1651  ) {
1652  $zjd = (int)( ( 1461 * ( $zy + 4800 + (int)( ( $zm - 14 ) / 12 ) ) ) / 4 ) +
1653  (int)( ( 367 * ( $zm - 2 - 12 * ( (int)( ( $zm - 14 ) / 12 ) ) ) ) / 12 ) -
1654  (int)( ( 3 * (int)( ( ( $zy + 4900 + (int)( ( $zm - 14 ) / 12 ) ) / 100 ) ) ) / 4 ) +
1655  $zd - 32075;
1656  } else {
1657  $zjd = 367 * $zy - (int)( ( 7 * ( $zy + 5001 + (int)( ( $zm - 9 ) / 7 ) ) ) / 4 ) +
1658  (int)( ( 275 * $zm ) / 9 ) + $zd + 1729777;
1659  }
1660 
1661  $zl = $zjd -1948440 + 10632;
1662  $zn = (int)( ( $zl - 1 ) / 10631 );
1663  $zl = $zl - 10631 * $zn + 354;
1664  $zj = ( (int)( ( 10985 - $zl ) / 5316 ) ) * ( (int)( ( 50 * $zl ) / 17719 ) ) +
1665  ( (int)( $zl / 5670 ) ) * ( (int)( ( 43 * $zl ) / 15238 ) );
1666  $zl = $zl - ( (int)( ( 30 - $zj ) / 15 ) ) * ( (int)( ( 17719 * $zj ) / 50 ) ) -
1667  ( (int)( $zj / 16 ) ) * ( (int)( ( 15238 * $zj ) / 43 ) ) + 29;
1668  $zm = (int)( ( 24 * $zl ) / 709 );
1669  $zd = $zl - (int)( ( 709 * $zm ) / 24 );
1670  $zy = 30 * $zn + $zj - 30;
1671 
1672  return [ $zy, $zm, $zd ];
1673  }
1674 
1690  private static function tsToHebrew( $ts ) {
1691  # Parse date
1692  $year = substr( $ts, 0, 4 );
1693  $month = substr( $ts, 4, 2 );
1694  $day = substr( $ts, 6, 2 );
1695 
1696  # Calculate Hebrew year
1697  $hebrewYear = $year + 3760;
1698 
1699  # Month number when September = 1, August = 12
1700  $month += 4;
1701  if ( $month > 12 ) {
1702  # Next year
1703  $month -= 12;
1704  $year++;
1705  $hebrewYear++;
1706  }
1707 
1708  # Calculate day of year from 1 September
1709  $dayOfYear = $day;
1710  for ( $i = 1; $i < $month; $i++ ) {
1711  if ( $i == 6 ) {
1712  # February
1713  $dayOfYear += 28;
1714  # Check if the year is leap
1715  if ( $year % 400 == 0 || ( $year % 4 == 0 && $year % 100 > 0 ) ) {
1716  $dayOfYear++;
1717  }
1718  } elseif ( $i == 8 || $i == 10 || $i == 1 || $i == 3 ) {
1719  $dayOfYear += 30;
1720  } else {
1721  $dayOfYear += 31;
1722  }
1723  }
1724 
1725  # Calculate the start of the Hebrew year
1726  $start = self::hebrewYearStart( $hebrewYear );
1727 
1728  # Calculate next year's start
1729  if ( $dayOfYear <= $start ) {
1730  # Day is before the start of the year - it is the previous year
1731  # Next year's start
1732  $nextStart = $start;
1733  # Previous year
1734  $year--;
1735  $hebrewYear--;
1736  # Add days since previous year's 1 September
1737  $dayOfYear += 365;
1738  if ( ( $year % 400 == 0 ) || ( $year % 100 != 0 && $year % 4 == 0 ) ) {
1739  # Leap year
1740  $dayOfYear++;
1741  }
1742  # Start of the new (previous) year
1743  $start = self::hebrewYearStart( $hebrewYear );
1744  } else {
1745  # Next year's start
1746  $nextStart = self::hebrewYearStart( $hebrewYear + 1 );
1747  }
1748 
1749  # Calculate Hebrew day of year
1750  $hebrewDayOfYear = $dayOfYear - $start;
1751 
1752  # Difference between year's days
1753  $diff = $nextStart - $start;
1754  # Add 12 (or 13 for leap years) days to ignore the difference between
1755  # Hebrew and Gregorian year (353 at least vs. 365/6) - now the
1756  # difference is only about the year type
1757  if ( ( $year % 400 == 0 ) || ( $year % 100 != 0 && $year % 4 == 0 ) ) {
1758  $diff += 13;
1759  } else {
1760  $diff += 12;
1761  }
1762 
1763  # Check the year pattern, and is leap year
1764  # 0 means an incomplete year, 1 means a regular year, 2 means a complete year
1765  # This is mod 30, to work on both leap years (which add 30 days of Adar I)
1766  # and non-leap years
1767  $yearPattern = $diff % 30;
1768  # Check if leap year
1769  $isLeap = $diff >= 30;
1770 
1771  # Calculate day in the month from number of day in the Hebrew year
1772  # Don't check Adar - if the day is not in Adar, we will stop before;
1773  # if it is in Adar, we will use it to check if it is Adar I or Adar II
1774  $hebrewDay = $hebrewDayOfYear;
1775  $hebrewMonth = 1;
1776  $days = 0;
1777  while ( $hebrewMonth <= 12 ) {
1778  # Calculate days in this month
1779  if ( $isLeap && $hebrewMonth == 6 ) {
1780  # Adar in a leap year
1781  if ( $isLeap ) {
1782  # Leap year - has Adar I, with 30 days, and Adar II, with 29 days
1783  $days = 30;
1784  if ( $hebrewDay <= $days ) {
1785  # Day in Adar I
1786  $hebrewMonth = 13;
1787  } else {
1788  # Subtract the days of Adar I
1789  $hebrewDay -= $days;
1790  # Try Adar II
1791  $days = 29;
1792  if ( $hebrewDay <= $days ) {
1793  # Day in Adar II
1794  $hebrewMonth = 14;
1795  }
1796  }
1797  }
1798  } elseif ( $hebrewMonth == 2 && $yearPattern == 2 ) {
1799  # Cheshvan in a complete year (otherwise as the rule below)
1800  $days = 30;
1801  } elseif ( $hebrewMonth == 3 && $yearPattern == 0 ) {
1802  # Kislev in an incomplete year (otherwise as the rule below)
1803  $days = 29;
1804  } else {
1805  # Odd months have 30 days, even have 29
1806  $days = 30 - ( $hebrewMonth - 1 ) % 2;
1807  }
1808  if ( $hebrewDay <= $days ) {
1809  # In the current month
1810  break;
1811  } else {
1812  # Subtract the days of the current month
1813  $hebrewDay -= $days;
1814  # Try in the next month
1815  $hebrewMonth++;
1816  }
1817  }
1818 
1819  return [ $hebrewYear, $hebrewMonth, $hebrewDay, $days ];
1820  }
1821 
1831  private static function hebrewYearStart( $year ) {
1832  $a = intval( ( 12 * ( $year - 1 ) + 17 ) % 19 );
1833  $b = intval( ( $year - 1 ) % 4 );
1834  $m = 32.044093161144 + 1.5542417966212 * $a + $b / 4.0 - 0.0031777940220923 * ( $year - 1 );
1835  if ( $m < 0 ) {
1836  $m--;
1837  }
1838  $Mar = intval( $m );
1839  if ( $m < 0 ) {
1840  $m++;
1841  }
1842  $m -= $Mar;
1843 
1844  $c = intval( ( $Mar + 3 * ( $year - 1 ) + 5 * $b + 5 ) % 7 );
1845  if ( $c == 0 && $a > 11 && $m >= 0.89772376543210 ) {
1846  $Mar++;
1847  } elseif ( $c == 1 && $a > 6 && $m >= 0.63287037037037 ) {
1848  $Mar += 2;
1849  } elseif ( $c == 2 || $c == 4 || $c == 6 ) {
1850  $Mar++;
1851  }
1852 
1853  $Mar += intval( ( $year - 3761 ) / 100 ) - intval( ( $year - 3761 ) / 400 ) - 24;
1854  return $Mar;
1855  }
1856 
1869  private static function tsToYear( $ts, $cName ) {
1870  $gy = substr( $ts, 0, 4 );
1871  $gm = substr( $ts, 4, 2 );
1872  $gd = substr( $ts, 6, 2 );
1873 
1874  if ( !strcmp( $cName, 'thai' ) ) {
1875  # Thai solar dates
1876  # Add 543 years to the Gregorian calendar
1877  # Months and days are identical
1878  $gy_offset = $gy + 543;
1879  } elseif ( ( !strcmp( $cName, 'minguo' ) ) || !strcmp( $cName, 'juche' ) ) {
1880  # Minguo dates
1881  # Deduct 1911 years from the Gregorian calendar
1882  # Months and days are identical
1883  $gy_offset = $gy - 1911;
1884  } elseif ( !strcmp( $cName, 'tenno' ) ) {
1885  # Nengō dates up to Meiji period
1886  # Deduct years from the Gregorian calendar
1887  # depending on the nengo periods
1888  # Months and days are identical
1889  if ( ( $gy < 1912 )
1890  || ( ( $gy == 1912 ) && ( $gm < 7 ) )
1891  || ( ( $gy == 1912 ) && ( $gm == 7 ) && ( $gd < 31 ) )
1892  ) {
1893  # Meiji period
1894  $gy_gannen = $gy - 1868 + 1;
1895  $gy_offset = $gy_gannen;
1896  if ( $gy_gannen == 1 ) {
1897  $gy_offset = '元';
1898  }
1899  $gy_offset = '明治' . $gy_offset;
1900  } elseif (
1901  ( ( $gy == 1912 ) && ( $gm == 7 ) && ( $gd == 31 ) ) ||
1902  ( ( $gy == 1912 ) && ( $gm >= 8 ) ) ||
1903  ( ( $gy > 1912 ) && ( $gy < 1926 ) ) ||
1904  ( ( $gy == 1926 ) && ( $gm < 12 ) ) ||
1905  ( ( $gy == 1926 ) && ( $gm == 12 ) && ( $gd < 26 ) )
1906  ) {
1907  # Taishō period
1908  $gy_gannen = $gy - 1912 + 1;
1909  $gy_offset = $gy_gannen;
1910  if ( $gy_gannen == 1 ) {
1911  $gy_offset = '元';
1912  }
1913  $gy_offset = '大正' . $gy_offset;
1914  } elseif (
1915  ( ( $gy == 1926 ) && ( $gm == 12 ) && ( $gd >= 26 ) ) ||
1916  ( ( $gy > 1926 ) && ( $gy < 1989 ) ) ||
1917  ( ( $gy == 1989 ) && ( $gm == 1 ) && ( $gd < 8 ) )
1918  ) {
1919  # Shōwa period
1920  $gy_gannen = $gy - 1926 + 1;
1921  $gy_offset = $gy_gannen;
1922  if ( $gy_gannen == 1 ) {
1923  $gy_offset = '元';
1924  }
1925  $gy_offset = '昭和' . $gy_offset;
1926  } else {
1927  # Heisei period
1928  $gy_gannen = $gy - 1989 + 1;
1929  $gy_offset = $gy_gannen;
1930  if ( $gy_gannen == 1 ) {
1931  $gy_offset = '元';
1932  }
1933  $gy_offset = '平成' . $gy_offset;
1934  }
1935  } else {
1936  $gy_offset = $gy;
1937  }
1938 
1939  return [ $gy_offset, $gm, $gd ];
1940  }
1941 
1955  private static function strongDirFromContent( $text = '' ) {
1956  if ( !preg_match( self::$strongDirRegex, $text, $matches ) ) {
1957  return null;
1958  }
1959  if ( $matches[1] === '' ) {
1960  return 'rtl';
1961  }
1962  return 'ltr';
1963  }
1964 
1972  static function romanNumeral( $num ) {
1973  static $table = [
1974  [ '', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X' ],
1975  [ '', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC', 'C' ],
1976  [ '', 'C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM', 'M' ],
1977  [ '', 'M', 'MM', 'MMM', 'MMMM', 'MMMMM', 'MMMMMM', 'MMMMMMM',
1978  'MMMMMMMM', 'MMMMMMMMM', 'MMMMMMMMMM' ]
1979  ];
1980 
1981  $num = intval( $num );
1982  if ( $num > 10000 || $num <= 0 ) {
1983  return $num;
1984  }
1985 
1986  $s = '';
1987  for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) {
1988  if ( $num >= $pow10 ) {
1989  $s .= $table[$i][(int)floor( $num / $pow10 )];
1990  }
1991  $num = $num % $pow10;
1992  }
1993  return $s;
1994  }
1995 
2003  static function hebrewNumeral( $num ) {
2004  static $table = [
2005  [ '', 'א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ז', 'ח', 'ט', 'י' ],
2006  [ '', 'י', 'כ', 'ל', 'מ', 'נ', 'ס', 'ע', 'פ', 'צ', 'ק' ],
2007  [ '',
2008  [ 'ק' ],
2009  [ 'ר' ],
2010  [ 'ש' ],
2011  [ 'ת' ],
2012  [ 'ת', 'ק' ],
2013  [ 'ת', 'ר' ],
2014  [ 'ת', 'ש' ],
2015  [ 'ת', 'ת' ],
2016  [ 'ת', 'ת', 'ק' ],
2017  [ 'ת', 'ת', 'ר' ],
2018  ],
2019  [ '', 'א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ז', 'ח', 'ט', 'י' ]
2020  ];
2021 
2022  $num = intval( $num );
2023  if ( $num > 9999 || $num <= 0 ) {
2024  return $num;
2025  }
2026 
2027  // Round thousands have special notations
2028  if ( $num === 1000 ) {
2029  return "א' אלף";
2030  } elseif ( $num % 1000 === 0 ) {
2031  return $table[0][$num / 1000] . "' אלפים";
2032  }
2033 
2034  $letters = [];
2035 
2036  for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) {
2037  if ( $num >= $pow10 ) {
2038  if ( $num === 15 || $num === 16 ) {
2039  $letters[] = $table[0][9];
2040  $letters[] = $table[0][$num - 9];
2041  $num = 0;
2042  } else {
2043  $letters = array_merge(
2044  $letters,
2045  (array)$table[$i][intval( $num / $pow10 )]
2046  );
2047 
2048  if ( $pow10 === 1000 ) {
2049  $letters[] = "'";
2050  }
2051  }
2052  }
2053 
2054  $num = $num % $pow10;
2055  }
2056 
2057  $preTransformLength = count( $letters );
2058  if ( $preTransformLength === 1 ) {
2059  // Add geresh (single quote) to one-letter numbers
2060  $letters[] = "'";
2061  } else {
2062  $lastIndex = $preTransformLength - 1;
2063  $letters[$lastIndex] = str_replace(
2064  [ 'כ', 'מ', 'נ', 'פ', 'צ' ],
2065  [ 'ך', 'ם', 'ן', 'ף', 'ץ' ],
2066  $letters[$lastIndex]
2067  );
2068 
2069  // Add gershayim (double quote) to multiple-letter numbers,
2070  // but exclude numbers with only one letter after the thousands
2071  // (1001-1009, 1020, 1030, 2001-2009, etc.)
2072  if ( $letters[1] === "'" && $preTransformLength === 3 ) {
2073  $letters[] = "'";
2074  } else {
2075  array_splice( $letters, -1, 0, '"' );
2076  }
2077  }
2078 
2079  return implode( $letters );
2080  }
2081 
2090  public function userAdjust( $ts, $tz = false ) {
2092 
2093  if ( $tz === false ) {
2094  $tz = $wgUser->getOption( 'timecorrection' );
2095  }
2096 
2097  $data = explode( '|', $tz, 3 );
2098 
2099  if ( $data[0] == 'ZoneInfo' ) {
2100  MediaWiki\suppressWarnings();
2101  $userTZ = timezone_open( $data[2] );
2102  MediaWiki\restoreWarnings();
2103  if ( $userTZ !== false ) {
2104  $date = date_create( $ts, timezone_open( 'UTC' ) );
2105  date_timezone_set( $date, $userTZ );
2106  $date = date_format( $date, 'YmdHis' );
2107  return $date;
2108  }
2109  # Unrecognized timezone, default to 'Offset' with the stored offset.
2110  $data[0] = 'Offset';
2111  }
2112 
2113  if ( $data[0] == 'System' || $tz == '' ) {
2114  # Global offset in minutes.
2115  $minDiff = $wgLocalTZoffset;
2116  } elseif ( $data[0] == 'Offset' ) {
2117  $minDiff = intval( $data[1] );
2118  } else {
2119  $data = explode( ':', $tz );
2120  if ( count( $data ) == 2 ) {
2121  $data[0] = intval( $data[0] );
2122  $data[1] = intval( $data[1] );
2123  $minDiff = abs( $data[0] ) * 60 + $data[1];
2124  if ( $data[0] < 0 ) {
2125  $minDiff = -$minDiff;
2126  }
2127  } else {
2128  $minDiff = intval( $data[0] ) * 60;
2129  }
2130  }
2131 
2132  # No difference ? Return time unchanged
2133  if ( 0 == $minDiff ) {
2134  return $ts;
2135  }
2136 
2137  MediaWiki\suppressWarnings(); // E_STRICT system time bitching
2138  # Generate an adjusted date; take advantage of the fact that mktime
2139  # will normalize out-of-range values so we don't have to split $minDiff
2140  # into hours and minutes.
2141  $t = mktime( (
2142  (int)substr( $ts, 8, 2 ) ), # Hours
2143  (int)substr( $ts, 10, 2 ) + $minDiff, # Minutes
2144  (int)substr( $ts, 12, 2 ), # Seconds
2145  (int)substr( $ts, 4, 2 ), # Month
2146  (int)substr( $ts, 6, 2 ), # Day
2147  (int)substr( $ts, 0, 4 ) ); # Year
2148 
2149  $date = date( 'YmdHis', $t );
2150  MediaWiki\restoreWarnings();
2151 
2152  return $date;
2153  }
2154 
2172  function dateFormat( $usePrefs = true ) {
2173  global $wgUser;
2174 
2175  if ( is_bool( $usePrefs ) ) {
2176  if ( $usePrefs ) {
2177  $datePreference = $wgUser->getDatePreference();
2178  } else {
2179  $datePreference = (string)User::getDefaultOption( 'date' );
2180  }
2181  } else {
2182  $datePreference = (string)$usePrefs;
2183  }
2184 
2185  // return int
2186  if ( $datePreference == '' ) {
2187  return 'default';
2188  }
2189 
2190  return $datePreference;
2191  }
2192 
2203  function getDateFormatString( $type, $pref ) {
2204  $wasDefault = false;
2205  if ( $pref == 'default' ) {
2206  $wasDefault = true;
2207  $pref = $this->getDefaultDateFormat();
2208  }
2209 
2210  if ( !isset( $this->dateFormatStrings[$type][$pref] ) ) {
2211  $df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" );
2212 
2213  if ( $type === 'pretty' && $df === null ) {
2214  $df = $this->getDateFormatString( 'date', $pref );
2215  }
2216 
2217  if ( !$wasDefault && $df === null ) {
2218  $pref = $this->getDefaultDateFormat();
2219  $df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" );
2220  }
2221 
2222  $this->dateFormatStrings[$type][$pref] = $df;
2223  }
2224  return $this->dateFormatStrings[$type][$pref];
2225  }
2226 
2237  public function date( $ts, $adj = false, $format = true, $timecorrection = false ) {
2238  $ts = wfTimestamp( TS_MW, $ts );
2239  if ( $adj ) {
2240  $ts = $this->userAdjust( $ts, $timecorrection );
2241  }
2242  $df = $this->getDateFormatString( 'date', $this->dateFormat( $format ) );
2243  return $this->sprintfDate( $df, $ts );
2244  }
2245 
2256  public function time( $ts, $adj = false, $format = true, $timecorrection = false ) {
2257  $ts = wfTimestamp( TS_MW, $ts );
2258  if ( $adj ) {
2259  $ts = $this->userAdjust( $ts, $timecorrection );
2260  }
2261  $df = $this->getDateFormatString( 'time', $this->dateFormat( $format ) );
2262  return $this->sprintfDate( $df, $ts );
2263  }
2264 
2276  public function timeanddate( $ts, $adj = false, $format = true, $timecorrection = false ) {
2277  $ts = wfTimestamp( TS_MW, $ts );
2278  if ( $adj ) {
2279  $ts = $this->userAdjust( $ts, $timecorrection );
2280  }
2281  $df = $this->getDateFormatString( 'both', $this->dateFormat( $format ) );
2282  return $this->sprintfDate( $df, $ts );
2283  }
2284 
2295  public function formatDuration( $seconds, array $chosenIntervals = [] ) {
2296  $intervals = $this->getDurationIntervals( $seconds, $chosenIntervals );
2297 
2298  $segments = [];
2299 
2300  foreach ( $intervals as $intervalName => $intervalValue ) {
2301  // Messages: duration-seconds, duration-minutes, duration-hours, duration-days, duration-weeks,
2302  // duration-years, duration-decades, duration-centuries, duration-millennia
2303  $message = wfMessage( 'duration-' . $intervalName )->numParams( $intervalValue );
2304  $segments[] = $message->inLanguage( $this )->escaped();
2305  }
2306 
2307  return $this->listToText( $segments );
2308  }
2309 
2321  public function getDurationIntervals( $seconds, array $chosenIntervals = [] ) {
2322  if ( empty( $chosenIntervals ) ) {
2323  $chosenIntervals = [
2324  'millennia',
2325  'centuries',
2326  'decades',
2327  'years',
2328  'days',
2329  'hours',
2330  'minutes',
2331  'seconds'
2332  ];
2333  }
2334 
2335  $intervals = array_intersect_key( self::$durationIntervals, array_flip( $chosenIntervals ) );
2336  $sortedNames = array_keys( $intervals );
2337  $smallestInterval = array_pop( $sortedNames );
2338 
2339  $segments = [];
2340 
2341  foreach ( $intervals as $name => $length ) {
2342  $value = floor( $seconds / $length );
2343 
2344  if ( $value > 0 || ( $name == $smallestInterval && empty( $segments ) ) ) {
2345  $seconds -= $value * $length;
2346  $segments[$name] = $value;
2347  }
2348  }
2349 
2350  return $segments;
2351  }
2352 
2372  private function internalUserTimeAndDate( $type, $ts, User $user, array $options ) {
2373  $ts = wfTimestamp( TS_MW, $ts );
2374  $options += [ 'timecorrection' => true, 'format' => true ];
2375  if ( $options['timecorrection'] !== false ) {
2376  if ( $options['timecorrection'] === true ) {
2377  $offset = $user->getOption( 'timecorrection' );
2378  } else {
2379  $offset = $options['timecorrection'];
2380  }
2381  $ts = $this->userAdjust( $ts, $offset );
2382  }
2383  if ( $options['format'] === true ) {
2384  $format = $user->getDatePreference();
2385  } else {
2386  $format = $options['format'];
2387  }
2388  $df = $this->getDateFormatString( $type, $this->dateFormat( $format ) );
2389  return $this->sprintfDate( $df, $ts );
2390  }
2391 
2411  public function userDate( $ts, User $user, array $options = [] ) {
2412  return $this->internalUserTimeAndDate( 'date', $ts, $user, $options );
2413  }
2414 
2434  public function userTime( $ts, User $user, array $options = [] ) {
2435  return $this->internalUserTimeAndDate( 'time', $ts, $user, $options );
2436  }
2437 
2457  public function userTimeAndDate( $ts, User $user, array $options = [] ) {
2458  return $this->internalUserTimeAndDate( 'both', $ts, $user, $options );
2459  }
2460 
2476  public function getHumanTimestamp(
2477  MWTimestamp $time, MWTimestamp $relativeTo = null, User $user = null
2478  ) {
2479  if ( $relativeTo === null ) {
2480  $relativeTo = new MWTimestamp();
2481  }
2482  if ( $user === null ) {
2483  $user = RequestContext::getMain()->getUser();
2484  }
2485 
2486  // Adjust for the user's timezone.
2487  $offsetThis = $time->offsetForUser( $user );
2488  $offsetRel = $relativeTo->offsetForUser( $user );
2489 
2490  $ts = '';
2491  if ( Hooks::run( 'GetHumanTimestamp', [ &$ts, $time, $relativeTo, $user, $this ] ) ) {
2492  $ts = $this->getHumanTimestampInternal( $time, $relativeTo, $user );
2493  }
2494 
2495  // Reset the timezone on the objects.
2496  $time->timestamp->sub( $offsetThis );
2497  $relativeTo->timestamp->sub( $offsetRel );
2498 
2499  return $ts;
2500  }
2501 
2513  private function getHumanTimestampInternal(
2514  MWTimestamp $ts, MWTimestamp $relativeTo, User $user
2515  ) {
2516  $diff = $ts->diff( $relativeTo );
2517  $diffDay = (bool)( (int)$ts->timestamp->format( 'w' ) -
2518  (int)$relativeTo->timestamp->format( 'w' ) );
2519  $days = $diff->days ?: (int)$diffDay;
2520  if ( $diff->invert || $days > 5
2521  && $ts->timestamp->format( 'Y' ) !== $relativeTo->timestamp->format( 'Y' )
2522  ) {
2523  // Timestamps are in different years: use full timestamp
2524  // Also do full timestamp for future dates
2528  $format = $this->getDateFormatString( 'both', $user->getDatePreference() ?: 'default' );
2529  $ts = $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) );
2530  } elseif ( $days > 5 ) {
2531  // Timestamps are in same year, but more than 5 days ago: show day and month only.
2532  $format = $this->getDateFormatString( 'pretty', $user->getDatePreference() ?: 'default' );
2533  $ts = $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) );
2534  } elseif ( $days > 1 ) {
2535  // Timestamp within the past week: show the day of the week and time
2536  $format = $this->getDateFormatString( 'time', $user->getDatePreference() ?: 'default' );
2537  $weekday = self::$mWeekdayMsgs[$ts->timestamp->format( 'w' )];
2538  // Messages:
2539  // sunday-at, monday-at, tuesday-at, wednesday-at, thursday-at, friday-at, saturday-at
2540  $ts = wfMessage( "$weekday-at" )
2541  ->inLanguage( $this )
2542  ->params( $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) ) )
2543  ->text();
2544  } elseif ( $days == 1 ) {
2545  // Timestamp was yesterday: say 'yesterday' and the time.
2546  $format = $this->getDateFormatString( 'time', $user->getDatePreference() ?: 'default' );
2547  $ts = wfMessage( 'yesterday-at' )
2548  ->inLanguage( $this )
2549  ->params( $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) ) )
2550  ->text();
2551  } elseif ( $diff->h > 1 || $diff->h == 1 && $diff->i > 30 ) {
2552  // Timestamp was today, but more than 90 minutes ago: say 'today' and the time.
2553  $format = $this->getDateFormatString( 'time', $user->getDatePreference() ?: 'default' );
2554  $ts = wfMessage( 'today-at' )
2555  ->inLanguage( $this )
2556  ->params( $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) ) )
2557  ->text();
2558 
2559  // From here on in, the timestamp was soon enough ago so that we can simply say
2560  // XX units ago, e.g., "2 hours ago" or "5 minutes ago"
2561  } elseif ( $diff->h == 1 ) {
2562  // Less than 90 minutes, but more than an hour ago.
2563  $ts = wfMessage( 'hours-ago' )->inLanguage( $this )->numParams( 1 )->text();
2564  } elseif ( $diff->i >= 1 ) {
2565  // A few minutes ago.
2566  $ts = wfMessage( 'minutes-ago' )->inLanguage( $this )->numParams( $diff->i )->text();
2567  } elseif ( $diff->s >= 30 ) {
2568  // Less than a minute, but more than 30 sec ago.
2569  $ts = wfMessage( 'seconds-ago' )->inLanguage( $this )->numParams( $diff->s )->text();
2570  } else {
2571  // Less than 30 seconds ago.
2572  $ts = wfMessage( 'just-now' )->text();
2573  }
2574 
2575  return $ts;
2576  }
2577 
2582  public function getMessage( $key ) {
2583  return self::$dataCache->getSubitem( $this->mCode, 'messages', $key );
2584  }
2585 
2589  function getAllMessages() {
2590  return self::$dataCache->getItem( $this->mCode, 'messages' );
2591  }
2592 
2599  public function iconv( $in, $out, $string ) {
2600  # Even with //IGNORE iconv can whine about illegal characters in
2601  # *input* string. We just ignore those too.
2602  # REF: https://bugs.php.net/bug.php?id=37166
2603  # REF: https://phabricator.wikimedia.org/T18885
2604  MediaWiki\suppressWarnings();
2605  $text = iconv( $in, $out . '//IGNORE', $string );
2606  MediaWiki\restoreWarnings();
2607  return $text;
2608  }
2609 
2610  // callback functions for ucwords(), ucwordbreaks()
2611 
2617  return $this->ucfirst( $matches[1] );
2618  }
2619 
2625  return mb_strtoupper( $matches[0] );
2626  }
2627 
2633  return mb_strtoupper( $matches[0] );
2634  }
2635 
2643  public function ucfirst( $str ) {
2644  $o = ord( $str );
2645  if ( $o < 96 ) { // if already uppercase...
2646  return $str;
2647  } elseif ( $o < 128 ) {
2648  return ucfirst( $str ); // use PHP's ucfirst()
2649  } else {
2650  // fall back to more complex logic in case of multibyte strings
2651  return $this->uc( $str, true );
2652  }
2653  }
2654 
2663  public function uc( $str, $first = false ) {
2664  if ( $first ) {
2665  if ( $this->isMultibyte( $str ) ) {
2666  return mb_strtoupper( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
2667  } else {
2668  return ucfirst( $str );
2669  }
2670  } else {
2671  return $this->isMultibyte( $str ) ? mb_strtoupper( $str ) : strtoupper( $str );
2672  }
2673  }
2674 
2679  function lcfirst( $str ) {
2680  $o = ord( $str );
2681  if ( !$o ) {
2682  return strval( $str );
2683  } elseif ( $o >= 128 ) {
2684  return $this->lc( $str, true );
2685  } elseif ( $o > 96 ) {
2686  return $str;
2687  } else {
2688  $str[0] = strtolower( $str[0] );
2689  return $str;
2690  }
2691  }
2692 
2698  function lc( $str, $first = false ) {
2699  if ( $first ) {
2700  if ( $this->isMultibyte( $str ) ) {
2701  return mb_strtolower( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
2702  } else {
2703  return strtolower( substr( $str, 0, 1 ) ) . substr( $str, 1 );
2704  }
2705  } else {
2706  return $this->isMultibyte( $str ) ? mb_strtolower( $str ) : strtolower( $str );
2707  }
2708  }
2709 
2714  function isMultibyte( $str ) {
2715  return strlen( $str ) !== mb_strlen( $str );
2716  }
2717 
2722  function ucwords( $str ) {
2723  if ( $this->isMultibyte( $str ) ) {
2724  $str = $this->lc( $str );
2725 
2726  // regexp to find first letter in each word (i.e. after each space)
2727  $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)| ([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
2728 
2729  // function to use to capitalize a single char
2730  return preg_replace_callback(
2731  $replaceRegexp,
2732  [ $this, 'ucwordsCallbackMB' ],
2733  $str
2734  );
2735  } else {
2736  return ucwords( strtolower( $str ) );
2737  }
2738  }
2739 
2746  function ucwordbreaks( $str ) {
2747  if ( $this->isMultibyte( $str ) ) {
2748  $str = $this->lc( $str );
2749 
2750  // since \b doesn't work for UTF-8, we explicitely define word break chars
2751  $breaks = "[ \-\(\)\}\{\.,\?!]";
2752 
2753  // find first letter after word break
2754  $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)|" .
2755  "$breaks([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
2756 
2757  return preg_replace_callback(
2758  $replaceRegexp,
2759  [ $this, 'ucwordbreaksCallbackMB' ],
2760  $str
2761  );
2762  } else {
2763  return preg_replace_callback(
2764  '/\b([\w\x80-\xff]+)\b/',
2765  [ $this, 'ucwordbreaksCallbackAscii' ],
2766  $str
2767  );
2768  }
2769  }
2770 
2786  function caseFold( $s ) {
2787  return $this->uc( $s );
2788  }
2789 
2795  function checkTitleEncoding( $s ) {
2796  if ( is_array( $s ) ) {
2797  throw new MWException( 'Given array to checkTitleEncoding.' );
2798  }
2799  if ( StringUtils::isUtf8( $s ) ) {
2800  return $s;
2801  }
2802 
2803  return $this->iconv( $this->fallback8bitEncoding(), 'utf-8', $s );
2804  }
2805 
2810  return self::$dataCache->getItem( $this->mCode, 'fallback8bitEncoding' );
2811  }
2812 
2821  function hasWordBreaks() {
2822  return true;
2823  }
2824 
2832  function segmentByWord( $string ) {
2833  return $string;
2834  }
2835 
2843  function normalizeForSearch( $string ) {
2844  return self::convertDoubleWidth( $string );
2845  }
2846 
2855  protected static function convertDoubleWidth( $string ) {
2856  static $full = null;
2857  static $half = null;
2858 
2859  if ( $full === null ) {
2860  $fullWidth = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
2861  $halfWidth = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
2862  $full = str_split( $fullWidth, 3 );
2863  $half = str_split( $halfWidth );
2864  }
2865 
2866  $string = str_replace( $full, $half, $string );
2867  return $string;
2868  }
2869 
2875  protected static function insertSpace( $string, $pattern ) {
2876  $string = preg_replace( $pattern, " $1 ", $string );
2877  $string = preg_replace( '/ +/', ' ', $string );
2878  return $string;
2879  }
2880 
2885  function convertForSearchResult( $termsArray ) {
2886  # some languages, e.g. Chinese, need to do a conversion
2887  # in order for search results to be displayed correctly
2888  return $termsArray;
2889  }
2890 
2897  function firstChar( $s ) {
2898  $matches = [];
2899  preg_match(
2900  '/^([\x00-\x7f]|[\xc0-\xdf][\x80-\xbf]|' .
2901  '[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xf7][\x80-\xbf]{3})/',
2902  $s,
2903  $matches
2904  );
2905 
2906  if ( isset( $matches[1] ) ) {
2907  if ( strlen( $matches[1] ) != 3 ) {
2908  return $matches[1];
2909  }
2910 
2911  // Break down Hangul syllables to grab the first jamo
2913  if ( $code < 0xac00 || 0xd7a4 <= $code ) {
2914  return $matches[1];
2915  } elseif ( $code < 0xb098 ) {
2916  return "\xe3\x84\xb1";
2917  } elseif ( $code < 0xb2e4 ) {
2918  return "\xe3\x84\xb4";
2919  } elseif ( $code < 0xb77c ) {
2920  return "\xe3\x84\xb7";
2921  } elseif ( $code < 0xb9c8 ) {
2922  return "\xe3\x84\xb9";
2923  } elseif ( $code < 0xbc14 ) {
2924  return "\xe3\x85\x81";
2925  } elseif ( $code < 0xc0ac ) {
2926  return "\xe3\x85\x82";
2927  } elseif ( $code < 0xc544 ) {
2928  return "\xe3\x85\x85";
2929  } elseif ( $code < 0xc790 ) {
2930  return "\xe3\x85\x87";
2931  } elseif ( $code < 0xcc28 ) {
2932  return "\xe3\x85\x88";
2933  } elseif ( $code < 0xce74 ) {
2934  return "\xe3\x85\x8a";
2935  } elseif ( $code < 0xd0c0 ) {
2936  return "\xe3\x85\x8b";
2937  } elseif ( $code < 0xd30c ) {
2938  return "\xe3\x85\x8c";
2939  } elseif ( $code < 0xd558 ) {
2940  return "\xe3\x85\x8d";
2941  } else {
2942  return "\xe3\x85\x8e";
2943  }
2944  } else {
2945  return '';
2946  }
2947  }
2948 
2952  function initEncoding() {
2953  // No-op.
2954  }
2955 
2961  function recodeForEdit( $s ) {
2962  return $s;
2963  }
2964 
2970  function recodeInput( $s ) {
2971  return $s;
2972  }
2973 
2985  function normalize( $s ) {
2987  $s = UtfNormal\Validator::cleanUp( $s );
2988  if ( $wgAllUnicodeFixes ) {
2989  $s = $this->transformUsingPairFile( 'normalize-ar.ser', $s );
2990  $s = $this->transformUsingPairFile( 'normalize-ml.ser', $s );
2991  }
2992 
2993  return $s;
2994  }
2995 
3010  function transformUsingPairFile( $file, $string ) {
3011  if ( !isset( $this->transformData[$file] ) ) {
3012  $data = wfGetPrecompiledData( $file );
3013  if ( $data === false ) {
3014  throw new MWException( __METHOD__ . ": The transformation file $file is missing" );
3015  }
3016  $this->transformData[$file] = new ReplacementArray( $data );
3017  }
3018  return $this->transformData[$file]->replace( $string );
3019  }
3020 
3026  function isRTL() {
3027  return self::$dataCache->getItem( $this->mCode, 'rtl' );
3028  }
3029 
3034  function getDir() {
3035  return $this->isRTL() ? 'rtl' : 'ltr';
3036  }
3037 
3046  function alignStart() {
3047  return $this->isRTL() ? 'right' : 'left';
3048  }
3049 
3058  function alignEnd() {
3059  return $this->isRTL() ? 'left' : 'right';
3060  }
3061 
3073  function getDirMarkEntity( $opposite = false ) {
3074  if ( $opposite ) {
3075  return $this->isRTL() ? '&lrm;' : '&rlm;';
3076  }
3077  return $this->isRTL() ? '&rlm;' : '&lrm;';
3078  }
3079 
3090  function getDirMark( $opposite = false ) {
3091  $lrm = "\xE2\x80\x8E"; # LEFT-TO-RIGHT MARK, commonly abbreviated LRM
3092  $rlm = "\xE2\x80\x8F"; # RIGHT-TO-LEFT MARK, commonly abbreviated RLM
3093  if ( $opposite ) {
3094  return $this->isRTL() ? $lrm : $rlm;
3095  }
3096  return $this->isRTL() ? $rlm : $lrm;
3097  }
3098 
3102  function capitalizeAllNouns() {
3103  return self::$dataCache->getItem( $this->mCode, 'capitalizeAllNouns' );
3104  }
3105 
3113  function getArrow( $direction = 'forwards' ) {
3114  switch ( $direction ) {
3115  case 'forwards':
3116  return $this->isRTL() ? '←' : '→';
3117  case 'backwards':
3118  return $this->isRTL() ? '→' : '←';
3119  case 'left':
3120  return '←';
3121  case 'right':
3122  return '→';
3123  case 'up':
3124  return '↑';
3125  case 'down':
3126  return '↓';
3127  }
3128  }
3129 
3135  function linkPrefixExtension() {
3136  return self::$dataCache->getItem( $this->mCode, 'linkPrefixExtension' );
3137  }
3138 
3143  function getMagicWords() {
3144  return self::$dataCache->getItem( $this->mCode, 'magicWords' );
3145  }
3146 
3150  protected function doMagicHook() {
3151  if ( $this->mMagicHookDone ) {
3152  return;
3153  }
3154  $this->mMagicHookDone = true;
3155  Hooks::run( 'LanguageGetMagic', [ &$this->mMagicExtensions, $this->getCode() ] );
3156  }
3157 
3163  function getMagic( $mw ) {
3164  // Saves a function call
3165  if ( !$this->mMagicHookDone ) {
3166  $this->doMagicHook();
3167  }
3168 
3169  if ( isset( $this->mMagicExtensions[$mw->mId] ) ) {
3170  $rawEntry = $this->mMagicExtensions[$mw->mId];
3171  } else {
3172  $rawEntry = self::$dataCache->getSubitem(
3173  $this->mCode, 'magicWords', $mw->mId );
3174  }
3175 
3176  if ( !is_array( $rawEntry ) ) {
3177  wfWarn( "\"$rawEntry\" is not a valid magic word for \"$mw->mId\"" );
3178  } else {
3179  $mw->mCaseSensitive = $rawEntry[0];
3180  $mw->mSynonyms = array_slice( $rawEntry, 1 );
3181  }
3182  }
3183 
3189  function addMagicWordsByLang( $newWords ) {
3190  $fallbackChain = $this->getFallbackLanguages();
3191  $fallbackChain = array_reverse( $fallbackChain );
3192  foreach ( $fallbackChain as $code ) {
3193  if ( isset( $newWords[$code] ) ) {
3194  $this->mMagicExtensions = $newWords[$code] + $this->mMagicExtensions;
3195  }
3196  }
3197  }
3198 
3205  // Cache aliases because it may be slow to load them
3206  if ( is_null( $this->mExtendedSpecialPageAliases ) ) {
3207  // Initialise array
3208  $this->mExtendedSpecialPageAliases =
3209  self::$dataCache->getItem( $this->mCode, 'specialPageAliases' );
3210  Hooks::run( 'LanguageGetSpecialPageAliases',
3211  [ &$this->mExtendedSpecialPageAliases, $this->getCode() ] );
3212  }
3213 
3215  }
3216 
3223  function emphasize( $text ) {
3224  return "<em>$text</em>";
3225  }
3226 
3249  public function formatNum( $number, $nocommafy = false ) {
3251  if ( !$nocommafy ) {
3252  $number = $this->commafy( $number );
3253  $s = $this->separatorTransformTable();
3254  if ( $s ) {
3255  $number = strtr( $number, $s );
3256  }
3257  }
3258 
3259  if ( $wgTranslateNumerals ) {
3260  $s = $this->digitTransformTable();
3261  if ( $s ) {
3262  $number = strtr( $number, $s );
3263  }
3264  }
3265 
3266  return $number;
3267  }
3268 
3277  public function formatNumNoSeparators( $number ) {
3278  return $this->formatNum( $number, true );
3279  }
3280 
3285  public function parseFormattedNumber( $number ) {
3286  $s = $this->digitTransformTable();
3287  if ( $s ) {
3288  // eliminate empty array values such as ''. (bug 64347)
3289  $s = array_filter( $s );
3290  $number = strtr( $number, array_flip( $s ) );
3291  }
3292 
3293  $s = $this->separatorTransformTable();
3294  if ( $s ) {
3295  // eliminate empty array values such as ''. (bug 64347)
3296  $s = array_filter( $s );
3297  $number = strtr( $number, array_flip( $s ) );
3298  }
3299 
3300  $number = strtr( $number, [ ',' => '' ] );
3301  return $number;
3302  }
3303 
3310  function commafy( $number ) {
3312  if ( $number === null ) {
3313  return '';
3314  }
3315 
3316  if ( !$digitGroupingPattern || $digitGroupingPattern === "###,###,###" ) {
3317  // default grouping is at thousands, use the same for ###,###,### pattern too.
3318  return strrev( (string)preg_replace( '/(\d{3})(?=\d)(?!\d*\.)/', '$1,', strrev( $number ) ) );
3319  } else {
3320  // Ref: http://cldr.unicode.org/translation/number-patterns
3321  $sign = "";
3322  if ( intval( $number ) < 0 ) {
3323  // For negative numbers apply the algorithm like positive number and add sign.
3324  $sign = "-";
3325  $number = substr( $number, 1 );
3326  }
3327  $integerPart = [];
3328  $decimalPart = [];
3329  $numMatches = preg_match_all( "/(#+)/", $digitGroupingPattern, $matches );
3330  preg_match( "/\d+/", $number, $integerPart );
3331  preg_match( "/\.\d*/", $number, $decimalPart );
3332  $groupedNumber = ( count( $decimalPart ) > 0 ) ? $decimalPart[0] : "";
3333  if ( $groupedNumber === $number ) {
3334  // the string does not have any number part. Eg: .12345
3335  return $sign . $groupedNumber;
3336  }
3337  $start = $end = ( $integerPart ) ? strlen( $integerPart[0] ) : 0;
3338  while ( $start > 0 ) {
3339  $match = $matches[0][$numMatches - 1];
3340  $matchLen = strlen( $match );
3341  $start = $end - $matchLen;
3342  if ( $start < 0 ) {
3343  $start = 0;
3344  }
3345  $groupedNumber = substr( $number, $start, $end -$start ) . $groupedNumber;
3346  $end = $start;
3347  if ( $numMatches > 1 ) {
3348  // use the last pattern for the rest of the number
3349  $numMatches--;
3350  }
3351  if ( $start > 0 ) {
3352  $groupedNumber = "," . $groupedNumber;
3353  }
3354  }
3355  return $sign . $groupedNumber;
3356  }
3357  }
3358 
3363  return self::$dataCache->getItem( $this->mCode, 'digitGroupingPattern' );
3364  }
3365 
3369  function digitTransformTable() {
3370  return self::$dataCache->getItem( $this->mCode, 'digitTransformTable' );
3371  }
3372 
3377  return self::$dataCache->getItem( $this->mCode, 'separatorTransformTable' );
3378  }
3379 
3389  function listToText( array $l ) {
3390  $m = count( $l ) - 1;
3391  if ( $m < 0 ) {
3392  return '';
3393  }
3394  if ( $m > 0 ) {
3395  $and = $this->msg( 'and' )->escaped();
3396  $space = $this->msg( 'word-separator' )->escaped();
3397  if ( $m > 1 ) {
3398  $comma = $this->msg( 'comma-separator' )->escaped();
3399  }
3400  }
3401  $s = $l[$m];
3402  for ( $i = $m - 1; $i >= 0; $i-- ) {
3403  if ( $i == $m - 1 ) {
3404  $s = $l[$i] . $and . $space . $s;
3405  } else {
3406  $s = $l[$i] . $comma . $s;
3407  }
3408  }
3409  return $s;
3410  }
3411 
3418  function commaList( array $list ) {
3419  return implode(
3420  wfMessage( 'comma-separator' )->inLanguage( $this )->escaped(),
3421  $list
3422  );
3423  }
3424 
3431  function semicolonList( array $list ) {
3432  return implode(
3433  wfMessage( 'semicolon-separator' )->inLanguage( $this )->escaped(),
3434  $list
3435  );
3436  }
3437 
3443  function pipeList( array $list ) {
3444  return implode(
3445  wfMessage( 'pipe-separator' )->inLanguage( $this )->escaped(),
3446  $list
3447  );
3448  }
3449 
3467  function truncate( $string, $length, $ellipsis = '...', $adjustLength = true ) {
3468  # Use the localized ellipsis character
3469  if ( $ellipsis == '...' ) {
3470  $ellipsis = wfMessage( 'ellipsis' )->inLanguage( $this )->escaped();
3471  }
3472  # Check if there is no need to truncate
3473  if ( $length == 0 ) {
3474  return $ellipsis; // convention
3475  } elseif ( strlen( $string ) <= abs( $length ) ) {
3476  return $string; // no need to truncate
3477  }
3478  $stringOriginal = $string;
3479  # If ellipsis length is >= $length then we can't apply $adjustLength
3480  if ( $adjustLength && strlen( $ellipsis ) >= abs( $length ) ) {
3481  $string = $ellipsis; // this can be slightly unexpected
3482  # Otherwise, truncate and add ellipsis...
3483  } else {
3484  $eLength = $adjustLength ? strlen( $ellipsis ) : 0;
3485  if ( $length > 0 ) {
3486  $length -= $eLength;
3487  $string = substr( $string, 0, $length ); // xyz...
3488  $string = $this->removeBadCharLast( $string );
3489  $string = rtrim( $string );
3490  $string = $string . $ellipsis;
3491  } else {
3492  $length += $eLength;
3493  $string = substr( $string, $length ); // ...xyz
3494  $string = $this->removeBadCharFirst( $string );
3495  $string = ltrim( $string );
3496  $string = $ellipsis . $string;
3497  }
3498  }
3499  # Do not truncate if the ellipsis makes the string longer/equal (bug 22181).
3500  # This check is *not* redundant if $adjustLength, due to the single case where
3501  # LEN($ellipsis) > ABS($limit arg); $stringOriginal could be shorter than $string.
3502  if ( strlen( $string ) < strlen( $stringOriginal ) ) {
3503  return $string;
3504  } else {
3505  return $stringOriginal;
3506  }
3507  }
3508 
3516  protected function removeBadCharLast( $string ) {
3517  if ( $string != '' ) {
3518  $char = ord( $string[strlen( $string ) - 1] );
3519  $m = [];
3520  if ( $char >= 0xc0 ) {
3521  # We got the first byte only of a multibyte char; remove it.
3522  $string = substr( $string, 0, -1 );
3523  } elseif ( $char >= 0x80 &&
3524  // Use the /s modifier (PCRE_DOTALL) so (.*) also matches newlines
3525  preg_match( '/^(.*)(?:[\xe0-\xef][\x80-\xbf]|' .
3526  '[\xf0-\xf7][\x80-\xbf]{1,2})$/s', $string, $m )
3527  ) {
3528  # We chopped in the middle of a character; remove it
3529  $string = $m[1];
3530  }
3531  }
3532  return $string;
3533  }
3534 
3542  protected function removeBadCharFirst( $string ) {
3543  if ( $string != '' ) {
3544  $char = ord( $string[0] );
3545  if ( $char >= 0x80 && $char < 0xc0 ) {
3546  # We chopped in the middle of a character; remove the whole thing
3547  $string = preg_replace( '/^[\x80-\xbf]+/', '', $string );
3548  }
3549  }
3550  return $string;
3551  }
3552 
3568  function truncateHtml( $text, $length, $ellipsis = '...' ) {
3569  # Use the localized ellipsis character
3570  if ( $ellipsis == '...' ) {
3571  $ellipsis = wfMessage( 'ellipsis' )->inLanguage( $this )->escaped();
3572  }
3573  # Check if there is clearly no need to truncate
3574  if ( $length <= 0 ) {
3575  return $ellipsis; // no text shown, nothing to format (convention)
3576  } elseif ( strlen( $text ) <= $length ) {
3577  return $text; // string short enough even *with* HTML (short-circuit)
3578  }
3579 
3580  $dispLen = 0; // innerHTML legth so far
3581  $testingEllipsis = false; // checking if ellipses will make string longer/equal?
3582  $tagType = 0; // 0-open, 1-close
3583  $bracketState = 0; // 1-tag start, 2-tag name, 0-neither
3584  $entityState = 0; // 0-not entity, 1-entity
3585  $tag = $ret = ''; // accumulated tag name, accumulated result string
3586  $openTags = []; // open tag stack
3587  $maybeState = null; // possible truncation state
3588 
3589  $textLen = strlen( $text );
3590  $neLength = max( 0, $length - strlen( $ellipsis ) ); // non-ellipsis len if truncated
3591  for ( $pos = 0; true; ++$pos ) {
3592  # Consider truncation once the display length has reached the maximim.
3593  # We check if $dispLen > 0 to grab tags for the $neLength = 0 case.
3594  # Check that we're not in the middle of a bracket/entity...
3595  if ( $dispLen && $dispLen >= $neLength && $bracketState == 0 && !$entityState ) {
3596  if ( !$testingEllipsis ) {
3597  $testingEllipsis = true;
3598  # Save where we are; we will truncate here unless there turn out to
3599  # be so few remaining characters that truncation is not necessary.
3600  if ( !$maybeState ) { // already saved? ($neLength = 0 case)
3601  $maybeState = [ $ret, $openTags ]; // save state
3602  }
3603  } elseif ( $dispLen > $length && $dispLen > strlen( $ellipsis ) ) {
3604  # String in fact does need truncation, the truncation point was OK.
3605  list( $ret, $openTags ) = $maybeState; // reload state
3606  $ret = $this->removeBadCharLast( $ret ); // multi-byte char fix
3607  $ret .= $ellipsis; // add ellipsis
3608  break;
3609  }
3610  }
3611  if ( $pos >= $textLen ) {
3612  break; // extra iteration just for above checks
3613  }
3614 
3615  # Read the next char...
3616  $ch = $text[$pos];
3617  $lastCh = $pos ? $text[$pos - 1] : '';
3618  $ret .= $ch; // add to result string
3619  if ( $ch == '<' ) {
3620  $this->truncate_endBracket( $tag, $tagType, $lastCh, $openTags ); // for bad HTML
3621  $entityState = 0; // for bad HTML
3622  $bracketState = 1; // tag started (checking for backslash)
3623  } elseif ( $ch == '>' ) {
3624  $this->truncate_endBracket( $tag, $tagType, $lastCh, $openTags );
3625  $entityState = 0; // for bad HTML
3626  $bracketState = 0; // out of brackets
3627  } elseif ( $bracketState == 1 ) {
3628  if ( $ch == '/' ) {
3629  $tagType = 1; // close tag (e.g. "</span>")
3630  } else {
3631  $tagType = 0; // open tag (e.g. "<span>")
3632  $tag .= $ch;
3633  }
3634  $bracketState = 2; // building tag name
3635  } elseif ( $bracketState == 2 ) {
3636  if ( $ch != ' ' ) {
3637  $tag .= $ch;
3638  } else {
3639  // Name found (e.g. "<a href=..."), add on tag attributes...
3640  $pos += $this->truncate_skip( $ret, $text, "<>", $pos + 1 );
3641  }
3642  } elseif ( $bracketState == 0 ) {
3643  if ( $entityState ) {
3644  if ( $ch == ';' ) {
3645  $entityState = 0;
3646  $dispLen++; // entity is one displayed char
3647  }
3648  } else {
3649  if ( $neLength == 0 && !$maybeState ) {
3650  // Save state without $ch. We want to *hit* the first
3651  // display char (to get tags) but not *use* it if truncating.
3652  $maybeState = [ substr( $ret, 0, -1 ), $openTags ];
3653  }
3654  if ( $ch == '&' ) {
3655  $entityState = 1; // entity found, (e.g. "&#160;")
3656  } else {
3657  $dispLen++; // this char is displayed
3658  // Add the next $max display text chars after this in one swoop...
3659  $max = ( $testingEllipsis ? $length : $neLength ) - $dispLen;
3660  $skipped = $this->truncate_skip( $ret, $text, "<>&", $pos + 1, $max );
3661  $dispLen += $skipped;
3662  $pos += $skipped;
3663  }
3664  }
3665  }
3666  }
3667  // Close the last tag if left unclosed by bad HTML
3668  $this->truncate_endBracket( $tag, $text[$textLen - 1], $tagType, $openTags );
3669  while ( count( $openTags ) > 0 ) {
3670  $ret .= '</' . array_pop( $openTags ) . '>'; // close open tags
3671  }
3672  return $ret;
3673  }
3674 
3686  private function truncate_skip( &$ret, $text, $search, $start, $len = null ) {
3687  if ( $len === null ) {
3688  $len = -1; // -1 means "no limit" for strcspn
3689  } elseif ( $len < 0 ) {
3690  $len = 0; // sanity
3691  }
3692  $skipCount = 0;
3693  if ( $start < strlen( $text ) ) {
3694  $skipCount = strcspn( $text, $search, $start, $len );
3695  $ret .= substr( $text, $start, $skipCount );
3696  }
3697  return $skipCount;
3698  }
3699 
3709  private function truncate_endBracket( &$tag, $tagType, $lastCh, &$openTags ) {
3710  $tag = ltrim( $tag );
3711  if ( $tag != '' ) {
3712  if ( $tagType == 0 && $lastCh != '/' ) {
3713  $openTags[] = $tag; // tag opened (didn't close itself)
3714  } elseif ( $tagType == 1 ) {
3715  if ( $openTags && $tag == $openTags[count( $openTags ) - 1] ) {
3716  array_pop( $openTags ); // tag closed
3717  }
3718  }
3719  $tag = '';
3720  }
3721  }
3722 
3731  function convertGrammar( $word, $case ) {
3733  if ( isset( $wgGrammarForms[$this->getCode()][$case][$word] ) ) {
3734  return $wgGrammarForms[$this->getCode()][$case][$word];
3735  }
3736 
3737  return $word;
3738  }
3739 
3745  function getGrammarForms() {
3747  if ( isset( $wgGrammarForms[$this->getCode()] )
3748  && is_array( $wgGrammarForms[$this->getCode()] )
3749  ) {
3750  return $wgGrammarForms[$this->getCode()];
3751  }
3752 
3753  return [];
3754  }
3755 
3765  public function getGrammarTransformations() {
3766  $languageCode = $this->getCode();
3767 
3768  if ( self::$grammarTransformations === null ) {
3769  self::$grammarTransformations = new MapCacheLRU( 10 );
3770  }
3771 
3772  if ( self::$grammarTransformations->has( $languageCode ) ) {
3773  return self::$grammarTransformations->get( $languageCode );
3774  }
3775 
3776  $data = [];
3777 
3778  $grammarDataFile = __DIR__ . "/data/grammarTransformations/$languageCode.json";
3779  if ( is_readable( $grammarDataFile ) ) {
3780  $data = FormatJson::decode(
3781  file_get_contents( $grammarDataFile ),
3782  true
3783  );
3784 
3785  if ( $data === null ) {
3786  throw new MWException( "Invalid grammar data for \"$languageCode\"." );
3787  }
3788 
3789  self::$grammarTransformations->set( $languageCode, $data );
3790  }
3791 
3792  return $data;
3793  }
3794 
3814  function gender( $gender, $forms ) {
3815  if ( !count( $forms ) ) {
3816  return '';
3817  }
3818  $forms = $this->preConvertPlural( $forms, 2 );
3819  if ( $gender === 'male' ) {
3820  return $forms[0];
3821  }
3822  if ( $gender === 'female' ) {
3823  return $forms[1];
3824  }
3825  return isset( $forms[2] ) ? $forms[2] : $forms[0];
3826  }
3827 
3843  function convertPlural( $count, $forms ) {
3844  // Handle explicit n=pluralform cases
3845  $forms = $this->handleExplicitPluralForms( $count, $forms );
3846  if ( is_string( $forms ) ) {
3847  return $forms;
3848  }
3849  if ( !count( $forms ) ) {
3850  return '';
3851  }
3852 
3853  $pluralForm = $this->getPluralRuleIndexNumber( $count );
3854  $pluralForm = min( $pluralForm, count( $forms ) - 1 );
3855  return $forms[$pluralForm];
3856  }
3857 
3873  protected function handleExplicitPluralForms( $count, array $forms ) {
3874  foreach ( $forms as $index => $form ) {
3875  if ( preg_match( '/\d+=/i', $form ) ) {
3876  $pos = strpos( $form, '=' );
3877  if ( substr( $form, 0, $pos ) === (string)$count ) {
3878  return substr( $form, $pos + 1 );
3879  }
3880  unset( $forms[$index] );
3881  }
3882  }
3883  return array_values( $forms );
3884  }
3885 
3894  protected function preConvertPlural( /* Array */ $forms, $count ) {
3895  while ( count( $forms ) < $count ) {
3896  $forms[] = $forms[count( $forms ) - 1];
3897  }
3898  return $forms;
3899  }
3900 
3917  public function embedBidi( $text = '' ) {
3919  if ( $dir === 'ltr' ) {
3920  // Wrap in LEFT-TO-RIGHT EMBEDDING ... POP DIRECTIONAL FORMATTING
3921  return self::$lre . $text . self::$pdf;
3922  }
3923  if ( $dir === 'rtl' ) {
3924  // Wrap in RIGHT-TO-LEFT EMBEDDING ... POP DIRECTIONAL FORMATTING
3925  return self::$rle . $text . self::$pdf;
3926  }
3927  // No strong directionality: do not wrap
3928  return $text;
3929  }
3930 
3943  function translateBlockExpiry( $str, User $user = null ) {
3944  $duration = SpecialBlock::getSuggestedDurations( $this );
3945  foreach ( $duration as $show => $value ) {
3946  if ( strcmp( $str, $value ) == 0 ) {
3947  return htmlspecialchars( trim( $show ) );
3948  }
3949  }
3950 
3951  if ( wfIsInfinity( $str ) ) {
3952  foreach ( $duration as $show => $value ) {
3953  if ( wfIsInfinity( $value ) ) {
3954  return htmlspecialchars( trim( $show ) );
3955  }
3956  }
3957  }
3958 
3959  // If all else fails, return a standard duration or timestamp description.
3960  $time = strtotime( $str, 0 );
3961  if ( $time === false ) { // Unknown format. Return it as-is in case.
3962  return $str;
3963  } elseif ( $time !== strtotime( $str, 1 ) ) { // It's a relative timestamp.
3964  // $time is relative to 0 so it's a duration length.
3965  return $this->formatDuration( $time );
3966  } else { // It's an absolute timestamp.
3967  if ( $time === 0 ) {
3968  // wfTimestamp() handles 0 as current time instead of epoch.
3969  $time = '19700101000000';
3970  }
3971  if ( $user ) {
3972  return $this->userTimeAndDate( $time, $user );
3973  }
3974  return $this->timeanddate( $time );
3975  }
3976  }
3977 
3985  public function segmentForDiff( $text ) {
3986  return $text;
3987  }
3988 
3995  public function unsegmentForDiff( $text ) {
3996  return $text;
3997  }
3998 
4005  public function getConverter() {
4006  return $this->mConverter;
4007  }
4008 
4015  public function autoConvertToAllVariants( $text ) {
4016  return $this->mConverter->autoConvertToAllVariants( $text );
4017  }
4018 
4025  public function convert( $text ) {
4026  return $this->mConverter->convert( $text );
4027  }
4028 
4035  public function convertTitle( $title ) {
4036  return $this->mConverter->convertTitle( $title );
4037  }
4038 
4045  public function convertNamespace( $ns ) {
4046  return $this->mConverter->convertNamespace( $ns );
4047  }
4048 
4054  public function hasVariants() {
4055  return count( $this->getVariants() ) > 1;
4056  }
4057 
4065  public function hasVariant( $variant ) {
4066  return (bool)$this->mConverter->validateVariant( $variant );
4067  }
4068 
4076  public function convertHtml( $text, $isTitle = false ) {
4077  return htmlspecialchars( $this->convert( $text, $isTitle ) );
4078  }
4079 
4084  public function convertCategoryKey( $key ) {
4085  return $this->mConverter->convertCategoryKey( $key );
4086  }
4087 
4094  public function getVariants() {
4095  return $this->mConverter->getVariants();
4096  }
4097 
4101  public function getPreferredVariant() {
4102  return $this->mConverter->getPreferredVariant();
4103  }
4104 
4108  public function getDefaultVariant() {
4109  return $this->mConverter->getDefaultVariant();
4110  }
4111 
4115  public function getURLVariant() {
4116  return $this->mConverter->getURLVariant();
4117  }
4118 
4131  public function findVariantLink( &$link, &$nt, $ignoreOtherCond = false ) {
4132  $this->mConverter->findVariantLink( $link, $nt, $ignoreOtherCond );
4133  }
4134 
4141  function getExtraHashOptions() {
4142  return $this->mConverter->getExtraHashOptions();
4143  }
4144 
4152  public function getParsedTitle() {
4153  return $this->mConverter->getParsedTitle();
4154  }
4155 
4162  public function updateConversionTable( Title $title ) {
4163  $this->mConverter->updateConversionTable( $title );
4164  }
4165 
4178  public function markNoConversion( $text, $noParse = false ) {
4179  // Excluding protocal-relative URLs may avoid many false positives.
4180  if ( $noParse || preg_match( '/^(?:' . wfUrlProtocolsWithoutProtRel() . ')/', $text ) ) {
4181  return $this->mConverter->markNoConversion( $text );
4182  } else {
4183  return $text;
4184  }
4185  }
4186 
4193  public function linkTrail() {
4194  return self::$dataCache->getItem( $this->mCode, 'linkTrail' );
4195  }
4196 
4203  public function linkPrefixCharset() {
4204  return self::$dataCache->getItem( $this->mCode, 'linkPrefixCharset' );
4205  }
4206 
4214  public function getParentLanguage() {
4215  if ( $this->mParentLanguage !== false ) {
4216  return $this->mParentLanguage;
4217  }
4218 
4219  $code = explode( '-', $this->getCode() )[0];
4220  if ( !in_array( $code, LanguageConverter::$languagesWithVariants ) ) {
4221  $this->mParentLanguage = null;
4222  return null;
4223  }
4225  if ( !$lang->hasVariant( $this->getCode() ) ) {
4226  $this->mParentLanguage = null;
4227  return null;
4228  }
4229 
4230  $this->mParentLanguage = $lang;
4231  return $lang;
4232  }
4233 
4241  public function equals( Language $lang ) {
4242  return $lang->getCode() === $this->mCode;
4243  }
4244 
4253  public function getCode() {
4254  return $this->mCode;
4255  }
4256 
4267  public function getHtmlCode() {
4268  if ( is_null( $this->mHtmlCode ) ) {
4269  $this->mHtmlCode = wfBCP47( $this->getCode() );
4270  }
4271  return $this->mHtmlCode;
4272  }
4273 
4277  public function setCode( $code ) {
4278  $this->mCode = $code;
4279  // Ensure we don't leave incorrect cached data lying around
4280  $this->mHtmlCode = null;
4281  $this->mParentLanguage = false;
4282  }
4283 
4291  public static function getCodeFromFileName( $filename, $prefix = 'Language', $suffix = '.php' ) {
4292  $m = null;
4293  preg_match( '/' . preg_quote( $prefix, '/' ) . '([A-Z][a-z_]+)' .
4294  preg_quote( $suffix, '/' ) . '/', $filename, $m );
4295  if ( !count( $m ) ) {
4296  return false;
4297  }
4298  return str_replace( '_', '-', strtolower( $m[1] ) );
4299  }
4300 
4305  public static function classFromCode( $code ) {
4306  if ( $code == 'en' ) {
4307  return 'Language';
4308  } else {
4309  return 'Language' . str_replace( '-', '_', ucfirst( $code ) );
4310  }
4311  }
4312 
4321  public static function getFileName( $prefix = 'Language', $code, $suffix = '.php' ) {
4322  if ( !self::isValidBuiltInCode( $code ) ) {
4323  throw new MWException( "Invalid language code \"$code\"" );
4324  }
4325 
4326  return $prefix . str_replace( '-', '_', ucfirst( $code ) ) . $suffix;
4327  }
4328 
4333  public static function getMessagesFileName( $code ) {
4334  global $IP;
4335  $file = self::getFileName( "$IP/languages/messages/Messages", $code, '.php' );
4336  Hooks::run( 'Language::getMessagesFileName', [ $code, &$file ] );
4337  return $file;
4338  }
4339 
4346  public static function getJsonMessagesFileName( $code ) {
4347  global $IP;
4348 
4349  if ( !self::isValidBuiltInCode( $code ) ) {
4350  throw new MWException( "Invalid language code \"$code\"" );
4351  }
4352 
4353  return "$IP/languages/i18n/$code.json";
4354  }
4355 
4363  public static function getFallbackFor( $code ) {
4364  $fallbacks = self::getFallbacksFor( $code );
4365  if ( $fallbacks ) {
4366  return $fallbacks[0];
4367  }
4368  return false;
4369  }
4370 
4378  public static function getFallbacksFor( $code ) {
4379  if ( $code === 'en' || !Language::isValidBuiltInCode( $code ) ) {
4380  return [];
4381  }
4382  // For unknown languages, fallbackSequence returns an empty array,
4383  // hardcode fallback to 'en' in that case.
4384  return self::getLocalisationCache()->getItem( $code, 'fallbackSequence' ) ?: [ 'en' ];
4385  }
4386 
4395  public static function getFallbacksIncludingSiteLanguage( $code ) {
4397 
4398  // Usually, we will only store a tiny number of fallback chains, so we
4399  // keep them in static memory.
4400  $cacheKey = "{$code}-{$wgLanguageCode}";
4401 
4402  if ( !array_key_exists( $cacheKey, self::$fallbackLanguageCache ) ) {
4403  $fallbacks = self::getFallbacksFor( $code );
4404 
4405  // Append the site's fallback chain, including the site language itself
4406  $siteFallbacks = self::getFallbacksFor( $wgLanguageCode );
4407  array_unshift( $siteFallbacks, $wgLanguageCode );
4408 
4409  // Eliminate any languages already included in the chain
4410  $siteFallbacks = array_diff( $siteFallbacks, $fallbacks );
4411 
4412  self::$fallbackLanguageCache[$cacheKey] = [ $fallbacks, $siteFallbacks ];
4413  }
4414  return self::$fallbackLanguageCache[$cacheKey];
4415  }
4416 
4426  public static function getMessagesFor( $code ) {
4427  return self::getLocalisationCache()->getItem( $code, 'messages' );
4428  }
4429 
4438  public static function getMessageFor( $key, $code ) {
4439  return self::getLocalisationCache()->getSubitem( $code, 'messages', $key );
4440  }
4441 
4450  public static function getMessageKeysFor( $code ) {
4451  return self::getLocalisationCache()->getSubitemList( $code, 'messages' );
4452  }
4453 
4458  function fixVariableInNamespace( $talk ) {
4459  if ( strpos( $talk, '$1' ) === false ) {
4460  return $talk;
4461  }
4462 
4464  $talk = str_replace( '$1', $wgMetaNamespace, $talk );
4465 
4466  # Allow grammar transformations
4467  # Allowing full message-style parsing would make simple requests
4468  # such as action=raw much more expensive than they need to be.
4469  # This will hopefully cover most cases.
4470  $talk = preg_replace_callback( '/{{grammar:(.*?)\|(.*?)}}/i',
4471  [ &$this, 'replaceGrammarInNamespace' ], $talk );
4472  return str_replace( ' ', '_', $talk );
4473  }
4474 
4479  function replaceGrammarInNamespace( $m ) {
4480  return $this->convertGrammar( trim( $m[2] ), trim( $m[1] ) );
4481  }
4482 
4493  public function formatExpiry( $expiry, $format = true, $infinity = 'infinity' ) {
4494  static $dbInfinity;
4495  if ( $dbInfinity === null ) {
4496  $dbInfinity = wfGetDB( DB_SLAVE )->getInfinity();
4497  }
4498 
4499  if ( $expiry == '' || $expiry === 'infinity' || $expiry == $dbInfinity ) {
4500  return $format === true
4501  ? $this->getMessageFromDB( 'infiniteblock' )
4502  : $infinity;
4503  } else {
4504  return $format === true
4505  ? $this->timeanddate( $expiry, /* User preference timezone */ true )
4506  : wfTimestamp( $format, $expiry );
4507  }
4508  }
4509 
4523  function formatTimePeriod( $seconds, $format = [] ) {
4524  if ( !is_array( $format ) ) {
4525  $format = [ 'avoid' => $format ]; // For backwards compatibility
4526  }
4527  if ( !isset( $format['avoid'] ) ) {
4528  $format['avoid'] = false;
4529  }
4530  if ( !isset( $format['noabbrevs'] ) ) {
4531  $format['noabbrevs'] = false;
4532  }
4533  $secondsMsg = wfMessage(
4534  $format['noabbrevs'] ? 'seconds' : 'seconds-abbrev' )->inLanguage( $this );
4535  $minutesMsg = wfMessage(
4536  $format['noabbrevs'] ? 'minutes' : 'minutes-abbrev' )->inLanguage( $this );
4537  $hoursMsg = wfMessage(
4538  $format['noabbrevs'] ? 'hours' : 'hours-abbrev' )->inLanguage( $this );
4539  $daysMsg = wfMessage(
4540  $format['noabbrevs'] ? 'days' : 'days-abbrev' )->inLanguage( $this );
4541 
4542  if ( round( $seconds * 10 ) < 100 ) {
4543  $s = $this->formatNum( sprintf( "%.1f", round( $seconds * 10 ) / 10 ) );
4544  $s = $secondsMsg->params( $s )->text();
4545  } elseif ( round( $seconds ) < 60 ) {
4546  $s = $this->formatNum( round( $seconds ) );
4547  $s = $secondsMsg->params( $s )->text();
4548  } elseif ( round( $seconds ) < 3600 ) {
4549  $minutes = floor( $seconds / 60 );
4550  $secondsPart = round( fmod( $seconds, 60 ) );
4551  if ( $secondsPart == 60 ) {
4552  $secondsPart = 0;
4553  $minutes++;
4554  }
4555  $s = $minutesMsg->params( $this->formatNum( $minutes ) )->text();
4556  $s .= ' ';
4557  $s .= $secondsMsg->params( $this->formatNum( $secondsPart ) )->text();
4558  } elseif ( round( $seconds ) <= 2 * 86400 ) {
4559  $hours = floor( $seconds / 3600 );
4560  $minutes = floor( ( $seconds - $hours * 3600 ) / 60 );
4561  $secondsPart = round( $seconds - $hours * 3600 - $minutes * 60 );
4562  if ( $secondsPart == 60 ) {
4563  $secondsPart = 0;
4564  $minutes++;
4565  }
4566  if ( $minutes == 60 ) {
4567  $minutes = 0;
4568  $hours++;
4569  }
4570  $s = $hoursMsg->params( $this->formatNum( $hours ) )->text();
4571  $s .= ' ';
4572  $s .= $minutesMsg->params( $this->formatNum( $minutes ) )->text();
4573  if ( !in_array( $format['avoid'], [ 'avoidseconds', 'avoidminutes' ] ) ) {
4574  $s .= ' ' . $secondsMsg->params( $this->formatNum( $secondsPart ) )->text();
4575  }
4576  } else {
4577  $days = floor( $seconds / 86400 );
4578  if ( $format['avoid'] === 'avoidminutes' ) {
4579  $hours = round( ( $seconds - $days * 86400 ) / 3600 );
4580  if ( $hours == 24 ) {
4581  $hours = 0;
4582  $days++;
4583  }
4584  $s = $daysMsg->params( $this->formatNum( $days ) )->text();
4585  $s .= ' ';
4586  $s .= $hoursMsg->params( $this->formatNum( $hours ) )->text();
4587  } elseif ( $format['avoid'] === 'avoidseconds' ) {
4588  $hours = floor( ( $seconds - $days * 86400 ) / 3600 );
4589  $minutes = round( ( $seconds - $days * 86400 - $hours * 3600 ) / 60 );
4590  if ( $minutes == 60 ) {
4591  $minutes = 0;
4592  $hours++;
4593  }
4594  if ( $hours == 24 ) {
4595  $hours = 0;
4596  $days++;
4597  }
4598  $s = $daysMsg->params( $this->formatNum( $days ) )->text();
4599  $s .= ' ';
4600  $s .= $hoursMsg->params( $this->formatNum( $hours ) )->text();
4601  $s .= ' ';
4602  $s .= $minutesMsg->params( $this->formatNum( $minutes ) )->text();
4603  } else {
4604  $s = $daysMsg->params( $this->formatNum( $days ) )->text();
4605  $s .= ' ';
4606  $s .= $this->formatTimePeriod( $seconds - $days * 86400, $format );
4607  }
4608  }
4609  return $s;
4610  }
4611 
4623  function formatBitrate( $bps ) {
4624  return $this->formatComputingNumbers( $bps, 1000, "bitrate-$1bits" );
4625  }
4626 
4633  function formatComputingNumbers( $size, $boundary, $messageKey ) {
4634  if ( $size <= 0 ) {
4635  return str_replace( '$1', $this->formatNum( $size ),
4636  $this->getMessageFromDB( str_replace( '$1', '', $messageKey ) )
4637  );
4638  }
4639  $sizes = [ '', 'kilo', 'mega', 'giga', 'tera', 'peta', 'exa', 'zeta', 'yotta' ];
4640  $index = 0;
4641 
4642  $maxIndex = count( $sizes ) - 1;
4643  while ( $size >= $boundary && $index < $maxIndex ) {
4644  $index++;
4645  $size /= $boundary;
4646  }
4647 
4648  // For small sizes no decimal places necessary
4649  $round = 0;
4650  if ( $index > 1 ) {
4651  // For MB and bigger two decimal places are smarter
4652  $round = 2;
4653  }
4654  $msg = str_replace( '$1', $sizes[$index], $messageKey );
4655 
4656  $size = round( $size, $round );
4657  $text = $this->getMessageFromDB( $msg );
4658  return str_replace( '$1', $this->formatNum( $size ), $text );
4659  }
4660 
4671  function formatSize( $size ) {
4672  return $this->formatComputingNumbers( $size, 1024, "size-$1bytes" );
4673  }
4674 
4684  function specialList( $page, $details, $oppositedm = true ) {
4685  if ( !$details ) {
4686  return $page;
4687  }
4688 
4689  $dirmark = ( $oppositedm ? $this->getDirMark( true ) : '' ) . $this->getDirMark();
4690  return
4691  $page .
4692  $dirmark .
4693  $this->msg( 'word-separator' )->escaped() .
4694  $this->msg( 'parentheses' )->rawParams( $details )->escaped();
4695  }
4696 
4707  public function viewPrevNext( Title $title, $offset, $limit,
4708  array $query = [], $atend = false
4709  ) {
4710  // @todo FIXME: Why on earth this needs one message for the text and another one for tooltip?
4711 
4712  # Make 'previous' link
4713  $prev = wfMessage( 'prevn' )->inLanguage( $this )->title( $title )->numParams( $limit )->text();
4714  if ( $offset > 0 ) {
4715  $plink = $this->numLink( $title, max( $offset - $limit, 0 ), $limit,
4716  $query, $prev, 'prevn-title', 'mw-prevlink' );
4717  } else {
4718  $plink = htmlspecialchars( $prev );
4719  }
4720 
4721  # Make 'next' link
4722  $next = wfMessage( 'nextn' )->inLanguage( $this )->title( $title )->numParams( $limit )->text();
4723  if ( $atend ) {
4724  $nlink = htmlspecialchars( $next );
4725  } else {
4726  $nlink = $this->numLink( $title, $offset + $limit, $limit,
4727  $query, $next, 'nextn-title', 'mw-nextlink' );
4728  }
4729 
4730  # Make links to set number of items per page
4731  $numLinks = [];
4732  foreach ( [ 20, 50, 100, 250, 500 ] as $num ) {
4733  $numLinks[] = $this->numLink( $title, $offset, $num,
4734  $query, $this->formatNum( $num ), 'shown-title', 'mw-numlink' );
4735  }
4736 
4737  return wfMessage( 'viewprevnext' )->inLanguage( $this )->title( $title
4738  )->rawParams( $plink, $nlink, $this->pipeList( $numLinks ) )->escaped();
4739  }
4740 
4753  private function numLink( Title $title, $offset, $limit, array $query, $link,
4754  $tooltipMsg, $class
4755  ) {
4756  $query = [ 'limit' => $limit, 'offset' => $offset ] + $query;
4757  $tooltip = wfMessage( $tooltipMsg )->inLanguage( $this )->title( $title )
4758  ->numParams( $limit )->text();
4759 
4760  return Html::element( 'a', [ 'href' => $title->getLocalURL( $query ),
4761  'title' => $tooltip, 'class' => $class ], $link );
4762  }
4763 
4769  public function getConvRuleTitle() {
4770  return $this->mConverter->getConvRuleTitle();
4771  }
4772 
4778  public function getCompiledPluralRules() {
4779  $pluralRules = self::$dataCache->getItem( strtolower( $this->mCode ), 'compiledPluralRules' );
4780  $fallbacks = Language::getFallbacksFor( $this->mCode );
4781  if ( !$pluralRules ) {
4782  foreach ( $fallbacks as $fallbackCode ) {
4783  $pluralRules = self::$dataCache->getItem( strtolower( $fallbackCode ), 'compiledPluralRules' );
4784  if ( $pluralRules ) {
4785  break;
4786  }
4787  }
4788  }
4789  return $pluralRules;
4790  }
4791 
4797  public function getPluralRules() {
4798  $pluralRules = self::$dataCache->getItem( strtolower( $this->mCode ), 'pluralRules' );
4799  $fallbacks = Language::getFallbacksFor( $this->mCode );
4800  if ( !$pluralRules ) {
4801  foreach ( $fallbacks as $fallbackCode ) {
4802  $pluralRules = self::$dataCache->getItem( strtolower( $fallbackCode ), 'pluralRules' );
4803  if ( $pluralRules ) {
4804  break;
4805  }
4806  }
4807  }
4808  return $pluralRules;
4809  }
4810 
4816  public function getPluralRuleTypes() {
4817  $pluralRuleTypes = self::$dataCache->getItem( strtolower( $this->mCode ), 'pluralRuleTypes' );
4818  $fallbacks = Language::getFallbacksFor( $this->mCode );
4819  if ( !$pluralRuleTypes ) {
4820  foreach ( $fallbacks as $fallbackCode ) {
4821  $pluralRuleTypes = self::$dataCache->getItem( strtolower( $fallbackCode ), 'pluralRuleTypes' );
4822  if ( $pluralRuleTypes ) {
4823  break;
4824  }
4825  }
4826  }
4827  return $pluralRuleTypes;
4828  }
4829 
4835  public function getPluralRuleIndexNumber( $number ) {
4836  $pluralRules = $this->getCompiledPluralRules();
4837  $form = Evaluator::evaluateCompiled( $number, $pluralRules );
4838  return $form;
4839  }
4840 
4849  public function getPluralRuleType( $number ) {
4850  $index = $this->getPluralRuleIndexNumber( $number );
4851  $pluralRuleTypes = $this->getPluralRuleTypes();
4852  if ( isset( $pluralRuleTypes[$index] ) ) {
4853  return $pluralRuleTypes[$index];
4854  } else {
4855  return 'other';
4856  }
4857  }
4858 }
utf8ToCodepoint($char)
Determine the Unicode codepoint of a single-character UTF-8 sequence.
#define the
table suitable for use with IDatabase::select()
formatTimePeriod($seconds, $format=[])
Formats a time given in seconds into a string representation of that time.
Definition: Language.php:4523
getDirMark($opposite=false)
A hidden direction mark (LRM or RLM), depending on the language direction.
Definition: Language.php:3090
static $mMonthAbbrevMsgs
Definition: Language.php:81
lc($str, $first=false)
Definition: Language.php:2698
$mParentLanguage
Definition: Language.php:43
getSpecialPageAliases()
Get special page names, as an associative array canonical name => array of valid names, including aliases.
Definition: Language.php:3204
getWeekdayAbbreviation($key)
Definition: Language.php:972
getDateFormatString($type, $pref)
Get a format string for a given type and preference.
Definition: Language.php:2203
getPluralRuleTypes()
Get the plural rule types for the language.
Definition: Language.php:4816
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:2476
static getLocalisationCache()
Get the LocalisationCache instance.
Definition: Language.php:404
convertPlural($count, $forms)
Plural form transformations, needed for some languages.
Definition: Language.php:3843
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
doMagicHook()
Run the LanguageGetMagic hook once.
Definition: Language.php:3150
wfGetDB($db, $groups=[], $wiki=false)
Get a Database object.
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:802
static isKnownLanguageTag($tag)
Returns true if a language code is an IETF tag known to MediaWiki.
Definition: Language.php:383
the array() calling protocol came about after MediaWiki 1.4rc1.
static insertSpace($string, $pattern)
Definition: Language.php:2875
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:1555
static getTitleInvalidRegex()
Returns a simple regex that will match on characters and sequences invalid in titles.
convertGrammar($word, $case)
Grammatical transformations, needed for inflected languages Invoked by putting {{grammar:case|word}} ...
Definition: Language.php:3731
static LocalisationCache $dataCache
Definition: Language.php:58
isMultibyte($str)
Definition: Language.php:2714
initEncoding()
Definition: Language.php:2952
handleExplicitPluralForms($count, array $forms)
Handles explicit plural forms for Language::convertPlural()
Definition: Language.php:3873
hasVariant($variant)
Check if the language has the specific variant.
Definition: Language.php:4065
if(count($args)==0) $dir
markNoConversion($text, $noParse=false)
Prepare external link text for conversion.
Definition: Language.php:4178
setNamespaces(array $namespaces)
Arbitrarily set all of the namespace names at once.
Definition: Language.php:501
recodeForEdit($s)
Definition: Language.php:2961
static HashBagOStuff null $languageNameCache
Cache for language names.
Definition: Language.php:150
transformUsingPairFile($file, $string)
Transform a string using serialized data stored in the given file (which must be in the serialized su...
Definition: Language.php:3010
date($ts, $adj=false, $format=true, $timecorrection=false)
Definition: Language.php:2237
getHtmlCode()
Get the code in BCP 47 format which we can use inside of html lang="" tags.
Definition: Language.php:4267
truncate_skip(&$ret, $text, $search, $start, $len=null)
truncateHtml() helper function like strcspn() but adds the skipped chars to $ret
Definition: Language.php:3686
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:281
$IP
Definition: WebStart.php:58
equals(Language $lang)
Compare with an other language object.
Definition: Language.php:4241
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:1936
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
removeBadCharFirst($string)
Remove bytes that represent an incomplete Unicode character at the start of string (e...
Definition: Language.php:3542
convertNamespace($ns)
Convert a namespace index to a string in the preferred variant.
Definition: Language.php:4045
static $IRANIAN_DAYS
Definition: Language.php:1564
static getMessageFor($key, $code)
Get a message for a given language.
Definition: Language.php:4438
lcfirst($str)
Definition: Language.php:2679
offsetForUser(User $user)
Adjust the timestamp depending on the given user's preferences.
Definition: MWTimestamp.php:78
getLocalNsIndex($text)
Get a namespace key by value, case insensitive.
Definition: Language.php:610
alignEnd()
Return 'right' or 'left' as appropriate alignment for line-end for this language's text direction...
Definition: Language.php:3058
getFallbackLanguages()
Definition: Language.php:444
firstChar($s)
Get the first character of a string.
Definition: Language.php:2897
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
$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:48
hasVariants()
Check if this is a language with variants.
Definition: Language.php:4054
getCompiledPluralRules()
Get the compiled plural rules for the language.
Definition: Language.php:4778
getNsIndex($text)
Get a namespace key by value, case insensitive.
Definition: Language.php:693
getNsText($index)
Get a namespace value by key.
Definition: Language.php:540
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:177
iconv($in, $out, $string)
Definition: Language.php:2599
$value
convertCategoryKey($key)
Definition: Language.php:4084
$wgMetaNamespace
Name of the project namespace.
formatComputingNumbers($size, $boundary, $messageKey)
Definition: Language.php:4633
fallback8bitEncoding()
Definition: Language.php:2809
static getFallbacksIncludingSiteLanguage($code)
Get the ordered list of fallback languages, ending with the fallback language chain for the site lang...
Definition: Language.php:4395
formatNumNoSeparators($number)
Front-end for non-commafied formatNum.
Definition: Language.php:3277
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:3709
A fake language converter.
linkTrail()
A regular expression to match legal word-trailing characters which should be merged onto a link of th...
Definition: Language.php:4193
getAllMessages()
Definition: Language.php:2589
wfUrlProtocolsWithoutProtRel()
Like wfUrlProtocols(), but excludes '//' from the protocol list.
normalize($s)
Convert a UTF-8 string to normal form C.
Definition: Language.php:2985
$wgNamespaceAliases
Namespace aliases.
static getCodeFromFileName($filename, $prefix= 'Language', $suffix= '.php')
Get the language code from a file name.
Definition: Language.php:4291
internalUserTimeAndDate($type, $ts, User $user, array $options)
Internal helper function for userDate(), userTime() and userTimeAndDate()
Definition: Language.php:2372
static getMessageKeysFor($code)
Get all message keys for a given language.
Definition: Language.php:4450
static tsToHijri($ts)
Converting Gregorian dates to Hijri dates.
Definition: Language.php:1638
formatExpiry($expiry, $format=true, $infinity= 'infinity')
Decode an expiry (block, protection, etc) which has come from the DB.
Definition: Language.php:4493
getVariants()
Get the list of variants supported by this language see sample implementation in LanguageZh.php.
Definition: Language.php:4094
A helper class for throttling authentication attempts.
$dateFormatStrings
Definition: Language.php:45
dateFormat($usePrefs=true)
This is meant to be used by time(), date(), and timeanddate() to get the date preference they're supp...
Definition: Language.php:2172
getNamespaceAliases()
Definition: Language.php:619
when a variable name is used in a it is silently declared as a new local masking the global
Definition: design.txt:93
autoConvertToAllVariants($text)
convert text to all supported variants
Definition: Language.php:4015
static getFallbacksFor($code)
Get the ordered list of fallback languages.
Definition: Language.php:4378
static $mMonthMsgs
Definition: Language.php:71
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
$wgExtraGenderNamespaces
Same as above, but for namespaces with gender distinction.
return['DBLoadBalancerFactory'=> function(MediaWikiServices $services){$mainConfig=$services->getMainConfig();$lbConf=MWLBFactory::applyDefaultConfig($mainConfig->get( 'LBFactoryConf'), $mainConfig);$class=MWLBFactory::getLBFactoryClass($lbConf);return new $class($lbConf);},'DBLoadBalancer'=> function(MediaWikiServices $services){return $services->getDBLoadBalancerFactory() ->getMainLB();},'SiteStore'=> function(MediaWikiServices $services){$rawSiteStore=new DBSiteStore($services->getDBLoadBalancer());$cache=wfGetCache(wfIsHHVM()?CACHE_ACCEL:CACHE_ANYTHING);return new CachingSiteStore($rawSiteStore, $cache);},'SiteLookup'=> function(MediaWikiServices $services){return $services->getSiteStore();},'ConfigFactory'=> function(MediaWikiServices $services){$registry=$services->getBootstrapConfig() ->get( 'ConfigRegistry');$factory=new ConfigFactory();foreach($registry as $name=> $callback){$factory->register($name, $callback);}return $factory;},'MainConfig'=> function(MediaWikiServices $services){return $services->getConfigFactory() ->makeConfig( 'main');},'InterwikiLookup'=> function(MediaWikiServices $services){global $wgContLang;$config=$services->getMainConfig();return new ClassicInterwikiLookup($wgContLang, ObjectCache::getMainWANInstance(), $config->get( 'InterwikiExpiry'), $config->get( 'InterwikiCache'), $config->get( 'InterwikiScopes'), $config->get( 'InterwikiFallbackSite'));},'StatsdDataFactory'=> function(MediaWikiServices $services){return new BufferingStatsdDataFactory(rtrim($services->getMainConfig() ->get( 'StatsdMetricPrefix'), '.'));},'EventRelayerGroup'=> function(MediaWikiServices $services){return new EventRelayerGroup($services->getMainConfig() ->get( 'EventRelayerConfig'));},'SearchEngineFactory'=> function(MediaWikiServices $services){return new SearchEngineFactory($services->getSearchEngineConfig());},'SearchEngineConfig'=> function(MediaWikiServices $services){global $wgContLang;return new SearchEngineConfig($services->getMainConfig(), $wgContLang);},'SkinFactory'=> function(MediaWikiServices $services){$factory=new SkinFactory();$names=$services->getMainConfig() ->get( 'ValidSkinNames');foreach($names as $name=> $skin){$factory->register($name, $skin, function() use($name, $skin){$class="Skin$skin";return new $class($name);});}$factory->register( 'fallback', 'Fallback', function(){return new SkinFallback;});$factory->register( 'apioutput', 'ApiOutput', function(){return new SkinApi;});return $factory;},'WatchedItemStore'=> function(MediaWikiServices $services){$store=new WatchedItemStore($services->getDBLoadBalancer(), new HashBagOStuff([ 'maxKeys'=> 100]));$store->setStatsdDataFactory($services->getStatsdDataFactory());return $store;},'WatchedItemQueryService'=> function(MediaWikiServices $services){return new WatchedItemQueryService($services->getDBLoadBalancer());},'CryptRand'=> function(MediaWikiServices $services){$secretKey=$services->getMainConfig() ->get( 'SecretKey');return new CryptRand(['wfHostname','wfWikiID', function() use($secretKey){return $secretKey?: '';}], defined( 'MW_CONFIG_FILE')?[MW_CONFIG_FILE]:[], LoggerFactory::getInstance( 'CryptRand'));},'CryptHKDF'=> function(MediaWikiServices $services){$config=$services->getMainConfig();$secret=$config->get( 'HKDFSecret')?:$config->get( 'SecretKey');if(!$secret){throw new RuntimeException("Cannot use MWCryptHKDF without a secret.");}$context=[microtime(), getmypid(), gethostname()];$cache=$services->getLocalServerObjectCache();if($cache instanceof EmptyBagOStuff){$cache=ObjectCache::getLocalClusterInstance();}return new CryptHKDF($secret, $config->get( 'HKDFAlgorithm'), $cache, $context, $services->getCryptRand());},'MediaHandlerFactory'=> function(MediaWikiServices $services){return new MediaHandlerFactory($services->getMainConfig() ->get( 'MediaHandlers'));},'MimeAnalyzer'=> function(MediaWikiServices $services){return new MimeMagic(MimeMagic::applyDefaultParameters([], $services->getMainConfig()));},'ProxyLookup'=> function(MediaWikiServices $services){$mainConfig=$services->getMainConfig();return new ProxyLookup($mainConfig->get( 'SquidServers'), $mainConfig->get( 'SquidServersNoPurge'));},'LinkCache'=> function(MediaWikiServices $services){return new LinkCache($services->getTitleFormatter(), ObjectCache::getMainWANInstance());},'LinkRendererFactory'=> function(MediaWikiServices $services){return new LinkRendererFactory($services->getTitleFormatter(), $services->getLinkCache());},'LinkRenderer'=> function(MediaWikiServices $services){global $wgUser;if(defined( 'MW_NO_SESSION')){return $services->getLinkRendererFactory() ->create();}else{return $services->getLinkRendererFactory() ->createForUser($wgUser);}},'GenderCache'=> function(MediaWikiServices $services){return new GenderCache();},'_MediaWikiTitleCodec'=> function(MediaWikiServices $services){global $wgContLang;return new MediaWikiTitleCodec($wgContLang, $services->getGenderCache(), $services->getMainConfig() ->get( 'LocalInterwikis'));},'TitleFormatter'=> function(MediaWikiServices $services){return $services->getService( '_MediaWikiTitleCodec');},'TitleParser'=> function(MediaWikiServices $services){return $services->getService( '_MediaWikiTitleCodec');},'MainObjectStash'=> function(MediaWikiServices $services){$mainConfig=$services->getMainConfig();$id=$mainConfig->get( 'MainStash');if(!isset($mainConfig->get( 'ObjectCaches')[$id])){throw new UnexpectedValueException("Cache type \"$id\" is not present in \$wgObjectCaches.");}return\ObjectCache::newFromParams($mainConfig->get( 'ObjectCaches')[$id]);},'MainWANObjectCache'=> function(MediaWikiServices $services){$mainConfig=$services->getMainConfig();$id=$mainConfig->get( 'MainWANCache');if(!isset($mainConfig->get( 'WANObjectCaches')[$id])){throw new UnexpectedValueException("WAN cache type \"$id\" is not present in \$wgWANObjectCaches.");}$params=$mainConfig->get( 'WANObjectCaches')[$id];$objectCacheId=$params['cacheId'];if(!isset($mainConfig->get( 'ObjectCaches')[$objectCacheId])){throw new UnexpectedValueException("Cache type \"$objectCacheId\" is not present in \$wgObjectCaches.");}$params['store']=$mainConfig->get( 'ObjectCaches')[$objectCacheId];return\ObjectCache::newWANCacheFromParams($params);},'LocalServerObjectCache'=> function(MediaWikiServices $services){$mainConfig=$services->getMainConfig();if(function_exists( 'apc_fetch')){$id= 'apc';}elseif(function_exists( 'apcu_fetch')){$id= 'apcu';}elseif(function_exists( 'xcache_get')&&wfIniGetBool( 'xcache.var_size')){$id= 'xcache';}elseif(function_exists( 'wincache_ucache_get')){$id= 'wincache';}else{$id=CACHE_NONE;}if(!isset($mainConfig->get( 'ObjectCaches')[$id])){throw new UnexpectedValueException("Cache type \"$id\" is not present in \$wgObjectCaches.");}return\ObjectCache::newFromParams($mainConfig->get( 'ObjectCaches')[$id]);},'VirtualRESTServiceClient'=> function(MediaWikiServices $services){$config=$services->getMainConfig() ->get( 'VirtualRestConfig');$vrsClient=new VirtualRESTServiceClient(new MultiHttpClient([]));foreach($config['paths'] as $prefix=> $serviceConfig){$class=$serviceConfig['class'];$constructArg=isset($serviceConfig['options'])?$serviceConfig['options']:[];$constructArg+=$config['global'];$vrsClient->mount($prefix, [ 'class'=> $class, 'config'=> $constructArg]);}return $vrsClient;},]
when a variable name is used in a function
Definition: design.txt:93
static $mLangObjCache
Definition: Language.php:60
parseFormattedNumber($number)
Definition: Language.php:3285
$wgTranslateNumerals
For Hindi and Arabic use local numerals instead of Western style (0-9) numerals in interface...
static isUtf8($value)
Test whether a string is valid UTF-8.
Definition: StringUtils.php:41
static $mWeekdayAbbrevMsgs
Definition: Language.php:67
diff(ConvertibleTimestamp $relativeTo)
Calculate the difference between two ConvertibleTimestamp objects.
$mMagicExtensions
Definition: Language.php:42
getWeekdayName($key)
Definition: Language.php:964
setCode($code)
Definition: Language.php:4277
static $pdf
Definition: Language.php:157
static newFromCode($code)
Create a language object for a given language code.
Definition: Language.php:207
usually copyright or history_copyright This message must be in HTML not wikitext & $link
Definition: hooks.txt:2889
time($ts, $adj=false, $format=true, $timecorrection=false)
Definition: Language.php:2256
embedBidi($text= '')
Wraps argument with unicode control characters for directionality safety.
Definition: Language.php:3917
__construct()
Definition: Language.php:413
static getJsonMessagesFileName($code)
Definition: Language.php:4346
wfTimestamp($outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
$wgLanguageCode
Site language code.
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:1936
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:4671
semicolonList(array $list)
Take a list of strings and build a locale-friendly semicolon-separated list, using the local semicolo...
Definition: Language.php:3431
addMagicWordsByLang($newWords)
Add magic words to the extension array.
Definition: Language.php:3189
segmentForDiff($text)
languages like Chinese need to be segmented in order for the diff to be of any use ...
Definition: Language.php:3985
static $lre
Unicode directional formatting characters, for embedBidi()
Definition: Language.php:155
convertHtml($text, $isTitle=false)
Perform output conversion on a string, and encode for safe HTML output.
Definition: Language.php:4076
ucwordbreaksCallbackAscii($matches)
Definition: Language.php:2616
const NS_PROJECT
Definition: Defines.php:60
static $mHijriCalendarMonthMsgs
Definition: Language.php:109
ucwordsCallbackMB($matches)
Definition: Language.php:2632
linkPrefixCharset()
A regular expression character set to match legal word-prefixing characters which should be merged on...
Definition: Language.php:4203
ucwords($str)
Definition: Language.php:2722
static getMain()
Static methods.
getIranianCalendarMonthName($key)
Definition: Language.php:980
getPreferredVariant()
Definition: Language.php:4101
LanguageConverter $mConverter
Definition: Language.php:39
msg($msg)
Get message object in this language.
Definition: Language.php:910
__destruct()
Reduce memory usage.
Definition: Language.php:427
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:4623
truncateHtml($text, $length, $ellipsis= '...')
Truncate a string of valid HTML to a specified length in bytes, appending an optional string (e...
Definition: Language.php:3568
$wgLocalisationCacheConf
Localisation cache configuration.
getArrow($direction= 'forwards')
An arrow, depending on the language direction.
Definition: Language.php:3113
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 convertDoubleWidth($string)
convert double-width roman characters to single-width.
Definition: Language.php:2855
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 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 unsetoffset-wrap String Wrap the message in html(usually something like"&lt
getDefaultVariant()
Definition: Language.php:4108
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context $options
Definition: hooks.txt:1046
viewPrevNext(Title $title, $offset, $limit, array $query=[], $atend=false)
Generate (prev x| next x) (20|50|100...) type links for paging.
Definition: Language.php:4707
getConverter()
Return the LanguageConverter used in the Language.
Definition: Language.php:4005
static $mIranianCalendarMonthMsgs
Definition: Language.php:86
static hebrewNumeral($num)
Hebrew Gematria number formatting up to 9999.
Definition: Language.php:2003
const NS_PROJECT_TALK
Definition: Defines.php:61
getFormattedNsText($index)
A convenience function that returns the same thing as getNsText() except with '_' changed to ' '...
Definition: Language.php:558
getMonthAbbreviation($key)
Definition: Language.php:945
getLocalURL($query= '', $query2=false)
Get a URL with no fragment or server name (relative URL) from a Title object.
Definition: Title.php:1740
static tsToHebrew($ts)
Converting Gregorian dates to Hebrew dates.
Definition: Language.php:1690
getBookstoreList()
Exports $wgBookstoreListEn.
Definition: Language.php:452
capitalizeAllNouns()
Definition: Language.php:3102
getMonthNameGen($key)
Definition: Language.php:937
listToText(array $l)
Take a list of strings and build a locale-friendly comma-separated list, using the local comma-separa...
Definition: Language.php:3389
$wgDummyLanguageCodes
List of language codes that don't correspond to an actual language.
format($format)
Format the timestamp in a given format.
caseFold($s)
Return a case-folded representation of $s.
Definition: Language.php:2786
static getMessagesFileName($code)
Definition: Language.php:4333
$wgAllUnicodeFixes
Set this to always convert certain Unicode sequences to modern ones regardless of the content languag...
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:360
$cache
Definition: mcc.php:33
getMonthName($key)
Definition: Language.php:918
getTimestamp($style=TS_UNIX)
Get the timestamp represented by this object in a certain form.
static isValidCode($code)
Returns true if a language code string is of a valid form, whether or not it exists.
Definition: Language.php:335
uc($str, $first=false)
Convert a string to uppercase.
Definition: Language.php:2663
static getMessagesFor($code)
Get all messages for a given language WARNING: this may take a long time.
Definition: Language.php:4426
ucwordbreaksCallbackMB($matches)
Definition: Language.php:2624
static $strongDirRegex
Directionality test regex for embedBidi().
Definition: Language.php:172
needsGenderDistinction()
Whether this language uses gender-dependent namespace aliases.
Definition: Language.php:586
wfIsInfinity($str)
Determine input string is represents as infinity.
getHebrewCalendarMonthNameGen($key)
Definition: Language.php:996
const TS_MW
MediaWiki concatenated string timestamp (YYYYMMDDHHMMSS)
Definition: defines.php:11
getURLVariant()
Definition: Language.php:4115
updateConversionTable(Title $title)
Refresh the cache of conversion tables when MediaWiki:Conversiontable* is updated.
Definition: Language.php:4162
convertTitle($title)
Convert a Title object to a string in the preferred variant.
Definition: Language.php:4035
static $rle
Definition: Language.php:156
convert($text)
convert text to different variants of a language.
Definition: Language.php:4025
wfBCP47($code)
Get the normalised IETF language tag See unit test for examples.
$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:953
getNamespaceIds()
Definition: Language.php:663
getVariantname($code, $usemsg=true)
short names for language variants used for language conversion links.
Definition: Language.php:710
static getFileName($prefix= 'Language', $code, $suffix= '.php')
Get the name of a file for a certain language code.
Definition: Language.php:4321
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:2295
static run($event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:131
static getSuggestedDurations($lang=null)
Get an array of suggested block durations from MediaWiki:Ipboptions.
ucwordbreaks($str)
capitalize words at word breaks
Definition: Language.php:2746
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books $tag
Definition: hooks.txt:1007
userTime($ts, User $user, array $options=[])
Get the formatted time for the given timestamp and formatted for the given user.
Definition: Language.php:2434
getMessage($key)
Definition: Language.php:2582
static $mHebrewCalendarMonthMsgs
Definition: Language.php:93
pipeList(array $list)
Same as commaList, but separate it with the pipe instead.
Definition: Language.php:3443
getMessageFromDB($msg)
Get a message from the MediaWiki namespace.
Definition: Language.php:900
formatNum($number, $nocommafy=false)
Normally we output all numbers in plain en_US style, that is 293,291.235 for twohundredninetythreetho...
Definition: Language.php:3249
recodeInput($s)
Definition: Language.php:2970
$mExtendedSpecialPageAliases
Definition: Language.php:46
namespace and then decline to actually register it & $namespaces
Definition: hooks.txt:953
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:4797
getDefaultDateFormat()
Definition: Language.php:741
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:802
static fetchLanguageName($code, $inLanguage=null, $include= 'all')
Definition: Language.php:888
resetNamespaces()
Resets all of the namespace caches.
Definition: Language.php:509
please add to it if you re going to add events to the MediaWiki code where normally authentication against an external auth plugin would be creating a local account $user
Definition: hooks.txt:242
static MapCacheLRU null $grammarTransformations
Cache for grammar rules data.
Definition: Language.php:144
segmentByWord($string)
Some languages such as Chinese require word segmentation, Specify such segmentation when overridden i...
Definition: Language.php:2832
getMagicWords()
Get all magic words from cache.
Definition: Language.php:3143
getGrammarTransformations()
Get the grammar transformations data for the language.
Definition: Language.php:3765
getMonthNamesArray()
Definition: Language.php:925
numLink(Title $title, $offset, $limit, array $query, $link, $tooltipMsg, $class)
Helper function for viewPrevNext() that generates links.
Definition: Language.php:4753
ucfirst($str)
Make a string's first character uppercase.
Definition: Language.php:2643
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context the output can only depend on parameters provided to this hook not on global state indicating whether full HTML should be generated If generation of HTML may be but other information should still be present in the ParserOutput object to manipulate or replace but no entry for that model exists in $wgContentHandlers if desired whether it is OK to use $contentModel on $title Handler functions that modify $ok should generally return false to prevent further hooks from further modifying $ok inclusive false for true for descending in case the handler function wants to provide a converted Content object Note that $result getContentModel() must return $toModel. 'CustomEditor'$rcid is used in generating this variable which contains information about the new such as the revision s whether the revision was marked as a minor edit or not
Definition: hooks.txt:1156
getOption($oname, $defaultOverride=null, $ignoreHidden=false)
Get the user's current setting for a given option.
Definition: User.php:2766
getHijriCalendarMonthName($key)
Definition: Language.php:1004
static getFallbackFor($code)
Get the first fallback for a given language.
Definition: Language.php:4363
timeanddate($ts, $adj=false, $format=true, $timecorrection=false)
Definition: Language.php:2276
getPluralRuleType($number)
Find the plural rule type appropriate for the given number For example, if the language is set to Ara...
Definition: Language.php:4849
userAdjust($ts, $tz=false)
Used by date() and time() to adjust the time output.
Definition: Language.php:2090
digitTransformTable()
Definition: Language.php:3369
userDate($ts, User $user, array $options=[])
Get the formatted date for the given timestamp and formatted for the given user.
Definition: Language.php:2411
getConvRuleTitle()
Get the conversion rule title, if any.
Definition: Language.php:4769
getMagic($mw)
Fill a MagicWord object with data from here.
Definition: Language.php:3163
static classFromCode($code)
Definition: Language.php:4305
$wgExtraNamespaces
Additional namespaces.
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:2513
getCode()
Get the internal language code for this language object.
Definition: Language.php:4253
replaceGrammarInNamespace($m)
Definition: Language.php:4479
commafy($number)
Adds commas to a given number.
Definition: Language.php:3310
digitGroupingPattern()
Definition: Language.php:3362
sprintfDate($format, $ts, DateTimeZone $zone=null, &$ttl= 'unused')
This is a workalike of PHP's date() function, but with better internationalisation, a reduced set of format characters, and a better escaping format.
Definition: Language.php:1094
$digitGroupingPattern
Definition: MessagesAs.php:167
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:2457
translateBlockExpiry($str, User $user=null)
Definition: Language.php:3943
truncate($string, $length, $ellipsis= '...', $adjustLength=true)
Truncate a string to a specified length in bytes, appending an optional string (e.g.
Definition: Language.php:3467
static romanNumeral($num)
Roman number formatting up to 10000.
Definition: Language.php:1972
convertForSearchResult($termsArray)
Definition: Language.php:2885
removeBadCharLast($string)
Remove bytes that represent an incomplete Unicode character at the end of string (e.g.
Definition: Language.php:3516
$namespaceNames
Definition: Language.php:48
static tsToIranian($ts)
Algorithm by Roozbeh Pournader and Mohammad Toossi to convert Gregorian dates to Iranian dates...
Definition: Language.php:1578
$transformData
ReplacementArray object caches.
Definition: Language.php:53
specialList($page, $details, $oppositedm=true)
Make a list item, used by various special pages.
Definition: Language.php:4684
getExtraHashOptions()
returns language specific options used by User::getPageRenderHash() for example, the preferred langua...
Definition: Language.php:4141
getParsedTitle()
For languages that support multiple variants, the title of an article may be displayed differently in...
Definition: Language.php:4152
preConvertPlural($forms, $count)
Checks that convertPlural was given an array and pads it to requested amount of forms by copying the ...
Definition: Language.php:3894
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:4131
$wgGrammarForms
Some languages need different word forms, usually for different cases.
emphasize($text)
Italic is unsuitable for some languages.
Definition: Language.php:3223
getDirMarkEntity($opposite=false)
A hidden direction mark (LRM or RLM), depending on the language direction.
Definition: Language.php:3073
separatorTransformTable()
Definition: Language.php:3376
isRTL()
For right-to-left language support.
Definition: Language.php:3026
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context the output can only depend on parameters provided to this hook not on global state indicating whether full HTML should be generated If generation of HTML may be but other information should still be present in the ParserOutput object to manipulate or replace but no entry for that model exists in $wgContentHandlers if desired whether it is OK to use $contentModel on $title Handler functions that modify $ok should generally return false to prevent further hooks from further modifying $ok inclusive $limit
Definition: hooks.txt:1046
getDatePreference()
Get the user's preferred date format.
Definition: User.php:3088
getParentLanguage()
Get the "parent" language which has a converter to convert a "compatible" language (in another varian...
Definition: Language.php:4214
getDir()
Return the correct HTML 'dir' attribute value for this language.
Definition: Language.php:3034
getFormattedNamespaces()
A convenience function that returns getNamespaces() with spaces instead of underscores in values...
Definition: Language.php:521
$wgLocalTZoffset
Set an offset from UTC in minutes to use for the default timezone setting for anonymous users and new...
checkTitleEncoding($s)
Definition: Language.php:2795
$count
normalizeForSearch($string)
Some languages have special punctuation need to be normalized.
Definition: Language.php:2843
static $mMonthGenMsgs
Definition: Language.php:76
getDurationIntervals($seconds, array $chosenIntervals=[])
Takes a number of seconds and returns an array with a set of corresponding intervals.
Definition: Language.php:2321
static $mWeekdayMsgs
Definition: Language.php:62
linkPrefixExtension()
To allow "foo[[bar]]" to extend the link over the whole word "foobar".
Definition: Language.php:3135
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
$mNamespaceIds
Definition: Language.php:48
getGrammarForms()
Get the grammar forms for the content language.
Definition: Language.php:3745
static dateTimeObjFormat(&$dateTimeObj, $ts, $zone, $code)
Pass through result from $dateTimeObj->format()
Definition: Language.php:1016
=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
getGenderNsText($index, $gender)
Returns gender-dependent namespace alias if available.
Definition: Language.php:571
static decode($value, $assoc=false)
Decodes a JSON string.
Definition: FormatJson.php:187
static getDefaultOption($opt)
Get a given default option value.
Definition: User.php:1563
initContLang()
Hook which will be called if this is the content language.
Definition: Language.php:437
static factory($code)
Get a cached or new language object for a given language code.
Definition: Language.php:181
fixVariableInNamespace($talk)
Definition: Language.php:4458
getNamespaces()
Returns an array of localised namespaces indexed by their numbers.
Definition: Language.php:462
wfGetPrecompiledData($name)
Get an object from the precompiled serialized directory.
$mMagicHookDone
Definition: Language.php:42
gender($gender, $forms)
Provides an alternative text depending on specified gender.
Definition: Language.php:3814
commaList(array $list)
Take a list of strings and build a locale-friendly comma-separated list, using the local comma-separa...
Definition: Language.php:3418
static $mHebrewCalendarMonthGenMsgs
Definition: Language.php:101
const DB_SLAVE
Definition: Defines.php:28
const NS_USER_TALK
Definition: Defines.php:59
static array $languagesWithVariants
languages supporting variants
static element($element, $attribs=[], $contents= '')
Identical to rawElement(), but HTML-escapes $contents (like Xml::element()).
Definition: Html.php:229
static getCanonicalIndex($name)
Returns the index for a given canonical name, or NULL The input must be converted to lower case first...
static getCanonicalNamespaces($rebuild=false)
Returns array of all defined namespaces with their canonical (English) names.
getMonthAbbreviationsArray()
Definition: Language.php:952
Library for creating and parsing MW-style timestamps.
Definition: MWTimestamp.php:31
do that in ParserLimitReportFormat instead use this to modify the parameters of the image and a DIV can begin in one section and end in another Make sure your code can handle that case gracefully See the EditSectionClearerLink extension for an example zero but section is usually empty its values are the globals values before the output is cached one of or reset my talk my contributions etc etc otherwise the built in rate limiting checks are if enabled allows for interception of redirect as a string mapping parameter names to values & $type
Definition: hooks.txt:2491
hasWordBreaks()
Most writing systems use whitespace to break up words.
Definition: Language.php:2821
static strongDirFromContent($text= '')
Gets directionality of the first strongly directional codepoint, for embedBidi()
Definition: Language.php:1955
static isSupportedLanguage($code)
Checks whether any localisation is available for that language tag in MediaWiki (MessagesXx.php exists).
Definition: Language.php:253
alignStart()
Return 'left' or 'right' as appropriate alignment for line-start for this language's text direction...
Definition: Language.php:3046
static tsToYear($ts, $cName)
Algorithm to convert Gregorian dates to Thai solar dates, Minguo dates or Minguo dates.
Definition: Language.php:1869
getPluralRuleIndexNumber($number)
Find the index number of the plural rule appropriate for the given number.
Definition: Language.php:4835
see documentation in includes Linker php for Linker::makeImageLink & $time
Definition: hooks.txt:1749
static hebrewYearStart($year)
This calculates the Hebrew year start, as days since 1 September.
Definition: Language.php:1831
do that in ParserLimitReportFormat instead use this to modify the parameters of the image and a DIV can begin in one section and end in another Make sure your code can handle that case gracefully See the EditSectionClearerLink extension for an example zero but section is usually empty its values are the globals values before the output is cached $page
Definition: hooks.txt:2491
static $GREG_DAYS
Definition: Language.php:1563
getHebrewCalendarMonthName($key)
Definition: Language.php:988
$wgUser
Definition: Setup.php:806
unsegmentForDiff($text)
and unsegment to show the result
Definition: Language.php:3995
$matches
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:300