MediaWiki  1.27.2
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 
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 
944  return $this->getMessageFromDB( self::$mMonthAbbrevMsgs[$key - 1] );
945  }
946 
951  $monthNames = [ '' ];
952  for ( $i = 1; $i < 13; $i++ ) {
953  $monthNames[] = $this->getMonthAbbreviation( $i );
954  }
955  return $monthNames;
956  }
957 
962  function getWeekdayName( $key ) {
963  return $this->getMessageFromDB( self::$mWeekdayMsgs[$key - 1] );
964  }
965 
971  return $this->getMessageFromDB( self::$mWeekdayAbbrevMsgs[$key - 1] );
972  }
973 
979  return $this->getMessageFromDB( self::$mIranianCalendarMonthMsgs[$key - 1] );
980  }
981 
987  return $this->getMessageFromDB( self::$mHebrewCalendarMonthMsgs[$key - 1] );
988  }
989 
995  return $this->getMessageFromDB( self::$mHebrewCalendarMonthGenMsgs[$key - 1] );
996  }
997 
1003  return $this->getMessageFromDB( self::$mHijriCalendarMonthMsgs[$key - 1] );
1004  }
1005 
1014  private static function dateTimeObjFormat( &$dateTimeObj, $ts, $zone, $code ) {
1015  if ( !$dateTimeObj ) {
1016  $dateTimeObj = DateTime::createFromFormat(
1017  'YmdHis', $ts, $zone ?: new DateTimeZone( 'UTC' )
1018  );
1019  }
1020  return $dateTimeObj->format( $code );
1021  }
1022 
1090  function sprintfDate( $format, $ts, DateTimeZone $zone = null, &$ttl = null ) {
1091  $s = '';
1092  $raw = false;
1093  $roman = false;
1094  $hebrewNum = false;
1095  $dateTimeObj = false;
1096  $rawToggle = false;
1097  $iranian = false;
1098  $hebrew = false;
1099  $hijri = false;
1100  $thai = false;
1101  $minguo = false;
1102  $tenno = false;
1103 
1104  $usedSecond = false;
1105  $usedMinute = false;
1106  $usedHour = false;
1107  $usedAMPM = false;
1108  $usedDay = false;
1109  $usedWeek = false;
1110  $usedMonth = false;
1111  $usedYear = false;
1112  $usedISOYear = false;
1113  $usedIsLeapYear = false;
1114 
1115  $usedHebrewMonth = false;
1116  $usedIranianMonth = false;
1117  $usedHijriMonth = false;
1118  $usedHebrewYear = false;
1119  $usedIranianYear = false;
1120  $usedHijriYear = false;
1121  $usedTennoYear = false;
1122 
1123  if ( strlen( $ts ) !== 14 ) {
1124  throw new MWException( __METHOD__ . ": The timestamp $ts should have 14 characters" );
1125  }
1126 
1127  if ( !ctype_digit( $ts ) ) {
1128  throw new MWException( __METHOD__ . ": The timestamp $ts should be a number" );
1129  }
1130 
1131  $formatLength = strlen( $format );
1132  for ( $p = 0; $p < $formatLength; $p++ ) {
1133  $num = false;
1134  $code = $format[$p];
1135  if ( $code == 'x' && $p < $formatLength - 1 ) {
1136  $code .= $format[++$p];
1137  }
1138 
1139  if ( ( $code === 'xi'
1140  || $code === 'xj'
1141  || $code === 'xk'
1142  || $code === 'xm'
1143  || $code === 'xo'
1144  || $code === 'xt' )
1145  && $p < $formatLength - 1 ) {
1146  $code .= $format[++$p];
1147  }
1148 
1149  switch ( $code ) {
1150  case 'xx':
1151  $s .= 'x';
1152  break;
1153  case 'xn':
1154  $raw = true;
1155  break;
1156  case 'xN':
1157  $rawToggle = !$rawToggle;
1158  break;
1159  case 'xr':
1160  $roman = true;
1161  break;
1162  case 'xh':
1163  $hebrewNum = true;
1164  break;
1165  case 'xg':
1166  $usedMonth = true;
1167  $s .= $this->getMonthNameGen( substr( $ts, 4, 2 ) );
1168  break;
1169  case 'xjx':
1170  $usedHebrewMonth = true;
1171  if ( !$hebrew ) {
1172  $hebrew = self::tsToHebrew( $ts );
1173  }
1174  $s .= $this->getHebrewCalendarMonthNameGen( $hebrew[1] );
1175  break;
1176  case 'd':
1177  $usedDay = true;
1178  $num = substr( $ts, 6, 2 );
1179  break;
1180  case 'D':
1181  $usedDay = true;
1182  $s .= $this->getWeekdayAbbreviation(
1183  Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'w' ) + 1
1184  );
1185  break;
1186  case 'j':
1187  $usedDay = true;
1188  $num = intval( substr( $ts, 6, 2 ) );
1189  break;
1190  case 'xij':
1191  $usedDay = true;
1192  if ( !$iranian ) {
1193  $iranian = self::tsToIranian( $ts );
1194  }
1195  $num = $iranian[2];
1196  break;
1197  case 'xmj':
1198  $usedDay = true;
1199  if ( !$hijri ) {
1200  $hijri = self::tsToHijri( $ts );
1201  }
1202  $num = $hijri[2];
1203  break;
1204  case 'xjj':
1205  $usedDay = true;
1206  if ( !$hebrew ) {
1207  $hebrew = self::tsToHebrew( $ts );
1208  }
1209  $num = $hebrew[2];
1210  break;
1211  case 'l':
1212  $usedDay = true;
1213  $s .= $this->getWeekdayName(
1214  Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'w' ) + 1
1215  );
1216  break;
1217  case 'F':
1218  $usedMonth = true;
1219  $s .= $this->getMonthName( substr( $ts, 4, 2 ) );
1220  break;
1221  case 'xiF':
1222  $usedIranianMonth = true;
1223  if ( !$iranian ) {
1224  $iranian = self::tsToIranian( $ts );
1225  }
1226  $s .= $this->getIranianCalendarMonthName( $iranian[1] );
1227  break;
1228  case 'xmF':
1229  $usedHijriMonth = true;
1230  if ( !$hijri ) {
1231  $hijri = self::tsToHijri( $ts );
1232  }
1233  $s .= $this->getHijriCalendarMonthName( $hijri[1] );
1234  break;
1235  case 'xjF':
1236  $usedHebrewMonth = true;
1237  if ( !$hebrew ) {
1238  $hebrew = self::tsToHebrew( $ts );
1239  }
1240  $s .= $this->getHebrewCalendarMonthName( $hebrew[1] );
1241  break;
1242  case 'm':
1243  $usedMonth = true;
1244  $num = substr( $ts, 4, 2 );
1245  break;
1246  case 'M':
1247  $usedMonth = true;
1248  $s .= $this->getMonthAbbreviation( substr( $ts, 4, 2 ) );
1249  break;
1250  case 'n':
1251  $usedMonth = true;
1252  $num = intval( substr( $ts, 4, 2 ) );
1253  break;
1254  case 'xin':
1255  $usedIranianMonth = true;
1256  if ( !$iranian ) {
1257  $iranian = self::tsToIranian( $ts );
1258  }
1259  $num = $iranian[1];
1260  break;
1261  case 'xmn':
1262  $usedHijriMonth = true;
1263  if ( !$hijri ) {
1264  $hijri = self::tsToHijri( $ts );
1265  }
1266  $num = $hijri[1];
1267  break;
1268  case 'xjn':
1269  $usedHebrewMonth = true;
1270  if ( !$hebrew ) {
1271  $hebrew = self::tsToHebrew( $ts );
1272  }
1273  $num = $hebrew[1];
1274  break;
1275  case 'xjt':
1276  $usedHebrewMonth = true;
1277  if ( !$hebrew ) {
1278  $hebrew = self::tsToHebrew( $ts );
1279  }
1280  $num = $hebrew[3];
1281  break;
1282  case 'Y':
1283  $usedYear = true;
1284  $num = substr( $ts, 0, 4 );
1285  break;
1286  case 'xiY':
1287  $usedIranianYear = true;
1288  if ( !$iranian ) {
1289  $iranian = self::tsToIranian( $ts );
1290  }
1291  $num = $iranian[0];
1292  break;
1293  case 'xmY':
1294  $usedHijriYear = true;
1295  if ( !$hijri ) {
1296  $hijri = self::tsToHijri( $ts );
1297  }
1298  $num = $hijri[0];
1299  break;
1300  case 'xjY':
1301  $usedHebrewYear = true;
1302  if ( !$hebrew ) {
1303  $hebrew = self::tsToHebrew( $ts );
1304  }
1305  $num = $hebrew[0];
1306  break;
1307  case 'xkY':
1308  $usedYear = true;
1309  if ( !$thai ) {
1310  $thai = self::tsToYear( $ts, 'thai' );
1311  }
1312  $num = $thai[0];
1313  break;
1314  case 'xoY':
1315  $usedYear = true;
1316  if ( !$minguo ) {
1317  $minguo = self::tsToYear( $ts, 'minguo' );
1318  }
1319  $num = $minguo[0];
1320  break;
1321  case 'xtY':
1322  $usedTennoYear = true;
1323  if ( !$tenno ) {
1324  $tenno = self::tsToYear( $ts, 'tenno' );
1325  }
1326  $num = $tenno[0];
1327  break;
1328  case 'y':
1329  $usedYear = true;
1330  $num = substr( $ts, 2, 2 );
1331  break;
1332  case 'xiy':
1333  $usedIranianYear = true;
1334  if ( !$iranian ) {
1335  $iranian = self::tsToIranian( $ts );
1336  }
1337  $num = substr( $iranian[0], -2 );
1338  break;
1339  case 'a':
1340  $usedAMPM = true;
1341  $s .= intval( substr( $ts, 8, 2 ) ) < 12 ? 'am' : 'pm';
1342  break;
1343  case 'A':
1344  $usedAMPM = true;
1345  $s .= intval( substr( $ts, 8, 2 ) ) < 12 ? 'AM' : 'PM';
1346  break;
1347  case 'g':
1348  $usedHour = true;
1349  $h = substr( $ts, 8, 2 );
1350  $num = $h % 12 ? $h % 12 : 12;
1351  break;
1352  case 'G':
1353  $usedHour = true;
1354  $num = intval( substr( $ts, 8, 2 ) );
1355  break;
1356  case 'h':
1357  $usedHour = true;
1358  $h = substr( $ts, 8, 2 );
1359  $num = sprintf( '%02d', $h % 12 ? $h % 12 : 12 );
1360  break;
1361  case 'H':
1362  $usedHour = true;
1363  $num = substr( $ts, 8, 2 );
1364  break;
1365  case 'i':
1366  $usedMinute = true;
1367  $num = substr( $ts, 10, 2 );
1368  break;
1369  case 's':
1370  $usedSecond = true;
1371  $num = substr( $ts, 12, 2 );
1372  break;
1373  case 'c':
1374  case 'r':
1375  $usedSecond = true;
1376  // fall through
1377  case 'e':
1378  case 'O':
1379  case 'P':
1380  case 'T':
1381  $s .= Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1382  break;
1383  case 'w':
1384  case 'N':
1385  case 'z':
1386  $usedDay = true;
1387  $num = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1388  break;
1389  case 'W':
1390  $usedWeek = true;
1391  $num = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1392  break;
1393  case 't':
1394  $usedMonth = true;
1395  $num = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1396  break;
1397  case 'L':
1398  $usedIsLeapYear = true;
1399  $num = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1400  break;
1401  case 'o':
1402  $usedISOYear = true;
1403  $num = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1404  break;
1405  case 'U':
1406  $usedSecond = true;
1407  // fall through
1408  case 'I':
1409  case 'Z':
1410  $num = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1411  break;
1412  case '\\':
1413  # Backslash escaping
1414  if ( $p < $formatLength - 1 ) {
1415  $s .= $format[++$p];
1416  } else {
1417  $s .= '\\';
1418  }
1419  break;
1420  case '"':
1421  # Quoted literal
1422  if ( $p < $formatLength - 1 ) {
1423  $endQuote = strpos( $format, '"', $p + 1 );
1424  if ( $endQuote === false ) {
1425  # No terminating quote, assume literal "
1426  $s .= '"';
1427  } else {
1428  $s .= substr( $format, $p + 1, $endQuote - $p - 1 );
1429  $p = $endQuote;
1430  }
1431  } else {
1432  # Quote at end of string, assume literal "
1433  $s .= '"';
1434  }
1435  break;
1436  default:
1437  $s .= $format[$p];
1438  }
1439  if ( $num !== false ) {
1440  if ( $rawToggle || $raw ) {
1441  $s .= $num;
1442  $raw = false;
1443  } elseif ( $roman ) {
1444  $s .= Language::romanNumeral( $num );
1445  $roman = false;
1446  } elseif ( $hebrewNum ) {
1447  $s .= self::hebrewNumeral( $num );
1448  $hebrewNum = false;
1449  } else {
1450  $s .= $this->formatNum( $num, true );
1451  }
1452  }
1453  }
1454 
1455  if ( $usedSecond ) {
1456  $ttl = 1;
1457  } elseif ( $usedMinute ) {
1458  $ttl = 60 - substr( $ts, 12, 2 );
1459  } elseif ( $usedHour ) {
1460  $ttl = 3600 - substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
1461  } elseif ( $usedAMPM ) {
1462  $ttl = 43200 - ( substr( $ts, 8, 2 ) % 12 ) * 3600 -
1463  substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
1464  } elseif (
1465  $usedDay ||
1466  $usedHebrewMonth ||
1467  $usedIranianMonth ||
1468  $usedHijriMonth ||
1469  $usedHebrewYear ||
1470  $usedIranianYear ||
1471  $usedHijriYear ||
1472  $usedTennoYear
1473  ) {
1474  // @todo Someone who understands the non-Gregorian calendars
1475  // should write proper logic for them so that they don't need purged every day.
1476  $ttl = 86400 - substr( $ts, 8, 2 ) * 3600 -
1477  substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
1478  } else {
1479  $possibleTtls = [];
1480  $timeRemainingInDay = 86400 - substr( $ts, 8, 2 ) * 3600 -
1481  substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
1482  if ( $usedWeek ) {
1483  $possibleTtls[] =
1484  ( 7 - Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'N' ) ) * 86400 +
1485  $timeRemainingInDay;
1486  } elseif ( $usedISOYear ) {
1487  // December 28th falls on the last ISO week of the year, every year.
1488  // The last ISO week of a year can be 52 or 53.
1489  $lastWeekOfISOYear = DateTime::createFromFormat(
1490  'Ymd',
1491  substr( $ts, 0, 4 ) . '1228',
1492  $zone ?: new DateTimeZone( 'UTC' )
1493  )->format( 'W' );
1494  $currentISOWeek = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'W' );
1495  $weeksRemaining = $lastWeekOfISOYear - $currentISOWeek;
1496  $timeRemainingInWeek =
1497  ( 7 - Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'N' ) ) * 86400
1498  + $timeRemainingInDay;
1499  $possibleTtls[] = $weeksRemaining * 604800 + $timeRemainingInWeek;
1500  }
1501 
1502  if ( $usedMonth ) {
1503  $possibleTtls[] =
1504  ( Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 't' ) -
1505  substr( $ts, 6, 2 ) ) * 86400
1506  + $timeRemainingInDay;
1507  } elseif ( $usedYear ) {
1508  $possibleTtls[] =
1509  ( Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'L' ) + 364 -
1510  Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'z' ) ) * 86400
1511  + $timeRemainingInDay;
1512  } elseif ( $usedIsLeapYear ) {
1513  $year = substr( $ts, 0, 4 );
1514  $timeRemainingInYear =
1515  ( Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'L' ) + 364 -
1516  Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'z' ) ) * 86400
1517  + $timeRemainingInDay;
1518  $mod = $year % 4;
1519  if ( $mod || ( !( $year % 100 ) && $year % 400 ) ) {
1520  // this isn't a leap year. see when the next one starts
1521  $nextCandidate = $year - $mod + 4;
1522  if ( $nextCandidate % 100 || !( $nextCandidate % 400 ) ) {
1523  $possibleTtls[] = ( $nextCandidate - $year - 1 ) * 365 * 86400 +
1524  $timeRemainingInYear;
1525  } else {
1526  $possibleTtls[] = ( $nextCandidate - $year + 3 ) * 365 * 86400 +
1527  $timeRemainingInYear;
1528  }
1529  } else {
1530  // this is a leap year, so the next year isn't
1531  $possibleTtls[] = $timeRemainingInYear;
1532  }
1533  }
1534 
1535  if ( $possibleTtls ) {
1536  $ttl = min( $possibleTtls );
1537  }
1538  }
1539 
1540  return $s;
1541  }
1542 
1543  private static $GREG_DAYS = [ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ];
1544  private static $IRANIAN_DAYS = [ 31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29 ];
1545 
1558  private static function tsToIranian( $ts ) {
1559  $gy = substr( $ts, 0, 4 ) -1600;
1560  $gm = substr( $ts, 4, 2 ) -1;
1561  $gd = substr( $ts, 6, 2 ) -1;
1562 
1563  # Days passed from the beginning (including leap years)
1564  $gDayNo = 365 * $gy
1565  + floor( ( $gy + 3 ) / 4 )
1566  - floor( ( $gy + 99 ) / 100 )
1567  + floor( ( $gy + 399 ) / 400 );
1568 
1569  // Add days of the past months of this year
1570  for ( $i = 0; $i < $gm; $i++ ) {
1571  $gDayNo += self::$GREG_DAYS[$i];
1572  }
1573 
1574  // Leap years
1575  if ( $gm > 1 && ( ( $gy % 4 === 0 && $gy % 100 !== 0 || ( $gy % 400 == 0 ) ) ) ) {
1576  $gDayNo++;
1577  }
1578 
1579  // Days passed in current month
1580  $gDayNo += (int)$gd;
1581 
1582  $jDayNo = $gDayNo - 79;
1583 
1584  $jNp = floor( $jDayNo / 12053 );
1585  $jDayNo %= 12053;
1586 
1587  $jy = 979 + 33 * $jNp + 4 * floor( $jDayNo / 1461 );
1588  $jDayNo %= 1461;
1589 
1590  if ( $jDayNo >= 366 ) {
1591  $jy += floor( ( $jDayNo - 1 ) / 365 );
1592  $jDayNo = floor( ( $jDayNo - 1 ) % 365 );
1593  }
1594 
1595  for ( $i = 0; $i < 11 && $jDayNo >= self::$IRANIAN_DAYS[$i]; $i++ ) {
1596  $jDayNo -= self::$IRANIAN_DAYS[$i];
1597  }
1598 
1599  $jm = $i + 1;
1600  $jd = $jDayNo + 1;
1601 
1602  return [ $jy, $jm, $jd ];
1603  }
1604 
1616  private static function tsToHijri( $ts ) {
1617  $year = substr( $ts, 0, 4 );
1618  $month = substr( $ts, 4, 2 );
1619  $day = substr( $ts, 6, 2 );
1620 
1621  $zyr = $year;
1622  $zd = $day;
1623  $zm = $month;
1624  $zy = $zyr;
1625 
1626  if (
1627  ( $zy > 1582 ) || ( ( $zy == 1582 ) && ( $zm > 10 ) ) ||
1628  ( ( $zy == 1582 ) && ( $zm == 10 ) && ( $zd > 14 ) )
1629  ) {
1630  $zjd = (int)( ( 1461 * ( $zy + 4800 + (int)( ( $zm - 14 ) / 12 ) ) ) / 4 ) +
1631  (int)( ( 367 * ( $zm - 2 - 12 * ( (int)( ( $zm - 14 ) / 12 ) ) ) ) / 12 ) -
1632  (int)( ( 3 * (int)( ( ( $zy + 4900 + (int)( ( $zm - 14 ) / 12 ) ) / 100 ) ) ) / 4 ) +
1633  $zd - 32075;
1634  } else {
1635  $zjd = 367 * $zy - (int)( ( 7 * ( $zy + 5001 + (int)( ( $zm - 9 ) / 7 ) ) ) / 4 ) +
1636  (int)( ( 275 * $zm ) / 9 ) + $zd + 1729777;
1637  }
1638 
1639  $zl = $zjd -1948440 + 10632;
1640  $zn = (int)( ( $zl - 1 ) / 10631 );
1641  $zl = $zl - 10631 * $zn + 354;
1642  $zj = ( (int)( ( 10985 - $zl ) / 5316 ) ) * ( (int)( ( 50 * $zl ) / 17719 ) ) +
1643  ( (int)( $zl / 5670 ) ) * ( (int)( ( 43 * $zl ) / 15238 ) );
1644  $zl = $zl - ( (int)( ( 30 - $zj ) / 15 ) ) * ( (int)( ( 17719 * $zj ) / 50 ) ) -
1645  ( (int)( $zj / 16 ) ) * ( (int)( ( 15238 * $zj ) / 43 ) ) + 29;
1646  $zm = (int)( ( 24 * $zl ) / 709 );
1647  $zd = $zl - (int)( ( 709 * $zm ) / 24 );
1648  $zy = 30 * $zn + $zj - 30;
1649 
1650  return [ $zy, $zm, $zd ];
1651  }
1652 
1668  private static function tsToHebrew( $ts ) {
1669  # Parse date
1670  $year = substr( $ts, 0, 4 );
1671  $month = substr( $ts, 4, 2 );
1672  $day = substr( $ts, 6, 2 );
1673 
1674  # Calculate Hebrew year
1675  $hebrewYear = $year + 3760;
1676 
1677  # Month number when September = 1, August = 12
1678  $month += 4;
1679  if ( $month > 12 ) {
1680  # Next year
1681  $month -= 12;
1682  $year++;
1683  $hebrewYear++;
1684  }
1685 
1686  # Calculate day of year from 1 September
1687  $dayOfYear = $day;
1688  for ( $i = 1; $i < $month; $i++ ) {
1689  if ( $i == 6 ) {
1690  # February
1691  $dayOfYear += 28;
1692  # Check if the year is leap
1693  if ( $year % 400 == 0 || ( $year % 4 == 0 && $year % 100 > 0 ) ) {
1694  $dayOfYear++;
1695  }
1696  } elseif ( $i == 8 || $i == 10 || $i == 1 || $i == 3 ) {
1697  $dayOfYear += 30;
1698  } else {
1699  $dayOfYear += 31;
1700  }
1701  }
1702 
1703  # Calculate the start of the Hebrew year
1704  $start = self::hebrewYearStart( $hebrewYear );
1705 
1706  # Calculate next year's start
1707  if ( $dayOfYear <= $start ) {
1708  # Day is before the start of the year - it is the previous year
1709  # Next year's start
1710  $nextStart = $start;
1711  # Previous year
1712  $year--;
1713  $hebrewYear--;
1714  # Add days since previous year's 1 September
1715  $dayOfYear += 365;
1716  if ( ( $year % 400 == 0 ) || ( $year % 100 != 0 && $year % 4 == 0 ) ) {
1717  # Leap year
1718  $dayOfYear++;
1719  }
1720  # Start of the new (previous) year
1721  $start = self::hebrewYearStart( $hebrewYear );
1722  } else {
1723  # Next year's start
1724  $nextStart = self::hebrewYearStart( $hebrewYear + 1 );
1725  }
1726 
1727  # Calculate Hebrew day of year
1728  $hebrewDayOfYear = $dayOfYear - $start;
1729 
1730  # Difference between year's days
1731  $diff = $nextStart - $start;
1732  # Add 12 (or 13 for leap years) days to ignore the difference between
1733  # Hebrew and Gregorian year (353 at least vs. 365/6) - now the
1734  # difference is only about the year type
1735  if ( ( $year % 400 == 0 ) || ( $year % 100 != 0 && $year % 4 == 0 ) ) {
1736  $diff += 13;
1737  } else {
1738  $diff += 12;
1739  }
1740 
1741  # Check the year pattern, and is leap year
1742  # 0 means an incomplete year, 1 means a regular year, 2 means a complete year
1743  # This is mod 30, to work on both leap years (which add 30 days of Adar I)
1744  # and non-leap years
1745  $yearPattern = $diff % 30;
1746  # Check if leap year
1747  $isLeap = $diff >= 30;
1748 
1749  # Calculate day in the month from number of day in the Hebrew year
1750  # Don't check Adar - if the day is not in Adar, we will stop before;
1751  # if it is in Adar, we will use it to check if it is Adar I or Adar II
1752  $hebrewDay = $hebrewDayOfYear;
1753  $hebrewMonth = 1;
1754  $days = 0;
1755  while ( $hebrewMonth <= 12 ) {
1756  # Calculate days in this month
1757  if ( $isLeap && $hebrewMonth == 6 ) {
1758  # Adar in a leap year
1759  if ( $isLeap ) {
1760  # Leap year - has Adar I, with 30 days, and Adar II, with 29 days
1761  $days = 30;
1762  if ( $hebrewDay <= $days ) {
1763  # Day in Adar I
1764  $hebrewMonth = 13;
1765  } else {
1766  # Subtract the days of Adar I
1767  $hebrewDay -= $days;
1768  # Try Adar II
1769  $days = 29;
1770  if ( $hebrewDay <= $days ) {
1771  # Day in Adar II
1772  $hebrewMonth = 14;
1773  }
1774  }
1775  }
1776  } elseif ( $hebrewMonth == 2 && $yearPattern == 2 ) {
1777  # Cheshvan in a complete year (otherwise as the rule below)
1778  $days = 30;
1779  } elseif ( $hebrewMonth == 3 && $yearPattern == 0 ) {
1780  # Kislev in an incomplete year (otherwise as the rule below)
1781  $days = 29;
1782  } else {
1783  # Odd months have 30 days, even have 29
1784  $days = 30 - ( $hebrewMonth - 1 ) % 2;
1785  }
1786  if ( $hebrewDay <= $days ) {
1787  # In the current month
1788  break;
1789  } else {
1790  # Subtract the days of the current month
1791  $hebrewDay -= $days;
1792  # Try in the next month
1793  $hebrewMonth++;
1794  }
1795  }
1796 
1797  return [ $hebrewYear, $hebrewMonth, $hebrewDay, $days ];
1798  }
1799 
1809  private static function hebrewYearStart( $year ) {
1810  $a = intval( ( 12 * ( $year - 1 ) + 17 ) % 19 );
1811  $b = intval( ( $year - 1 ) % 4 );
1812  $m = 32.044093161144 + 1.5542417966212 * $a + $b / 4.0 - 0.0031777940220923 * ( $year - 1 );
1813  if ( $m < 0 ) {
1814  $m--;
1815  }
1816  $Mar = intval( $m );
1817  if ( $m < 0 ) {
1818  $m++;
1819  }
1820  $m -= $Mar;
1821 
1822  $c = intval( ( $Mar + 3 * ( $year - 1 ) + 5 * $b + 5 ) % 7 );
1823  if ( $c == 0 && $a > 11 && $m >= 0.89772376543210 ) {
1824  $Mar++;
1825  } elseif ( $c == 1 && $a > 6 && $m >= 0.63287037037037 ) {
1826  $Mar += 2;
1827  } elseif ( $c == 2 || $c == 4 || $c == 6 ) {
1828  $Mar++;
1829  }
1830 
1831  $Mar += intval( ( $year - 3761 ) / 100 ) - intval( ( $year - 3761 ) / 400 ) - 24;
1832  return $Mar;
1833  }
1834 
1847  private static function tsToYear( $ts, $cName ) {
1848  $gy = substr( $ts, 0, 4 );
1849  $gm = substr( $ts, 4, 2 );
1850  $gd = substr( $ts, 6, 2 );
1851 
1852  if ( !strcmp( $cName, 'thai' ) ) {
1853  # Thai solar dates
1854  # Add 543 years to the Gregorian calendar
1855  # Months and days are identical
1856  $gy_offset = $gy + 543;
1857  } elseif ( ( !strcmp( $cName, 'minguo' ) ) || !strcmp( $cName, 'juche' ) ) {
1858  # Minguo dates
1859  # Deduct 1911 years from the Gregorian calendar
1860  # Months and days are identical
1861  $gy_offset = $gy - 1911;
1862  } elseif ( !strcmp( $cName, 'tenno' ) ) {
1863  # Nengō dates up to Meiji period
1864  # Deduct years from the Gregorian calendar
1865  # depending on the nengo periods
1866  # Months and days are identical
1867  if ( ( $gy < 1912 )
1868  || ( ( $gy == 1912 ) && ( $gm < 7 ) )
1869  || ( ( $gy == 1912 ) && ( $gm == 7 ) && ( $gd < 31 ) )
1870  ) {
1871  # Meiji period
1872  $gy_gannen = $gy - 1868 + 1;
1873  $gy_offset = $gy_gannen;
1874  if ( $gy_gannen == 1 ) {
1875  $gy_offset = '元';
1876  }
1877  $gy_offset = '明治' . $gy_offset;
1878  } elseif (
1879  ( ( $gy == 1912 ) && ( $gm == 7 ) && ( $gd == 31 ) ) ||
1880  ( ( $gy == 1912 ) && ( $gm >= 8 ) ) ||
1881  ( ( $gy > 1912 ) && ( $gy < 1926 ) ) ||
1882  ( ( $gy == 1926 ) && ( $gm < 12 ) ) ||
1883  ( ( $gy == 1926 ) && ( $gm == 12 ) && ( $gd < 26 ) )
1884  ) {
1885  # Taishō period
1886  $gy_gannen = $gy - 1912 + 1;
1887  $gy_offset = $gy_gannen;
1888  if ( $gy_gannen == 1 ) {
1889  $gy_offset = '元';
1890  }
1891  $gy_offset = '大正' . $gy_offset;
1892  } elseif (
1893  ( ( $gy == 1926 ) && ( $gm == 12 ) && ( $gd >= 26 ) ) ||
1894  ( ( $gy > 1926 ) && ( $gy < 1989 ) ) ||
1895  ( ( $gy == 1989 ) && ( $gm == 1 ) && ( $gd < 8 ) )
1896  ) {
1897  # Shōwa period
1898  $gy_gannen = $gy - 1926 + 1;
1899  $gy_offset = $gy_gannen;
1900  if ( $gy_gannen == 1 ) {
1901  $gy_offset = '元';
1902  }
1903  $gy_offset = '昭和' . $gy_offset;
1904  } else {
1905  # Heisei period
1906  $gy_gannen = $gy - 1989 + 1;
1907  $gy_offset = $gy_gannen;
1908  if ( $gy_gannen == 1 ) {
1909  $gy_offset = '元';
1910  }
1911  $gy_offset = '平成' . $gy_offset;
1912  }
1913  } else {
1914  $gy_offset = $gy;
1915  }
1916 
1917  return [ $gy_offset, $gm, $gd ];
1918  }
1919 
1933  private static function strongDirFromContent( $text = '' ) {
1934  if ( !preg_match( self::$strongDirRegex, $text, $matches ) ) {
1935  return null;
1936  }
1937  if ( $matches[1] === '' ) {
1938  return 'rtl';
1939  }
1940  return 'ltr';
1941  }
1942 
1950  static function romanNumeral( $num ) {
1951  static $table = [
1952  [ '', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X' ],
1953  [ '', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC', 'C' ],
1954  [ '', 'C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM', 'M' ],
1955  [ '', 'M', 'MM', 'MMM', 'MMMM', 'MMMMM', 'MMMMMM', 'MMMMMMM',
1956  'MMMMMMMM', 'MMMMMMMMM', 'MMMMMMMMMM' ]
1957  ];
1958 
1959  $num = intval( $num );
1960  if ( $num > 10000 || $num <= 0 ) {
1961  return $num;
1962  }
1963 
1964  $s = '';
1965  for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) {
1966  if ( $num >= $pow10 ) {
1967  $s .= $table[$i][(int)floor( $num / $pow10 )];
1968  }
1969  $num = $num % $pow10;
1970  }
1971  return $s;
1972  }
1973 
1981  static function hebrewNumeral( $num ) {
1982  static $table = [
1983  [ '', 'א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ז', 'ח', 'ט', 'י' ],
1984  [ '', 'י', 'כ', 'ל', 'מ', 'נ', 'ס', 'ע', 'פ', 'צ', 'ק' ],
1985  [ '',
1986  [ 'ק' ],
1987  [ 'ר' ],
1988  [ 'ש' ],
1989  [ 'ת' ],
1990  [ 'ת', 'ק' ],
1991  [ 'ת', 'ר' ],
1992  [ 'ת', 'ש' ],
1993  [ 'ת', 'ת' ],
1994  [ 'ת', 'ת', 'ק' ],
1995  [ 'ת', 'ת', 'ר' ],
1996  ],
1997  [ '', 'א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ז', 'ח', 'ט', 'י' ]
1998  ];
1999 
2000  $num = intval( $num );
2001  if ( $num > 9999 || $num <= 0 ) {
2002  return $num;
2003  }
2004 
2005  // Round thousands have special notations
2006  if ( $num === 1000 ) {
2007  return "א' אלף";
2008  } elseif ( $num % 1000 === 0 ) {
2009  return $table[0][$num / 1000] . "' אלפים";
2010  }
2011 
2012  $letters = [];
2013 
2014  for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) {
2015  if ( $num >= $pow10 ) {
2016  if ( $num === 15 || $num === 16 ) {
2017  $letters[] = $table[0][9];
2018  $letters[] = $table[0][$num - 9];
2019  $num = 0;
2020  } else {
2021  $letters = array_merge(
2022  $letters,
2023  (array)$table[$i][intval( $num / $pow10 )]
2024  );
2025 
2026  if ( $pow10 === 1000 ) {
2027  $letters[] = "'";
2028  }
2029  }
2030  }
2031 
2032  $num = $num % $pow10;
2033  }
2034 
2035  $preTransformLength = count( $letters );
2036  if ( $preTransformLength === 1 ) {
2037  // Add geresh (single quote) to one-letter numbers
2038  $letters[] = "'";
2039  } else {
2040  $lastIndex = $preTransformLength - 1;
2041  $letters[$lastIndex] = str_replace(
2042  [ 'כ', 'מ', 'נ', 'פ', 'צ' ],
2043  [ 'ך', 'ם', 'ן', 'ף', 'ץ' ],
2044  $letters[$lastIndex]
2045  );
2046 
2047  // Add gershayim (double quote) to multiple-letter numbers,
2048  // but exclude numbers with only one letter after the thousands
2049  // (1001-1009, 1020, 1030, 2001-2009, etc.)
2050  if ( $letters[1] === "'" && $preTransformLength === 3 ) {
2051  $letters[] = "'";
2052  } else {
2053  array_splice( $letters, -1, 0, '"' );
2054  }
2055  }
2056 
2057  return implode( $letters );
2058  }
2059 
2068  function userAdjust( $ts, $tz = false ) {
2070 
2071  if ( $tz === false ) {
2072  $tz = $wgUser->getOption( 'timecorrection' );
2073  }
2074 
2075  $data = explode( '|', $tz, 3 );
2076 
2077  if ( $data[0] == 'ZoneInfo' ) {
2078  MediaWiki\suppressWarnings();
2079  $userTZ = timezone_open( $data[2] );
2080  MediaWiki\restoreWarnings();
2081  if ( $userTZ !== false ) {
2082  $date = date_create( $ts, timezone_open( 'UTC' ) );
2083  date_timezone_set( $date, $userTZ );
2084  $date = date_format( $date, 'YmdHis' );
2085  return $date;
2086  }
2087  # Unrecognized timezone, default to 'Offset' with the stored offset.
2088  $data[0] = 'Offset';
2089  }
2090 
2091  if ( $data[0] == 'System' || $tz == '' ) {
2092  # Global offset in minutes.
2093  $minDiff = $wgLocalTZoffset;
2094  } elseif ( $data[0] == 'Offset' ) {
2095  $minDiff = intval( $data[1] );
2096  } else {
2097  $data = explode( ':', $tz );
2098  if ( count( $data ) == 2 ) {
2099  $data[0] = intval( $data[0] );
2100  $data[1] = intval( $data[1] );
2101  $minDiff = abs( $data[0] ) * 60 + $data[1];
2102  if ( $data[0] < 0 ) {
2103  $minDiff = -$minDiff;
2104  }
2105  } else {
2106  $minDiff = intval( $data[0] ) * 60;
2107  }
2108  }
2109 
2110  # No difference ? Return time unchanged
2111  if ( 0 == $minDiff ) {
2112  return $ts;
2113  }
2114 
2115  MediaWiki\suppressWarnings(); // E_STRICT system time bitching
2116  # Generate an adjusted date; take advantage of the fact that mktime
2117  # will normalize out-of-range values so we don't have to split $minDiff
2118  # into hours and minutes.
2119  $t = mktime( (
2120  (int)substr( $ts, 8, 2 ) ), # Hours
2121  (int)substr( $ts, 10, 2 ) + $minDiff, # Minutes
2122  (int)substr( $ts, 12, 2 ), # Seconds
2123  (int)substr( $ts, 4, 2 ), # Month
2124  (int)substr( $ts, 6, 2 ), # Day
2125  (int)substr( $ts, 0, 4 ) ); # Year
2126 
2127  $date = date( 'YmdHis', $t );
2128  MediaWiki\restoreWarnings();
2129 
2130  return $date;
2131  }
2132 
2150  function dateFormat( $usePrefs = true ) {
2151  global $wgUser;
2152 
2153  if ( is_bool( $usePrefs ) ) {
2154  if ( $usePrefs ) {
2155  $datePreference = $wgUser->getDatePreference();
2156  } else {
2157  $datePreference = (string)User::getDefaultOption( 'date' );
2158  }
2159  } else {
2160  $datePreference = (string)$usePrefs;
2161  }
2162 
2163  // return int
2164  if ( $datePreference == '' ) {
2165  return 'default';
2166  }
2167 
2168  return $datePreference;
2169  }
2170 
2181  function getDateFormatString( $type, $pref ) {
2182  $wasDefault = false;
2183  if ( $pref == 'default' ) {
2184  $wasDefault = true;
2185  $pref = $this->getDefaultDateFormat();
2186  }
2187 
2188  if ( !isset( $this->dateFormatStrings[$type][$pref] ) ) {
2189  $df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" );
2190 
2191  if ( $type === 'pretty' && $df === null ) {
2192  $df = $this->getDateFormatString( 'date', $pref );
2193  }
2194 
2195  if ( !$wasDefault && $df === null ) {
2196  $pref = $this->getDefaultDateFormat();
2197  $df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" );
2198  }
2199 
2200  $this->dateFormatStrings[$type][$pref] = $df;
2201  }
2202  return $this->dateFormatStrings[$type][$pref];
2203  }
2204 
2215  function date( $ts, $adj = false, $format = true, $timecorrection = false ) {
2216  $ts = wfTimestamp( TS_MW, $ts );
2217  if ( $adj ) {
2218  $ts = $this->userAdjust( $ts, $timecorrection );
2219  }
2220  $df = $this->getDateFormatString( 'date', $this->dateFormat( $format ) );
2221  return $this->sprintfDate( $df, $ts );
2222  }
2223 
2234  function time( $ts, $adj = false, $format = true, $timecorrection = false ) {
2235  $ts = wfTimestamp( TS_MW, $ts );
2236  if ( $adj ) {
2237  $ts = $this->userAdjust( $ts, $timecorrection );
2238  }
2239  $df = $this->getDateFormatString( 'time', $this->dateFormat( $format ) );
2240  return $this->sprintfDate( $df, $ts );
2241  }
2242 
2254  function timeanddate( $ts, $adj = false, $format = true, $timecorrection = false ) {
2255  $ts = wfTimestamp( TS_MW, $ts );
2256  if ( $adj ) {
2257  $ts = $this->userAdjust( $ts, $timecorrection );
2258  }
2259  $df = $this->getDateFormatString( 'both', $this->dateFormat( $format ) );
2260  return $this->sprintfDate( $df, $ts );
2261  }
2262 
2273  public function formatDuration( $seconds, array $chosenIntervals = [] ) {
2274  $intervals = $this->getDurationIntervals( $seconds, $chosenIntervals );
2275 
2276  $segments = [];
2277 
2278  foreach ( $intervals as $intervalName => $intervalValue ) {
2279  // Messages: duration-seconds, duration-minutes, duration-hours, duration-days, duration-weeks,
2280  // duration-years, duration-decades, duration-centuries, duration-millennia
2281  $message = wfMessage( 'duration-' . $intervalName )->numParams( $intervalValue );
2282  $segments[] = $message->inLanguage( $this )->escaped();
2283  }
2284 
2285  return $this->listToText( $segments );
2286  }
2287 
2299  public function getDurationIntervals( $seconds, array $chosenIntervals = [] ) {
2300  if ( empty( $chosenIntervals ) ) {
2301  $chosenIntervals = [
2302  'millennia',
2303  'centuries',
2304  'decades',
2305  'years',
2306  'days',
2307  'hours',
2308  'minutes',
2309  'seconds'
2310  ];
2311  }
2312 
2313  $intervals = array_intersect_key( self::$durationIntervals, array_flip( $chosenIntervals ) );
2314  $sortedNames = array_keys( $intervals );
2315  $smallestInterval = array_pop( $sortedNames );
2316 
2317  $segments = [];
2318 
2319  foreach ( $intervals as $name => $length ) {
2320  $value = floor( $seconds / $length );
2321 
2322  if ( $value > 0 || ( $name == $smallestInterval && empty( $segments ) ) ) {
2323  $seconds -= $value * $length;
2324  $segments[$name] = $value;
2325  }
2326  }
2327 
2328  return $segments;
2329  }
2330 
2350  private function internalUserTimeAndDate( $type, $ts, User $user, array $options ) {
2351  $ts = wfTimestamp( TS_MW, $ts );
2352  $options += [ 'timecorrection' => true, 'format' => true ];
2353  if ( $options['timecorrection'] !== false ) {
2354  if ( $options['timecorrection'] === true ) {
2355  $offset = $user->getOption( 'timecorrection' );
2356  } else {
2357  $offset = $options['timecorrection'];
2358  }
2359  $ts = $this->userAdjust( $ts, $offset );
2360  }
2361  if ( $options['format'] === true ) {
2362  $format = $user->getDatePreference();
2363  } else {
2364  $format = $options['format'];
2365  }
2366  $df = $this->getDateFormatString( $type, $this->dateFormat( $format ) );
2367  return $this->sprintfDate( $df, $ts );
2368  }
2369 
2389  public function userDate( $ts, User $user, array $options = [] ) {
2390  return $this->internalUserTimeAndDate( 'date', $ts, $user, $options );
2391  }
2392 
2412  public function userTime( $ts, User $user, array $options = [] ) {
2413  return $this->internalUserTimeAndDate( 'time', $ts, $user, $options );
2414  }
2415 
2435  public function userTimeAndDate( $ts, User $user, array $options = [] ) {
2436  return $this->internalUserTimeAndDate( 'both', $ts, $user, $options );
2437  }
2438 
2454  public function getHumanTimestamp(
2455  MWTimestamp $time, MWTimestamp $relativeTo = null, User $user = null
2456  ) {
2457  if ( $relativeTo === null ) {
2458  $relativeTo = new MWTimestamp();
2459  }
2460  if ( $user === null ) {
2461  $user = RequestContext::getMain()->getUser();
2462  }
2463 
2464  // Adjust for the user's timezone.
2465  $offsetThis = $time->offsetForUser( $user );
2466  $offsetRel = $relativeTo->offsetForUser( $user );
2467 
2468  $ts = '';
2469  if ( Hooks::run( 'GetHumanTimestamp', [ &$ts, $time, $relativeTo, $user, $this ] ) ) {
2470  $ts = $this->getHumanTimestampInternal( $time, $relativeTo, $user );
2471  }
2472 
2473  // Reset the timezone on the objects.
2474  $time->timestamp->sub( $offsetThis );
2475  $relativeTo->timestamp->sub( $offsetRel );
2476 
2477  return $ts;
2478  }
2479 
2491  private function getHumanTimestampInternal(
2492  MWTimestamp $ts, MWTimestamp $relativeTo, User $user
2493  ) {
2494  $diff = $ts->diff( $relativeTo );
2495  $diffDay = (bool)( (int)$ts->timestamp->format( 'w' ) -
2496  (int)$relativeTo->timestamp->format( 'w' ) );
2497  $days = $diff->days ?: (int)$diffDay;
2498  if ( $diff->invert || $days > 5
2499  && $ts->timestamp->format( 'Y' ) !== $relativeTo->timestamp->format( 'Y' )
2500  ) {
2501  // Timestamps are in different years: use full timestamp
2502  // Also do full timestamp for future dates
2506  $format = $this->getDateFormatString( 'both', $user->getDatePreference() ?: 'default' );
2507  $ts = $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) );
2508  } elseif ( $days > 5 ) {
2509  // Timestamps are in same year, but more than 5 days ago: show day and month only.
2510  $format = $this->getDateFormatString( 'pretty', $user->getDatePreference() ?: 'default' );
2511  $ts = $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) );
2512  } elseif ( $days > 1 ) {
2513  // Timestamp within the past week: show the day of the week and time
2514  $format = $this->getDateFormatString( 'time', $user->getDatePreference() ?: 'default' );
2515  $weekday = self::$mWeekdayMsgs[$ts->timestamp->format( 'w' )];
2516  // Messages:
2517  // sunday-at, monday-at, tuesday-at, wednesday-at, thursday-at, friday-at, saturday-at
2518  $ts = wfMessage( "$weekday-at" )
2519  ->inLanguage( $this )
2520  ->params( $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) ) )
2521  ->text();
2522  } elseif ( $days == 1 ) {
2523  // Timestamp was yesterday: say 'yesterday' and the time.
2524  $format = $this->getDateFormatString( 'time', $user->getDatePreference() ?: 'default' );
2525  $ts = wfMessage( 'yesterday-at' )
2526  ->inLanguage( $this )
2527  ->params( $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) ) )
2528  ->text();
2529  } elseif ( $diff->h > 1 || $diff->h == 1 && $diff->i > 30 ) {
2530  // Timestamp was today, but more than 90 minutes ago: say 'today' and the time.
2531  $format = $this->getDateFormatString( 'time', $user->getDatePreference() ?: 'default' );
2532  $ts = wfMessage( 'today-at' )
2533  ->inLanguage( $this )
2534  ->params( $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) ) )
2535  ->text();
2536 
2537  // From here on in, the timestamp was soon enough ago so that we can simply say
2538  // XX units ago, e.g., "2 hours ago" or "5 minutes ago"
2539  } elseif ( $diff->h == 1 ) {
2540  // Less than 90 minutes, but more than an hour ago.
2541  $ts = wfMessage( 'hours-ago' )->inLanguage( $this )->numParams( 1 )->text();
2542  } elseif ( $diff->i >= 1 ) {
2543  // A few minutes ago.
2544  $ts = wfMessage( 'minutes-ago' )->inLanguage( $this )->numParams( $diff->i )->text();
2545  } elseif ( $diff->s >= 30 ) {
2546  // Less than a minute, but more than 30 sec ago.
2547  $ts = wfMessage( 'seconds-ago' )->inLanguage( $this )->numParams( $diff->s )->text();
2548  } else {
2549  // Less than 30 seconds ago.
2550  $ts = wfMessage( 'just-now' )->text();
2551  }
2552 
2553  return $ts;
2554  }
2555 
2560  function getMessage( $key ) {
2561  return self::$dataCache->getSubitem( $this->mCode, 'messages', $key );
2562  }
2563 
2567  function getAllMessages() {
2568  return self::$dataCache->getItem( $this->mCode, 'messages' );
2569  }
2570 
2577  function iconv( $in, $out, $string ) {
2578  # This is a wrapper for iconv in all languages except esperanto,
2579  # which does some nasty x-conversions beforehand
2580 
2581  # Even with //IGNORE iconv can whine about illegal characters in
2582  # *input* string. We just ignore those too.
2583  # REF: http://bugs.php.net/bug.php?id=37166
2584  # REF: https://phabricator.wikimedia.org/T18885
2585  MediaWiki\suppressWarnings();
2586  $text = iconv( $in, $out . '//IGNORE', $string );
2587  MediaWiki\restoreWarnings();
2588  return $text;
2589  }
2590 
2591  // callback functions for ucwords(), ucwordbreaks()
2592 
2598  return $this->ucfirst( $matches[1] );
2599  }
2600 
2606  return mb_strtoupper( $matches[0] );
2607  }
2608 
2614  return mb_strtoupper( $matches[0] );
2615  }
2616 
2624  function ucfirst( $str ) {
2625  $o = ord( $str );
2626  if ( $o < 96 ) { // if already uppercase...
2627  return $str;
2628  } elseif ( $o < 128 ) {
2629  return ucfirst( $str ); // use PHP's ucfirst()
2630  } else {
2631  // fall back to more complex logic in case of multibyte strings
2632  return $this->uc( $str, true );
2633  }
2634  }
2635 
2644  function uc( $str, $first = false ) {
2645  if ( $first ) {
2646  if ( $this->isMultibyte( $str ) ) {
2647  return mb_strtoupper( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
2648  } else {
2649  return ucfirst( $str );
2650  }
2651  } else {
2652  return $this->isMultibyte( $str ) ? mb_strtoupper( $str ) : strtoupper( $str );
2653  }
2654  }
2655 
2660  function lcfirst( $str ) {
2661  $o = ord( $str );
2662  if ( !$o ) {
2663  return strval( $str );
2664  } elseif ( $o >= 128 ) {
2665  return $this->lc( $str, true );
2666  } elseif ( $o > 96 ) {
2667  return $str;
2668  } else {
2669  $str[0] = strtolower( $str[0] );
2670  return $str;
2671  }
2672  }
2673 
2679  function lc( $str, $first = false ) {
2680  if ( $first ) {
2681  if ( $this->isMultibyte( $str ) ) {
2682  return mb_strtolower( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
2683  } else {
2684  return strtolower( substr( $str, 0, 1 ) ) . substr( $str, 1 );
2685  }
2686  } else {
2687  return $this->isMultibyte( $str ) ? mb_strtolower( $str ) : strtolower( $str );
2688  }
2689  }
2690 
2695  function isMultibyte( $str ) {
2696  return strlen( $str ) !== mb_strlen( $str );
2697  }
2698 
2703  function ucwords( $str ) {
2704  if ( $this->isMultibyte( $str ) ) {
2705  $str = $this->lc( $str );
2706 
2707  // regexp to find first letter in each word (i.e. after each space)
2708  $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)| ([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
2709 
2710  // function to use to capitalize a single char
2711  return preg_replace_callback(
2712  $replaceRegexp,
2713  [ $this, 'ucwordsCallbackMB' ],
2714  $str
2715  );
2716  } else {
2717  return ucwords( strtolower( $str ) );
2718  }
2719  }
2720 
2727  function ucwordbreaks( $str ) {
2728  if ( $this->isMultibyte( $str ) ) {
2729  $str = $this->lc( $str );
2730 
2731  // since \b doesn't work for UTF-8, we explicitely define word break chars
2732  $breaks = "[ \-\(\)\}\{\.,\?!]";
2733 
2734  // find first letter after word break
2735  $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)|" .
2736  "$breaks([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
2737 
2738  return preg_replace_callback(
2739  $replaceRegexp,
2740  [ $this, 'ucwordbreaksCallbackMB' ],
2741  $str
2742  );
2743  } else {
2744  return preg_replace_callback(
2745  '/\b([\w\x80-\xff]+)\b/',
2746  [ $this, 'ucwordbreaksCallbackAscii' ],
2747  $str
2748  );
2749  }
2750  }
2751 
2767  function caseFold( $s ) {
2768  return $this->uc( $s );
2769  }
2770 
2776  function checkTitleEncoding( $s ) {
2777  if ( is_array( $s ) ) {
2778  throw new MWException( 'Given array to checkTitleEncoding.' );
2779  }
2780  if ( StringUtils::isUtf8( $s ) ) {
2781  return $s;
2782  }
2783 
2784  return $this->iconv( $this->fallback8bitEncoding(), 'utf-8', $s );
2785  }
2786 
2791  return self::$dataCache->getItem( $this->mCode, 'fallback8bitEncoding' );
2792  }
2793 
2802  function hasWordBreaks() {
2803  return true;
2804  }
2805 
2813  function segmentByWord( $string ) {
2814  return $string;
2815  }
2816 
2824  function normalizeForSearch( $string ) {
2825  return self::convertDoubleWidth( $string );
2826  }
2827 
2836  protected static function convertDoubleWidth( $string ) {
2837  static $full = null;
2838  static $half = null;
2839 
2840  if ( $full === null ) {
2841  $fullWidth = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
2842  $halfWidth = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
2843  $full = str_split( $fullWidth, 3 );
2844  $half = str_split( $halfWidth );
2845  }
2846 
2847  $string = str_replace( $full, $half, $string );
2848  return $string;
2849  }
2850 
2856  protected static function insertSpace( $string, $pattern ) {
2857  $string = preg_replace( $pattern, " $1 ", $string );
2858  $string = preg_replace( '/ +/', ' ', $string );
2859  return $string;
2860  }
2861 
2866  function convertForSearchResult( $termsArray ) {
2867  # some languages, e.g. Chinese, need to do a conversion
2868  # in order for search results to be displayed correctly
2869  return $termsArray;
2870  }
2871 
2878  function firstChar( $s ) {
2879  $matches = [];
2880  preg_match(
2881  '/^([\x00-\x7f]|[\xc0-\xdf][\x80-\xbf]|' .
2882  '[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xf7][\x80-\xbf]{3})/',
2883  $s,
2884  $matches
2885  );
2886 
2887  if ( isset( $matches[1] ) ) {
2888  if ( strlen( $matches[1] ) != 3 ) {
2889  return $matches[1];
2890  }
2891 
2892  // Break down Hangul syllables to grab the first jamo
2894  if ( $code < 0xac00 || 0xd7a4 <= $code ) {
2895  return $matches[1];
2896  } elseif ( $code < 0xb098 ) {
2897  return "\xe3\x84\xb1";
2898  } elseif ( $code < 0xb2e4 ) {
2899  return "\xe3\x84\xb4";
2900  } elseif ( $code < 0xb77c ) {
2901  return "\xe3\x84\xb7";
2902  } elseif ( $code < 0xb9c8 ) {
2903  return "\xe3\x84\xb9";
2904  } elseif ( $code < 0xbc14 ) {
2905  return "\xe3\x85\x81";
2906  } elseif ( $code < 0xc0ac ) {
2907  return "\xe3\x85\x82";
2908  } elseif ( $code < 0xc544 ) {
2909  return "\xe3\x85\x85";
2910  } elseif ( $code < 0xc790 ) {
2911  return "\xe3\x85\x87";
2912  } elseif ( $code < 0xcc28 ) {
2913  return "\xe3\x85\x88";
2914  } elseif ( $code < 0xce74 ) {
2915  return "\xe3\x85\x8a";
2916  } elseif ( $code < 0xd0c0 ) {
2917  return "\xe3\x85\x8b";
2918  } elseif ( $code < 0xd30c ) {
2919  return "\xe3\x85\x8c";
2920  } elseif ( $code < 0xd558 ) {
2921  return "\xe3\x85\x8d";
2922  } else {
2923  return "\xe3\x85\x8e";
2924  }
2925  } else {
2926  return '';
2927  }
2928  }
2929 
2930  function initEncoding() {
2931  # Some languages may have an alternate char encoding option
2932  # (Esperanto X-coding, Japanese furigana conversion, etc)
2933  # If this language is used as the primary content language,
2934  # an override to the defaults can be set here on startup.
2935  }
2936 
2941  function recodeForEdit( $s ) {
2942  # For some languages we'll want to explicitly specify
2943  # which characters make it into the edit box raw
2944  # or are converted in some way or another.
2946  if ( $wgEditEncoding == '' || $wgEditEncoding == 'UTF-8' ) {
2947  return $s;
2948  } else {
2949  return $this->iconv( 'UTF-8', $wgEditEncoding, $s );
2950  }
2951  }
2952 
2957  function recodeInput( $s ) {
2958  # Take the previous into account.
2960  if ( $wgEditEncoding != '' ) {
2961  $enc = $wgEditEncoding;
2962  } else {
2963  $enc = 'UTF-8';
2964  }
2965  if ( $enc == 'UTF-8' ) {
2966  return $s;
2967  } else {
2968  return $this->iconv( $enc, 'UTF-8', $s );
2969  }
2970  }
2971 
2983  function normalize( $s ) {
2985  $s = UtfNormal\Validator::cleanUp( $s );
2986  if ( $wgAllUnicodeFixes ) {
2987  $s = $this->transformUsingPairFile( 'normalize-ar.ser', $s );
2988  $s = $this->transformUsingPairFile( 'normalize-ml.ser', $s );
2989  }
2990 
2991  return $s;
2992  }
2993 
3008  function transformUsingPairFile( $file, $string ) {
3009  if ( !isset( $this->transformData[$file] ) ) {
3010  $data = wfGetPrecompiledData( $file );
3011  if ( $data === false ) {
3012  throw new MWException( __METHOD__ . ": The transformation file $file is missing" );
3013  }
3014  $this->transformData[$file] = new ReplacementArray( $data );
3015  }
3016  return $this->transformData[$file]->replace( $string );
3017  }
3018 
3024  function isRTL() {
3025  return self::$dataCache->getItem( $this->mCode, 'rtl' );
3026  }
3027 
3032  function getDir() {
3033  return $this->isRTL() ? 'rtl' : 'ltr';
3034  }
3035 
3044  function alignStart() {
3045  return $this->isRTL() ? 'right' : 'left';
3046  }
3047 
3056  function alignEnd() {
3057  return $this->isRTL() ? 'left' : 'right';
3058  }
3059 
3071  function getDirMarkEntity( $opposite = false ) {
3072  if ( $opposite ) {
3073  return $this->isRTL() ? '&lrm;' : '&rlm;';
3074  }
3075  return $this->isRTL() ? '&rlm;' : '&lrm;';
3076  }
3077 
3088  function getDirMark( $opposite = false ) {
3089  $lrm = "\xE2\x80\x8E"; # LEFT-TO-RIGHT MARK, commonly abbreviated LRM
3090  $rlm = "\xE2\x80\x8F"; # RIGHT-TO-LEFT MARK, commonly abbreviated RLM
3091  if ( $opposite ) {
3092  return $this->isRTL() ? $lrm : $rlm;
3093  }
3094  return $this->isRTL() ? $rlm : $lrm;
3095  }
3096 
3100  function capitalizeAllNouns() {
3101  return self::$dataCache->getItem( $this->mCode, 'capitalizeAllNouns' );
3102  }
3103 
3111  function getArrow( $direction = 'forwards' ) {
3112  switch ( $direction ) {
3113  case 'forwards':
3114  return $this->isRTL() ? '←' : '→';
3115  case 'backwards':
3116  return $this->isRTL() ? '→' : '←';
3117  case 'left':
3118  return '←';
3119  case 'right':
3120  return '→';
3121  case 'up':
3122  return '↑';
3123  case 'down':
3124  return '↓';
3125  }
3126  }
3127 
3133  function linkPrefixExtension() {
3134  return self::$dataCache->getItem( $this->mCode, 'linkPrefixExtension' );
3135  }
3136 
3141  function getMagicWords() {
3142  return self::$dataCache->getItem( $this->mCode, 'magicWords' );
3143  }
3144 
3148  protected function doMagicHook() {
3149  if ( $this->mMagicHookDone ) {
3150  return;
3151  }
3152  $this->mMagicHookDone = true;
3153  Hooks::run( 'LanguageGetMagic', [ &$this->mMagicExtensions, $this->getCode() ] );
3154  }
3155 
3161  function getMagic( $mw ) {
3162  // Saves a function call
3163  if ( !$this->mMagicHookDone ) {
3164  $this->doMagicHook();
3165  }
3166 
3167  if ( isset( $this->mMagicExtensions[$mw->mId] ) ) {
3168  $rawEntry = $this->mMagicExtensions[$mw->mId];
3169  } else {
3170  $rawEntry = self::$dataCache->getSubitem(
3171  $this->mCode, 'magicWords', $mw->mId );
3172  }
3173 
3174  if ( !is_array( $rawEntry ) ) {
3175  wfWarn( "\"$rawEntry\" is not a valid magic word for \"$mw->mId\"" );
3176  } else {
3177  $mw->mCaseSensitive = $rawEntry[0];
3178  $mw->mSynonyms = array_slice( $rawEntry, 1 );
3179  }
3180  }
3181 
3187  function addMagicWordsByLang( $newWords ) {
3188  $fallbackChain = $this->getFallbackLanguages();
3189  $fallbackChain = array_reverse( $fallbackChain );
3190  foreach ( $fallbackChain as $code ) {
3191  if ( isset( $newWords[$code] ) ) {
3192  $this->mMagicExtensions = $newWords[$code] + $this->mMagicExtensions;
3193  }
3194  }
3195  }
3196 
3203  // Cache aliases because it may be slow to load them
3204  if ( is_null( $this->mExtendedSpecialPageAliases ) ) {
3205  // Initialise array
3206  $this->mExtendedSpecialPageAliases =
3207  self::$dataCache->getItem( $this->mCode, 'specialPageAliases' );
3208  Hooks::run( 'LanguageGetSpecialPageAliases',
3209  [ &$this->mExtendedSpecialPageAliases, $this->getCode() ] );
3210  }
3211 
3213  }
3214 
3221  function emphasize( $text ) {
3222  return "<em>$text</em>";
3223  }
3224 
3247  public function formatNum( $number, $nocommafy = false ) {
3249  if ( !$nocommafy ) {
3250  $number = $this->commafy( $number );
3251  $s = $this->separatorTransformTable();
3252  if ( $s ) {
3253  $number = strtr( $number, $s );
3254  }
3255  }
3256 
3257  if ( $wgTranslateNumerals ) {
3258  $s = $this->digitTransformTable();
3259  if ( $s ) {
3260  $number = strtr( $number, $s );
3261  }
3262  }
3263 
3264  return $number;
3265  }
3266 
3275  public function formatNumNoSeparators( $number ) {
3276  return $this->formatNum( $number, true );
3277  }
3278 
3283  public function parseFormattedNumber( $number ) {
3284  $s = $this->digitTransformTable();
3285  if ( $s ) {
3286  // eliminate empty array values such as ''. (bug 64347)
3287  $s = array_filter( $s );
3288  $number = strtr( $number, array_flip( $s ) );
3289  }
3290 
3291  $s = $this->separatorTransformTable();
3292  if ( $s ) {
3293  // eliminate empty array values such as ''. (bug 64347)
3294  $s = array_filter( $s );
3295  $number = strtr( $number, array_flip( $s ) );
3296  }
3297 
3298  $number = strtr( $number, [ ',' => '' ] );
3299  return $number;
3300  }
3301 
3308  function commafy( $number ) {
3310  if ( $number === null ) {
3311  return '';
3312  }
3313 
3314  if ( !$digitGroupingPattern || $digitGroupingPattern === "###,###,###" ) {
3315  // default grouping is at thousands, use the same for ###,###,### pattern too.
3316  return strrev( (string)preg_replace( '/(\d{3})(?=\d)(?!\d*\.)/', '$1,', strrev( $number ) ) );
3317  } else {
3318  // Ref: http://cldr.unicode.org/translation/number-patterns
3319  $sign = "";
3320  if ( intval( $number ) < 0 ) {
3321  // For negative numbers apply the algorithm like positive number and add sign.
3322  $sign = "-";
3323  $number = substr( $number, 1 );
3324  }
3325  $integerPart = [];
3326  $decimalPart = [];
3327  $numMatches = preg_match_all( "/(#+)/", $digitGroupingPattern, $matches );
3328  preg_match( "/\d+/", $number, $integerPart );
3329  preg_match( "/\.\d*/", $number, $decimalPart );
3330  $groupedNumber = ( count( $decimalPart ) > 0 ) ? $decimalPart[0] : "";
3331  if ( $groupedNumber === $number ) {
3332  // the string does not have any number part. Eg: .12345
3333  return $sign . $groupedNumber;
3334  }
3335  $start = $end = ( $integerPart ) ? strlen( $integerPart[0] ) : 0;
3336  while ( $start > 0 ) {
3337  $match = $matches[0][$numMatches - 1];
3338  $matchLen = strlen( $match );
3339  $start = $end - $matchLen;
3340  if ( $start < 0 ) {
3341  $start = 0;
3342  }
3343  $groupedNumber = substr( $number, $start, $end -$start ) . $groupedNumber;
3344  $end = $start;
3345  if ( $numMatches > 1 ) {
3346  // use the last pattern for the rest of the number
3347  $numMatches--;
3348  }
3349  if ( $start > 0 ) {
3350  $groupedNumber = "," . $groupedNumber;
3351  }
3352  }
3353  return $sign . $groupedNumber;
3354  }
3355  }
3356 
3361  return self::$dataCache->getItem( $this->mCode, 'digitGroupingPattern' );
3362  }
3363 
3367  function digitTransformTable() {
3368  return self::$dataCache->getItem( $this->mCode, 'digitTransformTable' );
3369  }
3370 
3375  return self::$dataCache->getItem( $this->mCode, 'separatorTransformTable' );
3376  }
3377 
3387  function listToText( array $l ) {
3388  $m = count( $l ) - 1;
3389  if ( $m < 0 ) {
3390  return '';
3391  }
3392  if ( $m > 0 ) {
3393  $and = $this->msg( 'and' )->escaped();
3394  $space = $this->msg( 'word-separator' )->escaped();
3395  if ( $m > 1 ) {
3396  $comma = $this->msg( 'comma-separator' )->escaped();
3397  }
3398  }
3399  $s = $l[$m];
3400  for ( $i = $m - 1; $i >= 0; $i-- ) {
3401  if ( $i == $m - 1 ) {
3402  $s = $l[$i] . $and . $space . $s;
3403  } else {
3404  $s = $l[$i] . $comma . $s;
3405  }
3406  }
3407  return $s;
3408  }
3409 
3416  function commaList( array $list ) {
3417  return implode(
3418  wfMessage( 'comma-separator' )->inLanguage( $this )->escaped(),
3419  $list
3420  );
3421  }
3422 
3429  function semicolonList( array $list ) {
3430  return implode(
3431  wfMessage( 'semicolon-separator' )->inLanguage( $this )->escaped(),
3432  $list
3433  );
3434  }
3435 
3441  function pipeList( array $list ) {
3442  return implode(
3443  wfMessage( 'pipe-separator' )->inLanguage( $this )->escaped(),
3444  $list
3445  );
3446  }
3447 
3465  function truncate( $string, $length, $ellipsis = '...', $adjustLength = true ) {
3466  # Use the localized ellipsis character
3467  if ( $ellipsis == '...' ) {
3468  $ellipsis = wfMessage( 'ellipsis' )->inLanguage( $this )->escaped();
3469  }
3470  # Check if there is no need to truncate
3471  if ( $length == 0 ) {
3472  return $ellipsis; // convention
3473  } elseif ( strlen( $string ) <= abs( $length ) ) {
3474  return $string; // no need to truncate
3475  }
3476  $stringOriginal = $string;
3477  # If ellipsis length is >= $length then we can't apply $adjustLength
3478  if ( $adjustLength && strlen( $ellipsis ) >= abs( $length ) ) {
3479  $string = $ellipsis; // this can be slightly unexpected
3480  # Otherwise, truncate and add ellipsis...
3481  } else {
3482  $eLength = $adjustLength ? strlen( $ellipsis ) : 0;
3483  if ( $length > 0 ) {
3484  $length -= $eLength;
3485  $string = substr( $string, 0, $length ); // xyz...
3486  $string = $this->removeBadCharLast( $string );
3487  $string = rtrim( $string );
3488  $string = $string . $ellipsis;
3489  } else {
3490  $length += $eLength;
3491  $string = substr( $string, $length ); // ...xyz
3492  $string = $this->removeBadCharFirst( $string );
3493  $string = ltrim( $string );
3494  $string = $ellipsis . $string;
3495  }
3496  }
3497  # Do not truncate if the ellipsis makes the string longer/equal (bug 22181).
3498  # This check is *not* redundant if $adjustLength, due to the single case where
3499  # LEN($ellipsis) > ABS($limit arg); $stringOriginal could be shorter than $string.
3500  if ( strlen( $string ) < strlen( $stringOriginal ) ) {
3501  return $string;
3502  } else {
3503  return $stringOriginal;
3504  }
3505  }
3506 
3514  protected function removeBadCharLast( $string ) {
3515  if ( $string != '' ) {
3516  $char = ord( $string[strlen( $string ) - 1] );
3517  $m = [];
3518  if ( $char >= 0xc0 ) {
3519  # We got the first byte only of a multibyte char; remove it.
3520  $string = substr( $string, 0, -1 );
3521  } elseif ( $char >= 0x80 &&
3522  // Use the /s modifier (PCRE_DOTALL) so (.*) also matches newlines
3523  preg_match( '/^(.*)(?:[\xe0-\xef][\x80-\xbf]|' .
3524  '[\xf0-\xf7][\x80-\xbf]{1,2})$/s', $string, $m )
3525  ) {
3526  # We chopped in the middle of a character; remove it
3527  $string = $m[1];
3528  }
3529  }
3530  return $string;
3531  }
3532 
3540  protected function removeBadCharFirst( $string ) {
3541  if ( $string != '' ) {
3542  $char = ord( $string[0] );
3543  if ( $char >= 0x80 && $char < 0xc0 ) {
3544  # We chopped in the middle of a character; remove the whole thing
3545  $string = preg_replace( '/^[\x80-\xbf]+/', '', $string );
3546  }
3547  }
3548  return $string;
3549  }
3550 
3566  function truncateHtml( $text, $length, $ellipsis = '...' ) {
3567  # Use the localized ellipsis character
3568  if ( $ellipsis == '...' ) {
3569  $ellipsis = wfMessage( 'ellipsis' )->inLanguage( $this )->escaped();
3570  }
3571  # Check if there is clearly no need to truncate
3572  if ( $length <= 0 ) {
3573  return $ellipsis; // no text shown, nothing to format (convention)
3574  } elseif ( strlen( $text ) <= $length ) {
3575  return $text; // string short enough even *with* HTML (short-circuit)
3576  }
3577 
3578  $dispLen = 0; // innerHTML legth so far
3579  $testingEllipsis = false; // checking if ellipses will make string longer/equal?
3580  $tagType = 0; // 0-open, 1-close
3581  $bracketState = 0; // 1-tag start, 2-tag name, 0-neither
3582  $entityState = 0; // 0-not entity, 1-entity
3583  $tag = $ret = ''; // accumulated tag name, accumulated result string
3584  $openTags = []; // open tag stack
3585  $maybeState = null; // possible truncation state
3586 
3587  $textLen = strlen( $text );
3588  $neLength = max( 0, $length - strlen( $ellipsis ) ); // non-ellipsis len if truncated
3589  for ( $pos = 0; true; ++$pos ) {
3590  # Consider truncation once the display length has reached the maximim.
3591  # We check if $dispLen > 0 to grab tags for the $neLength = 0 case.
3592  # Check that we're not in the middle of a bracket/entity...
3593  if ( $dispLen && $dispLen >= $neLength && $bracketState == 0 && !$entityState ) {
3594  if ( !$testingEllipsis ) {
3595  $testingEllipsis = true;
3596  # Save where we are; we will truncate here unless there turn out to
3597  # be so few remaining characters that truncation is not necessary.
3598  if ( !$maybeState ) { // already saved? ($neLength = 0 case)
3599  $maybeState = [ $ret, $openTags ]; // save state
3600  }
3601  } elseif ( $dispLen > $length && $dispLen > strlen( $ellipsis ) ) {
3602  # String in fact does need truncation, the truncation point was OK.
3603  list( $ret, $openTags ) = $maybeState; // reload state
3604  $ret = $this->removeBadCharLast( $ret ); // multi-byte char fix
3605  $ret .= $ellipsis; // add ellipsis
3606  break;
3607  }
3608  }
3609  if ( $pos >= $textLen ) {
3610  break; // extra iteration just for above checks
3611  }
3612 
3613  # Read the next char...
3614  $ch = $text[$pos];
3615  $lastCh = $pos ? $text[$pos - 1] : '';
3616  $ret .= $ch; // add to result string
3617  if ( $ch == '<' ) {
3618  $this->truncate_endBracket( $tag, $tagType, $lastCh, $openTags ); // for bad HTML
3619  $entityState = 0; // for bad HTML
3620  $bracketState = 1; // tag started (checking for backslash)
3621  } elseif ( $ch == '>' ) {
3622  $this->truncate_endBracket( $tag, $tagType, $lastCh, $openTags );
3623  $entityState = 0; // for bad HTML
3624  $bracketState = 0; // out of brackets
3625  } elseif ( $bracketState == 1 ) {
3626  if ( $ch == '/' ) {
3627  $tagType = 1; // close tag (e.g. "</span>")
3628  } else {
3629  $tagType = 0; // open tag (e.g. "<span>")
3630  $tag .= $ch;
3631  }
3632  $bracketState = 2; // building tag name
3633  } elseif ( $bracketState == 2 ) {
3634  if ( $ch != ' ' ) {
3635  $tag .= $ch;
3636  } else {
3637  // Name found (e.g. "<a href=..."), add on tag attributes...
3638  $pos += $this->truncate_skip( $ret, $text, "<>", $pos + 1 );
3639  }
3640  } elseif ( $bracketState == 0 ) {
3641  if ( $entityState ) {
3642  if ( $ch == ';' ) {
3643  $entityState = 0;
3644  $dispLen++; // entity is one displayed char
3645  }
3646  } else {
3647  if ( $neLength == 0 && !$maybeState ) {
3648  // Save state without $ch. We want to *hit* the first
3649  // display char (to get tags) but not *use* it if truncating.
3650  $maybeState = [ substr( $ret, 0, -1 ), $openTags ];
3651  }
3652  if ( $ch == '&' ) {
3653  $entityState = 1; // entity found, (e.g. "&#160;")
3654  } else {
3655  $dispLen++; // this char is displayed
3656  // Add the next $max display text chars after this in one swoop...
3657  $max = ( $testingEllipsis ? $length : $neLength ) - $dispLen;
3658  $skipped = $this->truncate_skip( $ret, $text, "<>&", $pos + 1, $max );
3659  $dispLen += $skipped;
3660  $pos += $skipped;
3661  }
3662  }
3663  }
3664  }
3665  // Close the last tag if left unclosed by bad HTML
3666  $this->truncate_endBracket( $tag, $text[$textLen - 1], $tagType, $openTags );
3667  while ( count( $openTags ) > 0 ) {
3668  $ret .= '</' . array_pop( $openTags ) . '>'; // close open tags
3669  }
3670  return $ret;
3671  }
3672 
3684  private function truncate_skip( &$ret, $text, $search, $start, $len = null ) {
3685  if ( $len === null ) {
3686  $len = -1; // -1 means "no limit" for strcspn
3687  } elseif ( $len < 0 ) {
3688  $len = 0; // sanity
3689  }
3690  $skipCount = 0;
3691  if ( $start < strlen( $text ) ) {
3692  $skipCount = strcspn( $text, $search, $start, $len );
3693  $ret .= substr( $text, $start, $skipCount );
3694  }
3695  return $skipCount;
3696  }
3697 
3707  private function truncate_endBracket( &$tag, $tagType, $lastCh, &$openTags ) {
3708  $tag = ltrim( $tag );
3709  if ( $tag != '' ) {
3710  if ( $tagType == 0 && $lastCh != '/' ) {
3711  $openTags[] = $tag; // tag opened (didn't close itself)
3712  } elseif ( $tagType == 1 ) {
3713  if ( $openTags && $tag == $openTags[count( $openTags ) - 1] ) {
3714  array_pop( $openTags ); // tag closed
3715  }
3716  }
3717  $tag = '';
3718  }
3719  }
3720 
3729  function convertGrammar( $word, $case ) {
3731  if ( isset( $wgGrammarForms[$this->getCode()][$case][$word] ) ) {
3732  return $wgGrammarForms[$this->getCode()][$case][$word];
3733  }
3734 
3735  return $word;
3736  }
3742  function getGrammarForms() {
3744  if ( isset( $wgGrammarForms[$this->getCode()] )
3745  && is_array( $wgGrammarForms[$this->getCode()] )
3746  ) {
3747  return $wgGrammarForms[$this->getCode()];
3748  }
3749 
3750  return [];
3751  }
3771  function gender( $gender, $forms ) {
3772  if ( !count( $forms ) ) {
3773  return '';
3774  }
3775  $forms = $this->preConvertPlural( $forms, 2 );
3776  if ( $gender === 'male' ) {
3777  return $forms[0];
3778  }
3779  if ( $gender === 'female' ) {
3780  return $forms[1];
3781  }
3782  return isset( $forms[2] ) ? $forms[2] : $forms[0];
3783  }
3784 
3800  function convertPlural( $count, $forms ) {
3801  // Handle explicit n=pluralform cases
3802  $forms = $this->handleExplicitPluralForms( $count, $forms );
3803  if ( is_string( $forms ) ) {
3804  return $forms;
3805  }
3806  if ( !count( $forms ) ) {
3807  return '';
3808  }
3809 
3810  $pluralForm = $this->getPluralRuleIndexNumber( $count );
3811  $pluralForm = min( $pluralForm, count( $forms ) - 1 );
3812  return $forms[$pluralForm];
3813  }
3814 
3830  protected function handleExplicitPluralForms( $count, array $forms ) {
3831  foreach ( $forms as $index => $form ) {
3832  if ( preg_match( '/\d+=/i', $form ) ) {
3833  $pos = strpos( $form, '=' );
3834  if ( substr( $form, 0, $pos ) === (string)$count ) {
3835  return substr( $form, $pos + 1 );
3836  }
3837  unset( $forms[$index] );
3838  }
3839  }
3840  return array_values( $forms );
3841  }
3842 
3851  protected function preConvertPlural( /* Array */ $forms, $count ) {
3852  while ( count( $forms ) < $count ) {
3853  $forms[] = $forms[count( $forms ) - 1];
3854  }
3855  return $forms;
3856  }
3857 
3874  public function embedBidi( $text = '' ) {
3876  if ( $dir === 'ltr' ) {
3877  // Wrap in LEFT-TO-RIGHT EMBEDDING ... POP DIRECTIONAL FORMATTING
3878  return self::$lre . $text . self::$pdf;
3879  }
3880  if ( $dir === 'rtl' ) {
3881  // Wrap in RIGHT-TO-LEFT EMBEDDING ... POP DIRECTIONAL FORMATTING
3882  return self::$rle . $text . self::$pdf;
3883  }
3884  // No strong directionality: do not wrap
3885  return $text;
3886  }
3887 
3900  function translateBlockExpiry( $str, User $user = null ) {
3901  $duration = SpecialBlock::getSuggestedDurations( $this );
3902  foreach ( $duration as $show => $value ) {
3903  if ( strcmp( $str, $value ) == 0 ) {
3904  return htmlspecialchars( trim( $show ) );
3905  }
3906  }
3907 
3908  if ( wfIsInfinity( $str ) ) {
3909  foreach ( $duration as $show => $value ) {
3910  if ( wfIsInfinity( $value ) ) {
3911  return htmlspecialchars( trim( $show ) );
3912  }
3913  }
3914  }
3915 
3916  // If all else fails, return a standard duration or timestamp description.
3917  $time = strtotime( $str, 0 );
3918  if ( $time === false ) { // Unknown format. Return it as-is in case.
3919  return $str;
3920  } elseif ( $time !== strtotime( $str, 1 ) ) { // It's a relative timestamp.
3921  // $time is relative to 0 so it's a duration length.
3922  return $this->formatDuration( $time );
3923  } else { // It's an absolute timestamp.
3924  if ( $time === 0 ) {
3925  // wfTimestamp() handles 0 as current time instead of epoch.
3926  $time = '19700101000000';
3927  }
3928  if ( $user ) {
3929  return $this->userTimeAndDate( $time, $user );
3930  }
3931  return $this->timeanddate( $time );
3932  }
3933  }
3934 
3942  public function segmentForDiff( $text ) {
3943  return $text;
3944  }
3945 
3952  public function unsegmentForDiff( $text ) {
3953  return $text;
3954  }
3955 
3962  public function getConverter() {
3963  return $this->mConverter;
3964  }
3965 
3972  public function autoConvertToAllVariants( $text ) {
3973  return $this->mConverter->autoConvertToAllVariants( $text );
3974  }
3975 
3982  public function convert( $text ) {
3983  return $this->mConverter->convert( $text );
3984  }
3985 
3992  public function convertTitle( $title ) {
3993  return $this->mConverter->convertTitle( $title );
3994  }
3995 
4002  public function convertNamespace( $ns ) {
4003  return $this->mConverter->convertNamespace( $ns );
4004  }
4005 
4011  public function hasVariants() {
4012  return count( $this->getVariants() ) > 1;
4013  }
4014 
4022  public function hasVariant( $variant ) {
4023  return (bool)$this->mConverter->validateVariant( $variant );
4024  }
4025 
4033  public function convertHtml( $text, $isTitle = false ) {
4034  return htmlspecialchars( $this->convert( $text, $isTitle ) );
4035  }
4036 
4041  public function convertCategoryKey( $key ) {
4042  return $this->mConverter->convertCategoryKey( $key );
4043  }
4044 
4051  public function getVariants() {
4052  return $this->mConverter->getVariants();
4053  }
4054 
4058  public function getPreferredVariant() {
4059  return $this->mConverter->getPreferredVariant();
4060  }
4061 
4065  public function getDefaultVariant() {
4066  return $this->mConverter->getDefaultVariant();
4067  }
4068 
4072  public function getURLVariant() {
4073  return $this->mConverter->getURLVariant();
4074  }
4075 
4088  public function findVariantLink( &$link, &$nt, $ignoreOtherCond = false ) {
4089  $this->mConverter->findVariantLink( $link, $nt, $ignoreOtherCond );
4090  }
4091 
4098  function getExtraHashOptions() {
4099  return $this->mConverter->getExtraHashOptions();
4100  }
4101 
4109  public function getParsedTitle() {
4110  return $this->mConverter->getParsedTitle();
4111  }
4112 
4119  public function updateConversionTable( Title $title ) {
4120  $this->mConverter->updateConversionTable( $title );
4121  }
4122 
4135  public function markNoConversion( $text, $noParse = false ) {
4136  // Excluding protocal-relative URLs may avoid many false positives.
4137  if ( $noParse || preg_match( '/^(?:' . wfUrlProtocolsWithoutProtRel() . ')/', $text ) ) {
4138  return $this->mConverter->markNoConversion( $text );
4139  } else {
4140  return $text;
4141  }
4142  }
4143 
4150  public function linkTrail() {
4151  return self::$dataCache->getItem( $this->mCode, 'linkTrail' );
4152  }
4153 
4160  public function linkPrefixCharset() {
4161  return self::$dataCache->getItem( $this->mCode, 'linkPrefixCharset' );
4162  }
4163 
4171  public function getParentLanguage() {
4172  if ( $this->mParentLanguage !== false ) {
4173  return $this->mParentLanguage;
4174  }
4175 
4176  $code = explode( '-', $this->getCode() )[0];
4177  if ( !in_array( $code, LanguageConverter::$languagesWithVariants ) ) {
4178  $this->mParentLanguage = null;
4179  return null;
4180  }
4182  if ( !$lang->hasVariant( $this->getCode() ) ) {
4183  $this->mParentLanguage = null;
4184  return null;
4185  }
4186 
4187  $this->mParentLanguage = $lang;
4188  return $lang;
4189  }
4190 
4199  public function getCode() {
4200  return $this->mCode;
4201  }
4202 
4213  public function getHtmlCode() {
4214  if ( is_null( $this->mHtmlCode ) ) {
4215  $this->mHtmlCode = wfBCP47( $this->getCode() );
4216  }
4217  return $this->mHtmlCode;
4218  }
4219 
4223  public function setCode( $code ) {
4224  $this->mCode = $code;
4225  // Ensure we don't leave incorrect cached data lying around
4226  $this->mHtmlCode = null;
4227  $this->mParentLanguage = false;
4228  }
4229 
4237  public static function getCodeFromFileName( $filename, $prefix = 'Language', $suffix = '.php' ) {
4238  $m = null;
4239  preg_match( '/' . preg_quote( $prefix, '/' ) . '([A-Z][a-z_]+)' .
4240  preg_quote( $suffix, '/' ) . '/', $filename, $m );
4241  if ( !count( $m ) ) {
4242  return false;
4243  }
4244  return str_replace( '_', '-', strtolower( $m[1] ) );
4245  }
4246 
4251  public static function classFromCode( $code ) {
4252  if ( $code == 'en' ) {
4253  return 'Language';
4254  } else {
4255  return 'Language' . str_replace( '-', '_', ucfirst( $code ) );
4256  }
4257  }
4258 
4267  public static function getFileName( $prefix = 'Language', $code, $suffix = '.php' ) {
4268  if ( !self::isValidBuiltInCode( $code ) ) {
4269  throw new MWException( "Invalid language code \"$code\"" );
4270  }
4271 
4272  return $prefix . str_replace( '-', '_', ucfirst( $code ) ) . $suffix;
4273  }
4274 
4279  public static function getMessagesFileName( $code ) {
4280  global $IP;
4281  $file = self::getFileName( "$IP/languages/messages/Messages", $code, '.php' );
4282  Hooks::run( 'Language::getMessagesFileName', [ $code, &$file ] );
4283  return $file;
4284  }
4285 
4292  public static function getJsonMessagesFileName( $code ) {
4293  global $IP;
4294 
4295  if ( !self::isValidBuiltInCode( $code ) ) {
4296  throw new MWException( "Invalid language code \"$code\"" );
4297  }
4298 
4299  return "$IP/languages/i18n/$code.json";
4300  }
4301 
4309  public static function getFallbackFor( $code ) {
4310  $fallbacks = self::getFallbacksFor( $code );
4311  if ( $fallbacks ) {
4312  return $fallbacks[0];
4313  }
4314  return false;
4315  }
4316 
4324  public static function getFallbacksFor( $code ) {
4325  if ( $code === 'en' || !Language::isValidBuiltInCode( $code ) ) {
4326  return [];
4327  }
4328  // For unknown languages, fallbackSequence returns an empty array,
4329  // hardcode fallback to 'en' in that case.
4330  return self::getLocalisationCache()->getItem( $code, 'fallbackSequence' ) ?: [ 'en' ];
4331  }
4332 
4341  public static function getFallbacksIncludingSiteLanguage( $code ) {
4343 
4344  // Usually, we will only store a tiny number of fallback chains, so we
4345  // keep them in static memory.
4346  $cacheKey = "{$code}-{$wgLanguageCode}";
4347 
4348  if ( !array_key_exists( $cacheKey, self::$fallbackLanguageCache ) ) {
4349  $fallbacks = self::getFallbacksFor( $code );
4350 
4351  // Append the site's fallback chain, including the site language itself
4352  $siteFallbacks = self::getFallbacksFor( $wgLanguageCode );
4353  array_unshift( $siteFallbacks, $wgLanguageCode );
4354 
4355  // Eliminate any languages already included in the chain
4356  $siteFallbacks = array_diff( $siteFallbacks, $fallbacks );
4357 
4358  self::$fallbackLanguageCache[$cacheKey] = [ $fallbacks, $siteFallbacks ];
4359  }
4360  return self::$fallbackLanguageCache[$cacheKey];
4361  }
4362 
4372  public static function getMessagesFor( $code ) {
4373  return self::getLocalisationCache()->getItem( $code, 'messages' );
4374  }
4375 
4384  public static function getMessageFor( $key, $code ) {
4385  return self::getLocalisationCache()->getSubitem( $code, 'messages', $key );
4386  }
4387 
4396  public static function getMessageKeysFor( $code ) {
4397  return self::getLocalisationCache()->getSubitemList( $code, 'messages' );
4398  }
4399 
4404  function fixVariableInNamespace( $talk ) {
4405  if ( strpos( $talk, '$1' ) === false ) {
4406  return $talk;
4407  }
4408 
4410  $talk = str_replace( '$1', $wgMetaNamespace, $talk );
4411 
4412  # Allow grammar transformations
4413  # Allowing full message-style parsing would make simple requests
4414  # such as action=raw much more expensive than they need to be.
4415  # This will hopefully cover most cases.
4416  $talk = preg_replace_callback( '/{{grammar:(.*?)\|(.*?)}}/i',
4417  [ &$this, 'replaceGrammarInNamespace' ], $talk );
4418  return str_replace( ' ', '_', $talk );
4419  }
4420 
4425  function replaceGrammarInNamespace( $m ) {
4426  return $this->convertGrammar( trim( $m[2] ), trim( $m[1] ) );
4427  }
4428 
4439  public function formatExpiry( $expiry, $format = true, $infinity = 'infinity' ) {
4440  static $dbInfinity;
4441  if ( $dbInfinity === null ) {
4442  $dbInfinity = wfGetDB( DB_SLAVE )->getInfinity();
4443  }
4444 
4445  if ( $expiry == '' || $expiry === 'infinity' || $expiry == $dbInfinity ) {
4446  return $format === true
4447  ? $this->getMessageFromDB( 'infiniteblock' )
4448  : $infinity;
4449  } else {
4450  return $format === true
4451  ? $this->timeanddate( $expiry, /* User preference timezone */ true )
4452  : wfTimestamp( $format, $expiry );
4453  }
4454  }
4455 
4468  function formatTimePeriod( $seconds, $format = [] ) {
4469  if ( !is_array( $format ) ) {
4470  $format = [ 'avoid' => $format ]; // For backwards compatibility
4471  }
4472  if ( !isset( $format['avoid'] ) ) {
4473  $format['avoid'] = false;
4474  }
4475  if ( !isset( $format['noabbrevs'] ) ) {
4476  $format['noabbrevs'] = false;
4477  }
4478  $secondsMsg = wfMessage(
4479  $format['noabbrevs'] ? 'seconds' : 'seconds-abbrev' )->inLanguage( $this );
4480  $minutesMsg = wfMessage(
4481  $format['noabbrevs'] ? 'minutes' : 'minutes-abbrev' )->inLanguage( $this );
4482  $hoursMsg = wfMessage(
4483  $format['noabbrevs'] ? 'hours' : 'hours-abbrev' )->inLanguage( $this );
4484  $daysMsg = wfMessage(
4485  $format['noabbrevs'] ? 'days' : 'days-abbrev' )->inLanguage( $this );
4486 
4487  if ( round( $seconds * 10 ) < 100 ) {
4488  $s = $this->formatNum( sprintf( "%.1f", round( $seconds * 10 ) / 10 ) );
4489  $s = $secondsMsg->params( $s )->text();
4490  } elseif ( round( $seconds ) < 60 ) {
4491  $s = $this->formatNum( round( $seconds ) );
4492  $s = $secondsMsg->params( $s )->text();
4493  } elseif ( round( $seconds ) < 3600 ) {
4494  $minutes = floor( $seconds / 60 );
4495  $secondsPart = round( fmod( $seconds, 60 ) );
4496  if ( $secondsPart == 60 ) {
4497  $secondsPart = 0;
4498  $minutes++;
4499  }
4500  $s = $minutesMsg->params( $this->formatNum( $minutes ) )->text();
4501  $s .= ' ';
4502  $s .= $secondsMsg->params( $this->formatNum( $secondsPart ) )->text();
4503  } elseif ( round( $seconds ) <= 2 * 86400 ) {
4504  $hours = floor( $seconds / 3600 );
4505  $minutes = floor( ( $seconds - $hours * 3600 ) / 60 );
4506  $secondsPart = round( $seconds - $hours * 3600 - $minutes * 60 );
4507  if ( $secondsPart == 60 ) {
4508  $secondsPart = 0;
4509  $minutes++;
4510  }
4511  if ( $minutes == 60 ) {
4512  $minutes = 0;
4513  $hours++;
4514  }
4515  $s = $hoursMsg->params( $this->formatNum( $hours ) )->text();
4516  $s .= ' ';
4517  $s .= $minutesMsg->params( $this->formatNum( $minutes ) )->text();
4518  if ( !in_array( $format['avoid'], [ 'avoidseconds', 'avoidminutes' ] ) ) {
4519  $s .= ' ' . $secondsMsg->params( $this->formatNum( $secondsPart ) )->text();
4520  }
4521  } else {
4522  $days = floor( $seconds / 86400 );
4523  if ( $format['avoid'] === 'avoidminutes' ) {
4524  $hours = round( ( $seconds - $days * 86400 ) / 3600 );
4525  if ( $hours == 24 ) {
4526  $hours = 0;
4527  $days++;
4528  }
4529  $s = $daysMsg->params( $this->formatNum( $days ) )->text();
4530  $s .= ' ';
4531  $s .= $hoursMsg->params( $this->formatNum( $hours ) )->text();
4532  } elseif ( $format['avoid'] === 'avoidseconds' ) {
4533  $hours = floor( ( $seconds - $days * 86400 ) / 3600 );
4534  $minutes = round( ( $seconds - $days * 86400 - $hours * 3600 ) / 60 );
4535  if ( $minutes == 60 ) {
4536  $minutes = 0;
4537  $hours++;
4538  }
4539  if ( $hours == 24 ) {
4540  $hours = 0;
4541  $days++;
4542  }
4543  $s = $daysMsg->params( $this->formatNum( $days ) )->text();
4544  $s .= ' ';
4545  $s .= $hoursMsg->params( $this->formatNum( $hours ) )->text();
4546  $s .= ' ';
4547  $s .= $minutesMsg->params( $this->formatNum( $minutes ) )->text();
4548  } else {
4549  $s = $daysMsg->params( $this->formatNum( $days ) )->text();
4550  $s .= ' ';
4551  $s .= $this->formatTimePeriod( $seconds - $days * 86400, $format );
4552  }
4553  }
4554  return $s;
4555  }
4556 
4568  function formatBitrate( $bps ) {
4569  return $this->formatComputingNumbers( $bps, 1000, "bitrate-$1bits" );
4570  }
4571 
4578  function formatComputingNumbers( $size, $boundary, $messageKey ) {
4579  if ( $size <= 0 ) {
4580  return str_replace( '$1', $this->formatNum( $size ),
4581  $this->getMessageFromDB( str_replace( '$1', '', $messageKey ) )
4582  );
4583  }
4584  $sizes = [ '', 'kilo', 'mega', 'giga', 'tera', 'peta', 'exa', 'zeta', 'yotta' ];
4585  $index = 0;
4586 
4587  $maxIndex = count( $sizes ) - 1;
4588  while ( $size >= $boundary && $index < $maxIndex ) {
4589  $index++;
4590  $size /= $boundary;
4591  }
4592 
4593  // For small sizes no decimal places necessary
4594  $round = 0;
4595  if ( $index > 1 ) {
4596  // For MB and bigger two decimal places are smarter
4597  $round = 2;
4598  }
4599  $msg = str_replace( '$1', $sizes[$index], $messageKey );
4600 
4601  $size = round( $size, $round );
4602  $text = $this->getMessageFromDB( $msg );
4603  return str_replace( '$1', $this->formatNum( $size ), $text );
4604  }
4605 
4616  function formatSize( $size ) {
4617  return $this->formatComputingNumbers( $size, 1024, "size-$1bytes" );
4618  }
4619 
4629  function specialList( $page, $details, $oppositedm = true ) {
4630  if ( !$details ) {
4631  return $page;
4632  }
4633 
4634  $dirmark = ( $oppositedm ? $this->getDirMark( true ) : '' ) . $this->getDirMark();
4635  return
4636  $page .
4637  $dirmark .
4638  $this->msg( 'word-separator' )->escaped() .
4639  $this->msg( 'parentheses' )->rawParams( $details )->escaped();
4640  }
4641 
4652  public function viewPrevNext( Title $title, $offset, $limit,
4653  array $query = [], $atend = false
4654  ) {
4655  // @todo FIXME: Why on earth this needs one message for the text and another one for tooltip?
4656 
4657  # Make 'previous' link
4658  $prev = wfMessage( 'prevn' )->inLanguage( $this )->title( $title )->numParams( $limit )->text();
4659  if ( $offset > 0 ) {
4660  $plink = $this->numLink( $title, max( $offset - $limit, 0 ), $limit,
4661  $query, $prev, 'prevn-title', 'mw-prevlink' );
4662  } else {
4663  $plink = htmlspecialchars( $prev );
4664  }
4665 
4666  # Make 'next' link
4667  $next = wfMessage( 'nextn' )->inLanguage( $this )->title( $title )->numParams( $limit )->text();
4668  if ( $atend ) {
4669  $nlink = htmlspecialchars( $next );
4670  } else {
4671  $nlink = $this->numLink( $title, $offset + $limit, $limit,
4672  $query, $next, 'nextn-title', 'mw-nextlink' );
4673  }
4674 
4675  # Make links to set number of items per page
4676  $numLinks = [];
4677  foreach ( [ 20, 50, 100, 250, 500 ] as $num ) {
4678  $numLinks[] = $this->numLink( $title, $offset, $num,
4679  $query, $this->formatNum( $num ), 'shown-title', 'mw-numlink' );
4680  }
4681 
4682  return wfMessage( 'viewprevnext' )->inLanguage( $this )->title( $title
4683  )->rawParams( $plink, $nlink, $this->pipeList( $numLinks ) )->escaped();
4684  }
4685 
4698  private function numLink( Title $title, $offset, $limit, array $query, $link,
4699  $tooltipMsg, $class
4700  ) {
4701  $query = [ 'limit' => $limit, 'offset' => $offset ] + $query;
4702  $tooltip = wfMessage( $tooltipMsg )->inLanguage( $this )->title( $title )
4703  ->numParams( $limit )->text();
4704 
4705  return Html::element( 'a', [ 'href' => $title->getLocalURL( $query ),
4706  'title' => $tooltip, 'class' => $class ], $link );
4707  }
4708 
4714  public function getConvRuleTitle() {
4715  return $this->mConverter->getConvRuleTitle();
4716  }
4717 
4723  public function getCompiledPluralRules() {
4724  $pluralRules = self::$dataCache->getItem( strtolower( $this->mCode ), 'compiledPluralRules' );
4725  $fallbacks = Language::getFallbacksFor( $this->mCode );
4726  if ( !$pluralRules ) {
4727  foreach ( $fallbacks as $fallbackCode ) {
4728  $pluralRules = self::$dataCache->getItem( strtolower( $fallbackCode ), 'compiledPluralRules' );
4729  if ( $pluralRules ) {
4730  break;
4731  }
4732  }
4733  }
4734  return $pluralRules;
4735  }
4736 
4742  public function getPluralRules() {
4743  $pluralRules = self::$dataCache->getItem( strtolower( $this->mCode ), 'pluralRules' );
4744  $fallbacks = Language::getFallbacksFor( $this->mCode );
4745  if ( !$pluralRules ) {
4746  foreach ( $fallbacks as $fallbackCode ) {
4747  $pluralRules = self::$dataCache->getItem( strtolower( $fallbackCode ), 'pluralRules' );
4748  if ( $pluralRules ) {
4749  break;
4750  }
4751  }
4752  }
4753  return $pluralRules;
4754  }
4755 
4761  public function getPluralRuleTypes() {
4762  $pluralRuleTypes = self::$dataCache->getItem( strtolower( $this->mCode ), 'pluralRuleTypes' );
4763  $fallbacks = Language::getFallbacksFor( $this->mCode );
4764  if ( !$pluralRuleTypes ) {
4765  foreach ( $fallbacks as $fallbackCode ) {
4766  $pluralRuleTypes = self::$dataCache->getItem( strtolower( $fallbackCode ), 'pluralRuleTypes' );
4767  if ( $pluralRuleTypes ) {
4768  break;
4769  }
4770  }
4771  }
4772  return $pluralRuleTypes;
4773  }
4774 
4780  public function getPluralRuleIndexNumber( $number ) {
4781  $pluralRules = $this->getCompiledPluralRules();
4782  $form = Evaluator::evaluateCompiled( $number, $pluralRules );
4783  return $form;
4784  }
4785 
4794  public function getPluralRuleType( $number ) {
4795  $index = $this->getPluralRuleIndexNumber( $number );
4796  $pluralRuleTypes = $this->getPluralRuleTypes();
4797  if ( isset( $pluralRuleTypes[$index] ) ) {
4798  return $pluralRuleTypes[$index];
4799  } else {
4800  return 'other';
4801  }
4802  }
4803 }
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:4468
getDirMark($opposite=false)
A hidden direction mark (LRM or RLM), depending on the language direction.
Definition: Language.php:3088
static $mMonthAbbrevMsgs
Definition: Language.php:85
lc($str, $first=false)
Definition: Language.php:2679
$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:3202
getWeekdayAbbreviation($key)
Definition: Language.php:970
getDateFormatString($type, $pref)
Get a format string for a given type and preference.
Definition: Language.php:2181
getPluralRuleTypes()
Get the plural rule types for the language.
Definition: Language.php:4761
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:2454
static getLocalisationCache()
Get the LocalisationCache instance.
Definition: Language.php:402
convertPlural($count, $forms)
Plural form transformations, needed for some languages.
Definition: Language.php:3800
diff(MWTimestamp $relativeTo)
Calculate the difference between two MWTimestamp objects.
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:3148
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:762
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:2856
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:1418
magic word the default is to use $key to get the and $key value or $key value text $key value html to format the value $key
Definition: hooks.txt:2321
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:3729
static LocalisationCache $dataCache
Definition: Language.php:62
isMultibyte($str)
Definition: Language.php:2695
initEncoding()
Definition: Language.php:2930
handleExplicitPluralForms($count, array $forms)
Handles explicit plural forms for Language::convertPlural()
Definition: Language.php:3830
hasVariant($variant)
Check if the language has the specific variant.
Definition: Language.php:4022
if(count($args)==0) $dir
markNoConversion($text, $noParse=false)
Prepare external link text for conversion.
Definition: Language.php:4135
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:3008
date($ts, $adj=false, $format=true, $timecorrection=false)
Definition: Language.php:2215
getHtmlCode()
Get the code in BCP 47 format which we can use inside of html lang="" tags.
Definition: Language.php:4213
truncate_skip(&$ret, $text, $search, $start, $len=null)
truncateHtml() helper function like strcspn() but adds the skipped chars to $ret
Definition: Language.php:3684
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
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:3540
convertNamespace($ns)
Convert a namespace index to a string in the preferred variant.
Definition: Language.php:4002
static $IRANIAN_DAYS
Definition: Language.php:1544
static getMessageFor($key, $code)
Get a message for a given language.
Definition: Language.php:4384
lcfirst($str)
Definition: Language.php:2660
offsetForUser(User $user)
Adjust the timestamp depending on the given user's preferences.
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:1798
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:3056
getFallbackLanguages()
Definition: Language.php:442
firstChar($s)
Get the first character of a string.
Definition: Language.php:2878
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:4011
getCompiledPluralRules()
Get the compiled plural rules for the language.
Definition: Language.php:4723
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
sprintfDate($format, $ts, DateTimeZone $zone=null, &$ttl=null)
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
$wgEditEncoding
Character set for use in the article edit box.
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:2577
$value
convertCategoryKey($key)
Definition: Language.php:4041
$wgMetaNamespace
Name of the project namespace.
formatComputingNumbers($size, $boundary, $messageKey)
Definition: Language.php:4578
fallback8bitEncoding()
Definition: Language.php:2790
static getFallbacksIncludingSiteLanguage($code)
Get the ordered list of fallback languages, ending with the fallback language chain for the site lang...
Definition: Language.php:4341
formatNumNoSeparators($number)
Front-end for non-commafied formatNum.
Definition: Language.php:3275
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:3707
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:4150
getAllMessages()
Definition: Language.php:2567
wfUrlProtocolsWithoutProtRel()
Like wfUrlProtocols(), but excludes '//' from the protocol list.
normalize($s)
Convert a UTF-8 string to normal form C.
Definition: Language.php:2983
$wgNamespaceAliases
Namespace aliases.
static getCodeFromFileName($filename, $prefix= 'Language', $suffix= '.php')
Get the language code from a file name.
Definition: Language.php:4237
internalUserTimeAndDate($type, $ts, User $user, array $options)
Internal helper function for userDate(), userTime() and userTimeAndDate()
Definition: Language.php:2350
static getMessageKeysFor($code)
Get all message keys for a given language.
Definition: Language.php:4396
static tsToHijri($ts)
Converting Gregorian dates to Hijri dates.
Definition: Language.php:1616
formatExpiry($expiry, $format=true, $infinity= 'infinity')
Decode an expiry (block, protection, etc) which has come from the DB.
Definition: Language.php:4439
getVariants()
Get the list of variants supported by this language see sample implementation in LanguageZh.php.
Definition: Language.php:4051
A helper class for throttling authentication attempts.
$dateFormatStrings
Definition: Language.php:49
Represents a title within MediaWiki.
Definition: Title.php:34
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:2150
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:3972
static getFallbacksFor($code)
Get the ordered list of fallback languages.
Definition: Language.php:4324
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:3283
$wgTranslateNumerals
For Hindi and Arabic use local numerals instead of Western style (0-9) numerals in interface...
static isUtf8($value)
Test whether a string is valid UTF-8.
Definition: StringUtils.php:41
see documentation in includes Linker php for Linker::makeImageLink & $time
Definition: hooks.txt:1612
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:2581
setCode($code)
Definition: Language.php:4223
static $pdf
Definition: Language.php:155
static newFromCode($code)
Create a language object for a given language code.
Definition: Language.php:205
time($ts, $adj=false, $format=true, $timecorrection=false)
Definition: Language.php:2234
embedBidi($text= '')
Wraps argument with unicode control characters for directionality safety.
Definition: Language.php:3874
__construct()
Definition: Language.php:411
static getJsonMessagesFileName($code)
Definition: Language.php:4292
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:1798
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:4616
semicolonList(array $list)
Take a list of strings and build a locale-friendly semicolon-separated list, using the local semicolo...
Definition: Language.php:3429
addMagicWordsByLang($newWords)
Add magic words to the extension array.
Definition: Language.php:3187
segmentForDiff($text)
languages like Chinese need to be segmented in order for the diff to be of any use ...
Definition: Language.php:3942
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:4033
when a variable name is used in a function
Definition: design.txt:93
ucwordbreaksCallbackAscii($matches)
Definition: Language.php:2597
const NS_PROJECT
Definition: Defines.php:73
static $mHijriCalendarMonthMsgs
Definition: Language.php:113
ucwordsCallbackMB($matches)
Definition: Language.php:2613
linkPrefixCharset()
A regular expression character set to match legal word-prefixing characters which should be merged on...
Definition: Language.php:4160
ucwords($str)
Definition: Language.php:2703
static getMain()
Static methods.
getIranianCalendarMonthName($key)
Definition: Language.php:978
getPreferredVariant()
Definition: Language.php:4058
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:4568
truncateHtml($text, $length, $ellipsis= '...')
Truncate a string of valid HTML to a specified length in bytes, appending an optional string (e...
Definition: Language.php:3566
$wgLocalisationCacheConf
Localisation cache configuration.
getArrow($direction= 'forwards')
An arrow, depending on the language direction.
Definition: Language.php:3111
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:2836
getDefaultVariant()
Definition: Language.php:4065
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:1004
viewPrevNext(Title $title, $offset, $limit, array $query=[], $atend=false)
Generate (prev x| next x) (20|50|100...) type links for paging.
Definition: Language.php:4652
getConverter()
Return the LanguageConverter used in the Language.
Definition: Language.php:3962
static $mIranianCalendarMonthMsgs
Definition: Language.php:90
static hebrewNumeral($num)
Hebrew Gematria number formatting up to 9999.
Definition: Language.php:1981
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:1734
static tsToHebrew($ts)
Converting Gregorian dates to Hebrew dates.
Definition: Language.php:1668
getBookstoreList()
Exports $wgBookstoreListEn.
Definition: Language.php:450
capitalizeAllNouns()
Definition: Language.php:3100
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:3387
$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:2767
static getMessagesFileName($code)
Definition: Language.php:4279
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return an< a > element with HTML attributes $attribs and contents $html will be returned If you return $ret will be returned and may include noclasses after processing after in associative array form externallinks including delete and has completed for all link tables whether this was an auto creation default is conds Array Extra conditions for the No matching items in log is displayed if loglist is empty msgKey Array If you want a nice box with a set this to the key of the message First element is the message additional optional elements are parameters for the key that are processed with wfMessage() -> params() ->parseAsBlock()-offset Set to overwrite offset parameter in $wgRequest set to ''to unsetoffset-wrap String Wrap the message in html(usually something like"&lt
$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:2644
static getMessagesFor($code)
Get all messages for a given language WARNING: this may take a long time.
Definition: Language.php:4372
ucwordbreaksCallbackMB($matches)
Definition: Language.php:2605
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:4072
updateConversionTable(Title $title)
Refresh the cache of conversion tables when MediaWiki:Conversiontable* is updated.
Definition: Language.php:4119
convertTitle($title)
Convert a Title object to a string in the preferred variant.
Definition: Language.php:3992
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:3982
wfBCP47($code)
Get the normalised IETF language tag See unit test for examples.
$wgMetaNamespaceTalk
Name of the project talk namespace.
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:912
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:4267
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:2273
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:2727
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:965
userTime($ts, User $user, array $options=[])
Get the formatted time for the given timestamp and formatted for the given user.
Definition: Language.php:2412
getMessage($key)
Definition: Language.php:2560
static $mHebrewCalendarMonthMsgs
Definition: Language.php:97
pipeList(array $list)
Same as commaList, but separate it with the pipe instead.
Definition: Language.php:3441
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:3247
recodeInput($s)
Definition: Language.php:2957
return['SiteStore'=> function(MediaWikiServices $services){$loadBalancer=wfGetLB();$rawSiteStore=new DBSiteStore($loadBalancer);$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');},'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){return new SkinFactory();},]
$mExtendedSpecialPageAliases
Definition: Language.php:50
namespace and then decline to actually register it & $namespaces
Definition: hooks.txt:912
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:4742
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:762
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:2813
getMagicWords()
Get all magic words from cache.
Definition: Language.php:3141
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:4698
ucfirst($str)
Make a string's first character uppercase.
Definition: Language.php:2624
getOption($oname, $defaultOverride=null, $ignoreHidden=false)
Get the user's current setting for a given option.
Definition: User.php:2746
getHijriCalendarMonthName($key)
Definition: Language.php:1002
static getFallbackFor($code)
Get the first fallback for a given language.
Definition: Language.php:4309
timeanddate($ts, $adj=false, $format=true, $timecorrection=false)
Definition: Language.php:2254
getPluralRuleType($number)
Find the plural rule type appropriate for the given number For example, if the language is set to Ara...
Definition: Language.php:4794
userAdjust($ts, $tz=false)
Used by date() and time() to adjust the time output.
Definition: Language.php:2068
digitTransformTable()
Definition: Language.php:3367
userDate($ts, User $user, array $options=[])
Get the formatted date for the given timestamp and formatted for the given user.
Definition: Language.php:2389
getConvRuleTitle()
Get the conversion rule title, if any.
Definition: Language.php:4714
const TS_MW
MediaWiki concatenated string timestamp (YYYYMMDDHHMMSS)
getMagic($mw)
Fill a MagicWord object with data from here.
Definition: Language.php:3161
static classFromCode($code)
Definition: Language.php:4251
$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:2491
getCode()
Get the internal language code for this language object.
Definition: Language.php:4199
replaceGrammarInNamespace($m)
Definition: Language.php:4425
commafy($number)
Adds commas to a given number.
Definition: Language.php:3308
digitGroupingPattern()
Definition: Language.php:3360
$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:2435
translateBlockExpiry($str, User $user=null)
Definition: Language.php:3900
truncate($string, $length, $ellipsis= '...', $adjustLength=true)
Truncate a string to a specified length in bytes, appending an optional string (e.g.
Definition: Language.php:3465
static romanNumeral($num)
Roman number formatting up to 10000.
Definition: Language.php:1950
convertForSearchResult($termsArray)
Definition: Language.php:2866
removeBadCharLast($string)
Remove bytes that represent an incomplete Unicode character at the end of string (e.g.
Definition: Language.php:3514
$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:1558
$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:4629
getExtraHashOptions()
returns language specific options used by User::getPageRenderHash() for example, the preferred langua...
Definition: Language.php:4098
getParsedTitle()
For languages that support multiple variants, the title of an article may be displayed differently in...
Definition: Language.php:4109
preConvertPlural($forms, $count)
Checks that convertPlural was given an array and pads it to requested amount of forms by copying the ...
Definition: Language.php:3851
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:4088
$wgGrammarForms
Some languages need different word forms, usually for different cases.
emphasize($text)
Italic is unsuitable for some languages.
Definition: Language.php:3221
getDirMarkEntity($opposite=false)
A hidden direction mark (LRM or RLM), depending on the language direction.
Definition: Language.php:3071
separatorTransformTable()
Definition: Language.php:3374
isRTL()
For right-to-left language support.
Definition: Language.php:3024
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:1004
getDatePreference()
Get the user's preferred date format.
Definition: User.php:3068
getParentLanguage()
Get the "parent" language which has a converter to convert a "compatible" language (in another varian...
Definition: Language.php:4171
getDir()
Return the correct HTML 'dir' attribute value for this language.
Definition: Language.php:3032
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:2776
$count
normalizeForSearch($string)
Some languages have special punctuation need to be normalized.
Definition: Language.php:2824
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:2299
static $mWeekdayMsgs
Definition: Language.php:66
linkPrefixExtension()
To allow "foo[[bar]]" to extend the link over the whole word "foobar".
Definition: Language.php:3133
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:3742
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:1545
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:4404
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:3771
commaList(array $list)
Take a list of strings and build a locale-friendly comma-separated list, using the local comma-separa...
Definition: Language.php:3416
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:2338
hasWordBreaks()
Most writing systems use whitespace to break up words.
Definition: Language.php:2802
static strongDirFromContent($text= '')
Gets directionality of the first strongly directional codepoint, for embedBidi()
Definition: Language.php:1933
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:3044
static tsToYear($ts, $cName)
Algorithm to convert Gregorian dates to Thai solar dates, Minguo dates or Minguo dates.
Definition: Language.php:1847
getPluralRuleIndexNumber($number)
Find the index number of the plural rule appropriate for the given number.
Definition: Language.php:4780
static hebrewYearStart($year)
This calculates the Hebrew year start, as days since 1 September.
Definition: Language.php:1809
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:2338
static $GREG_DAYS
Definition: Language.php:1543
getHebrewCalendarMonthName($key)
Definition: Language.php:986
$wgUser
Definition: Setup.php:794
unsegmentForDiff($text)
and unsegment to show the result
Definition: Language.php:3952
$matches
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:310