xref: /template/sprintdoc/svg.php (revision 32cb5749b36ee85781ebbad564879cd97b5ed3a1)
11072ee52SAndreas Gohr<?php
21072ee52SAndreas Gohr
31072ee52SAndreas Gohrnamespace dokuwiki\template\sprintdoc;
41072ee52SAndreas Gohr
51072ee52SAndreas Gohrif(!defined('DOKU_INC')) define('DOKU_INC', dirname(__FILE__) . '/../../../');
61072ee52SAndreas Gohrrequire_once(DOKU_INC . 'inc/init.php');
71072ee52SAndreas Gohr
81072ee52SAndreas Gohr/**
91072ee52SAndreas Gohr * Custom XML node that allows prepending
101072ee52SAndreas Gohr */
111072ee52SAndreas Gohrclass SvgNode extends \SimpleXMLElement {
121072ee52SAndreas Gohr    /**
131072ee52SAndreas Gohr     * @param string $name Name of the new node
141072ee52SAndreas Gohr     * @param null|string $value
151072ee52SAndreas Gohr     * @return SvgNode
161072ee52SAndreas Gohr     */
171072ee52SAndreas Gohr    public function prependChild($name, $value = null) {
181072ee52SAndreas Gohr        $dom = dom_import_simplexml($this);
191072ee52SAndreas Gohr
201072ee52SAndreas Gohr        $new = $dom->insertBefore(
211072ee52SAndreas Gohr            $dom->ownerDocument->createElement($name, $value),
221072ee52SAndreas Gohr            $dom->firstChild
231072ee52SAndreas Gohr        );
241072ee52SAndreas Gohr
251072ee52SAndreas Gohr        return simplexml_import_dom($new, get_class($this));
261072ee52SAndreas Gohr    }
2780d784e1SMichael Grosse
2880d784e1SMichael Grosse    /**
2980d784e1SMichael Grosse     * @param \SimpleXMLElement $node the node to be added
3080d784e1SMichael Grosse     * @return \SimpleXMLElement
3180d784e1SMichael Grosse     */
3280d784e1SMichael Grosse    public function appendNode(\SimpleXMLElement $node) {
3380d784e1SMichael Grosse        $dom = dom_import_simplexml($this);
3480d784e1SMichael Grosse        $domNode = dom_import_simplexml($node);
3580d784e1SMichael Grosse
3680d784e1SMichael Grosse        $newNode = $dom->appendChild($domNode);
3780d784e1SMichael Grosse        return simplexml_import_dom($newNode, get_class($this));
3880d784e1SMichael Grosse    }
3980d784e1SMichael Grosse
4080d784e1SMichael Grosse    /**
4180d784e1SMichael Grosse     * @param \SimpleXMLElement $node the child to remove
4280d784e1SMichael Grosse     * @return \SimpleXMLElement
4380d784e1SMichael Grosse     */
4480d784e1SMichael Grosse    public function removeChild(\SimpleXMLElement $node) {
4580d784e1SMichael Grosse        $dom = dom_import_simplexml($node);
4680d784e1SMichael Grosse        $dom->parentNode->removeChild($dom);
4780d784e1SMichael Grosse        return $node;
4880d784e1SMichael Grosse    }
4924ab1f72SAndreas Gohr
5024ab1f72SAndreas Gohr    /**
5124ab1f72SAndreas Gohr     * Wraps all elements of $this in a `<g>` tag
5224ab1f72SAndreas Gohr     *
5324ab1f72SAndreas Gohr     * @return SvgNode
5424ab1f72SAndreas Gohr     */
5524ab1f72SAndreas Gohr    public function groupChildren() {
5624ab1f72SAndreas Gohr        $dom = dom_import_simplexml($this);
5724ab1f72SAndreas Gohr
5824ab1f72SAndreas Gohr        $g = $dom->ownerDocument->createElement('g');
5924ab1f72SAndreas Gohr        while($dom->childNodes->length > 0) {
6024ab1f72SAndreas Gohr            $child = $dom->childNodes->item(0);
6124ab1f72SAndreas Gohr            $dom->removeChild($child);
6224ab1f72SAndreas Gohr            $g->appendChild($child);
6324ab1f72SAndreas Gohr        }
6424ab1f72SAndreas Gohr        $g = $dom->appendChild($g);
6524ab1f72SAndreas Gohr
6624ab1f72SAndreas Gohr        return simplexml_import_dom($g, get_class($this));
6724ab1f72SAndreas Gohr    }
6824ab1f72SAndreas Gohr
6924ab1f72SAndreas Gohr    /**
7024ab1f72SAndreas Gohr     * Add new style definitions to this element
7124ab1f72SAndreas Gohr     * @param string $style
7224ab1f72SAndreas Gohr     */
7324ab1f72SAndreas Gohr    public function addStyle($style) {
7424ab1f72SAndreas Gohr        $defs = $this->defs;
7524ab1f72SAndreas Gohr        if(!$defs) {
7624ab1f72SAndreas Gohr            $defs = $this->prependChild('defs');
7724ab1f72SAndreas Gohr        }
7824ab1f72SAndreas Gohr        $defs->addChild('style', $style);
7924ab1f72SAndreas Gohr    }
801072ee52SAndreas Gohr}
811072ee52SAndreas Gohr
821072ee52SAndreas Gohr/**
831072ee52SAndreas Gohr * Manage SVG recoloring
841072ee52SAndreas Gohr */
851072ee52SAndreas Gohrclass SVG {
861072ee52SAndreas Gohr
871072ee52SAndreas Gohr    const IMGDIR = __DIR__ . '/img/';
8880d784e1SMichael Grosse    const BACKGROUNDCLASS = 'sprintdoc-background';
891ef4c4dbSAndreas Gohr    const CDNBASE = 'https://raw.githubusercontent.com/Templarian/MaterialDesign/master/svg/';
901072ee52SAndreas Gohr
9124ab1f72SAndreas Gohr    protected $file;
9294def893SAndreas Gohr    protected $replacements;
931072ee52SAndreas Gohr
941072ee52SAndreas Gohr    /**
951072ee52SAndreas Gohr     * SVG constructor
961072ee52SAndreas Gohr     */
971072ee52SAndreas Gohr    public function __construct() {
981072ee52SAndreas Gohr        global $INPUT;
991072ee52SAndreas Gohr
1001072ee52SAndreas Gohr        $svg = cleanID($INPUT->str('svg'));
1011072ee52SAndreas Gohr        if(blank($svg)) $this->abort(404);
1021072ee52SAndreas Gohr
1031072ee52SAndreas Gohr        // try local file first
1041072ee52SAndreas Gohr        $file = self::IMGDIR . $svg;
1051072ee52SAndreas Gohr        if(!file_exists($file)) {
106c24a2e1eSAndreas Gohr            // try media file
107c24a2e1eSAndreas Gohr            $file = mediaFN($svg);
108c24a2e1eSAndreas Gohr            if(file_exists($file)) {
1091072ee52SAndreas Gohr                // media files are ACL protected
1103ec07d58SAndreas Gohr                if(auth_quickaclcheck($svg) < AUTH_READ) $this->abort(403);
111c24a2e1eSAndreas Gohr            } else {
112c24a2e1eSAndreas Gohr                // get it from material design icons
113c24a2e1eSAndreas Gohr                $file = getCacheName($svg, '.svg');
1147b3c2fe1SMichael Große                if (!file_exists($file)) {
115c24a2e1eSAndreas Gohr                    io_download(self::CDNBASE . $svg, $file);
116c24a2e1eSAndreas Gohr                }
1177b3c2fe1SMichael Große            }
118c24a2e1eSAndreas Gohr
1191072ee52SAndreas Gohr        }
1201072ee52SAndreas Gohr        // check if media exists
1211072ee52SAndreas Gohr        if(!file_exists($file)) $this->abort(404);
1221072ee52SAndreas Gohr
12324ab1f72SAndreas Gohr        $this->file = $file;
1241072ee52SAndreas Gohr    }
1251072ee52SAndreas Gohr
1261072ee52SAndreas Gohr    /**
1271072ee52SAndreas Gohr     * Generate and output
1281072ee52SAndreas Gohr     */
1291072ee52SAndreas Gohr    public function out() {
130e988c176SAndreas Gohr        global $conf;
13124ab1f72SAndreas Gohr        $file = $this->file;
13224ab1f72SAndreas Gohr        $params = $this->getParameters();
13324ab1f72SAndreas Gohr
1344fd6492bSAndreas Gohr        header('Content-Type: image/svg+xml');
135e988c176SAndreas Gohr        $cachekey = md5($file . serialize($params) . $conf['template'] . filemtime(__FILE__));
136ac484891Sfiwswe        $cache = new \dokuwiki\Cache\Cache($cachekey, '.svg');
137ac484891Sfiwswe        $cache->setEvent('SVG_CACHE');
13824ab1f72SAndreas Gohr
13924ab1f72SAndreas Gohr        http_cached($cache->cache, $cache->useCache(array('files' => array($file, __FILE__))));
1409fd3d99bSAndreas Gohr        if($params['e']) {
1419fd3d99bSAndreas Gohr            $content = $this->embedSVG($file);
1429fd3d99bSAndreas Gohr        } else {
1439fd3d99bSAndreas Gohr            $content = $this->generateSVG($file, $params);
1449fd3d99bSAndreas Gohr        }
1459fd3d99bSAndreas Gohr        http_cached_finish($cache->cache, $content);
1461072ee52SAndreas Gohr    }
1471072ee52SAndreas Gohr
1481072ee52SAndreas Gohr    /**
14924ab1f72SAndreas Gohr     * Generate a new SVG based on the input file and the parameters
1501072ee52SAndreas Gohr     *
15124ab1f72SAndreas Gohr     * @param string $file the SVG file to load
15224ab1f72SAndreas Gohr     * @param array $params the parameters as returned by getParameters()
15324ab1f72SAndreas Gohr     * @return string the new XML contents
1541072ee52SAndreas Gohr     */
15524ab1f72SAndreas Gohr    protected function generateSVG($file, $params) {
15624ab1f72SAndreas Gohr        /** @var SvgNode $xml */
15724ab1f72SAndreas Gohr        $xml = simplexml_load_file($file, SvgNode::class);
15824ab1f72SAndreas Gohr        $xml->addStyle($this->makeStyle($params));
15924ab1f72SAndreas Gohr        $this->createBackground($xml);
16024ab1f72SAndreas Gohr        $xml->groupChildren();
16124ab1f72SAndreas Gohr
16224ab1f72SAndreas Gohr        return $xml->asXML();
16324ab1f72SAndreas Gohr    }
16424ab1f72SAndreas Gohr
16524ab1f72SAndreas Gohr    /**
1669fd3d99bSAndreas Gohr     * Return the absolute minimum path definition for direct embedding
1679fd3d99bSAndreas Gohr     *
1689fd3d99bSAndreas Gohr     * No styles will be applied. They have to be done in CSS
1699fd3d99bSAndreas Gohr     *
1709fd3d99bSAndreas Gohr     * @param string $file the SVG file to load
1719fd3d99bSAndreas Gohr     * @return string the new XML contents
1729fd3d99bSAndreas Gohr     */
1739fd3d99bSAndreas Gohr    protected function embedSVG($file) {
1749fd3d99bSAndreas Gohr        /** @var SvgNode $xml */
1759fd3d99bSAndreas Gohr        $xml = simplexml_load_file($file, SvgNode::class);
1769fd3d99bSAndreas Gohr
1779fd3d99bSAndreas Gohr        $def = hsc((string) $xml->path['d']);
178e4a08effSAnna Dabrowska        $w = hsc($xml['width'] ?? '100%');
179e4a08effSAnna Dabrowska        $h = hsc($xml['height'] ?? '100%');
1809fd3d99bSAndreas Gohr        $v = hsc($xml['viewBox']);
1819fd3d99bSAndreas Gohr
182*32cb5749SAnna Dabrowska        // if viewbox is not defined, construct it from width and height, if available
183*32cb5749SAnna Dabrowska        if (empty($v) && !empty($w) && !empty($h)) {
184*32cb5749SAnna Dabrowska            $v = hsc("0 0 $w $h");
185*32cb5749SAnna Dabrowska        }
186*32cb5749SAnna Dabrowska
187*32cb5749SAnna Dabrowska        return "<svg viewBox=\"$v\"><path d=\"$def\" /></svg>";
1889fd3d99bSAndreas Gohr    }
1899fd3d99bSAndreas Gohr
1909fd3d99bSAndreas Gohr    /**
19124ab1f72SAndreas Gohr     * Get the supported parameters from request
19224ab1f72SAndreas Gohr     *
19324ab1f72SAndreas Gohr     * @return array
19424ab1f72SAndreas Gohr     */
19524ab1f72SAndreas Gohr    protected function getParameters() {
1961072ee52SAndreas Gohr        global $INPUT;
1971072ee52SAndreas Gohr
19824ab1f72SAndreas Gohr        $params = array(
1999fd3d99bSAndreas Gohr            'e' => $INPUT->bool('e', false),
2001072ee52SAndreas Gohr            's' => $this->fixColor($INPUT->str('s')),
2011072ee52SAndreas Gohr            'f' => $this->fixColor($INPUT->str('f')),
20280d784e1SMichael Grosse            'b' => $this->fixColor($INPUT->str('b')),
2031072ee52SAndreas Gohr            'sh' => $this->fixColor($INPUT->str('sh')),
2041072ee52SAndreas Gohr            'fh' => $this->fixColor($INPUT->str('fh')),
20580d784e1SMichael Grosse            'bh' => $this->fixColor($INPUT->str('bh')),
2061072ee52SAndreas Gohr        );
2071072ee52SAndreas Gohr
20824ab1f72SAndreas Gohr        return $params;
20980d784e1SMichael Grosse    }
21080d784e1SMichael Grosse
21124ab1f72SAndreas Gohr    /**
21224ab1f72SAndreas Gohr     * Generate a style setting from the input variables
21324ab1f72SAndreas Gohr     *
21424ab1f72SAndreas Gohr     * @param array $params associative array with the given parameters
21524ab1f72SAndreas Gohr     * @return string
21624ab1f72SAndreas Gohr     */
21724ab1f72SAndreas Gohr    protected function makeStyle($params) {
21824ab1f72SAndreas Gohr        $element = 'path'; // FIXME configurable?
21980d784e1SMichael Grosse
22024ab1f72SAndreas Gohr        if(empty($params['b'])) {
22124ab1f72SAndreas Gohr            $params['b'] = $this->fixColor('00000000');
22280d784e1SMichael Grosse        }
22380d784e1SMichael Grosse
22424ab1f72SAndreas Gohr        $style = 'g rect.' . self::BACKGROUNDCLASS . '{fill:' . $params['b'] . ';}';
22524ab1f72SAndreas Gohr
22624ab1f72SAndreas Gohr        if($params['bh']) {
22724ab1f72SAndreas Gohr            $style .= 'g:hover rect.' . self::BACKGROUNDCLASS . '{fill:' . $params['bh'] . ';}';
22824ab1f72SAndreas Gohr        }
22924ab1f72SAndreas Gohr
23024ab1f72SAndreas Gohr        if($params['s'] || $params['f']) {
23180d784e1SMichael Grosse            $style .= 'g ' . $element . '{';
23224ab1f72SAndreas Gohr            if($params['s']) $style .= 'stroke:' . $params['s'] . ';';
23324ab1f72SAndreas Gohr            if($params['f']) $style .= 'fill:' . $params['f'] . ';';
2341072ee52SAndreas Gohr            $style .= '}';
2351072ee52SAndreas Gohr        }
2361072ee52SAndreas Gohr
23724ab1f72SAndreas Gohr        if($params['sh'] || $params['fh']) {
23880d784e1SMichael Grosse            $style .= 'g:hover ' . $element . '{';
23924ab1f72SAndreas Gohr            if($params['sh']) $style .= 'stroke:' . $params['sh'] . ';';
24024ab1f72SAndreas Gohr            if($params['fh']) $style .= 'fill:' . $params['fh'] . ';';
2411072ee52SAndreas Gohr            $style .= '}';
2421072ee52SAndreas Gohr        }
2431072ee52SAndreas Gohr
2441072ee52SAndreas Gohr        return $style;
2451072ee52SAndreas Gohr    }
2461072ee52SAndreas Gohr
2471072ee52SAndreas Gohr    /**
2481072ee52SAndreas Gohr     * Takes a hexadecimal color string in the following forms:
2491072ee52SAndreas Gohr     *
2501072ee52SAndreas Gohr     * RGB
2511072ee52SAndreas Gohr     * RRGGBB
2521072ee52SAndreas Gohr     * RRGGBBAA
2531072ee52SAndreas Gohr     *
25494def893SAndreas Gohr     * Converts it to rgba() form.
25594def893SAndreas Gohr     *
25694def893SAndreas Gohr     * Alternatively takes a replacement name from the current template's style.ini
2571072ee52SAndreas Gohr     *
2581072ee52SAndreas Gohr     * @param string $color
2591072ee52SAndreas Gohr     * @return string
2601072ee52SAndreas Gohr     */
261e988c176SAndreas Gohr    protected function fixColor($color) {
262e988c176SAndreas Gohr        if($color === '') return '';
2631072ee52SAndreas Gohr        if(preg_match('/^([0-9a-f])([0-9a-f])([0-9a-f])$/i', $color, $m)) {
2641072ee52SAndreas Gohr            $r = hexdec($m[1] . $m[1]);
2651072ee52SAndreas Gohr            $g = hexdec($m[2] . $m[2]);
2661072ee52SAndreas Gohr            $b = hexdec($m[3] . $m[3]);
2671072ee52SAndreas Gohr            $a = hexdec('ff');
2681072ee52SAndreas Gohr        } elseif(preg_match('/^([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})?$/i', $color, $m)) {
2691072ee52SAndreas Gohr            $r = hexdec($m[1]);
2701072ee52SAndreas Gohr            $g = hexdec($m[2]);
2711072ee52SAndreas Gohr            $b = hexdec($m[3]);
2721072ee52SAndreas Gohr            if(isset($m[4])) {
2731072ee52SAndreas Gohr                $a = hexdec($m[4]);
2741072ee52SAndreas Gohr            } else {
2751072ee52SAndreas Gohr                $a = hexdec('ff');
2761072ee52SAndreas Gohr            }
2771072ee52SAndreas Gohr        } else {
278e988c176SAndreas Gohr            if(is_null($this->replacements)) $this->initReplacements();
27994def893SAndreas Gohr            if(isset($this->replacements[$color])) {
28094def893SAndreas Gohr                return $this->replacements[$color];
28194def893SAndreas Gohr            }
2823a6c6601SAndreas Gohr            if(isset($this->replacements['__' . $color . '__'])) {
2833a6c6601SAndreas Gohr                return $this->replacements['__' . $color . '__'];
2843a6c6601SAndreas Gohr            }
2851072ee52SAndreas Gohr            return '';
2861072ee52SAndreas Gohr        }
2871072ee52SAndreas Gohr
2881072ee52SAndreas Gohr        return "rgba($r,$g,$b,$a)";
2891072ee52SAndreas Gohr    }
2901072ee52SAndreas Gohr
2911072ee52SAndreas Gohr    /**
29280d784e1SMichael Grosse     * sets a rectangular background of the size of the svg/this itself
29380d784e1SMichael Grosse     *
29480d784e1SMichael Grosse     * @param SvgNode $g
29580d784e1SMichael Grosse     * @return SvgNode
29680d784e1SMichael Grosse     */
29724ab1f72SAndreas Gohr    protected function createBackground(SvgNode $g) {
29880d784e1SMichael Grosse        $rect = $g->prependChild('rect');
29980d784e1SMichael Grosse        $rect->addAttribute('class', self::BACKGROUNDCLASS);
30080d784e1SMichael Grosse
30180d784e1SMichael Grosse        $rect->addAttribute('x', '0');
30280d784e1SMichael Grosse        $rect->addAttribute('y', '0');
30324ab1f72SAndreas Gohr        $rect->addAttribute('height', '100%');
30424ab1f72SAndreas Gohr        $rect->addAttribute('width', '100%');
30580d784e1SMichael Grosse        return $rect;
30680d784e1SMichael Grosse    }
30780d784e1SMichael Grosse
30880d784e1SMichael Grosse    /**
3091072ee52SAndreas Gohr     * Abort processing with given status code
3101072ee52SAndreas Gohr     *
3111072ee52SAndreas Gohr     * @param int $status
3121072ee52SAndreas Gohr     */
3131072ee52SAndreas Gohr    protected function abort($status) {
3141072ee52SAndreas Gohr        http_status($status);
3151072ee52SAndreas Gohr        exit;
3161072ee52SAndreas Gohr    }
3171072ee52SAndreas Gohr
31894def893SAndreas Gohr    /**
31994def893SAndreas Gohr     * Initialize the available replacement patterns
32094def893SAndreas Gohr     *
32194def893SAndreas Gohr     * Loads the style.ini from the template (and various local locations)
32294def893SAndreas Gohr     * via a core function only available through some hack.
32394def893SAndreas Gohr     */
32494def893SAndreas Gohr    protected function initReplacements() {
32594def893SAndreas Gohr        global $conf;
3268c8001b6SMichael Große        if (!class_exists('\dokuwiki\StyleUtils')) {
3278c8001b6SMichael Große            // Pre-Greebo Compatibility
3288c8001b6SMichael Große
32994def893SAndreas Gohr            define('SIMPLE_TEST', 1); // hacky shit
33094def893SAndreas Gohr            include DOKU_INC . 'lib/exe/css.php';
331e988c176SAndreas Gohr            $ini = css_styleini($conf['template']);
33294def893SAndreas Gohr            $this->replacements = $ini['replacements'];
3338c8001b6SMichael Große            return;
3348c8001b6SMichael Große        }
3358c8001b6SMichael Große
3368c8001b6SMichael Große        $stuleUtils = new \dokuwiki\StyleUtils();
3378c8001b6SMichael Große        $ini = $stuleUtils->cssStyleini('sprintdoc');
3388c8001b6SMichael Große        $this->replacements = $ini['replacements'];
33994def893SAndreas Gohr    }
3401072ee52SAndreas Gohr}
3411072ee52SAndreas Gohr
3421072ee52SAndreas Gohr// main
3431072ee52SAndreas Gohr$svg = new SVG();
3441072ee52SAndreas Gohr$svg->out();
3451072ee52SAndreas Gohr
3469fd3d99bSAndreas Gohr
347