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 ↑':'Show CSV ↓';\">Show CSV ↓</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