MediaWiki REL1_27
Language.php
Go to the documentation of this file.
1<?php
28if ( !defined( 'MEDIAWIKI' ) ) {
29 echo "This file is part of MediaWiki, it is not a valid entry point.\n";
30 exit( 1 );
31}
32
33use CLDRPluralRuleParser\Evaluator;
34
39class Language {
44
45 public $mVariants, $mCode, $mLoaded = false;
46 public $mMagicExtensions = [], $mMagicHookDone = false;
47 private $mHtmlCode = null, $mParentLanguage = false;
48
49 public $dateFormatStrings = [];
51
53
57 public $transformData = [];
58
62 static public $dataCache;
63
64 static public $mLangObjCache = [];
65
66 static public $mWeekdayMsgs = [
67 'sunday', 'monday', 'tuesday', 'wednesday', 'thursday',
68 'friday', 'saturday'
69 ];
70
71 static public $mWeekdayAbbrevMsgs = [
72 'sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'
73 ];
74
75 static public $mMonthMsgs = [
76 'january', 'february', 'march', 'april', 'may_long', 'june',
77 'july', 'august', 'september', 'october', 'november',
78 'december'
79 ];
80 static public $mMonthGenMsgs = [
81 'january-gen', 'february-gen', 'march-gen', 'april-gen', 'may-gen', 'june-gen',
82 'july-gen', 'august-gen', 'september-gen', 'october-gen', 'november-gen',
83 'december-gen'
84 ];
85 static public $mMonthAbbrevMsgs = [
86 'jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug',
87 'sep', 'oct', 'nov', 'dec'
88 ];
89
91 'iranian-calendar-m1', 'iranian-calendar-m2', 'iranian-calendar-m3',
92 'iranian-calendar-m4', 'iranian-calendar-m5', 'iranian-calendar-m6',
93 'iranian-calendar-m7', 'iranian-calendar-m8', 'iranian-calendar-m9',
94 'iranian-calendar-m10', 'iranian-calendar-m11', 'iranian-calendar-m12'
95 ];
96
97 static public $mHebrewCalendarMonthMsgs = [
98 'hebrew-calendar-m1', 'hebrew-calendar-m2', 'hebrew-calendar-m3',
99 'hebrew-calendar-m4', 'hebrew-calendar-m5', 'hebrew-calendar-m6',
100 'hebrew-calendar-m7', 'hebrew-calendar-m8', 'hebrew-calendar-m9',
101 'hebrew-calendar-m10', 'hebrew-calendar-m11', 'hebrew-calendar-m12',
102 'hebrew-calendar-m6a', 'hebrew-calendar-m6b'
103 ];
104
106 'hebrew-calendar-m1-gen', 'hebrew-calendar-m2-gen', 'hebrew-calendar-m3-gen',
107 'hebrew-calendar-m4-gen', 'hebrew-calendar-m5-gen', 'hebrew-calendar-m6-gen',
108 'hebrew-calendar-m7-gen', 'hebrew-calendar-m8-gen', 'hebrew-calendar-m9-gen',
109 'hebrew-calendar-m10-gen', 'hebrew-calendar-m11-gen', 'hebrew-calendar-m12-gen',
110 'hebrew-calendar-m6a-gen', 'hebrew-calendar-m6b-gen'
111 ];
112
113 static public $mHijriCalendarMonthMsgs = [
114 'hijri-calendar-m1', 'hijri-calendar-m2', 'hijri-calendar-m3',
115 'hijri-calendar-m4', 'hijri-calendar-m5', 'hijri-calendar-m6',
116 'hijri-calendar-m7', 'hijri-calendar-m8', 'hijri-calendar-m9',
117 'hijri-calendar-m10', 'hijri-calendar-m11', 'hijri-calendar-m12'
118 ];
119
124 static public $durationIntervals = [
125 'millennia' => 31556952000,
126 'centuries' => 3155695200,
127 'decades' => 315569520,
128 'years' => 31556952, // 86400 * ( 365 + ( 24 * 3 + 25 ) / 400 )
129 'weeks' => 604800,
130 'days' => 86400,
131 'hours' => 3600,
132 'minutes' => 60,
133 'seconds' => 1,
134 ];
135
142 static private $fallbackLanguageCache = [];
143
148 static private $languageNameCache;
149
153 static private $lre = "\xE2\x80\xAA"; // U+202A LEFT-TO-RIGHT EMBEDDING
154 static private $rle = "\xE2\x80\xAB"; // U+202B RIGHT-TO-LEFT EMBEDDING
155 static private $pdf = "\xE2\x80\xAC"; // U+202C POP DIRECTIONAL FORMATTING
156
168 // @codingStandardsIgnoreStart
169 // @codeCoverageIgnoreStart
170 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';
171 // @codeCoverageIgnoreEnd
172 // @codingStandardsIgnoreEnd
173
179 static function factory( $code ) {
181
182 if ( isset( $wgDummyLanguageCodes[$code] ) ) {
184 }
185
186 // get the language object to process
187 $langObj = isset( self::$mLangObjCache[$code] )
188 ? self::$mLangObjCache[$code]
189 : self::newFromCode( $code );
190
191 // merge the language object in to get it up front in the cache
192 self::$mLangObjCache = array_merge( [ $code => $langObj ], self::$mLangObjCache );
193 // get rid of the oldest ones in case we have an overflow
194 self::$mLangObjCache = array_slice( self::$mLangObjCache, 0, $wgLangObjCacheSize, true );
195
196 return $langObj;
197 }
198
205 protected static function newFromCode( $code ) {
206 if ( !Language::isValidCode( $code ) ) {
207 throw new MWException( "Invalid language code \"$code\"" );
208 }
209
211 // It's not possible to customise this code with class files, so
212 // just return a Language object. This is to support uselang= hacks.
213 $lang = new Language;
214 $lang->setCode( $code );
215 return $lang;
216 }
217
218 // Check if there is a language class for the code
219 $class = self::classFromCode( $code );
220 if ( class_exists( $class ) ) {
221 $lang = new $class;
222 return $lang;
223 }
224
225 // Keep trying the fallback list until we find an existing class
226 $fallbacks = Language::getFallbacksFor( $code );
227 foreach ( $fallbacks as $fallbackCode ) {
228 if ( !Language::isValidBuiltInCode( $fallbackCode ) ) {
229 throw new MWException( "Invalid fallback '$fallbackCode' in fallback sequence for '$code'" );
230 }
231
232 $class = self::classFromCode( $fallbackCode );
233 if ( class_exists( $class ) ) {
234 $lang = new $class;
235 $lang->setCode( $code );
236 return $lang;
237 }
238 }
239
240 throw new MWException( "Invalid fallback sequence for language '$code'" );
241 }
242
251 public static function isSupportedLanguage( $code ) {
252 if ( !self::isValidBuiltInCode( $code ) ) {
253 return false;
254 }
255
256 if ( $code === 'qqq' ) {
257 return false;
258 }
259
260 return is_readable( self::getMessagesFileName( $code ) ) ||
261 is_readable( self::getJsonMessagesFileName( $code ) );
262 }
263
279 public static function isWellFormedLanguageTag( $code, $lenient = false ) {
280 $alpha = '[a-z]';
281 $digit = '[0-9]';
282 $alphanum = '[a-z0-9]';
283 $x = 'x'; # private use singleton
284 $singleton = '[a-wy-z]'; # other singleton
285 $s = $lenient ? '[-_]' : '-';
286
287 $language = "$alpha{2,8}|$alpha{2,3}$s$alpha{3}";
288 $script = "$alpha{4}"; # ISO 15924
289 $region = "(?:$alpha{2}|$digit{3})"; # ISO 3166-1 alpha-2 or UN M.49
290 $variant = "(?:$alphanum{5,8}|$digit$alphanum{3})";
291 $extension = "$singleton(?:$s$alphanum{2,8})+";
292 $privateUse = "$x(?:$s$alphanum{1,8})+";
293
294 # Define certain grandfathered codes, since otherwise the regex is pretty useless.
295 # Since these are limited, this is safe even later changes to the registry --
296 # the only oddity is that it might change the type of the tag, and thus
297 # the results from the capturing groups.
298 # http://www.iana.org/assignments/language-subtag-registry
299
300 $grandfathered = "en{$s}GB{$s}oed"
301 . "|i{$s}(?:ami|bnn|default|enochian|hak|klingon|lux|mingo|navajo|pwn|tao|tay|tsu)"
302 . "|no{$s}(?:bok|nyn)"
303 . "|sgn{$s}(?:BE{$s}(?:fr|nl)|CH{$s}de)"
304 . "|zh{$s}min{$s}nan";
305
306 $variantList = "$variant(?:$s$variant)*";
307 $extensionList = "$extension(?:$s$extension)*";
308
309 $langtag = "(?:($language)"
310 . "(?:$s$script)?"
311 . "(?:$s$region)?"
312 . "(?:$s$variantList)?"
313 . "(?:$s$extensionList)?"
314 . "(?:$s$privateUse)?)";
315
316 # The final breakdown, with capturing groups for each of these components
317 # The variants, extensions, grandfathered, and private-use may have interior '-'
318
319 $root = "^(?:$langtag|$privateUse|$grandfathered)$";
320
321 return (bool)preg_match( "/$root/", strtolower( $code ) );
322 }
323
333 public static function isValidCode( $code ) {
334 static $cache = [];
335 if ( !isset( $cache[$code] ) ) {
336 // People think language codes are html safe, so enforce it.
337 // Ideally we should only allow a-zA-Z0-9-
338 // but, .+ and other chars are often used for {{int:}} hacks
339 // see bugs 37564, 37587, 36938
340 $cache[$code] =
341 // Protect against path traversal
342 strcspn( $code, ":/\\\000&<>'\"" ) === strlen( $code )
344 }
345 return $cache[$code];
346 }
347
358 public static function isValidBuiltInCode( $code ) {
359
360 if ( !is_string( $code ) ) {
361 if ( is_object( $code ) ) {
362 $addmsg = " of class " . get_class( $code );
363 } else {
364 $addmsg = '';
365 }
366 $type = gettype( $code );
367 throw new MWException( __METHOD__ . " must be passed a string, $type given$addmsg" );
368 }
369
370 return (bool)preg_match( '/^[a-z0-9-]{2,}$/', $code );
371 }
372
381 public static function isKnownLanguageTag( $tag ) {
382 // Quick escape for invalid input to avoid exceptions down the line
383 // when code tries to process tags which are not valid at all.
384 if ( !self::isValidBuiltInCode( $tag ) ) {
385 return false;
386 }
387
388 if ( isset( MediaWiki\Languages\Data\Names::$names[$tag] )
389 || self::fetchLanguageName( $tag, $tag ) !== ''
390 ) {
391 return true;
392 }
393
394 return false;
395 }
396
402 public static function getLocalisationCache() {
403 if ( is_null( self::$dataCache ) ) {
405 $class = $wgLocalisationCacheConf['class'];
406 self::$dataCache = new $class( $wgLocalisationCacheConf );
407 }
408 return self::$dataCache;
409 }
410
411 function __construct() {
412 $this->mConverter = new FakeConverter( $this );
413 // Set the code to the name of the descendant
414 if ( get_class( $this ) == 'Language' ) {
415 $this->mCode = 'en';
416 } else {
417 $this->mCode = str_replace( '_', '-', strtolower( substr( get_class( $this ), 8 ) ) );
418 }
419 self::getLocalisationCache();
420 }
421
425 function __destruct() {
426 foreach ( $this as $name => $value ) {
427 unset( $this->$name );
428 }
429 }
430
435 function initContLang() {
436 }
437
442 public function getFallbackLanguages() {
443 return self::getFallbacksFor( $this->mCode );
444 }
445
450 public function getBookstoreList() {
451 return self::$dataCache->getItem( $this->mCode, 'bookstoreList' );
452 }
453
460 public function getNamespaces() {
461 if ( is_null( $this->namespaceNames ) ) {
463
464 $this->namespaceNames = self::$dataCache->getItem( $this->mCode, 'namespaceNames' );
465 $validNamespaces = MWNamespace::getCanonicalNamespaces();
466
467 $this->namespaceNames = $wgExtraNamespaces + $this->namespaceNames + $validNamespaces;
468
469 $this->namespaceNames[NS_PROJECT] = $wgMetaNamespace;
470 if ( $wgMetaNamespaceTalk ) {
471 $this->namespaceNames[NS_PROJECT_TALK] = $wgMetaNamespaceTalk;
472 } else {
473 $talk = $this->namespaceNames[NS_PROJECT_TALK];
474 $this->namespaceNames[NS_PROJECT_TALK] =
475 $this->fixVariableInNamespace( $talk );
476 }
477
478 # Sometimes a language will be localised but not actually exist on this wiki.
479 foreach ( $this->namespaceNames as $key => $text ) {
480 if ( !isset( $validNamespaces[$key] ) ) {
481 unset( $this->namespaceNames[$key] );
482 }
483 }
484
485 # The above mixing may leave namespaces out of canonical order.
486 # Re-order by namespace ID number...
487 ksort( $this->namespaceNames );
488
489 Hooks::run( 'LanguageGetNamespaces', [ &$this->namespaceNames ] );
490 }
491
493 }
494
499 public function setNamespaces( array $namespaces ) {
500 $this->namespaceNames = $namespaces;
501 $this->mNamespaceIds = null;
502 }
503
507 public function resetNamespaces() {
508 $this->namespaceNames = null;
509 $this->mNamespaceIds = null;
510 $this->namespaceAliases = null;
511 }
512
519 public function getFormattedNamespaces() {
520 $ns = $this->getNamespaces();
521 foreach ( $ns as $k => $v ) {
522 $ns[$k] = strtr( $v, '_', ' ' );
523 }
524 return $ns;
525 }
526
538 public function getNsText( $index ) {
539 $ns = $this->getNamespaces();
540 return isset( $ns[$index] ) ? $ns[$index] : false;
541 }
542
556 public function getFormattedNsText( $index ) {
557 $ns = $this->getNsText( $index );
558 return strtr( $ns, '_', ' ' );
559 }
560
569 public function getGenderNsText( $index, $gender ) {
571
573 (array)self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' );
574
575 return isset( $ns[$index][$gender] ) ? $ns[$index][$gender] : $this->getNsText( $index );
576 }
577
584 public function needsGenderDistinction() {
586 if ( count( $wgExtraGenderNamespaces ) > 0 ) {
587 // $wgExtraGenderNamespaces overrides everything
588 return true;
589 } elseif ( isset( $wgExtraNamespaces[NS_USER] ) && isset( $wgExtraNamespaces[NS_USER_TALK] ) ) {
591 // $wgExtraNamespaces overrides any gender aliases specified in i18n files
592 return false;
593 } else {
594 // Check what is in i18n files
595 $aliases = self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' );
596 return count( $aliases ) > 0;
597 }
598 }
599
608 function getLocalNsIndex( $text ) {
609 $lctext = $this->lc( $text );
610 $ids = $this->getNamespaceIds();
611 return isset( $ids[$lctext] ) ? $ids[$lctext] : false;
612 }
613
617 public function getNamespaceAliases() {
618 if ( is_null( $this->namespaceAliases ) ) {
619 $aliases = self::$dataCache->getItem( $this->mCode, 'namespaceAliases' );
620 if ( !$aliases ) {
621 $aliases = [];
622 } else {
623 foreach ( $aliases as $name => $index ) {
624 if ( $index === NS_PROJECT_TALK ) {
625 unset( $aliases[$name] );
627 $aliases[$name] = $index;
628 }
629 }
630 }
631
633 $genders = $wgExtraGenderNamespaces +
634 (array)self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' );
635 foreach ( $genders as $index => $forms ) {
636 foreach ( $forms as $alias ) {
637 $aliases[$alias] = $index;
638 }
639 }
640
641 # Also add converted namespace names as aliases, to avoid confusion.
642 $convertedNames = [];
643 foreach ( $this->getVariants() as $variant ) {
644 if ( $variant === $this->mCode ) {
645 continue;
646 }
647 foreach ( $this->getNamespaces() as $ns => $_ ) {
648 $convertedNames[$this->getConverter()->convertNamespace( $ns, $variant )] = $ns;
649 }
650 }
651
652 $this->namespaceAliases = $aliases + $convertedNames;
653 }
654
656 }
657
661 public function getNamespaceIds() {
662 if ( is_null( $this->mNamespaceIds ) ) {
664 # Put namespace names and aliases into a hashtable.
665 # If this is too slow, then we should arrange it so that it is done
666 # before caching. The catch is that at pre-cache time, the above
667 # class-specific fixup hasn't been done.
668 $this->mNamespaceIds = [];
669 foreach ( $this->getNamespaces() as $index => $name ) {
670 $this->mNamespaceIds[$this->lc( $name )] = $index;
671 }
672 foreach ( $this->getNamespaceAliases() as $name => $index ) {
673 $this->mNamespaceIds[$this->lc( $name )] = $index;
674 }
675 if ( $wgNamespaceAliases ) {
676 foreach ( $wgNamespaceAliases as $name => $index ) {
677 $this->mNamespaceIds[$this->lc( $name )] = $index;
678 }
679 }
680 }
681 return $this->mNamespaceIds;
682 }
683
691 public function getNsIndex( $text ) {
692 $lctext = $this->lc( $text );
693 $ns = MWNamespace::getCanonicalIndex( $lctext );
694 if ( $ns !== null ) {
695 return $ns;
696 }
697 $ids = $this->getNamespaceIds();
698 return isset( $ids[$lctext] ) ? $ids[$lctext] : false;
699 }
700
708 public function getVariantname( $code, $usemsg = true ) {
709 $msg = "variantname-$code";
710 if ( $usemsg && wfMessage( $msg )->exists() ) {
711 return $this->getMessageFromDB( $msg );
712 }
713 $name = self::fetchLanguageName( $code );
714 if ( $name ) {
715 return $name; # if it's defined as a language name, show that
716 } else {
717 # otherwise, output the language code
718 return $code;
719 }
720 }
721
725 public function getDatePreferences() {
726 return self::$dataCache->getItem( $this->mCode, 'datePreferences' );
727 }
728
732 function getDateFormats() {
733 return self::$dataCache->getItem( $this->mCode, 'dateFormats' );
734 }
735
739 public function getDefaultDateFormat() {
740 $df = self::$dataCache->getItem( $this->mCode, 'defaultDateFormat' );
741 if ( $df === 'dmy or mdy' ) {
742 global $wgAmericanDates;
743 return $wgAmericanDates ? 'mdy' : 'dmy';
744 } else {
745 return $df;
746 }
747 }
748
752 public function getDatePreferenceMigrationMap() {
753 return self::$dataCache->getItem( $this->mCode, 'datePreferenceMigrationMap' );
754 }
755
760 function getImageFile( $image ) {
761 return self::$dataCache->getSubitem( $this->mCode, 'imageFiles', $image );
762 }
763
768 public function getImageFiles() {
769 return self::$dataCache->getItem( $this->mCode, 'imageFiles' );
770 }
771
775 public function getExtraUserToggles() {
776 return (array)self::$dataCache->getItem( $this->mCode, 'extraUserToggles' );
777 }
778
783 function getUserToggle( $tog ) {
784 return $this->getMessageFromDB( "tog-$tog" );
785 }
786
798 public static function fetchLanguageNames( $inLanguage = null, $include = 'mw' ) {
799 $cacheKey = $inLanguage === null ? 'null' : $inLanguage;
800 $cacheKey .= ":$include";
801 if ( self::$languageNameCache === null ) {
802 self::$languageNameCache = new HashBagOStuff( [ 'maxKeys' => 20 ] );
803 }
804
805 $ret = self::$languageNameCache->get( $cacheKey );
806 if ( !$ret ) {
807 $ret = self::fetchLanguageNamesUncached( $inLanguage, $include );
808 self::$languageNameCache->set( $cacheKey, $ret );
809 }
810 return $ret;
811 }
812
823 private static function fetchLanguageNamesUncached( $inLanguage = null, $include = 'mw' ) {
824 global $wgExtraLanguageNames;
825
826 // If passed an invalid language code to use, fallback to en
827 if ( $inLanguage !== null && !Language::isValidCode( $inLanguage ) ) {
828 $inLanguage = 'en';
829 }
830
831 $names = [];
832
833 if ( $inLanguage ) {
834 # TODO: also include when $inLanguage is null, when this code is more efficient
835 Hooks::run( 'LanguageGetTranslatedLanguageNames', [ &$names, $inLanguage ] );
836 }
837
838 $mwNames = $wgExtraLanguageNames + MediaWiki\Languages\Data\Names::$names;
839 foreach ( $mwNames as $mwCode => $mwName ) {
840 # - Prefer own MediaWiki native name when not using the hook
841 # - For other names just add if not added through the hook
842 if ( $mwCode === $inLanguage || !isset( $names[$mwCode] ) ) {
843 $names[$mwCode] = $mwName;
844 }
845 }
846
847 if ( $include === 'all' ) {
848 ksort( $names );
849 return $names;
850 }
851
852 $returnMw = [];
853 $coreCodes = array_keys( $mwNames );
854 foreach ( $coreCodes as $coreCode ) {
855 $returnMw[$coreCode] = $names[$coreCode];
856 }
857
858 if ( $include === 'mwfile' ) {
859 $namesMwFile = [];
860 # We do this using a foreach over the codes instead of a directory
861 # loop so that messages files in extensions will work correctly.
862 foreach ( $returnMw as $code => $value ) {
863 if ( is_readable( self::getMessagesFileName( $code ) )
864 || is_readable( self::getJsonMessagesFileName( $code ) )
865 ) {
866 $namesMwFile[$code] = $names[$code];
867 }
868 }
869
870 ksort( $namesMwFile );
871 return $namesMwFile;
872 }
873
874 ksort( $returnMw );
875 # 'mw' option; default if it's not one of the other two options (all/mwfile)
876 return $returnMw;
877 }
878
886 public static function fetchLanguageName( $code, $inLanguage = null, $include = 'all' ) {
887 $code = strtolower( $code );
888 $array = self::fetchLanguageNames( $inLanguage, $include );
889 return !array_key_exists( $code, $array ) ? '' : $array[$code];
890 }
891
898 public function getMessageFromDB( $msg ) {
899 return $this->msg( $msg )->text();
900 }
901
908 protected function msg( $msg ) {
909 return wfMessage( $msg )->inLanguage( $this );
910 }
911
916 public function getMonthName( $key ) {
917 return $this->getMessageFromDB( self::$mMonthMsgs[$key - 1] );
918 }
919
923 public function getMonthNamesArray() {
924 $monthNames = [ '' ];
925 for ( $i = 1; $i < 13; $i++ ) {
926 $monthNames[] = $this->getMonthName( $i );
927 }
928 return $monthNames;
929 }
930
935 public function getMonthNameGen( $key ) {
936 return $this->getMessageFromDB( self::$mMonthGenMsgs[$key - 1] );
937 }
938
943 function getMonthAbbreviation( $key ) {
944 return $this->getMessageFromDB( self::$mMonthAbbrevMsgs[$key - 1] );
945 }
946
951 $monthNames = [ '' ];
952 for ( $i = 1; $i < 13; $i++ ) {
953 $monthNames[] = $this->getMonthAbbreviation( $i );
954 }
955 return $monthNames;
956 }
957
962 function getWeekdayName( $key ) {
963 return $this->getMessageFromDB( self::$mWeekdayMsgs[$key - 1] );
964 }
965
970 function getWeekdayAbbreviation( $key ) {
971 return $this->getMessageFromDB( self::$mWeekdayAbbrevMsgs[$key - 1] );
972 }
973
979 return $this->getMessageFromDB( self::$mIranianCalendarMonthMsgs[$key - 1] );
980 }
981
986 function getHebrewCalendarMonthName( $key ) {
987 return $this->getMessageFromDB( self::$mHebrewCalendarMonthMsgs[$key - 1] );
988 }
989
995 return $this->getMessageFromDB( self::$mHebrewCalendarMonthGenMsgs[$key - 1] );
996 }
997
1002 function getHijriCalendarMonthName( $key ) {
1003 return $this->getMessageFromDB( self::$mHijriCalendarMonthMsgs[$key - 1] );
1004 }
1005
1014 private static function dateTimeObjFormat( &$dateTimeObj, $ts, $zone, $code ) {
1015 if ( !$dateTimeObj ) {
1016 $dateTimeObj = DateTime::createFromFormat(
1017 'YmdHis', $ts, $zone ?: new DateTimeZone( 'UTC' )
1018 );
1019 }
1020 return $dateTimeObj->format( $code );
1021 }
1022
1090 function sprintfDate( $format, $ts, DateTimeZone $zone = null, &$ttl = null ) {
1091 $s = '';
1092 $raw = false;
1093 $roman = false;
1094 $hebrewNum = false;
1095 $dateTimeObj = false;
1096 $rawToggle = false;
1097 $iranian = false;
1098 $hebrew = false;
1099 $hijri = false;
1100 $thai = false;
1101 $minguo = false;
1102 $tenno = false;
1103
1104 $usedSecond = false;
1105 $usedMinute = false;
1106 $usedHour = false;
1107 $usedAMPM = false;
1108 $usedDay = false;
1109 $usedWeek = false;
1110 $usedMonth = false;
1111 $usedYear = false;
1112 $usedISOYear = false;
1113 $usedIsLeapYear = false;
1114
1115 $usedHebrewMonth = false;
1116 $usedIranianMonth = false;
1117 $usedHijriMonth = false;
1118 $usedHebrewYear = false;
1119 $usedIranianYear = false;
1120 $usedHijriYear = false;
1121 $usedTennoYear = false;
1122
1123 if ( strlen( $ts ) !== 14 ) {
1124 throw new MWException( __METHOD__ . ": The timestamp $ts should have 14 characters" );
1125 }
1126
1127 if ( !ctype_digit( $ts ) ) {
1128 throw new MWException( __METHOD__ . ": The timestamp $ts should be a number" );
1129 }
1130
1131 $formatLength = strlen( $format );
1132 for ( $p = 0; $p < $formatLength; $p++ ) {
1133 $num = false;
1134 $code = $format[$p];
1135 if ( $code == 'x' && $p < $formatLength - 1 ) {
1136 $code .= $format[++$p];
1137 }
1138
1139 if ( ( $code === 'xi'
1140 || $code === 'xj'
1141 || $code === 'xk'
1142 || $code === 'xm'
1143 || $code === 'xo'
1144 || $code === 'xt' )
1145 && $p < $formatLength - 1 ) {
1146 $code .= $format[++$p];
1147 }
1148
1149 switch ( $code ) {
1150 case 'xx':
1151 $s .= 'x';
1152 break;
1153 case 'xn':
1154 $raw = true;
1155 break;
1156 case 'xN':
1157 $rawToggle = !$rawToggle;
1158 break;
1159 case 'xr':
1160 $roman = true;
1161 break;
1162 case 'xh':
1163 $hebrewNum = true;
1164 break;
1165 case 'xg':
1166 $usedMonth = true;
1167 $s .= $this->getMonthNameGen( substr( $ts, 4, 2 ) );
1168 break;
1169 case 'xjx':
1170 $usedHebrewMonth = true;
1171 if ( !$hebrew ) {
1172 $hebrew = self::tsToHebrew( $ts );
1173 }
1174 $s .= $this->getHebrewCalendarMonthNameGen( $hebrew[1] );
1175 break;
1176 case 'd':
1177 $usedDay = true;
1178 $num = substr( $ts, 6, 2 );
1179 break;
1180 case 'D':
1181 $usedDay = true;
1182 $s .= $this->getWeekdayAbbreviation(
1183 Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'w' ) + 1
1184 );
1185 break;
1186 case 'j':
1187 $usedDay = true;
1188 $num = intval( substr( $ts, 6, 2 ) );
1189 break;
1190 case 'xij':
1191 $usedDay = true;
1192 if ( !$iranian ) {
1193 $iranian = self::tsToIranian( $ts );
1194 }
1195 $num = $iranian[2];
1196 break;
1197 case 'xmj':
1198 $usedDay = true;
1199 if ( !$hijri ) {
1200 $hijri = self::tsToHijri( $ts );
1201 }
1202 $num = $hijri[2];
1203 break;
1204 case 'xjj':
1205 $usedDay = true;
1206 if ( !$hebrew ) {
1207 $hebrew = self::tsToHebrew( $ts );
1208 }
1209 $num = $hebrew[2];
1210 break;
1211 case 'l':
1212 $usedDay = true;
1213 $s .= $this->getWeekdayName(
1214 Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'w' ) + 1
1215 );
1216 break;
1217 case 'F':
1218 $usedMonth = true;
1219 $s .= $this->getMonthName( substr( $ts, 4, 2 ) );
1220 break;
1221 case 'xiF':
1222 $usedIranianMonth = true;
1223 if ( !$iranian ) {
1224 $iranian = self::tsToIranian( $ts );
1225 }
1226 $s .= $this->getIranianCalendarMonthName( $iranian[1] );
1227 break;
1228 case 'xmF':
1229 $usedHijriMonth = true;
1230 if ( !$hijri ) {
1231 $hijri = self::tsToHijri( $ts );
1232 }
1233 $s .= $this->getHijriCalendarMonthName( $hijri[1] );
1234 break;
1235 case 'xjF':
1236 $usedHebrewMonth = true;
1237 if ( !$hebrew ) {
1238 $hebrew = self::tsToHebrew( $ts );
1239 }
1240 $s .= $this->getHebrewCalendarMonthName( $hebrew[1] );
1241 break;
1242 case 'm':
1243 $usedMonth = true;
1244 $num = substr( $ts, 4, 2 );
1245 break;
1246 case 'M':
1247 $usedMonth = true;
1248 $s .= $this->getMonthAbbreviation( substr( $ts, 4, 2 ) );
1249 break;
1250 case 'n':
1251 $usedMonth = true;
1252 $num = intval( substr( $ts, 4, 2 ) );
1253 break;
1254 case 'xin':
1255 $usedIranianMonth = true;
1256 if ( !$iranian ) {
1257 $iranian = self::tsToIranian( $ts );
1258 }
1259 $num = $iranian[1];
1260 break;
1261 case 'xmn':
1262 $usedHijriMonth = true;
1263 if ( !$hijri ) {
1264 $hijri = self::tsToHijri( $ts );
1265 }
1266 $num = $hijri[1];
1267 break;
1268 case 'xjn':
1269 $usedHebrewMonth = true;
1270 if ( !$hebrew ) {
1271 $hebrew = self::tsToHebrew( $ts );
1272 }
1273 $num = $hebrew[1];
1274 break;
1275 case 'xjt':
1276 $usedHebrewMonth = true;
1277 if ( !$hebrew ) {
1278 $hebrew = self::tsToHebrew( $ts );
1279 }
1280 $num = $hebrew[3];
1281 break;
1282 case 'Y':
1283 $usedYear = true;
1284 $num = substr( $ts, 0, 4 );
1285 break;
1286 case 'xiY':
1287 $usedIranianYear = true;
1288 if ( !$iranian ) {
1289 $iranian = self::tsToIranian( $ts );
1290 }
1291 $num = $iranian[0];
1292 break;
1293 case 'xmY':
1294 $usedHijriYear = true;
1295 if ( !$hijri ) {
1296 $hijri = self::tsToHijri( $ts );
1297 }
1298 $num = $hijri[0];
1299 break;
1300 case 'xjY':
1301 $usedHebrewYear = true;
1302 if ( !$hebrew ) {
1303 $hebrew = self::tsToHebrew( $ts );
1304 }
1305 $num = $hebrew[0];
1306 break;
1307 case 'xkY':
1308 $usedYear = true;
1309 if ( !$thai ) {
1310 $thai = self::tsToYear( $ts, 'thai' );
1311 }
1312 $num = $thai[0];
1313 break;
1314 case 'xoY':
1315 $usedYear = true;
1316 if ( !$minguo ) {
1317 $minguo = self::tsToYear( $ts, 'minguo' );
1318 }
1319 $num = $minguo[0];
1320 break;
1321 case 'xtY':
1322 $usedTennoYear = true;
1323 if ( !$tenno ) {
1324 $tenno = self::tsToYear( $ts, 'tenno' );
1325 }
1326 $num = $tenno[0];
1327 break;
1328 case 'y':
1329 $usedYear = true;
1330 $num = substr( $ts, 2, 2 );
1331 break;
1332 case 'xiy':
1333 $usedIranianYear = true;
1334 if ( !$iranian ) {
1335 $iranian = self::tsToIranian( $ts );
1336 }
1337 $num = substr( $iranian[0], -2 );
1338 break;
1339 case 'a':
1340 $usedAMPM = true;
1341 $s .= intval( substr( $ts, 8, 2 ) ) < 12 ? 'am' : 'pm';
1342 break;
1343 case 'A':
1344 $usedAMPM = true;
1345 $s .= intval( substr( $ts, 8, 2 ) ) < 12 ? 'AM' : 'PM';
1346 break;
1347 case 'g':
1348 $usedHour = true;
1349 $h = substr( $ts, 8, 2 );
1350 $num = $h % 12 ? $h % 12 : 12;
1351 break;
1352 case 'G':
1353 $usedHour = true;
1354 $num = intval( substr( $ts, 8, 2 ) );
1355 break;
1356 case 'h':
1357 $usedHour = true;
1358 $h = substr( $ts, 8, 2 );
1359 $num = sprintf( '%02d', $h % 12 ? $h % 12 : 12 );
1360 break;
1361 case 'H':
1362 $usedHour = true;
1363 $num = substr( $ts, 8, 2 );
1364 break;
1365 case 'i':
1366 $usedMinute = true;
1367 $num = substr( $ts, 10, 2 );
1368 break;
1369 case 's':
1370 $usedSecond = true;
1371 $num = substr( $ts, 12, 2 );
1372 break;
1373 case 'c':
1374 case 'r':
1375 $usedSecond = true;
1376 // fall through
1377 case 'e':
1378 case 'O':
1379 case 'P':
1380 case 'T':
1381 $s .= Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1382 break;
1383 case 'w':
1384 case 'N':
1385 case 'z':
1386 $usedDay = true;
1387 $num = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1388 break;
1389 case 'W':
1390 $usedWeek = true;
1391 $num = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1392 break;
1393 case 't':
1394 $usedMonth = true;
1395 $num = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1396 break;
1397 case 'L':
1398 $usedIsLeapYear = true;
1399 $num = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1400 break;
1401 case 'o':
1402 $usedISOYear = true;
1403 $num = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1404 break;
1405 case 'U':
1406 $usedSecond = true;
1407 // fall through
1408 case 'I':
1409 case 'Z':
1410 $num = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1411 break;
1412 case '\\':
1413 # Backslash escaping
1414 if ( $p < $formatLength - 1 ) {
1415 $s .= $format[++$p];
1416 } else {
1417 $s .= '\\';
1418 }
1419 break;
1420 case '"':
1421 # Quoted literal
1422 if ( $p < $formatLength - 1 ) {
1423 $endQuote = strpos( $format, '"', $p + 1 );
1424 if ( $endQuote === false ) {
1425 # No terminating quote, assume literal "
1426 $s .= '"';
1427 } else {
1428 $s .= substr( $format, $p + 1, $endQuote - $p - 1 );
1429 $p = $endQuote;
1430 }
1431 } else {
1432 # Quote at end of string, assume literal "
1433 $s .= '"';
1434 }
1435 break;
1436 default:
1437 $s .= $format[$p];
1438 }
1439 if ( $num !== false ) {
1440 if ( $rawToggle || $raw ) {
1441 $s .= $num;
1442 $raw = false;
1443 } elseif ( $roman ) {
1444 $s .= Language::romanNumeral( $num );
1445 $roman = false;
1446 } elseif ( $hebrewNum ) {
1447 $s .= self::hebrewNumeral( $num );
1448 $hebrewNum = false;
1449 } else {
1450 $s .= $this->formatNum( $num, true );
1451 }
1452 }
1453 }
1454
1455 if ( $usedSecond ) {
1456 $ttl = 1;
1457 } elseif ( $usedMinute ) {
1458 $ttl = 60 - substr( $ts, 12, 2 );
1459 } elseif ( $usedHour ) {
1460 $ttl = 3600 - substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
1461 } elseif ( $usedAMPM ) {
1462 $ttl = 43200 - ( substr( $ts, 8, 2 ) % 12 ) * 3600 -
1463 substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
1464 } elseif (
1465 $usedDay ||
1466 $usedHebrewMonth ||
1467 $usedIranianMonth ||
1468 $usedHijriMonth ||
1469 $usedHebrewYear ||
1470 $usedIranianYear ||
1471 $usedHijriYear ||
1472 $usedTennoYear
1473 ) {
1474 // @todo Someone who understands the non-Gregorian calendars
1475 // should write proper logic for them so that they don't need purged every day.
1476 $ttl = 86400 - substr( $ts, 8, 2 ) * 3600 -
1477 substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
1478 } else {
1479 $possibleTtls = [];
1480 $timeRemainingInDay = 86400 - substr( $ts, 8, 2 ) * 3600 -
1481 substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
1482 if ( $usedWeek ) {
1483 $possibleTtls[] =
1484 ( 7 - Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'N' ) ) * 86400 +
1485 $timeRemainingInDay;
1486 } elseif ( $usedISOYear ) {
1487 // December 28th falls on the last ISO week of the year, every year.
1488 // The last ISO week of a year can be 52 or 53.
1489 $lastWeekOfISOYear = DateTime::createFromFormat(
1490 'Ymd',
1491 substr( $ts, 0, 4 ) . '1228',
1492 $zone ?: new DateTimeZone( 'UTC' )
1493 )->format( 'W' );
1494 $currentISOWeek = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'W' );
1495 $weeksRemaining = $lastWeekOfISOYear - $currentISOWeek;
1496 $timeRemainingInWeek =
1497 ( 7 - Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'N' ) ) * 86400
1498 + $timeRemainingInDay;
1499 $possibleTtls[] = $weeksRemaining * 604800 + $timeRemainingInWeek;
1500 }
1501
1502 if ( $usedMonth ) {
1503 $possibleTtls[] =
1504 ( Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 't' ) -
1505 substr( $ts, 6, 2 ) ) * 86400
1506 + $timeRemainingInDay;
1507 } elseif ( $usedYear ) {
1508 $possibleTtls[] =
1509 ( Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'L' ) + 364 -
1510 Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'z' ) ) * 86400
1511 + $timeRemainingInDay;
1512 } elseif ( $usedIsLeapYear ) {
1513 $year = substr( $ts, 0, 4 );
1514 $timeRemainingInYear =
1515 ( Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'L' ) + 364 -
1516 Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'z' ) ) * 86400
1517 + $timeRemainingInDay;
1518 $mod = $year % 4;
1519 if ( $mod || ( !( $year % 100 ) && $year % 400 ) ) {
1520 // this isn't a leap year. see when the next one starts
1521 $nextCandidate = $year - $mod + 4;
1522 if ( $nextCandidate % 100 || !( $nextCandidate % 400 ) ) {
1523 $possibleTtls[] = ( $nextCandidate - $year - 1 ) * 365 * 86400 +
1524 $timeRemainingInYear;
1525 } else {
1526 $possibleTtls[] = ( $nextCandidate - $year + 3 ) * 365 * 86400 +
1527 $timeRemainingInYear;
1528 }
1529 } else {
1530 // this is a leap year, so the next year isn't
1531 $possibleTtls[] = $timeRemainingInYear;
1532 }
1533 }
1534
1535 if ( $possibleTtls ) {
1536 $ttl = min( $possibleTtls );
1537 }
1538 }
1539
1540 return $s;
1541 }
1542
1543 private static $GREG_DAYS = [ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ];
1544 private static $IRANIAN_DAYS = [ 31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29 ];
1545
1558 private static function tsToIranian( $ts ) {
1559 $gy = substr( $ts, 0, 4 ) -1600;
1560 $gm = substr( $ts, 4, 2 ) -1;
1561 $gd = substr( $ts, 6, 2 ) -1;
1562
1563 # Days passed from the beginning (including leap years)
1564 $gDayNo = 365 * $gy
1565 + floor( ( $gy + 3 ) / 4 )
1566 - floor( ( $gy + 99 ) / 100 )
1567 + floor( ( $gy + 399 ) / 400 );
1568
1569 // Add days of the past months of this year
1570 for ( $i = 0; $i < $gm; $i++ ) {
1571 $gDayNo += self::$GREG_DAYS[$i];
1572 }
1573
1574 // Leap years
1575 if ( $gm > 1 && ( ( $gy % 4 === 0 && $gy % 100 !== 0 || ( $gy % 400 == 0 ) ) ) ) {
1576 $gDayNo++;
1577 }
1578
1579 // Days passed in current month
1580 $gDayNo += (int)$gd;
1581
1582 $jDayNo = $gDayNo - 79;
1583
1584 $jNp = floor( $jDayNo / 12053 );
1585 $jDayNo %= 12053;
1586
1587 $jy = 979 + 33 * $jNp + 4 * floor( $jDayNo / 1461 );
1588 $jDayNo %= 1461;
1589
1590 if ( $jDayNo >= 366 ) {
1591 $jy += floor( ( $jDayNo - 1 ) / 365 );
1592 $jDayNo = floor( ( $jDayNo - 1 ) % 365 );
1593 }
1594
1595 for ( $i = 0; $i < 11 && $jDayNo >= self::$IRANIAN_DAYS[$i]; $i++ ) {
1596 $jDayNo -= self::$IRANIAN_DAYS[$i];
1597 }
1598
1599 $jm = $i + 1;
1600 $jd = $jDayNo + 1;
1601
1602 return [ $jy, $jm, $jd ];
1603 }
1604
1616 private static function tsToHijri( $ts ) {
1617 $year = substr( $ts, 0, 4 );
1618 $month = substr( $ts, 4, 2 );
1619 $day = substr( $ts, 6, 2 );
1620
1621 $zyr = $year;
1622 $zd = $day;
1623 $zm = $month;
1624 $zy = $zyr;
1625
1626 if (
1627 ( $zy > 1582 ) || ( ( $zy == 1582 ) && ( $zm > 10 ) ) ||
1628 ( ( $zy == 1582 ) && ( $zm == 10 ) && ( $zd > 14 ) )
1629 ) {
1630 $zjd = (int)( ( 1461 * ( $zy + 4800 + (int)( ( $zm - 14 ) / 12 ) ) ) / 4 ) +
1631 (int)( ( 367 * ( $zm - 2 - 12 * ( (int)( ( $zm - 14 ) / 12 ) ) ) ) / 12 ) -
1632 (int)( ( 3 * (int)( ( ( $zy + 4900 + (int)( ( $zm - 14 ) / 12 ) ) / 100 ) ) ) / 4 ) +
1633 $zd - 32075;
1634 } else {
1635 $zjd = 367 * $zy - (int)( ( 7 * ( $zy + 5001 + (int)( ( $zm - 9 ) / 7 ) ) ) / 4 ) +
1636 (int)( ( 275 * $zm ) / 9 ) + $zd + 1729777;
1637 }
1638
1639 $zl = $zjd -1948440 + 10632;
1640 $zn = (int)( ( $zl - 1 ) / 10631 );
1641 $zl = $zl - 10631 * $zn + 354;
1642 $zj = ( (int)( ( 10985 - $zl ) / 5316 ) ) * ( (int)( ( 50 * $zl ) / 17719 ) ) +
1643 ( (int)( $zl / 5670 ) ) * ( (int)( ( 43 * $zl ) / 15238 ) );
1644 $zl = $zl - ( (int)( ( 30 - $zj ) / 15 ) ) * ( (int)( ( 17719 * $zj ) / 50 ) ) -
1645 ( (int)( $zj / 16 ) ) * ( (int)( ( 15238 * $zj ) / 43 ) ) + 29;
1646 $zm = (int)( ( 24 * $zl ) / 709 );
1647 $zd = $zl - (int)( ( 709 * $zm ) / 24 );
1648 $zy = 30 * $zn + $zj - 30;
1649
1650 return [ $zy, $zm, $zd ];
1651 }
1652
1668 private static function tsToHebrew( $ts ) {
1669 # Parse date
1670 $year = substr( $ts, 0, 4 );
1671 $month = substr( $ts, 4, 2 );
1672 $day = substr( $ts, 6, 2 );
1673
1674 # Calculate Hebrew year
1675 $hebrewYear = $year + 3760;
1676
1677 # Month number when September = 1, August = 12
1678 $month += 4;
1679 if ( $month > 12 ) {
1680 # Next year
1681 $month -= 12;
1682 $year++;
1683 $hebrewYear++;
1684 }
1685
1686 # Calculate day of year from 1 September
1687 $dayOfYear = $day;
1688 for ( $i = 1; $i < $month; $i++ ) {
1689 if ( $i == 6 ) {
1690 # February
1691 $dayOfYear += 28;
1692 # Check if the year is leap
1693 if ( $year % 400 == 0 || ( $year % 4 == 0 && $year % 100 > 0 ) ) {
1694 $dayOfYear++;
1695 }
1696 } elseif ( $i == 8 || $i == 10 || $i == 1 || $i == 3 ) {
1697 $dayOfYear += 30;
1698 } else {
1699 $dayOfYear += 31;
1700 }
1701 }
1702
1703 # Calculate the start of the Hebrew year
1704 $start = self::hebrewYearStart( $hebrewYear );
1705
1706 # Calculate next year's start
1707 if ( $dayOfYear <= $start ) {
1708 # Day is before the start of the year - it is the previous year
1709 # Next year's start
1710 $nextStart = $start;
1711 # Previous year
1712 $year--;
1713 $hebrewYear--;
1714 # Add days since previous year's 1 September
1715 $dayOfYear += 365;
1716 if ( ( $year % 400 == 0 ) || ( $year % 100 != 0 && $year % 4 == 0 ) ) {
1717 # Leap year
1718 $dayOfYear++;
1719 }
1720 # Start of the new (previous) year
1721 $start = self::hebrewYearStart( $hebrewYear );
1722 } else {
1723 # Next year's start
1724 $nextStart = self::hebrewYearStart( $hebrewYear + 1 );
1725 }
1726
1727 # Calculate Hebrew day of year
1728 $hebrewDayOfYear = $dayOfYear - $start;
1729
1730 # Difference between year's days
1731 $diff = $nextStart - $start;
1732 # Add 12 (or 13 for leap years) days to ignore the difference between
1733 # Hebrew and Gregorian year (353 at least vs. 365/6) - now the
1734 # difference is only about the year type
1735 if ( ( $year % 400 == 0 ) || ( $year % 100 != 0 && $year % 4 == 0 ) ) {
1736 $diff += 13;
1737 } else {
1738 $diff += 12;
1739 }
1740
1741 # Check the year pattern, and is leap year
1742 # 0 means an incomplete year, 1 means a regular year, 2 means a complete year
1743 # This is mod 30, to work on both leap years (which add 30 days of Adar I)
1744 # and non-leap years
1745 $yearPattern = $diff % 30;
1746 # Check if leap year
1747 $isLeap = $diff >= 30;
1748
1749 # Calculate day in the month from number of day in the Hebrew year
1750 # Don't check Adar - if the day is not in Adar, we will stop before;
1751 # if it is in Adar, we will use it to check if it is Adar I or Adar II
1752 $hebrewDay = $hebrewDayOfYear;
1753 $hebrewMonth = 1;
1754 $days = 0;
1755 while ( $hebrewMonth <= 12 ) {
1756 # Calculate days in this month
1757 if ( $isLeap && $hebrewMonth == 6 ) {
1758 # Adar in a leap year
1759 if ( $isLeap ) {
1760 # Leap year - has Adar I, with 30 days, and Adar II, with 29 days
1761 $days = 30;
1762 if ( $hebrewDay <= $days ) {
1763 # Day in Adar I
1764 $hebrewMonth = 13;
1765 } else {
1766 # Subtract the days of Adar I
1767 $hebrewDay -= $days;
1768 # Try Adar II
1769 $days = 29;
1770 if ( $hebrewDay <= $days ) {
1771 # Day in Adar II
1772 $hebrewMonth = 14;
1773 }
1774 }
1775 }
1776 } elseif ( $hebrewMonth == 2 && $yearPattern == 2 ) {
1777 # Cheshvan in a complete year (otherwise as the rule below)
1778 $days = 30;
1779 } elseif ( $hebrewMonth == 3 && $yearPattern == 0 ) {
1780 # Kislev in an incomplete year (otherwise as the rule below)
1781 $days = 29;
1782 } else {
1783 # Odd months have 30 days, even have 29
1784 $days = 30 - ( $hebrewMonth - 1 ) % 2;
1785 }
1786 if ( $hebrewDay <= $days ) {
1787 # In the current month
1788 break;
1789 } else {
1790 # Subtract the days of the current month
1791 $hebrewDay -= $days;
1792 # Try in the next month
1793 $hebrewMonth++;
1794 }
1795 }
1796
1797 return [ $hebrewYear, $hebrewMonth, $hebrewDay, $days ];
1798 }
1799
1809 private static function hebrewYearStart( $year ) {
1810 $a = intval( ( 12 * ( $year - 1 ) + 17 ) % 19 );
1811 $b = intval( ( $year - 1 ) % 4 );
1812 $m = 32.044093161144 + 1.5542417966212 * $a + $b / 4.0 - 0.0031777940220923 * ( $year - 1 );
1813 if ( $m < 0 ) {
1814 $m--;
1815 }
1816 $Mar = intval( $m );
1817 if ( $m < 0 ) {
1818 $m++;
1819 }
1820 $m -= $Mar;
1821
1822 $c = intval( ( $Mar + 3 * ( $year - 1 ) + 5 * $b + 5 ) % 7 );
1823 if ( $c == 0 && $a > 11 && $m >= 0.89772376543210 ) {
1824 $Mar++;
1825 } elseif ( $c == 1 && $a > 6 && $m >= 0.63287037037037 ) {
1826 $Mar += 2;
1827 } elseif ( $c == 2 || $c == 4 || $c == 6 ) {
1828 $Mar++;
1829 }
1830
1831 $Mar += intval( ( $year - 3761 ) / 100 ) - intval( ( $year - 3761 ) / 400 ) - 24;
1832 return $Mar;
1833 }
1834
1847 private static function tsToYear( $ts, $cName ) {
1848 $gy = substr( $ts, 0, 4 );
1849 $gm = substr( $ts, 4, 2 );
1850 $gd = substr( $ts, 6, 2 );
1851
1852 if ( !strcmp( $cName, 'thai' ) ) {
1853 # Thai solar dates
1854 # Add 543 years to the Gregorian calendar
1855 # Months and days are identical
1856 $gy_offset = $gy + 543;
1857 } elseif ( ( !strcmp( $cName, 'minguo' ) ) || !strcmp( $cName, 'juche' ) ) {
1858 # Minguo dates
1859 # Deduct 1911 years from the Gregorian calendar
1860 # Months and days are identical
1861 $gy_offset = $gy - 1911;
1862 } elseif ( !strcmp( $cName, 'tenno' ) ) {
1863 # Nengō dates up to Meiji period
1864 # Deduct years from the Gregorian calendar
1865 # depending on the nengo periods
1866 # Months and days are identical
1867 if ( ( $gy < 1912 )
1868 || ( ( $gy == 1912 ) && ( $gm < 7 ) )
1869 || ( ( $gy == 1912 ) && ( $gm == 7 ) && ( $gd < 31 ) )
1870 ) {
1871 # Meiji period
1872 $gy_gannen = $gy - 1868 + 1;
1873 $gy_offset = $gy_gannen;
1874 if ( $gy_gannen == 1 ) {
1875 $gy_offset = '元';
1876 }
1877 $gy_offset = '明治' . $gy_offset;
1878 } elseif (
1879 ( ( $gy == 1912 ) && ( $gm == 7 ) && ( $gd == 31 ) ) ||
1880 ( ( $gy == 1912 ) && ( $gm >= 8 ) ) ||
1881 ( ( $gy > 1912 ) && ( $gy < 1926 ) ) ||
1882 ( ( $gy == 1926 ) && ( $gm < 12 ) ) ||
1883 ( ( $gy == 1926 ) && ( $gm == 12 ) && ( $gd < 26 ) )
1884 ) {
1885 # Taishō period
1886 $gy_gannen = $gy - 1912 + 1;
1887 $gy_offset = $gy_gannen;
1888 if ( $gy_gannen == 1 ) {
1889 $gy_offset = '元';
1890 }
1891 $gy_offset = '大正' . $gy_offset;
1892 } elseif (
1893 ( ( $gy == 1926 ) && ( $gm == 12 ) && ( $gd >= 26 ) ) ||
1894 ( ( $gy > 1926 ) && ( $gy < 1989 ) ) ||
1895 ( ( $gy == 1989 ) && ( $gm == 1 ) && ( $gd < 8 ) )
1896 ) {
1897 # Shōwa period
1898 $gy_gannen = $gy - 1926 + 1;
1899 $gy_offset = $gy_gannen;
1900 if ( $gy_gannen == 1 ) {
1901 $gy_offset = '元';
1902 }
1903 $gy_offset = '昭和' . $gy_offset;
1904 } elseif (
1905 ( ( $gy == 1989 ) && ( $gm == 1 ) && ( $gd >= 8 ) ) ||
1906 ( ( $gy > 1989 ) && ( $gy < 2019 ) ) ||
1907 ( ( $gy == 2019 ) && ( $gm < 5 ) )
1908 ) {
1909 # Heisei period
1910 $gy_gannen = $gy - 1989 + 1;
1911 $gy_offset = $gy_gannen;
1912 if ( $gy_gannen == 1 ) {
1913 $gy_offset = '元';
1914 }
1915 $gy_offset = '平成' . $gy_offset;
1916 } else {
1917 # Reiwa period
1918 $gy_gannen = $gy - 2019 + 1;
1919 $gy_offset = $gy_gannen;
1920 if ( $gy_gannen == 1 ) {
1921 $gy_offset = '元';
1922 }
1923 $gy_offset = '令和' . $gy_offset;
1924 }
1925 } else {
1926 $gy_offset = $gy;
1927 }
1928
1929 return [ $gy_offset, $gm, $gd ];
1930 }
1931
1945 private static function strongDirFromContent( $text = '' ) {
1946 if ( !preg_match( self::$strongDirRegex, $text, $matches ) ) {
1947 return null;
1948 }
1949 if ( $matches[1] === '' ) {
1950 return 'rtl';
1951 }
1952 return 'ltr';
1953 }
1954
1962 static function romanNumeral( $num ) {
1963 static $table = [
1964 [ '', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X' ],
1965 [ '', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC', 'C' ],
1966 [ '', 'C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM', 'M' ],
1967 [ '', 'M', 'MM', 'MMM', 'MMMM', 'MMMMM', 'MMMMMM', 'MMMMMMM',
1968 'MMMMMMMM', 'MMMMMMMMM', 'MMMMMMMMMM' ]
1969 ];
1970
1971 $num = intval( $num );
1972 if ( $num > 10000 || $num <= 0 ) {
1973 return $num;
1974 }
1975
1976 $s = '';
1977 for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) {
1978 if ( $num >= $pow10 ) {
1979 $s .= $table[$i][(int)floor( $num / $pow10 )];
1980 }
1981 $num = $num % $pow10;
1982 }
1983 return $s;
1984 }
1985
1993 static function hebrewNumeral( $num ) {
1994 static $table = [
1995 [ '', 'א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ז', 'ח', 'ט', 'י' ],
1996 [ '', 'י', 'כ', 'ל', 'מ', 'נ', 'ס', 'ע', 'פ', 'צ', 'ק' ],
1997 [ '',
1998 [ 'ק' ],
1999 [ 'ר' ],
2000 [ 'ש' ],
2001 [ 'ת' ],
2002 [ 'ת', 'ק' ],
2003 [ 'ת', 'ר' ],
2004 [ 'ת', 'ש' ],
2005 [ 'ת', 'ת' ],
2006 [ 'ת', 'ת', 'ק' ],
2007 [ 'ת', 'ת', 'ר' ],
2008 ],
2009 [ '', 'א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ז', 'ח', 'ט', 'י' ]
2010 ];
2011
2012 $num = intval( $num );
2013 if ( $num > 9999 || $num <= 0 ) {
2014 return $num;
2015 }
2016
2017 // Round thousands have special notations
2018 if ( $num === 1000 ) {
2019 return "א' אלף";
2020 } elseif ( $num % 1000 === 0 ) {
2021 return $table[0][$num / 1000] . "' אלפים";
2022 }
2023
2024 $letters = [];
2025
2026 for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) {
2027 if ( $num >= $pow10 ) {
2028 if ( $num === 15 || $num === 16 ) {
2029 $letters[] = $table[0][9];
2030 $letters[] = $table[0][$num - 9];
2031 $num = 0;
2032 } else {
2033 $letters = array_merge(
2034 $letters,
2035 (array)$table[$i][intval( $num / $pow10 )]
2036 );
2037
2038 if ( $pow10 === 1000 ) {
2039 $letters[] = "'";
2040 }
2041 }
2042 }
2043
2044 $num = $num % $pow10;
2045 }
2046
2047 $preTransformLength = count( $letters );
2048 if ( $preTransformLength === 1 ) {
2049 // Add geresh (single quote) to one-letter numbers
2050 $letters[] = "'";
2051 } else {
2052 $lastIndex = $preTransformLength - 1;
2053 $letters[$lastIndex] = str_replace(
2054 [ 'כ', 'מ', 'נ', 'פ', 'צ' ],
2055 [ 'ך', 'ם', 'ן', 'ף', 'ץ' ],
2056 $letters[$lastIndex]
2057 );
2058
2059 // Add gershayim (double quote) to multiple-letter numbers,
2060 // but exclude numbers with only one letter after the thousands
2061 // (1001-1009, 1020, 1030, 2001-2009, etc.)
2062 if ( $letters[1] === "'" && $preTransformLength === 3 ) {
2063 $letters[] = "'";
2064 } else {
2065 array_splice( $letters, -1, 0, '"' );
2066 }
2067 }
2068
2069 return implode( $letters );
2070 }
2071
2080 function userAdjust( $ts, $tz = false ) {
2082
2083 if ( $tz === false ) {
2084 $tz = $wgUser->getOption( 'timecorrection' );
2085 }
2086
2087 $data = explode( '|', $tz, 3 );
2088
2089 if ( $data[0] == 'ZoneInfo' ) {
2090 MediaWiki\suppressWarnings();
2091 $userTZ = timezone_open( $data[2] );
2092 MediaWiki\restoreWarnings();
2093 if ( $userTZ !== false ) {
2094 $date = date_create( $ts, timezone_open( 'UTC' ) );
2095 date_timezone_set( $date, $userTZ );
2096 $date = date_format( $date, 'YmdHis' );
2097 return $date;
2098 }
2099 # Unrecognized timezone, default to 'Offset' with the stored offset.
2100 $data[0] = 'Offset';
2101 }
2102
2103 if ( $data[0] == 'System' || $tz == '' ) {
2104 # Global offset in minutes.
2105 $minDiff = $wgLocalTZoffset;
2106 } elseif ( $data[0] == 'Offset' ) {
2107 $minDiff = intval( $data[1] );
2108 } else {
2109 $data = explode( ':', $tz );
2110 if ( count( $data ) == 2 ) {
2111 $data[0] = intval( $data[0] );
2112 $data[1] = intval( $data[1] );
2113 $minDiff = abs( $data[0] ) * 60 + $data[1];
2114 if ( $data[0] < 0 ) {
2115 $minDiff = -$minDiff;
2116 }
2117 } else {
2118 $minDiff = intval( $data[0] ) * 60;
2119 }
2120 }
2121
2122 # No difference ? Return time unchanged
2123 if ( 0 == $minDiff ) {
2124 return $ts;
2125 }
2126
2127 MediaWiki\suppressWarnings(); // E_STRICT system time bitching
2128 # Generate an adjusted date; take advantage of the fact that mktime
2129 # will normalize out-of-range values so we don't have to split $minDiff
2130 # into hours and minutes.
2131 $t = mktime( (
2132 (int)substr( $ts, 8, 2 ) ), # Hours
2133 (int)substr( $ts, 10, 2 ) + $minDiff, # Minutes
2134 (int)substr( $ts, 12, 2 ), # Seconds
2135 (int)substr( $ts, 4, 2 ), # Month
2136 (int)substr( $ts, 6, 2 ), # Day
2137 (int)substr( $ts, 0, 4 ) ); # Year
2138
2139 $date = date( 'YmdHis', $t );
2140 MediaWiki\restoreWarnings();
2141
2142 return $date;
2143 }
2144
2162 function dateFormat( $usePrefs = true ) {
2164
2165 if ( is_bool( $usePrefs ) ) {
2166 if ( $usePrefs ) {
2167 $datePreference = $wgUser->getDatePreference();
2168 } else {
2169 $datePreference = (string)User::getDefaultOption( 'date' );
2170 }
2171 } else {
2172 $datePreference = (string)$usePrefs;
2173 }
2174
2175 // return int
2176 if ( $datePreference == '' ) {
2177 return 'default';
2178 }
2179
2180 return $datePreference;
2181 }
2182
2193 function getDateFormatString( $type, $pref ) {
2194 $wasDefault = false;
2195 if ( $pref == 'default' ) {
2196 $wasDefault = true;
2197 $pref = $this->getDefaultDateFormat();
2198 }
2199
2200 if ( !isset( $this->dateFormatStrings[$type][$pref] ) ) {
2201 $df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" );
2202
2203 if ( $type === 'pretty' && $df === null ) {
2204 $df = $this->getDateFormatString( 'date', $pref );
2205 }
2206
2207 if ( !$wasDefault && $df === null ) {
2208 $pref = $this->getDefaultDateFormat();
2209 $df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" );
2210 }
2211
2212 $this->dateFormatStrings[$type][$pref] = $df;
2213 }
2214 return $this->dateFormatStrings[$type][$pref];
2215 }
2216
2227 function date( $ts, $adj = false, $format = true, $timecorrection = false ) {
2228 $ts = wfTimestamp( TS_MW, $ts );
2229 if ( $adj ) {
2230 $ts = $this->userAdjust( $ts, $timecorrection );
2231 }
2232 $df = $this->getDateFormatString( 'date', $this->dateFormat( $format ) );
2233 return $this->sprintfDate( $df, $ts );
2234 }
2235
2246 function time( $ts, $adj = false, $format = true, $timecorrection = false ) {
2247 $ts = wfTimestamp( TS_MW, $ts );
2248 if ( $adj ) {
2249 $ts = $this->userAdjust( $ts, $timecorrection );
2250 }
2251 $df = $this->getDateFormatString( 'time', $this->dateFormat( $format ) );
2252 return $this->sprintfDate( $df, $ts );
2253 }
2254
2266 function timeanddate( $ts, $adj = false, $format = true, $timecorrection = false ) {
2267 $ts = wfTimestamp( TS_MW, $ts );
2268 if ( $adj ) {
2269 $ts = $this->userAdjust( $ts, $timecorrection );
2270 }
2271 $df = $this->getDateFormatString( 'both', $this->dateFormat( $format ) );
2272 return $this->sprintfDate( $df, $ts );
2273 }
2274
2285 public function formatDuration( $seconds, array $chosenIntervals = [] ) {
2286 $intervals = $this->getDurationIntervals( $seconds, $chosenIntervals );
2287
2288 $segments = [];
2289
2290 foreach ( $intervals as $intervalName => $intervalValue ) {
2291 // Messages: duration-seconds, duration-minutes, duration-hours, duration-days, duration-weeks,
2292 // duration-years, duration-decades, duration-centuries, duration-millennia
2293 $message = wfMessage( 'duration-' . $intervalName )->numParams( $intervalValue );
2294 $segments[] = $message->inLanguage( $this )->escaped();
2295 }
2296
2297 return $this->listToText( $segments );
2298 }
2299
2311 public function getDurationIntervals( $seconds, array $chosenIntervals = [] ) {
2312 if ( empty( $chosenIntervals ) ) {
2313 $chosenIntervals = [
2314 'millennia',
2315 'centuries',
2316 'decades',
2317 'years',
2318 'days',
2319 'hours',
2320 'minutes',
2321 'seconds'
2322 ];
2323 }
2324
2325 $intervals = array_intersect_key( self::$durationIntervals, array_flip( $chosenIntervals ) );
2326 $sortedNames = array_keys( $intervals );
2327 $smallestInterval = array_pop( $sortedNames );
2328
2329 $segments = [];
2330
2331 foreach ( $intervals as $name => $length ) {
2332 $value = floor( $seconds / $length );
2333
2334 if ( $value > 0 || ( $name == $smallestInterval && empty( $segments ) ) ) {
2335 $seconds -= $value * $length;
2336 $segments[$name] = $value;
2337 }
2338 }
2339
2340 return $segments;
2341 }
2342
2362 private function internalUserTimeAndDate( $type, $ts, User $user, array $options ) {
2363 $ts = wfTimestamp( TS_MW, $ts );
2364 $options += [ 'timecorrection' => true, 'format' => true ];
2365 if ( $options['timecorrection'] !== false ) {
2366 if ( $options['timecorrection'] === true ) {
2367 $offset = $user->getOption( 'timecorrection' );
2368 } else {
2369 $offset = $options['timecorrection'];
2370 }
2371 $ts = $this->userAdjust( $ts, $offset );
2372 }
2373 if ( $options['format'] === true ) {
2374 $format = $user->getDatePreference();
2375 } else {
2376 $format = $options['format'];
2377 }
2378 $df = $this->getDateFormatString( $type, $this->dateFormat( $format ) );
2379 return $this->sprintfDate( $df, $ts );
2380 }
2381
2401 public function userDate( $ts, User $user, array $options = [] ) {
2402 return $this->internalUserTimeAndDate( 'date', $ts, $user, $options );
2403 }
2404
2424 public function userTime( $ts, User $user, array $options = [] ) {
2425 return $this->internalUserTimeAndDate( 'time', $ts, $user, $options );
2426 }
2427
2447 public function userTimeAndDate( $ts, User $user, array $options = [] ) {
2448 return $this->internalUserTimeAndDate( 'both', $ts, $user, $options );
2449 }
2450
2466 public function getHumanTimestamp(
2467 MWTimestamp $time, MWTimestamp $relativeTo = null, User $user = null
2468 ) {
2469 if ( $relativeTo === null ) {
2470 $relativeTo = new MWTimestamp();
2471 }
2472 if ( $user === null ) {
2473 $user = RequestContext::getMain()->getUser();
2474 }
2475
2476 // Adjust for the user's timezone.
2477 $offsetThis = $time->offsetForUser( $user );
2478 $offsetRel = $relativeTo->offsetForUser( $user );
2479
2480 $ts = '';
2481 if ( Hooks::run( 'GetHumanTimestamp', [ &$ts, $time, $relativeTo, $user, $this ] ) ) {
2482 $ts = $this->getHumanTimestampInternal( $time, $relativeTo, $user );
2483 }
2484
2485 // Reset the timezone on the objects.
2486 $time->timestamp->sub( $offsetThis );
2487 $relativeTo->timestamp->sub( $offsetRel );
2488
2489 return $ts;
2490 }
2491
2504 MWTimestamp $ts, MWTimestamp $relativeTo, User $user
2505 ) {
2506 $diff = $ts->diff( $relativeTo );
2507 $diffDay = (bool)( (int)$ts->timestamp->format( 'w' ) -
2508 (int)$relativeTo->timestamp->format( 'w' ) );
2509 $days = $diff->days ?: (int)$diffDay;
2510 if ( $diff->invert || $days > 5
2511 && $ts->timestamp->format( 'Y' ) !== $relativeTo->timestamp->format( 'Y' )
2512 ) {
2513 // Timestamps are in different years: use full timestamp
2514 // Also do full timestamp for future dates
2518 $format = $this->getDateFormatString( 'both', $user->getDatePreference() ?: 'default' );
2519 $ts = $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) );
2520 } elseif ( $days > 5 ) {
2521 // Timestamps are in same year, but more than 5 days ago: show day and month only.
2522 $format = $this->getDateFormatString( 'pretty', $user->getDatePreference() ?: 'default' );
2523 $ts = $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) );
2524 } elseif ( $days > 1 ) {
2525 // Timestamp within the past week: show the day of the week and time
2526 $format = $this->getDateFormatString( 'time', $user->getDatePreference() ?: 'default' );
2527 $weekday = self::$mWeekdayMsgs[$ts->timestamp->format( 'w' )];
2528 // Messages:
2529 // sunday-at, monday-at, tuesday-at, wednesday-at, thursday-at, friday-at, saturday-at
2530 $ts = wfMessage( "$weekday-at" )
2531 ->inLanguage( $this )
2532 ->params( $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) ) )
2533 ->text();
2534 } elseif ( $days == 1 ) {
2535 // Timestamp was yesterday: say 'yesterday' and the time.
2536 $format = $this->getDateFormatString( 'time', $user->getDatePreference() ?: 'default' );
2537 $ts = wfMessage( 'yesterday-at' )
2538 ->inLanguage( $this )
2539 ->params( $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) ) )
2540 ->text();
2541 } elseif ( $diff->h > 1 || $diff->h == 1 && $diff->i > 30 ) {
2542 // Timestamp was today, but more than 90 minutes ago: say 'today' and the time.
2543 $format = $this->getDateFormatString( 'time', $user->getDatePreference() ?: 'default' );
2544 $ts = wfMessage( 'today-at' )
2545 ->inLanguage( $this )
2546 ->params( $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) ) )
2547 ->text();
2548
2549 // From here on in, the timestamp was soon enough ago so that we can simply say
2550 // XX units ago, e.g., "2 hours ago" or "5 minutes ago"
2551 } elseif ( $diff->h == 1 ) {
2552 // Less than 90 minutes, but more than an hour ago.
2553 $ts = wfMessage( 'hours-ago' )->inLanguage( $this )->numParams( 1 )->text();
2554 } elseif ( $diff->i >= 1 ) {
2555 // A few minutes ago.
2556 $ts = wfMessage( 'minutes-ago' )->inLanguage( $this )->numParams( $diff->i )->text();
2557 } elseif ( $diff->s >= 30 ) {
2558 // Less than a minute, but more than 30 sec ago.
2559 $ts = wfMessage( 'seconds-ago' )->inLanguage( $this )->numParams( $diff->s )->text();
2560 } else {
2561 // Less than 30 seconds ago.
2562 $ts = wfMessage( 'just-now' )->text();
2563 }
2564
2565 return $ts;
2566 }
2567
2572 function getMessage( $key ) {
2573 return self::$dataCache->getSubitem( $this->mCode, 'messages', $key );
2574 }
2575
2579 function getAllMessages() {
2580 return self::$dataCache->getItem( $this->mCode, 'messages' );
2581 }
2582
2589 function iconv( $in, $out, $string ) {
2590 # This is a wrapper for iconv in all languages except esperanto,
2591 # which does some nasty x-conversions beforehand
2592
2593 # Even with //IGNORE iconv can whine about illegal characters in
2594 # *input* string. We just ignore those too.
2595 # REF: http://bugs.php.net/bug.php?id=37166
2596 # REF: https://phabricator.wikimedia.org/T18885
2597 MediaWiki\suppressWarnings();
2598 $text = iconv( $in, $out . '//IGNORE', $string );
2599 MediaWiki\restoreWarnings();
2600 return $text;
2601 }
2602
2603 // callback functions for ucwords(), ucwordbreaks()
2604
2610 return $this->ucfirst( $matches[1] );
2611 }
2612
2618 return mb_strtoupper( $matches[0] );
2619 }
2620
2626 return mb_strtoupper( $matches[0] );
2627 }
2628
2636 function ucfirst( $str ) {
2637 $o = ord( $str );
2638 if ( $o < 96 ) { // if already uppercase...
2639 return $str;
2640 } elseif ( $o < 128 ) {
2641 return ucfirst( $str ); // use PHP's ucfirst()
2642 } else {
2643 // fall back to more complex logic in case of multibyte strings
2644 return $this->uc( $str, true );
2645 }
2646 }
2647
2656 function uc( $str, $first = false ) {
2657 if ( $first ) {
2658 if ( $this->isMultibyte( $str ) ) {
2659 return mb_strtoupper( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
2660 } else {
2661 return ucfirst( $str );
2662 }
2663 } else {
2664 return $this->isMultibyte( $str ) ? mb_strtoupper( $str ) : strtoupper( $str );
2665 }
2666 }
2667
2672 function lcfirst( $str ) {
2673 $o = ord( $str );
2674 if ( !$o ) {
2675 return strval( $str );
2676 } elseif ( $o >= 128 ) {
2677 return $this->lc( $str, true );
2678 } elseif ( $o > 96 ) {
2679 return $str;
2680 } else {
2681 $str[0] = strtolower( $str[0] );
2682 return $str;
2683 }
2684 }
2685
2691 function lc( $str, $first = false ) {
2692 if ( $first ) {
2693 if ( $this->isMultibyte( $str ) ) {
2694 return mb_strtolower( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
2695 } else {
2696 return strtolower( substr( $str, 0, 1 ) ) . substr( $str, 1 );
2697 }
2698 } else {
2699 return $this->isMultibyte( $str ) ? mb_strtolower( $str ) : strtolower( $str );
2700 }
2701 }
2702
2707 function isMultibyte( $str ) {
2708 return strlen( $str ) !== mb_strlen( $str );
2709 }
2710
2715 function ucwords( $str ) {
2716 if ( $this->isMultibyte( $str ) ) {
2717 $str = $this->lc( $str );
2718
2719 // regexp to find first letter in each word (i.e. after each space)
2720 $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)| ([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
2721
2722 // function to use to capitalize a single char
2723 return preg_replace_callback(
2724 $replaceRegexp,
2725 [ $this, 'ucwordsCallbackMB' ],
2726 $str
2727 );
2728 } else {
2729 return ucwords( strtolower( $str ) );
2730 }
2731 }
2732
2739 function ucwordbreaks( $str ) {
2740 if ( $this->isMultibyte( $str ) ) {
2741 $str = $this->lc( $str );
2742
2743 // since \b doesn't work for UTF-8, we explicitely define word break chars
2744 $breaks = "[ \-\‍(\‍)\}\{\.,\?!]";
2745
2746 // find first letter after word break
2747 $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)|" .
2748 "$breaks([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
2749
2750 return preg_replace_callback(
2751 $replaceRegexp,
2752 [ $this, 'ucwordbreaksCallbackMB' ],
2753 $str
2754 );
2755 } else {
2756 return preg_replace_callback(
2757 '/\b([\w\x80-\xff]+)\b/',
2758 [ $this, 'ucwordbreaksCallbackAscii' ],
2759 $str
2760 );
2761 }
2762 }
2763
2779 function caseFold( $s ) {
2780 return $this->uc( $s );
2781 }
2782
2789 if ( is_array( $s ) ) {
2790 throw new MWException( 'Given array to checkTitleEncoding.' );
2791 }
2792 if ( StringUtils::isUtf8( $s ) ) {
2793 return $s;
2794 }
2795
2796 return $this->iconv( $this->fallback8bitEncoding(), 'utf-8', $s );
2797 }
2798
2803 return self::$dataCache->getItem( $this->mCode, 'fallback8bitEncoding' );
2804 }
2805
2814 function hasWordBreaks() {
2815 return true;
2816 }
2817
2825 function segmentByWord( $string ) {
2826 return $string;
2827 }
2828
2836 function normalizeForSearch( $string ) {
2837 return self::convertDoubleWidth( $string );
2838 }
2839
2848 protected static function convertDoubleWidth( $string ) {
2849 static $full = null;
2850 static $half = null;
2851
2852 if ( $full === null ) {
2853 $fullWidth = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
2854 $halfWidth = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
2855 $full = str_split( $fullWidth, 3 );
2856 $half = str_split( $halfWidth );
2857 }
2858
2859 $string = str_replace( $full, $half, $string );
2860 return $string;
2861 }
2862
2868 protected static function insertSpace( $string, $pattern ) {
2869 $string = preg_replace( $pattern, " $1 ", $string );
2870 $string = preg_replace( '/ +/', ' ', $string );
2871 return $string;
2872 }
2873
2878 function convertForSearchResult( $termsArray ) {
2879 # some languages, e.g. Chinese, need to do a conversion
2880 # in order for search results to be displayed correctly
2881 return $termsArray;
2882 }
2883
2890 function firstChar( $s ) {
2891 $matches = [];
2892 preg_match(
2893 '/^([\x00-\x7f]|[\xc0-\xdf][\x80-\xbf]|' .
2894 '[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xf7][\x80-\xbf]{3})/',
2895 $s,
2896 $matches
2897 );
2898
2899 if ( isset( $matches[1] ) ) {
2900 if ( strlen( $matches[1] ) != 3 ) {
2901 return $matches[1];
2902 }
2903
2904 // Break down Hangul syllables to grab the first jamo
2905 $code = UtfNormal\Utils::utf8ToCodepoint( $matches[1] );
2906 if ( $code < 0xac00 || 0xd7a4 <= $code ) {
2907 return $matches[1];
2908 } elseif ( $code < 0xb098 ) {
2909 return "\xe3\x84\xb1";
2910 } elseif ( $code < 0xb2e4 ) {
2911 return "\xe3\x84\xb4";
2912 } elseif ( $code < 0xb77c ) {
2913 return "\xe3\x84\xb7";
2914 } elseif ( $code < 0xb9c8 ) {
2915 return "\xe3\x84\xb9";
2916 } elseif ( $code < 0xbc14 ) {
2917 return "\xe3\x85\x81";
2918 } elseif ( $code < 0xc0ac ) {
2919 return "\xe3\x85\x82";
2920 } elseif ( $code < 0xc544 ) {
2921 return "\xe3\x85\x85";
2922 } elseif ( $code < 0xc790 ) {
2923 return "\xe3\x85\x87";
2924 } elseif ( $code < 0xcc28 ) {
2925 return "\xe3\x85\x88";
2926 } elseif ( $code < 0xce74 ) {
2927 return "\xe3\x85\x8a";
2928 } elseif ( $code < 0xd0c0 ) {
2929 return "\xe3\x85\x8b";
2930 } elseif ( $code < 0xd30c ) {
2931 return "\xe3\x85\x8c";
2932 } elseif ( $code < 0xd558 ) {
2933 return "\xe3\x85\x8d";
2934 } else {
2935 return "\xe3\x85\x8e";
2936 }
2937 } else {
2938 return '';
2939 }
2940 }
2941
2942 function initEncoding() {
2943 # Some languages may have an alternate char encoding option
2944 # (Esperanto X-coding, Japanese furigana conversion, etc)
2945 # If this language is used as the primary content language,
2946 # an override to the defaults can be set here on startup.
2947 }
2948
2953 function recodeForEdit( $s ) {
2954 # For some languages we'll want to explicitly specify
2955 # which characters make it into the edit box raw
2956 # or are converted in some way or another.
2958 if ( $wgEditEncoding == '' || $wgEditEncoding == 'UTF-8' ) {
2959 return $s;
2960 } else {
2961 return $this->iconv( 'UTF-8', $wgEditEncoding, $s );
2962 }
2963 }
2964
2969 function recodeInput( $s ) {
2970 # Take the previous into account.
2972 if ( $wgEditEncoding != '' ) {
2973 $enc = $wgEditEncoding;
2974 } else {
2975 $enc = 'UTF-8';
2976 }
2977 if ( $enc == 'UTF-8' ) {
2978 return $s;
2979 } else {
2980 return $this->iconv( $enc, 'UTF-8', $s );
2981 }
2982 }
2983
2995 function normalize( $s ) {
2997 $s = UtfNormal\Validator::cleanUp( $s );
2998 if ( $wgAllUnicodeFixes ) {
2999 $s = $this->transformUsingPairFile( 'normalize-ar.ser', $s );
3000 $s = $this->transformUsingPairFile( 'normalize-ml.ser', $s );
3001 }
3002
3003 return $s;
3004 }
3005
3020 function transformUsingPairFile( $file, $string ) {
3021 if ( !isset( $this->transformData[$file] ) ) {
3022 $data = wfGetPrecompiledData( $file );
3023 if ( $data === false ) {
3024 throw new MWException( __METHOD__ . ": The transformation file $file is missing" );
3025 }
3026 $this->transformData[$file] = new ReplacementArray( $data );
3027 }
3028 return $this->transformData[$file]->replace( $string );
3029 }
3030
3036 function isRTL() {
3037 return self::$dataCache->getItem( $this->mCode, 'rtl' );
3038 }
3039
3044 function getDir() {
3045 return $this->isRTL() ? 'rtl' : 'ltr';
3046 }
3047
3056 function alignStart() {
3057 return $this->isRTL() ? 'right' : 'left';
3058 }
3059
3068 function alignEnd() {
3069 return $this->isRTL() ? 'left' : 'right';
3070 }
3071
3083 function getDirMarkEntity( $opposite = false ) {
3084 if ( $opposite ) {
3085 return $this->isRTL() ? '&lrm;' : '&rlm;';
3086 }
3087 return $this->isRTL() ? '&rlm;' : '&lrm;';
3088 }
3089
3100 function getDirMark( $opposite = false ) {
3101 $lrm = "\xE2\x80\x8E"; # LEFT-TO-RIGHT MARK, commonly abbreviated LRM
3102 $rlm = "\xE2\x80\x8F"; # RIGHT-TO-LEFT MARK, commonly abbreviated RLM
3103 if ( $opposite ) {
3104 return $this->isRTL() ? $lrm : $rlm;
3105 }
3106 return $this->isRTL() ? $rlm : $lrm;
3107 }
3108
3113 return self::$dataCache->getItem( $this->mCode, 'capitalizeAllNouns' );
3114 }
3115
3123 function getArrow( $direction = 'forwards' ) {
3124 switch ( $direction ) {
3125 case 'forwards':
3126 return $this->isRTL() ? '←' : '→';
3127 case 'backwards':
3128 return $this->isRTL() ? '→' : '←';
3129 case 'left':
3130 return '←';
3131 case 'right':
3132 return '→';
3133 case 'up':
3134 return '↑';
3135 case 'down':
3136 return '↓';
3137 }
3138 }
3139
3146 return self::$dataCache->getItem( $this->mCode, 'linkPrefixExtension' );
3147 }
3148
3153 function getMagicWords() {
3154 return self::$dataCache->getItem( $this->mCode, 'magicWords' );
3155 }
3156
3160 protected function doMagicHook() {
3161 if ( $this->mMagicHookDone ) {
3162 return;
3163 }
3164 $this->mMagicHookDone = true;
3165 Hooks::run( 'LanguageGetMagic', [ &$this->mMagicExtensions, $this->getCode() ] );
3166 }
3167
3173 function getMagic( $mw ) {
3174 // Saves a function call
3175 if ( !$this->mMagicHookDone ) {
3176 $this->doMagicHook();
3177 }
3178
3179 if ( isset( $this->mMagicExtensions[$mw->mId] ) ) {
3180 $rawEntry = $this->mMagicExtensions[$mw->mId];
3181 } else {
3182 $rawEntry = self::$dataCache->getSubitem(
3183 $this->mCode, 'magicWords', $mw->mId );
3184 }
3185
3186 if ( !is_array( $rawEntry ) ) {
3187 wfWarn( "\"$rawEntry\" is not a valid magic word for \"$mw->mId\"" );
3188 } else {
3189 $mw->mCaseSensitive = $rawEntry[0];
3190 $mw->mSynonyms = array_slice( $rawEntry, 1 );
3191 }
3192 }
3193
3199 function addMagicWordsByLang( $newWords ) {
3200 $fallbackChain = $this->getFallbackLanguages();
3201 $fallbackChain = array_reverse( $fallbackChain );
3202 foreach ( $fallbackChain as $code ) {
3203 if ( isset( $newWords[$code] ) ) {
3204 $this->mMagicExtensions = $newWords[$code] + $this->mMagicExtensions;
3205 }
3206 }
3207 }
3208
3215 // Cache aliases because it may be slow to load them
3216 if ( is_null( $this->mExtendedSpecialPageAliases ) ) {
3217 // Initialise array
3218 $this->mExtendedSpecialPageAliases =
3219 self::$dataCache->getItem( $this->mCode, 'specialPageAliases' );
3220 Hooks::run( 'LanguageGetSpecialPageAliases',
3221 [ &$this->mExtendedSpecialPageAliases, $this->getCode() ] );
3222 }
3223
3224 return $this->mExtendedSpecialPageAliases;
3225 }
3226
3233 function emphasize( $text ) {
3234 return "<em>$text</em>";
3235 }
3236
3259 public function formatNum( $number, $nocommafy = false ) {
3261 if ( !$nocommafy ) {
3262 $number = $this->commafy( $number );
3263 $s = $this->separatorTransformTable();
3264 if ( $s ) {
3265 $number = strtr( $number, $s );
3266 }
3267 }
3268
3269 if ( $wgTranslateNumerals ) {
3270 $s = $this->digitTransformTable();
3271 if ( $s ) {
3272 $number = strtr( $number, $s );
3273 }
3274 }
3275
3276 return $number;
3277 }
3278
3287 public function formatNumNoSeparators( $number ) {
3288 return $this->formatNum( $number, true );
3289 }
3290
3295 public function parseFormattedNumber( $number ) {
3296 $s = $this->digitTransformTable();
3297 if ( $s ) {
3298 // eliminate empty array values such as ''. (bug 64347)
3299 $s = array_filter( $s );
3300 $number = strtr( $number, array_flip( $s ) );
3301 }
3302
3303 $s = $this->separatorTransformTable();
3304 if ( $s ) {
3305 // eliminate empty array values such as ''. (bug 64347)
3306 $s = array_filter( $s );
3307 $number = strtr( $number, array_flip( $s ) );
3308 }
3309
3310 $number = strtr( $number, [ ',' => '' ] );
3311 return $number;
3312 }
3313
3320 function commafy( $number ) {
3322 if ( $number === null ) {
3323 return '';
3324 }
3325
3326 if ( !$digitGroupingPattern || $digitGroupingPattern === "###,###,###" ) {
3327 // default grouping is at thousands, use the same for ###,###,### pattern too.
3328 return strrev( (string)preg_replace( '/(\d{3})(?=\d)(?!\d*\.)/', '$1,', strrev( $number ) ) );
3329 } else {
3330 // Ref: http://cldr.unicode.org/translation/number-patterns
3331 $sign = "";
3332 if ( intval( $number ) < 0 ) {
3333 // For negative numbers apply the algorithm like positive number and add sign.
3334 $sign = "-";
3335 $number = substr( $number, 1 );
3336 }
3337 $integerPart = [];
3338 $decimalPart = [];
3339 $numMatches = preg_match_all( "/(#+)/", $digitGroupingPattern, $matches );
3340 preg_match( "/\d+/", $number, $integerPart );
3341 preg_match( "/\.\d*/", $number, $decimalPart );
3342 $groupedNumber = ( count( $decimalPart ) > 0 ) ? $decimalPart[0] : "";
3343 if ( $groupedNumber === $number ) {
3344 // the string does not have any number part. Eg: .12345
3345 return $sign . $groupedNumber;
3346 }
3347 $start = $end = ( $integerPart ) ? strlen( $integerPart[0] ) : 0;
3348 while ( $start > 0 ) {
3349 $match = $matches[0][$numMatches - 1];
3350 $matchLen = strlen( $match );
3351 $start = $end - $matchLen;
3352 if ( $start < 0 ) {
3353 $start = 0;
3354 }
3355 $groupedNumber = substr( $number, $start, $end -$start ) . $groupedNumber;
3356 $end = $start;
3357 if ( $numMatches > 1 ) {
3358 // use the last pattern for the rest of the number
3359 $numMatches--;
3360 }
3361 if ( $start > 0 ) {
3362 $groupedNumber = "," . $groupedNumber;
3363 }
3364 }
3365 return $sign . $groupedNumber;
3366 }
3367 }
3368
3373 return self::$dataCache->getItem( $this->mCode, 'digitGroupingPattern' );
3374 }
3375
3380 return self::$dataCache->getItem( $this->mCode, 'digitTransformTable' );
3381 }
3382
3387 return self::$dataCache->getItem( $this->mCode, 'separatorTransformTable' );
3388 }
3389
3399 function listToText( array $l ) {
3400 $m = count( $l ) - 1;
3401 if ( $m < 0 ) {
3402 return '';
3403 }
3404 if ( $m > 0 ) {
3405 $and = $this->msg( 'and' )->escaped();
3406 $space = $this->msg( 'word-separator' )->escaped();
3407 if ( $m > 1 ) {
3408 $comma = $this->msg( 'comma-separator' )->escaped();
3409 }
3410 }
3411 $s = $l[$m];
3412 for ( $i = $m - 1; $i >= 0; $i-- ) {
3413 if ( $i == $m - 1 ) {
3414 $s = $l[$i] . $and . $space . $s;
3415 } else {
3416 $s = $l[$i] . $comma . $s;
3417 }
3418 }
3419 return $s;
3420 }
3421
3428 function commaList( array $list ) {
3429 return implode(
3430 wfMessage( 'comma-separator' )->inLanguage( $this )->escaped(),
3431 $list
3432 );
3433 }
3434
3441 function semicolonList( array $list ) {
3442 return implode(
3443 wfMessage( 'semicolon-separator' )->inLanguage( $this )->escaped(),
3444 $list
3445 );
3446 }
3447
3453 function pipeList( array $list ) {
3454 return implode(
3455 wfMessage( 'pipe-separator' )->inLanguage( $this )->escaped(),
3456 $list
3457 );
3458 }
3459
3477 function truncate( $string, $length, $ellipsis = '...', $adjustLength = true ) {
3478 # Use the localized ellipsis character
3479 if ( $ellipsis == '...' ) {
3480 $ellipsis = wfMessage( 'ellipsis' )->inLanguage( $this )->escaped();
3481 }
3482 # Check if there is no need to truncate
3483 if ( $length == 0 ) {
3484 return $ellipsis; // convention
3485 } elseif ( strlen( $string ) <= abs( $length ) ) {
3486 return $string; // no need to truncate
3487 }
3488 $stringOriginal = $string;
3489 # If ellipsis length is >= $length then we can't apply $adjustLength
3490 if ( $adjustLength && strlen( $ellipsis ) >= abs( $length ) ) {
3491 $string = $ellipsis; // this can be slightly unexpected
3492 # Otherwise, truncate and add ellipsis...
3493 } else {
3494 $eLength = $adjustLength ? strlen( $ellipsis ) : 0;
3495 if ( $length > 0 ) {
3496 $length -= $eLength;
3497 $string = substr( $string, 0, $length ); // xyz...
3498 $string = $this->removeBadCharLast( $string );
3499 $string = rtrim( $string );
3500 $string = $string . $ellipsis;
3501 } else {
3502 $length += $eLength;
3503 $string = substr( $string, $length ); // ...xyz
3504 $string = $this->removeBadCharFirst( $string );
3505 $string = ltrim( $string );
3506 $string = $ellipsis . $string;
3507 }
3508 }
3509 # Do not truncate if the ellipsis makes the string longer/equal (bug 22181).
3510 # This check is *not* redundant if $adjustLength, due to the single case where
3511 # LEN($ellipsis) > ABS($limit arg); $stringOriginal could be shorter than $string.
3512 if ( strlen( $string ) < strlen( $stringOriginal ) ) {
3513 return $string;
3514 } else {
3515 return $stringOriginal;
3516 }
3517 }
3518
3526 protected function removeBadCharLast( $string ) {
3527 if ( $string != '' ) {
3528 $char = ord( $string[strlen( $string ) - 1] );
3529 $m = [];
3530 if ( $char >= 0xc0 ) {
3531 # We got the first byte only of a multibyte char; remove it.
3532 $string = substr( $string, 0, -1 );
3533 } elseif ( $char >= 0x80 &&
3534 // Use the /s modifier (PCRE_DOTALL) so (.*) also matches newlines
3535 preg_match( '/^(.*)(?:[\xe0-\xef][\x80-\xbf]|' .
3536 '[\xf0-\xf7][\x80-\xbf]{1,2})$/s', $string, $m )
3537 ) {
3538 # We chopped in the middle of a character; remove it
3539 $string = $m[1];
3540 }
3541 }
3542 return $string;
3543 }
3544
3552 protected function removeBadCharFirst( $string ) {
3553 if ( $string != '' ) {
3554 $char = ord( $string[0] );
3555 if ( $char >= 0x80 && $char < 0xc0 ) {
3556 # We chopped in the middle of a character; remove the whole thing
3557 $string = preg_replace( '/^[\x80-\xbf]+/', '', $string );
3558 }
3559 }
3560 return $string;
3561 }
3562
3578 function truncateHtml( $text, $length, $ellipsis = '...' ) {
3579 # Use the localized ellipsis character
3580 if ( $ellipsis == '...' ) {
3581 $ellipsis = wfMessage( 'ellipsis' )->inLanguage( $this )->escaped();
3582 }
3583 # Check if there is clearly no need to truncate
3584 if ( $length <= 0 ) {
3585 return $ellipsis; // no text shown, nothing to format (convention)
3586 } elseif ( strlen( $text ) <= $length ) {
3587 return $text; // string short enough even *with* HTML (short-circuit)
3588 }
3589
3590 $dispLen = 0; // innerHTML legth so far
3591 $testingEllipsis = false; // checking if ellipses will make string longer/equal?
3592 $tagType = 0; // 0-open, 1-close
3593 $bracketState = 0; // 1-tag start, 2-tag name, 0-neither
3594 $entityState = 0; // 0-not entity, 1-entity
3595 $tag = $ret = ''; // accumulated tag name, accumulated result string
3596 $openTags = []; // open tag stack
3597 $maybeState = null; // possible truncation state
3598
3599 $textLen = strlen( $text );
3600 $neLength = max( 0, $length - strlen( $ellipsis ) ); // non-ellipsis len if truncated
3601 for ( $pos = 0; true; ++$pos ) {
3602 # Consider truncation once the display length has reached the maximim.
3603 # We check if $dispLen > 0 to grab tags for the $neLength = 0 case.
3604 # Check that we're not in the middle of a bracket/entity...
3605 if ( $dispLen && $dispLen >= $neLength && $bracketState == 0 && !$entityState ) {
3606 if ( !$testingEllipsis ) {
3607 $testingEllipsis = true;
3608 # Save where we are; we will truncate here unless there turn out to
3609 # be so few remaining characters that truncation is not necessary.
3610 if ( !$maybeState ) { // already saved? ($neLength = 0 case)
3611 $maybeState = [ $ret, $openTags ]; // save state
3612 }
3613 } elseif ( $dispLen > $length && $dispLen > strlen( $ellipsis ) ) {
3614 # String in fact does need truncation, the truncation point was OK.
3615 list( $ret, $openTags ) = $maybeState; // reload state
3616 $ret = $this->removeBadCharLast( $ret ); // multi-byte char fix
3617 $ret .= $ellipsis; // add ellipsis
3618 break;
3619 }
3620 }
3621 if ( $pos >= $textLen ) {
3622 break; // extra iteration just for above checks
3623 }
3624
3625 # Read the next char...
3626 $ch = $text[$pos];
3627 $lastCh = $pos ? $text[$pos - 1] : '';
3628 $ret .= $ch; // add to result string
3629 if ( $ch == '<' ) {
3630 $this->truncate_endBracket( $tag, $tagType, $lastCh, $openTags ); // for bad HTML
3631 $entityState = 0; // for bad HTML
3632 $bracketState = 1; // tag started (checking for backslash)
3633 } elseif ( $ch == '>' ) {
3634 $this->truncate_endBracket( $tag, $tagType, $lastCh, $openTags );
3635 $entityState = 0; // for bad HTML
3636 $bracketState = 0; // out of brackets
3637 } elseif ( $bracketState == 1 ) {
3638 if ( $ch == '/' ) {
3639 $tagType = 1; // close tag (e.g. "</span>")
3640 } else {
3641 $tagType = 0; // open tag (e.g. "<span>")
3642 $tag .= $ch;
3643 }
3644 $bracketState = 2; // building tag name
3645 } elseif ( $bracketState == 2 ) {
3646 if ( $ch != ' ' ) {
3647 $tag .= $ch;
3648 } else {
3649 // Name found (e.g. "<a href=..."), add on tag attributes...
3650 $pos += $this->truncate_skip( $ret, $text, "<>", $pos + 1 );
3651 }
3652 } elseif ( $bracketState == 0 ) {
3653 if ( $entityState ) {
3654 if ( $ch == ';' ) {
3655 $entityState = 0;
3656 $dispLen++; // entity is one displayed char
3657 }
3658 } else {
3659 if ( $neLength == 0 && !$maybeState ) {
3660 // Save state without $ch. We want to *hit* the first
3661 // display char (to get tags) but not *use* it if truncating.
3662 $maybeState = [ substr( $ret, 0, -1 ), $openTags ];
3663 }
3664 if ( $ch == '&' ) {
3665 $entityState = 1; // entity found, (e.g. "&#160;")
3666 } else {
3667 $dispLen++; // this char is displayed
3668 // Add the next $max display text chars after this in one swoop...
3669 $max = ( $testingEllipsis ? $length : $neLength ) - $dispLen;
3670 $skipped = $this->truncate_skip( $ret, $text, "<>&", $pos + 1, $max );
3671 $dispLen += $skipped;
3672 $pos += $skipped;
3673 }
3674 }
3675 }
3676 }
3677 // Close the last tag if left unclosed by bad HTML
3678 $this->truncate_endBracket( $tag, $text[$textLen - 1], $tagType, $openTags );
3679 while ( count( $openTags ) > 0 ) {
3680 $ret .= '</' . array_pop( $openTags ) . '>'; // close open tags
3681 }
3682 return $ret;
3683 }
3684
3696 private function truncate_skip( &$ret, $text, $search, $start, $len = null ) {
3697 if ( $len === null ) {
3698 $len = -1; // -1 means "no limit" for strcspn
3699 } elseif ( $len < 0 ) {
3700 $len = 0; // sanity
3701 }
3702 $skipCount = 0;
3703 if ( $start < strlen( $text ) ) {
3704 $skipCount = strcspn( $text, $search, $start, $len );
3705 $ret .= substr( $text, $start, $skipCount );
3706 }
3707 return $skipCount;
3708 }
3709
3719 private function truncate_endBracket( &$tag, $tagType, $lastCh, &$openTags ) {
3720 $tag = ltrim( $tag );
3721 if ( $tag != '' ) {
3722 if ( $tagType == 0 && $lastCh != '/' ) {
3723 $openTags[] = $tag; // tag opened (didn't close itself)
3724 } elseif ( $tagType == 1 ) {
3725 if ( $openTags && $tag == $openTags[count( $openTags ) - 1] ) {
3726 array_pop( $openTags ); // tag closed
3727 }
3728 }
3729 $tag = '';
3730 }
3731 }
3732
3741 function convertGrammar( $word, $case ) {
3743 if ( isset( $wgGrammarForms[$this->getCode()][$case][$word] ) ) {
3744 return $wgGrammarForms[$this->getCode()][$case][$word];
3745 }
3746
3747 return $word;
3748 }
3754 function getGrammarForms() {
3756 if ( isset( $wgGrammarForms[$this->getCode()] )
3757 && is_array( $wgGrammarForms[$this->getCode()] )
3758 ) {
3759 return $wgGrammarForms[$this->getCode()];
3760 }
3761
3762 return [];
3763 }
3783 function gender( $gender, $forms ) {
3784 if ( !count( $forms ) ) {
3785 return '';
3786 }
3787 $forms = $this->preConvertPlural( $forms, 2 );
3788 if ( $gender === 'male' ) {
3789 return $forms[0];
3790 }
3791 if ( $gender === 'female' ) {
3792 return $forms[1];
3793 }
3794 return isset( $forms[2] ) ? $forms[2] : $forms[0];
3795 }
3796
3812 function convertPlural( $count, $forms ) {
3813 // Handle explicit n=pluralform cases
3814 $forms = $this->handleExplicitPluralForms( $count, $forms );
3815 if ( is_string( $forms ) ) {
3816 return $forms;
3817 }
3818 if ( !count( $forms ) ) {
3819 return '';
3820 }
3821
3822 $pluralForm = $this->getPluralRuleIndexNumber( $count );
3823 $pluralForm = min( $pluralForm, count( $forms ) - 1 );
3824 return $forms[$pluralForm];
3825 }
3826
3842 protected function handleExplicitPluralForms( $count, array $forms ) {
3843 foreach ( $forms as $index => $form ) {
3844 if ( preg_match( '/\d+=/i', $form ) ) {
3845 $pos = strpos( $form, '=' );
3846 if ( substr( $form, 0, $pos ) === (string)$count ) {
3847 return substr( $form, $pos + 1 );
3848 }
3849 unset( $forms[$index] );
3850 }
3851 }
3852 return array_values( $forms );
3853 }
3854
3863 protected function preConvertPlural( /* Array */ $forms, $count ) {
3864 while ( count( $forms ) < $count ) {
3865 $forms[] = $forms[count( $forms ) - 1];
3866 }
3867 return $forms;
3868 }
3869
3886 public function embedBidi( $text = '' ) {
3888 if ( $dir === 'ltr' ) {
3889 // Wrap in LEFT-TO-RIGHT EMBEDDING ... POP DIRECTIONAL FORMATTING
3890 return self::$lre . $text . self::$pdf;
3891 }
3892 if ( $dir === 'rtl' ) {
3893 // Wrap in RIGHT-TO-LEFT EMBEDDING ... POP DIRECTIONAL FORMATTING
3894 return self::$rle . $text . self::$pdf;
3895 }
3896 // No strong directionality: do not wrap
3897 return $text;
3898 }
3899
3912 function translateBlockExpiry( $str, User $user = null ) {
3913 $duration = SpecialBlock::getSuggestedDurations( $this );
3914 foreach ( $duration as $show => $value ) {
3915 if ( strcmp( $str, $value ) == 0 ) {
3916 return htmlspecialchars( trim( $show ) );
3917 }
3918 }
3919
3920 if ( wfIsInfinity( $str ) ) {
3921 foreach ( $duration as $show => $value ) {
3922 if ( wfIsInfinity( $value ) ) {
3923 return htmlspecialchars( trim( $show ) );
3924 }
3925 }
3926 }
3927
3928 // If all else fails, return a standard duration or timestamp description.
3929 $time = strtotime( $str, 0 );
3930 if ( $time === false ) { // Unknown format. Return it as-is in case.
3931 return $str;
3932 } elseif ( $time !== strtotime( $str, 1 ) ) { // It's a relative timestamp.
3933 // $time is relative to 0 so it's a duration length.
3934 return $this->formatDuration( $time );
3935 } else { // It's an absolute timestamp.
3936 if ( $time === 0 ) {
3937 // wfTimestamp() handles 0 as current time instead of epoch.
3938 $time = '19700101000000';
3939 }
3940 if ( $user ) {
3941 return $this->userTimeAndDate( $time, $user );
3942 }
3943 return $this->timeanddate( $time );
3944 }
3945 }
3946
3954 public function segmentForDiff( $text ) {
3955 return $text;
3956 }
3957
3964 public function unsegmentForDiff( $text ) {
3965 return $text;
3966 }
3967
3974 public function getConverter() {
3975 return $this->mConverter;
3976 }
3977
3984 public function autoConvertToAllVariants( $text ) {
3985 return $this->mConverter->autoConvertToAllVariants( $text );
3986 }
3987
3994 public function convert( $text ) {
3995 return $this->mConverter->convert( $text );
3996 }
3997
4004 public function convertTitle( $title ) {
4005 return $this->mConverter->convertTitle( $title );
4006 }
4007
4014 public function convertNamespace( $ns ) {
4015 return $this->mConverter->convertNamespace( $ns );
4016 }
4017
4023 public function hasVariants() {
4024 return count( $this->getVariants() ) > 1;
4025 }
4026
4034 public function hasVariant( $variant ) {
4035 return (bool)$this->mConverter->validateVariant( $variant );
4036 }
4037
4045 public function convertHtml( $text, $isTitle = false ) {
4046 return htmlspecialchars( $this->convert( $text, $isTitle ) );
4047 }
4048
4053 public function convertCategoryKey( $key ) {
4054 return $this->mConverter->convertCategoryKey( $key );
4055 }
4056
4063 public function getVariants() {
4064 return $this->mConverter->getVariants();
4065 }
4066
4070 public function getPreferredVariant() {
4071 return $this->mConverter->getPreferredVariant();
4072 }
4073
4077 public function getDefaultVariant() {
4078 return $this->mConverter->getDefaultVariant();
4079 }
4080
4084 public function getURLVariant() {
4085 return $this->mConverter->getURLVariant();
4086 }
4087
4100 public function findVariantLink( &$link, &$nt, $ignoreOtherCond = false ) {
4101 $this->mConverter->findVariantLink( $link, $nt, $ignoreOtherCond );
4102 }
4103
4111 return $this->mConverter->getExtraHashOptions();
4112 }
4113
4121 public function getParsedTitle() {
4122 return $this->mConverter->getParsedTitle();
4123 }
4124
4131 public function updateConversionTable( Title $title ) {
4132 $this->mConverter->updateConversionTable( $title );
4133 }
4134
4147 public function markNoConversion( $text, $noParse = false ) {
4148 // Excluding protocal-relative URLs may avoid many false positives.
4149 if ( $noParse || preg_match( '/^(?:' . wfUrlProtocolsWithoutProtRel() . ')/', $text ) ) {
4150 return $this->mConverter->markNoConversion( $text );
4151 } else {
4152 return $text;
4153 }
4154 }
4155
4162 public function linkTrail() {
4163 return self::$dataCache->getItem( $this->mCode, 'linkTrail' );
4164 }
4165
4172 public function linkPrefixCharset() {
4173 return self::$dataCache->getItem( $this->mCode, 'linkPrefixCharset' );
4174 }
4175
4183 public function getParentLanguage() {
4184 if ( $this->mParentLanguage !== false ) {
4185 return $this->mParentLanguage;
4186 }
4187
4188 $code = explode( '-', $this->getCode() )[0];
4190 $this->mParentLanguage = null;
4191 return null;
4192 }
4194 if ( !$lang->hasVariant( $this->getCode() ) ) {
4195 $this->mParentLanguage = null;
4196 return null;
4197 }
4198
4199 $this->mParentLanguage = $lang;
4200 return $lang;
4201 }
4202
4211 public function getCode() {
4212 return $this->mCode;
4213 }
4214
4225 public function getHtmlCode() {
4226 if ( is_null( $this->mHtmlCode ) ) {
4227 $this->mHtmlCode = wfBCP47( $this->getCode() );
4228 }
4229 return $this->mHtmlCode;
4230 }
4231
4235 public function setCode( $code ) {
4236 $this->mCode = $code;
4237 // Ensure we don't leave incorrect cached data lying around
4238 $this->mHtmlCode = null;
4239 $this->mParentLanguage = false;
4240 }
4241
4249 public static function getCodeFromFileName( $filename, $prefix = 'Language', $suffix = '.php' ) {
4250 $m = null;
4251 preg_match( '/' . preg_quote( $prefix, '/' ) . '([A-Z][a-z_]+)' .
4252 preg_quote( $suffix, '/' ) . '/', $filename, $m );
4253 if ( !count( $m ) ) {
4254 return false;
4255 }
4256 return str_replace( '_', '-', strtolower( $m[1] ) );
4257 }
4258
4263 public static function classFromCode( $code ) {
4264 if ( $code == 'en' ) {
4265 return 'Language';
4266 } else {
4267 return 'Language' . str_replace( '-', '_', ucfirst( $code ) );
4268 }
4269 }
4270
4279 public static function getFileName( $prefix = 'Language', $code, $suffix = '.php' ) {
4280 if ( !self::isValidBuiltInCode( $code ) ) {
4281 throw new MWException( "Invalid language code \"$code\"" );
4282 }
4283
4284 return $prefix . str_replace( '-', '_', ucfirst( $code ) ) . $suffix;
4285 }
4286
4291 public static function getMessagesFileName( $code ) {
4292 global $IP;
4293 $file = self::getFileName( "$IP/languages/messages/Messages", $code, '.php' );
4294 Hooks::run( 'Language::getMessagesFileName', [ $code, &$file ] );
4295 return $file;
4296 }
4297
4304 public static function getJsonMessagesFileName( $code ) {
4305 global $IP;
4306
4307 if ( !self::isValidBuiltInCode( $code ) ) {
4308 throw new MWException( "Invalid language code \"$code\"" );
4309 }
4310
4311 return "$IP/languages/i18n/$code.json";
4312 }
4313
4321 public static function getFallbackFor( $code ) {
4322 $fallbacks = self::getFallbacksFor( $code );
4323 if ( $fallbacks ) {
4324 return $fallbacks[0];
4325 }
4326 return false;
4327 }
4328
4336 public static function getFallbacksFor( $code ) {
4337 if ( $code === 'en' || !Language::isValidBuiltInCode( $code ) ) {
4338 return [];
4339 }
4340 // For unknown languages, fallbackSequence returns an empty array,
4341 // hardcode fallback to 'en' in that case.
4342 return self::getLocalisationCache()->getItem( $code, 'fallbackSequence' ) ?: [ 'en' ];
4343 }
4344
4353 public static function getFallbacksIncludingSiteLanguage( $code ) {
4355
4356 // Usually, we will only store a tiny number of fallback chains, so we
4357 // keep them in static memory.
4358 $cacheKey = "{$code}-{$wgLanguageCode}";
4359
4360 if ( !array_key_exists( $cacheKey, self::$fallbackLanguageCache ) ) {
4361 $fallbacks = self::getFallbacksFor( $code );
4362
4363 // Append the site's fallback chain, including the site language itself
4364 $siteFallbacks = self::getFallbacksFor( $wgLanguageCode );
4365 array_unshift( $siteFallbacks, $wgLanguageCode );
4366
4367 // Eliminate any languages already included in the chain
4368 $siteFallbacks = array_diff( $siteFallbacks, $fallbacks );
4369
4370 self::$fallbackLanguageCache[$cacheKey] = [ $fallbacks, $siteFallbacks ];
4371 }
4372 return self::$fallbackLanguageCache[$cacheKey];
4373 }
4374
4384 public static function getMessagesFor( $code ) {
4385 return self::getLocalisationCache()->getItem( $code, 'messages' );
4386 }
4387
4396 public static function getMessageFor( $key, $code ) {
4397 return self::getLocalisationCache()->getSubitem( $code, 'messages', $key );
4398 }
4399
4408 public static function getMessageKeysFor( $code ) {
4409 return self::getLocalisationCache()->getSubitemList( $code, 'messages' );
4410 }
4411
4416 function fixVariableInNamespace( $talk ) {
4417 if ( strpos( $talk, '$1' ) === false ) {
4418 return $talk;
4419 }
4420
4422 $talk = str_replace( '$1', $wgMetaNamespace, $talk );
4423
4424 # Allow grammar transformations
4425 # Allowing full message-style parsing would make simple requests
4426 # such as action=raw much more expensive than they need to be.
4427 # This will hopefully cover most cases.
4428 $talk = preg_replace_callback( '/{{grammar:(.*?)\|(.*?)}}/i',
4429 [ $this, 'replaceGrammarInNamespace' ], $talk );
4430 return str_replace( ' ', '_', $talk );
4431 }
4432
4438 return $this->convertGrammar( trim( $m[2] ), trim( $m[1] ) );
4439 }
4440
4451 public function formatExpiry( $expiry, $format = true, $infinity = 'infinity' ) {
4452 static $dbInfinity;
4453 if ( $dbInfinity === null ) {
4454 $dbInfinity = wfGetDB( DB_SLAVE )->getInfinity();
4455 }
4456
4457 if ( $expiry == '' || $expiry === 'infinity' || $expiry == $dbInfinity ) {
4458 return $format === true
4459 ? $this->getMessageFromDB( 'infiniteblock' )
4460 : $infinity;
4461 } else {
4462 return $format === true
4463 ? $this->timeanddate( $expiry, /* User preference timezone */ true )
4464 : wfTimestamp( $format, $expiry );
4465 }
4466 }
4467
4480 function formatTimePeriod( $seconds, $format = [] ) {
4481 if ( !is_array( $format ) ) {
4482 $format = [ 'avoid' => $format ]; // For backwards compatibility
4483 }
4484 if ( !isset( $format['avoid'] ) ) {
4485 $format['avoid'] = false;
4486 }
4487 if ( !isset( $format['noabbrevs'] ) ) {
4488 $format['noabbrevs'] = false;
4489 }
4490 $secondsMsg = wfMessage(
4491 $format['noabbrevs'] ? 'seconds' : 'seconds-abbrev' )->inLanguage( $this );
4492 $minutesMsg = wfMessage(
4493 $format['noabbrevs'] ? 'minutes' : 'minutes-abbrev' )->inLanguage( $this );
4494 $hoursMsg = wfMessage(
4495 $format['noabbrevs'] ? 'hours' : 'hours-abbrev' )->inLanguage( $this );
4496 $daysMsg = wfMessage(
4497 $format['noabbrevs'] ? 'days' : 'days-abbrev' )->inLanguage( $this );
4498
4499 if ( round( $seconds * 10 ) < 100 ) {
4500 $s = $this->formatNum( sprintf( "%.1f", round( $seconds * 10 ) / 10 ) );
4501 $s = $secondsMsg->params( $s )->text();
4502 } elseif ( round( $seconds ) < 60 ) {
4503 $s = $this->formatNum( round( $seconds ) );
4504 $s = $secondsMsg->params( $s )->text();
4505 } elseif ( round( $seconds ) < 3600 ) {
4506 $minutes = floor( $seconds / 60 );
4507 $secondsPart = round( fmod( $seconds, 60 ) );
4508 if ( $secondsPart == 60 ) {
4509 $secondsPart = 0;
4510 $minutes++;
4511 }
4512 $s = $minutesMsg->params( $this->formatNum( $minutes ) )->text();
4513 $s .= ' ';
4514 $s .= $secondsMsg->params( $this->formatNum( $secondsPart ) )->text();
4515 } elseif ( round( $seconds ) <= 2 * 86400 ) {
4516 $hours = floor( $seconds / 3600 );
4517 $minutes = floor( ( $seconds - $hours * 3600 ) / 60 );
4518 $secondsPart = round( $seconds - $hours * 3600 - $minutes * 60 );
4519 if ( $secondsPart == 60 ) {
4520 $secondsPart = 0;
4521 $minutes++;
4522 }
4523 if ( $minutes == 60 ) {
4524 $minutes = 0;
4525 $hours++;
4526 }
4527 $s = $hoursMsg->params( $this->formatNum( $hours ) )->text();
4528 $s .= ' ';
4529 $s .= $minutesMsg->params( $this->formatNum( $minutes ) )->text();
4530 if ( !in_array( $format['avoid'], [ 'avoidseconds', 'avoidminutes' ] ) ) {
4531 $s .= ' ' . $secondsMsg->params( $this->formatNum( $secondsPart ) )->text();
4532 }
4533 } else {
4534 $days = floor( $seconds / 86400 );
4535 if ( $format['avoid'] === 'avoidminutes' ) {
4536 $hours = round( ( $seconds - $days * 86400 ) / 3600 );
4537 if ( $hours == 24 ) {
4538 $hours = 0;
4539 $days++;
4540 }
4541 $s = $daysMsg->params( $this->formatNum( $days ) )->text();
4542 $s .= ' ';
4543 $s .= $hoursMsg->params( $this->formatNum( $hours ) )->text();
4544 } elseif ( $format['avoid'] === 'avoidseconds' ) {
4545 $hours = floor( ( $seconds - $days * 86400 ) / 3600 );
4546 $minutes = round( ( $seconds - $days * 86400 - $hours * 3600 ) / 60 );
4547 if ( $minutes == 60 ) {
4548 $minutes = 0;
4549 $hours++;
4550 }
4551 if ( $hours == 24 ) {
4552 $hours = 0;
4553 $days++;
4554 }
4555 $s = $daysMsg->params( $this->formatNum( $days ) )->text();
4556 $s .= ' ';
4557 $s .= $hoursMsg->params( $this->formatNum( $hours ) )->text();
4558 $s .= ' ';
4559 $s .= $minutesMsg->params( $this->formatNum( $minutes ) )->text();
4560 } else {
4561 $s = $daysMsg->params( $this->formatNum( $days ) )->text();
4562 $s .= ' ';
4563 $s .= $this->formatTimePeriod( $seconds - $days * 86400, $format );
4564 }
4565 }
4566 return $s;
4567 }
4568
4580 function formatBitrate( $bps ) {
4581 return $this->formatComputingNumbers( $bps, 1000, "bitrate-$1bits" );
4582 }
4583
4590 function formatComputingNumbers( $size, $boundary, $messageKey ) {
4591 if ( $size <= 0 ) {
4592 return str_replace( '$1', $this->formatNum( $size ),
4593 $this->getMessageFromDB( str_replace( '$1', '', $messageKey ) )
4594 );
4595 }
4596 $sizes = [ '', 'kilo', 'mega', 'giga', 'tera', 'peta', 'exa', 'zeta', 'yotta' ];
4597 $index = 0;
4598
4599 $maxIndex = count( $sizes ) - 1;
4600 while ( $size >= $boundary && $index < $maxIndex ) {
4601 $index++;
4602 $size /= $boundary;
4603 }
4604
4605 // For small sizes no decimal places necessary
4606 $round = 0;
4607 if ( $index > 1 ) {
4608 // For MB and bigger two decimal places are smarter
4609 $round = 2;
4610 }
4611 $msg = str_replace( '$1', $sizes[$index], $messageKey );
4612
4613 $size = round( $size, $round );
4614 $text = $this->getMessageFromDB( $msg );
4615 return str_replace( '$1', $this->formatNum( $size ), $text );
4616 }
4617
4628 function formatSize( $size ) {
4629 return $this->formatComputingNumbers( $size, 1024, "size-$1bytes" );
4630 }
4631
4641 function specialList( $page, $details, $oppositedm = true ) {
4642 if ( !$details ) {
4643 return $page;
4644 }
4645
4646 $dirmark = ( $oppositedm ? $this->getDirMark( true ) : '' ) . $this->getDirMark();
4647 return
4648 $page .
4649 $dirmark .
4650 $this->msg( 'word-separator' )->escaped() .
4651 $this->msg( 'parentheses' )->rawParams( $details )->escaped();
4652 }
4653
4664 public function viewPrevNext( Title $title, $offset, $limit,
4665 array $query = [], $atend = false
4666 ) {
4667 // @todo FIXME: Why on earth this needs one message for the text and another one for tooltip?
4668
4669 # Make 'previous' link
4670 $prev = wfMessage( 'prevn' )->inLanguage( $this )->title( $title )->numParams( $limit )->text();
4671 if ( $offset > 0 ) {
4672 $plink = $this->numLink( $title, max( $offset - $limit, 0 ), $limit,
4673 $query, $prev, 'prevn-title', 'mw-prevlink' );
4674 } else {
4675 $plink = htmlspecialchars( $prev );
4676 }
4677
4678 # Make 'next' link
4679 $next = wfMessage( 'nextn' )->inLanguage( $this )->title( $title )->numParams( $limit )->text();
4680 if ( $atend ) {
4681 $nlink = htmlspecialchars( $next );
4682 } else {
4683 $nlink = $this->numLink( $title, $offset + $limit, $limit,
4684 $query, $next, 'nextn-title', 'mw-nextlink' );
4685 }
4686
4687 # Make links to set number of items per page
4688 $numLinks = [];
4689 foreach ( [ 20, 50, 100, 250, 500 ] as $num ) {
4690 $numLinks[] = $this->numLink( $title, $offset, $num,
4691 $query, $this->formatNum( $num ), 'shown-title', 'mw-numlink' );
4692 }
4693
4694 return wfMessage( 'viewprevnext' )->inLanguage( $this )->title( $title
4695 )->rawParams( $plink, $nlink, $this->pipeList( $numLinks ) )->escaped();
4696 }
4697
4710 private function numLink( Title $title, $offset, $limit, array $query, $link,
4711 $tooltipMsg, $class
4712 ) {
4713 $query = [ 'limit' => $limit, 'offset' => $offset ] + $query;
4714 $tooltip = wfMessage( $tooltipMsg )->inLanguage( $this )->title( $title )
4715 ->numParams( $limit )->text();
4716
4717 return Html::element( 'a', [ 'href' => $title->getLocalURL( $query ),
4718 'title' => $tooltip, 'class' => $class ], $link );
4719 }
4720
4726 public function getConvRuleTitle() {
4727 return $this->mConverter->getConvRuleTitle();
4728 }
4729
4735 public function getCompiledPluralRules() {
4736 $pluralRules = self::$dataCache->getItem( strtolower( $this->mCode ), 'compiledPluralRules' );
4737 $fallbacks = Language::getFallbacksFor( $this->mCode );
4738 if ( !$pluralRules ) {
4739 foreach ( $fallbacks as $fallbackCode ) {
4740 $pluralRules = self::$dataCache->getItem( strtolower( $fallbackCode ), 'compiledPluralRules' );
4741 if ( $pluralRules ) {
4742 break;
4743 }
4744 }
4745 }
4746 return $pluralRules;
4747 }
4748
4754 public function getPluralRules() {
4755 $pluralRules = self::$dataCache->getItem( strtolower( $this->mCode ), 'pluralRules' );
4756 $fallbacks = Language::getFallbacksFor( $this->mCode );
4757 if ( !$pluralRules ) {
4758 foreach ( $fallbacks as $fallbackCode ) {
4759 $pluralRules = self::$dataCache->getItem( strtolower( $fallbackCode ), 'pluralRules' );
4760 if ( $pluralRules ) {
4761 break;
4762 }
4763 }
4764 }
4765 return $pluralRules;
4766 }
4767
4773 public function getPluralRuleTypes() {
4774 $pluralRuleTypes = self::$dataCache->getItem( strtolower( $this->mCode ), 'pluralRuleTypes' );
4775 $fallbacks = Language::getFallbacksFor( $this->mCode );
4776 if ( !$pluralRuleTypes ) {
4777 foreach ( $fallbacks as $fallbackCode ) {
4778 $pluralRuleTypes = self::$dataCache->getItem( strtolower( $fallbackCode ), 'pluralRuleTypes' );
4779 if ( $pluralRuleTypes ) {
4780 break;
4781 }
4782 }
4783 }
4784 return $pluralRuleTypes;
4785 }
4786
4792 public function getPluralRuleIndexNumber( $number ) {
4793 $pluralRules = $this->getCompiledPluralRules();
4794 $form = Evaluator::evaluateCompiled( $number, $pluralRules );
4795 return $form;
4796 }
4797
4806 public function getPluralRuleType( $number ) {
4807 $index = $this->getPluralRuleIndexNumber( $number );
4808 $pluralRuleTypes = $this->getPluralRuleTypes();
4809 if ( isset( $pluralRuleTypes[$index] ) ) {
4810 return $pluralRuleTypes[$index];
4811 } else {
4812 return 'other';
4813 }
4814 }
4815}
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...
$wgEditEncoding
Character set for use in the article edit box.
$wgDummyLanguageCodes
List of language codes that don't correspond to an actual language.
$wgNamespaceAliases
Namespace aliases.
$wgMetaNamespace
Name of the project namespace.
$wgMetaNamespaceTalk
Name of the project talk namespace.
$wgLocalTZoffset
Set an offset from UTC in minutes to use for the default timezone setting for anonymous users and new...
$wgLocalisationCacheConf
Localisation cache configuration.
$wgLangObjCacheSize
Language cache size, or really how many languages can we handle simultaneously without degrading to c...
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.
const TS_MW
MediaWiki concatenated string timestamp (YYYYMMDDHHMMSS)
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
$i
Definition Parser.php:1694
$wgUser
Definition Setup.php:794
$IP
Definition WebStart.php:58
A fake language converter.
Simple store for keeping values in an associative array for the current process.
static element( $element, $attribs=[], $contents='')
Identical to rawElement(), but HTML-escapes $contents (like Xml::element()).
Definition Html.php:230
Base class for language conversion.
static array $languagesWithVariants
languages supporting variants
Internationalisation code.
Definition Language.php:39
hasVariants()
Check if this is a language with variants.
translateBlockExpiry( $str, User $user=null)
initContLang()
Hook which will be called if this is the content language.
Definition Language.php:435
static $mWeekdayMsgs
Definition Language.php:66
getLocalNsIndex( $text)
Get a namespace key by value, case insensitive.
Definition Language.php:608
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:170
formatExpiry( $expiry, $format=true, $infinity='infinity')
Decode an expiry (block, protection, etc) which has come from the DB.
getPluralRuleTypes()
Get the plural rule types for the language.
static tsToIranian( $ts)
Algorithm by Roozbeh Pournader and Mohammad Toossi to convert Gregorian dates to Iranian dates.
getDefaultVariant()
convertCategoryKey( $key)
static getMessageKeysFor( $code)
Get all message keys for a given language.
static isWellFormedLanguageTag( $code, $lenient=false)
Returns true if a language code string is a well-formed language tag according to RFC 5646.
Definition Language.php:279
getVariantname( $code, $usemsg=true)
short names for language variants used for language conversion links.
Definition Language.php:708
static $mHebrewCalendarMonthGenMsgs
Definition Language.php:105
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:358
recodeForEdit( $s)
$mParentLanguage
Definition Language.php:47
getWeekdayName( $key)
Definition Language.php:962
getHebrewCalendarMonthName( $key)
Definition Language.php:986
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:155
digitGroupingPattern()
static array $fallbackLanguageCache
Cache for language fallbacks.
Definition Language.php:142
getMonthAbbreviation( $key)
Definition Language.php:943
getHebrewCalendarMonthNameGen( $key)
Definition Language.php:994
static $lre
Unicode directional formatting characters, for embedBidi()
Definition Language.php:153
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:251
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:886
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:62
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.
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:569
truncateHtml( $text, $length, $ellipsis='...')
Truncate a string of valid HTML to a specified length in bytes, appending an optional string (e....
getAllMessages()
__destruct()
Reduce memory usage.
Definition Language.php:425
getFormattedNsText( $index)
A convenience function that returns the same thing as getNsText() except with '_' changed to ' ',...
Definition Language.php:556
getURLVariant()
getMessageFromDB( $msg)
Get a message from the MediaWiki namespace.
Definition Language.php:898
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:411
getFormattedNamespaces()
A convenience function that returns getNamespaces() with spaces instead of underscores in values.
Definition Language.php:519
getExtraHashOptions()
returns language specific options used by User::getPageRenderHash() for example, the preferred langua...
$transformData
ReplacementArray object caches.
Definition Language.php:57
getMonthNamesArray()
Definition Language.php:923
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:935
setNamespaces(array $namespaces)
Arbitrarily set all of the namespace names at once.
Definition Language.php:499
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:50
getGrammarForms()
Get the grammar forms for the content language.
getNamespaces()
Returns an array of localised namespaces indexed by their numbers.
Definition Language.php:460
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:584
static array $durationIntervals
Definition Language.php:124
getNsIndex( $text)
Get a namespace key by value, case insensitive.
Definition Language.php:691
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:43
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.
sprintfDate( $format, $ts, DateTimeZone $zone=null, &$ttl=null)
This is a workalike of PHP's date() function, but with better internationalisation,...
static $mIranianCalendarMonthMsgs
Definition Language.php:90
uc( $str, $first=false)
Convert a string to uppercase.
static $mHebrewCalendarMonthMsgs
Definition Language.php:97
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:950
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:507
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)
static $mMonthGenMsgs
Definition Language.php:80
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:148
getIranianCalendarMonthName( $key)
Definition Language.php:978
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=[])
static getFallbackFor( $code)
Get the first fallback for a given language.
getWeekdayAbbreviation( $key)
Definition Language.php:970
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:85
replaceGrammarInNamespace( $m)
getBookstoreList()
Exports $wgBookstoreListEn.
Definition Language.php:450
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:179
$mMagicExtensions
Definition Language.php:46
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:442
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:916
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:154
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:402
static $mLangObjCache
Definition Language.php:64
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:381
msg( $msg)
Get message object in this language.
Definition Language.php:908
removeBadCharLast( $string)
Remove bytes that represent an incomplete Unicode character at the end of string (e....
static getJsonMessagesFileName( $code)
getNamespaceAliases()
Definition Language.php:617
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:538
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:661
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:739
removeBadCharFirst( $string)
Remove bytes that represent an incomplete Unicode character at the start of string (e....
static $mHijriCalendarMonthMsgs
Definition Language.php:113
static $mWeekdayAbbrevMsgs
Definition Language.php:71
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:46
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:333
static newFromCode( $code)
Create a language object for a given language code.
Definition Language.php:205
static $mMonthMsgs
Definition Language.php:75
$dateFormatStrings
Definition Language.php:49
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.
static getCanonicalIndex( $name)
Returns the index for a given canonical name, or NULL The input must be converted to lower case first...
static getCanonicalNamespaces( $rebuild=false)
Returns array of all defined namespaces with their canonical (English) names.
Library for creating and parsing MW-style timestamps.
getTimestamp( $style=TS_UNIX)
Get the timestamp represented by this object in a certain form.
format( $format)
Format the timestamp in a given format.
diff(MWTimestamp $relativeTo)
Calculate the difference between two MWTimestamp objects.
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:34
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition User.php:47
=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:72
const NS_PROJECT_TALK
Definition Defines.php:75
const NS_USER_TALK
Definition Defines.php:73
const NS_PROJECT
Definition Defines.php:74
const DB_SLAVE
Definition Defines.php:47
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
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:2379
We ve cleaned up the code here by removing clumps of infrequently used code and moving them off somewhere else It s much easier for someone working with this code to see what s _really_ going and make changes or fix bugs In we can take all the code that deals with the little used title reversing options(say) and put it in one place. Instead of having little title-reversing if-blocks spread all over the codebase in showAnArticle
This code would result in ircNotify being run twice when an article is and once for brion Hooks can return three possible true was required This is the default since MediaWiki *some string
Definition hooks.txt:183
namespace and then decline to actually register it & $namespaces
Definition hooks.txt:915
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 an< a > element with HTML attributes $attribs and contents $html will be returned If you return $ret will be returned and may include noclasses after processing 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
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context $options
Definition hooks.txt:1042
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:1810
do that in ParserLimitReportFormat instead use this to modify the parameters of the image and a DIV can begin in one section and end in another Make sure your code can handle that case gracefully See the EditSectionClearerLink extension for an example zero but section is usually empty its values are the globals values before the output is cached one of or reset my talk my contributions etc etc otherwise the built in rate limiting checks are if enabled allows for interception of redirect as a string mapping parameter names to values & $type
Definition hooks.txt:2413
namespace and then decline to actually register it file or subcat img or subcat $title
Definition hooks.txt:944
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:1811
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context the output can only depend on parameters provided to this hook not on global state indicating whether full HTML should be generated If generation of HTML may be but other information should still be present in the ParserOutput object to manipulate or replace but no entry for that model exists in $wgContentHandlers if desired whether it is OK to use $contentModel on $title Handler functions that modify $ok should generally return false to prevent further hooks from further modifying $ok inclusive $limit
Definition hooks.txt:1081
if the prop value should be in the metadata multi language array format
Definition hooks.txt:1483
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:846
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books $tag
Definition hooks.txt:991
see documentation in includes Linker php for Linker::makeImageLink & $time
Definition hooks.txt:1615
the value to return A Title object or null for latest to be modified or replaced by the hook handler or if authentication is not possible after cache objects are set for highlighting & $link
Definition hooks.txt:2692
Allows to change the fields on the form that will be generated $name
Definition hooks.txt:314
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:1458
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:847
if(count( $args)==0) $dir
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring which defines all default service and specifies how they depend on each other("wiring"). When a new service is added to MediaWiki core
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition injection.txt:37
$cache
Definition mcc.php:33
A helper class for throttling authentication attempts.
#define the
table suitable for use with IDatabase::select()
if(!isset( $args[0])) $lang