Merge pull request #3115 from moisesbr-dw/sort-with-collator

Sort with collator
This commit is contained in:
Andreas Gohr 2020-08-26 10:06:20 +02:00 committed by GitHub
commit d267a3cb9d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 734 additions and 30 deletions

View File

@ -0,0 +1,356 @@
<?php
use dokuwiki\Utf8\Sort;
/**
* @author Moisés Braga Ribeiro <moisesbr@gmail.com>
* @author Andreas Gohr <andi@splitbrain.org>
*/
class sort_with_collator_test extends DokuWikiTest
{
/*
* Dependency for tests that need "intl" extension.
*/
public function testIntlExtensionAvailability()
{
if (!class_exists('\Collator')) {
$this->markTestSkipped('Skipping all sort tests with collator, as they need "intl" extension');
}
$this->assertTrue(true); // avoid being marked as risky for having no assertion
}
/**
* Provide real word pairs of the languages being tested (when possible).
* Everything which is beyond the usual A-Z order should be checked,
* including every character with an accent (diacritic) used in the language.
*
* CHECKING NON-EQUIVALENT CHARACTERS (X < Y)
*
* In this case, the words are always sorted according to the character pair.
* Craft word pairs to double-check the collator, such that sort by the next
* character yields the opposite result.
*
* Esperanto example: ĉ < d
* ĉokolado, dento ==> ĉ < d ==> ĉokolado < dento
* (if ĉ < d would fail, o < e would also fail ==> collator failure)
*
* CHECKING EQUIVALENT CHARACTERS (X = Y)
*
* If the sole difference between the words is the character pair, the sort
* will be as if X < Y. Otherwise the characters will be treated as the same.
* Craft two word pairs to test both conditions.
*
* German example: a = ä
* Sole diff.: Apfel, Äpfel ==> a < ä ==> Apfel < Äpfel
* Otherwise: Ämter, Arzt ==> a = ä, m < r ==> Ämter < Arzt
*
* CHECKING MULTIPLE EQUIVALENT CHARACTERS (X = Y = Z = ...)
*
* An extension of the above case. If the sole difference between the words is
* a character pair from the given set, the sort will be as if X < Y < Z < ...
* Otherwise the characters will be treated as the same.
* Craft at least one word pair to test the first case and as many as possible
* to test the other case.
*
* Portuguese example: e = é = ê
* Sole diff.: de, ==> e < ê ==> de <
* Otherwise: , pedra ==> é = e, end of word < d ==> < pedra
* pêssego, peste ==> ê = e, s = s, s < t ==> pêssego < peste
*
* @return Generator|array
* @see testStrcmp
*/
public function provideWordPairs()
{
static $pairs = [
// Esperanto
'eo' => [
// c < ĉ < d
['celo', 'ĉapo'], ['ĉokolado', 'dento'],
// g < ĝ < h < ĥ < i
['glacio', 'ĝirafo'], ['ĝojo', 'haro'], ['horo', 'ĥameleono'], ['ĥoro', 'iam'],
// j < ĵ < k
['jes', 'ĵaŭdo'], ['ĵurnalo', 'kapo'],
// s < ŝ < t
['seka', 'ŝako'], ['ŝuo', 'tablo'],
// u < ŭ < v
['urso', 'ŭaŭ'], ['ŭo', 'vino'],
// natural sort
['paĝo 2', 'paĝo 10'], ['paĝo 51', 'paĝo 100']
],
// German
'de' => [
// a = ä
['Apfel', 'Äpfel'], ['Ämter', 'Arzt'],
// o = ö
['Tochter', 'Töchter'], ['Öl', 'Orange'],
// u = ü
['Mutter', 'Mütter'], ['Übersetzung', 'Uhrzeit'],
// ß = ss
['weiss', 'weiß'], ['Fuchs', 'Fuß'], ['Fraß', 'Frau'],
// natural sort
['Seite 2', 'Seite 10'], ['Seite 51', 'Seite 100']
],
// Portuguese
'pt' => [
// a = á = à = â = ã
['a', 'à'], ['água', 'amor'], ['às', 'ato'], ['âmbar', 'arte'], ['lã', 'lata'],
// e = é = ê
['de', 'dê'], ['pé', 'pedra'], ['pêssego', 'peste'],
// i = í
['liquido', 'líquido'], ['índio', 'indireto'],
// o = ó = ô = õ
['avó', 'avô'], ['ótimo', 'ovo'], ['ônibus', 'osso'], ['limões', 'limonada'],
// u = ú = ü (ü appears in old texts)
['numero', 'número'], ['último', 'um'], ['tranqüila', 'tranquilamente'],
// c = ç
['faca', 'faça'], ['taça', 'taco'],
// natural sort
['página 2', 'página 10'], ['página 51', 'página 100']
],
// Spanish
'es' => [
// n < ñ < o
['nube', 'ñoño'], ['ñu', 'ojo'],
// a = á
['mas', 'más'], ['ácido', 'agua'],
// e = é
['de', 'dé'], ['él', 'elefante'],
// i = í
['mi', 'mí'], ['íntimo', 'isla'],
// o = ó
['como', 'cómo'], ['óptimo', 'oreja'],
// u = ú
['tu', 'tú'], ['último', 'uno'],
// natural sort
['página 2', 'página 10'], ['página 51', 'página 100']
],
];
foreach ($pairs as $lang => $list) {
foreach ($list as $pair) {
yield [$lang, $pair[0], $pair[1]];
}
}
}
/**
* Provide the sorted sequences of all characters used in the languages being tested.
* Everything which is beyond the usual A-Z order should be checked.
*
* CHECKING NON-EQUIVALENT CHARACTERS (X < Y)
*
* Add a 2nd character to double-check the collator, such that sort by the 2nd
* character yields the opposite result.
*
* Esperanto example: ĉ < d
* 2nd character: ĉe, da ==> ĉ < d ==> ĉe < da
* (if ĉ < d would fail, e < a would also fail ==> collator failure)
*
* CHECKING EQUIVALENT CHARACTERS (X = Y = Z)
*
* Don't add a 2nd character, because it would break the test. The lone characters
* will be sorted as words with a sole difference, that is, as if X < Y < Z.
*
* German example: a = ä
* Sole difference: a, ä ==> a < ä
*
* @return Generator|array
* @see testSort
* @see testKSort
* @see testASort
* @see testASortFnUrl
* @see testASortFnSafe
* @see testASortFnUtf8
*/
public function provideSortedCharList()
{
static $lists = [
// Esperanto
// c < ĉ < d
// g < ĝ < h < ĥ < i
// j < ĵ < k
// s < ŝ < t
// u < ŭ < v
'eo' => 'a b ci ĉe da e f gu ĝo hi ĥe ia ju ĵo ke l m n o p r so ŝi te us ŭo ve z',
// German
// a = ä
// o = ö
// u = ü
// ß = ss
'de' => 'a ä b c d e f g h i j k l m n o ö p q r s ss ß st t u ü v w x y z',
// Portuguese
// a = á = à = â = ã
// e = é = ê
// i = í
// o = ó = ô = õ
// u = ú = ü (ü appears in old texts)
// c = ç
'pt' => 'a á à â ã b c ç d e é ê f g h i í j k l m n o ó ô õ p q r s t u ú ü v w x y z',
// Spanish
// n < ñ < o
// a = á
// e = é
// i = í
// o = ó
// u = ú
'es' => 'a á b c d e é f g h i í j k l m nu ño oh óh p q r s t u ú v w x y z',
];
foreach ($lists as $lang => $list) {
yield [$lang, $list];
}
}
/**
* @depends testIntlExtensionAvailability
* @dataProvider provideWordPairs
* @param string $lang
* @param string $word1
* @param string $word2
*/
public function testStrcmp($lang, $word1, $word2)
{
global $conf;
$conf['lang'] = $lang;
$this->assertLessThan(0, Sort::strcmp($word1, $word2));
}
/**
* @dataProvider provideSortedCharList
* @depends testIntlExtensionAvailability
* @param string $lang
* @param string $list
*/
public function testSort($lang, $list)
{
global $conf;
$conf['lang'] = $lang;
$sorted = explode(' ', $list);
$random = explode(' ', $list);
shuffle($random);
Sort::sort($random);
$this->assertEquals(array_values($random), array_values($sorted));
}
/**
* @dataProvider provideSortedCharList
* @depends testIntlExtensionAvailability
* @param string $lang
* @param string $list
*/
public function testKSort($lang, $list)
{
global $conf;
$conf['lang'] = $lang;
$sorted = array_flip(explode(' ', $list));
$random = explode(' ', $list);
shuffle($random);
$random = array_flip($random);
Sort::ksort($random);
$this->assertEquals(array_keys($random), array_keys($sorted));
}
/**
* @dataProvider provideSortedCharList
* @depends testIntlExtensionAvailability
* @param string $lang
* @param string $list
*/
public function testASort($lang, $list)
{
global $conf;
$conf['lang'] = $lang;
$sorted = explode(' ', $list);
$keys = array_keys($sorted);
shuffle($keys);
foreach ($keys as $key) {
$random[$key] = $sorted[$key];
}
Sort::asort($random);
$this->assertEquals(array_values($random), array_values($sorted));
$this->assertEquals(array_keys($random), array_keys($sorted));
}
/**
* @dataProvider provideSortedCharList
* @depends testIntlExtensionAvailability
* @param string $lang
* @param string $list
*/
public function testASortFnUrl($lang, $list)
{
global $conf;
$conf['fnencode'] = 'url';
$conf['lang'] = $lang;
$sorted = explode('+', urlencode($list));
$keys = array_keys($sorted);
shuffle($keys);
foreach ($keys as $key) {
$random[$key] = $sorted[$key];
}
Sort::asortFN($random);
$this->assertEquals(array_values($random), array_values($sorted));
$this->assertEquals(array_keys($random), array_keys($sorted));
}
/**
* @dataProvider provideSortedCharList
* @depends testIntlExtensionAvailability
* @param string $lang
* @param string $list
*/
public function testASortFnSafe($lang, $list)
{
global $conf;
$conf['fnencode'] = 'safe';
$conf['lang'] = $lang;
$sorted = explode(' ', $list);
foreach (array_keys($sorted) as $key) {
$sorted[$key] = SafeFN::encode($sorted[$key]);
}
$keys = array_keys($sorted);
shuffle($keys);
foreach ($keys as $key) {
$random[$key] = $sorted[$key];
}
Sort::asortFN($random);
$this->assertEquals(array_values($random), array_values($sorted));
$this->assertEquals(array_keys($random), array_keys($sorted));
}
/**
* @dataProvider provideSortedCharList
* @depends testIntlExtensionAvailability
* @param string $lang
* @param string $list
*/
public function testASortFnUtf8($lang, $list)
{
global $conf;
$conf['fnencode'] = 'utf-8';
$conf['lang'] = $lang;
$sorted = explode(' ', $list);
$keys = array_keys($sorted);
shuffle($keys);
foreach ($keys as $key) {
$random[$key] = $sorted[$key];
}
Sort::asortFN($random);
$this->assertEquals(array_values($random), array_values($sorted));
$this->assertEquals(array_keys($random), array_keys($sorted));
}
}

