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