MediaWiki fundraising/REL1_35
IcuCollation.php
Go to the documentation of this file.
1<?php
22
26class IcuCollation extends Collation {
27 private const FIRST_LETTER_VERSION = 4;
28
31
34
36 private $locale;
37
40
42 private $useNumericCollation = false;
43
46
56 private static $cjkBlocks = [
57 [ 0x2E80, 0x2EFF ], // CJK Radicals Supplement
58 [ 0x2F00, 0x2FDF ], // Kangxi Radicals
59 [ 0x2FF0, 0x2FFF ], // Ideographic Description Characters
60 [ 0x3000, 0x303F ], // CJK Symbols and Punctuation
61 [ 0x31C0, 0x31EF ], // CJK Strokes
62 [ 0x3200, 0x32FF ], // Enclosed CJK Letters and Months
63 [ 0x3300, 0x33FF ], // CJK Compatibility
64 [ 0x3400, 0x4DBF ], // CJK Unified Ideographs Extension A
65 [ 0x4E00, 0x9FFF ], // CJK Unified Ideographs
66 [ 0xF900, 0xFAFF ], // CJK Compatibility Ideographs
67 [ 0xFE30, 0xFE4F ], // CJK Compatibility Forms
68 [ 0x20000, 0x2A6DF ], // CJK Unified Ideographs Extension B
69 [ 0x2A700, 0x2B73F ], // CJK Unified Ideographs Extension C
70 [ 0x2B740, 0x2B81F ], // CJK Unified Ideographs Extension D
71 [ 0x2F800, 0x2FA1F ], // CJK Compatibility Ideographs Supplement
72 ];
73
95 private static $tailoringFirstLetters = [
96 'af' => [],
97 'am' => [],
98 'ar' => [],
99 'as' => [ "\u{0982}", "\u{0981}", "\u{0983}", "\u{09CE}", "ক্ষ " ],
100 'ast' => [ "Ch", "Ll", "Ñ" ], // not in libicu
101 'az' => [ "Ç", "Ə", "Ğ", "İ", "Ö", "Ş", "Ü" ],
102 'be' => [ "Ё" ],
103 'be-tarask' => [ "Ё" ],
104 'bg' => [],
105 'bn' => [ 'ং', 'ঃ', 'ঁ' ],
106 'bn@collation=traditional' => [
107 'ং', 'ঃ', 'ঁ', 'ক্', 'খ্', 'গ্', 'ঘ্', 'ঙ্', 'চ্', 'ছ্', 'জ্', 'ঝ্',
108 'ঞ্', 'ট্', 'ঠ্', 'ড্', 'ঢ্', 'ণ্', 'ৎ', 'থ্', 'দ্', 'ধ্', 'ন্', 'প্',
109 'ফ্', 'ব্', 'ভ্', 'ম্', 'য্', 'র্', 'ৰ্', 'ল্', 'ৱ্', 'শ্', 'ষ্', 'স্', 'হ্'
110 ],
111 'bo' => [],
112 'br' => [ "Ch", "C'h" ],
113 'bs' => [ "Č", "Ć", "Dž", "Đ", "Lj", "Nj", "Š", "Ž" ],
114 'bs-Cyrl' => [],
115 'ca' => [],
116 'chr' => [],
117 'co' => [], // not in libicu
118 'cs' => [ "Č", "Ch", "Ř", "Š", "Ž" ],
119 'cy' => [ "Ch", "Dd", "Ff", "Ng", "Ll", "Ph", "Rh", "Th" ],
120 'da' => [ "Æ", "Ø", "Å" ],
121 'de' => [],
122 'de-AT@collation=phonebook' => [ 'ä', 'ö', 'ü', 'ß' ],
123 'dsb' => [ "Č", "Ć", "Dź", "Ě", "Ch", "Ł", "Ń", "Ŕ", "Š", "Ś", "Ž", "Ź" ],
124 'ee' => [ "Dz", "Ɖ", "Ɛ", "Ƒ", "Gb", "Ɣ", "Kp", "Ny", "Ŋ", "Ɔ", "Ts", "Ʋ" ],
125 'el' => [],
126 'en' => [],
127 'eo' => [ "Ĉ", "Ĝ", "Ĥ", "Ĵ", "Ŝ", "Ŭ" ],
128 'es' => [ "Ñ" ],
129 'et' => [ "Š", "Ž", "Õ", "Ä", "Ö", "Ü" ],
130 'eu' => [ "Ñ" ], // not in libicu
131 'fa' => [
132 // RTL, let's put each letter on a new line
133 "آ",
134 "ء",
135 "ه",
136 "ا",
137 "و"
138 ],
139 'fi' => [ "Å", "Ä", "Ö" ],
140 'fil' => [ "Ñ", "Ng" ],
141 'fo' => [ "Á", "Ð", "Í", "Ó", "Ú", "Ý", "Æ", "Ø", "Å" ],
142 'fr' => [],
143 'fr-CA' => [], // fr-CA sorts accents slightly different from fr.
144 'fur' => [ "À", "Á", "Â", "È", "Ì", "Ò", "Ù" ], // not in libicu
145 'fy' => [], // not in libicu
146 'ga' => [],
147 'gd' => [], // not in libicu
148 'gl' => [ "Ch", "Ll", "Ñ" ],
149 'gu' => [ "\u{0A82}", "\u{0A83}", "\u{0A81}", "\u{0AB3}" ],
150 'ha' => [ 'Ɓ', 'Ɗ', 'Ƙ', 'Sh', 'Ts', 'Ƴ' ],
151 'haw' => [ 'ʻ' ],
152 'he' => [],
153 'hi' => [ "\u{0902}", "\u{0903}" ],
154 'hr' => [ "Č", "Ć", "Dž", "Đ", "Lj", "Nj", "Š", "Ž" ],
155 'hsb' => [ "Č", "Dź", "Ě", "Ch", "Ł", "Ń", "Ř", "Š", "Ć", "Ž" ],
156 'hu' => [ "Cs", "Dz", "Dzs", "Gy", "Ly", "Ny", "Ö", "Sz", "Ty", "Ü", "Zs" ],
157 'hy' => [ "և" ],
158 'id' => [],
159 'ig' => [ "Ch", "Gb", "Gh", "Gw", "Ị", "Kp", "Kw", "Ṅ", "Nw", "Ny", "Ọ", "Sh", "Ụ" ],
160 'is' => [ "Á", "Ð", "É", "Í", "Ó", "Ú", "Ý", "Þ", "Æ", "Ö", "Å" ],
161 'it' => [],
162 'ka' => [],
163 'kk' => [ "Ү", "І" ],
164 'kl' => [ "Æ", "Ø", "Å" ],
165 'km' => [
166 "រ", "ឫ", "ឬ", "ល", "ឭ", "ឮ", "\u{17BB}\u{17C6}",
167 "\u{17C6}", "\u{17B6}\u{17C6}", "\u{17C7}",
168 "\u{17B7}\u{17C7}", "\u{17BB}\u{17C7}",
169 "\u{17C1}\u{17C7}", "\u{17C4}\u{17C7}",
170 ],
171 'kn' => [ "\u{0C81}", "\u{0C83}", "\u{0CF1}", "\u{0CF2}" ],
172 'kok' => [ "\u{0902}", "\u{0903}", "ळ", "क्ष" ],
173 'ku' => [ "Ç", "Ê", "Î", "Ş", "Û" ], // not in libicu
174 'ky' => [ "Ё" ],
175 'la' => [], // not in libicu
176 'lb' => [],
177 'lkt' => [ 'Č', 'Ǧ', 'Ȟ', 'Š', 'Ž' ],
178 'ln' => [ 'Ɛ' ],
179 'lo' => [],
180 'lt' => [ "Č", "Š", "Ž" ],
181 'lv' => [ "Č", "Ģ", "Ķ", "Ļ", "Ņ", "Š", "Ž" ],
182 'mk' => [ "Ѓ", "Ќ" ],
183 'ml' => [],
184 'mn' => [],
185 'mo' => [ "Ă", "Â", "Î", "Ș", "Ț" ], // not in libicu
186 'mr' => [ "\u{0902}", "\u{0903}", "ळ", "क्ष", "ज्ञ" ],
187 'ms' => [],
188 'mt' => [ "Ċ", "Ġ", "Għ", "Ħ", "Ż" ],
189 'nb' => [ "Æ", "Ø", "Å" ],
190 'ne' => [],
191 'nl' => [],
192 'nn' => [ "Æ", "Ø", "Å" ],
193 'no' => [ "Æ", "Ø", "Å" ], // not in libicu. You should probably use nb or nn instead.
194 'oc' => [], // not in libicu
195 'om' => [ 'Ch', 'Dh', 'Kh', 'Ny', 'Ph', 'Sh' ],
196 'or' => [ "\u{0B01}", "\u{0B02}", "\u{0B03}", "କ୍ଷ" ],
197 'pa' => [ "\u{0A4D}" ],
198 'pl' => [ "Ą", "Ć", "Ę", "Ł", "Ń", "Ó", "Ś", "Ź", "Ż" ],
199 'pt' => [],
200 'rm' => [], // not in libicu
201 'ro' => [ "Ă", "Â", "Î", "Ș", "Ț" ],
202 'ru' => [],
203 'rup' => [ "Ă", "Â", "Î", "Ľ", "Ń", "Ș", "Ț" ], // not in libicu
204 'sco' => [],
205 'se' => [
206 'Á', 'Č', 'Ʒ', 'Ǯ', 'Đ', 'Ǧ', 'Ǥ', 'Ǩ', 'Ŋ',
207 'Š', 'Ŧ', 'Ž', 'Ø', 'Æ', 'Ȧ', 'Ä', 'Ö'
208 ],
209 'si' => [ "\u{0D82}", "\u{0D83}", "\u{0DA4}" ],
210 'sk' => [ "Ä", "Č", "Ch", "Ô", "Š", "Ž" ],
211 'sl' => [ "Č", "Š", "Ž" ],
212 'smn' => [ "Á", "Č", "Đ", "Ŋ", "Š", "Ŧ", "Ž", "Æ", "Ø", "Å", "Ä", "Ö" ],
213 'sq' => [ "Ç", "Dh", "Ë", "Gj", "Ll", "Nj", "Rr", "Sh", "Th", "Xh", "Zh" ],
214 'sr' => [],
215 'sr-Latn' => [ "Č", "Ć", "Dž", "Đ", "Lj", "Nj", "Š", "Ž" ],
216 'sv' => [ "Å", "Ä", "Ö" ],
217 'sv@collation=standard' => [ "Å", "Ä", "Ö" ],
218 'sw' => [],
219 'ta' => [
220 "\u{0B82}", "ஃ", "க்ஷ", "க்", "ங்", "ச்", "ஞ்", "ட்", "ண்", "த்", "ந்",
221 "ப்", "ம்", "ய்", "ர்", "ல்", "வ்", "ழ்", "ள்", "ற்", "ன்", "ஜ்", "ஶ்", "ஷ்",
222 "ஸ்", "ஹ்", "க்ஷ்"
223 ],
224 'te' => [ "\u{0C01}", "\u{0C02}", "\u{0C03}" ],
225 'th' => [ "ฯ", "\u{0E46}", "\u{0E4D}", "\u{0E3A}" ],
226 'tk' => [ "Ç", "Ä", "Ž", "Ň", "Ö", "Ş", "Ü", "Ý" ],
227 'tl' => [ "Ñ", "Ng" ], // not in libicu
228 'to' => [ "Ng", "ʻ" ],
229 'tr' => [ "Ç", "Ğ", "İ", "Ö", "Ş", "Ü" ],
230 '-tr' => [ "ı" ],
231 'tt' => [ "Ә", "Ө", "Ү", "Җ", "Ң", "Һ" ], // not in libicu
232 'uk' => [ "Ґ", "Ь" ],
233 'uz' => [ "Ch", "G'", "Ng", "O'", "Sh" ], // not in libicu
234 'vi' => [ "Ă", "Â", "Đ", "Ê", "Ô", "Ơ", "Ư" ],
235 'vo' => [ "Ä", "Ö", "Ü" ],
236 'yi' => [
237 "\u{05D1}\u{05BF}", "\u{05DB}\u{05BC}", "\u{05E4}\u{05BC}",
238 "\u{05E9}\u{05C2}", "\u{05EA}\u{05BC}"
239 ],
240 'yo' => [ "Ẹ", "Gb", "Ọ", "Ṣ" ],
241 'zu' => [],
242 ];
243
244 public function __construct( $locale ) {
245 if ( !extension_loaded( 'intl' ) ) {
246 throw new MWException( 'An ICU collation was requested, ' .
247 'but the intl extension is not available.' );
248 }
249
250 $this->locale = $locale;
251 // Drop everything after the '@' in locale's name
252 $localeParts = explode( '@', $locale );
253 $this->digitTransformLanguage = MediaWikiServices::getInstance()->getLanguageFactory()
254 ->getLanguage( $locale === 'root' ? 'en' : $localeParts[0] );
255
256 $this->mainCollator = Collator::create( $locale );
257 if ( !$this->mainCollator ) {
258 throw new MWException( "Invalid ICU locale specified for collation: $locale" );
259 }
260
261 $this->primaryCollator = Collator::create( $locale );
262 $this->primaryCollator->setStrength( Collator::PRIMARY );
263
264 // If the special suffix for numeric collation is present, turn on numeric collation.
265 if ( substr( $locale, -5, 5 ) === '-u-kn' ) {
266 $this->useNumericCollation = true;
267 // Strip off the special suffix so it doesn't trip up fetchFirstLetterData().
268 $this->locale = substr( $this->locale, 0, -5 );
269 $this->mainCollator->setAttribute( Collator::NUMERIC_COLLATION, Collator::ON );
270 $this->primaryCollator->setAttribute( Collator::NUMERIC_COLLATION, Collator::ON );
271 }
272 }
273
274 public function getSortKey( $string ) {
275 return $this->mainCollator->getSortKey( $string );
276 }
277
278 public function getPrimarySortKey( $string ) {
279 return $this->primaryCollator->getSortKey( $string );
280 }
281
282 public function getFirstLetter( $string ) {
283 $string = strval( $string );
284 if ( $string === '' ) {
285 return '';
286 }
287
288 $firstChar = mb_substr( $string, 0, 1, 'UTF-8' );
289
290 // If the first character is a CJK character, just return that character.
291 if ( ord( $firstChar ) > 0x7f && self::isCjk( UtfNormal\Utils::utf8ToCodepoint( $firstChar ) ) ) {
292 return $firstChar;
293 }
294
295 $sortKey = $this->getPrimarySortKey( $string );
296
297 // Do a binary search to find the correct letter to sort under
298 $min = ArrayUtils::findLowerBound(
299 [ $this, 'getSortKeyByLetterIndex' ],
300 $this->getFirstLetterCount(),
301 'strcmp',
302 $sortKey );
303
304 if ( $min === false ) {
305 // Before the first letter
306 return '';
307 }
308
309 $sortLetter = $this->getLetterByIndex( $min );
310
311 if ( $this->useNumericCollation ) {
312 // If the sort letter is a number, return '0–9' (or localized equivalent).
313 // ASCII value of 0 is 48. ASCII value of 9 is 57.
314 // Note that this also applies to non-Arabic numerals since they are
315 // mapped to Arabic numeral sort letters. For example, ২ sorts as 2.
316 if ( ord( $sortLetter ) >= 48 && ord( $sortLetter ) <= 57 ) {
317 $sortLetter = wfMessage( 'category-header-numerals' )->numParams( 0, 9 )->text();
318 }
319 }
320 return $sortLetter;
321 }
322
327 public function getFirstLetterData() {
328 if ( $this->firstLetterData === null ) {
329 $cache = ObjectCache::getLocalServerInstance( CACHE_ANYTHING );
330 $cacheKey = $cache->makeKey(
331 'first-letters',
332 static::class,
333 $this->locale,
334 $this->digitTransformLanguage->getCode(),
335 INTL_ICU_VERSION,
336 self::FIRST_LETTER_VERSION
337 );
338 $this->firstLetterData = $cache->getWithSetCallback( $cacheKey, $cache::TTL_WEEK, function () {
339 return $this->fetchFirstLetterData();
340 } );
341 }
343 }
344
349 private function fetchFirstLetterData() {
350 global $IP;
351 // Generate data from serialized data file
352 if ( isset( self::$tailoringFirstLetters[$this->locale] ) ) {
353 $letters = require "$IP/includes/collation/data/first-letters-root.php";
354 // Append additional characters
355 $letters = array_merge( $letters, self::$tailoringFirstLetters[$this->locale] );
356 // Remove unnecessary ones, if any
357 if ( isset( self::$tailoringFirstLetters['-' . $this->locale] ) ) {
358 $letters = array_diff( $letters, self::$tailoringFirstLetters['-' . $this->locale] );
359 }
360 // Apply digit transforms
361 $digits = [ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' ];
362 $letters = array_diff( $letters, $digits );
363 foreach ( $digits as $digit ) {
364 $letters[] = $this->digitTransformLanguage->formatNum( $digit, true );
365 }
366 } elseif ( $this->locale === 'root' ) {
367 $letters = require "$IP/includes/collation/data/first-letters-root.php";
368 } else {
369 // FIXME: Is this still used?
370 $letters = wfGetPrecompiledData( "first-letters-{$this->locale}.ser" );
371 if ( $letters === false ) {
372 throw new MWException( "MediaWiki does not support ICU locale " .
373 "\"{$this->locale}\"" );
374 }
375 }
376
377 /* Sort the letters.
378 *
379 * It's impossible to have the precompiled data file properly sorted,
380 * because the sort order changes depending on ICU version. If the
381 * array is not properly sorted, the binary search will return random
382 * results.
383 *
384 * We also take this opportunity to remove primary collisions.
385 */
386 $letterMap = [];
387 foreach ( $letters as $letter ) {
388 $key = $this->getPrimarySortKey( $letter );
389 if ( isset( $letterMap[$key] ) ) {
390 // Primary collision (two characters with the same sort position).
391 // Keep whichever one sorts first in the main collator.
392 $comp = $this->mainCollator->compare( $letter, $letterMap[$key] );
393 wfDebug( "Primary collision '$letter' '{$letterMap[$key]}' (comparison: $comp)" );
394 // If that also has a collision, use codepoint as a tiebreaker.
395 if ( $comp === 0 ) {
396 $comp = UtfNormal\Utils::utf8ToCodepoint( $letter ) <=>
397 UtfNormal\Utils::utf8ToCodepoint( $letterMap[$key] );
398 }
399 if ( $comp < 0 ) {
400 $letterMap[$key] = $letter;
401 }
402 } else {
403 $letterMap[$key] = $letter;
404 }
405 }
406 ksort( $letterMap, SORT_STRING );
407
408 /* Remove duplicate prefixes. Basically if something has a sortkey
409 * which is a prefix of some other sortkey, then it is an
410 * expansion and probably should not be considered a section
411 * header.
412 *
413 * For example 'þ' is sometimes sorted as if it is the letters
414 * 'th'. Other times it is its own primary element. Another
415 * example is '₨'. Sometimes its a currency symbol. Sometimes it
416 * is an 'R' followed by an 's'.
417 *
418 * Additionally an expanded element should always sort directly
419 * after its first element due to they way sortkeys work.
420 *
421 * UCA sortkey elements are of variable length but no collation
422 * element should be a prefix of some other element, so I think
423 * this is safe. See:
424 * - https://ssl.icu-project.org/repos/icu/icuhtml/trunk/design/collation/ICU_collation_design.htm
425 * - http://site.icu-project.org/design/collation/uca-weight-allocation
426 *
427 * Additionally, there is something called primary compression to
428 * worry about. Basically, if you have two primary elements that
429 * are more than one byte and both start with the same byte then
430 * the first byte is dropped on the second primary. Additionally
431 * either \x03 or \xFF may be added to mean that the next primary
432 * does not start with the first byte of the first primary.
433 *
434 * This shouldn't matter much, as the first primary is not
435 * changed, and that is what we are comparing against.
436 *
437 * tl;dr: This makes some assumptions about how icu implements
438 * collations. It seems incredibly unlikely these assumptions
439 * will change, but nonetheless they are assumptions.
440 */
441
442 $prev = false;
443 $duplicatePrefixes = [];
444 foreach ( $letterMap as $key => $value ) {
445 // Remove terminator byte. Otherwise the prefix
446 // comparison will get hung up on that.
447 $trimmedKey = rtrim( $key, "\0" );
448 if ( $prev === false || $prev === '' ) {
449 $prev = $trimmedKey;
450 // We don't yet have a collation element
451 // to compare against, so continue.
452 continue;
453 }
454
455 // Due to the fact the array is sorted, we only have
456 // to compare with the element directly previous
457 // to the current element (skipping expansions).
458 // An element "X" will always sort directly
459 // before "XZ" (Unless we have "XY", but we
460 // do not update $prev in that case).
461 if ( substr( $trimmedKey, 0, strlen( $prev ) ) === $prev ) {
462 $duplicatePrefixes[] = $key;
463 // If this is an expansion, we don't want to
464 // compare the next element to this element,
465 // but to what is currently $prev
466 continue;
467 }
468 $prev = $trimmedKey;
469 }
470 foreach ( $duplicatePrefixes as $badKey ) {
471 wfDebug( "Removing '{$letterMap[$badKey]}' from first letters." );
472 unset( $letterMap[$badKey] );
473 // This code assumes that unsetting does not change sort order.
474 }
475 $data = [
476 'chars' => array_values( $letterMap ),
477 'keys' => array_keys( $letterMap ),
478 ];
479
480 // Reduce memory usage before caching
481 unset( $letterMap );
482
483 return $data;
484 }
485
491 public function getLetterByIndex( $index ) {
492 return $this->getFirstLetterData()['chars'][$index];
493 }
494
500 public function getSortKeyByLetterIndex( $index ) {
501 return $this->getFirstLetterData()['keys'][$index];
502 }
503
508 public function getFirstLetterCount() {
509 return count( $this->getFirstLetterData()['chars'] );
510 }
511
518 public static function isCjk( $codepoint ) {
519 foreach ( self::$cjkBlocks as $block ) {
520 if ( $codepoint >= $block[0] && $codepoint <= $block[1] ) {
521 return true;
522 }
523 }
524 return false;
525 }
526
534 public static function getUnicodeVersionForICU() {
535 $icuVersion = INTL_ICU_VERSION;
536 if ( !$icuVersion ) {
537 return false;
538 }
539
540 $versionPrefix = substr( $icuVersion, 0, 3 );
541 // Source: http://site.icu-project.org/download
542 $map = [
543 '69.' => '13.0',
544 '68.' => '13.0',
545 '67.' => '13.0',
546 '66.' => '13.0',
547 '65.' => '12.0',
548 '64.' => '12.0',
549 '63.' => '11.0',
550 '62.' => '11.0',
551 '61.' => '10.0',
552 '60.' => '10.0',
553 '59.' => '9.0',
554 '58.' => '9.0',
555 '57.' => '8.0',
556 '56.' => '8.0',
557 '55.' => '7.0',
558 '54.' => '7.0',
559 '53.' => '6.3',
560 '52.' => '6.3',
561 '51.' => '6.2',
562 '50.' => '6.2',
563 '49.' => '6.1',
564 '4.8' => '6.0',
565 '4.6' => '6.0',
566 '4.4' => '5.2',
567 '4.2' => '5.1',
568 '4.0' => '5.1',
569 '3.8' => '5.0',
570 '3.6' => '5.0',
571 '3.4' => '4.1',
572 ];
573
574 return $map[$versionPrefix] ?? false;
575 }
576}
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
wfGetPrecompiledData( $name)
Get an object from the precompiled serialized directory.
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
getLetterByIndex( $index)
getSortKeyByLetterIndex( $index)
getFirstLetter( $string)
Given a string, return the logical "first letter" to be used for grouping on category pages and so on...
bool $useNumericCollation
getPrimarySortKey( $string)
__construct( $locale)
static isCjk( $codepoint)
Test if a code point is a CJK (Chinese, Japanese, Korean) character.
const FIRST_LETTER_VERSION
Collator $mainCollator
static $cjkBlocks
Unified CJK blocks.
Language $digitTransformLanguage
Collator $primaryCollator
getSortKey( $string)
Given a string, convert it to a (hopefully short) key that can be used for efficient sorting.
array $firstLetterData
static $tailoringFirstLetters
Additional characters (or character groups) to be considered separate letters for given languages,...
static getUnicodeVersionForICU()
Return the version of Unicode appropriate for the version of ICU library currently in use,...
Internationalisation code See https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation for more...
Definition Language.php:41
MediaWiki exception.
MediaWikiServices is the service locator for the application scope of MediaWiki.
const CACHE_ANYTHING
Definition Defines.php:91
$cache
Definition mcc.php:33
$IP
Definition rebuild.php:19