View File

@ -0,0 +1,148 @@
<?php
use dokuwiki\Utf8\Sort;
require_once __DIR__ . '/sort_with_collator.test.php';
/**
* Based on sort_with_collator.test.php.
*
* @author Moisés Braga Ribeiro <moisesbr@gmail.com>
* @author Andreas Gohr <andi@splitbrain.org>
*/
class sort_without_collator_test extends sort_with_collator_test
{
/**
* Disable the "intl" extension.
*/
public static function setUpBeforeClass()
{
parent::setUpBeforeClass();
Sort::useIntl(false);
}
/**
* Reenable the "intl" extension.
*/
public static function tearDownAfterClass()
{
Sort::useIntl(true);
parent::tearDownAfterClass();
}
/**
* Since we always use the fallback sort, we do not check for
* the availability of the "intl" extension here at all.
*/
public function testIntlExtensionAvailability()
{
$this->assertTrue(true); // avoid being marked as risky for having no assertion
}
/**
* Provide real word pairs of the languages being tested (when possible).
* The pairs should show what the fallback sort can or cannot do, as it
* simply follows character codes.
*
* In particular, there should be a test to show that every character with
* an accent (diacritic) used in the language is WRONGLY sorted after Z.
*
* @return Generator|array
* @see testStrcmp
*/
public function provideWordPairs()
{
static $pairs = [
// Esperanto
'eo' => [
// fallback sort works for c < ĉ, but not for ĉ < d (and so on)
['celo', 'ĉapo'], ['glacio', 'ĝirafo'], ['horo', 'ĥameleono'],
['jes', 'ĵaŭdo'], ['seka', 'ŝako'], ['urso', 'ŭaŭ'],
// fallback sort WRONGLY puts ĉ/ĝ/ĥ/ĵ/ŝ/ŭ after z
['zorio', 'ĉokolado'], ['zorio', 'ĝojo'], ['zorio', 'ĥoro'],
['zorio', 'ĵurnalo'], ['zorio', 'ŝuo'], ['zorio', 'ŭo'],
// natural sort works as usual
['paĝo 2', 'paĝo 10'], ['paĝo 51', 'paĝo 100']
],
// German
'de' => [
// fallback sort WRONGLY puts ä/ö/ü/ß after z
['Zebra', 'Äpfel'], ['Zebra', 'Öl'], ['Zebra', 'Übersetzung'],
['Weizen', 'weiß'],
// natural sort works as usual
['Seite 2', 'Seite 10'], ['Seite 51', 'Seite 100']
],
// Portuguese
'pt' => [
// fallback sort WRONGLY puts accented letters after z
['zebra', 'às'], ['zebra', 'água'], ['zebra', 'âmbar'],
['zebra', 'épico'], ['zebra', 'ênclise'], ['zebra', 'índio'],
['zebra', 'ótimo'], ['zebra', 'ônibus'], ['zebra', 'último'],
['pizza', 'pião'], ['pizza', 'piões'], ['azar', 'aço'],
// natural sort works as usual
['página 2', 'página 10'], ['página 51', 'página 100']
],
// Spanish
'es' => [
// fallback sort works for n < ñ, but not for ñ < o
['nube', 'ñu'],
// fallback sort WRONGLY puts accented letters after z
['zapato', 'ácido'], ['zapato', 'él'], ['zapato', 'íntimo'],
['zapato', 'óptimo'], ['zapato', 'último'],
['pizza', 'piña'],
// natural sort works as usual
['página 2', 'página 10'], ['página 51', 'página 100']
],
];
foreach ($pairs as $lang => $list) {
foreach ($list as $pair) {
yield [$lang, $pair[0], $pair[1]];
}
}
}
/**
* Provide WRONG sorted sequences of all characters used in the languages
* being tested, as the fallback sort simply follows character codes.
*
* The sorted sequences given in class "sort_with_collator" are simply
* reordered here, starting with A-Z and continuing with accented characters
* ordered by character codes.
*
* @return Generator|array
* @see testSort
* @see testKSort
* @see testASort
* @see testASortFnUrl
* @see testASortFnSafe
* @see testASortFnUtf8
*/
public function provideSortedCharList()
{
static $lists = [
// Esperanto
// 'a b c ĉ d e f g ĝ h ĥ i j ĵ k l m n o p r s ŝ t u ŭ v z'
'eo' => 'a b c d e f g h i j k l m n o p r s t u v z ĉ ĝ ĥ ĵ ŝ ŭ',
// German
// 'a ä b c d e f g h i j k l m n o ö p q r s ß t u ü v w x y z'
'de' => 'a b c d e f g h i j k l m n o p q r s t u v w x y z ß ä ö ü',
// Portuguese
// 'a á à â ã b c ç d e é ê f g h i í j k l m n o ó ô õ p q r s t u ú ü v w x y z'
'pt' => 'a b c d e f g h i j k l m n o p q r s t u v w x y z à á â ã ç é ê í ó ô õ ú ü',
// Spanish
// 'a á b c d e é f g h i í j k l m n ñ o ó p q r s t u ú v w x y z'
'es' => 'a b c d e f g h i j k l m n o p q r s t u v w x y z á é í ñ ó ú',
];
foreach ($lists as $lang => $list) {
yield [$lang, $list];
}
}
}

