1<?php
2/**
3 *
4 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
5 * @author     Andreas Gohr <andi@splitbrain.org>
6 */
7
8/**
9 * Class syntax_plugin_data_cloud
10 */
11class syntax_plugin_data_cloud extends syntax_plugin_data_table {
12
13    /**
14     * will hold the data helper plugin
15     * @var $dthlp helper_plugin_data
16     */
17    var $dthlp = null;
18
19    /**
20     * Constructor. Load helper plugin
21     */
22    public function __construct() {
23        $this->dthlp = plugin_load('helper', 'data');
24        if(!$this->dthlp) {
25            msg('Loading the data helper failed. Make sure the data plugin is installed.', -1);
26        }
27    }
28
29    /**
30     * What kind of syntax are we?
31     */
32    public function getType() {
33        return 'substition';
34    }
35
36    /**
37     * What about paragraphs?
38     */
39    public function getPType() {
40        return 'block';
41    }
42
43    /**
44     * Where to sort in?
45     */
46    public function getSort() {
47        return 155;
48    }
49
50    /**
51     * Connect pattern to lexer
52     *
53     * @param $mode
54     */
55    public function connectTo($mode) {
56        $this->Lexer->addSpecialPattern('----+ *datacloud(?: [ a-zA-Z0-9_]*)?-+\n.*?\n----+', $mode, 'plugin_data_cloud');
57    }
58
59    /**
60     * Builds the SQL query from the given data
61     *
62     * @param array &$data instruction by handler
63     * @return bool|string SQL query or false
64     */
65    public function _buildSQL(&$data) {
66        $ckey = array_keys($data['cols']);
67        $ckey = $ckey[0];
68
69        $from      = ' ';
70        $where     = ' ';
71        $pagesjoin = '';
72        $tables    = array();
73
74        $sqlite = $this->dthlp->_getDB();
75        if(!$sqlite) return false;
76
77        $fields = array(
78            'pageid' => 'page',
79            'class' => 'class',
80            'title' => 'title'
81        );
82        // prepare filters (no request filters - we set them ourselves)
83        if(is_array($data['filter']) && count($data['filter'])) {
84            $cnt = 0;
85
86            foreach($data['filter'] as $filter) {
87                $col = $filter['key'];
88                $closecompare = ($filter['compare'] == 'IN(' ? ')' : '');
89
90                if(preg_match('/^%(\w+)%$/', $col, $m) && isset($fields[$m[1]])) {
91                    $where .= " " . $filter['logic'] . " pages." . $fields[$m[1]] .
92                        " " . $filter['compare'] . " '" . $filter['value'] . "'" . $closecompare;
93                    $pagesjoin = ' LEFT JOIN pages ON pages.pid = data.pid';
94                } else {
95                    // filter by hidden column?
96                    if(!$tables[$col]) {
97                        $tables[$col] = 'T' . (++$cnt);
98                        $from .= ' LEFT JOIN data AS ' . $tables[$col] . ' ON ' . $tables[$col] . '.pid = data.pid';
99                        $from .= ' AND ' . $tables[$col] . ".key = " . $sqlite->quote_string($col);
100                    }
101
102                    $where .= ' ' . $filter['logic'] . ' ' . $tables[$col] . '.value ' . $filter['compare'] .
103                        " '" . $filter['value'] . "'" . $closecompare; //value is already escaped
104                }
105            }
106        }
107
108        // build query
109        $sql = "SELECT data.value AS value, COUNT(data.pid) AS cnt
110                  FROM data $from $pagesjoin
111                 WHERE data.key = " . $sqlite->quote_string($ckey) . "
112                 $where
113              GROUP BY data.value";
114        if(isset($data['min'])) {
115            $sql .= ' HAVING cnt >= ' . $data['min'];
116        }
117        $sql .= ' ORDER BY cnt DESC';
118        if($data['limit']) {
119            $sql .= ' LIMIT ' . $data['limit'];
120        }
121
122        return $sql;
123    }
124
125    protected $before_item = '<ul class="dataplugin_cloud %s">';
126    protected $after_item = '</ul>';
127    protected $before_val = '<li class="cl%s">';
128    protected $after_val = '</li>';
129
130    /**
131     * Create output or save the data
132     *
133     * @param $format
134     * @param Doku_Renderer $renderer
135     * @param $data
136     * @return bool
137     */
138    public function render($format, Doku_Renderer $renderer, $data) {
139        global $ID;
140
141        if($format != 'xhtml') return false;
142        if(is_null($data)) return false;
143        if(!$this->dthlp->ready()) return false;
144        $renderer->info['cache'] = false;
145
146        $sqlite = $this->dthlp->_getDB();
147        if(!$sqlite) return false;
148
149        $ckey = array_keys($data['cols']);
150        $ckey = $ckey[0];
151
152        if(!isset($data['page'])) {
153            $data['page'] = $ID;
154        }
155
156        $this->dthlp->_replacePlaceholdersInSQL($data);
157
158        // build cloud data
159        $res = $sqlite->query($data['sql']);
160        $rows = $sqlite->res2arr($res);
161        $min = 0;
162        $max = 0;
163        $tags = array();
164        foreach($rows as $row) {
165            if(!$max) {
166                $max = $row['cnt'];
167            }
168            $min = $row['cnt'];
169            $tags[$row['value']]['cnt'] = $row['cnt'];
170            $tags[$row['value']]['value'] = $row['value'];
171        }
172        $this->_cloud_weight($tags, $min, $max, 5);
173
174        // output cloud
175        $renderer->doc .= sprintf($this->before_item, hsc($data['classes']));
176        foreach($tags as $tag) {
177            $tagLabelText = hsc($tag['value']);
178            if($data['summarize'] == 1) {
179                $tagLabelText .= '<sub>(' . $tag['cnt'] . ')</sub>';
180            }
181
182            $renderer->doc .= sprintf($this->before_val, $tag['lvl']);
183            $renderer->doc .= '<a href="' . wl($data['page'], $this->dthlp->_getTagUrlparam($data['cols'][$ckey], $tag['value'])) .
184                              '" title="' . sprintf($this->getLang('tagfilter'), hsc($tag['value'])) .
185                              '" class="wikilink1">' . $tagLabelText . '</a>';
186            $renderer->doc .= $this->after_val;
187        }
188        $renderer->doc .= $this->after_item;
189        return true;
190    }
191
192    /**
193     * Create a weighted tag distribution
194     *
195     * @param $tags array ref The tags to weight ( tag => count)
196     * @param $min int      The lowest count of a single tag
197     * @param $max int      The highest count of a single tag
198     * @param $levels int   The number of levels you want. A 5 gives levels 0 to 4.
199     */
200    protected function _cloud_weight(&$tags, $min, $max, $levels) {
201        $levels--;
202
203        // calculate tresholds
204        $tresholds = array();
205        for($i = 0; $i <= $levels; $i++) {
206            $tresholds[$i] = pow($max - $min + 1, $i / $levels) + $min - 1;
207        }
208
209        // assign weights
210        foreach($tags as $tag) {
211            foreach($tresholds as $tresh => $val) {
212                if($tag['cnt'] <= $val) {
213                    $tags[$tag['value']]['lvl'] = $tresh;
214                    break;
215                }
216                $tags[$tag['value']]['lvl'] = $levels;
217            }
218        }
219
220        // sort
221        ksort($tags);
222    }
223
224}
225
226