MediaWiki  1.33.0
Language.php
Go to the documentation of this file.
1 <?php
29 use CLDRPluralRuleParser\Evaluator;
30 use Wikimedia\Assert\Assert;
31 
36 class Language {
41  const AS_AUTONYMS = null;
42 
47  const ALL = 'all';
48 
54  const SUPPORTED = 'mwfile';
55 
59  public $mConverter;
60 
61  public $mVariants, $mCode, $mLoaded = false;
62  public $mMagicExtensions = [];
63  private $mHtmlCode = null, $mParentLanguage = false;
64 
65  public $dateFormatStrings = [];
67 
69  protected $namespaceNames;
71 
75  public $transformData = [];
76 
80  public static $dataCache;
81 
82  public static $mLangObjCache = [];
83 
88  const MESSAGES_FALLBACKS = 0;
89 
94  const STRICT_FALLBACKS = 1;
95 
96  public static $mWeekdayMsgs = [
97  'sunday', 'monday', 'tuesday', 'wednesday', 'thursday',
98  'friday', 'saturday'
99  ];
100 
101  public static $mWeekdayAbbrevMsgs = [
102  'sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'
103  ];
104 
105  public static $mMonthMsgs = [
106  'january', 'february', 'march', 'april', 'may_long', 'june',
107  'july', 'august', 'september', 'october', 'november',
108  'december'
109  ];
110  public static $mMonthGenMsgs = [
111  'january-gen', 'february-gen', 'march-gen', 'april-gen', 'may-gen', 'june-gen',
112  'july-gen', 'august-gen', 'september-gen', 'october-gen', 'november-gen',
113  'december-gen'
114  ];
115  public static $mMonthAbbrevMsgs = [
116  'jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug',
117  'sep', 'oct', 'nov', 'dec'
118  ];
119 
120  public static $mIranianCalendarMonthMsgs = [
121  'iranian-calendar-m1', 'iranian-calendar-m2', 'iranian-calendar-m3',
122  'iranian-calendar-m4', 'iranian-calendar-m5', 'iranian-calendar-m6',
123  'iranian-calendar-m7', 'iranian-calendar-m8', 'iranian-calendar-m9',
124  'iranian-calendar-m10', 'iranian-calendar-m11', 'iranian-calendar-m12'
125  ];
126 
127  public static $mHebrewCalendarMonthMsgs = [
128  'hebrew-calendar-m1', 'hebrew-calendar-m2', 'hebrew-calendar-m3',
129  'hebrew-calendar-m4', 'hebrew-calendar-m5', 'hebrew-calendar-m6',
130  'hebrew-calendar-m7', 'hebrew-calendar-m8', 'hebrew-calendar-m9',
131  'hebrew-calendar-m10', 'hebrew-calendar-m11', 'hebrew-calendar-m12',
132  'hebrew-calendar-m6a', 'hebrew-calendar-m6b'
133  ];
134 
136  'hebrew-calendar-m1-gen', 'hebrew-calendar-m2-gen', 'hebrew-calendar-m3-gen',
137  'hebrew-calendar-m4-gen', 'hebrew-calendar-m5-gen', 'hebrew-calendar-m6-gen',
138  'hebrew-calendar-m7-gen', 'hebrew-calendar-m8-gen', 'hebrew-calendar-m9-gen',
139  'hebrew-calendar-m10-gen', 'hebrew-calendar-m11-gen', 'hebrew-calendar-m12-gen',
140  'hebrew-calendar-m6a-gen', 'hebrew-calendar-m6b-gen'
141  ];
142 
143  public static $mHijriCalendarMonthMsgs = [
144  'hijri-calendar-m1', 'hijri-calendar-m2', 'hijri-calendar-m3',
145  'hijri-calendar-m4', 'hijri-calendar-m5', 'hijri-calendar-m6',
146  'hijri-calendar-m7', 'hijri-calendar-m8', 'hijri-calendar-m9',
147  'hijri-calendar-m10', 'hijri-calendar-m11', 'hijri-calendar-m12'
148  ];
149 
154  public static $durationIntervals = [
155  'millennia' => 31556952000,
156  'centuries' => 3155695200,
157  'decades' => 315569520,
158  'years' => 31556952, // 86400 * ( 365 + ( 24 * 3 + 25 ) / 400 )
159  'weeks' => 604800,
160  'days' => 86400,
161  'hours' => 3600,
162  'minutes' => 60,
163  'seconds' => 1,
164  ];
165 
172  private static $fallbackLanguageCache = [];
173 
178  private static $grammarTransformations;
179 
184  private static $languageNameCache;
185 
189  private static $lre = "\u{202A}"; // U+202A LEFT-TO-RIGHT EMBEDDING
190  private static $rle = "\u{202B}"; // U+202B RIGHT-TO-LEFT EMBEDDING
191  private static $pdf = "\u{202C}"; // U+202C POP DIRECTIONAL FORMATTING
192 
204  // @codeCoverageIgnoreStart
205  // phpcs:ignore Generic.Files.LineLength
206  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';
207  // @codeCoverageIgnoreEnd
208 
215  static function factory( $code ) {
217 
218  if ( isset( $wgDummyLanguageCodes[$code] ) ) {
220  }
221 
222  // get the language object to process
223  $langObj = self::$mLangObjCache[$code] ?? self::newFromCode( $code );
224 
225  // merge the language object in to get it up front in the cache
226  self::$mLangObjCache = array_merge( [ $code => $langObj ], self::$mLangObjCache );
227  // get rid of the oldest ones in case we have an overflow
228  self::$mLangObjCache = array_slice( self::$mLangObjCache, 0, $wgLangObjCacheSize, true );
229 
230  return $langObj;
231  }
232 
240  protected static function newFromCode( $code, $fallback = false ) {
241  if ( !self::isValidCode( $code ) ) {
242  throw new MWException( "Invalid language code \"$code\"" );
243  }
244 
245  if ( !self::isValidBuiltInCode( $code ) ) {
246  // It's not possible to customise this code with class files, so
247  // just return a Language object. This is to support uselang= hacks.
248  $lang = new Language;
249  $lang->mCode = $code;
250  return $lang;
251  }
252 
253  // Check if there is a language class for the code
254  $class = self::classFromCode( $code, $fallback );
255  // LanguageCode does not inherit Language
256  if ( class_exists( $class ) && is_a( $class, 'Language', true ) ) {
257  $lang = new $class;
258  return $lang;
259  }
260 
261  // Keep trying the fallback list until we find an existing class
262  $fallbacks = self::getFallbacksFor( $code );
263  foreach ( $fallbacks as $fallbackCode ) {
264  if ( !self::isValidBuiltInCode( $fallbackCode ) ) {
265  throw new MWException( "Invalid fallback '$fallbackCode' in fallback sequence for '$code'" );
266  }
267 
268  $class = self::classFromCode( $fallbackCode );
269  if ( class_exists( $class ) ) {
270  $lang = new $class;
271  $lang->mCode = $code;
272  return $lang;
273  }
274  }
275 
276  throw new MWException( "Invalid fallback sequence for language '$code'" );
277  }
278 
284  public static function clearCaches() {
285  if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
286  throw new MWException( __METHOD__ . ' must not be used outside tests' );
287  }
288  self::$dataCache = null;
289  // Reinitialize $dataCache, since it's expected to always be available
291  self::$mLangObjCache = [];
292  self::$fallbackLanguageCache = [];
293  self::$grammarTransformations = null;
294  self::$languageNameCache = null;
295  }
296 
305  public static function isSupportedLanguage( $code ) {
306  if ( !self::isValidBuiltInCode( $code ) ) {
307  return false;
308  }
309 
310  if ( $code === 'qqq' ) {
311  return false;
312  }
313 
314  return is_readable( self::getMessagesFileName( $code ) ) ||
315  is_readable( self::getJsonMessagesFileName( $code ) );
316  }
317 
333  public static function isWellFormedLanguageTag( $code, $lenient = false ) {
334  $alpha = '[a-z]';
335  $digit = '[0-9]';
336  $alphanum = '[a-z0-9]';
337  $x = 'x'; # private use singleton
338  $singleton = '[a-wy-z]'; # other singleton
339  $s = $lenient ? '[-_]' : '-';
340 
341  $language = "$alpha{2,8}|$alpha{2,3}$s$alpha{3}";
342  $script = "$alpha{4}"; # ISO 15924
343  $region = "(?:$alpha{2}|$digit{3})"; # ISO 3166-1 alpha-2 or UN M.49
344  $variant = "(?:$alphanum{5,8}|$digit$alphanum{3})";
345  $extension = "$singleton(?:$s$alphanum{2,8})+";
346  $privateUse = "$x(?:$s$alphanum{1,8})+";
347 
348  # Define certain grandfathered codes, since otherwise the regex is pretty useless.
349  # Since these are limited, this is safe even later changes to the registry --
350  # the only oddity is that it might change the type of the tag, and thus
351  # the results from the capturing groups.
352  # https://www.iana.org/assignments/language-subtag-registry
353 
354  $grandfathered = "en{$s}GB{$s}oed"
355  . "|i{$s}(?:ami|bnn|default|enochian|hak|klingon|lux|mingo|navajo|pwn|tao|tay|tsu)"
356  . "|no{$s}(?:bok|nyn)"
357  . "|sgn{$s}(?:BE{$s}(?:fr|nl)|CH{$s}de)"
358  . "|zh{$s}min{$s}nan";
359 
360  $variantList = "$variant(?:$s$variant)*";
361  $extensionList = "$extension(?:$s$extension)*";
362 
363  $langtag = "(?:($language)"
364  . "(?:$s$script)?"
365  . "(?:$s$region)?"
366  . "(?:$s$variantList)?"
367  . "(?:$s$extensionList)?"
368  . "(?:$s$privateUse)?)";
369 
370  # The final breakdown, with capturing groups for each of these components
371  # The variants, extensions, grandfathered, and private-use may have interior '-'
372 
373  $root = "^(?:$langtag|$privateUse|$grandfathered)$";
374 
375  return (bool)preg_match( "/$root/", strtolower( $code ) );
376  }
377 
387  public static function isValidCode( $code ) {
388  static $cache = [];
389  Assert::parameterType( 'string', $code, '$code' );
390  if ( !isset( $cache[$code] ) ) {
391  // People think language codes are html safe, so enforce it.
392  // Ideally we should only allow a-zA-Z0-9-
393  // but, .+ and other chars are often used for {{int:}} hacks
394  // see bugs T39564, T39587, T38938
395  $cache[$code] =
396  // Protect against path traversal
397  strcspn( $code, ":/\\\000&<>'\"" ) === strlen( $code )
399  }
400  return $cache[$code];
401  }
402 
412  public static function isValidBuiltInCode( $code ) {
413  Assert::parameterType( 'string', $code, '$code' );
414 
415  return (bool)preg_match( '/^[a-z0-9-]{2,}$/', $code );
416  }
417 
426  public static function isKnownLanguageTag( $tag ) {
427  // Quick escape for invalid input to avoid exceptions down the line
428  // when code tries to process tags which are not valid at all.
429  if ( !self::isValidBuiltInCode( $tag ) ) {
430  return false;
431  }
432 
433  if ( isset( MediaWiki\Languages\Data\Names::$names[$tag] )
434  || self::fetchLanguageName( $tag, $tag ) !== ''
435  ) {
436  return true;
437  }
438 
439  return false;
440  }
441 
447  public static function getLocalisationCache() {
448  if ( is_null( self::$dataCache ) ) {
450  $class = $wgLocalisationCacheConf['class'];
451  self::$dataCache = new $class( $wgLocalisationCacheConf );
452  }
453  return self::$dataCache;
454  }
455 
456  function __construct() {
457  $this->mConverter = new FakeConverter( $this );
458  // Set the code to the name of the descendant
459  if ( static::class === 'Language' ) {
460  $this->mCode = 'en';
461  } else {
462  $this->mCode = str_replace( '_', '-', strtolower( substr( static::class, 8 ) ) );
463  }
465  }
466 
470  function __destruct() {
471  foreach ( $this as $name => $value ) {
472  unset( $this->$name );
473  }
474  }
475 
480  function initContLang() {
481  }
482 
487  public function getFallbackLanguages() {
488  return self::getFallbacksFor( $this->mCode );
489  }
490 
495  public function getBookstoreList() {
496  return self::$dataCache->getItem( $this->mCode, 'bookstoreList' );
497  }
498 
505  public function getNamespaces() {
506  if ( is_null( $this->namespaceNames ) ) {
508 
509  $validNamespaces = MWNamespace::getCanonicalNamespaces();
510 
511  $this->namespaceNames = $wgExtraNamespaces +
512  self::$dataCache->getItem( $this->mCode, 'namespaceNames' );
513  $this->namespaceNames += $validNamespaces;
514 
515  $this->namespaceNames[NS_PROJECT] = $wgMetaNamespace;
516  if ( $wgMetaNamespaceTalk ) {
517  $this->namespaceNames[NS_PROJECT_TALK] = $wgMetaNamespaceTalk;
518  } else {
519  $talk = $this->namespaceNames[NS_PROJECT_TALK];
520  $this->namespaceNames[NS_PROJECT_TALK] =
521  $this->fixVariableInNamespace( $talk );
522  }
523 
524  # Sometimes a language will be localised but not actually exist on this wiki.
525  foreach ( $this->namespaceNames as $key => $text ) {
526  if ( !isset( $validNamespaces[$key] ) ) {
527  unset( $this->namespaceNames[$key] );
528  }
529  }
530 
531  # The above mixing may leave namespaces out of canonical order.
532  # Re-order by namespace ID number...
533  ksort( $this->namespaceNames );
534 
535  Hooks::run( 'LanguageGetNamespaces', [ &$this->namespaceNames ] );
536  }
537 
538  return $this->namespaceNames;
539  }
540 
545  public function setNamespaces( array $namespaces ) {
546  $this->namespaceNames = $namespaces;
547  $this->mNamespaceIds = null;
548  }
549 
553  public function resetNamespaces() {
554  $this->namespaceNames = null;
555  $this->mNamespaceIds = null;
556  $this->namespaceAliases = null;
557  }
558 
565  public function getFormattedNamespaces() {
566  $ns = $this->getNamespaces();
567  foreach ( $ns as $k => $v ) {
568  $ns[$k] = strtr( $v, '_', ' ' );
569  }
570  return $ns;
571  }
572 
584  public function getNsText( $index ) {
585  $ns = $this->getNamespaces();
586  return $ns[$index] ?? false;
587  }
588 
602  public function getFormattedNsText( $index ) {
603  $ns = $this->getNsText( $index );
604  return strtr( $ns, '_', ' ' );
605  }
606 
615  public function getGenderNsText( $index, $gender ) {
617 
619  (array)self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' );
620 
621  return $ns[$index][$gender] ?? $this->getNsText( $index );
622  }
623 
630  public function needsGenderDistinction() {
632  if ( count( $wgExtraGenderNamespaces ) > 0 ) {
633  // $wgExtraGenderNamespaces overrides everything
634  return true;
635  } elseif ( isset( $wgExtraNamespaces[NS_USER] ) && isset( $wgExtraNamespaces[NS_USER_TALK] ) ) {
637  // $wgExtraNamespaces overrides any gender aliases specified in i18n files
638  return false;
639  } else {
640  // Check what is in i18n files
641  $aliases = self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' );
642  return count( $aliases ) > 0;
643  }
644  }
645 
654  function getLocalNsIndex( $text ) {
655  $lctext = $this->lc( $text );
656  $ids = $this->getNamespaceIds();
657  return $ids[$lctext] ?? false;
658  }
659 
663  public function getNamespaceAliases() {
664  if ( is_null( $this->namespaceAliases ) ) {
665  $aliases = self::$dataCache->getItem( $this->mCode, 'namespaceAliases' );
666  if ( !$aliases ) {
667  $aliases = [];
668  } else {
669  foreach ( $aliases as $name => $index ) {
670  if ( $index === NS_PROJECT_TALK ) {
671  unset( $aliases[$name] );
672  $name = $this->fixVariableInNamespace( $name );
673  $aliases[$name] = $index;
674  }
675  }
676  }
677 
679  $genders = $wgExtraGenderNamespaces +
680  (array)self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' );
681  foreach ( $genders as $index => $forms ) {
682  foreach ( $forms as $alias ) {
683  $aliases[$alias] = $index;
684  }
685  }
686 
687  # Also add converted namespace names as aliases, to avoid confusion.
688  $convertedNames = [];
689  foreach ( $this->getVariants() as $variant ) {
690  if ( $variant === $this->mCode ) {
691  continue;
692  }
693  foreach ( $this->getNamespaces() as $ns => $_ ) {
694  $convertedNames[$this->getConverter()->convertNamespace( $ns, $variant )] = $ns;
695  }
696  }
697 
698  $this->namespaceAliases = $aliases + $convertedNames;
699 
700  # Filter out aliases to namespaces that don't exist, e.g. from extensions
701  # that aren't loaded here but are included in the l10n cache.
702  # (array_intersect preserves keys from its first argument)
703  $this->namespaceAliases = array_intersect(
704  $this->namespaceAliases,
705  array_keys( $this->getNamespaces() )
706  );
707  }
708 
710  }
711 
715  public function getNamespaceIds() {
716  if ( is_null( $this->mNamespaceIds ) ) {
717  global $wgNamespaceAliases;
718  # Put namespace names and aliases into a hashtable.
719  # If this is too slow, then we should arrange it so that it is done
720  # before caching. The catch is that at pre-cache time, the above
721  # class-specific fixup hasn't been done.
722  $this->mNamespaceIds = [];
723  foreach ( $this->getNamespaces() as $index => $name ) {
724  $this->mNamespaceIds[$this->lc( $name )] = $index;
725  }
726  foreach ( $this->getNamespaceAliases() as $name => $index ) {
727  $this->mNamespaceIds[$this->lc( $name )] = $index;
728  }
729  if ( $wgNamespaceAliases ) {
730  foreach ( $wgNamespaceAliases as $name => $index ) {
731  $this->mNamespaceIds[$this->lc( $name )] = $index;
732  }
733  }
734  }
735  return $this->mNamespaceIds;
736  }
737 
745  public function getNsIndex( $text ) {
746  $lctext = $this->lc( $text );
747  $ns = MWNamespace::getCanonicalIndex( $lctext );
748  if ( $ns !== null ) {
749  return $ns;
750  }
751  $ids = $this->getNamespaceIds();
752  return $ids[$lctext] ?? false;
753  }
754 
762  public function getVariantname( $code, $usemsg = true ) {
763  $msg = "variantname-$code";
764  if ( $usemsg && wfMessage( $msg )->exists() ) {
765  return $this->getMessageFromDB( $msg );
766  }
768  if ( $name ) {
769  return $name; # if it's defined as a language name, show that
770  } else {
771  # otherwise, output the language code
772  return $code;
773  }
774  }
775 
779  public function getDatePreferences() {
780  return self::$dataCache->getItem( $this->mCode, 'datePreferences' );
781  }
782 
786  function getDateFormats() {
787  return self::$dataCache->getItem( $this->mCode, 'dateFormats' );
788  }
789 
793  public function getDefaultDateFormat() {
794  $df = self::$dataCache->getItem( $this->mCode, 'defaultDateFormat' );
795  if ( $df === 'dmy or mdy' ) {
796  global $wgAmericanDates;
797  return $wgAmericanDates ? 'mdy' : 'dmy';
798  } else {
799  return $df;
800  }
801  }
802 
806  public function getDatePreferenceMigrationMap() {
807  return self::$dataCache->getItem( $this->mCode, 'datePreferenceMigrationMap' );
808  }
809 
813  public function getExtraUserToggles() {
814  return (array)self::$dataCache->getItem( $this->mCode, 'extraUserToggles' );
815  }
816 
821  function getUserToggle( $tog ) {
822  return $this->getMessageFromDB( "tog-$tog" );
823  }
824 
836  public static function fetchLanguageNames( $inLanguage = self::AS_AUTONYMS, $include = 'mw' ) {
837  $cacheKey = $inLanguage === self::AS_AUTONYMS ? 'null' : $inLanguage;
838  $cacheKey .= ":$include";
839  if ( self::$languageNameCache === null ) {
840  self::$languageNameCache = new HashBagOStuff( [ 'maxKeys' => 20 ] );
841  }
842 
843  $ret = self::$languageNameCache->get( $cacheKey );
844  if ( !$ret ) {
845  $ret = self::fetchLanguageNamesUncached( $inLanguage, $include );
846  self::$languageNameCache->set( $cacheKey, $ret );
847  }
848  return $ret;
849  }
850 
861  private static function fetchLanguageNamesUncached(
862  $inLanguage = self::AS_AUTONYMS,
863  $include = 'mw'
864  ) {
865  global $wgExtraLanguageNames, $wgUsePigLatinVariant;
866 
867  // If passed an invalid language code to use, fallback to en
868  if ( $inLanguage !== self::AS_AUTONYMS && !self::isValidCode( $inLanguage ) ) {
869  $inLanguage = 'en';
870  }
871 
872  $names = [];
873 
874  if ( $inLanguage ) {
875  # TODO: also include when $inLanguage is null, when this code is more efficient
876  Hooks::run( 'LanguageGetTranslatedLanguageNames', [ &$names, $inLanguage ] );
877  }
878 
879  $mwNames = $wgExtraLanguageNames + MediaWiki\Languages\Data\Names::$names;
880  if ( $wgUsePigLatinVariant ) {
881  // Pig Latin (for variant development)
882  $mwNames['en-x-piglatin'] = 'Igpay Atinlay';
883  }
884 
885  foreach ( $mwNames as $mwCode => $mwName ) {
886  # - Prefer own MediaWiki native name when not using the hook
887  # - For other names just add if not added through the hook
888  if ( $mwCode === $inLanguage || !isset( $names[$mwCode] ) ) {
889  $names[$mwCode] = $mwName;
890  }
891  }
892 
893  if ( $include === self::ALL ) {
894  ksort( $names );
895  return $names;
896  }
897 
898  $returnMw = [];
899  $coreCodes = array_keys( $mwNames );
900  foreach ( $coreCodes as $coreCode ) {
901  $returnMw[$coreCode] = $names[$coreCode];
902  }
903 
904  if ( $include === self::SUPPORTED ) {
905  $namesMwFile = [];
906  # We do this using a foreach over the codes instead of a directory
907  # loop so that messages files in extensions will work correctly.
908  foreach ( $returnMw as $code => $value ) {
909  if ( is_readable( self::getMessagesFileName( $code ) )
910  || is_readable( self::getJsonMessagesFileName( $code ) )
911  ) {
912  $namesMwFile[$code] = $names[$code];
913  }
914  }
915 
916  ksort( $namesMwFile );
917  return $namesMwFile;
918  }
919 
920  ksort( $returnMw );
921  # 'mw' option; default if it's not one of the other two options (all/mwfile)
922  return $returnMw;
923  }
924 
933  public static function fetchLanguageName(
934  $code,
935  $inLanguage = self::AS_AUTONYMS,
936  $include = self::ALL
937  ) {
938  $code = strtolower( $code );
939  $array = self::fetchLanguageNames( $inLanguage, $include );
940  return !array_key_exists( $code, $array ) ? '' : $array[$code];
941  }
942 
949  public function getMessageFromDB( $msg ) {
950  return $this->msg( $msg )->text();
951  }
952 
959  protected function msg( $msg ) {
960  return wfMessage( $msg )->inLanguage( $this );
961  }
962 
967  public function getMonthName( $key ) {
968  return $this->getMessageFromDB( self::$mMonthMsgs[$key - 1] );
969  }
970 
974  public function getMonthNamesArray() {
975  $monthNames = [ '' ];
976  for ( $i = 1; $i < 13; $i++ ) {
977  $monthNames[] = $this->getMonthName( $i );
978  }
979  return $monthNames;
980  }
981 
986  public function getMonthNameGen( $key ) {
987  return $this->getMessageFromDB( self::$mMonthGenMsgs[$key - 1] );
988  }
989 
994  public function getMonthAbbreviation( $key ) {
995  return $this->getMessageFromDB( self::$mMonthAbbrevMsgs[$key - 1] );
996  }
997 
1001  public function getMonthAbbreviationsArray() {
1002  $monthNames = [ '' ];
1003  for ( $i = 1; $i < 13; $i++ ) {
1004  $monthNames[] = $this->getMonthAbbreviation( $i );
1005  }
1006  return $monthNames;
1007  }
1008 
1013  public function getWeekdayName( $key ) {
1014  return $this->getMessageFromDB( self::$mWeekdayMsgs[$key - 1] );
1015  }
1016 
1021  function getWeekdayAbbreviation( $key ) {
1022  return $this->getMessageFromDB( self::$mWeekdayAbbrevMsgs[$key - 1] );
1023  }
1024 
1029  function getIranianCalendarMonthName( $key ) {
1030  return $this->getMessageFromDB( self::$mIranianCalendarMonthMsgs[$key - 1] );
1031  }
1032 
1037  function getHebrewCalendarMonthName( $key ) {
1038  return $this->getMessageFromDB( self::$mHebrewCalendarMonthMsgs[$key - 1] );
1039  }
1040 
1046  return $this->getMessageFromDB( self::$mHebrewCalendarMonthGenMsgs[$key - 1] );
1047  }
1048 
1053  function getHijriCalendarMonthName( $key ) {
1054  return $this->getMessageFromDB( self::$mHijriCalendarMonthMsgs[$key - 1] );
1055  }
1056 
1065  private static function dateTimeObjFormat( &$dateTimeObj, $ts, $zone, $code ) {
1066  if ( !$dateTimeObj ) {
1067  $dateTimeObj = DateTime::createFromFormat(
1068  'YmdHis', $ts, $zone ?: new DateTimeZone( 'UTC' )
1069  );
1070  }
1071  return $dateTimeObj->format( $code );
1072  }
1073 
1143  public function sprintfDate( $format, $ts, DateTimeZone $zone = null, &$ttl = 'unused' ) {
1144  $s = '';
1145  $raw = false;
1146  $roman = false;
1147  $hebrewNum = false;
1148  $dateTimeObj = false;
1149  $rawToggle = false;
1150  $iranian = false;
1151  $hebrew = false;
1152  $hijri = false;
1153  $thai = false;
1154  $minguo = false;
1155  $tenno = false;
1156 
1157  $usedSecond = false;
1158  $usedMinute = false;
1159  $usedHour = false;
1160  $usedAMPM = false;
1161  $usedDay = false;
1162  $usedWeek = false;
1163  $usedMonth = false;
1164  $usedYear = false;
1165  $usedISOYear = false;
1166  $usedIsLeapYear = false;
1167 
1168  $usedHebrewMonth = false;
1169  $usedIranianMonth = false;
1170  $usedHijriMonth = false;
1171  $usedHebrewYear = false;
1172  $usedIranianYear = false;
1173  $usedHijriYear = false;
1174  $usedTennoYear = false;
1175 
1176  if ( strlen( $ts ) !== 14 ) {
1177  throw new MWException( __METHOD__ . ": The timestamp $ts should have 14 characters" );
1178  }
1179 
1180  if ( !ctype_digit( $ts ) ) {
1181  throw new MWException( __METHOD__ . ": The timestamp $ts should be a number" );
1182  }
1183 
1184  $formatLength = strlen( $format );
1185  for ( $p = 0; $p < $formatLength; $p++ ) {
1186  $num = false;
1187  $code = $format[$p];
1188  if ( $code == 'x' && $p < $formatLength - 1 ) {
1189  $code .= $format[++$p];
1190  }
1191 
1192  if ( ( $code === 'xi'
1193  || $code === 'xj'
1194  || $code === 'xk'
1195  || $code === 'xm'
1196  || $code === 'xo'
1197  || $code === 'xt' )
1198  && $p < $formatLength - 1 ) {
1199  $code .= $format[++$p];
1200  }
1201 
1202  switch ( $code ) {
1203  case 'xx':
1204  $s .= 'x';
1205  break;
1206  case 'xn':
1207  $raw = true;
1208  break;
1209  case 'xN':
1210  $rawToggle = !$rawToggle;
1211  break;
1212  case 'xr':
1213  $roman = true;
1214  break;
1215  case 'xh':
1216  $hebrewNum = true;
1217  break;
1218  case 'xg':
1219  $usedMonth = true;
1220  $s .= $this->getMonthNameGen( substr( $ts, 4, 2 ) );
1221  break;
1222  case 'xjx':
1223  $usedHebrewMonth = true;
1224  if ( !$hebrew ) {
1225  $hebrew = self::tsToHebrew( $ts );
1226  }
1227  $s .= $this->getHebrewCalendarMonthNameGen( $hebrew[1] );
1228  break;
1229  case 'd':
1230  $usedDay = true;
1231  $num = substr( $ts, 6, 2 );
1232  break;
1233  case 'D':
1234  $usedDay = true;
1235  $s .= $this->getWeekdayAbbreviation(
1236  self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'w' ) + 1
1237  );
1238  break;
1239  case 'j':
1240  $usedDay = true;
1241  $num = intval( substr( $ts, 6, 2 ) );
1242  break;
1243  case 'xij':
1244  $usedDay = true;
1245  if ( !$iranian ) {
1246  $iranian = self::tsToIranian( $ts );
1247  }
1248  $num = $iranian[2];
1249  break;
1250  case 'xmj':
1251  $usedDay = true;
1252  if ( !$hijri ) {
1253  $hijri = self::tsToHijri( $ts );
1254  }
1255  $num = $hijri[2];
1256  break;
1257  case 'xjj':
1258  $usedDay = true;
1259  if ( !$hebrew ) {
1260  $hebrew = self::tsToHebrew( $ts );
1261  }
1262  $num = $hebrew[2];
1263  break;
1264  case 'l':
1265  $usedDay = true;
1266  $s .= $this->getWeekdayName(
1267  self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'w' ) + 1
1268  );
1269  break;
1270  case 'F':
1271  $usedMonth = true;
1272  $s .= $this->getMonthName( substr( $ts, 4, 2 ) );
1273  break;
1274  case 'xiF':
1275  $usedIranianMonth = true;
1276  if ( !$iranian ) {
1277  $iranian = self::tsToIranian( $ts );
1278  }
1279  $s .= $this->getIranianCalendarMonthName( $iranian[1] );
1280  break;
1281  case 'xmF':
1282  $usedHijriMonth = true;
1283  if ( !$hijri ) {
1284  $hijri = self::tsToHijri( $ts );
1285  }
1286  $s .= $this->getHijriCalendarMonthName( $hijri[1] );
1287  break;
1288  case 'xjF':
1289  $usedHebrewMonth = true;
1290  if ( !$hebrew ) {
1291  $hebrew = self::tsToHebrew( $ts );
1292  }
1293  $s .= $this->getHebrewCalendarMonthName( $hebrew[1] );
1294  break;
1295  case 'm':
1296  $usedMonth = true;
1297  $num = substr( $ts, 4, 2 );
1298  break;
1299  case 'M':
1300  $usedMonth = true;
1301  $s .= $this->getMonthAbbreviation( substr( $ts, 4, 2 ) );
1302  break;
1303  case 'n':
1304  $usedMonth = true;
1305  $num = intval( substr( $ts, 4, 2 ) );
1306  break;
1307  case 'xin':
1308  $usedIranianMonth = true;
1309  if ( !$iranian ) {
1310  $iranian = self::tsToIranian( $ts );
1311  }
1312  $num = $iranian[1];
1313  break;
1314  case 'xmn':
1315  $usedHijriMonth = true;
1316  if ( !$hijri ) {
1317  $hijri = self::tsToHijri( $ts );
1318  }
1319  $num = $hijri[1];
1320  break;
1321  case 'xjn':
1322  $usedHebrewMonth = true;
1323  if ( !$hebrew ) {
1324  $hebrew = self::tsToHebrew( $ts );
1325  }
1326  $num = $hebrew[1];
1327  break;
1328  case 'xjt':
1329  $usedHebrewMonth = true;
1330  if ( !$hebrew ) {
1331  $hebrew = self::tsToHebrew( $ts );
1332  }
1333  $num = $hebrew[3];
1334  break;
1335  case 'Y':
1336  $usedYear = true;
1337  $num = substr( $ts, 0, 4 );
1338  break;
1339  case 'xiY':
1340  $usedIranianYear = true;
1341  if ( !$iranian ) {
1342  $iranian = self::tsToIranian( $ts );
1343  }
1344  $num = $iranian[0];
1345  break;
1346  case 'xmY':
1347  $usedHijriYear = true;
1348  if ( !$hijri ) {
1349  $hijri = self::tsToHijri( $ts );
1350  }
1351  $num = $hijri[0];
1352  break;
1353  case 'xjY':
1354  $usedHebrewYear = true;
1355  if ( !$hebrew ) {
1356  $hebrew = self::tsToHebrew( $ts );
1357  }
1358  $num = $hebrew[0];
1359  break;
1360  case 'xkY':
1361  $usedYear = true;
1362  if ( !$thai ) {
1363  $thai = self::tsToYear( $ts, 'thai' );
1364  }
1365  $num = $thai[0];
1366  break;
1367  case 'xoY':
1368  $usedYear = true;
1369  if ( !$minguo ) {
1370  $minguo = self::tsToYear( $ts, 'minguo' );
1371  }
1372  $num = $minguo[0];
1373  break;
1374  case 'xtY':
1375  $usedTennoYear = true;
1376  if ( !$tenno ) {
1377  $tenno = self::tsToYear( $ts, 'tenno' );
1378  }
1379  $num = $tenno[0];
1380  break;
1381  case 'y':
1382  $usedYear = true;
1383  $num = substr( $ts, 2, 2 );
1384  break;
1385  case 'xiy':
1386  $usedIranianYear = true;
1387  if ( !$iranian ) {
1388  $iranian = self::tsToIranian( $ts );
1389  }
1390  $num = substr( $iranian[0], -2 );
1391  break;
1392  case 'xit':
1393  $usedIranianYear = true;
1394  if ( !$iranian ) {
1395  $iranian = self::tsToIranian( $ts );
1396  }
1397  $num = self::$IRANIAN_DAYS[$iranian[1] - 1];
1398  break;
1399  case 'xiz':
1400  $usedIranianYear = true;
1401  if ( !$iranian ) {
1402  $iranian = self::tsToIranian( $ts );
1403  }
1404  $num = $iranian[3];
1405  break;
1406  case 'a':
1407  $usedAMPM = true;
1408  $s .= intval( substr( $ts, 8, 2 ) ) < 12 ? 'am' : 'pm';
1409  break;
1410  case 'A':
1411  $usedAMPM = true;
1412  $s .= intval( substr( $ts, 8, 2 ) ) < 12 ? 'AM' : 'PM';
1413  break;
1414  case 'g':
1415  $usedHour = true;
1416  $h = substr( $ts, 8, 2 );
1417  $num = $h % 12 ?: 12;
1418  break;
1419  case 'G':
1420  $usedHour = true;
1421  $num = intval( substr( $ts, 8, 2 ) );
1422  break;
1423  case 'h':
1424  $usedHour = true;
1425  $h = substr( $ts, 8, 2 );
1426  $num = sprintf( '%02d', $h % 12 ?: 12 );
1427  break;
1428  case 'H':
1429  $usedHour = true;
1430  $num = substr( $ts, 8, 2 );
1431  break;
1432  case 'i':
1433  $usedMinute = true;
1434  $num = substr( $ts, 10, 2 );
1435  break;
1436  case 's':
1437  $usedSecond = true;
1438  $num = substr( $ts, 12, 2 );
1439  break;
1440  case 'c':
1441  case 'r':
1442  $usedSecond = true;
1443  // fall through
1444  case 'e':
1445  case 'O':
1446  case 'P':
1447  case 'T':
1448  $s .= self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1449  break;
1450  case 'w':
1451  case 'N':
1452  case 'z':
1453  $usedDay = true;
1454  $num = self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1455  break;
1456  case 'W':
1457  $usedWeek = true;
1458  $num = self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1459  break;
1460  case 't':
1461  $usedMonth = true;
1462  $num = self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1463  break;
1464  case 'L':
1465  $usedIsLeapYear = true;
1466  $num = self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1467  break;
1468  case 'o':
1469  $usedISOYear = true;
1470  $num = self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1471  break;
1472  case 'U':
1473  $usedSecond = true;
1474  // fall through
1475  case 'I':
1476  case 'Z':
1477  $num = self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1478  break;
1479  case '\\':
1480  # Backslash escaping
1481  if ( $p < $formatLength - 1 ) {
1482  $s .= $format[++$p];
1483  } else {
1484  $s .= '\\';
1485  }
1486  break;
1487  case '"':
1488  # Quoted literal
1489  if ( $p < $formatLength - 1 ) {
1490  $endQuote = strpos( $format, '"', $p + 1 );
1491  if ( $endQuote === false ) {
1492  # No terminating quote, assume literal "
1493  $s .= '"';
1494  } else {
1495  $s .= substr( $format, $p + 1, $endQuote - $p - 1 );
1496  $p = $endQuote;
1497  }
1498  } else {
1499  # Quote at end of string, assume literal "
1500  $s .= '"';
1501  }
1502  break;
1503  default:
1504  $s .= $format[$p];
1505  }
1506  if ( $num !== false ) {
1507  if ( $rawToggle || $raw ) {
1508  $s .= $num;
1509  $raw = false;
1510  } elseif ( $roman ) {
1511  $s .= self::romanNumeral( $num );
1512  $roman = false;
1513  } elseif ( $hebrewNum ) {
1514  $s .= self::hebrewNumeral( $num );
1515  $hebrewNum = false;
1516  } else {
1517  $s .= $this->formatNum( $num, true );
1518  }
1519  }
1520  }
1521 
1522  if ( $ttl === 'unused' ) {
1523  // No need to calculate the TTL, the caller wont use it anyway.
1524  } elseif ( $usedSecond ) {
1525  $ttl = 1;
1526  } elseif ( $usedMinute ) {
1527  $ttl = 60 - substr( $ts, 12, 2 );
1528  } elseif ( $usedHour ) {
1529  $ttl = 3600 - substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
1530  } elseif ( $usedAMPM ) {
1531  $ttl = 43200 - ( substr( $ts, 8, 2 ) % 12 ) * 3600 -
1532  substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
1533  } elseif (
1534  $usedDay ||
1535  $usedHebrewMonth ||
1536  $usedIranianMonth ||
1537  $usedHijriMonth ||
1538  $usedHebrewYear ||
1539  $usedIranianYear ||
1540  $usedHijriYear ||
1541  $usedTennoYear
1542  ) {
1543  // @todo Someone who understands the non-Gregorian calendars
1544  // should write proper logic for them so that they don't need purged every day.
1545  $ttl = 86400 - substr( $ts, 8, 2 ) * 3600 -
1546  substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
1547  } else {
1548  $possibleTtls = [];
1549  $timeRemainingInDay = 86400 - substr( $ts, 8, 2 ) * 3600 -
1550  substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
1551  if ( $usedWeek ) {
1552  $possibleTtls[] =
1553  ( 7 - self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'N' ) ) * 86400 +
1554  $timeRemainingInDay;
1555  } elseif ( $usedISOYear ) {
1556  // December 28th falls on the last ISO week of the year, every year.
1557  // The last ISO week of a year can be 52 or 53.
1558  $lastWeekOfISOYear = DateTime::createFromFormat(
1559  'Ymd',
1560  substr( $ts, 0, 4 ) . '1228',
1561  $zone ?: new DateTimeZone( 'UTC' )
1562  )->format( 'W' );
1563  $currentISOWeek = self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'W' );
1564  $weeksRemaining = $lastWeekOfISOYear - $currentISOWeek;
1565  $timeRemainingInWeek =
1566  ( 7 - self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'N' ) ) * 86400
1567  + $timeRemainingInDay;
1568  $possibleTtls[] = $weeksRemaining * 604800 + $timeRemainingInWeek;
1569  }
1570 
1571  if ( $usedMonth ) {
1572  $possibleTtls[] =
1573  ( self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 't' ) -
1574  substr( $ts, 6, 2 ) ) * 86400
1575  + $timeRemainingInDay;
1576  } elseif ( $usedYear ) {
1577  $possibleTtls[] =
1578  ( self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'L' ) + 364 -
1579  self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'z' ) ) * 86400
1580  + $timeRemainingInDay;
1581  } elseif ( $usedIsLeapYear ) {
1582  $year = substr( $ts, 0, 4 );
1583  $timeRemainingInYear =
1584  ( self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'L' ) + 364 -
1585  self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'z' ) ) * 86400
1586  + $timeRemainingInDay;
1587  $mod = $year % 4;
1588  if ( $mod || ( !( $year % 100 ) && $year % 400 ) ) {
1589  // this isn't a leap year. see when the next one starts
1590  $nextCandidate = $year - $mod + 4;
1591  if ( $nextCandidate % 100 || !( $nextCandidate % 400 ) ) {
1592  $possibleTtls[] = ( $nextCandidate - $year - 1 ) * 365 * 86400 +
1593  $timeRemainingInYear;
1594  } else {
1595  $possibleTtls[] = ( $nextCandidate - $year + 3 ) * 365 * 86400 +
1596  $timeRemainingInYear;
1597  }
1598  } else {
1599  // this is a leap year, so the next year isn't
1600  $possibleTtls[] = $timeRemainingInYear;
1601  }
1602  }
1603 
1604  if ( $possibleTtls ) {
1605  $ttl = min( $possibleTtls );
1606  }
1607  }
1608 
1609  return $s;
1610  }
1611 
1612  private static $GREG_DAYS = [ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ];
1613  private static $IRANIAN_DAYS = [ 31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29 ];
1614 
1627  private static function tsToIranian( $ts ) {
1628  $gy = substr( $ts, 0, 4 ) - 1600;
1629  $gm = substr( $ts, 4, 2 ) - 1;
1630  $gd = substr( $ts, 6, 2 ) - 1;
1631 
1632  # Days passed from the beginning (including leap years)
1633  $gDayNo = 365 * $gy
1634  + floor( ( $gy + 3 ) / 4 )
1635  - floor( ( $gy + 99 ) / 100 )
1636  + floor( ( $gy + 399 ) / 400 );
1637 
1638  // Add days of the past months of this year
1639  for ( $i = 0; $i < $gm; $i++ ) {
1640  $gDayNo += self::$GREG_DAYS[$i];
1641  }
1642 
1643  // Leap years
1644  if ( $gm > 1 && ( ( $gy % 4 === 0 && $gy % 100 !== 0 || ( $gy % 400 == 0 ) ) ) ) {
1645  $gDayNo++;
1646  }
1647 
1648  // Days passed in current month
1649  $gDayNo += (int)$gd;
1650 
1651  $jDayNo = $gDayNo - 79;
1652 
1653  $jNp = floor( $jDayNo / 12053 );
1654  $jDayNo %= 12053;
1655 
1656  $jy = 979 + 33 * $jNp + 4 * floor( $jDayNo / 1461 );
1657  $jDayNo %= 1461;
1658 
1659  if ( $jDayNo >= 366 ) {
1660  $jy += floor( ( $jDayNo - 1 ) / 365 );
1661  $jDayNo = floor( ( $jDayNo - 1 ) % 365 );
1662  }
1663 
1664  $jz = $jDayNo;
1665 
1666  for ( $i = 0; $i < 11 && $jDayNo >= self::$IRANIAN_DAYS[$i]; $i++ ) {
1667  $jDayNo -= self::$IRANIAN_DAYS[$i];
1668  }
1669 
1670  $jm = $i + 1;
1671  $jd = $jDayNo + 1;
1672 
1673  return [ $jy, $jm, $jd, $jz ];
1674  }
1675 
1687  private static function tsToHijri( $ts ) {
1688  $year = substr( $ts, 0, 4 );
1689  $month = substr( $ts, 4, 2 );
1690  $day = substr( $ts, 6, 2 );
1691 
1692  $zyr = $year;
1693  $zd = $day;
1694  $zm = $month;
1695  $zy = $zyr;
1696 
1697  if (
1698  ( $zy > 1582 ) || ( ( $zy == 1582 ) && ( $zm > 10 ) ) ||
1699  ( ( $zy == 1582 ) && ( $zm == 10 ) && ( $zd > 14 ) )
1700  ) {
1701  $zjd = (int)( ( 1461 * ( $zy + 4800 + (int)( ( $zm - 14 ) / 12 ) ) ) / 4 ) +
1702  (int)( ( 367 * ( $zm - 2 - 12 * ( (int)( ( $zm - 14 ) / 12 ) ) ) ) / 12 ) -
1703  (int)( ( 3 * (int)( ( ( $zy + 4900 + (int)( ( $zm - 14 ) / 12 ) ) / 100 ) ) ) / 4 ) +
1704  $zd - 32075;
1705  } else {
1706  $zjd = 367 * $zy - (int)( ( 7 * ( $zy + 5001 + (int)( ( $zm - 9 ) / 7 ) ) ) / 4 ) +
1707  (int)( ( 275 * $zm ) / 9 ) + $zd + 1729777;
1708  }
1709 
1710  $zl = $zjd - 1948440 + 10632;
1711  $zn = (int)( ( $zl - 1 ) / 10631 );
1712  $zl = $zl - 10631 * $zn + 354;
1713  $zj = ( (int)( ( 10985 - $zl ) / 5316 ) ) * ( (int)( ( 50 * $zl ) / 17719 ) ) +
1714  ( (int)( $zl / 5670 ) ) * ( (int)( ( 43 * $zl ) / 15238 ) );
1715  $zl = $zl - ( (int)( ( 30 - $zj ) / 15 ) ) * ( (int)( ( 17719 * $zj ) / 50 ) ) -
1716  ( (int)( $zj / 16 ) ) * ( (int)( ( 15238 * $zj ) / 43 ) ) + 29;
1717  $zm = (int)( ( 24 * $zl ) / 709 );
1718  $zd = $zl - (int)( ( 709 * $zm ) / 24 );
1719  $zy = 30 * $zn + $zj - 30;
1720 
1721  return [ $zy, $zm, $zd ];
1722  }
1723 
1739  private static function tsToHebrew( $ts ) {
1740  # Parse date
1741  $year = substr( $ts, 0, 4 );
1742  $month = substr( $ts, 4, 2 );
1743  $day = substr( $ts, 6, 2 );
1744 
1745  # Calculate Hebrew year
1746  $hebrewYear = $year + 3760;
1747 
1748  # Month number when September = 1, August = 12
1749  $month += 4;
1750  if ( $month > 12 ) {
1751  # Next year
1752  $month -= 12;
1753  $year++;
1754  $hebrewYear++;
1755  }
1756 
1757  # Calculate day of year from 1 September
1758  $dayOfYear = $day;
1759  for ( $i = 1; $i < $month; $i++ ) {
1760  if ( $i == 6 ) {
1761  # February
1762  $dayOfYear += 28;
1763  # Check if the year is leap
1764  if ( $year % 400 == 0 || ( $year % 4 == 0 && $year % 100 > 0 ) ) {
1765  $dayOfYear++;
1766  }
1767  } elseif ( $i == 8 || $i == 10 || $i == 1 || $i == 3 ) {
1768  $dayOfYear += 30;
1769  } else {
1770  $dayOfYear += 31;
1771  }
1772  }
1773 
1774  # Calculate the start of the Hebrew year
1775  $start = self::hebrewYearStart( $hebrewYear );
1776 
1777  # Calculate next year's start
1778  if ( $dayOfYear <= $start ) {
1779  # Day is before the start of the year - it is the previous year
1780  # Next year's start
1781  $nextStart = $start;
1782  # Previous year
1783  $year--;
1784  $hebrewYear--;
1785  # Add days since previous year's 1 September
1786  $dayOfYear += 365;
1787  if ( ( $year % 400 == 0 ) || ( $year % 100 != 0 && $year % 4 == 0 ) ) {
1788  # Leap year
1789  $dayOfYear++;
1790  }
1791  # Start of the new (previous) year
1792  $start = self::hebrewYearStart( $hebrewYear );
1793  } else {
1794  # Next year's start
1795  $nextStart = self::hebrewYearStart( $hebrewYear + 1 );
1796  }
1797 
1798  # Calculate Hebrew day of year
1799  $hebrewDayOfYear = $dayOfYear - $start;
1800 
1801  # Difference between year's days
1802  $diff = $nextStart - $start;
1803  # Add 12 (or 13 for leap years) days to ignore the difference between
1804  # Hebrew and Gregorian year (353 at least vs. 365/6) - now the
1805  # difference is only about the year type
1806  if ( ( $year % 400 == 0 ) || ( $year % 100 != 0 && $year % 4 == 0 ) ) {
1807  $diff += 13;
1808  } else {
1809  $diff += 12;
1810  }
1811 
1812  # Check the year pattern, and is leap year
1813  # 0 means an incomplete year, 1 means a regular year, 2 means a complete year
1814  # This is mod 30, to work on both leap years (which add 30 days of Adar I)
1815  # and non-leap years
1816  $yearPattern = $diff % 30;
1817  # Check if leap year
1818  $isLeap = $diff >= 30;
1819 
1820  # Calculate day in the month from number of day in the Hebrew year
1821  # Don't check Adar - if the day is not in Adar, we will stop before;
1822  # if it is in Adar, we will use it to check if it is Adar I or Adar II
1823  $hebrewDay = $hebrewDayOfYear;
1824  $hebrewMonth = 1;
1825  $days = 0;
1826  while ( $hebrewMonth <= 12 ) {
1827  # Calculate days in this month
1828  if ( $isLeap && $hebrewMonth == 6 ) {
1829  # Leap year - has Adar I, with 30 days, and Adar II, with 29 days
1830  $days = 30;
1831  if ( $hebrewDay <= $days ) {
1832  # Day in Adar I
1833  $hebrewMonth = 13;
1834  } else {
1835  # Subtract the days of Adar I
1836  $hebrewDay -= $days;
1837  # Try Adar II
1838  $days = 29;
1839  if ( $hebrewDay <= $days ) {
1840  # Day in Adar II
1841  $hebrewMonth = 14;
1842  }
1843  }
1844  } elseif ( $hebrewMonth == 2 && $yearPattern == 2 ) {
1845  # Cheshvan in a complete year (otherwise as the rule below)
1846  $days = 30;
1847  } elseif ( $hebrewMonth == 3 && $yearPattern == 0 ) {
1848  # Kislev in an incomplete year (otherwise as the rule below)
1849  $days = 29;
1850  } else {
1851  # Odd months have 30 days, even have 29
1852  $days = 30 - ( $hebrewMonth - 1 ) % 2;
1853  }
1854  if ( $hebrewDay <= $days ) {
1855  # In the current month
1856  break;
1857  } else {
1858  # Subtract the days of the current month
1859  $hebrewDay -= $days;
1860  # Try in the next month
1861  $hebrewMonth++;
1862  }
1863  }
1864 
1865  return [ $hebrewYear, $hebrewMonth, $hebrewDay, $days ];
1866  }
1867 
1877  private static function hebrewYearStart( $year ) {
1878  $a = intval( ( 12 * ( $year - 1 ) + 17 ) % 19 );
1879  $b = intval( ( $year - 1 ) % 4 );
1880  $m = 32.044093161144 + 1.5542417966212 * $a + $b / 4.0 - 0.0031777940220923 * ( $year - 1 );
1881  if ( $m < 0 ) {
1882  $m--;
1883  }
1884  $Mar = intval( $m );
1885  if ( $m < 0 ) {
1886  $m++;
1887  }
1888  $m -= $Mar;
1889 
1890  $c = intval( ( $Mar + 3 * ( $year - 1 ) + 5 * $b + 5 ) % 7 );
1891  if ( $c == 0 && $a > 11 && $m >= 0.89772376543210 ) {
1892  $Mar++;
1893  } elseif ( $c == 1 && $a > 6 && $m >= 0.63287037037037 ) {
1894  $Mar += 2;
1895  } elseif ( $c == 2 || $c == 4 || $c == 6 ) {
1896  $Mar++;
1897  }
1898 
1899  $Mar += intval( ( $year - 3761 ) / 100 ) - intval( ( $year - 3761 ) / 400 ) - 24;
1900  return $Mar;
1901  }
1902 
1915  private static function tsToYear( $ts, $cName ) {
1916  $gy = substr( $ts, 0, 4 );
1917  $gm = substr( $ts, 4, 2 );
1918  $gd = substr( $ts, 6, 2 );
1919 
1920  if ( !strcmp( $cName, 'thai' ) ) {
1921  # Thai solar dates
1922  # Add 543 years to the Gregorian calendar
1923  # Months and days are identical
1924  $gy_offset = $gy + 543;
1925  # fix for dates between 1912 and 1941
1926  # https://en.wikipedia.org/?oldid=836596673#New_year
1927  if ( $gy >= 1912 && $gy <= 1940 ) {
1928  if ( $gm <= 3 ) {
1929  $gy_offset--;
1930  }
1931  $gm = ( $gm - 3 ) % 12;
1932  }
1933  } elseif ( ( !strcmp( $cName, 'minguo' ) ) || !strcmp( $cName, 'juche' ) ) {
1934  # Minguo dates
1935  # Deduct 1911 years from the Gregorian calendar
1936  # Months and days are identical
1937  $gy_offset = $gy - 1911;
1938  } elseif ( !strcmp( $cName, 'tenno' ) ) {
1939  # Nengō dates up to Meiji period
1940  # Deduct years from the Gregorian calendar
1941  # depending on the nengo periods
1942  # Months and days are identical
1943  if ( ( $gy < 1912 )
1944  || ( ( $gy == 1912 ) && ( $gm < 7 ) )
1945  || ( ( $gy == 1912 ) && ( $gm == 7 ) && ( $gd < 31 ) )
1946  ) {
1947  # Meiji period
1948  $gy_gannen = $gy - 1868 + 1;
1949  $gy_offset = $gy_gannen;
1950  if ( $gy_gannen == 1 ) {
1951  $gy_offset = '元';
1952  }
1953  $gy_offset = '明治' . $gy_offset;
1954  } elseif (
1955  ( ( $gy == 1912 ) && ( $gm == 7 ) && ( $gd == 31 ) ) ||
1956  ( ( $gy == 1912 ) && ( $gm >= 8 ) ) ||
1957  ( ( $gy > 1912 ) && ( $gy < 1926 ) ) ||
1958  ( ( $gy == 1926 ) && ( $gm < 12 ) ) ||
1959  ( ( $gy == 1926 ) && ( $gm == 12 ) && ( $gd < 26 ) )
1960  ) {
1961  # Taishō period
1962  $gy_gannen = $gy - 1912 + 1;
1963  $gy_offset = $gy_gannen;
1964  if ( $gy_gannen == 1 ) {
1965  $gy_offset = '元';
1966  }
1967  $gy_offset = '大正' . $gy_offset;
1968  } elseif (
1969  ( ( $gy == 1926 ) && ( $gm == 12 ) && ( $gd >= 26 ) ) ||
1970  ( ( $gy > 1926 ) && ( $gy < 1989 ) ) ||
1971  ( ( $gy == 1989 ) && ( $gm == 1 ) && ( $gd < 8 ) )
1972  ) {
1973  # Shōwa period
1974  $gy_gannen = $gy - 1926 + 1;
1975  $gy_offset = $gy_gannen;
1976  if ( $gy_gannen == 1 ) {
1977  $gy_offset = '元';
1978  }
1979  $gy_offset = '昭和' . $gy_offset;
1980  } elseif (
1981  ( ( $gy == 1989 ) && ( $gm == 1 ) && ( $gd >= 8 ) ) ||
1982  ( ( $gy > 1989 ) && ( $gy < 2019 ) ) ||
1983  ( ( $gy == 2019 ) && ( $gm < 5 ) )
1984  ) {
1985  # Heisei period
1986  $gy_gannen = $gy - 1989 + 1;
1987  $gy_offset = $gy_gannen;
1988  if ( $gy_gannen == 1 ) {
1989  $gy_offset = '元';
1990  }
1991  $gy_offset = '平成' . $gy_offset;
1992  } else {
1993  # Reiwa period
1994  $gy_gannen = $gy - 2019 + 1;
1995  $gy_offset = $gy_gannen;
1996  if ( $gy_gannen == 1 ) {
1997  $gy_offset = '元';
1998  }
1999  $gy_offset = '令和' . $gy_offset;
2000  }
2001  } else {
2002  $gy_offset = $gy;
2003  }
2004 
2005  return [ $gy_offset, $gm, $gd ];
2006  }
2007 
2021  private static function strongDirFromContent( $text = '' ) {
2022  if ( !preg_match( self::$strongDirRegex, $text, $matches ) ) {
2023  return null;
2024  }
2025  if ( $matches[1] === '' ) {
2026  return 'rtl';
2027  }
2028  return 'ltr';
2029  }
2030 
2038  static function romanNumeral( $num ) {
2039  static $table = [
2040  [ '', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X' ],
2041  [ '', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC', 'C' ],
2042  [ '', 'C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM', 'M' ],
2043  [ '', 'M', 'MM', 'MMM', 'MMMM', 'MMMMM', 'MMMMMM', 'MMMMMMM',
2044  'MMMMMMMM', 'MMMMMMMMM', 'MMMMMMMMMM' ]
2045  ];
2046 
2047  $num = intval( $num );
2048  if ( $num > 10000 || $num <= 0 ) {
2049  return $num;
2050  }
2051 
2052  $s = '';
2053  for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) {
2054  if ( $num >= $pow10 ) {
2055  $s .= $table[$i][(int)floor( $num / $pow10 )];
2056  }
2057  $num = $num % $pow10;
2058  }
2059  return $s;
2060  }
2061 
2069  static function hebrewNumeral( $num ) {
2070  static $table = [
2071  [ '', 'א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ז', 'ח', 'ט', 'י' ],
2072  [ '', 'י', 'כ', 'ל', 'מ', 'נ', 'ס', 'ע', 'פ', 'צ', 'ק' ],
2073  [ '',
2074  [ 'ק' ],
2075  [ 'ר' ],
2076  [ 'ש' ],
2077  [ 'ת' ],
2078  [ 'ת', 'ק' ],
2079  [ 'ת', 'ר' ],
2080  [ 'ת', 'ש' ],
2081  [ 'ת', 'ת' ],
2082  [ 'ת', 'ת', 'ק' ],
2083  [ 'ת', 'ת', 'ר' ],
2084  ],
2085  [ '', 'א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ז', 'ח', 'ט', 'י' ]
2086  ];
2087 
2088  $num = intval( $num );
2089  if ( $num > 9999 || $num <= 0 ) {
2090  return $num;
2091  }
2092 
2093  // Round thousands have special notations
2094  if ( $num === 1000 ) {
2095  return "א' אלף";
2096  } elseif ( $num % 1000 === 0 ) {
2097  return $table[0][$num / 1000] . "' אלפים";
2098  }
2099 
2100  $letters = [];
2101 
2102  for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) {
2103  if ( $num >= $pow10 ) {
2104  if ( $num === 15 || $num === 16 ) {
2105  $letters[] = $table[0][9];
2106  $letters[] = $table[0][$num - 9];
2107  $num = 0;
2108  } else {
2109  $letters = array_merge(
2110  $letters,
2111  (array)$table[$i][intval( $num / $pow10 )]
2112  );
2113 
2114  if ( $pow10 === 1000 ) {
2115  $letters[] = "'";
2116  }
2117  }
2118  }
2119 
2120  $num = $num % $pow10;
2121  }
2122 
2123  $preTransformLength = count( $letters );
2124  if ( $preTransformLength === 1 ) {
2125  // Add geresh (single quote) to one-letter numbers
2126  $letters[] = "'";
2127  } else {
2128  $lastIndex = $preTransformLength - 1;
2129  $letters[$lastIndex] = str_replace(
2130  [ 'כ', 'מ', 'נ', 'פ', 'צ' ],
2131  [ 'ך', 'ם', 'ן', 'ף', 'ץ' ],
2132  $letters[$lastIndex]
2133  );
2134 
2135  // Add gershayim (double quote) to multiple-letter numbers,
2136  // but exclude numbers with only one letter after the thousands
2137  // (1001-1009, 1020, 1030, 2001-2009, etc.)
2138  if ( $letters[1] === "'" && $preTransformLength === 3 ) {
2139  $letters[] = "'";
2140  } else {
2141  array_splice( $letters, -1, 0, '"' );
2142  }
2143  }
2144 
2145  return implode( $letters );
2146  }
2147 
2156  public function userAdjust( $ts, $tz = false ) {
2157  global $wgUser, $wgLocalTZoffset;
2158 
2159  if ( $tz === false ) {
2160  $tz = $wgUser->getOption( 'timecorrection' );
2161  }
2162 
2163  $data = explode( '|', $tz, 3 );
2164 
2165  if ( $data[0] == 'ZoneInfo' ) {
2166  try {
2167  $userTZ = new DateTimeZone( $data[2] );
2168  $date = new DateTime( $ts, new DateTimeZone( 'UTC' ) );
2169  $date->setTimezone( $userTZ );
2170  return $date->format( 'YmdHis' );
2171  } catch ( Exception $e ) {
2172  // Unrecognized timezone, default to 'Offset' with the stored offset.
2173  $data[0] = 'Offset';
2174  }
2175  }
2176 
2177  if ( $data[0] == 'System' || $tz == '' ) {
2178  # Global offset in minutes.
2179  $minDiff = $wgLocalTZoffset;
2180  } elseif ( $data[0] == 'Offset' ) {
2181  $minDiff = intval( $data[1] );
2182  } else {
2183  $data = explode( ':', $tz );
2184  if ( count( $data ) == 2 ) {
2185  $data[0] = intval( $data[0] );
2186  $data[1] = intval( $data[1] );
2187  $minDiff = abs( $data[0] ) * 60 + $data[1];
2188  if ( $data[0] < 0 ) {
2189  $minDiff = -$minDiff;
2190  }
2191  } else {
2192  $minDiff = intval( $data[0] ) * 60;
2193  }
2194  }
2195 
2196  # No difference ? Return time unchanged
2197  if ( $minDiff == 0 ) {
2198  return $ts;
2199  }
2200 
2201  Wikimedia\suppressWarnings(); // E_STRICT system time bitching
2202  # Generate an adjusted date; take advantage of the fact that mktime
2203  # will normalize out-of-range values so we don't have to split $minDiff
2204  # into hours and minutes.
2205  $t = mktime( (
2206  (int)substr( $ts, 8, 2 ) ), # Hours
2207  (int)substr( $ts, 10, 2 ) + $minDiff, # Minutes
2208  (int)substr( $ts, 12, 2 ), # Seconds
2209  (int)substr( $ts, 4, 2 ), # Month
2210  (int)substr( $ts, 6, 2 ), # Day
2211  (int)substr( $ts, 0, 4 ) ); # Year
2212 
2213  $date = date( 'YmdHis', $t );
2214  Wikimedia\restoreWarnings();
2215 
2216  return $date;
2217  }
2218 
2234  function dateFormat( $usePrefs = true ) {
2235  global $wgUser;
2236 
2237  if ( is_bool( $usePrefs ) ) {
2238  if ( $usePrefs ) {
2239  $datePreference = $wgUser->getDatePreference();
2240  } else {
2241  $datePreference = (string)User::getDefaultOption( 'date' );
2242  }
2243  } else {
2244  $datePreference = (string)$usePrefs;
2245  }
2246 
2247  // return int
2248  if ( $datePreference == '' ) {
2249  return 'default';
2250  }
2251 
2252  return $datePreference;
2253  }
2254 
2265  function getDateFormatString( $type, $pref ) {
2266  $wasDefault = false;
2267  if ( $pref == 'default' ) {
2268  $wasDefault = true;
2269  $pref = $this->getDefaultDateFormat();
2270  }
2271 
2272  if ( !isset( $this->dateFormatStrings[$type][$pref] ) ) {
2273  $df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" );
2274 
2275  if ( $type === 'pretty' && $df === null ) {
2276  $df = $this->getDateFormatString( 'date', $pref );
2277  }
2278 
2279  if ( !$wasDefault && $df === null ) {
2280  $pref = $this->getDefaultDateFormat();
2281  $df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" );
2282  }
2283 
2284  $this->dateFormatStrings[$type][$pref] = $df;
2285  }
2286  return $this->dateFormatStrings[$type][$pref];
2287  }
2288 
2299  public function date( $ts, $adj = false, $format = true, $timecorrection = false ) {
2300  $ts = wfTimestamp( TS_MW, $ts );
2301  if ( $adj ) {
2302  $ts = $this->userAdjust( $ts, $timecorrection );
2303  }
2304  $df = $this->getDateFormatString( 'date', $this->dateFormat( $format ) );
2305  return $this->sprintfDate( $df, $ts );
2306  }
2307 
2318  public function time( $ts, $adj = false, $format = true, $timecorrection = false ) {
2319  $ts = wfTimestamp( TS_MW, $ts );
2320  if ( $adj ) {
2321  $ts = $this->userAdjust( $ts, $timecorrection );
2322  }
2323  $df = $this->getDateFormatString( 'time', $this->dateFormat( $format ) );
2324  return $this->sprintfDate( $df, $ts );
2325  }
2326 
2338  public function timeanddate( $ts, $adj = false, $format = true, $timecorrection = false ) {
2339  $ts = wfTimestamp( TS_MW, $ts );
2340  if ( $adj ) {
2341  $ts = $this->userAdjust( $ts, $timecorrection );
2342  }
2343  $df = $this->getDateFormatString( 'both', $this->dateFormat( $format ) );
2344  return $this->sprintfDate( $df, $ts );
2345  }
2346 
2357  public function formatDuration( $seconds, array $chosenIntervals = [] ) {
2358  $intervals = $this->getDurationIntervals( $seconds, $chosenIntervals );
2359 
2360  $segments = [];
2361 
2362  foreach ( $intervals as $intervalName => $intervalValue ) {
2363  // Messages: duration-seconds, duration-minutes, duration-hours, duration-days, duration-weeks,
2364  // duration-years, duration-decades, duration-centuries, duration-millennia
2365  $message = wfMessage( 'duration-' . $intervalName )->numParams( $intervalValue );
2366  $segments[] = $message->inLanguage( $this )->escaped();
2367  }
2368 
2369  return $this->listToText( $segments );
2370  }
2371 
2383  public function getDurationIntervals( $seconds, array $chosenIntervals = [] ) {
2384  if ( empty( $chosenIntervals ) ) {
2385  $chosenIntervals = [
2386  'millennia',
2387  'centuries',
2388  'decades',
2389  'years',
2390  'days',
2391  'hours',
2392  'minutes',
2393  'seconds'
2394  ];
2395  }
2396 
2397  $intervals = array_intersect_key( self::$durationIntervals, array_flip( $chosenIntervals ) );
2398  $sortedNames = array_keys( $intervals );
2399  $smallestInterval = array_pop( $sortedNames );
2400 
2401  $segments = [];
2402 
2403  foreach ( $intervals as $name => $length ) {
2404  $value = floor( $seconds / $length );
2405 
2406  if ( $value > 0 || ( $name == $smallestInterval && empty( $segments ) ) ) {
2407  $seconds -= $value * $length;
2408  $segments[$name] = $value;
2409  }
2410  }
2411 
2412  return $segments;
2413  }
2414 
2434  private function internalUserTimeAndDate( $type, $ts, User $user, array $options ) {
2435  $ts = wfTimestamp( TS_MW, $ts );
2436  $options += [ 'timecorrection' => true, 'format' => true ];
2437  if ( $options['timecorrection'] !== false ) {
2438  if ( $options['timecorrection'] === true ) {
2439  $offset = $user->getOption( 'timecorrection' );
2440  } else {
2441  $offset = $options['timecorrection'];
2442  }
2443  $ts = $this->userAdjust( $ts, $offset );
2444  }
2445  if ( $options['format'] === true ) {
2446  $format = $user->getDatePreference();
2447  } else {
2448  $format = $options['format'];
2449  }
2450  $df = $this->getDateFormatString( $type, $this->dateFormat( $format ) );
2451  return $this->sprintfDate( $df, $ts );
2452  }
2453 
2473  public function userDate( $ts, User $user, array $options = [] ) {
2474  return $this->internalUserTimeAndDate( 'date', $ts, $user, $options );
2475  }
2476 
2496  public function userTime( $ts, User $user, array $options = [] ) {
2497  return $this->internalUserTimeAndDate( 'time', $ts, $user, $options );
2498  }
2499 
2519  public function userTimeAndDate( $ts, User $user, array $options = [] ) {
2520  return $this->internalUserTimeAndDate( 'both', $ts, $user, $options );
2521  }
2522 
2538  public function getHumanTimestamp(
2539  MWTimestamp $time, MWTimestamp $relativeTo = null, User $user = null
2540  ) {
2541  if ( $relativeTo === null ) {
2542  $relativeTo = new MWTimestamp();
2543  }
2544  if ( $user === null ) {
2545  $user = RequestContext::getMain()->getUser();
2546  }
2547 
2548  // Adjust for the user's timezone.
2549  $offsetThis = $time->offsetForUser( $user );
2550  $offsetRel = $relativeTo->offsetForUser( $user );
2551 
2552  $ts = '';
2553  if ( Hooks::run( 'GetHumanTimestamp', [ &$ts, $time, $relativeTo, $user, $this ] ) ) {
2554  $ts = $this->getHumanTimestampInternal( $time, $relativeTo, $user );
2555  }
2556 
2557  // Reset the timezone on the objects.
2558  $time->timestamp->sub( $offsetThis );
2559  $relativeTo->timestamp->sub( $offsetRel );
2560 
2561  return $ts;
2562  }
2563 
2575  private function getHumanTimestampInternal(
2576  MWTimestamp $ts, MWTimestamp $relativeTo, User $user
2577  ) {
2578  $diff = $ts->diff( $relativeTo );
2579  $diffDay = (bool)( (int)$ts->timestamp->format( 'w' ) -
2580  (int)$relativeTo->timestamp->format( 'w' ) );
2581  $days = $diff->days ?: (int)$diffDay;
2582  if ( $diff->invert || $days > 5
2583  && $ts->timestamp->format( 'Y' ) !== $relativeTo->timestamp->format( 'Y' )
2584  ) {
2585  // Timestamps are in different years: use full timestamp
2586  // Also do full timestamp for future dates
2590  $format = $this->getDateFormatString( 'both', $user->getDatePreference() ?: 'default' );
2591  $ts = $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) );
2592  } elseif ( $days > 5 ) {
2593  // Timestamps are in same year, but more than 5 days ago: show day and month only.
2594  $format = $this->getDateFormatString( 'pretty', $user->getDatePreference() ?: 'default' );
2595  $ts = $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) );
2596  } elseif ( $days > 1 ) {
2597  // Timestamp within the past week: show the day of the week and time
2598  $format = $this->getDateFormatString( 'time', $user->getDatePreference() ?: 'default' );
2599  $weekday = self::$mWeekdayMsgs[$ts->timestamp->format( 'w' )];
2600  // Messages:
2601  // sunday-at, monday-at, tuesday-at, wednesday-at, thursday-at, friday-at, saturday-at
2602  $ts = wfMessage( "$weekday-at" )
2603  ->inLanguage( $this )
2604  ->params( $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) ) )
2605  ->text();
2606  } elseif ( $days == 1 ) {
2607  // Timestamp was yesterday: say 'yesterday' and the time.
2608  $format = $this->getDateFormatString( 'time', $user->getDatePreference() ?: 'default' );
2609  $ts = wfMessage( 'yesterday-at' )
2610  ->inLanguage( $this )
2611  ->params( $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) ) )
2612  ->text();
2613  } elseif ( $diff->h > 1 || $diff->h == 1 && $diff->i > 30 ) {
2614  // Timestamp was today, but more than 90 minutes ago: say 'today' and the time.
2615  $format = $this->getDateFormatString( 'time', $user->getDatePreference() ?: 'default' );
2616  $ts = wfMessage( 'today-at' )
2617  ->inLanguage( $this )
2618  ->params( $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) ) )
2619  ->text();
2620 
2621  // From here on in, the timestamp was soon enough ago so that we can simply say
2622  // XX units ago, e.g., "2 hours ago" or "5 minutes ago"
2623  } elseif ( $diff->h == 1 ) {
2624  // Less than 90 minutes, but more than an hour ago.
2625  $ts = wfMessage( 'hours-ago' )->inLanguage( $this )->numParams( 1 )->text();
2626  } elseif ( $diff->i >= 1 ) {
2627  // A few minutes ago.
2628  $ts = wfMessage( 'minutes-ago' )->inLanguage( $this )->numParams( $diff->i )->text();
2629  } elseif ( $diff->s >= 30 ) {
2630  // Less than a minute, but more than 30 sec ago.
2631  $ts = wfMessage( 'seconds-ago' )->inLanguage( $this )->numParams( $diff->s )->text();
2632  } else {
2633  // Less than 30 seconds ago.
2634  $ts = wfMessage( 'just-now' )->text();
2635  }
2636 
2637  return $ts;
2638  }
2639 
2644  public function getMessage( $key ) {
2645  return self::$dataCache->getSubitem( $this->mCode, 'messages', $key );
2646  }
2647 
2651  function getAllMessages() {
2652  return self::$dataCache->getItem( $this->mCode, 'messages' );
2653  }
2654 
2661  public function iconv( $in, $out, $string ) {
2662  # Even with //IGNORE iconv can whine about illegal characters in
2663  # *input* string. We just ignore those too.
2664  # REF: https://bugs.php.net/bug.php?id=37166
2665  # REF: https://phabricator.wikimedia.org/T18885
2666  Wikimedia\suppressWarnings();
2667  $text = iconv( $in, $out . '//IGNORE', $string );
2668  Wikimedia\restoreWarnings();
2669  return $text;
2670  }
2671 
2672  // callback functions for ucwords(), ucwordbreaks()
2673 
2679  return $this->ucfirst( $matches[1] );
2680  }
2681 
2687  return mb_strtoupper( $matches[0] );
2688  }
2689 
2695  return mb_strtoupper( $matches[0] );
2696  }
2697 
2705  public function ucfirst( $str ) {
2706  $o = ord( $str );
2707  if ( $o < 96 ) { // if already uppercase...
2708  return $str;
2709  } elseif ( $o < 128 ) {
2710  return ucfirst( $str ); // use PHP's ucfirst()
2711  } else {
2712  // fall back to more complex logic in case of multibyte strings
2713  return $this->uc( $str, true );
2714  }
2715  }
2716 
2725  public function uc( $str, $first = false ) {
2726  if ( $first ) {
2727  if ( $this->isMultibyte( $str ) ) {
2728  return mb_strtoupper( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
2729  } else {
2730  return ucfirst( $str );
2731  }
2732  } else {
2733  return $this->isMultibyte( $str ) ? mb_strtoupper( $str ) : strtoupper( $str );
2734  }
2735  }
2736 
2741  function lcfirst( $str ) {
2742  $o = ord( $str );
2743  if ( !$o ) {
2744  return strval( $str );
2745  } elseif ( $o >= 128 ) {
2746  return $this->lc( $str, true );
2747  } elseif ( $o > 96 ) {
2748  return $str;
2749  } else {
2750  $str[0] = strtolower( $str[0] );
2751  return $str;
2752  }
2753  }
2754 
2760  function lc( $str, $first = false ) {
2761  if ( $first ) {
2762  if ( $this->isMultibyte( $str ) ) {
2763  return mb_strtolower( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
2764  } else {
2765  return strtolower( substr( $str, 0, 1 ) ) . substr( $str, 1 );
2766  }
2767  } else {
2768  return $this->isMultibyte( $str ) ? mb_strtolower( $str ) : strtolower( $str );
2769  }
2770  }
2771 
2776  function isMultibyte( $str ) {
2777  return strlen( $str ) !== mb_strlen( $str );
2778  }
2779 
2784  function ucwords( $str ) {
2785  if ( $this->isMultibyte( $str ) ) {
2786  $str = $this->lc( $str );
2787 
2788  // regexp to find first letter in each word (i.e. after each space)
2789  $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)| ([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
2790 
2791  // function to use to capitalize a single char
2792  return preg_replace_callback(
2793  $replaceRegexp,
2794  [ $this, 'ucwordsCallbackMB' ],
2795  $str
2796  );
2797  } else {
2798  return ucwords( strtolower( $str ) );
2799  }
2800  }
2801 
2808  function ucwordbreaks( $str ) {
2809  if ( $this->isMultibyte( $str ) ) {
2810  $str = $this->lc( $str );
2811 
2812  // since \b doesn't work for UTF-8, we explicitely define word break chars
2813  $breaks = "[ \-\(\)\}\{\.,\?!]";
2814 
2815  // find first letter after word break
2816  $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)|" .
2817  "$breaks([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
2818 
2819  return preg_replace_callback(
2820  $replaceRegexp,
2821  [ $this, 'ucwordbreaksCallbackMB' ],
2822  $str
2823  );
2824  } else {
2825  return preg_replace_callback(
2826  '/\b([\w\x80-\xff]+)\b/',
2827  [ $this, 'ucwordbreaksCallbackAscii' ],
2828  $str
2829  );
2830  }
2831  }
2832 
2848  function caseFold( $s ) {
2849  return $this->uc( $s );
2850  }
2851 
2857  function checkTitleEncoding( $s ) {
2858  if ( is_array( $s ) ) {
2859  throw new MWException( 'Given array to checkTitleEncoding.' );
2860  }
2861  if ( StringUtils::isUtf8( $s ) ) {
2862  return $s;
2863  }
2864 
2865  return $this->iconv( $this->fallback8bitEncoding(), 'utf-8', $s );
2866  }
2867 
2872  return self::$dataCache->getItem( $this->mCode, 'fallback8bitEncoding' );
2873  }
2874 
2883  function hasWordBreaks() {
2884  return true;
2885  }
2886 
2894  function segmentByWord( $string ) {
2895  return $string;
2896  }
2897 
2905  function normalizeForSearch( $string ) {
2906  return self::convertDoubleWidth( $string );
2907  }
2908 
2917  protected static function convertDoubleWidth( $string ) {
2918  static $full = null;
2919  static $half = null;
2920 
2921  if ( $full === null ) {
2922  $fullWidth = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
2923  $halfWidth = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
2924  $full = str_split( $fullWidth, 3 );
2925  $half = str_split( $halfWidth );
2926  }
2927 
2928  $string = str_replace( $full, $half, $string );
2929  return $string;
2930  }
2931 
2937  protected static function insertSpace( $string, $pattern ) {
2938  $string = preg_replace( $pattern, " $1 ", $string );
2939  $string = preg_replace( '/ +/', ' ', $string );
2940  return $string;
2941  }
2942 
2947  function convertForSearchResult( $termsArray ) {
2948  # some languages, e.g. Chinese, need to do a conversion
2949  # in order for search results to be displayed correctly
2950  return $termsArray;
2951  }
2952 
2959  function firstChar( $s ) {
2960  $matches = [];
2961  preg_match(
2962  '/^([\x00-\x7f]|[\xc0-\xdf][\x80-\xbf]|' .
2963  '[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xf7][\x80-\xbf]{3})/',
2964  $s,
2965  $matches
2966  );
2967 
2968  if ( isset( $matches[1] ) ) {
2969  if ( strlen( $matches[1] ) != 3 ) {
2970  return $matches[1];
2971  }
2972 
2973  // Break down Hangul syllables to grab the first jamo
2974  $code = UtfNormal\Utils::utf8ToCodepoint( $matches[1] );
2975  if ( $code < 0xac00 || 0xd7a4 <= $code ) {
2976  return $matches[1];
2977  } elseif ( $code < 0xb098 ) {
2978  return "\u{3131}";
2979  } elseif ( $code < 0xb2e4 ) {
2980  return "\u{3134}";
2981  } elseif ( $code < 0xb77c ) {
2982  return "\u{3137}";
2983  } elseif ( $code < 0xb9c8 ) {
2984  return "\u{3139}";
2985  } elseif ( $code < 0xbc14 ) {
2986  return "\u{3141}";
2987  } elseif ( $code < 0xc0ac ) {
2988  return "\u{3142}";
2989  } elseif ( $code < 0xc544 ) {
2990  return "\u{3145}";
2991  } elseif ( $code < 0xc790 ) {
2992  return "\u{3147}";
2993  } elseif ( $code < 0xcc28 ) {
2994  return "\u{3148}";
2995  } elseif ( $code < 0xce74 ) {
2996  return "\u{314A}";
2997  } elseif ( $code < 0xd0c0 ) {
2998  return "\u{314B}";
2999  } elseif ( $code < 0xd30c ) {
3000  return "\u{314C}";
3001  } elseif ( $code < 0xd558 ) {
3002  return "\u{314D}";
3003  } else {
3004  return "\u{314E}";
3005  }
3006  } else {
3007  return '';
3008  }
3009  }
3010 
3014  function initEncoding() {
3015  wfDeprecated( __METHOD__, '1.28' );
3016  // No-op.
3017  }
3018 
3024  function recodeForEdit( $s ) {
3025  wfDeprecated( __METHOD__, '1.28' );
3026  return $s;
3027  }
3028 
3034  function recodeInput( $s ) {
3035  wfDeprecated( __METHOD__, '1.28' );
3036  return $s;
3037  }
3038 
3050  public function normalize( $s ) {
3051  global $wgAllUnicodeFixes;
3052  $s = UtfNormal\Validator::cleanUp( $s );
3053  if ( $wgAllUnicodeFixes ) {
3054  $s = $this->transformUsingPairFile( 'normalize-ar.php', $s );
3055  $s = $this->transformUsingPairFile( 'normalize-ml.php', $s );
3056  }
3057 
3058  return $s;
3059  }
3060 
3075  protected function transformUsingPairFile( $file, $string ) {
3076  if ( !isset( $this->transformData[$file] ) ) {
3077  global $IP;
3078  $data = require "$IP/languages/data/{$file}";
3079  $this->transformData[$file] = new ReplacementArray( $data );
3080  }
3081  return $this->transformData[$file]->replace( $string );
3082  }
3083 
3089  function isRTL() {
3090  return self::$dataCache->getItem( $this->mCode, 'rtl' );
3091  }
3092 
3097  function getDir() {
3098  return $this->isRTL() ? 'rtl' : 'ltr';
3099  }
3100 
3109  function alignStart() {
3110  return $this->isRTL() ? 'right' : 'left';
3111  }
3112 
3121  function alignEnd() {
3122  return $this->isRTL() ? 'left' : 'right';
3123  }
3124 
3136  function getDirMarkEntity( $opposite = false ) {
3137  if ( $opposite ) {
3138  return $this->isRTL() ? '&lrm;' : '&rlm;';
3139  }
3140  return $this->isRTL() ? '&rlm;' : '&lrm;';
3141  }
3142 
3153  function getDirMark( $opposite = false ) {
3154  $lrm = "\u{200E}"; # LEFT-TO-RIGHT MARK, commonly abbreviated LRM
3155  $rlm = "\u{200F}"; # RIGHT-TO-LEFT MARK, commonly abbreviated RLM
3156  if ( $opposite ) {
3157  return $this->isRTL() ? $lrm : $rlm;
3158  }
3159  return $this->isRTL() ? $rlm : $lrm;
3160  }
3161 
3165  function capitalizeAllNouns() {
3166  return self::$dataCache->getItem( $this->mCode, 'capitalizeAllNouns' );
3167  }
3168 
3176  function getArrow( $direction = 'forwards' ) {
3177  switch ( $direction ) {
3178  case 'forwards':
3179  return $this->isRTL() ? '←' : '→';
3180  case 'backwards':
3181  return $this->isRTL() ? '→' : '←';
3182  case 'left':
3183  return '←';
3184  case 'right':
3185  return '→';
3186  case 'up':
3187  return '↑';
3188  case 'down':
3189  return '↓';
3190  }
3191  }
3192 
3198  function linkPrefixExtension() {
3199  return self::$dataCache->getItem( $this->mCode, 'linkPrefixExtension' );
3200  }
3201 
3206  function getMagicWords() {
3207  return self::$dataCache->getItem( $this->mCode, 'magicWords' );
3208  }
3209 
3215  function getMagic( $mw ) {
3216  $rawEntry = $this->mMagicExtensions[$mw->mId] ??
3217  self::$dataCache->getSubitem( $this->mCode, 'magicWords', $mw->mId );
3218 
3219  if ( !is_array( $rawEntry ) ) {
3220  wfWarn( "\"$rawEntry\" is not a valid magic word for \"$mw->mId\"" );
3221  } else {
3222  $mw->mCaseSensitive = $rawEntry[0];
3223  $mw->mSynonyms = array_slice( $rawEntry, 1 );
3224  }
3225  }
3226 
3232  function addMagicWordsByLang( $newWords ) {
3233  $fallbackChain = $this->getFallbackLanguages();
3234  $fallbackChain = array_reverse( $fallbackChain );
3235  foreach ( $fallbackChain as $code ) {
3236  if ( isset( $newWords[$code] ) ) {
3237  $this->mMagicExtensions = $newWords[$code] + $this->mMagicExtensions;
3238  }
3239  }
3240  }
3241 
3248  // Cache aliases because it may be slow to load them
3249  if ( is_null( $this->mExtendedSpecialPageAliases ) ) {
3250  // Initialise array
3251  $this->mExtendedSpecialPageAliases =
3252  self::$dataCache->getItem( $this->mCode, 'specialPageAliases' );
3253  }
3254 
3256  }
3257 
3264  function emphasize( $text ) {
3265  return "<em>$text</em>";
3266  }
3267 
3290  public function formatNum( $number, $nocommafy = false ) {
3291  global $wgTranslateNumerals;
3292  if ( !$nocommafy ) {
3293  $number = $this->commafy( $number );
3294  $s = $this->separatorTransformTable();
3295  if ( $s ) {
3296  $number = strtr( $number, $s );
3297  }
3298  }
3299 
3300  if ( $wgTranslateNumerals ) {
3301  $s = $this->digitTransformTable();
3302  if ( $s ) {
3303  $number = strtr( $number, $s );
3304  }
3305  }
3306 
3307  return (string)$number;
3308  }
3309 
3318  public function formatNumNoSeparators( $number ) {
3319  return $this->formatNum( $number, true );
3320  }
3321 
3326  public function parseFormattedNumber( $number ) {
3327  $s = $this->digitTransformTable();
3328  if ( $s ) {
3329  // eliminate empty array values such as ''. (T66347)
3330  $s = array_filter( $s );
3331  $number = strtr( $number, array_flip( $s ) );
3332  }
3333 
3334  $s = $this->separatorTransformTable();
3335  if ( $s ) {
3336  // eliminate empty array values such as ''. (T66347)
3337  $s = array_filter( $s );
3338  $number = strtr( $number, array_flip( $s ) );
3339  }
3340 
3341  $number = strtr( $number, [ ',' => '' ] );
3342  return $number;
3343  }
3344 
3351  function commafy( $number ) {
3354  if ( $number === null ) {
3355  return '';
3356  }
3357 
3358  if ( !$digitGroupingPattern || $digitGroupingPattern === "###,###,###" ) {
3359  // Default grouping is at thousands, use the same for ###,###,### pattern too.
3360  // In some languages it's conventional not to insert a thousands separator
3361  // in numbers that are four digits long (1000-9999).
3362  if ( $minimumGroupingDigits ) {
3363  // Number of '#' characters after last comma in the grouping pattern.
3364  // The pattern is hardcoded here, but this would vary for different patterns.
3365  $primaryGroupingSize = 3;
3366  // Maximum length of a number to suppress digit grouping for.
3367  $maximumLength = $minimumGroupingDigits + $primaryGroupingSize - 1;
3368  if ( preg_match( '/^\-?\d{1,' . $maximumLength . '}(\.\d+)?$/', $number ) ) {
3369  return $number;
3370  }
3371  }
3372  return strrev( (string)preg_replace( '/(\d{3})(?=\d)(?!\d*\.)/', '$1,', strrev( $number ) ) );
3373  } else {
3374  // Ref: http://cldr.unicode.org/translation/number-patterns
3375  $sign = "";
3376  if ( intval( $number ) < 0 ) {
3377  // For negative numbers apply the algorithm like positive number and add sign.
3378  $sign = "-";
3379  $number = substr( $number, 1 );
3380  }
3381  $integerPart = [];
3382  $decimalPart = [];
3383  $numMatches = preg_match_all( "/(#+)/", $digitGroupingPattern, $matches );
3384  preg_match( "/\d+/", $number, $integerPart );
3385  preg_match( "/\.\d*/", $number, $decimalPart );
3386  $groupedNumber = ( count( $decimalPart ) > 0 ) ? $decimalPart[0] : "";
3387  if ( $groupedNumber === $number ) {
3388  // the string does not have any number part. Eg: .12345
3389  return $sign . $groupedNumber;
3390  }
3391  $start = $end = ( $integerPart ) ? strlen( $integerPart[0] ) : 0;
3392  while ( $start > 0 ) {
3393  $match = $matches[0][$numMatches - 1];
3394  $matchLen = strlen( $match );
3395  $start = $end - $matchLen;
3396  if ( $start < 0 ) {
3397  $start = 0;
3398  }
3399  $groupedNumber = substr( $number, $start, $end - $start ) . $groupedNumber;
3400  $end = $start;
3401  if ( $numMatches > 1 ) {
3402  // use the last pattern for the rest of the number
3403  $numMatches--;
3404  }
3405  if ( $start > 0 ) {
3406  $groupedNumber = "," . $groupedNumber;
3407  }
3408  }
3409  return $sign . $groupedNumber;
3410  }
3411  }
3412 
3417  return self::$dataCache->getItem( $this->mCode, 'digitGroupingPattern' );
3418  }
3419 
3423  function digitTransformTable() {
3424  return self::$dataCache->getItem( $this->mCode, 'digitTransformTable' );
3425  }
3426 
3431  return self::$dataCache->getItem( $this->mCode, 'separatorTransformTable' );
3432  }
3433 
3438  return self::$dataCache->getItem( $this->mCode, 'minimumGroupingDigits' );
3439  }
3440 
3449  public function listToText( array $list ) {
3450  $itemCount = count( $list );
3451  if ( $itemCount < 1 ) {
3452  return '';
3453  }
3454  $text = array_pop( $list );
3455  if ( $itemCount > 1 ) {
3456  $and = $this->msg( 'and' )->escaped();
3457  $space = $this->msg( 'word-separator' )->escaped();
3458  $comma = '';
3459  if ( $itemCount > 2 ) {
3460  $comma = $this->msg( 'comma-separator' )->escaped();
3461  }
3462  $text = implode( $comma, $list ) . $and . $space . $text;
3463  }
3464  return $text;
3465  }
3466 
3473  function commaList( array $list ) {
3474  return implode(
3475  wfMessage( 'comma-separator' )->inLanguage( $this )->escaped(),
3476  $list
3477  );
3478  }
3479 
3486  function semicolonList( array $list ) {
3487  return implode(
3488  wfMessage( 'semicolon-separator' )->inLanguage( $this )->escaped(),
3489  $list
3490  );
3491  }
3492 
3498  function pipeList( array $list ) {
3499  return implode(
3500  wfMessage( 'pipe-separator' )->inLanguage( $this )->escaped(),
3501  $list
3502  );
3503  }
3504 
3520  function truncateForDatabase( $string, $length, $ellipsis = '...', $adjustLength = true ) {
3521  return $this->truncateInternal(
3522  $string, $length, $ellipsis, $adjustLength, 'strlen', 'substr'
3523  );
3524  }
3525 
3544  function truncateForVisual( $string, $length, $ellipsis = '...', $adjustLength = true ) {
3545  // Passing encoding to mb_strlen and mb_substr is optional.
3546  // Encoding defaults to mb_internal_encoding(), which is set to UTF-8 in Setup.php, so
3547  // explicit specification of encoding is skipped.
3548  // Note: Both multibyte methods are callables invoked in truncateInternal.
3549  return $this->truncateInternal(
3550  $string, $length, $ellipsis, $adjustLength, 'mb_strlen', 'mb_substr'
3551  );
3552  }
3553 
3570  private function truncateInternal(
3571  $string, $length, $ellipsis, $adjustLength, callable $measureLength, callable $getSubstring
3572  ) {
3573  # Check if there is no need to truncate
3574  if ( $measureLength( $string ) <= abs( $length ) ) {
3575  return $string; // no need to truncate
3576  }
3577 
3578  # Use the localized ellipsis character
3579  if ( $ellipsis == '...' ) {
3580  $ellipsis = wfMessage( 'ellipsis' )->inLanguage( $this )->escaped();
3581  }
3582  if ( $length == 0 ) {
3583  return $ellipsis; // convention
3584  }
3585 
3586  $stringOriginal = $string;
3587  # If ellipsis length is >= $length then we can't apply $adjustLength
3588  if ( $adjustLength && $measureLength( $ellipsis ) >= abs( $length ) ) {
3589  $string = $ellipsis; // this can be slightly unexpected
3590  # Otherwise, truncate and add ellipsis...
3591  } else {
3592  $ellipsisLength = $adjustLength ? $measureLength( $ellipsis ) : 0;
3593  if ( $length > 0 ) {
3594  $length -= $ellipsisLength;
3595  $string = $getSubstring( $string, 0, $length ); // xyz...
3596  $string = $this->removeBadCharLast( $string );
3597  $string = rtrim( $string ) . $ellipsis;
3598  } else {
3599  $length += $ellipsisLength;
3600  $string = $getSubstring( $string, $length ); // ...xyz
3601  $string = $this->removeBadCharFirst( $string );
3602  $string = $ellipsis . ltrim( $string );
3603  }
3604  }
3605 
3606  # Do not truncate if the ellipsis makes the string longer/equal (T24181).
3607  # This check is *not* redundant if $adjustLength, due to the single case where
3608  # LEN($ellipsis) > ABS($limit arg); $stringOriginal could be shorter than $string.
3609  if ( $measureLength( $string ) < $measureLength( $stringOriginal ) ) {
3610  return $string;
3611  } else {
3612  return $stringOriginal;
3613  }
3614  }
3615 
3623  protected function removeBadCharLast( $string ) {
3624  if ( $string != '' ) {
3625  $char = ord( $string[strlen( $string ) - 1] );
3626  $m = [];
3627  if ( $char >= 0xc0 ) {
3628  # We got the first byte only of a multibyte char; remove it.
3629  $string = substr( $string, 0, -1 );
3630  } elseif ( $char >= 0x80 &&
3631  // Use the /s modifier (PCRE_DOTALL) so (.*) also matches newlines
3632  preg_match( '/^(.*)(?:[\xe0-\xef][\x80-\xbf]|' .
3633  '[\xf0-\xf7][\x80-\xbf]{1,2})$/s', $string, $m )
3634  ) {
3635  # We chopped in the middle of a character; remove it
3636  $string = $m[1];
3637  }
3638  }
3639  return $string;
3640  }
3641 
3649  protected function removeBadCharFirst( $string ) {
3650  if ( $string != '' ) {
3651  $char = ord( $string[0] );
3652  if ( $char >= 0x80 && $char < 0xc0 ) {
3653  # We chopped in the middle of a character; remove the whole thing
3654  $string = preg_replace( '/^[\x80-\xbf]+/', '', $string );
3655  }
3656  }
3657  return $string;
3658  }
3659 
3675  function truncateHtml( $text, $length, $ellipsis = '...' ) {
3676  # Use the localized ellipsis character
3677  if ( $ellipsis == '...' ) {
3678  $ellipsis = wfMessage( 'ellipsis' )->inLanguage( $this )->escaped();
3679  }
3680  # Check if there is clearly no need to truncate
3681  if ( $length <= 0 ) {
3682  return $ellipsis; // no text shown, nothing to format (convention)
3683  } elseif ( strlen( $text ) <= $length ) {
3684  return $text; // string short enough even *with* HTML (short-circuit)
3685  }
3686 
3687  $dispLen = 0; // innerHTML legth so far
3688  $testingEllipsis = false; // checking if ellipses will make string longer/equal?
3689  $tagType = 0; // 0-open, 1-close
3690  $bracketState = 0; // 1-tag start, 2-tag name, 0-neither
3691  $entityState = 0; // 0-not entity, 1-entity
3692  $tag = $ret = ''; // accumulated tag name, accumulated result string
3693  $openTags = []; // open tag stack
3694  $maybeState = null; // possible truncation state
3695 
3696  $textLen = strlen( $text );
3697  $neLength = max( 0, $length - strlen( $ellipsis ) ); // non-ellipsis len if truncated
3698  for ( $pos = 0; true; ++$pos ) {
3699  # Consider truncation once the display length has reached the maximim.
3700  # We check if $dispLen > 0 to grab tags for the $neLength = 0 case.
3701  # Check that we're not in the middle of a bracket/entity...
3702  if ( $dispLen && $dispLen >= $neLength && $bracketState == 0 && !$entityState ) {
3703  if ( !$testingEllipsis ) {
3704  $testingEllipsis = true;
3705  # Save where we are; we will truncate here unless there turn out to
3706  # be so few remaining characters that truncation is not necessary.
3707  if ( !$maybeState ) { // already saved? ($neLength = 0 case)
3708  $maybeState = [ $ret, $openTags ]; // save state
3709  }
3710  } elseif ( $dispLen > $length && $dispLen > strlen( $ellipsis ) ) {
3711  # String in fact does need truncation, the truncation point was OK.
3712  // @phan-suppress-next-line PhanTypeInvalidExpressionArrayDestructuring
3713  list( $ret, $openTags ) = $maybeState; // reload state
3714  $ret = $this->removeBadCharLast( $ret ); // multi-byte char fix
3715  $ret .= $ellipsis; // add ellipsis
3716  break;
3717  }
3718  }
3719  if ( $pos >= $textLen ) {
3720  break; // extra iteration just for above checks
3721  }
3722 
3723  # Read the next char...
3724  $ch = $text[$pos];
3725  $lastCh = $pos ? $text[$pos - 1] : '';
3726  $ret .= $ch; // add to result string
3727  if ( $ch == '<' ) {
3728  $this->truncate_endBracket( $tag, $tagType, $lastCh, $openTags ); // for bad HTML
3729  $entityState = 0; // for bad HTML
3730  $bracketState = 1; // tag started (checking for backslash)
3731  } elseif ( $ch == '>' ) {
3732  $this->truncate_endBracket( $tag, $tagType, $lastCh, $openTags );
3733  $entityState = 0; // for bad HTML
3734  $bracketState = 0; // out of brackets
3735  } elseif ( $bracketState == 1 ) {
3736  if ( $ch == '/' ) {
3737  $tagType = 1; // close tag (e.g. "</span>")
3738  } else {
3739  $tagType = 0; // open tag (e.g. "<span>")
3740  $tag .= $ch;
3741  }
3742  $bracketState = 2; // building tag name
3743  } elseif ( $bracketState == 2 ) {
3744  if ( $ch != ' ' ) {
3745  $tag .= $ch;
3746  } else {
3747  // Name found (e.g. "<a href=..."), add on tag attributes...
3748  $pos += $this->truncate_skip( $ret, $text, "<>", $pos + 1 );
3749  }
3750  } elseif ( $bracketState == 0 ) {
3751  if ( $entityState ) {
3752  if ( $ch == ';' ) {
3753  $entityState = 0;
3754  $dispLen++; // entity is one displayed char
3755  }
3756  } else {
3757  if ( $neLength == 0 && !$maybeState ) {
3758  // Save state without $ch. We want to *hit* the first
3759  // display char (to get tags) but not *use* it if truncating.
3760  $maybeState = [ substr( $ret, 0, -1 ), $openTags ];
3761  }
3762  if ( $ch == '&' ) {
3763  $entityState = 1; // entity found, (e.g. "&#160;")
3764  } else {
3765  $dispLen++; // this char is displayed
3766  // Add the next $max display text chars after this in one swoop...
3767  $max = ( $testingEllipsis ? $length : $neLength ) - $dispLen;
3768  $skipped = $this->truncate_skip( $ret, $text, "<>&", $pos + 1, $max );
3769  $dispLen += $skipped;
3770  $pos += $skipped;
3771  }
3772  }
3773  }
3774  }
3775  // Close the last tag if left unclosed by bad HTML
3776  $this->truncate_endBracket( $tag, $text[$textLen - 1], $tagType, $openTags );
3777  while ( count( $openTags ) > 0 ) {
3778  $ret .= '</' . array_pop( $openTags ) . '>'; // close open tags
3779  }
3780  return $ret;
3781  }
3782 
3794  private function truncate_skip( &$ret, $text, $search, $start, $len = null ) {
3795  if ( $len === null ) {
3796  $len = -1; // -1 means "no limit" for strcspn
3797  } elseif ( $len < 0 ) {
3798  $len = 0; // sanity
3799  }
3800  $skipCount = 0;
3801  if ( $start < strlen( $text ) ) {
3802  $skipCount = strcspn( $text, $search, $start, $len );
3803  $ret .= substr( $text, $start, $skipCount );
3804  }
3805  return $skipCount;
3806  }
3807 
3817  private function truncate_endBracket( &$tag, $tagType, $lastCh, &$openTags ) {
3818  $tag = ltrim( $tag );
3819  if ( $tag != '' ) {
3820  if ( $tagType == 0 && $lastCh != '/' ) {
3821  $openTags[] = $tag; // tag opened (didn't close itself)
3822  } elseif ( $tagType == 1 ) {
3823  if ( $openTags && $tag == $openTags[count( $openTags ) - 1] ) {
3824  array_pop( $openTags ); // tag closed
3825  }
3826  }
3827  $tag = '';
3828  }
3829  }
3830 
3839  function convertGrammar( $word, $case ) {
3840  global $wgGrammarForms;
3841  if ( isset( $wgGrammarForms[$this->getCode()][$case][$word] ) ) {
3842  return $wgGrammarForms[$this->getCode()][$case][$word];
3843  }
3844 
3846 
3847  if ( isset( $grammarTransformations[$case] ) ) {
3848  $forms = $grammarTransformations[$case];
3849 
3850  // Some names of grammar rules are aliases for other rules.
3851  // In such cases the value is a string rather than object,
3852  // so load the actual rules.
3853  if ( is_string( $forms ) ) {
3854  $forms = $grammarTransformations[$forms];
3855  }
3856 
3857  foreach ( array_values( $forms ) as $rule ) {
3858  $form = $rule[0];
3859 
3860  if ( $form === '@metadata' ) {
3861  continue;
3862  }
3863 
3864  $replacement = $rule[1];
3865 
3866  $regex = '/' . addcslashes( $form, '/' ) . '/u';
3867  $patternMatches = preg_match( $regex, $word );
3868 
3869  if ( $patternMatches === false ) {
3870  wfLogWarning(
3871  'An error occurred while processing grammar. ' .
3872  "Word: '$word'. Regex: /$form/."
3873  );
3874  } elseif ( $patternMatches === 1 ) {
3875  $word = preg_replace( $regex, $replacement, $word );
3876 
3877  break;
3878  }
3879  }
3880  }
3881 
3882  return $word;
3883  }
3884 
3890  function getGrammarForms() {
3891  global $wgGrammarForms;
3892  if ( isset( $wgGrammarForms[$this->getCode()] )
3893  && is_array( $wgGrammarForms[$this->getCode()] )
3894  ) {
3895  return $wgGrammarForms[$this->getCode()];
3896  }
3897 
3898  return [];
3899  }
3900 
3910  public function getGrammarTransformations() {
3911  $languageCode = $this->getCode();
3912 
3913  if ( self::$grammarTransformations === null ) {
3914  self::$grammarTransformations = new MapCacheLRU( 10 );
3915  }
3916 
3917  if ( self::$grammarTransformations->has( $languageCode ) ) {
3918  return self::$grammarTransformations->get( $languageCode );
3919  }
3920 
3921  $data = [];
3922 
3923  $grammarDataFile = __DIR__ . "/data/grammarTransformations/$languageCode.json";
3924  if ( is_readable( $grammarDataFile ) ) {
3926  file_get_contents( $grammarDataFile ),
3927  true
3928  );
3929 
3930  if ( $data === null ) {
3931  throw new MWException( "Invalid grammar data for \"$languageCode\"." );
3932  }
3933 
3934  self::$grammarTransformations->set( $languageCode, $data );
3935  }
3936 
3937  return $data;
3938  }
3939 
3959  function gender( $gender, $forms ) {
3960  if ( !count( $forms ) ) {
3961  return '';
3962  }
3963  $forms = $this->preConvertPlural( $forms, 2 );
3964  if ( $gender === 'male' ) {
3965  return $forms[0];
3966  }
3967  if ( $gender === 'female' ) {
3968  return $forms[1];
3969  }
3970  return $forms[2] ?? $forms[0];
3971  }
3972 
3988  function convertPlural( $count, $forms ) {
3989  // Handle explicit n=pluralform cases
3990  $forms = $this->handleExplicitPluralForms( $count, $forms );
3991  if ( is_string( $forms ) ) {
3992  return $forms;
3993  }
3994  if ( !count( $forms ) ) {
3995  return '';
3996  }
3997 
3998  $pluralForm = $this->getPluralRuleIndexNumber( $count );
3999  $pluralForm = min( $pluralForm, count( $forms ) - 1 );
4000  return $forms[$pluralForm];
4001  }
4002 
4018  protected function handleExplicitPluralForms( $count, array $forms ) {
4019  foreach ( $forms as $index => $form ) {
4020  if ( preg_match( '/\d+=/i', $form ) ) {
4021  $pos = strpos( $form, '=' );
4022  if ( substr( $form, 0, $pos ) === (string)$count ) {
4023  return substr( $form, $pos + 1 );
4024  }
4025  unset( $forms[$index] );
4026  }
4027  }
4028  return array_values( $forms );
4029  }
4030 
4039  protected function preConvertPlural( /* Array */ $forms, $count ) {
4040  return array_pad( $forms, $count, end( $forms ) );
4041  }
4042 
4059  public function embedBidi( $text = '' ) {
4060  $dir = self::strongDirFromContent( $text );
4061  if ( $dir === 'ltr' ) {
4062  // Wrap in LEFT-TO-RIGHT EMBEDDING ... POP DIRECTIONAL FORMATTING
4063  return self::$lre . $text . self::$pdf;
4064  }
4065  if ( $dir === 'rtl' ) {
4066  // Wrap in RIGHT-TO-LEFT EMBEDDING ... POP DIRECTIONAL FORMATTING
4067  return self::$rle . $text . self::$pdf;
4068  }
4069  // No strong directionality: do not wrap
4070  return $text;
4071  }
4072 
4086  function translateBlockExpiry( $str, User $user = null, $now = 0 ) {
4087  $duration = SpecialBlock::getSuggestedDurations( $this );
4088  foreach ( $duration as $show => $value ) {
4089  if ( strcmp( $str, $value ) == 0 ) {
4090  return htmlspecialchars( trim( $show ) );
4091  }
4092  }
4093 
4094  if ( wfIsInfinity( $str ) ) {
4095  foreach ( $duration as $show => $value ) {
4096  if ( wfIsInfinity( $value ) ) {
4097  return htmlspecialchars( trim( $show ) );
4098  }
4099  }
4100  }
4101 
4102  // If all else fails, return a standard duration or timestamp description.
4103  $time = strtotime( $str, $now );
4104  if ( $time === false ) { // Unknown format. Return it as-is in case.
4105  return $str;
4106  } elseif ( $time !== strtotime( $str, $now + 1 ) ) { // It's a relative timestamp.
4107  // The result differs based on current time, so the difference
4108  // is a fixed duration length.
4109  return $this->formatDuration( $time - $now );
4110  } else { // It's an absolute timestamp.
4111  if ( $time === 0 ) {
4112  // wfTimestamp() handles 0 as current time instead of epoch.
4113  $time = '19700101000000';
4114  }
4115  if ( $user ) {
4116  return $this->userTimeAndDate( $time, $user );
4117  }
4118  return $this->timeanddate( $time );
4119  }
4120  }
4121 
4129  public function segmentForDiff( $text ) {
4130  return $text;
4131  }
4132 
4139  public function unsegmentForDiff( $text ) {
4140  return $text;
4141  }
4142 
4149  public function getConverter() {
4150  return $this->mConverter;
4151  }
4152 
4161  public function autoConvert( $text, $variant = false ) {
4162  return $this->mConverter->autoConvert( $text, $variant );
4163  }
4164 
4171  public function autoConvertToAllVariants( $text ) {
4172  return $this->mConverter->autoConvertToAllVariants( $text );
4173  }
4174 
4186  public function convert( $text ) {
4187  return $this->mConverter->convert( $text );
4188  }
4189 
4196  public function convertTitle( $title ) {
4197  return $this->mConverter->convertTitle( $title );
4198  }
4199 
4208  public function convertNamespace( $ns, $variant = null ) {
4209  return $this->mConverter->convertNamespace( $ns, $variant );
4210  }
4211 
4217  public function hasVariants() {
4218  return count( $this->getVariants() ) > 1;
4219  }
4220 
4231  public function hasVariant( $variant ) {
4232  return $variant && ( $variant === $this->mConverter->validateVariant( $variant ) );
4233  }
4234 
4241  public function convertHtml( $text ) {
4242  return htmlspecialchars( $this->convert( $text ) );
4243  }
4244 
4249  public function convertCategoryKey( $key ) {
4250  return $this->mConverter->convertCategoryKey( $key );
4251  }
4252 
4259  public function getVariants() {
4260  return $this->mConverter->getVariants();
4261  }
4262 
4266  public function getPreferredVariant() {
4267  return $this->mConverter->getPreferredVariant();
4268  }
4269 
4273  public function getDefaultVariant() {
4274  return $this->mConverter->getDefaultVariant();
4275  }
4276 
4280  public function getURLVariant() {
4281  return $this->mConverter->getURLVariant();
4282  }
4283 
4296  public function findVariantLink( &$link, &$nt, $ignoreOtherCond = false ) {
4297  $this->mConverter->findVariantLink( $link, $nt, $ignoreOtherCond );
4298  }
4299 
4306  function getExtraHashOptions() {
4307  return $this->mConverter->getExtraHashOptions();
4308  }
4309 
4317  public function getParsedTitle() {
4318  return $this->mConverter->getParsedTitle();
4319  }
4320 
4327  public function updateConversionTable( Title $title ) {
4328  $this->mConverter->updateConversionTable( $title );
4329  }
4330 
4337  public function linkTrail() {
4338  return self::$dataCache->getItem( $this->mCode, 'linkTrail' );
4339  }
4340 
4347  public function linkPrefixCharset() {
4348  return self::$dataCache->getItem( $this->mCode, 'linkPrefixCharset' );
4349  }
4350 
4358  public function getParentLanguage() {
4359  if ( $this->mParentLanguage !== false ) {
4360  return $this->mParentLanguage;
4361  }
4362 
4363  $code = explode( '-', $this->getCode() )[0];
4364  if ( !in_array( $code, LanguageConverter::$languagesWithVariants ) ) {
4365  $this->mParentLanguage = null;
4366  return null;
4367  }
4368  $lang = self::factory( $code );
4369  if ( !$lang->hasVariant( $this->getCode() ) ) {
4370  $this->mParentLanguage = null;
4371  return null;
4372  }
4373 
4374  $this->mParentLanguage = $lang;
4375  return $lang;
4376  }
4377 
4385  public function equals( Language $lang ) {
4386  return $lang === $this || $lang->getCode() === $this->mCode;
4387  }
4388 
4397  public function getCode() {
4398  return $this->mCode;
4399  }
4400 
4411  public function getHtmlCode() {
4412  if ( is_null( $this->mHtmlCode ) ) {
4413  $this->mHtmlCode = LanguageCode::bcp47( $this->getCode() );
4414  }
4415  return $this->mHtmlCode;
4416  }
4417 
4422  public function setCode( $code ) {
4423  wfDeprecated( __METHOD__, '1.32' );
4424  $this->mCode = $code;
4425  // Ensure we don't leave incorrect cached data lying around
4426  $this->mHtmlCode = null;
4427  $this->mParentLanguage = false;
4428  }
4429 
4437  public static function getCodeFromFileName( $filename, $prefix = 'Language', $suffix = '.php' ) {
4438  $m = null;
4439  preg_match( '/' . preg_quote( $prefix, '/' ) . '([A-Z][a-z_]+)' .
4440  preg_quote( $suffix, '/' ) . '/', $filename, $m );
4441  if ( !count( $m ) ) {
4442  return false;
4443  }
4444  return str_replace( '_', '-', strtolower( $m[1] ) );
4445  }
4446 
4452  public static function classFromCode( $code, $fallback = true ) {
4453  if ( $fallback && $code == 'en' ) {
4454  return 'Language';
4455  } else {
4456  return 'Language' . str_replace( '-', '_', ucfirst( $code ) );
4457  }
4458  }
4459 
4468  public static function getFileName( $prefix, $code, $suffix = '.php' ) {
4469  if ( !self::isValidBuiltInCode( $code ) ) {
4470  throw new MWException( "Invalid language code \"$code\"" );
4471  }
4472 
4473  return $prefix . str_replace( '-', '_', ucfirst( $code ) ) . $suffix;
4474  }
4475 
4480  public static function getMessagesFileName( $code ) {
4481  global $IP;
4482  $file = self::getFileName( "$IP/languages/messages/Messages", $code, '.php' );
4483  Hooks::run( 'Language::getMessagesFileName', [ $code, &$file ] );
4484  return $file;
4485  }
4486 
4493  public static function getJsonMessagesFileName( $code ) {
4494  global $IP;
4495 
4496  if ( !self::isValidBuiltInCode( $code ) ) {
4497  throw new MWException( "Invalid language code \"$code\"" );
4498  }
4499 
4500  return "$IP/languages/i18n/$code.json";
4501  }
4502 
4510  public static function getFallbackFor( $code ) {
4511  $fallbacks = self::getFallbacksFor( $code );
4512  if ( $fallbacks ) {
4513  return $fallbacks[0];
4514  }
4515  return false;
4516  }
4517 
4528  public static function getFallbacksFor( $code, $mode = self::MESSAGES_FALLBACKS ) {
4529  if ( $code === 'en' || !self::isValidBuiltInCode( $code ) ) {
4530  return [];
4531  }
4532  switch ( $mode ) {
4534  // For unknown languages, fallbackSequence returns an empty array,
4535  // hardcode fallback to 'en' in that case as English messages are
4536  // always defined.
4537  return self::getLocalisationCache()->getItem( $code, 'fallbackSequence' ) ?: [ 'en' ];
4539  // Use this mode when you don't want to fallback to English unless
4540  // explicitly defined, for example when you have language-variant icons
4541  // and an international language-independent fallback.
4542  return self::getLocalisationCache()->getItem( $code, 'originalFallbackSequence' );
4543  default:
4544  throw new MWException( "Invalid fallback mode \"$mode\"" );
4545  }
4546  }
4547 
4556  public static function getFallbacksIncludingSiteLanguage( $code ) {
4557  global $wgLanguageCode;
4558 
4559  // Usually, we will only store a tiny number of fallback chains, so we
4560  // keep them in static memory.
4561  $cacheKey = "{$code}-{$wgLanguageCode}";
4562 
4563  if ( !array_key_exists( $cacheKey, self::$fallbackLanguageCache ) ) {
4564  $fallbacks = self::getFallbacksFor( $code );
4565 
4566  // Append the site's fallback chain, including the site language itself
4567  $siteFallbacks = self::getFallbacksFor( $wgLanguageCode );
4568  array_unshift( $siteFallbacks, $wgLanguageCode );
4569 
4570  // Eliminate any languages already included in the chain
4571  $siteFallbacks = array_diff( $siteFallbacks, $fallbacks );
4572 
4573  self::$fallbackLanguageCache[$cacheKey] = [ $fallbacks, $siteFallbacks ];
4574  }
4575  return self::$fallbackLanguageCache[$cacheKey];
4576  }
4577 
4587  public static function getMessagesFor( $code ) {
4588  return self::getLocalisationCache()->getItem( $code, 'messages' );
4589  }
4590 
4599  public static function getMessageFor( $key, $code ) {
4600  return self::getLocalisationCache()->getSubitem( $code, 'messages', $key );
4601  }
4602 
4611  public static function getMessageKeysFor( $code ) {
4612  return self::getLocalisationCache()->getSubitemList( $code, 'messages' );
4613  }
4614 
4619  function fixVariableInNamespace( $talk ) {
4620  if ( strpos( $talk, '$1' ) === false ) {
4621  return $talk;
4622  }
4623 
4624  global $wgMetaNamespace;
4625  $talk = str_replace( '$1', $wgMetaNamespace, $talk );
4626 
4627  # Allow grammar transformations
4628  # Allowing full message-style parsing would make simple requests
4629  # such as action=raw much more expensive than they need to be.
4630  # This will hopefully cover most cases.
4631  $talk = preg_replace_callback( '/{{grammar:(.*?)\|(.*?)}}/i',
4632  [ $this, 'replaceGrammarInNamespace' ], $talk );
4633  return str_replace( ' ', '_', $talk );
4634  }
4635 
4640  function replaceGrammarInNamespace( $m ) {
4641  return $this->convertGrammar( trim( $m[2] ), trim( $m[1] ) );
4642  }
4643 
4654  public function formatExpiry( $expiry, $format = true, $infinity = 'infinity' ) {
4655  static $dbInfinity;
4656  if ( $dbInfinity === null ) {
4657  $dbInfinity = wfGetDB( DB_REPLICA )->getInfinity();
4658  }
4659 
4660  if ( $expiry == '' || $expiry === 'infinity' || $expiry == $dbInfinity ) {
4661  return $format === true
4662  ? $this->getMessageFromDB( 'infiniteblock' )
4663  : $infinity;
4664  } else {
4665  return $format === true
4666  ? $this->timeanddate( $expiry, /* User preference timezone */ true )
4667  : wfTimestamp( $format, $expiry );
4668  }
4669  }
4670 
4684  function formatTimePeriod( $seconds, $format = [] ) {
4685  if ( !is_array( $format ) ) {
4686  $format = [ 'avoid' => $format ]; // For backwards compatibility
4687  }
4688  if ( !isset( $format['avoid'] ) ) {
4689  $format['avoid'] = false;
4690  }
4691  if ( !isset( $format['noabbrevs'] ) ) {
4692  $format['noabbrevs'] = false;
4693  }
4694  $secondsMsg = wfMessage(
4695  $format['noabbrevs'] ? 'seconds' : 'seconds-abbrev' )->inLanguage( $this );
4696  $minutesMsg = wfMessage(
4697  $format['noabbrevs'] ? 'minutes' : 'minutes-abbrev' )->inLanguage( $this );
4698  $hoursMsg = wfMessage(
4699  $format['noabbrevs'] ? 'hours' : 'hours-abbrev' )->inLanguage( $this );
4700  $daysMsg = wfMessage(
4701  $format['noabbrevs'] ? 'days' : 'days-abbrev' )->inLanguage( $this );
4702 
4703  if ( round( $seconds * 10 ) < 100 ) {
4704  $s = $this->formatNum( sprintf( "%.1f", round( $seconds * 10 ) / 10 ) );
4705  $s = $secondsMsg->params( $s )->text();
4706  } elseif ( round( $seconds ) < 60 ) {
4707  $s = $this->formatNum( round( $seconds ) );
4708  $s = $secondsMsg->params( $s )->text();
4709  } elseif ( round( $seconds ) < 3600 ) {
4710  $minutes = floor( $seconds / 60 );
4711  $secondsPart = round( fmod( $seconds, 60 ) );
4712  if ( $secondsPart == 60 ) {
4713  $secondsPart = 0;
4714  $minutes++;
4715  }
4716  $s = $minutesMsg->params( $this->formatNum( $minutes ) )->text();
4717  $s .= ' ';
4718  $s .= $secondsMsg->params( $this->formatNum( $secondsPart ) )->text();
4719  } elseif ( round( $seconds ) <= 2 * 86400 ) {
4720  $hours = floor( $seconds / 3600 );
4721  $minutes = floor( ( $seconds - $hours * 3600 ) / 60 );
4722  $secondsPart = round( $seconds - $hours * 3600 - $minutes * 60 );
4723  if ( $secondsPart == 60 ) {
4724  $secondsPart = 0;
4725  $minutes++;
4726  }
4727  if ( $minutes == 60 ) {
4728  $minutes = 0;
4729  $hours++;
4730  }
4731  $s = $hoursMsg->params( $this->formatNum( $hours ) )->text();
4732  $s .= ' ';
4733  $s .= $minutesMsg->params( $this->formatNum( $minutes ) )->text();
4734  if ( !in_array( $format['avoid'], [ 'avoidseconds', 'avoidminutes' ] ) ) {
4735  $s .= ' ' . $secondsMsg->params( $this->formatNum( $secondsPart ) )->text();
4736  }
4737  } else {
4738  $days = floor( $seconds / 86400 );
4739  if ( $format['avoid'] === 'avoidminutes' ) {
4740  $hours = round( ( $seconds - $days * 86400 ) / 3600 );
4741  if ( $hours == 24 ) {
4742  $hours = 0;
4743  $days++;
4744  }
4745  $s = $daysMsg->params( $this->formatNum( $days ) )->text();
4746  $s .= ' ';
4747  $s .= $hoursMsg->params( $this->formatNum( $hours ) )->text();
4748  } elseif ( $format['avoid'] === 'avoidseconds' ) {
4749  $hours = floor( ( $seconds - $days * 86400 ) / 3600 );
4750  $minutes = round( ( $seconds - $days * 86400 - $hours * 3600 ) / 60 );
4751  if ( $minutes == 60 ) {
4752  $minutes = 0;
4753  $hours++;
4754  }
4755  if ( $hours == 24 ) {
4756  $hours = 0;
4757  $days++;
4758  }
4759  $s = $daysMsg->params( $this->formatNum( $days ) )->text();
4760  $s .= ' ';
4761  $s .= $hoursMsg->params( $this->formatNum( $hours ) )->text();
4762  $s .= ' ';
4763  $s .= $minutesMsg->params( $this->formatNum( $minutes ) )->text();
4764  } else {
4765  $s = $daysMsg->params( $this->formatNum( $days ) )->text();
4766  $s .= ' ';
4767  $s .= $this->formatTimePeriod( $seconds - $days * 86400, $format );
4768  }
4769  }
4770  return $s;
4771  }
4772 
4784  function formatBitrate( $bps ) {
4785  return $this->formatComputingNumbers( $bps, 1000, "bitrate-$1bits" );
4786  }
4787 
4794  function formatComputingNumbers( $size, $boundary, $messageKey ) {
4795  if ( $size <= 0 ) {
4796  return str_replace( '$1', $this->formatNum( $size ),
4797  $this->getMessageFromDB( str_replace( '$1', '', $messageKey ) )
4798  );
4799  }
4800  $sizes = [ '', 'kilo', 'mega', 'giga', 'tera', 'peta', 'exa', 'zeta', 'yotta' ];
4801  $index = 0;
4802 
4803  $maxIndex = count( $sizes ) - 1;
4804  while ( $size >= $boundary && $index < $maxIndex ) {
4805  $index++;
4806  $size /= $boundary;
4807  }
4808 
4809  // For small sizes no decimal places necessary
4810  $round = 0;
4811  if ( $index > 1 ) {
4812  // For MB and bigger two decimal places are smarter
4813  $round = 2;
4814  }
4815  $msg = str_replace( '$1', $sizes[$index], $messageKey );
4816 
4817  $size = round( $size, $round );
4818  $text = $this->getMessageFromDB( $msg );
4819  return str_replace( '$1', $this->formatNum( $size ), $text );
4820  }
4821 
4832  function formatSize( $size ) {
4833  return $this->formatComputingNumbers( $size, 1024, "size-$1bytes" );
4834  }
4835 
4845  function specialList( $page, $details, $oppositedm = true ) {
4846  if ( !$details ) {
4847  return $page;
4848  }
4849 
4850  $dirmark = ( $oppositedm ? $this->getDirMark( true ) : '' ) . $this->getDirMark();
4851  return $page .
4852  $dirmark .
4853  $this->msg( 'word-separator' )->escaped() .
4854  $this->msg( 'parentheses' )->rawParams( $details )->escaped();
4855  }
4856 
4869  public function viewPrevNext( Title $title, $offset, $limit,
4870  array $query = [], $atend = false
4871  ) {
4872  // @todo FIXME: Why on earth this needs one message for the text and another one for tooltip?
4873 
4874  # Make 'previous' link
4875  $prev = wfMessage( 'prevn' )->inLanguage( $this )->title( $title )->numParams( $limit )->text();
4876  if ( $offset > 0 ) {
4877  $plink = $this->numLink( $title, max( $offset - $limit, 0 ), $limit,
4878  $query, $prev, 'prevn-title', 'mw-prevlink' );
4879  } else {
4880  $plink = htmlspecialchars( $prev );
4881  }
4882 
4883  # Make 'next' link
4884  $next = wfMessage( 'nextn' )->inLanguage( $this )->title( $title )->numParams( $limit )->text();
4885  if ( $atend ) {
4886  $nlink = htmlspecialchars( $next );
4887  } else {
4888  $nlink = $this->numLink( $title, $offset + $limit, $limit,
4889  $query, $next, 'nextn-title', 'mw-nextlink' );
4890  }
4891 
4892  # Make links to set number of items per page
4893  $numLinks = [];
4894  foreach ( [ 20, 50, 100, 250, 500 ] as $num ) {
4895  $numLinks[] = $this->numLink( $title, $offset, $num,
4896  $query, $this->formatNum( $num ), 'shown-title', 'mw-numlink' );
4897  }
4898 
4899  return wfMessage( 'viewprevnext' )->inLanguage( $this )->title( $title
4900  )->rawParams( $plink, $nlink, $this->pipeList( $numLinks ) )->escaped();
4901  }
4902 
4915  private function numLink( Title $title, $offset, $limit, array $query, $link,
4916  $tooltipMsg, $class
4917  ) {
4918  $query = [ 'limit' => $limit, 'offset' => $offset ] + $query;
4919  $tooltip = wfMessage( $tooltipMsg )->inLanguage( $this )->title( $title )
4920  ->numParams( $limit )->text();
4921 
4922  return Html::element( 'a', [ 'href' => $title->getLocalURL( $query ),
4923  'title' => $tooltip, 'class' => $class ], $link );
4924  }
4925 
4931  public function getConvRuleTitle() {
4932  return $this->mConverter->getConvRuleTitle();
4933  }
4934 
4940  public function getCompiledPluralRules() {
4941  $pluralRules = self::$dataCache->getItem( strtolower( $this->mCode ), 'compiledPluralRules' );
4942  $fallbacks = self::getFallbacksFor( $this->mCode );
4943  if ( !$pluralRules ) {
4944  foreach ( $fallbacks as $fallbackCode ) {
4945  $pluralRules = self::$dataCache->getItem( strtolower( $fallbackCode ), 'compiledPluralRules' );
4946  if ( $pluralRules ) {
4947  break;
4948  }
4949  }
4950  }
4951  return $pluralRules;
4952  }
4953 
4959  public function getPluralRules() {
4960  $pluralRules = self::$dataCache->getItem( strtolower( $this->mCode ), 'pluralRules' );
4961  $fallbacks = self::getFallbacksFor( $this->mCode );
4962  if ( !$pluralRules ) {
4963  foreach ( $fallbacks as $fallbackCode ) {
4964  $pluralRules = self::$dataCache->getItem( strtolower( $fallbackCode ), 'pluralRules' );
4965  if ( $pluralRules ) {
4966  break;
4967  }
4968  }
4969  }
4970  return $pluralRules;
4971  }
4972 
4978  public function getPluralRuleTypes() {
4979  $pluralRuleTypes = self::$dataCache->getItem( strtolower( $this->mCode ), 'pluralRuleTypes' );
4980  $fallbacks = self::getFallbacksFor( $this->mCode );
4981  if ( !$pluralRuleTypes ) {
4982  foreach ( $fallbacks as $fallbackCode ) {
4983  $pluralRuleTypes = self::$dataCache->getItem( strtolower( $fallbackCode ), 'pluralRuleTypes' );
4984  if ( $pluralRuleTypes ) {
4985  break;
4986  }
4987  }
4988  }
4989  return $pluralRuleTypes;
4990  }
4991 
4997  public function getPluralRuleIndexNumber( $number ) {
4998  $pluralRules = $this->getCompiledPluralRules();
4999  $form = Evaluator::evaluateCompiled( $number, $pluralRules );
5000  return $form;
5001  }
5002 
5011  public function getPluralRuleType( $number ) {
5012  $index = $this->getPluralRuleIndexNumber( $number );
5013  $pluralRuleTypes = $this->getPluralRuleTypes();
5014  return $pluralRuleTypes[$index] ?? 'other';
5015  }
5016 }
User\getDefaultOption
static getDefaultOption( $opt)
Get a given default option value.
Definition: User.php:1818
Language\$mCode
$mCode
Definition: Language.php:61
Language\internalUserTimeAndDate
internalUserTimeAndDate( $type, $ts, User $user, array $options)
Internal helper function for userDate(), userTime() and userTimeAndDate()
Definition: Language.php:2434
Language\getCodeFromFileName
static getCodeFromFileName( $filename, $prefix='Language', $suffix='.php')
Get the language code from a file name.
Definition: Language.php:4437
Language\getExtraHashOptions
getExtraHashOptions()
returns language specific options used by User::getPageRenderHash() for example, the preferred langua...
Definition: Language.php:4306
Language\fetchLanguageName
static fetchLanguageName( $code, $inLanguage=self::AS_AUTONYMS, $include=self::ALL)
Definition: Language.php:933
Language\lc
lc( $str, $first=false)
Definition: Language.php:2760
MWTimestamp
Library for creating and parsing MW-style timestamps.
Definition: MWTimestamp.php:32
Language\parseFormattedNumber
parseFormattedNumber( $number)
Definition: Language.php:3326
Language\getURLVariant
getURLVariant()
Definition: Language.php:4280
Language\getVariantname
getVariantname( $code, $usemsg=true)
short names for language variants used for language conversion links.
Definition: Language.php:762
$user
return true to allow those checks to and false if checking is done & $user
Definition: hooks.txt:1476
Language\replaceGrammarInNamespace
replaceGrammarInNamespace( $m)
Definition: Language.php:4640
Language\linkPrefixExtension
linkPrefixExtension()
To allow "foo[[bar]]" to extend the link over the whole word "foobar".
Definition: Language.php:3198
$file
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Definition: router.php:42
StringUtils\isUtf8
static isUtf8( $value)
Test whether a string is valid UTF-8.
Definition: StringUtils.php:41
Language\sprintfDate
sprintfDate( $format, $ts, DateTimeZone $zone=null, &$ttl='unused')
This is a workalike of PHP's date() function, but with better internationalisation,...
Definition: Language.php:1143
Language\clearCaches
static clearCaches()
Intended for tests that may change configuration in a way that invalidates caches.
Definition: Language.php:284
Language\truncate_endBracket
truncate_endBracket(&$tag, $tagType, $lastCh, &$openTags)
truncateHtml() helper function (a) push or pop $tag from $openTags as needed (b) clear $tag value
Definition: Language.php:3817
Language\commaList
commaList(array $list)
Take a list of strings and build a locale-friendly comma-separated list, using the local comma-separa...
Definition: Language.php:3473
Language\semicolonList
semicolonList(array $list)
Take a list of strings and build a locale-friendly semicolon-separated list, using the local semicolo...
Definition: Language.php:3486
Language\$IRANIAN_DAYS
static $IRANIAN_DAYS
Definition: Language.php:1613
Language\$mIranianCalendarMonthMsgs
static $mIranianCalendarMonthMsgs
Definition: Language.php:120
Language\getIranianCalendarMonthName
getIranianCalendarMonthName( $key)
Definition: Language.php:1029
Language\$mMonthAbbrevMsgs
static $mMonthAbbrevMsgs
Definition: Language.php:115
Language\formatSize
formatSize( $size)
Format a size in bytes for output, using an appropriate unit (B, KB, MB, GB, TB, PB,...
Definition: Language.php:4832
Language\separatorTransformTable
separatorTransformTable()
Definition: Language.php:3430
HashBagOStuff
Simple store for keeping values in an associative array for the current process.
Definition: HashBagOStuff.php:31
Language\resetNamespaces
resetNamespaces()
Resets all of the namespace caches.
Definition: Language.php:553
Language\getConverter
getConverter()
Return the LanguageConverter used in the Language.
Definition: Language.php:4149
Language\hasVariants
hasVariants()
Check if this is a language with variants.
Definition: Language.php:4217
Language\$mWeekdayAbbrevMsgs
static $mWeekdayAbbrevMsgs
Definition: Language.php:101
Language\preConvertPlural
preConvertPlural($forms, $count)
Checks that convertPlural was given an array and pads it to requested amount of forms by copying the ...
Definition: Language.php:4039
$lang
if(!isset( $args[0])) $lang
Definition: testCompression.php:33
Language\convertHtml
convertHtml( $text)
Perform output conversion on a string, and encode for safe HTML output.
Definition: Language.php:4241
Language\minimumGroupingDigits
minimumGroupingDigits()
Definition: Language.php:3437
Language\segmentByWord
segmentByWord( $string)
Some languages such as Chinese require word segmentation, Specify such segmentation when overridden i...
Definition: Language.php:2894
Language\iconv
iconv( $in, $out, $string)
Definition: Language.php:2661
Language\getMessageFromDB
getMessageFromDB( $msg)
Get a message from the MediaWiki namespace.
Definition: Language.php:949
captcha-old.count
count
Definition: captcha-old.py:249
Language\$GREG_DAYS
static $GREG_DAYS
Definition: Language.php:1612
Language\convert
convert( $text)
convert text to different variants of a language.
Definition: Language.php:4186
Language\ALL
const ALL
Return all known languages in fetchLanguageName(s).
Definition: Language.php:47
Language\updateConversionTable
updateConversionTable(Title $title)
Refresh the cache of conversion tables when MediaWiki:Conversiontable* is updated.
Definition: Language.php:4327
Language\$languageNameCache
static HashBagOStuff null $languageNameCache
Cache for language names.
Definition: Language.php:184
Language\convertGrammar
convertGrammar( $word, $case)
Grammatical transformations, needed for inflected languages Invoked by putting {{grammar:case|word}} ...
Definition: Language.php:3839
$fallback
$fallback
Definition: MessagesAb.php:11
wfTimestamp
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
Definition: GlobalFunctions.php:1912
$namespaces
namespace and then decline to actually register it & $namespaces
Definition: hooks.txt:925
Language\truncateForVisual
truncateForVisual( $string, $length, $ellipsis='...', $adjustLength=true)
Truncate a string to a specified number of characters, appending an optional string (e....
Definition: Language.php:3544
$out
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that 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
Language\$mMonthGenMsgs
static $mMonthGenMsgs
Definition: Language.php:110
Language\timeanddate
timeanddate( $ts, $adj=false, $format=true, $timecorrection=false)
Definition: Language.php:2338
MediaWikiTitleCodec\getTitleInvalidRegex
static getTitleInvalidRegex()
Returns a simple regex that will match on characters and sequences invalid in titles.
Definition: MediaWikiTitleCodec.php:458
Language\getHebrewCalendarMonthNameGen
getHebrewCalendarMonthNameGen( $key)
Definition: Language.php:1045
it
=Architecture==Two class hierarchies are used to provide the functionality associated with the different content models:*Content interface(and AbstractContent base class) define functionality that acts on the concrete content of a page, and *ContentHandler base class provides functionality specific to a content model, but not acting on concrete content. The most important function of ContentHandler is to act as a factory for the appropriate implementation of Content. These Content objects are to be used by MediaWiki everywhere, instead of passing page content around as text. All manipulation and analysis of page content must be done via the appropriate methods of the Content object. For each content model, a subclass of ContentHandler has to be registered with $wgContentHandlers. The ContentHandler object for a given content model can be obtained using ContentHandler::getForModelID($id). Also Title, WikiPage and Revision now have getContentHandler() methods for convenience. ContentHandler objects are singletons that provide functionality specific to the content type, but not directly acting on the content of some page. ContentHandler::makeEmptyContent() and ContentHandler::unserializeContent() can be used to create a Content object of the appropriate type. However, it is recommended to instead use WikiPage::getContent() resp. Revision::getContent() to get a page 's content as a Content object. These two methods should be the ONLY way in which page content is accessed. Another important function of ContentHandler objects is to define custom action handlers for a content model, see ContentHandler::getActionOverrides(). This is similar to what WikiPage::getActionOverrides() was already doing.==Serialization==With the ContentHandler facility, page content no longer has to be text based. Objects implementing the Content interface are used to represent and handle the content internally. For storage and data exchange, each content model supports at least one serialization format via ContentHandler::serializeContent($content). The list of supported formats for a given content model can be accessed using ContentHandler::getSupportedFormats(). Content serialization formats are identified using MIME type like strings. The following formats are built in:*text/x-wiki - wikitext *text/javascript - for js pages *text/css - for css pages *text/plain - for future use, e.g. with plain text messages. *text/html - for future use, e.g. with plain html messages. *application/vnd.php.serialized - for future use with the api and for extensions *application/json - for future use with the api, and for use by extensions *application/xml - for future use with the api, and for use by extensions In PHP, use the corresponding CONTENT_FORMAT_XXX constant. Note that when using the API to access page content, especially action=edit, action=parse and action=query &prop=revisions, the model and format of the content should always be handled explicitly. Without that information, interpretation of the provided content is not reliable. The same applies to XML dumps generated via maintenance/dumpBackup.php or Special:Export. Also note that the API will provide encapsulated, serialized content - so if the API was called with format=json, and contentformat is also json(or rather, application/json), the page content is represented as a string containing an escaped json structure. Extensions that use JSON to serialize some types of page content may provide specialized API modules that allow access to that content in a more natural form.==Compatibility==The ContentHandler facility is introduced in a way that should allow all existing code to keep functioning at least for pages that contain wikitext or other text based content. However, a number of functions and hooks have been deprecated in favor of new versions that are aware of the page 's content model, and will now generate warnings when used. Most importantly, the following functions have been deprecated:*Revisions::getText() is deprecated in favor Revisions::getContent() *WikiPage::getText() is deprecated in favor WikiPage::getContent() Also, the old Article::getContent()(which returns text) is superceded by Article::getContentObject(). However, both methods should be avoided since they do not provide clean access to the page 's actual content. For instance, they may return a system message for non-existing pages. Use WikiPage::getContent() instead. Code that relies on a textual representation of the page content should eventually be rewritten. However, ContentHandler::getContentText() provides a stop-gap that can be used to get text for a page. Its behavior is controlled by $wgContentHandlerTextFallback it
Definition: contenthandler.txt:104
Language\$mConverter
LanguageConverter $mConverter
Definition: Language.php:59
$wgLangObjCacheSize
$wgLangObjCacheSize
Language cache size, or really how many languages can we handle simultaneously without degrading to c...
Definition: DefaultSettings.php:2918
Language\getMonthNamesArray
getMonthNamesArray()
Definition: Language.php:974
Language\MESSAGES_FALLBACKS
const MESSAGES_FALLBACKS
Return a fallback chain for messages in getFallbacksFor.
Definition: Language.php:88
$s
$s
Definition: mergeMessageFileList.php:186
Language\$mHijriCalendarMonthMsgs
static $mHijriCalendarMonthMsgs
Definition: Language.php:143
Language\$namespaceNames
array null $namespaceNames
Definition: Language.php:69
Language\getPreferredVariant
getPreferredVariant()
Definition: Language.php:4266
wfLogWarning
wfLogWarning( $msg, $callerOffset=1, $level=E_USER_WARNING)
Send a warning as a PHP error and the debug log.
Definition: GlobalFunctions.php:1105
Language\getMonthAbbreviation
getMonthAbbreviation( $key)
Definition: Language.php:994
Language\ucwordbreaksCallbackAscii
ucwordbreaksCallbackAscii( $matches)
Definition: Language.php:2678
$digitGroupingPattern
$digitGroupingPattern
Definition: MessagesAs.php:167
Language\recodeForEdit
recodeForEdit( $s)
Definition: Language.php:3024
User\getDatePreference
getDatePreference()
Get the user's preferred date format.
Definition: User.php:3495
Language\getDefaultVariant
getDefaultVariant()
Definition: Language.php:4273
Language\fallback8bitEncoding
fallback8bitEncoding()
Definition: Language.php:2871
Language\embedBidi
embedBidi( $text='')
Wraps argument with unicode control characters for directionality safety.
Definition: Language.php:4059
Language\truncateInternal
truncateInternal( $string, $length, $ellipsis, $adjustLength, callable $measureLength, callable $getSubstring)
Internal method used for truncation.
Definition: Language.php:3570
Language\formatTimePeriod
formatTimePeriod( $seconds, $format=[])
Formats a time given in seconds into a string representation of that time.
Definition: Language.php:4684
Language\formatBitrate
formatBitrate( $bps)
Format a bitrate for output, using an appropriate unit (bps, kbps, Mbps, Gbps, Tbps,...
Definition: Language.php:4784
Language\equals
equals(Language $lang)
Compare with an other language object.
Definition: Language.php:4385
Language\SUPPORTED
const SUPPORTED
Return in fetchLanguageName(s) only the languages for which we have at least some localisation.
Definition: Language.php:54
Language\getSpecialPageAliases
getSpecialPageAliases()
Get special page names, as an associative array canonical name => array of valid names,...
Definition: Language.php:3247
Language\getMessagesFileName
static getMessagesFileName( $code)
Definition: Language.php:4480
Language\getPluralRuleTypes
getPluralRuleTypes()
Get the plural rule types for the language.
Definition: Language.php:4978
Language\capitalizeAllNouns
capitalizeAllNouns()
Definition: Language.php:3165
Language\AS_AUTONYMS
const AS_AUTONYMS
Return autonyms in fetchLanguageName(s).
Definition: Language.php:41
Language\specialList
specialList( $page, $details, $oppositedm=true)
Make a list item, used by various special pages.
Definition: Language.php:4845
Language\$mHebrewCalendarMonthGenMsgs
static $mHebrewCalendarMonthGenMsgs
Definition: Language.php:135
$wgMetaNamespace
$wgMetaNamespace
Name of the project namespace.
Definition: DefaultSettings.php:3813
Language\checkTitleEncoding
checkTitleEncoding( $s)
TODO: $s is not always a string per T218883.
Definition: Language.php:2857
Language\$mParentLanguage
$mParentLanguage
Definition: Language.php:63
php
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:35
Language\isWellFormedLanguageTag
static isWellFormedLanguageTag( $code, $lenient=false)
Returns true if a language code string is a well-formed language tag according to RFC 5646.
Definition: Language.php:333
Language\getFormattedNamespaces
getFormattedNamespaces()
A convenience function that returns getNamespaces() with spaces instead of underscores in values.
Definition: Language.php:565
Language\lcfirst
lcfirst( $str)
Definition: Language.php:2741
Language\initEncoding
initEncoding()
Definition: Language.php:3014
Language\formatExpiry
formatExpiry( $expiry, $format=true, $infinity='infinity')
Decode an expiry (block, protection, etc) which has come from the DB.
Definition: Language.php:4654
Language\isKnownLanguageTag
static isKnownLanguageTag( $tag)
Returns true if a language code is an IETF tag known to MediaWiki.
Definition: Language.php:426
Language\emphasize
emphasize( $text)
Italic is unsuitable for some languages.
Definition: Language.php:3264
Language\getFallbackLanguages
getFallbackLanguages()
Definition: Language.php:487
Language\getDurationIntervals
getDurationIntervals( $seconds, array $chosenIntervals=[])
Takes a number of seconds and returns an array with a set of corresponding intervals.
Definition: Language.php:2383
Language\caseFold
caseFold( $s)
Return a case-folded representation of $s.
Definition: Language.php:2848
Language\userTimeAndDate
userTimeAndDate( $ts, User $user, array $options=[])
Get the formatted date and time for the given timestamp and formatted for the given user.
Definition: Language.php:2519
Language\segmentForDiff
segmentForDiff( $text)
languages like Chinese need to be segmented in order for the diff to be of any use
Definition: Language.php:4129
Language\classFromCode
static classFromCode( $code, $fallback=true)
Definition: Language.php:4452
MWNamespace\getCanonicalIndex
static getCanonicalIndex( $name)
Returns the index for a given canonical name, or NULL The input must be converted to lower case first...
Definition: MWNamespace.php:268
Language\getWeekdayAbbreviation
getWeekdayAbbreviation( $key)
Definition: Language.php:1021
Language\msg
msg( $msg)
Get message object in this language.
Definition: Language.php:959
$query
null for the wiki Added should default to null in handler for backwards compatibility add a value to it if you want to add a cookie that have to vary cache options can modify $query
Definition: hooks.txt:1588
Language\needsGenderDistinction
needsGenderDistinction()
Whether this language uses gender-dependent namespace aliases.
Definition: Language.php:630
$data
$data
Utility to generate mapping file used in mw.Title (phpCharToUpper.json)
Definition: generatePhpCharToUpperMappings.php:13
Language\getParsedTitle
getParsedTitle()
For languages that support multiple variants, the title of an article may be displayed differently in...
Definition: Language.php:4317
Language\dateTimeObjFormat
static dateTimeObjFormat(&$dateTimeObj, $ts, $zone, $code)
Pass through result from $dateTimeObj->format()
Definition: Language.php:1065
FormatJson\decode
static decode( $value, $assoc=false)
Decodes a JSON string.
Definition: FormatJson.php:174
Language\getLocalisationCache
static getLocalisationCache()
Get the LocalisationCache instance.
Definition: Language.php:447
MWException
MediaWiki exception.
Definition: MWException.php:26
$title
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:925
NS_PROJECT
const NS_PROJECT
Definition: Defines.php:68
Language\viewPrevNext
viewPrevNext(Title $title, $offset, $limit, array $query=[], $atend=false)
Generate (prev x| next x) (20|50|100...) type links for paging.
Definition: Language.php:4869
Language\$transformData
$transformData
ReplacementArray object caches.
Definition: Language.php:75
wfDeprecated
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
Definition: GlobalFunctions.php:1078
Language\tsToIranian
static tsToIranian( $ts)
Algorithm by Roozbeh Pournader and Mohammad Toossi to convert Gregorian dates to Iranian dates.
Definition: Language.php:1627
Language\unsegmentForDiff
unsegmentForDiff( $text)
and unsegment to show the result
Definition: Language.php:4139
Language\autoConvert
autoConvert( $text, $variant=false)
convert text to a variant
Definition: Language.php:4161
Language\truncateHtml
truncateHtml( $text, $length, $ellipsis='...')
Truncate a string of valid HTML to a specified length in bytes, appending an optional string (e....
Definition: Language.php:3675
Language\normalize
normalize( $s)
Convert a UTF-8 string to normal form C.
Definition: Language.php:3050
Language\getBookstoreList
getBookstoreList()
Exports $wgBookstoreListEn.
Definition: Language.php:495
Language\$mHebrewCalendarMonthMsgs
static $mHebrewCalendarMonthMsgs
Definition: Language.php:127
Language\pipeList
pipeList(array $list)
Same as commaList, but separate it with the pipe instead.
Definition: Language.php:3498
Language\$grammarTransformations
static MapCacheLRU null $grammarTransformations
Cache for grammar rules data.
Definition: Language.php:178
wfGetDB
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
Definition: GlobalFunctions.php:2636
Language\convertForSearchResult
convertForSearchResult( $termsArray)
Definition: Language.php:2947
Language\getMessageFor
static getMessageFor( $key, $code)
Get a message for a given language.
Definition: Language.php:4599
$matches
$matches
Definition: NoLocalSettings.php:24
Language\getGenderNsText
getGenderNsText( $index, $gender)
Returns gender-dependent namespace alias if available.
Definition: Language.php:615
Language\listToText
listToText(array $list)
Take a list of strings and build a locale-friendly comma-separated list, using the local comma-separa...
Definition: Language.php:3449
not
if not
Definition: COPYING.txt:307
Language\getHtmlCode
getHtmlCode()
Get the code in BCP 47 format which we can use inside of html lang="" tags.
Definition: Language.php:4411
Language\getNamespaceAliases
getNamespaceAliases()
Definition: Language.php:663
Language\getLocalNsIndex
getLocalNsIndex( $text)
Get a namespace key by value, case insensitive.
Definition: Language.php:654
Language\userDate
userDate( $ts, User $user, array $options=[])
Get the formatted date for the given timestamp and formatted for the given user.
Definition: Language.php:2473
MediaWiki
A helper class for throttling authentication attempts.
$IP
$IP
Definition: update.php:3
Language\addMagicWordsByLang
addMagicWordsByLang( $newWords)
Add magic words to the extension array.
Definition: Language.php:3232
Language\getMonthName
getMonthName( $key)
Definition: Language.php:967
Language\$mMonthMsgs
static $mMonthMsgs
Definition: Language.php:105
Language\truncateForDatabase
truncateForDatabase( $string, $length, $ellipsis='...', $adjustLength=true)
Truncate a string to a specified length in bytes, appending an optional string (e....
Definition: Language.php:3520
Language\getAllMessages
getAllMessages()
Definition: Language.php:2651
Language\getMagic
getMagic( $mw)
Fill a MagicWord object with data from here.
Definition: Language.php:3215
MapCacheLRU
Handles a simple LRU key/value map with a maximum number of entries.
Definition: MapCacheLRU.php:37
TO
we sometimes make exceptions for this Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally NO WARRANTY BECAUSE THE PROGRAM IS LICENSED FREE OF THERE IS NO WARRANTY FOR THE TO THE EXTENT PERMITTED BY APPLICABLE LAW EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND OR OTHER PARTIES PROVIDE THE PROGRAM AS IS WITHOUT WARRANTY OF ANY EITHER EXPRESSED OR BUT NOT LIMITED TO
Definition: COPYING.txt:260
use
as see the revision history and available at free of to any person obtaining a copy of this software and associated documentation to deal in the Software without including without limitation the rights to use
Definition: MIT-LICENSE.txt:10
$code
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that 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
Language\$rle
static $rle
Definition: Language.php:190
Language\isRTL
isRTL()
For right-to-left language support.
Definition: Language.php:3089
Language\userTime
userTime( $ts, User $user, array $options=[])
Get the formatted time for the given timestamp and formatted for the given user.
Definition: Language.php:2496
$wgLocalisationCacheConf
$wgLocalisationCacheConf
Localisation cache configuration.
Definition: DefaultSettings.php:2587
Language\getNsIndex
getNsIndex( $text)
Get a namespace key by value, case insensitive.
Definition: Language.php:745
Language\getNamespaceIds
getNamespaceIds()
Definition: Language.php:715
Language\getPluralRuleIndexNumber
getPluralRuleIndexNumber( $number)
Find the index number of the plural rule appropriate for the given number.
Definition: Language.php:4997
Language\getMonthNameGen
getMonthNameGen( $key)
Definition: Language.php:986
DB_REPLICA
const DB_REPLICA
Definition: defines.php:25
Language\convertDoubleWidth
static convertDoubleWidth( $string)
convert double-width roman characters to single-width.
Definition: Language.php:2917
Language\getCompiledPluralRules
getCompiledPluralRules()
Get the compiled plural rules for the language.
Definition: Language.php:4940
Language\getHumanTimestampInternal
getHumanTimestampInternal(MWTimestamp $ts, MWTimestamp $relativeTo, User $user)
Convert an MWTimestamp into a pretty human-readable timestamp using the given user preferences and re...
Definition: Language.php:2575
Language\convertCategoryKey
convertCategoryKey( $key)
Definition: Language.php:4249
Language\digitGroupingPattern
digitGroupingPattern()
Definition: Language.php:3416
Language\ucwords
ucwords( $str)
Definition: Language.php:2784
array
The wiki should then use memcached to cache various data To use multiple just add more items to the array To increase the weight of a make its entry a array("192.168.0.1:11211", 2))
Language\initContLang
initContLang()
Hook which will be called if this is the content language.
Definition: Language.php:480
string
This code would result in ircNotify being run twice when an article is and once for brion Hooks can return three possible true was required This is the default since MediaWiki *some string
Definition: hooks.txt:175
Language\getPluralRules
getPluralRules()
Get the plural rules for the language.
Definition: Language.php:4959
list
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
Definition: deferred.txt:11
Language\getArrow
getArrow( $direction='forwards')
An arrow, depending on the language direction.
Definition: Language.php:3176
Language\uc
uc( $str, $first=false)
Convert a string to uppercase.
Definition: Language.php:2725
Language\$mVariants
$mVariants
Definition: Language.php:61
Language\translateBlockExpiry
translateBlockExpiry( $str, User $user=null, $now=0)
Definition: Language.php:4086
or
or
Definition: COPYING.txt:140
Language\formatNumNoSeparators
formatNumNoSeparators( $number)
Front-end for non-commafied formatNum.
Definition: Language.php:3318
$wgTranslateNumerals
$wgTranslateNumerals
For Hindi and Arabic use local numerals instead of Western style (0-9) numerals in interface.
Definition: DefaultSettings.php:3064
$name
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:271
Language\$mHtmlCode
$mHtmlCode
Definition: Language.php:63
$wgNamespaceAliases
$wgNamespaceAliases
Namespace aliases.
Definition: DefaultSettings.php:3878
Language\$mLoaded
$mLoaded
Definition: Language.php:61
Language\isValidBuiltInCode
static isValidBuiltInCode( $code)
Returns true if a language code is of a valid form for the purposes of internal customisation of Medi...
Definition: Language.php:412
Language\formatDuration
formatDuration( $seconds, array $chosenIntervals=[])
Takes a number of seconds and turns it into a text using values such as hours and minutes.
Definition: Language.php:2357
Language\setNamespaces
setNamespaces(array $namespaces)
Arbitrarily set all of the namespace names at once.
Definition: Language.php:545
Language\linkPrefixCharset
linkPrefixCharset()
A regular expression character set to match legal word-prefixing characters which should be merged on...
Definition: Language.php:4347
NS_USER_TALK
const NS_USER_TALK
Definition: Defines.php:67
$e
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException' returning false will NOT prevent logging $e
Definition: hooks.txt:2162
ReplacementArray
Wrapper around strtr() that holds replacements.
Definition: ReplacementArray.php:24
Language\findVariantLink
findVariantLink(&$link, &$nt, $ignoreOtherCond=false)
If a language supports multiple variants, it is possible that non-existing link in one variant actual...
Definition: Language.php:4296
Language\fixVariableInNamespace
fixVariableInNamespace( $talk)
Definition: Language.php:4619
$value
$value
Definition: styleTest.css.php:49
Language\ucwordbreaks
ucwordbreaks( $str)
capitalize words at word breaks
Definition: Language.php:2808
Language\getNsText
getNsText( $index)
Get a namespace value by key.
Definition: Language.php:584
$wgLocalTZoffset
$wgLocalTZoffset
Set an offset from UTC in minutes to use for the default timezone setting for anonymous users and new...
Definition: DefaultSettings.php:3195
Language\getCode
getCode()
Get the internal language code for this language object.
Definition: Language.php:4397
Language\getPluralRuleType
getPluralRuleType( $number)
Find the plural rule type appropriate for the given number For example, if the language is set to Ara...
Definition: Language.php:5011
Language\dateFormat
dateFormat( $usePrefs=true)
This is meant to be used by time(), date(), and timeanddate() to get the date preference they're supp...
Definition: Language.php:2234
$wgMetaNamespaceTalk
$wgMetaNamespaceTalk
Name of the project talk namespace.
Definition: DefaultSettings.php:3822
Language\ucwordsCallbackMB
ucwordsCallbackMB( $matches)
Definition: Language.php:2694
$wgLanguageCode
$wgLanguageCode
Site language code.
Definition: DefaultSettings.php:2912
Language\hebrewNumeral
static hebrewNumeral( $num)
Hebrew Gematria number formatting up to 9999.
Definition: Language.php:2069
wfIsInfinity
wfIsInfinity( $str)
Determine input string is represents as infinity.
Definition: GlobalFunctions.php:3119
Language\getFormattedNsText
getFormattedNsText( $index)
A convenience function that returns the same thing as getNsText() except with '_' changed to ' ',...
Definition: Language.php:602
Language\getMessagesFor
static getMessagesFor( $code)
Get all messages for a given language WARNING: this may take a long time.
Definition: Language.php:4587
Language\ucwordbreaksCallbackMB
ucwordbreaksCallbackMB( $matches)
Definition: Language.php:2686
NS_PROJECT_TALK
const NS_PROJECT_TALK
Definition: Defines.php:69
Language\handleExplicitPluralForms
handleExplicitPluralForms( $count, array $forms)
Handles explicit plural forms for Language::convertPlural()
Definition: Language.php:4018
$ret
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses & $ret
Definition: hooks.txt:1985
Language\getFileName
static getFileName( $prefix, $code, $suffix='.php')
Get the name of a file for a certain language code.
Definition: Language.php:4468
LocalisationCache
Class for caching the contents of localisation files, Messages*.php and *.i18n.php.
Definition: LocalisationCache.php:39
Language\recodeInput
recodeInput( $s)
Definition: Language.php:3034
RequestContext\getMain
static getMain()
Get the RequestContext object associated with the main request.
Definition: RequestContext.php:430
Language\getDirMark
getDirMark( $opposite=false)
A hidden direction mark (LRM or RLM), depending on the language direction.
Definition: Language.php:3153
Language\normalizeForSearch
normalizeForSearch( $string)
Some languages have special punctuation need to be normalized.
Definition: Language.php:2905
Language\isValidCode
static isValidCode( $code)
Returns true if a language code string is of a valid form, whether or not it exists.
Definition: Language.php:387
FakeConverter
A fake language variant converter.
Definition: FakeConverter.php:34
Language\userAdjust
userAdjust( $ts, $tz=false)
Used by date() and time() to adjust the time output.
Definition: Language.php:2156
Language\formatComputingNumbers
formatComputingNumbers( $size, $boundary, $messageKey)
Definition: Language.php:4794
Language\getFallbackFor
static getFallbackFor( $code)
Get the first fallback for a given language.
Definition: Language.php:4510
Language\__construct
__construct()
Definition: Language.php:456
Language\getMonthAbbreviationsArray
getMonthAbbreviationsArray()
Definition: Language.php:1001
Language\getParentLanguage
getParentLanguage()
Get the "parent" language which has a converter to convert a "compatible" language (in another varian...
Definition: Language.php:4358
Language\$dateFormatStrings
$dateFormatStrings
Definition: Language.php:65
Language\hasWordBreaks
hasWordBreaks()
Most writing systems use whitespace to break up words.
Definition: Language.php:2883
Language\getDir
getDir()
Return the correct HTML 'dir' attribute value for this language.
Definition: Language.php:3097
Language\date
date( $ts, $adj=false, $format=true, $timecorrection=false)
Definition: Language.php:2299
Language\alignEnd
alignEnd()
Return 'right' or 'left' as appropriate alignment for line-end for this language's text direction.
Definition: Language.php:3121
Title
Represents a title within MediaWiki.
Definition: Title.php:40
Language\truncate_skip
truncate_skip(&$ret, $text, $search, $start, $len=null)
truncateHtml() helper function like strcspn() but adds the skipped chars to $ret
Definition: Language.php:3794
Language\setCode
setCode( $code)
Definition: Language.php:4422
Language\getDefaultDateFormat
getDefaultDateFormat()
Definition: Language.php:793
Language\getVariants
getVariants()
Get the list of variants supported by this language see sample implementation in LanguageZh....
Definition: Language.php:4259
Language\convertPlural
convertPlural( $count, $forms)
Plural form transformations, needed for some languages.
Definition: Language.php:3988
$cache
$cache
Definition: mcc.php:33
other
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring which defines all default service and specifies how they depend on each other("wiring"). When a new service is added to MediaWiki core
$options
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped & $options
Definition: hooks.txt:1985
Language\$mLangObjCache
static $mLangObjCache
Definition: Language.php:82
$minimumGroupingDigits
$minimumGroupingDigits
Definition: MessagesBe_tarask.php:239
Language\getFallbacksIncludingSiteLanguage
static getFallbacksIncludingSiteLanguage( $code)
Get the ordered list of fallback languages, ending with the fallback language chain for the site lang...
Definition: Language.php:4556
$wgGrammarForms
$wgGrammarForms
Some languages need different word forms, usually for different cases.
Definition: DefaultSettings.php:2929
Language\getGrammarForms
getGrammarForms()
Get the grammar forms for the content language.
Definition: Language.php:3890
Language\removeBadCharFirst
removeBadCharFirst( $string)
Remove bytes that represent an incomplete Unicode character at the start of string (e....
Definition: Language.php:3649
Language\$strongDirRegex
static $strongDirRegex
Directionality test regex for embedBidi().
Definition: Language.php:206
as
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
Definition: distributors.txt:9
MWNamespace\getCanonicalNamespaces
static getCanonicalNamespaces( $rebuild=false)
Returns array of all defined namespaces with their canonical (English) names.
Definition: MWNamespace.php:231
LanguageCode\bcp47
static bcp47( $code)
Get the normalised IETF language tag See unit test for examples.
Definition: LanguageCode.php:179
$wgExtraGenderNamespaces
$wgExtraGenderNamespaces
Same as above, but for namespaces with gender distinction.
Definition: DefaultSettings.php:3858
Language\getHijriCalendarMonthName
getHijriCalendarMonthName( $key)
Definition: Language.php:1053
Language\getMessage
getMessage( $key)
Definition: Language.php:2644
Language\getGrammarTransformations
getGrammarTransformations()
Get the grammar transformations data for the language.
Definition: Language.php:3910
Language\getMessageKeysFor
static getMessageKeysFor( $code)
Get all message keys for a given language.
Definition: Language.php:4611
Language\getJsonMessagesFileName
static getJsonMessagesFileName( $code)
Definition: Language.php:4493
Language\firstChar
firstChar( $s)
Get the first character of a string.
Definition: Language.php:2959
NS_USER
const NS_USER
Definition: Defines.php:66
$link
usually copyright or history_copyright This message must be in HTML not wikitext & $link
Definition: hooks.txt:3053
Language\getNamespaces
getNamespaces()
Returns an array of localised namespaces indexed by their numbers.
Definition: Language.php:505
SpecialBlock\getSuggestedDurations
static getSuggestedDurations(Language $lang=null, $includeOther=true)
Get an array of suggested block durations from MediaWiki:Ipboptions.
Definition: SpecialBlock.php:1046
Language\transformUsingPairFile
transformUsingPairFile( $file, $string)
Transform a string using serialized data stored in the given file (which must be in the serialized su...
Definition: Language.php:3075
Language\removeBadCharLast
removeBadCharLast( $string)
Remove bytes that represent an incomplete Unicode character at the end of string (e....
Definition: Language.php:3623
true
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return true
Definition: hooks.txt:1985
Language\romanNumeral
static romanNumeral( $num)
Roman number formatting up to 10000.
Definition: Language.php:2038
Language\getConvRuleTitle
getConvRuleTitle()
Get the conversion rule title, if any.
Definition: Language.php:4931
of
globals txt Globals are evil The original MediaWiki code relied on globals for processing context far too often MediaWiki development since then has been a story of slowly moving context out of global variables and into objects Storing processing context in object member variables allows those objects to be reused in a much more flexible way Consider the elegance of
Definition: globals.txt:10
Language\factory
static factory( $code)
Get a cached or new language object for a given language code.
Definition: Language.php:215
wfWarn
wfWarn( $msg, $callerOffset=1, $level=E_USER_NOTICE)
Send a warning either to the debug log or in a PHP error depending on $wgDevelopmentWarnings.
Definition: GlobalFunctions.php:1092
Language\$namespaceAliases
$namespaceAliases
Definition: Language.php:70
class
you have access to all of the normal MediaWiki so you can get a DB use the etc For full docs on the Maintenance class
Definition: maintenance.txt:52
Language\tsToHijri
static tsToHijri( $ts)
Converting Gregorian dates to Hijri dates.
Definition: Language.php:1687
Language\getHumanTimestamp
getHumanTimestamp(MWTimestamp $time, MWTimestamp $relativeTo=null, User $user=null)
Get the timestamp in a human-friendly relative format, e.g., "3 days ago".
Definition: Language.php:2538
$time
see documentation in includes Linker php for Linker::makeImageLink & $time
Definition: hooks.txt:1802
$t
$t
Definition: testCompression.php:69
Language\time
time( $ts, $adj=false, $format=true, $timecorrection=false)
Definition: Language.php:2318
Language\insertSpace
static insertSpace( $string, $pattern)
Definition: Language.php:2937
Language\isMultibyte
isMultibyte( $str)
Definition: Language.php:2776
Language\alignStart
alignStart()
Return 'left' or 'right' as appropriate alignment for line-start for this language's text direction.
Definition: Language.php:3109
Language\isSupportedLanguage
static isSupportedLanguage( $code)
Checks whether any localisation is available for that language tag in MediaWiki (MessagesXx....
Definition: Language.php:305
Language\$mWeekdayMsgs
static $mWeekdayMsgs
Definition: Language.php:96
Language\fetchLanguageNames
static fetchLanguageNames( $inLanguage=self::AS_AUTONYMS, $include='mw')
Get an array of language names, indexed by code.
Definition: Language.php:836
wfMessage
either a unescaped string or a HtmlArmor object after in associative array form externallinks including delete and has completed for all link tables whether this was an auto creation 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
Language\digitTransformTable
digitTransformTable()
Definition: Language.php:3423
Language\tsToYear
static tsToYear( $ts, $cName)
Algorithm to convert Gregorian dates to Thai solar dates, Minguo dates or Minguo dates.
Definition: Language.php:1915
Language\getDirMarkEntity
getDirMarkEntity( $opposite=false)
A hidden direction mark (LRM or RLM), depending on the language direction.
Definition: Language.php:3136
options
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
Language\strongDirFromContent
static strongDirFromContent( $text='')
Gets directionality of the first strongly directional codepoint, for embedBidi()
Definition: Language.php:2021
Language\hebrewYearStart
static hebrewYearStart( $year)
This calculates the Hebrew year start, as days since 1 September.
Definition: Language.php:1877
Language\$mNamespaceIds
$mNamespaceIds
Definition: Language.php:70
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:48
Language\getDateFormatString
getDateFormatString( $type, $pref)
Get a format string for a given type and preference.
Definition: Language.php:2265
Language\$dataCache
static LocalisationCache $dataCache
Definition: Language.php:80
Hooks\run
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:200
Language\getMagicWords
getMagicWords()
Get all magic words from cache.
Definition: Language.php:3206
Language\hasVariant
hasVariant( $variant)
Strict check if the language has the specific variant.
Definition: Language.php:4231
Language\numLink
numLink(Title $title, $offset, $limit, array $query, $link, $tooltipMsg, $class)
Helper function for viewPrevNext() that generates links.
Definition: Language.php:4915
Language\__destruct
__destruct()
Reduce memory usage.
Definition: Language.php:470
Language\commafy
commafy( $number)
Adds commas to a given number.
Definition: Language.php:3351
Language\$pdf
static $pdf
Definition: Language.php:191
Language\STRICT_FALLBACKS
const STRICT_FALLBACKS
Return a strict fallback chain in getFallbacksFor.
Definition: Language.php:94
$wgExtraNamespaces
$wgExtraNamespaces
Additional namespaces.
Definition: DefaultSettings.php:3850
Language
Internationalisation code.
Definition: Language.php:36
Language\newFromCode
static newFromCode( $code, $fallback=false)
Create a language object for a given language code.
Definition: Language.php:240
Language\getFallbacksFor
static getFallbacksFor( $code, $mode=self::MESSAGES_FALLBACKS)
Get the ordered list of fallback languages.
Definition: Language.php:4528
Language\$mExtendedSpecialPageAliases
$mExtendedSpecialPageAliases
Definition: Language.php:66
Language\$durationIntervals
static array $durationIntervals
Definition: Language.php:154
Language\tsToHebrew
static tsToHebrew( $ts)
Converting Gregorian dates to Hebrew dates.
Definition: Language.php:1739
Language\autoConvertToAllVariants
autoConvertToAllVariants( $text)
convert text to all supported variants
Definition: Language.php:4171
Language\linkTrail
linkTrail()
A regular expression to match legal word-trailing characters which should be merged onto a link of th...
Definition: Language.php:4337
$wgAllUnicodeFixes
$wgAllUnicodeFixes
Set this to always convert certain Unicode sequences to modern ones regardless of the content languag...
Definition: DefaultSettings.php:3026
Language\ucfirst
ucfirst( $str)
Make a string's first character uppercase.
Definition: Language.php:2705
Language\$mMagicExtensions
$mMagicExtensions
Definition: Language.php:62
$wgDummyLanguageCodes
$wgDummyLanguageCodes
Functionally the same as $wgExtraLanguageCodes, but deprecated.
Definition: DefaultSettings.php:2989
Language\getWeekdayName
getWeekdayName( $key)
Definition: Language.php:1013
Language\gender
gender( $gender, $forms)
Provides an alternative text depending on specified gender.
Definition: Language.php:3959
Language\$lre
static $lre
Unicode directional formatting characters, for embedBidi()
Definition: Language.php:189
Language\getHebrewCalendarMonthName
getHebrewCalendarMonthName( $key)
Definition: Language.php:1037
Language\formatNum
formatNum( $number, $nocommafy=false)
Normally we output all numbers in plain en_US style, that is 293,291.235 for twohundredninetythreetho...
Definition: Language.php:3290
$type
$type
Definition: testCompression.php:48
Language\$fallbackLanguageCache
static array $fallbackLanguageCache
Cache for language fallbacks.
Definition: Language.php:172
Language\convertNamespace
convertNamespace( $ns, $variant=null)
Convert a namespace index to a string in the preferred variant.
Definition: Language.php:4208
Language\convertTitle
convertTitle( $title)
Convert a Title object to a string in the preferred variant.
Definition: Language.php:4196