MediaWiki REL1_28
Language.php
Go to the documentation of this file.
1<?php
29use CLDRPluralRuleParser\Evaluator;
30
35class Language {
40
41 public $mVariants, $mCode, $mLoaded = false;
42 public $mMagicExtensions = [], $mMagicHookDone = false;
43 private $mHtmlCode = null, $mParentLanguage = false;
44
45 public $dateFormatStrings = [];
47
49
53 public $transformData = [];
54
58 static public $dataCache;
59
60 static public $mLangObjCache = [];
61
62 static public $mWeekdayMsgs = [
63 'sunday', 'monday', 'tuesday', 'wednesday', 'thursday',
64 'friday', 'saturday'
65 ];
66
67 static public $mWeekdayAbbrevMsgs = [
68 'sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'
69 ];
70
71 static public $mMonthMsgs = [
72 'january', 'february', 'march', 'april', 'may_long', 'june',
73 'july', 'august', 'september', 'october', 'november',
74 'december'
75 ];
76 static public $mMonthGenMsgs = [
77 'january-gen', 'february-gen', 'march-gen', 'april-gen', 'may-gen', 'june-gen',
78 'july-gen', 'august-gen', 'september-gen', 'october-gen', 'november-gen',
79 'december-gen'
80 ];
81 static public $mMonthAbbrevMsgs = [
82 'jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug',
83 'sep', 'oct', 'nov', 'dec'
84 ];
85
87 'iranian-calendar-m1', 'iranian-calendar-m2', 'iranian-calendar-m3',
88 'iranian-calendar-m4', 'iranian-calendar-m5', 'iranian-calendar-m6',
89 'iranian-calendar-m7', 'iranian-calendar-m8', 'iranian-calendar-m9',
90 'iranian-calendar-m10', 'iranian-calendar-m11', 'iranian-calendar-m12'
91 ];
92
93 static public $mHebrewCalendarMonthMsgs = [
94 'hebrew-calendar-m1', 'hebrew-calendar-m2', 'hebrew-calendar-m3',
95 'hebrew-calendar-m4', 'hebrew-calendar-m5', 'hebrew-calendar-m6',
96 'hebrew-calendar-m7', 'hebrew-calendar-m8', 'hebrew-calendar-m9',
97 'hebrew-calendar-m10', 'hebrew-calendar-m11', 'hebrew-calendar-m12',
98 'hebrew-calendar-m6a', 'hebrew-calendar-m6b'
99 ];
100
102 'hebrew-calendar-m1-gen', 'hebrew-calendar-m2-gen', 'hebrew-calendar-m3-gen',
103 'hebrew-calendar-m4-gen', 'hebrew-calendar-m5-gen', 'hebrew-calendar-m6-gen',
104 'hebrew-calendar-m7-gen', 'hebrew-calendar-m8-gen', 'hebrew-calendar-m9-gen',
105 'hebrew-calendar-m10-gen', 'hebrew-calendar-m11-gen', 'hebrew-calendar-m12-gen',
106 'hebrew-calendar-m6a-gen', 'hebrew-calendar-m6b-gen'
107 ];
108
109 static public $mHijriCalendarMonthMsgs = [
110 'hijri-calendar-m1', 'hijri-calendar-m2', 'hijri-calendar-m3',
111 'hijri-calendar-m4', 'hijri-calendar-m5', 'hijri-calendar-m6',
112 'hijri-calendar-m7', 'hijri-calendar-m8', 'hijri-calendar-m9',
113 'hijri-calendar-m10', 'hijri-calendar-m11', 'hijri-calendar-m12'
114 ];
115
120 static public $durationIntervals = [
121 'millennia' => 31556952000,
122 'centuries' => 3155695200,
123 'decades' => 315569520,
124 'years' => 31556952, // 86400 * ( 365 + ( 24 * 3 + 25 ) / 400 )
125 'weeks' => 604800,
126 'days' => 86400,
127 'hours' => 3600,
128 'minutes' => 60,
129 'seconds' => 1,
130 ];
131
138 static private $fallbackLanguageCache = [];
139
145
150 static private $languageNameCache;
151
155 static private $lre = "\xE2\x80\xAA"; // U+202A LEFT-TO-RIGHT EMBEDDING
156 static private $rle = "\xE2\x80\xAB"; // U+202B RIGHT-TO-LEFT EMBEDDING
157 static private $pdf = "\xE2\x80\xAC"; // U+202C POP DIRECTIONAL FORMATTING
158
170 // @codingStandardsIgnoreStart
171 // @codeCoverageIgnoreStart
172 static private $strongDirRegex = '/(?:([\x{41}-\x{5a}\x{61}-\x{7a}\x{aa}\x{b5}\x{ba}\x{c0}-\x{d6}\x{d8}-\x{f6}\x{f8}-\x{2b8}\x{2bb}-\x{2c1}\x{2d0}\x{2d1}\x{2e0}-\x{2e4}\x{2ee}\x{370}-\x{373}\x{376}\x{377}\x{37a}-\x{37d}\x{37f}\x{386}\x{388}-\x{38a}\x{38c}\x{38e}-\x{3a1}\x{3a3}-\x{3f5}\x{3f7}-\x{482}\x{48a}-\x{52f}\x{531}-\x{556}\x{559}-\x{55f}\x{561}-\x{587}\x{589}\x{903}-\x{939}\x{93b}\x{93d}-\x{940}\x{949}-\x{94c}\x{94e}-\x{950}\x{958}-\x{961}\x{964}-\x{980}\x{982}\x{983}\x{985}-\x{98c}\x{98f}\x{990}\x{993}-\x{9a8}\x{9aa}-\x{9b0}\x{9b2}\x{9b6}-\x{9b9}\x{9bd}-\x{9c0}\x{9c7}\x{9c8}\x{9cb}\x{9cc}\x{9ce}\x{9d7}\x{9dc}\x{9dd}\x{9df}-\x{9e1}\x{9e6}-\x{9f1}\x{9f4}-\x{9fa}\x{a03}\x{a05}-\x{a0a}\x{a0f}\x{a10}\x{a13}-\x{a28}\x{a2a}-\x{a30}\x{a32}\x{a33}\x{a35}\x{a36}\x{a38}\x{a39}\x{a3e}-\x{a40}\x{a59}-\x{a5c}\x{a5e}\x{a66}-\x{a6f}\x{a72}-\x{a74}\x{a83}\x{a85}-\x{a8d}\x{a8f}-\x{a91}\x{a93}-\x{aa8}\x{aaa}-\x{ab0}\x{ab2}\x{ab3}\x{ab5}-\x{ab9}\x{abd}-\x{ac0}\x{ac9}\x{acb}\x{acc}\x{ad0}\x{ae0}\x{ae1}\x{ae6}-\x{af0}\x{af9}\x{b02}\x{b03}\x{b05}-\x{b0c}\x{b0f}\x{b10}\x{b13}-\x{b28}\x{b2a}-\x{b30}\x{b32}\x{b33}\x{b35}-\x{b39}\x{b3d}\x{b3e}\x{b40}\x{b47}\x{b48}\x{b4b}\x{b4c}\x{b57}\x{b5c}\x{b5d}\x{b5f}-\x{b61}\x{b66}-\x{b77}\x{b83}\x{b85}-\x{b8a}\x{b8e}-\x{b90}\x{b92}-\x{b95}\x{b99}\x{b9a}\x{b9c}\x{b9e}\x{b9f}\x{ba3}\x{ba4}\x{ba8}-\x{baa}\x{bae}-\x{bb9}\x{bbe}\x{bbf}\x{bc1}\x{bc2}\x{bc6}-\x{bc8}\x{bca}-\x{bcc}\x{bd0}\x{bd7}\x{be6}-\x{bf2}\x{c01}-\x{c03}\x{c05}-\x{c0c}\x{c0e}-\x{c10}\x{c12}-\x{c28}\x{c2a}-\x{c39}\x{c3d}\x{c41}-\x{c44}\x{c58}-\x{c5a}\x{c60}\x{c61}\x{c66}-\x{c6f}\x{c7f}\x{c82}\x{c83}\x{c85}-\x{c8c}\x{c8e}-\x{c90}\x{c92}-\x{ca8}\x{caa}-\x{cb3}\x{cb5}-\x{cb9}\x{cbd}-\x{cc4}\x{cc6}-\x{cc8}\x{cca}\x{ccb}\x{cd5}\x{cd6}\x{cde}\x{ce0}\x{ce1}\x{ce6}-\x{cef}\x{cf1}\x{cf2}\x{d02}\x{d03}\x{d05}-\x{d0c}\x{d0e}-\x{d10}\x{d12}-\x{d3a}\x{d3d}-\x{d40}\x{d46}-\x{d48}\x{d4a}-\x{d4c}\x{d4e}\x{d57}\x{d5f}-\x{d61}\x{d66}-\x{d75}\x{d79}-\x{d7f}\x{d82}\x{d83}\x{d85}-\x{d96}\x{d9a}-\x{db1}\x{db3}-\x{dbb}\x{dbd}\x{dc0}-\x{dc6}\x{dcf}-\x{dd1}\x{dd8}-\x{ddf}\x{de6}-\x{def}\x{df2}-\x{df4}\x{e01}-\x{e30}\x{e32}\x{e33}\x{e40}-\x{e46}\x{e4f}-\x{e5b}\x{e81}\x{e82}\x{e84}\x{e87}\x{e88}\x{e8a}\x{e8d}\x{e94}-\x{e97}\x{e99}-\x{e9f}\x{ea1}-\x{ea3}\x{ea5}\x{ea7}\x{eaa}\x{eab}\x{ead}-\x{eb0}\x{eb2}\x{eb3}\x{ebd}\x{ec0}-\x{ec4}\x{ec6}\x{ed0}-\x{ed9}\x{edc}-\x{edf}\x{f00}-\x{f17}\x{f1a}-\x{f34}\x{f36}\x{f38}\x{f3e}-\x{f47}\x{f49}-\x{f6c}\x{f7f}\x{f85}\x{f88}-\x{f8c}\x{fbe}-\x{fc5}\x{fc7}-\x{fcc}\x{fce}-\x{fda}\x{1000}-\x{102c}\x{1031}\x{1038}\x{103b}\x{103c}\x{103f}-\x{1057}\x{105a}-\x{105d}\x{1061}-\x{1070}\x{1075}-\x{1081}\x{1083}\x{1084}\x{1087}-\x{108c}\x{108e}-\x{109c}\x{109e}-\x{10c5}\x{10c7}\x{10cd}\x{10d0}-\x{1248}\x{124a}-\x{124d}\x{1250}-\x{1256}\x{1258}\x{125a}-\x{125d}\x{1260}-\x{1288}\x{128a}-\x{128d}\x{1290}-\x{12b0}\x{12b2}-\x{12b5}\x{12b8}-\x{12be}\x{12c0}\x{12c2}-\x{12c5}\x{12c8}-\x{12d6}\x{12d8}-\x{1310}\x{1312}-\x{1315}\x{1318}-\x{135a}\x{1360}-\x{137c}\x{1380}-\x{138f}\x{13a0}-\x{13f5}\x{13f8}-\x{13fd}\x{1401}-\x{167f}\x{1681}-\x{169a}\x{16a0}-\x{16f8}\x{1700}-\x{170c}\x{170e}-\x{1711}\x{1720}-\x{1731}\x{1735}\x{1736}\x{1740}-\x{1751}\x{1760}-\x{176c}\x{176e}-\x{1770}\x{1780}-\x{17b3}\x{17b6}\x{17be}-\x{17c5}\x{17c7}\x{17c8}\x{17d4}-\x{17da}\x{17dc}\x{17e0}-\x{17e9}\x{1810}-\x{1819}\x{1820}-\x{1877}\x{1880}-\x{18a8}\x{18aa}\x{18b0}-\x{18f5}\x{1900}-\x{191e}\x{1923}-\x{1926}\x{1929}-\x{192b}\x{1930}\x{1931}\x{1933}-\x{1938}\x{1946}-\x{196d}\x{1970}-\x{1974}\x{1980}-\x{19ab}\x{19b0}-\x{19c9}\x{19d0}-\x{19da}\x{1a00}-\x{1a16}\x{1a19}\x{1a1a}\x{1a1e}-\x{1a55}\x{1a57}\x{1a61}\x{1a63}\x{1a64}\x{1a6d}-\x{1a72}\x{1a80}-\x{1a89}\x{1a90}-\x{1a99}\x{1aa0}-\x{1aad}\x{1b04}-\x{1b33}\x{1b35}\x{1b3b}\x{1b3d}-\x{1b41}\x{1b43}-\x{1b4b}\x{1b50}-\x{1b6a}\x{1b74}-\x{1b7c}\x{1b82}-\x{1ba1}\x{1ba6}\x{1ba7}\x{1baa}\x{1bae}-\x{1be5}\x{1be7}\x{1bea}-\x{1bec}\x{1bee}\x{1bf2}\x{1bf3}\x{1bfc}-\x{1c2b}\x{1c34}\x{1c35}\x{1c3b}-\x{1c49}\x{1c4d}-\x{1c7f}\x{1cc0}-\x{1cc7}\x{1cd3}\x{1ce1}\x{1ce9}-\x{1cec}\x{1cee}-\x{1cf3}\x{1cf5}\x{1cf6}\x{1d00}-\x{1dbf}\x{1e00}-\x{1f15}\x{1f18}-\x{1f1d}\x{1f20}-\x{1f45}\x{1f48}-\x{1f4d}\x{1f50}-\x{1f57}\x{1f59}\x{1f5b}\x{1f5d}\x{1f5f}-\x{1f7d}\x{1f80}-\x{1fb4}\x{1fb6}-\x{1fbc}\x{1fbe}\x{1fc2}-\x{1fc4}\x{1fc6}-\x{1fcc}\x{1fd0}-\x{1fd3}\x{1fd6}-\x{1fdb}\x{1fe0}-\x{1fec}\x{1ff2}-\x{1ff4}\x{1ff6}-\x{1ffc}\x{200e}\x{2071}\x{207f}\x{2090}-\x{209c}\x{2102}\x{2107}\x{210a}-\x{2113}\x{2115}\x{2119}-\x{211d}\x{2124}\x{2126}\x{2128}\x{212a}-\x{212d}\x{212f}-\x{2139}\x{213c}-\x{213f}\x{2145}-\x{2149}\x{214e}\x{214f}\x{2160}-\x{2188}\x{2336}-\x{237a}\x{2395}\x{249c}-\x{24e9}\x{26ac}\x{2800}-\x{28ff}\x{2c00}-\x{2c2e}\x{2c30}-\x{2c5e}\x{2c60}-\x{2ce4}\x{2ceb}-\x{2cee}\x{2cf2}\x{2cf3}\x{2d00}-\x{2d25}\x{2d27}\x{2d2d}\x{2d30}-\x{2d67}\x{2d6f}\x{2d70}\x{2d80}-\x{2d96}\x{2da0}-\x{2da6}\x{2da8}-\x{2dae}\x{2db0}-\x{2db6}\x{2db8}-\x{2dbe}\x{2dc0}-\x{2dc6}\x{2dc8}-\x{2dce}\x{2dd0}-\x{2dd6}\x{2dd8}-\x{2dde}\x{3005}-\x{3007}\x{3021}-\x{3029}\x{302e}\x{302f}\x{3031}-\x{3035}\x{3038}-\x{303c}\x{3041}-\x{3096}\x{309d}-\x{309f}\x{30a1}-\x{30fa}\x{30fc}-\x{30ff}\x{3105}-\x{312d}\x{3131}-\x{318e}\x{3190}-\x{31ba}\x{31f0}-\x{321c}\x{3220}-\x{324f}\x{3260}-\x{327b}\x{327f}-\x{32b0}\x{32c0}-\x{32cb}\x{32d0}-\x{32fe}\x{3300}-\x{3376}\x{337b}-\x{33dd}\x{33e0}-\x{33fe}\x{3400}-\x{4db5}\x{4e00}-\x{9fd5}\x{a000}-\x{a48c}\x{a4d0}-\x{a60c}\x{a610}-\x{a62b}\x{a640}-\x{a66e}\x{a680}-\x{a69d}\x{a6a0}-\x{a6ef}\x{a6f2}-\x{a6f7}\x{a722}-\x{a787}\x{a789}-\x{a7ad}\x{a7b0}-\x{a7b7}\x{a7f7}-\x{a801}\x{a803}-\x{a805}\x{a807}-\x{a80a}\x{a80c}-\x{a824}\x{a827}\x{a830}-\x{a837}\x{a840}-\x{a873}\x{a880}-\x{a8c3}\x{a8ce}-\x{a8d9}\x{a8f2}-\x{a8fd}\x{a900}-\x{a925}\x{a92e}-\x{a946}\x{a952}\x{a953}\x{a95f}-\x{a97c}\x{a983}-\x{a9b2}\x{a9b4}\x{a9b5}\x{a9ba}\x{a9bb}\x{a9bd}-\x{a9cd}\x{a9cf}-\x{a9d9}\x{a9de}-\x{a9e4}\x{a9e6}-\x{a9fe}\x{aa00}-\x{aa28}\x{aa2f}\x{aa30}\x{aa33}\x{aa34}\x{aa40}-\x{aa42}\x{aa44}-\x{aa4b}\x{aa4d}\x{aa50}-\x{aa59}\x{aa5c}-\x{aa7b}\x{aa7d}-\x{aaaf}\x{aab1}\x{aab5}\x{aab6}\x{aab9}-\x{aabd}\x{aac0}\x{aac2}\x{aadb}-\x{aaeb}\x{aaee}-\x{aaf5}\x{ab01}-\x{ab06}\x{ab09}-\x{ab0e}\x{ab11}-\x{ab16}\x{ab20}-\x{ab26}\x{ab28}-\x{ab2e}\x{ab30}-\x{ab65}\x{ab70}-\x{abe4}\x{abe6}\x{abe7}\x{abe9}-\x{abec}\x{abf0}-\x{abf9}\x{ac00}-\x{d7a3}\x{d7b0}-\x{d7c6}\x{d7cb}-\x{d7fb}\x{e000}-\x{fa6d}\x{fa70}-\x{fad9}\x{fb00}-\x{fb06}\x{fb13}-\x{fb17}\x{ff21}-\x{ff3a}\x{ff41}-\x{ff5a}\x{ff66}-\x{ffbe}\x{ffc2}-\x{ffc7}\x{ffca}-\x{ffcf}\x{ffd2}-\x{ffd7}\x{ffda}-\x{ffdc}\x{10000}-\x{1000b}\x{1000d}-\x{10026}\x{10028}-\x{1003a}\x{1003c}\x{1003d}\x{1003f}-\x{1004d}\x{10050}-\x{1005d}\x{10080}-\x{100fa}\x{10100}\x{10102}\x{10107}-\x{10133}\x{10137}-\x{1013f}\x{101d0}-\x{101fc}\x{10280}-\x{1029c}\x{102a0}-\x{102d0}\x{10300}-\x{10323}\x{10330}-\x{1034a}\x{10350}-\x{10375}\x{10380}-\x{1039d}\x{1039f}-\x{103c3}\x{103c8}-\x{103d5}\x{10400}-\x{1049d}\x{104a0}-\x{104a9}\x{10500}-\x{10527}\x{10530}-\x{10563}\x{1056f}\x{10600}-\x{10736}\x{10740}-\x{10755}\x{10760}-\x{10767}\x{11000}\x{11002}-\x{11037}\x{11047}-\x{1104d}\x{11066}-\x{1106f}\x{11082}-\x{110b2}\x{110b7}\x{110b8}\x{110bb}-\x{110c1}\x{110d0}-\x{110e8}\x{110f0}-\x{110f9}\x{11103}-\x{11126}\x{1112c}\x{11136}-\x{11143}\x{11150}-\x{11172}\x{11174}-\x{11176}\x{11182}-\x{111b5}\x{111bf}-\x{111c9}\x{111cd}\x{111d0}-\x{111df}\x{111e1}-\x{111f4}\x{11200}-\x{11211}\x{11213}-\x{1122e}\x{11232}\x{11233}\x{11235}\x{11238}-\x{1123d}\x{11280}-\x{11286}\x{11288}\x{1128a}-\x{1128d}\x{1128f}-\x{1129d}\x{1129f}-\x{112a9}\x{112b0}-\x{112de}\x{112e0}-\x{112e2}\x{112f0}-\x{112f9}\x{11302}\x{11303}\x{11305}-\x{1130c}\x{1130f}\x{11310}\x{11313}-\x{11328}\x{1132a}-\x{11330}\x{11332}\x{11333}\x{11335}-\x{11339}\x{1133d}-\x{1133f}\x{11341}-\x{11344}\x{11347}\x{11348}\x{1134b}-\x{1134d}\x{11350}\x{11357}\x{1135d}-\x{11363}\x{11480}-\x{114b2}\x{114b9}\x{114bb}-\x{114be}\x{114c1}\x{114c4}-\x{114c7}\x{114d0}-\x{114d9}\x{11580}-\x{115b1}\x{115b8}-\x{115bb}\x{115be}\x{115c1}-\x{115db}\x{11600}-\x{11632}\x{1163b}\x{1163c}\x{1163e}\x{11641}-\x{11644}\x{11650}-\x{11659}\x{11680}-\x{116aa}\x{116ac}\x{116ae}\x{116af}\x{116b6}\x{116c0}-\x{116c9}\x{11700}-\x{11719}\x{11720}\x{11721}\x{11726}\x{11730}-\x{1173f}\x{118a0}-\x{118f2}\x{118ff}\x{11ac0}-\x{11af8}\x{12000}-\x{12399}\x{12400}-\x{1246e}\x{12470}-\x{12474}\x{12480}-\x{12543}\x{13000}-\x{1342e}\x{14400}-\x{14646}\x{16800}-\x{16a38}\x{16a40}-\x{16a5e}\x{16a60}-\x{16a69}\x{16a6e}\x{16a6f}\x{16ad0}-\x{16aed}\x{16af5}\x{16b00}-\x{16b2f}\x{16b37}-\x{16b45}\x{16b50}-\x{16b59}\x{16b5b}-\x{16b61}\x{16b63}-\x{16b77}\x{16b7d}-\x{16b8f}\x{16f00}-\x{16f44}\x{16f50}-\x{16f7e}\x{16f93}-\x{16f9f}\x{1b000}\x{1b001}\x{1bc00}-\x{1bc6a}\x{1bc70}-\x{1bc7c}\x{1bc80}-\x{1bc88}\x{1bc90}-\x{1bc99}\x{1bc9c}\x{1bc9f}\x{1d000}-\x{1d0f5}\x{1d100}-\x{1d126}\x{1d129}-\x{1d166}\x{1d16a}-\x{1d172}\x{1d183}\x{1d184}\x{1d18c}-\x{1d1a9}\x{1d1ae}-\x{1d1e8}\x{1d360}-\x{1d371}\x{1d400}-\x{1d454}\x{1d456}-\x{1d49c}\x{1d49e}\x{1d49f}\x{1d4a2}\x{1d4a5}\x{1d4a6}\x{1d4a9}-\x{1d4ac}\x{1d4ae}-\x{1d4b9}\x{1d4bb}\x{1d4bd}-\x{1d4c3}\x{1d4c5}-\x{1d505}\x{1d507}-\x{1d50a}\x{1d50d}-\x{1d514}\x{1d516}-\x{1d51c}\x{1d51e}-\x{1d539}\x{1d53b}-\x{1d53e}\x{1d540}-\x{1d544}\x{1d546}\x{1d54a}-\x{1d550}\x{1d552}-\x{1d6a5}\x{1d6a8}-\x{1d6da}\x{1d6dc}-\x{1d714}\x{1d716}-\x{1d74e}\x{1d750}-\x{1d788}\x{1d78a}-\x{1d7c2}\x{1d7c4}-\x{1d7cb}\x{1d800}-\x{1d9ff}\x{1da37}-\x{1da3a}\x{1da6d}-\x{1da74}\x{1da76}-\x{1da83}\x{1da85}-\x{1da8b}\x{1f110}-\x{1f12e}\x{1f130}-\x{1f169}\x{1f170}-\x{1f19a}\x{1f1e6}-\x{1f202}\x{1f210}-\x{1f23a}\x{1f240}-\x{1f248}\x{1f250}\x{1f251}\x{20000}-\x{2a6d6}\x{2a700}-\x{2b734}\x{2b740}-\x{2b81d}\x{2b820}-\x{2cea1}\x{2f800}-\x{2fa1d}\x{f0000}-\x{ffffd}\x{100000}-\x{10fffd}])|([\x{590}\x{5be}\x{5c0}\x{5c3}\x{5c6}\x{5c8}-\x{5ff}\x{7c0}-\x{7ea}\x{7f4}\x{7f5}\x{7fa}-\x{815}\x{81a}\x{824}\x{828}\x{82e}-\x{858}\x{85c}-\x{89f}\x{200f}\x{fb1d}\x{fb1f}-\x{fb28}\x{fb2a}-\x{fb4f}\x{10800}-\x{1091e}\x{10920}-\x{10a00}\x{10a04}\x{10a07}-\x{10a0b}\x{10a10}-\x{10a37}\x{10a3b}-\x{10a3e}\x{10a40}-\x{10ae4}\x{10ae7}-\x{10b38}\x{10b40}-\x{10e5f}\x{10e7f}-\x{10fff}\x{1e800}-\x{1e8cf}\x{1e8d7}-\x{1edff}\x{1ef00}-\x{1efff}\x{608}\x{60b}\x{60d}\x{61b}-\x{64a}\x{66d}-\x{66f}\x{671}-\x{6d5}\x{6e5}\x{6e6}\x{6ee}\x{6ef}\x{6fa}-\x{710}\x{712}-\x{72f}\x{74b}-\x{7a5}\x{7b1}-\x{7bf}\x{8a0}-\x{8e2}\x{fb50}-\x{fd3d}\x{fd40}-\x{fdcf}\x{fdf0}-\x{fdfc}\x{fdfe}\x{fdff}\x{fe70}-\x{fefe}\x{1ee00}-\x{1eeef}\x{1eef2}-\x{1eeff}]))/u';
173 // @codeCoverageIgnoreEnd
174 // @codingStandardsIgnoreEnd
175
181 static function factory( $code ) {
183
184 if ( isset( $wgDummyLanguageCodes[$code] ) ) {
186 }
187
188 // get the language object to process
189 $langObj = isset( self::$mLangObjCache[$code] )
190 ? self::$mLangObjCache[$code]
191 : self::newFromCode( $code );
192
193 // merge the language object in to get it up front in the cache
194 self::$mLangObjCache = array_merge( [ $code => $langObj ], self::$mLangObjCache );
195 // get rid of the oldest ones in case we have an overflow
196 self::$mLangObjCache = array_slice( self::$mLangObjCache, 0, $wgLangObjCacheSize, true );
197
198 return $langObj;
199 }
200
207 protected static function newFromCode( $code ) {
208 if ( !Language::isValidCode( $code ) ) {
209 throw new MWException( "Invalid language code \"$code\"" );
210 }
211
213 // It's not possible to customise this code with class files, so
214 // just return a Language object. This is to support uselang= hacks.
215 $lang = new Language;
216 $lang->setCode( $code );
217 return $lang;
218 }
219
220 // Check if there is a language class for the code
221 $class = self::classFromCode( $code );
222 if ( class_exists( $class ) ) {
223 $lang = new $class;
224 return $lang;
225 }
226
227 // Keep trying the fallback list until we find an existing class
228 $fallbacks = Language::getFallbacksFor( $code );
229 foreach ( $fallbacks as $fallbackCode ) {
230 if ( !Language::isValidBuiltInCode( $fallbackCode ) ) {
231 throw new MWException( "Invalid fallback '$fallbackCode' in fallback sequence for '$code'" );
232 }
233
234 $class = self::classFromCode( $fallbackCode );
235 if ( class_exists( $class ) ) {
236 $lang = new $class;
237 $lang->setCode( $code );
238 return $lang;
239 }
240 }
241
242 throw new MWException( "Invalid fallback sequence for language '$code'" );
243 }
244
253 public static function isSupportedLanguage( $code ) {
254 if ( !self::isValidBuiltInCode( $code ) ) {
255 return false;
256 }
257
258 if ( $code === 'qqq' ) {
259 return false;
260 }
261
262 return is_readable( self::getMessagesFileName( $code ) ) ||
263 is_readable( self::getJsonMessagesFileName( $code ) );
264 }
265
281 public static function isWellFormedLanguageTag( $code, $lenient = false ) {
282 $alpha = '[a-z]';
283 $digit = '[0-9]';
284 $alphanum = '[a-z0-9]';
285 $x = 'x'; # private use singleton
286 $singleton = '[a-wy-z]'; # other singleton
287 $s = $lenient ? '[-_]' : '-';
288
289 $language = "$alpha{2,8}|$alpha{2,3}$s$alpha{3}";
290 $script = "$alpha{4}"; # ISO 15924
291 $region = "(?:$alpha{2}|$digit{3})"; # ISO 3166-1 alpha-2 or UN M.49
292 $variant = "(?:$alphanum{5,8}|$digit$alphanum{3})";
293 $extension = "$singleton(?:$s$alphanum{2,8})+";
294 $privateUse = "$x(?:$s$alphanum{1,8})+";
295
296 # Define certain grandfathered codes, since otherwise the regex is pretty useless.
297 # Since these are limited, this is safe even later changes to the registry --
298 # the only oddity is that it might change the type of the tag, and thus
299 # the results from the capturing groups.
300 # https://www.iana.org/assignments/language-subtag-registry
301
302 $grandfathered = "en{$s}GB{$s}oed"
303 . "|i{$s}(?:ami|bnn|default|enochian|hak|klingon|lux|mingo|navajo|pwn|tao|tay|tsu)"
304 . "|no{$s}(?:bok|nyn)"
305 . "|sgn{$s}(?:BE{$s}(?:fr|nl)|CH{$s}de)"
306 . "|zh{$s}min{$s}nan";
307
308 $variantList = "$variant(?:$s$variant)*";
309 $extensionList = "$extension(?:$s$extension)*";
310
311 $langtag = "(?:($language)"
312 . "(?:$s$script)?"
313 . "(?:$s$region)?"
314 . "(?:$s$variantList)?"
315 . "(?:$s$extensionList)?"
316 . "(?:$s$privateUse)?)";
317
318 # The final breakdown, with capturing groups for each of these components
319 # The variants, extensions, grandfathered, and private-use may have interior '-'
320
321 $root = "^(?:$langtag|$privateUse|$grandfathered)$";
322
323 return (bool)preg_match( "/$root/", strtolower( $code ) );
324 }
325
335 public static function isValidCode( $code ) {
336 static $cache = [];
337 if ( !isset( $cache[$code] ) ) {
338 // People think language codes are html safe, so enforce it.
339 // Ideally we should only allow a-zA-Z0-9-
340 // but, .+ and other chars are often used for {{int:}} hacks
341 // see bugs T39564, T39587, T38938
342 $cache[$code] =
343 // Protect against path traversal
344 strcspn( $code, ":/\\\000&<>'\"" ) === strlen( $code )
346 }
347 return $cache[$code];
348 }
349
360 public static function isValidBuiltInCode( $code ) {
361
362 if ( !is_string( $code ) ) {
363 if ( is_object( $code ) ) {
364 $addmsg = " of class " . get_class( $code );
365 } else {
366 $addmsg = '';
367 }
368 $type = gettype( $code );
369 throw new MWException( __METHOD__ . " must be passed a string, $type given$addmsg" );
370 }
371
372 return (bool)preg_match( '/^[a-z0-9-]{2,}$/', $code );
373 }
374
383 public static function isKnownLanguageTag( $tag ) {
384 // Quick escape for invalid input to avoid exceptions down the line
385 // when code tries to process tags which are not valid at all.
386 if ( !self::isValidBuiltInCode( $tag ) ) {
387 return false;
388 }
389
390 if ( isset( MediaWiki\Languages\Data\Names::$names[$tag] )
391 || self::fetchLanguageName( $tag, $tag ) !== ''
392 ) {
393 return true;
394 }
395
396 return false;
397 }
398
404 public static function getLocalisationCache() {
405 if ( is_null( self::$dataCache ) ) {
407 $class = $wgLocalisationCacheConf['class'];
408 self::$dataCache = new $class( $wgLocalisationCacheConf );
409 }
410 return self::$dataCache;
411 }
412
413 function __construct() {
414 $this->mConverter = new FakeConverter( $this );
415 // Set the code to the name of the descendant
416 if ( get_class( $this ) == 'Language' ) {
417 $this->mCode = 'en';
418 } else {
419 $this->mCode = str_replace( '_', '-', strtolower( substr( get_class( $this ), 8 ) ) );
420 }
421 self::getLocalisationCache();
422 }
423
427 function __destruct() {
428 foreach ( $this as $name => $value ) {
429 unset( $this->$name );
430 }
431 }
432
437 function initContLang() {
438 }
439
444 public function getFallbackLanguages() {
445 return self::getFallbacksFor( $this->mCode );
446 }
447
452 public function getBookstoreList() {
453 return self::$dataCache->getItem( $this->mCode, 'bookstoreList' );
454 }
455
462 public function getNamespaces() {
463 if ( is_null( $this->namespaceNames ) ) {
465
466 $this->namespaceNames = self::$dataCache->getItem( $this->mCode, 'namespaceNames' );
467 $validNamespaces = MWNamespace::getCanonicalNamespaces();
468
469 $this->namespaceNames = $wgExtraNamespaces + $this->namespaceNames + $validNamespaces;
470
471 $this->namespaceNames[NS_PROJECT] = $wgMetaNamespace;
472 if ( $wgMetaNamespaceTalk ) {
473 $this->namespaceNames[NS_PROJECT_TALK] = $wgMetaNamespaceTalk;
474 } else {
475 $talk = $this->namespaceNames[NS_PROJECT_TALK];
476 $this->namespaceNames[NS_PROJECT_TALK] =
477 $this->fixVariableInNamespace( $talk );
478 }
479
480 # Sometimes a language will be localised but not actually exist on this wiki.
481 foreach ( $this->namespaceNames as $key => $text ) {
482 if ( !isset( $validNamespaces[$key] ) ) {
483 unset( $this->namespaceNames[$key] );
484 }
485 }
486
487 # The above mixing may leave namespaces out of canonical order.
488 # Re-order by namespace ID number...
489 ksort( $this->namespaceNames );
490
491 Hooks::run( 'LanguageGetNamespaces', [ &$this->namespaceNames ] );
492 }
493
495 }
496
501 public function setNamespaces( array $namespaces ) {
502 $this->namespaceNames = $namespaces;
503 $this->mNamespaceIds = null;
504 }
505
509 public function resetNamespaces() {
510 $this->namespaceNames = null;
511 $this->mNamespaceIds = null;
512 $this->namespaceAliases = null;
513 }
514
521 public function getFormattedNamespaces() {
522 $ns = $this->getNamespaces();
523 foreach ( $ns as $k => $v ) {
524 $ns[$k] = strtr( $v, '_', ' ' );
525 }
526 return $ns;
527 }
528
540 public function getNsText( $index ) {
541 $ns = $this->getNamespaces();
542 return isset( $ns[$index] ) ? $ns[$index] : false;
543 }
544
558 public function getFormattedNsText( $index ) {
559 $ns = $this->getNsText( $index );
560 return strtr( $ns, '_', ' ' );
561 }
562
571 public function getGenderNsText( $index, $gender ) {
573
575 (array)self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' );
576
577 return isset( $ns[$index][$gender] ) ? $ns[$index][$gender] : $this->getNsText( $index );
578 }
579
586 public function needsGenderDistinction() {
588 if ( count( $wgExtraGenderNamespaces ) > 0 ) {
589 // $wgExtraGenderNamespaces overrides everything
590 return true;
591 } elseif ( isset( $wgExtraNamespaces[NS_USER] ) && isset( $wgExtraNamespaces[NS_USER_TALK] ) ) {
593 // $wgExtraNamespaces overrides any gender aliases specified in i18n files
594 return false;
595 } else {
596 // Check what is in i18n files
597 $aliases = self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' );
598 return count( $aliases ) > 0;
599 }
600 }
601
610 function getLocalNsIndex( $text ) {
611 $lctext = $this->lc( $text );
612 $ids = $this->getNamespaceIds();
613 return isset( $ids[$lctext] ) ? $ids[$lctext] : false;
614 }
615
619 public function getNamespaceAliases() {
620 if ( is_null( $this->namespaceAliases ) ) {
621 $aliases = self::$dataCache->getItem( $this->mCode, 'namespaceAliases' );
622 if ( !$aliases ) {
623 $aliases = [];
624 } else {
625 foreach ( $aliases as $name => $index ) {
626 if ( $index === NS_PROJECT_TALK ) {
627 unset( $aliases[$name] );
629 $aliases[$name] = $index;
630 }
631 }
632 }
633
635 $genders = $wgExtraGenderNamespaces +
636 (array)self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' );
637 foreach ( $genders as $index => $forms ) {
638 foreach ( $forms as $alias ) {
639 $aliases[$alias] = $index;
640 }
641 }
642
643 # Also add converted namespace names as aliases, to avoid confusion.
644 $convertedNames = [];
645 foreach ( $this->getVariants() as $variant ) {
646 if ( $variant === $this->mCode ) {
647 continue;
648 }
649 foreach ( $this->getNamespaces() as $ns => $_ ) {
650 $convertedNames[$this->getConverter()->convertNamespace( $ns, $variant )] = $ns;
651 }
652 }
653
654 $this->namespaceAliases = $aliases + $convertedNames;
655 }
656
658 }
659
663 public function getNamespaceIds() {
664 if ( is_null( $this->mNamespaceIds ) ) {
666 # Put namespace names and aliases into a hashtable.
667 # If this is too slow, then we should arrange it so that it is done
668 # before caching. The catch is that at pre-cache time, the above
669 # class-specific fixup hasn't been done.
670 $this->mNamespaceIds = [];
671 foreach ( $this->getNamespaces() as $index => $name ) {
672 $this->mNamespaceIds[$this->lc( $name )] = $index;
673 }
674 foreach ( $this->getNamespaceAliases() as $name => $index ) {
675 $this->mNamespaceIds[$this->lc( $name )] = $index;
676 }
677 if ( $wgNamespaceAliases ) {
678 foreach ( $wgNamespaceAliases as $name => $index ) {
679 $this->mNamespaceIds[$this->lc( $name )] = $index;
680 }
681 }
682 }
683 return $this->mNamespaceIds;
684 }
685
693 public function getNsIndex( $text ) {
694 $lctext = $this->lc( $text );
695 $ns = MWNamespace::getCanonicalIndex( $lctext );
696 if ( $ns !== null ) {
697 return $ns;
698 }
699 $ids = $this->getNamespaceIds();
700 return isset( $ids[$lctext] ) ? $ids[$lctext] : false;
701 }
702
710 public function getVariantname( $code, $usemsg = true ) {
711 $msg = "variantname-$code";
712 if ( $usemsg && wfMessage( $msg )->exists() ) {
713 return $this->getMessageFromDB( $msg );
714 }
715 $name = self::fetchLanguageName( $code );
716 if ( $name ) {
717 return $name; # if it's defined as a language name, show that
718 } else {
719 # otherwise, output the language code
720 return $code;
721 }
722 }
723
727 public function getDatePreferences() {
728 return self::$dataCache->getItem( $this->mCode, 'datePreferences' );
729 }
730
734 function getDateFormats() {
735 return self::$dataCache->getItem( $this->mCode, 'dateFormats' );
736 }
737
741 public function getDefaultDateFormat() {
742 $df = self::$dataCache->getItem( $this->mCode, 'defaultDateFormat' );
743 if ( $df === 'dmy or mdy' ) {
744 global $wgAmericanDates;
745 return $wgAmericanDates ? 'mdy' : 'dmy';
746 } else {
747 return $df;
748 }
749 }
750
754 public function getDatePreferenceMigrationMap() {
755 return self::$dataCache->getItem( $this->mCode, 'datePreferenceMigrationMap' );
756 }
757
762 function getImageFile( $image ) {
763 return self::$dataCache->getSubitem( $this->mCode, 'imageFiles', $image );
764 }
765
770 public function getImageFiles() {
771 return self::$dataCache->getItem( $this->mCode, 'imageFiles' );
772 }
773
777 public function getExtraUserToggles() {
778 return (array)self::$dataCache->getItem( $this->mCode, 'extraUserToggles' );
779 }
780
785 function getUserToggle( $tog ) {
786 return $this->getMessageFromDB( "tog-$tog" );
787 }
788
800 public static function fetchLanguageNames( $inLanguage = null, $include = 'mw' ) {
801 $cacheKey = $inLanguage === null ? 'null' : $inLanguage;
802 $cacheKey .= ":$include";
803 if ( self::$languageNameCache === null ) {
804 self::$languageNameCache = new HashBagOStuff( [ 'maxKeys' => 20 ] );
805 }
806
807 $ret = self::$languageNameCache->get( $cacheKey );
808 if ( !$ret ) {
809 $ret = self::fetchLanguageNamesUncached( $inLanguage, $include );
810 self::$languageNameCache->set( $cacheKey, $ret );
811 }
812 return $ret;
813 }
814
825 private static function fetchLanguageNamesUncached( $inLanguage = null, $include = 'mw' ) {
826 global $wgExtraLanguageNames;
827
828 // If passed an invalid language code to use, fallback to en
829 if ( $inLanguage !== null && !Language::isValidCode( $inLanguage ) ) {
830 $inLanguage = 'en';
831 }
832
833 $names = [];
834
835 if ( $inLanguage ) {
836 # TODO: also include when $inLanguage is null, when this code is more efficient
837 Hooks::run( 'LanguageGetTranslatedLanguageNames', [ &$names, $inLanguage ] );
838 }
839
840 $mwNames = $wgExtraLanguageNames + MediaWiki\Languages\Data\Names::$names;
841 foreach ( $mwNames as $mwCode => $mwName ) {
842 # - Prefer own MediaWiki native name when not using the hook
843 # - For other names just add if not added through the hook
844 if ( $mwCode === $inLanguage || !isset( $names[$mwCode] ) ) {
845 $names[$mwCode] = $mwName;
846 }
847 }
848
849 if ( $include === 'all' ) {
850 ksort( $names );
851 return $names;
852 }
853
854 $returnMw = [];
855 $coreCodes = array_keys( $mwNames );
856 foreach ( $coreCodes as $coreCode ) {
857 $returnMw[$coreCode] = $names[$coreCode];
858 }
859
860 if ( $include === 'mwfile' ) {
861 $namesMwFile = [];
862 # We do this using a foreach over the codes instead of a directory
863 # loop so that messages files in extensions will work correctly.
864 foreach ( $returnMw as $code => $value ) {
865 if ( is_readable( self::getMessagesFileName( $code ) )
866 || is_readable( self::getJsonMessagesFileName( $code ) )
867 ) {
868 $namesMwFile[$code] = $names[$code];
869 }
870 }
871
872 ksort( $namesMwFile );
873 return $namesMwFile;
874 }
875
876 ksort( $returnMw );
877 # 'mw' option; default if it's not one of the other two options (all/mwfile)
878 return $returnMw;
879 }
880
888 public static function fetchLanguageName( $code, $inLanguage = null, $include = 'all' ) {
889 $code = strtolower( $code );
890 $array = self::fetchLanguageNames( $inLanguage, $include );
891 return !array_key_exists( $code, $array ) ? '' : $array[$code];
892 }
893
900 public function getMessageFromDB( $msg ) {
901 return $this->msg( $msg )->text();
902 }
903
910 protected function msg( $msg ) {
911 return wfMessage( $msg )->inLanguage( $this );
912 }
913
918 public function getMonthName( $key ) {
919 return $this->getMessageFromDB( self::$mMonthMsgs[$key - 1] );
920 }
921
925 public function getMonthNamesArray() {
926 $monthNames = [ '' ];
927 for ( $i = 1; $i < 13; $i++ ) {
928 $monthNames[] = $this->getMonthName( $i );
929 }
930 return $monthNames;
931 }
932
937 public function getMonthNameGen( $key ) {
938 return $this->getMessageFromDB( self::$mMonthGenMsgs[$key - 1] );
939 }
940
945 public function getMonthAbbreviation( $key ) {
946 return $this->getMessageFromDB( self::$mMonthAbbrevMsgs[$key - 1] );
947 }
948
952 public function getMonthAbbreviationsArray() {
953 $monthNames = [ '' ];
954 for ( $i = 1; $i < 13; $i++ ) {
955 $monthNames[] = $this->getMonthAbbreviation( $i );
956 }
957 return $monthNames;
958 }
959
964 public function getWeekdayName( $key ) {
965 return $this->getMessageFromDB( self::$mWeekdayMsgs[$key - 1] );
966 }
967
972 function getWeekdayAbbreviation( $key ) {
973 return $this->getMessageFromDB( self::$mWeekdayAbbrevMsgs[$key - 1] );
974 }
975
981 return $this->getMessageFromDB( self::$mIranianCalendarMonthMsgs[$key - 1] );
982 }
983
988 function getHebrewCalendarMonthName( $key ) {
989 return $this->getMessageFromDB( self::$mHebrewCalendarMonthMsgs[$key - 1] );
990 }
991
997 return $this->getMessageFromDB( self::$mHebrewCalendarMonthGenMsgs[$key - 1] );
998 }
999
1004 function getHijriCalendarMonthName( $key ) {
1005 return $this->getMessageFromDB( self::$mHijriCalendarMonthMsgs[$key - 1] );
1006 }
1007
1016 private static function dateTimeObjFormat( &$dateTimeObj, $ts, $zone, $code ) {
1017 if ( !$dateTimeObj ) {
1018 $dateTimeObj = DateTime::createFromFormat(
1019 'YmdHis', $ts, $zone ?: new DateTimeZone( 'UTC' )
1020 );
1021 }
1022 return $dateTimeObj->format( $code );
1023 }
1024
1094 public function sprintfDate( $format, $ts, DateTimeZone $zone = null, &$ttl = 'unused' ) {
1095 $s = '';
1096 $raw = false;
1097 $roman = false;
1098 $hebrewNum = false;
1099 $dateTimeObj = false;
1100 $rawToggle = false;
1101 $iranian = false;
1102 $hebrew = false;
1103 $hijri = false;
1104 $thai = false;
1105 $minguo = false;
1106 $tenno = false;
1107
1108 $usedSecond = false;
1109 $usedMinute = false;
1110 $usedHour = false;
1111 $usedAMPM = false;
1112 $usedDay = false;
1113 $usedWeek = false;
1114 $usedMonth = false;
1115 $usedYear = false;
1116 $usedISOYear = false;
1117 $usedIsLeapYear = false;
1118
1119 $usedHebrewMonth = false;
1120 $usedIranianMonth = false;
1121 $usedHijriMonth = false;
1122 $usedHebrewYear = false;
1123 $usedIranianYear = false;
1124 $usedHijriYear = false;
1125 $usedTennoYear = false;
1126
1127 if ( strlen( $ts ) !== 14 ) {
1128 throw new MWException( __METHOD__ . ": The timestamp $ts should have 14 characters" );
1129 }
1130
1131 if ( !ctype_digit( $ts ) ) {
1132 throw new MWException( __METHOD__ . ": The timestamp $ts should be a number" );
1133 }
1134
1135 $formatLength = strlen( $format );
1136 for ( $p = 0; $p < $formatLength; $p++ ) {
1137 $num = false;
1138 $code = $format[$p];
1139 if ( $code == 'x' && $p < $formatLength - 1 ) {
1140 $code .= $format[++$p];
1141 }
1142
1143 if ( ( $code === 'xi'
1144 || $code === 'xj'
1145 || $code === 'xk'
1146 || $code === 'xm'
1147 || $code === 'xo'
1148 || $code === 'xt' )
1149 && $p < $formatLength - 1 ) {
1150 $code .= $format[++$p];
1151 }
1152
1153 switch ( $code ) {
1154 case 'xx':
1155 $s .= 'x';
1156 break;
1157 case 'xn':
1158 $raw = true;
1159 break;
1160 case 'xN':
1161 $rawToggle = !$rawToggle;
1162 break;
1163 case 'xr':
1164 $roman = true;
1165 break;
1166 case 'xh':
1167 $hebrewNum = true;
1168 break;
1169 case 'xg':
1170 $usedMonth = true;
1171 $s .= $this->getMonthNameGen( substr( $ts, 4, 2 ) );
1172 break;
1173 case 'xjx':
1174 $usedHebrewMonth = true;
1175 if ( !$hebrew ) {
1176 $hebrew = self::tsToHebrew( $ts );
1177 }
1178 $s .= $this->getHebrewCalendarMonthNameGen( $hebrew[1] );
1179 break;
1180 case 'd':
1181 $usedDay = true;
1182 $num = substr( $ts, 6, 2 );
1183 break;
1184 case 'D':
1185 $usedDay = true;
1186 $s .= $this->getWeekdayAbbreviation(
1187 Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'w' ) + 1
1188 );
1189 break;
1190 case 'j':
1191 $usedDay = true;
1192 $num = intval( substr( $ts, 6, 2 ) );
1193 break;
1194 case 'xij':
1195 $usedDay = true;
1196 if ( !$iranian ) {
1197 $iranian = self::tsToIranian( $ts );
1198 }
1199 $num = $iranian[2];
1200 break;
1201 case 'xmj':
1202 $usedDay = true;
1203 if ( !$hijri ) {
1204 $hijri = self::tsToHijri( $ts );
1205 }
1206 $num = $hijri[2];
1207 break;
1208 case 'xjj':
1209 $usedDay = true;
1210 if ( !$hebrew ) {
1211 $hebrew = self::tsToHebrew( $ts );
1212 }
1213 $num = $hebrew[2];
1214 break;
1215 case 'l':
1216 $usedDay = true;
1217 $s .= $this->getWeekdayName(
1218 Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'w' ) + 1
1219 );
1220 break;
1221 case 'F':
1222 $usedMonth = true;
1223 $s .= $this->getMonthName( substr( $ts, 4, 2 ) );
1224 break;
1225 case 'xiF':
1226 $usedIranianMonth = true;
1227 if ( !$iranian ) {
1228 $iranian = self::tsToIranian( $ts );
1229 }
1230 $s .= $this->getIranianCalendarMonthName( $iranian[1] );
1231 break;
1232 case 'xmF':
1233 $usedHijriMonth = true;
1234 if ( !$hijri ) {
1235 $hijri = self::tsToHijri( $ts );
1236 }
1237 $s .= $this->getHijriCalendarMonthName( $hijri[1] );
1238 break;
1239 case 'xjF':
1240 $usedHebrewMonth = true;
1241 if ( !$hebrew ) {
1242 $hebrew = self::tsToHebrew( $ts );
1243 }
1244 $s .= $this->getHebrewCalendarMonthName( $hebrew[1] );
1245 break;
1246 case 'm':
1247 $usedMonth = true;
1248 $num = substr( $ts, 4, 2 );
1249 break;
1250 case 'M':
1251 $usedMonth = true;
1252 $s .= $this->getMonthAbbreviation( substr( $ts, 4, 2 ) );
1253 break;
1254 case 'n':
1255 $usedMonth = true;
1256 $num = intval( substr( $ts, 4, 2 ) );
1257 break;
1258 case 'xin':
1259 $usedIranianMonth = true;
1260 if ( !$iranian ) {
1261 $iranian = self::tsToIranian( $ts );
1262 }
1263 $num = $iranian[1];
1264 break;
1265 case 'xmn':
1266 $usedHijriMonth = true;
1267 if ( !$hijri ) {
1268 $hijri = self::tsToHijri( $ts );
1269 }
1270 $num = $hijri[1];
1271 break;
1272 case 'xjn':
1273 $usedHebrewMonth = true;
1274 if ( !$hebrew ) {
1275 $hebrew = self::tsToHebrew( $ts );
1276 }
1277 $num = $hebrew[1];
1278 break;
1279 case 'xjt':
1280 $usedHebrewMonth = true;
1281 if ( !$hebrew ) {
1282 $hebrew = self::tsToHebrew( $ts );
1283 }
1284 $num = $hebrew[3];
1285 break;
1286 case 'Y':
1287 $usedYear = true;
1288 $num = substr( $ts, 0, 4 );
1289 break;
1290 case 'xiY':
1291 $usedIranianYear = true;
1292 if ( !$iranian ) {
1293 $iranian = self::tsToIranian( $ts );
1294 }
1295 $num = $iranian[0];
1296 break;
1297 case 'xmY':
1298 $usedHijriYear = true;
1299 if ( !$hijri ) {
1300 $hijri = self::tsToHijri( $ts );
1301 }
1302 $num = $hijri[0];
1303 break;
1304 case 'xjY':
1305 $usedHebrewYear = true;
1306 if ( !$hebrew ) {
1307 $hebrew = self::tsToHebrew( $ts );
1308 }
1309 $num = $hebrew[0];
1310 break;
1311 case 'xkY':
1312 $usedYear = true;
1313 if ( !$thai ) {
1314 $thai = self::tsToYear( $ts, 'thai' );
1315 }
1316 $num = $thai[0];
1317 break;
1318 case 'xoY':
1319 $usedYear = true;
1320 if ( !$minguo ) {
1321 $minguo = self::tsToYear( $ts, 'minguo' );
1322 }
1323 $num = $minguo[0];
1324 break;
1325 case 'xtY':
1326 $usedTennoYear = true;
1327 if ( !$tenno ) {
1328 $tenno = self::tsToYear( $ts, 'tenno' );
1329 }
1330 $num = $tenno[0];
1331 break;
1332 case 'y':
1333 $usedYear = true;
1334 $num = substr( $ts, 2, 2 );
1335 break;
1336 case 'xiy':
1337 $usedIranianYear = true;
1338 if ( !$iranian ) {
1339 $iranian = self::tsToIranian( $ts );
1340 }
1341 $num = substr( $iranian[0], -2 );
1342 break;
1343 case 'xit':
1344 $usedIranianYear = true;
1345 if ( !$iranian ) {
1346 $iranian = self::tsToIranian( $ts );
1347 }
1348 $num = self::$IRANIAN_DAYS[$iranian[1] - 1];
1349 break;
1350 case 'xiz':
1351 $usedIranianYear = true;
1352 if ( !$iranian ) {
1353 $iranian = self::tsToIranian( $ts );
1354 }
1355 $num = $iranian[3];
1356 break;
1357 case 'a':
1358 $usedAMPM = true;
1359 $s .= intval( substr( $ts, 8, 2 ) ) < 12 ? 'am' : 'pm';
1360 break;
1361 case 'A':
1362 $usedAMPM = true;
1363 $s .= intval( substr( $ts, 8, 2 ) ) < 12 ? 'AM' : 'PM';
1364 break;
1365 case 'g':
1366 $usedHour = true;
1367 $h = substr( $ts, 8, 2 );
1368 $num = $h % 12 ? $h % 12 : 12;
1369 break;
1370 case 'G':
1371 $usedHour = true;
1372 $num = intval( substr( $ts, 8, 2 ) );
1373 break;
1374 case 'h':
1375 $usedHour = true;
1376 $h = substr( $ts, 8, 2 );
1377 $num = sprintf( '%02d', $h % 12 ? $h % 12 : 12 );
1378 break;
1379 case 'H':
1380 $usedHour = true;
1381 $num = substr( $ts, 8, 2 );
1382 break;
1383 case 'i':
1384 $usedMinute = true;
1385 $num = substr( $ts, 10, 2 );
1386 break;
1387 case 's':
1388 $usedSecond = true;
1389 $num = substr( $ts, 12, 2 );
1390 break;
1391 case 'c':
1392 case 'r':
1393 $usedSecond = true;
1394 // fall through
1395 case 'e':
1396 case 'O':
1397 case 'P':
1398 case 'T':
1399 $s .= Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1400 break;
1401 case 'w':
1402 case 'N':
1403 case 'z':
1404 $usedDay = true;
1405 $num = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1406 break;
1407 case 'W':
1408 $usedWeek = true;
1409 $num = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1410 break;
1411 case 't':
1412 $usedMonth = true;
1413 $num = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1414 break;
1415 case 'L':
1416 $usedIsLeapYear = true;
1417 $num = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1418 break;
1419 case 'o':
1420 $usedISOYear = true;
1421 $num = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1422 break;
1423 case 'U':
1424 $usedSecond = true;
1425 // fall through
1426 case 'I':
1427 case 'Z':
1428 $num = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1429 break;
1430 case '\\':
1431 # Backslash escaping
1432 if ( $p < $formatLength - 1 ) {
1433 $s .= $format[++$p];
1434 } else {
1435 $s .= '\\';
1436 }
1437 break;
1438 case '"':
1439 # Quoted literal
1440 if ( $p < $formatLength - 1 ) {
1441 $endQuote = strpos( $format, '"', $p + 1 );
1442 if ( $endQuote === false ) {
1443 # No terminating quote, assume literal "
1444 $s .= '"';
1445 } else {
1446 $s .= substr( $format, $p + 1, $endQuote - $p - 1 );
1447 $p = $endQuote;
1448 }
1449 } else {
1450 # Quote at end of string, assume literal "
1451 $s .= '"';
1452 }
1453 break;
1454 default:
1455 $s .= $format[$p];
1456 }
1457 if ( $num !== false ) {
1458 if ( $rawToggle || $raw ) {
1459 $s .= $num;
1460 $raw = false;
1461 } elseif ( $roman ) {
1462 $s .= Language::romanNumeral( $num );
1463 $roman = false;
1464 } elseif ( $hebrewNum ) {
1465 $s .= self::hebrewNumeral( $num );
1466 $hebrewNum = false;
1467 } else {
1468 $s .= $this->formatNum( $num, true );
1469 }
1470 }
1471 }
1472
1473 if ( $ttl === 'unused' ) {
1474 // No need to calculate the TTL, the caller wont use it anyway.
1475 } elseif ( $usedSecond ) {
1476 $ttl = 1;
1477 } elseif ( $usedMinute ) {
1478 $ttl = 60 - substr( $ts, 12, 2 );
1479 } elseif ( $usedHour ) {
1480 $ttl = 3600 - substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
1481 } elseif ( $usedAMPM ) {
1482 $ttl = 43200 - ( substr( $ts, 8, 2 ) % 12 ) * 3600 -
1483 substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
1484 } elseif (
1485 $usedDay ||
1486 $usedHebrewMonth ||
1487 $usedIranianMonth ||
1488 $usedHijriMonth ||
1489 $usedHebrewYear ||
1490 $usedIranianYear ||
1491 $usedHijriYear ||
1492 $usedTennoYear
1493 ) {
1494 // @todo Someone who understands the non-Gregorian calendars
1495 // should write proper logic for them so that they don't need purged every day.
1496 $ttl = 86400 - substr( $ts, 8, 2 ) * 3600 -
1497 substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
1498 } else {
1499 $possibleTtls = [];
1500 $timeRemainingInDay = 86400 - substr( $ts, 8, 2 ) * 3600 -
1501 substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
1502 if ( $usedWeek ) {
1503 $possibleTtls[] =
1504 ( 7 - Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'N' ) ) * 86400 +
1505 $timeRemainingInDay;
1506 } elseif ( $usedISOYear ) {
1507 // December 28th falls on the last ISO week of the year, every year.
1508 // The last ISO week of a year can be 52 or 53.
1509 $lastWeekOfISOYear = DateTime::createFromFormat(
1510 'Ymd',
1511 substr( $ts, 0, 4 ) . '1228',
1512 $zone ?: new DateTimeZone( 'UTC' )
1513 )->format( 'W' );
1514 $currentISOWeek = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'W' );
1515 $weeksRemaining = $lastWeekOfISOYear - $currentISOWeek;
1516 $timeRemainingInWeek =
1517 ( 7 - Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'N' ) ) * 86400
1518 + $timeRemainingInDay;
1519 $possibleTtls[] = $weeksRemaining * 604800 + $timeRemainingInWeek;
1520 }
1521
1522 if ( $usedMonth ) {
1523 $possibleTtls[] =
1524 ( Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 't' ) -
1525 substr( $ts, 6, 2 ) ) * 86400
1526 + $timeRemainingInDay;
1527 } elseif ( $usedYear ) {
1528 $possibleTtls[] =
1529 ( Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'L' ) + 364 -
1530 Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'z' ) ) * 86400
1531 + $timeRemainingInDay;
1532 } elseif ( $usedIsLeapYear ) {
1533 $year = substr( $ts, 0, 4 );
1534 $timeRemainingInYear =
1535 ( Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'L' ) + 364 -
1536 Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'z' ) ) * 86400
1537 + $timeRemainingInDay;
1538 $mod = $year % 4;
1539 if ( $mod || ( !( $year % 100 ) && $year % 400 ) ) {
1540 // this isn't a leap year. see when the next one starts
1541 $nextCandidate = $year - $mod + 4;
1542 if ( $nextCandidate % 100 || !( $nextCandidate % 400 ) ) {
1543 $possibleTtls[] = ( $nextCandidate - $year - 1 ) * 365 * 86400 +
1544 $timeRemainingInYear;
1545 } else {
1546 $possibleTtls[] = ( $nextCandidate - $year + 3 ) * 365 * 86400 +
1547 $timeRemainingInYear;
1548 }
1549 } else {
1550 // this is a leap year, so the next year isn't
1551 $possibleTtls[] = $timeRemainingInYear;
1552 }
1553 }
1554
1555 if ( $possibleTtls ) {
1556 $ttl = min( $possibleTtls );
1557 }
1558 }
1559
1560 return $s;
1561 }
1562
1563 private static $GREG_DAYS = [ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ];
1564 private static $IRANIAN_DAYS = [ 31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29 ];
1565
1578 private static function tsToIranian( $ts ) {
1579 $gy = substr( $ts, 0, 4 ) -1600;
1580 $gm = substr( $ts, 4, 2 ) -1;
1581 $gd = substr( $ts, 6, 2 ) -1;
1582
1583 # Days passed from the beginning (including leap years)
1584 $gDayNo = 365 * $gy
1585 + floor( ( $gy + 3 ) / 4 )
1586 - floor( ( $gy + 99 ) / 100 )
1587 + floor( ( $gy + 399 ) / 400 );
1588
1589 // Add days of the past months of this year
1590 for ( $i = 0; $i < $gm; $i++ ) {
1591 $gDayNo += self::$GREG_DAYS[$i];
1592 }
1593
1594 // Leap years
1595 if ( $gm > 1 && ( ( $gy % 4 === 0 && $gy % 100 !== 0 || ( $gy % 400 == 0 ) ) ) ) {
1596 $gDayNo++;
1597 }
1598
1599 // Days passed in current month
1600 $gDayNo += (int)$gd;
1601
1602 $jDayNo = $gDayNo - 79;
1603
1604 $jNp = floor( $jDayNo / 12053 );
1605 $jDayNo %= 12053;
1606
1607 $jy = 979 + 33 * $jNp + 4 * floor( $jDayNo / 1461 );
1608 $jDayNo %= 1461;
1609
1610 if ( $jDayNo >= 366 ) {
1611 $jy += floor( ( $jDayNo - 1 ) / 365 );
1612 $jDayNo = floor( ( $jDayNo - 1 ) % 365 );
1613 }
1614
1615 $jz = $jDayNo;
1616
1617 for ( $i = 0; $i < 11 && $jDayNo >= self::$IRANIAN_DAYS[$i]; $i++ ) {
1618 $jDayNo -= self::$IRANIAN_DAYS[$i];
1619 }
1620
1621 $jm = $i + 1;
1622 $jd = $jDayNo + 1;
1623
1624 return [ $jy, $jm, $jd, $jz ];
1625 }
1626
1638 private static function tsToHijri( $ts ) {
1639 $year = substr( $ts, 0, 4 );
1640 $month = substr( $ts, 4, 2 );
1641 $day = substr( $ts, 6, 2 );
1642
1643 $zyr = $year;
1644 $zd = $day;
1645 $zm = $month;
1646 $zy = $zyr;
1647
1648 if (
1649 ( $zy > 1582 ) || ( ( $zy == 1582 ) && ( $zm > 10 ) ) ||
1650 ( ( $zy == 1582 ) && ( $zm == 10 ) && ( $zd > 14 ) )
1651 ) {
1652 $zjd = (int)( ( 1461 * ( $zy + 4800 + (int)( ( $zm - 14 ) / 12 ) ) ) / 4 ) +
1653 (int)( ( 367 * ( $zm - 2 - 12 * ( (int)( ( $zm - 14 ) / 12 ) ) ) ) / 12 ) -
1654 (int)( ( 3 * (int)( ( ( $zy + 4900 + (int)( ( $zm - 14 ) / 12 ) ) / 100 ) ) ) / 4 ) +
1655 $zd - 32075;
1656 } else {
1657 $zjd = 367 * $zy - (int)( ( 7 * ( $zy + 5001 + (int)( ( $zm - 9 ) / 7 ) ) ) / 4 ) +
1658 (int)( ( 275 * $zm ) / 9 ) + $zd + 1729777;
1659 }
1660
1661 $zl = $zjd -1948440 + 10632;
1662 $zn = (int)( ( $zl - 1 ) / 10631 );
1663 $zl = $zl - 10631 * $zn + 354;
1664 $zj = ( (int)( ( 10985 - $zl ) / 5316 ) ) * ( (int)( ( 50 * $zl ) / 17719 ) ) +
1665 ( (int)( $zl / 5670 ) ) * ( (int)( ( 43 * $zl ) / 15238 ) );
1666 $zl = $zl - ( (int)( ( 30 - $zj ) / 15 ) ) * ( (int)( ( 17719 * $zj ) / 50 ) ) -
1667 ( (int)( $zj / 16 ) ) * ( (int)( ( 15238 * $zj ) / 43 ) ) + 29;
1668 $zm = (int)( ( 24 * $zl ) / 709 );
1669 $zd = $zl - (int)( ( 709 * $zm ) / 24 );
1670 $zy = 30 * $zn + $zj - 30;
1671
1672 return [ $zy, $zm, $zd ];
1673 }
1674
1690 private static function tsToHebrew( $ts ) {
1691 # Parse date
1692 $year = substr( $ts, 0, 4 );
1693 $month = substr( $ts, 4, 2 );
1694 $day = substr( $ts, 6, 2 );
1695
1696 # Calculate Hebrew year
1697 $hebrewYear = $year + 3760;
1698
1699 # Month number when September = 1, August = 12
1700 $month += 4;
1701 if ( $month > 12 ) {
1702 # Next year
1703 $month -= 12;
1704 $year++;
1705 $hebrewYear++;
1706 }
1707
1708 # Calculate day of year from 1 September
1709 $dayOfYear = $day;
1710 for ( $i = 1; $i < $month; $i++ ) {
1711 if ( $i == 6 ) {
1712 # February
1713 $dayOfYear += 28;
1714 # Check if the year is leap
1715 if ( $year % 400 == 0 || ( $year % 4 == 0 && $year % 100 > 0 ) ) {
1716 $dayOfYear++;
1717 }
1718 } elseif ( $i == 8 || $i == 10 || $i == 1 || $i == 3 ) {
1719 $dayOfYear += 30;
1720 } else {
1721 $dayOfYear += 31;
1722 }
1723 }
1724
1725 # Calculate the start of the Hebrew year
1726 $start = self::hebrewYearStart( $hebrewYear );
1727
1728 # Calculate next year's start
1729 if ( $dayOfYear <= $start ) {
1730 # Day is before the start of the year - it is the previous year
1731 # Next year's start
1732 $nextStart = $start;
1733 # Previous year
1734 $year--;
1735 $hebrewYear--;
1736 # Add days since previous year's 1 September
1737 $dayOfYear += 365;
1738 if ( ( $year % 400 == 0 ) || ( $year % 100 != 0 && $year % 4 == 0 ) ) {
1739 # Leap year
1740 $dayOfYear++;
1741 }
1742 # Start of the new (previous) year
1743 $start = self::hebrewYearStart( $hebrewYear );
1744 } else {
1745 # Next year's start
1746 $nextStart = self::hebrewYearStart( $hebrewYear + 1 );
1747 }
1748
1749 # Calculate Hebrew day of year
1750 $hebrewDayOfYear = $dayOfYear - $start;
1751
1752 # Difference between year's days
1753 $diff = $nextStart - $start;
1754 # Add 12 (or 13 for leap years) days to ignore the difference between
1755 # Hebrew and Gregorian year (353 at least vs. 365/6) - now the
1756 # difference is only about the year type
1757 if ( ( $year % 400 == 0 ) || ( $year % 100 != 0 && $year % 4 == 0 ) ) {
1758 $diff += 13;
1759 } else {
1760 $diff += 12;
1761 }
1762
1763 # Check the year pattern, and is leap year
1764 # 0 means an incomplete year, 1 means a regular year, 2 means a complete year
1765 # This is mod 30, to work on both leap years (which add 30 days of Adar I)
1766 # and non-leap years
1767 $yearPattern = $diff % 30;
1768 # Check if leap year
1769 $isLeap = $diff >= 30;
1770
1771 # Calculate day in the month from number of day in the Hebrew year
1772 # Don't check Adar - if the day is not in Adar, we will stop before;
1773 # if it is in Adar, we will use it to check if it is Adar I or Adar II
1774 $hebrewDay = $hebrewDayOfYear;
1775 $hebrewMonth = 1;
1776 $days = 0;
1777 while ( $hebrewMonth <= 12 ) {
1778 # Calculate days in this month
1779 if ( $isLeap && $hebrewMonth == 6 ) {
1780 # Adar in a leap year
1781 if ( $isLeap ) {
1782 # Leap year - has Adar I, with 30 days, and Adar II, with 29 days
1783 $days = 30;
1784 if ( $hebrewDay <= $days ) {
1785 # Day in Adar I
1786 $hebrewMonth = 13;
1787 } else {
1788 # Subtract the days of Adar I
1789 $hebrewDay -= $days;
1790 # Try Adar II
1791 $days = 29;
1792 if ( $hebrewDay <= $days ) {
1793 # Day in Adar II
1794 $hebrewMonth = 14;
1795 }
1796 }
1797 }
1798 } elseif ( $hebrewMonth == 2 && $yearPattern == 2 ) {
1799 # Cheshvan in a complete year (otherwise as the rule below)
1800 $days = 30;
1801 } elseif ( $hebrewMonth == 3 && $yearPattern == 0 ) {
1802 # Kislev in an incomplete year (otherwise as the rule below)
1803 $days = 29;
1804 } else {
1805 # Odd months have 30 days, even have 29
1806 $days = 30 - ( $hebrewMonth - 1 ) % 2;
1807 }
1808 if ( $hebrewDay <= $days ) {
1809 # In the current month
1810 break;
1811 } else {
1812 # Subtract the days of the current month
1813 $hebrewDay -= $days;
1814 # Try in the next month
1815 $hebrewMonth++;
1816 }
1817 }
1818
1819 return [ $hebrewYear, $hebrewMonth, $hebrewDay, $days ];
1820 }
1821
1831 private static function hebrewYearStart( $year ) {
1832 $a = intval( ( 12 * ( $year - 1 ) + 17 ) % 19 );
1833 $b = intval( ( $year - 1 ) % 4 );
1834 $m = 32.044093161144 + 1.5542417966212 * $a + $b / 4.0 - 0.0031777940220923 * ( $year - 1 );
1835 if ( $m < 0 ) {
1836 $m--;
1837 }
1838 $Mar = intval( $m );
1839 if ( $m < 0 ) {
1840 $m++;
1841 }
1842 $m -= $Mar;
1843
1844 $c = intval( ( $Mar + 3 * ( $year - 1 ) + 5 * $b + 5 ) % 7 );
1845 if ( $c == 0 && $a > 11 && $m >= 0.89772376543210 ) {
1846 $Mar++;
1847 } elseif ( $c == 1 && $a > 6 && $m >= 0.63287037037037 ) {
1848 $Mar += 2;
1849 } elseif ( $c == 2 || $c == 4 || $c == 6 ) {
1850 $Mar++;
1851 }
1852
1853 $Mar += intval( ( $year - 3761 ) / 100 ) - intval( ( $year - 3761 ) / 400 ) - 24;
1854 return $Mar;
1855 }
1856
1869 private static function tsToYear( $ts, $cName ) {
1870 $gy = substr( $ts, 0, 4 );
1871 $gm = substr( $ts, 4, 2 );
1872 $gd = substr( $ts, 6, 2 );
1873
1874 if ( !strcmp( $cName, 'thai' ) ) {
1875 # Thai solar dates
1876 # Add 543 years to the Gregorian calendar
1877 # Months and days are identical
1878 $gy_offset = $gy + 543;
1879 } elseif ( ( !strcmp( $cName, 'minguo' ) ) || !strcmp( $cName, 'juche' ) ) {
1880 # Minguo dates
1881 # Deduct 1911 years from the Gregorian calendar
1882 # Months and days are identical
1883 $gy_offset = $gy - 1911;
1884 } elseif ( !strcmp( $cName, 'tenno' ) ) {
1885 # Nengō dates up to Meiji period
1886 # Deduct years from the Gregorian calendar
1887 # depending on the nengo periods
1888 # Months and days are identical
1889 if ( ( $gy < 1912 )
1890 || ( ( $gy == 1912 ) && ( $gm < 7 ) )
1891 || ( ( $gy == 1912 ) && ( $gm == 7 ) && ( $gd < 31 ) )
1892 ) {
1893 # Meiji period
1894 $gy_gannen = $gy - 1868 + 1;
1895 $gy_offset = $gy_gannen;
1896 if ( $gy_gannen == 1 ) {
1897 $gy_offset = '元';
1898 }
1899 $gy_offset = '明治' . $gy_offset;
1900 } elseif (
1901 ( ( $gy == 1912 ) && ( $gm == 7 ) && ( $gd == 31 ) ) ||
1902 ( ( $gy == 1912 ) && ( $gm >= 8 ) ) ||
1903 ( ( $gy > 1912 ) && ( $gy < 1926 ) ) ||
1904 ( ( $gy == 1926 ) && ( $gm < 12 ) ) ||
1905 ( ( $gy == 1926 ) && ( $gm == 12 ) && ( $gd < 26 ) )
1906 ) {
1907 # Taishō period
1908 $gy_gannen = $gy - 1912 + 1;
1909 $gy_offset = $gy_gannen;
1910 if ( $gy_gannen == 1 ) {
1911 $gy_offset = '元';
1912 }
1913 $gy_offset = '大正' . $gy_offset;
1914 } elseif (
1915 ( ( $gy == 1926 ) && ( $gm == 12 ) && ( $gd >= 26 ) ) ||
1916 ( ( $gy > 1926 ) && ( $gy < 1989 ) ) ||
1917 ( ( $gy == 1989 ) && ( $gm == 1 ) && ( $gd < 8 ) )
1918 ) {
1919 # Shōwa period
1920 $gy_gannen = $gy - 1926 + 1;
1921 $gy_offset = $gy_gannen;
1922 if ( $gy_gannen == 1 ) {
1923 $gy_offset = '元';
1924 }
1925 $gy_offset = '昭和' . $gy_offset;
1926 } else {
1927 # Heisei period
1928 $gy_gannen = $gy - 1989 + 1;
1929 $gy_offset = $gy_gannen;
1930 if ( $gy_gannen == 1 ) {
1931 $gy_offset = '元';
1932 }
1933 $gy_offset = '平成' . $gy_offset;
1934 }
1935 } else {
1936 $gy_offset = $gy;
1937 }
1938
1939 return [ $gy_offset, $gm, $gd ];
1940 }
1941
1955 private static function strongDirFromContent( $text = '' ) {
1956 if ( !preg_match( self::$strongDirRegex, $text, $matches ) ) {
1957 return null;
1958 }
1959 if ( $matches[1] === '' ) {
1960 return 'rtl';
1961 }
1962 return 'ltr';
1963 }
1964
1972 static function romanNumeral( $num ) {
1973 static $table = [
1974 [ '', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X' ],
1975 [ '', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC', 'C' ],
1976 [ '', 'C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM', 'M' ],
1977 [ '', 'M', 'MM', 'MMM', 'MMMM', 'MMMMM', 'MMMMMM', 'MMMMMMM',
1978 'MMMMMMMM', 'MMMMMMMMM', 'MMMMMMMMMM' ]
1979 ];
1980
1981 $num = intval( $num );
1982 if ( $num > 10000 || $num <= 0 ) {
1983 return $num;
1984 }
1985
1986 $s = '';
1987 for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) {
1988 if ( $num >= $pow10 ) {
1989 $s .= $table[$i][(int)floor( $num / $pow10 )];
1990 }
1991 $num = $num % $pow10;
1992 }
1993 return $s;
1994 }
1995
2003 static function hebrewNumeral( $num ) {
2004 static $table = [
2005 [ '', 'א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ז', 'ח', 'ט', 'י' ],
2006 [ '', 'י', 'כ', 'ל', 'מ', 'נ', 'ס', 'ע', 'פ', 'צ', 'ק' ],
2007 [ '',
2008 [ 'ק' ],
2009 [ 'ר' ],
2010 [ 'ש' ],
2011 [ 'ת' ],
2012 [ 'ת', 'ק' ],
2013 [ 'ת', 'ר' ],
2014 [ 'ת', 'ש' ],
2015 [ 'ת', 'ת' ],
2016 [ 'ת', 'ת', 'ק' ],
2017 [ 'ת', 'ת', 'ר' ],
2018 ],
2019 [ '', 'א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ז', 'ח', 'ט', 'י' ]
2020 ];
2021
2022 $num = intval( $num );
2023 if ( $num > 9999 || $num <= 0 ) {
2024 return $num;
2025 }
2026
2027 // Round thousands have special notations
2028 if ( $num === 1000 ) {
2029 return "א' אלף";
2030 } elseif ( $num % 1000 === 0 ) {
2031 return $table[0][$num / 1000] . "' אלפים";
2032 }
2033
2034 $letters = [];
2035
2036 for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) {
2037 if ( $num >= $pow10 ) {
2038 if ( $num === 15 || $num === 16 ) {
2039 $letters[] = $table[0][9];
2040 $letters[] = $table[0][$num - 9];
2041 $num = 0;
2042 } else {
2043 $letters = array_merge(
2044 $letters,
2045 (array)$table[$i][intval( $num / $pow10 )]
2046 );
2047
2048 if ( $pow10 === 1000 ) {
2049 $letters[] = "'";
2050 }
2051 }
2052 }
2053
2054 $num = $num % $pow10;
2055 }
2056
2057 $preTransformLength = count( $letters );
2058 if ( $preTransformLength === 1 ) {
2059 // Add geresh (single quote) to one-letter numbers
2060 $letters[] = "'";
2061 } else {
2062 $lastIndex = $preTransformLength - 1;
2063 $letters[$lastIndex] = str_replace(
2064 [ 'כ', 'מ', 'נ', 'פ', 'צ' ],
2065 [ 'ך', 'ם', 'ן', 'ף', 'ץ' ],
2066 $letters[$lastIndex]
2067 );
2068
2069 // Add gershayim (double quote) to multiple-letter numbers,
2070 // but exclude numbers with only one letter after the thousands
2071 // (1001-1009, 1020, 1030, 2001-2009, etc.)
2072 if ( $letters[1] === "'" && $preTransformLength === 3 ) {
2073 $letters[] = "'";
2074 } else {
2075 array_splice( $letters, -1, 0, '"' );
2076 }
2077 }
2078
2079 return implode( $letters );
2080 }
2081
2090 public function userAdjust( $ts, $tz = false ) {
2092
2093 if ( $tz === false ) {
2094 $tz = $wgUser->getOption( 'timecorrection' );
2095 }
2096
2097 $data = explode( '|', $tz, 3 );
2098
2099 if ( $data[0] == 'ZoneInfo' ) {
2100 MediaWiki\suppressWarnings();
2101 $userTZ = timezone_open( $data[2] );
2102 MediaWiki\restoreWarnings();
2103 if ( $userTZ !== false ) {
2104 $date = date_create( $ts, timezone_open( 'UTC' ) );
2105 date_timezone_set( $date, $userTZ );
2106 $date = date_format( $date, 'YmdHis' );
2107 return $date;
2108 }
2109 # Unrecognized timezone, default to 'Offset' with the stored offset.
2110 $data[0] = 'Offset';
2111 }
2112
2113 if ( $data[0] == 'System' || $tz == '' ) {
2114 # Global offset in minutes.
2115 $minDiff = $wgLocalTZoffset;
2116 } elseif ( $data[0] == 'Offset' ) {
2117 $minDiff = intval( $data[1] );
2118 } else {
2119 $data = explode( ':', $tz );
2120 if ( count( $data ) == 2 ) {
2121 $data[0] = intval( $data[0] );
2122 $data[1] = intval( $data[1] );
2123 $minDiff = abs( $data[0] ) * 60 + $data[1];
2124 if ( $data[0] < 0 ) {
2125 $minDiff = -$minDiff;
2126 }
2127 } else {
2128 $minDiff = intval( $data[0] ) * 60;
2129 }
2130 }
2131
2132 # No difference ? Return time unchanged
2133 if ( 0 == $minDiff ) {
2134 return $ts;
2135 }
2136
2137 MediaWiki\suppressWarnings(); // E_STRICT system time bitching
2138 # Generate an adjusted date; take advantage of the fact that mktime
2139 # will normalize out-of-range values so we don't have to split $minDiff
2140 # into hours and minutes.
2141 $t = mktime( (
2142 (int)substr( $ts, 8, 2 ) ), # Hours
2143 (int)substr( $ts, 10, 2 ) + $minDiff, # Minutes
2144 (int)substr( $ts, 12, 2 ), # Seconds
2145 (int)substr( $ts, 4, 2 ), # Month
2146 (int)substr( $ts, 6, 2 ), # Day
2147 (int)substr( $ts, 0, 4 ) ); # Year
2148
2149 $date = date( 'YmdHis', $t );
2150 MediaWiki\restoreWarnings();
2151
2152 return $date;
2153 }
2154
2172 function dateFormat( $usePrefs = true ) {
2174
2175 if ( is_bool( $usePrefs ) ) {
2176 if ( $usePrefs ) {
2177 $datePreference = $wgUser->getDatePreference();
2178 } else {
2179 $datePreference = (string)User::getDefaultOption( 'date' );
2180 }
2181 } else {
2182 $datePreference = (string)$usePrefs;
2183 }
2184
2185 // return int
2186 if ( $datePreference == '' ) {
2187 return 'default';
2188 }
2189
2190 return $datePreference;
2191 }
2192
2203 function getDateFormatString( $type, $pref ) {
2204 $wasDefault = false;
2205 if ( $pref == 'default' ) {
2206 $wasDefault = true;
2207 $pref = $this->getDefaultDateFormat();
2208 }
2209
2210 if ( !isset( $this->dateFormatStrings[$type][$pref] ) ) {
2211 $df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" );
2212
2213 if ( $type === 'pretty' && $df === null ) {
2214 $df = $this->getDateFormatString( 'date', $pref );
2215 }
2216
2217 if ( !$wasDefault && $df === null ) {
2218 $pref = $this->getDefaultDateFormat();
2219 $df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" );
2220 }
2221
2222 $this->dateFormatStrings[$type][$pref] = $df;
2223 }
2224 return $this->dateFormatStrings[$type][$pref];
2225 }
2226
2237 public function date( $ts, $adj = false, $format = true, $timecorrection = false ) {
2238 $ts = wfTimestamp( TS_MW, $ts );
2239 if ( $adj ) {
2240 $ts = $this->userAdjust( $ts, $timecorrection );
2241 }
2242 $df = $this->getDateFormatString( 'date', $this->dateFormat( $format ) );
2243 return $this->sprintfDate( $df, $ts );
2244 }
2245
2256 public function time( $ts, $adj = false, $format = true, $timecorrection = false ) {
2257 $ts = wfTimestamp( TS_MW, $ts );
2258 if ( $adj ) {
2259 $ts = $this->userAdjust( $ts, $timecorrection );
2260 }
2261 $df = $this->getDateFormatString( 'time', $this->dateFormat( $format ) );
2262 return $this->sprintfDate( $df, $ts );
2263 }
2264
2276 public function timeanddate( $ts, $adj = false, $format = true, $timecorrection = false ) {
2277 $ts = wfTimestamp( TS_MW, $ts );
2278 if ( $adj ) {
2279 $ts = $this->userAdjust( $ts, $timecorrection );
2280 }
2281 $df = $this->getDateFormatString( 'both', $this->dateFormat( $format ) );
2282 return $this->sprintfDate( $df, $ts );
2283 }
2284
2295 public function formatDuration( $seconds, array $chosenIntervals = [] ) {
2296 $intervals = $this->getDurationIntervals( $seconds, $chosenIntervals );
2297
2298 $segments = [];
2299
2300 foreach ( $intervals as $intervalName => $intervalValue ) {
2301 // Messages: duration-seconds, duration-minutes, duration-hours, duration-days, duration-weeks,
2302 // duration-years, duration-decades, duration-centuries, duration-millennia
2303 $message = wfMessage( 'duration-' . $intervalName )->numParams( $intervalValue );
2304 $segments[] = $message->inLanguage( $this )->escaped();
2305 }
2306
2307 return $this->listToText( $segments );
2308 }
2309
2321 public function getDurationIntervals( $seconds, array $chosenIntervals = [] ) {
2322 if ( empty( $chosenIntervals ) ) {
2323 $chosenIntervals = [
2324 'millennia',
2325 'centuries',
2326 'decades',
2327 'years',
2328 'days',
2329 'hours',
2330 'minutes',
2331 'seconds'
2332 ];
2333 }
2334
2335 $intervals = array_intersect_key( self::$durationIntervals, array_flip( $chosenIntervals ) );
2336 $sortedNames = array_keys( $intervals );
2337 $smallestInterval = array_pop( $sortedNames );
2338
2339 $segments = [];
2340
2341 foreach ( $intervals as $name => $length ) {
2342 $value = floor( $seconds / $length );
2343
2344 if ( $value > 0 || ( $name == $smallestInterval && empty( $segments ) ) ) {
2345 $seconds -= $value * $length;
2346 $segments[$name] = $value;
2347 }
2348 }
2349
2350 return $segments;
2351 }
2352
2372 private function internalUserTimeAndDate( $type, $ts, User $user, array $options ) {
2373 $ts = wfTimestamp( TS_MW, $ts );
2374 $options += [ 'timecorrection' => true, 'format' => true ];
2375 if ( $options['timecorrection'] !== false ) {
2376 if ( $options['timecorrection'] === true ) {
2377 $offset = $user->getOption( 'timecorrection' );
2378 } else {
2379 $offset = $options['timecorrection'];
2380 }
2381 $ts = $this->userAdjust( $ts, $offset );
2382 }
2383 if ( $options['format'] === true ) {
2384 $format = $user->getDatePreference();
2385 } else {
2386 $format = $options['format'];
2387 }
2388 $df = $this->getDateFormatString( $type, $this->dateFormat( $format ) );
2389 return $this->sprintfDate( $df, $ts );
2390 }
2391
2411 public function userDate( $ts, User $user, array $options = [] ) {
2412 return $this->internalUserTimeAndDate( 'date', $ts, $user, $options );
2413 }
2414
2434 public function userTime( $ts, User $user, array $options = [] ) {
2435 return $this->internalUserTimeAndDate( 'time', $ts, $user, $options );
2436 }
2437
2457 public function userTimeAndDate( $ts, User $user, array $options = [] ) {
2458 return $this->internalUserTimeAndDate( 'both', $ts, $user, $options );
2459 }
2460
2476 public function getHumanTimestamp(
2477 MWTimestamp $time, MWTimestamp $relativeTo = null, User $user = null
2478 ) {
2479 if ( $relativeTo === null ) {
2480 $relativeTo = new MWTimestamp();
2481 }
2482 if ( $user === null ) {
2483 $user = RequestContext::getMain()->getUser();
2484 }
2485
2486 // Adjust for the user's timezone.
2487 $offsetThis = $time->offsetForUser( $user );
2488 $offsetRel = $relativeTo->offsetForUser( $user );
2489
2490 $ts = '';
2491 if ( Hooks::run( 'GetHumanTimestamp', [ &$ts, $time, $relativeTo, $user, $this ] ) ) {
2492 $ts = $this->getHumanTimestampInternal( $time, $relativeTo, $user );
2493 }
2494
2495 // Reset the timezone on the objects.
2496 $time->timestamp->sub( $offsetThis );
2497 $relativeTo->timestamp->sub( $offsetRel );
2498
2499 return $ts;
2500 }
2501
2514 MWTimestamp $ts, MWTimestamp $relativeTo, User $user
2515 ) {
2516 $diff = $ts->diff( $relativeTo );
2517 $diffDay = (bool)( (int)$ts->timestamp->format( 'w' ) -
2518 (int)$relativeTo->timestamp->format( 'w' ) );
2519 $days = $diff->days ?: (int)$diffDay;
2520 if ( $diff->invert || $days > 5
2521 && $ts->timestamp->format( 'Y' ) !== $relativeTo->timestamp->format( 'Y' )
2522 ) {
2523 // Timestamps are in different years: use full timestamp
2524 // Also do full timestamp for future dates
2528 $format = $this->getDateFormatString( 'both', $user->getDatePreference() ?: 'default' );
2529 $ts = $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) );
2530 } elseif ( $days > 5 ) {
2531 // Timestamps are in same year, but more than 5 days ago: show day and month only.
2532 $format = $this->getDateFormatString( 'pretty', $user->getDatePreference() ?: 'default' );
2533 $ts = $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) );
2534 } elseif ( $days > 1 ) {
2535 // Timestamp within the past week: show the day of the week and time
2536 $format = $this->getDateFormatString( 'time', $user->getDatePreference() ?: 'default' );
2537 $weekday = self::$mWeekdayMsgs[$ts->timestamp->format( 'w' )];
2538 // Messages:
2539 // sunday-at, monday-at, tuesday-at, wednesday-at, thursday-at, friday-at, saturday-at
2540 $ts = wfMessage( "$weekday-at" )
2541 ->inLanguage( $this )
2542 ->params( $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) ) )
2543 ->text();
2544 } elseif ( $days == 1 ) {
2545 // Timestamp was yesterday: say 'yesterday' and the time.
2546 $format = $this->getDateFormatString( 'time', $user->getDatePreference() ?: 'default' );
2547 $ts = wfMessage( 'yesterday-at' )
2548 ->inLanguage( $this )
2549 ->params( $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) ) )
2550 ->text();
2551 } elseif ( $diff->h > 1 || $diff->h == 1 && $diff->i > 30 ) {
2552 // Timestamp was today, but more than 90 minutes ago: say 'today' and the time.
2553 $format = $this->getDateFormatString( 'time', $user->getDatePreference() ?: 'default' );
2554 $ts = wfMessage( 'today-at' )
2555 ->inLanguage( $this )
2556 ->params( $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) ) )
2557 ->text();
2558
2559 // From here on in, the timestamp was soon enough ago so that we can simply say
2560 // XX units ago, e.g., "2 hours ago" or "5 minutes ago"
2561 } elseif ( $diff->h == 1 ) {
2562 // Less than 90 minutes, but more than an hour ago.
2563 $ts = wfMessage( 'hours-ago' )->inLanguage( $this )->numParams( 1 )->text();
2564 } elseif ( $diff->i >= 1 ) {
2565 // A few minutes ago.
2566 $ts = wfMessage( 'minutes-ago' )->inLanguage( $this )->numParams( $diff->i )->text();
2567 } elseif ( $diff->s >= 30 ) {
2568 // Less than a minute, but more than 30 sec ago.
2569 $ts = wfMessage( 'seconds-ago' )->inLanguage( $this )->numParams( $diff->s )->text();
2570 } else {
2571 // Less than 30 seconds ago.
2572 $ts = wfMessage( 'just-now' )->text();
2573 }
2574
2575 return $ts;
2576 }
2577
2582 public function getMessage( $key ) {
2583 return self::$dataCache->getSubitem( $this->mCode, 'messages', $key );
2584 }
2585
2589 function getAllMessages() {
2590 return self::$dataCache->getItem( $this->mCode, 'messages' );
2591 }
2592
2599 public function iconv( $in, $out, $string ) {
2600 # Even with //IGNORE iconv can whine about illegal characters in
2601 # *input* string. We just ignore those too.
2602 # REF: https://bugs.php.net/bug.php?id=37166
2603 # REF: https://phabricator.wikimedia.org/T18885
2604 MediaWiki\suppressWarnings();
2605 $text = iconv( $in, $out . '//IGNORE', $string );
2606 MediaWiki\restoreWarnings();
2607 return $text;
2608 }
2609
2610 // callback functions for ucwords(), ucwordbreaks()
2611
2617 return $this->ucfirst( $matches[1] );
2618 }
2619
2625 return mb_strtoupper( $matches[0] );
2626 }
2627
2633 return mb_strtoupper( $matches[0] );
2634 }
2635
2643 public function ucfirst( $str ) {
2644 $o = ord( $str );
2645 if ( $o < 96 ) { // if already uppercase...
2646 return $str;
2647 } elseif ( $o < 128 ) {
2648 return ucfirst( $str ); // use PHP's ucfirst()
2649 } else {
2650 // fall back to more complex logic in case of multibyte strings
2651 return $this->uc( $str, true );
2652 }
2653 }
2654
2663 public function uc( $str, $first = false ) {
2664 if ( $first ) {
2665 if ( $this->isMultibyte( $str ) ) {
2666 return mb_strtoupper( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
2667 } else {
2668 return ucfirst( $str );
2669 }
2670 } else {
2671 return $this->isMultibyte( $str ) ? mb_strtoupper( $str ) : strtoupper( $str );
2672 }
2673 }
2674
2679 function lcfirst( $str ) {
2680 $o = ord( $str );
2681 if ( !$o ) {
2682 return strval( $str );
2683 } elseif ( $o >= 128 ) {
2684 return $this->lc( $str, true );
2685 } elseif ( $o > 96 ) {
2686 return $str;
2687 } else {
2688 $str[0] = strtolower( $str[0] );
2689 return $str;
2690 }
2691 }
2692
2698 function lc( $str, $first = false ) {
2699 if ( $first ) {
2700 if ( $this->isMultibyte( $str ) ) {
2701 return mb_strtolower( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
2702 } else {
2703 return strtolower( substr( $str, 0, 1 ) ) . substr( $str, 1 );
2704 }
2705 } else {
2706 return $this->isMultibyte( $str ) ? mb_strtolower( $str ) : strtolower( $str );
2707 }
2708 }
2709
2714 function isMultibyte( $str ) {
2715 return strlen( $str ) !== mb_strlen( $str );
2716 }
2717
2722 function ucwords( $str ) {
2723 if ( $this->isMultibyte( $str ) ) {
2724 $str = $this->lc( $str );
2725
2726 // regexp to find first letter in each word (i.e. after each space)
2727 $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)| ([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
2728
2729 // function to use to capitalize a single char
2730 return preg_replace_callback(
2731 $replaceRegexp,
2732 [ $this, 'ucwordsCallbackMB' ],
2733 $str
2734 );
2735 } else {
2736 return ucwords( strtolower( $str ) );
2737 }
2738 }
2739
2746 function ucwordbreaks( $str ) {
2747 if ( $this->isMultibyte( $str ) ) {
2748 $str = $this->lc( $str );
2749
2750 // since \b doesn't work for UTF-8, we explicitely define word break chars
2751 $breaks = "[ \-\‍(\‍)\}\{\.,\?!]";
2752
2753 // find first letter after word break
2754 $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)|" .
2755 "$breaks([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
2756
2757 return preg_replace_callback(
2758 $replaceRegexp,
2759 [ $this, 'ucwordbreaksCallbackMB' ],
2760 $str
2761 );
2762 } else {
2763 return preg_replace_callback(
2764 '/\b([\w\x80-\xff]+)\b/',
2765 [ $this, 'ucwordbreaksCallbackAscii' ],
2766 $str
2767 );
2768 }
2769 }
2770
2786 function caseFold( $s ) {
2787 return $this->uc( $s );
2788 }
2789
2796 if ( is_array( $s ) ) {
2797 throw new MWException( 'Given array to checkTitleEncoding.' );
2798 }
2799 if ( StringUtils::isUtf8( $s ) ) {
2800 return $s;
2801 }
2802
2803 return $this->iconv( $this->fallback8bitEncoding(), 'utf-8', $s );
2804 }
2805
2810 return self::$dataCache->getItem( $this->mCode, 'fallback8bitEncoding' );
2811 }
2812
2821 function hasWordBreaks() {
2822 return true;
2823 }
2824
2832 function segmentByWord( $string ) {
2833 return $string;
2834 }
2835
2843 function normalizeForSearch( $string ) {
2844 return self::convertDoubleWidth( $string );
2845 }
2846
2855 protected static function convertDoubleWidth( $string ) {
2856 static $full = null;
2857 static $half = null;
2858
2859 if ( $full === null ) {
2860 $fullWidth = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
2861 $halfWidth = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
2862 $full = str_split( $fullWidth, 3 );
2863 $half = str_split( $halfWidth );
2864 }
2865
2866 $string = str_replace( $full, $half, $string );
2867 return $string;
2868 }
2869
2875 protected static function insertSpace( $string, $pattern ) {
2876 $string = preg_replace( $pattern, " $1 ", $string );
2877 $string = preg_replace( '/ +/', ' ', $string );
2878 return $string;
2879 }
2880
2885 function convertForSearchResult( $termsArray ) {
2886 # some languages, e.g. Chinese, need to do a conversion
2887 # in order for search results to be displayed correctly
2888 return $termsArray;
2889 }
2890
2897 function firstChar( $s ) {
2898 $matches = [];
2899 preg_match(
2900 '/^([\x00-\x7f]|[\xc0-\xdf][\x80-\xbf]|' .
2901 '[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xf7][\x80-\xbf]{3})/',
2902 $s,
2903 $matches
2904 );
2905
2906 if ( isset( $matches[1] ) ) {
2907 if ( strlen( $matches[1] ) != 3 ) {
2908 return $matches[1];
2909 }
2910
2911 // Break down Hangul syllables to grab the first jamo
2912 $code = UtfNormal\Utils::utf8ToCodepoint( $matches[1] );
2913 if ( $code < 0xac00 || 0xd7a4 <= $code ) {
2914 return $matches[1];
2915 } elseif ( $code < 0xb098 ) {
2916 return "\xe3\x84\xb1";
2917 } elseif ( $code < 0xb2e4 ) {
2918 return "\xe3\x84\xb4";
2919 } elseif ( $code < 0xb77c ) {
2920 return "\xe3\x84\xb7";
2921 } elseif ( $code < 0xb9c8 ) {
2922 return "\xe3\x84\xb9";
2923 } elseif ( $code < 0xbc14 ) {
2924 return "\xe3\x85\x81";
2925 } elseif ( $code < 0xc0ac ) {
2926 return "\xe3\x85\x82";
2927 } elseif ( $code < 0xc544 ) {
2928 return "\xe3\x85\x85";
2929 } elseif ( $code < 0xc790 ) {
2930 return "\xe3\x85\x87";
2931 } elseif ( $code < 0xcc28 ) {
2932 return "\xe3\x85\x88";
2933 } elseif ( $code < 0xce74 ) {
2934 return "\xe3\x85\x8a";
2935 } elseif ( $code < 0xd0c0 ) {
2936 return "\xe3\x85\x8b";
2937 } elseif ( $code < 0xd30c ) {
2938 return "\xe3\x85\x8c";
2939 } elseif ( $code < 0xd558 ) {
2940 return "\xe3\x85\x8d";
2941 } else {
2942 return "\xe3\x85\x8e";
2943 }
2944 } else {
2945 return '';
2946 }
2947 }
2948
2952 function initEncoding() {
2953 // No-op.
2954 }
2955
2961 function recodeForEdit( $s ) {
2962 return $s;
2963 }
2964
2970 function recodeInput( $s ) {
2971 return $s;
2972 }
2973
2985 function normalize( $s ) {
2987 $s = UtfNormal\Validator::cleanUp( $s );
2988 if ( $wgAllUnicodeFixes ) {
2989 $s = $this->transformUsingPairFile( 'normalize-ar.ser', $s );
2990 $s = $this->transformUsingPairFile( 'normalize-ml.ser', $s );
2991 }
2992
2993 return $s;
2994 }
2995
3010 function transformUsingPairFile( $file, $string ) {
3011 if ( !isset( $this->transformData[$file] ) ) {
3012 $data = wfGetPrecompiledData( $file );
3013 if ( $data === false ) {
3014 throw new MWException( __METHOD__ . ": The transformation file $file is missing" );
3015 }
3016 $this->transformData[$file] = new ReplacementArray( $data );
3017 }
3018 return $this->transformData[$file]->replace( $string );
3019 }
3020
3026 function isRTL() {
3027 return self::$dataCache->getItem( $this->mCode, 'rtl' );
3028 }
3029
3034 function getDir() {
3035 return $this->isRTL() ? 'rtl' : 'ltr';
3036 }
3037
3046 function alignStart() {
3047 return $this->isRTL() ? 'right' : 'left';
3048 }
3049
3058 function alignEnd() {
3059 return $this->isRTL() ? 'left' : 'right';
3060 }
3061
3073 function getDirMarkEntity( $opposite = false ) {
3074 if ( $opposite ) {
3075 return $this->isRTL() ? '&lrm;' : '&rlm;';
3076 }
3077 return $this->isRTL() ? '&rlm;' : '&lrm;';
3078 }
3079
3090 function getDirMark( $opposite = false ) {
3091 $lrm = "\xE2\x80\x8E"; # LEFT-TO-RIGHT MARK, commonly abbreviated LRM
3092 $rlm = "\xE2\x80\x8F"; # RIGHT-TO-LEFT MARK, commonly abbreviated RLM
3093 if ( $opposite ) {
3094 return $this->isRTL() ? $lrm : $rlm;
3095 }
3096 return $this->isRTL() ? $rlm : $lrm;
3097 }
3098
3103 return self::$dataCache->getItem( $this->mCode, 'capitalizeAllNouns' );
3104 }
3105
3113 function getArrow( $direction = 'forwards' ) {
3114 switch ( $direction ) {
3115 case 'forwards':
3116 return $this->isRTL() ? '←' : '→';
3117 case 'backwards':
3118 return $this->isRTL() ? '→' : '←';
3119 case 'left':
3120 return '←';
3121 case 'right':
3122 return '→';
3123 case 'up':
3124 return '↑';
3125 case 'down':
3126 return '↓';
3127 }
3128 }
3129
3136 return self::$dataCache->getItem( $this->mCode, 'linkPrefixExtension' );
3137 }
3138
3143 function getMagicWords() {
3144 return self::$dataCache->getItem( $this->mCode, 'magicWords' );
3145 }
3146
3150 protected function doMagicHook() {
3151 if ( $this->mMagicHookDone ) {
3152 return;
3153 }
3154 $this->mMagicHookDone = true;
3155 Hooks::run( 'LanguageGetMagic', [ &$this->mMagicExtensions, $this->getCode() ] );
3156 }
3157
3163 function getMagic( $mw ) {
3164 // Saves a function call
3165 if ( !$this->mMagicHookDone ) {
3166 $this->doMagicHook();
3167 }
3168
3169 if ( isset( $this->mMagicExtensions[$mw->mId] ) ) {
3170 $rawEntry = $this->mMagicExtensions[$mw->mId];
3171 } else {
3172 $rawEntry = self::$dataCache->getSubitem(
3173 $this->mCode, 'magicWords', $mw->mId );
3174 }
3175
3176 if ( !is_array( $rawEntry ) ) {
3177 wfWarn( "\"$rawEntry\" is not a valid magic word for \"$mw->mId\"" );
3178 } else {
3179 $mw->mCaseSensitive = $rawEntry[0];
3180 $mw->mSynonyms = array_slice( $rawEntry, 1 );
3181 }
3182 }
3183
3189 function addMagicWordsByLang( $newWords ) {
3190 $fallbackChain = $this->getFallbackLanguages();
3191 $fallbackChain = array_reverse( $fallbackChain );
3192 foreach ( $fallbackChain as $code ) {
3193 if ( isset( $newWords[$code] ) ) {
3194 $this->mMagicExtensions = $newWords[$code] + $this->mMagicExtensions;
3195 }
3196 }
3197 }
3198
3205 // Cache aliases because it may be slow to load them
3206 if ( is_null( $this->mExtendedSpecialPageAliases ) ) {
3207 // Initialise array
3208 $this->mExtendedSpecialPageAliases =
3209 self::$dataCache->getItem( $this->mCode, 'specialPageAliases' );
3210 Hooks::run( 'LanguageGetSpecialPageAliases',
3211 [ &$this->mExtendedSpecialPageAliases, $this->getCode() ] );
3212 }
3213
3214 return $this->mExtendedSpecialPageAliases;
3215 }
3216
3223 function emphasize( $text ) {
3224 return "<em>$text</em>";
3225 }
3226
3249 public function formatNum( $number, $nocommafy = false ) {
3251 if ( !$nocommafy ) {
3252 $number = $this->commafy( $number );
3253 $s = $this->separatorTransformTable();
3254 if ( $s ) {
3255 $number = strtr( $number, $s );
3256 }
3257 }
3258
3259 if ( $wgTranslateNumerals ) {
3260 $s = $this->digitTransformTable();
3261 if ( $s ) {
3262 $number = strtr( $number, $s );
3263 }
3264 }
3265
3266 return $number;
3267 }
3268
3277 public function formatNumNoSeparators( $number ) {
3278 return $this->formatNum( $number, true );
3279 }
3280
3285 public function parseFormattedNumber( $number ) {
3286 $s = $this->digitTransformTable();
3287 if ( $s ) {
3288 // eliminate empty array values such as ''. (bug 64347)
3289 $s = array_filter( $s );
3290 $number = strtr( $number, array_flip( $s ) );
3291 }
3292
3293 $s = $this->separatorTransformTable();
3294 if ( $s ) {
3295 // eliminate empty array values such as ''. (bug 64347)
3296 $s = array_filter( $s );
3297 $number = strtr( $number, array_flip( $s ) );
3298 }
3299
3300 $number = strtr( $number, [ ',' => '' ] );
3301 return $number;
3302 }
3303
3310 function commafy( $number ) {
3312 if ( $number === null ) {
3313 return '';
3314 }
3315
3316 if ( !$digitGroupingPattern || $digitGroupingPattern === "###,###,###" ) {
3317 // default grouping is at thousands, use the same for ###,###,### pattern too.
3318 return strrev( (string)preg_replace( '/(\d{3})(?=\d)(?!\d*\.)/', '$1,', strrev( $number ) ) );
3319 } else {
3320 // Ref: http://cldr.unicode.org/translation/number-patterns
3321 $sign = "";
3322 if ( intval( $number ) < 0 ) {
3323 // For negative numbers apply the algorithm like positive number and add sign.
3324 $sign = "-";
3325 $number = substr( $number, 1 );
3326 }
3327 $integerPart = [];
3328 $decimalPart = [];
3329 $numMatches = preg_match_all( "/(#+)/", $digitGroupingPattern, $matches );
3330 preg_match( "/\d+/", $number, $integerPart );
3331 preg_match( "/\.\d*/", $number, $decimalPart );
3332 $groupedNumber = ( count( $decimalPart ) > 0 ) ? $decimalPart[0] : "";
3333 if ( $groupedNumber === $number ) {
3334 // the string does not have any number part. Eg: .12345
3335 return $sign . $groupedNumber;
3336 }
3337 $start = $end = ( $integerPart ) ? strlen( $integerPart[0] ) : 0;
3338 while ( $start > 0 ) {
3339 $match = $matches[0][$numMatches - 1];
3340 $matchLen = strlen( $match );
3341 $start = $end - $matchLen;
3342 if ( $start < 0 ) {
3343 $start = 0;
3344 }
3345 $groupedNumber = substr( $number, $start, $end -$start ) . $groupedNumber;
3346 $end = $start;
3347 if ( $numMatches > 1 ) {
3348 // use the last pattern for the rest of the number
3349 $numMatches--;
3350 }
3351 if ( $start > 0 ) {
3352 $groupedNumber = "," . $groupedNumber;
3353 }
3354 }
3355 return $sign . $groupedNumber;
3356 }
3357 }
3358
3363 return self::$dataCache->getItem( $this->mCode, 'digitGroupingPattern' );
3364 }
3365
3370 return self::$dataCache->getItem( $this->mCode, 'digitTransformTable' );
3371 }
3372
3377 return self::$dataCache->getItem( $this->mCode, 'separatorTransformTable' );
3378 }
3379
3389 function listToText( array $l ) {
3390 $m = count( $l ) - 1;
3391 if ( $m < 0 ) {
3392 return '';
3393 }
3394 if ( $m > 0 ) {
3395 $and = $this->msg( 'and' )->escaped();
3396 $space = $this->msg( 'word-separator' )->escaped();
3397 if ( $m > 1 ) {
3398 $comma = $this->msg( 'comma-separator' )->escaped();
3399 }
3400 }
3401 $s = $l[$m];
3402 for ( $i = $m - 1; $i >= 0; $i-- ) {
3403 if ( $i == $m - 1 ) {
3404 $s = $l[$i] . $and . $space . $s;
3405 } else {
3406 $s = $l[$i] . $comma . $s;
3407 }
3408 }
3409 return $s;
3410 }
3411
3418 function commaList( array $list ) {
3419 return implode(
3420 wfMessage( 'comma-separator' )->inLanguage( $this )->escaped(),
3421 $list
3422 );
3423 }
3424
3431 function semicolonList( array $list ) {
3432 return implode(
3433 wfMessage( 'semicolon-separator' )->inLanguage( $this )->escaped(),
3434 $list
3435 );
3436 }
3437
3443 function pipeList( array $list ) {
3444 return implode(
3445 wfMessage( 'pipe-separator' )->inLanguage( $this )->escaped(),
3446 $list
3447 );
3448 }
3449
3467 function truncate( $string, $length, $ellipsis = '...', $adjustLength = true ) {
3468 # Use the localized ellipsis character
3469 if ( $ellipsis == '...' ) {
3470 $ellipsis = wfMessage( 'ellipsis' )->inLanguage( $this )->escaped();
3471 }
3472 # Check if there is no need to truncate
3473 if ( $length == 0 ) {
3474 return $ellipsis; // convention
3475 } elseif ( strlen( $string ) <= abs( $length ) ) {
3476 return $string; // no need to truncate
3477 }
3478 $stringOriginal = $string;
3479 # If ellipsis length is >= $length then we can't apply $adjustLength
3480 if ( $adjustLength && strlen( $ellipsis ) >= abs( $length ) ) {
3481 $string = $ellipsis; // this can be slightly unexpected
3482 # Otherwise, truncate and add ellipsis...
3483 } else {
3484 $eLength = $adjustLength ? strlen( $ellipsis ) : 0;
3485 if ( $length > 0 ) {
3486 $length -= $eLength;
3487 $string = substr( $string, 0, $length ); // xyz...
3488 $string = $this->removeBadCharLast( $string );
3489 $string = rtrim( $string );
3490 $string = $string . $ellipsis;
3491 } else {
3492 $length += $eLength;
3493 $string = substr( $string, $length ); // ...xyz
3494 $string = $this->removeBadCharFirst( $string );
3495 $string = ltrim( $string );
3496 $string = $ellipsis . $string;
3497 }
3498 }
3499 # Do not truncate if the ellipsis makes the string longer/equal (bug 22181).
3500 # This check is *not* redundant if $adjustLength, due to the single case where
3501 # LEN($ellipsis) > ABS($limit arg); $stringOriginal could be shorter than $string.
3502 if ( strlen( $string ) < strlen( $stringOriginal ) ) {
3503 return $string;
3504 } else {
3505 return $stringOriginal;
3506 }
3507 }
3508
3516 protected function removeBadCharLast( $string ) {
3517 if ( $string != '' ) {
3518 $char = ord( $string[strlen( $string ) - 1] );
3519 $m = [];
3520 if ( $char >= 0xc0 ) {
3521 # We got the first byte only of a multibyte char; remove it.
3522 $string = substr( $string, 0, -1 );
3523 } elseif ( $char >= 0x80 &&
3524 // Use the /s modifier (PCRE_DOTALL) so (.*) also matches newlines
3525 preg_match( '/^(.*)(?:[\xe0-\xef][\x80-\xbf]|' .
3526 '[\xf0-\xf7][\x80-\xbf]{1,2})$/s', $string, $m )
3527 ) {
3528 # We chopped in the middle of a character; remove it
3529 $string = $m[1];
3530 }
3531 }
3532 return $string;
3533 }
3534
3542 protected function removeBadCharFirst( $string ) {
3543 if ( $string != '' ) {
3544 $char = ord( $string[0] );
3545 if ( $char >= 0x80 && $char < 0xc0 ) {
3546 # We chopped in the middle of a character; remove the whole thing
3547 $string = preg_replace( '/^[\x80-\xbf]+/', '', $string );
3548 }
3549 }
3550 return $string;
3551 }
3552
3568 function truncateHtml( $text, $length, $ellipsis = '...' ) {
3569 # Use the localized ellipsis character
3570 if ( $ellipsis == '...' ) {
3571 $ellipsis = wfMessage( 'ellipsis' )->inLanguage( $this )->escaped();
3572 }
3573 # Check if there is clearly no need to truncate
3574 if ( $length <= 0 ) {
3575 return $ellipsis; // no text shown, nothing to format (convention)
3576 } elseif ( strlen( $text ) <= $length ) {
3577 return $text; // string short enough even *with* HTML (short-circuit)
3578 }
3579
3580 $dispLen = 0; // innerHTML legth so far
3581 $testingEllipsis = false; // checking if ellipses will make string longer/equal?
3582 $tagType = 0; // 0-open, 1-close
3583 $bracketState = 0; // 1-tag start, 2-tag name, 0-neither
3584 $entityState = 0; // 0-not entity, 1-entity
3585 $tag = $ret = ''; // accumulated tag name, accumulated result string
3586 $openTags = []; // open tag stack
3587 $maybeState = null; // possible truncation state
3588
3589 $textLen = strlen( $text );
3590 $neLength = max( 0, $length - strlen( $ellipsis ) ); // non-ellipsis len if truncated
3591 for ( $pos = 0; true; ++$pos ) {
3592 # Consider truncation once the display length has reached the maximim.
3593 # We check if $dispLen > 0 to grab tags for the $neLength = 0 case.
3594 # Check that we're not in the middle of a bracket/entity...
3595 if ( $dispLen && $dispLen >= $neLength && $bracketState == 0 && !$entityState ) {
3596 if ( !$testingEllipsis ) {
3597 $testingEllipsis = true;
3598 # Save where we are; we will truncate here unless there turn out to
3599 # be so few remaining characters that truncation is not necessary.
3600 if ( !$maybeState ) { // already saved? ($neLength = 0 case)
3601 $maybeState = [ $ret, $openTags ]; // save state
3602 }
3603 } elseif ( $dispLen > $length && $dispLen > strlen( $ellipsis ) ) {
3604 # String in fact does need truncation, the truncation point was OK.
3605 list( $ret, $openTags ) = $maybeState; // reload state
3606 $ret = $this->removeBadCharLast( $ret ); // multi-byte char fix
3607 $ret .= $ellipsis; // add ellipsis
3608 break;
3609 }
3610 }
3611 if ( $pos >= $textLen ) {
3612 break; // extra iteration just for above checks
3613 }
3614
3615 # Read the next char...
3616 $ch = $text[$pos];
3617 $lastCh = $pos ? $text[$pos - 1] : '';
3618 $ret .= $ch; // add to result string
3619 if ( $ch == '<' ) {
3620 $this->truncate_endBracket( $tag, $tagType, $lastCh, $openTags ); // for bad HTML
3621 $entityState = 0; // for bad HTML
3622 $bracketState = 1; // tag started (checking for backslash)
3623 } elseif ( $ch == '>' ) {
3624 $this->truncate_endBracket( $tag, $tagType, $lastCh, $openTags );
3625 $entityState = 0; // for bad HTML
3626 $bracketState = 0; // out of brackets
3627 } elseif ( $bracketState == 1 ) {
3628 if ( $ch == '/' ) {
3629 $tagType = 1; // close tag (e.g. "</span>")
3630 } else {
3631 $tagType = 0; // open tag (e.g. "<span>")
3632 $tag .= $ch;
3633 }
3634 $bracketState = 2; // building tag name
3635 } elseif ( $bracketState == 2 ) {
3636 if ( $ch != ' ' ) {
3637 $tag .= $ch;
3638 } else {
3639 // Name found (e.g. "<a href=..."), add on tag attributes...
3640 $pos += $this->truncate_skip( $ret, $text, "<>", $pos + 1 );
3641 }
3642 } elseif ( $bracketState == 0 ) {
3643 if ( $entityState ) {
3644 if ( $ch == ';' ) {
3645 $entityState = 0;
3646 $dispLen++; // entity is one displayed char
3647 }
3648 } else {
3649 if ( $neLength == 0 && !$maybeState ) {
3650 // Save state without $ch. We want to *hit* the first
3651 // display char (to get tags) but not *use* it if truncating.
3652 $maybeState = [ substr( $ret, 0, -1 ), $openTags ];
3653 }
3654 if ( $ch == '&' ) {
3655 $entityState = 1; // entity found, (e.g. "&#160;")
3656 } else {
3657 $dispLen++; // this char is displayed
3658 // Add the next $max display text chars after this in one swoop...
3659 $max = ( $testingEllipsis ? $length : $neLength ) - $dispLen;
3660 $skipped = $this->truncate_skip( $ret, $text, "<>&", $pos + 1, $max );
3661 $dispLen += $skipped;
3662 $pos += $skipped;
3663 }
3664 }
3665 }
3666 }
3667 // Close the last tag if left unclosed by bad HTML
3668 $this->truncate_endBracket( $tag, $text[$textLen - 1], $tagType, $openTags );
3669 while ( count( $openTags ) > 0 ) {
3670 $ret .= '</' . array_pop( $openTags ) . '>'; // close open tags
3671 }
3672 return $ret;
3673 }
3674
3686 private function truncate_skip( &$ret, $text, $search, $start, $len = null ) {
3687 if ( $len === null ) {
3688 $len = -1; // -1 means "no limit" for strcspn
3689 } elseif ( $len < 0 ) {
3690 $len = 0; // sanity
3691 }
3692 $skipCount = 0;
3693 if ( $start < strlen( $text ) ) {
3694 $skipCount = strcspn( $text, $search, $start, $len );
3695 $ret .= substr( $text, $start, $skipCount );
3696 }
3697 return $skipCount;
3698 }
3699
3709 private function truncate_endBracket( &$tag, $tagType, $lastCh, &$openTags ) {
3710 $tag = ltrim( $tag );
3711 if ( $tag != '' ) {
3712 if ( $tagType == 0 && $lastCh != '/' ) {
3713 $openTags[] = $tag; // tag opened (didn't close itself)
3714 } elseif ( $tagType == 1 ) {
3715 if ( $openTags && $tag == $openTags[count( $openTags ) - 1] ) {
3716 array_pop( $openTags ); // tag closed
3717 }
3718 }
3719 $tag = '';
3720 }
3721 }
3722
3731 function convertGrammar( $word, $case ) {
3733 if ( isset( $wgGrammarForms[$this->getCode()][$case][$word] ) ) {
3734 return $wgGrammarForms[$this->getCode()][$case][$word];
3735 }
3736
3737 return $word;
3738 }
3739
3745 function getGrammarForms() {
3747 if ( isset( $wgGrammarForms[$this->getCode()] )
3748 && is_array( $wgGrammarForms[$this->getCode()] )
3749 ) {
3750 return $wgGrammarForms[$this->getCode()];
3751 }
3752
3753 return [];
3754 }
3755
3765 public function getGrammarTransformations() {
3766 $languageCode = $this->getCode();
3767
3768 if ( self::$grammarTransformations === null ) {
3769 self::$grammarTransformations = new MapCacheLRU( 10 );
3770 }
3771
3772 if ( self::$grammarTransformations->has( $languageCode ) ) {
3773 return self::$grammarTransformations->get( $languageCode );
3774 }
3775
3776 $data = [];
3777
3778 $grammarDataFile = __DIR__ . "/data/grammarTransformations/$languageCode.json";
3779 if ( is_readable( $grammarDataFile ) ) {
3780 $data = FormatJson::decode(
3781 file_get_contents( $grammarDataFile ),
3782 true
3783 );
3784
3785 if ( $data === null ) {
3786 throw new MWException( "Invalid grammar data for \"$languageCode\"." );
3787 }
3788
3789 self::$grammarTransformations->set( $languageCode, $data );
3790 }
3791
3792 return $data;
3793 }
3794
3814 function gender( $gender, $forms ) {
3815 if ( !count( $forms ) ) {
3816 return '';
3817 }
3818 $forms = $this->preConvertPlural( $forms, 2 );
3819 if ( $gender === 'male' ) {
3820 return $forms[0];
3821 }
3822 if ( $gender === 'female' ) {
3823 return $forms[1];
3824 }
3825 return isset( $forms[2] ) ? $forms[2] : $forms[0];
3826 }
3827
3843 function convertPlural( $count, $forms ) {
3844 // Handle explicit n=pluralform cases
3845 $forms = $this->handleExplicitPluralForms( $count, $forms );
3846 if ( is_string( $forms ) ) {
3847 return $forms;
3848 }
3849 if ( !count( $forms ) ) {
3850 return '';
3851 }
3852
3853 $pluralForm = $this->getPluralRuleIndexNumber( $count );
3854 $pluralForm = min( $pluralForm, count( $forms ) - 1 );
3855 return $forms[$pluralForm];
3856 }
3857
3873 protected function handleExplicitPluralForms( $count, array $forms ) {
3874 foreach ( $forms as $index => $form ) {
3875 if ( preg_match( '/\d+=/i', $form ) ) {
3876 $pos = strpos( $form, '=' );
3877 if ( substr( $form, 0, $pos ) === (string)$count ) {
3878 return substr( $form, $pos + 1 );
3879 }
3880 unset( $forms[$index] );
3881 }
3882 }
3883 return array_values( $forms );
3884 }
3885
3894 protected function preConvertPlural( /* Array */ $forms, $count ) {
3895 while ( count( $forms ) < $count ) {
3896 $forms[] = $forms[count( $forms ) - 1];
3897 }
3898 return $forms;
3899 }
3900
3917 public function embedBidi( $text = '' ) {
3919 if ( $dir === 'ltr' ) {
3920 // Wrap in LEFT-TO-RIGHT EMBEDDING ... POP DIRECTIONAL FORMATTING
3921 return self::$lre . $text . self::$pdf;
3922 }
3923 if ( $dir === 'rtl' ) {
3924 // Wrap in RIGHT-TO-LEFT EMBEDDING ... POP DIRECTIONAL FORMATTING
3925 return self::$rle . $text . self::$pdf;
3926 }
3927 // No strong directionality: do not wrap
3928 return $text;
3929 }
3930
3943 function translateBlockExpiry( $str, User $user = null ) {
3944 $duration = SpecialBlock::getSuggestedDurations( $this );
3945 foreach ( $duration as $show => $value ) {
3946 if ( strcmp( $str, $value ) == 0 ) {
3947 return htmlspecialchars( trim( $show ) );
3948 }
3949 }
3950
3951 if ( wfIsInfinity( $str ) ) {
3952 foreach ( $duration as $show => $value ) {
3953 if ( wfIsInfinity( $value ) ) {
3954 return htmlspecialchars( trim( $show ) );
3955 }
3956 }
3957 }
3958
3959 // If all else fails, return a standard duration or timestamp description.
3960 $time = strtotime( $str, 0 );
3961 if ( $time === false ) { // Unknown format. Return it as-is in case.
3962 return $str;
3963 } elseif ( $time !== strtotime( $str, 1 ) ) { // It's a relative timestamp.
3964 // $time is relative to 0 so it's a duration length.
3965 return $this->formatDuration( $time );
3966 } else { // It's an absolute timestamp.
3967 if ( $time === 0 ) {
3968 // wfTimestamp() handles 0 as current time instead of epoch.
3969 $time = '19700101000000';
3970 }
3971 if ( $user ) {
3972 return $this->userTimeAndDate( $time, $user );
3973 }
3974 return $this->timeanddate( $time );
3975 }
3976 }
3977
3985 public function segmentForDiff( $text ) {
3986 return $text;
3987 }
3988
3995 public function unsegmentForDiff( $text ) {
3996 return $text;
3997 }
3998
4005 public function getConverter() {
4006 return $this->mConverter;
4007 }
4008
4015 public function autoConvertToAllVariants( $text ) {
4016 return $this->mConverter->autoConvertToAllVariants( $text );
4017 }
4018
4025 public function convert( $text ) {
4026 return $this->mConverter->convert( $text );
4027 }
4028
4035 public function convertTitle( $title ) {
4036 return $this->mConverter->convertTitle( $title );
4037 }
4038
4045 public function convertNamespace( $ns ) {
4046 return $this->mConverter->convertNamespace( $ns );
4047 }
4048
4054 public function hasVariants() {
4055 return count( $this->getVariants() ) > 1;
4056 }
4057
4065 public function hasVariant( $variant ) {
4066 return (bool)$this->mConverter->validateVariant( $variant );
4067 }
4068
4076 public function convertHtml( $text, $isTitle = false ) {
4077 return htmlspecialchars( $this->convert( $text, $isTitle ) );
4078 }
4079
4084 public function convertCategoryKey( $key ) {
4085 return $this->mConverter->convertCategoryKey( $key );
4086 }
4087
4094 public function getVariants() {
4095 return $this->mConverter->getVariants();
4096 }
4097
4101 public function getPreferredVariant() {
4102 return $this->mConverter->getPreferredVariant();
4103 }
4104
4108 public function getDefaultVariant() {
4109 return $this->mConverter->getDefaultVariant();
4110 }
4111
4115 public function getURLVariant() {
4116 return $this->mConverter->getURLVariant();
4117 }
4118
4131 public function findVariantLink( &$link, &$nt, $ignoreOtherCond = false ) {
4132 $this->mConverter->findVariantLink( $link, $nt, $ignoreOtherCond );
4133 }
4134
4142 return $this->mConverter->getExtraHashOptions();
4143 }
4144
4152 public function getParsedTitle() {
4153 return $this->mConverter->getParsedTitle();
4154 }
4155
4162 public function updateConversionTable( Title $title ) {
4163 $this->mConverter->updateConversionTable( $title );
4164 }
4165
4178 public function markNoConversion( $text, $noParse = false ) {
4179 // Excluding protocal-relative URLs may avoid many false positives.
4180 if ( $noParse || preg_match( '/^(?:' . wfUrlProtocolsWithoutProtRel() . ')/', $text ) ) {
4181 return $this->mConverter->markNoConversion( $text );
4182 } else {
4183 return $text;
4184 }
4185 }
4186
4193 public function linkTrail() {
4194 return self::$dataCache->getItem( $this->mCode, 'linkTrail' );
4195 }
4196
4203 public function linkPrefixCharset() {
4204 return self::$dataCache->getItem( $this->mCode, 'linkPrefixCharset' );
4205 }
4206
4214 public function getParentLanguage() {
4215 if ( $this->mParentLanguage !== false ) {
4216 return $this->mParentLanguage;
4217 }
4218
4219 $code = explode( '-', $this->getCode() )[0];
4221 $this->mParentLanguage = null;
4222 return null;
4223 }
4225 if ( !$lang->hasVariant( $this->getCode() ) ) {
4226 $this->mParentLanguage = null;
4227 return null;
4228 }
4229
4230 $this->mParentLanguage = $lang;
4231 return $lang;
4232 }
4233
4241 public function equals( Language $lang ) {
4242 return $lang->getCode() === $this->mCode;
4243 }
4244
4253 public function getCode() {
4254 return $this->mCode;
4255 }
4256
4267 public function getHtmlCode() {
4268 if ( is_null( $this->mHtmlCode ) ) {
4269 $this->mHtmlCode = wfBCP47( $this->getCode() );
4270 }
4271 return $this->mHtmlCode;
4272 }
4273
4277 public function setCode( $code ) {
4278 $this->mCode = $code;
4279 // Ensure we don't leave incorrect cached data lying around
4280 $this->mHtmlCode = null;
4281 $this->mParentLanguage = false;
4282 }
4283
4291 public static function getCodeFromFileName( $filename, $prefix = 'Language', $suffix = '.php' ) {
4292 $m = null;
4293 preg_match( '/' . preg_quote( $prefix, '/' ) . '([A-Z][a-z_]+)' .
4294 preg_quote( $suffix, '/' ) . '/', $filename, $m );
4295 if ( !count( $m ) ) {
4296 return false;
4297 }
4298 return str_replace( '_', '-', strtolower( $m[1] ) );
4299 }
4300
4305 public static function classFromCode( $code ) {
4306 if ( $code == 'en' ) {
4307 return 'Language';
4308 } else {
4309 return 'Language' . str_replace( '-', '_', ucfirst( $code ) );
4310 }
4311 }
4312
4321 public static function getFileName( $prefix = 'Language', $code, $suffix = '.php' ) {
4322 if ( !self::isValidBuiltInCode( $code ) ) {
4323 throw new MWException( "Invalid language code \"$code\"" );
4324 }
4325
4326 return $prefix . str_replace( '-', '_', ucfirst( $code ) ) . $suffix;
4327 }
4328
4333 public static function getMessagesFileName( $code ) {
4334 global $IP;
4335 $file = self::getFileName( "$IP/languages/messages/Messages", $code, '.php' );
4336 Hooks::run( 'Language::getMessagesFileName', [ $code, &$file ] );
4337 return $file;
4338 }
4339
4346 public static function getJsonMessagesFileName( $code ) {
4347 global $IP;
4348
4349 if ( !self::isValidBuiltInCode( $code ) ) {
4350 throw new MWException( "Invalid language code \"$code\"" );
4351 }
4352
4353 return "$IP/languages/i18n/$code.json";
4354 }
4355
4363 public static function getFallbackFor( $code ) {
4364 $fallbacks = self::getFallbacksFor( $code );
4365 if ( $fallbacks ) {
4366 return $fallbacks[0];
4367 }
4368 return false;
4369 }
4370
4378 public static function getFallbacksFor( $code ) {
4379 if ( $code === 'en' || !Language::isValidBuiltInCode( $code ) ) {
4380 return [];
4381 }
4382 // For unknown languages, fallbackSequence returns an empty array,
4383 // hardcode fallback to 'en' in that case.
4384 return self::getLocalisationCache()->getItem( $code, 'fallbackSequence' ) ?: [ 'en' ];
4385 }
4386
4395 public static function getFallbacksIncludingSiteLanguage( $code ) {
4397
4398 // Usually, we will only store a tiny number of fallback chains, so we
4399 // keep them in static memory.
4400 $cacheKey = "{$code}-{$wgLanguageCode}";
4401
4402 if ( !array_key_exists( $cacheKey, self::$fallbackLanguageCache ) ) {
4403 $fallbacks = self::getFallbacksFor( $code );
4404
4405 // Append the site's fallback chain, including the site language itself
4406 $siteFallbacks = self::getFallbacksFor( $wgLanguageCode );
4407 array_unshift( $siteFallbacks, $wgLanguageCode );
4408
4409 // Eliminate any languages already included in the chain
4410 $siteFallbacks = array_diff( $siteFallbacks, $fallbacks );
4411
4412 self::$fallbackLanguageCache[$cacheKey] = [ $fallbacks, $siteFallbacks ];
4413 }
4414 return self::$fallbackLanguageCache[$cacheKey];
4415 }
4416
4426 public static function getMessagesFor( $code ) {
4427 return self::getLocalisationCache()->getItem( $code, 'messages' );
4428 }
4429
4438 public static function getMessageFor( $key, $code ) {
4439 return self::getLocalisationCache()->getSubitem( $code, 'messages', $key );
4440 }
4441
4450 public static function getMessageKeysFor( $code ) {
4451 return self::getLocalisationCache()->getSubitemList( $code, 'messages' );
4452 }
4453
4458 function fixVariableInNamespace( $talk ) {
4459 if ( strpos( $talk, '$1' ) === false ) {
4460 return $talk;
4461 }
4462
4464 $talk = str_replace( '$1', $wgMetaNamespace, $talk );
4465
4466 # Allow grammar transformations
4467 # Allowing full message-style parsing would make simple requests
4468 # such as action=raw much more expensive than they need to be.
4469 # This will hopefully cover most cases.
4470 $talk = preg_replace_callback( '/{{grammar:(.*?)\|(.*?)}}/i',
4471 [ $this, 'replaceGrammarInNamespace' ], $talk );
4472 return str_replace( ' ', '_', $talk );
4473 }
4474
4480 return $this->convertGrammar( trim( $m[2] ), trim( $m[1] ) );
4481 }
4482
4493 public function formatExpiry( $expiry, $format = true, $infinity = 'infinity' ) {
4494 static $dbInfinity;
4495 if ( $dbInfinity === null ) {
4496 $dbInfinity = wfGetDB( DB_SLAVE )->getInfinity();
4497 }
4498
4499 if ( $expiry == '' || $expiry === 'infinity' || $expiry == $dbInfinity ) {
4500 return $format === true
4501 ? $this->getMessageFromDB( 'infiniteblock' )
4502 : $infinity;
4503 } else {
4504 return $format === true
4505 ? $this->timeanddate( $expiry, /* User preference timezone */ true )
4506 : wfTimestamp( $format, $expiry );
4507 }
4508 }
4509
4523 function formatTimePeriod( $seconds, $format = [] ) {
4524 if ( !is_array( $format ) ) {
4525 $format = [ 'avoid' => $format ]; // For backwards compatibility
4526 }
4527 if ( !isset( $format['avoid'] ) ) {
4528 $format['avoid'] = false;
4529 }
4530 if ( !isset( $format['noabbrevs'] ) ) {
4531 $format['noabbrevs'] = false;
4532 }
4533 $secondsMsg = wfMessage(
4534 $format['noabbrevs'] ? 'seconds' : 'seconds-abbrev' )->inLanguage( $this );
4535 $minutesMsg = wfMessage(
4536 $format['noabbrevs'] ? 'minutes' : 'minutes-abbrev' )->inLanguage( $this );
4537 $hoursMsg = wfMessage(
4538 $format['noabbrevs'] ? 'hours' : 'hours-abbrev' )->inLanguage( $this );
4539 $daysMsg = wfMessage(
4540 $format['noabbrevs'] ? 'days' : 'days-abbrev' )->inLanguage( $this );
4541
4542 if ( round( $seconds * 10 ) < 100 ) {
4543 $s = $this->formatNum( sprintf( "%.1f", round( $seconds * 10 ) / 10 ) );
4544 $s = $secondsMsg->params( $s )->text();
4545 } elseif ( round( $seconds ) < 60 ) {
4546 $s = $this->formatNum( round( $seconds ) );
4547 $s = $secondsMsg->params( $s )->text();
4548 } elseif ( round( $seconds ) < 3600 ) {
4549 $minutes = floor( $seconds / 60 );
4550 $secondsPart = round( fmod( $seconds, 60 ) );
4551 if ( $secondsPart == 60 ) {
4552 $secondsPart = 0;
4553 $minutes++;
4554 }
4555 $s = $minutesMsg->params( $this->formatNum( $minutes ) )->text();
4556 $s .= ' ';
4557 $s .= $secondsMsg->params( $this->formatNum( $secondsPart ) )->text();
4558 } elseif ( round( $seconds ) <= 2 * 86400 ) {
4559 $hours = floor( $seconds / 3600 );
4560 $minutes = floor( ( $seconds - $hours * 3600 ) / 60 );
4561 $secondsPart = round( $seconds - $hours * 3600 - $minutes * 60 );
4562 if ( $secondsPart == 60 ) {
4563 $secondsPart = 0;
4564 $minutes++;
4565 }
4566 if ( $minutes == 60 ) {
4567 $minutes = 0;
4568 $hours++;
4569 }
4570 $s = $hoursMsg->params( $this->formatNum( $hours ) )->text();
4571 $s .= ' ';
4572 $s .= $minutesMsg->params( $this->formatNum( $minutes ) )->text();
4573 if ( !in_array( $format['avoid'], [ 'avoidseconds', 'avoidminutes' ] ) ) {
4574 $s .= ' ' . $secondsMsg->params( $this->formatNum( $secondsPart ) )->text();
4575 }
4576 } else {
4577 $days = floor( $seconds / 86400 );
4578 if ( $format['avoid'] === 'avoidminutes' ) {
4579 $hours = round( ( $seconds - $days * 86400 ) / 3600 );
4580 if ( $hours == 24 ) {
4581 $hours = 0;
4582 $days++;
4583 }
4584 $s = $daysMsg->params( $this->formatNum( $days ) )->text();
4585 $s .= ' ';
4586 $s .= $hoursMsg->params( $this->formatNum( $hours ) )->text();
4587 } elseif ( $format['avoid'] === 'avoidseconds' ) {
4588 $hours = floor( ( $seconds - $days * 86400 ) / 3600 );
4589 $minutes = round( ( $seconds - $days * 86400 - $hours * 3600 ) / 60 );
4590 if ( $minutes == 60 ) {
4591 $minutes = 0;
4592 $hours++;
4593 }
4594 if ( $hours == 24 ) {
4595 $hours = 0;
4596 $days++;
4597 }
4598 $s = $daysMsg->params( $this->formatNum( $days ) )->text();
4599 $s .= ' ';
4600 $s .= $hoursMsg->params( $this->formatNum( $hours ) )->text();
4601 $s .= ' ';
4602 $s .= $minutesMsg->params( $this->formatNum( $minutes ) )->text();
4603 } else {
4604 $s = $daysMsg->params( $this->formatNum( $days ) )->text();
4605 $s .= ' ';
4606 $s .= $this->formatTimePeriod( $seconds - $days * 86400, $format );
4607 }
4608 }
4609 return $s;
4610 }
4611
4623 function formatBitrate( $bps ) {
4624 return $this->formatComputingNumbers( $bps, 1000, "bitrate-$1bits" );
4625 }
4626
4633 function formatComputingNumbers( $size, $boundary, $messageKey ) {
4634 if ( $size <= 0 ) {
4635 return str_replace( '$1', $this->formatNum( $size ),
4636 $this->getMessageFromDB( str_replace( '$1', '', $messageKey ) )
4637 );
4638 }
4639 $sizes = [ '', 'kilo', 'mega', 'giga', 'tera', 'peta', 'exa', 'zeta', 'yotta' ];
4640 $index = 0;
4641
4642 $maxIndex = count( $sizes ) - 1;
4643 while ( $size >= $boundary && $index < $maxIndex ) {
4644 $index++;
4645 $size /= $boundary;
4646 }
4647
4648 // For small sizes no decimal places necessary
4649 $round = 0;
4650 if ( $index > 1 ) {
4651 // For MB and bigger two decimal places are smarter
4652 $round = 2;
4653 }
4654 $msg = str_replace( '$1', $sizes[$index], $messageKey );
4655
4656 $size = round( $size, $round );
4657 $text = $this->getMessageFromDB( $msg );
4658 return str_replace( '$1', $this->formatNum( $size ), $text );
4659 }
4660
4671 function formatSize( $size ) {
4672 return $this->formatComputingNumbers( $size, 1024, "size-$1bytes" );
4673 }
4674
4684 function specialList( $page, $details, $oppositedm = true ) {
4685 if ( !$details ) {
4686 return $page;
4687 }
4688
4689 $dirmark = ( $oppositedm ? $this->getDirMark( true ) : '' ) . $this->getDirMark();
4690 return
4691 $page .
4692 $dirmark .
4693 $this->msg( 'word-separator' )->escaped() .
4694 $this->msg( 'parentheses' )->rawParams( $details )->escaped();
4695 }
4696
4707 public function viewPrevNext( Title $title, $offset, $limit,
4708 array $query = [], $atend = false
4709 ) {
4710 // @todo FIXME: Why on earth this needs one message for the text and another one for tooltip?
4711
4712 # Make 'previous' link
4713 $prev = wfMessage( 'prevn' )->inLanguage( $this )->title( $title )->numParams( $limit )->text();
4714 if ( $offset > 0 ) {
4715 $plink = $this->numLink( $title, max( $offset - $limit, 0 ), $limit,
4716 $query, $prev, 'prevn-title', 'mw-prevlink' );
4717 } else {
4718 $plink = htmlspecialchars( $prev );
4719 }
4720
4721 # Make 'next' link
4722 $next = wfMessage( 'nextn' )->inLanguage( $this )->title( $title )->numParams( $limit )->text();
4723 if ( $atend ) {
4724 $nlink = htmlspecialchars( $next );
4725 } else {
4726 $nlink = $this->numLink( $title, $offset + $limit, $limit,
4727 $query, $next, 'nextn-title', 'mw-nextlink' );
4728 }
4729
4730 # Make links to set number of items per page
4731 $numLinks = [];
4732 foreach ( [ 20, 50, 100, 250, 500 ] as $num ) {
4733 $numLinks[] = $this->numLink( $title, $offset, $num,
4734 $query, $this->formatNum( $num ), 'shown-title', 'mw-numlink' );
4735 }
4736
4737 return wfMessage( 'viewprevnext' )->inLanguage( $this )->title( $title
4738 )->rawParams( $plink, $nlink, $this->pipeList( $numLinks ) )->escaped();
4739 }
4740
4753 private function numLink( Title $title, $offset, $limit, array $query, $link,
4754 $tooltipMsg, $class
4755 ) {
4756 $query = [ 'limit' => $limit, 'offset' => $offset ] + $query;
4757 $tooltip = wfMessage( $tooltipMsg )->inLanguage( $this )->title( $title )
4758 ->numParams( $limit )->text();
4759
4760 return Html::element( 'a', [ 'href' => $title->getLocalURL( $query ),
4761 'title' => $tooltip, 'class' => $class ], $link );
4762 }
4763
4769 public function getConvRuleTitle() {
4770 return $this->mConverter->getConvRuleTitle();
4771 }
4772
4778 public function getCompiledPluralRules() {
4779 $pluralRules = self::$dataCache->getItem( strtolower( $this->mCode ), 'compiledPluralRules' );
4780 $fallbacks = Language::getFallbacksFor( $this->mCode );
4781 if ( !$pluralRules ) {
4782 foreach ( $fallbacks as $fallbackCode ) {
4783 $pluralRules = self::$dataCache->getItem( strtolower( $fallbackCode ), 'compiledPluralRules' );
4784 if ( $pluralRules ) {
4785 break;
4786 }
4787 }
4788 }
4789 return $pluralRules;
4790 }
4791
4797 public function getPluralRules() {
4798 $pluralRules = self::$dataCache->getItem( strtolower( $this->mCode ), 'pluralRules' );
4799 $fallbacks = Language::getFallbacksFor( $this->mCode );
4800 if ( !$pluralRules ) {
4801 foreach ( $fallbacks as $fallbackCode ) {
4802 $pluralRules = self::$dataCache->getItem( strtolower( $fallbackCode ), 'pluralRules' );
4803 if ( $pluralRules ) {
4804 break;
4805 }
4806 }
4807 }
4808 return $pluralRules;
4809 }
4810
4816 public function getPluralRuleTypes() {
4817 $pluralRuleTypes = self::$dataCache->getItem( strtolower( $this->mCode ), 'pluralRuleTypes' );
4818 $fallbacks = Language::getFallbacksFor( $this->mCode );
4819 if ( !$pluralRuleTypes ) {
4820 foreach ( $fallbacks as $fallbackCode ) {
4821 $pluralRuleTypes = self::$dataCache->getItem( strtolower( $fallbackCode ), 'pluralRuleTypes' );
4822 if ( $pluralRuleTypes ) {
4823 break;
4824 }
4825 }
4826 }
4827 return $pluralRuleTypes;
4828 }
4829
4835 public function getPluralRuleIndexNumber( $number ) {
4836 $pluralRules = $this->getCompiledPluralRules();
4837 $form = Evaluator::evaluateCompiled( $number, $pluralRules );
4838 return $form;
4839 }
4840
4849 public function getPluralRuleType( $number ) {
4850 $index = $this->getPluralRuleIndexNumber( $number );
4851 $pluralRuleTypes = $this->getPluralRuleTypes();
4852 if ( isset( $pluralRuleTypes[$index] ) ) {
4853 return $pluralRuleTypes[$index];
4854 } else {
4855 return 'other';
4856 }
4857 }
4858}
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
List of language codes that don't correspond to an actual language.
$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...
const DB_SLAVE
Definition Defines.php:28
wfWarn( $msg, $callerOffset=1, $level=E_USER_NOTICE)
Send a warning either to the debug log or in a PHP error depending on $wgDevelopmentWarnings.
wfBCP47( $code)
Get the normalised IETF language tag See unit test for examples.
wfUrlProtocolsWithoutProtRel()
Like wfUrlProtocols(), but excludes '//' from the protocol list.
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
wfGetPrecompiledData( $name)
Get an object from the precompiled serialized directory.
wfIsInfinity( $str)
Determine input string is represents as infinity.
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
$namespaceAliases
$namespaceNames
$digitGroupingPattern
$wgUser
Definition Setup.php:806
$IP
Definition WebStart.php:58
format( $format)
Format the timestamp in a given format.
diff(ConvertibleTimestamp $relativeTo)
Calculate the difference between two ConvertibleTimestamp objects.
getTimestamp( $style=TS_UNIX)
Get the timestamp represented by this object in a certain form.
A fake language converter.
Simple store for keeping values in an associative array for the current process.
Base class for language conversion.
static array $languagesWithVariants
languages supporting variants
Internationalisation code.
Definition Language.php:35
hasVariants()
Check if this is a language with variants.
translateBlockExpiry( $str, User $user=null)
initContLang()
Hook which will be called if this is the content language.
Definition Language.php:437
static $mWeekdayMsgs
Definition Language.php:62
getLocalNsIndex( $text)
Get a namespace key by value, case insensitive.
Definition Language.php:610
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:172
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)
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:281
getVariantname( $code, $usemsg=true)
short names for language variants used for language conversion links.
Definition Language.php:710
static $mHebrewCalendarMonthGenMsgs
Definition Language.php:101
static isValidBuiltInCode( $code)
Returns true if a language code is of a valid form for the purposes of internal customisation of Medi...
Definition Language.php:360
recodeForEdit( $s)
$mParentLanguage
Definition Language.php:43
getWeekdayName( $key)
Definition Language.php:964
getHebrewCalendarMonthName( $key)
Definition Language.php:988
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.
ucwords( $str)
formatNum( $number, $nocommafy=false)
Normally we output all numbers in plain en_US style, that is 293,291.235 for twohundredninetythreetho...
static getFallbacksFor( $code)
Get the ordered list of fallback languages.
static $pdf
Definition Language.php:157
digitGroupingPattern()
static array $fallbackLanguageCache
Cache for language fallbacks.
Definition Language.php:138
getMonthAbbreviation( $key)
Definition Language.php:945
getHebrewCalendarMonthNameGen( $key)
Definition Language.php:996
static $lre
Unicode directional formatting characters, for embedBidi()
Definition Language.php:155
getSpecialPageAliases()
Get special page names, as an associative array canonical name => array of valid names,...
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:253
hasVariant( $variant)
Check if the language has the specific variant.
setCode( $code)
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)
static fetchLanguageName( $code, $inLanguage=null, $include='all')
Definition Language.php:888
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()
static getFileName( $prefix='Language', $code, $suffix='.php')
Get the name of a file for a certain language code.
ucwordbreaksCallbackAscii( $matches)
lc( $str, $first=false)
fallback8bitEncoding()
static LocalisationCache $dataCache
Definition Language.php:58
listToText(array $l)
Take a list of strings and build a locale-friendly comma-separated list, using the local comma-separa...
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:571
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:427
getFormattedNsText( $index)
A convenience function that returns the same thing as getNsText() except with '_' changed to ' ',...
Definition Language.php:558
getURLVariant()
getMessageFromDB( $msg)
Get a message from the MediaWiki namespace.
Definition Language.php:900
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:413
getFormattedNamespaces()
A convenience function that returns getNamespaces() with spaces instead of underscores in values.
Definition Language.php:521
getExtraHashOptions()
returns language specific options used by User::getPageRenderHash() for example, the preferred langua...
$transformData
ReplacementArray object caches.
Definition Language.php:53
getMonthNamesArray()
Definition Language.php:925
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:937
setNamespaces(array $namespaces)
Arbitrarily set all of the namespace names at once.
Definition Language.php:501
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:46
getGrammarForms()
Get the grammar forms for the content language.
getNamespaces()
Returns an array of localised namespaces indexed by their numbers.
Definition Language.php:462
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:586
static array $durationIntervals
Definition Language.php:120
getNsIndex( $text)
Get a namespace key by value, case insensitive.
Definition Language.php:693
linkPrefixCharset()
A regular expression character set to match legal word-prefixing characters which should be merged on...
static $IRANIAN_DAYS
LanguageConverter $mConverter
Definition Language.php:39
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:86
uc( $str, $first=false)
Convert a string to uppercase.
static $mHebrewCalendarMonthMsgs
Definition Language.php:93
normalizeForSearch( $string)
Some languages have special punctuation need to be normalized.
embedBidi( $text='')
Wraps argument with unicode control characters for directionality safety.
getMonthAbbreviationsArray()
Definition Language.php:952
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:509
getMagicWords()
Get all magic words from cache.
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:76
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:150
getIranianCalendarMonthName( $key)
Definition Language.php:980
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".
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)
Definition Language.php:972
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:81
replaceGrammarInNamespace( $m)
getBookstoreList()
Exports $wgBookstoreListEn.
Definition Language.php:452
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:181
$mMagicExtensions
Definition Language.php:42
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:444
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:918
getPluralRules()
Get the plural rules for the language.
autoConvertToAllVariants( $text)
convert text to all supported variants
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:156
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...
handleExplicitPluralForms( $count, array $forms)
Handles explicit plural forms for Language::convertPlural()
convertNamespace( $ns)
Convert a namespace index to a string in the preferred variant.
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:404
static $mLangObjCache
Definition Language.php:60
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:383
msg( $msg)
Get message object in this language.
Definition Language.php:910
removeBadCharLast( $string)
Remove bytes that represent an incomplete Unicode character at the end of string (e....
static getJsonMessagesFileName( $code)
getNamespaceAliases()
Definition Language.php:619
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:540
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:663
static MapCacheLRU null $grammarTransformations
Cache for grammar rules data.
Definition Language.php:144
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)
Truncate a string to a specified length in bytes, appending an optional string (e....
static getCodeFromFileName( $filename, $prefix='Language', $suffix='.php')
Get the language code from a file name.
getDefaultDateFormat()
Definition Language.php:741
removeBadCharFirst( $string)
Remove bytes that represent an incomplete Unicode character at the start of string (e....
static $mHijriCalendarMonthMsgs
Definition Language.php:109
static $mWeekdayAbbrevMsgs
Definition Language.php:67
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:42
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:335
static newFromCode( $code)
Create a language object for a given language code.
Definition Language.php:207
static $mMonthMsgs
Definition Language.php:71
$dateFormatStrings
Definition Language.php:45
static romanNumeral( $num)
Roman number formatting up to 10000.
static classFromCode( $code)
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 getMain()
Static methods.
static getSuggestedDurations( $lang=null)
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:36
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition User.php:48
=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
when a variable name is used in a it is silently declared as a new local masking the global
Definition design.txt:95
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:58
const NS_PROJECT_TALK
Definition Defines.php:61
const NS_USER_TALK
Definition Defines.php:59
const NS_PROJECT
Definition Defines.php:60
the array() calling protocol came about after MediaWiki 1.4rc1.
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:249
namespace are movable Hooks may change this value to override the return value of MWNamespace::isMovable(). 'NewDifferenceEngine' do that in ParserLimitReportFormat instead use this to modify the parameters of the image and a DIV can begin in one section and end in another Make sure your code can handle that case gracefully See the EditSectionClearerLink extension for an example zero but section is usually empty its values are the globals values before the output is cached one of or reset my talk my contributions etc etc otherwise the built in rate limiting checks are if enabled allows for interception of redirect as a string mapping parameter names to values & $type
Definition hooks.txt:2568
see documentation in includes Linker php for Linker::makeImageLink & $time
Definition hooks.txt:1752
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 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:183
namespace and then decline to actually register it & $namespaces
Definition hooks.txt:956
namespace and then decline to actually register it file or subcat img or subcat $title
Definition hooks.txt:986
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context $options
Definition hooks.txt:1096
either a unescaped string or a HtmlArmor object after in associative array form externallinks including delete and has completed for all link tables whether this was an auto creation default is conds Array Extra conditions for the No matching items in log is displayed if loglist is empty msgKey Array If you want a nice box with a set this to the key of the message First element is the message additional optional elements are parameters for the key that are processed with wfMessage() -> params() ->parseAsBlock() - offset Set to overwrite offset parameter in $wgRequest set to '' to 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
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:1950
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context the output can only depend on parameters provided to this hook not on global state indicating whether full HTML should be generated If generation of HTML may be but other information should still be present in the ParserOutput object to manipulate or replace but no entry for that model exists in $wgContentHandlers if desired whether it is OK to use $contentModel on $title Handler functions that modify $ok should generally return false to prevent further hooks from further modifying $ok inclusive false for true for descending in case the handler function wants to provide a converted Content object Note that $result getContentModel() must return $toModel. 'CustomEditor' $rcid is used in generating this variable which contains information about the new such as the revision s whether the revision was marked as a minor edit or not
Definition hooks.txt:1207
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context the output can only depend on parameters provided to this hook not on global state indicating whether full HTML should be generated If generation of HTML may be but other information should still be present in the ParserOutput object to manipulate or replace but no entry for that model exists in $wgContentHandlers if desired whether it is OK to use $contentModel on $title Handler functions that modify $ok should generally return false to prevent further hooks from further modifying $ok inclusive $limit
Definition hooks.txt:1135
if the prop value should be in the metadata multi language array format
Definition hooks.txt:1620
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:1949
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:886
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books $tag
Definition hooks.txt:1033
usually copyright or history_copyright This message must be in HTML not wikitext & $link
Definition hooks.txt:2900
Allows to change the fields on the form that will be generated $name
Definition hooks.txt:304
namespace are movable Hooks may change this value to override the return value of MWNamespace::isMovable(). 'NewDifferenceEngine' do that in ParserLimitReportFormat instead use this to modify the parameters of the image and a DIV can begin in one section and end in another Make sure your code can handle that case gracefully See the EditSectionClearerLink extension for an example zero but section is usually empty its values are the globals values before the output is cached $page
Definition hooks.txt:2534
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:1595
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:887
if(count( $args)==0) $dir
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
A helper class for throttling authentication attempts.
if(!isset( $args[0])) $lang
const TS_MW
MediaWiki concatenated string timestamp (YYYYMMDDHHMMSS)
Definition defines.php:11