MediaWiki REL1_30
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 protected $namespaceNames;
51
55 public $transformData = [];
56
60 static public $dataCache;
61
62 static public $mLangObjCache = [];
63
64 static public $mWeekdayMsgs = [
65 'sunday', 'monday', 'tuesday', 'wednesday', 'thursday',
66 'friday', 'saturday'
67 ];
68
69 static public $mWeekdayAbbrevMsgs = [
70 'sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'
71 ];
72
73 static public $mMonthMsgs = [
74 'january', 'february', 'march', 'april', 'may_long', 'june',
75 'july', 'august', 'september', 'october', 'november',
76 'december'
77 ];
78 static public $mMonthGenMsgs = [
79 'january-gen', 'february-gen', 'march-gen', 'april-gen', 'may-gen', 'june-gen',
80 'july-gen', 'august-gen', 'september-gen', 'october-gen', 'november-gen',
81 'december-gen'
82 ];
83 static public $mMonthAbbrevMsgs = [
84 'jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug',
85 'sep', 'oct', 'nov', 'dec'
86 ];
87
89 'iranian-calendar-m1', 'iranian-calendar-m2', 'iranian-calendar-m3',
90 'iranian-calendar-m4', 'iranian-calendar-m5', 'iranian-calendar-m6',
91 'iranian-calendar-m7', 'iranian-calendar-m8', 'iranian-calendar-m9',
92 'iranian-calendar-m10', 'iranian-calendar-m11', 'iranian-calendar-m12'
93 ];
94
95 static public $mHebrewCalendarMonthMsgs = [
96 'hebrew-calendar-m1', 'hebrew-calendar-m2', 'hebrew-calendar-m3',
97 'hebrew-calendar-m4', 'hebrew-calendar-m5', 'hebrew-calendar-m6',
98 'hebrew-calendar-m7', 'hebrew-calendar-m8', 'hebrew-calendar-m9',
99 'hebrew-calendar-m10', 'hebrew-calendar-m11', 'hebrew-calendar-m12',
100 'hebrew-calendar-m6a', 'hebrew-calendar-m6b'
101 ];
102
104 'hebrew-calendar-m1-gen', 'hebrew-calendar-m2-gen', 'hebrew-calendar-m3-gen',
105 'hebrew-calendar-m4-gen', 'hebrew-calendar-m5-gen', 'hebrew-calendar-m6-gen',
106 'hebrew-calendar-m7-gen', 'hebrew-calendar-m8-gen', 'hebrew-calendar-m9-gen',
107 'hebrew-calendar-m10-gen', 'hebrew-calendar-m11-gen', 'hebrew-calendar-m12-gen',
108 'hebrew-calendar-m6a-gen', 'hebrew-calendar-m6b-gen'
109 ];
110
111 static public $mHijriCalendarMonthMsgs = [
112 'hijri-calendar-m1', 'hijri-calendar-m2', 'hijri-calendar-m3',
113 'hijri-calendar-m4', 'hijri-calendar-m5', 'hijri-calendar-m6',
114 'hijri-calendar-m7', 'hijri-calendar-m8', 'hijri-calendar-m9',
115 'hijri-calendar-m10', 'hijri-calendar-m11', 'hijri-calendar-m12'
116 ];
117
122 static public $durationIntervals = [
123 'millennia' => 31556952000,
124 'centuries' => 3155695200,
125 'decades' => 315569520,
126 'years' => 31556952, // 86400 * ( 365 + ( 24 * 3 + 25 ) / 400 )
127 'weeks' => 604800,
128 'days' => 86400,
129 'hours' => 3600,
130 'minutes' => 60,
131 'seconds' => 1,
132 ];
133
140 static private $fallbackLanguageCache = [];
141
147
152 static private $languageNameCache;
153
157 static private $lre = "\xE2\x80\xAA"; // U+202A LEFT-TO-RIGHT EMBEDDING
158 static private $rle = "\xE2\x80\xAB"; // U+202B RIGHT-TO-LEFT EMBEDDING
159 static private $pdf = "\xE2\x80\xAC"; // U+202C POP DIRECTIONAL FORMATTING
160
172 // @codingStandardsIgnoreStart
173 // @codeCoverageIgnoreStart
174 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';
175 // @codeCoverageIgnoreEnd
176 // @codingStandardsIgnoreEnd
177
183 static function factory( $code ) {
185
186 if ( isset( $wgDummyLanguageCodes[$code] ) ) {
188 }
189
190 // get the language object to process
191 $langObj = isset( self::$mLangObjCache[$code] )
192 ? self::$mLangObjCache[$code]
193 : self::newFromCode( $code );
194
195 // merge the language object in to get it up front in the cache
196 self::$mLangObjCache = array_merge( [ $code => $langObj ], self::$mLangObjCache );
197 // get rid of the oldest ones in case we have an overflow
198 self::$mLangObjCache = array_slice( self::$mLangObjCache, 0, $wgLangObjCacheSize, true );
199
200 return $langObj;
201 }
202
210 protected static function newFromCode( $code, $fallback = false ) {
211 if ( !self::isValidCode( $code ) ) {
212 throw new MWException( "Invalid language code \"$code\"" );
213 }
214
215 if ( !self::isValidBuiltInCode( $code ) ) {
216 // It's not possible to customise this code with class files, so
217 // just return a Language object. This is to support uselang= hacks.
218 $lang = new Language;
219 $lang->setCode( $code );
220 return $lang;
221 }
222
223 // Check if there is a language class for the code
224 $class = self::classFromCode( $code, $fallback );
225 if ( class_exists( $class ) ) {
226 $lang = new $class;
227 return $lang;
228 }
229
230 // Keep trying the fallback list until we find an existing class
231 $fallbacks = self::getFallbacksFor( $code );
232 foreach ( $fallbacks as $fallbackCode ) {
233 if ( !self::isValidBuiltInCode( $fallbackCode ) ) {
234 throw new MWException( "Invalid fallback '$fallbackCode' in fallback sequence for '$code'" );
235 }
236
237 $class = self::classFromCode( $fallbackCode );
238 if ( class_exists( $class ) ) {
239 $lang = new $class;
240 $lang->setCode( $code );
241 return $lang;
242 }
243 }
244
245 throw new MWException( "Invalid fallback sequence for language '$code'" );
246 }
247
256 public static function isSupportedLanguage( $code ) {
257 if ( !self::isValidBuiltInCode( $code ) ) {
258 return false;
259 }
260
261 if ( $code === 'qqq' ) {
262 return false;
263 }
264
265 return is_readable( self::getMessagesFileName( $code ) ) ||
266 is_readable( self::getJsonMessagesFileName( $code ) );
267 }
268
284 public static function isWellFormedLanguageTag( $code, $lenient = false ) {
285 $alpha = '[a-z]';
286 $digit = '[0-9]';
287 $alphanum = '[a-z0-9]';
288 $x = 'x'; # private use singleton
289 $singleton = '[a-wy-z]'; # other singleton
290 $s = $lenient ? '[-_]' : '-';
291
292 $language = "$alpha{2,8}|$alpha{2,3}$s$alpha{3}";
293 $script = "$alpha{4}"; # ISO 15924
294 $region = "(?:$alpha{2}|$digit{3})"; # ISO 3166-1 alpha-2 or UN M.49
295 $variant = "(?:$alphanum{5,8}|$digit$alphanum{3})";
296 $extension = "$singleton(?:$s$alphanum{2,8})+";
297 $privateUse = "$x(?:$s$alphanum{1,8})+";
298
299 # Define certain grandfathered codes, since otherwise the regex is pretty useless.
300 # Since these are limited, this is safe even later changes to the registry --
301 # the only oddity is that it might change the type of the tag, and thus
302 # the results from the capturing groups.
303 # https://www.iana.org/assignments/language-subtag-registry
304
305 $grandfathered = "en{$s}GB{$s}oed"
306 . "|i{$s}(?:ami|bnn|default|enochian|hak|klingon|lux|mingo|navajo|pwn|tao|tay|tsu)"
307 . "|no{$s}(?:bok|nyn)"
308 . "|sgn{$s}(?:BE{$s}(?:fr|nl)|CH{$s}de)"
309 . "|zh{$s}min{$s}nan";
310
311 $variantList = "$variant(?:$s$variant)*";
312 $extensionList = "$extension(?:$s$extension)*";
313
314 $langtag = "(?:($language)"
315 . "(?:$s$script)?"
316 . "(?:$s$region)?"
317 . "(?:$s$variantList)?"
318 . "(?:$s$extensionList)?"
319 . "(?:$s$privateUse)?)";
320
321 # The final breakdown, with capturing groups for each of these components
322 # The variants, extensions, grandfathered, and private-use may have interior '-'
323
324 $root = "^(?:$langtag|$privateUse|$grandfathered)$";
325
326 return (bool)preg_match( "/$root/", strtolower( $code ) );
327 }
328
338 public static function isValidCode( $code ) {
339 static $cache = [];
340 if ( !isset( $cache[$code] ) ) {
341 // People think language codes are html safe, so enforce it.
342 // Ideally we should only allow a-zA-Z0-9-
343 // but, .+ and other chars are often used for {{int:}} hacks
344 // see bugs T39564, T39587, T38938
345 $cache[$code] =
346 // Protect against path traversal
347 strcspn( $code, ":/\\\000&<>'\"" ) === strlen( $code )
349 }
350 return $cache[$code];
351 }
352
363 public static function isValidBuiltInCode( $code ) {
364 if ( !is_string( $code ) ) {
365 if ( is_object( $code ) ) {
366 $addmsg = " of class " . get_class( $code );
367 } else {
368 $addmsg = '';
369 }
370 $type = gettype( $code );
371 throw new MWException( __METHOD__ . " must be passed a string, $type given$addmsg" );
372 }
373
374 return (bool)preg_match( '/^[a-z0-9-]{2,}$/', $code );
375 }
376
385 public static function isKnownLanguageTag( $tag ) {
386 // Quick escape for invalid input to avoid exceptions down the line
387 // when code tries to process tags which are not valid at all.
388 if ( !self::isValidBuiltInCode( $tag ) ) {
389 return false;
390 }
391
392 if ( isset( MediaWiki\Languages\Data\Names::$names[$tag] )
393 || self::fetchLanguageName( $tag, $tag ) !== ''
394 ) {
395 return true;
396 }
397
398 return false;
399 }
400
406 public static function getLocalisationCache() {
407 if ( is_null( self::$dataCache ) ) {
409 $class = $wgLocalisationCacheConf['class'];
410 self::$dataCache = new $class( $wgLocalisationCacheConf );
411 }
412 return self::$dataCache;
413 }
414
415 function __construct() {
416 $this->mConverter = new FakeConverter( $this );
417 // Set the code to the name of the descendant
418 if ( static::class === 'Language' ) {
419 $this->mCode = 'en';
420 } else {
421 $this->mCode = str_replace( '_', '-', strtolower( substr( static::class, 8 ) ) );
422 }
423 self::getLocalisationCache();
424 }
425
429 function __destruct() {
430 foreach ( $this as $name => $value ) {
431 unset( $this->$name );
432 }
433 }
434
439 function initContLang() {
440 }
441
446 public function getFallbackLanguages() {
447 return self::getFallbacksFor( $this->mCode );
448 }
449
454 public function getBookstoreList() {
455 return self::$dataCache->getItem( $this->mCode, 'bookstoreList' );
456 }
457
464 public function getNamespaces() {
465 if ( is_null( $this->namespaceNames ) ) {
467
468 $validNamespaces = MWNamespace::getCanonicalNamespaces();
469
470 $this->namespaceNames = $wgExtraNamespaces +
471 self::$dataCache->getItem( $this->mCode, 'namespaceNames' );
472 $this->namespaceNames += $validNamespaces;
473
474 $this->namespaceNames[NS_PROJECT] = $wgMetaNamespace;
475 if ( $wgMetaNamespaceTalk ) {
476 $this->namespaceNames[NS_PROJECT_TALK] = $wgMetaNamespaceTalk;
477 } else {
478 $talk = $this->namespaceNames[NS_PROJECT_TALK];
479 $this->namespaceNames[NS_PROJECT_TALK] =
480 $this->fixVariableInNamespace( $talk );
481 }
482
483 # Sometimes a language will be localised but not actually exist on this wiki.
484 foreach ( $this->namespaceNames as $key => $text ) {
485 if ( !isset( $validNamespaces[$key] ) ) {
486 unset( $this->namespaceNames[$key] );
487 }
488 }
489
490 # The above mixing may leave namespaces out of canonical order.
491 # Re-order by namespace ID number...
492 ksort( $this->namespaceNames );
493
494 Hooks::run( 'LanguageGetNamespaces', [ &$this->namespaceNames ] );
495 }
496
498 }
499
504 public function setNamespaces( array $namespaces ) {
505 $this->namespaceNames = $namespaces;
506 $this->mNamespaceIds = null;
507 }
508
512 public function resetNamespaces() {
513 $this->namespaceNames = null;
514 $this->mNamespaceIds = null;
515 $this->namespaceAliases = null;
516 }
517
524 public function getFormattedNamespaces() {
525 $ns = $this->getNamespaces();
526 foreach ( $ns as $k => $v ) {
527 $ns[$k] = strtr( $v, '_', ' ' );
528 }
529 return $ns;
530 }
531
543 public function getNsText( $index ) {
544 $ns = $this->getNamespaces();
545 return isset( $ns[$index] ) ? $ns[$index] : false;
546 }
547
561 public function getFormattedNsText( $index ) {
562 $ns = $this->getNsText( $index );
563 return strtr( $ns, '_', ' ' );
564 }
565
574 public function getGenderNsText( $index, $gender ) {
576
578 (array)self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' );
579
580 return isset( $ns[$index][$gender] ) ? $ns[$index][$gender] : $this->getNsText( $index );
581 }
582
589 public function needsGenderDistinction() {
591 if ( count( $wgExtraGenderNamespaces ) > 0 ) {
592 // $wgExtraGenderNamespaces overrides everything
593 return true;
594 } elseif ( isset( $wgExtraNamespaces[NS_USER] ) && isset( $wgExtraNamespaces[NS_USER_TALK] ) ) {
596 // $wgExtraNamespaces overrides any gender aliases specified in i18n files
597 return false;
598 } else {
599 // Check what is in i18n files
600 $aliases = self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' );
601 return count( $aliases ) > 0;
602 }
603 }
604
613 function getLocalNsIndex( $text ) {
614 $lctext = $this->lc( $text );
615 $ids = $this->getNamespaceIds();
616 return isset( $ids[$lctext] ) ? $ids[$lctext] : false;
617 }
618
622 public function getNamespaceAliases() {
623 if ( is_null( $this->namespaceAliases ) ) {
624 $aliases = self::$dataCache->getItem( $this->mCode, 'namespaceAliases' );
625 if ( !$aliases ) {
626 $aliases = [];
627 } else {
628 foreach ( $aliases as $name => $index ) {
629 if ( $index === NS_PROJECT_TALK ) {
630 unset( $aliases[$name] );
632 $aliases[$name] = $index;
633 }
634 }
635 }
636
638 $genders = $wgExtraGenderNamespaces +
639 (array)self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' );
640 foreach ( $genders as $index => $forms ) {
641 foreach ( $forms as $alias ) {
642 $aliases[$alias] = $index;
643 }
644 }
645
646 # Also add converted namespace names as aliases, to avoid confusion.
647 $convertedNames = [];
648 foreach ( $this->getVariants() as $variant ) {
649 if ( $variant === $this->mCode ) {
650 continue;
651 }
652 foreach ( $this->getNamespaces() as $ns => $_ ) {
653 $convertedNames[$this->getConverter()->convertNamespace( $ns, $variant )] = $ns;
654 }
655 }
656
657 $this->namespaceAliases = $aliases + $convertedNames;
658 }
659
661 }
662
666 public function getNamespaceIds() {
667 if ( is_null( $this->mNamespaceIds ) ) {
669 # Put namespace names and aliases into a hashtable.
670 # If this is too slow, then we should arrange it so that it is done
671 # before caching. The catch is that at pre-cache time, the above
672 # class-specific fixup hasn't been done.
673 $this->mNamespaceIds = [];
674 foreach ( $this->getNamespaces() as $index => $name ) {
675 $this->mNamespaceIds[$this->lc( $name )] = $index;
676 }
677 foreach ( $this->getNamespaceAliases() as $name => $index ) {
678 $this->mNamespaceIds[$this->lc( $name )] = $index;
679 }
680 if ( $wgNamespaceAliases ) {
681 foreach ( $wgNamespaceAliases as $name => $index ) {
682 $this->mNamespaceIds[$this->lc( $name )] = $index;
683 }
684 }
685 }
686 return $this->mNamespaceIds;
687 }
688
696 public function getNsIndex( $text ) {
697 $lctext = $this->lc( $text );
698 $ns = MWNamespace::getCanonicalIndex( $lctext );
699 if ( $ns !== null ) {
700 return $ns;
701 }
702 $ids = $this->getNamespaceIds();
703 return isset( $ids[$lctext] ) ? $ids[$lctext] : false;
704 }
705
713 public function getVariantname( $code, $usemsg = true ) {
714 $msg = "variantname-$code";
715 if ( $usemsg && wfMessage( $msg )->exists() ) {
716 return $this->getMessageFromDB( $msg );
717 }
718 $name = self::fetchLanguageName( $code );
719 if ( $name ) {
720 return $name; # if it's defined as a language name, show that
721 } else {
722 # otherwise, output the language code
723 return $code;
724 }
725 }
726
730 public function getDatePreferences() {
731 return self::$dataCache->getItem( $this->mCode, 'datePreferences' );
732 }
733
737 function getDateFormats() {
738 return self::$dataCache->getItem( $this->mCode, 'dateFormats' );
739 }
740
744 public function getDefaultDateFormat() {
745 $df = self::$dataCache->getItem( $this->mCode, 'defaultDateFormat' );
746 if ( $df === 'dmy or mdy' ) {
747 global $wgAmericanDates;
748 return $wgAmericanDates ? 'mdy' : 'dmy';
749 } else {
750 return $df;
751 }
752 }
753
757 public function getDatePreferenceMigrationMap() {
758 return self::$dataCache->getItem( $this->mCode, 'datePreferenceMigrationMap' );
759 }
760
765 function getImageFile( $image ) {
766 return self::$dataCache->getSubitem( $this->mCode, 'imageFiles', $image );
767 }
768
773 public function getImageFiles() {
774 return self::$dataCache->getItem( $this->mCode, 'imageFiles' );
775 }
776
780 public function getExtraUserToggles() {
781 return (array)self::$dataCache->getItem( $this->mCode, 'extraUserToggles' );
782 }
783
788 function getUserToggle( $tog ) {
789 return $this->getMessageFromDB( "tog-$tog" );
790 }
791
803 public static function fetchLanguageNames( $inLanguage = null, $include = 'mw' ) {
804 $cacheKey = $inLanguage === null ? 'null' : $inLanguage;
805 $cacheKey .= ":$include";
806 if ( self::$languageNameCache === null ) {
807 self::$languageNameCache = new HashBagOStuff( [ 'maxKeys' => 20 ] );
808 }
809
810 $ret = self::$languageNameCache->get( $cacheKey );
811 if ( !$ret ) {
812 $ret = self::fetchLanguageNamesUncached( $inLanguage, $include );
813 self::$languageNameCache->set( $cacheKey, $ret );
814 }
815 return $ret;
816 }
817
828 private static function fetchLanguageNamesUncached( $inLanguage = null, $include = 'mw' ) {
829 global $wgExtraLanguageNames, $wgUsePigLatinVariant;
830
831 // If passed an invalid language code to use, fallback to en
832 if ( $inLanguage !== null && !self::isValidCode( $inLanguage ) ) {
833 $inLanguage = 'en';
834 }
835
836 $names = [];
837
838 if ( $inLanguage ) {
839 # TODO: also include when $inLanguage is null, when this code is more efficient
840 Hooks::run( 'LanguageGetTranslatedLanguageNames', [ &$names, $inLanguage ] );
841 }
842
843 $mwNames = $wgExtraLanguageNames + MediaWiki\Languages\Data\Names::$names;
844 if ( $wgUsePigLatinVariant ) {
845 // Pig Latin (for variant development)
846 $mwNames['en-x-piglatin'] = 'Igpay Atinlay';
847 }
848
849 foreach ( $mwNames as $mwCode => $mwName ) {
850 # - Prefer own MediaWiki native name when not using the hook
851 # - For other names just add if not added through the hook
852 if ( $mwCode === $inLanguage || !isset( $names[$mwCode] ) ) {
853 $names[$mwCode] = $mwName;
854 }
855 }
856
857 if ( $include === 'all' ) {
858 ksort( $names );
859 return $names;
860 }
861
862 $returnMw = [];
863 $coreCodes = array_keys( $mwNames );
864 foreach ( $coreCodes as $coreCode ) {
865 $returnMw[$coreCode] = $names[$coreCode];
866 }
867
868 if ( $include === 'mwfile' ) {
869 $namesMwFile = [];
870 # We do this using a foreach over the codes instead of a directory
871 # loop so that messages files in extensions will work correctly.
872 foreach ( $returnMw as $code => $value ) {
873 if ( is_readable( self::getMessagesFileName( $code ) )
874 || is_readable( self::getJsonMessagesFileName( $code ) )
875 ) {
876 $namesMwFile[$code] = $names[$code];
877 }
878 }
879
880 ksort( $namesMwFile );
881 return $namesMwFile;
882 }
883
884 ksort( $returnMw );
885 # 'mw' option; default if it's not one of the other two options (all/mwfile)
886 return $returnMw;
887 }
888
896 public static function fetchLanguageName( $code, $inLanguage = null, $include = 'all' ) {
897 $code = strtolower( $code );
898 $array = self::fetchLanguageNames( $inLanguage, $include );
899 return !array_key_exists( $code, $array ) ? '' : $array[$code];
900 }
901
908 public function getMessageFromDB( $msg ) {
909 return $this->msg( $msg )->text();
910 }
911
918 protected function msg( $msg ) {
919 return wfMessage( $msg )->inLanguage( $this );
920 }
921
926 public function getMonthName( $key ) {
927 return $this->getMessageFromDB( self::$mMonthMsgs[$key - 1] );
928 }
929
933 public function getMonthNamesArray() {
934 $monthNames = [ '' ];
935 for ( $i = 1; $i < 13; $i++ ) {
936 $monthNames[] = $this->getMonthName( $i );
937 }
938 return $monthNames;
939 }
940
945 public function getMonthNameGen( $key ) {
946 return $this->getMessageFromDB( self::$mMonthGenMsgs[$key - 1] );
947 }
948
953 public function getMonthAbbreviation( $key ) {
954 return $this->getMessageFromDB( self::$mMonthAbbrevMsgs[$key - 1] );
955 }
956
960 public function getMonthAbbreviationsArray() {
961 $monthNames = [ '' ];
962 for ( $i = 1; $i < 13; $i++ ) {
963 $monthNames[] = $this->getMonthAbbreviation( $i );
964 }
965 return $monthNames;
966 }
967
972 public function getWeekdayName( $key ) {
973 return $this->getMessageFromDB( self::$mWeekdayMsgs[$key - 1] );
974 }
975
980 function getWeekdayAbbreviation( $key ) {
981 return $this->getMessageFromDB( self::$mWeekdayAbbrevMsgs[$key - 1] );
982 }
983
989 return $this->getMessageFromDB( self::$mIranianCalendarMonthMsgs[$key - 1] );
990 }
991
996 function getHebrewCalendarMonthName( $key ) {
997 return $this->getMessageFromDB( self::$mHebrewCalendarMonthMsgs[$key - 1] );
998 }
999
1005 return $this->getMessageFromDB( self::$mHebrewCalendarMonthGenMsgs[$key - 1] );
1006 }
1007
1012 function getHijriCalendarMonthName( $key ) {
1013 return $this->getMessageFromDB( self::$mHijriCalendarMonthMsgs[$key - 1] );
1014 }
1015
1024 private static function dateTimeObjFormat( &$dateTimeObj, $ts, $zone, $code ) {
1025 if ( !$dateTimeObj ) {
1026 $dateTimeObj = DateTime::createFromFormat(
1027 'YmdHis', $ts, $zone ?: new DateTimeZone( 'UTC' )
1028 );
1029 }
1030 return $dateTimeObj->format( $code );
1031 }
1032
1102 public function sprintfDate( $format, $ts, DateTimeZone $zone = null, &$ttl = 'unused' ) {
1103 $s = '';
1104 $raw = false;
1105 $roman = false;
1106 $hebrewNum = false;
1107 $dateTimeObj = false;
1108 $rawToggle = false;
1109 $iranian = false;
1110 $hebrew = false;
1111 $hijri = false;
1112 $thai = false;
1113 $minguo = false;
1114 $tenno = false;
1115
1116 $usedSecond = false;
1117 $usedMinute = false;
1118 $usedHour = false;
1119 $usedAMPM = false;
1120 $usedDay = false;
1121 $usedWeek = false;
1122 $usedMonth = false;
1123 $usedYear = false;
1124 $usedISOYear = false;
1125 $usedIsLeapYear = false;
1126
1127 $usedHebrewMonth = false;
1128 $usedIranianMonth = false;
1129 $usedHijriMonth = false;
1130 $usedHebrewYear = false;
1131 $usedIranianYear = false;
1132 $usedHijriYear = false;
1133 $usedTennoYear = false;
1134
1135 if ( strlen( $ts ) !== 14 ) {
1136 throw new MWException( __METHOD__ . ": The timestamp $ts should have 14 characters" );
1137 }
1138
1139 if ( !ctype_digit( $ts ) ) {
1140 throw new MWException( __METHOD__ . ": The timestamp $ts should be a number" );
1141 }
1142
1143 $formatLength = strlen( $format );
1144 for ( $p = 0; $p < $formatLength; $p++ ) {
1145 $num = false;
1146 $code = $format[$p];
1147 if ( $code == 'x' && $p < $formatLength - 1 ) {
1148 $code .= $format[++$p];
1149 }
1150
1151 if ( ( $code === 'xi'
1152 || $code === 'xj'
1153 || $code === 'xk'
1154 || $code === 'xm'
1155 || $code === 'xo'
1156 || $code === 'xt' )
1157 && $p < $formatLength - 1 ) {
1158 $code .= $format[++$p];
1159 }
1160
1161 switch ( $code ) {
1162 case 'xx':
1163 $s .= 'x';
1164 break;
1165 case 'xn':
1166 $raw = true;
1167 break;
1168 case 'xN':
1169 $rawToggle = !$rawToggle;
1170 break;
1171 case 'xr':
1172 $roman = true;
1173 break;
1174 case 'xh':
1175 $hebrewNum = true;
1176 break;
1177 case 'xg':
1178 $usedMonth = true;
1179 $s .= $this->getMonthNameGen( substr( $ts, 4, 2 ) );
1180 break;
1181 case 'xjx':
1182 $usedHebrewMonth = true;
1183 if ( !$hebrew ) {
1184 $hebrew = self::tsToHebrew( $ts );
1185 }
1186 $s .= $this->getHebrewCalendarMonthNameGen( $hebrew[1] );
1187 break;
1188 case 'd':
1189 $usedDay = true;
1190 $num = substr( $ts, 6, 2 );
1191 break;
1192 case 'D':
1193 $usedDay = true;
1194 $s .= $this->getWeekdayAbbreviation(
1195 self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'w' ) + 1
1196 );
1197 break;
1198 case 'j':
1199 $usedDay = true;
1200 $num = intval( substr( $ts, 6, 2 ) );
1201 break;
1202 case 'xij':
1203 $usedDay = true;
1204 if ( !$iranian ) {
1205 $iranian = self::tsToIranian( $ts );
1206 }
1207 $num = $iranian[2];
1208 break;
1209 case 'xmj':
1210 $usedDay = true;
1211 if ( !$hijri ) {
1212 $hijri = self::tsToHijri( $ts );
1213 }
1214 $num = $hijri[2];
1215 break;
1216 case 'xjj':
1217 $usedDay = true;
1218 if ( !$hebrew ) {
1219 $hebrew = self::tsToHebrew( $ts );
1220 }
1221 $num = $hebrew[2];
1222 break;
1223 case 'l':
1224 $usedDay = true;
1225 $s .= $this->getWeekdayName(
1226 self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'w' ) + 1
1227 );
1228 break;
1229 case 'F':
1230 $usedMonth = true;
1231 $s .= $this->getMonthName( substr( $ts, 4, 2 ) );
1232 break;
1233 case 'xiF':
1234 $usedIranianMonth = true;
1235 if ( !$iranian ) {
1236 $iranian = self::tsToIranian( $ts );
1237 }
1238 $s .= $this->getIranianCalendarMonthName( $iranian[1] );
1239 break;
1240 case 'xmF':
1241 $usedHijriMonth = true;
1242 if ( !$hijri ) {
1243 $hijri = self::tsToHijri( $ts );
1244 }
1245 $s .= $this->getHijriCalendarMonthName( $hijri[1] );
1246 break;
1247 case 'xjF':
1248 $usedHebrewMonth = true;
1249 if ( !$hebrew ) {
1250 $hebrew = self::tsToHebrew( $ts );
1251 }
1252 $s .= $this->getHebrewCalendarMonthName( $hebrew[1] );
1253 break;
1254 case 'm':
1255 $usedMonth = true;
1256 $num = substr( $ts, 4, 2 );
1257 break;
1258 case 'M':
1259 $usedMonth = true;
1260 $s .= $this->getMonthAbbreviation( substr( $ts, 4, 2 ) );
1261 break;
1262 case 'n':
1263 $usedMonth = true;
1264 $num = intval( substr( $ts, 4, 2 ) );
1265 break;
1266 case 'xin':
1267 $usedIranianMonth = true;
1268 if ( !$iranian ) {
1269 $iranian = self::tsToIranian( $ts );
1270 }
1271 $num = $iranian[1];
1272 break;
1273 case 'xmn':
1274 $usedHijriMonth = true;
1275 if ( !$hijri ) {
1276 $hijri = self::tsToHijri( $ts );
1277 }
1278 $num = $hijri[1];
1279 break;
1280 case 'xjn':
1281 $usedHebrewMonth = true;
1282 if ( !$hebrew ) {
1283 $hebrew = self::tsToHebrew( $ts );
1284 }
1285 $num = $hebrew[1];
1286 break;
1287 case 'xjt':
1288 $usedHebrewMonth = true;
1289 if ( !$hebrew ) {
1290 $hebrew = self::tsToHebrew( $ts );
1291 }
1292 $num = $hebrew[3];
1293 break;
1294 case 'Y':
1295 $usedYear = true;
1296 $num = substr( $ts, 0, 4 );
1297 break;
1298 case 'xiY':
1299 $usedIranianYear = true;
1300 if ( !$iranian ) {
1301 $iranian = self::tsToIranian( $ts );
1302 }
1303 $num = $iranian[0];
1304 break;
1305 case 'xmY':
1306 $usedHijriYear = true;
1307 if ( !$hijri ) {
1308 $hijri = self::tsToHijri( $ts );
1309 }
1310 $num = $hijri[0];
1311 break;
1312 case 'xjY':
1313 $usedHebrewYear = true;
1314 if ( !$hebrew ) {
1315 $hebrew = self::tsToHebrew( $ts );
1316 }
1317 $num = $hebrew[0];
1318 break;
1319 case 'xkY':
1320 $usedYear = true;
1321 if ( !$thai ) {
1322 $thai = self::tsToYear( $ts, 'thai' );
1323 }
1324 $num = $thai[0];
1325 break;
1326 case 'xoY':
1327 $usedYear = true;
1328 if ( !$minguo ) {
1329 $minguo = self::tsToYear( $ts, 'minguo' );
1330 }
1331 $num = $minguo[0];
1332 break;
1333 case 'xtY':
1334 $usedTennoYear = true;
1335 if ( !$tenno ) {
1336 $tenno = self::tsToYear( $ts, 'tenno' );
1337 }
1338 $num = $tenno[0];
1339 break;
1340 case 'y':
1341 $usedYear = true;
1342 $num = substr( $ts, 2, 2 );
1343 break;
1344 case 'xiy':
1345 $usedIranianYear = true;
1346 if ( !$iranian ) {
1347 $iranian = self::tsToIranian( $ts );
1348 }
1349 $num = substr( $iranian[0], -2 );
1350 break;
1351 case 'xit':
1352 $usedIranianYear = true;
1353 if ( !$iranian ) {
1354 $iranian = self::tsToIranian( $ts );
1355 }
1356 $num = self::$IRANIAN_DAYS[$iranian[1] - 1];
1357 break;
1358 case 'xiz':
1359 $usedIranianYear = true;
1360 if ( !$iranian ) {
1361 $iranian = self::tsToIranian( $ts );
1362 }
1363 $num = $iranian[3];
1364 break;
1365 case 'a':
1366 $usedAMPM = true;
1367 $s .= intval( substr( $ts, 8, 2 ) ) < 12 ? 'am' : 'pm';
1368 break;
1369 case 'A':
1370 $usedAMPM = true;
1371 $s .= intval( substr( $ts, 8, 2 ) ) < 12 ? 'AM' : 'PM';
1372 break;
1373 case 'g':
1374 $usedHour = true;
1375 $h = substr( $ts, 8, 2 );
1376 $num = $h % 12 ? $h % 12 : 12;
1377 break;
1378 case 'G':
1379 $usedHour = true;
1380 $num = intval( substr( $ts, 8, 2 ) );
1381 break;
1382 case 'h':
1383 $usedHour = true;
1384 $h = substr( $ts, 8, 2 );
1385 $num = sprintf( '%02d', $h % 12 ? $h % 12 : 12 );
1386 break;
1387 case 'H':
1388 $usedHour = true;
1389 $num = substr( $ts, 8, 2 );
1390 break;
1391 case 'i':
1392 $usedMinute = true;
1393 $num = substr( $ts, 10, 2 );
1394 break;
1395 case 's':
1396 $usedSecond = true;
1397 $num = substr( $ts, 12, 2 );
1398 break;
1399 case 'c':
1400 case 'r':
1401 $usedSecond = true;
1402 // fall through
1403 case 'e':
1404 case 'O':
1405 case 'P':
1406 case 'T':
1407 $s .= self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1408 break;
1409 case 'w':
1410 case 'N':
1411 case 'z':
1412 $usedDay = true;
1413 $num = self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1414 break;
1415 case 'W':
1416 $usedWeek = true;
1417 $num = self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1418 break;
1419 case 't':
1420 $usedMonth = true;
1421 $num = self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1422 break;
1423 case 'L':
1424 $usedIsLeapYear = true;
1425 $num = self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1426 break;
1427 case 'o':
1428 $usedISOYear = true;
1429 $num = self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1430 break;
1431 case 'U':
1432 $usedSecond = true;
1433 // fall through
1434 case 'I':
1435 case 'Z':
1436 $num = self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1437 break;
1438 case '\\':
1439 # Backslash escaping
1440 if ( $p < $formatLength - 1 ) {
1441 $s .= $format[++$p];
1442 } else {
1443 $s .= '\\';
1444 }
1445 break;
1446 case '"':
1447 # Quoted literal
1448 if ( $p < $formatLength - 1 ) {
1449 $endQuote = strpos( $format, '"', $p + 1 );
1450 if ( $endQuote === false ) {
1451 # No terminating quote, assume literal "
1452 $s .= '"';
1453 } else {
1454 $s .= substr( $format, $p + 1, $endQuote - $p - 1 );
1455 $p = $endQuote;
1456 }
1457 } else {
1458 # Quote at end of string, assume literal "
1459 $s .= '"';
1460 }
1461 break;
1462 default:
1463 $s .= $format[$p];
1464 }
1465 if ( $num !== false ) {
1466 if ( $rawToggle || $raw ) {
1467 $s .= $num;
1468 $raw = false;
1469 } elseif ( $roman ) {
1470 $s .= self::romanNumeral( $num );
1471 $roman = false;
1472 } elseif ( $hebrewNum ) {
1473 $s .= self::hebrewNumeral( $num );
1474 $hebrewNum = false;
1475 } else {
1476 $s .= $this->formatNum( $num, true );
1477 }
1478 }
1479 }
1480
1481 if ( $ttl === 'unused' ) {
1482 // No need to calculate the TTL, the caller wont use it anyway.
1483 } elseif ( $usedSecond ) {
1484 $ttl = 1;
1485 } elseif ( $usedMinute ) {
1486 $ttl = 60 - substr( $ts, 12, 2 );
1487 } elseif ( $usedHour ) {
1488 $ttl = 3600 - substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
1489 } elseif ( $usedAMPM ) {
1490 $ttl = 43200 - ( substr( $ts, 8, 2 ) % 12 ) * 3600 -
1491 substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
1492 } elseif (
1493 $usedDay ||
1494 $usedHebrewMonth ||
1495 $usedIranianMonth ||
1496 $usedHijriMonth ||
1497 $usedHebrewYear ||
1498 $usedIranianYear ||
1499 $usedHijriYear ||
1500 $usedTennoYear
1501 ) {
1502 // @todo Someone who understands the non-Gregorian calendars
1503 // should write proper logic for them so that they don't need purged every day.
1504 $ttl = 86400 - substr( $ts, 8, 2 ) * 3600 -
1505 substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
1506 } else {
1507 $possibleTtls = [];
1508 $timeRemainingInDay = 86400 - substr( $ts, 8, 2 ) * 3600 -
1509 substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
1510 if ( $usedWeek ) {
1511 $possibleTtls[] =
1512 ( 7 - self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'N' ) ) * 86400 +
1513 $timeRemainingInDay;
1514 } elseif ( $usedISOYear ) {
1515 // December 28th falls on the last ISO week of the year, every year.
1516 // The last ISO week of a year can be 52 or 53.
1517 $lastWeekOfISOYear = DateTime::createFromFormat(
1518 'Ymd',
1519 substr( $ts, 0, 4 ) . '1228',
1520 $zone ?: new DateTimeZone( 'UTC' )
1521 )->format( 'W' );
1522 $currentISOWeek = self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'W' );
1523 $weeksRemaining = $lastWeekOfISOYear - $currentISOWeek;
1524 $timeRemainingInWeek =
1525 ( 7 - self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'N' ) ) * 86400
1526 + $timeRemainingInDay;
1527 $possibleTtls[] = $weeksRemaining * 604800 + $timeRemainingInWeek;
1528 }
1529
1530 if ( $usedMonth ) {
1531 $possibleTtls[] =
1532 ( self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 't' ) -
1533 substr( $ts, 6, 2 ) ) * 86400
1534 + $timeRemainingInDay;
1535 } elseif ( $usedYear ) {
1536 $possibleTtls[] =
1537 ( self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'L' ) + 364 -
1538 self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'z' ) ) * 86400
1539 + $timeRemainingInDay;
1540 } elseif ( $usedIsLeapYear ) {
1541 $year = substr( $ts, 0, 4 );
1542 $timeRemainingInYear =
1543 ( self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'L' ) + 364 -
1544 self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'z' ) ) * 86400
1545 + $timeRemainingInDay;
1546 $mod = $year % 4;
1547 if ( $mod || ( !( $year % 100 ) && $year % 400 ) ) {
1548 // this isn't a leap year. see when the next one starts
1549 $nextCandidate = $year - $mod + 4;
1550 if ( $nextCandidate % 100 || !( $nextCandidate % 400 ) ) {
1551 $possibleTtls[] = ( $nextCandidate - $year - 1 ) * 365 * 86400 +
1552 $timeRemainingInYear;
1553 } else {
1554 $possibleTtls[] = ( $nextCandidate - $year + 3 ) * 365 * 86400 +
1555 $timeRemainingInYear;
1556 }
1557 } else {
1558 // this is a leap year, so the next year isn't
1559 $possibleTtls[] = $timeRemainingInYear;
1560 }
1561 }
1562
1563 if ( $possibleTtls ) {
1564 $ttl = min( $possibleTtls );
1565 }
1566 }
1567
1568 return $s;
1569 }
1570
1571 private static $GREG_DAYS = [ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ];
1572 private static $IRANIAN_DAYS = [ 31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29 ];
1573
1586 private static function tsToIranian( $ts ) {
1587 $gy = substr( $ts, 0, 4 ) - 1600;
1588 $gm = substr( $ts, 4, 2 ) - 1;
1589 $gd = substr( $ts, 6, 2 ) - 1;
1590
1591 # Days passed from the beginning (including leap years)
1592 $gDayNo = 365 * $gy
1593 + floor( ( $gy + 3 ) / 4 )
1594 - floor( ( $gy + 99 ) / 100 )
1595 + floor( ( $gy + 399 ) / 400 );
1596
1597 // Add days of the past months of this year
1598 for ( $i = 0; $i < $gm; $i++ ) {
1599 $gDayNo += self::$GREG_DAYS[$i];
1600 }
1601
1602 // Leap years
1603 if ( $gm > 1 && ( ( $gy % 4 === 0 && $gy % 100 !== 0 || ( $gy % 400 == 0 ) ) ) ) {
1604 $gDayNo++;
1605 }
1606
1607 // Days passed in current month
1608 $gDayNo += (int)$gd;
1609
1610 $jDayNo = $gDayNo - 79;
1611
1612 $jNp = floor( $jDayNo / 12053 );
1613 $jDayNo %= 12053;
1614
1615 $jy = 979 + 33 * $jNp + 4 * floor( $jDayNo / 1461 );
1616 $jDayNo %= 1461;
1617
1618 if ( $jDayNo >= 366 ) {
1619 $jy += floor( ( $jDayNo - 1 ) / 365 );
1620 $jDayNo = floor( ( $jDayNo - 1 ) % 365 );
1621 }
1622
1623 $jz = $jDayNo;
1624
1625 for ( $i = 0; $i < 11 && $jDayNo >= self::$IRANIAN_DAYS[$i]; $i++ ) {
1626 $jDayNo -= self::$IRANIAN_DAYS[$i];
1627 }
1628
1629 $jm = $i + 1;
1630 $jd = $jDayNo + 1;
1631
1632 return [ $jy, $jm, $jd, $jz ];
1633 }
1634
1646 private static function tsToHijri( $ts ) {
1647 $year = substr( $ts, 0, 4 );
1648 $month = substr( $ts, 4, 2 );
1649 $day = substr( $ts, 6, 2 );
1650
1651 $zyr = $year;
1652 $zd = $day;
1653 $zm = $month;
1654 $zy = $zyr;
1655
1656 if (
1657 ( $zy > 1582 ) || ( ( $zy == 1582 ) && ( $zm > 10 ) ) ||
1658 ( ( $zy == 1582 ) && ( $zm == 10 ) && ( $zd > 14 ) )
1659 ) {
1660 $zjd = (int)( ( 1461 * ( $zy + 4800 + (int)( ( $zm - 14 ) / 12 ) ) ) / 4 ) +
1661 (int)( ( 367 * ( $zm - 2 - 12 * ( (int)( ( $zm - 14 ) / 12 ) ) ) ) / 12 ) -
1662 (int)( ( 3 * (int)( ( ( $zy + 4900 + (int)( ( $zm - 14 ) / 12 ) ) / 100 ) ) ) / 4 ) +
1663 $zd - 32075;
1664 } else {
1665 $zjd = 367 * $zy - (int)( ( 7 * ( $zy + 5001 + (int)( ( $zm - 9 ) / 7 ) ) ) / 4 ) +
1666 (int)( ( 275 * $zm ) / 9 ) + $zd + 1729777;
1667 }
1668
1669 $zl = $zjd - 1948440 + 10632;
1670 $zn = (int)( ( $zl - 1 ) / 10631 );
1671 $zl = $zl - 10631 * $zn + 354;
1672 $zj = ( (int)( ( 10985 - $zl ) / 5316 ) ) * ( (int)( ( 50 * $zl ) / 17719 ) ) +
1673 ( (int)( $zl / 5670 ) ) * ( (int)( ( 43 * $zl ) / 15238 ) );
1674 $zl = $zl - ( (int)( ( 30 - $zj ) / 15 ) ) * ( (int)( ( 17719 * $zj ) / 50 ) ) -
1675 ( (int)( $zj / 16 ) ) * ( (int)( ( 15238 * $zj ) / 43 ) ) + 29;
1676 $zm = (int)( ( 24 * $zl ) / 709 );
1677 $zd = $zl - (int)( ( 709 * $zm ) / 24 );
1678 $zy = 30 * $zn + $zj - 30;
1679
1680 return [ $zy, $zm, $zd ];
1681 }
1682
1698 private static function tsToHebrew( $ts ) {
1699 # Parse date
1700 $year = substr( $ts, 0, 4 );
1701 $month = substr( $ts, 4, 2 );
1702 $day = substr( $ts, 6, 2 );
1703
1704 # Calculate Hebrew year
1705 $hebrewYear = $year + 3760;
1706
1707 # Month number when September = 1, August = 12
1708 $month += 4;
1709 if ( $month > 12 ) {
1710 # Next year
1711 $month -= 12;
1712 $year++;
1713 $hebrewYear++;
1714 }
1715
1716 # Calculate day of year from 1 September
1717 $dayOfYear = $day;
1718 for ( $i = 1; $i < $month; $i++ ) {
1719 if ( $i == 6 ) {
1720 # February
1721 $dayOfYear += 28;
1722 # Check if the year is leap
1723 if ( $year % 400 == 0 || ( $year % 4 == 0 && $year % 100 > 0 ) ) {
1724 $dayOfYear++;
1725 }
1726 } elseif ( $i == 8 || $i == 10 || $i == 1 || $i == 3 ) {
1727 $dayOfYear += 30;
1728 } else {
1729 $dayOfYear += 31;
1730 }
1731 }
1732
1733 # Calculate the start of the Hebrew year
1734 $start = self::hebrewYearStart( $hebrewYear );
1735
1736 # Calculate next year's start
1737 if ( $dayOfYear <= $start ) {
1738 # Day is before the start of the year - it is the previous year
1739 # Next year's start
1740 $nextStart = $start;
1741 # Previous year
1742 $year--;
1743 $hebrewYear--;
1744 # Add days since previous year's 1 September
1745 $dayOfYear += 365;
1746 if ( ( $year % 400 == 0 ) || ( $year % 100 != 0 && $year % 4 == 0 ) ) {
1747 # Leap year
1748 $dayOfYear++;
1749 }
1750 # Start of the new (previous) year
1751 $start = self::hebrewYearStart( $hebrewYear );
1752 } else {
1753 # Next year's start
1754 $nextStart = self::hebrewYearStart( $hebrewYear + 1 );
1755 }
1756
1757 # Calculate Hebrew day of year
1758 $hebrewDayOfYear = $dayOfYear - $start;
1759
1760 # Difference between year's days
1761 $diff = $nextStart - $start;
1762 # Add 12 (or 13 for leap years) days to ignore the difference between
1763 # Hebrew and Gregorian year (353 at least vs. 365/6) - now the
1764 # difference is only about the year type
1765 if ( ( $year % 400 == 0 ) || ( $year % 100 != 0 && $year % 4 == 0 ) ) {
1766 $diff += 13;
1767 } else {
1768 $diff += 12;
1769 }
1770
1771 # Check the year pattern, and is leap year
1772 # 0 means an incomplete year, 1 means a regular year, 2 means a complete year
1773 # This is mod 30, to work on both leap years (which add 30 days of Adar I)
1774 # and non-leap years
1775 $yearPattern = $diff % 30;
1776 # Check if leap year
1777 $isLeap = $diff >= 30;
1778
1779 # Calculate day in the month from number of day in the Hebrew year
1780 # Don't check Adar - if the day is not in Adar, we will stop before;
1781 # if it is in Adar, we will use it to check if it is Adar I or Adar II
1782 $hebrewDay = $hebrewDayOfYear;
1783 $hebrewMonth = 1;
1784 $days = 0;
1785 while ( $hebrewMonth <= 12 ) {
1786 # Calculate days in this month
1787 if ( $isLeap && $hebrewMonth == 6 ) {
1788 # Adar in a leap year
1789 if ( $isLeap ) {
1790 # Leap year - has Adar I, with 30 days, and Adar II, with 29 days
1791 $days = 30;
1792 if ( $hebrewDay <= $days ) {
1793 # Day in Adar I
1794 $hebrewMonth = 13;
1795 } else {
1796 # Subtract the days of Adar I
1797 $hebrewDay -= $days;
1798 # Try Adar II
1799 $days = 29;
1800 if ( $hebrewDay <= $days ) {
1801 # Day in Adar II
1802 $hebrewMonth = 14;
1803 }
1804 }
1805 }
1806 } elseif ( $hebrewMonth == 2 && $yearPattern == 2 ) {
1807 # Cheshvan in a complete year (otherwise as the rule below)
1808 $days = 30;
1809 } elseif ( $hebrewMonth == 3 && $yearPattern == 0 ) {
1810 # Kislev in an incomplete year (otherwise as the rule below)
1811 $days = 29;
1812 } else {
1813 # Odd months have 30 days, even have 29
1814 $days = 30 - ( $hebrewMonth - 1 ) % 2;
1815 }
1816 if ( $hebrewDay <= $days ) {
1817 # In the current month
1818 break;
1819 } else {
1820 # Subtract the days of the current month
1821 $hebrewDay -= $days;
1822 # Try in the next month
1823 $hebrewMonth++;
1824 }
1825 }
1826
1827 return [ $hebrewYear, $hebrewMonth, $hebrewDay, $days ];
1828 }
1829
1839 private static function hebrewYearStart( $year ) {
1840 $a = intval( ( 12 * ( $year - 1 ) + 17 ) % 19 );
1841 $b = intval( ( $year - 1 ) % 4 );
1842 $m = 32.044093161144 + 1.5542417966212 * $a + $b / 4.0 - 0.0031777940220923 * ( $year - 1 );
1843 if ( $m < 0 ) {
1844 $m--;
1845 }
1846 $Mar = intval( $m );
1847 if ( $m < 0 ) {
1848 $m++;
1849 }
1850 $m -= $Mar;
1851
1852 $c = intval( ( $Mar + 3 * ( $year - 1 ) + 5 * $b + 5 ) % 7 );
1853 if ( $c == 0 && $a > 11 && $m >= 0.89772376543210 ) {
1854 $Mar++;
1855 } elseif ( $c == 1 && $a > 6 && $m >= 0.63287037037037 ) {
1856 $Mar += 2;
1857 } elseif ( $c == 2 || $c == 4 || $c == 6 ) {
1858 $Mar++;
1859 }
1860
1861 $Mar += intval( ( $year - 3761 ) / 100 ) - intval( ( $year - 3761 ) / 400 ) - 24;
1862 return $Mar;
1863 }
1864
1877 private static function tsToYear( $ts, $cName ) {
1878 $gy = substr( $ts, 0, 4 );
1879 $gm = substr( $ts, 4, 2 );
1880 $gd = substr( $ts, 6, 2 );
1881
1882 if ( !strcmp( $cName, 'thai' ) ) {
1883 # Thai solar dates
1884 # Add 543 years to the Gregorian calendar
1885 # Months and days are identical
1886 $gy_offset = $gy + 543;
1887 } elseif ( ( !strcmp( $cName, 'minguo' ) ) || !strcmp( $cName, 'juche' ) ) {
1888 # Minguo dates
1889 # Deduct 1911 years from the Gregorian calendar
1890 # Months and days are identical
1891 $gy_offset = $gy - 1911;
1892 } elseif ( !strcmp( $cName, 'tenno' ) ) {
1893 # Nengō dates up to Meiji period
1894 # Deduct years from the Gregorian calendar
1895 # depending on the nengo periods
1896 # Months and days are identical
1897 if ( ( $gy < 1912 )
1898 || ( ( $gy == 1912 ) && ( $gm < 7 ) )
1899 || ( ( $gy == 1912 ) && ( $gm == 7 ) && ( $gd < 31 ) )
1900 ) {
1901 # Meiji period
1902 $gy_gannen = $gy - 1868 + 1;
1903 $gy_offset = $gy_gannen;
1904 if ( $gy_gannen == 1 ) {
1905 $gy_offset = '元';
1906 }
1907 $gy_offset = '明治' . $gy_offset;
1908 } elseif (
1909 ( ( $gy == 1912 ) && ( $gm == 7 ) && ( $gd == 31 ) ) ||
1910 ( ( $gy == 1912 ) && ( $gm >= 8 ) ) ||
1911 ( ( $gy > 1912 ) && ( $gy < 1926 ) ) ||
1912 ( ( $gy == 1926 ) && ( $gm < 12 ) ) ||
1913 ( ( $gy == 1926 ) && ( $gm == 12 ) && ( $gd < 26 ) )
1914 ) {
1915 # Taishō period
1916 $gy_gannen = $gy - 1912 + 1;
1917 $gy_offset = $gy_gannen;
1918 if ( $gy_gannen == 1 ) {
1919 $gy_offset = '元';
1920 }
1921 $gy_offset = '大正' . $gy_offset;
1922 } elseif (
1923 ( ( $gy == 1926 ) && ( $gm == 12 ) && ( $gd >= 26 ) ) ||
1924 ( ( $gy > 1926 ) && ( $gy < 1989 ) ) ||
1925 ( ( $gy == 1989 ) && ( $gm == 1 ) && ( $gd < 8 ) )
1926 ) {
1927 # Shōwa period
1928 $gy_gannen = $gy - 1926 + 1;
1929 $gy_offset = $gy_gannen;
1930 if ( $gy_gannen == 1 ) {
1931 $gy_offset = '元';
1932 }
1933 $gy_offset = '昭和' . $gy_offset;
1934 } else {
1935 # Heisei period
1936 $gy_gannen = $gy - 1989 + 1;
1937 $gy_offset = $gy_gannen;
1938 if ( $gy_gannen == 1 ) {
1939 $gy_offset = '元';
1940 }
1941 $gy_offset = '平成' . $gy_offset;
1942 }
1943 } else {
1944 $gy_offset = $gy;
1945 }
1946
1947 return [ $gy_offset, $gm, $gd ];
1948 }
1949
1963 private static function strongDirFromContent( $text = '' ) {
1964 if ( !preg_match( self::$strongDirRegex, $text, $matches ) ) {
1965 return null;
1966 }
1967 if ( $matches[1] === '' ) {
1968 return 'rtl';
1969 }
1970 return 'ltr';
1971 }
1972
1980 static function romanNumeral( $num ) {
1981 static $table = [
1982 [ '', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X' ],
1983 [ '', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC', 'C' ],
1984 [ '', 'C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM', 'M' ],
1985 [ '', 'M', 'MM', 'MMM', 'MMMM', 'MMMMM', 'MMMMMM', 'MMMMMMM',
1986 'MMMMMMMM', 'MMMMMMMMM', 'MMMMMMMMMM' ]
1987 ];
1988
1989 $num = intval( $num );
1990 if ( $num > 10000 || $num <= 0 ) {
1991 return $num;
1992 }
1993
1994 $s = '';
1995 for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) {
1996 if ( $num >= $pow10 ) {
1997 $s .= $table[$i][(int)floor( $num / $pow10 )];
1998 }
1999 $num = $num % $pow10;
2000 }
2001 return $s;
2002 }
2003
2011 static function hebrewNumeral( $num ) {
2012 static $table = [
2013 [ '', 'א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ז', 'ח', 'ט', 'י' ],
2014 [ '', 'י', 'כ', 'ל', 'מ', 'נ', 'ס', 'ע', 'פ', 'צ', 'ק' ],
2015 [ '',
2016 [ 'ק' ],
2017 [ 'ר' ],
2018 [ 'ש' ],
2019 [ 'ת' ],
2020 [ 'ת', 'ק' ],
2021 [ 'ת', 'ר' ],
2022 [ 'ת', 'ש' ],
2023 [ 'ת', 'ת' ],
2024 [ 'ת', 'ת', 'ק' ],
2025 [ 'ת', 'ת', 'ר' ],
2026 ],
2027 [ '', 'א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ז', 'ח', 'ט', 'י' ]
2028 ];
2029
2030 $num = intval( $num );
2031 if ( $num > 9999 || $num <= 0 ) {
2032 return $num;
2033 }
2034
2035 // Round thousands have special notations
2036 if ( $num === 1000 ) {
2037 return "א' אלף";
2038 } elseif ( $num % 1000 === 0 ) {
2039 return $table[0][$num / 1000] . "' אלפים";
2040 }
2041
2042 $letters = [];
2043
2044 for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) {
2045 if ( $num >= $pow10 ) {
2046 if ( $num === 15 || $num === 16 ) {
2047 $letters[] = $table[0][9];
2048 $letters[] = $table[0][$num - 9];
2049 $num = 0;
2050 } else {
2051 $letters = array_merge(
2052 $letters,
2053 (array)$table[$i][intval( $num / $pow10 )]
2054 );
2055
2056 if ( $pow10 === 1000 ) {
2057 $letters[] = "'";
2058 }
2059 }
2060 }
2061
2062 $num = $num % $pow10;
2063 }
2064
2065 $preTransformLength = count( $letters );
2066 if ( $preTransformLength === 1 ) {
2067 // Add geresh (single quote) to one-letter numbers
2068 $letters[] = "'";
2069 } else {
2070 $lastIndex = $preTransformLength - 1;
2071 $letters[$lastIndex] = str_replace(
2072 [ 'כ', 'מ', 'נ', 'פ', 'צ' ],
2073 [ 'ך', 'ם', 'ן', 'ף', 'ץ' ],
2074 $letters[$lastIndex]
2075 );
2076
2077 // Add gershayim (double quote) to multiple-letter numbers,
2078 // but exclude numbers with only one letter after the thousands
2079 // (1001-1009, 1020, 1030, 2001-2009, etc.)
2080 if ( $letters[1] === "'" && $preTransformLength === 3 ) {
2081 $letters[] = "'";
2082 } else {
2083 array_splice( $letters, -1, 0, '"' );
2084 }
2085 }
2086
2087 return implode( $letters );
2088 }
2089
2098 public function userAdjust( $ts, $tz = false ) {
2100
2101 if ( $tz === false ) {
2102 $tz = $wgUser->getOption( 'timecorrection' );
2103 }
2104
2105 $data = explode( '|', $tz, 3 );
2106
2107 if ( $data[0] == 'ZoneInfo' ) {
2108 try {
2109 $userTZ = new DateTimeZone( $data[2] );
2110 $date = new DateTime( $ts, new DateTimeZone( 'UTC' ) );
2111 $date->setTimezone( $userTZ );
2112 return $date->format( 'YmdHis' );
2113 } catch ( Exception $e ) {
2114 // Unrecognized timezone, default to 'Offset' with the stored offset.
2115 $data[0] = 'Offset';
2116 }
2117 }
2118
2119 if ( $data[0] == 'System' || $tz == '' ) {
2120 # Global offset in minutes.
2121 $minDiff = $wgLocalTZoffset;
2122 } elseif ( $data[0] == 'Offset' ) {
2123 $minDiff = intval( $data[1] );
2124 } else {
2125 $data = explode( ':', $tz );
2126 if ( count( $data ) == 2 ) {
2127 $data[0] = intval( $data[0] );
2128 $data[1] = intval( $data[1] );
2129 $minDiff = abs( $data[0] ) * 60 + $data[1];
2130 if ( $data[0] < 0 ) {
2131 $minDiff = -$minDiff;
2132 }
2133 } else {
2134 $minDiff = intval( $data[0] ) * 60;
2135 }
2136 }
2137
2138 # No difference ? Return time unchanged
2139 if ( 0 == $minDiff ) {
2140 return $ts;
2141 }
2142
2143 MediaWiki\suppressWarnings(); // E_STRICT system time bitching
2144 # Generate an adjusted date; take advantage of the fact that mktime
2145 # will normalize out-of-range values so we don't have to split $minDiff
2146 # into hours and minutes.
2147 $t = mktime( (
2148 (int)substr( $ts, 8, 2 ) ), # Hours
2149 (int)substr( $ts, 10, 2 ) + $minDiff, # Minutes
2150 (int)substr( $ts, 12, 2 ), # Seconds
2151 (int)substr( $ts, 4, 2 ), # Month
2152 (int)substr( $ts, 6, 2 ), # Day
2153 (int)substr( $ts, 0, 4 ) ); # Year
2154
2155 $date = date( 'YmdHis', $t );
2156 MediaWiki\restoreWarnings();
2157
2158 return $date;
2159 }
2160
2176 function dateFormat( $usePrefs = true ) {
2178
2179 if ( is_bool( $usePrefs ) ) {
2180 if ( $usePrefs ) {
2181 $datePreference = $wgUser->getDatePreference();
2182 } else {
2183 $datePreference = (string)User::getDefaultOption( 'date' );
2184 }
2185 } else {
2186 $datePreference = (string)$usePrefs;
2187 }
2188
2189 // return int
2190 if ( $datePreference == '' ) {
2191 return 'default';
2192 }
2193
2194 return $datePreference;
2195 }
2196
2207 function getDateFormatString( $type, $pref ) {
2208 $wasDefault = false;
2209 if ( $pref == 'default' ) {
2210 $wasDefault = true;
2211 $pref = $this->getDefaultDateFormat();
2212 }
2213
2214 if ( !isset( $this->dateFormatStrings[$type][$pref] ) ) {
2215 $df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" );
2216
2217 if ( $type === 'pretty' && $df === null ) {
2218 $df = $this->getDateFormatString( 'date', $pref );
2219 }
2220
2221 if ( !$wasDefault && $df === null ) {
2222 $pref = $this->getDefaultDateFormat();
2223 $df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" );
2224 }
2225
2226 $this->dateFormatStrings[$type][$pref] = $df;
2227 }
2228 return $this->dateFormatStrings[$type][$pref];
2229 }
2230
2241 public function date( $ts, $adj = false, $format = true, $timecorrection = false ) {
2242 $ts = wfTimestamp( TS_MW, $ts );
2243 if ( $adj ) {
2244 $ts = $this->userAdjust( $ts, $timecorrection );
2245 }
2246 $df = $this->getDateFormatString( 'date', $this->dateFormat( $format ) );
2247 return $this->sprintfDate( $df, $ts );
2248 }
2249
2260 public function time( $ts, $adj = false, $format = true, $timecorrection = false ) {
2261 $ts = wfTimestamp( TS_MW, $ts );
2262 if ( $adj ) {
2263 $ts = $this->userAdjust( $ts, $timecorrection );
2264 }
2265 $df = $this->getDateFormatString( 'time', $this->dateFormat( $format ) );
2266 return $this->sprintfDate( $df, $ts );
2267 }
2268
2280 public function timeanddate( $ts, $adj = false, $format = true, $timecorrection = false ) {
2281 $ts = wfTimestamp( TS_MW, $ts );
2282 if ( $adj ) {
2283 $ts = $this->userAdjust( $ts, $timecorrection );
2284 }
2285 $df = $this->getDateFormatString( 'both', $this->dateFormat( $format ) );
2286 return $this->sprintfDate( $df, $ts );
2287 }
2288
2299 public function formatDuration( $seconds, array $chosenIntervals = [] ) {
2300 $intervals = $this->getDurationIntervals( $seconds, $chosenIntervals );
2301
2302 $segments = [];
2303
2304 foreach ( $intervals as $intervalName => $intervalValue ) {
2305 // Messages: duration-seconds, duration-minutes, duration-hours, duration-days, duration-weeks,
2306 // duration-years, duration-decades, duration-centuries, duration-millennia
2307 $message = wfMessage( 'duration-' . $intervalName )->numParams( $intervalValue );
2308 $segments[] = $message->inLanguage( $this )->escaped();
2309 }
2310
2311 return $this->listToText( $segments );
2312 }
2313
2325 public function getDurationIntervals( $seconds, array $chosenIntervals = [] ) {
2326 if ( empty( $chosenIntervals ) ) {
2327 $chosenIntervals = [
2328 'millennia',
2329 'centuries',
2330 'decades',
2331 'years',
2332 'days',
2333 'hours',
2334 'minutes',
2335 'seconds'
2336 ];
2337 }
2338
2339 $intervals = array_intersect_key( self::$durationIntervals, array_flip( $chosenIntervals ) );
2340 $sortedNames = array_keys( $intervals );
2341 $smallestInterval = array_pop( $sortedNames );
2342
2343 $segments = [];
2344
2345 foreach ( $intervals as $name => $length ) {
2346 $value = floor( $seconds / $length );
2347
2348 if ( $value > 0 || ( $name == $smallestInterval && empty( $segments ) ) ) {
2349 $seconds -= $value * $length;
2350 $segments[$name] = $value;
2351 }
2352 }
2353
2354 return $segments;
2355 }
2356
2376 private function internalUserTimeAndDate( $type, $ts, User $user, array $options ) {
2377 $ts = wfTimestamp( TS_MW, $ts );
2378 $options += [ 'timecorrection' => true, 'format' => true ];
2379 if ( $options['timecorrection'] !== false ) {
2380 if ( $options['timecorrection'] === true ) {
2381 $offset = $user->getOption( 'timecorrection' );
2382 } else {
2383 $offset = $options['timecorrection'];
2384 }
2385 $ts = $this->userAdjust( $ts, $offset );
2386 }
2387 if ( $options['format'] === true ) {
2388 $format = $user->getDatePreference();
2389 } else {
2390 $format = $options['format'];
2391 }
2392 $df = $this->getDateFormatString( $type, $this->dateFormat( $format ) );
2393 return $this->sprintfDate( $df, $ts );
2394 }
2395
2415 public function userDate( $ts, User $user, array $options = [] ) {
2416 return $this->internalUserTimeAndDate( 'date', $ts, $user, $options );
2417 }
2418
2438 public function userTime( $ts, User $user, array $options = [] ) {
2439 return $this->internalUserTimeAndDate( 'time', $ts, $user, $options );
2440 }
2441
2461 public function userTimeAndDate( $ts, User $user, array $options = [] ) {
2462 return $this->internalUserTimeAndDate( 'both', $ts, $user, $options );
2463 }
2464
2480 public function getHumanTimestamp(
2481 MWTimestamp $time, MWTimestamp $relativeTo = null, User $user = null
2482 ) {
2483 if ( $relativeTo === null ) {
2484 $relativeTo = new MWTimestamp();
2485 }
2486 if ( $user === null ) {
2487 $user = RequestContext::getMain()->getUser();
2488 }
2489
2490 // Adjust for the user's timezone.
2491 $offsetThis = $time->offsetForUser( $user );
2492 $offsetRel = $relativeTo->offsetForUser( $user );
2493
2494 $ts = '';
2495 if ( Hooks::run( 'GetHumanTimestamp', [ &$ts, $time, $relativeTo, $user, $this ] ) ) {
2496 $ts = $this->getHumanTimestampInternal( $time, $relativeTo, $user );
2497 }
2498
2499 // Reset the timezone on the objects.
2500 $time->timestamp->sub( $offsetThis );
2501 $relativeTo->timestamp->sub( $offsetRel );
2502
2503 return $ts;
2504 }
2505
2518 MWTimestamp $ts, MWTimestamp $relativeTo, User $user
2519 ) {
2520 $diff = $ts->diff( $relativeTo );
2521 $diffDay = (bool)( (int)$ts->timestamp->format( 'w' ) -
2522 (int)$relativeTo->timestamp->format( 'w' ) );
2523 $days = $diff->days ?: (int)$diffDay;
2524 if ( $diff->invert || $days > 5
2525 && $ts->timestamp->format( 'Y' ) !== $relativeTo->timestamp->format( 'Y' )
2526 ) {
2527 // Timestamps are in different years: use full timestamp
2528 // Also do full timestamp for future dates
2532 $format = $this->getDateFormatString( 'both', $user->getDatePreference() ?: 'default' );
2533 $ts = $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) );
2534 } elseif ( $days > 5 ) {
2535 // Timestamps are in same year, but more than 5 days ago: show day and month only.
2536 $format = $this->getDateFormatString( 'pretty', $user->getDatePreference() ?: 'default' );
2537 $ts = $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) );
2538 } elseif ( $days > 1 ) {
2539 // Timestamp within the past week: show the day of the week and time
2540 $format = $this->getDateFormatString( 'time', $user->getDatePreference() ?: 'default' );
2541 $weekday = self::$mWeekdayMsgs[$ts->timestamp->format( 'w' )];
2542 // Messages:
2543 // sunday-at, monday-at, tuesday-at, wednesday-at, thursday-at, friday-at, saturday-at
2544 $ts = wfMessage( "$weekday-at" )
2545 ->inLanguage( $this )
2546 ->params( $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) ) )
2547 ->text();
2548 } elseif ( $days == 1 ) {
2549 // Timestamp was yesterday: say 'yesterday' and the time.
2550 $format = $this->getDateFormatString( 'time', $user->getDatePreference() ?: 'default' );
2551 $ts = wfMessage( 'yesterday-at' )
2552 ->inLanguage( $this )
2553 ->params( $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) ) )
2554 ->text();
2555 } elseif ( $diff->h > 1 || $diff->h == 1 && $diff->i > 30 ) {
2556 // Timestamp was today, but more than 90 minutes ago: say 'today' and the time.
2557 $format = $this->getDateFormatString( 'time', $user->getDatePreference() ?: 'default' );
2558 $ts = wfMessage( 'today-at' )
2559 ->inLanguage( $this )
2560 ->params( $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) ) )
2561 ->text();
2562
2563 // From here on in, the timestamp was soon enough ago so that we can simply say
2564 // XX units ago, e.g., "2 hours ago" or "5 minutes ago"
2565 } elseif ( $diff->h == 1 ) {
2566 // Less than 90 minutes, but more than an hour ago.
2567 $ts = wfMessage( 'hours-ago' )->inLanguage( $this )->numParams( 1 )->text();
2568 } elseif ( $diff->i >= 1 ) {
2569 // A few minutes ago.
2570 $ts = wfMessage( 'minutes-ago' )->inLanguage( $this )->numParams( $diff->i )->text();
2571 } elseif ( $diff->s >= 30 ) {
2572 // Less than a minute, but more than 30 sec ago.
2573 $ts = wfMessage( 'seconds-ago' )->inLanguage( $this )->numParams( $diff->s )->text();
2574 } else {
2575 // Less than 30 seconds ago.
2576 $ts = wfMessage( 'just-now' )->text();
2577 }
2578
2579 return $ts;
2580 }
2581
2586 public function getMessage( $key ) {
2587 return self::$dataCache->getSubitem( $this->mCode, 'messages', $key );
2588 }
2589
2593 function getAllMessages() {
2594 return self::$dataCache->getItem( $this->mCode, 'messages' );
2595 }
2596
2603 public function iconv( $in, $out, $string ) {
2604 # Even with //IGNORE iconv can whine about illegal characters in
2605 # *input* string. We just ignore those too.
2606 # REF: https://bugs.php.net/bug.php?id=37166
2607 # REF: https://phabricator.wikimedia.org/T18885
2608 MediaWiki\suppressWarnings();
2609 $text = iconv( $in, $out . '//IGNORE', $string );
2610 MediaWiki\restoreWarnings();
2611 return $text;
2612 }
2613
2614 // callback functions for ucwords(), ucwordbreaks()
2615
2621 return $this->ucfirst( $matches[1] );
2622 }
2623
2629 return mb_strtoupper( $matches[0] );
2630 }
2631
2637 return mb_strtoupper( $matches[0] );
2638 }
2639
2647 public function ucfirst( $str ) {
2648 $o = ord( $str );
2649 if ( $o < 96 ) { // if already uppercase...
2650 return $str;
2651 } elseif ( $o < 128 ) {
2652 return ucfirst( $str ); // use PHP's ucfirst()
2653 } else {
2654 // fall back to more complex logic in case of multibyte strings
2655 return $this->uc( $str, true );
2656 }
2657 }
2658
2667 public function uc( $str, $first = false ) {
2668 if ( $first ) {
2669 if ( $this->isMultibyte( $str ) ) {
2670 return mb_strtoupper( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
2671 } else {
2672 return ucfirst( $str );
2673 }
2674 } else {
2675 return $this->isMultibyte( $str ) ? mb_strtoupper( $str ) : strtoupper( $str );
2676 }
2677 }
2678
2683 function lcfirst( $str ) {
2684 $o = ord( $str );
2685 if ( !$o ) {
2686 return strval( $str );
2687 } elseif ( $o >= 128 ) {
2688 return $this->lc( $str, true );
2689 } elseif ( $o > 96 ) {
2690 return $str;
2691 } else {
2692 $str[0] = strtolower( $str[0] );
2693 return $str;
2694 }
2695 }
2696
2702 function lc( $str, $first = false ) {
2703 if ( $first ) {
2704 if ( $this->isMultibyte( $str ) ) {
2705 return mb_strtolower( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
2706 } else {
2707 return strtolower( substr( $str, 0, 1 ) ) . substr( $str, 1 );
2708 }
2709 } else {
2710 return $this->isMultibyte( $str ) ? mb_strtolower( $str ) : strtolower( $str );
2711 }
2712 }
2713
2718 function isMultibyte( $str ) {
2719 return strlen( $str ) !== mb_strlen( $str );
2720 }
2721
2726 function ucwords( $str ) {
2727 if ( $this->isMultibyte( $str ) ) {
2728 $str = $this->lc( $str );
2729
2730 // regexp to find first letter in each word (i.e. after each space)
2731 $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)| ([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
2732
2733 // function to use to capitalize a single char
2734 return preg_replace_callback(
2735 $replaceRegexp,
2736 [ $this, 'ucwordsCallbackMB' ],
2737 $str
2738 );
2739 } else {
2740 return ucwords( strtolower( $str ) );
2741 }
2742 }
2743
2750 function ucwordbreaks( $str ) {
2751 if ( $this->isMultibyte( $str ) ) {
2752 $str = $this->lc( $str );
2753
2754 // since \b doesn't work for UTF-8, we explicitely define word break chars
2755 $breaks = "[ \-\‍(\‍)\}\{\.,\?!]";
2756
2757 // find first letter after word break
2758 $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)|" .
2759 "$breaks([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
2760
2761 return preg_replace_callback(
2762 $replaceRegexp,
2763 [ $this, 'ucwordbreaksCallbackMB' ],
2764 $str
2765 );
2766 } else {
2767 return preg_replace_callback(
2768 '/\b([\w\x80-\xff]+)\b/',
2769 [ $this, 'ucwordbreaksCallbackAscii' ],
2770 $str
2771 );
2772 }
2773 }
2774
2790 function caseFold( $s ) {
2791 return $this->uc( $s );
2792 }
2793
2800 if ( is_array( $s ) ) {
2801 throw new MWException( 'Given array to checkTitleEncoding.' );
2802 }
2803 if ( StringUtils::isUtf8( $s ) ) {
2804 return $s;
2805 }
2806
2807 return $this->iconv( $this->fallback8bitEncoding(), 'utf-8', $s );
2808 }
2809
2814 return self::$dataCache->getItem( $this->mCode, 'fallback8bitEncoding' );
2815 }
2816
2825 function hasWordBreaks() {
2826 return true;
2827 }
2828
2836 function segmentByWord( $string ) {
2837 return $string;
2838 }
2839
2847 function normalizeForSearch( $string ) {
2848 return self::convertDoubleWidth( $string );
2849 }
2850
2859 protected static function convertDoubleWidth( $string ) {
2860 static $full = null;
2861 static $half = null;
2862
2863 if ( $full === null ) {
2864 $fullWidth = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
2865 $halfWidth = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
2866 $full = str_split( $fullWidth, 3 );
2867 $half = str_split( $halfWidth );
2868 }
2869
2870 $string = str_replace( $full, $half, $string );
2871 return $string;
2872 }
2873
2879 protected static function insertSpace( $string, $pattern ) {
2880 $string = preg_replace( $pattern, " $1 ", $string );
2881 $string = preg_replace( '/ +/', ' ', $string );
2882 return $string;
2883 }
2884
2889 function convertForSearchResult( $termsArray ) {
2890 # some languages, e.g. Chinese, need to do a conversion
2891 # in order for search results to be displayed correctly
2892 return $termsArray;
2893 }
2894
2901 function firstChar( $s ) {
2902 $matches = [];
2903 preg_match(
2904 '/^([\x00-\x7f]|[\xc0-\xdf][\x80-\xbf]|' .
2905 '[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xf7][\x80-\xbf]{3})/',
2906 $s,
2907 $matches
2908 );
2909
2910 if ( isset( $matches[1] ) ) {
2911 if ( strlen( $matches[1] ) != 3 ) {
2912 return $matches[1];
2913 }
2914
2915 // Break down Hangul syllables to grab the first jamo
2916 $code = UtfNormal\Utils::utf8ToCodepoint( $matches[1] );
2917 if ( $code < 0xac00 || 0xd7a4 <= $code ) {
2918 return $matches[1];
2919 } elseif ( $code < 0xb098 ) {
2920 return "\xe3\x84\xb1";
2921 } elseif ( $code < 0xb2e4 ) {
2922 return "\xe3\x84\xb4";
2923 } elseif ( $code < 0xb77c ) {
2924 return "\xe3\x84\xb7";
2925 } elseif ( $code < 0xb9c8 ) {
2926 return "\xe3\x84\xb9";
2927 } elseif ( $code < 0xbc14 ) {
2928 return "\xe3\x85\x81";
2929 } elseif ( $code < 0xc0ac ) {
2930 return "\xe3\x85\x82";
2931 } elseif ( $code < 0xc544 ) {
2932 return "\xe3\x85\x85";
2933 } elseif ( $code < 0xc790 ) {
2934 return "\xe3\x85\x87";
2935 } elseif ( $code < 0xcc28 ) {
2936 return "\xe3\x85\x88";
2937 } elseif ( $code < 0xce74 ) {
2938 return "\xe3\x85\x8a";
2939 } elseif ( $code < 0xd0c0 ) {
2940 return "\xe3\x85\x8b";
2941 } elseif ( $code < 0xd30c ) {
2942 return "\xe3\x85\x8c";
2943 } elseif ( $code < 0xd558 ) {
2944 return "\xe3\x85\x8d";
2945 } else {
2946 return "\xe3\x85\x8e";
2947 }
2948 } else {
2949 return '';
2950 }
2951 }
2952
2956 function initEncoding() {
2957 // No-op.
2958 }
2959
2965 function recodeForEdit( $s ) {
2966 return $s;
2967 }
2968
2974 function recodeInput( $s ) {
2975 return $s;
2976 }
2977
2989 function normalize( $s ) {
2991 $s = UtfNormal\Validator::cleanUp( $s );
2992 if ( $wgAllUnicodeFixes ) {
2993 $s = $this->transformUsingPairFile( 'normalize-ar.ser', $s );
2994 $s = $this->transformUsingPairFile( 'normalize-ml.ser', $s );
2995 }
2996
2997 return $s;
2998 }
2999
3014 function transformUsingPairFile( $file, $string ) {
3015 if ( !isset( $this->transformData[$file] ) ) {
3016 $data = wfGetPrecompiledData( $file );
3017 if ( $data === false ) {
3018 throw new MWException( __METHOD__ . ": The transformation file $file is missing" );
3019 }
3020 $this->transformData[$file] = new ReplacementArray( $data );
3021 }
3022 return $this->transformData[$file]->replace( $string );
3023 }
3024
3030 function isRTL() {
3031 return self::$dataCache->getItem( $this->mCode, 'rtl' );
3032 }
3033
3038 function getDir() {
3039 return $this->isRTL() ? 'rtl' : 'ltr';
3040 }
3041
3050 function alignStart() {
3051 return $this->isRTL() ? 'right' : 'left';
3052 }
3053
3062 function alignEnd() {
3063 return $this->isRTL() ? 'left' : 'right';
3064 }
3065
3077 function getDirMarkEntity( $opposite = false ) {
3078 if ( $opposite ) {
3079 return $this->isRTL() ? '&lrm;' : '&rlm;';
3080 }
3081 return $this->isRTL() ? '&rlm;' : '&lrm;';
3082 }
3083
3094 function getDirMark( $opposite = false ) {
3095 $lrm = "\xE2\x80\x8E"; # LEFT-TO-RIGHT MARK, commonly abbreviated LRM
3096 $rlm = "\xE2\x80\x8F"; # RIGHT-TO-LEFT MARK, commonly abbreviated RLM
3097 if ( $opposite ) {
3098 return $this->isRTL() ? $lrm : $rlm;
3099 }
3100 return $this->isRTL() ? $rlm : $lrm;
3101 }
3102
3107 return self::$dataCache->getItem( $this->mCode, 'capitalizeAllNouns' );
3108 }
3109
3117 function getArrow( $direction = 'forwards' ) {
3118 switch ( $direction ) {
3119 case 'forwards':
3120 return $this->isRTL() ? '←' : '→';
3121 case 'backwards':
3122 return $this->isRTL() ? '→' : '←';
3123 case 'left':
3124 return '←';
3125 case 'right':
3126 return '→';
3127 case 'up':
3128 return '↑';
3129 case 'down':
3130 return '↓';
3131 }
3132 }
3133
3140 return self::$dataCache->getItem( $this->mCode, 'linkPrefixExtension' );
3141 }
3142
3147 function getMagicWords() {
3148 return self::$dataCache->getItem( $this->mCode, 'magicWords' );
3149 }
3150
3154 protected function doMagicHook() {
3155 if ( $this->mMagicHookDone ) {
3156 return;
3157 }
3158 $this->mMagicHookDone = true;
3159 Hooks::run( 'LanguageGetMagic', [ &$this->mMagicExtensions, $this->getCode() ] );
3160 }
3161
3167 function getMagic( $mw ) {
3168 // Saves a function call
3169 if ( !$this->mMagicHookDone ) {
3170 $this->doMagicHook();
3171 }
3172
3173 if ( isset( $this->mMagicExtensions[$mw->mId] ) ) {
3174 $rawEntry = $this->mMagicExtensions[$mw->mId];
3175 } else {
3176 $rawEntry = self::$dataCache->getSubitem(
3177 $this->mCode, 'magicWords', $mw->mId );
3178 }
3179
3180 if ( !is_array( $rawEntry ) ) {
3181 wfWarn( "\"$rawEntry\" is not a valid magic word for \"$mw->mId\"" );
3182 } else {
3183 $mw->mCaseSensitive = $rawEntry[0];
3184 $mw->mSynonyms = array_slice( $rawEntry, 1 );
3185 }
3186 }
3187
3193 function addMagicWordsByLang( $newWords ) {
3194 $fallbackChain = $this->getFallbackLanguages();
3195 $fallbackChain = array_reverse( $fallbackChain );
3196 foreach ( $fallbackChain as $code ) {
3197 if ( isset( $newWords[$code] ) ) {
3198 $this->mMagicExtensions = $newWords[$code] + $this->mMagicExtensions;
3199 }
3200 }
3201 }
3202
3209 // Cache aliases because it may be slow to load them
3210 if ( is_null( $this->mExtendedSpecialPageAliases ) ) {
3211 // Initialise array
3212 $this->mExtendedSpecialPageAliases =
3213 self::$dataCache->getItem( $this->mCode, 'specialPageAliases' );
3214 Hooks::run( 'LanguageGetSpecialPageAliases',
3215 [ &$this->mExtendedSpecialPageAliases, $this->getCode() ] );
3216 }
3217
3218 return $this->mExtendedSpecialPageAliases;
3219 }
3220
3227 function emphasize( $text ) {
3228 return "<em>$text</em>";
3229 }
3230
3253 public function formatNum( $number, $nocommafy = false ) {
3255 if ( !$nocommafy ) {
3256 $number = $this->commafy( $number );
3257 $s = $this->separatorTransformTable();
3258 if ( $s ) {
3259 $number = strtr( $number, $s );
3260 }
3261 }
3262
3263 if ( $wgTranslateNumerals ) {
3264 $s = $this->digitTransformTable();
3265 if ( $s ) {
3266 $number = strtr( $number, $s );
3267 }
3268 }
3269
3270 return $number;
3271 }
3272
3281 public function formatNumNoSeparators( $number ) {
3282 return $this->formatNum( $number, true );
3283 }
3284
3289 public function parseFormattedNumber( $number ) {
3290 $s = $this->digitTransformTable();
3291 if ( $s ) {
3292 // eliminate empty array values such as ''. (T66347)
3293 $s = array_filter( $s );
3294 $number = strtr( $number, array_flip( $s ) );
3295 }
3296
3297 $s = $this->separatorTransformTable();
3298 if ( $s ) {
3299 // eliminate empty array values such as ''. (T66347)
3300 $s = array_filter( $s );
3301 $number = strtr( $number, array_flip( $s ) );
3302 }
3303
3304 $number = strtr( $number, [ ',' => '' ] );
3305 return $number;
3306 }
3307
3314 function commafy( $number ) {
3316 if ( $number === null ) {
3317 return '';
3318 }
3319
3320 if ( !$digitGroupingPattern || $digitGroupingPattern === "###,###,###" ) {
3321 // default grouping is at thousands, use the same for ###,###,### pattern too.
3322 return strrev( (string)preg_replace( '/(\d{3})(?=\d)(?!\d*\.)/', '$1,', strrev( $number ) ) );
3323 } else {
3324 // Ref: http://cldr.unicode.org/translation/number-patterns
3325 $sign = "";
3326 if ( intval( $number ) < 0 ) {
3327 // For negative numbers apply the algorithm like positive number and add sign.
3328 $sign = "-";
3329 $number = substr( $number, 1 );
3330 }
3331 $integerPart = [];
3332 $decimalPart = [];
3333 $numMatches = preg_match_all( "/(#+)/", $digitGroupingPattern, $matches );
3334 preg_match( "/\d+/", $number, $integerPart );
3335 preg_match( "/\.\d*/", $number, $decimalPart );
3336 $groupedNumber = ( count( $decimalPart ) > 0 ) ? $decimalPart[0] : "";
3337 if ( $groupedNumber === $number ) {
3338 // the string does not have any number part. Eg: .12345
3339 return $sign . $groupedNumber;
3340 }
3341 $start = $end = ( $integerPart ) ? strlen( $integerPart[0] ) : 0;
3342 while ( $start > 0 ) {
3343 $match = $matches[0][$numMatches - 1];
3344 $matchLen = strlen( $match );
3345 $start = $end - $matchLen;
3346 if ( $start < 0 ) {
3347 $start = 0;
3348 }
3349 $groupedNumber = substr( $number, $start, $end - $start ) . $groupedNumber;
3350 $end = $start;
3351 if ( $numMatches > 1 ) {
3352 // use the last pattern for the rest of the number
3353 $numMatches--;
3354 }
3355 if ( $start > 0 ) {
3356 $groupedNumber = "," . $groupedNumber;
3357 }
3358 }
3359 return $sign . $groupedNumber;
3360 }
3361 }
3362
3367 return self::$dataCache->getItem( $this->mCode, 'digitGroupingPattern' );
3368 }
3369
3374 return self::$dataCache->getItem( $this->mCode, 'digitTransformTable' );
3375 }
3376
3381 return self::$dataCache->getItem( $this->mCode, 'separatorTransformTable' );
3382 }
3383
3393 function listToText( array $l ) {
3394 $m = count( $l ) - 1;
3395 if ( $m < 0 ) {
3396 return '';
3397 }
3398 if ( $m > 0 ) {
3399 $and = $this->msg( 'and' )->escaped();
3400 $space = $this->msg( 'word-separator' )->escaped();
3401 if ( $m > 1 ) {
3402 $comma = $this->msg( 'comma-separator' )->escaped();
3403 }
3404 }
3405 $s = $l[$m];
3406 for ( $i = $m - 1; $i >= 0; $i-- ) {
3407 if ( $i == $m - 1 ) {
3408 $s = $l[$i] . $and . $space . $s;
3409 } else {
3410 $s = $l[$i] . $comma . $s;
3411 }
3412 }
3413 return $s;
3414 }
3415
3422 function commaList( array $list ) {
3423 return implode(
3424 wfMessage( 'comma-separator' )->inLanguage( $this )->escaped(),
3425 $list
3426 );
3427 }
3428
3435 function semicolonList( array $list ) {
3436 return implode(
3437 wfMessage( 'semicolon-separator' )->inLanguage( $this )->escaped(),
3438 $list
3439 );
3440 }
3441
3447 function pipeList( array $list ) {
3448 return implode(
3449 wfMessage( 'pipe-separator' )->inLanguage( $this )->escaped(),
3450 $list
3451 );
3452 }
3453
3471 function truncate( $string, $length, $ellipsis = '...', $adjustLength = true ) {
3472 # Use the localized ellipsis character
3473 if ( $ellipsis == '...' ) {
3474 $ellipsis = wfMessage( 'ellipsis' )->inLanguage( $this )->escaped();
3475 }
3476 # Check if there is no need to truncate
3477 if ( $length == 0 ) {
3478 return $ellipsis; // convention
3479 } elseif ( strlen( $string ) <= abs( $length ) ) {
3480 return $string; // no need to truncate
3481 }
3482 $stringOriginal = $string;
3483 # If ellipsis length is >= $length then we can't apply $adjustLength
3484 if ( $adjustLength && strlen( $ellipsis ) >= abs( $length ) ) {
3485 $string = $ellipsis; // this can be slightly unexpected
3486 # Otherwise, truncate and add ellipsis...
3487 } else {
3488 $eLength = $adjustLength ? strlen( $ellipsis ) : 0;
3489 if ( $length > 0 ) {
3490 $length -= $eLength;
3491 $string = substr( $string, 0, $length ); // xyz...
3492 $string = $this->removeBadCharLast( $string );
3493 $string = rtrim( $string );
3494 $string = $string . $ellipsis;
3495 } else {
3496 $length += $eLength;
3497 $string = substr( $string, $length ); // ...xyz
3498 $string = $this->removeBadCharFirst( $string );
3499 $string = ltrim( $string );
3500 $string = $ellipsis . $string;
3501 }
3502 }
3503 # Do not truncate if the ellipsis makes the string longer/equal (T24181).
3504 # This check is *not* redundant if $adjustLength, due to the single case where
3505 # LEN($ellipsis) > ABS($limit arg); $stringOriginal could be shorter than $string.
3506 if ( strlen( $string ) < strlen( $stringOriginal ) ) {
3507 return $string;
3508 } else {
3509 return $stringOriginal;
3510 }
3511 }
3512
3520 protected function removeBadCharLast( $string ) {
3521 if ( $string != '' ) {
3522 $char = ord( $string[strlen( $string ) - 1] );
3523 $m = [];
3524 if ( $char >= 0xc0 ) {
3525 # We got the first byte only of a multibyte char; remove it.
3526 $string = substr( $string, 0, -1 );
3527 } elseif ( $char >= 0x80 &&
3528 // Use the /s modifier (PCRE_DOTALL) so (.*) also matches newlines
3529 preg_match( '/^(.*)(?:[\xe0-\xef][\x80-\xbf]|' .
3530 '[\xf0-\xf7][\x80-\xbf]{1,2})$/s', $string, $m )
3531 ) {
3532 # We chopped in the middle of a character; remove it
3533 $string = $m[1];
3534 }
3535 }
3536 return $string;
3537 }
3538
3546 protected function removeBadCharFirst( $string ) {
3547 if ( $string != '' ) {
3548 $char = ord( $string[0] );
3549 if ( $char >= 0x80 && $char < 0xc0 ) {
3550 # We chopped in the middle of a character; remove the whole thing
3551 $string = preg_replace( '/^[\x80-\xbf]+/', '', $string );
3552 }
3553 }
3554 return $string;
3555 }
3556
3572 function truncateHtml( $text, $length, $ellipsis = '...' ) {
3573 # Use the localized ellipsis character
3574 if ( $ellipsis == '...' ) {
3575 $ellipsis = wfMessage( 'ellipsis' )->inLanguage( $this )->escaped();
3576 }
3577 # Check if there is clearly no need to truncate
3578 if ( $length <= 0 ) {
3579 return $ellipsis; // no text shown, nothing to format (convention)
3580 } elseif ( strlen( $text ) <= $length ) {
3581 return $text; // string short enough even *with* HTML (short-circuit)
3582 }
3583
3584 $dispLen = 0; // innerHTML legth so far
3585 $testingEllipsis = false; // checking if ellipses will make string longer/equal?
3586 $tagType = 0; // 0-open, 1-close
3587 $bracketState = 0; // 1-tag start, 2-tag name, 0-neither
3588 $entityState = 0; // 0-not entity, 1-entity
3589 $tag = $ret = ''; // accumulated tag name, accumulated result string
3590 $openTags = []; // open tag stack
3591 $maybeState = null; // possible truncation state
3592
3593 $textLen = strlen( $text );
3594 $neLength = max( 0, $length - strlen( $ellipsis ) ); // non-ellipsis len if truncated
3595 for ( $pos = 0; true; ++$pos ) {
3596 # Consider truncation once the display length has reached the maximim.
3597 # We check if $dispLen > 0 to grab tags for the $neLength = 0 case.
3598 # Check that we're not in the middle of a bracket/entity...
3599 if ( $dispLen && $dispLen >= $neLength && $bracketState == 0 && !$entityState ) {
3600 if ( !$testingEllipsis ) {
3601 $testingEllipsis = true;
3602 # Save where we are; we will truncate here unless there turn out to
3603 # be so few remaining characters that truncation is not necessary.
3604 if ( !$maybeState ) { // already saved? ($neLength = 0 case)
3605 $maybeState = [ $ret, $openTags ]; // save state
3606 }
3607 } elseif ( $dispLen > $length && $dispLen > strlen( $ellipsis ) ) {
3608 # String in fact does need truncation, the truncation point was OK.
3609 list( $ret, $openTags ) = $maybeState; // reload state
3610 $ret = $this->removeBadCharLast( $ret ); // multi-byte char fix
3611 $ret .= $ellipsis; // add ellipsis
3612 break;
3613 }
3614 }
3615 if ( $pos >= $textLen ) {
3616 break; // extra iteration just for above checks
3617 }
3618
3619 # Read the next char...
3620 $ch = $text[$pos];
3621 $lastCh = $pos ? $text[$pos - 1] : '';
3622 $ret .= $ch; // add to result string
3623 if ( $ch == '<' ) {
3624 $this->truncate_endBracket( $tag, $tagType, $lastCh, $openTags ); // for bad HTML
3625 $entityState = 0; // for bad HTML
3626 $bracketState = 1; // tag started (checking for backslash)
3627 } elseif ( $ch == '>' ) {
3628 $this->truncate_endBracket( $tag, $tagType, $lastCh, $openTags );
3629 $entityState = 0; // for bad HTML
3630 $bracketState = 0; // out of brackets
3631 } elseif ( $bracketState == 1 ) {
3632 if ( $ch == '/' ) {
3633 $tagType = 1; // close tag (e.g. "</span>")
3634 } else {
3635 $tagType = 0; // open tag (e.g. "<span>")
3636 $tag .= $ch;
3637 }
3638 $bracketState = 2; // building tag name
3639 } elseif ( $bracketState == 2 ) {
3640 if ( $ch != ' ' ) {
3641 $tag .= $ch;
3642 } else {
3643 // Name found (e.g. "<a href=..."), add on tag attributes...
3644 $pos += $this->truncate_skip( $ret, $text, "<>", $pos + 1 );
3645 }
3646 } elseif ( $bracketState == 0 ) {
3647 if ( $entityState ) {
3648 if ( $ch == ';' ) {
3649 $entityState = 0;
3650 $dispLen++; // entity is one displayed char
3651 }
3652 } else {
3653 if ( $neLength == 0 && !$maybeState ) {
3654 // Save state without $ch. We want to *hit* the first
3655 // display char (to get tags) but not *use* it if truncating.
3656 $maybeState = [ substr( $ret, 0, -1 ), $openTags ];
3657 }
3658 if ( $ch == '&' ) {
3659 $entityState = 1; // entity found, (e.g. "&#160;")
3660 } else {
3661 $dispLen++; // this char is displayed
3662 // Add the next $max display text chars after this in one swoop...
3663 $max = ( $testingEllipsis ? $length : $neLength ) - $dispLen;
3664 $skipped = $this->truncate_skip( $ret, $text, "<>&", $pos + 1, $max );
3665 $dispLen += $skipped;
3666 $pos += $skipped;
3667 }
3668 }
3669 }
3670 }
3671 // Close the last tag if left unclosed by bad HTML
3672 $this->truncate_endBracket( $tag, $text[$textLen - 1], $tagType, $openTags );
3673 while ( count( $openTags ) > 0 ) {
3674 $ret .= '</' . array_pop( $openTags ) . '>'; // close open tags
3675 }
3676 return $ret;
3677 }
3678
3690 private function truncate_skip( &$ret, $text, $search, $start, $len = null ) {
3691 if ( $len === null ) {
3692 $len = -1; // -1 means "no limit" for strcspn
3693 } elseif ( $len < 0 ) {
3694 $len = 0; // sanity
3695 }
3696 $skipCount = 0;
3697 if ( $start < strlen( $text ) ) {
3698 $skipCount = strcspn( $text, $search, $start, $len );
3699 $ret .= substr( $text, $start, $skipCount );
3700 }
3701 return $skipCount;
3702 }
3703
3713 private function truncate_endBracket( &$tag, $tagType, $lastCh, &$openTags ) {
3714 $tag = ltrim( $tag );
3715 if ( $tag != '' ) {
3716 if ( $tagType == 0 && $lastCh != '/' ) {
3717 $openTags[] = $tag; // tag opened (didn't close itself)
3718 } elseif ( $tagType == 1 ) {
3719 if ( $openTags && $tag == $openTags[count( $openTags ) - 1] ) {
3720 array_pop( $openTags ); // tag closed
3721 }
3722 }
3723 $tag = '';
3724 }
3725 }
3726
3735 function convertGrammar( $word, $case ) {
3737 if ( isset( $wgGrammarForms[$this->getCode()][$case][$word] ) ) {
3738 return $wgGrammarForms[$this->getCode()][$case][$word];
3739 }
3740
3742
3743 if ( isset( $grammarTransformations[$case] ) ) {
3744 $forms = $grammarTransformations[$case];
3745
3746 // Some names of grammar rules are aliases for other rules.
3747 // In such cases the value is a string rather than object,
3748 // so load the actual rules.
3749 if ( is_string( $forms ) ) {
3750 $forms = $grammarTransformations[$forms];
3751 }
3752
3753 foreach ( array_values( $forms ) as $rule ) {
3754 $form = $rule[0];
3755
3756 if ( $form === '@metadata' ) {
3757 continue;
3758 }
3759
3760 $replacement = $rule[1];
3761
3762 $regex = '/' . addcslashes( $form, '/' ) . '/u';
3763 $patternMatches = preg_match( $regex, $word );
3764
3765 if ( $patternMatches === false ) {
3767 'An error occurred while processing grammar. ' .
3768 "Word: '$word'. Regex: /$form/."
3769 );
3770 } elseif ( $patternMatches === 1 ) {
3771 $word = preg_replace( $regex, $replacement, $word );
3772
3773 break;
3774 }
3775 }
3776 }
3777
3778 return $word;
3779 }
3780
3786 function getGrammarForms() {
3788 if ( isset( $wgGrammarForms[$this->getCode()] )
3789 && is_array( $wgGrammarForms[$this->getCode()] )
3790 ) {
3791 return $wgGrammarForms[$this->getCode()];
3792 }
3793
3794 return [];
3795 }
3796
3806 public function getGrammarTransformations() {
3807 $languageCode = $this->getCode();
3808
3809 if ( self::$grammarTransformations === null ) {
3810 self::$grammarTransformations = new MapCacheLRU( 10 );
3811 }
3812
3813 if ( self::$grammarTransformations->has( $languageCode ) ) {
3814 return self::$grammarTransformations->get( $languageCode );
3815 }
3816
3817 $data = [];
3818
3819 $grammarDataFile = __DIR__ . "/data/grammarTransformations/$languageCode.json";
3820 if ( is_readable( $grammarDataFile ) ) {
3821 $data = FormatJson::decode(
3822 file_get_contents( $grammarDataFile ),
3823 true
3824 );
3825
3826 if ( $data === null ) {
3827 throw new MWException( "Invalid grammar data for \"$languageCode\"." );
3828 }
3829
3830 self::$grammarTransformations->set( $languageCode, $data );
3831 }
3832
3833 return $data;
3834 }
3835
3855 function gender( $gender, $forms ) {
3856 if ( !count( $forms ) ) {
3857 return '';
3858 }
3859 $forms = $this->preConvertPlural( $forms, 2 );
3860 if ( $gender === 'male' ) {
3861 return $forms[0];
3862 }
3863 if ( $gender === 'female' ) {
3864 return $forms[1];
3865 }
3866 return isset( $forms[2] ) ? $forms[2] : $forms[0];
3867 }
3868
3884 function convertPlural( $count, $forms ) {
3885 // Handle explicit n=pluralform cases
3886 $forms = $this->handleExplicitPluralForms( $count, $forms );
3887 if ( is_string( $forms ) ) {
3888 return $forms;
3889 }
3890 if ( !count( $forms ) ) {
3891 return '';
3892 }
3893
3894 $pluralForm = $this->getPluralRuleIndexNumber( $count );
3895 $pluralForm = min( $pluralForm, count( $forms ) - 1 );
3896 return $forms[$pluralForm];
3897 }
3898
3914 protected function handleExplicitPluralForms( $count, array $forms ) {
3915 foreach ( $forms as $index => $form ) {
3916 if ( preg_match( '/\d+=/i', $form ) ) {
3917 $pos = strpos( $form, '=' );
3918 if ( substr( $form, 0, $pos ) === (string)$count ) {
3919 return substr( $form, $pos + 1 );
3920 }
3921 unset( $forms[$index] );
3922 }
3923 }
3924 return array_values( $forms );
3925 }
3926
3935 protected function preConvertPlural( /* Array */ $forms, $count ) {
3936 while ( count( $forms ) < $count ) {
3937 $forms[] = $forms[count( $forms ) - 1];
3938 }
3939 return $forms;
3940 }
3941
3958 public function embedBidi( $text = '' ) {
3959 $dir = self::strongDirFromContent( $text );
3960 if ( $dir === 'ltr' ) {
3961 // Wrap in LEFT-TO-RIGHT EMBEDDING ... POP DIRECTIONAL FORMATTING
3962 return self::$lre . $text . self::$pdf;
3963 }
3964 if ( $dir === 'rtl' ) {
3965 // Wrap in RIGHT-TO-LEFT EMBEDDING ... POP DIRECTIONAL FORMATTING
3966 return self::$rle . $text . self::$pdf;
3967 }
3968 // No strong directionality: do not wrap
3969 return $text;
3970 }
3971
3985 function translateBlockExpiry( $str, User $user = null, $now = 0 ) {
3986 $duration = SpecialBlock::getSuggestedDurations( $this );
3987 foreach ( $duration as $show => $value ) {
3988 if ( strcmp( $str, $value ) == 0 ) {
3989 return htmlspecialchars( trim( $show ) );
3990 }
3991 }
3992
3993 if ( wfIsInfinity( $str ) ) {
3994 foreach ( $duration as $show => $value ) {
3995 if ( wfIsInfinity( $value ) ) {
3996 return htmlspecialchars( trim( $show ) );
3997 }
3998 }
3999 }
4000
4001 // If all else fails, return a standard duration or timestamp description.
4002 $time = strtotime( $str, $now );
4003 if ( $time === false ) { // Unknown format. Return it as-is in case.
4004 return $str;
4005 } elseif ( $time !== strtotime( $str, $now + 1 ) ) { // It's a relative timestamp.
4006 // The result differs based on current time, so the difference
4007 // is a fixed duration length.
4008 return $this->formatDuration( $time - $now );
4009 } else { // It's an absolute timestamp.
4010 if ( $time === 0 ) {
4011 // wfTimestamp() handles 0 as current time instead of epoch.
4012 $time = '19700101000000';
4013 }
4014 if ( $user ) {
4015 return $this->userTimeAndDate( $time, $user );
4016 }
4017 return $this->timeanddate( $time );
4018 }
4019 }
4020
4028 public function segmentForDiff( $text ) {
4029 return $text;
4030 }
4031
4038 public function unsegmentForDiff( $text ) {
4039 return $text;
4040 }
4041
4048 public function getConverter() {
4049 return $this->mConverter;
4050 }
4051
4058 public function autoConvertToAllVariants( $text ) {
4059 return $this->mConverter->autoConvertToAllVariants( $text );
4060 }
4061
4068 public function convert( $text ) {
4069 return $this->mConverter->convert( $text );
4070 }
4071
4078 public function convertTitle( $title ) {
4079 return $this->mConverter->convertTitle( $title );
4080 }
4081
4088 public function convertNamespace( $ns ) {
4089 return $this->mConverter->convertNamespace( $ns );
4090 }
4091
4097 public function hasVariants() {
4098 return count( $this->getVariants() ) > 1;
4099 }
4100
4108 public function hasVariant( $variant ) {
4109 return (bool)$this->mConverter->validateVariant( $variant );
4110 }
4111
4119 public function convertHtml( $text, $isTitle = false ) {
4120 return htmlspecialchars( $this->convert( $text, $isTitle ) );
4121 }
4122
4127 public function convertCategoryKey( $key ) {
4128 return $this->mConverter->convertCategoryKey( $key );
4129 }
4130
4137 public function getVariants() {
4138 return $this->mConverter->getVariants();
4139 }
4140
4144 public function getPreferredVariant() {
4145 return $this->mConverter->getPreferredVariant();
4146 }
4147
4151 public function getDefaultVariant() {
4152 return $this->mConverter->getDefaultVariant();
4153 }
4154
4158 public function getURLVariant() {
4159 return $this->mConverter->getURLVariant();
4160 }
4161
4174 public function findVariantLink( &$link, &$nt, $ignoreOtherCond = false ) {
4175 $this->mConverter->findVariantLink( $link, $nt, $ignoreOtherCond );
4176 }
4177
4185 return $this->mConverter->getExtraHashOptions();
4186 }
4187
4195 public function getParsedTitle() {
4196 return $this->mConverter->getParsedTitle();
4197 }
4198
4205 public function updateConversionTable( Title $title ) {
4206 $this->mConverter->updateConversionTable( $title );
4207 }
4208
4221 public function markNoConversion( $text, $noParse = false ) {
4222 // Excluding protocal-relative URLs may avoid many false positives.
4223 if ( $noParse || preg_match( '/^(?:' . wfUrlProtocolsWithoutProtRel() . ')/', $text ) ) {
4224 return $this->mConverter->markNoConversion( $text );
4225 } else {
4226 return $text;
4227 }
4228 }
4229
4236 public function linkTrail() {
4237 return self::$dataCache->getItem( $this->mCode, 'linkTrail' );
4238 }
4239
4246 public function linkPrefixCharset() {
4247 return self::$dataCache->getItem( $this->mCode, 'linkPrefixCharset' );
4248 }
4249
4257 public function getParentLanguage() {
4258 if ( $this->mParentLanguage !== false ) {
4259 return $this->mParentLanguage;
4260 }
4261
4262 $code = explode( '-', $this->getCode() )[0];
4264 $this->mParentLanguage = null;
4265 return null;
4266 }
4267 $lang = self::factory( $code );
4268 if ( !$lang->hasVariant( $this->getCode() ) ) {
4269 $this->mParentLanguage = null;
4270 return null;
4271 }
4272
4273 $this->mParentLanguage = $lang;
4274 return $lang;
4275 }
4276
4284 public function equals( Language $lang ) {
4285 return $lang->getCode() === $this->mCode;
4286 }
4287
4296 public function getCode() {
4297 return $this->mCode;
4298 }
4299
4310 public function getHtmlCode() {
4311 if ( is_null( $this->mHtmlCode ) ) {
4312 $this->mHtmlCode = wfBCP47( $this->getCode() );
4313 }
4314 return $this->mHtmlCode;
4315 }
4316
4320 public function setCode( $code ) {
4321 $this->mCode = $code;
4322 // Ensure we don't leave incorrect cached data lying around
4323 $this->mHtmlCode = null;
4324 $this->mParentLanguage = false;
4325 }
4326
4334 public static function getCodeFromFileName( $filename, $prefix = 'Language', $suffix = '.php' ) {
4335 $m = null;
4336 preg_match( '/' . preg_quote( $prefix, '/' ) . '([A-Z][a-z_]+)' .
4337 preg_quote( $suffix, '/' ) . '/', $filename, $m );
4338 if ( !count( $m ) ) {
4339 return false;
4340 }
4341 return str_replace( '_', '-', strtolower( $m[1] ) );
4342 }
4343
4349 public static function classFromCode( $code, $fallback = true ) {
4350 if ( $fallback && $code == 'en' ) {
4351 return 'Language';
4352 } else {
4353 return 'Language' . str_replace( '-', '_', ucfirst( $code ) );
4354 }
4355 }
4356
4365 public static function getFileName( $prefix = 'Language', $code, $suffix = '.php' ) {
4366 if ( !self::isValidBuiltInCode( $code ) ) {
4367 throw new MWException( "Invalid language code \"$code\"" );
4368 }
4369
4370 return $prefix . str_replace( '-', '_', ucfirst( $code ) ) . $suffix;
4371 }
4372
4377 public static function getMessagesFileName( $code ) {
4378 global $IP;
4379 $file = self::getFileName( "$IP/languages/messages/Messages", $code, '.php' );
4380 Hooks::run( 'Language::getMessagesFileName', [ $code, &$file ] );
4381 return $file;
4382 }
4383
4390 public static function getJsonMessagesFileName( $code ) {
4391 global $IP;
4392
4393 if ( !self::isValidBuiltInCode( $code ) ) {
4394 throw new MWException( "Invalid language code \"$code\"" );
4395 }
4396
4397 return "$IP/languages/i18n/$code.json";
4398 }
4399
4407 public static function getFallbackFor( $code ) {
4408 $fallbacks = self::getFallbacksFor( $code );
4409 if ( $fallbacks ) {
4410 return $fallbacks[0];
4411 }
4412 return false;
4413 }
4414
4422 public static function getFallbacksFor( $code ) {
4423 if ( $code === 'en' || !self::isValidBuiltInCode( $code ) ) {
4424 return [];
4425 }
4426 // For unknown languages, fallbackSequence returns an empty array,
4427 // hardcode fallback to 'en' in that case.
4428 return self::getLocalisationCache()->getItem( $code, 'fallbackSequence' ) ?: [ 'en' ];
4429 }
4430
4439 public static function getFallbacksIncludingSiteLanguage( $code ) {
4441
4442 // Usually, we will only store a tiny number of fallback chains, so we
4443 // keep them in static memory.
4444 $cacheKey = "{$code}-{$wgLanguageCode}";
4445
4446 if ( !array_key_exists( $cacheKey, self::$fallbackLanguageCache ) ) {
4447 $fallbacks = self::getFallbacksFor( $code );
4448
4449 // Append the site's fallback chain, including the site language itself
4450 $siteFallbacks = self::getFallbacksFor( $wgLanguageCode );
4451 array_unshift( $siteFallbacks, $wgLanguageCode );
4452
4453 // Eliminate any languages already included in the chain
4454 $siteFallbacks = array_diff( $siteFallbacks, $fallbacks );
4455
4456 self::$fallbackLanguageCache[$cacheKey] = [ $fallbacks, $siteFallbacks ];
4457 }
4458 return self::$fallbackLanguageCache[$cacheKey];
4459 }
4460
4470 public static function getMessagesFor( $code ) {
4471 return self::getLocalisationCache()->getItem( $code, 'messages' );
4472 }
4473
4482 public static function getMessageFor( $key, $code ) {
4483 return self::getLocalisationCache()->getSubitem( $code, 'messages', $key );
4484 }
4485
4494 public static function getMessageKeysFor( $code ) {
4495 return self::getLocalisationCache()->getSubitemList( $code, 'messages' );
4496 }
4497
4502 function fixVariableInNamespace( $talk ) {
4503 if ( strpos( $talk, '$1' ) === false ) {
4504 return $talk;
4505 }
4506
4508 $talk = str_replace( '$1', $wgMetaNamespace, $talk );
4509
4510 # Allow grammar transformations
4511 # Allowing full message-style parsing would make simple requests
4512 # such as action=raw much more expensive than they need to be.
4513 # This will hopefully cover most cases.
4514 $talk = preg_replace_callback( '/{{grammar:(.*?)\|(.*?)}}/i',
4515 [ $this, 'replaceGrammarInNamespace' ], $talk );
4516 return str_replace( ' ', '_', $talk );
4517 }
4518
4524 return $this->convertGrammar( trim( $m[2] ), trim( $m[1] ) );
4525 }
4526
4537 public function formatExpiry( $expiry, $format = true, $infinity = 'infinity' ) {
4538 static $dbInfinity;
4539 if ( $dbInfinity === null ) {
4540 $dbInfinity = wfGetDB( DB_REPLICA )->getInfinity();
4541 }
4542
4543 if ( $expiry == '' || $expiry === 'infinity' || $expiry == $dbInfinity ) {
4544 return $format === true
4545 ? $this->getMessageFromDB( 'infiniteblock' )
4546 : $infinity;
4547 } else {
4548 return $format === true
4549 ? $this->timeanddate( $expiry, /* User preference timezone */ true )
4550 : wfTimestamp( $format, $expiry );
4551 }
4552 }
4553
4567 function formatTimePeriod( $seconds, $format = [] ) {
4568 if ( !is_array( $format ) ) {
4569 $format = [ 'avoid' => $format ]; // For backwards compatibility
4570 }
4571 if ( !isset( $format['avoid'] ) ) {
4572 $format['avoid'] = false;
4573 }
4574 if ( !isset( $format['noabbrevs'] ) ) {
4575 $format['noabbrevs'] = false;
4576 }
4577 $secondsMsg = wfMessage(
4578 $format['noabbrevs'] ? 'seconds' : 'seconds-abbrev' )->inLanguage( $this );
4579 $minutesMsg = wfMessage(
4580 $format['noabbrevs'] ? 'minutes' : 'minutes-abbrev' )->inLanguage( $this );
4581 $hoursMsg = wfMessage(
4582 $format['noabbrevs'] ? 'hours' : 'hours-abbrev' )->inLanguage( $this );
4583 $daysMsg = wfMessage(
4584 $format['noabbrevs'] ? 'days' : 'days-abbrev' )->inLanguage( $this );
4585
4586 if ( round( $seconds * 10 ) < 100 ) {
4587 $s = $this->formatNum( sprintf( "%.1f", round( $seconds * 10 ) / 10 ) );
4588 $s = $secondsMsg->params( $s )->text();
4589 } elseif ( round( $seconds ) < 60 ) {
4590 $s = $this->formatNum( round( $seconds ) );
4591 $s = $secondsMsg->params( $s )->text();
4592 } elseif ( round( $seconds ) < 3600 ) {
4593 $minutes = floor( $seconds / 60 );
4594 $secondsPart = round( fmod( $seconds, 60 ) );
4595 if ( $secondsPart == 60 ) {
4596 $secondsPart = 0;
4597 $minutes++;
4598 }
4599 $s = $minutesMsg->params( $this->formatNum( $minutes ) )->text();
4600 $s .= ' ';
4601 $s .= $secondsMsg->params( $this->formatNum( $secondsPart ) )->text();
4602 } elseif ( round( $seconds ) <= 2 * 86400 ) {
4603 $hours = floor( $seconds / 3600 );
4604 $minutes = floor( ( $seconds - $hours * 3600 ) / 60 );
4605 $secondsPart = round( $seconds - $hours * 3600 - $minutes * 60 );
4606 if ( $secondsPart == 60 ) {
4607 $secondsPart = 0;
4608 $minutes++;
4609 }
4610 if ( $minutes == 60 ) {
4611 $minutes = 0;
4612 $hours++;
4613 }
4614 $s = $hoursMsg->params( $this->formatNum( $hours ) )->text();
4615 $s .= ' ';
4616 $s .= $minutesMsg->params( $this->formatNum( $minutes ) )->text();
4617 if ( !in_array( $format['avoid'], [ 'avoidseconds', 'avoidminutes' ] ) ) {
4618 $s .= ' ' . $secondsMsg->params( $this->formatNum( $secondsPart ) )->text();
4619 }
4620 } else {
4621 $days = floor( $seconds / 86400 );
4622 if ( $format['avoid'] === 'avoidminutes' ) {
4623 $hours = round( ( $seconds - $days * 86400 ) / 3600 );
4624 if ( $hours == 24 ) {
4625 $hours = 0;
4626 $days++;
4627 }
4628 $s = $daysMsg->params( $this->formatNum( $days ) )->text();
4629 $s .= ' ';
4630 $s .= $hoursMsg->params( $this->formatNum( $hours ) )->text();
4631 } elseif ( $format['avoid'] === 'avoidseconds' ) {
4632 $hours = floor( ( $seconds - $days * 86400 ) / 3600 );
4633 $minutes = round( ( $seconds - $days * 86400 - $hours * 3600 ) / 60 );
4634 if ( $minutes == 60 ) {
4635 $minutes = 0;
4636 $hours++;
4637 }
4638 if ( $hours == 24 ) {
4639 $hours = 0;
4640 $days++;
4641 }
4642 $s = $daysMsg->params( $this->formatNum( $days ) )->text();
4643 $s .= ' ';
4644 $s .= $hoursMsg->params( $this->formatNum( $hours ) )->text();
4645 $s .= ' ';
4646 $s .= $minutesMsg->params( $this->formatNum( $minutes ) )->text();
4647 } else {
4648 $s = $daysMsg->params( $this->formatNum( $days ) )->text();
4649 $s .= ' ';
4650 $s .= $this->formatTimePeriod( $seconds - $days * 86400, $format );
4651 }
4652 }
4653 return $s;
4654 }
4655
4667 function formatBitrate( $bps ) {
4668 return $this->formatComputingNumbers( $bps, 1000, "bitrate-$1bits" );
4669 }
4670
4677 function formatComputingNumbers( $size, $boundary, $messageKey ) {
4678 if ( $size <= 0 ) {
4679 return str_replace( '$1', $this->formatNum( $size ),
4680 $this->getMessageFromDB( str_replace( '$1', '', $messageKey ) )
4681 );
4682 }
4683 $sizes = [ '', 'kilo', 'mega', 'giga', 'tera', 'peta', 'exa', 'zeta', 'yotta' ];
4684 $index = 0;
4685
4686 $maxIndex = count( $sizes ) - 1;
4687 while ( $size >= $boundary && $index < $maxIndex ) {
4688 $index++;
4689 $size /= $boundary;
4690 }
4691
4692 // For small sizes no decimal places necessary
4693 $round = 0;
4694 if ( $index > 1 ) {
4695 // For MB and bigger two decimal places are smarter
4696 $round = 2;
4697 }
4698 $msg = str_replace( '$1', $sizes[$index], $messageKey );
4699
4700 $size = round( $size, $round );
4701 $text = $this->getMessageFromDB( $msg );
4702 return str_replace( '$1', $this->formatNum( $size ), $text );
4703 }
4704
4715 function formatSize( $size ) {
4716 return $this->formatComputingNumbers( $size, 1024, "size-$1bytes" );
4717 }
4718
4728 function specialList( $page, $details, $oppositedm = true ) {
4729 if ( !$details ) {
4730 return $page;
4731 }
4732
4733 $dirmark = ( $oppositedm ? $this->getDirMark( true ) : '' ) . $this->getDirMark();
4734 return
4735 $page .
4736 $dirmark .
4737 $this->msg( 'word-separator' )->escaped() .
4738 $this->msg( 'parentheses' )->rawParams( $details )->escaped();
4739 }
4740
4751 public function viewPrevNext( Title $title, $offset, $limit,
4752 array $query = [], $atend = false
4753 ) {
4754 // @todo FIXME: Why on earth this needs one message for the text and another one for tooltip?
4755
4756 # Make 'previous' link
4757 $prev = wfMessage( 'prevn' )->inLanguage( $this )->title( $title )->numParams( $limit )->text();
4758 if ( $offset > 0 ) {
4759 $plink = $this->numLink( $title, max( $offset - $limit, 0 ), $limit,
4760 $query, $prev, 'prevn-title', 'mw-prevlink' );
4761 } else {
4762 $plink = htmlspecialchars( $prev );
4763 }
4764
4765 # Make 'next' link
4766 $next = wfMessage( 'nextn' )->inLanguage( $this )->title( $title )->numParams( $limit )->text();
4767 if ( $atend ) {
4768 $nlink = htmlspecialchars( $next );
4769 } else {
4770 $nlink = $this->numLink( $title, $offset + $limit, $limit,
4771 $query, $next, 'nextn-title', 'mw-nextlink' );
4772 }
4773
4774 # Make links to set number of items per page
4775 $numLinks = [];
4776 foreach ( [ 20, 50, 100, 250, 500 ] as $num ) {
4777 $numLinks[] = $this->numLink( $title, $offset, $num,
4778 $query, $this->formatNum( $num ), 'shown-title', 'mw-numlink' );
4779 }
4780
4781 return wfMessage( 'viewprevnext' )->inLanguage( $this )->title( $title
4782 )->rawParams( $plink, $nlink, $this->pipeList( $numLinks ) )->escaped();
4783 }
4784
4797 private function numLink( Title $title, $offset, $limit, array $query, $link,
4798 $tooltipMsg, $class
4799 ) {
4800 $query = [ 'limit' => $limit, 'offset' => $offset ] + $query;
4801 $tooltip = wfMessage( $tooltipMsg )->inLanguage( $this )->title( $title )
4802 ->numParams( $limit )->text();
4803
4804 return Html::element( 'a', [ 'href' => $title->getLocalURL( $query ),
4805 'title' => $tooltip, 'class' => $class ], $link );
4806 }
4807
4813 public function getConvRuleTitle() {
4814 return $this->mConverter->getConvRuleTitle();
4815 }
4816
4822 public function getCompiledPluralRules() {
4823 $pluralRules = self::$dataCache->getItem( strtolower( $this->mCode ), 'compiledPluralRules' );
4824 $fallbacks = self::getFallbacksFor( $this->mCode );
4825 if ( !$pluralRules ) {
4826 foreach ( $fallbacks as $fallbackCode ) {
4827 $pluralRules = self::$dataCache->getItem( strtolower( $fallbackCode ), 'compiledPluralRules' );
4828 if ( $pluralRules ) {
4829 break;
4830 }
4831 }
4832 }
4833 return $pluralRules;
4834 }
4835
4841 public function getPluralRules() {
4842 $pluralRules = self::$dataCache->getItem( strtolower( $this->mCode ), 'pluralRules' );
4843 $fallbacks = self::getFallbacksFor( $this->mCode );
4844 if ( !$pluralRules ) {
4845 foreach ( $fallbacks as $fallbackCode ) {
4846 $pluralRules = self::$dataCache->getItem( strtolower( $fallbackCode ), 'pluralRules' );
4847 if ( $pluralRules ) {
4848 break;
4849 }
4850 }
4851 }
4852 return $pluralRules;
4853 }
4854
4860 public function getPluralRuleTypes() {
4861 $pluralRuleTypes = self::$dataCache->getItem( strtolower( $this->mCode ), 'pluralRuleTypes' );
4862 $fallbacks = self::getFallbacksFor( $this->mCode );
4863 if ( !$pluralRuleTypes ) {
4864 foreach ( $fallbacks as $fallbackCode ) {
4865 $pluralRuleTypes = self::$dataCache->getItem( strtolower( $fallbackCode ), 'pluralRuleTypes' );
4866 if ( $pluralRuleTypes ) {
4867 break;
4868 }
4869 }
4870 }
4871 return $pluralRuleTypes;
4872 }
4873
4879 public function getPluralRuleIndexNumber( $number ) {
4880 $pluralRules = $this->getCompiledPluralRules();
4881 $form = Evaluator::evaluateCompiled( $number, $pluralRules );
4882 return $form;
4883 }
4884
4893 public function getPluralRuleType( $number ) {
4894 $index = $this->getPluralRuleIndexNumber( $number );
4895 $pluralRuleTypes = $this->getPluralRuleTypes();
4896 if ( isset( $pluralRuleTypes[$index] ) ) {
4897 return $pluralRuleTypes[$index];
4898 } else {
4899 return 'other';
4900 }
4901 }
4902}
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
$wgLanguageCode
Site language code.
$wgExtraGenderNamespaces
Same as above, but for namespaces with gender distinction.
$wgTranslateNumerals
For Hindi and Arabic use local numerals instead of Western style (0-9) numerals in interface.
$wgGrammarForms
Some languages need different word forms, usually for different cases.
$wgExtraNamespaces
Additional namespaces.
$wgAllUnicodeFixes
Set this to always convert certain Unicode sequences to modern ones regardless of the content languag...
$wgDummyLanguageCodes
Functionally the same as $wgExtraLanguageCodes, but deprecated.
$wgNamespaceAliases
Namespace aliases.
$wgMetaNamespace
Name of the project namespace.
$wgMetaNamespaceTalk
Name of the project talk namespace.
$wgLocalTZoffset
Set an offset from UTC in minutes to use for the default timezone setting for anonymous users and new...
$wgLocalisationCacheConf
Localisation cache configuration.
$wgLangObjCacheSize
Language cache size, or really how many languages can we handle simultaneously without degrading to c...
wfWarn( $msg, $callerOffset=1, $level=E_USER_NOTICE)
Send a warning either to the debug log or in a PHP error depending on $wgDevelopmentWarnings.
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.
wfLogWarning( $msg, $callerOffset=1, $level=E_USER_WARNING)
Send a warning as a PHP error and the debug log.
wfIsInfinity( $str)
Determine input string is represents as infinity.
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
$fallback
$namespaceAliases
$namespaceNames
$digitGroupingPattern
$wgUser
Definition Setup.php:817
$IP
Definition WebStart.php:57
A fake language variant 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.
initContLang()
Hook which will be called if this is the content language.
Definition Language.php:439
static $mWeekdayMsgs
Definition Language.php:64
getLocalNsIndex( $text)
Get a namespace key by value, case insensitive.
Definition Language.php:613
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:174
formatExpiry( $expiry, $format=true, $infinity='infinity')
Decode an expiry (block, protection, etc) which has come from the DB.
getPluralRuleTypes()
Get the plural rule types for the language.
static tsToIranian( $ts)
Algorithm by Roozbeh Pournader and Mohammad Toossi to convert Gregorian dates to Iranian dates.
getDefaultVariant()
convertCategoryKey( $key)
array null $namespaceNames
Definition Language.php:49
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:284
getVariantname( $code, $usemsg=true)
short names for language variants used for language conversion links.
Definition Language.php:713
static $mHebrewCalendarMonthGenMsgs
Definition Language.php:103
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:363
recodeForEdit( $s)
$mParentLanguage
Definition Language.php:43
getWeekdayName( $key)
Definition Language.php:972
getHebrewCalendarMonthName( $key)
Definition Language.php:996
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:159
digitGroupingPattern()
static array $fallbackLanguageCache
Cache for language fallbacks.
Definition Language.php:140
getMonthAbbreviation( $key)
Definition Language.php:953
getHebrewCalendarMonthNameGen( $key)
static $lre
Unicode directional formatting characters, for embedBidi()
Definition Language.php:157
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:256
hasVariant( $variant)
Check if the language has the specific variant.
setCode( $code)
static classFromCode( $code, $fallback=true)
transformUsingPairFile( $file, $string)
Transform a string using serialized data stored in the given file (which must be in the serialized su...
formatDuration( $seconds, array $chosenIntervals=[])
Takes a number of seconds and turns it into a text using values such as hours and minutes.
getMessage( $key)
getHijriCalendarMonthName( $key)
static fetchLanguageName( $code, $inLanguage=null, $include='all')
Definition Language.php:896
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:60
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:574
translateBlockExpiry( $str, User $user=null, $now=0)
truncateHtml( $text, $length, $ellipsis='...')
Truncate a string of valid HTML to a specified length in bytes, appending an optional string (e....
getAllMessages()
getGrammarTransformations()
Get the grammar transformations data for the language.
__destruct()
Reduce memory usage.
Definition Language.php:429
getFormattedNsText( $index)
A convenience function that returns the same thing as getNsText() except with '_' changed to ' ',...
Definition Language.php:561
getURLVariant()
getMessageFromDB( $msg)
Get a message from the MediaWiki namespace.
Definition Language.php:908
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:415
getFormattedNamespaces()
A convenience function that returns getNamespaces() with spaces instead of underscores in values.
Definition Language.php:524
getExtraHashOptions()
returns language specific options used by User::getPageRenderHash() for example, the preferred langua...
$transformData
ReplacementArray object caches.
Definition Language.php:55
getMonthNamesArray()
Definition Language.php:933
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:945
setNamespaces(array $namespaces)
Arbitrarily set all of the namespace names at once.
Definition Language.php:504
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:464
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:589
static array $durationIntervals
Definition Language.php:122
getNsIndex( $text)
Get a namespace key by value, case insensitive.
Definition Language.php:696
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:88
uc( $str, $first=false)
Convert a string to uppercase.
static $mHebrewCalendarMonthMsgs
Definition Language.php:95
normalizeForSearch( $string)
Some languages have special punctuation need to be normalized.
static newFromCode( $code, $fallback=false)
Create a language object for a given language code.
Definition Language.php:210
embedBidi( $text='')
Wraps argument with unicode control characters for directionality safety.
getMonthAbbreviationsArray()
Definition Language.php:960
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:512
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:78
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:152
getIranianCalendarMonthName( $key)
Definition Language.php:988
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:980
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:83
replaceGrammarInNamespace( $m)
getBookstoreList()
Exports $wgBookstoreListEn.
Definition Language.php:454
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:183
$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:446
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:926
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:158
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:406
static $mLangObjCache
Definition Language.php:62
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:385
msg( $msg)
Get message object in this language.
Definition Language.php:918
removeBadCharLast( $string)
Remove bytes that represent an incomplete Unicode character at the end of string (e....
static getJsonMessagesFileName( $code)
getNamespaceAliases()
Definition Language.php:622
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:543
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:666
static MapCacheLRU null $grammarTransformations
Cache for grammar rules data.
Definition Language.php:146
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:744
removeBadCharFirst( $string)
Remove bytes that represent an incomplete Unicode character at the start of string (e....
static $mHijriCalendarMonthMsgs
Definition Language.php:111
static $mWeekdayAbbrevMsgs
Definition Language.php:69
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:338
static $mMonthMsgs
Definition Language.php:73
$dateFormatStrings
Definition Language.php:45
static romanNumeral( $num)
Roman number formatting up to 10000.
alignStart()
Return 'left' or 'right' as appropriate alignment for line-start for this language's text direction.
static hebrewYearStart( $year)
This calculates the Hebrew year start, as days since 1 September.
getCompiledPluralRules()
Get the compiled plural rules for the language.
Class for caching the contents of localisation files, Messages*.php and *.i18n.php.
MediaWiki exception.
Library for creating and parsing MW-style timestamps.
Handles a simple LRU key/value map with a maximum number of entries.
static getTitleInvalidRegex()
Returns a simple regex that will match on characters and sequences invalid in titles.
Wrapper around strtr() that holds replacements.
static 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:39
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition User.php:51
=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:67
const NS_PROJECT_TALK
Definition Defines.php:70
const NS_USER_TALK
Definition Defines.php:68
const NS_PROJECT
Definition Defines.php:69
the array() calling protocol came about after MediaWiki 1.4rc1.
see documentation in includes Linker php for Linker::makeImageLink & $time
Definition hooks.txt:1778
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:181
namespace and then decline to actually register it & $namespaces
Definition hooks.txt:932
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped & $options
Definition hooks.txt:1971
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:863
namespace and then decline to actually register it file or subcat img or subcat $title
Definition hooks.txt:962
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:1976
if the prop value should be in the metadata multi language array format
Definition hooks.txt:1646
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:1975
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:862
usually copyright or history_copyright This message must be in HTML not wikitext & $link
Definition hooks.txt:2989
Allows to change the fields on the form that will be generated $name
Definition hooks.txt:302
null for the local wiki Added should default to null in handler for backwards compatibility add a value to it if you want to add a cookie that have to vary cache options can modify $query
Definition hooks.txt:1610
please add to it if you re going to add events to the MediaWiki code where normally authentication against an external auth plugin would be creating a local account $user
Definition hooks.txt:247
returning false will NOT prevent logging $e
Definition hooks.txt:2146
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.
const DB_REPLICA
Definition defines.php:25
if(!isset( $args[0])) $lang