MediaWiki  master
Language.php
Go to the documentation of this file.
1 <?php
30 
35 class Language {
40  const AS_AUTONYMS = null;
41 
46  const ALL = 'all';
47 
53  const SUPPORTED = 'mwfile';
54 
58  public $mConverter;
59 
60  public $mVariants, $mCode, $mLoaded = false;
61  public $mMagicExtensions = [], $mMagicHookDone = false;
62  private $mHtmlCode = null, $mParentLanguage = false;
63 
64  public $dateFormatStrings = [];
66 
68  protected $namespaceNames;
70 
74  public $transformData = [];
75 
79  static public $dataCache;
80 
81  static public $mLangObjCache = [];
82 
87  const MESSAGES_FALLBACKS = 0;
88 
93  const STRICT_FALLBACKS = 1;
94 
95  static public $mWeekdayMsgs = [
96  'sunday', 'monday', 'tuesday', 'wednesday', 'thursday',
97  'friday', 'saturday'
98  ];
99 
100  static public $mWeekdayAbbrevMsgs = [
101  'sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'
102  ];
103 
104  static public $mMonthMsgs = [
105  'january', 'february', 'march', 'april', 'may_long', 'june',
106  'july', 'august', 'september', 'october', 'november',
107  'december'
108  ];
109  static public $mMonthGenMsgs = [
110  'january-gen', 'february-gen', 'march-gen', 'april-gen', 'may-gen', 'june-gen',
111  'july-gen', 'august-gen', 'september-gen', 'october-gen', 'november-gen',
112  'december-gen'
113  ];
114  static public $mMonthAbbrevMsgs = [
115  'jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug',
116  'sep', 'oct', 'nov', 'dec'
117  ];
118 
119  static public $mIranianCalendarMonthMsgs = [
120  'iranian-calendar-m1', 'iranian-calendar-m2', 'iranian-calendar-m3',
121  'iranian-calendar-m4', 'iranian-calendar-m5', 'iranian-calendar-m6',
122  'iranian-calendar-m7', 'iranian-calendar-m8', 'iranian-calendar-m9',
123  'iranian-calendar-m10', 'iranian-calendar-m11', 'iranian-calendar-m12'
124  ];
125 
126  static public $mHebrewCalendarMonthMsgs = [
127  'hebrew-calendar-m1', 'hebrew-calendar-m2', 'hebrew-calendar-m3',
128  'hebrew-calendar-m4', 'hebrew-calendar-m5', 'hebrew-calendar-m6',
129  'hebrew-calendar-m7', 'hebrew-calendar-m8', 'hebrew-calendar-m9',
130  'hebrew-calendar-m10', 'hebrew-calendar-m11', 'hebrew-calendar-m12',
131  'hebrew-calendar-m6a', 'hebrew-calendar-m6b'
132  ];
133 
135  'hebrew-calendar-m1-gen', 'hebrew-calendar-m2-gen', 'hebrew-calendar-m3-gen',
136  'hebrew-calendar-m4-gen', 'hebrew-calendar-m5-gen', 'hebrew-calendar-m6-gen',
137  'hebrew-calendar-m7-gen', 'hebrew-calendar-m8-gen', 'hebrew-calendar-m9-gen',
138  'hebrew-calendar-m10-gen', 'hebrew-calendar-m11-gen', 'hebrew-calendar-m12-gen',
139  'hebrew-calendar-m6a-gen', 'hebrew-calendar-m6b-gen'
140  ];
141 
142  static public $mHijriCalendarMonthMsgs = [
143  'hijri-calendar-m1', 'hijri-calendar-m2', 'hijri-calendar-m3',
144  'hijri-calendar-m4', 'hijri-calendar-m5', 'hijri-calendar-m6',
145  'hijri-calendar-m7', 'hijri-calendar-m8', 'hijri-calendar-m9',
146  'hijri-calendar-m10', 'hijri-calendar-m11', 'hijri-calendar-m12'
147  ];
148 
153  static public $durationIntervals = [
154  'millennia' => 31556952000,
155  'centuries' => 3155695200,
156  'decades' => 315569520,
157  'years' => 31556952, // 86400 * ( 365 + ( 24 * 3 + 25 ) / 400 )
158  'weeks' => 604800,
159  'days' => 86400,
160  'hours' => 3600,
161  'minutes' => 60,
162  'seconds' => 1,
163  ];
164 
171  static private $fallbackLanguageCache = [];
172 
177  static private $grammarTransformations;
178 
183  static private $languageNameCache;
184 
188  static private $lre = "\u{202A}"; // U+202A LEFT-TO-RIGHT EMBEDDING
189  static private $rle = "\u{202B}"; // U+202B RIGHT-TO-LEFT EMBEDDING
190  static private $pdf = "\u{202C}"; // U+202C POP DIRECTIONAL FORMATTING
191 
203  // @codeCoverageIgnoreStart
204  // phpcs:ignore Generic.Files.LineLength
205  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';
206  // @codeCoverageIgnoreEnd
207 
214  static function factory( $code ) {
216 
217  if ( isset( $wgDummyLanguageCodes[$code] ) ) {
218  $code = $wgDummyLanguageCodes[$code];
219  }
220 
221  // get the language object to process
222  $langObj = self::$mLangObjCache[$code] ?? self::newFromCode( $code );
223 
224  // merge the language object in to get it up front in the cache
225  self::$mLangObjCache = array_merge( [ $code => $langObj ], self::$mLangObjCache );
226  // get rid of the oldest ones in case we have an overflow
227  self::$mLangObjCache = array_slice( self::$mLangObjCache, 0, $wgLangObjCacheSize, true );
228 
229  return $langObj;
230  }
231 
239  protected static function newFromCode( $code, $fallback = false ) {
240  if ( !self::isValidCode( $code ) ) {
241  throw new MWException( "Invalid language code \"$code\"" );
242  }
243 
244  if ( !self::isValidBuiltInCode( $code ) ) {
245  // It's not possible to customise this code with class files, so
246  // just return a Language object. This is to support uselang= hacks.
247  $lang = new Language;
248  $lang->setCode( $code );
249  return $lang;
250  }
251 
252  // Check if there is a language class for the code
253  $class = self::classFromCode( $code, $fallback );
254  // LanguageCode does not inherit Language
255  if ( class_exists( $class ) && is_a( $class, 'Language', true ) ) {
256  $lang = new $class;
257  return $lang;
258  }
259 
260  // Keep trying the fallback list until we find an existing class
261  $fallbacks = self::getFallbacksFor( $code );
262  foreach ( $fallbacks as $fallbackCode ) {
263  if ( !self::isValidBuiltInCode( $fallbackCode ) ) {
264  throw new MWException( "Invalid fallback '$fallbackCode' in fallback sequence for '$code'" );
265  }
266 
267  $class = self::classFromCode( $fallbackCode );
268  if ( class_exists( $class ) ) {
269  $lang = new $class;
270  $lang->setCode( $code );
271  return $lang;
272  }
273  }
274 
275  throw new MWException( "Invalid fallback sequence for language '$code'" );
276  }
277 
283  public static function clearCaches() {
284  if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
285  throw new MWException( __METHOD__ . ' must not be used outside tests' );
286  }
287  self::$dataCache = null;
288  // Reinitialize $dataCache, since it's expected to always be available
289  self::getLocalisationCache();
290  self::$mLangObjCache = [];
291  self::$fallbackLanguageCache = [];
292  self::$grammarTransformations = null;
293  self::$languageNameCache = null;
294  }
295 
304  public static function isSupportedLanguage( $code ) {
305  if ( !self::isValidBuiltInCode( $code ) ) {
306  return false;
307  }
308 
309  if ( $code === 'qqq' ) {
310  return false;
311  }
312 
313  return is_readable( self::getMessagesFileName( $code ) ) ||
314  is_readable( self::getJsonMessagesFileName( $code ) );
315  }
316 
332  public static function isWellFormedLanguageTag( $code, $lenient = false ) {
333  $alpha = '[a-z]';
334  $digit = '[0-9]';
335  $alphanum = '[a-z0-9]';
336  $x = 'x'; # private use singleton
337  $singleton = '[a-wy-z]'; # other singleton
338  $s = $lenient ? '[-_]' : '-';
339 
340  $language = "$alpha{2,8}|$alpha{2,3}$s$alpha{3}";
341  $script = "$alpha{4}"; # ISO 15924
342  $region = "(?:$alpha{2}|$digit{3})"; # ISO 3166-1 alpha-2 or UN M.49
343  $variant = "(?:$alphanum{5,8}|$digit$alphanum{3})";
344  $extension = "$singleton(?:$s$alphanum{2,8})+";
345  $privateUse = "$x(?:$s$alphanum{1,8})+";
346 
347  # Define certain grandfathered codes, since otherwise the regex is pretty useless.
348  # Since these are limited, this is safe even later changes to the registry --
349  # the only oddity is that it might change the type of the tag, and thus
350  # the results from the capturing groups.
351  # https://www.iana.org/assignments/language-subtag-registry
352 
353  $grandfathered = "en{$s}GB{$s}oed"
354  . "|i{$s}(?:ami|bnn|default|enochian|hak|klingon|lux|mingo|navajo|pwn|tao|tay|tsu)"
355  . "|no{$s}(?:bok|nyn)"
356  . "|sgn{$s}(?:BE{$s}(?:fr|nl)|CH{$s}de)"
357  . "|zh{$s}min{$s}nan";
358 
359  $variantList = "$variant(?:$s$variant)*";
360  $extensionList = "$extension(?:$s$extension)*";
361 
362  $langtag = "(?:($language)"
363  . "(?:$s$script)?"
364  . "(?:$s$region)?"
365  . "(?:$s$variantList)?"
366  . "(?:$s$extensionList)?"
367  . "(?:$s$privateUse)?)";
368 
369  # The final breakdown, with capturing groups for each of these components
370  # The variants, extensions, grandfathered, and private-use may have interior '-'
371 
372  $root = "^(?:$langtag|$privateUse|$grandfathered)$";
373 
374  return (bool)preg_match( "/$root/", strtolower( $code ) );
375  }
376 
386  public static function isValidCode( $code ) {
387  static $cache = [];
388  if ( !isset( $cache[$code] ) ) {
389  // People think language codes are html safe, so enforce it.
390  // Ideally we should only allow a-zA-Z0-9-
391  // but, .+ and other chars are often used for {{int:}} hacks
392  // see bugs T39564, T39587, T38938
393  $cache[$code] =
394  // Protect against path traversal
395  strcspn( $code, ":/\\\000&<>'\"" ) === strlen( $code )
396  && !preg_match( MediaWikiTitleCodec::getTitleInvalidRegex(), $code );
397  }
398  return $cache[$code];
399  }
400 
411  public static function isValidBuiltInCode( $code ) {
412  if ( !is_string( $code ) ) {
413  if ( is_object( $code ) ) {
414  $addmsg = " of class " . get_class( $code );
415  } else {
416  $addmsg = '';
417  }
418  $type = gettype( $code );
419  throw new MWException( __METHOD__ . " must be passed a string, $type given$addmsg" );
420  }
421 
422  return (bool)preg_match( '/^[a-z0-9-]{2,}$/', $code );
423  }
424 
433  public static function isKnownLanguageTag( $tag ) {
434  // Quick escape for invalid input to avoid exceptions down the line
435  // when code tries to process tags which are not valid at all.
436  if ( !self::isValidBuiltInCode( $tag ) ) {
437  return false;
438  }
439 
440  if ( isset( MediaWiki\Languages\Data\Names::$names[$tag] )
441  || self::fetchLanguageName( $tag, $tag ) !== ''
442  ) {
443  return true;
444  }
445 
446  return false;
447  }
448 
454  public static function getLocalisationCache() {
455  if ( is_null( self::$dataCache ) ) {
457  $class = $wgLocalisationCacheConf['class'];
458  self::$dataCache = new $class( $wgLocalisationCacheConf );
459  }
460  return self::$dataCache;
461  }
462 
463  function __construct() {
464  $this->mConverter = new FakeConverter( $this );
465  // Set the code to the name of the descendant
466  if ( static::class === 'Language' ) {
467  $this->mCode = 'en';
468  } else {
469  $this->mCode = str_replace( '_', '-', strtolower( substr( static::class, 8 ) ) );
470  }
471  self::getLocalisationCache();
472  }
473 
477  function __destruct() {
478  foreach ( $this as $name => $value ) {
479  unset( $this->$name );
480  }
481  }
482 
487  function initContLang() {
488  }
489 
494  public function getFallbackLanguages() {
495  return self::getFallbacksFor( $this->mCode );
496  }
497 
502  public function getBookstoreList() {
503  return self::$dataCache->getItem( $this->mCode, 'bookstoreList' );
504  }
505 
512  public function getNamespaces() {
513  if ( is_null( $this->namespaceNames ) ) {
515 
516  $validNamespaces = MWNamespace::getCanonicalNamespaces();
517 
518  $this->namespaceNames = $wgExtraNamespaces +
519  self::$dataCache->getItem( $this->mCode, 'namespaceNames' );
520  $this->namespaceNames += $validNamespaces;
521 
522  $this->namespaceNames[NS_PROJECT] = $wgMetaNamespace;
523  if ( $wgMetaNamespaceTalk ) {
524  $this->namespaceNames[NS_PROJECT_TALK] = $wgMetaNamespaceTalk;
525  } else {
526  $talk = $this->namespaceNames[NS_PROJECT_TALK];
527  $this->namespaceNames[NS_PROJECT_TALK] =
528  $this->fixVariableInNamespace( $talk );
529  }
530 
531  # Sometimes a language will be localised but not actually exist on this wiki.
532  foreach ( $this->namespaceNames as $key => $text ) {
533  if ( !isset( $validNamespaces[$key] ) ) {
534  unset( $this->namespaceNames[$key] );
535  }
536  }
537 
538  # The above mixing may leave namespaces out of canonical order.
539  # Re-order by namespace ID number...
540  ksort( $this->namespaceNames );
541 
542  Hooks::run( 'LanguageGetNamespaces', [ &$this->namespaceNames ] );
543  }
544 
545  return $this->namespaceNames;
546  }
547 
552  public function setNamespaces( array $namespaces ) {
553  $this->namespaceNames = $namespaces;
554  $this->mNamespaceIds = null;
555  }
556 
560  public function resetNamespaces() {
561  $this->namespaceNames = null;
562  $this->mNamespaceIds = null;
563  $this->namespaceAliases = null;
564  }
565 
572  public function getFormattedNamespaces() {
573  $ns = $this->getNamespaces();
574  foreach ( $ns as $k => $v ) {
575  $ns[$k] = strtr( $v, '_', ' ' );
576  }
577  return $ns;
578  }
579 
591  public function getNsText( $index ) {
592  $ns = $this->getNamespaces();
593  return $ns[$index] ?? false;
594  }
595 
609  public function getFormattedNsText( $index ) {
610  $ns = $this->getNsText( $index );
611  return strtr( $ns, '_', ' ' );
612  }
613 
622  public function getGenderNsText( $index, $gender ) {
624 
625  $ns = $wgExtraGenderNamespaces +
626  (array)self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' );
627 
628  return $ns[$index][$gender] ?? $this->getNsText( $index );
629  }
630 
637  public function needsGenderDistinction() {
639  if ( count( $wgExtraGenderNamespaces ) > 0 ) {
640  // $wgExtraGenderNamespaces overrides everything
641  return true;
642  } elseif ( isset( $wgExtraNamespaces[NS_USER] ) && isset( $wgExtraNamespaces[NS_USER_TALK] ) ) {
644  // $wgExtraNamespaces overrides any gender aliases specified in i18n files
645  return false;
646  } else {
647  // Check what is in i18n files
648  $aliases = self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' );
649  return count( $aliases ) > 0;
650  }
651  }
652 
661  function getLocalNsIndex( $text ) {
662  $lctext = $this->lc( $text );
663  $ids = $this->getNamespaceIds();
664  return $ids[$lctext] ?? false;
665  }
666 
670  public function getNamespaceAliases() {
671  if ( is_null( $this->namespaceAliases ) ) {
672  $aliases = self::$dataCache->getItem( $this->mCode, 'namespaceAliases' );
673  if ( !$aliases ) {
674  $aliases = [];
675  } else {
676  foreach ( $aliases as $name => $index ) {
677  if ( $index === NS_PROJECT_TALK ) {
678  unset( $aliases[$name] );
679  $name = $this->fixVariableInNamespace( $name );
680  $aliases[$name] = $index;
681  }
682  }
683  }
684 
686  $genders = $wgExtraGenderNamespaces +
687  (array)self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' );
688  foreach ( $genders as $index => $forms ) {
689  foreach ( $forms as $alias ) {
690  $aliases[$alias] = $index;
691  }
692  }
693 
694  # Also add converted namespace names as aliases, to avoid confusion.
695  $convertedNames = [];
696  foreach ( $this->getVariants() as $variant ) {
697  if ( $variant === $this->mCode ) {
698  continue;
699  }
700  foreach ( $this->getNamespaces() as $ns => $_ ) {
701  $convertedNames[$this->getConverter()->convertNamespace( $ns, $variant )] = $ns;
702  }
703  }
704 
705  $this->namespaceAliases = $aliases + $convertedNames;
706  }
707 
709  }
710 
714  public function getNamespaceIds() {
715  if ( is_null( $this->mNamespaceIds ) ) {
716  global $wgNamespaceAliases;
717  # Put namespace names and aliases into a hashtable.
718  # If this is too slow, then we should arrange it so that it is done
719  # before caching. The catch is that at pre-cache time, the above
720  # class-specific fixup hasn't been done.
721  $this->mNamespaceIds = [];
722  foreach ( $this->getNamespaces() as $index => $name ) {
723  $this->mNamespaceIds[$this->lc( $name )] = $index;
724  }
725  foreach ( $this->getNamespaceAliases() as $name => $index ) {
726  $this->mNamespaceIds[$this->lc( $name )] = $index;
727  }
728  if ( $wgNamespaceAliases ) {
729  foreach ( $wgNamespaceAliases as $name => $index ) {
730  $this->mNamespaceIds[$this->lc( $name )] = $index;
731  }
732  }
733  }
734  return $this->mNamespaceIds;
735  }
736 
744  public function getNsIndex( $text ) {
745  $lctext = $this->lc( $text );
746  $ns = MWNamespace::getCanonicalIndex( $lctext );
747  if ( $ns !== null ) {
748  return $ns;
749  }
750  $ids = $this->getNamespaceIds();
751  return $ids[$lctext] ?? false;
752  }
753 
761  public function getVariantname( $code, $usemsg = true ) {
762  $msg = "variantname-$code";
763  if ( $usemsg && wfMessage( $msg )->exists() ) {
764  return $this->getMessageFromDB( $msg );
765  }
766  $name = self::fetchLanguageName( $code );
767  if ( $name ) {
768  return $name; # if it's defined as a language name, show that
769  } else {
770  # otherwise, output the language code
771  return $code;
772  }
773  }
774 
778  public function getDatePreferences() {
779  return self::$dataCache->getItem( $this->mCode, 'datePreferences' );
780  }
781 
785  function getDateFormats() {
786  return self::$dataCache->getItem( $this->mCode, 'dateFormats' );
787  }
788 
792  public function getDefaultDateFormat() {
793  $df = self::$dataCache->getItem( $this->mCode, 'defaultDateFormat' );
794  if ( $df === 'dmy or mdy' ) {
795  global $wgAmericanDates;
796  return $wgAmericanDates ? 'mdy' : 'dmy';
797  } else {
798  return $df;
799  }
800  }
801 
805  public function getDatePreferenceMigrationMap() {
806  return self::$dataCache->getItem( $this->mCode, 'datePreferenceMigrationMap' );
807  }
808 
813  function getImageFile( $image ) {
814  return self::$dataCache->getSubitem( $this->mCode, 'imageFiles', $image );
815  }
816 
821  public function getImageFiles() {
822  return self::$dataCache->getItem( $this->mCode, 'imageFiles' );
823  }
824 
828  public function getExtraUserToggles() {
829  return (array)self::$dataCache->getItem( $this->mCode, 'extraUserToggles' );
830  }
831 
836  function getUserToggle( $tog ) {
837  return $this->getMessageFromDB( "tog-$tog" );
838  }
839 
851  public static function fetchLanguageNames( $inLanguage = self::AS_AUTONYMS, $include = 'mw' ) {
852  $cacheKey = $inLanguage === self::AS_AUTONYMS ? 'null' : $inLanguage;
853  $cacheKey .= ":$include";
854  if ( self::$languageNameCache === null ) {
855  self::$languageNameCache = new HashBagOStuff( [ 'maxKeys' => 20 ] );
856  }
857 
858  $ret = self::$languageNameCache->get( $cacheKey );
859  if ( !$ret ) {
860  $ret = self::fetchLanguageNamesUncached( $inLanguage, $include );
861  self::$languageNameCache->set( $cacheKey, $ret );
862  }
863  return $ret;
864  }
865 
876  private static function fetchLanguageNamesUncached(
877  $inLanguage = self::AS_AUTONYMS,
878  $include = 'mw'
879  ) {
880  global $wgExtraLanguageNames, $wgUsePigLatinVariant;
881 
882  // If passed an invalid language code to use, fallback to en
883  if ( $inLanguage !== self::AS_AUTONYMS && !self::isValidCode( $inLanguage ) ) {
884  $inLanguage = 'en';
885  }
886 
887  $names = [];
888 
889  if ( $inLanguage ) {
890  # TODO: also include when $inLanguage is null, when this code is more efficient
891  Hooks::run( 'LanguageGetTranslatedLanguageNames', [ &$names, $inLanguage ] );
892  }
893 
894  $mwNames = $wgExtraLanguageNames + MediaWiki\Languages\Data\Names::$names;
895  if ( $wgUsePigLatinVariant ) {
896  // Pig Latin (for variant development)
897  $mwNames['en-x-piglatin'] = 'Igpay Atinlay';
898  }
899 
900  foreach ( $mwNames as $mwCode => $mwName ) {
901  # - Prefer own MediaWiki native name when not using the hook
902  # - For other names just add if not added through the hook
903  if ( $mwCode === $inLanguage || !isset( $names[$mwCode] ) ) {
904  $names[$mwCode] = $mwName;
905  }
906  }
907 
908  if ( $include === self::ALL ) {
909  ksort( $names );
910  return $names;
911  }
912 
913  $returnMw = [];
914  $coreCodes = array_keys( $mwNames );
915  foreach ( $coreCodes as $coreCode ) {
916  $returnMw[$coreCode] = $names[$coreCode];
917  }
918 
919  if ( $include === self::SUPPORTED ) {
920  $namesMwFile = [];
921  # We do this using a foreach over the codes instead of a directory
922  # loop so that messages files in extensions will work correctly.
923  foreach ( $returnMw as $code => $value ) {
924  if ( is_readable( self::getMessagesFileName( $code ) )
925  || is_readable( self::getJsonMessagesFileName( $code ) )
926  ) {
927  $namesMwFile[$code] = $names[$code];
928  }
929  }
930 
931  ksort( $namesMwFile );
932  return $namesMwFile;
933  }
934 
935  ksort( $returnMw );
936  # 'mw' option; default if it's not one of the other two options (all/mwfile)
937  return $returnMw;
938  }
939 
948  public static function fetchLanguageName(
949  $code,
950  $inLanguage = self::AS_AUTONYMS,
951  $include = self::ALL
952  ) {
953  $code = strtolower( $code );
954  $array = self::fetchLanguageNames( $inLanguage, $include );
955  return !array_key_exists( $code, $array ) ? '' : $array[$code];
956  }
957 
964  public function getMessageFromDB( $msg ) {
965  return $this->msg( $msg )->text();
966  }
967 
974  protected function msg( $msg ) {
975  return wfMessage( $msg )->inLanguage( $this );
976  }
977 
982  public function getMonthName( $key ) {
983  return $this->getMessageFromDB( self::$mMonthMsgs[$key - 1] );
984  }
985 
989  public function getMonthNamesArray() {
990  $monthNames = [ '' ];
991  for ( $i = 1; $i < 13; $i++ ) {
992  $monthNames[] = $this->getMonthName( $i );
993  }
994  return $monthNames;
995  }
996 
1001  public function getMonthNameGen( $key ) {
1002  return $this->getMessageFromDB( self::$mMonthGenMsgs[$key - 1] );
1003  }
1004 
1009  public function getMonthAbbreviation( $key ) {
1010  return $this->getMessageFromDB( self::$mMonthAbbrevMsgs[$key - 1] );
1011  }
1012 
1016  public function getMonthAbbreviationsArray() {
1017  $monthNames = [ '' ];
1018  for ( $i = 1; $i < 13; $i++ ) {
1019  $monthNames[] = $this->getMonthAbbreviation( $i );
1020  }
1021  return $monthNames;
1022  }
1023 
1028  public function getWeekdayName( $key ) {
1029  return $this->getMessageFromDB( self::$mWeekdayMsgs[$key - 1] );
1030  }
1031 
1036  function getWeekdayAbbreviation( $key ) {
1037  return $this->getMessageFromDB( self::$mWeekdayAbbrevMsgs[$key - 1] );
1038  }
1039 
1044  function getIranianCalendarMonthName( $key ) {
1045  return $this->getMessageFromDB( self::$mIranianCalendarMonthMsgs[$key - 1] );
1046  }
1047 
1052  function getHebrewCalendarMonthName( $key ) {
1053  return $this->getMessageFromDB( self::$mHebrewCalendarMonthMsgs[$key - 1] );
1054  }
1055 
1061  return $this->getMessageFromDB( self::$mHebrewCalendarMonthGenMsgs[$key - 1] );
1062  }
1063 
1068  function getHijriCalendarMonthName( $key ) {
1069  return $this->getMessageFromDB( self::$mHijriCalendarMonthMsgs[$key - 1] );
1070  }
1071 
1080  private static function dateTimeObjFormat( &$dateTimeObj, $ts, $zone, $code ) {
1081  if ( !$dateTimeObj ) {
1082  $dateTimeObj = DateTime::createFromFormat(
1083  'YmdHis', $ts, $zone ?: new DateTimeZone( 'UTC' )
1084  );
1085  }
1086  return $dateTimeObj->format( $code );
1087  }
1088 
1158  public function sprintfDate( $format, $ts, DateTimeZone $zone = null, &$ttl = 'unused' ) {
1159  $s = '';
1160  $raw = false;
1161  $roman = false;
1162  $hebrewNum = false;
1163  $dateTimeObj = false;
1164  $rawToggle = false;
1165  $iranian = false;
1166  $hebrew = false;
1167  $hijri = false;
1168  $thai = false;
1169  $minguo = false;
1170  $tenno = false;
1171 
1172  $usedSecond = false;
1173  $usedMinute = false;
1174  $usedHour = false;
1175  $usedAMPM = false;
1176  $usedDay = false;
1177  $usedWeek = false;
1178  $usedMonth = false;
1179  $usedYear = false;
1180  $usedISOYear = false;
1181  $usedIsLeapYear = false;
1182 
1183  $usedHebrewMonth = false;
1184  $usedIranianMonth = false;
1185  $usedHijriMonth = false;
1186  $usedHebrewYear = false;
1187  $usedIranianYear = false;
1188  $usedHijriYear = false;
1189  $usedTennoYear = false;
1190 
1191  if ( strlen( $ts ) !== 14 ) {
1192  throw new MWException( __METHOD__ . ": The timestamp $ts should have 14 characters" );
1193  }
1194 
1195  if ( !ctype_digit( $ts ) ) {
1196  throw new MWException( __METHOD__ . ": The timestamp $ts should be a number" );
1197  }
1198 
1199  $formatLength = strlen( $format );
1200  for ( $p = 0; $p < $formatLength; $p++ ) {
1201  $num = false;
1202  $code = $format[$p];
1203  if ( $code == 'x' && $p < $formatLength - 1 ) {
1204  $code .= $format[++$p];
1205  }
1206 
1207  if ( ( $code === 'xi'
1208  || $code === 'xj'
1209  || $code === 'xk'
1210  || $code === 'xm'
1211  || $code === 'xo'
1212  || $code === 'xt' )
1213  && $p < $formatLength - 1 ) {
1214  $code .= $format[++$p];
1215  }
1216 
1217  switch ( $code ) {
1218  case 'xx':
1219  $s .= 'x';
1220  break;
1221  case 'xn':
1222  $raw = true;
1223  break;
1224  case 'xN':
1225  $rawToggle = !$rawToggle;
1226  break;
1227  case 'xr':
1228  $roman = true;
1229  break;
1230  case 'xh':
1231  $hebrewNum = true;
1232  break;
1233  case 'xg':
1234  $usedMonth = true;
1235  $s .= $this->getMonthNameGen( substr( $ts, 4, 2 ) );
1236  break;
1237  case 'xjx':
1238  $usedHebrewMonth = true;
1239  if ( !$hebrew ) {
1240  $hebrew = self::tsToHebrew( $ts );
1241  }
1242  $s .= $this->getHebrewCalendarMonthNameGen( $hebrew[1] );
1243  break;
1244  case 'd':
1245  $usedDay = true;
1246  $num = substr( $ts, 6, 2 );
1247  break;
1248  case 'D':
1249  $usedDay = true;
1250  $s .= $this->getWeekdayAbbreviation(
1251  self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'w' ) + 1
1252  );
1253  break;
1254  case 'j':
1255  $usedDay = true;
1256  $num = intval( substr( $ts, 6, 2 ) );
1257  break;
1258  case 'xij':
1259  $usedDay = true;
1260  if ( !$iranian ) {
1261  $iranian = self::tsToIranian( $ts );
1262  }
1263  $num = $iranian[2];
1264  break;
1265  case 'xmj':
1266  $usedDay = true;
1267  if ( !$hijri ) {
1268  $hijri = self::tsToHijri( $ts );
1269  }
1270  $num = $hijri[2];
1271  break;
1272  case 'xjj':
1273  $usedDay = true;
1274  if ( !$hebrew ) {
1275  $hebrew = self::tsToHebrew( $ts );
1276  }
1277  $num = $hebrew[2];
1278  break;
1279  case 'l':
1280  $usedDay = true;
1281  $s .= $this->getWeekdayName(
1282  self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'w' ) + 1
1283  );
1284  break;
1285  case 'F':
1286  $usedMonth = true;
1287  $s .= $this->getMonthName( substr( $ts, 4, 2 ) );
1288  break;
1289  case 'xiF':
1290  $usedIranianMonth = true;
1291  if ( !$iranian ) {
1292  $iranian = self::tsToIranian( $ts );
1293  }
1294  $s .= $this->getIranianCalendarMonthName( $iranian[1] );
1295  break;
1296  case 'xmF':
1297  $usedHijriMonth = true;
1298  if ( !$hijri ) {
1299  $hijri = self::tsToHijri( $ts );
1300  }
1301  $s .= $this->getHijriCalendarMonthName( $hijri[1] );
1302  break;
1303  case 'xjF':
1304  $usedHebrewMonth = true;
1305  if ( !$hebrew ) {
1306  $hebrew = self::tsToHebrew( $ts );
1307  }
1308  $s .= $this->getHebrewCalendarMonthName( $hebrew[1] );
1309  break;
1310  case 'm':
1311  $usedMonth = true;
1312  $num = substr( $ts, 4, 2 );
1313  break;
1314  case 'M':
1315  $usedMonth = true;
1316  $s .= $this->getMonthAbbreviation( substr( $ts, 4, 2 ) );
1317  break;
1318  case 'n':
1319  $usedMonth = true;
1320  $num = intval( substr( $ts, 4, 2 ) );
1321  break;
1322  case 'xin':
1323  $usedIranianMonth = true;
1324  if ( !$iranian ) {
1325  $iranian = self::tsToIranian( $ts );
1326  }
1327  $num = $iranian[1];
1328  break;
1329  case 'xmn':
1330  $usedHijriMonth = true;
1331  if ( !$hijri ) {
1332  $hijri = self::tsToHijri( $ts );
1333  }
1334  $num = $hijri[1];
1335  break;
1336  case 'xjn':
1337  $usedHebrewMonth = true;
1338  if ( !$hebrew ) {
1339  $hebrew = self::tsToHebrew( $ts );
1340  }
1341  $num = $hebrew[1];
1342  break;
1343  case 'xjt':
1344  $usedHebrewMonth = true;
1345  if ( !$hebrew ) {
1346  $hebrew = self::tsToHebrew( $ts );
1347  }
1348  $num = $hebrew[3];
1349  break;
1350  case 'Y':
1351  $usedYear = true;
1352  $num = substr( $ts, 0, 4 );
1353  break;
1354  case 'xiY':
1355  $usedIranianYear = true;
1356  if ( !$iranian ) {
1357  $iranian = self::tsToIranian( $ts );
1358  }
1359  $num = $iranian[0];
1360  break;
1361  case 'xmY':
1362  $usedHijriYear = true;
1363  if ( !$hijri ) {
1364  $hijri = self::tsToHijri( $ts );
1365  }
1366  $num = $hijri[0];
1367  break;
1368  case 'xjY':
1369  $usedHebrewYear = true;
1370  if ( !$hebrew ) {
1371  $hebrew = self::tsToHebrew( $ts );
1372  }
1373  $num = $hebrew[0];
1374  break;
1375  case 'xkY':
1376  $usedYear = true;
1377  if ( !$thai ) {
1378  $thai = self::tsToYear( $ts, 'thai' );
1379  }
1380  $num = $thai[0];
1381  break;
1382  case 'xoY':
1383  $usedYear = true;
1384  if ( !$minguo ) {
1385  $minguo = self::tsToYear( $ts, 'minguo' );
1386  }
1387  $num = $minguo[0];
1388  break;
1389  case 'xtY':
1390  $usedTennoYear = true;
1391  if ( !$tenno ) {
1392  $tenno = self::tsToYear( $ts, 'tenno' );
1393  }
1394  $num = $tenno[0];
1395  break;
1396  case 'y':
1397  $usedYear = true;
1398  $num = substr( $ts, 2, 2 );
1399  break;
1400  case 'xiy':
1401  $usedIranianYear = true;
1402  if ( !$iranian ) {
1403  $iranian = self::tsToIranian( $ts );
1404  }
1405  $num = substr( $iranian[0], -2 );
1406  break;
1407  case 'xit':
1408  $usedIranianYear = true;
1409  if ( !$iranian ) {
1410  $iranian = self::tsToIranian( $ts );
1411  }
1412  $num = self::$IRANIAN_DAYS[$iranian[1] - 1];
1413  break;
1414  case 'xiz':
1415  $usedIranianYear = true;
1416  if ( !$iranian ) {
1417  $iranian = self::tsToIranian( $ts );
1418  }
1419  $num = $iranian[3];
1420  break;
1421  case 'a':
1422  $usedAMPM = true;
1423  $s .= intval( substr( $ts, 8, 2 ) ) < 12 ? 'am' : 'pm';
1424  break;
1425  case 'A':
1426  $usedAMPM = true;
1427  $s .= intval( substr( $ts, 8, 2 ) ) < 12 ? 'AM' : 'PM';
1428  break;
1429  case 'g':
1430  $usedHour = true;
1431  $h = substr( $ts, 8, 2 );
1432  $num = $h % 12 ? $h % 12 : 12;
1433  break;
1434  case 'G':
1435  $usedHour = true;
1436  $num = intval( substr( $ts, 8, 2 ) );
1437  break;
1438  case 'h':
1439  $usedHour = true;
1440  $h = substr( $ts, 8, 2 );
1441  $num = sprintf( '%02d', $h % 12 ? $h % 12 : 12 );
1442  break;
1443  case 'H':
1444  $usedHour = true;
1445  $num = substr( $ts, 8, 2 );
1446  break;
1447  case 'i':
1448  $usedMinute = true;
1449  $num = substr( $ts, 10, 2 );
1450  break;
1451  case 's':
1452  $usedSecond = true;
1453  $num = substr( $ts, 12, 2 );
1454  break;
1455  case 'c':
1456  case 'r':
1457  $usedSecond = true;
1458  // fall through
1459  case 'e':
1460  case 'O':
1461  case 'P':
1462  case 'T':
1463  $s .= self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1464  break;
1465  case 'w':
1466  case 'N':
1467  case 'z':
1468  $usedDay = true;
1469  $num = self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1470  break;
1471  case 'W':
1472  $usedWeek = true;
1473  $num = self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1474  break;
1475  case 't':
1476  $usedMonth = true;
1477  $num = self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1478  break;
1479  case 'L':
1480  $usedIsLeapYear = true;
1481  $num = self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1482  break;
1483  case 'o':
1484  $usedISOYear = true;
1485  $num = self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1486  break;
1487  case 'U':
1488  $usedSecond = true;
1489  // fall through
1490  case 'I':
1491  case 'Z':
1492  $num = self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1493  break;
1494  case '\\':
1495  # Backslash escaping
1496  if ( $p < $formatLength - 1 ) {
1497  $s .= $format[++$p];
1498  } else {
1499  $s .= '\\';
1500  }
1501  break;
1502  case '"':
1503  # Quoted literal
1504  if ( $p < $formatLength - 1 ) {
1505  $endQuote = strpos( $format, '"', $p + 1 );
1506  if ( $endQuote === false ) {
1507  # No terminating quote, assume literal "
1508  $s .= '"';
1509  } else {
1510  $s .= substr( $format, $p + 1, $endQuote - $p - 1 );
1511  $p = $endQuote;
1512  }
1513  } else {
1514  # Quote at end of string, assume literal "
1515  $s .= '"';
1516  }
1517  break;
1518  default:
1519  $s .= $format[$p];
1520  }
1521  if ( $num !== false ) {
1522  if ( $rawToggle || $raw ) {
1523  $s .= $num;
1524  $raw = false;
1525  } elseif ( $roman ) {
1526  $s .= self::romanNumeral( $num );
1527  $roman = false;
1528  } elseif ( $hebrewNum ) {
1529  $s .= self::hebrewNumeral( $num );
1530  $hebrewNum = false;
1531  } else {
1532  $s .= $this->formatNum( $num, true );
1533  }
1534  }
1535  }
1536 
1537  if ( $ttl === 'unused' ) {
1538  // No need to calculate the TTL, the caller wont use it anyway.
1539  } elseif ( $usedSecond ) {
1540  $ttl = 1;
1541  } elseif ( $usedMinute ) {
1542  $ttl = 60 - substr( $ts, 12, 2 );
1543  } elseif ( $usedHour ) {
1544  $ttl = 3600 - substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
1545  } elseif ( $usedAMPM ) {
1546  $ttl = 43200 - ( substr( $ts, 8, 2 ) % 12 ) * 3600 -
1547  substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
1548  } elseif (
1549  $usedDay ||
1550  $usedHebrewMonth ||
1551  $usedIranianMonth ||
1552  $usedHijriMonth ||
1553  $usedHebrewYear ||
1554  $usedIranianYear ||
1555  $usedHijriYear ||
1556  $usedTennoYear
1557  ) {
1558  // @todo Someone who understands the non-Gregorian calendars
1559  // should write proper logic for them so that they don't need purged every day.
1560  $ttl = 86400 - substr( $ts, 8, 2 ) * 3600 -
1561  substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
1562  } else {
1563  $possibleTtls = [];
1564  $timeRemainingInDay = 86400 - substr( $ts, 8, 2 ) * 3600 -
1565  substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
1566  if ( $usedWeek ) {
1567  $possibleTtls[] =
1568  ( 7 - self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'N' ) ) * 86400 +
1569  $timeRemainingInDay;
1570  } elseif ( $usedISOYear ) {
1571  // December 28th falls on the last ISO week of the year, every year.
1572  // The last ISO week of a year can be 52 or 53.
1573  $lastWeekOfISOYear = DateTime::createFromFormat(
1574  'Ymd',
1575  substr( $ts, 0, 4 ) . '1228',
1576  $zone ?: new DateTimeZone( 'UTC' )
1577  )->format( 'W' );
1578  $currentISOWeek = self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'W' );
1579  $weeksRemaining = $lastWeekOfISOYear - $currentISOWeek;
1580  $timeRemainingInWeek =
1581  ( 7 - self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'N' ) ) * 86400
1582  + $timeRemainingInDay;
1583  $possibleTtls[] = $weeksRemaining * 604800 + $timeRemainingInWeek;
1584  }
1585 
1586  if ( $usedMonth ) {
1587  $possibleTtls[] =
1588  ( self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 't' ) -
1589  substr( $ts, 6, 2 ) ) * 86400
1590  + $timeRemainingInDay;
1591  } elseif ( $usedYear ) {
1592  $possibleTtls[] =
1593  ( self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'L' ) + 364 -
1594  self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'z' ) ) * 86400
1595  + $timeRemainingInDay;
1596  } elseif ( $usedIsLeapYear ) {
1597  $year = substr( $ts, 0, 4 );
1598  $timeRemainingInYear =
1599  ( self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'L' ) + 364 -
1600  self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'z' ) ) * 86400
1601  + $timeRemainingInDay;
1602  $mod = $year % 4;
1603  if ( $mod || ( !( $year % 100 ) && $year % 400 ) ) {
1604  // this isn't a leap year. see when the next one starts
1605  $nextCandidate = $year - $mod + 4;
1606  if ( $nextCandidate % 100 || !( $nextCandidate % 400 ) ) {
1607  $possibleTtls[] = ( $nextCandidate - $year - 1 ) * 365 * 86400 +
1608  $timeRemainingInYear;
1609  } else {
1610  $possibleTtls[] = ( $nextCandidate - $year + 3 ) * 365 * 86400 +
1611  $timeRemainingInYear;
1612  }
1613  } else {
1614  // this is a leap year, so the next year isn't
1615  $possibleTtls[] = $timeRemainingInYear;
1616  }
1617  }
1618 
1619  if ( $possibleTtls ) {
1620  $ttl = min( $possibleTtls );
1621  }
1622  }
1623 
1624  return $s;
1625  }
1626 
1627  private static $GREG_DAYS = [ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ];
1628  private static $IRANIAN_DAYS = [ 31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29 ];
1629 
1642  private static function tsToIranian( $ts ) {
1643  $gy = substr( $ts, 0, 4 ) - 1600;
1644  $gm = substr( $ts, 4, 2 ) - 1;
1645  $gd = substr( $ts, 6, 2 ) - 1;
1646 
1647  # Days passed from the beginning (including leap years)
1648  $gDayNo = 365 * $gy
1649  + floor( ( $gy + 3 ) / 4 )
1650  - floor( ( $gy + 99 ) / 100 )
1651  + floor( ( $gy + 399 ) / 400 );
1652 
1653  // Add days of the past months of this year
1654  for ( $i = 0; $i < $gm; $i++ ) {
1655  $gDayNo += self::$GREG_DAYS[$i];
1656  }
1657 
1658  // Leap years
1659  if ( $gm > 1 && ( ( $gy % 4 === 0 && $gy % 100 !== 0 || ( $gy % 400 == 0 ) ) ) ) {
1660  $gDayNo++;
1661  }
1662 
1663  // Days passed in current month
1664  $gDayNo += (int)$gd;
1665 
1666  $jDayNo = $gDayNo - 79;
1667 
1668  $jNp = floor( $jDayNo / 12053 );
1669  $jDayNo %= 12053;
1670 
1671  $jy = 979 + 33 * $jNp + 4 * floor( $jDayNo / 1461 );
1672  $jDayNo %= 1461;
1673 
1674  if ( $jDayNo >= 366 ) {
1675  $jy += floor( ( $jDayNo - 1 ) / 365 );
1676  $jDayNo = floor( ( $jDayNo - 1 ) % 365 );
1677  }
1678 
1679  $jz = $jDayNo;
1680 
1681  for ( $i = 0; $i < 11 && $jDayNo >= self::$IRANIAN_DAYS[$i]; $i++ ) {
1682  $jDayNo -= self::$IRANIAN_DAYS[$i];
1683  }
1684 
1685  $jm = $i + 1;
1686  $jd = $jDayNo + 1;
1687 
1688  return [ $jy, $jm, $jd, $jz ];
1689  }
1690 
1702  private static function tsToHijri( $ts ) {
1703  $year = substr( $ts, 0, 4 );
1704  $month = substr( $ts, 4, 2 );
1705  $day = substr( $ts, 6, 2 );
1706 
1707  $zyr = $year;
1708  $zd = $day;
1709  $zm = $month;
1710  $zy = $zyr;
1711 
1712  if (
1713  ( $zy > 1582 ) || ( ( $zy == 1582 ) && ( $zm > 10 ) ) ||
1714  ( ( $zy == 1582 ) && ( $zm == 10 ) && ( $zd > 14 ) )
1715  ) {
1716  $zjd = (int)( ( 1461 * ( $zy + 4800 + (int)( ( $zm - 14 ) / 12 ) ) ) / 4 ) +
1717  (int)( ( 367 * ( $zm - 2 - 12 * ( (int)( ( $zm - 14 ) / 12 ) ) ) ) / 12 ) -
1718  (int)( ( 3 * (int)( ( ( $zy + 4900 + (int)( ( $zm - 14 ) / 12 ) ) / 100 ) ) ) / 4 ) +
1719  $zd - 32075;
1720  } else {
1721  $zjd = 367 * $zy - (int)( ( 7 * ( $zy + 5001 + (int)( ( $zm - 9 ) / 7 ) ) ) / 4 ) +
1722  (int)( ( 275 * $zm ) / 9 ) + $zd + 1729777;
1723  }
1724 
1725  $zl = $zjd - 1948440 + 10632;
1726  $zn = (int)( ( $zl - 1 ) / 10631 );
1727  $zl = $zl - 10631 * $zn + 354;
1728  $zj = ( (int)( ( 10985 - $zl ) / 5316 ) ) * ( (int)( ( 50 * $zl ) / 17719 ) ) +
1729  ( (int)( $zl / 5670 ) ) * ( (int)( ( 43 * $zl ) / 15238 ) );
1730  $zl = $zl - ( (int)( ( 30 - $zj ) / 15 ) ) * ( (int)( ( 17719 * $zj ) / 50 ) ) -
1731  ( (int)( $zj / 16 ) ) * ( (int)( ( 15238 * $zj ) / 43 ) ) + 29;
1732  $zm = (int)( ( 24 * $zl ) / 709 );
1733  $zd = $zl - (int)( ( 709 * $zm ) / 24 );
1734  $zy = 30 * $zn + $zj - 30;
1735 
1736  return [ $zy, $zm, $zd ];
1737  }
1738 
1754  private static function tsToHebrew( $ts ) {
1755  # Parse date
1756  $year = substr( $ts, 0, 4 );
1757  $month = substr( $ts, 4, 2 );
1758  $day = substr( $ts, 6, 2 );
1759 
1760  # Calculate Hebrew year
1761  $hebrewYear = $year + 3760;
1762 
1763  # Month number when September = 1, August = 12
1764  $month += 4;
1765  if ( $month > 12 ) {
1766  # Next year
1767  $month -= 12;
1768  $year++;
1769  $hebrewYear++;
1770  }
1771 
1772  # Calculate day of year from 1 September
1773  $dayOfYear = $day;
1774  for ( $i = 1; $i < $month; $i++ ) {
1775  if ( $i == 6 ) {
1776  # February
1777  $dayOfYear += 28;
1778  # Check if the year is leap
1779  if ( $year % 400 == 0 || ( $year % 4 == 0 && $year % 100 > 0 ) ) {
1780  $dayOfYear++;
1781  }
1782  } elseif ( $i == 8 || $i == 10 || $i == 1 || $i == 3 ) {
1783  $dayOfYear += 30;
1784  } else {
1785  $dayOfYear += 31;
1786  }
1787  }
1788 
1789  # Calculate the start of the Hebrew year
1790  $start = self::hebrewYearStart( $hebrewYear );
1791 
1792  # Calculate next year's start
1793  if ( $dayOfYear <= $start ) {
1794  # Day is before the start of the year - it is the previous year
1795  # Next year's start
1796  $nextStart = $start;
1797  # Previous year
1798  $year--;
1799  $hebrewYear--;
1800  # Add days since previous year's 1 September
1801  $dayOfYear += 365;
1802  if ( ( $year % 400 == 0 ) || ( $year % 100 != 0 && $year % 4 == 0 ) ) {
1803  # Leap year
1804  $dayOfYear++;
1805  }
1806  # Start of the new (previous) year
1807  $start = self::hebrewYearStart( $hebrewYear );
1808  } else {
1809  # Next year's start
1810  $nextStart = self::hebrewYearStart( $hebrewYear + 1 );
1811  }
1812 
1813  # Calculate Hebrew day of year
1814  $hebrewDayOfYear = $dayOfYear - $start;
1815 
1816  # Difference between year's days
1817  $diff = $nextStart - $start;
1818  # Add 12 (or 13 for leap years) days to ignore the difference between
1819  # Hebrew and Gregorian year (353 at least vs. 365/6) - now the
1820  # difference is only about the year type
1821  if ( ( $year % 400 == 0 ) || ( $year % 100 != 0 && $year % 4 == 0 ) ) {
1822  $diff += 13;
1823  } else {
1824  $diff += 12;
1825  }
1826 
1827  # Check the year pattern, and is leap year
1828  # 0 means an incomplete year, 1 means a regular year, 2 means a complete year
1829  # This is mod 30, to work on both leap years (which add 30 days of Adar I)
1830  # and non-leap years
1831  $yearPattern = $diff % 30;
1832  # Check if leap year
1833  $isLeap = $diff >= 30;
1834 
1835  # Calculate day in the month from number of day in the Hebrew year
1836  # Don't check Adar - if the day is not in Adar, we will stop before;
1837  # if it is in Adar, we will use it to check if it is Adar I or Adar II
1838  $hebrewDay = $hebrewDayOfYear;
1839  $hebrewMonth = 1;
1840  $days = 0;
1841  while ( $hebrewMonth <= 12 ) {
1842  # Calculate days in this month
1843  if ( $isLeap && $hebrewMonth == 6 ) {
1844  # Adar in a leap year
1845  if ( $isLeap ) {
1846  # Leap year - has Adar I, with 30 days, and Adar II, with 29 days
1847  $days = 30;
1848  if ( $hebrewDay <= $days ) {
1849  # Day in Adar I
1850  $hebrewMonth = 13;
1851  } else {
1852  # Subtract the days of Adar I
1853  $hebrewDay -= $days;
1854  # Try Adar II
1855  $days = 29;
1856  if ( $hebrewDay <= $days ) {
1857  # Day in Adar II
1858  $hebrewMonth = 14;
1859  }
1860  }
1861  }
1862  } elseif ( $hebrewMonth == 2 && $yearPattern == 2 ) {
1863  # Cheshvan in a complete year (otherwise as the rule below)
1864  $days = 30;
1865  } elseif ( $hebrewMonth == 3 && $yearPattern == 0 ) {
1866  # Kislev in an incomplete year (otherwise as the rule below)
1867  $days = 29;
1868  } else {
1869  # Odd months have 30 days, even have 29
1870  $days = 30 - ( $hebrewMonth - 1 ) % 2;
1871  }
1872  if ( $hebrewDay <= $days ) {
1873  # In the current month
1874  break;
1875  } else {
1876  # Subtract the days of the current month
1877  $hebrewDay -= $days;
1878  # Try in the next month
1879  $hebrewMonth++;
1880  }
1881  }
1882 
1883  return [ $hebrewYear, $hebrewMonth, $hebrewDay, $days ];
1884  }
1885 
1895  private static function hebrewYearStart( $year ) {
1896  $a = intval( ( 12 * ( $year - 1 ) + 17 ) % 19 );
1897  $b = intval( ( $year - 1 ) % 4 );
1898  $m = 32.044093161144 + 1.5542417966212 * $a + $b / 4.0 - 0.0031777940220923 * ( $year - 1 );
1899  if ( $m < 0 ) {
1900  $m--;
1901  }
1902  $Mar = intval( $m );
1903  if ( $m < 0 ) {
1904  $m++;
1905  }
1906  $m -= $Mar;
1907 
1908  $c = intval( ( $Mar + 3 * ( $year - 1 ) + 5 * $b + 5 ) % 7 );
1909  if ( $c == 0 && $a > 11 && $m >= 0.89772376543210 ) {
1910  $Mar++;
1911  } elseif ( $c == 1 && $a > 6 && $m >= 0.63287037037037 ) {
1912  $Mar += 2;
1913  } elseif ( $c == 2 || $c == 4 || $c == 6 ) {
1914  $Mar++;
1915  }
1916 
1917  $Mar += intval( ( $year - 3761 ) / 100 ) - intval( ( $year - 3761 ) / 400 ) - 24;
1918  return $Mar;
1919  }
1920 
1933  private static function tsToYear( $ts, $cName ) {
1934  $gy = substr( $ts, 0, 4 );
1935  $gm = substr( $ts, 4, 2 );
1936  $gd = substr( $ts, 6, 2 );
1937 
1938  if ( !strcmp( $cName, 'thai' ) ) {
1939  # Thai solar dates
1940  # Add 543 years to the Gregorian calendar
1941  # Months and days are identical
1942  $gy_offset = $gy + 543;
1943  # fix for dates between 1912 and 1941
1944  # https://en.wikipedia.org/?oldid=836596673#New_year
1945  if ( $gy >= 1912 && $gy <= 1940 ) {
1946  if ( $gm <= 3 ) {
1947  $gy_offset--;
1948  }
1949  $gm = ( $gm - 3 ) % 12;
1950  }
1951  } elseif ( ( !strcmp( $cName, 'minguo' ) ) || !strcmp( $cName, 'juche' ) ) {
1952  # Minguo dates
1953  # Deduct 1911 years from the Gregorian calendar
1954  # Months and days are identical
1955  $gy_offset = $gy - 1911;
1956  } elseif ( !strcmp( $cName, 'tenno' ) ) {
1957  # Nengō dates up to Meiji period
1958  # Deduct years from the Gregorian calendar
1959  # depending on the nengo periods
1960  # Months and days are identical
1961  if ( ( $gy < 1912 )
1962  || ( ( $gy == 1912 ) && ( $gm < 7 ) )
1963  || ( ( $gy == 1912 ) && ( $gm == 7 ) && ( $gd < 31 ) )
1964  ) {
1965  # Meiji period
1966  $gy_gannen = $gy - 1868 + 1;
1967  $gy_offset = $gy_gannen;
1968  if ( $gy_gannen == 1 ) {
1969  $gy_offset = '元';
1970  }
1971  $gy_offset = '明治' . $gy_offset;
1972  } elseif (
1973  ( ( $gy == 1912 ) && ( $gm == 7 ) && ( $gd == 31 ) ) ||
1974  ( ( $gy == 1912 ) && ( $gm >= 8 ) ) ||
1975  ( ( $gy > 1912 ) && ( $gy < 1926 ) ) ||
1976  ( ( $gy == 1926 ) && ( $gm < 12 ) ) ||
1977  ( ( $gy == 1926 ) && ( $gm == 12 ) && ( $gd < 26 ) )
1978  ) {
1979  # Taishō period
1980  $gy_gannen = $gy - 1912 + 1;
1981  $gy_offset = $gy_gannen;
1982  if ( $gy_gannen == 1 ) {
1983  $gy_offset = '元';
1984  }
1985  $gy_offset = '大正' . $gy_offset;
1986  } elseif (
1987  ( ( $gy == 1926 ) && ( $gm == 12 ) && ( $gd >= 26 ) ) ||
1988  ( ( $gy > 1926 ) && ( $gy < 1989 ) ) ||
1989  ( ( $gy == 1989 ) && ( $gm == 1 ) && ( $gd < 8 ) )
1990  ) {
1991  # Shōwa period
1992  $gy_gannen = $gy - 1926 + 1;
1993  $gy_offset = $gy_gannen;
1994  if ( $gy_gannen == 1 ) {
1995  $gy_offset = '元';
1996  }
1997  $gy_offset = '昭和' . $gy_offset;
1998  } else {
1999  # Heisei period
2000  $gy_gannen = $gy - 1989 + 1;
2001  $gy_offset = $gy_gannen;
2002  if ( $gy_gannen == 1 ) {
2003  $gy_offset = '元';
2004  }
2005  $gy_offset = '平成' . $gy_offset;
2006  }
2007  } else {
2008  $gy_offset = $gy;
2009  }
2010 
2011  return [ $gy_offset, $gm, $gd ];
2012  }
2013 
2027  private static function strongDirFromContent( $text = '' ) {
2028  if ( !preg_match( self::$strongDirRegex, $text, $matches ) ) {
2029  return null;
2030  }
2031  if ( $matches[1] === '' ) {
2032  return 'rtl';
2033  }
2034  return 'ltr';
2035  }
2036 
2044  static function romanNumeral( $num ) {
2045  static $table = [
2046  [ '', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X' ],
2047  [ '', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC', 'C' ],
2048  [ '', 'C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM', 'M' ],
2049  [ '', 'M', 'MM', 'MMM', 'MMMM', 'MMMMM', 'MMMMMM', 'MMMMMMM',
2050  'MMMMMMMM', 'MMMMMMMMM', 'MMMMMMMMMM' ]
2051  ];
2052 
2053  $num = intval( $num );
2054  if ( $num > 10000 || $num <= 0 ) {
2055  return $num;
2056  }
2057 
2058  $s = '';
2059  for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) {
2060  if ( $num >= $pow10 ) {
2061  $s .= $table[$i][(int)floor( $num / $pow10 )];
2062  }
2063  $num = $num % $pow10;
2064  }
2065  return $s;
2066  }
2067 
2075  static function hebrewNumeral( $num ) {
2076  static $table = [
2077  [ '', 'א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ז', 'ח', 'ט', 'י' ],
2078  [ '', 'י', 'כ', 'ל', 'מ', 'נ', 'ס', 'ע', 'פ', 'צ', 'ק' ],
2079  [ '',
2080  [ 'ק' ],
2081  [ 'ר' ],
2082  [ 'ש' ],
2083  [ 'ת' ],
2084  [ 'ת', 'ק' ],
2085  [ 'ת', 'ר' ],
2086  [ 'ת', 'ש' ],
2087  [ 'ת', 'ת' ],
2088  [ 'ת', 'ת', 'ק' ],
2089  [ 'ת', 'ת', 'ר' ],
2090  ],
2091  [ '', 'א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ז', 'ח', 'ט', 'י' ]
2092  ];
2093 
2094  $num = intval( $num );
2095  if ( $num > 9999 || $num <= 0 ) {
2096  return $num;
2097  }
2098 
2099  // Round thousands have special notations
2100  if ( $num === 1000 ) {
2101  return "א' אלף";
2102  } elseif ( $num % 1000 === 0 ) {
2103  return $table[0][$num / 1000] . "' אלפים";
2104  }
2105 
2106  $letters = [];
2107 
2108  for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) {
2109  if ( $num >= $pow10 ) {
2110  if ( $num === 15 || $num === 16 ) {
2111  $letters[] = $table[0][9];
2112  $letters[] = $table[0][$num - 9];
2113  $num = 0;
2114  } else {
2115  $letters = array_merge(
2116  $letters,
2117  (array)$table[$i][intval( $num / $pow10 )]
2118  );
2119 
2120  if ( $pow10 === 1000 ) {
2121  $letters[] = "'";
2122  }
2123  }
2124  }
2125 
2126  $num = $num % $pow10;
2127  }
2128 
2129  $preTransformLength = count( $letters );
2130  if ( $preTransformLength === 1 ) {
2131  // Add geresh (single quote) to one-letter numbers
2132  $letters[] = "'";
2133  } else {
2134  $lastIndex = $preTransformLength - 1;
2135  $letters[$lastIndex] = str_replace(
2136  [ 'כ', 'מ', 'נ', 'פ', 'צ' ],
2137  [ 'ך', 'ם', 'ן', 'ף', 'ץ' ],
2138  $letters[$lastIndex]
2139  );
2140 
2141  // Add gershayim (double quote) to multiple-letter numbers,
2142  // but exclude numbers with only one letter after the thousands
2143  // (1001-1009, 1020, 1030, 2001-2009, etc.)
2144  if ( $letters[1] === "'" && $preTransformLength === 3 ) {
2145  $letters[] = "'";
2146  } else {
2147  array_splice( $letters, -1, 0, '"' );
2148  }
2149  }
2150 
2151  return implode( $letters );
2152  }
2153 
2162  public function userAdjust( $ts, $tz = false ) {
2163  global $wgUser, $wgLocalTZoffset;
2164 
2165  if ( $tz === false ) {
2166  $tz = $wgUser->getOption( 'timecorrection' );
2167  }
2168 
2169  $data = explode( '|', $tz, 3 );
2170 
2171  if ( $data[0] == 'ZoneInfo' ) {
2172  try {
2173  $userTZ = new DateTimeZone( $data[2] );
2174  $date = new DateTime( $ts, new DateTimeZone( 'UTC' ) );
2175  $date->setTimezone( $userTZ );
2176  return $date->format( 'YmdHis' );
2177  } catch ( Exception $e ) {
2178  // Unrecognized timezone, default to 'Offset' with the stored offset.
2179  $data[0] = 'Offset';
2180  }
2181  }
2182 
2183  if ( $data[0] == 'System' || $tz == '' ) {
2184  # Global offset in minutes.
2185  $minDiff = $wgLocalTZoffset;
2186  } elseif ( $data[0] == 'Offset' ) {
2187  $minDiff = intval( $data[1] );
2188  } else {
2189  $data = explode( ':', $tz );
2190  if ( count( $data ) == 2 ) {
2191  $data[0] = intval( $data[0] );
2192  $data[1] = intval( $data[1] );
2193  $minDiff = abs( $data[0] ) * 60 + $data[1];
2194  if ( $data[0] < 0 ) {
2195  $minDiff = -$minDiff;
2196  }
2197  } else {
2198  $minDiff = intval( $data[0] ) * 60;
2199  }
2200  }
2201 
2202  # No difference ? Return time unchanged
2203  if ( 0 == $minDiff ) {
2204  return $ts;
2205  }
2206 
2207  Wikimedia\suppressWarnings(); // E_STRICT system time bitching
2208  # Generate an adjusted date; take advantage of the fact that mktime
2209  # will normalize out-of-range values so we don't have to split $minDiff
2210  # into hours and minutes.
2211  $t = mktime( (
2212  (int)substr( $ts, 8, 2 ) ), # Hours
2213  (int)substr( $ts, 10, 2 ) + $minDiff, # Minutes
2214  (int)substr( $ts, 12, 2 ), # Seconds
2215  (int)substr( $ts, 4, 2 ), # Month
2216  (int)substr( $ts, 6, 2 ), # Day
2217  (int)substr( $ts, 0, 4 ) ); # Year
2218 
2219  $date = date( 'YmdHis', $t );
2220  Wikimedia\restoreWarnings();
2221 
2222  return $date;
2223  }
2224 
2240  function dateFormat( $usePrefs = true ) {
2241  global $wgUser;
2242 
2243  if ( is_bool( $usePrefs ) ) {
2244  if ( $usePrefs ) {
2245  $datePreference = $wgUser->getDatePreference();
2246  } else {
2247  $datePreference = (string)User::getDefaultOption( 'date' );
2248  }
2249  } else {
2250  $datePreference = (string)$usePrefs;
2251  }
2252 
2253  // return int
2254  if ( $datePreference == '' ) {
2255  return 'default';
2256  }
2257 
2258  return $datePreference;
2259  }
2260 
2271  function getDateFormatString( $type, $pref ) {
2272  $wasDefault = false;
2273  if ( $pref == 'default' ) {
2274  $wasDefault = true;
2275  $pref = $this->getDefaultDateFormat();
2276  }
2277 
2278  if ( !isset( $this->dateFormatStrings[$type][$pref] ) ) {
2279  $df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" );
2280 
2281  if ( $type === 'pretty' && $df === null ) {
2282  $df = $this->getDateFormatString( 'date', $pref );
2283  }
2284 
2285  if ( !$wasDefault && $df === null ) {
2286  $pref = $this->getDefaultDateFormat();
2287  $df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" );
2288  }
2289 
2290  $this->dateFormatStrings[$type][$pref] = $df;
2291  }
2292  return $this->dateFormatStrings[$type][$pref];
2293  }
2294 
2305  public function date( $ts, $adj = false, $format = true, $timecorrection = false ) {
2306  $ts = wfTimestamp( TS_MW, $ts );
2307  if ( $adj ) {
2308  $ts = $this->userAdjust( $ts, $timecorrection );
2309  }
2310  $df = $this->getDateFormatString( 'date', $this->dateFormat( $format ) );
2311  return $this->sprintfDate( $df, $ts );
2312  }
2313 
2324  public function time( $ts, $adj = false, $format = true, $timecorrection = false ) {
2325  $ts = wfTimestamp( TS_MW, $ts );
2326  if ( $adj ) {
2327  $ts = $this->userAdjust( $ts, $timecorrection );
2328  }
2329  $df = $this->getDateFormatString( 'time', $this->dateFormat( $format ) );
2330  return $this->sprintfDate( $df, $ts );
2331  }
2332 
2344  public function timeanddate( $ts, $adj = false, $format = true, $timecorrection = false ) {
2345  $ts = wfTimestamp( TS_MW, $ts );
2346  if ( $adj ) {
2347  $ts = $this->userAdjust( $ts, $timecorrection );
2348  }
2349  $df = $this->getDateFormatString( 'both', $this->dateFormat( $format ) );
2350  return $this->sprintfDate( $df, $ts );
2351  }
2352 
2363  public function formatDuration( $seconds, array $chosenIntervals = [] ) {
2364  $intervals = $this->getDurationIntervals( $seconds, $chosenIntervals );
2365 
2366  $segments = [];
2367 
2368  foreach ( $intervals as $intervalName => $intervalValue ) {
2369  // Messages: duration-seconds, duration-minutes, duration-hours, duration-days, duration-weeks,
2370  // duration-years, duration-decades, duration-centuries, duration-millennia
2371  $message = wfMessage( 'duration-' . $intervalName )->numParams( $intervalValue );
2372  $segments[] = $message->inLanguage( $this )->escaped();
2373  }
2374 
2375  return $this->listToText( $segments );
2376  }
2377 
2389  public function getDurationIntervals( $seconds, array $chosenIntervals = [] ) {
2390  if ( empty( $chosenIntervals ) ) {
2391  $chosenIntervals = [
2392  'millennia',
2393  'centuries',
2394  'decades',
2395  'years',
2396  'days',
2397  'hours',
2398  'minutes',
2399  'seconds'
2400  ];
2401  }
2402 
2403  $intervals = array_intersect_key( self::$durationIntervals, array_flip( $chosenIntervals ) );
2404  $sortedNames = array_keys( $intervals );
2405  $smallestInterval = array_pop( $sortedNames );
2406 
2407  $segments = [];
2408 
2409  foreach ( $intervals as $name => $length ) {
2410  $value = floor( $seconds / $length );
2411 
2412  if ( $value > 0 || ( $name == $smallestInterval && empty( $segments ) ) ) {
2413  $seconds -= $value * $length;
2414  $segments[$name] = $value;
2415  }
2416  }
2417 
2418  return $segments;
2419  }
2420 
2440  private function internalUserTimeAndDate( $type, $ts, User $user, array $options ) {
2441  $ts = wfTimestamp( TS_MW, $ts );
2442  $options += [ 'timecorrection' => true, 'format' => true ];
2443  if ( $options['timecorrection'] !== false ) {
2444  if ( $options['timecorrection'] === true ) {
2445  $offset = $user->getOption( 'timecorrection' );
2446  } else {
2447  $offset = $options['timecorrection'];
2448  }
2449  $ts = $this->userAdjust( $ts, $offset );
2450  }
2451  if ( $options['format'] === true ) {
2452  $format = $user->getDatePreference();
2453  } else {
2454  $format = $options['format'];
2455  }
2456  $df = $this->getDateFormatString( $type, $this->dateFormat( $format ) );
2457  return $this->sprintfDate( $df, $ts );
2458  }
2459 
2479  public function userDate( $ts, User $user, array $options = [] ) {
2480  return $this->internalUserTimeAndDate( 'date', $ts, $user, $options );
2481  }
2482 
2502  public function userTime( $ts, User $user, array $options = [] ) {
2503  return $this->internalUserTimeAndDate( 'time', $ts, $user, $options );
2504  }
2505 
2525  public function userTimeAndDate( $ts, User $user, array $options = [] ) {
2526  return $this->internalUserTimeAndDate( 'both', $ts, $user, $options );
2527  }
2528 
2544  public function getHumanTimestamp(
2545  MWTimestamp $time, MWTimestamp $relativeTo = null, User $user = null
2546  ) {
2547  if ( $relativeTo === null ) {
2548  $relativeTo = new MWTimestamp();
2549  }
2550  if ( $user === null ) {
2551  $user = RequestContext::getMain()->getUser();
2552  }
2553 
2554  // Adjust for the user's timezone.
2555  $offsetThis = $time->offsetForUser( $user );
2556  $offsetRel = $relativeTo->offsetForUser( $user );
2557 
2558  $ts = '';
2559  if ( Hooks::run( 'GetHumanTimestamp', [ &$ts, $time, $relativeTo, $user, $this ] ) ) {
2560  $ts = $this->getHumanTimestampInternal( $time, $relativeTo, $user );
2561  }
2562 
2563  // Reset the timezone on the objects.
2564  $time->timestamp->sub( $offsetThis );
2565  $relativeTo->timestamp->sub( $offsetRel );
2566 
2567  return $ts;
2568  }
2569 
2581  private function getHumanTimestampInternal(
2582  MWTimestamp $ts, MWTimestamp $relativeTo, User $user
2583  ) {
2584  $diff = $ts->diff( $relativeTo );
2585  $diffDay = (bool)( (int)$ts->timestamp->format( 'w' ) -
2586  (int)$relativeTo->timestamp->format( 'w' ) );
2587  $days = $diff->days ?: (int)$diffDay;
2588  if ( $diff->invert || $days > 5
2589  && $ts->timestamp->format( 'Y' ) !== $relativeTo->timestamp->format( 'Y' )
2590  ) {
2591  // Timestamps are in different years: use full timestamp
2592  // Also do full timestamp for future dates
2596  $format = $this->getDateFormatString( 'both', $user->getDatePreference() ?: 'default' );
2597  $ts = $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) );
2598  } elseif ( $days > 5 ) {
2599  // Timestamps are in same year, but more than 5 days ago: show day and month only.
2600  $format = $this->getDateFormatString( 'pretty', $user->getDatePreference() ?: 'default' );
2601  $ts = $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) );
2602  } elseif ( $days > 1 ) {
2603  // Timestamp within the past week: show the day of the week and time
2604  $format = $this->getDateFormatString( 'time', $user->getDatePreference() ?: 'default' );
2605  $weekday = self::$mWeekdayMsgs[$ts->timestamp->format( 'w' )];
2606  // Messages:
2607  // sunday-at, monday-at, tuesday-at, wednesday-at, thursday-at, friday-at, saturday-at
2608  $ts = wfMessage( "$weekday-at" )
2609  ->inLanguage( $this )
2610  ->params( $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) ) )
2611  ->text();
2612  } elseif ( $days == 1 ) {
2613  // Timestamp was yesterday: say 'yesterday' and the time.
2614  $format = $this->getDateFormatString( 'time', $user->getDatePreference() ?: 'default' );
2615  $ts = wfMessage( 'yesterday-at' )
2616  ->inLanguage( $this )
2617  ->params( $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) ) )
2618  ->text();
2619  } elseif ( $diff->h > 1 || $diff->h == 1 && $diff->i > 30 ) {
2620  // Timestamp was today, but more than 90 minutes ago: say 'today' and the time.
2621  $format = $this->getDateFormatString( 'time', $user->getDatePreference() ?: 'default' );
2622  $ts = wfMessage( 'today-at' )
2623  ->inLanguage( $this )
2624  ->params( $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) ) )
2625  ->text();
2626 
2627  // From here on in, the timestamp was soon enough ago so that we can simply say
2628  // XX units ago, e.g., "2 hours ago" or "5 minutes ago"
2629  } elseif ( $diff->h == 1 ) {
2630  // Less than 90 minutes, but more than an hour ago.
2631  $ts = wfMessage( 'hours-ago' )->inLanguage( $this )->numParams( 1 )->text();
2632  } elseif ( $diff->i >= 1 ) {
2633  // A few minutes ago.
2634  $ts = wfMessage( 'minutes-ago' )->inLanguage( $this )->numParams( $diff->i )->text();
2635  } elseif ( $diff->s >= 30 ) {
2636  // Less than a minute, but more than 30 sec ago.
2637  $ts = wfMessage( 'seconds-ago' )->inLanguage( $this )->numParams( $diff->s )->text();
2638  } else {
2639  // Less than 30 seconds ago.
2640  $ts = wfMessage( 'just-now' )->text();
2641  }
2642 
2643  return $ts;
2644  }
2645 
2650  public function getMessage( $key ) {
2651  return self::$dataCache->getSubitem( $this->mCode, 'messages', $key );
2652  }
2653 
2657  function getAllMessages() {
2658  return self::$dataCache->getItem( $this->mCode, 'messages' );
2659  }
2660 
2667  public function iconv( $in, $out, $string ) {
2668  # Even with //IGNORE iconv can whine about illegal characters in
2669  # *input* string. We just ignore those too.
2670  # REF: https://bugs.php.net/bug.php?id=37166
2671  # REF: https://phabricator.wikimedia.org/T18885
2672  Wikimedia\suppressWarnings();
2673  $text = iconv( $in, $out . '//IGNORE', $string );
2674  Wikimedia\restoreWarnings();
2675  return $text;
2676  }
2677 
2678  // callback functions for ucwords(), ucwordbreaks()
2679 
2685  return $this->ucfirst( $matches[1] );
2686  }
2687 
2693  return mb_strtoupper( $matches[0] );
2694  }
2695 
2701  return mb_strtoupper( $matches[0] );
2702  }
2703 
2711  public function ucfirst( $str ) {
2712  $o = ord( $str );
2713  if ( $o < 96 ) { // if already uppercase...
2714  return $str;
2715  } elseif ( $o < 128 ) {
2716  return ucfirst( $str ); // use PHP's ucfirst()
2717  } else {
2718  // fall back to more complex logic in case of multibyte strings
2719  return $this->uc( $str, true );
2720  }
2721  }
2722 
2731  public function uc( $str, $first = false ) {
2732  if ( $first ) {
2733  if ( $this->isMultibyte( $str ) ) {
2734  return mb_strtoupper( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
2735  } else {
2736  return ucfirst( $str );
2737  }
2738  } else {
2739  return $this->isMultibyte( $str ) ? mb_strtoupper( $str ) : strtoupper( $str );
2740  }
2741  }
2742 
2747  function lcfirst( $str ) {
2748  $o = ord( $str );
2749  if ( !$o ) {
2750  return strval( $str );
2751  } elseif ( $o >= 128 ) {
2752  return $this->lc( $str, true );
2753  } elseif ( $o > 96 ) {
2754  return $str;
2755  } else {
2756  $str[0] = strtolower( $str[0] );
2757  return $str;
2758  }
2759  }
2760 
2766  function lc( $str, $first = false ) {
2767  if ( $first ) {
2768  if ( $this->isMultibyte( $str ) ) {
2769  return mb_strtolower( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
2770  } else {
2771  return strtolower( substr( $str, 0, 1 ) ) . substr( $str, 1 );
2772  }
2773  } else {
2774  return $this->isMultibyte( $str ) ? mb_strtolower( $str ) : strtolower( $str );
2775  }
2776  }
2777 
2782  function isMultibyte( $str ) {
2783  return strlen( $str ) !== mb_strlen( $str );
2784  }
2785 
2790  function ucwords( $str ) {
2791  if ( $this->isMultibyte( $str ) ) {
2792  $str = $this->lc( $str );
2793 
2794  // regexp to find first letter in each word (i.e. after each space)
2795  $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)| ([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
2796 
2797  // function to use to capitalize a single char
2798  return preg_replace_callback(
2799  $replaceRegexp,
2800  [ $this, 'ucwordsCallbackMB' ],
2801  $str
2802  );
2803  } else {
2804  return ucwords( strtolower( $str ) );
2805  }
2806  }
2807 
2814  function ucwordbreaks( $str ) {
2815  if ( $this->isMultibyte( $str ) ) {
2816  $str = $this->lc( $str );
2817 
2818  // since \b doesn't work for UTF-8, we explicitely define word break chars
2819  $breaks = "[ \-\(\)\}\{\.,\?!]";
2820 
2821  // find first letter after word break
2822  $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)|" .
2823  "$breaks([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
2824 
2825  return preg_replace_callback(
2826  $replaceRegexp,
2827  [ $this, 'ucwordbreaksCallbackMB' ],
2828  $str
2829  );
2830  } else {
2831  return preg_replace_callback(
2832  '/\b([\w\x80-\xff]+)\b/',
2833  [ $this, 'ucwordbreaksCallbackAscii' ],
2834  $str
2835  );
2836  }
2837  }
2838 
2854  function caseFold( $s ) {
2855  return $this->uc( $s );
2856  }
2857 
2863  function checkTitleEncoding( $s ) {
2864  if ( is_array( $s ) ) {
2865  throw new MWException( 'Given array to checkTitleEncoding.' );
2866  }
2867  if ( StringUtils::isUtf8( $s ) ) {
2868  return $s;
2869  }
2870 
2871  return $this->iconv( $this->fallback8bitEncoding(), 'utf-8', $s );
2872  }
2873 
2878  return self::$dataCache->getItem( $this->mCode, 'fallback8bitEncoding' );
2879  }
2880 
2889  function hasWordBreaks() {
2890  return true;
2891  }
2892 
2900  function segmentByWord( $string ) {
2901  return $string;
2902  }
2903 
2911  function normalizeForSearch( $string ) {
2912  return self::convertDoubleWidth( $string );
2913  }
2914 
2923  protected static function convertDoubleWidth( $string ) {
2924  static $full = null;
2925  static $half = null;
2926 
2927  if ( $full === null ) {
2928  $fullWidth = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
2929  $halfWidth = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
2930  $full = str_split( $fullWidth, 3 );
2931  $half = str_split( $halfWidth );
2932  }
2933 
2934  $string = str_replace( $full, $half, $string );
2935  return $string;
2936  }
2937 
2943  protected static function insertSpace( $string, $pattern ) {
2944  $string = preg_replace( $pattern, " $1 ", $string );
2945  $string = preg_replace( '/ +/', ' ', $string );
2946  return $string;
2947  }
2948 
2953  function convertForSearchResult( $termsArray ) {
2954  # some languages, e.g. Chinese, need to do a conversion
2955  # in order for search results to be displayed correctly
2956  return $termsArray;
2957  }
2958 
2965  function firstChar( $s ) {
2966  $matches = [];
2967  preg_match(
2968  '/^([\x00-\x7f]|[\xc0-\xdf][\x80-\xbf]|' .
2969  '[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xf7][\x80-\xbf]{3})/',
2970  $s,
2971  $matches
2972  );
2973 
2974  if ( isset( $matches[1] ) ) {
2975  if ( strlen( $matches[1] ) != 3 ) {
2976  return $matches[1];
2977  }
2978 
2979  // Break down Hangul syllables to grab the first jamo
2980  $code = UtfNormal\Utils::utf8ToCodepoint( $matches[1] );
2981  if ( $code < 0xac00 || 0xd7a4 <= $code ) {
2982  return $matches[1];
2983  } elseif ( $code < 0xb098 ) {
2984  return "\u{3131}";
2985  } elseif ( $code < 0xb2e4 ) {
2986  return "\u{3134}";
2987  } elseif ( $code < 0xb77c ) {
2988  return "\u{3137}";
2989  } elseif ( $code < 0xb9c8 ) {
2990  return "\u{3139}";
2991  } elseif ( $code < 0xbc14 ) {
2992  return "\u{3141}";
2993  } elseif ( $code < 0xc0ac ) {
2994  return "\u{3142}";
2995  } elseif ( $code < 0xc544 ) {
2996  return "\u{3145}";
2997  } elseif ( $code < 0xc790 ) {
2998  return "\u{3147}";
2999  } elseif ( $code < 0xcc28 ) {
3000  return "\u{3148}";
3001  } elseif ( $code < 0xce74 ) {
3002  return "\u{314A}";
3003  } elseif ( $code < 0xd0c0 ) {
3004  return "\u{314B}";
3005  } elseif ( $code < 0xd30c ) {
3006  return "\u{314C}";
3007  } elseif ( $code < 0xd558 ) {
3008  return "\u{314D}";
3009  } else {
3010  return "\u{314E}";
3011  }
3012  } else {
3013  return '';
3014  }
3015  }
3016 
3020  function initEncoding() {
3021  wfDeprecated( __METHOD__, '1.28' );
3022  // No-op.
3023  }
3024 
3030  function recodeForEdit( $s ) {
3031  wfDeprecated( __METHOD__, '1.28' );
3032  return $s;
3033  }
3034 
3040  function recodeInput( $s ) {
3041  wfDeprecated( __METHOD__, '1.28' );
3042  return $s;
3043  }
3044 
3056  public function normalize( $s ) {
3057  global $wgAllUnicodeFixes;
3058  $s = UtfNormal\Validator::cleanUp( $s );
3059  if ( $wgAllUnicodeFixes ) {
3060  $s = $this->transformUsingPairFile( 'normalize-ar.php', $s );
3061  $s = $this->transformUsingPairFile( 'normalize-ml.php', $s );
3062  }
3063 
3064  return $s;
3065  }
3066 
3081  protected function transformUsingPairFile( $file, $string ) {
3082  if ( !isset( $this->transformData[$file] ) ) {
3083  global $IP;
3084  $data = require "$IP/languages/data/{$file}";
3085  $this->transformData[$file] = new ReplacementArray( $data );
3086  }
3087  return $this->transformData[$file]->replace( $string );
3088  }
3089 
3095  function isRTL() {
3096  return self::$dataCache->getItem( $this->mCode, 'rtl' );
3097  }
3098 
3103  function getDir() {
3104  return $this->isRTL() ? 'rtl' : 'ltr';
3105  }
3106 
3115  function alignStart() {
3116  return $this->isRTL() ? 'right' : 'left';
3117  }
3118 
3127  function alignEnd() {
3128  return $this->isRTL() ? 'left' : 'right';
3129  }
3130 
3142  function getDirMarkEntity( $opposite = false ) {
3143  if ( $opposite ) {
3144  return $this->isRTL() ? '&lrm;' : '&rlm;';
3145  }
3146  return $this->isRTL() ? '&rlm;' : '&lrm;';
3147  }
3148 
3159  function getDirMark( $opposite = false ) {
3160  $lrm = "\u{200E}"; # LEFT-TO-RIGHT MARK, commonly abbreviated LRM
3161  $rlm = "\u{200F}"; # RIGHT-TO-LEFT MARK, commonly abbreviated RLM
3162  if ( $opposite ) {
3163  return $this->isRTL() ? $lrm : $rlm;
3164  }
3165  return $this->isRTL() ? $rlm : $lrm;
3166  }
3167 
3171  function capitalizeAllNouns() {
3172  return self::$dataCache->getItem( $this->mCode, 'capitalizeAllNouns' );
3173  }
3174 
3182  function getArrow( $direction = 'forwards' ) {
3183  switch ( $direction ) {
3184  case 'forwards':
3185  return $this->isRTL() ? '←' : '→';
3186  case 'backwards':
3187  return $this->isRTL() ? '→' : '←';
3188  case 'left':
3189  return '←';
3190  case 'right':
3191  return '→';
3192  case 'up':
3193  return '↑';
3194  case 'down':
3195  return '↓';
3196  }
3197  }
3198 
3204  function linkPrefixExtension() {
3205  return self::$dataCache->getItem( $this->mCode, 'linkPrefixExtension' );
3206  }
3207 
3212  function getMagicWords() {
3213  return self::$dataCache->getItem( $this->mCode, 'magicWords' );
3214  }
3215 
3219  protected function doMagicHook() {
3220  if ( $this->mMagicHookDone ) {
3221  return;
3222  }
3223  $this->mMagicHookDone = true;
3224  Hooks::run( 'LanguageGetMagic', [ &$this->mMagicExtensions, $this->getCode() ], '1.16' );
3225  }
3226 
3232  function getMagic( $mw ) {
3233  // Saves a function call
3234  if ( !$this->mMagicHookDone ) {
3235  $this->doMagicHook();
3236  }
3237 
3238  if ( isset( $this->mMagicExtensions[$mw->mId] ) ) {
3239  $rawEntry = $this->mMagicExtensions[$mw->mId];
3240  } else {
3241  $rawEntry = self::$dataCache->getSubitem(
3242  $this->mCode, 'magicWords', $mw->mId );
3243  }
3244 
3245  if ( !is_array( $rawEntry ) ) {
3246  wfWarn( "\"$rawEntry\" is not a valid magic word for \"$mw->mId\"" );
3247  } else {
3248  $mw->mCaseSensitive = $rawEntry[0];
3249  $mw->mSynonyms = array_slice( $rawEntry, 1 );
3250  }
3251  }
3252 
3258  function addMagicWordsByLang( $newWords ) {
3259  $fallbackChain = $this->getFallbackLanguages();
3260  $fallbackChain = array_reverse( $fallbackChain );
3261  foreach ( $fallbackChain as $code ) {
3262  if ( isset( $newWords[$code] ) ) {
3263  $this->mMagicExtensions = $newWords[$code] + $this->mMagicExtensions;
3264  }
3265  }
3266  }
3267 
3274  // Cache aliases because it may be slow to load them
3275  if ( is_null( $this->mExtendedSpecialPageAliases ) ) {
3276  // Initialise array
3277  $this->mExtendedSpecialPageAliases =
3278  self::$dataCache->getItem( $this->mCode, 'specialPageAliases' );
3279  Hooks::run( 'LanguageGetSpecialPageAliases',
3280  [ &$this->mExtendedSpecialPageAliases, $this->getCode() ], '1.16' );
3281  }
3282 
3284  }
3285 
3292  function emphasize( $text ) {
3293  return "<em>$text</em>";
3294  }
3295 
3318  public function formatNum( $number, $nocommafy = false ) {
3319  global $wgTranslateNumerals;
3320  if ( !$nocommafy ) {
3321  $number = $this->commafy( $number );
3322  $s = $this->separatorTransformTable();
3323  if ( $s ) {
3324  $number = strtr( $number, $s );
3325  }
3326  }
3327 
3328  if ( $wgTranslateNumerals ) {
3329  $s = $this->digitTransformTable();
3330  if ( $s ) {
3331  $number = strtr( $number, $s );
3332  }
3333  }
3334 
3335  return (string)$number;
3336  }
3337 
3346  public function formatNumNoSeparators( $number ) {
3347  return $this->formatNum( $number, true );
3348  }
3349 
3354  public function parseFormattedNumber( $number ) {
3355  $s = $this->digitTransformTable();
3356  if ( $s ) {
3357  // eliminate empty array values such as ''. (T66347)
3358  $s = array_filter( $s );
3359  $number = strtr( $number, array_flip( $s ) );
3360  }
3361 
3362  $s = $this->separatorTransformTable();
3363  if ( $s ) {
3364  // eliminate empty array values such as ''. (T66347)
3365  $s = array_filter( $s );
3366  $number = strtr( $number, array_flip( $s ) );
3367  }
3368 
3369  $number = strtr( $number, [ ',' => '' ] );
3370  return $number;
3371  }
3372 
3379  function commafy( $number ) {
3382  if ( $number === null ) {
3383  return '';
3384  }
3385 
3386  if ( !$digitGroupingPattern || $digitGroupingPattern === "###,###,###" ) {
3387  // Default grouping is at thousands, use the same for ###,###,### pattern too.
3388  // In some languages it's conventional not to insert a thousands separator
3389  // in numbers that are four digits long (1000-9999).
3390  if ( $minimumGroupingDigits ) {
3391  // Number of '#' characters after last comma in the grouping pattern.
3392  // The pattern is hardcoded here, but this would vary for different patterns.
3393  $primaryGroupingSize = 3;
3394  // Maximum length of a number to suppress digit grouping for.
3395  $maximumLength = $minimumGroupingDigits + $primaryGroupingSize - 1;
3396  if ( preg_match( '/^\-?\d{1,' . $maximumLength . '}(\.\d+)?$/', $number ) ) {
3397  return $number;
3398  }
3399  }
3400  return strrev( (string)preg_replace( '/(\d{3})(?=\d)(?!\d*\.)/', '$1,', strrev( $number ) ) );
3401  } else {
3402  // Ref: http://cldr.unicode.org/translation/number-patterns
3403  $sign = "";
3404  if ( intval( $number ) < 0 ) {
3405  // For negative numbers apply the algorithm like positive number and add sign.
3406  $sign = "-";
3407  $number = substr( $number, 1 );
3408  }
3409  $integerPart = [];
3410  $decimalPart = [];
3411  $numMatches = preg_match_all( "/(#+)/", $digitGroupingPattern, $matches );
3412  preg_match( "/\d+/", $number, $integerPart );
3413  preg_match( "/\.\d*/", $number, $decimalPart );
3414  $groupedNumber = ( count( $decimalPart ) > 0 ) ? $decimalPart[0] : "";
3415  if ( $groupedNumber === $number ) {
3416  // the string does not have any number part. Eg: .12345
3417  return $sign . $groupedNumber;
3418  }
3419  $start = $end = ( $integerPart ) ? strlen( $integerPart[0] ) : 0;
3420  while ( $start > 0 ) {
3421  $match = $matches[0][$numMatches - 1];
3422  $matchLen = strlen( $match );
3423  $start = $end - $matchLen;
3424  if ( $start < 0 ) {
3425  $start = 0;
3426  }
3427  $groupedNumber = substr( $number, $start, $end - $start ) . $groupedNumber;
3428  $end = $start;
3429  if ( $numMatches > 1 ) {
3430  // use the last pattern for the rest of the number
3431  $numMatches--;
3432  }
3433  if ( $start > 0 ) {
3434  $groupedNumber = "," . $groupedNumber;
3435  }
3436  }
3437  return $sign . $groupedNumber;
3438  }
3439  }
3440 
3445  return self::$dataCache->getItem( $this->mCode, 'digitGroupingPattern' );
3446  }
3447 
3451  function digitTransformTable() {
3452  return self::$dataCache->getItem( $this->mCode, 'digitTransformTable' );
3453  }
3454 
3459  return self::$dataCache->getItem( $this->mCode, 'separatorTransformTable' );
3460  }
3461 
3466  return self::$dataCache->getItem( $this->mCode, 'minimumGroupingDigits' );
3467  }
3468 
3477  public function listToText( array $list ) {
3478  $itemCount = count( $list );
3479  if ( $itemCount < 1 ) {
3480  return '';
3481  }
3482  $text = array_pop( $list );
3483  if ( $itemCount > 1 ) {
3484  $and = $this->msg( 'and' )->escaped();
3485  $space = $this->msg( 'word-separator' )->escaped();
3486  $comma = '';
3487  if ( $itemCount > 2 ) {
3488  $comma = $this->msg( 'comma-separator' )->escaped();
3489  }
3490  $text = implode( $comma, $list ) . $and . $space . $text;
3491  }
3492  return $text;
3493  }
3494 
3501  function commaList( array $list ) {
3502  return implode(
3503  wfMessage( 'comma-separator' )->inLanguage( $this )->escaped(),
3504  $list
3505  );
3506  }
3507 
3514  function semicolonList( array $list ) {
3515  return implode(
3516  wfMessage( 'semicolon-separator' )->inLanguage( $this )->escaped(),
3517  $list
3518  );
3519  }
3520 
3526  function pipeList( array $list ) {
3527  return implode(
3528  wfMessage( 'pipe-separator' )->inLanguage( $this )->escaped(),
3529  $list
3530  );
3531  }
3532 
3550  function truncate( $string, $length, $ellipsis = '...', $adjustLength = true ) {
3551  wfDeprecated( __METHOD__, '1.31' );
3552  return $this->truncateForDatabase( $string, $length, $ellipsis, $adjustLength );
3553  }
3554 
3570  function truncateForDatabase( $string, $length, $ellipsis = '...', $adjustLength = true ) {
3571  return $this->truncateInternal(
3572  $string, $length, $ellipsis, $adjustLength, 'strlen', 'substr'
3573  );
3574  }
3575 
3594  function truncateForVisual( $string, $length, $ellipsis = '...', $adjustLength = true ) {
3595  // Passing encoding to mb_strlen and mb_substr is optional.
3596  // Encoding defaults to mb_internal_encoding(), which is set to UTF-8 in Setup.php, so
3597  // explicit specification of encoding is skipped.
3598  // Note: Both multibyte methods are callables invoked in truncateInternal.
3599  return $this->truncateInternal(
3600  $string, $length, $ellipsis, $adjustLength, 'mb_strlen', 'mb_substr'
3601  );
3602  }
3603 
3620  private function truncateInternal(
3621  $string, $length, $ellipsis, $adjustLength, $measureLength, $getSubstring
3622  ) {
3623  if ( !is_callable( $measureLength ) || !is_callable( $getSubstring ) ) {
3624  throw new InvalidArgumentException( 'Invalid callback provided' );
3625  }
3626 
3627  # Check if there is no need to truncate
3628  if ( $measureLength( $string ) <= abs( $length ) ) {
3629  return $string; // no need to truncate
3630  }
3631 
3632  # Use the localized ellipsis character
3633  if ( $ellipsis == '...' ) {
3634  $ellipsis = wfMessage( 'ellipsis' )->inLanguage( $this )->escaped();
3635  }
3636  if ( $length == 0 ) {
3637  return $ellipsis; // convention
3638  }
3639 
3640  $stringOriginal = $string;
3641  # If ellipsis length is >= $length then we can't apply $adjustLength
3642  if ( $adjustLength && $measureLength( $ellipsis ) >= abs( $length ) ) {
3643  $string = $ellipsis; // this can be slightly unexpected
3644  # Otherwise, truncate and add ellipsis...
3645  } else {
3646  $ellipsisLength = $adjustLength ? $measureLength( $ellipsis ) : 0;
3647  if ( $length > 0 ) {
3648  $length -= $ellipsisLength;
3649  $string = $getSubstring( $string, 0, $length ); // xyz...
3650  $string = $this->removeBadCharLast( $string );
3651  $string = rtrim( $string );
3652  $string = $string . $ellipsis;
3653  } else {
3654  $length += $ellipsisLength;
3655  $string = $getSubstring( $string, $length ); // ...xyz
3656  $string = $this->removeBadCharFirst( $string );
3657  $string = ltrim( $string );
3658  $string = $ellipsis . $string;
3659  }
3660  }
3661 
3662  # Do not truncate if the ellipsis makes the string longer/equal (T24181).
3663  # This check is *not* redundant if $adjustLength, due to the single case where
3664  # LEN($ellipsis) > ABS($limit arg); $stringOriginal could be shorter than $string.
3665  if ( $measureLength( $string ) < $measureLength( $stringOriginal ) ) {
3666  return $string;
3667  } else {
3668  return $stringOriginal;
3669  }
3670  }
3671 
3679  protected function removeBadCharLast( $string ) {
3680  if ( $string != '' ) {
3681  $char = ord( $string[strlen( $string ) - 1] );
3682  $m = [];
3683  if ( $char >= 0xc0 ) {
3684  # We got the first byte only of a multibyte char; remove it.
3685  $string = substr( $string, 0, -1 );
3686  } elseif ( $char >= 0x80 &&
3687  // Use the /s modifier (PCRE_DOTALL) so (.*) also matches newlines
3688  preg_match( '/^(.*)(?:[\xe0-\xef][\x80-\xbf]|' .
3689  '[\xf0-\xf7][\x80-\xbf]{1,2})$/s', $string, $m )
3690  ) {
3691  # We chopped in the middle of a character; remove it
3692  $string = $m[1];
3693  }
3694  }
3695  return $string;
3696  }
3697 
3705  protected function removeBadCharFirst( $string ) {
3706  if ( $string != '' ) {
3707  $char = ord( $string[0] );
3708  if ( $char >= 0x80 && $char < 0xc0 ) {
3709  # We chopped in the middle of a character; remove the whole thing
3710  $string = preg_replace( '/^[\x80-\xbf]+/', '', $string );
3711  }
3712  }
3713  return $string;
3714  }
3715 
3731  function truncateHtml( $text, $length, $ellipsis = '...' ) {
3732  # Use the localized ellipsis character
3733  if ( $ellipsis == '...' ) {
3734  $ellipsis = wfMessage( 'ellipsis' )->inLanguage( $this )->escaped();
3735  }
3736  # Check if there is clearly no need to truncate
3737  if ( $length <= 0 ) {
3738  return $ellipsis; // no text shown, nothing to format (convention)
3739  } elseif ( strlen( $text ) <= $length ) {
3740  return $text; // string short enough even *with* HTML (short-circuit)
3741  }
3742 
3743  $dispLen = 0; // innerHTML legth so far
3744  $testingEllipsis = false; // checking if ellipses will make string longer/equal?
3745  $tagType = 0; // 0-open, 1-close
3746  $bracketState = 0; // 1-tag start, 2-tag name, 0-neither
3747  $entityState = 0; // 0-not entity, 1-entity
3748  $tag = $ret = ''; // accumulated tag name, accumulated result string
3749  $openTags = []; // open tag stack
3750  $maybeState = null; // possible truncation state
3751 
3752  $textLen = strlen( $text );
3753  $neLength = max( 0, $length - strlen( $ellipsis ) ); // non-ellipsis len if truncated
3754  for ( $pos = 0; true; ++$pos ) {
3755  # Consider truncation once the display length has reached the maximim.
3756  # We check if $dispLen > 0 to grab tags for the $neLength = 0 case.
3757  # Check that we're not in the middle of a bracket/entity...
3758  if ( $dispLen && $dispLen >= $neLength && $bracketState == 0 && !$entityState ) {
3759  if ( !$testingEllipsis ) {
3760  $testingEllipsis = true;
3761  # Save where we are; we will truncate here unless there turn out to
3762  # be so few remaining characters that truncation is not necessary.
3763  if ( !$maybeState ) { // already saved? ($neLength = 0 case)
3764  $maybeState = [ $ret, $openTags ]; // save state
3765  }
3766  } elseif ( $dispLen > $length && $dispLen > strlen( $ellipsis ) ) {
3767  # String in fact does need truncation, the truncation point was OK.
3768  list( $ret, $openTags ) = $maybeState; // reload state
3769  $ret = $this->removeBadCharLast( $ret ); // multi-byte char fix
3770  $ret .= $ellipsis; // add ellipsis
3771  break;
3772  }
3773  }
3774  if ( $pos >= $textLen ) {
3775  break; // extra iteration just for above checks
3776  }
3777 
3778  # Read the next char...
3779  $ch = $text[$pos];
3780  $lastCh = $pos ? $text[$pos - 1] : '';
3781  $ret .= $ch; // add to result string
3782  if ( $ch == '<' ) {
3783  $this->truncate_endBracket( $tag, $tagType, $lastCh, $openTags ); // for bad HTML
3784  $entityState = 0; // for bad HTML
3785  $bracketState = 1; // tag started (checking for backslash)
3786  } elseif ( $ch == '>' ) {
3787  $this->truncate_endBracket( $tag, $tagType, $lastCh, $openTags );
3788  $entityState = 0; // for bad HTML
3789  $bracketState = 0; // out of brackets
3790  } elseif ( $bracketState == 1 ) {
3791  if ( $ch == '/' ) {
3792  $tagType = 1; // close tag (e.g. "</span>")
3793  } else {
3794  $tagType = 0; // open tag (e.g. "<span>")
3795  $tag .= $ch;
3796  }
3797  $bracketState = 2; // building tag name
3798  } elseif ( $bracketState == 2 ) {
3799  if ( $ch != ' ' ) {
3800  $tag .= $ch;
3801  } else {
3802  // Name found (e.g. "<a href=..."), add on tag attributes...
3803  $pos += $this->truncate_skip( $ret, $text, "<>", $pos + 1 );
3804  }
3805  } elseif ( $bracketState == 0 ) {
3806  if ( $entityState ) {
3807  if ( $ch == ';' ) {
3808  $entityState = 0;
3809  $dispLen++; // entity is one displayed char
3810  }
3811  } else {
3812  if ( $neLength == 0 && !$maybeState ) {
3813  // Save state without $ch. We want to *hit* the first
3814  // display char (to get tags) but not *use* it if truncating.
3815  $maybeState = [ substr( $ret, 0, -1 ), $openTags ];
3816  }
3817  if ( $ch == '&' ) {
3818  $entityState = 1; // entity found, (e.g. "&#160;")
3819  } else {
3820  $dispLen++; // this char is displayed
3821  // Add the next $max display text chars after this in one swoop...
3822  $max = ( $testingEllipsis ? $length : $neLength ) - $dispLen;
3823  $skipped = $this->truncate_skip( $ret, $text, "<>&", $pos + 1, $max );
3824  $dispLen += $skipped;
3825  $pos += $skipped;
3826  }
3827  }
3828  }
3829  }
3830  // Close the last tag if left unclosed by bad HTML
3831  $this->truncate_endBracket( $tag, $text[$textLen - 1], $tagType, $openTags );
3832  while ( count( $openTags ) > 0 ) {
3833  $ret .= '</' . array_pop( $openTags ) . '>'; // close open tags
3834  }
3835  return $ret;
3836  }
3837 
3849  private function truncate_skip( &$ret, $text, $search, $start, $len = null ) {
3850  if ( $len === null ) {
3851  $len = -1; // -1 means "no limit" for strcspn
3852  } elseif ( $len < 0 ) {
3853  $len = 0; // sanity
3854  }
3855  $skipCount = 0;
3856  if ( $start < strlen( $text ) ) {
3857  $skipCount = strcspn( $text, $search, $start, $len );
3858  $ret .= substr( $text, $start, $skipCount );
3859  }
3860  return $skipCount;
3861  }
3862 
3872  private function truncate_endBracket( &$tag, $tagType, $lastCh, &$openTags ) {
3873  $tag = ltrim( $tag );
3874  if ( $tag != '' ) {
3875  if ( $tagType == 0 && $lastCh != '/' ) {
3876  $openTags[] = $tag; // tag opened (didn't close itself)
3877  } elseif ( $tagType == 1 ) {
3878  if ( $openTags && $tag == $openTags[count( $openTags ) - 1] ) {
3879  array_pop( $openTags ); // tag closed
3880  }
3881  }
3882  $tag = '';
3883  }
3884  }
3885 
3894  function convertGrammar( $word, $case ) {
3895  global $wgGrammarForms;
3896  if ( isset( $wgGrammarForms[$this->getCode()][$case][$word] ) ) {
3897  return $wgGrammarForms[$this->getCode()][$case][$word];
3898  }
3899 
3900  $grammarTransformations = $this->getGrammarTransformations();
3901 
3902  if ( isset( $grammarTransformations[$case] ) ) {
3903  $forms = $grammarTransformations[$case];
3904 
3905  // Some names of grammar rules are aliases for other rules.
3906  // In such cases the value is a string rather than object,
3907  // so load the actual rules.
3908  if ( is_string( $forms ) ) {
3909  $forms = $grammarTransformations[$forms];
3910  }
3911 
3912  foreach ( array_values( $forms ) as $rule ) {
3913  $form = $rule[0];
3914 
3915  if ( $form === '@metadata' ) {
3916  continue;
3917  }
3918 
3919  $replacement = $rule[1];
3920 
3921  $regex = '/' . addcslashes( $form, '/' ) . '/u';
3922  $patternMatches = preg_match( $regex, $word );
3923 
3924  if ( $patternMatches === false ) {
3925  wfLogWarning(
3926  'An error occurred while processing grammar. ' .
3927  "Word: '$word'. Regex: /$form/."
3928  );
3929  } elseif ( $patternMatches === 1 ) {
3930  $word = preg_replace( $regex, $replacement, $word );
3931 
3932  break;
3933  }
3934  }
3935  }
3936 
3937  return $word;
3938  }
3939 
3945  function getGrammarForms() {
3946  global $wgGrammarForms;
3947  if ( isset( $wgGrammarForms[$this->getCode()] )
3948  && is_array( $wgGrammarForms[$this->getCode()] )
3949  ) {
3950  return $wgGrammarForms[$this->getCode()];
3951  }
3952 
3953  return [];
3954  }
3955 
3965  public function getGrammarTransformations() {
3966  $languageCode = $this->getCode();
3967 
3968  if ( self::$grammarTransformations === null ) {
3969  self::$grammarTransformations = new MapCacheLRU( 10 );
3970  }
3971 
3972  if ( self::$grammarTransformations->has( $languageCode ) ) {
3973  return self::$grammarTransformations->get( $languageCode );
3974  }
3975 
3976  $data = [];
3977 
3978  $grammarDataFile = __DIR__ . "/data/grammarTransformations/$languageCode.json";
3979  if ( is_readable( $grammarDataFile ) ) {
3980  $data = FormatJson::decode(
3981  file_get_contents( $grammarDataFile ),
3982  true
3983  );
3984 
3985  if ( $data === null ) {
3986  throw new MWException( "Invalid grammar data for \"$languageCode\"." );
3987  }
3988 
3989  self::$grammarTransformations->set( $languageCode, $data );
3990  }
3991 
3992  return $data;
3993  }
3994 
4014  function gender( $gender, $forms ) {
4015  if ( !count( $forms ) ) {
4016  return '';
4017  }
4018  $forms = $this->preConvertPlural( $forms, 2 );
4019  if ( $gender === 'male' ) {
4020  return $forms[0];
4021  }
4022  if ( $gender === 'female' ) {
4023  return $forms[1];
4024  }
4025  return $forms[2] ?? $forms[0];
4026  }
4027 
4043  function convertPlural( $count, $forms ) {
4044  // Handle explicit n=pluralform cases
4045  $forms = $this->handleExplicitPluralForms( $count, $forms );
4046  if ( is_string( $forms ) ) {
4047  return $forms;
4048  }
4049  if ( !count( $forms ) ) {
4050  return '';
4051  }
4052 
4053  $pluralForm = $this->getPluralRuleIndexNumber( $count );
4054  $pluralForm = min( $pluralForm, count( $forms ) - 1 );
4055  return $forms[$pluralForm];
4056  }
4057 
4073  protected function handleExplicitPluralForms( $count, array $forms ) {
4074  foreach ( $forms as $index => $form ) {
4075  if ( preg_match( '/\d+=/i', $form ) ) {
4076  $pos = strpos( $form, '=' );
4077  if ( substr( $form, 0, $pos ) === (string)$count ) {
4078  return substr( $form, $pos + 1 );
4079  }
4080  unset( $forms[$index] );
4081  }
4082  }
4083  return array_values( $forms );
4084  }
4085 
4094  protected function preConvertPlural( /* Array */ $forms, $count ) {
4095  while ( count( $forms ) < $count ) {
4096  $forms[] = $forms[count( $forms ) - 1];
4097  }
4098  return $forms;
4099  }
4100 
4117  public function embedBidi( $text = '' ) {
4118  $dir = self::strongDirFromContent( $text );
4119  if ( $dir === 'ltr' ) {
4120  // Wrap in LEFT-TO-RIGHT EMBEDDING ... POP DIRECTIONAL FORMATTING
4121  return self::$lre . $text . self::$pdf;
4122  }
4123  if ( $dir === 'rtl' ) {
4124  // Wrap in RIGHT-TO-LEFT EMBEDDING ... POP DIRECTIONAL FORMATTING
4125  return self::$rle . $text . self::$pdf;
4126  }
4127  // No strong directionality: do not wrap
4128  return $text;
4129  }
4130 
4144  function translateBlockExpiry( $str, User $user = null, $now = 0 ) {
4145  $duration = SpecialBlock::getSuggestedDurations( $this );
4146  foreach ( $duration as $show => $value ) {
4147  if ( strcmp( $str, $value ) == 0 ) {
4148  return htmlspecialchars( trim( $show ) );
4149  }
4150  }
4151 
4152  if ( wfIsInfinity( $str ) ) {
4153  foreach ( $duration as $show => $value ) {
4154  if ( wfIsInfinity( $value ) ) {
4155  return htmlspecialchars( trim( $show ) );
4156  }
4157  }
4158  }
4159 
4160  // If all else fails, return a standard duration or timestamp description.
4161  $time = strtotime( $str, $now );
4162  if ( $time === false ) { // Unknown format. Return it as-is in case.
4163  return $str;
4164  } elseif ( $time !== strtotime( $str, $now + 1 ) ) { // It's a relative timestamp.
4165  // The result differs based on current time, so the difference
4166  // is a fixed duration length.
4167  return $this->formatDuration( $time - $now );
4168  } else { // It's an absolute timestamp.
4169  if ( $time === 0 ) {
4170  // wfTimestamp() handles 0 as current time instead of epoch.
4171  $time = '19700101000000';
4172  }
4173  if ( $user ) {
4174  return $this->userTimeAndDate( $time, $user );
4175  }
4176  return $this->timeanddate( $time );
4177  }
4178  }
4179 
4187  public function segmentForDiff( $text ) {
4188  return $text;
4189  }
4190 
4197  public function unsegmentForDiff( $text ) {
4198  return $text;
4199  }
4200 
4207  public function getConverter() {
4208  return $this->mConverter;
4209  }
4210 
4219  public function autoConvert( $text, $variant = false ) {
4220  return $this->mConverter->autoConvert( $text, $variant );
4221  }
4222 
4229  public function autoConvertToAllVariants( $text ) {
4230  return $this->mConverter->autoConvertToAllVariants( $text );
4231  }
4232 
4244  public function convert( $text ) {
4245  return $this->mConverter->convert( $text );
4246  }
4247 
4254  public function convertTitle( $title ) {
4255  return $this->mConverter->convertTitle( $title );
4256  }
4257 
4266  public function convertNamespace( $ns, $variant = null ) {
4267  return $this->mConverter->convertNamespace( $ns, $variant );
4268  }
4269 
4275  public function hasVariants() {
4276  return count( $this->getVariants() ) > 1;
4277  }
4278 
4286  public function hasVariant( $variant ) {
4287  return (bool)$this->mConverter->validateVariant( $variant );
4288  }
4289 
4297  public function convertHtml( $text, $isTitle = false ) {
4298  return htmlspecialchars( $this->convert( $text, $isTitle ) );
4299  }
4300 
4305  public function convertCategoryKey( $key ) {
4306  return $this->mConverter->convertCategoryKey( $key );
4307  }
4308 
4315  public function getVariants() {
4316  return $this->mConverter->getVariants();
4317  }
4318 
4322  public function getPreferredVariant() {
4323  return $this->mConverter->getPreferredVariant();
4324  }
4325 
4329  public function getDefaultVariant() {
4330  return $this->mConverter->getDefaultVariant();
4331  }
4332 
4336  public function getURLVariant() {
4337  return $this->mConverter->getURLVariant();
4338  }
4339 
4352  public function findVariantLink( &$link, &$nt, $ignoreOtherCond = false ) {
4353  $this->mConverter->findVariantLink( $link, $nt, $ignoreOtherCond );
4354  }
4355 
4362  function getExtraHashOptions() {
4363  return $this->mConverter->getExtraHashOptions();
4364  }
4365 
4373  public function getParsedTitle() {
4374  return $this->mConverter->getParsedTitle();
4375  }
4376 
4383  public function updateConversionTable( Title $title ) {
4384  $this->mConverter->updateConversionTable( $title );
4385  }
4386 
4403  public function markNoConversion( $text, $noParse = false ) {
4404  wfDeprecated( __METHOD__, '1.32' );
4405  // Excluding protocal-relative URLs may avoid many false positives.
4406  if ( $noParse || preg_match( '/^(?:' . wfUrlProtocolsWithoutProtRel() . ')/', $text ) ) {
4407  return $this->mConverter->markNoConversion( $text );
4408  } else {
4409  return $text;
4410  }
4411  }
4412 
4419  public function linkTrail() {
4420  return self::$dataCache->getItem( $this->mCode, 'linkTrail' );
4421  }
4422 
4429  public function linkPrefixCharset() {
4430  return self::$dataCache->getItem( $this->mCode, 'linkPrefixCharset' );
4431  }
4432 
4440  public function getParentLanguage() {
4441  if ( $this->mParentLanguage !== false ) {
4442  return $this->mParentLanguage;
4443  }
4444 
4445  $code = explode( '-', $this->getCode() )[0];
4446  if ( !in_array( $code, LanguageConverter::$languagesWithVariants ) ) {
4447  $this->mParentLanguage = null;
4448  return null;
4449  }
4450  $lang = self::factory( $code );
4451  if ( !$lang->hasVariant( $this->getCode() ) ) {
4452  $this->mParentLanguage = null;
4453  return null;
4454  }
4455 
4456  $this->mParentLanguage = $lang;
4457  return $lang;
4458  }
4459 
4467  public function equals( Language $lang ) {
4468  return $lang === $this || $lang->getCode() === $this->mCode;
4469  }
4470 
4479  public function getCode() {
4480  return $this->mCode;
4481  }
4482 
4493  public function getHtmlCode() {
4494  if ( is_null( $this->mHtmlCode ) ) {
4495  $this->mHtmlCode = LanguageCode::bcp47( $this->getCode() );
4496  }
4497  return $this->mHtmlCode;
4498  }
4499 
4503  public function setCode( $code ) {
4504  $this->mCode = $code;
4505  // Ensure we don't leave incorrect cached data lying around
4506  $this->mHtmlCode = null;
4507  $this->mParentLanguage = false;
4508  }
4509 
4517  public static function getCodeFromFileName( $filename, $prefix = 'Language', $suffix = '.php' ) {
4518  $m = null;
4519  preg_match( '/' . preg_quote( $prefix, '/' ) . '([A-Z][a-z_]+)' .
4520  preg_quote( $suffix, '/' ) . '/', $filename, $m );
4521  if ( !count( $m ) ) {
4522  return false;
4523  }
4524  return str_replace( '_', '-', strtolower( $m[1] ) );
4525  }
4526 
4532  public static function classFromCode( $code, $fallback = true ) {
4533  if ( $fallback && $code == 'en' ) {
4534  return 'Language';
4535  } else {
4536  return 'Language' . str_replace( '-', '_', ucfirst( $code ) );
4537  }
4538  }
4539 
4548  public static function getFileName( $prefix, $code, $suffix = '.php' ) {
4549  if ( !self::isValidBuiltInCode( $code ) ) {
4550  throw new MWException( "Invalid language code \"$code\"" );
4551  }
4552 
4553  return $prefix . str_replace( '-', '_', ucfirst( $code ) ) . $suffix;
4554  }
4555 
4560  public static function getMessagesFileName( $code ) {
4561  global $IP;
4562  $file = self::getFileName( "$IP/languages/messages/Messages", $code, '.php' );
4563  Hooks::run( 'Language::getMessagesFileName', [ $code, &$file ] );
4564  return $file;
4565  }
4566 
4573  public static function getJsonMessagesFileName( $code ) {
4574  global $IP;
4575 
4576  if ( !self::isValidBuiltInCode( $code ) ) {
4577  throw new MWException( "Invalid language code \"$code\"" );
4578  }
4579 
4580  return "$IP/languages/i18n/$code.json";
4581  }
4582 
4590  public static function getFallbackFor( $code ) {
4591  $fallbacks = self::getFallbacksFor( $code );
4592  if ( $fallbacks ) {
4593  return $fallbacks[0];
4594  }
4595  return false;
4596  }
4597 
4608  public static function getFallbacksFor( $code, $mode = self::MESSAGES_FALLBACKS ) {
4609  if ( $code === 'en' || !self::isValidBuiltInCode( $code ) ) {
4610  return [];
4611  }
4612  switch ( $mode ) {
4613  case self::MESSAGES_FALLBACKS:
4614  // For unknown languages, fallbackSequence returns an empty array,
4615  // hardcode fallback to 'en' in that case as English messages are
4616  // always defined.
4617  return self::getLocalisationCache()->getItem( $code, 'fallbackSequence' ) ?: [ 'en' ];
4618  case self::STRICT_FALLBACKS:
4619  // Use this mode when you don't want to fallback to English unless
4620  // explicitly defined, for example when you have language-variant icons
4621  // and an international language-independent fallback.
4622  return self::getLocalisationCache()->getItem( $code, 'originalFallbackSequence' );
4623  default:
4624  throw new MWException( "Invalid fallback mode \"$mode\"" );
4625  }
4626  }
4627 
4636  public static function getFallbacksIncludingSiteLanguage( $code ) {
4637  global $wgLanguageCode;
4638 
4639  // Usually, we will only store a tiny number of fallback chains, so we
4640  // keep them in static memory.
4641  $cacheKey = "{$code}-{$wgLanguageCode}";
4642 
4643  if ( !array_key_exists( $cacheKey, self::$fallbackLanguageCache ) ) {
4644  $fallbacks = self::getFallbacksFor( $code );
4645 
4646  // Append the site's fallback chain, including the site language itself
4647  $siteFallbacks = self::getFallbacksFor( $wgLanguageCode );
4648  array_unshift( $siteFallbacks, $wgLanguageCode );
4649 
4650  // Eliminate any languages already included in the chain
4651  $siteFallbacks = array_diff( $siteFallbacks, $fallbacks );
4652 
4653  self::$fallbackLanguageCache[$cacheKey] = [ $fallbacks, $siteFallbacks ];
4654  }
4655  return self::$fallbackLanguageCache[$cacheKey];
4656  }
4657 
4667  public static function getMessagesFor( $code ) {
4668  return self::getLocalisationCache()->getItem( $code, 'messages' );
4669  }
4670 
4679  public static function getMessageFor( $key, $code ) {
4680  return self::getLocalisationCache()->getSubitem( $code, 'messages', $key );
4681  }
4682 
4691  public static function getMessageKeysFor( $code ) {
4692  return self::getLocalisationCache()->getSubitemList( $code, 'messages' );
4693  }
4694 
4699  function fixVariableInNamespace( $talk ) {
4700  if ( strpos( $talk, '$1' ) === false ) {
4701  return $talk;
4702  }
4703 
4704  global $wgMetaNamespace;
4705  $talk = str_replace( '$1', $wgMetaNamespace, $talk );
4706 
4707  # Allow grammar transformations
4708  # Allowing full message-style parsing would make simple requests
4709  # such as action=raw much more expensive than they need to be.
4710  # This will hopefully cover most cases.
4711  $talk = preg_replace_callback( '/{{grammar:(.*?)\|(.*?)}}/i',
4712  [ $this, 'replaceGrammarInNamespace' ], $talk );
4713  return str_replace( ' ', '_', $talk );
4714  }
4715 
4720  function replaceGrammarInNamespace( $m ) {
4721  return $this->convertGrammar( trim( $m[2] ), trim( $m[1] ) );
4722  }
4723 
4734  public function formatExpiry( $expiry, $format = true, $infinity = 'infinity' ) {
4735  static $dbInfinity;
4736  if ( $dbInfinity === null ) {
4737  $dbInfinity = wfGetDB( DB_REPLICA )->getInfinity();
4738  }
4739 
4740  if ( $expiry == '' || $expiry === 'infinity' || $expiry == $dbInfinity ) {
4741  return $format === true
4742  ? $this->getMessageFromDB( 'infiniteblock' )
4743  : $infinity;
4744  } else {
4745  return $format === true
4746  ? $this->timeanddate( $expiry, /* User preference timezone */ true )
4747  : wfTimestamp( $format, $expiry );
4748  }
4749  }
4750 
4764  function formatTimePeriod( $seconds, $format = [] ) {
4765  if ( !is_array( $format ) ) {
4766  $format = [ 'avoid' => $format ]; // For backwards compatibility
4767  }
4768  if ( !isset( $format['avoid'] ) ) {
4769  $format['avoid'] = false;
4770  }
4771  if ( !isset( $format['noabbrevs'] ) ) {
4772  $format['noabbrevs'] = false;
4773  }
4774  $secondsMsg = wfMessage(
4775  $format['noabbrevs'] ? 'seconds' : 'seconds-abbrev' )->inLanguage( $this );
4776  $minutesMsg = wfMessage(
4777  $format['noabbrevs'] ? 'minutes' : 'minutes-abbrev' )->inLanguage( $this );
4778  $hoursMsg = wfMessage(
4779  $format['noabbrevs'] ? 'hours' : 'hours-abbrev' )->inLanguage( $this );
4780  $daysMsg = wfMessage(
4781  $format['noabbrevs'] ? 'days' : 'days-abbrev' )->inLanguage( $this );
4782 
4783  if ( round( $seconds * 10 ) < 100 ) {
4784  $s = $this->formatNum( sprintf( "%.1f", round( $seconds * 10 ) / 10 ) );
4785  $s = $secondsMsg->params( $s )->text();
4786  } elseif ( round( $seconds ) < 60 ) {
4787  $s = $this->formatNum( round( $seconds ) );
4788  $s = $secondsMsg->params( $s )->text();
4789  } elseif ( round( $seconds ) < 3600 ) {
4790  $minutes = floor( $seconds / 60 );
4791  $secondsPart = round( fmod( $seconds, 60 ) );
4792  if ( $secondsPart == 60 ) {
4793  $secondsPart = 0;
4794  $minutes++;
4795  }
4796  $s = $minutesMsg->params( $this->formatNum( $minutes ) )->text();
4797  $s .= ' ';
4798  $s .= $secondsMsg->params( $this->formatNum( $secondsPart ) )->text();
4799  } elseif ( round( $seconds ) <= 2 * 86400 ) {
4800  $hours = floor( $seconds / 3600 );
4801  $minutes = floor( ( $seconds - $hours * 3600 ) / 60 );
4802  $secondsPart = round( $seconds - $hours * 3600 - $minutes * 60 );
4803  if ( $secondsPart == 60 ) {
4804  $secondsPart = 0;
4805  $minutes++;
4806  }
4807  if ( $minutes == 60 ) {
4808  $minutes = 0;
4809  $hours++;
4810  }
4811  $s = $hoursMsg->params( $this->formatNum( $hours ) )->text();
4812  $s .= ' ';
4813  $s .= $minutesMsg->params( $this->formatNum( $minutes ) )->text();
4814  if ( !in_array( $format['avoid'], [ 'avoidseconds', 'avoidminutes' ] ) ) {
4815  $s .= ' ' . $secondsMsg->params( $this->formatNum( $secondsPart ) )->text();
4816  }
4817  } else {
4818  $days = floor( $seconds / 86400 );
4819  if ( $format['avoid'] === 'avoidminutes' ) {
4820  $hours = round( ( $seconds - $days * 86400 ) / 3600 );
4821  if ( $hours == 24 ) {
4822  $hours = 0;
4823  $days++;
4824  }
4825  $s = $daysMsg->params( $this->formatNum( $days ) )->text();
4826  $s .= ' ';
4827  $s .= $hoursMsg->params( $this->formatNum( $hours ) )->text();
4828  } elseif ( $format['avoid'] === 'avoidseconds' ) {
4829  $hours = floor( ( $seconds - $days * 86400 ) / 3600 );
4830  $minutes = round( ( $seconds - $days * 86400 - $hours * 3600 ) / 60 );
4831  if ( $minutes == 60 ) {
4832  $minutes = 0;
4833  $hours++;
4834  }
4835  if ( $hours == 24 ) {
4836  $hours = 0;
4837  $days++;
4838  }
4839  $s = $daysMsg->params( $this->formatNum( $days ) )->text();
4840  $s .= ' ';
4841  $s .= $hoursMsg->params( $this->formatNum( $hours ) )->text();
4842  $s .= ' ';
4843  $s .= $minutesMsg->params( $this->formatNum( $minutes ) )->text();
4844  } else {
4845  $s = $daysMsg->params( $this->formatNum( $days ) )->text();
4846  $s .= ' ';
4847  $s .= $this->formatTimePeriod( $seconds - $days * 86400, $format );
4848  }
4849  }
4850  return $s;
4851  }
4852 
4864  function formatBitrate( $bps ) {
4865  return $this->formatComputingNumbers( $bps, 1000, "bitrate-$1bits" );
4866  }
4867 
4874  function formatComputingNumbers( $size, $boundary, $messageKey ) {
4875  if ( $size <= 0 ) {
4876  return str_replace( '$1', $this->formatNum( $size ),
4877  $this->getMessageFromDB( str_replace( '$1', '', $messageKey ) )
4878  );
4879  }
4880  $sizes = [ '', 'kilo', 'mega', 'giga', 'tera', 'peta', 'exa', 'zeta', 'yotta' ];
4881  $index = 0;
4882 
4883  $maxIndex = count( $sizes ) - 1;
4884  while ( $size >= $boundary && $index < $maxIndex ) {
4885  $index++;
4886  $size /= $boundary;
4887  }
4888 
4889  // For small sizes no decimal places necessary
4890  $round = 0;
4891  if ( $index > 1 ) {
4892  // For MB and bigger two decimal places are smarter
4893  $round = 2;
4894  }
4895  $msg = str_replace( '$1', $sizes[$index], $messageKey );
4896 
4897  $size = round( $size, $round );
4898  $text = $this->getMessageFromDB( $msg );
4899  return str_replace( '$1', $this->formatNum( $size ), $text );
4900  }
4901 
4912  function formatSize( $size ) {
4913  return $this->formatComputingNumbers( $size, 1024, "size-$1bytes" );
4914  }
4915 
4925  function specialList( $page, $details, $oppositedm = true ) {
4926  if ( !$details ) {
4927  return $page;
4928  }
4929 
4930  $dirmark = ( $oppositedm ? $this->getDirMark( true ) : '' ) . $this->getDirMark();
4931  return $page .
4932  $dirmark .
4933  $this->msg( 'word-separator' )->escaped() .
4934  $this->msg( 'parentheses' )->rawParams( $details )->escaped();
4935  }
4936 
4947  public function viewPrevNext( Title $title, $offset, $limit,
4948  array $query = [], $atend = false
4949  ) {
4950  // @todo FIXME: Why on earth this needs one message for the text and another one for tooltip?
4951 
4952  # Make 'previous' link
4953  $prev = wfMessage( 'prevn' )->inLanguage( $this )->title( $title )->numParams( $limit )->text();
4954  if ( $offset > 0 ) {
4955  $plink = $this->numLink( $title, max( $offset - $limit, 0 ), $limit,
4956  $query, $prev, 'prevn-title', 'mw-prevlink' );
4957  } else {
4958  $plink = htmlspecialchars( $prev );
4959  }
4960 
4961  # Make 'next' link
4962  $next = wfMessage( 'nextn' )->inLanguage( $this )->title( $title )->numParams( $limit )->text();
4963  if ( $atend ) {
4964  $nlink = htmlspecialchars( $next );
4965  } else {
4966  $nlink = $this->numLink( $title, $offset + $limit, $limit,
4967  $query, $next, 'nextn-title', 'mw-nextlink' );
4968  }
4969 
4970  # Make links to set number of items per page
4971  $numLinks = [];
4972  foreach ( [ 20, 50, 100, 250, 500 ] as $num ) {
4973  $numLinks[] = $this->numLink( $title, $offset, $num,
4974  $query, $this->formatNum( $num ), 'shown-title', 'mw-numlink' );
4975  }
4976 
4977  return wfMessage( 'viewprevnext' )->inLanguage( $this )->title( $title
4978  )->rawParams( $plink, $nlink, $this->pipeList( $numLinks ) )->escaped();
4979  }
4980 
4993  private function numLink( Title $title, $offset, $limit, array $query, $link,
4994  $tooltipMsg, $class
4995  ) {
4996  $query = [ 'limit' => $limit, 'offset' => $offset ] + $query;
4997  $tooltip = wfMessage( $tooltipMsg )->inLanguage( $this )->title( $title )
4998  ->numParams( $limit )->text();
4999 
5000  return Html::element( 'a', [ 'href' => $title->getLocalURL( $query ),
5001  'title' => $tooltip, 'class' => $class ], $link );
5002  }
5003 
5009  public function getConvRuleTitle() {
5010  return $this->mConverter->getConvRuleTitle();
5011  }
5012 
5018  public function getCompiledPluralRules() {
5019  $pluralRules = self::$dataCache->getItem( strtolower( $this->mCode ), 'compiledPluralRules' );
5020  $fallbacks = self::getFallbacksFor( $this->mCode );
5021  if ( !$pluralRules ) {
5022  foreach ( $fallbacks as $fallbackCode ) {
5023  $pluralRules = self::$dataCache->getItem( strtolower( $fallbackCode ), 'compiledPluralRules' );
5024  if ( $pluralRules ) {
5025  break;
5026  }
5027  }
5028  }
5029  return $pluralRules;
5030  }
5031 
5037  public function getPluralRules() {
5038  $pluralRules = self::$dataCache->getItem( strtolower( $this->mCode ), 'pluralRules' );
5039  $fallbacks = self::getFallbacksFor( $this->mCode );
5040  if ( !$pluralRules ) {
5041  foreach ( $fallbacks as $fallbackCode ) {
5042  $pluralRules = self::$dataCache->getItem( strtolower( $fallbackCode ), 'pluralRules' );
5043  if ( $pluralRules ) {
5044  break;
5045  }
5046  }
5047  }
5048  return $pluralRules;
5049  }
5050 
5056  public function getPluralRuleTypes() {
5057  $pluralRuleTypes = self::$dataCache->getItem( strtolower( $this->mCode ), 'pluralRuleTypes' );
5058  $fallbacks = self::getFallbacksFor( $this->mCode );
5059  if ( !$pluralRuleTypes ) {
5060  foreach ( $fallbacks as $fallbackCode ) {
5061  $pluralRuleTypes = self::$dataCache->getItem( strtolower( $fallbackCode ), 'pluralRuleTypes' );
5062  if ( $pluralRuleTypes ) {
5063  break;
5064  }
5065  }
5066  }
5067  return $pluralRuleTypes;
5068  }
5069 
5075  public function getPluralRuleIndexNumber( $number ) {
5076  $pluralRules = $this->getCompiledPluralRules();
5077  $form = Evaluator::evaluateCompiled( $number, $pluralRules );
5078  return $form;
5079  }
5080 
5089  public function getPluralRuleType( $number ) {
5090  $index = $this->getPluralRuleIndexNumber( $number );
5091  $pluralRuleTypes = $this->getPluralRuleTypes();
5092  if ( isset( $pluralRuleTypes[$index] ) ) {
5093  return $pluralRuleTypes[$index];
5094  } else {
5095  return 'other';
5096  }
5097  }
5098 }
The wiki should then use memcached to cache various data To use multiple just add more items to the array To increase the weight of a make its entry a array("192.168.0.1:11211", 2))
static getSuggestedDurations(Language $lang=null, $includeOther=true)
Get an array of suggested block durations from MediaWiki:Ipboptions.
static newFromCode( $code, $fallback=false)
Create a language object for a given language code.
Definition: Language.php:239
getMessage( $key)
Definition: Language.php:2650
getPluralRuleType( $number)
Find the plural rule type appropriate for the given number For example, if the language is set to Ara...
Definition: Language.php:5089
getIranianCalendarMonthName( $key)
Definition: Language.php:1044
static $mMonthAbbrevMsgs
Definition: Language.php:114
$mParentLanguage
Definition: Language.php:62
getSpecialPageAliases()
Get special page names, as an associative array canonical name => array of valid names, including aliases.
Definition: Language.php:3273
getPluralRuleTypes()
Get the plural rule types for the language.
Definition: Language.php:5056
firstChar( $s)
Get the first character of a string.
Definition: Language.php:2965
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:2544
static getLocalisationCache()
Get the LocalisationCache instance.
Definition: Language.php:454
Wrapper around strtr() that holds replacements.
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
Definition: deferred.txt:11
doMagicHook()
Run the LanguageGetMagic hook once.
Definition: Language.php:3219
ucwords( $str)
Definition: Language.php:2790
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 hebrewNumeral( $num)
Hebrew Gematria number formatting up to 9999.
Definition: Language.php:2075
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:813
translateBlockExpiry( $str, User $user=null, $now=0)
Definition: Language.php:4144
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:1651
ucwordbreaksCallbackAscii( $matches)
Definition: Language.php:2684
static element( $element, $attribs=[], $contents='')
Identical to rawElement(), but HTML-escapes $contents (like Xml::element()).
Definition: Html.php:232
segmentByWord( $string)
Some languages such as Chinese require word segmentation, Specify such segmentation when overridden i...
Definition: Language.php:2900
normalize( $s)
Convert a UTF-8 string to normal form C.
Definition: Language.php:3056
handleExplicitPluralForms( $count, array $forms)
Handles explicit plural forms for Language::convertPlural()
Definition: Language.php:4073
static getCanonicalNamespaces( $rebuild=false)
Returns array of all defined namespaces with their canonical (English) names.
static getTitleInvalidRegex()
Returns a simple regex that will match on characters and sequences invalid in titles.
static LocalisationCache $dataCache
Definition: Language.php:79
initEncoding()
Definition: Language.php:3020
getDurationIntervals( $seconds, array $chosenIntervals=[])
Takes a number of seconds and returns an array with a set of corresponding intervals.
Definition: Language.php:2389
getVariantname( $code, $usemsg=true)
short names for language variants used for language conversion links.
Definition: Language.php:761
setNamespaces(array $namespaces)
Arbitrarily set all of the namespace names at once.
Definition: Language.php:552
static HashBagOStuff null $languageNameCache
Cache for language names.
Definition: Language.php:183
removeBadCharLast( $string)
Remove bytes that represent an incomplete Unicode character at the end of string (e.g.
Definition: Language.php:3679
static romanNumeral( $num)
Roman number formatting up to 10000.
Definition: Language.php:2044
getWeekdayAbbreviation( $key)
Definition: Language.php:1036
getDirMark( $opposite=false)
A hidden direction mark (LRM or RLM), depending on the language direction.
Definition: Language.php:3159
getHtmlCode()
Get the code in BCP 47 format which we can use inside of html lang="" tags.
Definition: Language.php:4493
static isUtf8( $value)
Test whether a string is valid UTF-8.
Definition: StringUtils.php:41
truncate_skip(&$ret, $text, $search, $start, $len=null)
truncateHtml() helper function like strcspn() but adds the skipped chars to $ret
Definition: Language.php:3849
$IP
Definition: WebStart.php:41
equals(Language $lang)
Compare with an other language object.
Definition: Language.php:4467
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:2060
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
lcfirst( $str)
Definition: Language.php:2747
static $IRANIAN_DAYS
Definition: Language.php:1628
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException' returning false will NOT prevent logging $e
Definition: hooks.txt:2237
offsetForUser(User $user)
Adjust the timestamp depending on the given user&#39;s preferences.
Definition: MWTimestamp.php:79
getHebrewCalendarMonthNameGen( $key)
Definition: Language.php:1060
alignEnd()
Return &#39;right&#39; or &#39;left&#39; as appropriate alignment for line-end for this language&#39;s text direction...
Definition: Language.php:3127
static insertSpace( $string, $pattern)
Definition: Language.php:2943
getFallbackLanguages()
Definition: Language.php:494
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:2525
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
getMonthNameGen( $key)
Definition: Language.php:1001
$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:69
hasVariants()
Check if this is a language with variants.
Definition: Language.php:4275
getOption( $oname, $defaultOverride=null, $ignoreHidden=false)
Get the user&#39;s current setting for a given option.
Definition: User.php:3150
getCompiledPluralRules()
Get the compiled plural rules for the language.
Definition: Language.php:5018
getLocalNsIndex( $text)
Get a namespace key by value, case insensitive.
Definition: Language.php:661
convertCategoryKey( $key)
Definition: Language.php:4305
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
getLocalURL( $query='', $query2=false)
Get a URL with no fragment or server name (relative URL) from a Title object.
Definition: Title.php:1980
truncateForDatabase( $string, $length, $ellipsis='...', $adjustLength=true)
Truncate a string to a specified length in bytes, appending an optional string (e.g.
Definition: Language.php:3570
getHebrewCalendarMonthName( $key)
Definition: Language.php:1052
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
This code would result in ircNotify being run twice when an article is and once for brion Hooks can return three possible true was required This is the default since MediaWiki *some string
Definition: hooks.txt:175
$value
static getCanonicalIndex( $name)
Returns the index for a given canonical name, or NULL The input must be converted to lower case first...
const ALL
Return all known languages in fetchLanguageName(s).
Definition: Language.php:46
$wgMetaNamespace
Name of the project namespace.
fallback8bitEncoding()
Definition: Language.php:2877
wfIsInfinity( $str)
Determine input string is represents as infinity.
unsegmentForDiff( $text)
and unsegment to show the result
Definition: Language.php:4197
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:3872
A fake language variant converter.
linkTrail()
A regular expression to match legal word-trailing characters which should be merged onto a link of th...
Definition: Language.php:4419
getAllMessages()
Definition: Language.php:2657
wfUrlProtocolsWithoutProtRel()
Like wfUrlProtocols(), but excludes &#39;//&#39; from the protocol list.
$wgNamespaceAliases
Namespace aliases.
wfLogWarning( $msg, $callerOffset=1, $level=E_USER_WARNING)
Send a warning as a PHP error and the debug log.
getPluralRuleIndexNumber( $number)
Find the index number of the plural rule appropriate for the given number.
Definition: Language.php:5075
getVariants()
Get the list of variants supported by this language see sample implementation in LanguageZh.php.
Definition: Language.php:4315
A helper class for throttling authentication attempts.
$dateFormatStrings
Definition: Language.php:64
static hebrewYearStart( $year)
This calculates the Hebrew year start, as days since 1 September.
Definition: Language.php:1895
getNamespaceAliases()
Definition: Language.php:670
getWeekdayName( $key)
Definition: Language.php:1028
minimumGroupingDigits()
Definition: Language.php:3465
static $mMonthMsgs
Definition: Language.php:104
convertForSearchResult( $termsArray)
Definition: Language.php:2953
$wgExtraGenderNamespaces
Same as above, but for namespaces with gender distinction.
static getFileName( $prefix, $code, $suffix='.php')
Get the name of a file for a certain language code.
Definition: Language.php:4548
convertTitle( $title)
Convert a Title object to a string in the preferred variant.
Definition: Language.php:4254
static $mLangObjCache
Definition: Language.php:81
static classFromCode( $code, $fallback=true)
Definition: Language.php:4532
internalUserTimeAndDate( $type, $ts, User $user, array $options)
Internal helper function for userDate(), userTime() and userTimeAndDate()
Definition: Language.php:2440
$wgTranslateNumerals
For Hindi and Arabic use local numerals instead of Western style (0-9) numerals in interface...
getHijriCalendarMonthName( $key)
Definition: Language.php:1068
static $mWeekdayAbbrevMsgs
Definition: Language.php:100
getMonthAbbreviation( $key)
Definition: Language.php:1009
lc( $str, $first=false)
Definition: Language.php:2766
ucwordbreaksCallbackMB( $matches)
Definition: Language.php:2692
$mMagicExtensions
Definition: Language.php:61
static $pdf
Definition: Language.php:190
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:411
usually copyright or history_copyright This message must be in HTML not wikitext & $link
Definition: hooks.txt:3121
The User object encapsulates all of the user-specific settings (user_id, name, rights, email address, options, last login time).
Definition: User.php:46
__construct()
Definition: Language.php:463
truncateHtml( $text, $length, $ellipsis='...')
Truncate a string of valid HTML to a specified length in bytes, appending an optional string (e...
Definition: Language.php:3731
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:2060
ucfirst( $str)
Make a string&#39;s first character uppercase.
Definition: Language.php:2711
$wgLanguageCode
Site language code.
semicolonList(array $list)
Take a list of strings and build a locale-friendly semicolon-separated list, using the local semicolo...
Definition: Language.php:3514
userAdjust( $ts, $tz=false)
Used by date() and time() to adjust the time output.
Definition: Language.php:2162
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
static getMessageFor( $key, $code)
Get a message for a given language.
Definition: Language.php:4679
static $lre
Unicode directional formatting characters, for embedBidi()
Definition: Language.php:188
formatNum( $number, $nocommafy=false)
Normally we output all numbers in plain en_US style, that is 293,291.235 for twohundredninetythreetho...
Definition: Language.php:3318
static getFallbackFor( $code)
Get the first fallback for a given language.
Definition: Language.php:4590
timeanddate( $ts, $adj=false, $format=true, $timecorrection=false)
Definition: Language.php:2344
formatExpiry( $expiry, $format=true, $infinity='infinity')
Decode an expiry (block, protection, etc) which has come from the DB.
Definition: Language.php:4734
listToText(array $list)
Take a list of strings and build a locale-friendly comma-separated list, using the local comma-separa...
Definition: Language.php:3477
static tsToYear( $ts, $cName)
Algorithm to convert Gregorian dates to Thai solar dates, Minguo dates or Minguo dates.
Definition: Language.php:1933
const NS_PROJECT
Definition: Defines.php:68
static $mHijriCalendarMonthMsgs
Definition: Language.php:142
linkPrefixCharset()
A regular expression character set to match legal word-prefixing characters which should be merged on...
Definition: Language.php:4429
either a unescaped string or a HtmlArmor object after in associative array form externallinks including delete and has completed for all link tables whether this was an auto creation use $formDescriptor instead default is conds Array Extra conditions for the No matching items in log is displayed if loglist is empty msgKey Array If you want a nice box with a set this to the key of the message First element is the message additional optional elements are parameters for the key that are processed with wfMessage() -> params() ->parseAsBlock() - offset Set to overwrite offset parameter in $wgRequest set to '' to unset offset - wrap String Wrap the message in html(usually something like "&lt
static getMain()
Get the RequestContext object associated with the main request.
getPreferredVariant()
Definition: Language.php:4322
LanguageConverter $mConverter
Definition: Language.php:58
recodeForEdit( $s)
Definition: Language.php:3030
static getMessagesFor( $code)
Get all messages for a given language WARNING: this may take a long time.
Definition: Language.php:4667
$minimumGroupingDigits
removeBadCharFirst( $string)
Remove bytes that represent an incomplete Unicode character at the start of string (e...
Definition: Language.php:3705
getDirMarkEntity( $opposite=false)
A hidden direction mark (LRM or RLM), depending on the language direction.
Definition: Language.php:3142
__destruct()
Reduce memory usage.
Definition: Language.php:477
static strongDirFromContent( $text='')
Gets directionality of the first strongly directional codepoint, for embedBidi()
Definition: Language.php:2027
getMagic( $mw)
Fill a MagicWord object with data from here.
Definition: Language.php:3232
static getMessageKeysFor( $code)
Get all message keys for a given language.
Definition: Language.php:4691
$wgLocalisationCacheConf
Localisation cache configuration.
static getFallbacksIncludingSiteLanguage( $code)
Get the ordered list of fallback languages, ending with the fallback language chain for the site lang...
Definition: Language.php:4636
getDefaultVariant()
Definition: Language.php:4329
getNsText( $index)
Get a namespace value by key.
Definition: Language.php:591
const STRICT_FALLBACKS
Return a strict fallback chain in getFallbacksFor.
Definition: Language.php:93
viewPrevNext(Title $title, $offset, $limit, array $query=[], $atend=false)
Generate (prev x| next x) (20|50|100...) type links for paging.
Definition: Language.php:4947
getConverter()
Return the LanguageConverter used in the Language.
Definition: Language.php:4207
static $mIranianCalendarMonthMsgs
Definition: Language.php:119
const NS_PROJECT_TALK
Definition: Defines.php:69
autoConvertToAllVariants( $text)
convert text to all supported variants
Definition: Language.php:4229
getBookstoreList()
Exports $wgBookstoreListEn.
Definition: Language.php:502
capitalizeAllNouns()
Definition: Language.php:3171
static decode( $value, $assoc=false)
Decodes a JSON string.
Definition: FormatJson.php:164
$wgDummyLanguageCodes
Functionally the same as $wgExtraLanguageCodes, but deprecated.
emphasize( $text)
Italic is unsuitable for some languages.
Definition: Language.php:3292
date( $ts, $adj=false, $format=true, $timecorrection=false)
Definition: Language.php:2305
isMultibyte( $str)
Definition: Language.php:2782
segmentForDiff( $text)
languages like Chinese need to be segmented in order for the diff to be of any use ...
Definition: Language.php:4187
$wgAllUnicodeFixes
Set this to always convert certain Unicode sequences to modern ones regardless of the content languag...
$cache
Definition: mcc.php:33
normalizeForSearch( $string)
Some languages have special punctuation need to be normalized.
Definition: Language.php:2911
static $strongDirRegex
Directionality test regex for embedBidi().
Definition: Language.php:205
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped & $options
Definition: hooks.txt:2060
static tsToHijri( $ts)
Converting Gregorian dates to Hijri dates.
Definition: Language.php:1702
needsGenderDistinction()
Whether this language uses gender-dependent namespace aliases.
Definition: Language.php:637
getURLVariant()
Definition: Language.php:4336
caseFold( $s)
Return a case-folded representation of $s.
Definition: Language.php:2854
updateConversionTable(Title $title)
Refresh the cache of conversion tables when MediaWiki:Conversiontable* is updated.
Definition: Language.php:4383
static $rle
Definition: Language.php:189
$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:964
static tsToIranian( $ts)
Algorithm by Roozbeh Pournader and Mohammad Toossi to convert Gregorian dates to Iranian dates...
Definition: Language.php:1642
getNamespaceIds()
Definition: Language.php:714
static factory( $code)
Get a cached or new language object for a given language code.
Definition: Language.php:214
dateFormat( $usePrefs=true)
This is meant to be used by time(), date(), and timeanddate() to get the date preference they&#39;re supp...
Definition: Language.php:2240
static $mHebrewCalendarMonthMsgs
Definition: Language.php:126
pipeList(array $list)
Same as commaList, but separate it with the pipe instead.
Definition: Language.php:3526
getGenderNsText( $index, $gender)
Returns gender-dependent namespace alias if available.
Definition: Language.php:622
getFormattedNsText( $index)
A convenience function that returns the same thing as getNsText() except with &#39;_&#39; changed to &#39; &#39;...
Definition: Language.php:609
$mExtendedSpecialPageAliases
Definition: Language.php:65
namespace and then decline to actually register it & $namespaces
Definition: hooks.txt:964
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:813
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:5037
getDefaultDateFormat()
Definition: Language.php:792
embedBidi( $text='')
Wraps argument with unicode control characters for directionality safety.
Definition: Language.php:4117
resetNamespaces()
Resets all of the namespace caches.
Definition: Language.php:560
recodeInput( $s)
Definition: Language.php:3040
getDateFormatString( $type, $pref)
Get a format string for a given type and preference.
Definition: Language.php:2271
static MapCacheLRU null $grammarTransformations
Cache for grammar rules data.
Definition: Language.php:177
time( $ts, $adj=false, $format=true, $timecorrection=false)
Definition: Language.php:2324
getMagicWords()
Get all magic words from cache.
Definition: Language.php:3212
getGrammarTransformations()
Get the grammar transformations data for the language.
Definition: Language.php:3965
getMonthNamesArray()
Definition: Language.php:989
numLink(Title $title, $offset, $limit, array $query, $link, $tooltipMsg, $class)
Helper function for viewPrevNext() that generates links.
Definition: Language.php:4993
static getMessagesFileName( $code)
Definition: Language.php:4560
static getDefaultOption( $opt)
Get a given default option value.
Definition: User.php:1807
convertGrammar( $word, $case)
Grammatical transformations, needed for inflected languages Invoked by putting {{grammar:case|word}} ...
Definition: Language.php:3894
digitTransformTable()
Definition: Language.php:3451
getConvRuleTitle()
Get the conversion rule title, if any.
Definition: Language.php:5009
formatTimePeriod( $seconds, $format=[])
Formats a time given in seconds into a string representation of that time.
Definition: Language.php:4764
truncate( $string, $length, $ellipsis='...', $adjustLength=true)
This method is deprecated since 1.31 and kept as alias for truncateForDatabase, which has replaced it...
Definition: Language.php:3550
userDate( $ts, User $user, array $options=[])
Get the formatted date for the given timestamp and formatted for the given user.
Definition: Language.php:2479
getNsIndex( $text)
Get a namespace key by value, case insensitive.
Definition: Language.php:744
addMagicWordsByLang( $newWords)
Add magic words to the extension array.
Definition: Language.php:3258
autoConvert( $text, $variant=false)
convert text to a variant
Definition: Language.php:4219
static fetchLanguageName( $code, $inLanguage=self::AS_AUTONYMS, $include=self::ALL)
Definition: Language.php:948
static getJsonMessagesFileName( $code)
Definition: Language.php:4573
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:332
ucwordbreaks( $str)
capitalize words at word breaks
Definition: Language.php:2814
$wgExtraNamespaces
Additional namespaces.
static convertDoubleWidth( $string)
convert double-width roman characters to single-width.
Definition: Language.php:2923
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:2581
getCode()
Get the internal language code for this language object.
Definition: Language.php:4479
digitGroupingPattern()
Definition: Language.php:3444
specialList( $page, $details, $oppositedm=true)
Make a list item, used by various special pages.
Definition: Language.php:4925
static isKnownLanguageTag( $tag)
Returns true if a language code is an IETF tag known to MediaWiki.
Definition: Language.php:433
Using a hook running we can avoid having all this option specific stuff in our mainline code Using the function 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
$fallback
Definition: MessagesAb.php:11
$digitGroupingPattern
Definition: MessagesAs.php:167
static isSupportedLanguage( $code)
Checks whether any localisation is available for that language tag in MediaWiki (MessagesXx.php exists).
Definition: Language.php:304
convertNamespace( $ns, $variant=null)
Convert a namespace index to a string in the preferred variant.
Definition: Language.php:4266
getArrow( $direction='forwards')
An arrow, depending on the language direction.
Definition: Language.php:3182
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:4912
you have access to all of the normal MediaWiki so you can get a DB use the etc For full docs on the Maintenance class
Definition: maintenance.txt:52
array null $namespaceNames
Definition: Language.php:68
commafy( $number)
Adds commas to a given number.
Definition: Language.php:3379
$transformData
ReplacementArray object caches.
Definition: Language.php:74
static getCodeFromFileName( $filename, $prefix='Language', $suffix='.php')
Get the language code from a file name.
Definition: Language.php:4517
getExtraHashOptions()
returns language specific options used by User::getPageRenderHash() for example, the preferred langua...
Definition: Language.php:4362
static getFallbacksFor( $code, $mode=self::MESSAGES_FALLBACKS)
Get the ordered list of fallback languages.
Definition: Language.php:4608
getParsedTitle()
For languages that support multiple variants, the title of an article may be displayed differently in...
Definition: Language.php:4373
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
static tsToHebrew( $ts)
Converting Gregorian dates to Hebrew dates.
Definition: Language.php:1754
preConvertPlural($forms, $count)
Checks that convertPlural was given an array and pads it to requested amount of forms by copying the ...
Definition: Language.php:4094
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:4352
$wgGrammarForms
Some languages need different word forms, usually for different cases.
const SUPPORTED
Return in fetchLanguageName(s) only the languages for which we have at least some localisation...
Definition: Language.php:53
hasVariant( $variant)
Check if the language has the specific variant.
Definition: Language.php:4286
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:4864
const AS_AUTONYMS
Return autonyms in fetchLanguageName(s).
Definition: Language.php:40
transformUsingPairFile( $file, $string)
Transform a string using serialized data stored in the given file (which must be in the serialized su...
Definition: Language.php:3081
separatorTransformTable()
Definition: Language.php:3458
isRTL()
For right-to-left language support.
Definition: Language.php:3095
getDatePreference()
Get the user&#39;s preferred date format.
Definition: User.php:3473
sprintfDate( $format, $ts, DateTimeZone $zone=null, &$ttl='unused')
This is a workalike of PHP&#39;s date() function, but with better internationalisation, a reduced set of format characters, and a better escaping format.
Definition: Language.php:1158
getParentLanguage()
Get the "parent" language which has a converter to convert a "compatible" language (in another varian...
Definition: Language.php:4440
static clearCaches()
Intended for tests that may change configuration in a way that invalidates caches.
Definition: Language.php:283
getDir()
Return the correct HTML &#39;dir&#39; attribute value for this language.
Definition: Language.php:3103
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:302
getFormattedNamespaces()
A convenience function that returns getNamespaces() with spaces instead of underscores in values...
Definition: Language.php:572
$wgLocalTZoffset
Set an offset from UTC in minutes to use for the default timezone setting for anonymous users and new...
setCode( $code)
Definition: Language.php:4503
iconv( $in, $out, $string)
Definition: Language.php:2667
static $mMonthGenMsgs
Definition: Language.php:109
static $mWeekdayMsgs
Definition: Language.php:95
linkPrefixExtension()
To allow "foo[[bar]]" to extend the link over the whole word "foobar".
Definition: Language.php:3204
msg( $msg)
Get message object in this language.
Definition: Language.php:974
const MESSAGES_FALLBACKS
Return a fallback chain for messages in getFallbacksFor.
Definition: Language.php:87
const DB_REPLICA
Definition: defines.php:25
$mNamespaceIds
Definition: Language.php:69
uc( $str, $first=false)
Convert a string to uppercase.
Definition: Language.php:2731
getGrammarForms()
Get the grammar forms for the content language.
Definition: Language.php:3945
fixVariableInNamespace( $talk)
Definition: Language.php:4699
markNoConversion( $text, $noParse=false)
Prepare external link text for conversion.
Definition: Language.php:4403
static dateTimeObjFormat(&$dateTimeObj, $ts, $zone, $code)
Pass through result from $dateTimeObj->format()
Definition: Language.php:1080
=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
formatComputingNumbers( $size, $boundary, $messageKey)
Definition: Language.php:4874
initContLang()
Hook which will be called if this is the content language.
Definition: Language.php:487
userTime( $ts, User $user, array $options=[])
Get the formatted time for the given timestamp and formatted for the given user.
Definition: Language.php:2502
ucwordsCallbackMB( $matches)
Definition: Language.php:2700
getNamespaces()
Returns an array of localised namespaces indexed by their numbers.
Definition: Language.php:512
$mMagicHookDone
Definition: Language.php:61
static isValidCode( $code)
Returns true if a language code string is of a valid form, whether or not it exists.
Definition: Language.php:386
commaList(array $list)
Take a list of strings and build a locale-friendly comma-separated list, using the local comma-separa...
Definition: Language.php:3501
static $mHebrewCalendarMonthGenMsgs
Definition: Language.php:134
const NS_USER_TALK
Definition: Defines.php:67
static array $languagesWithVariants
languages supporting variants
truncateForVisual( $string, $length, $ellipsis='...', $adjustLength=true)
Truncate a string to a specified number of characters, appending an optional string (e...
Definition: Language.php:3594
gender( $gender, $forms)
Provides an alternative text depending on specified gender.
Definition: Language.php:4014
truncateInternal( $string, $length, $ellipsis, $adjustLength, $measureLength, $getSubstring)
Internal method used for truncation.
Definition: Language.php:3620
getMessageFromDB( $msg)
Get a message from the MediaWiki namespace.
Definition: Language.php:964
getMonthAbbreviationsArray()
Definition: Language.php:1016
hasWordBreaks()
Most writing systems use whitespace to break up words.
Definition: Language.php:2889
convert( $text)
convert text to different variants of a language.
Definition: Language.php:4244
alignStart()
Return &#39;left&#39; or &#39;right&#39; as appropriate alignment for line-start for this language&#39;s text direction...
Definition: Language.php:3115
see documentation in includes Linker php for Linker::makeImageLink & $time
Definition: hooks.txt:1865
checkTitleEncoding( $s)
Definition: Language.php:2863
static bcp47( $code)
Get the normalised IETF language tag See unit test for examples.
convertPlural( $count, $forms)
Plural form transformations, needed for some languages.
Definition: Language.php:4043
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:244
formatNumNoSeparators( $number)
Front-end for non-commafied formatNum.
Definition: Language.php:3346
parseFormattedNumber( $number)
Definition: Language.php:3354
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:200
replaceGrammarInNamespace( $m)
Definition: Language.php:4720
static $GREG_DAYS
Definition: Language.php:1627
getMonthName( $key)
Definition: Language.php:982
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:2363
convertHtml( $text, $isTitle=false)
Perform output conversion on a string, and encode for safe HTML output.
Definition: Language.php:4297
$matches