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'; 89*c24a2e1eSAndreas Gohr const CDNBASE = 'https://cdn.rawgit.com/Templarian/MaterialDesign/master/icons/svg/'; 901072ee52SAndreas Gohr 9124ab1f72SAndreas Gohr protected $file; 921072ee52SAndreas Gohr 931072ee52SAndreas Gohr /** 941072ee52SAndreas Gohr * SVG constructor 951072ee52SAndreas Gohr */ 961072ee52SAndreas Gohr public function __construct() { 971072ee52SAndreas Gohr global $INPUT; 981072ee52SAndreas Gohr 991072ee52SAndreas Gohr $svg = cleanID($INPUT->str('svg')); 1001072ee52SAndreas Gohr if(blank($svg)) $this->abort(404); 1011072ee52SAndreas Gohr 1021072ee52SAndreas Gohr // try local file first 1031072ee52SAndreas Gohr $file = self::IMGDIR . $svg; 1041072ee52SAndreas Gohr if(!file_exists($file)) { 105*c24a2e1eSAndreas Gohr // try media file 106*c24a2e1eSAndreas Gohr $file = mediaFN($svg); 107*c24a2e1eSAndreas Gohr if(file_exists($file)) { 1081072ee52SAndreas Gohr // media files are ACL protected 1093ec07d58SAndreas Gohr if(auth_quickaclcheck($svg) < AUTH_READ) $this->abort(403); 110*c24a2e1eSAndreas Gohr } else { 111*c24a2e1eSAndreas Gohr // get it from material design icons 112*c24a2e1eSAndreas Gohr $file = getCacheName($svg, '.svg'); 113*c24a2e1eSAndreas Gohr io_download(self::CDNBASE . $svg, $file); 114*c24a2e1eSAndreas Gohr } 115*c24a2e1eSAndreas Gohr 1161072ee52SAndreas Gohr } 1171072ee52SAndreas Gohr // check if media exists 1181072ee52SAndreas Gohr if(!file_exists($file)) $this->abort(404); 1191072ee52SAndreas Gohr 12024ab1f72SAndreas Gohr $this->file = $file; 1211072ee52SAndreas Gohr } 1221072ee52SAndreas Gohr 1231072ee52SAndreas Gohr /** 1241072ee52SAndreas Gohr * Generate and output 1251072ee52SAndreas Gohr */ 1261072ee52SAndreas Gohr public function out() { 12724ab1f72SAndreas Gohr $file = $this->file; 12824ab1f72SAndreas Gohr $params = $this->getParameters(); 12924ab1f72SAndreas Gohr 1304fd6492bSAndreas Gohr header('Content-Type: image/svg+xml'); 13124ab1f72SAndreas Gohr $cachekey = md5($file . serialize($params)); 13224ab1f72SAndreas Gohr $cache = new \cache($cachekey, '.svg'); 13324ab1f72SAndreas Gohr $cache->_event = 'SVG_CACHE'; 13424ab1f72SAndreas Gohr 13524ab1f72SAndreas Gohr http_cached($cache->cache, $cache->useCache(array('files' => array($file, __FILE__)))); 13624ab1f72SAndreas Gohr http_cached_finish($cache->cache, $this->generateSVG($file, $params)); 1371072ee52SAndreas Gohr } 1381072ee52SAndreas Gohr 1391072ee52SAndreas Gohr /** 14024ab1f72SAndreas Gohr * Generate a new SVG based on the input file and the parameters 1411072ee52SAndreas Gohr * 14224ab1f72SAndreas Gohr * @param string $file the SVG file to load 14324ab1f72SAndreas Gohr * @param array $params the parameters as returned by getParameters() 14424ab1f72SAndreas Gohr * @return string the new XML contents 1451072ee52SAndreas Gohr */ 14624ab1f72SAndreas Gohr protected function generateSVG($file, $params) { 14724ab1f72SAndreas Gohr /** @var SvgNode $xml */ 14824ab1f72SAndreas Gohr $xml = simplexml_load_file($file, SvgNode::class); 14924ab1f72SAndreas Gohr $xml->addStyle($this->makeStyle($params)); 15024ab1f72SAndreas Gohr $this->createBackground($xml); 15124ab1f72SAndreas Gohr $xml->groupChildren(); 15224ab1f72SAndreas Gohr 15324ab1f72SAndreas Gohr return $xml->asXML(); 15424ab1f72SAndreas Gohr } 15524ab1f72SAndreas Gohr 15624ab1f72SAndreas Gohr /** 15724ab1f72SAndreas Gohr * Get the supported parameters from request 15824ab1f72SAndreas Gohr * 15924ab1f72SAndreas Gohr * @return array 16024ab1f72SAndreas Gohr */ 16124ab1f72SAndreas Gohr protected function getParameters() { 1621072ee52SAndreas Gohr global $INPUT; 1631072ee52SAndreas Gohr 16424ab1f72SAndreas Gohr $params = array( 1651072ee52SAndreas Gohr 's' => $this->fixColor($INPUT->str('s')), 1661072ee52SAndreas Gohr 'f' => $this->fixColor($INPUT->str('f')), 16780d784e1SMichael Grosse 'b' => $this->fixColor($INPUT->str('b')), 1681072ee52SAndreas Gohr 'sh' => $this->fixColor($INPUT->str('sh')), 1691072ee52SAndreas Gohr 'fh' => $this->fixColor($INPUT->str('fh')), 17080d784e1SMichael Grosse 'bh' => $this->fixColor($INPUT->str('bh')), 1711072ee52SAndreas Gohr ); 1721072ee52SAndreas Gohr 17324ab1f72SAndreas Gohr return $params; 17480d784e1SMichael Grosse } 17580d784e1SMichael Grosse 17624ab1f72SAndreas Gohr /** 17724ab1f72SAndreas Gohr * Generate a style setting from the input variables 17824ab1f72SAndreas Gohr * 17924ab1f72SAndreas Gohr * @param array $params associative array with the given parameters 18024ab1f72SAndreas Gohr * @return string 18124ab1f72SAndreas Gohr */ 18224ab1f72SAndreas Gohr protected function makeStyle($params) { 18324ab1f72SAndreas Gohr $element = 'path'; // FIXME configurable? 18480d784e1SMichael Grosse 18524ab1f72SAndreas Gohr if(empty($params['b'])) { 18624ab1f72SAndreas Gohr $params['b'] = $this->fixColor('00000000'); 18780d784e1SMichael Grosse } 18880d784e1SMichael Grosse 18924ab1f72SAndreas Gohr $style = 'g rect.' . self::BACKGROUNDCLASS . '{fill:' . $params['b'] . ';}'; 19024ab1f72SAndreas Gohr 19124ab1f72SAndreas Gohr if($params['bh']) { 19224ab1f72SAndreas Gohr $style .= 'g:hover rect.' . self::BACKGROUNDCLASS . '{fill:' . $params['bh'] . ';}'; 19324ab1f72SAndreas Gohr } 19424ab1f72SAndreas Gohr 19524ab1f72SAndreas Gohr if($params['s'] || $params['f']) { 19680d784e1SMichael Grosse $style .= 'g ' . $element . '{'; 19724ab1f72SAndreas Gohr if($params['s']) $style .= 'stroke:' . $params['s'] . ';'; 19824ab1f72SAndreas Gohr if($params['f']) $style .= 'fill:' . $params['f'] . ';'; 1991072ee52SAndreas Gohr $style .= '}'; 2001072ee52SAndreas Gohr } 2011072ee52SAndreas Gohr 20224ab1f72SAndreas Gohr if($params['sh'] || $params['fh']) { 20380d784e1SMichael Grosse $style .= 'g:hover ' . $element . '{'; 20424ab1f72SAndreas Gohr if($params['sh']) $style .= 'stroke:' . $params['sh'] . ';'; 20524ab1f72SAndreas Gohr if($params['fh']) $style .= 'fill:' . $params['fh'] . ';'; 2061072ee52SAndreas Gohr $style .= '}'; 2071072ee52SAndreas Gohr } 2081072ee52SAndreas Gohr 2091072ee52SAndreas Gohr return $style; 2101072ee52SAndreas Gohr } 2111072ee52SAndreas Gohr 2121072ee52SAndreas Gohr /** 2131072ee52SAndreas Gohr * Takes a hexadecimal color string in the following forms: 2141072ee52SAndreas Gohr * 2151072ee52SAndreas Gohr * RGB 2161072ee52SAndreas Gohr * RRGGBB 2171072ee52SAndreas Gohr * RRGGBBAA 2181072ee52SAndreas Gohr * 2191072ee52SAndreas Gohr * Converts it to rgba() form 2201072ee52SAndreas Gohr * 2211072ee52SAndreas Gohr * @param string $color 2221072ee52SAndreas Gohr * @return string 2231072ee52SAndreas Gohr */ 2241072ee52SAndreas Gohr protected function fixColor($color) { 2251072ee52SAndreas Gohr if(preg_match('/^([0-9a-f])([0-9a-f])([0-9a-f])$/i', $color, $m)) { 2261072ee52SAndreas Gohr $r = hexdec($m[1] . $m[1]); 2271072ee52SAndreas Gohr $g = hexdec($m[2] . $m[2]); 2281072ee52SAndreas Gohr $b = hexdec($m[3] . $m[3]); 2291072ee52SAndreas Gohr $a = hexdec('ff'); 2301072ee52SAndreas Gohr } elseif(preg_match('/^([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})?$/i', $color, $m)) { 2311072ee52SAndreas Gohr $r = hexdec($m[1]); 2321072ee52SAndreas Gohr $g = hexdec($m[2]); 2331072ee52SAndreas Gohr $b = hexdec($m[3]); 2341072ee52SAndreas Gohr if(isset($m[4])) { 2351072ee52SAndreas Gohr $a = hexdec($m[4]); 2361072ee52SAndreas Gohr } else { 2371072ee52SAndreas Gohr $a = hexdec('ff'); 2381072ee52SAndreas Gohr } 2391072ee52SAndreas Gohr } else { 2401072ee52SAndreas Gohr return ''; 2411072ee52SAndreas Gohr } 2421072ee52SAndreas Gohr 2431072ee52SAndreas Gohr return "rgba($r,$g,$b,$a)"; 2441072ee52SAndreas Gohr } 2451072ee52SAndreas Gohr 2461072ee52SAndreas Gohr /** 24780d784e1SMichael Grosse * sets a rectangular background of the size of the svg/this itself 24880d784e1SMichael Grosse * 24980d784e1SMichael Grosse * @param SvgNode $g 25080d784e1SMichael Grosse * @return SvgNode 25180d784e1SMichael Grosse */ 25224ab1f72SAndreas Gohr protected function createBackground(SvgNode $g) { 25380d784e1SMichael Grosse $rect = $g->prependChild('rect'); 25480d784e1SMichael Grosse $rect->addAttribute('class', self::BACKGROUNDCLASS); 25580d784e1SMichael Grosse 25680d784e1SMichael Grosse $rect->addAttribute('x', '0'); 25780d784e1SMichael Grosse $rect->addAttribute('y', '0'); 25824ab1f72SAndreas Gohr $rect->addAttribute('height', '100%'); 25924ab1f72SAndreas Gohr $rect->addAttribute('width', '100%'); 26080d784e1SMichael Grosse return $rect; 26180d784e1SMichael Grosse } 26280d784e1SMichael Grosse 26380d784e1SMichael Grosse /** 2641072ee52SAndreas Gohr * Abort processing with given status code 2651072ee52SAndreas Gohr * 2661072ee52SAndreas Gohr * @param int $status 2671072ee52SAndreas Gohr */ 2681072ee52SAndreas Gohr protected function abort($status) { 2691072ee52SAndreas Gohr http_status($status); 2701072ee52SAndreas Gohr exit; 2711072ee52SAndreas Gohr } 2721072ee52SAndreas Gohr 2731072ee52SAndreas Gohr} 2741072ee52SAndreas Gohr 2751072ee52SAndreas Gohr// main 2761072ee52SAndreas Gohr$svg = new SVG(); 2771072ee52SAndreas Gohr$svg->out(); 2781072ee52SAndreas Gohr 279