1<?php 2/** 3 * Info Plugin: switchpanel 4 * 5 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 6 * @author Bertrand Fruchet <bertrand@greenitsolutions.fr> 7 * @author Emmanuel Hidalgo <manu@greenitsolutions.fr> 8 * @author Benoit Moreau <benoit+github@virtit.fr> 9 * 10 * Based on the dokuwiki-plugin-patchpanel plugin (https://github.com/grantemsley/dokuwiki-plugin-patchpanel) by Grant Emsley <grant@emsley.ca> 11 */ 12// must be run within Dokuwiki 13if(!defined('DOKU_INC')) die(); 14 15/** 16 * All DokuWiki plugins to extend the parser/rendering mechanism 17 * need to inherit from this class 18 */ 19class syntax_plugin_switchpanel extends DokuWiki_Syntax_Plugin { 20 private $_sName = "switchpanel"; 21 private $_oTagsContent = array( 'line'=>array( 'number', 'color', 'case', 'labelLeft', 'colorLabelLeft', 'labelRight', 'colorLabelRight', 'labelBgColor', 'labelTxtColor', 'leftLedColor', 'rightLedColor' ), 'text'=>array( 'bgColor', 'color', 'size', 'brColor', 'brRadius' ), 'heightBar'=>array( 'height' ) ); 22 private $_oTagsItemsContent = array( 'line_items'=>array( 'color', 'text', 'link', 'case', 'target', 'textlink' , 'labelBgColor', 'labelTxtColor', 'leftLedColor', 'rightLedColor' ) ); 23 24 function getType(){ return 'substition'; } 25 function getSort(){ return 155; } 26 function getPType(){ return 'block'; } 27 28 function connectTo($mode){ 29 $this->Lexer->addSpecialPattern( "<switchpanel[^>]*>.*?(?:<\/switchpanel>)", $mode, 'plugin_switchpanel' ); 30 } 31 32 /** 33 * Handle the match 34 * 35 * @param string $match The text matched by the patterns 36 * @param int $state The lexer state for the match 37 * @param int $pos The character position of the matched text 38 * @param Doku_Handler $handler The Doku_Handler object 39 * @return array Return an array with all data you want to use in render 40 */ 41 function handle($match, $state, $pos, Doku_Handler $handler){ 42 43 // remove "</switchpanel>" from the match 44 $match = trim( substr( $match, 0, ( strlen( $this->_sName ) + 3 ) * -1 ) ); 45 46 // default options 47 $opt = array( 48 'logo'=>DOKU_BASE.'lib/plugins/switchpanel/images/greenIt.svg', 49 'logoLink'=>'http://www.greenitsolutions.fr/', 50 'labelBgColor'=>'#fff', 51 'labelTxtColor'=>'#000', 52 'leftLedColor'=>'#666666', 53 'rightLedColor'=>'#666666', 54 'target'=>'_blank', 55 'showEars'=>true, 56 'labelLeft'=>'', 57 'labelRight'=>'', 58 'colorLabelLeft'=>'#fff', 59 'colorLabelRight'=>'#fff', 60 'case'=>'rj45', 61 'group'=>0, 62 'groupSeparatorWidth'=>18, 63 'color'=>'#ccc', 64 'elementWidth'=>36, 65 'elementHeight'=>45, 66 'elementSeparatorWidth'=>5, 67 'elementSeparatorHeight'=>5, 68 'textSize'=>20, 69 'textColor'=>'#fff', 70 'textBgColor'=>'', 71 'textBrColor'=>'', 72 'textBrRadius'=>'', 73 'barHeight'=>5, 74 'screwHeightSpace'=>60, 75 'screwHeight'=>15, 76 'screwWidth'=>20, 77 'screwColor'=>'#fff', 78 'switchColor'=>'#808080', 79 'content'=>array() 80 ); 81 82 // recovered the first line and content 83 $iPosFirstLine = stripos( $match, "\n" ); 84 $sFirstLines = substr( $match, 0, $iPosFirstLine ); 85 $sContent = trim( substr( $match, $iPosFirstLine ) ); 86 unset( $match ); 87 88 // treatment of first-line 89 $sFirstLines = trim( substr( $sFirstLines, strlen( $this->_sName ) + 1 ) ); 90 $sFirstLines = trim( rtrim( $sFirstLines, '>' ) ); 91 $oAttributs = explode( ' ', $sFirstLines ); 92 foreach($oAttributs as $sKeyVal ){ 93 if( trim( $sKeyVal ) == '' || stripos( $sKeyVal, '=' ) === false ){ 94 continue; 95 } 96 list( $sKey, $sVal ) = explode( '=', $sKeyVal ); 97 $sVal = trim( $sVal, '"' ); 98 if( $sKey == 'content' || !array_key_exists( $sKey, $opt ) ){ 99 continue; 100 } 101 102 // change a default value 103 if( is_bool( $opt[ $sKey ] ) ){ 104 $opt[ $sKey ] = ( strtolower( $sVal ) == 'true' ); 105 }else if( is_int( $opt[ $sKey ] ) ){ 106 $opt[ $sKey ] = intval( $sVal ); 107 }else{ 108 $opt[ $sKey ] = $sVal; 109 } 110 } 111 112 // anonymous function recovery options 113 $fGetOptions = function( $sOptions, $oFilters = NULL ){ 114 $oOptions = array(); 115 do{ 116 $sOptions = trim( $sOptions, ',' ); 117 if( $sOptions == '' ){ 118 break; 119 } 120 $iPosStop = stripos( $sOptions, '=' ); 121 if( $sOptions === false ){ 122 break; 123 } 124 $sKey = trim( substr( $sOptions, 0, $iPosStop ) ); 125 $sOptions = trim( substr( $sOptions, $iPosStop + 1 ) ); 126 127 $iPosStop = stripos( $sOptions, ',' ); 128 if( $iPosStop === false ){ 129 $iPosStop = strlen( $sOptions ); 130 } 131 $sValue = trim( substr( $sOptions, 0, $iPosStop ) ); 132 133 if( substr( $sOptions, 0, 1 ) == '"' ){ 134 $iPosStop = stripos( $sOptions, '"', 1 ); 135 $sValue = trim( substr( $sOptions, 0, $iPosStop ), '"' ); 136 $iPosStop++; 137 } 138 139 // control of coherence options 140 if( !is_null( $oFilters ) && !in_array( $sKey, $oFilters ) ){ 141 142 // error, the option is not found 143 echo 'Syntax error : the option is not found : <pre style="color:red"> key : '.$sKey.', value : '.$sOptions."</pre>\n"; 144 $sOptions = trim( substr( $sOptions, $iPosStop ) ); 145 continue; 146 } 147 148 // recording option 149 $oOptions[ $sKey ] = $sValue; 150 $sOptions = trim( substr( $sOptions, $iPosStop ) ); 151 152 }while( true ); 153 154 return $oOptions; 155 }; 156 157 // analysis and processing of content 158 $oContent = array(); 159 $oLines = explode( "\n", $sContent ); 160 $sContext = ''; 161 foreach( $oLines as $sLine ){ 162 163 // recovery of the line 164 $sLine = trim( $sLine ); 165 if( $sLine == '' || substr( $sLine, 0, 1 ) == '#' ){ 166 continue; 167 } 168 169 // determine if the context has to be taken into account 170 if( strlen( $sLine ) > 2 && substr( $sLine, 0, 2 ) == '==' ){ 171 172 // recovered and the control context 173 $sContext = trim( substr( $sLine, 2 ) ); 174 $iPosSep = stripos( $sLine, ':' ); 175 if( $iPosSep !== false ){ 176 $sContext = trim( substr( $sLine, 2, $iPosSep - 2 ) ); 177 } 178 if( !array_key_exists( $sContext, $this->_oTagsContent ) ){ 179 180 // error, the context was not found 181 echo 'Syntax error : the context was not found : <pre style="color:red"> context : '.$sContext.', line : '.$sLine."</pre>\n"; 182 continue; 183 } 184 185 // if there are options 186 $oOptions = array(); 187 if( $iPosSep !== false ){ 188 $sOptions = substr( $sLine, $iPosSep + 1 ); 189 $oOptions = $fGetOptions( $sOptions, $this->_oTagsContent[ $sContext ] ); 190 } 191 192 // adding the new context 193 $oContent[] = array( 'type'=>$sContext, 'options'=>$oOptions, 'data'=>NULL ); 194 continue; 195 } 196 197 // recovery of the element 198 $oElement = &$oContent[ count( $oContent ) - 1 ]; 199 200 // if the line contains options 201 $oOptions = NULL; 202 $iPosSep = stripos( $sLine, ':' ); 203 if( $iPosSep !== false && array_key_exists( $oElement[ 'type' ].'_items', $this->_oTagsItemsContent ) ){ 204 $sOptions = trim( trim( substr( $sLine, $iPosSep ), ':' ) ); 205 $sLine = substr( $sLine, 0, $iPosSep ); 206 $oOptions = $fGetOptions( $sOptions, $this->_oTagsItemsContent[ $oElement[ 'type' ].'_items' ] ); 207 } 208 209 // get last context 210 if( $oElement[ 'type' ] == 'line' ){ 211 if( $oElement[ 'data' ] == NULL ){ 212 $oElement[ 'data' ] = array(); 213 } 214 215 $oInfos = explode( ',', $sLine ); 216 $oLine = array( 'number'=>$oInfos[ 0 ] ); 217 if( count( $oInfos ) > 1 ){ 218 $oLine[ 'label' ] = $oInfos[ 1 ]; 219 } 220 if( count( $oInfos ) > 2 ){ 221 $oLine[ 'title' ] = $oInfos[ 2 ]; 222 } 223 224 // propagation properties 225 $oLine[ 'options' ] = array(); 226 foreach( array( 'color', 'case', 'labelLeft', 'colorLabelLeft', 'labelRight', 'colorLabelRight', 'labelBgColor', 'labelTxtColor', 'rightLedColor', 'leftLedColor' ) as $sProp ){ 227 if( !isset( $oElement[ 'options' ][ $sProp ] ) ){ 228 $oElement[ 'options' ][ $sProp ] = $opt[ $sProp ]; 229 $oLine[ 'options' ][ $sProp ] = $oElement[ 'options' ][ $sProp ]; 230 } 231 $oLine[ 'options' ][ $sProp ] = $oElement[ 'options' ][ $sProp ]; 232 } 233 if( !is_null( $oOptions ) ){ 234 foreach( $oOptions as $sKey=>$oValue ){ 235 $oLine[ 'options' ][ $sKey ] = $oValue; 236 } 237 } 238 $oElement[ 'data' ][ intval( $oInfos[ 0 ] ) ] = $oLine; 239 240 }else if( $oElement[ 'type' ] == 'text' ){ 241 $oElement[ 'data' ] .= $sLine; 242 } 243 } 244 245 // update content 246 $opt[ 'content' ] = $oContent; 247 return $opt; 248 } 249 250 /* 251 * Create output 252 */ 253 function render($mode, Doku_Renderer $renderer, $opt) { 254 if( $mode == 'metadata' ){ return false; } 255 256 // determines the maximum number of elements in width & 257 // determine the position of the minimum and maximum index 258 $iNbrElementsWidth = 0; 259 $oElements = $opt[ 'content' ]; 260 foreach( $oElements as &$oElement ){ 261 if( $oElement[ 'type' ] != 'line' ){ 262 continue; 263 } 264 $MinIndex = 1000; 265 $iMaxIndex = 0; 266 if( isset( $oElement[ 'data' ] ) ){ 267 foreach( $oElement[ 'data' ] as $iLine=>$oLine ){ 268 if( $MinIndex > $iLine ){ 269 $MinIndex = $iLine; 270 } 271 if( $iMaxIndex < $iLine ){ 272 $iMaxIndex = $iLine; 273 } 274 } 275 } 276 $iDiff = ( $iMaxIndex - $MinIndex ) + 1; 277 if( $iNbrElementsWidth < $iDiff ){ 278 $iNbrElementsWidth = $iDiff; 279 } 280 if( isset( $oElement[ 'options' ][ 'number' ] ) && $oElement[ 'options' ][ 'number' ] > $iNbrElementsWidth ){ 281 $iNbrElementsWidth = $oElement[ 'options' ][ 'number' ]; 282 } 283 284 // re-index elements 285 if( isset( $oElement[ 'data' ] ) ){ 286 ksort( $oElement[ 'data' ] ); 287 $oTmpData = array(); 288 for( $i=$MinIndex; $i<=$iMaxIndex; $i++ ){ 289 $oTmpData[ $i ] = isset( $oElement[ 'data' ][ $i ] ) ? 290 $oElement[ 'data' ][ $i ] : 291 array( 'number'=>$i, 'label'=>'', 292 'options'=>array( 'color'=>$oElement[ 'options' ][ 'color' ], 'case'=>$oElement[ 'options' ][ 'case' ], 293 'labelLeft'=>$oElement[ 'options' ][ 'labelLeft' ], 'colorLabelLeft'=>$oElement[ 'options' ][ 'colorLabelLeft' ], 294 'labelRight'=>$oElement[ 'options' ][ 'labelRight' ], 'colorLabelRight'=>$oElement[ 'options' ][ 'colorLabelRight' ], 'labelBgColor'=>$oElement[ 'options' ][ 'labelBgColor' ], 'labelTxTColor'=>$oElement[ 'options' ][ 'labelTxtColor' ], 'leftLedColor'=>$oElement[ 'options' ][ 'leftLedColor' ], 'rightLedColor'=>$oElement[ 'options' ][ 'rightLedColor' ] ) ); 295 } 296 $oData = array(); 297 foreach( $oTmpData as $oLine ){ 298 $oData[ count( $oData ) ] = $oLine; 299 } 300 $oElement[ 'data' ] = $oData; 301 } 302 } 303 304 // if there are groups 305 $iWidthGroup = 0; 306 if( $opt[ 'group' ] > 0 ){ 307 $iWidthGroup = floor( $iNbrElementsWidth / $opt[ 'group' ] ) * $opt[ 'groupSeparatorWidth' ]; 308 if( $iNbrElementsWidth % $opt[ 'group' ] == 0 ){ 309 $iWidthGroup -= $opt[ 'groupSeparatorWidth' ]; 310 } 311 } 312 313 // calculates the width 314 $iGroup = $opt[ 'group' ]; 315 $iWidthSvg = $iWidthGroup + 316 ( $opt[ 'showEars' ] ? ( $opt[ 'elementWidth' ] * 4 ) : ( $opt[ 'elementSeparatorWidth' ] * 2 ) ) + // if show Ears 317 ( $iNbrElementsWidth * $opt[ 'elementWidth' ] ) + 318 ( $iNbrElementsWidth > 1 ? ( ( $iNbrElementsWidth - 1 ) * $opt[ 'elementSeparatorWidth' ] ) : 0 ); 319 320 // calculates the height 321 $iHeightSvg = 0; 322 foreach( $oElements as &$oElement ){ 323 $iHeightSvg += $opt[ 'elementSeparatorHeight' ]; 324 if( $oElement[ 'type' ] == 'line' ){ 325 $iHeightSvg += $opt[ 'elementHeight' ]; 326 }else if( $oElement[ 'type' ] == 'text' ){ 327 if( isset( $oElement[ 'options' ][ 'size' ] ) ){ 328 $iHeightSvg += $oElement[ 'options' ][ 'size' ]; 329 }else{ 330 $iHeightSvg += $opt[ 'textSize' ]; 331 } 332 }else if( $oElement[ 'type' ] == 'heightBar' ){ 333 $iBarHeight = $opt[ 'barHeight' ]; 334 if( isset( $oElement[ 'options' ][ 'height' ] ) ){ 335 $iBarHeight = $oElement[ 'options' ][ 'height' ]; 336 } 337 $iHeightSvg += $iBarHeight; 338 } 339 } 340 341 // the last element 342 if( count( $oElements ) > 0 ){ 343 $iHeightSvg += $opt[ 'elementSeparatorHeight' ]; 344 } 345 346 $sPathTemplateClass = dirname( __FILE__ ).DIRECTORY_SEPARATOR.'tpl'.DIRECTORY_SEPARATOR;; 347 $fDrawCase = function( $oCase, $iX, $iY ) use ( $opt, $sPathTemplateClass ){ 348 349 // search the associated class 350 $sCase = $oCase[ 'options' ][ 'case' ]; 351 if( !file_exists( $sPathTemplateClass.'switchpanel.case.'.$sCase.'.class.php' ) ){ 352 $sCase = $opt[ 'case' ]; 353 } 354 355 require_once( $sPathTemplateClass.'switchpanel.case.'.$sCase.'.class.php' ); 356 $sClassName = 'switchpanel_case_'.$sCase; 357 358 return $sClassName::getSvg( $oCase, $iX, $iY, $opt ); 359 }; 360 361 // construction of SVG 362 $sSvg = '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="'.$iHeightSvg.'px" width="'.$iWidthSvg.'px">'. 363 '<metadata>image/svg+xml</metadata>'. 364 '<rect fill="'.$opt[ 'switchColor' ].'" height="'.$iHeightSvg.'px" width="'.$iWidthSvg.'px" x="0" y="0" rx="'.( $opt[ 'showEars' ] ? 10 : 5 ).'" ry="'.( $opt[ 'showEars' ] ? 10 : 5 ).'" />'; 365 366 // inclusion of the logo and bolts 367 if( $opt[ 'showEars' ] ){ 368 require_once( $sPathTemplateClass.'switchpanel.screw.none.class.php' ); 369 if( !in_array( $opt[ 'logo' ], array( '', 'none' ), true ) ){ 370 if( $opt[ 'logoLink' ] != '' ){ 371 $sSvg .= '<a xlink:href="'.$opt[ 'logoLink' ].'" target="'.( $opt[ 'target' ] ).'" style="text-decoration:none">'; 372 } 373 $sSvg .= '<image x="'.( ( $opt[ 'elementWidth' ] * 2 ) - ( $opt[ 'elementSeparatorWidth' ] + 30 ) ).'" y="'.$opt[ 'elementSeparatorHeight' ].'" width="30" height="30" xlink:href="'.$opt[ 'logo' ].'" />'; 374 if( $opt[ 'logoLink' ] != '' ){ 375 $sSvg .= '</a>'; 376 } 377 } 378 $iHeightScrew = $iHeightSvg - ( ( $opt[ 'elementSeparatorHeight' ] * 2 ) + $opt[ 'screwHeight' ] ); 379 $iNbrScrews = floor( $iHeightScrew / $opt[ 'screwHeightSpace' ] ); 380 if( $iNbrScrews == 0 ){ 381 $iNbrScrews++; 382 } 383 $iHeightScrew = $iHeightScrew / $iNbrScrews; 384 $iNbrScrews++; 385 if( $iNbrScrews == 1 ){ 386 $iNbrScrews++; 387 $iHeightScrew = $iHeightSvg - ( ( $opt[ 'elementSeparatorHeight' ] * 2 ) + $opt[ 'screwHeight' ] ); 388 } 389 390 $iPosHeightScrew = $opt[ 'elementSeparatorHeight' ]; 391 for( $i=1; $i<=$iNbrScrews; $i++ ){ 392 $sSvg .= switchpanel_screw_none::getSvg( $opt[ 'elementSeparatorWidth' ], $iPosHeightScrew, $opt ). 393 switchpanel_screw_none::getSvg( ( $iWidthSvg - $opt[ 'elementSeparatorWidth' ] - $opt[ 'screwWidth' ] ), $iPosHeightScrew, $opt ); 394 $iPosHeightScrew += $iHeightScrew; 395 } 396 } 397 398 // drawing of the elements 399 $iIndexY = 0; 400 $bFirstLine = true; 401 foreach( $oElements as &$oElement ){ 402 $iIndexX = $opt[ 'showEars' ] ? ( $opt[ 'elementWidth' ] * 2 ) : $opt[ 'elementSeparatorWidth' ]; 403 $iIndexY += $opt[ 'elementSeparatorHeight' ]; 404 if( $oElement[ 'type' ] == 'line' ){ 405 $oCases = $oElement[ 'data' ]; 406 for( $i=0; $i<$iNbrElementsWidth; $i++ ){ 407 $oCase = array( 'case'=>'none' ); 408 if( isset( $oCases[ $i ] ) ){ 409 $oCase = $oCases[ $i ]; 410 } 411 $sSvg .= $fDrawCase( $oCase, $iIndexX, $iIndexY ); 412 if( $i - 1 < $iNbrElementsWidth ){ 413 $iIndexX += $opt[ 'elementSeparatorWidth' ]; 414 } 415 $iIndexX += $opt[ 'elementWidth' ]; 416 417 // if there are groups 418 if( $opt[ 'group' ] > 0 && ( $i + 1 < $iNbrElementsWidth ) && ( ( $i + 1 ) % $iGroup ) == 0 ){ 419 $iIndexX += $opt[ 'groupSeparatorWidth' ]; 420 } 421 } 422 423 if( $opt[ 'showEars' ] ){ 424 if( isset( $oElement[ 'options' ][ 'labelLeft' ] ) && trim( $oElement[ 'options' ][ 'labelLeft' ] ) != '' ){ 425 $iMoveX = 0; 426 if( $bFirstLine && !in_array( $opt[ 'logo' ], array( '', 'none' ), true ) && $iIndexY == $opt[ 'elementSeparatorHeight' ] ){ 427 $iMoveX = $opt[ 'elementWidth' ] - $opt[ 'elementSeparatorWidth' ]; 428 } 429 $sSvg .= '<text x="'.( $opt[ 'elementWidth' ] + ( $opt[ 'elementWidth' ] / 2 ) - $opt[ 'elementSeparatorWidth' ] - $iMoveX ).'" y="'.( $iIndexY + ( $opt[ 'elementHeight' ] / 1.5 ) + $opt[ 'elementSeparatorHeight' ] ).'" '. 430 'style="fill:'.$oElement[ 'options' ][ 'colorLabelLeft' ].';" font-size="22" '. 431 'text-anchor="middle">'.$oElement[ 'options' ][ 'labelLeft' ].'</text>'; 432 } 433 if( isset( $oElement[ 'options' ][ 'labelRight' ] ) && trim( $oElement[ 'options' ][ 'labelRight' ] ) != '' ){ 434 $sSvg .= '<text x="'.( $iIndexX + ( $opt[ 'elementWidth' ] / 2 ) ).'" y="'.( $iIndexY + ( $opt[ 'elementHeight' ] / 1.5 ) + $opt[ 'elementSeparatorHeight' ] ).'" '. 435 'style="fill:'.$oElement[ 'options' ][ 'colorLabelRight' ].';" font-size="22" '. 436 'text-anchor="middle">'.$oElement[ 'options' ][ 'labelRight' ].'</text>'; 437 } 438 } 439 $iIndexY += $opt[ 'elementHeight' ]; 440 $bFirstLine = false; 441 }else if( $oElement[ 'type' ] == 'text' ){ 442 require_once( $sPathTemplateClass.'switchpanel.text.none.class.php' ); 443 $sSvg .= switchpanel_text_none::getSvg( $oElement, $iIndexX, $iIndexY, $opt, $iWidthSvg ); 444 $iHeightText = $opt[ 'textSize' ]; 445 if( isset( $oElement[ 'options' ][ 'size' ] ) ){ 446 $iHeightText = $oElement[ 'options' ][ 'size' ]; 447 } 448 $iIndexY += $iHeightText; 449 }else if( $oElement[ 'type' ] == 'heightBar' ){ 450 $iBarHeight = $opt[ 'barHeight' ]; 451 if( isset( $oElement[ 'options' ][ 'height' ] ) ){ 452 $iBarHeight = $oElement[ 'options' ][ 'height' ]; 453 } 454 $iIndexY += $iBarHeight; 455 } 456 } 457 $sSvg .= '</svg>'; 458 459 // generation rendering 460 if ($mode != 'odt') { 461 $renderer->doc .= '<div style="overflow-x:auto;">'.$sSvg.'</div>'; 462 } else { 463 // When exporting to ODT format always make the switchpannel as wide 464 // as the whole page without margins (but keep the width/height relation!). 465 $widthInCm = $renderer->_getAbsWidthMindMargins(); 466 $heightInCm = $widthInCm * ($iHeightSvg/$iWidthSvg); 467 $renderer->_addStringAsSVGImage($sSvg, $widthInCm.'cm', $heightInCm.'cm'); 468 } 469 return true; 470 } 471} 472