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