MediaWiki  master
Language.php
Go to the documentation of this file.
1 <?php
28 if ( !defined( 'MEDIAWIKI' ) ) {
29  echo "This file is part of MediaWiki, it is not a valid entry point.\n";
30  exit( 1 );
31 }
32 
33 use CLDRPluralRuleParser\Evaluator;
34 
39 class Language {
43  public $mConverter;
44 
45  public $mVariants, $mCode, $mLoaded = false;
46  public $mMagicExtensions = [], $mMagicHookDone = false;
47  private $mHtmlCode = null, $mParentLanguage = false;
48 
49  public $dateFormatStrings = [];
51 
53 
57  public $transformData = [];
58 
62  static public $dataCache;
63 
64  static public $mLangObjCache = [];
65 
66  static public $mWeekdayMsgs = [
67  'sunday', 'monday', 'tuesday', 'wednesday', 'thursday',
68  'friday', 'saturday'
69  ];
70 
71  static public $mWeekdayAbbrevMsgs = [
72  'sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'
73  ];
74 
75  static public $mMonthMsgs = [
76  'january', 'february', 'march', 'april', 'may_long', 'june',
77  'july', 'august', 'september', 'october', 'november',
78  'december'
79  ];
80  static public $mMonthGenMsgs = [
81  'january-gen', 'february-gen', 'march-gen', 'april-gen', 'may-gen', 'june-gen',
82  'july-gen', 'august-gen', 'september-gen', 'october-gen', 'november-gen',
83  'december-gen'
84  ];
85  static public $mMonthAbbrevMsgs = [
86  'jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug',
87  'sep', 'oct', 'nov', 'dec'
88  ];
89 
90  static public $mIranianCalendarMonthMsgs = [
91  'iranian-calendar-m1', 'iranian-calendar-m2', 'iranian-calendar-m3',
92  'iranian-calendar-m4', 'iranian-calendar-m5', 'iranian-calendar-m6',
93  'iranian-calendar-m7', 'iranian-calendar-m8', 'iranian-calendar-m9',
94  'iranian-calendar-m10', 'iranian-calendar-m11', 'iranian-calendar-m12'
95  ];
96 
97  static public $mHebrewCalendarMonthMsgs = [
98  'hebrew-calendar-m1', 'hebrew-calendar-m2', 'hebrew-calendar-m3',
99  'hebrew-calendar-m4', 'hebrew-calendar-m5', 'hebrew-calendar-m6',
100  'hebrew-calendar-m7', 'hebrew-calendar-m8', 'hebrew-calendar-m9',
101  'hebrew-calendar-m10', 'hebrew-calendar-m11', 'hebrew-calendar-m12',
102  'hebrew-calendar-m6a', 'hebrew-calendar-m6b'
103  ];
104 
106  'hebrew-calendar-m1-gen', 'hebrew-calendar-m2-gen', 'hebrew-calendar-m3-gen',
107  'hebrew-calendar-m4-gen', 'hebrew-calendar-m5-gen', 'hebrew-calendar-m6-gen',
108  'hebrew-calendar-m7-gen', 'hebrew-calendar-m8-gen', 'hebrew-calendar-m9-gen',
109  'hebrew-calendar-m10-gen', 'hebrew-calendar-m11-gen', 'hebrew-calendar-m12-gen',
110  'hebrew-calendar-m6a-gen', 'hebrew-calendar-m6b-gen'
111  ];
112 
113  static public $mHijriCalendarMonthMsgs = [
114  'hijri-calendar-m1', 'hijri-calendar-m2', 'hijri-calendar-m3',
115  'hijri-calendar-m4', 'hijri-calendar-m5', 'hijri-calendar-m6',
116  'hijri-calendar-m7', 'hijri-calendar-m8', 'hijri-calendar-m9',
117  'hijri-calendar-m10', 'hijri-calendar-m11', 'hijri-calendar-m12'
118  ];
119 
124  static public $durationIntervals = [
125  'millennia' => 31556952000,
126  'centuries' => 3155695200,
127  'decades' => 315569520,
128  'years' => 31556952, // 86400 * ( 365 + ( 24 * 3 + 25 ) / 400 )
129  'weeks' => 604800,
130  'days' => 86400,
131  'hours' => 3600,
132  'minutes' => 60,
133  'seconds' => 1,
134  ];
135 
142  static private $fallbackLanguageCache = [];
143 
148  static private $languageNameCache;
149 
153  static private $lre = "\xE2\x80\xAA"; // U+202A LEFT-TO-RIGHT EMBEDDING
154  static private $rle = "\xE2\x80\xAB"; // U+202B RIGHT-TO-LEFT EMBEDDING
155  static private $pdf = "\xE2\x80\xAC"; // U+202C POP DIRECTIONAL FORMATTING
156 
168  // @codingStandardsIgnoreStart
169  // @codeCoverageIgnoreStart
170  static private $strongDirRegex = '/(?:([\x{41}-\x{5a}\x{61}-\x{7a}\x{aa}\x{b5}\x{ba}\x{c0}-\x{d6}\x{d8}-\x{f6}\x{f8}-\x{2b8}\x{2bb}-\x{2c1}\x{2d0}\x{2d1}\x{2e0}-\x{2e4}\x{2ee}\x{370}-\x{373}\x{376}\x{377}\x{37a}-\x{37d}\x{37f}\x{386}\x{388}-\x{38a}\x{38c}\x{38e}-\x{3a1}\x{3a3}-\x{3f5}\x{3f7}-\x{482}\x{48a}-\x{52f}\x{531}-\x{556}\x{559}-\x{55f}\x{561}-\x{587}\x{589}\x{903}-\x{939}\x{93b}\x{93d}-\x{940}\x{949}-\x{94c}\x{94e}-\x{950}\x{958}-\x{961}\x{964}-\x{980}\x{982}\x{983}\x{985}-\x{98c}\x{98f}\x{990}\x{993}-\x{9a8}\x{9aa}-\x{9b0}\x{9b2}\x{9b6}-\x{9b9}\x{9bd}-\x{9c0}\x{9c7}\x{9c8}\x{9cb}\x{9cc}\x{9ce}\x{9d7}\x{9dc}\x{9dd}\x{9df}-\x{9e1}\x{9e6}-\x{9f1}\x{9f4}-\x{9fa}\x{a03}\x{a05}-\x{a0a}\x{a0f}\x{a10}\x{a13}-\x{a28}\x{a2a}-\x{a30}\x{a32}\x{a33}\x{a35}\x{a36}\x{a38}\x{a39}\x{a3e}-\x{a40}\x{a59}-\x{a5c}\x{a5e}\x{a66}-\x{a6f}\x{a72}-\x{a74}\x{a83}\x{a85}-\x{a8d}\x{a8f}-\x{a91}\x{a93}-\x{aa8}\x{aaa}-\x{ab0}\x{ab2}\x{ab3}\x{ab5}-\x{ab9}\x{abd}-\x{ac0}\x{ac9}\x{acb}\x{acc}\x{ad0}\x{ae0}\x{ae1}\x{ae6}-\x{af0}\x{af9}\x{b02}\x{b03}\x{b05}-\x{b0c}\x{b0f}\x{b10}\x{b13}-\x{b28}\x{b2a}-\x{b30}\x{b32}\x{b33}\x{b35}-\x{b39}\x{b3d}\x{b3e}\x{b40}\x{b47}\x{b48}\x{b4b}\x{b4c}\x{b57}\x{b5c}\x{b5d}\x{b5f}-\x{b61}\x{b66}-\x{b77}\x{b83}\x{b85}-\x{b8a}\x{b8e}-\x{b90}\x{b92}-\x{b95}\x{b99}\x{b9a}\x{b9c}\x{b9e}\x{b9f}\x{ba3}\x{ba4}\x{ba8}-\x{baa}\x{bae}-\x{bb9}\x{bbe}\x{bbf}\x{bc1}\x{bc2}\x{bc6}-\x{bc8}\x{bca}-\x{bcc}\x{bd0}\x{bd7}\x{be6}-\x{bf2}\x{c01}-\x{c03}\x{c05}-\x{c0c}\x{c0e}-\x{c10}\x{c12}-\x{c28}\x{c2a}-\x{c39}\x{c3d}\x{c41}-\x{c44}\x{c58}-\x{c5a}\x{c60}\x{c61}\x{c66}-\x{c6f}\x{c7f}\x{c82}\x{c83}\x{c85}-\x{c8c}\x{c8e}-\x{c90}\x{c92}-\x{ca8}\x{caa}-\x{cb3}\x{cb5}-\x{cb9}\x{cbd}-\x{cc4}\x{cc6}-\x{cc8}\x{cca}\x{ccb}\x{cd5}\x{cd6}\x{cde}\x{ce0}\x{ce1}\x{ce6}-\x{cef}\x{cf1}\x{cf2}\x{d02}\x{d03}\x{d05}-\x{d0c}\x{d0e}-\x{d10}\x{d12}-\x{d3a}\x{d3d}-\x{d40}\x{d46}-\x{d48}\x{d4a}-\x{d4c}\x{d4e}\x{d57}\x{d5f}-\x{d61}\x{d66}-\x{d75}\x{d79}-\x{d7f}\x{d82}\x{d83}\x{d85}-\x{d96}\x{d9a}-\x{db1}\x{db3}-\x{dbb}\x{dbd}\x{dc0}-\x{dc6}\x{dcf}-\x{dd1}\x{dd8}-\x{ddf}\x{de6}-\x{def}\x{df2}-\x{df4}\x{e01}-\x{e30}\x{e32}\x{e33}\x{e40}-\x{e46}\x{e4f}-\x{e5b}\x{e81}\x{e82}\x{e84}\x{e87}\x{e88}\x{e8a}\x{e8d}\x{e94}-\x{e97}\x{e99}-\x{e9f}\x{ea1}-\x{ea3}\x{ea5}\x{ea7}\x{eaa}\x{eab}\x{ead}-\x{eb0}\x{eb2}\x{eb3}\x{ebd}\x{ec0}-\x{ec4}\x{ec6}\x{ed0}-\x{ed9}\x{edc}-\x{edf}\x{f00}-\x{f17}\x{f1a}-\x{f34}\x{f36}\x{f38}\x{f3e}-\x{f47}\x{f49}-\x{f6c}\x{f7f}\x{f85}\x{f88}-\x{f8c}\x{fbe}-\x{fc5}\x{fc7}-\x{fcc}\x{fce}-\x{fda}\x{1000}-\x{102c}\x{1031}\x{1038}\x{103b}\x{103c}\x{103f}-\x{1057}\x{105a}-\x{105d}\x{1061}-\x{1070}\x{1075}-\x{1081}\x{1083}\x{1084}\x{1087}-\x{108c}\x{108e}-\x{109c}\x{109e}-\x{10c5}\x{10c7}\x{10cd}\x{10d0}-\x{1248}\x{124a}-\x{124d}\x{1250}-\x{1256}\x{1258}\x{125a}-\x{125d}\x{1260}-\x{1288}\x{128a}-\x{128d}\x{1290}-\x{12b0}\x{12b2}-\x{12b5}\x{12b8}-\x{12be}\x{12c0}\x{12c2}-\x{12c5}\x{12c8}-\x{12d6}\x{12d8}-\x{1310}\x{1312}-\x{1315}\x{1318}-\x{135a}\x{1360}-\x{137c}\x{1380}-\x{138f}\x{13a0}-\x{13f5}\x{13f8}-\x{13fd}\x{1401}-\x{167f}\x{1681}-\x{169a}\x{16a0}-\x{16f8}\x{1700}-\x{170c}\x{170e}-\x{1711}\x{1720}-\x{1731}\x{1735}\x{1736}\x{1740}-\x{1751}\x{1760}-\x{176c}\x{176e}-\x{1770}\x{1780}-\x{17b3}\x{17b6}\x{17be}-\x{17c5}\x{17c7}\x{17c8}\x{17d4}-\x{17da}\x{17dc}\x{17e0}-\x{17e9}\x{1810}-\x{1819}\x{1820}-\x{1877}\x{1880}-\x{18a8}\x{18aa}\x{18b0}-\x{18f5}\x{1900}-\x{191e}\x{1923}-\x{1926}\x{1929}-\x{192b}\x{1930}\x{1931}\x{1933}-\x{1938}\x{1946}-\x{196d}\x{1970}-\x{1974}\x{1980}-\x{19ab}\x{19b0}-\x{19c9}\x{19d0}-\x{19da}\x{1a00}-\x{1a16}\x{1a19}\x{1a1a}\x{1a1e}-\x{1a55}\x{1a57}\x{1a61}\x{1a63}\x{1a64}\x{1a6d}-\x{1a72}\x{1a80}-\x{1a89}\x{1a90}-\x{1a99}\x{1aa0}-\x{1aad}\x{1b04}-\x{1b33}\x{1b35}\x{1b3b}\x{1b3d}-\x{1b41}\x{1b43}-\x{1b4b}\x{1b50}-\x{1b6a}\x{1b74}-\x{1b7c}\x{1b82}-\x{1ba1}\x{1ba6}\x{1ba7}\x{1baa}\x{1bae}-\x{1be5}\x{1be7}\x{1bea}-\x{1bec}\x{1bee}\x{1bf2}\x{1bf3}\x{1bfc}-\x{1c2b}\x{1c34}\x{1c35}\x{1c3b}-\x{1c49}\x{1c4d}-\x{1c7f}\x{1cc0}-\x{1cc7}\x{1cd3}\x{1ce1}\x{1ce9}-\x{1cec}\x{1cee}-\x{1cf3}\x{1cf5}\x{1cf6}\x{1d00}-\x{1dbf}\x{1e00}-\x{1f15}\x{1f18}-\x{1f1d}\x{1f20}-\x{1f45}\x{1f48}-\x{1f4d}\x{1f50}-\x{1f57}\x{1f59}\x{1f5b}\x{1f5d}\x{1f5f}-\x{1f7d}\x{1f80}-\x{1fb4}\x{1fb6}-\x{1fbc}\x{1fbe}\x{1fc2}-\x{1fc4}\x{1fc6}-\x{1fcc}\x{1fd0}-\x{1fd3}\x{1fd6}-\x{1fdb}\x{1fe0}-\x{1fec}\x{1ff2}-\x{1ff4}\x{1ff6}-\x{1ffc}\x{200e}\x{2071}\x{207f}\x{2090}-\x{209c}\x{2102}\x{2107}\x{210a}-\x{2113}\x{2115}\x{2119}-\x{211d}\x{2124}\x{2126}\x{2128}\x{212a}-\x{212d}\x{212f}-\x{2139}\x{213c}-\x{213f}\x{2145}-\x{2149}\x{214e}\x{214f}\x{2160}-\x{2188}\x{2336}-\x{237a}\x{2395}\x{249c}-\x{24e9}\x{26ac}\x{2800}-\x{28ff}\x{2c00}-\x{2c2e}\x{2c30}-\x{2c5e}\x{2c60}-\x{2ce4}\x{2ceb}-\x{2cee}\x{2cf2}\x{2cf3}\x{2d00}-\x{2d25}\x{2d27}\x{2d2d}\x{2d30}-\x{2d67}\x{2d6f}\x{2d70}\x{2d80}-\x{2d96}\x{2da0}-\x{2da6}\x{2da8}-\x{2dae}\x{2db0}-\x{2db6}\x{2db8}-\x{2dbe}\x{2dc0}-\x{2dc6}\x{2dc8}-\x{2dce}\x{2dd0}-\x{2dd6}\x{2dd8}-\x{2dde}\x{3005}-\x{3007}\x{3021}-\x{3029}\x{302e}\x{302f}\x{3031}-\x{3035}\x{3038}-\x{303c}\x{3041}-\x{3096}\x{309d}-\x{309f}\x{30a1}-\x{30fa}\x{30fc}-\x{30ff}\x{3105}-\x{312d}\x{3131}-\x{318e}\x{3190}-\x{31ba}\x{31f0}-\x{321c}\x{3220}-\x{324f}\x{3260}-\x{327b}\x{327f}-\x{32b0}\x{32c0}-\x{32cb}\x{32d0}-\x{32fe}\x{3300}-\x{3376}\x{337b}-\x{33dd}\x{33e0}-\x{33fe}\x{3400}-\x{4db5}\x{4e00}-\x{9fd5}\x{a000}-\x{a48c}\x{a4d0}-\x{a60c}\x{a610}-\x{a62b}\x{a640}-\x{a66e}\x{a680}-\x{a69d}\x{a6a0}-\x{a6ef}\x{a6f2}-\x{a6f7}\x{a722}-\x{a787}\x{a789}-\x{a7ad}\x{a7b0}-\x{a7b7}\x{a7f7}-\x{a801}\x{a803}-\x{a805}\x{a807}-\x{a80a}\x{a80c}-\x{a824}\x{a827}\x{a830}-\x{a837}\x{a840}-\x{a873}\x{a880}-\x{a8c3}\x{a8ce}-\x{a8d9}\x{a8f2}-\x{a8fd}\x{a900}-\x{a925}\x{a92e}-\x{a946}\x{a952}\x{a953}\x{a95f}-\x{a97c}\x{a983}-\x{a9b2}\x{a9b4}\x{a9b5}\x{a9ba}\x{a9bb}\x{a9bd}-\x{a9cd}\x{a9cf}-\x{a9d9}\x{a9de}-\x{a9e4}\x{a9e6}-\x{a9fe}\x{aa00}-\x{aa28}\x{aa2f}\x{aa30}\x{aa33}\x{aa34}\x{aa40}-\x{aa42}\x{aa44}-\x{aa4b}\x{aa4d}\x{aa50}-\x{aa59}\x{aa5c}-\x{aa7b}\x{aa7d}-\x{aaaf}\x{aab1}\x{aab5}\x{aab6}\x{aab9}-\x{aabd}\x{aac0}\x{aac2}\x{aadb}-\x{aaeb}\x{aaee}-\x{aaf5}\x{ab01}-\x{ab06}\x{ab09}-\x{ab0e}\x{ab11}-\x{ab16}\x{ab20}-\x{ab26}\x{ab28}-\x{ab2e}\x{ab30}-\x{ab65}\x{ab70}-\x{abe4}\x{abe6}\x{abe7}\x{abe9}-\x{abec}\x{abf0}-\x{abf9}\x{ac00}-\x{d7a3}\x{d7b0}-\x{d7c6}\x{d7cb}-\x{d7fb}\x{e000}-\x{fa6d}\x{fa70}-\x{fad9}\x{fb00}-\x{fb06}\x{fb13}-\x{fb17}\x{ff21}-\x{ff3a}\x{ff41}-\x{ff5a}\x{ff66}-\x{ffbe}\x{ffc2}-\x{ffc7}\x{ffca}-\x{ffcf}\x{ffd2}-\x{ffd7}\x{ffda}-\x{ffdc}\x{10000}-\x{1000b}\x{1000d}-\x{10026}\x{10028}-\x{1003a}\x{1003c}\x{1003d}\x{1003f}-\x{1004d}\x{10050}-\x{1005d}\x{10080}-\x{100fa}\x{10100}\x{10102}\x{10107}-\x{10133}\x{10137}-\x{1013f}\x{101d0}-\x{101fc}\x{10280}-\x{1029c}\x{102a0}-\x{102d0}\x{10300}-\x{10323}\x{10330}-\x{1034a}\x{10350}-\x{10375}\x{10380}-\x{1039d}\x{1039f}-\x{103c3}\x{103c8}-\x{103d5}\x{10400}-\x{1049d}\x{104a0}-\x{104a9}\x{10500}-\x{10527}\x{10530}-\x{10563}\x{1056f}\x{10600}-\x{10736}\x{10740}-\x{10755}\x{10760}-\x{10767}\x{11000}\x{11002}-\x{11037}\x{11047}-\x{1104d}\x{11066}-\x{1106f}\x{11082}-\x{110b2}\x{110b7}\x{110b8}\x{110bb}-\x{110c1}\x{110d0}-\x{110e8}\x{110f0}-\x{110f9}\x{11103}-\x{11126}\x{1112c}\x{11136}-\x{11143}\x{11150}-\x{11172}\x{11174}-\x{11176}\x{11182}-\x{111b5}\x{111bf}-\x{111c9}\x{111cd}\x{111d0}-\x{111df}\x{111e1}-\x{111f4}\x{11200}-\x{11211}\x{11213}-\x{1122e}\x{11232}\x{11233}\x{11235}\x{11238}-\x{1123d}\x{11280}-\x{11286}\x{11288}\x{1128a}-\x{1128d}\x{1128f}-\x{1129d}\x{1129f}-\x{112a9}\x{112b0}-\x{112de}\x{112e0}-\x{112e2}\x{112f0}-\x{112f9}\x{11302}\x{11303}\x{11305}-\x{1130c}\x{1130f}\x{11310}\x{11313}-\x{11328}\x{1132a}-\x{11330}\x{11332}\x{11333}\x{11335}-\x{11339}\x{1133d}-\x{1133f}\x{11341}-\x{11344}\x{11347}\x{11348}\x{1134b}-\x{1134d}\x{11350}\x{11357}\x{1135d}-\x{11363}\x{11480}-\x{114b2}\x{114b9}\x{114bb}-\x{114be}\x{114c1}\x{114c4}-\x{114c7}\x{114d0}-\x{114d9}\x{11580}-\x{115b1}\x{115b8}-\x{115bb}\x{115be}\x{115c1}-\x{115db}\x{11600}-\x{11632}\x{1163b}\x{1163c}\x{1163e}\x{11641}-\x{11644}\x{11650}-\x{11659}\x{11680}-\x{116aa}\x{116ac}\x{116ae}\x{116af}\x{116b6}\x{116c0}-\x{116c9}\x{11700}-\x{11719}\x{11720}\x{11721}\x{11726}\x{11730}-\x{1173f}\x{118a0}-\x{118f2}\x{118ff}\x{11ac0}-\x{11af8}\x{12000}-\x{12399}\x{12400}-\x{1246e}\x{12470}-\x{12474}\x{12480}-\x{12543}\x{13000}-\x{1342e}\x{14400}-\x{14646}\x{16800}-\x{16a38}\x{16a40}-\x{16a5e}\x{16a60}-\x{16a69}\x{16a6e}\x{16a6f}\x{16ad0}-\x{16aed}\x{16af5}\x{16b00}-\x{16b2f}\x{16b37}-\x{16b45}\x{16b50}-\x{16b59}\x{16b5b}-\x{16b61}\x{16b63}-\x{16b77}\x{16b7d}-\x{16b8f}\x{16f00}-\x{16f44}\x{16f50}-\x{16f7e}\x{16f93}-\x{16f9f}\x{1b000}\x{1b001}\x{1bc00}-\x{1bc6a}\x{1bc70}-\x{1bc7c}\x{1bc80}-\x{1bc88}\x{1bc90}-\x{1bc99}\x{1bc9c}\x{1bc9f}\x{1d000}-\x{1d0f5}\x{1d100}-\x{1d126}\x{1d129}-\x{1d166}\x{1d16a}-\x{1d172}\x{1d183}\x{1d184}\x{1d18c}-\x{1d1a9}\x{1d1ae}-\x{1d1e8}\x{1d360}-\x{1d371}\x{1d400}-\x{1d454}\x{1d456}-\x{1d49c}\x{1d49e}\x{1d49f}\x{1d4a2}\x{1d4a5}\x{1d4a6}\x{1d4a9}-\x{1d4ac}\x{1d4ae}-\x{1d4b9}\x{1d4bb}\x{1d4bd}-\x{1d4c3}\x{1d4c5}-\x{1d505}\x{1d507}-\x{1d50a}\x{1d50d}-\x{1d514}\x{1d516}-\x{1d51c}\x{1d51e}-\x{1d539}\x{1d53b}-\x{1d53e}\x{1d540}-\x{1d544}\x{1d546}\x{1d54a}-\x{1d550}\x{1d552}-\x{1d6a5}\x{1d6a8}-\x{1d6da}\x{1d6dc}-\x{1d714}\x{1d716}-\x{1d74e}\x{1d750}-\x{1d788}\x{1d78a}-\x{1d7c2}\x{1d7c4}-\x{1d7cb}\x{1d800}-\x{1d9ff}\x{1da37}-\x{1da3a}\x{1da6d}-\x{1da74}\x{1da76}-\x{1da83}\x{1da85}-\x{1da8b}\x{1f110}-\x{1f12e}\x{1f130}-\x{1f169}\x{1f170}-\x{1f19a}\x{1f1e6}-\x{1f202}\x{1f210}-\x{1f23a}\x{1f240}-\x{1f248}\x{1f250}\x{1f251}\x{20000}-\x{2a6d6}\x{2a700}-\x{2b734}\x{2b740}-\x{2b81d}\x{2b820}-\x{2cea1}\x{2f800}-\x{2fa1d}\x{f0000}-\x{ffffd}\x{100000}-\x{10fffd}])|([\x{590}\x{5be}\x{5c0}\x{5c3}\x{5c6}\x{5c8}-\x{5ff}\x{7c0}-\x{7ea}\x{7f4}\x{7f5}\x{7fa}-\x{815}\x{81a}\x{824}\x{828}\x{82e}-\x{858}\x{85c}-\x{89f}\x{200f}\x{fb1d}\x{fb1f}-\x{fb28}\x{fb2a}-\x{fb4f}\x{10800}-\x{1091e}\x{10920}-\x{10a00}\x{10a04}\x{10a07}-\x{10a0b}\x{10a10}-\x{10a37}\x{10a3b}-\x{10a3e}\x{10a40}-\x{10ae4}\x{10ae7}-\x{10b38}\x{10b40}-\x{10e5f}\x{10e7f}-\x{10fff}\x{1e800}-\x{1e8cf}\x{1e8d7}-\x{1edff}\x{1ef00}-\x{1efff}\x{608}\x{60b}\x{60d}\x{61b}-\x{64a}\x{66d}-\x{66f}\x{671}-\x{6d5}\x{6e5}\x{6e6}\x{6ee}\x{6ef}\x{6fa}-\x{710}\x{712}-\x{72f}\x{74b}-\x{7a5}\x{7b1}-\x{7bf}\x{8a0}-\x{8e2}\x{fb50}-\x{fd3d}\x{fd40}-\x{fdcf}\x{fdf0}-\x{fdfc}\x{fdfe}\x{fdff}\x{fe70}-\x{fefe}\x{1ee00}-\x{1eeef}\x{1eef2}-\x{1eeff}]))/u';
171  // @codeCoverageIgnoreEnd
172  // @codingStandardsIgnoreEnd
173 
179  static function factory( $code ) {
181 
182  if ( isset( $wgDummyLanguageCodes[$code] ) ) {
183  $code = $wgDummyLanguageCodes[$code];
184  }
185 
186  // get the language object to process
187  $langObj = isset( self::$mLangObjCache[$code] )
188  ? self::$mLangObjCache[$code]
189  : self::newFromCode( $code );
190 
191  // merge the language object in to get it up front in the cache
192  self::$mLangObjCache = array_merge( [ $code => $langObj ], self::$mLangObjCache );
193  // get rid of the oldest ones in case we have an overflow
194  self::$mLangObjCache = array_slice( self::$mLangObjCache, 0, $wgLangObjCacheSize, true );
195 
196  return $langObj;
197  }
198 
205  protected static function newFromCode( $code ) {
206  if ( !Language::isValidCode( $code ) ) {
207  throw new MWException( "Invalid language code \"$code\"" );
208  }
209 
211  // It's not possible to customise this code with class files, so
212  // just return a Language object. This is to support uselang= hacks.
213  $lang = new Language;
214  $lang->setCode( $code );
215  return $lang;
216  }
217 
218  // Check if there is a language class for the code
219  $class = self::classFromCode( $code );
220  if ( class_exists( $class ) ) {
221  $lang = new $class;
222  return $lang;
223  }
224 
225  // Keep trying the fallback list until we find an existing class
226  $fallbacks = Language::getFallbacksFor( $code );
227  foreach ( $fallbacks as $fallbackCode ) {
228  if ( !Language::isValidBuiltInCode( $fallbackCode ) ) {
229  throw new MWException( "Invalid fallback '$fallbackCode' in fallback sequence for '$code'" );
230  }
231 
232  $class = self::classFromCode( $fallbackCode );
233  if ( class_exists( $class ) ) {
234  $lang = new $class;
235  $lang->setCode( $code );
236  return $lang;
237  }
238  }
239 
240  throw new MWException( "Invalid fallback sequence for language '$code'" );
241  }
242 
251  public static function isSupportedLanguage( $code ) {
252  if ( !self::isValidBuiltInCode( $code ) ) {
253  return false;
254  }
255 
256  if ( $code === 'qqq' ) {
257  return false;
258  }
259 
260  return is_readable( self::getMessagesFileName( $code ) ) ||
261  is_readable( self::getJsonMessagesFileName( $code ) );
262  }
263 
279  public static function isWellFormedLanguageTag( $code, $lenient = false ) {
280  $alpha = '[a-z]';
281  $digit = '[0-9]';
282  $alphanum = '[a-z0-9]';
283  $x = 'x'; # private use singleton
284  $singleton = '[a-wy-z]'; # other singleton
285  $s = $lenient ? '[-_]' : '-';
286 
287  $language = "$alpha{2,8}|$alpha{2,3}$s$alpha{3}";
288  $script = "$alpha{4}"; # ISO 15924
289  $region = "(?:$alpha{2}|$digit{3})"; # ISO 3166-1 alpha-2 or UN M.49
290  $variant = "(?:$alphanum{5,8}|$digit$alphanum{3})";
291  $extension = "$singleton(?:$s$alphanum{2,8})+";
292  $privateUse = "$x(?:$s$alphanum{1,8})+";
293 
294  # Define certain grandfathered codes, since otherwise the regex is pretty useless.
295  # Since these are limited, this is safe even later changes to the registry --
296  # the only oddity is that it might change the type of the tag, and thus
297  # the results from the capturing groups.
298  # http://www.iana.org/assignments/language-subtag-registry
299 
300  $grandfathered = "en{$s}GB{$s}oed"
301  . "|i{$s}(?:ami|bnn|default|enochian|hak|klingon|lux|mingo|navajo|pwn|tao|tay|tsu)"
302  . "|no{$s}(?:bok|nyn)"
303  . "|sgn{$s}(?:BE{$s}(?:fr|nl)|CH{$s}de)"
304  . "|zh{$s}min{$s}nan";
305 
306  $variantList = "$variant(?:$s$variant)*";
307  $extensionList = "$extension(?:$s$extension)*";
308 
309  $langtag = "(?:($language)"
310  . "(?:$s$script)?"
311  . "(?:$s$region)?"
312  . "(?:$s$variantList)?"
313  . "(?:$s$extensionList)?"
314  . "(?:$s$privateUse)?)";
315 
316  # The final breakdown, with capturing groups for each of these components
317  # The variants, extensions, grandfathered, and private-use may have interior '-'
318 
319  $root = "^(?:$langtag|$privateUse|$grandfathered)$";
320 
321  return (bool)preg_match( "/$root/", strtolower( $code ) );
322  }
323 
333  public static function isValidCode( $code ) {
334  static $cache = [];
335  if ( !isset( $cache[$code] ) ) {
336  // People think language codes are html safe, so enforce it.
337  // Ideally we should only allow a-zA-Z0-9-
338  // but, .+ and other chars are often used for {{int:}} hacks
339  // see bugs 37564, 37587, 36938
340  $cache[$code] =
341  // Protect against path traversal
342  strcspn( $code, ":/\\\000&<>'\"" ) === strlen( $code )
343  && !preg_match( MediaWikiTitleCodec::getTitleInvalidRegex(), $code );
344  }
345  return $cache[$code];
346  }
347 
358  public static function isValidBuiltInCode( $code ) {
359 
360  if ( !is_string( $code ) ) {
361  if ( is_object( $code ) ) {
362  $addmsg = " of class " . get_class( $code );
363  } else {
364  $addmsg = '';
365  }
366  $type = gettype( $code );
367  throw new MWException( __METHOD__ . " must be passed a string, $type given$addmsg" );
368  }
369 
370  return (bool)preg_match( '/^[a-z0-9-]{2,}$/', $code );
371  }
372 
381  public static function isKnownLanguageTag( $tag ) {
382  // Quick escape for invalid input to avoid exceptions down the line
383  // when code tries to process tags which are not valid at all.
384  if ( !self::isValidBuiltInCode( $tag ) ) {
385  return false;
386  }
387 
388  if ( isset( MediaWiki\Languages\Data\Names::$names[$tag] )
389  || self::fetchLanguageName( $tag, $tag ) !== ''
390  ) {
391  return true;
392  }
393 
394  return false;
395  }
396 
402  public static function getLocalisationCache() {
403  if ( is_null( self::$dataCache ) ) {
405  $class = $wgLocalisationCacheConf['class'];
406  self::$dataCache = new $class( $wgLocalisationCacheConf );
407  }
408  return self::$dataCache;
409  }
410 
411  function __construct() {
412  $this->mConverter = new FakeConverter( $this );
413  // Set the code to the name of the descendant
414  if ( get_class( $this ) == 'Language' ) {
415  $this->mCode = 'en';
416  } else {
417  $this->mCode = str_replace( '_', '-', strtolower( substr( get_class( $this ), 8 ) ) );
418  }
419  self::getLocalisationCache();
420  }
421 
425  function __destruct() {
426  foreach ( $this as $name => $value ) {
427  unset( $this->$name );
428  }
429  }
430 
435  function initContLang() {
436  }
437 
442  public function getFallbackLanguages() {
443  return self::getFallbacksFor( $this->mCode );
444  }
445 
450  public function getBookstoreList() {
451  return self::$dataCache->getItem( $this->mCode, 'bookstoreList' );
452  }
453 
460  public function getNamespaces() {
461  if ( is_null( $this->namespaceNames ) ) {
463 
464  $this->namespaceNames = self::$dataCache->getItem( $this->mCode, 'namespaceNames' );
465  $validNamespaces = MWNamespace::getCanonicalNamespaces();
466 
467  $this->namespaceNames = $wgExtraNamespaces + $this->namespaceNames + $validNamespaces;
468 
469  $this->namespaceNames[NS_PROJECT] = $wgMetaNamespace;
470  if ( $wgMetaNamespaceTalk ) {
471  $this->namespaceNames[NS_PROJECT_TALK] = $wgMetaNamespaceTalk;
472  } else {
473  $talk = $this->namespaceNames[NS_PROJECT_TALK];
474  $this->namespaceNames[NS_PROJECT_TALK] =
475  $this->fixVariableInNamespace( $talk );
476  }
477 
478  # Sometimes a language will be localised but not actually exist on this wiki.
479  foreach ( $this->namespaceNames as $key => $text ) {
480  if ( !isset( $validNamespaces[$key] ) ) {
481  unset( $this->namespaceNames[$key] );
482  }
483  }
484 
485  # The above mixing may leave namespaces out of canonical order.
486  # Re-order by namespace ID number...
487  ksort( $this->namespaceNames );
488 
489  Hooks::run( 'LanguageGetNamespaces', [ &$this->namespaceNames ] );
490  }
491 
492  return $this->namespaceNames;
493  }
494 
499  public function setNamespaces( array $namespaces ) {
500  $this->namespaceNames = $namespaces;
501  $this->mNamespaceIds = null;
502  }
503 
507  public function resetNamespaces() {
508  $this->namespaceNames = null;
509  $this->mNamespaceIds = null;
510  $this->namespaceAliases = null;
511  }
512 
519  public function getFormattedNamespaces() {
520  $ns = $this->getNamespaces();
521  foreach ( $ns as $k => $v ) {
522  $ns[$k] = strtr( $v, '_', ' ' );
523  }
524  return $ns;
525  }
526 
538  public function getNsText( $index ) {
539  $ns = $this->getNamespaces();
540  return isset( $ns[$index] ) ? $ns[$index] : false;
541  }
542 
556  public function getFormattedNsText( $index ) {
557  $ns = $this->getNsText( $index );
558  return strtr( $ns, '_', ' ' );
559  }
560 
569  public function getGenderNsText( $index, $gender ) {
571 
572  $ns = $wgExtraGenderNamespaces +
573  (array)self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' );
574 
575  return isset( $ns[$index][$gender] ) ? $ns[$index][$gender] : $this->getNsText( $index );
576  }
577 
584  public function needsGenderDistinction() {
586  if ( count( $wgExtraGenderNamespaces ) > 0 ) {
587  // $wgExtraGenderNamespaces overrides everything
588  return true;
589  } elseif ( isset( $wgExtraNamespaces[NS_USER] ) && isset( $wgExtraNamespaces[NS_USER_TALK] ) ) {
591  // $wgExtraNamespaces overrides any gender aliases specified in i18n files
592  return false;
593  } else {
594  // Check what is in i18n files
595  $aliases = self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' );
596  return count( $aliases ) > 0;
597  }
598  }
599 
608  function getLocalNsIndex( $text ) {
609  $lctext = $this->lc( $text );
610  $ids = $this->getNamespaceIds();
611  return isset( $ids[$lctext] ) ? $ids[$lctext] : false;
612  }
613 
617  public function getNamespaceAliases() {
618  if ( is_null( $this->namespaceAliases ) ) {
619  $aliases = self::$dataCache->getItem( $this->mCode, 'namespaceAliases' );
620  if ( !$aliases ) {
621  $aliases = [];
622  } else {
623  foreach ( $aliases as $name => $index ) {
624  if ( $index === NS_PROJECT_TALK ) {
625  unset( $aliases[$name] );
626  $name = $this->fixVariableInNamespace( $name );
627  $aliases[$name] = $index;
628  }
629  }
630  }
631 
633  $genders = $wgExtraGenderNamespaces +
634  (array)self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' );
635  foreach ( $genders as $index => $forms ) {
636  foreach ( $forms as $alias ) {
637  $aliases[$alias] = $index;
638  }
639  }
640 
641  # Also add converted namespace names as aliases, to avoid confusion.
642  $convertedNames = [];
643  foreach ( $this->getVariants() as $variant ) {
644  if ( $variant === $this->mCode ) {
645  continue;
646  }
647  foreach ( $this->getNamespaces() as $ns => $_ ) {
648  $convertedNames[$this->getConverter()->convertNamespace( $ns, $variant )] = $ns;
649  }
650  }
651 
652  $this->namespaceAliases = $aliases + $convertedNames;
653  }
654 
656  }
657 
661  public function getNamespaceIds() {
662  if ( is_null( $this->mNamespaceIds ) ) {
664  # Put namespace names and aliases into a hashtable.
665  # If this is too slow, then we should arrange it so that it is done
666  # before caching. The catch is that at pre-cache time, the above
667  # class-specific fixup hasn't been done.
668  $this->mNamespaceIds = [];
669  foreach ( $this->getNamespaces() as $index => $name ) {
670  $this->mNamespaceIds[$this->lc( $name )] = $index;
671  }
672  foreach ( $this->getNamespaceAliases() as $name => $index ) {
673  $this->mNamespaceIds[$this->lc( $name )] = $index;
674  }
675  if ( $wgNamespaceAliases ) {
676  foreach ( $wgNamespaceAliases as $name => $index ) {
677  $this->mNamespaceIds[$this->lc( $name )] = $index;
678  }
679  }
680  }
681  return $this->mNamespaceIds;
682  }
683 
691  public function getNsIndex( $text ) {
692  $lctext = $this->lc( $text );
693  $ns = MWNamespace::getCanonicalIndex( $lctext );
694  if ( $ns !== null ) {
695  return $ns;
696  }
697  $ids = $this->getNamespaceIds();
698  return isset( $ids[$lctext] ) ? $ids[$lctext] : false;
699  }
700 
708  public function getVariantname( $code, $usemsg = true ) {
709  $msg = "variantname-$code";
710  if ( $usemsg && wfMessage( $msg )->exists() ) {
711  return $this->getMessageFromDB( $msg );
712  }
713  $name = self::fetchLanguageName( $code );
714  if ( $name ) {
715  return $name; # if it's defined as a language name, show that
716  } else {
717  # otherwise, output the language code
718  return $code;
719  }
720  }
721 
725  public function getDatePreferences() {
726  return self::$dataCache->getItem( $this->mCode, 'datePreferences' );
727  }
728 
732  function getDateFormats() {
733  return self::$dataCache->getItem( $this->mCode, 'dateFormats' );
734  }
735 
739  public function getDefaultDateFormat() {
740  $df = self::$dataCache->getItem( $this->mCode, 'defaultDateFormat' );
741  if ( $df === 'dmy or mdy' ) {
742  global $wgAmericanDates;
743  return $wgAmericanDates ? 'mdy' : 'dmy';
744  } else {
745  return $df;
746  }
747  }
748 
752  public function getDatePreferenceMigrationMap() {
753  return self::$dataCache->getItem( $this->mCode, 'datePreferenceMigrationMap' );
754  }
755 
760  function getImageFile( $image ) {
761  return self::$dataCache->getSubitem( $this->mCode, 'imageFiles', $image );
762  }
763 
768  public function getImageFiles() {
769  return self::$dataCache->getItem( $this->mCode, 'imageFiles' );
770  }
771 
775  public function getExtraUserToggles() {
776  return (array)self::$dataCache->getItem( $this->mCode, 'extraUserToggles' );
777  }
778 
783  function getUserToggle( $tog ) {
784  return $this->getMessageFromDB( "tog-$tog" );
785  }
786 
798  public static function fetchLanguageNames( $inLanguage = null, $include = 'mw' ) {
799  $cacheKey = $inLanguage === null ? 'null' : $inLanguage;
800  $cacheKey .= ":$include";
801  if ( self::$languageNameCache === null ) {
802  self::$languageNameCache = new HashBagOStuff( [ 'maxKeys' => 20 ] );
803  }
804 
805  $ret = self::$languageNameCache->get( $cacheKey );
806  if ( !$ret ) {
807  $ret = self::fetchLanguageNamesUncached( $inLanguage, $include );
808  self::$languageNameCache->set( $cacheKey, $ret );
809  }
810  return $ret;
811  }
812 
823  private static function fetchLanguageNamesUncached( $inLanguage = null, $include = 'mw' ) {
824  global $wgExtraLanguageNames;
825 
826  // If passed an invalid language code to use, fallback to en
827  if ( $inLanguage !== null && !Language::isValidCode( $inLanguage ) ) {
828  $inLanguage = 'en';
829  }
830 
831  $names = [];
832 
833  if ( $inLanguage ) {
834  # TODO: also include when $inLanguage is null, when this code is more efficient
835  Hooks::run( 'LanguageGetTranslatedLanguageNames', [ &$names, $inLanguage ] );
836  }
837 
838  $mwNames = $wgExtraLanguageNames + MediaWiki\Languages\Data\Names::$names;
839  foreach ( $mwNames as $mwCode => $mwName ) {
840  # - Prefer own MediaWiki native name when not using the hook
841  # - For other names just add if not added through the hook
842  if ( $mwCode === $inLanguage || !isset( $names[$mwCode] ) ) {
843  $names[$mwCode] = $mwName;
844  }
845  }
846 
847  if ( $include === 'all' ) {
848  ksort( $names );
849  return $names;
850  }
851 
852  $returnMw = [];
853  $coreCodes = array_keys( $mwNames );
854  foreach ( $coreCodes as $coreCode ) {
855  $returnMw[$coreCode] = $names[$coreCode];
856  }
857 
858  if ( $include === 'mwfile' ) {
859  $namesMwFile = [];
860  # We do this using a foreach over the codes instead of a directory
861  # loop so that messages files in extensions will work correctly.
862  foreach ( $returnMw as $code => $value ) {
863  if ( is_readable( self::getMessagesFileName( $code ) )
864  || is_readable( self::getJsonMessagesFileName( $code ) )
865  ) {
866  $namesMwFile[$code] = $names[$code];
867  }
868  }
869 
870  ksort( $namesMwFile );
871  return $namesMwFile;
872  }
873 
874  ksort( $returnMw );
875  # 'mw' option; default if it's not one of the other two options (all/mwfile)
876  return $returnMw;
877  }
878 
886  public static function fetchLanguageName( $code, $inLanguage = null, $include = 'all' ) {
887  $code = strtolower( $code );
888  $array = self::fetchLanguageNames( $inLanguage, $include );
889  return !array_key_exists( $code, $array ) ? '' : $array[$code];
890  }
891 
898  public function getMessageFromDB( $msg ) {
899  return $this->msg( $msg )->text();
900  }
901 
908  protected function msg( $msg ) {
909  return wfMessage( $msg )->inLanguage( $this );
910  }
911 
916  public function getMonthName( $key ) {
917  return $this->getMessageFromDB( self::$mMonthMsgs[$key - 1] );
918  }
919 
923  public function getMonthNamesArray() {
924  $monthNames = [ '' ];
925  for ( $i = 1; $i < 13; $i++ ) {
926  $monthNames[] = $this->getMonthName( $i );
927  }
928  return $monthNames;
929  }
930 
935  public function getMonthNameGen( $key ) {
936  return $this->getMessageFromDB( self::$mMonthGenMsgs[$key - 1] );
937  }
938 
943  public function getMonthAbbreviation( $key ) {
944  return $this->getMessageFromDB( self::$mMonthAbbrevMsgs[$key - 1] );
945  }
946 
950  public function getMonthAbbreviationsArray() {
951  $monthNames = [ '' ];
952  for ( $i = 1; $i < 13; $i++ ) {
953  $monthNames[] = $this->getMonthAbbreviation( $i );
954  }
955  return $monthNames;
956  }
957 
962  public function getWeekdayName( $key ) {
963  return $this->getMessageFromDB( self::$mWeekdayMsgs[$key - 1] );
964  }
965 
970  function getWeekdayAbbreviation( $key ) {
971  return $this->getMessageFromDB( self::$mWeekdayAbbrevMsgs[$key - 1] );
972  }
973 
978  function getIranianCalendarMonthName( $key ) {
979  return $this->getMessageFromDB( self::$mIranianCalendarMonthMsgs[$key - 1] );
980  }
981 
986  function getHebrewCalendarMonthName( $key ) {
987  return $this->getMessageFromDB( self::$mHebrewCalendarMonthMsgs[$key - 1] );
988  }
989 
994  function getHebrewCalendarMonthNameGen( $key ) {
995  return $this->getMessageFromDB( self::$mHebrewCalendarMonthGenMsgs[$key - 1] );
996  }
997 
1002  function getHijriCalendarMonthName( $key ) {
1003  return $this->getMessageFromDB( self::$mHijriCalendarMonthMsgs[$key - 1] );
1004  }
1005 
1014  private static function dateTimeObjFormat( &$dateTimeObj, $ts, $zone, $code ) {
1015  if ( !$dateTimeObj ) {
1016  $dateTimeObj = DateTime::createFromFormat(
1017  'YmdHis', $ts, $zone ?: new DateTimeZone( 'UTC' )
1018  );
1019  }
1020  return $dateTimeObj->format( $code );
1021  }
1022 
1090  public function sprintfDate( $format, $ts, DateTimeZone $zone = null, &$ttl = 'unused' ) {
1091  $s = '';
1092  $raw = false;
1093  $roman = false;
1094  $hebrewNum = false;
1095  $dateTimeObj = false;
1096  $rawToggle = false;
1097  $iranian = false;
1098  $hebrew = false;
1099  $hijri = false;
1100  $thai = false;
1101  $minguo = false;
1102  $tenno = false;
1103 
1104  $usedSecond = false;
1105  $usedMinute = false;
1106  $usedHour = false;
1107  $usedAMPM = false;
1108  $usedDay = false;
1109  $usedWeek = false;
1110  $usedMonth = false;
1111  $usedYear = false;
1112  $usedISOYear = false;
1113  $usedIsLeapYear = false;
1114 
1115  $usedHebrewMonth = false;
1116  $usedIranianMonth = false;
1117  $usedHijriMonth = false;
1118  $usedHebrewYear = false;
1119  $usedIranianYear = false;
1120  $usedHijriYear = false;
1121  $usedTennoYear = false;
1122 
1123  if ( strlen( $ts ) !== 14 ) {
1124  throw new MWException( __METHOD__ . ": The timestamp $ts should have 14 characters" );
1125  }
1126 
1127  if ( !ctype_digit( $ts ) ) {
1128  throw new MWException( __METHOD__ . ": The timestamp $ts should be a number" );
1129  }
1130 
1131  $formatLength = strlen( $format );
1132  for ( $p = 0; $p < $formatLength; $p++ ) {
1133  $num = false;
1134  $code = $format[$p];
1135  if ( $code == 'x' && $p < $formatLength - 1 ) {
1136  $code .= $format[++$p];
1137  }
1138 
1139  if ( ( $code === 'xi'
1140  || $code === 'xj'
1141  || $code === 'xk'
1142  || $code === 'xm'
1143  || $code === 'xo'
1144  || $code === 'xt' )
1145  && $p < $formatLength - 1 ) {
1146  $code .= $format[++$p];
1147  }
1148 
1149  switch ( $code ) {
1150  case 'xx':
1151  $s .= 'x';
1152  break;
1153  case 'xn':
1154  $raw = true;
1155  break;
1156  case 'xN':
1157  $rawToggle = !$rawToggle;
1158  break;
1159  case 'xr':
1160  $roman = true;
1161  break;
1162  case 'xh':
1163  $hebrewNum = true;
1164  break;
1165  case 'xg':
1166  $usedMonth = true;
1167  $s .= $this->getMonthNameGen( substr( $ts, 4, 2 ) );
1168  break;
1169  case 'xjx':
1170  $usedHebrewMonth = true;
1171  if ( !$hebrew ) {
1172  $hebrew = self::tsToHebrew( $ts );
1173  }
1174  $s .= $this->getHebrewCalendarMonthNameGen( $hebrew[1] );
1175  break;
1176  case 'd':
1177  $usedDay = true;
1178  $num = substr( $ts, 6, 2 );
1179  break;
1180  case 'D':
1181  $usedDay = true;
1182  $s .= $this->getWeekdayAbbreviation(
1183  Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'w' ) + 1
1184  );
1185  break;
1186  case 'j':
1187  $usedDay = true;
1188  $num = intval( substr( $ts, 6, 2 ) );
1189  break;
1190  case 'xij':
1191  $usedDay = true;
1192  if ( !$iranian ) {
1193  $iranian = self::tsToIranian( $ts );
1194  }
1195  $num = $iranian[2];
1196  break;
1197  case 'xmj':
1198  $usedDay = true;
1199  if ( !$hijri ) {
1200  $hijri = self::tsToHijri( $ts );
1201  }
1202  $num = $hijri[2];
1203  break;
1204  case 'xjj':
1205  $usedDay = true;
1206  if ( !$hebrew ) {
1207  $hebrew = self::tsToHebrew( $ts );
1208  }
1209  $num = $hebrew[2];
1210  break;
1211  case 'l':
1212  $usedDay = true;
1213  $s .= $this->getWeekdayName(
1214  Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'w' ) + 1
1215  );
1216  break;
1217  case 'F':
1218  $usedMonth = true;
1219  $s .= $this->getMonthName( substr( $ts, 4, 2 ) );
1220  break;
1221  case 'xiF':
1222  $usedIranianMonth = true;
1223  if ( !$iranian ) {
1224  $iranian = self::tsToIranian( $ts );
1225  }
1226  $s .= $this->getIranianCalendarMonthName( $iranian[1] );
1227  break;
1228  case 'xmF':
1229  $usedHijriMonth = true;
1230  if ( !$hijri ) {
1231  $hijri = self::tsToHijri( $ts );
1232  }
1233  $s .= $this->getHijriCalendarMonthName( $hijri[1] );
1234  break;
1235  case 'xjF':
1236  $usedHebrewMonth = true;
1237  if ( !$hebrew ) {
1238  $hebrew = self::tsToHebrew( $ts );
1239  }
1240  $s .= $this->getHebrewCalendarMonthName( $hebrew[1] );
1241  break;
1242  case 'm':
1243  $usedMonth = true;
1244  $num = substr( $ts, 4, 2 );
1245  break;
1246  case 'M':
1247  $usedMonth = true;
1248  $s .= $this->getMonthAbbreviation( substr( $ts, 4, 2 ) );
1249  break;
1250  case 'n':
1251  $usedMonth = true;
1252  $num = intval( substr( $ts, 4, 2 ) );
1253  break;
1254  case 'xin':
1255  $usedIranianMonth = true;
1256  if ( !$iranian ) {
1257  $iranian = self::tsToIranian( $ts );
1258  }
1259  $num = $iranian[1];
1260  break;
1261  case 'xmn':
1262  $usedHijriMonth = true;
1263  if ( !$hijri ) {
1264  $hijri = self::tsToHijri( $ts );
1265  }
1266  $num = $hijri[1];
1267  break;
1268  case 'xjn':
1269  $usedHebrewMonth = true;
1270  if ( !$hebrew ) {
1271  $hebrew = self::tsToHebrew( $ts );
1272  }
1273  $num = $hebrew[1];
1274  break;
1275  case 'xjt':
1276  $usedHebrewMonth = true;
1277  if ( !$hebrew ) {
1278  $hebrew = self::tsToHebrew( $ts );
1279  }
1280  $num = $hebrew[3];
1281  break;
1282  case 'Y':
1283  $usedYear = true;
1284  $num = substr( $ts, 0, 4 );
1285  break;
1286  case 'xiY':
1287  $usedIranianYear = true;
1288  if ( !$iranian ) {
1289  $iranian = self::tsToIranian( $ts );
1290  }
1291  $num = $iranian[0];
1292  break;
1293  case 'xmY':
1294  $usedHijriYear = true;
1295  if ( !$hijri ) {
1296  $hijri = self::tsToHijri( $ts );
1297  }
1298  $num = $hijri[0];
1299  break;
1300  case 'xjY':
1301  $usedHebrewYear = true;
1302  if ( !$hebrew ) {
1303  $hebrew = self::tsToHebrew( $ts );
1304  }
1305  $num = $hebrew[0];
1306  break;
1307  case 'xkY':
1308  $usedYear = true;
1309  if ( !$thai ) {
1310  $thai = self::tsToYear( $ts, 'thai' );
1311  }
1312  $num = $thai[0];
1313  break;
1314  case 'xoY':
1315  $usedYear = true;
1316  if ( !$minguo ) {
1317  $minguo = self::tsToYear( $ts, 'minguo' );
1318  }
1319  $num = $minguo[0];
1320  break;
1321  case 'xtY':
1322  $usedTennoYear = true;
1323  if ( !$tenno ) {
1324  $tenno = self::tsToYear( $ts, 'tenno' );
1325  }
1326  $num = $tenno[0];
1327  break;
1328  case 'y':
1329  $usedYear = true;
1330  $num = substr( $ts, 2, 2 );
1331  break;
1332  case 'xiy':
1333  $usedIranianYear = true;
1334  if ( !$iranian ) {
1335  $iranian = self::tsToIranian( $ts );
1336  }
1337  $num = substr( $iranian[0], -2 );
1338  break;
1339  case 'a':
1340  $usedAMPM = true;
1341  $s .= intval( substr( $ts, 8, 2 ) ) < 12 ? 'am' : 'pm';
1342  break;
1343  case 'A':
1344  $usedAMPM = true;
1345  $s .= intval( substr( $ts, 8, 2 ) ) < 12 ? 'AM' : 'PM';
1346  break;
1347  case 'g':
1348  $usedHour = true;
1349  $h = substr( $ts, 8, 2 );
1350  $num = $h % 12 ? $h % 12 : 12;
1351  break;
1352  case 'G':
1353  $usedHour = true;
1354  $num = intval( substr( $ts, 8, 2 ) );
1355  break;
1356  case 'h':
1357  $usedHour = true;
1358  $h = substr( $ts, 8, 2 );
1359  $num = sprintf( '%02d', $h % 12 ? $h % 12 : 12 );
1360  break;
1361  case 'H':
1362  $usedHour = true;
1363  $num = substr( $ts, 8, 2 );
1364  break;
1365  case 'i':
1366  $usedMinute = true;
1367  $num = substr( $ts, 10, 2 );
1368  break;
1369  case 's':
1370  $usedSecond = true;
1371  $num = substr( $ts, 12, 2 );
1372  break;
1373  case 'c':
1374  case 'r':
1375  $usedSecond = true;
1376  // fall through
1377  case 'e':
1378  case 'O':
1379  case 'P':
1380  case 'T':
1381  $s .= Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1382  break;
1383  case 'w':
1384  case 'N':
1385  case 'z':
1386  $usedDay = true;
1387  $num = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1388  break;
1389  case 'W':
1390  $usedWeek = true;
1391  $num = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1392  break;
1393  case 't':
1394  $usedMonth = true;
1395  $num = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1396  break;
1397  case 'L':
1398  $usedIsLeapYear = true;
1399  $num = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1400  break;
1401  case 'o':
1402  $usedISOYear = true;
1403  $num = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1404  break;
1405  case 'U':
1406  $usedSecond = true;
1407  // fall through
1408  case 'I':
1409  case 'Z':
1410  $num = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1411  break;
1412  case '\\':
1413  # Backslash escaping
1414  if ( $p < $formatLength - 1 ) {
1415  $s .= $format[++$p];
1416  } else {
1417  $s .= '\\';
1418  }
1419  break;
1420  case '"':
1421  # Quoted literal
1422  if ( $p < $formatLength - 1 ) {
1423  $endQuote = strpos( $format, '"', $p + 1 );
1424  if ( $endQuote === false ) {
1425  # No terminating quote, assume literal "
1426  $s .= '"';
1427  } else {
1428  $s .= substr( $format, $p + 1, $endQuote - $p - 1 );
1429  $p = $endQuote;
1430  }
1431  } else {
1432  # Quote at end of string, assume literal "
1433  $s .= '"';
1434  }
1435  break;
1436  default:
1437  $s .= $format[$p];
1438  }
1439  if ( $num !== false ) {
1440  if ( $rawToggle || $raw ) {
1441  $s .= $num;
1442  $raw = false;
1443  } elseif ( $roman ) {
1444  $s .= Language::romanNumeral( $num );
1445  $roman = false;
1446  } elseif ( $hebrewNum ) {
1447  $s .= self::hebrewNumeral( $num );
1448  $hebrewNum = false;
1449  } else {
1450  $s .= $this->formatNum( $num, true );
1451  }
1452  }
1453  }
1454 
1455  if ( $ttl === 'unused' ) {
1456  // No need to calculate the TTL, the caller wont use it anyway.
1457  } elseif ( $usedSecond ) {
1458  $ttl = 1;
1459  } elseif ( $usedMinute ) {
1460  $ttl = 60 - substr( $ts, 12, 2 );
1461  } elseif ( $usedHour ) {
1462  $ttl = 3600 - substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
1463  } elseif ( $usedAMPM ) {
1464  $ttl = 43200 - ( substr( $ts, 8, 2 ) % 12 ) * 3600 -
1465  substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
1466  } elseif (
1467  $usedDay ||
1468  $usedHebrewMonth ||
1469  $usedIranianMonth ||
1470  $usedHijriMonth ||
1471  $usedHebrewYear ||
1472  $usedIranianYear ||
1473  $usedHijriYear ||
1474  $usedTennoYear
1475  ) {
1476  // @todo Someone who understands the non-Gregorian calendars
1477  // should write proper logic for them so that they don't need purged every day.
1478  $ttl = 86400 - substr( $ts, 8, 2 ) * 3600 -
1479  substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
1480  } else {
1481  $possibleTtls = [];
1482  $timeRemainingInDay = 86400 - substr( $ts, 8, 2 ) * 3600 -
1483  substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
1484  if ( $usedWeek ) {
1485  $possibleTtls[] =
1486  ( 7 - Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'N' ) ) * 86400 +
1487  $timeRemainingInDay;
1488  } elseif ( $usedISOYear ) {
1489  // December 28th falls on the last ISO week of the year, every year.
1490  // The last ISO week of a year can be 52 or 53.
1491  $lastWeekOfISOYear = DateTime::createFromFormat(
1492  'Ymd',
1493  substr( $ts, 0, 4 ) . '1228',
1494  $zone ?: new DateTimeZone( 'UTC' )
1495  )->format( 'W' );
1496  $currentISOWeek = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'W' );
1497  $weeksRemaining = $lastWeekOfISOYear - $currentISOWeek;
1498  $timeRemainingInWeek =
1499  ( 7 - Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'N' ) ) * 86400
1500  + $timeRemainingInDay;
1501  $possibleTtls[] = $weeksRemaining * 604800 + $timeRemainingInWeek;
1502  }
1503 
1504  if ( $usedMonth ) {
1505  $possibleTtls[] =
1506  ( Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 't' ) -
1507  substr( $ts, 6, 2 ) ) * 86400
1508  + $timeRemainingInDay;
1509  } elseif ( $usedYear ) {
1510  $possibleTtls[] =
1511  ( Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'L' ) + 364 -
1512  Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'z' ) ) * 86400
1513  + $timeRemainingInDay;
1514  } elseif ( $usedIsLeapYear ) {
1515  $year = substr( $ts, 0, 4 );
1516  $timeRemainingInYear =
1517  ( Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'L' ) + 364 -
1518  Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'z' ) ) * 86400
1519  + $timeRemainingInDay;
1520  $mod = $year % 4;
1521  if ( $mod || ( !( $year % 100 ) && $year % 400 ) ) {
1522  // this isn't a leap year. see when the next one starts
1523  $nextCandidate = $year - $mod + 4;
1524  if ( $nextCandidate % 100 || !( $nextCandidate % 400 ) ) {
1525  $possibleTtls[] = ( $nextCandidate - $year - 1 ) * 365 * 86400 +
1526  $timeRemainingInYear;
1527  } else {
1528  $possibleTtls[] = ( $nextCandidate - $year + 3 ) * 365 * 86400 +
1529  $timeRemainingInYear;
1530  }
1531  } else {
1532  // this is a leap year, so the next year isn't
1533  $possibleTtls[] = $timeRemainingInYear;
1534  }
1535  }
1536 
1537  if ( $possibleTtls ) {
1538  $ttl = min( $possibleTtls );
1539  }
1540  }
1541 
1542  return $s;
1543  }
1544 
1545  private static $GREG_DAYS = [ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ];
1546  private static $IRANIAN_DAYS = [ 31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29 ];
1547 
1560  private static function tsToIranian( $ts ) {
1561  $gy = substr( $ts, 0, 4 ) -1600;
1562  $gm = substr( $ts, 4, 2 ) -1;
1563  $gd = substr( $ts, 6, 2 ) -1;
1564 
1565  # Days passed from the beginning (including leap years)
1566  $gDayNo = 365 * $gy
1567  + floor( ( $gy + 3 ) / 4 )
1568  - floor( ( $gy + 99 ) / 100 )
1569  + floor( ( $gy + 399 ) / 400 );
1570 
1571  // Add days of the past months of this year
1572  for ( $i = 0; $i < $gm; $i++ ) {
1573  $gDayNo += self::$GREG_DAYS[$i];
1574  }
1575 
1576  // Leap years
1577  if ( $gm > 1 && ( ( $gy % 4 === 0 && $gy % 100 !== 0 || ( $gy % 400 == 0 ) ) ) ) {
1578  $gDayNo++;
1579  }
1580 
1581  // Days passed in current month
1582  $gDayNo += (int)$gd;
1583 
1584  $jDayNo = $gDayNo - 79;
1585 
1586  $jNp = floor( $jDayNo / 12053 );
1587  $jDayNo %= 12053;
1588 
1589  $jy = 979 + 33 * $jNp + 4 * floor( $jDayNo / 1461 );
1590  $jDayNo %= 1461;
1591 
1592  if ( $jDayNo >= 366 ) {
1593  $jy += floor( ( $jDayNo - 1 ) / 365 );
1594  $jDayNo = floor( ( $jDayNo - 1 ) % 365 );
1595  }
1596 
1597  for ( $i = 0; $i < 11 && $jDayNo >= self::$IRANIAN_DAYS[$i]; $i++ ) {
1598  $jDayNo -= self::$IRANIAN_DAYS[$i];
1599  }
1600 
1601  $jm = $i + 1;
1602  $jd = $jDayNo + 1;
1603 
1604  return [ $jy, $jm, $jd ];
1605  }
1606 
1618  private static function tsToHijri( $ts ) {
1619  $year = substr( $ts, 0, 4 );
1620  $month = substr( $ts, 4, 2 );
1621  $day = substr( $ts, 6, 2 );
1622 
1623  $zyr = $year;
1624  $zd = $day;
1625  $zm = $month;
1626  $zy = $zyr;
1627 
1628  if (
1629  ( $zy > 1582 ) || ( ( $zy == 1582 ) && ( $zm > 10 ) ) ||
1630  ( ( $zy == 1582 ) && ( $zm == 10 ) && ( $zd > 14 ) )
1631  ) {
1632  $zjd = (int)( ( 1461 * ( $zy + 4800 + (int)( ( $zm - 14 ) / 12 ) ) ) / 4 ) +
1633  (int)( ( 367 * ( $zm - 2 - 12 * ( (int)( ( $zm - 14 ) / 12 ) ) ) ) / 12 ) -
1634  (int)( ( 3 * (int)( ( ( $zy + 4900 + (int)( ( $zm - 14 ) / 12 ) ) / 100 ) ) ) / 4 ) +
1635  $zd - 32075;
1636  } else {
1637  $zjd = 367 * $zy - (int)( ( 7 * ( $zy + 5001 + (int)( ( $zm - 9 ) / 7 ) ) ) / 4 ) +
1638  (int)( ( 275 * $zm ) / 9 ) + $zd + 1729777;
1639  }
1640 
1641  $zl = $zjd -1948440 + 10632;
1642  $zn = (int)( ( $zl - 1 ) / 10631 );
1643  $zl = $zl - 10631 * $zn + 354;
1644  $zj = ( (int)( ( 10985 - $zl ) / 5316 ) ) * ( (int)( ( 50 * $zl ) / 17719 ) ) +
1645  ( (int)( $zl / 5670 ) ) * ( (int)( ( 43 * $zl ) / 15238 ) );
1646  $zl = $zl - ( (int)( ( 30 - $zj ) / 15 ) ) * ( (int)( ( 17719 * $zj ) / 50 ) ) -
1647  ( (int)( $zj / 16 ) ) * ( (int)( ( 15238 * $zj ) / 43 ) ) + 29;
1648  $zm = (int)( ( 24 * $zl ) / 709 );
1649  $zd = $zl - (int)( ( 709 * $zm ) / 24 );
1650  $zy = 30 * $zn + $zj - 30;
1651 
1652  return [ $zy, $zm, $zd ];
1653  }
1654 
1670  private static function tsToHebrew( $ts ) {
1671  # Parse date
1672  $year = substr( $ts, 0, 4 );
1673  $month = substr( $ts, 4, 2 );
1674  $day = substr( $ts, 6, 2 );
1675 
1676  # Calculate Hebrew year
1677  $hebrewYear = $year + 3760;
1678 
1679  # Month number when September = 1, August = 12
1680  $month += 4;
1681  if ( $month > 12 ) {
1682  # Next year
1683  $month -= 12;
1684  $year++;
1685  $hebrewYear++;
1686  }
1687 
1688  # Calculate day of year from 1 September
1689  $dayOfYear = $day;
1690  for ( $i = 1; $i < $month; $i++ ) {
1691  if ( $i == 6 ) {
1692  # February
1693  $dayOfYear += 28;
1694  # Check if the year is leap
1695  if ( $year % 400 == 0 || ( $year % 4 == 0 && $year % 100 > 0 ) ) {
1696  $dayOfYear++;
1697  }
1698  } elseif ( $i == 8 || $i == 10 || $i == 1 || $i == 3 ) {
1699  $dayOfYear += 30;
1700  } else {
1701  $dayOfYear += 31;
1702  }
1703  }
1704 
1705  # Calculate the start of the Hebrew year
1706  $start = self::hebrewYearStart( $hebrewYear );
1707 
1708  # Calculate next year's start
1709  if ( $dayOfYear <= $start ) {
1710  # Day is before the start of the year - it is the previous year
1711  # Next year's start
1712  $nextStart = $start;
1713  # Previous year
1714  $year--;
1715  $hebrewYear--;
1716  # Add days since previous year's 1 September
1717  $dayOfYear += 365;
1718  if ( ( $year % 400 == 0 ) || ( $year % 100 != 0 && $year % 4 == 0 ) ) {
1719  # Leap year
1720  $dayOfYear++;
1721  }
1722  # Start of the new (previous) year
1723  $start = self::hebrewYearStart( $hebrewYear );
1724  } else {
1725  # Next year's start
1726  $nextStart = self::hebrewYearStart( $hebrewYear + 1 );
1727  }
1728 
1729  # Calculate Hebrew day of year
1730  $hebrewDayOfYear = $dayOfYear - $start;
1731 
1732  # Difference between year's days
1733  $diff = $nextStart - $start;
1734  # Add 12 (or 13 for leap years) days to ignore the difference between
1735  # Hebrew and Gregorian year (353 at least vs. 365/6) - now the
1736  # difference is only about the year type
1737  if ( ( $year % 400 == 0 ) || ( $year % 100 != 0 && $year % 4 == 0 ) ) {
1738  $diff += 13;
1739  } else {
1740  $diff += 12;
1741  }
1742 
1743  # Check the year pattern, and is leap year
1744  # 0 means an incomplete year, 1 means a regular year, 2 means a complete year
1745  # This is mod 30, to work on both leap years (which add 30 days of Adar I)
1746  # and non-leap years
1747  $yearPattern = $diff % 30;
1748  # Check if leap year
1749  $isLeap = $diff >= 30;
1750 
1751  # Calculate day in the month from number of day in the Hebrew year
1752  # Don't check Adar - if the day is not in Adar, we will stop before;
1753  # if it is in Adar, we will use it to check if it is Adar I or Adar II
1754  $hebrewDay = $hebrewDayOfYear;
1755  $hebrewMonth = 1;
1756  $days = 0;
1757  while ( $hebrewMonth <= 12 ) {
1758  # Calculate days in this month
1759  if ( $isLeap && $hebrewMonth == 6 ) {
1760  # Adar in a leap year
1761  if ( $isLeap ) {
1762  # Leap year - has Adar I, with 30 days, and Adar II, with 29 days
1763  $days = 30;
1764  if ( $hebrewDay <= $days ) {
1765  # Day in Adar I
1766  $hebrewMonth = 13;
1767  } else {
1768  # Subtract the days of Adar I
1769  $hebrewDay -= $days;
1770  # Try Adar II
1771  $days = 29;
1772  if ( $hebrewDay <= $days ) {
1773  # Day in Adar II
1774  $hebrewMonth = 14;
1775  }
1776  }
1777  }
1778  } elseif ( $hebrewMonth == 2 && $yearPattern == 2 ) {
1779  # Cheshvan in a complete year (otherwise as the rule below)
1780  $days = 30;
1781  } elseif ( $hebrewMonth == 3 && $yearPattern == 0 ) {
1782  # Kislev in an incomplete year (otherwise as the rule below)
1783  $days = 29;
1784  } else {
1785  # Odd months have 30 days, even have 29
1786  $days = 30 - ( $hebrewMonth - 1 ) % 2;
1787  }
1788  if ( $hebrewDay <= $days ) {
1789  # In the current month
1790  break;
1791  } else {
1792  # Subtract the days of the current month
1793  $hebrewDay -= $days;
1794  # Try in the next month
1795  $hebrewMonth++;
1796  }
1797  }
1798 
1799  return [ $hebrewYear, $hebrewMonth, $hebrewDay, $days ];
1800  }
1801 
1811  private static function hebrewYearStart( $year ) {
1812  $a = intval( ( 12 * ( $year - 1 ) + 17 ) % 19 );
1813  $b = intval( ( $year - 1 ) % 4 );
1814  $m = 32.044093161144 + 1.5542417966212 * $a + $b / 4.0 - 0.0031777940220923 * ( $year - 1 );
1815  if ( $m < 0 ) {
1816  $m--;
1817  }
1818  $Mar = intval( $m );
1819  if ( $m < 0 ) {
1820  $m++;
1821  }
1822  $m -= $Mar;
1823 
1824  $c = intval( ( $Mar + 3 * ( $year - 1 ) + 5 * $b + 5 ) % 7 );
1825  if ( $c == 0 && $a > 11 && $m >= 0.89772376543210 ) {
1826  $Mar++;
1827  } elseif ( $c == 1 && $a > 6 && $m >= 0.63287037037037 ) {
1828  $Mar += 2;
1829  } elseif ( $c == 2 || $c == 4 || $c == 6 ) {
1830  $Mar++;
1831  }
1832 
1833  $Mar += intval( ( $year - 3761 ) / 100 ) - intval( ( $year - 3761 ) / 400 ) - 24;
1834  return $Mar;
1835  }
1836 
1849  private static function tsToYear( $ts, $cName ) {
1850  $gy = substr( $ts, 0, 4 );
1851  $gm = substr( $ts, 4, 2 );
1852  $gd = substr( $ts, 6, 2 );
1853 
1854  if ( !strcmp( $cName, 'thai' ) ) {
1855  # Thai solar dates
1856  # Add 543 years to the Gregorian calendar
1857  # Months and days are identical
1858  $gy_offset = $gy + 543;
1859  } elseif ( ( !strcmp( $cName, 'minguo' ) ) || !strcmp( $cName, 'juche' ) ) {
1860  # Minguo dates
1861  # Deduct 1911 years from the Gregorian calendar
1862  # Months and days are identical
1863  $gy_offset = $gy - 1911;
1864  } elseif ( !strcmp( $cName, 'tenno' ) ) {
1865  # Nengō dates up to Meiji period
1866  # Deduct years from the Gregorian calendar
1867  # depending on the nengo periods
1868  # Months and days are identical
1869  if ( ( $gy < 1912 )
1870  || ( ( $gy == 1912 ) && ( $gm < 7 ) )
1871  || ( ( $gy == 1912 ) && ( $gm == 7 ) && ( $gd < 31 ) )
1872  ) {
1873  # Meiji period
1874  $gy_gannen = $gy - 1868 + 1;
1875  $gy_offset = $gy_gannen;
1876  if ( $gy_gannen == 1 ) {
1877  $gy_offset = '元';
1878  }
1879  $gy_offset = '明治' . $gy_offset;
1880  } elseif (
1881  ( ( $gy == 1912 ) && ( $gm == 7 ) && ( $gd == 31 ) ) ||
1882  ( ( $gy == 1912 ) && ( $gm >= 8 ) ) ||
1883  ( ( $gy > 1912 ) && ( $gy < 1926 ) ) ||
1884  ( ( $gy == 1926 ) && ( $gm < 12 ) ) ||
1885  ( ( $gy == 1926 ) && ( $gm == 12 ) && ( $gd < 26 ) )
1886  ) {
1887  # Taishō period
1888  $gy_gannen = $gy - 1912 + 1;
1889  $gy_offset = $gy_gannen;
1890  if ( $gy_gannen == 1 ) {
1891  $gy_offset = '元';
1892  }
1893  $gy_offset = '大正' . $gy_offset;
1894  } elseif (
1895  ( ( $gy == 1926 ) && ( $gm == 12 ) && ( $gd >= 26 ) ) ||
1896  ( ( $gy > 1926 ) && ( $gy < 1989 ) ) ||
1897  ( ( $gy == 1989 ) && ( $gm == 1 ) && ( $gd < 8 ) )
1898  ) {
1899  # Shōwa period
1900  $gy_gannen = $gy - 1926 + 1;
1901  $gy_offset = $gy_gannen;
1902  if ( $gy_gannen == 1 ) {
1903  $gy_offset = '元';
1904  }
1905  $gy_offset = '昭和' . $gy_offset;
1906  } else {
1907  # Heisei period
1908  $gy_gannen = $gy - 1989 + 1;
1909  $gy_offset = $gy_gannen;
1910  if ( $gy_gannen == 1 ) {
1911  $gy_offset = '元';
1912  }
1913  $gy_offset = '平成' . $gy_offset;
1914  }
1915  } else {
1916  $gy_offset = $gy;
1917  }
1918 
1919  return [ $gy_offset, $gm, $gd ];
1920  }
1921 
1935  private static function strongDirFromContent( $text = '' ) {
1936  if ( !preg_match( self::$strongDirRegex, $text, $matches ) ) {
1937  return null;
1938  }
1939  if ( $matches[1] === '' ) {
1940  return 'rtl';
1941  }
1942  return 'ltr';
1943  }
1944 
1952  static function romanNumeral( $num ) {
1953  static $table = [
1954  [ '', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X' ],
1955  [ '', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC', 'C' ],
1956  [ '', 'C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM', 'M' ],
1957  [ '', 'M', 'MM', 'MMM', 'MMMM', 'MMMMM', 'MMMMMM', 'MMMMMMM',
1958  'MMMMMMMM', 'MMMMMMMMM', 'MMMMMMMMMM' ]
1959  ];
1960 
1961  $num = intval( $num );
1962  if ( $num > 10000 || $num <= 0 ) {
1963  return $num;
1964  }
1965 
1966  $s = '';
1967  for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) {
1968  if ( $num >= $pow10 ) {
1969  $s .= $table[$i][(int)floor( $num / $pow10 )];
1970  }
1971  $num = $num % $pow10;
1972  }
1973  return $s;
1974  }
1975 
1983  static function hebrewNumeral( $num ) {
1984  static $table = [
1985  [ '', 'א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ז', 'ח', 'ט', 'י' ],
1986  [ '', 'י', 'כ', 'ל', 'מ', 'נ', 'ס', 'ע', 'פ', 'צ', 'ק' ],
1987  [ '',
1988  [ 'ק' ],
1989  [ 'ר' ],
1990  [ 'ש' ],
1991  [ 'ת' ],
1992  [ 'ת', 'ק' ],
1993  [ 'ת', 'ר' ],
1994  [ 'ת', 'ש' ],
1995  [ 'ת', 'ת' ],
1996  [ 'ת', 'ת', 'ק' ],
1997  [ 'ת', 'ת', 'ר' ],
1998  ],
1999  [ '', 'א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ז', 'ח', 'ט', 'י' ]
2000  ];
2001 
2002  $num = intval( $num );
2003  if ( $num > 9999 || $num <= 0 ) {
2004  return $num;
2005  }
2006 
2007  // Round thousands have special notations
2008  if ( $num === 1000 ) {
2009  return "א' אלף";
2010  } elseif ( $num % 1000 === 0 ) {
2011  return $table[0][$num / 1000] . "' אלפים";
2012  }
2013 
2014  $letters = [];
2015 
2016  for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) {
2017  if ( $num >= $pow10 ) {
2018  if ( $num === 15 || $num === 16 ) {
2019  $letters[] = $table[0][9];
2020  $letters[] = $table[0][$num - 9];
2021  $num = 0;
2022  } else {
2023  $letters = array_merge(
2024  $letters,
2025  (array)$table[$i][intval( $num / $pow10 )]
2026  );
2027 
2028  if ( $pow10 === 1000 ) {
2029  $letters[] = "'";
2030  }
2031  }
2032  }
2033 
2034  $num = $num % $pow10;
2035  }
2036 
2037  $preTransformLength = count( $letters );
2038  if ( $preTransformLength === 1 ) {
2039  // Add geresh (single quote) to one-letter numbers
2040  $letters[] = "'";
2041  } else {
2042  $lastIndex = $preTransformLength - 1;
2043  $letters[$lastIndex] = str_replace(
2044  [ 'כ', 'מ', 'נ', 'פ', 'צ' ],
2045  [ 'ך', 'ם', 'ן', 'ף', 'ץ' ],
2046  $letters[$lastIndex]
2047  );
2048 
2049  // Add gershayim (double quote) to multiple-letter numbers,
2050  // but exclude numbers with only one letter after the thousands
2051  // (1001-1009, 1020, 1030, 2001-2009, etc.)
2052  if ( $letters[1] === "'" && $preTransformLength === 3 ) {
2053  $letters[] = "'";
2054  } else {
2055  array_splice( $letters, -1, 0, '"' );
2056  }
2057  }
2058 
2059  return implode( $letters );
2060  }
2061 
2070  public function userAdjust( $ts, $tz = false ) {
2072 
2073  if ( $tz === false ) {
2074  $tz = $wgUser->getOption( 'timecorrection' );
2075  }
2076 
2077  $data = explode( '|', $tz, 3 );
2078 
2079  if ( $data[0] == 'ZoneInfo' ) {
2080  MediaWiki\suppressWarnings();
2081  $userTZ = timezone_open( $data[2] );
2082  MediaWiki\restoreWarnings();
2083  if ( $userTZ !== false ) {
2084  $date = date_create( $ts, timezone_open( 'UTC' ) );
2085  date_timezone_set( $date, $userTZ );
2086  $date = date_format( $date, 'YmdHis' );
2087  return $date;
2088  }
2089  # Unrecognized timezone, default to 'Offset' with the stored offset.
2090  $data[0] = 'Offset';
2091  }
2092 
2093  if ( $data[0] == 'System' || $tz == '' ) {
2094  # Global offset in minutes.
2095  $minDiff = $wgLocalTZoffset;
2096  } elseif ( $data[0] == 'Offset' ) {
2097  $minDiff = intval( $data[1] );
2098  } else {
2099  $data = explode( ':', $tz );
2100  if ( count( $data ) == 2 ) {
2101  $data[0] = intval( $data[0] );
2102  $data[1] = intval( $data[1] );
2103  $minDiff = abs( $data[0] ) * 60 + $data[1];
2104  if ( $data[0] < 0 ) {
2105  $minDiff = -$minDiff;
2106  }
2107  } else {
2108  $minDiff = intval( $data[0] ) * 60;
2109  }
2110  }
2111 
2112  # No difference ? Return time unchanged
2113  if ( 0 == $minDiff ) {
2114  return $ts;
2115  }
2116 
2117  MediaWiki\suppressWarnings(); // E_STRICT system time bitching
2118  # Generate an adjusted date; take advantage of the fact that mktime
2119  # will normalize out-of-range values so we don't have to split $minDiff
2120  # into hours and minutes.
2121  $t = mktime( (
2122  (int)substr( $ts, 8, 2 ) ), # Hours
2123  (int)substr( $ts, 10, 2 ) + $minDiff, # Minutes
2124  (int)substr( $ts, 12, 2 ), # Seconds
2125  (int)substr( $ts, 4, 2 ), # Month
2126  (int)substr( $ts, 6, 2 ), # Day
2127  (int)substr( $ts, 0, 4 ) ); # Year
2128 
2129  $date = date( 'YmdHis', $t );
2130  MediaWiki\restoreWarnings();
2131 
2132  return $date;
2133  }
2134 
2152  function dateFormat( $usePrefs = true ) {
2153  global $wgUser;
2154 
2155  if ( is_bool( $usePrefs ) ) {
2156  if ( $usePrefs ) {
2157  $datePreference = $wgUser->getDatePreference();
2158  } else {
2159  $datePreference = (string)User::getDefaultOption( 'date' );
2160  }
2161  } else {
2162  $datePreference = (string)$usePrefs;
2163  }
2164 
2165  // return int
2166  if ( $datePreference == '' ) {
2167  return 'default';
2168  }
2169 
2170  return $datePreference;
2171  }
2172 
2183  function getDateFormatString( $type, $pref ) {
2184  $wasDefault = false;
2185  if ( $pref == 'default' ) {
2186  $wasDefault = true;
2187  $pref = $this->getDefaultDateFormat();
2188  }
2189 
2190  if ( !isset( $this->dateFormatStrings[$type][$pref] ) ) {
2191  $df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" );
2192 
2193  if ( $type === 'pretty' && $df === null ) {
2194  $df = $this->getDateFormatString( 'date', $pref );
2195  }
2196 
2197  if ( !$wasDefault && $df === null ) {
2198  $pref = $this->getDefaultDateFormat();
2199  $df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" );
2200  }
2201 
2202  $this->dateFormatStrings[$type][$pref] = $df;
2203  }
2204  return $this->dateFormatStrings[$type][$pref];
2205  }
2206 
2217  public function date( $ts, $adj = false, $format = true, $timecorrection = false ) {
2218  $ts = wfTimestamp( TS_MW, $ts );
2219  if ( $adj ) {
2220  $ts = $this->userAdjust( $ts, $timecorrection );
2221  }
2222  $df = $this->getDateFormatString( 'date', $this->dateFormat( $format ) );
2223  return $this->sprintfDate( $df, $ts );
2224  }
2225 
2236  public function time( $ts, $adj = false, $format = true, $timecorrection = false ) {
2237  $ts = wfTimestamp( TS_MW, $ts );
2238  if ( $adj ) {
2239  $ts = $this->userAdjust( $ts, $timecorrection );
2240  }
2241  $df = $this->getDateFormatString( 'time', $this->dateFormat( $format ) );
2242  return $this->sprintfDate( $df, $ts );
2243  }
2244 
2256  public function timeanddate( $ts, $adj = false, $format = true, $timecorrection = false ) {
2257  $ts = wfTimestamp( TS_MW, $ts );
2258  if ( $adj ) {
2259  $ts = $this->userAdjust( $ts, $timecorrection );
2260  }
2261  $df = $this->getDateFormatString( 'both', $this->dateFormat( $format ) );
2262  return $this->sprintfDate( $df, $ts );
2263  }
2264 
2275  public function formatDuration( $seconds, array $chosenIntervals = [] ) {
2276  $intervals = $this->getDurationIntervals( $seconds, $chosenIntervals );
2277 
2278  $segments = [];
2279 
2280  foreach ( $intervals as $intervalName => $intervalValue ) {
2281  // Messages: duration-seconds, duration-minutes, duration-hours, duration-days, duration-weeks,
2282  // duration-years, duration-decades, duration-centuries, duration-millennia
2283  $message = wfMessage( 'duration-' . $intervalName )->numParams( $intervalValue );
2284  $segments[] = $message->inLanguage( $this )->escaped();
2285  }
2286 
2287  return $this->listToText( $segments );
2288  }
2289 
2301  public function getDurationIntervals( $seconds, array $chosenIntervals = [] ) {
2302  if ( empty( $chosenIntervals ) ) {
2303  $chosenIntervals = [
2304  'millennia',
2305  'centuries',
2306  'decades',
2307  'years',
2308  'days',
2309  'hours',
2310  'minutes',
2311  'seconds'
2312  ];
2313  }
2314 
2315  $intervals = array_intersect_key( self::$durationIntervals, array_flip( $chosenIntervals ) );
2316  $sortedNames = array_keys( $intervals );
2317  $smallestInterval = array_pop( $sortedNames );
2318 
2319  $segments = [];
2320 
2321  foreach ( $intervals as $name => $length ) {
2322  $value = floor( $seconds / $length );
2323 
2324  if ( $value > 0 || ( $name == $smallestInterval && empty( $segments ) ) ) {
2325  $seconds -= $value * $length;
2326  $segments[$name] = $value;
2327  }
2328  }
2329 
2330  return $segments;
2331  }
2332 
2352  private function internalUserTimeAndDate( $type, $ts, User $user, array $options ) {
2353  $ts = wfTimestamp( TS_MW, $ts );
2354  $options += [ 'timecorrection' => true, 'format' => true ];
2355  if ( $options['timecorrection'] !== false ) {
2356  if ( $options['timecorrection'] === true ) {
2357  $offset = $user->getOption( 'timecorrection' );
2358  } else {
2359  $offset = $options['timecorrection'];
2360  }
2361  $ts = $this->userAdjust( $ts, $offset );
2362  }
2363  if ( $options['format'] === true ) {
2364  $format = $user->getDatePreference();
2365  } else {
2366  $format = $options['format'];
2367  }
2368  $df = $this->getDateFormatString( $type, $this->dateFormat( $format ) );
2369  return $this->sprintfDate( $df, $ts );
2370  }
2371 
2391  public function userDate( $ts, User $user, array $options = [] ) {
2392  return $this->internalUserTimeAndDate( 'date', $ts, $user, $options );
2393  }
2394 
2414  public function userTime( $ts, User $user, array $options = [] ) {
2415  return $this->internalUserTimeAndDate( 'time', $ts, $user, $options );
2416  }
2417 
2437  public function userTimeAndDate( $ts, User $user, array $options = [] ) {
2438  return $this->internalUserTimeAndDate( 'both', $ts, $user, $options );
2439  }
2440 
2456  public function getHumanTimestamp(
2457  MWTimestamp $time, MWTimestamp $relativeTo = null, User $user = null
2458  ) {
2459  if ( $relativeTo === null ) {
2460  $relativeTo = new MWTimestamp();
2461  }
2462  if ( $user === null ) {
2463  $user = RequestContext::getMain()->getUser();
2464  }
2465 
2466  // Adjust for the user's timezone.
2467  $offsetThis = $time->offsetForUser( $user );
2468  $offsetRel = $relativeTo->offsetForUser( $user );
2469 
2470  $ts = '';
2471  if ( Hooks::run( 'GetHumanTimestamp', [ &$ts, $time, $relativeTo, $user, $this ] ) ) {
2472  $ts = $this->getHumanTimestampInternal( $time, $relativeTo, $user );
2473  }
2474 
2475  // Reset the timezone on the objects.
2476  $time->timestamp->sub( $offsetThis );
2477  $relativeTo->timestamp->sub( $offsetRel );
2478 
2479  return $ts;
2480  }
2481 
2493  private function getHumanTimestampInternal(
2494  MWTimestamp $ts, MWTimestamp $relativeTo, User $user
2495  ) {
2496  $diff = $ts->diff( $relativeTo );
2497  $diffDay = (bool)( (int)$ts->timestamp->format( 'w' ) -
2498  (int)$relativeTo->timestamp->format( 'w' ) );
2499  $days = $diff->days ?: (int)$diffDay;
2500  if ( $diff->invert || $days > 5
2501  && $ts->timestamp->format( 'Y' ) !== $relativeTo->timestamp->format( 'Y' )
2502  ) {
2503  // Timestamps are in different years: use full timestamp
2504  // Also do full timestamp for future dates
2508  $format = $this->getDateFormatString( 'both', $user->getDatePreference() ?: 'default' );
2509  $ts = $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) );
2510  } elseif ( $days > 5 ) {
2511  // Timestamps are in same year, but more than 5 days ago: show day and month only.
2512  $format = $this->getDateFormatString( 'pretty', $user->getDatePreference() ?: 'default' );
2513  $ts = $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) );
2514  } elseif ( $days > 1 ) {
2515  // Timestamp within the past week: show the day of the week and time
2516  $format = $this->getDateFormatString( 'time', $user->getDatePreference() ?: 'default' );
2517  $weekday = self::$mWeekdayMsgs[$ts->timestamp->format( 'w' )];
2518  // Messages:
2519  // sunday-at, monday-at, tuesday-at, wednesday-at, thursday-at, friday-at, saturday-at
2520  $ts = wfMessage( "$weekday-at" )
2521  ->inLanguage( $this )
2522  ->params( $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) ) )
2523  ->text();
2524  } elseif ( $days == 1 ) {
2525  // Timestamp was yesterday: say 'yesterday' and the time.
2526  $format = $this->getDateFormatString( 'time', $user->getDatePreference() ?: 'default' );
2527  $ts = wfMessage( 'yesterday-at' )
2528  ->inLanguage( $this )
2529  ->params( $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) ) )
2530  ->text();
2531  } elseif ( $diff->h > 1 || $diff->h == 1 && $diff->i > 30 ) {
2532  // Timestamp was today, but more than 90 minutes ago: say 'today' and the time.
2533  $format = $this->getDateFormatString( 'time', $user->getDatePreference() ?: 'default' );
2534  $ts = wfMessage( 'today-at' )
2535  ->inLanguage( $this )
2536  ->params( $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) ) )
2537  ->text();
2538 
2539  // From here on in, the timestamp was soon enough ago so that we can simply say
2540  // XX units ago, e.g., "2 hours ago" or "5 minutes ago"
2541  } elseif ( $diff->h == 1 ) {
2542  // Less than 90 minutes, but more than an hour ago.
2543  $ts = wfMessage( 'hours-ago' )->inLanguage( $this )->numParams( 1 )->text();
2544  } elseif ( $diff->i >= 1 ) {
2545  // A few minutes ago.
2546  $ts = wfMessage( 'minutes-ago' )->inLanguage( $this )->numParams( $diff->i )->text();
2547  } elseif ( $diff->s >= 30 ) {
2548  // Less than a minute, but more than 30 sec ago.
2549  $ts = wfMessage( 'seconds-ago' )->inLanguage( $this )->numParams( $diff->s )->text();
2550  } else {
2551  // Less than 30 seconds ago.
2552  $ts = wfMessage( 'just-now' )->text();
2553  }
2554 
2555  return $ts;
2556  }
2557 
2562  public function getMessage( $key ) {
2563  return self::$dataCache->getSubitem( $this->mCode, 'messages', $key );
2564  }
2565 
2569  function getAllMessages() {
2570  return self::$dataCache->getItem( $this->mCode, 'messages' );
2571  }
2572 
2579  public function iconv( $in, $out, $string ) {
2580  # Even with //IGNORE iconv can whine about illegal characters in
2581  # *input* string. We just ignore those too.
2582  # REF: http://bugs.php.net/bug.php?id=37166
2583  # REF: https://phabricator.wikimedia.org/T18885
2584  MediaWiki\suppressWarnings();
2585  $text = iconv( $in, $out . '//IGNORE', $string );
2586  MediaWiki\restoreWarnings();
2587  return $text;
2588  }
2589 
2590  // callback functions for ucwords(), ucwordbreaks()
2591 
2597  return $this->ucfirst( $matches[1] );
2598  }
2599 
2605  return mb_strtoupper( $matches[0] );
2606  }
2607 
2613  return mb_strtoupper( $matches[0] );
2614  }
2615 
2623  public function ucfirst( $str ) {
2624  $o = ord( $str );
2625  if ( $o < 96 ) { // if already uppercase...
2626  return $str;
2627  } elseif ( $o < 128 ) {
2628  return ucfirst( $str ); // use PHP's ucfirst()
2629  } else {
2630  // fall back to more complex logic in case of multibyte strings
2631  return $this->uc( $str, true );
2632  }
2633  }
2634 
2643  public function uc( $str, $first = false ) {
2644  if ( $first ) {
2645  if ( $this->isMultibyte( $str ) ) {
2646  return mb_strtoupper( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
2647  } else {
2648  return ucfirst( $str );
2649  }
2650  } else {
2651  return $this->isMultibyte( $str ) ? mb_strtoupper( $str ) : strtoupper( $str );
2652  }
2653  }
2654 
2659  function lcfirst( $str ) {
2660  $o = ord( $str );
2661  if ( !$o ) {
2662  return strval( $str );
2663  } elseif ( $o >= 128 ) {
2664  return $this->lc( $str, true );
2665  } elseif ( $o > 96 ) {
2666  return $str;
2667  } else {
2668  $str[0] = strtolower( $str[0] );
2669  return $str;
2670  }
2671  }
2672 
2678  function lc( $str, $first = false ) {
2679  if ( $first ) {
2680  if ( $this->isMultibyte( $str ) ) {
2681  return mb_strtolower( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
2682  } else {
2683  return strtolower( substr( $str, 0, 1 ) ) . substr( $str, 1 );
2684  }
2685  } else {
2686  return $this->isMultibyte( $str ) ? mb_strtolower( $str ) : strtolower( $str );
2687  }
2688  }
2689 
2694  function isMultibyte( $str ) {
2695  return strlen( $str ) !== mb_strlen( $str );
2696  }
2697 
2702  function ucwords( $str ) {
2703  if ( $this->isMultibyte( $str ) ) {
2704  $str = $this->lc( $str );
2705 
2706  // regexp to find first letter in each word (i.e. after each space)
2707  $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)| ([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
2708 
2709  // function to use to capitalize a single char
2710  return preg_replace_callback(
2711  $replaceRegexp,
2712  [ $this, 'ucwordsCallbackMB' ],
2713  $str
2714  );
2715  } else {
2716  return ucwords( strtolower( $str ) );
2717  }
2718  }
2719 
2726  function ucwordbreaks( $str ) {
2727  if ( $this->isMultibyte( $str ) ) {
2728  $str = $this->lc( $str );
2729 
2730  // since \b doesn't work for UTF-8, we explicitely define word break chars
2731  $breaks = "[ \-\(\)\}\{\.,\?!]";
2732 
2733  // find first letter after word break
2734  $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)|" .
2735  "$breaks([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
2736 
2737  return preg_replace_callback(
2738  $replaceRegexp,
2739  [ $this, 'ucwordbreaksCallbackMB' ],
2740  $str
2741  );
2742  } else {
2743  return preg_replace_callback(
2744  '/\b([\w\x80-\xff]+)\b/',
2745  [ $this, 'ucwordbreaksCallbackAscii' ],
2746  $str
2747  );
2748  }
2749  }
2750 
2766  function caseFold( $s ) {
2767  return $this->uc( $s );
2768  }
2769 
2775  function checkTitleEncoding( $s ) {
2776  if ( is_array( $s ) ) {
2777  throw new MWException( 'Given array to checkTitleEncoding.' );
2778  }
2779  if ( StringUtils::isUtf8( $s ) ) {
2780  return $s;
2781  }
2782 
2783  return $this->iconv( $this->fallback8bitEncoding(), 'utf-8', $s );
2784  }
2785 
2790  return self::$dataCache->getItem( $this->mCode, 'fallback8bitEncoding' );
2791  }
2792 
2801  function hasWordBreaks() {
2802  return true;
2803  }
2804 
2812  function segmentByWord( $string ) {
2813  return $string;
2814  }
2815 
2823  function normalizeForSearch( $string ) {
2824  return self::convertDoubleWidth( $string );
2825  }
2826 
2835  protected static function convertDoubleWidth( $string ) {
2836  static $full = null;
2837  static $half = null;
2838 
2839  if ( $full === null ) {
2840  $fullWidth = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
2841  $halfWidth = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
2842  $full = str_split( $fullWidth, 3 );
2843  $half = str_split( $halfWidth );
2844  }
2845 
2846  $string = str_replace( $full, $half, $string );
2847  return $string;
2848  }
2849 
2855  protected static function insertSpace( $string, $pattern ) {
2856  $string = preg_replace( $pattern, " $1 ", $string );
2857  $string = preg_replace( '/ +/', ' ', $string );
2858  return $string;
2859  }
2860 
2865  function convertForSearchResult( $termsArray ) {
2866  # some languages, e.g. Chinese, need to do a conversion
2867  # in order for search results to be displayed correctly
2868  return $termsArray;
2869  }
2870 
2877  function firstChar( $s ) {
2878  $matches = [];
2879  preg_match(
2880  '/^([\x00-\x7f]|[\xc0-\xdf][\x80-\xbf]|' .
2881  '[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xf7][\x80-\xbf]{3})/',
2882  $s,
2883  $matches
2884  );
2885 
2886  if ( isset( $matches[1] ) ) {
2887  if ( strlen( $matches[1] ) != 3 ) {
2888  return $matches[1];
2889  }
2890 
2891  // Break down Hangul syllables to grab the first jamo
2893  if ( $code < 0xac00 || 0xd7a4 <= $code ) {
2894  return $matches[1];
2895  } elseif ( $code < 0xb098 ) {
2896  return "\xe3\x84\xb1";
2897  } elseif ( $code < 0xb2e4 ) {
2898  return "\xe3\x84\xb4";
2899  } elseif ( $code < 0xb77c ) {
2900  return "\xe3\x84\xb7";
2901  } elseif ( $code < 0xb9c8 ) {
2902  return "\xe3\x84\xb9";
2903  } elseif ( $code < 0xbc14 ) {
2904  return "\xe3\x85\x81";
2905  } elseif ( $code < 0xc0ac ) {
2906  return "\xe3\x85\x82";
2907  } elseif ( $code < 0xc544 ) {
2908  return "\xe3\x85\x85";
2909  } elseif ( $code < 0xc790 ) {
2910  return "\xe3\x85\x87";
2911  } elseif ( $code < 0xcc28 ) {
2912  return "\xe3\x85\x88";
2913  } elseif ( $code < 0xce74 ) {
2914  return "\xe3\x85\x8a";
2915  } elseif ( $code < 0xd0c0 ) {
2916  return "\xe3\x85\x8b";
2917  } elseif ( $code < 0xd30c ) {
2918  return "\xe3\x85\x8c";
2919  } elseif ( $code < 0xd558 ) {
2920  return "\xe3\x85\x8d";
2921  } else {
2922  return "\xe3\x85\x8e";
2923  }
2924  } else {
2925  return '';
2926  }
2927  }
2928 
2932  function initEncoding() {
2933  // No-op.
2934  }
2935 
2941  function recodeForEdit( $s ) {
2942  return $s;
2943  }
2944 
2950  function recodeInput( $s ) {
2951  return $s;
2952  }
2953 
2965  function normalize( $s ) {
2967  $s = UtfNormal\Validator::cleanUp( $s );
2968  if ( $wgAllUnicodeFixes ) {
2969  $s = $this->transformUsingPairFile( 'normalize-ar.ser', $s );
2970  $s = $this->transformUsingPairFile( 'normalize-ml.ser', $s );
2971  }
2972 
2973  return $s;
2974  }
2975 
2990  function transformUsingPairFile( $file, $string ) {
2991  if ( !isset( $this->transformData[$file] ) ) {
2992  $data = wfGetPrecompiledData( $file );
2993  if ( $data === false ) {
2994  throw new MWException( __METHOD__ . ": The transformation file $file is missing" );
2995  }
2996  $this->transformData[$file] = new ReplacementArray( $data );
2997  }
2998  return $this->transformData[$file]->replace( $string );
2999  }
3000 
3006  function isRTL() {
3007  return self::$dataCache->getItem( $this->mCode, 'rtl' );
3008  }
3009 
3014  function getDir() {
3015  return $this->isRTL() ? 'rtl' : 'ltr';
3016  }
3017 
3026  function alignStart() {
3027  return $this->isRTL() ? 'right' : 'left';
3028  }
3029 
3038  function alignEnd() {
3039  return $this->isRTL() ? 'left' : 'right';
3040  }
3041 
3053  function getDirMarkEntity( $opposite = false ) {
3054  if ( $opposite ) {
3055  return $this->isRTL() ? '&lrm;' : '&rlm;';
3056  }
3057  return $this->isRTL() ? '&rlm;' : '&lrm;';
3058  }
3059 
3070  function getDirMark( $opposite = false ) {
3071  $lrm = "\xE2\x80\x8E"; # LEFT-TO-RIGHT MARK, commonly abbreviated LRM
3072  $rlm = "\xE2\x80\x8F"; # RIGHT-TO-LEFT MARK, commonly abbreviated RLM
3073  if ( $opposite ) {
3074  return $this->isRTL() ? $lrm : $rlm;
3075  }
3076  return $this->isRTL() ? $rlm : $lrm;
3077  }
3078 
3082  function capitalizeAllNouns() {
3083  return self::$dataCache->getItem( $this->mCode, 'capitalizeAllNouns' );
3084  }
3085 
3093  function getArrow( $direction = 'forwards' ) {
3094  switch ( $direction ) {
3095  case 'forwards':
3096  return $this->isRTL() ? '←' : '→';
3097  case 'backwards':
3098  return $this->isRTL() ? '→' : '←';
3099  case 'left':
3100  return '←';
3101  case 'right':
3102  return '→';
3103  case 'up':
3104  return '↑';
3105  case 'down':
3106  return '↓';
3107  }
3108  }
3109 
3115  function linkPrefixExtension() {
3116  return self::$dataCache->getItem( $this->mCode, 'linkPrefixExtension' );
3117  }
3118 
3123  function getMagicWords() {
3124  return self::$dataCache->getItem( $this->mCode, 'magicWords' );
3125  }
3126 
3130  protected function doMagicHook() {
3131  if ( $this->mMagicHookDone ) {
3132  return;
3133  }
3134  $this->mMagicHookDone = true;
3135  Hooks::run( 'LanguageGetMagic', [ &$this->mMagicExtensions, $this->getCode() ] );
3136  }
3137 
3143  function getMagic( $mw ) {
3144  // Saves a function call
3145  if ( !$this->mMagicHookDone ) {
3146  $this->doMagicHook();
3147  }
3148 
3149  if ( isset( $this->mMagicExtensions[$mw->mId] ) ) {
3150  $rawEntry = $this->mMagicExtensions[$mw->mId];
3151  } else {
3152  $rawEntry = self::$dataCache->getSubitem(
3153  $this->mCode, 'magicWords', $mw->mId );
3154  }
3155 
3156  if ( !is_array( $rawEntry ) ) {
3157  wfWarn( "\"$rawEntry\" is not a valid magic word for \"$mw->mId\"" );
3158  } else {
3159  $mw->mCaseSensitive = $rawEntry[0];
3160  $mw->mSynonyms = array_slice( $rawEntry, 1 );
3161  }
3162  }
3163 
3169  function addMagicWordsByLang( $newWords ) {
3170  $fallbackChain = $this->getFallbackLanguages();
3171  $fallbackChain = array_reverse( $fallbackChain );
3172  foreach ( $fallbackChain as $code ) {
3173  if ( isset( $newWords[$code] ) ) {
3174  $this->mMagicExtensions = $newWords[$code] + $this->mMagicExtensions;
3175  }
3176  }
3177  }
3178 
3185  // Cache aliases because it may be slow to load them
3186  if ( is_null( $this->mExtendedSpecialPageAliases ) ) {
3187  // Initialise array
3188  $this->mExtendedSpecialPageAliases =
3189  self::$dataCache->getItem( $this->mCode, 'specialPageAliases' );
3190  Hooks::run( 'LanguageGetSpecialPageAliases',
3191  [ &$this->mExtendedSpecialPageAliases, $this->getCode() ] );
3192  }
3193 
3195  }
3196 
3203  function emphasize( $text ) {
3204  return "<em>$text</em>";
3205  }
3206 
3229  public function formatNum( $number, $nocommafy = false ) {
3231  if ( !$nocommafy ) {
3232  $number = $this->commafy( $number );
3233  $s = $this->separatorTransformTable();
3234  if ( $s ) {
3235  $number = strtr( $number, $s );
3236  }
3237  }
3238 
3239  if ( $wgTranslateNumerals ) {
3240  $s = $this->digitTransformTable();
3241  if ( $s ) {
3242  $number = strtr( $number, $s );
3243  }
3244  }
3245 
3246  return $number;
3247  }
3248 
3257  public function formatNumNoSeparators( $number ) {
3258  return $this->formatNum( $number, true );
3259  }
3260 
3265  public function parseFormattedNumber( $number ) {
3266  $s = $this->digitTransformTable();
3267  if ( $s ) {
3268  // eliminate empty array values such as ''. (bug 64347)
3269  $s = array_filter( $s );
3270  $number = strtr( $number, array_flip( $s ) );
3271  }
3272 
3273  $s = $this->separatorTransformTable();
3274  if ( $s ) {
3275  // eliminate empty array values such as ''. (bug 64347)
3276  $s = array_filter( $s );
3277  $number = strtr( $number, array_flip( $s ) );
3278  }
3279 
3280  $number = strtr( $number, [ ',' => '' ] );
3281  return $number;
3282  }
3283 
3290  function commafy( $number ) {
3292  if ( $number === null ) {
3293  return '';
3294  }
3295 
3296  if ( !$digitGroupingPattern || $digitGroupingPattern === "###,###,###" ) {
3297  // default grouping is at thousands, use the same for ###,###,### pattern too.
3298  return strrev( (string)preg_replace( '/(\d{3})(?=\d)(?!\d*\.)/', '$1,', strrev( $number ) ) );
3299  } else {
3300  // Ref: http://cldr.unicode.org/translation/number-patterns
3301  $sign = "";
3302  if ( intval( $number ) < 0 ) {
3303  // For negative numbers apply the algorithm like positive number and add sign.
3304  $sign = "-";
3305  $number = substr( $number, 1 );
3306  }
3307  $integerPart = [];
3308  $decimalPart = [];
3309  $numMatches = preg_match_all( "/(#+)/", $digitGroupingPattern, $matches );
3310  preg_match( "/\d+/", $number, $integerPart );
3311  preg_match( "/\.\d*/", $number, $decimalPart );
3312  $groupedNumber = ( count( $decimalPart ) > 0 ) ? $decimalPart[0] : "";
3313  if ( $groupedNumber === $number ) {
3314  // the string does not have any number part. Eg: .12345
3315  return $sign . $groupedNumber;
3316  }
3317  $start = $end = ( $integerPart ) ? strlen( $integerPart[0] ) : 0;
3318  while ( $start > 0 ) {
3319  $match = $matches[0][$numMatches - 1];
3320  $matchLen = strlen( $match );
3321  $start = $end - $matchLen;
3322  if ( $start < 0 ) {
3323  $start = 0;
3324  }
3325  $groupedNumber = substr( $number, $start, $end -$start ) . $groupedNumber;
3326  $end = $start;
3327  if ( $numMatches > 1 ) {
3328  // use the last pattern for the rest of the number
3329  $numMatches--;
3330  }
3331  if ( $start > 0 ) {
3332  $groupedNumber = "," . $groupedNumber;
3333  }
3334  }
3335  return $sign . $groupedNumber;
3336  }
3337  }
3338 
3343  return self::$dataCache->getItem( $this->mCode, 'digitGroupingPattern' );
3344  }
3345 
3349  function digitTransformTable() {
3350  return self::$dataCache->getItem( $this->mCode, 'digitTransformTable' );
3351  }
3352 
3357  return self::$dataCache->getItem( $this->mCode, 'separatorTransformTable' );
3358  }
3359 
3369  function listToText( array $l ) {
3370  $m = count( $l ) - 1;
3371  if ( $m < 0 ) {
3372  return '';
3373  }
3374  if ( $m > 0 ) {
3375  $and = $this->msg( 'and' )->escaped();
3376  $space = $this->msg( 'word-separator' )->escaped();
3377  if ( $m > 1 ) {
3378  $comma = $this->msg( 'comma-separator' )->escaped();
3379  }
3380  }
3381  $s = $l[$m];
3382  for ( $i = $m - 1; $i >= 0; $i-- ) {
3383  if ( $i == $m - 1 ) {
3384  $s = $l[$i] . $and . $space . $s;
3385  } else {
3386  $s = $l[$i] . $comma . $s;
3387  }
3388  }
3389  return $s;
3390  }
3391 
3398  function commaList( array $list ) {
3399  return implode(
3400  wfMessage( 'comma-separator' )->inLanguage( $this )->escaped(),
3401  $list
3402  );
3403  }
3404 
3411  function semicolonList( array $list ) {
3412  return implode(
3413  wfMessage( 'semicolon-separator' )->inLanguage( $this )->escaped(),
3414  $list
3415  );
3416  }
3417 
3423  function pipeList( array $list ) {
3424  return implode(
3425  wfMessage( 'pipe-separator' )->inLanguage( $this )->escaped(),
3426  $list
3427  );
3428  }
3429 
3447  function truncate( $string, $length, $ellipsis = '...', $adjustLength = true ) {
3448  # Use the localized ellipsis character
3449  if ( $ellipsis == '...' ) {
3450  $ellipsis = wfMessage( 'ellipsis' )->inLanguage( $this )->escaped();
3451  }
3452  # Check if there is no need to truncate
3453  if ( $length == 0 ) {
3454  return $ellipsis; // convention
3455  } elseif ( strlen( $string ) <= abs( $length ) ) {
3456  return $string; // no need to truncate
3457  }
3458  $stringOriginal = $string;
3459  # If ellipsis length is >= $length then we can't apply $adjustLength
3460  if ( $adjustLength && strlen( $ellipsis ) >= abs( $length ) ) {
3461  $string = $ellipsis; // this can be slightly unexpected
3462  # Otherwise, truncate and add ellipsis...
3463  } else {
3464  $eLength = $adjustLength ? strlen( $ellipsis ) : 0;
3465  if ( $length > 0 ) {
3466  $length -= $eLength;
3467  $string = substr( $string, 0, $length ); // xyz...
3468  $string = $this->removeBadCharLast( $string );
3469  $string = rtrim( $string );
3470  $string = $string . $ellipsis;
3471  } else {
3472  $length += $eLength;
3473  $string = substr( $string, $length ); // ...xyz
3474  $string = $this->removeBadCharFirst( $string );
3475  $string = ltrim( $string );
3476  $string = $ellipsis . $string;
3477  }
3478  }
3479  # Do not truncate if the ellipsis makes the string longer/equal (bug 22181).
3480  # This check is *not* redundant if $adjustLength, due to the single case where
3481  # LEN($ellipsis) > ABS($limit arg); $stringOriginal could be shorter than $string.
3482  if ( strlen( $string ) < strlen( $stringOriginal ) ) {
3483  return $string;
3484  } else {
3485  return $stringOriginal;
3486  }
3487  }
3488 
3496  protected function removeBadCharLast( $string ) {
3497  if ( $string != '' ) {
3498  $char = ord( $string[strlen( $string ) - 1] );
3499  $m = [];
3500  if ( $char >= 0xc0 ) {
3501  # We got the first byte only of a multibyte char; remove it.
3502  $string = substr( $string, 0, -1 );
3503  } elseif ( $char >= 0x80 &&
3504  // Use the /s modifier (PCRE_DOTALL) so (.*) also matches newlines
3505  preg_match( '/^(.*)(?:[\xe0-\xef][\x80-\xbf]|' .
3506  '[\xf0-\xf7][\x80-\xbf]{1,2})$/s', $string, $m )
3507  ) {
3508  # We chopped in the middle of a character; remove it
3509  $string = $m[1];
3510  }
3511  }
3512  return $string;
3513  }
3514 
3522  protected function removeBadCharFirst( $string ) {
3523  if ( $string != '' ) {
3524  $char = ord( $string[0] );
3525  if ( $char >= 0x80 && $char < 0xc0 ) {
3526  # We chopped in the middle of a character; remove the whole thing
3527  $string = preg_replace( '/^[\x80-\xbf]+/', '', $string );
3528  }
3529  }
3530  return $string;
3531  }
3532 
3548  function truncateHtml( $text, $length, $ellipsis = '...' ) {
3549  # Use the localized ellipsis character
3550  if ( $ellipsis == '...' ) {
3551  $ellipsis = wfMessage( 'ellipsis' )->inLanguage( $this )->escaped();
3552  }
3553  # Check if there is clearly no need to truncate
3554  if ( $length <= 0 ) {
3555  return $ellipsis; // no text shown, nothing to format (convention)
3556  } elseif ( strlen( $text ) <= $length ) {
3557  return $text; // string short enough even *with* HTML (short-circuit)
3558  }
3559 
3560  $dispLen = 0; // innerHTML legth so far
3561  $testingEllipsis = false; // checking if ellipses will make string longer/equal?
3562  $tagType = 0; // 0-open, 1-close
3563  $bracketState = 0; // 1-tag start, 2-tag name, 0-neither
3564  $entityState = 0; // 0-not entity, 1-entity
3565  $tag = $ret = ''; // accumulated tag name, accumulated result string
3566  $openTags = []; // open tag stack
3567  $maybeState = null; // possible truncation state
3568 
3569  $textLen = strlen( $text );
3570  $neLength = max( 0, $length - strlen( $ellipsis ) ); // non-ellipsis len if truncated
3571  for ( $pos = 0; true; ++$pos ) {
3572  # Consider truncation once the display length has reached the maximim.
3573  # We check if $dispLen > 0 to grab tags for the $neLength = 0 case.
3574  # Check that we're not in the middle of a bracket/entity...
3575  if ( $dispLen && $dispLen >= $neLength && $bracketState == 0 && !$entityState ) {
3576  if ( !$testingEllipsis ) {
3577  $testingEllipsis = true;
3578  # Save where we are; we will truncate here unless there turn out to
3579  # be so few remaining characters that truncation is not necessary.
3580  if ( !$maybeState ) { // already saved? ($neLength = 0 case)
3581  $maybeState = [ $ret, $openTags ]; // save state
3582  }
3583  } elseif ( $dispLen > $length && $dispLen > strlen( $ellipsis ) ) {
3584  # String in fact does need truncation, the truncation point was OK.
3585  list( $ret, $openTags ) = $maybeState; // reload state
3586  $ret = $this->removeBadCharLast( $ret ); // multi-byte char fix
3587  $ret .= $ellipsis; // add ellipsis
3588  break;
3589  }
3590  }
3591  if ( $pos >= $textLen ) {
3592  break; // extra iteration just for above checks
3593  }
3594 
3595  # Read the next char...
3596  $ch = $text[$pos];
3597  $lastCh = $pos ? $text[$pos - 1] : '';
3598  $ret .= $ch; // add to result string
3599  if ( $ch == '<' ) {
3600  $this->truncate_endBracket( $tag, $tagType, $lastCh, $openTags ); // for bad HTML
3601  $entityState = 0; // for bad HTML
3602  $bracketState = 1; // tag started (checking for backslash)
3603  } elseif ( $ch == '>' ) {
3604  $this->truncate_endBracket( $tag, $tagType, $lastCh, $openTags );
3605  $entityState = 0; // for bad HTML
3606  $bracketState = 0; // out of brackets
3607  } elseif ( $bracketState == 1 ) {
3608  if ( $ch == '/' ) {
3609  $tagType = 1; // close tag (e.g. "</span>")
3610  } else {
3611  $tagType = 0; // open tag (e.g. "<span>")
3612  $tag .= $ch;
3613  }
3614  $bracketState = 2; // building tag name
3615  } elseif ( $bracketState == 2 ) {
3616  if ( $ch != ' ' ) {
3617  $tag .= $ch;
3618  } else {
3619  // Name found (e.g. "<a href=..."), add on tag attributes...
3620  $pos += $this->truncate_skip( $ret, $text, "<>", $pos + 1 );
3621  }
3622  } elseif ( $bracketState == 0 ) {
3623  if ( $entityState ) {
3624  if ( $ch == ';' ) {
3625  $entityState = 0;
3626  $dispLen++; // entity is one displayed char
3627  }
3628  } else {
3629  if ( $neLength == 0 && !$maybeState ) {
3630  // Save state without $ch. We want to *hit* the first
3631  // display char (to get tags) but not *use* it if truncating.
3632  $maybeState = [ substr( $ret, 0, -1 ), $openTags ];
3633  }
3634  if ( $ch == '&' ) {
3635  $entityState = 1; // entity found, (e.g. "&#160;")
3636  } else {
3637  $dispLen++; // this char is displayed
3638  // Add the next $max display text chars after this in one swoop...
3639  $max = ( $testingEllipsis ? $length : $neLength ) - $dispLen;
3640  $skipped = $this->truncate_skip( $ret, $text, "<>&", $pos + 1, $max );
3641  $dispLen += $skipped;
3642  $pos += $skipped;
3643  }
3644  }
3645  }
3646  }
3647  // Close the last tag if left unclosed by bad HTML
3648  $this->truncate_endBracket( $tag, $text[$textLen - 1], $tagType, $openTags );
3649  while ( count( $openTags ) > 0 ) {
3650  $ret .= '</' . array_pop( $openTags ) . '>'; // close open tags
3651  }
3652  return $ret;
3653  }
3654 
3666  private function truncate_skip( &$ret, $text, $search, $start, $len = null ) {
3667  if ( $len === null ) {
3668  $len = -1; // -1 means "no limit" for strcspn
3669  } elseif ( $len < 0 ) {
3670  $len = 0; // sanity
3671  }
3672  $skipCount = 0;
3673  if ( $start < strlen( $text ) ) {
3674  $skipCount = strcspn( $text, $search, $start, $len );
3675  $ret .= substr( $text, $start, $skipCount );
3676  }
3677  return $skipCount;
3678  }
3679 
3689  private function truncate_endBracket( &$tag, $tagType, $lastCh, &$openTags ) {
3690  $tag = ltrim( $tag );
3691  if ( $tag != '' ) {
3692  if ( $tagType == 0 && $lastCh != '/' ) {
3693  $openTags[] = $tag; // tag opened (didn't close itself)
3694  } elseif ( $tagType == 1 ) {
3695  if ( $openTags && $tag == $openTags[count( $openTags ) - 1] ) {
3696  array_pop( $openTags ); // tag closed
3697  }
3698  }
3699  $tag = '';
3700  }
3701  }
3702 
3711  function convertGrammar( $word, $case ) {
3713  if ( isset( $wgGrammarForms[$this->getCode()][$case][$word] ) ) {
3714  return $wgGrammarForms[$this->getCode()][$case][$word];
3715  }
3716 
3717  return $word;
3718  }
3724  function getGrammarForms() {
3726  if ( isset( $wgGrammarForms[$this->getCode()] )
3727  && is_array( $wgGrammarForms[$this->getCode()] )
3728  ) {
3729  return $wgGrammarForms[$this->getCode()];
3730  }
3731 
3732  return [];
3733  }
3753  function gender( $gender, $forms ) {
3754  if ( !count( $forms ) ) {
3755  return '';
3756  }
3757  $forms = $this->preConvertPlural( $forms, 2 );
3758  if ( $gender === 'male' ) {
3759  return $forms[0];
3760  }
3761  if ( $gender === 'female' ) {
3762  return $forms[1];
3763  }
3764  return isset( $forms[2] ) ? $forms[2] : $forms[0];
3765  }
3766 
3782  function convertPlural( $count, $forms ) {
3783  // Handle explicit n=pluralform cases
3784  $forms = $this->handleExplicitPluralForms( $count, $forms );
3785  if ( is_string( $forms ) ) {
3786  return $forms;
3787  }
3788  if ( !count( $forms ) ) {
3789  return '';
3790  }
3791 
3792  $pluralForm = $this->getPluralRuleIndexNumber( $count );
3793  $pluralForm = min( $pluralForm, count( $forms ) - 1 );
3794  return $forms[$pluralForm];
3795  }
3796 
3812  protected function handleExplicitPluralForms( $count, array $forms ) {
3813  foreach ( $forms as $index => $form ) {
3814  if ( preg_match( '/\d+=/i', $form ) ) {
3815  $pos = strpos( $form, '=' );
3816  if ( substr( $form, 0, $pos ) === (string)$count ) {
3817  return substr( $form, $pos + 1 );
3818  }
3819  unset( $forms[$index] );
3820  }
3821  }
3822  return array_values( $forms );
3823  }
3824 
3833  protected function preConvertPlural( /* Array */ $forms, $count ) {
3834  while ( count( $forms ) < $count ) {
3835  $forms[] = $forms[count( $forms ) - 1];
3836  }
3837  return $forms;
3838  }
3839 
3856  public function embedBidi( $text = '' ) {
3858  if ( $dir === 'ltr' ) {
3859  // Wrap in LEFT-TO-RIGHT EMBEDDING ... POP DIRECTIONAL FORMATTING
3860  return self::$lre . $text . self::$pdf;
3861  }
3862  if ( $dir === 'rtl' ) {
3863  // Wrap in RIGHT-TO-LEFT EMBEDDING ... POP DIRECTIONAL FORMATTING
3864  return self::$rle . $text . self::$pdf;
3865  }
3866  // No strong directionality: do not wrap
3867  return $text;
3868  }
3869 
3882  function translateBlockExpiry( $str, User $user = null ) {
3883  $duration = SpecialBlock::getSuggestedDurations( $this );
3884  foreach ( $duration as $show => $value ) {
3885  if ( strcmp( $str, $value ) == 0 ) {
3886  return htmlspecialchars( trim( $show ) );
3887  }
3888  }
3889 
3890  if ( wfIsInfinity( $str ) ) {
3891  foreach ( $duration as $show => $value ) {
3892  if ( wfIsInfinity( $value ) ) {
3893  return htmlspecialchars( trim( $show ) );
3894  }
3895  }
3896  }
3897 
3898  // If all else fails, return a standard duration or timestamp description.
3899  $time = strtotime( $str, 0 );
3900  if ( $time === false ) { // Unknown format. Return it as-is in case.
3901  return $str;
3902  } elseif ( $time !== strtotime( $str, 1 ) ) { // It's a relative timestamp.
3903  // $time is relative to 0 so it's a duration length.
3904  return $this->formatDuration( $time );
3905  } else { // It's an absolute timestamp.
3906  if ( $time === 0 ) {
3907  // wfTimestamp() handles 0 as current time instead of epoch.
3908  $time = '19700101000000';
3909  }
3910  if ( $user ) {
3911  return $this->userTimeAndDate( $time, $user );
3912  }
3913  return $this->timeanddate( $time );
3914  }
3915  }
3916 
3924  public function segmentForDiff( $text ) {
3925  return $text;
3926  }
3927 
3934  public function unsegmentForDiff( $text ) {
3935  return $text;
3936  }
3937 
3944  public function getConverter() {
3945  return $this->mConverter;
3946  }
3947 
3954  public function autoConvertToAllVariants( $text ) {
3955  return $this->mConverter->autoConvertToAllVariants( $text );
3956  }
3957 
3964  public function convert( $text ) {
3965  return $this->mConverter->convert( $text );
3966  }
3967 
3974  public function convertTitle( $title ) {
3975  return $this->mConverter->convertTitle( $title );
3976  }
3977 
3984  public function convertNamespace( $ns ) {
3985  return $this->mConverter->convertNamespace( $ns );
3986  }
3987 
3993  public function hasVariants() {
3994  return count( $this->getVariants() ) > 1;
3995  }
3996 
4004  public function hasVariant( $variant ) {
4005  return (bool)$this->mConverter->validateVariant( $variant );
4006  }
4007 
4015  public function convertHtml( $text, $isTitle = false ) {
4016  return htmlspecialchars( $this->convert( $text, $isTitle ) );
4017  }
4018 
4023  public function convertCategoryKey( $key ) {
4024  return $this->mConverter->convertCategoryKey( $key );
4025  }
4026 
4033  public function getVariants() {
4034  return $this->mConverter->getVariants();
4035  }
4036 
4040  public function getPreferredVariant() {
4041  return $this->mConverter->getPreferredVariant();
4042  }
4043 
4047  public function getDefaultVariant() {
4048  return $this->mConverter->getDefaultVariant();
4049  }
4050 
4054  public function getURLVariant() {
4055  return $this->mConverter->getURLVariant();
4056  }
4057 
4070  public function findVariantLink( &$link, &$nt, $ignoreOtherCond = false ) {
4071  $this->mConverter->findVariantLink( $link, $nt, $ignoreOtherCond );
4072  }
4073 
4080  function getExtraHashOptions() {
4081  return $this->mConverter->getExtraHashOptions();
4082  }
4083 
4091  public function getParsedTitle() {
4092  return $this->mConverter->getParsedTitle();
4093  }
4094 
4101  public function updateConversionTable( Title $title ) {
4102  $this->mConverter->updateConversionTable( $title );
4103  }
4104 
4117  public function markNoConversion( $text, $noParse = false ) {
4118  // Excluding protocal-relative URLs may avoid many false positives.
4119  if ( $noParse || preg_match( '/^(?:' . wfUrlProtocolsWithoutProtRel() . ')/', $text ) ) {
4120  return $this->mConverter->markNoConversion( $text );
4121  } else {
4122  return $text;
4123  }
4124  }
4125 
4132  public function linkTrail() {
4133  return self::$dataCache->getItem( $this->mCode, 'linkTrail' );
4134  }
4135 
4142  public function linkPrefixCharset() {
4143  return self::$dataCache->getItem( $this->mCode, 'linkPrefixCharset' );
4144  }
4145 
4153  public function getParentLanguage() {
4154  if ( $this->mParentLanguage !== false ) {
4155  return $this->mParentLanguage;
4156  }
4157 
4158  $code = explode( '-', $this->getCode() )[0];
4159  if ( !in_array( $code, LanguageConverter::$languagesWithVariants ) ) {
4160  $this->mParentLanguage = null;
4161  return null;
4162  }
4164  if ( !$lang->hasVariant( $this->getCode() ) ) {
4165  $this->mParentLanguage = null;
4166  return null;
4167  }
4168 
4169  $this->mParentLanguage = $lang;
4170  return $lang;
4171  }
4172 
4180  public function equals( Language $lang ) {
4181  return $lang->getCode() === $this->mCode;
4182  }
4183 
4192  public function getCode() {
4193  return $this->mCode;
4194  }
4195 
4206  public function getHtmlCode() {
4207  if ( is_null( $this->mHtmlCode ) ) {
4208  $this->mHtmlCode = wfBCP47( $this->getCode() );
4209  }
4210  return $this->mHtmlCode;
4211  }
4212 
4216  public function setCode( $code ) {
4217  $this->mCode = $code;
4218  // Ensure we don't leave incorrect cached data lying around
4219  $this->mHtmlCode = null;
4220  $this->mParentLanguage = false;
4221  }
4222 
4230  public static function getCodeFromFileName( $filename, $prefix = 'Language', $suffix = '.php' ) {
4231  $m = null;
4232  preg_match( '/' . preg_quote( $prefix, '/' ) . '([A-Z][a-z_]+)' .
4233  preg_quote( $suffix, '/' ) . '/', $filename, $m );
4234  if ( !count( $m ) ) {
4235  return false;
4236  }
4237  return str_replace( '_', '-', strtolower( $m[1] ) );
4238  }
4239 
4244  public static function classFromCode( $code ) {
4245  if ( $code == 'en' ) {
4246  return 'Language';
4247  } else {
4248  return 'Language' . str_replace( '-', '_', ucfirst( $code ) );
4249  }
4250  }
4251 
4260  public static function getFileName( $prefix = 'Language', $code, $suffix = '.php' ) {
4261  if ( !self::isValidBuiltInCode( $code ) ) {
4262  throw new MWException( "Invalid language code \"$code\"" );
4263  }
4264 
4265  return $prefix . str_replace( '-', '_', ucfirst( $code ) ) . $suffix;
4266  }
4267 
4272  public static function getMessagesFileName( $code ) {
4273  global $IP;
4274  $file = self::getFileName( "$IP/languages/messages/Messages", $code, '.php' );
4275  Hooks::run( 'Language::getMessagesFileName', [ $code, &$file ] );
4276  return $file;
4277  }
4278 
4285  public static function getJsonMessagesFileName( $code ) {
4286  global $IP;
4287 
4288  if ( !self::isValidBuiltInCode( $code ) ) {
4289  throw new MWException( "Invalid language code \"$code\"" );
4290  }
4291 
4292  return "$IP/languages/i18n/$code.json";
4293  }
4294 
4302  public static function getFallbackFor( $code ) {
4303  $fallbacks = self::getFallbacksFor( $code );
4304  if ( $fallbacks ) {
4305  return $fallbacks[0];
4306  }
4307  return false;
4308  }
4309 
4317  public static function getFallbacksFor( $code ) {
4318  if ( $code === 'en' || !Language::isValidBuiltInCode( $code ) ) {
4319  return [];
4320  }
4321  // For unknown languages, fallbackSequence returns an empty array,
4322  // hardcode fallback to 'en' in that case.
4323  return self::getLocalisationCache()->getItem( $code, 'fallbackSequence' ) ?: [ 'en' ];
4324  }
4325 
4334  public static function getFallbacksIncludingSiteLanguage( $code ) {
4336 
4337  // Usually, we will only store a tiny number of fallback chains, so we
4338  // keep them in static memory.
4339  $cacheKey = "{$code}-{$wgLanguageCode}";
4340 
4341  if ( !array_key_exists( $cacheKey, self::$fallbackLanguageCache ) ) {
4342  $fallbacks = self::getFallbacksFor( $code );
4343 
4344  // Append the site's fallback chain, including the site language itself
4345  $siteFallbacks = self::getFallbacksFor( $wgLanguageCode );
4346  array_unshift( $siteFallbacks, $wgLanguageCode );
4347 
4348  // Eliminate any languages already included in the chain
4349  $siteFallbacks = array_diff( $siteFallbacks, $fallbacks );
4350 
4351  self::$fallbackLanguageCache[$cacheKey] = [ $fallbacks, $siteFallbacks ];
4352  }
4353  return self::$fallbackLanguageCache[$cacheKey];
4354  }
4355 
4365  public static function getMessagesFor( $code ) {
4366  return self::getLocalisationCache()->getItem( $code, 'messages' );
4367  }
4368 
4377  public static function getMessageFor( $key, $code ) {
4378  return self::getLocalisationCache()->getSubitem( $code, 'messages', $key );
4379  }
4380 
4389  public static function getMessageKeysFor( $code ) {
4390  return self::getLocalisationCache()->getSubitemList( $code, 'messages' );
4391  }
4392 
4397  function fixVariableInNamespace( $talk ) {
4398  if ( strpos( $talk, '$1' ) === false ) {
4399  return $talk;
4400  }
4401 
4403  $talk = str_replace( '$1', $wgMetaNamespace, $talk );
4404 
4405  # Allow grammar transformations
4406  # Allowing full message-style parsing would make simple requests
4407  # such as action=raw much more expensive than they need to be.
4408  # This will hopefully cover most cases.
4409  $talk = preg_replace_callback( '/{{grammar:(.*?)\|(.*?)}}/i',
4410  [ &$this, 'replaceGrammarInNamespace' ], $talk );
4411  return str_replace( ' ', '_', $talk );
4412  }
4413 
4418  function replaceGrammarInNamespace( $m ) {
4419  return $this->convertGrammar( trim( $m[2] ), trim( $m[1] ) );
4420  }
4421 
4432  public function formatExpiry( $expiry, $format = true, $infinity = 'infinity' ) {
4433  static $dbInfinity;
4434  if ( $dbInfinity === null ) {
4435  $dbInfinity = wfGetDB( DB_SLAVE )->getInfinity();
4436  }
4437 
4438  if ( $expiry == '' || $expiry === 'infinity' || $expiry == $dbInfinity ) {
4439  return $format === true
4440  ? $this->getMessageFromDB( 'infiniteblock' )
4441  : $infinity;
4442  } else {
4443  return $format === true
4444  ? $this->timeanddate( $expiry, /* User preference timezone */ true )
4445  : wfTimestamp( $format, $expiry );
4446  }
4447  }
4448 
4461  function formatTimePeriod( $seconds, $format = [] ) {
4462  if ( !is_array( $format ) ) {
4463  $format = [ 'avoid' => $format ]; // For backwards compatibility
4464  }
4465  if ( !isset( $format['avoid'] ) ) {
4466  $format['avoid'] = false;
4467  }
4468  if ( !isset( $format['noabbrevs'] ) ) {
4469  $format['noabbrevs'] = false;
4470  }
4471  $secondsMsg = wfMessage(
4472  $format['noabbrevs'] ? 'seconds' : 'seconds-abbrev' )->inLanguage( $this );
4473  $minutesMsg = wfMessage(
4474  $format['noabbrevs'] ? 'minutes' : 'minutes-abbrev' )->inLanguage( $this );
4475  $hoursMsg = wfMessage(
4476  $format['noabbrevs'] ? 'hours' : 'hours-abbrev' )->inLanguage( $this );
4477  $daysMsg = wfMessage(
4478  $format['noabbrevs'] ? 'days' : 'days-abbrev' )->inLanguage( $this );
4479 
4480  if ( round( $seconds * 10 ) < 100 ) {
4481  $s = $this->formatNum( sprintf( "%.1f", round( $seconds * 10 ) / 10 ) );
4482  $s = $secondsMsg->params( $s )->text();
4483  } elseif ( round( $seconds ) < 60 ) {
4484  $s = $this->formatNum( round( $seconds ) );
4485  $s = $secondsMsg->params( $s )->text();
4486  } elseif ( round( $seconds ) < 3600 ) {
4487  $minutes = floor( $seconds / 60 );
4488  $secondsPart = round( fmod( $seconds, 60 ) );
4489  if ( $secondsPart == 60 ) {
4490  $secondsPart = 0;
4491  $minutes++;
4492  }
4493  $s = $minutesMsg->params( $this->formatNum( $minutes ) )->text();
4494  $s .= ' ';
4495  $s .= $secondsMsg->params( $this->formatNum( $secondsPart ) )->text();
4496  } elseif ( round( $seconds ) <= 2 * 86400 ) {
4497  $hours = floor( $seconds / 3600 );
4498  $minutes = floor( ( $seconds - $hours * 3600 ) / 60 );
4499  $secondsPart = round( $seconds - $hours * 3600 - $minutes * 60 );
4500  if ( $secondsPart == 60 ) {
4501  $secondsPart = 0;
4502  $minutes++;
4503  }
4504  if ( $minutes == 60 ) {
4505  $minutes = 0;
4506  $hours++;
4507  }
4508  $s = $hoursMsg->params( $this->formatNum( $hours ) )->text();
4509  $s .= ' ';
4510  $s .= $minutesMsg->params( $this->formatNum( $minutes ) )->text();
4511  if ( !in_array( $format['avoid'], [ 'avoidseconds', 'avoidminutes' ] ) ) {
4512  $s .= ' ' . $secondsMsg->params( $this->formatNum( $secondsPart ) )->text();
4513  }
4514  } else {
4515  $days = floor( $seconds / 86400 );
4516  if ( $format['avoid'] === 'avoidminutes' ) {
4517  $hours = round( ( $seconds - $days * 86400 ) / 3600 );
4518  if ( $hours == 24 ) {
4519  $hours = 0;
4520  $days++;
4521  }
4522  $s = $daysMsg->params( $this->formatNum( $days ) )->text();
4523  $s .= ' ';
4524  $s .= $hoursMsg->params( $this->formatNum( $hours ) )->text();
4525  } elseif ( $format['avoid'] === 'avoidseconds' ) {
4526  $hours = floor( ( $seconds - $days * 86400 ) / 3600 );
4527  $minutes = round( ( $seconds - $days * 86400 - $hours * 3600 ) / 60 );
4528  if ( $minutes == 60 ) {
4529  $minutes = 0;
4530  $hours++;
4531  }
4532  if ( $hours == 24 ) {
4533  $hours = 0;
4534  $days++;
4535  }
4536  $s = $daysMsg->params( $this->formatNum( $days ) )->text();
4537  $s .= ' ';
4538  $s .= $hoursMsg->params( $this->formatNum( $hours ) )->text();
4539  $s .= ' ';
4540  $s .= $minutesMsg->params( $this->formatNum( $minutes ) )->text();
4541  } else {
4542  $s = $daysMsg->params( $this->formatNum( $days ) )->text();
4543  $s .= ' ';
4544  $s .= $this->formatTimePeriod( $seconds - $days * 86400, $format );
4545  }
4546  }
4547  return $s;
4548  }
4549 
4561  function formatBitrate( $bps ) {
4562  return $this->formatComputingNumbers( $bps, 1000, "bitrate-$1bits" );
4563  }
4564 
4571  function formatComputingNumbers( $size, $boundary, $messageKey ) {
4572  if ( $size <= 0 ) {
4573  return str_replace( '$1', $this->formatNum( $size ),
4574  $this->getMessageFromDB( str_replace( '$1', '', $messageKey ) )
4575  );
4576  }
4577  $sizes = [ '', 'kilo', 'mega', 'giga', 'tera', 'peta', 'exa', 'zeta', 'yotta' ];
4578  $index = 0;
4579 
4580  $maxIndex = count( $sizes ) - 1;
4581  while ( $size >= $boundary && $index < $maxIndex ) {
4582  $index++;
4583  $size /= $boundary;
4584  }
4585 
4586  // For small sizes no decimal places necessary
4587  $round = 0;
4588  if ( $index > 1 ) {
4589  // For MB and bigger two decimal places are smarter
4590  $round = 2;
4591  }
4592  $msg = str_replace( '$1', $sizes[$index], $messageKey );
4593 
4594  $size = round( $size, $round );
4595  $text = $this->getMessageFromDB( $msg );
4596  return str_replace( '$1', $this->formatNum( $size ), $text );
4597  }
4598 
4609  function formatSize( $size ) {
4610  return $this->formatComputingNumbers( $size, 1024, "size-$1bytes" );
4611  }
4612 
4622  function specialList( $page, $details, $oppositedm = true ) {
4623  if ( !$details ) {
4624  return $page;
4625  }
4626 
4627  $dirmark = ( $oppositedm ? $this->getDirMark( true ) : '' ) . $this->getDirMark();
4628  return
4629  $page .
4630  $dirmark .
4631  $this->msg( 'word-separator' )->escaped() .
4632  $this->msg( 'parentheses' )->rawParams( $details )->escaped();
4633  }
4634 
4645  public function viewPrevNext( Title $title, $offset, $limit,
4646  array $query = [], $atend = false
4647  ) {
4648  // @todo FIXME: Why on earth this needs one message for the text and another one for tooltip?
4649 
4650  # Make 'previous' link
4651  $prev = wfMessage( 'prevn' )->inLanguage( $this )->title( $title )->numParams( $limit )->text();
4652  if ( $offset > 0 ) {
4653  $plink = $this->numLink( $title, max( $offset - $limit, 0 ), $limit,
4654  $query, $prev, 'prevn-title', 'mw-prevlink' );
4655  } else {
4656  $plink = htmlspecialchars( $prev );
4657  }
4658 
4659  # Make 'next' link
4660  $next = wfMessage( 'nextn' )->inLanguage( $this )->title( $title )->numParams( $limit )->text();
4661  if ( $atend ) {
4662  $nlink = htmlspecialchars( $next );
4663  } else {
4664  $nlink = $this->numLink( $title, $offset + $limit, $limit,
4665  $query, $next, 'nextn-title', 'mw-nextlink' );
4666  }
4667 
4668  # Make links to set number of items per page
4669  $numLinks = [];
4670  foreach ( [ 20, 50, 100, 250, 500 ] as $num ) {
4671  $numLinks[] = $this->numLink( $title, $offset, $num,
4672  $query, $this->formatNum( $num ), 'shown-title', 'mw-numlink' );
4673  }
4674 
4675  return wfMessage( 'viewprevnext' )->inLanguage( $this )->title( $title
4676  )->rawParams( $plink, $nlink, $this->pipeList( $numLinks ) )->escaped();
4677  }
4678 
4691  private function numLink( Title $title, $offset, $limit, array $query, $link,
4692  $tooltipMsg, $class
4693  ) {
4694  $query = [ 'limit' => $limit, 'offset' => $offset ] + $query;
4695  $tooltip = wfMessage( $tooltipMsg )->inLanguage( $this )->title( $title )
4696  ->numParams( $limit )->text();
4697 
4698  return Html::element( 'a', [ 'href' => $title->getLocalURL( $query ),
4699  'title' => $tooltip, 'class' => $class ], $link );
4700  }
4701 
4707  public function getConvRuleTitle() {
4708  return $this->mConverter->getConvRuleTitle();
4709  }
4710 
4716  public function getCompiledPluralRules() {
4717  $pluralRules = self::$dataCache->getItem( strtolower( $this->mCode ), 'compiledPluralRules' );
4718  $fallbacks = Language::getFallbacksFor( $this->mCode );
4719  if ( !$pluralRules ) {
4720  foreach ( $fallbacks as $fallbackCode ) {
4721  $pluralRules = self::$dataCache->getItem( strtolower( $fallbackCode ), 'compiledPluralRules' );
4722  if ( $pluralRules ) {
4723  break;
4724  }
4725  }
4726  }
4727  return $pluralRules;
4728  }
4729 
4735  public function getPluralRules() {
4736  $pluralRules = self::$dataCache->getItem( strtolower( $this->mCode ), 'pluralRules' );
4737  $fallbacks = Language::getFallbacksFor( $this->mCode );
4738  if ( !$pluralRules ) {
4739  foreach ( $fallbacks as $fallbackCode ) {
4740  $pluralRules = self::$dataCache->getItem( strtolower( $fallbackCode ), 'pluralRules' );
4741  if ( $pluralRules ) {
4742  break;
4743  }
4744  }
4745  }
4746  return $pluralRules;
4747  }
4748 
4754  public function getPluralRuleTypes() {
4755  $pluralRuleTypes = self::$dataCache->getItem( strtolower( $this->mCode ), 'pluralRuleTypes' );
4756  $fallbacks = Language::getFallbacksFor( $this->mCode );
4757  if ( !$pluralRuleTypes ) {
4758  foreach ( $fallbacks as $fallbackCode ) {
4759  $pluralRuleTypes = self::$dataCache->getItem( strtolower( $fallbackCode ), 'pluralRuleTypes' );
4760  if ( $pluralRuleTypes ) {
4761  break;
4762  }
4763  }
4764  }
4765  return $pluralRuleTypes;
4766  }
4767 
4773  public function getPluralRuleIndexNumber( $number ) {
4774  $pluralRules = $this->getCompiledPluralRules();
4775  $form = Evaluator::evaluateCompiled( $number, $pluralRules );
4776  return $form;
4777  }
4778 
4787  public function getPluralRuleType( $number ) {
4788  $index = $this->getPluralRuleIndexNumber( $number );
4789  $pluralRuleTypes = $this->getPluralRuleTypes();
4790  if ( isset( $pluralRuleTypes[$index] ) ) {
4791  return $pluralRuleTypes[$index];
4792  } else {
4793  return 'other';
4794  }
4795  }
4796 }
utf8ToCodepoint($char)
Determine the Unicode codepoint of a single-character UTF-8 sequence.
#define the
table suitable for use with IDatabase::select()
formatTimePeriod($seconds, $format=[])
Definition: Language.php:4461
getDirMark($opposite=false)
A hidden direction mark (LRM or RLM), depending on the language direction.
Definition: Language.php:3070
static $mMonthAbbrevMsgs
Definition: Language.php:85
lc($str, $first=false)
Definition: Language.php:2678
$mParentLanguage
Definition: Language.php:47
getSpecialPageAliases()
Get special page names, as an associative array canonical name => array of valid names, including aliases.
Definition: Language.php:3184
getWeekdayAbbreviation($key)
Definition: Language.php:970
getDateFormatString($type, $pref)
Get a format string for a given type and preference.
Definition: Language.php:2183
getPluralRuleTypes()
Get the plural rule types for the language.
Definition: Language.php:4754
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:2456
static getLocalisationCache()
Get the LocalisationCache instance.
Definition: Language.php:402
convertPlural($count, $forms)
Plural form transformations, needed for some languages.
Definition: Language.php:3782
diff(MWTimestamp $relativeTo)
Calculate the difference between two MWTimestamp objects.
Wrapper around strtr() that holds replacements.
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
doMagicHook()
Run the LanguageGetMagic hook once.
Definition: Language.php:3130
wfGetDB($db, $groups=[], $wiki=false)
Get a Database object.
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:776
static isKnownLanguageTag($tag)
Returns true if a language code is an IETF tag known to MediaWiki.
Definition: Language.php:381
the array() calling protocol came about after MediaWiki 1.4rc1.
static insertSpace($string, $pattern)
Definition: Language.php:2855
null for the local wiki Added should default to null in handler for backwards compatibility add a value to it if you want to add a cookie that have to vary cache options can modify $query
Definition: hooks.txt:1435
static getTitleInvalidRegex()
Returns a simple regex that will match on characters and sequences invalid in titles.
convertGrammar($word, $case)
Grammatical transformations, needed for inflected languages Invoked by putting {{grammar:case|word}} ...
Definition: Language.php:3711
static LocalisationCache $dataCache
Definition: Language.php:62
isMultibyte($str)
Definition: Language.php:2694
initEncoding()
Definition: Language.php:2932
handleExplicitPluralForms($count, array $forms)
Handles explicit plural forms for Language::convertPlural()
Definition: Language.php:3812
hasVariant($variant)
Check if the language has the specific variant.
Definition: Language.php:4004
if(count($args)==0) $dir
markNoConversion($text, $noParse=false)
Prepare external link text for conversion.
Definition: Language.php:4117
setNamespaces(array $namespaces)
Arbitrarily set all of the namespace names at once.
Definition: Language.php:499
format($format)
Format the timestamp in a given format.
recodeForEdit($s)
Definition: Language.php:2941
static HashBagOStuff null $languageNameCache
Cache for language names.
Definition: Language.php:148
transformUsingPairFile($file, $string)
Transform a string using serialized data stored in the given file (which must be in the serialized su...
Definition: Language.php:2990
date($ts, $adj=false, $format=true, $timecorrection=false)
Definition: Language.php:2217
getHtmlCode()
Get the code in BCP 47 format which we can use inside of html lang="" tags.
Definition: Language.php:4206
truncate_skip(&$ret, $text, $search, $start, $len=null)
truncateHtml() helper function like strcspn() but adds the skipped chars to $ret
Definition: Language.php:3666
static isWellFormedLanguageTag($code, $lenient=false)
Returns true if a language code string is a well-formed language tag according to RFC 5646...
Definition: Language.php:279
$IP
Definition: WebStart.php:58
equals(Language $lang)
Compare with an other language object.
Definition: Language.php:4180
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
removeBadCharFirst($string)
Remove bytes that represent an incomplete Unicode character at the start of string (e...
Definition: Language.php:3522
convertNamespace($ns)
Convert a namespace index to a string in the preferred variant.
Definition: Language.php:3984
static $IRANIAN_DAYS
Definition: Language.php:1546
static getMessageFor($key, $code)
Get a message for a given language.
Definition: Language.php:4377
lcfirst($str)
Definition: Language.php:2659
offsetForUser(User $user)
Adjust the timestamp depending on the given user's preferences.
getLocalNsIndex($text)
Get a namespace key by value, case insensitive.
Definition: Language.php:608
alignEnd()
Return 'right' or 'left' as appropriate alignment for line-end for this language's text direction...
Definition: Language.php:3038
getFallbackLanguages()
Definition: Language.php:442
firstChar($s)
Get the first character of a string.
Definition: Language.php:2877
globals txt Globals are evil The original MediaWiki code relied on globals for processing context far too often MediaWiki development since then has been a story of slowly moving context out of global variables and into objects Storing processing context in object member variables allows those objects to be reused in a much more flexible way Consider the elegance of
database rows
Definition: globals.txt:10
$wgLangObjCacheSize
Language cache size, or really how many languages can we handle simultaneously without degrading to c...
if(!isset($args[0])) $lang
$namespaceAliases
Definition: Language.php:52
hasVariants()
Check if this is a language with variants.
Definition: Language.php:3993
getCompiledPluralRules()
Get the compiled plural rules for the language.
Definition: Language.php:4716
getNsIndex($text)
Get a namespace key by value, case insensitive.
Definition: Language.php:691
getNsText($index)
Get a namespace value by key.
Definition: Language.php:538
This code would result in ircNotify being run twice when an article is and once for brion Hooks can return three possible true was required This is the default since MediaWiki *some string
Definition: hooks.txt:177
iconv($in, $out, $string)
Definition: Language.php:2579
$value
convertCategoryKey($key)
Definition: Language.php:4023
$wgMetaNamespace
Name of the project namespace.
formatComputingNumbers($size, $boundary, $messageKey)
Definition: Language.php:4571
fallback8bitEncoding()
Definition: Language.php:2789
static getFallbacksIncludingSiteLanguage($code)
Get the ordered list of fallback languages, ending with the fallback language chain for the site lang...
Definition: Language.php:4334
static array $durationIntervals
Definition: Language.php:124
formatNumNoSeparators($number)
Front-end for non-commafied formatNum.
Definition: Language.php:3257
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:3689
A fake language converter.
linkTrail()
A regular expression to match legal word-trailing characters which should be merged onto a link of th...
Definition: Language.php:4132
getAllMessages()
Definition: Language.php:2569
wfUrlProtocolsWithoutProtRel()
Like wfUrlProtocols(), but excludes '//' from the protocol list.
normalize($s)
Convert a UTF-8 string to normal form C.
Definition: Language.php:2965
$wgNamespaceAliases
Namespace aliases.
static getCodeFromFileName($filename, $prefix= 'Language', $suffix= '.php')
Get the language code from a file name.
Definition: Language.php:4230
internalUserTimeAndDate($type, $ts, User $user, array $options)
Internal helper function for userDate(), userTime() and userTimeAndDate()
Definition: Language.php:2352
The MediaWiki class is the helper class for the index.php entry point.
Definition: MediaWiki.php:28
static getMessageKeysFor($code)
Get all message keys for a given language.
Definition: Language.php:4389
static tsToHijri($ts)
Converting Gregorian dates to Hijri dates.
Definition: Language.php:1618
formatExpiry($expiry, $format=true, $infinity= 'infinity')
Decode an expiry (block, protection, etc) which has come from the DB.
Definition: Language.php:4432
getVariants()
Get the list of variants supported by this language see sample implementation in LanguageZh.php.
Definition: Language.php:4033
$dateFormatStrings
Definition: Language.php:49
Represents a title within MediaWiki.
Definition: Title.php:36
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:2152
getNamespaceAliases()
Definition: Language.php:617
when a variable name is used in a it is silently declared as a new local masking the global
Definition: design.txt:93
autoConvertToAllVariants($text)
convert text to all supported variants
Definition: Language.php:3954
static getFallbacksFor($code)
Get the ordered list of fallback languages.
Definition: Language.php:4317
static $mMonthMsgs
Definition: Language.php:75
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
$wgExtraGenderNamespaces
Same as above, but for namespaces with gender distinction.
static $mLangObjCache
Definition: Language.php:64
parseFormattedNumber($number)
Definition: Language.php:3265
$wgTranslateNumerals
For Hindi and Arabic use local numerals instead of Western style (0-9) numerals in interface...
see documentation in includes Linker php for Linker::makeImageLink & $time
Definition: hooks.txt:1629
static $mWeekdayAbbrevMsgs
Definition: Language.php:71
$mMagicExtensions
Definition: Language.php:46
getWeekdayName($key)
Definition: Language.php:962
the value to return A Title object or null for latest to be modified or replaced by the hook handler or if authentication is not possible after cache objects are set for highlighting & $link
Definition: hooks.txt:2621
setCode($code)
Definition: Language.php:4216
static $pdf
Definition: Language.php:155
static newFromCode($code)
Create a language object for a given language code.
Definition: Language.php:205
The User object encapsulates all of the user-specific settings (user_id, name, rights, email address, options, last login time).
Definition: User.php:47
time($ts, $adj=false, $format=true, $timecorrection=false)
Definition: Language.php:2236
embedBidi($text= '')
Wraps argument with unicode control characters for directionality safety.
Definition: Language.php:3856
__construct()
Definition: Language.php:411
static getJsonMessagesFileName($code)
Definition: Language.php:4285
wfTimestamp($outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
$wgLanguageCode
Site language code.
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:1816
formatSize($size)
Format a size in bytes for output, using an appropriate unit (B, KB, MB, GB, TB, PB, EB, ZB or YB) according to the magnitude in question.
Definition: Language.php:4609
semicolonList(array $list)
Take a list of strings and build a locale-friendly semicolon-separated list, using the local semicolo...
Definition: Language.php:3411
addMagicWordsByLang($newWords)
Add magic words to the extension array.
Definition: Language.php:3169
segmentForDiff($text)
languages like Chinese need to be segmented in order for the diff to be of any use ...
Definition: Language.php:3924
static $lre
Unicode directional formatting characters, for embedBidi()
Definition: Language.php:153
convertHtml($text, $isTitle=false)
Perform output conversion on a string, and encode for safe HTML output.
Definition: Language.php:4015
when a variable name is used in a function
Definition: design.txt:93
ucwordbreaksCallbackAscii($matches)
Definition: Language.php:2596
const NS_PROJECT
Definition: Defines.php:73
static $mHijriCalendarMonthMsgs
Definition: Language.php:113
ucwordsCallbackMB($matches)
Definition: Language.php:2612
linkPrefixCharset()
A regular expression character set to match legal word-prefixing characters which should be merged on...
Definition: Language.php:4142
ucwords($str)
Definition: Language.php:2702
static getMain()
Static methods.
getIranianCalendarMonthName($key)
Definition: Language.php:978
getPreferredVariant()
Definition: Language.php:4040
LanguageConverter $mConverter
Definition: Language.php:43
msg($msg)
Get message object in this language.
Definition: Language.php:908
__destruct()
Reduce memory usage.
Definition: Language.php:425
formatBitrate($bps)
Format a bitrate for output, using an appropriate unit (bps, kbps, Mbps, Gbps, Tbps, Pbps, Ebps, Zbps or Ybps) according to the magnitude in question.
Definition: Language.php:4561
truncateHtml($text, $length, $ellipsis= '...')
Truncate a string of valid HTML to a specified length in bytes, appending an optional string (e...
Definition: Language.php:3548
$wgLocalisationCacheConf
Localisation cache configuration.
getArrow($direction= 'forwards')
An arrow, depending on the language direction.
Definition: Language.php:3093
static array static array $fallbackLanguageCache
Cache for language fallbacks.
Definition: Language.php:142
wfWarn($msg, $callerOffset=1, $level=E_USER_NOTICE)
Send a warning either to the debug log or in a PHP error depending on $wgDevelopmentWarnings.
static convertDoubleWidth($string)
convert double-width roman characters to single-width.
Definition: Language.php:2835
getDefaultVariant()
Definition: Language.php:4047
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context $options
Definition: hooks.txt:1020
viewPrevNext(Title $title, $offset, $limit, array $query=[], $atend=false)
Generate (prev x| next x) (20|50|100...) type links for paging.
Definition: Language.php:4645
getConverter()
Return the LanguageConverter used in the Language.
Definition: Language.php:3944
static $mIranianCalendarMonthMsgs
Definition: Language.php:90
static hebrewNumeral($num)
Hebrew Gematria number formatting up to 9999.
Definition: Language.php:1983
const NS_PROJECT_TALK
Definition: Defines.php:74
getFormattedNsText($index)
A convenience function that returns the same thing as getNsText() except with '_' changed to ' '...
Definition: Language.php:556
getMonthAbbreviation($key)
Definition: Language.php:943
getLocalURL($query= '', $query2=false)
Get a URL with no fragment or server name (relative URL) from a Title object.
Definition: Title.php:1688
static tsToHebrew($ts)
Converting Gregorian dates to Hebrew dates.
Definition: Language.php:1670
getBookstoreList()
Exports $wgBookstoreListEn.
Definition: Language.php:450
capitalizeAllNouns()
Definition: Language.php:3082
getMonthNameGen($key)
Definition: Language.php:935
listToText(array $l)
Take a list of strings and build a locale-friendly comma-separated list, using the local comma-separa...
Definition: Language.php:3369
MediaWiki exception.
Definition: MWException.php:26
$wgDummyLanguageCodes
List of language codes that don't correspond to an actual language.
caseFold($s)
Return a case-folded representation of $s.
Definition: Language.php:2766
static getMessagesFileName($code)
Definition: Language.php:4272
Internationalisation code.
Definition: Language.php:39
$wgAllUnicodeFixes
Set this to always convert certain Unicode sequences to modern ones regardless of the content languag...
static isValidBuiltInCode($code)
Returns true if a language code is of a valid form for the purposes of internal customisation of Medi...
Definition: Language.php:358
$cache
Definition: mcc.php:33
getTimestamp($style=TS_UNIX)
Get the timestamp represented by this object in a certain form.
getMonthName($key)
Definition: Language.php:916
static isValidCode($code)
Returns true if a language code string is of a valid form, whether or not it exists.
Definition: Language.php:333
uc($str, $first=false)
Convert a string to uppercase.
Definition: Language.php:2643
static getMessagesFor($code)
Get all messages for a given language WARNING: this may take a long time.
Definition: Language.php:4365
ucwordbreaksCallbackMB($matches)
Definition: Language.php:2604
static $strongDirRegex
Directionality test regex for embedBidi().
Definition: Language.php:170
needsGenderDistinction()
Whether this language uses gender-dependent namespace aliases.
Definition: Language.php:584
wfIsInfinity($str)
Determine input string is represents as infinity.
getHebrewCalendarMonthNameGen($key)
Definition: Language.php:994
getURLVariant()
Definition: Language.php:4054
updateConversionTable(Title $title)
Refresh the cache of conversion tables when MediaWiki:Conversiontable* is updated.
Definition: Language.php:4101
convertTitle($title)
Convert a Title object to a string in the preferred variant.
Definition: Language.php:3974
const DB_SLAVE
Definition: Defines.php:46
static $rle
Definition: Language.php:154
convert($text)
convert text to different variants of a language.
Definition: Language.php:3964
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 just before the function returns a value If you return an< a > element with HTML attributes $attribs and contents $html will be returned If you return $ret will be returned after processing after in associative array form externallinks including delete and has completed for all link tables whether this was an auto creation default is conds Array Extra conditions for the No matching items in log is displayed if loglist is empty msgKey Array If you want a nice box with a set this to the key of the message First element is the message additional optional elements are parameters for the key that are processed with wfMessage() -> params() ->parseAsBlock()-offset Set to overwrite offset parameter in $wgRequest set to ''to unsetoffset-wrap String Wrap the message in html(usually something like"&lt
wfBCP47($code)
Get the normalised IETF language tag See unit test for examples.
$wgMetaNamespaceTalk
Name of the project talk namespace.
Allows to change the fields on the form that will be generated are created Can be used to omit specific feeds from being outputted You must not use this hook to add use OutputPage::addFeedLink() instead.&$feedLinks conditions will AND in the final query as a Content object as a Content object $title
Definition: hooks.txt:312
getNamespaceIds()
Definition: Language.php:661
getVariantname($code, $usemsg=true)
short names for language variants used for language conversion links.
Definition: Language.php:708
static getFileName($prefix= 'Language', $code, $suffix= '.php')
Get the name of a file for a certain language code.
Definition: Language.php:4260
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:2275
static run($event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:131
static getSuggestedDurations($lang=null)
Get an array of suggested block durations from MediaWiki:Ipboptions.
ucwordbreaks($str)
capitalize words at word breaks
Definition: Language.php:2726
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books $tag
Definition: hooks.txt:981
userTime($ts, User $user, array $options=[])
Get the formatted time for the given timestamp and formatted for the given user.
Definition: Language.php:2414
getMessage($key)
Definition: Language.php:2562
static $mHebrewCalendarMonthMsgs
Definition: Language.php:97
pipeList(array $list)
Same as commaList, but separate it with the pipe instead.
Definition: Language.php:3423
getMessageFromDB($msg)
Get a message from the MediaWiki namespace.
Definition: Language.php:898
formatNum($number, $nocommafy=false)
Normally we output all numbers in plain en_US style, that is 293,291.235 for twohundredninetythreetho...
Definition: Language.php:3229
recodeInput($s)
Definition: Language.php:2950
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:1816
$mExtendedSpecialPageAliases
Definition: Language.php:50
namespace and then decline to actually register it & $namespaces
Definition: hooks.txt:927
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
getPluralRules()
Get the plural rules for the language.
Definition: Language.php:4735
getDefaultDateFormat()
Definition: Language.php:739
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:776
static fetchLanguageName($code, $inLanguage=null, $include= 'all')
Definition: Language.php:886
resetNamespaces()
Resets all of the namespace caches.
Definition: Language.php:507
please add to it if you re going to add events to the MediaWiki code where normally authentication against an external auth plugin would be creating a local account $user
Definition: hooks.txt:242
segmentByWord($string)
Some languages such as Chinese require word segmentation, Specify such segmentation when overridden i...
Definition: Language.php:2812
getMagicWords()
Get all magic words from cache.
Definition: Language.php:3123
getMonthNamesArray()
Definition: Language.php:923
numLink(Title $title, $offset, $limit, array $query, $link, $tooltipMsg, $class)
Helper function for viewPrevNext() that generates links.
Definition: Language.php:4691
ucfirst($str)
Make a string's first character uppercase.
Definition: Language.php:2623
getOption($oname, $defaultOverride=null, $ignoreHidden=false)
Get the user's current setting for a given option.
Definition: User.php:2915
getHijriCalendarMonthName($key)
Definition: Language.php:1002
static getFallbackFor($code)
Get the first fallback for a given language.
Definition: Language.php:4302
timeanddate($ts, $adj=false, $format=true, $timecorrection=false)
Definition: Language.php:2256
getPluralRuleType($number)
Find the plural rule type appropriate for the given number For example, if the language is set to Ara...
Definition: Language.php:4787
userAdjust($ts, $tz=false)
Used by date() and time() to adjust the time output.
Definition: Language.php:2070
digitTransformTable()
Definition: Language.php:3349
userDate($ts, User $user, array $options=[])
Get the formatted date for the given timestamp and formatted for the given user.
Definition: Language.php:2391
getConvRuleTitle()
Get the conversion rule title, if any.
Definition: Language.php:4707
const TS_MW
MediaWiki concatenated string timestamp (YYYYMMDDHHMMSS)
getMagic($mw)
Fill a MagicWord object with data from here.
Definition: Language.php:3143
static classFromCode($code)
Definition: Language.php:4244
$wgExtraNamespaces
Additional namespaces.
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
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:2493
getCode()
Get the internal language code for this language object.
Definition: Language.php:4192
replaceGrammarInNamespace($m)
Definition: Language.php:4418
commafy($number)
Adds commas to a given number.
Definition: Language.php:3290
digitGroupingPattern()
Definition: Language.php:3342
sprintfDate($format, $ts, DateTimeZone $zone=null, &$ttl= 'unused')
This is a workalike of PHP's date() function, but with better internationalisation, a reduced set of format characters, and a better escaping format.
Definition: Language.php:1090
$digitGroupingPattern
Definition: MessagesAs.php:167
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:2437
translateBlockExpiry($str, User $user=null)
Definition: Language.php:3882
truncate($string, $length, $ellipsis= '...', $adjustLength=true)
Truncate a string to a specified length in bytes, appending an optional string (e.g.
Definition: Language.php:3447
static romanNumeral($num)
Roman number formatting up to 10000.
Definition: Language.php:1952
convertForSearchResult($termsArray)
Definition: Language.php:2865
removeBadCharLast($string)
Remove bytes that represent an incomplete Unicode character at the end of string (e.g.
Definition: Language.php:3496
return['DBLoadBalancerFactory'=> function(MediaWikiServices $services){$config=$services->getMainConfig() ->get( 'LBFactoryConf');$class=LBFactory::getLBFactoryClass($config);if(!isset($config['readOnlyReason'])){$config['readOnlyReason']=wfConfiguredReadOnlyReason();}return new $class($config);},'DBLoadBalancer'=> function(MediaWikiServices $services){return $services->getDBLoadBalancerFactory() ->getMainLB();},'SiteStore'=> function(MediaWikiServices $services){$rawSiteStore=new DBSiteStore($services->getDBLoadBalancer());$cache=wfGetCache(wfIsHHVM()?CACHE_ACCEL:CACHE_ANYTHING);return new CachingSiteStore($rawSiteStore, $cache);},'SiteLookup'=> function(MediaWikiServices $services){return $services->getSiteStore();},'ConfigFactory'=> function(MediaWikiServices $services){$registry=$services->getBootstrapConfig() ->get( 'ConfigRegistry');$factory=new ConfigFactory();foreach($registry as $name=> $callback){$factory->register($name, $callback);}return $factory;},'MainConfig'=> function(MediaWikiServices $services){return $services->getConfigFactory() ->makeConfig( 'main');},'InterwikiLookup'=> function(MediaWikiServices $services){global $wgContLang;$config=$services->getMainConfig();return new ClassicInterwikiLookup($wgContLang, ObjectCache::getMainWANInstance(), $config->get( 'InterwikiExpiry'), $config->get( 'InterwikiCache'), $config->get( 'InterwikiScopes'), $config->get( 'InterwikiFallbackSite'));},'StatsdDataFactory'=> function(MediaWikiServices $services){return new BufferingStatsdDataFactory(rtrim($services->getMainConfig() ->get( 'StatsdMetricPrefix'), '.'));},'EventRelayerGroup'=> function(MediaWikiServices $services){return new EventRelayerGroup($services->getMainConfig() ->get( 'EventRelayerConfig'));},'SearchEngineFactory'=> function(MediaWikiServices $services){return new SearchEngineFactory($services->getSearchEngineConfig());},'SearchEngineConfig'=> function(MediaWikiServices $services){global $wgContLang;return new SearchEngineConfig($services->getMainConfig(), $wgContLang);},'SkinFactory'=> function(MediaWikiServices $services){$factory=new SkinFactory();$names=$services->getMainConfig() ->get( 'ValidSkinNames');foreach($names as $name=> $skin){$factory->register($name, $skin, function() use($name, $skin){$class="Skin$skin";return new $class($name);});}$factory->register( 'fallback', 'Fallback', function(){return new SkinFallback;});$factory->register( 'apioutput', 'ApiOutput', function(){return new SkinApi;});return $factory;},'WatchedItemStore'=> function(MediaWikiServices $services){$store=new WatchedItemStore($services->getDBLoadBalancer(), new HashBagOStuff([ 'maxKeys'=> 100]));$store->setStatsdDataFactory($services->getStatsdDataFactory());return $store;},'WatchedItemQueryService'=> function(MediaWikiServices $services){return new WatchedItemQueryService($services->getDBLoadBalancer());},'LinkCache'=> function(MediaWikiServices $services){return new LinkCache($services->getTitleFormatter());},'LinkRendererFactory'=> function(MediaWikiServices $services){return new LinkRendererFactory($services->getTitleFormatter(), $services->getLinkCache());},'LinkRenderer'=> function(MediaWikiServices $services){global $wgUser;if(defined( 'MW_NO_SESSION')){return $services->getLinkRendererFactory() ->create();}else{return $services->getLinkRendererFactory() ->createForUser($wgUser);}},'GenderCache'=> function(MediaWikiServices $services){return new GenderCache();},'_MediaWikiTitleCodec'=> function(MediaWikiServices $services){global $wgContLang;return new MediaWikiTitleCodec($wgContLang, $services->getGenderCache(), $services->getMainConfig() ->get( 'LocalInterwikis'));},'TitleFormatter'=> function(MediaWikiServices $services){return $services->getService( '_MediaWikiTitleCodec');},'TitleParser'=> function(MediaWikiServices $services){return $services->getService( '_MediaWikiTitleCodec');},]
$namespaceNames
Definition: Language.php:52
static tsToIranian($ts)
Algorithm by Roozbeh Pournader and Mohammad Toossi to convert Gregorian dates to Iranian dates...
Definition: Language.php:1560
$transformData
ReplacementArray object caches.
Definition: Language.php:57
specialList($page, $details, $oppositedm=true)
Make a list item, used by various special pages.
Definition: Language.php:4622
getExtraHashOptions()
returns language specific options used by User::getPageRenderHash() for example, the preferred langua...
Definition: Language.php:4080
getParsedTitle()
For languages that support multiple variants, the title of an article may be displayed differently in...
Definition: Language.php:4091
preConvertPlural($forms, $count)
Checks that convertPlural was given an array and pads it to requested amount of forms by copying the ...
Definition: Language.php:3833
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:4070
$wgGrammarForms
Some languages need different word forms, usually for different cases.
emphasize($text)
Italic is unsuitable for some languages.
Definition: Language.php:3203
getDirMarkEntity($opposite=false)
A hidden direction mark (LRM or RLM), depending on the language direction.
Definition: Language.php:3053
separatorTransformTable()
Definition: Language.php:3356
isRTL()
For right-to-left language support.
Definition: Language.php:3006
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context the output can only depend on parameters provided to this hook not on global state indicating whether full HTML should be generated If generation of HTML may be but other information should still be present in the ParserOutput object to manipulate or replace but no entry for that model exists in $wgContentHandlers if desired whether it is OK to use $contentModel on $title Handler functions that modify $ok should generally return false to prevent further hooks from further modifying $ok inclusive $limit
Definition: hooks.txt:1020
getDatePreference()
Get the user's preferred date format.
Definition: User.php:3237
getParentLanguage()
Get the "parent" language which has a converter to convert a "compatible" language (in another varian...
Definition: Language.php:4153
getDir()
Return the correct HTML 'dir' attribute value for this language.
Definition: Language.php:3014
getFormattedNamespaces()
A convenience function that returns getNamespaces() with spaces instead of underscores in values...
Definition: Language.php:519
$wgLocalTZoffset
Set an offset from UTC in minutes to use for the default timezone setting for anonymous users and new...
checkTitleEncoding($s)
Definition: Language.php:2775
$count
normalizeForSearch($string)
Some languages have special punctuation need to be normalized.
Definition: Language.php:2823
static $mMonthGenMsgs
Definition: Language.php:80
getDurationIntervals($seconds, array $chosenIntervals=[])
Takes a number of seconds and returns an array with a set of corresponding intervals.
Definition: Language.php:2301
static $mWeekdayMsgs
Definition: Language.php:66
linkPrefixExtension()
To allow "foo[[bar]]" to extend the link over the whole word "foobar".
Definition: Language.php:3115
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
$mNamespaceIds
Definition: Language.php:52
getGrammarForms()
Get the grammar forms for the content language.
Definition: Language.php:3724
static dateTimeObjFormat(&$dateTimeObj, $ts, $zone, $code)
Pass through result from $dateTimeObj->format()
Definition: Language.php:1014
=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
getGenderNsText($index, $gender)
Returns gender-dependent namespace alias if available.
Definition: Language.php:569
static getDefaultOption($opt)
Get a given default option value.
Definition: User.php:1598
initContLang()
Hook which will be called if this is the content language.
Definition: Language.php:435
static factory($code)
Get a cached or new language object for a given language code.
Definition: Language.php:179
fixVariableInNamespace($talk)
Definition: Language.php:4397
getNamespaces()
Returns an array of localised namespaces indexed by their numbers.
Definition: Language.php:460
wfGetPrecompiledData($name)
Get an object from the precompiled serialized directory.
$mMagicHookDone
Definition: Language.php:46
gender($gender, $forms)
Provides an alternative text depending on specified gender.
Definition: Language.php:3753
commaList(array $list)
Take a list of strings and build a locale-friendly comma-separated list, using the local comma-separa...
Definition: Language.php:3398
static $mHebrewCalendarMonthGenMsgs
Definition: Language.php:105
const NS_USER_TALK
Definition: Defines.php:72
static array $languagesWithVariants
languages supporting variants
static element($element, $attribs=[], $contents= '')
Identical to rawElement(), but HTML-escapes $contents (like Xml::element()).
Definition: Html.php:230
static getCanonicalIndex($name)
Returns the index for a given canonical name, or NULL The input must be converted to lower case first...
static getCanonicalNamespaces($rebuild=false)
Returns array of all defined namespaces with their canonical (English) names.
getMonthAbbreviationsArray()
Definition: Language.php:950
Library for creating and parsing MW-style timestamps.
Definition: MWTimestamp.php:31
do that in ParserLimitReportFormat instead use this to modify the parameters of the image and a DIV can begin in one section and end in another Make sure your code can handle that case gracefully See the EditSectionClearerLink extension for an example zero but section is usually empty its values are the globals values before the output is cached one of or reset my talk my contributions etc etc otherwise the built in rate limiting checks are if enabled allows for interception of redirect as a string mapping parameter names to values & $type
Definition: hooks.txt:2376
hasWordBreaks()
Most writing systems use whitespace to break up words.
Definition: Language.php:2801
static strongDirFromContent($text= '')
Gets directionality of the first strongly directional codepoint, for embedBidi()
Definition: Language.php:1935
static isSupportedLanguage($code)
Checks whether any localisation is available for that language tag in MediaWiki (MessagesXx.php exists).
Definition: Language.php:251
alignStart()
Return 'left' or 'right' as appropriate alignment for line-start for this language's text direction...
Definition: Language.php:3026
static tsToYear($ts, $cName)
Algorithm to convert Gregorian dates to Thai solar dates, Minguo dates or Minguo dates.
Definition: Language.php:1849
getPluralRuleIndexNumber($number)
Find the index number of the plural rule appropriate for the given number.
Definition: Language.php:4773
static hebrewYearStart($year)
This calculates the Hebrew year start, as days since 1 September.
Definition: Language.php:1811
do that in ParserLimitReportFormat instead use this to modify the parameters of the image and a DIV can begin in one section and end in another Make sure your code can handle that case gracefully See the EditSectionClearerLink extension for an example zero but section is usually empty its values are the globals values before the output is cached $page
Definition: hooks.txt:2376
static $GREG_DAYS
Definition: Language.php:1545
getHebrewCalendarMonthName($key)
Definition: Language.php:986
$wgUser
Definition: Setup.php:801
unsegmentForDiff($text)
and unsegment to show the result
Definition: Language.php:3934
$matches
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:310