1<?php 2 3namespace dokuwiki\template\sprintdoc; 4 5if(!defined('DOKU_INC')) define('DOKU_INC', dirname(__FILE__) . '/../../../'); 6require_once(DOKU_INC . 'inc/init.php'); 7 8/** 9 * Custom XML node that allows prepending 10 */ 11class SvgNode extends \SimpleXMLElement { 12 /** 13 * @param string $name Name of the new node 14 * @param null|string $value 15 * @return SvgNode 16 */ 17 public function prependChild($name, $value = null) { 18 $dom = dom_import_simplexml($this); 19 20 $new = $dom->insertBefore( 21 $dom->ownerDocument->createElement($name, $value), 22 $dom->firstChild 23 ); 24 25 return simplexml_import_dom($new, get_class($this)); 26 } 27 28 /** 29 * @param \SimpleXMLElement $node the node to be added 30 * @return \SimpleXMLElement 31 */ 32 public function appendNode(\SimpleXMLElement $node) { 33 $dom = dom_import_simplexml($this); 34 $domNode = dom_import_simplexml($node); 35 36 $newNode = $dom->appendChild($domNode); 37 return simplexml_import_dom($newNode, get_class($this)); 38 } 39 40 /** 41 * @param \SimpleXMLElement $node the child to remove 42 * @return \SimpleXMLElement 43 */ 44 public function removeChild(\SimpleXMLElement $node) { 45 $dom = dom_import_simplexml($node); 46 $dom->parentNode->removeChild($dom); 47 return $node; 48 } 49 50 /** 51 * Wraps all elements of $this in a `<g>` tag 52 * 53 * @return SvgNode 54 */ 55 public function groupChildren() { 56 $dom = dom_import_simplexml($this); 57 58 $g = $dom->ownerDocument->createElement('g'); 59 while ($dom->childNodes->length > 0) { 60 $child = $dom->childNodes->item(0); 61 $dom->removeChild($child); 62 $g->appendChild($child); 63 } 64 $g = $dom->appendChild($g); 65 66 return simplexml_import_dom($g, get_class($this)); 67 } 68 69 /** 70 * Add new style definitions to this element 71 * @param string $style 72 */ 73 public function addStyle($style) { 74 $defs = $this->defs; 75 if(!$defs) { 76 $defs = $this->prependChild('defs'); 77 } 78 $defs->addChild('style', $style); 79 } 80} 81 82/** 83 * Manage SVG recoloring 84 */ 85class SVG { 86 87 const IMGDIR = __DIR__ . '/img/'; 88 const BACKGROUNDCLASS = 'sprintdoc-background'; 89 90 protected $file; 91 92 /** 93 * SVG constructor 94 */ 95 public function __construct() { 96 global $INPUT; 97 98 $svg = cleanID($INPUT->str('svg')); 99 if(blank($svg)) $this->abort(404); 100 101 // try local file first 102 $file = self::IMGDIR . $svg; 103 if(!file_exists($file)) { 104 // media files are ACL protected 105 if(auth_quickaclcheck($svg) < AUTH_READ) $this->abort(403); 106 $file = mediaFN($svg); 107 } 108 // check if media exists 109 if(!file_exists($file)) $this->abort(404); 110 111 $this->file = $file; 112 } 113 114 /** 115 * Generate and output 116 */ 117 public function out() { 118 $file = $this->file; 119 $params = $this->getParameters(); 120 121 header('Content-Type: image/svg+xml'); 122 $cachekey = md5($file . serialize($params)); 123 $cache = new \cache($cachekey, '.svg'); 124 $cache->_event = 'SVG_CACHE'; 125 126 http_cached($cache->cache, $cache->useCache(array('files' => array($file, __FILE__)))); 127 http_cached_finish($cache->cache, $this->generateSVG($file, $params)); 128 } 129 130 /** 131 * Generate a new SVG based on the input file and the parameters 132 * 133 * @param string $file the SVG file to load 134 * @param array $params the parameters as returned by getParameters() 135 * @return string the new XML contents 136 */ 137 protected function generateSVG($file, $params) { 138 /** @var SvgNode $xml */ 139 $xml = simplexml_load_file($file, SvgNode::class); 140 $xml->addStyle($this->makeStyle($params)); 141 $this->createBackground($xml); 142 $xml->groupChildren(); 143 144 return $xml->asXML(); 145 } 146 147 /** 148 * Get the supported parameters from request 149 * 150 * @return array 151 */ 152 protected function getParameters() { 153 global $INPUT; 154 155 $params = array( 156 's' => $this->fixColor($INPUT->str('s')), 157 'f' => $this->fixColor($INPUT->str('f')), 158 'b' => $this->fixColor($INPUT->str('b')), 159 'sh' => $this->fixColor($INPUT->str('sh')), 160 'fh' => $this->fixColor($INPUT->str('fh')), 161 'bh' => $this->fixColor($INPUT->str('bh')), 162 ); 163 164 return $params; 165 } 166 167 /** 168 * Generate a style setting from the input variables 169 * 170 * @param array $params associative array with the given parameters 171 * @return string 172 */ 173 protected function makeStyle($params) { 174 $element = 'path'; // FIXME configurable? 175 176 if(empty($params['b'])) { 177 $params['b'] = $this->fixColor('00000000'); 178 } 179 180 $style = 'g rect.' . self::BACKGROUNDCLASS . '{fill:' . $params['b'] . ';}'; 181 182 if($params['bh']) { 183 $style .= 'g:hover rect.' . self::BACKGROUNDCLASS . '{fill:' . $params['bh'] . ';}'; 184 } 185 186 if($params['s'] || $params['f']) { 187 $style .= 'g ' . $element . '{'; 188 if($params['s']) $style .= 'stroke:' . $params['s'] . ';'; 189 if($params['f']) $style .= 'fill:' . $params['f'] . ';'; 190 $style .= '}'; 191 } 192 193 if($params['sh'] || $params['fh']) { 194 $style .= 'g:hover ' . $element . '{'; 195 if($params['sh']) $style .= 'stroke:' . $params['sh'] . ';'; 196 if($params['fh']) $style .= 'fill:' . $params['fh'] . ';'; 197 $style .= '}'; 198 } 199 200 return $style; 201 } 202 203 /** 204 * Takes a hexadecimal color string in the following forms: 205 * 206 * RGB 207 * RRGGBB 208 * RRGGBBAA 209 * 210 * Converts it to rgba() form 211 * 212 * @param string $color 213 * @return string 214 */ 215 protected function fixColor($color) { 216 if(preg_match('/^([0-9a-f])([0-9a-f])([0-9a-f])$/i', $color, $m)) { 217 $r = hexdec($m[1] . $m[1]); 218 $g = hexdec($m[2] . $m[2]); 219 $b = hexdec($m[3] . $m[3]); 220 $a = hexdec('ff'); 221 } elseif(preg_match('/^([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})?$/i', $color, $m)) { 222 $r = hexdec($m[1]); 223 $g = hexdec($m[2]); 224 $b = hexdec($m[3]); 225 if(isset($m[4])) { 226 $a = hexdec($m[4]); 227 } else { 228 $a = hexdec('ff'); 229 } 230 } else { 231 return ''; 232 } 233 234 return "rgba($r,$g,$b,$a)"; 235 } 236 237 /** 238 * sets a rectangular background of the size of the svg/this itself 239 * 240 * @param SvgNode $g 241 * @return SvgNode 242 */ 243 protected function createBackground(SvgNode $g) { 244 $rect = $g->prependChild('rect'); 245 $rect->addAttribute('class', self::BACKGROUNDCLASS); 246 247 $rect->addAttribute('x', '0'); 248 $rect->addAttribute('y', '0'); 249 $rect->addAttribute('height', '100%'); 250 $rect->addAttribute('width', '100%'); 251 return $rect; 252 } 253 254 /** 255 * Abort processing with given status code 256 * 257 * @param int $status 258 */ 259 protected function abort($status) { 260 http_status($status); 261 exit; 262 } 263 264} 265 266// main 267$svg = new SVG(); 268$svg->out(); 269 270