MediaWiki REL1_29
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
209 protected static function newFromCode( $code ) {
210 if ( !Language::isValidCode( $code ) ) {
211 throw new MWException( "Invalid language code \"$code\"" );
212 }
213
215 // It's not possible to customise this code with class files, so
216 // just return a Language object. This is to support uselang= hacks.
217 $lang = new Language;
218 $lang->setCode( $code );
219 return $lang;
220 }
221
222 // Check if there is a language class for the code
223 $class = self::classFromCode( $code );
224 if ( class_exists( $class ) ) {
225 $lang = new $class;
226 return $lang;
227 }
228
229 // Keep trying the fallback list until we find an existing class
230 $fallbacks = Language::getFallbacksFor( $code );
231 foreach ( $fallbacks as $fallbackCode ) {
232 if ( !Language::isValidBuiltInCode( $fallbackCode ) ) {
233 throw new MWException( "Invalid fallback '$fallbackCode' in fallback sequence for '$code'" );
234 }
235
236 $class = self::classFromCode( $fallbackCode );
237 if ( class_exists( $class ) ) {
238 $lang = new $class;
239 $lang->setCode( $code );
240 return $lang;
241 }
242 }
243
244 throw new MWException( "Invalid fallback sequence for language '$code'" );
245 }
246
255 public static function isSupportedLanguage( $code ) {
256 if ( !self::isValidBuiltInCode( $code ) ) {
257 return false;
258 }
259
260 if ( $code === 'qqq' ) {
261 return false;
262 }
263
264 return is_readable( self::getMessagesFileName( $code ) ) ||
265 is_readable( self::getJsonMessagesFileName( $code ) );
266 }
267
283 public static function isWellFormedLanguageTag( $code, $lenient = false ) {
284 $alpha = '[a-z]';
285 $digit = '[0-9]';
286 $alphanum = '[a-z0-9]';
287 $x = 'x'; # private use singleton
288 $singleton = '[a-wy-z]'; # other singleton
289 $s = $lenient ? '[-_]' : '-';
290
291 $language = "$alpha{2,8}|$alpha{2,3}$s$alpha{3}";
292 $script = "$alpha{4}"; # ISO 15924
293 $region = "(?:$alpha{2}|$digit{3})"; # ISO 3166-1 alpha-2 or UN M.49
294 $variant = "(?:$alphanum{5,8}|$digit$alphanum{3})";
295 $extension = "$singleton(?:$s$alphanum{2,8})+";
296 $privateUse = "$x(?:$s$alphanum{1,8})+";
297
298 # Define certain grandfathered codes, since otherwise the regex is pretty useless.
299 # Since these are limited, this is safe even later changes to the registry --
300 # the only oddity is that it might change the type of the tag, and thus
301 # the results from the capturing groups.
302 # https://www.iana.org/assignments/language-subtag-registry
303
304 $grandfathered = "en{$s}GB{$s}oed"
305 . "|i{$s}(?:ami|bnn|default|enochian|hak|klingon|lux|mingo|navajo|pwn|tao|tay|tsu)"
306 . "|no{$s}(?:bok|nyn)"
307 . "|sgn{$s}(?:BE{$s}(?:fr|nl)|CH{$s}de)"
308 . "|zh{$s}min{$s}nan";
309
310 $variantList = "$variant(?:$s$variant)*";
311 $extensionList = "$extension(?:$s$extension)*";
312
313 $langtag = "(?:($language)"
314 . "(?:$s$script)?"
315 . "(?:$s$region)?"
316 . "(?:$s$variantList)?"
317 . "(?:$s$extensionList)?"
318 . "(?:$s$privateUse)?)";
319
320 # The final breakdown, with capturing groups for each of these components
321 # The variants, extensions, grandfathered, and private-use may have interior '-'
322
323 $root = "^(?:$langtag|$privateUse|$grandfathered)$";
324
325 return (bool)preg_match( "/$root/", strtolower( $code ) );
326 }
327
337 public static function isValidCode( $code ) {
338 static $cache = [];
339 if ( !isset( $cache[$code] ) ) {
340 // People think language codes are html safe, so enforce it.
341 // Ideally we should only allow a-zA-Z0-9-
342 // but, .+ and other chars are often used for {{int:}} hacks
343 // see bugs T39564, T39587, T38938
344 $cache[$code] =
345 // Protect against path traversal
346 strcspn( $code, ":/\\\000&<>'\"" ) === strlen( $code )
348 }
349 return $cache[$code];
350 }
351
362 public static function isValidBuiltInCode( $code ) {
363
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;
830
831 // If passed an invalid language code to use, fallback to en
832 if ( $inLanguage !== null && !Language::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 foreach ( $mwNames as $mwCode => $mwName ) {
845 # - Prefer own MediaWiki native name when not using the hook
846 # - For other names just add if not added through the hook
847 if ( $mwCode === $inLanguage || !isset( $names[$mwCode] ) ) {
848 $names[$mwCode] = $mwName;
849 }
850 }
851
852 if ( $include === 'all' ) {
853 ksort( $names );
854 return $names;
855 }
856
857 $returnMw = [];
858 $coreCodes = array_keys( $mwNames );
859 foreach ( $coreCodes as $coreCode ) {
860 $returnMw[$coreCode] = $names[$coreCode];
861 }
862
863 if ( $include === 'mwfile' ) {
864 $namesMwFile = [];
865 # We do this using a foreach over the codes instead of a directory
866 # loop so that messages files in extensions will work correctly.
867 foreach ( $returnMw as $code => $value ) {
868 if ( is_readable( self::getMessagesFileName( $code ) )
869 || is_readable( self::getJsonMessagesFileName( $code ) )
870 ) {
871 $namesMwFile[$code] = $names[$code];
872 }
873 }
874
875 ksort( $namesMwFile );
876 return $namesMwFile;
877 }
878
879 ksort( $returnMw );
880 # 'mw' option; default if it's not one of the other two options (all/mwfile)
881 return $returnMw;
882 }
883
891 public static function fetchLanguageName( $code, $inLanguage = null, $include = 'all' ) {
892 $code = strtolower( $code );
893 $array = self::fetchLanguageNames( $inLanguage, $include );
894 return !array_key_exists( $code, $array ) ? '' : $array[$code];
895 }
896
903 public function getMessageFromDB( $msg ) {
904 return $this->msg( $msg )->text();
905 }
906
913 protected function msg( $msg ) {
914 return wfMessage( $msg )->inLanguage( $this );
915 }
916
921 public function getMonthName( $key ) {
922 return $this->getMessageFromDB( self::$mMonthMsgs[$key - 1] );
923 }
924
928 public function getMonthNamesArray() {
929 $monthNames = [ '' ];
930 for ( $i = 1; $i < 13; $i++ ) {
931 $monthNames[] = $this->getMonthName( $i );
932 }
933 return $monthNames;
934 }
935
940 public function getMonthNameGen( $key ) {
941 return $this->getMessageFromDB( self::$mMonthGenMsgs[$key - 1] );
942 }
943
948 public function getMonthAbbreviation( $key ) {
949 return $this->getMessageFromDB( self::$mMonthAbbrevMsgs[$key - 1] );
950 }
951
955 public function getMonthAbbreviationsArray() {
956 $monthNames = [ '' ];
957 for ( $i = 1; $i < 13; $i++ ) {
958 $monthNames[] = $this->getMonthAbbreviation( $i );
959 }
960 return $monthNames;
961 }
962
967 public function getWeekdayName( $key ) {
968 return $this->getMessageFromDB( self::$mWeekdayMsgs[$key - 1] );
969 }
970
975 function getWeekdayAbbreviation( $key ) {
976 return $this->getMessageFromDB( self::$mWeekdayAbbrevMsgs[$key - 1] );
977 }
978
984 return $this->getMessageFromDB( self::$mIranianCalendarMonthMsgs[$key - 1] );
985 }
986
991 function getHebrewCalendarMonthName( $key ) {
992 return $this->getMessageFromDB( self::$mHebrewCalendarMonthMsgs[$key - 1] );
993 }
994
1000 return $this->getMessageFromDB( self::$mHebrewCalendarMonthGenMsgs[$key - 1] );
1001 }
1002
1007 function getHijriCalendarMonthName( $key ) {
1008 return $this->getMessageFromDB( self::$mHijriCalendarMonthMsgs[$key - 1] );
1009 }
1010
1019 private static function dateTimeObjFormat( &$dateTimeObj, $ts, $zone, $code ) {
1020 if ( !$dateTimeObj ) {
1021 $dateTimeObj = DateTime::createFromFormat(
1022 'YmdHis', $ts, $zone ?: new DateTimeZone( 'UTC' )
1023 );
1024 }
1025 return $dateTimeObj->format( $code );
1026 }
1027
1097 public function sprintfDate( $format, $ts, DateTimeZone $zone = null, &$ttl = 'unused' ) {
1098 $s = '';
1099 $raw = false;
1100 $roman = false;
1101 $hebrewNum = false;
1102 $dateTimeObj = false;
1103 $rawToggle = false;
1104 $iranian = false;
1105 $hebrew = false;
1106 $hijri = false;
1107 $thai = false;
1108 $minguo = false;
1109 $tenno = false;
1110
1111 $usedSecond = false;
1112 $usedMinute = false;
1113 $usedHour = false;
1114 $usedAMPM = false;
1115 $usedDay = false;
1116 $usedWeek = false;
1117 $usedMonth = false;
1118 $usedYear = false;
1119 $usedISOYear = false;
1120 $usedIsLeapYear = false;
1121
1122 $usedHebrewMonth = false;
1123 $usedIranianMonth = false;
1124 $usedHijriMonth = false;
1125 $usedHebrewYear = false;
1126 $usedIranianYear = false;
1127 $usedHijriYear = false;
1128 $usedTennoYear = false;
1129
1130 if ( strlen( $ts ) !== 14 ) {
1131 throw new MWException( __METHOD__ . ": The timestamp $ts should have 14 characters" );
1132 }
1133
1134 if ( !ctype_digit( $ts ) ) {
1135 throw new MWException( __METHOD__ . ": The timestamp $ts should be a number" );
1136 }
1137
1138 $formatLength = strlen( $format );
1139 for ( $p = 0; $p < $formatLength; $p++ ) {
1140 $num = false;
1141 $code = $format[$p];
1142 if ( $code == 'x' && $p < $formatLength - 1 ) {
1143 $code .= $format[++$p];
1144 }
1145
1146 if ( ( $code === 'xi'
1147 || $code === 'xj'
1148 || $code === 'xk'
1149 || $code === 'xm'
1150 || $code === 'xo'
1151 || $code === 'xt' )
1152 && $p < $formatLength - 1 ) {
1153 $code .= $format[++$p];
1154 }
1155
1156 switch ( $code ) {
1157 case 'xx':
1158 $s .= 'x';
1159 break;
1160 case 'xn':
1161 $raw = true;
1162 break;
1163 case 'xN':
1164 $rawToggle = !$rawToggle;
1165 break;
1166 case 'xr':
1167 $roman = true;
1168 break;
1169 case 'xh':
1170 $hebrewNum = true;
1171 break;
1172 case 'xg':
1173 $usedMonth = true;
1174 $s .= $this->getMonthNameGen( substr( $ts, 4, 2 ) );
1175 break;
1176 case 'xjx':
1177 $usedHebrewMonth = true;
1178 if ( !$hebrew ) {
1179 $hebrew = self::tsToHebrew( $ts );
1180 }
1181 $s .= $this->getHebrewCalendarMonthNameGen( $hebrew[1] );
1182 break;
1183 case 'd':
1184 $usedDay = true;
1185 $num = substr( $ts, 6, 2 );
1186 break;
1187 case 'D':
1188 $usedDay = true;
1189 $s .= $this->getWeekdayAbbreviation(
1190 Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'w' ) + 1
1191 );
1192 break;
1193 case 'j':
1194 $usedDay = true;
1195 $num = intval( substr( $ts, 6, 2 ) );
1196 break;
1197 case 'xij':
1198 $usedDay = true;
1199 if ( !$iranian ) {
1200 $iranian = self::tsToIranian( $ts );
1201 }
1202 $num = $iranian[2];
1203 break;
1204 case 'xmj':
1205 $usedDay = true;
1206 if ( !$hijri ) {
1207 $hijri = self::tsToHijri( $ts );
1208 }
1209 $num = $hijri[2];
1210 break;
1211 case 'xjj':
1212 $usedDay = true;
1213 if ( !$hebrew ) {
1214 $hebrew = self::tsToHebrew( $ts );
1215 }
1216 $num = $hebrew[2];
1217 break;
1218 case 'l':
1219 $usedDay = true;
1220 $s .= $this->getWeekdayName(
1221 Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'w' ) + 1
1222 );
1223 break;
1224 case 'F':
1225 $usedMonth = true;
1226 $s .= $this->getMonthName( substr( $ts, 4, 2 ) );
1227 break;
1228 case 'xiF':
1229 $usedIranianMonth = true;
1230 if ( !$iranian ) {
1231 $iranian = self::tsToIranian( $ts );
1232 }
1233 $s .= $this->getIranianCalendarMonthName( $iranian[1] );
1234 break;
1235 case 'xmF':
1236 $usedHijriMonth = true;
1237 if ( !$hijri ) {
1238 $hijri = self::tsToHijri( $ts );
1239 }
1240 $s .= $this->getHijriCalendarMonthName( $hijri[1] );
1241 break;
1242 case 'xjF':
1243 $usedHebrewMonth = true;
1244 if ( !$hebrew ) {
1245 $hebrew = self::tsToHebrew( $ts );
1246 }
1247 $s .= $this->getHebrewCalendarMonthName( $hebrew[1] );
1248 break;
1249 case 'm':
1250 $usedMonth = true;
1251 $num = substr( $ts, 4, 2 );
1252 break;
1253 case 'M':
1254 $usedMonth = true;
1255 $s .= $this->getMonthAbbreviation( substr( $ts, 4, 2 ) );
1256 break;
1257 case 'n':
1258 $usedMonth = true;
1259 $num = intval( substr( $ts, 4, 2 ) );
1260 break;
1261 case 'xin':
1262 $usedIranianMonth = true;
1263 if ( !$iranian ) {
1264 $iranian = self::tsToIranian( $ts );
1265 }
1266 $num = $iranian[1];
1267 break;
1268 case 'xmn':
1269 $usedHijriMonth = true;
1270 if ( !$hijri ) {
1271 $hijri = self::tsToHijri( $ts );
1272 }
1273 $num = $hijri[1];
1274 break;
1275 case 'xjn':
1276 $usedHebrewMonth = true;
1277 if ( !$hebrew ) {
1278 $hebrew = self::tsToHebrew( $ts );
1279 }
1280 $num = $hebrew[1];
1281 break;
1282 case 'xjt':
1283 $usedHebrewMonth = true;
1284 if ( !$hebrew ) {
1285 $hebrew = self::tsToHebrew( $ts );
1286 }
1287 $num = $hebrew[3];
1288 break;
1289 case 'Y':
1290 $usedYear = true;
1291 $num = substr( $ts, 0, 4 );
1292 break;
1293 case 'xiY':
1294 $usedIranianYear = true;
1295 if ( !$iranian ) {
1296 $iranian = self::tsToIranian( $ts );
1297 }
1298 $num = $iranian[0];
1299 break;
1300 case 'xmY':
1301 $usedHijriYear = true;
1302 if ( !$hijri ) {
1303 $hijri = self::tsToHijri( $ts );
1304 }
1305 $num = $hijri[0];
1306 break;
1307 case 'xjY':
1308 $usedHebrewYear = true;
1309 if ( !$hebrew ) {
1310 $hebrew = self::tsToHebrew( $ts );
1311 }
1312 $num = $hebrew[0];
1313 break;
1314 case 'xkY':
1315 $usedYear = true;
1316 if ( !$thai ) {
1317 $thai = self::tsToYear( $ts, 'thai' );
1318 }
1319 $num = $thai[0];
1320 break;
1321 case 'xoY':
1322 $usedYear = true;
1323 if ( !$minguo ) {
1324 $minguo = self::tsToYear( $ts, 'minguo' );
1325 }
1326 $num = $minguo[0];
1327 break;
1328 case 'xtY':
1329 $usedTennoYear = true;
1330 if ( !$tenno ) {
1331 $tenno = self::tsToYear( $ts, 'tenno' );
1332 }
1333 $num = $tenno[0];
1334 break;
1335 case 'y':
1336 $usedYear = true;
1337 $num = substr( $ts, 2, 2 );
1338 break;
1339 case 'xiy':
1340 $usedIranianYear = true;
1341 if ( !$iranian ) {
1342 $iranian = self::tsToIranian( $ts );
1343 }
1344 $num = substr( $iranian[0], -2 );
1345 break;
1346 case 'xit':
1347 $usedIranianYear = true;
1348 if ( !$iranian ) {
1349 $iranian = self::tsToIranian( $ts );
1350 }
1351 $num = self::$IRANIAN_DAYS[$iranian[1] - 1];
1352 break;
1353 case 'xiz':
1354 $usedIranianYear = true;
1355 if ( !$iranian ) {
1356 $iranian = self::tsToIranian( $ts );
1357 }
1358 $num = $iranian[3];
1359 break;
1360 case 'a':
1361 $usedAMPM = true;
1362 $s .= intval( substr( $ts, 8, 2 ) ) < 12 ? 'am' : 'pm';
1363 break;
1364 case 'A':
1365 $usedAMPM = true;
1366 $s .= intval( substr( $ts, 8, 2 ) ) < 12 ? 'AM' : 'PM';
1367 break;
1368 case 'g':
1369 $usedHour = true;
1370 $h = substr( $ts, 8, 2 );
1371 $num = $h % 12 ? $h % 12 : 12;
1372 break;
1373 case 'G':
1374 $usedHour = true;
1375 $num = intval( substr( $ts, 8, 2 ) );
1376 break;
1377 case 'h':
1378 $usedHour = true;
1379 $h = substr( $ts, 8, 2 );
1380 $num = sprintf( '%02d', $h % 12 ? $h % 12 : 12 );
1381 break;
1382 case 'H':
1383 $usedHour = true;
1384 $num = substr( $ts, 8, 2 );
1385 break;
1386 case 'i':
1387 $usedMinute = true;
1388 $num = substr( $ts, 10, 2 );
1389 break;
1390 case 's':
1391 $usedSecond = true;
1392 $num = substr( $ts, 12, 2 );
1393 break;
1394 case 'c':
1395 case 'r':
1396 $usedSecond = true;
1397 // fall through
1398 case 'e':
1399 case 'O':
1400 case 'P':
1401 case 'T':
1402 $s .= Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1403 break;
1404 case 'w':
1405 case 'N':
1406 case 'z':
1407 $usedDay = true;
1408 $num = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1409 break;
1410 case 'W':
1411 $usedWeek = true;
1412 $num = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1413 break;
1414 case 't':
1415 $usedMonth = true;
1416 $num = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1417 break;
1418 case 'L':
1419 $usedIsLeapYear = true;
1420 $num = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1421 break;
1422 case 'o':
1423 $usedISOYear = true;
1424 $num = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1425 break;
1426 case 'U':
1427 $usedSecond = true;
1428 // fall through
1429 case 'I':
1430 case 'Z':
1431 $num = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1432 break;
1433 case '\\':
1434 # Backslash escaping
1435 if ( $p < $formatLength - 1 ) {
1436 $s .= $format[++$p];
1437 } else {
1438 $s .= '\\';
1439 }
1440 break;
1441 case '"':
1442 # Quoted literal
1443 if ( $p < $formatLength - 1 ) {
1444 $endQuote = strpos( $format, '"', $p + 1 );
1445 if ( $endQuote === false ) {
1446 # No terminating quote, assume literal "
1447 $s .= '"';
1448 } else {
1449 $s .= substr( $format, $p + 1, $endQuote - $p - 1 );
1450 $p = $endQuote;
1451 }
1452 } else {
1453 # Quote at end of string, assume literal "
1454 $s .= '"';
1455 }
1456 break;
1457 default:
1458 $s .= $format[$p];
1459 }
1460 if ( $num !== false ) {
1461 if ( $rawToggle || $raw ) {
1462 $s .= $num;
1463 $raw = false;
1464 } elseif ( $roman ) {
1465 $s .= Language::romanNumeral( $num );
1466 $roman = false;
1467 } elseif ( $hebrewNum ) {
1468 $s .= self::hebrewNumeral( $num );
1469 $hebrewNum = false;
1470 } else {
1471 $s .= $this->formatNum( $num, true );
1472 }
1473 }
1474 }
1475
1476 if ( $ttl === 'unused' ) {
1477 // No need to calculate the TTL, the caller wont use it anyway.
1478 } elseif ( $usedSecond ) {
1479 $ttl = 1;
1480 } elseif ( $usedMinute ) {
1481 $ttl = 60 - substr( $ts, 12, 2 );
1482 } elseif ( $usedHour ) {
1483 $ttl = 3600 - substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
1484 } elseif ( $usedAMPM ) {
1485 $ttl = 43200 - ( substr( $ts, 8, 2 ) % 12 ) * 3600 -
1486 substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
1487 } elseif (
1488 $usedDay ||
1489 $usedHebrewMonth ||
1490 $usedIranianMonth ||
1491 $usedHijriMonth ||
1492 $usedHebrewYear ||
1493 $usedIranianYear ||
1494 $usedHijriYear ||
1495 $usedTennoYear
1496 ) {
1497 // @todo Someone who understands the non-Gregorian calendars
1498 // should write proper logic for them so that they don't need purged every day.
1499 $ttl = 86400 - substr( $ts, 8, 2 ) * 3600 -
1500 substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
1501 } else {
1502 $possibleTtls = [];
1503 $timeRemainingInDay = 86400 - substr( $ts, 8, 2 ) * 3600 -
1504 substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
1505 if ( $usedWeek ) {
1506 $possibleTtls[] =
1507 ( 7 - Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'N' ) ) * 86400 +
1508 $timeRemainingInDay;
1509 } elseif ( $usedISOYear ) {
1510 // December 28th falls on the last ISO week of the year, every year.
1511 // The last ISO week of a year can be 52 or 53.
1512 $lastWeekOfISOYear = DateTime::createFromFormat(
1513 'Ymd',
1514 substr( $ts, 0, 4 ) . '1228',
1515 $zone ?: new DateTimeZone( 'UTC' )
1516 )->format( 'W' );
1517 $currentISOWeek = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'W' );
1518 $weeksRemaining = $lastWeekOfISOYear - $currentISOWeek;
1519 $timeRemainingInWeek =
1520 ( 7 - Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'N' ) ) * 86400
1521 + $timeRemainingInDay;
1522 $possibleTtls[] = $weeksRemaining * 604800 + $timeRemainingInWeek;
1523 }
1524
1525 if ( $usedMonth ) {
1526 $possibleTtls[] =
1527 ( Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 't' ) -
1528 substr( $ts, 6, 2 ) ) * 86400
1529 + $timeRemainingInDay;
1530 } elseif ( $usedYear ) {
1531 $possibleTtls[] =
1532 ( Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'L' ) + 364 -
1533 Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'z' ) ) * 86400
1534 + $timeRemainingInDay;
1535 } elseif ( $usedIsLeapYear ) {
1536 $year = substr( $ts, 0, 4 );
1537 $timeRemainingInYear =
1538 ( Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'L' ) + 364 -
1539 Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'z' ) ) * 86400
1540 + $timeRemainingInDay;
1541 $mod = $year % 4;
1542 if ( $mod || ( !( $year % 100 ) && $year % 400 ) ) {
1543 // this isn't a leap year. see when the next one starts
1544 $nextCandidate = $year - $mod + 4;
1545 if ( $nextCandidate % 100 || !( $nextCandidate % 400 ) ) {
1546 $possibleTtls[] = ( $nextCandidate - $year - 1 ) * 365 * 86400 +
1547 $timeRemainingInYear;
1548 } else {
1549 $possibleTtls[] = ( $nextCandidate - $year + 3 ) * 365 * 86400 +
1550 $timeRemainingInYear;
1551 }
1552 } else {
1553 // this is a leap year, so the next year isn't
1554 $possibleTtls[] = $timeRemainingInYear;
1555 }
1556 }
1557
1558 if ( $possibleTtls ) {
1559 $ttl = min( $possibleTtls );
1560 }
1561 }
1562
1563 return $s;
1564 }
1565
1566 private static $GREG_DAYS = [ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ];
1567 private static $IRANIAN_DAYS = [ 31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29 ];
1568
1581 private static function tsToIranian( $ts ) {
1582 $gy = substr( $ts, 0, 4 ) -1600;
1583 $gm = substr( $ts, 4, 2 ) -1;
1584 $gd = substr( $ts, 6, 2 ) -1;
1585
1586 # Days passed from the beginning (including leap years)
1587 $gDayNo = 365 * $gy
1588 + floor( ( $gy + 3 ) / 4 )
1589 - floor( ( $gy + 99 ) / 100 )
1590 + floor( ( $gy + 399 ) / 400 );
1591
1592 // Add days of the past months of this year
1593 for ( $i = 0; $i < $gm; $i++ ) {
1594 $gDayNo += self::$GREG_DAYS[$i];
1595 }
1596
1597 // Leap years
1598 if ( $gm > 1 && ( ( $gy % 4 === 0 && $gy % 100 !== 0 || ( $gy % 400 == 0 ) ) ) ) {
1599 $gDayNo++;
1600 }
1601
1602 // Days passed in current month
1603 $gDayNo += (int)$gd;
1604
1605 $jDayNo = $gDayNo - 79;
1606
1607 $jNp = floor( $jDayNo / 12053 );
1608 $jDayNo %= 12053;
1609
1610 $jy = 979 + 33 * $jNp + 4 * floor( $jDayNo / 1461 );
1611 $jDayNo %= 1461;
1612
1613 if ( $jDayNo >= 366 ) {
1614 $jy += floor( ( $jDayNo - 1 ) / 365 );
1615 $jDayNo = floor( ( $jDayNo - 1 ) % 365 );
1616 }
1617
1618 $jz = $jDayNo;
1619
1620 for ( $i = 0; $i < 11 && $jDayNo >= self::$IRANIAN_DAYS[$i]; $i++ ) {
1621 $jDayNo -= self::$IRANIAN_DAYS[$i];
1622 }
1623
1624 $jm = $i + 1;
1625 $jd = $jDayNo + 1;
1626
1627 return [ $jy, $jm, $jd, $jz ];
1628 }
1629
1641 private static function tsToHijri( $ts ) {
1642 $year = substr( $ts, 0, 4 );
1643 $month = substr( $ts, 4, 2 );
1644 $day = substr( $ts, 6, 2 );
1645
1646 $zyr = $year;
1647 $zd = $day;
1648 $zm = $month;
1649 $zy = $zyr;
1650
1651 if (
1652 ( $zy > 1582 ) || ( ( $zy == 1582 ) && ( $zm > 10 ) ) ||
1653 ( ( $zy == 1582 ) && ( $zm == 10 ) && ( $zd > 14 ) )
1654 ) {
1655 $zjd = (int)( ( 1461 * ( $zy + 4800 + (int)( ( $zm - 14 ) / 12 ) ) ) / 4 ) +
1656 (int)( ( 367 * ( $zm - 2 - 12 * ( (int)( ( $zm - 14 ) / 12 ) ) ) ) / 12 ) -
1657 (int)( ( 3 * (int)( ( ( $zy + 4900 + (int)( ( $zm - 14 ) / 12 ) ) / 100 ) ) ) / 4 ) +
1658 $zd - 32075;
1659 } else {
1660 $zjd = 367 * $zy - (int)( ( 7 * ( $zy + 5001 + (int)( ( $zm - 9 ) / 7 ) ) ) / 4 ) +
1661 (int)( ( 275 * $zm ) / 9 ) + $zd + 1729777;
1662 }
1663
1664 $zl = $zjd -1948440 + 10632;
1665 $zn = (int)( ( $zl - 1 ) / 10631 );
1666 $zl = $zl - 10631 * $zn + 354;
1667 $zj = ( (int)( ( 10985 - $zl ) / 5316 ) ) * ( (int)( ( 50 * $zl ) / 17719 ) ) +
1668 ( (int)( $zl / 5670 ) ) * ( (int)( ( 43 * $zl ) / 15238 ) );
1669 $zl = $zl - ( (int)( ( 30 - $zj ) / 15 ) ) * ( (int)( ( 17719 * $zj ) / 50 ) ) -
1670 ( (int)( $zj / 16 ) ) * ( (int)( ( 15238 * $zj ) / 43 ) ) + 29;
1671 $zm = (int)( ( 24 * $zl ) / 709 );
1672 $zd = $zl - (int)( ( 709 * $zm ) / 24 );
1673 $zy = 30 * $zn + $zj - 30;
1674
1675 return [ $zy, $zm, $zd ];
1676 }
1677
1693 private static function tsToHebrew( $ts ) {
1694 # Parse date
1695 $year = substr( $ts, 0, 4 );
1696 $month = substr( $ts, 4, 2 );
1697 $day = substr( $ts, 6, 2 );
1698
1699 # Calculate Hebrew year
1700 $hebrewYear = $year + 3760;
1701
1702 # Month number when September = 1, August = 12
1703 $month += 4;
1704 if ( $month > 12 ) {
1705 # Next year
1706 $month -= 12;
1707 $year++;
1708 $hebrewYear++;
1709 }
1710
1711 # Calculate day of year from 1 September
1712 $dayOfYear = $day;
1713 for ( $i = 1; $i < $month; $i++ ) {
1714 if ( $i == 6 ) {
1715 # February
1716 $dayOfYear += 28;
1717 # Check if the year is leap
1718 if ( $year % 400 == 0 || ( $year % 4 == 0 && $year % 100 > 0 ) ) {
1719 $dayOfYear++;
1720 }
1721 } elseif ( $i == 8 || $i == 10 || $i == 1 || $i == 3 ) {
1722 $dayOfYear += 30;
1723 } else {
1724 $dayOfYear += 31;
1725 }
1726 }
1727
1728 # Calculate the start of the Hebrew year
1729 $start = self::hebrewYearStart( $hebrewYear );
1730
1731 # Calculate next year's start
1732 if ( $dayOfYear <= $start ) {
1733 # Day is before the start of the year - it is the previous year
1734 # Next year's start
1735 $nextStart = $start;
1736 # Previous year
1737 $year--;
1738 $hebrewYear--;
1739 # Add days since previous year's 1 September
1740 $dayOfYear += 365;
1741 if ( ( $year % 400 == 0 ) || ( $year % 100 != 0 && $year % 4 == 0 ) ) {
1742 # Leap year
1743 $dayOfYear++;
1744 }
1745 # Start of the new (previous) year
1746 $start = self::hebrewYearStart( $hebrewYear );
1747 } else {
1748 # Next year's start
1749 $nextStart = self::hebrewYearStart( $hebrewYear + 1 );
1750 }
1751
1752 # Calculate Hebrew day of year
1753 $hebrewDayOfYear = $dayOfYear - $start;
1754
1755 # Difference between year's days
1756 $diff = $nextStart - $start;
1757 # Add 12 (or 13 for leap years) days to ignore the difference between
1758 # Hebrew and Gregorian year (353 at least vs. 365/6) - now the
1759 # difference is only about the year type
1760 if ( ( $year % 400 == 0 ) || ( $year % 100 != 0 && $year % 4 == 0 ) ) {
1761 $diff += 13;
1762 } else {
1763 $diff += 12;
1764 }
1765
1766 # Check the year pattern, and is leap year
1767 # 0 means an incomplete year, 1 means a regular year, 2 means a complete year
1768 # This is mod 30, to work on both leap years (which add 30 days of Adar I)
1769 # and non-leap years
1770 $yearPattern = $diff % 30;
1771 # Check if leap year
1772 $isLeap = $diff >= 30;
1773
1774 # Calculate day in the month from number of day in the Hebrew year
1775 # Don't check Adar - if the day is not in Adar, we will stop before;
1776 # if it is in Adar, we will use it to check if it is Adar I or Adar II
1777 $hebrewDay = $hebrewDayOfYear;
1778 $hebrewMonth = 1;
1779 $days = 0;
1780 while ( $hebrewMonth <= 12 ) {
1781 # Calculate days in this month
1782 if ( $isLeap && $hebrewMonth == 6 ) {
1783 # Adar in a leap year
1784 if ( $isLeap ) {
1785 # Leap year - has Adar I, with 30 days, and Adar II, with 29 days
1786 $days = 30;
1787 if ( $hebrewDay <= $days ) {
1788 # Day in Adar I
1789 $hebrewMonth = 13;
1790 } else {
1791 # Subtract the days of Adar I
1792 $hebrewDay -= $days;
1793 # Try Adar II
1794 $days = 29;
1795 if ( $hebrewDay <= $days ) {
1796 # Day in Adar II
1797 $hebrewMonth = 14;
1798 }
1799 }
1800 }
1801 } elseif ( $hebrewMonth == 2 && $yearPattern == 2 ) {
1802 # Cheshvan in a complete year (otherwise as the rule below)
1803 $days = 30;
1804 } elseif ( $hebrewMonth == 3 && $yearPattern == 0 ) {
1805 # Kislev in an incomplete year (otherwise as the rule below)
1806 $days = 29;
1807 } else {
1808 # Odd months have 30 days, even have 29
1809 $days = 30 - ( $hebrewMonth - 1 ) % 2;
1810 }
1811 if ( $hebrewDay <= $days ) {
1812 # In the current month
1813 break;
1814 } else {
1815 # Subtract the days of the current month
1816 $hebrewDay -= $days;
1817 # Try in the next month
1818 $hebrewMonth++;
1819 }
1820 }
1821
1822 return [ $hebrewYear, $hebrewMonth, $hebrewDay, $days ];
1823 }
1824
1834 private static function hebrewYearStart( $year ) {
1835 $a = intval( ( 12 * ( $year - 1 ) + 17 ) % 19 );
1836 $b = intval( ( $year - 1 ) % 4 );
1837 $m = 32.044093161144 + 1.5542417966212 * $a + $b / 4.0 - 0.0031777940220923 * ( $year - 1 );
1838 if ( $m < 0 ) {
1839 $m--;
1840 }
1841 $Mar = intval( $m );
1842 if ( $m < 0 ) {
1843 $m++;
1844 }
1845 $m -= $Mar;
1846
1847 $c = intval( ( $Mar + 3 * ( $year - 1 ) + 5 * $b + 5 ) % 7 );
1848 if ( $c == 0 && $a > 11 && $m >= 0.89772376543210 ) {
1849 $Mar++;
1850 } elseif ( $c == 1 && $a > 6 && $m >= 0.63287037037037 ) {
1851 $Mar += 2;
1852 } elseif ( $c == 2 || $c == 4 || $c == 6 ) {
1853 $Mar++;
1854 }
1855
1856 $Mar += intval( ( $year - 3761 ) / 100 ) - intval( ( $year - 3761 ) / 400 ) - 24;
1857 return $Mar;
1858 }
1859
1872 private static function tsToYear( $ts, $cName ) {
1873 $gy = substr( $ts, 0, 4 );
1874 $gm = substr( $ts, 4, 2 );
1875 $gd = substr( $ts, 6, 2 );
1876
1877 if ( !strcmp( $cName, 'thai' ) ) {
1878 # Thai solar dates
1879 # Add 543 years to the Gregorian calendar
1880 # Months and days are identical
1881 $gy_offset = $gy + 543;
1882 } elseif ( ( !strcmp( $cName, 'minguo' ) ) || !strcmp( $cName, 'juche' ) ) {
1883 # Minguo dates
1884 # Deduct 1911 years from the Gregorian calendar
1885 # Months and days are identical
1886 $gy_offset = $gy - 1911;
1887 } elseif ( !strcmp( $cName, 'tenno' ) ) {
1888 # Nengō dates up to Meiji period
1889 # Deduct years from the Gregorian calendar
1890 # depending on the nengo periods
1891 # Months and days are identical
1892 if ( ( $gy < 1912 )
1893 || ( ( $gy == 1912 ) && ( $gm < 7 ) )
1894 || ( ( $gy == 1912 ) && ( $gm == 7 ) && ( $gd < 31 ) )
1895 ) {
1896 # Meiji period
1897 $gy_gannen = $gy - 1868 + 1;
1898 $gy_offset = $gy_gannen;
1899 if ( $gy_gannen == 1 ) {
1900 $gy_offset = '元';
1901 }
1902 $gy_offset = '明治' . $gy_offset;
1903 } elseif (
1904 ( ( $gy == 1912 ) && ( $gm == 7 ) && ( $gd == 31 ) ) ||
1905 ( ( $gy == 1912 ) && ( $gm >= 8 ) ) ||
1906 ( ( $gy > 1912 ) && ( $gy < 1926 ) ) ||
1907 ( ( $gy == 1926 ) && ( $gm < 12 ) ) ||
1908 ( ( $gy == 1926 ) && ( $gm == 12 ) && ( $gd < 26 ) )
1909 ) {
1910 # Taishō period
1911 $gy_gannen = $gy - 1912 + 1;
1912 $gy_offset = $gy_gannen;
1913 if ( $gy_gannen == 1 ) {
1914 $gy_offset = '元';
1915 }
1916 $gy_offset = '大正' . $gy_offset;
1917 } elseif (
1918 ( ( $gy == 1926 ) && ( $gm == 12 ) && ( $gd >= 26 ) ) ||
1919 ( ( $gy > 1926 ) && ( $gy < 1989 ) ) ||
1920 ( ( $gy == 1989 ) && ( $gm == 1 ) && ( $gd < 8 ) )
1921 ) {
1922 # Shōwa period
1923 $gy_gannen = $gy - 1926 + 1;
1924 $gy_offset = $gy_gannen;
1925 if ( $gy_gannen == 1 ) {
1926 $gy_offset = '元';
1927 }
1928 $gy_offset = '昭和' . $gy_offset;
1929 } else {
1930 # Heisei period
1931 $gy_gannen = $gy - 1989 + 1;
1932 $gy_offset = $gy_gannen;
1933 if ( $gy_gannen == 1 ) {
1934 $gy_offset = '元';
1935 }
1936 $gy_offset = '平成' . $gy_offset;
1937 }
1938 } else {
1939 $gy_offset = $gy;
1940 }
1941
1942 return [ $gy_offset, $gm, $gd ];
1943 }
1944
1958 private static function strongDirFromContent( $text = '' ) {
1959 if ( !preg_match( self::$strongDirRegex, $text, $matches ) ) {
1960 return null;
1961 }
1962 if ( $matches[1] === '' ) {
1963 return 'rtl';
1964 }
1965 return 'ltr';
1966 }
1967
1975 static function romanNumeral( $num ) {
1976 static $table = [
1977 [ '', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X' ],
1978 [ '', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC', 'C' ],
1979 [ '', 'C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM', 'M' ],
1980 [ '', 'M', 'MM', 'MMM', 'MMMM', 'MMMMM', 'MMMMMM', 'MMMMMMM',
1981 'MMMMMMMM', 'MMMMMMMMM', 'MMMMMMMMMM' ]
1982 ];
1983
1984 $num = intval( $num );
1985 if ( $num > 10000 || $num <= 0 ) {
1986 return $num;
1987 }
1988
1989 $s = '';
1990 for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) {
1991 if ( $num >= $pow10 ) {
1992 $s .= $table[$i][(int)floor( $num / $pow10 )];
1993 }
1994 $num = $num % $pow10;
1995 }
1996 return $s;
1997 }
1998
2006 static function hebrewNumeral( $num ) {
2007 static $table = [
2008 [ '', 'א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ז', 'ח', 'ט', 'י' ],
2009 [ '', 'י', 'כ', 'ל', 'מ', 'נ', 'ס', 'ע', 'פ', 'צ', 'ק' ],
2010 [ '',
2011 [ 'ק' ],
2012 [ 'ר' ],
2013 [ 'ש' ],
2014 [ 'ת' ],
2015 [ 'ת', 'ק' ],
2016 [ 'ת', 'ר' ],
2017 [ 'ת', 'ש' ],
2018 [ 'ת', 'ת' ],
2019 [ 'ת', 'ת', 'ק' ],
2020 [ 'ת', 'ת', 'ר' ],
2021 ],
2022 [ '', 'א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ז', 'ח', 'ט', 'י' ]
2023 ];
2024
2025 $num = intval( $num );
2026 if ( $num > 9999 || $num <= 0 ) {
2027 return $num;
2028 }
2029
2030 // Round thousands have special notations
2031 if ( $num === 1000 ) {
2032 return "א' אלף";
2033 } elseif ( $num % 1000 === 0 ) {
2034 return $table[0][$num / 1000] . "' אלפים";
2035 }
2036
2037 $letters = [];
2038
2039 for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) {
2040 if ( $num >= $pow10 ) {
2041 if ( $num === 15 || $num === 16 ) {
2042 $letters[] = $table[0][9];
2043 $letters[] = $table[0][$num - 9];
2044 $num = 0;
2045 } else {
2046 $letters = array_merge(
2047 $letters,
2048 (array)$table[$i][intval( $num / $pow10 )]
2049 );
2050
2051 if ( $pow10 === 1000 ) {
2052 $letters[] = "'";
2053 }
2054 }
2055 }
2056
2057 $num = $num % $pow10;
2058 }
2059
2060 $preTransformLength = count( $letters );
2061 if ( $preTransformLength === 1 ) {
2062 // Add geresh (single quote) to one-letter numbers
2063 $letters[] = "'";
2064 } else {
2065 $lastIndex = $preTransformLength - 1;
2066 $letters[$lastIndex] = str_replace(
2067 [ 'כ', 'מ', 'נ', 'פ', 'צ' ],
2068 [ 'ך', 'ם', 'ן', 'ף', 'ץ' ],
2069 $letters[$lastIndex]
2070 );
2071
2072 // Add gershayim (double quote) to multiple-letter numbers,
2073 // but exclude numbers with only one letter after the thousands
2074 // (1001-1009, 1020, 1030, 2001-2009, etc.)
2075 if ( $letters[1] === "'" && $preTransformLength === 3 ) {
2076 $letters[] = "'";
2077 } else {
2078 array_splice( $letters, -1, 0, '"' );
2079 }
2080 }
2081
2082 return implode( $letters );
2083 }
2084
2093 public function userAdjust( $ts, $tz = false ) {
2095
2096 if ( $tz === false ) {
2097 $tz = $wgUser->getOption( 'timecorrection' );
2098 }
2099
2100 $data = explode( '|', $tz, 3 );
2101
2102 if ( $data[0] == 'ZoneInfo' ) {
2103 try {
2104 $userTZ = new DateTimeZone( $data[2] );
2105 $date = new DateTime( $ts, new DateTimeZone( 'UTC' ) );
2106 $date->setTimezone( $userTZ );
2107 return $date->format( 'YmdHis' );
2108 } catch ( Exception $e ) {
2109 // Unrecognized timezone, default to 'Offset' with the stored offset.
2110 $data[0] = 'Offset';
2111 }
2112 }
2113
2114 if ( $data[0] == 'System' || $tz == '' ) {
2115 # Global offset in minutes.
2116 $minDiff = $wgLocalTZoffset;
2117 } elseif ( $data[0] == 'Offset' ) {
2118 $minDiff = intval( $data[1] );
2119 } else {
2120 $data = explode( ':', $tz );
2121 if ( count( $data ) == 2 ) {
2122 $data[0] = intval( $data[0] );
2123 $data[1] = intval( $data[1] );
2124 $minDiff = abs( $data[0] ) * 60 + $data[1];
2125 if ( $data[0] < 0 ) {
2126 $minDiff = -$minDiff;
2127 }
2128 } else {
2129 $minDiff = intval( $data[0] ) * 60;
2130 }
2131 }
2132
2133 # No difference ? Return time unchanged
2134 if ( 0 == $minDiff ) {
2135 return $ts;
2136 }
2137
2138 MediaWiki\suppressWarnings(); // E_STRICT system time bitching
2139 # Generate an adjusted date; take advantage of the fact that mktime
2140 # will normalize out-of-range values so we don't have to split $minDiff
2141 # into hours and minutes.
2142 $t = mktime( (
2143 (int)substr( $ts, 8, 2 ) ), # Hours
2144 (int)substr( $ts, 10, 2 ) + $minDiff, # Minutes
2145 (int)substr( $ts, 12, 2 ), # Seconds
2146 (int)substr( $ts, 4, 2 ), # Month
2147 (int)substr( $ts, 6, 2 ), # Day
2148 (int)substr( $ts, 0, 4 ) ); # Year
2149
2150 $date = date( 'YmdHis', $t );
2151 MediaWiki\restoreWarnings();
2152
2153 return $date;
2154 }
2155
2171 function dateFormat( $usePrefs = true ) {
2173
2174 if ( is_bool( $usePrefs ) ) {
2175 if ( $usePrefs ) {
2176 $datePreference = $wgUser->getDatePreference();
2177 } else {
2178 $datePreference = (string)User::getDefaultOption( 'date' );
2179 }
2180 } else {
2181 $datePreference = (string)$usePrefs;
2182 }
2183
2184 // return int
2185 if ( $datePreference == '' ) {
2186 return 'default';
2187 }
2188
2189 return $datePreference;
2190 }
2191
2202 function getDateFormatString( $type, $pref ) {
2203 $wasDefault = false;
2204 if ( $pref == 'default' ) {
2205 $wasDefault = true;
2206 $pref = $this->getDefaultDateFormat();
2207 }
2208
2209 if ( !isset( $this->dateFormatStrings[$type][$pref] ) ) {
2210 $df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" );
2211
2212 if ( $type === 'pretty' && $df === null ) {
2213 $df = $this->getDateFormatString( 'date', $pref );
2214 }
2215
2216 if ( !$wasDefault && $df === null ) {
2217 $pref = $this->getDefaultDateFormat();
2218 $df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" );
2219 }
2220
2221 $this->dateFormatStrings[$type][$pref] = $df;
2222 }
2223 return $this->dateFormatStrings[$type][$pref];
2224 }
2225
2236 public function date( $ts, $adj = false, $format = true, $timecorrection = false ) {
2237 $ts = wfTimestamp( TS_MW, $ts );
2238 if ( $adj ) {
2239 $ts = $this->userAdjust( $ts, $timecorrection );
2240 }
2241 $df = $this->getDateFormatString( 'date', $this->dateFormat( $format ) );
2242 return $this->sprintfDate( $df, $ts );
2243 }
2244
2255 public function time( $ts, $adj = false, $format = true, $timecorrection = false ) {
2256 $ts = wfTimestamp( TS_MW, $ts );
2257 if ( $adj ) {
2258 $ts = $this->userAdjust( $ts, $timecorrection );
2259 }
2260 $df = $this->getDateFormatString( 'time', $this->dateFormat( $format ) );
2261 return $this->sprintfDate( $df, $ts );
2262 }
2263
2275 public function timeanddate( $ts, $adj = false, $format = true, $timecorrection = false ) {
2276 $ts = wfTimestamp( TS_MW, $ts );
2277 if ( $adj ) {
2278 $ts = $this->userAdjust( $ts, $timecorrection );
2279 }
2280 $df = $this->getDateFormatString( 'both', $this->dateFormat( $format ) );
2281 return $this->sprintfDate( $df, $ts );
2282 }
2283
2294 public function formatDuration( $seconds, array $chosenIntervals = [] ) {
2295 $intervals = $this->getDurationIntervals( $seconds, $chosenIntervals );
2296
2297 $segments = [];
2298
2299 foreach ( $intervals as $intervalName => $intervalValue ) {
2300 // Messages: duration-seconds, duration-minutes, duration-hours, duration-days, duration-weeks,
2301 // duration-years, duration-decades, duration-centuries, duration-millennia
2302 $message = wfMessage( 'duration-' . $intervalName )->numParams( $intervalValue );
2303 $segments[] = $message->inLanguage( $this )->escaped();
2304 }
2305
2306 return $this->listToText( $segments );
2307 }
2308
2320 public function getDurationIntervals( $seconds, array $chosenIntervals = [] ) {
2321 if ( empty( $chosenIntervals ) ) {
2322 $chosenIntervals = [
2323 'millennia',
2324 'centuries',
2325 'decades',
2326 'years',
2327 'days',
2328 'hours',
2329 'minutes',
2330 'seconds'
2331 ];
2332 }
2333
2334 $intervals = array_intersect_key( self::$durationIntervals, array_flip( $chosenIntervals ) );
2335 $sortedNames = array_keys( $intervals );
2336 $smallestInterval = array_pop( $sortedNames );
2337
2338 $segments = [];
2339
2340 foreach ( $intervals as $name => $length ) {
2341 $value = floor( $seconds / $length );
2342
2343 if ( $value > 0 || ( $name == $smallestInterval && empty( $segments ) ) ) {
2344 $seconds -= $value * $length;
2345 $segments[$name] = $value;
2346 }
2347 }
2348
2349 return $segments;
2350 }
2351
2371 private function internalUserTimeAndDate( $type, $ts, User $user, array $options ) {
2372 $ts = wfTimestamp( TS_MW, $ts );
2373 $options += [ 'timecorrection' => true, 'format' => true ];
2374 if ( $options['timecorrection'] !== false ) {
2375 if ( $options['timecorrection'] === true ) {
2376 $offset = $user->getOption( 'timecorrection' );
2377 } else {
2378 $offset = $options['timecorrection'];
2379 }
2380 $ts = $this->userAdjust( $ts, $offset );
2381 }
2382 if ( $options['format'] === true ) {
2383 $format = $user->getDatePreference();
2384 } else {
2385 $format = $options['format'];
2386 }
2387 $df = $this->getDateFormatString( $type, $this->dateFormat( $format ) );
2388 return $this->sprintfDate( $df, $ts );
2389 }
2390
2410 public function userDate( $ts, User $user, array $options = [] ) {
2411 return $this->internalUserTimeAndDate( 'date', $ts, $user, $options );
2412 }
2413
2433 public function userTime( $ts, User $user, array $options = [] ) {
2434 return $this->internalUserTimeAndDate( 'time', $ts, $user, $options );
2435 }
2436
2456 public function userTimeAndDate( $ts, User $user, array $options = [] ) {
2457 return $this->internalUserTimeAndDate( 'both', $ts, $user, $options );
2458 }
2459
2475 public function getHumanTimestamp(
2476 MWTimestamp $time, MWTimestamp $relativeTo = null, User $user = null
2477 ) {
2478 if ( $relativeTo === null ) {
2479 $relativeTo = new MWTimestamp();
2480 }
2481 if ( $user === null ) {
2482 $user = RequestContext::getMain()->getUser();
2483 }
2484
2485 // Adjust for the user's timezone.
2486 $offsetThis = $time->offsetForUser( $user );
2487 $offsetRel = $relativeTo->offsetForUser( $user );
2488
2489 $ts = '';
2490 if ( Hooks::run( 'GetHumanTimestamp', [ &$ts, $time, $relativeTo, $user, $this ] ) ) {
2491 $ts = $this->getHumanTimestampInternal( $time, $relativeTo, $user );
2492 }
2493
2494 // Reset the timezone on the objects.
2495 $time->timestamp->sub( $offsetThis );
2496 $relativeTo->timestamp->sub( $offsetRel );
2497
2498 return $ts;
2499 }
2500
2513 MWTimestamp $ts, MWTimestamp $relativeTo, User $user
2514 ) {
2515 $diff = $ts->diff( $relativeTo );
2516 $diffDay = (bool)( (int)$ts->timestamp->format( 'w' ) -
2517 (int)$relativeTo->timestamp->format( 'w' ) );
2518 $days = $diff->days ?: (int)$diffDay;
2519 if ( $diff->invert || $days > 5
2520 && $ts->timestamp->format( 'Y' ) !== $relativeTo->timestamp->format( 'Y' )
2521 ) {
2522 // Timestamps are in different years: use full timestamp
2523 // Also do full timestamp for future dates
2527 $format = $this->getDateFormatString( 'both', $user->getDatePreference() ?: 'default' );
2528 $ts = $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) );
2529 } elseif ( $days > 5 ) {
2530 // Timestamps are in same year, but more than 5 days ago: show day and month only.
2531 $format = $this->getDateFormatString( 'pretty', $user->getDatePreference() ?: 'default' );
2532 $ts = $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) );
2533 } elseif ( $days > 1 ) {
2534 // Timestamp within the past week: show the day of the week and time
2535 $format = $this->getDateFormatString( 'time', $user->getDatePreference() ?: 'default' );
2536 $weekday = self::$mWeekdayMsgs[$ts->timestamp->format( 'w' )];
2537 // Messages:
2538 // sunday-at, monday-at, tuesday-at, wednesday-at, thursday-at, friday-at, saturday-at
2539 $ts = wfMessage( "$weekday-at" )
2540 ->inLanguage( $this )
2541 ->params( $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) ) )
2542 ->text();
2543 } elseif ( $days == 1 ) {
2544 // Timestamp was yesterday: say 'yesterday' and the time.
2545 $format = $this->getDateFormatString( 'time', $user->getDatePreference() ?: 'default' );
2546 $ts = wfMessage( 'yesterday-at' )
2547 ->inLanguage( $this )
2548 ->params( $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) ) )
2549 ->text();
2550 } elseif ( $diff->h > 1 || $diff->h == 1 && $diff->i > 30 ) {
2551 // Timestamp was today, but more than 90 minutes ago: say 'today' and the time.
2552 $format = $this->getDateFormatString( 'time', $user->getDatePreference() ?: 'default' );
2553 $ts = wfMessage( 'today-at' )
2554 ->inLanguage( $this )
2555 ->params( $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) ) )
2556 ->text();
2557
2558 // From here on in, the timestamp was soon enough ago so that we can simply say
2559 // XX units ago, e.g., "2 hours ago" or "5 minutes ago"
2560 } elseif ( $diff->h == 1 ) {
2561 // Less than 90 minutes, but more than an hour ago.
2562 $ts = wfMessage( 'hours-ago' )->inLanguage( $this )->numParams( 1 )->text();
2563 } elseif ( $diff->i >= 1 ) {
2564 // A few minutes ago.
2565 $ts = wfMessage( 'minutes-ago' )->inLanguage( $this )->numParams( $diff->i )->text();
2566 } elseif ( $diff->s >= 30 ) {
2567 // Less than a minute, but more than 30 sec ago.
2568 $ts = wfMessage( 'seconds-ago' )->inLanguage( $this )->numParams( $diff->s )->text();
2569 } else {
2570 // Less than 30 seconds ago.
2571 $ts = wfMessage( 'just-now' )->text();
2572 }
2573
2574 return $ts;
2575 }
2576
2581 public function getMessage( $key ) {
2582 return self::$dataCache->getSubitem( $this->mCode, 'messages', $key );
2583 }
2584
2588 function getAllMessages() {
2589 return self::$dataCache->getItem( $this->mCode, 'messages' );
2590 }
2591
2598 public function iconv( $in, $out, $string ) {
2599 # Even with //IGNORE iconv can whine about illegal characters in
2600 # *input* string. We just ignore those too.
2601 # REF: https://bugs.php.net/bug.php?id=37166
2602 # REF: https://phabricator.wikimedia.org/T18885
2603 MediaWiki\suppressWarnings();
2604 $text = iconv( $in, $out . '//IGNORE', $string );
2605 MediaWiki\restoreWarnings();
2606 return $text;
2607 }
2608
2609 // callback functions for ucwords(), ucwordbreaks()
2610
2616 return $this->ucfirst( $matches[1] );
2617 }
2618
2624 return mb_strtoupper( $matches[0] );
2625 }
2626
2632 return mb_strtoupper( $matches[0] );
2633 }
2634
2642 public function ucfirst( $str ) {
2643 $o = ord( $str );
2644 if ( $o < 96 ) { // if already uppercase...
2645 return $str;
2646 } elseif ( $o < 128 ) {
2647 return ucfirst( $str ); // use PHP's ucfirst()
2648 } else {
2649 // fall back to more complex logic in case of multibyte strings
2650 return $this->uc( $str, true );
2651 }
2652 }
2653
2662 public function uc( $str, $first = false ) {
2663 if ( $first ) {
2664 if ( $this->isMultibyte( $str ) ) {
2665 return mb_strtoupper( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
2666 } else {
2667 return ucfirst( $str );
2668 }
2669 } else {
2670 return $this->isMultibyte( $str ) ? mb_strtoupper( $str ) : strtoupper( $str );
2671 }
2672 }
2673
2678 function lcfirst( $str ) {
2679 $o = ord( $str );
2680 if ( !$o ) {
2681 return strval( $str );
2682 } elseif ( $o >= 128 ) {
2683 return $this->lc( $str, true );
2684 } elseif ( $o > 96 ) {
2685 return $str;
2686 } else {
2687 $str[0] = strtolower( $str[0] );
2688 return $str;
2689 }
2690 }
2691
2697 function lc( $str, $first = false ) {
2698 if ( $first ) {
2699 if ( $this->isMultibyte( $str ) ) {
2700 return mb_strtolower( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
2701 } else {
2702 return strtolower( substr( $str, 0, 1 ) ) . substr( $str, 1 );
2703 }
2704 } else {
2705 return $this->isMultibyte( $str ) ? mb_strtolower( $str ) : strtolower( $str );
2706 }
2707 }
2708
2713 function isMultibyte( $str ) {
2714 return strlen( $str ) !== mb_strlen( $str );
2715 }
2716
2721 function ucwords( $str ) {
2722 if ( $this->isMultibyte( $str ) ) {
2723 $str = $this->lc( $str );
2724
2725 // regexp to find first letter in each word (i.e. after each space)
2726 $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)| ([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
2727
2728 // function to use to capitalize a single char
2729 return preg_replace_callback(
2730 $replaceRegexp,
2731 [ $this, 'ucwordsCallbackMB' ],
2732 $str
2733 );
2734 } else {
2735 return ucwords( strtolower( $str ) );
2736 }
2737 }
2738
2745 function ucwordbreaks( $str ) {
2746 if ( $this->isMultibyte( $str ) ) {
2747 $str = $this->lc( $str );
2748
2749 // since \b doesn't work for UTF-8, we explicitely define word break chars
2750 $breaks = "[ \-\‍(\‍)\}\{\.,\?!]";
2751
2752 // find first letter after word break
2753 $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)|" .
2754 "$breaks([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
2755
2756 return preg_replace_callback(
2757 $replaceRegexp,
2758 [ $this, 'ucwordbreaksCallbackMB' ],
2759 $str
2760 );
2761 } else {
2762 return preg_replace_callback(
2763 '/\b([\w\x80-\xff]+)\b/',
2764 [ $this, 'ucwordbreaksCallbackAscii' ],
2765 $str
2766 );
2767 }
2768 }
2769
2785 function caseFold( $s ) {
2786 return $this->uc( $s );
2787 }
2788
2795 if ( is_array( $s ) ) {
2796 throw new MWException( 'Given array to checkTitleEncoding.' );
2797 }
2798 if ( StringUtils::isUtf8( $s ) ) {
2799 return $s;
2800 }
2801
2802 return $this->iconv( $this->fallback8bitEncoding(), 'utf-8', $s );
2803 }
2804
2809 return self::$dataCache->getItem( $this->mCode, 'fallback8bitEncoding' );
2810 }
2811
2820 function hasWordBreaks() {
2821 return true;
2822 }
2823
2831 function segmentByWord( $string ) {
2832 return $string;
2833 }
2834
2842 function normalizeForSearch( $string ) {
2843 return self::convertDoubleWidth( $string );
2844 }
2845
2854 protected static function convertDoubleWidth( $string ) {
2855 static $full = null;
2856 static $half = null;
2857
2858 if ( $full === null ) {
2859 $fullWidth = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
2860 $halfWidth = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
2861 $full = str_split( $fullWidth, 3 );
2862 $half = str_split( $halfWidth );
2863 }
2864
2865 $string = str_replace( $full, $half, $string );
2866 return $string;
2867 }
2868
2874 protected static function insertSpace( $string, $pattern ) {
2875 $string = preg_replace( $pattern, " $1 ", $string );
2876 $string = preg_replace( '/ +/', ' ', $string );
2877 return $string;
2878 }
2879
2884 function convertForSearchResult( $termsArray ) {
2885 # some languages, e.g. Chinese, need to do a conversion
2886 # in order for search results to be displayed correctly
2887 return $termsArray;
2888 }
2889
2896 function firstChar( $s ) {
2897 $matches = [];
2898 preg_match(
2899 '/^([\x00-\x7f]|[\xc0-\xdf][\x80-\xbf]|' .
2900 '[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xf7][\x80-\xbf]{3})/',
2901 $s,
2902 $matches
2903 );
2904
2905 if ( isset( $matches[1] ) ) {
2906 if ( strlen( $matches[1] ) != 3 ) {
2907 return $matches[1];
2908 }
2909
2910 // Break down Hangul syllables to grab the first jamo
2911 $code = UtfNormal\Utils::utf8ToCodepoint( $matches[1] );
2912 if ( $code < 0xac00 || 0xd7a4 <= $code ) {
2913 return $matches[1];
2914 } elseif ( $code < 0xb098 ) {
2915 return "\xe3\x84\xb1";
2916 } elseif ( $code < 0xb2e4 ) {
2917 return "\xe3\x84\xb4";
2918 } elseif ( $code < 0xb77c ) {
2919 return "\xe3\x84\xb7";
2920 } elseif ( $code < 0xb9c8 ) {
2921 return "\xe3\x84\xb9";
2922 } elseif ( $code < 0xbc14 ) {
2923 return "\xe3\x85\x81";
2924 } elseif ( $code < 0xc0ac ) {
2925 return "\xe3\x85\x82";
2926 } elseif ( $code < 0xc544 ) {
2927 return "\xe3\x85\x85";
2928 } elseif ( $code < 0xc790 ) {
2929 return "\xe3\x85\x87";
2930 } elseif ( $code < 0xcc28 ) {
2931 return "\xe3\x85\x88";
2932 } elseif ( $code < 0xce74 ) {
2933 return "\xe3\x85\x8a";
2934 } elseif ( $code < 0xd0c0 ) {
2935 return "\xe3\x85\x8b";
2936 } elseif ( $code < 0xd30c ) {
2937 return "\xe3\x85\x8c";
2938 } elseif ( $code < 0xd558 ) {
2939 return "\xe3\x85\x8d";
2940 } else {
2941 return "\xe3\x85\x8e";
2942 }
2943 } else {
2944 return '';
2945 }
2946 }
2947
2951 function initEncoding() {
2952 // No-op.
2953 }
2954
2960 function recodeForEdit( $s ) {
2961 return $s;
2962 }
2963
2969 function recodeInput( $s ) {
2970 return $s;
2971 }
2972
2984 function normalize( $s ) {
2986 $s = UtfNormal\Validator::cleanUp( $s );
2987 if ( $wgAllUnicodeFixes ) {
2988 $s = $this->transformUsingPairFile( 'normalize-ar.ser', $s );
2989 $s = $this->transformUsingPairFile( 'normalize-ml.ser', $s );
2990 }
2991
2992 return $s;
2993 }
2994
3009 function transformUsingPairFile( $file, $string ) {
3010 if ( !isset( $this->transformData[$file] ) ) {
3011 $data = wfGetPrecompiledData( $file );
3012 if ( $data === false ) {
3013 throw new MWException( __METHOD__ . ": The transformation file $file is missing" );
3014 }
3015 $this->transformData[$file] = new ReplacementArray( $data );
3016 }
3017 return $this->transformData[$file]->replace( $string );
3018 }
3019
3025 function isRTL() {
3026 return self::$dataCache->getItem( $this->mCode, 'rtl' );
3027 }
3028
3033 function getDir() {
3034 return $this->isRTL() ? 'rtl' : 'ltr';
3035 }
3036
3045 function alignStart() {
3046 return $this->isRTL() ? 'right' : 'left';
3047 }
3048
3057 function alignEnd() {
3058 return $this->isRTL() ? 'left' : 'right';
3059 }
3060
3072 function getDirMarkEntity( $opposite = false ) {
3073 if ( $opposite ) {
3074 return $this->isRTL() ? '&lrm;' : '&rlm;';
3075 }
3076 return $this->isRTL() ? '&rlm;' : '&lrm;';
3077 }
3078
3089 function getDirMark( $opposite = false ) {
3090 $lrm = "\xE2\x80\x8E"; # LEFT-TO-RIGHT MARK, commonly abbreviated LRM
3091 $rlm = "\xE2\x80\x8F"; # RIGHT-TO-LEFT MARK, commonly abbreviated RLM
3092 if ( $opposite ) {
3093 return $this->isRTL() ? $lrm : $rlm;
3094 }
3095 return $this->isRTL() ? $rlm : $lrm;
3096 }
3097
3102 return self::$dataCache->getItem( $this->mCode, 'capitalizeAllNouns' );
3103 }
3104
3112 function getArrow( $direction = 'forwards' ) {
3113 switch ( $direction ) {
3114 case 'forwards':
3115 return $this->isRTL() ? '←' : '→';
3116 case 'backwards':
3117 return $this->isRTL() ? '→' : '←';
3118 case 'left':
3119 return '←';
3120 case 'right':
3121 return '→';
3122 case 'up':
3123 return '↑';
3124 case 'down':
3125 return '↓';
3126 }
3127 }
3128
3135 return self::$dataCache->getItem( $this->mCode, 'linkPrefixExtension' );
3136 }
3137
3142 function getMagicWords() {
3143 return self::$dataCache->getItem( $this->mCode, 'magicWords' );
3144 }
3145
3149 protected function doMagicHook() {
3150 if ( $this->mMagicHookDone ) {
3151 return;
3152 }
3153 $this->mMagicHookDone = true;
3154 Hooks::run( 'LanguageGetMagic', [ &$this->mMagicExtensions, $this->getCode() ] );
3155 }
3156
3162 function getMagic( $mw ) {
3163 // Saves a function call
3164 if ( !$this->mMagicHookDone ) {
3165 $this->doMagicHook();
3166 }
3167
3168 if ( isset( $this->mMagicExtensions[$mw->mId] ) ) {
3169 $rawEntry = $this->mMagicExtensions[$mw->mId];
3170 } else {
3171 $rawEntry = self::$dataCache->getSubitem(
3172 $this->mCode, 'magicWords', $mw->mId );
3173 }
3174
3175 if ( !is_array( $rawEntry ) ) {
3176 wfWarn( "\"$rawEntry\" is not a valid magic word for \"$mw->mId\"" );
3177 } else {
3178 $mw->mCaseSensitive = $rawEntry[0];
3179 $mw->mSynonyms = array_slice( $rawEntry, 1 );
3180 }
3181 }
3182
3188 function addMagicWordsByLang( $newWords ) {
3189 $fallbackChain = $this->getFallbackLanguages();
3190 $fallbackChain = array_reverse( $fallbackChain );
3191 foreach ( $fallbackChain as $code ) {
3192 if ( isset( $newWords[$code] ) ) {
3193 $this->mMagicExtensions = $newWords[$code] + $this->mMagicExtensions;
3194 }
3195 }
3196 }
3197
3204 // Cache aliases because it may be slow to load them
3205 if ( is_null( $this->mExtendedSpecialPageAliases ) ) {
3206 // Initialise array
3207 $this->mExtendedSpecialPageAliases =
3208 self::$dataCache->getItem( $this->mCode, 'specialPageAliases' );
3209 Hooks::run( 'LanguageGetSpecialPageAliases',
3210 [ &$this->mExtendedSpecialPageAliases, $this->getCode() ] );
3211 }
3212
3213 return $this->mExtendedSpecialPageAliases;
3214 }
3215
3222 function emphasize( $text ) {
3223 return "<em>$text</em>";
3224 }
3225
3248 public function formatNum( $number, $nocommafy = false ) {
3250 if ( !$nocommafy ) {
3251 $number = $this->commafy( $number );
3252 $s = $this->separatorTransformTable();
3253 if ( $s ) {
3254 $number = strtr( $number, $s );
3255 }
3256 }
3257
3258 if ( $wgTranslateNumerals ) {
3259 $s = $this->digitTransformTable();
3260 if ( $s ) {
3261 $number = strtr( $number, $s );
3262 }
3263 }
3264
3265 return $number;
3266 }
3267
3276 public function formatNumNoSeparators( $number ) {
3277 return $this->formatNum( $number, true );
3278 }
3279
3284 public function parseFormattedNumber( $number ) {
3285 $s = $this->digitTransformTable();
3286 if ( $s ) {
3287 // eliminate empty array values such as ''. (T66347)
3288 $s = array_filter( $s );
3289 $number = strtr( $number, array_flip( $s ) );
3290 }
3291
3292 $s = $this->separatorTransformTable();
3293 if ( $s ) {
3294 // eliminate empty array values such as ''. (T66347)
3295 $s = array_filter( $s );
3296 $number = strtr( $number, array_flip( $s ) );
3297 }
3298
3299 $number = strtr( $number, [ ',' => '' ] );
3300 return $number;
3301 }
3302
3309 function commafy( $number ) {
3311 if ( $number === null ) {
3312 return '';
3313 }
3314
3315 if ( !$digitGroupingPattern || $digitGroupingPattern === "###,###,###" ) {
3316 // default grouping is at thousands, use the same for ###,###,### pattern too.
3317 return strrev( (string)preg_replace( '/(\d{3})(?=\d)(?!\d*\.)/', '$1,', strrev( $number ) ) );
3318 } else {
3319 // Ref: http://cldr.unicode.org/translation/number-patterns
3320 $sign = "";
3321 if ( intval( $number ) < 0 ) {
3322 // For negative numbers apply the algorithm like positive number and add sign.
3323 $sign = "-";
3324 $number = substr( $number, 1 );
3325 }
3326 $integerPart = [];
3327 $decimalPart = [];
3328 $numMatches = preg_match_all( "/(#+)/", $digitGroupingPattern, $matches );
3329 preg_match( "/\d+/", $number, $integerPart );
3330 preg_match( "/\.\d*/", $number, $decimalPart );
3331 $groupedNumber = ( count( $decimalPart ) > 0 ) ? $decimalPart[0] : "";
3332 if ( $groupedNumber === $number ) {
3333 // the string does not have any number part. Eg: .12345
3334 return $sign . $groupedNumber;
3335 }
3336 $start = $end = ( $integerPart ) ? strlen( $integerPart[0] ) : 0;
3337 while ( $start > 0 ) {
3338 $match = $matches[0][$numMatches - 1];
3339 $matchLen = strlen( $match );
3340 $start = $end - $matchLen;
3341 if ( $start < 0 ) {
3342 $start = 0;
3343 }
3344 $groupedNumber = substr( $number, $start, $end -$start ) . $groupedNumber;
3345 $end = $start;
3346 if ( $numMatches > 1 ) {
3347 // use the last pattern for the rest of the number
3348 $numMatches--;
3349 }
3350 if ( $start > 0 ) {
3351 $groupedNumber = "," . $groupedNumber;
3352 }
3353 }
3354 return $sign . $groupedNumber;
3355 }
3356 }
3357
3362 return self::$dataCache->getItem( $this->mCode, 'digitGroupingPattern' );
3363 }
3364
3369 return self::$dataCache->getItem( $this->mCode, 'digitTransformTable' );
3370 }
3371
3376 return self::$dataCache->getItem( $this->mCode, 'separatorTransformTable' );
3377 }
3378
3388 function listToText( array $l ) {
3389 $m = count( $l ) - 1;
3390 if ( $m < 0 ) {
3391 return '';
3392 }
3393 if ( $m > 0 ) {
3394 $and = $this->msg( 'and' )->escaped();
3395 $space = $this->msg( 'word-separator' )->escaped();
3396 if ( $m > 1 ) {
3397 $comma = $this->msg( 'comma-separator' )->escaped();
3398 }
3399 }
3400 $s = $l[$m];
3401 for ( $i = $m - 1; $i >= 0; $i-- ) {
3402 if ( $i == $m - 1 ) {
3403 $s = $l[$i] . $and . $space . $s;
3404 } else {
3405 $s = $l[$i] . $comma . $s;
3406 }
3407 }
3408 return $s;
3409 }
3410
3417 function commaList( array $list ) {
3418 return implode(
3419 wfMessage( 'comma-separator' )->inLanguage( $this )->escaped(),
3420 $list
3421 );
3422 }
3423
3430 function semicolonList( array $list ) {
3431 return implode(
3432 wfMessage( 'semicolon-separator' )->inLanguage( $this )->escaped(),
3433 $list
3434 );
3435 }
3436
3442 function pipeList( array $list ) {
3443 return implode(
3444 wfMessage( 'pipe-separator' )->inLanguage( $this )->escaped(),
3445 $list
3446 );
3447 }
3448
3466 function truncate( $string, $length, $ellipsis = '...', $adjustLength = true ) {
3467 # Use the localized ellipsis character
3468 if ( $ellipsis == '...' ) {
3469 $ellipsis = wfMessage( 'ellipsis' )->inLanguage( $this )->escaped();
3470 }
3471 # Check if there is no need to truncate
3472 if ( $length == 0 ) {
3473 return $ellipsis; // convention
3474 } elseif ( strlen( $string ) <= abs( $length ) ) {
3475 return $string; // no need to truncate
3476 }
3477 $stringOriginal = $string;
3478 # If ellipsis length is >= $length then we can't apply $adjustLength
3479 if ( $adjustLength && strlen( $ellipsis ) >= abs( $length ) ) {
3480 $string = $ellipsis; // this can be slightly unexpected
3481 # Otherwise, truncate and add ellipsis...
3482 } else {
3483 $eLength = $adjustLength ? strlen( $ellipsis ) : 0;
3484 if ( $length > 0 ) {
3485 $length -= $eLength;
3486 $string = substr( $string, 0, $length ); // xyz...
3487 $string = $this->removeBadCharLast( $string );
3488 $string = rtrim( $string );
3489 $string = $string . $ellipsis;
3490 } else {
3491 $length += $eLength;
3492 $string = substr( $string, $length ); // ...xyz
3493 $string = $this->removeBadCharFirst( $string );
3494 $string = ltrim( $string );
3495 $string = $ellipsis . $string;
3496 }
3497 }
3498 # Do not truncate if the ellipsis makes the string longer/equal (T24181).
3499 # This check is *not* redundant if $adjustLength, due to the single case where
3500 # LEN($ellipsis) > ABS($limit arg); $stringOriginal could be shorter than $string.
3501 if ( strlen( $string ) < strlen( $stringOriginal ) ) {
3502 return $string;
3503 } else {
3504 return $stringOriginal;
3505 }
3506 }
3507
3515 protected function removeBadCharLast( $string ) {
3516 if ( $string != '' ) {
3517 $char = ord( $string[strlen( $string ) - 1] );
3518 $m = [];
3519 if ( $char >= 0xc0 ) {
3520 # We got the first byte only of a multibyte char; remove it.
3521 $string = substr( $string, 0, -1 );
3522 } elseif ( $char >= 0x80 &&
3523 // Use the /s modifier (PCRE_DOTALL) so (.*) also matches newlines
3524 preg_match( '/^(.*)(?:[\xe0-\xef][\x80-\xbf]|' .
3525 '[\xf0-\xf7][\x80-\xbf]{1,2})$/s', $string, $m )
3526 ) {
3527 # We chopped in the middle of a character; remove it
3528 $string = $m[1];
3529 }
3530 }
3531 return $string;
3532 }
3533
3541 protected function removeBadCharFirst( $string ) {
3542 if ( $string != '' ) {
3543 $char = ord( $string[0] );
3544 if ( $char >= 0x80 && $char < 0xc0 ) {
3545 # We chopped in the middle of a character; remove the whole thing
3546 $string = preg_replace( '/^[\x80-\xbf]+/', '', $string );
3547 }
3548 }
3549 return $string;
3550 }
3551
3567 function truncateHtml( $text, $length, $ellipsis = '...' ) {
3568 # Use the localized ellipsis character
3569 if ( $ellipsis == '...' ) {
3570 $ellipsis = wfMessage( 'ellipsis' )->inLanguage( $this )->escaped();
3571 }
3572 # Check if there is clearly no need to truncate
3573 if ( $length <= 0 ) {
3574 return $ellipsis; // no text shown, nothing to format (convention)
3575 } elseif ( strlen( $text ) <= $length ) {
3576 return $text; // string short enough even *with* HTML (short-circuit)
3577 }
3578
3579 $dispLen = 0; // innerHTML legth so far
3580 $testingEllipsis = false; // checking if ellipses will make string longer/equal?
3581 $tagType = 0; // 0-open, 1-close
3582 $bracketState = 0; // 1-tag start, 2-tag name, 0-neither
3583 $entityState = 0; // 0-not entity, 1-entity
3584 $tag = $ret = ''; // accumulated tag name, accumulated result string
3585 $openTags = []; // open tag stack
3586 $maybeState = null; // possible truncation state
3587
3588 $textLen = strlen( $text );
3589 $neLength = max( 0, $length - strlen( $ellipsis ) ); // non-ellipsis len if truncated
3590 for ( $pos = 0; true; ++$pos ) {
3591 # Consider truncation once the display length has reached the maximim.
3592 # We check if $dispLen > 0 to grab tags for the $neLength = 0 case.
3593 # Check that we're not in the middle of a bracket/entity...
3594 if ( $dispLen && $dispLen >= $neLength && $bracketState == 0 && !$entityState ) {
3595 if ( !$testingEllipsis ) {
3596 $testingEllipsis = true;
3597 # Save where we are; we will truncate here unless there turn out to
3598 # be so few remaining characters that truncation is not necessary.
3599 if ( !$maybeState ) { // already saved? ($neLength = 0 case)
3600 $maybeState = [ $ret, $openTags ]; // save state
3601 }
3602 } elseif ( $dispLen > $length && $dispLen > strlen( $ellipsis ) ) {
3603 # String in fact does need truncation, the truncation point was OK.
3604 list( $ret, $openTags ) = $maybeState; // reload state
3605 $ret = $this->removeBadCharLast( $ret ); // multi-byte char fix
3606 $ret .= $ellipsis; // add ellipsis
3607 break;
3608 }
3609 }
3610 if ( $pos >= $textLen ) {
3611 break; // extra iteration just for above checks
3612 }
3613
3614 # Read the next char...
3615 $ch = $text[$pos];
3616 $lastCh = $pos ? $text[$pos - 1] : '';
3617 $ret .= $ch; // add to result string
3618 if ( $ch == '<' ) {
3619 $this->truncate_endBracket( $tag, $tagType, $lastCh, $openTags ); // for bad HTML
3620 $entityState = 0; // for bad HTML
3621 $bracketState = 1; // tag started (checking for backslash)
3622 } elseif ( $ch == '>' ) {
3623 $this->truncate_endBracket( $tag, $tagType, $lastCh, $openTags );
3624 $entityState = 0; // for bad HTML
3625 $bracketState = 0; // out of brackets
3626 } elseif ( $bracketState == 1 ) {
3627 if ( $ch == '/' ) {
3628 $tagType = 1; // close tag (e.g. "</span>")
3629 } else {
3630 $tagType = 0; // open tag (e.g. "<span>")
3631 $tag .= $ch;
3632 }
3633 $bracketState = 2; // building tag name
3634 } elseif ( $bracketState == 2 ) {
3635 if ( $ch != ' ' ) {
3636 $tag .= $ch;
3637 } else {
3638 // Name found (e.g. "<a href=..."), add on tag attributes...
3639 $pos += $this->truncate_skip( $ret, $text, "<>", $pos + 1 );
3640 }
3641 } elseif ( $bracketState == 0 ) {
3642 if ( $entityState ) {
3643 if ( $ch == ';' ) {
3644 $entityState = 0;
3645 $dispLen++; // entity is one displayed char
3646 }
3647 } else {
3648 if ( $neLength == 0 && !$maybeState ) {
3649 // Save state without $ch. We want to *hit* the first
3650 // display char (to get tags) but not *use* it if truncating.
3651 $maybeState = [ substr( $ret, 0, -1 ), $openTags ];
3652 }
3653 if ( $ch == '&' ) {
3654 $entityState = 1; // entity found, (e.g. "&#160;")
3655 } else {
3656 $dispLen++; // this char is displayed
3657 // Add the next $max display text chars after this in one swoop...
3658 $max = ( $testingEllipsis ? $length : $neLength ) - $dispLen;
3659 $skipped = $this->truncate_skip( $ret, $text, "<>&", $pos + 1, $max );
3660 $dispLen += $skipped;
3661 $pos += $skipped;
3662 }
3663 }
3664 }
3665 }
3666 // Close the last tag if left unclosed by bad HTML
3667 $this->truncate_endBracket( $tag, $text[$textLen - 1], $tagType, $openTags );
3668 while ( count( $openTags ) > 0 ) {
3669 $ret .= '</' . array_pop( $openTags ) . '>'; // close open tags
3670 }
3671 return $ret;
3672 }
3673
3685 private function truncate_skip( &$ret, $text, $search, $start, $len = null ) {
3686 if ( $len === null ) {
3687 $len = -1; // -1 means "no limit" for strcspn
3688 } elseif ( $len < 0 ) {
3689 $len = 0; // sanity
3690 }
3691 $skipCount = 0;
3692 if ( $start < strlen( $text ) ) {
3693 $skipCount = strcspn( $text, $search, $start, $len );
3694 $ret .= substr( $text, $start, $skipCount );
3695 }
3696 return $skipCount;
3697 }
3698
3708 private function truncate_endBracket( &$tag, $tagType, $lastCh, &$openTags ) {
3709 $tag = ltrim( $tag );
3710 if ( $tag != '' ) {
3711 if ( $tagType == 0 && $lastCh != '/' ) {
3712 $openTags[] = $tag; // tag opened (didn't close itself)
3713 } elseif ( $tagType == 1 ) {
3714 if ( $openTags && $tag == $openTags[count( $openTags ) - 1] ) {
3715 array_pop( $openTags ); // tag closed
3716 }
3717 }
3718 $tag = '';
3719 }
3720 }
3721
3730 function convertGrammar( $word, $case ) {
3732 if ( isset( $wgGrammarForms[$this->getCode()][$case][$word] ) ) {
3733 return $wgGrammarForms[$this->getCode()][$case][$word];
3734 }
3735
3737
3738 if ( isset( $grammarTransformations[$case] ) ) {
3739 $forms = $grammarTransformations[$case];
3740
3741 // Some names of grammar rules are aliases for other rules.
3742 // In such cases the value is a string rather than object,
3743 // so load the actual rules.
3744 if ( is_string( $forms ) ) {
3745 $forms = $grammarTransformations[$forms];
3746 }
3747
3748 foreach ( array_values( $forms ) as $rule ) {
3749 $form = $rule[0];
3750
3751 if ( $form === '@metadata' ) {
3752 continue;
3753 }
3754
3755 $replacement = $rule[1];
3756
3757 $regex = '/' . addcslashes( $form, '/' ) . '/u';
3758 $patternMatches = preg_match( $regex, $word );
3759
3760 if ( $patternMatches === false ) {
3762 'An error occurred while processing grammar. ' .
3763 "Word: '$word'. Regex: /$form/."
3764 );
3765 } elseif ( $patternMatches === 1 ) {
3766 $word = preg_replace( $regex, $replacement, $word );
3767
3768 break;
3769 }
3770 }
3771 }
3772
3773 return $word;
3774 }
3775
3781 function getGrammarForms() {
3783 if ( isset( $wgGrammarForms[$this->getCode()] )
3784 && is_array( $wgGrammarForms[$this->getCode()] )
3785 ) {
3786 return $wgGrammarForms[$this->getCode()];
3787 }
3788
3789 return [];
3790 }
3791
3801 public function getGrammarTransformations() {
3802 $languageCode = $this->getCode();
3803
3804 if ( self::$grammarTransformations === null ) {
3805 self::$grammarTransformations = new MapCacheLRU( 10 );
3806 }
3807
3808 if ( self::$grammarTransformations->has( $languageCode ) ) {
3809 return self::$grammarTransformations->get( $languageCode );
3810 }
3811
3812 $data = [];
3813
3814 $grammarDataFile = __DIR__ . "/data/grammarTransformations/$languageCode.json";
3815 if ( is_readable( $grammarDataFile ) ) {
3816 $data = FormatJson::decode(
3817 file_get_contents( $grammarDataFile ),
3818 true
3819 );
3820
3821 if ( $data === null ) {
3822 throw new MWException( "Invalid grammar data for \"$languageCode\"." );
3823 }
3824
3825 self::$grammarTransformations->set( $languageCode, $data );
3826 }
3827
3828 return $data;
3829 }
3830
3850 function gender( $gender, $forms ) {
3851 if ( !count( $forms ) ) {
3852 return '';
3853 }
3854 $forms = $this->preConvertPlural( $forms, 2 );
3855 if ( $gender === 'male' ) {
3856 return $forms[0];
3857 }
3858 if ( $gender === 'female' ) {
3859 return $forms[1];
3860 }
3861 return isset( $forms[2] ) ? $forms[2] : $forms[0];
3862 }
3863
3879 function convertPlural( $count, $forms ) {
3880 // Handle explicit n=pluralform cases
3881 $forms = $this->handleExplicitPluralForms( $count, $forms );
3882 if ( is_string( $forms ) ) {
3883 return $forms;
3884 }
3885 if ( !count( $forms ) ) {
3886 return '';
3887 }
3888
3889 $pluralForm = $this->getPluralRuleIndexNumber( $count );
3890 $pluralForm = min( $pluralForm, count( $forms ) - 1 );
3891 return $forms[$pluralForm];
3892 }
3893
3909 protected function handleExplicitPluralForms( $count, array $forms ) {
3910 foreach ( $forms as $index => $form ) {
3911 if ( preg_match( '/\d+=/i', $form ) ) {
3912 $pos = strpos( $form, '=' );
3913 if ( substr( $form, 0, $pos ) === (string)$count ) {
3914 return substr( $form, $pos + 1 );
3915 }
3916 unset( $forms[$index] );
3917 }
3918 }
3919 return array_values( $forms );
3920 }
3921
3930 protected function preConvertPlural( /* Array */ $forms, $count ) {
3931 while ( count( $forms ) < $count ) {
3932 $forms[] = $forms[count( $forms ) - 1];
3933 }
3934 return $forms;
3935 }
3936
3953 public function embedBidi( $text = '' ) {
3954 $dir = Language::strongDirFromContent( $text );
3955 if ( $dir === 'ltr' ) {
3956 // Wrap in LEFT-TO-RIGHT EMBEDDING ... POP DIRECTIONAL FORMATTING
3957 return self::$lre . $text . self::$pdf;
3958 }
3959 if ( $dir === 'rtl' ) {
3960 // Wrap in RIGHT-TO-LEFT EMBEDDING ... POP DIRECTIONAL FORMATTING
3961 return self::$rle . $text . self::$pdf;
3962 }
3963 // No strong directionality: do not wrap
3964 return $text;
3965 }
3966
3980 function translateBlockExpiry( $str, User $user = null, $now = 0 ) {
3981 $duration = SpecialBlock::getSuggestedDurations( $this );
3982 foreach ( $duration as $show => $value ) {
3983 if ( strcmp( $str, $value ) == 0 ) {
3984 return htmlspecialchars( trim( $show ) );
3985 }
3986 }
3987
3988 if ( wfIsInfinity( $str ) ) {
3989 foreach ( $duration as $show => $value ) {
3990 if ( wfIsInfinity( $value ) ) {
3991 return htmlspecialchars( trim( $show ) );
3992 }
3993 }
3994 }
3995
3996 // If all else fails, return a standard duration or timestamp description.
3997 $time = strtotime( $str, $now );
3998 if ( $time === false ) { // Unknown format. Return it as-is in case.
3999 return $str;
4000 } elseif ( $time !== strtotime( $str, $now + 1 ) ) { // It's a relative timestamp.
4001 // The result differs based on current time, so the difference
4002 // is a fixed duration length.
4003 return $this->formatDuration( $time - $now );
4004 } else { // It's an absolute timestamp.
4005 if ( $time === 0 ) {
4006 // wfTimestamp() handles 0 as current time instead of epoch.
4007 $time = '19700101000000';
4008 }
4009 if ( $user ) {
4010 return $this->userTimeAndDate( $time, $user );
4011 }
4012 return $this->timeanddate( $time );
4013 }
4014 }
4015
4023 public function segmentForDiff( $text ) {
4024 return $text;
4025 }
4026
4033 public function unsegmentForDiff( $text ) {
4034 return $text;
4035 }
4036
4043 public function getConverter() {
4044 return $this->mConverter;
4045 }
4046
4053 public function autoConvertToAllVariants( $text ) {
4054 return $this->mConverter->autoConvertToAllVariants( $text );
4055 }
4056
4063 public function convert( $text ) {
4064 return $this->mConverter->convert( $text );
4065 }
4066
4073 public function convertTitle( $title ) {
4074 return $this->mConverter->convertTitle( $title );
4075 }
4076
4083 public function convertNamespace( $ns ) {
4084 return $this->mConverter->convertNamespace( $ns );
4085 }
4086
4092 public function hasVariants() {
4093 return count( $this->getVariants() ) > 1;
4094 }
4095
4103 public function hasVariant( $variant ) {
4104 return (bool)$this->mConverter->validateVariant( $variant );
4105 }
4106
4114 public function convertHtml( $text, $isTitle = false ) {
4115 return htmlspecialchars( $this->convert( $text, $isTitle ) );
4116 }
4117
4122 public function convertCategoryKey( $key ) {
4123 return $this->mConverter->convertCategoryKey( $key );
4124 }
4125
4132 public function getVariants() {
4133 return $this->mConverter->getVariants();
4134 }
4135
4139 public function getPreferredVariant() {
4140 return $this->mConverter->getPreferredVariant();
4141 }
4142
4146 public function getDefaultVariant() {
4147 return $this->mConverter->getDefaultVariant();
4148 }
4149
4153 public function getURLVariant() {
4154 return $this->mConverter->getURLVariant();
4155 }
4156
4169 public function findVariantLink( &$link, &$nt, $ignoreOtherCond = false ) {
4170 $this->mConverter->findVariantLink( $link, $nt, $ignoreOtherCond );
4171 }
4172
4180 return $this->mConverter->getExtraHashOptions();
4181 }
4182
4190 public function getParsedTitle() {
4191 return $this->mConverter->getParsedTitle();
4192 }
4193
4200 public function updateConversionTable( Title $title ) {
4201 $this->mConverter->updateConversionTable( $title );
4202 }
4203
4216 public function markNoConversion( $text, $noParse = false ) {
4217 // Excluding protocal-relative URLs may avoid many false positives.
4218 if ( $noParse || preg_match( '/^(?:' . wfUrlProtocolsWithoutProtRel() . ')/', $text ) ) {
4219 return $this->mConverter->markNoConversion( $text );
4220 } else {
4221 return $text;
4222 }
4223 }
4224
4231 public function linkTrail() {
4232 return self::$dataCache->getItem( $this->mCode, 'linkTrail' );
4233 }
4234
4241 public function linkPrefixCharset() {
4242 return self::$dataCache->getItem( $this->mCode, 'linkPrefixCharset' );
4243 }
4244
4252 public function getParentLanguage() {
4253 if ( $this->mParentLanguage !== false ) {
4254 return $this->mParentLanguage;
4255 }
4256
4257 $code = explode( '-', $this->getCode() )[0];
4259 $this->mParentLanguage = null;
4260 return null;
4261 }
4263 if ( !$lang->hasVariant( $this->getCode() ) ) {
4264 $this->mParentLanguage = null;
4265 return null;
4266 }
4267
4268 $this->mParentLanguage = $lang;
4269 return $lang;
4270 }
4271
4279 public function equals( Language $lang ) {
4280 return $lang->getCode() === $this->mCode;
4281 }
4282
4291 public function getCode() {
4292 return $this->mCode;
4293 }
4294
4305 public function getHtmlCode() {
4306 if ( is_null( $this->mHtmlCode ) ) {
4307 $this->mHtmlCode = wfBCP47( $this->getCode() );
4308 }
4309 return $this->mHtmlCode;
4310 }
4311
4315 public function setCode( $code ) {
4316 $this->mCode = $code;
4317 // Ensure we don't leave incorrect cached data lying around
4318 $this->mHtmlCode = null;
4319 $this->mParentLanguage = false;
4320 }
4321
4329 public static function getCodeFromFileName( $filename, $prefix = 'Language', $suffix = '.php' ) {
4330 $m = null;
4331 preg_match( '/' . preg_quote( $prefix, '/' ) . '([A-Z][a-z_]+)' .
4332 preg_quote( $suffix, '/' ) . '/', $filename, $m );
4333 if ( !count( $m ) ) {
4334 return false;
4335 }
4336 return str_replace( '_', '-', strtolower( $m[1] ) );
4337 }
4338
4343 public static function classFromCode( $code ) {
4344 if ( $code == 'en' ) {
4345 return 'Language';
4346 } else {
4347 return 'Language' . str_replace( '-', '_', ucfirst( $code ) );
4348 }
4349 }
4350
4359 public static function getFileName( $prefix = 'Language', $code, $suffix = '.php' ) {
4360 if ( !self::isValidBuiltInCode( $code ) ) {
4361 throw new MWException( "Invalid language code \"$code\"" );
4362 }
4363
4364 return $prefix . str_replace( '-', '_', ucfirst( $code ) ) . $suffix;
4365 }
4366
4371 public static function getMessagesFileName( $code ) {
4372 global $IP;
4373 $file = self::getFileName( "$IP/languages/messages/Messages", $code, '.php' );
4374 Hooks::run( 'Language::getMessagesFileName', [ $code, &$file ] );
4375 return $file;
4376 }
4377
4384 public static function getJsonMessagesFileName( $code ) {
4385 global $IP;
4386
4387 if ( !self::isValidBuiltInCode( $code ) ) {
4388 throw new MWException( "Invalid language code \"$code\"" );
4389 }
4390
4391 return "$IP/languages/i18n/$code.json";
4392 }
4393
4401 public static function getFallbackFor( $code ) {
4402 $fallbacks = self::getFallbacksFor( $code );
4403 if ( $fallbacks ) {
4404 return $fallbacks[0];
4405 }
4406 return false;
4407 }
4408
4416 public static function getFallbacksFor( $code ) {
4417 if ( $code === 'en' || !Language::isValidBuiltInCode( $code ) ) {
4418 return [];
4419 }
4420 // For unknown languages, fallbackSequence returns an empty array,
4421 // hardcode fallback to 'en' in that case.
4422 return self::getLocalisationCache()->getItem( $code, 'fallbackSequence' ) ?: [ 'en' ];
4423 }
4424
4433 public static function getFallbacksIncludingSiteLanguage( $code ) {
4435
4436 // Usually, we will only store a tiny number of fallback chains, so we
4437 // keep them in static memory.
4438 $cacheKey = "{$code}-{$wgLanguageCode}";
4439
4440 if ( !array_key_exists( $cacheKey, self::$fallbackLanguageCache ) ) {
4441 $fallbacks = self::getFallbacksFor( $code );
4442
4443 // Append the site's fallback chain, including the site language itself
4444 $siteFallbacks = self::getFallbacksFor( $wgLanguageCode );
4445 array_unshift( $siteFallbacks, $wgLanguageCode );
4446
4447 // Eliminate any languages already included in the chain
4448 $siteFallbacks = array_diff( $siteFallbacks, $fallbacks );
4449
4450 self::$fallbackLanguageCache[$cacheKey] = [ $fallbacks, $siteFallbacks ];
4451 }
4452 return self::$fallbackLanguageCache[$cacheKey];
4453 }
4454
4464 public static function getMessagesFor( $code ) {
4465 return self::getLocalisationCache()->getItem( $code, 'messages' );
4466 }
4467
4476 public static function getMessageFor( $key, $code ) {
4477 return self::getLocalisationCache()->getSubitem( $code, 'messages', $key );
4478 }
4479
4488 public static function getMessageKeysFor( $code ) {
4489 return self::getLocalisationCache()->getSubitemList( $code, 'messages' );
4490 }
4491
4496 function fixVariableInNamespace( $talk ) {
4497 if ( strpos( $talk, '$1' ) === false ) {
4498 return $talk;
4499 }
4500
4502 $talk = str_replace( '$1', $wgMetaNamespace, $talk );
4503
4504 # Allow grammar transformations
4505 # Allowing full message-style parsing would make simple requests
4506 # such as action=raw much more expensive than they need to be.
4507 # This will hopefully cover most cases.
4508 $talk = preg_replace_callback( '/{{grammar:(.*?)\|(.*?)}}/i',
4509 [ $this, 'replaceGrammarInNamespace' ], $talk );
4510 return str_replace( ' ', '_', $talk );
4511 }
4512
4518 return $this->convertGrammar( trim( $m[2] ), trim( $m[1] ) );
4519 }
4520
4531 public function formatExpiry( $expiry, $format = true, $infinity = 'infinity' ) {
4532 static $dbInfinity;
4533 if ( $dbInfinity === null ) {
4534 $dbInfinity = wfGetDB( DB_SLAVE )->getInfinity();
4535 }
4536
4537 if ( $expiry == '' || $expiry === 'infinity' || $expiry == $dbInfinity ) {
4538 return $format === true
4539 ? $this->getMessageFromDB( 'infiniteblock' )
4540 : $infinity;
4541 } else {
4542 return $format === true
4543 ? $this->timeanddate( $expiry, /* User preference timezone */ true )
4544 : wfTimestamp( $format, $expiry );
4545 }
4546 }
4547
4561 function formatTimePeriod( $seconds, $format = [] ) {
4562 if ( !is_array( $format ) ) {
4563 $format = [ 'avoid' => $format ]; // For backwards compatibility
4564 }
4565 if ( !isset( $format['avoid'] ) ) {
4566 $format['avoid'] = false;
4567 }
4568 if ( !isset( $format['noabbrevs'] ) ) {
4569 $format['noabbrevs'] = false;
4570 }
4571 $secondsMsg = wfMessage(
4572 $format['noabbrevs'] ? 'seconds' : 'seconds-abbrev' )->inLanguage( $this );
4573 $minutesMsg = wfMessage(
4574 $format['noabbrevs'] ? 'minutes' : 'minutes-abbrev' )->inLanguage( $this );
4575 $hoursMsg = wfMessage(
4576 $format['noabbrevs'] ? 'hours' : 'hours-abbrev' )->inLanguage( $this );
4577 $daysMsg = wfMessage(
4578 $format['noabbrevs'] ? 'days' : 'days-abbrev' )->inLanguage( $this );
4579
4580 if ( round( $seconds * 10 ) < 100 ) {
4581 $s = $this->formatNum( sprintf( "%.1f", round( $seconds * 10 ) / 10 ) );
4582 $s = $secondsMsg->params( $s )->text();
4583 } elseif ( round( $seconds ) < 60 ) {
4584 $s = $this->formatNum( round( $seconds ) );
4585 $s = $secondsMsg->params( $s )->text();
4586 } elseif ( round( $seconds ) < 3600 ) {
4587 $minutes = floor( $seconds / 60 );
4588 $secondsPart = round( fmod( $seconds, 60 ) );
4589 if ( $secondsPart == 60 ) {
4590 $secondsPart = 0;
4591 $minutes++;
4592 }
4593 $s = $minutesMsg->params( $this->formatNum( $minutes ) )->text();
4594 $s .= ' ';
4595 $s .= $secondsMsg->params( $this->formatNum( $secondsPart ) )->text();
4596 } elseif ( round( $seconds ) <= 2 * 86400 ) {
4597 $hours = floor( $seconds / 3600 );
4598 $minutes = floor( ( $seconds - $hours * 3600 ) / 60 );
4599 $secondsPart = round( $seconds - $hours * 3600 - $minutes * 60 );
4600 if ( $secondsPart == 60 ) {
4601 $secondsPart = 0;
4602 $minutes++;
4603 }
4604 if ( $minutes == 60 ) {
4605 $minutes = 0;
4606 $hours++;
4607 }
4608 $s = $hoursMsg->params( $this->formatNum( $hours ) )->text();
4609 $s .= ' ';
4610 $s .= $minutesMsg->params( $this->formatNum( $minutes ) )->text();
4611 if ( !in_array( $format['avoid'], [ 'avoidseconds', 'avoidminutes' ] ) ) {
4612 $s .= ' ' . $secondsMsg->params( $this->formatNum( $secondsPart ) )->text();
4613 }
4614 } else {
4615 $days = floor( $seconds / 86400 );
4616 if ( $format['avoid'] === 'avoidminutes' ) {
4617 $hours = round( ( $seconds - $days * 86400 ) / 3600 );
4618 if ( $hours == 24 ) {
4619 $hours = 0;
4620 $days++;
4621 }
4622 $s = $daysMsg->params( $this->formatNum( $days ) )->text();
4623 $s .= ' ';
4624 $s .= $hoursMsg->params( $this->formatNum( $hours ) )->text();
4625 } elseif ( $format['avoid'] === 'avoidseconds' ) {
4626 $hours = floor( ( $seconds - $days * 86400 ) / 3600 );
4627 $minutes = round( ( $seconds - $days * 86400 - $hours * 3600 ) / 60 );
4628 if ( $minutes == 60 ) {
4629 $minutes = 0;
4630 $hours++;
4631 }
4632 if ( $hours == 24 ) {
4633 $hours = 0;
4634 $days++;
4635 }
4636 $s = $daysMsg->params( $this->formatNum( $days ) )->text();
4637 $s .= ' ';
4638 $s .= $hoursMsg->params( $this->formatNum( $hours ) )->text();
4639 $s .= ' ';
4640 $s .= $minutesMsg->params( $this->formatNum( $minutes ) )->text();
4641 } else {
4642 $s = $daysMsg->params( $this->formatNum( $days ) )->text();
4643 $s .= ' ';
4644 $s .= $this->formatTimePeriod( $seconds - $days * 86400, $format );
4645 }
4646 }
4647 return $s;
4648 }
4649
4661 function formatBitrate( $bps ) {
4662 return $this->formatComputingNumbers( $bps, 1000, "bitrate-$1bits" );
4663 }
4664
4671 function formatComputingNumbers( $size, $boundary, $messageKey ) {
4672 if ( $size <= 0 ) {
4673 return str_replace( '$1', $this->formatNum( $size ),
4674 $this->getMessageFromDB( str_replace( '$1', '', $messageKey ) )
4675 );
4676 }
4677 $sizes = [ '', 'kilo', 'mega', 'giga', 'tera', 'peta', 'exa', 'zeta', 'yotta' ];
4678 $index = 0;
4679
4680 $maxIndex = count( $sizes ) - 1;
4681 while ( $size >= $boundary && $index < $maxIndex ) {
4682 $index++;
4683 $size /= $boundary;
4684 }
4685
4686 // For small sizes no decimal places necessary
4687 $round = 0;
4688 if ( $index > 1 ) {
4689 // For MB and bigger two decimal places are smarter
4690 $round = 2;
4691 }
4692 $msg = str_replace( '$1', $sizes[$index], $messageKey );
4693
4694 $size = round( $size, $round );
4695 $text = $this->getMessageFromDB( $msg );
4696 return str_replace( '$1', $this->formatNum( $size ), $text );
4697 }
4698
4709 function formatSize( $size ) {
4710 return $this->formatComputingNumbers( $size, 1024, "size-$1bytes" );
4711 }
4712
4722 function specialList( $page, $details, $oppositedm = true ) {
4723 if ( !$details ) {
4724 return $page;
4725 }
4726
4727 $dirmark = ( $oppositedm ? $this->getDirMark( true ) : '' ) . $this->getDirMark();
4728 return
4729 $page .
4730 $dirmark .
4731 $this->msg( 'word-separator' )->escaped() .
4732 $this->msg( 'parentheses' )->rawParams( $details )->escaped();
4733 }
4734
4745 public function viewPrevNext( Title $title, $offset, $limit,
4746 array $query = [], $atend = false
4747 ) {
4748 // @todo FIXME: Why on earth this needs one message for the text and another one for tooltip?
4749
4750 # Make 'previous' link
4751 $prev = wfMessage( 'prevn' )->inLanguage( $this )->title( $title )->numParams( $limit )->text();
4752 if ( $offset > 0 ) {
4753 $plink = $this->numLink( $title, max( $offset - $limit, 0 ), $limit,
4754 $query, $prev, 'prevn-title', 'mw-prevlink' );
4755 } else {
4756 $plink = htmlspecialchars( $prev );
4757 }
4758
4759 # Make 'next' link
4760 $next = wfMessage( 'nextn' )->inLanguage( $this )->title( $title )->numParams( $limit )->text();
4761 if ( $atend ) {
4762 $nlink = htmlspecialchars( $next );
4763 } else {
4764 $nlink = $this->numLink( $title, $offset + $limit, $limit,
4765 $query, $next, 'nextn-title', 'mw-nextlink' );
4766 }
4767
4768 # Make links to set number of items per page
4769 $numLinks = [];
4770 foreach ( [ 20, 50, 100, 250, 500 ] as $num ) {
4771 $numLinks[] = $this->numLink( $title, $offset, $num,
4772 $query, $this->formatNum( $num ), 'shown-title', 'mw-numlink' );
4773 }
4774
4775 return wfMessage( 'viewprevnext' )->inLanguage( $this )->title( $title
4776 )->rawParams( $plink, $nlink, $this->pipeList( $numLinks ) )->escaped();
4777 }
4778
4791 private function numLink( Title $title, $offset, $limit, array $query, $link,
4792 $tooltipMsg, $class
4793 ) {
4794 $query = [ 'limit' => $limit, 'offset' => $offset ] + $query;
4795 $tooltip = wfMessage( $tooltipMsg )->inLanguage( $this )->title( $title )
4796 ->numParams( $limit )->text();
4797
4798 return Html::element( 'a', [ 'href' => $title->getLocalURL( $query ),
4799 'title' => $tooltip, 'class' => $class ], $link );
4800 }
4801
4807 public function getConvRuleTitle() {
4808 return $this->mConverter->getConvRuleTitle();
4809 }
4810
4816 public function getCompiledPluralRules() {
4817 $pluralRules = self::$dataCache->getItem( strtolower( $this->mCode ), 'compiledPluralRules' );
4818 $fallbacks = Language::getFallbacksFor( $this->mCode );
4819 if ( !$pluralRules ) {
4820 foreach ( $fallbacks as $fallbackCode ) {
4821 $pluralRules = self::$dataCache->getItem( strtolower( $fallbackCode ), 'compiledPluralRules' );
4822 if ( $pluralRules ) {
4823 break;
4824 }
4825 }
4826 }
4827 return $pluralRules;
4828 }
4829
4835 public function getPluralRules() {
4836 $pluralRules = self::$dataCache->getItem( strtolower( $this->mCode ), 'pluralRules' );
4837 $fallbacks = Language::getFallbacksFor( $this->mCode );
4838 if ( !$pluralRules ) {
4839 foreach ( $fallbacks as $fallbackCode ) {
4840 $pluralRules = self::$dataCache->getItem( strtolower( $fallbackCode ), 'pluralRules' );
4841 if ( $pluralRules ) {
4842 break;
4843 }
4844 }
4845 }
4846 return $pluralRules;
4847 }
4848
4854 public function getPluralRuleTypes() {
4855 $pluralRuleTypes = self::$dataCache->getItem( strtolower( $this->mCode ), 'pluralRuleTypes' );
4856 $fallbacks = Language::getFallbacksFor( $this->mCode );
4857 if ( !$pluralRuleTypes ) {
4858 foreach ( $fallbacks as $fallbackCode ) {
4859 $pluralRuleTypes = self::$dataCache->getItem( strtolower( $fallbackCode ), 'pluralRuleTypes' );
4860 if ( $pluralRuleTypes ) {
4861 break;
4862 }
4863 }
4864 }
4865 return $pluralRuleTypes;
4866 }
4867
4873 public function getPluralRuleIndexNumber( $number ) {
4874 $pluralRules = $this->getCompiledPluralRules();
4875 $form = Evaluator::evaluateCompiled( $number, $pluralRules );
4876 return $form;
4877 }
4878
4887 public function getPluralRuleType( $number ) {
4888 $index = $this->getPluralRuleIndexNumber( $number );
4889 $pluralRuleTypes = $this->getPluralRuleTypes();
4890 if ( isset( $pluralRuleTypes[$index] ) ) {
4891 return $pluralRuleTypes[$index];
4892 } else {
4893 return 'other';
4894 }
4895 }
4896}
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...
const DB_SLAVE
Definition Defines.php:34
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.
$namespaceAliases
$namespaceNames
$digitGroupingPattern
$wgUser
Definition Setup.php:781
$IP
Definition WebStart.php:58
A fake language converter.
Simple store for keeping values in an associative array for the current process.
Base class for language conversion.
static array $languagesWithVariants
languages supporting variants
Internationalisation code.
Definition Language.php:35
hasVariants()
Check if this is a language with variants.
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:283
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:362
recodeForEdit( $s)
$mParentLanguage
Definition Language.php:43
getWeekdayName( $key)
Definition Language.php:967
getHebrewCalendarMonthName( $key)
Definition Language.php:991
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:948
getHebrewCalendarMonthNameGen( $key)
Definition Language.php:999
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:255
hasVariant( $variant)
Check if the language has the specific variant.
setCode( $code)
transformUsingPairFile( $file, $string)
Transform a string using serialized data stored in the given file (which must be in the serialized su...
formatDuration( $seconds, array $chosenIntervals=[])
Takes a number of seconds and turns it into a text using values such as hours and minutes.
getMessage( $key)
getHijriCalendarMonthName( $key)
static fetchLanguageName( $code, $inLanguage=null, $include='all')
Definition Language.php:891
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:903
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:928
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:940
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.
embedBidi( $text='')
Wraps argument with unicode control characters for directionality safety.
getMonthAbbreviationsArray()
Definition Language.php:955
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:983
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:975
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:921
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:913
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:337
static newFromCode( $code)
Create a language object for a given language code.
Definition Language.php:209
static $mMonthMsgs
Definition Language.php:73
$dateFormatStrings
Definition Language.php:45
static romanNumeral( $num)
Roman number formatting up to 10000.
static classFromCode( $code)
alignStart()
Return 'left' or 'right' as appropriate alignment for line-start for this language's text direction.
static hebrewYearStart( $year)
This calculates the Hebrew year start, as days since 1 September.
getCompiledPluralRules()
Get the compiled plural rules for the language.
Class for caching the contents of localisation files, Messages*.php and *.i18n.php.
MediaWiki exception.
Library for creating and parsing MW-style timestamps.
Handles a simple LRU key/value map with a maximum number of entries.
static getTitleInvalidRegex()
Returns a simple regex that will match on characters and sequences invalid in titles.
Wrapper around strtr() that holds replacements.
static getMain()
Static methods.
static getSuggestedDurations( $lang=null)
Get an array of suggested block durations from MediaWiki:Ipboptions.
static isUtf8( $value)
Test whether a string is valid UTF-8.
Represents a title within MediaWiki.
Definition Title.php:39
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition User.php:50
=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:64
const NS_PROJECT_TALK
Definition Defines.php:67
const NS_USER_TALK
Definition Defines.php:65
const NS_PROJECT
Definition Defines.php:66
the array() calling protocol came about after MediaWiki 1.4rc1.
please add to it if you re going to add events to the MediaWiki code where normally authentication against an external auth plugin would be creating a local account $user
Definition hooks.txt:249
see documentation in includes Linker php for Linker::makeImageLink & $time
Definition hooks.txt:1769
do that in ParserLimitReportFormat instead use this to modify the parameters of the image and a DIV can begin in one section and end in another Make sure your code can handle that case gracefully See the EditSectionClearerLink extension for an example zero but section is usually empty its values are the globals values before the output is cached $page
Definition hooks.txt:2578
We ve cleaned up the code here by removing clumps of infrequently used code and moving them off somewhere else It s much easier for someone working with this code to see what s _really_ going and make changes or fix bugs In we can take all the code that deals with the little used title reversing options(say) and put it in one place. Instead of having little title-reversing if-blocks spread all over the codebase in showAnArticle
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context the output can only depend on parameters provided to this hook not on global state indicating whether full HTML should be generated If generation of HTML may be but other information should still be present in the ParserOutput object to manipulate or replace but no entry for that model exists in $wgContentHandlers please use GetContentModels hook to make them known to core if desired whether it is OK to use $contentModel on $title Handler functions that modify $ok should generally return false to prevent further hooks from further modifying $ok inclusive $limit
Definition hooks.txt:1143
This code would result in ircNotify being run twice when an article is and once for brion Hooks can return three possible true was required This is the default since MediaWiki *some string
Definition hooks.txt:183
namespace and then decline to actually register it & $namespaces
Definition hooks.txt:934
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context $options
Definition hooks.txt:1102
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:865
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books $tag
Definition hooks.txt:1033
namespace and then decline to actually register it file or subcat img or subcat $title
Definition hooks.txt:964
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:1967
do that in ParserLimitReportFormat instead use this to modify the parameters of the image and a DIV can begin in one section and end in another Make sure your code can handle that case gracefully See the EditSectionClearerLink extension for an example zero but section is usually empty its values are the globals values before the output is cached my talk my contributions etc etc otherwise the built in rate limiting checks are if enabled allows for interception of redirect as a string mapping parameter names to values & $type
Definition hooks.txt:2604
if the prop value should be in the metadata multi language array format
Definition hooks.txt:1637
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:1966
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:864
usually copyright or history_copyright This message must be in HTML not wikitext & $link
Definition hooks.txt:2937
Allows to change the fields on the form that will be generated $name
Definition hooks.txt:304
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:1601
returning false will NOT prevent logging $e
Definition hooks.txt:2127
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring which defines all default service and specifies how they depend on each other("wiring"). When a new service is added to MediaWiki core
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition injection.txt:37
$cache
Definition mcc.php:33
A helper class for throttling authentication attempts.
if(!isset( $args[0])) $lang