1<?php
2/*
3 * Patch Panel Plugin: display a patch panel from a plaintext source
4 *
5 * Each patch panel is enclosed in <patchpanel>...</patchpanel> tags. The tag can have the
6 * following parameters (all optional):
7 *   name=<name>        The name of the patch panel (default: 'Patch Panel')
8 *   ports=<number>     The total number of ports.  (default: 48)
9 *   rows=<number>      Number of rows.  (default: 2)
10 *   groups=<number>    Number of ports in a group (default: 6)
11 *   rotate=[0,1]       If true, rotate the patch panel 90deg clockwise.
12 *   switch=[0,1,2]     If 1, port numbering changes to match switches.
13 *                      If 2, same as above, but starting from bottom to top. (e.g. 3Com/HP)
14 * Between these tags is a series of lines, each describing a port:
15 *
16 *		<port> <label> [#color] [comment]
17 *
18 * The fields:
19 *  - <port>: The port number on the patch panel, starting from the top left.
20 *  - <label>: The label for the port.  Must be quoted if it contains spaces.
21 *  - [#color]: Optional.  Specify an #RRGGBB HTML color code.
22 *  - [comment]: Optional. All remaining text is treated as a comment.
23 *
24 * You can also include comment lines starting with a pound sign #.
25 *
26 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
27 * @author     Grant Emsley <grant@emsley.ca>
28 * @version    2014.05.11.1
29 *
30 * Based on the rack plugin (https://www.dokuwiki.org/plugin:rack) by Tyler Bletsch <tyler.bletsch@netapp.com>
31 *
32 */
33
34if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../').'/');
35if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
36require_once(DOKU_PLUGIN.'syntax.php');
37
38/*
39 * All DokuWiki plugins to extend the parser/rendering mechanism
40 * need to inherit from this class
41 */
42class syntax_plugin_patchpanel extends DokuWiki_Syntax_Plugin {
43	function getType(){
44		return 'substition';
45	}
46	function getSort(){
47		return 155;
48	}
49	function getPType(){
50		return 'block';
51	}
52	function connectTo($mode) {
53		$this->Lexer->addSpecialPattern("<patchpanel[^>]*>.*?(?:<\/patchpanel>)",$mode,'plugin_patchpanel');
54	}
55
56
57
58	/*
59	 * Handle the matches
60	 */
61	function handle($match, $state, $pos, &$handler){
62
63		// remove "</patchpanel>" from the match
64		$match = substr($match,0,-13);
65
66		//default options
67		$opt = array(
68			'name' => 'Patch Panel',
69			'ports' => 48,
70			'rows' => '2',
71			'groups' => '6',
72			'rotate' => 0,
73			'switch' => 0
74		);
75
76		list($optstr,$opt['content']) = explode('>',$match,2);
77		unset($match);
78		// parse options
79		// http://stackoverflow.com/questions/2202435/php-explode-the-string-but-treat-words-in-quotes-as-a-single-word
80		preg_match_all('/\w*?="(?:\\.|[^\\"])*"|\S+/', $optstr, $matches);
81
82		$optsin = $matches[0];
83		foreach($optsin as $o){
84			$o = trim($o);
85			if (preg_match("/^name=(.+)/",$o,$matches)) {
86				// Remove beginning and ending quotes, then html encode
87				$opt['name'] = htmlspecialchars(trim($matches[1], '"\''), ENT_QUOTES);
88			} elseif (preg_match("/^ports=(\d+)/",$o,$matches)) {
89				$opt['ports'] = $matches[1];
90			} elseif (preg_match("/^rows=(\d+)/",$o,$matches)) {
91				$opt['rows'] = $matches[1];
92			} elseif (preg_match("/^groups=(\d+)/",$o,$matches)) {
93				$opt['groups'] = $matches[1];
94			} elseif (preg_match("/^rotate=(\d+)/",$o, $matches)) {
95				$opt['rotate'] = $matches[1];
96			} elseif (preg_match("/^switch=(\d+)/",$o, $matches)) {
97				$opt['switch'] = $matches[1];
98			}
99		}
100		return $opt;
101	}
102
103
104	// This function creates an SVG image of an ethernet port and positions it on the patch panel.
105	function ethernet_svg($row, $position, $port, $item, $opt, $imagewidth, $imageheight) {
106		// Make row and position start at 0.
107		$row--;
108		$position--;
109
110		// Calculate things we need to create the image
111		// If there is no data for the port, set it as unknown
112		if($item['label'] == '' && $item['comment'] == '') {
113			$item['label'] = '?';
114			$item['comment'] = 'This port has not been documented.';
115			$item['color'] = '#333';
116		}
117
118		$fullcaption = "<div class=\'title\'>" . $opt['name'] . " Port $port</div>";
119		$fullcaption .= "<div class=\'content\'>";
120		$fullcaption .= "<table><tr><th>Label:</th><td>" . $item['label'] . "</td></tr>";
121		$fullcaption .= "<tr><th>Comment:</th><td>" . $item['comment'] . "</td></tr><table></div>";
122
123		$group = floor($position/$opt['groups']);
124
125		// Ethernet port image, with #STRINGS# to replace later
126		$iRatio = $imagewidth / $imageheight;
127		$iWidth = $imagewidth / 17;
128		$iHeight = $imageheight / 2.85;
129
130		$iBorderLeft = ( $iWidth / 13 ) * $position;
131		$iBorderTop = ( $iHeight / 2.8 ) * $row;
132		if( $position > 5 ){
133			$iBorderLeft += $iWidth * 0.3;
134		}
135		if( $row > 0 ){
136			$iBorderTop -= $iHeight * 0.14;
137		}
138
139		$iPosX = ( ( $iWidth * $position ) + 2 * $iWidth ) + $iBorderLeft;
140		$iPosY = ( ( $iHeight * $row ) + 0.4 * $iHeight ) + $iBorderTop;
141
142		// for metallic conductors
143		$sConductors = '';
144		for( $i=0; $i<8; $i++ ){
145			$sConductors .= '<rect x="'.( ( $iPosX + ( $iWidth / 4 ) ) + ( ( $iWidth / 15 ) * $i ) ).'" y="'.( $iPosY + ( $iHeight / 2 ) ).'" width="'.( $iWidth / 32 ).'" height="'.( $iHeight / 15 ).'" fill="#ffff00"/>';
146		}
147
148		$image = '<g onmousemove="patchpanel_show_tooltip(evt, \'#REPLACECAPTION#\')" onmouseout="patchpanel_hide_tooltip()">
149			<rect x="'.$iPosX.'" y="'.$iPosY.'" width="'.$iWidth.'" height="'.$iHeight.'" fill="#REPLACECOLOR#"/>
150			<rect x="'.$iPosX.'" y="'.$iPosY.'" width="'.$iWidth.'" height="'.( $iHeight / 2.65 ).'" stroke-width="'.( $iRatio / 6 ).'" stroke="#000000" fill="#ffffff" ry="'.( $iRatio / 1.4 ).'" rx="'.( $iRatio / 1.5 ).'"/>
151			<text x="'.( $iPosX + ( $iWidth / 2 ) ).'" y="'.( $iPosY + ( $iHeight / 3.6 ) ).'" style="font-weight:bold;" text-anchor="middle" font-family="sans-serif" font-size="'.( $iRatio * 2.5 ).'" fill="#000000">#REPLACELABEL#</text>
152			<rect x="'.( $iPosX + ( $iWidth / 2.7 ) ).'" y="'.( $iPosY + ( $iHeight / 1.58 ) ).'" width="'.( $iWidth / 4 ).'" height="'.( $iHeight / 3.4 ).'" fill="#000000"/>
153			<rect x="'.( $iPosX + ( $iWidth / 3.5 ) ).'" y="'.( $iPosY + ( $iHeight / 1.58 ) ).'" width="'.( $iWidth / 2.35 ).'" height="'.( $iHeight / 4 ).'" fill="#000000"/>
154			<rect x="'.( $iPosX + ( $iWidth / 8 ) ).'" y="'.( $iPosY + ( $iHeight / 2 ) ).'" width="'.( $iWidth / 1.35 ).'" height="'.( $iHeight / 3 ).'" fill="#000000"/>
155			'.$sConductors.'
156			<text x="'.( $iPosX + ( $iWidth / 2 ) ).'" y="'.( $iPosY + ( $iHeight / 1.3 ) ).'" text-anchor="middle" font-family="sans-serif" font-size="'.( $iRatio * 2.6 ).'" fill="#ffffff">#REPLACEPORTNUMBER#</text>
157		</g>';
158
159		// Replace color, setting the default if one wasn't specified
160		if(!substr($item['color'],0,1) == "#") { $item['color'] = '#CCCCCC'; }
161		$image = str_replace("#REPLACECOLOR#", $item['color'], $image);
162
163		// Replace label
164		$image = str_replace("#REPLACELABEL#", $item['label'], $image);
165
166		// Replace caption
167		$image = str_replace("#REPLACECAPTION#", htmlspecialchars($fullcaption,ENT_QUOTES), $image);
168
169		// Add port number
170		$image = str_replace("#REPLACEPORTNUMBER#", $port, $image);
171
172		// Position the port
173		$image = str_replace("#REPLACEX#", 80+$position*43+$group*10, $image); // offset from edge+width of preceeding ports+group spacing
174		$image = str_replace("#REPLACEY#", 20+$row*66, $image);
175		return $image;
176	}
177
178	/*
179	 * Create output
180	 */
181	function render($mode, &$renderer, $opt) {
182		if($mode == 'metadata') return false;
183
184		$content = $opt['content'];
185		// clear any trailing or leading empty lines from the data set
186		$content = preg_replace("/[\r\n]*$/","",$content);
187		$content = preg_replace("/^\s*[\r\n]*/","",$content);
188
189		$items = array();
190
191		$csv_id = uniqid("csv_");
192		$csv = "Port,Label,Comment\n";
193
194		foreach (explode("\n",$content) as $line) {
195			$item = array();
196			if (!preg_match("/^\s*\d+/",$line)) { continue; } // skip lines that don't start with a port number
197
198			// split on whitespace, keep quoted strings together
199			$matchcount = preg_match_all('/"(?:\\.|[^\\"])*"|\S+/',$line,$matches);
200			if ($matchcount > 0) {
201				$item['port'] = $matches[0][0];
202				$item['label'] = htmlspecialchars(trim($matches[0][1], '"\''), ENT_QUOTES);
203				// If 3rd element starts with #, it's a color.  Otherwise part of the comment
204				if (substr($matches[0][2], 0, 1) == "#") {
205					$item['color'] = $matches[0][2];
206				} else {
207					$item['comment'] = $matches[0][2];
208				}
209				// Any remaining text is part of the comment.
210				for($x=3;$x<=$matchcount;$x++) {
211					$item['comment'] .= " ".( isset( $matches[0][$x] ) ? $matches[0][$x] : '' );
212				}
213				$csv .= '"' . $item['port'] . '","' . $item['label'] . '","' . trim($item['comment'], '"\' ') . '"' . "\n";
214				$item['comment'] = str_replace(array("\r","\n"), '', p_render('xhtml',p_get_instructions(trim($item['comment'], '"\'')),$info));
215				$items[$item['port']] = $item;
216			} else {
217				$renderer->doc .= 'Syntax error on the following line: <pre style="color:red">'.hsc($line)."</pre>\n";
218			}
219		}
220
221		// Calculate the size of the image and port spacing
222		$portsPerRow = ceil($opt['ports']/$opt['rows']);
223		$groups = ceil($portsPerRow/$opt['groups']);
224		$imagewidth = 80+$portsPerRow*43+$groups*10+60;
225		$imageheight = 20+$opt['rows']*66;
226
227		$renderer->doc .= '<div class="patchpanel">';
228		$renderer->doc .= '<div class="patchpanel_container">';
229
230		if( $opt['rotate'] ){
231			// Draw an outer SVG and transform the inner one
232			$renderer->doc .= "<div style='height:" . $imagewidth . "px; width:" . $imageheight . "px;'>";
233			$renderer->doc .= '<svg xmlns="http://www.w3.org/2000/svg" width="'.$imageheight.'px" height="'.$imagewidth.'px" viewbox="0 0 '.$imageheight.' '.$imagewidth.'" style="line-height:0px;width:'.$imageheight.'px;height:'.$imagewidth.'px;">'.
234				'<metadata>image/svg+xml</metadata>'.
235				'<g transform="rotate(90 0 '.$imageheight.') translate(-'.$imageheight.' 0)">';
236		} else {
237			$renderer->doc .= "<div style='height:" . $imageheight . "px; width:" . $imagewidth . "px;'>";
238			$renderer->doc .= '<svg xmlns="http://www.w3.org/2000/svg" width="'.$imagewidth.'px" height="'.$imageheight.'px" viewbox="0 0 '.$imagewidth.' '.$imageheight.'" style="line-height:0px;width:'.$imagewidth.'px;height:'.$imageheight.'px;">'.
239				'<metadata>image/svg+xml</metadata>';
240		}
241
242		// Draw a rounded rectangle for our patch panel
243		// grey for the patch panel, pjahn, 29.07.2014
244		if( $opt['rotate'] ){
245			$renderer->doc .=  '<rect stroke-width="5" fill="#808080" height="'.$imageheight.'px" width="'.$imagewidth.'px" x="0" y="0" rx="30" ry="30" />';
246		}else{
247			$renderer->doc .=  '<rect stroke-width="5" fill="#808080" height="100%" width="100%" x="0" y="0" rx="30" ry="30" />';
248		}
249
250		// original - color black for the panel
251		// Draw some mounting holes
252		$renderer->doc .= '<rect fill="#fff" x="20" y="20" width="30" height="17.6" ry="9" />';
253		$renderer->doc .= '<rect fill="#fff" x="' . ($imagewidth-20-30) . '" y="20" width="30" height="17.6" ry="9" />';
254		$renderer->doc .= '<rect fill="#fff" x="20" y="'. ($imageheight-20-17.6) .'" width="30" height="17.6" ry="9" />';
255		$renderer->doc .= '<rect fill="#fff" x="' . ($imagewidth-20-30) . '" y="' . ($imageheight-20-17.6) . '" width="30" height="17.6" ry="9" />';
256		// Add a label
257		$renderer->doc .= '<text transform="rotate(-90 70,' . $imageheight/2 . ') " text-anchor="middle" font-size="12" fill="#fff" y="' . $imageheight/2 . '" x="70">' . $opt['name'] . ' </text>';
258
259		if ($opt['switch']) {
260
261			if ($opt['switch'] == 1) {
262				$startPortEven = 1;
263				$startPortOdd = 2;
264			} else {
265                                // MaxWinterstein 03.02.2015 modify port positioning according to 3com switches (2 above 1)
266
267                                $startPortEven = 2;
268				$startPortOdd = 1;
269			}
270
271			for ($row=1; $row <= $opt['rows']; $row++) {
272				// swerner 29.07.2014 modify port positioning according to hp switches
273
274				if ($row % 2 == 0) {
275					$port=$startPortOdd;
276				} else {
277					$port=$startPortEven;
278				}
279				for ($position=1; $position <= $portsPerRow; $position++) {
280						$renderer->doc .= $this->ethernet_svg($row, $position, $port, $items[$port], $opt, $imagewidth, $imageheight);
281						$port=$port+2;
282				}
283				if ($row % 2 == 0) {
284					$startPortOdd = $startPortOdd+(2*$portsPerRow);
285				} else {
286					$startPortEven = $startPortEven+(2*$portsPerRow);
287				}
288			}
289
290		} else {
291			// std port drawing code */
292			for ($row=1; $row <= $opt['rows']; $row++) {
293
294				// Calculate the starting and ending ports for this row.
295				$startPort = 1+$portsPerRow*($row-1);
296				$endPort = $portsPerRow+$portsPerRow*($row-1);
297				if ($endPort > $opt['ports']) { $endPort = $opt['ports']; }
298
299				// Draw ethernet ports over the patch panel
300				for ($port=$startPort; $port <= $endPort ; $port++) {
301					$position = $port - $portsPerRow*($row-1);
302					$renderer->doc .= $this->ethernet_svg($row, $position, $port, $items[$port], $opt, $imagewidth, $imageheight);
303				}
304			}
305		}
306
307		if( $opt['rotate'] ){
308			$renderer->doc .= "</g>";
309		}
310		$renderer->doc .= "</svg>";
311		$renderer->doc .= "</div></div>";
312
313		// Button to show the CSV version
314		$renderer->doc .= "<div class='patchpanel_csv'><span onclick=\"this.innerHTML = patchpanel_toggle_vis(document.getElementById('$csv_id'),'block')?'Hide CSV &uarr;':'Show CSV &darr;';\">Show CSV &darr;</span>";
315		$renderer->doc .= "<pre style='display:none;' id='$csv_id'>$csv</pre>\n";
316		$renderer->doc .= "</div></div>";
317
318		// Make sure the tooltip div gets created
319		$renderer->doc .= '<script type="text/javascript">patchpanel_create_tooltip_div();</script>';
320
321		// Add a script that creates the tooltips
322		$renderer->doc .= '<script type="text/javascript">//<![CDATA[
323			function patchpanel_show_tooltip(evt, text) {
324				tooltip = jQuery("#patchpanel_tooltip");
325				tooltip.html(text);
326				tooltip.css({left: evt.clientX+10, top: evt.clientY+10, display: "block" });
327			}
328			function patchpanel_hide_tooltip() {
329				jQuery("#patchpanel_tooltip").css("display", "none");
330			}
331			//]]>
332		</script>';
333
334		return true;
335	}
336}
337