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