MediaWiki REL1_32
Language.php
Go to the documentation of this file.
1<?php
29use CLDRPluralRuleParser\Evaluator;
30
35class Language {
40 const AS_AUTONYMS = null;
41
46 const ALL = 'all';
47
53 const SUPPORTED = 'mwfile';
54
59
60 public $mVariants, $mCode, $mLoaded = false;
61 public $mMagicExtensions = [], $mMagicHookDone = false;
62 private $mHtmlCode = null, $mParentLanguage = false;
63
64 public $dateFormatStrings = [];
66
68 protected $namespaceNames;
70
74 public $transformData = [];
75
79 static public $dataCache;
80
81 static public $mLangObjCache = [];
82
88
94
95 static public $mWeekdayMsgs = [
96 'sunday', 'monday', 'tuesday', 'wednesday', 'thursday',
97 'friday', 'saturday'
98 ];
99
100 static public $mWeekdayAbbrevMsgs = [
101 'sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'
102 ];
103
104 static public $mMonthMsgs = [
105 'january', 'february', 'march', 'april', 'may_long', 'june',
106 'july', 'august', 'september', 'october', 'november',
107 'december'
108 ];
109 static public $mMonthGenMsgs = [
110 'january-gen', 'february-gen', 'march-gen', 'april-gen', 'may-gen', 'june-gen',
111 'july-gen', 'august-gen', 'september-gen', 'october-gen', 'november-gen',
112 'december-gen'
113 ];
114 static public $mMonthAbbrevMsgs = [
115 'jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug',
116 'sep', 'oct', 'nov', 'dec'
117 ];
118
120 'iranian-calendar-m1', 'iranian-calendar-m2', 'iranian-calendar-m3',
121 'iranian-calendar-m4', 'iranian-calendar-m5', 'iranian-calendar-m6',
122 'iranian-calendar-m7', 'iranian-calendar-m8', 'iranian-calendar-m9',
123 'iranian-calendar-m10', 'iranian-calendar-m11', 'iranian-calendar-m12'
124 ];
125
127 'hebrew-calendar-m1', 'hebrew-calendar-m2', 'hebrew-calendar-m3',
128 'hebrew-calendar-m4', 'hebrew-calendar-m5', 'hebrew-calendar-m6',
129 'hebrew-calendar-m7', 'hebrew-calendar-m8', 'hebrew-calendar-m9',
130 'hebrew-calendar-m10', 'hebrew-calendar-m11', 'hebrew-calendar-m12',
131 'hebrew-calendar-m6a', 'hebrew-calendar-m6b'
132 ];
133
135 'hebrew-calendar-m1-gen', 'hebrew-calendar-m2-gen', 'hebrew-calendar-m3-gen',
136 'hebrew-calendar-m4-gen', 'hebrew-calendar-m5-gen', 'hebrew-calendar-m6-gen',
137 'hebrew-calendar-m7-gen', 'hebrew-calendar-m8-gen', 'hebrew-calendar-m9-gen',
138 'hebrew-calendar-m10-gen', 'hebrew-calendar-m11-gen', 'hebrew-calendar-m12-gen',
139 'hebrew-calendar-m6a-gen', 'hebrew-calendar-m6b-gen'
140 ];
141
142 static public $mHijriCalendarMonthMsgs = [
143 'hijri-calendar-m1', 'hijri-calendar-m2', 'hijri-calendar-m3',
144 'hijri-calendar-m4', 'hijri-calendar-m5', 'hijri-calendar-m6',
145 'hijri-calendar-m7', 'hijri-calendar-m8', 'hijri-calendar-m9',
146 'hijri-calendar-m10', 'hijri-calendar-m11', 'hijri-calendar-m12'
147 ];
148
153 static public $durationIntervals = [
154 'millennia' => 31556952000,
155 'centuries' => 3155695200,
156 'decades' => 315569520,
157 'years' => 31556952, // 86400 * ( 365 + ( 24 * 3 + 25 ) / 400 )
158 'weeks' => 604800,
159 'days' => 86400,
160 'hours' => 3600,
161 'minutes' => 60,
162 'seconds' => 1,
163 ];
164
171 static private $fallbackLanguageCache = [];
172
178
183 static private $languageNameCache;
184
188 static private $lre = "\u{202A}"; // U+202A LEFT-TO-RIGHT EMBEDDING
189 static private $rle = "\u{202B}"; // U+202B RIGHT-TO-LEFT EMBEDDING
190 static private $pdf = "\u{202C}"; // U+202C POP DIRECTIONAL FORMATTING
191
203 // @codeCoverageIgnoreStart
204 // phpcs:ignore Generic.Files.LineLength
205 static private $strongDirRegex = '/(?:([\x{41}-\x{5a}\x{61}-\x{7a}\x{aa}\x{b5}\x{ba}\x{c0}-\x{d6}\x{d8}-\x{f6}\x{f8}-\x{2b8}\x{2bb}-\x{2c1}\x{2d0}\x{2d1}\x{2e0}-\x{2e4}\x{2ee}\x{370}-\x{373}\x{376}\x{377}\x{37a}-\x{37d}\x{37f}\x{386}\x{388}-\x{38a}\x{38c}\x{38e}-\x{3a1}\x{3a3}-\x{3f5}\x{3f7}-\x{482}\x{48a}-\x{52f}\x{531}-\x{556}\x{559}-\x{55f}\x{561}-\x{587}\x{589}\x{903}-\x{939}\x{93b}\x{93d}-\x{940}\x{949}-\x{94c}\x{94e}-\x{950}\x{958}-\x{961}\x{964}-\x{980}\x{982}\x{983}\x{985}-\x{98c}\x{98f}\x{990}\x{993}-\x{9a8}\x{9aa}-\x{9b0}\x{9b2}\x{9b6}-\x{9b9}\x{9bd}-\x{9c0}\x{9c7}\x{9c8}\x{9cb}\x{9cc}\x{9ce}\x{9d7}\x{9dc}\x{9dd}\x{9df}-\x{9e1}\x{9e6}-\x{9f1}\x{9f4}-\x{9fa}\x{a03}\x{a05}-\x{a0a}\x{a0f}\x{a10}\x{a13}-\x{a28}\x{a2a}-\x{a30}\x{a32}\x{a33}\x{a35}\x{a36}\x{a38}\x{a39}\x{a3e}-\x{a40}\x{a59}-\x{a5c}\x{a5e}\x{a66}-\x{a6f}\x{a72}-\x{a74}\x{a83}\x{a85}-\x{a8d}\x{a8f}-\x{a91}\x{a93}-\x{aa8}\x{aaa}-\x{ab0}\x{ab2}\x{ab3}\x{ab5}-\x{ab9}\x{abd}-\x{ac0}\x{ac9}\x{acb}\x{acc}\x{ad0}\x{ae0}\x{ae1}\x{ae6}-\x{af0}\x{af9}\x{b02}\x{b03}\x{b05}-\x{b0c}\x{b0f}\x{b10}\x{b13}-\x{b28}\x{b2a}-\x{b30}\x{b32}\x{b33}\x{b35}-\x{b39}\x{b3d}\x{b3e}\x{b40}\x{b47}\x{b48}\x{b4b}\x{b4c}\x{b57}\x{b5c}\x{b5d}\x{b5f}-\x{b61}\x{b66}-\x{b77}\x{b83}\x{b85}-\x{b8a}\x{b8e}-\x{b90}\x{b92}-\x{b95}\x{b99}\x{b9a}\x{b9c}\x{b9e}\x{b9f}\x{ba3}\x{ba4}\x{ba8}-\x{baa}\x{bae}-\x{bb9}\x{bbe}\x{bbf}\x{bc1}\x{bc2}\x{bc6}-\x{bc8}\x{bca}-\x{bcc}\x{bd0}\x{bd7}\x{be6}-\x{bf2}\x{c01}-\x{c03}\x{c05}-\x{c0c}\x{c0e}-\x{c10}\x{c12}-\x{c28}\x{c2a}-\x{c39}\x{c3d}\x{c41}-\x{c44}\x{c58}-\x{c5a}\x{c60}\x{c61}\x{c66}-\x{c6f}\x{c7f}\x{c82}\x{c83}\x{c85}-\x{c8c}\x{c8e}-\x{c90}\x{c92}-\x{ca8}\x{caa}-\x{cb3}\x{cb5}-\x{cb9}\x{cbd}-\x{cc4}\x{cc6}-\x{cc8}\x{cca}\x{ccb}\x{cd5}\x{cd6}\x{cde}\x{ce0}\x{ce1}\x{ce6}-\x{cef}\x{cf1}\x{cf2}\x{d02}\x{d03}\x{d05}-\x{d0c}\x{d0e}-\x{d10}\x{d12}-\x{d3a}\x{d3d}-\x{d40}\x{d46}-\x{d48}\x{d4a}-\x{d4c}\x{d4e}\x{d57}\x{d5f}-\x{d61}\x{d66}-\x{d75}\x{d79}-\x{d7f}\x{d82}\x{d83}\x{d85}-\x{d96}\x{d9a}-\x{db1}\x{db3}-\x{dbb}\x{dbd}\x{dc0}-\x{dc6}\x{dcf}-\x{dd1}\x{dd8}-\x{ddf}\x{de6}-\x{def}\x{df2}-\x{df4}\x{e01}-\x{e30}\x{e32}\x{e33}\x{e40}-\x{e46}\x{e4f}-\x{e5b}\x{e81}\x{e82}\x{e84}\x{e87}\x{e88}\x{e8a}\x{e8d}\x{e94}-\x{e97}\x{e99}-\x{e9f}\x{ea1}-\x{ea3}\x{ea5}\x{ea7}\x{eaa}\x{eab}\x{ead}-\x{eb0}\x{eb2}\x{eb3}\x{ebd}\x{ec0}-\x{ec4}\x{ec6}\x{ed0}-\x{ed9}\x{edc}-\x{edf}\x{f00}-\x{f17}\x{f1a}-\x{f34}\x{f36}\x{f38}\x{f3e}-\x{f47}\x{f49}-\x{f6c}\x{f7f}\x{f85}\x{f88}-\x{f8c}\x{fbe}-\x{fc5}\x{fc7}-\x{fcc}\x{fce}-\x{fda}\x{1000}-\x{102c}\x{1031}\x{1038}\x{103b}\x{103c}\x{103f}-\x{1057}\x{105a}-\x{105d}\x{1061}-\x{1070}\x{1075}-\x{1081}\x{1083}\x{1084}\x{1087}-\x{108c}\x{108e}-\x{109c}\x{109e}-\x{10c5}\x{10c7}\x{10cd}\x{10d0}-\x{1248}\x{124a}-\x{124d}\x{1250}-\x{1256}\x{1258}\x{125a}-\x{125d}\x{1260}-\x{1288}\x{128a}-\x{128d}\x{1290}-\x{12b0}\x{12b2}-\x{12b5}\x{12b8}-\x{12be}\x{12c0}\x{12c2}-\x{12c5}\x{12c8}-\x{12d6}\x{12d8}-\x{1310}\x{1312}-\x{1315}\x{1318}-\x{135a}\x{1360}-\x{137c}\x{1380}-\x{138f}\x{13a0}-\x{13f5}\x{13f8}-\x{13fd}\x{1401}-\x{167f}\x{1681}-\x{169a}\x{16a0}-\x{16f8}\x{1700}-\x{170c}\x{170e}-\x{1711}\x{1720}-\x{1731}\x{1735}\x{1736}\x{1740}-\x{1751}\x{1760}-\x{176c}\x{176e}-\x{1770}\x{1780}-\x{17b3}\x{17b6}\x{17be}-\x{17c5}\x{17c7}\x{17c8}\x{17d4}-\x{17da}\x{17dc}\x{17e0}-\x{17e9}\x{1810}-\x{1819}\x{1820}-\x{1877}\x{1880}-\x{18a8}\x{18aa}\x{18b0}-\x{18f5}\x{1900}-\x{191e}\x{1923}-\x{1926}\x{1929}-\x{192b}\x{1930}\x{1931}\x{1933}-\x{1938}\x{1946}-\x{196d}\x{1970}-\x{1974}\x{1980}-\x{19ab}\x{19b0}-\x{19c9}\x{19d0}-\x{19da}\x{1a00}-\x{1a16}\x{1a19}\x{1a1a}\x{1a1e}-\x{1a55}\x{1a57}\x{1a61}\x{1a63}\x{1a64}\x{1a6d}-\x{1a72}\x{1a80}-\x{1a89}\x{1a90}-\x{1a99}\x{1aa0}-\x{1aad}\x{1b04}-\x{1b33}\x{1b35}\x{1b3b}\x{1b3d}-\x{1b41}\x{1b43}-\x{1b4b}\x{1b50}-\x{1b6a}\x{1b74}-\x{1b7c}\x{1b82}-\x{1ba1}\x{1ba6}\x{1ba7}\x{1baa}\x{1bae}-\x{1be5}\x{1be7}\x{1bea}-\x{1bec}\x{1bee}\x{1bf2}\x{1bf3}\x{1bfc}-\x{1c2b}\x{1c34}\x{1c35}\x{1c3b}-\x{1c49}\x{1c4d}-\x{1c7f}\x{1cc0}-\x{1cc7}\x{1cd3}\x{1ce1}\x{1ce9}-\x{1cec}\x{1cee}-\x{1cf3}\x{1cf5}\x{1cf6}\x{1d00}-\x{1dbf}\x{1e00}-\x{1f15}\x{1f18}-\x{1f1d}\x{1f20}-\x{1f45}\x{1f48}-\x{1f4d}\x{1f50}-\x{1f57}\x{1f59}\x{1f5b}\x{1f5d}\x{1f5f}-\x{1f7d}\x{1f80}-\x{1fb4}\x{1fb6}-\x{1fbc}\x{1fbe}\x{1fc2}-\x{1fc4}\x{1fc6}-\x{1fcc}\x{1fd0}-\x{1fd3}\x{1fd6}-\x{1fdb}\x{1fe0}-\x{1fec}\x{1ff2}-\x{1ff4}\x{1ff6}-\x{1ffc}\x{200e}\x{2071}\x{207f}\x{2090}-\x{209c}\x{2102}\x{2107}\x{210a}-\x{2113}\x{2115}\x{2119}-\x{211d}\x{2124}\x{2126}\x{2128}\x{212a}-\x{212d}\x{212f}-\x{2139}\x{213c}-\x{213f}\x{2145}-\x{2149}\x{214e}\x{214f}\x{2160}-\x{2188}\x{2336}-\x{237a}\x{2395}\x{249c}-\x{24e9}\x{26ac}\x{2800}-\x{28ff}\x{2c00}-\x{2c2e}\x{2c30}-\x{2c5e}\x{2c60}-\x{2ce4}\x{2ceb}-\x{2cee}\x{2cf2}\x{2cf3}\x{2d00}-\x{2d25}\x{2d27}\x{2d2d}\x{2d30}-\x{2d67}\x{2d6f}\x{2d70}\x{2d80}-\x{2d96}\x{2da0}-\x{2da6}\x{2da8}-\x{2dae}\x{2db0}-\x{2db6}\x{2db8}-\x{2dbe}\x{2dc0}-\x{2dc6}\x{2dc8}-\x{2dce}\x{2dd0}-\x{2dd6}\x{2dd8}-\x{2dde}\x{3005}-\x{3007}\x{3021}-\x{3029}\x{302e}\x{302f}\x{3031}-\x{3035}\x{3038}-\x{303c}\x{3041}-\x{3096}\x{309d}-\x{309f}\x{30a1}-\x{30fa}\x{30fc}-\x{30ff}\x{3105}-\x{312d}\x{3131}-\x{318e}\x{3190}-\x{31ba}\x{31f0}-\x{321c}\x{3220}-\x{324f}\x{3260}-\x{327b}\x{327f}-\x{32b0}\x{32c0}-\x{32cb}\x{32d0}-\x{32fe}\x{3300}-\x{3376}\x{337b}-\x{33dd}\x{33e0}-\x{33fe}\x{3400}-\x{4db5}\x{4e00}-\x{9fd5}\x{a000}-\x{a48c}\x{a4d0}-\x{a60c}\x{a610}-\x{a62b}\x{a640}-\x{a66e}\x{a680}-\x{a69d}\x{a6a0}-\x{a6ef}\x{a6f2}-\x{a6f7}\x{a722}-\x{a787}\x{a789}-\x{a7ad}\x{a7b0}-\x{a7b7}\x{a7f7}-\x{a801}\x{a803}-\x{a805}\x{a807}-\x{a80a}\x{a80c}-\x{a824}\x{a827}\x{a830}-\x{a837}\x{a840}-\x{a873}\x{a880}-\x{a8c3}\x{a8ce}-\x{a8d9}\x{a8f2}-\x{a8fd}\x{a900}-\x{a925}\x{a92e}-\x{a946}\x{a952}\x{a953}\x{a95f}-\x{a97c}\x{a983}-\x{a9b2}\x{a9b4}\x{a9b5}\x{a9ba}\x{a9bb}\x{a9bd}-\x{a9cd}\x{a9cf}-\x{a9d9}\x{a9de}-\x{a9e4}\x{a9e6}-\x{a9fe}\x{aa00}-\x{aa28}\x{aa2f}\x{aa30}\x{aa33}\x{aa34}\x{aa40}-\x{aa42}\x{aa44}-\x{aa4b}\x{aa4d}\x{aa50}-\x{aa59}\x{aa5c}-\x{aa7b}\x{aa7d}-\x{aaaf}\x{aab1}\x{aab5}\x{aab6}\x{aab9}-\x{aabd}\x{aac0}\x{aac2}\x{aadb}-\x{aaeb}\x{aaee}-\x{aaf5}\x{ab01}-\x{ab06}\x{ab09}-\x{ab0e}\x{ab11}-\x{ab16}\x{ab20}-\x{ab26}\x{ab28}-\x{ab2e}\x{ab30}-\x{ab65}\x{ab70}-\x{abe4}\x{abe6}\x{abe7}\x{abe9}-\x{abec}\x{abf0}-\x{abf9}\x{ac00}-\x{d7a3}\x{d7b0}-\x{d7c6}\x{d7cb}-\x{d7fb}\x{e000}-\x{fa6d}\x{fa70}-\x{fad9}\x{fb00}-\x{fb06}\x{fb13}-\x{fb17}\x{ff21}-\x{ff3a}\x{ff41}-\x{ff5a}\x{ff66}-\x{ffbe}\x{ffc2}-\x{ffc7}\x{ffca}-\x{ffcf}\x{ffd2}-\x{ffd7}\x{ffda}-\x{ffdc}\x{10000}-\x{1000b}\x{1000d}-\x{10026}\x{10028}-\x{1003a}\x{1003c}\x{1003d}\x{1003f}-\x{1004d}\x{10050}-\x{1005d}\x{10080}-\x{100fa}\x{10100}\x{10102}\x{10107}-\x{10133}\x{10137}-\x{1013f}\x{101d0}-\x{101fc}\x{10280}-\x{1029c}\x{102a0}-\x{102d0}\x{10300}-\x{10323}\x{10330}-\x{1034a}\x{10350}-\x{10375}\x{10380}-\x{1039d}\x{1039f}-\x{103c3}\x{103c8}-\x{103d5}\x{10400}-\x{1049d}\x{104a0}-\x{104a9}\x{10500}-\x{10527}\x{10530}-\x{10563}\x{1056f}\x{10600}-\x{10736}\x{10740}-\x{10755}\x{10760}-\x{10767}\x{11000}\x{11002}-\x{11037}\x{11047}-\x{1104d}\x{11066}-\x{1106f}\x{11082}-\x{110b2}\x{110b7}\x{110b8}\x{110bb}-\x{110c1}\x{110d0}-\x{110e8}\x{110f0}-\x{110f9}\x{11103}-\x{11126}\x{1112c}\x{11136}-\x{11143}\x{11150}-\x{11172}\x{11174}-\x{11176}\x{11182}-\x{111b5}\x{111bf}-\x{111c9}\x{111cd}\x{111d0}-\x{111df}\x{111e1}-\x{111f4}\x{11200}-\x{11211}\x{11213}-\x{1122e}\x{11232}\x{11233}\x{11235}\x{11238}-\x{1123d}\x{11280}-\x{11286}\x{11288}\x{1128a}-\x{1128d}\x{1128f}-\x{1129d}\x{1129f}-\x{112a9}\x{112b0}-\x{112de}\x{112e0}-\x{112e2}\x{112f0}-\x{112f9}\x{11302}\x{11303}\x{11305}-\x{1130c}\x{1130f}\x{11310}\x{11313}-\x{11328}\x{1132a}-\x{11330}\x{11332}\x{11333}\x{11335}-\x{11339}\x{1133d}-\x{1133f}\x{11341}-\x{11344}\x{11347}\x{11348}\x{1134b}-\x{1134d}\x{11350}\x{11357}\x{1135d}-\x{11363}\x{11480}-\x{114b2}\x{114b9}\x{114bb}-\x{114be}\x{114c1}\x{114c4}-\x{114c7}\x{114d0}-\x{114d9}\x{11580}-\x{115b1}\x{115b8}-\x{115bb}\x{115be}\x{115c1}-\x{115db}\x{11600}-\x{11632}\x{1163b}\x{1163c}\x{1163e}\x{11641}-\x{11644}\x{11650}-\x{11659}\x{11680}-\x{116aa}\x{116ac}\x{116ae}\x{116af}\x{116b6}\x{116c0}-\x{116c9}\x{11700}-\x{11719}\x{11720}\x{11721}\x{11726}\x{11730}-\x{1173f}\x{118a0}-\x{118f2}\x{118ff}\x{11ac0}-\x{11af8}\x{12000}-\x{12399}\x{12400}-\x{1246e}\x{12470}-\x{12474}\x{12480}-\x{12543}\x{13000}-\x{1342e}\x{14400}-\x{14646}\x{16800}-\x{16a38}\x{16a40}-\x{16a5e}\x{16a60}-\x{16a69}\x{16a6e}\x{16a6f}\x{16ad0}-\x{16aed}\x{16af5}\x{16b00}-\x{16b2f}\x{16b37}-\x{16b45}\x{16b50}-\x{16b59}\x{16b5b}-\x{16b61}\x{16b63}-\x{16b77}\x{16b7d}-\x{16b8f}\x{16f00}-\x{16f44}\x{16f50}-\x{16f7e}\x{16f93}-\x{16f9f}\x{1b000}\x{1b001}\x{1bc00}-\x{1bc6a}\x{1bc70}-\x{1bc7c}\x{1bc80}-\x{1bc88}\x{1bc90}-\x{1bc99}\x{1bc9c}\x{1bc9f}\x{1d000}-\x{1d0f5}\x{1d100}-\x{1d126}\x{1d129}-\x{1d166}\x{1d16a}-\x{1d172}\x{1d183}\x{1d184}\x{1d18c}-\x{1d1a9}\x{1d1ae}-\x{1d1e8}\x{1d360}-\x{1d371}\x{1d400}-\x{1d454}\x{1d456}-\x{1d49c}\x{1d49e}\x{1d49f}\x{1d4a2}\x{1d4a5}\x{1d4a6}\x{1d4a9}-\x{1d4ac}\x{1d4ae}-\x{1d4b9}\x{1d4bb}\x{1d4bd}-\x{1d4c3}\x{1d4c5}-\x{1d505}\x{1d507}-\x{1d50a}\x{1d50d}-\x{1d514}\x{1d516}-\x{1d51c}\x{1d51e}-\x{1d539}\x{1d53b}-\x{1d53e}\x{1d540}-\x{1d544}\x{1d546}\x{1d54a}-\x{1d550}\x{1d552}-\x{1d6a5}\x{1d6a8}-\x{1d6da}\x{1d6dc}-\x{1d714}\x{1d716}-\x{1d74e}\x{1d750}-\x{1d788}\x{1d78a}-\x{1d7c2}\x{1d7c4}-\x{1d7cb}\x{1d800}-\x{1d9ff}\x{1da37}-\x{1da3a}\x{1da6d}-\x{1da74}\x{1da76}-\x{1da83}\x{1da85}-\x{1da8b}\x{1f110}-\x{1f12e}\x{1f130}-\x{1f169}\x{1f170}-\x{1f19a}\x{1f1e6}-\x{1f202}\x{1f210}-\x{1f23a}\x{1f240}-\x{1f248}\x{1f250}\x{1f251}\x{20000}-\x{2a6d6}\x{2a700}-\x{2b734}\x{2b740}-\x{2b81d}\x{2b820}-\x{2cea1}\x{2f800}-\x{2fa1d}\x{f0000}-\x{ffffd}\x{100000}-\x{10fffd}])|([\x{590}\x{5be}\x{5c0}\x{5c3}\x{5c6}\x{5c8}-\x{5ff}\x{7c0}-\x{7ea}\x{7f4}\x{7f5}\x{7fa}-\x{815}\x{81a}\x{824}\x{828}\x{82e}-\x{858}\x{85c}-\x{89f}\x{200f}\x{fb1d}\x{fb1f}-\x{fb28}\x{fb2a}-\x{fb4f}\x{10800}-\x{1091e}\x{10920}-\x{10a00}\x{10a04}\x{10a07}-\x{10a0b}\x{10a10}-\x{10a37}\x{10a3b}-\x{10a3e}\x{10a40}-\x{10ae4}\x{10ae7}-\x{10b38}\x{10b40}-\x{10e5f}\x{10e7f}-\x{10fff}\x{1e800}-\x{1e8cf}\x{1e8d7}-\x{1edff}\x{1ef00}-\x{1efff}\x{608}\x{60b}\x{60d}\x{61b}-\x{64a}\x{66d}-\x{66f}\x{671}-\x{6d5}\x{6e5}\x{6e6}\x{6ee}\x{6ef}\x{6fa}-\x{710}\x{712}-\x{72f}\x{74b}-\x{7a5}\x{7b1}-\x{7bf}\x{8a0}-\x{8e2}\x{fb50}-\x{fd3d}\x{fd40}-\x{fdcf}\x{fdf0}-\x{fdfc}\x{fdfe}\x{fdff}\x{fe70}-\x{fefe}\x{1ee00}-\x{1eeef}\x{1eef2}-\x{1eeff}]))/u';
206 // @codeCoverageIgnoreEnd
207
214 static function factory( $code ) {
216
217 if ( isset( $wgDummyLanguageCodes[$code] ) ) {
219 }
220
221 // get the language object to process
222 $langObj = self::$mLangObjCache[$code] ?? self::newFromCode( $code );
223
224 // merge the language object in to get it up front in the cache
225 self::$mLangObjCache = array_merge( [ $code => $langObj ], self::$mLangObjCache );
226 // get rid of the oldest ones in case we have an overflow
227 self::$mLangObjCache = array_slice( self::$mLangObjCache, 0, $wgLangObjCacheSize, true );
228
229 return $langObj;
230 }
231
239 protected static function newFromCode( $code, $fallback = false ) {
240 if ( !self::isValidCode( $code ) ) {
241 throw new MWException( "Invalid language code \"$code\"" );
242 }
243
244 if ( !self::isValidBuiltInCode( $code ) ) {
245 // It's not possible to customise this code with class files, so
246 // just return a Language object. This is to support uselang= hacks.
247 $lang = new Language;
248 $lang->setCode( $code );
249 return $lang;
250 }
251
252 // Check if there is a language class for the code
253 $class = self::classFromCode( $code, $fallback );
254 // LanguageCode does not inherit Language
255 if ( class_exists( $class ) && is_a( $class, 'Language', true ) ) {
256 $lang = new $class;
257 return $lang;
258 }
259
260 // Keep trying the fallback list until we find an existing class
261 $fallbacks = self::getFallbacksFor( $code );
262 foreach ( $fallbacks as $fallbackCode ) {
263 if ( !self::isValidBuiltInCode( $fallbackCode ) ) {
264 throw new MWException( "Invalid fallback '$fallbackCode' in fallback sequence for '$code'" );
265 }
266
267 $class = self::classFromCode( $fallbackCode );
268 if ( class_exists( $class ) ) {
269 $lang = new $class;
270 $lang->setCode( $code );
271 return $lang;
272 }
273 }
274
275 throw new MWException( "Invalid fallback sequence for language '$code'" );
276 }
277
283 public static function clearCaches() {
284 if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
285 throw new MWException( __METHOD__ . ' must not be used outside tests' );
286 }
287 self::$dataCache = null;
288 // Reinitialize $dataCache, since it's expected to always be available
289 self::getLocalisationCache();
290 self::$mLangObjCache = [];
291 self::$fallbackLanguageCache = [];
292 self::$grammarTransformations = null;
293 self::$languageNameCache = null;
294 }
295
304 public static function isSupportedLanguage( $code ) {
305 if ( !self::isValidBuiltInCode( $code ) ) {
306 return false;
307 }
308
309 if ( $code === 'qqq' ) {
310 return false;
311 }
312
313 return is_readable( self::getMessagesFileName( $code ) ) ||
314 is_readable( self::getJsonMessagesFileName( $code ) );
315 }
316
332 public static function isWellFormedLanguageTag( $code, $lenient = false ) {
333 $alpha = '[a-z]';
334 $digit = '[0-9]';
335 $alphanum = '[a-z0-9]';
336 $x = 'x'; # private use singleton
337 $singleton = '[a-wy-z]'; # other singleton
338 $s = $lenient ? '[-_]' : '-';
339
340 $language = "$alpha{2,8}|$alpha{2,3}$s$alpha{3}";
341 $script = "$alpha{4}"; # ISO 15924
342 $region = "(?:$alpha{2}|$digit{3})"; # ISO 3166-1 alpha-2 or UN M.49
343 $variant = "(?:$alphanum{5,8}|$digit$alphanum{3})";
344 $extension = "$singleton(?:$s$alphanum{2,8})+";
345 $privateUse = "$x(?:$s$alphanum{1,8})+";
346
347 # Define certain grandfathered codes, since otherwise the regex is pretty useless.
348 # Since these are limited, this is safe even later changes to the registry --
349 # the only oddity is that it might change the type of the tag, and thus
350 # the results from the capturing groups.
351 # https://www.iana.org/assignments/language-subtag-registry
352
353 $grandfathered = "en{$s}GB{$s}oed"
354 . "|i{$s}(?:ami|bnn|default|enochian|hak|klingon|lux|mingo|navajo|pwn|tao|tay|tsu)"
355 . "|no{$s}(?:bok|nyn)"
356 . "|sgn{$s}(?:BE{$s}(?:fr|nl)|CH{$s}de)"
357 . "|zh{$s}min{$s}nan";
358
359 $variantList = "$variant(?:$s$variant)*";
360 $extensionList = "$extension(?:$s$extension)*";
361
362 $langtag = "(?:($language)"
363 . "(?:$s$script)?"
364 . "(?:$s$region)?"
365 . "(?:$s$variantList)?"
366 . "(?:$s$extensionList)?"
367 . "(?:$s$privateUse)?)";
368
369 # The final breakdown, with capturing groups for each of these components
370 # The variants, extensions, grandfathered, and private-use may have interior '-'
371
372 $root = "^(?:$langtag|$privateUse|$grandfathered)$";
373
374 return (bool)preg_match( "/$root/", strtolower( $code ) );
375 }
376
386 public static function isValidCode( $code ) {
387 static $cache = [];
388 if ( !isset( $cache[$code] ) ) {
389 // People think language codes are html safe, so enforce it.
390 // Ideally we should only allow a-zA-Z0-9-
391 // but, .+ and other chars are often used for {{int:}} hacks
392 // see bugs T39564, T39587, T38938
393 $cache[$code] =
394 // Protect against path traversal
395 strcspn( $code, ":/\\\000&<>'\"" ) === strlen( $code )
397 }
398 return $cache[$code];
399 }
400
411 public static function isValidBuiltInCode( $code ) {
412 if ( !is_string( $code ) ) {
413 if ( is_object( $code ) ) {
414 $addmsg = " of class " . get_class( $code );
415 } else {
416 $addmsg = '';
417 }
418 $type = gettype( $code );
419 throw new MWException( __METHOD__ . " must be passed a string, $type given$addmsg" );
420 }
421
422 return (bool)preg_match( '/^[a-z0-9-]{2,}$/', $code );
423 }
424
433 public static function isKnownLanguageTag( $tag ) {
434 // Quick escape for invalid input to avoid exceptions down the line
435 // when code tries to process tags which are not valid at all.
436 if ( !self::isValidBuiltInCode( $tag ) ) {
437 return false;
438 }
439
440 if ( isset( MediaWiki\Languages\Data\Names::$names[$tag] )
441 || self::fetchLanguageName( $tag, $tag ) !== ''
442 ) {
443 return true;
444 }
445
446 return false;
447 }
448
454 public static function getLocalisationCache() {
455 if ( is_null( self::$dataCache ) ) {
457 $class = $wgLocalisationCacheConf['class'];
458 self::$dataCache = new $class( $wgLocalisationCacheConf );
459 }
460 return self::$dataCache;
461 }
462
463 function __construct() {
464 $this->mConverter = new FakeConverter( $this );
465 // Set the code to the name of the descendant
466 if ( static::class === 'Language' ) {
467 $this->mCode = 'en';
468 } else {
469 $this->mCode = str_replace( '_', '-', strtolower( substr( static::class, 8 ) ) );
470 }
471 self::getLocalisationCache();
472 }
473
477 function __destruct() {
478 foreach ( $this as $name => $value ) {
479 unset( $this->$name );
480 }
481 }
482
487 function initContLang() {
488 }
489
494 public function getFallbackLanguages() {
495 return self::getFallbacksFor( $this->mCode );
496 }
497
502 public function getBookstoreList() {
503 return self::$dataCache->getItem( $this->mCode, 'bookstoreList' );
504 }
505
512 public function getNamespaces() {
513 if ( is_null( $this->namespaceNames ) ) {
515
516 $validNamespaces = MWNamespace::getCanonicalNamespaces();
517
518 $this->namespaceNames = $wgExtraNamespaces +
519 self::$dataCache->getItem( $this->mCode, 'namespaceNames' );
520 $this->namespaceNames += $validNamespaces;
521
522 $this->namespaceNames[NS_PROJECT] = $wgMetaNamespace;
523 if ( $wgMetaNamespaceTalk ) {
524 $this->namespaceNames[NS_PROJECT_TALK] = $wgMetaNamespaceTalk;
525 } else {
526 $talk = $this->namespaceNames[NS_PROJECT_TALK];
527 $this->namespaceNames[NS_PROJECT_TALK] =
528 $this->fixVariableInNamespace( $talk );
529 }
530
531 # Sometimes a language will be localised but not actually exist on this wiki.
532 foreach ( $this->namespaceNames as $key => $text ) {
533 if ( !isset( $validNamespaces[$key] ) ) {
534 unset( $this->namespaceNames[$key] );
535 }
536 }
537
538 # The above mixing may leave namespaces out of canonical order.
539 # Re-order by namespace ID number...
540 ksort( $this->namespaceNames );
541
542 Hooks::run( 'LanguageGetNamespaces', [ &$this->namespaceNames ] );
543 }
544
546 }
547
552 public function setNamespaces( array $namespaces ) {
553 $this->namespaceNames = $namespaces;
554 $this->mNamespaceIds = null;
555 }
556
560 public function resetNamespaces() {
561 $this->namespaceNames = null;
562 $this->mNamespaceIds = null;
563 $this->namespaceAliases = null;
564 }
565
572 public function getFormattedNamespaces() {
573 $ns = $this->getNamespaces();
574 foreach ( $ns as $k => $v ) {
575 $ns[$k] = strtr( $v, '_', ' ' );
576 }
577 return $ns;
578 }
579
591 public function getNsText( $index ) {
592 $ns = $this->getNamespaces();
593 return $ns[$index] ?? false;
594 }
595
609 public function getFormattedNsText( $index ) {
610 $ns = $this->getNsText( $index );
611 return strtr( $ns, '_', ' ' );
612 }
613
622 public function getGenderNsText( $index, $gender ) {
624
626 (array)self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' );
627
628 return $ns[$index][$gender] ?? $this->getNsText( $index );
629 }
630
637 public function needsGenderDistinction() {
639 if ( count( $wgExtraGenderNamespaces ) > 0 ) {
640 // $wgExtraGenderNamespaces overrides everything
641 return true;
642 } elseif ( isset( $wgExtraNamespaces[NS_USER] ) && isset( $wgExtraNamespaces[NS_USER_TALK] ) ) {
644 // $wgExtraNamespaces overrides any gender aliases specified in i18n files
645 return false;
646 } else {
647 // Check what is in i18n files
648 $aliases = self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' );
649 return count( $aliases ) > 0;
650 }
651 }
652
661 function getLocalNsIndex( $text ) {
662 $lctext = $this->lc( $text );
663 $ids = $this->getNamespaceIds();
664 return $ids[$lctext] ?? false;
665 }
666
670 public function getNamespaceAliases() {
671 if ( is_null( $this->namespaceAliases ) ) {
672 $aliases = self::$dataCache->getItem( $this->mCode, 'namespaceAliases' );
673 if ( !$aliases ) {
674 $aliases = [];
675 } else {
676 foreach ( $aliases as $name => $index ) {
677 if ( $index === NS_PROJECT_TALK ) {
678 unset( $aliases[$name] );
680 $aliases[$name] = $index;
681 }
682 }
683 }
684
686 $genders = $wgExtraGenderNamespaces +
687 (array)self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' );
688 foreach ( $genders as $index => $forms ) {
689 foreach ( $forms as $alias ) {
690 $aliases[$alias] = $index;
691 }
692 }
693
694 # Also add converted namespace names as aliases, to avoid confusion.
695 $convertedNames = [];
696 foreach ( $this->getVariants() as $variant ) {
697 if ( $variant === $this->mCode ) {
698 continue;
699 }
700 foreach ( $this->getNamespaces() as $ns => $_ ) {
701 $convertedNames[$this->getConverter()->convertNamespace( $ns, $variant )] = $ns;
702 }
703 }
704
705 $this->namespaceAliases = $aliases + $convertedNames;
706
707 # Filter out aliases to namespaces that don't exist, e.g. from extensions
708 # that aren't loaded here but are included in the l10n cache.
709 # (array_intersect preserves keys from its first argument)
710 $this->namespaceAliases = array_intersect(
711 $this->namespaceAliases,
712 array_keys( $this->getNamespaces() )
713 );
714 }
715
717 }
718
722 public function getNamespaceIds() {
723 if ( is_null( $this->mNamespaceIds ) ) {
724 global $wgNamespaceAliases;
725 # Put namespace names and aliases into a hashtable.
726 # If this is too slow, then we should arrange it so that it is done
727 # before caching. The catch is that at pre-cache time, the above
728 # class-specific fixup hasn't been done.
729 $this->mNamespaceIds = [];
730 foreach ( $this->getNamespaces() as $index => $name ) {
731 $this->mNamespaceIds[$this->lc( $name )] = $index;
732 }
733 foreach ( $this->getNamespaceAliases() as $name => $index ) {
734 $this->mNamespaceIds[$this->lc( $name )] = $index;
735 }
736 if ( $wgNamespaceAliases ) {
737 foreach ( $wgNamespaceAliases as $name => $index ) {
738 $this->mNamespaceIds[$this->lc( $name )] = $index;
739 }
740 }
741 }
742 return $this->mNamespaceIds;
743 }
744
752 public function getNsIndex( $text ) {
753 $lctext = $this->lc( $text );
754 $ns = MWNamespace::getCanonicalIndex( $lctext );
755 if ( $ns !== null ) {
756 return $ns;
757 }
758 $ids = $this->getNamespaceIds();
759 return $ids[$lctext] ?? false;
760 }
761
769 public function getVariantname( $code, $usemsg = true ) {
770 $msg = "variantname-$code";
771 if ( $usemsg && wfMessage( $msg )->exists() ) {
772 return $this->getMessageFromDB( $msg );
773 }
774 $name = self::fetchLanguageName( $code );
775 if ( $name ) {
776 return $name; # if it's defined as a language name, show that
777 } else {
778 # otherwise, output the language code
779 return $code;
780 }
781 }
782
786 public function getDatePreferences() {
787 return self::$dataCache->getItem( $this->mCode, 'datePreferences' );
788 }
789
793 function getDateFormats() {
794 return self::$dataCache->getItem( $this->mCode, 'dateFormats' );
795 }
796
800 public function getDefaultDateFormat() {
801 $df = self::$dataCache->getItem( $this->mCode, 'defaultDateFormat' );
802 if ( $df === 'dmy or mdy' ) {
803 global $wgAmericanDates;
804 return $wgAmericanDates ? 'mdy' : 'dmy';
805 } else {
806 return $df;
807 }
808 }
809
813 public function getDatePreferenceMigrationMap() {
814 return self::$dataCache->getItem( $this->mCode, 'datePreferenceMigrationMap' );
815 }
816
820 public function getExtraUserToggles() {
821 return (array)self::$dataCache->getItem( $this->mCode, 'extraUserToggles' );
822 }
823
828 function getUserToggle( $tog ) {
829 return $this->getMessageFromDB( "tog-$tog" );
830 }
831
843 public static function fetchLanguageNames( $inLanguage = self::AS_AUTONYMS, $include = 'mw' ) {
844 $cacheKey = $inLanguage === self::AS_AUTONYMS ? 'null' : $inLanguage;
845 $cacheKey .= ":$include";
846 if ( self::$languageNameCache === null ) {
847 self::$languageNameCache = new HashBagOStuff( [ 'maxKeys' => 20 ] );
848 }
849
850 $ret = self::$languageNameCache->get( $cacheKey );
851 if ( !$ret ) {
852 $ret = self::fetchLanguageNamesUncached( $inLanguage, $include );
853 self::$languageNameCache->set( $cacheKey, $ret );
854 }
855 return $ret;
856 }
857
868 private static function fetchLanguageNamesUncached(
869 $inLanguage = self::AS_AUTONYMS,
870 $include = 'mw'
871 ) {
872 global $wgExtraLanguageNames, $wgUsePigLatinVariant;
873
874 // If passed an invalid language code to use, fallback to en
875 if ( $inLanguage !== self::AS_AUTONYMS && !self::isValidCode( $inLanguage ) ) {
876 $inLanguage = 'en';
877 }
878
879 $names = [];
880
881 if ( $inLanguage ) {
882 # TODO: also include when $inLanguage is null, when this code is more efficient
883 Hooks::run( 'LanguageGetTranslatedLanguageNames', [ &$names, $inLanguage ] );
884 }
885
886 $mwNames = $wgExtraLanguageNames + MediaWiki\Languages\Data\Names::$names;
887 if ( $wgUsePigLatinVariant ) {
888 // Pig Latin (for variant development)
889 $mwNames['en-x-piglatin'] = 'Igpay Atinlay';
890 }
891
892 foreach ( $mwNames as $mwCode => $mwName ) {
893 # - Prefer own MediaWiki native name when not using the hook
894 # - For other names just add if not added through the hook
895 if ( $mwCode === $inLanguage || !isset( $names[$mwCode] ) ) {
896 $names[$mwCode] = $mwName;
897 }
898 }
899
900 if ( $include === self::ALL ) {
901 ksort( $names );
902 return $names;
903 }
904
905 $returnMw = [];
906 $coreCodes = array_keys( $mwNames );
907 foreach ( $coreCodes as $coreCode ) {
908 $returnMw[$coreCode] = $names[$coreCode];
909 }
910
911 if ( $include === self::SUPPORTED ) {
912 $namesMwFile = [];
913 # We do this using a foreach over the codes instead of a directory
914 # loop so that messages files in extensions will work correctly.
915 foreach ( $returnMw as $code => $value ) {
916 if ( is_readable( self::getMessagesFileName( $code ) )
917 || is_readable( self::getJsonMessagesFileName( $code ) )
918 ) {
919 $namesMwFile[$code] = $names[$code];
920 }
921 }
922
923 ksort( $namesMwFile );
924 return $namesMwFile;
925 }
926
927 ksort( $returnMw );
928 # 'mw' option; default if it's not one of the other two options (all/mwfile)
929 return $returnMw;
930 }
931
940 public static function fetchLanguageName(
941 $code,
942 $inLanguage = self::AS_AUTONYMS,
943 $include = self::ALL
944 ) {
945 $code = strtolower( $code );
946 $array = self::fetchLanguageNames( $inLanguage, $include );
947 return !array_key_exists( $code, $array ) ? '' : $array[$code];
948 }
949
956 public function getMessageFromDB( $msg ) {
957 return $this->msg( $msg )->text();
958 }
959
966 protected function msg( $msg ) {
967 return wfMessage( $msg )->inLanguage( $this );
968 }
969
974 public function getMonthName( $key ) {
975 return $this->getMessageFromDB( self::$mMonthMsgs[$key - 1] );
976 }
977
981 public function getMonthNamesArray() {
982 $monthNames = [ '' ];
983 for ( $i = 1; $i < 13; $i++ ) {
984 $monthNames[] = $this->getMonthName( $i );
985 }
986 return $monthNames;
987 }
988
993 public function getMonthNameGen( $key ) {
994 return $this->getMessageFromDB( self::$mMonthGenMsgs[$key - 1] );
995 }
996
1001 public function getMonthAbbreviation( $key ) {
1002 return $this->getMessageFromDB( self::$mMonthAbbrevMsgs[$key - 1] );
1003 }
1004
1008 public function getMonthAbbreviationsArray() {
1009 $monthNames = [ '' ];
1010 for ( $i = 1; $i < 13; $i++ ) {
1011 $monthNames[] = $this->getMonthAbbreviation( $i );
1012 }
1013 return $monthNames;
1014 }
1015
1020 public function getWeekdayName( $key ) {
1021 return $this->getMessageFromDB( self::$mWeekdayMsgs[$key - 1] );
1022 }
1023
1028 function getWeekdayAbbreviation( $key ) {
1029 return $this->getMessageFromDB( self::$mWeekdayAbbrevMsgs[$key - 1] );
1030 }
1031
1037 return $this->getMessageFromDB( self::$mIranianCalendarMonthMsgs[$key - 1] );
1038 }
1039
1045 return $this->getMessageFromDB( self::$mHebrewCalendarMonthMsgs[$key - 1] );
1046 }
1047
1053 return $this->getMessageFromDB( self::$mHebrewCalendarMonthGenMsgs[$key - 1] );
1054 }
1055
1060 function getHijriCalendarMonthName( $key ) {
1061 return $this->getMessageFromDB( self::$mHijriCalendarMonthMsgs[$key - 1] );
1062 }
1063
1072 private static function dateTimeObjFormat( &$dateTimeObj, $ts, $zone, $code ) {
1073 if ( !$dateTimeObj ) {
1074 $dateTimeObj = DateTime::createFromFormat(
1075 'YmdHis', $ts, $zone ?: new DateTimeZone( 'UTC' )
1076 );
1077 }
1078 return $dateTimeObj->format( $code );
1079 }
1080
1150 public function sprintfDate( $format, $ts, DateTimeZone $zone = null, &$ttl = 'unused' ) {
1151 $s = '';
1152 $raw = false;
1153 $roman = false;
1154 $hebrewNum = false;
1155 $dateTimeObj = false;
1156 $rawToggle = false;
1157 $iranian = false;
1158 $hebrew = false;
1159 $hijri = false;
1160 $thai = false;
1161 $minguo = false;
1162 $tenno = false;
1163
1164 $usedSecond = false;
1165 $usedMinute = false;
1166 $usedHour = false;
1167 $usedAMPM = false;
1168 $usedDay = false;
1169 $usedWeek = false;
1170 $usedMonth = false;
1171 $usedYear = false;
1172 $usedISOYear = false;
1173 $usedIsLeapYear = false;
1174
1175 $usedHebrewMonth = false;
1176 $usedIranianMonth = false;
1177 $usedHijriMonth = false;
1178 $usedHebrewYear = false;
1179 $usedIranianYear = false;
1180 $usedHijriYear = false;
1181 $usedTennoYear = false;
1182
1183 if ( strlen( $ts ) !== 14 ) {
1184 throw new MWException( __METHOD__ . ": The timestamp $ts should have 14 characters" );
1185 }
1186
1187 if ( !ctype_digit( $ts ) ) {
1188 throw new MWException( __METHOD__ . ": The timestamp $ts should be a number" );
1189 }
1190
1191 $formatLength = strlen( $format );
1192 for ( $p = 0; $p < $formatLength; $p++ ) {
1193 $num = false;
1194 $code = $format[$p];
1195 if ( $code == 'x' && $p < $formatLength - 1 ) {
1196 $code .= $format[++$p];
1197 }
1198
1199 if ( ( $code === 'xi'
1200 || $code === 'xj'
1201 || $code === 'xk'
1202 || $code === 'xm'
1203 || $code === 'xo'
1204 || $code === 'xt' )
1205 && $p < $formatLength - 1 ) {
1206 $code .= $format[++$p];
1207 }
1208
1209 switch ( $code ) {
1210 case 'xx':
1211 $s .= 'x';
1212 break;
1213 case 'xn':
1214 $raw = true;
1215 break;
1216 case 'xN':
1217 $rawToggle = !$rawToggle;
1218 break;
1219 case 'xr':
1220 $roman = true;
1221 break;
1222 case 'xh':
1223 $hebrewNum = true;
1224 break;
1225 case 'xg':
1226 $usedMonth = true;
1227 $s .= $this->getMonthNameGen( substr( $ts, 4, 2 ) );
1228 break;
1229 case 'xjx':
1230 $usedHebrewMonth = true;
1231 if ( !$hebrew ) {
1232 $hebrew = self::tsToHebrew( $ts );
1233 }
1234 $s .= $this->getHebrewCalendarMonthNameGen( $hebrew[1] );
1235 break;
1236 case 'd':
1237 $usedDay = true;
1238 $num = substr( $ts, 6, 2 );
1239 break;
1240 case 'D':
1241 $usedDay = true;
1242 $s .= $this->getWeekdayAbbreviation(
1243 self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'w' ) + 1
1244 );
1245 break;
1246 case 'j':
1247 $usedDay = true;
1248 $num = intval( substr( $ts, 6, 2 ) );
1249 break;
1250 case 'xij':
1251 $usedDay = true;
1252 if ( !$iranian ) {
1253 $iranian = self::tsToIranian( $ts );
1254 }
1255 $num = $iranian[2];
1256 break;
1257 case 'xmj':
1258 $usedDay = true;
1259 if ( !$hijri ) {
1260 $hijri = self::tsToHijri( $ts );
1261 }
1262 $num = $hijri[2];
1263 break;
1264 case 'xjj':
1265 $usedDay = true;
1266 if ( !$hebrew ) {
1267 $hebrew = self::tsToHebrew( $ts );
1268 }
1269 $num = $hebrew[2];
1270 break;
1271 case 'l':
1272 $usedDay = true;
1273 $s .= $this->getWeekdayName(
1274 self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'w' ) + 1
1275 );
1276 break;
1277 case 'F':
1278 $usedMonth = true;
1279 $s .= $this->getMonthName( substr( $ts, 4, 2 ) );
1280 break;
1281 case 'xiF':
1282 $usedIranianMonth = true;
1283 if ( !$iranian ) {
1284 $iranian = self::tsToIranian( $ts );
1285 }
1286 $s .= $this->getIranianCalendarMonthName( $iranian[1] );
1287 break;
1288 case 'xmF':
1289 $usedHijriMonth = true;
1290 if ( !$hijri ) {
1291 $hijri = self::tsToHijri( $ts );
1292 }
1293 $s .= $this->getHijriCalendarMonthName( $hijri[1] );
1294 break;
1295 case 'xjF':
1296 $usedHebrewMonth = true;
1297 if ( !$hebrew ) {
1298 $hebrew = self::tsToHebrew( $ts );
1299 }
1300 $s .= $this->getHebrewCalendarMonthName( $hebrew[1] );
1301 break;
1302 case 'm':
1303 $usedMonth = true;
1304 $num = substr( $ts, 4, 2 );
1305 break;
1306 case 'M':
1307 $usedMonth = true;
1308 $s .= $this->getMonthAbbreviation( substr( $ts, 4, 2 ) );
1309 break;
1310 case 'n':
1311 $usedMonth = true;
1312 $num = intval( substr( $ts, 4, 2 ) );
1313 break;
1314 case 'xin':
1315 $usedIranianMonth = true;
1316 if ( !$iranian ) {
1317 $iranian = self::tsToIranian( $ts );
1318 }
1319 $num = $iranian[1];
1320 break;
1321 case 'xmn':
1322 $usedHijriMonth = true;
1323 if ( !$hijri ) {
1324 $hijri = self::tsToHijri( $ts );
1325 }
1326 $num = $hijri[1];
1327 break;
1328 case 'xjn':
1329 $usedHebrewMonth = true;
1330 if ( !$hebrew ) {
1331 $hebrew = self::tsToHebrew( $ts );
1332 }
1333 $num = $hebrew[1];
1334 break;
1335 case 'xjt':
1336 $usedHebrewMonth = true;
1337 if ( !$hebrew ) {
1338 $hebrew = self::tsToHebrew( $ts );
1339 }
1340 $num = $hebrew[3];
1341 break;
1342 case 'Y':
1343 $usedYear = true;
1344 $num = substr( $ts, 0, 4 );
1345 break;
1346 case 'xiY':
1347 $usedIranianYear = true;
1348 if ( !$iranian ) {
1349 $iranian = self::tsToIranian( $ts );
1350 }
1351 $num = $iranian[0];
1352 break;
1353 case 'xmY':
1354 $usedHijriYear = true;
1355 if ( !$hijri ) {
1356 $hijri = self::tsToHijri( $ts );
1357 }
1358 $num = $hijri[0];
1359 break;
1360 case 'xjY':
1361 $usedHebrewYear = true;
1362 if ( !$hebrew ) {
1363 $hebrew = self::tsToHebrew( $ts );
1364 }
1365 $num = $hebrew[0];
1366 break;
1367 case 'xkY':
1368 $usedYear = true;
1369 if ( !$thai ) {
1370 $thai = self::tsToYear( $ts, 'thai' );
1371 }
1372 $num = $thai[0];
1373 break;
1374 case 'xoY':
1375 $usedYear = true;
1376 if ( !$minguo ) {
1377 $minguo = self::tsToYear( $ts, 'minguo' );
1378 }
1379 $num = $minguo[0];
1380 break;
1381 case 'xtY':
1382 $usedTennoYear = true;
1383 if ( !$tenno ) {
1384 $tenno = self::tsToYear( $ts, 'tenno' );
1385 }
1386 $num = $tenno[0];
1387 break;
1388 case 'y':
1389 $usedYear = true;
1390 $num = substr( $ts, 2, 2 );
1391 break;
1392 case 'xiy':
1393 $usedIranianYear = true;
1394 if ( !$iranian ) {
1395 $iranian = self::tsToIranian( $ts );
1396 }
1397 $num = substr( $iranian[0], -2 );
1398 break;
1399 case 'xit':
1400 $usedIranianYear = true;
1401 if ( !$iranian ) {
1402 $iranian = self::tsToIranian( $ts );
1403 }
1404 $num = self::$IRANIAN_DAYS[$iranian[1] - 1];
1405 break;
1406 case 'xiz':
1407 $usedIranianYear = true;
1408 if ( !$iranian ) {
1409 $iranian = self::tsToIranian( $ts );
1410 }
1411 $num = $iranian[3];
1412 break;
1413 case 'a':
1414 $usedAMPM = true;
1415 $s .= intval( substr( $ts, 8, 2 ) ) < 12 ? 'am' : 'pm';
1416 break;
1417 case 'A':
1418 $usedAMPM = true;
1419 $s .= intval( substr( $ts, 8, 2 ) ) < 12 ? 'AM' : 'PM';
1420 break;
1421 case 'g':
1422 $usedHour = true;
1423 $h = substr( $ts, 8, 2 );
1424 $num = $h % 12 ? $h % 12 : 12;
1425 break;
1426 case 'G':
1427 $usedHour = true;
1428 $num = intval( substr( $ts, 8, 2 ) );
1429 break;
1430 case 'h':
1431 $usedHour = true;
1432 $h = substr( $ts, 8, 2 );
1433 $num = sprintf( '%02d', $h % 12 ? $h % 12 : 12 );
1434 break;
1435 case 'H':
1436 $usedHour = true;
1437 $num = substr( $ts, 8, 2 );
1438 break;
1439 case 'i':
1440 $usedMinute = true;
1441 $num = substr( $ts, 10, 2 );
1442 break;
1443 case 's':
1444 $usedSecond = true;
1445 $num = substr( $ts, 12, 2 );
1446 break;
1447 case 'c':
1448 case 'r':
1449 $usedSecond = true;
1450 // fall through
1451 case 'e':
1452 case 'O':
1453 case 'P':
1454 case 'T':
1455 $s .= self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1456 break;
1457 case 'w':
1458 case 'N':
1459 case 'z':
1460 $usedDay = true;
1461 $num = self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1462 break;
1463 case 'W':
1464 $usedWeek = true;
1465 $num = self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1466 break;
1467 case 't':
1468 $usedMonth = true;
1469 $num = self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1470 break;
1471 case 'L':
1472 $usedIsLeapYear = true;
1473 $num = self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1474 break;
1475 case 'o':
1476 $usedISOYear = true;
1477 $num = self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1478 break;
1479 case 'U':
1480 $usedSecond = true;
1481 // fall through
1482 case 'I':
1483 case 'Z':
1484 $num = self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1485 break;
1486 case '\\':
1487 # Backslash escaping
1488 if ( $p < $formatLength - 1 ) {
1489 $s .= $format[++$p];
1490 } else {
1491 $s .= '\\';
1492 }
1493 break;
1494 case '"':
1495 # Quoted literal
1496 if ( $p < $formatLength - 1 ) {
1497 $endQuote = strpos( $format, '"', $p + 1 );
1498 if ( $endQuote === false ) {
1499 # No terminating quote, assume literal "
1500 $s .= '"';
1501 } else {
1502 $s .= substr( $format, $p + 1, $endQuote - $p - 1 );
1503 $p = $endQuote;
1504 }
1505 } else {
1506 # Quote at end of string, assume literal "
1507 $s .= '"';
1508 }
1509 break;
1510 default:
1511 $s .= $format[$p];
1512 }
1513 if ( $num !== false ) {
1514 if ( $rawToggle || $raw ) {
1515 $s .= $num;
1516 $raw = false;
1517 } elseif ( $roman ) {
1518 $s .= self::romanNumeral( $num );
1519 $roman = false;
1520 } elseif ( $hebrewNum ) {
1521 $s .= self::hebrewNumeral( $num );
1522 $hebrewNum = false;
1523 } else {
1524 $s .= $this->formatNum( $num, true );
1525 }
1526 }
1527 }
1528
1529 if ( $ttl === 'unused' ) {
1530 // No need to calculate the TTL, the caller wont use it anyway.
1531 } elseif ( $usedSecond ) {
1532 $ttl = 1;
1533 } elseif ( $usedMinute ) {
1534 $ttl = 60 - substr( $ts, 12, 2 );
1535 } elseif ( $usedHour ) {
1536 $ttl = 3600 - substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
1537 } elseif ( $usedAMPM ) {
1538 $ttl = 43200 - ( substr( $ts, 8, 2 ) % 12 ) * 3600 -
1539 substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
1540 } elseif (
1541 $usedDay ||
1542 $usedHebrewMonth ||
1543 $usedIranianMonth ||
1544 $usedHijriMonth ||
1545 $usedHebrewYear ||
1546 $usedIranianYear ||
1547 $usedHijriYear ||
1548 $usedTennoYear
1549 ) {
1550 // @todo Someone who understands the non-Gregorian calendars
1551 // should write proper logic for them so that they don't need purged every day.
1552 $ttl = 86400 - substr( $ts, 8, 2 ) * 3600 -
1553 substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
1554 } else {
1555 $possibleTtls = [];
1556 $timeRemainingInDay = 86400 - substr( $ts, 8, 2 ) * 3600 -
1557 substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
1558 if ( $usedWeek ) {
1559 $possibleTtls[] =
1560 ( 7 - self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'N' ) ) * 86400 +
1561 $timeRemainingInDay;
1562 } elseif ( $usedISOYear ) {
1563 // December 28th falls on the last ISO week of the year, every year.
1564 // The last ISO week of a year can be 52 or 53.
1565 $lastWeekOfISOYear = DateTime::createFromFormat(
1566 'Ymd',
1567 substr( $ts, 0, 4 ) . '1228',
1568 $zone ?: new DateTimeZone( 'UTC' )
1569 )->format( 'W' );
1570 $currentISOWeek = self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'W' );
1571 $weeksRemaining = $lastWeekOfISOYear - $currentISOWeek;
1572 $timeRemainingInWeek =
1573 ( 7 - self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'N' ) ) * 86400
1574 + $timeRemainingInDay;
1575 $possibleTtls[] = $weeksRemaining * 604800 + $timeRemainingInWeek;
1576 }
1577
1578 if ( $usedMonth ) {
1579 $possibleTtls[] =
1580 ( self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 't' ) -
1581 substr( $ts, 6, 2 ) ) * 86400
1582 + $timeRemainingInDay;
1583 } elseif ( $usedYear ) {
1584 $possibleTtls[] =
1585 ( self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'L' ) + 364 -
1586 self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'z' ) ) * 86400
1587 + $timeRemainingInDay;
1588 } elseif ( $usedIsLeapYear ) {
1589 $year = substr( $ts, 0, 4 );
1590 $timeRemainingInYear =
1591 ( self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'L' ) + 364 -
1592 self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'z' ) ) * 86400
1593 + $timeRemainingInDay;
1594 $mod = $year % 4;
1595 if ( $mod || ( !( $year % 100 ) && $year % 400 ) ) {
1596 // this isn't a leap year. see when the next one starts
1597 $nextCandidate = $year - $mod + 4;
1598 if ( $nextCandidate % 100 || !( $nextCandidate % 400 ) ) {
1599 $possibleTtls[] = ( $nextCandidate - $year - 1 ) * 365 * 86400 +
1600 $timeRemainingInYear;
1601 } else {
1602 $possibleTtls[] = ( $nextCandidate - $year + 3 ) * 365 * 86400 +
1603 $timeRemainingInYear;
1604 }
1605 } else {
1606 // this is a leap year, so the next year isn't
1607 $possibleTtls[] = $timeRemainingInYear;
1608 }
1609 }
1610
1611 if ( $possibleTtls ) {
1612 $ttl = min( $possibleTtls );
1613 }
1614 }
1615
1616 return $s;
1617 }
1618
1619 private static $GREG_DAYS = [ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ];
1620 private static $IRANIAN_DAYS = [ 31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29 ];
1621
1634 private static function tsToIranian( $ts ) {
1635 $gy = substr( $ts, 0, 4 ) - 1600;
1636 $gm = substr( $ts, 4, 2 ) - 1;
1637 $gd = substr( $ts, 6, 2 ) - 1;
1638
1639 # Days passed from the beginning (including leap years)
1640 $gDayNo = 365 * $gy
1641 + floor( ( $gy + 3 ) / 4 )
1642 - floor( ( $gy + 99 ) / 100 )
1643 + floor( ( $gy + 399 ) / 400 );
1644
1645 // Add days of the past months of this year
1646 for ( $i = 0; $i < $gm; $i++ ) {
1647 $gDayNo += self::$GREG_DAYS[$i];
1648 }
1649
1650 // Leap years
1651 if ( $gm > 1 && ( ( $gy % 4 === 0 && $gy % 100 !== 0 || ( $gy % 400 == 0 ) ) ) ) {
1652 $gDayNo++;
1653 }
1654
1655 // Days passed in current month
1656 $gDayNo += (int)$gd;
1657
1658 $jDayNo = $gDayNo - 79;
1659
1660 $jNp = floor( $jDayNo / 12053 );
1661 $jDayNo %= 12053;
1662
1663 $jy = 979 + 33 * $jNp + 4 * floor( $jDayNo / 1461 );
1664 $jDayNo %= 1461;
1665
1666 if ( $jDayNo >= 366 ) {
1667 $jy += floor( ( $jDayNo - 1 ) / 365 );
1668 $jDayNo = floor( ( $jDayNo - 1 ) % 365 );
1669 }
1670
1671 $jz = $jDayNo;
1672
1673 for ( $i = 0; $i < 11 && $jDayNo >= self::$IRANIAN_DAYS[$i]; $i++ ) {
1674 $jDayNo -= self::$IRANIAN_DAYS[$i];
1675 }
1676
1677 $jm = $i + 1;
1678 $jd = $jDayNo + 1;
1679
1680 return [ $jy, $jm, $jd, $jz ];
1681 }
1682
1694 private static function tsToHijri( $ts ) {
1695 $year = substr( $ts, 0, 4 );
1696 $month = substr( $ts, 4, 2 );
1697 $day = substr( $ts, 6, 2 );
1698
1699 $zyr = $year;
1700 $zd = $day;
1701 $zm = $month;
1702 $zy = $zyr;
1703
1704 if (
1705 ( $zy > 1582 ) || ( ( $zy == 1582 ) && ( $zm > 10 ) ) ||
1706 ( ( $zy == 1582 ) && ( $zm == 10 ) && ( $zd > 14 ) )
1707 ) {
1708 $zjd = (int)( ( 1461 * ( $zy + 4800 + (int)( ( $zm - 14 ) / 12 ) ) ) / 4 ) +
1709 (int)( ( 367 * ( $zm - 2 - 12 * ( (int)( ( $zm - 14 ) / 12 ) ) ) ) / 12 ) -
1710 (int)( ( 3 * (int)( ( ( $zy + 4900 + (int)( ( $zm - 14 ) / 12 ) ) / 100 ) ) ) / 4 ) +
1711 $zd - 32075;
1712 } else {
1713 $zjd = 367 * $zy - (int)( ( 7 * ( $zy + 5001 + (int)( ( $zm - 9 ) / 7 ) ) ) / 4 ) +
1714 (int)( ( 275 * $zm ) / 9 ) + $zd + 1729777;
1715 }
1716
1717 $zl = $zjd - 1948440 + 10632;
1718 $zn = (int)( ( $zl - 1 ) / 10631 );
1719 $zl = $zl - 10631 * $zn + 354;
1720 $zj = ( (int)( ( 10985 - $zl ) / 5316 ) ) * ( (int)( ( 50 * $zl ) / 17719 ) ) +
1721 ( (int)( $zl / 5670 ) ) * ( (int)( ( 43 * $zl ) / 15238 ) );
1722 $zl = $zl - ( (int)( ( 30 - $zj ) / 15 ) ) * ( (int)( ( 17719 * $zj ) / 50 ) ) -
1723 ( (int)( $zj / 16 ) ) * ( (int)( ( 15238 * $zj ) / 43 ) ) + 29;
1724 $zm = (int)( ( 24 * $zl ) / 709 );
1725 $zd = $zl - (int)( ( 709 * $zm ) / 24 );
1726 $zy = 30 * $zn + $zj - 30;
1727
1728 return [ $zy, $zm, $zd ];
1729 }
1730
1746 private static function tsToHebrew( $ts ) {
1747 # Parse date
1748 $year = substr( $ts, 0, 4 );
1749 $month = substr( $ts, 4, 2 );
1750 $day = substr( $ts, 6, 2 );
1751
1752 # Calculate Hebrew year
1753 $hebrewYear = $year + 3760;
1754
1755 # Month number when September = 1, August = 12
1756 $month += 4;
1757 if ( $month > 12 ) {
1758 # Next year
1759 $month -= 12;
1760 $year++;
1761 $hebrewYear++;
1762 }
1763
1764 # Calculate day of year from 1 September
1765 $dayOfYear = $day;
1766 for ( $i = 1; $i < $month; $i++ ) {
1767 if ( $i == 6 ) {
1768 # February
1769 $dayOfYear += 28;
1770 # Check if the year is leap
1771 if ( $year % 400 == 0 || ( $year % 4 == 0 && $year % 100 > 0 ) ) {
1772 $dayOfYear++;
1773 }
1774 } elseif ( $i == 8 || $i == 10 || $i == 1 || $i == 3 ) {
1775 $dayOfYear += 30;
1776 } else {
1777 $dayOfYear += 31;
1778 }
1779 }
1780
1781 # Calculate the start of the Hebrew year
1782 $start = self::hebrewYearStart( $hebrewYear );
1783
1784 # Calculate next year's start
1785 if ( $dayOfYear <= $start ) {
1786 # Day is before the start of the year - it is the previous year
1787 # Next year's start
1788 $nextStart = $start;
1789 # Previous year
1790 $year--;
1791 $hebrewYear--;
1792 # Add days since previous year's 1 September
1793 $dayOfYear += 365;
1794 if ( ( $year % 400 == 0 ) || ( $year % 100 != 0 && $year % 4 == 0 ) ) {
1795 # Leap year
1796 $dayOfYear++;
1797 }
1798 # Start of the new (previous) year
1799 $start = self::hebrewYearStart( $hebrewYear );
1800 } else {
1801 # Next year's start
1802 $nextStart = self::hebrewYearStart( $hebrewYear + 1 );
1803 }
1804
1805 # Calculate Hebrew day of year
1806 $hebrewDayOfYear = $dayOfYear - $start;
1807
1808 # Difference between year's days
1809 $diff = $nextStart - $start;
1810 # Add 12 (or 13 for leap years) days to ignore the difference between
1811 # Hebrew and Gregorian year (353 at least vs. 365/6) - now the
1812 # difference is only about the year type
1813 if ( ( $year % 400 == 0 ) || ( $year % 100 != 0 && $year % 4 == 0 ) ) {
1814 $diff += 13;
1815 } else {
1816 $diff += 12;
1817 }
1818
1819 # Check the year pattern, and is leap year
1820 # 0 means an incomplete year, 1 means a regular year, 2 means a complete year
1821 # This is mod 30, to work on both leap years (which add 30 days of Adar I)
1822 # and non-leap years
1823 $yearPattern = $diff % 30;
1824 # Check if leap year
1825 $isLeap = $diff >= 30;
1826
1827 # Calculate day in the month from number of day in the Hebrew year
1828 # Don't check Adar - if the day is not in Adar, we will stop before;
1829 # if it is in Adar, we will use it to check if it is Adar I or Adar II
1830 $hebrewDay = $hebrewDayOfYear;
1831 $hebrewMonth = 1;
1832 $days = 0;
1833 while ( $hebrewMonth <= 12 ) {
1834 # Calculate days in this month
1835 if ( $isLeap && $hebrewMonth == 6 ) {
1836 # Adar in a leap year
1837 if ( $isLeap ) {
1838 # Leap year - has Adar I, with 30 days, and Adar II, with 29 days
1839 $days = 30;
1840 if ( $hebrewDay <= $days ) {
1841 # Day in Adar I
1842 $hebrewMonth = 13;
1843 } else {
1844 # Subtract the days of Adar I
1845 $hebrewDay -= $days;
1846 # Try Adar II
1847 $days = 29;
1848 if ( $hebrewDay <= $days ) {
1849 # Day in Adar II
1850 $hebrewMonth = 14;
1851 }
1852 }
1853 }
1854 } elseif ( $hebrewMonth == 2 && $yearPattern == 2 ) {
1855 # Cheshvan in a complete year (otherwise as the rule below)
1856 $days = 30;
1857 } elseif ( $hebrewMonth == 3 && $yearPattern == 0 ) {
1858 # Kislev in an incomplete year (otherwise as the rule below)
1859 $days = 29;
1860 } else {
1861 # Odd months have 30 days, even have 29
1862 $days = 30 - ( $hebrewMonth - 1 ) % 2;
1863 }
1864 if ( $hebrewDay <= $days ) {
1865 # In the current month
1866 break;
1867 } else {
1868 # Subtract the days of the current month
1869 $hebrewDay -= $days;
1870 # Try in the next month
1871 $hebrewMonth++;
1872 }
1873 }
1874
1875 return [ $hebrewYear, $hebrewMonth, $hebrewDay, $days ];
1876 }
1877
1887 private static function hebrewYearStart( $year ) {
1888 $a = intval( ( 12 * ( $year - 1 ) + 17 ) % 19 );
1889 $b = intval( ( $year - 1 ) % 4 );
1890 $m = 32.044093161144 + 1.5542417966212 * $a + $b / 4.0 - 0.0031777940220923 * ( $year - 1 );
1891 if ( $m < 0 ) {
1892 $m--;
1893 }
1894 $Mar = intval( $m );
1895 if ( $m < 0 ) {
1896 $m++;
1897 }
1898 $m -= $Mar;
1899
1900 $c = intval( ( $Mar + 3 * ( $year - 1 ) + 5 * $b + 5 ) % 7 );
1901 if ( $c == 0 && $a > 11 && $m >= 0.89772376543210 ) {
1902 $Mar++;
1903 } elseif ( $c == 1 && $a > 6 && $m >= 0.63287037037037 ) {
1904 $Mar += 2;
1905 } elseif ( $c == 2 || $c == 4 || $c == 6 ) {
1906 $Mar++;
1907 }
1908
1909 $Mar += intval( ( $year - 3761 ) / 100 ) - intval( ( $year - 3761 ) / 400 ) - 24;
1910 return $Mar;
1911 }
1912
1925 private static function tsToYear( $ts, $cName ) {
1926 $gy = substr( $ts, 0, 4 );
1927 $gm = substr( $ts, 4, 2 );
1928 $gd = substr( $ts, 6, 2 );
1929
1930 if ( !strcmp( $cName, 'thai' ) ) {
1931 # Thai solar dates
1932 # Add 543 years to the Gregorian calendar
1933 # Months and days are identical
1934 $gy_offset = $gy + 543;
1935 # fix for dates between 1912 and 1941
1936 # https://en.wikipedia.org/?oldid=836596673#New_year
1937 if ( $gy >= 1912 && $gy <= 1940 ) {
1938 if ( $gm <= 3 ) {
1939 $gy_offset--;
1940 }
1941 $gm = ( $gm - 3 ) % 12;
1942 }
1943 } elseif ( ( !strcmp( $cName, 'minguo' ) ) || !strcmp( $cName, 'juche' ) ) {
1944 # Minguo dates
1945 # Deduct 1911 years from the Gregorian calendar
1946 # Months and days are identical
1947 $gy_offset = $gy - 1911;
1948 } elseif ( !strcmp( $cName, 'tenno' ) ) {
1949 # Nengō dates up to Meiji period
1950 # Deduct years from the Gregorian calendar
1951 # depending on the nengo periods
1952 # Months and days are identical
1953 if ( ( $gy < 1912 )
1954 || ( ( $gy == 1912 ) && ( $gm < 7 ) )
1955 || ( ( $gy == 1912 ) && ( $gm == 7 ) && ( $gd < 31 ) )
1956 ) {
1957 # Meiji period
1958 $gy_gannen = $gy - 1868 + 1;
1959 $gy_offset = $gy_gannen;
1960 if ( $gy_gannen == 1 ) {
1961 $gy_offset = '元';
1962 }
1963 $gy_offset = '明治' . $gy_offset;
1964 } elseif (
1965 ( ( $gy == 1912 ) && ( $gm == 7 ) && ( $gd == 31 ) ) ||
1966 ( ( $gy == 1912 ) && ( $gm >= 8 ) ) ||
1967 ( ( $gy > 1912 ) && ( $gy < 1926 ) ) ||
1968 ( ( $gy == 1926 ) && ( $gm < 12 ) ) ||
1969 ( ( $gy == 1926 ) && ( $gm == 12 ) && ( $gd < 26 ) )
1970 ) {
1971 # Taishō period
1972 $gy_gannen = $gy - 1912 + 1;
1973 $gy_offset = $gy_gannen;
1974 if ( $gy_gannen == 1 ) {
1975 $gy_offset = '元';
1976 }
1977 $gy_offset = '大正' . $gy_offset;
1978 } elseif (
1979 ( ( $gy == 1926 ) && ( $gm == 12 ) && ( $gd >= 26 ) ) ||
1980 ( ( $gy > 1926 ) && ( $gy < 1989 ) ) ||
1981 ( ( $gy == 1989 ) && ( $gm == 1 ) && ( $gd < 8 ) )
1982 ) {
1983 # Shōwa period
1984 $gy_gannen = $gy - 1926 + 1;
1985 $gy_offset = $gy_gannen;
1986 if ( $gy_gannen == 1 ) {
1987 $gy_offset = '元';
1988 }
1989 $gy_offset = '昭和' . $gy_offset;
1990 } elseif (
1991 ( ( $gy == 1989 ) && ( $gm == 1 ) && ( $gd >= 8 ) ) ||
1992 ( ( $gy > 1989 ) && ( $gy < 2019 ) ) ||
1993 ( ( $gy == 2019 ) && ( $gm < 5 ) )
1994 ) {
1995 # Heisei period
1996 $gy_gannen = $gy - 1989 + 1;
1997 $gy_offset = $gy_gannen;
1998 if ( $gy_gannen == 1 ) {
1999 $gy_offset = '元';
2000 }
2001 $gy_offset = '平成' . $gy_offset;
2002 } else {
2003 # Reiwa period
2004 $gy_gannen = $gy - 2019 + 1;
2005 $gy_offset = $gy_gannen;
2006 if ( $gy_gannen == 1 ) {
2007 $gy_offset = '元';
2008 }
2009 $gy_offset = '令和' . $gy_offset;
2010 }
2011 } else {
2012 $gy_offset = $gy;
2013 }
2014
2015 return [ $gy_offset, $gm, $gd ];
2016 }
2017
2031 private static function strongDirFromContent( $text = '' ) {
2032 if ( !preg_match( self::$strongDirRegex, $text, $matches ) ) {
2033 return null;
2034 }
2035 if ( $matches[1] === '' ) {
2036 return 'rtl';
2037 }
2038 return 'ltr';
2039 }
2040
2048 static function romanNumeral( $num ) {
2049 static $table = [
2050 [ '', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X' ],
2051 [ '', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC', 'C' ],
2052 [ '', 'C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM', 'M' ],
2053 [ '', 'M', 'MM', 'MMM', 'MMMM', 'MMMMM', 'MMMMMM', 'MMMMMMM',
2054 'MMMMMMMM', 'MMMMMMMMM', 'MMMMMMMMMM' ]
2055 ];
2056
2057 $num = intval( $num );
2058 if ( $num > 10000 || $num <= 0 ) {
2059 return $num;
2060 }
2061
2062 $s = '';
2063 for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) {
2064 if ( $num >= $pow10 ) {
2065 $s .= $table[$i][(int)floor( $num / $pow10 )];
2066 }
2067 $num = $num % $pow10;
2068 }
2069 return $s;
2070 }
2071
2079 static function hebrewNumeral( $num ) {
2080 static $table = [
2081 [ '', 'א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ז', 'ח', 'ט', 'י' ],
2082 [ '', 'י', 'כ', 'ל', 'מ', 'נ', 'ס', 'ע', 'פ', 'צ', 'ק' ],
2083 [ '',
2084 [ 'ק' ],
2085 [ 'ר' ],
2086 [ 'ש' ],
2087 [ 'ת' ],
2088 [ 'ת', 'ק' ],
2089 [ 'ת', 'ר' ],
2090 [ 'ת', 'ש' ],
2091 [ 'ת', 'ת' ],
2092 [ 'ת', 'ת', 'ק' ],
2093 [ 'ת', 'ת', 'ר' ],
2094 ],
2095 [ '', 'א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ז', 'ח', 'ט', 'י' ]
2096 ];
2097
2098 $num = intval( $num );
2099 if ( $num > 9999 || $num <= 0 ) {
2100 return $num;
2101 }
2102
2103 // Round thousands have special notations
2104 if ( $num === 1000 ) {
2105 return "א' אלף";
2106 } elseif ( $num % 1000 === 0 ) {
2107 return $table[0][$num / 1000] . "' אלפים";
2108 }
2109
2110 $letters = [];
2111
2112 for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) {
2113 if ( $num >= $pow10 ) {
2114 if ( $num === 15 || $num === 16 ) {
2115 $letters[] = $table[0][9];
2116 $letters[] = $table[0][$num - 9];
2117 $num = 0;
2118 } else {
2119 $letters = array_merge(
2120 $letters,
2121 (array)$table[$i][intval( $num / $pow10 )]
2122 );
2123
2124 if ( $pow10 === 1000 ) {
2125 $letters[] = "'";
2126 }
2127 }
2128 }
2129
2130 $num = $num % $pow10;
2131 }
2132
2133 $preTransformLength = count( $letters );
2134 if ( $preTransformLength === 1 ) {
2135 // Add geresh (single quote) to one-letter numbers
2136 $letters[] = "'";
2137 } else {
2138 $lastIndex = $preTransformLength - 1;
2139 $letters[$lastIndex] = str_replace(
2140 [ 'כ', 'מ', 'נ', 'פ', 'צ' ],
2141 [ 'ך', 'ם', 'ן', 'ף', 'ץ' ],
2142 $letters[$lastIndex]
2143 );
2144
2145 // Add gershayim (double quote) to multiple-letter numbers,
2146 // but exclude numbers with only one letter after the thousands
2147 // (1001-1009, 1020, 1030, 2001-2009, etc.)
2148 if ( $letters[1] === "'" && $preTransformLength === 3 ) {
2149 $letters[] = "'";
2150 } else {
2151 array_splice( $letters, -1, 0, '"' );
2152 }
2153 }
2154
2155 return implode( $letters );
2156 }
2157
2166 public function userAdjust( $ts, $tz = false ) {
2167 global $wgUser, $wgLocalTZoffset;
2168
2169 if ( $tz === false ) {
2170 $tz = $wgUser->getOption( 'timecorrection' );
2171 }
2172
2173 $data = explode( '|', $tz, 3 );
2174
2175 if ( $data[0] == 'ZoneInfo' ) {
2176 try {
2177 $userTZ = new DateTimeZone( $data[2] );
2178 $date = new DateTime( $ts, new DateTimeZone( 'UTC' ) );
2179 $date->setTimezone( $userTZ );
2180 return $date->format( 'YmdHis' );
2181 } catch ( Exception $e ) {
2182 // Unrecognized timezone, default to 'Offset' with the stored offset.
2183 $data[0] = 'Offset';
2184 }
2185 }
2186
2187 if ( $data[0] == 'System' || $tz == '' ) {
2188 # Global offset in minutes.
2189 $minDiff = $wgLocalTZoffset;
2190 } elseif ( $data[0] == 'Offset' ) {
2191 $minDiff = intval( $data[1] );
2192 } else {
2193 $data = explode( ':', $tz );
2194 if ( count( $data ) == 2 ) {
2195 $data[0] = intval( $data[0] );
2196 $data[1] = intval( $data[1] );
2197 $minDiff = abs( $data[0] ) * 60 + $data[1];
2198 if ( $data[0] < 0 ) {
2199 $minDiff = -$minDiff;
2200 }
2201 } else {
2202 $minDiff = intval( $data[0] ) * 60;
2203 }
2204 }
2205
2206 # No difference ? Return time unchanged
2207 if ( 0 == $minDiff ) {
2208 return $ts;
2209 }
2210
2211 Wikimedia\suppressWarnings(); // E_STRICT system time bitching
2212 # Generate an adjusted date; take advantage of the fact that mktime
2213 # will normalize out-of-range values so we don't have to split $minDiff
2214 # into hours and minutes.
2215 $t = mktime( (
2216 (int)substr( $ts, 8, 2 ) ), # Hours
2217 (int)substr( $ts, 10, 2 ) + $minDiff, # Minutes
2218 (int)substr( $ts, 12, 2 ), # Seconds
2219 (int)substr( $ts, 4, 2 ), # Month
2220 (int)substr( $ts, 6, 2 ), # Day
2221 (int)substr( $ts, 0, 4 ) ); # Year
2222
2223 $date = date( 'YmdHis', $t );
2224 Wikimedia\restoreWarnings();
2225
2226 return $date;
2227 }
2228
2244 function dateFormat( $usePrefs = true ) {
2245 global $wgUser;
2246
2247 if ( is_bool( $usePrefs ) ) {
2248 if ( $usePrefs ) {
2249 $datePreference = $wgUser->getDatePreference();
2250 } else {
2251 $datePreference = (string)User::getDefaultOption( 'date' );
2252 }
2253 } else {
2254 $datePreference = (string)$usePrefs;
2255 }
2256
2257 // return int
2258 if ( $datePreference == '' ) {
2259 return 'default';
2260 }
2261
2262 return $datePreference;
2263 }
2264
2275 function getDateFormatString( $type, $pref ) {
2276 $wasDefault = false;
2277 if ( $pref == 'default' ) {
2278 $wasDefault = true;
2279 $pref = $this->getDefaultDateFormat();
2280 }
2281
2282 if ( !isset( $this->dateFormatStrings[$type][$pref] ) ) {
2283 $df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" );
2284
2285 if ( $type === 'pretty' && $df === null ) {
2286 $df = $this->getDateFormatString( 'date', $pref );
2287 }
2288
2289 if ( !$wasDefault && $df === null ) {
2290 $pref = $this->getDefaultDateFormat();
2291 $df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" );
2292 }
2293
2294 $this->dateFormatStrings[$type][$pref] = $df;
2295 }
2296 return $this->dateFormatStrings[$type][$pref];
2297 }
2298
2309 public function date( $ts, $adj = false, $format = true, $timecorrection = false ) {
2310 $ts = wfTimestamp( TS_MW, $ts );
2311 if ( $adj ) {
2312 $ts = $this->userAdjust( $ts, $timecorrection );
2313 }
2314 $df = $this->getDateFormatString( 'date', $this->dateFormat( $format ) );
2315 return $this->sprintfDate( $df, $ts );
2316 }
2317
2328 public function time( $ts, $adj = false, $format = true, $timecorrection = false ) {
2329 $ts = wfTimestamp( TS_MW, $ts );
2330 if ( $adj ) {
2331 $ts = $this->userAdjust( $ts, $timecorrection );
2332 }
2333 $df = $this->getDateFormatString( 'time', $this->dateFormat( $format ) );
2334 return $this->sprintfDate( $df, $ts );
2335 }
2336
2348 public function timeanddate( $ts, $adj = false, $format = true, $timecorrection = false ) {
2349 $ts = wfTimestamp( TS_MW, $ts );
2350 if ( $adj ) {
2351 $ts = $this->userAdjust( $ts, $timecorrection );
2352 }
2353 $df = $this->getDateFormatString( 'both', $this->dateFormat( $format ) );
2354 return $this->sprintfDate( $df, $ts );
2355 }
2356
2367 public function formatDuration( $seconds, array $chosenIntervals = [] ) {
2368 $intervals = $this->getDurationIntervals( $seconds, $chosenIntervals );
2369
2370 $segments = [];
2371
2372 foreach ( $intervals as $intervalName => $intervalValue ) {
2373 // Messages: duration-seconds, duration-minutes, duration-hours, duration-days, duration-weeks,
2374 // duration-years, duration-decades, duration-centuries, duration-millennia
2375 $message = wfMessage( 'duration-' . $intervalName )->numParams( $intervalValue );
2376 $segments[] = $message->inLanguage( $this )->escaped();
2377 }
2378
2379 return $this->listToText( $segments );
2380 }
2381
2393 public function getDurationIntervals( $seconds, array $chosenIntervals = [] ) {
2394 if ( empty( $chosenIntervals ) ) {
2395 $chosenIntervals = [
2396 'millennia',
2397 'centuries',
2398 'decades',
2399 'years',
2400 'days',
2401 'hours',
2402 'minutes',
2403 'seconds'
2404 ];
2405 }
2406
2407 $intervals = array_intersect_key( self::$durationIntervals, array_flip( $chosenIntervals ) );
2408 $sortedNames = array_keys( $intervals );
2409 $smallestInterval = array_pop( $sortedNames );
2410
2411 $segments = [];
2412
2413 foreach ( $intervals as $name => $length ) {
2414 $value = floor( $seconds / $length );
2415
2416 if ( $value > 0 || ( $name == $smallestInterval && empty( $segments ) ) ) {
2417 $seconds -= $value * $length;
2418 $segments[$name] = $value;
2419 }
2420 }
2421
2422 return $segments;
2423 }
2424
2444 private function internalUserTimeAndDate( $type, $ts, User $user, array $options ) {
2445 $ts = wfTimestamp( TS_MW, $ts );
2446 $options += [ 'timecorrection' => true, 'format' => true ];
2447 if ( $options['timecorrection'] !== false ) {
2448 if ( $options['timecorrection'] === true ) {
2449 $offset = $user->getOption( 'timecorrection' );
2450 } else {
2451 $offset = $options['timecorrection'];
2452 }
2453 $ts = $this->userAdjust( $ts, $offset );
2454 }
2455 if ( $options['format'] === true ) {
2456 $format = $user->getDatePreference();
2457 } else {
2458 $format = $options['format'];
2459 }
2460 $df = $this->getDateFormatString( $type, $this->dateFormat( $format ) );
2461 return $this->sprintfDate( $df, $ts );
2462 }
2463
2483 public function userDate( $ts, User $user, array $options = [] ) {
2484 return $this->internalUserTimeAndDate( 'date', $ts, $user, $options );
2485 }
2486
2506 public function userTime( $ts, User $user, array $options = [] ) {
2507 return $this->internalUserTimeAndDate( 'time', $ts, $user, $options );
2508 }
2509
2529 public function userTimeAndDate( $ts, User $user, array $options = [] ) {
2530 return $this->internalUserTimeAndDate( 'both', $ts, $user, $options );
2531 }
2532
2548 public function getHumanTimestamp(
2549 MWTimestamp $time, MWTimestamp $relativeTo = null, User $user = null
2550 ) {
2551 if ( $relativeTo === null ) {
2552 $relativeTo = new MWTimestamp();
2553 }
2554 if ( $user === null ) {
2555 $user = RequestContext::getMain()->getUser();
2556 }
2557
2558 // Adjust for the user's timezone.
2559 $offsetThis = $time->offsetForUser( $user );
2560 $offsetRel = $relativeTo->offsetForUser( $user );
2561
2562 $ts = '';
2563 if ( Hooks::run( 'GetHumanTimestamp', [ &$ts, $time, $relativeTo, $user, $this ] ) ) {
2564 $ts = $this->getHumanTimestampInternal( $time, $relativeTo, $user );
2565 }
2566
2567 // Reset the timezone on the objects.
2568 $time->timestamp->sub( $offsetThis );
2569 $relativeTo->timestamp->sub( $offsetRel );
2570
2571 return $ts;
2572 }
2573
2586 MWTimestamp $ts, MWTimestamp $relativeTo, User $user
2587 ) {
2588 $diff = $ts->diff( $relativeTo );
2589 $diffDay = (bool)( (int)$ts->timestamp->format( 'w' ) -
2590 (int)$relativeTo->timestamp->format( 'w' ) );
2591 $days = $diff->days ?: (int)$diffDay;
2592 if ( $diff->invert || $days > 5
2593 && $ts->timestamp->format( 'Y' ) !== $relativeTo->timestamp->format( 'Y' )
2594 ) {
2595 // Timestamps are in different years: use full timestamp
2596 // Also do full timestamp for future dates
2600 $format = $this->getDateFormatString( 'both', $user->getDatePreference() ?: 'default' );
2601 $ts = $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) );
2602 } elseif ( $days > 5 ) {
2603 // Timestamps are in same year, but more than 5 days ago: show day and month only.
2604 $format = $this->getDateFormatString( 'pretty', $user->getDatePreference() ?: 'default' );
2605 $ts = $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) );
2606 } elseif ( $days > 1 ) {
2607 // Timestamp within the past week: show the day of the week and time
2608 $format = $this->getDateFormatString( 'time', $user->getDatePreference() ?: 'default' );
2609 $weekday = self::$mWeekdayMsgs[$ts->timestamp->format( 'w' )];
2610 // Messages:
2611 // sunday-at, monday-at, tuesday-at, wednesday-at, thursday-at, friday-at, saturday-at
2612 $ts = wfMessage( "$weekday-at" )
2613 ->inLanguage( $this )
2614 ->params( $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) ) )
2615 ->text();
2616 } elseif ( $days == 1 ) {
2617 // Timestamp was yesterday: say 'yesterday' and the time.
2618 $format = $this->getDateFormatString( 'time', $user->getDatePreference() ?: 'default' );
2619 $ts = wfMessage( 'yesterday-at' )
2620 ->inLanguage( $this )
2621 ->params( $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) ) )
2622 ->text();
2623 } elseif ( $diff->h > 1 || $diff->h == 1 && $diff->i > 30 ) {
2624 // Timestamp was today, but more than 90 minutes ago: say 'today' and the time.
2625 $format = $this->getDateFormatString( 'time', $user->getDatePreference() ?: 'default' );
2626 $ts = wfMessage( 'today-at' )
2627 ->inLanguage( $this )
2628 ->params( $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) ) )
2629 ->text();
2630
2631 // From here on in, the timestamp was soon enough ago so that we can simply say
2632 // XX units ago, e.g., "2 hours ago" or "5 minutes ago"
2633 } elseif ( $diff->h == 1 ) {
2634 // Less than 90 minutes, but more than an hour ago.
2635 $ts = wfMessage( 'hours-ago' )->inLanguage( $this )->numParams( 1 )->text();
2636 } elseif ( $diff->i >= 1 ) {
2637 // A few minutes ago.
2638 $ts = wfMessage( 'minutes-ago' )->inLanguage( $this )->numParams( $diff->i )->text();
2639 } elseif ( $diff->s >= 30 ) {
2640 // Less than a minute, but more than 30 sec ago.
2641 $ts = wfMessage( 'seconds-ago' )->inLanguage( $this )->numParams( $diff->s )->text();
2642 } else {
2643 // Less than 30 seconds ago.
2644 $ts = wfMessage( 'just-now' )->text();
2645 }
2646
2647 return $ts;
2648 }
2649
2654 public function getMessage( $key ) {
2655 return self::$dataCache->getSubitem( $this->mCode, 'messages', $key );
2656 }
2657
2661 function getAllMessages() {
2662 return self::$dataCache->getItem( $this->mCode, 'messages' );
2663 }
2664
2671 public function iconv( $in, $out, $string ) {
2672 # Even with //IGNORE iconv can whine about illegal characters in
2673 # *input* string. We just ignore those too.
2674 # REF: https://bugs.php.net/bug.php?id=37166
2675 # REF: https://phabricator.wikimedia.org/T18885
2676 Wikimedia\suppressWarnings();
2677 $text = iconv( $in, $out . '//IGNORE', $string );
2678 Wikimedia\restoreWarnings();
2679 return $text;
2680 }
2681
2682 // callback functions for ucwords(), ucwordbreaks()
2683
2689 return $this->ucfirst( $matches[1] );
2690 }
2691
2697 return mb_strtoupper( $matches[0] );
2698 }
2699
2705 return mb_strtoupper( $matches[0] );
2706 }
2707
2715 public function ucfirst( $str ) {
2716 $o = ord( $str );
2717 if ( $o < 96 ) { // if already uppercase...
2718 return $str;
2719 } elseif ( $o < 128 ) {
2720 return ucfirst( $str ); // use PHP's ucfirst()
2721 } else {
2722 // fall back to more complex logic in case of multibyte strings
2723 return $this->uc( $str, true );
2724 }
2725 }
2726
2735 public function uc( $str, $first = false ) {
2736 if ( $first ) {
2737 if ( $this->isMultibyte( $str ) ) {
2738 return mb_strtoupper( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
2739 } else {
2740 return ucfirst( $str );
2741 }
2742 } else {
2743 return $this->isMultibyte( $str ) ? mb_strtoupper( $str ) : strtoupper( $str );
2744 }
2745 }
2746
2751 function lcfirst( $str ) {
2752 $o = ord( $str );
2753 if ( !$o ) {
2754 return strval( $str );
2755 } elseif ( $o >= 128 ) {
2756 return $this->lc( $str, true );
2757 } elseif ( $o > 96 ) {
2758 return $str;
2759 } else {
2760 $str[0] = strtolower( $str[0] );
2761 return $str;
2762 }
2763 }
2764
2770 function lc( $str, $first = false ) {
2771 if ( $first ) {
2772 if ( $this->isMultibyte( $str ) ) {
2773 return mb_strtolower( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
2774 } else {
2775 return strtolower( substr( $str, 0, 1 ) ) . substr( $str, 1 );
2776 }
2777 } else {
2778 return $this->isMultibyte( $str ) ? mb_strtolower( $str ) : strtolower( $str );
2779 }
2780 }
2781
2786 function isMultibyte( $str ) {
2787 return strlen( $str ) !== mb_strlen( $str );
2788 }
2789
2794 function ucwords( $str ) {
2795 if ( $this->isMultibyte( $str ) ) {
2796 $str = $this->lc( $str );
2797
2798 // regexp to find first letter in each word (i.e. after each space)
2799 $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)| ([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
2800
2801 // function to use to capitalize a single char
2802 return preg_replace_callback(
2803 $replaceRegexp,
2804 [ $this, 'ucwordsCallbackMB' ],
2805 $str
2806 );
2807 } else {
2808 return ucwords( strtolower( $str ) );
2809 }
2810 }
2811
2818 function ucwordbreaks( $str ) {
2819 if ( $this->isMultibyte( $str ) ) {
2820 $str = $this->lc( $str );
2821
2822 // since \b doesn't work for UTF-8, we explicitely define word break chars
2823 $breaks = "[ \-\‍(\‍)\}\{\.,\?!]";
2824
2825 // find first letter after word break
2826 $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)|" .
2827 "$breaks([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
2828
2829 return preg_replace_callback(
2830 $replaceRegexp,
2831 [ $this, 'ucwordbreaksCallbackMB' ],
2832 $str
2833 );
2834 } else {
2835 return preg_replace_callback(
2836 '/\b([\w\x80-\xff]+)\b/',
2837 [ $this, 'ucwordbreaksCallbackAscii' ],
2838 $str
2839 );
2840 }
2841 }
2842
2858 function caseFold( $s ) {
2859 return $this->uc( $s );
2860 }
2861
2868 if ( is_array( $s ) ) {
2869 throw new MWException( 'Given array to checkTitleEncoding.' );
2870 }
2871 if ( StringUtils::isUtf8( $s ) ) {
2872 return $s;
2873 }
2874
2875 return $this->iconv( $this->fallback8bitEncoding(), 'utf-8', $s );
2876 }
2877
2882 return self::$dataCache->getItem( $this->mCode, 'fallback8bitEncoding' );
2883 }
2884
2893 function hasWordBreaks() {
2894 return true;
2895 }
2896
2904 function segmentByWord( $string ) {
2905 return $string;
2906 }
2907
2915 function normalizeForSearch( $string ) {
2916 return self::convertDoubleWidth( $string );
2917 }
2918
2927 protected static function convertDoubleWidth( $string ) {
2928 static $full = null;
2929 static $half = null;
2930
2931 if ( $full === null ) {
2932 $fullWidth = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
2933 $halfWidth = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
2934 $full = str_split( $fullWidth, 3 );
2935 $half = str_split( $halfWidth );
2936 }
2937
2938 $string = str_replace( $full, $half, $string );
2939 return $string;
2940 }
2941
2947 protected static function insertSpace( $string, $pattern ) {
2948 $string = preg_replace( $pattern, " $1 ", $string );
2949 $string = preg_replace( '/ +/', ' ', $string );
2950 return $string;
2951 }
2952
2957 function convertForSearchResult( $termsArray ) {
2958 # some languages, e.g. Chinese, need to do a conversion
2959 # in order for search results to be displayed correctly
2960 return $termsArray;
2961 }
2962
2969 function firstChar( $s ) {
2970 $matches = [];
2971 preg_match(
2972 '/^([\x00-\x7f]|[\xc0-\xdf][\x80-\xbf]|' .
2973 '[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xf7][\x80-\xbf]{3})/',
2974 $s,
2975 $matches
2976 );
2977
2978 if ( isset( $matches[1] ) ) {
2979 if ( strlen( $matches[1] ) != 3 ) {
2980 return $matches[1];
2981 }
2982
2983 // Break down Hangul syllables to grab the first jamo
2984 $code = UtfNormal\Utils::utf8ToCodepoint( $matches[1] );
2985 if ( $code < 0xac00 || 0xd7a4 <= $code ) {
2986 return $matches[1];
2987 } elseif ( $code < 0xb098 ) {
2988 return "\u{3131}";
2989 } elseif ( $code < 0xb2e4 ) {
2990 return "\u{3134}";
2991 } elseif ( $code < 0xb77c ) {
2992 return "\u{3137}";
2993 } elseif ( $code < 0xb9c8 ) {
2994 return "\u{3139}";
2995 } elseif ( $code < 0xbc14 ) {
2996 return "\u{3141}";
2997 } elseif ( $code < 0xc0ac ) {
2998 return "\u{3142}";
2999 } elseif ( $code < 0xc544 ) {
3000 return "\u{3145}";
3001 } elseif ( $code < 0xc790 ) {
3002 return "\u{3147}";
3003 } elseif ( $code < 0xcc28 ) {
3004 return "\u{3148}";
3005 } elseif ( $code < 0xce74 ) {
3006 return "\u{314A}";
3007 } elseif ( $code < 0xd0c0 ) {
3008 return "\u{314B}";
3009 } elseif ( $code < 0xd30c ) {
3010 return "\u{314C}";
3011 } elseif ( $code < 0xd558 ) {
3012 return "\u{314D}";
3013 } else {
3014 return "\u{314E}";
3015 }
3016 } else {
3017 return '';
3018 }
3019 }
3020
3024 function initEncoding() {
3025 wfDeprecated( __METHOD__, '1.28' );
3026 // No-op.
3027 }
3028
3034 function recodeForEdit( $s ) {
3035 wfDeprecated( __METHOD__, '1.28' );
3036 return $s;
3037 }
3038
3044 function recodeInput( $s ) {
3045 wfDeprecated( __METHOD__, '1.28' );
3046 return $s;
3047 }
3048
3060 public function normalize( $s ) {
3061 global $wgAllUnicodeFixes;
3062 $s = UtfNormal\Validator::cleanUp( $s );
3063 if ( $wgAllUnicodeFixes ) {
3064 $s = $this->transformUsingPairFile( 'normalize-ar.php', $s );
3065 $s = $this->transformUsingPairFile( 'normalize-ml.php', $s );
3066 }
3067
3068 return $s;
3069 }
3070
3085 protected function transformUsingPairFile( $file, $string ) {
3086 if ( !isset( $this->transformData[$file] ) ) {
3087 global $IP;
3088 $data = require "$IP/languages/data/{$file}";
3089 $this->transformData[$file] = new ReplacementArray( $data );
3090 }
3091 return $this->transformData[$file]->replace( $string );
3092 }
3093
3099 function isRTL() {
3100 return self::$dataCache->getItem( $this->mCode, 'rtl' );
3101 }
3102
3107 function getDir() {
3108 return $this->isRTL() ? 'rtl' : 'ltr';
3109 }
3110
3119 function alignStart() {
3120 return $this->isRTL() ? 'right' : 'left';
3121 }
3122
3131 function alignEnd() {
3132 return $this->isRTL() ? 'left' : 'right';
3133 }
3134
3146 function getDirMarkEntity( $opposite = false ) {
3147 if ( $opposite ) {
3148 return $this->isRTL() ? '&lrm;' : '&rlm;';
3149 }
3150 return $this->isRTL() ? '&rlm;' : '&lrm;';
3151 }
3152
3163 function getDirMark( $opposite = false ) {
3164 $lrm = "\u{200E}"; # LEFT-TO-RIGHT MARK, commonly abbreviated LRM
3165 $rlm = "\u{200F}"; # RIGHT-TO-LEFT MARK, commonly abbreviated RLM
3166 if ( $opposite ) {
3167 return $this->isRTL() ? $lrm : $rlm;
3168 }
3169 return $this->isRTL() ? $rlm : $lrm;
3170 }
3171
3176 return self::$dataCache->getItem( $this->mCode, 'capitalizeAllNouns' );
3177 }
3178
3186 function getArrow( $direction = 'forwards' ) {
3187 switch ( $direction ) {
3188 case 'forwards':
3189 return $this->isRTL() ? '←' : '→';
3190 case 'backwards':
3191 return $this->isRTL() ? '→' : '←';
3192 case 'left':
3193 return '←';
3194 case 'right':
3195 return '→';
3196 case 'up':
3197 return '↑';
3198 case 'down':
3199 return '↓';
3200 }
3201 }
3202
3209 return self::$dataCache->getItem( $this->mCode, 'linkPrefixExtension' );
3210 }
3211
3216 function getMagicWords() {
3217 return self::$dataCache->getItem( $this->mCode, 'magicWords' );
3218 }
3219
3223 protected function doMagicHook() {
3224 if ( $this->mMagicHookDone ) {
3225 return;
3226 }
3227 $this->mMagicHookDone = true;
3228 Hooks::run( 'LanguageGetMagic', [ &$this->mMagicExtensions, $this->getCode() ], '1.16' );
3229 }
3230
3236 function getMagic( $mw ) {
3237 // Saves a function call
3238 if ( !$this->mMagicHookDone ) {
3239 $this->doMagicHook();
3240 }
3241
3242 if ( isset( $this->mMagicExtensions[$mw->mId] ) ) {
3243 $rawEntry = $this->mMagicExtensions[$mw->mId];
3244 } else {
3245 $rawEntry = self::$dataCache->getSubitem(
3246 $this->mCode, 'magicWords', $mw->mId );
3247 }
3248
3249 if ( !is_array( $rawEntry ) ) {
3250 wfWarn( "\"$rawEntry\" is not a valid magic word for \"$mw->mId\"" );
3251 } else {
3252 $mw->mCaseSensitive = $rawEntry[0];
3253 $mw->mSynonyms = array_slice( $rawEntry, 1 );
3254 }
3255 }
3256
3262 function addMagicWordsByLang( $newWords ) {
3263 $fallbackChain = $this->getFallbackLanguages();
3264 $fallbackChain = array_reverse( $fallbackChain );
3265 foreach ( $fallbackChain as $code ) {
3266 if ( isset( $newWords[$code] ) ) {
3267 $this->mMagicExtensions = $newWords[$code] + $this->mMagicExtensions;
3268 }
3269 }
3270 }
3271
3278 // Cache aliases because it may be slow to load them
3279 if ( is_null( $this->mExtendedSpecialPageAliases ) ) {
3280 // Initialise array
3281 $this->mExtendedSpecialPageAliases =
3282 self::$dataCache->getItem( $this->mCode, 'specialPageAliases' );
3283 Hooks::run( 'LanguageGetSpecialPageAliases',
3284 [ &$this->mExtendedSpecialPageAliases, $this->getCode() ], '1.16' );
3285 }
3286
3287 return $this->mExtendedSpecialPageAliases;
3288 }
3289
3296 function emphasize( $text ) {
3297 return "<em>$text</em>";
3298 }
3299
3322 public function formatNum( $number, $nocommafy = false ) {
3323 global $wgTranslateNumerals;
3324 if ( !$nocommafy ) {
3325 $number = $this->commafy( $number );
3326 $s = $this->separatorTransformTable();
3327 if ( $s ) {
3328 $number = strtr( $number, $s );
3329 }
3330 }
3331
3332 if ( $wgTranslateNumerals ) {
3333 $s = $this->digitTransformTable();
3334 if ( $s ) {
3335 $number = strtr( $number, $s );
3336 }
3337 }
3338
3339 return (string)$number;
3340 }
3341
3350 public function formatNumNoSeparators( $number ) {
3351 return $this->formatNum( $number, true );
3352 }
3353
3358 public function parseFormattedNumber( $number ) {
3359 $s = $this->digitTransformTable();
3360 if ( $s ) {
3361 // eliminate empty array values such as ''. (T66347)
3362 $s = array_filter( $s );
3363 $number = strtr( $number, array_flip( $s ) );
3364 }
3365
3366 $s = $this->separatorTransformTable();
3367 if ( $s ) {
3368 // eliminate empty array values such as ''. (T66347)
3369 $s = array_filter( $s );
3370 $number = strtr( $number, array_flip( $s ) );
3371 }
3372
3373 $number = strtr( $number, [ ',' => '' ] );
3374 return $number;
3375 }
3376
3383 function commafy( $number ) {
3386 if ( $number === null ) {
3387 return '';
3388 }
3389
3390 if ( !$digitGroupingPattern || $digitGroupingPattern === "###,###,###" ) {
3391 // Default grouping is at thousands, use the same for ###,###,### pattern too.
3392 // In some languages it's conventional not to insert a thousands separator
3393 // in numbers that are four digits long (1000-9999).
3394 if ( $minimumGroupingDigits ) {
3395 // Number of '#' characters after last comma in the grouping pattern.
3396 // The pattern is hardcoded here, but this would vary for different patterns.
3397 $primaryGroupingSize = 3;
3398 // Maximum length of a number to suppress digit grouping for.
3399 $maximumLength = $minimumGroupingDigits + $primaryGroupingSize - 1;
3400 if ( preg_match( '/^\-?\d{1,' . $maximumLength . '}(\.\d+)?$/', $number ) ) {
3401 return $number;
3402 }
3403 }
3404 return strrev( (string)preg_replace( '/(\d{3})(?=\d)(?!\d*\.)/', '$1,', strrev( $number ) ) );
3405 } else {
3406 // Ref: http://cldr.unicode.org/translation/number-patterns
3407 $sign = "";
3408 if ( intval( $number ) < 0 ) {
3409 // For negative numbers apply the algorithm like positive number and add sign.
3410 $sign = "-";
3411 $number = substr( $number, 1 );
3412 }
3413 $integerPart = [];
3414 $decimalPart = [];
3415 $numMatches = preg_match_all( "/(#+)/", $digitGroupingPattern, $matches );
3416 preg_match( "/\d+/", $number, $integerPart );
3417 preg_match( "/\.\d*/", $number, $decimalPart );
3418 $groupedNumber = ( count( $decimalPart ) > 0 ) ? $decimalPart[0] : "";
3419 if ( $groupedNumber === $number ) {
3420 // the string does not have any number part. Eg: .12345
3421 return $sign . $groupedNumber;
3422 }
3423 $start = $end = ( $integerPart ) ? strlen( $integerPart[0] ) : 0;
3424 while ( $start > 0 ) {
3425 $match = $matches[0][$numMatches - 1];
3426 $matchLen = strlen( $match );
3427 $start = $end - $matchLen;
3428 if ( $start < 0 ) {
3429 $start = 0;
3430 }
3431 $groupedNumber = substr( $number, $start, $end - $start ) . $groupedNumber;
3432 $end = $start;
3433 if ( $numMatches > 1 ) {
3434 // use the last pattern for the rest of the number
3435 $numMatches--;
3436 }
3437 if ( $start > 0 ) {
3438 $groupedNumber = "," . $groupedNumber;
3439 }
3440 }
3441 return $sign . $groupedNumber;
3442 }
3443 }
3444
3449 return self::$dataCache->getItem( $this->mCode, 'digitGroupingPattern' );
3450 }
3451
3456 return self::$dataCache->getItem( $this->mCode, 'digitTransformTable' );
3457 }
3458
3463 return self::$dataCache->getItem( $this->mCode, 'separatorTransformTable' );
3464 }
3465
3470 return self::$dataCache->getItem( $this->mCode, 'minimumGroupingDigits' );
3471 }
3472
3481 public function listToText( array $list ) {
3482 $itemCount = count( $list );
3483 if ( $itemCount < 1 ) {
3484 return '';
3485 }
3486 $text = array_pop( $list );
3487 if ( $itemCount > 1 ) {
3488 $and = $this->msg( 'and' )->escaped();
3489 $space = $this->msg( 'word-separator' )->escaped();
3490 $comma = '';
3491 if ( $itemCount > 2 ) {
3492 $comma = $this->msg( 'comma-separator' )->escaped();
3493 }
3494 $text = implode( $comma, $list ) . $and . $space . $text;
3495 }
3496 return $text;
3497 }
3498
3505 function commaList( array $list ) {
3506 return implode(
3507 wfMessage( 'comma-separator' )->inLanguage( $this )->escaped(),
3508 $list
3509 );
3510 }
3511
3518 function semicolonList( array $list ) {
3519 return implode(
3520 wfMessage( 'semicolon-separator' )->inLanguage( $this )->escaped(),
3521 $list
3522 );
3523 }
3524
3530 function pipeList( array $list ) {
3531 return implode(
3532 wfMessage( 'pipe-separator' )->inLanguage( $this )->escaped(),
3533 $list
3534 );
3535 }
3536
3554 function truncate( $string, $length, $ellipsis = '...', $adjustLength = true ) {
3555 wfDeprecated( __METHOD__, '1.31' );
3556 return $this->truncateForDatabase( $string, $length, $ellipsis, $adjustLength );
3557 }
3558
3574 function truncateForDatabase( $string, $length, $ellipsis = '...', $adjustLength = true ) {
3575 return $this->truncateInternal(
3576 $string, $length, $ellipsis, $adjustLength, 'strlen', 'substr'
3577 );
3578 }
3579
3598 function truncateForVisual( $string, $length, $ellipsis = '...', $adjustLength = true ) {
3599 // Passing encoding to mb_strlen and mb_substr is optional.
3600 // Encoding defaults to mb_internal_encoding(), which is set to UTF-8 in Setup.php, so
3601 // explicit specification of encoding is skipped.
3602 // Note: Both multibyte methods are callables invoked in truncateInternal.
3603 return $this->truncateInternal(
3604 $string, $length, $ellipsis, $adjustLength, 'mb_strlen', 'mb_substr'
3605 );
3606 }
3607
3624 private function truncateInternal(
3625 $string, $length, $ellipsis, $adjustLength, $measureLength, $getSubstring
3626 ) {
3627 if ( !is_callable( $measureLength ) || !is_callable( $getSubstring ) ) {
3628 throw new InvalidArgumentException( 'Invalid callback provided' );
3629 }
3630
3631 # Check if there is no need to truncate
3632 if ( $measureLength( $string ) <= abs( $length ) ) {
3633 return $string; // no need to truncate
3634 }
3635
3636 # Use the localized ellipsis character
3637 if ( $ellipsis == '...' ) {
3638 $ellipsis = wfMessage( 'ellipsis' )->inLanguage( $this )->escaped();
3639 }
3640 if ( $length == 0 ) {
3641 return $ellipsis; // convention
3642 }
3643
3644 $stringOriginal = $string;
3645 # If ellipsis length is >= $length then we can't apply $adjustLength
3646 if ( $adjustLength && $measureLength( $ellipsis ) >= abs( $length ) ) {
3647 $string = $ellipsis; // this can be slightly unexpected
3648 # Otherwise, truncate and add ellipsis...
3649 } else {
3650 $ellipsisLength = $adjustLength ? $measureLength( $ellipsis ) : 0;
3651 if ( $length > 0 ) {
3652 $length -= $ellipsisLength;
3653 $string = $getSubstring( $string, 0, $length ); // xyz...
3654 $string = $this->removeBadCharLast( $string );
3655 $string = rtrim( $string );
3656 $string = $string . $ellipsis;
3657 } else {
3658 $length += $ellipsisLength;
3659 $string = $getSubstring( $string, $length ); // ...xyz
3660 $string = $this->removeBadCharFirst( $string );
3661 $string = ltrim( $string );
3662 $string = $ellipsis . $string;
3663 }
3664 }
3665
3666 # Do not truncate if the ellipsis makes the string longer/equal (T24181).
3667 # This check is *not* redundant if $adjustLength, due to the single case where
3668 # LEN($ellipsis) > ABS($limit arg); $stringOriginal could be shorter than $string.
3669 if ( $measureLength( $string ) < $measureLength( $stringOriginal ) ) {
3670 return $string;
3671 } else {
3672 return $stringOriginal;
3673 }
3674 }
3675
3683 protected function removeBadCharLast( $string ) {
3684 if ( $string != '' ) {
3685 $char = ord( $string[strlen( $string ) - 1] );
3686 $m = [];
3687 if ( $char >= 0xc0 ) {
3688 # We got the first byte only of a multibyte char; remove it.
3689 $string = substr( $string, 0, -1 );
3690 } elseif ( $char >= 0x80 &&
3691 // Use the /s modifier (PCRE_DOTALL) so (.*) also matches newlines
3692 preg_match( '/^(.*)(?:[\xe0-\xef][\x80-\xbf]|' .
3693 '[\xf0-\xf7][\x80-\xbf]{1,2})$/s', $string, $m )
3694 ) {
3695 # We chopped in the middle of a character; remove it
3696 $string = $m[1];
3697 }
3698 }
3699 return $string;
3700 }
3701
3709 protected function removeBadCharFirst( $string ) {
3710 if ( $string != '' ) {
3711 $char = ord( $string[0] );
3712 if ( $char >= 0x80 && $char < 0xc0 ) {
3713 # We chopped in the middle of a character; remove the whole thing
3714 $string = preg_replace( '/^[\x80-\xbf]+/', '', $string );
3715 }
3716 }
3717 return $string;
3718 }
3719
3735 function truncateHtml( $text, $length, $ellipsis = '...' ) {
3736 # Use the localized ellipsis character
3737 if ( $ellipsis == '...' ) {
3738 $ellipsis = wfMessage( 'ellipsis' )->inLanguage( $this )->escaped();
3739 }
3740 # Check if there is clearly no need to truncate
3741 if ( $length <= 0 ) {
3742 return $ellipsis; // no text shown, nothing to format (convention)
3743 } elseif ( strlen( $text ) <= $length ) {
3744 return $text; // string short enough even *with* HTML (short-circuit)
3745 }
3746
3747 $dispLen = 0; // innerHTML legth so far
3748 $testingEllipsis = false; // checking if ellipses will make string longer/equal?
3749 $tagType = 0; // 0-open, 1-close
3750 $bracketState = 0; // 1-tag start, 2-tag name, 0-neither
3751 $entityState = 0; // 0-not entity, 1-entity
3752 $tag = $ret = ''; // accumulated tag name, accumulated result string
3753 $openTags = []; // open tag stack
3754 $maybeState = null; // possible truncation state
3755
3756 $textLen = strlen( $text );
3757 $neLength = max( 0, $length - strlen( $ellipsis ) ); // non-ellipsis len if truncated
3758 for ( $pos = 0; true; ++$pos ) {
3759 # Consider truncation once the display length has reached the maximim.
3760 # We check if $dispLen > 0 to grab tags for the $neLength = 0 case.
3761 # Check that we're not in the middle of a bracket/entity...
3762 if ( $dispLen && $dispLen >= $neLength && $bracketState == 0 && !$entityState ) {
3763 if ( !$testingEllipsis ) {
3764 $testingEllipsis = true;
3765 # Save where we are; we will truncate here unless there turn out to
3766 # be so few remaining characters that truncation is not necessary.
3767 if ( !$maybeState ) { // already saved? ($neLength = 0 case)
3768 $maybeState = [ $ret, $openTags ]; // save state
3769 }
3770 } elseif ( $dispLen > $length && $dispLen > strlen( $ellipsis ) ) {
3771 # String in fact does need truncation, the truncation point was OK.
3772 list( $ret, $openTags ) = $maybeState; // reload state
3773 $ret = $this->removeBadCharLast( $ret ); // multi-byte char fix
3774 $ret .= $ellipsis; // add ellipsis
3775 break;
3776 }
3777 }
3778 if ( $pos >= $textLen ) {
3779 break; // extra iteration just for above checks
3780 }
3781
3782 # Read the next char...
3783 $ch = $text[$pos];
3784 $lastCh = $pos ? $text[$pos - 1] : '';
3785 $ret .= $ch; // add to result string
3786 if ( $ch == '<' ) {
3787 $this->truncate_endBracket( $tag, $tagType, $lastCh, $openTags ); // for bad HTML
3788 $entityState = 0; // for bad HTML
3789 $bracketState = 1; // tag started (checking for backslash)
3790 } elseif ( $ch == '>' ) {
3791 $this->truncate_endBracket( $tag, $tagType, $lastCh, $openTags );
3792 $entityState = 0; // for bad HTML
3793 $bracketState = 0; // out of brackets
3794 } elseif ( $bracketState == 1 ) {
3795 if ( $ch == '/' ) {
3796 $tagType = 1; // close tag (e.g. "</span>")
3797 } else {
3798 $tagType = 0; // open tag (e.g. "<span>")
3799 $tag .= $ch;
3800 }
3801 $bracketState = 2; // building tag name
3802 } elseif ( $bracketState == 2 ) {
3803 if ( $ch != ' ' ) {
3804 $tag .= $ch;
3805 } else {
3806 // Name found (e.g. "<a href=..."), add on tag attributes...
3807 $pos += $this->truncate_skip( $ret, $text, "<>", $pos + 1 );
3808 }
3809 } elseif ( $bracketState == 0 ) {
3810 if ( $entityState ) {
3811 if ( $ch == ';' ) {
3812 $entityState = 0;
3813 $dispLen++; // entity is one displayed char
3814 }
3815 } else {
3816 if ( $neLength == 0 && !$maybeState ) {
3817 // Save state without $ch. We want to *hit* the first
3818 // display char (to get tags) but not *use* it if truncating.
3819 $maybeState = [ substr( $ret, 0, -1 ), $openTags ];
3820 }
3821 if ( $ch == '&' ) {
3822 $entityState = 1; // entity found, (e.g. "&#160;")
3823 } else {
3824 $dispLen++; // this char is displayed
3825 // Add the next $max display text chars after this in one swoop...
3826 $max = ( $testingEllipsis ? $length : $neLength ) - $dispLen;
3827 $skipped = $this->truncate_skip( $ret, $text, "<>&", $pos + 1, $max );
3828 $dispLen += $skipped;
3829 $pos += $skipped;
3830 }
3831 }
3832 }
3833 }
3834 // Close the last tag if left unclosed by bad HTML
3835 $this->truncate_endBracket( $tag, $text[$textLen - 1], $tagType, $openTags );
3836 while ( count( $openTags ) > 0 ) {
3837 $ret .= '</' . array_pop( $openTags ) . '>'; // close open tags
3838 }
3839 return $ret;
3840 }
3841
3853 private function truncate_skip( &$ret, $text, $search, $start, $len = null ) {
3854 if ( $len === null ) {
3855 $len = -1; // -1 means "no limit" for strcspn
3856 } elseif ( $len < 0 ) {
3857 $len = 0; // sanity
3858 }
3859 $skipCount = 0;
3860 if ( $start < strlen( $text ) ) {
3861 $skipCount = strcspn( $text, $search, $start, $len );
3862 $ret .= substr( $text, $start, $skipCount );
3863 }
3864 return $skipCount;
3865 }
3866
3876 private function truncate_endBracket( &$tag, $tagType, $lastCh, &$openTags ) {
3877 $tag = ltrim( $tag );
3878 if ( $tag != '' ) {
3879 if ( $tagType == 0 && $lastCh != '/' ) {
3880 $openTags[] = $tag; // tag opened (didn't close itself)
3881 } elseif ( $tagType == 1 ) {
3882 if ( $openTags && $tag == $openTags[count( $openTags ) - 1] ) {
3883 array_pop( $openTags ); // tag closed
3884 }
3885 }
3886 $tag = '';
3887 }
3888 }
3889
3898 function convertGrammar( $word, $case ) {
3899 global $wgGrammarForms;
3900 if ( isset( $wgGrammarForms[$this->getCode()][$case][$word] ) ) {
3901 return $wgGrammarForms[$this->getCode()][$case][$word];
3902 }
3903
3905
3906 if ( isset( $grammarTransformations[$case] ) ) {
3907 $forms = $grammarTransformations[$case];
3908
3909 // Some names of grammar rules are aliases for other rules.
3910 // In such cases the value is a string rather than object,
3911 // so load the actual rules.
3912 if ( is_string( $forms ) ) {
3913 $forms = $grammarTransformations[$forms];
3914 }
3915
3916 foreach ( array_values( $forms ) as $rule ) {
3917 $form = $rule[0];
3918
3919 if ( $form === '@metadata' ) {
3920 continue;
3921 }
3922
3923 $replacement = $rule[1];
3924
3925 $regex = '/' . addcslashes( $form, '/' ) . '/u';
3926 $patternMatches = preg_match( $regex, $word );
3927
3928 if ( $patternMatches === false ) {
3930 'An error occurred while processing grammar. ' .
3931 "Word: '$word'. Regex: /$form/."
3932 );
3933 } elseif ( $patternMatches === 1 ) {
3934 $word = preg_replace( $regex, $replacement, $word );
3935
3936 break;
3937 }
3938 }
3939 }
3940
3941 return $word;
3942 }
3943
3949 function getGrammarForms() {
3950 global $wgGrammarForms;
3951 if ( isset( $wgGrammarForms[$this->getCode()] )
3952 && is_array( $wgGrammarForms[$this->getCode()] )
3953 ) {
3954 return $wgGrammarForms[$this->getCode()];
3955 }
3956
3957 return [];
3958 }
3959
3969 public function getGrammarTransformations() {
3970 $languageCode = $this->getCode();
3971
3972 if ( self::$grammarTransformations === null ) {
3973 self::$grammarTransformations = new MapCacheLRU( 10 );
3974 }
3975
3976 if ( self::$grammarTransformations->has( $languageCode ) ) {
3977 return self::$grammarTransformations->get( $languageCode );
3978 }
3979
3980 $data = [];
3981
3982 $grammarDataFile = __DIR__ . "/data/grammarTransformations/$languageCode.json";
3983 if ( is_readable( $grammarDataFile ) ) {
3984 $data = FormatJson::decode(
3985 file_get_contents( $grammarDataFile ),
3986 true
3987 );
3988
3989 if ( $data === null ) {
3990 throw new MWException( "Invalid grammar data for \"$languageCode\"." );
3991 }
3992
3993 self::$grammarTransformations->set( $languageCode, $data );
3994 }
3995
3996 return $data;
3997 }
3998
4018 function gender( $gender, $forms ) {
4019 if ( !count( $forms ) ) {
4020 return '';
4021 }
4022 $forms = $this->preConvertPlural( $forms, 2 );
4023 if ( $gender === 'male' ) {
4024 return $forms[0];
4025 }
4026 if ( $gender === 'female' ) {
4027 return $forms[1];
4028 }
4029 return $forms[2] ?? $forms[0];
4030 }
4031
4047 function convertPlural( $count, $forms ) {
4048 // Handle explicit n=pluralform cases
4049 $forms = $this->handleExplicitPluralForms( $count, $forms );
4050 if ( is_string( $forms ) ) {
4051 return $forms;
4052 }
4053 if ( !count( $forms ) ) {
4054 return '';
4055 }
4056
4057 $pluralForm = $this->getPluralRuleIndexNumber( $count );
4058 $pluralForm = min( $pluralForm, count( $forms ) - 1 );
4059 return $forms[$pluralForm];
4060 }
4061
4077 protected function handleExplicitPluralForms( $count, array $forms ) {
4078 foreach ( $forms as $index => $form ) {
4079 if ( preg_match( '/\d+=/i', $form ) ) {
4080 $pos = strpos( $form, '=' );
4081 if ( substr( $form, 0, $pos ) === (string)$count ) {
4082 return substr( $form, $pos + 1 );
4083 }
4084 unset( $forms[$index] );
4085 }
4086 }
4087 return array_values( $forms );
4088 }
4089
4098 protected function preConvertPlural( /* Array */ $forms, $count ) {
4099 while ( count( $forms ) < $count ) {
4100 $forms[] = $forms[count( $forms ) - 1];
4101 }
4102 return $forms;
4103 }
4104
4121 public function embedBidi( $text = '' ) {
4122 $dir = self::strongDirFromContent( $text );
4123 if ( $dir === 'ltr' ) {
4124 // Wrap in LEFT-TO-RIGHT EMBEDDING ... POP DIRECTIONAL FORMATTING
4125 return self::$lre . $text . self::$pdf;
4126 }
4127 if ( $dir === 'rtl' ) {
4128 // Wrap in RIGHT-TO-LEFT EMBEDDING ... POP DIRECTIONAL FORMATTING
4129 return self::$rle . $text . self::$pdf;
4130 }
4131 // No strong directionality: do not wrap
4132 return $text;
4133 }
4134
4148 function translateBlockExpiry( $str, User $user = null, $now = 0 ) {
4149 $duration = SpecialBlock::getSuggestedDurations( $this );
4150 foreach ( $duration as $show => $value ) {
4151 if ( strcmp( $str, $value ) == 0 ) {
4152 return htmlspecialchars( trim( $show ) );
4153 }
4154 }
4155
4156 if ( wfIsInfinity( $str ) ) {
4157 foreach ( $duration as $show => $value ) {
4158 if ( wfIsInfinity( $value ) ) {
4159 return htmlspecialchars( trim( $show ) );
4160 }
4161 }
4162 }
4163
4164 // If all else fails, return a standard duration or timestamp description.
4165 $time = strtotime( $str, $now );
4166 if ( $time === false ) { // Unknown format. Return it as-is in case.
4167 return $str;
4168 } elseif ( $time !== strtotime( $str, $now + 1 ) ) { // It's a relative timestamp.
4169 // The result differs based on current time, so the difference
4170 // is a fixed duration length.
4171 return $this->formatDuration( $time - $now );
4172 } else { // It's an absolute timestamp.
4173 if ( $time === 0 ) {
4174 // wfTimestamp() handles 0 as current time instead of epoch.
4175 $time = '19700101000000';
4176 }
4177 if ( $user ) {
4178 return $this->userTimeAndDate( $time, $user );
4179 }
4180 return $this->timeanddate( $time );
4181 }
4182 }
4183
4191 public function segmentForDiff( $text ) {
4192 return $text;
4193 }
4194
4201 public function unsegmentForDiff( $text ) {
4202 return $text;
4203 }
4204
4211 public function getConverter() {
4212 return $this->mConverter;
4213 }
4214
4223 public function autoConvert( $text, $variant = false ) {
4224 return $this->mConverter->autoConvert( $text, $variant );
4225 }
4226
4233 public function autoConvertToAllVariants( $text ) {
4234 return $this->mConverter->autoConvertToAllVariants( $text );
4235 }
4236
4248 public function convert( $text ) {
4249 return $this->mConverter->convert( $text );
4250 }
4251
4258 public function convertTitle( $title ) {
4259 return $this->mConverter->convertTitle( $title );
4260 }
4261
4270 public function convertNamespace( $ns, $variant = null ) {
4271 return $this->mConverter->convertNamespace( $ns, $variant );
4272 }
4273
4279 public function hasVariants() {
4280 return count( $this->getVariants() ) > 1;
4281 }
4282
4293 public function hasVariant( $variant ) {
4294 return $variant && ( $variant === $this->mConverter->validateVariant( $variant ) );
4295 }
4296
4304 public function convertHtml( $text, $isTitle = false ) {
4305 return htmlspecialchars( $this->convert( $text, $isTitle ) );
4306 }
4307
4312 public function convertCategoryKey( $key ) {
4313 return $this->mConverter->convertCategoryKey( $key );
4314 }
4315
4322 public function getVariants() {
4323 return $this->mConverter->getVariants();
4324 }
4325
4329 public function getPreferredVariant() {
4330 return $this->mConverter->getPreferredVariant();
4331 }
4332
4336 public function getDefaultVariant() {
4337 return $this->mConverter->getDefaultVariant();
4338 }
4339
4343 public function getURLVariant() {
4344 return $this->mConverter->getURLVariant();
4345 }
4346
4359 public function findVariantLink( &$link, &$nt, $ignoreOtherCond = false ) {
4360 $this->mConverter->findVariantLink( $link, $nt, $ignoreOtherCond );
4361 }
4362
4370 return $this->mConverter->getExtraHashOptions();
4371 }
4372
4380 public function getParsedTitle() {
4381 return $this->mConverter->getParsedTitle();
4382 }
4383
4390 public function updateConversionTable( Title $title ) {
4391 $this->mConverter->updateConversionTable( $title );
4392 }
4393
4410 public function markNoConversion( $text, $noParse = false ) {
4411 wfDeprecated( __METHOD__, '1.32' );
4412 // Excluding protocal-relative URLs may avoid many false positives.
4413 if ( $noParse || preg_match( '/^(?:' . wfUrlProtocolsWithoutProtRel() . ')/', $text ) ) {
4414 return $this->mConverter->markNoConversion( $text );
4415 } else {
4416 return $text;
4417 }
4418 }
4419
4426 public function linkTrail() {
4427 return self::$dataCache->getItem( $this->mCode, 'linkTrail' );
4428 }
4429
4436 public function linkPrefixCharset() {
4437 return self::$dataCache->getItem( $this->mCode, 'linkPrefixCharset' );
4438 }
4439
4447 public function getParentLanguage() {
4448 if ( $this->mParentLanguage !== false ) {
4449 return $this->mParentLanguage;
4450 }
4451
4452 $code = explode( '-', $this->getCode() )[0];
4453 if ( !in_array( $code, LanguageConverter::$languagesWithVariants ) ) {
4454 $this->mParentLanguage = null;
4455 return null;
4456 }
4457 $lang = self::factory( $code );
4458 if ( !$lang->hasVariant( $this->getCode() ) ) {
4459 $this->mParentLanguage = null;
4460 return null;
4461 }
4462
4463 $this->mParentLanguage = $lang;
4464 return $lang;
4465 }
4466
4474 public function equals( Language $lang ) {
4475 return $lang === $this || $lang->getCode() === $this->mCode;
4476 }
4477
4486 public function getCode() {
4487 return $this->mCode;
4488 }
4489
4500 public function getHtmlCode() {
4501 if ( is_null( $this->mHtmlCode ) ) {
4502 $this->mHtmlCode = LanguageCode::bcp47( $this->getCode() );
4503 }
4504 return $this->mHtmlCode;
4505 }
4506
4511 public function setCode( $code ) {
4512 $this->mCode = $code;
4513 // Ensure we don't leave incorrect cached data lying around
4514 $this->mHtmlCode = null;
4515 $this->mParentLanguage = false;
4516 }
4517
4525 public static function getCodeFromFileName( $filename, $prefix = 'Language', $suffix = '.php' ) {
4526 $m = null;
4527 preg_match( '/' . preg_quote( $prefix, '/' ) . '([A-Z][a-z_]+)' .
4528 preg_quote( $suffix, '/' ) . '/', $filename, $m );
4529 if ( !count( $m ) ) {
4530 return false;
4531 }
4532 return str_replace( '_', '-', strtolower( $m[1] ) );
4533 }
4534
4540 public static function classFromCode( $code, $fallback = true ) {
4541 if ( $fallback && $code == 'en' ) {
4542 return 'Language';
4543 } else {
4544 return 'Language' . str_replace( '-', '_', ucfirst( $code ) );
4545 }
4546 }
4547
4556 public static function getFileName( $prefix, $code, $suffix = '.php' ) {
4557 if ( !self::isValidBuiltInCode( $code ) ) {
4558 throw new MWException( "Invalid language code \"$code\"" );
4559 }
4560
4561 return $prefix . str_replace( '-', '_', ucfirst( $code ) ) . $suffix;
4562 }
4563
4568 public static function getMessagesFileName( $code ) {
4569 global $IP;
4570 $file = self::getFileName( "$IP/languages/messages/Messages", $code, '.php' );
4571 Hooks::run( 'Language::getMessagesFileName', [ $code, &$file ] );
4572 return $file;
4573 }
4574
4581 public static function getJsonMessagesFileName( $code ) {
4582 global $IP;
4583
4584 if ( !self::isValidBuiltInCode( $code ) ) {
4585 throw new MWException( "Invalid language code \"$code\"" );
4586 }
4587
4588 return "$IP/languages/i18n/$code.json";
4589 }
4590
4598 public static function getFallbackFor( $code ) {
4599 $fallbacks = self::getFallbacksFor( $code );
4600 if ( $fallbacks ) {
4601 return $fallbacks[0];
4602 }
4603 return false;
4604 }
4605
4616 public static function getFallbacksFor( $code, $mode = self::MESSAGES_FALLBACKS ) {
4617 if ( $code === 'en' || !self::isValidBuiltInCode( $code ) ) {
4618 return [];
4619 }
4620 switch ( $mode ) {
4621 case self::MESSAGES_FALLBACKS:
4622 // For unknown languages, fallbackSequence returns an empty array,
4623 // hardcode fallback to 'en' in that case as English messages are
4624 // always defined.
4625 return self::getLocalisationCache()->getItem( $code, 'fallbackSequence' ) ?: [ 'en' ];
4626 case self::STRICT_FALLBACKS:
4627 // Use this mode when you don't want to fallback to English unless
4628 // explicitly defined, for example when you have language-variant icons
4629 // and an international language-independent fallback.
4630 return self::getLocalisationCache()->getItem( $code, 'originalFallbackSequence' );
4631 default:
4632 throw new MWException( "Invalid fallback mode \"$mode\"" );
4633 }
4634 }
4635
4644 public static function getFallbacksIncludingSiteLanguage( $code ) {
4645 global $wgLanguageCode;
4646
4647 // Usually, we will only store a tiny number of fallback chains, so we
4648 // keep them in static memory.
4649 $cacheKey = "{$code}-{$wgLanguageCode}";
4650
4651 if ( !array_key_exists( $cacheKey, self::$fallbackLanguageCache ) ) {
4652 $fallbacks = self::getFallbacksFor( $code );
4653
4654 // Append the site's fallback chain, including the site language itself
4655 $siteFallbacks = self::getFallbacksFor( $wgLanguageCode );
4656 array_unshift( $siteFallbacks, $wgLanguageCode );
4657
4658 // Eliminate any languages already included in the chain
4659 $siteFallbacks = array_diff( $siteFallbacks, $fallbacks );
4660
4661 self::$fallbackLanguageCache[$cacheKey] = [ $fallbacks, $siteFallbacks ];
4662 }
4663 return self::$fallbackLanguageCache[$cacheKey];
4664 }
4665
4675 public static function getMessagesFor( $code ) {
4676 return self::getLocalisationCache()->getItem( $code, 'messages' );
4677 }
4678
4687 public static function getMessageFor( $key, $code ) {
4688 return self::getLocalisationCache()->getSubitem( $code, 'messages', $key );
4689 }
4690
4699 public static function getMessageKeysFor( $code ) {
4700 return self::getLocalisationCache()->getSubitemList( $code, 'messages' );
4701 }
4702
4707 function fixVariableInNamespace( $talk ) {
4708 if ( strpos( $talk, '$1' ) === false ) {
4709 return $talk;
4710 }
4711
4712 global $wgMetaNamespace;
4713 $talk = str_replace( '$1', $wgMetaNamespace, $talk );
4714
4715 # Allow grammar transformations
4716 # Allowing full message-style parsing would make simple requests
4717 # such as action=raw much more expensive than they need to be.
4718 # This will hopefully cover most cases.
4719 $talk = preg_replace_callback( '/{{grammar:(.*?)\|(.*?)}}/i',
4720 [ $this, 'replaceGrammarInNamespace' ], $talk );
4721 return str_replace( ' ', '_', $talk );
4722 }
4723
4729 return $this->convertGrammar( trim( $m[2] ), trim( $m[1] ) );
4730 }
4731
4742 public function formatExpiry( $expiry, $format = true, $infinity = 'infinity' ) {
4743 static $dbInfinity;
4744 if ( $dbInfinity === null ) {
4745 $dbInfinity = wfGetDB( DB_REPLICA )->getInfinity();
4746 }
4747
4748 if ( $expiry == '' || $expiry === 'infinity' || $expiry == $dbInfinity ) {
4749 return $format === true
4750 ? $this->getMessageFromDB( 'infiniteblock' )
4751 : $infinity;
4752 } else {
4753 return $format === true
4754 ? $this->timeanddate( $expiry, /* User preference timezone */ true )
4755 : wfTimestamp( $format, $expiry );
4756 }
4757 }
4758
4772 function formatTimePeriod( $seconds, $format = [] ) {
4773 if ( !is_array( $format ) ) {
4774 $format = [ 'avoid' => $format ]; // For backwards compatibility
4775 }
4776 if ( !isset( $format['avoid'] ) ) {
4777 $format['avoid'] = false;
4778 }
4779 if ( !isset( $format['noabbrevs'] ) ) {
4780 $format['noabbrevs'] = false;
4781 }
4782 $secondsMsg = wfMessage(
4783 $format['noabbrevs'] ? 'seconds' : 'seconds-abbrev' )->inLanguage( $this );
4784 $minutesMsg = wfMessage(
4785 $format['noabbrevs'] ? 'minutes' : 'minutes-abbrev' )->inLanguage( $this );
4786 $hoursMsg = wfMessage(
4787 $format['noabbrevs'] ? 'hours' : 'hours-abbrev' )->inLanguage( $this );
4788 $daysMsg = wfMessage(
4789 $format['noabbrevs'] ? 'days' : 'days-abbrev' )->inLanguage( $this );
4790
4791 if ( round( $seconds * 10 ) < 100 ) {
4792 $s = $this->formatNum( sprintf( "%.1f", round( $seconds * 10 ) / 10 ) );
4793 $s = $secondsMsg->params( $s )->text();
4794 } elseif ( round( $seconds ) < 60 ) {
4795 $s = $this->formatNum( round( $seconds ) );
4796 $s = $secondsMsg->params( $s )->text();
4797 } elseif ( round( $seconds ) < 3600 ) {
4798 $minutes = floor( $seconds / 60 );
4799 $secondsPart = round( fmod( $seconds, 60 ) );
4800 if ( $secondsPart == 60 ) {
4801 $secondsPart = 0;
4802 $minutes++;
4803 }
4804 $s = $minutesMsg->params( $this->formatNum( $minutes ) )->text();
4805 $s .= ' ';
4806 $s .= $secondsMsg->params( $this->formatNum( $secondsPart ) )->text();
4807 } elseif ( round( $seconds ) <= 2 * 86400 ) {
4808 $hours = floor( $seconds / 3600 );
4809 $minutes = floor( ( $seconds - $hours * 3600 ) / 60 );
4810 $secondsPart = round( $seconds - $hours * 3600 - $minutes * 60 );
4811 if ( $secondsPart == 60 ) {
4812 $secondsPart = 0;
4813 $minutes++;
4814 }
4815 if ( $minutes == 60 ) {
4816 $minutes = 0;
4817 $hours++;
4818 }
4819 $s = $hoursMsg->params( $this->formatNum( $hours ) )->text();
4820 $s .= ' ';
4821 $s .= $minutesMsg->params( $this->formatNum( $minutes ) )->text();
4822 if ( !in_array( $format['avoid'], [ 'avoidseconds', 'avoidminutes' ] ) ) {
4823 $s .= ' ' . $secondsMsg->params( $this->formatNum( $secondsPart ) )->text();
4824 }
4825 } else {
4826 $days = floor( $seconds / 86400 );
4827 if ( $format['avoid'] === 'avoidminutes' ) {
4828 $hours = round( ( $seconds - $days * 86400 ) / 3600 );
4829 if ( $hours == 24 ) {
4830 $hours = 0;
4831 $days++;
4832 }
4833 $s = $daysMsg->params( $this->formatNum( $days ) )->text();
4834 $s .= ' ';
4835 $s .= $hoursMsg->params( $this->formatNum( $hours ) )->text();
4836 } elseif ( $format['avoid'] === 'avoidseconds' ) {
4837 $hours = floor( ( $seconds - $days * 86400 ) / 3600 );
4838 $minutes = round( ( $seconds - $days * 86400 - $hours * 3600 ) / 60 );
4839 if ( $minutes == 60 ) {
4840 $minutes = 0;
4841 $hours++;
4842 }
4843 if ( $hours == 24 ) {
4844 $hours = 0;
4845 $days++;
4846 }
4847 $s = $daysMsg->params( $this->formatNum( $days ) )->text();
4848 $s .= ' ';
4849 $s .= $hoursMsg->params( $this->formatNum( $hours ) )->text();
4850 $s .= ' ';
4851 $s .= $minutesMsg->params( $this->formatNum( $minutes ) )->text();
4852 } else {
4853 $s = $daysMsg->params( $this->formatNum( $days ) )->text();
4854 $s .= ' ';
4855 $s .= $this->formatTimePeriod( $seconds - $days * 86400, $format );
4856 }
4857 }
4858 return $s;
4859 }
4860
4872 function formatBitrate( $bps ) {
4873 return $this->formatComputingNumbers( $bps, 1000, "bitrate-$1bits" );
4874 }
4875
4882 function formatComputingNumbers( $size, $boundary, $messageKey ) {
4883 if ( $size <= 0 ) {
4884 return str_replace( '$1', $this->formatNum( $size ),
4885 $this->getMessageFromDB( str_replace( '$1', '', $messageKey ) )
4886 );
4887 }
4888 $sizes = [ '', 'kilo', 'mega', 'giga', 'tera', 'peta', 'exa', 'zeta', 'yotta' ];
4889 $index = 0;
4890
4891 $maxIndex = count( $sizes ) - 1;
4892 while ( $size >= $boundary && $index < $maxIndex ) {
4893 $index++;
4894 $size /= $boundary;
4895 }
4896
4897 // For small sizes no decimal places necessary
4898 $round = 0;
4899 if ( $index > 1 ) {
4900 // For MB and bigger two decimal places are smarter
4901 $round = 2;
4902 }
4903 $msg = str_replace( '$1', $sizes[$index], $messageKey );
4904
4905 $size = round( $size, $round );
4906 $text = $this->getMessageFromDB( $msg );
4907 return str_replace( '$1', $this->formatNum( $size ), $text );
4908 }
4909
4920 function formatSize( $size ) {
4921 return $this->formatComputingNumbers( $size, 1024, "size-$1bytes" );
4922 }
4923
4933 function specialList( $page, $details, $oppositedm = true ) {
4934 if ( !$details ) {
4935 return $page;
4936 }
4937
4938 $dirmark = ( $oppositedm ? $this->getDirMark( true ) : '' ) . $this->getDirMark();
4939 return $page .
4940 $dirmark .
4941 $this->msg( 'word-separator' )->escaped() .
4942 $this->msg( 'parentheses' )->rawParams( $details )->escaped();
4943 }
4944
4955 public function viewPrevNext( Title $title, $offset, $limit,
4956 array $query = [], $atend = false
4957 ) {
4958 // @todo FIXME: Why on earth this needs one message for the text and another one for tooltip?
4959
4960 # Make 'previous' link
4961 $prev = wfMessage( 'prevn' )->inLanguage( $this )->title( $title )->numParams( $limit )->text();
4962 if ( $offset > 0 ) {
4963 $plink = $this->numLink( $title, max( $offset - $limit, 0 ), $limit,
4964 $query, $prev, 'prevn-title', 'mw-prevlink' );
4965 } else {
4966 $plink = htmlspecialchars( $prev );
4967 }
4968
4969 # Make 'next' link
4970 $next = wfMessage( 'nextn' )->inLanguage( $this )->title( $title )->numParams( $limit )->text();
4971 if ( $atend ) {
4972 $nlink = htmlspecialchars( $next );
4973 } else {
4974 $nlink = $this->numLink( $title, $offset + $limit, $limit,
4975 $query, $next, 'nextn-title', 'mw-nextlink' );
4976 }
4977
4978 # Make links to set number of items per page
4979 $numLinks = [];
4980 foreach ( [ 20, 50, 100, 250, 500 ] as $num ) {
4981 $numLinks[] = $this->numLink( $title, $offset, $num,
4982 $query, $this->formatNum( $num ), 'shown-title', 'mw-numlink' );
4983 }
4984
4985 return wfMessage( 'viewprevnext' )->inLanguage( $this )->title( $title
4986 )->rawParams( $plink, $nlink, $this->pipeList( $numLinks ) )->escaped();
4987 }
4988
5001 private function numLink( Title $title, $offset, $limit, array $query, $link,
5002 $tooltipMsg, $class
5003 ) {
5004 $query = [ 'limit' => $limit, 'offset' => $offset ] + $query;
5005 $tooltip = wfMessage( $tooltipMsg )->inLanguage( $this )->title( $title )
5006 ->numParams( $limit )->text();
5007
5008 return Html::element( 'a', [ 'href' => $title->getLocalURL( $query ),
5009 'title' => $tooltip, 'class' => $class ], $link );
5010 }
5011
5017 public function getConvRuleTitle() {
5018 return $this->mConverter->getConvRuleTitle();
5019 }
5020
5026 public function getCompiledPluralRules() {
5027 $pluralRules = self::$dataCache->getItem( strtolower( $this->mCode ), 'compiledPluralRules' );
5028 $fallbacks = self::getFallbacksFor( $this->mCode );
5029 if ( !$pluralRules ) {
5030 foreach ( $fallbacks as $fallbackCode ) {
5031 $pluralRules = self::$dataCache->getItem( strtolower( $fallbackCode ), 'compiledPluralRules' );
5032 if ( $pluralRules ) {
5033 break;
5034 }
5035 }
5036 }
5037 return $pluralRules;
5038 }
5039
5045 public function getPluralRules() {
5046 $pluralRules = self::$dataCache->getItem( strtolower( $this->mCode ), 'pluralRules' );
5047 $fallbacks = self::getFallbacksFor( $this->mCode );
5048 if ( !$pluralRules ) {
5049 foreach ( $fallbacks as $fallbackCode ) {
5050 $pluralRules = self::$dataCache->getItem( strtolower( $fallbackCode ), 'pluralRules' );
5051 if ( $pluralRules ) {
5052 break;
5053 }
5054 }
5055 }
5056 return $pluralRules;
5057 }
5058
5064 public function getPluralRuleTypes() {
5065 $pluralRuleTypes = self::$dataCache->getItem( strtolower( $this->mCode ), 'pluralRuleTypes' );
5066 $fallbacks = self::getFallbacksFor( $this->mCode );
5067 if ( !$pluralRuleTypes ) {
5068 foreach ( $fallbacks as $fallbackCode ) {
5069 $pluralRuleTypes = self::$dataCache->getItem( strtolower( $fallbackCode ), 'pluralRuleTypes' );
5070 if ( $pluralRuleTypes ) {
5071 break;
5072 }
5073 }
5074 }
5075 return $pluralRuleTypes;
5076 }
5077
5083 public function getPluralRuleIndexNumber( $number ) {
5084 $pluralRules = $this->getCompiledPluralRules();
5085 $form = Evaluator::evaluateCompiled( $number, $pluralRules );
5086 return $form;
5087 }
5088
5097 public function getPluralRuleType( $number ) {
5098 $index = $this->getPluralRuleIndexNumber( $number );
5099 $pluralRuleTypes = $this->getPluralRuleTypes();
5100 if ( isset( $pluralRuleTypes[$index] ) ) {
5101 return $pluralRuleTypes[$index];
5102 } else {
5103 return 'other';
5104 }
5105 }
5106}
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
$wgLanguageCode
Site language code.
$wgExtraGenderNamespaces
Same as above, but for namespaces with gender distinction.
$wgTranslateNumerals
For Hindi and Arabic use local numerals instead of Western style (0-9) numerals in interface.
$wgGrammarForms
Some languages need different word forms, usually for different cases.
$wgExtraNamespaces
Additional namespaces.
$wgAllUnicodeFixes
Set this to always convert certain Unicode sequences to modern ones regardless of the content languag...
$wgDummyLanguageCodes
Functionally the same as $wgExtraLanguageCodes, but deprecated.
$wgNamespaceAliases
Namespace aliases.
$wgMetaNamespace
Name of the project namespace.
$wgMetaNamespaceTalk
Name of the project talk namespace.
$wgLocalTZoffset
Set an offset from UTC in minutes to use for the default timezone setting for anonymous users and new...
$wgLocalisationCacheConf
Localisation cache configuration.
$wgLangObjCacheSize
Language cache size, or really how many languages can we handle simultaneously without degrading to c...
wfWarn( $msg, $callerOffset=1, $level=E_USER_NOTICE)
Send a warning either to the debug log or in a PHP error depending on $wgDevelopmentWarnings.
wfUrlProtocolsWithoutProtRel()
Like wfUrlProtocols(), but excludes '//' from the protocol list.
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
wfLogWarning( $msg, $callerOffset=1, $level=E_USER_WARNING)
Send a warning as a PHP error and the debug log.
wfIsInfinity( $str)
Determine input string is represents as infinity.
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
$fallback
$namespaceAliases
$namespaceNames
$digitGroupingPattern
$minimumGroupingDigits
$IP
Definition WebStart.php:41
A fake language variant converter.
Simple store for keeping values in an associative array for the current process.
Base class for language conversion.
Internationalisation code.
Definition Language.php:35
const STRICT_FALLBACKS
Return a strict fallback chain in getFallbacksFor.
Definition Language.php:93
const MESSAGES_FALLBACKS
Return a fallback chain for messages in getFallbacksFor.
Definition Language.php:87
hasVariants()
Check if this is a language with variants.
initContLang()
Hook which will be called if this is the content language.
Definition Language.php:487
static $mWeekdayMsgs
Definition Language.php:95
getLocalNsIndex( $text)
Get a namespace key by value, case insensitive.
Definition Language.php:661
date( $ts, $adj=false, $format=true, $timecorrection=false)
linkTrail()
A regular expression to match legal word-trailing characters which should be merged onto a link of th...
static $strongDirRegex
Directionality test regex for embedBidi().
Definition Language.php:205
formatExpiry( $expiry, $format=true, $infinity='infinity')
Decode an expiry (block, protection, etc) which has come from the DB.
getPluralRuleTypes()
Get the plural rule types for the language.
static tsToIranian( $ts)
Algorithm by Roozbeh Pournader and Mohammad Toossi to convert Gregorian dates to Iranian dates.
getDefaultVariant()
convertCategoryKey( $key)
array null $namespaceNames
Definition Language.php:68
static getMessageKeysFor( $code)
Get all message keys for a given language.
static isWellFormedLanguageTag( $code, $lenient=false)
Returns true if a language code string is a well-formed language tag according to RFC 5646.
Definition Language.php:332
getVariantname( $code, $usemsg=true)
short names for language variants used for language conversion links.
Definition Language.php:769
static $mHebrewCalendarMonthGenMsgs
Definition Language.php:134
static isValidBuiltInCode( $code)
Returns true if a language code is of a valid form for the purposes of internal customisation of Medi...
Definition Language.php:411
recodeForEdit( $s)
$mParentLanguage
Definition Language.php:62
getWeekdayName( $key)
getHebrewCalendarMonthName( $key)
static getMessageFor( $key, $code)
Get a message for a given language.
gender( $gender, $forms)
Provides an alternative text depending on specified gender.
static tsToYear( $ts, $cName)
Algorithm to convert Gregorian dates to Thai solar dates, Minguo dates or Minguo dates.
truncateForVisual( $string, $length, $ellipsis='...', $adjustLength=true)
Truncate a string to a specified number of characters, appending an optional string (e....
ucwords( $str)
formatNum( $number, $nocommafy=false)
Normally we output all numbers in plain en_US style, that is 293,291.235 for twohundredninetythreetho...
static clearCaches()
Intended for tests that may change configuration in a way that invalidates caches.
Definition Language.php:283
static $pdf
Definition Language.php:190
digitGroupingPattern()
static array $fallbackLanguageCache
Cache for language fallbacks.
Definition Language.php:171
getMonthAbbreviation( $key)
getHebrewCalendarMonthNameGen( $key)
static $lre
Unicode directional formatting characters, for embedBidi()
Definition Language.php:188
getSpecialPageAliases()
Get special page names, as an associative array canonical name => array of valid names,...
truncateInternal( $string, $length, $ellipsis, $adjustLength, $measureLength, $getSubstring)
Internal method used for truncation.
getParsedTitle()
For languages that support multiple variants, the title of an article may be displayed differently in...
static isSupportedLanguage( $code)
Checks whether any localisation is available for that language tag in MediaWiki (MessagesXx....
Definition Language.php:304
hasVariant( $variant)
Strict check if the language has the specific variant.
setCode( $code)
static classFromCode( $code, $fallback=true)
transformUsingPairFile( $file, $string)
Transform a string using serialized data stored in the given file (which must be in the serialized su...
formatDuration( $seconds, array $chosenIntervals=[])
Takes a number of seconds and turns it into a text using values such as hours and minutes.
getMessage( $key)
getHijriCalendarMonthName( $key)
const ALL
Return all known languages in fetchLanguageName(s).
Definition Language.php:46
pipeList(array $list)
Same as commaList, but separate it with the pipe instead.
internalUserTimeAndDate( $type, $ts, User $user, array $options)
Internal helper function for userDate(), userTime() and userTimeAndDate()
ucwordbreaksCallbackAscii( $matches)
lc( $str, $first=false)
fallback8bitEncoding()
static LocalisationCache $dataCache
Definition Language.php:79
getPluralRuleType( $number)
Find the plural rule type appropriate for the given number For example, if the language is set to Ara...
normalize( $s)
Convert a UTF-8 string to normal form C.
userAdjust( $ts, $tz=false)
Used by date() and time() to adjust the time output.
getArrow( $direction='forwards')
An arrow, depending on the language direction.
equals(Language $lang)
Compare with an other language object.
ucwordbreaksCallbackMB( $matches)
convert( $text)
convert text to different variants of a language.
getParentLanguage()
Get the "parent" language which has a converter to convert a "compatible" language (in another varian...
getGenderNsText( $index, $gender)
Returns gender-dependent namespace alias if available.
Definition Language.php:622
translateBlockExpiry( $str, User $user=null, $now=0)
truncateHtml( $text, $length, $ellipsis='...')
Truncate a string of valid HTML to a specified length in bytes, appending an optional string (e....
getAllMessages()
getGrammarTransformations()
Get the grammar transformations data for the language.
__destruct()
Reduce memory usage.
Definition Language.php:477
getFormattedNsText( $index)
A convenience function that returns the same thing as getNsText() except with '_' changed to ' ',...
Definition Language.php:609
getURLVariant()
getMessageFromDB( $msg)
Get a message from the MediaWiki namespace.
Definition Language.php:956
capitalizeAllNouns()
getDirMark( $opposite=false)
A hidden direction mark (LRM or RLM), depending on the language direction.
truncate_skip(&$ret, $text, $search, $start, $len=null)
truncateHtml() helper function like strcspn() but adds the skipped chars to $ret
__construct()
Definition Language.php:463
getFormattedNamespaces()
A convenience function that returns getNamespaces() with spaces instead of underscores in values.
Definition Language.php:572
getExtraHashOptions()
returns language specific options used by User::getPageRenderHash() for example, the preferred langua...
$transformData
ReplacementArray object caches.
Definition Language.php:74
getMonthNamesArray()
Definition Language.php:981
userTimeAndDate( $ts, User $user, array $options=[])
Get the formatted date and time for the given timestamp and formatted for the given user.
getMonthNameGen( $key)
Definition Language.php:993
setNamespaces(array $namespaces)
Arbitrarily set all of the namespace names at once.
Definition Language.php:552
static $GREG_DAYS
commafy( $number)
Adds commas to a given number.
truncate_endBracket(&$tag, $tagType, $lastCh, &$openTags)
truncateHtml() helper function (a) push or pop $tag from $openTags as needed (b) clear $tag value
firstChar( $s)
Get the first character of a string.
doMagicHook()
Run the LanguageGetMagic hook once.
$mExtendedSpecialPageAliases
Definition Language.php:65
getGrammarForms()
Get the grammar forms for the content language.
static getFallbacksFor( $code, $mode=self::MESSAGES_FALLBACKS)
Get the ordered list of fallback languages.
getNamespaces()
Returns an array of localised namespaces indexed by their numbers.
Definition Language.php:512
static convertDoubleWidth( $string)
convert double-width roman characters to single-width.
static tsToHebrew( $ts)
Converting Gregorian dates to Hebrew dates.
specialList( $page, $details, $oppositedm=true)
Make a list item, used by various special pages.
getHtmlCode()
Get the code in BCP 47 format which we can use inside of html lang="" tags.
needsGenderDistinction()
Whether this language uses gender-dependent namespace aliases.
Definition Language.php:637
static array $durationIntervals
Definition Language.php:153
getNsIndex( $text)
Get a namespace key by value, case insensitive.
Definition Language.php:752
linkPrefixCharset()
A regular expression character set to match legal word-prefixing characters which should be merged on...
static $IRANIAN_DAYS
listToText(array $list)
Take a list of strings and build a locale-friendly comma-separated list, using the local comma-separa...
LanguageConverter $mConverter
Definition Language.php:58
caseFold( $s)
Return a case-folded representation of $s.
static hebrewNumeral( $num)
Hebrew Gematria number formatting up to 9999.
getCode()
Get the internal language code for this language object.
static $mIranianCalendarMonthMsgs
Definition Language.php:119
uc( $str, $first=false)
Convert a string to uppercase.
static $mHebrewCalendarMonthMsgs
Definition Language.php:126
normalizeForSearch( $string)
Some languages have special punctuation need to be normalized.
truncateForDatabase( $string, $length, $ellipsis='...', $adjustLength=true)
Truncate a string to a specified length in bytes, appending an optional string (e....
static newFromCode( $code, $fallback=false)
Create a language object for a given language code.
Definition Language.php:239
embedBidi( $text='')
Wraps argument with unicode control characters for directionality safety.
getMonthAbbreviationsArray()
static fetchLanguageName( $code, $inLanguage=self::AS_AUTONYMS, $include=self::ALL)
Definition Language.php:940
getMagic( $mw)
Fill a MagicWord object with data from here.
viewPrevNext(Title $title, $offset, $limit, array $query=[], $atend=false)
Generate (prev x| next x) (20|50|100...) type links for paging.
resetNamespaces()
Resets all of the namespace caches.
Definition Language.php:560
getMagicWords()
Get all magic words from cache.
minimumGroupingDigits()
emphasize( $text)
Italic is unsuitable for some languages.
timeanddate( $ts, $adj=false, $format=true, $timecorrection=false)
static getFallbacksIncludingSiteLanguage( $code)
Get the ordered list of fallback languages, ending with the fallback language chain for the site lang...
convertForSearchResult( $termsArray)
sprintfDate( $format, $ts, DateTimeZone $zone=null, &$ttl='unused')
This is a workalike of PHP's date() function, but with better internationalisation,...
static $mMonthGenMsgs
Definition Language.php:109
getPreferredVariant()
getDirMarkEntity( $opposite=false)
A hidden direction mark (LRM or RLM), depending on the language direction.
convertGrammar( $word, $case)
Grammatical transformations, needed for inflected languages Invoked by putting {{grammar:case|word}} ...
static HashBagOStuff null $languageNameCache
Cache for language names.
Definition Language.php:183
autoConvert( $text, $variant=false)
convert text to a variant
getIranianCalendarMonthName( $key)
getHumanTimestampInternal(MWTimestamp $ts, MWTimestamp $relativeTo, User $user)
Convert an MWTimestamp into a pretty human-readable timestamp using the given user preferences and re...
getHumanTimestamp(MWTimestamp $time, MWTimestamp $relativeTo=null, User $user=null)
Get the timestamp in a human-friendly relative format, e.g., "3 days ago".
convertNamespace( $ns, $variant=null)
Convert a namespace index to a string in the preferred variant.
preConvertPlural($forms, $count)
Checks that convertPlural was given an array and pads it to requested amount of forms by copying the ...
formatTimePeriod( $seconds, $format=[])
Formats a time given in seconds into a string representation of that time.
static getFallbackFor( $code)
Get the first fallback for a given language.
getWeekdayAbbreviation( $key)
isRTL()
For right-to-left language support.
separatorTransformTable()
segmentForDiff( $text)
languages like Chinese need to be segmented in order for the diff to be of any use
markNoConversion( $text, $noParse=false)
Prepare external link text for conversion.
static $mMonthAbbrevMsgs
Definition Language.php:114
replaceGrammarInNamespace( $m)
getBookstoreList()
Exports $wgBookstoreListEn.
Definition Language.php:502
getDir()
Return the correct HTML 'dir' attribute value for this language.
parseFormattedNumber( $number)
static factory( $code)
Get a cached or new language object for a given language code.
Definition Language.php:214
$mMagicExtensions
Definition Language.php:61
addMagicWordsByLang( $newWords)
Add magic words to the extension array.
linkPrefixExtension()
To allow "foo[[bar]]" to extend the link over the whole word "foobar".
getFallbackLanguages()
Definition Language.php:494
userDate( $ts, User $user, array $options=[])
Get the formatted date for the given timestamp and formatted for the given user.
recodeInput( $s)
getMonthName( $key)
Definition Language.php:974
getPluralRules()
Get the plural rules for the language.
autoConvertToAllVariants( $text)
convert text to all supported variants
const AS_AUTONYMS
Return autonyms in fetchLanguageName(s).
Definition Language.php:40
iconv( $in, $out, $string)
formatSize( $size)
Format a size in bytes for output, using an appropriate unit (B, KB, MB, GB, TB, PB,...
semicolonList(array $list)
Take a list of strings and build a locale-friendly semicolon-separated list, using the local semicolo...
fixVariableInNamespace( $talk)
static $rle
Definition Language.php:189
static insertSpace( $string, $pattern)
commaList(array $list)
Take a list of strings and build a locale-friendly comma-separated list, using the local comma-separa...
dateFormat( $usePrefs=true)
This is meant to be used by time(), date(), and timeanddate() to get the date preference they're supp...
static getMessagesFor( $code)
Get all messages for a given language WARNING: this may take a long time.
findVariantLink(&$link, &$nt, $ignoreOtherCond=false)
If a language supports multiple variants, it is possible that non-existing link in one variant actual...
const SUPPORTED
Return in fetchLanguageName(s) only the languages for which we have at least some localisation.
Definition Language.php:53
handleExplicitPluralForms( $count, array $forms)
Handles explicit plural forms for Language::convertPlural()
static dateTimeObjFormat(&$dateTimeObj, $ts, $zone, $code)
Pass through result from $dateTimeObj->format()
alignEnd()
Return 'right' or 'left' as appropriate alignment for line-end for this language's text direction.
getDateFormatString( $type, $pref)
Get a format string for a given type and preference.
convertPlural( $count, $forms)
Plural form transformations, needed for some languages.
getDurationIntervals( $seconds, array $chosenIntervals=[])
Takes a number of seconds and returns an array with a set of corresponding intervals.
static getLocalisationCache()
Get the LocalisationCache instance.
Definition Language.php:454
static $mLangObjCache
Definition Language.php:81
static getFileName( $prefix, $code, $suffix='.php')
Get the name of a file for a certain language code.
formatNumNoSeparators( $number)
Front-end for non-commafied formatNum.
static isKnownLanguageTag( $tag)
Returns true if a language code is an IETF tag known to MediaWiki.
Definition Language.php:433
msg( $msg)
Get message object in this language.
Definition Language.php:966
removeBadCharLast( $string)
Remove bytes that represent an incomplete Unicode character at the end of string (e....
static getJsonMessagesFileName( $code)
getNamespaceAliases()
Definition Language.php:670
ucwordbreaks( $str)
capitalize words at word breaks
ucfirst( $str)
Make a string's first character uppercase.
static strongDirFromContent( $text='')
Gets directionality of the first strongly directional codepoint, for embedBidi()
userTime( $ts, User $user, array $options=[])
Get the formatted time for the given timestamp and formatted for the given user.
getNsText( $index)
Get a namespace value by key.
Definition Language.php:591
hasWordBreaks()
Most writing systems use whitespace to break up words.
getConvRuleTitle()
Get the conversion rule title, if any.
formatBitrate( $bps)
Format a bitrate for output, using an appropriate unit (bps, kbps, Mbps, Gbps, Tbps,...
convertTitle( $title)
Convert a Title object to a string in the preferred variant.
numLink(Title $title, $offset, $limit, array $query, $link, $tooltipMsg, $class)
Helper function for viewPrevNext() that generates links.
static getMessagesFileName( $code)
getNamespaceIds()
Definition Language.php:722
static MapCacheLRU null $grammarTransformations
Cache for grammar rules data.
Definition Language.php:177
getVariants()
Get the list of variants supported by this language see sample implementation in LanguageZh....
time( $ts, $adj=false, $format=true, $timecorrection=false)
truncate( $string, $length, $ellipsis='...', $adjustLength=true)
This method is deprecated since 1.31 and kept as alias for truncateForDatabase, which has replaced it...
static getCodeFromFileName( $filename, $prefix='Language', $suffix='.php')
Get the language code from a file name.
getDefaultDateFormat()
Definition Language.php:800
removeBadCharFirst( $string)
Remove bytes that represent an incomplete Unicode character at the start of string (e....
static $mHijriCalendarMonthMsgs
Definition Language.php:142
static $mWeekdayAbbrevMsgs
Definition Language.php:100
unsegmentForDiff( $text)
and unsegment to show the result
formatComputingNumbers( $size, $boundary, $messageKey)
ucwordsCallbackMB( $matches)
isMultibyte( $str)
lcfirst( $str)
getPluralRuleIndexNumber( $number)
Find the index number of the plural rule appropriate for the given number.
updateConversionTable(Title $title)
Refresh the cache of conversion tables when MediaWiki:Conversiontable* is updated.
$mMagicHookDone
Definition Language.php:61
segmentByWord( $string)
Some languages such as Chinese require word segmentation, Specify such segmentation when overridden i...
convertHtml( $text, $isTitle=false)
Perform output conversion on a string, and encode for safe HTML output.
checkTitleEncoding( $s)
getConverter()
Return the LanguageConverter used in the Language.
static tsToHijri( $ts)
Converting Gregorian dates to Hijri dates.
digitTransformTable()
static isValidCode( $code)
Returns true if a language code string is of a valid form, whether or not it exists.
Definition Language.php:386
static $mMonthMsgs
Definition Language.php:104
$dateFormatStrings
Definition Language.php:64
static romanNumeral( $num)
Roman number formatting up to 10000.
alignStart()
Return 'left' or 'right' as appropriate alignment for line-start for this language's text direction.
static hebrewYearStart( $year)
This calculates the Hebrew year start, as days since 1 September.
getCompiledPluralRules()
Get the compiled plural rules for the language.
Class for caching the contents of localisation files, Messages*.php and *.i18n.php.
MediaWiki exception.
Library for creating and parsing MW-style timestamps.
Handles a simple LRU key/value map with a maximum number of entries.
static getTitleInvalidRegex()
Returns a simple regex that will match on characters and sequences invalid in titles.
Wrapper around strtr() that holds replacements.
static getSuggestedDurations(Language $lang=null, $includeOther=true)
Get an array of suggested block durations from MediaWiki:Ipboptions.
static isUtf8( $value)
Test whether a string is valid UTF-8.
Represents a title within MediaWiki.
Definition Title.php:39
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition User.php:47
static getDefaultOption( $opt)
Get a given default option value.
Definition User.php:1818
=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
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
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
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
const NS_USER
Definition Defines.php:66
const NS_PROJECT_TALK
Definition Defines.php:69
const NS_USER_TALK
Definition Defines.php:67
const NS_PROJECT
Definition Defines.php:68
see documentation in includes Linker php for Linker::makeImageLink & $time
Definition hooks.txt:1841
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:181
namespace and then decline to actually register it & $namespaces
Definition hooks.txt:964
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped & $options
Definition hooks.txt:2050
namespace and then decline to actually register it file or subcat img or subcat $title
Definition hooks.txt:994
Using a hook running we can avoid having all this option specific stuff in our mainline code Using the function We ve cleaned up the code here by removing clumps of infrequently used code and moving them off somewhere else It s much easier for someone working with this code to see what s _really_ going and make changes or fix bugs In we can take all the code that deals with the little used title reversing options(say) and put it in one place. Instead of having little title-reversing if-blocks spread all over the codebase in showAnArticle
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:895
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:2055
if the prop value should be in the metadata multi language array format
Definition hooks.txt:1690
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:2054
either a unescaped string or a HtmlArmor object after in associative array form externallinks including delete and has completed for all link tables whether this was an auto creation use $formDescriptor instead default is conds Array Extra conditions for the No matching items in log is displayed if loglist is empty msgKey Array If you want a nice box with a set this to the key of the message First element is the message additional optional elements are parameters for the key that are processed with wfMessage() -> params() ->parseAsBlock() - offset Set to overwrite offset parameter in $wgRequest set to '' to unset offset - wrap String Wrap the message in html(usually something like "&lt;div ...>$1&lt;/div>"). - flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException':Called before an exception(or PHP error) is logged. This is meant for integration with external error aggregation services
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:894
usually copyright or history_copyright This message must be in HTML not wikitext & $link
Definition hooks.txt:3106
Allows to change the fields on the form that will be generated $name
Definition hooks.txt:302
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:1656
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:247
returning false will NOT prevent logging $e
Definition hooks.txt:2226
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
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:37
$cache
Definition mcc.php:33
The wiki should then use memcached to cache various data To use multiple just add more items to the array To increase the weight of a make its entry a array("192.168.0.1:11211", 2))
A helper class for throttling authentication attempts.
const DB_REPLICA
Definition defines.php:25
if(!isset( $args[0])) $lang