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/** 52 * Manage SVG recoloring 53 */ 54class SVG { 55 56 const IMGDIR = __DIR__ . '/img/'; 57 const BACKGROUNDCLASS = 'sprintdoc-background'; 58 59 /** @var SvgNode */ 60 protected $xml; 61 62 /** 63 * SVG constructor 64 */ 65 public function __construct() { 66 global $INPUT; 67 68 $svg = cleanID($INPUT->str('svg')); 69 if(blank($svg)) $this->abort(404); 70 71 // try local file first 72 $file = self::IMGDIR . $svg; 73 if(!file_exists($file)) { 74 // media files are ACL protected 75 if(auth_quickaclcheck($svg) < AUTH_READ) $this->abort(403); 76 $file = mediaFN($svg); 77 } 78 // check if media exists 79 if(!file_exists($file)) $this->abort(404); 80 81 $this->xml = simplexml_load_file($file, SvgNode::class); 82 } 83 84 /** 85 * Generate and output 86 */ 87 public function out() { 88 $g = $this->wrapChildren(); 89 $this->setBackground($g); 90 $this->setStyle(); 91 header('Content-Type: image/svg+xml'); 92 echo $this->xml->asXML(); 93 } 94 95 /** 96 * Generate a style setting from the input variables 97 * 98 * @return string 99 */ 100 protected function makeStyle() { 101 global $INPUT; 102 103 $element = 'path'; // FIXME configurable? 104 105 $colors = array( 106 's' => $this->fixColor($INPUT->str('s')), 107 'f' => $this->fixColor($INPUT->str('f')), 108 'b' => $this->fixColor($INPUT->str('b')), 109 'sh' => $this->fixColor($INPUT->str('sh')), 110 'fh' => $this->fixColor($INPUT->str('fh')), 111 'bh' => $this->fixColor($INPUT->str('bh')), 112 ); 113 114 if (empty($colors['b'])) { 115 $colors['b'] = $this->fixColor('00000000'); 116 } 117 118 $style = 'g rect.' . self::BACKGROUNDCLASS . '{fill:' . $colors['b'] . ';}'; 119 120 if($colors['bh']) { 121 $style .= 'g:hover rect.' . self::BACKGROUNDCLASS . '{fill:' . $colors['bh'] . ';}'; 122 } 123 124 if($colors['s'] || $colors['f']) { 125 $style .= 'g ' . $element . '{'; 126 if($colors['s']) $style .= 'stroke:' . $colors['s'] . ';'; 127 if($colors['f']) $style .= 'fill:' . $colors['f'] . ';'; 128 $style .= '}'; 129 } 130 131 if($colors['sh'] || $colors['fh']) { 132 $style .= 'g:hover ' . $element . '{'; 133 if($colors['sh']) $style .= 'stroke:' . $colors['sh'] . ';'; 134 if($colors['fh']) $style .= 'fill:' . $colors['fh'] . ';'; 135 $style .= '}'; 136 } 137 138 return $style; 139 } 140 141 /** 142 * Takes a hexadecimal color string in the following forms: 143 * 144 * RGB 145 * RRGGBB 146 * RRGGBBAA 147 * 148 * Converts it to rgba() form 149 * 150 * @param string $color 151 * @return string 152 */ 153 protected function fixColor($color) { 154 if(preg_match('/^([0-9a-f])([0-9a-f])([0-9a-f])$/i', $color, $m)) { 155 $r = hexdec($m[1] . $m[1]); 156 $g = hexdec($m[2] . $m[2]); 157 $b = hexdec($m[3] . $m[3]); 158 $a = hexdec('ff'); 159 } elseif(preg_match('/^([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})?$/i', $color, $m)) { 160 $r = hexdec($m[1]); 161 $g = hexdec($m[2]); 162 $b = hexdec($m[3]); 163 if(isset($m[4])) { 164 $a = hexdec($m[4]); 165 } else { 166 $a = hexdec('ff'); 167 } 168 } else { 169 return ''; 170 } 171 172 return "rgba($r,$g,$b,$a)"; 173 } 174 175 /** 176 * sets a rectangular background of the size of the svg/this itself 177 * 178 * @param SvgNode $g 179 * @return SvgNode 180 */ 181 protected function setBackground(SvgNode $g) { 182 $attributes = $this->xml->attributes(); 183 $rect = $g->prependChild('rect'); 184 $rect->addAttribute('class', self::BACKGROUNDCLASS); 185 186 $rect->addAttribute('x', '0'); 187 $rect->addAttribute('y', '0'); 188 $rect->addAttribute('height', $attributes['height']); 189 $rect->addAttribute('width', $attributes['width']); 190 return $rect; 191 } 192 193 /** 194 * Wraps all elements of $this in a `<g>` tag 195 * 196 * @return SvgNode 197 */ 198 protected function wrapChildren() { 199 $svgChildren = array(); 200 foreach ($this->xml->children() as $child) { 201 $svgChildren[] = $this->xml->removeChild($child); 202 } 203 $g = $this->xml->prependChild('g'); 204 foreach ($svgChildren as $child) { 205 $g->appendNode($child); 206 } 207 return $g; 208 } 209 210 /** 211 * Apply the style to the SVG 212 */ 213 protected function setStyle() { 214 $defs = $this->xml->defs; 215 if(!$defs) { 216 $defs = $this->xml->prependChild('defs'); 217 } 218 $defs->addChild('style', $this->makeStyle()); 219 } 220 221 /** 222 * Abort processing with given status code 223 * 224 * @param int $status 225 */ 226 protected function abort($status) { 227 http_status($status); 228 exit; 229 } 230 231} 232 233// main 234$svg = new SVG(); 235$svg->out(); 236 237