1<?php
2
3namespace dokuwiki\plugin\struct\types;
4
5use dokuwiki\plugin\struct\meta\ValidationException;
6
7class Color extends AbstractBaseType
8{
9
10    protected $config = array(
11        'default' => '#ffffff'
12    );
13
14    /**
15     * @inheritDoc
16     */
17    public function validate($rawvalue)
18    {
19        $rawvalue = trim(strtolower($rawvalue));
20        if (!preg_match('/^#[a-f0-9]{6}$/', $rawvalue)) {
21            throw new ValidationException('bad color specification');
22        }
23
24        // ignore if default
25        if ($rawvalue == strtolower($this->config['default'])) {
26            $rawvalue = '';
27        }
28
29        return $rawvalue;
30    }
31
32    /**
33     * @inheritDoc
34     */
35    public function renderValue($value, \Doku_Renderer $R, $mode)
36    {
37        if ($mode == 'xhtml') {
38            $R->doc .= '<div title="' . hsc($value) . '" style="background-color:' . hsc($value) . ';" class="struct_color"></div>';
39        } else {
40            $R->cdata($value);
41        }
42
43        return true;
44    }
45
46    /**
47     * @inheritDoc
48     */
49    public function renderMultiValue($values, \Doku_Renderer $R, $mode)
50    {
51        if ($mode == 'xhtml') {
52            foreach ($values as $value) {
53                $this->renderValue($value, $R, $mode);
54            }
55        } else {
56            $R->cdata(join(', ', $values));
57        }
58        return true;
59    }
60
61    /**
62     * @inheritDoc
63     */
64    public function valueEditor($name, $rawvalue, $htmlID)
65    {
66        if (!preg_match('/^#[a-f0-9]{6}$/', $rawvalue)) {
67            // any non-color (eg. from a previous type) should default to the default
68            $rawvalue = $this->config['default'];
69        }
70
71        $params = array(
72            'name' => $name,
73            'value' => $rawvalue,
74            'class' => 'struct_color',
75            'type' => 'color',
76            'id' => $htmlID
77        );
78        $attributes = buildAttributes($params, true);
79        return "<input $attributes />";
80    }
81
82    /**
83     * @inheritDoc
84     */
85    public function renderTagCloudLink($value, \Doku_Renderer $R, $mode, $page, $filter, $weight)
86    {
87        $color = $this->displayValue($value);
88        if ($mode == 'xhtml') {
89            $url = wl($page, $filter);
90            $style = "background-color:$color;";
91            $R->doc .= "<a class='struct_color_tagcloud' href='$url' style='$style'><span class='a11y'>$color</span></a>";
92            return;
93        }
94        $R->internallink("$page?$filter", $color);
95    }
96
97
98    /**
99     * Sort by the hue of a color, not by its hex-representation
100     */
101    public function getSortString($value)
102    {
103        $hue = $this->getHue(parent::getSortString($value));
104        return $hue;
105    }
106
107    /**
108     * Calculate the hue of a color to use it for sorting so we can sort similar colors together.
109     *
110     * @param string $color the color as #RRGGBB
111     * @return float|int
112     */
113    protected function getHue($color)
114    {
115        if (!preg_match('/^#[0-9A-F]{6}$/i', $color)) {
116            return 0;
117        }
118
119        $red   = hexdec(substr($color, 1, 2));
120        $green = hexdec(substr($color, 3, 2));
121        $blue  = hexdec(substr($color, 5, 2));
122
123        $min = min([$red, $green, $blue]);
124        $max = max([$red, $green, $blue]);
125
126        if ($max == $red) {
127            $hue = ($green - $blue) / ($max - $min);
128        }
129        if ($max == $green) {
130            $hue = 2 + ($blue - $red) / ($max - $min);
131        }
132        if ($max == $blue) {
133            $hue = 4 + ($red - $green) / ($max - $min);
134        }
135        $hue = $hue * 60;
136        if ($hue < 0) {
137            $hue += 360;
138        }
139        return $hue;
140    }
141}
142