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