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