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