Differences Between: [Versions 402 and 403]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 /** 18 * Defines string apis 19 * 20 * @package core 21 * @copyright 2011 Sam Hemelryk 22 * 2012 Petr Skoda 23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 */ 25 26 defined('MOODLE_INTERNAL') || die(); 27 28 /** 29 * A collator class with static methods that can be used for sorting. 30 * 31 * @package core 32 * @copyright 2011 Sam Hemelryk 33 * 2012 Petr Skoda 34 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 35 */ 36 class core_collator { 37 /** @const compare items using general PHP comparison, equivalent to Collator::SORT_REGULAR, this may bot be locale aware! */ 38 const SORT_REGULAR = 0; 39 40 /** @const compare items as strings, equivalent to Collator::SORT_STRING */ 41 const SORT_STRING = 1; 42 43 /** @const compare items as numbers, equivalent to Collator::SORT_NUMERIC */ 44 const SORT_NUMERIC = 2; 45 46 /** @const compare items like natsort(), equivalent to SORT_NATURAL */ 47 const SORT_NATURAL = 6; 48 49 /** @const do not ignore case when sorting, use bitwise "|" with SORT_NATURAL or SORT_STRING, equivalent to Collator::UPPER_FIRST */ 50 const CASE_SENSITIVE = 64; 51 52 /** @var Collator|false|null **/ 53 protected static $collator = null; 54 55 /** @var string|null The locale that was used in instantiating the current collator **/ 56 protected static $locale = null; 57 58 /** 59 * Prevent class instances, all methods are static. 60 */ 61 private function __construct() { 62 } 63 64 /** 65 * Ensures that a collator is available and created 66 * 67 * @return bool Returns true if collation is available and ready 68 */ 69 protected static function ensure_collator_available() { 70 $locale = get_string('locale', 'langconfig'); 71 if (is_null(self::$collator) || $locale != self::$locale) { 72 self::$collator = false; 73 self::$locale = $locale; 74 if (class_exists('Collator', false)) { 75 $collator = new Collator($locale); 76 if (!empty($collator) && $collator instanceof Collator) { 77 // Check for non fatal error messages. This has to be done immediately 78 // after instantiation as any further calls to collation will cause 79 // it to reset to 0 again (or another error code if one occurred) 80 $errorcode = $collator->getErrorCode(); 81 $errormessage = $collator->getErrorMessage(); 82 // Check for an error code, 0 means no error occurred 83 if ($errorcode !== 0) { 84 // Get the actual locale being used, e.g. en, he, zh 85 $localeinuse = $collator->getLocale(Locale::ACTUAL_LOCALE); 86 // Check for the common fallback warning error codes. If any of the two 87 // following errors occurred, there is normally little to worry about: 88 // * U_USING_FALLBACK_WARNING (-128) indicates that a fall back locale was 89 // used. For example, 'de_CH' was requested, but nothing was found 90 // there, so 'de' was used. 91 // * U_USING_DEFAULT_WARNING (-127) indicates that the default locale 92 // data was used; neither the requested locale nor any of its fall 93 // back locales could be found. For example, 'pt' was requested, but 94 // UCA was used (Unicode Collation Algorithm http://unicode.org/reports/tr10/). 95 // See http://www.icu-project.org/apiref/icu4c/classicu_1_1ResourceBundle.html 96 if ($errorcode === -127 || $errorcode === -128) { 97 // Check if the locale in use is UCA default one ('root') or 98 // if it is anything like the locale we asked for 99 if ($localeinuse !== 'root' && strpos($locale, $localeinuse) !== 0) { 100 // The locale we asked for is completely different to the locale 101 // we have received, let the user know via debugging 102 debugging('Locale warning (not fatal) '.$errormessage.': '. 103 'Requested locale "'.$locale.'" not found, locale "'.$localeinuse.'" used instead. '. 104 'The most specific locale supported by ICU relatively to the requested locale is "'. 105 $collator->getLocale(Locale::VALID_LOCALE).'".'); 106 } else { 107 // Nothing to do here, this is expected! 108 // The Moodle locale setting isn't what the collator expected but 109 // it is smart enough to match the first characters of our locale 110 // to find the correct locale or to use UCA collation 111 } 112 } else { 113 // We've received some other sort of non fatal warning - let the 114 // user know about it via debugging. 115 debugging('Problem with locale: '.$errormessage.'. '. 116 'Requested locale: "'.$locale.'", actual locale "'.$localeinuse.'". '. 117 'The most specific locale supported by ICU relatively to the requested locale is "'. 118 $collator->getLocale(Locale::VALID_LOCALE).'".'); 119 } 120 } 121 // Store the collator object now that we can be sure it is in a workable condition 122 self::$collator = $collator; 123 } else { 124 // Fatal error while trying to instantiate the collator... something went wrong 125 debugging('Error instantiating collator for locale: "' . $locale . '", with error [' . 126 intl_get_error_code() . '] ' . intl_get_error_message($collator)); 127 } 128 } 129 } 130 return (self::$collator instanceof Collator); 131 } 132 133 /** 134 * Restore array contents keeping new keys. 135 * @static 136 * @param array $arr 137 * @param array $original 138 * @return void modifies $arr 139 */ 140 protected static function restore_array(array &$arr, array &$original) { 141 foreach ($arr as $key => $ignored) { 142 $arr[$key] = $original[$key]; 143 } 144 } 145 146 /** 147 * Normalise numbers in strings for natural sorting comparisons. 148 * @static 149 * @param string $string 150 * @return string string with normalised numbers 151 */ 152 protected static function naturalise($string) { 153 return preg_replace_callback('/[0-9]+/', array('core_collator', 'callback_naturalise'), $string); 154 } 155 156 /** 157 * @internal 158 * @static 159 * @param array $matches 160 * @return string 161 */ 162 public static function callback_naturalise($matches) { 163 return str_pad($matches[0], 20, '0', STR_PAD_LEFT); 164 } 165 166 /** 167 * Locale aware sorting, the key associations are kept, values are sorted alphabetically. 168 * 169 * @param array $arr array to be sorted (reference) 170 * @param int $sortflag One of core_collator::SORT_NUMERIC, core_collator::SORT_STRING, core_collator::SORT_NATURAL, core_collator::SORT_REGULAR 171 * optionally "|" core_collator::CASE_SENSITIVE 172 * @return bool True on success 173 */ 174 public static function asort(array &$arr, $sortflag = core_collator::SORT_STRING) { 175 if (empty($arr)) { 176 // nothing to do 177 return true; 178 } 179 180 $original = null; 181 182 $casesensitive = (bool)($sortflag & core_collator::CASE_SENSITIVE); 183 $sortflag = ($sortflag & ~core_collator::CASE_SENSITIVE); 184 if ($sortflag != core_collator::SORT_NATURAL and $sortflag != core_collator::SORT_STRING) { 185 $casesensitive = false; 186 } 187 188 if (self::ensure_collator_available()) { 189 if ($sortflag == core_collator::SORT_NUMERIC) { 190 $flag = Collator::SORT_NUMERIC; 191 192 } else if ($sortflag == core_collator::SORT_REGULAR) { 193 $flag = Collator::SORT_REGULAR; 194 195 } else { 196 $flag = Collator::SORT_STRING; 197 } 198 199 if ($sortflag == core_collator::SORT_NATURAL) { 200 $original = $arr; 201 if ($sortflag == core_collator::SORT_NATURAL) { 202 foreach ($arr as $key => $value) { 203 $arr[$key] = self::naturalise((string)$value); 204 } 205 } 206 } 207 if ($casesensitive) { 208 self::$collator->setAttribute(Collator::CASE_FIRST, Collator::UPPER_FIRST); 209 } else { 210 self::$collator->setAttribute(Collator::CASE_FIRST, Collator::OFF); 211 } 212 $result = self::$collator->asort($arr, $flag); 213 if ($original) { 214 self::restore_array($arr, $original); 215 } 216 return $result; 217 } 218 219 // try some fallback that works at least for English 220 221 if ($sortflag == core_collator::SORT_NUMERIC) { 222 return asort($arr, SORT_NUMERIC); 223 224 } else if ($sortflag == core_collator::SORT_REGULAR) { 225 return asort($arr, SORT_REGULAR); 226 } 227 228 if (!$casesensitive) { 229 $original = $arr; 230 foreach ($arr as $key => $value) { 231 $arr[$key] = core_text::strtolower($value); 232 } 233 } 234 235 if ($sortflag == core_collator::SORT_NATURAL) { 236 $result = natsort($arr); 237 238 } else { 239 $result = asort($arr, SORT_LOCALE_STRING); 240 } 241 242 if ($original) { 243 self::restore_array($arr, $original); 244 } 245 246 return $result; 247 } 248 249 /** 250 * Locale aware sort of objects by a property in common to all objects 251 * 252 * @param array $objects An array of objects to sort (handled by reference) 253 * @param string $property The property to use for comparison 254 * @param int $sortflag One of core_collator::SORT_NUMERIC, core_collator::SORT_STRING, core_collator::SORT_NATURAL, core_collator::SORT_REGULAR 255 * optionally "|" core_collator::CASE_SENSITIVE 256 * @return bool True on success 257 */ 258 public static function asort_objects_by_property(array &$objects, $property, $sortflag = core_collator::SORT_STRING) { 259 $original = $objects; 260 foreach ($objects as $key => $object) { 261 $objects[$key] = $object->$property; 262 } 263 $result = self::asort($objects, $sortflag); 264 self::restore_array($objects, $original); 265 return $result; 266 } 267 268 /** 269 * Locale aware sort of objects by a method in common to all objects 270 * 271 * @param array $objects An array of objects to sort (handled by reference) 272 * @param string $method The method to call to generate a value for comparison 273 * @param int $sortflag One of core_collator::SORT_NUMERIC, core_collator::SORT_STRING, core_collator::SORT_NATURAL, core_collator::SORT_REGULAR 274 * optionally "|" core_collator::CASE_SENSITIVE 275 * @return bool True on success 276 */ 277 public static function asort_objects_by_method(array &$objects, $method, $sortflag = core_collator::SORT_STRING) { 278 $original = $objects; 279 foreach ($objects as $key => $object) { 280 $objects[$key] = $object->{$method}(); 281 } 282 $result = self::asort($objects, $sortflag); 283 self::restore_array($objects, $original); 284 return $result; 285 } 286 287 /** 288 * Locale aware sort of array of arrays. 289 * 290 * Given an array like: 291 * $array = array( 292 * array('name' => 'bravo'), 293 * array('name' => 'charlie'), 294 * array('name' => 'alpha') 295 * ); 296 * 297 * If you call: 298 * core_collator::asort_array_of_arrays_by_key($array, 'name') 299 * 300 * You will be returned $array sorted by the name key of the subarrays. e.g. 301 * $array = array( 302 * array('name' => 'alpha'), 303 * array('name' => 'bravo'), 304 * array('name' => 'charlie') 305 * ); 306 * 307 * @param array $array An array of objects to sort (handled by reference) 308 * @param string $key The key to use for comparison 309 * @param int $sortflag One of 310 * core_collator::SORT_NUMERIC, 311 * core_collator::SORT_STRING, 312 * core_collator::SORT_NATURAL, 313 * core_collator::SORT_REGULAR 314 * optionally "|" core_collator::CASE_SENSITIVE 315 * @return bool True on success 316 */ 317 public static function asort_array_of_arrays_by_key(array &$array, $key, $sortflag = core_collator::SORT_STRING) { 318 $original = $array; 319 foreach ($array as $initkey => $item) { 320 $array[$initkey] = $item[$key]; 321 } 322 $result = self::asort($array, $sortflag); 323 self::restore_array($array, $original); 324 return $result; 325 } 326 327 /** 328 * Locale aware sorting, the key associations are kept, keys are sorted alphabetically. 329 * 330 * @param array $arr array to be sorted (reference) 331 * @param int $sortflag One of core_collator::SORT_NUMERIC, core_collator::SORT_STRING, core_collator::SORT_NATURAL, core_collator::SORT_REGULAR 332 * optionally "|" core_collator::CASE_SENSITIVE 333 * @return bool True on success 334 */ 335 public static function ksort(array &$arr, $sortflag = core_collator::SORT_STRING) { 336 $keys = array_keys($arr); 337 if (!self::asort($keys, $sortflag)) { 338 return false; 339 } 340 // This is a bit slow, but we need to keep the references 341 $original = $arr; 342 $arr = array(); // Surprisingly this does not break references outside 343 foreach ($keys as $key) { 344 $arr[$key] = $original[$key]; 345 } 346 347 return true; 348 } 349 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body