Merge pull request #3115 from moisesbr-dw/sort-with-collator
Sort with collator
This commit is contained in:
commit
d267a3cb9d
|
@ -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, dê ==> e < ê ==> de < dê
|
||||
* Otherwise: pé, pedra ==> é = e, end of word < d ==> pé < 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));
|
||||
}
|
||||
}
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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]);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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']);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue