MediaWiki  1.29.2
Language.php
Go to the documentation of this file.
1 <?php
29 use CLDRPluralRuleParser\Evaluator;
30 
35 class Language {
39  public $mConverter;
40 
41  public $mVariants, $mCode, $mLoaded = false;
42  public $mMagicExtensions = [], $mMagicHookDone = false;
43  private $mHtmlCode = null, $mParentLanguage = false;
44 
45  public $dateFormatStrings = [];
47 
49  protected $namespaceNames;
51 
55  public $transformData = [];
56 
60  static public $dataCache;
61 
62  static public $mLangObjCache = [];
63 
64  static public $mWeekdayMsgs = [
65  'sunday', 'monday', 'tuesday', 'wednesday', 'thursday',
66  'friday', 'saturday'
67  ];
68 
69  static public $mWeekdayAbbrevMsgs = [
70  'sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'
71  ];
72 
73  static public $mMonthMsgs = [
74  'january', 'february', 'march', 'april', 'may_long', 'june',
75  'july', 'august', 'september', 'october', 'november',
76  'december'
77  ];
78  static public $mMonthGenMsgs = [
79  'january-gen', 'february-gen', 'march-gen', 'april-gen', 'may-gen', 'june-gen',
80  'july-gen', 'august-gen', 'september-gen', 'october-gen', 'november-gen',
81  'december-gen'
82  ];
83  static public $mMonthAbbrevMsgs = [
84  'jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug',
85  'sep', 'oct', 'nov', 'dec'
86  ];
87 
88  static public $mIranianCalendarMonthMsgs = [
89  'iranian-calendar-m1', 'iranian-calendar-m2', 'iranian-calendar-m3',
90  'iranian-calendar-m4', 'iranian-calendar-m5', 'iranian-calendar-m6',
91  'iranian-calendar-m7', 'iranian-calendar-m8', 'iranian-calendar-m9',
92  'iranian-calendar-m10', 'iranian-calendar-m11', 'iranian-calendar-m12'
93  ];
94 
95  static public $mHebrewCalendarMonthMsgs = [
96  'hebrew-calendar-m1', 'hebrew-calendar-m2', 'hebrew-calendar-m3',
97  'hebrew-calendar-m4', 'hebrew-calendar-m5', 'hebrew-calendar-m6',
98  'hebrew-calendar-m7', 'hebrew-calendar-m8', 'hebrew-calendar-m9',
99  'hebrew-calendar-m10', 'hebrew-calendar-m11', 'hebrew-calendar-m12',
100  'hebrew-calendar-m6a', 'hebrew-calendar-m6b'
101  ];
102 
104  'hebrew-calendar-m1-gen', 'hebrew-calendar-m2-gen', 'hebrew-calendar-m3-gen',
105  'hebrew-calendar-m4-gen', 'hebrew-calendar-m5-gen', 'hebrew-calendar-m6-gen',
106  'hebrew-calendar-m7-gen', 'hebrew-calendar-m8-gen', 'hebrew-calendar-m9-gen',
107  'hebrew-calendar-m10-gen', 'hebrew-calendar-m11-gen', 'hebrew-calendar-m12-gen',
108  'hebrew-calendar-m6a-gen', 'hebrew-calendar-m6b-gen'
109  ];
110 
111  static public $mHijriCalendarMonthMsgs = [
112  'hijri-calendar-m1', 'hijri-calendar-m2', 'hijri-calendar-m3',
113  'hijri-calendar-m4', 'hijri-calendar-m5', 'hijri-calendar-m6',
114  'hijri-calendar-m7', 'hijri-calendar-m8', 'hijri-calendar-m9',
115  'hijri-calendar-m10', 'hijri-calendar-m11', 'hijri-calendar-m12'
116  ];
117 
122  static public $durationIntervals = [
123  'millennia' => 31556952000,
124  'centuries' => 3155695200,
125  'decades' => 315569520,
126  'years' => 31556952, // 86400 * ( 365 + ( 24 * 3 + 25 ) / 400 )
127  'weeks' => 604800,
128  'days' => 86400,
129  'hours' => 3600,
130  'minutes' => 60,
131  'seconds' => 1,
132  ];
133 
140  static private $fallbackLanguageCache = [];
141 
146  static private $grammarTransformations;
147 
152  static private $languageNameCache;
153 
157  static private $lre = "\xE2\x80\xAA"; // U+202A LEFT-TO-RIGHT EMBEDDING
158  static private $rle = "\xE2\x80\xAB"; // U+202B RIGHT-TO-LEFT EMBEDDING
159  static private $pdf = "\xE2\x80\xAC"; // U+202C POP DIRECTIONAL FORMATTING
160 
172  // @codingStandardsIgnoreStart
173  // @codeCoverageIgnoreStart
174  static private $strongDirRegex = '/(?:([\x{41}-\x{5a}\x{61}-\x{7a}\x{aa}\x{b5}\x{ba}\x{c0}-\x{d6}\x{d8}-\x{f6}\x{f8}-\x{2b8}\x{2bb}-\x{2c1}\x{2d0}\x{2d1}\x{2e0}-\x{2e4}\x{2ee}\x{370}-\x{373}\x{376}\x{377}\x{37a}-\x{37d}\x{37f}\x{386}\x{388}-\x{38a}\x{38c}\x{38e}-\x{3a1}\x{3a3}-\x{3f5}\x{3f7}-\x{482}\x{48a}-\x{52f}\x{531}-\x{556}\x{559}-\x{55f}\x{561}-\x{587}\x{589}\x{903}-\x{939}\x{93b}\x{93d}-\x{940}\x{949}-\x{94c}\x{94e}-\x{950}\x{958}-\x{961}\x{964}-\x{980}\x{982}\x{983}\x{985}-\x{98c}\x{98f}\x{990}\x{993}-\x{9a8}\x{9aa}-\x{9b0}\x{9b2}\x{9b6}-\x{9b9}\x{9bd}-\x{9c0}\x{9c7}\x{9c8}\x{9cb}\x{9cc}\x{9ce}\x{9d7}\x{9dc}\x{9dd}\x{9df}-\x{9e1}\x{9e6}-\x{9f1}\x{9f4}-\x{9fa}\x{a03}\x{a05}-\x{a0a}\x{a0f}\x{a10}\x{a13}-\x{a28}\x{a2a}-\x{a30}\x{a32}\x{a33}\x{a35}\x{a36}\x{a38}\x{a39}\x{a3e}-\x{a40}\x{a59}-\x{a5c}\x{a5e}\x{a66}-\x{a6f}\x{a72}-\x{a74}\x{a83}\x{a85}-\x{a8d}\x{a8f}-\x{a91}\x{a93}-\x{aa8}\x{aaa}-\x{ab0}\x{ab2}\x{ab3}\x{ab5}-\x{ab9}\x{abd}-\x{ac0}\x{ac9}\x{acb}\x{acc}\x{ad0}\x{ae0}\x{ae1}\x{ae6}-\x{af0}\x{af9}\x{b02}\x{b03}\x{b05}-\x{b0c}\x{b0f}\x{b10}\x{b13}-\x{b28}\x{b2a}-\x{b30}\x{b32}\x{b33}\x{b35}-\x{b39}\x{b3d}\x{b3e}\x{b40}\x{b47}\x{b48}\x{b4b}\x{b4c}\x{b57}\x{b5c}\x{b5d}\x{b5f}-\x{b61}\x{b66}-\x{b77}\x{b83}\x{b85}-\x{b8a}\x{b8e}-\x{b90}\x{b92}-\x{b95}\x{b99}\x{b9a}\x{b9c}\x{b9e}\x{b9f}\x{ba3}\x{ba4}\x{ba8}-\x{baa}\x{bae}-\x{bb9}\x{bbe}\x{bbf}\x{bc1}\x{bc2}\x{bc6}-\x{bc8}\x{bca}-\x{bcc}\x{bd0}\x{bd7}\x{be6}-\x{bf2}\x{c01}-\x{c03}\x{c05}-\x{c0c}\x{c0e}-\x{c10}\x{c12}-\x{c28}\x{c2a}-\x{c39}\x{c3d}\x{c41}-\x{c44}\x{c58}-\x{c5a}\x{c60}\x{c61}\x{c66}-\x{c6f}\x{c7f}\x{c82}\x{c83}\x{c85}-\x{c8c}\x{c8e}-\x{c90}\x{c92}-\x{ca8}\x{caa}-\x{cb3}\x{cb5}-\x{cb9}\x{cbd}-\x{cc4}\x{cc6}-\x{cc8}\x{cca}\x{ccb}\x{cd5}\x{cd6}\x{cde}\x{ce0}\x{ce1}\x{ce6}-\x{cef}\x{cf1}\x{cf2}\x{d02}\x{d03}\x{d05}-\x{d0c}\x{d0e}-\x{d10}\x{d12}-\x{d3a}\x{d3d}-\x{d40}\x{d46}-\x{d48}\x{d4a}-\x{d4c}\x{d4e}\x{d57}\x{d5f}-\x{d61}\x{d66}-\x{d75}\x{d79}-\x{d7f}\x{d82}\x{d83}\x{d85}-\x{d96}\x{d9a}-\x{db1}\x{db3}-\x{dbb}\x{dbd}\x{dc0}-\x{dc6}\x{dcf}-\x{dd1}\x{dd8}-\x{ddf}\x{de6}-\x{def}\x{df2}-\x{df4}\x{e01}-\x{e30}\x{e32}\x{e33}\x{e40}-\x{e46}\x{e4f}-\x{e5b}\x{e81}\x{e82}\x{e84}\x{e87}\x{e88}\x{e8a}\x{e8d}\x{e94}-\x{e97}\x{e99}-\x{e9f}\x{ea1}-\x{ea3}\x{ea5}\x{ea7}\x{eaa}\x{eab}\x{ead}-\x{eb0}\x{eb2}\x{eb3}\x{ebd}\x{ec0}-\x{ec4}\x{ec6}\x{ed0}-\x{ed9}\x{edc}-\x{edf}\x{f00}-\x{f17}\x{f1a}-\x{f34}\x{f36}\x{f38}\x{f3e}-\x{f47}\x{f49}-\x{f6c}\x{f7f}\x{f85}\x{f88}-\x{f8c}\x{fbe}-\x{fc5}\x{fc7}-\x{fcc}\x{fce}-\x{fda}\x{1000}-\x{102c}\x{1031}\x{1038}\x{103b}\x{103c}\x{103f}-\x{1057}\x{105a}-\x{105d}\x{1061}-\x{1070}\x{1075}-\x{1081}\x{1083}\x{1084}\x{1087}-\x{108c}\x{108e}-\x{109c}\x{109e}-\x{10c5}\x{10c7}\x{10cd}\x{10d0}-\x{1248}\x{124a}-\x{124d}\x{1250}-\x{1256}\x{1258}\x{125a}-\x{125d}\x{1260}-\x{1288}\x{128a}-\x{128d}\x{1290}-\x{12b0}\x{12b2}-\x{12b5}\x{12b8}-\x{12be}\x{12c0}\x{12c2}-\x{12c5}\x{12c8}-\x{12d6}\x{12d8}-\x{1310}\x{1312}-\x{1315}\x{1318}-\x{135a}\x{1360}-\x{137c}\x{1380}-\x{138f}\x{13a0}-\x{13f5}\x{13f8}-\x{13fd}\x{1401}-\x{167f}\x{1681}-\x{169a}\x{16a0}-\x{16f8}\x{1700}-\x{170c}\x{170e}-\x{1711}\x{1720}-\x{1731}\x{1735}\x{1736}\x{1740}-\x{1751}\x{1760}-\x{176c}\x{176e}-\x{1770}\x{1780}-\x{17b3}\x{17b6}\x{17be}-\x{17c5}\x{17c7}\x{17c8}\x{17d4}-\x{17da}\x{17dc}\x{17e0}-\x{17e9}\x{1810}-\x{1819}\x{1820}-\x{1877}\x{1880}-\x{18a8}\x{18aa}\x{18b0}-\x{18f5}\x{1900}-\x{191e}\x{1923}-\x{1926}\x{1929}-\x{192b}\x{1930}\x{1931}\x{1933}-\x{1938}\x{1946}-\x{196d}\x{1970}-\x{1974}\x{1980}-\x{19ab}\x{19b0}-\x{19c9}\x{19d0}-\x{19da}\x{1a00}-\x{1a16}\x{1a19}\x{1a1a}\x{1a1e}-\x{1a55}\x{1a57}\x{1a61}\x{1a63}\x{1a64}\x{1a6d}-\x{1a72}\x{1a80}-\x{1a89}\x{1a90}-\x{1a99}\x{1aa0}-\x{1aad}\x{1b04}-\x{1b33}\x{1b35}\x{1b3b}\x{1b3d}-\x{1b41}\x{1b43}-\x{1b4b}\x{1b50}-\x{1b6a}\x{1b74}-\x{1b7c}\x{1b82}-\x{1ba1}\x{1ba6}\x{1ba7}\x{1baa}\x{1bae}-\x{1be5}\x{1be7}\x{1bea}-\x{1bec}\x{1bee}\x{1bf2}\x{1bf3}\x{1bfc}-\x{1c2b}\x{1c34}\x{1c35}\x{1c3b}-\x{1c49}\x{1c4d}-\x{1c7f}\x{1cc0}-\x{1cc7}\x{1cd3}\x{1ce1}\x{1ce9}-\x{1cec}\x{1cee}-\x{1cf3}\x{1cf5}\x{1cf6}\x{1d00}-\x{1dbf}\x{1e00}-\x{1f15}\x{1f18}-\x{1f1d}\x{1f20}-\x{1f45}\x{1f48}-\x{1f4d}\x{1f50}-\x{1f57}\x{1f59}\x{1f5b}\x{1f5d}\x{1f5f}-\x{1f7d}\x{1f80}-\x{1fb4}\x{1fb6}-\x{1fbc}\x{1fbe}\x{1fc2}-\x{1fc4}\x{1fc6}-\x{1fcc}\x{1fd0}-\x{1fd3}\x{1fd6}-\x{1fdb}\x{1fe0}-\x{1fec}\x{1ff2}-\x{1ff4}\x{1ff6}-\x{1ffc}\x{200e}\x{2071}\x{207f}\x{2090}-\x{209c}\x{2102}\x{2107}\x{210a}-\x{2113}\x{2115}\x{2119}-\x{211d}\x{2124}\x{2126}\x{2128}\x{212a}-\x{212d}\x{212f}-\x{2139}\x{213c}-\x{213f}\x{2145}-\x{2149}\x{214e}\x{214f}\x{2160}-\x{2188}\x{2336}-\x{237a}\x{2395}\x{249c}-\x{24e9}\x{26ac}\x{2800}-\x{28ff}\x{2c00}-\x{2c2e}\x{2c30}-\x{2c5e}\x{2c60}-\x{2ce4}\x{2ceb}-\x{2cee}\x{2cf2}\x{2cf3}\x{2d00}-\x{2d25}\x{2d27}\x{2d2d}\x{2d30}-\x{2d67}\x{2d6f}\x{2d70}\x{2d80}-\x{2d96}\x{2da0}-\x{2da6}\x{2da8}-\x{2dae}\x{2db0}-\x{2db6}\x{2db8}-\x{2dbe}\x{2dc0}-\x{2dc6}\x{2dc8}-\x{2dce}\x{2dd0}-\x{2dd6}\x{2dd8}-\x{2dde}\x{3005}-\x{3007}\x{3021}-\x{3029}\x{302e}\x{302f}\x{3031}-\x{3035}\x{3038}-\x{303c}\x{3041}-\x{3096}\x{309d}-\x{309f}\x{30a1}-\x{30fa}\x{30fc}-\x{30ff}\x{3105}-\x{312d}\x{3131}-\x{318e}\x{3190}-\x{31ba}\x{31f0}-\x{321c}\x{3220}-\x{324f}\x{3260}-\x{327b}\x{327f}-\x{32b0}\x{32c0}-\x{32cb}\x{32d0}-\x{32fe}\x{3300}-\x{3376}\x{337b}-\x{33dd}\x{33e0}-\x{33fe}\x{3400}-\x{4db5}\x{4e00}-\x{9fd5}\x{a000}-\x{a48c}\x{a4d0}-\x{a60c}\x{a610}-\x{a62b}\x{a640}-\x{a66e}\x{a680}-\x{a69d}\x{a6a0}-\x{a6ef}\x{a6f2}-\x{a6f7}\x{a722}-\x{a787}\x{a789}-\x{a7ad}\x{a7b0}-\x{a7b7}\x{a7f7}-\x{a801}\x{a803}-\x{a805}\x{a807}-\x{a80a}\x{a80c}-\x{a824}\x{a827}\x{a830}-\x{a837}\x{a840}-\x{a873}\x{a880}-\x{a8c3}\x{a8ce}-\x{a8d9}\x{a8f2}-\x{a8fd}\x{a900}-\x{a925}\x{a92e}-\x{a946}\x{a952}\x{a953}\x{a95f}-\x{a97c}\x{a983}-\x{a9b2}\x{a9b4}\x{a9b5}\x{a9ba}\x{a9bb}\x{a9bd}-\x{a9cd}\x{a9cf}-\x{a9d9}\x{a9de}-\x{a9e4}\x{a9e6}-\x{a9fe}\x{aa00}-\x{aa28}\x{aa2f}\x{aa30}\x{aa33}\x{aa34}\x{aa40}-\x{aa42}\x{aa44}-\x{aa4b}\x{aa4d}\x{aa50}-\x{aa59}\x{aa5c}-\x{aa7b}\x{aa7d}-\x{aaaf}\x{aab1}\x{aab5}\x{aab6}\x{aab9}-\x{aabd}\x{aac0}\x{aac2}\x{aadb}-\x{aaeb}\x{aaee}-\x{aaf5}\x{ab01}-\x{ab06}\x{ab09}-\x{ab0e}\x{ab11}-\x{ab16}\x{ab20}-\x{ab26}\x{ab28}-\x{ab2e}\x{ab30}-\x{ab65}\x{ab70}-\x{abe4}\x{abe6}\x{abe7}\x{abe9}-\x{abec}\x{abf0}-\x{abf9}\x{ac00}-\x{d7a3}\x{d7b0}-\x{d7c6}\x{d7cb}-\x{d7fb}\x{e000}-\x{fa6d}\x{fa70}-\x{fad9}\x{fb00}-\x{fb06}\x{fb13}-\x{fb17}\x{ff21}-\x{ff3a}\x{ff41}-\x{ff5a}\x{ff66}-\x{ffbe}\x{ffc2}-\x{ffc7}\x{ffca}-\x{ffcf}\x{ffd2}-\x{ffd7}\x{ffda}-\x{ffdc}\x{10000}-\x{1000b}\x{1000d}-\x{10026}\x{10028}-\x{1003a}\x{1003c}\x{1003d}\x{1003f}-\x{1004d}\x{10050}-\x{1005d}\x{10080}-\x{100fa}\x{10100}\x{10102}\x{10107}-\x{10133}\x{10137}-\x{1013f}\x{101d0}-\x{101fc}\x{10280}-\x{1029c}\x{102a0}-\x{102d0}\x{10300}-\x{10323}\x{10330}-\x{1034a}\x{10350}-\x{10375}\x{10380}-\x{1039d}\x{1039f}-\x{103c3}\x{103c8}-\x{103d5}\x{10400}-\x{1049d}\x{104a0}-\x{104a9}\x{10500}-\x{10527}\x{10530}-\x{10563}\x{1056f}\x{10600}-\x{10736}\x{10740}-\x{10755}\x{10760}-\x{10767}\x{11000}\x{11002}-\x{11037}\x{11047}-\x{1104d}\x{11066}-\x{1106f}\x{11082}-\x{110b2}\x{110b7}\x{110b8}\x{110bb}-\x{110c1}\x{110d0}-\x{110e8}\x{110f0}-\x{110f9}\x{11103}-\x{11126}\x{1112c}\x{11136}-\x{11143}\x{11150}-\x{11172}\x{11174}-\x{11176}\x{11182}-\x{111b5}\x{111bf}-\x{111c9}\x{111cd}\x{111d0}-\x{111df}\x{111e1}-\x{111f4}\x{11200}-\x{11211}\x{11213}-\x{1122e}\x{11232}\x{11233}\x{11235}\x{11238}-\x{1123d}\x{11280}-\x{11286}\x{11288}\x{1128a}-\x{1128d}\x{1128f}-\x{1129d}\x{1129f}-\x{112a9}\x{112b0}-\x{112de}\x{112e0}-\x{112e2}\x{112f0}-\x{112f9}\x{11302}\x{11303}\x{11305}-\x{1130c}\x{1130f}\x{11310}\x{11313}-\x{11328}\x{1132a}-\x{11330}\x{11332}\x{11333}\x{11335}-\x{11339}\x{1133d}-\x{1133f}\x{11341}-\x{11344}\x{11347}\x{11348}\x{1134b}-\x{1134d}\x{11350}\x{11357}\x{1135d}-\x{11363}\x{11480}-\x{114b2}\x{114b9}\x{114bb}-\x{114be}\x{114c1}\x{114c4}-\x{114c7}\x{114d0}-\x{114d9}\x{11580}-\x{115b1}\x{115b8}-\x{115bb}\x{115be}\x{115c1}-\x{115db}\x{11600}-\x{11632}\x{1163b}\x{1163c}\x{1163e}\x{11641}-\x{11644}\x{11650}-\x{11659}\x{11680}-\x{116aa}\x{116ac}\x{116ae}\x{116af}\x{116b6}\x{116c0}-\x{116c9}\x{11700}-\x{11719}\x{11720}\x{11721}\x{11726}\x{11730}-\x{1173f}\x{118a0}-\x{118f2}\x{118ff}\x{11ac0}-\x{11af8}\x{12000}-\x{12399}\x{12400}-\x{1246e}\x{12470}-\x{12474}\x{12480}-\x{12543}\x{13000}-\x{1342e}\x{14400}-\x{14646}\x{16800}-\x{16a38}\x{16a40}-\x{16a5e}\x{16a60}-\x{16a69}\x{16a6e}\x{16a6f}\x{16ad0}-\x{16aed}\x{16af5}\x{16b00}-\x{16b2f}\x{16b37}-\x{16b45}\x{16b50}-\x{16b59}\x{16b5b}-\x{16b61}\x{16b63}-\x{16b77}\x{16b7d}-\x{16b8f}\x{16f00}-\x{16f44}\x{16f50}-\x{16f7e}\x{16f93}-\x{16f9f}\x{1b000}\x{1b001}\x{1bc00}-\x{1bc6a}\x{1bc70}-\x{1bc7c}\x{1bc80}-\x{1bc88}\x{1bc90}-\x{1bc99}\x{1bc9c}\x{1bc9f}\x{1d000}-\x{1d0f5}\x{1d100}-\x{1d126}\x{1d129}-\x{1d166}\x{1d16a}-\x{1d172}\x{1d183}\x{1d184}\x{1d18c}-\x{1d1a9}\x{1d1ae}-\x{1d1e8}\x{1d360}-\x{1d371}\x{1d400}-\x{1d454}\x{1d456}-\x{1d49c}\x{1d49e}\x{1d49f}\x{1d4a2}\x{1d4a5}\x{1d4a6}\x{1d4a9}-\x{1d4ac}\x{1d4ae}-\x{1d4b9}\x{1d4bb}\x{1d4bd}-\x{1d4c3}\x{1d4c5}-\x{1d505}\x{1d507}-\x{1d50a}\x{1d50d}-\x{1d514}\x{1d516}-\x{1d51c}\x{1d51e}-\x{1d539}\x{1d53b}-\x{1d53e}\x{1d540}-\x{1d544}\x{1d546}\x{1d54a}-\x{1d550}\x{1d552}-\x{1d6a5}\x{1d6a8}-\x{1d6da}\x{1d6dc}-\x{1d714}\x{1d716}-\x{1d74e}\x{1d750}-\x{1d788}\x{1d78a}-\x{1d7c2}\x{1d7c4}-\x{1d7cb}\x{1d800}-\x{1d9ff}\x{1da37}-\x{1da3a}\x{1da6d}-\x{1da74}\x{1da76}-\x{1da83}\x{1da85}-\x{1da8b}\x{1f110}-\x{1f12e}\x{1f130}-\x{1f169}\x{1f170}-\x{1f19a}\x{1f1e6}-\x{1f202}\x{1f210}-\x{1f23a}\x{1f240}-\x{1f248}\x{1f250}\x{1f251}\x{20000}-\x{2a6d6}\x{2a700}-\x{2b734}\x{2b740}-\x{2b81d}\x{2b820}-\x{2cea1}\x{2f800}-\x{2fa1d}\x{f0000}-\x{ffffd}\x{100000}-\x{10fffd}])|([\x{590}\x{5be}\x{5c0}\x{5c3}\x{5c6}\x{5c8}-\x{5ff}\x{7c0}-\x{7ea}\x{7f4}\x{7f5}\x{7fa}-\x{815}\x{81a}\x{824}\x{828}\x{82e}-\x{858}\x{85c}-\x{89f}\x{200f}\x{fb1d}\x{fb1f}-\x{fb28}\x{fb2a}-\x{fb4f}\x{10800}-\x{1091e}\x{10920}-\x{10a00}\x{10a04}\x{10a07}-\x{10a0b}\x{10a10}-\x{10a37}\x{10a3b}-\x{10a3e}\x{10a40}-\x{10ae4}\x{10ae7}-\x{10b38}\x{10b40}-\x{10e5f}\x{10e7f}-\x{10fff}\x{1e800}-\x{1e8cf}\x{1e8d7}-\x{1edff}\x{1ef00}-\x{1efff}\x{608}\x{60b}\x{60d}\x{61b}-\x{64a}\x{66d}-\x{66f}\x{671}-\x{6d5}\x{6e5}\x{6e6}\x{6ee}\x{6ef}\x{6fa}-\x{710}\x{712}-\x{72f}\x{74b}-\x{7a5}\x{7b1}-\x{7bf}\x{8a0}-\x{8e2}\x{fb50}-\x{fd3d}\x{fd40}-\x{fdcf}\x{fdf0}-\x{fdfc}\x{fdfe}\x{fdff}\x{fe70}-\x{fefe}\x{1ee00}-\x{1eeef}\x{1eef2}-\x{1eeff}]))/u';
175  // @codeCoverageIgnoreEnd
176  // @codingStandardsIgnoreEnd
177 
183  static function factory( $code ) {
185 
186  if ( isset( $wgDummyLanguageCodes[$code] ) ) {
188  }
189 
190  // get the language object to process
191  $langObj = isset( self::$mLangObjCache[$code] )
192  ? self::$mLangObjCache[$code]
194 
195  // merge the language object in to get it up front in the cache
196  self::$mLangObjCache = array_merge( [ $code => $langObj ], self::$mLangObjCache );
197  // get rid of the oldest ones in case we have an overflow
198  self::$mLangObjCache = array_slice( self::$mLangObjCache, 0, $wgLangObjCacheSize, true );
199 
200  return $langObj;
201  }
202 
209  protected static function newFromCode( $code ) {
210  if ( !Language::isValidCode( $code ) ) {
211  throw new MWException( "Invalid language code \"$code\"" );
212  }
213 
215  // It's not possible to customise this code with class files, so
216  // just return a Language object. This is to support uselang= hacks.
217  $lang = new Language;
218  $lang->setCode( $code );
219  return $lang;
220  }
221 
222  // Check if there is a language class for the code
223  $class = self::classFromCode( $code );
224  if ( class_exists( $class ) ) {
225  $lang = new $class;
226  return $lang;
227  }
228 
229  // Keep trying the fallback list until we find an existing class
230  $fallbacks = Language::getFallbacksFor( $code );
231  foreach ( $fallbacks as $fallbackCode ) {
232  if ( !Language::isValidBuiltInCode( $fallbackCode ) ) {
233  throw new MWException( "Invalid fallback '$fallbackCode' in fallback sequence for '$code'" );
234  }
235 
236  $class = self::classFromCode( $fallbackCode );
237  if ( class_exists( $class ) ) {
238  $lang = new $class;
239  $lang->setCode( $code );
240  return $lang;
241  }
242  }
243 
244  throw new MWException( "Invalid fallback sequence for language '$code'" );
245  }
246 
255  public static function isSupportedLanguage( $code ) {
256  if ( !self::isValidBuiltInCode( $code ) ) {
257  return false;
258  }
259 
260  if ( $code === 'qqq' ) {
261  return false;
262  }
263 
264  return is_readable( self::getMessagesFileName( $code ) ) ||
265  is_readable( self::getJsonMessagesFileName( $code ) );
266  }
267 
283  public static function isWellFormedLanguageTag( $code, $lenient = false ) {
284  $alpha = '[a-z]';
285  $digit = '[0-9]';
286  $alphanum = '[a-z0-9]';
287  $x = 'x'; # private use singleton
288  $singleton = '[a-wy-z]'; # other singleton
289  $s = $lenient ? '[-_]' : '-';
290 
291  $language = "$alpha{2,8}|$alpha{2,3}$s$alpha{3}";
292  $script = "$alpha{4}"; # ISO 15924
293  $region = "(?:$alpha{2}|$digit{3})"; # ISO 3166-1 alpha-2 or UN M.49
294  $variant = "(?:$alphanum{5,8}|$digit$alphanum{3})";
295  $extension = "$singleton(?:$s$alphanum{2,8})+";
296  $privateUse = "$x(?:$s$alphanum{1,8})+";
297 
298  # Define certain grandfathered codes, since otherwise the regex is pretty useless.
299  # Since these are limited, this is safe even later changes to the registry --
300  # the only oddity is that it might change the type of the tag, and thus
301  # the results from the capturing groups.
302  # https://www.iana.org/assignments/language-subtag-registry
303 
304  $grandfathered = "en{$s}GB{$s}oed"
305  . "|i{$s}(?:ami|bnn|default|enochian|hak|klingon|lux|mingo|navajo|pwn|tao|tay|tsu)"
306  . "|no{$s}(?:bok|nyn)"
307  . "|sgn{$s}(?:BE{$s}(?:fr|nl)|CH{$s}de)"
308  . "|zh{$s}min{$s}nan";
309 
310  $variantList = "$variant(?:$s$variant)*";
311  $extensionList = "$extension(?:$s$extension)*";
312 
313  $langtag = "(?:($language)"
314  . "(?:$s$script)?"
315  . "(?:$s$region)?"
316  . "(?:$s$variantList)?"
317  . "(?:$s$extensionList)?"
318  . "(?:$s$privateUse)?)";
319 
320  # The final breakdown, with capturing groups for each of these components
321  # The variants, extensions, grandfathered, and private-use may have interior '-'
322 
323  $root = "^(?:$langtag|$privateUse|$grandfathered)$";
324 
325  return (bool)preg_match( "/$root/", strtolower( $code ) );
326  }
327 
337  public static function isValidCode( $code ) {
338  static $cache = [];
339  if ( !isset( $cache[$code] ) ) {
340  // People think language codes are html safe, so enforce it.
341  // Ideally we should only allow a-zA-Z0-9-
342  // but, .+ and other chars are often used for {{int:}} hacks
343  // see bugs T39564, T39587, T38938
344  $cache[$code] =
345  // Protect against path traversal
346  strcspn( $code, ":/\\\000&<>'\"" ) === strlen( $code )
348  }
349  return $cache[$code];
350  }
351 
362  public static function isValidBuiltInCode( $code ) {
363 
364  if ( !is_string( $code ) ) {
365  if ( is_object( $code ) ) {
366  $addmsg = " of class " . get_class( $code );
367  } else {
368  $addmsg = '';
369  }
370  $type = gettype( $code );
371  throw new MWException( __METHOD__ . " must be passed a string, $type given$addmsg" );
372  }
373 
374  return (bool)preg_match( '/^[a-z0-9-]{2,}$/', $code );
375  }
376 
385  public static function isKnownLanguageTag( $tag ) {
386  // Quick escape for invalid input to avoid exceptions down the line
387  // when code tries to process tags which are not valid at all.
388  if ( !self::isValidBuiltInCode( $tag ) ) {
389  return false;
390  }
391 
392  if ( isset( MediaWiki\Languages\Data\Names::$names[$tag] )
393  || self::fetchLanguageName( $tag, $tag ) !== ''
394  ) {
395  return true;
396  }
397 
398  return false;
399  }
400 
406  public static function getLocalisationCache() {
407  if ( is_null( self::$dataCache ) ) {
409  $class = $wgLocalisationCacheConf['class'];
410  self::$dataCache = new $class( $wgLocalisationCacheConf );
411  }
412  return self::$dataCache;
413  }
414 
415  function __construct() {
416  $this->mConverter = new FakeConverter( $this );
417  // Set the code to the name of the descendant
418  if ( static::class === 'Language' ) {
419  $this->mCode = 'en';
420  } else {
421  $this->mCode = str_replace( '_', '-', strtolower( substr( static::class, 8 ) ) );
422  }
424  }
425 
429  function __destruct() {
430  foreach ( $this as $name => $value ) {
431  unset( $this->$name );
432  }
433  }
434 
439  function initContLang() {
440  }
441 
446  public function getFallbackLanguages() {
447  return self::getFallbacksFor( $this->mCode );
448  }
449 
454  public function getBookstoreList() {
455  return self::$dataCache->getItem( $this->mCode, 'bookstoreList' );
456  }
457 
464  public function getNamespaces() {
465  if ( is_null( $this->namespaceNames ) ) {
466  global $wgMetaNamespace, $wgMetaNamespaceTalk, $wgExtraNamespaces;
467 
468  $validNamespaces = MWNamespace::getCanonicalNamespaces();
469 
470  $this->namespaceNames = $wgExtraNamespaces +
471  self::$dataCache->getItem( $this->mCode, 'namespaceNames' );
472  $this->namespaceNames += $validNamespaces;
473 
474  $this->namespaceNames[NS_PROJECT] = $wgMetaNamespace;
475  if ( $wgMetaNamespaceTalk ) {
476  $this->namespaceNames[NS_PROJECT_TALK] = $wgMetaNamespaceTalk;
477  } else {
478  $talk = $this->namespaceNames[NS_PROJECT_TALK];
479  $this->namespaceNames[NS_PROJECT_TALK] =
480  $this->fixVariableInNamespace( $talk );
481  }
482 
483  # Sometimes a language will be localised but not actually exist on this wiki.
484  foreach ( $this->namespaceNames as $key => $text ) {
485  if ( !isset( $validNamespaces[$key] ) ) {
486  unset( $this->namespaceNames[$key] );
487  }
488  }
489 
490  # The above mixing may leave namespaces out of canonical order.
491  # Re-order by namespace ID number...
492  ksort( $this->namespaceNames );
493 
494  Hooks::run( 'LanguageGetNamespaces', [ &$this->namespaceNames ] );
495  }
496 
497  return $this->namespaceNames;
498  }
499 
504  public function setNamespaces( array $namespaces ) {
505  $this->namespaceNames = $namespaces;
506  $this->mNamespaceIds = null;
507  }
508 
512  public function resetNamespaces() {
513  $this->namespaceNames = null;
514  $this->mNamespaceIds = null;
515  $this->namespaceAliases = null;
516  }
517 
524  public function getFormattedNamespaces() {
525  $ns = $this->getNamespaces();
526  foreach ( $ns as $k => $v ) {
527  $ns[$k] = strtr( $v, '_', ' ' );
528  }
529  return $ns;
530  }
531 
543  public function getNsText( $index ) {
544  $ns = $this->getNamespaces();
545  return isset( $ns[$index] ) ? $ns[$index] : false;
546  }
547 
561  public function getFormattedNsText( $index ) {
562  $ns = $this->getNsText( $index );
563  return strtr( $ns, '_', ' ' );
564  }
565 
574  public function getGenderNsText( $index, $gender ) {
575  global $wgExtraGenderNamespaces;
576 
577  $ns = $wgExtraGenderNamespaces +
578  (array)self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' );
579 
580  return isset( $ns[$index][$gender] ) ? $ns[$index][$gender] : $this->getNsText( $index );
581  }
582 
589  public function needsGenderDistinction() {
590  global $wgExtraGenderNamespaces, $wgExtraNamespaces;
591  if ( count( $wgExtraGenderNamespaces ) > 0 ) {
592  // $wgExtraGenderNamespaces overrides everything
593  return true;
594  } elseif ( isset( $wgExtraNamespaces[NS_USER] ) && isset( $wgExtraNamespaces[NS_USER_TALK] ) ) {
596  // $wgExtraNamespaces overrides any gender aliases specified in i18n files
597  return false;
598  } else {
599  // Check what is in i18n files
600  $aliases = self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' );
601  return count( $aliases ) > 0;
602  }
603  }
604 
613  function getLocalNsIndex( $text ) {
614  $lctext = $this->lc( $text );
615  $ids = $this->getNamespaceIds();
616  return isset( $ids[$lctext] ) ? $ids[$lctext] : false;
617  }
618 
622  public function getNamespaceAliases() {
623  if ( is_null( $this->namespaceAliases ) ) {
624  $aliases = self::$dataCache->getItem( $this->mCode, 'namespaceAliases' );
625  if ( !$aliases ) {
626  $aliases = [];
627  } else {
628  foreach ( $aliases as $name => $index ) {
629  if ( $index === NS_PROJECT_TALK ) {
630  unset( $aliases[$name] );
631  $name = $this->fixVariableInNamespace( $name );
632  $aliases[$name] = $index;
633  }
634  }
635  }
636 
637  global $wgExtraGenderNamespaces;
638  $genders = $wgExtraGenderNamespaces +
639  (array)self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' );
640  foreach ( $genders as $index => $forms ) {
641  foreach ( $forms as $alias ) {
642  $aliases[$alias] = $index;
643  }
644  }
645 
646  # Also add converted namespace names as aliases, to avoid confusion.
647  $convertedNames = [];
648  foreach ( $this->getVariants() as $variant ) {
649  if ( $variant === $this->mCode ) {
650  continue;
651  }
652  foreach ( $this->getNamespaces() as $ns => $_ ) {
653  $convertedNames[$this->getConverter()->convertNamespace( $ns, $variant )] = $ns;
654  }
655  }
656 
657  $this->namespaceAliases = $aliases + $convertedNames;
658  }
659 
661  }
662 
666  public function getNamespaceIds() {
667  if ( is_null( $this->mNamespaceIds ) ) {
669  # Put namespace names and aliases into a hashtable.
670  # If this is too slow, then we should arrange it so that it is done
671  # before caching. The catch is that at pre-cache time, the above
672  # class-specific fixup hasn't been done.
673  $this->mNamespaceIds = [];
674  foreach ( $this->getNamespaces() as $index => $name ) {
675  $this->mNamespaceIds[$this->lc( $name )] = $index;
676  }
677  foreach ( $this->getNamespaceAliases() as $name => $index ) {
678  $this->mNamespaceIds[$this->lc( $name )] = $index;
679  }
680  if ( $wgNamespaceAliases ) {
681  foreach ( $wgNamespaceAliases as $name => $index ) {
682  $this->mNamespaceIds[$this->lc( $name )] = $index;
683  }
684  }
685  }
686  return $this->mNamespaceIds;
687  }
688 
696  public function getNsIndex( $text ) {
697  $lctext = $this->lc( $text );
698  $ns = MWNamespace::getCanonicalIndex( $lctext );
699  if ( $ns !== null ) {
700  return $ns;
701  }
702  $ids = $this->getNamespaceIds();
703  return isset( $ids[$lctext] ) ? $ids[$lctext] : false;
704  }
705 
713  public function getVariantname( $code, $usemsg = true ) {
714  $msg = "variantname-$code";
715  if ( $usemsg && wfMessage( $msg )->exists() ) {
716  return $this->getMessageFromDB( $msg );
717  }
719  if ( $name ) {
720  return $name; # if it's defined as a language name, show that
721  } else {
722  # otherwise, output the language code
723  return $code;
724  }
725  }
726 
730  public function getDatePreferences() {
731  return self::$dataCache->getItem( $this->mCode, 'datePreferences' );
732  }
733 
737  function getDateFormats() {
738  return self::$dataCache->getItem( $this->mCode, 'dateFormats' );
739  }
740 
744  public function getDefaultDateFormat() {
745  $df = self::$dataCache->getItem( $this->mCode, 'defaultDateFormat' );
746  if ( $df === 'dmy or mdy' ) {
747  global $wgAmericanDates;
748  return $wgAmericanDates ? 'mdy' : 'dmy';
749  } else {
750  return $df;
751  }
752  }
753 
757  public function getDatePreferenceMigrationMap() {
758  return self::$dataCache->getItem( $this->mCode, 'datePreferenceMigrationMap' );
759  }
760 
765  function getImageFile( $image ) {
766  return self::$dataCache->getSubitem( $this->mCode, 'imageFiles', $image );
767  }
768 
773  public function getImageFiles() {
774  return self::$dataCache->getItem( $this->mCode, 'imageFiles' );
775  }
776 
780  public function getExtraUserToggles() {
781  return (array)self::$dataCache->getItem( $this->mCode, 'extraUserToggles' );
782  }
783 
788  function getUserToggle( $tog ) {
789  return $this->getMessageFromDB( "tog-$tog" );
790  }
791 
803  public static function fetchLanguageNames( $inLanguage = null, $include = 'mw' ) {
804  $cacheKey = $inLanguage === null ? 'null' : $inLanguage;
805  $cacheKey .= ":$include";
806  if ( self::$languageNameCache === null ) {
807  self::$languageNameCache = new HashBagOStuff( [ 'maxKeys' => 20 ] );
808  }
809 
810  $ret = self::$languageNameCache->get( $cacheKey );
811  if ( !$ret ) {
812  $ret = self::fetchLanguageNamesUncached( $inLanguage, $include );
813  self::$languageNameCache->set( $cacheKey, $ret );
814  }
815  return $ret;
816  }
817 
828  private static function fetchLanguageNamesUncached( $inLanguage = null, $include = 'mw' ) {
829  global $wgExtraLanguageNames;
830 
831  // If passed an invalid language code to use, fallback to en
832  if ( $inLanguage !== null && !Language::isValidCode( $inLanguage ) ) {
833  $inLanguage = 'en';
834  }
835 
836  $names = [];
837 
838  if ( $inLanguage ) {
839  # TODO: also include when $inLanguage is null, when this code is more efficient
840  Hooks::run( 'LanguageGetTranslatedLanguageNames', [ &$names, $inLanguage ] );
841  }
842 
843  $mwNames = $wgExtraLanguageNames + MediaWiki\Languages\Data\Names::$names;
844  foreach ( $mwNames as $mwCode => $mwName ) {
845  # - Prefer own MediaWiki native name when not using the hook
846  # - For other names just add if not added through the hook
847  if ( $mwCode === $inLanguage || !isset( $names[$mwCode] ) ) {
848  $names[$mwCode] = $mwName;
849  }
850  }
851 
852  if ( $include === 'all' ) {
853  ksort( $names );
854  return $names;
855  }
856 
857  $returnMw = [];
858  $coreCodes = array_keys( $mwNames );
859  foreach ( $coreCodes as $coreCode ) {
860  $returnMw[$coreCode] = $names[$coreCode];
861  }
862 
863  if ( $include === 'mwfile' ) {
864  $namesMwFile = [];
865  # We do this using a foreach over the codes instead of a directory
866  # loop so that messages files in extensions will work correctly.
867  foreach ( $returnMw as $code => $value ) {
868  if ( is_readable( self::getMessagesFileName( $code ) )
869  || is_readable( self::getJsonMessagesFileName( $code ) )
870  ) {
871  $namesMwFile[$code] = $names[$code];
872  }
873  }
874 
875  ksort( $namesMwFile );
876  return $namesMwFile;
877  }
878 
879  ksort( $returnMw );
880  # 'mw' option; default if it's not one of the other two options (all/mwfile)
881  return $returnMw;
882  }
883 
891  public static function fetchLanguageName( $code, $inLanguage = null, $include = 'all' ) {
892  $code = strtolower( $code );
893  $array = self::fetchLanguageNames( $inLanguage, $include );
894  return !array_key_exists( $code, $array ) ? '' : $array[$code];
895  }
896 
903  public function getMessageFromDB( $msg ) {
904  return $this->msg( $msg )->text();
905  }
906 
913  protected function msg( $msg ) {
914  return wfMessage( $msg )->inLanguage( $this );
915  }
916 
921  public function getMonthName( $key ) {
922  return $this->getMessageFromDB( self::$mMonthMsgs[$key - 1] );
923  }
924 
928  public function getMonthNamesArray() {
929  $monthNames = [ '' ];
930  for ( $i = 1; $i < 13; $i++ ) {
931  $monthNames[] = $this->getMonthName( $i );
932  }
933  return $monthNames;
934  }
935 
940  public function getMonthNameGen( $key ) {
941  return $this->getMessageFromDB( self::$mMonthGenMsgs[$key - 1] );
942  }
943 
948  public function getMonthAbbreviation( $key ) {
949  return $this->getMessageFromDB( self::$mMonthAbbrevMsgs[$key - 1] );
950  }
951 
955  public function getMonthAbbreviationsArray() {
956  $monthNames = [ '' ];
957  for ( $i = 1; $i < 13; $i++ ) {
958  $monthNames[] = $this->getMonthAbbreviation( $i );
959  }
960  return $monthNames;
961  }
962 
967  public function getWeekdayName( $key ) {
968  return $this->getMessageFromDB( self::$mWeekdayMsgs[$key - 1] );
969  }
970 
975  function getWeekdayAbbreviation( $key ) {
976  return $this->getMessageFromDB( self::$mWeekdayAbbrevMsgs[$key - 1] );
977  }
978 
983  function getIranianCalendarMonthName( $key ) {
984  return $this->getMessageFromDB( self::$mIranianCalendarMonthMsgs[$key - 1] );
985  }
986 
991  function getHebrewCalendarMonthName( $key ) {
992  return $this->getMessageFromDB( self::$mHebrewCalendarMonthMsgs[$key - 1] );
993  }
994 
999  function getHebrewCalendarMonthNameGen( $key ) {
1000  return $this->getMessageFromDB( self::$mHebrewCalendarMonthGenMsgs[$key - 1] );
1001  }
1002 
1007  function getHijriCalendarMonthName( $key ) {
1008  return $this->getMessageFromDB( self::$mHijriCalendarMonthMsgs[$key - 1] );
1009  }
1010 
1019  private static function dateTimeObjFormat( &$dateTimeObj, $ts, $zone, $code ) {
1020  if ( !$dateTimeObj ) {
1021  $dateTimeObj = DateTime::createFromFormat(
1022  'YmdHis', $ts, $zone ?: new DateTimeZone( 'UTC' )
1023  );
1024  }
1025  return $dateTimeObj->format( $code );
1026  }
1027 
1097  public function sprintfDate( $format, $ts, DateTimeZone $zone = null, &$ttl = 'unused' ) {
1098  $s = '';
1099  $raw = false;
1100  $roman = false;
1101  $hebrewNum = false;
1102  $dateTimeObj = false;
1103  $rawToggle = false;
1104  $iranian = false;
1105  $hebrew = false;
1106  $hijri = false;
1107  $thai = false;
1108  $minguo = false;
1109  $tenno = false;
1110 
1111  $usedSecond = false;
1112  $usedMinute = false;
1113  $usedHour = false;
1114  $usedAMPM = false;
1115  $usedDay = false;
1116  $usedWeek = false;
1117  $usedMonth = false;
1118  $usedYear = false;
1119  $usedISOYear = false;
1120  $usedIsLeapYear = false;
1121 
1122  $usedHebrewMonth = false;
1123  $usedIranianMonth = false;
1124  $usedHijriMonth = false;
1125  $usedHebrewYear = false;
1126  $usedIranianYear = false;
1127  $usedHijriYear = false;
1128  $usedTennoYear = false;
1129 
1130  if ( strlen( $ts ) !== 14 ) {
1131  throw new MWException( __METHOD__ . ": The timestamp $ts should have 14 characters" );
1132  }
1133 
1134  if ( !ctype_digit( $ts ) ) {
1135  throw new MWException( __METHOD__ . ": The timestamp $ts should be a number" );
1136  }
1137 
1138  $formatLength = strlen( $format );
1139  for ( $p = 0; $p < $formatLength; $p++ ) {
1140  $num = false;
1141  $code = $format[$p];
1142  if ( $code == 'x' && $p < $formatLength - 1 ) {
1143  $code .= $format[++$p];
1144  }
1145 
1146  if ( ( $code === 'xi'
1147  || $code === 'xj'
1148  || $code === 'xk'
1149  || $code === 'xm'
1150  || $code === 'xo'
1151  || $code === 'xt' )
1152  && $p < $formatLength - 1 ) {
1153  $code .= $format[++$p];
1154  }
1155 
1156  switch ( $code ) {
1157  case 'xx':
1158  $s .= 'x';
1159  break;
1160  case 'xn':
1161  $raw = true;
1162  break;
1163  case 'xN':
1164  $rawToggle = !$rawToggle;
1165  break;
1166  case 'xr':
1167  $roman = true;
1168  break;
1169  case 'xh':
1170  $hebrewNum = true;
1171  break;
1172  case 'xg':
1173  $usedMonth = true;
1174  $s .= $this->getMonthNameGen( substr( $ts, 4, 2 ) );
1175  break;
1176  case 'xjx':
1177  $usedHebrewMonth = true;
1178  if ( !$hebrew ) {
1179  $hebrew = self::tsToHebrew( $ts );
1180  }
1181  $s .= $this->getHebrewCalendarMonthNameGen( $hebrew[1] );
1182  break;
1183  case 'd':
1184  $usedDay = true;
1185  $num = substr( $ts, 6, 2 );
1186  break;
1187  case 'D':
1188  $usedDay = true;
1189  $s .= $this->getWeekdayAbbreviation(
1190  Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'w' ) + 1
1191  );
1192  break;
1193  case 'j':
1194  $usedDay = true;
1195  $num = intval( substr( $ts, 6, 2 ) );
1196  break;
1197  case 'xij':
1198  $usedDay = true;
1199  if ( !$iranian ) {
1200  $iranian = self::tsToIranian( $ts );
1201  }
1202  $num = $iranian[2];
1203  break;
1204  case 'xmj':
1205  $usedDay = true;
1206  if ( !$hijri ) {
1207  $hijri = self::tsToHijri( $ts );
1208  }
1209  $num = $hijri[2];
1210  break;
1211  case 'xjj':
1212  $usedDay = true;
1213  if ( !$hebrew ) {
1214  $hebrew = self::tsToHebrew( $ts );
1215  }
1216  $num = $hebrew[2];
1217  break;
1218  case 'l':
1219  $usedDay = true;
1220  $s .= $this->getWeekdayName(
1221  Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'w' ) + 1
1222  );
1223  break;
1224  case 'F':
1225  $usedMonth = true;
1226  $s .= $this->getMonthName( substr( $ts, 4, 2 ) );
1227  break;
1228  case 'xiF':
1229  $usedIranianMonth = true;
1230  if ( !$iranian ) {
1231  $iranian = self::tsToIranian( $ts );
1232  }
1233  $s .= $this->getIranianCalendarMonthName( $iranian[1] );
1234  break;
1235  case 'xmF':
1236  $usedHijriMonth = true;
1237  if ( !$hijri ) {
1238  $hijri = self::tsToHijri( $ts );
1239  }
1240  $s .= $this->getHijriCalendarMonthName( $hijri[1] );
1241  break;
1242  case 'xjF':
1243  $usedHebrewMonth = true;
1244  if ( !$hebrew ) {
1245  $hebrew = self::tsToHebrew( $ts );
1246  }
1247  $s .= $this->getHebrewCalendarMonthName( $hebrew[1] );
1248  break;
1249  case 'm':
1250  $usedMonth = true;
1251  $num = substr( $ts, 4, 2 );
1252  break;
1253  case 'M':
1254  $usedMonth = true;
1255  $s .= $this->getMonthAbbreviation( substr( $ts, 4, 2 ) );
1256  break;
1257  case 'n':
1258  $usedMonth = true;
1259  $num = intval( substr( $ts, 4, 2 ) );
1260  break;
1261  case 'xin':
1262  $usedIranianMonth = true;
1263  if ( !$iranian ) {
1264  $iranian = self::tsToIranian( $ts );
1265  }
1266  $num = $iranian[1];
1267  break;
1268  case 'xmn':
1269  $usedHijriMonth = true;
1270  if ( !$hijri ) {
1271  $hijri = self::tsToHijri( $ts );
1272  }
1273  $num = $hijri[1];
1274  break;
1275  case 'xjn':
1276  $usedHebrewMonth = true;
1277  if ( !$hebrew ) {
1278  $hebrew = self::tsToHebrew( $ts );
1279  }
1280  $num = $hebrew[1];
1281  break;
1282  case 'xjt':
1283  $usedHebrewMonth = true;
1284  if ( !$hebrew ) {
1285  $hebrew = self::tsToHebrew( $ts );
1286  }
1287  $num = $hebrew[3];
1288  break;
1289  case 'Y':
1290  $usedYear = true;
1291  $num = substr( $ts, 0, 4 );
1292  break;
1293  case 'xiY':
1294  $usedIranianYear = true;
1295  if ( !$iranian ) {
1296  $iranian = self::tsToIranian( $ts );
1297  }
1298  $num = $iranian[0];
1299  break;
1300  case 'xmY':
1301  $usedHijriYear = true;
1302  if ( !$hijri ) {
1303  $hijri = self::tsToHijri( $ts );
1304  }
1305  $num = $hijri[0];
1306  break;
1307  case 'xjY':
1308  $usedHebrewYear = true;
1309  if ( !$hebrew ) {
1310  $hebrew = self::tsToHebrew( $ts );
1311  }
1312  $num = $hebrew[0];
1313  break;
1314  case 'xkY':
1315  $usedYear = true;
1316  if ( !$thai ) {
1317  $thai = self::tsToYear( $ts, 'thai' );
1318  }
1319  $num = $thai[0];
1320  break;
1321  case 'xoY':
1322  $usedYear = true;
1323  if ( !$minguo ) {
1324  $minguo = self::tsToYear( $ts, 'minguo' );
1325  }
1326  $num = $minguo[0];
1327  break;
1328  case 'xtY':
1329  $usedTennoYear = true;
1330  if ( !$tenno ) {
1331  $tenno = self::tsToYear( $ts, 'tenno' );
1332  }
1333  $num = $tenno[0];
1334  break;
1335  case 'y':
1336  $usedYear = true;
1337  $num = substr( $ts, 2, 2 );
1338  break;
1339  case 'xiy':
1340  $usedIranianYear = true;
1341  if ( !$iranian ) {
1342  $iranian = self::tsToIranian( $ts );
1343  }
1344  $num = substr( $iranian[0], -2 );
1345  break;
1346  case 'xit':
1347  $usedIranianYear = true;
1348  if ( !$iranian ) {
1349  $iranian = self::tsToIranian( $ts );
1350  }
1351  $num = self::$IRANIAN_DAYS[$iranian[1] - 1];
1352  break;
1353  case 'xiz':
1354  $usedIranianYear = true;
1355  if ( !$iranian ) {
1356  $iranian = self::tsToIranian( $ts );
1357  }
1358  $num = $iranian[3];
1359  break;
1360  case 'a':
1361  $usedAMPM = true;
1362  $s .= intval( substr( $ts, 8, 2 ) ) < 12 ? 'am' : 'pm';
1363  break;
1364  case 'A':
1365  $usedAMPM = true;
1366  $s .= intval( substr( $ts, 8, 2 ) ) < 12 ? 'AM' : 'PM';
1367  break;
1368  case 'g':
1369  $usedHour = true;
1370  $h = substr( $ts, 8, 2 );
1371  $num = $h % 12 ? $h % 12 : 12;
1372  break;
1373  case 'G':
1374  $usedHour = true;
1375  $num = intval( substr( $ts, 8, 2 ) );
1376  break;
1377  case 'h':
1378  $usedHour = true;
1379  $h = substr( $ts, 8, 2 );
1380  $num = sprintf( '%02d', $h % 12 ? $h % 12 : 12 );
1381  break;
1382  case 'H':
1383  $usedHour = true;
1384  $num = substr( $ts, 8, 2 );
1385  break;
1386  case 'i':
1387  $usedMinute = true;
1388  $num = substr( $ts, 10, 2 );
1389  break;
1390  case 's':
1391  $usedSecond = true;
1392  $num = substr( $ts, 12, 2 );
1393  break;
1394  case 'c':
1395  case 'r':
1396  $usedSecond = true;
1397  // fall through
1398  case 'e':
1399  case 'O':
1400  case 'P':
1401  case 'T':
1402  $s .= Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1403  break;
1404  case 'w':
1405  case 'N':
1406  case 'z':
1407  $usedDay = true;
1408  $num = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1409  break;
1410  case 'W':
1411  $usedWeek = true;
1412  $num = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1413  break;
1414  case 't':
1415  $usedMonth = true;
1416  $num = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1417  break;
1418  case 'L':
1419  $usedIsLeapYear = true;
1420  $num = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1421  break;
1422  case 'o':
1423  $usedISOYear = true;
1424  $num = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1425  break;
1426  case 'U':
1427  $usedSecond = true;
1428  // fall through
1429  case 'I':
1430  case 'Z':
1431  $num = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1432  break;
1433  case '\\':
1434  # Backslash escaping
1435  if ( $p < $formatLength - 1 ) {
1436  $s .= $format[++$p];
1437  } else {
1438  $s .= '\\';
1439  }
1440  break;
1441  case '"':
1442  # Quoted literal
1443  if ( $p < $formatLength - 1 ) {
1444  $endQuote = strpos( $format, '"', $p + 1 );
1445  if ( $endQuote === false ) {
1446  # No terminating quote, assume literal "
1447  $s .= '"';
1448  } else {
1449  $s .= substr( $format, $p + 1, $endQuote - $p - 1 );
1450  $p = $endQuote;
1451  }
1452  } else {
1453  # Quote at end of string, assume literal "
1454  $s .= '"';
1455  }
1456  break;
1457  default:
1458  $s .= $format[$p];
1459  }
1460  if ( $num !== false ) {
1461  if ( $rawToggle || $raw ) {
1462  $s .= $num;
1463  $raw = false;
1464  } elseif ( $roman ) {
1465  $s .= Language::romanNumeral( $num );
1466  $roman = false;
1467  } elseif ( $hebrewNum ) {
1468  $s .= self::hebrewNumeral( $num );
1469  $hebrewNum = false;
1470  } else {
1471  $s .= $this->formatNum( $num, true );
1472  }
1473  }
1474  }
1475 
1476  if ( $ttl === 'unused' ) {
1477  // No need to calculate the TTL, the caller wont use it anyway.
1478  } elseif ( $usedSecond ) {
1479  $ttl = 1;
1480  } elseif ( $usedMinute ) {
1481  $ttl = 60 - substr( $ts, 12, 2 );
1482  } elseif ( $usedHour ) {
1483  $ttl = 3600 - substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
1484  } elseif ( $usedAMPM ) {
1485  $ttl = 43200 - ( substr( $ts, 8, 2 ) % 12 ) * 3600 -
1486  substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
1487  } elseif (
1488  $usedDay ||
1489  $usedHebrewMonth ||
1490  $usedIranianMonth ||
1491  $usedHijriMonth ||
1492  $usedHebrewYear ||
1493  $usedIranianYear ||
1494  $usedHijriYear ||
1495  $usedTennoYear
1496  ) {
1497  // @todo Someone who understands the non-Gregorian calendars
1498  // should write proper logic for them so that they don't need purged every day.
1499  $ttl = 86400 - substr( $ts, 8, 2 ) * 3600 -
1500  substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
1501  } else {
1502  $possibleTtls = [];
1503  $timeRemainingInDay = 86400 - substr( $ts, 8, 2 ) * 3600 -
1504  substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
1505  if ( $usedWeek ) {
1506  $possibleTtls[] =
1507  ( 7 - Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'N' ) ) * 86400 +
1508  $timeRemainingInDay;
1509  } elseif ( $usedISOYear ) {
1510  // December 28th falls on the last ISO week of the year, every year.
1511  // The last ISO week of a year can be 52 or 53.
1512  $lastWeekOfISOYear = DateTime::createFromFormat(
1513  'Ymd',
1514  substr( $ts, 0, 4 ) . '1228',
1515  $zone ?: new DateTimeZone( 'UTC' )
1516  )->format( 'W' );
1517  $currentISOWeek = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'W' );
1518  $weeksRemaining = $lastWeekOfISOYear - $currentISOWeek;
1519  $timeRemainingInWeek =
1520  ( 7 - Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'N' ) ) * 86400
1521  + $timeRemainingInDay;
1522  $possibleTtls[] = $weeksRemaining * 604800 + $timeRemainingInWeek;
1523  }
1524 
1525  if ( $usedMonth ) {
1526  $possibleTtls[] =
1527  ( Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 't' ) -
1528  substr( $ts, 6, 2 ) ) * 86400
1529  + $timeRemainingInDay;
1530  } elseif ( $usedYear ) {
1531  $possibleTtls[] =
1532  ( Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'L' ) + 364 -
1533  Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'z' ) ) * 86400
1534  + $timeRemainingInDay;
1535  } elseif ( $usedIsLeapYear ) {
1536  $year = substr( $ts, 0, 4 );
1537  $timeRemainingInYear =
1538  ( Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'L' ) + 364 -
1539  Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'z' ) ) * 86400
1540  + $timeRemainingInDay;
1541  $mod = $year % 4;
1542  if ( $mod || ( !( $year % 100 ) && $year % 400 ) ) {
1543  // this isn't a leap year. see when the next one starts
1544  $nextCandidate = $year - $mod + 4;
1545  if ( $nextCandidate % 100 || !( $nextCandidate % 400 ) ) {
1546  $possibleTtls[] = ( $nextCandidate - $year - 1 ) * 365 * 86400 +
1547  $timeRemainingInYear;
1548  } else {
1549  $possibleTtls[] = ( $nextCandidate - $year + 3 ) * 365 * 86400 +
1550  $timeRemainingInYear;
1551  }
1552  } else {
1553  // this is a leap year, so the next year isn't
1554  $possibleTtls[] = $timeRemainingInYear;
1555  }
1556  }
1557 
1558  if ( $possibleTtls ) {
1559  $ttl = min( $possibleTtls );
1560  }
1561  }
1562 
1563  return $s;
1564  }
1565 
1566  private static $GREG_DAYS = [ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ];
1567  private static $IRANIAN_DAYS = [ 31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29 ];
1568 
1581  private static function tsToIranian( $ts ) {
1582  $gy = substr( $ts, 0, 4 ) -1600;
1583  $gm = substr( $ts, 4, 2 ) -1;
1584  $gd = substr( $ts, 6, 2 ) -1;
1585 
1586  # Days passed from the beginning (including leap years)
1587  $gDayNo = 365 * $gy
1588  + floor( ( $gy + 3 ) / 4 )
1589  - floor( ( $gy + 99 ) / 100 )
1590  + floor( ( $gy + 399 ) / 400 );
1591 
1592  // Add days of the past months of this year
1593  for ( $i = 0; $i < $gm; $i++ ) {
1594  $gDayNo += self::$GREG_DAYS[$i];
1595  }
1596 
1597  // Leap years
1598  if ( $gm > 1 && ( ( $gy % 4 === 0 && $gy % 100 !== 0 || ( $gy % 400 == 0 ) ) ) ) {
1599  $gDayNo++;
1600  }
1601 
1602  // Days passed in current month
1603  $gDayNo += (int)$gd;
1604 
1605  $jDayNo = $gDayNo - 79;
1606 
1607  $jNp = floor( $jDayNo / 12053 );
1608  $jDayNo %= 12053;
1609 
1610  $jy = 979 + 33 * $jNp + 4 * floor( $jDayNo / 1461 );
1611  $jDayNo %= 1461;
1612 
1613  if ( $jDayNo >= 366 ) {
1614  $jy += floor( ( $jDayNo - 1 ) / 365 );
1615  $jDayNo = floor( ( $jDayNo - 1 ) % 365 );
1616  }
1617 
1618  $jz = $jDayNo;
1619 
1620  for ( $i = 0; $i < 11 && $jDayNo >= self::$IRANIAN_DAYS[$i]; $i++ ) {
1621  $jDayNo -= self::$IRANIAN_DAYS[$i];
1622  }
1623 
1624  $jm = $i + 1;
1625  $jd = $jDayNo + 1;
1626 
1627  return [ $jy, $jm, $jd, $jz ];
1628  }
1629 
1641  private static function tsToHijri( $ts ) {
1642  $year = substr( $ts, 0, 4 );
1643  $month = substr( $ts, 4, 2 );
1644  $day = substr( $ts, 6, 2 );
1645 
1646  $zyr = $year;
1647  $zd = $day;
1648  $zm = $month;
1649  $zy = $zyr;
1650 
1651  if (
1652  ( $zy > 1582 ) || ( ( $zy == 1582 ) && ( $zm > 10 ) ) ||
1653  ( ( $zy == 1582 ) && ( $zm == 10 ) && ( $zd > 14 ) )
1654  ) {
1655  $zjd = (int)( ( 1461 * ( $zy + 4800 + (int)( ( $zm - 14 ) / 12 ) ) ) / 4 ) +
1656  (int)( ( 367 * ( $zm - 2 - 12 * ( (int)( ( $zm - 14 ) / 12 ) ) ) ) / 12 ) -
1657  (int)( ( 3 * (int)( ( ( $zy + 4900 + (int)( ( $zm - 14 ) / 12 ) ) / 100 ) ) ) / 4 ) +
1658  $zd - 32075;
1659  } else {
1660  $zjd = 367 * $zy - (int)( ( 7 * ( $zy + 5001 + (int)( ( $zm - 9 ) / 7 ) ) ) / 4 ) +
1661  (int)( ( 275 * $zm ) / 9 ) + $zd + 1729777;
1662  }
1663 
1664  $zl = $zjd -1948440 + 10632;
1665  $zn = (int)( ( $zl - 1 ) / 10631 );
1666  $zl = $zl - 10631 * $zn + 354;
1667  $zj = ( (int)( ( 10985 - $zl ) / 5316 ) ) * ( (int)( ( 50 * $zl ) / 17719 ) ) +
1668  ( (int)( $zl / 5670 ) ) * ( (int)( ( 43 * $zl ) / 15238 ) );
1669  $zl = $zl - ( (int)( ( 30 - $zj ) / 15 ) ) * ( (int)( ( 17719 * $zj ) / 50 ) ) -
1670  ( (int)( $zj / 16 ) ) * ( (int)( ( 15238 * $zj ) / 43 ) ) + 29;
1671  $zm = (int)( ( 24 * $zl ) / 709 );
1672  $zd = $zl - (int)( ( 709 * $zm ) / 24 );
1673  $zy = 30 * $zn + $zj - 30;
1674 
1675  return [ $zy, $zm, $zd ];
1676  }
1677 
1693  private static function tsToHebrew( $ts ) {
1694  # Parse date
1695  $year = substr( $ts, 0, 4 );
1696  $month = substr( $ts, 4, 2 );
1697  $day = substr( $ts, 6, 2 );
1698 
1699  # Calculate Hebrew year
1700  $hebrewYear = $year + 3760;
1701 
1702  # Month number when September = 1, August = 12
1703  $month += 4;
1704  if ( $month > 12 ) {
1705  # Next year
1706  $month -= 12;
1707  $year++;
1708  $hebrewYear++;
1709  }
1710 
1711  # Calculate day of year from 1 September
1712  $dayOfYear = $day;
1713  for ( $i = 1; $i < $month; $i++ ) {
1714  if ( $i == 6 ) {
1715  # February
1716  $dayOfYear += 28;
1717  # Check if the year is leap
1718  if ( $year % 400 == 0 || ( $year % 4 == 0 && $year % 100 > 0 ) ) {
1719  $dayOfYear++;
1720  }
1721  } elseif ( $i == 8 || $i == 10 || $i == 1 || $i == 3 ) {
1722  $dayOfYear += 30;
1723  } else {
1724  $dayOfYear += 31;
1725  }
1726  }
1727 
1728  # Calculate the start of the Hebrew year
1729  $start = self::hebrewYearStart( $hebrewYear );
1730 
1731  # Calculate next year's start
1732  if ( $dayOfYear <= $start ) {
1733  # Day is before the start of the year - it is the previous year
1734  # Next year's start
1735  $nextStart = $start;
1736  # Previous year
1737  $year--;
1738  $hebrewYear--;
1739  # Add days since previous year's 1 September
1740  $dayOfYear += 365;
1741  if ( ( $year % 400 == 0 ) || ( $year % 100 != 0 && $year % 4 == 0 ) ) {
1742  # Leap year
1743  $dayOfYear++;
1744  }
1745  # Start of the new (previous) year
1746  $start = self::hebrewYearStart( $hebrewYear );
1747  } else {
1748  # Next year's start
1749  $nextStart = self::hebrewYearStart( $hebrewYear + 1 );
1750  }
1751 
1752  # Calculate Hebrew day of year
1753  $hebrewDayOfYear = $dayOfYear - $start;
1754 
1755  # Difference between year's days
1756  $diff = $nextStart - $start;
1757  # Add 12 (or 13 for leap years) days to ignore the difference between
1758  # Hebrew and Gregorian year (353 at least vs. 365/6) - now the
1759  # difference is only about the year type
1760  if ( ( $year % 400 == 0 ) || ( $year % 100 != 0 && $year % 4 == 0 ) ) {
1761  $diff += 13;
1762  } else {
1763  $diff += 12;
1764  }
1765 
1766  # Check the year pattern, and is leap year
1767  # 0 means an incomplete year, 1 means a regular year, 2 means a complete year
1768  # This is mod 30, to work on both leap years (which add 30 days of Adar I)
1769  # and non-leap years
1770  $yearPattern = $diff % 30;
1771  # Check if leap year
1772  $isLeap = $diff >= 30;
1773 
1774  # Calculate day in the month from number of day in the Hebrew year
1775  # Don't check Adar - if the day is not in Adar, we will stop before;
1776  # if it is in Adar, we will use it to check if it is Adar I or Adar II
1777  $hebrewDay = $hebrewDayOfYear;
1778  $hebrewMonth = 1;
1779  $days = 0;
1780  while ( $hebrewMonth <= 12 ) {
1781  # Calculate days in this month
1782  if ( $isLeap && $hebrewMonth == 6 ) {
1783  # Adar in a leap year
1784  if ( $isLeap ) {
1785  # Leap year - has Adar I, with 30 days, and Adar II, with 29 days
1786  $days = 30;
1787  if ( $hebrewDay <= $days ) {
1788  # Day in Adar I
1789  $hebrewMonth = 13;
1790  } else {
1791  # Subtract the days of Adar I
1792  $hebrewDay -= $days;
1793  # Try Adar II
1794  $days = 29;
1795  if ( $hebrewDay <= $days ) {
1796  # Day in Adar II
1797  $hebrewMonth = 14;
1798  }
1799  }
1800  }
1801  } elseif ( $hebrewMonth == 2 && $yearPattern == 2 ) {
1802  # Cheshvan in a complete year (otherwise as the rule below)
1803  $days = 30;
1804  } elseif ( $hebrewMonth == 3 && $yearPattern == 0 ) {
1805  # Kislev in an incomplete year (otherwise as the rule below)
1806  $days = 29;
1807  } else {
1808  # Odd months have 30 days, even have 29
1809  $days = 30 - ( $hebrewMonth - 1 ) % 2;
1810  }
1811  if ( $hebrewDay <= $days ) {
1812  # In the current month
1813  break;
1814  } else {
1815  # Subtract the days of the current month
1816  $hebrewDay -= $days;
1817  # Try in the next month
1818  $hebrewMonth++;
1819  }
1820  }
1821 
1822  return [ $hebrewYear, $hebrewMonth, $hebrewDay, $days ];
1823  }
1824 
1834  private static function hebrewYearStart( $year ) {
1835  $a = intval( ( 12 * ( $year - 1 ) + 17 ) % 19 );
1836  $b = intval( ( $year - 1 ) % 4 );
1837  $m = 32.044093161144 + 1.5542417966212 * $a + $b / 4.0 - 0.0031777940220923 * ( $year - 1 );
1838  if ( $m < 0 ) {
1839  $m--;
1840  }
1841  $Mar = intval( $m );
1842  if ( $m < 0 ) {
1843  $m++;
1844  }
1845  $m -= $Mar;
1846 
1847  $c = intval( ( $Mar + 3 * ( $year - 1 ) + 5 * $b + 5 ) % 7 );
1848  if ( $c == 0 && $a > 11 && $m >= 0.89772376543210 ) {
1849  $Mar++;
1850  } elseif ( $c == 1 && $a > 6 && $m >= 0.63287037037037 ) {
1851  $Mar += 2;
1852  } elseif ( $c == 2 || $c == 4 || $c == 6 ) {
1853  $Mar++;
1854  }
1855 
1856  $Mar += intval( ( $year - 3761 ) / 100 ) - intval( ( $year - 3761 ) / 400 ) - 24;
1857  return $Mar;
1858  }
1859 
1872  private static function tsToYear( $ts, $cName ) {
1873  $gy = substr( $ts, 0, 4 );
1874  $gm = substr( $ts, 4, 2 );
1875  $gd = substr( $ts, 6, 2 );
1876 
1877  if ( !strcmp( $cName, 'thai' ) ) {
1878  # Thai solar dates
1879  # Add 543 years to the Gregorian calendar
1880  # Months and days are identical
1881  $gy_offset = $gy + 543;
1882  } elseif ( ( !strcmp( $cName, 'minguo' ) ) || !strcmp( $cName, 'juche' ) ) {
1883  # Minguo dates
1884  # Deduct 1911 years from the Gregorian calendar
1885  # Months and days are identical
1886  $gy_offset = $gy - 1911;
1887  } elseif ( !strcmp( $cName, 'tenno' ) ) {
1888  # Nengō dates up to Meiji period
1889  # Deduct years from the Gregorian calendar
1890  # depending on the nengo periods
1891  # Months and days are identical
1892  if ( ( $gy < 1912 )
1893  || ( ( $gy == 1912 ) && ( $gm < 7 ) )
1894  || ( ( $gy == 1912 ) && ( $gm == 7 ) && ( $gd < 31 ) )
1895  ) {
1896  # Meiji period
1897  $gy_gannen = $gy - 1868 + 1;
1898  $gy_offset = $gy_gannen;
1899  if ( $gy_gannen == 1 ) {
1900  $gy_offset = '元';
1901  }
1902  $gy_offset = '明治' . $gy_offset;
1903  } elseif (
1904  ( ( $gy == 1912 ) && ( $gm == 7 ) && ( $gd == 31 ) ) ||
1905  ( ( $gy == 1912 ) && ( $gm >= 8 ) ) ||
1906  ( ( $gy > 1912 ) && ( $gy < 1926 ) ) ||
1907  ( ( $gy == 1926 ) && ( $gm < 12 ) ) ||
1908  ( ( $gy == 1926 ) && ( $gm == 12 ) && ( $gd < 26 ) )
1909  ) {
1910  # Taishō period
1911  $gy_gannen = $gy - 1912 + 1;
1912  $gy_offset = $gy_gannen;
1913  if ( $gy_gannen == 1 ) {
1914  $gy_offset = '元';
1915  }
1916  $gy_offset = '大正' . $gy_offset;
1917  } elseif (
1918  ( ( $gy == 1926 ) && ( $gm == 12 ) && ( $gd >= 26 ) ) ||
1919  ( ( $gy > 1926 ) && ( $gy < 1989 ) ) ||
1920  ( ( $gy == 1989 ) && ( $gm == 1 ) && ( $gd < 8 ) )
1921  ) {
1922  # Shōwa period
1923  $gy_gannen = $gy - 1926 + 1;
1924  $gy_offset = $gy_gannen;
1925  if ( $gy_gannen == 1 ) {
1926  $gy_offset = '元';
1927  }
1928  $gy_offset = '昭和' . $gy_offset;
1929  } else {
1930  # Heisei period
1931  $gy_gannen = $gy - 1989 + 1;
1932  $gy_offset = $gy_gannen;
1933  if ( $gy_gannen == 1 ) {
1934  $gy_offset = '元';
1935  }
1936  $gy_offset = '平成' . $gy_offset;
1937  }
1938  } else {
1939  $gy_offset = $gy;
1940  }
1941 
1942  return [ $gy_offset, $gm, $gd ];
1943  }
1944 
1958  private static function strongDirFromContent( $text = '' ) {
1959  if ( !preg_match( self::$strongDirRegex, $text, $matches ) ) {
1960  return null;
1961  }
1962  if ( $matches[1] === '' ) {
1963  return 'rtl';
1964  }
1965  return 'ltr';
1966  }
1967 
1975  static function romanNumeral( $num ) {
1976  static $table = [
1977  [ '', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X' ],
1978  [ '', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC', 'C' ],
1979  [ '', 'C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM', 'M' ],
1980  [ '', 'M', 'MM', 'MMM', 'MMMM', 'MMMMM', 'MMMMMM', 'MMMMMMM',
1981  'MMMMMMMM', 'MMMMMMMMM', 'MMMMMMMMMM' ]
1982  ];
1983 
1984  $num = intval( $num );
1985  if ( $num > 10000 || $num <= 0 ) {
1986  return $num;
1987  }
1988 
1989  $s = '';
1990  for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) {
1991  if ( $num >= $pow10 ) {
1992  $s .= $table[$i][(int)floor( $num / $pow10 )];
1993  }
1994  $num = $num % $pow10;
1995  }
1996  return $s;
1997  }
1998 
2006  static function hebrewNumeral( $num ) {
2007  static $table = [
2008  [ '', 'א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ז', 'ח', 'ט', 'י' ],
2009  [ '', 'י', 'כ', 'ל', 'מ', 'נ', 'ס', 'ע', 'פ', 'צ', 'ק' ],
2010  [ '',
2011  [ 'ק' ],
2012  [ 'ר' ],
2013  [ 'ש' ],
2014  [ 'ת' ],
2015  [ 'ת', 'ק' ],
2016  [ 'ת', 'ר' ],
2017  [ 'ת', 'ש' ],
2018  [ 'ת', 'ת' ],
2019  [ 'ת', 'ת', 'ק' ],
2020  [ 'ת', 'ת', 'ר' ],
2021  ],
2022  [ '', 'א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ז', 'ח', 'ט', 'י' ]
2023  ];
2024 
2025  $num = intval( $num );
2026  if ( $num > 9999 || $num <= 0 ) {
2027  return $num;
2028  }
2029 
2030  // Round thousands have special notations
2031  if ( $num === 1000 ) {
2032  return "א' אלף";
2033  } elseif ( $num % 1000 === 0 ) {
2034  return $table[0][$num / 1000] . "' אלפים";
2035  }
2036 
2037  $letters = [];
2038 
2039  for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) {
2040  if ( $num >= $pow10 ) {
2041  if ( $num === 15 || $num === 16 ) {
2042  $letters[] = $table[0][9];
2043  $letters[] = $table[0][$num - 9];
2044  $num = 0;
2045  } else {
2046  $letters = array_merge(
2047  $letters,
2048  (array)$table[$i][intval( $num / $pow10 )]
2049  );
2050 
2051  if ( $pow10 === 1000 ) {
2052  $letters[] = "'";
2053  }
2054  }
2055  }
2056 
2057  $num = $num % $pow10;
2058  }
2059 
2060  $preTransformLength = count( $letters );
2061  if ( $preTransformLength === 1 ) {
2062  // Add geresh (single quote) to one-letter numbers
2063  $letters[] = "'";
2064  } else {
2065  $lastIndex = $preTransformLength - 1;
2066  $letters[$lastIndex] = str_replace(
2067  [ 'כ', 'מ', 'נ', 'פ', 'צ' ],
2068  [ 'ך', 'ם', 'ן', 'ף', 'ץ' ],
2069  $letters[$lastIndex]
2070  );
2071 
2072  // Add gershayim (double quote) to multiple-letter numbers,
2073  // but exclude numbers with only one letter after the thousands
2074  // (1001-1009, 1020, 1030, 2001-2009, etc.)
2075  if ( $letters[1] === "'" && $preTransformLength === 3 ) {
2076  $letters[] = "'";
2077  } else {
2078  array_splice( $letters, -1, 0, '"' );
2079  }
2080  }
2081 
2082  return implode( $letters );
2083  }
2084 
2093  public function userAdjust( $ts, $tz = false ) {
2094  global $wgUser, $wgLocalTZoffset;
2095 
2096  if ( $tz === false ) {
2097  $tz = $wgUser->getOption( 'timecorrection' );
2098  }
2099 
2100  $data = explode( '|', $tz, 3 );
2101 
2102  if ( $data[0] == 'ZoneInfo' ) {
2103  try {
2104  $userTZ = new DateTimeZone( $data[2] );
2105  $date = new DateTime( $ts, new DateTimeZone( 'UTC' ) );
2106  $date->setTimezone( $userTZ );
2107  return $date->format( 'YmdHis' );
2108  } catch ( Exception $e ) {
2109  // Unrecognized timezone, default to 'Offset' with the stored offset.
2110  $data[0] = 'Offset';
2111  }
2112  }
2113 
2114  if ( $data[0] == 'System' || $tz == '' ) {
2115  # Global offset in minutes.
2116  $minDiff = $wgLocalTZoffset;
2117  } elseif ( $data[0] == 'Offset' ) {
2118  $minDiff = intval( $data[1] );
2119  } else {
2120  $data = explode( ':', $tz );
2121  if ( count( $data ) == 2 ) {
2122  $data[0] = intval( $data[0] );
2123  $data[1] = intval( $data[1] );
2124  $minDiff = abs( $data[0] ) * 60 + $data[1];
2125  if ( $data[0] < 0 ) {
2126  $minDiff = -$minDiff;
2127  }
2128  } else {
2129  $minDiff = intval( $data[0] ) * 60;
2130  }
2131  }
2132 
2133  # No difference ? Return time unchanged
2134  if ( 0 == $minDiff ) {
2135  return $ts;
2136  }
2137 
2138  MediaWiki\suppressWarnings(); // E_STRICT system time bitching
2139  # Generate an adjusted date; take advantage of the fact that mktime
2140  # will normalize out-of-range values so we don't have to split $minDiff
2141  # into hours and minutes.
2142  $t = mktime( (
2143  (int)substr( $ts, 8, 2 ) ), # Hours
2144  (int)substr( $ts, 10, 2 ) + $minDiff, # Minutes
2145  (int)substr( $ts, 12, 2 ), # Seconds
2146  (int)substr( $ts, 4, 2 ), # Month
2147  (int)substr( $ts, 6, 2 ), # Day
2148  (int)substr( $ts, 0, 4 ) ); # Year
2149 
2150  $date = date( 'YmdHis', $t );
2151  MediaWiki\restoreWarnings();
2152 
2153  return $date;
2154  }
2155 
2171  function dateFormat( $usePrefs = true ) {
2172  global $wgUser;
2173 
2174  if ( is_bool( $usePrefs ) ) {
2175  if ( $usePrefs ) {
2176  $datePreference = $wgUser->getDatePreference();
2177  } else {
2178  $datePreference = (string)User::getDefaultOption( 'date' );
2179  }
2180  } else {
2181  $datePreference = (string)$usePrefs;
2182  }
2183 
2184  // return int
2185  if ( $datePreference == '' ) {
2186  return 'default';
2187  }
2188 
2189  return $datePreference;
2190  }
2191 
2202  function getDateFormatString( $type, $pref ) {
2203  $wasDefault = false;
2204  if ( $pref == 'default' ) {
2205  $wasDefault = true;
2206  $pref = $this->getDefaultDateFormat();
2207  }
2208 
2209  if ( !isset( $this->dateFormatStrings[$type][$pref] ) ) {
2210  $df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" );
2211 
2212  if ( $type === 'pretty' && $df === null ) {
2213  $df = $this->getDateFormatString( 'date', $pref );
2214  }
2215 
2216  if ( !$wasDefault && $df === null ) {
2217  $pref = $this->getDefaultDateFormat();
2218  $df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" );
2219  }
2220 
2221  $this->dateFormatStrings[$type][$pref] = $df;
2222  }
2223  return $this->dateFormatStrings[$type][$pref];
2224  }
2225 
2236  public function date( $ts, $adj = false, $format = true, $timecorrection = false ) {
2237  $ts = wfTimestamp( TS_MW, $ts );
2238  if ( $adj ) {
2239  $ts = $this->userAdjust( $ts, $timecorrection );
2240  }
2241  $df = $this->getDateFormatString( 'date', $this->dateFormat( $format ) );
2242  return $this->sprintfDate( $df, $ts );
2243  }
2244 
2255  public function time( $ts, $adj = false, $format = true, $timecorrection = false ) {
2256  $ts = wfTimestamp( TS_MW, $ts );
2257  if ( $adj ) {
2258  $ts = $this->userAdjust( $ts, $timecorrection );
2259  }
2260  $df = $this->getDateFormatString( 'time', $this->dateFormat( $format ) );
2261  return $this->sprintfDate( $df, $ts );
2262  }
2263 
2275  public function timeanddate( $ts, $adj = false, $format = true, $timecorrection = false ) {
2276  $ts = wfTimestamp( TS_MW, $ts );
2277  if ( $adj ) {
2278  $ts = $this->userAdjust( $ts, $timecorrection );
2279  }
2280  $df = $this->getDateFormatString( 'both', $this->dateFormat( $format ) );
2281  return $this->sprintfDate( $df, $ts );
2282  }
2283 
2294  public function formatDuration( $seconds, array $chosenIntervals = [] ) {
2295  $intervals = $this->getDurationIntervals( $seconds, $chosenIntervals );
2296 
2297  $segments = [];
2298 
2299  foreach ( $intervals as $intervalName => $intervalValue ) {
2300  // Messages: duration-seconds, duration-minutes, duration-hours, duration-days, duration-weeks,
2301  // duration-years, duration-decades, duration-centuries, duration-millennia
2302  $message = wfMessage( 'duration-' . $intervalName )->numParams( $intervalValue );
2303  $segments[] = $message->inLanguage( $this )->escaped();
2304  }
2305 
2306  return $this->listToText( $segments );
2307  }
2308 
2320  public function getDurationIntervals( $seconds, array $chosenIntervals = [] ) {
2321  if ( empty( $chosenIntervals ) ) {
2322  $chosenIntervals = [
2323  'millennia',
2324  'centuries',
2325  'decades',
2326  'years',
2327  'days',
2328  'hours',
2329  'minutes',
2330  'seconds'
2331  ];
2332  }
2333 
2334  $intervals = array_intersect_key( self::$durationIntervals, array_flip( $chosenIntervals ) );
2335  $sortedNames = array_keys( $intervals );
2336  $smallestInterval = array_pop( $sortedNames );
2337 
2338  $segments = [];
2339 
2340  foreach ( $intervals as $name => $length ) {
2341  $value = floor( $seconds / $length );
2342 
2343  if ( $value > 0 || ( $name == $smallestInterval && empty( $segments ) ) ) {
2344  $seconds -= $value * $length;
2345  $segments[$name] = $value;
2346  }
2347  }
2348 
2349  return $segments;
2350  }
2351 
2371  private function internalUserTimeAndDate( $type, $ts, User $user, array $options ) {
2372  $ts = wfTimestamp( TS_MW, $ts );
2373  $options += [ 'timecorrection' => true, 'format' => true ];
2374  if ( $options['timecorrection'] !== false ) {
2375  if ( $options['timecorrection'] === true ) {
2376  $offset = $user->getOption( 'timecorrection' );
2377  } else {
2378  $offset = $options['timecorrection'];
2379  }
2380  $ts = $this->userAdjust( $ts, $offset );
2381  }
2382  if ( $options['format'] === true ) {
2383  $format = $user->getDatePreference();
2384  } else {
2385  $format = $options['format'];
2386  }
2387  $df = $this->getDateFormatString( $type, $this->dateFormat( $format ) );
2388  return $this->sprintfDate( $df, $ts );
2389  }
2390 
2410  public function userDate( $ts, User $user, array $options = [] ) {
2411  return $this->internalUserTimeAndDate( 'date', $ts, $user, $options );
2412  }
2413 
2433  public function userTime( $ts, User $user, array $options = [] ) {
2434  return $this->internalUserTimeAndDate( 'time', $ts, $user, $options );
2435  }
2436 
2456  public function userTimeAndDate( $ts, User $user, array $options = [] ) {
2457  return $this->internalUserTimeAndDate( 'both', $ts, $user, $options );
2458  }
2459 
2475  public function getHumanTimestamp(
2476  MWTimestamp $time, MWTimestamp $relativeTo = null, User $user = null
2477  ) {
2478  if ( $relativeTo === null ) {
2479  $relativeTo = new MWTimestamp();
2480  }
2481  if ( $user === null ) {
2482  $user = RequestContext::getMain()->getUser();
2483  }
2484 
2485  // Adjust for the user's timezone.
2486  $offsetThis = $time->offsetForUser( $user );
2487  $offsetRel = $relativeTo->offsetForUser( $user );
2488 
2489  $ts = '';
2490  if ( Hooks::run( 'GetHumanTimestamp', [ &$ts, $time, $relativeTo, $user, $this ] ) ) {
2491  $ts = $this->getHumanTimestampInternal( $time, $relativeTo, $user );
2492  }
2493 
2494  // Reset the timezone on the objects.
2495  $time->timestamp->sub( $offsetThis );
2496  $relativeTo->timestamp->sub( $offsetRel );
2497 
2498  return $ts;
2499  }
2500 
2512  private function getHumanTimestampInternal(
2513  MWTimestamp $ts, MWTimestamp $relativeTo, User $user
2514  ) {
2515  $diff = $ts->diff( $relativeTo );
2516  $diffDay = (bool)( (int)$ts->timestamp->format( 'w' ) -
2517  (int)$relativeTo->timestamp->format( 'w' ) );
2518  $days = $diff->days ?: (int)$diffDay;
2519  if ( $diff->invert || $days > 5
2520  && $ts->timestamp->format( 'Y' ) !== $relativeTo->timestamp->format( 'Y' )
2521  ) {
2522  // Timestamps are in different years: use full timestamp
2523  // Also do full timestamp for future dates
2527  $format = $this->getDateFormatString( 'both', $user->getDatePreference() ?: 'default' );
2528  $ts = $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) );
2529  } elseif ( $days > 5 ) {
2530  // Timestamps are in same year, but more than 5 days ago: show day and month only.
2531  $format = $this->getDateFormatString( 'pretty', $user->getDatePreference() ?: 'default' );
2532  $ts = $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) );
2533  } elseif ( $days > 1 ) {
2534  // Timestamp within the past week: show the day of the week and time
2535  $format = $this->getDateFormatString( 'time', $user->getDatePreference() ?: 'default' );
2536  $weekday = self::$mWeekdayMsgs[$ts->timestamp->format( 'w' )];
2537  // Messages:
2538  // sunday-at, monday-at, tuesday-at, wednesday-at, thursday-at, friday-at, saturday-at
2539  $ts = wfMessage( "$weekday-at" )
2540  ->inLanguage( $this )
2541  ->params( $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) ) )
2542  ->text();
2543  } elseif ( $days == 1 ) {
2544  // Timestamp was yesterday: say 'yesterday' and the time.
2545  $format = $this->getDateFormatString( 'time', $user->getDatePreference() ?: 'default' );
2546  $ts = wfMessage( 'yesterday-at' )
2547  ->inLanguage( $this )
2548  ->params( $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) ) )
2549  ->text();
2550  } elseif ( $diff->h > 1 || $diff->h == 1 && $diff->i > 30 ) {
2551  // Timestamp was today, but more than 90 minutes ago: say 'today' and the time.
2552  $format = $this->getDateFormatString( 'time', $user->getDatePreference() ?: 'default' );
2553  $ts = wfMessage( 'today-at' )
2554  ->inLanguage( $this )
2555  ->params( $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) ) )
2556  ->text();
2557 
2558  // From here on in, the timestamp was soon enough ago so that we can simply say
2559  // XX units ago, e.g., "2 hours ago" or "5 minutes ago"
2560  } elseif ( $diff->h == 1 ) {
2561  // Less than 90 minutes, but more than an hour ago.
2562  $ts = wfMessage( 'hours-ago' )->inLanguage( $this )->numParams( 1 )->text();
2563  } elseif ( $diff->i >= 1 ) {
2564  // A few minutes ago.
2565  $ts = wfMessage( 'minutes-ago' )->inLanguage( $this )->numParams( $diff->i )->text();
2566  } elseif ( $diff->s >= 30 ) {
2567  // Less than a minute, but more than 30 sec ago.
2568  $ts = wfMessage( 'seconds-ago' )->inLanguage( $this )->numParams( $diff->s )->text();
2569  } else {
2570  // Less than 30 seconds ago.
2571  $ts = wfMessage( 'just-now' )->text();
2572  }
2573 
2574  return $ts;
2575  }
2576 
2581  public function getMessage( $key ) {
2582  return self::$dataCache->getSubitem( $this->mCode, 'messages', $key );
2583  }
2584 
2588  function getAllMessages() {
2589  return self::$dataCache->getItem( $this->mCode, 'messages' );
2590  }
2591 
2598  public function iconv( $in, $out, $string ) {
2599  # Even with //IGNORE iconv can whine about illegal characters in
2600  # *input* string. We just ignore those too.
2601  # REF: https://bugs.php.net/bug.php?id=37166
2602  # REF: https://phabricator.wikimedia.org/T18885
2603  MediaWiki\suppressWarnings();
2604  $text = iconv( $in, $out . '//IGNORE', $string );
2605  MediaWiki\restoreWarnings();
2606  return $text;
2607  }
2608 
2609  // callback functions for ucwords(), ucwordbreaks()
2610 
2616  return $this->ucfirst( $matches[1] );
2617  }
2618 
2624  return mb_strtoupper( $matches[0] );
2625  }
2626 
2632  return mb_strtoupper( $matches[0] );
2633  }
2634 
2642  public function ucfirst( $str ) {
2643  $o = ord( $str );
2644  if ( $o < 96 ) { // if already uppercase...
2645  return $str;
2646  } elseif ( $o < 128 ) {
2647  return ucfirst( $str ); // use PHP's ucfirst()
2648  } else {
2649  // fall back to more complex logic in case of multibyte strings
2650  return $this->uc( $str, true );
2651  }
2652  }
2653 
2662  public function uc( $str, $first = false ) {
2663  if ( $first ) {
2664  if ( $this->isMultibyte( $str ) ) {
2665  return mb_strtoupper( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
2666  } else {
2667  return ucfirst( $str );
2668  }
2669  } else {
2670  return $this->isMultibyte( $str ) ? mb_strtoupper( $str ) : strtoupper( $str );
2671  }
2672  }
2673 
2678  function lcfirst( $str ) {
2679  $o = ord( $str );
2680  if ( !$o ) {
2681  return strval( $str );
2682  } elseif ( $o >= 128 ) {
2683  return $this->lc( $str, true );
2684  } elseif ( $o > 96 ) {
2685  return $str;
2686  } else {
2687  $str[0] = strtolower( $str[0] );
2688  return $str;
2689  }
2690  }
2691 
2697  function lc( $str, $first = false ) {
2698  if ( $first ) {
2699  if ( $this->isMultibyte( $str ) ) {
2700  return mb_strtolower( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
2701  } else {
2702  return strtolower( substr( $str, 0, 1 ) ) . substr( $str, 1 );
2703  }
2704  } else {
2705  return $this->isMultibyte( $str ) ? mb_strtolower( $str ) : strtolower( $str );
2706  }
2707  }
2708 
2713  function isMultibyte( $str ) {
2714  return strlen( $str ) !== mb_strlen( $str );
2715  }
2716 
2721  function ucwords( $str ) {
2722  if ( $this->isMultibyte( $str ) ) {
2723  $str = $this->lc( $str );
2724 
2725  // regexp to find first letter in each word (i.e. after each space)
2726  $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)| ([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
2727 
2728  // function to use to capitalize a single char
2729  return preg_replace_callback(
2730  $replaceRegexp,
2731  [ $this, 'ucwordsCallbackMB' ],
2732  $str
2733  );
2734  } else {
2735  return ucwords( strtolower( $str ) );
2736  }
2737  }
2738 
2745  function ucwordbreaks( $str ) {
2746  if ( $this->isMultibyte( $str ) ) {
2747  $str = $this->lc( $str );
2748 
2749  // since \b doesn't work for UTF-8, we explicitely define word break chars
2750  $breaks = "[ \-\(\)\}\{\.,\?!]";
2751 
2752  // find first letter after word break
2753  $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)|" .
2754  "$breaks([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
2755 
2756  return preg_replace_callback(
2757  $replaceRegexp,
2758  [ $this, 'ucwordbreaksCallbackMB' ],
2759  $str
2760  );
2761  } else {
2762  return preg_replace_callback(
2763  '/\b([\w\x80-\xff]+)\b/',
2764  [ $this, 'ucwordbreaksCallbackAscii' ],
2765  $str
2766  );
2767  }
2768  }
2769 
2785  function caseFold( $s ) {
2786  return $this->uc( $s );
2787  }
2788 
2794  function checkTitleEncoding( $s ) {
2795  if ( is_array( $s ) ) {
2796  throw new MWException( 'Given array to checkTitleEncoding.' );
2797  }
2798  if ( StringUtils::isUtf8( $s ) ) {
2799  return $s;
2800  }
2801 
2802  return $this->iconv( $this->fallback8bitEncoding(), 'utf-8', $s );
2803  }
2804 
2809  return self::$dataCache->getItem( $this->mCode, 'fallback8bitEncoding' );
2810  }
2811 
2820  function hasWordBreaks() {
2821  return true;
2822  }
2823 
2831  function segmentByWord( $string ) {
2832  return $string;
2833  }
2834 
2842  function normalizeForSearch( $string ) {
2843  return self::convertDoubleWidth( $string );
2844  }
2845 
2854  protected static function convertDoubleWidth( $string ) {
2855  static $full = null;
2856  static $half = null;
2857 
2858  if ( $full === null ) {
2859  $fullWidth = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
2860  $halfWidth = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
2861  $full = str_split( $fullWidth, 3 );
2862  $half = str_split( $halfWidth );
2863  }
2864 
2865  $string = str_replace( $full, $half, $string );
2866  return $string;
2867  }
2868 
2874  protected static function insertSpace( $string, $pattern ) {
2875  $string = preg_replace( $pattern, " $1 ", $string );
2876  $string = preg_replace( '/ +/', ' ', $string );
2877  return $string;
2878  }
2879 
2884  function convertForSearchResult( $termsArray ) {
2885  # some languages, e.g. Chinese, need to do a conversion
2886  # in order for search results to be displayed correctly
2887  return $termsArray;
2888  }
2889 
2896  function firstChar( $s ) {
2897  $matches = [];
2898  preg_match(
2899  '/^([\x00-\x7f]|[\xc0-\xdf][\x80-\xbf]|' .
2900  '[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xf7][\x80-\xbf]{3})/',
2901  $s,
2902  $matches
2903  );
2904 
2905  if ( isset( $matches[1] ) ) {
2906  if ( strlen( $matches[1] ) != 3 ) {
2907  return $matches[1];
2908  }
2909 
2910  // Break down Hangul syllables to grab the first jamo
2912  if ( $code < 0xac00 || 0xd7a4 <= $code ) {
2913  return $matches[1];
2914  } elseif ( $code < 0xb098 ) {
2915  return "\xe3\x84\xb1";
2916  } elseif ( $code < 0xb2e4 ) {
2917  return "\xe3\x84\xb4";
2918  } elseif ( $code < 0xb77c ) {
2919  return "\xe3\x84\xb7";
2920  } elseif ( $code < 0xb9c8 ) {
2921  return "\xe3\x84\xb9";
2922  } elseif ( $code < 0xbc14 ) {
2923  return "\xe3\x85\x81";
2924  } elseif ( $code < 0xc0ac ) {
2925  return "\xe3\x85\x82";
2926  } elseif ( $code < 0xc544 ) {
2927  return "\xe3\x85\x85";
2928  } elseif ( $code < 0xc790 ) {
2929  return "\xe3\x85\x87";
2930  } elseif ( $code < 0xcc28 ) {
2931  return "\xe3\x85\x88";
2932  } elseif ( $code < 0xce74 ) {
2933  return "\xe3\x85\x8a";
2934  } elseif ( $code < 0xd0c0 ) {
2935  return "\xe3\x85\x8b";
2936  } elseif ( $code < 0xd30c ) {
2937  return "\xe3\x85\x8c";
2938  } elseif ( $code < 0xd558 ) {
2939  return "\xe3\x85\x8d";
2940  } else {
2941  return "\xe3\x85\x8e";
2942  }
2943  } else {
2944  return '';
2945  }
2946  }
2947 
2951  function initEncoding() {
2952  // No-op.
2953  }
2954 
2960  function recodeForEdit( $s ) {
2961  return $s;
2962  }
2963 
2969  function recodeInput( $s ) {
2970  return $s;
2971  }
2972 
2984  function normalize( $s ) {
2986  $s = UtfNormal\Validator::cleanUp( $s );
2987  if ( $wgAllUnicodeFixes ) {
2988  $s = $this->transformUsingPairFile( 'normalize-ar.ser', $s );
2989  $s = $this->transformUsingPairFile( 'normalize-ml.ser', $s );
2990  }
2991 
2992  return $s;
2993  }
2994 
3009  function transformUsingPairFile( $file, $string ) {
3010  if ( !isset( $this->transformData[$file] ) ) {
3011  $data = wfGetPrecompiledData( $file );
3012  if ( $data === false ) {
3013  throw new MWException( __METHOD__ . ": The transformation file $file is missing" );
3014  }
3015  $this->transformData[$file] = new ReplacementArray( $data );
3016  }
3017  return $this->transformData[$file]->replace( $string );
3018  }
3019 
3025  function isRTL() {
3026  return self::$dataCache->getItem( $this->mCode, 'rtl' );
3027  }
3028 
3033  function getDir() {
3034  return $this->isRTL() ? 'rtl' : 'ltr';
3035  }
3036 
3045  function alignStart() {
3046  return $this->isRTL() ? 'right' : 'left';
3047  }
3048 
3057  function alignEnd() {
3058  return $this->isRTL() ? 'left' : 'right';
3059  }
3060 
3072  function getDirMarkEntity( $opposite = false ) {
3073  if ( $opposite ) {
3074  return $this->isRTL() ? '&lrm;' : '&rlm;';
3075  }
3076  return $this->isRTL() ? '&rlm;' : '&lrm;';
3077  }
3078 
3089  function getDirMark( $opposite = false ) {
3090  $lrm = "\xE2\x80\x8E"; # LEFT-TO-RIGHT MARK, commonly abbreviated LRM
3091  $rlm = "\xE2\x80\x8F"; # RIGHT-TO-LEFT MARK, commonly abbreviated RLM
3092  if ( $opposite ) {
3093  return $this->isRTL() ? $lrm : $rlm;
3094  }
3095  return $this->isRTL() ? $rlm : $lrm;
3096  }
3097 
3101  function capitalizeAllNouns() {
3102  return self::$dataCache->getItem( $this->mCode, 'capitalizeAllNouns' );
3103  }
3104 
3112  function getArrow( $direction = 'forwards' ) {
3113  switch ( $direction ) {
3114  case 'forwards':
3115  return $this->isRTL() ? '←' : '→';
3116  case 'backwards':
3117  return $this->isRTL() ? '→' : '←';
3118  case 'left':
3119  return '←';
3120  case 'right':
3121  return '→';
3122  case 'up':
3123  return '↑';
3124  case 'down':
3125  return '↓';
3126  }
3127  }
3128 
3134  function linkPrefixExtension() {
3135  return self::$dataCache->getItem( $this->mCode, 'linkPrefixExtension' );
3136  }
3137 
3142  function getMagicWords() {
3143  return self::$dataCache->getItem( $this->mCode, 'magicWords' );
3144  }
3145 
3149  protected function doMagicHook() {
3150  if ( $this->mMagicHookDone ) {
3151  return;
3152  }
3153  $this->mMagicHookDone = true;
3154  Hooks::run( 'LanguageGetMagic', [ &$this->mMagicExtensions, $this->getCode() ] );
3155  }
3156 
3162  function getMagic( $mw ) {
3163  // Saves a function call
3164  if ( !$this->mMagicHookDone ) {
3165  $this->doMagicHook();
3166  }
3167 
3168  if ( isset( $this->mMagicExtensions[$mw->mId] ) ) {
3169  $rawEntry = $this->mMagicExtensions[$mw->mId];
3170  } else {
3171  $rawEntry = self::$dataCache->getSubitem(
3172  $this->mCode, 'magicWords', $mw->mId );
3173  }
3174 
3175  if ( !is_array( $rawEntry ) ) {
3176  wfWarn( "\"$rawEntry\" is not a valid magic word for \"$mw->mId\"" );
3177  } else {
3178  $mw->mCaseSensitive = $rawEntry[0];
3179  $mw->mSynonyms = array_slice( $rawEntry, 1 );
3180  }
3181  }
3182 
3188  function addMagicWordsByLang( $newWords ) {
3189  $fallbackChain = $this->getFallbackLanguages();
3190  $fallbackChain = array_reverse( $fallbackChain );
3191  foreach ( $fallbackChain as $code ) {
3192  if ( isset( $newWords[$code] ) ) {
3193  $this->mMagicExtensions = $newWords[$code] + $this->mMagicExtensions;
3194  }
3195  }
3196  }
3197 
3204  // Cache aliases because it may be slow to load them
3205  if ( is_null( $this->mExtendedSpecialPageAliases ) ) {
3206  // Initialise array
3207  $this->mExtendedSpecialPageAliases =
3208  self::$dataCache->getItem( $this->mCode, 'specialPageAliases' );
3209  Hooks::run( 'LanguageGetSpecialPageAliases',
3210  [ &$this->mExtendedSpecialPageAliases, $this->getCode() ] );
3211  }
3212 
3214  }
3215 
3222  function emphasize( $text ) {
3223  return "<em>$text</em>";
3224  }
3225 
3248  public function formatNum( $number, $nocommafy = false ) {
3249  global $wgTranslateNumerals;
3250  if ( !$nocommafy ) {
3251  $number = $this->commafy( $number );
3252  $s = $this->separatorTransformTable();
3253  if ( $s ) {
3254  $number = strtr( $number, $s );
3255  }
3256  }
3257 
3258  if ( $wgTranslateNumerals ) {
3259  $s = $this->digitTransformTable();
3260  if ( $s ) {
3261  $number = strtr( $number, $s );
3262  }
3263  }
3264 
3265  return $number;
3266  }
3267 
3276  public function formatNumNoSeparators( $number ) {
3277  return $this->formatNum( $number, true );
3278  }
3279 
3284  public function parseFormattedNumber( $number ) {
3285  $s = $this->digitTransformTable();
3286  if ( $s ) {
3287  // eliminate empty array values such as ''. (T66347)
3288  $s = array_filter( $s );
3289  $number = strtr( $number, array_flip( $s ) );
3290  }
3291 
3292  $s = $this->separatorTransformTable();
3293  if ( $s ) {
3294  // eliminate empty array values such as ''. (T66347)
3295  $s = array_filter( $s );
3296  $number = strtr( $number, array_flip( $s ) );
3297  }
3298 
3299  $number = strtr( $number, [ ',' => '' ] );
3300  return $number;
3301  }
3302 
3309  function commafy( $number ) {
3311  if ( $number === null ) {
3312  return '';
3313  }
3314 
3315  if ( !$digitGroupingPattern || $digitGroupingPattern === "###,###,###" ) {
3316  // default grouping is at thousands, use the same for ###,###,### pattern too.
3317  return strrev( (string)preg_replace( '/(\d{3})(?=\d)(?!\d*\.)/', '$1,', strrev( $number ) ) );
3318  } else {
3319  // Ref: http://cldr.unicode.org/translation/number-patterns
3320  $sign = "";
3321  if ( intval( $number ) < 0 ) {
3322  // For negative numbers apply the algorithm like positive number and add sign.
3323  $sign = "-";
3324  $number = substr( $number, 1 );
3325  }
3326  $integerPart = [];
3327  $decimalPart = [];
3328  $numMatches = preg_match_all( "/(#+)/", $digitGroupingPattern, $matches );
3329  preg_match( "/\d+/", $number, $integerPart );
3330  preg_match( "/\.\d*/", $number, $decimalPart );
3331  $groupedNumber = ( count( $decimalPart ) > 0 ) ? $decimalPart[0] : "";
3332  if ( $groupedNumber === $number ) {
3333  // the string does not have any number part. Eg: .12345
3334  return $sign . $groupedNumber;
3335  }
3336  $start = $end = ( $integerPart ) ? strlen( $integerPart[0] ) : 0;
3337  while ( $start > 0 ) {
3338  $match = $matches[0][$numMatches - 1];
3339  $matchLen = strlen( $match );
3340  $start = $end - $matchLen;
3341  if ( $start < 0 ) {
3342  $start = 0;
3343  }
3344  $groupedNumber = substr( $number, $start, $end -$start ) . $groupedNumber;
3345  $end = $start;
3346  if ( $numMatches > 1 ) {
3347  // use the last pattern for the rest of the number
3348  $numMatches--;
3349  }
3350  if ( $start > 0 ) {
3351  $groupedNumber = "," . $groupedNumber;
3352  }
3353  }
3354  return $sign . $groupedNumber;
3355  }
3356  }
3357 
3362  return self::$dataCache->getItem( $this->mCode, 'digitGroupingPattern' );
3363  }
3364 
3368  function digitTransformTable() {
3369  return self::$dataCache->getItem( $this->mCode, 'digitTransformTable' );
3370  }
3371 
3376  return self::$dataCache->getItem( $this->mCode, 'separatorTransformTable' );
3377  }
3378 
3388  function listToText( array $l ) {
3389  $m = count( $l ) - 1;
3390  if ( $m < 0 ) {
3391  return '';
3392  }
3393  if ( $m > 0 ) {
3394  $and = $this->msg( 'and' )->escaped();
3395  $space = $this->msg( 'word-separator' )->escaped();
3396  if ( $m > 1 ) {
3397  $comma = $this->msg( 'comma-separator' )->escaped();
3398  }
3399  }
3400  $s = $l[$m];
3401  for ( $i = $m - 1; $i >= 0; $i-- ) {
3402  if ( $i == $m - 1 ) {
3403  $s = $l[$i] . $and . $space . $s;
3404  } else {
3405  $s = $l[$i] . $comma . $s;
3406  }
3407  }
3408  return $s;
3409  }
3410 
3417  function commaList( array $list ) {
3418  return implode(
3419  wfMessage( 'comma-separator' )->inLanguage( $this )->escaped(),
3420  $list
3421  );
3422  }
3423 
3430  function semicolonList( array $list ) {
3431  return implode(
3432  wfMessage( 'semicolon-separator' )->inLanguage( $this )->escaped(),
3433  $list
3434  );
3435  }
3436 
3442  function pipeList( array $list ) {
3443  return implode(
3444  wfMessage( 'pipe-separator' )->inLanguage( $this )->escaped(),
3445  $list
3446  );
3447  }
3448 
3466  function truncate( $string, $length, $ellipsis = '...', $adjustLength = true ) {
3467  # Use the localized ellipsis character
3468  if ( $ellipsis == '...' ) {
3469  $ellipsis = wfMessage( 'ellipsis' )->inLanguage( $this )->escaped();
3470  }
3471  # Check if there is no need to truncate
3472  if ( $length == 0 ) {
3473  return $ellipsis; // convention
3474  } elseif ( strlen( $string ) <= abs( $length ) ) {
3475  return $string; // no need to truncate
3476  }
3477  $stringOriginal = $string;
3478  # If ellipsis length is >= $length then we can't apply $adjustLength
3479  if ( $adjustLength && strlen( $ellipsis ) >= abs( $length ) ) {
3480  $string = $ellipsis; // this can be slightly unexpected
3481  # Otherwise, truncate and add ellipsis...
3482  } else {
3483  $eLength = $adjustLength ? strlen( $ellipsis ) : 0;
3484  if ( $length > 0 ) {
3485  $length -= $eLength;
3486  $string = substr( $string, 0, $length ); // xyz...
3487  $string = $this->removeBadCharLast( $string );
3488  $string = rtrim( $string );
3489  $string = $string . $ellipsis;
3490  } else {
3491  $length += $eLength;
3492  $string = substr( $string, $length ); // ...xyz
3493  $string = $this->removeBadCharFirst( $string );
3494  $string = ltrim( $string );
3495  $string = $ellipsis . $string;
3496  }
3497  }
3498  # Do not truncate if the ellipsis makes the string longer/equal (T24181).
3499  # This check is *not* redundant if $adjustLength, due to the single case where
3500  # LEN($ellipsis) > ABS($limit arg); $stringOriginal could be shorter than $string.
3501  if ( strlen( $string ) < strlen( $stringOriginal ) ) {
3502  return $string;
3503  } else {
3504  return $stringOriginal;
3505  }
3506  }
3507 
3515  protected function removeBadCharLast( $string ) {
3516  if ( $string != '' ) {
3517  $char = ord( $string[strlen( $string ) - 1] );
3518  $m = [];
3519  if ( $char >= 0xc0 ) {
3520  # We got the first byte only of a multibyte char; remove it.
3521  $string = substr( $string, 0, -1 );
3522  } elseif ( $char >= 0x80 &&
3523  // Use the /s modifier (PCRE_DOTALL) so (.*) also matches newlines
3524  preg_match( '/^(.*)(?:[\xe0-\xef][\x80-\xbf]|' .
3525  '[\xf0-\xf7][\x80-\xbf]{1,2})$/s', $string, $m )
3526  ) {
3527  # We chopped in the middle of a character; remove it
3528  $string = $m[1];
3529  }
3530  }
3531  return $string;
3532  }
3533 
3541  protected function removeBadCharFirst( $string ) {
3542  if ( $string != '' ) {
3543  $char = ord( $string[0] );
3544  if ( $char >= 0x80 && $char < 0xc0 ) {
3545  # We chopped in the middle of a character; remove the whole thing
3546  $string = preg_replace( '/^[\x80-\xbf]+/', '', $string );
3547  }
3548  }
3549  return $string;
3550  }
3551 
3567  function truncateHtml( $text, $length, $ellipsis = '...' ) {
3568  # Use the localized ellipsis character
3569  if ( $ellipsis == '...' ) {
3570  $ellipsis = wfMessage( 'ellipsis' )->inLanguage( $this )->escaped();
3571  }
3572  # Check if there is clearly no need to truncate
3573  if ( $length <= 0 ) {
3574  return $ellipsis; // no text shown, nothing to format (convention)
3575  } elseif ( strlen( $text ) <= $length ) {
3576  return $text; // string short enough even *with* HTML (short-circuit)
3577  }
3578 
3579  $dispLen = 0; // innerHTML legth so far
3580  $testingEllipsis = false; // checking if ellipses will make string longer/equal?
3581  $tagType = 0; // 0-open, 1-close
3582  $bracketState = 0; // 1-tag start, 2-tag name, 0-neither
3583  $entityState = 0; // 0-not entity, 1-entity
3584  $tag = $ret = ''; // accumulated tag name, accumulated result string
3585  $openTags = []; // open tag stack
3586  $maybeState = null; // possible truncation state
3587 
3588  $textLen = strlen( $text );
3589  $neLength = max( 0, $length - strlen( $ellipsis ) ); // non-ellipsis len if truncated
3590  for ( $pos = 0; true; ++$pos ) {
3591  # Consider truncation once the display length has reached the maximim.
3592  # We check if $dispLen > 0 to grab tags for the $neLength = 0 case.
3593  # Check that we're not in the middle of a bracket/entity...
3594  if ( $dispLen && $dispLen >= $neLength && $bracketState == 0 && !$entityState ) {
3595  if ( !$testingEllipsis ) {
3596  $testingEllipsis = true;
3597  # Save where we are; we will truncate here unless there turn out to
3598  # be so few remaining characters that truncation is not necessary.
3599  if ( !$maybeState ) { // already saved? ($neLength = 0 case)
3600  $maybeState = [ $ret, $openTags ]; // save state
3601  }
3602  } elseif ( $dispLen > $length && $dispLen > strlen( $ellipsis ) ) {
3603  # String in fact does need truncation, the truncation point was OK.
3604  list( $ret, $openTags ) = $maybeState; // reload state
3605  $ret = $this->removeBadCharLast( $ret ); // multi-byte char fix
3606  $ret .= $ellipsis; // add ellipsis
3607  break;
3608  }
3609  }
3610  if ( $pos >= $textLen ) {
3611  break; // extra iteration just for above checks
3612  }
3613 
3614  # Read the next char...
3615  $ch = $text[$pos];
3616  $lastCh = $pos ? $text[$pos - 1] : '';
3617  $ret .= $ch; // add to result string
3618  if ( $ch == '<' ) {
3619  $this->truncate_endBracket( $tag, $tagType, $lastCh, $openTags ); // for bad HTML
3620  $entityState = 0; // for bad HTML
3621  $bracketState = 1; // tag started (checking for backslash)
3622  } elseif ( $ch == '>' ) {
3623  $this->truncate_endBracket( $tag, $tagType, $lastCh, $openTags );
3624  $entityState = 0; // for bad HTML
3625  $bracketState = 0; // out of brackets
3626  } elseif ( $bracketState == 1 ) {
3627  if ( $ch == '/' ) {
3628  $tagType = 1; // close tag (e.g. "</span>")
3629  } else {
3630  $tagType = 0; // open tag (e.g. "<span>")
3631  $tag .= $ch;
3632  }
3633  $bracketState = 2; // building tag name
3634  } elseif ( $bracketState == 2 ) {
3635  if ( $ch != ' ' ) {
3636  $tag .= $ch;
3637  } else {
3638  // Name found (e.g. "<a href=..."), add on tag attributes...
3639  $pos += $this->truncate_skip( $ret, $text, "<>", $pos + 1 );
3640  }
3641  } elseif ( $bracketState == 0 ) {
3642  if ( $entityState ) {
3643  if ( $ch == ';' ) {
3644  $entityState = 0;
3645  $dispLen++; // entity is one displayed char
3646  }
3647  } else {
3648  if ( $neLength == 0 && !$maybeState ) {
3649  // Save state without $ch. We want to *hit* the first
3650  // display char (to get tags) but not *use* it if truncating.
3651  $maybeState = [ substr( $ret, 0, -1 ), $openTags ];
3652  }
3653  if ( $ch == '&' ) {
3654  $entityState = 1; // entity found, (e.g. "&#160;")
3655  } else {
3656  $dispLen++; // this char is displayed
3657  // Add the next $max display text chars after this in one swoop...
3658  $max = ( $testingEllipsis ? $length : $neLength ) - $dispLen;
3659  $skipped = $this->truncate_skip( $ret, $text, "<>&", $pos + 1, $max );
3660  $dispLen += $skipped;
3661  $pos += $skipped;
3662  }
3663  }
3664  }
3665  }
3666  // Close the last tag if left unclosed by bad HTML
3667  $this->truncate_endBracket( $tag, $text[$textLen - 1], $tagType, $openTags );
3668  while ( count( $openTags ) > 0 ) {
3669  $ret .= '</' . array_pop( $openTags ) . '>'; // close open tags
3670  }
3671  return $ret;
3672  }
3673 
3685  private function truncate_skip( &$ret, $text, $search, $start, $len = null ) {
3686  if ( $len === null ) {
3687  $len = -1; // -1 means "no limit" for strcspn
3688  } elseif ( $len < 0 ) {
3689  $len = 0; // sanity
3690  }
3691  $skipCount = 0;
3692  if ( $start < strlen( $text ) ) {
3693  $skipCount = strcspn( $text, $search, $start, $len );
3694  $ret .= substr( $text, $start, $skipCount );
3695  }
3696  return $skipCount;
3697  }
3698 
3708  private function truncate_endBracket( &$tag, $tagType, $lastCh, &$openTags ) {
3709  $tag = ltrim( $tag );
3710  if ( $tag != '' ) {
3711  if ( $tagType == 0 && $lastCh != '/' ) {
3712  $openTags[] = $tag; // tag opened (didn't close itself)
3713  } elseif ( $tagType == 1 ) {
3714  if ( $openTags && $tag == $openTags[count( $openTags ) - 1] ) {
3715  array_pop( $openTags ); // tag closed
3716  }
3717  }
3718  $tag = '';
3719  }
3720  }
3721 
3730  function convertGrammar( $word, $case ) {
3732  if ( isset( $wgGrammarForms[$this->getCode()][$case][$word] ) ) {
3733  return $wgGrammarForms[$this->getCode()][$case][$word];
3734  }
3735 
3737 
3738  if ( isset( $grammarTransformations[$case] ) ) {
3739  $forms = $grammarTransformations[$case];
3740 
3741  // Some names of grammar rules are aliases for other rules.
3742  // In such cases the value is a string rather than object,
3743  // so load the actual rules.
3744  if ( is_string( $forms ) ) {
3745  $forms = $grammarTransformations[$forms];
3746  }
3747 
3748  foreach ( array_values( $forms ) as $rule ) {
3749  $form = $rule[0];
3750 
3751  if ( $form === '@metadata' ) {
3752  continue;
3753  }
3754 
3755  $replacement = $rule[1];
3756 
3757  $regex = '/' . addcslashes( $form, '/' ) . '/u';
3758  $patternMatches = preg_match( $regex, $word );
3759 
3760  if ( $patternMatches === false ) {
3761  wfLogWarning(
3762  'An error occurred while processing grammar. ' .
3763  "Word: '$word'. Regex: /$form/."
3764  );
3765  } elseif ( $patternMatches === 1 ) {
3766  $word = preg_replace( $regex, $replacement, $word );
3767 
3768  break;
3769  }
3770  }
3771  }
3772 
3773  return $word;
3774  }
3775 
3781  function getGrammarForms() {
3783  if ( isset( $wgGrammarForms[$this->getCode()] )
3784  && is_array( $wgGrammarForms[$this->getCode()] )
3785  ) {
3786  return $wgGrammarForms[$this->getCode()];
3787  }
3788 
3789  return [];
3790  }
3791 
3801  public function getGrammarTransformations() {
3802  $languageCode = $this->getCode();
3803 
3804  if ( self::$grammarTransformations === null ) {
3805  self::$grammarTransformations = new MapCacheLRU( 10 );
3806  }
3807 
3808  if ( self::$grammarTransformations->has( $languageCode ) ) {
3809  return self::$grammarTransformations->get( $languageCode );
3810  }
3811 
3812  $data = [];
3813 
3814  $grammarDataFile = __DIR__ . "/data/grammarTransformations/$languageCode.json";
3815  if ( is_readable( $grammarDataFile ) ) {
3816  $data = FormatJson::decode(
3817  file_get_contents( $grammarDataFile ),
3818  true
3819  );
3820 
3821  if ( $data === null ) {
3822  throw new MWException( "Invalid grammar data for \"$languageCode\"." );
3823  }
3824 
3825  self::$grammarTransformations->set( $languageCode, $data );
3826  }
3827 
3828  return $data;
3829  }
3830 
3850  function gender( $gender, $forms ) {
3851  if ( !count( $forms ) ) {
3852  return '';
3853  }
3854  $forms = $this->preConvertPlural( $forms, 2 );
3855  if ( $gender === 'male' ) {
3856  return $forms[0];
3857  }
3858  if ( $gender === 'female' ) {
3859  return $forms[1];
3860  }
3861  return isset( $forms[2] ) ? $forms[2] : $forms[0];
3862  }
3863 
3879  function convertPlural( $count, $forms ) {
3880  // Handle explicit n=pluralform cases
3881  $forms = $this->handleExplicitPluralForms( $count, $forms );
3882  if ( is_string( $forms ) ) {
3883  return $forms;
3884  }
3885  if ( !count( $forms ) ) {
3886  return '';
3887  }
3888 
3889  $pluralForm = $this->getPluralRuleIndexNumber( $count );
3890  $pluralForm = min( $pluralForm, count( $forms ) - 1 );
3891  return $forms[$pluralForm];
3892  }
3893 
3909  protected function handleExplicitPluralForms( $count, array $forms ) {
3910  foreach ( $forms as $index => $form ) {
3911  if ( preg_match( '/\d+=/i', $form ) ) {
3912  $pos = strpos( $form, '=' );
3913  if ( substr( $form, 0, $pos ) === (string)$count ) {
3914  return substr( $form, $pos + 1 );
3915  }
3916  unset( $forms[$index] );
3917  }
3918  }
3919  return array_values( $forms );
3920  }
3921 
3930  protected function preConvertPlural( /* Array */ $forms, $count ) {
3931  while ( count( $forms ) < $count ) {
3932  $forms[] = $forms[count( $forms ) - 1];
3933  }
3934  return $forms;
3935  }
3936 
3953  public function embedBidi( $text = '' ) {
3955  if ( $dir === 'ltr' ) {
3956  // Wrap in LEFT-TO-RIGHT EMBEDDING ... POP DIRECTIONAL FORMATTING
3957  return self::$lre . $text . self::$pdf;
3958  }
3959  if ( $dir === 'rtl' ) {
3960  // Wrap in RIGHT-TO-LEFT EMBEDDING ... POP DIRECTIONAL FORMATTING
3961  return self::$rle . $text . self::$pdf;
3962  }
3963  // No strong directionality: do not wrap
3964  return $text;
3965  }
3966 
3980  function translateBlockExpiry( $str, User $user = null, $now = 0 ) {
3981  $duration = SpecialBlock::getSuggestedDurations( $this );
3982  foreach ( $duration as $show => $value ) {
3983  if ( strcmp( $str, $value ) == 0 ) {
3984  return htmlspecialchars( trim( $show ) );
3985  }
3986  }
3987 
3988  if ( wfIsInfinity( $str ) ) {
3989  foreach ( $duration as $show => $value ) {
3990  if ( wfIsInfinity( $value ) ) {
3991  return htmlspecialchars( trim( $show ) );
3992  }
3993  }
3994  }
3995 
3996  // If all else fails, return a standard duration or timestamp description.
3997  $time = strtotime( $str, $now );
3998  if ( $time === false ) { // Unknown format. Return it as-is in case.
3999  return $str;
4000  } elseif ( $time !== strtotime( $str, $now + 1 ) ) { // It's a relative timestamp.
4001  // The result differs based on current time, so the difference
4002  // is a fixed duration length.
4003  return $this->formatDuration( $time - $now );
4004  } else { // It's an absolute timestamp.
4005  if ( $time === 0 ) {
4006  // wfTimestamp() handles 0 as current time instead of epoch.
4007  $time = '19700101000000';
4008  }
4009  if ( $user ) {
4010  return $this->userTimeAndDate( $time, $user );
4011  }
4012  return $this->timeanddate( $time );
4013  }
4014  }
4015 
4023  public function segmentForDiff( $text ) {
4024  return $text;
4025  }
4026 
4033  public function unsegmentForDiff( $text ) {
4034  return $text;
4035  }
4036 
4043  public function getConverter() {
4044  return $this->mConverter;
4045  }
4046 
4053  public function autoConvertToAllVariants( $text ) {
4054  return $this->mConverter->autoConvertToAllVariants( $text );
4055  }
4056 
4063  public function convert( $text ) {
4064  return $this->mConverter->convert( $text );
4065  }
4066 
4073  public function convertTitle( $title ) {
4074  return $this->mConverter->convertTitle( $title );
4075  }
4076 
4083  public function convertNamespace( $ns ) {
4084  return $this->mConverter->convertNamespace( $ns );
4085  }
4086 
4092  public function hasVariants() {
4093  return count( $this->getVariants() ) > 1;
4094  }
4095 
4103  public function hasVariant( $variant ) {
4104  return (bool)$this->mConverter->validateVariant( $variant );
4105  }
4106 
4114  public function convertHtml( $text, $isTitle = false ) {
4115  return htmlspecialchars( $this->convert( $text, $isTitle ) );
4116  }
4117 
4122  public function convertCategoryKey( $key ) {
4123  return $this->mConverter->convertCategoryKey( $key );
4124  }
4125 
4132  public function getVariants() {
4133  return $this->mConverter->getVariants();
4134  }
4135 
4139  public function getPreferredVariant() {
4140  return $this->mConverter->getPreferredVariant();
4141  }
4142 
4146  public function getDefaultVariant() {
4147  return $this->mConverter->getDefaultVariant();
4148  }
4149 
4153  public function getURLVariant() {
4154  return $this->mConverter->getURLVariant();
4155  }
4156 
4169  public function findVariantLink( &$link, &$nt, $ignoreOtherCond = false ) {
4170  $this->mConverter->findVariantLink( $link, $nt, $ignoreOtherCond );
4171  }
4172 
4179  function getExtraHashOptions() {
4180  return $this->mConverter->getExtraHashOptions();
4181  }
4182 
4190  public function getParsedTitle() {
4191  return $this->mConverter->getParsedTitle();
4192  }
4193 
4200  public function updateConversionTable( Title $title ) {
4201  $this->mConverter->updateConversionTable( $title );
4202  }
4203 
4216  public function markNoConversion( $text, $noParse = false ) {
4217  // Excluding protocal-relative URLs may avoid many false positives.
4218  if ( $noParse || preg_match( '/^(?:' . wfUrlProtocolsWithoutProtRel() . ')/', $text ) ) {
4219  return $this->mConverter->markNoConversion( $text );
4220  } else {
4221  return $text;
4222  }
4223  }
4224 
4231  public function linkTrail() {
4232  return self::$dataCache->getItem( $this->mCode, 'linkTrail' );
4233  }
4234 
4241  public function linkPrefixCharset() {
4242  return self::$dataCache->getItem( $this->mCode, 'linkPrefixCharset' );
4243  }
4244 
4252  public function getParentLanguage() {
4253  if ( $this->mParentLanguage !== false ) {
4254  return $this->mParentLanguage;
4255  }
4256 
4257  $code = explode( '-', $this->getCode() )[0];
4258  if ( !in_array( $code, LanguageConverter::$languagesWithVariants ) ) {
4259  $this->mParentLanguage = null;
4260  return null;
4261  }
4263  if ( !$lang->hasVariant( $this->getCode() ) ) {
4264  $this->mParentLanguage = null;
4265  return null;
4266  }
4267 
4268  $this->mParentLanguage = $lang;
4269  return $lang;
4270  }
4271 
4279  public function equals( Language $lang ) {
4280  return $lang->getCode() === $this->mCode;
4281  }
4282 
4291  public function getCode() {
4292  return $this->mCode;
4293  }
4294 
4305  public function getHtmlCode() {
4306  if ( is_null( $this->mHtmlCode ) ) {
4307  $this->mHtmlCode = wfBCP47( $this->getCode() );
4308  }
4309  return $this->mHtmlCode;
4310  }
4311 
4315  public function setCode( $code ) {
4316  $this->mCode = $code;
4317  // Ensure we don't leave incorrect cached data lying around
4318  $this->mHtmlCode = null;
4319  $this->mParentLanguage = false;
4320  }
4321 
4329  public static function getCodeFromFileName( $filename, $prefix = 'Language', $suffix = '.php' ) {
4330  $m = null;
4331  preg_match( '/' . preg_quote( $prefix, '/' ) . '([A-Z][a-z_]+)' .
4332  preg_quote( $suffix, '/' ) . '/', $filename, $m );
4333  if ( !count( $m ) ) {
4334  return false;
4335  }
4336  return str_replace( '_', '-', strtolower( $m[1] ) );
4337  }
4338 
4343  public static function classFromCode( $code ) {
4344  if ( $code == 'en' ) {
4345  return 'Language';
4346  } else {
4347  return 'Language' . str_replace( '-', '_', ucfirst( $code ) );
4348  }
4349  }
4350 
4359  public static function getFileName( $prefix = 'Language', $code, $suffix = '.php' ) {
4360  if ( !self::isValidBuiltInCode( $code ) ) {
4361  throw new MWException( "Invalid language code \"$code\"" );
4362  }
4363 
4364  return $prefix . str_replace( '-', '_', ucfirst( $code ) ) . $suffix;
4365  }
4366 
4371  public static function getMessagesFileName( $code ) {
4372  global $IP;
4373  $file = self::getFileName( "$IP/languages/messages/Messages", $code, '.php' );
4374  Hooks::run( 'Language::getMessagesFileName', [ $code, &$file ] );
4375  return $file;
4376  }
4377 
4384  public static function getJsonMessagesFileName( $code ) {
4385  global $IP;
4386 
4387  if ( !self::isValidBuiltInCode( $code ) ) {
4388  throw new MWException( "Invalid language code \"$code\"" );
4389  }
4390 
4391  return "$IP/languages/i18n/$code.json";
4392  }
4393 
4401  public static function getFallbackFor( $code ) {
4402  $fallbacks = self::getFallbacksFor( $code );
4403  if ( $fallbacks ) {
4404  return $fallbacks[0];
4405  }
4406  return false;
4407  }
4408 
4416  public static function getFallbacksFor( $code ) {
4417  if ( $code === 'en' || !Language::isValidBuiltInCode( $code ) ) {
4418  return [];
4419  }
4420  // For unknown languages, fallbackSequence returns an empty array,
4421  // hardcode fallback to 'en' in that case.
4422  return self::getLocalisationCache()->getItem( $code, 'fallbackSequence' ) ?: [ 'en' ];
4423  }
4424 
4433  public static function getFallbacksIncludingSiteLanguage( $code ) {
4435 
4436  // Usually, we will only store a tiny number of fallback chains, so we
4437  // keep them in static memory.
4438  $cacheKey = "{$code}-{$wgLanguageCode}";
4439 
4440  if ( !array_key_exists( $cacheKey, self::$fallbackLanguageCache ) ) {
4441  $fallbacks = self::getFallbacksFor( $code );
4442 
4443  // Append the site's fallback chain, including the site language itself
4444  $siteFallbacks = self::getFallbacksFor( $wgLanguageCode );
4445  array_unshift( $siteFallbacks, $wgLanguageCode );
4446 
4447  // Eliminate any languages already included in the chain
4448  $siteFallbacks = array_diff( $siteFallbacks, $fallbacks );
4449 
4450  self::$fallbackLanguageCache[$cacheKey] = [ $fallbacks, $siteFallbacks ];
4451  }
4452  return self::$fallbackLanguageCache[$cacheKey];
4453  }
4454 
4464  public static function getMessagesFor( $code ) {
4465  return self::getLocalisationCache()->getItem( $code, 'messages' );
4466  }
4467 
4476  public static function getMessageFor( $key, $code ) {
4477  return self::getLocalisationCache()->getSubitem( $code, 'messages', $key );
4478  }
4479 
4488  public static function getMessageKeysFor( $code ) {
4489  return self::getLocalisationCache()->getSubitemList( $code, 'messages' );
4490  }
4491 
4496  function fixVariableInNamespace( $talk ) {
4497  if ( strpos( $talk, '$1' ) === false ) {
4498  return $talk;
4499  }
4500 
4501  global $wgMetaNamespace;
4502  $talk = str_replace( '$1', $wgMetaNamespace, $talk );
4503 
4504  # Allow grammar transformations
4505  # Allowing full message-style parsing would make simple requests
4506  # such as action=raw much more expensive than they need to be.
4507  # This will hopefully cover most cases.
4508  $talk = preg_replace_callback( '/{{grammar:(.*?)\|(.*?)}}/i',
4509  [ $this, 'replaceGrammarInNamespace' ], $talk );
4510  return str_replace( ' ', '_', $talk );
4511  }
4512 
4517  function replaceGrammarInNamespace( $m ) {
4518  return $this->convertGrammar( trim( $m[2] ), trim( $m[1] ) );
4519  }
4520 
4531  public function formatExpiry( $expiry, $format = true, $infinity = 'infinity' ) {
4532  static $dbInfinity;
4533  if ( $dbInfinity === null ) {
4534  $dbInfinity = wfGetDB( DB_SLAVE )->getInfinity();
4535  }
4536 
4537  if ( $expiry == '' || $expiry === 'infinity' || $expiry == $dbInfinity ) {
4538  return $format === true
4539  ? $this->getMessageFromDB( 'infiniteblock' )
4540  : $infinity;
4541  } else {
4542  return $format === true
4543  ? $this->timeanddate( $expiry, /* User preference timezone */ true )
4544  : wfTimestamp( $format, $expiry );
4545  }
4546  }
4547 
4561  function formatTimePeriod( $seconds, $format = [] ) {
4562  if ( !is_array( $format ) ) {
4563  $format = [ 'avoid' => $format ]; // For backwards compatibility
4564  }
4565  if ( !isset( $format['avoid'] ) ) {
4566  $format['avoid'] = false;
4567  }
4568  if ( !isset( $format['noabbrevs'] ) ) {
4569  $format['noabbrevs'] = false;
4570  }
4571  $secondsMsg = wfMessage(
4572  $format['noabbrevs'] ? 'seconds' : 'seconds-abbrev' )->inLanguage( $this );
4573  $minutesMsg = wfMessage(
4574  $format['noabbrevs'] ? 'minutes' : 'minutes-abbrev' )->inLanguage( $this );
4575  $hoursMsg = wfMessage(
4576  $format['noabbrevs'] ? 'hours' : 'hours-abbrev' )->inLanguage( $this );
4577  $daysMsg = wfMessage(
4578  $format['noabbrevs'] ? 'days' : 'days-abbrev' )->inLanguage( $this );
4579 
4580  if ( round( $seconds * 10 ) < 100 ) {
4581  $s = $this->formatNum( sprintf( "%.1f", round( $seconds * 10 ) / 10 ) );
4582  $s = $secondsMsg->params( $s )->text();
4583  } elseif ( round( $seconds ) < 60 ) {
4584  $s = $this->formatNum( round( $seconds ) );
4585  $s = $secondsMsg->params( $s )->text();
4586  } elseif ( round( $seconds ) < 3600 ) {
4587  $minutes = floor( $seconds / 60 );
4588  $secondsPart = round( fmod( $seconds, 60 ) );
4589  if ( $secondsPart == 60 ) {
4590  $secondsPart = 0;
4591  $minutes++;
4592  }
4593  $s = $minutesMsg->params( $this->formatNum( $minutes ) )->text();
4594  $s .= ' ';
4595  $s .= $secondsMsg->params( $this->formatNum( $secondsPart ) )->text();
4596  } elseif ( round( $seconds ) <= 2 * 86400 ) {
4597  $hours = floor( $seconds / 3600 );
4598  $minutes = floor( ( $seconds - $hours * 3600 ) / 60 );
4599  $secondsPart = round( $seconds - $hours * 3600 - $minutes * 60 );
4600  if ( $secondsPart == 60 ) {
4601  $secondsPart = 0;
4602  $minutes++;
4603  }
4604  if ( $minutes == 60 ) {
4605  $minutes = 0;
4606  $hours++;
4607  }
4608  $s = $hoursMsg->params( $this->formatNum( $hours ) )->text();
4609  $s .= ' ';
4610  $s .= $minutesMsg->params( $this->formatNum( $minutes ) )->text();
4611  if ( !in_array( $format['avoid'], [ 'avoidseconds', 'avoidminutes' ] ) ) {
4612  $s .= ' ' . $secondsMsg->params( $this->formatNum( $secondsPart ) )->text();
4613  }
4614  } else {
4615  $days = floor( $seconds / 86400 );
4616  if ( $format['avoid'] === 'avoidminutes' ) {
4617  $hours = round( ( $seconds - $days * 86400 ) / 3600 );
4618  if ( $hours == 24 ) {
4619  $hours = 0;
4620  $days++;
4621  }
4622  $s = $daysMsg->params( $this->formatNum( $days ) )->text();
4623  $s .= ' ';
4624  $s .= $hoursMsg->params( $this->formatNum( $hours ) )->text();
4625  } elseif ( $format['avoid'] === 'avoidseconds' ) {
4626  $hours = floor( ( $seconds - $days * 86400 ) / 3600 );
4627  $minutes = round( ( $seconds - $days * 86400 - $hours * 3600 ) / 60 );
4628  if ( $minutes == 60 ) {
4629  $minutes = 0;
4630  $hours++;
4631  }
4632  if ( $hours == 24 ) {
4633  $hours = 0;
4634  $days++;
4635  }
4636  $s = $daysMsg->params( $this->formatNum( $days ) )->text();
4637  $s .= ' ';
4638  $s .= $hoursMsg->params( $this->formatNum( $hours ) )->text();
4639  $s .= ' ';
4640  $s .= $minutesMsg->params( $this->formatNum( $minutes ) )->text();
4641  } else {
4642  $s = $daysMsg->params( $this->formatNum( $days ) )->text();
4643  $s .= ' ';
4644  $s .= $this->formatTimePeriod( $seconds - $days * 86400, $format );
4645  }
4646  }
4647  return $s;
4648  }
4649 
4661  function formatBitrate( $bps ) {
4662  return $this->formatComputingNumbers( $bps, 1000, "bitrate-$1bits" );
4663  }
4664 
4671  function formatComputingNumbers( $size, $boundary, $messageKey ) {
4672  if ( $size <= 0 ) {
4673  return str_replace( '$1', $this->formatNum( $size ),
4674  $this->getMessageFromDB( str_replace( '$1', '', $messageKey ) )
4675  );
4676  }
4677  $sizes = [ '', 'kilo', 'mega', 'giga', 'tera', 'peta', 'exa', 'zeta', 'yotta' ];
4678  $index = 0;
4679 
4680  $maxIndex = count( $sizes ) - 1;
4681  while ( $size >= $boundary && $index < $maxIndex ) {
4682  $index++;
4683  $size /= $boundary;
4684  }
4685 
4686  // For small sizes no decimal places necessary
4687  $round = 0;
4688  if ( $index > 1 ) {
4689  // For MB and bigger two decimal places are smarter
4690  $round = 2;
4691  }
4692  $msg = str_replace( '$1', $sizes[$index], $messageKey );
4693 
4694  $size = round( $size, $round );
4695  $text = $this->getMessageFromDB( $msg );
4696  return str_replace( '$1', $this->formatNum( $size ), $text );
4697  }
4698 
4709  function formatSize( $size ) {
4710  return $this->formatComputingNumbers( $size, 1024, "size-$1bytes" );
4711  }
4712 
4722  function specialList( $page, $details, $oppositedm = true ) {
4723  if ( !$details ) {
4724  return $page;
4725  }
4726 
4727  $dirmark = ( $oppositedm ? $this->getDirMark( true ) : '' ) . $this->getDirMark();
4728  return
4729  $page .
4730  $dirmark .
4731  $this->msg( 'word-separator' )->escaped() .
4732  $this->msg( 'parentheses' )->rawParams( $details )->escaped();
4733  }
4734 
4745  public function viewPrevNext( Title $title, $offset, $limit,
4746  array $query = [], $atend = false
4747  ) {
4748  // @todo FIXME: Why on earth this needs one message for the text and another one for tooltip?
4749 
4750  # Make 'previous' link
4751  $prev = wfMessage( 'prevn' )->inLanguage( $this )->title( $title )->numParams( $limit )->text();
4752  if ( $offset > 0 ) {
4753  $plink = $this->numLink( $title, max( $offset - $limit, 0 ), $limit,
4754  $query, $prev, 'prevn-title', 'mw-prevlink' );
4755  } else {
4756  $plink = htmlspecialchars( $prev );
4757  }
4758 
4759  # Make 'next' link
4760  $next = wfMessage( 'nextn' )->inLanguage( $this )->title( $title )->numParams( $limit )->text();
4761  if ( $atend ) {
4762  $nlink = htmlspecialchars( $next );
4763  } else {
4764  $nlink = $this->numLink( $title, $offset + $limit, $limit,
4765  $query, $next, 'nextn-title', 'mw-nextlink' );
4766  }
4767 
4768  # Make links to set number of items per page
4769  $numLinks = [];
4770  foreach ( [ 20, 50, 100, 250, 500 ] as $num ) {
4771  $numLinks[] = $this->numLink( $title, $offset, $num,
4772  $query, $this->formatNum( $num ), 'shown-title', 'mw-numlink' );
4773  }
4774 
4775  return wfMessage( 'viewprevnext' )->inLanguage( $this )->title( $title
4776  )->rawParams( $plink, $nlink, $this->pipeList( $numLinks ) )->escaped();
4777  }
4778 
4791  private function numLink( Title $title, $offset, $limit, array $query, $link,
4792  $tooltipMsg, $class
4793  ) {
4794  $query = [ 'limit' => $limit, 'offset' => $offset ] + $query;
4795  $tooltip = wfMessage( $tooltipMsg )->inLanguage( $this )->title( $title )
4796  ->numParams( $limit )->text();
4797 
4798  return Html::element( 'a', [ 'href' => $title->getLocalURL( $query ),
4799  'title' => $tooltip, 'class' => $class ], $link );
4800  }
4801 
4807  public function getConvRuleTitle() {
4808  return $this->mConverter->getConvRuleTitle();
4809  }
4810 
4816  public function getCompiledPluralRules() {
4817  $pluralRules = self::$dataCache->getItem( strtolower( $this->mCode ), 'compiledPluralRules' );
4818  $fallbacks = Language::getFallbacksFor( $this->mCode );
4819  if ( !$pluralRules ) {
4820  foreach ( $fallbacks as $fallbackCode ) {
4821  $pluralRules = self::$dataCache->getItem( strtolower( $fallbackCode ), 'compiledPluralRules' );
4822  if ( $pluralRules ) {
4823  break;
4824  }
4825  }
4826  }
4827  return $pluralRules;
4828  }
4829 
4835  public function getPluralRules() {
4836  $pluralRules = self::$dataCache->getItem( strtolower( $this->mCode ), 'pluralRules' );
4837  $fallbacks = Language::getFallbacksFor( $this->mCode );
4838  if ( !$pluralRules ) {
4839  foreach ( $fallbacks as $fallbackCode ) {
4840  $pluralRules = self::$dataCache->getItem( strtolower( $fallbackCode ), 'pluralRules' );
4841  if ( $pluralRules ) {
4842  break;
4843  }
4844  }
4845  }
4846  return $pluralRules;
4847  }
4848 
4854  public function getPluralRuleTypes() {
4855  $pluralRuleTypes = self::$dataCache->getItem( strtolower( $this->mCode ), 'pluralRuleTypes' );
4856  $fallbacks = Language::getFallbacksFor( $this->mCode );
4857  if ( !$pluralRuleTypes ) {
4858  foreach ( $fallbacks as $fallbackCode ) {
4859  $pluralRuleTypes = self::$dataCache->getItem( strtolower( $fallbackCode ), 'pluralRuleTypes' );
4860  if ( $pluralRuleTypes ) {
4861  break;
4862  }
4863  }
4864  }
4865  return $pluralRuleTypes;
4866  }
4867 
4873  public function getPluralRuleIndexNumber( $number ) {
4874  $pluralRules = $this->getCompiledPluralRules();
4875  $form = Evaluator::evaluateCompiled( $number, $pluralRules );
4876  return $form;
4877  }
4878 
4887  public function getPluralRuleType( $number ) {
4888  $index = $this->getPluralRuleIndexNumber( $number );
4889  $pluralRuleTypes = $this->getPluralRuleTypes();
4890  if ( isset( $pluralRuleTypes[$index] ) ) {
4891  return $pluralRuleTypes[$index];
4892  } else {
4893  return 'other';
4894  }
4895  }
4896 }
User\getDefaultOption
static getDefaultOption( $opt)
Get a given default option value.
Definition: User.php:1603
Language\$mCode
$mCode
Definition: Language.php:41
Language\internalUserTimeAndDate
internalUserTimeAndDate( $type, $ts, User $user, array $options)
Internal helper function for userDate(), userTime() and userTimeAndDate()
Definition: Language.php:2371
Language\getFallbacksFor
static getFallbacksFor( $code)
Get the ordered list of fallback languages.
Definition: Language.php:4416
Language\getCodeFromFileName
static getCodeFromFileName( $filename, $prefix='Language', $suffix='.php')
Get the language code from a file name.
Definition: Language.php:4329
Language\classFromCode
static classFromCode( $code)
Definition: Language.php:4343
Language\getExtraHashOptions
getExtraHashOptions()
returns language specific options used by User::getPageRenderHash() for example, the preferred langua...
Definition: Language.php:4179
Language\lc
lc( $str, $first=false)
Definition: Language.php:2697
MWTimestamp
Library for creating and parsing MW-style timestamps.
Definition: MWTimestamp.php:32
Language\parseFormattedNumber
parseFormattedNumber( $number)
Definition: Language.php:3284
Language\getURLVariant
getURLVariant()
Definition: Language.php:4153
Language\getVariantname
getVariantname( $code, $usemsg=true)
short names for language variants used for language conversion links.
Definition: Language.php:713
Language\replaceGrammarInNamespace
replaceGrammarInNamespace( $m)
Definition: Language.php:4517
$wgUser
$wgUser
Definition: Setup.php:781
Language\linkPrefixExtension
linkPrefixExtension()
To allow "foo[[bar]]" to extend the link over the whole word "foobar".
Definition: Language.php:3134
wfBCP47
wfBCP47( $code)
Get the normalised IETF language tag See unit test for examples.
Definition: GlobalFunctions.php:3370
StringUtils\isUtf8
static isUtf8( $value)
Test whether a string is valid UTF-8.
Definition: StringUtils.php:41
Language\sprintfDate
sprintfDate( $format, $ts, DateTimeZone $zone=null, &$ttl='unused')
This is a workalike of PHP's date() function, but with better internationalisation,...
Definition: Language.php:1097
Language\truncate_endBracket
truncate_endBracket(&$tag, $tagType, $lastCh, &$openTags)
truncateHtml() helper function (a) push or pop $tag from $openTags as needed (b) clear $tag value
Definition: Language.php:3708
Language\commaList
commaList(array $list)
Take a list of strings and build a locale-friendly comma-separated list, using the local comma-separa...
Definition: Language.php:3417
Language\semicolonList
semicolonList(array $list)
Take a list of strings and build a locale-friendly semicolon-separated list, using the local semicolo...
Definition: Language.php:3430
Language\$IRANIAN_DAYS
static $IRANIAN_DAYS
Definition: Language.php:1567
Language\$mIranianCalendarMonthMsgs
static $mIranianCalendarMonthMsgs
Definition: Language.php:88
Language\getIranianCalendarMonthName
getIranianCalendarMonthName( $key)
Definition: Language.php:983
Language\$mMonthAbbrevMsgs
static $mMonthAbbrevMsgs
Definition: Language.php:83
Language\formatSize
formatSize( $size)
Format a size in bytes for output, using an appropriate unit (B, KB, MB, GB, TB, PB,...
Definition: Language.php:4709
Language\separatorTransformTable
separatorTransformTable()
Definition: Language.php:3375
HashBagOStuff
Simple store for keeping values in an associative array for the current process.
Definition: HashBagOStuff.php:31
Language\resetNamespaces
resetNamespaces()
Resets all of the namespace caches.
Definition: Language.php:512
Language\getConverter
getConverter()
Return the LanguageConverter used in the Language.
Definition: Language.php:4043
Language\hasVariants
hasVariants()
Check if this is a language with variants.
Definition: Language.php:4092
Language\$mWeekdayAbbrevMsgs
static $mWeekdayAbbrevMsgs
Definition: Language.php:69
Language\preConvertPlural
preConvertPlural($forms, $count)
Checks that convertPlural was given an array and pads it to requested amount of forms by copying the ...
Definition: Language.php:3930
$lang
if(!isset( $args[0])) $lang
Definition: testCompression.php:33
Language\segmentByWord
segmentByWord( $string)
Some languages such as Chinese require word segmentation, Specify such segmentation when overridden i...
Definition: Language.php:2831
Language\iconv
iconv( $in, $out, $string)
Definition: Language.php:2598
Language\getMessageFromDB
getMessageFromDB( $msg)
Get a message from the MediaWiki namespace.
Definition: Language.php:903
captcha-old.count
count
Definition: captcha-old.py:225
Language\$GREG_DAYS
static $GREG_DAYS
Definition: Language.php:1566
Language\convert
convert( $text)
convert text to different variants of a language.
Definition: Language.php:4063
Language\truncate
truncate( $string, $length, $ellipsis='...', $adjustLength=true)
Truncate a string to a specified length in bytes, appending an optional string (e....
Definition: Language.php:3466
Language\updateConversionTable
updateConversionTable(Title $title)
Refresh the cache of conversion tables when MediaWiki:Conversiontable* is updated.
Definition: Language.php:4200
Language\$languageNameCache
static HashBagOStuff null $languageNameCache
Cache for language names.
Definition: Language.php:152
Language\convertGrammar
convertGrammar( $word, $case)
Grammatical transformations, needed for inflected languages Invoked by putting {{grammar:case|word}} ...
Definition: Language.php:3730
wfTimestamp
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
Definition: GlobalFunctions.php:1994
$namespaces
namespace and then decline to actually register it & $namespaces
Definition: hooks.txt:934
Language\$mMonthGenMsgs
static $mMonthGenMsgs
Definition: Language.php:78
use
as see the revision history and available at free of to any person obtaining a copy of this software and associated documentation to deal in the Software without including without limitation the rights to use
Definition: MIT-LICENSE.txt:10
Language\timeanddate
timeanddate( $ts, $adj=false, $format=true, $timecorrection=false)
Definition: Language.php:2275
$user
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 account $user
Definition: hooks.txt:246
MediaWikiTitleCodec\getTitleInvalidRegex
static getTitleInvalidRegex()
Returns a simple regex that will match on characters and sequences invalid in titles.
Definition: MediaWikiTitleCodec.php:475
Language\getHebrewCalendarMonthNameGen
getHebrewCalendarMonthNameGen( $key)
Definition: Language.php:999
it
=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
Definition: contenthandler.txt:104
Language\$mMagicHookDone
$mMagicHookDone
Definition: Language.php:42
Language\$mConverter
LanguageConverter $mConverter
Definition: Language.php:39
$wgLangObjCacheSize
$wgLangObjCacheSize
Language cache size, or really how many languages can we handle simultaneously without degrading to c...
Definition: DefaultSettings.php:2845
Language\listToText
listToText(array $l)
Take a list of strings and build a locale-friendly comma-separated list, using the local comma-separa...
Definition: Language.php:3388
DB_SLAVE
const DB_SLAVE
Definition: Defines.php:34
Language\getMonthNamesArray
getMonthNamesArray()
Definition: Language.php:928
$s
$s
Definition: mergeMessageFileList.php:188
Language\$mHijriCalendarMonthMsgs
static $mHijriCalendarMonthMsgs
Definition: Language.php:111
Language\$namespaceNames
array null $namespaceNames
Definition: Language.php:49
Language\getPreferredVariant
getPreferredVariant()
Definition: Language.php:4139
wfLogWarning
wfLogWarning( $msg, $callerOffset=1, $level=E_USER_WARNING)
Send a warning as a PHP error and the debug log.
Definition: GlobalFunctions.php:1155
Language\getMonthAbbreviation
getMonthAbbreviation( $key)
Definition: Language.php:948
Language\ucwordbreaksCallbackAscii
ucwordbreaksCallbackAscii( $matches)
Definition: Language.php:2615
$name
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:304
$digitGroupingPattern
$digitGroupingPattern
Definition: MessagesAs.php:167
Language\recodeForEdit
recodeForEdit( $s)
Definition: Language.php:2960
Language\markNoConversion
markNoConversion( $text, $noParse=false)
Prepare external link text for conversion.
Definition: Language.php:4216
User\getDatePreference
getDatePreference()
Get the user's preferred date format.
Definition: User.php:3179
Language\getDefaultVariant
getDefaultVariant()
Definition: Language.php:4146
Language\fallback8bitEncoding
fallback8bitEncoding()
Definition: Language.php:2808
Language\getFileName
static getFileName( $prefix='Language', $code, $suffix='.php')
Get the name of a file for a certain language code.
Definition: Language.php:4359
Language\embedBidi
embedBidi( $text='')
Wraps argument with unicode control characters for directionality safety.
Definition: Language.php:3953
Language\formatTimePeriod
formatTimePeriod( $seconds, $format=[])
Formats a time given in seconds into a string representation of that time.
Definition: Language.php:4561
Language\formatBitrate
formatBitrate( $bps)
Format a bitrate for output, using an appropriate unit (bps, kbps, Mbps, Gbps, Tbps,...
Definition: Language.php:4661
Language\equals
equals(Language $lang)
Compare with an other language object.
Definition: Language.php:4279
$type
do that in ParserLimitReportFormat instead use this to modify the parameters of the image and a DIV can begin in one section and end in another Make sure your code can handle that case gracefully See the EditSectionClearerLink extension for an example zero but section is usually empty its values are the globals values before the output is cached my talk my contributions etc etc otherwise the built in rate limiting checks are if enabled allows for interception of redirect as a string mapping parameter names to values & $type
Definition: hooks.txt:2536
Language\convertNamespace
convertNamespace( $ns)
Convert a namespace index to a string in the preferred variant.
Definition: Language.php:4083
Language\getSpecialPageAliases
getSpecialPageAliases()
Get special page names, as an associative array canonical name => array of valid names,...
Definition: Language.php:3203
Language\getMessagesFileName
static getMessagesFileName( $code)
Definition: Language.php:4371
Language\getPluralRuleTypes
getPluralRuleTypes()
Get the plural rule types for the language.
Definition: Language.php:4854
Language\capitalizeAllNouns
capitalizeAllNouns()
Definition: Language.php:3101
Language\specialList
specialList( $page, $details, $oppositedm=true)
Make a list item, used by various special pages.
Definition: Language.php:4722
Language\$mHebrewCalendarMonthGenMsgs
static $mHebrewCalendarMonthGenMsgs
Definition: Language.php:103
Language\checkTitleEncoding
checkTitleEncoding( $s)
Definition: Language.php:2794
Language\$mParentLanguage
$mParentLanguage
Definition: Language.php:43
php
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:35
Language\isWellFormedLanguageTag
static isWellFormedLanguageTag( $code, $lenient=false)
Returns true if a language code string is a well-formed language tag according to RFC 5646.
Definition: Language.php:283
Language\getFormattedNamespaces
getFormattedNamespaces()
A convenience function that returns getNamespaces() with spaces instead of underscores in values.
Definition: Language.php:524
Language\lcfirst
lcfirst( $str)
Definition: Language.php:2678
Language\initEncoding
initEncoding()
Definition: Language.php:2951
Language\formatExpiry
formatExpiry( $expiry, $format=true, $infinity='infinity')
Decode an expiry (block, protection, etc) which has come from the DB.
Definition: Language.php:4531
Language\isKnownLanguageTag
static isKnownLanguageTag( $tag)
Returns true if a language code is an IETF tag known to MediaWiki.
Definition: Language.php:385
Language\emphasize
emphasize( $text)
Italic is unsuitable for some languages.
Definition: Language.php:3222
Language\getFallbackLanguages
getFallbackLanguages()
Definition: Language.php:446
Language\getDurationIntervals
getDurationIntervals( $seconds, array $chosenIntervals=[])
Takes a number of seconds and returns an array with a set of corresponding intervals.
Definition: Language.php:2320
Language\caseFold
caseFold( $s)
Return a case-folded representation of $s.
Definition: Language.php:2785
Language\userTimeAndDate
userTimeAndDate( $ts, User $user, array $options=[])
Get the formatted date and time for the given timestamp and formatted for the given user.
Definition: Language.php:2456
Language\segmentForDiff
segmentForDiff( $text)
languages like Chinese need to be segmented in order for the diff to be of any use
Definition: Language.php:4023
MWNamespace\getCanonicalIndex
static getCanonicalIndex( $name)
Returns the index for a given canonical name, or NULL The input must be converted to lower case first...
Definition: MWNamespace.php:244
Language\getWeekdayAbbreviation
getWeekdayAbbreviation( $key)
Definition: Language.php:975
Language\msg
msg( $msg)
Get message object in this language.
Definition: Language.php:913
$query
null for the 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:1572
Language\needsGenderDistinction
needsGenderDistinction()
Whether this language uses gender-dependent namespace aliases.
Definition: Language.php:589
Language\getParsedTitle
getParsedTitle()
For languages that support multiple variants, the title of an article may be displayed differently in...
Definition: Language.php:4190
Language\dateTimeObjFormat
static dateTimeObjFormat(&$dateTimeObj, $ts, $zone, $code)
Pass through result from $dateTimeObj->format()
Definition: Language.php:1019
FormatJson\decode
static decode( $value, $assoc=false)
Decodes a JSON string.
Definition: FormatJson.php:187
Language\getLocalisationCache
static getLocalisationCache()
Get the LocalisationCache instance.
Definition: Language.php:406
MWException
MediaWiki exception.
Definition: MWException.php:26
$title
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:934
NS_PROJECT
const NS_PROJECT
Definition: Defines.php:66
Language\viewPrevNext
viewPrevNext(Title $title, $offset, $limit, array $query=[], $atend=false)
Generate (prev x| next x) (20|50|100...) type links for paging.
Definition: Language.php:4745
Language\$transformData
$transformData
ReplacementArray object caches.
Definition: Language.php:55
Language\tsToIranian
static tsToIranian( $ts)
Algorithm by Roozbeh Pournader and Mohammad Toossi to convert Gregorian dates to Iranian dates.
Definition: Language.php:1581
Language\unsegmentForDiff
unsegmentForDiff( $text)
and unsegment to show the result
Definition: Language.php:4033
Language\fetchLanguageNames
static fetchLanguageNames( $inLanguage=null, $include='mw')
Get an array of language names, indexed by code.
Definition: Language.php:803
Language\truncateHtml
truncateHtml( $text, $length, $ellipsis='...')
Truncate a string of valid HTML to a specified length in bytes, appending an optional string (e....
Definition: Language.php:3567
Language\normalize
normalize( $s)
Convert a UTF-8 string to normal form C.
Definition: Language.php:2984
Language\getBookstoreList
getBookstoreList()
Exports $wgBookstoreListEn.
Definition: Language.php:454
Language\$mHebrewCalendarMonthMsgs
static $mHebrewCalendarMonthMsgs
Definition: Language.php:95
Language\pipeList
pipeList(array $list)
Same as commaList, but separate it with the pipe instead.
Definition: Language.php:3442
Language\$grammarTransformations
static MapCacheLRU null $grammarTransformations
Cache for grammar rules data.
Definition: Language.php:146
Language\doMagicHook
doMagicHook()
Run the LanguageGetMagic hook once.
Definition: Language.php:3149
wfGetDB
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
Definition: GlobalFunctions.php:3060
Language\convertForSearchResult
convertForSearchResult( $termsArray)
Definition: Language.php:2884
wfUrlProtocolsWithoutProtRel
wfUrlProtocolsWithoutProtRel()
Like wfUrlProtocols(), but excludes '//' from the protocol list.
Definition: GlobalFunctions.php:803
Language\getMessageFor
static getMessageFor( $key, $code)
Get a message for a given language.
Definition: Language.php:4476
$page
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:2536
$tag
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books $tag
Definition: hooks.txt:1028
$matches
$matches
Definition: NoLocalSettings.php:24
Language\getGenderNsText
getGenderNsText( $index, $gender)
Returns gender-dependent namespace alias if available.
Definition: Language.php:574
not
if not
Definition: COPYING.txt:307
Language\getHtmlCode
getHtmlCode()
Get the code in BCP 47 format which we can use inside of html lang="" tags.
Definition: Language.php:4305
Language\getNamespaceAliases
getNamespaceAliases()
Definition: Language.php:622
Language\getLocalNsIndex
getLocalNsIndex( $text)
Get a namespace key by value, case insensitive.
Definition: Language.php:613
Language\userDate
userDate( $ts, User $user, array $options=[])
Get the formatted date for the given timestamp and formatted for the given user.
Definition: Language.php:2410
MediaWiki
A helper class for throttling authentication attempts.
$IP
$IP
Definition: update.php:3
Language\addMagicWordsByLang
addMagicWordsByLang( $newWords)
Add magic words to the extension array.
Definition: Language.php:3188
Language\getMonthName
getMonthName( $key)
Definition: Language.php:921
Language\$mMonthMsgs
static $mMonthMsgs
Definition: Language.php:73
$limit
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context the output can only depend on parameters provided to this hook not on global state indicating whether full HTML should be generated If generation of HTML may be but other information should still be present in the ParserOutput object to manipulate or replace but no entry for that model exists in $wgContentHandlers please use GetContentModels hook to make them known to core if desired whether it is OK to use $contentModel on $title Handler functions that modify $ok should generally return false to prevent further hooks from further modifying $ok inclusive $limit
Definition: hooks.txt:1049
Language\getAllMessages
getAllMessages()
Definition: Language.php:2588
Language\getMagic
getMagic( $mw)
Fill a MagicWord object with data from here.
Definition: Language.php:3162
MapCacheLRU
Handles a simple LRU key/value map with a maximum number of entries.
Definition: MapCacheLRU.php:34
TO
we sometimes make exceptions for this Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally NO WARRANTY BECAUSE THE PROGRAM IS LICENSED FREE OF THERE IS NO WARRANTY FOR THE TO THE EXTENT PERMITTED BY APPLICABLE LAW EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND OR OTHER PARTIES PROVIDE THE PROGRAM AS IS WITHOUT WARRANTY OF ANY EITHER EXPRESSED OR BUT NOT LIMITED TO
Definition: COPYING.txt:260
Language\$rle
static $rle
Definition: Language.php:158
Language\isRTL
isRTL()
For right-to-left language support.
Definition: Language.php:3025
Language\userTime
userTime( $ts, User $user, array $options=[])
Get the formatted time for the given timestamp and formatted for the given user.
Definition: Language.php:2433
$wgLocalisationCacheConf
$wgLocalisationCacheConf
Localisation cache configuration.
Definition: DefaultSettings.php:2489
Language\getNsIndex
getNsIndex( $text)
Get a namespace key by value, case insensitive.
Definition: Language.php:696
Language\getNamespaceIds
getNamespaceIds()
Definition: Language.php:666
Language\getPluralRuleIndexNumber
getPluralRuleIndexNumber( $number)
Find the index number of the plural rule appropriate for the given number.
Definition: Language.php:4873
Language\getMonthNameGen
getMonthNameGen( $key)
Definition: Language.php:940
$time
see documentation in includes Linker php for Linker::makeImageLink & $time
Definition: hooks.txt:1769
global
when a variable name is used in a it is silently declared as a new masking the global
Definition: design.txt:93
Language\convertDoubleWidth
static convertDoubleWidth( $string)
convert double-width roman characters to single-width.
Definition: Language.php:2854
Language\getCompiledPluralRules
getCompiledPluralRules()
Get the compiled plural rules for the language.
Definition: Language.php:4816
Language\getHumanTimestampInternal
getHumanTimestampInternal(MWTimestamp $ts, MWTimestamp $relativeTo, User $user)
Convert an MWTimestamp into a pretty human-readable timestamp using the given user preferences and re...
Definition: Language.php:2512
Language\convertCategoryKey
convertCategoryKey( $key)
Definition: Language.php:4122
Language\digitGroupingPattern
digitGroupingPattern()
Definition: Language.php:3361
Language\ucwords
ucwords( $str)
Definition: Language.php:2721
Language\initContLang
initContLang()
Hook which will be called if this is the content language.
Definition: Language.php:439
string
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:177
Language\getPluralRules
getPluralRules()
Get the plural rules for the language.
Definition: Language.php:4835
list
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
Language\getArrow
getArrow( $direction='forwards')
An arrow, depending on the language direction.
Definition: Language.php:3112
$dir
$dir
Definition: Autoload.php:8
Language\uc
uc( $str, $first=false)
Convert a string to uppercase.
Definition: Language.php:2662
Language\$mVariants
$mVariants
Definition: Language.php:41
Language\translateBlockExpiry
translateBlockExpiry( $str, User $user=null, $now=0)
Definition: Language.php:3980
or
or
Definition: COPYING.txt:140
Language\formatNumNoSeparators
formatNumNoSeparators( $number)
Front-end for non-commafied formatNum.
Definition: Language.php:3276
Language\$mHtmlCode
$mHtmlCode
Definition: Language.php:43
Language\$mLoaded
$mLoaded
Definition: Language.php:41
Language\isValidBuiltInCode
static isValidBuiltInCode( $code)
Returns true if a language code is of a valid form for the purposes of internal customisation of Medi...
Definition: Language.php:362
Language\formatDuration
formatDuration( $seconds, array $chosenIntervals=[])
Takes a number of seconds and turns it into a text using values such as hours and minutes.
Definition: Language.php:2294
Language\setNamespaces
setNamespaces(array $namespaces)
Arbitrarily set all of the namespace names at once.
Definition: Language.php:504
Language\linkPrefixCharset
linkPrefixCharset()
A regular expression character set to match legal word-prefixing characters which should be merged on...
Definition: Language.php:4241
NS_USER_TALK
const NS_USER_TALK
Definition: Defines.php:65
$e
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException' returning false will NOT prevent logging $e
Definition: hooks.txt:2122
ReplacementArray
Wrapper around strtr() that holds replacements.
Definition: ReplacementArray.php:24
Language\findVariantLink
findVariantLink(&$link, &$nt, $ignoreOtherCond=false)
If a language supports multiple variants, it is possible that non-existing link in one variant actual...
Definition: Language.php:4169
Language\fixVariableInNamespace
fixVariableInNamespace( $talk)
Definition: Language.php:4496
$value
$value
Definition: styleTest.css.php:45
Language\ucwordbreaks
ucwordbreaks( $str)
capitalize words at word breaks
Definition: Language.php:2745
Language\getNsText
getNsText( $index)
Get a namespace value by key.
Definition: Language.php:543
Language\getCode
getCode()
Get the internal language code for this language object.
Definition: Language.php:4291
Language\getPluralRuleType
getPluralRuleType( $number)
Find the plural rule type appropriate for the given number For example, if the language is set to Ara...
Definition: Language.php:4887
$wgNamespaceAliases
$wgNamespaceAliases['Image']
The canonical names of namespaces 6 and 7 are, as of v1.14, "File" and "File_talk".
Definition: Setup.php:164
Language\dateFormat
dateFormat( $usePrefs=true)
This is meant to be used by time(), date(), and timeanddate() to get the date preference they're supp...
Definition: Language.php:2171
Language\newFromCode
static newFromCode( $code)
Create a language object for a given language code.
Definition: Language.php:209
Language\ucwordsCallbackMB
ucwordsCallbackMB( $matches)
Definition: Language.php:2631
SpecialBlock\getSuggestedDurations
static getSuggestedDurations( $lang=null)
Get an array of suggested block durations from MediaWiki:Ipboptions.
Definition: SpecialBlock.php:849
$wgLanguageCode
$wgLanguageCode
Site language code.
Definition: DefaultSettings.php:2839
Language\hebrewNumeral
static hebrewNumeral( $num)
Hebrew Gematria number formatting up to 9999.
Definition: Language.php:2006
wfIsInfinity
wfIsInfinity( $str)
Determine input string is represents as infinity.
Definition: GlobalFunctions.php:3577
Language\getFormattedNsText
getFormattedNsText( $index)
A convenience function that returns the same thing as getNsText() except with '_' changed to ' ',...
Definition: Language.php:561
Language\getMessagesFor
static getMessagesFor( $code)
Get all messages for a given language WARNING: this may take a long time.
Definition: Language.php:4464
Language\ucwordbreaksCallbackMB
ucwordbreaksCallbackMB( $matches)
Definition: Language.php:2623
options
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
NS_PROJECT_TALK
const NS_PROJECT_TALK
Definition: Defines.php:67
Language\handleExplicitPluralForms
handleExplicitPluralForms( $count, array $forms)
Handles explicit plural forms for Language::convertPlural()
Definition: Language.php:3909
$ret
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:1956
LocalisationCache
Class for caching the contents of localisation files, Messages*.php and *.i18n.php.
Definition: LocalisationCache.php:41
Language\recodeInput
recodeInput( $s)
Definition: Language.php:2969
Language\fetchLanguageName
static fetchLanguageName( $code, $inLanguage=null, $include='all')
Definition: Language.php:891
RequestContext\getMain
static getMain()
Static methods.
Definition: RequestContext.php:468
Language\getDirMark
getDirMark( $opposite=false)
A hidden direction mark (LRM or RLM), depending on the language direction.
Definition: Language.php:3089
Language\normalizeForSearch
normalizeForSearch( $string)
Some languages have special punctuation need to be normalized.
Definition: Language.php:2842
Language\isValidCode
static isValidCode( $code)
Returns true if a language code string is of a valid form, whether or not it exists.
Definition: Language.php:337
FakeConverter
A fake language converter.
Definition: FakeConverter.php:29
Language\userAdjust
userAdjust( $ts, $tz=false)
Used by date() and time() to adjust the time output.
Definition: Language.php:2093
Language\formatComputingNumbers
formatComputingNumbers( $size, $boundary, $messageKey)
Definition: Language.php:4671
Language\convertHtml
convertHtml( $text, $isTitle=false)
Perform output conversion on a string, and encode for safe HTML output.
Definition: Language.php:4114
Language\getFallbackFor
static getFallbackFor( $code)
Get the first fallback for a given language.
Definition: Language.php:4401
Language\__construct
__construct()
Definition: Language.php:415
Language\getMonthAbbreviationsArray
getMonthAbbreviationsArray()
Definition: Language.php:955
Language\getParentLanguage
getParentLanguage()
Get the "parent" language which has a converter to convert a "compatible" language (in another varian...
Definition: Language.php:4252
Language\$dateFormatStrings
$dateFormatStrings
Definition: Language.php:45
Language\hasWordBreaks
hasWordBreaks()
Most writing systems use whitespace to break up words.
Definition: Language.php:2820
Language\getDir
getDir()
Return the correct HTML 'dir' attribute value for this language.
Definition: Language.php:3033
Language\date
date( $ts, $adj=false, $format=true, $timecorrection=false)
Definition: Language.php:2236
Language\alignEnd
alignEnd()
Return 'right' or 'left' as appropriate alignment for line-end for this language's text direction.
Definition: Language.php:3057
Title
Represents a title within MediaWiki.
Definition: Title.php:39
Language\truncate_skip
truncate_skip(&$ret, $text, $search, $start, $len=null)
truncateHtml() helper function like strcspn() but adds the skipped chars to $ret
Definition: Language.php:3685
Language\setCode
setCode( $code)
Definition: Language.php:4315
Language\getDefaultDateFormat
getDefaultDateFormat()
Definition: Language.php:744
Language\getVariants
getVariants()
Get the list of variants supported by this language see sample implementation in LanguageZh....
Definition: Language.php:4132
Language\convertPlural
convertPlural( $count, $forms)
Plural form transformations, needed for some languages.
Definition: Language.php:3879
$cache
$cache
Definition: mcc.php:33
other
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
Language\$mLangObjCache
static $mLangObjCache
Definition: Language.php:62
$code
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:783
wfGetPrecompiledData
wfGetPrecompiledData( $name)
Get an object from the precompiled serialized directory.
Definition: GlobalFunctions.php:2942
Language\getFallbacksIncludingSiteLanguage
static getFallbacksIncludingSiteLanguage( $code)
Get the ordered list of fallback languages, ending with the fallback language chain for the site lang...
Definition: Language.php:4433
$wgGrammarForms
$wgGrammarForms
Some languages need different word forms, usually for different cases.
Definition: DefaultSettings.php:2856
Language\getGrammarForms
getGrammarForms()
Get the grammar forms for the content language.
Definition: Language.php:3781
Language\removeBadCharFirst
removeBadCharFirst( $string)
Remove bytes that represent an incomplete Unicode character at the start of string (e....
Definition: Language.php:3541
Language\$strongDirRegex
static $strongDirRegex
Directionality test regex for embedBidi().
Definition: Language.php:174
as
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
Definition: distributors.txt:9
MWNamespace\getCanonicalNamespaces
static getCanonicalNamespaces( $rebuild=false)
Returns array of all defined namespaces with their canonical (English) names.
Definition: MWNamespace.php:207
Language\getHijriCalendarMonthName
getHijriCalendarMonthName( $key)
Definition: Language.php:1007
Language\getMessage
getMessage( $key)
Definition: Language.php:2581
Language\getGrammarTransformations
getGrammarTransformations()
Get the grammar transformations data for the language.
Definition: Language.php:3801
Language\getMessageKeysFor
static getMessageKeysFor( $code)
Get all message keys for a given language.
Definition: Language.php:4488
Language\getJsonMessagesFileName
static getJsonMessagesFileName( $code)
Definition: Language.php:4384
Language\firstChar
firstChar( $s)
Get the first character of a string.
Definition: Language.php:2896
NS_USER
const NS_USER
Definition: Defines.php:64
$link
usually copyright or history_copyright This message must be in HTML not wikitext & $link
Definition: hooks.txt:2929
Language\getNamespaces
getNamespaces()
Returns an array of localised namespaces indexed by their numbers.
Definition: Language.php:464
Language\transformUsingPairFile
transformUsingPairFile( $file, $string)
Transform a string using serialized data stored in the given file (which must be in the serialized su...
Definition: Language.php:3009
utf8ToCodepoint
utf8ToCodepoint( $char)
Determine the Unicode codepoint of a single-character UTF-8 sequence.
Definition: UtfNormalUtil.php:85
Language\removeBadCharLast
removeBadCharLast( $string)
Remove bytes that represent an incomplete Unicode character at the end of string (e....
Definition: Language.php:3515
true
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:1956
Language\romanNumeral
static romanNumeral( $num)
Roman number formatting up to 10000.
Definition: Language.php:1975
Language\getConvRuleTitle
getConvRuleTitle()
Get the conversion rule title, if any.
Definition: Language.php:4807
of
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
Definition: globals.txt:10
Language\factory
static factory( $code)
Get a cached or new language object for a given language code.
Definition: Language.php:183
wfMessage
either a unescaped string or a HtmlArmor object after in associative array form externallinks including delete and has completed for all link tables whether this was an auto creation default is conds Array Extra conditions for the No matching items in log is displayed if loglist is empty msgKey Array If you want a nice box with a set this to the key of the message First element is the message additional optional elements are parameters for the key that are processed with wfMessage() -> params() ->parseAsBlock() - offset Set to overwrite offset parameter in $wgRequest set to '' to unset offset - wrap String Wrap the message in html(usually something like "&lt
wfWarn
wfWarn( $msg, $callerOffset=1, $level=E_USER_NOTICE)
Send a warning either to the debug log or in a PHP error depending on $wgDevelopmentWarnings.
Definition: GlobalFunctions.php:1142
Language\$namespaceAliases
$namespaceAliases
Definition: Language.php:50
class
you have access to all of the normal MediaWiki so you can get a DB use the etc For full docs on the Maintenance class
Definition: maintenance.txt:52
Language\tsToHijri
static tsToHijri( $ts)
Converting Gregorian dates to Hijri dates.
Definition: Language.php:1641
Language\getHumanTimestamp
getHumanTimestamp(MWTimestamp $time, MWTimestamp $relativeTo=null, User $user=null)
Get the timestamp in a human-friendly relative format, e.g., "3 days ago".
Definition: Language.php:2475
$t
$t
Definition: testCompression.php:67
Language\time
time( $ts, $adj=false, $format=true, $timecorrection=false)
Definition: Language.php:2255
Html\element
static element( $element, $attribs=[], $contents='')
Identical to rawElement(), but HTML-escapes $contents (like Xml::element()).
Definition: Html.php:231
Language\insertSpace
static insertSpace( $string, $pattern)
Definition: Language.php:2874
Language\isMultibyte
isMultibyte( $str)
Definition: Language.php:2713
Language\alignStart
alignStart()
Return 'left' or 'right' as appropriate alignment for line-start for this language's text direction.
Definition: Language.php:3045
Language\isSupportedLanguage
static isSupportedLanguage( $code)
Checks whether any localisation is available for that language tag in MediaWiki (MessagesXx....
Definition: Language.php:255
Language\$mWeekdayMsgs
static $mWeekdayMsgs
Definition: Language.php:64
Language\digitTransformTable
digitTransformTable()
Definition: Language.php:3368
Language\tsToYear
static tsToYear( $ts, $cName)
Algorithm to convert Gregorian dates to Thai solar dates, Minguo dates or Minguo dates.
Definition: Language.php:1872
Language\getDirMarkEntity
getDirMarkEntity( $opposite=false)
A hidden direction mark (LRM or RLM), depending on the language direction.
Definition: Language.php:3072
Language\strongDirFromContent
static strongDirFromContent( $text='')
Gets directionality of the first strongly directional codepoint, for embedBidi()
Definition: Language.php:1958
Language\hebrewYearStart
static hebrewYearStart( $year)
This calculates the Hebrew year start, as days since 1 September.
Definition: Language.php:1834
Language\$mNamespaceIds
$mNamespaceIds
Definition: Language.php:50
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:50
Language\getDateFormatString
getDateFormatString( $type, $pref)
Get a format string for a given type and preference.
Definition: Language.php:2202
Language\$dataCache
static LocalisationCache $dataCache
Definition: Language.php:60
Hooks\run
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:131
Language\getMagicWords
getMagicWords()
Get all magic words from cache.
Definition: Language.php:3142
Language\hasVariant
hasVariant( $variant)
Check if the language has the specific variant.
Definition: Language.php:4103
Language\numLink
numLink(Title $title, $offset, $limit, array $query, $link, $tooltipMsg, $class)
Helper function for viewPrevNext() that generates links.
Definition: Language.php:4791
Language\__destruct
__destruct()
Reduce memory usage.
Definition: Language.php:429
Language\commafy
commafy( $number)
Adds commas to a given number.
Definition: Language.php:3309
Language\$pdf
static $pdf
Definition: Language.php:159
$options
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context $options
Definition: hooks.txt:1049
Language
Internationalisation code.
Definition: Language.php:35
Language\$mExtendedSpecialPageAliases
$mExtendedSpecialPageAliases
Definition: Language.php:46
Language\$durationIntervals
static array $durationIntervals
Definition: Language.php:122
Language\tsToHebrew
static tsToHebrew( $ts)
Converting Gregorian dates to Hebrew dates.
Definition: Language.php:1693
Language\autoConvertToAllVariants
autoConvertToAllVariants( $text)
convert text to all supported variants
Definition: Language.php:4053
Language\linkTrail
linkTrail()
A regular expression to match legal word-trailing characters which should be merged onto a link of th...
Definition: Language.php:4231
$wgAllUnicodeFixes
$wgAllUnicodeFixes
Set this to always convert certain Unicode sequences to modern ones regardless of the content languag...
Definition: DefaultSettings.php:2946
Language\ucfirst
ucfirst( $str)
Make a string's first character uppercase.
Definition: Language.php:2642
Language\$mMagicExtensions
$mMagicExtensions
Definition: Language.php:42
$wgDummyLanguageCodes
$wgDummyLanguageCodes
Functionally the same as $wgExtraLanguageCodes, but deprecated.
Definition: DefaultSettings.php:2913
array
the array() calling protocol came about after MediaWiki 1.4rc1.
Language\getWeekdayName
getWeekdayName( $key)
Definition: Language.php:967
Language\gender
gender( $gender, $forms)
Provides an alternative text depending on specified gender.
Definition: Language.php:3850
Language\$lre
static $lre
Unicode directional formatting characters, for embedBidi()
Definition: Language.php:157
Language\getHebrewCalendarMonthName
getHebrewCalendarMonthName( $key)
Definition: Language.php:991
Language\formatNum
formatNum( $number, $nocommafy=false)
Normally we output all numbers in plain en_US style, that is 293,291.235 for twohundredninetythreetho...
Definition: Language.php:3248
$out
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:783
Language\$fallbackLanguageCache
static array $fallbackLanguageCache
Cache for language fallbacks.
Definition: Language.php:140
Language\convertTitle
convertTitle( $title)
Convert a Title object to a string in the preferred variant.
Definition: Language.php:4073