1<?php
2/**
3 * Plugin nspages : Displays nicely a list of the pages of a namespace
4 *
5 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
6 */
7
8if(!defined('DOKU_INC')) die();
9require_once 'printer.php';
10require_once 'rendererXhtmlHelper.php';
11
12use dokuwiki\Utf8\PhpString;
13
14class nspages_printerNice extends nspages_printer {
15    private $nbCols;
16    private $anchorName;
17
18    function __construct($plugin, $mode, $renderer, $nbCols, $anchorName, $data){
19        parent::__construct($plugin, $mode, $renderer, $data);
20        if ( $this->mode !== 'xhtml' ){
21          throw Exception('nspages_printerNice can only work in xhtml mode');
22        }
23        $this->nbCols = $this->_computeActualNbCols($nbCols);
24        $this->anchorName = $anchorName;
25    }
26
27    private function _computeActualNbCols($nbCols){
28        $nbCols = (int) $nbCols;
29        if(!isset($nbCols) || is_null($nbCols) || $nbCols < 1) {
30            $nbCols = 3;
31        }
32        return $nbCols;
33    }
34
35    function _print($tab, $type) {
36        $nbItemsPrinted = 0;
37
38        $nbItemPerColumns = $this->_computeNbItemPerColumns(sizeof($tab));
39        $actualNbCols = count($nbItemPerColumns);
40        $helper = new rendererXhtmlHelper($this->renderer, $actualNbCols, $this->plugin, $this->anchorName);
41
42        $helper->openColumn();
43        $firstCharOfLastAddedPage = $this->_firstChar($tab[0]);
44
45        $helper->printHeaderChar($firstCharOfLastAddedPage);
46        $helper->openListOfItems();
47
48        $idxCol = 0;
49        foreach($tab as $item) {
50            //change to the next column if necessary
51            if($nbItemsPrinted == $nbItemPerColumns[$idxCol]) {
52                $idxCol++;
53                $helper->closeListOfItems();
54                $helper->closeColumn();
55                $helper->openColumn();
56
57                $newLetter = $this->_firstChar($item);
58                if($newLetter != $firstCharOfLastAddedPage) {
59                    $firstCharOfLastAddedPage = $newLetter;
60                    $helper->printHeaderChar($firstCharOfLastAddedPage);
61                } else {
62                    $helper->printHeaderChar($firstCharOfLastAddedPage, true);
63                }
64                $helper->openListOfItems();
65            }
66
67            $newLetter = $this->_firstChar($item);
68            if($newLetter != $firstCharOfLastAddedPage) {
69                $firstCharOfLastAddedPage = $newLetter;
70                $helper->closeListOfItems();
71                $helper->printHeaderChar($firstCharOfLastAddedPage);
72                $helper->openListOfItems();
73            }
74
75            $this->_printElement($item);
76            $nbItemsPrinted++;
77        }
78        $helper->closeListOfItems();
79        $helper->closeColumn();
80    }
81
82    private function _firstChar($item) {
83        $return_char = PhpString::strtoupper(PhpString::substr($item['sort'], 0, 1));
84        $uniord_char = $this->_uniord($return_char);
85
86        // korean support. See #111 for more context
87        if ($uniord_char > 44031 && $uniord_char < 55204) {
88            $return_char = ['ㄱ','ㄱ','ㄴ','ㄷ','ㄷ','ㄹ','ㅁ','ㅂ','ㅂ','ㅅ','ㅅ','ㅇ','ㅈ','ㅈ','ㅊ','ㅋ','ㅌ','ㅍ','ㅎ'][($uniord_char-44032)/588];
89        }
90
91        return $return_char;
92    }
93
94    /**
95     * This code is from:
96     *   https://stackoverflow.com/questions/9361303/
97     */
98    private function _uniord($c) {
99        if (ord($c[0]) >=0 && ord($c[0]) <= 127)
100            return ord($c[0]);
101        if (ord($c[0]) >= 192 && ord($c[0]) <= 223)
102            return (ord($c[0])-192)*64 + (ord($c[1])-128);
103        if (ord($c[0]) >= 224 && ord($c[0]) <= 239)
104            return (ord($c[0])-224)*4096 + (ord($c[1])-128)*64 + (ord($c[2])-128);
105        if (ord($c[0]) >= 240 && ord($c[0]) <= 247)
106            return (ord($c[0])-240)*262144 + (ord($c[1])-128)*4096 + (ord($c[2])-128)*64 + (ord($c[3])-128);
107        if (ord($c[0]) >= 248 && ord($c[0]) <= 251)
108            return (ord($c[0])-248)*16777216 + (ord($c[1])-128)*262144 + (ord($c[2])-128)*4096 + (ord($c[3])-128)*64 + (ord($c[4])-128);
109        if (ord($c[0]) >= 252 && ord($c[0]) <= 253)
110            return (ord($c[0])-252)*1073741824 + (ord($c[1])-128)*16777216 + (ord($c[2])-128)*262144 + (ord($c[3])-128)*4096 + (ord($c[4])-128)*64 + (ord($c[5])-128);
111        if (ord($c[0]) >= 254 && ord($c[0]) <= 255)
112            return false;
113    return false;
114    }
115
116    /**
117     * Compute the number of element to display per column
118     * When $nbItems / $nbCols isn't an int, we make sure, for aesthetic reasons,
119     * that the first are the ones which have the more items
120     * Moreover, if we don't have enought items to display, we may choose to display less than the number of columns wanted
121     *
122     * @param int $nbItems The total number of items to display
123     * @return an array which contains $nbCols int.
124     */
125    private function _computeNbItemPerColumns($nbItems) {
126        $result = array();
127
128        if($nbItems < $this->nbCols) {
129            for($idx = 0; $idx < $nbItems; $idx++) {
130                $result[] = $idx + 1;
131            }
132            return $result;
133        }
134
135        $collength    = $nbItems / $this->nbCols;
136        $nbItemPerCol = array();
137        for($idx = 0; $idx < $this->nbCols; $idx++) {
138            $nbItemPerCol[] = ceil(($idx + 1) * $collength) - ceil($idx * $collength);
139        }
140        rsort($nbItemPerCol);
141
142        $result[] = $nbItemPerCol[0];
143        for($idx = 1; $idx < $this->nbCols; $idx++) {
144            $result[] = end($result) + $nbItemPerCol[$idx];
145        }
146
147        return $result;
148    }
149}
150