MediaWiki  master
Language.php
Go to the documentation of this file.
1 <?php
32 
37 class Language {
42  const AS_AUTONYMS = null;
43 
48  const ALL = 'all';
49 
55  const SUPPORTED = 'mwfile';
56 
60  public $mConverter;
61 
62  public $mVariants, $mCode, $mLoaded = false;
63  public $mMagicExtensions = [];
64  private $mHtmlCode = null, $mParentLanguage = false;
65 
66  public $dateFormatStrings = [];
68 
70  protected $namespaceNames;
72 
76  public $transformData = [];
77 
81  public static $dataCache;
82 
83  public static $mLangObjCache = [];
84 
89  const MESSAGES_FALLBACKS = 0;
90 
95  const STRICT_FALLBACKS = 1;
96 
97  public static $mWeekdayMsgs = [
98  'sunday', 'monday', 'tuesday', 'wednesday', 'thursday',
99  'friday', 'saturday'
100  ];
101 
102  public static $mWeekdayAbbrevMsgs = [
103  'sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'
104  ];
105 
106  public static $mMonthMsgs = [
107  'january', 'february', 'march', 'april', 'may_long', 'june',
108  'july', 'august', 'september', 'october', 'november',
109  'december'
110  ];
111  public static $mMonthGenMsgs = [
112  'january-gen', 'february-gen', 'march-gen', 'april-gen', 'may-gen', 'june-gen',
113  'july-gen', 'august-gen', 'september-gen', 'october-gen', 'november-gen',
114  'december-gen'
115  ];
116  public static $mMonthAbbrevMsgs = [
117  'jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug',
118  'sep', 'oct', 'nov', 'dec'
119  ];
120 
121  public static $mIranianCalendarMonthMsgs = [
122  'iranian-calendar-m1', 'iranian-calendar-m2', 'iranian-calendar-m3',
123  'iranian-calendar-m4', 'iranian-calendar-m5', 'iranian-calendar-m6',
124  'iranian-calendar-m7', 'iranian-calendar-m8', 'iranian-calendar-m9',
125  'iranian-calendar-m10', 'iranian-calendar-m11', 'iranian-calendar-m12'
126  ];
127 
128  public static $mHebrewCalendarMonthMsgs = [
129  'hebrew-calendar-m1', 'hebrew-calendar-m2', 'hebrew-calendar-m3',
130  'hebrew-calendar-m4', 'hebrew-calendar-m5', 'hebrew-calendar-m6',
131  'hebrew-calendar-m7', 'hebrew-calendar-m8', 'hebrew-calendar-m9',
132  'hebrew-calendar-m10', 'hebrew-calendar-m11', 'hebrew-calendar-m12',
133  'hebrew-calendar-m6a', 'hebrew-calendar-m6b'
134  ];
135 
137  'hebrew-calendar-m1-gen', 'hebrew-calendar-m2-gen', 'hebrew-calendar-m3-gen',
138  'hebrew-calendar-m4-gen', 'hebrew-calendar-m5-gen', 'hebrew-calendar-m6-gen',
139  'hebrew-calendar-m7-gen', 'hebrew-calendar-m8-gen', 'hebrew-calendar-m9-gen',
140  'hebrew-calendar-m10-gen', 'hebrew-calendar-m11-gen', 'hebrew-calendar-m12-gen',
141  'hebrew-calendar-m6a-gen', 'hebrew-calendar-m6b-gen'
142  ];
143 
144  public static $mHijriCalendarMonthMsgs = [
145  'hijri-calendar-m1', 'hijri-calendar-m2', 'hijri-calendar-m3',
146  'hijri-calendar-m4', 'hijri-calendar-m5', 'hijri-calendar-m6',
147  'hijri-calendar-m7', 'hijri-calendar-m8', 'hijri-calendar-m9',
148  'hijri-calendar-m10', 'hijri-calendar-m11', 'hijri-calendar-m12'
149  ];
150 
155  public static $durationIntervals = [
156  'millennia' => 31556952000,
157  'centuries' => 3155695200,
158  'decades' => 315569520,
159  'years' => 31556952, // 86400 * ( 365 + ( 24 * 3 + 25 ) / 400 )
160  'weeks' => 604800,
161  'days' => 86400,
162  'hours' => 3600,
163  'minutes' => 60,
164  'seconds' => 1,
165  ];
166 
173  private static $fallbackLanguageCache = [];
174 
179  private static $grammarTransformations;
180 
185  private static $languageNameCache;
186 
190  private static $lre = "\u{202A}"; // U+202A LEFT-TO-RIGHT EMBEDDING
191  private static $rle = "\u{202B}"; // U+202B RIGHT-TO-LEFT EMBEDDING
192  private static $pdf = "\u{202C}"; // U+202C POP DIRECTIONAL FORMATTING
193 
205  // @codeCoverageIgnoreStart
206  // phpcs:ignore Generic.Files.LineLength
207  private static $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';
208  // @codeCoverageIgnoreEnd
209 
216  static function factory( $code ) {
218 
219  if ( isset( $wgDummyLanguageCodes[$code] ) ) {
220  $code = $wgDummyLanguageCodes[$code];
221  }
222 
223  // get the language object to process
224  $langObj = self::$mLangObjCache[$code] ?? self::newFromCode( $code );
225 
226  // merge the language object in to get it up front in the cache
227  self::$mLangObjCache = array_merge( [ $code => $langObj ], self::$mLangObjCache );
228  // get rid of the oldest ones in case we have an overflow
229  self::$mLangObjCache = array_slice( self::$mLangObjCache, 0, $wgLangObjCacheSize, true );
230 
231  return $langObj;
232  }
233 
241  protected static function newFromCode( $code, $fallback = false ) {
242  if ( !self::isValidCode( $code ) ) {
243  throw new MWException( "Invalid language code \"$code\"" );
244  }
245 
246  if ( !self::isValidBuiltInCode( $code ) ) {
247  // It's not possible to customise this code with class files, so
248  // just return a Language object. This is to support uselang= hacks.
249  $lang = new Language;
250  $lang->mCode = $code;
251  return $lang;
252  }
253 
254  // Check if there is a language class for the code
255  $class = self::classFromCode( $code, $fallback );
256  // LanguageCode does not inherit Language
257  if ( class_exists( $class ) && is_a( $class, 'Language', true ) ) {
258  $lang = new $class;
259  return $lang;
260  }
261 
262  // Keep trying the fallback list until we find an existing class
263  $fallbacks = self::getFallbacksFor( $code );
264  foreach ( $fallbacks as $fallbackCode ) {
265  if ( !self::isValidBuiltInCode( $fallbackCode ) ) {
266  throw new MWException( "Invalid fallback '$fallbackCode' in fallback sequence for '$code'" );
267  }
268 
269  $class = self::classFromCode( $fallbackCode );
270  if ( class_exists( $class ) ) {
271  $lang = new $class;
272  $lang->mCode = $code;
273  return $lang;
274  }
275  }
276 
277  throw new MWException( "Invalid fallback sequence for language '$code'" );
278  }
279 
285  public static function clearCaches() {
286  if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
287  throw new MWException( __METHOD__ . ' must not be used outside tests' );
288  }
289  self::$dataCache = null;
290  // Reinitialize $dataCache, since it's expected to always be available
291  self::getLocalisationCache();
292  self::$mLangObjCache = [];
293  self::$fallbackLanguageCache = [];
294  self::$grammarTransformations = null;
295  self::$languageNameCache = null;
296  }
297 
306  public static function isSupportedLanguage( $code ) {
307  if ( !self::isValidBuiltInCode( $code ) ) {
308  return false;
309  }
310 
311  if ( $code === 'qqq' ) {
312  return false;
313  }
314 
315  return is_readable( self::getMessagesFileName( $code ) ) ||
316  is_readable( self::getJsonMessagesFileName( $code ) );
317  }
318 
334  public static function isWellFormedLanguageTag( $code, $lenient = false ) {
335  $alpha = '[a-z]';
336  $digit = '[0-9]';
337  $alphanum = '[a-z0-9]';
338  $x = 'x'; # private use singleton
339  $singleton = '[a-wy-z]'; # other singleton
340  $s = $lenient ? '[-_]' : '-';
341 
342  $language = "$alpha{2,8}|$alpha{2,3}$s$alpha{3}";
343  $script = "$alpha{4}"; # ISO 15924
344  $region = "(?:$alpha{2}|$digit{3})"; # ISO 3166-1 alpha-2 or UN M.49
345  $variant = "(?:$alphanum{5,8}|$digit$alphanum{3})";
346  $extension = "$singleton(?:$s$alphanum{2,8})+";
347  $privateUse = "$x(?:$s$alphanum{1,8})+";
348 
349  # Define certain grandfathered codes, since otherwise the regex is pretty useless.
350  # Since these are limited, this is safe even later changes to the registry --
351  # the only oddity is that it might change the type of the tag, and thus
352  # the results from the capturing groups.
353  # https://www.iana.org/assignments/language-subtag-registry
354 
355  $grandfathered = "en{$s}GB{$s}oed"
356  . "|i{$s}(?:ami|bnn|default|enochian|hak|klingon|lux|mingo|navajo|pwn|tao|tay|tsu)"
357  . "|no{$s}(?:bok|nyn)"
358  . "|sgn{$s}(?:BE{$s}(?:fr|nl)|CH{$s}de)"
359  . "|zh{$s}min{$s}nan";
360 
361  $variantList = "$variant(?:$s$variant)*";
362  $extensionList = "$extension(?:$s$extension)*";
363 
364  $langtag = "(?:($language)"
365  . "(?:$s$script)?"
366  . "(?:$s$region)?"
367  . "(?:$s$variantList)?"
368  . "(?:$s$extensionList)?"
369  . "(?:$s$privateUse)?)";
370 
371  # The final breakdown, with capturing groups for each of these components
372  # The variants, extensions, grandfathered, and private-use may have interior '-'
373 
374  $root = "^(?:$langtag|$privateUse|$grandfathered)$";
375 
376  return (bool)preg_match( "/$root/", strtolower( $code ) );
377  }
378 
388  public static function isValidCode( $code ) {
389  static $cache = [];
390  Assert::parameterType( 'string', $code, '$code' );
391  if ( !isset( $cache[$code] ) ) {
392  // People think language codes are html safe, so enforce it.
393  // Ideally we should only allow a-zA-Z0-9-
394  // but, .+ and other chars are often used for {{int:}} hacks
395  // see bugs T39564, T39587, T38938
396  $cache[$code] =
397  // Protect against path traversal
398  strcspn( $code, ":/\\\000&<>'\"" ) === strlen( $code )
399  && !preg_match( MediaWikiTitleCodec::getTitleInvalidRegex(), $code );
400  }
401  return $cache[$code];
402  }
403 
413  public static function isValidBuiltInCode( $code ) {
414  Assert::parameterType( 'string', $code, '$code' );
415 
416  return (bool)preg_match( '/^[a-z0-9-]{2,}$/', $code );
417  }
418 
427  public static function isKnownLanguageTag( $tag ) {
428  // Quick escape for invalid input to avoid exceptions down the line
429  // when code tries to process tags which are not valid at all.
430  if ( !self::isValidBuiltInCode( $tag ) ) {
431  return false;
432  }
433 
434  if ( isset( MediaWiki\Languages\Data\Names::$names[$tag] )
435  || self::fetchLanguageName( $tag, $tag ) !== ''
436  ) {
437  return true;
438  }
439 
440  return false;
441  }
442 
448  public static function getLocalisationCache() {
449  if ( is_null( self::$dataCache ) ) {
451  $class = $wgLocalisationCacheConf['class'];
452  self::$dataCache = new $class( $wgLocalisationCacheConf );
453  }
454  return self::$dataCache;
455  }
456 
457  function __construct() {
458  $this->mConverter = new FakeConverter( $this );
459  // Set the code to the name of the descendant
460  if ( static::class === 'Language' ) {
461  $this->mCode = 'en';
462  } else {
463  $this->mCode = str_replace( '_', '-', strtolower( substr( static::class, 8 ) ) );
464  }
465  self::getLocalisationCache();
466  }
467 
471  function __destruct() {
472  foreach ( $this as $name => $value ) {
473  unset( $this->$name );
474  }
475  }
476 
481  function initContLang() {
482  }
483 
488  public function getFallbackLanguages() {
489  return self::getFallbacksFor( $this->mCode );
490  }
491 
496  public function getBookstoreList() {
497  return self::$dataCache->getItem( $this->mCode, 'bookstoreList' );
498  }
499 
506  public function getNamespaces() {
507  if ( is_null( $this->namespaceNames ) ) {
509 
510  $validNamespaces = MediaWikiServices::getInstance()->getNamespaceInfo()->
511  getCanonicalNamespaces();
512 
513  $this->namespaceNames = $wgExtraNamespaces +
514  self::$dataCache->getItem( $this->mCode, 'namespaceNames' );
515  $this->namespaceNames += $validNamespaces;
516 
517  $this->namespaceNames[NS_PROJECT] = $wgMetaNamespace;
518  if ( $wgMetaNamespaceTalk ) {
519  $this->namespaceNames[NS_PROJECT_TALK] = $wgMetaNamespaceTalk;
520  } else {
521  $talk = $this->namespaceNames[NS_PROJECT_TALK];
522  $this->namespaceNames[NS_PROJECT_TALK] =
523  $this->fixVariableInNamespace( $talk );
524  }
525 
526  # Sometimes a language will be localised but not actually exist on this wiki.
527  foreach ( $this->namespaceNames as $key => $text ) {
528  if ( !isset( $validNamespaces[$key] ) ) {
529  unset( $this->namespaceNames[$key] );
530  }
531  }
532 
533  # The above mixing may leave namespaces out of canonical order.
534  # Re-order by namespace ID number...
535  ksort( $this->namespaceNames );
536 
537  Hooks::run( 'LanguageGetNamespaces', [ &$this->namespaceNames ] );
538  }
539 
540  return $this->namespaceNames;
541  }
542 
547  public function setNamespaces( array $namespaces ) {
548  $this->namespaceNames = $namespaces;
549  $this->mNamespaceIds = null;
550  }
551 
555  public function resetNamespaces() {
556  $this->namespaceNames = null;
557  $this->mNamespaceIds = null;
558  $this->namespaceAliases = null;
559  }
560 
567  public function getFormattedNamespaces() {
568  $ns = $this->getNamespaces();
569  foreach ( $ns as $k => $v ) {
570  $ns[$k] = strtr( $v, '_', ' ' );
571  }
572  return $ns;
573  }
574 
586  public function getNsText( $index ) {
587  $ns = $this->getNamespaces();
588  return $ns[$index] ?? false;
589  }
590 
604  public function getFormattedNsText( $index ) {
605  $ns = $this->getNsText( $index );
606  return strtr( $ns, '_', ' ' );
607  }
608 
617  public function getGenderNsText( $index, $gender ) {
619 
620  $ns = $wgExtraGenderNamespaces +
621  (array)self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' );
622 
623  return $ns[$index][$gender] ?? $this->getNsText( $index );
624  }
625 
632  public function needsGenderDistinction() {
634  if ( count( $wgExtraGenderNamespaces ) > 0 ) {
635  // $wgExtraGenderNamespaces overrides everything
636  return true;
637  } elseif ( isset( $wgExtraNamespaces[NS_USER] ) && isset( $wgExtraNamespaces[NS_USER_TALK] ) ) {
639  // $wgExtraNamespaces overrides any gender aliases specified in i18n files
640  return false;
641  } else {
642  // Check what is in i18n files
643  $aliases = self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' );
644  return count( $aliases ) > 0;
645  }
646  }
647 
656  function getLocalNsIndex( $text ) {
657  $lctext = $this->lc( $text );
658  $ids = $this->getNamespaceIds();
659  return $ids[$lctext] ?? false;
660  }
661 
665  public function getNamespaceAliases() {
666  if ( is_null( $this->namespaceAliases ) ) {
667  $aliases = self::$dataCache->getItem( $this->mCode, 'namespaceAliases' );
668  if ( !$aliases ) {
669  $aliases = [];
670  } else {
671  foreach ( $aliases as $name => $index ) {
672  if ( $index === NS_PROJECT_TALK ) {
673  unset( $aliases[$name] );
674  $name = $this->fixVariableInNamespace( $name );
675  $aliases[$name] = $index;
676  }
677  }
678  }
679 
681  $genders = $wgExtraGenderNamespaces +
682  (array)self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' );
683  foreach ( $genders as $index => $forms ) {
684  foreach ( $forms as $alias ) {
685  $aliases[$alias] = $index;
686  }
687  }
688 
689  # Also add converted namespace names as aliases, to avoid confusion.
690  $convertedNames = [];
691  foreach ( $this->getVariants() as $variant ) {
692  if ( $variant === $this->mCode ) {
693  continue;
694  }
695  foreach ( $this->getNamespaces() as $ns => $_ ) {
696  $convertedNames[$this->getConverter()->convertNamespace( $ns, $variant )] = $ns;
697  }
698  }
699 
700  $this->namespaceAliases = $aliases + $convertedNames;
701 
702  # Filter out aliases to namespaces that don't exist, e.g. from extensions
703  # that aren't loaded here but are included in the l10n cache.
704  # (array_intersect preserves keys from its first argument)
705  $this->namespaceAliases = array_intersect(
706  $this->namespaceAliases,
707  array_keys( $this->getNamespaces() )
708  );
709  }
710 
712  }
713 
717  public function getNamespaceIds() {
718  if ( is_null( $this->mNamespaceIds ) ) {
719  global $wgNamespaceAliases;
720  # Put namespace names and aliases into a hashtable.
721  # If this is too slow, then we should arrange it so that it is done
722  # before caching. The catch is that at pre-cache time, the above
723  # class-specific fixup hasn't been done.
724  $this->mNamespaceIds = [];
725  foreach ( $this->getNamespaces() as $index => $name ) {
726  $this->mNamespaceIds[$this->lc( $name )] = $index;
727  }
728  foreach ( $this->getNamespaceAliases() as $name => $index ) {
729  $this->mNamespaceIds[$this->lc( $name )] = $index;
730  }
731  if ( $wgNamespaceAliases ) {
732  foreach ( $wgNamespaceAliases as $name => $index ) {
733  $this->mNamespaceIds[$this->lc( $name )] = $index;
734  }
735  }
736  }
737  return $this->mNamespaceIds;
738  }
739 
747  public function getNsIndex( $text ) {
748  $lctext = $this->lc( $text );
749  $ns = MediaWikiServices::getInstance()->getNamespaceInfo()->
750  getCanonicalIndex( $lctext );
751  if ( $ns !== null ) {
752  return $ns;
753  }
754  $ids = $this->getNamespaceIds();
755  return $ids[$lctext] ?? false;
756  }
757 
765  public function getVariantname( $code, $usemsg = true ) {
766  $msg = "variantname-$code";
767  if ( $usemsg && wfMessage( $msg )->exists() ) {
768  return $this->getMessageFromDB( $msg );
769  }
770  $name = self::fetchLanguageName( $code );
771  if ( $name ) {
772  return $name; # if it's defined as a language name, show that
773  } else {
774  # otherwise, output the language code
775  return $code;
776  }
777  }
778 
782  public function getDatePreferences() {
783  return self::$dataCache->getItem( $this->mCode, 'datePreferences' );
784  }
785 
789  function getDateFormats() {
790  return self::$dataCache->getItem( $this->mCode, 'dateFormats' );
791  }
792 
796  public function getDefaultDateFormat() {
797  $df = self::$dataCache->getItem( $this->mCode, 'defaultDateFormat' );
798  if ( $df === 'dmy or mdy' ) {
799  global $wgAmericanDates;
800  return $wgAmericanDates ? 'mdy' : 'dmy';
801  } else {
802  return $df;
803  }
804  }
805 
809  public function getDatePreferenceMigrationMap() {
810  return self::$dataCache->getItem( $this->mCode, 'datePreferenceMigrationMap' );
811  }
812 
816  public function getExtraUserToggles() {
817  wfDeprecated( __METHOD__, '1.34' );
818  return [];
819  }
820 
825  function getUserToggle( $tog ) {
826  return $this->getMessageFromDB( "tog-$tog" );
827  }
828 
840  public static function fetchLanguageNames( $inLanguage = self::AS_AUTONYMS, $include = 'mw' ) {
841  $cacheKey = $inLanguage === self::AS_AUTONYMS ? 'null' : $inLanguage;
842  $cacheKey .= ":$include";
843  if ( self::$languageNameCache === null ) {
844  self::$languageNameCache = new HashBagOStuff( [ 'maxKeys' => 20 ] );
845  }
846 
847  $ret = self::$languageNameCache->get( $cacheKey );
848  if ( !$ret ) {
849  $ret = self::fetchLanguageNamesUncached( $inLanguage, $include );
850  self::$languageNameCache->set( $cacheKey, $ret );
851  }
852  return $ret;
853  }
854 
865  private static function fetchLanguageNamesUncached(
866  $inLanguage = self::AS_AUTONYMS,
867  $include = 'mw'
868  ) {
869  global $wgExtraLanguageNames, $wgUsePigLatinVariant;
870 
871  // If passed an invalid language code to use, fallback to en
872  if ( $inLanguage !== self::AS_AUTONYMS && !self::isValidCode( $inLanguage ) ) {
873  $inLanguage = 'en';
874  }
875 
876  $names = [];
877 
878  if ( $inLanguage ) {
879  # TODO: also include when $inLanguage is null, when this code is more efficient
880  Hooks::run( 'LanguageGetTranslatedLanguageNames', [ &$names, $inLanguage ] );
881  }
882 
883  $mwNames = $wgExtraLanguageNames + MediaWiki\Languages\Data\Names::$names;
884  if ( $wgUsePigLatinVariant ) {
885  // Pig Latin (for variant development)
886  $mwNames['en-x-piglatin'] = 'Igpay Atinlay';
887  }
888 
889  foreach ( $mwNames as $mwCode => $mwName ) {
890  # - Prefer own MediaWiki native name when not using the hook
891  # - For other names just add if not added through the hook
892  if ( $mwCode === $inLanguage || !isset( $names[$mwCode] ) ) {
893  $names[$mwCode] = $mwName;
894  }
895  }
896 
897  if ( $include === self::ALL ) {
898  ksort( $names );
899  return $names;
900  }
901 
902  $returnMw = [];
903  $coreCodes = array_keys( $mwNames );
904  foreach ( $coreCodes as $coreCode ) {
905  $returnMw[$coreCode] = $names[$coreCode];
906  }
907 
908  if ( $include === self::SUPPORTED ) {
909  $namesMwFile = [];
910  # We do this using a foreach over the codes instead of a directory
911  # loop so that messages files in extensions will work correctly.
912  foreach ( $returnMw as $code => $value ) {
913  if ( is_readable( self::getMessagesFileName( $code ) )
914  || is_readable( self::getJsonMessagesFileName( $code ) )
915  ) {
916  $namesMwFile[$code] = $names[$code];
917  }
918  }
919 
920  ksort( $namesMwFile );
921  return $namesMwFile;
922  }
923 
924  ksort( $returnMw );
925  # 'mw' option; default if it's not one of the other two options (all/mwfile)
926  return $returnMw;
927  }
928 
937  public static function fetchLanguageName(
938  $code,
939  $inLanguage = self::AS_AUTONYMS,
940  $include = self::ALL
941  ) {
942  $code = strtolower( $code );
943  $array = self::fetchLanguageNames( $inLanguage, $include );
944  return !array_key_exists( $code, $array ) ? '' : $array[$code];
945  }
946 
953  public function getMessageFromDB( $msg ) {
954  return $this->msg( $msg )->text();
955  }
956 
963  protected function msg( $msg ) {
964  return wfMessage( $msg )->inLanguage( $this );
965  }
966 
971  public function getMonthName( $key ) {
972  return $this->getMessageFromDB( self::$mMonthMsgs[$key - 1] );
973  }
974 
978  public function getMonthNamesArray() {
979  $monthNames = [ '' ];
980  for ( $i = 1; $i < 13; $i++ ) {
981  $monthNames[] = $this->getMonthName( $i );
982  }
983  return $monthNames;
984  }
985 
990  public function getMonthNameGen( $key ) {
991  return $this->getMessageFromDB( self::$mMonthGenMsgs[$key - 1] );
992  }
993 
998  public function getMonthAbbreviation( $key ) {
999  return $this->getMessageFromDB( self::$mMonthAbbrevMsgs[$key - 1] );
1000  }
1001 
1005  public function getMonthAbbreviationsArray() {
1006  $monthNames = [ '' ];
1007  for ( $i = 1; $i < 13; $i++ ) {
1008  $monthNames[] = $this->getMonthAbbreviation( $i );
1009  }
1010  return $monthNames;
1011  }
1012 
1017  public function getWeekdayName( $key ) {
1018  return $this->getMessageFromDB( self::$mWeekdayMsgs[$key - 1] );
1019  }
1020 
1025  function getWeekdayAbbreviation( $key ) {
1026  return $this->getMessageFromDB( self::$mWeekdayAbbrevMsgs[$key - 1] );
1027  }
1028 
1033  function getIranianCalendarMonthName( $key ) {
1034  return $this->getMessageFromDB( self::$mIranianCalendarMonthMsgs[$key - 1] );
1035  }
1036 
1041  function getHebrewCalendarMonthName( $key ) {
1042  return $this->getMessageFromDB( self::$mHebrewCalendarMonthMsgs[$key - 1] );
1043  }
1044 
1050  return $this->getMessageFromDB( self::$mHebrewCalendarMonthGenMsgs[$key - 1] );
1051  }
1052 
1057  function getHijriCalendarMonthName( $key ) {
1058  return $this->getMessageFromDB( self::$mHijriCalendarMonthMsgs[$key - 1] );
1059  }
1060 
1069  private static function dateTimeObjFormat( &$dateTimeObj, $ts, $zone, $code ) {
1070  if ( !$dateTimeObj ) {
1071  $dateTimeObj = DateTime::createFromFormat(
1072  'YmdHis', $ts, $zone ?: new DateTimeZone( 'UTC' )
1073  );
1074  }
1075  return $dateTimeObj->format( $code );
1076  }
1077 
1147  public function sprintfDate( $format, $ts, DateTimeZone $zone = null, &$ttl = 'unused' ) {
1148  $s = '';
1149  $raw = false;
1150  $roman = false;
1151  $hebrewNum = false;
1152  $dateTimeObj = false;
1153  $rawToggle = false;
1154  $iranian = false;
1155  $hebrew = false;
1156  $hijri = false;
1157  $thai = false;
1158  $minguo = false;
1159  $tenno = false;
1160 
1161  $usedSecond = false;
1162  $usedMinute = false;
1163  $usedHour = false;
1164  $usedAMPM = false;
1165  $usedDay = false;
1166  $usedWeek = false;
1167  $usedMonth = false;
1168  $usedYear = false;
1169  $usedISOYear = false;
1170  $usedIsLeapYear = false;
1171 
1172  $usedHebrewMonth = false;
1173  $usedIranianMonth = false;
1174  $usedHijriMonth = false;
1175  $usedHebrewYear = false;
1176  $usedIranianYear = false;
1177  $usedHijriYear = false;
1178  $usedTennoYear = false;
1179 
1180  if ( strlen( $ts ) !== 14 ) {
1181  throw new MWException( __METHOD__ . ": The timestamp $ts should have 14 characters" );
1182  }
1183 
1184  if ( !ctype_digit( $ts ) ) {
1185  throw new MWException( __METHOD__ . ": The timestamp $ts should be a number" );
1186  }
1187 
1188  $formatLength = strlen( $format );
1189  for ( $p = 0; $p < $formatLength; $p++ ) {
1190  $num = false;
1191  $code = $format[$p];
1192  if ( $code == 'x' && $p < $formatLength - 1 ) {
1193  $code .= $format[++$p];
1194  }
1195 
1196  if ( ( $code === 'xi'
1197  || $code === 'xj'
1198  || $code === 'xk'
1199  || $code === 'xm'
1200  || $code === 'xo'
1201  || $code === 'xt' )
1202  && $p < $formatLength - 1 ) {
1203  $code .= $format[++$p];
1204  }
1205 
1206  switch ( $code ) {
1207  case 'xx':
1208  $s .= 'x';
1209  break;
1210  case 'xn':
1211  $raw = true;
1212  break;
1213  case 'xN':
1214  $rawToggle = !$rawToggle;
1215  break;
1216  case 'xr':
1217  $roman = true;
1218  break;
1219  case 'xh':
1220  $hebrewNum = true;
1221  break;
1222  case 'xg':
1223  $usedMonth = true;
1224  $s .= $this->getMonthNameGen( substr( $ts, 4, 2 ) );
1225  break;
1226  case 'xjx':
1227  $usedHebrewMonth = true;
1228  if ( !$hebrew ) {
1229  $hebrew = self::tsToHebrew( $ts );
1230  }
1231  $s .= $this->getHebrewCalendarMonthNameGen( $hebrew[1] );
1232  break;
1233  case 'd':
1234  $usedDay = true;
1235  $num = substr( $ts, 6, 2 );
1236  break;
1237  case 'D':
1238  $usedDay = true;
1239  $s .= $this->getWeekdayAbbreviation(
1240  self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'w' ) + 1
1241  );
1242  break;
1243  case 'j':
1244  $usedDay = true;
1245  $num = intval( substr( $ts, 6, 2 ) );
1246  break;
1247  case 'xij':
1248  $usedDay = true;
1249  if ( !$iranian ) {
1250  $iranian = self::tsToIranian( $ts );
1251  }
1252  $num = $iranian[2];
1253  break;
1254  case 'xmj':
1255  $usedDay = true;
1256  if ( !$hijri ) {
1257  $hijri = self::tsToHijri( $ts );
1258  }
1259  $num = $hijri[2];
1260  break;
1261  case 'xjj':
1262  $usedDay = true;
1263  if ( !$hebrew ) {
1264  $hebrew = self::tsToHebrew( $ts );
1265  }
1266  $num = $hebrew[2];
1267  break;
1268  case 'l':
1269  $usedDay = true;
1270  $s .= $this->getWeekdayName(
1271  self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'w' ) + 1
1272  );
1273  break;
1274  case 'F':
1275  $usedMonth = true;
1276  $s .= $this->getMonthName( substr( $ts, 4, 2 ) );
1277  break;
1278  case 'xiF':
1279  $usedIranianMonth = true;
1280  if ( !$iranian ) {
1281  $iranian = self::tsToIranian( $ts );
1282  }
1283  $s .= $this->getIranianCalendarMonthName( $iranian[1] );
1284  break;
1285  case 'xmF':
1286  $usedHijriMonth = true;
1287  if ( !$hijri ) {
1288  $hijri = self::tsToHijri( $ts );
1289  }
1290  $s .= $this->getHijriCalendarMonthName( $hijri[1] );
1291  break;
1292  case 'xjF':
1293  $usedHebrewMonth = true;
1294  if ( !$hebrew ) {
1295  $hebrew = self::tsToHebrew( $ts );
1296  }
1297  $s .= $this->getHebrewCalendarMonthName( $hebrew[1] );
1298  break;
1299  case 'm':
1300  $usedMonth = true;
1301  $num = substr( $ts, 4, 2 );
1302  break;
1303  case 'M':
1304  $usedMonth = true;
1305  $s .= $this->getMonthAbbreviation( substr( $ts, 4, 2 ) );
1306  break;
1307  case 'n':
1308  $usedMonth = true;
1309  $num = intval( substr( $ts, 4, 2 ) );
1310  break;
1311  case 'xin':
1312  $usedIranianMonth = true;
1313  if ( !$iranian ) {
1314  $iranian = self::tsToIranian( $ts );
1315  }
1316  $num = $iranian[1];
1317  break;
1318  case 'xmn':
1319  $usedHijriMonth = true;
1320  if ( !$hijri ) {
1321  $hijri = self::tsToHijri( $ts );
1322  }
1323  $num = $hijri[1];
1324  break;
1325  case 'xjn':
1326  $usedHebrewMonth = true;
1327  if ( !$hebrew ) {
1328  $hebrew = self::tsToHebrew( $ts );
1329  }
1330  $num = $hebrew[1];
1331  break;
1332  case 'xjt':
1333  $usedHebrewMonth = true;
1334  if ( !$hebrew ) {
1335  $hebrew = self::tsToHebrew( $ts );
1336  }
1337  $num = $hebrew[3];
1338  break;
1339  case 'Y':
1340  $usedYear = true;
1341  $num = substr( $ts, 0, 4 );
1342  break;
1343  case 'xiY':
1344  $usedIranianYear = true;
1345  if ( !$iranian ) {
1346  $iranian = self::tsToIranian( $ts );
1347  }
1348  $num = $iranian[0];
1349  break;
1350  case 'xmY':
1351  $usedHijriYear = true;
1352  if ( !$hijri ) {
1353  $hijri = self::tsToHijri( $ts );
1354  }
1355  $num = $hijri[0];
1356  break;
1357  case 'xjY':
1358  $usedHebrewYear = true;
1359  if ( !$hebrew ) {
1360  $hebrew = self::tsToHebrew( $ts );
1361  }
1362  $num = $hebrew[0];
1363  break;
1364  case 'xkY':
1365  $usedYear = true;
1366  if ( !$thai ) {
1367  $thai = self::tsToYear( $ts, 'thai' );
1368  }
1369  $num = $thai[0];
1370  break;
1371  case 'xoY':
1372  $usedYear = true;
1373  if ( !$minguo ) {
1374  $minguo = self::tsToYear( $ts, 'minguo' );
1375  }
1376  $num = $minguo[0];
1377  break;
1378  case 'xtY':
1379  $usedTennoYear = true;
1380  if ( !$tenno ) {
1381  $tenno = self::tsToYear( $ts, 'tenno' );
1382  }
1383  $num = $tenno[0];
1384  break;
1385  case 'y':
1386  $usedYear = true;
1387  $num = substr( $ts, 2, 2 );
1388  break;
1389  case 'xiy':
1390  $usedIranianYear = true;
1391  if ( !$iranian ) {
1392  $iranian = self::tsToIranian( $ts );
1393  }
1394  $num = substr( $iranian[0], -2 );
1395  break;
1396  case 'xit':
1397  $usedIranianYear = true;
1398  if ( !$iranian ) {
1399  $iranian = self::tsToIranian( $ts );
1400  }
1401  $num = self::$IRANIAN_DAYS[$iranian[1] - 1];
1402  break;
1403  case 'xiz':
1404  $usedIranianYear = true;
1405  if ( !$iranian ) {
1406  $iranian = self::tsToIranian( $ts );
1407  }
1408  $num = $iranian[3];
1409  break;
1410  case 'a':
1411  $usedAMPM = true;
1412  $s .= intval( substr( $ts, 8, 2 ) ) < 12 ? 'am' : 'pm';
1413  break;
1414  case 'A':
1415  $usedAMPM = true;
1416  $s .= intval( substr( $ts, 8, 2 ) ) < 12 ? 'AM' : 'PM';
1417  break;
1418  case 'g':
1419  $usedHour = true;
1420  $h = substr( $ts, 8, 2 );
1421  $num = $h % 12 ?: 12;
1422  break;
1423  case 'G':
1424  $usedHour = true;
1425  $num = intval( substr( $ts, 8, 2 ) );
1426  break;
1427  case 'h':
1428  $usedHour = true;
1429  $h = substr( $ts, 8, 2 );
1430  $num = sprintf( '%02d', $h % 12 ?: 12 );
1431  break;
1432  case 'H':
1433  $usedHour = true;
1434  $num = substr( $ts, 8, 2 );
1435  break;
1436  case 'i':
1437  $usedMinute = true;
1438  $num = substr( $ts, 10, 2 );
1439  break;
1440  case 's':
1441  $usedSecond = true;
1442  $num = substr( $ts, 12, 2 );
1443  break;
1444  case 'c':
1445  case 'r':
1446  $usedSecond = true;
1447  // fall through
1448  case 'e':
1449  case 'O':
1450  case 'P':
1451  case 'T':
1452  $s .= self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1453  break;
1454  case 'w':
1455  case 'N':
1456  case 'z':
1457  $usedDay = true;
1458  $num = self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1459  break;
1460  case 'W':
1461  $usedWeek = true;
1462  $num = self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1463  break;
1464  case 't':
1465  $usedMonth = true;
1466  $num = self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1467  break;
1468  case 'L':
1469  $usedIsLeapYear = true;
1470  $num = self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1471  break;
1472  case 'o':
1473  $usedISOYear = true;
1474  $num = self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1475  break;
1476  case 'U':
1477  $usedSecond = true;
1478  // fall through
1479  case 'I':
1480  case 'Z':
1481  $num = self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1482  break;
1483  case '\\':
1484  # Backslash escaping
1485  if ( $p < $formatLength - 1 ) {
1486  $s .= $format[++$p];
1487  } else {
1488  $s .= '\\';
1489  }
1490  break;
1491  case '"':
1492  # Quoted literal
1493  if ( $p < $formatLength - 1 ) {
1494  $endQuote = strpos( $format, '"', $p + 1 );
1495  if ( $endQuote === false ) {
1496  # No terminating quote, assume literal "
1497  $s .= '"';
1498  } else {
1499  $s .= substr( $format, $p + 1, $endQuote - $p - 1 );
1500  $p = $endQuote;
1501  }
1502  } else {
1503  # Quote at end of string, assume literal "
1504  $s .= '"';
1505  }
1506  break;
1507  default:
1508  $s .= $format[$p];
1509  }
1510  if ( $num !== false ) {
1511  if ( $rawToggle || $raw ) {
1512  $s .= $num;
1513  $raw = false;
1514  } elseif ( $roman ) {
1515  $s .= self::romanNumeral( $num );
1516  $roman = false;
1517  } elseif ( $hebrewNum ) {
1518  $s .= self::hebrewNumeral( $num );
1519  $hebrewNum = false;
1520  } else {
1521  $s .= $this->formatNum( $num, true );
1522  }
1523  }
1524  }
1525 
1526  if ( $ttl === 'unused' ) {
1527  // No need to calculate the TTL, the caller wont use it anyway.
1528  } elseif ( $usedSecond ) {
1529  $ttl = 1;
1530  } elseif ( $usedMinute ) {
1531  $ttl = 60 - substr( $ts, 12, 2 );
1532  } elseif ( $usedHour ) {
1533  $ttl = 3600 - substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
1534  } elseif ( $usedAMPM ) {
1535  $ttl = 43200 - ( substr( $ts, 8, 2 ) % 12 ) * 3600 -
1536  substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
1537  } elseif (
1538  $usedDay ||
1539  $usedHebrewMonth ||
1540  $usedIranianMonth ||
1541  $usedHijriMonth ||
1542  $usedHebrewYear ||
1543  $usedIranianYear ||
1544  $usedHijriYear ||
1545  $usedTennoYear
1546  ) {
1547  // @todo Someone who understands the non-Gregorian calendars
1548  // should write proper logic for them so that they don't need purged every day.
1549  $ttl = 86400 - substr( $ts, 8, 2 ) * 3600 -
1550  substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
1551  } else {
1552  $possibleTtls = [];
1553  $timeRemainingInDay = 86400 - substr( $ts, 8, 2 ) * 3600 -
1554  substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
1555  if ( $usedWeek ) {
1556  $possibleTtls[] =
1557  ( 7 - self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'N' ) ) * 86400 +
1558  $timeRemainingInDay;
1559  } elseif ( $usedISOYear ) {
1560  // December 28th falls on the last ISO week of the year, every year.
1561  // The last ISO week of a year can be 52 or 53.
1562  $lastWeekOfISOYear = DateTime::createFromFormat(
1563  'Ymd',
1564  substr( $ts, 0, 4 ) . '1228',
1565  $zone ?: new DateTimeZone( 'UTC' )
1566  )->format( 'W' );
1567  $currentISOWeek = self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'W' );
1568  $weeksRemaining = $lastWeekOfISOYear - $currentISOWeek;
1569  $timeRemainingInWeek =
1570  ( 7 - self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'N' ) ) * 86400
1571  + $timeRemainingInDay;
1572  $possibleTtls[] = $weeksRemaining * 604800 + $timeRemainingInWeek;
1573  }
1574 
1575  if ( $usedMonth ) {
1576  $possibleTtls[] =
1577  ( self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 't' ) -
1578  substr( $ts, 6, 2 ) ) * 86400
1579  + $timeRemainingInDay;
1580  } elseif ( $usedYear ) {
1581  $possibleTtls[] =
1582  ( self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'L' ) + 364 -
1583  self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'z' ) ) * 86400
1584  + $timeRemainingInDay;
1585  } elseif ( $usedIsLeapYear ) {
1586  $year = substr( $ts, 0, 4 );
1587  $timeRemainingInYear =
1588  ( self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'L' ) + 364 -
1589  self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'z' ) ) * 86400
1590  + $timeRemainingInDay;
1591  $mod = $year % 4;
1592  if ( $mod || ( !( $year % 100 ) && $year % 400 ) ) {
1593  // this isn't a leap year. see when the next one starts
1594  $nextCandidate = $year - $mod + 4;
1595  if ( $nextCandidate % 100 || !( $nextCandidate % 400 ) ) {
1596  $possibleTtls[] = ( $nextCandidate - $year - 1 ) * 365 * 86400 +
1597  $timeRemainingInYear;
1598  } else {
1599  $possibleTtls[] = ( $nextCandidate - $year + 3 ) * 365 * 86400 +
1600  $timeRemainingInYear;
1601  }
1602  } else {
1603  // this is a leap year, so the next year isn't
1604  $possibleTtls[] = $timeRemainingInYear;
1605  }
1606  }
1607 
1608  if ( $possibleTtls ) {
1609  $ttl = min( $possibleTtls );
1610  }
1611  }
1612 
1613  return $s;
1614  }
1615 
1616  private static $GREG_DAYS = [ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ];
1617  private static $IRANIAN_DAYS = [ 31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29 ];
1618 
1631  private static function tsToIranian( $ts ) {
1632  $gy = substr( $ts, 0, 4 ) - 1600;
1633  $gm = substr( $ts, 4, 2 ) - 1;
1634  $gd = substr( $ts, 6, 2 ) - 1;
1635 
1636  # Days passed from the beginning (including leap years)
1637  $gDayNo = 365 * $gy
1638  + floor( ( $gy + 3 ) / 4 )
1639  - floor( ( $gy + 99 ) / 100 )
1640  + floor( ( $gy + 399 ) / 400 );
1641 
1642  // Add days of the past months of this year
1643  for ( $i = 0; $i < $gm; $i++ ) {
1644  $gDayNo += self::$GREG_DAYS[$i];
1645  }
1646 
1647  // Leap years
1648  if ( $gm > 1 && ( ( $gy % 4 === 0 && $gy % 100 !== 0 || ( $gy % 400 == 0 ) ) ) ) {
1649  $gDayNo++;
1650  }
1651 
1652  // Days passed in current month
1653  $gDayNo += (int)$gd;
1654 
1655  $jDayNo = $gDayNo - 79;
1656 
1657  $jNp = floor( $jDayNo / 12053 );
1658  $jDayNo %= 12053;
1659 
1660  $jy = 979 + 33 * $jNp + 4 * floor( $jDayNo / 1461 );
1661  $jDayNo %= 1461;
1662 
1663  if ( $jDayNo >= 366 ) {
1664  $jy += floor( ( $jDayNo - 1 ) / 365 );
1665  $jDayNo = floor( ( $jDayNo - 1 ) % 365 );
1666  }
1667 
1668  $jz = $jDayNo;
1669 
1670  for ( $i = 0; $i < 11 && $jDayNo >= self::$IRANIAN_DAYS[$i]; $i++ ) {
1671  $jDayNo -= self::$IRANIAN_DAYS[$i];
1672  }
1673 
1674  $jm = $i + 1;
1675  $jd = $jDayNo + 1;
1676 
1677  return [ $jy, $jm, $jd, $jz ];
1678  }
1679 
1691  private static function tsToHijri( $ts ) {
1692  $year = substr( $ts, 0, 4 );
1693  $month = substr( $ts, 4, 2 );
1694  $day = substr( $ts, 6, 2 );
1695 
1696  $zyr = $year;
1697  $zd = $day;
1698  $zm = $month;
1699  $zy = $zyr;
1700 
1701  if (
1702  ( $zy > 1582 ) || ( ( $zy == 1582 ) && ( $zm > 10 ) ) ||
1703  ( ( $zy == 1582 ) && ( $zm == 10 ) && ( $zd > 14 ) )
1704  ) {
1705  $zjd = (int)( ( 1461 * ( $zy + 4800 + (int)( ( $zm - 14 ) / 12 ) ) ) / 4 ) +
1706  (int)( ( 367 * ( $zm - 2 - 12 * ( (int)( ( $zm - 14 ) / 12 ) ) ) ) / 12 ) -
1707  (int)( ( 3 * (int)( ( ( $zy + 4900 + (int)( ( $zm - 14 ) / 12 ) ) / 100 ) ) ) / 4 ) +
1708  $zd - 32075;
1709  } else {
1710  $zjd = 367 * $zy - (int)( ( 7 * ( $zy + 5001 + (int)( ( $zm - 9 ) / 7 ) ) ) / 4 ) +
1711  (int)( ( 275 * $zm ) / 9 ) + $zd + 1729777;
1712  }
1713 
1714  $zl = $zjd - 1948440 + 10632;
1715  $zn = (int)( ( $zl - 1 ) / 10631 );
1716  $zl = $zl - 10631 * $zn + 354;
1717  $zj = ( (int)( ( 10985 - $zl ) / 5316 ) ) * ( (int)( ( 50 * $zl ) / 17719 ) ) +
1718  ( (int)( $zl / 5670 ) ) * ( (int)( ( 43 * $zl ) / 15238 ) );
1719  $zl = $zl - ( (int)( ( 30 - $zj ) / 15 ) ) * ( (int)( ( 17719 * $zj ) / 50 ) ) -
1720  ( (int)( $zj / 16 ) ) * ( (int)( ( 15238 * $zj ) / 43 ) ) + 29;
1721  $zm = (int)( ( 24 * $zl ) / 709 );
1722  $zd = $zl - (int)( ( 709 * $zm ) / 24 );
1723  $zy = 30 * $zn + $zj - 30;
1724 
1725  return [ $zy, $zm, $zd ];
1726  }
1727 
1743  private static function tsToHebrew( $ts ) {
1744  # Parse date
1745  $year = substr( $ts, 0, 4 );
1746  $month = substr( $ts, 4, 2 );
1747  $day = substr( $ts, 6, 2 );
1748 
1749  # Calculate Hebrew year
1750  $hebrewYear = $year + 3760;
1751 
1752  # Month number when September = 1, August = 12
1753  $month += 4;
1754  if ( $month > 12 ) {
1755  # Next year
1756  $month -= 12;
1757  $year++;
1758  $hebrewYear++;
1759  }
1760 
1761  # Calculate day of year from 1 September
1762  $dayOfYear = $day;
1763  for ( $i = 1; $i < $month; $i++ ) {
1764  if ( $i == 6 ) {
1765  # February
1766  $dayOfYear += 28;
1767  # Check if the year is leap
1768  if ( $year % 400 == 0 || ( $year % 4 == 0 && $year % 100 > 0 ) ) {
1769  $dayOfYear++;
1770  }
1771  } elseif ( $i == 8 || $i == 10 || $i == 1 || $i == 3 ) {
1772  $dayOfYear += 30;
1773  } else {
1774  $dayOfYear += 31;
1775  }
1776  }
1777 
1778  # Calculate the start of the Hebrew year
1779  $start = self::hebrewYearStart( $hebrewYear );
1780 
1781  # Calculate next year's start
1782  if ( $dayOfYear <= $start ) {
1783  # Day is before the start of the year - it is the previous year
1784  # Next year's start
1785  $nextStart = $start;
1786  # Previous year
1787  $year--;
1788  $hebrewYear--;
1789  # Add days since previous year's 1 September
1790  $dayOfYear += 365;
1791  if ( ( $year % 400 == 0 ) || ( $year % 100 != 0 && $year % 4 == 0 ) ) {
1792  # Leap year
1793  $dayOfYear++;
1794  }
1795  # Start of the new (previous) year
1796  $start = self::hebrewYearStart( $hebrewYear );
1797  } else {
1798  # Next year's start
1799  $nextStart = self::hebrewYearStart( $hebrewYear + 1 );
1800  }
1801 
1802  # Calculate Hebrew day of year
1803  $hebrewDayOfYear = $dayOfYear - $start;
1804 
1805  # Difference between year's days
1806  $diff = $nextStart - $start;
1807  # Add 12 (or 13 for leap years) days to ignore the difference between
1808  # Hebrew and Gregorian year (353 at least vs. 365/6) - now the
1809  # difference is only about the year type
1810  if ( ( $year % 400 == 0 ) || ( $year % 100 != 0 && $year % 4 == 0 ) ) {
1811  $diff += 13;
1812  } else {
1813  $diff += 12;
1814  }
1815 
1816  # Check the year pattern, and is leap year
1817  # 0 means an incomplete year, 1 means a regular year, 2 means a complete year
1818  # This is mod 30, to work on both leap years (which add 30 days of Adar I)
1819  # and non-leap years
1820  $yearPattern = $diff % 30;
1821  # Check if leap year
1822  $isLeap = $diff >= 30;
1823 
1824  # Calculate day in the month from number of day in the Hebrew year
1825  # Don't check Adar - if the day is not in Adar, we will stop before;
1826  # if it is in Adar, we will use it to check if it is Adar I or Adar II
1827  $hebrewDay = $hebrewDayOfYear;
1828  $hebrewMonth = 1;
1829  $days = 0;
1830  while ( $hebrewMonth <= 12 ) {
1831  # Calculate days in this month
1832  if ( $isLeap && $hebrewMonth == 6 ) {
1833  # Leap year - has Adar I, with 30 days, and Adar II, with 29 days
1834  $days = 30;
1835  if ( $hebrewDay <= $days ) {
1836  # Day in Adar I
1837  $hebrewMonth = 13;
1838  } else {
1839  # Subtract the days of Adar I
1840  $hebrewDay -= $days;
1841  # Try Adar II
1842  $days = 29;
1843  if ( $hebrewDay <= $days ) {
1844  # Day in Adar II
1845  $hebrewMonth = 14;
1846  }
1847  }
1848  } elseif ( $hebrewMonth == 2 && $yearPattern == 2 ) {
1849  # Cheshvan in a complete year (otherwise as the rule below)
1850  $days = 30;
1851  } elseif ( $hebrewMonth == 3 && $yearPattern == 0 ) {
1852  # Kislev in an incomplete year (otherwise as the rule below)
1853  $days = 29;
1854  } else {
1855  # Odd months have 30 days, even have 29
1856  $days = 30 - ( $hebrewMonth - 1 ) % 2;
1857  }
1858  if ( $hebrewDay <= $days ) {
1859  # In the current month
1860  break;
1861  } else {
1862  # Subtract the days of the current month
1863  $hebrewDay -= $days;
1864  # Try in the next month
1865  $hebrewMonth++;
1866  }
1867  }
1868 
1869  return [ $hebrewYear, $hebrewMonth, $hebrewDay, $days ];
1870  }
1871 
1881  private static function hebrewYearStart( $year ) {
1882  $a = intval( ( 12 * ( $year - 1 ) + 17 ) % 19 );
1883  $b = intval( ( $year - 1 ) % 4 );
1884  $m = 32.044093161144 + 1.5542417966212 * $a + $b / 4.0 - 0.0031777940220923 * ( $year - 1 );
1885  if ( $m < 0 ) {
1886  $m--;
1887  }
1888  $Mar = intval( $m );
1889  if ( $m < 0 ) {
1890  $m++;
1891  }
1892  $m -= $Mar;
1893 
1894  $c = intval( ( $Mar + 3 * ( $year - 1 ) + 5 * $b + 5 ) % 7 );
1895  if ( $c == 0 && $a > 11 && $m >= 0.89772376543210 ) {
1896  $Mar++;
1897  } elseif ( $c == 1 && $a > 6 && $m >= 0.63287037037037 ) {
1898  $Mar += 2;
1899  } elseif ( $c == 2 || $c == 4 || $c == 6 ) {
1900  $Mar++;
1901  }
1902 
1903  $Mar += intval( ( $year - 3761 ) / 100 ) - intval( ( $year - 3761 ) / 400 ) - 24;
1904  return $Mar;
1905  }
1906 
1919  private static function tsToYear( $ts, $cName ) {
1920  $gy = substr( $ts, 0, 4 );
1921  $gm = substr( $ts, 4, 2 );
1922  $gd = substr( $ts, 6, 2 );
1923 
1924  if ( !strcmp( $cName, 'thai' ) ) {
1925  # Thai solar dates
1926  # Add 543 years to the Gregorian calendar
1927  # Months and days are identical
1928  $gy_offset = $gy + 543;
1929  # fix for dates between 1912 and 1941
1930  # https://en.wikipedia.org/?oldid=836596673#New_year
1931  if ( $gy >= 1912 && $gy <= 1940 ) {
1932  if ( $gm <= 3 ) {
1933  $gy_offset--;
1934  }
1935  $gm = ( $gm - 3 ) % 12;
1936  }
1937  } elseif ( ( !strcmp( $cName, 'minguo' ) ) || !strcmp( $cName, 'juche' ) ) {
1938  # Minguo dates
1939  # Deduct 1911 years from the Gregorian calendar
1940  # Months and days are identical
1941  $gy_offset = $gy - 1911;
1942  } elseif ( !strcmp( $cName, 'tenno' ) ) {
1943  # Nengō dates up to Meiji period
1944  # Deduct years from the Gregorian calendar
1945  # depending on the nengo periods
1946  # Months and days are identical
1947  if ( ( $gy < 1912 )
1948  || ( ( $gy == 1912 ) && ( $gm < 7 ) )
1949  || ( ( $gy == 1912 ) && ( $gm == 7 ) && ( $gd < 31 ) )
1950  ) {
1951  # Meiji period
1952  $gy_gannen = $gy - 1868 + 1;
1953  $gy_offset = $gy_gannen;
1954  if ( $gy_gannen == 1 ) {
1955  $gy_offset = '元';
1956  }
1957  $gy_offset = '明治' . $gy_offset;
1958  } elseif (
1959  ( ( $gy == 1912 ) && ( $gm == 7 ) && ( $gd == 31 ) ) ||
1960  ( ( $gy == 1912 ) && ( $gm >= 8 ) ) ||
1961  ( ( $gy > 1912 ) && ( $gy < 1926 ) ) ||
1962  ( ( $gy == 1926 ) && ( $gm < 12 ) ) ||
1963  ( ( $gy == 1926 ) && ( $gm == 12 ) && ( $gd < 26 ) )
1964  ) {
1965  # Taishō period
1966  $gy_gannen = $gy - 1912 + 1;
1967  $gy_offset = $gy_gannen;
1968  if ( $gy_gannen == 1 ) {
1969  $gy_offset = '元';
1970  }
1971  $gy_offset = '大正' . $gy_offset;
1972  } elseif (
1973  ( ( $gy == 1926 ) && ( $gm == 12 ) && ( $gd >= 26 ) ) ||
1974  ( ( $gy > 1926 ) && ( $gy < 1989 ) ) ||
1975  ( ( $gy == 1989 ) && ( $gm == 1 ) && ( $gd < 8 ) )
1976  ) {
1977  # Shōwa period
1978  $gy_gannen = $gy - 1926 + 1;
1979  $gy_offset = $gy_gannen;
1980  if ( $gy_gannen == 1 ) {
1981  $gy_offset = '元';
1982  }
1983  $gy_offset = '昭和' . $gy_offset;
1984  } elseif (
1985  ( ( $gy == 1989 ) && ( $gm == 1 ) && ( $gd >= 8 ) ) ||
1986  ( ( $gy > 1989 ) && ( $gy < 2019 ) ) ||
1987  ( ( $gy == 2019 ) && ( $gm < 5 ) )
1988  ) {
1989  # Heisei period
1990  $gy_gannen = $gy - 1989 + 1;
1991  $gy_offset = $gy_gannen;
1992  if ( $gy_gannen == 1 ) {
1993  $gy_offset = '元';
1994  }
1995  $gy_offset = '平成' . $gy_offset;
1996  } else {
1997  # Reiwa period
1998  $gy_gannen = $gy - 2019 + 1;
1999  $gy_offset = $gy_gannen;
2000  if ( $gy_gannen == 1 ) {
2001  $gy_offset = '元';
2002  }
2003  $gy_offset = '令和' . $gy_offset;
2004  }
2005  } else {
2006  $gy_offset = $gy;
2007  }
2008 
2009  return [ $gy_offset, $gm, $gd ];
2010  }
2011 
2025  private static function strongDirFromContent( $text = '' ) {
2026  if ( !preg_match( self::$strongDirRegex, $text, $matches ) ) {
2027  return null;
2028  }
2029  if ( $matches[1] === '' ) {
2030  return 'rtl';
2031  }
2032  return 'ltr';
2033  }
2034 
2042  static function romanNumeral( $num ) {
2043  static $table = [
2044  [ '', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X' ],
2045  [ '', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC', 'C' ],
2046  [ '', 'C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM', 'M' ],
2047  [ '', 'M', 'MM', 'MMM', 'MMMM', 'MMMMM', 'MMMMMM', 'MMMMMMM',
2048  'MMMMMMMM', 'MMMMMMMMM', 'MMMMMMMMMM' ]
2049  ];
2050 
2051  $num = intval( $num );
2052  if ( $num > 10000 || $num <= 0 ) {
2053  return $num;
2054  }
2055 
2056  $s = '';
2057  for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) {
2058  if ( $num >= $pow10 ) {
2059  $s .= $table[$i][(int)floor( $num / $pow10 )];
2060  }
2061  $num = $num % $pow10;
2062  }
2063  return $s;
2064  }
2065 
2073  static function hebrewNumeral( $num ) {
2074  static $table = [
2075  [ '', 'א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ז', 'ח', 'ט', 'י' ],
2076  [ '', 'י', 'כ', 'ל', 'מ', 'נ', 'ס', 'ע', 'פ', 'צ', 'ק' ],
2077  [ '',
2078  [ 'ק' ],
2079  [ 'ר' ],
2080  [ 'ש' ],
2081  [ 'ת' ],
2082  [ 'ת', 'ק' ],
2083  [ 'ת', 'ר' ],
2084  [ 'ת', 'ש' ],
2085  [ 'ת', 'ת' ],
2086  [ 'ת', 'ת', 'ק' ],
2087  [ 'ת', 'ת', 'ר' ],
2088  ],
2089  [ '', 'א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ז', 'ח', 'ט', 'י' ]
2090  ];
2091 
2092  $num = intval( $num );
2093  if ( $num > 9999 || $num <= 0 ) {
2094  return $num;
2095  }
2096 
2097  // Round thousands have special notations
2098  if ( $num === 1000 ) {
2099  return "א' אלף";
2100  } elseif ( $num % 1000 === 0 ) {
2101  return $table[0][$num / 1000] . "' אלפים";
2102  }
2103 
2104  $letters = [];
2105 
2106  for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) {
2107  if ( $num >= $pow10 ) {
2108  if ( $num === 15 || $num === 16 ) {
2109  $letters[] = $table[0][9];
2110  $letters[] = $table[0][$num - 9];
2111  $num = 0;
2112  } else {
2113  $letters = array_merge(
2114  $letters,
2115  (array)$table[$i][intval( $num / $pow10 )]
2116  );
2117 
2118  if ( $pow10 === 1000 ) {
2119  $letters[] = "'";
2120  }
2121  }
2122  }
2123 
2124  $num = $num % $pow10;
2125  }
2126 
2127  $preTransformLength = count( $letters );
2128  if ( $preTransformLength === 1 ) {
2129  // Add geresh (single quote) to one-letter numbers
2130  $letters[] = "'";
2131  } else {
2132  $lastIndex = $preTransformLength - 1;
2133  $letters[$lastIndex] = str_replace(
2134  [ 'כ', 'מ', 'נ', 'פ', 'צ' ],
2135  [ 'ך', 'ם', 'ן', 'ף', 'ץ' ],
2136  $letters[$lastIndex]
2137  );
2138 
2139  // Add gershayim (double quote) to multiple-letter numbers,
2140  // but exclude numbers with only one letter after the thousands
2141  // (1001-1009, 1020, 1030, 2001-2009, etc.)
2142  if ( $letters[1] === "'" && $preTransformLength === 3 ) {
2143  $letters[] = "'";
2144  } else {
2145  array_splice( $letters, -1, 0, '"' );
2146  }
2147  }
2148 
2149  return implode( $letters );
2150  }
2151 
2160  public function userAdjust( $ts, $tz = false ) {
2161  global $wgUser, $wgLocalTZoffset;
2162 
2163  if ( $tz === false ) {
2164  $tz = $wgUser->getOption( 'timecorrection' );
2165  }
2166 
2167  $data = explode( '|', $tz, 3 );
2168 
2169  if ( $data[0] == 'ZoneInfo' ) {
2170  try {
2171  $userTZ = new DateTimeZone( $data[2] );
2172  $date = new DateTime( $ts, new DateTimeZone( 'UTC' ) );
2173  $date->setTimezone( $userTZ );
2174  return $date->format( 'YmdHis' );
2175  } catch ( Exception $e ) {
2176  // Unrecognized timezone, default to 'Offset' with the stored offset.
2177  $data[0] = 'Offset';
2178  }
2179  }
2180 
2181  if ( $data[0] == 'System' || $tz == '' ) {
2182  # Global offset in minutes.
2183  $minDiff = $wgLocalTZoffset;
2184  } elseif ( $data[0] == 'Offset' ) {
2185  $minDiff = intval( $data[1] );
2186  } else {
2187  $data = explode( ':', $tz );
2188  if ( count( $data ) == 2 ) {
2189  $data[0] = intval( $data[0] );
2190  $data[1] = intval( $data[1] );
2191  $minDiff = abs( $data[0] ) * 60 + $data[1];
2192  if ( $data[0] < 0 ) {
2193  $minDiff = -$minDiff;
2194  }
2195  } else {
2196  $minDiff = intval( $data[0] ) * 60;
2197  }
2198  }
2199 
2200  # No difference ? Return time unchanged
2201  if ( $minDiff == 0 ) {
2202  return $ts;
2203  }
2204 
2205  Wikimedia\suppressWarnings(); // E_STRICT system time bitching
2206  # Generate an adjusted date; take advantage of the fact that mktime
2207  # will normalize out-of-range values so we don't have to split $minDiff
2208  # into hours and minutes.
2209  $t = mktime( (
2210  (int)substr( $ts, 8, 2 ) ), # Hours
2211  (int)substr( $ts, 10, 2 ) + $minDiff, # Minutes
2212  (int)substr( $ts, 12, 2 ), # Seconds
2213  (int)substr( $ts, 4, 2 ), # Month
2214  (int)substr( $ts, 6, 2 ), # Day
2215  (int)substr( $ts, 0, 4 ) ); # Year
2216 
2217  $date = date( 'YmdHis', $t );
2218  Wikimedia\restoreWarnings();
2219 
2220  return $date;
2221  }
2222 
2238  function dateFormat( $usePrefs = true ) {
2239  global $wgUser;
2240 
2241  if ( is_bool( $usePrefs ) ) {
2242  if ( $usePrefs ) {
2243  $datePreference = $wgUser->getDatePreference();
2244  } else {
2245  $datePreference = (string)User::getDefaultOption( 'date' );
2246  }
2247  } else {
2248  $datePreference = (string)$usePrefs;
2249  }
2250 
2251  // return int
2252  if ( $datePreference == '' ) {
2253  return 'default';
2254  }
2255 
2256  return $datePreference;
2257  }
2258 
2269  function getDateFormatString( $type, $pref ) {
2270  $wasDefault = false;
2271  if ( $pref == 'default' ) {
2272  $wasDefault = true;
2273  $pref = $this->getDefaultDateFormat();
2274  }
2275 
2276  if ( !isset( $this->dateFormatStrings[$type][$pref] ) ) {
2277  $df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" );
2278 
2279  if ( $type === 'pretty' && $df === null ) {
2280  $df = $this->getDateFormatString( 'date', $pref );
2281  }
2282 
2283  if ( !$wasDefault && $df === null ) {
2284  $pref = $this->getDefaultDateFormat();
2285  $df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" );
2286  }
2287 
2288  $this->dateFormatStrings[$type][$pref] = $df;
2289  }
2290  return $this->dateFormatStrings[$type][$pref];
2291  }
2292 
2303  public function date( $ts, $adj = false, $format = true, $timecorrection = false ) {
2304  $ts = wfTimestamp( TS_MW, $ts );
2305  if ( $adj ) {
2306  $ts = $this->userAdjust( $ts, $timecorrection );
2307  }
2308  $df = $this->getDateFormatString( 'date', $this->dateFormat( $format ) );
2309  return $this->sprintfDate( $df, $ts );
2310  }
2311 
2322  public function time( $ts, $adj = false, $format = true, $timecorrection = false ) {
2323  $ts = wfTimestamp( TS_MW, $ts );
2324  if ( $adj ) {
2325  $ts = $this->userAdjust( $ts, $timecorrection );
2326  }
2327  $df = $this->getDateFormatString( 'time', $this->dateFormat( $format ) );
2328  return $this->sprintfDate( $df, $ts );
2329  }
2330 
2342  public function timeanddate( $ts, $adj = false, $format = true, $timecorrection = false ) {
2343  $ts = wfTimestamp( TS_MW, $ts );
2344  if ( $adj ) {
2345  $ts = $this->userAdjust( $ts, $timecorrection );
2346  }
2347  $df = $this->getDateFormatString( 'both', $this->dateFormat( $format ) );
2348  return $this->sprintfDate( $df, $ts );
2349  }
2350 
2361  public function formatDuration( $seconds, array $chosenIntervals = [] ) {
2362  $intervals = $this->getDurationIntervals( $seconds, $chosenIntervals );
2363 
2364  $segments = [];
2365 
2366  foreach ( $intervals as $intervalName => $intervalValue ) {
2367  // Messages: duration-seconds, duration-minutes, duration-hours, duration-days, duration-weeks,
2368  // duration-years, duration-decades, duration-centuries, duration-millennia
2369  $message = wfMessage( 'duration-' . $intervalName )->numParams( $intervalValue );
2370  $segments[] = $message->inLanguage( $this )->escaped();
2371  }
2372 
2373  return $this->listToText( $segments );
2374  }
2375 
2387  public function getDurationIntervals( $seconds, array $chosenIntervals = [] ) {
2388  if ( empty( $chosenIntervals ) ) {
2389  $chosenIntervals = [
2390  'millennia',
2391  'centuries',
2392  'decades',
2393  'years',
2394  'days',
2395  'hours',
2396  'minutes',
2397  'seconds'
2398  ];
2399  }
2400 
2401  $intervals = array_intersect_key( self::$durationIntervals, array_flip( $chosenIntervals ) );
2402  $sortedNames = array_keys( $intervals );
2403  $smallestInterval = array_pop( $sortedNames );
2404 
2405  $segments = [];
2406 
2407  foreach ( $intervals as $name => $length ) {
2408  $value = floor( $seconds / $length );
2409 
2410  if ( $value > 0 || ( $name == $smallestInterval && empty( $segments ) ) ) {
2411  $seconds -= $value * $length;
2412  $segments[$name] = $value;
2413  }
2414  }
2415 
2416  return $segments;
2417  }
2418 
2438  private function internalUserTimeAndDate( $type, $ts, User $user, array $options ) {
2439  $ts = wfTimestamp( TS_MW, $ts );
2440  $options += [ 'timecorrection' => true, 'format' => true ];
2441  if ( $options['timecorrection'] !== false ) {
2442  if ( $options['timecorrection'] === true ) {
2443  $offset = $user->getOption( 'timecorrection' );
2444  } else {
2445  $offset = $options['timecorrection'];
2446  }
2447  $ts = $this->userAdjust( $ts, $offset );
2448  }
2449  if ( $options['format'] === true ) {
2450  $format = $user->getDatePreference();
2451  } else {
2452  $format = $options['format'];
2453  }
2454  $df = $this->getDateFormatString( $type, $this->dateFormat( $format ) );
2455  return $this->sprintfDate( $df, $ts );
2456  }
2457 
2477  public function userDate( $ts, User $user, array $options = [] ) {
2478  return $this->internalUserTimeAndDate( 'date', $ts, $user, $options );
2479  }
2480 
2500  public function userTime( $ts, User $user, array $options = [] ) {
2501  return $this->internalUserTimeAndDate( 'time', $ts, $user, $options );
2502  }
2503 
2523  public function userTimeAndDate( $ts, User $user, array $options = [] ) {
2524  return $this->internalUserTimeAndDate( 'both', $ts, $user, $options );
2525  }
2526 
2542  public function getHumanTimestamp(
2543  MWTimestamp $time, MWTimestamp $relativeTo = null, User $user = null
2544  ) {
2545  if ( $relativeTo === null ) {
2546  $relativeTo = new MWTimestamp();
2547  }
2548  if ( $user === null ) {
2549  $user = RequestContext::getMain()->getUser();
2550  }
2551 
2552  // Adjust for the user's timezone.
2553  $offsetThis = $time->offsetForUser( $user );
2554  $offsetRel = $relativeTo->offsetForUser( $user );
2555 
2556  $ts = '';
2557  if ( Hooks::run( 'GetHumanTimestamp', [ &$ts, $time, $relativeTo, $user, $this ] ) ) {
2558  $ts = $this->getHumanTimestampInternal( $time, $relativeTo, $user );
2559  }
2560 
2561  // Reset the timezone on the objects.
2562  $time->timestamp->sub( $offsetThis );
2563  $relativeTo->timestamp->sub( $offsetRel );
2564 
2565  return $ts;
2566  }
2567 
2579  private function getHumanTimestampInternal(
2580  MWTimestamp $ts, MWTimestamp $relativeTo, User $user
2581  ) {
2582  $diff = $ts->diff( $relativeTo );
2583  $diffDay = (bool)( (int)$ts->timestamp->format( 'w' ) -
2584  (int)$relativeTo->timestamp->format( 'w' ) );
2585  $days = $diff->days ?: (int)$diffDay;
2586  if ( $diff->invert || $days > 5
2587  && $ts->timestamp->format( 'Y' ) !== $relativeTo->timestamp->format( 'Y' )
2588  ) {
2589  // Timestamps are in different years: use full timestamp
2590  // Also do full timestamp for future dates
2594  $format = $this->getDateFormatString( 'both', $user->getDatePreference() ?: 'default' );
2595  $ts = $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) );
2596  } elseif ( $days > 5 ) {
2597  // Timestamps are in same year, but more than 5 days ago: show day and month only.
2598  $format = $this->getDateFormatString( 'pretty', $user->getDatePreference() ?: 'default' );
2599  $ts = $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) );
2600  } elseif ( $days > 1 ) {
2601  // Timestamp within the past week: show the day of the week and time
2602  $format = $this->getDateFormatString( 'time', $user->getDatePreference() ?: 'default' );
2603  $weekday = self::$mWeekdayMsgs[$ts->timestamp->format( 'w' )];
2604  // Messages:
2605  // sunday-at, monday-at, tuesday-at, wednesday-at, thursday-at, friday-at, saturday-at
2606  $ts = wfMessage( "$weekday-at" )
2607  ->inLanguage( $this )
2608  ->params( $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) ) )
2609  ->text();
2610  } elseif ( $days == 1 ) {
2611  // Timestamp was yesterday: say 'yesterday' and the time.
2612  $format = $this->getDateFormatString( 'time', $user->getDatePreference() ?: 'default' );
2613  $ts = wfMessage( 'yesterday-at' )
2614  ->inLanguage( $this )
2615  ->params( $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) ) )
2616  ->text();
2617  } elseif ( $diff->h > 1 || $diff->h == 1 && $diff->i > 30 ) {
2618  // Timestamp was today, but more than 90 minutes ago: say 'today' and the time.
2619  $format = $this->getDateFormatString( 'time', $user->getDatePreference() ?: 'default' );
2620  $ts = wfMessage( 'today-at' )
2621  ->inLanguage( $this )
2622  ->params( $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) ) )
2623  ->text();
2624 
2625  // From here on in, the timestamp was soon enough ago so that we can simply say
2626  // XX units ago, e.g., "2 hours ago" or "5 minutes ago"
2627  } elseif ( $diff->h == 1 ) {
2628  // Less than 90 minutes, but more than an hour ago.
2629  $ts = wfMessage( 'hours-ago' )->inLanguage( $this )->numParams( 1 )->text();
2630  } elseif ( $diff->i >= 1 ) {
2631  // A few minutes ago.
2632  $ts = wfMessage( 'minutes-ago' )->inLanguage( $this )->numParams( $diff->i )->text();
2633  } elseif ( $diff->s >= 30 ) {
2634  // Less than a minute, but more than 30 sec ago.
2635  $ts = wfMessage( 'seconds-ago' )->inLanguage( $this )->numParams( $diff->s )->text();
2636  } else {
2637  // Less than 30 seconds ago.
2638  $ts = wfMessage( 'just-now' )->text();
2639  }
2640 
2641  return $ts;
2642  }
2643 
2648  public function getMessage( $key ) {
2649  return self::$dataCache->getSubitem( $this->mCode, 'messages', $key );
2650  }
2651 
2655  function getAllMessages() {
2656  return self::$dataCache->getItem( $this->mCode, 'messages' );
2657  }
2658 
2665  public function iconv( $in, $out, $string ) {
2666  # Even with //IGNORE iconv can whine about illegal characters in
2667  # *input* string. We just ignore those too.
2668  # REF: https://bugs.php.net/bug.php?id=37166
2669  # REF: https://phabricator.wikimedia.org/T18885
2670  Wikimedia\suppressWarnings();
2671  $text = iconv( $in, $out . '//IGNORE', $string );
2672  Wikimedia\restoreWarnings();
2673  return $text;
2674  }
2675 
2676  // callback functions for ucwords(), ucwordbreaks()
2677 
2683  return $this->ucfirst( $matches[1] );
2684  }
2685 
2691  return mb_strtoupper( $matches[0] );
2692  }
2693 
2699  return mb_strtoupper( $matches[0] );
2700  }
2701 
2709  public function ucfirst( $str ) {
2710  $o = ord( $str );
2711  if ( $o < 96 ) { // if already uppercase...
2712  return $str;
2713  } elseif ( $o < 128 ) {
2714  return ucfirst( $str ); // use PHP's ucfirst()
2715  } else {
2716  // fall back to more complex logic in case of multibyte strings
2717  return $this->uc( $str, true );
2718  }
2719  }
2720 
2729  public function uc( $str, $first = false ) {
2730  if ( $first ) {
2731  if ( $this->isMultibyte( $str ) ) {
2732  return $this->mbUpperChar( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
2733  } else {
2734  return ucfirst( $str );
2735  }
2736  } else {
2737  return $this->isMultibyte( $str ) ? mb_strtoupper( $str ) : strtoupper( $str );
2738  }
2739  }
2740 
2754  protected function mbUpperChar( $char ) {
2756  if ( array_key_exists( $char, $wgOverrideUcfirstCharacters ) ) {
2757  return $wgOverrideUcfirstCharacters[$char];
2758  } else {
2759  return mb_strtoupper( $char );
2760  }
2761  }
2762 
2767  function lcfirst( $str ) {
2768  $o = ord( $str );
2769  if ( !$o ) {
2770  return strval( $str );
2771  } elseif ( $o >= 128 ) {
2772  return $this->lc( $str, true );
2773  } elseif ( $o > 96 ) {
2774  return $str;
2775  } else {
2776  $str[0] = strtolower( $str[0] );
2777  return $str;
2778  }
2779  }
2780 
2786  function lc( $str, $first = false ) {
2787  if ( $first ) {
2788  if ( $this->isMultibyte( $str ) ) {
2789  return mb_strtolower( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
2790  } else {
2791  return strtolower( substr( $str, 0, 1 ) ) . substr( $str, 1 );
2792  }
2793  } else {
2794  return $this->isMultibyte( $str ) ? mb_strtolower( $str ) : strtolower( $str );
2795  }
2796  }
2797 
2802  function isMultibyte( $str ) {
2803  return strlen( $str ) !== mb_strlen( $str );
2804  }
2805 
2810  function ucwords( $str ) {
2811  if ( $this->isMultibyte( $str ) ) {
2812  $str = $this->lc( $str );
2813 
2814  // regexp to find first letter in each word (i.e. after each space)
2815  $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)| ([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
2816 
2817  // function to use to capitalize a single char
2818  return preg_replace_callback(
2819  $replaceRegexp,
2820  [ $this, 'ucwordsCallbackMB' ],
2821  $str
2822  );
2823  } else {
2824  return ucwords( strtolower( $str ) );
2825  }
2826  }
2827 
2834  function ucwordbreaks( $str ) {
2835  if ( $this->isMultibyte( $str ) ) {
2836  $str = $this->lc( $str );
2837 
2838  // since \b doesn't work for UTF-8, we explicitely define word break chars
2839  $breaks = "[ \-\(\)\}\{\.,\?!]";
2840 
2841  // find first letter after word break
2842  $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)|" .
2843  "$breaks([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
2844 
2845  return preg_replace_callback(
2846  $replaceRegexp,
2847  [ $this, 'ucwordbreaksCallbackMB' ],
2848  $str
2849  );
2850  } else {
2851  return preg_replace_callback(
2852  '/\b([\w\x80-\xff]+)\b/',
2853  [ $this, 'ucwordbreaksCallbackAscii' ],
2854  $str
2855  );
2856  }
2857  }
2858 
2874  function caseFold( $s ) {
2875  return $this->uc( $s );
2876  }
2877 
2883  function checkTitleEncoding( $s ) {
2884  if ( is_array( $s ) ) {
2885  throw new MWException( 'Given array to checkTitleEncoding.' );
2886  }
2887  if ( StringUtils::isUtf8( $s ) ) {
2888  return $s;
2889  }
2890 
2891  return $this->iconv( $this->fallback8bitEncoding(), 'utf-8', $s );
2892  }
2893 
2898  return self::$dataCache->getItem( $this->mCode, 'fallback8bitEncoding' );
2899  }
2900 
2909  function hasWordBreaks() {
2910  return true;
2911  }
2912 
2920  function segmentByWord( $string ) {
2921  return $string;
2922  }
2923 
2931  function normalizeForSearch( $string ) {
2932  return self::convertDoubleWidth( $string );
2933  }
2934 
2943  protected static function convertDoubleWidth( $string ) {
2944  static $full = null;
2945  static $half = null;
2946 
2947  if ( $full === null ) {
2948  $fullWidth = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
2949  $halfWidth = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
2950  $full = str_split( $fullWidth, 3 );
2951  $half = str_split( $halfWidth );
2952  }
2953 
2954  $string = str_replace( $full, $half, $string );
2955  return $string;
2956  }
2957 
2963  protected static function insertSpace( $string, $pattern ) {
2964  $string = preg_replace( $pattern, " $1 ", $string );
2965  $string = preg_replace( '/ +/', ' ', $string );
2966  return $string;
2967  }
2968 
2973  function convertForSearchResult( $termsArray ) {
2974  # some languages, e.g. Chinese, need to do a conversion
2975  # in order for search results to be displayed correctly
2976  return $termsArray;
2977  }
2978 
2985  function firstChar( $s ) {
2986  $matches = [];
2987  preg_match(
2988  '/^([\x00-\x7f]|[\xc0-\xdf][\x80-\xbf]|' .
2989  '[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xf7][\x80-\xbf]{3})/',
2990  $s,
2991  $matches
2992  );
2993 
2994  if ( isset( $matches[1] ) ) {
2995  if ( strlen( $matches[1] ) != 3 ) {
2996  return $matches[1];
2997  }
2998 
2999  // Break down Hangul syllables to grab the first jamo
3000  $code = UtfNormal\Utils::utf8ToCodepoint( $matches[1] );
3001  if ( $code < 0xac00 || 0xd7a4 <= $code ) {
3002  return $matches[1];
3003  } elseif ( $code < 0xb098 ) {
3004  return "\u{3131}";
3005  } elseif ( $code < 0xb2e4 ) {
3006  return "\u{3134}";
3007  } elseif ( $code < 0xb77c ) {
3008  return "\u{3137}";
3009  } elseif ( $code < 0xb9c8 ) {
3010  return "\u{3139}";
3011  } elseif ( $code < 0xbc14 ) {
3012  return "\u{3141}";
3013  } elseif ( $code < 0xc0ac ) {
3014  return "\u{3142}";
3015  } elseif ( $code < 0xc544 ) {
3016  return "\u{3145}";
3017  } elseif ( $code < 0xc790 ) {
3018  return "\u{3147}";
3019  } elseif ( $code < 0xcc28 ) {
3020  return "\u{3148}";
3021  } elseif ( $code < 0xce74 ) {
3022  return "\u{314A}";
3023  } elseif ( $code < 0xd0c0 ) {
3024  return "\u{314B}";
3025  } elseif ( $code < 0xd30c ) {
3026  return "\u{314C}";
3027  } elseif ( $code < 0xd558 ) {
3028  return "\u{314D}";
3029  } else {
3030  return "\u{314E}";
3031  }
3032  } else {
3033  return '';
3034  }
3035  }
3036 
3048  public function normalize( $s ) {
3049  global $wgAllUnicodeFixes;
3050  $s = UtfNormal\Validator::cleanUp( $s );
3051  if ( $wgAllUnicodeFixes ) {
3052  $s = $this->transformUsingPairFile( 'normalize-ar.php', $s );
3053  $s = $this->transformUsingPairFile( 'normalize-ml.php', $s );
3054  }
3055 
3056  return $s;
3057  }
3058 
3073  protected function transformUsingPairFile( $file, $string ) {
3074  if ( !isset( $this->transformData[$file] ) ) {
3075  global $IP;
3076  $data = require "$IP/languages/data/{$file}";
3077  $this->transformData[$file] = new ReplacementArray( $data );
3078  }
3079  return $this->transformData[$file]->replace( $string );
3080  }
3081 
3087  function isRTL() {
3088  return self::$dataCache->getItem( $this->mCode, 'rtl' );
3089  }
3090 
3095  function getDir() {
3096  return $this->isRTL() ? 'rtl' : 'ltr';
3097  }
3098 
3107  function alignStart() {
3108  return $this->isRTL() ? 'right' : 'left';
3109  }
3110 
3119  function alignEnd() {
3120  return $this->isRTL() ? 'left' : 'right';
3121  }
3122 
3134  function getDirMarkEntity( $opposite = false ) {
3135  if ( $opposite ) {
3136  return $this->isRTL() ? '&lrm;' : '&rlm;';
3137  }
3138  return $this->isRTL() ? '&rlm;' : '&lrm;';
3139  }
3140 
3151  function getDirMark( $opposite = false ) {
3152  $lrm = "\u{200E}"; # LEFT-TO-RIGHT MARK, commonly abbreviated LRM
3153  $rlm = "\u{200F}"; # RIGHT-TO-LEFT MARK, commonly abbreviated RLM
3154  if ( $opposite ) {
3155  return $this->isRTL() ? $lrm : $rlm;
3156  }
3157  return $this->isRTL() ? $rlm : $lrm;
3158  }
3159 
3163  function capitalizeAllNouns() {
3164  return self::$dataCache->getItem( $this->mCode, 'capitalizeAllNouns' );
3165  }
3166 
3174  function getArrow( $direction = 'forwards' ) {
3175  switch ( $direction ) {
3176  case 'forwards':
3177  return $this->isRTL() ? '←' : '→';
3178  case 'backwards':
3179  return $this->isRTL() ? '→' : '←';
3180  case 'left':
3181  return '←';
3182  case 'right':
3183  return '→';
3184  case 'up':
3185  return '↑';
3186  case 'down':
3187  return '↓';
3188  }
3189  }
3190 
3196  function linkPrefixExtension() {
3197  return self::$dataCache->getItem( $this->mCode, 'linkPrefixExtension' );
3198  }
3199 
3204  function getMagicWords() {
3205  return self::$dataCache->getItem( $this->mCode, 'magicWords' );
3206  }
3207 
3213  function getMagic( $mw ) {
3214  $rawEntry = $this->mMagicExtensions[$mw->mId] ??
3215  self::$dataCache->getSubitem( $this->mCode, 'magicWords', $mw->mId );
3216 
3217  if ( !is_array( $rawEntry ) ) {
3218  wfWarn( "\"$rawEntry\" is not a valid magic word for \"$mw->mId\"" );
3219  } else {
3220  $mw->mCaseSensitive = $rawEntry[0];
3221  $mw->mSynonyms = array_slice( $rawEntry, 1 );
3222  }
3223  }
3224 
3230  function addMagicWordsByLang( $newWords ) {
3231  $fallbackChain = $this->getFallbackLanguages();
3232  $fallbackChain = array_reverse( $fallbackChain );
3233  foreach ( $fallbackChain as $code ) {
3234  if ( isset( $newWords[$code] ) ) {
3235  $this->mMagicExtensions = $newWords[$code] + $this->mMagicExtensions;
3236  }
3237  }
3238  }
3239 
3246  // Cache aliases because it may be slow to load them
3247  if ( is_null( $this->mExtendedSpecialPageAliases ) ) {
3248  // Initialise array
3249  $this->mExtendedSpecialPageAliases =
3250  self::$dataCache->getItem( $this->mCode, 'specialPageAliases' );
3251  }
3252 
3254  }
3255 
3262  function emphasize( $text ) {
3263  return "<em>$text</em>";
3264  }
3265 
3288  public function formatNum( $number, $nocommafy = false ) {
3289  global $wgTranslateNumerals;
3290  if ( !$nocommafy ) {
3291  $number = $this->commafy( $number );
3292  $s = $this->separatorTransformTable();
3293  if ( $s ) {
3294  $number = strtr( $number, $s );
3295  }
3296  }
3297 
3298  if ( $wgTranslateNumerals ) {
3299  $s = $this->digitTransformTable();
3300  if ( $s ) {
3301  $number = strtr( $number, $s );
3302  }
3303  }
3304 
3305  return (string)$number;
3306  }
3307 
3316  public function formatNumNoSeparators( $number ) {
3317  return $this->formatNum( $number, true );
3318  }
3319 
3324  public function parseFormattedNumber( $number ) {
3325  $s = $this->digitTransformTable();
3326  if ( $s ) {
3327  // eliminate empty array values such as ''. (T66347)
3328  $s = array_filter( $s );
3329  $number = strtr( $number, array_flip( $s ) );
3330  }
3331 
3332  $s = $this->separatorTransformTable();
3333  if ( $s ) {
3334  // eliminate empty array values such as ''. (T66347)
3335  $s = array_filter( $s );
3336  $number = strtr( $number, array_flip( $s ) );
3337  }
3338 
3339  $number = strtr( $number, [ ',' => '' ] );
3340  return $number;
3341  }
3342 
3349  function commafy( $number ) {
3352  if ( $number === null ) {
3353  return '';
3354  }
3355 
3356  if ( !$digitGroupingPattern || $digitGroupingPattern === "###,###,###" ) {
3357  // Default grouping is at thousands, use the same for ###,###,### pattern too.
3358  // In some languages it's conventional not to insert a thousands separator
3359  // in numbers that are four digits long (1000-9999).
3360  if ( $minimumGroupingDigits ) {
3361  // Number of '#' characters after last comma in the grouping pattern.
3362  // The pattern is hardcoded here, but this would vary for different patterns.
3363  $primaryGroupingSize = 3;
3364  // Maximum length of a number to suppress digit grouping for.
3365  $maximumLength = $minimumGroupingDigits + $primaryGroupingSize - 1;
3366  if ( preg_match( '/^\-?\d{1,' . $maximumLength . '}(\.\d+)?$/', $number ) ) {
3367  return $number;
3368  }
3369  }
3370  return strrev( (string)preg_replace( '/(\d{3})(?=\d)(?!\d*\.)/', '$1,', strrev( $number ) ) );
3371  } else {
3372  // Ref: http://cldr.unicode.org/translation/number-patterns
3373  $sign = "";
3374  if ( intval( $number ) < 0 ) {
3375  // For negative numbers apply the algorithm like positive number and add sign.
3376  $sign = "-";
3377  $number = substr( $number, 1 );
3378  }
3379  $integerPart = [];
3380  $decimalPart = [];
3381  $numMatches = preg_match_all( "/(#+)/", $digitGroupingPattern, $matches );
3382  preg_match( "/\d+/", $number, $integerPart );
3383  preg_match( "/\.\d*/", $number, $decimalPart );
3384  $groupedNumber = ( count( $decimalPart ) > 0 ) ? $decimalPart[0] : "";
3385  if ( $groupedNumber === $number ) {
3386  // the string does not have any number part. Eg: .12345
3387  return $sign . $groupedNumber;
3388  }
3389  $start = $end = ( $integerPart ) ? strlen( $integerPart[0] ) : 0;
3390  while ( $start > 0 ) {
3391  $match = $matches[0][$numMatches - 1];
3392  $matchLen = strlen( $match );
3393  $start = $end - $matchLen;
3394  if ( $start < 0 ) {
3395  $start = 0;
3396  }
3397  $groupedNumber = substr( $number, $start, $end - $start ) . $groupedNumber;
3398  $end = $start;
3399  if ( $numMatches > 1 ) {
3400  // use the last pattern for the rest of the number
3401  $numMatches--;
3402  }
3403  if ( $start > 0 ) {
3404  $groupedNumber = "," . $groupedNumber;
3405  }
3406  }
3407  return $sign . $groupedNumber;
3408  }
3409  }
3410 
3415  return self::$dataCache->getItem( $this->mCode, 'digitGroupingPattern' );
3416  }
3417 
3421  function digitTransformTable() {
3422  return self::$dataCache->getItem( $this->mCode, 'digitTransformTable' );
3423  }
3424 
3429  return self::$dataCache->getItem( $this->mCode, 'separatorTransformTable' );
3430  }
3431 
3436  return self::$dataCache->getItem( $this->mCode, 'minimumGroupingDigits' );
3437  }
3438 
3447  public function listToText( array $list ) {
3448  $itemCount = count( $list );
3449  if ( $itemCount < 1 ) {
3450  return '';
3451  }
3452  $text = array_pop( $list );
3453  if ( $itemCount > 1 ) {
3454  $and = $this->msg( 'and' )->escaped();
3455  $space = $this->msg( 'word-separator' )->escaped();
3456  $comma = '';
3457  if ( $itemCount > 2 ) {
3458  $comma = $this->msg( 'comma-separator' )->escaped();
3459  }
3460  $text = implode( $comma, $list ) . $and . $space . $text;
3461  }
3462  return $text;
3463  }
3464 
3471  function commaList( array $list ) {
3472  return implode(
3473  wfMessage( 'comma-separator' )->inLanguage( $this )->escaped(),
3474  $list
3475  );
3476  }
3477 
3484  function semicolonList( array $list ) {
3485  return implode(
3486  wfMessage( 'semicolon-separator' )->inLanguage( $this )->escaped(),
3487  $list
3488  );
3489  }
3490 
3496  function pipeList( array $list ) {
3497  return implode(
3498  wfMessage( 'pipe-separator' )->inLanguage( $this )->escaped(),
3499  $list
3500  );
3501  }
3502 
3518  function truncateForDatabase( $string, $length, $ellipsis = '...', $adjustLength = true ) {
3519  return $this->truncateInternal(
3520  $string, $length, $ellipsis, $adjustLength, 'strlen', 'substr'
3521  );
3522  }
3523 
3542  function truncateForVisual( $string, $length, $ellipsis = '...', $adjustLength = true ) {
3543  // Passing encoding to mb_strlen and mb_substr is optional.
3544  // Encoding defaults to mb_internal_encoding(), which is set to UTF-8 in Setup.php, so
3545  // explicit specification of encoding is skipped.
3546  // Note: Both multibyte methods are callables invoked in truncateInternal.
3547  return $this->truncateInternal(
3548  $string, $length, $ellipsis, $adjustLength, 'mb_strlen', 'mb_substr'
3549  );
3550  }
3551 
3568  private function truncateInternal(
3569  $string, $length, $ellipsis, $adjustLength, callable $measureLength, callable $getSubstring
3570  ) {
3571  # Check if there is no need to truncate
3572  if ( $measureLength( $string ) <= abs( $length ) ) {
3573  return $string; // no need to truncate
3574  }
3575 
3576  # Use the localized ellipsis character
3577  if ( $ellipsis == '...' ) {
3578  $ellipsis = wfMessage( 'ellipsis' )->inLanguage( $this )->escaped();
3579  }
3580  if ( $length == 0 ) {
3581  return $ellipsis; // convention
3582  }
3583 
3584  $stringOriginal = $string;
3585  # If ellipsis length is >= $length then we can't apply $adjustLength
3586  if ( $adjustLength && $measureLength( $ellipsis ) >= abs( $length ) ) {
3587  $string = $ellipsis; // this can be slightly unexpected
3588  # Otherwise, truncate and add ellipsis...
3589  } else {
3590  $ellipsisLength = $adjustLength ? $measureLength( $ellipsis ) : 0;
3591  if ( $length > 0 ) {
3592  $length -= $ellipsisLength;
3593  $string = $getSubstring( $string, 0, $length ); // xyz...
3594  $string = $this->removeBadCharLast( $string );
3595  $string = rtrim( $string ) . $ellipsis;
3596  } else {
3597  $length += $ellipsisLength;
3598  $string = $getSubstring( $string, $length ); // ...xyz
3599  $string = $this->removeBadCharFirst( $string );
3600  $string = $ellipsis . ltrim( $string );
3601  }
3602  }
3603 
3604  # Do not truncate if the ellipsis makes the string longer/equal (T24181).
3605  # This check is *not* redundant if $adjustLength, due to the single case where
3606  # LEN($ellipsis) > ABS($limit arg); $stringOriginal could be shorter than $string.
3607  if ( $measureLength( $string ) < $measureLength( $stringOriginal ) ) {
3608  return $string;
3609  } else {
3610  return $stringOriginal;
3611  }
3612  }
3613 
3621  protected function removeBadCharLast( $string ) {
3622  if ( $string != '' ) {
3623  $char = ord( $string[strlen( $string ) - 1] );
3624  $m = [];
3625  if ( $char >= 0xc0 ) {
3626  # We got the first byte only of a multibyte char; remove it.
3627  $string = substr( $string, 0, -1 );
3628  } elseif ( $char >= 0x80 &&
3629  // Use the /s modifier (PCRE_DOTALL) so (.*) also matches newlines
3630  preg_match( '/^(.*)(?:[\xe0-\xef][\x80-\xbf]|' .
3631  '[\xf0-\xf7][\x80-\xbf]{1,2})$/s', $string, $m )
3632  ) {
3633  # We chopped in the middle of a character; remove it
3634  $string = $m[1];
3635  }
3636  }
3637  return $string;
3638  }
3639 
3647  protected function removeBadCharFirst( $string ) {
3648  if ( $string != '' ) {
3649  $char = ord( $string[0] );
3650  if ( $char >= 0x80 && $char < 0xc0 ) {
3651  # We chopped in the middle of a character; remove the whole thing
3652  $string = preg_replace( '/^[\x80-\xbf]+/', '', $string );
3653  }
3654  }
3655  return $string;
3656  }
3657 
3673  function truncateHtml( $text, $length, $ellipsis = '...' ) {
3674  # Use the localized ellipsis character
3675  if ( $ellipsis == '...' ) {
3676  $ellipsis = wfMessage( 'ellipsis' )->inLanguage( $this )->escaped();
3677  }
3678  # Check if there is clearly no need to truncate
3679  if ( $length <= 0 ) {
3680  return $ellipsis; // no text shown, nothing to format (convention)
3681  } elseif ( strlen( $text ) <= $length ) {
3682  return $text; // string short enough even *with* HTML (short-circuit)
3683  }
3684 
3685  $dispLen = 0; // innerHTML legth so far
3686  $testingEllipsis = false; // checking if ellipses will make string longer/equal?
3687  $tagType = 0; // 0-open, 1-close
3688  $bracketState = 0; // 1-tag start, 2-tag name, 0-neither
3689  $entityState = 0; // 0-not entity, 1-entity
3690  $tag = $ret = ''; // accumulated tag name, accumulated result string
3691  $openTags = []; // open tag stack
3692  $maybeState = null; // possible truncation state
3693 
3694  $textLen = strlen( $text );
3695  $neLength = max( 0, $length - strlen( $ellipsis ) ); // non-ellipsis len if truncated
3696  for ( $pos = 0; true; ++$pos ) {
3697  # Consider truncation once the display length has reached the maximim.
3698  # We check if $dispLen > 0 to grab tags for the $neLength = 0 case.
3699  # Check that we're not in the middle of a bracket/entity...
3700  if ( $dispLen && $dispLen >= $neLength && $bracketState == 0 && !$entityState ) {
3701  if ( !$testingEllipsis ) {
3702  $testingEllipsis = true;
3703  # Save where we are; we will truncate here unless there turn out to
3704  # be so few remaining characters that truncation is not necessary.
3705  if ( !$maybeState ) { // already saved? ($neLength = 0 case)
3706  $maybeState = [ $ret, $openTags ]; // save state
3707  }
3708  } elseif ( $dispLen > $length && $dispLen > strlen( $ellipsis ) ) {
3709  # String in fact does need truncation, the truncation point was OK.
3710  // @phan-suppress-next-line PhanTypeInvalidExpressionArrayDestructuring
3711  list( $ret, $openTags ) = $maybeState; // reload state
3712  $ret = $this->removeBadCharLast( $ret ); // multi-byte char fix
3713  $ret .= $ellipsis; // add ellipsis
3714  break;
3715  }
3716  }
3717  if ( $pos >= $textLen ) {
3718  break; // extra iteration just for above checks
3719  }
3720 
3721  # Read the next char...
3722  $ch = $text[$pos];
3723  $lastCh = $pos ? $text[$pos - 1] : '';
3724  $ret .= $ch; // add to result string
3725  if ( $ch == '<' ) {
3726  $this->truncate_endBracket( $tag, $tagType, $lastCh, $openTags ); // for bad HTML
3727  $entityState = 0; // for bad HTML
3728  $bracketState = 1; // tag started (checking for backslash)
3729  } elseif ( $ch == '>' ) {
3730  $this->truncate_endBracket( $tag, $tagType, $lastCh, $openTags );
3731  $entityState = 0; // for bad HTML
3732  $bracketState = 0; // out of brackets
3733  } elseif ( $bracketState == 1 ) {
3734  if ( $ch == '/' ) {
3735  $tagType = 1; // close tag (e.g. "</span>")
3736  } else {
3737  $tagType = 0; // open tag (e.g. "<span>")
3738  $tag .= $ch;
3739  }
3740  $bracketState = 2; // building tag name
3741  } elseif ( $bracketState == 2 ) {
3742  if ( $ch != ' ' ) {
3743  $tag .= $ch;
3744  } else {
3745  // Name found (e.g. "<a href=..."), add on tag attributes...
3746  $pos += $this->truncate_skip( $ret, $text, "<>", $pos + 1 );
3747  }
3748  } elseif ( $bracketState == 0 ) {
3749  if ( $entityState ) {
3750  if ( $ch == ';' ) {
3751  $entityState = 0;
3752  $dispLen++; // entity is one displayed char
3753  }
3754  } else {
3755  if ( $neLength == 0 && !$maybeState ) {
3756  // Save state without $ch. We want to *hit* the first
3757  // display char (to get tags) but not *use* it if truncating.
3758  $maybeState = [ substr( $ret, 0, -1 ), $openTags ];
3759  }
3760  if ( $ch == '&' ) {
3761  $entityState = 1; // entity found, (e.g. "&#160;")
3762  } else {
3763  $dispLen++; // this char is displayed
3764  // Add the next $max display text chars after this in one swoop...
3765  $max = ( $testingEllipsis ? $length : $neLength ) - $dispLen;
3766  $skipped = $this->truncate_skip( $ret, $text, "<>&", $pos + 1, $max );
3767  $dispLen += $skipped;
3768  $pos += $skipped;
3769  }
3770  }
3771  }
3772  }
3773  // Close the last tag if left unclosed by bad HTML
3774  $this->truncate_endBracket( $tag, $text[$textLen - 1], $tagType, $openTags );
3775  while ( count( $openTags ) > 0 ) {
3776  $ret .= '</' . array_pop( $openTags ) . '>'; // close open tags
3777  }
3778  return $ret;
3779  }
3780 
3792  private function truncate_skip( &$ret, $text, $search, $start, $len = null ) {
3793  if ( $len === null ) {
3794  $len = -1; // -1 means "no limit" for strcspn
3795  } elseif ( $len < 0 ) {
3796  $len = 0; // sanity
3797  }
3798  $skipCount = 0;
3799  if ( $start < strlen( $text ) ) {
3800  $skipCount = strcspn( $text, $search, $start, $len );
3801  $ret .= substr( $text, $start, $skipCount );
3802  }
3803  return $skipCount;
3804  }
3805 
3815  private function truncate_endBracket( &$tag, $tagType, $lastCh, &$openTags ) {
3816  $tag = ltrim( $tag );
3817  if ( $tag != '' ) {
3818  if ( $tagType == 0 && $lastCh != '/' ) {
3819  $openTags[] = $tag; // tag opened (didn't close itself)
3820  } elseif ( $tagType == 1 ) {
3821  if ( $openTags && $tag == $openTags[count( $openTags ) - 1] ) {
3822  array_pop( $openTags ); // tag closed
3823  }
3824  }
3825  $tag = '';
3826  }
3827  }
3828 
3837  function convertGrammar( $word, $case ) {
3838  global $wgGrammarForms;
3839  if ( isset( $wgGrammarForms[$this->getCode()][$case][$word] ) ) {
3840  return $wgGrammarForms[$this->getCode()][$case][$word];
3841  }
3842 
3843  $grammarTransformations = $this->getGrammarTransformations();
3844 
3845  if ( isset( $grammarTransformations[$case] ) ) {
3846  $forms = $grammarTransformations[$case];
3847 
3848  // Some names of grammar rules are aliases for other rules.
3849  // In such cases the value is a string rather than object,
3850  // so load the actual rules.
3851  if ( is_string( $forms ) ) {
3852  $forms = $grammarTransformations[$forms];
3853  }
3854 
3855  foreach ( array_values( $forms ) as $rule ) {
3856  $form = $rule[0];
3857 
3858  if ( $form === '@metadata' ) {
3859  continue;
3860  }
3861 
3862  $replacement = $rule[1];
3863 
3864  $regex = '/' . addcslashes( $form, '/' ) . '/u';
3865  $patternMatches = preg_match( $regex, $word );
3866 
3867  if ( $patternMatches === false ) {
3868  wfLogWarning(
3869  'An error occurred while processing grammar. ' .
3870  "Word: '$word'. Regex: /$form/."
3871  );
3872  } elseif ( $patternMatches === 1 ) {
3873  $word = preg_replace( $regex, $replacement, $word );
3874 
3875  break;
3876  }
3877  }
3878  }
3879 
3880  return $word;
3881  }
3882 
3888  function getGrammarForms() {
3889  global $wgGrammarForms;
3890  if ( isset( $wgGrammarForms[$this->getCode()] )
3891  && is_array( $wgGrammarForms[$this->getCode()] )
3892  ) {
3893  return $wgGrammarForms[$this->getCode()];
3894  }
3895 
3896  return [];
3897  }
3898 
3908  public function getGrammarTransformations() {
3909  $languageCode = $this->getCode();
3910 
3911  if ( self::$grammarTransformations === null ) {
3912  self::$grammarTransformations = new MapCacheLRU( 10 );
3913  }
3914 
3915  if ( self::$grammarTransformations->has( $languageCode ) ) {
3916  return self::$grammarTransformations->get( $languageCode );
3917  }
3918 
3919  $data = [];
3920 
3921  $grammarDataFile = __DIR__ . "/data/grammarTransformations/$languageCode.json";
3922  if ( is_readable( $grammarDataFile ) ) {
3923  $data = FormatJson::decode(
3924  file_get_contents( $grammarDataFile ),
3925  true
3926  );
3927 
3928  if ( $data === null ) {
3929  throw new MWException( "Invalid grammar data for \"$languageCode\"." );
3930  }
3931 
3932  self::$grammarTransformations->set( $languageCode, $data );
3933  }
3934 
3935  return $data;
3936  }
3937 
3957  function gender( $gender, $forms ) {
3958  if ( !count( $forms ) ) {
3959  return '';
3960  }
3961  $forms = $this->preConvertPlural( $forms, 2 );
3962  if ( $gender === 'male' ) {
3963  return $forms[0];
3964  }
3965  if ( $gender === 'female' ) {
3966  return $forms[1];
3967  }
3968  return $forms[2] ?? $forms[0];
3969  }
3970 
3986  function convertPlural( $count, $forms ) {
3987  // Handle explicit n=pluralform cases
3988  $forms = $this->handleExplicitPluralForms( $count, $forms );
3989  if ( is_string( $forms ) ) {
3990  return $forms;
3991  }
3992  if ( !count( $forms ) ) {
3993  return '';
3994  }
3995 
3996  $pluralForm = $this->getPluralRuleIndexNumber( $count );
3997  $pluralForm = min( $pluralForm, count( $forms ) - 1 );
3998  return $forms[$pluralForm];
3999  }
4000 
4016  protected function handleExplicitPluralForms( $count, array $forms ) {
4017  foreach ( $forms as $index => $form ) {
4018  if ( preg_match( '/\d+=/i', $form ) ) {
4019  $pos = strpos( $form, '=' );
4020  if ( substr( $form, 0, $pos ) === (string)$count ) {
4021  return substr( $form, $pos + 1 );
4022  }
4023  unset( $forms[$index] );
4024  }
4025  }
4026  return array_values( $forms );
4027  }
4028 
4037  protected function preConvertPlural( /* Array */ $forms, $count ) {
4038  return array_pad( $forms, $count, end( $forms ) );
4039  }
4040 
4057  public function embedBidi( $text = '' ) {
4058  $dir = self::strongDirFromContent( $text );
4059  if ( $dir === 'ltr' ) {
4060  // Wrap in LEFT-TO-RIGHT EMBEDDING ... POP DIRECTIONAL FORMATTING
4061  return self::$lre . $text . self::$pdf;
4062  }
4063  if ( $dir === 'rtl' ) {
4064  // Wrap in RIGHT-TO-LEFT EMBEDDING ... POP DIRECTIONAL FORMATTING
4065  return self::$rle . $text . self::$pdf;
4066  }
4067  // No strong directionality: do not wrap
4068  return $text;
4069  }
4070 
4084  function translateBlockExpiry( $str, User $user = null, $now = 0 ) {
4085  $duration = SpecialBlock::getSuggestedDurations( $this );
4086  foreach ( $duration as $show => $value ) {
4087  if ( strcmp( $str, $value ) == 0 ) {
4088  return htmlspecialchars( trim( $show ) );
4089  }
4090  }
4091 
4092  if ( wfIsInfinity( $str ) ) {
4093  foreach ( $duration as $show => $value ) {
4094  if ( wfIsInfinity( $value ) ) {
4095  return htmlspecialchars( trim( $show ) );
4096  }
4097  }
4098  }
4099 
4100  // If all else fails, return a standard duration or timestamp description.
4101  $time = strtotime( $str, $now );
4102  if ( $time === false ) { // Unknown format. Return it as-is in case.
4103  return $str;
4104  } elseif ( $time !== strtotime( $str, $now + 1 ) ) { // It's a relative timestamp.
4105  // The result differs based on current time, so the difference
4106  // is a fixed duration length.
4107  return $this->formatDuration( $time - $now );
4108  } else { // It's an absolute timestamp.
4109  if ( $time === 0 ) {
4110  // wfTimestamp() handles 0 as current time instead of epoch.
4111  $time = '19700101000000';
4112  }
4113  if ( $user ) {
4114  return $this->userTimeAndDate( $time, $user );
4115  }
4116  return $this->timeanddate( $time );
4117  }
4118  }
4119 
4127  public function segmentForDiff( $text ) {
4128  return $text;
4129  }
4130 
4137  public function unsegmentForDiff( $text ) {
4138  return $text;
4139  }
4140 
4147  public function getConverter() {
4148  return $this->mConverter;
4149  }
4150 
4159  public function autoConvert( $text, $variant = false ) {
4160  return $this->mConverter->autoConvert( $text, $variant );
4161  }
4162 
4169  public function autoConvertToAllVariants( $text ) {
4170  return $this->mConverter->autoConvertToAllVariants( $text );
4171  }
4172 
4184  public function convert( $text ) {
4185  return $this->mConverter->convert( $text );
4186  }
4187 
4194  public function convertTitle( $title ) {
4195  return $this->mConverter->convertTitle( $title );
4196  }
4197 
4206  public function convertNamespace( $ns, $variant = null ) {
4207  return $this->mConverter->convertNamespace( $ns, $variant );
4208  }
4209 
4215  public function hasVariants() {
4216  return count( $this->getVariants() ) > 1;
4217  }
4218 
4229  public function hasVariant( $variant ) {
4230  return $variant && ( $variant === $this->mConverter->validateVariant( $variant ) );
4231  }
4232 
4239  public function convertHtml( $text ) {
4240  return htmlspecialchars( $this->convert( $text ) );
4241  }
4242 
4247  public function convertCategoryKey( $key ) {
4248  return $this->mConverter->convertCategoryKey( $key );
4249  }
4250 
4257  public function getVariants() {
4258  return $this->mConverter->getVariants();
4259  }
4260 
4264  public function getPreferredVariant() {
4265  return $this->mConverter->getPreferredVariant();
4266  }
4267 
4271  public function getDefaultVariant() {
4272  return $this->mConverter->getDefaultVariant();
4273  }
4274 
4278  public function getURLVariant() {
4279  return $this->mConverter->getURLVariant();
4280  }
4281 
4294  public function findVariantLink( &$link, &$nt, $ignoreOtherCond = false ) {
4295  $this->mConverter->findVariantLink( $link, $nt, $ignoreOtherCond );
4296  }
4297 
4304  function getExtraHashOptions() {
4305  return $this->mConverter->getExtraHashOptions();
4306  }
4307 
4315  public function getParsedTitle() {
4316  return $this->mConverter->getParsedTitle();
4317  }
4318 
4325  public function updateConversionTable( Title $title ) {
4326  $this->mConverter->updateConversionTable( $title );
4327  }
4328 
4335  public function linkTrail() {
4336  return self::$dataCache->getItem( $this->mCode, 'linkTrail' );
4337  }
4338 
4345  public function linkPrefixCharset() {
4346  return self::$dataCache->getItem( $this->mCode, 'linkPrefixCharset' );
4347  }
4348 
4356  public function getParentLanguage() {
4357  if ( $this->mParentLanguage !== false ) {
4358  return $this->mParentLanguage;
4359  }
4360 
4361  $code = explode( '-', $this->getCode() )[0];
4362  if ( !in_array( $code, LanguageConverter::$languagesWithVariants ) ) {
4363  $this->mParentLanguage = null;
4364  return null;
4365  }
4366  $lang = self::factory( $code );
4367  if ( !$lang->hasVariant( $this->getCode() ) ) {
4368  $this->mParentLanguage = null;
4369  return null;
4370  }
4371 
4372  $this->mParentLanguage = $lang;
4373  return $lang;
4374  }
4375 
4383  public function equals( Language $lang ) {
4384  return $lang === $this || $lang->getCode() === $this->mCode;
4385  }
4386 
4395  public function getCode() {
4396  return $this->mCode;
4397  }
4398 
4409  public function getHtmlCode() {
4410  if ( is_null( $this->mHtmlCode ) ) {
4411  $this->mHtmlCode = LanguageCode::bcp47( $this->getCode() );
4412  }
4413  return $this->mHtmlCode;
4414  }
4415 
4423  public static function getCodeFromFileName( $filename, $prefix = 'Language', $suffix = '.php' ) {
4424  $m = null;
4425  preg_match( '/' . preg_quote( $prefix, '/' ) . '([A-Z][a-z_]+)' .
4426  preg_quote( $suffix, '/' ) . '/', $filename, $m );
4427  if ( !count( $m ) ) {
4428  return false;
4429  }
4430  return str_replace( '_', '-', strtolower( $m[1] ) );
4431  }
4432 
4438  public static function classFromCode( $code, $fallback = true ) {
4439  if ( $fallback && $code == 'en' ) {
4440  return 'Language';
4441  } else {
4442  return 'Language' . str_replace( '-', '_', ucfirst( $code ) );
4443  }
4444  }
4445 
4454  public static function getFileName( $prefix, $code, $suffix = '.php' ) {
4455  if ( !self::isValidBuiltInCode( $code ) ) {
4456  throw new MWException( "Invalid language code \"$code\"" );
4457  }
4458 
4459  return $prefix . str_replace( '-', '_', ucfirst( $code ) ) . $suffix;
4460  }
4461 
4466  public static function getMessagesFileName( $code ) {
4467  global $IP;
4468  $file = self::getFileName( "$IP/languages/messages/Messages", $code, '.php' );
4469  Hooks::run( 'Language::getMessagesFileName', [ $code, &$file ] );
4470  return $file;
4471  }
4472 
4479  public static function getJsonMessagesFileName( $code ) {
4480  global $IP;
4481 
4482  if ( !self::isValidBuiltInCode( $code ) ) {
4483  throw new MWException( "Invalid language code \"$code\"" );
4484  }
4485 
4486  return "$IP/languages/i18n/$code.json";
4487  }
4488 
4496  public static function getFallbackFor( $code ) {
4497  $fallbacks = self::getFallbacksFor( $code );
4498  if ( $fallbacks ) {
4499  return $fallbacks[0];
4500  }
4501  return false;
4502  }
4503 
4514  public static function getFallbacksFor( $code, $mode = self::MESSAGES_FALLBACKS ) {
4515  if ( $code === 'en' || !self::isValidBuiltInCode( $code ) ) {
4516  return [];
4517  }
4518  switch ( $mode ) {
4519  case self::MESSAGES_FALLBACKS:
4520  // For unknown languages, fallbackSequence returns an empty array,
4521  // hardcode fallback to 'en' in that case as English messages are
4522  // always defined.
4523  return self::getLocalisationCache()->getItem( $code, 'fallbackSequence' ) ?: [ 'en' ];
4524  case self::STRICT_FALLBACKS:
4525  // Use this mode when you don't want to fallback to English unless
4526  // explicitly defined, for example when you have language-variant icons
4527  // and an international language-independent fallback.
4528  return self::getLocalisationCache()->getItem( $code, 'originalFallbackSequence' );
4529  default:
4530  throw new MWException( "Invalid fallback mode \"$mode\"" );
4531  }
4532  }
4533 
4542  public static function getFallbacksIncludingSiteLanguage( $code ) {
4543  global $wgLanguageCode;
4544 
4545  // Usually, we will only store a tiny number of fallback chains, so we
4546  // keep them in static memory.
4547  $cacheKey = "{$code}-{$wgLanguageCode}";
4548 
4549  if ( !array_key_exists( $cacheKey, self::$fallbackLanguageCache ) ) {
4550  $fallbacks = self::getFallbacksFor( $code );
4551 
4552  // Append the site's fallback chain, including the site language itself
4553  $siteFallbacks = self::getFallbacksFor( $wgLanguageCode );
4554  array_unshift( $siteFallbacks, $wgLanguageCode );
4555 
4556  // Eliminate any languages already included in the chain
4557  $siteFallbacks = array_diff( $siteFallbacks, $fallbacks );
4558 
4559  self::$fallbackLanguageCache[$cacheKey] = [ $fallbacks, $siteFallbacks ];
4560  }
4561  return self::$fallbackLanguageCache[$cacheKey];
4562  }
4563 
4573  public static function getMessagesFor( $code ) {
4574  return self::getLocalisationCache()->getItem( $code, 'messages' );
4575  }
4576 
4585  public static function getMessageFor( $key, $code ) {
4586  return self::getLocalisationCache()->getSubitem( $code, 'messages', $key );
4587  }
4588 
4597  public static function getMessageKeysFor( $code ) {
4598  return self::getLocalisationCache()->getSubitemList( $code, 'messages' );
4599  }
4600 
4605  function fixVariableInNamespace( $talk ) {
4606  if ( strpos( $talk, '$1' ) === false ) {
4607  return $talk;
4608  }
4609 
4610  global $wgMetaNamespace;
4611  $talk = str_replace( '$1', $wgMetaNamespace, $talk );
4612 
4613  # Allow grammar transformations
4614  # Allowing full message-style parsing would make simple requests
4615  # such as action=raw much more expensive than they need to be.
4616  # This will hopefully cover most cases.
4617  $talk = preg_replace_callback( '/{{grammar:(.*?)\|(.*?)}}/i',
4618  [ $this, 'replaceGrammarInNamespace' ], $talk );
4619  return str_replace( ' ', '_', $talk );
4620  }
4621 
4626  function replaceGrammarInNamespace( $m ) {
4627  return $this->convertGrammar( trim( $m[2] ), trim( $m[1] ) );
4628  }
4629 
4640  public function formatExpiry( $expiry, $format = true, $infinity = 'infinity' ) {
4641  static $dbInfinity;
4642  if ( $dbInfinity === null ) {
4643  $dbInfinity = wfGetDB( DB_REPLICA )->getInfinity();
4644  }
4645 
4646  if ( $expiry == '' || $expiry === 'infinity' || $expiry == $dbInfinity ) {
4647  return $format === true
4648  ? $this->getMessageFromDB( 'infiniteblock' )
4649  : $infinity;
4650  } else {
4651  return $format === true
4652  ? $this->timeanddate( $expiry, /* User preference timezone */ true )
4653  : wfTimestamp( $format, $expiry );
4654  }
4655  }
4656 
4671  function formatTimePeriod( $seconds, $format = [] ) {
4672  if ( !is_array( $format ) ) {
4673  $format = [ 'avoid' => $format ]; // For backwards compatibility
4674  }
4675  if ( !isset( $format['avoid'] ) ) {
4676  $format['avoid'] = false;
4677  }
4678  if ( !isset( $format['noabbrevs'] ) ) {
4679  $format['noabbrevs'] = false;
4680  }
4681  $secondsMsg = wfMessage(
4682  $format['noabbrevs'] ? 'seconds' : 'seconds-abbrev' )->inLanguage( $this );
4683  $minutesMsg = wfMessage(
4684  $format['noabbrevs'] ? 'minutes' : 'minutes-abbrev' )->inLanguage( $this );
4685  $hoursMsg = wfMessage(
4686  $format['noabbrevs'] ? 'hours' : 'hours-abbrev' )->inLanguage( $this );
4687  $daysMsg = wfMessage(
4688  $format['noabbrevs'] ? 'days' : 'days-abbrev' )->inLanguage( $this );
4689 
4690  if ( round( $seconds * 10 ) < 100 ) {
4691  $s = $this->formatNum( sprintf( "%.1f", round( $seconds * 10 ) / 10 ) );
4692  $s = $secondsMsg->params( $s )->text();
4693  } elseif ( round( $seconds ) < 60 ) {
4694  $s = $this->formatNum( round( $seconds ) );
4695  $s = $secondsMsg->params( $s )->text();
4696  } elseif ( round( $seconds ) < 3600 ) {
4697  $minutes = floor( $seconds / 60 );
4698  $secondsPart = round( fmod( $seconds, 60 ) );
4699  if ( $secondsPart == 60 ) {
4700  $secondsPart = 0;
4701  $minutes++;
4702  }
4703  $s = $minutesMsg->params( $this->formatNum( $minutes ) )->text();
4704  $s .= ' ';
4705  $s .= $secondsMsg->params( $this->formatNum( $secondsPart ) )->text();
4706  } elseif ( round( $seconds ) <= 2 * 86400 ) {
4707  $hours = floor( $seconds / 3600 );
4708  $minutes = floor( ( $seconds - $hours * 3600 ) / 60 );
4709  $secondsPart = round( $seconds - $hours * 3600 - $minutes * 60 );
4710  if ( $secondsPart == 60 ) {
4711  $secondsPart = 0;
4712  $minutes++;
4713  }
4714  if ( $minutes == 60 ) {
4715  $minutes = 0;
4716  $hours++;
4717  }
4718  $s = $hoursMsg->params( $this->formatNum( $hours ) )->text();
4719  $s .= ' ';
4720  $s .= $minutesMsg->params( $this->formatNum( $minutes ) )->text();
4721  if ( !in_array( $format['avoid'], [ 'avoidseconds', 'avoidminutes', 'avoidhours' ] ) ) {
4722  $s .= ' ' . $secondsMsg->params( $this->formatNum( $secondsPart ) )->text();
4723  }
4724  } else {
4725  $days = floor( $seconds / 86400 );
4726  if ( $format['avoid'] === 'avoidhours' ) {
4727  $hours = round( ( $seconds - $days * 86400 ) / 3600 );
4728  if ( $hours == 24 ) {
4729  $hours = 0;
4730  $days++;
4731  }
4732  $s = $daysMsg->params( $this->formatNum( $days ) )->text();
4733  } elseif ( $format['avoid'] === 'avoidminutes' ) {
4734  $hours = round( ( $seconds - $days * 86400 ) / 3600 );
4735  if ( $hours == 24 ) {
4736  $hours = 0;
4737  $days++;
4738  }
4739  $s = $daysMsg->params( $this->formatNum( $days ) )->text();
4740  $s .= ' ';
4741  $s .= $hoursMsg->params( $this->formatNum( $hours ) )->text();
4742  } elseif ( $format['avoid'] === 'avoidseconds' ) {
4743  $hours = floor( ( $seconds - $days * 86400 ) / 3600 );
4744  $minutes = round( ( $seconds - $days * 86400 - $hours * 3600 ) / 60 );
4745  if ( $minutes == 60 ) {
4746  $minutes = 0;
4747  $hours++;
4748  }
4749  if ( $hours == 24 ) {
4750  $hours = 0;
4751  $days++;
4752  }
4753  $s = $daysMsg->params( $this->formatNum( $days ) )->text();
4754  $s .= ' ';
4755  $s .= $hoursMsg->params( $this->formatNum( $hours ) )->text();
4756  $s .= ' ';
4757  $s .= $minutesMsg->params( $this->formatNum( $minutes ) )->text();
4758  } else {
4759  $s = $daysMsg->params( $this->formatNum( $days ) )->text();
4760  $s .= ' ';
4761  $s .= $this->formatTimePeriod( $seconds - $days * 86400, $format );
4762  }
4763  }
4764  return $s;
4765  }
4766 
4778  function formatBitrate( $bps ) {
4779  return $this->formatComputingNumbers( $bps, 1000, "bitrate-$1bits" );
4780  }
4781 
4788  function formatComputingNumbers( $size, $boundary, $messageKey ) {
4789  if ( $size <= 0 ) {
4790  return str_replace( '$1', $this->formatNum( $size ),
4791  $this->getMessageFromDB( str_replace( '$1', '', $messageKey ) )
4792  );
4793  }
4794  $sizes = [ '', 'kilo', 'mega', 'giga', 'tera', 'peta', 'exa', 'zeta', 'yotta' ];
4795  $index = 0;
4796 
4797  $maxIndex = count( $sizes ) - 1;
4798  while ( $size >= $boundary && $index < $maxIndex ) {
4799  $index++;
4800  $size /= $boundary;
4801  }
4802 
4803  // For small sizes no decimal places necessary
4804  $round = 0;
4805  if ( $index > 1 ) {
4806  // For MB and bigger two decimal places are smarter
4807  $round = 2;
4808  }
4809  $msg = str_replace( '$1', $sizes[$index], $messageKey );
4810 
4811  $size = round( $size, $round );
4812  $text = $this->getMessageFromDB( $msg );
4813  return str_replace( '$1', $this->formatNum( $size ), $text );
4814  }
4815 
4826  function formatSize( $size ) {
4827  return $this->formatComputingNumbers( $size, 1024, "size-$1bytes" );
4828  }
4829 
4839  function specialList( $page, $details, $oppositedm = true ) {
4840  if ( !$details ) {
4841  return $page;
4842  }
4843 
4844  $dirmark = ( $oppositedm ? $this->getDirMark( true ) : '' ) . $this->getDirMark();
4845  return $page .
4846  $dirmark .
4847  $this->msg( 'word-separator' )->escaped() .
4848  $this->msg( 'parentheses' )->rawParams( $details )->escaped();
4849  }
4850 
4863  public function viewPrevNext( Title $title, $offset, $limit,
4864  array $query = [], $atend = false
4865  ) {
4866  // @todo FIXME: Why on earth this needs one message for the text and another one for tooltip?
4867 
4868  # Make 'previous' link
4869  $prev = wfMessage( 'prevn' )->inLanguage( $this )->title( $title )->numParams( $limit )->text();
4870  if ( $offset > 0 ) {
4871  $plink = $this->numLink( $title, max( $offset - $limit, 0 ), $limit,
4872  $query, $prev, 'prevn-title', 'mw-prevlink' );
4873  } else {
4874  $plink = htmlspecialchars( $prev );
4875  }
4876 
4877  # Make 'next' link
4878  $next = wfMessage( 'nextn' )->inLanguage( $this )->title( $title )->numParams( $limit )->text();
4879  if ( $atend ) {
4880  $nlink = htmlspecialchars( $next );
4881  } else {
4882  $nlink = $this->numLink( $title, $offset + $limit, $limit,
4883  $query, $next, 'nextn-title', 'mw-nextlink' );
4884  }
4885 
4886  # Make links to set number of items per page
4887  $numLinks = [];
4888  foreach ( [ 20, 50, 100, 250, 500 ] as $num ) {
4889  $numLinks[] = $this->numLink( $title, $offset, $num,
4890  $query, $this->formatNum( $num ), 'shown-title', 'mw-numlink' );
4891  }
4892 
4893  return wfMessage( 'viewprevnext' )->inLanguage( $this )->title( $title
4894  )->rawParams( $plink, $nlink, $this->pipeList( $numLinks ) )->escaped();
4895  }
4896 
4909  private function numLink( Title $title, $offset, $limit, array $query, $link,
4910  $tooltipMsg, $class
4911  ) {
4912  $query = [ 'limit' => $limit, 'offset' => $offset ] + $query;
4913  $tooltip = wfMessage( $tooltipMsg )->inLanguage( $this )->title( $title )
4914  ->numParams( $limit )->text();
4915 
4916  return Html::element( 'a', [ 'href' => $title->getLocalURL( $query ),
4917  'title' => $tooltip, 'class' => $class ], $link );
4918  }
4919 
4925  public function getConvRuleTitle() {
4926  return $this->mConverter->getConvRuleTitle();
4927  }
4928 
4934  public function getCompiledPluralRules() {
4935  $pluralRules = self::$dataCache->getItem( strtolower( $this->mCode ), 'compiledPluralRules' );
4936  $fallbacks = self::getFallbacksFor( $this->mCode );
4937  if ( !$pluralRules ) {
4938  foreach ( $fallbacks as $fallbackCode ) {
4939  $pluralRules = self::$dataCache->getItem( strtolower( $fallbackCode ), 'compiledPluralRules' );
4940  if ( $pluralRules ) {
4941  break;
4942  }
4943  }
4944  }
4945  return $pluralRules;
4946  }
4947 
4953  public function getPluralRules() {
4954  $pluralRules = self::$dataCache->getItem( strtolower( $this->mCode ), 'pluralRules' );
4955  $fallbacks = self::getFallbacksFor( $this->mCode );
4956  if ( !$pluralRules ) {
4957  foreach ( $fallbacks as $fallbackCode ) {
4958  $pluralRules = self::$dataCache->getItem( strtolower( $fallbackCode ), 'pluralRules' );
4959  if ( $pluralRules ) {
4960  break;
4961  }
4962  }
4963  }
4964  return $pluralRules;
4965  }
4966 
4972  public function getPluralRuleTypes() {
4973  $pluralRuleTypes = self::$dataCache->getItem( strtolower( $this->mCode ), 'pluralRuleTypes' );
4974  $fallbacks = self::getFallbacksFor( $this->mCode );
4975  if ( !$pluralRuleTypes ) {
4976  foreach ( $fallbacks as $fallbackCode ) {
4977  $pluralRuleTypes = self::$dataCache->getItem( strtolower( $fallbackCode ), 'pluralRuleTypes' );
4978  if ( $pluralRuleTypes ) {
4979  break;
4980  }
4981  }
4982  }
4983  return $pluralRuleTypes;
4984  }
4985 
4991  public function getPluralRuleIndexNumber( $number ) {
4992  $pluralRules = $this->getCompiledPluralRules();
4993  $form = Evaluator::evaluateCompiled( $number, $pluralRules );
4994  return $form;
4995  }
4996 
5005  public function getPluralRuleType( $number ) {
5006  $index = $this->getPluralRuleIndexNumber( $number );
5007  $pluralRuleTypes = $this->getPluralRuleTypes();
5008  return $pluralRuleTypes[$index] ?? 'other';
5009  }
5010 }
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:241
getMessage( $key)
Definition: Language.php:2648
getPluralRuleType( $number)
Find the plural rule type appropriate for the given number For example, if the language is set to Ara...
Definition: Language.php:5005
getIranianCalendarMonthName( $key)
Definition: Language.php:1033
static $mMonthAbbrevMsgs
Definition: Language.php:116
$mParentLanguage
Definition: Language.php:64
getSpecialPageAliases()
Get special page names, as an associative array canonical name => array of valid names, including aliases.
Definition: Language.php:3245
getPluralRuleTypes()
Get the plural rule types for the language.
Definition: Language.php:4972
firstChar( $s)
Get the first character of a string.
Definition: Language.php:2985
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:2542
static getLocalisationCache()
Get the LocalisationCache instance.
Definition: Language.php:448
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
ucwords( $str)
Definition: Language.php:2810
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:2073
translateBlockExpiry( $str, User $user=null, $now=0)
Definition: Language.php:4084
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:1585
ucwordbreaksCallbackAscii( $matches)
Definition: Language.php:2682
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:2920
normalize( $s)
Convert a UTF-8 string to normal form C.
Definition: Language.php:3048
handleExplicitPluralForms( $count, array $forms)
Handles explicit plural forms for Language::convertPlural()
Definition: Language.php:4016
static getTitleInvalidRegex()
Returns a simple regex that will match on characters and sequences invalid in titles.
static LocalisationCache $dataCache
Definition: Language.php:81
getDurationIntervals( $seconds, array $chosenIntervals=[])
Takes a number of seconds and returns an array with a set of corresponding intervals.
Definition: Language.php:2387
getVariantname( $code, $usemsg=true)
short names for language variants used for language conversion links.
Definition: Language.php:765
setNamespaces(array $namespaces)
Arbitrarily set all of the namespace names at once.
Definition: Language.php:547
static HashBagOStuff null $languageNameCache
Cache for language names.
Definition: Language.php:185
removeBadCharLast( $string)
Remove bytes that represent an incomplete Unicode character at the end of string (e.g.
Definition: Language.php:3621
static romanNumeral( $num)
Roman number formatting up to 10000.
Definition: Language.php:2042
getWeekdayAbbreviation( $key)
Definition: Language.php:1025
getDirMark( $opposite=false)
A hidden direction mark (LRM or RLM), depending on the language direction.
Definition: Language.php:3151
getHtmlCode()
Get the code in BCP 47 format which we can use inside of html lang="" tags.
Definition: Language.php:4409
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:3792
$IP
Definition: WebStart.php:41
equals(Language $lang)
Compare with an other language object.
Definition: Language.php:4383
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:1982
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
lcfirst( $str)
Definition: Language.php:2767
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Definition: router.php:42
static $IRANIAN_DAYS
Definition: Language.php:1617
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException' returning false will NOT prevent logging $e
Definition: hooks.txt:2159
offsetForUser(User $user)
Adjust the timestamp depending on the given user&#39;s preferences.
Definition: MWTimestamp.php:79
getHebrewCalendarMonthNameGen( $key)
Definition: Language.php:1049
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:3119
static insertSpace( $string, $pattern)
Definition: Language.php:2963
getFallbackLanguages()
Definition: Language.php:488
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:2523
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:990
$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:71
hasVariants()
Check if this is a language with variants.
Definition: Language.php:4215
getOption( $oname, $defaultOverride=null, $ignoreHidden=false)
Get the user&#39;s current setting for a given option.
Definition: User.php:3028
getCompiledPluralRules()
Get the compiled plural rules for the language.
Definition: Language.php:4934
getLocalNsIndex( $text)
Get a namespace key by value, case insensitive.
Definition: Language.php:656
convertCategoryKey( $key)
Definition: Language.php:4247
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:1993
truncateForDatabase( $string, $length, $ellipsis='...', $adjustLength=true)
Truncate a string to a specified length in bytes, appending an optional string (e.g.
Definition: Language.php:3518
getHebrewCalendarMonthName( $key)
Definition: Language.php:1041
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
const ALL
Return all known languages in fetchLanguageName(s).
Definition: Language.php:48
$wgMetaNamespace
Name of the project namespace.
fallback8bitEncoding()
Definition: Language.php:2897
wfIsInfinity( $str)
Determine input string is represents as infinity.
unsegmentForDiff( $text)
and unsegment to show the result
Definition: Language.php:4137
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:3815
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:4335
getAllMessages()
Definition: Language.php:2655
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 MediaWikiServices
Definition: injection.txt:23
$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:4991
getVariants()
Get the list of variants supported by this language see sample implementation in LanguageZh.php.
Definition: Language.php:4257
A helper class for throttling authentication attempts.
$dateFormatStrings
Definition: Language.php:66
static hebrewYearStart( $year)
This calculates the Hebrew year start, as days since 1 September.
Definition: Language.php:1881
getNamespaceAliases()
Definition: Language.php:665
getWeekdayName( $key)
Definition: Language.php:1017
minimumGroupingDigits()
Definition: Language.php:3435
static $mMonthMsgs
Definition: Language.php:106
convertForSearchResult( $termsArray)
Definition: Language.php:2973
see documentation in includes Linker php for Linker::makeImageLink & $time
Definition: hooks.txt:1799
$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:4454
convertTitle( $title)
Convert a Title object to a string in the preferred variant.
Definition: Language.php:4194
static $mLangObjCache
Definition: Language.php:83
static classFromCode( $code, $fallback=true)
Definition: Language.php:4438
internalUserTimeAndDate( $type, $ts, User $user, array $options)
Internal helper function for userDate(), userTime() and userTimeAndDate()
Definition: Language.php:2438
array $wgOverrideUcfirstCharacters
List of Unicode characters for which capitalization is overridden in Language::ucfirst.
$wgTranslateNumerals
For Hindi and Arabic use local numerals instead of Western style (0-9) numerals in interface...
getHijriCalendarMonthName( $key)
Definition: Language.php:1057
static $mWeekdayAbbrevMsgs
Definition: Language.php:102
getMonthAbbreviation( $key)
Definition: Language.php:998
lc( $str, $first=false)
Definition: Language.php:2786
ucwordbreaksCallbackMB( $matches)
Definition: Language.php:2690
$mMagicExtensions
Definition: Language.php:63
static $pdf
Definition: Language.php:192
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:413
usually copyright or history_copyright This message must be in HTML not wikitext & $link
Definition: hooks.txt:3051
The User object encapsulates all of the user-specific settings (user_id, name, rights, email address, options, last login time).
Definition: User.php:51
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not it can be in the form of< username >< more info > e g for bot passwords intended to be added to log contexts Fields it might only if the login was with a bot password 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:780
__construct()
Definition: Language.php:457
truncateHtml( $text, $length, $ellipsis='...')
Truncate a string of valid HTML to a specified length in bytes, appending an optional string (e...
Definition: Language.php:3673
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:1982
ucfirst( $str)
Make a string&#39;s first character uppercase.
Definition: Language.php:2709
$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:3484
userAdjust( $ts, $tz=false)
Used by date() and time() to adjust the time output.
Definition: Language.php:2160
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:4585
static $lre
Unicode directional formatting characters, for embedBidi()
Definition: Language.php:190
formatNum( $number, $nocommafy=false)
Normally we output all numbers in plain en_US style, that is 293,291.235 for twohundredninetythreetho...
Definition: Language.php:3288
static getFallbackFor( $code)
Get the first fallback for a given language.
Definition: Language.php:4496
timeanddate( $ts, $adj=false, $format=true, $timecorrection=false)
Definition: Language.php:2342
formatExpiry( $expiry, $format=true, $infinity='infinity')
Decode an expiry (block, protection, etc) which has come from the DB.
Definition: Language.php:4640
listToText(array $list)
Take a list of strings and build a locale-friendly comma-separated list, using the local comma-separa...
Definition: Language.php:3447
static tsToYear( $ts, $cName)
Algorithm to convert Gregorian dates to Thai solar dates, Minguo dates or Minguo dates.
Definition: Language.php:1919
const NS_PROJECT
Definition: Defines.php:64
static $mHijriCalendarMonthMsgs
Definition: Language.php:144
linkPrefixCharset()
A regular expression character set to match legal word-prefixing characters which should be merged on...
Definition: Language.php:4345
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:4264
LanguageConverter $mConverter
Definition: Language.php:60
static getMessagesFor( $code)
Get all messages for a given language WARNING: this may take a long time.
Definition: Language.php:4573
$minimumGroupingDigits
removeBadCharFirst( $string)
Remove bytes that represent an incomplete Unicode character at the start of string (e...
Definition: Language.php:3647
getDirMarkEntity( $opposite=false)
A hidden direction mark (LRM or RLM), depending on the language direction.
Definition: Language.php:3134
__destruct()
Reduce memory usage.
Definition: Language.php:471
static strongDirFromContent( $text='')
Gets directionality of the first strongly directional codepoint, for embedBidi()
Definition: Language.php:2025
getMagic( $mw)
Fill a MagicWord object with data from here.
Definition: Language.php:3213
static getMessageKeysFor( $code)
Get all message keys for a given language.
Definition: Language.php:4597
$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:4542
getDefaultVariant()
Definition: Language.php:4271
getNsText( $index)
Get a namespace value by key.
Definition: Language.php:586
const STRICT_FALLBACKS
Return a strict fallback chain in getFallbacksFor.
Definition: Language.php:95
viewPrevNext(Title $title, $offset, $limit, array $query=[], $atend=false)
Generate (prev x| next x) (20|50|100...) type links for paging.
Definition: Language.php:4863
getConverter()
Return the LanguageConverter used in the Language.
Definition: Language.php:4147
static $mIranianCalendarMonthMsgs
Definition: Language.php:121
const NS_PROJECT_TALK
Definition: Defines.php:65
autoConvertToAllVariants( $text)
convert text to all supported variants
Definition: Language.php:4169
getBookstoreList()
Exports $wgBookstoreListEn.
Definition: Language.php:496
capitalizeAllNouns()
Definition: Language.php:3163
static decode( $value, $assoc=false)
Decodes a JSON string.
Definition: FormatJson.php:174
$wgDummyLanguageCodes
Functionally the same as $wgExtraLanguageCodes, but deprecated.
emphasize( $text)
Italic is unsuitable for some languages.
Definition: Language.php:3262
date( $ts, $adj=false, $format=true, $timecorrection=false)
Definition: Language.php:2303
isMultibyte( $str)
Definition: Language.php:2802
segmentForDiff( $text)
languages like Chinese need to be segmented in order for the diff to be of any use ...
Definition: Language.php:4127
$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:2931
static $strongDirRegex
Directionality test regex for embedBidi().
Definition: Language.php:207
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:1982
static tsToHijri( $ts)
Converting Gregorian dates to Hijri dates.
Definition: Language.php:1691
needsGenderDistinction()
Whether this language uses gender-dependent namespace aliases.
Definition: Language.php:632
getURLVariant()
Definition: Language.php:4278
caseFold( $s)
Return a case-folded representation of $s.
Definition: Language.php:2874
updateConversionTable(Title $title)
Refresh the cache of conversion tables when MediaWiki:Conversiontable* is updated.
Definition: Language.php:4325
mbUpperChar( $char)
Convert character to uppercase, allowing overrides of the default mb_upper behaviour, which is buggy in many ways.
Definition: Language.php:2754
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not null
Definition: hooks.txt:780
static $rle
Definition: Language.php:191
$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:925
static tsToIranian( $ts)
Algorithm by Roozbeh Pournader and Mohammad Toossi to convert Gregorian dates to Iranian dates...
Definition: Language.php:1631
getNamespaceIds()
Definition: Language.php:717
static factory( $code)
Get a cached or new language object for a given language code.
Definition: Language.php:216
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:2238
static $mHebrewCalendarMonthMsgs
Definition: Language.php:128
pipeList(array $list)
Same as commaList, but separate it with the pipe instead.
Definition: Language.php:3496
getGenderNsText( $index, $gender)
Returns gender-dependent namespace alias if available.
Definition: Language.php:617
getFormattedNsText( $index)
A convenience function that returns the same thing as getNsText() except with &#39;_&#39; changed to &#39; &#39;...
Definition: Language.php:604
$mExtendedSpecialPageAliases
Definition: Language.php:67
namespace and then decline to actually register it & $namespaces
Definition: hooks.txt:925
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not it can be in the form of< username >< more info > e g for bot passwords intended to be added to log contexts Fields it might only if the login was with a bot password 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:780
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:4953
getDefaultDateFormat()
Definition: Language.php:796
embedBidi( $text='')
Wraps argument with unicode control characters for directionality safety.
Definition: Language.php:4057
resetNamespaces()
Resets all of the namespace caches.
Definition: Language.php:555
getDateFormatString( $type, $pref)
Get a format string for a given type and preference.
Definition: Language.php:2269
static MapCacheLRU null $grammarTransformations
Cache for grammar rules data.
Definition: Language.php:179
time( $ts, $adj=false, $format=true, $timecorrection=false)
Definition: Language.php:2322
getMagicWords()
Get all magic words from cache.
Definition: Language.php:3204
getGrammarTransformations()
Get the grammar transformations data for the language.
Definition: Language.php:3908
getMonthNamesArray()
Definition: Language.php:978
numLink(Title $title, $offset, $limit, array $query, $link, $tooltipMsg, $class)
Helper function for viewPrevNext() that generates links.
Definition: Language.php:4909
static getMessagesFileName( $code)
Definition: Language.php:4466
static getDefaultOption( $opt)
Get a given default option value.
Definition: User.php:1792
convertGrammar( $word, $case)
Grammatical transformations, needed for inflected languages Invoked by putting {{grammar:case|word}} ...
Definition: Language.php:3837
digitTransformTable()
Definition: Language.php:3421
getConvRuleTitle()
Get the conversion rule title, if any.
Definition: Language.php:4925
formatTimePeriod( $seconds, $format=[])
Formats a time given in seconds into a string representation of that time.
Definition: Language.php:4671
userDate( $ts, User $user, array $options=[])
Get the formatted date for the given timestamp and formatted for the given user.
Definition: Language.php:2477
getNsIndex( $text)
Get a namespace key by value, case insensitive.
Definition: Language.php:747
addMagicWordsByLang( $newWords)
Add magic words to the extension array.
Definition: Language.php:3230
autoConvert( $text, $variant=false)
convert text to a variant
Definition: Language.php:4159
static fetchLanguageName( $code, $inLanguage=self::AS_AUTONYMS, $include=self::ALL)
Definition: Language.php:937
static getJsonMessagesFileName( $code)
Definition: Language.php:4479
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:334
ucwordbreaks( $str)
capitalize words at word breaks
Definition: Language.php:2834
$wgExtraNamespaces
Additional namespaces.
static convertDoubleWidth( $string)
convert double-width roman characters to single-width.
Definition: Language.php:2943
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:2579
getCode()
Get the internal language code for this language object.
Definition: Language.php:4395
digitGroupingPattern()
Definition: Language.php:3414
specialList( $page, $details, $oppositedm=true)
Make a list item, used by various special pages.
Definition: Language.php:4839
static isKnownLanguageTag( $tag)
Returns true if a language code is an IETF tag known to MediaWiki.
Definition: Language.php:427
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:306
convertNamespace( $ns, $variant=null)
Convert a namespace index to a string in the preferred variant.
Definition: Language.php:4206
getArrow( $direction='forwards')
An arrow, depending on the language direction.
Definition: Language.php:3174
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:4826
convertHtml( $text)
Perform output conversion on a string, and encode for safe HTML output.
Definition: Language.php:4239
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:70
commafy( $number)
Adds commas to a given number.
Definition: Language.php:3349
$transformData
ReplacementArray object caches.
Definition: Language.php:76
static getCodeFromFileName( $filename, $prefix='Language', $suffix='.php')
Get the language code from a file name.
Definition: Language.php:4423
getExtraHashOptions()
returns language specific options used by User::getPageRenderHash() for example, the preferred langua...
Definition: Language.php:4304
static getFallbacksFor( $code, $mode=self::MESSAGES_FALLBACKS)
Get the ordered list of fallback languages.
Definition: Language.php:4514
getParsedTitle()
For languages that support multiple variants, the title of an article may be displayed differently in...
Definition: Language.php:4315
static tsToHebrew( $ts)
Converting Gregorian dates to Hebrew dates.
Definition: Language.php:1743
preConvertPlural($forms, $count)
Checks that convertPlural was given an array and pads it to requested amount of forms by copying the ...
Definition: Language.php:4037
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:4294
$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:55
hasVariant( $variant)
Strict check if the language has the specific variant.
Definition: Language.php:4229
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:4778
const AS_AUTONYMS
Return autonyms in fetchLanguageName(s).
Definition: Language.php:42
transformUsingPairFile( $file, $string)
Transform a string using serialized data stored in the given file (which must be in the serialized su...
Definition: Language.php:3073
separatorTransformTable()
Definition: Language.php:3428
isRTL()
For right-to-left language support.
Definition: Language.php:3087
getDatePreference()
Get the user&#39;s preferred date format.
Definition: User.php:3351
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:1147
getParentLanguage()
Get the "parent" language which has a converter to convert a "compatible" language (in another varian...
Definition: Language.php:4356
static clearCaches()
Intended for tests that may change configuration in a way that invalidates caches.
Definition: Language.php:285
getDir()
Return the correct HTML &#39;dir&#39; attribute value for this language.
Definition: Language.php:3095
getFormattedNamespaces()
A convenience function that returns getNamespaces() with spaces instead of underscores in values...
Definition: Language.php:567
$wgLocalTZoffset
Set an offset from UTC in minutes to use for the default timezone setting for anonymous users and new...
iconv( $in, $out, $string)
Definition: Language.php:2665
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:271
static $mMonthGenMsgs
Definition: Language.php:111
static $mWeekdayMsgs
Definition: Language.php:97
linkPrefixExtension()
To allow "foo[[bar]]" to extend the link over the whole word "foobar".
Definition: Language.php:3196
msg( $msg)
Get message object in this language.
Definition: Language.php:963
const MESSAGES_FALLBACKS
Return a fallback chain for messages in getFallbacksFor.
Definition: Language.php:89
const DB_REPLICA
Definition: defines.php:25
$mNamespaceIds
Definition: Language.php:71
uc( $str, $first=false)
Convert a string to uppercase.
Definition: Language.php:2729
getGrammarForms()
Get the grammar forms for the content language.
Definition: Language.php:3888
fixVariableInNamespace( $talk)
Definition: Language.php:4605
static dateTimeObjFormat(&$dateTimeObj, $ts, $zone, $code)
Pass through result from $dateTimeObj->format()
Definition: Language.php:1069
=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:4788
initContLang()
Hook which will be called if this is the content language.
Definition: Language.php:481
userTime( $ts, User $user, array $options=[])
Get the formatted time for the given timestamp and formatted for the given user.
Definition: Language.php:2500
ucwordsCallbackMB( $matches)
Definition: Language.php:2698
getNamespaces()
Returns an array of localised namespaces indexed by their numbers.
Definition: Language.php:506
static isValidCode( $code)
Returns true if a language code string is of a valid form, whether or not it exists.
Definition: Language.php:388
commaList(array $list)
Take a list of strings and build a locale-friendly comma-separated list, using the local comma-separa...
Definition: Language.php:3471
static $mHebrewCalendarMonthGenMsgs
Definition: Language.php:136
const NS_USER_TALK
Definition: Defines.php:63
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:3542
gender( $gender, $forms)
Provides an alternative text depending on specified gender.
Definition: Language.php:3957
getMessageFromDB( $msg)
Get a message from the MediaWiki namespace.
Definition: Language.php:953
getMonthAbbreviationsArray()
Definition: Language.php:1005
hasWordBreaks()
Most writing systems use whitespace to break up words.
Definition: Language.php:2909
convert( $text)
convert text to different variants of a language.
Definition: Language.php:4184
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:3107
checkTitleEncoding( $s)
TODO: $s is not always a string per T218883.
Definition: Language.php:2883
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:3986
return true to allow those checks to and false if checking is done & $user
Definition: hooks.txt:1473
formatNumNoSeparators( $number)
Front-end for non-commafied formatNum.
Definition: Language.php:3316
parseFormattedNumber( $number)
Definition: Language.php:3324
truncateInternal( $string, $length, $ellipsis, $adjustLength, callable $measureLength, callable $getSubstring)
Internal method used for truncation.
Definition: Language.php:3568
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:4626
static $GREG_DAYS
Definition: Language.php:1616
getMonthName( $key)
Definition: Language.php:971
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:2361
$matches