1<?php
2
3namespace dokuwiki\plugin\struct\types;
4
5use dokuwiki\plugin\struct\meta\ValidationException;
6
7class Color extends AbstractBaseType
8{
9    protected $config = [
10        'default' => '#ffffff'
11    ];
12
13    /**
14     * @inheritDoc
15     */
16    public function validate($rawvalue)
17    {
18        $rawvalue = trim(strtolower($rawvalue));
19        if (!preg_match('/^#[a-f0-9]{6}$/', $rawvalue)) {
20            throw new ValidationException('bad color specification');
21        }
22
23        // ignore if default
24        if ($rawvalue === strtolower($this->config['default'])) {
25            $rawvalue = '';
26        }
27
28        return $rawvalue;
29    }
30
31    /**
32     * @inheritDoc
33     */
34    public function renderValue($value, \Doku_Renderer $R, $mode)
35    {
36        if ($mode == 'xhtml') {
37            $R->doc .= '<div title="' . hsc($value) . '" style="background-color:' . hsc($value) . ';"
38                        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(implode(', ', $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 = [
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, $showCount = null)
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'>
92                        <span class='a11y'>$color</span>
93                        </a>";
94            return;
95        }
96        $R->internallink("$page?$filter", $color);
97    }
98
99
100    /**
101     * Sort by the hue of a color, not by its hex-representation
102     */
103    public function getSortString($value)
104    {
105        $hue = $this->getHue(parent::getSortString($value));
106        return $hue;
107    }
108
109    /**
110     * Calculate the hue of a color to use it for sorting so we can sort similar colors together.
111     *
112     * @param string $color the color as #RRGGBB
113     * @return float|int
114     */
115    protected function getHue($color)
116    {
117        if (!preg_match('/^#[0-9A-F]{6}$/i', $color)) {
118            return 0;
119        }
120
121        $red = hexdec(substr($color, 1, 2));
122        $green = hexdec(substr($color, 3, 2));
123        $blue = hexdec(substr($color, 5, 2));
124
125        $min = min([$red, $green, $blue]);
126        $max = max([$red, $green, $blue]);
127
128        if ($max === $red) {
129            $hue = ($green - $blue) / ($max - $min);
130        }
131        if ($max === $green) {
132            $hue = 2 + ($blue - $red) / ($max - $min);
133        }
134        if ($max === $blue) {
135            $hue = 4 + ($red - $green) / ($max - $min);
136        }
137        $hue *= 60;
138        if ($hue < 0) {
139            $hue += 360;
140        }
141        return $hue;
142    }
143}
144