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']); 178*e4a08effSAnna Dabrowska $w = hsc($xml['width'] ?? '100%'); 179*e4a08effSAnna Dabrowska $h = hsc($xml['height'] ?? '100%'); 1809fd3d99bSAndreas Gohr $v = hsc($xml['viewBox']); 1819fd3d99bSAndreas Gohr 1829fd3d99bSAndreas Gohr return "<svg width=\"$w\" height=\"$h\" viewBox=\"$v\"><path d=\"$def\" /></svg>"; 1839fd3d99bSAndreas Gohr } 1849fd3d99bSAndreas Gohr 1859fd3d99bSAndreas Gohr /** 18624ab1f72SAndreas Gohr * Get the supported parameters from request 18724ab1f72SAndreas Gohr * 18824ab1f72SAndreas Gohr * @return array 18924ab1f72SAndreas Gohr */ 19024ab1f72SAndreas Gohr protected function getParameters() { 1911072ee52SAndreas Gohr global $INPUT; 1921072ee52SAndreas Gohr 19324ab1f72SAndreas Gohr $params = array( 1949fd3d99bSAndreas Gohr 'e' => $INPUT->bool('e', false), 1951072ee52SAndreas Gohr 's' => $this->fixColor($INPUT->str('s')), 1961072ee52SAndreas Gohr 'f' => $this->fixColor($INPUT->str('f')), 19780d784e1SMichael Grosse 'b' => $this->fixColor($INPUT->str('b')), 1981072ee52SAndreas Gohr 'sh' => $this->fixColor($INPUT->str('sh')), 1991072ee52SAndreas Gohr 'fh' => $this->fixColor($INPUT->str('fh')), 20080d784e1SMichael Grosse 'bh' => $this->fixColor($INPUT->str('bh')), 2011072ee52SAndreas Gohr ); 2021072ee52SAndreas Gohr 20324ab1f72SAndreas Gohr return $params; 20480d784e1SMichael Grosse } 20580d784e1SMichael Grosse 20624ab1f72SAndreas Gohr /** 20724ab1f72SAndreas Gohr * Generate a style setting from the input variables 20824ab1f72SAndreas Gohr * 20924ab1f72SAndreas Gohr * @param array $params associative array with the given parameters 21024ab1f72SAndreas Gohr * @return string 21124ab1f72SAndreas Gohr */ 21224ab1f72SAndreas Gohr protected function makeStyle($params) { 21324ab1f72SAndreas Gohr $element = 'path'; // FIXME configurable? 21480d784e1SMichael Grosse 21524ab1f72SAndreas Gohr if(empty($params['b'])) { 21624ab1f72SAndreas Gohr $params['b'] = $this->fixColor('00000000'); 21780d784e1SMichael Grosse } 21880d784e1SMichael Grosse 21924ab1f72SAndreas Gohr $style = 'g rect.' . self::BACKGROUNDCLASS . '{fill:' . $params['b'] . ';}'; 22024ab1f72SAndreas Gohr 22124ab1f72SAndreas Gohr if($params['bh']) { 22224ab1f72SAndreas Gohr $style .= 'g:hover rect.' . self::BACKGROUNDCLASS . '{fill:' . $params['bh'] . ';}'; 22324ab1f72SAndreas Gohr } 22424ab1f72SAndreas Gohr 22524ab1f72SAndreas Gohr if($params['s'] || $params['f']) { 22680d784e1SMichael Grosse $style .= 'g ' . $element . '{'; 22724ab1f72SAndreas Gohr if($params['s']) $style .= 'stroke:' . $params['s'] . ';'; 22824ab1f72SAndreas Gohr if($params['f']) $style .= 'fill:' . $params['f'] . ';'; 2291072ee52SAndreas Gohr $style .= '}'; 2301072ee52SAndreas Gohr } 2311072ee52SAndreas Gohr 23224ab1f72SAndreas Gohr if($params['sh'] || $params['fh']) { 23380d784e1SMichael Grosse $style .= 'g:hover ' . $element . '{'; 23424ab1f72SAndreas Gohr if($params['sh']) $style .= 'stroke:' . $params['sh'] . ';'; 23524ab1f72SAndreas Gohr if($params['fh']) $style .= 'fill:' . $params['fh'] . ';'; 2361072ee52SAndreas Gohr $style .= '}'; 2371072ee52SAndreas Gohr } 2381072ee52SAndreas Gohr 2391072ee52SAndreas Gohr return $style; 2401072ee52SAndreas Gohr } 2411072ee52SAndreas Gohr 2421072ee52SAndreas Gohr /** 2431072ee52SAndreas Gohr * Takes a hexadecimal color string in the following forms: 2441072ee52SAndreas Gohr * 2451072ee52SAndreas Gohr * RGB 2461072ee52SAndreas Gohr * RRGGBB 2471072ee52SAndreas Gohr * RRGGBBAA 2481072ee52SAndreas Gohr * 24994def893SAndreas Gohr * Converts it to rgba() form. 25094def893SAndreas Gohr * 25194def893SAndreas Gohr * Alternatively takes a replacement name from the current template's style.ini 2521072ee52SAndreas Gohr * 2531072ee52SAndreas Gohr * @param string $color 2541072ee52SAndreas Gohr * @return string 2551072ee52SAndreas Gohr */ 256e988c176SAndreas Gohr protected function fixColor($color) { 257e988c176SAndreas Gohr if($color === '') return ''; 2581072ee52SAndreas Gohr if(preg_match('/^([0-9a-f])([0-9a-f])([0-9a-f])$/i', $color, $m)) { 2591072ee52SAndreas Gohr $r = hexdec($m[1] . $m[1]); 2601072ee52SAndreas Gohr $g = hexdec($m[2] . $m[2]); 2611072ee52SAndreas Gohr $b = hexdec($m[3] . $m[3]); 2621072ee52SAndreas Gohr $a = hexdec('ff'); 2631072ee52SAndreas Gohr } elseif(preg_match('/^([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})?$/i', $color, $m)) { 2641072ee52SAndreas Gohr $r = hexdec($m[1]); 2651072ee52SAndreas Gohr $g = hexdec($m[2]); 2661072ee52SAndreas Gohr $b = hexdec($m[3]); 2671072ee52SAndreas Gohr if(isset($m[4])) { 2681072ee52SAndreas Gohr $a = hexdec($m[4]); 2691072ee52SAndreas Gohr } else { 2701072ee52SAndreas Gohr $a = hexdec('ff'); 2711072ee52SAndreas Gohr } 2721072ee52SAndreas Gohr } else { 273e988c176SAndreas Gohr if(is_null($this->replacements)) $this->initReplacements(); 27494def893SAndreas Gohr if(isset($this->replacements[$color])) { 27594def893SAndreas Gohr return $this->replacements[$color]; 27694def893SAndreas Gohr } 2773a6c6601SAndreas Gohr if(isset($this->replacements['__' . $color . '__'])) { 2783a6c6601SAndreas Gohr return $this->replacements['__' . $color . '__']; 2793a6c6601SAndreas Gohr } 2801072ee52SAndreas Gohr return ''; 2811072ee52SAndreas Gohr } 2821072ee52SAndreas Gohr 2831072ee52SAndreas Gohr return "rgba($r,$g,$b,$a)"; 2841072ee52SAndreas Gohr } 2851072ee52SAndreas Gohr 2861072ee52SAndreas Gohr /** 28780d784e1SMichael Grosse * sets a rectangular background of the size of the svg/this itself 28880d784e1SMichael Grosse * 28980d784e1SMichael Grosse * @param SvgNode $g 29080d784e1SMichael Grosse * @return SvgNode 29180d784e1SMichael Grosse */ 29224ab1f72SAndreas Gohr protected function createBackground(SvgNode $g) { 29380d784e1SMichael Grosse $rect = $g->prependChild('rect'); 29480d784e1SMichael Grosse $rect->addAttribute('class', self::BACKGROUNDCLASS); 29580d784e1SMichael Grosse 29680d784e1SMichael Grosse $rect->addAttribute('x', '0'); 29780d784e1SMichael Grosse $rect->addAttribute('y', '0'); 29824ab1f72SAndreas Gohr $rect->addAttribute('height', '100%'); 29924ab1f72SAndreas Gohr $rect->addAttribute('width', '100%'); 30080d784e1SMichael Grosse return $rect; 30180d784e1SMichael Grosse } 30280d784e1SMichael Grosse 30380d784e1SMichael Grosse /** 3041072ee52SAndreas Gohr * Abort processing with given status code 3051072ee52SAndreas Gohr * 3061072ee52SAndreas Gohr * @param int $status 3071072ee52SAndreas Gohr */ 3081072ee52SAndreas Gohr protected function abort($status) { 3091072ee52SAndreas Gohr http_status($status); 3101072ee52SAndreas Gohr exit; 3111072ee52SAndreas Gohr } 3121072ee52SAndreas Gohr 31394def893SAndreas Gohr /** 31494def893SAndreas Gohr * Initialize the available replacement patterns 31594def893SAndreas Gohr * 31694def893SAndreas Gohr * Loads the style.ini from the template (and various local locations) 31794def893SAndreas Gohr * via a core function only available through some hack. 31894def893SAndreas Gohr */ 31994def893SAndreas Gohr protected function initReplacements() { 32094def893SAndreas Gohr global $conf; 3218c8001b6SMichael Große if (!class_exists('\dokuwiki\StyleUtils')) { 3228c8001b6SMichael Große // Pre-Greebo Compatibility 3238c8001b6SMichael Große 32494def893SAndreas Gohr define('SIMPLE_TEST', 1); // hacky shit 32594def893SAndreas Gohr include DOKU_INC . 'lib/exe/css.php'; 326e988c176SAndreas Gohr $ini = css_styleini($conf['template']); 32794def893SAndreas Gohr $this->replacements = $ini['replacements']; 3288c8001b6SMichael Große return; 3298c8001b6SMichael Große } 3308c8001b6SMichael Große 3318c8001b6SMichael Große $stuleUtils = new \dokuwiki\StyleUtils(); 3328c8001b6SMichael Große $ini = $stuleUtils->cssStyleini('sprintdoc'); 3338c8001b6SMichael Große $this->replacements = $ini['replacements']; 33494def893SAndreas Gohr } 3351072ee52SAndreas Gohr} 3361072ee52SAndreas Gohr 3371072ee52SAndreas Gohr// main 3381072ee52SAndreas Gohr$svg = new SVG(); 3391072ee52SAndreas Gohr$svg->out(); 3401072ee52SAndreas Gohr 3419fd3d99bSAndreas Gohr 342