xref: /plugin/struct/meta/AggregationCloud.php (revision c7dd6b6ae6b1d46da10628f8b099c209561c17b2)
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