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 */ 19class Sort 20{ 21 /** @var \Collator[] language specific collators, usually only one */ 22 protected static $collator = []; 23 24 /** @var bool should the intl extension be used if available? For testing only */ 25 protected static $useIntl = true; 26 27 /** 28 * Initialization of a collator using $conf['lang'] as the locale. 29 * The initialization is done only once, except when $reload is set to true. 30 * The collation takes "natural ordering" into account, that is, "page 2" is before "page 10". 31 * 32 * @param bool $reload Usually false; true forces collator re-creation 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($reload = false) 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 ($reload || !isset(self::$collator[$lc])) { 49 $collator = \Collator::create($lc); 50 if (!isset($collator)) return null; 51 $collator->setAttribute(\Collator::NUMERIC_COLLATION, \Collator::ON); 52 self::$collator[$lc] = $collator; 53 } 54 55 return self::$collator[$lc]; 56 } 57 58 /** 59 * Enable or disable the use of the intl extension collator 60 * 61 * This is mostly used for testing and should not be used in normal code 62 * 63 * @param bool $use 64 */ 65 public static function useIntl($use = true) 66 { 67 self::$useIntl = $use; 68 self::$collator = []; 69 } 70 71 /** 72 * Drop-in replacement for strcmp(), strcasecmp(), strnatcmp() and strnatcasecmp(). 73 * It uses a collator-based comparison, or strnatcasecmp() as a fallback. 74 * 75 * @param string $str1 The first string. 76 * @param string $str2 The second string. 77 * @return int Returns < 0 if $str1 is less than $str2; > 0 if $str1 is greater than $str2, and 0 if they are equal. 78 * 79 * @author Moisés Braga Ribeiro <moisesbr@gmail.com> 80 */ 81 public static function strcmp($str1, $str2) 82 { 83 $collator = self::getCollator(); 84 if (isset($collator)) { 85 return $collator->compare($str1, $str2); 86 } else { 87 return strnatcasecmp($str1, $str2); 88 } 89 } 90 91 /** 92 * Drop-in replacement for sort(). 93 * It uses a collator-based sort, or sort() with flags SORT_NATURAL and SORT_FLAG_CASE as a fallback. 94 * 95 * @param array $array The input array. 96 * @return bool Returns true on success or false on failure. 97 * 98 * @author Moisés Braga Ribeiro <moisesbr@gmail.com> 99 */ 100 public static function sort(&$array) 101 { 102 $collator = self::getCollator(); 103 if (isset($collator)) { 104 return $collator->sort($array); 105 } else { 106 return sort($array, SORT_NATURAL | SORT_FLAG_CASE); 107 } 108 } 109 110 /** 111 * Drop-in replacement for ksort(). 112 * It uses a collator-based sort, or ksort() with flags SORT_NATURAL and SORT_FLAG_CASE as a fallback. 113 * 114 * @param array $array The input array. 115 * @return bool Returns true on success or false on failure. 116 * 117 * @author Moisés Braga Ribeiro <moisesbr@gmail.com> 118 */ 119 public static function ksort(&$array) 120 { 121 $collator = self::getCollator(); 122 if (isset($collator)) { 123 return uksort($array, array($collator, 'compare')); 124 } else { 125 return ksort($array, SORT_NATURAL | SORT_FLAG_CASE); 126 } 127 } 128 129 /** 130 * Drop-in replacement for asort(), natsort() and natcasesort(). 131 * It uses a collator-based sort, or asort() with flags SORT_NATURAL and SORT_FLAG_CASE as a fallback. 132 * 133 * @param array $array The input array. 134 * @return bool Returns true on success or false on failure. 135 * 136 * @author Moisés Braga Ribeiro <moisesbr@gmail.com> 137 */ 138 public static function asort(&$array) 139 { 140 $collator = self::getCollator(); 141 if (isset($collator)) { 142 return $collator->asort($array); 143 } else { 144 return asort($array, SORT_NATURAL | SORT_FLAG_CASE); 145 } 146 } 147 148 /** 149 * Drop-in replacement for asort(), natsort() and natcasesort() when the parameter is an array of filenames. 150 * Filenames may not be equal to page names, depending on the setting in $conf['fnencode'], 151 * so the correct behavior is to sort page names and reflect this sorting in the filename array. 152 * 153 * @param array $array The input array. 154 * @return bool Returns true on success or false on failure. 155 * 156 * @author Moisés Braga Ribeiro <moisesbr@gmail.com> 157 */ 158 public static function asortFN(&$array) 159 { 160 $collator = self::getCollator(); 161 return uasort($array, function ($fn1, $fn2) use ($collator) { 162 if (isset($collator)) { 163 return $collator->compare(utf8_decodeFN($fn1), utf8_decodeFN($fn2)); 164 } else { 165 return strnatcasecmp(utf8_decodeFN($fn1), utf8_decodeFN($fn2)); 166 } 167 }); 168 } 169 170} 171