1fa04b28cSMichael Grosse<?php 25282d027SMichael Grosse 3fa04b28cSMichael Grossenamespace dokuwiki\plugin\struct\meta; 45282d027SMichael Grosse 5fa04b28cSMichael Grosseclass AggregationCloud { 65282d027SMichael Grosse 7fa04b28cSMichael Grosse /** 8fa04b28cSMichael Grosse * @var string the page id of the page this is rendered to 9fa04b28cSMichael Grosse */ 10fa04b28cSMichael Grosse protected $id; 115282d027SMichael Grosse 12fa04b28cSMichael Grosse /** 13fa04b28cSMichael Grosse * @var string the Type of renderer used 14fa04b28cSMichael Grosse */ 15fa04b28cSMichael Grosse protected $mode; 165282d027SMichael Grosse 17fa04b28cSMichael Grosse /** 18fa04b28cSMichael Grosse * @var \Doku_Renderer the DokuWiki renderer used to create the output 19fa04b28cSMichael Grosse */ 20fa04b28cSMichael Grosse protected $renderer; 215282d027SMichael Grosse 22fa04b28cSMichael Grosse /** 23fa04b28cSMichael Grosse * @var SearchConfig the configured search - gives access to columns etc. 24fa04b28cSMichael Grosse */ 25fa04b28cSMichael Grosse protected $searchConfig; 265282d027SMichael Grosse 27fa04b28cSMichael Grosse /** 28fa04b28cSMichael Grosse * @var Column[] the list of columns to be displayed 29fa04b28cSMichael Grosse */ 30fa04b28cSMichael Grosse protected $columns; 315282d027SMichael Grosse 32fa04b28cSMichael Grosse /** 33fa04b28cSMichael Grosse * @var Value[][] the search result 34fa04b28cSMichael Grosse */ 35fa04b28cSMichael Grosse protected $result; 365282d027SMichael Grosse 37fa04b28cSMichael Grosse /** 38fa04b28cSMichael Grosse * @var int number of all results 39fa04b28cSMichael Grosse */ 40fa04b28cSMichael Grosse protected $resultCount; 415282d027SMichael Grosse 42fa04b28cSMichael Grosse /** 43fa04b28cSMichael Grosse * Initialize the Aggregation renderer and executes the search 44fa04b28cSMichael Grosse * 45fa04b28cSMichael Grosse * You need to call @see render() on the resulting object. 46fa04b28cSMichael Grosse * 47fa04b28cSMichael Grosse * @param string $id 48fa04b28cSMichael Grosse * @param string $mode 49fa04b28cSMichael Grosse * @param \Doku_Renderer $renderer 50fa04b28cSMichael Grosse * @param SearchConfig $searchConfig 51fa04b28cSMichael Grosse */ 520699ff47SMichael Grosse public function __construct($id, $mode, \Doku_Renderer $renderer, SearchCloud $searchConfig) { 53fa04b28cSMichael Grosse $this->id = $id; 54fa04b28cSMichael Grosse $this->mode = $mode; 55fa04b28cSMichael Grosse $this->renderer = $renderer; 56fa04b28cSMichael Grosse $this->searchConfig = $searchConfig; 57fa04b28cSMichael Grosse $this->data = $searchConfig->getConf(); 58fa04b28cSMichael Grosse $this->columns = $searchConfig->getColumns(); 590699ff47SMichael Grosse $this->result = $this->searchConfig->execute(); 600699ff47SMichael Grosse $this->resultCount = $this->searchConfig->getCount(); 61aafac1c1SMichael Grosse 62aafac1c1SMichael Grosse $this->max = $this->result[0]['count']; 63aafac1c1SMichael Grosse $this->min = end($this->result)['count']; 64fa04b28cSMichael Grosse } 65fa04b28cSMichael Grosse 66fa04b28cSMichael Grosse /** 67fa04b28cSMichael Grosse * Create the table on the renderer 68fa04b28cSMichael Grosse */ 69fa04b28cSMichael Grosse public function render() { 708b4531cfSMichael Grosse 718b4531cfSMichael Grosse $this->sortResults(); 728b4531cfSMichael Grosse 73fa04b28cSMichael Grosse $this->startScope(); 74*c7dd6b6aSMichael Grosse $this->startList(); 75fa04b28cSMichael Grosse foreach ($this->result as $result) { 7633bd00e9SMichael Grosse $this->renderTag($result); 77fa04b28cSMichael Grosse } 78*c7dd6b6aSMichael Grosse $this->finishList(); 79fa04b28cSMichael Grosse $this->finishScope(); 80fa04b28cSMichael Grosse return; 81fa04b28cSMichael Grosse } 825282d027SMichael Grosse 83fa04b28cSMichael Grosse /** 84fa04b28cSMichael Grosse * Adds additional info to document and renderer in XHTML mode 85fa04b28cSMichael Grosse * 86fa04b28cSMichael Grosse * @see finishScope() 87fa04b28cSMichael Grosse */ 88fa04b28cSMichael Grosse protected function startScope() { 89fa04b28cSMichael Grosse // wrapping div 90fa04b28cSMichael Grosse if($this->mode != 'xhtml') return; 91be2ae900SMichael Grosse $this->renderer->doc .= "<div class=\"structcloud\">"; 92fa04b28cSMichael Grosse } 935282d027SMichael Grosse 94fa04b28cSMichael Grosse /** 95fa04b28cSMichael Grosse * Closes the table and anything opened in startScope() 96fa04b28cSMichael Grosse * 97fa04b28cSMichael Grosse * @see startScope() 98fa04b28cSMichael Grosse */ 99fa04b28cSMichael Grosse protected function finishScope() { 100fa04b28cSMichael Grosse // wrapping div 101fa04b28cSMichael Grosse if($this->mode != 'xhtml') return; 102fa04b28cSMichael Grosse $this->renderer->doc .= '</div>'; 103fa04b28cSMichael Grosse } 104aafac1c1SMichael Grosse 10533bd00e9SMichael Grosse /** 10633bd00e9SMichael Grosse * Render a tag of the cloud 10733bd00e9SMichael Grosse * 10833bd00e9SMichael Grosse * @param ['tag' => Value, 'count' => int] $result 10933bd00e9SMichael Grosse */ 11033bd00e9SMichael Grosse protected function renderTag($result) { 111fa04b28cSMichael Grosse /** 112fa04b28cSMichael Grosse * @var Value $value 113fa04b28cSMichael Grosse */ 114fa04b28cSMichael Grosse $value = $result['tag']; 115fa04b28cSMichael Grosse $count = $result['count']; 116fa04b28cSMichael Grosse if ($value->isEmpty()) { 117fa04b28cSMichael Grosse return; 118fa04b28cSMichael Grosse } 119fa04b28cSMichael Grosse 120*c7dd6b6aSMichael Grosse $this->renderer->listitem_open(1); 121*c7dd6b6aSMichael Grosse $this->renderer->listcontent_open(); 1228e54e6f4SMichael Grosse 123*c7dd6b6aSMichael Grosse $this->renderTagLink($value, $count); 124*c7dd6b6aSMichael Grosse $this->renderer->listcontent_close(); 125*c7dd6b6aSMichael Grosse $this->renderer->listitem_close(); 1268e54e6f4SMichael Grosse } 1278e54e6f4SMichael Grosse 1288e54e6f4SMichael Grosse /** 1298e54e6f4SMichael Grosse * @param Value $value 130*c7dd6b6aSMichael Grosse * @param int $count 1318e54e6f4SMichael Grosse */ 132*c7dd6b6aSMichael Grosse protected function renderTagLink(Value $value, $count) { 133*c7dd6b6aSMichael Grosse $type = strtolower($value->getColumn()->getType()->getClass()); 134*c7dd6b6aSMichael Grosse $weight = $this->getWeight($count, $this->min, $this->max); 1358e54e6f4SMichael Grosse $schema = $this->data['schemas'][0][0]; 1368e54e6f4SMichael Grosse $col = $value->getColumn()->getLabel(); 1378e54e6f4SMichael Grosse 13885edf4f2SMichael Grosse if (!empty($this->data['target'])) { 13985edf4f2SMichael Grosse $target = $this->data['target']; 14085edf4f2SMichael Grosse } else { 14185edf4f2SMichael Grosse global $INFO; 14285edf4f2SMichael Grosse $target = $INFO['id']; 14385edf4f2SMichael Grosse } 1448e54e6f4SMichael Grosse 145ce8676aeSMichael Grosse $tagValue = $value->getDisplayValue(); 146ce8676aeSMichael Grosse if (empty($tagValue)) { 147ce8676aeSMichael Grosse $tagValue = $value->getRawValue(); 148ce8676aeSMichael Grosse } 149ce8676aeSMichael Grosse if (is_array($tagValue)) { 150ce8676aeSMichael Grosse $tagValue = $tagValue[0]; 151fa04b28cSMichael Grosse } 1528e54e6f4SMichael Grosse $filter = "flt[$schema.$col*~]=" . urlencode($tagValue); 1538e54e6f4SMichael Grosse $linktext = $tagValue; 1544c969d82SMichael Grosse 155*c7dd6b6aSMichael Grosse 156*c7dd6b6aSMichael Grosse if($this->mode != 'xhtml') { 157*c7dd6b6aSMichael Grosse $this->renderer->internallink("$target?$filter",$linktext); 158*c7dd6b6aSMichael Grosse return; 159*c7dd6b6aSMichael Grosse } 160*c7dd6b6aSMichael Grosse 161*c7dd6b6aSMichael Grosse $this->renderer->doc .= "<div style='font-size:$weight%' data-count='$count' class='cloudtag struct_$type'>"; 162*c7dd6b6aSMichael Grosse 163*c7dd6b6aSMichael Grosse if ($type == 'color') { 16485edf4f2SMichael Grosse $url = wl($target, $filter); 1658e54e6f4SMichael Grosse $style = "background-color:$tagValue;display:block;height:100%"; 166*c7dd6b6aSMichael Grosse $this->renderer->doc .= "<a href='$url' style='$style'></a>"; 167*c7dd6b6aSMichael Grosse } else { 168*c7dd6b6aSMichael Grosse if ($type == 'media' && $value->getColumn()->getType()->getConfig()['mime'] == 'image/') { 1698e54e6f4SMichael Grosse $linktext = p_get_instructions("[[|{{{$tagValue}?$weight}}]]")[2][1][1]; 1708e54e6f4SMichael Grosse } 1718e54e6f4SMichael Grosse 172*c7dd6b6aSMichael Grosse $this->renderer->internallink("$target?$filter", $linktext); 173*c7dd6b6aSMichael Grosse } 174*c7dd6b6aSMichael Grosse $this->renderer->doc .= '</div>'; 175fa04b28cSMichael Grosse } 176aafac1c1SMichael Grosse 177aafac1c1SMichael Grosse /** 178aafac1c1SMichael Grosse * This interpolates the weight between 70 and 150 based on $min, $max and $current 179aafac1c1SMichael Grosse * 180aafac1c1SMichael Grosse * @param int $current 181aafac1c1SMichael Grosse * @param int $min 182aafac1c1SMichael Grosse * @param int $max 1838e54e6f4SMichael Grosse * @return int 184aafac1c1SMichael Grosse */ 185aafac1c1SMichael Grosse protected function getWeight($current, $min, $max) { 186aafac1c1SMichael Grosse if ($min == $max) { 187aafac1c1SMichael Grosse return 100; 188aafac1c1SMichael Grosse } 1898e54e6f4SMichael Grosse return round(($current - $min)/($max - $min) * 80 + 70); 190aafac1c1SMichael Grosse } 1918b4531cfSMichael Grosse 1928b4531cfSMichael Grosse /** 1938b4531cfSMichael Grosse * Sort the list of results 1948b4531cfSMichael Grosse */ 1958b4531cfSMichael Grosse protected function sortResults() { 1968b4531cfSMichael Grosse foreach ($this->result as &$result) { 1978b4531cfSMichael Grosse if ($result['tag']->getColumn()->getType()->getClass() == 'Color') { 1988b4531cfSMichael Grosse $result['sort'] = $this->getHue($result['tag']->getRawValue()); 1998b4531cfSMichael Grosse } else { 2008b4531cfSMichael Grosse $result['sort'] = $result['tag']->getDisplayValue(); 2018b4531cfSMichael Grosse } 2028b4531cfSMichael Grosse } 2038b4531cfSMichael Grosse usort($this->result, function ($a, $b) { 2048b4531cfSMichael Grosse if ($a['sort'] < $b['sort']) { 2058b4531cfSMichael Grosse return -1; 2068b4531cfSMichael Grosse } 2078b4531cfSMichael Grosse if ($a['sort'] > $b['sort']) { 2088b4531cfSMichael Grosse return 1; 2098b4531cfSMichael Grosse } 2108b4531cfSMichael Grosse return 0; 2118b4531cfSMichael Grosse }); 2128b4531cfSMichael Grosse } 2138b4531cfSMichael Grosse 2148b4531cfSMichael Grosse /** 2158b4531cfSMichael Grosse * Calculate the hue of a color to use it for sorting so we can sort similar colors together. 2168b4531cfSMichael Grosse * 2178b4531cfSMichael Grosse * @param string $color the color as #RRGGBB 2188b4531cfSMichael Grosse * @return float|int 2198b4531cfSMichael Grosse */ 2208b4531cfSMichael Grosse protected function getHue($color) { 2218b4531cfSMichael Grosse if (!preg_match('/^#[0-9A-F]{6}$/i', $color)) { 2228b4531cfSMichael Grosse return 0; 2238b4531cfSMichael Grosse } 2248b4531cfSMichael Grosse 2258b4531cfSMichael Grosse $red = hexdec(substr($color, 1, 2)); 2268b4531cfSMichael Grosse $green = hexdec(substr($color, 3, 2)); 2278b4531cfSMichael Grosse $blue = hexdec(substr($color, 5, 2)); 2288b4531cfSMichael Grosse 2298b4531cfSMichael Grosse $min = min([$red, $green, $blue]); 2308b4531cfSMichael Grosse $max = max([$red, $green, $blue]); 2318b4531cfSMichael Grosse 2328b4531cfSMichael Grosse if ($max == $red) { 2338b4531cfSMichael Grosse $hue = ($green-$blue)/($max-$min); 2348b4531cfSMichael Grosse } 2358b4531cfSMichael Grosse if ($max == $green) { 2368b4531cfSMichael Grosse $hue = 2 + ($blue-$red)/($max-$min); 2378b4531cfSMichael Grosse } 2388b4531cfSMichael Grosse if ($max == $blue) { 2398b4531cfSMichael Grosse $hue = 4 + ($red-$green)/($max-$min); 2408b4531cfSMichael Grosse } 2418b4531cfSMichael Grosse $hue = $hue * 60; 2428b4531cfSMichael Grosse if ($hue < 0) { 2438b4531cfSMichael Grosse $hue += 360; 2448b4531cfSMichael Grosse } 2458b4531cfSMichael Grosse return $hue; 2468b4531cfSMichael Grosse } 247*c7dd6b6aSMichael Grosse 248*c7dd6b6aSMichael Grosse protected function startList() { 249*c7dd6b6aSMichael Grosse $this->renderer->listu_open(); 250*c7dd6b6aSMichael Grosse } 251*c7dd6b6aSMichael Grosse 252*c7dd6b6aSMichael Grosse protected function finishList() { 253*c7dd6b6aSMichael Grosse $this->renderer->listu_close(); 254*c7dd6b6aSMichael Grosse } 255fa04b28cSMichael Grosse} 256