MediaWiki  master
Language.php
Go to the documentation of this file.
1 <?php
30 
35 class Language {
39  public $mConverter;
40 
41  public $mVariants, $mCode, $mLoaded = false;
42  public $mMagicExtensions = [], $mMagicHookDone = false;
43  private $mHtmlCode = null, $mParentLanguage = false;
44 
45  public $dateFormatStrings = [];
47 
49  protected $namespaceNames;
51 
55  public $transformData = [];
56 
60  static public $dataCache;
61 
62  static public $mLangObjCache = [];
63 
64  static public $mWeekdayMsgs = [
65  'sunday', 'monday', 'tuesday', 'wednesday', 'thursday',
66  'friday', 'saturday'
67  ];
68 
69  static public $mWeekdayAbbrevMsgs = [
70  'sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'
71  ];
72 
73  static public $mMonthMsgs = [
74  'january', 'february', 'march', 'april', 'may_long', 'june',
75  'july', 'august', 'september', 'october', 'november',
76  'december'
77  ];
78  static public $mMonthGenMsgs = [
79  'january-gen', 'february-gen', 'march-gen', 'april-gen', 'may-gen', 'june-gen',
80  'july-gen', 'august-gen', 'september-gen', 'october-gen', 'november-gen',
81  'december-gen'
82  ];
83  static public $mMonthAbbrevMsgs = [
84  'jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug',
85  'sep', 'oct', 'nov', 'dec'
86  ];
87 
88  static public $mIranianCalendarMonthMsgs = [
89  'iranian-calendar-m1', 'iranian-calendar-m2', 'iranian-calendar-m3',
90  'iranian-calendar-m4', 'iranian-calendar-m5', 'iranian-calendar-m6',
91  'iranian-calendar-m7', 'iranian-calendar-m8', 'iranian-calendar-m9',
92  'iranian-calendar-m10', 'iranian-calendar-m11', 'iranian-calendar-m12'
93  ];
94 
95  static public $mHebrewCalendarMonthMsgs = [
96  'hebrew-calendar-m1', 'hebrew-calendar-m2', 'hebrew-calendar-m3',
97  'hebrew-calendar-m4', 'hebrew-calendar-m5', 'hebrew-calendar-m6',
98  'hebrew-calendar-m7', 'hebrew-calendar-m8', 'hebrew-calendar-m9',
99  'hebrew-calendar-m10', 'hebrew-calendar-m11', 'hebrew-calendar-m12',
100  'hebrew-calendar-m6a', 'hebrew-calendar-m6b'
101  ];
102 
104  'hebrew-calendar-m1-gen', 'hebrew-calendar-m2-gen', 'hebrew-calendar-m3-gen',
105  'hebrew-calendar-m4-gen', 'hebrew-calendar-m5-gen', 'hebrew-calendar-m6-gen',
106  'hebrew-calendar-m7-gen', 'hebrew-calendar-m8-gen', 'hebrew-calendar-m9-gen',
107  'hebrew-calendar-m10-gen', 'hebrew-calendar-m11-gen', 'hebrew-calendar-m12-gen',
108  'hebrew-calendar-m6a-gen', 'hebrew-calendar-m6b-gen'
109  ];
110 
111  static public $mHijriCalendarMonthMsgs = [
112  'hijri-calendar-m1', 'hijri-calendar-m2', 'hijri-calendar-m3',
113  'hijri-calendar-m4', 'hijri-calendar-m5', 'hijri-calendar-m6',
114  'hijri-calendar-m7', 'hijri-calendar-m8', 'hijri-calendar-m9',
115  'hijri-calendar-m10', 'hijri-calendar-m11', 'hijri-calendar-m12'
116  ];
117 
122  static public $durationIntervals = [
123  'millennia' => 31556952000,
124  'centuries' => 3155695200,
125  'decades' => 315569520,
126  'years' => 31556952, // 86400 * ( 365 + ( 24 * 3 + 25 ) / 400 )
127  'weeks' => 604800,
128  'days' => 86400,
129  'hours' => 3600,
130  'minutes' => 60,
131  'seconds' => 1,
132  ];
133 
140  static private $fallbackLanguageCache = [];
141 
146  static private $grammarTransformations;
147 
152  static private $languageNameCache;
153 
157  static private $lre = "\xE2\x80\xAA"; // U+202A LEFT-TO-RIGHT EMBEDDING
158  static private $rle = "\xE2\x80\xAB"; // U+202B RIGHT-TO-LEFT EMBEDDING
159  static private $pdf = "\xE2\x80\xAC"; // U+202C POP DIRECTIONAL FORMATTING
160 
172  // @codeCoverageIgnoreStart
173  // phpcs:ignore Generic.Files.LineLength
174  static private $strongDirRegex = '/(?:([\x{41}-\x{5a}\x{61}-\x{7a}\x{aa}\x{b5}\x{ba}\x{c0}-\x{d6}\x{d8}-\x{f6}\x{f8}-\x{2b8}\x{2bb}-\x{2c1}\x{2d0}\x{2d1}\x{2e0}-\x{2e4}\x{2ee}\x{370}-\x{373}\x{376}\x{377}\x{37a}-\x{37d}\x{37f}\x{386}\x{388}-\x{38a}\x{38c}\x{38e}-\x{3a1}\x{3a3}-\x{3f5}\x{3f7}-\x{482}\x{48a}-\x{52f}\x{531}-\x{556}\x{559}-\x{55f}\x{561}-\x{587}\x{589}\x{903}-\x{939}\x{93b}\x{93d}-\x{940}\x{949}-\x{94c}\x{94e}-\x{950}\x{958}-\x{961}\x{964}-\x{980}\x{982}\x{983}\x{985}-\x{98c}\x{98f}\x{990}\x{993}-\x{9a8}\x{9aa}-\x{9b0}\x{9b2}\x{9b6}-\x{9b9}\x{9bd}-\x{9c0}\x{9c7}\x{9c8}\x{9cb}\x{9cc}\x{9ce}\x{9d7}\x{9dc}\x{9dd}\x{9df}-\x{9e1}\x{9e6}-\x{9f1}\x{9f4}-\x{9fa}\x{a03}\x{a05}-\x{a0a}\x{a0f}\x{a10}\x{a13}-\x{a28}\x{a2a}-\x{a30}\x{a32}\x{a33}\x{a35}\x{a36}\x{a38}\x{a39}\x{a3e}-\x{a40}\x{a59}-\x{a5c}\x{a5e}\x{a66}-\x{a6f}\x{a72}-\x{a74}\x{a83}\x{a85}-\x{a8d}\x{a8f}-\x{a91}\x{a93}-\x{aa8}\x{aaa}-\x{ab0}\x{ab2}\x{ab3}\x{ab5}-\x{ab9}\x{abd}-\x{ac0}\x{ac9}\x{acb}\x{acc}\x{ad0}\x{ae0}\x{ae1}\x{ae6}-\x{af0}\x{af9}\x{b02}\x{b03}\x{b05}-\x{b0c}\x{b0f}\x{b10}\x{b13}-\x{b28}\x{b2a}-\x{b30}\x{b32}\x{b33}\x{b35}-\x{b39}\x{b3d}\x{b3e}\x{b40}\x{b47}\x{b48}\x{b4b}\x{b4c}\x{b57}\x{b5c}\x{b5d}\x{b5f}-\x{b61}\x{b66}-\x{b77}\x{b83}\x{b85}-\x{b8a}\x{b8e}-\x{b90}\x{b92}-\x{b95}\x{b99}\x{b9a}\x{b9c}\x{b9e}\x{b9f}\x{ba3}\x{ba4}\x{ba8}-\x{baa}\x{bae}-\x{bb9}\x{bbe}\x{bbf}\x{bc1}\x{bc2}\x{bc6}-\x{bc8}\x{bca}-\x{bcc}\x{bd0}\x{bd7}\x{be6}-\x{bf2}\x{c01}-\x{c03}\x{c05}-\x{c0c}\x{c0e}-\x{c10}\x{c12}-\x{c28}\x{c2a}-\x{c39}\x{c3d}\x{c41}-\x{c44}\x{c58}-\x{c5a}\x{c60}\x{c61}\x{c66}-\x{c6f}\x{c7f}\x{c82}\x{c83}\x{c85}-\x{c8c}\x{c8e}-\x{c90}\x{c92}-\x{ca8}\x{caa}-\x{cb3}\x{cb5}-\x{cb9}\x{cbd}-\x{cc4}\x{cc6}-\x{cc8}\x{cca}\x{ccb}\x{cd5}\x{cd6}\x{cde}\x{ce0}\x{ce1}\x{ce6}-\x{cef}\x{cf1}\x{cf2}\x{d02}\x{d03}\x{d05}-\x{d0c}\x{d0e}-\x{d10}\x{d12}-\x{d3a}\x{d3d}-\x{d40}\x{d46}-\x{d48}\x{d4a}-\x{d4c}\x{d4e}\x{d57}\x{d5f}-\x{d61}\x{d66}-\x{d75}\x{d79}-\x{d7f}\x{d82}\x{d83}\x{d85}-\x{d96}\x{d9a}-\x{db1}\x{db3}-\x{dbb}\x{dbd}\x{dc0}-\x{dc6}\x{dcf}-\x{dd1}\x{dd8}-\x{ddf}\x{de6}-\x{def}\x{df2}-\x{df4}\x{e01}-\x{e30}\x{e32}\x{e33}\x{e40}-\x{e46}\x{e4f}-\x{e5b}\x{e81}\x{e82}\x{e84}\x{e87}\x{e88}\x{e8a}\x{e8d}\x{e94}-\x{e97}\x{e99}-\x{e9f}\x{ea1}-\x{ea3}\x{ea5}\x{ea7}\x{eaa}\x{eab}\x{ead}-\x{eb0}\x{eb2}\x{eb3}\x{ebd}\x{ec0}-\x{ec4}\x{ec6}\x{ed0}-\x{ed9}\x{edc}-\x{edf}\x{f00}-\x{f17}\x{f1a}-\x{f34}\x{f36}\x{f38}\x{f3e}-\x{f47}\x{f49}-\x{f6c}\x{f7f}\x{f85}\x{f88}-\x{f8c}\x{fbe}-\x{fc5}\x{fc7}-\x{fcc}\x{fce}-\x{fda}\x{1000}-\x{102c}\x{1031}\x{1038}\x{103b}\x{103c}\x{103f}-\x{1057}\x{105a}-\x{105d}\x{1061}-\x{1070}\x{1075}-\x{1081}\x{1083}\x{1084}\x{1087}-\x{108c}\x{108e}-\x{109c}\x{109e}-\x{10c5}\x{10c7}\x{10cd}\x{10d0}-\x{1248}\x{124a}-\x{124d}\x{1250}-\x{1256}\x{1258}\x{125a}-\x{125d}\x{1260}-\x{1288}\x{128a}-\x{128d}\x{1290}-\x{12b0}\x{12b2}-\x{12b5}\x{12b8}-\x{12be}\x{12c0}\x{12c2}-\x{12c5}\x{12c8}-\x{12d6}\x{12d8}-\x{1310}\x{1312}-\x{1315}\x{1318}-\x{135a}\x{1360}-\x{137c}\x{1380}-\x{138f}\x{13a0}-\x{13f5}\x{13f8}-\x{13fd}\x{1401}-\x{167f}\x{1681}-\x{169a}\x{16a0}-\x{16f8}\x{1700}-\x{170c}\x{170e}-\x{1711}\x{1720}-\x{1731}\x{1735}\x{1736}\x{1740}-\x{1751}\x{1760}-\x{176c}\x{176e}-\x{1770}\x{1780}-\x{17b3}\x{17b6}\x{17be}-\x{17c5}\x{17c7}\x{17c8}\x{17d4}-\x{17da}\x{17dc}\x{17e0}-\x{17e9}\x{1810}-\x{1819}\x{1820}-\x{1877}\x{1880}-\x{18a8}\x{18aa}\x{18b0}-\x{18f5}\x{1900}-\x{191e}\x{1923}-\x{1926}\x{1929}-\x{192b}\x{1930}\x{1931}\x{1933}-\x{1938}\x{1946}-\x{196d}\x{1970}-\x{1974}\x{1980}-\x{19ab}\x{19b0}-\x{19c9}\x{19d0}-\x{19da}\x{1a00}-\x{1a16}\x{1a19}\x{1a1a}\x{1a1e}-\x{1a55}\x{1a57}\x{1a61}\x{1a63}\x{1a64}\x{1a6d}-\x{1a72}\x{1a80}-\x{1a89}\x{1a90}-\x{1a99}\x{1aa0}-\x{1aad}\x{1b04}-\x{1b33}\x{1b35}\x{1b3b}\x{1b3d}-\x{1b41}\x{1b43}-\x{1b4b}\x{1b50}-\x{1b6a}\x{1b74}-\x{1b7c}\x{1b82}-\x{1ba1}\x{1ba6}\x{1ba7}\x{1baa}\x{1bae}-\x{1be5}\x{1be7}\x{1bea}-\x{1bec}\x{1bee}\x{1bf2}\x{1bf3}\x{1bfc}-\x{1c2b}\x{1c34}\x{1c35}\x{1c3b}-\x{1c49}\x{1c4d}-\x{1c7f}\x{1cc0}-\x{1cc7}\x{1cd3}\x{1ce1}\x{1ce9}-\x{1cec}\x{1cee}-\x{1cf3}\x{1cf5}\x{1cf6}\x{1d00}-\x{1dbf}\x{1e00}-\x{1f15}\x{1f18}-\x{1f1d}\x{1f20}-\x{1f45}\x{1f48}-\x{1f4d}\x{1f50}-\x{1f57}\x{1f59}\x{1f5b}\x{1f5d}\x{1f5f}-\x{1f7d}\x{1f80}-\x{1fb4}\x{1fb6}-\x{1fbc}\x{1fbe}\x{1fc2}-\x{1fc4}\x{1fc6}-\x{1fcc}\x{1fd0}-\x{1fd3}\x{1fd6}-\x{1fdb}\x{1fe0}-\x{1fec}\x{1ff2}-\x{1ff4}\x{1ff6}-\x{1ffc}\x{200e}\x{2071}\x{207f}\x{2090}-\x{209c}\x{2102}\x{2107}\x{210a}-\x{2113}\x{2115}\x{2119}-\x{211d}\x{2124}\x{2126}\x{2128}\x{212a}-\x{212d}\x{212f}-\x{2139}\x{213c}-\x{213f}\x{2145}-\x{2149}\x{214e}\x{214f}\x{2160}-\x{2188}\x{2336}-\x{237a}\x{2395}\x{249c}-\x{24e9}\x{26ac}\x{2800}-\x{28ff}\x{2c00}-\x{2c2e}\x{2c30}-\x{2c5e}\x{2c60}-\x{2ce4}\x{2ceb}-\x{2cee}\x{2cf2}\x{2cf3}\x{2d00}-\x{2d25}\x{2d27}\x{2d2d}\x{2d30}-\x{2d67}\x{2d6f}\x{2d70}\x{2d80}-\x{2d96}\x{2da0}-\x{2da6}\x{2da8}-\x{2dae}\x{2db0}-\x{2db6}\x{2db8}-\x{2dbe}\x{2dc0}-\x{2dc6}\x{2dc8}-\x{2dce}\x{2dd0}-\x{2dd6}\x{2dd8}-\x{2dde}\x{3005}-\x{3007}\x{3021}-\x{3029}\x{302e}\x{302f}\x{3031}-\x{3035}\x{3038}-\x{303c}\x{3041}-\x{3096}\x{309d}-\x{309f}\x{30a1}-\x{30fa}\x{30fc}-\x{30ff}\x{3105}-\x{312d}\x{3131}-\x{318e}\x{3190}-\x{31ba}\x{31f0}-\x{321c}\x{3220}-\x{324f}\x{3260}-\x{327b}\x{327f}-\x{32b0}\x{32c0}-\x{32cb}\x{32d0}-\x{32fe}\x{3300}-\x{3376}\x{337b}-\x{33dd}\x{33e0}-\x{33fe}\x{3400}-\x{4db5}\x{4e00}-\x{9fd5}\x{a000}-\x{a48c}\x{a4d0}-\x{a60c}\x{a610}-\x{a62b}\x{a640}-\x{a66e}\x{a680}-\x{a69d}\x{a6a0}-\x{a6ef}\x{a6f2}-\x{a6f7}\x{a722}-\x{a787}\x{a789}-\x{a7ad}\x{a7b0}-\x{a7b7}\x{a7f7}-\x{a801}\x{a803}-\x{a805}\x{a807}-\x{a80a}\x{a80c}-\x{a824}\x{a827}\x{a830}-\x{a837}\x{a840}-\x{a873}\x{a880}-\x{a8c3}\x{a8ce}-\x{a8d9}\x{a8f2}-\x{a8fd}\x{a900}-\x{a925}\x{a92e}-\x{a946}\x{a952}\x{a953}\x{a95f}-\x{a97c}\x{a983}-\x{a9b2}\x{a9b4}\x{a9b5}\x{a9ba}\x{a9bb}\x{a9bd}-\x{a9cd}\x{a9cf}-\x{a9d9}\x{a9de}-\x{a9e4}\x{a9e6}-\x{a9fe}\x{aa00}-\x{aa28}\x{aa2f}\x{aa30}\x{aa33}\x{aa34}\x{aa40}-\x{aa42}\x{aa44}-\x{aa4b}\x{aa4d}\x{aa50}-\x{aa59}\x{aa5c}-\x{aa7b}\x{aa7d}-\x{aaaf}\x{aab1}\x{aab5}\x{aab6}\x{aab9}-\x{aabd}\x{aac0}\x{aac2}\x{aadb}-\x{aaeb}\x{aaee}-\x{aaf5}\x{ab01}-\x{ab06}\x{ab09}-\x{ab0e}\x{ab11}-\x{ab16}\x{ab20}-\x{ab26}\x{ab28}-\x{ab2e}\x{ab30}-\x{ab65}\x{ab70}-\x{abe4}\x{abe6}\x{abe7}\x{abe9}-\x{abec}\x{abf0}-\x{abf9}\x{ac00}-\x{d7a3}\x{d7b0}-\x{d7c6}\x{d7cb}-\x{d7fb}\x{e000}-\x{fa6d}\x{fa70}-\x{fad9}\x{fb00}-\x{fb06}\x{fb13}-\x{fb17}\x{ff21}-\x{ff3a}\x{ff41}-\x{ff5a}\x{ff66}-\x{ffbe}\x{ffc2}-\x{ffc7}\x{ffca}-\x{ffcf}\x{ffd2}-\x{ffd7}\x{ffda}-\x{ffdc}\x{10000}-\x{1000b}\x{1000d}-\x{10026}\x{10028}-\x{1003a}\x{1003c}\x{1003d}\x{1003f}-\x{1004d}\x{10050}-\x{1005d}\x{10080}-\x{100fa}\x{10100}\x{10102}\x{10107}-\x{10133}\x{10137}-\x{1013f}\x{101d0}-\x{101fc}\x{10280}-\x{1029c}\x{102a0}-\x{102d0}\x{10300}-\x{10323}\x{10330}-\x{1034a}\x{10350}-\x{10375}\x{10380}-\x{1039d}\x{1039f}-\x{103c3}\x{103c8}-\x{103d5}\x{10400}-\x{1049d}\x{104a0}-\x{104a9}\x{10500}-\x{10527}\x{10530}-\x{10563}\x{1056f}\x{10600}-\x{10736}\x{10740}-\x{10755}\x{10760}-\x{10767}\x{11000}\x{11002}-\x{11037}\x{11047}-\x{1104d}\x{11066}-\x{1106f}\x{11082}-\x{110b2}\x{110b7}\x{110b8}\x{110bb}-\x{110c1}\x{110d0}-\x{110e8}\x{110f0}-\x{110f9}\x{11103}-\x{11126}\x{1112c}\x{11136}-\x{11143}\x{11150}-\x{11172}\x{11174}-\x{11176}\x{11182}-\x{111b5}\x{111bf}-\x{111c9}\x{111cd}\x{111d0}-\x{111df}\x{111e1}-\x{111f4}\x{11200}-\x{11211}\x{11213}-\x{1122e}\x{11232}\x{11233}\x{11235}\x{11238}-\x{1123d}\x{11280}-\x{11286}\x{11288}\x{1128a}-\x{1128d}\x{1128f}-\x{1129d}\x{1129f}-\x{112a9}\x{112b0}-\x{112de}\x{112e0}-\x{112e2}\x{112f0}-\x{112f9}\x{11302}\x{11303}\x{11305}-\x{1130c}\x{1130f}\x{11310}\x{11313}-\x{11328}\x{1132a}-\x{11330}\x{11332}\x{11333}\x{11335}-\x{11339}\x{1133d}-\x{1133f}\x{11341}-\x{11344}\x{11347}\x{11348}\x{1134b}-\x{1134d}\x{11350}\x{11357}\x{1135d}-\x{11363}\x{11480}-\x{114b2}\x{114b9}\x{114bb}-\x{114be}\x{114c1}\x{114c4}-\x{114c7}\x{114d0}-\x{114d9}\x{11580}-\x{115b1}\x{115b8}-\x{115bb}\x{115be}\x{115c1}-\x{115db}\x{11600}-\x{11632}\x{1163b}\x{1163c}\x{1163e}\x{11641}-\x{11644}\x{11650}-\x{11659}\x{11680}-\x{116aa}\x{116ac}\x{116ae}\x{116af}\x{116b6}\x{116c0}-\x{116c9}\x{11700}-\x{11719}\x{11720}\x{11721}\x{11726}\x{11730}-\x{1173f}\x{118a0}-\x{118f2}\x{118ff}\x{11ac0}-\x{11af8}\x{12000}-\x{12399}\x{12400}-\x{1246e}\x{12470}-\x{12474}\x{12480}-\x{12543}\x{13000}-\x{1342e}\x{14400}-\x{14646}\x{16800}-\x{16a38}\x{16a40}-\x{16a5e}\x{16a60}-\x{16a69}\x{16a6e}\x{16a6f}\x{16ad0}-\x{16aed}\x{16af5}\x{16b00}-\x{16b2f}\x{16b37}-\x{16b45}\x{16b50}-\x{16b59}\x{16b5b}-\x{16b61}\x{16b63}-\x{16b77}\x{16b7d}-\x{16b8f}\x{16f00}-\x{16f44}\x{16f50}-\x{16f7e}\x{16f93}-\x{16f9f}\x{1b000}\x{1b001}\x{1bc00}-\x{1bc6a}\x{1bc70}-\x{1bc7c}\x{1bc80}-\x{1bc88}\x{1bc90}-\x{1bc99}\x{1bc9c}\x{1bc9f}\x{1d000}-\x{1d0f5}\x{1d100}-\x{1d126}\x{1d129}-\x{1d166}\x{1d16a}-\x{1d172}\x{1d183}\x{1d184}\x{1d18c}-\x{1d1a9}\x{1d1ae}-\x{1d1e8}\x{1d360}-\x{1d371}\x{1d400}-\x{1d454}\x{1d456}-\x{1d49c}\x{1d49e}\x{1d49f}\x{1d4a2}\x{1d4a5}\x{1d4a6}\x{1d4a9}-\x{1d4ac}\x{1d4ae}-\x{1d4b9}\x{1d4bb}\x{1d4bd}-\x{1d4c3}\x{1d4c5}-\x{1d505}\x{1d507}-\x{1d50a}\x{1d50d}-\x{1d514}\x{1d516}-\x{1d51c}\x{1d51e}-\x{1d539}\x{1d53b}-\x{1d53e}\x{1d540}-\x{1d544}\x{1d546}\x{1d54a}-\x{1d550}\x{1d552}-\x{1d6a5}\x{1d6a8}-\x{1d6da}\x{1d6dc}-\x{1d714}\x{1d716}-\x{1d74e}\x{1d750}-\x{1d788}\x{1d78a}-\x{1d7c2}\x{1d7c4}-\x{1d7cb}\x{1d800}-\x{1d9ff}\x{1da37}-\x{1da3a}\x{1da6d}-\x{1da74}\x{1da76}-\x{1da83}\x{1da85}-\x{1da8b}\x{1f110}-\x{1f12e}\x{1f130}-\x{1f169}\x{1f170}-\x{1f19a}\x{1f1e6}-\x{1f202}\x{1f210}-\x{1f23a}\x{1f240}-\x{1f248}\x{1f250}\x{1f251}\x{20000}-\x{2a6d6}\x{2a700}-\x{2b734}\x{2b740}-\x{2b81d}\x{2b820}-\x{2cea1}\x{2f800}-\x{2fa1d}\x{f0000}-\x{ffffd}\x{100000}-\x{10fffd}])|([\x{590}\x{5be}\x{5c0}\x{5c3}\x{5c6}\x{5c8}-\x{5ff}\x{7c0}-\x{7ea}\x{7f4}\x{7f5}\x{7fa}-\x{815}\x{81a}\x{824}\x{828}\x{82e}-\x{858}\x{85c}-\x{89f}\x{200f}\x{fb1d}\x{fb1f}-\x{fb28}\x{fb2a}-\x{fb4f}\x{10800}-\x{1091e}\x{10920}-\x{10a00}\x{10a04}\x{10a07}-\x{10a0b}\x{10a10}-\x{10a37}\x{10a3b}-\x{10a3e}\x{10a40}-\x{10ae4}\x{10ae7}-\x{10b38}\x{10b40}-\x{10e5f}\x{10e7f}-\x{10fff}\x{1e800}-\x{1e8cf}\x{1e8d7}-\x{1edff}\x{1ef00}-\x{1efff}\x{608}\x{60b}\x{60d}\x{61b}-\x{64a}\x{66d}-\x{66f}\x{671}-\x{6d5}\x{6e5}\x{6e6}\x{6ee}\x{6ef}\x{6fa}-\x{710}\x{712}-\x{72f}\x{74b}-\x{7a5}\x{7b1}-\x{7bf}\x{8a0}-\x{8e2}\x{fb50}-\x{fd3d}\x{fd40}-\x{fdcf}\x{fdf0}-\x{fdfc}\x{fdfe}\x{fdff}\x{fe70}-\x{fefe}\x{1ee00}-\x{1eeef}\x{1eef2}-\x{1eeff}]))/u';
175  // @codeCoverageIgnoreEnd
176 
183  static function factory( $code ) {
185 
186  if ( isset( $wgDummyLanguageCodes[$code] ) ) {
187  $code = $wgDummyLanguageCodes[$code];
188  }
189 
190  // get the language object to process
191  $langObj = isset( self::$mLangObjCache[$code] )
192  ? self::$mLangObjCache[$code]
193  : self::newFromCode( $code );
194 
195  // merge the language object in to get it up front in the cache
196  self::$mLangObjCache = array_merge( [ $code => $langObj ], self::$mLangObjCache );
197  // get rid of the oldest ones in case we have an overflow
198  self::$mLangObjCache = array_slice( self::$mLangObjCache, 0, $wgLangObjCacheSize, true );
199 
200  return $langObj;
201  }
202 
210  protected static function newFromCode( $code, $fallback = false ) {
211  if ( !self::isValidCode( $code ) ) {
212  throw new MWException( "Invalid language code \"$code\"" );
213  }
214 
215  if ( !self::isValidBuiltInCode( $code ) ) {
216  // It's not possible to customise this code with class files, so
217  // just return a Language object. This is to support uselang= hacks.
218  $lang = new Language;
219  $lang->setCode( $code );
220  return $lang;
221  }
222 
223  // Check if there is a language class for the code
224  $class = self::classFromCode( $code, $fallback );
225  if ( class_exists( $class ) ) {
226  $lang = new $class;
227  return $lang;
228  }
229 
230  // Keep trying the fallback list until we find an existing class
231  $fallbacks = self::getFallbacksFor( $code );
232  foreach ( $fallbacks as $fallbackCode ) {
233  if ( !self::isValidBuiltInCode( $fallbackCode ) ) {
234  throw new MWException( "Invalid fallback '$fallbackCode' in fallback sequence for '$code'" );
235  }
236 
237  $class = self::classFromCode( $fallbackCode );
238  if ( class_exists( $class ) ) {
239  $lang = new $class;
240  $lang->setCode( $code );
241  return $lang;
242  }
243  }
244 
245  throw new MWException( "Invalid fallback sequence for language '$code'" );
246  }
247 
256  public static function isSupportedLanguage( $code ) {
257  if ( !self::isValidBuiltInCode( $code ) ) {
258  return false;
259  }
260 
261  if ( $code === 'qqq' ) {
262  return false;
263  }
264 
265  return is_readable( self::getMessagesFileName( $code ) ) ||
266  is_readable( self::getJsonMessagesFileName( $code ) );
267  }
268 
284  public static function isWellFormedLanguageTag( $code, $lenient = false ) {
285  $alpha = '[a-z]';
286  $digit = '[0-9]';
287  $alphanum = '[a-z0-9]';
288  $x = 'x'; # private use singleton
289  $singleton = '[a-wy-z]'; # other singleton
290  $s = $lenient ? '[-_]' : '-';
291 
292  $language = "$alpha{2,8}|$alpha{2,3}$s$alpha{3}";
293  $script = "$alpha{4}"; # ISO 15924
294  $region = "(?:$alpha{2}|$digit{3})"; # ISO 3166-1 alpha-2 or UN M.49
295  $variant = "(?:$alphanum{5,8}|$digit$alphanum{3})";
296  $extension = "$singleton(?:$s$alphanum{2,8})+";
297  $privateUse = "$x(?:$s$alphanum{1,8})+";
298 
299  # Define certain grandfathered codes, since otherwise the regex is pretty useless.
300  # Since these are limited, this is safe even later changes to the registry --
301  # the only oddity is that it might change the type of the tag, and thus
302  # the results from the capturing groups.
303  # https://www.iana.org/assignments/language-subtag-registry
304 
305  $grandfathered = "en{$s}GB{$s}oed"
306  . "|i{$s}(?:ami|bnn|default|enochian|hak|klingon|lux|mingo|navajo|pwn|tao|tay|tsu)"
307  . "|no{$s}(?:bok|nyn)"
308  . "|sgn{$s}(?:BE{$s}(?:fr|nl)|CH{$s}de)"
309  . "|zh{$s}min{$s}nan";
310 
311  $variantList = "$variant(?:$s$variant)*";
312  $extensionList = "$extension(?:$s$extension)*";
313 
314  $langtag = "(?:($language)"
315  . "(?:$s$script)?"
316  . "(?:$s$region)?"
317  . "(?:$s$variantList)?"
318  . "(?:$s$extensionList)?"
319  . "(?:$s$privateUse)?)";
320 
321  # The final breakdown, with capturing groups for each of these components
322  # The variants, extensions, grandfathered, and private-use may have interior '-'
323 
324  $root = "^(?:$langtag|$privateUse|$grandfathered)$";
325 
326  return (bool)preg_match( "/$root/", strtolower( $code ) );
327  }
328 
338  public static function isValidCode( $code ) {
339  static $cache = [];
340  if ( !isset( $cache[$code] ) ) {
341  // People think language codes are html safe, so enforce it.
342  // Ideally we should only allow a-zA-Z0-9-
343  // but, .+ and other chars are often used for {{int:}} hacks
344  // see bugs T39564, T39587, T38938
345  $cache[$code] =
346  // Protect against path traversal
347  strcspn( $code, ":/\\\000&<>'\"" ) === strlen( $code )
348  && !preg_match( MediaWikiTitleCodec::getTitleInvalidRegex(), $code );
349  }
350  return $cache[$code];
351  }
352 
363  public static function isValidBuiltInCode( $code ) {
364  if ( !is_string( $code ) ) {
365  if ( is_object( $code ) ) {
366  $addmsg = " of class " . get_class( $code );
367  } else {
368  $addmsg = '';
369  }
370  $type = gettype( $code );
371  throw new MWException( __METHOD__ . " must be passed a string, $type given$addmsg" );
372  }
373 
374  return (bool)preg_match( '/^[a-z0-9-]{2,}$/', $code );
375  }
376 
385  public static function isKnownLanguageTag( $tag ) {
386  // Quick escape for invalid input to avoid exceptions down the line
387  // when code tries to process tags which are not valid at all.
388  if ( !self::isValidBuiltInCode( $tag ) ) {
389  return false;
390  }
391 
392  if ( isset( MediaWiki\Languages\Data\Names::$names[$tag] )
393  || self::fetchLanguageName( $tag, $tag ) !== ''
394  ) {
395  return true;
396  }
397 
398  return false;
399  }
400 
406  public static function getLocalisationCache() {
407  if ( is_null( self::$dataCache ) ) {
409  $class = $wgLocalisationCacheConf['class'];
410  self::$dataCache = new $class( $wgLocalisationCacheConf );
411  }
412  return self::$dataCache;
413  }
414 
415  function __construct() {
416  $this->mConverter = new FakeConverter( $this );
417  // Set the code to the name of the descendant
418  if ( static::class === 'Language' ) {
419  $this->mCode = 'en';
420  } else {
421  $this->mCode = str_replace( '_', '-', strtolower( substr( static::class, 8 ) ) );
422  }
423  self::getLocalisationCache();
424  }
425 
429  function __destruct() {
430  foreach ( $this as $name => $value ) {
431  unset( $this->$name );
432  }
433  }
434 
439  function initContLang() {
440  }
441 
446  public function getFallbackLanguages() {
447  return self::getFallbacksFor( $this->mCode );
448  }
449 
454  public function getBookstoreList() {
455  return self::$dataCache->getItem( $this->mCode, 'bookstoreList' );
456  }
457 
464  public function getNamespaces() {
465  if ( is_null( $this->namespaceNames ) ) {
467 
468  $validNamespaces = MWNamespace::getCanonicalNamespaces();
469 
470  $this->namespaceNames = $wgExtraNamespaces +
471  self::$dataCache->getItem( $this->mCode, 'namespaceNames' );
472  $this->namespaceNames += $validNamespaces;
473 
474  $this->namespaceNames[NS_PROJECT] = $wgMetaNamespace;
475  if ( $wgMetaNamespaceTalk ) {
476  $this->namespaceNames[NS_PROJECT_TALK] = $wgMetaNamespaceTalk;
477  } else {
478  $talk = $this->namespaceNames[NS_PROJECT_TALK];
479  $this->namespaceNames[NS_PROJECT_TALK] =
480  $this->fixVariableInNamespace( $talk );
481  }
482 
483  # Sometimes a language will be localised but not actually exist on this wiki.
484  foreach ( $this->namespaceNames as $key => $text ) {
485  if ( !isset( $validNamespaces[$key] ) ) {
486  unset( $this->namespaceNames[$key] );
487  }
488  }
489 
490  # The above mixing may leave namespaces out of canonical order.
491  # Re-order by namespace ID number...
492  ksort( $this->namespaceNames );
493 
494  Hooks::run( 'LanguageGetNamespaces', [ &$this->namespaceNames ] );
495  }
496 
497  return $this->namespaceNames;
498  }
499 
504  public function setNamespaces( array $namespaces ) {
505  $this->namespaceNames = $namespaces;
506  $this->mNamespaceIds = null;
507  }
508 
512  public function resetNamespaces() {
513  $this->namespaceNames = null;
514  $this->mNamespaceIds = null;
515  $this->namespaceAliases = null;
516  }
517 
524  public function getFormattedNamespaces() {
525  $ns = $this->getNamespaces();
526  foreach ( $ns as $k => $v ) {
527  $ns[$k] = strtr( $v, '_', ' ' );
528  }
529  return $ns;
530  }
531 
543  public function getNsText( $index ) {
544  $ns = $this->getNamespaces();
545  return isset( $ns[$index] ) ? $ns[$index] : false;
546  }
547 
561  public function getFormattedNsText( $index ) {
562  $ns = $this->getNsText( $index );
563  return strtr( $ns, '_', ' ' );
564  }
565 
574  public function getGenderNsText( $index, $gender ) {
576 
577  $ns = $wgExtraGenderNamespaces +
578  (array)self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' );
579 
580  return isset( $ns[$index][$gender] ) ? $ns[$index][$gender] : $this->getNsText( $index );
581  }
582 
589  public function needsGenderDistinction() {
591  if ( count( $wgExtraGenderNamespaces ) > 0 ) {
592  // $wgExtraGenderNamespaces overrides everything
593  return true;
594  } elseif ( isset( $wgExtraNamespaces[NS_USER] ) && isset( $wgExtraNamespaces[NS_USER_TALK] ) ) {
596  // $wgExtraNamespaces overrides any gender aliases specified in i18n files
597  return false;
598  } else {
599  // Check what is in i18n files
600  $aliases = self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' );
601  return count( $aliases ) > 0;
602  }
603  }
604 
613  function getLocalNsIndex( $text ) {
614  $lctext = $this->lc( $text );
615  $ids = $this->getNamespaceIds();
616  return isset( $ids[$lctext] ) ? $ids[$lctext] : false;
617  }
618 
622  public function getNamespaceAliases() {
623  if ( is_null( $this->namespaceAliases ) ) {
624  $aliases = self::$dataCache->getItem( $this->mCode, 'namespaceAliases' );
625  if ( !$aliases ) {
626  $aliases = [];
627  } else {
628  foreach ( $aliases as $name => $index ) {
629  if ( $index === NS_PROJECT_TALK ) {
630  unset( $aliases[$name] );
631  $name = $this->fixVariableInNamespace( $name );
632  $aliases[$name] = $index;
633  }
634  }
635  }
636 
638  $genders = $wgExtraGenderNamespaces +
639  (array)self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' );
640  foreach ( $genders as $index => $forms ) {
641  foreach ( $forms as $alias ) {
642  $aliases[$alias] = $index;
643  }
644  }
645 
646  # Also add converted namespace names as aliases, to avoid confusion.
647  $convertedNames = [];
648  foreach ( $this->getVariants() as $variant ) {
649  if ( $variant === $this->mCode ) {
650  continue;
651  }
652  foreach ( $this->getNamespaces() as $ns => $_ ) {
653  $convertedNames[$this->getConverter()->convertNamespace( $ns, $variant )] = $ns;
654  }
655  }
656 
657  $this->namespaceAliases = $aliases + $convertedNames;
658  }
659 
661  }
662 
666  public function getNamespaceIds() {
667  if ( is_null( $this->mNamespaceIds ) ) {
669  # Put namespace names and aliases into a hashtable.
670  # If this is too slow, then we should arrange it so that it is done
671  # before caching. The catch is that at pre-cache time, the above
672  # class-specific fixup hasn't been done.
673  $this->mNamespaceIds = [];
674  foreach ( $this->getNamespaces() as $index => $name ) {
675  $this->mNamespaceIds[$this->lc( $name )] = $index;
676  }
677  foreach ( $this->getNamespaceAliases() as $name => $index ) {
678  $this->mNamespaceIds[$this->lc( $name )] = $index;
679  }
680  if ( $wgNamespaceAliases ) {
681  foreach ( $wgNamespaceAliases as $name => $index ) {
682  $this->mNamespaceIds[$this->lc( $name )] = $index;
683  }
684  }
685  }
686  return $this->mNamespaceIds;
687  }
688 
696  public function getNsIndex( $text ) {
697  $lctext = $this->lc( $text );
698  $ns = MWNamespace::getCanonicalIndex( $lctext );
699  if ( $ns !== null ) {
700  return $ns;
701  }
702  $ids = $this->getNamespaceIds();
703  return isset( $ids[$lctext] ) ? $ids[$lctext] : false;
704  }
705 
713  public function getVariantname( $code, $usemsg = true ) {
714  $msg = "variantname-$code";
715  if ( $usemsg && wfMessage( $msg )->exists() ) {
716  return $this->getMessageFromDB( $msg );
717  }
718  $name = self::fetchLanguageName( $code );
719  if ( $name ) {
720  return $name; # if it's defined as a language name, show that
721  } else {
722  # otherwise, output the language code
723  return $code;
724  }
725  }
726 
730  public function getDatePreferences() {
731  return self::$dataCache->getItem( $this->mCode, 'datePreferences' );
732  }
733 
737  function getDateFormats() {
738  return self::$dataCache->getItem( $this->mCode, 'dateFormats' );
739  }
740 
744  public function getDefaultDateFormat() {
745  $df = self::$dataCache->getItem( $this->mCode, 'defaultDateFormat' );
746  if ( $df === 'dmy or mdy' ) {
747  global $wgAmericanDates;
748  return $wgAmericanDates ? 'mdy' : 'dmy';
749  } else {
750  return $df;
751  }
752  }
753 
757  public function getDatePreferenceMigrationMap() {
758  return self::$dataCache->getItem( $this->mCode, 'datePreferenceMigrationMap' );
759  }
760 
765  function getImageFile( $image ) {
766  return self::$dataCache->getSubitem( $this->mCode, 'imageFiles', $image );
767  }
768 
773  public function getImageFiles() {
774  return self::$dataCache->getItem( $this->mCode, 'imageFiles' );
775  }
776 
780  public function getExtraUserToggles() {
781  return (array)self::$dataCache->getItem( $this->mCode, 'extraUserToggles' );
782  }
783 
788  function getUserToggle( $tog ) {
789  return $this->getMessageFromDB( "tog-$tog" );
790  }
791 
803  public static function fetchLanguageNames( $inLanguage = null, $include = 'mw' ) {
804  $cacheKey = $inLanguage === null ? 'null' : $inLanguage;
805  $cacheKey .= ":$include";
806  if ( self::$languageNameCache === null ) {
807  self::$languageNameCache = new HashBagOStuff( [ 'maxKeys' => 20 ] );
808  }
809 
810  $ret = self::$languageNameCache->get( $cacheKey );
811  if ( !$ret ) {
812  $ret = self::fetchLanguageNamesUncached( $inLanguage, $include );
813  self::$languageNameCache->set( $cacheKey, $ret );
814  }
815  return $ret;
816  }
817 
828  private static function fetchLanguageNamesUncached( $inLanguage = null, $include = 'mw' ) {
829  global $wgExtraLanguageNames, $wgUsePigLatinVariant;
830 
831  // If passed an invalid language code to use, fallback to en
832  if ( $inLanguage !== null && !self::isValidCode( $inLanguage ) ) {
833  $inLanguage = 'en';
834  }
835 
836  $names = [];
837 
838  if ( $inLanguage ) {
839  # TODO: also include when $inLanguage is null, when this code is more efficient
840  Hooks::run( 'LanguageGetTranslatedLanguageNames', [ &$names, $inLanguage ] );
841  }
842 
843  $mwNames = $wgExtraLanguageNames + MediaWiki\Languages\Data\Names::$names;
844  if ( $wgUsePigLatinVariant ) {
845  // Pig Latin (for variant development)
846  $mwNames['en-x-piglatin'] = 'Igpay Atinlay';
847  }
848 
849  foreach ( $mwNames as $mwCode => $mwName ) {
850  # - Prefer own MediaWiki native name when not using the hook
851  # - For other names just add if not added through the hook
852  if ( $mwCode === $inLanguage || !isset( $names[$mwCode] ) ) {
853  $names[$mwCode] = $mwName;
854  }
855  }
856 
857  if ( $include === 'all' ) {
858  ksort( $names );
859  return $names;
860  }
861 
862  $returnMw = [];
863  $coreCodes = array_keys( $mwNames );
864  foreach ( $coreCodes as $coreCode ) {
865  $returnMw[$coreCode] = $names[$coreCode];
866  }
867 
868  if ( $include === 'mwfile' ) {
869  $namesMwFile = [];
870  # We do this using a foreach over the codes instead of a directory
871  # loop so that messages files in extensions will work correctly.
872  foreach ( $returnMw as $code => $value ) {
873  if ( is_readable( self::getMessagesFileName( $code ) )
874  || is_readable( self::getJsonMessagesFileName( $code ) )
875  ) {
876  $namesMwFile[$code] = $names[$code];
877  }
878  }
879 
880  ksort( $namesMwFile );
881  return $namesMwFile;
882  }
883 
884  ksort( $returnMw );
885  # 'mw' option; default if it's not one of the other two options (all/mwfile)
886  return $returnMw;
887  }
888 
896  public static function fetchLanguageName( $code, $inLanguage = null, $include = 'all' ) {
897  $code = strtolower( $code );
898  $array = self::fetchLanguageNames( $inLanguage, $include );
899  return !array_key_exists( $code, $array ) ? '' : $array[$code];
900  }
901 
908  public function getMessageFromDB( $msg ) {
909  return $this->msg( $msg )->text();
910  }
911 
918  protected function msg( $msg ) {
919  return wfMessage( $msg )->inLanguage( $this );
920  }
921 
926  public function getMonthName( $key ) {
927  return $this->getMessageFromDB( self::$mMonthMsgs[$key - 1] );
928  }
929 
933  public function getMonthNamesArray() {
934  $monthNames = [ '' ];
935  for ( $i = 1; $i < 13; $i++ ) {
936  $monthNames[] = $this->getMonthName( $i );
937  }
938  return $monthNames;
939  }
940 
945  public function getMonthNameGen( $key ) {
946  return $this->getMessageFromDB( self::$mMonthGenMsgs[$key - 1] );
947  }
948 
953  public function getMonthAbbreviation( $key ) {
954  return $this->getMessageFromDB( self::$mMonthAbbrevMsgs[$key - 1] );
955  }
956 
960  public function getMonthAbbreviationsArray() {
961  $monthNames = [ '' ];
962  for ( $i = 1; $i < 13; $i++ ) {
963  $monthNames[] = $this->getMonthAbbreviation( $i );
964  }
965  return $monthNames;
966  }
967 
972  public function getWeekdayName( $key ) {
973  return $this->getMessageFromDB( self::$mWeekdayMsgs[$key - 1] );
974  }
975 
981  return $this->getMessageFromDB( self::$mWeekdayAbbrevMsgs[$key - 1] );
982  }
983 
989  return $this->getMessageFromDB( self::$mIranianCalendarMonthMsgs[$key - 1] );
990  }
991 
997  return $this->getMessageFromDB( self::$mHebrewCalendarMonthMsgs[$key - 1] );
998  }
999 
1005  return $this->getMessageFromDB( self::$mHebrewCalendarMonthGenMsgs[$key - 1] );
1006  }
1007 
1013  return $this->getMessageFromDB( self::$mHijriCalendarMonthMsgs[$key - 1] );
1014  }
1015 
1024  private static function dateTimeObjFormat( &$dateTimeObj, $ts, $zone, $code ) {
1025  if ( !$dateTimeObj ) {
1026  $dateTimeObj = DateTime::createFromFormat(
1027  'YmdHis', $ts, $zone ?: new DateTimeZone( 'UTC' )
1028  );
1029  }
1030  return $dateTimeObj->format( $code );
1031  }
1032 
1102  public function sprintfDate( $format, $ts, DateTimeZone $zone = null, &$ttl = 'unused' ) {
1103  $s = '';
1104  $raw = false;
1105  $roman = false;
1106  $hebrewNum = false;
1107  $dateTimeObj = false;
1108  $rawToggle = false;
1109  $iranian = false;
1110  $hebrew = false;
1111  $hijri = false;
1112  $thai = false;
1113  $minguo = false;
1114  $tenno = false;
1115 
1116  $usedSecond = false;
1117  $usedMinute = false;
1118  $usedHour = false;
1119  $usedAMPM = false;
1120  $usedDay = false;
1121  $usedWeek = false;
1122  $usedMonth = false;
1123  $usedYear = false;
1124  $usedISOYear = false;
1125  $usedIsLeapYear = false;
1126 
1127  $usedHebrewMonth = false;
1128  $usedIranianMonth = false;
1129  $usedHijriMonth = false;
1130  $usedHebrewYear = false;
1131  $usedIranianYear = false;
1132  $usedHijriYear = false;
1133  $usedTennoYear = false;
1134 
1135  if ( strlen( $ts ) !== 14 ) {
1136  throw new MWException( __METHOD__ . ": The timestamp $ts should have 14 characters" );
1137  }
1138 
1139  if ( !ctype_digit( $ts ) ) {
1140  throw new MWException( __METHOD__ . ": The timestamp $ts should be a number" );
1141  }
1142 
1143  $formatLength = strlen( $format );
1144  for ( $p = 0; $p < $formatLength; $p++ ) {
1145  $num = false;
1146  $code = $format[$p];
1147  if ( $code == 'x' && $p < $formatLength - 1 ) {
1148  $code .= $format[++$p];
1149  }
1150 
1151  if ( ( $code === 'xi'
1152  || $code === 'xj'
1153  || $code === 'xk'
1154  || $code === 'xm'
1155  || $code === 'xo'
1156  || $code === 'xt' )
1157  && $p < $formatLength - 1 ) {
1158  $code .= $format[++$p];
1159  }
1160 
1161  switch ( $code ) {
1162  case 'xx':
1163  $s .= 'x';
1164  break;
1165  case 'xn':
1166  $raw = true;
1167  break;
1168  case 'xN':
1169  $rawToggle = !$rawToggle;
1170  break;
1171  case 'xr':
1172  $roman = true;
1173  break;
1174  case 'xh':
1175  $hebrewNum = true;
1176  break;
1177  case 'xg':
1178  $usedMonth = true;
1179  $s .= $this->getMonthNameGen( substr( $ts, 4, 2 ) );
1180  break;
1181  case 'xjx':
1182  $usedHebrewMonth = true;
1183  if ( !$hebrew ) {
1184  $hebrew = self::tsToHebrew( $ts );
1185  }
1186  $s .= $this->getHebrewCalendarMonthNameGen( $hebrew[1] );
1187  break;
1188  case 'd':
1189  $usedDay = true;
1190  $num = substr( $ts, 6, 2 );
1191  break;
1192  case 'D':
1193  $usedDay = true;
1194  $s .= $this->getWeekdayAbbreviation(
1195  self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'w' ) + 1
1196  );
1197  break;
1198  case 'j':
1199  $usedDay = true;
1200  $num = intval( substr( $ts, 6, 2 ) );
1201  break;
1202  case 'xij':
1203  $usedDay = true;
1204  if ( !$iranian ) {
1205  $iranian = self::tsToIranian( $ts );
1206  }
1207  $num = $iranian[2];
1208  break;
1209  case 'xmj':
1210  $usedDay = true;
1211  if ( !$hijri ) {
1212  $hijri = self::tsToHijri( $ts );
1213  }
1214  $num = $hijri[2];
1215  break;
1216  case 'xjj':
1217  $usedDay = true;
1218  if ( !$hebrew ) {
1219  $hebrew = self::tsToHebrew( $ts );
1220  }
1221  $num = $hebrew[2];
1222  break;
1223  case 'l':
1224  $usedDay = true;
1225  $s .= $this->getWeekdayName(
1226  self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'w' ) + 1
1227  );
1228  break;
1229  case 'F':
1230  $usedMonth = true;
1231  $s .= $this->getMonthName( substr( $ts, 4, 2 ) );
1232  break;
1233  case 'xiF':
1234  $usedIranianMonth = true;
1235  if ( !$iranian ) {
1236  $iranian = self::tsToIranian( $ts );
1237  }
1238  $s .= $this->getIranianCalendarMonthName( $iranian[1] );
1239  break;
1240  case 'xmF':
1241  $usedHijriMonth = true;
1242  if ( !$hijri ) {
1243  $hijri = self::tsToHijri( $ts );
1244  }
1245  $s .= $this->getHijriCalendarMonthName( $hijri[1] );
1246  break;
1247  case 'xjF':
1248  $usedHebrewMonth = true;
1249  if ( !$hebrew ) {
1250  $hebrew = self::tsToHebrew( $ts );
1251  }
1252  $s .= $this->getHebrewCalendarMonthName( $hebrew[1] );
1253  break;
1254  case 'm':
1255  $usedMonth = true;
1256  $num = substr( $ts, 4, 2 );
1257  break;
1258  case 'M':
1259  $usedMonth = true;
1260  $s .= $this->getMonthAbbreviation( substr( $ts, 4, 2 ) );
1261  break;
1262  case 'n':
1263  $usedMonth = true;
1264  $num = intval( substr( $ts, 4, 2 ) );
1265  break;
1266  case 'xin':
1267  $usedIranianMonth = true;
1268  if ( !$iranian ) {
1269  $iranian = self::tsToIranian( $ts );
1270  }
1271  $num = $iranian[1];
1272  break;
1273  case 'xmn':
1274  $usedHijriMonth = true;
1275  if ( !$hijri ) {
1276  $hijri = self::tsToHijri( $ts );
1277  }
1278  $num = $hijri[1];
1279  break;
1280  case 'xjn':
1281  $usedHebrewMonth = true;
1282  if ( !$hebrew ) {
1283  $hebrew = self::tsToHebrew( $ts );
1284  }
1285  $num = $hebrew[1];
1286  break;
1287  case 'xjt':
1288  $usedHebrewMonth = true;
1289  if ( !$hebrew ) {
1290  $hebrew = self::tsToHebrew( $ts );
1291  }
1292  $num = $hebrew[3];
1293  break;
1294  case 'Y':
1295  $usedYear = true;
1296  $num = substr( $ts, 0, 4 );
1297  break;
1298  case 'xiY':
1299  $usedIranianYear = true;
1300  if ( !$iranian ) {
1301  $iranian = self::tsToIranian( $ts );
1302  }
1303  $num = $iranian[0];
1304  break;
1305  case 'xmY':
1306  $usedHijriYear = true;
1307  if ( !$hijri ) {
1308  $hijri = self::tsToHijri( $ts );
1309  }
1310  $num = $hijri[0];
1311  break;
1312  case 'xjY':
1313  $usedHebrewYear = true;
1314  if ( !$hebrew ) {
1315  $hebrew = self::tsToHebrew( $ts );
1316  }
1317  $num = $hebrew[0];
1318  break;
1319  case 'xkY':
1320  $usedYear = true;
1321  if ( !$thai ) {
1322  $thai = self::tsToYear( $ts, 'thai' );
1323  }
1324  $num = $thai[0];
1325  break;
1326  case 'xoY':
1327  $usedYear = true;
1328  if ( !$minguo ) {
1329  $minguo = self::tsToYear( $ts, 'minguo' );
1330  }
1331  $num = $minguo[0];
1332  break;
1333  case 'xtY':
1334  $usedTennoYear = true;
1335  if ( !$tenno ) {
1336  $tenno = self::tsToYear( $ts, 'tenno' );
1337  }
1338  $num = $tenno[0];
1339  break;
1340  case 'y':
1341  $usedYear = true;
1342  $num = substr( $ts, 2, 2 );
1343  break;
1344  case 'xiy':
1345  $usedIranianYear = true;
1346  if ( !$iranian ) {
1347  $iranian = self::tsToIranian( $ts );
1348  }
1349  $num = substr( $iranian[0], -2 );
1350  break;
1351  case 'xit':
1352  $usedIranianYear = true;
1353  if ( !$iranian ) {
1354  $iranian = self::tsToIranian( $ts );
1355  }
1356  $num = self::$IRANIAN_DAYS[$iranian[1] - 1];
1357  break;
1358  case 'xiz':
1359  $usedIranianYear = true;
1360  if ( !$iranian ) {
1361  $iranian = self::tsToIranian( $ts );
1362  }
1363  $num = $iranian[3];
1364  break;
1365  case 'a':
1366  $usedAMPM = true;
1367  $s .= intval( substr( $ts, 8, 2 ) ) < 12 ? 'am' : 'pm';
1368  break;
1369  case 'A':
1370  $usedAMPM = true;
1371  $s .= intval( substr( $ts, 8, 2 ) ) < 12 ? 'AM' : 'PM';
1372  break;
1373  case 'g':
1374  $usedHour = true;
1375  $h = substr( $ts, 8, 2 );
1376  $num = $h % 12 ? $h % 12 : 12;
1377  break;
1378  case 'G':
1379  $usedHour = true;
1380  $num = intval( substr( $ts, 8, 2 ) );
1381  break;
1382  case 'h':
1383  $usedHour = true;
1384  $h = substr( $ts, 8, 2 );
1385  $num = sprintf( '%02d', $h % 12 ? $h % 12 : 12 );
1386  break;
1387  case 'H':
1388  $usedHour = true;
1389  $num = substr( $ts, 8, 2 );
1390  break;
1391  case 'i':
1392  $usedMinute = true;
1393  $num = substr( $ts, 10, 2 );
1394  break;
1395  case 's':
1396  $usedSecond = true;
1397  $num = substr( $ts, 12, 2 );
1398  break;
1399  case 'c':
1400  case 'r':
1401  $usedSecond = true;
1402  // fall through
1403  case 'e':
1404  case 'O':
1405  case 'P':
1406  case 'T':
1407  $s .= self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1408  break;
1409  case 'w':
1410  case 'N':
1411  case 'z':
1412  $usedDay = true;
1413  $num = self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1414  break;
1415  case 'W':
1416  $usedWeek = true;
1417  $num = self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1418  break;
1419  case 't':
1420  $usedMonth = true;
1421  $num = self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1422  break;
1423  case 'L':
1424  $usedIsLeapYear = true;
1425  $num = self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1426  break;
1427  case 'o':
1428  $usedISOYear = true;
1429  $num = self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1430  break;
1431  case 'U':
1432  $usedSecond = true;
1433  // fall through
1434  case 'I':
1435  case 'Z':
1436  $num = self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1437  break;
1438  case '\\':
1439  # Backslash escaping
1440  if ( $p < $formatLength - 1 ) {
1441  $s .= $format[++$p];
1442  } else {
1443  $s .= '\\';
1444  }
1445  break;
1446  case '"':
1447  # Quoted literal
1448  if ( $p < $formatLength - 1 ) {
1449  $endQuote = strpos( $format, '"', $p + 1 );
1450  if ( $endQuote === false ) {
1451  # No terminating quote, assume literal "
1452  $s .= '"';
1453  } else {
1454  $s .= substr( $format, $p + 1, $endQuote - $p - 1 );
1455  $p = $endQuote;
1456  }
1457  } else {
1458  # Quote at end of string, assume literal "
1459  $s .= '"';
1460  }
1461  break;
1462  default:
1463  $s .= $format[$p];
1464  }
1465  if ( $num !== false ) {
1466  if ( $rawToggle || $raw ) {
1467  $s .= $num;
1468  $raw = false;
1469  } elseif ( $roman ) {
1470  $s .= self::romanNumeral( $num );
1471  $roman = false;
1472  } elseif ( $hebrewNum ) {
1473  $s .= self::hebrewNumeral( $num );
1474  $hebrewNum = false;
1475  } else {
1476  $s .= $this->formatNum( $num, true );
1477  }
1478  }
1479  }
1480 
1481  if ( $ttl === 'unused' ) {
1482  // No need to calculate the TTL, the caller wont use it anyway.
1483  } elseif ( $usedSecond ) {
1484  $ttl = 1;
1485  } elseif ( $usedMinute ) {
1486  $ttl = 60 - substr( $ts, 12, 2 );
1487  } elseif ( $usedHour ) {
1488  $ttl = 3600 - substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
1489  } elseif ( $usedAMPM ) {
1490  $ttl = 43200 - ( substr( $ts, 8, 2 ) % 12 ) * 3600 -
1491  substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
1492  } elseif (
1493  $usedDay ||
1494  $usedHebrewMonth ||
1495  $usedIranianMonth ||
1496  $usedHijriMonth ||
1497  $usedHebrewYear ||
1498  $usedIranianYear ||
1499  $usedHijriYear ||
1500  $usedTennoYear
1501  ) {
1502  // @todo Someone who understands the non-Gregorian calendars
1503  // should write proper logic for them so that they don't need purged every day.
1504  $ttl = 86400 - substr( $ts, 8, 2 ) * 3600 -
1505  substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
1506  } else {
1507  $possibleTtls = [];
1508  $timeRemainingInDay = 86400 - substr( $ts, 8, 2 ) * 3600 -
1509  substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
1510  if ( $usedWeek ) {
1511  $possibleTtls[] =
1512  ( 7 - self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'N' ) ) * 86400 +
1513  $timeRemainingInDay;
1514  } elseif ( $usedISOYear ) {
1515  // December 28th falls on the last ISO week of the year, every year.
1516  // The last ISO week of a year can be 52 or 53.
1517  $lastWeekOfISOYear = DateTime::createFromFormat(
1518  'Ymd',
1519  substr( $ts, 0, 4 ) . '1228',
1520  $zone ?: new DateTimeZone( 'UTC' )
1521  )->format( 'W' );
1522  $currentISOWeek = self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'W' );
1523  $weeksRemaining = $lastWeekOfISOYear - $currentISOWeek;
1524  $timeRemainingInWeek =
1525  ( 7 - self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'N' ) ) * 86400
1526  + $timeRemainingInDay;
1527  $possibleTtls[] = $weeksRemaining * 604800 + $timeRemainingInWeek;
1528  }
1529 
1530  if ( $usedMonth ) {
1531  $possibleTtls[] =
1532  ( self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 't' ) -
1533  substr( $ts, 6, 2 ) ) * 86400
1534  + $timeRemainingInDay;
1535  } elseif ( $usedYear ) {
1536  $possibleTtls[] =
1537  ( self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'L' ) + 364 -
1538  self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'z' ) ) * 86400
1539  + $timeRemainingInDay;
1540  } elseif ( $usedIsLeapYear ) {
1541  $year = substr( $ts, 0, 4 );
1542  $timeRemainingInYear =
1543  ( self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'L' ) + 364 -
1544  self::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'z' ) ) * 86400
1545  + $timeRemainingInDay;
1546  $mod = $year % 4;
1547  if ( $mod || ( !( $year % 100 ) && $year % 400 ) ) {
1548  // this isn't a leap year. see when the next one starts
1549  $nextCandidate = $year - $mod + 4;
1550  if ( $nextCandidate % 100 || !( $nextCandidate % 400 ) ) {
1551  $possibleTtls[] = ( $nextCandidate - $year - 1 ) * 365 * 86400 +
1552  $timeRemainingInYear;
1553  } else {
1554  $possibleTtls[] = ( $nextCandidate - $year + 3 ) * 365 * 86400 +
1555  $timeRemainingInYear;
1556  }
1557  } else {
1558  // this is a leap year, so the next year isn't
1559  $possibleTtls[] = $timeRemainingInYear;
1560  }
1561  }
1562 
1563  if ( $possibleTtls ) {
1564  $ttl = min( $possibleTtls );
1565  }
1566  }
1567 
1568  return $s;
1569  }
1570 
1571  private static $GREG_DAYS = [ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ];
1572  private static $IRANIAN_DAYS = [ 31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29 ];
1573 
1586  private static function tsToIranian( $ts ) {
1587  $gy = substr( $ts, 0, 4 ) - 1600;
1588  $gm = substr( $ts, 4, 2 ) - 1;
1589  $gd = substr( $ts, 6, 2 ) - 1;
1590 
1591  # Days passed from the beginning (including leap years)
1592  $gDayNo = 365 * $gy
1593  + floor( ( $gy + 3 ) / 4 )
1594  - floor( ( $gy + 99 ) / 100 )
1595  + floor( ( $gy + 399 ) / 400 );
1596 
1597  // Add days of the past months of this year
1598  for ( $i = 0; $i < $gm; $i++ ) {
1599  $gDayNo += self::$GREG_DAYS[$i];
1600  }
1601 
1602  // Leap years
1603  if ( $gm > 1 && ( ( $gy % 4 === 0 && $gy % 100 !== 0 || ( $gy % 400 == 0 ) ) ) ) {
1604  $gDayNo++;
1605  }
1606 
1607  // Days passed in current month
1608  $gDayNo += (int)$gd;
1609 
1610  $jDayNo = $gDayNo - 79;
1611 
1612  $jNp = floor( $jDayNo / 12053 );
1613  $jDayNo %= 12053;
1614 
1615  $jy = 979 + 33 * $jNp + 4 * floor( $jDayNo / 1461 );
1616  $jDayNo %= 1461;
1617 
1618  if ( $jDayNo >= 366 ) {
1619  $jy += floor( ( $jDayNo - 1 ) / 365 );
1620  $jDayNo = floor( ( $jDayNo - 1 ) % 365 );
1621  }
1622 
1623  $jz = $jDayNo;
1624 
1625  for ( $i = 0; $i < 11 && $jDayNo >= self::$IRANIAN_DAYS[$i]; $i++ ) {
1626  $jDayNo -= self::$IRANIAN_DAYS[$i];
1627  }
1628 
1629  $jm = $i + 1;
1630  $jd = $jDayNo + 1;
1631 
1632  return [ $jy, $jm, $jd, $jz ];
1633  }
1634 
1646  private static function tsToHijri( $ts ) {
1647  $year = substr( $ts, 0, 4 );
1648  $month = substr( $ts, 4, 2 );
1649  $day = substr( $ts, 6, 2 );
1650 
1651  $zyr = $year;
1652  $zd = $day;
1653  $zm = $month;
1654  $zy = $zyr;
1655 
1656  if (
1657  ( $zy > 1582 ) || ( ( $zy == 1582 ) && ( $zm > 10 ) ) ||
1658  ( ( $zy == 1582 ) && ( $zm == 10 ) && ( $zd > 14 ) )
1659  ) {
1660  $zjd = (int)( ( 1461 * ( $zy + 4800 + (int)( ( $zm - 14 ) / 12 ) ) ) / 4 ) +
1661  (int)( ( 367 * ( $zm - 2 - 12 * ( (int)( ( $zm - 14 ) / 12 ) ) ) ) / 12 ) -
1662  (int)( ( 3 * (int)( ( ( $zy + 4900 + (int)( ( $zm - 14 ) / 12 ) ) / 100 ) ) ) / 4 ) +
1663  $zd - 32075;
1664  } else {
1665  $zjd = 367 * $zy - (int)( ( 7 * ( $zy + 5001 + (int)( ( $zm - 9 ) / 7 ) ) ) / 4 ) +
1666  (int)( ( 275 * $zm ) / 9 ) + $zd + 1729777;
1667  }
1668 
1669  $zl = $zjd - 1948440 + 10632;
1670  $zn = (int)( ( $zl - 1 ) / 10631 );
1671  $zl = $zl - 10631 * $zn + 354;
1672  $zj = ( (int)( ( 10985 - $zl ) / 5316 ) ) * ( (int)( ( 50 * $zl ) / 17719 ) ) +
1673  ( (int)( $zl / 5670 ) ) * ( (int)( ( 43 * $zl ) / 15238 ) );
1674  $zl = $zl - ( (int)( ( 30 - $zj ) / 15 ) ) * ( (int)( ( 17719 * $zj ) / 50 ) ) -
1675  ( (int)( $zj / 16 ) ) * ( (int)( ( 15238 * $zj ) / 43 ) ) + 29;
1676  $zm = (int)( ( 24 * $zl ) / 709 );
1677  $zd = $zl - (int)( ( 709 * $zm ) / 24 );
1678  $zy = 30 * $zn + $zj - 30;
1679 
1680  return [ $zy, $zm, $zd ];
1681  }
1682 
1698  private static function tsToHebrew( $ts ) {
1699  # Parse date
1700  $year = substr( $ts, 0, 4 );
1701  $month = substr( $ts, 4, 2 );
1702  $day = substr( $ts, 6, 2 );
1703 
1704  # Calculate Hebrew year
1705  $hebrewYear = $year + 3760;
1706 
1707  # Month number when September = 1, August = 12
1708  $month += 4;
1709  if ( $month > 12 ) {
1710  # Next year
1711  $month -= 12;
1712  $year++;
1713  $hebrewYear++;
1714  }
1715 
1716  # Calculate day of year from 1 September
1717  $dayOfYear = $day;
1718  for ( $i = 1; $i < $month; $i++ ) {
1719  if ( $i == 6 ) {
1720  # February
1721  $dayOfYear += 28;
1722  # Check if the year is leap
1723  if ( $year % 400 == 0 || ( $year % 4 == 0 && $year % 100 > 0 ) ) {
1724  $dayOfYear++;
1725  }
1726  } elseif ( $i == 8 || $i == 10 || $i == 1 || $i == 3 ) {
1727  $dayOfYear += 30;
1728  } else {
1729  $dayOfYear += 31;
1730  }
1731  }
1732 
1733  # Calculate the start of the Hebrew year
1734  $start = self::hebrewYearStart( $hebrewYear );
1735 
1736  # Calculate next year's start
1737  if ( $dayOfYear <= $start ) {
1738  # Day is before the start of the year - it is the previous year
1739  # Next year's start
1740  $nextStart = $start;
1741  # Previous year
1742  $year--;
1743  $hebrewYear--;
1744  # Add days since previous year's 1 September
1745  $dayOfYear += 365;
1746  if ( ( $year % 400 == 0 ) || ( $year % 100 != 0 && $year % 4 == 0 ) ) {
1747  # Leap year
1748  $dayOfYear++;
1749  }
1750  # Start of the new (previous) year
1751  $start = self::hebrewYearStart( $hebrewYear );
1752  } else {
1753  # Next year's start
1754  $nextStart = self::hebrewYearStart( $hebrewYear + 1 );
1755  }
1756 
1757  # Calculate Hebrew day of year
1758  $hebrewDayOfYear = $dayOfYear - $start;
1759 
1760  # Difference between year's days
1761  $diff = $nextStart - $start;
1762  # Add 12 (or 13 for leap years) days to ignore the difference between
1763  # Hebrew and Gregorian year (353 at least vs. 365/6) - now the
1764  # difference is only about the year type
1765  if ( ( $year % 400 == 0 ) || ( $year % 100 != 0 && $year % 4 == 0 ) ) {
1766  $diff += 13;
1767  } else {
1768  $diff += 12;
1769  }
1770 
1771  # Check the year pattern, and is leap year
1772  # 0 means an incomplete year, 1 means a regular year, 2 means a complete year
1773  # This is mod 30, to work on both leap years (which add 30 days of Adar I)
1774  # and non-leap years
1775  $yearPattern = $diff % 30;
1776  # Check if leap year
1777  $isLeap = $diff >= 30;
1778 
1779  # Calculate day in the month from number of day in the Hebrew year
1780  # Don't check Adar - if the day is not in Adar, we will stop before;
1781  # if it is in Adar, we will use it to check if it is Adar I or Adar II
1782  $hebrewDay = $hebrewDayOfYear;
1783  $hebrewMonth = 1;
1784  $days = 0;
1785  while ( $hebrewMonth <= 12 ) {
1786  # Calculate days in this month
1787  if ( $isLeap && $hebrewMonth == 6 ) {
1788  # Adar in a leap year
1789  if ( $isLeap ) {
1790  # Leap year - has Adar I, with 30 days, and Adar II, with 29 days
1791  $days = 30;
1792  if ( $hebrewDay <= $days ) {
1793  # Day in Adar I
1794  $hebrewMonth = 13;
1795  } else {
1796  # Subtract the days of Adar I
1797  $hebrewDay -= $days;
1798  # Try Adar II
1799  $days = 29;
1800  if ( $hebrewDay <= $days ) {
1801  # Day in Adar II
1802  $hebrewMonth = 14;
1803  }
1804  }
1805  }
1806  } elseif ( $hebrewMonth == 2 && $yearPattern == 2 ) {
1807  # Cheshvan in a complete year (otherwise as the rule below)
1808  $days = 30;
1809  } elseif ( $hebrewMonth == 3 && $yearPattern == 0 ) {
1810  # Kislev in an incomplete year (otherwise as the rule below)
1811  $days = 29;
1812  } else {
1813  # Odd months have 30 days, even have 29
1814  $days = 30 - ( $hebrewMonth - 1 ) % 2;
1815  }
1816  if ( $hebrewDay <= $days ) {
1817  # In the current month
1818  break;
1819  } else {
1820  # Subtract the days of the current month
1821  $hebrewDay -= $days;
1822  # Try in the next month
1823  $hebrewMonth++;
1824  }
1825  }
1826 
1827  return [ $hebrewYear, $hebrewMonth, $hebrewDay, $days ];
1828  }
1829 
1839  private static function hebrewYearStart( $year ) {
1840  $a = intval( ( 12 * ( $year - 1 ) + 17 ) % 19 );
1841  $b = intval( ( $year - 1 ) % 4 );
1842  $m = 32.044093161144 + 1.5542417966212 * $a + $b / 4.0 - 0.0031777940220923 * ( $year - 1 );
1843  if ( $m < 0 ) {
1844  $m--;
1845  }
1846  $Mar = intval( $m );
1847  if ( $m < 0 ) {
1848  $m++;
1849  }
1850  $m -= $Mar;
1851 
1852  $c = intval( ( $Mar + 3 * ( $year - 1 ) + 5 * $b + 5 ) % 7 );
1853  if ( $c == 0 && $a > 11 && $m >= 0.89772376543210 ) {
1854  $Mar++;
1855  } elseif ( $c == 1 && $a > 6 && $m >= 0.63287037037037 ) {
1856  $Mar += 2;
1857  } elseif ( $c == 2 || $c == 4 || $c == 6 ) {
1858  $Mar++;
1859  }
1860 
1861  $Mar += intval( ( $year - 3761 ) / 100 ) - intval( ( $year - 3761 ) / 400 ) - 24;
1862  return $Mar;
1863  }
1864 
1877  private static function tsToYear( $ts, $cName ) {
1878  $gy = substr( $ts, 0, 4 );
1879  $gm = substr( $ts, 4, 2 );
1880  $gd = substr( $ts, 6, 2 );
1881 
1882  if ( !strcmp( $cName, 'thai' ) ) {
1883  # Thai solar dates
1884  # Add 543 years to the Gregorian calendar
1885  # Months and days are identical
1886  $gy_offset = $gy + 543;
1887  } elseif ( ( !strcmp( $cName, 'minguo' ) ) || !strcmp( $cName, 'juche' ) ) {
1888  # Minguo dates
1889  # Deduct 1911 years from the Gregorian calendar
1890  # Months and days are identical
1891  $gy_offset = $gy - 1911;
1892  } elseif ( !strcmp( $cName, 'tenno' ) ) {
1893  # Nengō dates up to Meiji period
1894  # Deduct years from the Gregorian calendar
1895  # depending on the nengo periods
1896  # Months and days are identical
1897  if ( ( $gy < 1912 )
1898  || ( ( $gy == 1912 ) && ( $gm < 7 ) )
1899  || ( ( $gy == 1912 ) && ( $gm == 7 ) && ( $gd < 31 ) )
1900  ) {
1901  # Meiji period
1902  $gy_gannen = $gy - 1868 + 1;
1903  $gy_offset = $gy_gannen;
1904  if ( $gy_gannen == 1 ) {
1905  $gy_offset = '元';
1906  }
1907  $gy_offset = '明治' . $gy_offset;
1908  } elseif (
1909  ( ( $gy == 1912 ) && ( $gm == 7 ) && ( $gd == 31 ) ) ||
1910  ( ( $gy == 1912 ) && ( $gm >= 8 ) ) ||
1911  ( ( $gy > 1912 ) && ( $gy < 1926 ) ) ||
1912  ( ( $gy == 1926 ) && ( $gm < 12 ) ) ||
1913  ( ( $gy == 1926 ) && ( $gm == 12 ) && ( $gd < 26 ) )
1914  ) {
1915  # Taishō period
1916  $gy_gannen = $gy - 1912 + 1;
1917  $gy_offset = $gy_gannen;
1918  if ( $gy_gannen == 1 ) {
1919  $gy_offset = '元';
1920  }
1921  $gy_offset = '大正' . $gy_offset;
1922  } elseif (
1923  ( ( $gy == 1926 ) && ( $gm == 12 ) && ( $gd >= 26 ) ) ||
1924  ( ( $gy > 1926 ) && ( $gy < 1989 ) ) ||
1925  ( ( $gy == 1989 ) && ( $gm == 1 ) && ( $gd < 8 ) )
1926  ) {
1927  # Shōwa period
1928  $gy_gannen = $gy - 1926 + 1;
1929  $gy_offset = $gy_gannen;
1930  if ( $gy_gannen == 1 ) {
1931  $gy_offset = '元';
1932  }
1933  $gy_offset = '昭和' . $gy_offset;
1934  } else {
1935  # Heisei period
1936  $gy_gannen = $gy - 1989 + 1;
1937  $gy_offset = $gy_gannen;
1938  if ( $gy_gannen == 1 ) {
1939  $gy_offset = '元';
1940  }
1941  $gy_offset = '平成' . $gy_offset;
1942  }
1943  } else {
1944  $gy_offset = $gy;
1945  }
1946 
1947  return [ $gy_offset, $gm, $gd ];
1948  }
1949 
1963  private static function strongDirFromContent( $text = '' ) {
1964  if ( !preg_match( self::$strongDirRegex, $text, $matches ) ) {
1965  return null;
1966  }
1967  if ( $matches[1] === '' ) {
1968  return 'rtl';
1969  }
1970  return 'ltr';
1971  }
1972 
1980  static function romanNumeral( $num ) {
1981  static $table = [
1982  [ '', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X' ],
1983  [ '', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC', 'C' ],
1984  [ '', 'C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM', 'M' ],
1985  [ '', 'M', 'MM', 'MMM', 'MMMM', 'MMMMM', 'MMMMMM', 'MMMMMMM',
1986  'MMMMMMMM', 'MMMMMMMMM', 'MMMMMMMMMM' ]
1987  ];
1988 
1989  $num = intval( $num );
1990  if ( $num > 10000 || $num <= 0 ) {
1991  return $num;
1992  }
1993 
1994  $s = '';
1995  for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) {
1996  if ( $num >= $pow10 ) {
1997  $s .= $table[$i][(int)floor( $num / $pow10 )];
1998  }
1999  $num = $num % $pow10;
2000  }
2001  return $s;
2002  }
2003 
2011  static function hebrewNumeral( $num ) {
2012  static $table = [
2013  [ '', 'א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ז', 'ח', 'ט', 'י' ],
2014  [ '', 'י', 'כ', 'ל', 'מ', 'נ', 'ס', 'ע', 'פ', 'צ', 'ק' ],
2015  [ '',
2016  [ 'ק' ],
2017  [ 'ר' ],
2018  [ 'ש' ],
2019  [ 'ת' ],
2020  [ 'ת', 'ק' ],
2021  [ 'ת', 'ר' ],
2022  [ 'ת', 'ש' ],
2023  [ 'ת', 'ת' ],
2024  [ 'ת', 'ת', 'ק' ],
2025  [ 'ת', 'ת', 'ר' ],
2026  ],
2027  [ '', 'א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ז', 'ח', 'ט', 'י' ]
2028  ];
2029 
2030  $num = intval( $num );
2031  if ( $num > 9999 || $num <= 0 ) {
2032  return $num;
2033  }
2034 
2035  // Round thousands have special notations
2036  if ( $num === 1000 ) {
2037  return "א' אלף";
2038  } elseif ( $num % 1000 === 0 ) {
2039  return $table[0][$num / 1000] . "' אלפים";
2040  }
2041 
2042  $letters = [];
2043 
2044  for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) {
2045  if ( $num >= $pow10 ) {
2046  if ( $num === 15 || $num === 16 ) {
2047  $letters[] = $table[0][9];
2048  $letters[] = $table[0][$num - 9];
2049  $num = 0;
2050  } else {
2051  $letters = array_merge(
2052  $letters,
2053  (array)$table[$i][intval( $num / $pow10 )]
2054  );
2055 
2056  if ( $pow10 === 1000 ) {
2057  $letters[] = "'";
2058  }
2059  }
2060  }
2061 
2062  $num = $num % $pow10;
2063  }
2064 
2065  $preTransformLength = count( $letters );
2066  if ( $preTransformLength === 1 ) {
2067  // Add geresh (single quote) to one-letter numbers
2068  $letters[] = "'";
2069  } else {
2070  $lastIndex = $preTransformLength - 1;
2071  $letters[$lastIndex] = str_replace(
2072  [ 'כ', 'מ', 'נ', 'פ', 'צ' ],
2073  [ 'ך', 'ם', 'ן', 'ף', 'ץ' ],
2074  $letters[$lastIndex]
2075  );
2076 
2077  // Add gershayim (double quote) to multiple-letter numbers,
2078  // but exclude numbers with only one letter after the thousands
2079  // (1001-1009, 1020, 1030, 2001-2009, etc.)
2080  if ( $letters[1] === "'" && $preTransformLength === 3 ) {
2081  $letters[] = "'";
2082  } else {
2083  array_splice( $letters, -1, 0, '"' );
2084  }
2085  }
2086 
2087  return implode( $letters );
2088  }
2089 
2098  public function userAdjust( $ts, $tz = false ) {
2099  global $wgUser, $wgLocalTZoffset;
2100 
2101  if ( $tz === false ) {
2102  $tz = $wgUser->getOption( 'timecorrection' );
2103  }
2104 
2105  $data = explode( '|', $tz, 3 );
2106 
2107  if ( $data[0] == 'ZoneInfo' ) {
2108  try {
2109  $userTZ = new DateTimeZone( $data[2] );
2110  $date = new DateTime( $ts, new DateTimeZone( 'UTC' ) );
2111  $date->setTimezone( $userTZ );
2112  return $date->format( 'YmdHis' );
2113  } catch ( Exception $e ) {
2114  // Unrecognized timezone, default to 'Offset' with the stored offset.
2115  $data[0] = 'Offset';
2116  }
2117  }
2118 
2119  if ( $data[0] == 'System' || $tz == '' ) {
2120  # Global offset in minutes.
2121  $minDiff = $wgLocalTZoffset;
2122  } elseif ( $data[0] == 'Offset' ) {
2123  $minDiff = intval( $data[1] );
2124  } else {
2125  $data = explode( ':', $tz );
2126  if ( count( $data ) == 2 ) {
2127  $data[0] = intval( $data[0] );
2128  $data[1] = intval( $data[1] );
2129  $minDiff = abs( $data[0] ) * 60 + $data[1];
2130  if ( $data[0] < 0 ) {
2131  $minDiff = -$minDiff;
2132  }
2133  } else {
2134  $minDiff = intval( $data[0] ) * 60;
2135  }
2136  }
2137 
2138  # No difference ? Return time unchanged
2139  if ( 0 == $minDiff ) {
2140  return $ts;
2141  }
2142 
2143  Wikimedia\suppressWarnings(); // E_STRICT system time bitching
2144  # Generate an adjusted date; take advantage of the fact that mktime
2145  # will normalize out-of-range values so we don't have to split $minDiff
2146  # into hours and minutes.
2147  $t = mktime( (
2148  (int)substr( $ts, 8, 2 ) ), # Hours
2149  (int)substr( $ts, 10, 2 ) + $minDiff, # Minutes
2150  (int)substr( $ts, 12, 2 ), # Seconds
2151  (int)substr( $ts, 4, 2 ), # Month
2152  (int)substr( $ts, 6, 2 ), # Day
2153  (int)substr( $ts, 0, 4 ) ); # Year
2154 
2155  $date = date( 'YmdHis', $t );
2156  Wikimedia\restoreWarnings();
2157 
2158  return $date;
2159  }
2160 
2176  function dateFormat( $usePrefs = true ) {
2177  global $wgUser;
2178 
2179  if ( is_bool( $usePrefs ) ) {
2180  if ( $usePrefs ) {
2181  $datePreference = $wgUser->getDatePreference();
2182  } else {
2183  $datePreference = (string)User::getDefaultOption( 'date' );
2184  }
2185  } else {
2186  $datePreference = (string)$usePrefs;
2187  }
2188 
2189  // return int
2190  if ( $datePreference == '' ) {
2191  return 'default';
2192  }
2193 
2194  return $datePreference;
2195  }
2196 
2207  function getDateFormatString( $type, $pref ) {
2208  $wasDefault = false;
2209  if ( $pref == 'default' ) {
2210  $wasDefault = true;
2211  $pref = $this->getDefaultDateFormat();
2212  }
2213 
2214  if ( !isset( $this->dateFormatStrings[$type][$pref] ) ) {
2215  $df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" );
2216 
2217  if ( $type === 'pretty' && $df === null ) {
2218  $df = $this->getDateFormatString( 'date', $pref );
2219  }
2220 
2221  if ( !$wasDefault && $df === null ) {
2222  $pref = $this->getDefaultDateFormat();
2223  $df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" );
2224  }
2225 
2226  $this->dateFormatStrings[$type][$pref] = $df;
2227  }
2228  return $this->dateFormatStrings[$type][$pref];
2229  }
2230 
2241  public function date( $ts, $adj = false, $format = true, $timecorrection = false ) {
2242  $ts = wfTimestamp( TS_MW, $ts );
2243  if ( $adj ) {
2244  $ts = $this->userAdjust( $ts, $timecorrection );
2245  }
2246  $df = $this->getDateFormatString( 'date', $this->dateFormat( $format ) );
2247  return $this->sprintfDate( $df, $ts );
2248  }
2249 
2260  public function time( $ts, $adj = false, $format = true, $timecorrection = false ) {
2261  $ts = wfTimestamp( TS_MW, $ts );
2262  if ( $adj ) {
2263  $ts = $this->userAdjust( $ts, $timecorrection );
2264  }
2265  $df = $this->getDateFormatString( 'time', $this->dateFormat( $format ) );
2266  return $this->sprintfDate( $df, $ts );
2267  }
2268 
2280  public function timeanddate( $ts, $adj = false, $format = true, $timecorrection = false ) {
2281  $ts = wfTimestamp( TS_MW, $ts );
2282  if ( $adj ) {
2283  $ts = $this->userAdjust( $ts, $timecorrection );
2284  }
2285  $df = $this->getDateFormatString( 'both', $this->dateFormat( $format ) );
2286  return $this->sprintfDate( $df, $ts );
2287  }
2288 
2299  public function formatDuration( $seconds, array $chosenIntervals = [] ) {
2300  $intervals = $this->getDurationIntervals( $seconds, $chosenIntervals );
2301 
2302  $segments = [];
2303 
2304  foreach ( $intervals as $intervalName => $intervalValue ) {
2305  // Messages: duration-seconds, duration-minutes, duration-hours, duration-days, duration-weeks,
2306  // duration-years, duration-decades, duration-centuries, duration-millennia
2307  $message = wfMessage( 'duration-' . $intervalName )->numParams( $intervalValue );
2308  $segments[] = $message->inLanguage( $this )->escaped();
2309  }
2310 
2311  return $this->listToText( $segments );
2312  }
2313 
2325  public function getDurationIntervals( $seconds, array $chosenIntervals = [] ) {
2326  if ( empty( $chosenIntervals ) ) {
2327  $chosenIntervals = [
2328  'millennia',
2329  'centuries',
2330  'decades',
2331  'years',
2332  'days',
2333  'hours',
2334  'minutes',
2335  'seconds'
2336  ];
2337  }
2338 
2339  $intervals = array_intersect_key( self::$durationIntervals, array_flip( $chosenIntervals ) );
2340  $sortedNames = array_keys( $intervals );
2341  $smallestInterval = array_pop( $sortedNames );
2342 
2343  $segments = [];
2344 
2345  foreach ( $intervals as $name => $length ) {
2346  $value = floor( $seconds / $length );
2347 
2348  if ( $value > 0 || ( $name == $smallestInterval && empty( $segments ) ) ) {
2349  $seconds -= $value * $length;
2350  $segments[$name] = $value;
2351  }
2352  }
2353 
2354  return $segments;
2355  }
2356 
2376  private function internalUserTimeAndDate( $type, $ts, User $user, array $options ) {
2377  $ts = wfTimestamp( TS_MW, $ts );
2378  $options += [ 'timecorrection' => true, 'format' => true ];
2379  if ( $options['timecorrection'] !== false ) {
2380  if ( $options['timecorrection'] === true ) {
2381  $offset = $user->getOption( 'timecorrection' );
2382  } else {
2383  $offset = $options['timecorrection'];
2384  }
2385  $ts = $this->userAdjust( $ts, $offset );
2386  }
2387  if ( $options['format'] === true ) {
2388  $format = $user->getDatePreference();
2389  } else {
2390  $format = $options['format'];
2391  }
2392  $df = $this->getDateFormatString( $type, $this->dateFormat( $format ) );
2393  return $this->sprintfDate( $df, $ts );
2394  }
2395 
2415  public function userDate( $ts, User $user, array $options = [] ) {
2416  return $this->internalUserTimeAndDate( 'date', $ts, $user, $options );
2417  }
2418 
2438  public function userTime( $ts, User $user, array $options = [] ) {
2439  return $this->internalUserTimeAndDate( 'time', $ts, $user, $options );
2440  }
2441 
2461  public function userTimeAndDate( $ts, User $user, array $options = [] ) {
2462  return $this->internalUserTimeAndDate( 'both', $ts, $user, $options );
2463  }
2464 
2480  public function getHumanTimestamp(
2481  MWTimestamp $time, MWTimestamp $relativeTo = null, User $user = null
2482  ) {
2483  if ( $relativeTo === null ) {
2484  $relativeTo = new MWTimestamp();
2485  }
2486  if ( $user === null ) {
2487  $user = RequestContext::getMain()->getUser();
2488  }
2489 
2490  // Adjust for the user's timezone.
2491  $offsetThis = $time->offsetForUser( $user );
2492  $offsetRel = $relativeTo->offsetForUser( $user );
2493 
2494  $ts = '';
2495  if ( Hooks::run( 'GetHumanTimestamp', [ &$ts, $time, $relativeTo, $user, $this ] ) ) {
2496  $ts = $this->getHumanTimestampInternal( $time, $relativeTo, $user );
2497  }
2498 
2499  // Reset the timezone on the objects.
2500  $time->timestamp->sub( $offsetThis );
2501  $relativeTo->timestamp->sub( $offsetRel );
2502 
2503  return $ts;
2504  }
2505 
2517  private function getHumanTimestampInternal(
2518  MWTimestamp $ts, MWTimestamp $relativeTo, User $user
2519  ) {
2520  $diff = $ts->diff( $relativeTo );
2521  $diffDay = (bool)( (int)$ts->timestamp->format( 'w' ) -
2522  (int)$relativeTo->timestamp->format( 'w' ) );
2523  $days = $diff->days ?: (int)$diffDay;
2524  if ( $diff->invert || $days > 5
2525  && $ts->timestamp->format( 'Y' ) !== $relativeTo->timestamp->format( 'Y' )
2526  ) {
2527  // Timestamps are in different years: use full timestamp
2528  // Also do full timestamp for future dates
2532  $format = $this->getDateFormatString( 'both', $user->getDatePreference() ?: 'default' );
2533  $ts = $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) );
2534  } elseif ( $days > 5 ) {
2535  // Timestamps are in same year, but more than 5 days ago: show day and month only.
2536  $format = $this->getDateFormatString( 'pretty', $user->getDatePreference() ?: 'default' );
2537  $ts = $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) );
2538  } elseif ( $days > 1 ) {
2539  // Timestamp within the past week: show the day of the week and time
2540  $format = $this->getDateFormatString( 'time', $user->getDatePreference() ?: 'default' );
2541  $weekday = self::$mWeekdayMsgs[$ts->timestamp->format( 'w' )];
2542  // Messages:
2543  // sunday-at, monday-at, tuesday-at, wednesday-at, thursday-at, friday-at, saturday-at
2544  $ts = wfMessage( "$weekday-at" )
2545  ->inLanguage( $this )
2546  ->params( $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) ) )
2547  ->text();
2548  } elseif ( $days == 1 ) {
2549  // Timestamp was yesterday: say 'yesterday' and the time.
2550  $format = $this->getDateFormatString( 'time', $user->getDatePreference() ?: 'default' );
2551  $ts = wfMessage( 'yesterday-at' )
2552  ->inLanguage( $this )
2553  ->params( $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) ) )
2554  ->text();
2555  } elseif ( $diff->h > 1 || $diff->h == 1 && $diff->i > 30 ) {
2556  // Timestamp was today, but more than 90 minutes ago: say 'today' and the time.
2557  $format = $this->getDateFormatString( 'time', $user->getDatePreference() ?: 'default' );
2558  $ts = wfMessage( 'today-at' )
2559  ->inLanguage( $this )
2560  ->params( $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) ) )
2561  ->text();
2562 
2563  // From here on in, the timestamp was soon enough ago so that we can simply say
2564  // XX units ago, e.g., "2 hours ago" or "5 minutes ago"
2565  } elseif ( $diff->h == 1 ) {
2566  // Less than 90 minutes, but more than an hour ago.
2567  $ts = wfMessage( 'hours-ago' )->inLanguage( $this )->numParams( 1 )->text();
2568  } elseif ( $diff->i >= 1 ) {
2569  // A few minutes ago.
2570  $ts = wfMessage( 'minutes-ago' )->inLanguage( $this )->numParams( $diff->i )->text();
2571  } elseif ( $diff->s >= 30 ) {
2572  // Less than a minute, but more than 30 sec ago.
2573  $ts = wfMessage( 'seconds-ago' )->inLanguage( $this )->numParams( $diff->s )->text();
2574  } else {
2575  // Less than 30 seconds ago.
2576  $ts = wfMessage( 'just-now' )->text();
2577  }
2578 
2579  return $ts;
2580  }
2581 
2586  public function getMessage( $key ) {
2587  return self::$dataCache->getSubitem( $this->mCode, 'messages', $key );
2588  }
2589 
2593  function getAllMessages() {
2594  return self::$dataCache->getItem( $this->mCode, 'messages' );
2595  }
2596 
2603  public function iconv( $in, $out, $string ) {
2604  # Even with //IGNORE iconv can whine about illegal characters in
2605  # *input* string. We just ignore those too.
2606  # REF: https://bugs.php.net/bug.php?id=37166
2607  # REF: https://phabricator.wikimedia.org/T18885
2608  Wikimedia\suppressWarnings();
2609  $text = iconv( $in, $out . '//IGNORE', $string );
2610  Wikimedia\restoreWarnings();
2611  return $text;
2612  }
2613 
2614  // callback functions for ucwords(), ucwordbreaks()
2615 
2621  return $this->ucfirst( $matches[1] );
2622  }
2623 
2629  return mb_strtoupper( $matches[0] );
2630  }
2631 
2637  return mb_strtoupper( $matches[0] );
2638  }
2639 
2647  public function ucfirst( $str ) {
2648  $o = ord( $str );
2649  if ( $o < 96 ) { // if already uppercase...
2650  return $str;
2651  } elseif ( $o < 128 ) {
2652  return ucfirst( $str ); // use PHP's ucfirst()
2653  } else {
2654  // fall back to more complex logic in case of multibyte strings
2655  return $this->uc( $str, true );
2656  }
2657  }
2658 
2667  public function uc( $str, $first = false ) {
2668  if ( $first ) {
2669  if ( $this->isMultibyte( $str ) ) {
2670  return mb_strtoupper( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
2671  } else {
2672  return ucfirst( $str );
2673  }
2674  } else {
2675  return $this->isMultibyte( $str ) ? mb_strtoupper( $str ) : strtoupper( $str );
2676  }
2677  }
2678 
2683  function lcfirst( $str ) {
2684  $o = ord( $str );
2685  if ( !$o ) {
2686  return strval( $str );
2687  } elseif ( $o >= 128 ) {
2688  return $this->lc( $str, true );
2689  } elseif ( $o > 96 ) {
2690  return $str;
2691  } else {
2692  $str[0] = strtolower( $str[0] );
2693  return $str;
2694  }
2695  }
2696 
2702  function lc( $str, $first = false ) {
2703  if ( $first ) {
2704  if ( $this->isMultibyte( $str ) ) {
2705  return mb_strtolower( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
2706  } else {
2707  return strtolower( substr( $str, 0, 1 ) ) . substr( $str, 1 );
2708  }
2709  } else {
2710  return $this->isMultibyte( $str ) ? mb_strtolower( $str ) : strtolower( $str );
2711  }
2712  }
2713 
2718  function isMultibyte( $str ) {
2719  return strlen( $str ) !== mb_strlen( $str );
2720  }
2721 
2726  function ucwords( $str ) {
2727  if ( $this->isMultibyte( $str ) ) {
2728  $str = $this->lc( $str );
2729 
2730  // regexp to find first letter in each word (i.e. after each space)
2731  $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)| ([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
2732 
2733  // function to use to capitalize a single char
2734  return preg_replace_callback(
2735  $replaceRegexp,
2736  [ $this, 'ucwordsCallbackMB' ],
2737  $str
2738  );
2739  } else {
2740  return ucwords( strtolower( $str ) );
2741  }
2742  }
2743 
2750  function ucwordbreaks( $str ) {
2751  if ( $this->isMultibyte( $str ) ) {
2752  $str = $this->lc( $str );
2753 
2754  // since \b doesn't work for UTF-8, we explicitely define word break chars
2755  $breaks = "[ \-\(\)\}\{\.,\?!]";
2756 
2757  // find first letter after word break
2758  $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)|" .
2759  "$breaks([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
2760 
2761  return preg_replace_callback(
2762  $replaceRegexp,
2763  [ $this, 'ucwordbreaksCallbackMB' ],
2764  $str
2765  );
2766  } else {
2767  return preg_replace_callback(
2768  '/\b([\w\x80-\xff]+)\b/',
2769  [ $this, 'ucwordbreaksCallbackAscii' ],
2770  $str
2771  );
2772  }
2773  }
2774 
2790  function caseFold( $s ) {
2791  return $this->uc( $s );
2792  }
2793 
2799  function checkTitleEncoding( $s ) {
2800  if ( is_array( $s ) ) {
2801  throw new MWException( 'Given array to checkTitleEncoding.' );
2802  }
2803  if ( StringUtils::isUtf8( $s ) ) {
2804  return $s;
2805  }
2806 
2807  return $this->iconv( $this->fallback8bitEncoding(), 'utf-8', $s );
2808  }
2809 
2814  return self::$dataCache->getItem( $this->mCode, 'fallback8bitEncoding' );
2815  }
2816 
2825  function hasWordBreaks() {
2826  return true;
2827  }
2828 
2836  function segmentByWord( $string ) {
2837  return $string;
2838  }
2839 
2847  function normalizeForSearch( $string ) {
2848  return self::convertDoubleWidth( $string );
2849  }
2850 
2859  protected static function convertDoubleWidth( $string ) {
2860  static $full = null;
2861  static $half = null;
2862 
2863  if ( $full === null ) {
2864  $fullWidth = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
2865  $halfWidth = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
2866  $full = str_split( $fullWidth, 3 );
2867  $half = str_split( $halfWidth );
2868  }
2869 
2870  $string = str_replace( $full, $half, $string );
2871  return $string;
2872  }
2873 
2879  protected static function insertSpace( $string, $pattern ) {
2880  $string = preg_replace( $pattern, " $1 ", $string );
2881  $string = preg_replace( '/ +/', ' ', $string );
2882  return $string;
2883  }
2884 
2889  function convertForSearchResult( $termsArray ) {
2890  # some languages, e.g. Chinese, need to do a conversion
2891  # in order for search results to be displayed correctly
2892  return $termsArray;
2893  }
2894 
2901  function firstChar( $s ) {
2902  $matches = [];
2903  preg_match(
2904  '/^([\x00-\x7f]|[\xc0-\xdf][\x80-\xbf]|' .
2905  '[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xf7][\x80-\xbf]{3})/',
2906  $s,
2907  $matches
2908  );
2909 
2910  if ( isset( $matches[1] ) ) {
2911  if ( strlen( $matches[1] ) != 3 ) {
2912  return $matches[1];
2913  }
2914 
2915  // Break down Hangul syllables to grab the first jamo
2917  if ( $code < 0xac00 || 0xd7a4 <= $code ) {
2918  return $matches[1];
2919  } elseif ( $code < 0xb098 ) {
2920  return "\xe3\x84\xb1";
2921  } elseif ( $code < 0xb2e4 ) {
2922  return "\xe3\x84\xb4";
2923  } elseif ( $code < 0xb77c ) {
2924  return "\xe3\x84\xb7";
2925  } elseif ( $code < 0xb9c8 ) {
2926  return "\xe3\x84\xb9";
2927  } elseif ( $code < 0xbc14 ) {
2928  return "\xe3\x85\x81";
2929  } elseif ( $code < 0xc0ac ) {
2930  return "\xe3\x85\x82";
2931  } elseif ( $code < 0xc544 ) {
2932  return "\xe3\x85\x85";
2933  } elseif ( $code < 0xc790 ) {
2934  return "\xe3\x85\x87";
2935  } elseif ( $code < 0xcc28 ) {
2936  return "\xe3\x85\x88";
2937  } elseif ( $code < 0xce74 ) {
2938  return "\xe3\x85\x8a";
2939  } elseif ( $code < 0xd0c0 ) {
2940  return "\xe3\x85\x8b";
2941  } elseif ( $code < 0xd30c ) {
2942  return "\xe3\x85\x8c";
2943  } elseif ( $code < 0xd558 ) {
2944  return "\xe3\x85\x8d";
2945  } else {
2946  return "\xe3\x85\x8e";
2947  }
2948  } else {
2949  return '';
2950  }
2951  }
2952 
2956  function initEncoding() {
2957  // No-op.
2958  }
2959 
2965  function recodeForEdit( $s ) {
2966  return $s;
2967  }
2968 
2974  function recodeInput( $s ) {
2975  return $s;
2976  }
2977 
2989  function normalize( $s ) {
2991  $s = UtfNormal\Validator::cleanUp( $s );
2992  if ( $wgAllUnicodeFixes ) {
2993  $s = $this->transformUsingPairFile( 'normalize-ar.ser', $s );
2994  $s = $this->transformUsingPairFile( 'normalize-ml.ser', $s );
2995  }
2996 
2997  return $s;
2998  }
2999 
3014  function transformUsingPairFile( $file, $string ) {
3015  if ( !isset( $this->transformData[$file] ) ) {
3016  $data = wfGetPrecompiledData( $file );
3017  if ( $data === false ) {
3018  throw new MWException( __METHOD__ . ": The transformation file $file is missing" );
3019  }
3020  $this->transformData[$file] = new ReplacementArray( $data );
3021  }
3022  return $this->transformData[$file]->replace( $string );
3023  }
3024 
3030  function isRTL() {
3031  return self::$dataCache->getItem( $this->mCode, 'rtl' );
3032  }
3033 
3038  function getDir() {
3039  return $this->isRTL() ? 'rtl' : 'ltr';
3040  }
3041 
3050  function alignStart() {
3051  return $this->isRTL() ? 'right' : 'left';
3052  }
3053 
3062  function alignEnd() {
3063  return $this->isRTL() ? 'left' : 'right';
3064  }
3065 
3077  function getDirMarkEntity( $opposite = false ) {
3078  if ( $opposite ) {
3079  return $this->isRTL() ? '&lrm;' : '&rlm;';
3080  }
3081  return $this->isRTL() ? '&rlm;' : '&lrm;';
3082  }
3083 
3094  function getDirMark( $opposite = false ) {
3095  $lrm = "\xE2\x80\x8E"; # LEFT-TO-RIGHT MARK, commonly abbreviated LRM
3096  $rlm = "\xE2\x80\x8F"; # RIGHT-TO-LEFT MARK, commonly abbreviated RLM
3097  if ( $opposite ) {
3098  return $this->isRTL() ? $lrm : $rlm;
3099  }
3100  return $this->isRTL() ? $rlm : $lrm;
3101  }
3102 
3106  function capitalizeAllNouns() {
3107  return self::$dataCache->getItem( $this->mCode, 'capitalizeAllNouns' );
3108  }
3109 
3117  function getArrow( $direction = 'forwards' ) {
3118  switch ( $direction ) {
3119  case 'forwards':
3120  return $this->isRTL() ? '←' : '→';
3121  case 'backwards':
3122  return $this->isRTL() ? '→' : '←';
3123  case 'left':
3124  return '←';
3125  case 'right':
3126  return '→';
3127  case 'up':
3128  return '↑';
3129  case 'down':
3130  return '↓';
3131  }
3132  }
3133 
3139  function linkPrefixExtension() {
3140  return self::$dataCache->getItem( $this->mCode, 'linkPrefixExtension' );
3141  }
3142 
3147  function getMagicWords() {
3148  return self::$dataCache->getItem( $this->mCode, 'magicWords' );
3149  }
3150 
3154  protected function doMagicHook() {
3155  if ( $this->mMagicHookDone ) {
3156  return;
3157  }
3158  $this->mMagicHookDone = true;
3159  Hooks::run( 'LanguageGetMagic', [ &$this->mMagicExtensions, $this->getCode() ] );
3160  }
3161 
3167  function getMagic( $mw ) {
3168  // Saves a function call
3169  if ( !$this->mMagicHookDone ) {
3170  $this->doMagicHook();
3171  }
3172 
3173  if ( isset( $this->mMagicExtensions[$mw->mId] ) ) {
3174  $rawEntry = $this->mMagicExtensions[$mw->mId];
3175  } else {
3176  $rawEntry = self::$dataCache->getSubitem(
3177  $this->mCode, 'magicWords', $mw->mId );
3178  }
3179 
3180  if ( !is_array( $rawEntry ) ) {
3181  wfWarn( "\"$rawEntry\" is not a valid magic word for \"$mw->mId\"" );
3182  } else {
3183  $mw->mCaseSensitive = $rawEntry[0];
3184  $mw->mSynonyms = array_slice( $rawEntry, 1 );
3185  }
3186  }
3187 
3193  function addMagicWordsByLang( $newWords ) {
3194  $fallbackChain = $this->getFallbackLanguages();
3195  $fallbackChain = array_reverse( $fallbackChain );
3196  foreach ( $fallbackChain as $code ) {
3197  if ( isset( $newWords[$code] ) ) {
3198  $this->mMagicExtensions = $newWords[$code] + $this->mMagicExtensions;
3199  }
3200  }
3201  }
3202 
3209  // Cache aliases because it may be slow to load them
3210  if ( is_null( $this->mExtendedSpecialPageAliases ) ) {
3211  // Initialise array
3212  $this->mExtendedSpecialPageAliases =
3213  self::$dataCache->getItem( $this->mCode, 'specialPageAliases' );
3214  Hooks::run( 'LanguageGetSpecialPageAliases',
3215  [ &$this->mExtendedSpecialPageAliases, $this->getCode() ] );
3216  }
3217 
3219  }
3220 
3227  function emphasize( $text ) {
3228  return "<em>$text</em>";
3229  }
3230 
3253  public function formatNum( $number, $nocommafy = false ) {
3255  if ( !$nocommafy ) {
3256  $number = $this->commafy( $number );
3257  $s = $this->separatorTransformTable();
3258  if ( $s ) {
3259  $number = strtr( $number, $s );
3260  }
3261  }
3262 
3263  if ( $wgTranslateNumerals ) {
3264  $s = $this->digitTransformTable();
3265  if ( $s ) {
3266  $number = strtr( $number, $s );
3267  }
3268  }
3269 
3270  return (string)$number;
3271  }
3272 
3281  public function formatNumNoSeparators( $number ) {
3282  return $this->formatNum( $number, true );
3283  }
3284 
3289  public function parseFormattedNumber( $number ) {
3290  $s = $this->digitTransformTable();
3291  if ( $s ) {
3292  // eliminate empty array values such as ''. (T66347)
3293  $s = array_filter( $s );
3294  $number = strtr( $number, array_flip( $s ) );
3295  }
3296 
3297  $s = $this->separatorTransformTable();
3298  if ( $s ) {
3299  // eliminate empty array values such as ''. (T66347)
3300  $s = array_filter( $s );
3301  $number = strtr( $number, array_flip( $s ) );
3302  }
3303 
3304  $number = strtr( $number, [ ',' => '' ] );
3305  return $number;
3306  }
3307 
3314  function commafy( $number ) {
3317  if ( $number === null ) {
3318  return '';
3319  }
3320 
3321  if ( !$digitGroupingPattern || $digitGroupingPattern === "###,###,###" ) {
3322  // Default grouping is at thousands, use the same for ###,###,### pattern too.
3323  // In some languages it's conventional not to insert a thousands separator
3324  // in numbers that are four digits long (1000-9999).
3325  if ( $minimumGroupingDigits ) {
3326  // Number of '#' characters after last comma in the grouping pattern.
3327  // The pattern is hardcoded here, but this would vary for different patterns.
3328  $primaryGroupingSize = 3;
3329  // Maximum length of a number to suppress digit grouping for.
3330  $maximumLength = $minimumGroupingDigits + $primaryGroupingSize - 1;
3331  if ( preg_match( '/^\-?\d{1,' . $maximumLength . '}(\.\d+)?$/', $number ) ) {
3332  return $number;
3333  }
3334  }
3335  return strrev( (string)preg_replace( '/(\d{3})(?=\d)(?!\d*\.)/', '$1,', strrev( $number ) ) );
3336  } else {
3337  // Ref: http://cldr.unicode.org/translation/number-patterns
3338  $sign = "";
3339  if ( intval( $number ) < 0 ) {
3340  // For negative numbers apply the algorithm like positive number and add sign.
3341  $sign = "-";
3342  $number = substr( $number, 1 );
3343  }
3344  $integerPart = [];
3345  $decimalPart = [];
3346  $numMatches = preg_match_all( "/(#+)/", $digitGroupingPattern, $matches );
3347  preg_match( "/\d+/", $number, $integerPart );
3348  preg_match( "/\.\d*/", $number, $decimalPart );
3349  $groupedNumber = ( count( $decimalPart ) > 0 ) ? $decimalPart[0] : "";
3350  if ( $groupedNumber === $number ) {
3351  // the string does not have any number part. Eg: .12345
3352  return $sign . $groupedNumber;
3353  }
3354  $start = $end = ( $integerPart ) ? strlen( $integerPart[0] ) : 0;
3355  while ( $start > 0 ) {
3356  $match = $matches[0][$numMatches - 1];
3357  $matchLen = strlen( $match );
3358  $start = $end - $matchLen;
3359  if ( $start < 0 ) {
3360  $start = 0;
3361  }
3362  $groupedNumber = substr( $number, $start, $end - $start ) . $groupedNumber;
3363  $end = $start;
3364  if ( $numMatches > 1 ) {
3365  // use the last pattern for the rest of the number
3366  $numMatches--;
3367  }
3368  if ( $start > 0 ) {
3369  $groupedNumber = "," . $groupedNumber;
3370  }
3371  }
3372  return $sign . $groupedNumber;
3373  }
3374  }
3375 
3380  return self::$dataCache->getItem( $this->mCode, 'digitGroupingPattern' );
3381  }
3382 
3386  function digitTransformTable() {
3387  return self::$dataCache->getItem( $this->mCode, 'digitTransformTable' );
3388  }
3389 
3394  return self::$dataCache->getItem( $this->mCode, 'separatorTransformTable' );
3395  }
3396 
3401  return self::$dataCache->getItem( $this->mCode, 'minimumGroupingDigits' );
3402  }
3403 
3413  function listToText( array $l ) {
3414  $m = count( $l ) - 1;
3415  if ( $m < 0 ) {
3416  return '';
3417  }
3418  if ( $m > 0 ) {
3419  $and = $this->msg( 'and' )->escaped();
3420  $space = $this->msg( 'word-separator' )->escaped();
3421  if ( $m > 1 ) {
3422  $comma = $this->msg( 'comma-separator' )->escaped();
3423  }
3424  }
3425  $s = $l[$m];
3426  for ( $i = $m - 1; $i >= 0; $i-- ) {
3427  if ( $i == $m - 1 ) {
3428  $s = $l[$i] . $and . $space . $s;
3429  } else {
3430  $s = $l[$i] . $comma . $s;
3431  }
3432  }
3433  return $s;
3434  }
3435 
3442  function commaList( array $list ) {
3443  return implode(
3444  wfMessage( 'comma-separator' )->inLanguage( $this )->escaped(),
3445  $list
3446  );
3447  }
3448 
3455  function semicolonList( array $list ) {
3456  return implode(
3457  wfMessage( 'semicolon-separator' )->inLanguage( $this )->escaped(),
3458  $list
3459  );
3460  }
3461 
3467  function pipeList( array $list ) {
3468  return implode(
3469  wfMessage( 'pipe-separator' )->inLanguage( $this )->escaped(),
3470  $list
3471  );
3472  }
3473 
3491  function truncate( $string, $length, $ellipsis = '...', $adjustLength = true ) {
3492  return $this->truncateForDatabase( $string, $length, $ellipsis, $adjustLength );
3493  }
3494 
3510  function truncateForDatabase( $string, $length, $ellipsis = '...', $adjustLength = true ) {
3511  return $this->truncateInternal(
3512  $string, $length, $ellipsis, $adjustLength, 'strlen', 'substr'
3513  );
3514  }
3515 
3534  function truncateForVisual( $string, $length, $ellipsis = '...', $adjustLength = true ) {
3535  // Passing encoding to mb_strlen and mb_substr is optional.
3536  // Encoding defaults to mb_internal_encoding(), which is set to UTF-8 in Setup.php, so
3537  // explicit specification of encoding is skipped.
3538  // Note: Both multibyte methods are callables invoked in truncateInternal.
3539  return $this->truncateInternal(
3540  $string, $length, $ellipsis, $adjustLength, 'mb_strlen', 'mb_substr'
3541  );
3542  }
3543 
3560  private function truncateInternal(
3561  $string, $length, $ellipsis = '...', $adjustLength = true, $measureLength, $getSubstring
3562  ) {
3563  if ( !is_callable( $measureLength ) || !is_callable( $getSubstring ) ) {
3564  throw new InvalidArgumentException( 'Invalid callback provided' );
3565  }
3566 
3567  # Check if there is no need to truncate
3568  if ( $measureLength( $string ) <= abs( $length ) ) {
3569  return $string; // no need to truncate
3570  }
3571 
3572  # Use the localized ellipsis character
3573  if ( $ellipsis == '...' ) {
3574  $ellipsis = wfMessage( 'ellipsis' )->inLanguage( $this )->escaped();
3575  }
3576  if ( $length == 0 ) {
3577  return $ellipsis; // convention
3578  }
3579 
3580  $stringOriginal = $string;
3581  # If ellipsis length is >= $length then we can't apply $adjustLength
3582  if ( $adjustLength && $measureLength( $ellipsis ) >= abs( $length ) ) {
3583  $string = $ellipsis; // this can be slightly unexpected
3584  # Otherwise, truncate and add ellipsis...
3585  } else {
3586  $ellipsisLength = $adjustLength ? $measureLength( $ellipsis ) : 0;
3587  if ( $length > 0 ) {
3588  $length -= $ellipsisLength;
3589  $string = $getSubstring( $string, 0, $length ); // xyz...
3590  $string = $this->removeBadCharLast( $string );
3591  $string = rtrim( $string );
3592  $string = $string . $ellipsis;
3593  } else {
3594  $length += $ellipsisLength;
3595  $string = $getSubstring( $string, $length ); // ...xyz
3596  $string = $this->removeBadCharFirst( $string );
3597  $string = ltrim( $string );
3598  $string = $ellipsis . $string;
3599  }
3600  }
3601 
3602  # Do not truncate if the ellipsis makes the string longer/equal (T24181).
3603  # This check is *not* redundant if $adjustLength, due to the single case where
3604  # LEN($ellipsis) > ABS($limit arg); $stringOriginal could be shorter than $string.
3605  if ( $measureLength( $string ) < $measureLength( $stringOriginal ) ) {
3606  return $string;
3607  } else {
3608  return $stringOriginal;
3609  }
3610  }
3611 
3619  protected function removeBadCharLast( $string ) {
3620  if ( $string != '' ) {
3621  $char = ord( $string[strlen( $string ) - 1] );
3622  $m = [];
3623  if ( $char >= 0xc0 ) {
3624  # We got the first byte only of a multibyte char; remove it.
3625  $string = substr( $string, 0, -1 );
3626  } elseif ( $char >= 0x80 &&
3627  // Use the /s modifier (PCRE_DOTALL) so (.*) also matches newlines
3628  preg_match( '/^(.*)(?:[\xe0-\xef][\x80-\xbf]|' .
3629  '[\xf0-\xf7][\x80-\xbf]{1,2})$/s', $string, $m )
3630  ) {
3631  # We chopped in the middle of a character; remove it
3632  $string = $m[1];
3633  }
3634  }
3635  return $string;
3636  }
3637 
3645  protected function removeBadCharFirst( $string ) {
3646  if ( $string != '' ) {
3647  $char = ord( $string[0] );
3648  if ( $char >= 0x80 && $char < 0xc0 ) {
3649  # We chopped in the middle of a character; remove the whole thing
3650  $string = preg_replace( '/^[\x80-\xbf]+/', '', $string );
3651  }
3652  }
3653  return $string;
3654  }
3655 
3671  function truncateHtml( $text, $length, $ellipsis = '...' ) {
3672  # Use the localized ellipsis character
3673  if ( $ellipsis == '...' ) {
3674  $ellipsis = wfMessage( 'ellipsis' )->inLanguage( $this )->escaped();
3675  }
3676  # Check if there is clearly no need to truncate
3677  if ( $length <= 0 ) {
3678  return $ellipsis; // no text shown, nothing to format (convention)
3679  } elseif ( strlen( $text ) <= $length ) {
3680  return $text; // string short enough even *with* HTML (short-circuit)
3681  }
3682 
3683  $dispLen = 0; // innerHTML legth so far
3684  $testingEllipsis = false; // checking if ellipses will make string longer/equal?
3685  $tagType = 0; // 0-open, 1-close
3686  $bracketState = 0; // 1-tag start, 2-tag name, 0-neither
3687  $entityState = 0; // 0-not entity, 1-entity
3688  $tag = $ret = ''; // accumulated tag name, accumulated result string
3689  $openTags = []; // open tag stack
3690  $maybeState = null; // possible truncation state
3691 
3692  $textLen = strlen( $text );
3693  $neLength = max( 0, $length - strlen( $ellipsis ) ); // non-ellipsis len if truncated
3694  for ( $pos = 0; true; ++$pos ) {
3695  # Consider truncation once the display length has reached the maximim.
3696  # We check if $dispLen > 0 to grab tags for the $neLength = 0 case.
3697  # Check that we're not in the middle of a bracket/entity...
3698  if ( $dispLen && $dispLen >= $neLength && $bracketState == 0 && !$entityState ) {
3699  if ( !$testingEllipsis ) {
3700  $testingEllipsis = true;
3701  # Save where we are; we will truncate here unless there turn out to
3702  # be so few remaining characters that truncation is not necessary.
3703  if ( !$maybeState ) { // already saved? ($neLength = 0 case)
3704  $maybeState = [ $ret, $openTags ]; // save state
3705  }
3706  } elseif ( $dispLen > $length && $dispLen > strlen( $ellipsis ) ) {
3707  # String in fact does need truncation, the truncation point was OK.
3708  list( $ret, $openTags ) = $maybeState; // reload state
3709  $ret = $this->removeBadCharLast( $ret ); // multi-byte char fix
3710  $ret .= $ellipsis; // add ellipsis
3711  break;
3712  }
3713  }
3714  if ( $pos >= $textLen ) {
3715  break; // extra iteration just for above checks
3716  }
3717 
3718  # Read the next char...
3719  $ch = $text[$pos];
3720  $lastCh = $pos ? $text[$pos - 1] : '';
3721  $ret .= $ch; // add to result string
3722  if ( $ch == '<' ) {
3723  $this->truncate_endBracket( $tag, $tagType, $lastCh, $openTags ); // for bad HTML
3724  $entityState = 0; // for bad HTML
3725  $bracketState = 1; // tag started (checking for backslash)
3726  } elseif ( $ch == '>' ) {
3727  $this->truncate_endBracket( $tag, $tagType, $lastCh, $openTags );
3728  $entityState = 0; // for bad HTML
3729  $bracketState = 0; // out of brackets
3730  } elseif ( $bracketState == 1 ) {
3731  if ( $ch == '/' ) {
3732  $tagType = 1; // close tag (e.g. "</span>")
3733  } else {
3734  $tagType = 0; // open tag (e.g. "<span>")
3735  $tag .= $ch;
3736  }
3737  $bracketState = 2; // building tag name
3738  } elseif ( $bracketState == 2 ) {
3739  if ( $ch != ' ' ) {
3740  $tag .= $ch;
3741  } else {
3742  // Name found (e.g. "<a href=..."), add on tag attributes...
3743  $pos += $this->truncate_skip( $ret, $text, "<>", $pos + 1 );
3744  }
3745  } elseif ( $bracketState == 0 ) {
3746  if ( $entityState ) {
3747  if ( $ch == ';' ) {
3748  $entityState = 0;
3749  $dispLen++; // entity is one displayed char
3750  }
3751  } else {
3752  if ( $neLength == 0 && !$maybeState ) {
3753  // Save state without $ch. We want to *hit* the first
3754  // display char (to get tags) but not *use* it if truncating.
3755  $maybeState = [ substr( $ret, 0, -1 ), $openTags ];
3756  }
3757  if ( $ch == '&' ) {
3758  $entityState = 1; // entity found, (e.g. "&#160;")
3759  } else {
3760  $dispLen++; // this char is displayed
3761  // Add the next $max display text chars after this in one swoop...
3762  $max = ( $testingEllipsis ? $length : $neLength ) - $dispLen;
3763  $skipped = $this->truncate_skip( $ret, $text, "<>&", $pos + 1, $max );
3764  $dispLen += $skipped;
3765  $pos += $skipped;
3766  }
3767  }
3768  }
3769  }
3770  // Close the last tag if left unclosed by bad HTML
3771  $this->truncate_endBracket( $tag, $text[$textLen - 1], $tagType, $openTags );
3772  while ( count( $openTags ) > 0 ) {
3773  $ret .= '</' . array_pop( $openTags ) . '>'; // close open tags
3774  }
3775  return $ret;
3776  }
3777 
3789  private function truncate_skip( &$ret, $text, $search, $start, $len = null ) {
3790  if ( $len === null ) {
3791  $len = -1; // -1 means "no limit" for strcspn
3792  } elseif ( $len < 0 ) {
3793  $len = 0; // sanity
3794  }
3795  $skipCount = 0;
3796  if ( $start < strlen( $text ) ) {
3797  $skipCount = strcspn( $text, $search, $start, $len );
3798  $ret .= substr( $text, $start, $skipCount );
3799  }
3800  return $skipCount;
3801  }
3802 
3812  private function truncate_endBracket( &$tag, $tagType, $lastCh, &$openTags ) {
3813  $tag = ltrim( $tag );
3814  if ( $tag != '' ) {
3815  if ( $tagType == 0 && $lastCh != '/' ) {
3816  $openTags[] = $tag; // tag opened (didn't close itself)
3817  } elseif ( $tagType == 1 ) {
3818  if ( $openTags && $tag == $openTags[count( $openTags ) - 1] ) {
3819  array_pop( $openTags ); // tag closed
3820  }
3821  }
3822  $tag = '';
3823  }
3824  }
3825 
3834  function convertGrammar( $word, $case ) {
3836  if ( isset( $wgGrammarForms[$this->getCode()][$case][$word] ) ) {
3837  return $wgGrammarForms[$this->getCode()][$case][$word];
3838  }
3839 
3840  $grammarTransformations = $this->getGrammarTransformations();
3841 
3842  if ( isset( $grammarTransformations[$case] ) ) {
3843  $forms = $grammarTransformations[$case];
3844 
3845  // Some names of grammar rules are aliases for other rules.
3846  // In such cases the value is a string rather than object,
3847  // so load the actual rules.
3848  if ( is_string( $forms ) ) {
3849  $forms = $grammarTransformations[$forms];
3850  }
3851 
3852  foreach ( array_values( $forms ) as $rule ) {
3853  $form = $rule[0];
3854 
3855  if ( $form === '@metadata' ) {
3856  continue;
3857  }
3858 
3859  $replacement = $rule[1];
3860 
3861  $regex = '/' . addcslashes( $form, '/' ) . '/u';
3862  $patternMatches = preg_match( $regex, $word );
3863 
3864  if ( $patternMatches === false ) {
3865  wfLogWarning(
3866  'An error occurred while processing grammar. ' .
3867  "Word: '$word'. Regex: /$form/."
3868  );
3869  } elseif ( $patternMatches === 1 ) {
3870  $word = preg_replace( $regex, $replacement, $word );
3871 
3872  break;
3873  }
3874  }
3875  }
3876 
3877  return $word;
3878  }
3879 
3885  function getGrammarForms() {
3887  if ( isset( $wgGrammarForms[$this->getCode()] )
3888  && is_array( $wgGrammarForms[$this->getCode()] )
3889  ) {
3890  return $wgGrammarForms[$this->getCode()];
3891  }
3892 
3893  return [];
3894  }
3895 
3905  public function getGrammarTransformations() {
3906  $languageCode = $this->getCode();
3907 
3908  if ( self::$grammarTransformations === null ) {
3909  self::$grammarTransformations = new MapCacheLRU( 10 );
3910  }
3911 
3912  if ( self::$grammarTransformations->has( $languageCode ) ) {
3913  return self::$grammarTransformations->get( $languageCode );
3914  }
3915 
3916  $data = [];
3917 
3918  $grammarDataFile = __DIR__ . "/data/grammarTransformations/$languageCode.json";
3919  if ( is_readable( $grammarDataFile ) ) {
3920  $data = FormatJson::decode(
3921  file_get_contents( $grammarDataFile ),
3922  true
3923  );
3924 
3925  if ( $data === null ) {
3926  throw new MWException( "Invalid grammar data for \"$languageCode\"." );
3927  }
3928 
3929  self::$grammarTransformations->set( $languageCode, $data );
3930  }
3931 
3932  return $data;
3933  }
3934 
3954  function gender( $gender, $forms ) {
3955  if ( !count( $forms ) ) {
3956  return '';
3957  }
3958  $forms = $this->preConvertPlural( $forms, 2 );
3959  if ( $gender === 'male' ) {
3960  return $forms[0];
3961  }
3962  if ( $gender === 'female' ) {
3963  return $forms[1];
3964  }
3965  return isset( $forms[2] ) ? $forms[2] : $forms[0];
3966  }
3967 
3983  function convertPlural( $count, $forms ) {
3984  // Handle explicit n=pluralform cases
3985  $forms = $this->handleExplicitPluralForms( $count, $forms );
3986  if ( is_string( $forms ) ) {
3987  return $forms;
3988  }
3989  if ( !count( $forms ) ) {
3990  return '';
3991  }
3992 
3993  $pluralForm = $this->getPluralRuleIndexNumber( $count );
3994  $pluralForm = min( $pluralForm, count( $forms ) - 1 );
3995  return $forms[$pluralForm];
3996  }
3997 
4013  protected function handleExplicitPluralForms( $count, array $forms ) {
4014  foreach ( $forms as $index => $form ) {
4015  if ( preg_match( '/\d+=/i', $form ) ) {
4016  $pos = strpos( $form, '=' );
4017  if ( substr( $form, 0, $pos ) === (string)$count ) {
4018  return substr( $form, $pos + 1 );
4019  }
4020  unset( $forms[$index] );
4021  }
4022  }
4023  return array_values( $forms );
4024  }
4025 
4034  protected function preConvertPlural( /* Array */ $forms, $count ) {
4035  while ( count( $forms ) < $count ) {
4036  $forms[] = $forms[count( $forms ) - 1];
4037  }
4038  return $forms;
4039  }
4040 
4057  public function embedBidi( $text = '' ) {
4058  $dir = self::strongDirFromContent( $text );
4059  if ( $dir === 'ltr' ) {
4060  // Wrap in LEFT-TO-RIGHT EMBEDDING ... POP DIRECTIONAL FORMATTING
4061  return self::$lre . $text . self::$pdf;
4062  }
4063  if ( $dir === 'rtl' ) {
4064  // Wrap in RIGHT-TO-LEFT EMBEDDING ... POP DIRECTIONAL FORMATTING
4065  return self::$rle . $text . self::$pdf;
4066  }
4067  // No strong directionality: do not wrap
4068  return $text;
4069  }
4070 
4084  function translateBlockExpiry( $str, User $user = null, $now = 0 ) {
4085  $duration = SpecialBlock::getSuggestedDurations( $this );
4086  foreach ( $duration as $show => $value ) {
4087  if ( strcmp( $str, $value ) == 0 ) {
4088  return htmlspecialchars( trim( $show ) );
4089  }
4090  }
4091 
4092  if ( wfIsInfinity( $str ) ) {
4093  foreach ( $duration as $show => $value ) {
4094  if ( wfIsInfinity( $value ) ) {
4095  return htmlspecialchars( trim( $show ) );
4096  }
4097  }
4098  }
4099 
4100  // If all else fails, return a standard duration or timestamp description.
4101  $time = strtotime( $str, $now );
4102  if ( $time === false ) { // Unknown format. Return it as-is in case.
4103  return $str;
4104  } elseif ( $time !== strtotime( $str, $now + 1 ) ) { // It's a relative timestamp.
4105  // The result differs based on current time, so the difference
4106  // is a fixed duration length.
4107  return $this->formatDuration( $time - $now );
4108  } else { // It's an absolute timestamp.
4109  if ( $time === 0 ) {
4110  // wfTimestamp() handles 0 as current time instead of epoch.
4111  $time = '19700101000000';
4112  }
4113  if ( $user ) {
4114  return $this->userTimeAndDate( $time, $user );
4115  }
4116  return $this->timeanddate( $time );
4117  }
4118  }
4119 
4127  public function segmentForDiff( $text ) {
4128  return $text;
4129  }
4130 
4137  public function unsegmentForDiff( $text ) {
4138  return $text;
4139  }
4140 
4147  public function getConverter() {
4148  return $this->mConverter;
4149  }
4150 
4159  public function autoConvert( $text, $variant = false ) {
4160  return $this->mConverter->autoConvert( $text, $variant );
4161  }
4162 
4169  public function autoConvertToAllVariants( $text ) {
4170  return $this->mConverter->autoConvertToAllVariants( $text );
4171  }
4172 
4179  public function convert( $text ) {
4180  return $this->mConverter->convert( $text );
4181  }
4182 
4189  public function convertTitle( $title ) {
4190  return $this->mConverter->convertTitle( $title );
4191  }
4192 
4201  public function convertNamespace( $ns, $variant = null ) {
4202  return $this->mConverter->convertNamespace( $ns, $variant );
4203  }
4204 
4210  public function hasVariants() {
4211  return count( $this->getVariants() ) > 1;
4212  }
4213 
4221  public function hasVariant( $variant ) {
4222  return (bool)$this->mConverter->validateVariant( $variant );
4223  }
4224 
4232  public function convertHtml( $text, $isTitle = false ) {
4233  return htmlspecialchars( $this->convert( $text, $isTitle ) );
4234  }
4235 
4240  public function convertCategoryKey( $key ) {
4241  return $this->mConverter->convertCategoryKey( $key );
4242  }
4243 
4250  public function getVariants() {
4251  return $this->mConverter->getVariants();
4252  }
4253 
4257  public function getPreferredVariant() {
4258  return $this->mConverter->getPreferredVariant();
4259  }
4260 
4264  public function getDefaultVariant() {
4265  return $this->mConverter->getDefaultVariant();
4266  }
4267 
4271  public function getURLVariant() {
4272  return $this->mConverter->getURLVariant();
4273  }
4274 
4287  public function findVariantLink( &$link, &$nt, $ignoreOtherCond = false ) {
4288  $this->mConverter->findVariantLink( $link, $nt, $ignoreOtherCond );
4289  }
4290 
4297  function getExtraHashOptions() {
4298  return $this->mConverter->getExtraHashOptions();
4299  }
4300 
4308  public function getParsedTitle() {
4309  return $this->mConverter->getParsedTitle();
4310  }
4311 
4318  public function updateConversionTable( Title $title ) {
4319  $this->mConverter->updateConversionTable( $title );
4320  }
4321 
4334  public function markNoConversion( $text, $noParse = false ) {
4335  // Excluding protocal-relative URLs may avoid many false positives.
4336  if ( $noParse || preg_match( '/^(?:' . wfUrlProtocolsWithoutProtRel() . ')/', $text ) ) {
4337  return $this->mConverter->markNoConversion( $text );
4338  } else {
4339  return $text;
4340  }
4341  }
4342 
4349  public function linkTrail() {
4350  return self::$dataCache->getItem( $this->mCode, 'linkTrail' );
4351  }
4352 
4359  public function linkPrefixCharset() {
4360  return self::$dataCache->getItem( $this->mCode, 'linkPrefixCharset' );
4361  }
4362 
4370  public function getParentLanguage() {
4371  if ( $this->mParentLanguage !== false ) {
4372  return $this->mParentLanguage;
4373  }
4374 
4375  $code = explode( '-', $this->getCode() )[0];
4376  if ( !in_array( $code, LanguageConverter::$languagesWithVariants ) ) {
4377  $this->mParentLanguage = null;
4378  return null;
4379  }
4380  $lang = self::factory( $code );
4381  if ( !$lang->hasVariant( $this->getCode() ) ) {
4382  $this->mParentLanguage = null;
4383  return null;
4384  }
4385 
4386  $this->mParentLanguage = $lang;
4387  return $lang;
4388  }
4389 
4397  public function equals( Language $lang ) {
4398  return $lang->getCode() === $this->mCode;
4399  }
4400 
4409  public function getCode() {
4410  return $this->mCode;
4411  }
4412 
4423  public function getHtmlCode() {
4424  if ( is_null( $this->mHtmlCode ) ) {
4425  $this->mHtmlCode = LanguageCode::bcp47( $this->getCode() );
4426  }
4427  return $this->mHtmlCode;
4428  }
4429 
4433  public function setCode( $code ) {
4434  $this->mCode = $code;
4435  // Ensure we don't leave incorrect cached data lying around
4436  $this->mHtmlCode = null;
4437  $this->mParentLanguage = false;
4438  }
4439 
4447  public static function getCodeFromFileName( $filename, $prefix = 'Language', $suffix = '.php' ) {
4448  $m = null;
4449  preg_match( '/' . preg_quote( $prefix, '/' ) . '([A-Z][a-z_]+)' .
4450  preg_quote( $suffix, '/' ) . '/', $filename, $m );
4451  if ( !count( $m ) ) {
4452  return false;
4453  }
4454  return str_replace( '_', '-', strtolower( $m[1] ) );
4455  }
4456 
4462  public static function classFromCode( $code, $fallback = true ) {
4463  if ( $fallback && $code == 'en' ) {
4464  return 'Language';
4465  } else {
4466  return 'Language' . str_replace( '-', '_', ucfirst( $code ) );
4467  }
4468  }
4469 
4478  public static function getFileName( $prefix = 'Language', $code, $suffix = '.php' ) {
4479  if ( !self::isValidBuiltInCode( $code ) ) {
4480  throw new MWException( "Invalid language code \"$code\"" );
4481  }
4482 
4483  return $prefix . str_replace( '-', '_', ucfirst( $code ) ) . $suffix;
4484  }
4485 
4490  public static function getMessagesFileName( $code ) {
4491  global $IP;
4492  $file = self::getFileName( "$IP/languages/messages/Messages", $code, '.php' );
4493  Hooks::run( 'Language::getMessagesFileName', [ $code, &$file ] );
4494  return $file;
4495  }
4496 
4503  public static function getJsonMessagesFileName( $code ) {
4504  global $IP;
4505 
4506  if ( !self::isValidBuiltInCode( $code ) ) {
4507  throw new MWException( "Invalid language code \"$code\"" );
4508  }
4509 
4510  return "$IP/languages/i18n/$code.json";
4511  }
4512 
4520  public static function getFallbackFor( $code ) {
4521  $fallbacks = self::getFallbacksFor( $code );
4522  if ( $fallbacks ) {
4523  return $fallbacks[0];
4524  }
4525  return false;
4526  }
4527 
4535  public static function getFallbacksFor( $code ) {
4536  if ( $code === 'en' || !self::isValidBuiltInCode( $code ) ) {
4537  return [];
4538  }
4539  // For unknown languages, fallbackSequence returns an empty array,
4540  // hardcode fallback to 'en' in that case.
4541  return self::getLocalisationCache()->getItem( $code, 'fallbackSequence' ) ?: [ 'en' ];
4542  }
4543 
4552  public static function getFallbacksIncludingSiteLanguage( $code ) {
4554 
4555  // Usually, we will only store a tiny number of fallback chains, so we
4556  // keep them in static memory.
4557  $cacheKey = "{$code}-{$wgLanguageCode}";
4558 
4559  if ( !array_key_exists( $cacheKey, self::$fallbackLanguageCache ) ) {
4560  $fallbacks = self::getFallbacksFor( $code );
4561 
4562  // Append the site's fallback chain, including the site language itself
4563  $siteFallbacks = self::getFallbacksFor( $wgLanguageCode );
4564  array_unshift( $siteFallbacks, $wgLanguageCode );
4565 
4566  // Eliminate any languages already included in the chain
4567  $siteFallbacks = array_diff( $siteFallbacks, $fallbacks );
4568 
4569  self::$fallbackLanguageCache[$cacheKey] = [ $fallbacks, $siteFallbacks ];
4570  }
4571  return self::$fallbackLanguageCache[$cacheKey];
4572  }
4573 
4583  public static function getMessagesFor( $code ) {
4584  return self::getLocalisationCache()->getItem( $code, 'messages' );
4585  }
4586 
4595  public static function getMessageFor( $key, $code ) {
4596  return self::getLocalisationCache()->getSubitem( $code, 'messages', $key );
4597  }
4598 
4607  public static function getMessageKeysFor( $code ) {
4608  return self::getLocalisationCache()->getSubitemList( $code, 'messages' );
4609  }
4610 
4615  function fixVariableInNamespace( $talk ) {
4616  if ( strpos( $talk, '$1' ) === false ) {
4617  return $talk;
4618  }
4619 
4621  $talk = str_replace( '$1', $wgMetaNamespace, $talk );
4622 
4623  # Allow grammar transformations
4624  # Allowing full message-style parsing would make simple requests
4625  # such as action=raw much more expensive than they need to be.
4626  # This will hopefully cover most cases.
4627  $talk = preg_replace_callback( '/{{grammar:(.*?)\|(.*?)}}/i',
4628  [ $this, 'replaceGrammarInNamespace' ], $talk );
4629  return str_replace( ' ', '_', $talk );
4630  }
4631 
4636  function replaceGrammarInNamespace( $m ) {
4637  return $this->convertGrammar( trim( $m[2] ), trim( $m[1] ) );
4638  }
4639 
4650  public function formatExpiry( $expiry, $format = true, $infinity = 'infinity' ) {
4651  static $dbInfinity;
4652  if ( $dbInfinity === null ) {
4653  $dbInfinity = wfGetDB( DB_REPLICA )->getInfinity();
4654  }
4655 
4656  if ( $expiry == '' || $expiry === 'infinity' || $expiry == $dbInfinity ) {
4657  return $format === true
4658  ? $this->getMessageFromDB( 'infiniteblock' )
4659  : $infinity;
4660  } else {
4661  return $format === true
4662  ? $this->timeanddate( $expiry, /* User preference timezone */ true )
4663  : wfTimestamp( $format, $expiry );
4664  }
4665  }
4666 
4680  function formatTimePeriod( $seconds, $format = [] ) {
4681  if ( !is_array( $format ) ) {
4682  $format = [ 'avoid' => $format ]; // For backwards compatibility
4683  }
4684  if ( !isset( $format['avoid'] ) ) {
4685  $format['avoid'] = false;
4686  }
4687  if ( !isset( $format['noabbrevs'] ) ) {
4688  $format['noabbrevs'] = false;
4689  }
4690  $secondsMsg = wfMessage(
4691  $format['noabbrevs'] ? 'seconds' : 'seconds-abbrev' )->inLanguage( $this );
4692  $minutesMsg = wfMessage(
4693  $format['noabbrevs'] ? 'minutes' : 'minutes-abbrev' )->inLanguage( $this );
4694  $hoursMsg = wfMessage(
4695  $format['noabbrevs'] ? 'hours' : 'hours-abbrev' )->inLanguage( $this );
4696  $daysMsg = wfMessage(
4697  $format['noabbrevs'] ? 'days' : 'days-abbrev' )->inLanguage( $this );
4698 
4699  if ( round( $seconds * 10 ) < 100 ) {
4700  $s = $this->formatNum( sprintf( "%.1f", round( $seconds * 10 ) / 10 ) );
4701  $s = $secondsMsg->params( $s )->text();
4702  } elseif ( round( $seconds ) < 60 ) {
4703  $s = $this->formatNum( round( $seconds ) );
4704  $s = $secondsMsg->params( $s )->text();
4705  } elseif ( round( $seconds ) < 3600 ) {
4706  $minutes = floor( $seconds / 60 );
4707  $secondsPart = round( fmod( $seconds, 60 ) );
4708  if ( $secondsPart == 60 ) {
4709  $secondsPart = 0;
4710  $minutes++;
4711  }
4712  $s = $minutesMsg->params( $this->formatNum( $minutes ) )->text();
4713  $s .= ' ';
4714  $s .= $secondsMsg->params( $this->formatNum( $secondsPart ) )->text();
4715  } elseif ( round( $seconds ) <= 2 * 86400 ) {
4716  $hours = floor( $seconds / 3600 );
4717  $minutes = floor( ( $seconds - $hours * 3600 ) / 60 );
4718  $secondsPart = round( $seconds - $hours * 3600 - $minutes * 60 );
4719  if ( $secondsPart == 60 ) {
4720  $secondsPart = 0;
4721  $minutes++;
4722  }
4723  if ( $minutes == 60 ) {
4724  $minutes = 0;
4725  $hours++;
4726  }
4727  $s = $hoursMsg->params( $this->formatNum( $hours ) )->text();
4728  $s .= ' ';
4729  $s .= $minutesMsg->params( $this->formatNum( $minutes ) )->text();
4730  if ( !in_array( $format['avoid'], [ 'avoidseconds', 'avoidminutes' ] ) ) {
4731  $s .= ' ' . $secondsMsg->params( $this->formatNum( $secondsPart ) )->text();
4732  }
4733  } else {
4734  $days = floor( $seconds / 86400 );
4735  if ( $format['avoid'] === 'avoidminutes' ) {
4736  $hours = round( ( $seconds - $days * 86400 ) / 3600 );
4737  if ( $hours == 24 ) {
4738  $hours = 0;
4739  $days++;
4740  }
4741  $s = $daysMsg->params( $this->formatNum( $days ) )->text();
4742  $s .= ' ';
4743  $s .= $hoursMsg->params( $this->formatNum( $hours ) )->text();
4744  } elseif ( $format['avoid'] === 'avoidseconds' ) {
4745  $hours = floor( ( $seconds - $days * 86400 ) / 3600 );
4746  $minutes = round( ( $seconds - $days * 86400 - $hours * 3600 ) / 60 );
4747  if ( $minutes == 60 ) {
4748  $minutes = 0;
4749  $hours++;
4750  }
4751  if ( $hours == 24 ) {
4752  $hours = 0;
4753  $days++;
4754  }
4755  $s = $daysMsg->params( $this->formatNum( $days ) )->text();
4756  $s .= ' ';
4757  $s .= $hoursMsg->params( $this->formatNum( $hours ) )->text();
4758  $s .= ' ';
4759  $s .= $minutesMsg->params( $this->formatNum( $minutes ) )->text();
4760  } else {
4761  $s = $daysMsg->params( $this->formatNum( $days ) )->text();
4762  $s .= ' ';
4763  $s .= $this->formatTimePeriod( $seconds - $days * 86400, $format );
4764  }
4765  }
4766  return $s;
4767  }
4768 
4780  function formatBitrate( $bps ) {
4781  return $this->formatComputingNumbers( $bps, 1000, "bitrate-$1bits" );
4782  }
4783 
4790  function formatComputingNumbers( $size, $boundary, $messageKey ) {
4791  if ( $size <= 0 ) {
4792  return str_replace( '$1', $this->formatNum( $size ),
4793  $this->getMessageFromDB( str_replace( '$1', '', $messageKey ) )
4794  );
4795  }
4796  $sizes = [ '', 'kilo', 'mega', 'giga', 'tera', 'peta', 'exa', 'zeta', 'yotta' ];
4797  $index = 0;
4798 
4799  $maxIndex = count( $sizes ) - 1;
4800  while ( $size >= $boundary && $index < $maxIndex ) {
4801  $index++;
4802  $size /= $boundary;
4803  }
4804 
4805  // For small sizes no decimal places necessary
4806  $round = 0;
4807  if ( $index > 1 ) {
4808  // For MB and bigger two decimal places are smarter
4809  $round = 2;
4810  }
4811  $msg = str_replace( '$1', $sizes[$index], $messageKey );
4812 
4813  $size = round( $size, $round );
4814  $text = $this->getMessageFromDB( $msg );
4815  return str_replace( '$1', $this->formatNum( $size ), $text );
4816  }
4817 
4828  function formatSize( $size ) {
4829  return $this->formatComputingNumbers( $size, 1024, "size-$1bytes" );
4830  }
4831 
4841  function specialList( $page, $details, $oppositedm = true ) {
4842  if ( !$details ) {
4843  return $page;
4844  }
4845 
4846  $dirmark = ( $oppositedm ? $this->getDirMark( true ) : '' ) . $this->getDirMark();
4847  return $page .
4848  $dirmark .
4849  $this->msg( 'word-separator' )->escaped() .
4850  $this->msg( 'parentheses' )->rawParams( $details )->escaped();
4851  }
4852 
4863  public function viewPrevNext( Title $title, $offset, $limit,
4864  array $query = [], $atend = false
4865  ) {
4866  // @todo FIXME: Why on earth this needs one message for the text and another one for tooltip?
4867 
4868  # Make 'previous' link
4869  $prev = wfMessage( 'prevn' )->inLanguage( $this )->title( $title )->numParams( $limit )->text();
4870  if ( $offset > 0 ) {
4871  $plink = $this->numLink( $title, max( $offset - $limit, 0 ), $limit,
4872  $query, $prev, 'prevn-title', 'mw-prevlink' );
4873  } else {
4874  $plink = htmlspecialchars( $prev );
4875  }
4876 
4877  # Make 'next' link
4878  $next = wfMessage( 'nextn' )->inLanguage( $this )->title( $title )->numParams( $limit )->text();
4879  if ( $atend ) {
4880  $nlink = htmlspecialchars( $next );
4881  } else {
4882  $nlink = $this->numLink( $title, $offset + $limit, $limit,
4883  $query, $next, 'nextn-title', 'mw-nextlink' );
4884  }
4885 
4886  # Make links to set number of items per page
4887  $numLinks = [];
4888  foreach ( [ 20, 50, 100, 250, 500 ] as $num ) {
4889  $numLinks[] = $this->numLink( $title, $offset, $num,
4890  $query, $this->formatNum( $num ), 'shown-title', 'mw-numlink' );
4891  }
4892 
4893  return wfMessage( 'viewprevnext' )->inLanguage( $this )->title( $title
4894  )->rawParams( $plink, $nlink, $this->pipeList( $numLinks ) )->escaped();
4895  }
4896 
4909  private function numLink( Title $title, $offset, $limit, array $query, $link,
4910  $tooltipMsg, $class
4911  ) {
4912  $query = [ 'limit' => $limit, 'offset' => $offset ] + $query;
4913  $tooltip = wfMessage( $tooltipMsg )->inLanguage( $this )->title( $title )
4914  ->numParams( $limit )->text();
4915 
4916  return Html::element( 'a', [ 'href' => $title->getLocalURL( $query ),
4917  'title' => $tooltip, 'class' => $class ], $link );
4918  }
4919 
4925  public function getConvRuleTitle() {
4926  return $this->mConverter->getConvRuleTitle();
4927  }
4928 
4934  public function getCompiledPluralRules() {
4935  $pluralRules = self::$dataCache->getItem( strtolower( $this->mCode ), 'compiledPluralRules' );
4936  $fallbacks = self::getFallbacksFor( $this->mCode );
4937  if ( !$pluralRules ) {
4938  foreach ( $fallbacks as $fallbackCode ) {
4939  $pluralRules = self::$dataCache->getItem( strtolower( $fallbackCode ), 'compiledPluralRules' );
4940  if ( $pluralRules ) {
4941  break;
4942  }
4943  }
4944  }
4945  return $pluralRules;
4946  }
4947 
4953  public function getPluralRules() {
4954  $pluralRules = self::$dataCache->getItem( strtolower( $this->mCode ), 'pluralRules' );
4955  $fallbacks = self::getFallbacksFor( $this->mCode );
4956  if ( !$pluralRules ) {
4957  foreach ( $fallbacks as $fallbackCode ) {
4958  $pluralRules = self::$dataCache->getItem( strtolower( $fallbackCode ), 'pluralRules' );
4959  if ( $pluralRules ) {
4960  break;
4961  }
4962  }
4963  }
4964  return $pluralRules;
4965  }
4966 
4972  public function getPluralRuleTypes() {
4973  $pluralRuleTypes = self::$dataCache->getItem( strtolower( $this->mCode ), 'pluralRuleTypes' );
4974  $fallbacks = self::getFallbacksFor( $this->mCode );
4975  if ( !$pluralRuleTypes ) {
4976  foreach ( $fallbacks as $fallbackCode ) {
4977  $pluralRuleTypes = self::$dataCache->getItem( strtolower( $fallbackCode ), 'pluralRuleTypes' );
4978  if ( $pluralRuleTypes ) {
4979  break;
4980  }
4981  }
4982  }
4983  return $pluralRuleTypes;
4984  }
4985 
4991  public function getPluralRuleIndexNumber( $number ) {
4992  $pluralRules = $this->getCompiledPluralRules();
4993  $form = Evaluator::evaluateCompiled( $number, $pluralRules );
4994  return $form;
4995  }
4996 
5005  public function getPluralRuleType( $number ) {
5006  $index = $this->getPluralRuleIndexNumber( $number );
5007  $pluralRuleTypes = $this->getPluralRuleTypes();
5008  if ( isset( $pluralRuleTypes[$index] ) ) {
5009  return $pluralRuleTypes[$index];
5010  } else {
5011  return 'other';
5012  }
5013  }
5014 }
utf8ToCodepoint($char)
Determine the Unicode codepoint of a single-character UTF-8 sequence.
formatTimePeriod($seconds, $format=[])
Formats a time given in seconds into a string representation of that time.
Definition: Language.php:4680
getDirMark($opposite=false)
A hidden direction mark (LRM or RLM), depending on the language direction.
Definition: Language.php:3094
static $mMonthAbbrevMsgs
Definition: Language.php:83
lc($str, $first=false)
Definition: Language.php:2702
$mParentLanguage
Definition: Language.php:43
getSpecialPageAliases()
Get special page names, as an associative array canonical name => array of valid names, including aliases.
Definition: Language.php:3208
getWeekdayAbbreviation($key)
Definition: Language.php:980
getDateFormatString($type, $pref)
Get a format string for a given type and preference.
Definition: Language.php:2207
getPluralRuleTypes()
Get the plural rule types for the language.
Definition: Language.php:4972
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:2480
static getLocalisationCache()
Get the LocalisationCache instance.
Definition: Language.php:406
convertPlural($count, $forms)
Plural form transformations, needed for some languages.
Definition: Language.php:3983
Wrapper around strtr() that holds replacements.
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
Definition: deferred.txt:11
doMagicHook()
Run the LanguageGetMagic hook once.
Definition: Language.php:3154
wfGetDB($db, $groups=[], $wiki=false)
Get a Database object.
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output $out
Definition: hooks.txt:790
static isKnownLanguageTag($tag)
Returns true if a language code is an IETF tag known to MediaWiki.
Definition: Language.php:385
the array() calling protocol came about after MediaWiki 1.4rc1.
static insertSpace($string, $pattern)
Definition: Language.php:2879
null for the local wiki Added should default to null in handler for backwards compatibility add a value to it if you want to add a cookie that have to vary cache options can modify $query
Definition: hooks.txt:1623
magic word the default is to use $key to get the and $key value or $key value text $key value html to format the value $key
Definition: hooks.txt:2610
static getTitleInvalidRegex()
Returns a simple regex that will match on characters and sequences invalid in titles.
convertGrammar($word, $case)
Grammatical transformations, needed for inflected languages Invoked by putting {{grammar:case|word}} ...
Definition: Language.php:3834
static LocalisationCache $dataCache
Definition: Language.php:60
isMultibyte($str)
Definition: Language.php:2718
initEncoding()
Definition: Language.php:2956
handleExplicitPluralForms($count, array $forms)
Handles explicit plural forms for Language::convertPlural()
Definition: Language.php:4013
hasVariant($variant)
Check if the language has the specific variant.
Definition: Language.php:4221
markNoConversion($text, $noParse=false)
Prepare external link text for conversion.
Definition: Language.php:4334
setNamespaces(array $namespaces)
Arbitrarily set all of the namespace names at once.
Definition: Language.php:504
recodeForEdit($s)
Definition: Language.php:2965
static HashBagOStuff null $languageNameCache
Cache for language names.
Definition: Language.php:152
transformUsingPairFile($file, $string)
Transform a string using serialized data stored in the given file (which must be in the serialized su...
Definition: Language.php:3014
date($ts, $adj=false, $format=true, $timecorrection=false)
Definition: Language.php:2241
getHtmlCode()
Get the code in BCP 47 format which we can use inside of html lang="" tags.
Definition: Language.php:4423
truncate_skip(&$ret, $text, $search, $start, $len=null)
truncateHtml() helper function like strcspn() but adds the skipped chars to $ret
Definition: Language.php:3789
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:284
$IP
Definition: WebStart.php:41
equals(Language $lang)
Compare with an other language object.
Definition: Language.php:4397
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:2019
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
removeBadCharFirst($string)
Remove bytes that represent an incomplete Unicode character at the start of string (e...
Definition: Language.php:3645
static $IRANIAN_DAYS
Definition: Language.php:1572
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException'returning false will NOT prevent logging $e
Definition: hooks.txt:2195
static getMessageFor($key, $code)
Get a message for a given language.
Definition: Language.php:4595
lcfirst($str)
Definition: Language.php:2683
offsetForUser(User $user)
Adjust the timestamp depending on the given user's preferences.
Definition: MWTimestamp.php:79
getLocalNsIndex($text)
Get a namespace key by value, case insensitive.
Definition: Language.php:613
alignEnd()
Return 'right' or 'left' as appropriate alignment for line-end for this language's text direction...
Definition: Language.php:3062
getFallbackLanguages()
Definition: Language.php:446
firstChar($s)
Get the first character of a string.
Definition: Language.php:2901
globals txt Globals are evil The original MediaWiki code relied on globals for processing context far too often MediaWiki development since then has been a story of slowly moving context out of global variables and into objects Storing processing context in object member variables allows those objects to be reused in a much more flexible way Consider the elegance of
database rows
Definition: globals.txt:10
$wgLangObjCacheSize
Language cache size, or really how many languages can we handle simultaneously without degrading to c...
if(!isset($args[0])) $lang
$namespaceAliases
Definition: Language.php:50
hasVariants()
Check if this is a language with variants.
Definition: Language.php:4210
getCompiledPluralRules()
Get the compiled plural rules for the language.
Definition: Language.php:4934
getNsIndex($text)
Get a namespace key by value, case insensitive.
Definition: Language.php:696
getNsText($index)
Get a namespace value by key.
Definition: Language.php:543
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
iconv($in, $out, $string)
Definition: Language.php:2603
$value
convertCategoryKey($key)
Definition: Language.php:4240
$wgMetaNamespace
Name of the project namespace.
formatComputingNumbers($size, $boundary, $messageKey)
Definition: Language.php:4790
fallback8bitEncoding()
Definition: Language.php:2813
static getFallbacksIncludingSiteLanguage($code)
Get the ordered list of fallback languages, ending with the fallback language chain for the site lang...
Definition: Language.php:4552
formatNumNoSeparators($number)
Front-end for non-commafied formatNum.
Definition: Language.php:3281
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:3812
A fake language variant converter.
linkTrail()
A regular expression to match legal word-trailing characters which should be merged onto a link of th...
Definition: Language.php:4349
getAllMessages()
Definition: Language.php:2593
wfUrlProtocolsWithoutProtRel()
Like wfUrlProtocols(), but excludes '//' from the protocol list.
normalize($s)
Convert a UTF-8 string to normal form C.
Definition: Language.php:2989
$wgNamespaceAliases
Namespace aliases.
static getCodeFromFileName($filename, $prefix= 'Language', $suffix= '.php')
Get the language code from a file name.
Definition: Language.php:4447
internalUserTimeAndDate($type, $ts, User $user, array $options)
Internal helper function for userDate(), userTime() and userTimeAndDate()
Definition: Language.php:2376
static getMessageKeysFor($code)
Get all message keys for a given language.
Definition: Language.php:4607
static tsToHijri($ts)
Converting Gregorian dates to Hijri dates.
Definition: Language.php:1646
formatExpiry($expiry, $format=true, $infinity= 'infinity')
Decode an expiry (block, protection, etc) which has come from the DB.
Definition: Language.php:4650
getVariants()
Get the list of variants supported by this language see sample implementation in LanguageZh.php.
Definition: Language.php:4250
A helper class for throttling authentication attempts.
$dateFormatStrings
Definition: Language.php:45
static newFromCode($code, $fallback=false)
Create a language object for a given language code.
Definition: Language.php:210
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:2176
getNamespaceAliases()
Definition: Language.php:622
when a variable name is used in a it is silently declared as a new local masking the global
Definition: design.txt:93
autoConvertToAllVariants($text)
convert text to all supported variants
Definition: Language.php:4169
minimumGroupingDigits()
Definition: Language.php:3400
static getFallbacksFor($code)
Get the ordered list of fallback languages.
Definition: Language.php:4535
static $mMonthMsgs
Definition: Language.php:73
We ve cleaned up the code here by removing clumps of infrequently used code and moving them off somewhere else It s much easier for someone working with this code to see what s _really_ going and make changes or fix bugs In we can take all the code that deals with the little used title reversing options(say) and put it in one place.Instead of having little title-reversing if-blocks spread all over the codebase in showAnArticle
$wgExtraGenderNamespaces
Same as above, but for namespaces with gender distinction.
when a variable name is used in a function
Definition: design.txt:93
static $mLangObjCache
Definition: Language.php:62
parseFormattedNumber($number)
Definition: Language.php:3289
$wgTranslateNumerals
For Hindi and Arabic use local numerals instead of Western style (0-9) numerals in interface...
static isUtf8($value)
Test whether a string is valid UTF-8.
Definition: StringUtils.php:41
static $mWeekdayAbbrevMsgs
Definition: Language.php:69
static classFromCode($code, $fallback=true)
Definition: Language.php:4462
$mMagicExtensions
Definition: Language.php:42
getWeekdayName($key)
Definition: Language.php:972
setCode($code)
Definition: Language.php:4433
static $pdf
Definition: Language.php:159
usually copyright or history_copyright This message must be in HTML not wikitext & $link
Definition: hooks.txt:3038
The User object encapsulates all of the user-specific settings (user_id, name, rights, email address, options, last login time).
Definition: User.php:46
time($ts, $adj=false, $format=true, $timecorrection=false)
Definition: Language.php:2260
embedBidi($text= '')
Wraps argument with unicode control characters for directionality safety.
Definition: Language.php:4057
__construct()
Definition: Language.php:415
static getJsonMessagesFileName($code)
Definition: Language.php:4503
wfTimestamp($outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
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:2019
$wgLanguageCode
Site language code.
formatSize($size)
Format a size in bytes for output, using an appropriate unit (B, KB, MB, GB, TB, PB, EB, ZB or YB) according to the magnitude in question.
Definition: Language.php:4828
semicolonList(array $list)
Take a list of strings and build a locale-friendly semicolon-separated list, using the local semicolo...
Definition: Language.php:3455
addMagicWordsByLang($newWords)
Add magic words to the extension array.
Definition: Language.php:3193
segmentForDiff($text)
languages like Chinese need to be segmented in order for the diff to be of any use ...
Definition: Language.php:4127
static $lre
Unicode directional formatting characters, for embedBidi()
Definition: Language.php:157
convertHtml($text, $isTitle=false)
Perform output conversion on a string, and encode for safe HTML output.
Definition: Language.php:4232
ucwordbreaksCallbackAscii($matches)
Definition: Language.php:2620
const NS_PROJECT
Definition: Defines.php:69
static $mHijriCalendarMonthMsgs
Definition: Language.php:111
ucwordsCallbackMB($matches)
Definition: Language.php:2636
linkPrefixCharset()
A regular expression character set to match legal word-prefixing characters which should be merged on...
Definition: Language.php:4359
ucwords($str)
Definition: Language.php:2726
static getMain()
Get the RequestContext object associated with the main request.
getIranianCalendarMonthName($key)
Definition: Language.php:988
getPreferredVariant()
Definition: Language.php:4257
LanguageConverter $mConverter
Definition: Language.php:39
msg($msg)
Get message object in this language.
Definition: Language.php:918
$minimumGroupingDigits
__destruct()
Reduce memory usage.
Definition: Language.php:429
formatBitrate($bps)
Format a bitrate for output, using an appropriate unit (bps, kbps, Mbps, Gbps, Tbps, Pbps, Ebps, Zbps or Ybps) according to the magnitude in question.
Definition: Language.php:4780
truncateHtml($text, $length, $ellipsis= '...')
Truncate a string of valid HTML to a specified length in bytes, appending an optional string (e...
Definition: Language.php:3671
$wgLocalisationCacheConf
Localisation cache configuration.
getArrow($direction= 'forwards')
An arrow, depending on the language direction.
Definition: Language.php:3117
wfWarn($msg, $callerOffset=1, $level=E_USER_NOTICE)
Send a warning either to the debug log or in a PHP error depending on $wgDevelopmentWarnings.
static convertDoubleWidth($string)
convert double-width roman characters to single-width.
Definition: Language.php:2859
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 default is conds Array Extra conditions for the No matching items in log is displayed if loglist is empty msgKey Array If you want a nice box with a set this to the key of the message First element is the message additional optional elements are parameters for the key that are processed with wfMessage() -> params() ->parseAsBlock()-offset Set to overwrite offset parameter in $wgRequest set to ''to unsetoffset-wrap String Wrap the message in html(usually something like"&lt
getDefaultVariant()
Definition: Language.php:4264
viewPrevNext(Title $title, $offset, $limit, array $query=[], $atend=false)
Generate (prev x| next x) (20|50|100...) type links for paging.
Definition: Language.php:4863
getConverter()
Return the LanguageConverter used in the Language.
Definition: Language.php:4147
static $mIranianCalendarMonthMsgs
Definition: Language.php:88
static hebrewNumeral($num)
Hebrew Gematria number formatting up to 9999.
Definition: Language.php:2011
const NS_PROJECT_TALK
Definition: Defines.php:70
getFormattedNsText($index)
A convenience function that returns the same thing as getNsText() except with '_' changed to ' '...
Definition: Language.php:561
getMonthAbbreviation($key)
Definition: Language.php:953
getLocalURL($query= '', $query2=false)
Get a URL with no fragment or server name (relative URL) from a Title object.
Definition: Title.php:1911
static tsToHebrew($ts)
Converting Gregorian dates to Hebrew dates.
Definition: Language.php:1698
getBookstoreList()
Exports $wgBookstoreListEn.
Definition: Language.php:454
capitalizeAllNouns()
Definition: Language.php:3106
getMonthNameGen($key)
Definition: Language.php:945
listToText(array $l)
Take a list of strings and build a locale-friendly comma-separated list, using the local comma-separa...
Definition: Language.php:3413
return['DBLoadBalancerFactory'=> function(MediaWikiServices $services){$mainConfig=$services->getMainConfig();$lbConf=MWLBFactory::applyDefaultConfig($mainConfig->get( 'LBFactoryConf'), $mainConfig, $services->getConfiguredReadOnlyMode());$class=MWLBFactory::getLBFactoryClass($lbConf);$instance=new $class($lbConf);MWLBFactory::setSchemaAliases($instance, $mainConfig);return $instance;},'DBLoadBalancer'=> function(MediaWikiServices $services){return $services->getDBLoadBalancerFactory() ->getMainLB();},'SiteStore'=> function(MediaWikiServices $services){$rawSiteStore=new DBSiteStore($services->getDBLoadBalancer());$cache=wfGetCache(wfIsHHVM()?CACHE_ACCEL:CACHE_ANYTHING);return new CachingSiteStore($rawSiteStore, $cache);},'SiteLookup'=> function(MediaWikiServices $services){$cacheFile=$services->getMainConfig() ->get( 'SitesCacheFile');if($cacheFile!==false){return new FileBasedSiteLookup($cacheFile);}else{return $services->getSiteStore();}},'ConfigFactory'=> function(MediaWikiServices $services){$registry=$services->getBootstrapConfig() ->get( 'ConfigRegistry');$factory=new ConfigFactory();foreach($registry as $name=> $callback){$factory->register($name, $callback);}return $factory;},'MainConfig'=> function(MediaWikiServices $services){return $services->getConfigFactory() ->makeConfig( 'main');},'InterwikiLookup'=> function(MediaWikiServices $services){global $wgContLang;$config=$services->getMainConfig();return new ClassicInterwikiLookup($wgContLang, $services->getMainWANObjectCache(), $config->get( 'InterwikiExpiry'), $config->get( 'InterwikiCache'), $config->get( 'InterwikiScopes'), $config->get( 'InterwikiFallbackSite'));},'StatsdDataFactory'=> function(MediaWikiServices $services){return new BufferingStatsdDataFactory(rtrim($services->getMainConfig() ->get( 'StatsdMetricPrefix'), '.'));},'EventRelayerGroup'=> function(MediaWikiServices $services){return new EventRelayerGroup($services->getMainConfig() ->get( 'EventRelayerConfig'));},'SearchEngineFactory'=> function(MediaWikiServices $services){return new SearchEngineFactory($services->getSearchEngineConfig());},'SearchEngineConfig'=> function(MediaWikiServices $services){global $wgContLang;return new SearchEngineConfig($services->getMainConfig(), $wgContLang);},'SkinFactory'=> function(MediaWikiServices $services){$factory=new SkinFactory();$names=$services->getMainConfig() ->get( 'ValidSkinNames');foreach($names as $name=> $skin){$factory->register($name, $skin, function() use($name, $skin){$class="Skin$skin";return new $class($name);});}$factory->register( 'fallback', 'Fallback', function(){return new SkinFallback;});$factory->register( 'apioutput', 'ApiOutput', function(){return new SkinApi;});return $factory;},'WatchedItemStore'=> function(MediaWikiServices $services){$store=new WatchedItemStore($services->getDBLoadBalancer(), new HashBagOStuff([ 'maxKeys'=> 100]), $services->getReadOnlyMode(), $services->getMainConfig() ->get( 'UpdateRowsPerQuery'));$store->setStatsdDataFactory($services->getStatsdDataFactory());if($services->getMainConfig() ->get( 'ReadOnlyWatchedItemStore')){$store=new NoWriteWatchedItemStore($store);}return $store;},'WatchedItemQueryService'=> function(MediaWikiServices $services){return new WatchedItemQueryService($services->getDBLoadBalancer(), $services->getCommentStore(), $services->getActorMigration());},'CryptRand'=> function(MediaWikiServices $services){$secretKey=$services->getMainConfig() ->get( 'SecretKey');return new CryptRand(['wfHostname','wfWikiID', function() use($secretKey){return $secretKey?: '';}], defined( 'MW_CONFIG_FILE')?[MW_CONFIG_FILE]:[], LoggerFactory::getInstance( 'CryptRand'));},'CryptHKDF'=> function(MediaWikiServices $services){$config=$services->getMainConfig();$secret=$config->get( 'HKDFSecret')?:$config->get( 'SecretKey');if(!$secret){throw new RuntimeException("Cannot use MWCryptHKDF without a secret.");}$context=[microtime(), getmypid(), gethostname()];$cache=$services->getLocalServerObjectCache();if($cache instanceof EmptyBagOStuff){$cache=ObjectCache::getLocalClusterInstance();}return new CryptHKDF($secret, $config->get( 'HKDFAlgorithm'), $cache, $context, $services->getCryptRand());},'MediaHandlerFactory'=> function(MediaWikiServices $services){return new MediaHandlerFactory($services->getMainConfig() ->get( 'MediaHandlers'));},'MimeAnalyzer'=> function(MediaWikiServices $services){$logger=LoggerFactory::getInstance( 'Mime');$mainConfig=$services->getMainConfig();$params=['typeFile'=> $mainConfig->get( 'MimeTypeFile'),'infoFile'=> $mainConfig->get( 'MimeInfoFile'),'xmlTypes'=> $mainConfig->get( 'XMLMimeTypes'),'guessCallback'=>function($mimeAnalyzer, &$head, &$tail, $file, &$mime) use($logger){$deja=new DjVuImage($file);if($deja->isValid()){$logger->info(__METHOD__.": detected $file as image/vnd.djvu\n");$mime= 'image/vnd.djvu';return;}Hooks::run('MimeMagicGuessFromContent', [$mimeAnalyzer, &$head, &$tail, $file, &$mime]);},'extCallback'=> function($mimeAnalyzer, $ext, &$mime){Hooks::run( 'MimeMagicImproveFromExtension', [$mimeAnalyzer, $ext, &$mime]);},'initCallback'=> function($mimeAnalyzer){Hooks::run( 'MimeMagicInit', [$mimeAnalyzer]);},'logger'=> $logger];if($params['infoFile']=== 'includes/mime.info'){$params['infoFile']=__DIR__."/libs/mime/mime.info";}if($params['typeFile']=== 'includes/mime.types'){$params['typeFile']=__DIR__."/libs/mime/mime.types";}$detectorCmd=$mainConfig->get( 'MimeDetectorCommand');if($detectorCmd){$factory=$services->getShellCommandFactory();$params['detectCallback']=function($file) use($detectorCmd, $factory){$result=$factory->create() ->unsafeParams($detectorCmd) ->params($file) ->execute();return $result->getStdout();};}return new MimeMagic($params);},'ProxyLookup'=> function(MediaWikiServices $services){$mainConfig=$services->getMainConfig();return new ProxyLookup($mainConfig->get( 'SquidServers'), $mainConfig->get( 'SquidServersNoPurge'));},'Parser'=> function(MediaWikiServices $services){$conf=$services->getMainConfig() ->get( 'ParserConf');return ObjectFactory::constructClassInstance($conf['class'], [$conf]);},'ParserCache'=> function(MediaWikiServices $services){$config=$services->getMainConfig();$cache=ObjectCache::getInstance($config->get( 'ParserCacheType'));wfDebugLog( 'caches', 'parser: '.get_class($cache));return new ParserCache($cache, $config->get( 'CacheEpoch'));},'LinkCache'=> function(MediaWikiServices $services){return new LinkCache($services->getTitleFormatter(), $services->getMainWANObjectCache());},'LinkRendererFactory'=> function(MediaWikiServices $services){return new LinkRendererFactory($services->getTitleFormatter(), $services->getLinkCache());},'LinkRenderer'=> function(MediaWikiServices $services){global $wgUser;if(defined( 'MW_NO_SESSION')){return $services->getLinkRendererFactory() ->create();}else{return $services->getLinkRendererFactory() ->createForUser($wgUser);}},'GenderCache'=> function(MediaWikiServices $services){return new GenderCache();},'_MediaWikiTitleCodec'=> function(MediaWikiServices $services){global $wgContLang;return new MediaWikiTitleCodec($wgContLang, $services->getGenderCache(), $services->getMainConfig() ->get( 'LocalInterwikis'));},'TitleFormatter'=> function(MediaWikiServices $services){return $services->getService( '_MediaWikiTitleCodec');},'TitleParser'=> function(MediaWikiServices $services){return $services->getService( '_MediaWikiTitleCodec');},'MainObjectStash'=> function(MediaWikiServices $services){$mainConfig=$services->getMainConfig();$id=$mainConfig->get( 'MainStash');if(!isset($mainConfig->get( 'ObjectCaches')[$id])){throw new UnexpectedValueException("Cache type \"$id\" is not present in \$wgObjectCaches.");}return\ObjectCache::newFromParams($mainConfig->get( 'ObjectCaches')[$id]);},'MainWANObjectCache'=> function(MediaWikiServices $services){$mainConfig=$services->getMainConfig();$id=$mainConfig->get( 'MainWANCache');if(!isset($mainConfig->get( 'WANObjectCaches')[$id])){throw new UnexpectedValueException("WAN cache type \"$id\" is not present in \$wgWANObjectCaches.");}$params=$mainConfig->get( 'WANObjectCaches')[$id];$objectCacheId=$params['cacheId'];if(!isset($mainConfig->get( 'ObjectCaches')[$objectCacheId])){throw new UnexpectedValueException("Cache type \"$objectCacheId\" is not present in \$wgObjectCaches.");}$params['store']=$mainConfig->get( 'ObjectCaches')[$objectCacheId];return\ObjectCache::newWANCacheFromParams($params);},'LocalServerObjectCache'=> function(MediaWikiServices $services){$cacheId=\ObjectCache::detectLocalServerCache();return\ObjectCache::newFromId($cacheId);},'VirtualRESTServiceClient'=> function(MediaWikiServices $services){$config=$services->getMainConfig() ->get( 'VirtualRestConfig');$vrsClient=new VirtualRESTServiceClient(new MultiHttpClient([]));foreach($config['paths'] as $prefix=> $serviceConfig){$class=$serviceConfig['class'];$constructArg=isset($serviceConfig['options'])?$serviceConfig['options']:[];$constructArg+=$config['global'];$vrsClient->mount($prefix, [ 'class'=> $class, 'config'=> $constructArg]);}return $vrsClient;},'ConfiguredReadOnlyMode'=> function(MediaWikiServices $services){return new ConfiguredReadOnlyMode($services->getMainConfig());},'ReadOnlyMode'=> function(MediaWikiServices $services){return new ReadOnlyMode($services->getConfiguredReadOnlyMode(), $services->getDBLoadBalancer());},'UploadRevisionImporter'=> function(MediaWikiServices $services){return new ImportableUploadRevisionImporter($services->getMainConfig() ->get( 'EnableUploads'), LoggerFactory::getInstance( 'UploadRevisionImporter'));},'OldRevisionImporter'=> function(MediaWikiServices $services){return new ImportableOldRevisionImporter(true, LoggerFactory::getInstance( 'OldRevisionImporter'), $services->getDBLoadBalancer());},'WikiRevisionOldRevisionImporterNoUpdates'=> function(MediaWikiServices $services){return new ImportableOldRevisionImporter(false, LoggerFactory::getInstance( 'OldRevisionImporter'), $services->getDBLoadBalancer());},'ShellCommandFactory'=> function(MediaWikiServices $services){$config=$services->getMainConfig();$limits=['time'=> $config->get( 'MaxShellTime'),'walltime'=> $config->get( 'MaxShellWallClockTime'),'memory'=> $config->get( 'MaxShellMemory'),'filesize'=> $config->get( 'MaxShellFileSize'),];$cgroup=$config->get( 'ShellCgroup');$restrictionMethod=$config->get( 'ShellRestrictionMethod');$factory=new CommandFactory($limits, $cgroup, $restrictionMethod);$factory->setLogger(LoggerFactory::getInstance( 'exec'));$factory->logStderr();return $factory;},'ExternalStoreFactory'=> function(MediaWikiServices $services){$config=$services->getMainConfig();return new ExternalStoreFactory($config->get( 'ExternalStores'));},'RevisionStore'=> function(MediaWikiServices $services){$blobStore=$services->getService( '_SqlBlobStore');$store=new RevisionStore($services->getDBLoadBalancer(), $blobStore, $services->getMainWANObjectCache(), $services->getCommentStore(), $services->getActorMigration());$store->setLogger(LoggerFactory::getInstance( 'RevisionStore'));$config=$services->getMainConfig();$store->setContentHandlerUseDB($config->get( 'ContentHandlerUseDB'));return $store;},'RevisionLookup'=> function(MediaWikiServices $services){return $services->getRevisionStore();},'RevisionFactory'=> function(MediaWikiServices $services){return $services->getRevisionStore();},'BlobStoreFactory'=> function(MediaWikiServices $services){global $wgContLang;return new BlobStoreFactory($services->getDBLoadBalancer(), $services->getMainWANObjectCache(), $services->getMainConfig(), $wgContLang);},'BlobStore'=> function(MediaWikiServices $services){return $services->getService( '_SqlBlobStore');},'_SqlBlobStore'=> function(MediaWikiServices $services){return $services->getBlobStoreFactory() ->newSqlBlobStore();},'ContentModelStore'=> function(MediaWikiServices $services){return new NameTableStore($services->getDBLoadBalancer(), $services->getMainWANObjectCache(), LoggerFactory::getInstance( 'NameTableSqlStore'),'content_models','model_id','model_name');},'SlotRoleStore'=> function(MediaWikiServices $services){return new NameTableStore($services->getDBLoadBalancer(), $services->getMainWANObjectCache(), LoggerFactory::getInstance( 'NameTableSqlStore'),'slot_roles','role_id','role_name','strtolower');},'PreferencesFactory'=> function(MediaWikiServices $services){global $wgContLang;$authManager=AuthManager::singleton();$linkRenderer=$services->getLinkRendererFactory() ->create();$config=$services->getMainConfig();$factory=new DefaultPreferencesFactory($config, $wgContLang, $authManager, $linkRenderer);$factory->setLogger(LoggerFactory::getInstance( 'preferences'));return $factory;},'HttpRequestFactory'=> function(MediaWikiServices $services){return new\MediaWiki\Http\HttpRequestFactory();},'CommentStore'=> function(MediaWikiServices $services){global $wgContLang;return new CommentStore($wgContLang, $services->getMainConfig() ->get( 'CommentTableSchemaMigrationStage'));},'ActorMigration'=> function(MediaWikiServices $services){return new ActorMigration($services->getMainConfig() ->get( 'ActorTableSchemaMigrationStage'));},]
$wgDummyLanguageCodes
Functionally the same as $wgExtraLanguageCodes, but deprecated.
caseFold($s)
Return a case-folded representation of $s.
Definition: Language.php:2790
static getMessagesFileName($code)
Definition: Language.php:4490
$wgAllUnicodeFixes
Set this to always convert certain Unicode sequences to modern ones regardless of the content languag...
static isValidBuiltInCode($code)
Returns true if a language code is of a valid form for the purposes of internal customisation of Medi...
Definition: Language.php:363
$cache
Definition: mcc.php:33
getMonthName($key)
Definition: Language.php:926
static isValidCode($code)
Returns true if a language code string is of a valid form, whether or not it exists.
Definition: Language.php:338
uc($str, $first=false)
Convert a string to uppercase.
Definition: Language.php:2667
static getMessagesFor($code)
Get all messages for a given language WARNING: this may take a long time.
Definition: Language.php:4583
ucwordbreaksCallbackMB($matches)
Definition: Language.php:2628
static $strongDirRegex
Directionality test regex for embedBidi().
Definition: Language.php:174
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:2019
needsGenderDistinction()
Whether this language uses gender-dependent namespace aliases.
Definition: Language.php:589
wfIsInfinity($str)
Determine input string is represents as infinity.
getHebrewCalendarMonthNameGen($key)
Definition: Language.php:1004
getURLVariant()
Definition: Language.php:4271
updateConversionTable(Title $title)
Refresh the cache of conversion tables when MediaWiki:Conversiontable* is updated.
Definition: Language.php:4318
convertTitle($title)
Convert a Title object to a string in the preferred variant.
Definition: Language.php:4189
static $rle
Definition: Language.php:158
convert($text)
convert text to different variants of a language.
Definition: Language.php:4179
$wgMetaNamespaceTalk
Name of the project talk namespace.
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:941
getNamespaceIds()
Definition: Language.php:666
getVariantname($code, $usemsg=true)
short names for language variants used for language conversion links.
Definition: Language.php:713
static getFileName($prefix= 'Language', $code, $suffix= '.php')
Get the name of a file for a certain language code.
Definition: Language.php:4478
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:2299
static run($event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:203
static getSuggestedDurations($lang=null)
Get an array of suggested block durations from MediaWiki:Ipboptions.
ucwordbreaks($str)
capitalize words at word breaks
Definition: Language.php:2750
truncateInternal($string, $length, $ellipsis= '...', $adjustLength=true, $measureLength, $getSubstring)
Internal method used for truncation.
Definition: Language.php:3560
userTime($ts, User $user, array $options=[])
Get the formatted time for the given timestamp and formatted for the given user.
Definition: Language.php:2438
getMessage($key)
Definition: Language.php:2586
static $mHebrewCalendarMonthMsgs
Definition: Language.php:95
pipeList(array $list)
Same as commaList, but separate it with the pipe instead.
Definition: Language.php:3467
getMessageFromDB($msg)
Get a message from the MediaWiki namespace.
Definition: Language.php:908
formatNum($number, $nocommafy=false)
Normally we output all numbers in plain en_US style, that is 293,291.235 for twohundredninetythreetho...
Definition: Language.php:3253
recodeInput($s)
Definition: Language.php:2974
$mExtendedSpecialPageAliases
Definition: Language.php:46
namespace and then decline to actually register it & $namespaces
Definition: hooks.txt:941
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
Definition: distributors.txt:9
getPluralRules()
Get the plural rules for the language.
Definition: Language.php:4953
getDefaultDateFormat()
Definition: Language.php:744
static fetchLanguageName($code, $inLanguage=null, $include= 'all')
Definition: Language.php:896
resetNamespaces()
Resets all of the namespace caches.
Definition: Language.php:512
static MapCacheLRU null $grammarTransformations
Cache for grammar rules data.
Definition: Language.php:146
segmentByWord($string)
Some languages such as Chinese require word segmentation, Specify such segmentation when overridden i...
Definition: Language.php:2836
getMagicWords()
Get all magic words from cache.
Definition: Language.php:3147
getGrammarTransformations()
Get the grammar transformations data for the language.
Definition: Language.php:3905
getMonthNamesArray()
Definition: Language.php:933
numLink(Title $title, $offset, $limit, array $query, $link, $tooltipMsg, $class)
Helper function for viewPrevNext() that generates links.
Definition: Language.php:4909
ucfirst($str)
Make a string's first character uppercase.
Definition: Language.php:2647
getOption($oname, $defaultOverride=null, $ignoreHidden=false)
Get the user's current setting for a given option.
Definition: User.php:3087
getHijriCalendarMonthName($key)
Definition: Language.php:1012
static getFallbackFor($code)
Get the first fallback for a given language.
Definition: Language.php:4520
timeanddate($ts, $adj=false, $format=true, $timecorrection=false)
Definition: Language.php:2280
getPluralRuleType($number)
Find the plural rule type appropriate for the given number For example, if the language is set to Ara...
Definition: Language.php:5005
userAdjust($ts, $tz=false)
Used by date() and time() to adjust the time output.
Definition: Language.php:2098
digitTransformTable()
Definition: Language.php:3386
userDate($ts, User $user, array $options=[])
Get the formatted date for the given timestamp and formatted for the given user.
Definition: Language.php:2415
getConvRuleTitle()
Get the conversion rule title, if any.
Definition: Language.php:4925
getMagic($mw)
Fill a MagicWord object with data from here.
Definition: Language.php:3167
$wgExtraNamespaces
Additional namespaces.
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:35
getHumanTimestampInternal(MWTimestamp $ts, MWTimestamp $relativeTo, User $user)
Convert an MWTimestamp into a pretty human-readable timestamp using the given user preferences and re...
Definition: Language.php:2517
getCode()
Get the internal language code for this language object.
Definition: Language.php:4409
replaceGrammarInNamespace($m)
Definition: Language.php:4636
commafy($number)
Adds commas to a given number.
Definition: Language.php:3314
digitGroupingPattern()
Definition: Language.php:3379
sprintfDate($format, $ts, DateTimeZone $zone=null, &$ttl= 'unused')
This is a workalike of PHP's date() function, but with better internationalisation, a reduced set of format characters, and a better escaping format.
Definition: Language.php:1102
$fallback
Definition: MessagesAb.php:11
$digitGroupingPattern
Definition: MessagesAs.php:167
userTimeAndDate($ts, User $user, array $options=[])
Get the formatted date and time for the given timestamp and formatted for the given user...
Definition: Language.php:2461
truncate($string, $length, $ellipsis= '...', $adjustLength=true)
This method is deprecated since 1.31 and kept as alias for truncateForDatabase, which has replaced it...
Definition: Language.php:3491
translateBlockExpiry($str, User $user=null, $now=0)
Definition: Language.php:4084
static romanNumeral($num)
Roman number formatting up to 10000.
Definition: Language.php:1980
convertForSearchResult($termsArray)
Definition: Language.php:2889
removeBadCharLast($string)
Remove bytes that represent an incomplete Unicode character at the end of string (e.g.
Definition: Language.php:3619
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output modifiable & $code
Definition: hooks.txt:790
you have access to all of the normal MediaWiki so you can get a DB use the etc For full docs on the Maintenance class
Definition: maintenance.txt:52
array null $namespaceNames
Definition: Language.php:49
static bcp47($code)
Get the normalised IETF language tag See unit test for examples.
static tsToIranian($ts)
Algorithm by Roozbeh Pournader and Mohammad Toossi to convert Gregorian dates to Iranian dates...
Definition: Language.php:1586
$transformData
ReplacementArray object caches.
Definition: Language.php:55
specialList($page, $details, $oppositedm=true)
Make a list item, used by various special pages.
Definition: Language.php:4841
autoConvert($text, $variant=false)
convert text to a variant
Definition: Language.php:4159
getExtraHashOptions()
returns language specific options used by User::getPageRenderHash() for example, the preferred langua...
Definition: Language.php:4297
getParsedTitle()
For languages that support multiple variants, the title of an article may be displayed differently in...
Definition: Language.php:4308
preConvertPlural($forms, $count)
Checks that convertPlural was given an array and pads it to requested amount of forms by copying the ...
Definition: Language.php:4034
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:4287
$wgGrammarForms
Some languages need different word forms, usually for different cases.
emphasize($text)
Italic is unsuitable for some languages.
Definition: Language.php:3227
getDirMarkEntity($opposite=false)
A hidden direction mark (LRM or RLM), depending on the language direction.
Definition: Language.php:3077
separatorTransformTable()
Definition: Language.php:3393
isRTL()
For right-to-left language support.
Definition: Language.php:3030
getDatePreference()
Get the user's preferred date format.
Definition: User.php:3410
getParentLanguage()
Get the "parent" language which has a converter to convert a "compatible" language (in another varian...
Definition: Language.php:4370
getDir()
Return the correct HTML 'dir' attribute value for this language.
Definition: Language.php:3038
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:302
getFormattedNamespaces()
A convenience function that returns getNamespaces() with spaces instead of underscores in values...
Definition: Language.php:524
$wgLocalTZoffset
Set an offset from UTC in minutes to use for the default timezone setting for anonymous users and new...
checkTitleEncoding($s)
Definition: Language.php:2799
normalizeForSearch($string)
Some languages have special punctuation need to be normalized.
Definition: Language.php:2847
static $mMonthGenMsgs
Definition: Language.php:78
getDurationIntervals($seconds, array $chosenIntervals=[])
Takes a number of seconds and returns an array with a set of corresponding intervals.
Definition: Language.php:2325
static $mWeekdayMsgs
Definition: Language.php:64
linkPrefixExtension()
To allow "foo[[bar]]" to extend the link over the whole word "foobar".
Definition: Language.php:3139
const DB_REPLICA
Definition: defines.php:25
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring which defines all default service and specifies how they depend on each other("wiring").When a new service is added to MediaWiki core
$mNamespaceIds
Definition: Language.php:50
getGrammarForms()
Get the grammar forms for the content language.
Definition: Language.php:3885
wfLogWarning($msg, $callerOffset=1, $level=E_USER_WARNING)
Send a warning as a PHP error and the debug log.
static dateTimeObjFormat(&$dateTimeObj, $ts, $zone, $code)
Pass through result from $dateTimeObj->format()
Definition: Language.php:1024
=Architecture==Two class hierarchies are used to provide the functionality associated with the different content models:*Content interface(and AbstractContent base class) define functionality that acts on the concrete content of a page, and *ContentHandler base class provides functionality specific to a content model, but not acting on concrete content.The most important function of ContentHandler is to act as a factory for the appropriate implementation of Content.These Content objects are to be used by MediaWiki everywhere, instead of passing page content around as text.All manipulation and analysis of page content must be done via the appropriate methods of the Content object.For each content model, a subclass of ContentHandler has to be registered with $wgContentHandlers.The ContentHandler object for a given content model can be obtained using ContentHandler::getForModelID($id).Also Title, WikiPage and Revision now have getContentHandler() methods for convenience.ContentHandler objects are singletons that provide functionality specific to the content type, but not directly acting on the content of some page.ContentHandler::makeEmptyContent() and ContentHandler::unserializeContent() can be used to create a Content object of the appropriate type.However, it is recommended to instead use WikiPage::getContent() resp.Revision::getContent() to get a page's content as a Content object.These two methods should be the ONLY way in which page content is accessed.Another important function of ContentHandler objects is to define custom action handlers for a content model, see ContentHandler::getActionOverrides().This is similar to what WikiPage::getActionOverrides() was already doing.==Serialization==With the ContentHandler facility, page content no longer has to be text based.Objects implementing the Content interface are used to represent and handle the content internally.For storage and data exchange, each content model supports at least one serialization format via ContentHandler::serializeContent($content).The list of supported formats for a given content model can be accessed using ContentHandler::getSupportedFormats().Content serialization formats are identified using MIME type like strings.The following formats are built in:*text/x-wiki-wikitext *text/javascript-for js pages *text/css-for css pages *text/plain-for future use, e.g.with plain text messages.*text/html-for future use, e.g.with plain html messages.*application/vnd.php.serialized-for future use with the api and for extensions *application/json-for future use with the api, and for use by extensions *application/xml-for future use with the api, and for use by extensions In PHP, use the corresponding CONTENT_FORMAT_XXX constant.Note that when using the API to access page content, especially action=edit, action=parse and action=query &prop=revisions, the model and format of the content should always be handled explicitly.Without that information, interpretation of the provided content is not reliable.The same applies to XML dumps generated via maintenance/dumpBackup.php or Special:Export.Also note that the API will provide encapsulated, serialized content-so if the API was called with format=json, and contentformat is also json(or rather, application/json), the page content is represented as a string containing an escaped json structure.Extensions that use JSON to serialize some types of page content may provide specialized API modules that allow access to that content in a more natural form.==Compatibility==The ContentHandler facility is introduced in a way that should allow all existing code to keep functioning at least for pages that contain wikitext or other text based content.However, a number of functions and hooks have been deprecated in favor of new versions that are aware of the page's content model, and will now generate warnings when used.Most importantly, the following functions have been deprecated:*Revisions::getText() is deprecated in favor Revisions::getContent()*WikiPage::getText() is deprecated in favor WikiPage::getContent() Also, the old Article::getContent()(which returns text) is superceded by Article::getContentObject().However, both methods should be avoided since they do not provide clean access to the page's actual content.For instance, they may return a system message for non-existing pages.Use WikiPage::getContent() instead.Code that relies on a textual representation of the page content should eventually be rewritten.However, ContentHandler::getContentText() provides a stop-gap that can be used to get text for a page.Its behavior is controlled by $wgContentHandlerTextFallback it
getGenderNsText($index, $gender)
Returns gender-dependent namespace alias if available.
Definition: Language.php:574
static decode($value, $assoc=false)
Decodes a JSON string.
Definition: FormatJson.php:187
static getDefaultOption($opt)
Get a given default option value.
Definition: User.php:1756
initContLang()
Hook which will be called if this is the content language.
Definition: Language.php:439
static factory($code)
Get a cached or new language object for a given language code.
Definition: Language.php:183
fixVariableInNamespace($talk)
Definition: Language.php:4615
getNamespaces()
Returns an array of localised namespaces indexed by their numbers.
Definition: Language.php:464
wfGetPrecompiledData($name)
Get an object from the precompiled serialized directory.
$mMagicHookDone
Definition: Language.php:42
gender($gender, $forms)
Provides an alternative text depending on specified gender.
Definition: Language.php:3954
commaList(array $list)
Take a list of strings and build a locale-friendly comma-separated list, using the local comma-separa...
Definition: Language.php:3442
static $mHebrewCalendarMonthGenMsgs
Definition: Language.php:103
const NS_USER_TALK
Definition: Defines.php:68
static array $languagesWithVariants
languages supporting variants
truncateForDatabase($string, $length, $ellipsis= '...', $adjustLength=true)
Truncate a string to a specified length in bytes, appending an optional string (e.g.
Definition: Language.php:3510
static element($element, $attribs=[], $contents= '')
Identical to rawElement(), but HTML-escapes $contents (like Xml::element()).
Definition: Html.php:231
static getCanonicalIndex($name)
Returns the index for a given canonical name, or NULL The input must be converted to lower case first...
truncateForVisual($string, $length, $ellipsis= '...', $adjustLength=true)
Truncate a string to a specified number of characters, appending an optional string (e...
Definition: Language.php:3534
static getCanonicalNamespaces($rebuild=false)
Returns array of all defined namespaces with their canonical (English) names.
getMonthAbbreviationsArray()
Definition: Language.php:960
hasWordBreaks()
Most writing systems use whitespace to break up words.
Definition: Language.php:2825
static strongDirFromContent($text= '')
Gets directionality of the first strongly directional codepoint, for embedBidi()
Definition: Language.php:1963
convertNamespace($ns, $variant=null)
Convert a namespace index to a string in the preferred variant.
Definition: Language.php:4201
static isSupportedLanguage($code)
Checks whether any localisation is available for that language tag in MediaWiki (MessagesXx.php exists).
Definition: Language.php:256
alignStart()
Return 'left' or 'right' as appropriate alignment for line-start for this language's text direction...
Definition: Language.php:3050
static tsToYear($ts, $cName)
Algorithm to convert Gregorian dates to Thai solar dates, Minguo dates or Minguo dates.
Definition: Language.php:1877
getPluralRuleIndexNumber($number)
Find the index number of the plural rule appropriate for the given number.
Definition: Language.php:4991
see documentation in includes Linker php for Linker::makeImageLink & $time
Definition: hooks.txt:1827
static hebrewYearStart($year)
This calculates the Hebrew year start, as days since 1 September.
Definition: Language.php:1839
please add to it if you re going to add events to the MediaWiki code where normally authentication against an external auth plugin would be creating a local account $user
Definition: hooks.txt:244
static $GREG_DAYS
Definition: Language.php:1571
getHebrewCalendarMonthName($key)
Definition: Language.php:996
unsegmentForDiff($text)
and unsegment to show the result
Definition: Language.php:4137
$matches