1<?php 2 3namespace dokuwiki\Utf8; 4 5/** 6 * DokuWiki sort functions 7 * 8 * When "intl" extension is available, all sorts are done using a collator. 9 * Otherwise, primitive PHP functions are called. 10 * 11 * The collator is created using the locale given in $conf['lang']. 12 * It always uses case insensitive "natural" ordering in its collation. 13 * The fallback solution uses the primitive PHP functions that return almost the same results 14 * when the input is text with only [A-Za-z0-9] characters. 15 * 16 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 17 * @author Moisés Braga Ribeiro <moisesbr@gmail.com> 18 * @author Andreas Gohr <andi@splitbrain.org> 19 */ 20class Sort 21{ 22 /** @var \Collator[] language specific collators, usually only one */ 23 protected static $collators = []; 24 25 /** @var bool should the intl extension be used if available? For testing only */ 26 protected static $useIntl = true; 27 28 /** 29 * Initialization of a collator using $conf['lang'] as the locale. 30 * The initialization is done only once. 31 * The collation takes "natural ordering" into account, that is, "page 2" is before "page 10". 32 * 33 * @return \Collator Returns a configured collator or null if the collator cannot be created. 34 * 35 * @author Moisés Braga Ribeiro <moisesbr@gmail.com> 36 */ 37 protected static function getCollator() 38 { 39 global $conf; 40 $lc = $conf['lang']; 41 42 // check if intl extension is available 43 if (!self::$useIntl || !class_exists('\Collator')) { 44 return null; 45 } 46 47 // load collator if not available yet 48 if (!isset(self::$collators[$lc])) { 49 $collator = \Collator::create($lc); 50 if (!isset($collator)) return null; // check needed as stated in the docs 51 $collator->setAttribute(\Collator::NUMERIC_COLLATION, \Collator::ON); 52 dbglog('Collator created with locale "' . $lc . '": numeric collation on, ' . 53 'valid locale "' . $collator->getLocale(\Locale::VALID_LOCALE) . '", ' . 54 'actual locale "' . $collator->getLocale(\Locale::ACTUAL_LOCALE) . '"'); 55 self::$collators[$lc] = $collator; 56 } 57 58 return self::$collators[$lc]; 59 } 60 61 /** 62 * Enable or disable the use of the "intl" extension collator. 63 * This is used for testing and should not be used in normal code. 64 * 65 * @param bool $use 66 * 67 * @author Andreas Gohr <andi@splitbrain.org> 68 */ 69 public static function useIntl($use = true) 70 { 71 self::$useIntl = $use; 72 } 73 74 /** 75 * Drop-in replacement for strcmp(), strcasecmp(), strnatcmp() and strnatcasecmp(). 76 * It uses a collator-based comparison, or strnatcasecmp() as a fallback. 77 * 78 * @param string $str1 The first string. 79 * @param string $str2 The second string. 80 * @return int Returns < 0 if $str1 is less than $str2; > 0 if $str1 is greater than $str2, and 0 if they are equal. 81 * 82 * @author Moisés Braga Ribeiro <moisesbr@gmail.com> 83 */ 84 public static function strcmp($str1, $str2) 85 { 86 $collator = self::getCollator(); 87 if (isset($collator)) { 88 return $collator->compare($str1, $str2); 89 } else { 90 return strnatcasecmp($str1, $str2); 91 } 92 } 93 94 /** 95 * Drop-in replacement for sort(). 96 * It uses a collator-based sort, or sort() with flags SORT_NATURAL and SORT_FLAG_CASE as a fallback. 97 * 98 * @param array $array The input array. 99 * @return bool Returns true on success or false on failure. 100 * 101 * @author Moisés Braga Ribeiro <moisesbr@gmail.com> 102 */ 103 public static function sort(&$array) 104 { 105 $collator = self::getCollator(); 106 if (isset($collator)) { 107 return $collator->sort($array); 108 } else { 109 return sort($array, SORT_NATURAL | SORT_FLAG_CASE); 110 } 111 } 112 113 /** 114 * Drop-in replacement for ksort(). 115 * It uses a collator-based sort, or ksort() with flags SORT_NATURAL and SORT_FLAG_CASE as a fallback. 116 * 117 * @param array $array The input array. 118 * @return bool Returns true on success or false on failure. 119 * 120 * @author Moisés Braga Ribeiro <moisesbr@gmail.com> 121 */ 122 public static function ksort(&$array) 123 { 124 $collator = self::getCollator(); 125 if (isset($collator)) { 126 return uksort($array, array($collator, 'compare')); 127 } else { 128 return ksort($array, SORT_NATURAL | SORT_FLAG_CASE); 129 } 130 } 131 132 /** 133 * Drop-in replacement for asort(), natsort() and natcasesort(). 134 * It uses a collator-based sort, or asort() with flags SORT_NATURAL and SORT_FLAG_CASE as a fallback. 135 * 136 * @param array $array The input array. 137 * @return bool Returns true on success or false on failure. 138 * 139 * @author Moisés Braga Ribeiro <moisesbr@gmail.com> 140 */ 141 public static function asort(&$array) 142 { 143 $collator = self::getCollator(); 144 if (isset($collator)) { 145 return $collator->asort($array); 146 } else { 147 return asort($array, SORT_NATURAL | SORT_FLAG_CASE); 148 } 149 } 150 151 /** 152 * Drop-in replacement for asort(), natsort() and natcasesort() when the parameter is an array of filenames. 153 * Filenames may not be equal to page names, depending on the setting in $conf['fnencode'], 154 * so the correct behavior is to sort page names and reflect this sorting in the filename array. 155 * 156 * @param array $array The input array. 157 * @return bool Returns true on success or false on failure. 158 * 159 * @author Moisés Braga Ribeiro <moisesbr@gmail.com> 160 * @author Andreas Gohr <andi@splitbrain.org> 161 */ 162 public static function asortFN(&$array) 163 { 164 $collator = self::getCollator(); 165 return uasort($array, function ($fn1, $fn2) use ($collator) { 166 if (isset($collator)) { 167 return $collator->compare(utf8_decodeFN($fn1), utf8_decodeFN($fn2)); 168 } else { 169 return strnatcasecmp(utf8_decodeFN($fn1), utf8_decodeFN($fn2)); 170 } 171 }); 172 } 173} 174