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