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