1<?php
2/**
3 * Horde_Core_Ui_TagCloud:: for creating and displaying tag clouds.
4 *
5 * Based on a striped down version of Pear's HTML_TagCloud
6 *
7 * Copyright 2009-2017 Horde LLC (http://www.horde.org/)
8 *
9 * See the enclosed file COPYING for license information (LGPL). If you
10 * did not receive this file, see http://www.horde.org/licenses/lgpl21.
11 *
12 * @category Horde
13 * @license  http://www.horde.org/licenses/lgpl21 LGPL 2.1
14 * @package  Core
15 */
16class Horde_Core_Ui_TagCloud
17{
18    /**
19     * @var integer
20     */
21    public $basefontsize;
22
23    /**
24     * @var integer
25     */
26    public $fontsizerange;
27
28    /**
29     * @var string
30     */
31    public $css_class = 'tagcloud';
32
33    /**
34     * @var    string
35     * mm,cm,in,pt,pc,px,em
36     */
37    public $size_suffix = 'px';
38
39    /**
40     * @var integer
41     */
42    public $factor;
43
44    /**
45     * @var array
46     */
47    public $epoc_level = array(
48        'earliest',
49        'earlier',
50        'later',
51        'latest'
52    );
53
54    /**
55     * @var array
56     */
57    protected $_elements = array();
58
59    /**
60     * @var integer
61     */
62    protected $_max = 0;
63
64    /**
65     * @var integer
66     */
67    protected $_min = 0;
68
69    /**
70     * @var integer
71     */
72    protected $_max_epoc;
73
74    /**
75     * @var integer
76     */
77    protected $_min_epoc;
78
79    /**
80     * @var array
81     */
82    protected $_map = array();
83
84    /**
85     * Constructor
86     *
87     * @param integer $basefontsize   Base font size of output tag (option).
88     * @param integer $fontsizerange  Font size range.
89     */
90    public function __construct($basefontsize = 24, $fontsizerange = 12)
91    {
92        $this->basefontsize = $basefontsize;
93        $this->fontsizerange = $fontsizerange;
94
95        $this->minfontsize = max($basefontsize - $fontsizerange, 0);
96        $this->maxfontsize = $basefontsize + $fontsizerange;
97    }
98
99    /**
100     * Add a Tag Element to build Tag Cloud.
101     *
102     * @param string $name        TODO
103     * @param string $url         TODO
104     * @param integer $count      TODO
105     * @param integer $timestamp  UNIX timestamp.
106     * @param string $onclick     Javascript onclick event handler.
107     */
108    public function addElement($name, $url ='', $count = 0, $timestamp = null,
109                               $onclick = null)
110    {
111
112        if (isset($this->_map[$name])) {
113            $i = $this->_map[$name];
114            // Increase the count
115            $this->_elements[$i]['count'] += $count;
116
117            // Keep the latest timestamp
118            if (!empty($timestamp) &&
119                $timestamp > $this->_elements[$i]['timestamp']) {
120                $this->_elements[$i]['timestamp'] = $timestamp;
121            }
122            // For onclick and url we will simply overwrite the existing values
123            // instead of checking if they are empty, then overwriting.
124            $this->_elements[$i]['onclick'] = $onclick;
125            $this->elements[$i]['url'] = $url;
126        } else {
127            $i = count($this->_elements);
128            $this->_elements[$i]['name'] = $name;
129            $this->_elements[$i]['url'] = $url;
130            $this->_elements[$i]['count'] = $count;
131            $this->_elements[$i]['timestamp'] = $timestamp == null ? time() : $timestamp;
132            $this->_elements[$i]['onclick'] = $onclick;
133            $this->_map[$name] = $i;
134        }
135    }
136
137    /**
138     * Add a Tag Element to build Tag Cloud.
139     *
140     * @param array $tags  Associative array to $this->_elements.
141     */
142    public function addElements($tags)
143    {
144        $this->_elements = array_merge($this->_elements, $tags);
145    }
146
147    /**
148     * Clear Tag Elements.
149     */
150    public function clearElements()
151    {
152        $this->_elements = array();
153    }
154
155    /**
156     * Build HTML part.
157     *
158     * @param array $param  'limit' => int limit of generation tag num.
159     *
160     * @return string   HTML
161     */
162    public function buildHTML($param = array())
163    {
164        return $this->_wrapDiv($this->_buidHTMLTags($param));
165    }
166
167    /**
168     * Calc Tag level and create whole HTML of each Tags.
169     *
170     * @param array $param  Limit of Tag Number.
171     *
172     * @return string  HTML
173     */
174    protected function _buidHTMLTags($param)
175    {
176        $this->total = count($this->_elements);
177        // no tags elements
178        if ($this->total == 0) {
179            return '';
180        } elseif ($this->total == 1) {
181            $tag = $this->_elements[0];
182            return $this->_createHTMLTag($tag, 'latest', $this->basefontsize);
183        }
184
185        $limit = array_key_exists('limit', $param) ? $param['limit'] : 0;
186        $this->_sortTags($limit);
187        $this->_calcMumCount();
188        $this->_calcMumEpoc();
189
190        $range = $this->maxfontsize - $this->minfontsize;
191        $this->factor = ($this->_max == $this->_min)
192            ? 1
193            : $range / (sqrt($this->_max) - sqrt($this->_min));
194        $this->epoc_factor = ($this->_max_epoc == $this->_min_epoc)
195            ? 1
196            : count($this->epoc_level) / (sqrt($this->_max_epoc) - sqrt($this->_min_epoc));
197        $rtn = array();
198        foreach ($this->_elements as $tag){
199            $count_lv = $this->_getCountLevel($tag['count']);
200            if (!isset($tag['timestamp']) || empty($tag['timestamp'])) {
201                $epoc_lv = count($this->epoc_level) - 1;
202            } else {
203                $epoc_lv = $this->_getEpocLevel($tag['timestamp']);
204            }
205            $color_type = $this->epoc_level[$epoc_lv];
206            $font_size  = $this->minfontsize + $count_lv;
207            $rtn[] = $this->_createHTMLTag($tag, $color_type, $font_size);
208        }
209        return implode('', $rtn);
210    }
211
212    /**
213     * Create a Element of HTML part.
214     *
215     * @param array $tag         TODO
216     * @param string $type       CSS class of time line param.
217     * @param integer $fontsize  TODO
218     *
219     * @return  string a Element of Tag HTML
220     */
221    protected function _createHTMLTag($tag, $type, $fontsize)
222    {
223        return sprintf('<a style="font-size:%d%s" class="%s" href="%s"%s>%s</a>' . "\n",
224                       $fontsize,
225                       $this->size_suffix,
226                       $type,
227                       $tag['url'],
228                       (empty($tag['onclick']) ? '' : ' onclick="' . $tag['onclick'] . '"'),
229                       htmlspecialchars($tag['name']));
230    }
231
232    /**
233     * Sort tags by name.
234     *
235     * @param integer $limit  Limit element number of create TagCloud.
236     */
237    protected function _sortTags($limit = 0)
238    {
239        usort($this->_elements, array($this, 'cmpElementsName'));
240        if ($limit != 0){
241            $this->_elements = array_splice($this->_elements, 0, $limit);
242        }
243    }
244
245    /**
246     * Using for usort().
247     *
248     * @return integer  TODO
249     */
250    public function cmpElementsName($a, $b)
251    {
252        return ($a['name'] == $b['name'])
253            ? 0
254            : (($a['name'] < $b['name']) ? -1 : 1);
255    }
256
257    /**
258     * Calc max and min tag count of use.
259     */
260    protected function _calcMumCount()
261    {
262        foreach($this->_elements as $item){
263            $array[] = $item['count'];
264        }
265        $this->_min = min($array);
266        $this->_max = max($array);
267    }
268
269    /**
270     * Calc max and min timestamp.
271     */
272    protected function _calcMumEpoc()
273    {
274        foreach($this->_elements as $item){
275            $array[] = $item['timestamp'];
276        }
277        $this->_min_epoc = min($array);
278        $this->_max_epoc = max($array);
279    }
280
281    /**
282     * Calc Tag Level of size.
283     *
284     * @param integer $count  TODO
285     *
286     * @return integer  Level.
287     */
288    protected function _getCountLevel($count = 0)
289    {
290        return (int)((sqrt($count) - sqrt($this->_min)) * $this->factor);
291    }
292
293    /**
294     * Calc timeline level of Tag.
295     *
296     * @param integer $timestamp  TODO
297     *
298     * @return integer  Level of timeline.
299     */
300    protected function _getEpocLevel($timestamp = 0)
301    {
302        return (int)((sqrt($timestamp) - sqrt($this->_min_epoc)) * $this->epoc_factor);
303    }
304
305    /**
306     * Wrap div tag.
307     *
308     * @param string $html  TODO
309     *
310     * @return string  TODO
311     */
312    protected function _wrapDiv($html)
313    {
314        return ($html == '')
315            ? ''
316            : sprintf("<div class=\"%s\">\n%s</div>\n", $this->css_class, $html);
317    }
318
319}
320