View File

@ -3,6 +3,7 @@
use splitbrain\phpcli\CLI;
use splitbrain\phpcli\Options;
use dokuwiki\Utf8\Sort;
if(!defined('DOKU_INC')) define('DOKU_INC', realpath(dirname(__FILE__) . '/../') . '/');
define('NOSESSION', 1);
@ -77,13 +78,13 @@ class WantedPagesCLI extends CLI {
foreach($this->getPages($startdir) as $page) {
$this->internalLinks($page);
}
ksort($this->result);
Sort::ksort($this->result);
foreach($this->result as $main => $subs) {
if($this->skip) {
print "$main\n";
} else {
$subs = array_unique($subs);
sort($subs);
Sort::sort($subs);
foreach($subs as $sub) {
printf("%-40s %s\n", $main, $sub);
}

View File

@ -2,6 +2,8 @@
namespace dokuwiki;
use dokuwiki\Utf8\Sort;
/**
* Manage all builtin AJAX calls
*
@ -98,7 +100,7 @@ class Ajax {
$data = array_map('trim', $data);
$data = array_map('noNS', $data);
$data = array_unique($data);
sort($data);
Sort::sort($data);
/* now construct a json */
$suggestions = array(

View File

@ -6,6 +6,7 @@ use Doku_Renderer_xhtml;
use dokuwiki\ChangeLog\MediaChangeLog;
use dokuwiki\ChangeLog\PageChangeLog;
use dokuwiki\Extension\Event;
use dokuwiki\Utf8\Sort;
define('DOKU_API_VERSION', 10);
@ -142,11 +143,11 @@ class ApiCore
), 'wiki.getRecentChanges' => array(
'args' => array('int'),
'return' => 'array',
'Returns a struct about all recent changes since given timestamp.'
'doc' => 'Returns a struct about all recent changes since given timestamp.'
), 'wiki.getRecentMediaChanges' => array(
'args' => array('int'),
'return' => 'array',
'Returns a struct about all recent media changes since given timestamp.'
'doc' => 'Returns a struct about all recent media changes since given timestamp.'
), 'wiki.aclCheck' => array(
'args' => array('string', 'string', 'array'),
'return' => 'int',
@ -310,6 +311,7 @@ class ApiCore
$list = array();
$pages = idx_get_indexer()->getPages();
$pages = array_filter(array_filter($pages, 'isVisiblePage'), 'page_exists');
Sort::ksort($pages);
foreach (array_keys($pages) as $idx) {
$perm = auth_quickaclcheck($pages[$idx]);

View File

@ -1,6 +1,8 @@
<?php
namespace dokuwiki\Ui;
use dokuwiki\Utf8\Sort;
/**
* Class Admin
*
@ -159,7 +161,7 @@ class Admin extends Ui {
* @return int
*/
protected function menuSort($a, $b) {
$strcmp = strcasecmp($a['prompt'], $b['prompt']);
$strcmp = Sort::strcmp($a['prompt'], $b['prompt']);
if($strcmp != 0) return $strcmp;
if($a['sort'] === $b['sort']) return 0;
return ($a['sort'] < $b['sort']) ? -1 : 1;

View File

@ -4,6 +4,7 @@ namespace dokuwiki\Ui;
use dokuwiki\Extension\Event;
use dokuwiki\Form\Form;
use dokuwiki\Utf8\Sort;
class Search extends Ui
{
@ -384,7 +385,7 @@ class Search extends Ui
}
$namespaces[$subtopNS] += 1;
}
ksort($namespaces);
Sort::ksort($namespaces);
arsort($namespaces);
return $namespaces;
}

173
inc/Utf8/Sort.php Normal file
View File

@ -0,0 +1,173 @@
<?php
namespace dokuwiki\Utf8;
/**
* DokuWiki sort functions
*
* When "intl" extension is available, all sorts are done using a collator.
* Otherwise, primitive PHP functions are called.
*
* The collator is created using the locale given in $conf['lang'].
* It always uses case insensitive "natural" ordering in its collation.
* The fallback solution uses the primitive PHP functions that return almost the same results
* when the input is text with only [A-Za-z0-9] characters.
*
* @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
* @author Moisés Braga Ribeiro <moisesbr@gmail.com>
* @author Andreas Gohr <andi@splitbrain.org>
*/
class Sort
{
/** @var \Collator[] language specific collators, usually only one */
protected static $collators = [];
/** @var bool should the intl extension be used if available? For testing only */
protected static $useIntl = true;
/**
* Initialization of a collator using $conf['lang'] as the locale.
* The initialization is done only once.
* The collation takes "natural ordering" into account, that is, "page 2" is before "page 10".
*
* @return \Collator Returns a configured collator or null if the collator cannot be created.
*
* @author Moisés Braga Ribeiro <moisesbr@gmail.com>
*/
protected static function getCollator()
{
global $conf;
$lc = $conf['lang'];
// check if intl extension is available
if (!self::$useIntl || !class_exists('\Collator')) {
return null;
}
// load collator if not available yet
if (!isset(self::$collators[$lc])) {
$collator = \Collator::create($lc);
if (!isset($collator)) return null; // check needed as stated in the docs
$collator->setAttribute(\Collator::NUMERIC_COLLATION, \Collator::ON);
dbglog('Collator created with locale "' . $lc . '": numeric collation on, ' .
'valid locale "' . $collator->getLocale(\Locale::VALID_LOCALE) . '", ' .
'actual locale "' . $collator->getLocale(\Locale::ACTUAL_LOCALE) . '"');
self::$collators[$lc] = $collator;
}
return self::$collators[$lc];
}
/**
* Enable or disable the use of the "intl" extension collator.
* This is used for testing and should not be used in normal code.
*
* @param bool $use
*
* @author Andreas Gohr <andi@splitbrain.org>
*/
public static function useIntl($use = true)
{
self::$useIntl = $use;
}
/**
* Drop-in replacement for strcmp(), strcasecmp(), strnatcmp() and strnatcasecmp().
* It uses a collator-based comparison, or strnatcasecmp() as a fallback.
*
* @param string $str1 The first string.
* @param string $str2 The second string.
* @return int Returns < 0 if $str1 is less than $str2; > 0 if $str1 is greater than $str2, and 0 if they are equal.
*
* @author Moisés Braga Ribeiro <moisesbr@gmail.com>
*/
public static function strcmp($str1, $str2)
{
$collator = self::getCollator();
if (isset($collator)) {
return $collator->compare($str1, $str2);
} else {
return strnatcasecmp($str1, $str2);
}
}
/**
* Drop-in replacement for sort().
* It uses a collator-based sort, or sort() with flags SORT_NATURAL and SORT_FLAG_CASE as a fallback.
*
* @param array $array The input array.
* @return bool Returns true on success or false on failure.
*
* @author Moisés Braga Ribeiro <moisesbr@gmail.com>
*/
public static function sort(&$array)
{
$collator = self::getCollator();
if (isset($collator)) {
return $collator->sort($array);
} else {
return sort($array, SORT_NATURAL | SORT_FLAG_CASE);
}
}
/**
* Drop-in replacement for ksort().
* It uses a collator-based sort, or ksort() with flags SORT_NATURAL and SORT_FLAG_CASE as a fallback.
*
* @param array $array The input array.
* @return bool Returns true on success or false on failure.
*
* @author Moisés Braga Ribeiro <moisesbr@gmail.com>
*/
public static function ksort(&$array)
{
$collator = self::getCollator();
if (isset($collator)) {
return uksort($array, array($collator, 'compare'));
} else {
return ksort($array, SORT_NATURAL | SORT_FLAG_CASE);
}
}
/**
* Drop-in replacement for asort(), natsort() and natcasesort().
* It uses a collator-based sort, or asort() with flags SORT_NATURAL and SORT_FLAG_CASE as a fallback.
*
* @param array $array The input array.
* @return bool Returns true on success or false on failure.
*
* @author Moisés Braga Ribeiro <moisesbr@gmail.com>
*/
public static function asort(&$array)
{
$collator = self::getCollator();
if (isset($collator)) {
return $collator->asort($array);
} else {
return asort($array, SORT_NATURAL | SORT_FLAG_CASE);
}
}
/**
* Drop-in replacement for asort(), natsort() and natcasesort() when the parameter is an array of filenames.
* Filenames may not be equal to page names, depending on the setting in $conf['fnencode'],
* so the correct behavior is to sort page names and reflect this sorting in the filename array.
*
* @param array $array The input array.
* @return bool Returns true on success or false on failure.
*
* @author Moisés Braga Ribeiro <moisesbr@gmail.com>
* @author Andreas Gohr <andi@splitbrain.org>
*/
public static function asortFN(&$array)
{
$collator = self::getCollator();
return uasort($array, function ($fn1, $fn2) use ($collator) {
if (isset($collator)) {
return $collator->compare(utf8_decodeFN($fn1), utf8_decodeFN($fn2));
} else {
return strnatcasecmp(utf8_decodeFN($fn1), utf8_decodeFN($fn2));
}
});
}
}

View File

@ -7,6 +7,7 @@
*/
use dokuwiki\Extension\Event;
use dokuwiki\Utf8\Sort;
/**
* create snippets for the first few results only
@ -151,6 +152,7 @@ function _ft_pageSearch(&$data) {
uksort($docs, 'ft_pagemtimesorter');
} else {
// sort docs by count
uksort($docs, 'ft_pagesorter');
arsort($docs);
}
@ -180,7 +182,7 @@ function ft_backlinks($id, $ignore_perms = false){
}
}
sort($result);
Sort::sort($result);
return $result;
}
@ -211,7 +213,7 @@ function ft_mediause($id, $ignore_perms = false){
}
}
sort($result);
Sort::sort($result);
return $result;
}
@ -370,7 +372,7 @@ function ft_pagesorter($a, $b){
}elseif($ac > $bc){
return 1;
}
return strcmp ($a,$b);
return Sort::strcmp($a,$b);
}
/**

View File

@ -10,6 +10,7 @@ use dokuwiki\ChangeLog\MediaChangeLog;
use dokuwiki\HTTP\DokuHTTPClient;
use dokuwiki\Subscriptions\MediaSubscriptionSender;
use dokuwiki\Extension\Event;
use dokuwiki\Utf8\Sort;
/**
* Lists pages which currently use a media file selected for deletion
@ -1977,10 +1978,7 @@ function media_nstree($ns){
while ($data[$pos]['id'] != $tmp_ns) {
if (
$pos >= count($data) ||
(
$data[$pos]['level'] <= $level+1 &&
strnatcmp(utf8_encodeFN($data[$pos]['id']), utf8_encodeFN($tmp_ns)) > 0
)
($data[$pos]['level'] <= $level+1 && Sort::strcmp($data[$pos]['id'], $tmp_ns) > 0)
) {
array_splice($data, $pos, 0, array(array('level' => $level+1, 'id' => $tmp_ns, 'open' => 'true')));
break;

View File

@ -6,6 +6,8 @@
* @author Andreas Gohr <andi@splitbrain.org>
*/
use dokuwiki\Utf8\Sort;
/**
* Recurse directory
*
@ -49,9 +51,9 @@ function search(&$data,$base,$func,$opts,$dir='',$lvl=1,$sort='natural'){
if ($sort == 'date') {
@array_multisort(array_map('filemtime', $filepaths), SORT_NUMERIC, SORT_DESC, $files);
} else /* natural */ {
natsort($files);
Sort::asortFN($files);
}
natsort($dirs);
Sort::asortFN($dirs);
}
//give directories to userfunction then recurse
@ -368,7 +370,7 @@ function sort_search_fulltext($a,$b){
}elseif($a['count'] < $b['count']){
return 1;
}else{
return strcmp($a['id'],$b['id']);
return Sort::strcmp($a['id'],$b['id']);
}
}

View File

@ -1,4 +1,6 @@
<?php
use dokuwiki\Utf8\Sort;
/**
* ACL administration functions
*
@ -304,7 +306,7 @@ class admin_plugin_acl extends DokuWiki_Admin_Plugin
while (count($a_ids) && count($b_ids)) {
// compare each level from upper to lower
// until a non-equal component is found
$cur_result = strcmp(array_shift($a_ids), array_shift($b_ids));
$cur_result = Sort::strcmp(array_shift($a_ids), array_shift($b_ids));
if ($cur_result) {
// if one of the components is the last component and is a file
// and the other one is either of a deeper level or a directory,
@ -589,7 +591,7 @@ class admin_plugin_acl extends DokuWiki_Admin_Plugin
}
$this->specials = array_filter($this->specials);
$this->specials = array_unique($this->specials);
sort($this->specials);
Sort::sort($this->specials);
foreach ($AUTH_ACL as $line) {
$line = trim(preg_replace('/#.*$/', '', $line)); //ignore comments
@ -608,8 +610,11 @@ class admin_plugin_acl extends DokuWiki_Admin_Plugin
}
$usersgroups = array_unique($usersgroups);
sort($usersgroups);
ksort($acl_config);
Sort::sort($usersgroups);
Sort::ksort($acl_config);
foreach (array_keys($acl_config) as $pagename) {
Sort::ksort($acl_config[$pagename]);
}
$this->acl = $acl_config;
$this->usersgroups = $usersgroups;

View File

@ -38,6 +38,8 @@
require_once(dirname(__FILE__) . '/../adLDAP.php');
require_once(dirname(__FILE__) . '/../collections/adLDAPContactCollection.php');
use dokuwiki\Utf8\Sort;
class adLDAPContacts {
/**
* The current adLDAP connection via dependency injection
@ -271,7 +273,7 @@ class adLDAPContacts {
}
}
if ($sorted) {
asort($usersArray);
Sort::asort($usersArray);
}
return $usersArray;
}

View File

@ -37,6 +37,8 @@
require_once(dirname(__FILE__) . '/../adLDAP.php');
require_once(dirname(__FILE__) . '/../collections/adLDAPGroupCollection.php');
use dokuwiki\Utf8\Sort;
/**
* GROUP FUNCTIONS
*/
@ -524,7 +526,7 @@ class adLDAPGroups {
}
}
if ($sorted) {
asort($groupsArray);
Sort::asort($groupsArray);
}
return $groupsArray;
}

