346 lines
9.9 KiB
PHP
346 lines
9.9 KiB
PHP
<?php
|
|
|
|
namespace dokuwiki\template\sprintdoc;
|
|
|
|
if(!defined('DOKU_INC')) define('DOKU_INC', dirname(__FILE__) . '/../../../');
|
|
require_once(DOKU_INC . 'inc/init.php');
|
|
|
|
/**
|
|
* Custom XML node that allows prepending
|
|
*/
|
|
class SvgNode extends \SimpleXMLElement {
|
|
/**
|
|
* @param string $name Name of the new node
|
|
* @param null|string $value
|
|
* @return SvgNode
|
|
*/
|
|
public function prependChild($name, $value = null) {
|
|
$dom = dom_import_simplexml($this);
|
|
|
|
$new = $dom->insertBefore(
|
|
$dom->ownerDocument->createElement($name, $value),
|
|
$dom->firstChild
|
|
);
|
|
|
|
return simplexml_import_dom($new, get_class($this));
|
|
}
|
|
|
|
/**
|
|
* @param \SimpleXMLElement $node the node to be added
|
|
* @return \SimpleXMLElement
|
|
*/
|
|
public function appendNode(\SimpleXMLElement $node) {
|
|
$dom = dom_import_simplexml($this);
|
|
$domNode = dom_import_simplexml($node);
|
|
|
|
$newNode = $dom->appendChild($domNode);
|
|
return simplexml_import_dom($newNode, get_class($this));
|
|
}
|
|
|
|
/**
|
|
* @param \SimpleXMLElement $node the child to remove
|
|
* @return \SimpleXMLElement
|
|
*/
|
|
public function removeChild(\SimpleXMLElement $node) {
|
|
$dom = dom_import_simplexml($node);
|
|
$dom->parentNode->removeChild($dom);
|
|
return $node;
|
|
}
|
|
|
|
/**
|
|
* Wraps all elements of $this in a `<g>` tag
|
|
*
|
|
* @return SvgNode
|
|
*/
|
|
public function groupChildren() {
|
|
$dom = dom_import_simplexml($this);
|
|
|
|
$g = $dom->ownerDocument->createElement('g');
|
|
while($dom->childNodes->length > 0) {
|
|
$child = $dom->childNodes->item(0);
|
|
$dom->removeChild($child);
|
|
$g->appendChild($child);
|
|
}
|
|
$g = $dom->appendChild($g);
|
|
|
|
return simplexml_import_dom($g, get_class($this));
|
|
}
|
|
|
|
/**
|
|
* Add new style definitions to this element
|
|
* @param string $style
|
|
*/
|
|
public function addStyle($style) {
|
|
$defs = $this->defs;
|
|
if(!$defs) {
|
|
$defs = $this->prependChild('defs');
|
|
}
|
|
$defs->addChild('style', $style);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Manage SVG recoloring
|
|
*/
|
|
class SVG {
|
|
|
|
const IMGDIR = __DIR__ . '/img/';
|
|
const BACKGROUNDCLASS = 'sprintdoc-background';
|
|
const CDNBASE = 'https://raw.githubusercontent.com/Templarian/MaterialDesign/master/svg/';
|
|
|
|
protected $file;
|
|
protected $replacements;
|
|
|
|
/**
|
|
* SVG constructor
|
|
*/
|
|
public function __construct() {
|
|
global $INPUT;
|
|
|
|
$svg = cleanID($INPUT->str('svg'));
|
|
if(blank($svg)) $this->abort(404);
|
|
|
|
// try local file first
|
|
$file = self::IMGDIR . $svg;
|
|
if(!file_exists($file)) {
|
|
// try media file
|
|
$file = mediaFN($svg);
|
|
if(file_exists($file)) {
|
|
// media files are ACL protected
|
|
if(auth_quickaclcheck($svg) < AUTH_READ) $this->abort(403);
|
|
} else {
|
|
// get it from material design icons
|
|
$file = getCacheName($svg, '.svg');
|
|
if (!file_exists($file)) {
|
|
io_download(self::CDNBASE . $svg, $file);
|
|
}
|
|
}
|
|
|
|
}
|
|
// check if media exists
|
|
if(!file_exists($file)) $this->abort(404);
|
|
|
|
$this->file = $file;
|
|
}
|
|
|
|
/**
|
|
* Generate and output
|
|
*/
|
|
public function out() {
|
|
global $conf;
|
|
$file = $this->file;
|
|
$params = $this->getParameters();
|
|
|
|
header('Content-Type: image/svg+xml');
|
|
$cachekey = md5($file . serialize($params) . $conf['template'] . filemtime(__FILE__));
|
|
$cache = new \dokuwiki\Cache\Cache($cachekey, '.svg');
|
|
$cache->setEvent('SVG_CACHE');
|
|
|
|
http_cached($cache->cache, $cache->useCache(array('files' => array($file, __FILE__))));
|
|
if($params['e']) {
|
|
$content = $this->embedSVG($file);
|
|
} else {
|
|
$content = $this->generateSVG($file, $params);
|
|
}
|
|
http_cached_finish($cache->cache, $content);
|
|
}
|
|
|
|
/**
|
|
* Generate a new SVG based on the input file and the parameters
|
|
*
|
|
* @param string $file the SVG file to load
|
|
* @param array $params the parameters as returned by getParameters()
|
|
* @return string the new XML contents
|
|
*/
|
|
protected function generateSVG($file, $params) {
|
|
/** @var SvgNode $xml */
|
|
$xml = simplexml_load_file($file, SvgNode::class);
|
|
$xml->addStyle($this->makeStyle($params));
|
|
$this->createBackground($xml);
|
|
$xml->groupChildren();
|
|
|
|
return $xml->asXML();
|
|
}
|
|
|
|
/**
|
|
* Return the absolute minimum path definition for direct embedding
|
|
*
|
|
* No styles will be applied. They have to be done in CSS
|
|
*
|
|
* @param string $file the SVG file to load
|
|
* @return string the new XML contents
|
|
*/
|
|
protected function embedSVG($file) {
|
|
/** @var SvgNode $xml */
|
|
$xml = simplexml_load_file($file, SvgNode::class);
|
|
|
|
$def = hsc((string) $xml->path['d']);
|
|
$w = hsc($xml['width'] ?? '100%');
|
|
$h = hsc($xml['height'] ?? '100%');
|
|
$v = hsc($xml['viewBox']);
|
|
|
|
// if viewbox is not defined, construct it from width and height, if available
|
|
if (empty($v) && !empty($w) && !empty($h)) {
|
|
$v = hsc("0 0 $w $h");
|
|
}
|
|
|
|
return "<svg viewBox=\"$v\"><path d=\"$def\" /></svg>";
|
|
}
|
|
|
|
/**
|
|
* Get the supported parameters from request
|
|
*
|
|
* @return array
|
|
*/
|
|
protected function getParameters() {
|
|
global $INPUT;
|
|
|
|
$params = array(
|
|
'e' => $INPUT->bool('e', false),
|
|
's' => $this->fixColor($INPUT->str('s')),
|
|
'f' => $this->fixColor($INPUT->str('f')),
|
|
'b' => $this->fixColor($INPUT->str('b')),
|
|
'sh' => $this->fixColor($INPUT->str('sh')),
|
|
'fh' => $this->fixColor($INPUT->str('fh')),
|
|
'bh' => $this->fixColor($INPUT->str('bh')),
|
|
);
|
|
|
|
return $params;
|
|
}
|
|
|
|
/**
|
|
* Generate a style setting from the input variables
|
|
*
|
|
* @param array $params associative array with the given parameters
|
|
* @return string
|
|
*/
|
|
protected function makeStyle($params) {
|
|
$element = 'path'; // FIXME configurable?
|
|
|
|
if(empty($params['b'])) {
|
|
$params['b'] = $this->fixColor('00000000');
|
|
}
|
|
|
|
$style = 'g rect.' . self::BACKGROUNDCLASS . '{fill:' . $params['b'] . ';}';
|
|
|
|
if($params['bh']) {
|
|
$style .= 'g:hover rect.' . self::BACKGROUNDCLASS . '{fill:' . $params['bh'] . ';}';
|
|
}
|
|
|
|
if($params['s'] || $params['f']) {
|
|
$style .= 'g ' . $element . '{';
|
|
if($params['s']) $style .= 'stroke:' . $params['s'] . ';';
|
|
if($params['f']) $style .= 'fill:' . $params['f'] . ';';
|
|
$style .= '}';
|
|
}
|
|
|
|
if($params['sh'] || $params['fh']) {
|
|
$style .= 'g:hover ' . $element . '{';
|
|
if($params['sh']) $style .= 'stroke:' . $params['sh'] . ';';
|
|
if($params['fh']) $style .= 'fill:' . $params['fh'] . ';';
|
|
$style .= '}';
|
|
}
|
|
|
|
return $style;
|
|
}
|
|
|
|
/**
|
|
* Takes a hexadecimal color string in the following forms:
|
|
*
|
|
* RGB
|
|
* RRGGBB
|
|
* RRGGBBAA
|
|
*
|
|
* Converts it to rgba() form.
|
|
*
|
|
* Alternatively takes a replacement name from the current template's style.ini
|
|
*
|
|
* @param string $color
|
|
* @return string
|
|
*/
|
|
protected function fixColor($color) {
|
|
if($color === '') return '';
|
|
if(preg_match('/^([0-9a-f])([0-9a-f])([0-9a-f])$/i', $color, $m)) {
|
|
$r = hexdec($m[1] . $m[1]);
|
|
$g = hexdec($m[2] . $m[2]);
|
|
$b = hexdec($m[3] . $m[3]);
|
|
$a = hexdec('ff');
|
|
} elseif(preg_match('/^([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})?$/i', $color, $m)) {
|
|
$r = hexdec($m[1]);
|
|
$g = hexdec($m[2]);
|
|
$b = hexdec($m[3]);
|
|
if(isset($m[4])) {
|
|
$a = hexdec($m[4]);
|
|
} else {
|
|
$a = hexdec('ff');
|
|
}
|
|
} else {
|
|
if(is_null($this->replacements)) $this->initReplacements();
|
|
if(isset($this->replacements[$color])) {
|
|
return $this->replacements[$color];
|
|
}
|
|
if(isset($this->replacements['__' . $color . '__'])) {
|
|
return $this->replacements['__' . $color . '__'];
|
|
}
|
|
return '';
|
|
}
|
|
|
|
return "rgba($r,$g,$b,$a)";
|
|
}
|
|
|
|
/**
|
|
* sets a rectangular background of the size of the svg/this itself
|
|
*
|
|
* @param SvgNode $g
|
|
* @return SvgNode
|
|
*/
|
|
protected function createBackground(SvgNode $g) {
|
|
$rect = $g->prependChild('rect');
|
|
$rect->addAttribute('class', self::BACKGROUNDCLASS);
|
|
|
|
$rect->addAttribute('x', '0');
|
|
$rect->addAttribute('y', '0');
|
|
$rect->addAttribute('height', '100%');
|
|
$rect->addAttribute('width', '100%');
|
|
return $rect;
|
|
}
|
|
|
|
/**
|
|
* Abort processing with given status code
|
|
*
|
|
* @param int $status
|
|
*/
|
|
protected function abort($status) {
|
|
http_status($status);
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Initialize the available replacement patterns
|
|
*
|
|
* Loads the style.ini from the template (and various local locations)
|
|
* via a core function only available through some hack.
|
|
*/
|
|
protected function initReplacements() {
|
|
global $conf;
|
|
if (!class_exists('\dokuwiki\StyleUtils')) {
|
|
// Pre-Greebo Compatibility
|
|
|
|
define('SIMPLE_TEST', 1); // hacky shit
|
|
include DOKU_INC . 'lib/exe/css.php';
|
|
$ini = css_styleini($conf['template']);
|
|
$this->replacements = $ini['replacements'];
|
|
return;
|
|
}
|
|
|
|
$stuleUtils = new \dokuwiki\StyleUtils();
|
|
$ini = $stuleUtils->cssStyleini('sprintdoc');
|
|
$this->replacements = $ini['replacements'];
|
|
}
|
|
}
|
|
|
|
// main
|
|
$svg = new SVG();
|
|
$svg->out();
|
|
|
|
|