View File

@ -37,6 +37,8 @@
require_once(dirname(__FILE__) . '/../adLDAP.php');
require_once(dirname(__FILE__) . '/../collections/adLDAPUserCollection.php');
use dokuwiki\Utf8\Sort;
/**
* USER FUNCTIONS
*/
@ -568,7 +570,7 @@ class adLDAPUsers {
}
}
if ($sorted) {
asort($usersArray);
Sort::asort($usersArray);
}
return $usersArray;
}
@ -631,7 +633,7 @@ class adLDAPUsers {
}
}
if ($sorted){
asort($usersArray);
Sort::asort($usersArray);
}
return ($usersArray);
}

View File

@ -1,4 +1,5 @@
<?php
use dokuwiki\Utf8\Sort;
/**
* Active Directory authentication backend for DokuWiki
@ -739,7 +740,7 @@ class auth_plugin_authad extends DokuWiki_Auth_Plugin
$domains[$key] = ltrim($val['account_suffix'], '@');
}
}
ksort($domains);
Sort::ksort($domains);
return $domains;
}

View File

@ -1,4 +1,5 @@
<?php
use dokuwiki\Utf8\Sort;
/**
* LDAP authentication backend
@ -413,7 +414,7 @@ class auth_plugin_authldap extends DokuWiki_Auth_Plugin
for ($i = 0; $i < $entries["count"]; $i++) {
array_push($users_array, $entries[$i][$userkey][0]);
}
asort($users_array);
Sort::asort($users_array);
$result = $users_array;
if (!$result) return array();
$this->users = array_fill_keys($result, false);

View File

@ -1,4 +1,6 @@
<?php
use dokuwiki\Utf8\Sort;
/**
* DokuWiki Plugin authpdo (Auth Component)
*
@ -599,7 +601,7 @@ class auth_plugin_authpdo extends DokuWiki_Auth_Plugin
}
$groups = array_unique($groups);
sort($groups);
Sort::sort($groups);
return $groups;
}
@ -632,7 +634,7 @@ class auth_plugin_authpdo extends DokuWiki_Auth_Plugin
$this->debugMsg("select-groups statement did not return a list of result", -1, __LINE__);
}
ksort($groups);
Sort::ksort($groups);
return $groups;
}

View File

@ -1,4 +1,5 @@
<?php
use dokuwiki\Utf8\Sort;
/**
* Plaintext authentication backend
@ -298,7 +299,7 @@ class auth_plugin_authplain extends DokuWiki_Auth_Plugin
if ($this->users === null) $this->loadUserData();
ksort($this->users);
Sort::ksort($this->users);
$i = 0;
$count = 0;
@ -335,6 +336,7 @@ class auth_plugin_authplain extends DokuWiki_Auth_Plugin
foreach($this->users as $user => $info) {
$groups = array_merge($groups, array_diff($info['grps'], $groups));
}
Sort::ksort($groups);
if($limit > 0) {
return array_splice($groups, $start, $limit);